diff --git a/book/.nojekyll b/book/.nojekyll new file mode 100644 index 0000000000..f17311098f --- /dev/null +++ b/book/.nojekyll @@ -0,0 +1 @@ +This file makes sure that Github Pages doesn't process mdBook's output. diff --git a/book/404.html b/book/404.html new file mode 100644 index 0000000000..926e14de31 --- /dev/null +++ b/book/404.html @@ -0,0 +1,226 @@ + + + + + + Page not found - Cross-platform Rust Components + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Document not found (404)

+

This URL is invalid, sorry. Please use the navigation bar or search to continue.

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + diff --git a/book/FontAwesome/css/font-awesome.css b/book/FontAwesome/css/font-awesome.css new file mode 100644 index 0000000000..540440ce89 --- /dev/null +++ b/book/FontAwesome/css/font-awesome.css @@ -0,0 +1,4 @@ +/*! + * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.7.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.7.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.7.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.7.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-resistance:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}.fa-gitlab:before{content:"\f296"}.fa-wpbeginner:before{content:"\f297"}.fa-wpforms:before{content:"\f298"}.fa-envira:before{content:"\f299"}.fa-universal-access:before{content:"\f29a"}.fa-wheelchair-alt:before{content:"\f29b"}.fa-question-circle-o:before{content:"\f29c"}.fa-blind:before{content:"\f29d"}.fa-audio-description:before{content:"\f29e"}.fa-volume-control-phone:before{content:"\f2a0"}.fa-braille:before{content:"\f2a1"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asl-interpreting:before,.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-deafness:before,.fa-hard-of-hearing:before,.fa-deaf:before{content:"\f2a4"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-signing:before,.fa-sign-language:before{content:"\f2a7"}.fa-low-vision:before{content:"\f2a8"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-pied-piper:before{content:"\f2ae"}.fa-first-order:before{content:"\f2b0"}.fa-yoast:before{content:"\f2b1"}.fa-themeisle:before{content:"\f2b2"}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:"\f2b3"}.fa-fa:before,.fa-font-awesome:before{content:"\f2b4"}.fa-handshake-o:before{content:"\f2b5"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-o:before{content:"\f2b7"}.fa-linode:before{content:"\f2b8"}.fa-address-book:before{content:"\f2b9"}.fa-address-book-o:before{content:"\f2ba"}.fa-vcard:before,.fa-address-card:before{content:"\f2bb"}.fa-vcard-o:before,.fa-address-card-o:before{content:"\f2bc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-circle-o:before{content:"\f2be"}.fa-user-o:before{content:"\f2c0"}.fa-id-badge:before{content:"\f2c1"}.fa-drivers-license:before,.fa-id-card:before{content:"\f2c2"}.fa-drivers-license-o:before,.fa-id-card-o:before{content:"\f2c3"}.fa-quora:before{content:"\f2c4"}.fa-free-code-camp:before{content:"\f2c5"}.fa-telegram:before{content:"\f2c6"}.fa-thermometer-4:before,.fa-thermometer:before,.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-thermometer-2:before,.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:"\f2cb"}.fa-shower:before{content:"\f2cc"}.fa-bathtub:before,.fa-s15:before,.fa-bath:before{content:"\f2cd"}.fa-podcast:before{content:"\f2ce"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-times-rectangle:before,.fa-window-close:before{content:"\f2d3"}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:"\f2d4"}.fa-bandcamp:before{content:"\f2d5"}.fa-grav:before{content:"\f2d6"}.fa-etsy:before{content:"\f2d7"}.fa-imdb:before{content:"\f2d8"}.fa-ravelry:before{content:"\f2d9"}.fa-eercast:before{content:"\f2da"}.fa-microchip:before{content:"\f2db"}.fa-snowflake-o:before{content:"\f2dc"}.fa-superpowers:before{content:"\f2dd"}.fa-wpexplorer:before{content:"\f2de"}.fa-meetup:before{content:"\f2e0"}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto} diff --git a/book/FontAwesome/fonts/FontAwesome.ttf b/book/FontAwesome/fonts/FontAwesome.ttf new file mode 100644 index 0000000000..35acda2fa1 Binary files /dev/null and b/book/FontAwesome/fonts/FontAwesome.ttf differ diff --git a/book/FontAwesome/fonts/fontawesome-webfont.eot b/book/FontAwesome/fonts/fontawesome-webfont.eot new file mode 100644 index 0000000000..e9f60ca953 Binary files /dev/null and b/book/FontAwesome/fonts/fontawesome-webfont.eot differ diff --git a/book/FontAwesome/fonts/fontawesome-webfont.svg b/book/FontAwesome/fonts/fontawesome-webfont.svg new file mode 100644 index 0000000000..855c845e53 --- /dev/null +++ b/book/FontAwesome/fonts/fontawesome-webfont.svg @@ -0,0 +1,2671 @@ + + + + +Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 + By ,,, +Copyright Dave Gandy 2016. All rights reserved. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/book/FontAwesome/fonts/fontawesome-webfont.ttf b/book/FontAwesome/fonts/fontawesome-webfont.ttf new file mode 100644 index 0000000000..35acda2fa1 Binary files /dev/null and b/book/FontAwesome/fonts/fontawesome-webfont.ttf differ diff --git a/book/FontAwesome/fonts/fontawesome-webfont.woff b/book/FontAwesome/fonts/fontawesome-webfont.woff new file mode 100644 index 0000000000..400014a4b0 Binary files /dev/null and b/book/FontAwesome/fonts/fontawesome-webfont.woff differ diff --git a/book/FontAwesome/fonts/fontawesome-webfont.woff2 b/book/FontAwesome/fonts/fontawesome-webfont.woff2 new file mode 100644 index 0000000000..4d13fc6040 Binary files /dev/null and b/book/FontAwesome/fonts/fontawesome-webfont.woff2 differ diff --git a/book/adding-docs.html b/book/adding-docs.html new file mode 100644 index 0000000000..ae65fa8e4f --- /dev/null +++ b/book/adding-docs.html @@ -0,0 +1,241 @@ + + + + + + Adding to these documents - Cross-platform Rust Components + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Developing documentation

+

The documentation in this repository pertains to the application-services library, primarily the sync and storage components, firefox account client and the nimbus-sdk experimentation client.

+

The markdown is converted to static HTML using mdbook. To add a new document, you need to add it to the SUMMARY.md file which produces the sidebar table of contents.

+

Building documentation

+

Building the narrative (book) documentation

+

The mdbook crate is required in order to build the documentation:

+
cargo install mdbook mdbook-mermaid mdbook-open-on-gh
+
+

The repository documents are be built with:

+
./tools/build.docs.sh
+
+

The built documentation is saved in build/docs/book.

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + diff --git a/book/adr/0000-use-markdown-architectural-decision-records.html b/book/adr/0000-use-markdown-architectural-decision-records.html new file mode 100644 index 0000000000..b0f2cce071 --- /dev/null +++ b/book/adr/0000-use-markdown-architectural-decision-records.html @@ -0,0 +1,259 @@ + + + + + + ADR-0000 - Cross-platform Rust Components + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Use Markdown Architectural Decision Records

+

Context and Problem Statement

+

We want to record architectural decisions made in this project. +Which format and structure should these records follow?

+

Considered Options

+ +

Decision Outcome

+

Chosen option: "MADR 2.1.2", because

+
    +
  • Implicit assumptions should be made explicit. +Design documentation is important to enable people understanding the decisions later on. +See also A rational design process: How and why to fake it.
  • +
  • The MADR format is lean and fits our development style.
  • +
  • The MADR structure is comprehensible and facilitates usage & maintenance.
  • +
  • The MADR project is vivid.
  • +
  • Version 2.1.2 is the latest one available when starting to document ADRs.
  • +
+ + +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + diff --git a/book/adr/0001-update-logins-api.html b/book/adr/0001-update-logins-api.html new file mode 100644 index 0000000000..cec6446c19 --- /dev/null +++ b/book/adr/0001-update-logins-api.html @@ -0,0 +1,373 @@ + + + + + + ADR-0001 - Cross-platform Rust Components + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Update Logins API

+
    +
  • Status: accepted
  • +
  • Date: 2021-06-17
  • +
+

Technical Story: #4101

+

Context and Problem Statement

+

We no longer want to depend on SQLCipher and want to use SQLite directly for build complexity and concerns over the long term future of the rust bindings. The encryption approach taken by SQLCipher means that in practice, the entire database is decrypted at startup, even if the logins functionality is not interacted with, defeating some of the benefits of using an encrypted database.

+

The per-field encryption in autofill, which we are planning to replicate in logins, separates the storage and encryption logic by limiting the storage layer to the management of encrypted data. Applying this approach in logins will break the existing validation and deduping code so we need a way to implement per-field encryption while supporting the validation and de-duping behavior.

+

Decision Drivers

+
    +
  • Addressing previously identified deficiencies in the logins API while we are breaking the API for the encryption work
  • +
  • Continuing to support the existing logins validation and deduping logic
  • +
  • Avoiding the implementation of new security approaches that may require additional time and security resources
  • +
  • Establishing a standard encyrption approach across components
  • +
+

Considered Options

+
    +
  • Option 1 - Reduce the API functions that require the encryption key and pass the key to the remaining functions
  • +
  • Option 2 - Keep the general shape of the API that is in place now - the app can pass the encryption key at any time to "unlock" the API, and re-lock it at any time, but the API in its entirety is only available when unlocked
  • +
+

Decision Outcome

+

Chosen Option: "Reduce the API functions that require the encryption key and pass the key to the remaining functions" because it will not require a security review as similar to the approach we have established in the codebase.

+

Pros and Cons of the Options

+

Option 1 - Reduce the API functions that require the encryption key and pass the key to the remaining functions

+
    +
  • +

    Description

    +

    Currently the below logins API functions would require the per-field encryption key:

    + +

    Propsed changes:

    +
      +
    • Combine the add and update functions into a new add_or_update function +
        +
      • This will allow the removal of consumer code that distinguishes when a login record should be created or updated
      • +
      • Note: This function needs the encryption key for the fixup and deduping logic and for continued support of the accurate population of the time_password_changed field
      • +
      +
    • +
    • Pass the per-field encryption key to the import_multiple function +
        +
      • This function will be removed once the Fennec to Fenix migration period ends
      • +
      +
    • +
    • Remove both the potential_dupes_ignoring_username and check_valid_with_no_dupes from the API +
        +
      • Neither function is called in Firefox iOS
      • +
      • Android Components uses both to provide validation and de-duping before logins are added or updated so we can eliminate the need to externalize these functions by replicating this logic in the new add_or_update function
      • +
      +
    • +
    • Create a decrypt_and_fixup_login function that both decrypts a login and performs the validate and fixup logic +
        +
      • This will eliminate the need for the get_all, get_by_base_domain, and get_by_id API functions to perform the fixup logic
      • +
      +
    • +
    +

    Making the above changes will reduce the API functions requiring the encryption key to the following:

    +
      +
    • add_or_update
    • +
    • decrypt_and_fixup_login
    • +
    • import_multiple
    • +
    +
  • +
  • +

    Pros

    +
      +
    • Improves the logins API for consumers by combining add/update functionality (see #3899 for details)
    • +
    • Removes redundant validation and de-duping logic in consumer code
    • +
    • Uses the same encryption model as autofill so there is consistency in our approaches
    • +
    +
  • +
  • +

    Cons

    +
      +
    • Requires consumer code to both encrypt login fields and pass the encryption key when calling either add_or_update and import_multiple
    • +
    +
  • +
+

Option 2 - Implement a different key management approach

+
    +
  • +

    Description

    +

    Unlike the first option, the publicly exposed login API would only handle decrypted login records and all encryption is internal (which works because we always have the key). Any attempt to use the API will fail as the login records are not encrypted or decrypted if the key is not available.

    +

    Proposed changes:

    +
      +
    • Combine the add and update functions into add_or_update
    • +
    • Remove both the potential_dupes_ignoring_username and check_valid_with_no_dupes from the API
    • +
    +
  • +
  • +

    Pros

    +
      +
    • Prevents the consumer from having to encrypt or decrypt login records
    • +
    • Maintains our current fixup and validation approach
    • +
    • Improves the logins API for consumers by combining add/update functionality
    • +
    • Removes redundant validation and de-duping logic in consumer code
    • +
    +
  • +
  • +

    Cons

    +
      +
    • Makes us responsible for securing the encryption key and will most likely require a security review
    • +
    +
  • +
+ + + +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + diff --git a/book/adr/0002-database-corruption.html b/book/adr/0002-database-corruption.html new file mode 100644 index 0000000000..a9cb12f1b0 --- /dev/null +++ b/book/adr/0002-database-corruption.html @@ -0,0 +1,349 @@ + + + + + + ADR-0002 - Cross-platform Rust Components + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Handling Database Corruption

+
    +
  • Status: accepted
  • +
  • Date: 2021-06-08
  • +
+

Context and Problem Statement

+

Some of our users have corrupt SQLite databases and this makes the related +component unusable. The best way to deal with corrupt databases is to simply +delete the database and start fresh (#2628). However, we only want to do this +for persistent errors, not transient errors like programming logic errors, disk +full, etc. This ADR deals with 2 related questions:

+
    +
  • A) When and how do we identify corrupted databases?
  • +
  • B) What do we do when we identify corrupted databases?
  • +
+

Decision Drivers

+
    +
  • Deleting valid user data should be avoided at almost any cost
  • +
  • Keeping a corrupted database around is almost as bad. It currently prevents +the component from working at all.
  • +
  • We don't currently have a good way to distinguish between persistent and +transient errors, but this can be improved by reviewing telemetry and sentry +data.
  • +
+

Considered Options

+
    +
  • A) When and how do we identify corrupted databases? +
      +
    • 1: Assume all errors when opening a database are from corrupt databases
    • +
    • 2: Check for errors when opening a database and compare against known corruption error types
    • +
    • 3: Check for errors for all database operations and compare against known corruption error types
    • +
    +
  • +
  • B) What do we do when we identify corrupted databases? +
      +
    • 1: Delete the database file and recreate the database
    • +
    • 2: Move the database file and recreate the database
    • +
    • 3: Have the component fail
    • +
    +
  • +
+

Decision Outcome

+
    +
  • A2: Check for errors when opening a database and compare against known corruption error types
  • +
  • B1: Delete the database file and recreate the database
  • +
+

Decision B follows from the choice of A. Since we're being conservative in +identifying errors, we can delete the database file with relative confidence.

+

"Check for errors for all database operations and compare against known +corruption error types" also seems like a reasonable solution that we may +pursue in the future, but we decided to wait for now. Checking for errors +during opening time is the simpler solution to implement and should fix the +issue in many cases. The plan is to implement that first, then monitor +sentry/telemetry to decide what to do next.

+

Pros and Cons of the Options

+

A1: Assume all errors when opening a database are from corrupt databases

+
    +
  • Good, because the sentry data indicates that many errors happen during opening time
  • +
  • Good, because migrations are especially likely to trigger corruption errors
  • +
  • Good, because it's a natural time to delete the database -- the consumer code +hasn't run any queries yet and doesn't have any open connections.
  • +
  • Bad, because it will delete valid user data in several situations that are +relatively common: migration logic errors, OOM errors, Disk full.
  • +
+

A2: Check for errors when opening a database and compare against known corruption error types (Decided)

+
    +
  • Good, because should eliminate the possibility of deleting valid user data.
  • +
  • Good, because the sentry data indicates that many errors happen during opening time
  • +
  • Good, because it's a natural time to delete the database -- the consumer code +hasn't run any queries yet and doesn't have any open connections.
  • +
  • Bad, because we don't currently have a good list corruption errors
  • +
+

A3: Check for errors for all database operations and compare against known corruption error types

+
    +
  • Good, because the sentry data indicates that many errors happen outside of opening time
  • +
  • Good, because should eliminate the possibility of deleting valid user data.
  • +
  • Bad, because the consumer code probably doesn't expect the database to be +deleted and recreated in the middle of a query. However, this is just an +extreme case of normal database behavior -- for example any given row can be +deleted during a sync.
  • +
  • Bad, because we don't currently have a good list corruption errors
  • +
+

B1: Delete the database file and recreate the database (Decided)

+
    +
  • Good, because it would allow users with corrupted databases to use the +affected components again
  • +
  • Bad, because any misidentification will lead to data loss.
  • +
+

B2: Move the database file and recreate the database

+

This option would be similar to 1, but instead of deleting the file we would +move it to a backup location. When we started up, we could look for backup +files and try to import lost data.

+
    +
  • Good, because if we misidentify corrupt databases, then we have the +possibility of recovering the data
  • +
  • Good, because it allows a way for users to delete their data (in theory). +If the consumer code executed a wipe() on the database, we could also +delete any backup data.
  • +
  • Bad, because it's very difficult to write a recovery function that merged +deleted data with any new data. This function would be fairly hard to test +and it would be easy to introduce a new logic error.
  • +
  • Bad, because it adds significant complexity to the database opening code
  • +
  • Bad, because the user experience would be strange. A user would open the +app, discover that their data was gone, then sometime later discover that the +data is back again.
  • +
+

B3: Return a failure code

+
    +
  • Good, because this option leaves no chance of user data being deleted
  • +
  • Good, because it's the simplest to implement
  • +
  • Bad, because the component will not be usable if the database is corrupt
  • +
  • Bad, because the user's data is potentially exposed in the corrupted database +file and we don't provide any way for them to delete it.
  • +
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + diff --git a/book/adr/0003-swift-packaging.html b/book/adr/0003-swift-packaging.html new file mode 100644 index 0000000000..7b40c2451e --- /dev/null +++ b/book/adr/0003-swift-packaging.html @@ -0,0 +1,474 @@ + + + + + + ADR-0003 - Cross-platform Rust Components + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Distributing Swift Packages

+
    +
  • Status: accepted
  • +
  • Deciders: rfkelly
  • +
  • Date: 2021-07-22
  • +
+

Context and Problem Statement

+

Our iOS consumers currently obtain application-services as a pre-compiled .framework bundle +distributed via Carthage. The current setup is not +compatible with building on new M1 Apple Silicon machines and has a number of other problems. +As part of a broader effort to modernize the build process of iOS applications at Mozilla, +we have been asked to re-evaluate how application-services components are dsitributed for iOS.

+

See Problems with the current setup for more details.

+

Decision Drivers

+
    +
  • Ease-of-use for iOS consumers.
  • +
  • Compatibility with M1 Apple Silicon machines.
  • +
  • Consistency with other iOS components being developed at Mozilla.
  • +
  • Ability for the Nimbus Swift bindings to easily depend on Glean.
  • +
  • Ease of maintainability for application-services developers.
  • +
+

Considered Options

+
    +
  • (A) Do Nothing +
      +
    • Keep our current build and distribution setup as-is.
    • +
    +
  • +
  • (B) Use Carthage to build XCFramework bundles +
      +
    • Make a minimal change to our Carthage setup so that it builds +the newer XCFramework format, which can support M1 Apple Silicon.
    • +
    +
  • +
  • (C) Distribute a single pre-compiled Swift Package +
      +
    • Convert the all-in-one MozillaAppServices Carthage build to a similar +all-in-one Swift Package, distributed as a binary artifact.
    • +
    +
  • +
  • (D) Distribute multiple source-based Swift Package targets, with pre-compiled Rust code +
      +
    • Split the all-in-one MozillaAppServices Carthage build into a separate +Swift Package target for each component, with a shared dependency on pre-compiled +Rust code as a binary artiact.
    • +
    +
  • +
+

Decision Outcome

+

Chosen option: (D) Distribute multiple source-based Swift Packages, with pre-compiled Rust code.

+

This option will provide the best long-term consumer experience for iOS developers, and +has the potential to simplify maintenance for application-services developers after an +initial investment of effort.

+

Positive Consequences

+
    +
  • Swift packages are very convenient to consume in newer versions of Xcode.
  • +
  • Different iOS apps can choose to import a different subset of the available components, +potentiallying helping keep application size down.
  • +
  • Avoids issues with mis-matched Swift version between application-services build +and consumers, since Swift files are distributed in source form.
  • +
  • Encourages better conceptual separation between Swift code for different components; +e.g. it will make it possible for two Swift components to define an item of the same +name without conflicts.
  • +
  • Reduces the need to use Xcode as part of application-services build process, in favour +of command-line tools.
  • +
+

Negative Consequences

+
    +
  • More up-front work to move to this new setup.
  • +
  • We may be less likely to notice if our build setup breaks when used from within Xcode, +because we're not exercising that code path ourselves.
  • +
  • May be harder to concurrently publish a Carthage framework for current consumers who aren't +able to move to Swift packages.
  • +
  • There is likely to be some amount of API breakage for existing consumers, if only in having +to replace a single import MozillaAppServices with independent imports of each component.
  • +
+

Implementation Sketch

+

We will maintain the existing Carthage build infrastructure in the application-services repo and continue publishing a pre-built Carthage framework, +to support firefox-ios until they migrate to Swift Packages.

+

We will add an additional iOS build task in the application-services repo, that builds just the Rust code as a .xcframework bundle. +An initial prototype shows that this can be achieved using a relatively straightforward shell script, rather than requiring a second Xcode project. +It will be published as a .zip artifact on each release in the same way as the current Carthage framework. +The Rust code will be built as a static library, so that the linking process of the consuming application can pull in +just the subset of the Rust code that is needed for the components it consumes.

+

We will initially include only Nimbus and its dependencies in the .xcframework bundle, +but will eventually expand it to include all Rust components (including Glean, which will continue +to be included in the application-services repo as a git submodule)

+

We will create a new repository rust-components-swift to serve as the root of the new Swift Package distribution. +It will import the application-services repository as a git submodule. This will let us iterate quickly on the +Swift packaging setup without impacting existing consumers.

+

We will initially include only Nimbus and its dependencies in this new repository, and the Nimbus swift code +it will depend on Glean via the external glean-swift package. In the future we will publish all application-services +components that have a Swift interface through this repository, as well as Glean and any future Rust components. +(That's why the repository is being given a deliberately generic name).

+

The rust-components-swift repo will contain a Package.swift file that defines:

+
    +
  • A single binary target that references the pre-built .xcframework bundle of Rust code.
  • +
  • One Swift target for each component, that references the Swift code from the git submodule +and depends on the pre-built Rust code.
  • +
+

We will add automation to the rust-components-swift repo so that it automatically tracks +releases made in the application-services repo and creates a corresponding git tag for +the Swift package.

+

At some future date when all consumers have migrated to using Swift packages, we will remove +the Carthage build setup from the application-services repo.

+

At some future date, we will consider whether to move the Package.swift definition in to the application-services repo, +or whether it's better to keep it separate. (Attempting to move it into the application-services will involve non-trivial +changes to the release process, because the checksum of the released .xcframework bundle needs to be included in +the release taged version of the Package.swift file.)

+

Pros and Cons of the Options

+

(A) Do Nothing

+

In this option, we would make no changes to our iOS build and publishing process.

+
    +
  • Good, because it's the least amount of work.
  • +
  • Neutral, because it doesn't change the maintainability of the system for appservices +developers.
  • +
  • Neutral, because it doesn't change the amount of separation between Swift code +for our various components.
  • +
  • Neutral, because it doesn't address the Swift version incompatibility issues around +binary artifacts.
  • +
  • Bad, because it will frustrate consumers who want to develop on M1 Apple Silicon.
  • +
  • Bad, because it may prevent consumers from migrating to a more modern build setup.
  • +
  • Bad, because it would prevent consumers from consuming Glean as a Swift package; +we would require them to use the Glean that is bundled in our build.
  • +
+

This option isn't really tractable for us, but it's included for completeness.

+

(B) Use Carthage to build XCFramework bundles

+

In this option, we would try to change our iOS build and publising process as little +as possible, but use Carthage's recent support for building platform-independent +XCFrameworks in order +to support consumers running on M1 Apple Silicon.

+
    +
  • Good, because the size of the change is small.
  • +
  • Good, because we can support development on newer Apple machines.
  • +
  • Neutral, because it doesn't change the maintainability of the system for appservices +developers.
  • +
  • Neutral, because it doesn't change the amount of separation between Swift code +for our various components.
  • +
  • Neutral, because it doesn't address the Swift version incompatibility issues around +binary artifacts.
  • +
  • Bad, because our iOS consumers have expressed a preference for moving away from Carthage.
  • +
  • Bad, because other iOS projects at Mozilla are moving to Swift Packages, making +us inconsistent with perceived best practice.
  • +
  • Bad, because it would prevent consumers from consuming Glean as a Swift package; +we would require them to use the Glean that is bundled in our build.
  • +
  • Bad, because consumers don't get to choose which components they want to use (without +us building a whole new "megazord" with just the components they want).
  • +
+

Overall, current circumstances feel like a good opportunity to invest a little more +time in order to set ourselves up for better long-term maintainability +and happier consumers. The main benefit of this option (it's quicker!) is less attractive +under those circumstances.

+

(C) Distribute a single pre-compiled Swift Package

+

In this option, we would compile the Rust code and Swift code for all our components into +a single .xcframework bundle, and then distribute that as a +binary artifact via Swift Package. This is similar to the approach +currently taken by Glean (ref Bug 1711447) +except that they only have a single component.

+
    +
  • Good, because Swift Packages are the preferred distribution format for new iOS consumers.
  • +
  • Good, because we can support development on newer Apple machines.
  • +
  • Good, because it aligns with what other iOS component developers are doing at Mozilla.
  • +
  • Neutral, because it doesn't change the maintainability of the system for appservices +developers. +
      +
    • (We'd need to keep the current Xcode project broadly intact).
    • +
    +
  • +
  • Neutral, because it doesn't change the amount of separation between Swift code +for our various components.
  • +
  • Neutral, because it doesn't address the Swift version incompatibility issues around +binary artifacts.
  • +
  • Neutral, because it would prevent consumers from consuming Glean as a separate Swift package; +they'd have to get it as part of our all-in-one Swift package.
  • +
  • Bad, because it's a larger change and we have to learn about a new package manager.
  • +
  • Bad, because consumers don't get to choose which components they want to use (without +building a whole new "megazord" with just the components they want).
  • +
+

Overall, this option would be a marked improvement on the status quo, but leaves out some potential +improvements. For not that much more work, we can make some of the "Neutral" and "Bad" points +here into "Good" points.

+

(D) Distribute multiple source-based Swift Packages, with pre-compiled Rust code

+

In this option, we would compile just the Rust code for all our components into a single +.xcframework bundle and distribute that as a binary artifact via Swift Package. +We would then declare a separate Swift source target for the Swift wrapper of each component, +each depending on the compiled Rust code but appearing as a separate item in the Swift package +definition.

+
    +
  • Good, because Swift Packages are the preferred distribution format for new iOS consumers.
  • +
  • Good, because we can support development on newer Apple machines.
  • +
  • Good, because it aligns with what other iOS component developers are doing at Mozilla.
  • +
  • Good, because it can potentially simplify the maintenance of the system for appservices +developers, by removing Xcode in favour of some command-line scripts.
  • +
  • Good, because it introduces strict separation between the Swift code for each component, +instead of compiling them all together in a single shared namespace.
  • +
  • Good, because the Nimbus Swift package could cleanly depend on the Glean Swift package.
  • +
  • Good, because consumers can choose which components they want to include.
  • +
  • Good, because it avoids issues with Swift version incompatibility in binary artifacts.
  • +
  • Bad, because it's a larger change and we have to learn about a new package manager.
  • +
+

The only downside to this option appears to be the amount of work involved, but an initial +prototype has given us some confidence that the change is tractable and that it may lead +to a system that is easier to maintain over time. It is thus our preferred option.

+

Appendix

+

Further Reading

+ +

Problems with the current setup

+

It doesn't build for M1 Apple Silicon machines, because it's not possible to support +both arm64 device builds and arm64 simulator builds in a single binary .framework.

+

Carthage is dispreferred by our current iOS consumers.

+

We don't have much experience with the setup on the current Application Services team, +and many of its details are under-documented. Changing the build setup requires Xcode +and some baseline knowledge of how to use it.

+

All components are built as a single Swift module, meaning they can see each other's +internal symbols and may accidentally conflict when naming things. For example we can't +currently have two components that define a structure of the same name.

+

Consumers can only use the pre-built binary artifacts if they are using the same +version of Xcode as was used during the application-services build. We are not able +to use Swift's BUILD_LIBRARY_FOR_DISTRIBUTION flag to overcome this, because some +of our dependencies do not support this flag (specifically, the Swift protobuf lib).

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + diff --git a/book/adr/0004-early-startup-experiments.html b/book/adr/0004-early-startup-experiments.html new file mode 100644 index 0000000000..e0290df1b6 --- /dev/null +++ b/book/adr/0004-early-startup-experiments.html @@ -0,0 +1,310 @@ + + + + + + ADR-0004 - Cross-platform Rust Components + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Running experiments on first run early startup

+
    +
  • Status: rejected
  • +
  • Deciders: teshaq, travis, k88hudson, jhugman, jaredlockhart
  • +
  • Date: 2021-08-16
  • +
+

Technical Story: https://mozilla-hub.atlassian.net/browse/SDK-323

+

Context and Problem Statement

+

As an experimenter, I would like to run experiments early on a user's first run of the application. However, the experiment data is only available on the second run. We would like to have that experiment data available before the user's first run. +For more information: https://docs.google.com/document/d/1Qw36_7G6XyHvJZdM-Hxh4nqYZyCsYajG0L5mO33Yd5M/edit

+

Decision Drivers

+
    +
  • Availability of experiments early on the first run
  • +
  • No impact on experimentation data analysis
  • +
  • Flexibility in creating experiments
  • +
  • Ability to quickly disable experiments
  • +
  • Simplicity of releases
  • +
  • Mobile's expectations of Nimbus (The SDK should be idempotent)
  • +
+

Considered Options

+
    +
  • (A) Do Nothing +
      +
    • Keep everything the way it is, preventing us from experimenting on users early on their first run
    • +
    +
  • +
  • (B) Bundle Experiment data with app on release +
      +
    • On release, have an initial_experiments.json that defines the experiments that will be applied early on the first run
    • +
    • Later on the first run, the client would retrieve the actual experiment data from remote-settings and overwrite the bundled data
    • +
    +
  • +
  • (C) Retrieve Experiment data on first run, and deal with delay +
      +
    • We can retrieve the experiment data on the first run, experiment data however will not be available until after a short delay (network I/O + some disk I/O)
    • +
    +
  • +
+

Decision Outcome

+

None of the options were feasible, so for now we are sticking with option (A) Do Nothing until there are experiments planned that are expected to run on early startup on the first run, then we will revaluate our options.

+

The (B) Bundle Experiment data with app on release option was rejected mainly due to difficulty in disabling experiments and pausing enrollments. This can create a negative user experience as it prevents us from disabling any problematic experiments. Additionally, it ties experiment creation with application release cycles.

+

The (C) Retrieve Experiment data on first run, and deal with delay option was rejected due to the fact it changes the Nimbus SDK will no longer be idempotent,and the possibility of introducing undesirable UI.

+

Pros and Cons of the Options

+

Do nothing

+
    +
  • Good, because it keeps the flexibility in experiment creation
  • +
  • Good, because disabling experiments can still done remotely for all experiments
  • +
  • Good, because it keeps the Nimbus SDK idempotent.
  • +
  • Bad, because it doesn't address the main problem of exposing experiments to user on their first run
  • +
+

Bundle Experiment data with app on release

+
    +
  • Good, because it allows us to run experiments early on a user's first run
  • +
  • Good, because it prevents us from having to wait for experiments, especially if a user has a slow network connection
  • +
  • Bad, because it ties experiment creation with release cycles
  • +
  • Bad, because it prevents us from disabling problematic first-run experiments without a dot release
  • +
  • Bad, because it prevents us from pausing enrollment on first-run experiments without a dot release
  • +
  • Bad, because it requires investment from the console team, and can modify existing flows.
  • +
+

Retrieve Experiment data on first run, and deal with delay

+
    +
  • Good, because it enables us to retrieve experiments for users on their first run
  • +
  • Good, because it keeps the flexibility in experiment creation
  • +
  • Good, because disabling experiments can still done remotely for all experiments
  • +
  • Bad, because experiments may not be ready early on the user's experience
  • +
  • Bad, because it forces the customer application to deal with either the delay, or changing the configuration shortly after startup. e.g. a loading spinner or a pre-onboarding screen not under experimental control; delaying initialization of onboarding screens until after experiments have been loaded.
  • +
  • Bad, because it changes the programming model from Nimbus being an idempotent configuration store to configuration changing non-deterministically.
  • +
  • Bad, because the experimentation platform could force the app to add unchangeable user interface for the entire population. This itself may have an effect on key metrics.
  • +
+ +
    +
  • RFC for bundling into iOS and Fenix
  • +
  • Document presented to product managers about (C) Retrieve Experiment data on first run, and deal with delay: https://docs.google.com/document/d/1X1hC3t5zC7-Rp0OPIoiUr_ueLOAI0ez_jqslaNzOHjY/edit
  • +
  • Demo presenting option (C) Retrieve Experiment data on first run, and deal with delay: https://drive.google.com/file/d/19HwnlwrabmSNsB7tjW2l4kZD3PWABi4u/view?usp=sharing
  • +
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + diff --git a/book/adr/0005-remote-settings-client.html b/book/adr/0005-remote-settings-client.html new file mode 100644 index 0000000000..c7c619f14e --- /dev/null +++ b/book/adr/0005-remote-settings-client.html @@ -0,0 +1,434 @@ + + + + + + ADR-0005 - Cross-platform Rust Components + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

A remote-settings client for our mobile browsers.

+
    +
  • +

    Status: proposed

    +
  • +
  • +

    Discussion: https://github.com/mozilla/application-services/pull/5302

    +
  • +
  • +

    Deciders:

    +
      +
    • csadilek for the mobile teams ✔️
    • +
    • leplatrem for the remote-settings team ✔️
    • +
    • mhammond for the application-services team ✔️
    • +
    +
  • +
  • +

    Date: 2022-12-16

    +
  • +
+

Context and Problem Statement

+

Mozilla’s mobile browsers have a requirement to access the remote settings service, but currently lack any libraries or tools which are suitable without some work. +A concrete use case is the management of search engine configurations, which are stored in Remote Settings for Firefox Desktop, but shipped as individual files on our mobile browsers, requiring application releases for all changes.

+

A constraint on any proposed solutions is that this work will be performed by Mozilla's mobile team, who have limited experience with Rust, and that it is required to be completed in Q1 2023.

+

This document identifies the requirements, then identifies tools which already exist and are close to being suitable, then identifies all available options we can take, and outlines our decision.

+

Requirements

+

The requirements are for a library which is able to access Mozilla’s Remote Settings service and return the results to our mobile browsers. +This list of requirements is not exhaustive, but instead focuses on the requirements which will drive our decision making process. +As such, it identifies the non-requirements first.

+

Non-requirements

+

The following items all may have some degree of desirability, but they are not hard requirements for the initial version

+
    +
  • While the https connection to the server must be validated, there is no requirement to verify the content received by the server - ie, there’s no requirement to check the signature of the body itself.
  • +
  • There’s no requirement to validate the result of the server conforms to a pre-defined schema - we trust the server data.
  • +
  • There’s no requirement to return strongly-typed data to the applications - returning a JSON string/object is suitable.
  • +
  • There’s no requirement to cache server responses to the file-system - if the app requests content, it’s fine for the library to always hit the server.
  • +
  • There’s no requirement for any kind of scheduling or awareness of network state - when we are requested for content, we do it immediately and return an appropriate error if it can not be fetched.
  • +
  • There’s no requirement to support publishing records, requesting reviews or providing approvals via this new library.
  • +
  • There’s no requirement that push be used to communicate changes to the application (eg, to enable rapid-enrolment type features)
  • +
  • There’s no requirement to manage buckets, groups and collections via this new library.
  • +
+

Initial Requirements

+

The requirements we do have for the initial version are:

+
    +
  • The library should allow fetching records from Mozilla’s Remote Settings servers. This includes support for attachments, and fetching incremental changes.
  • +
  • The library should not create threads or run any event loops - the mobile apps themselves are responsible for all threading requirements. While this might change in the future, considering this kind of change to our mobile applications is out of scope for this project.
  • +
  • We must use Necko for all networking on Android, must enforce all connections are via valid https hosts (although some test-only exceptions might be helpful for QA, such as allowing localhost connections to be http)
  • +
  • The library should be the only remote-settings library used in the browser. Specifically, this means that Nimbus must also be capable of using the library, and the work to move Nimbus to the library must be considered as part of the project.
  • +
+

Existing Libraries

+

We have identified the following libraries which may be suitable for this project.

+

Remote-settings on desktop

+

There is a version of the remote settings client in desktop, written in Javascript. +It has been used and been relatively stable since at least 2018, so can be considered very capable, but the roadblock to it being suitable for use by our mobile browsers is that it is written in Javascript, so while it might be possible to expose it to Android via geckoview, there’s no reasonable path to have it made available to iOS.

+

Rust Remote Settings Client

+

There is an existing remote settings client on github. +This client is written in Rust and has evolved over a number of years. +The most recent changes were made to support being used in Merino, which was re-written in Python, so there are no known consumers of this library left.

+

The main attributes of this library relevant to this discussion are:

+
    +
  • It’s written in Rust, but has no FFI - ie, it’s currently only consumable by other Rust code.
  • +
  • It has recently been updated to use async rust, so requires an internal event loop.
  • +
  • It includes the capability to verify the signatures of the content.
  • +
+

The Nimbus-sdk Client

+

The nimbus-sdk is a component in the application-services repository written in Rust. It has client code which talks to the remote-settings server and while this has only actually been used with the "Nimbus" collection there's no reason to believe it can't be used in the more general case. +The main attributes of this library relevant to this discussion are:

+
    +
  • It’s consumed by a component which is already consumed by our mobile browsers via UniFFI.
  • +
  • It does not verify the signatures of the content - while this could be done, there hasn’t been sufficient justification made for this (ie, there are no realistic threat models which would be solved by this capability.)
  • +
  • The client itself does not persist a local cache of remote resources, but instead delegates this responsibility to the consuming application (in this case, nimbus itself, which does persist them via the rkv library)
  • +
  • It does not use async Rust, but instead everything is blocking and run on threads exclusively created by the app itself.
  • +
  • It has good test support, which run against a docker image.
  • +
+

Considered Options

+

Option 1: Writing a new library

+

The requirements of this client are such that writing new libraries in Kotlin and Swift is currently a realistic option. +However, we are rejecting this option because we don’t want to duplicate the effort required to write and maintain two libraries - inevitably, the features and capabilities will diverge. +Future requirements such as supporting content signature verification would lead to significant duplication.

+

Writing a new library from scratch in Rust and exposing it via UniFFI so it can be used by both platforms is also a possibility. +However, we are rejecting this option because existing Rust libraries already exist, so we would be better served by modifying or forking one of the existing libraries.

+

Option 2: Use the existing remote settings client

+

Modifying or forking the existing client is an attractive option. +It would require a number of changes - the async capabilities would probably need to be removed (using a Rust event loop in our mobile browsers is something we are trying to avoid until we better understand the implications given these browsers already have an event loop and their own threading model).

+

The persistence model used by this library is something that is not a requirement for the new library, which isn’t itself a problem, but it probably would preclude being able to use this library by Nimbus - so the end result is that we would effectively have two remote-settings clients written in Rust and used by our browsers.

+

Some API changes would probably be required to make it suitable for use by UniFFI would also be necessary, but these would be tractable.

+

We would need to update nimbus to use this client, which would almost certainly require moving this client into the application-services repository to avoid the following issues:

+
    +
  • Marrying the persistence model of this client with the existing rkv-based persistence used by nimbus would be required.
  • +
  • Ensuring the upstream version changes continued to work for us.
  • +
  • Managing the circular dependency which exists due to this library needing to use viaduct.
  • +
  • Complication of our build process because the library needs to end up in our “megazord”. +These are the exact reasons why Nimbus itself is in the application-services repo.
  • +
+

Option 3: Use the existing nimbus client

+

Splitting the existing client out from Nimbus in a way that allows Nimbus to continue to use it, while also making it available for stand-alone use is also an attractive option.

+

In particular, the feature set of that client overlaps with the requirements of the new library - no local persistence is necessary and no signature verification is required. It is already used by a component which is exposed via UniFFI.

+

Note that this option does not preclude both Nimbus and this new crate from moving to the existing remote settings client at some point in the future. +A key benefit of this decision is that it keeps nimbus and the new crate using the same client, so updating both to use a different client in the future will always remain an option.

+

Chosen Option

+

We have chosen Option 3 because it allows us to reuse the new client in Nimbus, as well as on iOS and on Android with minimal initial development effort. +If the new library ends up growing requirements that are already in the existing remote settings client, we remain able to copy that functionality from that library into this.

+

Specific Plans

+

This section is non-normative - ie, is not strictly part of the ADR, but exists +for context.

+

This is a very high-level view of the tasks required here.

+
    +
  • +

    Create a new top-level component in the application-services repository, identify the exact API we wish to expose for this new library, describe this API using UniFFI, then implement the API with “stubs” (eg, using rust todo!()or similar). This is depicted as RemoteSettings in the diagram.

    +
  • +
  • +

    Identify which parts of Nimbus should be factored out into a shared component (depicted as rs-client in the diagram below) and move that functionality to the new shared component. Of note:

    +
      +
    • This component probably will not have a UniFFI .udl file, but is just for consumption by the new component above and the existing nimbus component.
    • +
    • There is still some uncertaintly here - if it is a requirement that nimbus and the new component share some configuration or initialization code, we might need to do something more complex here. This seems unlikely, but possible, so is included here for completeness.
    • +
    +
  • +
  • +

    Identify which of the nimbus tests should move to the new client and move them.

    +
  • +
  • +

    Update Nimbus to take a dependency on the new component and use it, including tests.

    +
  • +
  • +

    Flesh out the API of the new top-level component using the new shared component (ie, replace the todo!() macros with real code.)

    +
  • +
  • +

    Identify any impact on the Nimbus android/swift code - in particular, any shared configuration and initialization code identified above in the application-services repo.

    +
  • +
  • +

    Implement the Android and iOS code in the application-services repo desired to make this an ergonomic library for the mobile platforms.

    +
  • +
  • +

    Update the mobile code for the UniFFI changes made to Nimbus, if any.

    +
  • +
  • +

    Implement the mobile code which consumes the new library, including tests.

    +
  • +
  • +

    Profit?

    +
  • +
+

This diagram attempts to depict this final layout. Note:

+
    +
  • rs-client and RemoteSettings are both new components, everything else already exists. Please do not consider these names as suggestions! Names are hard, I'm sure we can do better.
  • +
  • Dashed lines are normal Rust dependencies (ie, dependencies listed in Cargo.toml)
  • +
  • Solid lines are where the component uses UniFFI
  • +
  • Viaduct is a little odd in that it is consumed by the mobile applications indirectly (eg, via Glean), hence it's not in support, but please ignore that anomaly.
  • +
+
flowchart RL
+    subgraph app-services-support[Shared components in application-services/components/support]
+    rs-client
+    other-support-components
+    end
+    subgraph app-services-components[Top-level application-services components, in application-services/components]
+    Nimbus
+    RemoteSettings
+    Viaduct
+    end
+    subgraph mobile [Code in the mobile repositories]
+        Fenix
+        Firefox-iOS
+    end
+
+    Nimbus -- nimbus.udl --> mobile
+    RemoteSettings -- remote_settings.udl --> mobile
+
+    rs-client -.-> Nimbus
+    other-support-components -.-> Nimbus
+    rs-client -.-> RemoteSettings
+    other-support-components -.-> RemoteSettings
+    Viaduct -.-> rs-client
+    other-support-components -.-> rs-client
+
+

Content Signatures

+

This section is non-normative - ie, is not strictly part of the ADR, but exists +for context.

+

Content Signatures have been explicitly called out as a non-requirement. Because this capability was a sticking point in the desktop version of the remote settings client, and because significant effort was spent on it, it's worth expanding on this here.

+

Because https will be enforced for all network requests, the consumers of this library can have a high degree of confidence that:

+
    +
  • The servers hit by this client are the servers we expect to hit (ie, no man-in-the-middle attacks will be able to redirect to a different server).
  • +
  • The response from the server is exactly what was sent by the Mozilla controlled server (ie, no man-in-the-middle attacks will be able to change the content in-flight)
  • +
  • Therefore, the content received must be exactly as sent by the Mozilla controlled servers.
  • +
+

Content signatures offer an additional capability of checking the content of a remote settings response matches the signature generated with a secret key owned by Mozilla, independenty of the https certificates used for the request itself.

+

This capability was added to the desktop version primarily to protect the integrity of the data at rest. +Because the Desktop client cached the responses on disk, there was a risk that this data could be tampered with - so it was effectively impossible to guarantee that the data finally presented to the application is what was initially sent.

+

The main threat-model that required this capability was 3rd party applications installed on the same system where Firefox was installed. +Because of the security model enforced by Desktop operating systems (most notably Windows), there was evidence that these 3rd-party applications would locate and modify the cache of remote-settings responses and modify them in a way that benefited them and caused revenue harm to Mozilla - the most obvious example is changing the search provider settings.

+

The reason we are declaring this capability a non-requirement in the initial version is two-fold:

+
    +
  • +

    We have also declared caching of responses a non-requirement, meaning there's no data at rest managed by this library which is vulnerable to this kind of attack.

    +
  • +
  • +

    The mobile operating systems have far stronger application isolation - in the general case, a 3rd party mobile application is prevented from touching any of the files used by other applications.

    +
  • +
+

Obviously though, things may change in the future - for example, we might add response caching, so we must be sure to reevaluate this requirement as other requirements change.

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + diff --git a/book/adr/0007-limit-visits-migration-to-10000.html b/book/adr/0007-limit-visits-migration-to-10000.html new file mode 100644 index 0000000000..7f3e4fd097 --- /dev/null +++ b/book/adr/0007-limit-visits-migration-to-10000.html @@ -0,0 +1,446 @@ + + + + + + ADR-0007 - Cross-platform Rust Components + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Limit Visits Migrated to Places History in Firefox iOS

+
    +
  • Status: accepted
  • +
  • Deciders: teshaq, mhammond, lougeniaC64, dnarcese
  • +
  • Date: 2023-01-06
  • +
+

Context and Problem Statement

+

The Application-Services team removed a legacy implementation of history in Firefox-ios and replaced it with a maintained implementation that was powering Firefox Android.

+

A significant part of the project is migrating users’ history from the old database to a new one. To measure risk, we ran a dry-run migration. A dry-run migration runs a background thread in the user’s application and attempts to migrate to a fake database. The dry-run was implemented purely to collect telemetry on the migration to evaluate risk. The results can be found in the following Looker dashboard. Below is a list of observations.

+

Observations from Dry-Run Experiment

+

The following is a list of observations from the experiment:

+
    +
  • 5-6% of migrations do not end. This means for 5-6% of users, the application was terminated before migration ended. For a real migration, this would mean those users lose all of their history unless we attempt the migration multiple times.
  • +
  • Out of the migrations that failed (the 5-6% mentioned above) 97% of those users had over 10,000 history visits.
  • +
  • Out of migrations that do end, over 99% of migrations are successful. +
      +
    • This means that we are not experiencing many errors with the migration beyond the time it takes.
    • +
    +
  • +
  • The average for visits migrated is around 25,000 - 45,000 visits.
  • +
  • The median for visits migrated is around 5,000-15,000 visits. +
      +
    • The difference between the average and the median suggests that we have many users with a large number of visits
    • +
    +
  • +
  • For migrations that did end, the following are the percentiles for how long it took (in milliseconds). We would like to emphasize that the following only includes migrations that did end +
      +
    • 10th percentile: 37 ms
    • +
    • 25th percentile: 80 ms
    • +
    • 50th percentile: 400 ms
    • +
    • 75th percentile: 2,500 ms (2.5 seconds)
    • +
    • 90th percentile: 6,400 ms (6.4 seconds)
    • +
    • 95th percentile: 11,000 ms (11 seconds)
    • +
    • 99th percentile: 25,000 ms (25 seconds)
    • +
    • 99.9th percentile: 50,000 ms (50 seconds)
    • +
    +
  • +
+

Problem Statement

+

Given the observations from the dry-run experiment, the rest of the document examines an approach to answer the question: How can we increase the rate of which migrations end, and simultaneously keep the user’s database size at a reasonable size?

+

The user implication of keeping the rate of ended migrations high is that users keep their history, and can interact normally with the URL bar to search history, searching history in the history panel and navigating to sites they visited in the past.

+

The user implication of keeping a reasonable database size is that the database is less likely to lock on long queries. Meaning we reduce performance issues when users use the search bar, the history panel and when navigating to sites.

+

Finally, it’s important to note that power users and daily active users will be more likely to have large histories and thus:

+
    +
  • Power users are more likely to fail their migration.
  • +
  • Power users are more likely to have performance issues with history and the search bar. +
      +
    • We saw a version of this with Favicons in an earlier incident, where users were coming across a significant number of database locks, crashing the app. This isn’t to say that the incident is directly related to this, however, large histories could contribute to the state we saw in the incident as it would take longer to run the queries.
    • +
    +
  • +
+

Decision Drivers

+ +

Considered Options

+
    +
  • Keep the migration as-is. +
      +
    • This option means that we have no limit. We will attempt to migrate all history for our users.
    • +
    +
  • +
  • Introduce a date-based limit on visits for the migration +
      +
    • This option means that we only migrate visits that occurred in the past X days/weeks/months etc
    • +
    +
  • +
  • Introduce a visit number-based limit for the migration +
      +
    • This option means we only migrate the latest X visits
    • +
    +
  • +
+

Decision Outcome

+

Chosen option: Introduce a visit number-based limit for the migration. This option was chosen because given our decision drivers:

+
    +
  1. We must not lose users’ recent history:
  2. +
+
    +
  • We have established in the results of the dry-run, that the majority of failed migrations were for users with a large number of visits.
  • +
  • By setting a reasonable limit, we can increase the likelihood the migration succeeds. We can set the limit to encompass “recent history” while choosing a limit that has an over 99% success rate.
  • +
+
    +
  1. User’s experience with History must not regress, and ideally should improve.
  2. +
+
    +
  • We have established in our decision driver that the user’s experience with history is coupled with the database size.
  • +
  • By setting a reasonable limit, we can keep the size of the history database controlled.
  • +
  • It's also worth noting that with the switch to the new implementation of history, we are also introducing a target size for the database. This means that we have maintenance logic that would compact the database and prune it if it grows beyond the target size.
  • +
+

Positive Consequences

+
    +
  • The migration runs in a shorter time. +
      +
    • This means a higher chance of the migration succeeding, thus keeping the user’s recent history without loss.
    • +
    +
  • +
  • Users who have less than the selected limit, will still get all their history. More on this in the Suggested Limit section.
  • +
  • We keep the size of the history database low. +
      +
    • This way users with more than the limit, will only keep their recent history.
    • +
    • Additionally, when we delete users’ history from the old database, the size of the data the app keeps will decrease dramatically.
    • +
    • Keeping the database size low means we lower the chance a user has performance issues with the database.
    • +
    +
  • +
+

Negative Consequences

+

The biggest negative consequence is that Users with more visits than the limit, will lose visits.

+
    +
  • Since we would only keep the latest X visits for a user, if a user has Y visits, they would lose all of the Y-X visits (assuming Y is greater than X)
  • +
  • The effect here is mitigated by the observation that recent history is more important to users than older history. Unfortunately, we do not have any telemetry to demonstrate this claim, but it’s an assumption based on the existing limits on history imposed in Android and Desktop mentioned in the decision drivers section.
  • +
+

Pros and Cons of the Other Options

+

Keep the migration as-is

+
    +
  • Good because if the migration succeeds, users keep all their history.
  • +
  • Bad, because it’s less likely for migrations to succeed.
  • +
  • Bad, because even if the migration succeeds it causes the size of the database to be large if a user has a lot of history. +
      +
    • Large databases can cause a regression in performance.
    • +
    • Users with a lot of history will now have two large databases (the old and new ones) since we won’t delete the data in the old database right away to support downgrades.
    • +
    +
  • +
  • Bad, because it can take a long time for the migration to finish.
  • +
  • Bad because until the migration is over users will experience the app without history.
  • +
+

Introduce a date-based limit on visits

+
    +
  • Good, because we match users’ usage of the app. +
      +
    • Users that use the app more, will keep more of their history.
    • +
    +
  • +
  • Good, because it’s more likely that the migration ends because we set a limit
  • +
  • Bad because it’s hard to predict how large user’s databases will be. +
      +
    • This is particularly important for Sync users. As Firefox-iOS syncs all your history, meaning if a user has many visits before the limit across multiple platforms, a large number of visits will be migrated.
    • +
    +
  • +
  • Bad, because users who haven’t used the app since the limit, will lose all their history +
      +
    • For example, if the limit is 3 months, a user who last used the app 3 months ago will suddenly lose all their history
    • +
    +
  • +
+

Suggested Limit

+

This section describes a suggested limit for the visits. Although it’s backed up with telemetry, the specific number is up for discussion. It’s also important to note that statistical significance was not a part of the analysis. Migration has run for over 16,000 users and although that may not be a statistically significant representation of our population, it’s good enough input to make an educated suggestion.

+
    +
  • First, we start by observing the distribution of visit counts. This will tell us how many of our users have between 0-10000 visits, 10000-20000, etc. We will identify that most of our users have less than 10,000 visits.
  • +
  • Then, we will observe the dry-run migration ended rate based on the above buckets. We will observe that users with under 10,000 visits have a high chance of migration success.
  • +
  • Finally, based on the analysis and prior art we’ll suggest 10,000 visits.
  • +
+

User History Distribution

+

We will look at https://mozilla.cloud.looker.com/looks/1078 which demonstrates a distribution of our users based on the number of history visits. Note that the chart is based on our release population.

+

Observations

+
    +
  • 67% of firefox-ios users have less than 10,000 visits
  • +
  • There is a very long tail to the distribution, with 6% of users having over 100,000 visits.
  • +
+

Dry-run Ending Rate by Visits

+

We will look at https://mozilla.cloud.looker.com/looks/1081. The chart demonstrates the rate at which migrations end by the number of visits. We bucket users in buckets of 10,000 visits.

+

Observations

+
    +
  • We observe that for users that have visits under 10,000, the success rate is over 99.6%.
  • +
  • We observe that for users with over 100,000 visits, the success rate drops to 75~80%.
  • +
  • Users in between, have success rates in between. For example, users with visits between 10,000 and 20,000 have a 98-99% success rate.
  • +
  • All success rates for buckets beyond 20,000 visits drop under 96%.
  • +
+

Suggestion

+

Based on the above, we’re suggesting a limit of 10,000 visits because

+ + + + +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + diff --git a/book/adr/index.html b/book/adr/index.html new file mode 100644 index 0000000000..1af91a09f0 --- /dev/null +++ b/book/adr/index.html @@ -0,0 +1,251 @@ + + + + + + Architectural Decision Records - Cross-platform Rust Components + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Architectural Decision Log

+

This log lists the architectural decisions for MADR.

+ +
    +
  • ADR-0000 - Use Markdown Architectural Decision Records
  • +
  • ADR-0001 - Update Logins API
  • +
  • ADR-0002 - Handling Database Corruption
  • +
  • ADR-0003 - Distributing Swift Packages
  • +
  • ADR-0004 - Running experiments on first run early startup
  • +
  • ADR-0005 - A remote-settings client for our mobile browsers.
  • +
  • ADR-0007 - Limit Visits Migrated to Places History in Firefox iOS
  • +
+ +

For new ADRs, please use template.md as basis. +More information on MADR is available at https://adr.github.io/madr/. +General information about architectural decision records is available at https://adr.github.io/.

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + diff --git a/book/adr/update-readme.sh b/book/adr/update-readme.sh new file mode 100755 index 0000000000..e0323d5a02 --- /dev/null +++ b/book/adr/update-readme.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +adr-log -i README.md -e template.md diff --git a/book/android-faqs.html b/book/android-faqs.html new file mode 100644 index 0000000000..18287ebc21 --- /dev/null +++ b/book/android-faqs.html @@ -0,0 +1,339 @@ + + + + + + How to use Rust Components in Android - Cross-platform Rust Components + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Rust + Android FAQs

+

How do I expose Rust code to Kotlin?

+

Use UniFFI, which can produce Kotlin +bindings for your Rust code from an interface definition file.

+

If UniFFI doesn't currently meet your needs, please open an issue to discuss how the tool can +be improved.

+

As a last resort, you can make hand-written bindings from Rust to Kotlin, +essentially manually performing the steps that UniFFI tries to automate +for you: flatten your Rust API into a bunch of pub extern "C" functions, +then use JNA to call them +from Kotlin. The details of how to do that are well beyond the scope of +this document.

+

How should I name the package?

+

Published packages should be named org.mozilla.appservices.$NAME where $NAME +is the name of your component, such as logins. The Java namespace in which +your package defines its classes etc should be mozilla.appservices.$NAME.*.

+

How do I publish the resulting package?

+

Add it to .buildconfig-android.yml in the root of this repository. +This will cause it to be automatically included as part of our release +publishing pipeline.

+

How do I know what library name to load to access the compiled rust code?

+

Assuming that you're building the Rust code as part of the application-services +build and release process, your pub extern "C" API should always be available +from a file named libmegazord.so.

+

What challenges exist when calling back into Kotlin from Rust?

+

There are a number of them. The issue boils down to the fact that you need to be +completely certain that a JVM is associated with a given thread in order to call +java code on it. The difficulty is that the JVM can GC its threads and will not +let rust know about it.

+

JNA can work around this for us to some extent, at the cost of some complexity. +The approach it takes is essentially to spawn a thread for each callback +invocation. If you are certain you’re going to do a lot of callbacks and they +all originate on the same thread, you can have them all run on a single thread +by using the CallbackThreadInitializer.

+

With the help of JNA's workarounds, calling back from Rust into Kotlin isn’t too bad +so long as you ensure that Kotlin cannot GC the callback while rust code holds onto it +(perhaps by stashing it in a global variable), and so long as you can either accept the overhead of extra threads being instantiated on each call or are willing to manage +the threads explicitly.

+

Note that the situation would be somewhat better if we used JNI directly (and +not JNA), but this would cause us to need to generate different Rust FFI code for +Android than for iOS.

+

Ultimately, in any case where there is an alternative to using a callback, you +should probably pursue that alternative.

+

For example if you're using callbacks to implement async I/O, it's likely better to +move to doing a blocking call, and have the calling code dispatch it on a background +thread. It’s very easy to run such things on a background thread in Kotlin, is in line +with the Android documentation on JNI usage, and in our experience is vastly simpler +and less painful than using callbacks.

+

(Of course, not every case is solvable like this).

+

Why are we using JNA rather than JNI, and what tradeoffs does that involve?

+

We get a couple things from using JNA that we wouldn't with JNI.

+
    +
  1. +

    We are able to use the same Rust FFI code on all platforms. If we used JNI we'd +need to generate an Android-specific Rust FFI crate that used the JNI APIs, and +a separate Rust FFI crate for exposing to Swift.

    +
  2. +
  3. +

    JNA provides a mapping of threads to callbacks for us, making callbacks over +the FFI possible. That said, in practice this is still error prone, and easy +to misuse/cause memory safety bugs, but it's required for cases like logging, +among others, and so it is a nontrivial piece of complexity we'd have to +reimplement.

    +
  4. +
+

However, it comes with the following downsides:

+
    +
  1. JNA has bugs. In particular, its not safe to use bools with them, it thinks +they are 32 bits, when on most platforms (every platform Rust supports) they +are 8 bits. They've been unwilling to fix the issue due to it breaking +backwards compatibility (which is... somewhat fair, there is a lot of C89 +code out there that uses bool as a typedef for a 32-bit int).
  2. +
  3. JNA makes it really easy to do the wrong thing and have it work but corrupt +memory. Several of the caveats around this are documented in the +ffi_support docs, but a +major one is when to use Pointer vs String (getting this wrong will +often work, but may corrupt memory).
  4. +
+

We aim to avoid triggering these bugs by auto-generating the JNA bindings +rather than writing them by hand.

+

How do I debug Rust code with the step-debugger in Android Studio

+
    +
  1. Uncomment the packagingOptions { doNotStrip "**/*.so" } line from the +build.gradle file of the component you want to debug.
  2. +
  3. In the rust code, either: +
      +
    1. Cause something to crash where you want the breakpoint. Note: Panics +don't work here, unfortunately. (I have not found a convenient way to +set a breakpoint to rust code, so +unsafe { std::ptr::write_volatile(0 as *const _, 1u8) } usually is +what I do).
    2. +
    3. If you manage to get an LLDB prompt, you can set a breakpoint using +breakpoint set --name foo, or breakpoint set --file foo.rs --line 123. +I don't know how to bring up this prompt reliably, so I often do step 1 to +get it to appear, delete the crashing code, and then set the +breakpoint using the CLI. This is admittedly suboptimal.
    4. +
    +
  4. +
  5. Click the Debug button in Android Studio, to display the "Select Deployment +Target" window.
  6. +
  7. Make sure the debugger selection is set to "Both". This tends to unset +itself, so make sure.
  8. +
  9. Click "Run", and debug away.
  10. +
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + diff --git a/book/ayu-highlight.css b/book/ayu-highlight.css new file mode 100644 index 0000000000..32c9432224 --- /dev/null +++ b/book/ayu-highlight.css @@ -0,0 +1,78 @@ +/* +Based off of the Ayu theme +Original by Dempfi (https://github.com/dempfi/ayu) +*/ + +.hljs { + display: block; + overflow-x: auto; + background: #191f26; + color: #e6e1cf; +} + +.hljs-comment, +.hljs-quote { + color: #5c6773; + font-style: italic; +} + +.hljs-variable, +.hljs-template-variable, +.hljs-attribute, +.hljs-attr, +.hljs-regexp, +.hljs-link, +.hljs-selector-id, +.hljs-selector-class { + color: #ff7733; +} + +.hljs-number, +.hljs-meta, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params { + color: #ffee99; +} + +.hljs-string, +.hljs-bullet { + color: #b8cc52; +} + +.hljs-title, +.hljs-built_in, +.hljs-section { + color: #ffb454; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-symbol { + color: #ff7733; +} + +.hljs-name { + color: #36a3d9; +} + +.hljs-tag { + color: #00568d; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} + +.hljs-addition { + color: #91b362; +} + +.hljs-deletion { + color: #d96c75; +} diff --git a/book/book.js b/book/book.js new file mode 100644 index 0000000000..aa12e7eccf --- /dev/null +++ b/book/book.js @@ -0,0 +1,697 @@ +"use strict"; + +// Fix back button cache problem +window.onunload = function () { }; + +// Global variable, shared between modules +function playground_text(playground, hidden = true) { + let code_block = playground.querySelector("code"); + + if (window.ace && code_block.classList.contains("editable")) { + let editor = window.ace.edit(code_block); + return editor.getValue(); + } else if (hidden) { + return code_block.textContent; + } else { + return code_block.innerText; + } +} + +(function codeSnippets() { + function fetch_with_timeout(url, options, timeout = 6000) { + return Promise.race([ + fetch(url, options), + new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), timeout)) + ]); + } + + var playgrounds = Array.from(document.querySelectorAll(".playground")); + if (playgrounds.length > 0) { + fetch_with_timeout("https://play.rust-lang.org/meta/crates", { + headers: { + 'Content-Type': "application/json", + }, + method: 'POST', + mode: 'cors', + }) + .then(response => response.json()) + .then(response => { + // get list of crates available in the rust playground + let playground_crates = response.crates.map(item => item["id"]); + playgrounds.forEach(block => handle_crate_list_update(block, playground_crates)); + }); + } + + function handle_crate_list_update(playground_block, playground_crates) { + // update the play buttons after receiving the response + update_play_button(playground_block, playground_crates); + + // and install on change listener to dynamically update ACE editors + if (window.ace) { + let code_block = playground_block.querySelector("code"); + if (code_block.classList.contains("editable")) { + let editor = window.ace.edit(code_block); + editor.addEventListener("change", function (e) { + update_play_button(playground_block, playground_crates); + }); + // add Ctrl-Enter command to execute rust code + editor.commands.addCommand({ + name: "run", + bindKey: { + win: "Ctrl-Enter", + mac: "Ctrl-Enter" + }, + exec: _editor => run_rust_code(playground_block) + }); + } + } + } + + // updates the visibility of play button based on `no_run` class and + // used crates vs ones available on https://play.rust-lang.org + function update_play_button(pre_block, playground_crates) { + var play_button = pre_block.querySelector(".play-button"); + + // skip if code is `no_run` + if (pre_block.querySelector('code').classList.contains("no_run")) { + play_button.classList.add("hidden"); + return; + } + + // get list of `extern crate`'s from snippet + var txt = playground_text(pre_block); + var re = /extern\s+crate\s+([a-zA-Z_0-9]+)\s*;/g; + var snippet_crates = []; + var item; + while (item = re.exec(txt)) { + snippet_crates.push(item[1]); + } + + // check if all used crates are available on play.rust-lang.org + var all_available = snippet_crates.every(function (elem) { + return playground_crates.indexOf(elem) > -1; + }); + + if (all_available) { + play_button.classList.remove("hidden"); + } else { + play_button.classList.add("hidden"); + } + } + + function run_rust_code(code_block) { + var result_block = code_block.querySelector(".result"); + if (!result_block) { + result_block = document.createElement('code'); + result_block.className = 'result hljs language-bash'; + + code_block.append(result_block); + } + + let text = playground_text(code_block); + let classes = code_block.querySelector('code').classList; + let edition = "2015"; + if(classes.contains("edition2018")) { + edition = "2018"; + } else if(classes.contains("edition2021")) { + edition = "2021"; + } + var params = { + version: "stable", + optimize: "0", + code: text, + edition: edition + }; + + if (text.indexOf("#![feature") !== -1) { + params.version = "nightly"; + } + + result_block.innerText = "Running..."; + + fetch_with_timeout("https://play.rust-lang.org/evaluate.json", { + headers: { + 'Content-Type': "application/json", + }, + method: 'POST', + mode: 'cors', + body: JSON.stringify(params) + }) + .then(response => response.json()) + .then(response => { + if (response.result.trim() === '') { + result_block.innerText = "No output"; + result_block.classList.add("result-no-output"); + } else { + result_block.innerText = response.result; + result_block.classList.remove("result-no-output"); + } + }) + .catch(error => result_block.innerText = "Playground Communication: " + error.message); + } + + // Syntax highlighting Configuration + hljs.configure({ + tabReplace: ' ', // 4 spaces + languages: [], // Languages used for auto-detection + }); + + let code_nodes = Array + .from(document.querySelectorAll('code')) + // Don't highlight `inline code` blocks in headers. + .filter(function (node) {return !node.parentElement.classList.contains("header"); }); + + if (window.ace) { + // language-rust class needs to be removed for editable + // blocks or highlightjs will capture events + code_nodes + .filter(function (node) {return node.classList.contains("editable"); }) + .forEach(function (block) { block.classList.remove('language-rust'); }); + + code_nodes + .filter(function (node) {return !node.classList.contains("editable"); }) + .forEach(function (block) { hljs.highlightBlock(block); }); + } else { + code_nodes.forEach(function (block) { hljs.highlightBlock(block); }); + } + + // Adding the hljs class gives code blocks the color css + // even if highlighting doesn't apply + code_nodes.forEach(function (block) { block.classList.add('hljs'); }); + + Array.from(document.querySelectorAll("code.hljs")).forEach(function (block) { + + var lines = Array.from(block.querySelectorAll('.boring')); + // If no lines were hidden, return + if (!lines.length) { return; } + block.classList.add("hide-boring"); + + var buttons = document.createElement('div'); + buttons.className = 'buttons'; + buttons.innerHTML = ""; + + // add expand button + var pre_block = block.parentNode; + pre_block.insertBefore(buttons, pre_block.firstChild); + + pre_block.querySelector('.buttons').addEventListener('click', function (e) { + if (e.target.classList.contains('fa-eye')) { + e.target.classList.remove('fa-eye'); + e.target.classList.add('fa-eye-slash'); + e.target.title = 'Hide lines'; + e.target.setAttribute('aria-label', e.target.title); + + block.classList.remove('hide-boring'); + } else if (e.target.classList.contains('fa-eye-slash')) { + e.target.classList.remove('fa-eye-slash'); + e.target.classList.add('fa-eye'); + e.target.title = 'Show hidden lines'; + e.target.setAttribute('aria-label', e.target.title); + + block.classList.add('hide-boring'); + } + }); + }); + + if (window.playground_copyable) { + Array.from(document.querySelectorAll('pre code')).forEach(function (block) { + var pre_block = block.parentNode; + if (!pre_block.classList.contains('playground')) { + var buttons = pre_block.querySelector(".buttons"); + if (!buttons) { + buttons = document.createElement('div'); + buttons.className = 'buttons'; + pre_block.insertBefore(buttons, pre_block.firstChild); + } + + var clipButton = document.createElement('button'); + clipButton.className = 'fa fa-copy clip-button'; + clipButton.title = 'Copy to clipboard'; + clipButton.setAttribute('aria-label', clipButton.title); + clipButton.innerHTML = ''; + + buttons.insertBefore(clipButton, buttons.firstChild); + } + }); + } + + // Process playground code blocks + Array.from(document.querySelectorAll(".playground")).forEach(function (pre_block) { + // Add play button + var buttons = pre_block.querySelector(".buttons"); + if (!buttons) { + buttons = document.createElement('div'); + buttons.className = 'buttons'; + pre_block.insertBefore(buttons, pre_block.firstChild); + } + + var runCodeButton = document.createElement('button'); + runCodeButton.className = 'fa fa-play play-button'; + runCodeButton.hidden = true; + runCodeButton.title = 'Run this code'; + runCodeButton.setAttribute('aria-label', runCodeButton.title); + + buttons.insertBefore(runCodeButton, buttons.firstChild); + runCodeButton.addEventListener('click', function (e) { + run_rust_code(pre_block); + }); + + if (window.playground_copyable) { + var copyCodeClipboardButton = document.createElement('button'); + copyCodeClipboardButton.className = 'fa fa-copy clip-button'; + copyCodeClipboardButton.innerHTML = ''; + copyCodeClipboardButton.title = 'Copy to clipboard'; + copyCodeClipboardButton.setAttribute('aria-label', copyCodeClipboardButton.title); + + buttons.insertBefore(copyCodeClipboardButton, buttons.firstChild); + } + + let code_block = pre_block.querySelector("code"); + if (window.ace && code_block.classList.contains("editable")) { + var undoChangesButton = document.createElement('button'); + undoChangesButton.className = 'fa fa-history reset-button'; + undoChangesButton.title = 'Undo changes'; + undoChangesButton.setAttribute('aria-label', undoChangesButton.title); + + buttons.insertBefore(undoChangesButton, buttons.firstChild); + + undoChangesButton.addEventListener('click', function () { + let editor = window.ace.edit(code_block); + editor.setValue(editor.originalCode); + editor.clearSelection(); + }); + } + }); +})(); + +(function themes() { + var html = document.querySelector('html'); + var themeToggleButton = document.getElementById('theme-toggle'); + var themePopup = document.getElementById('theme-list'); + var themeColorMetaTag = document.querySelector('meta[name="theme-color"]'); + var stylesheets = { + ayuHighlight: document.querySelector("[href$='ayu-highlight.css']"), + tomorrowNight: document.querySelector("[href$='tomorrow-night.css']"), + highlight: document.querySelector("[href$='highlight.css']"), + }; + + function showThemes() { + themePopup.style.display = 'block'; + themeToggleButton.setAttribute('aria-expanded', true); + themePopup.querySelector("button#" + get_theme()).focus(); + } + + function updateThemeSelected() { + themePopup.querySelectorAll('.theme-selected').forEach(function (el) { + el.classList.remove('theme-selected'); + }); + themePopup.querySelector("button#" + get_theme()).classList.add('theme-selected'); + } + + function hideThemes() { + themePopup.style.display = 'none'; + themeToggleButton.setAttribute('aria-expanded', false); + themeToggleButton.focus(); + } + + function get_theme() { + var theme; + try { theme = localStorage.getItem('mdbook-theme'); } catch (e) { } + if (theme === null || theme === undefined) { + return default_theme; + } else { + return theme; + } + } + + function set_theme(theme, store = true) { + let ace_theme; + + if (theme == 'coal' || theme == 'navy') { + stylesheets.ayuHighlight.disabled = true; + stylesheets.tomorrowNight.disabled = false; + stylesheets.highlight.disabled = true; + + ace_theme = "ace/theme/tomorrow_night"; + } else if (theme == 'ayu') { + stylesheets.ayuHighlight.disabled = false; + stylesheets.tomorrowNight.disabled = true; + stylesheets.highlight.disabled = true; + ace_theme = "ace/theme/tomorrow_night"; + } else { + stylesheets.ayuHighlight.disabled = true; + stylesheets.tomorrowNight.disabled = true; + stylesheets.highlight.disabled = false; + ace_theme = "ace/theme/dawn"; + } + + setTimeout(function () { + themeColorMetaTag.content = getComputedStyle(document.documentElement).backgroundColor; + }, 1); + + if (window.ace && window.editors) { + window.editors.forEach(function (editor) { + editor.setTheme(ace_theme); + }); + } + + var previousTheme = get_theme(); + + if (store) { + try { localStorage.setItem('mdbook-theme', theme); } catch (e) { } + } + + html.classList.remove(previousTheme); + html.classList.add(theme); + updateThemeSelected(); + } + + // Set theme + var theme = get_theme(); + + set_theme(theme, false); + + themeToggleButton.addEventListener('click', function () { + if (themePopup.style.display === 'block') { + hideThemes(); + } else { + showThemes(); + } + }); + + themePopup.addEventListener('click', function (e) { + var theme; + if (e.target.className === "theme") { + theme = e.target.id; + } else if (e.target.parentElement.className === "theme") { + theme = e.target.parentElement.id; + } else { + return; + } + set_theme(theme); + }); + + themePopup.addEventListener('focusout', function(e) { + // e.relatedTarget is null in Safari and Firefox on macOS (see workaround below) + if (!!e.relatedTarget && !themeToggleButton.contains(e.relatedTarget) && !themePopup.contains(e.relatedTarget)) { + hideThemes(); + } + }); + + // Should not be needed, but it works around an issue on macOS & iOS: https://github.com/rust-lang/mdBook/issues/628 + document.addEventListener('click', function(e) { + if (themePopup.style.display === 'block' && !themeToggleButton.contains(e.target) && !themePopup.contains(e.target)) { + hideThemes(); + } + }); + + document.addEventListener('keydown', function (e) { + if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) { return; } + if (!themePopup.contains(e.target)) { return; } + + switch (e.key) { + case 'Escape': + e.preventDefault(); + hideThemes(); + break; + case 'ArrowUp': + e.preventDefault(); + var li = document.activeElement.parentElement; + if (li && li.previousElementSibling) { + li.previousElementSibling.querySelector('button').focus(); + } + break; + case 'ArrowDown': + e.preventDefault(); + var li = document.activeElement.parentElement; + if (li && li.nextElementSibling) { + li.nextElementSibling.querySelector('button').focus(); + } + break; + case 'Home': + e.preventDefault(); + themePopup.querySelector('li:first-child button').focus(); + break; + case 'End': + e.preventDefault(); + themePopup.querySelector('li:last-child button').focus(); + break; + } + }); +})(); + +(function sidebar() { + var body = document.querySelector("body"); + var sidebar = document.getElementById("sidebar"); + var sidebarLinks = document.querySelectorAll('#sidebar a'); + var sidebarToggleButton = document.getElementById("sidebar-toggle"); + var sidebarResizeHandle = document.getElementById("sidebar-resize-handle"); + var firstContact = null; + + function showSidebar() { + body.classList.remove('sidebar-hidden') + body.classList.add('sidebar-visible'); + Array.from(sidebarLinks).forEach(function (link) { + link.setAttribute('tabIndex', 0); + }); + sidebarToggleButton.setAttribute('aria-expanded', true); + sidebar.setAttribute('aria-hidden', false); + try { localStorage.setItem('mdbook-sidebar', 'visible'); } catch (e) { } + } + + + var sidebarAnchorToggles = document.querySelectorAll('#sidebar a.toggle'); + + function toggleSection(ev) { + ev.currentTarget.parentElement.classList.toggle('expanded'); + } + + Array.from(sidebarAnchorToggles).forEach(function (el) { + el.addEventListener('click', toggleSection); + }); + + function hideSidebar() { + body.classList.remove('sidebar-visible') + body.classList.add('sidebar-hidden'); + Array.from(sidebarLinks).forEach(function (link) { + link.setAttribute('tabIndex', -1); + }); + sidebarToggleButton.setAttribute('aria-expanded', false); + sidebar.setAttribute('aria-hidden', true); + try { localStorage.setItem('mdbook-sidebar', 'hidden'); } catch (e) { } + } + + // Toggle sidebar + sidebarToggleButton.addEventListener('click', function sidebarToggle() { + if (body.classList.contains("sidebar-hidden")) { + var current_width = parseInt( + document.documentElement.style.getPropertyValue('--sidebar-width'), 10); + if (current_width < 150) { + document.documentElement.style.setProperty('--sidebar-width', '150px'); + } + showSidebar(); + } else if (body.classList.contains("sidebar-visible")) { + hideSidebar(); + } else { + if (getComputedStyle(sidebar)['transform'] === 'none') { + hideSidebar(); + } else { + showSidebar(); + } + } + }); + + sidebarResizeHandle.addEventListener('mousedown', initResize, false); + + function initResize(e) { + window.addEventListener('mousemove', resize, false); + window.addEventListener('mouseup', stopResize, false); + body.classList.add('sidebar-resizing'); + } + function resize(e) { + var pos = (e.clientX - sidebar.offsetLeft); + if (pos < 20) { + hideSidebar(); + } else { + if (body.classList.contains("sidebar-hidden")) { + showSidebar(); + } + pos = Math.min(pos, window.innerWidth - 100); + document.documentElement.style.setProperty('--sidebar-width', pos + 'px'); + } + } + //on mouseup remove windows functions mousemove & mouseup + function stopResize(e) { + body.classList.remove('sidebar-resizing'); + window.removeEventListener('mousemove', resize, false); + window.removeEventListener('mouseup', stopResize, false); + } + + document.addEventListener('touchstart', function (e) { + firstContact = { + x: e.touches[0].clientX, + time: Date.now() + }; + }, { passive: true }); + + document.addEventListener('touchmove', function (e) { + if (!firstContact) + return; + + var curX = e.touches[0].clientX; + var xDiff = curX - firstContact.x, + tDiff = Date.now() - firstContact.time; + + if (tDiff < 250 && Math.abs(xDiff) >= 150) { + if (xDiff >= 0 && firstContact.x < Math.min(document.body.clientWidth * 0.25, 300)) + showSidebar(); + else if (xDiff < 0 && curX < 300) + hideSidebar(); + + firstContact = null; + } + }, { passive: true }); +})(); + +(function chapterNavigation() { + document.addEventListener('keydown', function (e) { + if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) { return; } + if (window.search && window.search.hasFocus()) { return; } + var html = document.querySelector('html'); + + function next() { + var nextButton = document.querySelector('.nav-chapters.next'); + if (nextButton) { + window.location.href = nextButton.href; + } + } + function prev() { + var previousButton = document.querySelector('.nav-chapters.previous'); + if (previousButton) { + window.location.href = previousButton.href; + } + } + switch (e.key) { + case 'ArrowRight': + e.preventDefault(); + if (html.dir == 'rtl') { + prev(); + } else { + next(); + } + break; + case 'ArrowLeft': + e.preventDefault(); + if (html.dir == 'rtl') { + next(); + } else { + prev(); + } + break; + } + }); +})(); + +(function clipboard() { + var clipButtons = document.querySelectorAll('.clip-button'); + + function hideTooltip(elem) { + elem.firstChild.innerText = ""; + elem.className = 'fa fa-copy clip-button'; + } + + function showTooltip(elem, msg) { + elem.firstChild.innerText = msg; + elem.className = 'fa fa-copy tooltipped'; + } + + var clipboardSnippets = new ClipboardJS('.clip-button', { + text: function (trigger) { + hideTooltip(trigger); + let playground = trigger.closest("pre"); + return playground_text(playground, false); + } + }); + + Array.from(clipButtons).forEach(function (clipButton) { + clipButton.addEventListener('mouseout', function (e) { + hideTooltip(e.currentTarget); + }); + }); + + clipboardSnippets.on('success', function (e) { + e.clearSelection(); + showTooltip(e.trigger, "Copied!"); + }); + + clipboardSnippets.on('error', function (e) { + showTooltip(e.trigger, "Clipboard error!"); + }); +})(); + +(function scrollToTop () { + var menuTitle = document.querySelector('.menu-title'); + + menuTitle.addEventListener('click', function () { + document.scrollingElement.scrollTo({ top: 0, behavior: 'smooth' }); + }); +})(); + +(function controllMenu() { + var menu = document.getElementById('menu-bar'); + + (function controllPosition() { + var scrollTop = document.scrollingElement.scrollTop; + var prevScrollTop = scrollTop; + var minMenuY = -menu.clientHeight - 50; + // When the script loads, the page can be at any scroll (e.g. if you reforesh it). + menu.style.top = scrollTop + 'px'; + // Same as parseInt(menu.style.top.slice(0, -2), but faster + var topCache = menu.style.top.slice(0, -2); + menu.classList.remove('sticky'); + var stickyCache = false; // Same as menu.classList.contains('sticky'), but faster + document.addEventListener('scroll', function () { + scrollTop = Math.max(document.scrollingElement.scrollTop, 0); + // `null` means that it doesn't need to be updated + var nextSticky = null; + var nextTop = null; + var scrollDown = scrollTop > prevScrollTop; + var menuPosAbsoluteY = topCache - scrollTop; + if (scrollDown) { + nextSticky = false; + if (menuPosAbsoluteY > 0) { + nextTop = prevScrollTop; + } + } else { + if (menuPosAbsoluteY > 0) { + nextSticky = true; + } else if (menuPosAbsoluteY < minMenuY) { + nextTop = prevScrollTop + minMenuY; + } + } + if (nextSticky === true && stickyCache === false) { + menu.classList.add('sticky'); + stickyCache = true; + } else if (nextSticky === false && stickyCache === true) { + menu.classList.remove('sticky'); + stickyCache = false; + } + if (nextTop !== null) { + menu.style.top = nextTop + 'px'; + topCache = nextTop; + } + prevScrollTop = scrollTop; + }, { passive: true }); + })(); + (function controllBorder() { + function updateBorder() { + if (menu.offsetTop === 0) { + menu.classList.remove('bordered'); + } else { + menu.classList.add('bordered'); + } + } + updateBorder(); + document.addEventListener('scroll', updateBorder, { passive: true }); + })(); +})(); diff --git a/book/book.toml b/book/book.toml new file mode 100644 index 0000000000..4f0033fb46 --- /dev/null +++ b/book/book.toml @@ -0,0 +1,21 @@ +[book] +title = "Cross-platform Rust Components" +authors = ["Firefox SACI Team"] +src = "." + +[build] +build-dir = "book" +create-missing = false + +[output.html] +additional-css = ["shared/a-s.css", "shared/mermaid.css"] +additional-js = ["shared/tabs.js", "shared/mermaid.min.js", "shared/mermaid-init.js"] +git-repository-url = "https://github.com/mozilla/application-services" +git-branch = "main" +mathjax-support = true + +[preprocessor.open-on-gh] +command = "mdbook-open-on-gh" + +[preprocessor.mermaid] +command = "mdbook-mermaid" diff --git a/book/build-and-publish-pipeline.html b/book/build-and-publish-pipeline.html new file mode 100644 index 0000000000..afe64452e7 --- /dev/null +++ b/book/build-and-publish-pipeline.html @@ -0,0 +1,356 @@ + + + + + + CI Publishing tools and flow - Cross-platform Rust Components + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Application Services Build and Publish Pipeline

+

This document provides an overview of the build-and-publish pipeline used to make our work +in this repo available to consuming applications. It's intended both to document the pipeline +for development and maintenance purposes, and to serve as a basic analysis of the integrity +protections that it offers (so you'll notice there are notes and open questions in place where +we haven't fully hashed out all those details).

+

The key points:

+
    +
  • We use "stable" Rust. CI is pinned to whatever version is currently used on mozilla-central +to help with vendoring into that repository. You should check what current values are +specified for CircleCI and for TaskCluster
  • +
  • We use Cargo for building and testing the core Rust code in isolation, +Gradle with rust-android-gradle +for combining Rust and Kotlin code into Android components and running tests against them, +and XCframeworks driving XCode +for combining Rust and Swift code into iOS components.
  • +
  • TaskCluster runs on every pull-request, release, +and push to main, to ensure Android artifacts build correctly and to execute their +tests via gradle.
  • +
  • CircleCI runs on every branch, pull-request (including forks), and release, +to execute lint checks and automated tests at the Rust and Swift level.
  • +
  • Releases align with the Firefox Releases schedules, and nightly releases are automated to run daily see the releases for more information
  • +
  • Notifications about build failures are sent to a mailing list at +a-s-ci-failures@mozilla.com
  • +
  • Our Taskcluster implementation is almost entirely maintained by the Release Engineering team. +The proper way to contact them in case of emergency or for new developments is to ask on the #releaseduty-mobile Slack channel. +Our main point of contact is @mihai.
  • +
+

For Android consumers these are the steps by which Application Services code becomes available, +and the integrity-protection mechanisms that apply at each step:

+
    +
  1. Code is developed in branches and lands on main via pull request. +
      +
    • GitHub branch protection prevents code being pushed to main without review.
    • +
    • CircleCI and TaskCluster run automated tests against the code, but do not have +the ability to push modified code back to GitHub thanks to the above branch protection. +
        +
      • TaskCluster jobs do not run against PRs opened by the general public, +only for PRs from repo collaborators.
      • +
      +
    • +
    • Contra the github org security guidelines, +signing of individual commits is encouraged but is not required. Our experience in practice +has been that this adds friction for contributors without sufficient tangible benefit.
    • +
    +
  2. +
  3. Developers manually create a release from latest main. +
      +
    • The ability to create new releases is managed entirely via github's permission model.
    • +
    • TODO: the github org security guidelines +recommend signing tags, and auditing all included commits as part of the release process. +We should consider some tooling to support this. I don't think there's any way to force +githib to only accept signed releases in the same way it can enforce signed commits.
    • +
    +
  4. +
  5. TaskCluster checks out the release tag, builds it for all target platforms, and runs automated tests. +
      +
    • These tasks run in a pre-built docker image, helping assure integrity of the build environment.
    • +
    • TODO: could this step check for signed tags as an additional integrity measure?
    • +
    +
  6. +
  7. TaskCluster uploads symbols to Socorro. +
      +
    • The access token for this is currently tied to @eoger's LDAP account.
    • +
    +
  8. +
  9. TaskCluster uploads built artifacts to maven.mozilla.org +
      +
    • Secret key for uploading to maven is provisioned via TaskCluster, +guarded by a scope that's only available to this task.
    • +
    • TODO: could a malicious dev dependency from step (3) influence the build environment here?
    • +
    • TODO: talk about how TC's "chain of trust" might be useful here.
    • +
    +
  10. +
  11. Consumers fetch the published artifacts from maven.mozilla.org.
  12. +
+

For iOS consumers the corresponding steps are:

+
    +
  1. Code is developed in branches and lands on main via pull request, as above.
  2. +
  3. Developers manually create a release from latest main, as above.
  4. +
  5. CircleCI checks out the release tag, builds it, and runs automated tests. +
      +
    • TODO: These tasks bootstrap their build environment by fetching software over https. +could we do more to ensure the integrity of the build environment?
    • +
    • TODO: could this step check for signed tags as an additional integrity measure?
    • +
    • TODO: can we prevent these steps from being able to see the tokens used +for publishing in subsequent steps?
    • +
    +
  6. +
  7. CircleCI builds a binary artifact: +
      +
    • An XCFramework containing just Rust code and header files, as a zipfile, for use by Swift Packags.
    • +
    • TODO: could a malicious dev dependency from step (3) influence the build environment here?
    • +
    +
  8. +
  9. CircleCI uses dpl to publish to GitHub as a release artifact. + +
  10. +
  11. Consumers add Application services as a dependency from the Rust Components Swift repo using Apple's Swift Package Manager.
  12. +
+

For consuming in mozilla-central, see how to vendor components into mozilla-central +

+

This is a diagram of the pipeline as it exists (and is planned) for the Nimbus SDK, one of the +libraries in Application Services: +(Source: https://miro.com/app/board/o9J_lWx3jhY=/)

+

Nimbus SDK Build and Publish Pipeline

+

Authentication and secrets

+

@appsvc-moz account

+

There's an appsvc-moz github account owned by one of the application-services team (currently markh, but we should consider rotating ownership). +Given only 1 2fa device can be connected to a github account, multiple owners doesn't seem practical. +In most cases, whenever a github account needs to own a secret for any CI, it will be owned by this account.

+

CircleCI

+

CircleCI config requires a github token (owned by @appsvc-moz). This is a "personal access token" +obtained via github's Settings -> Developer Settings -> Personal Access Tokens -> Classic Token. This token:

+
    +
  • Should be named something like "circleci"
  • +
  • Have "no expiration" (XXX - this seems wrong, should we adjust?)
  • +
+

Once you have generated the token, it must be added to https://app.circleci.com/settings/project/github/mozilla/application-services/environment-variables as the environment variable GITHUB_TOKEN

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + diff --git a/book/building.html b/book/building.html new file mode 100644 index 0000000000..941c6535a9 --- /dev/null +++ b/book/building.html @@ -0,0 +1,410 @@ + + + + + + Building - Cross-platform Rust Components + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Building Application Services

+

When working on Application Services, it's important to set up your environment for building the Rust code and the Android or iOS code needed by the application.

+

First time builds

+

Building for the first time is more complicated than a typical Rust project. +To build for an end-to-end experience that enables you to test changes in +client applications like Firefox for Android (Fenix) and Firefox iOS, there are a number of build +systems required for all the dependencies. The initial setup is likely to take +a number of hours to complete.

+

Building the Rust Components

+

Complete this section before moving to the android/iOS build instructions.

+
    +
  1. Make sure you cloned the repository:
  2. +
+
  $ git clone https://github.com/mozilla/application-services # (or use the ssh link)
+  $ cd application-services
+  $ git submodule update --init --recursive
+
+
    +
  1. +

    Install Rust: install via rustup

    +
  2. +
  3. +

    Install your system dependencies:

    +

    Linux

    +
      +
    1. +

      Install the system dependencies required for building NSS

      +
        +
      1. Install gyp: apt install gyp (required for NSS)
      2. +
      3. Install ninja-build: apt install ninja-build
      4. +
      5. Install python3 (at least 3.6): apt install python3
      6. +
      7. Install zlib: apt install zlib1g-dev
      8. +
      9. Install perl (needed to build openssl): apt install perl
      10. +
      11. Install patch (to build the libs): apt install patch
      12. +
      +
    2. +
    3. +

      Install the system dependencies required for SQLcipher

      +
        +
      1. Install tcl: apt install tclsh (required for SQLcipher)
      2. +
      +
    4. +
    +

    MacOS

    +
      +
    1. Install Xcode: check the ci config for the correct version.
    2. +
    3. Install Xcode tools: xcode-select --install
    4. +
    5. Install homebrew via its installation instructions (it's what we use for ci).
    6. +
    7. Install the system dependencies required for building NSS: +
        +
      1. Install ninja and python: brew install ninja python
      2. +
      3. Make sure which python3 maps to the freshly installed homebrew python. +
          +
        1. If it isn't, add the following to your bash/zsh profile and source the profile before continuing: +
          alias python3=$(brew --prefix)/bin/python3
          +
          +
        2. +
        3. Ensure python maps to the same Python version. You may have to +create a symlink: +
          PYPATH=$(which python3); ln -s $PYPATH `dirname $PYPATH`/python
          +
          +
        4. +
        +
      4. +
      5. Install gyp: +
        wget https://bootstrap.pypa.io/ez_setup.py -O - | python3 -
        +git clone https://chromium.googlesource.com/external/gyp.git ~/tools/gyp
        +cd ~/tools/gyp
        +python3 setup.py install
        +
        +
          +
        1. Add ~/tools/gyp to your path: +
          export PATH="~/tools/gyp:$PATH"
          +
          +
        2. +
        3. If you have additional questions, consult this guide.
        4. +
        +
      6. +
      7. Make sure your homebrew python's bin folder is on your path by updating your bash/zsh profile with the following: +
        export PATH="$PATH:$(brew --prefix)/opt/python@3.9/Frameworks/Python.framework/Versions/3.9/bin"
        +
        +
      8. +
      +
    8. +
    +

    Windows

    +

    Install windows build tools

    +
    +

    Why Windows Subsystem for Linux (WSL)?

    +

    It's currently tricky to get some of these builds working on Windows, primarily due to our use of SQLcipher. By using WSL it is possible to get builds working, but still have them published to your "native" local maven cache so it's available for use by a "native" Android Studio.

    +
    +
      +
    1. Install WSL (recommended over native tooling)
    2. +
    3. Install unzip: sudo apt install unzip
    4. +
    5. Install python3: sudo apt install python3 Note: must be python 3.6 or later
    6. +
    7. Install system build tools: sudo apt install build-essential
    8. +
    9. Install zlib: sudo apt-get install zlib1g-dev
    10. +
    11. Install tcl: sudo apt install tcl-dev
    12. +
    +
  4. +
  5. +

    Check dependencies and environment variables by running: ./libs/verify-desktop-environment.sh

    +
  6. +
+
+

Note that this script might instruct you to set some environment variables, set those by adding them to your +.zshrc or .bashrc so they are set by default on your terminal. If it does so instruct you, you must +run the command again after setting them so the libraries are built.

+
+
    +
  1. Run cargo test: cargo test
  2. +
+

Once you have successfully run ./libs/verify-desktop-environment.sh and cargo test you can move to the Building for Fenix and Building for iOS sections below to setup your local environment for testing with our client applications.

+
+

Building for Fenix

+

The following instructions assume that you are building application-services for Fenix, and want to take advantage of the +Fenix Auto-publication workflow for android-components and application-services.

+
    +
  1. Install Android SDK, JAVA, NDK and set required env vars +
      +
    1. Clone the firefox-android repository (not inside the Application Service repository).
    2. +
    3. Install Java 17 for your system
    4. +
    5. Set JAVA_HOME to point to the JDK 17 installation directory.
    6. +
    7. Download and install Android Studio.
    8. +
    9. Set ANDROID_SDK_ROOT and ANDROID_HOME to the Android Studio sdk location and add it to your rc file (either .zshrc or .bashrc depending on the shell you use for your terminal).
    10. +
    11. Configure the required versions of NDK +Configure menu > System Settings > Android SDK > SDK Tools > NDK > Show Package Details > NDK (Side by side) +
        +
      • 21.4.7075529 (required by Fenix; note: a specific NDK version isn't configured, this maps to default NDK version for the AGP version)
      • +
      • 25.2.9519653 (required by Application Services, as configured)
      • +
      +
    12. +
    +
  2. +
  3. If you are on Windows using WSL - drop to the section below, Windows setup +for Android (WSL) before proceeding.
  4. +
  5. Check dependencies, environment variables +
      +
    1. Run ./libs/verify-android-environment.sh
    2. +
    3. Follow instructions and rerun until it is successful.
    4. +
    +
  6. +
+

Windows setup for Android (via WSL)

+

Note: For non-Ubuntu linux versions, it may be necessary to execute $ANDROID_HOME/tools/bin/sdkmanager "build-tools;26.0.2" "platform-tools" "platforms;android-26" "tools". See also this gist for additional information.

+

Configure Maven

+

Configure maven to use the native windows maven repository - then, when doing ./gradlew install from WSL, it ends up in the Windows maven repo. This means we can do a number of things with Android Studio in "native" windows and have then work correctly with stuff we built in WSL.

+
    +
  1. Install maven: sudo apt install maven
  2. +
  3. Confirm existence of (or create) a ~/.m2 folder
  4. +
  5. In the ~/.m2 create a file called settings.xml
  6. +
  7. Add the content below replacing {username} with your username:
  8. +
+
    <settings>
+      <localRepository>/mnt/c/Users/{username}/.m2/repository</localRepository>
+    </settings>
+
+
+

Building for Firefox iOS

+
    +
  1. Install xcpretty: gem install xcpretty
  2. +
  3. Run ./libs/verify-ios-environment.sh to check your setup and environment +variables.
  4. +
  5. Make any corrections recommended by the script and re-run.
  6. +
  7. Next, run ./megazords/ios-rust/build-xcframework.sh to build all the binaries needed to consume a-s in iOS
  8. +
+

Once the script passes, you should be able to run the Xcode project.

+
+

Note: The built Xcode project is located at megazords/ios-rust/MozillaTestServices.xcodeproj.

+
+
+

Note: This is mainly for testing the rust components, the artifact generated in the above steps should be all you need for building application with application-services

+
+

Locally building Firefox iOS against a local Application Services

+

Detailed steps to build Firefox iOS against a local application services can be found this document

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + diff --git a/book/clipboard.min.js b/book/clipboard.min.js new file mode 100644 index 0000000000..02c549e35c --- /dev/null +++ b/book/clipboard.min.js @@ -0,0 +1,7 @@ +/*! + * clipboard.js v2.0.4 + * https://zenorocha.github.io/clipboard.js + * + * Licensed MIT © Zeno Rocha + */ +!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return function(n){var o={};function r(t){if(o[t])return o[t].exports;var e=o[t]={i:t,l:!1,exports:{}};return n[t].call(e.exports,e,e.exports,r),e.l=!0,e.exports}return r.m=n,r.c=o,r.d=function(t,e,n){r.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:n})},r.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return r.d(e,"a",e),e},r.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},r.p="",r(r.s=0)}([function(t,e,n){"use strict";var r="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},i=function(){function o(t,e){for(var n=0;n + + + + + Contributing - Cross-platform Rust Components + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Contributing to Application Services

+

Anyone is welcome to help with the Application Services project. Feel free to get in touch with other community members on Matrix or through issues on GitHub.

+

Participation in this project is governed by the +Mozilla Community Participation Guidelines.

+

Bug Reports

+

You can file issues on GitHub. Please try to include as much information as you can and under what conditions +you saw the issue.

+

Building the project

+

Build instructions are available in the building page. Please let us know if you encounter any pain-points setting up your environment.

+

Finding issues

+

Below are a few different queries you can use to find appropriate issues to work on. Feel free to reach out if you need any additional clarification before picking up an issue.

+
    +
  • good first issues - If you are a new contributor, search for issues labeled good-first-issue
  • +
  • good second issues - Once you've got that first PR approved and you are looking for something a little more challenging, we are keeping a list of next-level issues. Search for the good-second-issue label.
  • +
  • papercuts - A collection of smaller sized issues that may be a bit more advanced than a first or second issue.
  • +
  • important, but not urgent - For more advanced contributors, we have a collection of issues that we consider important and would like to resolve sooner, but work isn't currently prioritized by the core team.
  • +
+

Sending Pull Requests

+

Patches should be submitted as pull requests (PRs).

+
+

When submitting PRs, We expect external contributors to push patches to a fork of application-services. For more information about submitting PRs from forks, read GitHub's guide.

+
+

Before submitting a PR:

+
    +
  • Your patch should include new tests that cover your changes, or be accompanied by explanation for why it doesn't need any. It is your and your reviewer's responsibility to ensure your patch includes adequate tests. +
      +
    • Consult the testing guide for some tips on writing effective tests.
    • +
    +
  • +
  • Your code should pass all the automated tests before you submit your PR for review. +
      +
    • Before pushing your changes, run ./automation/tests.py changes. The script will calculate which components were changed and run test suites, linters and formatters against those components. Because the script runs a limited set of tests, the script should execute in a fairly reasonable amount of time. +
        +
      • If you have modified any Swift code, also run swiftformat --swiftversion 5 on the modified code.
      • +
      +
    • +
    +
  • +
  • Your patch should include a changelog entry in CHANGELOG.md or an explanation of why +it does not need one. Any breaking changes to Swift or Kotlin binding APIs should be noted explicitly.
  • +
  • If your patch adds new dependencies, they must follow our dependency management guidelines. +Please include a summary of the due diligence applied in selecting new dependencies.
  • +
  • After you open a PR, our Continuous Integration system will run a full test suite. It's possible that this step will result in errors not caught with the script so make sure to check the results.
  • +
  • "Work in progress" pull requests are welcome, but should be clearly labeled as such and should not be merged until all tests pass and the code has been reviewed. +
      +
    • You can label pull requests as "Work in progress" by using the Github PR UI to indicate this PR is a draft (learn more about draft PRs).
    • +
    +
  • +
+

When submitting a PR:

+
    +
  • You agree to license your code under the project's open source license (MPL 2.0).
  • +
  • Base your branch off the current main branch.
  • +
  • Add both your code and new tests if relevant.
  • +
  • Please do not include merge commits in pull requests; include only commits with the new relevant code.
  • +
  • We encourage you to GPG sign your commits.
  • +
+

Code Review

+

This project is production Mozilla code and subject to our engineering practices and quality standards. Every patch must be peer reviewed by a member of the Application Services team.

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + diff --git a/book/css/chrome.css b/book/css/chrome.css new file mode 100644 index 0000000000..2314f7a161 --- /dev/null +++ b/book/css/chrome.css @@ -0,0 +1,587 @@ +/* CSS for UI elements (a.k.a. chrome) */ + +@import 'variables.css'; + +html { + scrollbar-color: var(--scrollbar) var(--bg); +} +#searchresults a, +.content a:link, +a:visited, +a > .hljs { + color: var(--links); +} + +/* + body-container is necessary because mobile browsers don't seem to like + overflow-x on the body tag when there is a tag. +*/ +#body-container { + /* + This is used when the sidebar pushes the body content off the side of + the screen on small screens. Without it, dragging on mobile Safari + will want to reposition the viewport in a weird way. + */ + overflow-x: clip; +} + +/* Menu Bar */ + +#menu-bar, +#menu-bar-hover-placeholder { + z-index: 101; + margin: auto calc(0px - var(--page-padding)); +} +#menu-bar { + position: relative; + display: flex; + flex-wrap: wrap; + background-color: var(--bg); + border-block-end-color: var(--bg); + border-block-end-width: 1px; + border-block-end-style: solid; +} +#menu-bar.sticky, +.js #menu-bar-hover-placeholder:hover + #menu-bar, +.js #menu-bar:hover, +.js.sidebar-visible #menu-bar { + position: -webkit-sticky; + position: sticky; + top: 0 !important; +} +#menu-bar-hover-placeholder { + position: sticky; + position: -webkit-sticky; + top: 0; + height: var(--menu-bar-height); +} +#menu-bar.bordered { + border-block-end-color: var(--table-border-color); +} +#menu-bar i, #menu-bar .icon-button { + position: relative; + padding: 0 8px; + z-index: 10; + line-height: var(--menu-bar-height); + cursor: pointer; + transition: color 0.5s; +} +@media only screen and (max-width: 420px) { + #menu-bar i, #menu-bar .icon-button { + padding: 0 5px; + } +} + +.icon-button { + border: none; + background: none; + padding: 0; + color: inherit; +} +.icon-button i { + margin: 0; +} + +.right-buttons { + margin: 0 15px; +} +.right-buttons a { + text-decoration: none; +} + +.left-buttons { + display: flex; + margin: 0 5px; +} +.no-js .left-buttons button { + display: none; +} + +.menu-title { + display: inline-block; + font-weight: 200; + font-size: 2.4rem; + line-height: var(--menu-bar-height); + text-align: center; + margin: 0; + flex: 1; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.js .menu-title { + cursor: pointer; +} + +.menu-bar, +.menu-bar:visited, +.nav-chapters, +.nav-chapters:visited, +.mobile-nav-chapters, +.mobile-nav-chapters:visited, +.menu-bar .icon-button, +.menu-bar a i { + color: var(--icons); +} + +.menu-bar i:hover, +.menu-bar .icon-button:hover, +.nav-chapters:hover, +.mobile-nav-chapters i:hover { + color: var(--icons-hover); +} + +/* Nav Icons */ + +.nav-chapters { + font-size: 2.5em; + text-align: center; + text-decoration: none; + + position: fixed; + top: 0; + bottom: 0; + margin: 0; + max-width: 150px; + min-width: 90px; + + display: flex; + justify-content: center; + align-content: center; + flex-direction: column; + + transition: color 0.5s, background-color 0.5s; +} + +.nav-chapters:hover { + text-decoration: none; + background-color: var(--theme-hover); + transition: background-color 0.15s, color 0.15s; +} + +.nav-wrapper { + margin-block-start: 50px; + display: none; +} + +.mobile-nav-chapters { + font-size: 2.5em; + text-align: center; + text-decoration: none; + width: 90px; + border-radius: 5px; + background-color: var(--sidebar-bg); +} + +/* Only Firefox supports flow-relative values */ +.previous { float: left; } +[dir=rtl] .previous { float: right; } + +/* Only Firefox supports flow-relative values */ +.next { + float: right; + right: var(--page-padding); +} +[dir=rtl] .next { + float: left; + right: unset; + left: var(--page-padding); +} + +/* Use the correct buttons for RTL layouts*/ +[dir=rtl] .previous i.fa-angle-left:before {content:"\f105";} +[dir=rtl] .next i.fa-angle-right:before { content:"\f104"; } + +@media only screen and (max-width: 1080px) { + .nav-wide-wrapper { display: none; } + .nav-wrapper { display: block; } +} + +/* sidebar-visible */ +@media only screen and (max-width: 1380px) { + #sidebar-toggle-anchor:checked ~ .page-wrapper .nav-wide-wrapper { display: none; } + #sidebar-toggle-anchor:checked ~ .page-wrapper .nav-wrapper { display: block; } +} + +/* Inline code */ + +:not(pre) > .hljs { + display: inline; + padding: 0.1em 0.3em; + border-radius: 3px; +} + +:not(pre):not(a) > .hljs { + color: var(--inline-code-color); + overflow-x: initial; +} + +a:hover > .hljs { + text-decoration: underline; +} + +pre { + position: relative; +} +pre > .buttons { + position: absolute; + z-index: 100; + right: 0px; + top: 2px; + margin: 0px; + padding: 2px 0px; + + color: var(--sidebar-fg); + cursor: pointer; + visibility: hidden; + opacity: 0; + transition: visibility 0.1s linear, opacity 0.1s linear; +} +pre:hover > .buttons { + visibility: visible; + opacity: 1 +} +pre > .buttons :hover { + color: var(--sidebar-active); + border-color: var(--icons-hover); + background-color: var(--theme-hover); +} +pre > .buttons i { + margin-inline-start: 8px; +} +pre > .buttons button { + cursor: inherit; + margin: 0px 5px; + padding: 3px 5px; + font-size: 14px; + + border-style: solid; + border-width: 1px; + border-radius: 4px; + border-color: var(--icons); + background-color: var(--theme-popup-bg); + transition: 100ms; + transition-property: color,border-color,background-color; + color: var(--icons); +} +@media (pointer: coarse) { + pre > .buttons button { + /* On mobile, make it easier to tap buttons. */ + padding: 0.3rem 1rem; + } +} +pre > code { + padding: 1rem; +} + +/* FIXME: ACE editors overlap their buttons because ACE does absolute + positioning within the code block which breaks padding. The only solution I + can think of is to move the padding to the outer pre tag (or insert a div + wrapper), but that would require fixing a whole bunch of CSS rules. +*/ +.hljs.ace_editor { + padding: 0rem 0rem; +} + +pre > .result { + margin-block-start: 10px; +} + +/* Search */ + +#searchresults a { + text-decoration: none; +} + +mark { + border-radius: 2px; + padding-block-start: 0; + padding-block-end: 1px; + padding-inline-start: 3px; + padding-inline-end: 3px; + margin-block-start: 0; + margin-block-end: -1px; + margin-inline-start: -3px; + margin-inline-end: -3px; + background-color: var(--search-mark-bg); + transition: background-color 300ms linear; + cursor: pointer; +} + +mark.fade-out { + background-color: rgba(0,0,0,0) !important; + cursor: auto; +} + +.searchbar-outer { + margin-inline-start: auto; + margin-inline-end: auto; + max-width: var(--content-max-width); +} + +#searchbar { + width: 100%; + margin-block-start: 5px; + margin-block-end: 0; + margin-inline-start: auto; + margin-inline-end: auto; + padding: 10px 16px; + transition: box-shadow 300ms ease-in-out; + border: 1px solid var(--searchbar-border-color); + border-radius: 3px; + background-color: var(--searchbar-bg); + color: var(--searchbar-fg); +} +#searchbar:focus, +#searchbar.active { + box-shadow: 0 0 3px var(--searchbar-shadow-color); +} + +.searchresults-header { + font-weight: bold; + font-size: 1em; + padding-block-start: 18px; + padding-block-end: 0; + padding-inline-start: 5px; + padding-inline-end: 0; + color: var(--searchresults-header-fg); +} + +.searchresults-outer { + margin-inline-start: auto; + margin-inline-end: auto; + max-width: var(--content-max-width); + border-block-end: 1px dashed var(--searchresults-border-color); +} + +ul#searchresults { + list-style: none; + padding-inline-start: 20px; +} +ul#searchresults li { + margin: 10px 0px; + padding: 2px; + border-radius: 2px; +} +ul#searchresults li.focus { + background-color: var(--searchresults-li-bg); +} +ul#searchresults span.teaser { + display: block; + clear: both; + margin-block-start: 5px; + margin-block-end: 0; + margin-inline-start: 20px; + margin-inline-end: 0; + font-size: 0.8em; +} +ul#searchresults span.teaser em { + font-weight: bold; + font-style: normal; +} + +/* Sidebar */ + +.sidebar { + position: fixed; + left: 0; + top: 0; + bottom: 0; + width: var(--sidebar-width); + font-size: 0.875em; + box-sizing: border-box; + -webkit-overflow-scrolling: touch; + overscroll-behavior-y: contain; + background-color: var(--sidebar-bg); + color: var(--sidebar-fg); +} +[dir=rtl] .sidebar { left: unset; right: 0; } +.sidebar-resizing { + -moz-user-select: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; +} +.no-js .sidebar, +.js:not(.sidebar-resizing) .sidebar { + transition: transform 0.3s; /* Animation: slide away */ +} +.sidebar code { + line-height: 2em; +} +.sidebar .sidebar-scrollbox { + overflow-y: auto; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + padding: 10px 10px; +} +.sidebar .sidebar-resize-handle { + position: absolute; + cursor: col-resize; + width: 0; + right: 0; + top: 0; + bottom: 0; +} +[dir=rtl] .sidebar .sidebar-resize-handle { right: unset; left: 0; } +.js .sidebar .sidebar-resize-handle { + cursor: col-resize; + width: 5px; +} +/* sidebar-hidden */ +#sidebar-toggle-anchor:not(:checked) ~ .sidebar { + transform: translateX(calc(0px - var(--sidebar-width))); + z-index: -1; +} +[dir=rtl] #sidebar-toggle-anchor:not(:checked) ~ .sidebar { + transform: translateX(var(--sidebar-width)); +} +.sidebar::-webkit-scrollbar { + background: var(--sidebar-bg); +} +.sidebar::-webkit-scrollbar-thumb { + background: var(--scrollbar); +} + +/* sidebar-visible */ +#sidebar-toggle-anchor:checked ~ .page-wrapper { + transform: translateX(var(--sidebar-width)); +} +[dir=rtl] #sidebar-toggle-anchor:checked ~ .page-wrapper { + transform: translateX(calc(0px - var(--sidebar-width))); +} +@media only screen and (min-width: 620px) { + #sidebar-toggle-anchor:checked ~ .page-wrapper { + transform: none; + margin-inline-start: var(--sidebar-width); + } + [dir=rtl] #sidebar-toggle-anchor:checked ~ .page-wrapper { + transform: none; + } +} + +.chapter { + list-style: none outside none; + padding-inline-start: 0; + line-height: 2.2em; +} + +.chapter ol { + width: 100%; +} + +.chapter li { + display: flex; + color: var(--sidebar-non-existant); +} +.chapter li a { + display: block; + padding: 0; + text-decoration: none; + color: var(--sidebar-fg); +} + +.chapter li a:hover { + color: var(--sidebar-active); +} + +.chapter li a.active { + color: var(--sidebar-active); +} + +.chapter li > a.toggle { + cursor: pointer; + display: block; + margin-inline-start: auto; + padding: 0 10px; + user-select: none; + opacity: 0.68; +} + +.chapter li > a.toggle div { + transition: transform 0.5s; +} + +/* collapse the section */ +.chapter li:not(.expanded) + li > ol { + display: none; +} + +.chapter li.chapter-item { + line-height: 1.5em; + margin-block-start: 0.6em; +} + +.chapter li.expanded > a.toggle div { + transform: rotate(90deg); +} + +.spacer { + width: 100%; + height: 3px; + margin: 5px 0px; +} +.chapter .spacer { + background-color: var(--sidebar-spacer); +} + +@media (-moz-touch-enabled: 1), (pointer: coarse) { + .chapter li a { padding: 5px 0; } + .spacer { margin: 10px 0; } +} + +.section { + list-style: none outside none; + padding-inline-start: 20px; + line-height: 1.9em; +} + +/* Theme Menu Popup */ + +.theme-popup { + position: absolute; + left: 10px; + top: var(--menu-bar-height); + z-index: 1000; + border-radius: 4px; + font-size: 0.7em; + color: var(--fg); + background: var(--theme-popup-bg); + border: 1px solid var(--theme-popup-border); + margin: 0; + padding: 0; + list-style: none; + display: none; + /* Don't let the children's background extend past the rounded corners. */ + overflow: hidden; +} +[dir=rtl] .theme-popup { left: unset; right: 10px; } +.theme-popup .default { + color: var(--icons); +} +.theme-popup .theme { + width: 100%; + border: 0; + margin: 0; + padding: 2px 20px; + line-height: 25px; + white-space: nowrap; + text-align: start; + cursor: pointer; + color: inherit; + background: inherit; + font-size: inherit; +} +.theme-popup .theme:hover { + background-color: var(--theme-hover); +} + +.theme-selected::before { + display: inline-block; + content: "✓"; + margin-inline-start: -14px; + width: 14px; +} diff --git a/book/css/general.css b/book/css/general.css new file mode 100644 index 0000000000..e7d20da725 --- /dev/null +++ b/book/css/general.css @@ -0,0 +1,234 @@ +/* Base styles and content styles */ + +@import 'variables.css'; + +:root { + /* Browser default font-size is 16px, this way 1 rem = 10px */ + font-size: 62.5%; + color-scheme: var(--color-scheme); +} + +html { + font-family: "Open Sans", sans-serif; + color: var(--fg); + background-color: var(--bg); + text-size-adjust: none; + -webkit-text-size-adjust: none; +} + +body { + margin: 0; + font-size: 1.6rem; + overflow-x: hidden; +} + +code { + font-family: var(--mono-font) !important; + font-size: var(--code-font-size); + direction: ltr !important; +} + +/* make long words/inline code not x overflow */ +main { + overflow-wrap: break-word; +} + +/* make wide tables scroll if they overflow */ +.table-wrapper { + overflow-x: auto; +} + +/* Don't change font size in headers. */ +h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { + font-size: unset; +} + +.left { float: left; } +.right { float: right; } +.boring { opacity: 0.6; } +.hide-boring .boring { display: none; } +.hidden { display: none !important; } + +h2, h3 { margin-block-start: 2.5em; } +h4, h5 { margin-block-start: 2em; } + +.header + .header h3, +.header + .header h4, +.header + .header h5 { + margin-block-start: 1em; +} + +h1:target::before, +h2:target::before, +h3:target::before, +h4:target::before, +h5:target::before, +h6:target::before { + display: inline-block; + content: "»"; + margin-inline-start: -30px; + width: 30px; +} + +/* This is broken on Safari as of version 14, but is fixed + in Safari Technology Preview 117 which I think will be Safari 14.2. + https://bugs.webkit.org/show_bug.cgi?id=218076 +*/ +:target { + /* Safari does not support logical properties */ + scroll-margin-top: calc(var(--menu-bar-height) + 0.5em); +} + +.page { + outline: 0; + padding: 0 var(--page-padding); + margin-block-start: calc(0px - var(--menu-bar-height)); /* Compensate for the #menu-bar-hover-placeholder */ +} +.page-wrapper { + box-sizing: border-box; + background-color: var(--bg); +} +.no-js .page-wrapper, +.js:not(.sidebar-resizing) .page-wrapper { + transition: margin-left 0.3s ease, transform 0.3s ease; /* Animation: slide away */ +} +[dir=rtl] .js:not(.sidebar-resizing) .page-wrapper { + transition: margin-right 0.3s ease, transform 0.3s ease; /* Animation: slide away */ +} + +.content { + overflow-y: auto; + padding: 0 5px 50px 5px; +} +.content main { + margin-inline-start: auto; + margin-inline-end: auto; + max-width: var(--content-max-width); +} +.content p { line-height: 1.45em; } +.content ol { line-height: 1.45em; } +.content ul { line-height: 1.45em; } +.content a { text-decoration: none; } +.content a:hover { text-decoration: underline; } +.content img, .content video { max-width: 100%; } +.content .header:link, +.content .header:visited { + color: var(--fg); +} +.content .header:link, +.content .header:visited:hover { + text-decoration: none; +} + +table { + margin: 0 auto; + border-collapse: collapse; +} +table td { + padding: 3px 20px; + border: 1px var(--table-border-color) solid; +} +table thead { + background: var(--table-header-bg); +} +table thead td { + font-weight: 700; + border: none; +} +table thead th { + padding: 3px 20px; +} +table thead tr { + border: 1px var(--table-header-bg) solid; +} +/* Alternate background colors for rows */ +table tbody tr:nth-child(2n) { + background: var(--table-alternate-bg); +} + + +blockquote { + margin: 20px 0; + padding: 0 20px; + color: var(--fg); + background-color: var(--quote-bg); + border-block-start: .1em solid var(--quote-border); + border-block-end: .1em solid var(--quote-border); +} + +.warning { + margin: 20px; + padding: 0 20px; + border-inline-start: 2px solid var(--warning-border); +} + +.warning:before { + position: absolute; + width: 3rem; + height: 3rem; + margin-inline-start: calc(-1.5rem - 21px); + content: "ⓘ"; + text-align: center; + background-color: var(--bg); + color: var(--warning-border); + font-weight: bold; + font-size: 2rem; +} + +blockquote .warning:before { + background-color: var(--quote-bg); +} + +kbd { + background-color: var(--table-border-color); + border-radius: 4px; + border: solid 1px var(--theme-popup-border); + box-shadow: inset 0 -1px 0 var(--theme-hover); + display: inline-block; + font-size: var(--code-font-size); + font-family: var(--mono-font); + line-height: 10px; + padding: 4px 5px; + vertical-align: middle; +} + +:not(.footnote-definition) + .footnote-definition, +.footnote-definition + :not(.footnote-definition) { + margin-block-start: 2em; +} +.footnote-definition { + font-size: 0.9em; + margin: 0.5em 0; +} +.footnote-definition p { + display: inline; +} + +.tooltiptext { + position: absolute; + visibility: hidden; + color: #fff; + background-color: #333; + transform: translateX(-50%); /* Center by moving tooltip 50% of its width left */ + left: -8px; /* Half of the width of the icon */ + top: -35px; + font-size: 0.8em; + text-align: center; + border-radius: 6px; + padding: 5px 8px; + margin: 5px; + z-index: 1000; +} +.tooltipped .tooltiptext { + visibility: visible; +} + +.chapter li.part-title { + color: var(--sidebar-fg); + margin: 5px 0px; + font-weight: bold; +} + +.result-no-output { + font-style: italic; +} diff --git a/book/css/print.css b/book/css/print.css new file mode 100644 index 0000000000..dcf0ba64b9 --- /dev/null +++ b/book/css/print.css @@ -0,0 +1,50 @@ + +#sidebar, +#menu-bar, +.nav-chapters, +.mobile-nav-chapters { + display: none; +} + +#page-wrapper.page-wrapper { + transform: none; + margin-inline-start: 0px; + overflow-y: initial; +} + +#content { + max-width: none; + margin: 0; + padding: 0; +} + +.page { + overflow-y: initial; +} + +code { + direction: ltr !important; +} + +pre > .buttons { + z-index: 2; +} + +a, a:visited, a:active, a:hover { + color: #4183c4; + text-decoration: none; +} + +h1, h2, h3, h4, h5, h6 { + page-break-inside: avoid; + page-break-after: avoid; +} + +pre, code { + page-break-inside: avoid; + white-space: pre-wrap; +} + +.fa { + display: none !important; +} diff --git a/book/css/variables.css b/book/css/variables.css new file mode 100644 index 0000000000..10a7590944 --- /dev/null +++ b/book/css/variables.css @@ -0,0 +1,277 @@ + +/* Globals */ + +:root { + --sidebar-width: 300px; + --page-padding: 15px; + --content-max-width: 750px; + --menu-bar-height: 50px; + --mono-font: "Source Code Pro", Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", monospace, monospace; + --code-font-size: 0.875em /* please adjust the ace font size accordingly in editor.js */ +} + +/* Themes */ + +.ayu { + --bg: hsl(210, 25%, 8%); + --fg: #c5c5c5; + + --sidebar-bg: #14191f; + --sidebar-fg: #c8c9db; + --sidebar-non-existant: #5c6773; + --sidebar-active: #ffb454; + --sidebar-spacer: #2d334f; + + --scrollbar: var(--sidebar-fg); + + --icons: #737480; + --icons-hover: #b7b9cc; + + --links: #0096cf; + + --inline-code-color: #ffb454; + + --theme-popup-bg: #14191f; + --theme-popup-border: #5c6773; + --theme-hover: #191f26; + + --quote-bg: hsl(226, 15%, 17%); + --quote-border: hsl(226, 15%, 22%); + + --warning-border: #ff8e00; + + --table-border-color: hsl(210, 25%, 13%); + --table-header-bg: hsl(210, 25%, 28%); + --table-alternate-bg: hsl(210, 25%, 11%); + + --searchbar-border-color: #848484; + --searchbar-bg: #424242; + --searchbar-fg: #fff; + --searchbar-shadow-color: #d4c89f; + --searchresults-header-fg: #666; + --searchresults-border-color: #888; + --searchresults-li-bg: #252932; + --search-mark-bg: #e3b171; + + --color-scheme: dark; +} + +.coal { + --bg: hsl(200, 7%, 8%); + --fg: #98a3ad; + + --sidebar-bg: #292c2f; + --sidebar-fg: #a1adb8; + --sidebar-non-existant: #505254; + --sidebar-active: #3473ad; + --sidebar-spacer: #393939; + + --scrollbar: var(--sidebar-fg); + + --icons: #43484d; + --icons-hover: #b3c0cc; + + --links: #2b79a2; + + --inline-code-color: #c5c8c6; + + --theme-popup-bg: #141617; + --theme-popup-border: #43484d; + --theme-hover: #1f2124; + + --quote-bg: hsl(234, 21%, 18%); + --quote-border: hsl(234, 21%, 23%); + + --warning-border: #ff8e00; + + --table-border-color: hsl(200, 7%, 13%); + --table-header-bg: hsl(200, 7%, 28%); + --table-alternate-bg: hsl(200, 7%, 11%); + + --searchbar-border-color: #aaa; + --searchbar-bg: #b7b7b7; + --searchbar-fg: #000; + --searchbar-shadow-color: #aaa; + --searchresults-header-fg: #666; + --searchresults-border-color: #98a3ad; + --searchresults-li-bg: #2b2b2f; + --search-mark-bg: #355c7d; + + --color-scheme: dark; +} + +.light { + --bg: hsl(0, 0%, 100%); + --fg: hsl(0, 0%, 0%); + + --sidebar-bg: #fafafa; + --sidebar-fg: hsl(0, 0%, 0%); + --sidebar-non-existant: #aaaaaa; + --sidebar-active: #1f1fff; + --sidebar-spacer: #f4f4f4; + + --scrollbar: #8F8F8F; + + --icons: #747474; + --icons-hover: #000000; + + --links: #20609f; + + --inline-code-color: #301900; + + --theme-popup-bg: #fafafa; + --theme-popup-border: #cccccc; + --theme-hover: #e6e6e6; + + --quote-bg: hsl(197, 37%, 96%); + --quote-border: hsl(197, 37%, 91%); + + --warning-border: #ff8e00; + + --table-border-color: hsl(0, 0%, 95%); + --table-header-bg: hsl(0, 0%, 80%); + --table-alternate-bg: hsl(0, 0%, 97%); + + --searchbar-border-color: #aaa; + --searchbar-bg: #fafafa; + --searchbar-fg: #000; + --searchbar-shadow-color: #aaa; + --searchresults-header-fg: #666; + --searchresults-border-color: #888; + --searchresults-li-bg: #e4f2fe; + --search-mark-bg: #a2cff5; + + --color-scheme: light; +} + +.navy { + --bg: hsl(226, 23%, 11%); + --fg: #bcbdd0; + + --sidebar-bg: #282d3f; + --sidebar-fg: #c8c9db; + --sidebar-non-existant: #505274; + --sidebar-active: #2b79a2; + --sidebar-spacer: #2d334f; + + --scrollbar: var(--sidebar-fg); + + --icons: #737480; + --icons-hover: #b7b9cc; + + --links: #2b79a2; + + --inline-code-color: #c5c8c6; + + --theme-popup-bg: #161923; + --theme-popup-border: #737480; + --theme-hover: #282e40; + + --quote-bg: hsl(226, 15%, 17%); + --quote-border: hsl(226, 15%, 22%); + + --warning-border: #ff8e00; + + --table-border-color: hsl(226, 23%, 16%); + --table-header-bg: hsl(226, 23%, 31%); + --table-alternate-bg: hsl(226, 23%, 14%); + + --searchbar-border-color: #aaa; + --searchbar-bg: #aeaec6; + --searchbar-fg: #000; + --searchbar-shadow-color: #aaa; + --searchresults-header-fg: #5f5f71; + --searchresults-border-color: #5c5c68; + --searchresults-li-bg: #242430; + --search-mark-bg: #a2cff5; + + --color-scheme: dark; +} + +.rust { + --bg: hsl(60, 9%, 87%); + --fg: #262625; + + --sidebar-bg: #3b2e2a; + --sidebar-fg: #c8c9db; + --sidebar-non-existant: #505254; + --sidebar-active: #e69f67; + --sidebar-spacer: #45373a; + + --scrollbar: var(--sidebar-fg); + + --icons: #737480; + --icons-hover: #262625; + + --links: #2b79a2; + + --inline-code-color: #6e6b5e; + + --theme-popup-bg: #e1e1db; + --theme-popup-border: #b38f6b; + --theme-hover: #99908a; + + --quote-bg: hsl(60, 5%, 75%); + --quote-border: hsl(60, 5%, 70%); + + --warning-border: #ff8e00; + + --table-border-color: hsl(60, 9%, 82%); + --table-header-bg: #b3a497; + --table-alternate-bg: hsl(60, 9%, 84%); + + --searchbar-border-color: #aaa; + --searchbar-bg: #fafafa; + --searchbar-fg: #000; + --searchbar-shadow-color: #aaa; + --searchresults-header-fg: #666; + --searchresults-border-color: #888; + --searchresults-li-bg: #dec2a2; + --search-mark-bg: #e69f67; + + --color-scheme: light; +} + +@media (prefers-color-scheme: dark) { + .light.no-js { + --bg: hsl(200, 7%, 8%); + --fg: #98a3ad; + + --sidebar-bg: #292c2f; + --sidebar-fg: #a1adb8; + --sidebar-non-existant: #505254; + --sidebar-active: #3473ad; + --sidebar-spacer: #393939; + + --scrollbar: var(--sidebar-fg); + + --icons: #43484d; + --icons-hover: #b3c0cc; + + --links: #2b79a2; + + --inline-code-color: #c5c8c6; + + --theme-popup-bg: #141617; + --theme-popup-border: #43484d; + --theme-hover: #1f2124; + + --quote-bg: hsl(234, 21%, 18%); + --quote-border: hsl(234, 21%, 23%); + + --warning-border: #ff8e00; + + --table-border-color: hsl(200, 7%, 13%); + --table-header-bg: hsl(200, 7%, 28%); + --table-alternate-bg: hsl(200, 7%, 11%); + + --searchbar-border-color: #aaa; + --searchbar-bg: #b7b7b7; + --searchbar-fg: #000; + --searchbar-shadow-color: #aaa; + --searchresults-header-fg: #666; + --searchresults-border-color: #98a3ad; + --searchresults-li-bg: #2b2b2f; + --search-mark-bg: #355c7d; + } +} diff --git a/book/dependency-management.html b/book/dependency-management.html new file mode 100644 index 0000000000..4ee7a1cdfb --- /dev/null +++ b/book/dependency-management.html @@ -0,0 +1,332 @@ + + + + + + Dependency management - Cross-platform Rust Components + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Dependency Management Guidelines

+

This repository uses third-party code from a variety of sources, so we need to be mindful +of how these dependencies will affect our consumers. Considerations include:

+ +

We're still evolving our policies in this area, but these are the +guidelines we've developed so far.

+

Rust Code

+

Unlike Firefox, +we do not vendor third-party source code directly into the repository. Instead we rely on +Cargo.lock and its hash validation to ensure that each build uses an identical copy +of all third-party crates. These are the measures we use for ongoing maintence of our +existing dependencies:

+
    +
  • Check Cargo.lock into the repository.
  • +
  • Generate built artifacts using the --locked flag to cargo build, as an additional +assurance that the existing Cargo.lock will be respected.
  • +
  • Regularly run cargo-audit in CI to alert us to +security problems in our dependencies. +
      +
    • It runs on every PR, and once per hour on the main branch
    • +
    +
  • +
  • Use a home-grown tool to generate a summary of dependency licenses +and to check them for compatibility with MPL-2.0. +
      +
    • Check these summaries into the repository and have CI alert on unexpected changes, +to guard against pulling in new versions of a dependency under a different license.
    • +
    +
  • +
+

Adding a new dependency, whether we like it or not, is a big deal - that dependency and everything +it brings with it will become part of Firefox-branded products that we ship to end users. +We try to balance this responsibility against the many benefits of using existing code, as follows:

+
    +
  • In general, be conservative in adding new third-party dependencies. +
      +
    • For trivial functionality, consider just writing it yourself. +Remember the cautionary tale of left-pad.
    • +
    • Check if we already have a crate in our dependency tree that can provide the needed functionality.
    • +
    +
  • +
  • Prefer crates that have a a high level of due-dilligence already applied, such as: + +
  • +
  • Check that it is clearly licensed and is MPL-2.0 compatible.
  • +
  • Take the time to investigate the crate's source and ensure it is suitably high-quality. +
      +
    • Be especially wary of uses of unsafe, or of code that is unusually resource-intensive to build.
    • +
    • Dev dependencies do not require as much scrutiny as dependencies that will ship in consuming applications, +but should still be given some thought. +
        +
      • There is still the potential for supply-chain compromise with dev dependencies!
      • +
      +
    • +
    +
  • +
  • As part of the PR that introduces the new dependency: + +
  • +
+

Updating to new versions of existing dependencies is a normal part of software development +and is not accompanied by any particular ceremony.

+

Android/Kotlin Code

+

We currently depend only on the following Kotlin dependencies:

+ +

We currently depend on the following developer dependencies in the Kotlin codebase, +but they do not get included in built distribution files:

+
    +
  • detekt
  • +
  • ktlint
  • +
+

No additional Kotlin dependencies should be added to the project unless absolutely necessary.

+

iOS/Swift Code

+

We currently do not depend on any Swift dependencies. And no Swift dependencies should be added to the project unless absolutely necessary.

+

Other Code

+

We currently depend on local builds of the following system dependencies:

+ +

No additional system dependencies should be added to the project unless absolutely necessary.

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + diff --git a/book/design/components-strategy.html b/book/design/components-strategy.html new file mode 100644 index 0000000000..eebb1a7bbd --- /dev/null +++ b/book/design/components-strategy.html @@ -0,0 +1,281 @@ + + + + + + Rust Component's Strategy - Cross-platform Rust Components + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

High level firefox sync interactions

+

On a high level, Firefox Sync has three main components:

+
    +
  • The Firefox Account Server: Which uses oauth to authenticate and provide users with scoped access. The FxA Server also stores input that will +be used by the clients to generate the sync keys.
  • +
  • Firefox: This is the firefox app itself, which implements the client logic to communicate with the firefox account servers, generate sync keys, +use them to encrypt data and send/receive encrypted data to/from the sync +storage servers
  • +
  • Sync Storage Server: The server that stores encrypted sync data. The clients would retrieve the encrypted data and decrypt +it client side
  • +
+

Additionally, the token server assists in providing metadata to Firefox, so that it knows which sync server to communicate with. +Diagram showing on a high level, how Firefox sync interacts with Firefox Accounts and Sync Services

+

Multi-platform sync diagram

+

Since we have multiple Firefox apps (Desktop, iOS, Android, Focus, etc) Firefox sync can sync across platforms. Allowing users +to access their up-to-date data across apps and devices. +Diagram showing how firefox sync is a multi-platform feature

+

Before: How sync was

+

Before our Rust Components came to life, each application had its own implementation of the sync and FxA client protocols. +This lead to duplicate logic across platforms. This was problematic since any modification to the sync or FxA client business logic +would need to be modified in all implementations and the likelihood of errors was high. +Diagram showing how firefox sync used to be, with each platform having its own implementation

+

Now: Sync is starting to streamline its components

+

Currently, we are in the process of migrating many of the sync implementation to use our Rust Component strategy. +Fenix primarily uses our Rust Components and iOS has some integrated as well. Additionally, Firefox Desktop also uses +one Rust component (Web Extension Storage).

+

The Rust components not only unify the different implementations of sync, they also provide a convenient local storage for the apps. +In other words, the apps can use the components for storage, with or without syncing to the server. +Diagram showing how firefox sync is now, with iOS and Fenix platform sharing some implementations

+

Current Status

+

The following table has the status of each of our sync Rust Components +| Application\Component | Bookmarks | History | Tabs | Passwords | Autofill | Web Extension Storage | FxA Client | +|-----------------------|-----------|---------|------|-----------|----------|-----------------------|------------| +| Fenix | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | | ✔️ | +| Firefox iOS | ✔️ | | ✔️ | ✔️ | | | ✔️ | +| Firefox Desktop | | | | | | ✔️ | | +| Focus | | | | | | | |

+

Future: Only one implementation for each sync engine

+

In an aspirational future, all the applications would use the same implementation for Sync. +However, it's unlikely that we would migrate everything to use the Rust components since some implementations +may not be prioritized, this is especially true for desktop which already has stable implementations. +That said, we can get close to this future and minimize duplicate logic and the likelihood of errors. +Diagram showing how firefox sync should be, with all platforms using one implementation

+

You can edit the diagrams in the following lucid chart (Note: Currently only Mozilla Employees can edit those diagrams): https://lucid.app/lucidchart/invitations/accept/inv_ab72e218-3ad9-4604-a7cd-7e0b0c259aa2

+

Once they are edited, you can re-import them here by replacing the old diagrams in the docs/diagrams directory on GitHub. As long as the +names are the same, you shouldn't need to edit those docs!

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + diff --git a/book/design/db-pragmas.html b/book/design/db-pragmas.html new file mode 100644 index 0000000000..5716d6bf7f --- /dev/null +++ b/book/design/db-pragmas.html @@ -0,0 +1,253 @@ + + + + + + Sqlite Database Pragma Usage - Cross-platform Rust Components + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Sqlite Database Pragma Usage

+

The data below has been added as a tool for future pragma analysis work and is expected to be useful so long as our pragma usage remains stable or this doc is kept up-to-date. This should help us understand our current pragma usage and where we may be able to make improvements.

+
+ + + + + + + + +
PragmaValueComponentNotes
cache_size-6144places
foreign_keysONautofill, places, tabs, webext-storage
journal_modeWALautofill, places, tabs, webext-storage
page_size32768places
secure_deletetruelogins
temp_store2autofill, logins, places, tabs, webext_storageSetting temp_store to 2 (MEMORY) is necessary to avoid SQLITE_IOERR_GETTEMPPATH errors on Android (see here for details)
wal_autocheckpoint62places
wal_checkpointPASSIVEplacesUsed in the sync finished step in history and bookmarks syncing and in the places run_maintenance function
+
+
    +
  • The user_version pragma is excluded because the value varies and sqlite does not do anything with the value.
  • +
  • The push component does not implement any of the commonly used pragmas noted above.
  • +
  • The sqlcipher pragmas that we set have been excluded from this list as we are trying to remove sqlcipher and do not want to encourage future use.
  • +
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + diff --git a/book/design/index.html b/book/design/index.html new file mode 100644 index 0000000000..571deb36cb --- /dev/null +++ b/book/design/index.html @@ -0,0 +1,245 @@ + + + + + + Design - Cross-platform Rust Components + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Design Documents

+ + +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + diff --git a/book/design/megazords.html b/book/design/megazords.html new file mode 100644 index 0000000000..931bb46e98 --- /dev/null +++ b/book/design/megazords.html @@ -0,0 +1,299 @@ + + + + + + Megazords - Cross-platform Rust Components + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Megazording

+

Each Rust component published by Application Services is conceptually a stand-alone library, but for +distribution we compile all the rust code for all components together into a single .so file. This +has a number of advantages:

+
    +
  • Easy and direct interoperability between different components at the Rust level
  • +
  • Cross-component optimization of generated code
  • +
  • Reduced code size thanks to distributing a single copy of the rust stdlib, low-level dependencies, etc.
  • +
+

This process is affectionately known as "megazording" and the resulting artifact as a megazord library.

+

On Android, the situation is quite complex due to the way packages and dependencies are managed. +We need to distribute each component as a separate Android ARchive (AAR) that can be managed as a dependency +via gradle, we need to provide a way for the application to avoid shipping rust code for components that it +isn't using, and we need to do it in a way that maintanins the advantages listed above.

+

This document describes our current approach to meeting all those requirements on Android. Other platforms +such as iOS are not considered.

+

AAR Dependency Graph

+

We publish a separate AAR for each component (e.g. fxaclient, places, logins) which contains +just the Kotlin wrappers that expose the relevant functionality to Android. Each of these AARs depends on a separate +shared "megazord" AAR in which all the rust code has been compiled together into a single .so file. +The application's dependency graph thus looks like this:

+

megazord dependency diagram

+

This generates a kind of strange inversion of dependencies in our build pipeline:

+
    +
  • Each individual component defines both a rust crate and an Android AAR.
  • +
  • There is a special "full-megazord" component that also defines a rust crate and an Android AAR.
  • +
  • The full-megazord rust crate depends on the rust crates for each individual component.
  • +
  • But the Android AAR for each component depends on the Android AAR of the full-megazord!
  • +
+

It's a little odd, but it has the benefit that we can use gradle's dependency-replacement features to easily +manage the rust code that is shipping in each application.

+

Custom Megazords

+

By default, an application that uses any appservices component will include the compiled rust code +for all appservices components.

+

To reduce its overall code size, the application can use gradle's module replacement +rules +to replace the "full-megazord" AAR with a custom-built megazord AAR containing only the components it requires. +Such an AAR can be built in the same way as the "full-megazord", and simply avoid depending on the rust +crates for components that are not required.

+

To help ensure this replacement is done safely at runtime, the mozilla.appservices.support.native package +provides helper functions for loading the correct megazord .so file. The Kotlin wrapper for each component +should load its shared library by calling mozilla.appservices.support.native.loadIndirect, specifying both +the name of the component and the expected version number of the shared library.

+

Unit Tests

+

The full-megazord AAR contains compiled rust code that targets various Android platforms, and is not +suitable for running on a Desktop development machine. In order to support integration with unittest +suites such as robolectric, each megazord has a corresponding Java ARchive (JAR) distribution named e.g. +full-megazord-forUnitTests.jar. This contains the rust code compiled for various Desktop architectures, +and consumers can add it to their classpath when running tests on a Desktop machine.

+

Gotchas and Rough Edges

+

This setup mostly works, but has a handful of rough edges.

+

The build.gradle for each component needs to declare an explicit dependency on project(":full-megazord"), +otherwise the resulting AAR will not be able to locate the compiled rust code at runtime. It also needs to +declare a dependency between its build task and that of the full-megazord, for reasons. Typically this looks something +like:

+
tasks["generate${productFlavor}${buildType}Assets"].dependsOn(project(':full-megazord').tasks["cargoBuild"])
+
+

In order for unit tests to work correctly, the build.gradle for each component needs to add the rustJniLibs +directory of the full-megazord project to its srcDirs, otherwise the unittests will not be able to find and load +the compiled rust code. Typically this looks something like:

+
test.resources.srcDirs += "${project(':full-megazord').buildDir}/rustJniLibs/desktop"
+
+

The above also means that unittests will not work correctly when doing local composite builds, +because it's unreasonable to expect the main project (e.g. Fenix) to include the above in its build scripts.

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + diff --git a/book/design/metrics.html b/book/design/metrics.html new file mode 100644 index 0000000000..a8b850902d --- /dev/null +++ b/book/design/metrics.html @@ -0,0 +1,240 @@ + + + + + + Metrics - (Glean Telemetry) - Cross-platform Rust Components + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Metrics collected by Application Services components

+

Some application-services components collect telemetry using the Glean SDK.

+

Products that send telemetry via Glean must request a data-review following +the Firefox Data Collection process +before integrating any of the components listed below.

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + diff --git a/book/design/rust-versions.html b/book/design/rust-versions.html new file mode 100644 index 0000000000..c886f01867 --- /dev/null +++ b/book/design/rust-versions.html @@ -0,0 +1,288 @@ + + + + + + Rust Version Policy - Cross-platform Rust Components + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Rust Versions

+

Like almost all Rust projects, the entire point of the application-services +components is that they be used by external projects. If these components +use Rust features available in only the very latest Rust version, this will +cause problems for projects which aren't always able to be on that latest +version.

+

Given application-services is currently developed and maintained by Mozilla +staff, it should be no surprise that an important consideration is +mozilla-central (aka, the main Firefox repository).

+

Mozilla-central Rust policies.

+

It should also come as no surprise that the Rust policy for mozilla-central +is somewhat flexible. There is an official Rust Update Policy Document + +but everything in the future is documented as "estimated".

+

Ultimately though, that page defines 2 Rust versions - "Uses" and "Requires", +and our policy revolves around these.

+

To discover the current, actual "Uses" version, there is a Meta bug on Bugzilla that keeps +track of the latest versions as they are upgraded.

+

To discover the current, actual "Requires" version, see searchfox

+

application-services Rust version policy

+

Our official Rust version policy is:

+
    +
  • +

    All components will ship using, have all tests passing, and have clippy emit +no warnings, with the same version mozilla-central currently "uses".

    +
  • +
  • +

    All components must be capable of building (although not necessarily with +all tests passing nor without clippy errors or other warnings) with the same +version mozilla-central currently "requires".

    +
  • +
  • +

    This policy only applies to the "major" and "minor" versions - a different +patch level is still considered compliant with this policy.

    +
  • +
+

Implications of this

+

All CI for this project will try and pin itself to this same version. At +time of writing, this means that our circle CI integration + and +rust-toolchain configuration +will specify the versions (and where possible, the CI configuration file will +avoid duplicating the information in rust-toolchain)

+

We should maintain CI to ensure we still build with the "Requires" version.

+

As versions inside mozilla-central change, we will bump these versions +accordingly. While newer versions of Rust can be expected to work correctly +with our existing code, it's likely that clippy will complain in various ways +with the new version. Thus, a PR to bump the minimum version is likely to also +require a PR to make changes which keep clippy happy.

+

In the interests of avoiding redundant information which will inevitably +become stale, the circleci and rust-toolchain configuration links above +should be considered the canonical source of truth for the currently supported +official Rust version.

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + diff --git a/book/design/swift-package-manager.html b/book/design/swift-package-manager.html new file mode 100644 index 0000000000..999ece88b8 --- /dev/null +++ b/book/design/swift-package-manager.html @@ -0,0 +1,302 @@ + + + + + + Shipping Rust Components as Swift Packages - Cross-platform Rust Components + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

High level design for shipping Rust Components as Swift Packages

+
+

This is a high level description of the decision highlighted in the ADR that introduced Swift Packages as a strategy to ship our Rust components. That document includes that tradeoffs and why we chose this approach.

+
+ +A box diagram describing how the rust-components-swift repo, applicaiton-services repo, and MozillaRustComponents XCFramework interact +

The strategy includes two main parts:

+
    +
  • The xcframework that is built from a megazord. The xcframework contains the following, built for all our target iOS platforms. +
      +
    • The compiled Rust code for all the crates listed in Cargo.toml as a static library
    • +
    • The C header files and Swift module maps for the components
    • +
    +
  • +
  • The rust-components-swift repository which has a Package.swift that includes the xcframework and acts as the swift package the consumers import
  • +
+

The xcframework and application-services

+

In application-services, in the megazords/ios-rust directory, we have the following:

+
    +
  • A Rust crate that serves as the megazord for our iOS distributions. The megazord depends on all the Rust Component crates and re-exports their public APIs.
  • +
  • Some skeleton files for building an xcframework: +1. module.modulemap: The module map tells the Swift compiler how to use C APIs. +1. MozillaRustComponents.h: The header is used by the module map as a shortcut to specify all the available header files +1. Info.plist: The plist file specifies metadata about the resulting xcframework. For example, architectures and subdirectories.
  • +
  • The build-xcframework.sh script that stitches things together into a full xcframework bundle: +
      +
    • The xcframework format is not well documented; briefly: +
        +
      • The xcframework is a directory containing the resources compiled for multiple target architectures. The xcframework is distributed as a .zip file.
      • +
      • The top-level directory contains a subdirectory per architecture and an Info.plist. The Info.plist describes what lives in which directory.
      • +
      • Each subdirectory represents an architecture. And contains a .framework directory for that architecture.
      • +
      +
    • +
    +
  • +
+
+

It's a little unusual that we're building the xcframework by hand, rather than defining it as the build output of an Xcode project. It turns out to be simpler for our purposes, but does risk diverging from the expected format if Apple changes the details of xcframeworks in future Xcode releases.

+
+

The rust-components-swift repository

+

The repository is a Swift Package for distributing releases of Mozilla's various Rust-based application components. It provides the Swift source code packaged in a format understood by the Swift package manager, and depends on a pre-compiled binary release of the underlying Rust code published from application-services

+

The rust-components-swift repo mainly includes the following:

+
    +
  • Package.swift: Defines all the targets and products the package exposes. +
      +
    • Package.swift also includes where the package gets the xcframework that application-services builds
    • +
    +
  • +
  • make_tag.sh: A script that does the following: +
      +
    • Generates any dynamically generated Swift code, mainly: + +
    • +
    • Creates and commits a git tag that can be pushed to cut a release
    • +
    +
  • +
+
+

Consumers would then import the rust-components-swift swift package, by indicating the url of the package on github (i.e https://github.com/mozilla/rust-components-swift) and selecting a version using the git tag.

+
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + diff --git a/book/design/sync-manager.html b/book/design/sync-manager.html new file mode 100644 index 0000000000..441999b1b7 --- /dev/null +++ b/book/design/sync-manager.html @@ -0,0 +1,746 @@ + + + + + + Sync Manager - Cross-platform Rust Components + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Sync manager

+

We've identified the need for a "sync manager" (although are yet to identify a +good name for it!) This manager will be responsible for managing "global sync +state" and coordinating each engine.

+

At a very high level, the sync manager is responsible for all syncing. So far, +so obvious. However, given our architecture, it's possible to identify a +key architectural split.

+
    +
  • +

    The embedding application will be responsible for most high-level operations. +For example, the app itself will choose how often regular syncs should +happen, what environmental concerns apply (eg, should I only sync on WiFi?), +letting the user choose exactly what to sync, and so forth.

    +
  • +
  • +

    A lower-level component will be responsible for the direct interaction with +the engines and with the various servers needed to perform a sync. It will +also have the ultimate responsibility to not cause harm to the service (for +example, it will be likely to enforce some kind of rate limiting or ensuring +that service requests for backoff are enforced)

    +
  • +
+

Because all application-services engines are written in Rust, it's tempting to +suggest that this lower-level component also be written in Rust and everything +"just works", but there are a couple of complications here:

+
    +
  • +

    For iOS, we hope to integrate with older engines which aren't written in +Rust, even if iOS does move to the new Sync Manager.

    +
  • +
  • +

    For Desktop, we hope to start by reusing the existing "sync manager" +implemented by Desktop, and start moving individual engines across.

    +
  • +
  • +

    There may be some cross-crate issues even for the Rust implemented engines. +Or more specifically, we'd like to avoid assuming any particular linkage or +packaging of Rust implemented engines.

    +
  • +
+

Even with these complications, we expect there to be a number of high-level +components, each written in a platform specific language (eg, Kotlin or Swift) +and a single lower-level component to be implemented in Rust and delivered +as part of the application-services library - but that's not a free-pass.

+

Why "a number of high-level components"? Because that is the thing which +understands the requirements of the embedding application. For example, Android +may end up with a single high-level component in the android-components repo +and shared between all Android components. Alternatively, the Android teams +may decide the sync manager isn't generic enough to share, so each app will +have their own. iOS will probably end up with its own and you could imagine +a future where Desktop does too - but they should all be able to share the +low level component.

+

The responsibilities of the Sync Manager.

+

The primary responsibilities of the "high level" portion of the sync manager are:

+
    +
  • +

    Manage all FxA interaction. The low-level component will have a way to +communicate auth related problems, but it is the high-level component +which takes concrete FxA action.

    +
  • +
  • +

    Expose all UI for the user to choose what to sync and coordinate this with +the low-level component. Note that because these choices can be made on any +connected device, these choices must be communicated in both directions.

    +
  • +
  • +

    Implement timers or any other mechanism to fully implement the "sync +scheduler", including any policy decisions such as only syncing on WiFi, +etc.

    +
  • +
  • +

    Implement a UI so the user can "sync now".

    +
  • +
  • +

    Collect telemetry from the low-level component, probably augment it, then +submit it to the telemetry pipeline.

    +
  • +
+

The primary responsibilities of the "low level" portion of the sync manager are:

+
    +
  • +

    Manage the meta/global, crypto/keys and info/collections resources, +and interact with each engine as necessary based on the content of these +resources.

    +
  • +
  • +

    Manage interaction with the token server.

    +
  • +
  • +

    Enforce constraints necessary to ensure the entire ecosystem is not +subject to undue load. For example, this component should ignore attempts to +sync continuously, or to sync when the services have requested backoff.

    +
  • +
  • +

    Manage the "clients" collection - we probably can't ignore this any longer, +especially for bookmarks (as desktop will send a wipe command on bookmark +restore, and things will "be bad" if we don't see that command).

    +
  • +
  • +

    Define a minimal "service state" so certain things can be coordinated with +the high-level component. Examples of these states are "everything seems ok", +"the service requested we backoff for some period", "an authentication error +occurred", and possibly others.

    +
  • +
  • +

    Perform, or coordinate, the actual sync of the rust implemented engines - +from the containing app's POV, there's a single "sync now" entry-point (in +practice there might be a couple, but conceptually there's a single way to +sync). Note that as below, how non-rust implemented engines are managed is +TBD.

    +
  • +
  • +

    Manage the collection of (but not the submission of) telemetry from the +various engines.

    +
  • +
  • +

    Expose APIs and otherwise coordinate with the high-level component.

    +
  • +
+

Stuff we aren't quite sure where it fits include:

+
    +
  • Coordination with non-rust implemented engines. These engines are almost +certainly going to be implemented in the same language as the high-level +component, which will make integration simpler. However, the low-level +component will almost certainly need some information about these engines for +populating info/collections etc. For now, we are punting on this until things +become a bit clearer.
  • +
+

Implementation Details.

+

The above has been carefully written to try and avoid implementation details - +the intent is that it's an overview of the architecture without any specific +implementation decisions.

+

These next sections start getting specific, so implementation choices need to +be made, and thus will possibly be more contentious.

+

In other words, get your spray-cans ready because there's a bikeshed being built!

+

However, let's start small and make some general observations.

+

Current implementations and challenges with the Rust components

+
    +
  • +

    Some apps only care about a subset of the engines - lockbox is one such app +and only cares about a single collection/engine. It might be the case that +lockbox uses a generic application-services library with many engines +available, even though it only wants logins. Thus, the embedding application +is the only thing which knows which engines should be considered to "exist". +It may be that the app layer passes an engine to the sync manager, or the +sync manager knows via some magic how to obtain these handles.

    +
  • +
  • +

    Some apps will use a combination of Rust components and "legacy" +engines. For example, iOS is moving some of the engines to using Rust +components, while other engines will be ported after delivery of the +sync manager, if they are ported at all. We also plan +to introduce some rust engines into desktop without integrating the +"sync manager"

    +
  • +
  • +

    The rust components themselves are designed to be consumed as individual +components - the "logins" component doesn't know anything about the +"bookmarks" component.

    +
  • +
+

There are a couple of gotchyas in the current implementations too - there's an +issue when certain engines don't yet appear in meta/global - see bug 1479929 +for all the details.

+

The tl;dr of the above is that each rust component should be capable of +working with different sync managers. That said though, let's not over-engineer +this and pretend we can design a single, canonical thing that will not need +changing as we consider desktop and iOS.

+

State, state and more state. And then some state.

+

There's loads of state here. The app itself has some state. The high-level +Sync Manager component will have state, the low-level component will have state, +and each engine has state. Some of this state will need to be persisted (either +on the device or on the storage servers) and some of this state can be considered +ephemeral and lives only as long as the app.

+

A key challenge will be defining this state in a coherent way with clear +boundaries between them, in an effort to allow decoupling of the various bits +so Desktop and iOS can fit into this world.

+

This state management should also provide the correct degree of isolation for +the various components. For example, each engine should only know about state +which directly impacts how it works. For example, the keys used to encrypt +a collection should only be exposed to that specific engine, and there's no +need for one engine to know what info/collections returns for other engines, +nor whether the device is currently connected to WiFi.

+

A thorn here is for persisted state - it would be ideal if the low-level +component could avoid needing to persist any state, so it can avoid any +kind of storage abstraction. We have a couple of ways of managing this:

+
    +
  • +

    The state which needs to be persisted is quite small, so we could delegate +state storage to the high-level component in an opaque way, as this +high-level component almost certainly already has storage requirements, such +as storing the "choose what to sync" preferences.

    +
  • +
  • +

    The low-level component could add its own storage abstraction. This would +isolate the high-level component from this storage requirement, but would +add complexity to the sync manager - for example, it would need to be passed +a directory where it should create a file or database.

    +
  • +
+

We'll probably go with the former.

+

Implementation plan for the low-level component.

+

Let's try and move into actionable decisions for the implementation. We expect +the implementation of the low-level component to happen first, followed very +closely by the implementation of the high-level component for Android. So we +focus first on these.

+

Clients Engine

+

The clients engine includes some meta-data about each client. We've decided +we can't replace the clients engine with the FxA device record and we can't +simply drop this engine entirely.

+

Of particular interest is "commands" - these involve communicating with the +engine regarding commands targetting it, and accepting commands to be send to +other devices. Note that outgoing commands are likely to not originate from a sync, +but instead from other actions, such as "restore bookmarks".

+

However, because the only current requirement for commands is to wipe the +store, and because you could anticipate "wipe" also being used when remotely +disconnecting a device (eg, when a device is lost or stolen), our lives would +probably be made much simpler by initially supporting only per-engine wipe +commands.

+

Note that there has been some discussion about not implementing the client +engine and replacing "commands" with some other mechanism. However, we have +decided to not do that because the implementation isn't considered too +difficult, and because desktop will probably require a number of changes to +remove it (eg, "synced tabs" will not work correctly without a client record +with the same guid as the clients engine.)

+

Note however that unlike desktop, we will use the FxA device ID as the client +ID. Because FxA device IDs are more ephemeral than sync IDs, it will be +necessary for engines using this ID to track the most-recent ID they synced +with so the old record can be deleted when a change is detected.

+

Collections vs engines vs stores vs preferences vs Apis

+

For the purposes of the sync manager, we define:

+
    +
  • +

    An engine is the unit exposed to the user - an "engine" can be enabled +or disabled. There is a single set of canonical "engines" used across the +entire sync ecosystem - ie, desktop and mobile devices all need to agree +about what engines exist and what the identifier for an engine is.

    +
  • +
  • +

    An Api is the unit exposed to the application layer for general application +functionality. Application services has 'places' and 'logins' Apis and is +the API used by the application to store and fetch items. Each 'Api' may +have one or more 'stores' (although the application layer will generally not +interact directly with a store)

    +
  • +
  • +

    A store is the code which actually syncs. This is largely an implementation +detail. There may be multiple stores per engine (eg, the "history" engine +may have "history" and "forms" stores) and a single 'Api' may expose multiple +stores (eg, the "places Api" will expose history and bookmarks stores)

    +
  • +
  • +

    A collection is a unit of storage on a server. It's even more of an +implementation detail than a store. For example, you might imagine a future +where the "history" store uses multiple "collections" to help with containers.

    +
  • +
+

In practice, this means that the high-level component should only need to care +about an engine (for exposing a choice of what to sync to the user) and an +api (for interacting with the data managed by that api). The low-level +component will manage the mapping of engines to stores.

+

The declined list

+

This document isn't going to outline the history of how "declined" is used, nor +talk about how this might change in the future. For the purposes of the sync +manager, we have the following hard requirements:

+
    +
  • +

    The low-level component needs to know what the currently declined set of +engines is for the purposes of re-populating meta/global.

    +
  • +
  • +

    The low-level component needs to know when the declined set needs to change +based on user input (ie, when the user opts in to or out of a particular +engine on this device)

    +
  • +
  • +

    The high-level component needs to be aware that the set of declined engines +may change on every sync (ie, when the user opts in to or out of a particular +engine on another device)

    +
  • +
+

A complication is that due to networks being unreliable, there's an inherent +conflict between "what is the current state?" and "what state changes are +requested?". For example, if the user changes the state of an engine while +there's no network, then exits the app, how do we ensure the user's new state +is updated next time the app starts? What if the user has since made a +different state request on a different device? Is the state as last-known on +this device considered canonical?

+

To clarify, consider:

+
    +
  • +

    User on this device declines logins. This device now believes logins is +disabled but history is enabled, but is unable to write this to the server +due to no network.

    +
  • +
  • +

    The user declines history on a different device, but doesn't change logins. +This device does manage to write the new list to the server.

    +
  • +
  • +

    This device restarts and the network is up. It believes history is enabled +but logins is not - however, the state on the server is the exact opposite.

    +
  • +
+

How does this device react?

+

(On the plus side, this is an extreme edge-case which none of our existing +implementations handle "correctly" - which is easy to say, because there's +no real definition for "correctly")

+

Regardless, the low-level component will not pretend to hide this complexity +(ie, it will ignore it!). The low-level component will allow the app to ask +for state changes as part of a sync, and will return what's on the server at +the end of every sync. The app is then free to build whatever experience +it desires around this.

+

Disconnecting from Sync

+

The low-level component needs to have the ability to disconnect all engines +from Sync. Engines which are declined should also be reset.

+

Because we will need wipe() functionality to implement the clients engine, +and because Lockbox wants to wipe on disconnect, we will provide disconnect +and wipe functionality.

+

Specific deliverables for the low-level component.

+

Breaking the above down into actionable tasks which can be some somewhat +concurrently, we will deliver:

+

The API

+

A straw-man for the API we will expose to the high-level components. This +probably isn't too big, but we should do this as thoroughly as we can. In +particular, ensure we have covered:

+
    +
  • +

    Declined management - how the app changes the declined list and how it learns +of changes from other devices.

    +
  • +
  • +

    How telemetry gets handed from the low-level to the high-level.

    +
  • +
  • +

    The "state" - in particular, how the high-level component understands the +auth state is wrong, and whether the service is in a degraded mode (eg, +server requested backoff)

    +
  • +
  • +

    How the high-level component might specify "special" syncs, such as "just +one engine" or "this is a pre-sleep, quick-as-possible sync", etc

    +
  • +
+

There's a straw-man proposal for this at the end of the document.

+

A command-line (and possibly Android) utility.

+

We should build a utility (or 2) which can stand in for the high-level +component, for testing and demonstration purposes.

+

This is something like places-utils.rs and the little utility Grisha has +been using. This utility should act like a real client (ie, it should have +an FxA device record, etc) and it should use the low-level component in +exactly the same we we expect real products to use it.

+

Because it is just a consumer of the low-level component, it will force us to +confront some key issues, such as how to get references to engines stored in +multiple crates, how to present a unified "state" for things like auth errors, +etc.

+

The "clients" engine

+

The initial work for the clients engine can probably be done without too +much regard for how things are tied together - for example, much work could +be done without caring how we get a reference to engines across crates.

+

State work

+

Implementing things needed to we can expose the correct state to the high-level +manager for things like auth errors, backoff semantics, etc

+

Tie it together and other misc things.

+

There will be lots of loose ends to clean up - things like telemetry, etc.

+

Followup with non-rust engines.

+

We have identified that iOS will, at least in the short term, want the +sync manager to be implemented in Swift. This will be responsible for +syncing both the Swift and Rust implemented engines.

+

At some point in the future, Desktop may do the same - we will have both +Rust and JS implemented engines which need to be coordinated. We ignore this +requirement for now.

+

This approach still has a fairly easy time coordinating with the Rust +implemented engines - the FFI will need to expose the relevant sync +entry-points to be called by Swift, but the Swift code can hard-code the +Rust engines it has and make explicit calls to these entry-points.

+

This Swift code will need to create the structures identified below, but this +shouldn't be too much of a burden as it already has the information necessary +to do so (ie, it already has info/collections etc)

+

TODO: dig into the Swift code and make sure this is sane.

+

Details

+

While we use rust struct definitions here, it's important to keep in mind that +as mentioned above, we'll need to support the manager being written in +something other than rust, and to support engines written in other than rust.

+

The structures below are a straw-man, but hopefully capture all the information +that needs to be passed around.

+
#![allow(unused)]
+
+fn main() {
+// We want to define a list of "engine IDs" - ie, canonical strings which
+// refer to what the user perceives as an "enigine" - but as above, these
+// *do not* correspond 1:1 with either "stores" or "collections" (eg, "history"
+// refers to 2 stores, and in a future world, might involve 3 collections).
+enum Engine {
+  History, // The "History" and "Forms" stores.
+  Bookmarks, // The "Bookmark" store.
+  Passwords,
+}
+
+impl Engine {
+  fn as_str(&self) -> &'static str {
+    match self {
+      History => "history",
+      // etc
+  }
+}
+
+// A struct which reflects engine declined states.
+struct EngineState {
+  engine: Engine,
+  enabled: bool,
+}
+
+// A straw-man for the reasons why a sync is happening.
+enum SyncReason {
+  Scheduled,
+  User,
+  PreSleep,
+  Startup,
+}
+
+// A straw man for the general status.
+enum ServiceStatus {
+  Ok,
+  // Some general network issue.
+  NetworkError,
+  // Some apparent issue with the servers.
+  ServiceError,
+  // Some external FxA action needs to be taken.
+  AuthenticationError,
+  // We declined to do anything for backoff or rate-limiting reasons.
+  BackedOff,
+  // Something else - you need to check the logs for more details.
+  OtherError,
+}
+
+// Info we need from FxA to sync. This is roughly our Sync15StorageClientInit
+// structure with the FxA device ID.
+struct AccountInfo {
+  key_id: String,
+  access_token: String,
+  tokenserver_url: Url,
+  device_id: String,
+}
+
+// Instead of massive param and result lists, we use structures.
+// This structure is passed to each and every sync.
+struct SyncParams {
+  // The engines to Sync. None means "sync all"
+  engines: Option<Vec<Engine>>,
+  // Why this sync is being done.
+  reason: SyncReason,
+
+  // Any state changes which should be done as part of this sync.
+  engine_state_changes: Vec<EngineState>,
+
+  // An opaque state "blob". This should be persisted by the app so it is
+  // reused next sync.
+  persisted_state: Option<String>,
+}
+
+struct SyncResult {
+  // The general health.
+  service_status: ServiceStatus,
+
+  // The result for each engine.
+  engine_results: HashMap<Engine, Result<()>>,
+
+  // The list of declined engines, or None if we failed to get that far.
+  declined_engines: Option<Vec<Engine>>,
+
+  // When we are allowed to sync again. If > now() then there's some kind
+  // of back-off. Note that it's not strictly necessary for the app to
+  // enforce this (ie, it can keep asking us to sync, but we might decline).
+  // But we might not too - eg, we might try a user-initiated sync.
+  next_sync_allowed_at: Timestamp,
+
+  // New opaque state which should be persisted by the embedding app and supplied
+  // the next time Sync is called.
+  persisted_state: String,
+
+  // Telemetry. Nailing this down is tbd.
+  telemetry: Option<JSONValue>,
+}
+
+struct SyncManager {}
+
+impl SyncManager {
+  // Initialize the sync manager with the set of Engines known by this
+  // application without regard to the enabled/declined states.
+  // XXX - still TBD is how we will pass "stores" around - it may be that
+  // this function ends up taking an `impl Store`
+  fn init(&self, engines: Vec<&str>) -> Result<()>;
+
+  fn sync(&self, params: SyncParams) -> Result<SyncResult>;
+
+  // Interrupt any current syncs. Note that this can be called from a different
+  // thread.
+  fn interrupt() -> Result<()>;
+
+  // Disconnect this device from sync. This may "reset" the stores, but will
+  // not wipe local data.
+  fn disconnect(&self) -> Result<()>;
+
+  // Wipe all local data for all local stores. This can be done after
+  // disconnecting.
+  // There's no exposed way to wipe the remote store - while it's possible
+  // stores will want to do this, there's no need to expose this to the user.
+  fn wipe(&self) -> Result<()>;
+}
+}
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + diff --git a/book/design/sync-overview.html b/book/design/sync-overview.html new file mode 100644 index 0000000000..93f7e76635 --- /dev/null +++ b/book/design/sync-overview.html @@ -0,0 +1,315 @@ + + + + + + Sync overview - Cross-platform Rust Components + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Sync Overview

+

This document provides a high-level overview of how syncing works. Note: each component has its own quirks and will handle sync slightly differently than the general process described here.

+

General flow and architecture

+
    +
  • Crates involved: +
      +
    • The sync15 and support/sync15-traits handle the general syncing logic and define the SyncEngine trait
    • +
    • Individual component crates (logins, places, autofill, etc). These implement SyncEngine.
    • +
    • sync_manager manages the overall syncing process.
    • +
    +
  • +
  • High level sync flow: +
      +
    • Sync is initiated by the application that embeds application-services.
    • +
    • The application calls SyncManager.sync() to start the sync process.
    • +
    • SyncManager creates SyncEngine instances to sync the individual components. Each SyncEngine corresponds to a collection on the sync server.
    • +
    +
  • +
+

Sync manager

+

SyncManager is responsible for performing the high-level parts of the sync process:

+
    +
  • The consumer code calls it's sync() function to start the sync, passing +in a SyncParams object in, which describes what should be synced.
  • +
  • SyncManager performs all network operations on behalf of the individual engines. It's also responsible for tracking the general authentication state (primarily by inspecting the responses from these network requests) and fetching tokens from the token server.
  • +
  • SyncManager checks if we are currently in a backoff period and should wait before contacting the server again.
  • +
  • Before syncing any engines, the sync manager checks the state of the meta/global collection and compares it with the enabled engines specified in the SyncParams. This handles the cases when the user has requested an engine be enabled or disabled on this device, or when it was requested on a different device. (Note that engines enabled and disabled states are state on the account itself and not a per-device setting). Part of this process is comparing the collection's GUID on the server with the GUID known locally - if they are different, it implies some other device has "reset" the collection, so the engine drops all metadata and attempts to reconcile with every record on the server (ie, acts as though this is the very first sync this engine has ever done).
  • +
  • SyncManager instantiates a SyncEngine for each enabled component. We currently use 2 different methods for this: +
      +
    • The older method is for the SyncManager to hold a weakref to a Store use that to create the SyncEngine (tabs and places). The SyncEngine uses the Store for database access, see the TabsStore for an example.
    • +
    • The newer method is for the components to provide a function to create the SyncEngine, hiding the details of how that engine gets created (autofill/logins). These components also define a Store instance for the SyncEngine to use, but it's all transparent to the SyncManager. (See autofill::get_registered_sync_engine() and autofill::db::store::Store)
    • +
    +
  • +
  • For components that use local encryption, SyncManager passes the local encryption key to their SyncEngine
  • +
  • Finally, calls sync_multiple() function from the sync15 crate, sending it the SyncEngine instances. sync_multiple() then calls the sync() function for each individual SyncEngine
  • +
+

Sync engines

+
    +
  • SyncEngine is defined in the support/sync15-traits crate and defines the interface for syncing a component.
  • +
  • A new SyncEngine instance is created for each sync
  • +
  • SyncEngine.apply_incoming() does the main work. It is responsible for processing incoming records from the server in order to update the local records and calculating which local records should be synced back.
  • +
+

The apply_incoming pattern

+

SyncEngine instances are free to implement apply_incoming() any way they want, but the most components follow a general pattern.

+

Database Tables

+
    +
  • The local table stores records for the local application
  • +
  • The mirror table stores the last known record from the server
  • +
  • The staging temporary table stores the incoming records that we're currently processing
  • +
  • The local/mirror/staging tables contains a guid as its primary key. A record will share the same guid for the local/mirror/staging table.
  • +
  • The metadata table stores the GUID for the collection as a whole and the the last-known server timestamp of the collection.
  • +
+

apply_incoming stages

+
    +
  • stage incoming: write out each incoming server record to the staging table
  • +
  • fetch states: take the rows from all 3 tables and combine them into a single struct containing Options for the local/mirror/staging records.
  • +
  • iterate states: loop through each state, decide how to do change the local records, then execute that plan. +
      +
    • reconcile/plan: For each state we create an action plan for it. The action plan is a low-level description of what to change (add this record, delete this one, modify this field, etc). Here are some common situations: +
        +
      • A record only appears in the staging table. It's a new record from the server and should be added to the local DB
      • +
      • A record only appears in the local table. It's a new record on the local instance and should be synced back to the serve
      • +
      • Identical records appear in the local/mirror tables and a changed record is in the staging table. The record was updated remotely and the changes should be propagated to the local DB.
      • +
      • A record appears in the mirror table and changed records appear in both the local and staging tables. The record was updated both locally and remotely and we should perform a 3-way merge.
      • +
      +
    • +
    • apply plan: After we create the action plan, then we execute it.
    • +
    +
  • +
  • fetch outgoing: +
      +
    • Calculate which records need to be sent back to the server
    • +
    • Update the mirror table
    • +
    • Return those records back to the sync15 code so that it can upload them to the server.
    • +
    • The sync15 code returns the timestamp reported by the server in the POST response and hands it back to the engine. The engine persists this timestamp in the metadata table - the next sync will then use this timestamp to only fetch records that have since been changed by other devices
    • +
    +
  • +
+

syncChangeCounter

+

The local table has an integer column syncChangeCounter which is incremented every time the embedding app makes a change to a local record (eg, updating a field). Thus, any local record with a non-zero change counter will need to be updated on the server (with either the local record being used, or after it being merged if the record also changed remotely). At the start of the sync, when we are determining what action to take, we take a copy of the change counter, typically in a temp staging table. After we have uploaded the record to the server, we decrement the counter by whatever it was when the sync started. This means that if a record is changed in between staging the record and uploading it, the change counter will not drop to zero, and so it will correctly be seen as locally modified on the next sync

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + diff --git a/book/design/test-faster.html b/book/design/test-faster.html new file mode 100644 index 0000000000..d89b7383d9 --- /dev/null +++ b/book/design/test-faster.html @@ -0,0 +1,381 @@ + + + + + + Writing efficient tests - Cross-platform Rust Components + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Testing faster: How to avoid making compile times worse by adding tests

+

Background

+

We'd like to keep cargo test, cargo build, cargo check, ... reasonably +fast, and we'd really like to keep them fast if you pass -p for a specific +project. Unfortunately, there are a few ways this can become unexpectedly slow. +The easiest of these problems for us to combat at the moment is the unfortunate +placement of dev-dependencies in our build graph.

+

If you perform a cargo test -p foo, all dev-dependencies of foo must be +compiled before foo's tests can start. This includes dependencies only used +non-test targets, such as examples or benchmarks.

+

In an ideal world, cargo could run your tests as soon as it finished with the +dependencies it needs for those tests, instead of waiting for your benchmark +suite, or the arg-parser your examples use, or etc.

+

Unfortunately, all cargo knows is that these are dev-dependencies, and not +which targets actually use them.

+

Additionally, unqualified invocations of cargo (that is, without -p) might +have an even worse time if we aren't careful. If I run, cargo test, cargo +knows every crate in the workspace needs to be built with all dev +dependencies, if places depends on fxa-client, all of fxa-clients +dev-dependencies must be compiled, ready, and linked in at least to the lib +target before we can even think about starting on places.

+

We have not been careful about what shape the dependency graph ends up as when example code is +taken into consideration (as it is by cargo during certain builds), and as a +result, we have this problem. Which isn't really a problem we +want to fix: Example code can and should depend on several different components, +and use them together in interesting ways.

+

So, because we don't want to change what our examples do, or make +major architectural changes of the non-test code for something like this, we +need to do something else.

+

The Solution

+

To fix this, we manually insert "cuts" into the dependency graph to help cargo +out. That is, we pull some of these build targets (e.g. examples, benchmarks, +tests if they cause a substantial compile overhead) into their own dedicated +crates so that:

+
    +
  1. They can be built in parallel with each other.
  2. +
  3. Crates depending on the component itself are not waiting on the +test/bench/example build in order for their test build to begin.
  4. +
  5. A potentially smaller set of our crates need to be rebuilt -- and a smaller +set of possible configurations exist meaning fewer items to add pressure to +caches.
  6. +
  7. ...
  8. +
+

Some rules of thumb for when / when not to do this:

+
    +
  • +

    All rust examples should be put in examples/*.

    +
  • +
  • +

    All rust benchmarks should be put in testing/separated/*. See the section +below on how to set your benchmark up to avoid redundant compiles.

    +
  • +
  • +

    Rust tests which brings in heavyweight dependencies should be evaluated on an +ad-hoc basis. If you're concerned, measure how long compilation takes +with/without, and consider how many crates depend on the crate where the test +lives (e.g. a slow test in support/foo might be far worse than one in a leaf +crate), etc...

    +
  • +
+

Appendix: How to avoid redundant compiles for benchmarks and integration tests

+

To be clear, this is way more important for benchmarks (which always compile as +release and have a costly link phase).

+

Say you have a directory structure like the following:

+
mycrate
+ ├── src
+ │   └── lib.rs
+ | ...
+ ├── benches
+ │   ├── bench0.rs
+ |   ├── bench1.rs
+ │   └── bench2.rs
+ ├── tests
+ │   ├── test0.rs
+ |   ├── test1.rs
+ │   └── test2.rs
+ └── ...
+
+

When you run your integration tests or benchmarks, each of test0, test1, +test2 or bench0, bench1, bench2 is compiled as it's own crate that runs +the tests in question and exits.

+

That means 3 benchmark executables are built on release settings, and 3 +integration test executables.

+

If you've ever tried to add a piece of shared utility code into your integration +tests, only to have cargo (falsely) complain that it is dead code: this is why. +Even if test0.rs and test2.rs both use the utility function, unless +every test crate uses every shared utility, the crate that doesn't will +complain.

+

(Aside: This turns out to be an unintentional secondary benefit of this approach +-- easier shared code among tests, without having to put a +#![allow(dead_code)] in your utils.rs. We haven't hit that very much here, +since we tend to stick to unit tests, but it came up in mentat several times, +and is a frequent complaint people have)

+

Anyway, the solution here is simple: Create a new crate. If you were working in +components/mycrate and you want to add some integration tests or benchmarks, +you should do cargo new --lib testing/separated/mycrate-test (or +.../mycrate-bench).

+

Delete .../mycrate-test/src/lib.rs. Yep, really, we're making a crate that +only has integration tests/benchmarks (See the "FAQ0" section at the bottom of +the file if you're getting incredulous).

+

Now, add a src/tests.rs or a src/benches.rs. This file should contain mod foo; declarations for each submodule containing tests/benchmarks, if any.

+

For benches, this is also where you set up the benchmark harness (refer to +benchmark library docs for how).

+

Now, for a test, add: into your Cargo.toml

+
[[test]]
+name = "mycrate-test"
+path = "src/tests.rs"
+
+

and for a benchmark, add:

+
[[test]]
+name = "mycrate-benches"
+path = "src/benches.rs"
+harness = false
+
+

Because we aren't using src/lib.rs, this is what declares which file is the +root of the test/benchmark crate. Because there's only one target (unlike with +tests/* / benches/* under default settings), this will compile more quickly.

+

Additionally, src/tests.rs and src/benches.rs will behave like a normal +crate, the only difference being that they don't produce a lib, and that they're +triggered by cargo test/cargo run respectively.

+

FAQ0: Why put tests/benches in src/* instead of disabling autotests/autobenches

+

Instead of putting tests/benchmarks inside src, we could just delete the src +dir outright, and place everything in tests/benches.

+

Then, to get the same one-rebuild-per-file behavior that we'll get in src, we +need to add autotests = false or autobenches = false to our Cargo.toml, +adding a root tests/tests.rs (or benches/benches.rs) containing mod decls +for all submodules, and finally by referencing that "root" in the Cargo.toml +[[tests]] / [[benches]] list, exactly the same way we did for using src/*.

+

This would work, and on the surface, using tests/*.rs and benches/*.rs seems +more consistent, so it seems weird to use src/*.rs for these files.

+

My reasoning is as follows: Almost universally, tests/*.rs, examples/*.rs, +benches/*.rs, etc. are automatic. If you add a test into the tests folder, it +will run without anything else.

+

If we're going to set up one-build-per-{test,bench}suite as I described, this +fundamentally cannot be true. In this paradigm, if you add a test file named +blah.rs, you must add a mod blah it to the parent module.

+

It seems both confusing and error-prone to use tests/*, but have it behave +that way, however this is absolutely the normal behavior for files in src/*.rs +-- When you add a file, you then need to add it to it's parent module, and this +is something Rust programmers are pretty used to.

+

(In fact, we even replicated this behavior (for no reason) in the places +integration tests, and added the mod declarations to a "controlling" parent +module -- It seems weird to be in an environment where this isn't required)

+

So, that's why. This way, we make it way less likely that you add a test file +to some directory, and have it get ignored because you didn't realize that in +this one folder, you need to add a mod mytest into a neighboring tests.rs.

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + diff --git a/book/diagrams/Nimbus-SDK-Build-and-Publish-Pipeline.jpg b/book/diagrams/Nimbus-SDK-Build-and-Publish-Pipeline.jpg new file mode 100644 index 0000000000..dbf2692275 Binary files /dev/null and b/book/diagrams/Nimbus-SDK-Build-and-Publish-Pipeline.jpg differ diff --git a/book/diagrams/Push-Component-Subscription-flow.png b/book/diagrams/Push-Component-Subscription-flow.png new file mode 100644 index 0000000000..1f303276f7 Binary files /dev/null and b/book/diagrams/Push-Component-Subscription-flow.png differ diff --git a/book/diagrams/before-cross-components.png b/book/diagrams/before-cross-components.png new file mode 100644 index 0000000000..a907f4b9ad Binary files /dev/null and b/book/diagrams/before-cross-components.png differ diff --git a/book/diagrams/future-cross-components.png b/book/diagrams/future-cross-components.png new file mode 100644 index 0000000000..01a9ec6d50 Binary files /dev/null and b/book/diagrams/future-cross-components.png differ diff --git a/book/diagrams/high-level-sync-ecosystem.png b/book/diagrams/high-level-sync-ecosystem.png new file mode 100644 index 0000000000..9049ad7129 Binary files /dev/null and b/book/diagrams/high-level-sync-ecosystem.png differ diff --git a/book/diagrams/multi-platform-sync-diagram.png b/book/diagrams/multi-platform-sync-diagram.png new file mode 100644 index 0000000000..b24e6a08f0 Binary files /dev/null and b/book/diagrams/multi-platform-sync-diagram.png differ diff --git a/book/diagrams/now-cross-components.png b/book/diagrams/now-cross-components.png new file mode 100644 index 0000000000..58ecec3d78 Binary files /dev/null and b/book/diagrams/now-cross-components.png differ diff --git a/book/elasticlunr.min.js b/book/elasticlunr.min.js new file mode 100644 index 0000000000..94b20dd2ef --- /dev/null +++ b/book/elasticlunr.min.js @@ -0,0 +1,10 @@ +/** + * elasticlunr - http://weixsong.github.io + * Lightweight full-text search engine in Javascript for browser search and offline search. - 0.9.5 + * + * Copyright (C) 2017 Oliver Nightingale + * Copyright (C) 2017 Wei Song + * MIT Licensed + * @license + */ +!function(){function e(e){if(null===e||"object"!=typeof e)return e;var t=e.constructor();for(var n in e)e.hasOwnProperty(n)&&(t[n]=e[n]);return t}var t=function(e){var n=new t.Index;return n.pipeline.add(t.trimmer,t.stopWordFilter,t.stemmer),e&&e.call(n,n),n};t.version="0.9.5",lunr=t,t.utils={},t.utils.warn=function(e){return function(t){e.console&&console.warn&&console.warn(t)}}(this),t.utils.toString=function(e){return void 0===e||null===e?"":e.toString()},t.EventEmitter=function(){this.events={}},t.EventEmitter.prototype.addListener=function(){var e=Array.prototype.slice.call(arguments),t=e.pop(),n=e;if("function"!=typeof t)throw new TypeError("last argument must be a function");n.forEach(function(e){this.hasHandler(e)||(this.events[e]=[]),this.events[e].push(t)},this)},t.EventEmitter.prototype.removeListener=function(e,t){if(this.hasHandler(e)){var n=this.events[e].indexOf(t);-1!==n&&(this.events[e].splice(n,1),0==this.events[e].length&&delete this.events[e])}},t.EventEmitter.prototype.emit=function(e){if(this.hasHandler(e)){var t=Array.prototype.slice.call(arguments,1);this.events[e].forEach(function(e){e.apply(void 0,t)},this)}},t.EventEmitter.prototype.hasHandler=function(e){return e in this.events},t.tokenizer=function(e){if(!arguments.length||null===e||void 0===e)return[];if(Array.isArray(e)){var n=e.filter(function(e){return null===e||void 0===e?!1:!0});n=n.map(function(e){return t.utils.toString(e).toLowerCase()});var i=[];return n.forEach(function(e){var n=e.split(t.tokenizer.seperator);i=i.concat(n)},this),i}return e.toString().trim().toLowerCase().split(t.tokenizer.seperator)},t.tokenizer.defaultSeperator=/[\s\-]+/,t.tokenizer.seperator=t.tokenizer.defaultSeperator,t.tokenizer.setSeperator=function(e){null!==e&&void 0!==e&&"object"==typeof e&&(t.tokenizer.seperator=e)},t.tokenizer.resetSeperator=function(){t.tokenizer.seperator=t.tokenizer.defaultSeperator},t.tokenizer.getSeperator=function(){return t.tokenizer.seperator},t.Pipeline=function(){this._queue=[]},t.Pipeline.registeredFunctions={},t.Pipeline.registerFunction=function(e,n){n in t.Pipeline.registeredFunctions&&t.utils.warn("Overwriting existing registered function: "+n),e.label=n,t.Pipeline.registeredFunctions[n]=e},t.Pipeline.getRegisteredFunction=function(e){return e in t.Pipeline.registeredFunctions!=!0?null:t.Pipeline.registeredFunctions[e]},t.Pipeline.warnIfFunctionNotRegistered=function(e){var n=e.label&&e.label in this.registeredFunctions;n||t.utils.warn("Function is not registered with pipeline. This may cause problems when serialising the index.\n",e)},t.Pipeline.load=function(e){var n=new t.Pipeline;return e.forEach(function(e){var i=t.Pipeline.getRegisteredFunction(e);if(!i)throw new Error("Cannot load un-registered function: "+e);n.add(i)}),n},t.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach(function(e){t.Pipeline.warnIfFunctionNotRegistered(e),this._queue.push(e)},this)},t.Pipeline.prototype.after=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._queue.indexOf(e);if(-1===i)throw new Error("Cannot find existingFn");this._queue.splice(i+1,0,n)},t.Pipeline.prototype.before=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._queue.indexOf(e);if(-1===i)throw new Error("Cannot find existingFn");this._queue.splice(i,0,n)},t.Pipeline.prototype.remove=function(e){var t=this._queue.indexOf(e);-1!==t&&this._queue.splice(t,1)},t.Pipeline.prototype.run=function(e){for(var t=[],n=e.length,i=this._queue.length,o=0;n>o;o++){for(var r=e[o],s=0;i>s&&(r=this._queue[s](r,o,e),void 0!==r&&null!==r);s++);void 0!==r&&null!==r&&t.push(r)}return t},t.Pipeline.prototype.reset=function(){this._queue=[]},t.Pipeline.prototype.get=function(){return this._queue},t.Pipeline.prototype.toJSON=function(){return this._queue.map(function(e){return t.Pipeline.warnIfFunctionNotRegistered(e),e.label})},t.Index=function(){this._fields=[],this._ref="id",this.pipeline=new t.Pipeline,this.documentStore=new t.DocumentStore,this.index={},this.eventEmitter=new t.EventEmitter,this._idfCache={},this.on("add","remove","update",function(){this._idfCache={}}.bind(this))},t.Index.prototype.on=function(){var e=Array.prototype.slice.call(arguments);return this.eventEmitter.addListener.apply(this.eventEmitter,e)},t.Index.prototype.off=function(e,t){return this.eventEmitter.removeListener(e,t)},t.Index.load=function(e){e.version!==t.version&&t.utils.warn("version mismatch: current "+t.version+" importing "+e.version);var n=new this;n._fields=e.fields,n._ref=e.ref,n.documentStore=t.DocumentStore.load(e.documentStore),n.pipeline=t.Pipeline.load(e.pipeline),n.index={};for(var i in e.index)n.index[i]=t.InvertedIndex.load(e.index[i]);return n},t.Index.prototype.addField=function(e){return this._fields.push(e),this.index[e]=new t.InvertedIndex,this},t.Index.prototype.setRef=function(e){return this._ref=e,this},t.Index.prototype.saveDocument=function(e){return this.documentStore=new t.DocumentStore(e),this},t.Index.prototype.addDoc=function(e,n){if(e){var n=void 0===n?!0:n,i=e[this._ref];this.documentStore.addDoc(i,e),this._fields.forEach(function(n){var o=this.pipeline.run(t.tokenizer(e[n]));this.documentStore.addFieldLength(i,n,o.length);var r={};o.forEach(function(e){e in r?r[e]+=1:r[e]=1},this);for(var s in r){var u=r[s];u=Math.sqrt(u),this.index[n].addToken(s,{ref:i,tf:u})}},this),n&&this.eventEmitter.emit("add",e,this)}},t.Index.prototype.removeDocByRef=function(e){if(e&&this.documentStore.isDocStored()!==!1&&this.documentStore.hasDoc(e)){var t=this.documentStore.getDoc(e);this.removeDoc(t,!1)}},t.Index.prototype.removeDoc=function(e,n){if(e){var n=void 0===n?!0:n,i=e[this._ref];this.documentStore.hasDoc(i)&&(this.documentStore.removeDoc(i),this._fields.forEach(function(n){var o=this.pipeline.run(t.tokenizer(e[n]));o.forEach(function(e){this.index[n].removeToken(e,i)},this)},this),n&&this.eventEmitter.emit("remove",e,this))}},t.Index.prototype.updateDoc=function(e,t){var t=void 0===t?!0:t;this.removeDocByRef(e[this._ref],!1),this.addDoc(e,!1),t&&this.eventEmitter.emit("update",e,this)},t.Index.prototype.idf=function(e,t){var n="@"+t+"/"+e;if(Object.prototype.hasOwnProperty.call(this._idfCache,n))return this._idfCache[n];var i=this.index[t].getDocFreq(e),o=1+Math.log(this.documentStore.length/(i+1));return this._idfCache[n]=o,o},t.Index.prototype.getFields=function(){return this._fields.slice()},t.Index.prototype.search=function(e,n){if(!e)return[];e="string"==typeof e?{any:e}:JSON.parse(JSON.stringify(e));var i=null;null!=n&&(i=JSON.stringify(n));for(var o=new t.Configuration(i,this.getFields()).get(),r={},s=Object.keys(e),u=0;u0&&t.push(e);for(var i in n)"docs"!==i&&"df"!==i&&this.expandToken(e+i,t,n[i]);return t},t.InvertedIndex.prototype.toJSON=function(){return{root:this.root}},t.Configuration=function(e,n){var e=e||"";if(void 0==n||null==n)throw new Error("fields should not be null");this.config={};var i;try{i=JSON.parse(e),this.buildUserConfig(i,n)}catch(o){t.utils.warn("user configuration parse failed, will use default configuration"),this.buildDefaultConfig(n)}},t.Configuration.prototype.buildDefaultConfig=function(e){this.reset(),e.forEach(function(e){this.config[e]={boost:1,bool:"OR",expand:!1}},this)},t.Configuration.prototype.buildUserConfig=function(e,n){var i="OR",o=!1;if(this.reset(),"bool"in e&&(i=e.bool||i),"expand"in e&&(o=e.expand||o),"fields"in e)for(var r in e.fields)if(n.indexOf(r)>-1){var s=e.fields[r],u=o;void 0!=s.expand&&(u=s.expand),this.config[r]={boost:s.boost||0===s.boost?s.boost:1,bool:s.bool||i,expand:u}}else t.utils.warn("field name in user configuration not found in index instance fields");else this.addAllFields2UserConfig(i,o,n)},t.Configuration.prototype.addAllFields2UserConfig=function(e,t,n){n.forEach(function(n){this.config[n]={boost:1,bool:e,expand:t}},this)},t.Configuration.prototype.get=function(){return this.config},t.Configuration.prototype.reset=function(){this.config={}},lunr.SortedSet=function(){this.length=0,this.elements=[]},lunr.SortedSet.load=function(e){var t=new this;return t.elements=e,t.length=e.length,t},lunr.SortedSet.prototype.add=function(){var e,t;for(e=0;e1;){if(r===e)return o;e>r&&(t=o),r>e&&(n=o),i=n-t,o=t+Math.floor(i/2),r=this.elements[o]}return r===e?o:-1},lunr.SortedSet.prototype.locationFor=function(e){for(var t=0,n=this.elements.length,i=n-t,o=t+Math.floor(i/2),r=this.elements[o];i>1;)e>r&&(t=o),r>e&&(n=o),i=n-t,o=t+Math.floor(i/2),r=this.elements[o];return r>e?o:e>r?o+1:void 0},lunr.SortedSet.prototype.intersect=function(e){for(var t=new lunr.SortedSet,n=0,i=0,o=this.length,r=e.length,s=this.elements,u=e.elements;;){if(n>o-1||i>r-1)break;s[n]!==u[i]?s[n]u[i]&&i++:(t.add(s[n]),n++,i++)}return t},lunr.SortedSet.prototype.clone=function(){var e=new lunr.SortedSet;return e.elements=this.toArray(),e.length=e.elements.length,e},lunr.SortedSet.prototype.union=function(e){var t,n,i;this.length>=e.length?(t=this,n=e):(t=e,n=this),i=t.clone();for(var o=0,r=n.toArray();o + + + + diff --git a/book/fonts/OPEN-SANS-LICENSE.txt b/book/fonts/OPEN-SANS-LICENSE.txt new file mode 100644 index 0000000000..d645695673 --- /dev/null +++ b/book/fonts/OPEN-SANS-LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/book/fonts/SOURCE-CODE-PRO-LICENSE.txt b/book/fonts/SOURCE-CODE-PRO-LICENSE.txt new file mode 100644 index 0000000000..366206f549 --- /dev/null +++ b/book/fonts/SOURCE-CODE-PRO-LICENSE.txt @@ -0,0 +1,93 @@ +Copyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries. + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/book/fonts/fonts.css b/book/fonts/fonts.css new file mode 100644 index 0000000000..858efa5980 --- /dev/null +++ b/book/fonts/fonts.css @@ -0,0 +1,100 @@ +/* Open Sans is licensed under the Apache License, Version 2.0. See http://www.apache.org/licenses/LICENSE-2.0 */ +/* Source Code Pro is under the Open Font License. See https://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL */ + +/* open-sans-300 - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 300; + src: local('Open Sans Light'), local('OpenSans-Light'), + url('open-sans-v17-all-charsets-300.woff2') format('woff2'); +} + +/* open-sans-300italic - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 300; + src: local('Open Sans Light Italic'), local('OpenSans-LightItalic'), + url('open-sans-v17-all-charsets-300italic.woff2') format('woff2'); +} + +/* open-sans-regular - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 400; + src: local('Open Sans Regular'), local('OpenSans-Regular'), + url('open-sans-v17-all-charsets-regular.woff2') format('woff2'); +} + +/* open-sans-italic - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 400; + src: local('Open Sans Italic'), local('OpenSans-Italic'), + url('open-sans-v17-all-charsets-italic.woff2') format('woff2'); +} + +/* open-sans-600 - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 600; + src: local('Open Sans SemiBold'), local('OpenSans-SemiBold'), + url('open-sans-v17-all-charsets-600.woff2') format('woff2'); +} + +/* open-sans-600italic - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 600; + src: local('Open Sans SemiBold Italic'), local('OpenSans-SemiBoldItalic'), + url('open-sans-v17-all-charsets-600italic.woff2') format('woff2'); +} + +/* open-sans-700 - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 700; + src: local('Open Sans Bold'), local('OpenSans-Bold'), + url('open-sans-v17-all-charsets-700.woff2') format('woff2'); +} + +/* open-sans-700italic - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 700; + src: local('Open Sans Bold Italic'), local('OpenSans-BoldItalic'), + url('open-sans-v17-all-charsets-700italic.woff2') format('woff2'); +} + +/* open-sans-800 - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ +@font-face { + font-family: 'Open Sans'; + font-style: normal; + font-weight: 800; + src: local('Open Sans ExtraBold'), local('OpenSans-ExtraBold'), + url('open-sans-v17-all-charsets-800.woff2') format('woff2'); +} + +/* open-sans-800italic - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */ +@font-face { + font-family: 'Open Sans'; + font-style: italic; + font-weight: 800; + src: local('Open Sans ExtraBold Italic'), local('OpenSans-ExtraBoldItalic'), + url('open-sans-v17-all-charsets-800italic.woff2') format('woff2'); +} + +/* source-code-pro-500 - latin_vietnamese_latin-ext_greek_cyrillic-ext_cyrillic */ +@font-face { + font-family: 'Source Code Pro'; + font-style: normal; + font-weight: 500; + src: url('source-code-pro-v11-all-charsets-500.woff2') format('woff2'); +} diff --git a/book/fonts/open-sans-v17-all-charsets-300.woff2 b/book/fonts/open-sans-v17-all-charsets-300.woff2 new file mode 100644 index 0000000000..9f51be370f Binary files /dev/null and b/book/fonts/open-sans-v17-all-charsets-300.woff2 differ diff --git a/book/fonts/open-sans-v17-all-charsets-300italic.woff2 b/book/fonts/open-sans-v17-all-charsets-300italic.woff2 new file mode 100644 index 0000000000..2f54544841 Binary files /dev/null and b/book/fonts/open-sans-v17-all-charsets-300italic.woff2 differ diff --git a/book/fonts/open-sans-v17-all-charsets-600.woff2 b/book/fonts/open-sans-v17-all-charsets-600.woff2 new file mode 100644 index 0000000000..f503d558d5 Binary files /dev/null and b/book/fonts/open-sans-v17-all-charsets-600.woff2 differ diff --git a/book/fonts/open-sans-v17-all-charsets-600italic.woff2 b/book/fonts/open-sans-v17-all-charsets-600italic.woff2 new file mode 100644 index 0000000000..c99aabe803 Binary files /dev/null and b/book/fonts/open-sans-v17-all-charsets-600italic.woff2 differ diff --git a/book/fonts/open-sans-v17-all-charsets-700.woff2 b/book/fonts/open-sans-v17-all-charsets-700.woff2 new file mode 100644 index 0000000000..421a1ab25f Binary files /dev/null and b/book/fonts/open-sans-v17-all-charsets-700.woff2 differ diff --git a/book/fonts/open-sans-v17-all-charsets-700italic.woff2 b/book/fonts/open-sans-v17-all-charsets-700italic.woff2 new file mode 100644 index 0000000000..12ce3d20d1 Binary files /dev/null and b/book/fonts/open-sans-v17-all-charsets-700italic.woff2 differ diff --git a/book/fonts/open-sans-v17-all-charsets-800.woff2 b/book/fonts/open-sans-v17-all-charsets-800.woff2 new file mode 100644 index 0000000000..c94a223b03 Binary files /dev/null and b/book/fonts/open-sans-v17-all-charsets-800.woff2 differ diff --git a/book/fonts/open-sans-v17-all-charsets-800italic.woff2 b/book/fonts/open-sans-v17-all-charsets-800italic.woff2 new file mode 100644 index 0000000000..eed7d3c63d Binary files /dev/null and b/book/fonts/open-sans-v17-all-charsets-800italic.woff2 differ diff --git a/book/fonts/open-sans-v17-all-charsets-italic.woff2 b/book/fonts/open-sans-v17-all-charsets-italic.woff2 new file mode 100644 index 0000000000..398b68a085 Binary files /dev/null and b/book/fonts/open-sans-v17-all-charsets-italic.woff2 differ diff --git a/book/fonts/open-sans-v17-all-charsets-regular.woff2 b/book/fonts/open-sans-v17-all-charsets-regular.woff2 new file mode 100644 index 0000000000..8383e94c65 Binary files /dev/null and b/book/fonts/open-sans-v17-all-charsets-regular.woff2 differ diff --git a/book/fonts/source-code-pro-v11-all-charsets-500.woff2 b/book/fonts/source-code-pro-v11-all-charsets-500.woff2 new file mode 100644 index 0000000000..722245682f Binary files /dev/null and b/book/fonts/source-code-pro-v11-all-charsets-500.woff2 differ diff --git a/book/highlight.css b/book/highlight.css new file mode 100644 index 0000000000..ba57b82b27 --- /dev/null +++ b/book/highlight.css @@ -0,0 +1,82 @@ +/* + * An increased contrast highlighting scheme loosely based on the + * "Base16 Atelier Dune Light" theme by Bram de Haan + * (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/dune) + * Original Base16 color scheme by Chris Kempson + * (https://github.com/chriskempson/base16) + */ + +/* Comment */ +.hljs-comment, +.hljs-quote { + color: #575757; +} + +/* Red */ +.hljs-variable, +.hljs-template-variable, +.hljs-attribute, +.hljs-tag, +.hljs-name, +.hljs-regexp, +.hljs-link, +.hljs-name, +.hljs-selector-id, +.hljs-selector-class { + color: #d70025; +} + +/* Orange */ +.hljs-number, +.hljs-meta, +.hljs-built_in, +.hljs-builtin-name, +.hljs-literal, +.hljs-type, +.hljs-params { + color: #b21e00; +} + +/* Green */ +.hljs-string, +.hljs-symbol, +.hljs-bullet { + color: #008200; +} + +/* Blue */ +.hljs-title, +.hljs-section { + color: #0030f2; +} + +/* Purple */ +.hljs-keyword, +.hljs-selector-tag { + color: #9d00ec; +} + +.hljs { + display: block; + overflow-x: auto; + background: #f6f7f6; + color: #000; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} + +.hljs-addition { + color: #22863a; + background-color: #f0fff4; +} + +.hljs-deletion { + color: #b31d28; + background-color: #ffeef0; +} diff --git a/book/highlight.js b/book/highlight.js new file mode 100644 index 0000000000..180385b702 --- /dev/null +++ b/book/highlight.js @@ -0,0 +1,6 @@ +/* + Highlight.js 10.1.1 (93fd0d73) + License: BSD-3-Clause + Copyright (c) 2006-2020, Ivan Sagalaev +*/ +var hljs=function(){"use strict";function e(n){Object.freeze(n);var t="function"==typeof n;return Object.getOwnPropertyNames(n).forEach((function(r){!Object.hasOwnProperty.call(n,r)||null===n[r]||"object"!=typeof n[r]&&"function"!=typeof n[r]||t&&("caller"===r||"callee"===r||"arguments"===r)||Object.isFrozen(n[r])||e(n[r])})),n}class n{constructor(e){void 0===e.data&&(e.data={}),this.data=e.data}ignoreMatch(){this.ignore=!0}}function t(e){return e.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function r(e,...n){var t={};for(const n in e)t[n]=e[n];return n.forEach((function(e){for(const n in e)t[n]=e[n]})),t}function a(e){return e.nodeName.toLowerCase()}var i=Object.freeze({__proto__:null,escapeHTML:t,inherit:r,nodeStream:function(e){var n=[];return function e(t,r){for(var i=t.firstChild;i;i=i.nextSibling)3===i.nodeType?r+=i.nodeValue.length:1===i.nodeType&&(n.push({event:"start",offset:r,node:i}),r=e(i,r),a(i).match(/br|hr|img|input/)||n.push({event:"stop",offset:r,node:i}));return r}(e,0),n},mergeStreams:function(e,n,r){var i=0,s="",o=[];function l(){return e.length&&n.length?e[0].offset!==n[0].offset?e[0].offset"}function u(e){s+=""}function d(e){("start"===e.event?c:u)(e.node)}for(;e.length||n.length;){var g=l();if(s+=t(r.substring(i,g[0].offset)),i=g[0].offset,g===e){o.reverse().forEach(u);do{d(g.splice(0,1)[0]),g=l()}while(g===e&&g.length&&g[0].offset===i);o.reverse().forEach(c)}else"start"===g[0].event?o.push(g[0].node):o.pop(),d(g.splice(0,1)[0])}return s+t(r.substr(i))}});const s="",o=e=>!!e.kind;class l{constructor(e,n){this.buffer="",this.classPrefix=n.classPrefix,e.walk(this)}addText(e){this.buffer+=t(e)}openNode(e){if(!o(e))return;let n=e.kind;e.sublanguage||(n=`${this.classPrefix}${n}`),this.span(n)}closeNode(e){o(e)&&(this.buffer+=s)}value(){return this.buffer}span(e){this.buffer+=``}}class c{constructor(){this.rootNode={children:[]},this.stack=[this.rootNode]}get top(){return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(e){this.top.children.push(e)}openNode(e){const n={kind:e,children:[]};this.add(n),this.stack.push(n)}closeNode(){if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)}walk(e){return this.constructor._walk(e,this.rootNode)}static _walk(e,n){return"string"==typeof n?e.addText(n):n.children&&(e.openNode(n),n.children.forEach(n=>this._walk(e,n)),e.closeNode(n)),e}static _collapse(e){"string"!=typeof e&&e.children&&(e.children.every(e=>"string"==typeof e)?e.children=[e.children.join("")]:e.children.forEach(e=>{c._collapse(e)}))}}class u extends c{constructor(e){super(),this.options=e}addKeyword(e,n){""!==e&&(this.openNode(n),this.addText(e),this.closeNode())}addText(e){""!==e&&this.add(e)}addSublanguage(e,n){const t=e.root;t.kind=n,t.sublanguage=!0,this.add(t)}toHTML(){return new l(this,this.options).value()}finalize(){return!0}}function d(e){return e?"string"==typeof e?e:e.source:null}const g="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",h={begin:"\\\\[\\s\\S]",relevance:0},f={className:"string",begin:"'",end:"'",illegal:"\\n",contains:[h]},p={className:"string",begin:'"',end:'"',illegal:"\\n",contains:[h]},b={begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},m=function(e,n,t={}){var a=r({className:"comment",begin:e,end:n,contains:[]},t);return a.contains.push(b),a.contains.push({className:"doctag",begin:"(?:TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):",relevance:0}),a},v=m("//","$"),x=m("/\\*","\\*/"),E=m("#","$");var _=Object.freeze({__proto__:null,IDENT_RE:"[a-zA-Z]\\w*",UNDERSCORE_IDENT_RE:"[a-zA-Z_]\\w*",NUMBER_RE:"\\b\\d+(\\.\\d+)?",C_NUMBER_RE:g,BINARY_NUMBER_RE:"\\b(0b[01]+)",RE_STARTERS_RE:"!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",SHEBANG:(e={})=>{const n=/^#![ ]*\//;return e.binary&&(e.begin=function(...e){return e.map(e=>d(e)).join("")}(n,/.*\b/,e.binary,/\b.*/)),r({className:"meta",begin:n,end:/$/,relevance:0,"on:begin":(e,n)=>{0!==e.index&&n.ignoreMatch()}},e)},BACKSLASH_ESCAPE:h,APOS_STRING_MODE:f,QUOTE_STRING_MODE:p,PHRASAL_WORDS_MODE:b,COMMENT:m,C_LINE_COMMENT_MODE:v,C_BLOCK_COMMENT_MODE:x,HASH_COMMENT_MODE:E,NUMBER_MODE:{className:"number",begin:"\\b\\d+(\\.\\d+)?",relevance:0},C_NUMBER_MODE:{className:"number",begin:g,relevance:0},BINARY_NUMBER_MODE:{className:"number",begin:"\\b(0b[01]+)",relevance:0},CSS_NUMBER_MODE:{className:"number",begin:"\\b\\d+(\\.\\d+)?(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",relevance:0},REGEXP_MODE:{begin:/(?=\/[^/\n]*\/)/,contains:[{className:"regexp",begin:/\//,end:/\/[gimuy]*/,illegal:/\n/,contains:[h,{begin:/\[/,end:/\]/,relevance:0,contains:[h]}]}]},TITLE_MODE:{className:"title",begin:"[a-zA-Z]\\w*",relevance:0},UNDERSCORE_TITLE_MODE:{className:"title",begin:"[a-zA-Z_]\\w*",relevance:0},METHOD_GUARD:{begin:"\\.\\s*[a-zA-Z_]\\w*",relevance:0},END_SAME_AS_BEGIN:function(e){return Object.assign(e,{"on:begin":(e,n)=>{n.data._beginMatch=e[1]},"on:end":(e,n)=>{n.data._beginMatch!==e[1]&&n.ignoreMatch()}})}}),N="of and for in not or if then".split(" ");function w(e,n){return n?+n:function(e){return N.includes(e.toLowerCase())}(e)?0:1}const R=t,y=r,{nodeStream:k,mergeStreams:O}=i,M=Symbol("nomatch");return function(t){var a=[],i={},s={},o=[],l=!0,c=/(^(<[^>]+>|\t|)+|\n)/gm,g="Could not find the language '{}', did you forget to load/include a language module?";const h={disableAutodetect:!0,name:"Plain text",contains:[]};var f={noHighlightRe:/^(no-?highlight)$/i,languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:null,__emitter:u};function p(e){return f.noHighlightRe.test(e)}function b(e,n,t,r){var a={code:n,language:e};S("before:highlight",a);var i=a.result?a.result:m(a.language,a.code,t,r);return i.code=a.code,S("after:highlight",i),i}function m(e,t,a,s){var o=t;function c(e,n){var t=E.case_insensitive?n[0].toLowerCase():n[0];return Object.prototype.hasOwnProperty.call(e.keywords,t)&&e.keywords[t]}function u(){null!=y.subLanguage?function(){if(""!==A){var e=null;if("string"==typeof y.subLanguage){if(!i[y.subLanguage])return void O.addText(A);e=m(y.subLanguage,A,!0,k[y.subLanguage]),k[y.subLanguage]=e.top}else e=v(A,y.subLanguage.length?y.subLanguage:null);y.relevance>0&&(I+=e.relevance),O.addSublanguage(e.emitter,e.language)}}():function(){if(!y.keywords)return void O.addText(A);let e=0;y.keywordPatternRe.lastIndex=0;let n=y.keywordPatternRe.exec(A),t="";for(;n;){t+=A.substring(e,n.index);const r=c(y,n);if(r){const[e,a]=r;O.addText(t),t="",I+=a,O.addKeyword(n[0],e)}else t+=n[0];e=y.keywordPatternRe.lastIndex,n=y.keywordPatternRe.exec(A)}t+=A.substr(e),O.addText(t)}(),A=""}function h(e){return e.className&&O.openNode(e.className),y=Object.create(e,{parent:{value:y}})}function p(e){return 0===y.matcher.regexIndex?(A+=e[0],1):(L=!0,0)}var b={};function x(t,r){var i=r&&r[0];if(A+=t,null==i)return u(),0;if("begin"===b.type&&"end"===r.type&&b.index===r.index&&""===i){if(A+=o.slice(r.index,r.index+1),!l){const n=Error("0 width match regex");throw n.languageName=e,n.badRule=b.rule,n}return 1}if(b=r,"begin"===r.type)return function(e){var t=e[0],r=e.rule;const a=new n(r),i=[r.__beforeBegin,r["on:begin"]];for(const n of i)if(n&&(n(e,a),a.ignore))return p(t);return r&&r.endSameAsBegin&&(r.endRe=RegExp(t.replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&"),"m")),r.skip?A+=t:(r.excludeBegin&&(A+=t),u(),r.returnBegin||r.excludeBegin||(A=t)),h(r),r.returnBegin?0:t.length}(r);if("illegal"===r.type&&!a){const e=Error('Illegal lexeme "'+i+'" for mode "'+(y.className||"")+'"');throw e.mode=y,e}if("end"===r.type){var s=function(e){var t=e[0],r=o.substr(e.index),a=function e(t,r,a){let i=function(e,n){var t=e&&e.exec(n);return t&&0===t.index}(t.endRe,a);if(i){if(t["on:end"]){const e=new n(t);t["on:end"](r,e),e.ignore&&(i=!1)}if(i){for(;t.endsParent&&t.parent;)t=t.parent;return t}}if(t.endsWithParent)return e(t.parent,r,a)}(y,e,r);if(!a)return M;var i=y;i.skip?A+=t:(i.returnEnd||i.excludeEnd||(A+=t),u(),i.excludeEnd&&(A=t));do{y.className&&O.closeNode(),y.skip||y.subLanguage||(I+=y.relevance),y=y.parent}while(y!==a.parent);return a.starts&&(a.endSameAsBegin&&(a.starts.endRe=a.endRe),h(a.starts)),i.returnEnd?0:t.length}(r);if(s!==M)return s}if("illegal"===r.type&&""===i)return 1;if(B>1e5&&B>3*r.index)throw Error("potential infinite loop, way more iterations than matches");return A+=i,i.length}var E=T(e);if(!E)throw console.error(g.replace("{}",e)),Error('Unknown language: "'+e+'"');var _=function(e){function n(n,t){return RegExp(d(n),"m"+(e.case_insensitive?"i":"")+(t?"g":""))}class t{constructor(){this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0}addRule(e,n){n.position=this.position++,this.matchIndexes[this.matchAt]=n,this.regexes.push([n,e]),this.matchAt+=function(e){return RegExp(e.toString()+"|").exec("").length-1}(e)+1}compile(){0===this.regexes.length&&(this.exec=()=>null);const e=this.regexes.map(e=>e[1]);this.matcherRe=n(function(e,n="|"){for(var t=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./,r=0,a="",i=0;i0&&(a+=n),a+="(";o.length>0;){var l=t.exec(o);if(null==l){a+=o;break}a+=o.substring(0,l.index),o=o.substring(l.index+l[0].length),"\\"===l[0][0]&&l[1]?a+="\\"+(+l[1]+s):(a+=l[0],"("===l[0]&&r++)}a+=")"}return a}(e),!0),this.lastIndex=0}exec(e){this.matcherRe.lastIndex=this.lastIndex;const n=this.matcherRe.exec(e);if(!n)return null;const t=n.findIndex((e,n)=>n>0&&void 0!==e),r=this.matchIndexes[t];return n.splice(0,t),Object.assign(n,r)}}class a{constructor(){this.rules=[],this.multiRegexes=[],this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(e){if(this.multiRegexes[e])return this.multiRegexes[e];const n=new t;return this.rules.slice(e).forEach(([e,t])=>n.addRule(e,t)),n.compile(),this.multiRegexes[e]=n,n}considerAll(){this.regexIndex=0}addRule(e,n){this.rules.push([e,n]),"begin"===n.type&&this.count++}exec(e){const n=this.getMatcher(this.regexIndex);n.lastIndex=this.lastIndex;const t=n.exec(e);return t&&(this.regexIndex+=t.position+1,this.regexIndex===this.count&&(this.regexIndex=0)),t}}function i(e,n){const t=e.input[e.index-1],r=e.input[e.index+e[0].length];"."!==t&&"."!==r||n.ignoreMatch()}if(e.contains&&e.contains.includes("self"))throw Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.");return function t(s,o){const l=s;if(s.compiled)return l;s.compiled=!0,s.__beforeBegin=null,s.keywords=s.keywords||s.beginKeywords;let c=null;if("object"==typeof s.keywords&&(c=s.keywords.$pattern,delete s.keywords.$pattern),s.keywords&&(s.keywords=function(e,n){var t={};return"string"==typeof e?r("keyword",e):Object.keys(e).forEach((function(n){r(n,e[n])})),t;function r(e,r){n&&(r=r.toLowerCase()),r.split(" ").forEach((function(n){var r=n.split("|");t[r[0]]=[e,w(r[0],r[1])]}))}}(s.keywords,e.case_insensitive)),s.lexemes&&c)throw Error("ERR: Prefer `keywords.$pattern` to `mode.lexemes`, BOTH are not allowed. (see mode reference) ");return l.keywordPatternRe=n(s.lexemes||c||/\w+/,!0),o&&(s.beginKeywords&&(s.begin="\\b("+s.beginKeywords.split(" ").join("|")+")(?=\\b|\\s)",s.__beforeBegin=i),s.begin||(s.begin=/\B|\b/),l.beginRe=n(s.begin),s.endSameAsBegin&&(s.end=s.begin),s.end||s.endsWithParent||(s.end=/\B|\b/),s.end&&(l.endRe=n(s.end)),l.terminator_end=d(s.end)||"",s.endsWithParent&&o.terminator_end&&(l.terminator_end+=(s.end?"|":"")+o.terminator_end)),s.illegal&&(l.illegalRe=n(s.illegal)),void 0===s.relevance&&(s.relevance=1),s.contains||(s.contains=[]),s.contains=[].concat(...s.contains.map((function(e){return function(e){return e.variants&&!e.cached_variants&&(e.cached_variants=e.variants.map((function(n){return r(e,{variants:null},n)}))),e.cached_variants?e.cached_variants:function e(n){return!!n&&(n.endsWithParent||e(n.starts))}(e)?r(e,{starts:e.starts?r(e.starts):null}):Object.isFrozen(e)?r(e):e}("self"===e?s:e)}))),s.contains.forEach((function(e){t(e,l)})),s.starts&&t(s.starts,o),l.matcher=function(e){const n=new a;return e.contains.forEach(e=>n.addRule(e.begin,{rule:e,type:"begin"})),e.terminator_end&&n.addRule(e.terminator_end,{type:"end"}),e.illegal&&n.addRule(e.illegal,{type:"illegal"}),n}(l),l}(e)}(E),N="",y=s||_,k={},O=new f.__emitter(f);!function(){for(var e=[],n=y;n!==E;n=n.parent)n.className&&e.unshift(n.className);e.forEach(e=>O.openNode(e))}();var A="",I=0,S=0,B=0,L=!1;try{for(y.matcher.considerAll();;){B++,L?L=!1:(y.matcher.lastIndex=S,y.matcher.considerAll());const e=y.matcher.exec(o);if(!e)break;const n=x(o.substring(S,e.index),e);S=e.index+n}return x(o.substr(S)),O.closeAllNodes(),O.finalize(),N=O.toHTML(),{relevance:I,value:N,language:e,illegal:!1,emitter:O,top:y}}catch(n){if(n.message&&n.message.includes("Illegal"))return{illegal:!0,illegalBy:{msg:n.message,context:o.slice(S-100,S+100),mode:n.mode},sofar:N,relevance:0,value:R(o),emitter:O};if(l)return{illegal:!1,relevance:0,value:R(o),emitter:O,language:e,top:y,errorRaised:n};throw n}}function v(e,n){n=n||f.languages||Object.keys(i);var t=function(e){const n={relevance:0,emitter:new f.__emitter(f),value:R(e),illegal:!1,top:h};return n.emitter.addText(e),n}(e),r=t;return n.filter(T).filter(I).forEach((function(n){var a=m(n,e,!1);a.language=n,a.relevance>r.relevance&&(r=a),a.relevance>t.relevance&&(r=t,t=a)})),r.language&&(t.second_best=r),t}function x(e){return f.tabReplace||f.useBR?e.replace(c,e=>"\n"===e?f.useBR?"
":e:f.tabReplace?e.replace(/\t/g,f.tabReplace):e):e}function E(e){let n=null;const t=function(e){var n=e.className+" ";n+=e.parentNode?e.parentNode.className:"";const t=f.languageDetectRe.exec(n);if(t){var r=T(t[1]);return r||(console.warn(g.replace("{}",t[1])),console.warn("Falling back to no-highlight mode for this block.",e)),r?t[1]:"no-highlight"}return n.split(/\s+/).find(e=>p(e)||T(e))}(e);if(p(t))return;S("before:highlightBlock",{block:e,language:t}),f.useBR?(n=document.createElement("div")).innerHTML=e.innerHTML.replace(/\n/g,"").replace(//g,"\n"):n=e;const r=n.textContent,a=t?b(t,r,!0):v(r),i=k(n);if(i.length){const e=document.createElement("div");e.innerHTML=a.value,a.value=O(i,k(e),r)}a.value=x(a.value),S("after:highlightBlock",{block:e,result:a}),e.innerHTML=a.value,e.className=function(e,n,t){var r=n?s[n]:t,a=[e.trim()];return e.match(/\bhljs\b/)||a.push("hljs"),e.includes(r)||a.push(r),a.join(" ").trim()}(e.className,t,a.language),e.result={language:a.language,re:a.relevance,relavance:a.relevance},a.second_best&&(e.second_best={language:a.second_best.language,re:a.second_best.relevance,relavance:a.second_best.relevance})}const N=()=>{if(!N.called){N.called=!0;var e=document.querySelectorAll("pre code");a.forEach.call(e,E)}};function T(e){return e=(e||"").toLowerCase(),i[e]||i[s[e]]}function A(e,{languageName:n}){"string"==typeof e&&(e=[e]),e.forEach(e=>{s[e]=n})}function I(e){var n=T(e);return n&&!n.disableAutodetect}function S(e,n){var t=e;o.forEach((function(e){e[t]&&e[t](n)}))}Object.assign(t,{highlight:b,highlightAuto:v,fixMarkup:x,highlightBlock:E,configure:function(e){f=y(f,e)},initHighlighting:N,initHighlightingOnLoad:function(){window.addEventListener("DOMContentLoaded",N,!1)},registerLanguage:function(e,n){var r=null;try{r=n(t)}catch(n){if(console.error("Language definition for '{}' could not be registered.".replace("{}",e)),!l)throw n;console.error(n),r=h}r.name||(r.name=e),i[e]=r,r.rawDefinition=n.bind(null,t),r.aliases&&A(r.aliases,{languageName:e})},listLanguages:function(){return Object.keys(i)},getLanguage:T,registerAliases:A,requireLanguage:function(e){var n=T(e);if(n)return n;throw Error("The '{}' language is required, but not loaded.".replace("{}",e))},autoDetection:I,inherit:y,addPlugin:function(e){o.push(e)}}),t.debugMode=function(){l=!1},t.safeMode=function(){l=!0},t.versionString="10.1.1";for(const n in _)"object"==typeof _[n]&&e(_[n]);return Object.assign(t,_),t}({})}();"object"==typeof exports&&"undefined"!=typeof module&&(module.exports=hljs);hljs.registerLanguage("php",function(){"use strict";return function(e){var r={begin:"\\$+[a-zA-Z_-ÿ][a-zA-Z0-9_-ÿ]*"},t={className:"meta",variants:[{begin:/<\?php/,relevance:10},{begin:/<\?[=]?/},{begin:/\?>/}]},a={className:"string",contains:[e.BACKSLASH_ESCAPE,t],variants:[{begin:'b"',end:'"'},{begin:"b'",end:"'"},e.inherit(e.APOS_STRING_MODE,{illegal:null}),e.inherit(e.QUOTE_STRING_MODE,{illegal:null})]},n={variants:[e.BINARY_NUMBER_MODE,e.C_NUMBER_MODE]},i={keyword:"__CLASS__ __DIR__ __FILE__ __FUNCTION__ __LINE__ __METHOD__ __NAMESPACE__ __TRAIT__ die echo exit include include_once print require require_once array abstract and as binary bool boolean break callable case catch class clone const continue declare default do double else elseif empty enddeclare endfor endforeach endif endswitch endwhile eval extends final finally float for foreach from global goto if implements instanceof insteadof int integer interface isset iterable list new object or private protected public real return string switch throw trait try unset use var void while xor yield",literal:"false null true",built_in:"Error|0 AppendIterator ArgumentCountError ArithmeticError ArrayIterator ArrayObject AssertionError BadFunctionCallException BadMethodCallException CachingIterator CallbackFilterIterator CompileError Countable DirectoryIterator DivisionByZeroError DomainException EmptyIterator ErrorException Exception FilesystemIterator FilterIterator GlobIterator InfiniteIterator InvalidArgumentException IteratorIterator LengthException LimitIterator LogicException MultipleIterator NoRewindIterator OutOfBoundsException OutOfRangeException OuterIterator OverflowException ParentIterator ParseError RangeException RecursiveArrayIterator RecursiveCachingIterator RecursiveCallbackFilterIterator RecursiveDirectoryIterator RecursiveFilterIterator RecursiveIterator RecursiveIteratorIterator RecursiveRegexIterator RecursiveTreeIterator RegexIterator RuntimeException SeekableIterator SplDoublyLinkedList SplFileInfo SplFileObject SplFixedArray SplHeap SplMaxHeap SplMinHeap SplObjectStorage SplObserver SplObserver SplPriorityQueue SplQueue SplStack SplSubject SplSubject SplTempFileObject TypeError UnderflowException UnexpectedValueException ArrayAccess Closure Generator Iterator IteratorAggregate Serializable Throwable Traversable WeakReference Directory __PHP_Incomplete_Class parent php_user_filter self static stdClass"};return{aliases:["php","php3","php4","php5","php6","php7"],case_insensitive:!0,keywords:i,contains:[e.HASH_COMMENT_MODE,e.COMMENT("//","$",{contains:[t]}),e.COMMENT("/\\*","\\*/",{contains:[{className:"doctag",begin:"@[A-Za-z]+"}]}),e.COMMENT("__halt_compiler.+?;",!1,{endsWithParent:!0,keywords:"__halt_compiler"}),{className:"string",begin:/<<<['"]?\w+['"]?$/,end:/^\w+;?$/,contains:[e.BACKSLASH_ESCAPE,{className:"subst",variants:[{begin:/\$\w+/},{begin:/\{\$/,end:/\}/}]}]},t,{className:"keyword",begin:/\$this\b/},r,{begin:/(::|->)+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/},{className:"function",beginKeywords:"fn function",end:/[;{]/,excludeEnd:!0,illegal:"[$%\\[]",contains:[e.UNDERSCORE_TITLE_MODE,{className:"params",begin:"\\(",end:"\\)",excludeBegin:!0,excludeEnd:!0,keywords:i,contains:["self",r,e.C_BLOCK_COMMENT_MODE,a,n]}]},{className:"class",beginKeywords:"class interface",end:"{",excludeEnd:!0,illegal:/[:\(\$"]/,contains:[{beginKeywords:"extends implements"},e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"namespace",end:";",illegal:/[\.']/,contains:[e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"use",end:";",contains:[e.UNDERSCORE_TITLE_MODE]},{begin:"=>"},a,n]}}}());hljs.registerLanguage("nginx",function(){"use strict";return function(e){var n={className:"variable",variants:[{begin:/\$\d+/},{begin:/\$\{/,end:/}/},{begin:"[\\$\\@]"+e.UNDERSCORE_IDENT_RE}]},a={endsWithParent:!0,keywords:{$pattern:"[a-z/_]+",literal:"on off yes no true false none blocked debug info notice warn error crit select break last permanent redirect kqueue rtsig epoll poll /dev/poll"},relevance:0,illegal:"=>",contains:[e.HASH_COMMENT_MODE,{className:"string",contains:[e.BACKSLASH_ESCAPE,n],variants:[{begin:/"/,end:/"/},{begin:/'/,end:/'/}]},{begin:"([a-z]+):/",end:"\\s",endsWithParent:!0,excludeEnd:!0,contains:[n]},{className:"regexp",contains:[e.BACKSLASH_ESCAPE,n],variants:[{begin:"\\s\\^",end:"\\s|{|;",returnEnd:!0},{begin:"~\\*?\\s+",end:"\\s|{|;",returnEnd:!0},{begin:"\\*(\\.[a-z\\-]+)+"},{begin:"([a-z\\-]+\\.)+\\*"}]},{className:"number",begin:"\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}(:\\d{1,5})?\\b"},{className:"number",begin:"\\b\\d+[kKmMgGdshdwy]*\\b",relevance:0},n]};return{name:"Nginx config",aliases:["nginxconf"],contains:[e.HASH_COMMENT_MODE,{begin:e.UNDERSCORE_IDENT_RE+"\\s+{",returnBegin:!0,end:"{",contains:[{className:"section",begin:e.UNDERSCORE_IDENT_RE}],relevance:0},{begin:e.UNDERSCORE_IDENT_RE+"\\s",end:";|{",returnBegin:!0,contains:[{className:"attribute",begin:e.UNDERSCORE_IDENT_RE,starts:a}],relevance:0}],illegal:"[^\\s\\}]"}}}());hljs.registerLanguage("csharp",function(){"use strict";return function(e){var n={keyword:"abstract as base bool break byte case catch char checked const continue decimal default delegate do double enum event explicit extern finally fixed float for foreach goto if implicit in int interface internal is lock long object operator out override params private protected public readonly ref sbyte sealed short sizeof stackalloc static string struct switch this try typeof uint ulong unchecked unsafe ushort using virtual void volatile while add alias ascending async await by descending dynamic equals from get global group into join let nameof on orderby partial remove select set value var when where yield",literal:"null false true"},i=e.inherit(e.TITLE_MODE,{begin:"[a-zA-Z](\\.?\\w)*"}),a={className:"number",variants:[{begin:"\\b(0b[01']+)"},{begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"}],relevance:0},s={className:"string",begin:'@"',end:'"',contains:[{begin:'""'}]},t=e.inherit(s,{illegal:/\n/}),l={className:"subst",begin:"{",end:"}",keywords:n},r=e.inherit(l,{illegal:/\n/}),c={className:"string",begin:/\$"/,end:'"',illegal:/\n/,contains:[{begin:"{{"},{begin:"}}"},e.BACKSLASH_ESCAPE,r]},o={className:"string",begin:/\$@"/,end:'"',contains:[{begin:"{{"},{begin:"}}"},{begin:'""'},l]},g=e.inherit(o,{illegal:/\n/,contains:[{begin:"{{"},{begin:"}}"},{begin:'""'},r]});l.contains=[o,c,s,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,a,e.C_BLOCK_COMMENT_MODE],r.contains=[g,c,t,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,a,e.inherit(e.C_BLOCK_COMMENT_MODE,{illegal:/\n/})];var d={variants:[o,c,s,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},E={begin:"<",end:">",contains:[{beginKeywords:"in out"},i]},_=e.IDENT_RE+"(<"+e.IDENT_RE+"(\\s*,\\s*"+e.IDENT_RE+")*>)?(\\[\\])?",b={begin:"@"+e.IDENT_RE,relevance:0};return{name:"C#",aliases:["cs","c#"],keywords:n,illegal:/::/,contains:[e.COMMENT("///","$",{returnBegin:!0,contains:[{className:"doctag",variants:[{begin:"///",relevance:0},{begin:"\x3c!--|--\x3e"},{begin:""}]}]}),e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{className:"meta",begin:"#",end:"$",keywords:{"meta-keyword":"if else elif endif define undef warning error line region endregion pragma checksum"}},d,a,{beginKeywords:"class interface",end:/[{;=]/,illegal:/[^\s:,]/,contains:[{beginKeywords:"where class"},i,E,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{beginKeywords:"namespace",end:/[{;=]/,illegal:/[^\s:]/,contains:[i,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{className:"meta",begin:"^\\s*\\[",excludeBegin:!0,end:"\\]",excludeEnd:!0,contains:[{className:"meta-string",begin:/"/,end:/"/}]},{beginKeywords:"new return throw await else",relevance:0},{className:"function",begin:"("+_+"\\s+)+"+e.IDENT_RE+"\\s*(\\<.+\\>)?\\s*\\(",returnBegin:!0,end:/\s*[{;=]/,excludeEnd:!0,keywords:n,contains:[{begin:e.IDENT_RE+"\\s*(\\<.+\\>)?\\s*\\(",returnBegin:!0,contains:[e.TITLE_MODE,E],relevance:0},{className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:n,relevance:0,contains:[d,a,e.C_BLOCK_COMMENT_MODE]},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},b]}}}());hljs.registerLanguage("perl",function(){"use strict";return function(e){var n={$pattern:/[\w.]+/,keyword:"getpwent getservent quotemeta msgrcv scalar kill dbmclose undef lc ma syswrite tr send umask sysopen shmwrite vec qx utime local oct semctl localtime readpipe do return format read sprintf dbmopen pop getpgrp not getpwnam rewinddir qq fileno qw endprotoent wait sethostent bless s|0 opendir continue each sleep endgrent shutdown dump chomp connect getsockname die socketpair close flock exists index shmget sub for endpwent redo lstat msgctl setpgrp abs exit select print ref gethostbyaddr unshift fcntl syscall goto getnetbyaddr join gmtime symlink semget splice x|0 getpeername recv log setsockopt cos last reverse gethostbyname getgrnam study formline endhostent times chop length gethostent getnetent pack getprotoent getservbyname rand mkdir pos chmod y|0 substr endnetent printf next open msgsnd readdir use unlink getsockopt getpriority rindex wantarray hex system getservbyport endservent int chr untie rmdir prototype tell listen fork shmread ucfirst setprotoent else sysseek link getgrgid shmctl waitpid unpack getnetbyname reset chdir grep split require caller lcfirst until warn while values shift telldir getpwuid my getprotobynumber delete and sort uc defined srand accept package seekdir getprotobyname semop our rename seek if q|0 chroot sysread setpwent no crypt getc chown sqrt write setnetent setpriority foreach tie sin msgget map stat getlogin unless elsif truncate exec keys glob tied closedir ioctl socket readlink eval xor readline binmode setservent eof ord bind alarm pipe atan2 getgrent exp time push setgrent gt lt or ne m|0 break given say state when"},t={className:"subst",begin:"[$@]\\{",end:"\\}",keywords:n},s={begin:"->{",end:"}"},r={variants:[{begin:/\$\d/},{begin:/[\$%@](\^\w\b|#\w+(::\w+)*|{\w+}|\w+(::\w*)*)/},{begin:/[\$%@][^\s\w{]/,relevance:0}]},i=[e.BACKSLASH_ESCAPE,t,r],a=[r,e.HASH_COMMENT_MODE,e.COMMENT("^\\=\\w","\\=cut",{endsWithParent:!0}),s,{className:"string",contains:i,variants:[{begin:"q[qwxr]?\\s*\\(",end:"\\)",relevance:5},{begin:"q[qwxr]?\\s*\\[",end:"\\]",relevance:5},{begin:"q[qwxr]?\\s*\\{",end:"\\}",relevance:5},{begin:"q[qwxr]?\\s*\\|",end:"\\|",relevance:5},{begin:"q[qwxr]?\\s*\\<",end:"\\>",relevance:5},{begin:"qw\\s+q",end:"q",relevance:5},{begin:"'",end:"'",contains:[e.BACKSLASH_ESCAPE]},{begin:'"',end:'"'},{begin:"`",end:"`",contains:[e.BACKSLASH_ESCAPE]},{begin:"{\\w+}",contains:[],relevance:0},{begin:"-?\\w+\\s*\\=\\>",contains:[],relevance:0}]},{className:"number",begin:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",relevance:0},{begin:"(\\/\\/|"+e.RE_STARTERS_RE+"|\\b(split|return|print|reverse|grep)\\b)\\s*",keywords:"split return print reverse grep",relevance:0,contains:[e.HASH_COMMENT_MODE,{className:"regexp",begin:"(s|tr|y)/(\\\\.|[^/])*/(\\\\.|[^/])*/[a-z]*",relevance:10},{className:"regexp",begin:"(m|qr)?/",end:"/[a-z]*",contains:[e.BACKSLASH_ESCAPE],relevance:0}]},{className:"function",beginKeywords:"sub",end:"(\\s*\\(.*?\\))?[;{]",excludeEnd:!0,relevance:5,contains:[e.TITLE_MODE]},{begin:"-\\w\\b",relevance:0},{begin:"^__DATA__$",end:"^__END__$",subLanguage:"mojolicious",contains:[{begin:"^@@.*",end:"$",className:"comment"}]}];return t.contains=a,s.contains=a,{name:"Perl",aliases:["pl","pm"],keywords:n,contains:a}}}());hljs.registerLanguage("swift",function(){"use strict";return function(e){var i={keyword:"#available #colorLiteral #column #else #elseif #endif #file #fileLiteral #function #if #imageLiteral #line #selector #sourceLocation _ __COLUMN__ __FILE__ __FUNCTION__ __LINE__ Any as as! as? associatedtype associativity break case catch class continue convenience default defer deinit didSet do dynamic dynamicType else enum extension fallthrough false fileprivate final for func get guard if import in indirect infix init inout internal is lazy left let mutating nil none nonmutating open operator optional override postfix precedence prefix private protocol Protocol public repeat required rethrows return right self Self set static struct subscript super switch throw throws true try try! try? Type typealias unowned var weak where while willSet",literal:"true false nil",built_in:"abs advance alignof alignofValue anyGenerator assert assertionFailure bridgeFromObjectiveC bridgeFromObjectiveCUnconditional bridgeToObjectiveC bridgeToObjectiveCUnconditional c compactMap contains count countElements countLeadingZeros debugPrint debugPrintln distance dropFirst dropLast dump encodeBitsAsWords enumerate equal fatalError filter find getBridgedObjectiveCType getVaList indices insertionSort isBridgedToObjectiveC isBridgedVerbatimToObjectiveC isUniquelyReferenced isUniquelyReferencedNonObjC join lazy lexicographicalCompare map max maxElement min minElement numericCast overlaps partition posix precondition preconditionFailure print println quickSort readLine reduce reflect reinterpretCast reverse roundUpToAlignment sizeof sizeofValue sort split startsWith stride strideof strideofValue swap toString transcode underestimateCount unsafeAddressOf unsafeBitCast unsafeDowncast unsafeUnwrap unsafeReflect withExtendedLifetime withObjectAtPlusZero withUnsafePointer withUnsafePointerToObject withUnsafeMutablePointer withUnsafeMutablePointers withUnsafePointer withUnsafePointers withVaList zip"},n=e.COMMENT("/\\*","\\*/",{contains:["self"]}),t={className:"subst",begin:/\\\(/,end:"\\)",keywords:i,contains:[]},a={className:"string",contains:[e.BACKSLASH_ESCAPE,t],variants:[{begin:/"""/,end:/"""/},{begin:/"/,end:/"/}]},r={className:"number",begin:"\\b([\\d_]+(\\.[\\deE_]+)?|0x[a-fA-F0-9_]+(\\.[a-fA-F0-9p_]+)?|0b[01_]+|0o[0-7_]+)\\b",relevance:0};return t.contains=[r],{name:"Swift",keywords:i,contains:[a,e.C_LINE_COMMENT_MODE,n,{className:"type",begin:"\\b[A-Z][\\wÀ-ʸ']*[!?]"},{className:"type",begin:"\\b[A-Z][\\wÀ-ʸ']*",relevance:0},r,{className:"function",beginKeywords:"func",end:"{",excludeEnd:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/[A-Za-z$_][0-9A-Za-z$_]*/}),{begin://},{className:"params",begin:/\(/,end:/\)/,endsParent:!0,keywords:i,contains:["self",r,a,e.C_BLOCK_COMMENT_MODE,{begin:":"}],illegal:/["']/}],illegal:/\[|%/},{className:"class",beginKeywords:"struct protocol class extension enum",keywords:i,end:"\\{",excludeEnd:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/[A-Za-z$_][\u00C0-\u02B80-9A-Za-z$_]*/})]},{className:"meta",begin:"(@discardableResult|@warn_unused_result|@exported|@lazy|@noescape|@NSCopying|@NSManaged|@objc|@objcMembers|@convention|@required|@noreturn|@IBAction|@IBDesignable|@IBInspectable|@IBOutlet|@infix|@prefix|@postfix|@autoclosure|@testable|@available|@nonobjc|@NSApplicationMain|@UIApplicationMain|@dynamicMemberLookup|@propertyWrapper)\\b"},{beginKeywords:"import",end:/$/,contains:[e.C_LINE_COMMENT_MODE,n]}]}}}());hljs.registerLanguage("makefile",function(){"use strict";return function(e){var i={className:"variable",variants:[{begin:"\\$\\("+e.UNDERSCORE_IDENT_RE+"\\)",contains:[e.BACKSLASH_ESCAPE]},{begin:/\$[@%`]+/}]}]}]};return{name:"HTML, XML",aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist","wsf","svg"],case_insensitive:!0,contains:[{className:"meta",begin:"",relevance:10,contains:[a,i,t,s,{begin:"\\[",end:"\\]",contains:[{className:"meta",begin:"",contains:[a,s,i,t]}]}]},e.COMMENT("\x3c!--","--\x3e",{relevance:10}),{begin:"<\\!\\[CDATA\\[",end:"\\]\\]>",relevance:10},n,{className:"meta",begin:/<\?xml/,end:/\?>/,relevance:10},{className:"tag",begin:")",end:">",keywords:{name:"style"},contains:[c],starts:{end:"",returnEnd:!0,subLanguage:["css","xml"]}},{className:"tag",begin:")",end:">",keywords:{name:"script"},contains:[c],starts:{end:"<\/script>",returnEnd:!0,subLanguage:["javascript","handlebars","xml"]}},{className:"tag",begin:"",contains:[{className:"name",begin:/[^\/><\s]+/,relevance:0},c]}]}}}());hljs.registerLanguage("bash",function(){"use strict";return function(e){const s={};Object.assign(s,{className:"variable",variants:[{begin:/\$[\w\d#@][\w\d_]*/},{begin:/\$\{/,end:/\}/,contains:[{begin:/:-/,contains:[s]}]}]});const t={className:"subst",begin:/\$\(/,end:/\)/,contains:[e.BACKSLASH_ESCAPE]},n={className:"string",begin:/"/,end:/"/,contains:[e.BACKSLASH_ESCAPE,s,t]};t.contains.push(n);const a={begin:/\$\(\(/,end:/\)\)/,contains:[{begin:/\d+#[0-9a-f]+/,className:"number"},e.NUMBER_MODE,s]},i=e.SHEBANG({binary:"(fish|bash|zsh|sh|csh|ksh|tcsh|dash|scsh)",relevance:10}),c={className:"function",begin:/\w[\w\d_]*\s*\(\s*\)\s*\{/,returnBegin:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/\w[\w\d_]*/})],relevance:0};return{name:"Bash",aliases:["sh","zsh"],keywords:{$pattern:/\b-?[a-z\._]+\b/,keyword:"if then else elif fi for while in do done case esac function",literal:"true false",built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp",_:"-ne -eq -lt -gt -f -d -e -s -l -a"},contains:[i,e.SHEBANG(),c,a,e.HASH_COMMENT_MODE,n,{className:"",begin:/\\"/},{className:"string",begin:/'/,end:/'/},s]}}}());hljs.registerLanguage("c-like",function(){"use strict";return function(e){function t(e){return"(?:"+e+")?"}var n="(decltype\\(auto\\)|"+t("[a-zA-Z_]\\w*::")+"[a-zA-Z_]\\w*"+t("<.*?>")+")",r={className:"keyword",begin:"\\b[a-z\\d_]*_t\\b"},a={className:"string",variants:[{begin:'(u8?|U|L)?"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE]},{begin:"(u8?|U|L)?'(\\\\(x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4,8}|[0-7]{3}|\\S)|.)",end:"'",illegal:"."},e.END_SAME_AS_BEGIN({begin:/(?:u8?|U|L)?R"([^()\\ ]{0,16})\(/,end:/\)([^()\\ ]{0,16})"/})]},i={className:"number",variants:[{begin:"\\b(0b[01']+)"},{begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"}],relevance:0},s={className:"meta",begin:/#\s*[a-z]+\b/,end:/$/,keywords:{"meta-keyword":"if else elif endif define undef warning error line pragma _Pragma ifdef ifndef include"},contains:[{begin:/\\\n/,relevance:0},e.inherit(a,{className:"meta-string"}),{className:"meta-string",begin:/<.*?>/,end:/$/,illegal:"\\n"},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},o={className:"title",begin:t("[a-zA-Z_]\\w*::")+e.IDENT_RE,relevance:0},c=t("[a-zA-Z_]\\w*::")+e.IDENT_RE+"\\s*\\(",l={keyword:"int float while private char char8_t char16_t char32_t catch import module export virtual operator sizeof dynamic_cast|10 typedef const_cast|10 const for static_cast|10 union namespace unsigned long volatile static protected bool template mutable if public friend do goto auto void enum else break extern using asm case typeid wchar_t short reinterpret_cast|10 default double register explicit signed typename try this switch continue inline delete alignas alignof constexpr consteval constinit decltype concept co_await co_return co_yield requires noexcept static_assert thread_local restrict final override atomic_bool atomic_char atomic_schar atomic_uchar atomic_short atomic_ushort atomic_int atomic_uint atomic_long atomic_ulong atomic_llong atomic_ullong new throw return and and_eq bitand bitor compl not not_eq or or_eq xor xor_eq",built_in:"std string wstring cin cout cerr clog stdin stdout stderr stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set pair bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap priority_queue make_pair array shared_ptr abort terminate abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf future isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc realloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf endl initializer_list unique_ptr _Bool complex _Complex imaginary _Imaginary",literal:"true false nullptr NULL"},d=[r,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,i,a],_={variants:[{begin:/=/,end:/;/},{begin:/\(/,end:/\)/},{beginKeywords:"new throw return else",end:/;/}],keywords:l,contains:d.concat([{begin:/\(/,end:/\)/,keywords:l,contains:d.concat(["self"]),relevance:0}]),relevance:0},u={className:"function",begin:"("+n+"[\\*&\\s]+)+"+c,returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:l,illegal:/[^\w\s\*&:<>]/,contains:[{begin:"decltype\\(auto\\)",keywords:l,relevance:0},{begin:c,returnBegin:!0,contains:[o],relevance:0},{className:"params",begin:/\(/,end:/\)/,keywords:l,relevance:0,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,i,r,{begin:/\(/,end:/\)/,keywords:l,relevance:0,contains:["self",e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,i,r]}]},r,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,s]};return{aliases:["c","cc","h","c++","h++","hpp","hh","hxx","cxx"],keywords:l,disableAutodetect:!0,illegal:"",keywords:l,contains:["self",r]},{begin:e.IDENT_RE+"::",keywords:l},{className:"class",beginKeywords:"class struct",end:/[{;:]/,contains:[{begin://,contains:["self"]},e.TITLE_MODE]}]),exports:{preprocessor:s,strings:a,keywords:l}}}}());hljs.registerLanguage("coffeescript",function(){"use strict";const e=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],n=["true","false","null","undefined","NaN","Infinity"],a=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"]);return function(r){var t={keyword:e.concat(["then","unless","until","loop","by","when","and","or","is","isnt","not"]).filter((e=>n=>!e.includes(n))(["var","const","let","function","static"])).join(" "),literal:n.concat(["yes","no","on","off"]).join(" "),built_in:a.concat(["npm","print"]).join(" ")},i="[A-Za-z$_][0-9A-Za-z$_]*",s={className:"subst",begin:/#\{/,end:/}/,keywords:t},o=[r.BINARY_NUMBER_MODE,r.inherit(r.C_NUMBER_MODE,{starts:{end:"(\\s*/)?",relevance:0}}),{className:"string",variants:[{begin:/'''/,end:/'''/,contains:[r.BACKSLASH_ESCAPE]},{begin:/'/,end:/'/,contains:[r.BACKSLASH_ESCAPE]},{begin:/"""/,end:/"""/,contains:[r.BACKSLASH_ESCAPE,s]},{begin:/"/,end:/"/,contains:[r.BACKSLASH_ESCAPE,s]}]},{className:"regexp",variants:[{begin:"///",end:"///",contains:[s,r.HASH_COMMENT_MODE]},{begin:"//[gim]{0,3}(?=\\W)",relevance:0},{begin:/\/(?![ *]).*?(?![\\]).\/[gim]{0,3}(?=\W)/}]},{begin:"@"+i},{subLanguage:"javascript",excludeBegin:!0,excludeEnd:!0,variants:[{begin:"```",end:"```"},{begin:"`",end:"`"}]}];s.contains=o;var c=r.inherit(r.TITLE_MODE,{begin:i}),l={className:"params",begin:"\\([^\\(]",returnBegin:!0,contains:[{begin:/\(/,end:/\)/,keywords:t,contains:["self"].concat(o)}]};return{name:"CoffeeScript",aliases:["coffee","cson","iced"],keywords:t,illegal:/\/\*/,contains:o.concat([r.COMMENT("###","###"),r.HASH_COMMENT_MODE,{className:"function",begin:"^\\s*"+i+"\\s*=\\s*(\\(.*\\))?\\s*\\B[-=]>",end:"[-=]>",returnBegin:!0,contains:[c,l]},{begin:/[:\(,=]\s*/,relevance:0,contains:[{className:"function",begin:"(\\(.*\\))?\\s*\\B[-=]>",end:"[-=]>",returnBegin:!0,contains:[l]}]},{className:"class",beginKeywords:"class",end:"$",illegal:/[:="\[\]]/,contains:[{beginKeywords:"extends",endsWithParent:!0,illegal:/[:="\[\]]/,contains:[c]},c]},{begin:i+":",end:":",returnBegin:!0,returnEnd:!0,relevance:0}])}}}());hljs.registerLanguage("ruby",function(){"use strict";return function(e){var n="[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?",a={keyword:"and then defined module in return redo if BEGIN retry end for self when next until do begin unless END rescue else break undef not super class case require yield alias while ensure elsif or include attr_reader attr_writer attr_accessor",literal:"true false nil"},s={className:"doctag",begin:"@[A-Za-z]+"},i={begin:"#<",end:">"},r=[e.COMMENT("#","$",{contains:[s]}),e.COMMENT("^\\=begin","^\\=end",{contains:[s],relevance:10}),e.COMMENT("^__END__","\\n$")],c={className:"subst",begin:"#\\{",end:"}",keywords:a},t={className:"string",contains:[e.BACKSLASH_ESCAPE,c],variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/`/,end:/`/},{begin:"%[qQwWx]?\\(",end:"\\)"},{begin:"%[qQwWx]?\\[",end:"\\]"},{begin:"%[qQwWx]?{",end:"}"},{begin:"%[qQwWx]?<",end:">"},{begin:"%[qQwWx]?/",end:"/"},{begin:"%[qQwWx]?%",end:"%"},{begin:"%[qQwWx]?-",end:"-"},{begin:"%[qQwWx]?\\|",end:"\\|"},{begin:/\B\?(\\\d{1,3}|\\x[A-Fa-f0-9]{1,2}|\\u[A-Fa-f0-9]{4}|\\?\S)\b/},{begin:/<<[-~]?'?(\w+)(?:.|\n)*?\n\s*\1\b/,returnBegin:!0,contains:[{begin:/<<[-~]?'?/},e.END_SAME_AS_BEGIN({begin:/(\w+)/,end:/(\w+)/,contains:[e.BACKSLASH_ESCAPE,c]})]}]},b={className:"params",begin:"\\(",end:"\\)",endsParent:!0,keywords:a},d=[t,i,{className:"class",beginKeywords:"class module",end:"$|;",illegal:/=/,contains:[e.inherit(e.TITLE_MODE,{begin:"[A-Za-z_]\\w*(::\\w+)*(\\?|\\!)?"}),{begin:"<\\s*",contains:[{begin:"("+e.IDENT_RE+"::)?"+e.IDENT_RE}]}].concat(r)},{className:"function",beginKeywords:"def",end:"$|;",contains:[e.inherit(e.TITLE_MODE,{begin:n}),b].concat(r)},{begin:e.IDENT_RE+"::"},{className:"symbol",begin:e.UNDERSCORE_IDENT_RE+"(\\!|\\?)?:",relevance:0},{className:"symbol",begin:":(?!\\s)",contains:[t,{begin:n}],relevance:0},{className:"number",begin:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",relevance:0},{begin:"(\\$\\W)|((\\$|\\@\\@?)(\\w+))"},{className:"params",begin:/\|/,end:/\|/,keywords:a},{begin:"("+e.RE_STARTERS_RE+"|unless)\\s*",keywords:"unless",contains:[i,{className:"regexp",contains:[e.BACKSLASH_ESCAPE,c],illegal:/\n/,variants:[{begin:"/",end:"/[a-z]*"},{begin:"%r{",end:"}[a-z]*"},{begin:"%r\\(",end:"\\)[a-z]*"},{begin:"%r!",end:"![a-z]*"},{begin:"%r\\[",end:"\\][a-z]*"}]}].concat(r),relevance:0}].concat(r);c.contains=d,b.contains=d;var g=[{begin:/^\s*=>/,starts:{end:"$",contains:d}},{className:"meta",begin:"^([>?]>|[\\w#]+\\(\\w+\\):\\d+:\\d+>|(\\w+-)?\\d+\\.\\d+\\.\\d(p\\d+)?[^>]+>)",starts:{end:"$",contains:d}}];return{name:"Ruby",aliases:["rb","gemspec","podspec","thor","irb"],keywords:a,illegal:/\/\*/,contains:r.concat(g).concat(d)}}}());hljs.registerLanguage("yaml",function(){"use strict";return function(e){var n="true false yes no null",a="[\\w#;/?:@&=+$,.~*\\'()[\\]]+",s={className:"string",relevance:0,variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/\S+/}],contains:[e.BACKSLASH_ESCAPE,{className:"template-variable",variants:[{begin:"{{",end:"}}"},{begin:"%{",end:"}"}]}]},i=e.inherit(s,{variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/[^\s,{}[\]]+/}]}),l={end:",",endsWithParent:!0,excludeEnd:!0,contains:[],keywords:n,relevance:0},t={begin:"{",end:"}",contains:[l],illegal:"\\n",relevance:0},g={begin:"\\[",end:"\\]",contains:[l],illegal:"\\n",relevance:0},b=[{className:"attr",variants:[{begin:"\\w[\\w :\\/.-]*:(?=[ \t]|$)"},{begin:'"\\w[\\w :\\/.-]*":(?=[ \t]|$)'},{begin:"'\\w[\\w :\\/.-]*':(?=[ \t]|$)"}]},{className:"meta",begin:"^---s*$",relevance:10},{className:"string",begin:"[\\|>]([0-9]?[+-])?[ ]*\\n( *)[\\S ]+\\n(\\2[\\S ]+\\n?)*"},{begin:"<%[%=-]?",end:"[%-]?%>",subLanguage:"ruby",excludeBegin:!0,excludeEnd:!0,relevance:0},{className:"type",begin:"!\\w+!"+a},{className:"type",begin:"!<"+a+">"},{className:"type",begin:"!"+a},{className:"type",begin:"!!"+a},{className:"meta",begin:"&"+e.UNDERSCORE_IDENT_RE+"$"},{className:"meta",begin:"\\*"+e.UNDERSCORE_IDENT_RE+"$"},{className:"bullet",begin:"\\-(?=[ ]|$)",relevance:0},e.HASH_COMMENT_MODE,{beginKeywords:n,keywords:{literal:n}},{className:"number",begin:"\\b[0-9]{4}(-[0-9][0-9]){0,2}([Tt \\t][0-9][0-9]?(:[0-9][0-9]){2})?(\\.[0-9]*)?([ \\t])*(Z|[-+][0-9][0-9]?(:[0-9][0-9])?)?\\b"},{className:"number",begin:e.C_NUMBER_RE+"\\b"},t,g,s],c=[...b];return c.pop(),c.push(i),l.contains=c,{name:"YAML",case_insensitive:!0,aliases:["yml","YAML"],contains:b}}}());hljs.registerLanguage("d",function(){"use strict";return function(e){var a={$pattern:e.UNDERSCORE_IDENT_RE,keyword:"abstract alias align asm assert auto body break byte case cast catch class const continue debug default delete deprecated do else enum export extern final finally for foreach foreach_reverse|10 goto if immutable import in inout int interface invariant is lazy macro mixin module new nothrow out override package pragma private protected public pure ref return scope shared static struct super switch synchronized template this throw try typedef typeid typeof union unittest version void volatile while with __FILE__ __LINE__ __gshared|10 __thread __traits __DATE__ __EOF__ __TIME__ __TIMESTAMP__ __VENDOR__ __VERSION__",built_in:"bool cdouble cent cfloat char creal dchar delegate double dstring float function idouble ifloat ireal long real short string ubyte ucent uint ulong ushort wchar wstring",literal:"false null true"},d="((0|[1-9][\\d_]*)|0[bB][01_]+|0[xX]([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*))",n="\\\\(['\"\\?\\\\abfnrtv]|u[\\dA-Fa-f]{4}|[0-7]{1,3}|x[\\dA-Fa-f]{2}|U[\\dA-Fa-f]{8})|&[a-zA-Z\\d]{2,};",t={className:"number",begin:"\\b"+d+"(L|u|U|Lu|LU|uL|UL)?",relevance:0},_={className:"number",begin:"\\b(((0[xX](([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*)\\.([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*)|\\.?([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*))[pP][+-]?(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d))|((0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)(\\.\\d*|([eE][+-]?(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)))|\\d+\\.(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)|\\.(0|[1-9][\\d_]*)([eE][+-]?(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d))?))([fF]|L|i|[fF]i|Li)?|"+d+"(i|[fF]i|Li))",relevance:0},r={className:"string",begin:"'("+n+"|.)",end:"'",illegal:"."},i={className:"string",begin:'"',contains:[{begin:n,relevance:0}],end:'"[cwd]?'},s=e.COMMENT("\\/\\+","\\+\\/",{contains:["self"],relevance:10});return{name:"D",keywords:a,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,s,{className:"string",begin:'x"[\\da-fA-F\\s\\n\\r]*"[cwd]?',relevance:10},i,{className:"string",begin:'[rq]"',end:'"[cwd]?',relevance:5},{className:"string",begin:"`",end:"`[cwd]?"},{className:"string",begin:'q"\\{',end:'\\}"'},_,t,r,{className:"meta",begin:"^#!",end:"$",relevance:5},{className:"meta",begin:"#(line)",end:"$",relevance:5},{className:"keyword",begin:"@[a-zA-Z_][a-zA-Z_\\d]*"}]}}}());hljs.registerLanguage("properties",function(){"use strict";return function(e){var n="[ \\t\\f]*",t="("+n+"[:=]"+n+"|[ \\t\\f]+)",a="([^\\\\:= \\t\\f\\n]|\\\\.)+",s={end:t,relevance:0,starts:{className:"string",end:/$/,relevance:0,contains:[{begin:"\\\\\\n"}]}};return{name:".properties",case_insensitive:!0,illegal:/\S/,contains:[e.COMMENT("^\\s*[!#]","$"),{begin:"([^\\\\\\W:= \\t\\f\\n]|\\\\.)+"+t,returnBegin:!0,contains:[{className:"attr",begin:"([^\\\\\\W:= \\t\\f\\n]|\\\\.)+",endsParent:!0,relevance:0}],starts:s},{begin:a+t,returnBegin:!0,relevance:0,contains:[{className:"meta",begin:a,endsParent:!0,relevance:0}],starts:s},{className:"attr",relevance:0,begin:a+n+"$"}]}}}());hljs.registerLanguage("http",function(){"use strict";return function(e){var n="HTTP/[0-9\\.]+";return{name:"HTTP",aliases:["https"],illegal:"\\S",contains:[{begin:"^"+n,end:"$",contains:[{className:"number",begin:"\\b\\d{3}\\b"}]},{begin:"^[A-Z]+ (.*?) "+n+"$",returnBegin:!0,end:"$",contains:[{className:"string",begin:" ",end:" ",excludeBegin:!0,excludeEnd:!0},{begin:n},{className:"keyword",begin:"[A-Z]+"}]},{className:"attribute",begin:"^\\w",end:": ",excludeEnd:!0,illegal:"\\n|\\s|=",starts:{end:"$",relevance:0}},{begin:"\\n\\n",starts:{subLanguage:[],endsWithParent:!0}}]}}}());hljs.registerLanguage("haskell",function(){"use strict";return function(e){var n={variants:[e.COMMENT("--","$"),e.COMMENT("{-","-}",{contains:["self"]})]},i={className:"meta",begin:"{-#",end:"#-}"},a={className:"meta",begin:"^#",end:"$"},s={className:"type",begin:"\\b[A-Z][\\w']*",relevance:0},l={begin:"\\(",end:"\\)",illegal:'"',contains:[i,a,{className:"type",begin:"\\b[A-Z][\\w]*(\\((\\.\\.|,|\\w+)\\))?"},e.inherit(e.TITLE_MODE,{begin:"[_a-z][\\w']*"}),n]};return{name:"Haskell",aliases:["hs"],keywords:"let in if then else case of where do module import hiding qualified type data newtype deriving class instance as default infix infixl infixr foreign export ccall stdcall cplusplus jvm dotnet safe unsafe family forall mdo proc rec",contains:[{beginKeywords:"module",end:"where",keywords:"module where",contains:[l,n],illegal:"\\W\\.|;"},{begin:"\\bimport\\b",end:"$",keywords:"import qualified as hiding",contains:[l,n],illegal:"\\W\\.|;"},{className:"class",begin:"^(\\s*)?(class|instance)\\b",end:"where",keywords:"class family instance where",contains:[s,l,n]},{className:"class",begin:"\\b(data|(new)?type)\\b",end:"$",keywords:"data family type newtype deriving",contains:[i,s,l,{begin:"{",end:"}",contains:l.contains},n]},{beginKeywords:"default",end:"$",contains:[s,l,n]},{beginKeywords:"infix infixl infixr",end:"$",contains:[e.C_NUMBER_MODE,n]},{begin:"\\bforeign\\b",end:"$",keywords:"foreign import export ccall stdcall cplusplus jvm dotnet safe unsafe",contains:[s,e.QUOTE_STRING_MODE,n]},{className:"meta",begin:"#!\\/usr\\/bin\\/env runhaskell",end:"$"},i,a,e.QUOTE_STRING_MODE,e.C_NUMBER_MODE,s,e.inherit(e.TITLE_MODE,{begin:"^[_a-z][\\w']*"}),n,{begin:"->|<-"}]}}}());hljs.registerLanguage("handlebars",function(){"use strict";function e(...e){return e.map(e=>(function(e){return e?"string"==typeof e?e:e.source:null})(e)).join("")}return function(n){const a={"builtin-name":"action bindattr collection component concat debugger each each-in get hash if in input link-to loc log lookup mut outlet partial query-params render template textarea unbound unless view with yield"},t=/\[.*?\]/,s=/[^\s!"#%&'()*+,.\/;<=>@\[\\\]^`{|}~]+/,i=e("(",/'.*?'/,"|",/".*?"/,"|",t,"|",s,"|",/\.|\//,")+"),r=e("(",t,"|",s,")(?==)"),l={begin:i,lexemes:/[\w.\/]+/},c=n.inherit(l,{keywords:{literal:"true false undefined null"}}),o={begin:/\(/,end:/\)/},m={className:"attr",begin:r,relevance:0,starts:{begin:/=/,end:/=/,starts:{contains:[n.NUMBER_MODE,n.QUOTE_STRING_MODE,n.APOS_STRING_MODE,c,o]}}},d={contains:[n.NUMBER_MODE,n.QUOTE_STRING_MODE,n.APOS_STRING_MODE,{begin:/as\s+\|/,keywords:{keyword:"as"},end:/\|/,contains:[{begin:/\w+/}]},m,c,o],returnEnd:!0},g=n.inherit(l,{className:"name",keywords:a,starts:n.inherit(d,{end:/\)/})});o.contains=[g];const u=n.inherit(l,{keywords:a,className:"name",starts:n.inherit(d,{end:/}}/})}),b=n.inherit(l,{keywords:a,className:"name"}),h=n.inherit(l,{className:"name",keywords:a,starts:n.inherit(d,{end:/}}/})});return{name:"Handlebars",aliases:["hbs","html.hbs","html.handlebars","htmlbars"],case_insensitive:!0,subLanguage:"xml",contains:[{begin:/\\\{\{/,skip:!0},{begin:/\\\\(?=\{\{)/,skip:!0},n.COMMENT(/\{\{!--/,/--\}\}/),n.COMMENT(/\{\{!/,/\}\}/),{className:"template-tag",begin:/\{\{\{\{(?!\/)/,end:/\}\}\}\}/,contains:[u],starts:{end:/\{\{\{\{\//,returnEnd:!0,subLanguage:"xml"}},{className:"template-tag",begin:/\{\{\{\{\//,end:/\}\}\}\}/,contains:[b]},{className:"template-tag",begin:/\{\{#/,end:/\}\}/,contains:[u]},{className:"template-tag",begin:/\{\{(?=else\}\})/,end:/\}\}/,keywords:"else"},{className:"template-tag",begin:/\{\{\//,end:/\}\}/,contains:[b]},{className:"template-variable",begin:/\{\{\{/,end:/\}\}\}/,contains:[h]},{className:"template-variable",begin:/\{\{/,end:/\}\}/,contains:[h]}]}}}());hljs.registerLanguage("rust",function(){"use strict";return function(e){var n="([ui](8|16|32|64|128|size)|f(32|64))?",t="drop i8 i16 i32 i64 i128 isize u8 u16 u32 u64 u128 usize f32 f64 str char bool Box Option Result String Vec Copy Send Sized Sync Drop Fn FnMut FnOnce ToOwned Clone Debug PartialEq PartialOrd Eq Ord AsRef AsMut Into From Default Iterator Extend IntoIterator DoubleEndedIterator ExactSizeIterator SliceConcatExt ToString assert! assert_eq! bitflags! bytes! cfg! col! concat! concat_idents! debug_assert! debug_assert_eq! env! panic! file! format! format_args! include_bin! include_str! line! local_data_key! module_path! option_env! print! println! select! stringify! try! unimplemented! unreachable! vec! write! writeln! macro_rules! assert_ne! debug_assert_ne!";return{name:"Rust",aliases:["rs"],keywords:{$pattern:e.IDENT_RE+"!?",keyword:"abstract as async await become box break const continue crate do dyn else enum extern false final fn for if impl in let loop macro match mod move mut override priv pub ref return self Self static struct super trait true try type typeof unsafe unsized use virtual where while yield",literal:"true false Some None Ok Err",built_in:t},illegal:""}]}}}());hljs.registerLanguage("cpp",function(){"use strict";return function(e){var t=e.getLanguage("c-like").rawDefinition();return t.disableAutodetect=!1,t.name="C++",t.aliases=["cc","c++","h++","hpp","hh","hxx","cxx"],t}}());hljs.registerLanguage("ini",function(){"use strict";function e(e){return e?"string"==typeof e?e:e.source:null}function n(...n){return n.map(n=>e(n)).join("")}return function(a){var s={className:"number",relevance:0,variants:[{begin:/([\+\-]+)?[\d]+_[\d_]+/},{begin:a.NUMBER_RE}]},i=a.COMMENT();i.variants=[{begin:/;/,end:/$/},{begin:/#/,end:/$/}];var t={className:"variable",variants:[{begin:/\$[\w\d"][\w\d_]*/},{begin:/\$\{(.*?)}/}]},r={className:"literal",begin:/\bon|off|true|false|yes|no\b/},l={className:"string",contains:[a.BACKSLASH_ESCAPE],variants:[{begin:"'''",end:"'''",relevance:10},{begin:'"""',end:'"""',relevance:10},{begin:'"',end:'"'},{begin:"'",end:"'"}]},c={begin:/\[/,end:/\]/,contains:[i,r,t,l,s,"self"],relevance:0},g="("+[/[A-Za-z0-9_-]+/,/"(\\"|[^"])*"/,/'[^']*'/].map(n=>e(n)).join("|")+")";return{name:"TOML, also INI",aliases:["toml"],case_insensitive:!0,illegal:/\S/,contains:[i,{className:"section",begin:/\[+/,end:/\]+/},{begin:n(g,"(\\s*\\.\\s*",g,")*",n("(?=",/\s*=\s*[^#\s]/,")")),className:"attr",starts:{end:/$/,contains:[i,c,r,t,l,s]}}]}}}());hljs.registerLanguage("objectivec",function(){"use strict";return function(e){var n=/[a-zA-Z@][a-zA-Z0-9_]*/,_={$pattern:n,keyword:"@interface @class @protocol @implementation"};return{name:"Objective-C",aliases:["mm","objc","obj-c"],keywords:{$pattern:n,keyword:"int float while char export sizeof typedef const struct for union unsigned long volatile static bool mutable if do return goto void enum else break extern asm case short default double register explicit signed typename this switch continue wchar_t inline readonly assign readwrite self @synchronized id typeof nonatomic super unichar IBOutlet IBAction strong weak copy in out inout bycopy byref oneway __strong __weak __block __autoreleasing @private @protected @public @try @property @end @throw @catch @finally @autoreleasepool @synthesize @dynamic @selector @optional @required @encode @package @import @defs @compatibility_alias __bridge __bridge_transfer __bridge_retained __bridge_retain __covariant __contravariant __kindof _Nonnull _Nullable _Null_unspecified __FUNCTION__ __PRETTY_FUNCTION__ __attribute__ getter setter retain unsafe_unretained nonnull nullable null_unspecified null_resettable class instancetype NS_DESIGNATED_INITIALIZER NS_UNAVAILABLE NS_REQUIRES_SUPER NS_RETURNS_INNER_POINTER NS_INLINE NS_AVAILABLE NS_DEPRECATED NS_ENUM NS_OPTIONS NS_SWIFT_UNAVAILABLE NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_END NS_REFINED_FOR_SWIFT NS_SWIFT_NAME NS_SWIFT_NOTHROW NS_DURING NS_HANDLER NS_ENDHANDLER NS_VALUERETURN NS_VOIDRETURN",literal:"false true FALSE TRUE nil YES NO NULL",built_in:"BOOL dispatch_once_t dispatch_queue_t dispatch_sync dispatch_async dispatch_once"},illegal:"/,end:/$/,illegal:"\\n"},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{className:"class",begin:"("+_.keyword.split(" ").join("|")+")\\b",end:"({|$)",excludeEnd:!0,keywords:_,contains:[e.UNDERSCORE_TITLE_MODE]},{begin:"\\."+e.UNDERSCORE_IDENT_RE,relevance:0}]}}}());hljs.registerLanguage("apache",function(){"use strict";return function(e){var n={className:"number",begin:"\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}(:\\d{1,5})?"};return{name:"Apache config",aliases:["apacheconf"],case_insensitive:!0,contains:[e.HASH_COMMENT_MODE,{className:"section",begin:"",contains:[n,{className:"number",begin:":\\d{1,5}"},e.inherit(e.QUOTE_STRING_MODE,{relevance:0})]},{className:"attribute",begin:/\w+/,relevance:0,keywords:{nomarkup:"order deny allow setenv rewriterule rewriteengine rewritecond documentroot sethandler errordocument loadmodule options header listen serverroot servername"},starts:{end:/$/,relevance:0,keywords:{literal:"on off all deny allow"},contains:[{className:"meta",begin:"\\s\\[",end:"\\]$"},{className:"variable",begin:"[\\$%]\\{",end:"\\}",contains:["self",{className:"number",begin:"[\\$%]\\d+"}]},n,{className:"number",begin:"\\d+"},e.QUOTE_STRING_MODE]}}],illegal:/\S/}}}());hljs.registerLanguage("java",function(){"use strict";function e(e){return e?"string"==typeof e?e:e.source:null}function n(e){return a("(",e,")?")}function a(...n){return n.map(n=>e(n)).join("")}function s(...n){return"("+n.map(n=>e(n)).join("|")+")"}return function(e){var t="false synchronized int abstract float private char boolean var static null if const for true while long strictfp finally protected import native final void enum else break transient catch instanceof byte super volatile case assert short package default double public try this switch continue throws protected public private module requires exports do",i={className:"meta",begin:"@[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*",contains:[{begin:/\(/,end:/\)/,contains:["self"]}]},r=e=>a("[",e,"]+([",e,"_]*[",e,"]+)?"),c={className:"number",variants:[{begin:`\\b(0[bB]${r("01")})[lL]?`},{begin:`\\b(0${r("0-7")})[dDfFlL]?`},{begin:a(/\b0[xX]/,s(a(r("a-fA-F0-9"),/\./,r("a-fA-F0-9")),a(r("a-fA-F0-9"),/\.?/),a(/\./,r("a-fA-F0-9"))),/([pP][+-]?(\d+))?/,/[fFdDlL]?/)},{begin:a(/\b/,s(a(/\d*\./,r("\\d")),r("\\d")),/[eE][+-]?[\d]+[dDfF]?/)},{begin:a(/\b/,r(/\d/),n(/\.?/),n(r(/\d/)),/[dDfFlL]?/)}],relevance:0};return{name:"Java",aliases:["jsp"],keywords:t,illegal:/<\/|#/,contains:[e.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{begin:/\w+@/,relevance:0},{className:"doctag",begin:"@[A-Za-z]+"}]}),e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,{className:"class",beginKeywords:"class interface",end:/[{;=]/,excludeEnd:!0,keywords:"class interface",illegal:/[:"\[\]]/,contains:[{beginKeywords:"extends implements"},e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"new throw return else",relevance:0},{className:"function",begin:"([À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*(<[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*(\\s*,\\s*[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*)*>)?\\s+)+"+e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:t,contains:[{begin:e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,relevance:0,contains:[e.UNDERSCORE_TITLE_MODE]},{className:"params",begin:/\(/,end:/\)/,keywords:t,relevance:0,contains:[i,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.C_NUMBER_MODE,e.C_BLOCK_COMMENT_MODE]},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},c,i]}}}());hljs.registerLanguage("x86asm",function(){"use strict";return function(s){return{name:"Intel x86 Assembly",case_insensitive:!0,keywords:{$pattern:"[.%]?"+s.IDENT_RE,keyword:"lock rep repe repz repne repnz xaquire xrelease bnd nobnd aaa aad aam aas adc add and arpl bb0_reset bb1_reset bound bsf bsr bswap bt btc btr bts call cbw cdq cdqe clc cld cli clts cmc cmp cmpsb cmpsd cmpsq cmpsw cmpxchg cmpxchg486 cmpxchg8b cmpxchg16b cpuid cpu_read cpu_write cqo cwd cwde daa das dec div dmint emms enter equ f2xm1 fabs fadd faddp fbld fbstp fchs fclex fcmovb fcmovbe fcmove fcmovnb fcmovnbe fcmovne fcmovnu fcmovu fcom fcomi fcomip fcomp fcompp fcos fdecstp fdisi fdiv fdivp fdivr fdivrp femms feni ffree ffreep fiadd ficom ficomp fidiv fidivr fild fimul fincstp finit fist fistp fisttp fisub fisubr fld fld1 fldcw fldenv fldl2e fldl2t fldlg2 fldln2 fldpi fldz fmul fmulp fnclex fndisi fneni fninit fnop fnsave fnstcw fnstenv fnstsw fpatan fprem fprem1 fptan frndint frstor fsave fscale fsetpm fsin fsincos fsqrt fst fstcw fstenv fstp fstsw fsub fsubp fsubr fsubrp ftst fucom fucomi fucomip fucomp fucompp fxam fxch fxtract fyl2x fyl2xp1 hlt ibts icebp idiv imul in inc incbin insb insd insw int int01 int1 int03 int3 into invd invpcid invlpg invlpga iret iretd iretq iretw jcxz jecxz jrcxz jmp jmpe lahf lar lds lea leave les lfence lfs lgdt lgs lidt lldt lmsw loadall loadall286 lodsb lodsd lodsq lodsw loop loope loopne loopnz loopz lsl lss ltr mfence monitor mov movd movq movsb movsd movsq movsw movsx movsxd movzx mul mwait neg nop not or out outsb outsd outsw packssdw packsswb packuswb paddb paddd paddsb paddsiw paddsw paddusb paddusw paddw pand pandn pause paveb pavgusb pcmpeqb pcmpeqd pcmpeqw pcmpgtb pcmpgtd pcmpgtw pdistib pf2id pfacc pfadd pfcmpeq pfcmpge pfcmpgt pfmax pfmin pfmul pfrcp pfrcpit1 pfrcpit2 pfrsqit1 pfrsqrt pfsub pfsubr pi2fd pmachriw pmaddwd pmagw pmulhriw pmulhrwa pmulhrwc pmulhw pmullw pmvgezb pmvlzb pmvnzb pmvzb pop popa popad popaw popf popfd popfq popfw por prefetch prefetchw pslld psllq psllw psrad psraw psrld psrlq psrlw psubb psubd psubsb psubsiw psubsw psubusb psubusw psubw punpckhbw punpckhdq punpckhwd punpcklbw punpckldq punpcklwd push pusha pushad pushaw pushf pushfd pushfq pushfw pxor rcl rcr rdshr rdmsr rdpmc rdtsc rdtscp ret retf retn rol ror rdm rsdc rsldt rsm rsts sahf sal salc sar sbb scasb scasd scasq scasw sfence sgdt shl shld shr shrd sidt sldt skinit smi smint smintold smsw stc std sti stosb stosd stosq stosw str sub svdc svldt svts swapgs syscall sysenter sysexit sysret test ud0 ud1 ud2b ud2 ud2a umov verr verw fwait wbinvd wrshr wrmsr xadd xbts xchg xlatb xlat xor cmove cmovz cmovne cmovnz cmova cmovnbe cmovae cmovnb cmovb cmovnae cmovbe cmovna cmovg cmovnle cmovge cmovnl cmovl cmovnge cmovle cmovng cmovc cmovnc cmovo cmovno cmovs cmovns cmovp cmovpe cmovnp cmovpo je jz jne jnz ja jnbe jae jnb jb jnae jbe jna jg jnle jge jnl jl jnge jle jng jc jnc jo jno js jns jpo jnp jpe jp sete setz setne setnz seta setnbe setae setnb setnc setb setnae setcset setbe setna setg setnle setge setnl setl setnge setle setng sets setns seto setno setpe setp setpo setnp addps addss andnps andps cmpeqps cmpeqss cmpleps cmpless cmpltps cmpltss cmpneqps cmpneqss cmpnleps cmpnless cmpnltps cmpnltss cmpordps cmpordss cmpunordps cmpunordss cmpps cmpss comiss cvtpi2ps cvtps2pi cvtsi2ss cvtss2si cvttps2pi cvttss2si divps divss ldmxcsr maxps maxss minps minss movaps movhps movlhps movlps movhlps movmskps movntps movss movups mulps mulss orps rcpps rcpss rsqrtps rsqrtss shufps sqrtps sqrtss stmxcsr subps subss ucomiss unpckhps unpcklps xorps fxrstor fxrstor64 fxsave fxsave64 xgetbv xsetbv xsave xsave64 xsaveopt xsaveopt64 xrstor xrstor64 prefetchnta prefetcht0 prefetcht1 prefetcht2 maskmovq movntq pavgb pavgw pextrw pinsrw pmaxsw pmaxub pminsw pminub pmovmskb pmulhuw psadbw pshufw pf2iw pfnacc pfpnacc pi2fw pswapd maskmovdqu clflush movntdq movnti movntpd movdqa movdqu movdq2q movq2dq paddq pmuludq pshufd pshufhw pshuflw pslldq psrldq psubq punpckhqdq punpcklqdq addpd addsd andnpd andpd cmpeqpd cmpeqsd cmplepd cmplesd cmpltpd cmpltsd cmpneqpd cmpneqsd cmpnlepd cmpnlesd cmpnltpd cmpnltsd cmpordpd cmpordsd cmpunordpd cmpunordsd cmppd comisd cvtdq2pd cvtdq2ps cvtpd2dq cvtpd2pi cvtpd2ps cvtpi2pd cvtps2dq cvtps2pd cvtsd2si cvtsd2ss cvtsi2sd cvtss2sd cvttpd2pi cvttpd2dq cvttps2dq cvttsd2si divpd divsd maxpd maxsd minpd minsd movapd movhpd movlpd movmskpd movupd mulpd mulsd orpd shufpd sqrtpd sqrtsd subpd subsd ucomisd unpckhpd unpcklpd xorpd addsubpd addsubps haddpd haddps hsubpd hsubps lddqu movddup movshdup movsldup clgi stgi vmcall vmclear vmfunc vmlaunch vmload vmmcall vmptrld vmptrst vmread vmresume vmrun vmsave vmwrite vmxoff vmxon invept invvpid pabsb pabsw pabsd palignr phaddw phaddd phaddsw phsubw phsubd phsubsw pmaddubsw pmulhrsw pshufb psignb psignw psignd extrq insertq movntsd movntss lzcnt blendpd blendps blendvpd blendvps dppd dpps extractps insertps movntdqa mpsadbw packusdw pblendvb pblendw pcmpeqq pextrb pextrd pextrq phminposuw pinsrb pinsrd pinsrq pmaxsb pmaxsd pmaxud pmaxuw pminsb pminsd pminud pminuw pmovsxbw pmovsxbd pmovsxbq pmovsxwd pmovsxwq pmovsxdq pmovzxbw pmovzxbd pmovzxbq pmovzxwd pmovzxwq pmovzxdq pmuldq pmulld ptest roundpd roundps roundsd roundss crc32 pcmpestri pcmpestrm pcmpistri pcmpistrm pcmpgtq popcnt getsec pfrcpv pfrsqrtv movbe aesenc aesenclast aesdec aesdeclast aesimc aeskeygenassist vaesenc vaesenclast vaesdec vaesdeclast vaesimc vaeskeygenassist vaddpd vaddps vaddsd vaddss vaddsubpd vaddsubps vandpd vandps vandnpd vandnps vblendpd vblendps vblendvpd vblendvps vbroadcastss vbroadcastsd vbroadcastf128 vcmpeq_ospd vcmpeqpd vcmplt_ospd vcmpltpd vcmple_ospd vcmplepd vcmpunord_qpd vcmpunordpd vcmpneq_uqpd vcmpneqpd vcmpnlt_uspd vcmpnltpd vcmpnle_uspd vcmpnlepd vcmpord_qpd vcmpordpd vcmpeq_uqpd vcmpnge_uspd vcmpngepd vcmpngt_uspd vcmpngtpd vcmpfalse_oqpd vcmpfalsepd vcmpneq_oqpd vcmpge_ospd vcmpgepd vcmpgt_ospd vcmpgtpd vcmptrue_uqpd vcmptruepd vcmplt_oqpd vcmple_oqpd vcmpunord_spd vcmpneq_uspd vcmpnlt_uqpd vcmpnle_uqpd vcmpord_spd vcmpeq_uspd vcmpnge_uqpd vcmpngt_uqpd vcmpfalse_ospd vcmpneq_ospd vcmpge_oqpd vcmpgt_oqpd vcmptrue_uspd vcmppd vcmpeq_osps vcmpeqps vcmplt_osps vcmpltps vcmple_osps vcmpleps vcmpunord_qps vcmpunordps vcmpneq_uqps vcmpneqps vcmpnlt_usps vcmpnltps vcmpnle_usps vcmpnleps vcmpord_qps vcmpordps vcmpeq_uqps vcmpnge_usps vcmpngeps vcmpngt_usps vcmpngtps vcmpfalse_oqps vcmpfalseps vcmpneq_oqps vcmpge_osps vcmpgeps vcmpgt_osps vcmpgtps vcmptrue_uqps vcmptrueps vcmplt_oqps vcmple_oqps vcmpunord_sps vcmpneq_usps vcmpnlt_uqps vcmpnle_uqps vcmpord_sps vcmpeq_usps vcmpnge_uqps vcmpngt_uqps vcmpfalse_osps vcmpneq_osps vcmpge_oqps vcmpgt_oqps vcmptrue_usps vcmpps vcmpeq_ossd vcmpeqsd vcmplt_ossd vcmpltsd vcmple_ossd vcmplesd vcmpunord_qsd vcmpunordsd vcmpneq_uqsd vcmpneqsd vcmpnlt_ussd vcmpnltsd vcmpnle_ussd vcmpnlesd vcmpord_qsd vcmpordsd vcmpeq_uqsd vcmpnge_ussd vcmpngesd vcmpngt_ussd vcmpngtsd vcmpfalse_oqsd vcmpfalsesd vcmpneq_oqsd vcmpge_ossd vcmpgesd vcmpgt_ossd vcmpgtsd vcmptrue_uqsd vcmptruesd vcmplt_oqsd vcmple_oqsd vcmpunord_ssd vcmpneq_ussd vcmpnlt_uqsd vcmpnle_uqsd vcmpord_ssd vcmpeq_ussd vcmpnge_uqsd vcmpngt_uqsd vcmpfalse_ossd vcmpneq_ossd vcmpge_oqsd vcmpgt_oqsd vcmptrue_ussd vcmpsd vcmpeq_osss vcmpeqss vcmplt_osss vcmpltss vcmple_osss vcmpless vcmpunord_qss vcmpunordss vcmpneq_uqss vcmpneqss vcmpnlt_usss vcmpnltss vcmpnle_usss vcmpnless vcmpord_qss vcmpordss vcmpeq_uqss vcmpnge_usss vcmpngess vcmpngt_usss vcmpngtss vcmpfalse_oqss vcmpfalsess vcmpneq_oqss vcmpge_osss vcmpgess vcmpgt_osss vcmpgtss vcmptrue_uqss vcmptruess vcmplt_oqss vcmple_oqss vcmpunord_sss vcmpneq_usss vcmpnlt_uqss vcmpnle_uqss vcmpord_sss vcmpeq_usss vcmpnge_uqss vcmpngt_uqss vcmpfalse_osss vcmpneq_osss vcmpge_oqss vcmpgt_oqss vcmptrue_usss vcmpss vcomisd vcomiss vcvtdq2pd vcvtdq2ps vcvtpd2dq vcvtpd2ps vcvtps2dq vcvtps2pd vcvtsd2si vcvtsd2ss vcvtsi2sd vcvtsi2ss vcvtss2sd vcvtss2si vcvttpd2dq vcvttps2dq vcvttsd2si vcvttss2si vdivpd vdivps vdivsd vdivss vdppd vdpps vextractf128 vextractps vhaddpd vhaddps vhsubpd vhsubps vinsertf128 vinsertps vlddqu vldqqu vldmxcsr vmaskmovdqu vmaskmovps vmaskmovpd vmaxpd vmaxps vmaxsd vmaxss vminpd vminps vminsd vminss vmovapd vmovaps vmovd vmovq vmovddup vmovdqa vmovqqa vmovdqu vmovqqu vmovhlps vmovhpd vmovhps vmovlhps vmovlpd vmovlps vmovmskpd vmovmskps vmovntdq vmovntqq vmovntdqa vmovntpd vmovntps vmovsd vmovshdup vmovsldup vmovss vmovupd vmovups vmpsadbw vmulpd vmulps vmulsd vmulss vorpd vorps vpabsb vpabsw vpabsd vpacksswb vpackssdw vpackuswb vpackusdw vpaddb vpaddw vpaddd vpaddq vpaddsb vpaddsw vpaddusb vpaddusw vpalignr vpand vpandn vpavgb vpavgw vpblendvb vpblendw vpcmpestri vpcmpestrm vpcmpistri vpcmpistrm vpcmpeqb vpcmpeqw vpcmpeqd vpcmpeqq vpcmpgtb vpcmpgtw vpcmpgtd vpcmpgtq vpermilpd vpermilps vperm2f128 vpextrb vpextrw vpextrd vpextrq vphaddw vphaddd vphaddsw vphminposuw vphsubw vphsubd vphsubsw vpinsrb vpinsrw vpinsrd vpinsrq vpmaddwd vpmaddubsw vpmaxsb vpmaxsw vpmaxsd vpmaxub vpmaxuw vpmaxud vpminsb vpminsw vpminsd vpminub vpminuw vpminud vpmovmskb vpmovsxbw vpmovsxbd vpmovsxbq vpmovsxwd vpmovsxwq vpmovsxdq vpmovzxbw vpmovzxbd vpmovzxbq vpmovzxwd vpmovzxwq vpmovzxdq vpmulhuw vpmulhrsw vpmulhw vpmullw vpmulld vpmuludq vpmuldq vpor vpsadbw vpshufb vpshufd vpshufhw vpshuflw vpsignb vpsignw vpsignd vpslldq vpsrldq vpsllw vpslld vpsllq vpsraw vpsrad vpsrlw vpsrld vpsrlq vptest vpsubb vpsubw vpsubd vpsubq vpsubsb vpsubsw vpsubusb vpsubusw vpunpckhbw vpunpckhwd vpunpckhdq vpunpckhqdq vpunpcklbw vpunpcklwd vpunpckldq vpunpcklqdq vpxor vrcpps vrcpss vrsqrtps vrsqrtss vroundpd vroundps vroundsd vroundss vshufpd vshufps vsqrtpd vsqrtps vsqrtsd vsqrtss vstmxcsr vsubpd vsubps vsubsd vsubss vtestps vtestpd vucomisd vucomiss vunpckhpd vunpckhps vunpcklpd vunpcklps vxorpd vxorps vzeroall vzeroupper pclmullqlqdq pclmulhqlqdq pclmullqhqdq pclmulhqhqdq pclmulqdq vpclmullqlqdq vpclmulhqlqdq vpclmullqhqdq vpclmulhqhqdq vpclmulqdq vfmadd132ps vfmadd132pd vfmadd312ps vfmadd312pd vfmadd213ps vfmadd213pd vfmadd123ps vfmadd123pd vfmadd231ps vfmadd231pd vfmadd321ps vfmadd321pd vfmaddsub132ps vfmaddsub132pd vfmaddsub312ps vfmaddsub312pd vfmaddsub213ps vfmaddsub213pd vfmaddsub123ps vfmaddsub123pd vfmaddsub231ps vfmaddsub231pd vfmaddsub321ps vfmaddsub321pd vfmsub132ps vfmsub132pd vfmsub312ps vfmsub312pd vfmsub213ps vfmsub213pd vfmsub123ps vfmsub123pd vfmsub231ps vfmsub231pd vfmsub321ps vfmsub321pd vfmsubadd132ps vfmsubadd132pd vfmsubadd312ps vfmsubadd312pd vfmsubadd213ps vfmsubadd213pd vfmsubadd123ps vfmsubadd123pd vfmsubadd231ps vfmsubadd231pd vfmsubadd321ps vfmsubadd321pd vfnmadd132ps vfnmadd132pd vfnmadd312ps vfnmadd312pd vfnmadd213ps vfnmadd213pd vfnmadd123ps vfnmadd123pd vfnmadd231ps vfnmadd231pd vfnmadd321ps vfnmadd321pd vfnmsub132ps vfnmsub132pd vfnmsub312ps vfnmsub312pd vfnmsub213ps vfnmsub213pd vfnmsub123ps vfnmsub123pd vfnmsub231ps vfnmsub231pd vfnmsub321ps vfnmsub321pd vfmadd132ss vfmadd132sd vfmadd312ss vfmadd312sd vfmadd213ss vfmadd213sd vfmadd123ss vfmadd123sd vfmadd231ss vfmadd231sd vfmadd321ss vfmadd321sd vfmsub132ss vfmsub132sd vfmsub312ss vfmsub312sd vfmsub213ss vfmsub213sd vfmsub123ss vfmsub123sd vfmsub231ss vfmsub231sd vfmsub321ss vfmsub321sd vfnmadd132ss vfnmadd132sd vfnmadd312ss vfnmadd312sd vfnmadd213ss vfnmadd213sd vfnmadd123ss vfnmadd123sd vfnmadd231ss vfnmadd231sd vfnmadd321ss vfnmadd321sd vfnmsub132ss vfnmsub132sd vfnmsub312ss vfnmsub312sd vfnmsub213ss vfnmsub213sd vfnmsub123ss vfnmsub123sd vfnmsub231ss vfnmsub231sd vfnmsub321ss vfnmsub321sd rdfsbase rdgsbase rdrand wrfsbase wrgsbase vcvtph2ps vcvtps2ph adcx adox rdseed clac stac xstore xcryptecb xcryptcbc xcryptctr xcryptcfb xcryptofb montmul xsha1 xsha256 llwpcb slwpcb lwpval lwpins vfmaddpd vfmaddps vfmaddsd vfmaddss vfmaddsubpd vfmaddsubps vfmsubaddpd vfmsubaddps vfmsubpd vfmsubps vfmsubsd vfmsubss vfnmaddpd vfnmaddps vfnmaddsd vfnmaddss vfnmsubpd vfnmsubps vfnmsubsd vfnmsubss vfrczpd vfrczps vfrczsd vfrczss vpcmov vpcomb vpcomd vpcomq vpcomub vpcomud vpcomuq vpcomuw vpcomw vphaddbd vphaddbq vphaddbw vphadddq vphaddubd vphaddubq vphaddubw vphaddudq vphadduwd vphadduwq vphaddwd vphaddwq vphsubbw vphsubdq vphsubwd vpmacsdd vpmacsdqh vpmacsdql vpmacssdd vpmacssdqh vpmacssdql vpmacsswd vpmacssww vpmacswd vpmacsww vpmadcsswd vpmadcswd vpperm vprotb vprotd vprotq vprotw vpshab vpshad vpshaq vpshaw vpshlb vpshld vpshlq vpshlw vbroadcasti128 vpblendd vpbroadcastb vpbroadcastw vpbroadcastd vpbroadcastq vpermd vpermpd vpermps vpermq vperm2i128 vextracti128 vinserti128 vpmaskmovd vpmaskmovq vpsllvd vpsllvq vpsravd vpsrlvd vpsrlvq vgatherdpd vgatherqpd vgatherdps vgatherqps vpgatherdd vpgatherqd vpgatherdq vpgatherqq xabort xbegin xend xtest andn bextr blci blcic blsi blsic blcfill blsfill blcmsk blsmsk blsr blcs bzhi mulx pdep pext rorx sarx shlx shrx tzcnt tzmsk t1mskc valignd valignq vblendmpd vblendmps vbroadcastf32x4 vbroadcastf64x4 vbroadcasti32x4 vbroadcasti64x4 vcompresspd vcompressps vcvtpd2udq vcvtps2udq vcvtsd2usi vcvtss2usi vcvttpd2udq vcvttps2udq vcvttsd2usi vcvttss2usi vcvtudq2pd vcvtudq2ps vcvtusi2sd vcvtusi2ss vexpandpd vexpandps vextractf32x4 vextractf64x4 vextracti32x4 vextracti64x4 vfixupimmpd vfixupimmps vfixupimmsd vfixupimmss vgetexppd vgetexpps vgetexpsd vgetexpss vgetmantpd vgetmantps vgetmantsd vgetmantss vinsertf32x4 vinsertf64x4 vinserti32x4 vinserti64x4 vmovdqa32 vmovdqa64 vmovdqu32 vmovdqu64 vpabsq vpandd vpandnd vpandnq vpandq vpblendmd vpblendmq vpcmpltd vpcmpled vpcmpneqd vpcmpnltd vpcmpnled vpcmpd vpcmpltq vpcmpleq vpcmpneqq vpcmpnltq vpcmpnleq vpcmpq vpcmpequd vpcmpltud vpcmpleud vpcmpnequd vpcmpnltud vpcmpnleud vpcmpud vpcmpequq vpcmpltuq vpcmpleuq vpcmpnequq vpcmpnltuq vpcmpnleuq vpcmpuq vpcompressd vpcompressq vpermi2d vpermi2pd vpermi2ps vpermi2q vpermt2d vpermt2pd vpermt2ps vpermt2q vpexpandd vpexpandq vpmaxsq vpmaxuq vpminsq vpminuq vpmovdb vpmovdw vpmovqb vpmovqd vpmovqw vpmovsdb vpmovsdw vpmovsqb vpmovsqd vpmovsqw vpmovusdb vpmovusdw vpmovusqb vpmovusqd vpmovusqw vpord vporq vprold vprolq vprolvd vprolvq vprord vprorq vprorvd vprorvq vpscatterdd vpscatterdq vpscatterqd vpscatterqq vpsraq vpsravq vpternlogd vpternlogq vptestmd vptestmq vptestnmd vptestnmq vpxord vpxorq vrcp14pd vrcp14ps vrcp14sd vrcp14ss vrndscalepd vrndscaleps vrndscalesd vrndscaless vrsqrt14pd vrsqrt14ps vrsqrt14sd vrsqrt14ss vscalefpd vscalefps vscalefsd vscalefss vscatterdpd vscatterdps vscatterqpd vscatterqps vshuff32x4 vshuff64x2 vshufi32x4 vshufi64x2 kandnw kandw kmovw knotw kortestw korw kshiftlw kshiftrw kunpckbw kxnorw kxorw vpbroadcastmb2q vpbroadcastmw2d vpconflictd vpconflictq vplzcntd vplzcntq vexp2pd vexp2ps vrcp28pd vrcp28ps vrcp28sd vrcp28ss vrsqrt28pd vrsqrt28ps vrsqrt28sd vrsqrt28ss vgatherpf0dpd vgatherpf0dps vgatherpf0qpd vgatherpf0qps vgatherpf1dpd vgatherpf1dps vgatherpf1qpd vgatherpf1qps vscatterpf0dpd vscatterpf0dps vscatterpf0qpd vscatterpf0qps vscatterpf1dpd vscatterpf1dps vscatterpf1qpd vscatterpf1qps prefetchwt1 bndmk bndcl bndcu bndcn bndmov bndldx bndstx sha1rnds4 sha1nexte sha1msg1 sha1msg2 sha256rnds2 sha256msg1 sha256msg2 hint_nop0 hint_nop1 hint_nop2 hint_nop3 hint_nop4 hint_nop5 hint_nop6 hint_nop7 hint_nop8 hint_nop9 hint_nop10 hint_nop11 hint_nop12 hint_nop13 hint_nop14 hint_nop15 hint_nop16 hint_nop17 hint_nop18 hint_nop19 hint_nop20 hint_nop21 hint_nop22 hint_nop23 hint_nop24 hint_nop25 hint_nop26 hint_nop27 hint_nop28 hint_nop29 hint_nop30 hint_nop31 hint_nop32 hint_nop33 hint_nop34 hint_nop35 hint_nop36 hint_nop37 hint_nop38 hint_nop39 hint_nop40 hint_nop41 hint_nop42 hint_nop43 hint_nop44 hint_nop45 hint_nop46 hint_nop47 hint_nop48 hint_nop49 hint_nop50 hint_nop51 hint_nop52 hint_nop53 hint_nop54 hint_nop55 hint_nop56 hint_nop57 hint_nop58 hint_nop59 hint_nop60 hint_nop61 hint_nop62 hint_nop63",built_in:"ip eip rip al ah bl bh cl ch dl dh sil dil bpl spl r8b r9b r10b r11b r12b r13b r14b r15b ax bx cx dx si di bp sp r8w r9w r10w r11w r12w r13w r14w r15w eax ebx ecx edx esi edi ebp esp eip r8d r9d r10d r11d r12d r13d r14d r15d rax rbx rcx rdx rsi rdi rbp rsp r8 r9 r10 r11 r12 r13 r14 r15 cs ds es fs gs ss st st0 st1 st2 st3 st4 st5 st6 st7 mm0 mm1 mm2 mm3 mm4 mm5 mm6 mm7 xmm0 xmm1 xmm2 xmm3 xmm4 xmm5 xmm6 xmm7 xmm8 xmm9 xmm10 xmm11 xmm12 xmm13 xmm14 xmm15 xmm16 xmm17 xmm18 xmm19 xmm20 xmm21 xmm22 xmm23 xmm24 xmm25 xmm26 xmm27 xmm28 xmm29 xmm30 xmm31 ymm0 ymm1 ymm2 ymm3 ymm4 ymm5 ymm6 ymm7 ymm8 ymm9 ymm10 ymm11 ymm12 ymm13 ymm14 ymm15 ymm16 ymm17 ymm18 ymm19 ymm20 ymm21 ymm22 ymm23 ymm24 ymm25 ymm26 ymm27 ymm28 ymm29 ymm30 ymm31 zmm0 zmm1 zmm2 zmm3 zmm4 zmm5 zmm6 zmm7 zmm8 zmm9 zmm10 zmm11 zmm12 zmm13 zmm14 zmm15 zmm16 zmm17 zmm18 zmm19 zmm20 zmm21 zmm22 zmm23 zmm24 zmm25 zmm26 zmm27 zmm28 zmm29 zmm30 zmm31 k0 k1 k2 k3 k4 k5 k6 k7 bnd0 bnd1 bnd2 bnd3 cr0 cr1 cr2 cr3 cr4 cr8 dr0 dr1 dr2 dr3 dr8 tr3 tr4 tr5 tr6 tr7 r0 r1 r2 r3 r4 r5 r6 r7 r0b r1b r2b r3b r4b r5b r6b r7b r0w r1w r2w r3w r4w r5w r6w r7w r0d r1d r2d r3d r4d r5d r6d r7d r0h r1h r2h r3h r0l r1l r2l r3l r4l r5l r6l r7l r8l r9l r10l r11l r12l r13l r14l r15l db dw dd dq dt ddq do dy dz resb resw resd resq rest resdq reso resy resz incbin equ times byte word dword qword nosplit rel abs seg wrt strict near far a32 ptr",meta:"%define %xdefine %+ %undef %defstr %deftok %assign %strcat %strlen %substr %rotate %elif %else %endif %if %ifmacro %ifctx %ifidn %ifidni %ifid %ifnum %ifstr %iftoken %ifempty %ifenv %error %warning %fatal %rep %endrep %include %push %pop %repl %pathsearch %depend %use %arg %stacksize %local %line %comment %endcomment .nolist __FILE__ __LINE__ __SECT__ __BITS__ __OUTPUT_FORMAT__ __DATE__ __TIME__ __DATE_NUM__ __TIME_NUM__ __UTC_DATE__ __UTC_TIME__ __UTC_DATE_NUM__ __UTC_TIME_NUM__ __PASS__ struc endstruc istruc at iend align alignb sectalign daz nodaz up down zero default option assume public bits use16 use32 use64 default section segment absolute extern global common cpu float __utf16__ __utf16le__ __utf16be__ __utf32__ __utf32le__ __utf32be__ __float8__ __float16__ __float32__ __float64__ __float80m__ __float80e__ __float128l__ __float128h__ __Infinity__ __QNaN__ __SNaN__ Inf NaN QNaN SNaN float8 float16 float32 float64 float80m float80e float128l float128h __FLOAT_DAZ__ __FLOAT_ROUND__ __FLOAT__"},contains:[s.COMMENT(";","$",{relevance:0}),{className:"number",variants:[{begin:"\\b(?:([0-9][0-9_]*)?\\.[0-9_]*(?:[eE][+-]?[0-9_]+)?|(0[Xx])?[0-9][0-9_]*\\.?[0-9_]*(?:[pP](?:[+-]?[0-9_]+)?)?)\\b",relevance:0},{begin:"\\$[0-9][0-9A-Fa-f]*",relevance:0},{begin:"\\b(?:[0-9A-Fa-f][0-9A-Fa-f_]*[Hh]|[0-9][0-9_]*[DdTt]?|[0-7][0-7_]*[QqOo]|[0-1][0-1_]*[BbYy])\\b"},{begin:"\\b(?:0[Xx][0-9A-Fa-f_]+|0[DdTt][0-9_]+|0[QqOo][0-7_]+|0[BbYy][0-1_]+)\\b"}]},s.QUOTE_STRING_MODE,{className:"string",variants:[{begin:"'",end:"[^\\\\]'"},{begin:"`",end:"[^\\\\]`"}],relevance:0},{className:"symbol",variants:[{begin:"^\\s*[A-Za-z._?][A-Za-z0-9_$#@~.?]*(:|\\s+label)"},{begin:"^\\s*%%[A-Za-z0-9_$#@~.?]*:"}],relevance:0},{className:"subst",begin:"%[0-9]+",relevance:0},{className:"subst",begin:"%!S+",relevance:0},{className:"meta",begin:/^\s*\.[\w_-]+/}]}}}());hljs.registerLanguage("kotlin",function(){"use strict";return function(e){var n={keyword:"abstract as val var vararg get set class object open private protected public noinline crossinline dynamic final enum if else do while for when throw try catch finally import package is in fun override companion reified inline lateinit init interface annotation data sealed internal infix operator out by constructor super tailrec where const inner suspend typealias external expect actual trait volatile transient native default",built_in:"Byte Short Char Int Long Boolean Float Double Void Unit Nothing",literal:"true false null"},a={className:"symbol",begin:e.UNDERSCORE_IDENT_RE+"@"},i={className:"subst",begin:"\\${",end:"}",contains:[e.C_NUMBER_MODE]},s={className:"variable",begin:"\\$"+e.UNDERSCORE_IDENT_RE},t={className:"string",variants:[{begin:'"""',end:'"""(?=[^"])',contains:[s,i]},{begin:"'",end:"'",illegal:/\n/,contains:[e.BACKSLASH_ESCAPE]},{begin:'"',end:'"',illegal:/\n/,contains:[e.BACKSLASH_ESCAPE,s,i]}]};i.contains.push(t);var r={className:"meta",begin:"@(?:file|property|field|get|set|receiver|param|setparam|delegate)\\s*:(?:\\s*"+e.UNDERSCORE_IDENT_RE+")?"},l={className:"meta",begin:"@"+e.UNDERSCORE_IDENT_RE,contains:[{begin:/\(/,end:/\)/,contains:[e.inherit(t,{className:"meta-string"})]}]},c=e.COMMENT("/\\*","\\*/",{contains:[e.C_BLOCK_COMMENT_MODE]}),o={variants:[{className:"type",begin:e.UNDERSCORE_IDENT_RE},{begin:/\(/,end:/\)/,contains:[]}]},d=o;return d.variants[1].contains=[o],o.variants[1].contains=[d],{name:"Kotlin",aliases:["kt"],keywords:n,contains:[e.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{className:"doctag",begin:"@[A-Za-z]+"}]}),e.C_LINE_COMMENT_MODE,c,{className:"keyword",begin:/\b(break|continue|return|this)\b/,starts:{contains:[{className:"symbol",begin:/@\w+/}]}},a,r,l,{className:"function",beginKeywords:"fun",end:"[(]|$",returnBegin:!0,excludeEnd:!0,keywords:n,illegal:/fun\s+(<.*>)?[^\s\(]+(\s+[^\s\(]+)\s*=/,relevance:5,contains:[{begin:e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,relevance:0,contains:[e.UNDERSCORE_TITLE_MODE]},{className:"type",begin://,keywords:"reified",relevance:0},{className:"params",begin:/\(/,end:/\)/,endsParent:!0,keywords:n,relevance:0,contains:[{begin:/:/,end:/[=,\/]/,endsWithParent:!0,contains:[o,e.C_LINE_COMMENT_MODE,c],relevance:0},e.C_LINE_COMMENT_MODE,c,r,l,t,e.C_NUMBER_MODE]},c]},{className:"class",beginKeywords:"class interface trait",end:/[:\{(]|$/,excludeEnd:!0,illegal:"extends implements",contains:[{beginKeywords:"public protected internal private constructor"},e.UNDERSCORE_TITLE_MODE,{className:"type",begin://,excludeBegin:!0,excludeEnd:!0,relevance:0},{className:"type",begin:/[,:]\s*/,end:/[<\(,]|$/,excludeBegin:!0,returnEnd:!0},r,l]},t,{className:"meta",begin:"^#!/usr/bin/env",end:"$",illegal:"\n"},{className:"number",begin:"\\b(0[bB]([01]+[01_]+[01]+|[01]+)|0[xX]([a-fA-F0-9]+[a-fA-F0-9_]+[a-fA-F0-9]+|[a-fA-F0-9]+)|(([\\d]+[\\d_]+[\\d]+|[\\d]+)(\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))?|\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))([eE][-+]?\\d+)?)[lLfF]?",relevance:0}]}}}());hljs.registerLanguage("armasm",function(){"use strict";return function(s){const e={variants:[s.COMMENT("^[ \\t]*(?=#)","$",{relevance:0,excludeBegin:!0}),s.COMMENT("[;@]","$",{relevance:0}),s.C_LINE_COMMENT_MODE,s.C_BLOCK_COMMENT_MODE]};return{name:"ARM Assembly",case_insensitive:!0,aliases:["arm"],keywords:{$pattern:"\\.?"+s.IDENT_RE,meta:".2byte .4byte .align .ascii .asciz .balign .byte .code .data .else .end .endif .endm .endr .equ .err .exitm .extern .global .hword .if .ifdef .ifndef .include .irp .long .macro .rept .req .section .set .skip .space .text .word .arm .thumb .code16 .code32 .force_thumb .thumb_func .ltorg ALIAS ALIGN ARM AREA ASSERT ATTR CN CODE CODE16 CODE32 COMMON CP DATA DCB DCD DCDU DCDO DCFD DCFDU DCI DCQ DCQU DCW DCWU DN ELIF ELSE END ENDFUNC ENDIF ENDP ENTRY EQU EXPORT EXPORTAS EXTERN FIELD FILL FUNCTION GBLA GBLL GBLS GET GLOBAL IF IMPORT INCBIN INCLUDE INFO KEEP LCLA LCLL LCLS LTORG MACRO MAP MEND MEXIT NOFP OPT PRESERVE8 PROC QN READONLY RELOC REQUIRE REQUIRE8 RLIST FN ROUT SETA SETL SETS SN SPACE SUBT THUMB THUMBX TTL WHILE WEND ",built_in:"r0 r1 r2 r3 r4 r5 r6 r7 r8 r9 r10 r11 r12 r13 r14 r15 pc lr sp ip sl sb fp a1 a2 a3 a4 v1 v2 v3 v4 v5 v6 v7 v8 f0 f1 f2 f3 f4 f5 f6 f7 p0 p1 p2 p3 p4 p5 p6 p7 p8 p9 p10 p11 p12 p13 p14 p15 c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 c10 c11 c12 c13 c14 c15 q0 q1 q2 q3 q4 q5 q6 q7 q8 q9 q10 q11 q12 q13 q14 q15 cpsr_c cpsr_x cpsr_s cpsr_f cpsr_cx cpsr_cxs cpsr_xs cpsr_xsf cpsr_sf cpsr_cxsf spsr_c spsr_x spsr_s spsr_f spsr_cx spsr_cxs spsr_xs spsr_xsf spsr_sf spsr_cxsf s0 s1 s2 s3 s4 s5 s6 s7 s8 s9 s10 s11 s12 s13 s14 s15 s16 s17 s18 s19 s20 s21 s22 s23 s24 s25 s26 s27 s28 s29 s30 s31 d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 d10 d11 d12 d13 d14 d15 d16 d17 d18 d19 d20 d21 d22 d23 d24 d25 d26 d27 d28 d29 d30 d31 {PC} {VAR} {TRUE} {FALSE} {OPT} {CONFIG} {ENDIAN} {CODESIZE} {CPU} {FPU} {ARCHITECTURE} {PCSTOREOFFSET} {ARMASM_VERSION} {INTER} {ROPI} {RWPI} {SWST} {NOSWST} . @"},contains:[{className:"keyword",begin:"\\b(adc|(qd?|sh?|u[qh]?)?add(8|16)?|usada?8|(q|sh?|u[qh]?)?(as|sa)x|and|adrl?|sbc|rs[bc]|asr|b[lx]?|blx|bxj|cbn?z|tb[bh]|bic|bfc|bfi|[su]bfx|bkpt|cdp2?|clz|clrex|cmp|cmn|cpsi[ed]|cps|setend|dbg|dmb|dsb|eor|isb|it[te]{0,3}|lsl|lsr|ror|rrx|ldm(([id][ab])|f[ds])?|ldr((s|ex)?[bhd])?|movt?|mvn|mra|mar|mul|[us]mull|smul[bwt][bt]|smu[as]d|smmul|smmla|mla|umlaal|smlal?([wbt][bt]|d)|mls|smlsl?[ds]|smc|svc|sev|mia([bt]{2}|ph)?|mrr?c2?|mcrr2?|mrs|msr|orr|orn|pkh(tb|bt)|rbit|rev(16|sh)?|sel|[su]sat(16)?|nop|pop|push|rfe([id][ab])?|stm([id][ab])?|str(ex)?[bhd]?|(qd?)?sub|(sh?|q|u[qh]?)?sub(8|16)|[su]xt(a?h|a?b(16)?)|srs([id][ab])?|swpb?|swi|smi|tst|teq|wfe|wfi|yield)(eq|ne|cs|cc|mi|pl|vs|vc|hi|ls|ge|lt|gt|le|al|hs|lo)?[sptrx]?(?=\\s)"},e,s.QUOTE_STRING_MODE,{className:"string",begin:"'",end:"[^\\\\]'",relevance:0},{className:"title",begin:"\\|",end:"\\|",illegal:"\\n",relevance:0},{className:"number",variants:[{begin:"[#$=]?0x[0-9a-f]+"},{begin:"[#$=]?0b[01]+"},{begin:"[#$=]\\d+"},{begin:"\\b\\d+"}],relevance:0},{className:"symbol",variants:[{begin:"^[ \\t]*[a-z_\\.\\$][a-z0-9_\\.\\$]+:"},{begin:"^[a-z_\\.\\$][a-z0-9_\\.\\$]+"},{begin:"[=#]\\w+"}],relevance:0}]}}}());hljs.registerLanguage("go",function(){"use strict";return function(e){var n={keyword:"break default func interface select case map struct chan else goto package switch const fallthrough if range type continue for import return var go defer bool byte complex64 complex128 float32 float64 int8 int16 int32 int64 string uint8 uint16 uint32 uint64 int uint uintptr rune",literal:"true false iota nil",built_in:"append cap close complex copy imag len make new panic print println real recover delete"};return{name:"Go",aliases:["golang"],keywords:n,illegal:">>|\.\.\.) /},i={className:"subst",begin:/\{/,end:/\}/,keywords:n,illegal:/#/},s={begin:/\{\{/,relevance:0},r={className:"string",contains:[e.BACKSLASH_ESCAPE],variants:[{begin:/(u|b)?r?'''/,end:/'''/,contains:[e.BACKSLASH_ESCAPE,a],relevance:10},{begin:/(u|b)?r?"""/,end:/"""/,contains:[e.BACKSLASH_ESCAPE,a],relevance:10},{begin:/(fr|rf|f)'''/,end:/'''/,contains:[e.BACKSLASH_ESCAPE,a,s,i]},{begin:/(fr|rf|f)"""/,end:/"""/,contains:[e.BACKSLASH_ESCAPE,a,s,i]},{begin:/(u|r|ur)'/,end:/'/,relevance:10},{begin:/(u|r|ur)"/,end:/"/,relevance:10},{begin:/(b|br)'/,end:/'/},{begin:/(b|br)"/,end:/"/},{begin:/(fr|rf|f)'/,end:/'/,contains:[e.BACKSLASH_ESCAPE,s,i]},{begin:/(fr|rf|f)"/,end:/"/,contains:[e.BACKSLASH_ESCAPE,s,i]},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},l={className:"number",relevance:0,variants:[{begin:e.BINARY_NUMBER_RE+"[lLjJ]?"},{begin:"\\b(0o[0-7]+)[lLjJ]?"},{begin:e.C_NUMBER_RE+"[lLjJ]?"}]},t={className:"params",variants:[{begin:/\(\s*\)/,skip:!0,className:null},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,contains:["self",a,l,r,e.HASH_COMMENT_MODE]}]};return i.contains=[r,l,a],{name:"Python",aliases:["py","gyp","ipython"],keywords:n,illegal:/(<\/|->|\?)|=>/,contains:[a,l,{beginKeywords:"if",relevance:0},r,e.HASH_COMMENT_MODE,{variants:[{className:"function",beginKeywords:"def"},{className:"class",beginKeywords:"class"}],end:/:/,illegal:/[${=;\n,]/,contains:[e.UNDERSCORE_TITLE_MODE,t,{begin:/->/,endsWithParent:!0,keywords:"None"}]},{className:"meta",begin:/^[\t ]*@/,end:/$/},{begin:/\b(print|exec)\(/}]}}}());hljs.registerLanguage("shell",function(){"use strict";return function(s){return{name:"Shell Session",aliases:["console"],contains:[{className:"meta",begin:"^\\s{0,3}[/\\w\\d\\[\\]()@-]*[>%$#]",starts:{end:"$",subLanguage:"bash"}}]}}}());hljs.registerLanguage("scala",function(){"use strict";return function(e){var n={className:"subst",variants:[{begin:"\\$[A-Za-z0-9_]+"},{begin:"\\${",end:"}"}]},a={className:"string",variants:[{begin:'"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE]},{begin:'"""',end:'"""',relevance:10},{begin:'[a-z]+"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE,n]},{className:"string",begin:'[a-z]+"""',end:'"""',contains:[n],relevance:10}]},s={className:"type",begin:"\\b[A-Z][A-Za-z0-9_]*",relevance:0},t={className:"title",begin:/[^0-9\n\t "'(),.`{}\[\]:;][^\n\t "'(),.`{}\[\]:;]+|[^0-9\n\t "'(),.`{}\[\]:;=]/,relevance:0},i={className:"class",beginKeywords:"class object trait type",end:/[:={\[\n;]/,excludeEnd:!0,contains:[{beginKeywords:"extends with",relevance:10},{begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0,relevance:0,contains:[s]},{className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,relevance:0,contains:[s]},t]},l={className:"function",beginKeywords:"def",end:/[:={\[(\n;]/,excludeEnd:!0,contains:[t]};return{name:"Scala",keywords:{literal:"true false null",keyword:"type yield lazy override def with val var sealed abstract private trait object if forSome for while throw finally protected extends import final return else break new catch super class case package default try this match continue throws implicit"},contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,{className:"symbol",begin:"'\\w[\\w\\d_]*(?!')"},s,l,i,e.C_NUMBER_MODE,{className:"meta",begin:"@[A-Za-z]+"}]}}}());hljs.registerLanguage("julia",function(){"use strict";return function(e){var r="[A-Za-z_\\u00A1-\\uFFFF][A-Za-z_0-9\\u00A1-\\uFFFF]*",t={$pattern:r,keyword:"in isa where baremodule begin break catch ccall const continue do else elseif end export false finally for function global if import importall let local macro module quote return true try using while type immutable abstract bitstype typealias ",literal:"true false ARGS C_NULL DevNull ENDIAN_BOM ENV I Inf Inf16 Inf32 Inf64 InsertionSort JULIA_HOME LOAD_PATH MergeSort NaN NaN16 NaN32 NaN64 PROGRAM_FILE QuickSort RoundDown RoundFromZero RoundNearest RoundNearestTiesAway RoundNearestTiesUp RoundToZero RoundUp STDERR STDIN STDOUT VERSION catalan e|0 eu|0 eulergamma golden im nothing pi γ π φ ",built_in:"ANY AbstractArray AbstractChannel AbstractFloat AbstractMatrix AbstractRNG AbstractSerializer AbstractSet AbstractSparseArray AbstractSparseMatrix AbstractSparseVector AbstractString AbstractUnitRange AbstractVecOrMat AbstractVector Any ArgumentError Array AssertionError Associative Base64DecodePipe Base64EncodePipe Bidiagonal BigFloat BigInt BitArray BitMatrix BitVector Bool BoundsError BufferStream CachingPool CapturedException CartesianIndex CartesianRange Cchar Cdouble Cfloat Channel Char Cint Cintmax_t Clong Clonglong ClusterManager Cmd CodeInfo Colon Complex Complex128 Complex32 Complex64 CompositeException Condition ConjArray ConjMatrix ConjVector Cptrdiff_t Cshort Csize_t Cssize_t Cstring Cuchar Cuint Cuintmax_t Culong Culonglong Cushort Cwchar_t Cwstring DataType Date DateFormat DateTime DenseArray DenseMatrix DenseVecOrMat DenseVector Diagonal Dict DimensionMismatch Dims DirectIndexString Display DivideError DomainError EOFError EachLine Enum Enumerate ErrorException Exception ExponentialBackOff Expr Factorization FileMonitor Float16 Float32 Float64 Function Future GlobalRef GotoNode HTML Hermitian IO IOBuffer IOContext IOStream IPAddr IPv4 IPv6 IndexCartesian IndexLinear IndexStyle InexactError InitError Int Int128 Int16 Int32 Int64 Int8 IntSet Integer InterruptException InvalidStateException Irrational KeyError LabelNode LinSpace LineNumberNode LoadError LowerTriangular MIME Matrix MersenneTwister Method MethodError MethodTable Module NTuple NewvarNode NullException Nullable Number ObjectIdDict OrdinalRange OutOfMemoryError OverflowError Pair ParseError PartialQuickSort PermutedDimsArray Pipe PollingFileWatcher ProcessExitedException Ptr QuoteNode RandomDevice Range RangeIndex Rational RawFD ReadOnlyMemoryError Real ReentrantLock Ref Regex RegexMatch RemoteChannel RemoteException RevString RoundingMode RowVector SSAValue SegmentationFault SerializationState Set SharedArray SharedMatrix SharedVector Signed SimpleVector Slot SlotNumber SparseMatrixCSC SparseVector StackFrame StackOverflowError StackTrace StepRange StepRangeLen StridedArray StridedMatrix StridedVecOrMat StridedVector String SubArray SubString SymTridiagonal Symbol Symmetric SystemError TCPSocket Task Text TextDisplay Timer Tridiagonal Tuple Type TypeError TypeMapEntry TypeMapLevel TypeName TypeVar TypedSlot UDPSocket UInt UInt128 UInt16 UInt32 UInt64 UInt8 UndefRefError UndefVarError UnicodeError UniformScaling Union UnionAll UnitRange Unsigned UpperTriangular Val Vararg VecElement VecOrMat Vector VersionNumber Void WeakKeyDict WeakRef WorkerConfig WorkerPool "},a={keywords:t,illegal:/<\//},n={className:"subst",begin:/\$\(/,end:/\)/,keywords:t},o={className:"variable",begin:"\\$"+r},i={className:"string",contains:[e.BACKSLASH_ESCAPE,n,o],variants:[{begin:/\w*"""/,end:/"""\w*/,relevance:10},{begin:/\w*"/,end:/"\w*/}]},l={className:"string",contains:[e.BACKSLASH_ESCAPE,n,o],begin:"`",end:"`"},s={className:"meta",begin:"@"+r};return a.name="Julia",a.contains=[{className:"number",begin:/(\b0x[\d_]*(\.[\d_]*)?|0x\.\d[\d_]*)p[-+]?\d+|\b0[box][a-fA-F0-9][a-fA-F0-9_]*|(\b\d[\d_]*(\.[\d_]*)?|\.\d[\d_]*)([eEfF][-+]?\d+)?/,relevance:0},{className:"string",begin:/'(.|\\[xXuU][a-zA-Z0-9]+)'/},i,l,s,{className:"comment",variants:[{begin:"#=",end:"=#",relevance:10},{begin:"#",end:"$"}]},e.HASH_COMMENT_MODE,{className:"keyword",begin:"\\b(((abstract|primitive)\\s+)type|(mutable\\s+)?struct)\\b"},{begin:/<:/}],n.contains=a.contains,a}}());hljs.registerLanguage("php-template",function(){"use strict";return function(n){return{name:"PHP template",subLanguage:"xml",contains:[{begin:/<\?(php|=)?/,end:/\?>/,subLanguage:"php",contains:[{begin:"/\\*",end:"\\*/",skip:!0},{begin:'b"',end:'"',skip:!0},{begin:"b'",end:"'",skip:!0},n.inherit(n.APOS_STRING_MODE,{illegal:null,className:null,contains:null,skip:!0}),n.inherit(n.QUOTE_STRING_MODE,{illegal:null,className:null,contains:null,skip:!0})]}]}}}());hljs.registerLanguage("scss",function(){"use strict";return function(e){var t={className:"variable",begin:"(\\$[a-zA-Z-][a-zA-Z0-9_-]*)\\b"},i={className:"number",begin:"#[0-9A-Fa-f]+"};return e.CSS_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,e.C_BLOCK_COMMENT_MODE,{name:"SCSS",case_insensitive:!0,illegal:"[=/|']",contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{className:"selector-id",begin:"\\#[A-Za-z0-9_-]+",relevance:0},{className:"selector-class",begin:"\\.[A-Za-z0-9_-]+",relevance:0},{className:"selector-attr",begin:"\\[",end:"\\]",illegal:"$"},{className:"selector-tag",begin:"\\b(a|abbr|acronym|address|area|article|aside|audio|b|base|big|blockquote|body|br|button|canvas|caption|cite|code|col|colgroup|command|datalist|dd|del|details|dfn|div|dl|dt|em|embed|fieldset|figcaption|figure|footer|form|frame|frameset|(h[1-6])|head|header|hgroup|hr|html|i|iframe|img|input|ins|kbd|keygen|label|legend|li|link|map|mark|meta|meter|nav|noframes|noscript|object|ol|optgroup|option|output|p|param|pre|progress|q|rp|rt|ruby|samp|script|section|select|small|span|strike|strong|style|sub|sup|table|tbody|td|textarea|tfoot|th|thead|time|title|tr|tt|ul|var|video)\\b",relevance:0},{className:"selector-pseudo",begin:":(visited|valid|root|right|required|read-write|read-only|out-range|optional|only-of-type|only-child|nth-of-type|nth-last-of-type|nth-last-child|nth-child|not|link|left|last-of-type|last-child|lang|invalid|indeterminate|in-range|hover|focus|first-of-type|first-line|first-letter|first-child|first|enabled|empty|disabled|default|checked|before|after|active)"},{className:"selector-pseudo",begin:"::(after|before|choices|first-letter|first-line|repeat-index|repeat-item|selection|value)"},t,{className:"attribute",begin:"\\b(src|z-index|word-wrap|word-spacing|word-break|width|widows|white-space|visibility|vertical-align|unicode-bidi|transition-timing-function|transition-property|transition-duration|transition-delay|transition|transform-style|transform-origin|transform|top|text-underline-position|text-transform|text-shadow|text-rendering|text-overflow|text-indent|text-decoration-style|text-decoration-line|text-decoration-color|text-decoration|text-align-last|text-align|tab-size|table-layout|right|resize|quotes|position|pointer-events|perspective-origin|perspective|page-break-inside|page-break-before|page-break-after|padding-top|padding-right|padding-left|padding-bottom|padding|overflow-y|overflow-x|overflow-wrap|overflow|outline-width|outline-style|outline-offset|outline-color|outline|orphans|order|opacity|object-position|object-fit|normal|none|nav-up|nav-right|nav-left|nav-index|nav-down|min-width|min-height|max-width|max-height|mask|marks|margin-top|margin-right|margin-left|margin-bottom|margin|list-style-type|list-style-position|list-style-image|list-style|line-height|letter-spacing|left|justify-content|initial|inherit|ime-mode|image-orientation|image-resolution|image-rendering|icon|hyphens|height|font-weight|font-variant-ligatures|font-variant|font-style|font-stretch|font-size-adjust|font-size|font-language-override|font-kerning|font-feature-settings|font-family|font|float|flex-wrap|flex-shrink|flex-grow|flex-flow|flex-direction|flex-basis|flex|filter|empty-cells|display|direction|cursor|counter-reset|counter-increment|content|column-width|column-span|column-rule-width|column-rule-style|column-rule-color|column-rule|column-gap|column-fill|column-count|columns|color|clip-path|clip|clear|caption-side|break-inside|break-before|break-after|box-sizing|box-shadow|box-decoration-break|bottom|border-width|border-top-width|border-top-style|border-top-right-radius|border-top-left-radius|border-top-color|border-top|border-style|border-spacing|border-right-width|border-right-style|border-right-color|border-right|border-radius|border-left-width|border-left-style|border-left-color|border-left|border-image-width|border-image-source|border-image-slice|border-image-repeat|border-image-outset|border-image|border-color|border-collapse|border-bottom-width|border-bottom-style|border-bottom-right-radius|border-bottom-left-radius|border-bottom-color|border-bottom|border|background-size|background-repeat|background-position|background-origin|background-image|background-color|background-clip|background-attachment|background-blend-mode|background|backface-visibility|auto|animation-timing-function|animation-play-state|animation-name|animation-iteration-count|animation-fill-mode|animation-duration|animation-direction|animation-delay|animation|align-self|align-items|align-content)\\b",illegal:"[^\\s]"},{begin:"\\b(whitespace|wait|w-resize|visible|vertical-text|vertical-ideographic|uppercase|upper-roman|upper-alpha|underline|transparent|top|thin|thick|text|text-top|text-bottom|tb-rl|table-header-group|table-footer-group|sw-resize|super|strict|static|square|solid|small-caps|separate|se-resize|scroll|s-resize|rtl|row-resize|ridge|right|repeat|repeat-y|repeat-x|relative|progress|pointer|overline|outside|outset|oblique|nowrap|not-allowed|normal|none|nw-resize|no-repeat|no-drop|newspaper|ne-resize|n-resize|move|middle|medium|ltr|lr-tb|lowercase|lower-roman|lower-alpha|loose|list-item|line|line-through|line-edge|lighter|left|keep-all|justify|italic|inter-word|inter-ideograph|inside|inset|inline|inline-block|inherit|inactive|ideograph-space|ideograph-parenthesis|ideograph-numeric|ideograph-alpha|horizontal|hidden|help|hand|groove|fixed|ellipsis|e-resize|double|dotted|distribute|distribute-space|distribute-letter|distribute-all-lines|disc|disabled|default|decimal|dashed|crosshair|collapse|col-resize|circle|char|center|capitalize|break-word|break-all|bottom|both|bolder|bold|block|bidi-override|below|baseline|auto|always|all-scroll|absolute|table|table-cell)\\b"},{begin:":",end:";",contains:[t,i,e.CSS_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,{className:"meta",begin:"!important"}]},{begin:"@(page|font-face)",lexemes:"@[a-z-]+",keywords:"@page @font-face"},{begin:"@",end:"[{;]",returnBegin:!0,keywords:"and or not only",contains:[{begin:"@[a-z-]+",className:"keyword"},t,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,i,e.CSS_NUMBER_MODE]}]}}}());hljs.registerLanguage("r",function(){"use strict";return function(e){var n="([a-zA-Z]|\\.[a-zA-Z.])[a-zA-Z0-9._]*";return{name:"R",contains:[e.HASH_COMMENT_MODE,{begin:n,keywords:{$pattern:n,keyword:"function if in break next repeat else for return switch while try tryCatch stop warning require library attach detach source setMethod setGeneric setGroupGeneric setClass ...",literal:"NULL NA TRUE FALSE T F Inf NaN NA_integer_|10 NA_real_|10 NA_character_|10 NA_complex_|10"},relevance:0},{className:"number",begin:"0[xX][0-9a-fA-F]+[Li]?\\b",relevance:0},{className:"number",begin:"\\d+(?:[eE][+\\-]?\\d*)?L\\b",relevance:0},{className:"number",begin:"\\d+\\.(?!\\d)(?:i\\b)?",relevance:0},{className:"number",begin:"\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d*)?i?\\b",relevance:0},{className:"number",begin:"\\.\\d+(?:[eE][+\\-]?\\d*)?i?\\b",relevance:0},{begin:"`",end:"`",relevance:0},{className:"string",contains:[e.BACKSLASH_ESCAPE],variants:[{begin:'"',end:'"'},{begin:"'",end:"'"}]}]}}}());hljs.registerLanguage("sql",function(){"use strict";return function(e){var t=e.COMMENT("--","$");return{name:"SQL",case_insensitive:!0,illegal:/[<>{}*]/,contains:[{beginKeywords:"begin end start commit rollback savepoint lock alter create drop rename call delete do handler insert load replace select truncate update set show pragma grant merge describe use explain help declare prepare execute deallocate release unlock purge reset change stop analyze cache flush optimize repair kill install uninstall checksum restore check backup revoke comment values with",end:/;/,endsWithParent:!0,keywords:{$pattern:/[\w\.]+/,keyword:"as abort abs absolute acc acce accep accept access accessed accessible account acos action activate add addtime admin administer advanced advise aes_decrypt aes_encrypt after agent aggregate ali alia alias all allocate allow alter always analyze ancillary and anti any anydata anydataset anyschema anytype apply archive archived archivelog are as asc ascii asin assembly assertion associate asynchronous at atan atn2 attr attri attrib attribu attribut attribute attributes audit authenticated authentication authid authors auto autoallocate autodblink autoextend automatic availability avg backup badfile basicfile before begin beginning benchmark between bfile bfile_base big bigfile bin binary_double binary_float binlog bit_and bit_count bit_length bit_or bit_xor bitmap blob_base block blocksize body both bound bucket buffer_cache buffer_pool build bulk by byte byteordermark bytes cache caching call calling cancel capacity cascade cascaded case cast catalog category ceil ceiling chain change changed char_base char_length character_length characters characterset charindex charset charsetform charsetid check checksum checksum_agg child choose chr chunk class cleanup clear client clob clob_base clone close cluster_id cluster_probability cluster_set clustering coalesce coercibility col collate collation collect colu colum column column_value columns columns_updated comment commit compact compatibility compiled complete composite_limit compound compress compute concat concat_ws concurrent confirm conn connec connect connect_by_iscycle connect_by_isleaf connect_by_root connect_time connection consider consistent constant constraint constraints constructor container content contents context contributors controlfile conv convert convert_tz corr corr_k corr_s corresponding corruption cos cost count count_big counted covar_pop covar_samp cpu_per_call cpu_per_session crc32 create creation critical cross cube cume_dist curdate current current_date current_time current_timestamp current_user cursor curtime customdatum cycle data database databases datafile datafiles datalength date_add date_cache date_format date_sub dateadd datediff datefromparts datename datepart datetime2fromparts day day_to_second dayname dayofmonth dayofweek dayofyear days db_role_change dbtimezone ddl deallocate declare decode decompose decrement decrypt deduplicate def defa defau defaul default defaults deferred defi defin define degrees delayed delegate delete delete_all delimited demand dense_rank depth dequeue des_decrypt des_encrypt des_key_file desc descr descri describ describe descriptor deterministic diagnostics difference dimension direct_load directory disable disable_all disallow disassociate discardfile disconnect diskgroup distinct distinctrow distribute distributed div do document domain dotnet double downgrade drop dumpfile duplicate duration each edition editionable editions element ellipsis else elsif elt empty enable enable_all enclosed encode encoding encrypt end end-exec endian enforced engine engines enqueue enterprise entityescaping eomonth error errors escaped evalname evaluate event eventdata events except exception exceptions exchange exclude excluding execu execut execute exempt exists exit exp expire explain explode export export_set extended extent external external_1 external_2 externally extract failed failed_login_attempts failover failure far fast feature_set feature_value fetch field fields file file_name_convert filesystem_like_logging final finish first first_value fixed flash_cache flashback floor flush following follows for forall force foreign form forma format found found_rows freelist freelists freepools fresh from from_base64 from_days ftp full function general generated get get_format get_lock getdate getutcdate global global_name globally go goto grant grants greatest group group_concat group_id grouping grouping_id groups gtid_subtract guarantee guard handler hash hashkeys having hea head headi headin heading heap help hex hierarchy high high_priority hosts hour hours http id ident_current ident_incr ident_seed identified identity idle_time if ifnull ignore iif ilike ilm immediate import in include including increment index indexes indexing indextype indicator indices inet6_aton inet6_ntoa inet_aton inet_ntoa infile initial initialized initially initrans inmemory inner innodb input insert install instance instantiable instr interface interleaved intersect into invalidate invisible is is_free_lock is_ipv4 is_ipv4_compat is_not is_not_null is_used_lock isdate isnull isolation iterate java join json json_exists keep keep_duplicates key keys kill language large last last_day last_insert_id last_value lateral lax lcase lead leading least leaves left len lenght length less level levels library like like2 like4 likec limit lines link list listagg little ln load load_file lob lobs local localtime localtimestamp locate locator lock locked log log10 log2 logfile logfiles logging logical logical_reads_per_call logoff logon logs long loop low low_priority lower lpad lrtrim ltrim main make_set makedate maketime managed management manual map mapping mask master master_pos_wait match matched materialized max maxextents maximize maxinstances maxlen maxlogfiles maxloghistory maxlogmembers maxsize maxtrans md5 measures median medium member memcompress memory merge microsecond mid migration min minextents minimum mining minus minute minutes minvalue missing mod mode model modification modify module monitoring month months mount move movement multiset mutex name name_const names nan national native natural nav nchar nclob nested never new newline next nextval no no_write_to_binlog noarchivelog noaudit nobadfile nocheck nocompress nocopy nocycle nodelay nodiscardfile noentityescaping noguarantee nokeep nologfile nomapping nomaxvalue nominimize nominvalue nomonitoring none noneditionable nonschema noorder nopr nopro noprom nopromp noprompt norely noresetlogs noreverse normal norowdependencies noschemacheck noswitch not nothing notice notnull notrim novalidate now nowait nth_value nullif nulls num numb numbe nvarchar nvarchar2 object ocicoll ocidate ocidatetime ociduration ociinterval ociloblocator ocinumber ociref ocirefcursor ocirowid ocistring ocitype oct octet_length of off offline offset oid oidindex old on online only opaque open operations operator optimal optimize option optionally or oracle oracle_date oradata ord ordaudio orddicom orddoc order ordimage ordinality ordvideo organization orlany orlvary out outer outfile outline output over overflow overriding package pad parallel parallel_enable parameters parent parse partial partition partitions pascal passing password password_grace_time password_lock_time password_reuse_max password_reuse_time password_verify_function patch path patindex pctincrease pctthreshold pctused pctversion percent percent_rank percentile_cont percentile_disc performance period period_add period_diff permanent physical pi pipe pipelined pivot pluggable plugin policy position post_transaction pow power pragma prebuilt precedes preceding precision prediction prediction_cost prediction_details prediction_probability prediction_set prepare present preserve prior priority private private_sga privileges procedural procedure procedure_analyze processlist profiles project prompt protection public publishingservername purge quarter query quick quiesce quota quotename radians raise rand range rank raw read reads readsize rebuild record records recover recovery recursive recycle redo reduced ref reference referenced references referencing refresh regexp_like register regr_avgx regr_avgy regr_count regr_intercept regr_r2 regr_slope regr_sxx regr_sxy reject rekey relational relative relaylog release release_lock relies_on relocate rely rem remainder rename repair repeat replace replicate replication required reset resetlogs resize resource respect restore restricted result result_cache resumable resume retention return returning returns reuse reverse revoke right rlike role roles rollback rolling rollup round row row_count rowdependencies rowid rownum rows rtrim rules safe salt sample save savepoint sb1 sb2 sb4 scan schema schemacheck scn scope scroll sdo_georaster sdo_topo_geometry search sec_to_time second seconds section securefile security seed segment select self semi sequence sequential serializable server servererror session session_user sessions_per_user set sets settings sha sha1 sha2 share shared shared_pool short show shrink shutdown si_averagecolor si_colorhistogram si_featurelist si_positionalcolor si_stillimage si_texture siblings sid sign sin size size_t sizes skip slave sleep smalldatetimefromparts smallfile snapshot some soname sort soundex source space sparse spfile split sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_small_result sql_variant_property sqlcode sqldata sqlerror sqlname sqlstate sqrt square standalone standby start starting startup statement static statistics stats_binomial_test stats_crosstab stats_ks_test stats_mode stats_mw_test stats_one_way_anova stats_t_test_ stats_t_test_indep stats_t_test_one stats_t_test_paired stats_wsr_test status std stddev stddev_pop stddev_samp stdev stop storage store stored str str_to_date straight_join strcmp strict string struct stuff style subdate subpartition subpartitions substitutable substr substring subtime subtring_index subtype success sum suspend switch switchoffset switchover sync synchronous synonym sys sys_xmlagg sysasm sysaux sysdate sysdatetimeoffset sysdba sysoper system system_user sysutcdatetime table tables tablespace tablesample tan tdo template temporary terminated tertiary_weights test than then thread through tier ties time time_format time_zone timediff timefromparts timeout timestamp timestampadd timestampdiff timezone_abbr timezone_minute timezone_region to to_base64 to_date to_days to_seconds todatetimeoffset trace tracking transaction transactional translate translation treat trigger trigger_nestlevel triggers trim truncate try_cast try_convert try_parse type ub1 ub2 ub4 ucase unarchived unbounded uncompress under undo unhex unicode uniform uninstall union unique unix_timestamp unknown unlimited unlock unnest unpivot unrecoverable unsafe unsigned until untrusted unusable unused update updated upgrade upped upper upsert url urowid usable usage use use_stored_outlines user user_data user_resources users using utc_date utc_timestamp uuid uuid_short validate validate_password_strength validation valist value values var var_samp varcharc vari varia variab variabl variable variables variance varp varraw varrawc varray verify version versions view virtual visible void wait wallet warning warnings week weekday weekofyear wellformed when whene whenev wheneve whenever where while whitespace window with within without work wrapped xdb xml xmlagg xmlattributes xmlcast xmlcolattval xmlelement xmlexists xmlforest xmlindex xmlnamespaces xmlpi xmlquery xmlroot xmlschema xmlserialize xmltable xmltype xor year year_to_month years yearweek",literal:"true false null unknown",built_in:"array bigint binary bit blob bool boolean char character date dec decimal float int int8 integer interval number numeric real record serial serial8 smallint text time timestamp tinyint varchar varchar2 varying void"},contains:[{className:"string",begin:"'",end:"'",contains:[{begin:"''"}]},{className:"string",begin:'"',end:'"',contains:[{begin:'""'}]},{className:"string",begin:"`",end:"`"},e.C_NUMBER_MODE,e.C_BLOCK_COMMENT_MODE,t,e.HASH_COMMENT_MODE]},e.C_BLOCK_COMMENT_MODE,t,e.HASH_COMMENT_MODE]}}}());hljs.registerLanguage("c",function(){"use strict";return function(e){var n=e.getLanguage("c-like").rawDefinition();return n.name="C",n.aliases=["c","h"],n}}());hljs.registerLanguage("json",function(){"use strict";return function(n){var e={literal:"true false null"},i=[n.C_LINE_COMMENT_MODE,n.C_BLOCK_COMMENT_MODE],t=[n.QUOTE_STRING_MODE,n.C_NUMBER_MODE],a={end:",",endsWithParent:!0,excludeEnd:!0,contains:t,keywords:e},l={begin:"{",end:"}",contains:[{className:"attr",begin:/"/,end:/"/,contains:[n.BACKSLASH_ESCAPE],illegal:"\\n"},n.inherit(a,{begin:/:/})].concat(i),illegal:"\\S"},s={begin:"\\[",end:"\\]",contains:[n.inherit(a)],illegal:"\\S"};return t.push(l,s),i.forEach((function(n){t.push(n)})),{name:"JSON",contains:t,keywords:e,illegal:"\\S"}}}());hljs.registerLanguage("python-repl",function(){"use strict";return function(n){return{aliases:["pycon"],contains:[{className:"meta",starts:{end:/ |$/,starts:{end:"$",subLanguage:"python"}},variants:[{begin:/^>>>(?=[ ]|$)/},{begin:/^\.\.\.(?=[ ]|$)/}]}]}}}());hljs.registerLanguage("markdown",function(){"use strict";return function(n){const e={begin:"<",end:">",subLanguage:"xml",relevance:0},a={begin:"\\[.+?\\][\\(\\[].*?[\\)\\]]",returnBegin:!0,contains:[{className:"string",begin:"\\[",end:"\\]",excludeBegin:!0,returnEnd:!0,relevance:0},{className:"link",begin:"\\]\\(",end:"\\)",excludeBegin:!0,excludeEnd:!0},{className:"symbol",begin:"\\]\\[",end:"\\]",excludeBegin:!0,excludeEnd:!0}],relevance:10},i={className:"strong",contains:[],variants:[{begin:/_{2}/,end:/_{2}/},{begin:/\*{2}/,end:/\*{2}/}]},s={className:"emphasis",contains:[],variants:[{begin:/\*(?!\*)/,end:/\*/},{begin:/_(?!_)/,end:/_/,relevance:0}]};i.contains.push(s),s.contains.push(i);var c=[e,a];return i.contains=i.contains.concat(c),s.contains=s.contains.concat(c),{name:"Markdown",aliases:["md","mkdown","mkd"],contains:[{className:"section",variants:[{begin:"^#{1,6}",end:"$",contains:c=c.concat(i,s)},{begin:"(?=^.+?\\n[=-]{2,}$)",contains:[{begin:"^[=-]*$"},{begin:"^",end:"\\n",contains:c}]}]},e,{className:"bullet",begin:"^[ \t]*([*+-]|(\\d+\\.))(?=\\s+)",end:"\\s+",excludeEnd:!0},i,s,{className:"quote",begin:"^>\\s+",contains:c,end:"$"},{className:"code",variants:[{begin:"(`{3,})(.|\\n)*?\\1`*[ ]*"},{begin:"(~{3,})(.|\\n)*?\\1~*[ ]*"},{begin:"```",end:"```+[ ]*$"},{begin:"~~~",end:"~~~+[ ]*$"},{begin:"`.+?`"},{begin:"(?=^( {4}|\\t))",contains:[{begin:"^( {4}|\\t)",end:"(\\n)$"}],relevance:0}]},{begin:"^[-\\*]{3,}",end:"$"},a,{begin:/^\[[^\n]+\]:/,returnBegin:!0,contains:[{className:"symbol",begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0},{className:"link",begin:/:\s*/,end:/$/,excludeBegin:!0}]}]}}}());hljs.registerLanguage("javascript",function(){"use strict";const e=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],n=["true","false","null","undefined","NaN","Infinity"],a=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"]);function s(e){return r("(?=",e,")")}function r(...e){return e.map(e=>(function(e){return e?"string"==typeof e?e:e.source:null})(e)).join("")}return function(t){var i="[A-Za-z$_][0-9A-Za-z$_]*",c={begin:/<[A-Za-z0-9\\._:-]+/,end:/\/[A-Za-z0-9\\._:-]+>|\/>/},o={$pattern:"[A-Za-z$_][0-9A-Za-z$_]*",keyword:e.join(" "),literal:n.join(" "),built_in:a.join(" ")},l={className:"number",variants:[{begin:"\\b(0[bB][01]+)n?"},{begin:"\\b(0[oO][0-7]+)n?"},{begin:t.C_NUMBER_RE+"n?"}],relevance:0},E={className:"subst",begin:"\\$\\{",end:"\\}",keywords:o,contains:[]},d={begin:"html`",end:"",starts:{end:"`",returnEnd:!1,contains:[t.BACKSLASH_ESCAPE,E],subLanguage:"xml"}},g={begin:"css`",end:"",starts:{end:"`",returnEnd:!1,contains:[t.BACKSLASH_ESCAPE,E],subLanguage:"css"}},u={className:"string",begin:"`",end:"`",contains:[t.BACKSLASH_ESCAPE,E]};E.contains=[t.APOS_STRING_MODE,t.QUOTE_STRING_MODE,d,g,u,l,t.REGEXP_MODE];var b=E.contains.concat([{begin:/\(/,end:/\)/,contains:["self"].concat(E.contains,[t.C_BLOCK_COMMENT_MODE,t.C_LINE_COMMENT_MODE])},t.C_BLOCK_COMMENT_MODE,t.C_LINE_COMMENT_MODE]),_={className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,contains:b};return{name:"JavaScript",aliases:["js","jsx","mjs","cjs"],keywords:o,contains:[t.SHEBANG({binary:"node",relevance:5}),{className:"meta",relevance:10,begin:/^\s*['"]use (strict|asm)['"]/},t.APOS_STRING_MODE,t.QUOTE_STRING_MODE,d,g,u,t.C_LINE_COMMENT_MODE,t.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{className:"doctag",begin:"@[A-Za-z]+",contains:[{className:"type",begin:"\\{",end:"\\}",relevance:0},{className:"variable",begin:i+"(?=\\s*(-)|$)",endsParent:!0,relevance:0},{begin:/(?=[^\n])\s/,relevance:0}]}]}),t.C_BLOCK_COMMENT_MODE,l,{begin:r(/[{,\n]\s*/,s(r(/(((\/\/.*)|(\/\*(.|\n)*\*\/))\s*)*/,i+"\\s*:"))),relevance:0,contains:[{className:"attr",begin:i+s("\\s*:"),relevance:0}]},{begin:"("+t.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*",keywords:"return throw case",contains:[t.C_LINE_COMMENT_MODE,t.C_BLOCK_COMMENT_MODE,t.REGEXP_MODE,{className:"function",begin:"(\\([^(]*(\\([^(]*(\\([^(]*\\))?\\))?\\)|"+t.UNDERSCORE_IDENT_RE+")\\s*=>",returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{begin:t.UNDERSCORE_IDENT_RE},{className:null,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:o,contains:b}]}]},{begin:/,/,relevance:0},{className:"",begin:/\s/,end:/\s*/,skip:!0},{variants:[{begin:"<>",end:""},{begin:c.begin,end:c.end}],subLanguage:"xml",contains:[{begin:c.begin,end:c.end,skip:!0,contains:["self"]}]}],relevance:0},{className:"function",beginKeywords:"function",end:/\{/,excludeEnd:!0,contains:[t.inherit(t.TITLE_MODE,{begin:i}),_],illegal:/\[|%/},{begin:/\$[(.]/},t.METHOD_GUARD,{className:"class",beginKeywords:"class",end:/[{;=]/,excludeEnd:!0,illegal:/[:"\[\]]/,contains:[{beginKeywords:"extends"},t.UNDERSCORE_TITLE_MODE]},{beginKeywords:"constructor",end:/\{/,excludeEnd:!0},{begin:"(get|set)\\s+(?="+i+"\\()",end:/{/,keywords:"get set",contains:[t.inherit(t.TITLE_MODE,{begin:i}),{begin:/\(\)/},_]}],illegal:/#(?!!)/}}}());hljs.registerLanguage("typescript",function(){"use strict";const e=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],n=["true","false","null","undefined","NaN","Infinity"],a=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"]);return function(r){var t={$pattern:"[A-Za-z$_][0-9A-Za-z$_]*",keyword:e.concat(["type","namespace","typedef","interface","public","private","protected","implements","declare","abstract","readonly"]).join(" "),literal:n.join(" "),built_in:a.concat(["any","void","number","boolean","string","object","never","enum"]).join(" ")},s={className:"meta",begin:"@[A-Za-z$_][0-9A-Za-z$_]*"},i={className:"number",variants:[{begin:"\\b(0[bB][01]+)n?"},{begin:"\\b(0[oO][0-7]+)n?"},{begin:r.C_NUMBER_RE+"n?"}],relevance:0},o={className:"subst",begin:"\\$\\{",end:"\\}",keywords:t,contains:[]},c={begin:"html`",end:"",starts:{end:"`",returnEnd:!1,contains:[r.BACKSLASH_ESCAPE,o],subLanguage:"xml"}},l={begin:"css`",end:"",starts:{end:"`",returnEnd:!1,contains:[r.BACKSLASH_ESCAPE,o],subLanguage:"css"}},E={className:"string",begin:"`",end:"`",contains:[r.BACKSLASH_ESCAPE,o]};o.contains=[r.APOS_STRING_MODE,r.QUOTE_STRING_MODE,c,l,E,i,r.REGEXP_MODE];var d={begin:"\\(",end:/\)/,keywords:t,contains:["self",r.QUOTE_STRING_MODE,r.APOS_STRING_MODE,r.NUMBER_MODE]},u={className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:t,contains:[r.C_LINE_COMMENT_MODE,r.C_BLOCK_COMMENT_MODE,s,d]};return{name:"TypeScript",aliases:["ts"],keywords:t,contains:[r.SHEBANG(),{className:"meta",begin:/^\s*['"]use strict['"]/},r.APOS_STRING_MODE,r.QUOTE_STRING_MODE,c,l,E,r.C_LINE_COMMENT_MODE,r.C_BLOCK_COMMENT_MODE,i,{begin:"("+r.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*",keywords:"return throw case",contains:[r.C_LINE_COMMENT_MODE,r.C_BLOCK_COMMENT_MODE,r.REGEXP_MODE,{className:"function",begin:"(\\([^(]*(\\([^(]*(\\([^(]*\\))?\\))?\\)|"+r.UNDERSCORE_IDENT_RE+")\\s*=>",returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{begin:r.UNDERSCORE_IDENT_RE},{className:null,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:t,contains:d.contains}]}]}],relevance:0},{className:"function",beginKeywords:"function",end:/[\{;]/,excludeEnd:!0,keywords:t,contains:["self",r.inherit(r.TITLE_MODE,{begin:"[A-Za-z$_][0-9A-Za-z$_]*"}),u],illegal:/%/,relevance:0},{beginKeywords:"constructor",end:/[\{;]/,excludeEnd:!0,contains:["self",u]},{begin:/module\./,keywords:{built_in:"module"},relevance:0},{beginKeywords:"module",end:/\{/,excludeEnd:!0},{beginKeywords:"interface",end:/\{/,excludeEnd:!0,keywords:"interface extends"},{begin:/\$[(.]/},{begin:"\\."+r.IDENT_RE,relevance:0},s,d]}}}());hljs.registerLanguage("plaintext",function(){"use strict";return function(t){return{name:"Plain text",aliases:["text","txt"],disableAutodetect:!0}}}());hljs.registerLanguage("less",function(){"use strict";return function(e){var n="([\\w-]+|@{[\\w-]+})",a=[],s=[],t=function(e){return{className:"string",begin:"~?"+e+".*?"+e}},r=function(e,n,a){return{className:e,begin:n,relevance:a}},i={begin:"\\(",end:"\\)",contains:s,relevance:0};s.push(e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,t("'"),t('"'),e.CSS_NUMBER_MODE,{begin:"(url|data-uri)\\(",starts:{className:"string",end:"[\\)\\n]",excludeEnd:!0}},r("number","#[0-9A-Fa-f]+\\b"),i,r("variable","@@?[\\w-]+",10),r("variable","@{[\\w-]+}"),r("built_in","~?`[^`]*?`"),{className:"attribute",begin:"[\\w-]+\\s*:",end:":",returnBegin:!0,excludeEnd:!0},{className:"meta",begin:"!important"});var c=s.concat({begin:"{",end:"}",contains:a}),l={beginKeywords:"when",endsWithParent:!0,contains:[{beginKeywords:"and not"}].concat(s)},o={begin:n+"\\s*:",returnBegin:!0,end:"[;}]",relevance:0,contains:[{className:"attribute",begin:n,end:":",excludeEnd:!0,starts:{endsWithParent:!0,illegal:"[<=$]",relevance:0,contains:s}}]},g={className:"keyword",begin:"@(import|media|charset|font-face|(-[a-z]+-)?keyframes|supports|document|namespace|page|viewport|host)\\b",starts:{end:"[;{}]",returnEnd:!0,contains:s,relevance:0}},d={className:"variable",variants:[{begin:"@[\\w-]+\\s*:",relevance:15},{begin:"@[\\w-]+"}],starts:{end:"[;}]",returnEnd:!0,contains:c}},b={variants:[{begin:"[\\.#:&\\[>]",end:"[;{}]"},{begin:n,end:"{"}],returnBegin:!0,returnEnd:!0,illegal:"[<='$\"]",relevance:0,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,l,r("keyword","all\\b"),r("variable","@{[\\w-]+}"),r("selector-tag",n+"%?",0),r("selector-id","#"+n),r("selector-class","\\."+n,0),r("selector-tag","&",0),{className:"selector-attr",begin:"\\[",end:"\\]"},{className:"selector-pseudo",begin:/:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/},{begin:"\\(",end:"\\)",contains:c},{begin:"!important"}]};return a.push(e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,g,d,o,b),{name:"Less",case_insensitive:!0,illegal:"[=>'/<($\"]",contains:a}}}());hljs.registerLanguage("lua",function(){"use strict";return function(e){var t={begin:"\\[=*\\[",end:"\\]=*\\]",contains:["self"]},a=[e.COMMENT("--(?!\\[=*\\[)","$"),e.COMMENT("--\\[=*\\[","\\]=*\\]",{contains:[t],relevance:10})];return{name:"Lua",keywords:{$pattern:e.UNDERSCORE_IDENT_RE,literal:"true false nil",keyword:"and break do else elseif end for goto if in local not or repeat return then until while",built_in:"_G _ENV _VERSION __index __newindex __mode __call __metatable __tostring __len __gc __add __sub __mul __div __mod __pow __concat __unm __eq __lt __le assert collectgarbage dofile error getfenv getmetatable ipairs load loadfile loadstring module next pairs pcall print rawequal rawget rawset require select setfenv setmetatable tonumber tostring type unpack xpcall arg self coroutine resume yield status wrap create running debug getupvalue debug sethook getmetatable gethook setmetatable setlocal traceback setfenv getinfo setupvalue getlocal getregistry getfenv io lines write close flush open output type read stderr stdin input stdout popen tmpfile math log max acos huge ldexp pi cos tanh pow deg tan cosh sinh random randomseed frexp ceil floor rad abs sqrt modf asin min mod fmod log10 atan2 exp sin atan os exit setlocale date getenv difftime remove time clock tmpname rename execute package preload loadlib loaded loaders cpath config path seeall string sub upper len gfind rep find match char dump gmatch reverse byte format gsub lower table setn insert getn foreachi maxn foreach concat sort remove"},contains:a.concat([{className:"function",beginKeywords:"function",end:"\\)",contains:[e.inherit(e.TITLE_MODE,{begin:"([_a-zA-Z]\\w*\\.)*([_a-zA-Z]\\w*:)?[_a-zA-Z]\\w*"}),{className:"params",begin:"\\(",endsWithParent:!0,contains:a}].concat(a)},e.C_NUMBER_MODE,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,{className:"string",begin:"\\[=*\\[",end:"\\]=*\\]",contains:[t],relevance:5}])}}}()); diff --git a/book/howtos/adding-a-new-component.html b/book/howtos/adding-a-new-component.html new file mode 100644 index 0000000000..eb9968e2a0 --- /dev/null +++ b/book/howtos/adding-a-new-component.html @@ -0,0 +1,474 @@ + + + + + + How to add a new component - Cross-platform Rust Components + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Adding a new component to Application Services

+

Each component in the Application Services repository has three parts (the Rust code, +the Kotlin wrapper, and the Swift wrapper) so there are quite a few moving +parts involved in adding a new component. This is a rapid-fire list of all +the things you'll need to do if adding a new component from scratch.

+

The Rust Code

+

Your component should live under ./components in this repo. +Use cargo new --lib ./components/<your_crate_name>to create a new library crate, +and please try to avoid using hyphens in the crate name.

+

See the Guide to Building a Rust Component for general +advice on designing and structuring the actual Rust code, and follow the +Dependency Management Guidelines if your crate +introduces any new dependencies.

+

Use UniFFI to define how your crate's +API will get exposed to foreign-language bindings. By convention, put the interface +definition file at ./components/<your_crate_name>/<your_crate_name>.udl. Use +the builtin-bindgen feature of UniFFI to simplify the build process, by +putting the following in your Cargo.toml:

+
[build-dependencies]
+uniffi_build = { version = "<latest version here>", features=["builtin-bindgen"] }
+
+

Include your new crate in the application-services workspace, by adding +it to the members and default-members lists in the Cargo.toml at +the root of the repository.

+

In order to be published to consumers, your crate must be included in the +"megazord" crate for each target platform:

+
    +
  • For Android, add it as a dependency in ./megazords/full/Cargo.toml and +add a pub use <your_crate_name> to ./megazords/full/src/lib.rs.
  • +
  • For iOS, add it as a dependency in ./megazords/ios-rust/rust/Cargo.toml and +add a pub use <your_crate_name> to ./megazords/ios-rust/src/lib.rs.
  • +
+

Run cargo check -p <your_crate_name> in the repository root to confirm that +things are configured properly. This will also have the side-effect of updating +Cargo.lock to contain your new crate and its dependencies.

+

The Kotlin Bindings

+

Make a ./components/<your_crate_name>/android subdirectory to contain +Kotlin- and Android-specific code. This directory will contain a gradle +project for building your Kotlin bindings.

+

Copy the build.gradle file from ./components/crashtest/android/ into +your own component's directory, and edit it to replace the references to +crashtest.udl with your own component's .udl file.

+

Create a file ./components/<your_crate_name>/uniffi.toml with the +following contents:

+
[bindings.kotlin]
+package_name = "mozilla.appservices.<your_crate_name>"
+cdylib_name = "megazord"
+
+

Create a file ./components/<your_crate_name>/android/src/main/AndroidManifest.xml +with the following contents:

+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="org.mozilla.appservices.<your_crate_name>" />
+
+

In the root of the repository, edit .buildconfig-android.ymlto add +your component's metadata. This will cause it to be included in the +gradle workspace and in our build and publish pipeline. Check whether +it builds correctly by running:

+
    +
  • ./gradlew <your_crate_name>:assembleDebug
  • +
+

You can include hand-written Kotlin code alongside the automatically +generated bindings, by placing `.kt`` files in a directory named:

+
    +
  • ./android/src/test/java/mozilla/appservices/<your_crate_name>/
  • +
+

You can write Kotlin-level tests that consume your component's API, +by placing `.kt`` files in a directory named:

+
    +
  • ./android/src/test/java/mozilla/appservices/<your_crate_name>/.
  • +
+

So you would end up with a directory structure something like this:

+
    +
  • components/<your_crate_name>/ +
      +
    • Cargo.toml
    • +
    • uniffi.toml
    • +
    • src/ +
        +
      • Rust code here.
      • +
      +
    • +
    • android/ +
        +
      • build.gradle
      • +
      • src/ +
          +
        • main/ +
            +
          • AndroidManifest.xml
          • +
          • java/mozilla/appservices/<your_crate_name>/ +
              +
            • Hand-written Kotlin code here.
            • +
            +
          • +
          +
        • +
        • test/java/mozilla/appservices/<your_crate_name>/ +
            +
          • Kotlin test-cases here.
          • +
          +
        • +
        +
      • +
      +
    • +
    +
  • +
+

Run your component's Kotlin tests with ./gradlew <your_crate_name>:test +to confirm that this is all working correctly.

+

The Swift Bindings

+

Creating the directory structure

+

Make a ./components/<your_crate_name>/ios subdirectory to contain +Swift- and iOS-specific code. The UniFFI-generated swift bindings will +be written to a subdirectory named Generated.

+

You can include hand-written Swift code alongside the automatically +generated bindings, by placing .swift files in a directory named: +./ios/<your_crate_name>/.

+

So you would end up with a directory structure something like this:

+
    +
  • components/<your_crate_name>/ +
      +
    • Cargo.toml
    • +
    • uniffi.toml
    • +
    • src/ +
        +
      • Rust code here.
      • +
      +
    • +
    • ios/ +
        +
      • <your_crate_name>/ +
          +
        • Hand-written Swift code here.
        • +
        +
      • +
      • Generated/ +
          +
        • Generated Swift code will be written into this directory.
        • +
        +
      • +
      +
    • +
    +
  • +
+

Adding your component to the Swift Package Manager Megazord

+
+

For more information on our how we ship components using the Swift Package Manager, check the ADR that introduced the Swift Package Manager

+
+

You will need to do the following steps to include the component in the megazord:

+
    +
  1. +

    Update its uniffi.toml to include the following settings:

    +
    [bindings.swift]
    +ffi_module_name = "MozillaRustComponents"
    +ffi_module_filename = "<crate_name>FFI"
    +
    +
  2. +
  3. +

    Add the component as a dependency to the Cargo.toml in megazords/ios-rust/

    +
  4. +
  5. +

    Add a pub use declaration for the component in megazords/ios-rust/src/lib.rs

    +
  6. +
  7. +

    Add logic to the megazords/ios-rust/build-xcframework.sh to copy or generate its header file into the build

    +
  8. +
  9. +

    Add an #import for its header file to megazords/ios-rust/MozillaRustComponents.h

    +
  10. +
  11. +

    Add your component into the iOS "megazord" through the Xcode project, which can only really by done using the Xcode application, which can only really be done if you're on a Mac.

    +
      +
    1. +

      Open megazords/ios-rust/MozillaTestServices/MozillaTestServices.xcodeproj in Xcode.

      +
    2. +
    3. +

      In the Project navigator, add a new Group for your new component, pointing to +the ./ios/ directory you created above. Add the following entries to the Group:

      +
        +
      • The .udl file for you component, from ../src/<your_crate_name>.udl.
      • +
      • Any hand-written .swift files for your component
      • +
      +
    4. +
    +
  12. +
+
+

Make sure that the "Copy items if needed" option is unchecked, and that +nothing is checked in the "Add to targets" list.

+
+

The result should look something like this:

+

Screenshot of Xcode Project Navigator

+

Click on the top-level "MozillaTestServices" project in the navigator, then go to "Build Phases".

+
+

Double-check that <your_crate_name>.udl does not appear in the "Copy Bundle Resources" section.

+
+

Add <your_crate_name>.udl to the list of "Compile Sources". This will trigger an Xcode Build Rule that generates +the Swift bindings automatically. Also include any hand-written .swift files in this list.

+

Finally, in the Project navigator, add a sub-group named "Generated", pointing to the ./Generated/ subdirectory, and +containing entries for the files generated by UniFFI: +* <your_crate_name>.swift +* <your_crate_name>FFI.h +Make sure that "Copy items if needed" is unchecked, and that nothing is checked in "Add to targets".

+
+

Double-check that <your_crate_name>.swift does not appear in the "Compile Sources" section.

+
+

The result should look something like this:

+

Screenshot of Xcode Compile Sources list

+

Build the project in Xcode to check whether that all worked correctly.

+

To add Swift tests for your component API, create them in a file under +megazords/ios-rust/MozillaTestServicesTests/. Use this syntax to import +your component's bindings from the compiled megazord:

+
@testable import MozillaTestServices
+
+

In Xcode, navigate to the MozillaTestServicesTests Group and add your +new test file as an entry. Select the corresponding target, click on +"Build Phases", and add your test file to the list of "Compile Sources". +The result should look something like this:

+

Screenshot of Xcode Test Setup

+

Use the Xcode Test Navigator to run your tests and check whether +they're passing.

+

Distribute your component with rust-components-swift

+

The Swift source code and generated UniFFI bindings are distributed to consumers (eg: Firefox iOS) through rust-components-swift.

+

A nightly taskcluster job prepares the rust-component-swift packages from the source code in the application-services repository. To distribute your component with rust-component-swift, add the following to the taskcluster script in taskcluster/scripts/build-and-test-swift.py:

+
    +
  • Add the path to the <your_crate_name>.udl file to BINDINGS_UDL_PATHS +
      +
    • Optionally also to FOCUS_UDL_PATHS if your component is also targeting Firefox Focus
    • +
    +
  • +
  • Add the path to the directory containing any hand-written swift code to SOURCE_TO_COPY +
      +
    • Optionally also to FOCUS_SOURCE_TO_COPY if your component is also targeting Firefox Focus
    • +
    +
  • +
+

Your component should now automatically get included in the next rust-component-swift nightly release.

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + diff --git a/book/howtos/branch-builds.html b/book/howtos/branch-builds.html new file mode 100644 index 0000000000..b2569dd749 --- /dev/null +++ b/book/howtos/branch-builds.html @@ -0,0 +1,276 @@ + + + + + + Branch builds - Cross-platform Rust Components + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Branch builds

+

Branch builds are a way to build and test Fenix using branches from application-services and firefox-android. +iOS is not currently supported, although we may add it in the future (see #4966).

+

Breaking changes in an application-services branch.

+

When we make breaking changes in an application-services branch, we typically make corresponding changes in an +android-components branch. Branch builds allow combining those branches together in order to run CI tests +and to produce APKs for manual testing. To trigger a branch build for this:

+
    +
  • Create the PR for the application-services branch you're working on
  • +
  • Add [firefox-android: branch-name] to the PR title
  • +
  • The branch build tasks will be listed as checks the Github PR. In particular: +
      +
    • branch-build-fenix-test and branch-build-ac-test will run the unit android-components/fenix unit tests
    • +
    • branch-build-fenix-build will contain the Fenix APK.
    • +
    +
  • +
+

Application-services nightlies

+

When we make non-breaking changes, we typically merge them into main and let them sit there until the next release. In +order to check that the current main really does only have non-breaking changes, we run a nightly branch build from the +main branch of application-services,

+
    +
  • To view the latest branch builds: +
      +
    • Open the latest decision task from the task index.
    • +
    • Click the "View Task" link
    • +
    • Click "Task Group" in the top-left
    • +
    • You should now see a list of tasks from the latest nightly +
        +
      • *-build were for building the application. A failure here indicates there's probably a breaking change that +needs to be resolved.
      • +
      • To get the APK, navigate to branch-build-fenix-build and download app-x86-debug.apk from the artifacts list
      • +
      • branch-build-ac-test.* are the android-components tests tasks. These are split up by gradle project, which matches +how the android-components CI handles things. Running all the tests together often leads to failures.
      • +
      • branch-build-fenix-test is the Fenix tests. These are not split up per-project.
      • +
      +
    • +
    +
  • +
  • These builds are triggered by our .cron.yml file
  • +
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + diff --git a/book/howtos/breaking-changes.html b/book/howtos/breaking-changes.html new file mode 100644 index 0000000000..89ed4ffcb6 --- /dev/null +++ b/book/howtos/breaking-changes.html @@ -0,0 +1,263 @@ + + + + + + Breaking API changes - Cross-platform Rust Components + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Breaking changes in application-services code

+

Application-services components are consumed by multiple consumers including Firefox Android, +Firefox iOS, Focus Android, and Focus iOS. To minimize the disruption to those projects when making +breaking API changes, we follow a simple rule: Have approved PRs ready to land that fix the +breakage in the other repos before merging the PR into application-services.

+

This means writing code for the +firefox-android and +firefox-ios repositories that resolves any +breaking changes, creating a PR in those repositories, and waiting for it to be approved.

+

You can test this code locally using the autopublish flow (Android, iOS) and use the branch build system to run CI tests.

+

Merging

+

Do not merge any PRs until all are approved. Once they are all approved then:

+
    +
  • Merge the application-services PR into main
  • +
  • Manually trigger a new nightly build using the taskcluster hook: +https://firefox-ci-tc.services.mozilla.com/hooks/project-releng/cron-task-mozilla-application-services%2Fnightly
  • +
  • Once the nightly task completes, trigger a new rust-components-swift build using the github action: +https://github.com/mozilla/rust-components-swift/actions/workflows/update-as-nightly.yml
  • +
  • Update the firefox-android and firefox-ios PRs to use the newly built nightly: + +
  • +
  • Ideally, get the PRs merged before the firefox-android/firefox-ios nightly bump the next day. +If you don't get these merged, then the nightly bump PR will fail. Add a link to your PR in +the nightly bump PR so the mobile teams know how to fix this.
  • +
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + diff --git a/book/howtos/building-a-rust-component.html b/book/howtos/building-a-rust-component.html new file mode 100644 index 0000000000..890a20df29 --- /dev/null +++ b/book/howtos/building-a-rust-component.html @@ -0,0 +1,409 @@ + + + + + + How to build a new syncable component - Cross-platform Rust Components + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Guide to Building a Syncable Rust Component

+
+

This is a guide to creating a new Syncable Rust Component like many of the components in this repo. If you are looking for information how to build (ie,compile, etc) the existing components, you are looking for our build documentation

+
+

Welcome!

+

It's great that you want to build a Rust Component - this guide should help +get you started. It documents some nomenclature, best-practices and other +tips and tricks to get you started.

+

This document is just for general guidance - every component will be different +and we are still learning how to make these components. Please update this +document with these learnings.

+

To repeat with emphasis - please consider this a living document.

+

General design and structure of the component

+

We think components should be structured as described here.

+

We build libraries, not frameworks

+

Think of building a "library", not a "framework" - the application should be in +control and calling functions exposed by your component, not providing functions +for your component to call.

+

The "store" is the "entry-point"

+

[Note that some of the older components use the term "store" differently; we +should rename them! In Places, it's called an "API"; in Logins an "engine". See +webext-storage for a more recent component that uses the term "Store" as we +think it should be used.]

+

The "Store" is the entry-point for the consuming application - it provides the +core functionality exposed by the component and manages your databases and other +singletons. The responsibilities of the "Store" will include things like creating the +DB if it doesn't exist, doing schema upgrades etc.

+

The functionality exposed by the "Store" will depend on the complexity of the +API being exposed. For example, for webext-storage, where there are only a +handful of simple public functions, it just directly exposes all the +functionality of the component. However, for Places, which has a much more +complex API, the (logical) Store instead supplies "Connection" instances which +expose the actual functionality.

+

Using sqlite

+

We prefer sqlite instead of (say) JSON files or RKV.

+

Always put sqlite into WAL mode, then have exactly 1 writer connection and as +many reader connections you need - which will depend on your use-case - for +example, webext_storage has 1 reader, while places has many.

+

(Note that places has 2 writers (one for sync, one for the api), but we +believe this was a mistake and should have been able to make things work +better with exactly 1 shared between sync and the api)

+

We typically have a "DB" abstraction which manages the database itself - the +logic for handling schema upgrades etc and enforcing the "only 1 writer" rule +is done by this.

+

However, this is just a convenience - the DB abstractions aren't really passed +around - we just pass raw connections (or transactions) around. For example, if +there's a utility function that reads from the DB, it will just have a Rusqlite +connection passed. (Again, older components don't really do this well, but +webext-storage does)

+

We try and leverage rust to ensure transactions are enforced at the correct +boundaries - for example, functions which write data but which must be done as +part of a transaction will accept a Rusqlite Transaction reference as the +param, whereas something that only reads the Db will accept a Rusqlite +Connection - note that because Transaction supports +Deref<Target = Connection>, you can pass a &Transaction wherever a +&Connection is needed - but not vice-versa.

+

Meta-data

+

You are likely to have a table just for key/value metadata, and this table will +be used by sync (and possibly other parts of the component) to track the +sync IDs, lastModified timestamps etc.

+

Schema management

+

The schemas are stored in the tree in .sql files and pulled into the source at +build time via include_str!. Depending on the complexity of your component, +there may be a need for different Connections to have different Sql (for +example, it may be that only your 'write' connection requires the sql to define +triggers or temp tables, so these might be in their own file.)

+

Because requirements evolve, there will be a need to support schema upgrades. +This is done by way of sqlite's PRAGMA user_version - which can be thought of +as simple metadata for the database itself. In short, immediately after opening +the database for the first time, we check this version and if it's less than +expected we perform the schema upgrades necessary, then re-write the version +to the new version.

+

This is easier to read than explain, so read the upgrade() function in +the Places schema code

+

You will need to be a big careful here because schema upgrades are going to +block the calling application immediately after they upgrade to a new version, +so if your schema change requires a table scan of a massive table, you are going +to have a bad time. Apart from that though, you are largely free to do whatever +sqlite lets you do!

+

Note that most of our components have very similar schema and database +management code - these are screaming out to be refactored so common logic can +be shared. Please be brave and have a go at this!

+

Triggers

+

We tend to like triggers for encompasing application logic - for example, if +updating one row means a row in a different table should be updated based on +that data, we'd tend to prefer an, eg, AFTER UPDATE trigger than having our +code manually implement the logic.

+

However, you should take care here, because functionality based on triggers is +difficult to debug (eg, logging in a trigger is difficult) and the functionality +can be difficult to locate (eg, users unfamiliar with the component may wonder +why they can't find certain functionity in the rust code and may not consider +looking in the sqlite triggers)

+

You should also be careful when creating triggers on persistent main tables. +For example, bumping the change counter isn't a good use for a trigger, +because it'll run for all changes on the table—including those made by Sync. +This means Sync will end up tracking its own changes, and getting into infinite +syncing loops. Triggers on temporary tables, or ones that are used for +bookkeeping where the caller doesn't matter, like bumping the foreign +reference count for a URL, are generally okay.

+

General structure of the rust code

+

We prefer flatter module hierarchies where possible. For example, in Places +we ended up with sync_history and sync_bookmarks sub-modules rather than +a sync submodule itself with history and bookmarks.

+

Note that the raw connections are never exposed to consumers - for example, they +will tend to be stored as private fields in, eg, a Mutex.

+

Syncing

+

The traits you need to implement to sync aren't directly covered here.

+

All meta-data related to sync must be stored in the same database as the +data itself - often in a meta table.

+

All logic for knowing which records need to be sync must be part of the +application logic, and will often be implemented using triggers. It's quite +common for components to use a "change counter" strategy, which can be +summarized as:

+
    +
  • +

    Every table which defines the "top level" items being synced will have a +column called something like 'sync_change_counter' - the app will probably +track this counter manually instead of using a trigger, because sync itself +will need different behavior when it updates the records.

    +
  • +
  • +

    At sync time, items with a non-zero change counter are candidates for syncing.

    +
  • +
  • +

    As the sync starts, for each item, the current value of the change counter is +remembered. At the end of the sync, the counter is decremented by this value. +Thus, items which were changed between the time the sync started and completed +will be left with a non-zero change counter at the end of the sync.

    +
  • +
+

Syncing FAQs

+

This section is stolen from this document

+

What’s the global sync ID and the collection sync ID?

+

Both guids, both used to identify when the data in the server has changed +radically underneath us (eg, when looking at lastModified is no longer a sane +thing to do.)

+

The "global sync ID" changing means that every collection needs to be assumed as +having changed radically, whereas just the "collection sync ID" changing means +just that one collection.

+

These global IDs are most likely to change on a node reassignment (which should +be rare now with durable storage), a password reset, etc. An example of when the +collection ID will change is a "bookmarks restore" - handling an old version of +a database re-appearing is why we store these IDs in the database itself.

+

What’s get_sync_assoc, why is it important? What is StoreSyncAssociation?

+

They are all used to track the guids above. It’s vitally important we know when +these guids change.

+

StoreSyncAssociation is a simple enum which reflects the state a sync engine +can be in - either Disconnected (ie, we have no idea what the GUIDs are) or +Connected where we know what we think the IDs are (but the server may or may +not match with this)

+

These GUIDs will typically be stored in the DB in the metadata table.

+

what is apply_incoming versus sync_finished

+

apply_incoming is where any records incoming from the server (ie, possibly +all records on the server if this is a first-sync, records with a timestamp +later than our last sync otherwise) are processed.

+

sync_finished is where we've done all the sync work other than uploading new +changes to the server.

+

What's the diff between reset and wipe?

+
    +
  • Reset means “I don’t know what’s on the server - I need to reconcile everything there with everything I have”. IOW, a “first sync”
  • +
  • Wipe means literally “wipe all server data”
  • +
+

Exposing to consumers

+

You will need an FFI or some other way of exposing stuff to your consumers.

+

We use a tool called UniFFI to automatically +generate FFI bindings from the Rust code.

+

If UniFFI doesn't work for you, then you'll need to hand-write the FFI layer. +Here are some earlier blog posts on the topic which might be helpful:

+ +

The above are likely to be superseded by uniffi docs, but for now, good luck!

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + diff --git a/book/howtos/converting-a-component-to-uniffi.html b/book/howtos/converting-a-component-to-uniffi.html new file mode 100644 index 0000000000..3619b579e9 --- /dev/null +++ b/book/howtos/converting-a-component-to-uniffi.html @@ -0,0 +1,545 @@ + + + + + + How to convert a Rust Component to Uniffi - Cross-platform Rust Components + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Converting an existing Component to use UniFFI

+

When we started building the components in this repo, exposing Rust code to +Kotlin and Swift was a manual process and each component had its own +hand-written FFI layer and foreign-language bindings.

+

As we've gained more experience with building components in this way, we've +started to automate bindings generation and capture best practices in a +tool called UniFFI, which is the +currently recommended approach when adding a new component from scratch.

+

We expect that existing components will gradually be ported over to use +UniFFI, and this document is a guide to doing that port.

+

First, get familiar with UniFFI

+

First, make sure you've perused the UniFFI guide +to understand the overall architecture of a UniFFI component, and take a look +at the guide to adding a new component to understand +how such components fit in to this repo. The aim of porting will be to have a component +that looks like it was added by the process described therein.

+

Next, get familiar with the target component

+

Pre-UniFFI components typically consist of four main parts:

+
    +
  • A Rust crate implementing the core functionality of the component
  • +
  • A separate Rust crate that exposes the core functionality over a C-style FFI.
  • +
  • An Android package that imports the C-style FFI into idiomatic Kotlin.
  • +
  • A Swift module that imports the C-style FFI into idiomatic Swift.
  • +
+

The code for these parts will be laid out something like this:

+
    +
  • components/<component_name>/ +
      +
    • Cargo.toml
    • +
    • src/ +
        +
      • Rust code for the core functionality of the component goes here.
      • +
      +
    • +
    • ffi/ +
        +
      • Cargo.toml
      • +
      • src/ +
          +
        • Rust code specifically for exposing the C-style FFI goes here.
        • +
        +
      • +
      +
    • +
    • android/ +
        +
      • build.gradle
      • +
      • src/ +
          +
        • main/ +
            +
          • AndroidManifest.xml
          • +
          • java/mozilla/appservices/<component_name>/ +
              +
            • Lib<ComponentName>FFI.kt (low-level bindings to the C-style FFI)
            • +
            • Higher-level hand-written Kotlin that wraps the FFI.
            • +
            +
          • +
          +
        • +
        +
      • +
      +
    • +
    • ios/ +
        +
      • <component_name>/ +
          +
        • Rust<ComponentName>API.h (low-level bindings to the C-style FFI)
        • +
        • Higher-level hand-written Swift that wraps the FFI.
        • +
        +
      • +
      +
    • +
    +
  • +
+

The goal here is to replace much of the hand-written wrapper layers with autogenerated +code:

+
    +
  • The ./ffi/ crate will disappear entirely, its work is automated by UniFFI +
      +
    • If you still need some hand-written pub extern "C" functions, perhaps to +implement features not currently supported by UniFFI, then they should move +into lib.rs of the main component crate.
    • +
    +
  • +
  • The low-level Lib<ComponentName>FFI.kt file will disappear entirely, as will some of the +code that converts it back into nice high-level Kotlin classes and interfaces. +
      +
    • Some of the hand-written Kotlin code may remain, if it provides functionality that +cannot be implemented in Rust.
    • +
    +
  • +
  • The low-level Rust<ComponentName>API.h file will disappear entirely, as will some of the +code that converts it back into nice high-level Swift classes and interfaces. +
      +
    • Some of the hand-written Swift code may remain, if it provides functionality that +cannot be implemented in Rust.
    • +
    +
  • +
+

You'll aim to end up with a simplified file structure that looks like this:

+
    +
  • components/<component_name>/ +
      +
    • Cargo.toml
    • +
    • uniffi.toml
    • +
    • src/ +
        +
      • <component_name>.udl (abstract interface definition)
      • +
      • Rust code here.
      • +
      +
    • +
    • android/ +
        +
      • build.gradle
      • +
      • src/ +
          +
        • main/ +
            +
          • AndroidManifest.xml
          • +
          • java/mozilla/appservices/<component_name>/ +
              +
            • Optional hand-written Kotlin code here.
            • +
            +
          • +
          +
        • +
        +
      • +
      +
    • +
    • ios/ +
        +
      • <component_name>/ +
          +
        • Optional hand-written Swift code here.
        • +
        +
      • +
      +
    • +
    +
  • +
+

Write a first draft of the .udl file for the component's interface

+

Make sure you've got the uniffi-bindgen command available; cargo install uniffi_bindgen will +ensure you have the latest version.

+

Create ./src/<component_name>.udl and try to describe the intended interface for the component +using UniFFI's interface definition language. +You'll probably need to reverse-engineer it a little bit from the existing hand-written Kotlin and/or +Swift code.

+

Don't spend too much time on trying to match every minute detail of the existing hand-written API. +There are likely to be small differences between how UniFFI likes to do things and how the hand-written +APIs were structured, and it's in everyone's best long-term interests to just push ahead and update +consumers to accommodate any breaking API changes, rathern than e.g. trying to convince UniFFI to +capitalize enum variant names in the same style that the hand-written code was using.

+

To check whether the .udl file is syntactically valid, you can use uniffi-bindgen to generate +the Rust FFI scaffolding like so:

+
uniffi-bindgen scaffolding ./src/<component_name>.udl
+
+

If this succeeds, it will generate a file ./src/<component_name>.uniffi.rs with a bunch of +thorny auto-generated Rust code. If it fails, it will likely fail with an inscrutable error message. +Unfortunately the error reporting in UniFFI is currently a known pain point, and it can take a +bit of trial-and-error to identify what part of the file is causing the issue. Sorry :-(

+

The aim at this point is to ensure that the intended interface of the component can be expressed +in terms that UniFFI understands. Most cases should be supported, but you may find some aspect of +the existing component that is hard to express in UniFFI, perhaps even uncovering new functionality +that needs to be added to UniFFI itself!

+

The .udl file is definitely a first draft at this point. It is normal and expected to need +to iterate on this file as you port over the underlying Rust code.

+

Restructure the Rust code to introduce UniFFI

+

You will now restructure the existing Rust crate so that its public API surface +and overall "shape" match what you defined in the .udl file.

+

Start by deleting the ./ffi sub-crate, because you're going to use UniFFI to generate +all of that code. You'll also need to remove it from the workspace in the top-level +Cargo.toml file, as well as change the crates under /megazords to import the core +Rust crate for the component rather than importing the FFI sub-crate.

+

Add UniFFI to the crate's dependencies and configure its build.rs script to invoke the +UniFFI scaffolding generator, as described in "adding a new component".

+

Now, edit ./lib.rs so that it matches the interface defined in the .udl file as closely +as possible. If the .udl has an interface Example then lib.rs should contain a +pub struct Example, if the .udl contains an enum ExampleItem then lib.rs should +contain a pub enum ExampleItem, and so-on.

+

The details of this step will depend heavily on the specific crate, but some tips include:

+
    +
  • +

    You may find it useful to move all of the existing code into a sub-module named internal, +and then make a brand new lib.rs that imports or re-defines just the pieces it needs +in order to implement the interface from the .udl file. The fxa-client crate is an +example of a case where this worked out well, though of course your mileage may vary.

    +
  • +
  • +

    If the existing crate contains a file named like <component_name>_msg_types.proto, then +it was using Protocol Buffers to serialize data to pass over the FFI. The message types +defined in the .proto file will need to be converted into dictionary or enum definitions +in your .udl file. See the section below for more details.

    +
  • +
+

As noted above, don't be afraid to accept some API churn during the conversion process. +We're willing to accept some breaking API changes as the cost of getting bindings generated +for free, as long as the core functionality and mental model of the component remain intact.

+

At this point, in theory the crate should be buildable with UniFFI, although it's likely +to require some iteration to get it all working! Run cargo check to check for any +compilation errors without having to do a full build.

+

Removing Protobuf Messages

+

Passing rich structured data over the FFI is the most complex part of our hand-written bindings, +and was previously done by serializing data via Protocol Buffers. +This is something that UniFFI tries to make as simple as possible.

+

Start by locating the <component_name>_msg_types.proto file for the component. This file defines +the structured messages that can be passed over the FFI, and you should see that they correspond +to various types of structured data that the component wants to receive from, or return to, +the foreign-language code.

+

Find the places in your .udl interface that correspond to these message types and make sure +that you've got a similarly-shaped dictionary or enum for each one. You should find that +representing this structured data in UDL is simpler than protobuf in many cases - for example +many of our .protobuf files need to use a separate ExampleStructs message in order to +pass a list of ExampleStruct messages over the FFI, but in UniFFI this is represented +directly as sequence<ExampleStruct>.

+

Find the places in the Rust code that are using these message types to return structured data. +In simple cases, you may be able to directly replace uses of msg_types::ExampleStruct with +the corresponding crate::ExampleStruct from your public API. +For more complex cases, you may find it helpful to define an Into mapping between the +UniFFI dictionary/enum in the crate's public interface, and a more complex struct designed +for internal use.

+

As noted above, don't be afraid to accept some API churn during this conversion process.

+

Once you have replaced all uses of the msg_types structs in the Rust code:

+
    +
  • Delete ./src/<component_name>_msg_types.proto.
  • +
  • Delete ./src/mozilla.appservices.<component_name>.protobuf.rs, which is generated from the .proto file.
  • +
  • Remote prost and prost-derive from the crate's dependencies.
  • +
  • Delete the crate from the list in /tools/protobuf_files.toml.
  • +
+

If you happen to find that you've deleted the last crate from the list in protobuf_files.toml, +congratulations! You've successfully removed protocol buffers from this repo entirely, and should +file a bug to track the complete removal of protobuf from our tooling and dependency chain.

+

Document the Public API in the Rust code

+

Write consumer-facing documentation on the public API in lib.rs using Rust's standard +rustdoc conventions +and tools. The fxa-client crate may serve as a good example.

+

You can view the generated documentation by running:

+
cargo doc --no-deps --open
+
+

In future, we intend to automatically extract documentation from the Rust code +and make it easily available to consumers of the generated bindings.

+

(In fact there is some work-in-progress code in uniffi-rs#416 +that can read docs from the Rust code and write them back into the .udl file, which you're +welcome to try out if you're feeling adventurous. But it's just a very hacky prototype.)

+

Set up the Kotlin wrapper

+

It's easiest to start by removing all of the hand-written Kotlin code under android/src/main/java +and then restoring parts of it later if necessary. Leave the AndroidManifest.xml file and any tests +in place.

+

Delete the android/build.gradle file and then follow the instructions for adding Kotlin bindings +for a new component to create a new build.gradle +file and a corresponding uniffi.toml.

+

This should be all that's required to set up UniFFI to build the Kotlin bindings. Try building +the Android package to confirm:

+
    +
  • ./gradlew <component_name>:assembleDebug
  • +
+

The UniFFI-generated Kotlin code will be under ./android/build/generated/source/uniffi/ and +may be useful for debugging.

+

If there are existing Kotlin tests for the component, the next step is to get those passing:

+
    +
  • ./gradlew <component_name>:test
  • +
+

As noted above, it is normal and expected for the autogenerated bindings to be subtly different +from the previous hand-written ones. For example, UniFFI insists on using SHOUTY_SNAKE_CASE +variant names in Kotlin enums while the hand-written code may have used CamelCase. Some components +also have small naming differences between the Rust code and the hand-written Kotlin bindings, +which UniFFI will not allow.

+

If the component had functionality in its Kotlin layer that was not part of the Rust API, +then you'll need to add some hand-written Kotlin code under android/src/main/java to +implement it. The fxa-client component may be a good example here: its Rust layer exposes +a FirefoxAccount struct that the Kotlin code wraps into a PersistedFirefoxAccount class, +adding the ability to set a persistence callback.

+

Finally, you will need to try out the new bindings with a consuming app. For Kotlin code you should +make a local build of android-components and Fenix, +updating them to accomodate any changes in the component's public API.

+

Set up the Swift wrapper

+

It's easiest to start by removing all of the hand-written Swift code under ./ios and then +restoring parts of it later if necessary.

+

Edit /megazords/ios-rust/MozillaTestServices.h to remove any references to Rust<ComponentName>API.h, +replacing them with the UniFFI-generated header file name <component_name>FFI.h.

+

Open /megazords/ios-rust/MozillaTestServices.xcodeproj in Xcode and follow the instructions for +adding Swift bindings for a new component to +configure Xcode to build your UniFFI-generated bindings.

+

While you are in the Xcode Project Navigator, you should also delete any references to +Rust<ComponentName>API.h or to the old hand-written Swift wrappers. (They should be highlighted +in red in the Project Navigator, because the files will be missing from disk after you +deleted them above).

+

This should be all that's required to set up UniFFI to build the Swift bindings. Try building +the project in Xcode to confirm.

+

The UniFFI-generated Swift code will be under ios/Generated and may be useful for debugging.

+

If there are existing Swift tests for the component, the next step is to get those passing:

+
    +
  • ./automation/run_ios_tests.sh
  • +
  • (or run them from the Xcode GUI)
  • +
+

As noted above, it is normal and expected for the autogenerated bindings to be subtly different +from the previous hand-written ones. Many existing components have small naming differences +between the Rust code and the hand-written Swift bindings, which UniFFI will not allow.

+

If the component had functionality in its Swift layer that was not part of the Rust API, +then you'll need to add some hand-written Swift code under ./ios/<ComponentName> to +implement it. The fxa-client component may be a good example here: its Rust layer exposes +a FirefoxAccount struct that the Swift code wraps into a PersistedFirefoxAccount class, +adding the ability to set a persistence callback.

+

You will need to add any such file to the "Compile Sources" list in Xcode, in the same way +that you added the .udl file.

+

Finally, you will need to try out the new bindings with a consuming app. For Swift code you should make a local build of Firefox iOS, you can do that by following the steps in this document

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + diff --git a/book/howtos/debug-sql.html b/book/howtos/debug-sql.html new file mode 100644 index 0000000000..583e5e9cfe --- /dev/null +++ b/book/howtos/debug-sql.html @@ -0,0 +1,318 @@ + + + + + + How to debug SQL/sqlite - Cross-platform Rust Components + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Debugging Sql

+

It can be quite tricky to debug what is going on with sql statement, especially +once the sql gets complicated or many triggers are involved.

+

The sql_support create provides some utilities to help. Note that +these utilities are gated behind a debug-tools feature. The module +provides docstrings, so you should read them before you start.

+

This document describes how to use these capabilities and we'll use places +as an example.

+

First, we must enable the feature:

+
--- a/components/places/Cargo.toml
++++ b/components/places/Cargo.toml
+@@ -22,7 +22,7 @@ lazy_static = "1.4"
+ url = { version = "2.1", features = ["serde"] }
+ percent-encoding = "2.1"
+ caseless = "0.2"
+-sql-support = { path = "../support/sql" }
++sql-support = { path = "../support/sql", features=["debug-tools"] }
+
+

and we probably need to make the debug functions available:

+
--- a/components/places/src/db/db.rs
++++ b/components/places/src/db/db.rs
+@@ -108,6 +108,7 @@ impl ConnectionInitializer for PlacesInitializer {
+         ";
+         conn.execute_batch(initial_pragmas)?;
+         define_functions(conn, self.api_id)?;
++        sql_support::debug_tools::define_debug_functions(conn)?;
+
+

We now have a Rust function print_query() and a SQL function dbg() available.

+

Let's say we were trying to debug a test such as test_bookmark_tombstone_auto_created. +We might want to print the entire contents of a table, then instrument a query to check +what the value of a query is. We might end up with a patch something like:

+
index 28f19307..225dccbb 100644
+--- a/components/places/src/db/schema.rs
++++ b/components/places/src/db/schema.rs
+@@ -666,7 +666,8 @@ mod tests {
+             [],
+         )
+         .expect("should insert regular bookmark folder");
+-        conn.execute("DELETE FROM moz_bookmarks WHERE guid = 'bookmarkguid'", [])
++        sql_support::debug_tools::print_query(&conn, "select * from moz_bookmarks").unwrap();
++        conn.execute("DELETE FROM moz_bookmarks WHERE dbg('CHECKING GUID', guid) = 'bookmarkguid'", [])
+             .expect("should delete");
+         // should have a tombstone.
+         assert_eq!(
+
+

There are 2 things of note:

+
    +
  • We used the print_query function to dump the entire moz_bookmarks table before executing the query.
  • +
  • We instrumented the query to print the guid every time sqlite reads a row and compares it against +a literal.
  • +
+

The output of this test now looks something like:

+
running 1 test
+query: select * from moz_bookmarks
++----+------+------+--------+----------+---------+---------------+---------------+--------------+------------+-------------------+
+| id | fk   | type | parent | position | title   | dateAdded     | lastModified  | guid         | syncStatus | syncChangeCounter |
++====+======+======+========+==========+=========+===============+===============+==============+============+===================+
+| 1  | null | 2    | null   | 0        | root    | 1686248350470 | 1686248350470 | root________ | 1          | 1                 |
++----+------+------+--------+----------+---------+---------------+---------------+--------------+------------+-------------------+
+| 2  | null | 2    | 1      | 0        | menu    | 1686248350470 | 1686248350470 | menu________ | 1          | 1                 |
++----+------+------+--------+----------+---------+---------------+---------------+--------------+------------+-------------------+
+| 3  | null | 2    | 1      | 1        | toolbar | 1686248350470 | 1686248350470 | toolbar_____ | 1          | 1                 |
++----+------+------+--------+----------+---------+---------------+---------------+--------------+------------+-------------------+
+| 4  | null | 2    | 1      | 2        | unfiled | 1686248350470 | 1686248350470 | unfiled_____ | 1          | 1                 |
++----+------+------+--------+----------+---------+---------------+---------------+--------------+------------+-------------------+
+| 5  | null | 2    | 1      | 3        | mobile  | 1686248350470 | 1686248350470 | mobile______ | 1          | 1                 |
++----+------+------+--------+----------+---------+---------------+---------------+--------------+------------+-------------------+
+| 6  | null | 3    | 1      | 0        | null    | 1             | 1             | bookmarkguid | 2          | 1                 |
++----+------+------+--------+----------+---------+---------------+---------------+--------------+------------+-------------------+
+test db::schema::tests::test_bookmark_tombstone_auto_created ... FAILED
+
+failures:
+
+---- db::schema::tests::test_bookmark_tombstone_auto_created stdout ----
+CHECKING GUID root________
+CHECKING GUID menu________
+CHECKING GUID toolbar_____
+CHECKING GUID unfiled_____
+CHECKING GUID mobile______
+CHECKING GUID bookmarkguid
+
+

It's unfortunate that the output of print_table() goes to the tty while the output of dbg goes to stderr, so +you might find the output isn't quite intermingled as you would expect, but it's better than nothing!

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + diff --git a/book/howtos/img/nss_tryserver_artifacts.png b/book/howtos/img/nss_tryserver_artifacts.png new file mode 100755 index 0000000000..32fc1560ed Binary files /dev/null and b/book/howtos/img/nss_tryserver_artifacts.png differ diff --git a/book/howtos/img/xcode-blockzilla.png b/book/howtos/img/xcode-blockzilla.png new file mode 100644 index 0000000000..5037957ba1 Binary files /dev/null and b/book/howtos/img/xcode-blockzilla.png differ diff --git a/book/howtos/img/xcode-client-package-settings.png b/book/howtos/img/xcode-client-package-settings.png new file mode 100644 index 0000000000..a9f7f3b670 Binary files /dev/null and b/book/howtos/img/xcode-client-package-settings.png differ diff --git a/book/howtos/img/xcode-include-packages-firefox-ios.png b/book/howtos/img/xcode-include-packages-firefox-ios.png new file mode 100644 index 0000000000..4ca83dd77b Binary files /dev/null and b/book/howtos/img/xcode-include-packages-firefox-ios.png differ diff --git a/book/howtos/img/xcode-include-packages-focus-ios.png b/book/howtos/img/xcode-include-packages-focus-ios.png new file mode 100644 index 0000000000..a04cf3dce2 Binary files /dev/null and b/book/howtos/img/xcode-include-packages-focus-ios.png differ diff --git a/book/howtos/img/xcode-package-deps.png b/book/howtos/img/xcode-package-deps.png new file mode 100644 index 0000000000..73ec0af903 Binary files /dev/null and b/book/howtos/img/xcode-package-deps.png differ diff --git a/book/howtos/img/xcode_add_component_1.png b/book/howtos/img/xcode_add_component_1.png new file mode 100644 index 0000000000..007aaaaadf Binary files /dev/null and b/book/howtos/img/xcode_add_component_1.png differ diff --git a/book/howtos/img/xcode_add_component_2.png b/book/howtos/img/xcode_add_component_2.png new file mode 100644 index 0000000000..08ab047379 Binary files /dev/null and b/book/howtos/img/xcode_add_component_2.png differ diff --git a/book/howtos/img/xcode_add_component_3.png b/book/howtos/img/xcode_add_component_3.png new file mode 100644 index 0000000000..b13efb3c0e Binary files /dev/null and b/book/howtos/img/xcode_add_component_3.png differ diff --git a/book/howtos/img/xcode_add_component_4.png b/book/howtos/img/xcode_add_component_4.png new file mode 100644 index 0000000000..1a4d0f2cb6 Binary files /dev/null and b/book/howtos/img/xcode_add_component_4.png differ diff --git a/book/howtos/locally-building-jna.html b/book/howtos/locally-building-jna.html new file mode 100644 index 0000000000..1cf624cb40 --- /dev/null +++ b/book/howtos/locally-building-jna.html @@ -0,0 +1,327 @@ + + + + + + How to locally build JNA - Cross-platform Rust Components + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Building and using a locally-modified version of JNA

+

Java Native Access is an important dependency +for the Application Services components on Android, as it provides the low-level interface +from the JVM into the natively-compiled Rust code.

+

If you need to work with a locally-modified version of JNA (e.g. to investigate an apparent +JNA bug) then you may find these notes helpful.

+
+

The JNA docs do have an Android Development Environment guide +that is a good starting point, but the instructions did not work for me and appear a little out of date. +Here are the steps that worked for me:

+
    +
  • +

    Modify your environment to specify $NDK_PLATFORM, and to ensure the Android NDK tools +for each target platform are in your $PATH. On my Mac with Android Studio the +config was as follows:

    +
    export NDK_ROOT="$HOME/Library/Android/sdk/ndk/25.2.9519653"
    +export NDK_PLATFORM="$NDK_ROOT/platforms/android-25"
    +export PATH="$PATH:$NDK_ROOT/toolchains/llvm/prebuilt/darwin-x86_64/bin"
    +export PATH="$PATH:$NDK_ROOT/toolchains/aarch64-linux-android-4.9/prebuilt/darwin-x86_64/bin"
    +export PATH="$PATH:$NDK_ROOT/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64/bin"
    +export PATH="$PATH:$NDK_ROOT/toolchains/x86-4.9/prebuilt/darwin-x86_64/bin"
    +export PATH="$PATH:$NDK_ROOT/toolchains/x86_64-4.9/prebuilt/darwin-x86_64/bin"
    +
    +

    You will probably need to tweak the paths and version numbers based on your operating system and +the details of how you installed the Android NDK.

    +
  • +
  • +

    Install the ant build tool (using brew install ant worked for me).

    +
  • +
  • +

    Checkout the JNA source from Github. Try doing a basic +build via ant dist and ant test. This won't build for Android but will test the rest of the tooling.

    +
  • +
  • +

    Adjust ./native/Makefile for compatibility with your Android NSK install. Here's what I had to do for mine:

    +
      +
    • Adjust the $CC variable to use clang instead of gcc: CC=aarch64-linux-android21-clang.
    • +
    • Adjust thd $CCP variable to use the version from your system: CPP=cpp.
    • +
    • Add -landroid -llog to the list of libraries to link against in $LIBS.
    • +
    +
  • +
  • +

    Build the JNA native libraries for the target platforms of interest:

    +
      +
    • ant -Dos.prefix=android-aarch64
    • +
    • ant -Dos.prefix=android-armv7
    • +
    • ant -Dos.prefix=android-x86
    • +
    • ant -Dos.prefix=android-x86-64
    • +
    +
  • +
  • +

    Package the newly-built native libraries into a JAR/AAR using ant dist. +This should produce ./dist/jna.aar.

    +
  • +
  • +

    Configure build.gradle for the consuming application to use the locally-built JNA artifact:

    +
    // Tell gradle where to look for local artifacts.
    +repositories {
    +    flatDir {
    +        dirs "/PATH/TO/YOUR/CHECKOUT/OF/jna/dist"
    +    }
    +}
    +
    +// Tell gradle to exclude the published version of JNA.
    +configurations {
    +    implementation {
    +        exclude group: "net.java.dev.jna", module:"jna"
    +    }
    +}
    +
    +// Take a direct dependency on the local JNA AAR.
    +dependencies {
    +    implementation name: "jna", ext: "aar"
    +}
    +
    +
  • +
  • +

    Rebuild and run your consuming application, and it should be using the locally-built JNA!

    +
  • +
+

If you're trying to debug some unexpected JNA behaviour (and if you favour old-school printf-style debugging) +then you can this code snippet to print to the Android log from the compiled native code:

+
#ifdef __ANDROID__
+#include <android/log.h>
+#define HACKY_ANDROID_LOG(...) __android_log_print(ANDROID_LOG_VERBOSE, "HACKY-DEBUGGING-FOR-ALL", __VA_ARGS__)
+#else
+#define HACKY_ANDROID_LOG(MSG)
+#endif
+
+HACKY_ANDROID_LOG("this will go to the android logcat output");
+HACKY_ANDROID_LOG("it accepts printf-style format sequences, like this: %d", 42);
+
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + diff --git a/book/howtos/locally-published-components-in-fenix.html b/book/howtos/locally-published-components-in-fenix.html new file mode 100644 index 0000000000..2cd1d04b65 --- /dev/null +++ b/book/howtos/locally-published-components-in-fenix.html @@ -0,0 +1,399 @@ + + + + + + How to use the local development autopublish flow for Fenix - Cross-platform Rust Components + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Using locally published components in Fenix

+

It's often important to test work-in-progress changes to Application Services components against a real-world +consumer project. The most reliable method of performing such testing is to publish your +components to a local Maven repository, and adjust the consuming project to install them +from there.

+

With support from the upstream project, it's possible to do this in a single step using +our auto-publishing workflow.

+

rust.targets

+

Both the auto-publishing and manual workflows can be sped up significantly by +using the rust.targets property which limits which architectures the Rust +code gets build against. You can set this property by creating/editing the +local.properties file in the repository root and adding a line like +rust.targets=x86,linux-x86-64. The trick is knowing which targets to put in +that comma separated list:

+
    +
  • Use x86 for running the app on most emulators (in rare cases, when you have a 64-bit emulator, you'll want x86_64)
  • +
  • If you're running the android-components or fenix unit tests, then you'll need the architecture of your machine: +
      +
    • OSX running Intel chips: darwin-x86-64
    • +
    • OSX running M1 chips: darwin-aarch64
    • +
    • Linux: linux-x86-64
    • +
    +
  • +
+

Using the auto-publishing workflow

+

Some consumers (notably Fenix) have support for +automatically publishing and including a local development version of application-services +in their build. The workflow is:

+
    +
  1. +

    Check out the firefox-android mono-repo.

    +
  2. +
  3. +

    Edit (or create) the file fenix/local.properties and tell it where to +find your local checkout of application-services, by adding a line like:

    +

    autoPublish.application-services.dir=relative/path/to/your/checkout/of/application-services

    +

    Note that the path should be relative from local.properties. For example, if application-services +and firefox-android are at the same level, the relative path would be ../../application-services

    +
  4. +
  5. +

    Do the same for android-components/local.properties - so yes, your local checkout of firefox-android +requires 2 copies of local.properties, both with identical values for autoPublish.application-services.dir +(and probably identical in every other way too)

    +
  6. +
  7. +

    Build the consuming project following its usual build procedure, e.g. via ./gradlew assembleDebug or ./gradlew test.

    +
  8. +
+

If all goes well, this should automatically build your checkout of application-services, publish it +to a local maven repository, and configure the consuming project to install it from there instead of +from our published releases.

+

Using Windows/WSL

+

If you are using Windows, there's a good chance you do most application-services work +in WSL, but want to run Android Studio on native Windows. In that scenario you must:

+
    +
  • From the app-services root, in WSL, execute ./automation/publish_to_maven_local_if_modified.py
  • +
  • In native Windows, just work as normal - that build process knows to not even attempt to +execute the above command automatically.
  • +
+

Using a manual workflow

+

Note: This is a bit tedious, and you should first try the auto-publishing workflow described +above. But if the auto-publishing workflow fails then it's important to know how to do the publishing process manually. Since most consuming apps get their copy of application-services via a dependency +on android-components, this procedure involves three separate repos:

+
    +
  1. +

    Inside the application-services repository root:

    +
      +
    1. +

      In version.txt, change +the version to end in -TESTING$N 1, +where $N is some number that you haven't used for this before.

      +

      Example: 0.27.0-TESTING3

      +
    2. +
    3. +

      Run ./gradlew publishToMavenLocal. This may take between 5 and 10 minutes.

      +
    4. +
    +
  2. +
  3. +

    Inside the consuming project repository root (eg, firefox-android/fenix):

    +
      +
    1. +

      Inside build.gradle, add +mavenLocal() inside allprojects { repositories { <here> } }.

      +
    2. +
    3. +

      Ensure that local.properties does not contain any configuration to +related to auto-publishing the application-services repo.

      +
    4. +
    5. +

      Inside buildSrc/src/main/java/AndroidComponents.kt, change the +version numbers for android-components to +match the new versions you defined above.

      +

      Example: const val VERSION = "0.51.0-TESTING3"

      +
    6. +
    +
  4. +
+

You should now be able to build and run the consuming application (assuming you could +do so before all this).

+

Caveats

+
    +
  1. This assumes you have followed the build instructions for Fenix
  2. +
  3. Make sure you're fully up to date in all repos, unless you know you need to +not be.
  4. +
  5. This omits the steps if changes needed because, e.g. application-services +made a breaking change to an API used in android-components. These should be +understandable to fix, you usually should be able to find a PR with the fixes +somewhere in the android-component's list of pending PRs (or, failing that, a +description of what to do in the application-services changelog).
  6. +
  7. Contact us if you get stuck.
  8. +
+

Adding support for the auto-publish workflow

+

If you had to use the manual workflow above and found it incredibly tedious, you might like to +try adding support for the auto-publish workflow to the consuming project! The details will differ +depending on the specifics of the project's build setup, but at a high level you will need to:

+
    +
  1. +

    In your settings.gradle, locate (or add) the code for parsing the local.properties file, +and add support for loading a directory path from the property autoPublish.application-services.dir.

    +

    If this property is present, spawn a subprocess to run ./gradlew autoPublishForLocalDevelopment +in the specified directory. This automates step (1) of the manual workflow above, publishing your +changes to application-services into a local maven repository under a unique version number.

    +
  2. +
  3. +

    In your build.gradle, if the autoPublish.application-services.dir property +is present, have each project apply the build script from ./build-scripts/substitute-local-appservices.gradle +in the specified directory.

    +

    This automates steps (2) and (3) of the manual workflow above, using gradle's dependency substitution +capabilities to override the verion requirements for application-services components. It may be necessary +to experiment with the ordering of this relative to other build configuration steps, in order for the +dependency substitution to work correctly.

    +

    For a single-project build this would look something like:

    +
    if (gradle.hasProperty('localProperties.autoPublish.application-services.dir')) {
    +   ext.appServicesSrcDir = gradle."localProperties.autoPublish.application-services.dir"
    +   apply from: "${appServicesSrcDir}/build-scripts/substitute-local-appservices.gradle"
    +}
    +
    +

    For a multi-project build it should be applied to all subprojects, like:

    +
    subprojects {
    +   if (gradle.hasProperty('localProperties.autoPublish.application-services.dir')) {
    +      ext.appServicesSrcDir = gradle."localProperties.autoPublish.application-services.dir"
    +      apply from: "${rootProject.projectDir}/${appServicesSrcDir}/build-scripts/substitute-local-appservices.gradle"
    +   }
    +}
    +
    +
  4. +
  5. +

    Confirm that the setup is working, by adding autoPublish.application-services.dir to your +local.properties file and running ./gradlew dependencies for the project.

    +

    You should be able to see gradle checking the build status of the various application-services +dependencies as part of its setup phase. When the command completes, it should print the resolved +versions of all dependencies, and you should see that application-services components have a version +number in the format 0.0.1-SNAPSHOT-{TIMESTAMP}.

    +
  6. +
+
+

[1]: It doesn't have to start with -TESTING, it only needs +to have the format -someidentifier. -SNAPSHOT$N is also very common to use, +however without the numeric suffix, this has specific meaning to gradle, so we +avoid it. Additionally, while the $N we have used in our running example has +matched (e.g. all of the identifiers ended in -TESTING3, this is not required, +so long as you match everything up correctly at the end. This can be tricky, so +I always try to use the same number).

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + diff --git a/book/howtos/locally-published-components-in-firefox-ios.html b/book/howtos/locally-published-components-in-firefox-ios.html new file mode 100644 index 0000000000..35e48bb76d --- /dev/null +++ b/book/howtos/locally-published-components-in-firefox-ios.html @@ -0,0 +1,371 @@ + + + + + + How to use the local development autopublish flow for Firefox iOS - Cross-platform Rust Components + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

How to locally test Swift Package Manager components on Firefox iOS

+
+

This is a guide on testing the Swift Package Manager component locally against a local build of Firefox iOS. For more information on our Swift Package Manager design, read the ADR that introduced it

+
+
+

This guide assumes the component you want to test is already distributed with the rust-components-swift repository, you can read the guide for adding a new component if you would like to distribute a new component.

+
+

The goal for this document is to be able to build a local firefox iOS against a local application-services. On a high level, that requires the following:

+
    +
  1. Build an xcframework in a local checkout of application-services
  2. +
  3. Include the xcframework in a local checkout of rust-components-swift
  4. +
  5. Run the generate script in rust-components-swift using a local checkout of application-services
  6. +
  7. Include the local checkout of rust-components-swift in firefox-ios
  8. +
+

Prerequisites:

+
    +
  1. A local checkout of firefox-ios that is ready to build
  2. +
  3. A local checkout of rust-components-swift
  4. +
  5. A local checkout of application-services that is ready to build for iOS
  6. +
+

Using the automated flow

+

For convenience, there is a script that will do all the necessary steps to configure your local firefox-ios build with a local application-services repository. You do not need to do the manual steps if you follow those steps.

+
    +
  1. +

    Run the following to execute the script, the example below assumes all of firefox-ios, rust-components-swift and application-services are in the same directory. Adjust the paths according to where they are on your filesystem.

    +
    $ cd firefox-ios # This is your local checkout of firefox-ios
    +$ ./rust_components_local.sh -a ../application-services ../rust-components-swift
    +
    +
  2. +
  3. +

    Using Xcode, open Client.xcodeproj in firefox-ios

    +
  4. +
  5. +

    Then, make sure to reset packages cache in Xcode. This forces Xcode to remove any previously cached versions of the Rust components.

    +
      +
    • You can reset package caches by going to File -> Packages -> Reset Package Caches
    • +
    +
  6. +
  7. +

    If this is not the first time you run the script, make sure to also update package versions. This forces Xcode to pull the latest changes in the rust-components-swift branch.

    +
      +
    • You can update package versions by going to File -> Packages -> Update To Latest Package Versions
    • +
    • If this step fails, it's possible that the Reset Package Caches step above left some cruft behind. You can force this step by manually removing ~/Library/Caches/org.swift.swiftpm and ~/Library/Developer/Xcode/DerivedData/Client-{some-long-string}
    • +
    +
  8. +
  9. +

    Once the above steps are done, attempt building firefox ios. If you face problems, feel free to contact us

    +
  10. +
+

Disabling local development

+

The easiest way to disable local development is to simply revert any changes to firefox-ios/Client.xcodeproj/project.pbxproj.

+

However, if there are other changes to the file that you would like to preserve, you can use the same script. To use the same script, you will need to:

+
    +
  1. Know what version of rust-components-swift was used beforehand. You can find this by checking the git diff on firefox-ios/Client.xcodeproj/project.pbxproj.
  2. +
  3. Run: +
    $ ./rust_components_local.sh --disable <VERSION> ../rust-components-swift
    +
    +
  4. +
  5. Then, make sure to reset packages cache in Xcode. This forces Xcode to remove any previously cached versions of the Rust components. +
      +
    • You can reset package caches by going to File -> Packages -> Reset Package Caches
    • +
    +
  6. +
+
+

If you happen to change branches in rust-components-swift, you will need to disable then re-enable local development. The script is not currently smart enough to switch branches. Alternatively, keep the branch in rust-components-swift the same. rust-components-swift serves only as a release surface so there is little use to switching branches and pushing changes to it, unless you are changing something related to the release process.

+
+

Using the manual flow

+

It's important to note the automated flow runs through all the necessary steps in a script, so if possible use the script as it's a tedious manual process

+

However, if the script is failing or you would like to run the manual process for any other reason follow the following steps.

+

Building the xcframework

+

To build the xcframework do the following:

+
    +
  1. In your local checkout of application-services, navigate to megazords/ios-rust/
  2. +
  3. Run the build-xcframework.sh script:
  4. +
+
$ ./build-xcframework.sh
+
+

This will produce a file name MozillaRustComponents.xcframework.zip that contains the following, built for all our target iOS platforms.

+
    +
  • The compiled Rust code for all the crates listed in Cargo.toml as a static library
  • +
  • The C header files and Swift module maps for the components
  • +
+

Include the xcframework in a local checkout of rust-components-swift

+

After you generated the MozillaRustComponents.xcframework.zip in the previous step, do the following to include it in a local checkout of rust-components-swift. The file will be in the megazords/ios-rust directory.

+
    +
  1. Unzip the MozillaRustComponents.xcframework.zip into the rust-components-swift repository: (Assuming you are in the root of the rust-components-swift directory and application-services is a neighbor directory) +
     unzip -o ../application-services/megazords/ios-rust/MozillaRustComponents.xcframework.zip -d .
    +
    +
  2. +
  3. Change the Package.swift's reference to the xcframework to point to the unzipped MozillaRustComponents.xcframework that was created in the previous step. You can do this by uncommenting the following line: +
        path: "./MozillaRustComponents.xcframework"
    +
    +and commenting out the following lines: +
        url: url,
    +    checksum: checksum,
    +
    +
  4. +
+

Run the generation script with a local checkout of application services

+

For this step, run the following script from inside the rust-components-swift repository (assuming that application-services is a neighboring directory to rust-components-swift).

+
./generate.sh ../application-services
+
+

Once that is done, stage and commit the changes the script ran. Xcode can only pick up committed changes.

+

Include the local checkout of rust-components-swift in firefox-ios

+

This is the final step to include your local changes into firefox-ios. Do the following steps:

+
    +
  1. +

    Open Client.xcodeproj in Xcode

    +
  2. +
  3. +

    Navigate to the Swift Packages in Xcode: +Screenshot of where to find the setting for Client

    +
  4. +
  5. +

    Remove the dependency on rust-components-swift as listed on Xcode, you can click the dependency then click the -

    +
  6. +
  7. +

    Add a new swift package by clicking the +:

    +
      +
    1. On the top right, enter the full path to your rust-components-swift checkout, preceded by file://. If you don't know what that is, run pwd in while in rust-components-swift. For example: file:///Users/tarikeshaq/code/rust-components-swift
    2. +
    3. Change the branch to be the checked-out branch of rust-component-swift you have locally. This is what the dialog should look like: +Dialog for including the rust-components-swift package
    4. +
    +
    +

    Note: If Xcode prevents you from adding the dependency to reference a local package, you will need to manually modify the Client.xcodeproj/project.pbxproj and replace every occurrence of https://github.com/mozilla/rust-components-swift with the full path to your local checkout.

    +
    +
      +
    1. Click Add Package
    2. +
    3. Now include the packages you would like to include, choose MozillaAppServices
    4. +
    +
  8. +
  9. +

    Finally, attempt to build firefox-ios, and if all goes well it should launch with your code. If you face problems, feel free to contact us

    +
  10. +
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + diff --git a/book/howtos/locally-published-components-in-focus-ios.html b/book/howtos/locally-published-components-in-focus-ios.html new file mode 100644 index 0000000000..c1ba762dd6 --- /dev/null +++ b/book/howtos/locally-published-components-in-focus-ios.html @@ -0,0 +1,322 @@ + + + + + + How to use the local development flow for Focus for iOS - Cross-platform Rust Components + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

How to locally test Swift Package Manager components on Focus iOS

+
+

This is a guide on testing the Swift Package Manager component locally against a local build of Focus iOS. For more information on our Swift Package Manager design, read the ADR that introduced it

+
+
+

This guide assumes the component you want to test is already distributed with the rust-components-swift repository, you can read the guide for adding a new component if you would like to distribute a new component.

+
+

To test a component locally, you will need to do the following:

+
    +
  1. Build an xcframework in a local checkout of application-services
  2. +
  3. Include the xcframework in a local checkout of rust-components-swift
  4. +
  5. Run the make-tag script in rust-components-swift using a local checkout of application-services
  6. +
  7. Include the local checkout of rust-components-swift in Focus
  8. +
+

Below are more detailed instructions for each step

+

Building the xcframework

+

To build the xcframework do the following:

+
    +
  1. In a local checkout of application-services, navigate to megazords/ios-rust/
  2. +
  3. Run the build-xcframework.sh script:
  4. +
+
$ ./build-xcframework.sh --focus
+
+

This will produce a file name FocusRustComponents.xcframework.zip in the focus directory that contains the following, built for all our target iOS platforms.

+
    +
  • The compiled Rust code for all the crates listed in Cargo.toml as a static library
  • +
  • The C header files and Swift module maps for the components
  • +
+

Include the xcframework in a local checkout of rust-components-swift

+

After you generated the FocusRustComponents.xcframework.zip in the previous step, do the following to include it in a local checkout of rust-components-swift:

+
    +
  1. clone a local checkout of rust-components-swift, not inside the application-services repository: +
    git clone https://github.com/mozilla/rust-components.swift.git
    +
    +
  2. +
  3. Unzip the FocusRustComponents.xcframework.zip into the rust-components-swift repository: (Assuming you are in the root of the rust-components-swift directory and application-services is a neighbor directory) +
     unzip -o ../application-services/megazords/ios-rust/focus/FocusRustComponents.xcframework.zip -d .
    +
    +
  4. +
  5. Change the Package.swift's reference to the xcframework to point to the unzipped FocusRustComponents.xcframework that was created in the previous step. You can do this by uncommenting the following line: +
        path: "./FocusRustComponents.xcframework"
    +
    +and commenting out the following lines: +
        url: focusUrl,
    +    checksum: focusChecksum,
    +
    +
  6. +
+

Run the generation script with a local checkout of application services

+

For this step, run the following script from inside the rust-components-swift repository (assuming that application-services is a neighboring directory to rust-components-swift).

+
./generate.sh ../application-services
+
+

Once that is done, stage and commit the changes the script ran. Xcode can only pick up committed changes.

+

Include the local checkout of rust-components-swift in Focus

+

This is the final step to include your local changes into Focus. Do the following steps:

+
    +
  1. +

    Clone a local checkout of Focus if you haven't already. Make sure you also install the project dependencies, more information in their build instructions

    +
  2. +
  3. +

    Open Blockzilla.xcodeproj in Xcode

    +
  4. +
  5. +

    Navigate to the Swift Packages in Xcode: +Screenshot of where to find the setting for Blockzilla +Screenshot of where to find the package dependencies

    +
  6. +
  7. +

    Remove the dependency on rust-components-swift as listed on Xcode, you can click the dependency then click the -

    +
  8. +
  9. +

    Add a new swift package by clicking the +:

    +
      +
    1. On the top right, enter the full path to your rust-components-swift checkout, preceded by file://. If you don't know what that is, run pwd in while in rust-components-swift. For example: file:///Users/tarikeshaq/code/rust-components-swift
    2. +
    3. Change the branch to be the checked-out branch of rust-component-swift you have locally. This is what the dialog should look like: +Dialog for including the rust-components-swift package
    4. +
    5. Click Add Package
    6. +
    7. Now include the FocusAppServices library.
    8. +
    +
    +

    Note: If Xcode prevents you from adding the dependency to reference a local package, you will need to manually modify the Blockzilla.xcodeproj/project.pbxproj and replace every occurrence of https://github.com/mozilla/rust-components-swift with the full path to your local checkout.

    +
    +
  10. +
  11. +

    Finally, attempt to build focus, and if all goes well it should launch with your code. If you face any problems, feel free to contact us

    +
  12. +
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + diff --git a/book/howtos/releases.html b/book/howtos/releases.html new file mode 100644 index 0000000000..04f0e944af --- /dev/null +++ b/book/howtos/releases.html @@ -0,0 +1,448 @@ + + + + + + Releases - Cross-platform Rust Components + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Application Services Release Process

+

Nightly builds

+

Nightly builds are automatically generated using a taskcluster cron task.

+
    +
  • The results of the latest successful nightly build is listed here: +https://firefox-ci-tc.services.mozilla.com/tasks/index/project.application-services.v2.nightly/latest
  • +
  • The latest nightly decision task should be listed here: +https://firefox-ci-tc.services.mozilla.com/tasks/index/project.application-services.v2.branch.main.latest.taskgraph/decision-nightly
  • +
  • If you don't see a decision task from the day before, then contact releng. It's likely that the cron decision task is broken.
  • +
+

Release builds

+

Release builds are generated from the release-vXXX branches and triggered in Ship-it

+
    +
  • Whenever a commit is pushed to a release branch, we build candidate artifacts. These artifacts are +shippable -- if we decide that the release is ready, they just need to be copied to the correct +location.
  • +
  • The push phase of release-promotion copies the candidate to a staging location where they can +be tested.
  • +
  • The ship phase of release-promotion copies the candidate to their final, published, location.
  • +
+

[Release management] Creating a new release

+
+

This part is 100% covered by the Release Management team. The dev team should not perform these steps.

+
+

On Merge Day we take a snapshot of the current main, and prepare a release. See Firefox Release Calendar.

+
    +
  1. +

    Create a branch name with the format releases-v[release_version] off of the main branch (for example, release-v118) through the GitHub UI. +[release_version] should follow the Firefox release number. See Firefox Release Calendar.

    +
  2. +
  3. +

    Create a PR against the release branch that updates version.txt and updates the CHANGELOG.md as follows:

    +
  4. +
+
    +
  • In version.txt, update the version from [release_version].0a1 to [release_version].0.
  • +
+
diff --git a/version.txt b/version.txt
+--- a/version.txt
++++ b/version.txt
+@@ -1 +1 @@
+-118.0a1
++118.0
+
+
    +
  • In CHANGELOG.md, change In progress to _YYYY-MM-DD_ to match the Merge Day date and add a URL to the release version change log.
  • +
+
diff --git a/CHANGELOG.md b/CHANGELOG.md
+index 7f2c07a1a8..06688fdcab 100644
+--- a/CHANGELOG.md
++++ b/CHANGELOG.md
+@@ -1,8 +1,7 @@
+-# v118.0 (In progress)
+-
+-[Full Changelog](In progress)
++# v118.0 (_2023-08-28_)
+ 
+ ## General
++
+ ### 🦊 What's Changed 🦊
+ 
+ - Backward-incompatible changes to the Suggest database schema to accommodate custom details for providers ([#5745](https://github.com/mozilla/application-services/pull/5745)) and future suggestion types ([#5766](https://github.com/mozilla/application-services/pull/5766)). This only affects prototyping, because we aren't consuming Suggest in any of our products yet.
+@@ -16,7 +15,6 @@
+ - The Remote Settings client has a new `Client::get_records_with_options()` method ([#5764](https://github.com/mozilla/application-services/pull/5764)). This is for Rust consumers only; it's not exposed to Swift or Kotlin.
+ - `RemoteSettingsRecord` objects have a new `deleted` property that indicates if the record is a tombstone ([#5764](https://github.com/mozilla/application-services/pull/5764)).
+ 
+-
+ ## Rust log forwarder
+ ### 🦊 What's Changed 🦊
+ 
+@@ -34,6 +32,8 @@
+ 
+ - Removed previously deprecated commands `experimenter`, `ios`, `android`, `intermediate-repr` ([#5784](https://github.com/mozilla/application-services/pull/5784)).
+ 
++[Full Changelog](https://github.com/mozilla/application-services/compare/v117.0...v118.0)
++
+ # v117.0 (_2023-07-31_)
+
+
    +
  • Create a commit named 'Cut release v[release_version].0` and a PR for this change.
  • +
  • See example PR
  • +
+
    +
  1. Create a PR against the main branch that updates version.txt and updates the CHANGELOG.md as follows:
  2. +
+
    +
  • In version.txt, update the version from [release_version].0a1 to [next_release_version].0a1.
  • +
+
diff --git a/version.txt b/version.txt
+--- a/version.txt
++++ b/version.txt
+@@ -1 +1@@
+-118.0a1
++119.0a1
+
+
    +
  • In CHANGELOG.md, change the in progress version from [release_version].0 to [next_release_version].0, add a header for the previous release version, and add a URL to the previous release version change log.
  • +
+
diff --git a/CHANGELOG.md b/CHANGELOG.md
+--- a/CHANGELOG.md
++++ b/CHANGELOG.md
+@@ -1,8 +1,7 @@
+-# v118.0 (In progress)
++# v119.0 (In progress)
+
+[Full Changelog](In progress)
+
++# v118.0 (_2023-08-28_)
+@@ -34,6 +36,8 @@
++[Full Changelog](https://github.com/mozilla/application-services/compare/v117.0...v118.0)
++
+# v117.0 (_2023-07-31_)
+
+
    +
  • Create a commit named 'Start release v[next_release_version].0` and a PR for this change.
  • +
  • See example PR
  • +
+
    +
  1. Once all of the above PRs have landed, create a new Application Services release in Ship-It.
  2. +
+
    +
  • Promote and Ship the release.
  • +
+
    +
  1. +

    Tag the release in the Application Services repo.

    +
  2. +
  3. +

    Inform the Application Services team to cut a release of rust-components-swift

    +
  4. +
+
    +
  • The team will tag the repo and let you know the git hash to use when updating the consumer applications
  • +
+
    +
  1. Update consumer applications
  2. +
+ +

[Release management] Creating a new release via scripts:

+
    +
  1. Run pip3 install -r automation/requirements.txt to install the required Python packages.
  2. +
  3. Run the automation/prepare-release.py script. This should:
  4. +
+
    +
  • Create a new branch named release-vXXX
  • +
  • Create a PR against that branch that updates version.txt like this:
  • +
+
diff --git a/version.txt b/version.txt
+index 8cd923873..6482018e0 100644
+--- a/version.txt
++++ b/version.txt
+@@ -1,4 +1,4 @@
+-114.0a1
++114.0
+
+
    +
  • Create a PR on main that starts a new CHANGELOG header.
  • +
+
    +
  1. Tag the release with automation/tag-release.py [major-version-number]
  2. +
+

Cutting patch releases for uplifted changes

+

If you want to uplift changes into the a previous release:

+
    +
  • Make sure the changes are present in main and have been thoroughly tested.
  • +
  • Checkout the release-vXXX branch, where XXX is the major version number.
  • +
  • Bump the version in version.txt
  • +
  • Cherry-pick any commits that you want to uplift
  • +
  • Create a PR for the changes
  • +
  • Trigger release-promotion for the branch once the PRs are approved, merged, and CI has completed
  • +
+

What gets built in a release?

+

We build several artifacts for both nightlies and releases:

+
    +
  • nightly.json / release.json. This is a JSON file containing metadata from successful +builds. The metadata for the latest successful build can be found from a taskcluster index: +https://firefox-ci-tc.services.mozilla.com/api/index/v1/task/project.application-services.v2.release.latest/artifacts/public%2Fbuild%2Frelease.json +The JSON file contains: +
      +
    • The version number for the nightly/release
    • +
    • The git commit ID
    • +
    • The maven channel for Kotlin packages: +
        +
      • maven-production: https://maven.mozilla.org/?prefix=maven2/org/mozilla/appservices/
      • +
      • maven-nightly-production: https://maven.mozilla.org/?prefix=maven2/org/mozilla/appservices/nightly/
      • +
      • maven-staging: https://maven-default.stage.mozaws.net/?prefix=maven2/org/mozilla/appservices/
      • +
      • maven-nightly-staging: https://maven-default.stage.mozaws.net/?prefix=maven2/org/mozilla/appservices/nightly/
      • +
      +
    • +
    • Links to nimbus-fml.*: used to build Firefox/Focus on Android and iOS
    • +
    • Links to *RustComponentsSwift.xcframework.zip: XCFramework archives used to build Firefox/Focus on iOS
    • +
    • Link to swift-components.tar.xz: UniFFI-generated swift files which get extracted into the +rust-components-swift repository for each release.
    • +
    +
  • +
+

Nightly builds

+

For nightly builds, consumers get the artifacts directly from the taskcluster.

+
    +
  • For, firefox-android, the nightlies are handled by relbot
  • +
  • For, firefox-ios, the nightlies are consumed by rust-components-swift. rust-components-swift makes a github release, which is picked up by a Github action in firefox-ios
  • +
+

Release promotion

+

For real releases, we use the taskcluster release-promotion action. Release promotion happens in two phases:

+
    +
  • promote copies the artifacts from taskcluster and moves them to a staging area. This +allows for testing the consumer apps with the artifacts.
  • +
  • ship copies the artifacts from the staging area to archive.mozilla.org, which serves as +their permanent storage area.
  • +
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + diff --git a/book/howtos/smoke-testing-app-services.html b/book/howtos/smoke-testing-app-services.html new file mode 100644 index 0000000000..6cce6a9338 --- /dev/null +++ b/book/howtos/smoke-testing-app-services.html @@ -0,0 +1,250 @@ + + + + + + How to integration (smoke) test application-services - Cross-platform Rust Components + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Smoke testing Application Services against end-user apps

+

This is a great way of finding integration bugs with application-services. +The testing can be done manually using substitution scripts, but we also have scripts that will do the smoke-testing for you.

+

Dependencies

+

Run pip3 install -r automation/requirements.txt to install the required Python packages.

+

Android Components

+

The automation/smoke-test-android-components.py script will clone (or use a local version) of +android-components and run a subset of its tests against the current application-services worktree. +It tries to only run tests that might be relevant to application-services functionality.

+

Fenix

+

The automation/smoke-test-fenix.py script will clone (or use a local version) of Fenix and +run tests against the current application-services worktree.

+

Firefox iOS

+

The automation/smoke-test-fxios.py script will clone (or use a local version) of Firefox iOS and +run tests against the current application-services worktree.

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + diff --git a/book/howtos/testing-a-rust-component.html b/book/howtos/testing-a-rust-component.html new file mode 100644 index 0000000000..9edec5139e --- /dev/null +++ b/book/howtos/testing-a-rust-component.html @@ -0,0 +1,333 @@ + + + + + + How to test Rust Components - Cross-platform Rust Components + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Guide to Testing a Rust Component

+

This document gives a high-level overview of how we test components in application-services. +It will be useful to you if you're adding a new component, or working on increasing the test +coverage of an existing component.

+

If you are only interested in running the existing test suite, please consult the +contributor docs and the tests.py script.

+

Unit and Functional Tests

+

Rust code

+

Since the core implementations of our components live in rust, so does the core of our testing strategy.

+

Each rust component should be accompanied by a suite of unit tests, following the guidelines for writing +tests from the Rust +Book. +Some additional tips:

+
    +
  • +

    Where possible, it's better use use the Rust typesystem to make bugs impossible than to write +tests to assert that they don't occur in practice. But given that the ultimate consumers of our +code are not in Rust, that's sometimes not possible. The best idiomatic Rust API for a feature +is not necessarily the best API for consuming it over an FFI boundary.

    +
  • +
  • +

    Rust's builtin assertion macros are sparse; we use the more_asserts +for some additional helpers.

    +
  • +
  • +

    Rust's strict typing can make test mocks difficult. If there's something you need to mock out in tests, +make it a Trait and use the mockiato crate to mock it.

    +
  • +
+

The Rust tests for a component should be runnable via cargo test.

+

FFI Layer code

+

We are currently using uniffi to generate most ((and soon all!) of our FFI code and thus the FFI code itself does not need to be extensively tested.

+

Kotlin code

+

The Kotlin wrapper code for a component should have its own test suite, which should follow the general guidelines for +testing Android code in Mozilla projects. +In practice that means we use +JUnit +as the test framework and +Robolectric +to provide implementations of Android-specific APIs.

+

The Kotlin tests for a component should be runnable via ./gradlew <component>:test.

+

The tests at this layer are designed to ensure that the API binding code is working as intended, +and should not repeat tests for functionality that is already well tested at the Rust level. +But given that the Kotlin bindings involve a non-trivial amount of hand-written boilerplate code, +it's important to exercise that code throughly.

+

One complication with running Kotlin tests is that the code needs to run on your local development machine, +but the Kotlin code's native dependencies are typically compiled and packaged for Android devices. The +tests need to ensure that an appropriate version of JNA and of the compiled Rust code is available in +their library search path at runtime. Our build.gradle files contain a collection of hackery that ensures +this, which should be copied into any new components.

+

The majority of our Kotlin bindings are autogenerated using uniffi and do not need extensive testing.

+

Swift code

+

The Swift wrapper code for a component should have its own test suite, using Apple's +Xcode unittest framework.

+

Due to the way that all rust components need to be compiled together into a single "megazord" +framework, this entire repository is a single Xcode project. The Swift tests for each component +thus need to live under megazords/ios-rust/MozillaTestServicesTests/ rather than in the directory +for the corresponding component. (XXX TODO: is this true? it would be nice to find a way to avoid having +them live separately because it makes them easy to overlook).

+

The tests at this layer are designed to ensure that the API binding code is working as intended, +and should not repeat tests for functionality that is already well tested at the Rust level. +But given that the Swift bindings involve a non-trivial amount of hand-written boilerplate code, +it's important to exercise that code thoroughly.

+

The majority of our Swift bindings are autogenerated using uniffi and do not need extensive testing.

+

Integration tests

+

End-to-end Sync Tests

+

⚠️ Those tests were disabled because of how flakey the stage server was. See #3909 ⚠️

+

The testing/sync-test directory contains a test harness for running sync-related +Rust components against a live Firefox Sync infrastructure, so that we can verifying the functionality +end-to-end.

+

Each component that implements a sync engine should have a corresponding suite of tests in this directory.

+
    +
  • XXX TODO: places doesn't.
  • +
  • XXX TODO: send-tab doesn't (not technically a sync engine, but still, it's related)
  • +
  • XXX TODO: sync-manager doesn't
  • +
+

Android Components Test Suite

+

It's important that changes in application-services are tested against upstream consumer code in the +android-components repo. This is currently +a manual process involving:

+
    +
  • Configuring your local checkout of android-components to use your local application-services +build.
  • +
  • Running the android-components test suite via ./gradle test.
  • +
  • Manually building and running the android-components sample apps to verify that they're still working.
  • +
+

Ideally some or all of this would be automated and run in CI, but we have not yet invested in such automation.

+

Test Coverage

+

We currently have code coverage reporting on Github using codecov. However, our code coverage does not tell us how much more coverage is caused by our consumers' tests.

+

Ideas for Improvement

+
    +
  • ASan, Memsan, and maybe other sanitizer checks, especially around the points where we cross FFI boundaries.
  • +
  • General-purpose fuzzing, such as via https://github.com/jakubadamw/arbitrary-model-tests
  • +
  • We could consider making a mocking backend for viaduct, which would also be mockable from Kotlin/Swift.
  • +
  • Add more end-to-end integration tests!
  • +
  • Live device tests, e.g. actual Fenixes running in an emulator and syncing to each other.
  • +
  • Run consumer integration tests in CI against main.
  • +
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + diff --git a/book/howtos/uniffi-object-destruction-on-kotlin.html b/book/howtos/uniffi-object-destruction-on-kotlin.html new file mode 100644 index 0000000000..3c74ce0898 --- /dev/null +++ b/book/howtos/uniffi-object-destruction-on-kotlin.html @@ -0,0 +1,255 @@ + + + + + + UniFFI Object Destruction on Kotlin - Cross-platform Rust Components + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

UniFFI object destruction on Kotlin

+

UniFFI supports interface objects, which are implemented by Boxing a Rust object and sending the raw pointer to the foreign code. Once the objects are no longer in use, the foreign code needs to destroy the object and free the underlying resources.

+

This is slightly tricky on Kotlin. The prevailing Java wisdom is to use explicit destructors and avoid using finalizers for destruction, which means we can't simply rely on the garbage collector to free the pointer. The wisdom seems simple to follow, but in practice it can be difficult to know how to apply it to specific situations. This document examines provides guidelines for handling UniFFI objects.

+

You can create objects in a function if you also destroy them there

+

The simplest way to get destruction right is to create an object and destroy it in the same function. The use function makes this really easy:

+
SomeUniFFIObject()
+  .use { obj ->
+      obj.doSomething()
+      obj.doSomethingElse()
+  }
+
+

You can create and store objects in singletons

+

If we are okay with UniFFI objects living for the entire application lifetime, then they can be stored in singletons. This is how we handle our database connections, for example SyncableLoginsStorage and PlacesReaderConnection.

+

You can create and store objects in an class, then destroy them in a corresponding lifecycle method

+

UniFFI objects can stored in classes like the Android Fragment class that have a defined lifecycle, with methods called at different stages. Classes can construct UniFFI objects in one of the lifecycle methods, then destroy it in the corresponding one. For example, creating an object in Fragment.onCreate and destroying it in Fragment.onDestroy().

+

You can share objects

+

Several classes can hold references to an object, as long as (exactly) one class is responsible for managing it and destroying it when it's not used. A good example is the GeckoLoginStorageDelegate. The LoginStorage is initialized and managed by another object, and GeckoLoginStorageDelegate is passed a (lazy) reference to it.

+

Care should be taken to ensure that once the managing class destroys the object, no other class attempts to use it. If they do, then the generate code will raise an IllegalStateException. This clearly should be avoided, although it won't result in memory corruption.

+

Destruction may not always happen

+

Destructors may not run when a process is killed, which can easily happen on Android. This is especially true of lifecycle methods. This is normally fine, since the OS will close resources like file handles and network connections on its own. However, be aware that custom code in the destructor may not run.

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + diff --git a/book/howtos/upgrading-nss-guide.html b/book/howtos/upgrading-nss-guide.html new file mode 100644 index 0000000000..6b2d669d2f --- /dev/null +++ b/book/howtos/upgrading-nss-guide.html @@ -0,0 +1,289 @@ + + + + + + How to upgrade NSS - Cross-platform Rust Components + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Guide to upgrading NSS

+

Our components rely on cryptographic primitives provided by NSS. +Every month or so, a new version of NSS is published and we should try to keep our version as up-to-date as possible.

+

Because it makes unit testing easier on Android, and helps startup performance on iOS, we compile NSS ourselves and link to it statically. Note that NSS is mainly used by Mozilla as a dynamic library and the NSS project is missing related CI jobs (iOS builds, windows cross-compile builds etc.) so you should expect breakage when updating the library (hence this guide).

+
+

Updating the Version

+

The build code is located in the libs/ folder.

+

The version string is located in the beginning of build-all.sh.

+

For most NSS upgrades, you'll need to bump the version number in this file and update the downloaded archive checksum. Then follow the steps for Updating the cross-compiled NSS Artifacts below. The actual build invocations are located in platform-specific script files (e.g. build-nss-ios.sh) but usually don't require any changes.

+

To test out updating NSS version:

+
    +
  • Ensure you've bumped the NSS in build-all.sh
  • +
  • Clear any old NSS build artifacts: rm -rf ./libs/desktop && cargo clean
  • +
  • Install the updates version: ./libs/verify-desktop-environment.sh
  • +
  • Try it out: cargo test
  • +
+
+

Updating the Cross-Compiled NSS Artifacts

+

We use a Linux TC worker for cross-compiling NSS for iOS, Android and Linux desktop machines. However, due to the complexity of the NSS build process, there is no easy way for cross-compiling MacOS and Windows -- so we currently use pre-built artifacts for MacOS desktop machines (ref #5210).

+
    +
  1. Look for the tagged version from the NSS CI +
    +

    usually a description with something like Added tag NSS_3_90_RTM

    +
    +
  2. +
  3. Select the build for the following system(s) (first task with the title "B"): +
      +
    • For Intel MacOS: mac opt-static
    • +
    +
  4. +
  5. Update taskcluster/ci/fetch/kind.yml, specifically nss-artifact task to the appropriate url and checksum and size +
    +

    Note: To get the checksum, you can run shasum -a 256 {path-to-artifact} or you can make a PR and see the output of the failed log.

    +
    +
  6. +
  7. Update the SHA256 value for darwin cross-compile in libs/build-nss-desktop.sh to the same checksum as above.
  8. +
  9. Open a pull request with these changes and it should update the Taskcluster artifact
  10. +
+
+

Exposing new functions

+

If the new version of NSS comes with new functions that you want to expose, you will need to:

+
    +
  • Add low-level bindings for those functions in the nss_sys crate; follow the instructions in +README for that crate.
  • +
  • Expose a safe wrapper API for the functions from the nss crate;
  • +
  • Expose a convenient high-level API for the functions from the rc_crypto crate;
  • +
+

Tips for Fixing Bustage

+

On top of the primitives provided by NSS, we have built a safe Rust wrapper named rc_crypto that links to NSS and makes these cryptographic primitives available to our components.

+

The linkage is done by the nss_build_common crate. Note that it supports a is_gecko feature to link to NSS dynamically on Desktop.

+

Because the NSS static build process does not output a single .a file (it would be great if it did), this file must describe for each architecture which modules should we link against. It is mostly a duplication of logic from the NSS gyp build files. Note that this logic is also duplicated in our NSS lib build steps (e.g. build-nss-desktop.sh).

+

One of the most common build failures we get when upgrading NSS comes from NSS adding new vectorized/asm versions of a crypto algorithm for a specific architecture in order to improve performance. This new optimized code gets implemented as a new gyp target/module that is emitted only for the supported architectures. +When we upgrade our copy of NSS we notice the linking step failing on CI jobs because of undefined symbols.

+

This PR shows how we update nss_common_build and the build scripts to accommodate for these new modules. Checking the changelog for any suspect commit relating to hardware acceleration is rumored to help.

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + diff --git a/book/howtos/vendoring-into-mozilla-central.html b/book/howtos/vendoring-into-mozilla-central.html new file mode 100644 index 0000000000..fbe8c4abbd --- /dev/null +++ b/book/howtos/vendoring-into-mozilla-central.html @@ -0,0 +1,338 @@ + + + + + + How to vendor application-services into mozilla-central - Cross-platform Rust Components + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Vendoring Application Services into mozilla-central

+

Some of these components are used in mozilla-central. +This document describes how to update existing components or add new components.

+

The general process for vendoring rust code into mozilla-central has its own +documentation - +please make sure you read that before continuing.

+

When to vendor

+

We want to keep our versions in moz-central relatively up-to-date, but it takes some manual effort +to do. The main possibility of breakage is from a dependency mismatch, so our current vendoring +policy is:

+
    +
  • Whenever a 3rd-party dependency is added or updated, the dev who made the change is responsible +for vendoring.
  • +
  • At the start of the release cycle the +triage owner is response for vendoring.
  • +
+

Updating existing components.

+

To update components which are already in mozilla-central, follow these steps:

+
    +
  1. +

    Ensure your mozilla-central build environment is setup correctly to make +"non-artifact" builds - check you can get a full working build before +starting this process.

    +
  2. +
  3. +

    Run ./tools/update-moz-central-vendoring.py [path-to-moz-central] from the application-services +root directory.

    +
  4. +
  5. +

    If this generates errors regarding duplicate crates, you will enter a world +of pain, and probably need to ask for advice from the application-services +team, and/or the #build channel on matrix.

    +
  6. +
  7. +

    Run ./mach cargo vet to check if there any any new dependencies that need to be vetted. If +there are ask for advice from the application-services team.

    +
  8. +
  9. +

    Build and test your tree. Ideally make a try run.

    +
  10. +
  11. +

    Put your patch up to phabricator, requesting review from, at least, someone +on the application-services team and one of the "build peers" - asking on +#build on matrix for a suitable +reviewer might help. Alternatively, try and find the bug which made the +most recent update and ask the same reviewer in that patch.

    +
  12. +
  13. +

    Profit!

    +
  14. +
+

Adding a new component

+

Follow the Uniffi documentation on mozilla-central to understand where you'll need to add your crate path and UDL. In general:

+
    +
  • The consuming component will specify the dependency as a nominal "version 0.1"
  • +
  • The top-level Cargo.toml will override that dependency with a specific git +revision.
  • +
+

For example, consider the webext-storage crate:

+ +

Adding a new component implies there will be related mozilla-central changes +which leverage it. The best practice here is to land both the vendoring of the +new component and the related mozilla-central changes in the same bug, but in +different phabricator patches. As noted above, the best-practice is that all +application-services components are on the same revision, so adding a new +component implies you will generally also be updating all the existing +components.

+

For an example of a recently added component, the tabs was recently added to mozilla-central with uniffi and shows a general process to follow.

+

Vendoring an unreleased version for testing purposes

+

Sometimes you will need to make changes in application-services and in mozilla-central +simultaneously - for example, you may need to add new features or capabilities +to a component, and matching changes in mozilla-central to use that new feature.

+

In that scenario, you don't want to check your changes in and re-vendor as you +iterate - it would be far better to use a local checkout of application-services +with uncommitted changes with your mozilla-central tree which also has uncommited +changes.

+

To do this, you can edit the top-level Cargo.toml to specify a path. Note +however that in this scenario, you need to specify the path to the +individual component rather than to the top-level of the repo.

+

For example, you might end up with something like:

+
# application-services overrides to make updating them all simpler.
+interrupt-support = { path = "../application-services/components/support/interrupt" }
+sql-support = { path = "../application-services/components/support/sql" }
+sync15-traits = { path = "../application-services/components/support/sync15-traits" }
+viaduct = { path = "../application-services/components/viaduct" }
+webext-storage = { path = "../application-services/components/webext-storage" }
+
+

Note that when you first do this, it will still be necessary to run +./mach vendor rust and to re-build.

+

After you make a change to the local repository, you do not need to run +./mach vendor rust, but you do still obviously need to rebuild.

+

Once you are happy with all the changes, you would:

+
    +
  • Open a PR up in application-services and land your changes there.
  • +
  • Follow the process above to re-vendor your new changes, and in that same +bug (although not necessarily the same phabricator patch), include the other +mozilla-central changes which rely on the new version.
  • +
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + diff --git a/book/index.html b/book/index.html new file mode 100644 index 0000000000..f7bf13ecf1 --- /dev/null +++ b/book/index.html @@ -0,0 +1,242 @@ + + + + + + Application Services Rust Components - Cross-platform Rust Components + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Application Services Rust Components

+

Application Services is collection of Rust Components. The components are used to enable Firefox, and related applications to integrate with Firefox accounts, sync and enable experimentation. Each component is built using a core of shared code written in Rust, wrapped with native language bindings for different platforms.

+

Contact us

+

To contact the Application Services team you can:

+ +

The source code is available on GitHub.

+

License

+

The Application Services Source Code is subject to the terms of the Mozilla Public License v2.0. +You can obtain a copy of the MPL at https://mozilla.org/MPL/2.0/.

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + diff --git a/book/logging.html b/book/logging.html new file mode 100644 index 0000000000..d65f7345a0 --- /dev/null +++ b/book/logging.html @@ -0,0 +1,257 @@ + + + + + + Logging - Cross-platform Rust Components + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Application Services Logging

+

When writing code in application-services, code implemented in Rust, Kotlin, +Java, or Swift might have to write debug logs. To do so, one should generally +log using the normal logging facilities for the language. Where the logs go +depends on the application which is embedding the components.

+

Accessing logs when running Fenix

+

On android, logs currently go to logcat. (This may change in the future.) +Android Studio can be used to view the logcat logs; connect the device over USB +and view the Logcat tab at the bottom of Android Studio. Check to make sure you +have the right device selected at the top left of the Logcat pane, and the +correct process to the right of that. One trick to avoid having to select the +correct process (as there are main and content processes) is to choose "No +Filters" from the menu on the top right of the Logcat pane. Then, use the search +box to search for the log messages you are trying to find.

+

There are also many other utilities, command line and graphical, that can be +used to view logcat logs from a connected android device in a more flexible +manner.

+

Changing the loglevel in Fenix

+

If you need more verbose logging, after the call to RustLog.enable() in +FenixApplication, you may call RustLog.setMaxLevel(Log.Priority.DEBUG, true).

+

Accessing logs when running iOS

+

[TODO]

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + diff --git a/book/mark.min.js b/book/mark.min.js new file mode 100644 index 0000000000..1636231883 --- /dev/null +++ b/book/mark.min.js @@ -0,0 +1,7 @@ +/*!*************************************************** +* mark.js v8.11.1 +* https://markjs.io/ +* Copyright (c) 2014–2018, Julian Kühnel +* Released under the MIT license https://git.io/vwTVl +*****************************************************/ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.Mark=t()}(this,function(){"use strict";var e="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},t=function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")},n=function(){function e(e,t){for(var n=0;n1&&void 0!==arguments[1])||arguments[1],i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:[],o=arguments.length>3&&void 0!==arguments[3]?arguments[3]:5e3;t(this,e),this.ctx=n,this.iframes=r,this.exclude=i,this.iframesTimeout=o}return n(e,[{key:"getContexts",value:function(){var e=[];return(void 0!==this.ctx&&this.ctx?NodeList.prototype.isPrototypeOf(this.ctx)?Array.prototype.slice.call(this.ctx):Array.isArray(this.ctx)?this.ctx:"string"==typeof this.ctx?Array.prototype.slice.call(document.querySelectorAll(this.ctx)):[this.ctx]:[]).forEach(function(t){var n=e.filter(function(e){return e.contains(t)}).length>0;-1!==e.indexOf(t)||n||e.push(t)}),e}},{key:"getIframeContents",value:function(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:function(){},r=void 0;try{var i=e.contentWindow;if(r=i.document,!i||!r)throw new Error("iframe inaccessible")}catch(e){n()}r&&t(r)}},{key:"isIframeBlank",value:function(e){var t="about:blank",n=e.getAttribute("src").trim();return e.contentWindow.location.href===t&&n!==t&&n}},{key:"observeIframeLoad",value:function(e,t,n){var r=this,i=!1,o=null,a=function a(){if(!i){i=!0,clearTimeout(o);try{r.isIframeBlank(e)||(e.removeEventListener("load",a),r.getIframeContents(e,t,n))}catch(e){n()}}};e.addEventListener("load",a),o=setTimeout(a,this.iframesTimeout)}},{key:"onIframeReady",value:function(e,t,n){try{"complete"===e.contentWindow.document.readyState?this.isIframeBlank(e)?this.observeIframeLoad(e,t,n):this.getIframeContents(e,t,n):this.observeIframeLoad(e,t,n)}catch(e){n()}}},{key:"waitForIframes",value:function(e,t){var n=this,r=0;this.forEachIframe(e,function(){return!0},function(e){r++,n.waitForIframes(e.querySelector("html"),function(){--r||t()})},function(e){e||t()})}},{key:"forEachIframe",value:function(t,n,r){var i=this,o=arguments.length>3&&void 0!==arguments[3]?arguments[3]:function(){},a=t.querySelectorAll("iframe"),s=a.length,c=0;a=Array.prototype.slice.call(a);var u=function(){--s<=0&&o(c)};s||u(),a.forEach(function(t){e.matches(t,i.exclude)?u():i.onIframeReady(t,function(e){n(t)&&(c++,r(e)),u()},u)})}},{key:"createIterator",value:function(e,t,n){return document.createNodeIterator(e,t,n,!1)}},{key:"createInstanceOnIframe",value:function(t){return new e(t.querySelector("html"),this.iframes)}},{key:"compareNodeIframe",value:function(e,t,n){if(e.compareDocumentPosition(n)&Node.DOCUMENT_POSITION_PRECEDING){if(null===t)return!0;if(t.compareDocumentPosition(n)&Node.DOCUMENT_POSITION_FOLLOWING)return!0}return!1}},{key:"getIteratorNode",value:function(e){var t=e.previousNode();return{prevNode:t,node:null===t?e.nextNode():e.nextNode()&&e.nextNode()}}},{key:"checkIframeFilter",value:function(e,t,n,r){var i=!1,o=!1;return r.forEach(function(e,t){e.val===n&&(i=t,o=e.handled)}),this.compareNodeIframe(e,t,n)?(!1!==i||o?!1===i||o||(r[i].handled=!0):r.push({val:n,handled:!0}),!0):(!1===i&&r.push({val:n,handled:!1}),!1)}},{key:"handleOpenIframes",value:function(e,t,n,r){var i=this;e.forEach(function(e){e.handled||i.getIframeContents(e.val,function(e){i.createInstanceOnIframe(e).forEachNode(t,n,r)})})}},{key:"iterateThroughNodes",value:function(e,t,n,r,i){for(var o,a=this,s=this.createIterator(t,e,r),c=[],u=[],l=void 0,h=void 0;void 0,o=a.getIteratorNode(s),h=o.prevNode,l=o.node;)this.iframes&&this.forEachIframe(t,function(e){return a.checkIframeFilter(l,h,e,c)},function(t){a.createInstanceOnIframe(t).forEachNode(e,function(e){return u.push(e)},r)}),u.push(l);u.forEach(function(e){n(e)}),this.iframes&&this.handleOpenIframes(c,e,n,r),i()}},{key:"forEachNode",value:function(e,t,n){var r=this,i=arguments.length>3&&void 0!==arguments[3]?arguments[3]:function(){},o=this.getContexts(),a=o.length;a||i(),o.forEach(function(o){var s=function(){r.iterateThroughNodes(e,o,t,n,function(){--a<=0&&i()})};r.iframes?r.waitForIframes(o,s):s()})}}],[{key:"matches",value:function(e,t){var n="string"==typeof t?[t]:t,r=e.matches||e.matchesSelector||e.msMatchesSelector||e.mozMatchesSelector||e.oMatchesSelector||e.webkitMatchesSelector;if(r){var i=!1;return n.every(function(t){return!r.call(e,t)||(i=!0,!1)}),i}return!1}}]),e}(),o=function(){function e(n){t(this,e),this.opt=r({},{diacritics:!0,synonyms:{},accuracy:"partially",caseSensitive:!1,ignoreJoiners:!1,ignorePunctuation:[],wildcards:"disabled"},n)}return n(e,[{key:"create",value:function(e){return"disabled"!==this.opt.wildcards&&(e=this.setupWildcardsRegExp(e)),e=this.escapeStr(e),Object.keys(this.opt.synonyms).length&&(e=this.createSynonymsRegExp(e)),(this.opt.ignoreJoiners||this.opt.ignorePunctuation.length)&&(e=this.setupIgnoreJoinersRegExp(e)),this.opt.diacritics&&(e=this.createDiacriticsRegExp(e)),e=this.createMergedBlanksRegExp(e),(this.opt.ignoreJoiners||this.opt.ignorePunctuation.length)&&(e=this.createJoinersRegExp(e)),"disabled"!==this.opt.wildcards&&(e=this.createWildcardsRegExp(e)),e=this.createAccuracyRegExp(e),new RegExp(e,"gm"+(this.opt.caseSensitive?"":"i"))}},{key:"escapeStr",value:function(e){return e.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&")}},{key:"createSynonymsRegExp",value:function(e){var t=this.opt.synonyms,n=this.opt.caseSensitive?"":"i",r=this.opt.ignoreJoiners||this.opt.ignorePunctuation.length?"\0":"";for(var i in t)if(t.hasOwnProperty(i)){var o=t[i],a="disabled"!==this.opt.wildcards?this.setupWildcardsRegExp(i):this.escapeStr(i),s="disabled"!==this.opt.wildcards?this.setupWildcardsRegExp(o):this.escapeStr(o);""!==a&&""!==s&&(e=e.replace(new RegExp("("+this.escapeStr(a)+"|"+this.escapeStr(s)+")","gm"+n),r+"("+this.processSynonyms(a)+"|"+this.processSynonyms(s)+")"+r))}return e}},{key:"processSynonyms",value:function(e){return(this.opt.ignoreJoiners||this.opt.ignorePunctuation.length)&&(e=this.setupIgnoreJoinersRegExp(e)),e}},{key:"setupWildcardsRegExp",value:function(e){return(e=e.replace(/(?:\\)*\?/g,function(e){return"\\"===e.charAt(0)?"?":""})).replace(/(?:\\)*\*/g,function(e){return"\\"===e.charAt(0)?"*":""})}},{key:"createWildcardsRegExp",value:function(e){var t="withSpaces"===this.opt.wildcards;return e.replace(/\u0001/g,t?"[\\S\\s]?":"\\S?").replace(/\u0002/g,t?"[\\S\\s]*?":"\\S*")}},{key:"setupIgnoreJoinersRegExp",value:function(e){return e.replace(/[^(|)\\]/g,function(e,t,n){var r=n.charAt(t+1);return/[(|)\\]/.test(r)||""===r?e:e+"\0"})}},{key:"createJoinersRegExp",value:function(e){var t=[],n=this.opt.ignorePunctuation;return Array.isArray(n)&&n.length&&t.push(this.escapeStr(n.join(""))),this.opt.ignoreJoiners&&t.push("\\u00ad\\u200b\\u200c\\u200d"),t.length?e.split(/\u0000+/).join("["+t.join("")+"]*"):e}},{key:"createDiacriticsRegExp",value:function(e){var t=this.opt.caseSensitive?"":"i",n=this.opt.caseSensitive?["aàáảãạăằắẳẵặâầấẩẫậäåāą","AÀÁẢÃẠĂẰẮẲẴẶÂẦẤẨẪẬÄÅĀĄ","cçćč","CÇĆČ","dđď","DĐĎ","eèéẻẽẹêềếểễệëěēę","EÈÉẺẼẸÊỀẾỂỄỆËĚĒĘ","iìíỉĩịîïī","IÌÍỈĨỊÎÏĪ","lł","LŁ","nñňń","NÑŇŃ","oòóỏõọôồốổỗộơởỡớờợöøō","OÒÓỎÕỌÔỒỐỔỖỘƠỞỠỚỜỢÖØŌ","rř","RŘ","sšśșş","SŠŚȘŞ","tťțţ","TŤȚŢ","uùúủũụưừứửữựûüůū","UÙÚỦŨỤƯỪỨỬỮỰÛÜŮŪ","yýỳỷỹỵÿ","YÝỲỶỸỴŸ","zžżź","ZŽŻŹ"]:["aàáảãạăằắẳẵặâầấẩẫậäåāąAÀÁẢÃẠĂẰẮẲẴẶÂẦẤẨẪẬÄÅĀĄ","cçćčCÇĆČ","dđďDĐĎ","eèéẻẽẹêềếểễệëěēęEÈÉẺẼẸÊỀẾỂỄỆËĚĒĘ","iìíỉĩịîïīIÌÍỈĨỊÎÏĪ","lłLŁ","nñňńNÑŇŃ","oòóỏõọôồốổỗộơởỡớờợöøōOÒÓỎÕỌÔỒỐỔỖỘƠỞỠỚỜỢÖØŌ","rřRŘ","sšśșşSŠŚȘŞ","tťțţTŤȚŢ","uùúủũụưừứửữựûüůūUÙÚỦŨỤƯỪỨỬỮỰÛÜŮŪ","yýỳỷỹỵÿYÝỲỶỸỴŸ","zžżźZŽŻŹ"],r=[];return e.split("").forEach(function(i){n.every(function(n){if(-1!==n.indexOf(i)){if(r.indexOf(n)>-1)return!1;e=e.replace(new RegExp("["+n+"]","gm"+t),"["+n+"]"),r.push(n)}return!0})}),e}},{key:"createMergedBlanksRegExp",value:function(e){return e.replace(/[\s]+/gim,"[\\s]+")}},{key:"createAccuracyRegExp",value:function(e){var t=this,n=this.opt.accuracy,r="string"==typeof n?n:n.value,i="";switch(("string"==typeof n?[]:n.limiters).forEach(function(e){i+="|"+t.escapeStr(e)}),r){case"partially":default:return"()("+e+")";case"complementary":return"()([^"+(i="\\s"+(i||this.escapeStr("!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~¡¿")))+"]*"+e+"[^"+i+"]*)";case"exactly":return"(^|\\s"+i+")("+e+")(?=$|\\s"+i+")"}}}]),e}(),a=function(){function a(e){t(this,a),this.ctx=e,this.ie=!1;var n=window.navigator.userAgent;(n.indexOf("MSIE")>-1||n.indexOf("Trident")>-1)&&(this.ie=!0)}return n(a,[{key:"log",value:function(t){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"debug",r=this.opt.log;this.opt.debug&&"object"===(void 0===r?"undefined":e(r))&&"function"==typeof r[n]&&r[n]("mark.js: "+t)}},{key:"getSeparatedKeywords",value:function(e){var t=this,n=[];return e.forEach(function(e){t.opt.separateWordSearch?e.split(" ").forEach(function(e){e.trim()&&-1===n.indexOf(e)&&n.push(e)}):e.trim()&&-1===n.indexOf(e)&&n.push(e)}),{keywords:n.sort(function(e,t){return t.length-e.length}),length:n.length}}},{key:"isNumeric",value:function(e){return Number(parseFloat(e))==e}},{key:"checkRanges",value:function(e){var t=this;if(!Array.isArray(e)||"[object Object]"!==Object.prototype.toString.call(e[0]))return this.log("markRanges() will only accept an array of objects"),this.opt.noMatch(e),[];var n=[],r=0;return e.sort(function(e,t){return e.start-t.start}).forEach(function(e){var i=t.callNoMatchOnInvalidRanges(e,r),o=i.start,a=i.end;i.valid&&(e.start=o,e.length=a-o,n.push(e),r=a)}),n}},{key:"callNoMatchOnInvalidRanges",value:function(e,t){var n=void 0,r=void 0,i=!1;return e&&void 0!==e.start?(r=(n=parseInt(e.start,10))+parseInt(e.length,10),this.isNumeric(e.start)&&this.isNumeric(e.length)&&r-t>0&&r-n>0?i=!0:(this.log("Ignoring invalid or overlapping range: "+JSON.stringify(e)),this.opt.noMatch(e))):(this.log("Ignoring invalid range: "+JSON.stringify(e)),this.opt.noMatch(e)),{start:n,end:r,valid:i}}},{key:"checkWhitespaceRanges",value:function(e,t,n){var r=void 0,i=!0,o=n.length,a=t-o,s=parseInt(e.start,10)-a;return(r=(s=s>o?o:s)+parseInt(e.length,10))>o&&(r=o,this.log("End range automatically set to the max value of "+o)),s<0||r-s<0||s>o||r>o?(i=!1,this.log("Invalid range: "+JSON.stringify(e)),this.opt.noMatch(e)):""===n.substring(s,r).replace(/\s+/g,"")&&(i=!1,this.log("Skipping whitespace only range: "+JSON.stringify(e)),this.opt.noMatch(e)),{start:s,end:r,valid:i}}},{key:"getTextNodes",value:function(e){var t=this,n="",r=[];this.iterator.forEachNode(NodeFilter.SHOW_TEXT,function(e){r.push({start:n.length,end:(n+=e.textContent).length,node:e})},function(e){return t.matchesExclude(e.parentNode)?NodeFilter.FILTER_REJECT:NodeFilter.FILTER_ACCEPT},function(){e({value:n,nodes:r})})}},{key:"matchesExclude",value:function(e){return i.matches(e,this.opt.exclude.concat(["script","style","title","head","html"]))}},{key:"wrapRangeInTextNode",value:function(e,t,n){var r=this.opt.element?this.opt.element:"mark",i=e.splitText(t),o=i.splitText(n-t),a=document.createElement(r);return a.setAttribute("data-markjs","true"),this.opt.className&&a.setAttribute("class",this.opt.className),a.textContent=i.textContent,i.parentNode.replaceChild(a,i),o}},{key:"wrapRangeInMappedTextNode",value:function(e,t,n,r,i){var o=this;e.nodes.every(function(a,s){var c=e.nodes[s+1];if(void 0===c||c.start>t){if(!r(a.node))return!1;var u=t-a.start,l=(n>a.end?a.end:n)-a.start,h=e.value.substr(0,a.start),f=e.value.substr(l+a.start);if(a.node=o.wrapRangeInTextNode(a.node,u,l),e.value=h+f,e.nodes.forEach(function(t,n){n>=s&&(e.nodes[n].start>0&&n!==s&&(e.nodes[n].start-=l),e.nodes[n].end-=l)}),n-=l,i(a.node.previousSibling,a.start),!(n>a.end))return!1;t=a.end}return!0})}},{key:"wrapGroups",value:function(e,t,n,r){return r((e=this.wrapRangeInTextNode(e,t,t+n)).previousSibling),e}},{key:"separateGroups",value:function(e,t,n,r,i){for(var o=t.length,a=1;a-1&&r(t[a],e)&&(e=this.wrapGroups(e,s,t[a].length,i))}return e}},{key:"wrapMatches",value:function(e,t,n,r,i){var o=this,a=0===t?0:t+1;this.getTextNodes(function(t){t.nodes.forEach(function(t){t=t.node;for(var i=void 0;null!==(i=e.exec(t.textContent))&&""!==i[a];){if(o.opt.separateGroups)t=o.separateGroups(t,i,a,n,r);else{if(!n(i[a],t))continue;var s=i.index;if(0!==a)for(var c=1;c + + + + + Naming Conventions - Cross-platform Rust Components + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Naming Conventions

+

All names in this project should adhere to the guidelines outlined in this document.

+

Rust Code

+

TL;DR: do what Rust's builtin warnings and clippy lints tell you +(and CI will fail if there are any unresolved warnings or clippy lints).

+

Overview

+
    +
  • +

    All variable names, function names, module names, and macros in Rust code should follow typical snake_case conventions.

    +
  • +
  • +

    All Rust types, traits, structs, and enum variants must follow UpperCamelCase.

    +
  • +
  • +

    Static and constant variables should be written in SCREAMING_SNAKE_CASE. s

    +
  • +
+

For more in-depth Rust conventions, see the Rust Style Guide.

+

Examples:

+
#![allow(unused)]
+fn main() {
+fn sync15_passwords_get_all()
+struct PushConfiguration{...}
+const COMMON_SQL
+}
+

Swift Code

+

Overview

+
    +
  • +

    Names of types and protocols are UpperCamelCase.

    +
  • +
  • +

    All other uses are lowerCamelCase.

    +
  • +
+

For more in-depth Swift conventions, check out the Swift API Design Guidelines.

+

Examples:

+
enum CheckChildren{...}
+func checkTree()
+public var syncKey: String
+
+

Kotlin Code

+

If a source file contains only a top-level class, the source file should reflect the case-sensitive name of the class plus the .kt extension. Otherwise, if the source contains multiple top-level declarations, choose a name that describes the contents of the file, apply UpperCamelCase and append .kt extension.

+

Overview

+
    +
  • +

    Names of packages are always lower case and do not include underscores. Using multi-word names should be avoided. However, if used, they should be concatenated or use lowerCamelCase.

    +
  • +
  • +

    Names of classes and objects use UpperCamelCase.

    +
  • +
  • +

    Names of functions, properties, and local variables use lowerCamelCase.

    +
  • +
+

For more in-depth Kotlin Conventions, see the Kotlin Style Guide.

+

Examples:

+
//FooBar.kt
+class FooBar{...}
+fun fromJSONString()
+package mozilla.appservices.places
+
+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + diff --git a/book/print.html b/book/print.html new file mode 100644 index 0000000000..b2fdd30712 --- /dev/null +++ b/book/print.html @@ -0,0 +1,4813 @@ + + + + + + Cross-platform Rust Components + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Application Services Rust Components

+

Application Services is collection of Rust Components. The components are used to enable Firefox, and related applications to integrate with Firefox accounts, sync and enable experimentation. Each component is built using a core of shared code written in Rust, wrapped with native language bindings for different platforms.

+

Contact us

+

To contact the Application Services team you can:

+ +

The source code is available on GitHub.

+

License

+

The Application Services Source Code is subject to the terms of the Mozilla Public License v2.0. +You can obtain a copy of the MPL at https://mozilla.org/MPL/2.0/.

+

Contributing to Application Services

+

Anyone is welcome to help with the Application Services project. Feel free to get in touch with other community members on Matrix or through issues on GitHub.

+

Participation in this project is governed by the +Mozilla Community Participation Guidelines.

+

Bug Reports

+

You can file issues on GitHub. Please try to include as much information as you can and under what conditions +you saw the issue.

+

Building the project

+

Build instructions are available in the building page. Please let us know if you encounter any pain-points setting up your environment.

+

Finding issues

+

Below are a few different queries you can use to find appropriate issues to work on. Feel free to reach out if you need any additional clarification before picking up an issue.

+
    +
  • good first issues - If you are a new contributor, search for issues labeled good-first-issue
  • +
  • good second issues - Once you've got that first PR approved and you are looking for something a little more challenging, we are keeping a list of next-level issues. Search for the good-second-issue label.
  • +
  • papercuts - A collection of smaller sized issues that may be a bit more advanced than a first or second issue.
  • +
  • important, but not urgent - For more advanced contributors, we have a collection of issues that we consider important and would like to resolve sooner, but work isn't currently prioritized by the core team.
  • +
+

Sending Pull Requests

+

Patches should be submitted as pull requests (PRs).

+
+

When submitting PRs, We expect external contributors to push patches to a fork of application-services. For more information about submitting PRs from forks, read GitHub's guide.

+
+

Before submitting a PR:

+
    +
  • Your patch should include new tests that cover your changes, or be accompanied by explanation for why it doesn't need any. It is your and your reviewer's responsibility to ensure your patch includes adequate tests. +
      +
    • Consult the testing guide for some tips on writing effective tests.
    • +
    +
  • +
  • Your code should pass all the automated tests before you submit your PR for review. +
      +
    • Before pushing your changes, run ./automation/tests.py changes. The script will calculate which components were changed and run test suites, linters and formatters against those components. Because the script runs a limited set of tests, the script should execute in a fairly reasonable amount of time. +
        +
      • If you have modified any Swift code, also run swiftformat --swiftversion 5 on the modified code.
      • +
      +
    • +
    +
  • +
  • Your patch should include a changelog entry in CHANGELOG.md or an explanation of why +it does not need one. Any breaking changes to Swift or Kotlin binding APIs should be noted explicitly.
  • +
  • If your patch adds new dependencies, they must follow our dependency management guidelines. +Please include a summary of the due diligence applied in selecting new dependencies.
  • +
  • After you open a PR, our Continuous Integration system will run a full test suite. It's possible that this step will result in errors not caught with the script so make sure to check the results.
  • +
  • "Work in progress" pull requests are welcome, but should be clearly labeled as such and should not be merged until all tests pass and the code has been reviewed. +
      +
    • You can label pull requests as "Work in progress" by using the Github PR UI to indicate this PR is a draft (learn more about draft PRs).
    • +
    +
  • +
+

When submitting a PR:

+
    +
  • You agree to license your code under the project's open source license (MPL 2.0).
  • +
  • Base your branch off the current main branch.
  • +
  • Add both your code and new tests if relevant.
  • +
  • Please do not include merge commits in pull requests; include only commits with the new relevant code.
  • +
  • We encourage you to GPG sign your commits.
  • +
+

Code Review

+

This project is production Mozilla code and subject to our engineering practices and quality standards. Every patch must be peer reviewed by a member of the Application Services team.

+

Building Application Services

+

When working on Application Services, it's important to set up your environment for building the Rust code and the Android or iOS code needed by the application.

+

First time builds

+

Building for the first time is more complicated than a typical Rust project. +To build for an end-to-end experience that enables you to test changes in +client applications like Firefox for Android (Fenix) and Firefox iOS, there are a number of build +systems required for all the dependencies. The initial setup is likely to take +a number of hours to complete.

+

Building the Rust Components

+

Complete this section before moving to the android/iOS build instructions.

+
    +
  1. Make sure you cloned the repository:
  2. +
+
  $ git clone https://github.com/mozilla/application-services # (or use the ssh link)
+  $ cd application-services
+  $ git submodule update --init --recursive
+
+
    +
  1. +

    Install Rust: install via rustup

    +
  2. +
  3. +

    Install your system dependencies:

    +

    Linux

    +
      +
    1. +

      Install the system dependencies required for building NSS

      +
        +
      1. Install gyp: apt install gyp (required for NSS)
      2. +
      3. Install ninja-build: apt install ninja-build
      4. +
      5. Install python3 (at least 3.6): apt install python3
      6. +
      7. Install zlib: apt install zlib1g-dev
      8. +
      9. Install perl (needed to build openssl): apt install perl
      10. +
      11. Install patch (to build the libs): apt install patch
      12. +
      +
    2. +
    3. +

      Install the system dependencies required for SQLcipher

      +
        +
      1. Install tcl: apt install tclsh (required for SQLcipher)
      2. +
      +
    4. +
    +

    MacOS

    +
      +
    1. Install Xcode: check the ci config for the correct version.
    2. +
    3. Install Xcode tools: xcode-select --install
    4. +
    5. Install homebrew via its installation instructions (it's what we use for ci).
    6. +
    7. Install the system dependencies required for building NSS: +
        +
      1. Install ninja and python: brew install ninja python
      2. +
      3. Make sure which python3 maps to the freshly installed homebrew python. +
          +
        1. If it isn't, add the following to your bash/zsh profile and source the profile before continuing: +
          alias python3=$(brew --prefix)/bin/python3
          +
          +
        2. +
        3. Ensure python maps to the same Python version. You may have to +create a symlink: +
          PYPATH=$(which python3); ln -s $PYPATH `dirname $PYPATH`/python
          +
          +
        4. +
        +
      4. +
      5. Install gyp: +
        wget https://bootstrap.pypa.io/ez_setup.py -O - | python3 -
        +git clone https://chromium.googlesource.com/external/gyp.git ~/tools/gyp
        +cd ~/tools/gyp
        +python3 setup.py install
        +
        +
          +
        1. Add ~/tools/gyp to your path: +
          export PATH="~/tools/gyp:$PATH"
          +
          +
        2. +
        3. If you have additional questions, consult this guide.
        4. +
        +
      6. +
      7. Make sure your homebrew python's bin folder is on your path by updating your bash/zsh profile with the following: +
        export PATH="$PATH:$(brew --prefix)/opt/python@3.9/Frameworks/Python.framework/Versions/3.9/bin"
        +
        +
      8. +
      +
    8. +
    +

    Windows

    +

    Install windows build tools

    +
    +

    Why Windows Subsystem for Linux (WSL)?

    +

    It's currently tricky to get some of these builds working on Windows, primarily due to our use of SQLcipher. By using WSL it is possible to get builds working, but still have them published to your "native" local maven cache so it's available for use by a "native" Android Studio.

    +
    +
      +
    1. Install WSL (recommended over native tooling)
    2. +
    3. Install unzip: sudo apt install unzip
    4. +
    5. Install python3: sudo apt install python3 Note: must be python 3.6 or later
    6. +
    7. Install system build tools: sudo apt install build-essential
    8. +
    9. Install zlib: sudo apt-get install zlib1g-dev
    10. +
    11. Install tcl: sudo apt install tcl-dev
    12. +
    +
  4. +
  5. +

    Check dependencies and environment variables by running: ./libs/verify-desktop-environment.sh

    +
  6. +
+
+

Note that this script might instruct you to set some environment variables, set those by adding them to your +.zshrc or .bashrc so they are set by default on your terminal. If it does so instruct you, you must +run the command again after setting them so the libraries are built.

+
+
    +
  1. Run cargo test: cargo test
  2. +
+

Once you have successfully run ./libs/verify-desktop-environment.sh and cargo test you can move to the Building for Fenix and Building for iOS sections below to setup your local environment for testing with our client applications.

+
+

Building for Fenix

+

The following instructions assume that you are building application-services for Fenix, and want to take advantage of the +Fenix Auto-publication workflow for android-components and application-services.

+
    +
  1. Install Android SDK, JAVA, NDK and set required env vars +
      +
    1. Clone the firefox-android repository (not inside the Application Service repository).
    2. +
    3. Install Java 17 for your system
    4. +
    5. Set JAVA_HOME to point to the JDK 17 installation directory.
    6. +
    7. Download and install Android Studio.
    8. +
    9. Set ANDROID_SDK_ROOT and ANDROID_HOME to the Android Studio sdk location and add it to your rc file (either .zshrc or .bashrc depending on the shell you use for your terminal).
    10. +
    11. Configure the required versions of NDK +Configure menu > System Settings > Android SDK > SDK Tools > NDK > Show Package Details > NDK (Side by side) +
        +
      • 21.4.7075529 (required by Fenix; note: a specific NDK version isn't configured, this maps to default NDK version for the AGP version)
      • +
      • 25.2.9519653 (required by Application Services, as configured)
      • +
      +
    12. +
    +
  2. +
  3. If you are on Windows using WSL - drop to the section below, Windows setup +for Android (WSL) before proceeding.
  4. +
  5. Check dependencies, environment variables +
      +
    1. Run ./libs/verify-android-environment.sh
    2. +
    3. Follow instructions and rerun until it is successful.
    4. +
    +
  6. +
+

Windows setup for Android (via WSL)

+

Note: For non-Ubuntu linux versions, it may be necessary to execute $ANDROID_HOME/tools/bin/sdkmanager "build-tools;26.0.2" "platform-tools" "platforms;android-26" "tools". See also this gist for additional information.

+

Configure Maven

+

Configure maven to use the native windows maven repository - then, when doing ./gradlew install from WSL, it ends up in the Windows maven repo. This means we can do a number of things with Android Studio in "native" windows and have then work correctly with stuff we built in WSL.

+
    +
  1. Install maven: sudo apt install maven
  2. +
  3. Confirm existence of (or create) a ~/.m2 folder
  4. +
  5. In the ~/.m2 create a file called settings.xml
  6. +
  7. Add the content below replacing {username} with your username:
  8. +
+
    <settings>
+      <localRepository>/mnt/c/Users/{username}/.m2/repository</localRepository>
+    </settings>
+
+
+

Building for Firefox iOS

+
    +
  1. Install xcpretty: gem install xcpretty
  2. +
  3. Run ./libs/verify-ios-environment.sh to check your setup and environment +variables.
  4. +
  5. Make any corrections recommended by the script and re-run.
  6. +
  7. Next, run ./megazords/ios-rust/build-xcframework.sh to build all the binaries needed to consume a-s in iOS
  8. +
+

Once the script passes, you should be able to run the Xcode project.

+
+

Note: The built Xcode project is located at megazords/ios-rust/MozillaTestServices.xcodeproj.

+
+
+

Note: This is mainly for testing the rust components, the artifact generated in the above steps should be all you need for building application with application-services

+
+

Locally building Firefox iOS against a local Application Services

+

Detailed steps to build Firefox iOS against a local application services can be found this document

+

Using locally published components in Fenix

+

It's often important to test work-in-progress changes to Application Services components against a real-world +consumer project. The most reliable method of performing such testing is to publish your +components to a local Maven repository, and adjust the consuming project to install them +from there.

+

With support from the upstream project, it's possible to do this in a single step using +our auto-publishing workflow.

+

rust.targets

+

Both the auto-publishing and manual workflows can be sped up significantly by +using the rust.targets property which limits which architectures the Rust +code gets build against. You can set this property by creating/editing the +local.properties file in the repository root and adding a line like +rust.targets=x86,linux-x86-64. The trick is knowing which targets to put in +that comma separated list:

+
    +
  • Use x86 for running the app on most emulators (in rare cases, when you have a 64-bit emulator, you'll want x86_64)
  • +
  • If you're running the android-components or fenix unit tests, then you'll need the architecture of your machine: +
      +
    • OSX running Intel chips: darwin-x86-64
    • +
    • OSX running M1 chips: darwin-aarch64
    • +
    • Linux: linux-x86-64
    • +
    +
  • +
+

Using the auto-publishing workflow

+

Some consumers (notably Fenix) have support for +automatically publishing and including a local development version of application-services +in their build. The workflow is:

+
    +
  1. +

    Check out the firefox-android mono-repo.

    +
  2. +
  3. +

    Edit (or create) the file fenix/local.properties and tell it where to +find your local checkout of application-services, by adding a line like:

    +

    autoPublish.application-services.dir=relative/path/to/your/checkout/of/application-services

    +

    Note that the path should be relative from local.properties. For example, if application-services +and firefox-android are at the same level, the relative path would be ../../application-services

    +
  4. +
  5. +

    Do the same for android-components/local.properties - so yes, your local checkout of firefox-android +requires 2 copies of local.properties, both with identical values for autoPublish.application-services.dir +(and probably identical in every other way too)

    +
  6. +
  7. +

    Build the consuming project following its usual build procedure, e.g. via ./gradlew assembleDebug or ./gradlew test.

    +
  8. +
+

If all goes well, this should automatically build your checkout of application-services, publish it +to a local maven repository, and configure the consuming project to install it from there instead of +from our published releases.

+

Using Windows/WSL

+

If you are using Windows, there's a good chance you do most application-services work +in WSL, but want to run Android Studio on native Windows. In that scenario you must:

+
    +
  • From the app-services root, in WSL, execute ./automation/publish_to_maven_local_if_modified.py
  • +
  • In native Windows, just work as normal - that build process knows to not even attempt to +execute the above command automatically.
  • +
+

Using a manual workflow

+

Note: This is a bit tedious, and you should first try the auto-publishing workflow described +above. But if the auto-publishing workflow fails then it's important to know how to do the publishing process manually. Since most consuming apps get their copy of application-services via a dependency +on android-components, this procedure involves three separate repos:

+
    +
  1. +

    Inside the application-services repository root:

    +
      +
    1. +

      In version.txt, change +the version to end in -TESTING$N 1, +where $N is some number that you haven't used for this before.

      +

      Example: 0.27.0-TESTING3

      +
    2. +
    3. +

      Run ./gradlew publishToMavenLocal. This may take between 5 and 10 minutes.

      +
    4. +
    +
  2. +
  3. +

    Inside the consuming project repository root (eg, firefox-android/fenix):

    +
      +
    1. +

      Inside build.gradle, add +mavenLocal() inside allprojects { repositories { <here> } }.

      +
    2. +
    3. +

      Ensure that local.properties does not contain any configuration to +related to auto-publishing the application-services repo.

      +
    4. +
    5. +

      Inside buildSrc/src/main/java/AndroidComponents.kt, change the +version numbers for android-components to +match the new versions you defined above.

      +

      Example: const val VERSION = "0.51.0-TESTING3"

      +
    6. +
    +
  4. +
+

You should now be able to build and run the consuming application (assuming you could +do so before all this).

+

Caveats

+
    +
  1. This assumes you have followed the build instructions for Fenix
  2. +
  3. Make sure you're fully up to date in all repos, unless you know you need to +not be.
  4. +
  5. This omits the steps if changes needed because, e.g. application-services +made a breaking change to an API used in android-components. These should be +understandable to fix, you usually should be able to find a PR with the fixes +somewhere in the android-component's list of pending PRs (or, failing that, a +description of what to do in the application-services changelog).
  6. +
  7. Contact us if you get stuck.
  8. +
+

Adding support for the auto-publish workflow

+

If you had to use the manual workflow above and found it incredibly tedious, you might like to +try adding support for the auto-publish workflow to the consuming project! The details will differ +depending on the specifics of the project's build setup, but at a high level you will need to:

+
    +
  1. +

    In your settings.gradle, locate (or add) the code for parsing the local.properties file, +and add support for loading a directory path from the property autoPublish.application-services.dir.

    +

    If this property is present, spawn a subprocess to run ./gradlew autoPublishForLocalDevelopment +in the specified directory. This automates step (1) of the manual workflow above, publishing your +changes to application-services into a local maven repository under a unique version number.

    +
  2. +
  3. +

    In your build.gradle, if the autoPublish.application-services.dir property +is present, have each project apply the build script from ./build-scripts/substitute-local-appservices.gradle +in the specified directory.

    +

    This automates steps (2) and (3) of the manual workflow above, using gradle's dependency substitution +capabilities to override the verion requirements for application-services components. It may be necessary +to experiment with the ordering of this relative to other build configuration steps, in order for the +dependency substitution to work correctly.

    +

    For a single-project build this would look something like:

    +
    if (gradle.hasProperty('localProperties.autoPublish.application-services.dir')) {
    +   ext.appServicesSrcDir = gradle."localProperties.autoPublish.application-services.dir"
    +   apply from: "${appServicesSrcDir}/build-scripts/substitute-local-appservices.gradle"
    +}
    +
    +

    For a multi-project build it should be applied to all subprojects, like:

    +
    subprojects {
    +   if (gradle.hasProperty('localProperties.autoPublish.application-services.dir')) {
    +      ext.appServicesSrcDir = gradle."localProperties.autoPublish.application-services.dir"
    +      apply from: "${rootProject.projectDir}/${appServicesSrcDir}/build-scripts/substitute-local-appservices.gradle"
    +   }
    +}
    +
    +
  4. +
  5. +

    Confirm that the setup is working, by adding autoPublish.application-services.dir to your +local.properties file and running ./gradlew dependencies for the project.

    +

    You should be able to see gradle checking the build status of the various application-services +dependencies as part of its setup phase. When the command completes, it should print the resolved +versions of all dependencies, and you should see that application-services components have a version +number in the format 0.0.1-SNAPSHOT-{TIMESTAMP}.

    +
  6. +
+
+

[1]: It doesn't have to start with -TESTING, it only needs +to have the format -someidentifier. -SNAPSHOT$N is also very common to use, +however without the numeric suffix, this has specific meaning to gradle, so we +avoid it. Additionally, while the $N we have used in our running example has +matched (e.g. all of the identifiers ended in -TESTING3, this is not required, +so long as you match everything up correctly at the end. This can be tricky, so +I always try to use the same number).

+

How to locally test Swift Package Manager components on Firefox iOS

+
+

This is a guide on testing the Swift Package Manager component locally against a local build of Firefox iOS. For more information on our Swift Package Manager design, read the ADR that introduced it

+
+
+

This guide assumes the component you want to test is already distributed with the rust-components-swift repository, you can read the guide for adding a new component if you would like to distribute a new component.

+
+

The goal for this document is to be able to build a local firefox iOS against a local application-services. On a high level, that requires the following:

+
    +
  1. Build an xcframework in a local checkout of application-services
  2. +
  3. Include the xcframework in a local checkout of rust-components-swift
  4. +
  5. Run the generate script in rust-components-swift using a local checkout of application-services
  6. +
  7. Include the local checkout of rust-components-swift in firefox-ios
  8. +
+

Prerequisites:

+
    +
  1. A local checkout of firefox-ios that is ready to build
  2. +
  3. A local checkout of rust-components-swift
  4. +
  5. A local checkout of application-services that is ready to build for iOS
  6. +
+

Using the automated flow

+

For convenience, there is a script that will do all the necessary steps to configure your local firefox-ios build with a local application-services repository. You do not need to do the manual steps if you follow those steps.

+
    +
  1. +

    Run the following to execute the script, the example below assumes all of firefox-ios, rust-components-swift and application-services are in the same directory. Adjust the paths according to where they are on your filesystem.

    +
    $ cd firefox-ios # This is your local checkout of firefox-ios
    +$ ./rust_components_local.sh -a ../application-services ../rust-components-swift
    +
    +
  2. +
  3. +

    Using Xcode, open Client.xcodeproj in firefox-ios

    +
  4. +
  5. +

    Then, make sure to reset packages cache in Xcode. This forces Xcode to remove any previously cached versions of the Rust components.

    +
      +
    • You can reset package caches by going to File -> Packages -> Reset Package Caches
    • +
    +
  6. +
  7. +

    If this is not the first time you run the script, make sure to also update package versions. This forces Xcode to pull the latest changes in the rust-components-swift branch.

    +
      +
    • You can update package versions by going to File -> Packages -> Update To Latest Package Versions
    • +
    • If this step fails, it's possible that the Reset Package Caches step above left some cruft behind. You can force this step by manually removing ~/Library/Caches/org.swift.swiftpm and ~/Library/Developer/Xcode/DerivedData/Client-{some-long-string}
    • +
    +
  8. +
  9. +

    Once the above steps are done, attempt building firefox ios. If you face problems, feel free to contact us

    +
  10. +
+

Disabling local development

+

The easiest way to disable local development is to simply revert any changes to firefox-ios/Client.xcodeproj/project.pbxproj.

+

However, if there are other changes to the file that you would like to preserve, you can use the same script. To use the same script, you will need to:

+
    +
  1. Know what version of rust-components-swift was used beforehand. You can find this by checking the git diff on firefox-ios/Client.xcodeproj/project.pbxproj.
  2. +
  3. Run: +
    $ ./rust_components_local.sh --disable <VERSION> ../rust-components-swift
    +
    +
  4. +
  5. Then, make sure to reset packages cache in Xcode. This forces Xcode to remove any previously cached versions of the Rust components. +
      +
    • You can reset package caches by going to File -> Packages -> Reset Package Caches
    • +
    +
  6. +
+
+

If you happen to change branches in rust-components-swift, you will need to disable then re-enable local development. The script is not currently smart enough to switch branches. Alternatively, keep the branch in rust-components-swift the same. rust-components-swift serves only as a release surface so there is little use to switching branches and pushing changes to it, unless you are changing something related to the release process.

+
+

Using the manual flow

+

It's important to note the automated flow runs through all the necessary steps in a script, so if possible use the script as it's a tedious manual process

+

However, if the script is failing or you would like to run the manual process for any other reason follow the following steps.

+

Building the xcframework

+

To build the xcframework do the following:

+
    +
  1. In your local checkout of application-services, navigate to megazords/ios-rust/
  2. +
  3. Run the build-xcframework.sh script:
  4. +
+
$ ./build-xcframework.sh
+
+

This will produce a file name MozillaRustComponents.xcframework.zip that contains the following, built for all our target iOS platforms.

+
    +
  • The compiled Rust code for all the crates listed in Cargo.toml as a static library
  • +
  • The C header files and Swift module maps for the components
  • +
+

Include the xcframework in a local checkout of rust-components-swift

+

After you generated the MozillaRustComponents.xcframework.zip in the previous step, do the following to include it in a local checkout of rust-components-swift. The file will be in the megazords/ios-rust directory.

+
    +
  1. Unzip the MozillaRustComponents.xcframework.zip into the rust-components-swift repository: (Assuming you are in the root of the rust-components-swift directory and application-services is a neighbor directory) +
     unzip -o ../application-services/megazords/ios-rust/MozillaRustComponents.xcframework.zip -d .
    +
    +
  2. +
  3. Change the Package.swift's reference to the xcframework to point to the unzipped MozillaRustComponents.xcframework that was created in the previous step. You can do this by uncommenting the following line: +
        path: "./MozillaRustComponents.xcframework"
    +
    +and commenting out the following lines: +
        url: url,
    +    checksum: checksum,
    +
    +
  4. +
+

Run the generation script with a local checkout of application services

+

For this step, run the following script from inside the rust-components-swift repository (assuming that application-services is a neighboring directory to rust-components-swift).

+
./generate.sh ../application-services
+
+

Once that is done, stage and commit the changes the script ran. Xcode can only pick up committed changes.

+

Include the local checkout of rust-components-swift in firefox-ios

+

This is the final step to include your local changes into firefox-ios. Do the following steps:

+
    +
  1. +

    Open Client.xcodeproj in Xcode

    +
  2. +
  3. +

    Navigate to the Swift Packages in Xcode: +Screenshot of where to find the setting for Client

    +
  4. +
  5. +

    Remove the dependency on rust-components-swift as listed on Xcode, you can click the dependency then click the -

    +
  6. +
  7. +

    Add a new swift package by clicking the +:

    +
      +
    1. On the top right, enter the full path to your rust-components-swift checkout, preceded by file://. If you don't know what that is, run pwd in while in rust-components-swift. For example: file:///Users/tarikeshaq/code/rust-components-swift
    2. +
    3. Change the branch to be the checked-out branch of rust-component-swift you have locally. This is what the dialog should look like: +Dialog for including the rust-components-swift package
    4. +
    +
    +

    Note: If Xcode prevents you from adding the dependency to reference a local package, you will need to manually modify the Client.xcodeproj/project.pbxproj and replace every occurrence of https://github.com/mozilla/rust-components-swift with the full path to your local checkout.

    +
    +
      +
    1. Click Add Package
    2. +
    3. Now include the packages you would like to include, choose MozillaAppServices
    4. +
    +
  8. +
  9. +

    Finally, attempt to build firefox-ios, and if all goes well it should launch with your code. If you face problems, feel free to contact us

    +
  10. +
+

How to locally test Swift Package Manager components on Focus iOS

+
+

This is a guide on testing the Swift Package Manager component locally against a local build of Focus iOS. For more information on our Swift Package Manager design, read the ADR that introduced it

+
+
+

This guide assumes the component you want to test is already distributed with the rust-components-swift repository, you can read the guide for adding a new component if you would like to distribute a new component.

+
+

To test a component locally, you will need to do the following:

+
    +
  1. Build an xcframework in a local checkout of application-services
  2. +
  3. Include the xcframework in a local checkout of rust-components-swift
  4. +
  5. Run the make-tag script in rust-components-swift using a local checkout of application-services
  6. +
  7. Include the local checkout of rust-components-swift in Focus
  8. +
+

Below are more detailed instructions for each step

+

Building the xcframework

+

To build the xcframework do the following:

+
    +
  1. In a local checkout of application-services, navigate to megazords/ios-rust/
  2. +
  3. Run the build-xcframework.sh script:
  4. +
+
$ ./build-xcframework.sh --focus
+
+

This will produce a file name FocusRustComponents.xcframework.zip in the focus directory that contains the following, built for all our target iOS platforms.

+
    +
  • The compiled Rust code for all the crates listed in Cargo.toml as a static library
  • +
  • The C header files and Swift module maps for the components
  • +
+

Include the xcframework in a local checkout of rust-components-swift

+

After you generated the FocusRustComponents.xcframework.zip in the previous step, do the following to include it in a local checkout of rust-components-swift:

+
    +
  1. clone a local checkout of rust-components-swift, not inside the application-services repository: +
    git clone https://github.com/mozilla/rust-components.swift.git
    +
    +
  2. +
  3. Unzip the FocusRustComponents.xcframework.zip into the rust-components-swift repository: (Assuming you are in the root of the rust-components-swift directory and application-services is a neighbor directory) +
     unzip -o ../application-services/megazords/ios-rust/focus/FocusRustComponents.xcframework.zip -d .
    +
    +
  4. +
  5. Change the Package.swift's reference to the xcframework to point to the unzipped FocusRustComponents.xcframework that was created in the previous step. You can do this by uncommenting the following line: +
        path: "./FocusRustComponents.xcframework"
    +
    +and commenting out the following lines: +
        url: focusUrl,
    +    checksum: focusChecksum,
    +
    +
  6. +
+

Run the generation script with a local checkout of application services

+

For this step, run the following script from inside the rust-components-swift repository (assuming that application-services is a neighboring directory to rust-components-swift).

+
./generate.sh ../application-services
+
+

Once that is done, stage and commit the changes the script ran. Xcode can only pick up committed changes.

+

Include the local checkout of rust-components-swift in Focus

+

This is the final step to include your local changes into Focus. Do the following steps:

+
    +
  1. +

    Clone a local checkout of Focus if you haven't already. Make sure you also install the project dependencies, more information in their build instructions

    +
  2. +
  3. +

    Open Blockzilla.xcodeproj in Xcode

    +
  4. +
  5. +

    Navigate to the Swift Packages in Xcode: +Screenshot of where to find the setting for Blockzilla +Screenshot of where to find the package dependencies

    +
  6. +
  7. +

    Remove the dependency on rust-components-swift as listed on Xcode, you can click the dependency then click the -

    +
  8. +
  9. +

    Add a new swift package by clicking the +:

    +
      +
    1. On the top right, enter the full path to your rust-components-swift checkout, preceded by file://. If you don't know what that is, run pwd in while in rust-components-swift. For example: file:///Users/tarikeshaq/code/rust-components-swift
    2. +
    3. Change the branch to be the checked-out branch of rust-component-swift you have locally. This is what the dialog should look like: +Dialog for including the rust-components-swift package
    4. +
    5. Click Add Package
    6. +
    7. Now include the FocusAppServices library.
    8. +
    +
    +

    Note: If Xcode prevents you from adding the dependency to reference a local package, you will need to manually modify the Blockzilla.xcodeproj/project.pbxproj and replace every occurrence of https://github.com/mozilla/rust-components-swift with the full path to your local checkout.

    +
    +
  10. +
  11. +

    Finally, attempt to build focus, and if all goes well it should launch with your code. If you face any problems, feel free to contact us

    +
  12. +
+

Building and using a locally-modified version of JNA

+

Java Native Access is an important dependency +for the Application Services components on Android, as it provides the low-level interface +from the JVM into the natively-compiled Rust code.

+

If you need to work with a locally-modified version of JNA (e.g. to investigate an apparent +JNA bug) then you may find these notes helpful.

+
+

The JNA docs do have an Android Development Environment guide +that is a good starting point, but the instructions did not work for me and appear a little out of date. +Here are the steps that worked for me:

+
    +
  • +

    Modify your environment to specify $NDK_PLATFORM, and to ensure the Android NDK tools +for each target platform are in your $PATH. On my Mac with Android Studio the +config was as follows:

    +
    export NDK_ROOT="$HOME/Library/Android/sdk/ndk/25.2.9519653"
    +export NDK_PLATFORM="$NDK_ROOT/platforms/android-25"
    +export PATH="$PATH:$NDK_ROOT/toolchains/llvm/prebuilt/darwin-x86_64/bin"
    +export PATH="$PATH:$NDK_ROOT/toolchains/aarch64-linux-android-4.9/prebuilt/darwin-x86_64/bin"
    +export PATH="$PATH:$NDK_ROOT/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64/bin"
    +export PATH="$PATH:$NDK_ROOT/toolchains/x86-4.9/prebuilt/darwin-x86_64/bin"
    +export PATH="$PATH:$NDK_ROOT/toolchains/x86_64-4.9/prebuilt/darwin-x86_64/bin"
    +
    +

    You will probably need to tweak the paths and version numbers based on your operating system and +the details of how you installed the Android NDK.

    +
  • +
  • +

    Install the ant build tool (using brew install ant worked for me).

    +
  • +
  • +

    Checkout the JNA source from Github. Try doing a basic +build via ant dist and ant test. This won't build for Android but will test the rest of the tooling.

    +
  • +
  • +

    Adjust ./native/Makefile for compatibility with your Android NSK install. Here's what I had to do for mine:

    +
      +
    • Adjust the $CC variable to use clang instead of gcc: CC=aarch64-linux-android21-clang.
    • +
    • Adjust thd $CCP variable to use the version from your system: CPP=cpp.
    • +
    • Add -landroid -llog to the list of libraries to link against in $LIBS.
    • +
    +
  • +
  • +

    Build the JNA native libraries for the target platforms of interest:

    +
      +
    • ant -Dos.prefix=android-aarch64
    • +
    • ant -Dos.prefix=android-armv7
    • +
    • ant -Dos.prefix=android-x86
    • +
    • ant -Dos.prefix=android-x86-64
    • +
    +
  • +
  • +

    Package the newly-built native libraries into a JAR/AAR using ant dist. +This should produce ./dist/jna.aar.

    +
  • +
  • +

    Configure build.gradle for the consuming application to use the locally-built JNA artifact:

    +
    // Tell gradle where to look for local artifacts.
    +repositories {
    +    flatDir {
    +        dirs "/PATH/TO/YOUR/CHECKOUT/OF/jna/dist"
    +    }
    +}
    +
    +// Tell gradle to exclude the published version of JNA.
    +configurations {
    +    implementation {
    +        exclude group: "net.java.dev.jna", module:"jna"
    +    }
    +}
    +
    +// Take a direct dependency on the local JNA AAR.
    +dependencies {
    +    implementation name: "jna", ext: "aar"
    +}
    +
    +
  • +
  • +

    Rebuild and run your consuming application, and it should be using the locally-built JNA!

    +
  • +
+

If you're trying to debug some unexpected JNA behaviour (and if you favour old-school printf-style debugging) +then you can this code snippet to print to the Android log from the compiled native code:

+
#ifdef __ANDROID__
+#include <android/log.h>
+#define HACKY_ANDROID_LOG(...) __android_log_print(ANDROID_LOG_VERBOSE, "HACKY-DEBUGGING-FOR-ALL", __VA_ARGS__)
+#else
+#define HACKY_ANDROID_LOG(MSG)
+#endif
+
+HACKY_ANDROID_LOG("this will go to the android logcat output");
+HACKY_ANDROID_LOG("it accepts printf-style format sequences, like this: %d", 42);
+
+

Branch builds

+

Branch builds are a way to build and test Fenix using branches from application-services and firefox-android. +iOS is not currently supported, although we may add it in the future (see #4966).

+

Breaking changes in an application-services branch.

+

When we make breaking changes in an application-services branch, we typically make corresponding changes in an +android-components branch. Branch builds allow combining those branches together in order to run CI tests +and to produce APKs for manual testing. To trigger a branch build for this:

+
    +
  • Create the PR for the application-services branch you're working on
  • +
  • Add [firefox-android: branch-name] to the PR title
  • +
  • The branch build tasks will be listed as checks the Github PR. In particular: +
      +
    • branch-build-fenix-test and branch-build-ac-test will run the unit android-components/fenix unit tests
    • +
    • branch-build-fenix-build will contain the Fenix APK.
    • +
    +
  • +
+

Application-services nightlies

+

When we make non-breaking changes, we typically merge them into main and let them sit there until the next release. In +order to check that the current main really does only have non-breaking changes, we run a nightly branch build from the +main branch of application-services,

+
    +
  • To view the latest branch builds: +
      +
    • Open the latest decision task from the task index.
    • +
    • Click the "View Task" link
    • +
    • Click "Task Group" in the top-left
    • +
    • You should now see a list of tasks from the latest nightly +
        +
      • *-build were for building the application. A failure here indicates there's probably a breaking change that +needs to be resolved.
      • +
      • To get the APK, navigate to branch-build-fenix-build and download app-x86-debug.apk from the artifacts list
      • +
      • branch-build-ac-test.* are the android-components tests tasks. These are split up by gradle project, which matches +how the android-components CI handles things. Running all the tests together often leads to failures.
      • +
      • branch-build-fenix-test is the Fenix tests. These are not split up per-project.
      • +
      +
    • +
    +
  • +
  • These builds are triggered by our .cron.yml file
  • +
+

Guide to Testing a Rust Component

+

This document gives a high-level overview of how we test components in application-services. +It will be useful to you if you're adding a new component, or working on increasing the test +coverage of an existing component.

+

If you are only interested in running the existing test suite, please consult the +contributor docs and the tests.py script.

+

Unit and Functional Tests

+

Rust code

+

Since the core implementations of our components live in rust, so does the core of our testing strategy.

+

Each rust component should be accompanied by a suite of unit tests, following the guidelines for writing +tests from the Rust +Book. +Some additional tips:

+
    +
  • +

    Where possible, it's better use use the Rust typesystem to make bugs impossible than to write +tests to assert that they don't occur in practice. But given that the ultimate consumers of our +code are not in Rust, that's sometimes not possible. The best idiomatic Rust API for a feature +is not necessarily the best API for consuming it over an FFI boundary.

    +
  • +
  • +

    Rust's builtin assertion macros are sparse; we use the more_asserts +for some additional helpers.

    +
  • +
  • +

    Rust's strict typing can make test mocks difficult. If there's something you need to mock out in tests, +make it a Trait and use the mockiato crate to mock it.

    +
  • +
+

The Rust tests for a component should be runnable via cargo test.

+

FFI Layer code

+

We are currently using uniffi to generate most ((and soon all!) of our FFI code and thus the FFI code itself does not need to be extensively tested.

+

Kotlin code

+

The Kotlin wrapper code for a component should have its own test suite, which should follow the general guidelines for +testing Android code in Mozilla projects. +In practice that means we use +JUnit +as the test framework and +Robolectric +to provide implementations of Android-specific APIs.

+

The Kotlin tests for a component should be runnable via ./gradlew <component>:test.

+

The tests at this layer are designed to ensure that the API binding code is working as intended, +and should not repeat tests for functionality that is already well tested at the Rust level. +But given that the Kotlin bindings involve a non-trivial amount of hand-written boilerplate code, +it's important to exercise that code throughly.

+

One complication with running Kotlin tests is that the code needs to run on your local development machine, +but the Kotlin code's native dependencies are typically compiled and packaged for Android devices. The +tests need to ensure that an appropriate version of JNA and of the compiled Rust code is available in +their library search path at runtime. Our build.gradle files contain a collection of hackery that ensures +this, which should be copied into any new components.

+

The majority of our Kotlin bindings are autogenerated using uniffi and do not need extensive testing.

+

Swift code

+

The Swift wrapper code for a component should have its own test suite, using Apple's +Xcode unittest framework.

+

Due to the way that all rust components need to be compiled together into a single "megazord" +framework, this entire repository is a single Xcode project. The Swift tests for each component +thus need to live under megazords/ios-rust/MozillaTestServicesTests/ rather than in the directory +for the corresponding component. (XXX TODO: is this true? it would be nice to find a way to avoid having +them live separately because it makes them easy to overlook).

+

The tests at this layer are designed to ensure that the API binding code is working as intended, +and should not repeat tests for functionality that is already well tested at the Rust level. +But given that the Swift bindings involve a non-trivial amount of hand-written boilerplate code, +it's important to exercise that code thoroughly.

+

The majority of our Swift bindings are autogenerated using uniffi and do not need extensive testing.

+

Integration tests

+

End-to-end Sync Tests

+

⚠️ Those tests were disabled because of how flakey the stage server was. See #3909 ⚠️

+

The testing/sync-test directory contains a test harness for running sync-related +Rust components against a live Firefox Sync infrastructure, so that we can verifying the functionality +end-to-end.

+

Each component that implements a sync engine should have a corresponding suite of tests in this directory.

+
    +
  • XXX TODO: places doesn't.
  • +
  • XXX TODO: send-tab doesn't (not technically a sync engine, but still, it's related)
  • +
  • XXX TODO: sync-manager doesn't
  • +
+

Android Components Test Suite

+

It's important that changes in application-services are tested against upstream consumer code in the +android-components repo. This is currently +a manual process involving:

+
    +
  • Configuring your local checkout of android-components to use your local application-services +build.
  • +
  • Running the android-components test suite via ./gradle test.
  • +
  • Manually building and running the android-components sample apps to verify that they're still working.
  • +
+

Ideally some or all of this would be automated and run in CI, but we have not yet invested in such automation.

+

Test Coverage

+

We currently have code coverage reporting on Github using codecov. However, our code coverage does not tell us how much more coverage is caused by our consumers' tests.

+

Ideas for Improvement

+
    +
  • ASan, Memsan, and maybe other sanitizer checks, especially around the points where we cross FFI boundaries.
  • +
  • General-purpose fuzzing, such as via https://github.com/jakubadamw/arbitrary-model-tests
  • +
  • We could consider making a mocking backend for viaduct, which would also be mockable from Kotlin/Swift.
  • +
  • Add more end-to-end integration tests!
  • +
  • Live device tests, e.g. actual Fenixes running in an emulator and syncing to each other.
  • +
  • Run consumer integration tests in CI against main.
  • +
+

Smoke testing Application Services against end-user apps

+

This is a great way of finding integration bugs with application-services. +The testing can be done manually using substitution scripts, but we also have scripts that will do the smoke-testing for you.

+

Dependencies

+

Run pip3 install -r automation/requirements.txt to install the required Python packages.

+

Android Components

+

The automation/smoke-test-android-components.py script will clone (or use a local version) of +android-components and run a subset of its tests against the current application-services worktree. +It tries to only run tests that might be relevant to application-services functionality.

+

Fenix

+

The automation/smoke-test-fenix.py script will clone (or use a local version) of Fenix and +run tests against the current application-services worktree.

+

Firefox iOS

+

The automation/smoke-test-fxios.py script will clone (or use a local version) of Firefox iOS and +run tests against the current application-services worktree.

+

Testing faster: How to avoid making compile times worse by adding tests

+

Background

+

We'd like to keep cargo test, cargo build, cargo check, ... reasonably +fast, and we'd really like to keep them fast if you pass -p for a specific +project. Unfortunately, there are a few ways this can become unexpectedly slow. +The easiest of these problems for us to combat at the moment is the unfortunate +placement of dev-dependencies in our build graph.

+

If you perform a cargo test -p foo, all dev-dependencies of foo must be +compiled before foo's tests can start. This includes dependencies only used +non-test targets, such as examples or benchmarks.

+

In an ideal world, cargo could run your tests as soon as it finished with the +dependencies it needs for those tests, instead of waiting for your benchmark +suite, or the arg-parser your examples use, or etc.

+

Unfortunately, all cargo knows is that these are dev-dependencies, and not +which targets actually use them.

+

Additionally, unqualified invocations of cargo (that is, without -p) might +have an even worse time if we aren't careful. If I run, cargo test, cargo +knows every crate in the workspace needs to be built with all dev +dependencies, if places depends on fxa-client, all of fxa-clients +dev-dependencies must be compiled, ready, and linked in at least to the lib +target before we can even think about starting on places.

+

We have not been careful about what shape the dependency graph ends up as when example code is +taken into consideration (as it is by cargo during certain builds), and as a +result, we have this problem. Which isn't really a problem we +want to fix: Example code can and should depend on several different components, +and use them together in interesting ways.

+

So, because we don't want to change what our examples do, or make +major architectural changes of the non-test code for something like this, we +need to do something else.

+

The Solution

+

To fix this, we manually insert "cuts" into the dependency graph to help cargo +out. That is, we pull some of these build targets (e.g. examples, benchmarks, +tests if they cause a substantial compile overhead) into their own dedicated +crates so that:

+
    +
  1. They can be built in parallel with each other.
  2. +
  3. Crates depending on the component itself are not waiting on the +test/bench/example build in order for their test build to begin.
  4. +
  5. A potentially smaller set of our crates need to be rebuilt -- and a smaller +set of possible configurations exist meaning fewer items to add pressure to +caches.
  6. +
  7. ...
  8. +
+

Some rules of thumb for when / when not to do this:

+
    +
  • +

    All rust examples should be put in examples/*.

    +
  • +
  • +

    All rust benchmarks should be put in testing/separated/*. See the section +below on how to set your benchmark up to avoid redundant compiles.

    +
  • +
  • +

    Rust tests which brings in heavyweight dependencies should be evaluated on an +ad-hoc basis. If you're concerned, measure how long compilation takes +with/without, and consider how many crates depend on the crate where the test +lives (e.g. a slow test in support/foo might be far worse than one in a leaf +crate), etc...

    +
  • +
+

Appendix: How to avoid redundant compiles for benchmarks and integration tests

+

To be clear, this is way more important for benchmarks (which always compile as +release and have a costly link phase).

+

Say you have a directory structure like the following:

+
mycrate
+ ├── src
+ │   └── lib.rs
+ | ...
+ ├── benches
+ │   ├── bench0.rs
+ |   ├── bench1.rs
+ │   └── bench2.rs
+ ├── tests
+ │   ├── test0.rs
+ |   ├── test1.rs
+ │   └── test2.rs
+ └── ...
+
+

When you run your integration tests or benchmarks, each of test0, test1, +test2 or bench0, bench1, bench2 is compiled as it's own crate that runs +the tests in question and exits.

+

That means 3 benchmark executables are built on release settings, and 3 +integration test executables.

+

If you've ever tried to add a piece of shared utility code into your integration +tests, only to have cargo (falsely) complain that it is dead code: this is why. +Even if test0.rs and test2.rs both use the utility function, unless +every test crate uses every shared utility, the crate that doesn't will +complain.

+

(Aside: This turns out to be an unintentional secondary benefit of this approach +-- easier shared code among tests, without having to put a +#![allow(dead_code)] in your utils.rs. We haven't hit that very much here, +since we tend to stick to unit tests, but it came up in mentat several times, +and is a frequent complaint people have)

+

Anyway, the solution here is simple: Create a new crate. If you were working in +components/mycrate and you want to add some integration tests or benchmarks, +you should do cargo new --lib testing/separated/mycrate-test (or +.../mycrate-bench).

+

Delete .../mycrate-test/src/lib.rs. Yep, really, we're making a crate that +only has integration tests/benchmarks (See the "FAQ0" section at the bottom of +the file if you're getting incredulous).

+

Now, add a src/tests.rs or a src/benches.rs. This file should contain mod foo; declarations for each submodule containing tests/benchmarks, if any.

+

For benches, this is also where you set up the benchmark harness (refer to +benchmark library docs for how).

+

Now, for a test, add: into your Cargo.toml

+
[[test]]
+name = "mycrate-test"
+path = "src/tests.rs"
+
+

and for a benchmark, add:

+
[[test]]
+name = "mycrate-benches"
+path = "src/benches.rs"
+harness = false
+
+

Because we aren't using src/lib.rs, this is what declares which file is the +root of the test/benchmark crate. Because there's only one target (unlike with +tests/* / benches/* under default settings), this will compile more quickly.

+

Additionally, src/tests.rs and src/benches.rs will behave like a normal +crate, the only difference being that they don't produce a lib, and that they're +triggered by cargo test/cargo run respectively.

+

FAQ0: Why put tests/benches in src/* instead of disabling autotests/autobenches

+

Instead of putting tests/benchmarks inside src, we could just delete the src +dir outright, and place everything in tests/benches.

+

Then, to get the same one-rebuild-per-file behavior that we'll get in src, we +need to add autotests = false or autobenches = false to our Cargo.toml, +adding a root tests/tests.rs (or benches/benches.rs) containing mod decls +for all submodules, and finally by referencing that "root" in the Cargo.toml +[[tests]] / [[benches]] list, exactly the same way we did for using src/*.

+

This would work, and on the surface, using tests/*.rs and benches/*.rs seems +more consistent, so it seems weird to use src/*.rs for these files.

+

My reasoning is as follows: Almost universally, tests/*.rs, examples/*.rs, +benches/*.rs, etc. are automatic. If you add a test into the tests folder, it +will run without anything else.

+

If we're going to set up one-build-per-{test,bench}suite as I described, this +fundamentally cannot be true. In this paradigm, if you add a test file named +blah.rs, you must add a mod blah it to the parent module.

+

It seems both confusing and error-prone to use tests/*, but have it behave +that way, however this is absolutely the normal behavior for files in src/*.rs +-- When you add a file, you then need to add it to it's parent module, and this +is something Rust programmers are pretty used to.

+

(In fact, we even replicated this behavior (for no reason) in the places +integration tests, and added the mod declarations to a "controlling" parent +module -- It seems weird to be in an environment where this isn't required)

+

So, that's why. This way, we make it way less likely that you add a test file +to some directory, and have it get ignored because you didn't realize that in +this one folder, you need to add a mod mytest into a neighboring tests.rs.

+

Debugging Sql

+

It can be quite tricky to debug what is going on with sql statement, especially +once the sql gets complicated or many triggers are involved.

+

The sql_support create provides some utilities to help. Note that +these utilities are gated behind a debug-tools feature. The module +provides docstrings, so you should read them before you start.

+

This document describes how to use these capabilities and we'll use places +as an example.

+

First, we must enable the feature:

+
--- a/components/places/Cargo.toml
++++ b/components/places/Cargo.toml
+@@ -22,7 +22,7 @@ lazy_static = "1.4"
+ url = { version = "2.1", features = ["serde"] }
+ percent-encoding = "2.1"
+ caseless = "0.2"
+-sql-support = { path = "../support/sql" }
++sql-support = { path = "../support/sql", features=["debug-tools"] }
+
+

and we probably need to make the debug functions available:

+
--- a/components/places/src/db/db.rs
++++ b/components/places/src/db/db.rs
+@@ -108,6 +108,7 @@ impl ConnectionInitializer for PlacesInitializer {
+         ";
+         conn.execute_batch(initial_pragmas)?;
+         define_functions(conn, self.api_id)?;
++        sql_support::debug_tools::define_debug_functions(conn)?;
+
+

We now have a Rust function print_query() and a SQL function dbg() available.

+

Let's say we were trying to debug a test such as test_bookmark_tombstone_auto_created. +We might want to print the entire contents of a table, then instrument a query to check +what the value of a query is. We might end up with a patch something like:

+
index 28f19307..225dccbb 100644
+--- a/components/places/src/db/schema.rs
++++ b/components/places/src/db/schema.rs
+@@ -666,7 +666,8 @@ mod tests {
+             [],
+         )
+         .expect("should insert regular bookmark folder");
+-        conn.execute("DELETE FROM moz_bookmarks WHERE guid = 'bookmarkguid'", [])
++        sql_support::debug_tools::print_query(&conn, "select * from moz_bookmarks").unwrap();
++        conn.execute("DELETE FROM moz_bookmarks WHERE dbg('CHECKING GUID', guid) = 'bookmarkguid'", [])
+             .expect("should delete");
+         // should have a tombstone.
+         assert_eq!(
+
+

There are 2 things of note:

+
    +
  • We used the print_query function to dump the entire moz_bookmarks table before executing the query.
  • +
  • We instrumented the query to print the guid every time sqlite reads a row and compares it against +a literal.
  • +
+

The output of this test now looks something like:

+
running 1 test
+query: select * from moz_bookmarks
++----+------+------+--------+----------+---------+---------------+---------------+--------------+------------+-------------------+
+| id | fk   | type | parent | position | title   | dateAdded     | lastModified  | guid         | syncStatus | syncChangeCounter |
++====+======+======+========+==========+=========+===============+===============+==============+============+===================+
+| 1  | null | 2    | null   | 0        | root    | 1686248350470 | 1686248350470 | root________ | 1          | 1                 |
++----+------+------+--------+----------+---------+---------------+---------------+--------------+------------+-------------------+
+| 2  | null | 2    | 1      | 0        | menu    | 1686248350470 | 1686248350470 | menu________ | 1          | 1                 |
++----+------+------+--------+----------+---------+---------------+---------------+--------------+------------+-------------------+
+| 3  | null | 2    | 1      | 1        | toolbar | 1686248350470 | 1686248350470 | toolbar_____ | 1          | 1                 |
++----+------+------+--------+----------+---------+---------------+---------------+--------------+------------+-------------------+
+| 4  | null | 2    | 1      | 2        | unfiled | 1686248350470 | 1686248350470 | unfiled_____ | 1          | 1                 |
++----+------+------+--------+----------+---------+---------------+---------------+--------------+------------+-------------------+
+| 5  | null | 2    | 1      | 3        | mobile  | 1686248350470 | 1686248350470 | mobile______ | 1          | 1                 |
++----+------+------+--------+----------+---------+---------------+---------------+--------------+------------+-------------------+
+| 6  | null | 3    | 1      | 0        | null    | 1             | 1             | bookmarkguid | 2          | 1                 |
++----+------+------+--------+----------+---------+---------------+---------------+--------------+------------+-------------------+
+test db::schema::tests::test_bookmark_tombstone_auto_created ... FAILED
+
+failures:
+
+---- db::schema::tests::test_bookmark_tombstone_auto_created stdout ----
+CHECKING GUID root________
+CHECKING GUID menu________
+CHECKING GUID toolbar_____
+CHECKING GUID unfiled_____
+CHECKING GUID mobile______
+CHECKING GUID bookmarkguid
+
+

It's unfortunate that the output of print_table() goes to the tty while the output of dbg goes to stderr, so +you might find the output isn't quite intermingled as you would expect, but it's better than nothing!

+

Dependency Management Guidelines

+

This repository uses third-party code from a variety of sources, so we need to be mindful +of how these dependencies will affect our consumers. Considerations include:

+ +

We're still evolving our policies in this area, but these are the +guidelines we've developed so far.

+

Rust Code

+

Unlike Firefox, +we do not vendor third-party source code directly into the repository. Instead we rely on +Cargo.lock and its hash validation to ensure that each build uses an identical copy +of all third-party crates. These are the measures we use for ongoing maintence of our +existing dependencies:

+
    +
  • Check Cargo.lock into the repository.
  • +
  • Generate built artifacts using the --locked flag to cargo build, as an additional +assurance that the existing Cargo.lock will be respected.
  • +
  • Regularly run cargo-audit in CI to alert us to +security problems in our dependencies. +
      +
    • It runs on every PR, and once per hour on the main branch
    • +
    +
  • +
  • Use a home-grown tool to generate a summary of dependency licenses +and to check them for compatibility with MPL-2.0. +
      +
    • Check these summaries into the repository and have CI alert on unexpected changes, +to guard against pulling in new versions of a dependency under a different license.
    • +
    +
  • +
+

Adding a new dependency, whether we like it or not, is a big deal - that dependency and everything +it brings with it will become part of Firefox-branded products that we ship to end users. +We try to balance this responsibility against the many benefits of using existing code, as follows:

+
    +
  • In general, be conservative in adding new third-party dependencies. +
      +
    • For trivial functionality, consider just writing it yourself. +Remember the cautionary tale of left-pad.
    • +
    • Check if we already have a crate in our dependency tree that can provide the needed functionality.
    • +
    +
  • +
  • Prefer crates that have a a high level of due-dilligence already applied, such as: + +
  • +
  • Check that it is clearly licensed and is MPL-2.0 compatible.
  • +
  • Take the time to investigate the crate's source and ensure it is suitably high-quality. +
      +
    • Be especially wary of uses of unsafe, or of code that is unusually resource-intensive to build.
    • +
    • Dev dependencies do not require as much scrutiny as dependencies that will ship in consuming applications, +but should still be given some thought. +
        +
      • There is still the potential for supply-chain compromise with dev dependencies!
      • +
      +
    • +
    +
  • +
  • As part of the PR that introduces the new dependency: + +
  • +
+

Updating to new versions of existing dependencies is a normal part of software development +and is not accompanied by any particular ceremony.

+

Android/Kotlin Code

+

We currently depend only on the following Kotlin dependencies:

+ +

We currently depend on the following developer dependencies in the Kotlin codebase, +but they do not get included in built distribution files:

+
    +
  • detekt
  • +
  • ktlint
  • +
+

No additional Kotlin dependencies should be added to the project unless absolutely necessary.

+

iOS/Swift Code

+

We currently do not depend on any Swift dependencies. And no Swift dependencies should be added to the project unless absolutely necessary.

+

Other Code

+

We currently depend on local builds of the following system dependencies:

+ +

No additional system dependencies should be added to the project unless absolutely necessary.

+

Adding a new component to Application Services

+

Each component in the Application Services repository has three parts (the Rust code, +the Kotlin wrapper, and the Swift wrapper) so there are quite a few moving +parts involved in adding a new component. This is a rapid-fire list of all +the things you'll need to do if adding a new component from scratch.

+

The Rust Code

+

Your component should live under ./components in this repo. +Use cargo new --lib ./components/<your_crate_name>to create a new library crate, +and please try to avoid using hyphens in the crate name.

+

See the Guide to Building a Rust Component for general +advice on designing and structuring the actual Rust code, and follow the +Dependency Management Guidelines if your crate +introduces any new dependencies.

+

Use UniFFI to define how your crate's +API will get exposed to foreign-language bindings. By convention, put the interface +definition file at ./components/<your_crate_name>/<your_crate_name>.udl. Use +the builtin-bindgen feature of UniFFI to simplify the build process, by +putting the following in your Cargo.toml:

+
[build-dependencies]
+uniffi_build = { version = "<latest version here>", features=["builtin-bindgen"] }
+
+

Include your new crate in the application-services workspace, by adding +it to the members and default-members lists in the Cargo.toml at +the root of the repository.

+

In order to be published to consumers, your crate must be included in the +"megazord" crate for each target platform:

+
    +
  • For Android, add it as a dependency in ./megazords/full/Cargo.toml and +add a pub use <your_crate_name> to ./megazords/full/src/lib.rs.
  • +
  • For iOS, add it as a dependency in ./megazords/ios-rust/rust/Cargo.toml and +add a pub use <your_crate_name> to ./megazords/ios-rust/src/lib.rs.
  • +
+

Run cargo check -p <your_crate_name> in the repository root to confirm that +things are configured properly. This will also have the side-effect of updating +Cargo.lock to contain your new crate and its dependencies.

+

The Kotlin Bindings

+

Make a ./components/<your_crate_name>/android subdirectory to contain +Kotlin- and Android-specific code. This directory will contain a gradle +project for building your Kotlin bindings.

+

Copy the build.gradle file from ./components/crashtest/android/ into +your own component's directory, and edit it to replace the references to +crashtest.udl with your own component's .udl file.

+

Create a file ./components/<your_crate_name>/uniffi.toml with the +following contents:

+
[bindings.kotlin]
+package_name = "mozilla.appservices.<your_crate_name>"
+cdylib_name = "megazord"
+
+

Create a file ./components/<your_crate_name>/android/src/main/AndroidManifest.xml +with the following contents:

+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="org.mozilla.appservices.<your_crate_name>" />
+
+

In the root of the repository, edit .buildconfig-android.ymlto add +your component's metadata. This will cause it to be included in the +gradle workspace and in our build and publish pipeline. Check whether +it builds correctly by running:

+
    +
  • ./gradlew <your_crate_name>:assembleDebug
  • +
+

You can include hand-written Kotlin code alongside the automatically +generated bindings, by placing `.kt`` files in a directory named:

+
    +
  • ./android/src/test/java/mozilla/appservices/<your_crate_name>/
  • +
+

You can write Kotlin-level tests that consume your component's API, +by placing `.kt`` files in a directory named:

+
    +
  • ./android/src/test/java/mozilla/appservices/<your_crate_name>/.
  • +
+

So you would end up with a directory structure something like this:

+
    +
  • components/<your_crate_name>/ +
      +
    • Cargo.toml
    • +
    • uniffi.toml
    • +
    • src/ +
        +
      • Rust code here.
      • +
      +
    • +
    • android/ +
        +
      • build.gradle
      • +
      • src/ +
          +
        • main/ +
            +
          • AndroidManifest.xml
          • +
          • java/mozilla/appservices/<your_crate_name>/ +
              +
            • Hand-written Kotlin code here.
            • +
            +
          • +
          +
        • +
        • test/java/mozilla/appservices/<your_crate_name>/ +
            +
          • Kotlin test-cases here.
          • +
          +
        • +
        +
      • +
      +
    • +
    +
  • +
+

Run your component's Kotlin tests with ./gradlew <your_crate_name>:test +to confirm that this is all working correctly.

+

The Swift Bindings

+

Creating the directory structure

+

Make a ./components/<your_crate_name>/ios subdirectory to contain +Swift- and iOS-specific code. The UniFFI-generated swift bindings will +be written to a subdirectory named Generated.

+

You can include hand-written Swift code alongside the automatically +generated bindings, by placing .swift files in a directory named: +./ios/<your_crate_name>/.

+

So you would end up with a directory structure something like this:

+
    +
  • components/<your_crate_name>/ +
      +
    • Cargo.toml
    • +
    • uniffi.toml
    • +
    • src/ +
        +
      • Rust code here.
      • +
      +
    • +
    • ios/ +
        +
      • <your_crate_name>/ +
          +
        • Hand-written Swift code here.
        • +
        +
      • +
      • Generated/ +
          +
        • Generated Swift code will be written into this directory.
        • +
        +
      • +
      +
    • +
    +
  • +
+

Adding your component to the Swift Package Manager Megazord

+
+

For more information on our how we ship components using the Swift Package Manager, check the ADR that introduced the Swift Package Manager

+
+

You will need to do the following steps to include the component in the megazord:

+
    +
  1. +

    Update its uniffi.toml to include the following settings:

    +
    [bindings.swift]
    +ffi_module_name = "MozillaRustComponents"
    +ffi_module_filename = "<crate_name>FFI"
    +
    +
  2. +
  3. +

    Add the component as a dependency to the Cargo.toml in megazords/ios-rust/

    +
  4. +
  5. +

    Add a pub use declaration for the component in megazords/ios-rust/src/lib.rs

    +
  6. +
  7. +

    Add logic to the megazords/ios-rust/build-xcframework.sh to copy or generate its header file into the build

    +
  8. +
  9. +

    Add an #import for its header file to megazords/ios-rust/MozillaRustComponents.h

    +
  10. +
  11. +

    Add your component into the iOS "megazord" through the Xcode project, which can only really by done using the Xcode application, which can only really be done if you're on a Mac.

    +
      +
    1. +

      Open megazords/ios-rust/MozillaTestServices/MozillaTestServices.xcodeproj in Xcode.

      +
    2. +
    3. +

      In the Project navigator, add a new Group for your new component, pointing to +the ./ios/ directory you created above. Add the following entries to the Group:

      +
        +
      • The .udl file for you component, from ../src/<your_crate_name>.udl.
      • +
      • Any hand-written .swift files for your component
      • +
      +
    4. +
    +
  12. +
+
+

Make sure that the "Copy items if needed" option is unchecked, and that +nothing is checked in the "Add to targets" list.

+
+

The result should look something like this:

+

Screenshot of Xcode Project Navigator

+

Click on the top-level "MozillaTestServices" project in the navigator, then go to "Build Phases".

+
+

Double-check that <your_crate_name>.udl does not appear in the "Copy Bundle Resources" section.

+
+

Add <your_crate_name>.udl to the list of "Compile Sources". This will trigger an Xcode Build Rule that generates +the Swift bindings automatically. Also include any hand-written .swift files in this list.

+

Finally, in the Project navigator, add a sub-group named "Generated", pointing to the ./Generated/ subdirectory, and +containing entries for the files generated by UniFFI: +* <your_crate_name>.swift +* <your_crate_name>FFI.h +Make sure that "Copy items if needed" is unchecked, and that nothing is checked in "Add to targets".

+
+

Double-check that <your_crate_name>.swift does not appear in the "Compile Sources" section.

+
+

The result should look something like this:

+

Screenshot of Xcode Compile Sources list

+

Build the project in Xcode to check whether that all worked correctly.

+

To add Swift tests for your component API, create them in a file under +megazords/ios-rust/MozillaTestServicesTests/. Use this syntax to import +your component's bindings from the compiled megazord:

+
@testable import MozillaTestServices
+
+

In Xcode, navigate to the MozillaTestServicesTests Group and add your +new test file as an entry. Select the corresponding target, click on +"Build Phases", and add your test file to the list of "Compile Sources". +The result should look something like this:

+

Screenshot of Xcode Test Setup

+

Use the Xcode Test Navigator to run your tests and check whether +they're passing.

+

Distribute your component with rust-components-swift

+

The Swift source code and generated UniFFI bindings are distributed to consumers (eg: Firefox iOS) through rust-components-swift.

+

A nightly taskcluster job prepares the rust-component-swift packages from the source code in the application-services repository. To distribute your component with rust-component-swift, add the following to the taskcluster script in taskcluster/scripts/build-and-test-swift.py:

+
    +
  • Add the path to the <your_crate_name>.udl file to BINDINGS_UDL_PATHS +
      +
    • Optionally also to FOCUS_UDL_PATHS if your component is also targeting Firefox Focus
    • +
    +
  • +
  • Add the path to the directory containing any hand-written swift code to SOURCE_TO_COPY +
      +
    • Optionally also to FOCUS_SOURCE_TO_COPY if your component is also targeting Firefox Focus
    • +
    +
  • +
+

Your component should now automatically get included in the next rust-component-swift nightly release.

+

Guide to Building a Syncable Rust Component

+
+

This is a guide to creating a new Syncable Rust Component like many of the components in this repo. If you are looking for information how to build (ie,compile, etc) the existing components, you are looking for our build documentation

+
+

Welcome!

+

It's great that you want to build a Rust Component - this guide should help +get you started. It documents some nomenclature, best-practices and other +tips and tricks to get you started.

+

This document is just for general guidance - every component will be different +and we are still learning how to make these components. Please update this +document with these learnings.

+

To repeat with emphasis - please consider this a living document.

+

General design and structure of the component

+

We think components should be structured as described here.

+

We build libraries, not frameworks

+

Think of building a "library", not a "framework" - the application should be in +control and calling functions exposed by your component, not providing functions +for your component to call.

+

The "store" is the "entry-point"

+

[Note that some of the older components use the term "store" differently; we +should rename them! In Places, it's called an "API"; in Logins an "engine". See +webext-storage for a more recent component that uses the term "Store" as we +think it should be used.]

+

The "Store" is the entry-point for the consuming application - it provides the +core functionality exposed by the component and manages your databases and other +singletons. The responsibilities of the "Store" will include things like creating the +DB if it doesn't exist, doing schema upgrades etc.

+

The functionality exposed by the "Store" will depend on the complexity of the +API being exposed. For example, for webext-storage, where there are only a +handful of simple public functions, it just directly exposes all the +functionality of the component. However, for Places, which has a much more +complex API, the (logical) Store instead supplies "Connection" instances which +expose the actual functionality.

+

Using sqlite

+

We prefer sqlite instead of (say) JSON files or RKV.

+

Always put sqlite into WAL mode, then have exactly 1 writer connection and as +many reader connections you need - which will depend on your use-case - for +example, webext_storage has 1 reader, while places has many.

+

(Note that places has 2 writers (one for sync, one for the api), but we +believe this was a mistake and should have been able to make things work +better with exactly 1 shared between sync and the api)

+

We typically have a "DB" abstraction which manages the database itself - the +logic for handling schema upgrades etc and enforcing the "only 1 writer" rule +is done by this.

+

However, this is just a convenience - the DB abstractions aren't really passed +around - we just pass raw connections (or transactions) around. For example, if +there's a utility function that reads from the DB, it will just have a Rusqlite +connection passed. (Again, older components don't really do this well, but +webext-storage does)

+

We try and leverage rust to ensure transactions are enforced at the correct +boundaries - for example, functions which write data but which must be done as +part of a transaction will accept a Rusqlite Transaction reference as the +param, whereas something that only reads the Db will accept a Rusqlite +Connection - note that because Transaction supports +Deref<Target = Connection>, you can pass a &Transaction wherever a +&Connection is needed - but not vice-versa.

+

Meta-data

+

You are likely to have a table just for key/value metadata, and this table will +be used by sync (and possibly other parts of the component) to track the +sync IDs, lastModified timestamps etc.

+

Schema management

+

The schemas are stored in the tree in .sql files and pulled into the source at +build time via include_str!. Depending on the complexity of your component, +there may be a need for different Connections to have different Sql (for +example, it may be that only your 'write' connection requires the sql to define +triggers or temp tables, so these might be in their own file.)

+

Because requirements evolve, there will be a need to support schema upgrades. +This is done by way of sqlite's PRAGMA user_version - which can be thought of +as simple metadata for the database itself. In short, immediately after opening +the database for the first time, we check this version and if it's less than +expected we perform the schema upgrades necessary, then re-write the version +to the new version.

+

This is easier to read than explain, so read the upgrade() function in +the Places schema code

+

You will need to be a big careful here because schema upgrades are going to +block the calling application immediately after they upgrade to a new version, +so if your schema change requires a table scan of a massive table, you are going +to have a bad time. Apart from that though, you are largely free to do whatever +sqlite lets you do!

+

Note that most of our components have very similar schema and database +management code - these are screaming out to be refactored so common logic can +be shared. Please be brave and have a go at this!

+

Triggers

+

We tend to like triggers for encompasing application logic - for example, if +updating one row means a row in a different table should be updated based on +that data, we'd tend to prefer an, eg, AFTER UPDATE trigger than having our +code manually implement the logic.

+

However, you should take care here, because functionality based on triggers is +difficult to debug (eg, logging in a trigger is difficult) and the functionality +can be difficult to locate (eg, users unfamiliar with the component may wonder +why they can't find certain functionity in the rust code and may not consider +looking in the sqlite triggers)

+

You should also be careful when creating triggers on persistent main tables. +For example, bumping the change counter isn't a good use for a trigger, +because it'll run for all changes on the table—including those made by Sync. +This means Sync will end up tracking its own changes, and getting into infinite +syncing loops. Triggers on temporary tables, or ones that are used for +bookkeeping where the caller doesn't matter, like bumping the foreign +reference count for a URL, are generally okay.

+

General structure of the rust code

+

We prefer flatter module hierarchies where possible. For example, in Places +we ended up with sync_history and sync_bookmarks sub-modules rather than +a sync submodule itself with history and bookmarks.

+

Note that the raw connections are never exposed to consumers - for example, they +will tend to be stored as private fields in, eg, a Mutex.

+

Syncing

+

The traits you need to implement to sync aren't directly covered here.

+

All meta-data related to sync must be stored in the same database as the +data itself - often in a meta table.

+

All logic for knowing which records need to be sync must be part of the +application logic, and will often be implemented using triggers. It's quite +common for components to use a "change counter" strategy, which can be +summarized as:

+
    +
  • +

    Every table which defines the "top level" items being synced will have a +column called something like 'sync_change_counter' - the app will probably +track this counter manually instead of using a trigger, because sync itself +will need different behavior when it updates the records.

    +
  • +
  • +

    At sync time, items with a non-zero change counter are candidates for syncing.

    +
  • +
  • +

    As the sync starts, for each item, the current value of the change counter is +remembered. At the end of the sync, the counter is decremented by this value. +Thus, items which were changed between the time the sync started and completed +will be left with a non-zero change counter at the end of the sync.

    +
  • +
+

Syncing FAQs

+

This section is stolen from this document

+

What’s the global sync ID and the collection sync ID?

+

Both guids, both used to identify when the data in the server has changed +radically underneath us (eg, when looking at lastModified is no longer a sane +thing to do.)

+

The "global sync ID" changing means that every collection needs to be assumed as +having changed radically, whereas just the "collection sync ID" changing means +just that one collection.

+

These global IDs are most likely to change on a node reassignment (which should +be rare now with durable storage), a password reset, etc. An example of when the +collection ID will change is a "bookmarks restore" - handling an old version of +a database re-appearing is why we store these IDs in the database itself.

+

What’s get_sync_assoc, why is it important? What is StoreSyncAssociation?

+

They are all used to track the guids above. It’s vitally important we know when +these guids change.

+

StoreSyncAssociation is a simple enum which reflects the state a sync engine +can be in - either Disconnected (ie, we have no idea what the GUIDs are) or +Connected where we know what we think the IDs are (but the server may or may +not match with this)

+

These GUIDs will typically be stored in the DB in the metadata table.

+

what is apply_incoming versus sync_finished

+

apply_incoming is where any records incoming from the server (ie, possibly +all records on the server if this is a first-sync, records with a timestamp +later than our last sync otherwise) are processed.

+

sync_finished is where we've done all the sync work other than uploading new +changes to the server.

+

What's the diff between reset and wipe?

+
    +
  • Reset means “I don’t know what’s on the server - I need to reconcile everything there with everything I have”. IOW, a “first sync”
  • +
  • Wipe means literally “wipe all server data”
  • +
+

Exposing to consumers

+

You will need an FFI or some other way of exposing stuff to your consumers.

+

We use a tool called UniFFI to automatically +generate FFI bindings from the Rust code.

+

If UniFFI doesn't work for you, then you'll need to hand-write the FFI layer. +Here are some earlier blog posts on the topic which might be helpful:

+ +

The above are likely to be superseded by uniffi docs, but for now, good luck!

+

Naming Conventions

+

All names in this project should adhere to the guidelines outlined in this document.

+

Rust Code

+

TL;DR: do what Rust's builtin warnings and clippy lints tell you +(and CI will fail if there are any unresolved warnings or clippy lints).

+

Overview

+
    +
  • +

    All variable names, function names, module names, and macros in Rust code should follow typical snake_case conventions.

    +
  • +
  • +

    All Rust types, traits, structs, and enum variants must follow UpperCamelCase.

    +
  • +
  • +

    Static and constant variables should be written in SCREAMING_SNAKE_CASE. s

    +
  • +
+

For more in-depth Rust conventions, see the Rust Style Guide.

+

Examples:

+
#![allow(unused)]
+fn main() {
+fn sync15_passwords_get_all()
+struct PushConfiguration{...}
+const COMMON_SQL
+}
+

Swift Code

+

Overview

+
    +
  • +

    Names of types and protocols are UpperCamelCase.

    +
  • +
  • +

    All other uses are lowerCamelCase.

    +
  • +
+

For more in-depth Swift conventions, check out the Swift API Design Guidelines.

+

Examples:

+
enum CheckChildren{...}
+func checkTree()
+public var syncKey: String
+
+

Kotlin Code

+

If a source file contains only a top-level class, the source file should reflect the case-sensitive name of the class plus the .kt extension. Otherwise, if the source contains multiple top-level declarations, choose a name that describes the contents of the file, apply UpperCamelCase and append .kt extension.

+

Overview

+
    +
  • +

    Names of packages are always lower case and do not include underscores. Using multi-word names should be avoided. However, if used, they should be concatenated or use lowerCamelCase.

    +
  • +
  • +

    Names of classes and objects use UpperCamelCase.

    +
  • +
  • +

    Names of functions, properties, and local variables use lowerCamelCase.

    +
  • +
+

For more in-depth Kotlin Conventions, see the Kotlin Style Guide.

+

Examples:

+
//FooBar.kt
+class FooBar{...}
+fun fromJSONString()
+package mozilla.appservices.places
+
+

Converting an existing Component to use UniFFI

+

When we started building the components in this repo, exposing Rust code to +Kotlin and Swift was a manual process and each component had its own +hand-written FFI layer and foreign-language bindings.

+

As we've gained more experience with building components in this way, we've +started to automate bindings generation and capture best practices in a +tool called UniFFI, which is the +currently recommended approach when adding a new component from scratch.

+

We expect that existing components will gradually be ported over to use +UniFFI, and this document is a guide to doing that port.

+

First, get familiar with UniFFI

+

First, make sure you've perused the UniFFI guide +to understand the overall architecture of a UniFFI component, and take a look +at the guide to adding a new component to understand +how such components fit in to this repo. The aim of porting will be to have a component +that looks like it was added by the process described therein.

+

Next, get familiar with the target component

+

Pre-UniFFI components typically consist of four main parts:

+
    +
  • A Rust crate implementing the core functionality of the component
  • +
  • A separate Rust crate that exposes the core functionality over a C-style FFI.
  • +
  • An Android package that imports the C-style FFI into idiomatic Kotlin.
  • +
  • A Swift module that imports the C-style FFI into idiomatic Swift.
  • +
+

The code for these parts will be laid out something like this:

+
    +
  • components/<component_name>/ +
      +
    • Cargo.toml
    • +
    • src/ +
        +
      • Rust code for the core functionality of the component goes here.
      • +
      +
    • +
    • ffi/ +
        +
      • Cargo.toml
      • +
      • src/ +
          +
        • Rust code specifically for exposing the C-style FFI goes here.
        • +
        +
      • +
      +
    • +
    • android/ +
        +
      • build.gradle
      • +
      • src/ +
          +
        • main/ +
            +
          • AndroidManifest.xml
          • +
          • java/mozilla/appservices/<component_name>/ +
              +
            • Lib<ComponentName>FFI.kt (low-level bindings to the C-style FFI)
            • +
            • Higher-level hand-written Kotlin that wraps the FFI.
            • +
            +
          • +
          +
        • +
        +
      • +
      +
    • +
    • ios/ +
        +
      • <component_name>/ +
          +
        • Rust<ComponentName>API.h (low-level bindings to the C-style FFI)
        • +
        • Higher-level hand-written Swift that wraps the FFI.
        • +
        +
      • +
      +
    • +
    +
  • +
+

The goal here is to replace much of the hand-written wrapper layers with autogenerated +code:

+
    +
  • The ./ffi/ crate will disappear entirely, its work is automated by UniFFI +
      +
    • If you still need some hand-written pub extern "C" functions, perhaps to +implement features not currently supported by UniFFI, then they should move +into lib.rs of the main component crate.
    • +
    +
  • +
  • The low-level Lib<ComponentName>FFI.kt file will disappear entirely, as will some of the +code that converts it back into nice high-level Kotlin classes and interfaces. +
      +
    • Some of the hand-written Kotlin code may remain, if it provides functionality that +cannot be implemented in Rust.
    • +
    +
  • +
  • The low-level Rust<ComponentName>API.h file will disappear entirely, as will some of the +code that converts it back into nice high-level Swift classes and interfaces. +
      +
    • Some of the hand-written Swift code may remain, if it provides functionality that +cannot be implemented in Rust.
    • +
    +
  • +
+

You'll aim to end up with a simplified file structure that looks like this:

+
    +
  • components/<component_name>/ +
      +
    • Cargo.toml
    • +
    • uniffi.toml
    • +
    • src/ +
        +
      • <component_name>.udl (abstract interface definition)
      • +
      • Rust code here.
      • +
      +
    • +
    • android/ +
        +
      • build.gradle
      • +
      • src/ +
          +
        • main/ +
            +
          • AndroidManifest.xml
          • +
          • java/mozilla/appservices/<component_name>/ +
              +
            • Optional hand-written Kotlin code here.
            • +
            +
          • +
          +
        • +
        +
      • +
      +
    • +
    • ios/ +
        +
      • <component_name>/ +
          +
        • Optional hand-written Swift code here.
        • +
        +
      • +
      +
    • +
    +
  • +
+

Write a first draft of the .udl file for the component's interface

+

Make sure you've got the uniffi-bindgen command available; cargo install uniffi_bindgen will +ensure you have the latest version.

+

Create ./src/<component_name>.udl and try to describe the intended interface for the component +using UniFFI's interface definition language. +You'll probably need to reverse-engineer it a little bit from the existing hand-written Kotlin and/or +Swift code.

+

Don't spend too much time on trying to match every minute detail of the existing hand-written API. +There are likely to be small differences between how UniFFI likes to do things and how the hand-written +APIs were structured, and it's in everyone's best long-term interests to just push ahead and update +consumers to accommodate any breaking API changes, rathern than e.g. trying to convince UniFFI to +capitalize enum variant names in the same style that the hand-written code was using.

+

To check whether the .udl file is syntactically valid, you can use uniffi-bindgen to generate +the Rust FFI scaffolding like so:

+
uniffi-bindgen scaffolding ./src/<component_name>.udl
+
+

If this succeeds, it will generate a file ./src/<component_name>.uniffi.rs with a bunch of +thorny auto-generated Rust code. If it fails, it will likely fail with an inscrutable error message. +Unfortunately the error reporting in UniFFI is currently a known pain point, and it can take a +bit of trial-and-error to identify what part of the file is causing the issue. Sorry :-(

+

The aim at this point is to ensure that the intended interface of the component can be expressed +in terms that UniFFI understands. Most cases should be supported, but you may find some aspect of +the existing component that is hard to express in UniFFI, perhaps even uncovering new functionality +that needs to be added to UniFFI itself!

+

The .udl file is definitely a first draft at this point. It is normal and expected to need +to iterate on this file as you port over the underlying Rust code.

+

Restructure the Rust code to introduce UniFFI

+

You will now restructure the existing Rust crate so that its public API surface +and overall "shape" match what you defined in the .udl file.

+

Start by deleting the ./ffi sub-crate, because you're going to use UniFFI to generate +all of that code. You'll also need to remove it from the workspace in the top-level +Cargo.toml file, as well as change the crates under /megazords to import the core +Rust crate for the component rather than importing the FFI sub-crate.

+

Add UniFFI to the crate's dependencies and configure its build.rs script to invoke the +UniFFI scaffolding generator, as described in "adding a new component".

+

Now, edit ./lib.rs so that it matches the interface defined in the .udl file as closely +as possible. If the .udl has an interface Example then lib.rs should contain a +pub struct Example, if the .udl contains an enum ExampleItem then lib.rs should +contain a pub enum ExampleItem, and so-on.

+

The details of this step will depend heavily on the specific crate, but some tips include:

+
    +
  • +

    You may find it useful to move all of the existing code into a sub-module named internal, +and then make a brand new lib.rs that imports or re-defines just the pieces it needs +in order to implement the interface from the .udl file. The fxa-client crate is an +example of a case where this worked out well, though of course your mileage may vary.

    +
  • +
  • +

    If the existing crate contains a file named like <component_name>_msg_types.proto, then +it was using Protocol Buffers to serialize data to pass over the FFI. The message types +defined in the .proto file will need to be converted into dictionary or enum definitions +in your .udl file. See the section below for more details.

    +
  • +
+

As noted above, don't be afraid to accept some API churn during the conversion process. +We're willing to accept some breaking API changes as the cost of getting bindings generated +for free, as long as the core functionality and mental model of the component remain intact.

+

At this point, in theory the crate should be buildable with UniFFI, although it's likely +to require some iteration to get it all working! Run cargo check to check for any +compilation errors without having to do a full build.

+

Removing Protobuf Messages

+

Passing rich structured data over the FFI is the most complex part of our hand-written bindings, +and was previously done by serializing data via Protocol Buffers. +This is something that UniFFI tries to make as simple as possible.

+

Start by locating the <component_name>_msg_types.proto file for the component. This file defines +the structured messages that can be passed over the FFI, and you should see that they correspond +to various types of structured data that the component wants to receive from, or return to, +the foreign-language code.

+

Find the places in your .udl interface that correspond to these message types and make sure +that you've got a similarly-shaped dictionary or enum for each one. You should find that +representing this structured data in UDL is simpler than protobuf in many cases - for example +many of our .protobuf files need to use a separate ExampleStructs message in order to +pass a list of ExampleStruct messages over the FFI, but in UniFFI this is represented +directly as sequence<ExampleStruct>.

+

Find the places in the Rust code that are using these message types to return structured data. +In simple cases, you may be able to directly replace uses of msg_types::ExampleStruct with +the corresponding crate::ExampleStruct from your public API. +For more complex cases, you may find it helpful to define an Into mapping between the +UniFFI dictionary/enum in the crate's public interface, and a more complex struct designed +for internal use.

+

As noted above, don't be afraid to accept some API churn during this conversion process.

+

Once you have replaced all uses of the msg_types structs in the Rust code:

+
    +
  • Delete ./src/<component_name>_msg_types.proto.
  • +
  • Delete ./src/mozilla.appservices.<component_name>.protobuf.rs, which is generated from the .proto file.
  • +
  • Remote prost and prost-derive from the crate's dependencies.
  • +
  • Delete the crate from the list in /tools/protobuf_files.toml.
  • +
+

If you happen to find that you've deleted the last crate from the list in protobuf_files.toml, +congratulations! You've successfully removed protocol buffers from this repo entirely, and should +file a bug to track the complete removal of protobuf from our tooling and dependency chain.

+

Document the Public API in the Rust code

+

Write consumer-facing documentation on the public API in lib.rs using Rust's standard +rustdoc conventions +and tools. The fxa-client crate may serve as a good example.

+

You can view the generated documentation by running:

+
cargo doc --no-deps --open
+
+

In future, we intend to automatically extract documentation from the Rust code +and make it easily available to consumers of the generated bindings.

+

(In fact there is some work-in-progress code in uniffi-rs#416 +that can read docs from the Rust code and write them back into the .udl file, which you're +welcome to try out if you're feeling adventurous. But it's just a very hacky prototype.)

+

Set up the Kotlin wrapper

+

It's easiest to start by removing all of the hand-written Kotlin code under android/src/main/java +and then restoring parts of it later if necessary. Leave the AndroidManifest.xml file and any tests +in place.

+

Delete the android/build.gradle file and then follow the instructions for adding Kotlin bindings +for a new component to create a new build.gradle +file and a corresponding uniffi.toml.

+

This should be all that's required to set up UniFFI to build the Kotlin bindings. Try building +the Android package to confirm:

+
    +
  • ./gradlew <component_name>:assembleDebug
  • +
+

The UniFFI-generated Kotlin code will be under ./android/build/generated/source/uniffi/ and +may be useful for debugging.

+

If there are existing Kotlin tests for the component, the next step is to get those passing:

+
    +
  • ./gradlew <component_name>:test
  • +
+

As noted above, it is normal and expected for the autogenerated bindings to be subtly different +from the previous hand-written ones. For example, UniFFI insists on using SHOUTY_SNAKE_CASE +variant names in Kotlin enums while the hand-written code may have used CamelCase. Some components +also have small naming differences between the Rust code and the hand-written Kotlin bindings, +which UniFFI will not allow.

+

If the component had functionality in its Kotlin layer that was not part of the Rust API, +then you'll need to add some hand-written Kotlin code under android/src/main/java to +implement it. The fxa-client component may be a good example here: its Rust layer exposes +a FirefoxAccount struct that the Kotlin code wraps into a PersistedFirefoxAccount class, +adding the ability to set a persistence callback.

+

Finally, you will need to try out the new bindings with a consuming app. For Kotlin code you should +make a local build of android-components and Fenix, +updating them to accomodate any changes in the component's public API.

+

Set up the Swift wrapper

+

It's easiest to start by removing all of the hand-written Swift code under ./ios and then +restoring parts of it later if necessary.

+

Edit /megazords/ios-rust/MozillaTestServices.h to remove any references to Rust<ComponentName>API.h, +replacing them with the UniFFI-generated header file name <component_name>FFI.h.

+

Open /megazords/ios-rust/MozillaTestServices.xcodeproj in Xcode and follow the instructions for +adding Swift bindings for a new component to +configure Xcode to build your UniFFI-generated bindings.

+

While you are in the Xcode Project Navigator, you should also delete any references to +Rust<ComponentName>API.h or to the old hand-written Swift wrappers. (They should be highlighted +in red in the Project Navigator, because the files will be missing from disk after you +deleted them above).

+

This should be all that's required to set up UniFFI to build the Swift bindings. Try building +the project in Xcode to confirm.

+

The UniFFI-generated Swift code will be under ios/Generated and may be useful for debugging.

+

If there are existing Swift tests for the component, the next step is to get those passing:

+
    +
  • ./automation/run_ios_tests.sh
  • +
  • (or run them from the Xcode GUI)
  • +
+

As noted above, it is normal and expected for the autogenerated bindings to be subtly different +from the previous hand-written ones. Many existing components have small naming differences +between the Rust code and the hand-written Swift bindings, which UniFFI will not allow.

+

If the component had functionality in its Swift layer that was not part of the Rust API, +then you'll need to add some hand-written Swift code under ./ios/<ComponentName> to +implement it. The fxa-client component may be a good example here: its Rust layer exposes +a FirefoxAccount struct that the Swift code wraps into a PersistedFirefoxAccount class, +adding the ability to set a persistence callback.

+

You will need to add any such file to the "Compile Sources" list in Xcode, in the same way +that you added the .udl file.

+

Finally, you will need to try out the new bindings with a consuming app. For Swift code you should make a local build of Firefox iOS, you can do that by following the steps in this document

+

Rust + Android FAQs

+

How do I expose Rust code to Kotlin?

+

Use UniFFI, which can produce Kotlin +bindings for your Rust code from an interface definition file.

+

If UniFFI doesn't currently meet your needs, please open an issue to discuss how the tool can +be improved.

+

As a last resort, you can make hand-written bindings from Rust to Kotlin, +essentially manually performing the steps that UniFFI tries to automate +for you: flatten your Rust API into a bunch of pub extern "C" functions, +then use JNA to call them +from Kotlin. The details of how to do that are well beyond the scope of +this document.

+

How should I name the package?

+

Published packages should be named org.mozilla.appservices.$NAME where $NAME +is the name of your component, such as logins. The Java namespace in which +your package defines its classes etc should be mozilla.appservices.$NAME.*.

+

How do I publish the resulting package?

+

Add it to .buildconfig-android.yml in the root of this repository. +This will cause it to be automatically included as part of our release +publishing pipeline.

+

How do I know what library name to load to access the compiled rust code?

+

Assuming that you're building the Rust code as part of the application-services +build and release process, your pub extern "C" API should always be available +from a file named libmegazord.so.

+

What challenges exist when calling back into Kotlin from Rust?

+

There are a number of them. The issue boils down to the fact that you need to be +completely certain that a JVM is associated with a given thread in order to call +java code on it. The difficulty is that the JVM can GC its threads and will not +let rust know about it.

+

JNA can work around this for us to some extent, at the cost of some complexity. +The approach it takes is essentially to spawn a thread for each callback +invocation. If you are certain you’re going to do a lot of callbacks and they +all originate on the same thread, you can have them all run on a single thread +by using the CallbackThreadInitializer.

+

With the help of JNA's workarounds, calling back from Rust into Kotlin isn’t too bad +so long as you ensure that Kotlin cannot GC the callback while rust code holds onto it +(perhaps by stashing it in a global variable), and so long as you can either accept the overhead of extra threads being instantiated on each call or are willing to manage +the threads explicitly.

+

Note that the situation would be somewhat better if we used JNI directly (and +not JNA), but this would cause us to need to generate different Rust FFI code for +Android than for iOS.

+

Ultimately, in any case where there is an alternative to using a callback, you +should probably pursue that alternative.

+

For example if you're using callbacks to implement async I/O, it's likely better to +move to doing a blocking call, and have the calling code dispatch it on a background +thread. It’s very easy to run such things on a background thread in Kotlin, is in line +with the Android documentation on JNI usage, and in our experience is vastly simpler +and less painful than using callbacks.

+

(Of course, not every case is solvable like this).

+

Why are we using JNA rather than JNI, and what tradeoffs does that involve?

+

We get a couple things from using JNA that we wouldn't with JNI.

+
    +
  1. +

    We are able to use the same Rust FFI code on all platforms. If we used JNI we'd +need to generate an Android-specific Rust FFI crate that used the JNI APIs, and +a separate Rust FFI crate for exposing to Swift.

    +
  2. +
  3. +

    JNA provides a mapping of threads to callbacks for us, making callbacks over +the FFI possible. That said, in practice this is still error prone, and easy +to misuse/cause memory safety bugs, but it's required for cases like logging, +among others, and so it is a nontrivial piece of complexity we'd have to +reimplement.

    +
  4. +
+

However, it comes with the following downsides:

+
    +
  1. JNA has bugs. In particular, its not safe to use bools with them, it thinks +they are 32 bits, when on most platforms (every platform Rust supports) they +are 8 bits. They've been unwilling to fix the issue due to it breaking +backwards compatibility (which is... somewhat fair, there is a lot of C89 +code out there that uses bool as a typedef for a 32-bit int).
  2. +
  3. JNA makes it really easy to do the wrong thing and have it work but corrupt +memory. Several of the caveats around this are documented in the +ffi_support docs, but a +major one is when to use Pointer vs String (getting this wrong will +often work, but may corrupt memory).
  4. +
+

We aim to avoid triggering these bugs by auto-generating the JNA bindings +rather than writing them by hand.

+

How do I debug Rust code with the step-debugger in Android Studio

+
    +
  1. Uncomment the packagingOptions { doNotStrip "**/*.so" } line from the +build.gradle file of the component you want to debug.
  2. +
  3. In the rust code, either: +
      +
    1. Cause something to crash where you want the breakpoint. Note: Panics +don't work here, unfortunately. (I have not found a convenient way to +set a breakpoint to rust code, so +unsafe { std::ptr::write_volatile(0 as *const _, 1u8) } usually is +what I do).
    2. +
    3. If you manage to get an LLDB prompt, you can set a breakpoint using +breakpoint set --name foo, or breakpoint set --file foo.rs --line 123. +I don't know how to bring up this prompt reliably, so I often do step 1 to +get it to appear, delete the crashing code, and then set the +breakpoint using the CLI. This is admittedly suboptimal.
    4. +
    +
  4. +
  5. Click the Debug button in Android Studio, to display the "Select Deployment +Target" window.
  6. +
  7. Make sure the debugger selection is set to "Both". This tends to unset +itself, so make sure.
  8. +
  9. Click "Run", and debug away.
  10. +
+

Breaking changes in application-services code

+

Application-services components are consumed by multiple consumers including Firefox Android, +Firefox iOS, Focus Android, and Focus iOS. To minimize the disruption to those projects when making +breaking API changes, we follow a simple rule: Have approved PRs ready to land that fix the +breakage in the other repos before merging the PR into application-services.

+

This means writing code for the +firefox-android and +firefox-ios repositories that resolves any +breaking changes, creating a PR in those repositories, and waiting for it to be approved.

+

You can test this code locally using the autopublish flow (Android, iOS) and use the branch build system to run CI tests.

+

Merging

+

Do not merge any PRs until all are approved. Once they are all approved then:

+
    +
  • Merge the application-services PR into main
  • +
  • Manually trigger a new nightly build using the taskcluster hook: +https://firefox-ci-tc.services.mozilla.com/hooks/project-releng/cron-task-mozilla-application-services%2Fnightly
  • +
  • Once the nightly task completes, trigger a new rust-components-swift build using the github action: +https://github.com/mozilla/rust-components-swift/actions/workflows/update-as-nightly.yml
  • +
  • Update the firefox-android and firefox-ios PRs to use the newly built nightly: + +
  • +
  • Ideally, get the PRs merged before the firefox-android/firefox-ios nightly bump the next day. +If you don't get these merged, then the nightly bump PR will fail. Add a link to your PR in +the nightly bump PR so the mobile teams know how to fix this.
  • +
+

Vendoring Application Services into mozilla-central

+

Some of these components are used in mozilla-central. +This document describes how to update existing components or add new components.

+

The general process for vendoring rust code into mozilla-central has its own +documentation - +please make sure you read that before continuing.

+

When to vendor

+

We want to keep our versions in moz-central relatively up-to-date, but it takes some manual effort +to do. The main possibility of breakage is from a dependency mismatch, so our current vendoring +policy is:

+
    +
  • Whenever a 3rd-party dependency is added or updated, the dev who made the change is responsible +for vendoring.
  • +
  • At the start of the release cycle the +triage owner is response for vendoring.
  • +
+

Updating existing components.

+

To update components which are already in mozilla-central, follow these steps:

+
    +
  1. +

    Ensure your mozilla-central build environment is setup correctly to make +"non-artifact" builds - check you can get a full working build before +starting this process.

    +
  2. +
  3. +

    Run ./tools/update-moz-central-vendoring.py [path-to-moz-central] from the application-services +root directory.

    +
  4. +
  5. +

    If this generates errors regarding duplicate crates, you will enter a world +of pain, and probably need to ask for advice from the application-services +team, and/or the #build channel on matrix.

    +
  6. +
  7. +

    Run ./mach cargo vet to check if there any any new dependencies that need to be vetted. If +there are ask for advice from the application-services team.

    +
  8. +
  9. +

    Build and test your tree. Ideally make a try run.

    +
  10. +
  11. +

    Put your patch up to phabricator, requesting review from, at least, someone +on the application-services team and one of the "build peers" - asking on +#build on matrix for a suitable +reviewer might help. Alternatively, try and find the bug which made the +most recent update and ask the same reviewer in that patch.

    +
  12. +
  13. +

    Profit!

    +
  14. +
+

Adding a new component

+

Follow the Uniffi documentation on mozilla-central to understand where you'll need to add your crate path and UDL. In general:

+
    +
  • The consuming component will specify the dependency as a nominal "version 0.1"
  • +
  • The top-level Cargo.toml will override that dependency with a specific git +revision.
  • +
+

For example, consider the webext-storage crate:

+ +

Adding a new component implies there will be related mozilla-central changes +which leverage it. The best practice here is to land both the vendoring of the +new component and the related mozilla-central changes in the same bug, but in +different phabricator patches. As noted above, the best-practice is that all +application-services components are on the same revision, so adding a new +component implies you will generally also be updating all the existing +components.

+

For an example of a recently added component, the tabs was recently added to mozilla-central with uniffi and shows a general process to follow.

+

Vendoring an unreleased version for testing purposes

+

Sometimes you will need to make changes in application-services and in mozilla-central +simultaneously - for example, you may need to add new features or capabilities +to a component, and matching changes in mozilla-central to use that new feature.

+

In that scenario, you don't want to check your changes in and re-vendor as you +iterate - it would be far better to use a local checkout of application-services +with uncommitted changes with your mozilla-central tree which also has uncommited +changes.

+

To do this, you can edit the top-level Cargo.toml to specify a path. Note +however that in this scenario, you need to specify the path to the +individual component rather than to the top-level of the repo.

+

For example, you might end up with something like:

+
# application-services overrides to make updating them all simpler.
+interrupt-support = { path = "../application-services/components/support/interrupt" }
+sql-support = { path = "../application-services/components/support/sql" }
+sync15-traits = { path = "../application-services/components/support/sync15-traits" }
+viaduct = { path = "../application-services/components/viaduct" }
+webext-storage = { path = "../application-services/components/webext-storage" }
+
+

Note that when you first do this, it will still be necessary to run +./mach vendor rust and to re-build.

+

After you make a change to the local repository, you do not need to run +./mach vendor rust, but you do still obviously need to rebuild.

+

Once you are happy with all the changes, you would:

+
    +
  • Open a PR up in application-services and land your changes there.
  • +
  • Follow the process above to re-vendor your new changes, and in that same +bug (although not necessarily the same phabricator patch), include the other +mozilla-central changes which rely on the new version.
  • +
+

Application Services Logging

+

When writing code in application-services, code implemented in Rust, Kotlin, +Java, or Swift might have to write debug logs. To do so, one should generally +log using the normal logging facilities for the language. Where the logs go +depends on the application which is embedding the components.

+

Accessing logs when running Fenix

+

On android, logs currently go to logcat. (This may change in the future.) +Android Studio can be used to view the logcat logs; connect the device over USB +and view the Logcat tab at the bottom of Android Studio. Check to make sure you +have the right device selected at the top left of the Logcat pane, and the +correct process to the right of that. One trick to avoid having to select the +correct process (as there are main and content processes) is to choose "No +Filters" from the menu on the top right of the Logcat pane. Then, use the search +box to search for the log messages you are trying to find.

+

There are also many other utilities, command line and graphical, that can be +used to view logcat logs from a connected android device in a more flexible +manner.

+

Changing the loglevel in Fenix

+

If you need more verbose logging, after the call to RustLog.enable() in +FenixApplication, you may call RustLog.setMaxLevel(Log.Priority.DEBUG, true).

+

Accessing logs when running iOS

+

[TODO]

+

UniFFI object destruction on Kotlin

+

UniFFI supports interface objects, which are implemented by Boxing a Rust object and sending the raw pointer to the foreign code. Once the objects are no longer in use, the foreign code needs to destroy the object and free the underlying resources.

+

This is slightly tricky on Kotlin. The prevailing Java wisdom is to use explicit destructors and avoid using finalizers for destruction, which means we can't simply rely on the garbage collector to free the pointer. The wisdom seems simple to follow, but in practice it can be difficult to know how to apply it to specific situations. This document examines provides guidelines for handling UniFFI objects.

+

You can create objects in a function if you also destroy them there

+

The simplest way to get destruction right is to create an object and destroy it in the same function. The use function makes this really easy:

+
SomeUniFFIObject()
+  .use { obj ->
+      obj.doSomething()
+      obj.doSomethingElse()
+  }
+
+

You can create and store objects in singletons

+

If we are okay with UniFFI objects living for the entire application lifetime, then they can be stored in singletons. This is how we handle our database connections, for example SyncableLoginsStorage and PlacesReaderConnection.

+

You can create and store objects in an class, then destroy them in a corresponding lifecycle method

+

UniFFI objects can stored in classes like the Android Fragment class that have a defined lifecycle, with methods called at different stages. Classes can construct UniFFI objects in one of the lifecycle methods, then destroy it in the corresponding one. For example, creating an object in Fragment.onCreate and destroying it in Fragment.onDestroy().

+

You can share objects

+

Several classes can hold references to an object, as long as (exactly) one class is responsible for managing it and destroying it when it's not used. A good example is the GeckoLoginStorageDelegate. The LoginStorage is initialized and managed by another object, and GeckoLoginStorageDelegate is passed a (lazy) reference to it.

+

Care should be taken to ensure that once the managing class destroys the object, no other class attempts to use it. If they do, then the generate code will raise an IllegalStateException. This clearly should be avoided, although it won't result in memory corruption.

+

Destruction may not always happen

+

Destructors may not run when a process is killed, which can easily happen on Android. This is especially true of lifecycle methods. This is normally fine, since the OS will close resources like file handles and network connections on its own. However, be aware that custom code in the destructor may not run.

+

Architectural Decision Log

+

This log lists the architectural decisions for MADR.

+ +
    +
  • ADR-0000 - Use Markdown Architectural Decision Records
  • +
  • ADR-0001 - Update Logins API
  • +
  • ADR-0002 - Handling Database Corruption
  • +
  • ADR-0003 - Distributing Swift Packages
  • +
  • ADR-0004 - Running experiments on first run early startup
  • +
  • ADR-0005 - A remote-settings client for our mobile browsers.
  • +
  • ADR-0007 - Limit Visits Migrated to Places History in Firefox iOS
  • +
+ +

For new ADRs, please use template.md as basis. +More information on MADR is available at https://adr.github.io/madr/. +General information about architectural decision records is available at https://adr.github.io/.

+

Use Markdown Architectural Decision Records

+

Context and Problem Statement

+

We want to record architectural decisions made in this project. +Which format and structure should these records follow?

+

Considered Options

+ +

Decision Outcome

+

Chosen option: "MADR 2.1.2", because

+
    +
  • Implicit assumptions should be made explicit. +Design documentation is important to enable people understanding the decisions later on. +See also A rational design process: How and why to fake it.
  • +
  • The MADR format is lean and fits our development style.
  • +
  • The MADR structure is comprehensible and facilitates usage & maintenance.
  • +
  • The MADR project is vivid.
  • +
  • Version 2.1.2 is the latest one available when starting to document ADRs.
  • +
+ +

Update Logins API

+
    +
  • Status: accepted
  • +
  • Date: 2021-06-17
  • +
+

Technical Story: #4101

+

Context and Problem Statement

+

We no longer want to depend on SQLCipher and want to use SQLite directly for build complexity and concerns over the long term future of the rust bindings. The encryption approach taken by SQLCipher means that in practice, the entire database is decrypted at startup, even if the logins functionality is not interacted with, defeating some of the benefits of using an encrypted database.

+

The per-field encryption in autofill, which we are planning to replicate in logins, separates the storage and encryption logic by limiting the storage layer to the management of encrypted data. Applying this approach in logins will break the existing validation and deduping code so we need a way to implement per-field encryption while supporting the validation and de-duping behavior.

+

Decision Drivers

+
    +
  • Addressing previously identified deficiencies in the logins API while we are breaking the API for the encryption work
  • +
  • Continuing to support the existing logins validation and deduping logic
  • +
  • Avoiding the implementation of new security approaches that may require additional time and security resources
  • +
  • Establishing a standard encyrption approach across components
  • +
+

Considered Options

+
    +
  • Option 1 - Reduce the API functions that require the encryption key and pass the key to the remaining functions
  • +
  • Option 2 - Keep the general shape of the API that is in place now - the app can pass the encryption key at any time to "unlock" the API, and re-lock it at any time, but the API in its entirety is only available when unlocked
  • +
+

Decision Outcome

+

Chosen Option: "Reduce the API functions that require the encryption key and pass the key to the remaining functions" because it will not require a security review as similar to the approach we have established in the codebase.

+

Pros and Cons of the Options

+

Option 1 - Reduce the API functions that require the encryption key and pass the key to the remaining functions

+
    +
  • +

    Description

    +

    Currently the below logins API functions would require the per-field encryption key:

    + +

    Propsed changes:

    +
      +
    • Combine the add and update functions into a new add_or_update function +
        +
      • This will allow the removal of consumer code that distinguishes when a login record should be created or updated
      • +
      • Note: This function needs the encryption key for the fixup and deduping logic and for continued support of the accurate population of the time_password_changed field
      • +
      +
    • +
    • Pass the per-field encryption key to the import_multiple function +
        +
      • This function will be removed once the Fennec to Fenix migration period ends
      • +
      +
    • +
    • Remove both the potential_dupes_ignoring_username and check_valid_with_no_dupes from the API +
        +
      • Neither function is called in Firefox iOS
      • +
      • Android Components uses both to provide validation and de-duping before logins are added or updated so we can eliminate the need to externalize these functions by replicating this logic in the new add_or_update function
      • +
      +
    • +
    • Create a decrypt_and_fixup_login function that both decrypts a login and performs the validate and fixup logic +
        +
      • This will eliminate the need for the get_all, get_by_base_domain, and get_by_id API functions to perform the fixup logic
      • +
      +
    • +
    +

    Making the above changes will reduce the API functions requiring the encryption key to the following:

    +
      +
    • add_or_update
    • +
    • decrypt_and_fixup_login
    • +
    • import_multiple
    • +
    +
  • +
  • +

    Pros

    +
      +
    • Improves the logins API for consumers by combining add/update functionality (see #3899 for details)
    • +
    • Removes redundant validation and de-duping logic in consumer code
    • +
    • Uses the same encryption model as autofill so there is consistency in our approaches
    • +
    +
  • +
  • +

    Cons

    +
      +
    • Requires consumer code to both encrypt login fields and pass the encryption key when calling either add_or_update and import_multiple
    • +
    +
  • +
+

Option 2 - Implement a different key management approach

+
    +
  • +

    Description

    +

    Unlike the first option, the publicly exposed login API would only handle decrypted login records and all encryption is internal (which works because we always have the key). Any attempt to use the API will fail as the login records are not encrypted or decrypted if the key is not available.

    +

    Proposed changes:

    +
      +
    • Combine the add and update functions into add_or_update
    • +
    • Remove both the potential_dupes_ignoring_username and check_valid_with_no_dupes from the API
    • +
    +
  • +
  • +

    Pros

    +
      +
    • Prevents the consumer from having to encrypt or decrypt login records
    • +
    • Maintains our current fixup and validation approach
    • +
    • Improves the logins API for consumers by combining add/update functionality
    • +
    • Removes redundant validation and de-duping logic in consumer code
    • +
    +
  • +
  • +

    Cons

    +
      +
    • Makes us responsible for securing the encryption key and will most likely require a security review
    • +
    +
  • +
+ + +

Handling Database Corruption

+
    +
  • Status: accepted
  • +
  • Date: 2021-06-08
  • +
+

Context and Problem Statement

+

Some of our users have corrupt SQLite databases and this makes the related +component unusable. The best way to deal with corrupt databases is to simply +delete the database and start fresh (#2628). However, we only want to do this +for persistent errors, not transient errors like programming logic errors, disk +full, etc. This ADR deals with 2 related questions:

+
    +
  • A) When and how do we identify corrupted databases?
  • +
  • B) What do we do when we identify corrupted databases?
  • +
+

Decision Drivers

+
    +
  • Deleting valid user data should be avoided at almost any cost
  • +
  • Keeping a corrupted database around is almost as bad. It currently prevents +the component from working at all.
  • +
  • We don't currently have a good way to distinguish between persistent and +transient errors, but this can be improved by reviewing telemetry and sentry +data.
  • +
+

Considered Options

+
    +
  • A) When and how do we identify corrupted databases? +
      +
    • 1: Assume all errors when opening a database are from corrupt databases
    • +
    • 2: Check for errors when opening a database and compare against known corruption error types
    • +
    • 3: Check for errors for all database operations and compare against known corruption error types
    • +
    +
  • +
  • B) What do we do when we identify corrupted databases? +
      +
    • 1: Delete the database file and recreate the database
    • +
    • 2: Move the database file and recreate the database
    • +
    • 3: Have the component fail
    • +
    +
  • +
+

Decision Outcome

+
    +
  • A2: Check for errors when opening a database and compare against known corruption error types
  • +
  • B1: Delete the database file and recreate the database
  • +
+

Decision B follows from the choice of A. Since we're being conservative in +identifying errors, we can delete the database file with relative confidence.

+

"Check for errors for all database operations and compare against known +corruption error types" also seems like a reasonable solution that we may +pursue in the future, but we decided to wait for now. Checking for errors +during opening time is the simpler solution to implement and should fix the +issue in many cases. The plan is to implement that first, then monitor +sentry/telemetry to decide what to do next.

+

Pros and Cons of the Options

+

A1: Assume all errors when opening a database are from corrupt databases

+
    +
  • Good, because the sentry data indicates that many errors happen during opening time
  • +
  • Good, because migrations are especially likely to trigger corruption errors
  • +
  • Good, because it's a natural time to delete the database -- the consumer code +hasn't run any queries yet and doesn't have any open connections.
  • +
  • Bad, because it will delete valid user data in several situations that are +relatively common: migration logic errors, OOM errors, Disk full.
  • +
+

A2: Check for errors when opening a database and compare against known corruption error types (Decided)

+
    +
  • Good, because should eliminate the possibility of deleting valid user data.
  • +
  • Good, because the sentry data indicates that many errors happen during opening time
  • +
  • Good, because it's a natural time to delete the database -- the consumer code +hasn't run any queries yet and doesn't have any open connections.
  • +
  • Bad, because we don't currently have a good list corruption errors
  • +
+

A3: Check for errors for all database operations and compare against known corruption error types

+
    +
  • Good, because the sentry data indicates that many errors happen outside of opening time
  • +
  • Good, because should eliminate the possibility of deleting valid user data.
  • +
  • Bad, because the consumer code probably doesn't expect the database to be +deleted and recreated in the middle of a query. However, this is just an +extreme case of normal database behavior -- for example any given row can be +deleted during a sync.
  • +
  • Bad, because we don't currently have a good list corruption errors
  • +
+

B1: Delete the database file and recreate the database (Decided)

+
    +
  • Good, because it would allow users with corrupted databases to use the +affected components again
  • +
  • Bad, because any misidentification will lead to data loss.
  • +
+

B2: Move the database file and recreate the database

+

This option would be similar to 1, but instead of deleting the file we would +move it to a backup location. When we started up, we could look for backup +files and try to import lost data.

+
    +
  • Good, because if we misidentify corrupt databases, then we have the +possibility of recovering the data
  • +
  • Good, because it allows a way for users to delete their data (in theory). +If the consumer code executed a wipe() on the database, we could also +delete any backup data.
  • +
  • Bad, because it's very difficult to write a recovery function that merged +deleted data with any new data. This function would be fairly hard to test +and it would be easy to introduce a new logic error.
  • +
  • Bad, because it adds significant complexity to the database opening code
  • +
  • Bad, because the user experience would be strange. A user would open the +app, discover that their data was gone, then sometime later discover that the +data is back again.
  • +
+

B3: Return a failure code

+
    +
  • Good, because this option leaves no chance of user data being deleted
  • +
  • Good, because it's the simplest to implement
  • +
  • Bad, because the component will not be usable if the database is corrupt
  • +
  • Bad, because the user's data is potentially exposed in the corrupted database +file and we don't provide any way for them to delete it.
  • +
+

Distributing Swift Packages

+
    +
  • Status: accepted
  • +
  • Deciders: rfkelly
  • +
  • Date: 2021-07-22
  • +
+

Context and Problem Statement

+

Our iOS consumers currently obtain application-services as a pre-compiled .framework bundle +distributed via Carthage. The current setup is not +compatible with building on new M1 Apple Silicon machines and has a number of other problems. +As part of a broader effort to modernize the build process of iOS applications at Mozilla, +we have been asked to re-evaluate how application-services components are dsitributed for iOS.

+

See Problems with the current setup for more details.

+

Decision Drivers

+
    +
  • Ease-of-use for iOS consumers.
  • +
  • Compatibility with M1 Apple Silicon machines.
  • +
  • Consistency with other iOS components being developed at Mozilla.
  • +
  • Ability for the Nimbus Swift bindings to easily depend on Glean.
  • +
  • Ease of maintainability for application-services developers.
  • +
+

Considered Options

+
    +
  • (A) Do Nothing +
      +
    • Keep our current build and distribution setup as-is.
    • +
    +
  • +
  • (B) Use Carthage to build XCFramework bundles +
      +
    • Make a minimal change to our Carthage setup so that it builds +the newer XCFramework format, which can support M1 Apple Silicon.
    • +
    +
  • +
  • (C) Distribute a single pre-compiled Swift Package +
      +
    • Convert the all-in-one MozillaAppServices Carthage build to a similar +all-in-one Swift Package, distributed as a binary artifact.
    • +
    +
  • +
  • (D) Distribute multiple source-based Swift Package targets, with pre-compiled Rust code +
      +
    • Split the all-in-one MozillaAppServices Carthage build into a separate +Swift Package target for each component, with a shared dependency on pre-compiled +Rust code as a binary artiact.
    • +
    +
  • +
+

Decision Outcome

+

Chosen option: (D) Distribute multiple source-based Swift Packages, with pre-compiled Rust code.

+

This option will provide the best long-term consumer experience for iOS developers, and +has the potential to simplify maintenance for application-services developers after an +initial investment of effort.

+

Positive Consequences

+
    +
  • Swift packages are very convenient to consume in newer versions of Xcode.
  • +
  • Different iOS apps can choose to import a different subset of the available components, +potentiallying helping keep application size down.
  • +
  • Avoids issues with mis-matched Swift version between application-services build +and consumers, since Swift files are distributed in source form.
  • +
  • Encourages better conceptual separation between Swift code for different components; +e.g. it will make it possible for two Swift components to define an item of the same +name without conflicts.
  • +
  • Reduces the need to use Xcode as part of application-services build process, in favour +of command-line tools.
  • +
+

Negative Consequences

+
    +
  • More up-front work to move to this new setup.
  • +
  • We may be less likely to notice if our build setup breaks when used from within Xcode, +because we're not exercising that code path ourselves.
  • +
  • May be harder to concurrently publish a Carthage framework for current consumers who aren't +able to move to Swift packages.
  • +
  • There is likely to be some amount of API breakage for existing consumers, if only in having +to replace a single import MozillaAppServices with independent imports of each component.
  • +
+

Implementation Sketch

+

We will maintain the existing Carthage build infrastructure in the application-services repo and continue publishing a pre-built Carthage framework, +to support firefox-ios until they migrate to Swift Packages.

+

We will add an additional iOS build task in the application-services repo, that builds just the Rust code as a .xcframework bundle. +An initial prototype shows that this can be achieved using a relatively straightforward shell script, rather than requiring a second Xcode project. +It will be published as a .zip artifact on each release in the same way as the current Carthage framework. +The Rust code will be built as a static library, so that the linking process of the consuming application can pull in +just the subset of the Rust code that is needed for the components it consumes.

+

We will initially include only Nimbus and its dependencies in the .xcframework bundle, +but will eventually expand it to include all Rust components (including Glean, which will continue +to be included in the application-services repo as a git submodule)

+

We will create a new repository rust-components-swift to serve as the root of the new Swift Package distribution. +It will import the application-services repository as a git submodule. This will let us iterate quickly on the +Swift packaging setup without impacting existing consumers.

+

We will initially include only Nimbus and its dependencies in this new repository, and the Nimbus swift code +it will depend on Glean via the external glean-swift package. In the future we will publish all application-services +components that have a Swift interface through this repository, as well as Glean and any future Rust components. +(That's why the repository is being given a deliberately generic name).

+

The rust-components-swift repo will contain a Package.swift file that defines:

+
    +
  • A single binary target that references the pre-built .xcframework bundle of Rust code.
  • +
  • One Swift target for each component, that references the Swift code from the git submodule +and depends on the pre-built Rust code.
  • +
+

We will add automation to the rust-components-swift repo so that it automatically tracks +releases made in the application-services repo and creates a corresponding git tag for +the Swift package.

+

At some future date when all consumers have migrated to using Swift packages, we will remove +the Carthage build setup from the application-services repo.

+

At some future date, we will consider whether to move the Package.swift definition in to the application-services repo, +or whether it's better to keep it separate. (Attempting to move it into the application-services will involve non-trivial +changes to the release process, because the checksum of the released .xcframework bundle needs to be included in +the release taged version of the Package.swift file.)

+

Pros and Cons of the Options

+

(A) Do Nothing

+

In this option, we would make no changes to our iOS build and publishing process.

+
    +
  • Good, because it's the least amount of work.
  • +
  • Neutral, because it doesn't change the maintainability of the system for appservices +developers.
  • +
  • Neutral, because it doesn't change the amount of separation between Swift code +for our various components.
  • +
  • Neutral, because it doesn't address the Swift version incompatibility issues around +binary artifacts.
  • +
  • Bad, because it will frustrate consumers who want to develop on M1 Apple Silicon.
  • +
  • Bad, because it may prevent consumers from migrating to a more modern build setup.
  • +
  • Bad, because it would prevent consumers from consuming Glean as a Swift package; +we would require them to use the Glean that is bundled in our build.
  • +
+

This option isn't really tractable for us, but it's included for completeness.

+

(B) Use Carthage to build XCFramework bundles

+

In this option, we would try to change our iOS build and publising process as little +as possible, but use Carthage's recent support for building platform-independent +XCFrameworks in order +to support consumers running on M1 Apple Silicon.

+
    +
  • Good, because the size of the change is small.
  • +
  • Good, because we can support development on newer Apple machines.
  • +
  • Neutral, because it doesn't change the maintainability of the system for appservices +developers.
  • +
  • Neutral, because it doesn't change the amount of separation between Swift code +for our various components.
  • +
  • Neutral, because it doesn't address the Swift version incompatibility issues around +binary artifacts.
  • +
  • Bad, because our iOS consumers have expressed a preference for moving away from Carthage.
  • +
  • Bad, because other iOS projects at Mozilla are moving to Swift Packages, making +us inconsistent with perceived best practice.
  • +
  • Bad, because it would prevent consumers from consuming Glean as a Swift package; +we would require them to use the Glean that is bundled in our build.
  • +
  • Bad, because consumers don't get to choose which components they want to use (without +us building a whole new "megazord" with just the components they want).
  • +
+

Overall, current circumstances feel like a good opportunity to invest a little more +time in order to set ourselves up for better long-term maintainability +and happier consumers. The main benefit of this option (it's quicker!) is less attractive +under those circumstances.

+

(C) Distribute a single pre-compiled Swift Package

+

In this option, we would compile the Rust code and Swift code for all our components into +a single .xcframework bundle, and then distribute that as a +binary artifact via Swift Package. This is similar to the approach +currently taken by Glean (ref Bug 1711447) +except that they only have a single component.

+
    +
  • Good, because Swift Packages are the preferred distribution format for new iOS consumers.
  • +
  • Good, because we can support development on newer Apple machines.
  • +
  • Good, because it aligns with what other iOS component developers are doing at Mozilla.
  • +
  • Neutral, because it doesn't change the maintainability of the system for appservices +developers. +
      +
    • (We'd need to keep the current Xcode project broadly intact).
    • +
    +
  • +
  • Neutral, because it doesn't change the amount of separation between Swift code +for our various components.
  • +
  • Neutral, because it doesn't address the Swift version incompatibility issues around +binary artifacts.
  • +
  • Neutral, because it would prevent consumers from consuming Glean as a separate Swift package; +they'd have to get it as part of our all-in-one Swift package.
  • +
  • Bad, because it's a larger change and we have to learn about a new package manager.
  • +
  • Bad, because consumers don't get to choose which components they want to use (without +building a whole new "megazord" with just the components they want).
  • +
+

Overall, this option would be a marked improvement on the status quo, but leaves out some potential +improvements. For not that much more work, we can make some of the "Neutral" and "Bad" points +here into "Good" points.

+

(D) Distribute multiple source-based Swift Packages, with pre-compiled Rust code

+

In this option, we would compile just the Rust code for all our components into a single +.xcframework bundle and distribute that as a binary artifact via Swift Package. +We would then declare a separate Swift source target for the Swift wrapper of each component, +each depending on the compiled Rust code but appearing as a separate item in the Swift package +definition.

+
    +
  • Good, because Swift Packages are the preferred distribution format for new iOS consumers.
  • +
  • Good, because we can support development on newer Apple machines.
  • +
  • Good, because it aligns with what other iOS component developers are doing at Mozilla.
  • +
  • Good, because it can potentially simplify the maintenance of the system for appservices +developers, by removing Xcode in favour of some command-line scripts.
  • +
  • Good, because it introduces strict separation between the Swift code for each component, +instead of compiling them all together in a single shared namespace.
  • +
  • Good, because the Nimbus Swift package could cleanly depend on the Glean Swift package.
  • +
  • Good, because consumers can choose which components they want to include.
  • +
  • Good, because it avoids issues with Swift version incompatibility in binary artifacts.
  • +
  • Bad, because it's a larger change and we have to learn about a new package manager.
  • +
+

The only downside to this option appears to be the amount of work involved, but an initial +prototype has given us some confidence that the change is tractable and that it may lead +to a system that is easier to maintain over time. It is thus our preferred option.

+

Appendix

+

Further Reading

+ +

Problems with the current setup

+

It doesn't build for M1 Apple Silicon machines, because it's not possible to support +both arm64 device builds and arm64 simulator builds in a single binary .framework.

+

Carthage is dispreferred by our current iOS consumers.

+

We don't have much experience with the setup on the current Application Services team, +and many of its details are under-documented. Changing the build setup requires Xcode +and some baseline knowledge of how to use it.

+

All components are built as a single Swift module, meaning they can see each other's +internal symbols and may accidentally conflict when naming things. For example we can't +currently have two components that define a structure of the same name.

+

Consumers can only use the pre-built binary artifacts if they are using the same +version of Xcode as was used during the application-services build. We are not able +to use Swift's BUILD_LIBRARY_FOR_DISTRIBUTION flag to overcome this, because some +of our dependencies do not support this flag (specifically, the Swift protobuf lib).

+

Running experiments on first run early startup

+
    +
  • Status: rejected
  • +
  • Deciders: teshaq, travis, k88hudson, jhugman, jaredlockhart
  • +
  • Date: 2021-08-16
  • +
+

Technical Story: https://mozilla-hub.atlassian.net/browse/SDK-323

+

Context and Problem Statement

+

As an experimenter, I would like to run experiments early on a user's first run of the application. However, the experiment data is only available on the second run. We would like to have that experiment data available before the user's first run. +For more information: https://docs.google.com/document/d/1Qw36_7G6XyHvJZdM-Hxh4nqYZyCsYajG0L5mO33Yd5M/edit

+

Decision Drivers

+
    +
  • Availability of experiments early on the first run
  • +
  • No impact on experimentation data analysis
  • +
  • Flexibility in creating experiments
  • +
  • Ability to quickly disable experiments
  • +
  • Simplicity of releases
  • +
  • Mobile's expectations of Nimbus (The SDK should be idempotent)
  • +
+

Considered Options

+
    +
  • (A) Do Nothing +
      +
    • Keep everything the way it is, preventing us from experimenting on users early on their first run
    • +
    +
  • +
  • (B) Bundle Experiment data with app on release +
      +
    • On release, have an initial_experiments.json that defines the experiments that will be applied early on the first run
    • +
    • Later on the first run, the client would retrieve the actual experiment data from remote-settings and overwrite the bundled data
    • +
    +
  • +
  • (C) Retrieve Experiment data on first run, and deal with delay +
      +
    • We can retrieve the experiment data on the first run, experiment data however will not be available until after a short delay (network I/O + some disk I/O)
    • +
    +
  • +
+

Decision Outcome

+

None of the options were feasible, so for now we are sticking with option (A) Do Nothing until there are experiments planned that are expected to run on early startup on the first run, then we will revaluate our options.

+

The (B) Bundle Experiment data with app on release option was rejected mainly due to difficulty in disabling experiments and pausing enrollments. This can create a negative user experience as it prevents us from disabling any problematic experiments. Additionally, it ties experiment creation with application release cycles.

+

The (C) Retrieve Experiment data on first run, and deal with delay option was rejected due to the fact it changes the Nimbus SDK will no longer be idempotent,and the possibility of introducing undesirable UI.

+

Pros and Cons of the Options

+

Do nothing

+
    +
  • Good, because it keeps the flexibility in experiment creation
  • +
  • Good, because disabling experiments can still done remotely for all experiments
  • +
  • Good, because it keeps the Nimbus SDK idempotent.
  • +
  • Bad, because it doesn't address the main problem of exposing experiments to user on their first run
  • +
+

Bundle Experiment data with app on release

+
    +
  • Good, because it allows us to run experiments early on a user's first run
  • +
  • Good, because it prevents us from having to wait for experiments, especially if a user has a slow network connection
  • +
  • Bad, because it ties experiment creation with release cycles
  • +
  • Bad, because it prevents us from disabling problematic first-run experiments without a dot release
  • +
  • Bad, because it prevents us from pausing enrollment on first-run experiments without a dot release
  • +
  • Bad, because it requires investment from the console team, and can modify existing flows.
  • +
+

Retrieve Experiment data on first run, and deal with delay

+
    +
  • Good, because it enables us to retrieve experiments for users on their first run
  • +
  • Good, because it keeps the flexibility in experiment creation
  • +
  • Good, because disabling experiments can still done remotely for all experiments
  • +
  • Bad, because experiments may not be ready early on the user's experience
  • +
  • Bad, because it forces the customer application to deal with either the delay, or changing the configuration shortly after startup. e.g. a loading spinner or a pre-onboarding screen not under experimental control; delaying initialization of onboarding screens until after experiments have been loaded.
  • +
  • Bad, because it changes the programming model from Nimbus being an idempotent configuration store to configuration changing non-deterministically.
  • +
  • Bad, because the experimentation platform could force the app to add unchangeable user interface for the entire population. This itself may have an effect on key metrics.
  • +
+ +
    +
  • RFC for bundling into iOS and Fenix
  • +
  • Document presented to product managers about (C) Retrieve Experiment data on first run, and deal with delay: https://docs.google.com/document/d/1X1hC3t5zC7-Rp0OPIoiUr_ueLOAI0ez_jqslaNzOHjY/edit
  • +
  • Demo presenting option (C) Retrieve Experiment data on first run, and deal with delay: https://drive.google.com/file/d/19HwnlwrabmSNsB7tjW2l4kZD3PWABi4u/view?usp=sharing
  • +
+

A remote-settings client for our mobile browsers.

+
    +
  • +

    Status: proposed

    +
  • +
  • +

    Discussion: https://github.com/mozilla/application-services/pull/5302

    +
  • +
  • +

    Deciders:

    +
      +
    • csadilek for the mobile teams ✔️
    • +
    • leplatrem for the remote-settings team ✔️
    • +
    • mhammond for the application-services team ✔️
    • +
    +
  • +
  • +

    Date: 2022-12-16

    +
  • +
+

Context and Problem Statement

+

Mozilla’s mobile browsers have a requirement to access the remote settings service, but currently lack any libraries or tools which are suitable without some work. +A concrete use case is the management of search engine configurations, which are stored in Remote Settings for Firefox Desktop, but shipped as individual files on our mobile browsers, requiring application releases for all changes.

+

A constraint on any proposed solutions is that this work will be performed by Mozilla's mobile team, who have limited experience with Rust, and that it is required to be completed in Q1 2023.

+

This document identifies the requirements, then identifies tools which already exist and are close to being suitable, then identifies all available options we can take, and outlines our decision.

+

Requirements

+

The requirements are for a library which is able to access Mozilla’s Remote Settings service and return the results to our mobile browsers. +This list of requirements is not exhaustive, but instead focuses on the requirements which will drive our decision making process. +As such, it identifies the non-requirements first.

+

Non-requirements

+

The following items all may have some degree of desirability, but they are not hard requirements for the initial version

+
    +
  • While the https connection to the server must be validated, there is no requirement to verify the content received by the server - ie, there’s no requirement to check the signature of the body itself.
  • +
  • There’s no requirement to validate the result of the server conforms to a pre-defined schema - we trust the server data.
  • +
  • There’s no requirement to return strongly-typed data to the applications - returning a JSON string/object is suitable.
  • +
  • There’s no requirement to cache server responses to the file-system - if the app requests content, it’s fine for the library to always hit the server.
  • +
  • There’s no requirement for any kind of scheduling or awareness of network state - when we are requested for content, we do it immediately and return an appropriate error if it can not be fetched.
  • +
  • There’s no requirement to support publishing records, requesting reviews or providing approvals via this new library.
  • +
  • There’s no requirement that push be used to communicate changes to the application (eg, to enable rapid-enrolment type features)
  • +
  • There’s no requirement to manage buckets, groups and collections via this new library.
  • +
+

Initial Requirements

+

The requirements we do have for the initial version are:

+
    +
  • The library should allow fetching records from Mozilla’s Remote Settings servers. This includes support for attachments, and fetching incremental changes.
  • +
  • The library should not create threads or run any event loops - the mobile apps themselves are responsible for all threading requirements. While this might change in the future, considering this kind of change to our mobile applications is out of scope for this project.
  • +
  • We must use Necko for all networking on Android, must enforce all connections are via valid https hosts (although some test-only exceptions might be helpful for QA, such as allowing localhost connections to be http)
  • +
  • The library should be the only remote-settings library used in the browser. Specifically, this means that Nimbus must also be capable of using the library, and the work to move Nimbus to the library must be considered as part of the project.
  • +
+

Existing Libraries

+

We have identified the following libraries which may be suitable for this project.

+

Remote-settings on desktop

+

There is a version of the remote settings client in desktop, written in Javascript. +It has been used and been relatively stable since at least 2018, so can be considered very capable, but the roadblock to it being suitable for use by our mobile browsers is that it is written in Javascript, so while it might be possible to expose it to Android via geckoview, there’s no reasonable path to have it made available to iOS.

+

Rust Remote Settings Client

+

There is an existing remote settings client on github. +This client is written in Rust and has evolved over a number of years. +The most recent changes were made to support being used in Merino, which was re-written in Python, so there are no known consumers of this library left.

+

The main attributes of this library relevant to this discussion are:

+
    +
  • It’s written in Rust, but has no FFI - ie, it’s currently only consumable by other Rust code.
  • +
  • It has recently been updated to use async rust, so requires an internal event loop.
  • +
  • It includes the capability to verify the signatures of the content.
  • +
+

The Nimbus-sdk Client

+

The nimbus-sdk is a component in the application-services repository written in Rust. It has client code which talks to the remote-settings server and while this has only actually been used with the "Nimbus" collection there's no reason to believe it can't be used in the more general case. +The main attributes of this library relevant to this discussion are:

+
    +
  • It’s consumed by a component which is already consumed by our mobile browsers via UniFFI.
  • +
  • It does not verify the signatures of the content - while this could be done, there hasn’t been sufficient justification made for this (ie, there are no realistic threat models which would be solved by this capability.)
  • +
  • The client itself does not persist a local cache of remote resources, but instead delegates this responsibility to the consuming application (in this case, nimbus itself, which does persist them via the rkv library)
  • +
  • It does not use async Rust, but instead everything is blocking and run on threads exclusively created by the app itself.
  • +
  • It has good test support, which run against a docker image.
  • +
+

Considered Options

+

Option 1: Writing a new library

+

The requirements of this client are such that writing new libraries in Kotlin and Swift is currently a realistic option. +However, we are rejecting this option because we don’t want to duplicate the effort required to write and maintain two libraries - inevitably, the features and capabilities will diverge. +Future requirements such as supporting content signature verification would lead to significant duplication.

+

Writing a new library from scratch in Rust and exposing it via UniFFI so it can be used by both platforms is also a possibility. +However, we are rejecting this option because existing Rust libraries already exist, so we would be better served by modifying or forking one of the existing libraries.

+

Option 2: Use the existing remote settings client

+

Modifying or forking the existing client is an attractive option. +It would require a number of changes - the async capabilities would probably need to be removed (using a Rust event loop in our mobile browsers is something we are trying to avoid until we better understand the implications given these browsers already have an event loop and their own threading model).

+

The persistence model used by this library is something that is not a requirement for the new library, which isn’t itself a problem, but it probably would preclude being able to use this library by Nimbus - so the end result is that we would effectively have two remote-settings clients written in Rust and used by our browsers.

+

Some API changes would probably be required to make it suitable for use by UniFFI would also be necessary, but these would be tractable.

+

We would need to update nimbus to use this client, which would almost certainly require moving this client into the application-services repository to avoid the following issues:

+
    +
  • Marrying the persistence model of this client with the existing rkv-based persistence used by nimbus would be required.
  • +
  • Ensuring the upstream version changes continued to work for us.
  • +
  • Managing the circular dependency which exists due to this library needing to use viaduct.
  • +
  • Complication of our build process because the library needs to end up in our “megazord”. +These are the exact reasons why Nimbus itself is in the application-services repo.
  • +
+

Option 3: Use the existing nimbus client

+

Splitting the existing client out from Nimbus in a way that allows Nimbus to continue to use it, while also making it available for stand-alone use is also an attractive option.

+

In particular, the feature set of that client overlaps with the requirements of the new library - no local persistence is necessary and no signature verification is required. It is already used by a component which is exposed via UniFFI.

+

Note that this option does not preclude both Nimbus and this new crate from moving to the existing remote settings client at some point in the future. +A key benefit of this decision is that it keeps nimbus and the new crate using the same client, so updating both to use a different client in the future will always remain an option.

+

Chosen Option

+

We have chosen Option 3 because it allows us to reuse the new client in Nimbus, as well as on iOS and on Android with minimal initial development effort. +If the new library ends up growing requirements that are already in the existing remote settings client, we remain able to copy that functionality from that library into this.

+

Specific Plans

+

This section is non-normative - ie, is not strictly part of the ADR, but exists +for context.

+

This is a very high-level view of the tasks required here.

+
    +
  • +

    Create a new top-level component in the application-services repository, identify the exact API we wish to expose for this new library, describe this API using UniFFI, then implement the API with “stubs” (eg, using rust todo!()or similar). This is depicted as RemoteSettings in the diagram.

    +
  • +
  • +

    Identify which parts of Nimbus should be factored out into a shared component (depicted as rs-client in the diagram below) and move that functionality to the new shared component. Of note:

    +
      +
    • This component probably will not have a UniFFI .udl file, but is just for consumption by the new component above and the existing nimbus component.
    • +
    • There is still some uncertaintly here - if it is a requirement that nimbus and the new component share some configuration or initialization code, we might need to do something more complex here. This seems unlikely, but possible, so is included here for completeness.
    • +
    +
  • +
  • +

    Identify which of the nimbus tests should move to the new client and move them.

    +
  • +
  • +

    Update Nimbus to take a dependency on the new component and use it, including tests.

    +
  • +
  • +

    Flesh out the API of the new top-level component using the new shared component (ie, replace the todo!() macros with real code.)

    +
  • +
  • +

    Identify any impact on the Nimbus android/swift code - in particular, any shared configuration and initialization code identified above in the application-services repo.

    +
  • +
  • +

    Implement the Android and iOS code in the application-services repo desired to make this an ergonomic library for the mobile platforms.

    +
  • +
  • +

    Update the mobile code for the UniFFI changes made to Nimbus, if any.

    +
  • +
  • +

    Implement the mobile code which consumes the new library, including tests.

    +
  • +
  • +

    Profit?

    +
  • +
+

This diagram attempts to depict this final layout. Note:

+
    +
  • rs-client and RemoteSettings are both new components, everything else already exists. Please do not consider these names as suggestions! Names are hard, I'm sure we can do better.
  • +
  • Dashed lines are normal Rust dependencies (ie, dependencies listed in Cargo.toml)
  • +
  • Solid lines are where the component uses UniFFI
  • +
  • Viaduct is a little odd in that it is consumed by the mobile applications indirectly (eg, via Glean), hence it's not in support, but please ignore that anomaly.
  • +
+
flowchart RL
+    subgraph app-services-support[Shared components in application-services/components/support]
+    rs-client
+    other-support-components
+    end
+    subgraph app-services-components[Top-level application-services components, in application-services/components]
+    Nimbus
+    RemoteSettings
+    Viaduct
+    end
+    subgraph mobile [Code in the mobile repositories]
+        Fenix
+        Firefox-iOS
+    end
+
+    Nimbus -- nimbus.udl --> mobile
+    RemoteSettings -- remote_settings.udl --> mobile
+
+    rs-client -.-> Nimbus
+    other-support-components -.-> Nimbus
+    rs-client -.-> RemoteSettings
+    other-support-components -.-> RemoteSettings
+    Viaduct -.-> rs-client
+    other-support-components -.-> rs-client
+
+

Content Signatures

+

This section is non-normative - ie, is not strictly part of the ADR, but exists +for context.

+

Content Signatures have been explicitly called out as a non-requirement. Because this capability was a sticking point in the desktop version of the remote settings client, and because significant effort was spent on it, it's worth expanding on this here.

+

Because https will be enforced for all network requests, the consumers of this library can have a high degree of confidence that:

+
    +
  • The servers hit by this client are the servers we expect to hit (ie, no man-in-the-middle attacks will be able to redirect to a different server).
  • +
  • The response from the server is exactly what was sent by the Mozilla controlled server (ie, no man-in-the-middle attacks will be able to change the content in-flight)
  • +
  • Therefore, the content received must be exactly as sent by the Mozilla controlled servers.
  • +
+

Content signatures offer an additional capability of checking the content of a remote settings response matches the signature generated with a secret key owned by Mozilla, independenty of the https certificates used for the request itself.

+

This capability was added to the desktop version primarily to protect the integrity of the data at rest. +Because the Desktop client cached the responses on disk, there was a risk that this data could be tampered with - so it was effectively impossible to guarantee that the data finally presented to the application is what was initially sent.

+

The main threat-model that required this capability was 3rd party applications installed on the same system where Firefox was installed. +Because of the security model enforced by Desktop operating systems (most notably Windows), there was evidence that these 3rd-party applications would locate and modify the cache of remote-settings responses and modify them in a way that benefited them and caused revenue harm to Mozilla - the most obvious example is changing the search provider settings.

+

The reason we are declaring this capability a non-requirement in the initial version is two-fold:

+
    +
  • +

    We have also declared caching of responses a non-requirement, meaning there's no data at rest managed by this library which is vulnerable to this kind of attack.

    +
  • +
  • +

    The mobile operating systems have far stronger application isolation - in the general case, a 3rd party mobile application is prevented from touching any of the files used by other applications.

    +
  • +
+

Obviously though, things may change in the future - for example, we might add response caching, so we must be sure to reevaluate this requirement as other requirements change.

+

Limit Visits Migrated to Places History in Firefox iOS

+
    +
  • Status: accepted
  • +
  • Deciders: teshaq, mhammond, lougeniaC64, dnarcese
  • +
  • Date: 2023-01-06
  • +
+

Context and Problem Statement

+

The Application-Services team removed a legacy implementation of history in Firefox-ios and replaced it with a maintained implementation that was powering Firefox Android.

+

A significant part of the project is migrating users’ history from the old database to a new one. To measure risk, we ran a dry-run migration. A dry-run migration runs a background thread in the user’s application and attempts to migrate to a fake database. The dry-run was implemented purely to collect telemetry on the migration to evaluate risk. The results can be found in the following Looker dashboard. Below is a list of observations.

+

Observations from Dry-Run Experiment

+

The following is a list of observations from the experiment:

+
    +
  • 5-6% of migrations do not end. This means for 5-6% of users, the application was terminated before migration ended. For a real migration, this would mean those users lose all of their history unless we attempt the migration multiple times.
  • +
  • Out of the migrations that failed (the 5-6% mentioned above) 97% of those users had over 10,000 history visits.
  • +
  • Out of migrations that do end, over 99% of migrations are successful. +
      +
    • This means that we are not experiencing many errors with the migration beyond the time it takes.
    • +
    +
  • +
  • The average for visits migrated is around 25,000 - 45,000 visits.
  • +
  • The median for visits migrated is around 5,000-15,000 visits. +
      +
    • The difference between the average and the median suggests that we have many users with a large number of visits
    • +
    +
  • +
  • For migrations that did end, the following are the percentiles for how long it took (in milliseconds). We would like to emphasize that the following only includes migrations that did end +
      +
    • 10th percentile: 37 ms
    • +
    • 25th percentile: 80 ms
    • +
    • 50th percentile: 400 ms
    • +
    • 75th percentile: 2,500 ms (2.5 seconds)
    • +
    • 90th percentile: 6,400 ms (6.4 seconds)
    • +
    • 95th percentile: 11,000 ms (11 seconds)
    • +
    • 99th percentile: 25,000 ms (25 seconds)
    • +
    • 99.9th percentile: 50,000 ms (50 seconds)
    • +
    +
  • +
+

Problem Statement

+

Given the observations from the dry-run experiment, the rest of the document examines an approach to answer the question: How can we increase the rate of which migrations end, and simultaneously keep the user’s database size at a reasonable size?

+

The user implication of keeping the rate of ended migrations high is that users keep their history, and can interact normally with the URL bar to search history, searching history in the history panel and navigating to sites they visited in the past.

+

The user implication of keeping a reasonable database size is that the database is less likely to lock on long queries. Meaning we reduce performance issues when users use the search bar, the history panel and when navigating to sites.

+

Finally, it’s important to note that power users and daily active users will be more likely to have large histories and thus:

+
    +
  • Power users are more likely to fail their migration.
  • +
  • Power users are more likely to have performance issues with history and the search bar. +
      +
    • We saw a version of this with Favicons in an earlier incident, where users were coming across a significant number of database locks, crashing the app. This isn’t to say that the incident is directly related to this, however, large histories could contribute to the state we saw in the incident as it would take longer to run the queries.
    • +
    +
  • +
+

Decision Drivers

+ +

Considered Options

+
    +
  • Keep the migration as-is. +
      +
    • This option means that we have no limit. We will attempt to migrate all history for our users.
    • +
    +
  • +
  • Introduce a date-based limit on visits for the migration +
      +
    • This option means that we only migrate visits that occurred in the past X days/weeks/months etc
    • +
    +
  • +
  • Introduce a visit number-based limit for the migration +
      +
    • This option means we only migrate the latest X visits
    • +
    +
  • +
+

Decision Outcome

+

Chosen option: Introduce a visit number-based limit for the migration. This option was chosen because given our decision drivers:

+
    +
  1. We must not lose users’ recent history:
  2. +
+
    +
  • We have established in the results of the dry-run, that the majority of failed migrations were for users with a large number of visits.
  • +
  • By setting a reasonable limit, we can increase the likelihood the migration succeeds. We can set the limit to encompass “recent history” while choosing a limit that has an over 99% success rate.
  • +
+
    +
  1. User’s experience with History must not regress, and ideally should improve.
  2. +
+
    +
  • We have established in our decision driver that the user’s experience with history is coupled with the database size.
  • +
  • By setting a reasonable limit, we can keep the size of the history database controlled.
  • +
  • It's also worth noting that with the switch to the new implementation of history, we are also introducing a target size for the database. This means that we have maintenance logic that would compact the database and prune it if it grows beyond the target size.
  • +
+

Positive Consequences

+
    +
  • The migration runs in a shorter time. +
      +
    • This means a higher chance of the migration succeeding, thus keeping the user’s recent history without loss.
    • +
    +
  • +
  • Users who have less than the selected limit, will still get all their history. More on this in the Suggested Limit section.
  • +
  • We keep the size of the history database low. +
      +
    • This way users with more than the limit, will only keep their recent history.
    • +
    • Additionally, when we delete users’ history from the old database, the size of the data the app keeps will decrease dramatically.
    • +
    • Keeping the database size low means we lower the chance a user has performance issues with the database.
    • +
    +
  • +
+

Negative Consequences

+

The biggest negative consequence is that Users with more visits than the limit, will lose visits.

+
    +
  • Since we would only keep the latest X visits for a user, if a user has Y visits, they would lose all of the Y-X visits (assuming Y is greater than X)
  • +
  • The effect here is mitigated by the observation that recent history is more important to users than older history. Unfortunately, we do not have any telemetry to demonstrate this claim, but it’s an assumption based on the existing limits on history imposed in Android and Desktop mentioned in the decision drivers section.
  • +
+

Pros and Cons of the Other Options

+

Keep the migration as-is

+
    +
  • Good because if the migration succeeds, users keep all their history.
  • +
  • Bad, because it’s less likely for migrations to succeed.
  • +
  • Bad, because even if the migration succeeds it causes the size of the database to be large if a user has a lot of history. +
      +
    • Large databases can cause a regression in performance.
    • +
    • Users with a lot of history will now have two large databases (the old and new ones) since we won’t delete the data in the old database right away to support downgrades.
    • +
    +
  • +
  • Bad, because it can take a long time for the migration to finish.
  • +
  • Bad because until the migration is over users will experience the app without history.
  • +
+

Introduce a date-based limit on visits

+
    +
  • Good, because we match users’ usage of the app. +
      +
    • Users that use the app more, will keep more of their history.
    • +
    +
  • +
  • Good, because it’s more likely that the migration ends because we set a limit
  • +
  • Bad because it’s hard to predict how large user’s databases will be. +
      +
    • This is particularly important for Sync users. As Firefox-iOS syncs all your history, meaning if a user has many visits before the limit across multiple platforms, a large number of visits will be migrated.
    • +
    +
  • +
  • Bad, because users who haven’t used the app since the limit, will lose all their history +
      +
    • For example, if the limit is 3 months, a user who last used the app 3 months ago will suddenly lose all their history
    • +
    +
  • +
+

Suggested Limit

+

This section describes a suggested limit for the visits. Although it’s backed up with telemetry, the specific number is up for discussion. It’s also important to note that statistical significance was not a part of the analysis. Migration has run for over 16,000 users and although that may not be a statistically significant representation of our population, it’s good enough input to make an educated suggestion.

+
    +
  • First, we start by observing the distribution of visit counts. This will tell us how many of our users have between 0-10000 visits, 10000-20000, etc. We will identify that most of our users have less than 10,000 visits.
  • +
  • Then, we will observe the dry-run migration ended rate based on the above buckets. We will observe that users with under 10,000 visits have a high chance of migration success.
  • +
  • Finally, based on the analysis and prior art we’ll suggest 10,000 visits.
  • +
+

User History Distribution

+

We will look at https://mozilla.cloud.looker.com/looks/1078 which demonstrates a distribution of our users based on the number of history visits. Note that the chart is based on our release population.

+

Observations

+
    +
  • 67% of firefox-ios users have less than 10,000 visits
  • +
  • There is a very long tail to the distribution, with 6% of users having over 100,000 visits.
  • +
+

Dry-run Ending Rate by Visits

+

We will look at https://mozilla.cloud.looker.com/looks/1081. The chart demonstrates the rate at which migrations end by the number of visits. We bucket users in buckets of 10,000 visits.

+

Observations

+
    +
  • We observe that for users that have visits under 10,000, the success rate is over 99.6%.
  • +
  • We observe that for users with over 100,000 visits, the success rate drops to 75~80%.
  • +
  • Users in between, have success rates in between. For example, users with visits between 10,000 and 20,000 have a 98-99% success rate.
  • +
  • All success rates for buckets beyond 20,000 visits drop under 96%.
  • +
+

Suggestion

+

Based on the above, we’re suggesting a limit of 10,000 visits because

+ + + +

Design Documents

+ +

Megazording

+

Each Rust component published by Application Services is conceptually a stand-alone library, but for +distribution we compile all the rust code for all components together into a single .so file. This +has a number of advantages:

+
    +
  • Easy and direct interoperability between different components at the Rust level
  • +
  • Cross-component optimization of generated code
  • +
  • Reduced code size thanks to distributing a single copy of the rust stdlib, low-level dependencies, etc.
  • +
+

This process is affectionately known as "megazording" and the resulting artifact as a megazord library.

+

On Android, the situation is quite complex due to the way packages and dependencies are managed. +We need to distribute each component as a separate Android ARchive (AAR) that can be managed as a dependency +via gradle, we need to provide a way for the application to avoid shipping rust code for components that it +isn't using, and we need to do it in a way that maintanins the advantages listed above.

+

This document describes our current approach to meeting all those requirements on Android. Other platforms +such as iOS are not considered.

+

AAR Dependency Graph

+

We publish a separate AAR for each component (e.g. fxaclient, places, logins) which contains +just the Kotlin wrappers that expose the relevant functionality to Android. Each of these AARs depends on a separate +shared "megazord" AAR in which all the rust code has been compiled together into a single .so file. +The application's dependency graph thus looks like this:

+

megazord dependency diagram

+

This generates a kind of strange inversion of dependencies in our build pipeline:

+
    +
  • Each individual component defines both a rust crate and an Android AAR.
  • +
  • There is a special "full-megazord" component that also defines a rust crate and an Android AAR.
  • +
  • The full-megazord rust crate depends on the rust crates for each individual component.
  • +
  • But the Android AAR for each component depends on the Android AAR of the full-megazord!
  • +
+

It's a little odd, but it has the benefit that we can use gradle's dependency-replacement features to easily +manage the rust code that is shipping in each application.

+

Custom Megazords

+

By default, an application that uses any appservices component will include the compiled rust code +for all appservices components.

+

To reduce its overall code size, the application can use gradle's module replacement +rules +to replace the "full-megazord" AAR with a custom-built megazord AAR containing only the components it requires. +Such an AAR can be built in the same way as the "full-megazord", and simply avoid depending on the rust +crates for components that are not required.

+

To help ensure this replacement is done safely at runtime, the mozilla.appservices.support.native package +provides helper functions for loading the correct megazord .so file. The Kotlin wrapper for each component +should load its shared library by calling mozilla.appservices.support.native.loadIndirect, specifying both +the name of the component and the expected version number of the shared library.

+

Unit Tests

+

The full-megazord AAR contains compiled rust code that targets various Android platforms, and is not +suitable for running on a Desktop development machine. In order to support integration with unittest +suites such as robolectric, each megazord has a corresponding Java ARchive (JAR) distribution named e.g. +full-megazord-forUnitTests.jar. This contains the rust code compiled for various Desktop architectures, +and consumers can add it to their classpath when running tests on a Desktop machine.

+

Gotchas and Rough Edges

+

This setup mostly works, but has a handful of rough edges.

+

The build.gradle for each component needs to declare an explicit dependency on project(":full-megazord"), +otherwise the resulting AAR will not be able to locate the compiled rust code at runtime. It also needs to +declare a dependency between its build task and that of the full-megazord, for reasons. Typically this looks something +like:

+
tasks["generate${productFlavor}${buildType}Assets"].dependsOn(project(':full-megazord').tasks["cargoBuild"])
+
+

In order for unit tests to work correctly, the build.gradle for each component needs to add the rustJniLibs +directory of the full-megazord project to its srcDirs, otherwise the unittests will not be able to find and load +the compiled rust code. Typically this looks something like:

+
test.resources.srcDirs += "${project(':full-megazord').buildDir}/rustJniLibs/desktop"
+
+

The above also means that unittests will not work correctly when doing local composite builds, +because it's unreasonable to expect the main project (e.g. Fenix) to include the above in its build scripts.

+

Sync manager

+

We've identified the need for a "sync manager" (although are yet to identify a +good name for it!) This manager will be responsible for managing "global sync +state" and coordinating each engine.

+

At a very high level, the sync manager is responsible for all syncing. So far, +so obvious. However, given our architecture, it's possible to identify a +key architectural split.

+
    +
  • +

    The embedding application will be responsible for most high-level operations. +For example, the app itself will choose how often regular syncs should +happen, what environmental concerns apply (eg, should I only sync on WiFi?), +letting the user choose exactly what to sync, and so forth.

    +
  • +
  • +

    A lower-level component will be responsible for the direct interaction with +the engines and with the various servers needed to perform a sync. It will +also have the ultimate responsibility to not cause harm to the service (for +example, it will be likely to enforce some kind of rate limiting or ensuring +that service requests for backoff are enforced)

    +
  • +
+

Because all application-services engines are written in Rust, it's tempting to +suggest that this lower-level component also be written in Rust and everything +"just works", but there are a couple of complications here:

+
    +
  • +

    For iOS, we hope to integrate with older engines which aren't written in +Rust, even if iOS does move to the new Sync Manager.

    +
  • +
  • +

    For Desktop, we hope to start by reusing the existing "sync manager" +implemented by Desktop, and start moving individual engines across.

    +
  • +
  • +

    There may be some cross-crate issues even for the Rust implemented engines. +Or more specifically, we'd like to avoid assuming any particular linkage or +packaging of Rust implemented engines.

    +
  • +
+

Even with these complications, we expect there to be a number of high-level +components, each written in a platform specific language (eg, Kotlin or Swift) +and a single lower-level component to be implemented in Rust and delivered +as part of the application-services library - but that's not a free-pass.

+

Why "a number of high-level components"? Because that is the thing which +understands the requirements of the embedding application. For example, Android +may end up with a single high-level component in the android-components repo +and shared between all Android components. Alternatively, the Android teams +may decide the sync manager isn't generic enough to share, so each app will +have their own. iOS will probably end up with its own and you could imagine +a future where Desktop does too - but they should all be able to share the +low level component.

+

The responsibilities of the Sync Manager.

+

The primary responsibilities of the "high level" portion of the sync manager are:

+
    +
  • +

    Manage all FxA interaction. The low-level component will have a way to +communicate auth related problems, but it is the high-level component +which takes concrete FxA action.

    +
  • +
  • +

    Expose all UI for the user to choose what to sync and coordinate this with +the low-level component. Note that because these choices can be made on any +connected device, these choices must be communicated in both directions.

    +
  • +
  • +

    Implement timers or any other mechanism to fully implement the "sync +scheduler", including any policy decisions such as only syncing on WiFi, +etc.

    +
  • +
  • +

    Implement a UI so the user can "sync now".

    +
  • +
  • +

    Collect telemetry from the low-level component, probably augment it, then +submit it to the telemetry pipeline.

    +
  • +
+

The primary responsibilities of the "low level" portion of the sync manager are:

+
    +
  • +

    Manage the meta/global, crypto/keys and info/collections resources, +and interact with each engine as necessary based on the content of these +resources.

    +
  • +
  • +

    Manage interaction with the token server.

    +
  • +
  • +

    Enforce constraints necessary to ensure the entire ecosystem is not +subject to undue load. For example, this component should ignore attempts to +sync continuously, or to sync when the services have requested backoff.

    +
  • +
  • +

    Manage the "clients" collection - we probably can't ignore this any longer, +especially for bookmarks (as desktop will send a wipe command on bookmark +restore, and things will "be bad" if we don't see that command).

    +
  • +
  • +

    Define a minimal "service state" so certain things can be coordinated with +the high-level component. Examples of these states are "everything seems ok", +"the service requested we backoff for some period", "an authentication error +occurred", and possibly others.

    +
  • +
  • +

    Perform, or coordinate, the actual sync of the rust implemented engines - +from the containing app's POV, there's a single "sync now" entry-point (in +practice there might be a couple, but conceptually there's a single way to +sync). Note that as below, how non-rust implemented engines are managed is +TBD.

    +
  • +
  • +

    Manage the collection of (but not the submission of) telemetry from the +various engines.

    +
  • +
  • +

    Expose APIs and otherwise coordinate with the high-level component.

    +
  • +
+

Stuff we aren't quite sure where it fits include:

+
    +
  • Coordination with non-rust implemented engines. These engines are almost +certainly going to be implemented in the same language as the high-level +component, which will make integration simpler. However, the low-level +component will almost certainly need some information about these engines for +populating info/collections etc. For now, we are punting on this until things +become a bit clearer.
  • +
+

Implementation Details.

+

The above has been carefully written to try and avoid implementation details - +the intent is that it's an overview of the architecture without any specific +implementation decisions.

+

These next sections start getting specific, so implementation choices need to +be made, and thus will possibly be more contentious.

+

In other words, get your spray-cans ready because there's a bikeshed being built!

+

However, let's start small and make some general observations.

+

Current implementations and challenges with the Rust components

+
    +
  • +

    Some apps only care about a subset of the engines - lockbox is one such app +and only cares about a single collection/engine. It might be the case that +lockbox uses a generic application-services library with many engines +available, even though it only wants logins. Thus, the embedding application +is the only thing which knows which engines should be considered to "exist". +It may be that the app layer passes an engine to the sync manager, or the +sync manager knows via some magic how to obtain these handles.

    +
  • +
  • +

    Some apps will use a combination of Rust components and "legacy" +engines. For example, iOS is moving some of the engines to using Rust +components, while other engines will be ported after delivery of the +sync manager, if they are ported at all. We also plan +to introduce some rust engines into desktop without integrating the +"sync manager"

    +
  • +
  • +

    The rust components themselves are designed to be consumed as individual +components - the "logins" component doesn't know anything about the +"bookmarks" component.

    +
  • +
+

There are a couple of gotchyas in the current implementations too - there's an +issue when certain engines don't yet appear in meta/global - see bug 1479929 +for all the details.

+

The tl;dr of the above is that each rust component should be capable of +working with different sync managers. That said though, let's not over-engineer +this and pretend we can design a single, canonical thing that will not need +changing as we consider desktop and iOS.

+

State, state and more state. And then some state.

+

There's loads of state here. The app itself has some state. The high-level +Sync Manager component will have state, the low-level component will have state, +and each engine has state. Some of this state will need to be persisted (either +on the device or on the storage servers) and some of this state can be considered +ephemeral and lives only as long as the app.

+

A key challenge will be defining this state in a coherent way with clear +boundaries between them, in an effort to allow decoupling of the various bits +so Desktop and iOS can fit into this world.

+

This state management should also provide the correct degree of isolation for +the various components. For example, each engine should only know about state +which directly impacts how it works. For example, the keys used to encrypt +a collection should only be exposed to that specific engine, and there's no +need for one engine to know what info/collections returns for other engines, +nor whether the device is currently connected to WiFi.

+

A thorn here is for persisted state - it would be ideal if the low-level +component could avoid needing to persist any state, so it can avoid any +kind of storage abstraction. We have a couple of ways of managing this:

+
    +
  • +

    The state which needs to be persisted is quite small, so we could delegate +state storage to the high-level component in an opaque way, as this +high-level component almost certainly already has storage requirements, such +as storing the "choose what to sync" preferences.

    +
  • +
  • +

    The low-level component could add its own storage abstraction. This would +isolate the high-level component from this storage requirement, but would +add complexity to the sync manager - for example, it would need to be passed +a directory where it should create a file or database.

    +
  • +
+

We'll probably go with the former.

+

Implementation plan for the low-level component.

+

Let's try and move into actionable decisions for the implementation. We expect +the implementation of the low-level component to happen first, followed very +closely by the implementation of the high-level component for Android. So we +focus first on these.

+

Clients Engine

+

The clients engine includes some meta-data about each client. We've decided +we can't replace the clients engine with the FxA device record and we can't +simply drop this engine entirely.

+

Of particular interest is "commands" - these involve communicating with the +engine regarding commands targetting it, and accepting commands to be send to +other devices. Note that outgoing commands are likely to not originate from a sync, +but instead from other actions, such as "restore bookmarks".

+

However, because the only current requirement for commands is to wipe the +store, and because you could anticipate "wipe" also being used when remotely +disconnecting a device (eg, when a device is lost or stolen), our lives would +probably be made much simpler by initially supporting only per-engine wipe +commands.

+

Note that there has been some discussion about not implementing the client +engine and replacing "commands" with some other mechanism. However, we have +decided to not do that because the implementation isn't considered too +difficult, and because desktop will probably require a number of changes to +remove it (eg, "synced tabs" will not work correctly without a client record +with the same guid as the clients engine.)

+

Note however that unlike desktop, we will use the FxA device ID as the client +ID. Because FxA device IDs are more ephemeral than sync IDs, it will be +necessary for engines using this ID to track the most-recent ID they synced +with so the old record can be deleted when a change is detected.

+

Collections vs engines vs stores vs preferences vs Apis

+

For the purposes of the sync manager, we define:

+
    +
  • +

    An engine is the unit exposed to the user - an "engine" can be enabled +or disabled. There is a single set of canonical "engines" used across the +entire sync ecosystem - ie, desktop and mobile devices all need to agree +about what engines exist and what the identifier for an engine is.

    +
  • +
  • +

    An Api is the unit exposed to the application layer for general application +functionality. Application services has 'places' and 'logins' Apis and is +the API used by the application to store and fetch items. Each 'Api' may +have one or more 'stores' (although the application layer will generally not +interact directly with a store)

    +
  • +
  • +

    A store is the code which actually syncs. This is largely an implementation +detail. There may be multiple stores per engine (eg, the "history" engine +may have "history" and "forms" stores) and a single 'Api' may expose multiple +stores (eg, the "places Api" will expose history and bookmarks stores)

    +
  • +
  • +

    A collection is a unit of storage on a server. It's even more of an +implementation detail than a store. For example, you might imagine a future +where the "history" store uses multiple "collections" to help with containers.

    +
  • +
+

In practice, this means that the high-level component should only need to care +about an engine (for exposing a choice of what to sync to the user) and an +api (for interacting with the data managed by that api). The low-level +component will manage the mapping of engines to stores.

+

The declined list

+

This document isn't going to outline the history of how "declined" is used, nor +talk about how this might change in the future. For the purposes of the sync +manager, we have the following hard requirements:

+
    +
  • +

    The low-level component needs to know what the currently declined set of +engines is for the purposes of re-populating meta/global.

    +
  • +
  • +

    The low-level component needs to know when the declined set needs to change +based on user input (ie, when the user opts in to or out of a particular +engine on this device)

    +
  • +
  • +

    The high-level component needs to be aware that the set of declined engines +may change on every sync (ie, when the user opts in to or out of a particular +engine on another device)

    +
  • +
+

A complication is that due to networks being unreliable, there's an inherent +conflict between "what is the current state?" and "what state changes are +requested?". For example, if the user changes the state of an engine while +there's no network, then exits the app, how do we ensure the user's new state +is updated next time the app starts? What if the user has since made a +different state request on a different device? Is the state as last-known on +this device considered canonical?

+

To clarify, consider:

+
    +
  • +

    User on this device declines logins. This device now believes logins is +disabled but history is enabled, but is unable to write this to the server +due to no network.

    +
  • +
  • +

    The user declines history on a different device, but doesn't change logins. +This device does manage to write the new list to the server.

    +
  • +
  • +

    This device restarts and the network is up. It believes history is enabled +but logins is not - however, the state on the server is the exact opposite.

    +
  • +
+

How does this device react?

+

(On the plus side, this is an extreme edge-case which none of our existing +implementations handle "correctly" - which is easy to say, because there's +no real definition for "correctly")

+

Regardless, the low-level component will not pretend to hide this complexity +(ie, it will ignore it!). The low-level component will allow the app to ask +for state changes as part of a sync, and will return what's on the server at +the end of every sync. The app is then free to build whatever experience +it desires around this.

+

Disconnecting from Sync

+

The low-level component needs to have the ability to disconnect all engines +from Sync. Engines which are declined should also be reset.

+

Because we will need wipe() functionality to implement the clients engine, +and because Lockbox wants to wipe on disconnect, we will provide disconnect +and wipe functionality.

+

Specific deliverables for the low-level component.

+

Breaking the above down into actionable tasks which can be some somewhat +concurrently, we will deliver:

+

The API

+

A straw-man for the API we will expose to the high-level components. This +probably isn't too big, but we should do this as thoroughly as we can. In +particular, ensure we have covered:

+
    +
  • +

    Declined management - how the app changes the declined list and how it learns +of changes from other devices.

    +
  • +
  • +

    How telemetry gets handed from the low-level to the high-level.

    +
  • +
  • +

    The "state" - in particular, how the high-level component understands the +auth state is wrong, and whether the service is in a degraded mode (eg, +server requested backoff)

    +
  • +
  • +

    How the high-level component might specify "special" syncs, such as "just +one engine" or "this is a pre-sleep, quick-as-possible sync", etc

    +
  • +
+

There's a straw-man proposal for this at the end of the document.

+

A command-line (and possibly Android) utility.

+

We should build a utility (or 2) which can stand in for the high-level +component, for testing and demonstration purposes.

+

This is something like places-utils.rs and the little utility Grisha has +been using. This utility should act like a real client (ie, it should have +an FxA device record, etc) and it should use the low-level component in +exactly the same we we expect real products to use it.

+

Because it is just a consumer of the low-level component, it will force us to +confront some key issues, such as how to get references to engines stored in +multiple crates, how to present a unified "state" for things like auth errors, +etc.

+

The "clients" engine

+

The initial work for the clients engine can probably be done without too +much regard for how things are tied together - for example, much work could +be done without caring how we get a reference to engines across crates.

+

State work

+

Implementing things needed to we can expose the correct state to the high-level +manager for things like auth errors, backoff semantics, etc

+

Tie it together and other misc things.

+

There will be lots of loose ends to clean up - things like telemetry, etc.

+

Followup with non-rust engines.

+

We have identified that iOS will, at least in the short term, want the +sync manager to be implemented in Swift. This will be responsible for +syncing both the Swift and Rust implemented engines.

+

At some point in the future, Desktop may do the same - we will have both +Rust and JS implemented engines which need to be coordinated. We ignore this +requirement for now.

+

This approach still has a fairly easy time coordinating with the Rust +implemented engines - the FFI will need to expose the relevant sync +entry-points to be called by Swift, but the Swift code can hard-code the +Rust engines it has and make explicit calls to these entry-points.

+

This Swift code will need to create the structures identified below, but this +shouldn't be too much of a burden as it already has the information necessary +to do so (ie, it already has info/collections etc)

+

TODO: dig into the Swift code and make sure this is sane.

+

Details

+

While we use rust struct definitions here, it's important to keep in mind that +as mentioned above, we'll need to support the manager being written in +something other than rust, and to support engines written in other than rust.

+

The structures below are a straw-man, but hopefully capture all the information +that needs to be passed around.

+
#![allow(unused)]
+
+fn main() {
+// We want to define a list of "engine IDs" - ie, canonical strings which
+// refer to what the user perceives as an "enigine" - but as above, these
+// *do not* correspond 1:1 with either "stores" or "collections" (eg, "history"
+// refers to 2 stores, and in a future world, might involve 3 collections).
+enum Engine {
+  History, // The "History" and "Forms" stores.
+  Bookmarks, // The "Bookmark" store.
+  Passwords,
+}
+
+impl Engine {
+  fn as_str(&self) -> &'static str {
+    match self {
+      History => "history",
+      // etc
+  }
+}
+
+// A struct which reflects engine declined states.
+struct EngineState {
+  engine: Engine,
+  enabled: bool,
+}
+
+// A straw-man for the reasons why a sync is happening.
+enum SyncReason {
+  Scheduled,
+  User,
+  PreSleep,
+  Startup,
+}
+
+// A straw man for the general status.
+enum ServiceStatus {
+  Ok,
+  // Some general network issue.
+  NetworkError,
+  // Some apparent issue with the servers.
+  ServiceError,
+  // Some external FxA action needs to be taken.
+  AuthenticationError,
+  // We declined to do anything for backoff or rate-limiting reasons.
+  BackedOff,
+  // Something else - you need to check the logs for more details.
+  OtherError,
+}
+
+// Info we need from FxA to sync. This is roughly our Sync15StorageClientInit
+// structure with the FxA device ID.
+struct AccountInfo {
+  key_id: String,
+  access_token: String,
+  tokenserver_url: Url,
+  device_id: String,
+}
+
+// Instead of massive param and result lists, we use structures.
+// This structure is passed to each and every sync.
+struct SyncParams {
+  // The engines to Sync. None means "sync all"
+  engines: Option<Vec<Engine>>,
+  // Why this sync is being done.
+  reason: SyncReason,
+
+  // Any state changes which should be done as part of this sync.
+  engine_state_changes: Vec<EngineState>,
+
+  // An opaque state "blob". This should be persisted by the app so it is
+  // reused next sync.
+  persisted_state: Option<String>,
+}
+
+struct SyncResult {
+  // The general health.
+  service_status: ServiceStatus,
+
+  // The result for each engine.
+  engine_results: HashMap<Engine, Result<()>>,
+
+  // The list of declined engines, or None if we failed to get that far.
+  declined_engines: Option<Vec<Engine>>,
+
+  // When we are allowed to sync again. If > now() then there's some kind
+  // of back-off. Note that it's not strictly necessary for the app to
+  // enforce this (ie, it can keep asking us to sync, but we might decline).
+  // But we might not too - eg, we might try a user-initiated sync.
+  next_sync_allowed_at: Timestamp,
+
+  // New opaque state which should be persisted by the embedding app and supplied
+  // the next time Sync is called.
+  persisted_state: String,
+
+  // Telemetry. Nailing this down is tbd.
+  telemetry: Option<JSONValue>,
+}
+
+struct SyncManager {}
+
+impl SyncManager {
+  // Initialize the sync manager with the set of Engines known by this
+  // application without regard to the enabled/declined states.
+  // XXX - still TBD is how we will pass "stores" around - it may be that
+  // this function ends up taking an `impl Store`
+  fn init(&self, engines: Vec<&str>) -> Result<()>;
+
+  fn sync(&self, params: SyncParams) -> Result<SyncResult>;
+
+  // Interrupt any current syncs. Note that this can be called from a different
+  // thread.
+  fn interrupt() -> Result<()>;
+
+  // Disconnect this device from sync. This may "reset" the stores, but will
+  // not wipe local data.
+  fn disconnect(&self) -> Result<()>;
+
+  // Wipe all local data for all local stores. This can be done after
+  // disconnecting.
+  // There's no exposed way to wipe the remote store - while it's possible
+  // stores will want to do this, there's no need to expose this to the user.
+  fn wipe(&self) -> Result<()>;
+}
+}
+

Sync Overview

+

This document provides a high-level overview of how syncing works. Note: each component has its own quirks and will handle sync slightly differently than the general process described here.

+

General flow and architecture

+
    +
  • Crates involved: +
      +
    • The sync15 and support/sync15-traits handle the general syncing logic and define the SyncEngine trait
    • +
    • Individual component crates (logins, places, autofill, etc). These implement SyncEngine.
    • +
    • sync_manager manages the overall syncing process.
    • +
    +
  • +
  • High level sync flow: +
      +
    • Sync is initiated by the application that embeds application-services.
    • +
    • The application calls SyncManager.sync() to start the sync process.
    • +
    • SyncManager creates SyncEngine instances to sync the individual components. Each SyncEngine corresponds to a collection on the sync server.
    • +
    +
  • +
+

Sync manager

+

SyncManager is responsible for performing the high-level parts of the sync process:

+
    +
  • The consumer code calls it's sync() function to start the sync, passing +in a SyncParams object in, which describes what should be synced.
  • +
  • SyncManager performs all network operations on behalf of the individual engines. It's also responsible for tracking the general authentication state (primarily by inspecting the responses from these network requests) and fetching tokens from the token server.
  • +
  • SyncManager checks if we are currently in a backoff period and should wait before contacting the server again.
  • +
  • Before syncing any engines, the sync manager checks the state of the meta/global collection and compares it with the enabled engines specified in the SyncParams. This handles the cases when the user has requested an engine be enabled or disabled on this device, or when it was requested on a different device. (Note that engines enabled and disabled states are state on the account itself and not a per-device setting). Part of this process is comparing the collection's GUID on the server with the GUID known locally - if they are different, it implies some other device has "reset" the collection, so the engine drops all metadata and attempts to reconcile with every record on the server (ie, acts as though this is the very first sync this engine has ever done).
  • +
  • SyncManager instantiates a SyncEngine for each enabled component. We currently use 2 different methods for this: +
      +
    • The older method is for the SyncManager to hold a weakref to a Store use that to create the SyncEngine (tabs and places). The SyncEngine uses the Store for database access, see the TabsStore for an example.
    • +
    • The newer method is for the components to provide a function to create the SyncEngine, hiding the details of how that engine gets created (autofill/logins). These components also define a Store instance for the SyncEngine to use, but it's all transparent to the SyncManager. (See autofill::get_registered_sync_engine() and autofill::db::store::Store)
    • +
    +
  • +
  • For components that use local encryption, SyncManager passes the local encryption key to their SyncEngine
  • +
  • Finally, calls sync_multiple() function from the sync15 crate, sending it the SyncEngine instances. sync_multiple() then calls the sync() function for each individual SyncEngine
  • +
+

Sync engines

+
    +
  • SyncEngine is defined in the support/sync15-traits crate and defines the interface for syncing a component.
  • +
  • A new SyncEngine instance is created for each sync
  • +
  • SyncEngine.apply_incoming() does the main work. It is responsible for processing incoming records from the server in order to update the local records and calculating which local records should be synced back.
  • +
+

The apply_incoming pattern

+

SyncEngine instances are free to implement apply_incoming() any way they want, but the most components follow a general pattern.

+

Database Tables

+
    +
  • The local table stores records for the local application
  • +
  • The mirror table stores the last known record from the server
  • +
  • The staging temporary table stores the incoming records that we're currently processing
  • +
  • The local/mirror/staging tables contains a guid as its primary key. A record will share the same guid for the local/mirror/staging table.
  • +
  • The metadata table stores the GUID for the collection as a whole and the the last-known server timestamp of the collection.
  • +
+

apply_incoming stages

+
    +
  • stage incoming: write out each incoming server record to the staging table
  • +
  • fetch states: take the rows from all 3 tables and combine them into a single struct containing Options for the local/mirror/staging records.
  • +
  • iterate states: loop through each state, decide how to do change the local records, then execute that plan. +
      +
    • reconcile/plan: For each state we create an action plan for it. The action plan is a low-level description of what to change (add this record, delete this one, modify this field, etc). Here are some common situations: +
        +
      • A record only appears in the staging table. It's a new record from the server and should be added to the local DB
      • +
      • A record only appears in the local table. It's a new record on the local instance and should be synced back to the serve
      • +
      • Identical records appear in the local/mirror tables and a changed record is in the staging table. The record was updated remotely and the changes should be propagated to the local DB.
      • +
      • A record appears in the mirror table and changed records appear in both the local and staging tables. The record was updated both locally and remotely and we should perform a 3-way merge.
      • +
      +
    • +
    • apply plan: After we create the action plan, then we execute it.
    • +
    +
  • +
  • fetch outgoing: +
      +
    • Calculate which records need to be sent back to the server
    • +
    • Update the mirror table
    • +
    • Return those records back to the sync15 code so that it can upload them to the server.
    • +
    • The sync15 code returns the timestamp reported by the server in the POST response and hands it back to the engine. The engine persists this timestamp in the metadata table - the next sync will then use this timestamp to only fetch records that have since been changed by other devices
    • +
    +
  • +
+

syncChangeCounter

+

The local table has an integer column syncChangeCounter which is incremented every time the embedding app makes a change to a local record (eg, updating a field). Thus, any local record with a non-zero change counter will need to be updated on the server (with either the local record being used, or after it being merged if the record also changed remotely). At the start of the sync, when we are determining what action to take, we take a copy of the change counter, typically in a temp staging table. After we have uploaded the record to the server, we decrement the counter by whatever it was when the sync started. This means that if a record is changed in between staging the record and uploading it, the change counter will not drop to zero, and so it will correctly be seen as locally modified on the next sync

+

High level design for shipping Rust Components as Swift Packages

+
+

This is a high level description of the decision highlighted in the ADR that introduced Swift Packages as a strategy to ship our Rust components. That document includes that tradeoffs and why we chose this approach.

+
+ +A box diagram describing how the rust-components-swift repo, applicaiton-services repo, and MozillaRustComponents XCFramework interact +

The strategy includes two main parts:

+
    +
  • The xcframework that is built from a megazord. The xcframework contains the following, built for all our target iOS platforms. +
      +
    • The compiled Rust code for all the crates listed in Cargo.toml as a static library
    • +
    • The C header files and Swift module maps for the components
    • +
    +
  • +
  • The rust-components-swift repository which has a Package.swift that includes the xcframework and acts as the swift package the consumers import
  • +
+

The xcframework and application-services

+

In application-services, in the megazords/ios-rust directory, we have the following:

+
    +
  • A Rust crate that serves as the megazord for our iOS distributions. The megazord depends on all the Rust Component crates and re-exports their public APIs.
  • +
  • Some skeleton files for building an xcframework: +1. module.modulemap: The module map tells the Swift compiler how to use C APIs. +1. MozillaRustComponents.h: The header is used by the module map as a shortcut to specify all the available header files +1. Info.plist: The plist file specifies metadata about the resulting xcframework. For example, architectures and subdirectories.
  • +
  • The build-xcframework.sh script that stitches things together into a full xcframework bundle: +
      +
    • The xcframework format is not well documented; briefly: +
        +
      • The xcframework is a directory containing the resources compiled for multiple target architectures. The xcframework is distributed as a .zip file.
      • +
      • The top-level directory contains a subdirectory per architecture and an Info.plist. The Info.plist describes what lives in which directory.
      • +
      • Each subdirectory represents an architecture. And contains a .framework directory for that architecture.
      • +
      +
    • +
    +
  • +
+
+

It's a little unusual that we're building the xcframework by hand, rather than defining it as the build output of an Xcode project. It turns out to be simpler for our purposes, but does risk diverging from the expected format if Apple changes the details of xcframeworks in future Xcode releases.

+
+

The rust-components-swift repository

+

The repository is a Swift Package for distributing releases of Mozilla's various Rust-based application components. It provides the Swift source code packaged in a format understood by the Swift package manager, and depends on a pre-compiled binary release of the underlying Rust code published from application-services

+

The rust-components-swift repo mainly includes the following:

+
    +
  • Package.swift: Defines all the targets and products the package exposes. +
      +
    • Package.swift also includes where the package gets the xcframework that application-services builds
    • +
    +
  • +
  • make_tag.sh: A script that does the following: +
      +
    • Generates any dynamically generated Swift code, mainly: + +
    • +
    • Creates and commits a git tag that can be pushed to cut a release
    • +
    +
  • +
+
+

Consumers would then import the rust-components-swift swift package, by indicating the url of the package on github (i.e https://github.com/mozilla/rust-components-swift) and selecting a version using the git tag.

+
+

High level firefox sync interactions

+

On a high level, Firefox Sync has three main components:

+
    +
  • The Firefox Account Server: Which uses oauth to authenticate and provide users with scoped access. The FxA Server also stores input that will +be used by the clients to generate the sync keys.
  • +
  • Firefox: This is the firefox app itself, which implements the client logic to communicate with the firefox account servers, generate sync keys, +use them to encrypt data and send/receive encrypted data to/from the sync +storage servers
  • +
  • Sync Storage Server: The server that stores encrypted sync data. The clients would retrieve the encrypted data and decrypt +it client side
  • +
+

Additionally, the token server assists in providing metadata to Firefox, so that it knows which sync server to communicate with. +Diagram showing on a high level, how Firefox sync interacts with Firefox Accounts and Sync Services

+

Multi-platform sync diagram

+

Since we have multiple Firefox apps (Desktop, iOS, Android, Focus, etc) Firefox sync can sync across platforms. Allowing users +to access their up-to-date data across apps and devices. +Diagram showing how firefox sync is a multi-platform feature

+

Before: How sync was

+

Before our Rust Components came to life, each application had its own implementation of the sync and FxA client protocols. +This lead to duplicate logic across platforms. This was problematic since any modification to the sync or FxA client business logic +would need to be modified in all implementations and the likelihood of errors was high. +Diagram showing how firefox sync used to be, with each platform having its own implementation

+

Now: Sync is starting to streamline its components

+

Currently, we are in the process of migrating many of the sync implementation to use our Rust Component strategy. +Fenix primarily uses our Rust Components and iOS has some integrated as well. Additionally, Firefox Desktop also uses +one Rust component (Web Extension Storage).

+

The Rust components not only unify the different implementations of sync, they also provide a convenient local storage for the apps. +In other words, the apps can use the components for storage, with or without syncing to the server. +Diagram showing how firefox sync is now, with iOS and Fenix platform sharing some implementations

+

Current Status

+

The following table has the status of each of our sync Rust Components +| Application\Component | Bookmarks | History | Tabs | Passwords | Autofill | Web Extension Storage | FxA Client | +|-----------------------|-----------|---------|------|-----------|----------|-----------------------|------------| +| Fenix | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | | ✔️ | +| Firefox iOS | ✔️ | | ✔️ | ✔️ | | | ✔️ | +| Firefox Desktop | | | | | | ✔️ | | +| Focus | | | | | | | |

+

Future: Only one implementation for each sync engine

+

In an aspirational future, all the applications would use the same implementation for Sync. +However, it's unlikely that we would migrate everything to use the Rust components since some implementations +may not be prioritized, this is especially true for desktop which already has stable implementations. +That said, we can get close to this future and minimize duplicate logic and the likelihood of errors. +Diagram showing how firefox sync should be, with all platforms using one implementation

+

You can edit the diagrams in the following lucid chart (Note: Currently only Mozilla Employees can edit those diagrams): https://lucid.app/lucidchart/invitations/accept/inv_ab72e218-3ad9-4604-a7cd-7e0b0c259aa2

+

Once they are edited, you can re-import them here by replacing the old diagrams in the docs/diagrams directory on GitHub. As long as the +names are the same, you shouldn't need to edit those docs!

+

Metrics collected by Application Services components

+

Some application-services components collect telemetry using the Glean SDK.

+

Products that send telemetry via Glean must request a data-review following +the Firefox Data Collection process +before integrating any of the components listed below.

+

Rust Versions

+

Like almost all Rust projects, the entire point of the application-services +components is that they be used by external projects. If these components +use Rust features available in only the very latest Rust version, this will +cause problems for projects which aren't always able to be on that latest +version.

+

Given application-services is currently developed and maintained by Mozilla +staff, it should be no surprise that an important consideration is +mozilla-central (aka, the main Firefox repository).

+

Mozilla-central Rust policies.

+

It should also come as no surprise that the Rust policy for mozilla-central +is somewhat flexible. There is an official Rust Update Policy Document + +but everything in the future is documented as "estimated".

+

Ultimately though, that page defines 2 Rust versions - "Uses" and "Requires", +and our policy revolves around these.

+

To discover the current, actual "Uses" version, there is a Meta bug on Bugzilla that keeps +track of the latest versions as they are upgraded.

+

To discover the current, actual "Requires" version, see searchfox

+

application-services Rust version policy

+

Our official Rust version policy is:

+
    +
  • +

    All components will ship using, have all tests passing, and have clippy emit +no warnings, with the same version mozilla-central currently "uses".

    +
  • +
  • +

    All components must be capable of building (although not necessarily with +all tests passing nor without clippy errors or other warnings) with the same +version mozilla-central currently "requires".

    +
  • +
  • +

    This policy only applies to the "major" and "minor" versions - a different +patch level is still considered compliant with this policy.

    +
  • +
+

Implications of this

+

All CI for this project will try and pin itself to this same version. At +time of writing, this means that our circle CI integration + and +rust-toolchain configuration +will specify the versions (and where possible, the CI configuration file will +avoid duplicating the information in rust-toolchain)

+

We should maintain CI to ensure we still build with the "Requires" version.

+

As versions inside mozilla-central change, we will bump these versions +accordingly. While newer versions of Rust can be expected to work correctly +with our existing code, it's likely that clippy will complain in various ways +with the new version. Thus, a PR to bump the minimum version is likely to also +require a PR to make changes which keep clippy happy.

+

In the interests of avoiding redundant information which will inevitably +become stale, the circleci and rust-toolchain configuration links above +should be considered the canonical source of truth for the currently supported +official Rust version.

+

Sqlite Database Pragma Usage

+

The data below has been added as a tool for future pragma analysis work and is expected to be useful so long as our pragma usage remains stable or this doc is kept up-to-date. This should help us understand our current pragma usage and where we may be able to make improvements.

+
+ + + + + + + + +
PragmaValueComponentNotes
cache_size-6144places
foreign_keysONautofill, places, tabs, webext-storage
journal_modeWALautofill, places, tabs, webext-storage
page_size32768places
secure_deletetruelogins
temp_store2autofill, logins, places, tabs, webext_storageSetting temp_store to 2 (MEMORY) is necessary to avoid SQLITE_IOERR_GETTEMPPATH errors on Android (see here for details)
wal_autocheckpoint62places
wal_checkpointPASSIVEplacesUsed in the sync finished step in history and bookmarks syncing and in the places run_maintenance function
+
+
    +
  • The user_version pragma is excluded because the value varies and sqlite does not do anything with the value.
  • +
  • The push component does not implement any of the commonly used pragmas noted above.
  • +
  • The sqlcipher pragmas that we set have been excluded from this list as we are trying to remove sqlcipher and do not want to encourage future use.
  • +
+

Application Services Release Process

+

Nightly builds

+

Nightly builds are automatically generated using a taskcluster cron task.

+
    +
  • The results of the latest successful nightly build is listed here: +https://firefox-ci-tc.services.mozilla.com/tasks/index/project.application-services.v2.nightly/latest
  • +
  • The latest nightly decision task should be listed here: +https://firefox-ci-tc.services.mozilla.com/tasks/index/project.application-services.v2.branch.main.latest.taskgraph/decision-nightly
  • +
  • If you don't see a decision task from the day before, then contact releng. It's likely that the cron decision task is broken.
  • +
+

Release builds

+

Release builds are generated from the release-vXXX branches and triggered in Ship-it

+
    +
  • Whenever a commit is pushed to a release branch, we build candidate artifacts. These artifacts are +shippable -- if we decide that the release is ready, they just need to be copied to the correct +location.
  • +
  • The push phase of release-promotion copies the candidate to a staging location where they can +be tested.
  • +
  • The ship phase of release-promotion copies the candidate to their final, published, location.
  • +
+

[Release management] Creating a new release

+
+

This part is 100% covered by the Release Management team. The dev team should not perform these steps.

+
+

On Merge Day we take a snapshot of the current main, and prepare a release. See Firefox Release Calendar.

+
    +
  1. +

    Create a branch name with the format releases-v[release_version] off of the main branch (for example, release-v118) through the GitHub UI. +[release_version] should follow the Firefox release number. See Firefox Release Calendar.

    +
  2. +
  3. +

    Create a PR against the release branch that updates version.txt and updates the CHANGELOG.md as follows:

    +
  4. +
+
    +
  • In version.txt, update the version from [release_version].0a1 to [release_version].0.
  • +
+
diff --git a/version.txt b/version.txt
+--- a/version.txt
++++ b/version.txt
+@@ -1 +1 @@
+-118.0a1
++118.0
+
+
    +
  • In CHANGELOG.md, change In progress to _YYYY-MM-DD_ to match the Merge Day date and add a URL to the release version change log.
  • +
+
diff --git a/CHANGELOG.md b/CHANGELOG.md
+index 7f2c07a1a8..06688fdcab 100644
+--- a/CHANGELOG.md
++++ b/CHANGELOG.md
+@@ -1,8 +1,7 @@
+-# v118.0 (In progress)
+-
+-[Full Changelog](In progress)
++# v118.0 (_2023-08-28_)
+ 
+ ## General
++
+ ### 🦊 What's Changed 🦊
+ 
+ - Backward-incompatible changes to the Suggest database schema to accommodate custom details for providers ([#5745](https://github.com/mozilla/application-services/pull/5745)) and future suggestion types ([#5766](https://github.com/mozilla/application-services/pull/5766)). This only affects prototyping, because we aren't consuming Suggest in any of our products yet.
+@@ -16,7 +15,6 @@
+ - The Remote Settings client has a new `Client::get_records_with_options()` method ([#5764](https://github.com/mozilla/application-services/pull/5764)). This is for Rust consumers only; it's not exposed to Swift or Kotlin.
+ - `RemoteSettingsRecord` objects have a new `deleted` property that indicates if the record is a tombstone ([#5764](https://github.com/mozilla/application-services/pull/5764)).
+ 
+-
+ ## Rust log forwarder
+ ### 🦊 What's Changed 🦊
+ 
+@@ -34,6 +32,8 @@
+ 
+ - Removed previously deprecated commands `experimenter`, `ios`, `android`, `intermediate-repr` ([#5784](https://github.com/mozilla/application-services/pull/5784)).
+ 
++[Full Changelog](https://github.com/mozilla/application-services/compare/v117.0...v118.0)
++
+ # v117.0 (_2023-07-31_)
+
+
    +
  • Create a commit named 'Cut release v[release_version].0` and a PR for this change.
  • +
  • See example PR
  • +
+
    +
  1. Create a PR against the main branch that updates version.txt and updates the CHANGELOG.md as follows:
  2. +
+
    +
  • In version.txt, update the version from [release_version].0a1 to [next_release_version].0a1.
  • +
+
diff --git a/version.txt b/version.txt
+--- a/version.txt
++++ b/version.txt
+@@ -1 +1@@
+-118.0a1
++119.0a1
+
+
    +
  • In CHANGELOG.md, change the in progress version from [release_version].0 to [next_release_version].0, add a header for the previous release version, and add a URL to the previous release version change log.
  • +
+
diff --git a/CHANGELOG.md b/CHANGELOG.md
+--- a/CHANGELOG.md
++++ b/CHANGELOG.md
+@@ -1,8 +1,7 @@
+-# v118.0 (In progress)
++# v119.0 (In progress)
+
+[Full Changelog](In progress)
+
++# v118.0 (_2023-08-28_)
+@@ -34,6 +36,8 @@
++[Full Changelog](https://github.com/mozilla/application-services/compare/v117.0...v118.0)
++
+# v117.0 (_2023-07-31_)
+
+
    +
  • Create a commit named 'Start release v[next_release_version].0` and a PR for this change.
  • +
  • See example PR
  • +
+
    +
  1. Once all of the above PRs have landed, create a new Application Services release in Ship-It.
  2. +
+
    +
  • Promote and Ship the release.
  • +
+
    +
  1. +

    Tag the release in the Application Services repo.

    +
  2. +
  3. +

    Inform the Application Services team to cut a release of rust-components-swift

    +
  4. +
+
    +
  • The team will tag the repo and let you know the git hash to use when updating the consumer applications
  • +
+
    +
  1. Update consumer applications
  2. +
+ +

[Release management] Creating a new release via scripts:

+
    +
  1. Run pip3 install -r automation/requirements.txt to install the required Python packages.
  2. +
  3. Run the automation/prepare-release.py script. This should:
  4. +
+
    +
  • Create a new branch named release-vXXX
  • +
  • Create a PR against that branch that updates version.txt like this:
  • +
+
diff --git a/version.txt b/version.txt
+index 8cd923873..6482018e0 100644
+--- a/version.txt
++++ b/version.txt
+@@ -1,4 +1,4 @@
+-114.0a1
++114.0
+
+
    +
  • Create a PR on main that starts a new CHANGELOG header.
  • +
+
    +
  1. Tag the release with automation/tag-release.py [major-version-number]
  2. +
+

Cutting patch releases for uplifted changes

+

If you want to uplift changes into the a previous release:

+
    +
  • Make sure the changes are present in main and have been thoroughly tested.
  • +
  • Checkout the release-vXXX branch, where XXX is the major version number.
  • +
  • Bump the version in version.txt
  • +
  • Cherry-pick any commits that you want to uplift
  • +
  • Create a PR for the changes
  • +
  • Trigger release-promotion for the branch once the PRs are approved, merged, and CI has completed
  • +
+

What gets built in a release?

+

We build several artifacts for both nightlies and releases:

+
    +
  • nightly.json / release.json. This is a JSON file containing metadata from successful +builds. The metadata for the latest successful build can be found from a taskcluster index: +https://firefox-ci-tc.services.mozilla.com/api/index/v1/task/project.application-services.v2.release.latest/artifacts/public%2Fbuild%2Frelease.json +The JSON file contains: +
      +
    • The version number for the nightly/release
    • +
    • The git commit ID
    • +
    • The maven channel for Kotlin packages: +
        +
      • maven-production: https://maven.mozilla.org/?prefix=maven2/org/mozilla/appservices/
      • +
      • maven-nightly-production: https://maven.mozilla.org/?prefix=maven2/org/mozilla/appservices/nightly/
      • +
      • maven-staging: https://maven-default.stage.mozaws.net/?prefix=maven2/org/mozilla/appservices/
      • +
      • maven-nightly-staging: https://maven-default.stage.mozaws.net/?prefix=maven2/org/mozilla/appservices/nightly/
      • +
      +
    • +
    • Links to nimbus-fml.*: used to build Firefox/Focus on Android and iOS
    • +
    • Links to *RustComponentsSwift.xcframework.zip: XCFramework archives used to build Firefox/Focus on iOS
    • +
    • Link to swift-components.tar.xz: UniFFI-generated swift files which get extracted into the +rust-components-swift repository for each release.
    • +
    +
  • +
+

Nightly builds

+

For nightly builds, consumers get the artifacts directly from the taskcluster.

+
    +
  • For, firefox-android, the nightlies are handled by relbot
  • +
  • For, firefox-ios, the nightlies are consumed by rust-components-swift. rust-components-swift makes a github release, which is picked up by a Github action in firefox-ios
  • +
+

Release promotion

+

For real releases, we use the taskcluster release-promotion action. Release promotion happens in two phases:

+
    +
  • promote copies the artifacts from taskcluster and moves them to a staging area. This +allows for testing the consumer apps with the artifacts.
  • +
  • ship copies the artifacts from the staging area to archive.mozilla.org, which serves as +their permanent storage area.
  • +
+

Application Services Build and Publish Pipeline

+

This document provides an overview of the build-and-publish pipeline used to make our work +in this repo available to consuming applications. It's intended both to document the pipeline +for development and maintenance purposes, and to serve as a basic analysis of the integrity +protections that it offers (so you'll notice there are notes and open questions in place where +we haven't fully hashed out all those details).

+

The key points:

+
    +
  • We use "stable" Rust. CI is pinned to whatever version is currently used on mozilla-central +to help with vendoring into that repository. You should check what current values are +specified for CircleCI and for TaskCluster
  • +
  • We use Cargo for building and testing the core Rust code in isolation, +Gradle with rust-android-gradle +for combining Rust and Kotlin code into Android components and running tests against them, +and XCframeworks driving XCode +for combining Rust and Swift code into iOS components.
  • +
  • TaskCluster runs on every pull-request, release, +and push to main, to ensure Android artifacts build correctly and to execute their +tests via gradle.
  • +
  • CircleCI runs on every branch, pull-request (including forks), and release, +to execute lint checks and automated tests at the Rust and Swift level.
  • +
  • Releases align with the Firefox Releases schedules, and nightly releases are automated to run daily see the releases for more information
  • +
  • Notifications about build failures are sent to a mailing list at +a-s-ci-failures@mozilla.com
  • +
  • Our Taskcluster implementation is almost entirely maintained by the Release Engineering team. +The proper way to contact them in case of emergency or for new developments is to ask on the #releaseduty-mobile Slack channel. +Our main point of contact is @mihai.
  • +
+

For Android consumers these are the steps by which Application Services code becomes available, +and the integrity-protection mechanisms that apply at each step:

+
    +
  1. Code is developed in branches and lands on main via pull request. +
      +
    • GitHub branch protection prevents code being pushed to main without review.
    • +
    • CircleCI and TaskCluster run automated tests against the code, but do not have +the ability to push modified code back to GitHub thanks to the above branch protection. +
        +
      • TaskCluster jobs do not run against PRs opened by the general public, +only for PRs from repo collaborators.
      • +
      +
    • +
    • Contra the github org security guidelines, +signing of individual commits is encouraged but is not required. Our experience in practice +has been that this adds friction for contributors without sufficient tangible benefit.
    • +
    +
  2. +
  3. Developers manually create a release from latest main. +
      +
    • The ability to create new releases is managed entirely via github's permission model.
    • +
    • TODO: the github org security guidelines +recommend signing tags, and auditing all included commits as part of the release process. +We should consider some tooling to support this. I don't think there's any way to force +githib to only accept signed releases in the same way it can enforce signed commits.
    • +
    +
  4. +
  5. TaskCluster checks out the release tag, builds it for all target platforms, and runs automated tests. +
      +
    • These tasks run in a pre-built docker image, helping assure integrity of the build environment.
    • +
    • TODO: could this step check for signed tags as an additional integrity measure?
    • +
    +
  6. +
  7. TaskCluster uploads symbols to Socorro. +
      +
    • The access token for this is currently tied to @eoger's LDAP account.
    • +
    +
  8. +
  9. TaskCluster uploads built artifacts to maven.mozilla.org +
      +
    • Secret key for uploading to maven is provisioned via TaskCluster, +guarded by a scope that's only available to this task.
    • +
    • TODO: could a malicious dev dependency from step (3) influence the build environment here?
    • +
    • TODO: talk about how TC's "chain of trust" might be useful here.
    • +
    +
  10. +
  11. Consumers fetch the published artifacts from maven.mozilla.org.
  12. +
+

For iOS consumers the corresponding steps are:

+
    +
  1. Code is developed in branches and lands on main via pull request, as above.
  2. +
  3. Developers manually create a release from latest main, as above.
  4. +
  5. CircleCI checks out the release tag, builds it, and runs automated tests. +
      +
    • TODO: These tasks bootstrap their build environment by fetching software over https. +could we do more to ensure the integrity of the build environment?
    • +
    • TODO: could this step check for signed tags as an additional integrity measure?
    • +
    • TODO: can we prevent these steps from being able to see the tokens used +for publishing in subsequent steps?
    • +
    +
  6. +
  7. CircleCI builds a binary artifact: +
      +
    • An XCFramework containing just Rust code and header files, as a zipfile, for use by Swift Packags.
    • +
    • TODO: could a malicious dev dependency from step (3) influence the build environment here?
    • +
    +
  8. +
  9. CircleCI uses dpl to publish to GitHub as a release artifact. + +
  10. +
  11. Consumers add Application services as a dependency from the Rust Components Swift repo using Apple's Swift Package Manager.
  12. +
+

For consuming in mozilla-central, see how to vendor components into mozilla-central +

+

This is a diagram of the pipeline as it exists (and is planned) for the Nimbus SDK, one of the +libraries in Application Services: +(Source: https://miro.com/app/board/o9J_lWx3jhY=/)

+

Nimbus SDK Build and Publish Pipeline

+

Authentication and secrets

+

@appsvc-moz account

+

There's an appsvc-moz github account owned by one of the application-services team (currently markh, but we should consider rotating ownership). +Given only 1 2fa device can be connected to a github account, multiple owners doesn't seem practical. +In most cases, whenever a github account needs to own a secret for any CI, it will be owned by this account.

+

CircleCI

+

CircleCI config requires a github token (owned by @appsvc-moz). This is a "personal access token" +obtained via github's Settings -> Developer Settings -> Personal Access Tokens -> Classic Token. This token:

+
    +
  • Should be named something like "circleci"
  • +
  • Have "no expiration" (XXX - this seems wrong, should we adjust?)
  • +
+

Once you have generated the token, it must be added to https://app.circleci.com/settings/project/github/mozilla/application-services/environment-variables as the environment variable GITHUB_TOKEN

+

Guide to upgrading NSS

+

Our components rely on cryptographic primitives provided by NSS. +Every month or so, a new version of NSS is published and we should try to keep our version as up-to-date as possible.

+

Because it makes unit testing easier on Android, and helps startup performance on iOS, we compile NSS ourselves and link to it statically. Note that NSS is mainly used by Mozilla as a dynamic library and the NSS project is missing related CI jobs (iOS builds, windows cross-compile builds etc.) so you should expect breakage when updating the library (hence this guide).

+
+

Updating the Version

+

The build code is located in the libs/ folder.

+

The version string is located in the beginning of build-all.sh.

+

For most NSS upgrades, you'll need to bump the version number in this file and update the downloaded archive checksum. Then follow the steps for Updating the cross-compiled NSS Artifacts below. The actual build invocations are located in platform-specific script files (e.g. build-nss-ios.sh) but usually don't require any changes.

+

To test out updating NSS version:

+
    +
  • Ensure you've bumped the NSS in build-all.sh
  • +
  • Clear any old NSS build artifacts: rm -rf ./libs/desktop && cargo clean
  • +
  • Install the updates version: ./libs/verify-desktop-environment.sh
  • +
  • Try it out: cargo test
  • +
+
+

Updating the Cross-Compiled NSS Artifacts

+

We use a Linux TC worker for cross-compiling NSS for iOS, Android and Linux desktop machines. However, due to the complexity of the NSS build process, there is no easy way for cross-compiling MacOS and Windows -- so we currently use pre-built artifacts for MacOS desktop machines (ref #5210).

+
    +
  1. Look for the tagged version from the NSS CI +
    +

    usually a description with something like Added tag NSS_3_90_RTM

    +
    +
  2. +
  3. Select the build for the following system(s) (first task with the title "B"): +
      +
    • For Intel MacOS: mac opt-static
    • +
    +
  4. +
  5. Update taskcluster/ci/fetch/kind.yml, specifically nss-artifact task to the appropriate url and checksum and size +
    +

    Note: To get the checksum, you can run shasum -a 256 {path-to-artifact} or you can make a PR and see the output of the failed log.

    +
    +
  6. +
  7. Update the SHA256 value for darwin cross-compile in libs/build-nss-desktop.sh to the same checksum as above.
  8. +
  9. Open a pull request with these changes and it should update the Taskcluster artifact
  10. +
+
+

Exposing new functions

+

If the new version of NSS comes with new functions that you want to expose, you will need to:

+
    +
  • Add low-level bindings for those functions in the nss_sys crate; follow the instructions in +README for that crate.
  • +
  • Expose a safe wrapper API for the functions from the nss crate;
  • +
  • Expose a convenient high-level API for the functions from the rc_crypto crate;
  • +
+

Tips for Fixing Bustage

+

On top of the primitives provided by NSS, we have built a safe Rust wrapper named rc_crypto that links to NSS and makes these cryptographic primitives available to our components.

+

The linkage is done by the nss_build_common crate. Note that it supports a is_gecko feature to link to NSS dynamically on Desktop.

+

Because the NSS static build process does not output a single .a file (it would be great if it did), this file must describe for each architecture which modules should we link against. It is mostly a duplication of logic from the NSS gyp build files. Note that this logic is also duplicated in our NSS lib build steps (e.g. build-nss-desktop.sh).

+

One of the most common build failures we get when upgrading NSS comes from NSS adding new vectorized/asm versions of a crypto algorithm for a specific architecture in order to improve performance. This new optimized code gets implemented as a new gyp target/module that is emitted only for the supported architectures. +When we upgrade our copy of NSS we notice the linking step failing on CI jobs because of undefined symbols.

+

This PR shows how we update nss_common_build and the build scripts to accommodate for these new modules. Checking the changelog for any suspect commit relating to hardware acceleration is rumored to help.

+
fxa_client - Rust
Expand description

Firefox Accounts Client

+

The fxa-client component lets applications integrate with the +Firefox Accounts +identity service. The shape of a typical integration would look +something like:

+
    +
  • +

    Out-of-band, register your application with the Firefox Accounts service, +providing an OAuth redirect_uri controlled by your application and +obtaining an OAuth client_id.

    +
  • +
  • +

    On application startup, create a FirefoxAccount object to represent the +signed-in state of the application.

    + +
  • +
  • +

    When the user wants to sign in to your application, direct them through +a web-based OAuth flow using begin_oauth_flow +or begin_pairing_flow; when they return +to your registered redirect_uri, pass the resulting authorization state back to +complete_oauth_flow to sign them in.

    +
  • +
  • +

    Display information about the signed-in user by using the data from +get_profile.

    +
  • +
  • +

    Access account-related services on behalf of the user by obtaining OAuth +access tokens via get_access_token.

    +
  • +
  • +

    If the user opts to sign out of the application, calling disconnect +and then discarding any persisted account data.

    +
  • +
+

Structs

Enums

Type Definitions

  • Result returned by public-facing API functions
  • Result returned by internal functions
+

Developing documentation

+

The documentation in this repository pertains to the application-services library, primarily the sync and storage components, firefox account client and the nimbus-sdk experimentation client.

+

The markdown is converted to static HTML using mdbook. To add a new document, you need to add it to the SUMMARY.md file which produces the sidebar table of contents.

+

Building documentation

+

Building the narrative (book) documentation

+

The mdbook crate is required in order to build the documentation:

+
cargo install mdbook mdbook-mermaid mdbook-open-on-gh
+
+

The repository documents are be built with:

+
./tools/build.docs.sh
+
+

The built documentation is saved in build/docs/book.

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + +
+ + diff --git a/book/rust-docs/as_ohttp_client/all.html b/book/rust-docs/as_ohttp_client/all.html new file mode 100644 index 0000000000..a6491d6f43 --- /dev/null +++ b/book/rust-docs/as_ohttp_client/all.html @@ -0,0 +1 @@ +List of all items in this crate
\ No newline at end of file diff --git a/book/rust-docs/as_ohttp_client/enum.OhttpError.html b/book/rust-docs/as_ohttp_client/enum.OhttpError.html new file mode 100644 index 0000000000..f7b95657a1 --- /dev/null +++ b/book/rust-docs/as_ohttp_client/enum.OhttpError.html @@ -0,0 +1,23 @@ +OhttpError in as_ohttp_client - Rust
pub enum OhttpError {
+    KeyFetchFailed,
+    MalformedKeyConfig,
+    UnsupportedKeyConfig,
+    InvalidSession,
+    RelayFailed,
+    CannotEncodeMessage,
+    MalformedMessage,
+    DuplicateHeaders,
+}

Variants§

§

KeyFetchFailed

§

MalformedKeyConfig

§

UnsupportedKeyConfig

§

InvalidSession

§

RelayFailed

§

CannotEncodeMessage

§

MalformedMessage

§

DuplicateHeaders

Trait Implementations§

source§

impl Debug for OhttpError

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Display for OhttpError

source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Error for OhttpError

1.30.0 · source§

fn source(&self) -> Option<&(dyn Error + 'static)>

The lower-level source of this error, if any. Read more
1.0.0 · source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type based access to context intended for error reports. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<E> Provider for Ewhere + E: Error + ?Sized,

source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (provide_any)
Data providers should implement this method to provide all values they are able to +provide by using demand. Read more
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/as_ohttp_client/index.html b/book/rust-docs/as_ohttp_client/index.html new file mode 100644 index 0000000000..4eb77325fa --- /dev/null +++ b/book/rust-docs/as_ohttp_client/index.html @@ -0,0 +1 @@ +as_ohttp_client - Rust
\ No newline at end of file diff --git a/book/rust-docs/as_ohttp_client/sidebar-items.js b/book/rust-docs/as_ohttp_client/sidebar-items.js new file mode 100644 index 0000000000..49f8362905 --- /dev/null +++ b/book/rust-docs/as_ohttp_client/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["OhttpError"],"struct":["OhttpResponse","OhttpSession","OhttpTestServer","TestServerRequest"]}; \ No newline at end of file diff --git a/book/rust-docs/as_ohttp_client/struct.OhttpResponse.html b/book/rust-docs/as_ohttp_client/struct.OhttpResponse.html new file mode 100644 index 0000000000..845c5cfbe2 --- /dev/null +++ b/book/rust-docs/as_ohttp_client/struct.OhttpResponse.html @@ -0,0 +1,11 @@ +OhttpResponse in as_ohttp_client - Rust
pub struct OhttpResponse { /* private fields */ }

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/as_ohttp_client/struct.OhttpSession.html b/book/rust-docs/as_ohttp_client/struct.OhttpSession.html new file mode 100644 index 0000000000..06aa66a2d3 --- /dev/null +++ b/book/rust-docs/as_ohttp_client/struct.OhttpSession.html @@ -0,0 +1,24 @@ +OhttpSession in as_ohttp_client - Rust
pub struct OhttpSession { /* private fields */ }

Implementations§

source§

impl OhttpSession

source

pub fn new(config: &[u8]) -> Result<Self, OhttpError>

Create a new encryption session for use with specific key configuration

+
source

pub fn encapsulate( + &self, + method: &str, + scheme: &str, + server: &str, + endpoint: &str, + headers: HashMap<String, String>, + payload: &[u8] +) -> Result<Vec<u8>, OhttpError>

Encode an HTTP request in Binary HTTP format and then encrypt it into an +Oblivious HTTP request message.

+
source

pub fn decapsulate(&self, encoded: &[u8]) -> Result<OhttpResponse, OhttpError>

Decode an OHTTP response returned in response to a request encoded on +this session.

+

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/as_ohttp_client/struct.OhttpTestServer.html b/book/rust-docs/as_ohttp_client/struct.OhttpTestServer.html new file mode 100644 index 0000000000..4011623b3a --- /dev/null +++ b/book/rust-docs/as_ohttp_client/struct.OhttpTestServer.html @@ -0,0 +1,11 @@ +OhttpTestServer in as_ohttp_client - Rust
pub struct OhttpTestServer { /* private fields */ }

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/as_ohttp_client/struct.TestServerRequest.html b/book/rust-docs/as_ohttp_client/struct.TestServerRequest.html new file mode 100644 index 0000000000..200c103688 --- /dev/null +++ b/book/rust-docs/as_ohttp_client/struct.TestServerRequest.html @@ -0,0 +1,11 @@ +TestServerRequest in as_ohttp_client - Rust
pub struct TestServerRequest { /* private fields */ }

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/autofill/all.html b/book/rust-docs/autofill/all.html new file mode 100644 index 0000000000..590f99bcf1 --- /dev/null +++ b/book/rust-docs/autofill/all.html @@ -0,0 +1 @@ +List of all items in this crate
\ No newline at end of file diff --git a/book/rust-docs/autofill/db/addresses/fn.touch.html b/book/rust-docs/autofill/db/addresses/fn.touch.html new file mode 100644 index 0000000000..44ac5989b1 --- /dev/null +++ b/book/rust-docs/autofill/db/addresses/fn.touch.html @@ -0,0 +1 @@ +touch in autofill::db::addresses - Rust

Function autofill::db::addresses::touch

source ·
pub fn touch(conn: &Connection, guid: &Guid) -> Result<()>
\ No newline at end of file diff --git a/book/rust-docs/autofill/db/addresses/index.html b/book/rust-docs/autofill/db/addresses/index.html new file mode 100644 index 0000000000..373bbf0674 --- /dev/null +++ b/book/rust-docs/autofill/db/addresses/index.html @@ -0,0 +1 @@ +autofill::db::addresses - Rust
\ No newline at end of file diff --git a/book/rust-docs/autofill/db/addresses/sidebar-items.js b/book/rust-docs/autofill/db/addresses/sidebar-items.js new file mode 100644 index 0000000000..98e7865164 --- /dev/null +++ b/book/rust-docs/autofill/db/addresses/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["touch"]}; \ No newline at end of file diff --git a/book/rust-docs/autofill/db/credit_cards/fn.delete_credit_card.html b/book/rust-docs/autofill/db/credit_cards/fn.delete_credit_card.html new file mode 100644 index 0000000000..c23bcd904b --- /dev/null +++ b/book/rust-docs/autofill/db/credit_cards/fn.delete_credit_card.html @@ -0,0 +1 @@ +delete_credit_card in autofill::db::credit_cards - Rust
pub fn delete_credit_card(conn: &Connection, guid: &Guid) -> Result<bool>
\ No newline at end of file diff --git a/book/rust-docs/autofill/db/credit_cards/fn.scrub_encrypted_credit_card_data.html b/book/rust-docs/autofill/db/credit_cards/fn.scrub_encrypted_credit_card_data.html new file mode 100644 index 0000000000..3c4e14fd38 --- /dev/null +++ b/book/rust-docs/autofill/db/credit_cards/fn.scrub_encrypted_credit_card_data.html @@ -0,0 +1 @@ +scrub_encrypted_credit_card_data in autofill::db::credit_cards - Rust
pub fn scrub_encrypted_credit_card_data(conn: &Connection) -> Result<()>
\ No newline at end of file diff --git a/book/rust-docs/autofill/db/credit_cards/fn.touch.html b/book/rust-docs/autofill/db/credit_cards/fn.touch.html new file mode 100644 index 0000000000..db2b3bc798 --- /dev/null +++ b/book/rust-docs/autofill/db/credit_cards/fn.touch.html @@ -0,0 +1 @@ +touch in autofill::db::credit_cards - Rust

Function autofill::db::credit_cards::touch

source ·
pub fn touch(conn: &Connection, guid: &Guid) -> Result<()>
\ No newline at end of file diff --git a/book/rust-docs/autofill/db/credit_cards/fn.update_credit_card.html b/book/rust-docs/autofill/db/credit_cards/fn.update_credit_card.html new file mode 100644 index 0000000000..efb66292e4 --- /dev/null +++ b/book/rust-docs/autofill/db/credit_cards/fn.update_credit_card.html @@ -0,0 +1,5 @@ +update_credit_card in autofill::db::credit_cards - Rust
pub fn update_credit_card(
+    conn: &Connection,
+    guid: &Guid,
+    credit_card: &UpdatableCreditCardFields
+) -> Result<()>
\ No newline at end of file diff --git a/book/rust-docs/autofill/db/credit_cards/index.html b/book/rust-docs/autofill/db/credit_cards/index.html new file mode 100644 index 0000000000..a14f90f00d --- /dev/null +++ b/book/rust-docs/autofill/db/credit_cards/index.html @@ -0,0 +1 @@ +autofill::db::credit_cards - Rust
\ No newline at end of file diff --git a/book/rust-docs/autofill/db/credit_cards/sidebar-items.js b/book/rust-docs/autofill/db/credit_cards/sidebar-items.js new file mode 100644 index 0000000000..0da2e93c81 --- /dev/null +++ b/book/rust-docs/autofill/db/credit_cards/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["delete_credit_card","scrub_encrypted_credit_card_data","touch","update_credit_card"]}; \ No newline at end of file diff --git a/book/rust-docs/autofill/db/index.html b/book/rust-docs/autofill/db/index.html new file mode 100644 index 0000000000..cb8e88cdc1 --- /dev/null +++ b/book/rust-docs/autofill/db/index.html @@ -0,0 +1 @@ +autofill::db - Rust
\ No newline at end of file diff --git a/book/rust-docs/autofill/db/models/address/index.html b/book/rust-docs/autofill/db/models/address/index.html new file mode 100644 index 0000000000..de2945707c --- /dev/null +++ b/book/rust-docs/autofill/db/models/address/index.html @@ -0,0 +1 @@ +autofill::db::models::address - Rust
\ No newline at end of file diff --git a/book/rust-docs/autofill/db/models/address/sidebar-items.js b/book/rust-docs/autofill/db/models/address/sidebar-items.js new file mode 100644 index 0000000000..88441f1a17 --- /dev/null +++ b/book/rust-docs/autofill/db/models/address/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"struct":["Address","InternalAddress","UpdatableAddressFields"]}; \ No newline at end of file diff --git a/book/rust-docs/autofill/db/models/address/struct.Address.html b/book/rust-docs/autofill/db/models/address/struct.Address.html new file mode 100644 index 0000000000..e3be6ca336 --- /dev/null +++ b/book/rust-docs/autofill/db/models/address/struct.Address.html @@ -0,0 +1,38 @@ +Address in autofill::db::models::address - Rust

Struct autofill::db::models::address::Address

source ·
pub struct Address {
Show 17 fields + pub guid: String, + pub given_name: String, + pub additional_name: String, + pub family_name: String, + pub organization: String, + pub street_address: String, + pub address_level3: String, + pub address_level2: String, + pub address_level1: String, + pub postal_code: String, + pub country: String, + pub tel: String, + pub email: String, + pub time_created: i64, + pub time_last_used: Option<i64>, + pub time_last_modified: i64, + pub times_used: i64, +
}

Fields§

§guid: String§given_name: String§additional_name: String§family_name: String§organization: String§street_address: String§address_level3: String§address_level2: String§address_level1: String§postal_code: String§country: String§tel: String§email: String§time_created: i64§time_last_used: Option<i64>§time_last_modified: i64§times_used: i64

Trait Implementations§

source§

impl Clone for Address

source§

fn clone(&self) -> Address

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for Address

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for Address

source§

fn default() -> Address

Returns the “default value” for a type. Read more
source§

impl From<InternalAddress> for Address

source§

fn from(ia: InternalAddress) -> Self

Converts to this type from the input type.
source§

impl Hash for Address

source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · source§

fn hash_slice<H>(data: &[Self], state: &mut H)where + H: Hasher, + Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
source§

impl PartialEq<Address> for Address

source§

fn eq(&self, other: &Address) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Eq for Address

source§

impl StructuralEq for Address

source§

impl StructuralPartialEq for Address

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> CallHasher for Twhere + T: Hash + ?Sized,

§

fn get_hash<H, B>(value: &H, build_hasher: &B) -> u64where + H: Hash + ?Sized, + B: BuildHasher,

source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/autofill/db/models/address/struct.InternalAddress.html b/book/rust-docs/autofill/db/models/address/struct.InternalAddress.html new file mode 100644 index 0000000000..3435ea3e1f --- /dev/null +++ b/book/rust-docs/autofill/db/models/address/struct.InternalAddress.html @@ -0,0 +1,38 @@ +InternalAddress in autofill::db::models::address - Rust
pub struct InternalAddress {
Show 14 fields + pub guid: Guid, + pub given_name: String, + pub additional_name: String, + pub family_name: String, + pub organization: String, + pub street_address: String, + pub address_level3: String, + pub address_level2: String, + pub address_level1: String, + pub postal_code: String, + pub country: String, + pub tel: String, + pub email: String, + pub metadata: Metadata, +
}

Fields§

§guid: Guid§given_name: String§additional_name: String§family_name: String§organization: String§street_address: String§address_level3: String§address_level2: String§address_level1: String§postal_code: String§country: String§tel: String§email: String§metadata: Metadata

Implementations§

source§

impl InternalAddress

source

pub fn from_row(row: &Row<'_>) -> Result<InternalAddress, Error>

Trait Implementations§

source§

impl Clone for InternalAddress

source§

fn clone(&self) -> InternalAddress

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for InternalAddress

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for InternalAddress

source§

fn default() -> InternalAddress

Returns the “default value” for a type. Read more
source§

impl From<InternalAddress> for Address

source§

fn from(ia: InternalAddress) -> Self

Converts to this type from the input type.
source§

impl SyncRecord for InternalAddress

source§

fn merge( + incoming: &Self, + local: &Self, + mirror: &Option<Self> +) -> MergeResult<Self>

Performs a three-way merge between an incoming, local, and mirror record. +If a merge cannot be successfully completed (ie, if we find the same +field has changed both locally and remotely since the last sync), the +local record data is returned with a new guid and updated sync metadata. +Note that mirror being None is an edge-case and typically means first +sync since a “reset” (eg, disconnecting and reconnecting.

+
source§

fn record_name() -> &'static str

source§

fn id(&self) -> &Guid

source§

fn metadata(&self) -> &Metadata

source§

fn metadata_mut(&mut self) -> &mut Metadata

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/autofill/db/models/address/struct.UpdatableAddressFields.html b/book/rust-docs/autofill/db/models/address/struct.UpdatableAddressFields.html new file mode 100644 index 0000000000..cee1e21faf --- /dev/null +++ b/book/rust-docs/autofill/db/models/address/struct.UpdatableAddressFields.html @@ -0,0 +1,26 @@ +UpdatableAddressFields in autofill::db::models::address - Rust
pub struct UpdatableAddressFields {
+    pub given_name: String,
+    pub additional_name: String,
+    pub family_name: String,
+    pub organization: String,
+    pub street_address: String,
+    pub address_level3: String,
+    pub address_level2: String,
+    pub address_level1: String,
+    pub postal_code: String,
+    pub country: String,
+    pub tel: String,
+    pub email: String,
+}

Fields§

§given_name: String§additional_name: String§family_name: String§organization: String§street_address: String§address_level3: String§address_level2: String§address_level1: String§postal_code: String§country: String§tel: String§email: String

Trait Implementations§

source§

impl Clone for UpdatableAddressFields

source§

fn clone(&self) -> UpdatableAddressFields

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for UpdatableAddressFields

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for UpdatableAddressFields

source§

fn default() -> UpdatableAddressFields

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/autofill/db/models/credit_card/index.html b/book/rust-docs/autofill/db/models/credit_card/index.html new file mode 100644 index 0000000000..e4d0281112 --- /dev/null +++ b/book/rust-docs/autofill/db/models/credit_card/index.html @@ -0,0 +1 @@ +autofill::db::models::credit_card - Rust
\ No newline at end of file diff --git a/book/rust-docs/autofill/db/models/credit_card/sidebar-items.js b/book/rust-docs/autofill/db/models/credit_card/sidebar-items.js new file mode 100644 index 0000000000..bbc1c46460 --- /dev/null +++ b/book/rust-docs/autofill/db/models/credit_card/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"struct":["CreditCard","InternalCreditCard","UpdatableCreditCardFields"]}; \ No newline at end of file diff --git a/book/rust-docs/autofill/db/models/credit_card/struct.CreditCard.html b/book/rust-docs/autofill/db/models/credit_card/struct.CreditCard.html new file mode 100644 index 0000000000..357ff48162 --- /dev/null +++ b/book/rust-docs/autofill/db/models/credit_card/struct.CreditCard.html @@ -0,0 +1,25 @@ +CreditCard in autofill::db::models::credit_card - Rust
pub struct CreditCard {
+    pub guid: String,
+    pub cc_name: String,
+    pub cc_number_enc: String,
+    pub cc_number_last_4: String,
+    pub cc_exp_month: i64,
+    pub cc_exp_year: i64,
+    pub cc_type: String,
+    pub time_created: i64,
+    pub time_last_used: Option<i64>,
+    pub time_last_modified: i64,
+    pub times_used: i64,
+}

Fields§

§guid: String§cc_name: String§cc_number_enc: String§cc_number_last_4: String§cc_exp_month: i64§cc_exp_year: i64§cc_type: String§time_created: i64§time_last_used: Option<i64>§time_last_modified: i64§times_used: i64

Trait Implementations§

source§

impl Clone for CreditCard

source§

fn clone(&self) -> CreditCard

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for CreditCard

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for CreditCard

source§

fn default() -> CreditCard

Returns the “default value” for a type. Read more
source§

impl From<InternalCreditCard> for CreditCard

source§

fn from(icc: InternalCreditCard) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/autofill/db/models/credit_card/struct.InternalCreditCard.html b/book/rust-docs/autofill/db/models/credit_card/struct.InternalCreditCard.html new file mode 100644 index 0000000000..28cabcefb0 --- /dev/null +++ b/book/rust-docs/autofill/db/models/credit_card/struct.InternalCreditCard.html @@ -0,0 +1,32 @@ +InternalCreditCard in autofill::db::models::credit_card - Rust
pub struct InternalCreditCard {
+    pub guid: Guid,
+    pub cc_name: String,
+    pub cc_number_enc: String,
+    pub cc_number_last_4: String,
+    pub cc_exp_month: i64,
+    pub cc_exp_year: i64,
+    pub cc_type: String,
+    pub metadata: Metadata,
+}

Fields§

§guid: Guid§cc_name: String§cc_number_enc: String§cc_number_last_4: String§cc_exp_month: i64§cc_exp_year: i64§cc_type: String§metadata: Metadata

Implementations§

Trait Implementations§

source§

impl Clone for InternalCreditCard

source§

fn clone(&self) -> InternalCreditCard

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for InternalCreditCard

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for InternalCreditCard

source§

fn default() -> InternalCreditCard

Returns the “default value” for a type. Read more
source§

impl From<InternalCreditCard> for CreditCard

source§

fn from(icc: InternalCreditCard) -> Self

Converts to this type from the input type.
source§

impl SyncRecord for InternalCreditCard

source§

fn merge( + incoming: &Self, + local: &Self, + mirror: &Option<Self> +) -> MergeResult<Self>

Performs a three-way merge between an incoming, local, and mirror record. +If a merge cannot be successfully completed (ie, if we find the same +field has changed both locally and remotely since the last sync), the +local record data is returned with a new guid and updated sync metadata. +Note that mirror being None is an edge-case and typically means first +sync since a “reset” (eg, disconnecting and reconnecting.

+
source§

fn record_name() -> &'static str

source§

fn id(&self) -> &Guid

source§

fn metadata(&self) -> &Metadata

source§

fn metadata_mut(&mut self) -> &mut Metadata

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/autofill/db/models/credit_card/struct.UpdatableCreditCardFields.html b/book/rust-docs/autofill/db/models/credit_card/struct.UpdatableCreditCardFields.html new file mode 100644 index 0000000000..9de1aba452 --- /dev/null +++ b/book/rust-docs/autofill/db/models/credit_card/struct.UpdatableCreditCardFields.html @@ -0,0 +1,20 @@ +UpdatableCreditCardFields in autofill::db::models::credit_card - Rust
pub struct UpdatableCreditCardFields {
+    pub cc_name: String,
+    pub cc_number_enc: String,
+    pub cc_number_last_4: String,
+    pub cc_exp_month: i64,
+    pub cc_exp_year: i64,
+    pub cc_type: String,
+}

Fields§

§cc_name: String§cc_number_enc: String§cc_number_last_4: String§cc_exp_month: i64§cc_exp_year: i64§cc_type: String

Trait Implementations§

source§

impl Clone for UpdatableCreditCardFields

source§

fn clone(&self) -> UpdatableCreditCardFields

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for UpdatableCreditCardFields

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for UpdatableCreditCardFields

source§

fn default() -> UpdatableCreditCardFields

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/autofill/db/models/index.html b/book/rust-docs/autofill/db/models/index.html new file mode 100644 index 0000000000..ddaa4de42d --- /dev/null +++ b/book/rust-docs/autofill/db/models/index.html @@ -0,0 +1 @@ +autofill::db::models - Rust

Module autofill::db::models

source ·

Modules

Structs

  • Metadata that’s common between the records.
\ No newline at end of file diff --git a/book/rust-docs/autofill/db/models/sidebar-items.js b/book/rust-docs/autofill/db/models/sidebar-items.js new file mode 100644 index 0000000000..3a2e9c02d1 --- /dev/null +++ b/book/rust-docs/autofill/db/models/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"mod":["address","credit_card"],"struct":["Metadata"]}; \ No newline at end of file diff --git a/book/rust-docs/autofill/db/models/struct.Metadata.html b/book/rust-docs/autofill/db/models/struct.Metadata.html new file mode 100644 index 0000000000..0af5b1b0b7 --- /dev/null +++ b/book/rust-docs/autofill/db/models/struct.Metadata.html @@ -0,0 +1,26 @@ +Metadata in autofill::db::models - Rust

Struct autofill::db::models::Metadata

source ·
pub struct Metadata {
+    pub time_created: Timestamp,
+    pub time_last_used: Timestamp,
+    pub time_last_modified: Timestamp,
+    pub times_used: i64,
+    pub sync_change_counter: i64,
+}
Expand description

Metadata that’s common between the records.

+

Fields§

§time_created: Timestamp§time_last_used: Timestamp§time_last_modified: Timestamp§times_used: i64§sync_change_counter: i64

Implementations§

source§

impl Metadata

source

pub fn merge(&mut self, other: &Metadata, mirror: Option<&Metadata>)

Merge the metadata from other, and possibly mirror, into self +(which must already have valid metadata). +Note that mirror being None is an edge-case and typically means first +sync since a “reset” (eg, disconnecting and reconnecting.

+

Trait Implementations§

source§

impl Clone for Metadata

source§

fn clone(&self) -> Metadata

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for Metadata

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for Metadata

source§

fn default() -> Metadata

Returns the “default value” for a type. Read more
source§

impl PartialEq<Metadata> for Metadata

source§

fn eq(&self, other: &Metadata) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Copy for Metadata

source§

impl Eq for Metadata

source§

impl StructuralEq for Metadata

source§

impl StructuralPartialEq for Metadata

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/autofill/db/schema/constant.ADDRESS_COMMON_COLS.html b/book/rust-docs/autofill/db/schema/constant.ADDRESS_COMMON_COLS.html new file mode 100644 index 0000000000..9fdbecbb9a --- /dev/null +++ b/book/rust-docs/autofill/db/schema/constant.ADDRESS_COMMON_COLS.html @@ -0,0 +1,18 @@ +ADDRESS_COMMON_COLS in autofill::db::schema - Rust
pub const ADDRESS_COMMON_COLS: &str = "
+    guid,
+    given_name,
+    additional_name,
+    family_name,
+    organization,
+    street_address,
+    address_level3,
+    address_level2,
+    address_level1,
+    postal_code,
+    country,
+    tel,
+    email,
+    time_created,
+    time_last_used,
+    time_last_modified,
+    times_used";
\ No newline at end of file diff --git a/book/rust-docs/autofill/db/schema/constant.ADDRESS_COMMON_VALS.html b/book/rust-docs/autofill/db/schema/constant.ADDRESS_COMMON_VALS.html new file mode 100644 index 0000000000..ba6abc2b39 --- /dev/null +++ b/book/rust-docs/autofill/db/schema/constant.ADDRESS_COMMON_VALS.html @@ -0,0 +1,18 @@ +ADDRESS_COMMON_VALS in autofill::db::schema - Rust
pub const ADDRESS_COMMON_VALS: &str = "
+    :guid,
+    :given_name,
+    :additional_name,
+    :family_name,
+    :organization,
+    :street_address,
+    :address_level3,
+    :address_level2,
+    :address_level1,
+    :postal_code,
+    :country,
+    :tel,
+    :email,
+    :time_created,
+    :time_last_used,
+    :time_last_modified,
+    :times_used";
\ No newline at end of file diff --git a/book/rust-docs/autofill/db/schema/constant.CREDIT_CARD_COMMON_COLS.html b/book/rust-docs/autofill/db/schema/constant.CREDIT_CARD_COMMON_COLS.html new file mode 100644 index 0000000000..1048d45f23 --- /dev/null +++ b/book/rust-docs/autofill/db/schema/constant.CREDIT_CARD_COMMON_COLS.html @@ -0,0 +1,12 @@ +CREDIT_CARD_COMMON_COLS in autofill::db::schema - Rust
pub const CREDIT_CARD_COMMON_COLS: &str = "
+    guid,
+    cc_name,
+    cc_number_enc,
+    cc_number_last_4,
+    cc_exp_month,
+    cc_exp_year,
+    cc_type,
+    time_created,
+    time_last_used,
+    time_last_modified,
+    times_used";
\ No newline at end of file diff --git a/book/rust-docs/autofill/db/schema/constant.CREDIT_CARD_COMMON_VALS.html b/book/rust-docs/autofill/db/schema/constant.CREDIT_CARD_COMMON_VALS.html new file mode 100644 index 0000000000..1167424666 --- /dev/null +++ b/book/rust-docs/autofill/db/schema/constant.CREDIT_CARD_COMMON_VALS.html @@ -0,0 +1,12 @@ +CREDIT_CARD_COMMON_VALS in autofill::db::schema - Rust
pub const CREDIT_CARD_COMMON_VALS: &str = "
+    :guid,
+    :cc_name,
+    :cc_number_enc,
+    :cc_number_last_4,
+    :cc_exp_month,
+    :cc_exp_year,
+    :cc_type,
+    :time_created,
+    :time_last_used,
+    :time_last_modified,
+    :times_used";
\ No newline at end of file diff --git a/book/rust-docs/autofill/db/schema/fn.create_empty_sync_temp_tables.html b/book/rust-docs/autofill/db/schema/fn.create_empty_sync_temp_tables.html new file mode 100644 index 0000000000..d8dc64c28c --- /dev/null +++ b/book/rust-docs/autofill/db/schema/fn.create_empty_sync_temp_tables.html @@ -0,0 +1 @@ +create_empty_sync_temp_tables in autofill::db::schema - Rust
pub fn create_empty_sync_temp_tables(db: &Connection) -> Result<()>
\ No newline at end of file diff --git a/book/rust-docs/autofill/db/schema/index.html b/book/rust-docs/autofill/db/schema/index.html new file mode 100644 index 0000000000..c012d8bcaa --- /dev/null +++ b/book/rust-docs/autofill/db/schema/index.html @@ -0,0 +1 @@ +autofill::db::schema - Rust
\ No newline at end of file diff --git a/book/rust-docs/autofill/db/schema/sidebar-items.js b/book/rust-docs/autofill/db/schema/sidebar-items.js new file mode 100644 index 0000000000..5f4bae7eae --- /dev/null +++ b/book/rust-docs/autofill/db/schema/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"constant":["ADDRESS_COMMON_COLS","ADDRESS_COMMON_VALS","CREDIT_CARD_COMMON_COLS","CREDIT_CARD_COMMON_VALS"],"fn":["create_empty_sync_temp_tables"],"struct":["AutofillConnectionInitializer"]}; \ No newline at end of file diff --git a/book/rust-docs/autofill/db/schema/struct.AutofillConnectionInitializer.html b/book/rust-docs/autofill/db/schema/struct.AutofillConnectionInitializer.html new file mode 100644 index 0000000000..6b23998aa6 --- /dev/null +++ b/book/rust-docs/autofill/db/schema/struct.AutofillConnectionInitializer.html @@ -0,0 +1,12 @@ +AutofillConnectionInitializer in autofill::db::schema - Rust
pub struct AutofillConnectionInitializer;

Trait Implementations§

source§

impl ConnectionInitializer for AutofillConnectionInitializer

source§

const NAME: &'static str = "autofill db"

source§

const END_VERSION: u32 = 2u32

source§

fn prepare(&self, conn: &Connection, _db_empty: bool) -> Result<()>

source§

fn init(&self, db: &Transaction<'_>) -> Result<()>

source§

fn upgrade_from(&self, db: &Transaction<'_>, version: u32) -> Result<()>

source§

fn finish(&self, db: &Connection) -> Result<()>

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/autofill/db/sidebar-items.js b/book/rust-docs/autofill/db/sidebar-items.js new file mode 100644 index 0000000000..0f9c7e6ff4 --- /dev/null +++ b/book/rust-docs/autofill/db/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"mod":["addresses","credit_cards","models","schema","store"],"struct":["AutofillDb"]}; \ No newline at end of file diff --git a/book/rust-docs/autofill/db/store/fn.get_registered_sync_engine.html b/book/rust-docs/autofill/db/store/fn.get_registered_sync_engine.html new file mode 100644 index 0000000000..a040844b21 --- /dev/null +++ b/book/rust-docs/autofill/db/store/fn.get_registered_sync_engine.html @@ -0,0 +1,5 @@ +get_registered_sync_engine in autofill::db::store - Rust
pub fn get_registered_sync_engine(
+    engine_id: &SyncEngineId
+) -> Option<Box<dyn SyncEngine>>
Expand description

Called by the sync manager to get a sync engine via the store previously +registered with the sync manager.

+
\ No newline at end of file diff --git a/book/rust-docs/autofill/db/store/index.html b/book/rust-docs/autofill/db/store/index.html new file mode 100644 index 0000000000..e7eba71569 --- /dev/null +++ b/book/rust-docs/autofill/db/store/index.html @@ -0,0 +1,2 @@ +autofill::db::store - Rust

Module autofill::db::store

source ·

Structs

Functions

  • Called by the sync manager to get a sync engine via the store previously +registered with the sync manager.
\ No newline at end of file diff --git a/book/rust-docs/autofill/db/store/sidebar-items.js b/book/rust-docs/autofill/db/store/sidebar-items.js new file mode 100644 index 0000000000..aaf6a5e034 --- /dev/null +++ b/book/rust-docs/autofill/db/store/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["get_registered_sync_engine"],"struct":["Store"]}; \ No newline at end of file diff --git a/book/rust-docs/autofill/db/store/struct.Store.html b/book/rust-docs/autofill/db/store/struct.Store.html new file mode 100644 index 0000000000..9a6472f140 --- /dev/null +++ b/book/rust-docs/autofill/db/store/struct.Store.html @@ -0,0 +1,27 @@ +Store in autofill::db::store - Rust

Struct autofill::db::store::Store

source ·
pub struct Store { /* private fields */ }

Implementations§

source§

impl Store

source

pub fn new(db_path: impl AsRef<Path>) -> ApiResult<Self>

source

pub fn new_shared_memory(db_name: &str) -> ApiResult<Self>

Creates a store backed by an in-memory database that shares its memory API (required for autofill sync tests).

+
source

pub fn add_credit_card( + &self, + fields: UpdatableCreditCardFields +) -> ApiResult<CreditCard>

source

pub fn get_credit_card(&self, guid: String) -> ApiResult<CreditCard>

source

pub fn get_all_credit_cards(&self) -> ApiResult<Vec<CreditCard>>

source

pub fn update_credit_card( + &self, + guid: String, + credit_card: UpdatableCreditCardFields +) -> ApiResult<()>

source

pub fn delete_credit_card(&self, guid: String) -> ApiResult<bool>

source

pub fn touch_credit_card(&self, guid: String) -> ApiResult<()>

source

pub fn add_address( + &self, + new_address: UpdatableAddressFields +) -> ApiResult<Address>

source

pub fn get_address(&self, guid: String) -> ApiResult<Address>

source

pub fn get_all_addresses(&self) -> ApiResult<Vec<Address>>

source

pub fn update_address( + &self, + guid: String, + address: UpdatableAddressFields +) -> ApiResult<()>

source

pub fn delete_address(&self, guid: String) -> ApiResult<bool>

source

pub fn touch_address(&self, guid: String) -> ApiResult<()>

source

pub fn scrub_encrypted_data(self: Arc<Self>) -> ApiResult<()>

source

pub fn register_with_sync_manager(self: Arc<Self>)

source

pub fn create_credit_cards_sync_engine(self: Arc<Self>) -> Box<dyn SyncEngine>

source

pub fn create_addresses_sync_engine(self: Arc<Self>) -> Box<dyn SyncEngine>

Auto Trait Implementations§

§

impl RefUnwindSafe for Store

§

impl Send for Store

§

impl Sync for Store

§

impl Unpin for Store

§

impl UnwindSafe for Store

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/autofill/db/struct.AutofillDb.html b/book/rust-docs/autofill/db/struct.AutofillDb.html new file mode 100644 index 0000000000..914a7b0148 --- /dev/null +++ b/book/rust-docs/autofill/db/struct.AutofillDb.html @@ -0,0 +1,431 @@ +AutofillDb in autofill::db - Rust

Struct autofill::db::AutofillDb

source ·
pub struct AutofillDb {
+    pub writer: Connection,
+    /* private fields */
+}

Fields§

§writer: Connection

Implementations§

source§

impl AutofillDb

source

pub fn new(db_path: impl AsRef<Path>) -> Result<Self>

source

pub fn new_memory(db_path: &str) -> Result<Self>

source

pub fn begin_interrupt_scope(&self) -> Result<SqlInterruptScope>

Methods from Deref<Target = Connection>§

pub fn busy_timeout(&self, timeout: Duration) -> Result<(), Error>

Set a busy handler that sleeps for a specified amount of time when a +table is locked. The handler will sleep multiple times until at +least “ms” milliseconds of sleeping have accumulated.

+

Calling this routine with an argument equal to zero turns off all busy +handlers.

+

There can only be a single busy handler for a particular database +connection at any given moment. If another busy handler was defined +(using busy_handler) prior to calling this +routine, that other busy handler is cleared.

+

Newly created connections currently have a default busy timeout of +5000ms, but this may be subject to change.

+

pub fn busy_handler( + &self, + callback: Option<fn(_: i32) -> bool> +) -> Result<(), Error>

Register a callback to handle SQLITE_BUSY errors.

+

If the busy callback is None, then SQLITE_BUSY is returned +immediately upon encountering the lock. The argument to the busy +handler callback is the number of times that the +busy handler has been invoked previously for the +same locking event. If the busy callback returns false, then no +additional attempts are made to access the +database and SQLITE_BUSY is returned to the +application. If the callback returns true, then another attempt +is made to access the database and the cycle repeats.

+

There can only be a single busy handler defined for each database +connection. Setting a new busy handler clears any previously set +handler. Note that calling busy_timeout() +or evaluating PRAGMA busy_timeout=N will change the busy handler +and thus clear any previously set busy handler.

+

Newly created connections default to a +busy_timeout() handler with a timeout +of 5000ms, although this is subject to change.

+

pub fn prepare_cached(&self, sql: &str) -> Result<CachedStatement<'_>, Error>

Prepare a SQL statement for execution, returning a previously prepared +(but not currently in-use) statement if one is available. The +returned statement will be cached for reuse by future calls to +prepare_cached once it is dropped.

+ +
fn insert_new_people(conn: &Connection) -> Result<()> {
+    {
+        let mut stmt = conn.prepare_cached("INSERT INTO People (name) VALUES (?1)")?;
+        stmt.execute(["Joe Smith"])?;
+    }
+    {
+        // This will return the same underlying SQLite statement handle without
+        // having to prepare it again.
+        let mut stmt = conn.prepare_cached("INSERT INTO People (name) VALUES (?1)")?;
+        stmt.execute(["Bob Jones"])?;
+    }
+    Ok(())
+}
+
Failure
+

Will return Err if sql cannot be converted to a C-compatible string +or if the underlying SQLite call fails.

+

pub fn set_prepared_statement_cache_capacity(&self, capacity: usize)

Set the maximum number of cached prepared statements this connection +will hold. By default, a connection will hold a relatively small +number of cached statements. If you need more, or know that you +will not use cached statements, you +can set the capacity manually using this method.

+

pub fn flush_prepared_statement_cache(&self)

Remove/finalize all prepared statements currently in the cache.

+

pub fn db_config(&self, config: DbConfig) -> Result<bool, Error>

Returns the current value of a config.

+
    +
  • SQLITE_DBCONFIG_ENABLE_FKEY: return false or true to indicate +whether FK enforcement is off or on
  • +
  • SQLITE_DBCONFIG_ENABLE_TRIGGER: return false or true to indicate +whether triggers are disabled or enabled
  • +
  • SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER: return false or true to +indicate whether fts3_tokenizer are disabled or enabled
  • +
  • SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE: return false to indicate +checkpoints-on-close are not disabled or true if they are
  • +
  • SQLITE_DBCONFIG_ENABLE_QPSG: return false or true to indicate +whether the QPSG is disabled or enabled
  • +
  • SQLITE_DBCONFIG_TRIGGER_EQP: return false to indicate +output-for-trigger are not disabled or true if it is
  • +
+

pub fn set_db_config( + &self, + config: DbConfig, + new_val: bool +) -> Result<bool, Error>

Make configuration changes to a database connection

+
    +
  • SQLITE_DBCONFIG_ENABLE_FKEY: false to disable FK enforcement, +true to enable FK enforcement
  • +
  • SQLITE_DBCONFIG_ENABLE_TRIGGER: false to disable triggers, true +to enable triggers
  • +
  • SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER: false to disable +fts3_tokenizer(), true to enable fts3_tokenizer()
  • +
  • SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE: false (the default) to enable +checkpoints-on-close, true to disable them
  • +
  • SQLITE_DBCONFIG_ENABLE_QPSG: false to disable the QPSG, true to +enable QPSG
  • +
  • SQLITE_DBCONFIG_TRIGGER_EQP: false to disable output for trigger +programs, true to enable it
  • +
+

pub fn create_scalar_function<F, T>( + &self, + fn_name: &str, + n_arg: i32, + flags: FunctionFlags, + x_func: F +) -> Result<(), Error>where + F: FnMut(&Context<'_>) -> Result<T, Error> + Send + UnwindSafe + 'static, + T: ToSql,

Attach a user-defined scalar function to +this database connection.

+

fn_name is the name the function will be accessible from SQL. +n_arg is the number of arguments to the function. Use -1 for a +variable number. If the function always returns the same value +given the same input, deterministic should be true.

+

The function will remain available until the connection is closed or +until it is explicitly removed via +remove_function.

+
Example
+
fn scalar_function_example(db: Connection) -> Result<()> {
+    db.create_scalar_function(
+        "halve",
+        1,
+        FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
+        |ctx| {
+            let value = ctx.get::<f64>(0)?;
+            Ok(value / 2f64)
+        },
+    )?;
+
+    let six_halved: f64 = db.query_row("SELECT halve(6)", [], |r| r.get(0))?;
+    assert_eq!(six_halved, 3f64);
+    Ok(())
+}
+
Failure
+

Will return Err if the function could not be attached to the connection.

+

pub fn create_aggregate_function<A, D, T>( + &self, + fn_name: &str, + n_arg: i32, + flags: FunctionFlags, + aggr: D +) -> Result<(), Error>where + A: RefUnwindSafe + UnwindSafe, + D: Aggregate<A, T> + 'static, + T: ToSql,

Attach a user-defined aggregate function to this +database connection.

+
Failure
+

Will return Err if the function could not be attached to the connection.

+

pub fn create_window_function<A, W, T>( + &self, + fn_name: &str, + n_arg: i32, + flags: FunctionFlags, + aggr: W +) -> Result<(), Error>where + A: RefUnwindSafe + UnwindSafe, + W: WindowAggregate<A, T> + 'static, + T: ToSql,

Attach a user-defined aggregate window function to +this database connection.

+

See https://sqlite.org/windowfunctions.html#udfwinfunc for more +information.

+

pub fn remove_function(&self, fn_name: &str, n_arg: i32) -> Result<(), Error>

Removes a user-defined function from this +database connection.

+

fn_name and n_arg should match the name and number of arguments +given to create_scalar_function +or create_aggregate_function.

+
Failure
+

Will return Err if the function could not be removed.

+

pub fn limit(&self, limit: Limit) -> i32

Returns the current value of a [Limit].

+

pub fn set_limit(&self, limit: Limit, new_val: i32) -> i32

Changes the [Limit] to new_val, returning the prior +value of the limit.

+

pub fn pragma_query_value<T, F>( + &self, + schema_name: Option<DatabaseName<'_>>, + pragma_name: &str, + f: F +) -> Result<T, Error>where + F: FnOnce(&Row<'_>) -> Result<T, Error>,

Query the current value of pragma_name.

+

Some pragmas will return multiple rows/values which cannot be retrieved +with this method.

+

Prefer PRAGMA function introduced in SQLite 3.20: +SELECT user_version FROM pragma_user_version;

+

pub fn pragma_query<F>( + &self, + schema_name: Option<DatabaseName<'_>>, + pragma_name: &str, + f: F +) -> Result<(), Error>where + F: FnMut(&Row<'_>) -> Result<(), Error>,

Query the current rows/values of pragma_name.

+

Prefer PRAGMA function introduced in SQLite 3.20: +SELECT * FROM pragma_collation_list;

+

pub fn pragma<F, V>( + &self, + schema_name: Option<DatabaseName<'_>>, + pragma_name: &str, + pragma_value: V, + f: F +) -> Result<(), Error>where + F: FnMut(&Row<'_>) -> Result<(), Error>, + V: ToSql,

Query the current value(s) of pragma_name associated to +pragma_value.

+

This method can be used with query-only pragmas which need an argument +(e.g. table_info('one_tbl')) or pragmas which returns value(s) +(e.g. integrity_check).

+

Prefer PRAGMA function introduced in SQLite 3.20: +SELECT * FROM pragma_table_info(?1);

+

pub fn pragma_update<V>( + &self, + schema_name: Option<DatabaseName<'_>>, + pragma_name: &str, + pragma_value: V +) -> Result<(), Error>where + V: ToSql,

Set a new value to pragma_name.

+

Some pragmas will return the updated value which cannot be retrieved +with this method.

+

pub fn pragma_update_and_check<F, T, V>( + &self, + schema_name: Option<DatabaseName<'_>>, + pragma_name: &str, + pragma_value: V, + f: F +) -> Result<T, Error>where + F: FnOnce(&Row<'_>) -> Result<T, Error>, + V: ToSql,

Set a new value to pragma_name and return the updated value.

+

Only few pragmas automatically return the updated value.

+

pub fn transaction(&mut self) -> Result<Transaction<'_>, Error>

Begin a new transaction with the default behavior (DEFERRED).

+

The transaction defaults to rolling back when it is dropped. If you +want the transaction to commit, you must call +commit or +set_drop_behavior(DropBehavior::Commit).

+
Example
+
fn perform_queries(conn: &mut Connection) -> Result<()> {
+    let tx = conn.transaction()?;
+
+    do_queries_part_1(&tx)?; // tx causes rollback if this fails
+    do_queries_part_2(&tx)?; // tx causes rollback if this fails
+
+    tx.commit()
+}
+
Failure
+

Will return Err if the underlying SQLite call fails.

+

pub fn transaction_with_behavior( + &mut self, + behavior: TransactionBehavior +) -> Result<Transaction<'_>, Error>

Begin a new transaction with a specified behavior.

+

See transaction.

+
Failure
+

Will return Err if the underlying SQLite call fails.

+

pub fn unchecked_transaction(&self) -> Result<Transaction<'_>, Error>

Begin a new transaction with the default behavior (DEFERRED).

+

Attempt to open a nested transaction will result in a SQLite error. +Connection::transaction prevents this at compile time by taking &mut self, but Connection::unchecked_transaction() may be used to defer +the checking until runtime.

+

See [Connection::transaction] and [Transaction::new_unchecked] +(which can be used if the default transaction behavior is undesirable).

+
Example
+
fn perform_queries(conn: Rc<Connection>) -> Result<()> {
+    let tx = conn.unchecked_transaction()?;
+
+    do_queries_part_1(&tx)?; // tx causes rollback if this fails
+    do_queries_part_2(&tx)?; // tx causes rollback if this fails
+
+    tx.commit()
+}
+
Failure
+

Will return Err if the underlying SQLite call fails. The specific +error returned if transactions are nested is currently unspecified.

+

pub fn savepoint(&mut self) -> Result<Savepoint<'_>, Error>

Begin a new savepoint with the default behavior (DEFERRED).

+

The savepoint defaults to rolling back when it is dropped. If you want +the savepoint to commit, you must call commit or +[set_drop_behavior(DropBehavior::Commit)](Savepoint:: +set_drop_behavior).

+
Example
+
fn perform_queries(conn: &mut Connection) -> Result<()> {
+    let sp = conn.savepoint()?;
+
+    do_queries_part_1(&sp)?; // sp causes rollback if this fails
+    do_queries_part_2(&sp)?; // sp causes rollback if this fails
+
+    sp.commit()
+}
+
Failure
+

Will return Err if the underlying SQLite call fails.

+

pub fn savepoint_with_name<T>( + &mut self, + name: T +) -> Result<Savepoint<'_>, Error>where + T: Into<String>,

Begin a new savepoint with a specified name.

+

See savepoint.

+
Failure
+

Will return Err if the underlying SQLite call fails.

+

pub fn transaction_state( + &self, + db_name: Option<DatabaseName<'_>> +) -> Result<TransactionState, Error>

Determine the transaction state of a database

+

pub fn execute_batch(&self, sql: &str) -> Result<(), Error>

Convenience method to run multiple SQL statements (that cannot take any +parameters).

+
Example
+
fn create_tables(conn: &Connection) -> Result<()> {
+    conn.execute_batch(
+        "BEGIN;
+         CREATE TABLE foo(x INTEGER);
+         CREATE TABLE bar(y TEXT);
+         COMMIT;",
+    )
+}
+
Failure
+

Will return Err if sql cannot be converted to a C-compatible string +or if the underlying SQLite call fails.

+

pub fn execute<P>(&self, sql: &str, params: P) -> Result<usize, Error>where + P: Params,

Convenience method to prepare and execute a single SQL statement.

+

On success, returns the number of rows that were changed or inserted or +deleted (via sqlite3_changes).

+
Example
With positional params
+
fn update_rows(conn: &Connection) {
+    match conn.execute("UPDATE foo SET bar = 'baz' WHERE qux = ?1", [1i32]) {
+        Ok(updated) => println!("{} rows were updated", updated),
+        Err(err) => println!("update failed: {}", err),
+    }
+}
+
With positional params of varying types
+
fn update_rows(conn: &Connection) {
+    match conn.execute(
+        "UPDATE foo SET bar = 'baz' WHERE qux = ?1 AND quux = ?2",
+        params![1i32, 1.5f64],
+    ) {
+        Ok(updated) => println!("{} rows were updated", updated),
+        Err(err) => println!("update failed: {}", err),
+    }
+}
+
With named params
+
fn insert(conn: &Connection) -> Result<usize> {
+    conn.execute(
+        "INSERT INTO test (name) VALUES (:name)",
+        &[(":name", "one")],
+    )
+}
+
Failure
+

Will return Err if sql cannot be converted to a C-compatible string +or if the underlying SQLite call fails.

+

pub fn path(&self) -> Option<&str>

Returns the path to the database file, if one exists and is known.

+

Returns Some("") for a temporary or in-memory database.

+

Note that in some cases PRAGMA +database_list is +likely to be more robust.

+

pub fn last_insert_rowid(&self) -> i64

Get the SQLite rowid of the most recent successful INSERT.

+

Uses sqlite3_last_insert_rowid under +the hood.

+

pub fn query_row<T, P, F>(&self, sql: &str, params: P, f: F) -> Result<T, Error>where + P: Params, + F: FnOnce(&Row<'_>) -> Result<T, Error>,

Convenience method to execute a query that is expected to return a +single row.

+
Example
+
fn preferred_locale(conn: &Connection) -> Result<String> {
+    conn.query_row(
+        "SELECT value FROM preferences WHERE name='locale'",
+        [],
+        |row| row.get(0),
+    )
+}
+

If the query returns more than one row, all rows except the first are +ignored.

+

Returns Err(QueryReturnedNoRows) if no results are returned. If the +query truly is optional, you can call .optional() on the result of +this to get a Result<Option<T>>.

+
Failure
+

Will return Err if sql cannot be converted to a C-compatible string +or if the underlying SQLite call fails.

+

pub fn query_row_and_then<T, E, P, F>( + &self, + sql: &str, + params: P, + f: F +) -> Result<T, E>where + P: Params, + F: FnOnce(&Row<'_>) -> Result<T, E>, + E: From<Error>,

Convenience method to execute a query that is expected to return a +single row, and execute a mapping via f on that returned row with +the possibility of failure. The Result type of f must implement +std::convert::From<Error>.

+
Example
+
fn preferred_locale(conn: &Connection) -> Result<String> {
+    conn.query_row_and_then(
+        "SELECT value FROM preferences WHERE name='locale'",
+        [],
+        |row| row.get(0),
+    )
+}
+

If the query returns more than one row, all rows except the first are +ignored.

+
Failure
+

Will return Err if sql cannot be converted to a C-compatible string +or if the underlying SQLite call fails.

+

pub fn prepare(&self, sql: &str) -> Result<Statement<'_>, Error>

Prepare a SQL statement for execution.

+
Example
+
fn insert_new_people(conn: &Connection) -> Result<()> {
+    let mut stmt = conn.prepare("INSERT INTO People (name) VALUES (?1)")?;
+    stmt.execute(["Joe Smith"])?;
+    stmt.execute(["Bob Jones"])?;
+    Ok(())
+}
+
Failure
+

Will return Err if sql cannot be converted to a C-compatible string +or if the underlying SQLite call fails.

+

pub unsafe fn handle(&self) -> *mut sqlite3

Get access to the underlying SQLite database connection handle.

+
Warning
+

You should not need to use this function. If you do need to, please +open an issue on the rusqlite repository and describe +your use case.

+
Safety
+

This function is unsafe because it gives you raw access +to the SQLite connection, and what you do with it could impact the +safety of this Connection.

+

pub fn get_interrupt_handle(&self) -> InterruptHandle

Get access to a handle that can be used to interrupt long running +queries from another thread.

+

pub fn changes(&self) -> u64

Return the number of rows modified, inserted or deleted by the most +recently completed INSERT, UPDATE or DELETE statement on the database +connection.

+

See https://www.sqlite.org/c3ref/changes.html

+

pub fn is_autocommit(&self) -> bool

Test for auto-commit mode. +Autocommit mode is on by default.

+

pub fn is_busy(&self) -> bool

Determine if all associated prepared statements have been reset.

+

pub fn cache_flush(&self) -> Result<(), Error>

Flush caches to disk mid-transaction

+

pub fn is_readonly(&self, db_name: DatabaseName<'_>) -> Result<bool, Error>

Determine if a database is read-only

+

Trait Implementations§

source§

impl Deref for AutofillDb

§

type Target = Connection

The resulting type after dereferencing.
source§

fn deref(&self) -> &Self::Target

Dereferences the value.
source§

impl DerefMut for AutofillDb

source§

fn deref_mut(&mut self) -> &mut Self::Target

Mutably dereferences the value.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/autofill/encryption/fn.create_autofill_key.html b/book/rust-docs/autofill/encryption/fn.create_autofill_key.html new file mode 100644 index 0000000000..25d330eb29 --- /dev/null +++ b/book/rust-docs/autofill/encryption/fn.create_autofill_key.html @@ -0,0 +1 @@ +create_autofill_key in autofill::encryption - Rust
pub fn create_autofill_key() -> ApiResult<String>
\ No newline at end of file diff --git a/book/rust-docs/autofill/encryption/fn.decrypt_string.html b/book/rust-docs/autofill/encryption/fn.decrypt_string.html new file mode 100644 index 0000000000..a3bf17625c --- /dev/null +++ b/book/rust-docs/autofill/encryption/fn.decrypt_string.html @@ -0,0 +1 @@ +decrypt_string in autofill::encryption - Rust
pub fn decrypt_string(key: String, ciphertext: String) -> ApiResult<String>
\ No newline at end of file diff --git a/book/rust-docs/autofill/encryption/fn.encrypt_string.html b/book/rust-docs/autofill/encryption/fn.encrypt_string.html new file mode 100644 index 0000000000..fe93b87847 --- /dev/null +++ b/book/rust-docs/autofill/encryption/fn.encrypt_string.html @@ -0,0 +1 @@ +encrypt_string in autofill::encryption - Rust
pub fn encrypt_string(key: String, cleartext: String) -> ApiResult<String>
\ No newline at end of file diff --git a/book/rust-docs/autofill/encryption/index.html b/book/rust-docs/autofill/encryption/index.html new file mode 100644 index 0000000000..191b730d27 --- /dev/null +++ b/book/rust-docs/autofill/encryption/index.html @@ -0,0 +1 @@ +autofill::encryption - Rust
\ No newline at end of file diff --git a/book/rust-docs/autofill/encryption/sidebar-items.js b/book/rust-docs/autofill/encryption/sidebar-items.js new file mode 100644 index 0000000000..5dffab57da --- /dev/null +++ b/book/rust-docs/autofill/encryption/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["create_autofill_key","decrypt_string","encrypt_string"],"type":["EncryptorDecryptor"]}; \ No newline at end of file diff --git a/book/rust-docs/autofill/encryption/type.EncryptorDecryptor.html b/book/rust-docs/autofill/encryption/type.EncryptorDecryptor.html new file mode 100644 index 0000000000..d5c0565d22 --- /dev/null +++ b/book/rust-docs/autofill/encryption/type.EncryptorDecryptor.html @@ -0,0 +1 @@ +EncryptorDecryptor in autofill::encryption - Rust

Type Definition autofill::encryption::EncryptorDecryptor

source ·
pub type EncryptorDecryptor = EncryptorDecryptor<Error>;
\ No newline at end of file diff --git a/book/rust-docs/autofill/error/enum.AutofillApiError.html b/book/rust-docs/autofill/error/enum.AutofillApiError.html new file mode 100644 index 0000000000..fe101cb895 --- /dev/null +++ b/book/rust-docs/autofill/error/enum.AutofillApiError.html @@ -0,0 +1,29 @@ +AutofillApiError in autofill::error - Rust
pub enum AutofillApiError {
+    SqlError {
+        reason: String,
+    },
+    InterruptedError,
+    CryptoError {
+        reason: String,
+    },
+    NoSuchRecord {
+        guid: String,
+    },
+    UnexpectedAutofillApiError {
+        reason: String,
+    },
+}

Variants§

§

SqlError

Fields

§reason: String
§

InterruptedError

§

CryptoError

Fields

§reason: String
§

NoSuchRecord

Fields

§guid: String
§

UnexpectedAutofillApiError

Fields

§reason: String

Trait Implementations§

source§

impl Debug for AutofillApiError

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Display for AutofillApiError

source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Error for AutofillApiError

1.30.0 · source§

fn source(&self) -> Option<&(dyn Error + 'static)>

The lower-level source of this error, if any. Read more
1.0.0 · source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type based access to context intended for error reports. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<E> Provider for Ewhere + E: Error + ?Sized,

source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (provide_any)
Data providers should implement this method to provide all values they are able to +provide by using demand. Read more
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/autofill/error/enum.Error.html b/book/rust-docs/autofill/error/enum.Error.html new file mode 100644 index 0000000000..cb33991c6c --- /dev/null +++ b/book/rust-docs/autofill/error/enum.Error.html @@ -0,0 +1,26 @@ +Error in autofill::error - Rust

Enum autofill::error::Error

source ·
pub enum Error {
+    OpenDatabaseError(Error),
+    SqlError(Error),
+    IoError(Error),
+    InterruptedError(Interrupted),
+    IllegalDatabasePath(PathBuf),
+    JsonError(Error),
+    InvalidSyncPayload(String),
+    CryptoError(EncryptorDecryptorError),
+    MissingEncryptionKey,
+    NoSuchRecord(String),
+}

Variants§

§

OpenDatabaseError(Error)

§

SqlError(Error)

§

IoError(Error)

§

InterruptedError(Interrupted)

§

IllegalDatabasePath(PathBuf)

§

JsonError(Error)

§

InvalidSyncPayload(String)

§

CryptoError(EncryptorDecryptorError)

§

MissingEncryptionKey

§

NoSuchRecord(String)

Trait Implementations§

source§

impl Debug for Error

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Display for Error

source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Error for Error

source§

fn source(&self) -> Option<&(dyn Error + 'static)>

The lower-level source of this error, if any. Read more
1.0.0 · source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type based access to context intended for error reports. Read more
source§

impl From<EncryptorDecryptorError> for Error

source§

fn from(source: EncryptorDecryptorError) -> Self

Converts to this type from the input type.
source§

impl From<Error> for Error

source§

fn from(source: Error) -> Self

Converts to this type from the input type.
source§

impl From<Error> for Error

source§

fn from(source: Error) -> Self

Converts to this type from the input type.
source§

impl From<Error> for Error

source§

fn from(source: Error) -> Self

Converts to this type from the input type.
source§

impl From<Error> for Error

source§

fn from(source: Error) -> Self

Converts to this type from the input type.
source§

impl From<Interrupted> for Error

source§

fn from(source: Interrupted) -> Self

Converts to this type from the input type.
source§

impl GetErrorHandling for Error

§

type ExternalError = AutofillApiError

source§

fn get_error_handling(&self) -> ErrorHandling<Self::ExternalError>

Return how to handle our internal errors

Auto Trait Implementations§

§

impl !RefUnwindSafe for Error

§

impl Send for Error

§

impl Sync for Error

§

impl Unpin for Error

§

impl !UnwindSafe for Error

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<E> Provider for Ewhere + E: Error + ?Sized,

source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (provide_any)
Data providers should implement this method to provide all values they are able to +provide by using demand. Read more
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/autofill/error/index.html b/book/rust-docs/autofill/error/index.html new file mode 100644 index 0000000000..c2a4f8f2f7 --- /dev/null +++ b/book/rust-docs/autofill/error/index.html @@ -0,0 +1 @@ +autofill::error - Rust

Module autofill::error

source ·

Enums

Type Definitions

  • Result enum for the public API
  • Result enum for internal functions
\ No newline at end of file diff --git a/book/rust-docs/autofill/error/sidebar-items.js b/book/rust-docs/autofill/error/sidebar-items.js new file mode 100644 index 0000000000..a988c4683b --- /dev/null +++ b/book/rust-docs/autofill/error/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["AutofillApiError","Error"],"type":["ApiResult","Result"]}; \ No newline at end of file diff --git a/book/rust-docs/autofill/error/type.ApiResult.html b/book/rust-docs/autofill/error/type.ApiResult.html new file mode 100644 index 0000000000..c46c47615c --- /dev/null +++ b/book/rust-docs/autofill/error/type.ApiResult.html @@ -0,0 +1,2 @@ +ApiResult in autofill::error - Rust

Type Definition autofill::error::ApiResult

source ·
pub type ApiResult<T> = Result<T, AutofillApiError>;
Expand description

Result enum for the public API

+
\ No newline at end of file diff --git a/book/rust-docs/autofill/error/type.Result.html b/book/rust-docs/autofill/error/type.Result.html new file mode 100644 index 0000000000..b3458127be --- /dev/null +++ b/book/rust-docs/autofill/error/type.Result.html @@ -0,0 +1,2 @@ +Result in autofill::error - Rust

Type Definition autofill::error::Result

source ·
pub type Result<T> = Result<T, Error>;
Expand description

Result enum for internal functions

+
\ No newline at end of file diff --git a/book/rust-docs/autofill/index.html b/book/rust-docs/autofill/index.html new file mode 100644 index 0000000000..42aac2c77c --- /dev/null +++ b/book/rust-docs/autofill/index.html @@ -0,0 +1 @@ +autofill - Rust

Crate autofill

source ·

Re-exports

Modules

Macros

\ No newline at end of file diff --git a/book/rust-docs/autofill/macro.sync_merge_field_check!.html b/book/rust-docs/autofill/macro.sync_merge_field_check!.html new file mode 100644 index 0000000000..dfed51354e --- /dev/null +++ b/book/rust-docs/autofill/macro.sync_merge_field_check!.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to macro.sync_merge_field_check.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/autofill/macro.sync_merge_field_check.html b/book/rust-docs/autofill/macro.sync_merge_field_check.html new file mode 100644 index 0000000000..d190286719 --- /dev/null +++ b/book/rust-docs/autofill/macro.sync_merge_field_check.html @@ -0,0 +1,8 @@ +sync_merge_field_check in autofill - Rust
macro_rules! sync_merge_field_check {
+    ($field_name:ident,
+    $incoming:ident,
+    $local:ident,
+    $mirror:ident,
+    $merged_record:ident
+    ) => { ... };
+}
\ No newline at end of file diff --git a/book/rust-docs/autofill/sidebar-items.js b/book/rust-docs/autofill/sidebar-items.js new file mode 100644 index 0000000000..685a814c40 --- /dev/null +++ b/book/rust-docs/autofill/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"macro":["sync_merge_field_check"],"mod":["db","encryption","error","sync"]}; \ No newline at end of file diff --git a/book/rust-docs/autofill/sync/address/incoming/index.html b/book/rust-docs/autofill/sync/address/incoming/index.html new file mode 100644 index 0000000000..f9eb767b05 --- /dev/null +++ b/book/rust-docs/autofill/sync/address/incoming/index.html @@ -0,0 +1 @@ +autofill::sync::address::incoming - Rust
\ No newline at end of file diff --git a/book/rust-docs/autofill/sync/address/incoming/sidebar-items.js b/book/rust-docs/autofill/sync/address/incoming/sidebar-items.js new file mode 100644 index 0000000000..5244ce01cc --- /dev/null +++ b/book/rust-docs/autofill/sync/address/incoming/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {}; \ No newline at end of file diff --git a/book/rust-docs/autofill/sync/address/index.html b/book/rust-docs/autofill/sync/address/index.html new file mode 100644 index 0000000000..5482819ff0 --- /dev/null +++ b/book/rust-docs/autofill/sync/address/index.html @@ -0,0 +1 @@ +autofill::sync::address - Rust
\ No newline at end of file diff --git a/book/rust-docs/autofill/sync/address/outgoing/index.html b/book/rust-docs/autofill/sync/address/outgoing/index.html new file mode 100644 index 0000000000..55cadd0aef --- /dev/null +++ b/book/rust-docs/autofill/sync/address/outgoing/index.html @@ -0,0 +1 @@ +autofill::sync::address::outgoing - Rust
\ No newline at end of file diff --git a/book/rust-docs/autofill/sync/address/outgoing/sidebar-items.js b/book/rust-docs/autofill/sync/address/outgoing/sidebar-items.js new file mode 100644 index 0000000000..5244ce01cc --- /dev/null +++ b/book/rust-docs/autofill/sync/address/outgoing/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {}; \ No newline at end of file diff --git a/book/rust-docs/autofill/sync/address/sidebar-items.js b/book/rust-docs/autofill/sync/address/sidebar-items.js new file mode 100644 index 0000000000..fd49b68f39 --- /dev/null +++ b/book/rust-docs/autofill/sync/address/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"mod":["incoming","outgoing"],"struct":["AddressPayload"]}; \ No newline at end of file diff --git a/book/rust-docs/autofill/sync/address/struct.AddressPayload.html b/book/rust-docs/autofill/sync/address/struct.AddressPayload.html new file mode 100644 index 0000000000..ef059f473e --- /dev/null +++ b/book/rust-docs/autofill/sync/address/struct.AddressPayload.html @@ -0,0 +1,15 @@ +AddressPayload in autofill::sync::address - Rust
pub struct AddressPayload { /* private fields */ }

Trait Implementations§

source§

impl Default for AddressPayload

source§

fn default() -> AddressPayload

Returns the “default value” for a type. Read more
source§

impl<'de> Deserialize<'de> for AddressPayload

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl Serialize for AddressPayload

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/autofill/sync/credit_card/incoming/index.html b/book/rust-docs/autofill/sync/credit_card/incoming/index.html new file mode 100644 index 0000000000..2ba6b81c56 --- /dev/null +++ b/book/rust-docs/autofill/sync/credit_card/incoming/index.html @@ -0,0 +1 @@ +autofill::sync::credit_card::incoming - Rust
\ No newline at end of file diff --git a/book/rust-docs/autofill/sync/credit_card/incoming/sidebar-items.js b/book/rust-docs/autofill/sync/credit_card/incoming/sidebar-items.js new file mode 100644 index 0000000000..5244ce01cc --- /dev/null +++ b/book/rust-docs/autofill/sync/credit_card/incoming/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {}; \ No newline at end of file diff --git a/book/rust-docs/autofill/sync/credit_card/index.html b/book/rust-docs/autofill/sync/credit_card/index.html new file mode 100644 index 0000000000..ecebdc1dcb --- /dev/null +++ b/book/rust-docs/autofill/sync/credit_card/index.html @@ -0,0 +1 @@ +autofill::sync::credit_card - Rust
\ No newline at end of file diff --git a/book/rust-docs/autofill/sync/credit_card/outgoing/index.html b/book/rust-docs/autofill/sync/credit_card/outgoing/index.html new file mode 100644 index 0000000000..21adaabf76 --- /dev/null +++ b/book/rust-docs/autofill/sync/credit_card/outgoing/index.html @@ -0,0 +1 @@ +autofill::sync::credit_card::outgoing - Rust
\ No newline at end of file diff --git a/book/rust-docs/autofill/sync/credit_card/outgoing/sidebar-items.js b/book/rust-docs/autofill/sync/credit_card/outgoing/sidebar-items.js new file mode 100644 index 0000000000..5244ce01cc --- /dev/null +++ b/book/rust-docs/autofill/sync/credit_card/outgoing/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {}; \ No newline at end of file diff --git a/book/rust-docs/autofill/sync/credit_card/sidebar-items.js b/book/rust-docs/autofill/sync/credit_card/sidebar-items.js new file mode 100644 index 0000000000..d647412540 --- /dev/null +++ b/book/rust-docs/autofill/sync/credit_card/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"mod":["incoming","outgoing"]}; \ No newline at end of file diff --git a/book/rust-docs/autofill/sync/engine/constant.COLLECTION_SYNCID_META_KEY.html b/book/rust-docs/autofill/sync/engine/constant.COLLECTION_SYNCID_META_KEY.html new file mode 100644 index 0000000000..052edeea68 --- /dev/null +++ b/book/rust-docs/autofill/sync/engine/constant.COLLECTION_SYNCID_META_KEY.html @@ -0,0 +1 @@ +COLLECTION_SYNCID_META_KEY in autofill::sync::engine - Rust
pub const COLLECTION_SYNCID_META_KEY: &str = "sync_id";
\ No newline at end of file diff --git a/book/rust-docs/autofill/sync/engine/constant.GLOBAL_SYNCID_META_KEY.html b/book/rust-docs/autofill/sync/engine/constant.GLOBAL_SYNCID_META_KEY.html new file mode 100644 index 0000000000..9cbe21389c --- /dev/null +++ b/book/rust-docs/autofill/sync/engine/constant.GLOBAL_SYNCID_META_KEY.html @@ -0,0 +1 @@ +GLOBAL_SYNCID_META_KEY in autofill::sync::engine - Rust
pub const GLOBAL_SYNCID_META_KEY: &str = "global_sync_id";
\ No newline at end of file diff --git a/book/rust-docs/autofill/sync/engine/constant.LAST_SYNC_META_KEY.html b/book/rust-docs/autofill/sync/engine/constant.LAST_SYNC_META_KEY.html new file mode 100644 index 0000000000..2d8e5ef041 --- /dev/null +++ b/book/rust-docs/autofill/sync/engine/constant.LAST_SYNC_META_KEY.html @@ -0,0 +1 @@ +LAST_SYNC_META_KEY in autofill::sync::engine - Rust
pub const LAST_SYNC_META_KEY: &str = "last_sync_time";
\ No newline at end of file diff --git a/book/rust-docs/autofill/sync/engine/index.html b/book/rust-docs/autofill/sync/engine/index.html new file mode 100644 index 0000000000..4d9fed6c9e --- /dev/null +++ b/book/rust-docs/autofill/sync/engine/index.html @@ -0,0 +1 @@ +autofill::sync::engine - Rust
\ No newline at end of file diff --git a/book/rust-docs/autofill/sync/engine/sidebar-items.js b/book/rust-docs/autofill/sync/engine/sidebar-items.js new file mode 100644 index 0000000000..ae07dfa4d9 --- /dev/null +++ b/book/rust-docs/autofill/sync/engine/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"constant":["COLLECTION_SYNCID_META_KEY","GLOBAL_SYNCID_META_KEY","LAST_SYNC_META_KEY"],"struct":["ConfigSyncEngine","EngineConfig"],"trait":["SyncEngineStorageImpl"]}; \ No newline at end of file diff --git a/book/rust-docs/autofill/sync/engine/struct.ConfigSyncEngine.html b/book/rust-docs/autofill/sync/engine/struct.ConfigSyncEngine.html new file mode 100644 index 0000000000..b4fb97b397 --- /dev/null +++ b/book/rust-docs/autofill/sync/engine/struct.ConfigSyncEngine.html @@ -0,0 +1,60 @@ +ConfigSyncEngine in autofill::sync::engine - Rust
pub struct ConfigSyncEngine<T> { /* private fields */ }

Implementations§

source§

impl<T> ConfigSyncEngine<T>

source

pub fn new( + config: EngineConfig, + store: Arc<Store>, + storage_impl: Box<dyn SyncEngineStorageImpl<T>> +) -> Self

source

pub fn reset_local_sync_data(&self) -> Result<()>

Trait Implementations§

source§

impl<T: SyncRecord + Debug> SyncEngine for ConfigSyncEngine<T>

source§

fn collection_name(&self) -> CollectionName

source§

fn set_local_encryption_key(&mut self, key: &str) -> Result<()>

Tells the engine what the local encryption key is for the data managed +by the engine. This is only used by collections that store data +encrypted locally and is unrelated to the encryption used by Sync. +The intent is that for such collections, this key can be used to +decrypt local data before it is re-encrypted by Sync and sent to the +storage servers, and similarly, data from the storage servers will be +decrypted by Sync, then encrypted by the local encryption key before +being added to the local database. Read more
source§

fn prepare_for_sync( + &self, + _get_client_data: &dyn Fn() -> ClientData +) -> Result<()>

Prepares the engine for syncing. The tabs engine currently uses this to +store the current list of clients, which it uses to look up device names +and types. Read more
source§

fn stage_incoming( + &self, + inbound: Vec<IncomingBso>, + telem: &mut Engine +) -> Result<()>

Stage some incoming records. This might be called multiple times in the same sync +if we fetch the incoming records in batches. Read more
source§

fn apply( + &self, + timestamp: ServerTimestamp, + _telem: &mut Engine +) -> Result<Vec<OutgoingBso>>

Apply the staged records, returning outgoing records. +Ideally we would adjust this model to better support batching of outgoing records +without needing to keep them all in memory (ie, an iterator or similar?)
source§

fn set_uploaded( + &self, + new_timestamp: ServerTimestamp, + ids: Vec<Guid> +) -> Result<()>

Indicates that the given record IDs were uploaded successfully to the server. +This may be called multiple times per sync, once for each batch. Batching is determined +dynamically based on payload sizes and counts via the server’s advertised limits.
source§

fn get_collection_request( + &self, + server_timestamp: ServerTimestamp +) -> Result<Option<CollectionRequest>>

The engine is responsible for building a single collection request. Engines +typically will store a lastModified timestamp and use that to build a +request saying “give me full records since that date” - however, other +engines might do something fancier. It can return None if the server timestamp +has not advanced since the last sync. +This could even later be extended to handle “backfills”, and we might end up +wanting one engine to use multiple collections (eg, as a “foreign key” via guid), etc.
source§

fn get_sync_assoc(&self) -> Result<EngineSyncAssociation>

Get persisted sync IDs. If they don’t match the global state we’ll be +reset() with the new IDs.
source§

fn reset(&self, assoc: &EngineSyncAssociation) -> Result<()>

Reset the engine (and associated store) without wiping local data, +ready for a “first sync”. +assoc defines how this store is to be associated with sync.
source§

fn wipe(&self) -> Result<()>

§

fn sync_finished(&self) -> Result<(), Error>

Called once the sync is finished. Not currently called if uploads fail (which +seems sad, but the other batching confusion there needs sorting out first). +Many engines will have nothing to do here, as most “post upload” work should be +done in set_uploaded()

Auto Trait Implementations§

§

impl<T> !RefUnwindSafe for ConfigSyncEngine<T>

§

impl<T> !Send for ConfigSyncEngine<T>

§

impl<T> !Sync for ConfigSyncEngine<T>

§

impl<T> Unpin for ConfigSyncEngine<T>

§

impl<T> !UnwindSafe for ConfigSyncEngine<T>

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/autofill/sync/engine/struct.EngineConfig.html b/book/rust-docs/autofill/sync/engine/struct.EngineConfig.html new file mode 100644 index 0000000000..9f244368f7 --- /dev/null +++ b/book/rust-docs/autofill/sync/engine/struct.EngineConfig.html @@ -0,0 +1,12 @@ +EngineConfig in autofill::sync::engine - Rust
pub struct EngineConfig { /* private fields */ }

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/autofill/sync/engine/trait.SyncEngineStorageImpl.html b/book/rust-docs/autofill/sync/engine/trait.SyncEngineStorageImpl.html new file mode 100644 index 0000000000..be1abc7e52 --- /dev/null +++ b/book/rust-docs/autofill/sync/engine/trait.SyncEngineStorageImpl.html @@ -0,0 +1,18 @@ +SyncEngineStorageImpl in autofill::sync::engine - Rust
pub trait SyncEngineStorageImpl<T> {
+    // Required methods
+    fn get_incoming_impl(
+        &self,
+        enc_key: &Option<String>
+    ) -> Result<Box<dyn ProcessIncomingRecordImpl<Record = T>>>;
+    fn reset_storage(&self, conn: &Transaction<'_>) -> Result<()>;
+    fn get_outgoing_impl(
+        &self,
+        enc_key: &Option<String>
+    ) -> Result<Box<dyn ProcessOutgoingRecordImpl<Record = T>>>;
+}

Required Methods§

source

fn get_incoming_impl( + &self, + enc_key: &Option<String> +) -> Result<Box<dyn ProcessIncomingRecordImpl<Record = T>>>

source

fn reset_storage(&self, conn: &Transaction<'_>) -> Result<()>

source

fn get_outgoing_impl( + &self, + enc_key: &Option<String> +) -> Result<Box<dyn ProcessOutgoingRecordImpl<Record = T>>>

Implementors§

\ No newline at end of file diff --git a/book/rust-docs/autofill/sync/enum.MergeResult.html b/book/rust-docs/autofill/sync/enum.MergeResult.html new file mode 100644 index 0000000000..3e84c2a9d1 --- /dev/null +++ b/book/rust-docs/autofill/sync/enum.MergeResult.html @@ -0,0 +1,24 @@ +MergeResult in autofill::sync - Rust
pub enum MergeResult<T> {
+    Merged {
+        merged: T,
+    },
+    Forked {
+        forked: T,
+    },
+}

Variants§

§

Merged

Fields

§merged: T
§

Forked

Fields

§forked: T

Trait Implementations§

source§

impl<T: Debug> Debug for MergeResult<T>

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl<T> RefUnwindSafe for MergeResult<T>where + T: RefUnwindSafe,

§

impl<T> Send for MergeResult<T>where + T: Send,

§

impl<T> Sync for MergeResult<T>where + T: Sync,

§

impl<T> Unpin for MergeResult<T>where + T: Unpin,

§

impl<T> UnwindSafe for MergeResult<T>where + T: UnwindSafe,

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/autofill/sync/index.html b/book/rust-docs/autofill/sync/index.html new file mode 100644 index 0000000000..ba086fe1b4 --- /dev/null +++ b/book/rust-docs/autofill/sync/index.html @@ -0,0 +1 @@ +autofill::sync - Rust
\ No newline at end of file diff --git a/book/rust-docs/autofill/sync/sidebar-items.js b/book/rust-docs/autofill/sync/sidebar-items.js new file mode 100644 index 0000000000..788bfc4998 --- /dev/null +++ b/book/rust-docs/autofill/sync/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["MergeResult"],"mod":["address","credit_card","engine"],"struct":["IncomingState"],"trait":["ProcessIncomingRecordImpl","ProcessOutgoingRecordImpl","SyncRecord"]}; \ No newline at end of file diff --git a/book/rust-docs/autofill/sync/struct.IncomingState.html b/book/rust-docs/autofill/sync/struct.IncomingState.html new file mode 100644 index 0000000000..6fe253adb1 --- /dev/null +++ b/book/rust-docs/autofill/sync/struct.IncomingState.html @@ -0,0 +1,17 @@ +IncomingState in autofill::sync - Rust
pub struct IncomingState<T> { /* private fields */ }

Trait Implementations§

source§

impl<T: Debug> Debug for IncomingState<T>

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl<T> RefUnwindSafe for IncomingState<T>where + T: RefUnwindSafe,

§

impl<T> Send for IncomingState<T>where + T: Send,

§

impl<T> Sync for IncomingState<T>where + T: Sync,

§

impl<T> Unpin for IncomingState<T>where + T: Unpin,

§

impl<T> UnwindSafe for IncomingState<T>where + T: UnwindSafe,

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/autofill/sync/trait.ProcessIncomingRecordImpl.html b/book/rust-docs/autofill/sync/trait.ProcessIncomingRecordImpl.html new file mode 100644 index 0000000000..ddbb9d8988 --- /dev/null +++ b/book/rust-docs/autofill/sync/trait.ProcessIncomingRecordImpl.html @@ -0,0 +1,70 @@ +ProcessIncomingRecordImpl in autofill::sync - Rust
pub trait ProcessIncomingRecordImpl {
+    type Record;
+
+    // Required methods
+    fn stage_incoming(
+        &self,
+        tx: &Transaction<'_>,
+        incoming: Vec<IncomingBso>,
+        signal: &dyn Interruptee
+    ) -> Result<()>;
+    fn finish_incoming(&self, tx: &Transaction<'_>) -> Result<()>;
+    fn fetch_incoming_states(
+        &self,
+        tx: &Transaction<'_>
+    ) -> Result<Vec<IncomingState<Self::Record>>>;
+    fn get_local_dupe(
+        &self,
+        tx: &Transaction<'_>,
+        incoming: &Self::Record
+    ) -> Result<Option<Self::Record>>;
+    fn update_local_record(
+        &self,
+        tx: &Transaction<'_>,
+        record: Self::Record,
+        was_merged: bool
+    ) -> Result<()>;
+    fn insert_local_record(
+        &self,
+        tx: &Transaction<'_>,
+        record: Self::Record
+    ) -> Result<()>;
+    fn change_record_guid(
+        &self,
+        tx: &Transaction<'_>,
+        old_guid: &Guid,
+        new_guid: &Guid
+    ) -> Result<()>;
+    fn remove_record(&self, tx: &Transaction<'_>, guid: &Guid) -> Result<()>;
+    fn remove_tombstone(&self, tx: &Transaction<'_>, guid: &Guid) -> Result<()>;
+}

Required Associated Types§

Required Methods§

source

fn stage_incoming( + &self, + tx: &Transaction<'_>, + incoming: Vec<IncomingBso>, + signal: &dyn Interruptee +) -> Result<()>

source

fn finish_incoming(&self, tx: &Transaction<'_>) -> Result<()>

Finish the incoming phase. This will typically caused staged records

+
source

fn fetch_incoming_states( + &self, + tx: &Transaction<'_> +) -> Result<Vec<IncomingState<Self::Record>>>

source

fn get_local_dupe( + &self, + tx: &Transaction<'_>, + incoming: &Self::Record +) -> Result<Option<Self::Record>>

Returns a local record that has the same values as the given incoming record (with the exception +of the guid values which should differ) that will be used as a local duplicate record for +syncing.

+
source

fn update_local_record( + &self, + tx: &Transaction<'_>, + record: Self::Record, + was_merged: bool +) -> Result<()>

source

fn insert_local_record( + &self, + tx: &Transaction<'_>, + record: Self::Record +) -> Result<()>

source

fn change_record_guid( + &self, + tx: &Transaction<'_>, + old_guid: &Guid, + new_guid: &Guid +) -> Result<()>

source

fn remove_record(&self, tx: &Transaction<'_>, guid: &Guid) -> Result<()>

source

fn remove_tombstone(&self, tx: &Transaction<'_>, guid: &Guid) -> Result<()>

Implementors§

\ No newline at end of file diff --git a/book/rust-docs/autofill/sync/trait.ProcessOutgoingRecordImpl.html b/book/rust-docs/autofill/sync/trait.ProcessOutgoingRecordImpl.html new file mode 100644 index 0000000000..da0b37a95d --- /dev/null +++ b/book/rust-docs/autofill/sync/trait.ProcessOutgoingRecordImpl.html @@ -0,0 +1,21 @@ +ProcessOutgoingRecordImpl in autofill::sync - Rust
pub trait ProcessOutgoingRecordImpl {
+    type Record;
+
+    // Required methods
+    fn fetch_outgoing_records(
+        &self,
+        tx: &Transaction<'_>
+    ) -> Result<Vec<OutgoingBso>>;
+    fn finish_synced_items(
+        &self,
+        tx: &Transaction<'_>,
+        records_synced: Vec<Guid>
+    ) -> Result<()>;
+}

Required Associated Types§

Required Methods§

source

fn fetch_outgoing_records( + &self, + tx: &Transaction<'_> +) -> Result<Vec<OutgoingBso>>

source

fn finish_synced_items( + &self, + tx: &Transaction<'_>, + records_synced: Vec<Guid> +) -> Result<()>

Implementors§

\ No newline at end of file diff --git a/book/rust-docs/autofill/sync/trait.SyncRecord.html b/book/rust-docs/autofill/sync/trait.SyncRecord.html new file mode 100644 index 0000000000..8ccceb7bcf --- /dev/null +++ b/book/rust-docs/autofill/sync/trait.SyncRecord.html @@ -0,0 +1,18 @@ +SyncRecord in autofill::sync - Rust

Trait autofill::sync::SyncRecord

source ·
pub trait SyncRecord {
+    // Required methods
+    fn record_name() -> &'static str;
+    fn id(&self) -> &Guid;
+    fn metadata(&self) -> &Metadata;
+    fn metadata_mut(&mut self) -> &mut Metadata;
+    fn merge(
+        incoming: &Self,
+        local: &Self,
+        mirror: &Option<Self>
+    ) -> MergeResult<Self>
+       where Self: Sized;
+}

Required Methods§

source

fn record_name() -> &'static str

source

fn id(&self) -> &Guid

source

fn metadata(&self) -> &Metadata

source

fn metadata_mut(&mut self) -> &mut Metadata

source

fn merge( + incoming: &Self, + local: &Self, + mirror: &Option<Self> +) -> MergeResult<Self>where + Self: Sized,

Implementors§

\ No newline at end of file diff --git a/book/rust-docs/cli_support/all.html b/book/rust-docs/cli_support/all.html new file mode 100644 index 0000000000..329560629d --- /dev/null +++ b/book/rust-docs/cli_support/all.html @@ -0,0 +1 @@ +List of all items in this crate
\ No newline at end of file diff --git a/book/rust-docs/cli_support/fn.init_logging.html b/book/rust-docs/cli_support/fn.init_logging.html new file mode 100644 index 0000000000..9bc7f25f4f --- /dev/null +++ b/book/rust-docs/cli_support/fn.init_logging.html @@ -0,0 +1 @@ +init_logging in cli_support - Rust

Function cli_support::init_logging

source ·
pub fn init_logging()
\ No newline at end of file diff --git a/book/rust-docs/cli_support/fn.init_logging_with.html b/book/rust-docs/cli_support/fn.init_logging_with.html new file mode 100644 index 0000000000..53bab536f8 --- /dev/null +++ b/book/rust-docs/cli_support/fn.init_logging_with.html @@ -0,0 +1 @@ +init_logging_with in cli_support - Rust
pub fn init_logging_with(s: &str)
\ No newline at end of file diff --git a/book/rust-docs/cli_support/fn.init_trace_logging.html b/book/rust-docs/cli_support/fn.init_trace_logging.html new file mode 100644 index 0000000000..3c32ce7554 --- /dev/null +++ b/book/rust-docs/cli_support/fn.init_trace_logging.html @@ -0,0 +1 @@ +init_trace_logging in cli_support - Rust
pub fn init_trace_logging()
\ No newline at end of file diff --git a/book/rust-docs/cli_support/fxa_creds/fn.get_account_and_token.html b/book/rust-docs/cli_support/fxa_creds/fn.get_account_and_token.html new file mode 100644 index 0000000000..5c1805d1d9 --- /dev/null +++ b/book/rust-docs/cli_support/fxa_creds/fn.get_account_and_token.html @@ -0,0 +1,4 @@ +get_account_and_token in cli_support::fxa_creds - Rust
pub fn get_account_and_token(
+    config: FxaConfig,
+    cred_file: &str
+) -> Result<(FirefoxAccount, AccessTokenInfo)>
\ No newline at end of file diff --git a/book/rust-docs/cli_support/fxa_creds/fn.get_cli_fxa.html b/book/rust-docs/cli_support/fxa_creds/fn.get_cli_fxa.html new file mode 100644 index 0000000000..25bf17c80b --- /dev/null +++ b/book/rust-docs/cli_support/fxa_creds/fn.get_cli_fxa.html @@ -0,0 +1 @@ +get_cli_fxa in cli_support::fxa_creds - Rust
pub fn get_cli_fxa(config: FxaConfig, cred_file: &str) -> Result<CliFxa>
\ No newline at end of file diff --git a/book/rust-docs/cli_support/fxa_creds/fn.get_default_fxa_config.html b/book/rust-docs/cli_support/fxa_creds/fn.get_default_fxa_config.html new file mode 100644 index 0000000000..68f5b7e4f3 --- /dev/null +++ b/book/rust-docs/cli_support/fxa_creds/fn.get_default_fxa_config.html @@ -0,0 +1 @@ +get_default_fxa_config in cli_support::fxa_creds - Rust
pub fn get_default_fxa_config() -> FxaConfig
\ No newline at end of file diff --git a/book/rust-docs/cli_support/fxa_creds/index.html b/book/rust-docs/cli_support/fxa_creds/index.html new file mode 100644 index 0000000000..21e34d9a18 --- /dev/null +++ b/book/rust-docs/cli_support/fxa_creds/index.html @@ -0,0 +1 @@ +cli_support::fxa_creds - Rust
\ No newline at end of file diff --git a/book/rust-docs/cli_support/fxa_creds/sidebar-items.js b/book/rust-docs/cli_support/fxa_creds/sidebar-items.js new file mode 100644 index 0000000000..6c1ef1eca5 --- /dev/null +++ b/book/rust-docs/cli_support/fxa_creds/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["get_account_and_token","get_cli_fxa","get_default_fxa_config"],"struct":["CliFxa"]}; \ No newline at end of file diff --git a/book/rust-docs/cli_support/fxa_creds/struct.CliFxa.html b/book/rust-docs/cli_support/fxa_creds/struct.CliFxa.html new file mode 100644 index 0000000000..76380a12af --- /dev/null +++ b/book/rust-docs/cli_support/fxa_creds/struct.CliFxa.html @@ -0,0 +1,17 @@ +CliFxa in cli_support::fxa_creds - Rust
pub struct CliFxa {
+    pub account: FirefoxAccount,
+    pub client_init: Sync15StorageClientInit,
+    pub tokenserver_url: Url,
+    pub token_info: AccessTokenInfo,
+}

Fields§

§account: FirefoxAccount§client_init: Sync15StorageClientInit§tokenserver_url: Url§token_info: AccessTokenInfo

Implementations§

source§

impl CliFxa

source

pub fn as_auth_info(&self) -> SyncAuthInfo

source

pub fn as_key_bundle(&self) -> Result<KeyBundle>

Auto Trait Implementations§

§

impl !RefUnwindSafe for CliFxa

§

impl Send for CliFxa

§

impl Sync for CliFxa

§

impl Unpin for CliFxa

§

impl !UnwindSafe for CliFxa

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/cli_support/index.html b/book/rust-docs/cli_support/index.html new file mode 100644 index 0000000000..a8d8cce0b6 --- /dev/null +++ b/book/rust-docs/cli_support/index.html @@ -0,0 +1 @@ +cli_support - Rust
\ No newline at end of file diff --git a/book/rust-docs/cli_support/prompt/fn.prompt_char.html b/book/rust-docs/cli_support/prompt/fn.prompt_char.html new file mode 100644 index 0000000000..efd22b2f5c --- /dev/null +++ b/book/rust-docs/cli_support/prompt/fn.prompt_char.html @@ -0,0 +1 @@ +prompt_char in cli_support::prompt - Rust
pub fn prompt_char(msg: &str) -> Option<char>
\ No newline at end of file diff --git a/book/rust-docs/cli_support/prompt/fn.prompt_string.html b/book/rust-docs/cli_support/prompt/fn.prompt_string.html new file mode 100644 index 0000000000..4f8af6f8b1 --- /dev/null +++ b/book/rust-docs/cli_support/prompt/fn.prompt_string.html @@ -0,0 +1 @@ +prompt_string in cli_support::prompt - Rust
pub fn prompt_string<S: AsRef<str>>(prompt: S) -> Option<String>
\ No newline at end of file diff --git a/book/rust-docs/cli_support/prompt/fn.prompt_usize.html b/book/rust-docs/cli_support/prompt/fn.prompt_usize.html new file mode 100644 index 0000000000..fbe3ad4be0 --- /dev/null +++ b/book/rust-docs/cli_support/prompt/fn.prompt_usize.html @@ -0,0 +1 @@ +prompt_usize in cli_support::prompt - Rust
pub fn prompt_usize<S: AsRef<str>>(prompt: S) -> Option<usize>
\ No newline at end of file diff --git a/book/rust-docs/cli_support/prompt/index.html b/book/rust-docs/cli_support/prompt/index.html new file mode 100644 index 0000000000..76738b26d0 --- /dev/null +++ b/book/rust-docs/cli_support/prompt/index.html @@ -0,0 +1 @@ +cli_support::prompt - Rust
\ No newline at end of file diff --git a/book/rust-docs/cli_support/prompt/sidebar-items.js b/book/rust-docs/cli_support/prompt/sidebar-items.js new file mode 100644 index 0000000000..df6248ec39 --- /dev/null +++ b/book/rust-docs/cli_support/prompt/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["prompt_char","prompt_string","prompt_usize"]}; \ No newline at end of file diff --git a/book/rust-docs/cli_support/sidebar-items.js b/book/rust-docs/cli_support/sidebar-items.js new file mode 100644 index 0000000000..2da3a08e27 --- /dev/null +++ b/book/rust-docs/cli_support/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["init_logging","init_logging_with","init_trace_logging"],"mod":["fxa_creds","prompt"]}; \ No newline at end of file diff --git a/book/rust-docs/crashtest/all.html b/book/rust-docs/crashtest/all.html new file mode 100644 index 0000000000..62aac9917f --- /dev/null +++ b/book/rust-docs/crashtest/all.html @@ -0,0 +1 @@ +List of all items in this crate
\ No newline at end of file diff --git a/book/rust-docs/crashtest/enum.CrashTestError.html b/book/rust-docs/crashtest/enum.CrashTestError.html new file mode 100644 index 0000000000..15643c64d2 --- /dev/null +++ b/book/rust-docs/crashtest/enum.CrashTestError.html @@ -0,0 +1,17 @@ +CrashTestError in crashtest - Rust
pub enum CrashTestError {
+    ErrorFromTheRustCode,
+}
Expand description

An error that can be returned from Rust code.

+

Variants§

§

ErrorFromTheRustCode

Trait Implementations§

source§

impl Debug for CrashTestError

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Display for CrashTestError

source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Error for CrashTestError

1.30.0 · source§

fn source(&self) -> Option<&(dyn Error + 'static)>

The lower-level source of this error, if any. Read more
1.0.0 · source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type based access to context intended for error reports. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<E> Provider for Ewhere + E: Error + ?Sized,

source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (provide_any)
Data providers should implement this method to provide all values they are able to +provide by using demand. Read more
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/crashtest/fn.trigger_rust_abort.html b/book/rust-docs/crashtest/fn.trigger_rust_abort.html new file mode 100644 index 0000000000..d25cc6c0cc --- /dev/null +++ b/book/rust-docs/crashtest/fn.trigger_rust_abort.html @@ -0,0 +1,5 @@ +trigger_rust_abort in crashtest - Rust
pub fn trigger_rust_abort()
Expand description

Trigger a hard abort inside the Rust code.

+

This function simulates some kind of uncatchable illegal operation +performed inside the Rust code. After calling this function you should +expect your application to be halted with e.g. a SIGABRT or similar.

+
\ No newline at end of file diff --git a/book/rust-docs/crashtest/fn.trigger_rust_error.html b/book/rust-docs/crashtest/fn.trigger_rust_error.html new file mode 100644 index 0000000000..89be5e2783 --- /dev/null +++ b/book/rust-docs/crashtest/fn.trigger_rust_error.html @@ -0,0 +1,5 @@ +trigger_rust_error in crashtest - Rust
pub fn trigger_rust_error() -> Result<(), CrashTestError>
Expand description

Trigger an error inside the Rust code.

+

This function simulates the occurence of an expected error inside +the Rust code. You should expect calling this function to throw the +foreign-language representation of the CrashTestError class.

+
\ No newline at end of file diff --git a/book/rust-docs/crashtest/fn.trigger_rust_panic.html b/book/rust-docs/crashtest/fn.trigger_rust_panic.html new file mode 100644 index 0000000000..86dc0e2679 --- /dev/null +++ b/book/rust-docs/crashtest/fn.trigger_rust_panic.html @@ -0,0 +1,11 @@ +trigger_rust_panic in crashtest - Rust
pub fn trigger_rust_panic()
Expand description

Trigger a panic inside the Rust code.

+

This function simulates the occurence of an unexpected state inside +the Rust code that causes it to panic. We build our Rust components to +unwind on panic, so after calling this function through the foreign +language bindings, you should expect it to intercept the panic translate +it into some foreign-language-appropriate equivalent:

+
    +
  • In Kotlin, it will throw an exception.
  • +
  • In Swift, it will fail with a try! runtime error.
  • +
+
\ No newline at end of file diff --git a/book/rust-docs/crashtest/index.html b/book/rust-docs/crashtest/index.html new file mode 100644 index 0000000000..6b7c32cd32 --- /dev/null +++ b/book/rust-docs/crashtest/index.html @@ -0,0 +1,5 @@ +crashtest - Rust

Crate crashtest

source ·
Expand description

Crash Test Helper APIs

+

The crashtest component offers a little helper API that lets you deliberately +crash the application. It’s intended to help developers test the crash-handling +and crash-reporting capabilities of their app.

+

Enums

Functions

\ No newline at end of file diff --git a/book/rust-docs/crashtest/sidebar-items.js b/book/rust-docs/crashtest/sidebar-items.js new file mode 100644 index 0000000000..079f18ef73 --- /dev/null +++ b/book/rust-docs/crashtest/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["CrashTestError"],"fn":["trigger_rust_abort","trigger_rust_error","trigger_rust_panic"]}; \ No newline at end of file diff --git a/book/rust-docs/crates.js b/book/rust-docs/crates.js new file mode 100644 index 0000000000..87e5558f29 --- /dev/null +++ b/book/rust-docs/crates.js @@ -0,0 +1 @@ +window.ALL_CRATES = ["as_ohttp_client","autofill","cli_support","crashtest","embedded_uniffi_bindgen","error_support","error_support_macros","examples_fxa_client","fxa_client","interrupt_support","logins","nimbus","nimbus_cli","nimbus_fml","nss","nss_build_common","nss_sys","places","protobuf_gen","push","rc_crypto","rc_log_ffi","remote_settings","restmail_client","sql_support","sync15","sync_guid","sync_manager","tabs","types","viaduct","viaduct_reqwest","webext_storage"]; \ No newline at end of file diff --git a/book/rust-docs/embedded_uniffi_bindgen/all.html b/book/rust-docs/embedded_uniffi_bindgen/all.html new file mode 100644 index 0000000000..0e97be9f2c --- /dev/null +++ b/book/rust-docs/embedded_uniffi_bindgen/all.html @@ -0,0 +1 @@ +List of all items in this crate

List of all items

Functions

\ No newline at end of file diff --git a/book/rust-docs/embedded_uniffi_bindgen/fn.main.html b/book/rust-docs/embedded_uniffi_bindgen/fn.main.html new file mode 100644 index 0000000000..2c09f004b7 --- /dev/null +++ b/book/rust-docs/embedded_uniffi_bindgen/fn.main.html @@ -0,0 +1 @@ +main in embedded_uniffi_bindgen - Rust
pub(crate) fn main()
\ No newline at end of file diff --git a/book/rust-docs/embedded_uniffi_bindgen/index.html b/book/rust-docs/embedded_uniffi_bindgen/index.html new file mode 100644 index 0000000000..741866b934 --- /dev/null +++ b/book/rust-docs/embedded_uniffi_bindgen/index.html @@ -0,0 +1 @@ +embedded_uniffi_bindgen - Rust
\ No newline at end of file diff --git a/book/rust-docs/embedded_uniffi_bindgen/sidebar-items.js b/book/rust-docs/embedded_uniffi_bindgen/sidebar-items.js new file mode 100644 index 0000000000..9b556a055c --- /dev/null +++ b/book/rust-docs/embedded_uniffi_bindgen/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["main"]}; \ No newline at end of file diff --git a/book/rust-docs/error_support/all.html b/book/rust-docs/error_support/all.html new file mode 100644 index 0000000000..651878d005 --- /dev/null +++ b/book/rust-docs/error_support/all.html @@ -0,0 +1 @@ +List of all items in this crate
\ No newline at end of file diff --git a/book/rust-docs/error_support/attr.handle_error.html b/book/rust-docs/error_support/attr.handle_error.html new file mode 100644 index 0000000000..45e5e39a0f --- /dev/null +++ b/book/rust-docs/error_support/attr.handle_error.html @@ -0,0 +1,50 @@ +handle_error in error_support - Rust

Attribute Macro error_support::handle_error

source ·
#[handle_error]
Expand description

A procedural macro that exposes internal errors to external errors the +consuming applications should handle. It requires that the internal error +implements [error_support::ErrorHandling].

+

Additionally, this procedural macro has side effects, including:

+
    +
  • It would log the error based on a pre-defined log level. The log level is defined +in the [error_support::ErrorHandling] implementation.
  • +
  • It would report some errors using an external error reporter, in practice, this +is implemented using Sentry in the app.
  • +
+

Example

+
use error_support::{handle_error, GetErrorHandling, ErrorHandling};
+use std::fmt::Display
+#[derive(Debug, thiserror::Error)]
+struct Error {}
+type Result<T, E = Error> = std::result::Result<T, E>;
+impl Display for Error {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "Internal Error!")
+    }
+}
+
+#[derive(Debug, thiserror::Error)]
+struct ExternalError {}
+
+impl Display for ExternalError {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "External Error!")
+    }
+}
+
+impl GetErrorHandling for Error {
+   type ExternalError = ExternalError;
+
+   fn get_error_handling(&self) -> ErrorHandling<Self::ExternalError> {
+       ErrorHandling::convert(ExternalError {})
+   }
+}
+
+// The `handle_error` macro maps from the error supplied in the mandatory argument
+// (ie, `Error` in this example) to the error returned by the function (`ExternalError`
+// in this example)
+#[handle_error(Error)]
+fn do_something() -> std::result::Result<String, ExternalError> {
+   Err(Error{})
+}
+
+// The error here is an `ExternalError`
+let _: ExternalError = do_something().unwrap_err();
+
\ No newline at end of file diff --git a/book/rust-docs/error_support/fn.convert_log_report_error.html b/book/rust-docs/error_support/fn.convert_log_report_error.html new file mode 100644 index 0000000000..e81cd6a607 --- /dev/null +++ b/book/rust-docs/error_support/fn.convert_log_report_error.html @@ -0,0 +1,6 @@ +convert_log_report_error in error_support - Rust
pub fn convert_log_report_error<IE, EE>(e: IE) -> EEwhere
+    IE: GetErrorHandling<ExternalError = EE> + Error,
+    EE: Error,
Expand description

Handle the specified “internal” error, taking any logging or error +reporting actions and converting the error to the public error. +Called by our handle_error macro so needs to be public.

+
\ No newline at end of file diff --git a/book/rust-docs/error_support/fn.redact_compact_jwe.html b/book/rust-docs/error_support/fn.redact_compact_jwe.html new file mode 100644 index 0000000000..37b4b2ce5b --- /dev/null +++ b/book/rust-docs/error_support/fn.redact_compact_jwe.html @@ -0,0 +1,2 @@ +redact_compact_jwe in error_support - Rust
pub fn redact_compact_jwe(url: &str) -> String
Expand description

Redact compact jwe string (Five base64 segments, separated by . chars)

+
\ No newline at end of file diff --git a/book/rust-docs/error_support/fn.redact_url.html b/book/rust-docs/error_support/fn.redact_url.html new file mode 100644 index 0000000000..33f3c6a932 --- /dev/null +++ b/book/rust-docs/error_support/fn.redact_url.html @@ -0,0 +1,4 @@ +redact_url in error_support - Rust

Function error_support::redact_url

source ·
pub fn redact_url(url: &str) -> String
Expand description

Redact a URL.

+

It’s tricky to redact an URL without revealing PII. We check for various known bad URL forms +and report them, otherwise we just log “”.

+
\ No newline at end of file diff --git a/book/rust-docs/error_support/fn.report_breadcrumb.html b/book/rust-docs/error_support/fn.report_breadcrumb.html new file mode 100644 index 0000000000..c23792dc98 --- /dev/null +++ b/book/rust-docs/error_support/fn.report_breadcrumb.html @@ -0,0 +1,6 @@ +report_breadcrumb in error_support - Rust
pub fn report_breadcrumb(
+    message: String,
+    module: String,
+    line: u32,
+    column: u32
+)
\ No newline at end of file diff --git a/book/rust-docs/error_support/fn.report_error_to_app.html b/book/rust-docs/error_support/fn.report_error_to_app.html new file mode 100644 index 0000000000..98caa37c44 --- /dev/null +++ b/book/rust-docs/error_support/fn.report_error_to_app.html @@ -0,0 +1 @@ +report_error_to_app in error_support - Rust
pub fn report_error_to_app(type_name: String, message: String)
\ No newline at end of file diff --git a/book/rust-docs/error_support/fn.set_application_error_reporter.html b/book/rust-docs/error_support/fn.set_application_error_reporter.html new file mode 100644 index 0000000000..3ac794de99 --- /dev/null +++ b/book/rust-docs/error_support/fn.set_application_error_reporter.html @@ -0,0 +1,3 @@ +set_application_error_reporter in error_support - Rust
pub fn set_application_error_reporter(
+    reporter: Box<dyn ApplicationErrorReporter>
+)
\ No newline at end of file diff --git a/book/rust-docs/error_support/fn.unset_application_error_reporter.html b/book/rust-docs/error_support/fn.unset_application_error_reporter.html new file mode 100644 index 0000000000..5e64cc226e --- /dev/null +++ b/book/rust-docs/error_support/fn.unset_application_error_reporter.html @@ -0,0 +1 @@ +unset_application_error_reporter in error_support - Rust
pub fn unset_application_error_reporter()
\ No newline at end of file diff --git a/book/rust-docs/error_support/handling/fn.convert_log_report_error.html b/book/rust-docs/error_support/handling/fn.convert_log_report_error.html new file mode 100644 index 0000000000..a304f2b8ee --- /dev/null +++ b/book/rust-docs/error_support/handling/fn.convert_log_report_error.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../error_support/fn.convert_log_report_error.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/error_support/handling/struct.ErrorHandling.html b/book/rust-docs/error_support/handling/struct.ErrorHandling.html new file mode 100644 index 0000000000..34e7a27ba2 --- /dev/null +++ b/book/rust-docs/error_support/handling/struct.ErrorHandling.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../error_support/struct.ErrorHandling.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/error_support/handling/struct.ErrorReporting.html b/book/rust-docs/error_support/handling/struct.ErrorReporting.html new file mode 100644 index 0000000000..b4b4bb7185 --- /dev/null +++ b/book/rust-docs/error_support/handling/struct.ErrorReporting.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../error_support/struct.ErrorReporting.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/error_support/handling/trait.GetErrorHandling.html b/book/rust-docs/error_support/handling/trait.GetErrorHandling.html new file mode 100644 index 0000000000..ff6847e2f7 --- /dev/null +++ b/book/rust-docs/error_support/handling/trait.GetErrorHandling.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../error_support/trait.GetErrorHandling.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/error_support/index.html b/book/rust-docs/error_support/index.html new file mode 100644 index 0000000000..a917414a55 --- /dev/null +++ b/book/rust-docs/error_support/index.html @@ -0,0 +1,12 @@ +error_support - Rust

Crate error_support

source ·

Re-exports

Macros

  • Tell the application to log a breadcrumb
  • All the error boilerplate (okay, with a couple exceptions in some cases) in +one place.
  • Define a set of conversions from external error types into the provided +error kind. Use define_error to do this at the same time as +define_error_wrapper.
  • XXX - Most of this is now considered deprecated - only FxA uses it, and +should be replaced with the facilities in the handling module. +Define a wrapper around the the provided ErrorKind type. +See also define_error which is more likely to be what you want.
  • Tell the application to report an error
  • Log a breadcrumb if we see an Result::Err value

Structs

  • Specifies how an “internal” error is converted to an “external” public error and +any logging or reporting that should happen.
  • Describes what error reporting action should be taken.

Traits

Functions

Attribute Macros

  • A procedural macro that exposes internal errors to external errors the +consuming applications should handle. It requires that the internal error +implements [error_support::ErrorHandling].
\ No newline at end of file diff --git a/book/rust-docs/error_support/macro.breadcrumb!.html b/book/rust-docs/error_support/macro.breadcrumb!.html new file mode 100644 index 0000000000..c3540b9473 --- /dev/null +++ b/book/rust-docs/error_support/macro.breadcrumb!.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to macro.breadcrumb.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/error_support/macro.breadcrumb.html b/book/rust-docs/error_support/macro.breadcrumb.html new file mode 100644 index 0000000000..206e6dfe4c --- /dev/null +++ b/book/rust-docs/error_support/macro.breadcrumb.html @@ -0,0 +1,6 @@ +breadcrumb in error_support - Rust
macro_rules! breadcrumb {
+    ($($arg:tt)*) => { ... };
+}
Expand description

Tell the application to log a breadcrumb

+

Breadcrumbs are log-like entries that get tracked by the error reporting system. When we +report an error, recent breadcrumbs will be associated with it.

+
\ No newline at end of file diff --git a/book/rust-docs/error_support/macro.define_error!.html b/book/rust-docs/error_support/macro.define_error!.html new file mode 100644 index 0000000000..ac98fe07f3 --- /dev/null +++ b/book/rust-docs/error_support/macro.define_error!.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to macro.define_error.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/error_support/macro.define_error.html b/book/rust-docs/error_support/macro.define_error.html new file mode 100644 index 0000000000..0e30ce3a1d --- /dev/null +++ b/book/rust-docs/error_support/macro.define_error.html @@ -0,0 +1,5 @@ +define_error in error_support - Rust
macro_rules! define_error {
+    ($Kind:ident { $(($variant:ident, $type:ty)),* $(,)? }) => { ... };
+}
Expand description

All the error boilerplate (okay, with a couple exceptions in some cases) in +one place.

+
\ No newline at end of file diff --git a/book/rust-docs/error_support/macro.define_error_conversions!.html b/book/rust-docs/error_support/macro.define_error_conversions!.html new file mode 100644 index 0000000000..ba019284f7 --- /dev/null +++ b/book/rust-docs/error_support/macro.define_error_conversions!.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to macro.define_error_conversions.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/error_support/macro.define_error_conversions.html b/book/rust-docs/error_support/macro.define_error_conversions.html new file mode 100644 index 0000000000..7567b89862 --- /dev/null +++ b/book/rust-docs/error_support/macro.define_error_conversions.html @@ -0,0 +1,6 @@ +define_error_conversions in error_support - Rust
macro_rules! define_error_conversions {
+    ($Kind:ident { $(($variant:ident, $type:ty)),* $(,)? }) => { ... };
+}
Expand description

Define a set of conversions from external error types into the provided +error kind. Use define_error to do this at the same time as +define_error_wrapper.

+
\ No newline at end of file diff --git a/book/rust-docs/error_support/macro.define_error_wrapper!.html b/book/rust-docs/error_support/macro.define_error_wrapper!.html new file mode 100644 index 0000000000..d25b3713e0 --- /dev/null +++ b/book/rust-docs/error_support/macro.define_error_wrapper!.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to macro.define_error_wrapper.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/error_support/macro.define_error_wrapper.html b/book/rust-docs/error_support/macro.define_error_wrapper.html new file mode 100644 index 0000000000..d3ba0a8e19 --- /dev/null +++ b/book/rust-docs/error_support/macro.define_error_wrapper.html @@ -0,0 +1,7 @@ +define_error_wrapper in error_support - Rust
macro_rules! define_error_wrapper {
+    ($Kind:ty) => { ... };
+}
Expand description

XXX - Most of this is now considered deprecated - only FxA uses it, and +should be replaced with the facilities in the handling module. +Define a wrapper around the the provided ErrorKind type. +See also define_error which is more likely to be what you want.

+
\ No newline at end of file diff --git a/book/rust-docs/error_support/macro.report_error!.html b/book/rust-docs/error_support/macro.report_error!.html new file mode 100644 index 0000000000..b70e520f0b --- /dev/null +++ b/book/rust-docs/error_support/macro.report_error!.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to macro.report_error.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/error_support/macro.report_error.html b/book/rust-docs/error_support/macro.report_error.html new file mode 100644 index 0000000000..8ed09bffc3 --- /dev/null +++ b/book/rust-docs/error_support/macro.report_error.html @@ -0,0 +1,16 @@ +report_error in error_support - Rust
macro_rules! report_error {
+    ($type_name:expr, $($arg:tt)*) => { ... };
+}
Expand description

Tell the application to report an error

+

If configured by the application, this sent to the application, which should report it to a +Sentry-like system. This should only be used for errors that we don’t expect to see and will +work on fixing if we see a non-trivial volume of them.

+

type_name identifies the error. It should be the main text that gets shown to the +user and also how the error system groups errors together. It should be in UpperCamelCase +form.

+

Good type_names require some trial and error, for example:

+
    +
  • Start with the error kind variant name
  • +
  • Add more text to distinguish errors more. For example an error code, or an extra word +based on inspecting the error details
  • +
+
\ No newline at end of file diff --git a/book/rust-docs/error_support/macro.trace_error!.html b/book/rust-docs/error_support/macro.trace_error!.html new file mode 100644 index 0000000000..fd13150cd1 --- /dev/null +++ b/book/rust-docs/error_support/macro.trace_error!.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to macro.trace_error.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/error_support/macro.trace_error.html b/book/rust-docs/error_support/macro.trace_error.html new file mode 100644 index 0000000000..210cf04a48 --- /dev/null +++ b/book/rust-docs/error_support/macro.trace_error.html @@ -0,0 +1,7 @@ +trace_error in error_support - Rust
macro_rules! trace_error {
+    ($result:expr) => { ... };
+}
Expand description

Log a breadcrumb if we see an Result::Err value

+

Use this macro to wrap a function call that returns a Result<>. If that call returns an +error, then we will log a breadcrumb for it. This can be used to track down the codepath where +an error happened.

+
\ No newline at end of file diff --git a/book/rust-docs/error_support/redact/fn.redact_compact_jwe.html b/book/rust-docs/error_support/redact/fn.redact_compact_jwe.html new file mode 100644 index 0000000000..2478a65968 --- /dev/null +++ b/book/rust-docs/error_support/redact/fn.redact_compact_jwe.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../error_support/fn.redact_compact_jwe.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/error_support/redact/fn.redact_url.html b/book/rust-docs/error_support/redact/fn.redact_url.html new file mode 100644 index 0000000000..811b59b1b8 --- /dev/null +++ b/book/rust-docs/error_support/redact/fn.redact_url.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../error_support/fn.redact_url.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/error_support/reporting/fn.report_breadcrumb.html b/book/rust-docs/error_support/reporting/fn.report_breadcrumb.html new file mode 100644 index 0000000000..ce30b4c600 --- /dev/null +++ b/book/rust-docs/error_support/reporting/fn.report_breadcrumb.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../error_support/fn.report_breadcrumb.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/error_support/reporting/fn.report_error_to_app.html b/book/rust-docs/error_support/reporting/fn.report_error_to_app.html new file mode 100644 index 0000000000..75fe762db6 --- /dev/null +++ b/book/rust-docs/error_support/reporting/fn.report_error_to_app.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../error_support/fn.report_error_to_app.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/error_support/reporting/fn.set_application_error_reporter.html b/book/rust-docs/error_support/reporting/fn.set_application_error_reporter.html new file mode 100644 index 0000000000..db102cfacf --- /dev/null +++ b/book/rust-docs/error_support/reporting/fn.set_application_error_reporter.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../error_support/fn.set_application_error_reporter.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/error_support/reporting/fn.unset_application_error_reporter.html b/book/rust-docs/error_support/reporting/fn.unset_application_error_reporter.html new file mode 100644 index 0000000000..af1a77ef42 --- /dev/null +++ b/book/rust-docs/error_support/reporting/fn.unset_application_error_reporter.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../error_support/fn.unset_application_error_reporter.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/error_support/reporting/trait.ApplicationErrorReporter.html b/book/rust-docs/error_support/reporting/trait.ApplicationErrorReporter.html new file mode 100644 index 0000000000..3a784baae9 --- /dev/null +++ b/book/rust-docs/error_support/reporting/trait.ApplicationErrorReporter.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../error_support/trait.ApplicationErrorReporter.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/error_support/sidebar-items.js b/book/rust-docs/error_support/sidebar-items.js new file mode 100644 index 0000000000..2923cdd1fb --- /dev/null +++ b/book/rust-docs/error_support/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"attr":["handle_error"],"fn":["convert_log_report_error","redact_compact_jwe","redact_url","report_breadcrumb","report_error_to_app","set_application_error_reporter","unset_application_error_reporter"],"macro":["breadcrumb","define_error","define_error_conversions","define_error_wrapper","report_error","trace_error"],"struct":["ErrorHandling","ErrorReporting"],"trait":["ApplicationErrorReporter","GetErrorHandling"]}; \ No newline at end of file diff --git a/book/rust-docs/error_support/struct.ErrorHandling.html b/book/rust-docs/error_support/struct.ErrorHandling.html new file mode 100644 index 0000000000..6f673f7265 --- /dev/null +++ b/book/rust-docs/error_support/struct.ErrorHandling.html @@ -0,0 +1,31 @@ +ErrorHandling in error_support - Rust
pub struct ErrorHandling<E> {
+    pub err: E,
+    pub reporting: ErrorReporting,
+}
Expand description

Specifies how an “internal” error is converted to an “external” public error and +any logging or reporting that should happen.

+

Fields§

§err: E

The external error that should be returned.

+
§reporting: ErrorReporting

How the error should be reported.

+

Implementations§

source§

impl<E> ErrorHandling<E>

source

pub fn convert(err: E) -> Self

Create an ErrorHandling instance with an error conversion.

+

ErrorHandling instance are created using a builder-style API. This is always the first +function in the chain, optionally followed by log(), report(), etc.

+
source

pub fn log(self, level: Level) -> Self

Add logging to an ErrorHandling instance

+
source

pub fn report(self, report_class: impl Into<String>) -> Self

Add reporting to an ErrorHandling instance

+
source

pub fn log_warning(self) -> Self

log a warning

+
source

pub fn log_info(self) -> Self

log an info

+
source

pub fn report_error(self, report_class: impl Into<String>) -> Self

Add reporting to an ErrorHandling instance and also log an Error

+

Auto Trait Implementations§

§

impl<E> RefUnwindSafe for ErrorHandling<E>where + E: RefUnwindSafe,

§

impl<E> Send for ErrorHandling<E>where + E: Send,

§

impl<E> Sync for ErrorHandling<E>where + E: Sync,

§

impl<E> Unpin for ErrorHandling<E>where + E: Unpin,

§

impl<E> UnwindSafe for ErrorHandling<E>where + E: UnwindSafe,

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/error_support/struct.ErrorReporting.html b/book/rust-docs/error_support/struct.ErrorReporting.html new file mode 100644 index 0000000000..fd737c1af6 --- /dev/null +++ b/book/rust-docs/error_support/struct.ErrorReporting.html @@ -0,0 +1,12 @@ +ErrorReporting in error_support - Rust
pub struct ErrorReporting { /* private fields */ }
Expand description

Describes what error reporting action should be taken.

+

Trait Implementations§

source§

impl Debug for ErrorReporting

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for ErrorReporting

source§

fn default() -> ErrorReporting

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/error_support/trait.ApplicationErrorReporter.html b/book/rust-docs/error_support/trait.ApplicationErrorReporter.html new file mode 100644 index 0000000000..16561ee731 --- /dev/null +++ b/book/rust-docs/error_support/trait.ApplicationErrorReporter.html @@ -0,0 +1,24 @@ +ApplicationErrorReporter in error_support - Rust
pub trait ApplicationErrorReporter: Sync + Send {
+    // Required methods
+    fn report_error(&self, type_name: String, message: String);
+    fn report_breadcrumb(
+        &self,
+        message: String,
+        module: String,
+        line: u32,
+        column: u32
+    );
+}
Expand description

Application error reporting trait

+

The application that’s consuming application-services implements this via a UniFFI callback +interface, then calls set_application_error_reporter() to setup a global +ApplicationErrorReporter.

+

Required Methods§

source

fn report_error(&self, type_name: String, message: String)

Send an error report to a Sentry-like error reporting system

+

type_name should be used to group errors together

+
source

fn report_breadcrumb( + &self, + message: String, + module: String, + line: u32, + column: u32 +)

Send a breadcrumb to a Sentry-like error reporting system

+

Implementors§

\ No newline at end of file diff --git a/book/rust-docs/error_support/trait.GetErrorHandling.html b/book/rust-docs/error_support/trait.GetErrorHandling.html new file mode 100644 index 0000000000..1f327a4830 --- /dev/null +++ b/book/rust-docs/error_support/trait.GetErrorHandling.html @@ -0,0 +1,8 @@ +GetErrorHandling in error_support - Rust
pub trait GetErrorHandling {
+    type ExternalError;
+
+    // Required method
+    fn get_error_handling(&self) -> ErrorHandling<Self::ExternalError>;
+}
Expand description

A trait to define how errors are converted and reported.

+

Required Associated Types§

Required Methods§

source

fn get_error_handling(&self) -> ErrorHandling<Self::ExternalError>

Return how to handle our internal errors

+

Implementors§

\ No newline at end of file diff --git a/book/rust-docs/error_support_macros/all.html b/book/rust-docs/error_support_macros/all.html new file mode 100644 index 0000000000..80c0525499 --- /dev/null +++ b/book/rust-docs/error_support_macros/all.html @@ -0,0 +1 @@ +List of all items in this crate

List of all items

Attribute Macros

\ No newline at end of file diff --git a/book/rust-docs/error_support_macros/attr.handle_error.html b/book/rust-docs/error_support_macros/attr.handle_error.html new file mode 100644 index 0000000000..9e34c3b977 --- /dev/null +++ b/book/rust-docs/error_support_macros/attr.handle_error.html @@ -0,0 +1,50 @@ +handle_error in error_support_macros - Rust

Attribute Macro error_support_macros::handle_error

source ·
#[handle_error]
Expand description

A procedural macro that exposes internal errors to external errors the +consuming applications should handle. It requires that the internal error +implements [error_support::ErrorHandling].

+

Additionally, this procedural macro has side effects, including:

+
    +
  • It would log the error based on a pre-defined log level. The log level is defined +in the [error_support::ErrorHandling] implementation.
  • +
  • It would report some errors using an external error reporter, in practice, this +is implemented using Sentry in the app.
  • +
+

Example

+
use error_support::{handle_error, GetErrorHandling, ErrorHandling};
+use std::fmt::Display
+#[derive(Debug, thiserror::Error)]
+struct Error {}
+type Result<T, E = Error> = std::result::Result<T, E>;
+impl Display for Error {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "Internal Error!")
+    }
+}
+
+#[derive(Debug, thiserror::Error)]
+struct ExternalError {}
+
+impl Display for ExternalError {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "External Error!")
+    }
+}
+
+impl GetErrorHandling for Error {
+   type ExternalError = ExternalError;
+
+   fn get_error_handling(&self) -> ErrorHandling<Self::ExternalError> {
+       ErrorHandling::convert(ExternalError {})
+   }
+}
+
+// The `handle_error` macro maps from the error supplied in the mandatory argument
+// (ie, `Error` in this example) to the error returned by the function (`ExternalError`
+// in this example)
+#[handle_error(Error)]
+fn do_something() -> std::result::Result<String, ExternalError> {
+   Err(Error{})
+}
+
+// The error here is an `ExternalError`
+let _: ExternalError = do_something().unwrap_err();
+
\ No newline at end of file diff --git a/book/rust-docs/error_support_macros/index.html b/book/rust-docs/error_support_macros/index.html new file mode 100644 index 0000000000..d2b43d232d --- /dev/null +++ b/book/rust-docs/error_support_macros/index.html @@ -0,0 +1,3 @@ +error_support_macros - Rust

Attribute Macros

  • A procedural macro that exposes internal errors to external errors the +consuming applications should handle. It requires that the internal error +implements [error_support::ErrorHandling].
\ No newline at end of file diff --git a/book/rust-docs/error_support_macros/sidebar-items.js b/book/rust-docs/error_support_macros/sidebar-items.js new file mode 100644 index 0000000000..81c15d62f5 --- /dev/null +++ b/book/rust-docs/error_support_macros/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"attr":["handle_error"]}; \ No newline at end of file diff --git a/book/rust-docs/examples_fxa_client/all.html b/book/rust-docs/examples_fxa_client/all.html new file mode 100644 index 0000000000..ac2357c522 --- /dev/null +++ b/book/rust-docs/examples_fxa_client/all.html @@ -0,0 +1 @@ +List of all items in this crate
\ No newline at end of file diff --git a/book/rust-docs/examples_fxa_client/devices/enum.Command.html b/book/rust-docs/examples_fxa_client/devices/enum.Command.html new file mode 100644 index 0000000000..df3d973384 --- /dev/null +++ b/book/rust-docs/examples_fxa_client/devices/enum.Command.html @@ -0,0 +1,30 @@ +Command in examples_fxa_client::devices - Rust
enum Command {
+    List,
+    SetName {
+        name: String,
+    },
+}

Variants§

§

List

§

SetName

Fields

§name: String

Trait Implementations§

source§

impl FromArgMatches for Command

source§

fn from_arg_matches(__clap_arg_matches: &ArgMatches) -> Result<Self, Error>

Instantiate Self from [ArgMatches], parsing the arguments as needed. Read more
source§

fn from_arg_matches_mut( + __clap_arg_matches: &mut ArgMatches +) -> Result<Self, Error>

Instantiate Self from [ArgMatches], parsing the arguments as needed. Read more
source§

fn update_from_arg_matches( + &mut self, + __clap_arg_matches: &ArgMatches +) -> Result<(), Error>

Assign values from ArgMatches to self.
source§

fn update_from_arg_matches_mut<'b>( + &mut self, + __clap_arg_matches: &mut ArgMatches +) -> Result<(), Error>

Assign values from ArgMatches to self.
source§

impl Subcommand for Command

source§

fn augment_subcommands<'b>(__clap_app: Command) -> Command

Append to [Command] so it can instantiate Self. Read more
source§

fn augment_subcommands_for_update<'b>(__clap_app: Command) -> Command

Append to [Command] so it can update self. Read more
source§

fn has_subcommand(__clap_name: &str) -> bool

Test whether Self can parse a specific subcommand

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/examples_fxa_client/devices/fn.list.html b/book/rust-docs/examples_fxa_client/devices/fn.list.html new file mode 100644 index 0000000000..c55c65a7d2 --- /dev/null +++ b/book/rust-docs/examples_fxa_client/devices/fn.list.html @@ -0,0 +1 @@ +list in examples_fxa_client::devices - Rust
fn list(account: &FirefoxAccount) -> Result<()>
\ No newline at end of file diff --git a/book/rust-docs/examples_fxa_client/devices/fn.run.html b/book/rust-docs/examples_fxa_client/devices/fn.run.html new file mode 100644 index 0000000000..81517161f9 --- /dev/null +++ b/book/rust-docs/examples_fxa_client/devices/fn.run.html @@ -0,0 +1 @@ +run in examples_fxa_client::devices - Rust
pub fn run(account: &FirefoxAccount, args: DeviceArgs) -> Result<()>
\ No newline at end of file diff --git a/book/rust-docs/examples_fxa_client/devices/fn.set_name.html b/book/rust-docs/examples_fxa_client/devices/fn.set_name.html new file mode 100644 index 0000000000..b26edb16ec --- /dev/null +++ b/book/rust-docs/examples_fxa_client/devices/fn.set_name.html @@ -0,0 +1 @@ +set_name in examples_fxa_client::devices - Rust
fn set_name(account: &FirefoxAccount, name: String) -> Result<()>
\ No newline at end of file diff --git a/book/rust-docs/examples_fxa_client/devices/index.html b/book/rust-docs/examples_fxa_client/devices/index.html new file mode 100644 index 0000000000..396e7678e4 --- /dev/null +++ b/book/rust-docs/examples_fxa_client/devices/index.html @@ -0,0 +1 @@ +examples_fxa_client::devices - Rust
\ No newline at end of file diff --git a/book/rust-docs/examples_fxa_client/devices/sidebar-items.js b/book/rust-docs/examples_fxa_client/devices/sidebar-items.js new file mode 100644 index 0000000000..bfe39045b1 --- /dev/null +++ b/book/rust-docs/examples_fxa_client/devices/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["Command"],"fn":["list","run","set_name"],"struct":["DeviceArgs"]}; \ No newline at end of file diff --git a/book/rust-docs/examples_fxa_client/devices/struct.DeviceArgs.html b/book/rust-docs/examples_fxa_client/devices/struct.DeviceArgs.html new file mode 100644 index 0000000000..6eb7be1b20 --- /dev/null +++ b/book/rust-docs/examples_fxa_client/devices/struct.DeviceArgs.html @@ -0,0 +1,27 @@ +DeviceArgs in examples_fxa_client::devices - Rust
pub struct DeviceArgs {
+    command: Option<Command>,
+}

Fields§

§command: Option<Command>

Trait Implementations§

source§

impl Args for DeviceArgs

source§

fn group_id() -> Option<Id>

Report the [ArgGroup::id][crate::ArgGroup::id] for this set of arguments
source§

fn augment_args<'b>(__clap_app: Command) -> Command

Append to [Command] so it can instantiate Self. Read more
source§

fn augment_args_for_update<'b>(__clap_app: Command) -> Command

Append to [Command] so it can update self. Read more
source§

impl FromArgMatches for DeviceArgs

source§

fn from_arg_matches(__clap_arg_matches: &ArgMatches) -> Result<Self, Error>

Instantiate Self from [ArgMatches], parsing the arguments as needed. Read more
source§

fn from_arg_matches_mut( + __clap_arg_matches: &mut ArgMatches +) -> Result<Self, Error>

Instantiate Self from [ArgMatches], parsing the arguments as needed. Read more
source§

fn update_from_arg_matches( + &mut self, + __clap_arg_matches: &ArgMatches +) -> Result<(), Error>

Assign values from ArgMatches to self.
source§

fn update_from_arg_matches_mut( + &mut self, + __clap_arg_matches: &mut ArgMatches +) -> Result<(), Error>

Assign values from ArgMatches to self.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/examples_fxa_client/enum.Command.html b/book/rust-docs/examples_fxa_client/enum.Command.html new file mode 100644 index 0000000000..d033d28fd1 --- /dev/null +++ b/book/rust-docs/examples_fxa_client/enum.Command.html @@ -0,0 +1,29 @@ +Command in examples_fxa_client - Rust
pub(crate) enum Command {
+    Devices(DeviceArgs),
+    SendTab(SendTabArgs),
+    Disconnect,
+}

Variants§

§

Devices(DeviceArgs)

§

SendTab(SendTabArgs)

§

Disconnect

Trait Implementations§

source§

impl FromArgMatches for Command

source§

fn from_arg_matches(__clap_arg_matches: &ArgMatches) -> Result<Self, Error>

Instantiate Self from [ArgMatches], parsing the arguments as needed. Read more
source§

fn from_arg_matches_mut( + __clap_arg_matches: &mut ArgMatches +) -> Result<Self, Error>

Instantiate Self from [ArgMatches], parsing the arguments as needed. Read more
source§

fn update_from_arg_matches( + &mut self, + __clap_arg_matches: &ArgMatches +) -> Result<(), Error>

Assign values from ArgMatches to self.
source§

fn update_from_arg_matches_mut<'b>( + &mut self, + __clap_arg_matches: &mut ArgMatches +) -> Result<(), Error>

Assign values from ArgMatches to self.
source§

impl Subcommand for Command

source§

fn augment_subcommands<'b>(__clap_app: Command) -> Command

Append to [Command] so it can instantiate Self. Read more
source§

fn augment_subcommands_for_update<'b>(__clap_app: Command) -> Command

Append to [Command] so it can update self. Read more
source§

fn has_subcommand(__clap_name: &str) -> bool

Test whether Self can parse a specific subcommand

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/examples_fxa_client/enum.Server.html b/book/rust-docs/examples_fxa_client/enum.Server.html new file mode 100644 index 0000000000..a34dce8269 --- /dev/null +++ b/book/rust-docs/examples_fxa_client/enum.Server.html @@ -0,0 +1,38 @@ +Server in examples_fxa_client - Rust
pub(crate) enum Server {
+    Release,
+    China,
+    Stable,
+    Stage,
+    LocalDev,
+}

Variants§

§

Release

Official server

+
§

China

China server

+
§

Stable

stable dev sever

+
§

Stage

staging dev sever

+
§

LocalDev

local dev sever

+

Trait Implementations§

source§

impl Clone for Server

source§

fn clone(&self) -> Server

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for Server

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Ord for Server

source§

fn cmp(&self, other: &Server) -> Ordering

This method returns an Ordering between self and other. Read more
1.21.0 · source§

fn max(self, other: Self) -> Selfwhere + Self: Sized,

Compares and returns the maximum of two values. Read more
1.21.0 · source§

fn min(self, other: Self) -> Selfwhere + Self: Sized,

Compares and returns the minimum of two values. Read more
1.50.0 · source§

fn clamp(self, min: Self, max: Self) -> Selfwhere + Self: Sized + PartialOrd<Self>,

Restrict a value to a certain interval. Read more
source§

impl PartialEq<Server> for Server

source§

fn eq(&self, other: &Server) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl PartialOrd<Server> for Server

source§

fn partial_cmp(&self, other: &Server) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · source§

fn lt(&self, other: &Rhs) -> bool

This method tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · source§

fn le(&self, other: &Rhs) -> bool

This method tests less than or equal to (for self and other) and is used by the <= +operator. Read more
1.0.0 · source§

fn gt(&self, other: &Rhs) -> bool

This method tests greater than (for self and other) and is used by the > operator. Read more
1.0.0 · source§

fn ge(&self, other: &Rhs) -> bool

This method tests greater than or equal to (for self and other) and is used by the >= +operator. Read more
source§

impl ValueEnum for Server

source§

fn value_variants<'a>() -> &'a [Self]

All possible argument values, in display order.
source§

fn to_possible_value<'a>(&self) -> Option<PossibleValue>

The canonical argument value. Read more
§

fn from_str(input: &str, ignore_case: bool) -> Result<Self, String>

Parse an argument into Self.
source§

impl Copy for Server

source§

impl Eq for Server

source§

impl StructuralEq for Server

source§

impl StructuralPartialEq for Server

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<Q, K> Equivalent<K> for Qwhere + Q: Eq + ?Sized, + K: Borrow<Q> + ?Sized,

source§

fn equivalent(&self, key: &K) -> bool

Compare self to key and return true if they are equal.
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/examples_fxa_client/fn.load_account.html b/book/rust-docs/examples_fxa_client/fn.load_account.html new file mode 100644 index 0000000000..79ab9ecf29 --- /dev/null +++ b/book/rust-docs/examples_fxa_client/fn.load_account.html @@ -0,0 +1 @@ +load_account in examples_fxa_client - Rust
pub(crate) fn load_account(cli: &Cli) -> Result<FirefoxAccount>
\ No newline at end of file diff --git a/book/rust-docs/examples_fxa_client/fn.main.html b/book/rust-docs/examples_fxa_client/fn.main.html new file mode 100644 index 0000000000..f9ed063797 --- /dev/null +++ b/book/rust-docs/examples_fxa_client/fn.main.html @@ -0,0 +1 @@ +main in examples_fxa_client - Rust

Function examples_fxa_client::main

source ·
pub(crate) fn main() -> Result<()>
\ No newline at end of file diff --git a/book/rust-docs/examples_fxa_client/fn.persist_fxa_state.html b/book/rust-docs/examples_fxa_client/fn.persist_fxa_state.html new file mode 100644 index 0000000000..aeeea608ad --- /dev/null +++ b/book/rust-docs/examples_fxa_client/fn.persist_fxa_state.html @@ -0,0 +1 @@ +persist_fxa_state in examples_fxa_client - Rust
pub fn persist_fxa_state(acct: &FirefoxAccount) -> Result<()>
\ No newline at end of file diff --git a/book/rust-docs/examples_fxa_client/index.html b/book/rust-docs/examples_fxa_client/index.html new file mode 100644 index 0000000000..fc2d6779db --- /dev/null +++ b/book/rust-docs/examples_fxa_client/index.html @@ -0,0 +1 @@ +examples_fxa_client - Rust
\ No newline at end of file diff --git a/book/rust-docs/examples_fxa_client/send_tab/enum.Command.html b/book/rust-docs/examples_fxa_client/send_tab/enum.Command.html new file mode 100644 index 0000000000..2f5c5e309f --- /dev/null +++ b/book/rust-docs/examples_fxa_client/send_tab/enum.Command.html @@ -0,0 +1,35 @@ +Command in examples_fxa_client::send_tab - Rust
enum Command {
+    Poll,
+    Send {
+        device_id: String,
+        title: String,
+        url: String,
+    },
+}

Variants§

§

Poll

Perform a single poll for tabs sent to this device

+
§

Send

Fields

§device_id: String

Device ID (use the devices command to list)

+
§title: String

Send a tab to another device

+

Trait Implementations§

source§

impl FromArgMatches for Command

source§

fn from_arg_matches(__clap_arg_matches: &ArgMatches) -> Result<Self, Error>

Instantiate Self from [ArgMatches], parsing the arguments as needed. Read more
source§

fn from_arg_matches_mut( + __clap_arg_matches: &mut ArgMatches +) -> Result<Self, Error>

Instantiate Self from [ArgMatches], parsing the arguments as needed. Read more
source§

fn update_from_arg_matches( + &mut self, + __clap_arg_matches: &ArgMatches +) -> Result<(), Error>

Assign values from ArgMatches to self.
source§

fn update_from_arg_matches_mut<'b>( + &mut self, + __clap_arg_matches: &mut ArgMatches +) -> Result<(), Error>

Assign values from ArgMatches to self.
source§

impl Subcommand for Command

source§

fn augment_subcommands<'b>(__clap_app: Command) -> Command

Append to [Command] so it can instantiate Self. Read more
source§

fn augment_subcommands_for_update<'b>(__clap_app: Command) -> Command

Append to [Command] so it can update self. Read more
source§

fn has_subcommand(__clap_name: &str) -> bool

Test whether Self can parse a specific subcommand

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/examples_fxa_client/send_tab/fn.poll.html b/book/rust-docs/examples_fxa_client/send_tab/fn.poll.html new file mode 100644 index 0000000000..a548fd138a --- /dev/null +++ b/book/rust-docs/examples_fxa_client/send_tab/fn.poll.html @@ -0,0 +1 @@ +poll in examples_fxa_client::send_tab - Rust
fn poll(account: &FirefoxAccount) -> Result<()>
\ No newline at end of file diff --git a/book/rust-docs/examples_fxa_client/send_tab/fn.run.html b/book/rust-docs/examples_fxa_client/send_tab/fn.run.html new file mode 100644 index 0000000000..35e6679f5b --- /dev/null +++ b/book/rust-docs/examples_fxa_client/send_tab/fn.run.html @@ -0,0 +1 @@ +run in examples_fxa_client::send_tab - Rust
pub fn run(account: &FirefoxAccount, args: SendTabArgs) -> Result<()>
\ No newline at end of file diff --git a/book/rust-docs/examples_fxa_client/send_tab/fn.send.html b/book/rust-docs/examples_fxa_client/send_tab/fn.send.html new file mode 100644 index 0000000000..a3f9759205 --- /dev/null +++ b/book/rust-docs/examples_fxa_client/send_tab/fn.send.html @@ -0,0 +1,6 @@ +send in examples_fxa_client::send_tab - Rust
fn send(
+    account: &FirefoxAccount,
+    device_id: String,
+    title: String,
+    url: String
+) -> Result<()>
\ No newline at end of file diff --git a/book/rust-docs/examples_fxa_client/send_tab/index.html b/book/rust-docs/examples_fxa_client/send_tab/index.html new file mode 100644 index 0000000000..0cc5d2fd08 --- /dev/null +++ b/book/rust-docs/examples_fxa_client/send_tab/index.html @@ -0,0 +1 @@ +examples_fxa_client::send_tab - Rust
\ No newline at end of file diff --git a/book/rust-docs/examples_fxa_client/send_tab/sidebar-items.js b/book/rust-docs/examples_fxa_client/send_tab/sidebar-items.js new file mode 100644 index 0000000000..783706775d --- /dev/null +++ b/book/rust-docs/examples_fxa_client/send_tab/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["Command"],"fn":["poll","run","send"],"struct":["SendTabArgs"]}; \ No newline at end of file diff --git a/book/rust-docs/examples_fxa_client/send_tab/struct.SendTabArgs.html b/book/rust-docs/examples_fxa_client/send_tab/struct.SendTabArgs.html new file mode 100644 index 0000000000..b9cfd596cd --- /dev/null +++ b/book/rust-docs/examples_fxa_client/send_tab/struct.SendTabArgs.html @@ -0,0 +1,27 @@ +SendTabArgs in examples_fxa_client::send_tab - Rust
pub struct SendTabArgs {
+    command: Command,
+}

Fields§

§command: Command

Trait Implementations§

source§

impl Args for SendTabArgs

source§

fn group_id() -> Option<Id>

Report the [ArgGroup::id][crate::ArgGroup::id] for this set of arguments
source§

fn augment_args<'b>(__clap_app: Command) -> Command

Append to [Command] so it can instantiate Self. Read more
source§

fn augment_args_for_update<'b>(__clap_app: Command) -> Command

Append to [Command] so it can update self. Read more
source§

impl FromArgMatches for SendTabArgs

source§

fn from_arg_matches(__clap_arg_matches: &ArgMatches) -> Result<Self, Error>

Instantiate Self from [ArgMatches], parsing the arguments as needed. Read more
source§

fn from_arg_matches_mut( + __clap_arg_matches: &mut ArgMatches +) -> Result<Self, Error>

Instantiate Self from [ArgMatches], parsing the arguments as needed. Read more
source§

fn update_from_arg_matches( + &mut self, + __clap_arg_matches: &ArgMatches +) -> Result<(), Error>

Assign values from ArgMatches to self.
source§

fn update_from_arg_matches_mut( + &mut self, + __clap_arg_matches: &mut ArgMatches +) -> Result<(), Error>

Assign values from ArgMatches to self.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/examples_fxa_client/sidebar-items.js b/book/rust-docs/examples_fxa_client/sidebar-items.js new file mode 100644 index 0000000000..d09db47d18 --- /dev/null +++ b/book/rust-docs/examples_fxa_client/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["Command","Server"],"fn":["load_account","main","persist_fxa_state"],"mod":["devices","send_tab"],"static":["CLIENT_ID","CREDENTIALS_PATH","REDIRECT_URI"],"struct":["Cli"]}; \ No newline at end of file diff --git a/book/rust-docs/examples_fxa_client/static.CLIENT_ID.html b/book/rust-docs/examples_fxa_client/static.CLIENT_ID.html new file mode 100644 index 0000000000..dc3dcbc926 --- /dev/null +++ b/book/rust-docs/examples_fxa_client/static.CLIENT_ID.html @@ -0,0 +1 @@ +CLIENT_ID in examples_fxa_client - Rust
pub(crate) static CLIENT_ID: &str
\ No newline at end of file diff --git a/book/rust-docs/examples_fxa_client/static.CREDENTIALS_PATH.html b/book/rust-docs/examples_fxa_client/static.CREDENTIALS_PATH.html new file mode 100644 index 0000000000..60c4ad4d0d --- /dev/null +++ b/book/rust-docs/examples_fxa_client/static.CREDENTIALS_PATH.html @@ -0,0 +1 @@ +CREDENTIALS_PATH in examples_fxa_client - Rust
pub(crate) static CREDENTIALS_PATH: &str
\ No newline at end of file diff --git a/book/rust-docs/examples_fxa_client/static.REDIRECT_URI.html b/book/rust-docs/examples_fxa_client/static.REDIRECT_URI.html new file mode 100644 index 0000000000..b3e2cd385f --- /dev/null +++ b/book/rust-docs/examples_fxa_client/static.REDIRECT_URI.html @@ -0,0 +1 @@ +REDIRECT_URI in examples_fxa_client - Rust
pub(crate) static REDIRECT_URI: &str
\ No newline at end of file diff --git a/book/rust-docs/examples_fxa_client/struct.Cli.html b/book/rust-docs/examples_fxa_client/struct.Cli.html new file mode 100644 index 0000000000..ff113d9c11 --- /dev/null +++ b/book/rust-docs/examples_fxa_client/struct.Cli.html @@ -0,0 +1,37 @@ +Cli in examples_fxa_client - Rust
pub(crate) struct Cli {
+    pub(crate) server: Server,
+    pub(crate) command: Command,
+}

Fields§

§server: Server

The FxA server to use

+
§command: Command

Trait Implementations§

source§

impl Args for Cli

source§

fn group_id() -> Option<Id>

Report the [ArgGroup::id][crate::ArgGroup::id] for this set of arguments
source§

fn augment_args<'b>(__clap_app: Command) -> Command

Append to [Command] so it can instantiate Self. Read more
source§

fn augment_args_for_update<'b>(__clap_app: Command) -> Command

Append to [Command] so it can update self. Read more
source§

impl CommandFactory for Cli

source§

fn command<'b>() -> Command

Build a [Command] that can instantiate Self. Read more
source§

fn command_for_update<'b>() -> Command

Build a [Command] that can update self. Read more
source§

impl FromArgMatches for Cli

source§

fn from_arg_matches(__clap_arg_matches: &ArgMatches) -> Result<Self, Error>

Instantiate Self from [ArgMatches], parsing the arguments as needed. Read more
source§

fn from_arg_matches_mut( + __clap_arg_matches: &mut ArgMatches +) -> Result<Self, Error>

Instantiate Self from [ArgMatches], parsing the arguments as needed. Read more
source§

fn update_from_arg_matches( + &mut self, + __clap_arg_matches: &ArgMatches +) -> Result<(), Error>

Assign values from ArgMatches to self.
source§

fn update_from_arg_matches_mut( + &mut self, + __clap_arg_matches: &mut ArgMatches +) -> Result<(), Error>

Assign values from ArgMatches to self.
source§

impl Parser for Cli

§

fn parse() -> Self

Parse from std::env::args_os(), exit on error
§

fn try_parse() -> Result<Self, Error<RichFormatter>>

Parse from std::env::args_os(), return Err on error.
§

fn parse_from<I, T>(itr: I) -> Selfwhere + I: IntoIterator<Item = T>, + T: Into<OsString> + Clone,

Parse from iterator, exit on error
§

fn try_parse_from<I, T>(itr: I) -> Result<Self, Error<RichFormatter>>where + I: IntoIterator<Item = T>, + T: Into<OsString> + Clone,

Parse from iterator, return Err on error.
§

fn update_from<I, T>(&mut self, itr: I)where + I: IntoIterator<Item = T>, + T: Into<OsString> + Clone,

Update from iterator, exit on error
§

fn try_update_from<I, T>(&mut self, itr: I) -> Result<(), Error<RichFormatter>>where + I: IntoIterator<Item = T>, + T: Into<OsString> + Clone,

Update from iterator, return Err on error.

Auto Trait Implementations§

§

impl RefUnwindSafe for Cli

§

impl Send for Cli

§

impl Sync for Cli

§

impl Unpin for Cli

§

impl UnwindSafe for Cli

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/fxa_client/all.html b/book/rust-docs/fxa_client/all.html new file mode 100644 index 0000000000..0886d09f20 --- /dev/null +++ b/book/rust-docs/fxa_client/all.html @@ -0,0 +1 @@ +List of all items in this crate
\ No newline at end of file diff --git a/book/rust-docs/fxa_client/auth/enum.FxaRustAuthState.html b/book/rust-docs/fxa_client/auth/enum.FxaRustAuthState.html new file mode 100644 index 0000000000..f6eb93ac7e --- /dev/null +++ b/book/rust-docs/fxa_client/auth/enum.FxaRustAuthState.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../fxa_client/enum.FxaRustAuthState.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/fxa_client/auth/struct.AuthorizationInfo.html b/book/rust-docs/fxa_client/auth/struct.AuthorizationInfo.html new file mode 100644 index 0000000000..1c84129217 --- /dev/null +++ b/book/rust-docs/fxa_client/auth/struct.AuthorizationInfo.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../fxa_client/struct.AuthorizationInfo.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/fxa_client/auth/struct.MetricsParams.html b/book/rust-docs/fxa_client/auth/struct.MetricsParams.html new file mode 100644 index 0000000000..48091fd216 --- /dev/null +++ b/book/rust-docs/fxa_client/auth/struct.MetricsParams.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../fxa_client/struct.MetricsParams.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/fxa_client/device/enum.DeviceCapability.html b/book/rust-docs/fxa_client/device/enum.DeviceCapability.html new file mode 100644 index 0000000000..410b0f8462 --- /dev/null +++ b/book/rust-docs/fxa_client/device/enum.DeviceCapability.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../fxa_client/enum.DeviceCapability.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/fxa_client/device/struct.AttachedClient.html b/book/rust-docs/fxa_client/device/struct.AttachedClient.html new file mode 100644 index 0000000000..8597aab9cd --- /dev/null +++ b/book/rust-docs/fxa_client/device/struct.AttachedClient.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../fxa_client/struct.AttachedClient.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/fxa_client/device/struct.Device.html b/book/rust-docs/fxa_client/device/struct.Device.html new file mode 100644 index 0000000000..9e58f80769 --- /dev/null +++ b/book/rust-docs/fxa_client/device/struct.Device.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../fxa_client/struct.Device.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/fxa_client/device/struct.LocalDevice.html b/book/rust-docs/fxa_client/device/struct.LocalDevice.html new file mode 100644 index 0000000000..375fe0ae4c --- /dev/null +++ b/book/rust-docs/fxa_client/device/struct.LocalDevice.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../fxa_client/struct.LocalDevice.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/fxa_client/enum.AccountEvent.html b/book/rust-docs/fxa_client/enum.AccountEvent.html new file mode 100644 index 0000000000..3fb1dbb145 --- /dev/null +++ b/book/rust-docs/fxa_client/enum.AccountEvent.html @@ -0,0 +1,55 @@ +AccountEvent in fxa_client - Rust
pub enum AccountEvent {
+    CommandReceived {
+        command: IncomingDeviceCommand,
+    },
+    ProfileUpdated,
+    AccountAuthStateChanged,
+    AccountDestroyed,
+    DeviceConnected {
+        device_name: String,
+    },
+    DeviceDisconnected {
+        device_id: String,
+        is_local_device: bool,
+    },
+    Unknown,
+}
Expand description

An event that happened on the user’s account.

+

If the application has registered a DevicePushSubscription as part of its +device record, then the Firefox Accounts server can send push notifications +about important events that happen on the user’s account. This enum represents +the different kinds of event that can occur.

+

Variants§

§

CommandReceived

Sent when another device has invoked a command for this device to execute.

+

When receiving this event, the application should inspect the contained +command and react appropriately.

+
§

ProfileUpdated

Sent when the user has modified their account profile information.

+

When receiving this event, the application should request fresh profile +information by calling get_profile with +ignore_cache set to true, and update any profile information displayed +in its UI.

+
§

AccountAuthStateChanged

Sent when when there has been a change in authorization status.

+

When receiving this event, the application should check whether it is +still connected to the user’s account by calling check_authorization_status, and updating its UI as appropriate.

+
§

AccountDestroyed

Sent when the user deletes their Firefox Account.

+

When receiving this event, the application should act as though the user had +signed out, discarding any persisted account state.

+
§

DeviceConnected

Fields

§device_name: String

Sent when a new device connects to the user’s account.

+

When receiving this event, the application may use it to trigger an update +of any UI that shows the list of connected devices. It may also show the +user an informational notice about the new device, as a security measure.

+
§

DeviceDisconnected

Fields

§device_id: String
§is_local_device: bool

Sent when a device disconnects from the user’s account.

+

When receiving this event, the application may use it to trigger an update +of any UI that shows the list of connected devices.

+
§

Unknown

An unknown event, most likely an event the client doesn’t support yet.

+

When receiving this event, the application should gracefully ignore it.

+

Trait Implementations§

source§

impl Debug for AccountEvent

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/fxa_client/enum.DeviceCapability.html b/book/rust-docs/fxa_client/enum.DeviceCapability.html new file mode 100644 index 0000000000..af6331cc90 --- /dev/null +++ b/book/rust-docs/fxa_client/enum.DeviceCapability.html @@ -0,0 +1,32 @@ +DeviceCapability in fxa_client - Rust
pub enum DeviceCapability {
+    SendTab,
+}
Expand description

A “capability” offered by a device.

+

In the FxA ecosystem, connected devices may advertise their ability to respond +to various “commands” that can be invoked by other devices. The details of +executing these commands are encapsulated as part of the FxA Client component, +so consumers simply need to select which ones they want to support, and can +use the variants of this enum to do so.

+

In practice, the only currently-supported command is the ability to receive a tab.

+

Variants§

§

SendTab

Trait Implementations§

source§

impl Clone for DeviceCapability

source§

fn clone(&self) -> DeviceCapability

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for DeviceCapability

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl<'de> Deserialize<'de> for DeviceCapability

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl Hash for DeviceCapability

source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · source§

fn hash_slice<H>(data: &[Self], state: &mut H)where + H: Hasher, + Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
source§

impl PartialEq<DeviceCapability> for DeviceCapability

source§

fn eq(&self, other: &DeviceCapability) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Serialize for DeviceCapability

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more
source§

impl TryFrom<String> for DeviceCapability

§

type Error = Error

The type returned in the event of a conversion error.
source§

fn try_from(command: String) -> Result<Self>

Performs the conversion.
source§

impl Eq for DeviceCapability

source§

impl StructuralEq for DeviceCapability

source§

impl StructuralPartialEq for DeviceCapability

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> CallHasher for Twhere + T: Hash + ?Sized,

§

fn get_hash<H, B>(value: &H, build_hasher: &B) -> u64where + H: Hash + ?Sized, + B: BuildHasher,

source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/fxa_client/enum.DeviceType.html b/book/rust-docs/fxa_client/enum.DeviceType.html new file mode 100644 index 0000000000..0f27beaa9d --- /dev/null +++ b/book/rust-docs/fxa_client/enum.DeviceType.html @@ -0,0 +1,59 @@ +DeviceType in fxa_client - Rust
pub enum DeviceType {
+    Desktop,
+    Mobile,
+    Tablet,
+    VR,
+    TV,
+    Unknown,
+}
Expand description

Enumeration for the different types of device.

+

Firefox Accounts and the broader Sync universe separates devices into broad categories for +various purposes, such as distinguishing a desktop PC from a mobile phone.

+

A special variant in this enum, DeviceType::Unknown is used to capture +the string values we don’t recognise. It also has a custom serde serializer and deserializer +which implements the following semantics:

+
    +
  • deserializing a DeviceType which uses a string value we don’t recognise or null will return +DeviceType::Unknown rather than returning an error.
  • +
  • serializing DeviceType::Unknown will serialize null.
  • +
+

This has a few important implications:

+
    +
  • In general, Option<DeviceType> should be avoided, and a plain DeviceType used instead, +because in that case, None would be semantically identical to DeviceType::Unknown and +as mentioned above, null already deserializes as DeviceType::Unknown.
  • +
  • Any unknown device types can not be round-tripped via this enum - eg, if you deserialize +a struct holding a DeviceType string value we don’t recognize, then re-serialize it, the +original string value is lost. We don’t consider this a problem because in practice, we only +upload records with this device’s type, not the type of other devices, and it’s reasonable +to assume that this module knows about all valid device types for the device type it is +deployed on.
  • +
+

Variants§

§

Desktop

§

Mobile

§

Tablet

§

VR

§

TV

§

Unknown

Trait Implementations§

§

impl Clone for DeviceType

§

fn clone(&self) -> DeviceType

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
§

impl Debug for DeviceType

§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
§

impl Default for DeviceType

§

fn default() -> DeviceType

Returns the “default value” for a type. Read more
§

impl<'de> Deserialize<'de> for DeviceType

§

fn deserialize<D>( + deserializer: D +) -> Result<DeviceType, <D as Deserializer<'de>>::Error>where + D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
§

impl Hash for DeviceType

§

fn hash<__H>(&self, state: &mut __H)where + __H: Hasher,

Feeds this value into the given Hasher. Read more
1.3.0 · source§

fn hash_slice<H>(data: &[Self], state: &mut H)where + H: Hasher, + Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
§

impl PartialEq<DeviceType> for DeviceType

§

fn eq(&self, other: &DeviceType) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
§

impl Serialize for DeviceType

§

fn serialize<S>( + &self, + s: S +) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>where + S: Serializer,

Serialize this value into the given Serde serializer. Read more
§

impl Copy for DeviceType

§

impl Eq for DeviceType

§

impl StructuralEq for DeviceType

§

impl StructuralPartialEq for DeviceType

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> CallHasher for Twhere + T: Hash + ?Sized,

§

fn get_hash<H, B>(value: &H, build_hasher: &B) -> u64where + H: Hash + ?Sized, + B: BuildHasher,

source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/fxa_client/enum.Error.html b/book/rust-docs/fxa_client/enum.Error.html new file mode 100644 index 0000000000..fbc7db8c4e --- /dev/null +++ b/book/rust-docs/fxa_client/enum.Error.html @@ -0,0 +1,64 @@ +Error in fxa_client - Rust

Enum fxa_client::Error

source ·
pub enum Error {
+
Show 40 variants BackoffError(u64), + UnknownOAuthState, + MultipleScopesRequested, + NoCachedToken(String), + NoScopedKey(String), + NoRefreshToken, + NoSessionToken, + NoMigrationData, + NoCurrentDeviceId, + UnknownTargetDevice(String), + ApiClientError(&'static str), + IllegalState(&'static str), + UnknownCommand(String), + SendTabDiagnosisError(&'static str), + XorLengthMismatch(usize, usize), + OriginMismatch, + MismatchedKeys, + SyncScopedKeyMissingInServerResponse, + ScopeNotAllowed(String, String), + UnsupportedCommand(&'static str), + MissingUrlParameter(&'static str), + NullPointer, + InvalidBufferLength(i32), + AuthCircuitBreakerError, + RemoteError { + code: u64, + errno: u64, + error: String, + message: String, + info: String, + }, + CryptoError(Error), + EceError(Error), + HexDecodeError(FromHexError), + Base64Decode(DecodeError), + JsonError(Error), + JwCryptoError(JwCryptoError), + UTF8DecodeError(FromUtf8Error), + RequestError(Error), + MalformedUrl(ParseError), + UnexpectedStatus(UnexpectedStatus), + SyncError(Error), + HawkError(Error), + IntegerConversionError(TryFromIntError), + CommandNotFound, + InvalidPushEvent, +
}
Expand description

FxA internal error type +These are used in the internal code. This error type is never returned to the consumer.

+

Variants§

§

BackoffError(u64)

§

UnknownOAuthState

§

MultipleScopesRequested

§

NoCachedToken(String)

§

NoScopedKey(String)

§

NoRefreshToken

§

NoSessionToken

§

NoMigrationData

§

NoCurrentDeviceId

§

UnknownTargetDevice(String)

§

ApiClientError(&'static str)

§

IllegalState(&'static str)

§

UnknownCommand(String)

§

SendTabDiagnosisError(&'static str)

§

XorLengthMismatch(usize, usize)

§

OriginMismatch

§

MismatchedKeys

§

SyncScopedKeyMissingInServerResponse

§

ScopeNotAllowed(String, String)

§

UnsupportedCommand(&'static str)

§

MissingUrlParameter(&'static str)

§

NullPointer

§

InvalidBufferLength(i32)

§

AuthCircuitBreakerError

§

RemoteError

Fields

§code: u64
§errno: u64
§error: String
§message: String
§info: String
§

CryptoError(Error)

§

EceError(Error)

§

HexDecodeError(FromHexError)

§

Base64Decode(DecodeError)

§

JsonError(Error)

§

JwCryptoError(JwCryptoError)

§

UTF8DecodeError(FromUtf8Error)

§

RequestError(Error)

§

MalformedUrl(ParseError)

§

UnexpectedStatus(UnexpectedStatus)

§

SyncError(Error)

§

HawkError(Error)

§

IntegerConversionError(TryFromIntError)

§

CommandNotFound

§

InvalidPushEvent

Trait Implementations§

source§

impl Debug for Error

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Display for Error

source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Error for Error

source§

fn source(&self) -> Option<&(dyn Error + 'static)>

The lower-level source of this error, if any. Read more
1.0.0 · source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type based access to context intended for error reports. Read more
source§

impl From<DecodeError> for Error

source§

fn from(source: DecodeError) -> Self

Converts to this type from the input type.
source§

impl From<Error> for Error

source§

fn from(source: Error) -> Self

Converts to this type from the input type.
source§

impl From<Error> for Error

source§

fn from(source: Error) -> Self

Converts to this type from the input type.
source§

impl From<Error> for Error

source§

fn from(source: Error) -> Self

Converts to this type from the input type.
source§

impl From<Error> for Error

source§

fn from(source: Error) -> Self

Converts to this type from the input type.
source§

impl From<Error> for Error

source§

fn from(source: Error) -> Self

Converts to this type from the input type.
source§

impl From<Error> for Error

source§

fn from(source: Error) -> Self

Converts to this type from the input type.
source§

impl From<FromHexError> for Error

source§

fn from(source: FromHexError) -> Self

Converts to this type from the input type.
source§

impl From<FromUtf8Error> for Error

source§

fn from(source: FromUtf8Error) -> Self

Converts to this type from the input type.
source§

impl From<JwCryptoError> for Error

source§

fn from(source: JwCryptoError) -> Self

Converts to this type from the input type.
source§

impl From<ParseError> for Error

source§

fn from(source: ParseError) -> Self

Converts to this type from the input type.
source§

impl From<TryFromIntError> for Error

source§

fn from(source: TryFromIntError) -> Self

Converts to this type from the input type.
source§

impl From<UnexpectedStatus> for Error

source§

fn from(source: UnexpectedStatus) -> Self

Converts to this type from the input type.
source§

impl GetErrorHandling for Error

§

type ExternalError = FxaError

source§

fn get_error_handling(&self) -> ErrorHandling<Self::ExternalError>

Return how to handle our internal errors

Auto Trait Implementations§

§

impl !RefUnwindSafe for Error

§

impl Send for Error

§

impl Sync for Error

§

impl Unpin for Error

§

impl !UnwindSafe for Error

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<E> Provider for Ewhere + E: Error + ?Sized,

source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (provide_any)
Data providers should implement this method to provide all values they are able to +provide by using demand. Read more
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/fxa_client/enum.FxaError.html b/book/rust-docs/fxa_client/enum.FxaError.html new file mode 100644 index 0000000000..819ed80edf --- /dev/null +++ b/book/rust-docs/fxa_client/enum.FxaError.html @@ -0,0 +1,45 @@ +FxaError in fxa_client - Rust

Enum fxa_client::FxaError

source ·
pub enum FxaError {
+    Authentication,
+    Network,
+    NoExistingAuthFlow,
+    WrongAuthFlow,
+    SyncScopedKeyMissingInServerResponse,
+    Panic,
+    Other,
+}
Expand description

Public error type thrown by many [FirefoxAccount] operations.

+

Precise details of the error are hidden from consumers. The type of the error indicates how the +calling code should respond.

+

Variants§

§

Authentication

Thrown when there was a problem with the authentication status of the account, +such as an expired token. The application should check its authorization status to see whether it has been disconnected, +or retry the operation with a freshly-generated token.

+
§

Network

Thrown if an operation fails due to network access problems. +The application may retry at a later time once connectivity is restored.

+
§

NoExistingAuthFlow

Thrown if the application attempts to complete an OAuth flow when no OAuth flow +has been initiated. This may indicate a user who navigated directly to the OAuth +redirect_uri for the application.

+

Note: This error is currently only thrown in the Swift language bindings.

+
§

WrongAuthFlow

Thrown if the application attempts to complete an OAuth flow, but the state +tokens returned from the Firefox Account server do not match with the ones +expected by the client. +This may indicate a stale OAuth flow, or potentially an attempted hijacking +of the flow by an attacker. The signin attempt cannot be completed.

+

Note: This error is currently only thrown in the Swift language bindings.

+
§

SyncScopedKeyMissingInServerResponse

A scoped key was missing in the server response when requesting the OLD_SYNC scope.

+
§

Panic

Thrown if there is a panic in the underlying Rust code.

+

Note: This error is currently only thrown in the Kotlin language bindings.

+
§

Other

A catch-all for other unspecified errors.

+

Trait Implementations§

source§

impl Debug for FxaError

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Display for FxaError

source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Error for FxaError

1.30.0 · source§

fn source(&self) -> Option<&(dyn Error + 'static)>

The lower-level source of this error, if any. Read more
1.0.0 · source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type based access to context intended for error reports. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<E> Provider for Ewhere + E: Error + ?Sized,

source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (provide_any)
Data providers should implement this method to provide all values they are able to +provide by using demand. Read more
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/fxa_client/enum.FxaRustAuthState.html b/book/rust-docs/fxa_client/enum.FxaRustAuthState.html new file mode 100644 index 0000000000..2dff3d8d78 --- /dev/null +++ b/book/rust-docs/fxa_client/enum.FxaRustAuthState.html @@ -0,0 +1,24 @@ +FxaRustAuthState in fxa_client - Rust
pub enum FxaRustAuthState {
+    Disconnected,
+    Connected,
+    AuthIssues,
+}
Expand description

High-level view of the authorization state

+

This is named FxaRustAuthState because it doesn’t track all the states we want yet and needs +help from the wrapper code. The wrapper code defines the actual FxaAuthState type based on +this, adding the extra data.

+

In the long-term, we should track that data in Rust, remove the wrapper, and rename this to +FxaAuthState.

+

Variants§

§

Disconnected

§

Connected

§

AuthIssues

Trait Implementations§

source§

impl Debug for FxaRustAuthState

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl PartialEq<FxaRustAuthState> for FxaRustAuthState

source§

fn eq(&self, other: &FxaRustAuthState) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Eq for FxaRustAuthState

source§

impl StructuralEq for FxaRustAuthState

source§

impl StructuralPartialEq for FxaRustAuthState

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/fxa_client/enum.FxaServer.html b/book/rust-docs/fxa_client/enum.FxaServer.html new file mode 100644 index 0000000000..946fcb11f6 --- /dev/null +++ b/book/rust-docs/fxa_client/enum.FxaServer.html @@ -0,0 +1,22 @@ +FxaServer in fxa_client - Rust
pub enum FxaServer {
+    Release,
+    Stable,
+    Stage,
+    China,
+    LocalDev,
+    Custom {
+        url: String,
+    },
+}

Variants§

§

Release

§

Stable

§

Stage

§

China

§

LocalDev

§

Custom

Fields

Trait Implementations§

source§

impl Clone for FxaServer

source§

fn clone(&self) -> FxaServer

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for FxaServer

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/fxa_client/enum.IncomingDeviceCommand.html b/book/rust-docs/fxa_client/enum.IncomingDeviceCommand.html new file mode 100644 index 0000000000..1483aa9ce5 --- /dev/null +++ b/book/rust-docs/fxa_client/enum.IncomingDeviceCommand.html @@ -0,0 +1,22 @@ +IncomingDeviceCommand in fxa_client - Rust
pub enum IncomingDeviceCommand {
+    TabReceived {
+        sender: Option<Device>,
+        payload: SendTabPayload,
+    },
+}
Expand description

A command invoked by another device.

+

This enum represents all possible commands that can be invoked on +the device. It is the responsibility of the application to interpret +each command.

+

Variants§

§

TabReceived

Fields

§sender: Option<Device>

Indicates that a tab has been sent to this device.

+

Trait Implementations§

source§

impl Debug for IncomingDeviceCommand

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/fxa_client/error/enum.Error.html b/book/rust-docs/fxa_client/error/enum.Error.html new file mode 100644 index 0000000000..bad0c51725 --- /dev/null +++ b/book/rust-docs/fxa_client/error/enum.Error.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../fxa_client/enum.Error.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/fxa_client/error/enum.FxaError.html b/book/rust-docs/fxa_client/error/enum.FxaError.html new file mode 100644 index 0000000000..726b27ae10 --- /dev/null +++ b/book/rust-docs/fxa_client/error/enum.FxaError.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../fxa_client/enum.FxaError.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/fxa_client/index.html b/book/rust-docs/fxa_client/index.html new file mode 100644 index 0000000000..36382ed12d --- /dev/null +++ b/book/rust-docs/fxa_client/index.html @@ -0,0 +1,44 @@ +fxa_client - Rust

Crate fxa_client

source ·
Expand description

Firefox Accounts Client

+

The fxa-client component lets applications integrate with the +Firefox Accounts +identity service. The shape of a typical integration would look +something like:

+
    +
  • +

    Out-of-band, register your application with the Firefox Accounts service, +providing an OAuth redirect_uri controlled by your application and +obtaining an OAuth client_id.

    +
  • +
  • +

    On application startup, create a FirefoxAccount object to represent the +signed-in state of the application.

    + +
  • +
  • +

    When the user wants to sign in to your application, direct them through +a web-based OAuth flow using begin_oauth_flow +or begin_pairing_flow; when they return +to your registered redirect_uri, pass the resulting authorization state back to +complete_oauth_flow to sign them in.

    +
  • +
  • +

    Display information about the signed-in user by using the data from +get_profile.

    +
  • +
  • +

    Access account-related services on behalf of the user by obtaining OAuth +access tokens via get_access_token.

    +
  • +
  • +

    If the user opts to sign out of the application, calling disconnect +and then discarding any persisted account data.

    +
  • +
+

Structs

Enums

Type Definitions

  • Result returned by public-facing API functions
  • Result returned by internal functions
\ No newline at end of file diff --git a/book/rust-docs/fxa_client/profile/struct.Profile.html b/book/rust-docs/fxa_client/profile/struct.Profile.html new file mode 100644 index 0000000000..ca27d23f9d --- /dev/null +++ b/book/rust-docs/fxa_client/profile/struct.Profile.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../fxa_client/struct.Profile.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/fxa_client/push/enum.AccountEvent.html b/book/rust-docs/fxa_client/push/enum.AccountEvent.html new file mode 100644 index 0000000000..e17114de4e --- /dev/null +++ b/book/rust-docs/fxa_client/push/enum.AccountEvent.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../fxa_client/enum.AccountEvent.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/fxa_client/push/enum.IncomingDeviceCommand.html b/book/rust-docs/fxa_client/push/enum.IncomingDeviceCommand.html new file mode 100644 index 0000000000..fd989b83a1 --- /dev/null +++ b/book/rust-docs/fxa_client/push/enum.IncomingDeviceCommand.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../fxa_client/enum.IncomingDeviceCommand.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/fxa_client/push/struct.DevicePushSubscription.html b/book/rust-docs/fxa_client/push/struct.DevicePushSubscription.html new file mode 100644 index 0000000000..e3eb477a70 --- /dev/null +++ b/book/rust-docs/fxa_client/push/struct.DevicePushSubscription.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../fxa_client/struct.DevicePushSubscription.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/fxa_client/push/struct.SendTabPayload.html b/book/rust-docs/fxa_client/push/struct.SendTabPayload.html new file mode 100644 index 0000000000..784f547949 --- /dev/null +++ b/book/rust-docs/fxa_client/push/struct.SendTabPayload.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../fxa_client/struct.SendTabPayload.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/fxa_client/push/struct.TabHistoryEntry.html b/book/rust-docs/fxa_client/push/struct.TabHistoryEntry.html new file mode 100644 index 0000000000..31303ee966 --- /dev/null +++ b/book/rust-docs/fxa_client/push/struct.TabHistoryEntry.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../fxa_client/struct.TabHistoryEntry.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/fxa_client/sidebar-items.js b/book/rust-docs/fxa_client/sidebar-items.js new file mode 100644 index 0000000000..271aa3ba88 --- /dev/null +++ b/book/rust-docs/fxa_client/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["AccountEvent","DeviceCapability","DeviceType","Error","FxaError","FxaRustAuthState","FxaServer","IncomingDeviceCommand"],"struct":["AccessTokenInfo","AttachedClient","AuthorizationInfo","AuthorizationParameters","Device","DevicePushSubscription","FirefoxAccount","FxaConfig","LocalDevice","MetricsParams","Profile","ScopedKey","SendTabPayload","TabHistoryEntry"],"type":["ApiResult","Result"]}; \ No newline at end of file diff --git a/book/rust-docs/fxa_client/struct.AccessTokenInfo.html b/book/rust-docs/fxa_client/struct.AccessTokenInfo.html new file mode 100644 index 0000000000..032ce26d8e --- /dev/null +++ b/book/rust-docs/fxa_client/struct.AccessTokenInfo.html @@ -0,0 +1,36 @@ +AccessTokenInfo in fxa_client - Rust
pub struct AccessTokenInfo {
+    pub scope: String,
+    pub token: String,
+    pub key: Option<ScopedKey>,
+    pub expires_at: i64,
+}
Expand description

An OAuth access token, with its associated keys and metadata.

+

This struct represents an FxA OAuth access token, which can be used to access a resource +or service on behalf of the user. For example, accessing the user’s data in Firefox Sync +an access token for the scope https://identity.mozilla.com/apps/sync along with the +associated encryption key.

+

Fields§

§scope: String

The scope of access granted by token.

+
§token: String

The access token itself.

+

This is the value that should be included in the Authorization header when +accessing an OAuth protected resource on behalf of the user.

+
§key: Option<ScopedKey>

The client-side encryption key associated with this scope.

+

⚠️ Warning: the value of this field should never be revealed outside of the +application. For example, it should never to sent to a server or logged in a log file.

+
§expires_at: i64

The expiry time of the token, in seconds.

+

This is the timestamp at which the token is set to expire, in seconds since +unix epoch. Note that it is a signed integer, for compatibility with languages +that do not have an unsigned integer type.

+

This timestamp is for guidance only. Access tokens are not guaranteed to remain +value for any particular lengthof time, and consumers should be prepared to handle +auth failures even if the token has not yet expired.

+

Trait Implementations§

source§

impl Debug for AccessTokenInfo

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/fxa_client/struct.AttachedClient.html b/book/rust-docs/fxa_client/struct.AttachedClient.html new file mode 100644 index 0000000000..533ea39c39 --- /dev/null +++ b/book/rust-docs/fxa_client/struct.AttachedClient.html @@ -0,0 +1,28 @@ +AttachedClient in fxa_client - Rust
pub struct AttachedClient {
+    pub client_id: Option<String>,
+    pub device_id: Option<String>,
+    pub device_type: DeviceType,
+    pub is_current_session: bool,
+    pub name: Option<String>,
+    pub created_time: Option<i64>,
+    pub last_access_time: Option<i64>,
+    pub scope: Option<Vec<String>>,
+}
Expand description

A client connected to the user’s account.

+

This struct provides metadata about a client connected to the user’s account. +Unlike the Device struct, “clients” encompasses both client-side and server-side +applications - basically anything where the user is able to sign in with their +Firefox Account.

+

This data would typically be used for targeted messaging purposes, catering the +contents of the message to what other applications the user has on their account.

+

Fields§

§client_id: Option<String>§device_id: Option<String>§device_type: DeviceType§is_current_session: bool§name: Option<String>§created_time: Option<i64>§last_access_time: Option<i64>§scope: Option<Vec<String>>

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/fxa_client/struct.AuthorizationInfo.html b/book/rust-docs/fxa_client/struct.AuthorizationInfo.html new file mode 100644 index 0000000000..3e5b2a2cf1 --- /dev/null +++ b/book/rust-docs/fxa_client/struct.AuthorizationInfo.html @@ -0,0 +1,17 @@ +AuthorizationInfo in fxa_client - Rust
pub struct AuthorizationInfo {
+    pub active: bool,
+}
Expand description

Information about the authorization state of the application.

+

This struct represents metadata about whether the application is currently +connected to the user’s account.

+

Fields§

§active: bool

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/fxa_client/struct.AuthorizationParameters.html b/book/rust-docs/fxa_client/struct.AuthorizationParameters.html new file mode 100644 index 0000000000..4900d439aa --- /dev/null +++ b/book/rust-docs/fxa_client/struct.AuthorizationParameters.html @@ -0,0 +1,24 @@ +AuthorizationParameters in fxa_client - Rust
pub struct AuthorizationParameters {
+    pub client_id: String,
+    pub scope: Vec<String>,
+    pub state: String,
+    pub access_type: String,
+    pub code_challenge: Option<String>,
+    pub code_challenge_method: Option<String>,
+    pub keys_jwk: Option<String>,
+}
Expand description

Parameters provided in an incoming OAuth request.

+

This struct represents parameters obtained from an incoming OAuth request - that is, +the values that an OAuth client would append to the authorization URL when initiating +an OAuth sign-in flow.

+

Fields§

§client_id: String§scope: Vec<String>§state: String§access_type: String§code_challenge: Option<String>§code_challenge_method: Option<String>§keys_jwk: Option<String>

Trait Implementations§

source§

impl TryFrom<Url> for AuthorizationParameters

§

type Error = Error

The type returned in the event of a conversion error.
source§

fn try_from(url: Url) -> Result<Self>

Performs the conversion.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/fxa_client/struct.Device.html b/book/rust-docs/fxa_client/struct.Device.html new file mode 100644 index 0000000000..b685ed0a2e --- /dev/null +++ b/book/rust-docs/fxa_client/struct.Device.html @@ -0,0 +1,25 @@ +Device in fxa_client - Rust

Struct fxa_client::Device

source ·
pub struct Device {
+    pub id: String,
+    pub display_name: String,
+    pub device_type: DeviceType,
+    pub capabilities: Vec<DeviceCapability>,
+    pub push_subscription: Option<DevicePushSubscription>,
+    pub push_endpoint_expired: bool,
+    pub is_current_device: bool,
+    pub last_access_time: Option<i64>,
+}
Expand description

A device connected to the user’s account.

+

This struct provides metadata about a device connected to the user’s account. +This data would typically be used to display e.g. the list of candidate devices +in a “send tab” menu.

+

Fields§

§id: String§display_name: String§device_type: DeviceType§capabilities: Vec<DeviceCapability>§push_subscription: Option<DevicePushSubscription>§push_endpoint_expired: bool§is_current_device: bool§last_access_time: Option<i64>

Trait Implementations§

source§

impl Debug for Device

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/fxa_client/struct.DevicePushSubscription.html b/book/rust-docs/fxa_client/struct.DevicePushSubscription.html new file mode 100644 index 0000000000..e315632561 --- /dev/null +++ b/book/rust-docs/fxa_client/struct.DevicePushSubscription.html @@ -0,0 +1,26 @@ +DevicePushSubscription in fxa_client - Rust
pub struct DevicePushSubscription {
+    pub endpoint: String,
+    pub public_key: String,
+    pub auth_key: String,
+}
Expand description

Details of a web-push subscription endpoint.

+

This struct encapsulates the details of a web-push subscription endpoint, +including all the information necessary to send a notification to its owner. +Devices attached to the user’s account may register one of these in order +to receive timely updates about account-related events.

+

Managing a web-push subscription is outside of the scope of this component.

+

Fields§

§endpoint: String§public_key: String§auth_key: String

Trait Implementations§

source§

impl Clone for DevicePushSubscription

source§

fn clone(&self) -> DevicePushSubscription

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for DevicePushSubscription

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl<'de> Deserialize<'de> for DevicePushSubscription

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl Serialize for DevicePushSubscription

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/fxa_client/struct.FirefoxAccount.html b/book/rust-docs/fxa_client/struct.FirefoxAccount.html new file mode 100644 index 0000000000..d393ba7716 --- /dev/null +++ b/book/rust-docs/fxa_client/struct.FirefoxAccount.html @@ -0,0 +1,441 @@ +FirefoxAccount in fxa_client - Rust
pub struct FirefoxAccount { /* private fields */ }
Expand description

Object representing the signed-in state of an application.

+

The FirefoxAccount object is the main interface provided by this crate. +It represents the signed-in state of an application that may be connected to +user’s Firefox Account, and provides methods for inspecting the state of the +account and accessing other services on behalf of the user.

+

Implementations§

source§

impl FirefoxAccount

source

pub fn get_token_server_endpoint_url(&self) -> ApiResult<String>

Get the token server URL

+

The token server URL can be used to get the URL and access token for the user’s sync data.

+

💾 This method alters the persisted account state.

+
source

pub fn get_connection_success_url(&self) -> ApiResult<String>

Get a URL which shows a “successfully connected!” message.

+

💾 This method alters the persisted account state.

+

Applications can use this method after a successful signin, to redirect the +user to a success message displayed in web content rather than having to +implement their own native success UI.

+
source

pub fn get_manage_account_url(&self, entrypoint: &str) -> ApiResult<String>

Get a URL at which the user can manage their account and profile data.

+

💾 This method alters the persisted account state.

+

Applications should link the user out to this URL from an appropriate place +in their signed-in settings UI.

+
Arguments
+
    +
  • entrypoint - metrics identifier for UX entrypoint. +
      +
    • This parameter is used for metrics purposes, to identify the +UX entrypoint from which the user followed the link.
    • +
    +
  • +
+
source

pub fn get_manage_devices_url(&self, entrypoint: &str) -> ApiResult<String>

Get a URL at which the user can manage the devices connected to their account.

+

💾 This method alters the persisted account state.

+

Applications should link the user out to this URL from an appropriate place +in their signed-in settings UI. For example, “Manage your devices…” may be +a useful link to place somewhere near the device list in the send-tab UI.

+
Arguments
+
    +
  • entrypoint - metrics identifier for UX entrypoint. +
      +
    • This parameter is used for metrics purposes, to identify the +UX entrypoint from which the user followed the link.
    • +
    +
  • +
+
source§

impl FirefoxAccount

source

pub fn get_auth_state(&self) -> FxaRustAuthState

Get the high-level authentication state of the client

+
source

pub fn begin_oauth_flow<T: AsRef<str>>( + &self, + scopes: &[T], + entrypoint: &str, + metrics: Option<MetricsParams> +) -> ApiResult<String>

Initiate a web-based OAuth sign-in flow.

+

This method initializes some internal state and then returns a URL at which the +user may perform a web-based authorization flow to connect the application to +their account. The application should direct the user to the provided URL.

+

When the resulting OAuth flow redirects back to the configured redirect_uri, +the query parameters should be extracting from the URL and passed to the +complete_oauth_flow method to finalize +the signin.

+
Arguments
+
    +
  • scopes - list of OAuth scopes to request. +
      +
    • The requested scopes will determine what account-related data +the application is able to access.
    • +
    +
  • +
  • entrypoint - metrics identifier for UX entrypoint. +
      +
    • This parameter is used for metrics purposes, to identify the +UX entrypoint from which the user triggered the signin request. +For example, the application toolbar, on the onboarding flow.
    • +
    +
  • +
  • metrics - optionally, additional metrics tracking parameters. +
      +
    • These will be included as query parameters in the resulting URL.
    • +
    +
  • +
+
source

pub fn get_pairing_authority_url(&self) -> ApiResult<String>

Get the URL at which to begin a device-pairing signin flow.

+

If the user wants to sign in using device pairing, call this method and then +direct them to visit the resulting URL on an already-signed-in device. Doing +so will trigger the other device to show a QR code to be scanned, and the result +from said QR code can be passed to begin_pairing_flow.

+
source

pub fn begin_pairing_flow( + &self, + pairing_url: &str, + scopes: &[String], + entrypoint: &str, + metrics: Option<MetricsParams> +) -> ApiResult<String>

Initiate a device-pairing sign-in flow.

+

Once the user has scanned a pairing QR code, pass the scanned value to this +method. It will return a URL to which the application should redirect the user +in order to continue the sign-in flow.

+

When the resulting flow redirects back to the configured redirect_uri, +the resulting OAuth parameters should be extracting from the URL and passed +to complete_oauth_flow to finalize +the signin.

+
Arguments
+
    +
  • pairing_url - the URL scanned from a QR code on another device.
  • +
  • scopes - list of OAuth scopes to request. +
      +
    • The requested scopes will determine what account-related data +the application is able to access.
    • +
    +
  • +
  • entrypoint - metrics identifier for UX entrypoint. +
      +
    • This parameter is used for metrics purposes, to identify the +UX entrypoint from which the user triggered the signin request. +For example, the application toolbar, on the onboarding flow.
    • +
    +
  • +
  • metrics - optionally, additional metrics tracking parameters. +
      +
    • These will be included as query parameters in the resulting URL.
    • +
    +
  • +
+
source

pub fn complete_oauth_flow(&self, code: &str, state: &str) -> ApiResult<()>

Complete an OAuth flow.

+

💾 This method alters the persisted account state.

+

At the conclusion of an OAuth flow, the user will be redirect to the +application’s registered redirect_uri. It should extract the code +and state parameters from the resulting URL and pass them to this +method in order to complete the sign-in.

+
Arguments
+
    +
  • code - the OAuth authorization code obtained from the redirect URI.
  • +
  • state - the OAuth state parameter obtained from the redirect URI.
  • +
+
source

pub fn check_authorization_status(&self) -> ApiResult<AuthorizationInfo>

Check authorization status for this application.

+

💾 This method alters the persisted account state.

+

Applications may call this method to check with the FxA server about the status +of their authentication tokens. It returns an AuthorizationInfo struct +with details about whether the tokens are still active.

+
source

pub fn disconnect(&self)

Disconnect from the user’s account.

+

💾 This method alters the persisted account state.

+

This method destroys any tokens held by the client, effectively disconnecting +from the user’s account. Applications should call this when the user opts to +sign out.

+

The persisted account state after calling this method will contain only the +user’s last-seen profile information, if any. This may be useful in helping +the user to reconnect to their account. If reconnecting to the same account +is not desired then the application should discard the persisted account state.

+
source

pub fn on_auth_issues(&self)

Update the state based on authentication issues.

+

💾 This method alters the persisted account state.

+

Call this if you know there’s an authentication / authorization issue that requires the +user to re-authenticated. It transitions the user to the [FxaRustAuthState.AuthIssues] state.

+
source

pub fn simulate_temporary_auth_token_issue(&self)

Used by the application to test auth token issues

+
source

pub fn simulate_permanent_auth_token_issue(&self)

Used by the application to test auth token issues

+
source§

impl FirefoxAccount

source

pub fn initialize_device( + &self, + name: &str, + device_type: DeviceType, + supported_capabilities: Vec<DeviceCapability> +) -> ApiResult<LocalDevice>

Create a new device record for this application.

+

💾 This method alters the persisted account state.

+

This method register a device record for the application, providing basic metadata for +the device along with a list of supported Device Capabilities for +participating in the “device commands” ecosystem.

+

Applications should call this method soon after a successful sign-in, to ensure +they they appear correctly in the user’s account-management pages and when discovered +by other devices connected to the account.

+
Arguments
+
    +
  • name - human-readable display name to use for this application
  • +
  • device_type - the type of device the application is installed on
  • +
  • supported_capabilities - the set of capabilities to register +for this device in the “device commands” ecosystem.
  • +
+
Notes
+
    +
  • Device registration is only available to applications that have been +granted the https://identity.mozilla.com/apps/oldsync scope.
  • +
+
source

pub fn get_current_device_id(&self) -> ApiResult<String>

Get the device id registered for this application.

+
Notes
+
    +
  • If the application has not registered a device record, this method will +throw an Other error. +
      +
    • (Yeah…sorry. This should be changed to do something better.)
    • +
    +
  • +
  • Device metadata is only visible to applications that have been +granted the https://identity.mozilla.com/apps/oldsync scope.
  • +
+
source

pub fn get_devices(&self, ignore_cache: bool) -> ApiResult<Vec<Device>>

Get the list of devices registered on the user’s account.

+

💾 This method alters the persisted account state.

+

This method returns a list of Device structs representing all the devices +currently attached to the user’s account (including the current device). +The application might use this information to e.g. display a list of appropriate +send-tab targets.

+
Arguments
+
    +
  • ignore_cache - if true, always hit the server for fresh profile information.
  • +
+
Notes
+
    +
  • Device metadata is only visible to applications that have been +granted the https://identity.mozilla.com/apps/oldsync scope.
  • +
+
source

pub fn get_attached_clients(&self) -> ApiResult<Vec<AttachedClient>>

Get the list of all client applications attached to the user’s account.

+

This method returns a list of AttachedClient structs representing all the applications +connected to the user’s account. This includes applications that are registered as a device +as well as server-side services that the user has connected.

+

This information is really only useful for targeted messaging or marketing purposes, +e.g. if the application wants to advertise a related product, but first wants to check +whether the user is already using that product.

+
Notes
+
    +
  • Attached client metadata is only visible to applications that have been +granted the https://identity.mozilla.com/apps/oldsync scope.
  • +
+
source

pub fn set_device_name(&self, display_name: &str) -> ApiResult<LocalDevice>

Update the display name used for this application instance.

+

💾 This method alters the persisted account state.

+

This method modifies the name of the current application’s device record, as seen by +other applications and in the user’s account management pages.

+
Arguments
+
    +
  • display_name - the new name for the current device.
  • +
+
Notes
+
    +
  • Device registration is only available to applications that have been +granted the https://identity.mozilla.com/apps/oldsync scope.
  • +
+
source

pub fn clear_device_name(&self) -> ApiResult<()>

Clear any custom display name used for this application instance.

+

💾 This method alters the persisted account state.

+

This method clears the name of the current application’s device record, causing other +applications or the user’s account management pages to have to fill in some sort of +default name when displaying this device.

+
Notes
+
    +
  • Device registration is only available to applications that have been +granted the https://identity.mozilla.com/apps/oldsync scope.
  • +
+
source

pub fn ensure_capabilities( + &self, + supported_capabilities: Vec<DeviceCapability> +) -> ApiResult<LocalDevice>

Ensure that the device record has a specific set of capabilities.

+

💾 This method alters the persisted account state.

+

This method checks that the currently-registered device record is advertising the +given set of capabilities in the FxA “device commands” ecosystem. If not, then it +updates the device record to do so.

+

Applications should call this method on each startup as a way to ensure that their +expected set of capabilities is being accurately reflected on the FxA server, and +to handle the rollout of new capabilities over time.

+
Arguments
+
    +
  • supported_capabilities - the set of capabilities to register +for this device in the “device commands” ecosystem.
  • +
+
Notes
+
    +
  • Device registration is only available to applications that have been +granted the https://identity.mozilla.com/apps/oldsync scope.
  • +
+
source§

impl FirefoxAccount

source

pub fn get_profile(&self, ignore_cache: bool) -> ApiResult<Profile>

Get profile information for the signed-in user, if any.

+

💾 This method alters the persisted account state.

+

This method fetches a Profile struct with information about the currently-signed-in +user, either by using locally-cached profile information or by fetching fresh data from +the server.

+
Arguments
+
    +
  • ignore_cache - if true, always hit the server for fresh profile information.
  • +
+
Notes
+
    +
  • Profile information is only available to applications that have been +granted the profile scope.
  • +
  • There is currently no API for fetching cached profile information without +potentially hitting the server.
  • +
  • If there is no signed-in user, this method will throw an +Authentication error.
  • +
+
source§

impl FirefoxAccount

source

pub fn set_push_subscription( + &self, + subscription: DevicePushSubscription +) -> ApiResult<LocalDevice>

Set or update a push subscription endpoint for this device.

+

💾 This method alters the persisted account state.

+

This method registers the given webpush subscription with the FxA server, requesting +that is send notifications in the event of any significant changes to the user’s +account. When the application receives a push message at the registered subscription +endpoint, it should decrypt the payload and pass it to the handle_push_message method for processing.

+
Arguments
+ +
Notes
+
    +
  • Device registration is only available to applications that have been +granted the https://identity.mozilla.com/apps/oldsync scope.
  • +
+
source

pub fn handle_push_message(&self, payload: &str) -> ApiResult<AccountEvent>

Process and respond to a server-delivered account update message

+

💾 This method alters the persisted account state.

+

Applications should call this method whenever they receive a push notification from the Firefox Accounts server. +Such messages typically indicate a noteworthy change of state on the user’s account, such as an update to their profile information +or the disconnection of a client. The FirefoxAccount struct will update its internal state +accordingly and return an individual AccountEvent struct describing the event, which the application +may use for further processing.

+

It’s important to note if the event is AccountEvent::CommandReceived, the caller should call +FirefoxAccount::poll_device_commands

+
source

pub fn poll_device_commands(&self) -> ApiResult<Vec<IncomingDeviceCommand>>

Poll the server for any pending device commands.

+

💾 This method alters the persisted account state.

+

Applications that have registered one or more [DeviceCapability]s with the server can use +this method to check whether other devices on the account have sent them any commands. +It will return a list of IncomingDeviceCommand structs for the application to process.

+
Notes
+
    +
  • Device commands are typically delivered via push message and the CommandReceived event. Polling should only be used as a backup delivery +mechanism, f the application has reason to believe that push messages may have been missed.
  • +
  • Device commands functionality is only available to applications that have been +granted the https://identity.mozilla.com/apps/oldsync scope.
  • +
+
source

pub fn send_single_tab( + &self, + target_device_id: &str, + title: &str, + url: &str +) -> ApiResult<()>

Use device commands to send a single tab to another device.

+

💾 This method alters the persisted account state.

+

If a device on the account has registered the SendTab +capability, this method can be used to send it a tab.

+
Notes
+
    +
  • If the given device id does not existing or is not capable of receiving tabs, +this method will throw an Other error. +
      +
    • (Yeah…sorry. This should be changed to do something better.)
    • +
    +
  • +
  • It is not currently possible to send a full SendTabPayload to another device, +but that’s purely an API limitation that should go away in future.
  • +
  • Device commands functionality is only available to applications that have been +granted the https://identity.mozilla.com/apps/oldsync scope.
  • +
+
source§

impl FirefoxAccount

source

pub fn from_json(data: &str) -> ApiResult<FirefoxAccount>

Restore a FirefoxAccount instance from serialized state.

+

Given a JSON string previously obtained from FirefoxAccount::to_json, this +method will deserialize it and return a live FirefoxAccount instance.

+

⚠️ Warning: since the serialized state contains access tokens, you should +not call from_json multiple times on the same data. This would result +in multiple live objects sharing the same access tokens and is likely to +produce unexpected behaviour.

+
source

pub fn to_json(&self) -> ApiResult<String>

Save current state to a JSON string.

+

This method serializes the current account state into a JSON string, which +the application can use to persist the user’s signed-in state across restarts. +The application should call this method and update its persisted state after +any potentially-state-changing operation.

+

⚠️ Warning: the serialized state may contain encryption keys and access +tokens that let anyone holding them access the user’s data in Firefox Sync +and/or other FxA services. Applications should take care to store the resulting +data in a secure fashion, as appropriate for their target platform.

+
source§

impl FirefoxAccount

source

pub fn gather_telemetry(&self) -> ApiResult<String>

Collect and return telemetry about send-tab attempts.

+

Applications that register the SendTab capability +should also arrange to submit “sync ping” telemetry. Calling this method will +return a JSON string of telemetry data that can be incorporated into that ping.

+

Sorry, this is not particularly carefully documented because it is intended +as a stop-gap until we get native Glean support. If you know how to submit +a sync ping, you’ll know what to do with the contents of the JSON string.

+
source§

impl FirefoxAccount

source

pub fn get_access_token( + &self, + scope: &str, + ttl: Option<i64> +) -> ApiResult<AccessTokenInfo>

Get a short-lived OAuth access token for the user’s account.

+

💾 This method alters the persisted account state.

+

Applications that need to access resources on behalf of the user must obtain an +access_token in order to do so. For example, an access token is required when +fetching the user’s profile data, or when accessing their data stored in Firefox Sync.

+

This method will obtain and return an access token bearing the requested scopes, either +from a local cache of previously-issued tokens, or by creating a new one from the server.

+
Arguments
+
    +
  • scope - the OAuth scope to be granted by the token. +
      +
    • This must be one of the scopes requested during the signin flow.
    • +
    • Only a single scope is supported; for multiple scopes request multiple tokens.
    • +
    +
  • +
  • ttl - optionally, the time for which the token should be valid, in seconds.
  • +
+
Notes
+
    +
  • If the application receives an authorization error when trying to use the resulting +token, it should call clear_access_token_cache +before requesting a fresh token.
  • +
+
source

pub fn get_session_token(&self) -> ApiResult<String>

Get the session token for the user’s account, if one is available.

+

💾 This method alters the persisted account state.

+

Applications that function as a web browser may need to hold on to a session token +on behalf of Firefox Accounts web content. This method exists so that they can retreive +it an pass it back to said web content when required.

+
Notes
+
    +
  • Please do not attempt to use the resulting token to directly make calls to the +Firefox Accounts servers! All account management functionality should be performed +in web content.
  • +
  • A session token is only available to applications that have requested the +https://identity.mozilla.com/tokens/session scope.
  • +
+
source

pub fn handle_session_token_change(&self, session_token: &str) -> ApiResult<()>

Update the stored session token for the user’s account.

+

💾 This method alters the persisted account state.

+

Applications that function as a web browser may need to hold on to a session token +on behalf of Firefox Accounts web content. This method exists so that said web content +signals that it has generated a new session token, the stored value can be updated +to match.

+
Arguments
+
    +
  • session_token - the new session token value provided from web content.
  • +
+
source

pub fn authorize_code_using_session_token( + &self, + params: AuthorizationParameters +) -> ApiResult<String>

Create a new OAuth authorization code using the stored session token.

+

When a signed-in application receives an incoming device pairing request, it can +use this method to grant the request and generate a corresponding OAuth authorization +code. This code would then be passed back to the connecting device over the +pairing channel (a process which is not currently supported by any code in this +component).

+
Arguments
+
    +
  • params - the OAuth parameters from the incoming authorization request
  • +
+
source

pub fn clear_access_token_cache(&self)

Clear the access token cache in response to an auth failure.

+

💾 This method alters the persisted account state.

+

Applications that receive an authentication error when trying to use an access token, +should call this method before creating a new token and retrying the failed operation. +It ensures that the expired token is removed and a fresh one generated.

+
source§

impl FirefoxAccount

source

pub fn new(config: FxaConfig) -> FirefoxAccount

Create a new FirefoxAccount instance, not connected to any account.

+

💾 This method alters the persisted account state.

+

This method constructs as new FirefoxAccount instance configured to connect +the application to a user’s account.

+

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/fxa_client/struct.FxaConfig.html b/book/rust-docs/fxa_client/struct.FxaConfig.html new file mode 100644 index 0000000000..16a29cd8c9 --- /dev/null +++ b/book/rust-docs/fxa_client/struct.FxaConfig.html @@ -0,0 +1,27 @@ +FxaConfig in fxa_client - Rust

Struct fxa_client::FxaConfig

source ·
pub struct FxaConfig {
+    pub server: FxaServer,
+    pub client_id: String,
+    pub redirect_uri: String,
+    pub token_server_url_override: Option<String>,
+}

Fields§

§server: FxaServer

FxaServer to connect with

+
§client_id: String

registered OAuth client id of the application.

+
§redirect_uri: String

redirect_uri - the registered OAuth redirect URI of the application.

+
§token_server_url_override: Option<String>

URL for the user’s Sync Tokenserver. This can be used to support users who self-host their +sync data. If None then it will default to the Mozilla-hosted Sync server.

+

Note: this lives here for historical reasons, but probably shouldn’t. Applications pass +the token server URL they get from fxa-client to SyncManager. It would be simpler to +cut out fxa-client out of the middle and have applications send the overridden URL +directly to SyncManager.

+

Implementations§

source§

impl FxaConfig

source

pub fn release(client_id: impl ToString, redirect_uri: impl ToString) -> Self

source

pub fn stable(client_id: impl ToString, redirect_uri: impl ToString) -> Self

source

pub fn stage(client_id: impl ToString, redirect_uri: impl ToString) -> Self

source

pub fn china(client_id: impl ToString, redirect_uri: impl ToString) -> Self

source

pub fn dev(client_id: impl ToString, redirect_uri: impl ToString) -> Self

Trait Implementations§

source§

impl Clone for FxaConfig

source§

fn clone(&self) -> FxaConfig

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for FxaConfig

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/fxa_client/struct.LocalDevice.html b/book/rust-docs/fxa_client/struct.LocalDevice.html new file mode 100644 index 0000000000..60a3dcf65d --- /dev/null +++ b/book/rust-docs/fxa_client/struct.LocalDevice.html @@ -0,0 +1,24 @@ +LocalDevice in fxa_client - Rust

Struct fxa_client::LocalDevice

source ·
pub struct LocalDevice {
+    pub id: String,
+    pub display_name: String,
+    pub device_type: DeviceType,
+    pub capabilities: Vec<DeviceCapability>,
+    pub push_subscription: Option<DevicePushSubscription>,
+    pub push_endpoint_expired: bool,
+}
Expand description

Local device that’s connecting to FxA

+

Fields§

§id: String§display_name: String§device_type: DeviceType§capabilities: Vec<DeviceCapability>§push_subscription: Option<DevicePushSubscription>§push_endpoint_expired: bool

Trait Implementations§

source§

impl Clone for LocalDevice

source§

fn clone(&self) -> LocalDevice

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for LocalDevice

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl<'de> Deserialize<'de> for LocalDevice

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl Serialize for LocalDevice

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/fxa_client/struct.MetricsParams.html b/book/rust-docs/fxa_client/struct.MetricsParams.html new file mode 100644 index 0000000000..979a551378 --- /dev/null +++ b/book/rust-docs/fxa_client/struct.MetricsParams.html @@ -0,0 +1,15 @@ +MetricsParams in fxa_client - Rust
pub struct MetricsParams {
+    pub parameters: HashMap<String, String>,
+}
Expand description

Additional metrics tracking parameters to include in an OAuth request.

+

Fields§

§parameters: HashMap<String, String>

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/fxa_client/struct.Profile.html b/book/rust-docs/fxa_client/struct.Profile.html new file mode 100644 index 0000000000..85a9c230e8 --- /dev/null +++ b/book/rust-docs/fxa_client/struct.Profile.html @@ -0,0 +1,31 @@ +Profile in fxa_client - Rust

Struct fxa_client::Profile

source ·
pub struct Profile {
+    pub uid: String,
+    pub email: String,
+    pub display_name: Option<String>,
+    pub avatar: String,
+    pub is_default_avatar: bool,
+}
Expand description

Information about the user that controls a Firefox Account.

+

This struct represents details about the user themselves, and would typically be +used to customize account-related UI in the browser so that it is personalize +for the current user.

+

Fields§

§uid: String

The user’s account uid

+

This is an opaque immutable unique identifier for their account.

+
§email: String

The user’s current primary email address.

+

Note that unlike the uid field, the email address may change over time.

+
§display_name: Option<String>

The user’s preferred textual display name.

+
§avatar: String

The URL of a profile picture representing the user.

+

All accounts have a corresponding profile picture. If the user has not +provided one then a default image is used.

+
§is_default_avatar: bool

Whether the avatar URL represents the default avatar image.

+

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/fxa_client/struct.ScopedKey.html b/book/rust-docs/fxa_client/struct.ScopedKey.html new file mode 100644 index 0000000000..7465f451a5 --- /dev/null +++ b/book/rust-docs/fxa_client/struct.ScopedKey.html @@ -0,0 +1,34 @@ +ScopedKey in fxa_client - Rust

Struct fxa_client::ScopedKey

source ·
pub struct ScopedKey {
+    pub kty: String,
+    pub scope: String,
+    pub k: String,
+    pub kid: String,
+}
Expand description

A cryptograpic key associated with an OAuth scope.

+

Some OAuth scopes have a corresponding client-side encryption key that is required +in order to access protected data. This struct represents such key material in a +format compatible with the common “JWK” standard.

+

Fields§

§kty: String

The type of key.

+

In practice for FxA, this will always be string string “oct” (short for “octal”) +to represent a raw symmetric key.

+
§scope: String

The OAuth scope with which this key is associated.

+
§k: String

The key material, as base64-url-encoded bytes.

+

⚠️ Warning: the value of this field should never be revealed outside of the +application. For example, it should never to sent to a server or logged in a log file.

+
§kid: String

An opaque unique identifier for this key.

+

Unlike the k field, this value is not secret and may be revealed to the server.

+

Implementations§

Trait Implementations§

source§

impl Clone for ScopedKey

source§

fn clone(&self) -> ScopedKey

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for ScopedKey

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl<'de> Deserialize<'de> for ScopedKey

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl Serialize for ScopedKey

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/fxa_client/struct.SendTabPayload.html b/book/rust-docs/fxa_client/struct.SendTabPayload.html new file mode 100644 index 0000000000..61176e3223 --- /dev/null +++ b/book/rust-docs/fxa_client/struct.SendTabPayload.html @@ -0,0 +1,25 @@ +SendTabPayload in fxa_client - Rust
pub struct SendTabPayload {
+    pub entries: Vec<TabHistoryEntry>,
+    pub flow_id: String,
+    pub stream_id: String,
+}
Expand description

The payload sent when invoking a “send tab” command.

+

Fields§

§entries: Vec<TabHistoryEntry>

The navigation history of the sent tab.

+

The last item in this list represents the page to be displayed, +while earlier items may be included in the navigation history +as a convenience to the user.

+
§flow_id: String

A unique identifier to be included in send-tab metrics.

+

The application should treat this as opaque.

+
§stream_id: String

A unique identifier to be included in send-tab metrics.

+

The application should treat this as opaque.

+

Trait Implementations§

source§

impl Debug for SendTabPayload

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/fxa_client/struct.TabHistoryEntry.html b/book/rust-docs/fxa_client/struct.TabHistoryEntry.html new file mode 100644 index 0000000000..48be349939 --- /dev/null +++ b/book/rust-docs/fxa_client/struct.TabHistoryEntry.html @@ -0,0 +1,16 @@ +TabHistoryEntry in fxa_client - Rust
pub struct TabHistoryEntry {
+    pub title: String,
+    pub url: String,
+}
Expand description

An individual entry in the navigation history of a sent tab.

+

Fields§

§title: String§url: String

Trait Implementations§

source§

impl Debug for TabHistoryEntry

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/fxa_client/token/struct.AccessTokenInfo.html b/book/rust-docs/fxa_client/token/struct.AccessTokenInfo.html new file mode 100644 index 0000000000..05439cc99a --- /dev/null +++ b/book/rust-docs/fxa_client/token/struct.AccessTokenInfo.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../fxa_client/struct.AccessTokenInfo.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/fxa_client/token/struct.AuthorizationParameters.html b/book/rust-docs/fxa_client/token/struct.AuthorizationParameters.html new file mode 100644 index 0000000000..c3458621e4 --- /dev/null +++ b/book/rust-docs/fxa_client/token/struct.AuthorizationParameters.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../fxa_client/struct.AuthorizationParameters.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/fxa_client/token/struct.ScopedKey.html b/book/rust-docs/fxa_client/token/struct.ScopedKey.html new file mode 100644 index 0000000000..2bf2ffd699 --- /dev/null +++ b/book/rust-docs/fxa_client/token/struct.ScopedKey.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../fxa_client/struct.ScopedKey.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/fxa_client/type.ApiResult.html b/book/rust-docs/fxa_client/type.ApiResult.html new file mode 100644 index 0000000000..2e234ef694 --- /dev/null +++ b/book/rust-docs/fxa_client/type.ApiResult.html @@ -0,0 +1,2 @@ +ApiResult in fxa_client - Rust

Type Definition fxa_client::ApiResult

source ·
pub type ApiResult<T> = Result<T, FxaError>;
Expand description

Result returned by public-facing API functions

+
\ No newline at end of file diff --git a/book/rust-docs/fxa_client/type.Result.html b/book/rust-docs/fxa_client/type.Result.html new file mode 100644 index 0000000000..61b2f29032 --- /dev/null +++ b/book/rust-docs/fxa_client/type.Result.html @@ -0,0 +1,2 @@ +Result in fxa_client - Rust

Type Definition fxa_client::Result

source ·
pub type Result<T> = Result<T, Error>;
Expand description

Result returned by internal functions

+
\ No newline at end of file diff --git a/book/rust-docs/help.html b/book/rust-docs/help.html new file mode 100644 index 0000000000..f29f368e31 --- /dev/null +++ b/book/rust-docs/help.html @@ -0,0 +1 @@ +Rustdoc help

Rustdoc help

Back
\ No newline at end of file diff --git a/book/rust-docs/implementors/askama/trait.Template.js b/book/rust-docs/implementors/askama/trait.Template.js new file mode 100644 index 0000000000..5787d0f370 --- /dev/null +++ b/book/rust-docs/implementors/askama/trait.Template.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"nimbus_fml":[["impl<'a> Template for FeatureManifestDeclaration<'a>"],["impl Template for FeatureCodeDeclaration"],["impl<'a> Template for ImportedModuleInitialization<'a>"],["impl Template for ObjectCodeDeclaration"],["impl<'a> Template for FeatureManifestDeclaration<'a>"],["impl Template for FeatureCodeDeclaration"],["impl<'a> Template for ImportedModuleInitialization<'a>"],["impl Template for EnumCodeDeclaration"],["impl Template for EnumCodeDeclaration"],["impl Template for ObjectCodeDeclaration"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/autofill/sync/trait.SyncRecord.js b/book/rust-docs/implementors/autofill/sync/trait.SyncRecord.js new file mode 100644 index 0000000000..2b6a72d307 --- /dev/null +++ b/book/rust-docs/implementors/autofill/sync/trait.SyncRecord.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"autofill":[] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/clap_builder/derive/trait.Args.js b/book/rust-docs/implementors/clap_builder/derive/trait.Args.js new file mode 100644 index 0000000000..115c6a77c7 --- /dev/null +++ b/book/rust-docs/implementors/clap_builder/derive/trait.Args.js @@ -0,0 +1,4 @@ +(function() {var implementors = { +"examples_fxa_client":[["impl Args for SendTabArgs"],["impl Args for Cli"],["impl Args for DeviceArgs"]], +"nimbus_cli":[["impl Args for Cli"],["impl Args for ExperimentListArgs"],["impl Args for OpenArgs"],["impl Args for ExperimentListFilterArgs"],["impl Args for ExperimentArgs"],["impl Args for ManifestArgs"],["impl Args for ExperimentListSourceArgs"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/clap_builder/derive/trait.CommandFactory.js b/book/rust-docs/implementors/clap_builder/derive/trait.CommandFactory.js new file mode 100644 index 0000000000..e9a972ab32 --- /dev/null +++ b/book/rust-docs/implementors/clap_builder/derive/trait.CommandFactory.js @@ -0,0 +1,4 @@ +(function() {var implementors = { +"examples_fxa_client":[["impl CommandFactory for Cli"]], +"nimbus_cli":[["impl CommandFactory for Cli"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/clap_builder/derive/trait.FromArgMatches.js b/book/rust-docs/implementors/clap_builder/derive/trait.FromArgMatches.js new file mode 100644 index 0000000000..1106a44f14 --- /dev/null +++ b/book/rust-docs/implementors/clap_builder/derive/trait.FromArgMatches.js @@ -0,0 +1,4 @@ +(function() {var implementors = { +"examples_fxa_client":[["impl FromArgMatches for Command"],["impl FromArgMatches for Command"],["impl FromArgMatches for Cli"],["impl FromArgMatches for SendTabArgs"],["impl FromArgMatches for Command"],["impl FromArgMatches for DeviceArgs"]], +"nimbus_cli":[["impl FromArgMatches for ExperimentListFilterArgs"],["impl FromArgMatches for ExperimentListSourceArgs"],["impl FromArgMatches for ExperimentListArgs"],["impl FromArgMatches for CliCommand"],["impl FromArgMatches for OpenArgs"],["impl FromArgMatches for ManifestArgs"],["impl FromArgMatches for ExperimentArgs"],["impl FromArgMatches for Cli"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/clap_builder/derive/trait.Parser.js b/book/rust-docs/implementors/clap_builder/derive/trait.Parser.js new file mode 100644 index 0000000000..25a2cd1182 --- /dev/null +++ b/book/rust-docs/implementors/clap_builder/derive/trait.Parser.js @@ -0,0 +1,4 @@ +(function() {var implementors = { +"examples_fxa_client":[["impl Parser for Cli"]], +"nimbus_cli":[["impl Parser for Cli"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/clap_builder/derive/trait.Subcommand.js b/book/rust-docs/implementors/clap_builder/derive/trait.Subcommand.js new file mode 100644 index 0000000000..c0a5ff3dd9 --- /dev/null +++ b/book/rust-docs/implementors/clap_builder/derive/trait.Subcommand.js @@ -0,0 +1,4 @@ +(function() {var implementors = { +"examples_fxa_client":[["impl Subcommand for Command"],["impl Subcommand for Command"],["impl Subcommand for Command"]], +"nimbus_cli":[["impl Subcommand for CliCommand"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/clap_builder/derive/trait.ValueEnum.js b/book/rust-docs/implementors/clap_builder/derive/trait.ValueEnum.js new file mode 100644 index 0000000000..0772ff3d04 --- /dev/null +++ b/book/rust-docs/implementors/clap_builder/derive/trait.ValueEnum.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"examples_fxa_client":[["impl ValueEnum for Server"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/core/clone/trait.Clone.js b/book/rust-docs/implementors/core/clone/trait.Clone.js new file mode 100644 index 0000000000..2ad47f113f --- /dev/null +++ b/book/rust-docs/implementors/core/clone/trait.Clone.js @@ -0,0 +1,25 @@ +(function() {var implementors = { +"autofill":[["impl Clone for CreditCard"],["impl Clone for Metadata"],["impl Clone for UpdatableAddressFields"],["impl Clone for Address"],["impl Clone for UpdatableCreditCardFields"],["impl Clone for InternalCreditCard"],["impl Clone for InternalAddress"]], +"examples_fxa_client":[["impl Clone for Server"]], +"fxa_client":[["impl Clone for FxaConfig"],["impl Clone for DeviceCapability"],["impl Clone for DevicePushSubscription"],["impl Clone for ScopedKey"],["impl Clone for LocalDevice"],["impl Clone for FxaServer"]], +"interrupt_support":[["impl Clone for Interrupted"]], +"logins":[["impl Clone for LoginEntry"],["impl Clone for Login"],["impl Clone for LoginFields"],["impl Clone for EncryptedLogin"],["impl Clone for RecordFields"],["impl Clone for SecureLoginFields"]], +"nimbus":[["impl Clone for Version"],["impl Clone for AppContext"],["impl Clone for Branch"],["impl Clone for Experiment"],["impl Clone for FeatureConfig"],["impl Clone for EnrollmentStatusExtraDef"],["impl Clone for IntervalData"],["impl Clone for RandomizationUnit"],["impl Clone for EventStore"],["impl Clone for MultiIntervalCounter"],["impl Clone for EnrolledExperiment"],["impl Clone for TargetingAttributes"],["impl Clone for IntervalConfig"],["impl Clone for Interval"],["impl Clone for EnrolledFeature"],["impl Clone for BucketConfig"],["impl Clone for EnrollmentStatus"],["impl Clone for SingleIntervalCounter"]], +"nimbus_cli":[["impl Clone for NimbusApp"],["impl Clone for ExperimentListSource"],["impl Clone for ExperimentListFilterArgs"],["impl Clone for ManifestArgs"],["impl Clone for CliCommand"],["impl Clone for ExperimentSource"],["impl Clone for OpenArgs"],["impl Clone for ExperimentArgs"],["impl Clone for ExperimentListSourceArgs"],["impl<'a> Clone for StartAppProtocol<'a>"],["impl Clone for ExperimentListFilter"],["impl Clone for ExperimentListArgs"],["impl Clone for LaunchableApp"]], +"nimbus_fml":[["impl Clone for Types"],["impl Clone for DocumentationLink"],["impl Clone for ObjectDef"],["impl Clone for TargetLanguage"],["impl Clone for TypeRef"],["impl Clone for EnumBody"],["impl Clone for AboutBlock"],["impl Clone for LoaderConfig"],["impl Clone for ExperimenterFeatureProperty"],["impl Clone for ConcreteCodeOracle"],["impl Clone for DefaultBlock"],["impl Clone for EnumVariantBody"],["impl<'a> Clone for ImportedModule<'a>"],["impl Clone for FieldBody"],["impl Clone for FeatureManifest"],["impl Clone for GenerateStructCmd"],["impl Clone for FromStringDef"],["impl Clone for VariantDef"],["impl Clone for ConcreteCodeOracle"],["impl Clone for ObjectBody"],["impl Clone for KotlinAboutBlock"],["impl Clone for FilePath"],["impl Clone for FeatureMetadata"],["impl Clone for ExperimenterFeature"],["impl Clone for FeatureDef"],["impl Clone for FeatureFieldBody"],["impl Clone for EnumDef"],["impl Clone for ImportBlock"],["impl Clone for SwiftAboutBlock"],["impl Clone for PropDef"],["impl Clone for ModuleId"],["impl Clone for ManifestFrontEnd"],["impl Clone for FeatureBody"],["impl Clone for FileLoader"]], +"nss":[["impl Clone for Curve"],["impl Clone for Operation"],["impl Clone for HashAlgorithm"],["impl Clone for EcKey"]], +"nss_build_common":[["impl Clone for NoNssDir"],["impl Clone for LinkingKind"]], +"nss_sys":[["impl Clone for SECItemStr"],["impl Clone for SECKEYFortezzaPublicKeyStr"],["impl Clone for SECOidDataStr"],["impl Clone for SECKEYECPublicKeyStr"],["impl Clone for SECKEYDSAPublicKeyStr"],["impl Clone for SECKEYKEAParamsStr"],["impl Clone for ECPointEncoding"],["impl Clone for CK_ATTRIBUTE"],["impl Clone for PLArenaPool"],["impl Clone for SECKEYRSAPublicKeyStr"],["impl Clone for SECKEYKEAPublicKeyStr"],["impl Clone for SECAlgorithmIDStr"],["impl Clone for PLArena"],["impl Clone for SECKEYDHPublicKeyStr"],["impl Clone for SECKEYPQGParamsStr"]], +"places":[["impl Clone for HistoryVisitInfosWithBound"],["impl Clone for VisitObservation"],["impl Clone for DocumentType"],["impl Clone for HistoryMetadataObservation"],["impl<'a> Clone for ValidatedTag<'a>"],["impl Clone for SearchParams"],["impl Clone for ConnectionType"],["impl Clone for SeparatorRecord"],["impl Clone for UpdateTreeLocation"],["impl Clone for ServerVisitTimestamp"],["impl Clone for PrefixMode"],["impl Clone for BookmarkUpdateInfo"],["impl Clone for InsertableFolder"],["impl Clone for SyncedBookmarkValidity"],["impl Clone for HistoryMetadata"],["impl Clone for BookmarkPosition"],["impl Clone for SearchResult"],["impl Clone for HistoryHighlightWeights"],["impl Clone for SyncedBookmarkKind"],["impl Clone for FrecencySettings"],["impl Clone for InvalidVisitType"],["impl Clone for Separator"],["impl Clone for SearchBehavior"],["impl Clone for HistoryRecord"],["impl Clone for Folder"],["impl Clone for BookmarkRecordId"],["impl Clone for RowId"],["impl Clone for FolderRecord"],["impl Clone for UpdatableItem"],["impl Clone for UpdatableFolder"],["impl Clone for InsertableSeparator"],["impl Clone for BookmarkData"],["impl Clone for HistoryRecordVisit"],["impl Clone for MatchBehavior"],["impl Clone for UpdatableSeparator"],["impl Clone for BookmarkRecord"],["impl Clone for RedirectSourceType"],["impl Clone for InsertableBookmark"],["impl Clone for UpdatableBookmark"],["impl Clone for InsertableItem"],["impl Clone for VisitType"],["impl Clone for VisitTransitionSet"],["impl Clone for FetchedVisit"],["impl Clone for Item"],["impl Clone for QueryRecord"],["impl Clone for SyncStatus"],["impl Clone for LivemarkRecord"],["impl Clone for BookmarkRootGuid"],["impl Clone for BookmarkType"],["impl Clone for HistoryMigrationResult"],["impl Clone for BookmarkItemRecord"],["impl Clone for HistoryHighlight"],["impl Clone for HistoryVisitInfo"]], +"push":[["impl Clone for PushConfiguration"],["impl Clone for PushSubscriptionChanged"],["impl Clone for SubscriptionInfo"],["impl Clone for Protocol"],["impl Clone for KeyInfo"],["impl Clone for SubscriptionResponse"],["impl Clone for BridgeType"]], +"rc_crypto":[["impl Clone for Digest"],["impl Clone for Signature"]], +"rc_log_ffi":[["impl Clone for LogLevel"]], +"remote_settings":[["impl Clone for RemoteSettingsRecord"],["impl Clone for RemoteSettingsConfig"],["impl Clone for SortOrder"],["impl Clone for Attachment"],["impl Clone for GetItemsOptions"],["impl Clone for RemoteSettingsResponse"]], +"sql_support":[["impl<'a, F: Clone> Clone for RepeatDisplay<'a, F>"]], +"sync15":[["impl Clone for CollectionRequest"],["impl Clone for KeyBundle"],["impl Clone for ClientData"],["impl<T: Clone> Clone for Sync15ClientResponse<T>"],["impl Clone for EncryptedPayload"],["impl Clone for Command"],["impl Clone for CommandStatus"],["impl Clone for SyncEngineId"],["impl Clone for Settings"],["impl Clone for ServerTimestamp"],["impl Clone for OutgoingEnvelope"],["impl Clone for ServiceStatus"],["impl Clone for IncomingEnvelope"],["impl Clone for EngineSyncAssociation"],["impl Clone for RemoteClient"],["impl Clone for CollSyncIds"],["impl Clone for Sync15StorageClientInit"],["impl Clone for RequestOrder"],["impl Clone for DeviceType"]], +"sync_guid":[["impl Clone for Guid"]], +"tabs":[["impl Clone for ClientRemoteTabs"]], +"types":[["impl Clone for Timestamp"]], +"viaduct":[["impl Clone for Response"],["impl Clone for UnexpectedStatus"],["impl Clone for Method"],["impl Clone for Request"],["impl Clone for Header"],["impl Clone for Headers"],["impl Clone for InvalidHeaderName"],["impl Clone for HeaderName"]], +"webext_storage":[["impl Clone for MigrationInfo"],["impl Clone for UsageInfo"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/core/cmp/trait.Eq.js b/book/rust-docs/implementors/core/cmp/trait.Eq.js new file mode 100644 index 0000000000..8fc443f686 --- /dev/null +++ b/book/rust-docs/implementors/core/cmp/trait.Eq.js @@ -0,0 +1,21 @@ +(function() {var implementors = { +"autofill":[["impl Eq for Metadata"],["impl Eq for Address"]], +"examples_fxa_client":[["impl Eq for Server"]], +"fxa_client":[["impl Eq for FxaRustAuthState"],["impl Eq for DeviceCapability"]], +"logins":[["impl Eq for RecordFields"],["impl Eq for SecureLoginFields"],["impl Eq for Login"],["impl Eq for LoginEntry"],["impl Eq for EncryptedLogin"],["impl Eq for LoginFields"]], +"nimbus":[["impl Eq for Branch"],["impl Eq for Experiment"],["impl Eq for Interval"],["impl Eq for EnrollmentStatus"],["impl Eq for BucketConfig"],["impl Eq for FeatureConfig"],["impl Eq for EnrolledFeature"],["impl Eq for RandomizationUnit"]], +"nimbus_fml":[["impl Eq for TypeRef"],["impl Eq for ObjectDef"],["impl Eq for SwiftAboutBlock"],["impl Eq for FeatureMetadata"],["impl Eq for KotlinAboutBlock"],["impl Eq for FeatureDef"],["impl Eq for PropDef"],["impl Eq for TargetLanguage"],["impl Eq for EnumDef"],["impl Eq for ModuleId"],["impl Eq for AboutBlock"],["impl Eq for FromStringDef"],["impl Eq for DocumentationLink"],["impl Eq for VariantDef"],["impl Eq for FeatureManifest"]], +"nss":[["impl Eq for Curve"]], +"nss_build_common":[["impl Eq for NoNssDir"],["impl Eq for LinkingKind"]], +"nss_sys":[["impl Eq for _SECStatus"]], +"places":[["impl Eq for HistoryVisitInfosWithBound"],["impl Eq for InvalidVisitType"],["impl Eq for SearchResult"],["impl Eq for BookmarkItemRecord"],["impl Eq for HistoryMigrationResult"],["impl Eq for HistoryVisitInfo"],["impl Eq for SyncedBookmarkValidity"],["impl Eq for SyncStatus"],["impl Eq for BookmarkRootGuid"],["impl Eq for HistoryMetadataObservation"],["impl Eq for HistoryRecordVisit"],["impl Eq for VisitType"],["impl Eq for BookmarkRecord"],["impl<'a> Eq for ValidatedTag<'a>"],["impl Eq for RedirectSourceType"],["impl Eq for SyncedBookmarkKind"],["impl Eq for MatchBehavior"],["impl Eq for RowId"],["impl Eq for SeparatorRecord"],["impl Eq for HistoryMetadata"],["impl Eq for FolderRecord"],["impl Eq for QueryRecord"],["impl Eq for LivemarkRecord"],["impl Eq for PrefixMode"],["impl Eq for ConnectionType"],["impl Eq for FetchedVisit"],["impl Eq for SearchBehavior"],["impl Eq for FrecencySettings"],["impl Eq for DocumentType"],["impl Eq for ServerVisitTimestamp"],["impl Eq for HistoryRecord"],["impl Eq for BookmarkUpdateInfo"],["impl Eq for BookmarkType"],["impl Eq for VisitTransitionSet"],["impl Eq for BookmarkRecordId"]], +"push":[["impl Eq for Protocol"],["impl Eq for BridgeType"],["impl Eq for SubscriptionInfo"],["impl Eq for KeyInfo"],["impl Eq for SubscriptionResponse"]], +"rc_crypto":[["impl Eq for Algorithm"]], +"rc_log_ffi":[["impl Eq for LogLevel"]], +"remote_settings":[["impl Eq for RemoteSettingsResponse"],["impl Eq for RemoteSettingsRecord"],["impl Eq for SortOrder"],["impl Eq for Attachment"]], +"sync15":[["impl Eq for EngineSyncAssociation"],["impl Eq for CollectionRequest"],["impl Eq for CollSyncIds"],["impl Eq for Sync15StorageClientInit"],["impl Eq for ClientData"],["impl Eq for CommandStatus"],["impl Eq for Command"],["impl Eq for KeyBundle"],["impl Eq for ServiceStatus"],["impl Eq for ServerTimestamp"],["impl Eq for SyncEngineId"],["impl Eq for RequestOrder"],["impl Eq for Settings"],["impl Eq for RemoteClient"],["impl Eq for DeviceType"]], +"sync_guid":[["impl Eq for Guid"]], +"types":[["impl Eq for Timestamp"]], +"viaduct":[["impl Eq for Method"],["impl Eq for InvalidHeaderName"],["impl Eq for Header"],["impl Eq for Headers"],["impl Eq for HeaderName"]], +"webext_storage":[["impl Eq for MigrationInfo"],["impl Eq for UsageInfo"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/core/cmp/trait.Ord.js b/book/rust-docs/implementors/core/cmp/trait.Ord.js new file mode 100644 index 0000000000..3f05f4c23e --- /dev/null +++ b/book/rust-docs/implementors/core/cmp/trait.Ord.js @@ -0,0 +1,10 @@ +(function() {var implementors = { +"examples_fxa_client":[["impl Ord for Server"]], +"nimbus_fml":[["impl Ord for ModuleId"]], +"places":[["impl Ord for InvalidVisitType"],["impl Ord for SyncedBookmarkValidity"],["impl Ord for SearchBehavior"],["impl Ord for SyncStatus"],["impl Ord for RowId"],["impl Ord for SyncedBookmarkKind"],["impl Ord for ServerVisitTimestamp"],["impl Ord for BookmarkType"]], +"push":[["impl Ord for Protocol"],["impl Ord for BridgeType"],["impl Ord for KeyInfo"],["impl Ord for SubscriptionInfo"],["impl Ord for SubscriptionResponse"]], +"sync15":[["impl Ord for RequestOrder"],["impl Ord for SyncEngineId"],["impl Ord for Command"],["impl Ord for Sync15StorageClientInit"]], +"sync_guid":[["impl Ord for Guid"]], +"types":[["impl Ord for Timestamp"]], +"viaduct":[["impl Ord for Header"],["impl Ord for HeaderName"],["impl Ord for Method"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/core/cmp/trait.PartialEq.js b/book/rust-docs/implementors/core/cmp/trait.PartialEq.js new file mode 100644 index 0000000000..f3451dab20 --- /dev/null +++ b/book/rust-docs/implementors/core/cmp/trait.PartialEq.js @@ -0,0 +1,22 @@ +(function() {var implementors = { +"autofill":[["impl PartialEq<Address> for Address"],["impl PartialEq<Metadata> for Metadata"]], +"examples_fxa_client":[["impl PartialEq<Server> for Server"]], +"fxa_client":[["impl PartialEq<DeviceCapability> for DeviceCapability"],["impl PartialEq<FxaRustAuthState> for FxaRustAuthState"]], +"logins":[["impl PartialEq<LoginEntry> for LoginEntry"],["impl PartialEq<SecureLoginFields> for SecureLoginFields"],["impl PartialEq<EncryptedLogin> for EncryptedLogin"],["impl PartialEq<Login> for Login"],["impl PartialEq<RecordFields> for RecordFields"],["impl PartialEq<LoginFields> for LoginFields"]], +"nimbus":[["impl PartialEq<Version> for Version"],["impl PartialEq<EnrollmentStatus> for EnrollmentStatus"],["impl PartialEq<BucketConfig> for BucketConfig"],["impl PartialEq<Experiment> for Experiment"],["impl PartialEq<EnrolledFeature> for EnrolledFeature"],["impl PartialEq<Interval> for Interval"],["impl PartialEq<RandomizationUnit> for RandomizationUnit"],["impl PartialEq<FeatureConfig> for FeatureConfig"],["impl PartialEq<Branch> for Branch"]], +"nimbus_cli":[["impl PartialEq<ExperimentListSource> for ExperimentListSource"],["impl PartialEq<LaunchableApp> for LaunchableApp"],["impl PartialEq<AppOpenArgs> for AppOpenArgs"],["impl PartialEq<ManifestSource> for ManifestSource"],["impl PartialEq<AppCommand> for AppCommand"],["impl PartialEq<NimbusApp> for NimbusApp"],["impl PartialEq<ExperimentSource> for ExperimentSource"],["impl PartialEq<ExperimentListFilter> for ExperimentListFilter"]], +"nimbus_fml":[["impl PartialEq<VariantDef> for VariantDef"],["impl PartialEq<FeatureManifest> for FeatureManifest"],["impl PartialEq<KotlinAboutBlock> for KotlinAboutBlock"],["impl PartialEq<ModuleId> for ModuleId"],["impl PartialEq<EnumDef> for EnumDef"],["impl PartialEq<SwiftAboutBlock> for SwiftAboutBlock"],["impl PartialEq<DocumentationLink> for DocumentationLink"],["impl PartialEq<FeatureDef> for FeatureDef"],["impl PartialEq<FromStringDef> for FromStringDef"],["impl PartialEq<TargetLanguage> for TargetLanguage"],["impl PartialEq<FeatureMetadata> for FeatureMetadata"],["impl PartialEq<AboutBlock> for AboutBlock"],["impl PartialEq<PropDef> for PropDef"],["impl PartialEq<TypeRef> for TypeRef"],["impl PartialEq<ObjectDef> for ObjectDef"]], +"nss":[["impl PartialEq<Curve> for Curve"]], +"nss_build_common":[["impl PartialEq<NoNssDir> for NoNssDir"],["impl PartialEq<LinkingKind> for LinkingKind"]], +"nss_sys":[["impl PartialEq<_SECStatus> for _SECStatus"]], +"places":[["impl PartialEq<HistoryVisitInfosWithBound> for HistoryVisitInfosWithBound"],["impl PartialEq<HistoryRecord> for HistoryRecord"],["impl PartialEq<BookmarkRecordId> for BookmarkRecordId"],["impl PartialEq<FetchedVisit> for FetchedVisit"],["impl PartialEq<LivemarkRecord> for LivemarkRecord"],["impl PartialEq<HistoryMigrationResult> for HistoryMigrationResult"],["impl PartialEq<BookmarkRootGuid> for SyncGuid"],["impl PartialEq<BookmarkItemRecord> for BookmarkItemRecord"],["impl<'a> PartialEq<ValidatedTag<'a>> for ValidatedTag<'a>"],["impl PartialEq<HistoryRecordVisit> for HistoryRecordVisit"],["impl PartialEq<BookmarkType> for BookmarkType"],["impl PartialEq<HistoryMetadata> for HistoryMetadata"],["impl PartialEq<ServerVisitTimestamp> for ServerVisitTimestamp"],["impl PartialEq<SearchBehavior> for SearchBehavior"],["impl PartialEq<HistoryMetadataObservation> for HistoryMetadataObservation"],["impl PartialEq<HistoryVisitInfo> for HistoryVisitInfo"],["impl PartialEq<SeparatorRecord> for SeparatorRecord"],["impl<'a> PartialEq<BookmarkRootGuid> for &'a str"],["impl<'a> PartialEq<BookmarkRootGuid> for &'a SyncGuid"],["impl PartialEq<DocumentType> for DocumentType"],["impl PartialEq<QueryRecord> for QueryRecord"],["impl PartialEq<BookmarkUpdateInfo> for BookmarkUpdateInfo"],["impl PartialEq<FolderRecord> for FolderRecord"],["impl PartialEq<BookmarkRecord> for BookmarkRecord"],["impl PartialEq<MatchBehavior> for MatchBehavior"],["impl PartialEq<RedirectSourceType> for RedirectSourceType"],["impl PartialEq<Guid> for BookmarkRootGuid"],["impl PartialEq<SyncedBookmarkKind> for SyncedBookmarkKind"],["impl PartialEq<VisitType> for VisitType"],["impl PartialEq<BookmarkRootGuid> for BookmarkRootGuid"],["impl PartialEq<PrefixMode> for PrefixMode"],["impl<'a> PartialEq<&'a str> for BookmarkRootGuid"],["impl PartialEq<ConnectionType> for ConnectionType"],["impl PartialEq<SyncStatus> for SyncStatus"],["impl PartialEq<VisitTransitionSet> for VisitTransitionSet"],["impl PartialEq<InvalidVisitType> for InvalidVisitType"],["impl<'a> PartialEq<&'a Guid> for BookmarkRootGuid"],["impl PartialEq<RowId> for RowId"],["impl PartialEq<SearchResult> for SearchResult"],["impl PartialEq<FrecencySettings> for FrecencySettings"],["impl PartialEq<SyncedBookmarkValidity> for SyncedBookmarkValidity"]], +"push":[["impl PartialEq<SubscriptionInfo> for SubscriptionInfo"],["impl PartialEq<KeyInfo> for KeyInfo"],["impl PartialEq<Protocol> for Protocol"],["impl PartialEq<SubscriptionResponse> for SubscriptionResponse"],["impl PartialEq<BridgeType> for BridgeType"]], +"rc_crypto":[["impl PartialEq<Algorithm> for Algorithm"]], +"rc_log_ffi":[["impl PartialEq<LogLevel> for LogLevel"]], +"remote_settings":[["impl PartialEq<Attachment> for Attachment"],["impl PartialEq<SortOrder> for SortOrder"],["impl PartialEq<RemoteSettingsRecord> for RemoteSettingsRecord"],["impl PartialEq<RemoteSettingsResponse> for RemoteSettingsResponse"]], +"sync15":[["impl PartialEq<RequestOrder> for RequestOrder"],["impl PartialEq<CommandStatus> for CommandStatus"],["impl PartialEq<Sync15StorageClientInit> for Sync15StorageClientInit"],["impl PartialEq<Command> for Command"],["impl PartialEq<CollectionRequest> for CollectionRequest"],["impl PartialEq<ClientData> for ClientData"],["impl PartialEq<SyncEngineId> for SyncEngineId"],["impl PartialEq<DeviceType> for DeviceType"],["impl PartialEq<Settings> for Settings"],["impl PartialEq<RemoteClient> for RemoteClient"],["impl PartialEq<CollSyncIds> for CollSyncIds"],["impl PartialEq<ServiceStatus> for ServiceStatus"],["impl PartialEq<ServerTimestamp> for ServerTimestamp"],["impl PartialEq<EngineSyncAssociation> for EngineSyncAssociation"],["impl PartialEq<KeyBundle> for KeyBundle"]], +"sync_guid":[["impl<'a> PartialEq<Vec<u8, Global>> for Guid"],["impl<'a> PartialEq<Guid> for [u8]"],["impl<'a> PartialEq<Guid> for &'a [u8]"],["impl<'a> PartialEq<Guid> for str"],["impl<'a> PartialEq<&'a [u8]> for Guid"],["impl<'a> PartialEq<str> for Guid"],["impl<'a> PartialEq<Guid> for &'a str"],["impl<'a> PartialEq<Guid> for String"],["impl<'a> PartialEq<&'a str> for Guid"],["impl PartialEq<Guid> for Guid"],["impl<'a> PartialEq<Guid> for Vec<u8>"],["impl<'a> PartialEq<String> for Guid"],["impl<'a> PartialEq<[u8]> for Guid"]], +"types":[["impl PartialEq<Timestamp> for Timestamp"]], +"viaduct":[["impl<'a> PartialEq<&'a str> for HeaderName"],["impl<'a> PartialEq<&'a String> for HeaderName"],["impl PartialEq<Headers> for Headers"],["impl PartialEq<InvalidHeaderName> for InvalidHeaderName"],["impl PartialEq<HeaderName> for HeaderName"],["impl<'a> PartialEq<String> for HeaderName"],["impl PartialEq<Header> for Header"],["impl<'a> PartialEq<HeaderName> for String"],["impl<'a> PartialEq<str> for HeaderName"],["impl<'a> PartialEq<HeaderName> for Cow<'a, str>"],["impl<'a> PartialEq<HeaderName> for &'a str"],["impl<'a> PartialEq<HeaderName> for str"],["impl<'a> PartialEq<Cow<'a, str>> for HeaderName"],["impl<'a> PartialEq<HeaderName> for &'a String"],["impl PartialEq<Method> for Method"]], +"webext_storage":[["impl PartialEq<UsageInfo> for UsageInfo"],["impl PartialEq<MigrationInfo> for MigrationInfo"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/core/cmp/trait.PartialOrd.js b/book/rust-docs/implementors/core/cmp/trait.PartialOrd.js new file mode 100644 index 0000000000..ff8bd8b5cc --- /dev/null +++ b/book/rust-docs/implementors/core/cmp/trait.PartialOrd.js @@ -0,0 +1,11 @@ +(function() {var implementors = { +"examples_fxa_client":[["impl PartialOrd<Server> for Server"]], +"nimbus":[["impl PartialOrd<Version> for Version"]], +"nimbus_fml":[["impl PartialOrd<ModuleId> for ModuleId"]], +"places":[["impl PartialOrd<SearchBehavior> for SearchBehavior"],["impl PartialOrd<SyncStatus> for SyncStatus"],["impl PartialOrd<BookmarkRootGuid> for BookmarkRootGuid"],["impl PartialOrd<BookmarkType> for BookmarkType"],["impl PartialOrd<RedirectSourceType> for RedirectSourceType"],["impl PartialOrd<SyncedBookmarkKind> for SyncedBookmarkKind"],["impl PartialOrd<RowId> for RowId"],["impl PartialOrd<ServerVisitTimestamp> for ServerVisitTimestamp"],["impl PartialOrd<SyncedBookmarkValidity> for SyncedBookmarkValidity"],["impl PartialOrd<InvalidVisitType> for InvalidVisitType"]], +"push":[["impl PartialOrd<Protocol> for Protocol"],["impl PartialOrd<KeyInfo> for KeyInfo"],["impl PartialOrd<BridgeType> for BridgeType"],["impl PartialOrd<SubscriptionResponse> for SubscriptionResponse"],["impl PartialOrd<SubscriptionInfo> for SubscriptionInfo"]], +"sync15":[["impl PartialOrd<ServerTimestamp> for ServerTimestamp"],["impl PartialOrd<Sync15StorageClientInit> for Sync15StorageClientInit"],["impl PartialOrd<Command> for Command"],["impl PartialOrd<SyncEngineId> for SyncEngineId"],["impl PartialOrd<RequestOrder> for RequestOrder"]], +"sync_guid":[["impl PartialOrd<Guid> for Guid"]], +"types":[["impl PartialOrd<Timestamp> for Timestamp"]], +"viaduct":[["impl PartialOrd<HeaderName> for HeaderName"],["impl PartialOrd<Header> for Header"],["impl PartialOrd<Method> for Method"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/core/convert/trait.AsRef.js b/book/rust-docs/implementors/core/convert/trait.AsRef.js new file mode 100644 index 0000000000..bb17982618 --- /dev/null +++ b/book/rust-docs/implementors/core/convert/trait.AsRef.js @@ -0,0 +1,6 @@ +(function() {var implementors = { +"places":[["impl AsRef<SqlInterruptHandle> for PlacesConnection"],["impl AsRef<SqlInterruptHandle> for SharedPlacesDb"]], +"rc_crypto":[["impl AsRef<[u8]> for Digest"],["impl AsRef<[u8]> for Signature"]], +"sync_guid":[["impl AsRef<[u8]> for Guid"],["impl AsRef<str> for Guid"]], +"viaduct":[["impl AsRef<[u8]> for HeaderName"],["impl AsRef<str> for HeaderName"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/core/convert/trait.From.js b/book/rust-docs/implementors/core/convert/trait.From.js new file mode 100644 index 0000000000..b8578a4129 --- /dev/null +++ b/book/rust-docs/implementors/core/convert/trait.From.js @@ -0,0 +1,22 @@ +(function() {var implementors = { +"autofill":[["impl From<Error> for Error"],["impl From<Error> for Error"],["impl From<Error> for Error"],["impl From<InternalAddress> for Address"],["impl From<EncryptorDecryptorError> for Error"],["impl From<Interrupted> for Error"],["impl From<Error> for Error"],["impl From<InternalCreditCard> for CreditCard"]], +"fxa_client":[["impl From<Error> for Error"],["impl From<Error> for Error"],["impl From<Error> for Error"],["impl From<Error> for Error"],["impl From<JwCryptoError> for Error"],["impl From<UnexpectedStatus> for Error"],["impl From<Error> for Error"],["impl From<Error> for Error"],["impl From<ParseError> for Error"],["impl From<FromHexError> for Error"],["impl From<FromUtf8Error> for Error"],["impl From<DecodeError> for Error"],["impl From<TryFromIntError> for Error"]], +"logins":[["impl From<Error> for Error"],["impl From<Error> for Error"],["impl From<ParseError> for Error"],["impl From<InvalidLogin> for Error"],["impl From<Error> for Error"],["impl From<EncryptorDecryptorError> for Error"],["impl From<Error> for Error"],["impl From<Interrupted> for Error"]], +"nimbus":[["impl From<TryFromIntError> for NimbusError"],["impl From<Error> for NimbusError"],["impl From<UnexpectedUniFFICallbackError> for NimbusError"],["impl From<TryFromSliceError> for NimbusError"],["impl From<Error> for NimbusError"],["impl From<Error> for NimbusError"],["impl From<BehaviorError> for NimbusError"],["impl From<HashMap<String, MultiIntervalCounter, RandomState>> for EventStore"],["impl From<Vec<(String, MultiIntervalCounter), Global>> for EventStore"],["impl From<AppContext> for TargetingAttributes"],["impl From<Branch> for ExperimentBranch"],["impl From<StoreError> for NimbusError"],["impl<'a> From<EvaluationError<'a>> for NimbusError"],["impl From<Experiment> for AvailableExperiment"],["impl From<ParseError> for NimbusError"],["impl From<RemoteSettingsError> for NimbusError"],["impl From<ParseIntError> for NimbusError"]], +"nimbus_cli":[["impl From<&Cli> for NimbusApp"],["impl From<OpenArgs> for AppOpenArgs"],["impl From<&ExperimentListFilterArgs> for ExperimentListFilter"]], +"nimbus_fml":[["impl From<Error> for FMLError"],["impl From<PropDef> for FieldBody"],["impl From<ObjectDef> for ObjectBody"],["impl From<Error> for FMLError"],["impl From<Error> for FMLError"],["impl From<&Path> for FilePath"],["impl From<Error> for FMLError"],["impl From<FeatureDef> for FeatureBody"],["impl From<ClientError> for FMLError"],["impl From<TypeRef> for ExperimentManifestPropType"],["impl From<VariantDef> for EnumVariantBody"],["impl From<Error> for FMLError"],["impl From<Value> for DefaultBlock"],["impl From<FeatureManifest> for ManifestFrontEnd"],["impl From<Error> for FMLError"],["impl From<ParseError> for FMLError"],["impl From<Box<TypeRef, Global>> for ExperimentManifestPropType"],["impl From<EnumDef> for EnumBody"],["impl From<PropDef> for FeatureFieldBody"],["impl From<Error> for FMLError"]], +"nss":[["impl From<DecodeError> for Error"],["impl From<ErrorKind> for Error"],["impl From<TryFromIntError> for ErrorKind"],["impl From<&HashAlgorithm> for SECOidTag"],["impl From<DecodeError> for ErrorKind"],["impl From<TryFromIntError> for Error"]], +"places":[["impl From<VisitTransitionSet> for u16"],["impl From<Separator> for Item"],["impl From<BookmarkRootGuid> for SyncGuid"],["impl From<UpdatableBookmark> for UpdatableItem"],["impl From<InsertableBookmark> for InsertableItem"],["impl From<LivemarkRecord> for BookmarkItemRecord"],["impl From<FolderNode> for BookmarkTreeNode"],["impl From<Interrupted> for Error"],["impl From<BookmarkData> for Item"],["impl From<FolderRecord> for BookmarkItemRecord"],["impl From<Guid> for BookmarkRecordId"],["impl From<UpdatableFolder> for UpdatableItem"],["impl From<InsertableFolder> for InsertableItem"],["impl From<Utf8Error> for Error"],["impl From<SeparatorNode> for BookmarkTreeNode"],["impl From<InvalidMetadataObservation> for Error"],["impl From<BookmarkNode> for BookmarkTreeNode"],["impl From<Folder> for Item"],["impl From<Error> for Error"],["impl From<BookmarkRecordId> for SyncGuid"],["impl From<InvalidPlaceInfo> for Error"],["impl From<InsertableSeparator> for InsertableItem"],["impl From<UpdatableSeparator> for UpdatableItem"],["impl From<SyncedBookmarkValidity> for Validity"],["impl From<Error> for Error"],["impl From<SystemTime> for ServerVisitTimestamp"],["impl From<RowId> for i64"],["impl From<Error> for Error"],["impl From<Kind> for SyncedBookmarkKind"],["impl From<ServerVisitTimestamp> for Timestamp"],["impl From<BookmarkTreeNode> for InsertableItem"],["impl From<Error> for Error"],["impl From<Error> for Error"],["impl From<Timestamp> for ServerVisitTimestamp"],["impl From<Corruption> for Error"],["impl From<BookmarkRecord> for BookmarkItemRecord"],["impl From<SearchResult> for FfiSearchResult"],["impl From<Error> for Error"],["impl From<ParseError> for Error"],["impl From<SyncedBookmarkKind> for Kind"],["impl From<QueryRecord> for BookmarkItemRecord"],["impl From<SeparatorRecord> for BookmarkItemRecord"]], +"push":[["impl From<Error> for PushError"],["impl From<Error> for PushError"],["impl From<ParseError> for PushError"],["impl From<DecodeError> for PushError"],["impl From<Error> for PushError"],["impl From<Box<ErrorKind, Global>> for PushError"],["impl From<Error> for PushError"],["impl From<Error> for PushError"]], +"rc_crypto":[["impl From<Error> for ErrorKind"],["impl From<Error> for Error"],["impl From<TryFromIntError> for Error"],["impl From<ErrorKind> for Error"],["impl From<Error> for CryptoError"],["impl From<Error> for Error"],["impl From<TryFromIntError> for ErrorKind"]], +"rc_log_ffi":[["impl From<Level> for LogLevel"]], +"remote_settings":[["impl From<Error> for RemoteSettingsError"],["impl From<ParseError> for RemoteSettingsError"],["impl From<Error> for RemoteSettingsError"],["impl From<Error> for RemoteSettingsError"]], +"sql_support":[["impl From<Error> for Error"],["impl<'conn> From<CachedStatement<'conn>> for MaybeCached<'conn>"],["impl<'conn> From<Statement<'conn>> for MaybeCached<'conn>"]], +"sync15":[["impl From<Error> for Error"],["impl From<Error> for Error"],["impl From<Vec<OutgoingBso, Global>> for ApplyResults"],["impl From<Error> for Error"],["impl From<Error> for Error"],["impl From<FromUtf8Error> for Error"],["impl From<UnexpectedStatus> for Error"],["impl From<Error> for Error"],["impl<'a> From<&'a Error> for SyncFailure"],["impl From<Interrupted> for Error"],["impl From<Guid> for OutgoingEnvelope"],["impl From<DecodeError> for Error"],["impl From<ParseError> for Error"]], +"sync_guid":[["impl From<Vec<u8, Global>> for Guid"],["impl<'a> From<&'a [u8]> for Guid"],["impl From<Guid> for String"],["impl<'a> From<&'a str> for Guid"],["impl From<Guid> for Vec<u8>"],["impl From<String> for Guid"],["impl<'a> From<&'a &str> for Guid"]], +"sync_manager":[["impl From<Error> for SyncManagerError"],["impl From<ParseError> for SyncManagerError"],["impl From<Error> for SyncManagerError"],["impl From<Error> for SyncManagerError"],["impl From<ServiceStatus> for ServiceStatus"],["impl From<Error> for SyncManagerError"],["impl From<Error> for SyncManagerError"],["impl From<Interrupted> for SyncManagerError"]], +"tabs":[["impl From<ParseError> for Error"],["impl From<Error> for Error"],["impl From<Error> for TabsApiError"],["impl From<Error> for Error"],["impl From<Error> for Error"]], +"types":[["impl From<Timestamp> for u64"],["impl From<u64> for Timestamp"],["impl From<SystemTime> for Timestamp"],["impl From<Timestamp> for SystemTime"]], +"viaduct":[["impl From<ParseError> for Error"],["impl From<Headers> for HashMap<String, String>"],["impl From<Cow<'static, str>> for HeaderName"],["impl From<HeaderName> for String"],["impl From<HeaderName> for Cow<'static, str>"],["impl From<HeaderName> for Vec<u8>"],["impl From<&'static str> for HeaderName"],["impl From<String> for HeaderName"]], +"webext_storage":[["impl From<Error> for Error"],["impl From<Error> for Error"],["impl From<Interrupted> for ErrorKind"],["impl From<Error> for ExternError"],["impl From<Error> for Error"],["impl From<Error> for Error"],["impl From<Interrupted> for Error"],["impl From<Error> for ErrorKind"],["impl From<Error> for ErrorKind"],["impl From<Utf8Error> for ErrorKind"],["impl From<Error> for Error"],["impl From<Utf8Error> for Error"],["impl From<Error> for ErrorKind"],["impl From<ErrorKind> for Error"],["impl From<Error> for ErrorKind"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/core/convert/trait.TryFrom.js b/book/rust-docs/implementors/core/convert/trait.TryFrom.js new file mode 100644 index 0000000000..06469e0111 --- /dev/null +++ b/book/rust-docs/implementors/core/convert/trait.TryFrom.js @@ -0,0 +1,8 @@ +(function() {var implementors = { +"fxa_client":[["impl TryFrom<String> for DeviceCapability"],["impl TryFrom<Url> for AuthorizationParameters"]], +"nimbus":[["impl TryFrom<&Database> for EventStore"],["impl TryFrom<String> for Version"],["impl TryFrom<&str> for Version"]], +"nimbus_cli":[["impl TryFrom<&ExperimentListSource> for Value"],["impl TryFrom<&Cli> for LaunchableApp"],["impl TryFrom<&Cli> for ExperimentListSource"],["impl<'a> TryFrom<&'a Value> for ExperimentInfo<'a>"],["impl TryFrom<&Cli> for AppCommand"],["impl TryFrom<&ManifestSource> for FeatureManifest"],["impl TryFrom<&Cli> for ExperimentSource"],["impl TryFrom<&Path> for ExperimentListSource"],["impl TryFrom<&ExperimentSource> for Value"],["impl TryFrom<&ExperimentListArgs> for ExperimentListSource"],["impl TryFrom<&ExperimentArgs> for ExperimentSource"]], +"nimbus_fml":[["impl TryFrom<&str> for TargetLanguage"],["impl TryFrom<String> for TargetLanguage"],["impl TryFrom<&OsStr> for TargetLanguage"],["impl TryFrom<&Path> for TargetLanguage"],["impl TryFrom<FeatureManifest> for BTreeMap<String, ExperimenterFeature>"],["impl TryFrom<&FilePath> for ModuleId"],["impl TryFrom<&LoaderConfig> for FileLoader"]], +"places":[["impl TryFrom<u16> for VisitTransitionSet"],["impl TryFrom<u8> for VisitType"]], +"sync15":[["impl TryFrom<&str> for SyncEngineId"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/core/default/trait.Default.js b/book/rust-docs/implementors/core/default/trait.Default.js new file mode 100644 index 0000000000..a1d15457cb --- /dev/null +++ b/book/rust-docs/implementors/core/default/trait.Default.js @@ -0,0 +1,17 @@ +(function() {var implementors = { +"autofill":[["impl Default for UpdatableCreditCardFields"],["impl Default for InternalCreditCard"],["impl Default for Address"],["impl Default for InternalAddress"],["impl Default for AddressPayload"],["impl Default for UpdatableAddressFields"],["impl Default for CreditCard"],["impl Default for Metadata"]], +"error_support":[["impl Default for ErrorReporting"]], +"logins":[["impl Default for LoginEntry"],["impl Default for SecureLoginFields"],["impl Default for EncryptedLogin"],["impl Default for LoginFields"],["impl Default for RecordFields"],["impl Default for Login"]], +"nimbus":[["impl Default for BucketConfig"],["impl Default for Branch"],["impl Default for AppContext"],["impl Default for IntervalData"],["impl Default for FeatureConfig"],["impl Default for AvailableRandomizationUnits"],["impl Default for DatabaseCache"],["impl Default for MultiIntervalCounter"],["impl Default for Experiment"],["impl Default for Version"],["impl Default for IntervalConfig"],["impl Default for RandomizationUnit"],["impl Default for InternalMutableState"],["impl Default for TargetingAttributes"],["impl Default for EventStore"]], +"nimbus_cli":[["impl Default for ExperimentListFilter"],["impl Default for ExperimentListArgs"],["impl<'a> Default for ExperimentInfo<'a>"],["impl Default for AppOpenArgs"],["impl<'a> Default for StartAppProtocol<'a>"],["impl Default for OpenArgs"],["impl Default for ExperimentListSourceArgs"],["impl Default for ExperimentArgs"],["impl<'a> Default for DateRange<'a>"],["impl Default for ExperimentListFilterArgs"],["impl Default for ManifestArgs"]], +"nimbus_fml":[["impl Default for ModuleId"],["impl Default for FeatureManifest"],["impl Default for ExperimenterFeatureProperty"],["impl Default for ImportBlock"],["impl Default for AboutBlock"],["impl Default for Types"],["impl Default for SwiftAboutBlock"],["impl Default for ConcreteCodeOracle"],["impl Default for KotlinAboutBlock"],["impl Default for FeatureMetadata"],["impl Default for ObjectDef"],["impl Default for EnumDef"],["impl Default for FeatureDef"],["impl Default for LoaderConfig"],["impl Default for ExperimenterFeature"],["impl Default for ManifestFrontEnd"],["impl Default for VariantDef"],["impl Default for ConcreteCodeOracle"]], +"places":[["impl Default for HistoryMigrationResult"],["impl Default for VisitTransitionSet"],["impl Default for ServerVisitTimestamp"],["impl Default for Folder"],["impl Default for UpdatableFolder"],["impl Default for UpdatableBookmark"],["impl Default for RowId"],["impl Default for FrecencySettings"],["impl Default for SearchBehavior"],["impl Default for SeparatorNode"],["impl Default for HistoryRecordVisit"],["impl Default for FolderNode"],["impl Default for UpdateTreeLocation"]], +"push":[["impl Default for Protocol"]], +"remote_settings":[["impl Default for GetItemsOptions"]], +"sync15":[["impl Default for SyncTelemetry"],["impl Default for MemoryCachedState"],["impl Default for Problem"],["impl Default for Validation"],["impl Default for ServerTimestamp"],["impl Default for CollectionRequest"],["impl Default for EngineOutgoing"],["impl Default for ApplyResults"],["impl Default for OutgoingEnvelope"],["impl<'a> Default for SyncRequestInfo<'a>"],["impl Default for EngineIncoming"],["impl Default for SyncTelemetryPing"],["impl Default for DeviceType"]], +"sync_guid":[["impl Default for Guid"]], +"sync_manager":[["impl Default for SyncManager"]], +"types":[["impl Default for Timestamp"]], +"viaduct":[["impl Default for Headers"]], +"webext_storage":[["impl Default for MigrationInfo"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/core/error/trait.Error.js b/book/rust-docs/implementors/core/error/trait.Error.js new file mode 100644 index 0000000000..efb36ec060 --- /dev/null +++ b/book/rust-docs/implementors/core/error/trait.Error.js @@ -0,0 +1,21 @@ +(function() {var implementors = { +"as_ohttp_client":[["impl Error for OhttpError"]], +"autofill":[["impl Error for AutofillApiError"],["impl Error for Error"]], +"crashtest":[["impl Error for CrashTestError"]], +"fxa_client":[["impl Error for Error"],["impl Error for FxaError"]], +"interrupt_support":[["impl Error for Interrupted"]], +"logins":[["impl Error for InvalidLogin"],["impl Error for Error"],["impl Error for LoginsApiError"]], +"nimbus":[["impl Error for BehaviorError"],["impl Error for NimbusError"]], +"nimbus_fml":[["impl Error for ClientError"],["impl Error for FMLError"]], +"nss":[["impl Error for ErrorKind"],["impl Error for Error"]], +"places":[["impl Error for Corruption"],["impl Error for Error"],["impl Error for InvalidVisitType"],["impl Error for InvalidPlaceInfo"],["impl Error for InvalidMetadataObservation"],["impl Error for PlacesApiError"]], +"push":[["impl Error for PushApiError"],["impl Error for PushError"]], +"rc_crypto":[["impl Error for ErrorKind"],["impl Error for Error"]], +"remote_settings":[["impl Error for RemoteSettingsError"]], +"sql_support":[["impl Error for Error"]], +"sync15":[["impl Error for Error"]], +"sync_manager":[["impl Error for SyncManagerError"]], +"tabs":[["impl Error for TabsApiError"],["impl Error for Error"]], +"viaduct":[["impl Error for UnexpectedStatus"],["impl Error for Error"],["impl Error for InvalidHeaderName"]], +"webext_storage":[["impl Error for ErrorKind"],["impl Error for Error"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/core/fmt/trait.Binary.js b/book/rust-docs/implementors/core/fmt/trait.Binary.js new file mode 100644 index 0000000000..aacbe0188e --- /dev/null +++ b/book/rust-docs/implementors/core/fmt/trait.Binary.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"places":[["impl Binary for SearchBehavior"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/core/fmt/trait.Debug.js b/book/rust-docs/implementors/core/fmt/trait.Debug.js new file mode 100644 index 0000000000..c39fa7e03a --- /dev/null +++ b/book/rust-docs/implementors/core/fmt/trait.Debug.js @@ -0,0 +1,30 @@ +(function() {var implementors = { +"as_ohttp_client":[["impl Debug for OhttpError"]], +"autofill":[["impl Debug for InternalCreditCard"],["impl Debug for CreditCard"],["impl<T: Debug> Debug for IncomingState<T>"],["impl<T: Debug> Debug for MergeResult<T>"],["impl Debug for Metadata"],["impl Debug for UpdatableCreditCardFields"],["impl Debug for AutofillApiError"],["impl Debug for UpdatableAddressFields"],["impl Debug for Address"],["impl Debug for InternalAddress"],["impl Debug for Error"]], +"crashtest":[["impl Debug for CrashTestError"]], +"error_support":[["impl Debug for ErrorReporting"]], +"examples_fxa_client":[["impl Debug for Server"]], +"fxa_client":[["impl Debug for SendTabPayload"],["impl Debug for LocalDevice"],["impl Debug for TabHistoryEntry"],["impl Debug for FxaConfig"],["impl Debug for DeviceCapability"],["impl Debug for FxaError"],["impl Debug for AccountEvent"],["impl Debug for AccessTokenInfo"],["impl Debug for FxaRustAuthState"],["impl Debug for Device"],["impl Debug for ScopedKey"],["impl Debug for DevicePushSubscription"],["impl Debug for IncomingDeviceCommand"],["impl Debug for FxaServer"],["impl Debug for Error"]], +"interrupt_support":[["impl Debug for SqlInterruptScope"],["impl Debug for Interrupted"],["impl Debug for SqlInterruptHandle"]], +"logins":[["impl Debug for LoginEntry"],["impl Debug for LoginFields"],["impl Debug for Login"],["impl Debug for SecureLoginFields"],["impl Debug for RecordFields"],["impl Debug for InvalidLogin"],["impl Debug for Error"],["impl Debug for LoginsApiError"],["impl Debug for EncryptedLogin"]], +"nimbus":[["impl Debug for NimbusError"],["impl Debug for EnrolledFeature"],["impl Debug for EnrollmentStatus"],["impl Debug for BehaviorError"],["impl Debug for EventStore"],["impl Debug for EventQueryType"],["impl Debug for MultiIntervalCounter"],["impl Debug for TargetingAttributes"],["impl Debug for IntervalData"],["impl Debug for SingleIntervalCounter"],["impl Debug for Interval"],["impl Debug for EnrolledExperiment"],["impl Debug for Experiment"],["impl Debug for Version"],["impl Debug for BucketConfig"],["impl Debug for Branch"],["impl Debug for AppContext"],["impl Debug for FeatureConfig"],["impl Debug for RandomizationUnit"],["impl Debug for IntervalConfig"]], +"nimbus_cli":[["impl Debug for AppOpenArgs"],["impl Debug for NimbusApp"],["impl Debug for ExperimentListArgs"],["impl<'a> Debug for ExperimentInfo<'a>"],["impl Debug for OpenArgs"],["impl Debug for ExperimentListFilterArgs"],["impl Debug for ManifestSource"],["impl Debug for ExperimentSource"],["impl Debug for ExperimentArgs"],["impl Debug for ExperimentListSourceArgs"],["impl Debug for ExperimentListSource"],["impl Debug for AppCommand"],["impl Debug for ManifestArgs"],["impl<'a> Debug for DateRange<'a>"],["impl Debug for LaunchableApp"],["impl Debug for ExperimentListFilter"],["impl<'a> Debug for StartAppProtocol<'a>"]], +"nimbus_fml":[["impl Debug for EnumBody"],["impl Debug for ObjectBody"],["impl Debug for EnumVariantBody"],["impl Debug for VariantDef"],["impl Debug for FieldBody"],["impl Debug for FMLError"],["impl Debug for FilePath"],["impl Debug for FeatureBody"],["impl Debug for TargetLanguage"],["impl Debug for TypeRef"],["impl Debug for FromStringDef"],["impl Debug for FeatureDef"],["impl Debug for DefaultBlock"],["impl Debug for KotlinAboutBlock"],["impl Debug for ModuleId"],["impl Debug for FileLoader"],["impl Debug for FeatureManifest"],["impl Debug for ClientError"],["impl Debug for FeatureMetadata"],["impl Debug for Types"],["impl Debug for SwiftAboutBlock"],["impl Debug for ObjectDef"],["impl Debug for ExperimenterFeature"],["impl Debug for PropDef"],["impl Debug for Parser"],["impl Debug for EnumDef"],["impl Debug for DocumentationLink"],["impl Debug for ManifestFrontEnd"],["impl Debug for ImportBlock"],["impl Debug for AboutBlock"],["impl Debug for FeatureFieldBody"],["impl<'a> Debug for ImportedModule<'a>"],["impl Debug for ExperimenterFeatureProperty"]], +"nss":[["impl Debug for ErrorKind"],["impl Debug for Error"],["impl Debug for HashAlgorithm"],["impl Debug for EcKey"],["impl Debug for Operation"],["impl Debug for Curve"]], +"nss_build_common":[["impl Debug for LinkingKind"],["impl Debug for NoNssDir"]], +"nss_sys":[["impl Debug for SECItemStr"]], +"places":[["impl Debug for FolderNode"],["impl Debug for VisitType"],["impl Debug for RowId"],["impl Debug for UpdatableBookmark"],["impl Debug for AddableVisit"],["impl Debug for Error"],["impl Debug for UpdatableItem"],["impl Debug for SyncedBookmarkValidity"],["impl Debug for VisitTransitionSet"],["impl Debug for FetchedVisit"],["impl Debug for InsertableItem"],["impl Debug for PlacesDb"],["impl Debug for BookmarkData"],["impl Debug for InsertableBookmark"],["impl Debug for SearchResult"],["impl Debug for BookmarkType"],["impl Debug for Item"],["impl Debug for BookmarkRecordId"],["impl Debug for InvalidPlaceInfo"],["impl Debug for UpdatableFolder"],["impl Debug for QueryRecord"],["impl Debug for RunMaintenanceMetrics"],["impl Debug for UpdateTreeLocation"],["impl Debug for InvalidMetadataObservation"],["impl Debug for BookmarkUpdateInfo"],["impl Debug for SyncedBookmarkKind"],["impl Debug for ConnectionType"],["impl Debug for VisitObservation"],["impl Debug for HistoryMetadata"],["impl Debug for Folder"],["impl Debug for PrefixMode"],["impl Debug for SearchBehavior"],["impl Debug for SeparatorNode"],["impl Debug for BookmarkNode"],["impl Debug for InsertableFolder"],["impl Debug for HistoryMetadataObservation"],["impl Debug for SyncStatus"],["impl Debug for BookmarkRootGuid"],["impl Debug for BookmarkPosition"],["impl Debug for SearchParams"],["impl Debug for HistoryMigrationResult"],["impl Debug for BookmarkTreeNode"],["impl<'a> Debug for ValidatedTag<'a>"],["impl Debug for MatchBehavior"],["impl Debug for BookmarkItemRecord"],["impl Debug for InsertableSeparator"],["impl Debug for PageInfo"],["impl Debug for PlacesApiError"],["impl Debug for RedirectSourceType"],["impl Debug for BookmarkRecord"],["impl Debug for FrecencySettings"],["impl Debug for ServerVisitTimestamp"],["impl Debug for LivemarkRecord"],["impl Debug for FetchedPageInfo"],["impl Debug for InvalidVisitType"],["impl Debug for FetchedVisitPage"],["impl Debug for DocumentType"],["impl Debug for HistoryRecordVisit"],["impl Debug for UpdatableSeparator"],["impl Debug for Separator"],["impl Debug for AddablePlaceInfo"],["impl Debug for Corruption"],["impl Debug for SeparatorRecord"],["impl Debug for FolderRecord"],["impl Debug for HistoryRecord"]], +"protobuf_gen":[["impl Debug for ProtobufOpts"]], +"push":[["impl Debug for PushConfiguration"],["impl Debug for KeyInfo"],["impl Debug for SubscriptionInfo"],["impl Debug for SubscriptionResponse"],["impl Debug for Protocol"],["impl Debug for PushApiError"],["impl Debug for PushError"],["impl Debug for PushSubscriptionChanged"],["impl Debug for BridgeType"]], +"rc_crypto":[["impl Debug for ErrorKind"],["impl Debug for Error"]], +"rc_log_ffi":[["impl Debug for LogLevel"]], +"remote_settings":[["impl Debug for RemoteSettingsRecord"],["impl Debug for SortOrder"],["impl Debug for GetItemsOptions"],["impl Debug for RemoteSettingsConfig"],["impl Debug for Attachment"],["impl Debug for RemoteSettingsError"],["impl Debug for RemoteSettingsResponse"]], +"sql_support":[["impl<'a, F: Debug> Debug for RepeatDisplay<'a, F>"],["impl Debug for Error"]], +"sync15":[["impl Debug for IncomingEnvelope"],["impl Debug for EngineOutgoing"],["impl Debug for Command"],["impl Debug for OutgoingEnvelope"],["impl Debug for MemoryCachedState"],["impl Debug for CommandStatus"],["impl Debug for DeviceType"],["impl Debug for SyncTelemetry"],["impl Debug for EngineSyncAssociation"],["impl Debug for Problem"],["impl Debug for CollSyncIds"],["impl Debug for IncomingBso"],["impl Debug for Sync15StorageClientInit"],["impl<T: Debug> Debug for IncomingKind<T>"],["impl<T: Debug> Debug for IncomingContent<T>"],["impl Debug for Event"],["impl Debug for ServerTimestamp"],["impl Debug for SyncFailure"],["impl Debug for OutgoingBso"],["impl Debug for ClientData"],["impl Debug for OutgoingEncryptedBso"],["impl Debug for Validation"],["impl Debug for Settings"],["impl Debug for Sync15StorageClient"],["impl<T: Debug> Debug for Sync15ClientResponse<T>"],["impl Debug for SyncTelemetryPing"],["impl Debug for SyncEngineId"],["impl Debug for EngineIncoming"],["impl Debug for Engine"],["impl Debug for Error"],["impl Debug for IncomingEncryptedBso"],["impl Debug for RequestOrder"],["impl Debug for ApplyResults"],["impl Debug for ServiceStatus"],["impl Debug for CollectionRequest"],["impl<'a> Debug for SyncRequestInfo<'a>"],["impl Debug for EncryptedPayload"],["impl Debug for KeyBundle"],["impl Debug for RemoteClient"],["impl Debug for SyncResult"]], +"sync_guid":[["impl Debug for Guid"]], +"sync_manager":[["impl Debug for SyncReason"],["impl Debug for SyncManagerError"],["impl Debug for ServiceStatus"],["impl Debug for SyncParams"],["impl Debug for SyncEngineSelection"],["impl Debug for SyncAuthInfo"],["impl Debug for SyncResult"],["impl Debug for DeviceSettings"]], +"tabs":[["impl Debug for TabsApiError"],["impl Debug for Error"],["impl Debug for ClientRemoteTabs"]], +"types":[["impl Debug for Timestamp"]], +"viaduct":[["impl Debug for Error"],["impl Debug for UnexpectedStatus"],["impl Debug for HeaderName"],["impl Debug for Response"],["impl Debug for Settings"],["impl Debug for InvalidHeaderName"],["impl Debug for Method"],["impl Debug for Header"],["impl Debug for Headers"],["impl Debug for Request"]], +"webext_storage":[["impl Debug for Error"],["impl Debug for MigrationInfo"],["impl Debug for QuotaReason"],["impl Debug for ErrorKind"],["impl Debug for UsageInfo"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/core/fmt/trait.Display.js b/book/rust-docs/implementors/core/fmt/trait.Display.js new file mode 100644 index 0000000000..589c4edf35 --- /dev/null +++ b/book/rust-docs/implementors/core/fmt/trait.Display.js @@ -0,0 +1,24 @@ +(function() {var implementors = { +"as_ohttp_client":[["impl Display for OhttpError"]], +"autofill":[["impl Display for Error"],["impl Display for AutofillApiError"]], +"crashtest":[["impl Display for CrashTestError"]], +"fxa_client":[["impl Display for FxaError"],["impl Display for Error"]], +"interrupt_support":[["impl Display for Interrupted"]], +"logins":[["impl Display for LoginsApiError"],["impl Display for InvalidLogin"],["impl Display for Error"]], +"nimbus":[["impl Display for Interval"],["impl Display for BehaviorError"],["impl Display for EventQueryType"],["impl Display for NimbusError"]], +"nimbus_cli":[["impl Display for ManifestSource"],["impl Display for DateRange<'_>"],["impl Display for ExperimentSource"]], +"nimbus_fml":[["impl Display for TypeRef"],["impl Display for FMLError"],["impl Display for ExperimentManifestPropType"],["impl Display for EnumCodeDeclaration"],["impl Display for ObjectCodeDeclaration"],["impl Display for FeatureCodeDeclaration"],["impl Display for ClientError"],["impl Display for EnumCodeDeclaration"],["impl<'a> Display for ImportedModuleInitialization<'a>"],["impl Display for VariablesType"],["impl Display for ObjectCodeDeclaration"],["impl<'a> Display for ImportedModuleInitialization<'a>"],["impl Display for FilePath"],["impl<'a> Display for FeatureManifestDeclaration<'a>"],["impl Display for FeatureCodeDeclaration"],["impl<'a> Display for FeatureManifestDeclaration<'a>"],["impl Display for ModuleId"]], +"nss":[["impl Display for Error"],["impl Display for ErrorKind"]], +"places":[["impl Display for InvalidPlaceInfo"],["impl Display for InvalidVisitType"],["impl Display for Corruption"],["impl Display for Error"],["impl Display for ServerVisitTimestamp"],["impl Display for InvalidMetadataObservation"],["impl Display for PlacesApiError"],["impl Display for RowId"]], +"push":[["impl Display for PushError"],["impl Display for PushApiError"],["impl Display for Protocol"],["impl Display for BridgeType"]], +"rc_crypto":[["impl Display for Error"],["impl Display for ErrorKind"]], +"remote_settings":[["impl Display for RemoteSettingsError"]], +"sql_support":[["impl Display for Error"],["impl<'a, F> Display for RepeatDisplay<'a, F>where\n F: Fn(usize, &mut Formatter<'_>) -> Result,"]], +"sync15":[["impl Display for SyncEngineId"],["impl Display for Error"],["impl Display for ServerTimestamp"],["impl Display for RequestOrder"]], +"sync_guid":[["impl Display for Guid"]], +"sync_manager":[["impl Display for SyncManagerError"]], +"tabs":[["impl Display for TabsApiError"],["impl Display for Error"]], +"types":[["impl Display for Timestamp"]], +"viaduct":[["impl Display for Error"],["impl Display for Header"],["impl Display for Method"],["impl Display for InvalidHeaderName"],["impl Display for HeaderName"],["impl Display for UnexpectedStatus"]], +"webext_storage":[["impl Display for Error"],["impl Display for ErrorKind"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/core/fmt/trait.LowerHex.js b/book/rust-docs/implementors/core/fmt/trait.LowerHex.js new file mode 100644 index 0000000000..6ce4c0b736 --- /dev/null +++ b/book/rust-docs/implementors/core/fmt/trait.LowerHex.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"places":[["impl LowerHex for SearchBehavior"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/core/fmt/trait.Octal.js b/book/rust-docs/implementors/core/fmt/trait.Octal.js new file mode 100644 index 0000000000..c01ed8f63b --- /dev/null +++ b/book/rust-docs/implementors/core/fmt/trait.Octal.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"places":[["impl Octal for SearchBehavior"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/core/fmt/trait.UpperHex.js b/book/rust-docs/implementors/core/fmt/trait.UpperHex.js new file mode 100644 index 0000000000..3c76a8d624 --- /dev/null +++ b/book/rust-docs/implementors/core/fmt/trait.UpperHex.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"places":[["impl UpperHex for SearchBehavior"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/core/hash/trait.Hash.js b/book/rust-docs/implementors/core/hash/trait.Hash.js new file mode 100644 index 0000000000..01463b1dfe --- /dev/null +++ b/book/rust-docs/implementors/core/hash/trait.Hash.js @@ -0,0 +1,13 @@ +(function() {var implementors = { +"autofill":[["impl Hash for Address"]], +"fxa_client":[["impl Hash for DeviceCapability"]], +"logins":[["impl Hash for LoginFields"],["impl Hash for EncryptedLogin"],["impl Hash for RecordFields"],["impl Hash for SecureLoginFields"],["impl Hash for LoginEntry"],["impl Hash for Login"]], +"nimbus":[["impl Hash for Interval"],["impl Hash for EnrollmentStatus"]], +"nimbus_fml":[["impl Hash for ModuleId"],["impl Hash for TargetLanguage"],["impl Hash for TypeRef"]], +"places":[["impl Hash for VisitType"],["impl Hash for BookmarkRecordId"],["impl Hash for SearchBehavior"],["impl Hash for SyncStatus"],["impl Hash for VisitTransitionSet"],["impl Hash for SyncedBookmarkValidity"],["impl Hash for BookmarkType"],["impl Hash for RowId"],["impl Hash for BookmarkRootGuid"],["impl Hash for SyncedBookmarkKind"],["impl Hash for ServerVisitTimestamp"]], +"remote_settings":[["impl Hash for SortOrder"]], +"sync15":[["impl Hash for KeyBundle"],["impl Hash for SyncEngineId"],["impl Hash for Sync15StorageClientInit"],["impl Hash for RemoteClient"],["impl Hash for RequestOrder"],["impl Hash for CommandStatus"],["impl Hash for Settings"],["impl Hash for Command"],["impl Hash for DeviceType"]], +"sync_guid":[["impl Hash for Guid"]], +"types":[["impl Hash for Timestamp"]], +"viaduct":[["impl Hash for HeaderName"],["impl Hash for Method"],["impl Hash for Header"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/core/iter/traits/collect/trait.Extend.js b/book/rust-docs/implementors/core/iter/traits/collect/trait.Extend.js new file mode 100644 index 0000000000..085b529e0d --- /dev/null +++ b/book/rust-docs/implementors/core/iter/traits/collect/trait.Extend.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"places":[["impl Extend<SearchBehavior> for SearchBehavior"],["impl Extend<VisitType> for VisitTransitionSet"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/core/iter/traits/collect/trait.FromIterator.js b/book/rust-docs/implementors/core/iter/traits/collect/trait.FromIterator.js new file mode 100644 index 0000000000..4fe8219a31 --- /dev/null +++ b/book/rust-docs/implementors/core/iter/traits/collect/trait.FromIterator.js @@ -0,0 +1,4 @@ +(function() {var implementors = { +"places":[["impl FromIterator<SearchBehavior> for SearchBehavior"],["impl FromIterator<VisitType> for VisitTransitionSet"]], +"viaduct":[["impl FromIterator<Header> for Headers"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/core/iter/traits/collect/trait.IntoIterator.js b/book/rust-docs/implementors/core/iter/traits/collect/trait.IntoIterator.js new file mode 100644 index 0000000000..9d02ceafb9 --- /dev/null +++ b/book/rust-docs/implementors/core/iter/traits/collect/trait.IntoIterator.js @@ -0,0 +1,4 @@ +(function() {var implementors = { +"places":[["impl IntoIterator for VisitTransitionSet"]], +"viaduct":[["impl<'a> IntoIterator for &'a Headers"],["impl IntoIterator for Headers"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/core/marker/trait.Copy.js b/book/rust-docs/implementors/core/marker/trait.Copy.js new file mode 100644 index 0000000000..087b11d863 --- /dev/null +++ b/book/rust-docs/implementors/core/marker/trait.Copy.js @@ -0,0 +1,14 @@ +(function() {var implementors = { +"autofill":[["impl Copy for Metadata"]], +"examples_fxa_client":[["impl Copy for Server"]], +"nss":[["impl Copy for HashAlgorithm"],["impl Copy for Operation"],["impl Copy for Curve"]], +"nss_build_common":[["impl Copy for LinkingKind"]], +"nss_sys":[["impl Copy for SECItemStr"],["impl Copy for SECKEYKEAPublicKeyStr"],["impl Copy for SECKEYPQGParamsStr"],["impl Copy for PLArena"],["impl Copy for SECKEYECPublicKeyStr"],["impl Copy for SECKEYFortezzaPublicKeyStr"],["impl Copy for CK_ATTRIBUTE"],["impl Copy for SECKEYDSAPublicKeyStr"],["impl Copy for SECKEYRSAPublicKeyStr"],["impl Copy for ECPointEncoding"],["impl Copy for SECAlgorithmIDStr"],["impl Copy for SECKEYKEAParamsStr"],["impl Copy for SECOidDataStr"],["impl Copy for PLArenaPool"],["impl Copy for SECKEYDHPublicKeyStr"]], +"places":[["impl Copy for SyncedBookmarkKind"],["impl Copy for PrefixMode"],["impl Copy for BookmarkPosition"],["impl Copy for BookmarkRootGuid"],["impl Copy for SyncedBookmarkValidity"],["impl Copy for ConnectionType"],["impl Copy for SyncStatus"],["impl Copy for BookmarkType"],["impl Copy for DocumentType"],["impl Copy for VisitType"],["impl Copy for SearchBehavior"],["impl Copy for RedirectSourceType"],["impl Copy for VisitTransitionSet"],["impl Copy for InvalidVisitType"],["impl Copy for ServerVisitTimestamp"],["impl Copy for MatchBehavior"],["impl Copy for RowId"],["impl<'a> Copy for ValidatedTag<'a>"]], +"push":[["impl Copy for Protocol"],["impl Copy for BridgeType"]], +"rc_log_ffi":[["impl Copy for LogLevel"]], +"remote_settings":[["impl Copy for SortOrder"]], +"sync15":[["impl Copy for CommandStatus"],["impl Copy for ServerTimestamp"],["impl Copy for RequestOrder"],["impl Copy for DeviceType"]], +"types":[["impl Copy for Timestamp"]], +"viaduct":[["impl Copy for Method"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/core/marker/trait.Freeze.js b/book/rust-docs/implementors/core/marker/trait.Freeze.js new file mode 100644 index 0000000000..2c017f9ca3 --- /dev/null +++ b/book/rust-docs/implementors/core/marker/trait.Freeze.js @@ -0,0 +1,32 @@ +(function() {var implementors = { +"as_ohttp_client":[["impl Freeze for OhttpError",1,["as_ohttp_client::OhttpError"]],["impl !Freeze for OhttpSession",1,["as_ohttp_client::OhttpSession"]],["impl Freeze for OhttpResponse",1,["as_ohttp_client::OhttpResponse"]],["impl !Freeze for OhttpTestServer",1,["as_ohttp_client::OhttpTestServer"]],["impl Freeze for TestServerRequest",1,["as_ohttp_client::TestServerRequest"]]], +"autofill":[["impl Freeze for UpdatableAddressFields",1,["autofill::db::models::address::UpdatableAddressFields"]],["impl Freeze for Address",1,["autofill::db::models::address::Address"]],["impl Freeze for InternalAddress",1,["autofill::db::models::address::InternalAddress"]],["impl Freeze for UpdatableCreditCardFields",1,["autofill::db::models::credit_card::UpdatableCreditCardFields"]],["impl Freeze for CreditCard",1,["autofill::db::models::credit_card::CreditCard"]],["impl Freeze for InternalCreditCard",1,["autofill::db::models::credit_card::InternalCreditCard"]],["impl Freeze for Metadata",1,["autofill::db::models::Metadata"]],["impl Freeze for AutofillConnectionInitializer",1,["autofill::db::schema::AutofillConnectionInitializer"]],["impl !Freeze for Store",1,["autofill::db::store::Store"]],["impl !Freeze for AutofillDb",1,["autofill::db::AutofillDb"]],["impl Freeze for AutofillApiError",1,["autofill::error::AutofillApiError"]],["impl Freeze for Error",1,["autofill::error::Error"]],["impl Freeze for AddressPayload",1,["autofill::sync::address::AddressPayload"]],["impl Freeze for EngineConfig",1,["autofill::sync::engine::EngineConfig"]],["impl<T> Freeze for ConfigSyncEngine<T>",1,["autofill::sync::engine::ConfigSyncEngine"]],["impl<T> Freeze for MergeResult<T>where\n T: Freeze,",1,["autofill::sync::MergeResult"]],["impl<T> Freeze for IncomingState<T>where\n T: Freeze,",1,["autofill::sync::IncomingState"]]], +"cli_support":[["impl !Freeze for CliFxa",1,["cli_support::fxa_creds::CliFxa"]]], +"crashtest":[["impl Freeze for CrashTestError",1,["crashtest::CrashTestError"]]], +"error_support":[["impl Freeze for ErrorReporting",1,["error_support::handling::ErrorReporting"]],["impl<E> Freeze for ErrorHandling<E>where\n E: Freeze,",1,["error_support::handling::ErrorHandling"]]], +"examples_fxa_client":[["impl Freeze for DeviceArgs",1,["examples_fxa_client::devices::DeviceArgs"]],["impl Freeze for Command",1,["examples_fxa_client::devices::Command"]],["impl Freeze for SendTabArgs",1,["examples_fxa_client::send_tab::SendTabArgs"]],["impl Freeze for Command",1,["examples_fxa_client::send_tab::Command"]],["impl Freeze for Cli",1,["examples_fxa_client::Cli"]],["impl Freeze for Server",1,["examples_fxa_client::Server"]],["impl Freeze for Command",1,["examples_fxa_client::Command"]]], +"fxa_client":[["impl Freeze for AuthorizationInfo",1,["fxa_client::auth::AuthorizationInfo"]],["impl Freeze for MetricsParams",1,["fxa_client::auth::MetricsParams"]],["impl Freeze for FxaRustAuthState",1,["fxa_client::auth::FxaRustAuthState"]],["impl Freeze for LocalDevice",1,["fxa_client::device::LocalDevice"]],["impl Freeze for Device",1,["fxa_client::device::Device"]],["impl Freeze for DeviceCapability",1,["fxa_client::device::DeviceCapability"]],["impl Freeze for AttachedClient",1,["fxa_client::device::AttachedClient"]],["impl Freeze for FxaError",1,["fxa_client::error::FxaError"]],["impl Freeze for Error",1,["fxa_client::error::Error"]],["impl Freeze for Profile",1,["fxa_client::profile::Profile"]],["impl Freeze for DevicePushSubscription",1,["fxa_client::push::DevicePushSubscription"]],["impl Freeze for AccountEvent",1,["fxa_client::push::AccountEvent"]],["impl Freeze for IncomingDeviceCommand",1,["fxa_client::push::IncomingDeviceCommand"]],["impl Freeze for SendTabPayload",1,["fxa_client::push::SendTabPayload"]],["impl Freeze for TabHistoryEntry",1,["fxa_client::push::TabHistoryEntry"]],["impl Freeze for AccessTokenInfo",1,["fxa_client::token::AccessTokenInfo"]],["impl Freeze for ScopedKey",1,["fxa_client::token::ScopedKey"]],["impl Freeze for AuthorizationParameters",1,["fxa_client::token::AuthorizationParameters"]],["impl !Freeze for FirefoxAccount",1,["fxa_client::FirefoxAccount"]],["impl Freeze for FxaConfig",1,["fxa_client::FxaConfig"]],["impl Freeze for FxaServer",1,["fxa_client::FxaServer"]]], +"interrupt_support":[["impl Freeze for Interrupted",1,["interrupt_support::error::Interrupted"]],["impl Freeze for NeverInterrupts",1,["interrupt_support::interruptee::NeverInterrupts"]],["impl Freeze for ShutdownInterruptee",1,["interrupt_support::shutdown::ShutdownInterruptee"]],["impl Freeze for SqlInterruptHandle",1,["interrupt_support::sql::SqlInterruptHandle"]],["impl Freeze for SqlInterruptScope",1,["interrupt_support::sql::SqlInterruptScope"]]], +"logins":[["impl Freeze for LoginsApiError",1,["logins::error::LoginsApiError"]],["impl Freeze for Error",1,["logins::error::Error"]],["impl Freeze for InvalidLogin",1,["logins::error::InvalidLogin"]],["impl Freeze for LoginFields",1,["logins::login::LoginFields"]],["impl Freeze for SecureLoginFields",1,["logins::login::SecureLoginFields"]],["impl Freeze for RecordFields",1,["logins::login::RecordFields"]],["impl Freeze for LoginEntry",1,["logins::login::LoginEntry"]],["impl Freeze for Login",1,["logins::login::Login"]],["impl Freeze for EncryptedLogin",1,["logins::login::EncryptedLogin"]],["impl !Freeze for LoginDb",1,["logins::db::LoginDb"]],["impl !Freeze for LoginStore",1,["logins::store::LoginStore"]],["impl !Freeze for LoginsSyncEngine",1,["logins::sync::engine::LoginsSyncEngine"]]], +"nimbus":[["impl Freeze for EnrollmentStatus",1,["nimbus::enrollment::EnrollmentStatus"]],["impl Freeze for EnrolledFeature",1,["nimbus::enrollment::EnrolledFeature"]],["impl Freeze for NimbusTargetingHelper",1,["nimbus::targeting::NimbusTargetingHelper"]],["impl Freeze for NimbusError",1,["nimbus::error::NimbusError"]],["impl Freeze for BehaviorError",1,["nimbus::error::BehaviorError"]],["impl Freeze for EnrollmentStatusExtraDef",1,["nimbus::metrics::EnrollmentStatusExtraDef"]],["impl Freeze for EnrolledExperiment",1,["nimbus::schema::EnrolledExperiment"]],["impl Freeze for Experiment",1,["nimbus::schema::Experiment"]],["impl Freeze for FeatureConfig",1,["nimbus::schema::FeatureConfig"]],["impl Freeze for Branch",1,["nimbus::schema::Branch"]],["impl Freeze for BucketConfig",1,["nimbus::schema::BucketConfig"]],["impl Freeze for AvailableExperiment",1,["nimbus::schema::AvailableExperiment"]],["impl Freeze for ExperimentBranch",1,["nimbus::schema::ExperimentBranch"]],["impl Freeze for RandomizationUnit",1,["nimbus::schema::RandomizationUnit"]],["impl Freeze for AvailableRandomizationUnits",1,["nimbus::schema::AvailableRandomizationUnits"]],["impl Freeze for Version",1,["nimbus::versioning::Version"]],["impl Freeze for Interval",1,["nimbus::stateful::behavior::Interval"]],["impl Freeze for IntervalConfig",1,["nimbus::stateful::behavior::IntervalConfig"]],["impl Freeze for IntervalData",1,["nimbus::stateful::behavior::IntervalData"]],["impl Freeze for SingleIntervalCounter",1,["nimbus::stateful::behavior::SingleIntervalCounter"]],["impl Freeze for MultiIntervalCounter",1,["nimbus::stateful::behavior::MultiIntervalCounter"]],["impl Freeze for EventQueryType",1,["nimbus::stateful::behavior::EventQueryType"]],["impl Freeze for EventStore",1,["nimbus::stateful::behavior::EventStore"]],["impl !Freeze for DatabaseCache",1,["nimbus::stateful::dbcache::DatabaseCache"]],["impl Freeze for TargetingAttributes",1,["nimbus::stateful::evaluator::TargetingAttributes"]],["impl Freeze for AppContext",1,["nimbus::stateful::matcher::AppContext"]],["impl Freeze for InternalMutableState",1,["nimbus::stateful::nimbus_client::InternalMutableState"]],["impl !Freeze for NimbusClient",1,["nimbus::stateful::nimbus_client::NimbusClient"]],["impl Freeze for NimbusStringHelper",1,["nimbus::stateful::nimbus_client::NimbusStringHelper"]],["impl Freeze for StoreId",1,["nimbus::stateful::persistence::StoreId"]],["impl Freeze for SingleStore",1,["nimbus::stateful::persistence::SingleStore"]],["impl !Freeze for Database",1,["nimbus::stateful::persistence::Database"]]], +"nimbus_cli":[["impl Freeze for Cli",1,["nimbus_cli::cli::Cli"]],["impl Freeze for CliCommand",1,["nimbus_cli::cli::CliCommand"]],["impl Freeze for ManifestArgs",1,["nimbus_cli::cli::ManifestArgs"]],["impl Freeze for OpenArgs",1,["nimbus_cli::cli::OpenArgs"]],["impl Freeze for ExperimentArgs",1,["nimbus_cli::cli::ExperimentArgs"]],["impl Freeze for ExperimentListArgs",1,["nimbus_cli::cli::ExperimentListArgs"]],["impl Freeze for ExperimentListSourceArgs",1,["nimbus_cli::cli::ExperimentListSourceArgs"]],["impl Freeze for ExperimentListFilterArgs",1,["nimbus_cli::cli::ExperimentListFilterArgs"]],["impl<'a> Freeze for ExperimentInfo<'a>",1,["nimbus_cli::output::info::ExperimentInfo"]],["impl<'a> Freeze for DateRange<'a>",1,["nimbus_cli::output::info::DateRange"]],["impl Freeze for StartAppPostPayload",1,["nimbus_cli::output::server::StartAppPostPayload"]],["impl Freeze for InMemoryDb",1,["nimbus_cli::output::server::InMemoryDb"]],["impl<'a> Freeze for StartAppProtocol<'a>",1,["nimbus_cli::protocol::StartAppProtocol"]],["impl Freeze for ExperimentSource",1,["nimbus_cli::sources::experiment::ExperimentSource"]],["impl Freeze for FeatureDefaults",1,["nimbus_cli::sources::experiment::FeatureDefaults"]],["impl Freeze for ExperimentListSource",1,["nimbus_cli::sources::experiment_list::ExperimentListSource"]],["impl Freeze for ExperimentListFilter",1,["nimbus_cli::sources::filter::ExperimentListFilter"]],["impl Freeze for ManifestSource",1,["nimbus_cli::sources::manifest::ManifestSource"]],["impl Freeze for Response",1,["nimbus_cli::updater::taskcluster::Response"]],["impl Freeze for TaskClusterRegistry",1,["nimbus_cli::updater::taskcluster::TaskClusterRegistry"]],["impl Freeze for ReqwestGunzippingHttpClient",1,["nimbus_cli::updater::taskcluster::ReqwestGunzippingHttpClient"]],["impl Freeze for LaunchableApp",1,["nimbus_cli::LaunchableApp"]],["impl Freeze for NimbusApp",1,["nimbus_cli::NimbusApp"]],["impl Freeze for AppCommand",1,["nimbus_cli::AppCommand"]],["impl Freeze for AppOpenArgs",1,["nimbus_cli::AppOpenArgs"]]], +"nimbus_fml":[["impl Freeze for ExperimenterFeature",1,["nimbus_fml::backends::experimenter_manifest::ExperimenterFeature"]],["impl Freeze for ExperimenterFeatureProperty",1,["nimbus_fml::backends::experimenter_manifest::ExperimenterFeatureProperty"]],["impl Freeze for ExperimentManifestPropType",1,["nimbus_fml::backends::experimenter_manifest::ExperimentManifestPropType"]],["impl Freeze for TextCodeType",1,["nimbus_fml::backends::kotlin::gen_structs::bundled::TextCodeType"]],["impl Freeze for ImageCodeType",1,["nimbus_fml::backends::kotlin::gen_structs::bundled::ImageCodeType"]],["impl Freeze for EnumCodeType",1,["nimbus_fml::backends::kotlin::gen_structs::enum_::EnumCodeType"]],["impl Freeze for EnumCodeDeclaration",1,["nimbus_fml::backends::kotlin::gen_structs::enum_::EnumCodeDeclaration"]],["impl Freeze for FeatureCodeDeclaration",1,["nimbus_fml::backends::kotlin::gen_structs::feature::FeatureCodeDeclaration"]],["impl<'a> Freeze for ImportedModuleInitialization<'a>",1,["nimbus_fml::backends::kotlin::gen_structs::imports::ImportedModuleInitialization"]],["impl Freeze for ObjectRuntime",1,["nimbus_fml::backends::kotlin::gen_structs::object::ObjectRuntime"]],["impl Freeze for ObjectCodeType",1,["nimbus_fml::backends::kotlin::gen_structs::object::ObjectCodeType"]],["impl Freeze for ObjectCodeDeclaration",1,["nimbus_fml::backends::kotlin::gen_structs::object::ObjectCodeDeclaration"]],["impl Freeze for BooleanCodeType",1,["nimbus_fml::backends::kotlin::gen_structs::primitives::BooleanCodeType"]],["impl Freeze for IntCodeType",1,["nimbus_fml::backends::kotlin::gen_structs::primitives::IntCodeType"]],["impl Freeze for StringCodeType",1,["nimbus_fml::backends::kotlin::gen_structs::primitives::StringCodeType"]],["impl Freeze for OptionalCodeType",1,["nimbus_fml::backends::kotlin::gen_structs::structural::OptionalCodeType"]],["impl Freeze for MapCodeType",1,["nimbus_fml::backends::kotlin::gen_structs::structural::MapCodeType"]],["impl Freeze for ListCodeType",1,["nimbus_fml::backends::kotlin::gen_structs::structural::ListCodeType"]],["impl<'a> Freeze for FeatureManifestDeclaration<'a>",1,["nimbus_fml::backends::kotlin::gen_structs::FeatureManifestDeclaration"]],["impl Freeze for ConcreteCodeOracle",1,["nimbus_fml::backends::kotlin::gen_structs::ConcreteCodeOracle"]],["impl Freeze for TextCodeType",1,["nimbus_fml::backends::swift::gen_structs::bundled::TextCodeType"]],["impl Freeze for ImageCodeType",1,["nimbus_fml::backends::swift::gen_structs::bundled::ImageCodeType"]],["impl Freeze for EnumCodeType",1,["nimbus_fml::backends::swift::gen_structs::enum_::EnumCodeType"]],["impl Freeze for EnumCodeDeclaration",1,["nimbus_fml::backends::swift::gen_structs::enum_::EnumCodeDeclaration"]],["impl Freeze for FeatureCodeDeclaration",1,["nimbus_fml::backends::swift::gen_structs::feature::FeatureCodeDeclaration"]],["impl<'a> Freeze for ImportedModuleInitialization<'a>",1,["nimbus_fml::backends::swift::gen_structs::imports::ImportedModuleInitialization"]],["impl Freeze for ObjectRuntime",1,["nimbus_fml::backends::swift::gen_structs::object::ObjectRuntime"]],["impl Freeze for ObjectCodeType",1,["nimbus_fml::backends::swift::gen_structs::object::ObjectCodeType"]],["impl Freeze for ObjectCodeDeclaration",1,["nimbus_fml::backends::swift::gen_structs::object::ObjectCodeDeclaration"]],["impl Freeze for BooleanCodeType",1,["nimbus_fml::backends::swift::gen_structs::primitives::BooleanCodeType"]],["impl Freeze for IntCodeType",1,["nimbus_fml::backends::swift::gen_structs::primitives::IntCodeType"]],["impl Freeze for StringCodeType",1,["nimbus_fml::backends::swift::gen_structs::primitives::StringCodeType"]],["impl Freeze for OptionalCodeType",1,["nimbus_fml::backends::swift::gen_structs::structural::OptionalCodeType"]],["impl Freeze for MapCodeType",1,["nimbus_fml::backends::swift::gen_structs::structural::MapCodeType"]],["impl Freeze for ListCodeType",1,["nimbus_fml::backends::swift::gen_structs::structural::ListCodeType"]],["impl<'a> Freeze for FeatureManifestDeclaration<'a>",1,["nimbus_fml::backends::swift::gen_structs::FeatureManifestDeclaration"]],["impl Freeze for ConcreteCodeOracle",1,["nimbus_fml::backends::swift::gen_structs::ConcreteCodeOracle"]],["impl Freeze for VariablesType",1,["nimbus_fml::backends::VariablesType"]],["impl Freeze for CliCmd",1,["nimbus_fml::command_line::commands::CliCmd"]],["impl Freeze for GenerateStructCmd",1,["nimbus_fml::command_line::commands::GenerateStructCmd"]],["impl Freeze for GenerateExperimenterManifestCmd",1,["nimbus_fml::command_line::commands::GenerateExperimenterManifestCmd"]],["impl Freeze for GenerateSingleFileManifestCmd",1,["nimbus_fml::command_line::commands::GenerateSingleFileManifestCmd"]],["impl Freeze for ValidateCmd",1,["nimbus_fml::command_line::commands::ValidateCmd"]],["impl Freeze for PrintChannelsCmd",1,["nimbus_fml::command_line::commands::PrintChannelsCmd"]],["impl<'object> Freeze for DefaultsMerger<'object>",1,["nimbus_fml::defaults_merger::DefaultsMerger"]],["impl Freeze for FMLError",1,["nimbus_fml::error::FMLError"]],["impl Freeze for ClientError",1,["nimbus_fml::error::ClientError"]],["impl Freeze for EnumVariantBody",1,["nimbus_fml::frontend::EnumVariantBody"]],["impl Freeze for EnumBody",1,["nimbus_fml::frontend::EnumBody"]],["impl Freeze for FeatureFieldBody",1,["nimbus_fml::frontend::FeatureFieldBody"]],["impl Freeze for FieldBody",1,["nimbus_fml::frontend::FieldBody"]],["impl Freeze for ObjectBody",1,["nimbus_fml::frontend::ObjectBody"]],["impl Freeze for Types",1,["nimbus_fml::frontend::Types"]],["impl Freeze for AboutBlock",1,["nimbus_fml::frontend::AboutBlock"]],["impl Freeze for SwiftAboutBlock",1,["nimbus_fml::frontend::SwiftAboutBlock"]],["impl Freeze for KotlinAboutBlock",1,["nimbus_fml::frontend::KotlinAboutBlock"]],["impl Freeze for ImportBlock",1,["nimbus_fml::frontend::ImportBlock"]],["impl Freeze for FeatureBody",1,["nimbus_fml::frontend::FeatureBody"]],["impl Freeze for FeatureMetadata",1,["nimbus_fml::frontend::FeatureMetadata"]],["impl Freeze for DocumentationLink",1,["nimbus_fml::frontend::DocumentationLink"]],["impl Freeze for ManifestFrontEnd",1,["nimbus_fml::frontend::ManifestFrontEnd"]],["impl Freeze for DefaultBlock",1,["nimbus_fml::frontend::DefaultBlock"]],["impl Freeze for TargetLanguage",1,["nimbus_fml::intermediate_representation::TargetLanguage"]],["impl Freeze for TypeRef",1,["nimbus_fml::intermediate_representation::TypeRef"]],["impl Freeze for ModuleId",1,["nimbus_fml::intermediate_representation::ModuleId"]],["impl Freeze for FeatureManifest",1,["nimbus_fml::intermediate_representation::FeatureManifest"]],["impl Freeze for FeatureDef",1,["nimbus_fml::intermediate_representation::FeatureDef"]],["impl Freeze for EnumDef",1,["nimbus_fml::intermediate_representation::EnumDef"]],["impl Freeze for FromStringDef",1,["nimbus_fml::intermediate_representation::FromStringDef"]],["impl Freeze for VariantDef",1,["nimbus_fml::intermediate_representation::VariantDef"]],["impl Freeze for ObjectDef",1,["nimbus_fml::intermediate_representation::ObjectDef"]],["impl Freeze for PropDef",1,["nimbus_fml::intermediate_representation::PropDef"]],["impl<'a> Freeze for ImportedModule<'a>",1,["nimbus_fml::intermediate_representation::ImportedModule"]],["impl Freeze for Parser",1,["nimbus_fml::parser::Parser"]],["impl Freeze for LoaderConfig",1,["nimbus_fml::util::loaders::LoaderConfig"]],["impl Freeze for FilePath",1,["nimbus_fml::util::loaders::FilePath"]],["impl Freeze for FileLoader",1,["nimbus_fml::util::loaders::FileLoader"]]], +"nss":[["impl Freeze for Operation",1,["nss::aes::Operation"]],["impl Freeze for Curve",1,["nss::ec::Curve"]],["impl Freeze for EcKey",1,["nss::ec::EcKey"]],["impl Freeze for PrivateKey",1,["nss::ec::PrivateKey"]],["impl Freeze for PublicKey",1,["nss::ec::PublicKey"]],["impl Freeze for ErrorKind",1,["nss::error::ErrorKind"]],["impl Freeze for Error",1,["nss::error::Error"]],["impl Freeze for HashAlgorithm",1,["nss::pk11::context::HashAlgorithm"]],["impl Freeze for SymKey",1,["nss::pk11::types::SymKey"]],["impl Freeze for PrivateKey",1,["nss::pk11::types::PrivateKey"]],["impl Freeze for PublicKey",1,["nss::pk11::types::PublicKey"]],["impl Freeze for GenericObject",1,["nss::pk11::types::GenericObject"]],["impl Freeze for Certificate",1,["nss::pk11::types::Certificate"]],["impl Freeze for Context",1,["nss::pk11::types::Context"]],["impl Freeze for Slot",1,["nss::pk11::types::Slot"]],["impl Freeze for AlgorithmID",1,["nss::pk11::types::AlgorithmID"]]], +"nss_build_common":[["impl Freeze for LinkingKind",1,["nss_build_common::LinkingKind"]],["impl Freeze for NoNssDir",1,["nss_build_common::NoNssDir"]]], +"nss_sys":[["impl Freeze for SECKEYPublicKeyStr",1,["nss_sys::bindings::keythi::SECKEYPublicKeyStr"]],["impl Freeze for SECKEYPublicKeyStr_u",1,["nss_sys::bindings::keythi::SECKEYPublicKeyStr_u"]],["impl Freeze for SECKEYPrivateKeyStr",1,["nss_sys::bindings::keythi::SECKEYPrivateKeyStr"]],["impl Freeze for KeyType",1,["nss_sys::bindings::keythi::KeyType"]],["impl Freeze for SECKEYRSAPublicKeyStr",1,["nss_sys::bindings::keythi::SECKEYRSAPublicKeyStr"]],["impl Freeze for SECKEYDSAPublicKeyStr",1,["nss_sys::bindings::keythi::SECKEYDSAPublicKeyStr"]],["impl Freeze for SECKEYPQGParamsStr",1,["nss_sys::bindings::keythi::SECKEYPQGParamsStr"]],["impl Freeze for SECKEYDHPublicKeyStr",1,["nss_sys::bindings::keythi::SECKEYDHPublicKeyStr"]],["impl Freeze for SECKEYKEAPublicKeyStr",1,["nss_sys::bindings::keythi::SECKEYKEAPublicKeyStr"]],["impl Freeze for SECKEYKEAParamsStr",1,["nss_sys::bindings::keythi::SECKEYKEAParamsStr"]],["impl Freeze for SECKEYFortezzaPublicKeyStr",1,["nss_sys::bindings::keythi::SECKEYFortezzaPublicKeyStr"]],["impl Freeze for SECKEYECPublicKeyStr",1,["nss_sys::bindings::keythi::SECKEYECPublicKeyStr"]],["impl Freeze for ECPointEncoding",1,["nss_sys::bindings::keythi::ECPointEncoding"]],["impl Freeze for CK_GCM_PARAMS_V3",1,["nss_sys::bindings::pkcs11n::CK_GCM_PARAMS_V3"]],["impl Freeze for CK_NSS_HKDFParams",1,["nss_sys::bindings::pkcs11n::CK_NSS_HKDFParams"]],["impl Freeze for CK_ATTRIBUTE",1,["nss_sys::bindings::pkcs11t::CK_ATTRIBUTE"]],["impl Freeze for PLArena",1,["nss_sys::bindings::plarena::PLArena"]],["impl Freeze for PLArenaPool",1,["nss_sys::bindings::plarena::PLArenaPool"]],["impl Freeze for SECItemType",1,["nss_sys::bindings::seccomon::SECItemType"]],["impl Freeze for SECItemStr",1,["nss_sys::bindings::seccomon::SECItemStr"]],["impl Freeze for _SECStatus",1,["nss_sys::bindings::seccomon::_SECStatus"]],["impl Freeze for PK11Origin",1,["nss_sys::bindings::secmodt::PK11Origin"]],["impl Freeze for PK11ObjectType",1,["nss_sys::bindings::secmodt::PK11ObjectType"]],["impl Freeze for SECAlgorithmIDStr",1,["nss_sys::bindings::secoidt::SECAlgorithmIDStr"]],["impl Freeze for SECOidDataStr",1,["nss_sys::bindings::secoidt::SECOidDataStr"]],["impl Freeze for SECSupportExtenTag",1,["nss_sys::bindings::secoidt::SECSupportExtenTag"]],["impl Freeze for SECOidTag",1,["nss_sys::bindings::secoidt::SECOidTag"]]], +"places":[["impl Freeze for AddablePlaceInfo",1,["places::api::history::AddablePlaceInfo"]],["impl Freeze for AddableVisit",1,["places::api::history::AddableVisit"]],["impl Freeze for RedirectSourceType",1,["places::api::history::RedirectSourceType"]],["impl Freeze for SearchParams",1,["places::api::matcher::SearchParams"]],["impl Freeze for SearchResult",1,["places::api::matcher::SearchResult"]],["impl Freeze for ConnectionType",1,["places::api::places_api::ConnectionType"]],["impl !Freeze for SyncState",1,["places::api::places_api::SyncState"]],["impl !Freeze for PlacesApi",1,["places::api::places_api::PlacesApi"]],["impl Freeze for PlacesApiError",1,["places::error::PlacesApiError"]],["impl Freeze for Error",1,["places::error::Error"]],["impl Freeze for InvalidPlaceInfo",1,["places::error::InvalidPlaceInfo"]],["impl Freeze for Corruption",1,["places::error::Corruption"]],["impl Freeze for InvalidMetadataObservation",1,["places::error::InvalidMetadataObservation"]],["impl Freeze for VisitTransitionSet",1,["places::types::visit_transition_set::VisitTransitionSet"]],["impl Freeze for InvalidVisitType",1,["places::types::InvalidVisitType"]],["impl Freeze for VisitType",1,["places::types::VisitType"]],["impl Freeze for BookmarkType",1,["places::types::BookmarkType"]],["impl Freeze for SyncStatus",1,["places::types::SyncStatus"]],["impl Freeze for BookmarksSyncEngine",1,["places::bookmark_sync::engine::BookmarksSyncEngine"]],["impl Freeze for BookmarkRecordId",1,["places::bookmark_sync::record::BookmarkRecordId"]],["impl Freeze for BookmarkRecord",1,["places::bookmark_sync::record::BookmarkRecord"]],["impl Freeze for QueryRecord",1,["places::bookmark_sync::record::QueryRecord"]],["impl Freeze for FolderRecord",1,["places::bookmark_sync::record::FolderRecord"]],["impl Freeze for LivemarkRecord",1,["places::bookmark_sync::record::LivemarkRecord"]],["impl Freeze for SeparatorRecord",1,["places::bookmark_sync::record::SeparatorRecord"]],["impl Freeze for BookmarkItemRecord",1,["places::bookmark_sync::record::BookmarkItemRecord"]],["impl Freeze for SyncedBookmarkKind",1,["places::bookmark_sync::SyncedBookmarkKind"]],["impl Freeze for SyncedBookmarkValidity",1,["places::bookmark_sync::SyncedBookmarkValidity"]],["impl Freeze for GLOBAL_BOOKMARK_CHANGE_COUNTERS",1,["places::db::db::GLOBAL_BOOKMARK_CHANGE_COUNTERS"]],["impl Freeze for PlacesInitializer",1,["places::db::db::PlacesInitializer"]],["impl !Freeze for PlacesDb",1,["places::db::db::PlacesDb"]],["impl !Freeze for SharedPlacesDb",1,["places::db::db::SharedPlacesDb"]],["impl Freeze for GlobalChangeCounterTracker",1,["places::db::db::GlobalChangeCounterTracker"]],["impl<'conn> Freeze for PlacesTransaction<'conn>",1,["places::db::tx::PlacesTransaction"]],["impl !Freeze for PlacesConnection",1,["places::ffi::PlacesConnection"]],["impl Freeze for HistoryVisitInfo",1,["places::ffi::HistoryVisitInfo"]],["impl Freeze for HistoryVisitInfosWithBound",1,["places::ffi::HistoryVisitInfosWithBound"]],["impl Freeze for TopFrecentSiteInfo",1,["places::ffi::TopFrecentSiteInfo"]],["impl Freeze for FrecencyThresholdOption",1,["places::ffi::FrecencyThresholdOption"]],["impl Freeze for SearchResult",1,["places::ffi::SearchResult"]],["impl Freeze for Dummy",1,["places::ffi::Dummy"]],["impl Freeze for FrecencySettings",1,["places::frecency::FrecencySettings"]],["impl Freeze for PrefixMode",1,["places::hash::PrefixMode"]],["impl Freeze for HistorySyncEngine",1,["places::history_sync::engine::HistorySyncEngine"]],["impl Freeze for HistoryRecordVisit",1,["places::history_sync::record::HistoryRecordVisit"]],["impl Freeze for HistoryRecord",1,["places::history_sync::record::HistoryRecord"]],["impl Freeze for ServerVisitTimestamp",1,["places::history_sync::ServerVisitTimestamp"]],["impl Freeze for NOW",1,["places::import::common::NOW"]],["impl<'a> Freeze for ExecuteOnDrop<'a>",1,["places::import::common::ExecuteOnDrop"]],["impl Freeze for HistoryMigrationResult",1,["places::import::common::HistoryMigrationResult"]],["impl Freeze for MatchBehavior",1,["places::match_impl::MatchBehavior"]],["impl Freeze for SearchBehavior",1,["places::match_impl::SearchBehavior"]],["impl<'search, 'url, 'title, 'tags> Freeze for AutocompleteMatch<'search, 'url, 'title, 'tags>",1,["places::match_impl::AutocompleteMatch"]],["impl Freeze for VisitObservation",1,["places::observation::VisitObservation"]],["impl Freeze for BookmarkData",1,["places::storage::bookmarks::fetch::BookmarkData"]],["impl Freeze for Separator",1,["places::storage::bookmarks::fetch::Separator"]],["impl Freeze for Folder",1,["places::storage::bookmarks::fetch::Folder"]],["impl Freeze for Item",1,["places::storage::bookmarks::fetch::Item"]],["impl Freeze for SEARCH_QUERY",1,["places::storage::bookmarks::fetch::SEARCH_QUERY"]],["impl Freeze for RECENT_BOOKMARKS_QUERY",1,["places::storage::bookmarks::fetch::RECENT_BOOKMARKS_QUERY"]],["impl Freeze for BookmarkNode",1,["places::storage::bookmarks::json_tree::BookmarkNode"]],["impl Freeze for SeparatorNode",1,["places::storage::bookmarks::json_tree::SeparatorNode"]],["impl Freeze for FolderNode",1,["places::storage::bookmarks::json_tree::FolderNode"]],["impl Freeze for BookmarkTreeNode",1,["places::storage::bookmarks::json_tree::BookmarkTreeNode"]],["impl Freeze for FetchDepth",1,["places::storage::bookmarks::json_tree::FetchDepth"]],["impl Freeze for BookmarkRootGuid",1,["places::storage::bookmarks::root_guid::BookmarkRootGuid"]],["impl Freeze for BookmarkPosition",1,["places::storage::bookmarks::BookmarkPosition"]],["impl Freeze for InsertableBookmark",1,["places::storage::bookmarks::InsertableBookmark"]],["impl Freeze for InsertableSeparator",1,["places::storage::bookmarks::InsertableSeparator"]],["impl Freeze for InsertableFolder",1,["places::storage::bookmarks::InsertableFolder"]],["impl Freeze for InsertableItem",1,["places::storage::bookmarks::InsertableItem"]],["impl Freeze for UpdateTreeLocation",1,["places::storage::bookmarks::UpdateTreeLocation"]],["impl Freeze for UpdatableBookmark",1,["places::storage::bookmarks::UpdatableBookmark"]],["impl Freeze for UpdatableSeparator",1,["places::storage::bookmarks::UpdatableSeparator"]],["impl Freeze for UpdatableFolder",1,["places::storage::bookmarks::UpdatableFolder"]],["impl Freeze for UpdatableItem",1,["places::storage::bookmarks::UpdatableItem"]],["impl Freeze for BookmarkUpdateInfo",1,["places::storage::bookmarks::BookmarkUpdateInfo"]],["impl Freeze for FetchedVisit",1,["places::storage::history::history_sync::FetchedVisit"]],["impl Freeze for FetchedVisitPage",1,["places::storage::history::history_sync::FetchedVisitPage"]],["impl Freeze for DocumentType",1,["places::storage::history_metadata::DocumentType"]],["impl Freeze for HistoryHighlightWeights",1,["places::storage::history_metadata::HistoryHighlightWeights"]],["impl Freeze for HistoryHighlight",1,["places::storage::history_metadata::HistoryHighlight"]],["impl Freeze for HistoryMetadataObservation",1,["places::storage::history_metadata::HistoryMetadataObservation"]],["impl Freeze for HistoryMetadata",1,["places::storage::history_metadata::HistoryMetadata"]],["impl<'a> Freeze for ValidatedTag<'a>",1,["places::storage::tags::ValidatedTag"]],["impl Freeze for RowId",1,["places::storage::RowId"]],["impl Freeze for PageInfo",1,["places::storage::PageInfo"]],["impl Freeze for FetchedPageInfo",1,["places::storage::FetchedPageInfo"]],["impl Freeze for RunMaintenanceMetrics",1,["places::storage::RunMaintenanceMetrics"]]], +"protobuf_gen":[["impl Freeze for ProtobufOpts",1,["protobuf_gen::ProtobufOpts"]]], +"push":[["impl Freeze for BridgeType",1,["push::internal::config::BridgeType"]],["impl Freeze for PushConfiguration",1,["push::internal::config::PushConfiguration"]],["impl Freeze for Protocol",1,["push::internal::config::Protocol"]],["impl Freeze for PushApiError",1,["push::error::PushApiError"]],["impl Freeze for PushError",1,["push::error::PushError"]],["impl !Freeze for PushManager",1,["push::PushManager"]],["impl Freeze for KeyInfo",1,["push::KeyInfo"]],["impl Freeze for SubscriptionInfo",1,["push::SubscriptionInfo"]],["impl Freeze for SubscriptionResponse",1,["push::SubscriptionResponse"]],["impl Freeze for PushSubscriptionChanged",1,["push::PushSubscriptionChanged"]]], +"rc_crypto":[["impl<'a> Freeze for Aad<'a>",1,["rc_crypto::aead::Aad"]],["impl Freeze for Nonce",1,["rc_crypto::aead::Nonce"]],["impl Freeze for OpeningKey",1,["rc_crypto::aead::OpeningKey"]],["impl Freeze for SealingKey",1,["rc_crypto::aead::SealingKey"]],["impl Freeze for Algorithm",1,["rc_crypto::aead::Algorithm"]],["impl Freeze for Algorithm",1,["rc_crypto::agreement::Algorithm"]],["impl Freeze for Ephemeral",1,["rc_crypto::agreement::Ephemeral"]],["impl Freeze for Static",1,["rc_crypto::agreement::Static"]],["impl<U> Freeze for KeyPair<U>",1,["rc_crypto::agreement::KeyPair"]],["impl Freeze for PublicKey",1,["rc_crypto::agreement::PublicKey"]],["impl<'a> Freeze for UnparsedPublicKey<'a>",1,["rc_crypto::agreement::UnparsedPublicKey"]],["impl<U> Freeze for PrivateKey<U>",1,["rc_crypto::agreement::PrivateKey"]],["impl Freeze for InputKeyMaterial",1,["rc_crypto::agreement::InputKeyMaterial"]],["impl Freeze for Digest",1,["rc_crypto::digest::Digest"]],["impl Freeze for RcCryptoLocalKeyPair",1,["rc_crypto::ece_crypto::RcCryptoLocalKeyPair"]],["impl Freeze for RcCryptoRemotePublicKey",1,["rc_crypto::ece_crypto::RcCryptoRemotePublicKey"]],["impl Freeze for ErrorKind",1,["rc_crypto::error::ErrorKind"]],["impl Freeze for Error",1,["rc_crypto::error::Error"]],["impl Freeze for Signature",1,["rc_crypto::hmac::Signature"]],["impl Freeze for SigningKey",1,["rc_crypto::hmac::SigningKey"]],["impl Freeze for VerificationKey",1,["rc_crypto::hmac::VerificationKey"]],["impl Freeze for VerificationAlgorithm",1,["rc_crypto::signature::VerificationAlgorithm"]],["impl<'a> Freeze for UnparsedPublicKey<'a>",1,["rc_crypto::signature::UnparsedPublicKey"]]], +"rc_log_ffi":[["impl Freeze for LogAdapterState",1,["rc_log_ffi::android::LogAdapterState"]],["impl !Freeze for LogSink",1,["rc_log_ffi::android::LogSink"]],["impl Freeze for LogLevel",1,["rc_log_ffi::LogLevel"]]], +"remote_settings":[["impl Freeze for RemoteSettingsError",1,["remote_settings::error::RemoteSettingsError"]],["impl !Freeze for Client",1,["remote_settings::client::Client"]],["impl Freeze for RemoteSettingsResponse",1,["remote_settings::client::RemoteSettingsResponse"]],["impl Freeze for RemoteSettingsRecord",1,["remote_settings::client::RemoteSettingsRecord"]],["impl Freeze for Attachment",1,["remote_settings::client::Attachment"]],["impl Freeze for GetItemsOptions",1,["remote_settings::client::GetItemsOptions"]],["impl Freeze for SortOrder",1,["remote_settings::client::SortOrder"]],["impl Freeze for RemoteSettingsConfig",1,["remote_settings::config::RemoteSettingsConfig"]],["impl !Freeze for RemoteSettings",1,["remote_settings::RemoteSettings"]]], +"sql_support":[["impl !Freeze for Conn",1,["sql_support::conn_ext::Conn"]],["impl<'conn> Freeze for UncheckedTransaction<'conn>",1,["sql_support::conn_ext::UncheckedTransaction"]],["impl<'conn> !Freeze for MaybeCached<'conn>",1,["sql_support::maybe_cached::MaybeCached"]],["impl<CI> Freeze for MigratedDatabaseFile<CI>where\n CI: Freeze,",1,["sql_support::open_database::test_utils::MigratedDatabaseFile"]],["impl Freeze for Error",1,["sql_support::open_database::Error"]],["impl<'a, F> Freeze for RepeatDisplay<'a, F>where\n F: Freeze,",1,["sql_support::repeat::RepeatDisplay"]]], +"sync15":[["impl Freeze for IncomingEncryptedBso",1,["sync15::bso::crypto::IncomingEncryptedBso"]],["impl Freeze for OutgoingEncryptedBso",1,["sync15::bso::crypto::OutgoingEncryptedBso"]],["impl Freeze for IncomingEnvelope",1,["sync15::bso::IncomingEnvelope"]],["impl Freeze for OutgoingEnvelope",1,["sync15::bso::OutgoingEnvelope"]],["impl Freeze for IncomingBso",1,["sync15::bso::IncomingBso"]],["impl Freeze for OutgoingBso",1,["sync15::bso::OutgoingBso"]],["impl<T> Freeze for IncomingContent<T>where\n T: Freeze,",1,["sync15::bso::IncomingContent"]],["impl<T> Freeze for IncomingKind<T>where\n T: Freeze,",1,["sync15::bso::IncomingKind"]],["impl Freeze for ServiceStatus",1,["sync15::client::status::ServiceStatus"]],["impl Freeze for SyncResult",1,["sync15::client::status::SyncResult"]],["impl<T> Freeze for Sync15ClientResponse<T>where\n T: Freeze,",1,["sync15::client::storage_client::Sync15ClientResponse"]],["impl Freeze for Sync15StorageClientInit",1,["sync15::client::storage_client::Sync15StorageClientInit"]],["impl !Freeze for Sync15StorageClient",1,["sync15::client::storage_client::Sync15StorageClient"]],["impl !Freeze for MemoryCachedState",1,["sync15::client::sync_multiple::MemoryCachedState"]],["impl<'a> Freeze for SyncRequestInfo<'a>",1,["sync15::client::sync_multiple::SyncRequestInfo"]],["impl Freeze for DeviceType",1,["sync15::device_type::DeviceType"]],["impl Freeze for ClientData",1,["sync15::client_types::ClientData"]],["impl Freeze for RemoteClient",1,["sync15::client_types::RemoteClient"]],["impl<'a> Freeze for Engine<'a>",1,["sync15::clients_engine::engine::Engine"]],["impl Freeze for CommandStatus",1,["sync15::clients_engine::CommandStatus"]],["impl Freeze for Settings",1,["sync15::clients_engine::Settings"]],["impl Freeze for Command",1,["sync15::clients_engine::Command"]],["impl Freeze for EncryptedPayload",1,["sync15::enc_payload::EncryptedPayload"]],["impl Freeze for ApplyResults",1,["sync15::engine::bridged_engine::ApplyResults"]],["impl Freeze for CollectionRequest",1,["sync15::engine::request::CollectionRequest"]],["impl Freeze for RequestOrder",1,["sync15::engine::request::RequestOrder"]],["impl Freeze for CollSyncIds",1,["sync15::engine::sync_engine::CollSyncIds"]],["impl Freeze for EngineSyncAssociation",1,["sync15::engine::sync_engine::EngineSyncAssociation"]],["impl Freeze for SyncEngineId",1,["sync15::engine::sync_engine::SyncEngineId"]],["impl Freeze for Error",1,["sync15::error::Error"]],["impl Freeze for KeyBundle",1,["sync15::key_bundle::KeyBundle"]],["impl Freeze for ServerTimestamp",1,["sync15::server_timestamp::ServerTimestamp"]],["impl Freeze for Event",1,["sync15::telemetry::Event"]],["impl Freeze for SyncFailure",1,["sync15::telemetry::SyncFailure"]],["impl Freeze for EngineIncoming",1,["sync15::telemetry::EngineIncoming"]],["impl Freeze for EngineOutgoing",1,["sync15::telemetry::EngineOutgoing"]],["impl Freeze for Engine",1,["sync15::telemetry::Engine"]],["impl Freeze for Validation",1,["sync15::telemetry::Validation"]],["impl Freeze for Problem",1,["sync15::telemetry::Problem"]],["impl Freeze for SyncTelemetry",1,["sync15::telemetry::SyncTelemetry"]],["impl Freeze for SyncTelemetryPing",1,["sync15::telemetry::SyncTelemetryPing"]]], +"sync_guid":[["impl Freeze for Guid",1,["sync_guid::Guid"]]], +"sync_manager":[["impl Freeze for SyncManagerError",1,["sync_manager::error::SyncManagerError"]],["impl !Freeze for SyncManager",1,["sync_manager::manager::SyncManager"]],["impl Freeze for SyncParams",1,["sync_manager::types::SyncParams"]],["impl Freeze for SyncReason",1,["sync_manager::types::SyncReason"]],["impl Freeze for SyncEngineSelection",1,["sync_manager::types::SyncEngineSelection"]],["impl Freeze for SyncAuthInfo",1,["sync_manager::types::SyncAuthInfo"]],["impl Freeze for DeviceSettings",1,["sync_manager::types::DeviceSettings"]],["impl Freeze for SyncResult",1,["sync_manager::types::SyncResult"]],["impl Freeze for ServiceStatus",1,["sync_manager::types::ServiceStatus"]]], +"tabs":[["impl Freeze for TabsApiError",1,["tabs::error::TabsApiError"]],["impl Freeze for Error",1,["tabs::error::Error"]],["impl Freeze for ClientRemoteTabs",1,["tabs::storage::ClientRemoteTabs"]],["impl !Freeze for TabsStore",1,["tabs::store::TabsStore"]],["impl Freeze for TabsBridgedEngine",1,["tabs::sync::bridge::TabsBridgedEngine"]],["impl !Freeze for TabsEngine",1,["tabs::sync::engine::TabsEngine"]]], +"types":[["impl Freeze for Timestamp",1,["types::Timestamp"]]], +"viaduct":[["impl Freeze for HeaderName",1,["viaduct::headers::name::HeaderName"]],["impl Freeze for InvalidHeaderName",1,["viaduct::headers::name::InvalidHeaderName"]],["impl Freeze for Header",1,["viaduct::headers::Header"]],["impl Freeze for Headers",1,["viaduct::headers::Headers"]],["impl Freeze for Error",1,["viaduct::error::Error"]],["impl Freeze for UnexpectedStatus",1,["viaduct::error::UnexpectedStatus"]],["impl Freeze for Settings",1,["viaduct::settings::Settings"]],["impl Freeze for Method",1,["viaduct::Method"]],["impl Freeze for Request",1,["viaduct::Request"]],["impl Freeze for Response",1,["viaduct::Response"]]], +"viaduct_reqwest":[["impl Freeze for ReqwestBackend",1,["viaduct_reqwest::ReqwestBackend"]]], +"webext_storage":[["impl Freeze for UsageInfo",1,["webext_storage::api::UsageInfo"]],["impl Freeze for QuotaReason",1,["webext_storage::error::QuotaReason"]],["impl Freeze for ErrorKind",1,["webext_storage::error::ErrorKind"]],["impl Freeze for Error",1,["webext_storage::error::Error"]],["impl Freeze for MigrationInfo",1,["webext_storage::migration::MigrationInfo"]],["impl Freeze for Store",1,["webext_storage::store::Store"]]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/core/marker/trait.Send.js b/book/rust-docs/implementors/core/marker/trait.Send.js new file mode 100644 index 0000000000..5d553cab21 --- /dev/null +++ b/book/rust-docs/implementors/core/marker/trait.Send.js @@ -0,0 +1,32 @@ +(function() {var implementors = { +"as_ohttp_client":[["impl Send for OhttpError",1,["as_ohttp_client::OhttpError"]],["impl Send for OhttpSession",1,["as_ohttp_client::OhttpSession"]],["impl Send for OhttpResponse",1,["as_ohttp_client::OhttpResponse"]],["impl Send for OhttpTestServer",1,["as_ohttp_client::OhttpTestServer"]],["impl Send for TestServerRequest",1,["as_ohttp_client::TestServerRequest"]]], +"autofill":[["impl Send for UpdatableAddressFields",1,["autofill::db::models::address::UpdatableAddressFields"]],["impl Send for Address",1,["autofill::db::models::address::Address"]],["impl Send for InternalAddress",1,["autofill::db::models::address::InternalAddress"]],["impl Send for UpdatableCreditCardFields",1,["autofill::db::models::credit_card::UpdatableCreditCardFields"]],["impl Send for CreditCard",1,["autofill::db::models::credit_card::CreditCard"]],["impl Send for InternalCreditCard",1,["autofill::db::models::credit_card::InternalCreditCard"]],["impl Send for Metadata",1,["autofill::db::models::Metadata"]],["impl Send for AutofillConnectionInitializer",1,["autofill::db::schema::AutofillConnectionInitializer"]],["impl Send for Store",1,["autofill::db::store::Store"]],["impl Send for AutofillDb",1,["autofill::db::AutofillDb"]],["impl Send for AutofillApiError",1,["autofill::error::AutofillApiError"]],["impl Send for Error",1,["autofill::error::Error"]],["impl Send for AddressPayload",1,["autofill::sync::address::AddressPayload"]],["impl Send for EngineConfig",1,["autofill::sync::engine::EngineConfig"]],["impl<T> !Send for ConfigSyncEngine<T>",1,["autofill::sync::engine::ConfigSyncEngine"]],["impl<T> Send for MergeResult<T>where\n T: Send,",1,["autofill::sync::MergeResult"]],["impl<T> Send for IncomingState<T>where\n T: Send,",1,["autofill::sync::IncomingState"]]], +"cli_support":[["impl Send for CliFxa",1,["cli_support::fxa_creds::CliFxa"]]], +"crashtest":[["impl Send for CrashTestError",1,["crashtest::CrashTestError"]]], +"error_support":[["impl Send for ErrorReporting",1,["error_support::handling::ErrorReporting"]],["impl<E> Send for ErrorHandling<E>where\n E: Send,",1,["error_support::handling::ErrorHandling"]]], +"examples_fxa_client":[["impl Send for DeviceArgs",1,["examples_fxa_client::devices::DeviceArgs"]],["impl Send for Command",1,["examples_fxa_client::devices::Command"]],["impl Send for SendTabArgs",1,["examples_fxa_client::send_tab::SendTabArgs"]],["impl Send for Command",1,["examples_fxa_client::send_tab::Command"]],["impl Send for Cli",1,["examples_fxa_client::Cli"]],["impl Send for Server",1,["examples_fxa_client::Server"]],["impl Send for Command",1,["examples_fxa_client::Command"]]], +"fxa_client":[["impl Send for AuthorizationInfo",1,["fxa_client::auth::AuthorizationInfo"]],["impl Send for MetricsParams",1,["fxa_client::auth::MetricsParams"]],["impl Send for FxaRustAuthState",1,["fxa_client::auth::FxaRustAuthState"]],["impl Send for LocalDevice",1,["fxa_client::device::LocalDevice"]],["impl Send for Device",1,["fxa_client::device::Device"]],["impl Send for DeviceCapability",1,["fxa_client::device::DeviceCapability"]],["impl Send for AttachedClient",1,["fxa_client::device::AttachedClient"]],["impl Send for FxaError",1,["fxa_client::error::FxaError"]],["impl Send for Error",1,["fxa_client::error::Error"]],["impl Send for Profile",1,["fxa_client::profile::Profile"]],["impl Send for DevicePushSubscription",1,["fxa_client::push::DevicePushSubscription"]],["impl Send for AccountEvent",1,["fxa_client::push::AccountEvent"]],["impl Send for IncomingDeviceCommand",1,["fxa_client::push::IncomingDeviceCommand"]],["impl Send for SendTabPayload",1,["fxa_client::push::SendTabPayload"]],["impl Send for TabHistoryEntry",1,["fxa_client::push::TabHistoryEntry"]],["impl Send for AccessTokenInfo",1,["fxa_client::token::AccessTokenInfo"]],["impl Send for ScopedKey",1,["fxa_client::token::ScopedKey"]],["impl Send for AuthorizationParameters",1,["fxa_client::token::AuthorizationParameters"]],["impl Send for FirefoxAccount",1,["fxa_client::FirefoxAccount"]],["impl Send for FxaConfig",1,["fxa_client::FxaConfig"]],["impl Send for FxaServer",1,["fxa_client::FxaServer"]]], +"interrupt_support":[["impl Send for Interrupted",1,["interrupt_support::error::Interrupted"]],["impl Send for NeverInterrupts",1,["interrupt_support::interruptee::NeverInterrupts"]],["impl Send for ShutdownInterruptee",1,["interrupt_support::shutdown::ShutdownInterruptee"]],["impl Send for SqlInterruptHandle",1,["interrupt_support::sql::SqlInterruptHandle"]],["impl Send for SqlInterruptScope",1,["interrupt_support::sql::SqlInterruptScope"]]], +"logins":[["impl Send for LoginsApiError",1,["logins::error::LoginsApiError"]],["impl Send for Error",1,["logins::error::Error"]],["impl Send for InvalidLogin",1,["logins::error::InvalidLogin"]],["impl Send for LoginFields",1,["logins::login::LoginFields"]],["impl Send for SecureLoginFields",1,["logins::login::SecureLoginFields"]],["impl Send for RecordFields",1,["logins::login::RecordFields"]],["impl Send for LoginEntry",1,["logins::login::LoginEntry"]],["impl Send for Login",1,["logins::login::Login"]],["impl Send for EncryptedLogin",1,["logins::login::EncryptedLogin"]],["impl Send for LoginDb",1,["logins::db::LoginDb"]],["impl Send for LoginStore",1,["logins::store::LoginStore"]],["impl Send for LoginsSyncEngine",1,["logins::sync::engine::LoginsSyncEngine"]]], +"nimbus":[["impl Send for EnrollmentStatus",1,["nimbus::enrollment::EnrollmentStatus"]],["impl Send for EnrolledFeature",1,["nimbus::enrollment::EnrolledFeature"]],["impl Send for NimbusTargetingHelper",1,["nimbus::targeting::NimbusTargetingHelper"]],["impl Send for NimbusError",1,["nimbus::error::NimbusError"]],["impl Send for BehaviorError",1,["nimbus::error::BehaviorError"]],["impl Send for EnrollmentStatusExtraDef",1,["nimbus::metrics::EnrollmentStatusExtraDef"]],["impl Send for EnrolledExperiment",1,["nimbus::schema::EnrolledExperiment"]],["impl Send for Experiment",1,["nimbus::schema::Experiment"]],["impl Send for FeatureConfig",1,["nimbus::schema::FeatureConfig"]],["impl Send for Branch",1,["nimbus::schema::Branch"]],["impl Send for BucketConfig",1,["nimbus::schema::BucketConfig"]],["impl Send for AvailableExperiment",1,["nimbus::schema::AvailableExperiment"]],["impl Send for ExperimentBranch",1,["nimbus::schema::ExperimentBranch"]],["impl Send for RandomizationUnit",1,["nimbus::schema::RandomizationUnit"]],["impl Send for AvailableRandomizationUnits",1,["nimbus::schema::AvailableRandomizationUnits"]],["impl Send for Version",1,["nimbus::versioning::Version"]],["impl Send for Interval",1,["nimbus::stateful::behavior::Interval"]],["impl Send for IntervalConfig",1,["nimbus::stateful::behavior::IntervalConfig"]],["impl Send for IntervalData",1,["nimbus::stateful::behavior::IntervalData"]],["impl Send for SingleIntervalCounter",1,["nimbus::stateful::behavior::SingleIntervalCounter"]],["impl Send for MultiIntervalCounter",1,["nimbus::stateful::behavior::MultiIntervalCounter"]],["impl Send for EventQueryType",1,["nimbus::stateful::behavior::EventQueryType"]],["impl Send for EventStore",1,["nimbus::stateful::behavior::EventStore"]],["impl Send for DatabaseCache",1,["nimbus::stateful::dbcache::DatabaseCache"]],["impl Send for TargetingAttributes",1,["nimbus::stateful::evaluator::TargetingAttributes"]],["impl Send for AppContext",1,["nimbus::stateful::matcher::AppContext"]],["impl Send for InternalMutableState",1,["nimbus::stateful::nimbus_client::InternalMutableState"]],["impl Send for NimbusClient",1,["nimbus::stateful::nimbus_client::NimbusClient"]],["impl Send for NimbusStringHelper",1,["nimbus::stateful::nimbus_client::NimbusStringHelper"]],["impl Send for StoreId",1,["nimbus::stateful::persistence::StoreId"]],["impl Send for SingleStore",1,["nimbus::stateful::persistence::SingleStore"]],["impl Send for Database",1,["nimbus::stateful::persistence::Database"]]], +"nimbus_cli":[["impl Send for Cli",1,["nimbus_cli::cli::Cli"]],["impl Send for CliCommand",1,["nimbus_cli::cli::CliCommand"]],["impl Send for ManifestArgs",1,["nimbus_cli::cli::ManifestArgs"]],["impl Send for OpenArgs",1,["nimbus_cli::cli::OpenArgs"]],["impl Send for ExperimentArgs",1,["nimbus_cli::cli::ExperimentArgs"]],["impl Send for ExperimentListArgs",1,["nimbus_cli::cli::ExperimentListArgs"]],["impl Send for ExperimentListSourceArgs",1,["nimbus_cli::cli::ExperimentListSourceArgs"]],["impl Send for ExperimentListFilterArgs",1,["nimbus_cli::cli::ExperimentListFilterArgs"]],["impl<'a> Send for ExperimentInfo<'a>",1,["nimbus_cli::output::info::ExperimentInfo"]],["impl<'a> Send for DateRange<'a>",1,["nimbus_cli::output::info::DateRange"]],["impl Send for StartAppPostPayload",1,["nimbus_cli::output::server::StartAppPostPayload"]],["impl Send for InMemoryDb",1,["nimbus_cli::output::server::InMemoryDb"]],["impl<'a> Send for StartAppProtocol<'a>",1,["nimbus_cli::protocol::StartAppProtocol"]],["impl Send for ExperimentSource",1,["nimbus_cli::sources::experiment::ExperimentSource"]],["impl Send for FeatureDefaults",1,["nimbus_cli::sources::experiment::FeatureDefaults"]],["impl Send for ExperimentListSource",1,["nimbus_cli::sources::experiment_list::ExperimentListSource"]],["impl Send for ExperimentListFilter",1,["nimbus_cli::sources::filter::ExperimentListFilter"]],["impl Send for ManifestSource",1,["nimbus_cli::sources::manifest::ManifestSource"]],["impl Send for Response",1,["nimbus_cli::updater::taskcluster::Response"]],["impl Send for TaskClusterRegistry",1,["nimbus_cli::updater::taskcluster::TaskClusterRegistry"]],["impl Send for ReqwestGunzippingHttpClient",1,["nimbus_cli::updater::taskcluster::ReqwestGunzippingHttpClient"]],["impl Send for LaunchableApp",1,["nimbus_cli::LaunchableApp"]],["impl Send for NimbusApp",1,["nimbus_cli::NimbusApp"]],["impl Send for AppCommand",1,["nimbus_cli::AppCommand"]],["impl Send for AppOpenArgs",1,["nimbus_cli::AppOpenArgs"]]], +"nimbus_fml":[["impl Send for ExperimenterFeature",1,["nimbus_fml::backends::experimenter_manifest::ExperimenterFeature"]],["impl Send for ExperimenterFeatureProperty",1,["nimbus_fml::backends::experimenter_manifest::ExperimenterFeatureProperty"]],["impl Send for ExperimentManifestPropType",1,["nimbus_fml::backends::experimenter_manifest::ExperimentManifestPropType"]],["impl Send for TextCodeType",1,["nimbus_fml::backends::kotlin::gen_structs::bundled::TextCodeType"]],["impl Send for ImageCodeType",1,["nimbus_fml::backends::kotlin::gen_structs::bundled::ImageCodeType"]],["impl Send for EnumCodeType",1,["nimbus_fml::backends::kotlin::gen_structs::enum_::EnumCodeType"]],["impl Send for EnumCodeDeclaration",1,["nimbus_fml::backends::kotlin::gen_structs::enum_::EnumCodeDeclaration"]],["impl Send for FeatureCodeDeclaration",1,["nimbus_fml::backends::kotlin::gen_structs::feature::FeatureCodeDeclaration"]],["impl<'a> Send for ImportedModuleInitialization<'a>",1,["nimbus_fml::backends::kotlin::gen_structs::imports::ImportedModuleInitialization"]],["impl Send for ObjectRuntime",1,["nimbus_fml::backends::kotlin::gen_structs::object::ObjectRuntime"]],["impl Send for ObjectCodeType",1,["nimbus_fml::backends::kotlin::gen_structs::object::ObjectCodeType"]],["impl Send for ObjectCodeDeclaration",1,["nimbus_fml::backends::kotlin::gen_structs::object::ObjectCodeDeclaration"]],["impl Send for BooleanCodeType",1,["nimbus_fml::backends::kotlin::gen_structs::primitives::BooleanCodeType"]],["impl Send for IntCodeType",1,["nimbus_fml::backends::kotlin::gen_structs::primitives::IntCodeType"]],["impl Send for StringCodeType",1,["nimbus_fml::backends::kotlin::gen_structs::primitives::StringCodeType"]],["impl Send for OptionalCodeType",1,["nimbus_fml::backends::kotlin::gen_structs::structural::OptionalCodeType"]],["impl Send for MapCodeType",1,["nimbus_fml::backends::kotlin::gen_structs::structural::MapCodeType"]],["impl Send for ListCodeType",1,["nimbus_fml::backends::kotlin::gen_structs::structural::ListCodeType"]],["impl<'a> Send for FeatureManifestDeclaration<'a>",1,["nimbus_fml::backends::kotlin::gen_structs::FeatureManifestDeclaration"]],["impl Send for ConcreteCodeOracle",1,["nimbus_fml::backends::kotlin::gen_structs::ConcreteCodeOracle"]],["impl Send for TextCodeType",1,["nimbus_fml::backends::swift::gen_structs::bundled::TextCodeType"]],["impl Send for ImageCodeType",1,["nimbus_fml::backends::swift::gen_structs::bundled::ImageCodeType"]],["impl Send for EnumCodeType",1,["nimbus_fml::backends::swift::gen_structs::enum_::EnumCodeType"]],["impl Send for EnumCodeDeclaration",1,["nimbus_fml::backends::swift::gen_structs::enum_::EnumCodeDeclaration"]],["impl Send for FeatureCodeDeclaration",1,["nimbus_fml::backends::swift::gen_structs::feature::FeatureCodeDeclaration"]],["impl<'a> Send for ImportedModuleInitialization<'a>",1,["nimbus_fml::backends::swift::gen_structs::imports::ImportedModuleInitialization"]],["impl Send for ObjectRuntime",1,["nimbus_fml::backends::swift::gen_structs::object::ObjectRuntime"]],["impl Send for ObjectCodeType",1,["nimbus_fml::backends::swift::gen_structs::object::ObjectCodeType"]],["impl Send for ObjectCodeDeclaration",1,["nimbus_fml::backends::swift::gen_structs::object::ObjectCodeDeclaration"]],["impl Send for BooleanCodeType",1,["nimbus_fml::backends::swift::gen_structs::primitives::BooleanCodeType"]],["impl Send for IntCodeType",1,["nimbus_fml::backends::swift::gen_structs::primitives::IntCodeType"]],["impl Send for StringCodeType",1,["nimbus_fml::backends::swift::gen_structs::primitives::StringCodeType"]],["impl Send for OptionalCodeType",1,["nimbus_fml::backends::swift::gen_structs::structural::OptionalCodeType"]],["impl Send for MapCodeType",1,["nimbus_fml::backends::swift::gen_structs::structural::MapCodeType"]],["impl Send for ListCodeType",1,["nimbus_fml::backends::swift::gen_structs::structural::ListCodeType"]],["impl<'a> Send for FeatureManifestDeclaration<'a>",1,["nimbus_fml::backends::swift::gen_structs::FeatureManifestDeclaration"]],["impl Send for ConcreteCodeOracle",1,["nimbus_fml::backends::swift::gen_structs::ConcreteCodeOracle"]],["impl Send for VariablesType",1,["nimbus_fml::backends::VariablesType"]],["impl Send for CliCmd",1,["nimbus_fml::command_line::commands::CliCmd"]],["impl Send for GenerateStructCmd",1,["nimbus_fml::command_line::commands::GenerateStructCmd"]],["impl Send for GenerateExperimenterManifestCmd",1,["nimbus_fml::command_line::commands::GenerateExperimenterManifestCmd"]],["impl Send for GenerateSingleFileManifestCmd",1,["nimbus_fml::command_line::commands::GenerateSingleFileManifestCmd"]],["impl Send for ValidateCmd",1,["nimbus_fml::command_line::commands::ValidateCmd"]],["impl Send for PrintChannelsCmd",1,["nimbus_fml::command_line::commands::PrintChannelsCmd"]],["impl<'object> Send for DefaultsMerger<'object>",1,["nimbus_fml::defaults_merger::DefaultsMerger"]],["impl Send for FMLError",1,["nimbus_fml::error::FMLError"]],["impl Send for ClientError",1,["nimbus_fml::error::ClientError"]],["impl Send for EnumVariantBody",1,["nimbus_fml::frontend::EnumVariantBody"]],["impl Send for EnumBody",1,["nimbus_fml::frontend::EnumBody"]],["impl Send for FeatureFieldBody",1,["nimbus_fml::frontend::FeatureFieldBody"]],["impl Send for FieldBody",1,["nimbus_fml::frontend::FieldBody"]],["impl Send for ObjectBody",1,["nimbus_fml::frontend::ObjectBody"]],["impl Send for Types",1,["nimbus_fml::frontend::Types"]],["impl Send for AboutBlock",1,["nimbus_fml::frontend::AboutBlock"]],["impl Send for SwiftAboutBlock",1,["nimbus_fml::frontend::SwiftAboutBlock"]],["impl Send for KotlinAboutBlock",1,["nimbus_fml::frontend::KotlinAboutBlock"]],["impl Send for ImportBlock",1,["nimbus_fml::frontend::ImportBlock"]],["impl Send for FeatureBody",1,["nimbus_fml::frontend::FeatureBody"]],["impl Send for FeatureMetadata",1,["nimbus_fml::frontend::FeatureMetadata"]],["impl Send for DocumentationLink",1,["nimbus_fml::frontend::DocumentationLink"]],["impl Send for ManifestFrontEnd",1,["nimbus_fml::frontend::ManifestFrontEnd"]],["impl Send for DefaultBlock",1,["nimbus_fml::frontend::DefaultBlock"]],["impl Send for TargetLanguage",1,["nimbus_fml::intermediate_representation::TargetLanguage"]],["impl Send for TypeRef",1,["nimbus_fml::intermediate_representation::TypeRef"]],["impl Send for ModuleId",1,["nimbus_fml::intermediate_representation::ModuleId"]],["impl Send for FeatureManifest",1,["nimbus_fml::intermediate_representation::FeatureManifest"]],["impl Send for FeatureDef",1,["nimbus_fml::intermediate_representation::FeatureDef"]],["impl Send for EnumDef",1,["nimbus_fml::intermediate_representation::EnumDef"]],["impl Send for FromStringDef",1,["nimbus_fml::intermediate_representation::FromStringDef"]],["impl Send for VariantDef",1,["nimbus_fml::intermediate_representation::VariantDef"]],["impl Send for ObjectDef",1,["nimbus_fml::intermediate_representation::ObjectDef"]],["impl Send for PropDef",1,["nimbus_fml::intermediate_representation::PropDef"]],["impl<'a> Send for ImportedModule<'a>",1,["nimbus_fml::intermediate_representation::ImportedModule"]],["impl Send for Parser",1,["nimbus_fml::parser::Parser"]],["impl Send for LoaderConfig",1,["nimbus_fml::util::loaders::LoaderConfig"]],["impl Send for FilePath",1,["nimbus_fml::util::loaders::FilePath"]],["impl Send for FileLoader",1,["nimbus_fml::util::loaders::FileLoader"]]], +"nss":[["impl Send for Operation",1,["nss::aes::Operation"]],["impl Send for Curve",1,["nss::ec::Curve"]],["impl Send for EcKey",1,["nss::ec::EcKey"]],["impl Send for PrivateKey",1,["nss::ec::PrivateKey"]],["impl Send for PublicKey",1,["nss::ec::PublicKey"]],["impl Send for ErrorKind",1,["nss::error::ErrorKind"]],["impl Send for Error",1,["nss::error::Error"]],["impl Send for HashAlgorithm",1,["nss::pk11::context::HashAlgorithm"]],["impl !Send for SymKey",1,["nss::pk11::types::SymKey"]],["impl !Send for GenericObject",1,["nss::pk11::types::GenericObject"]],["impl !Send for Certificate",1,["nss::pk11::types::Certificate"]],["impl !Send for Context",1,["nss::pk11::types::Context"]],["impl !Send for Slot",1,["nss::pk11::types::Slot"]],["impl !Send for AlgorithmID",1,["nss::pk11::types::AlgorithmID"]],["impl Send for PrivateKey"],["impl Send for PublicKey"]], +"nss_build_common":[["impl Send for LinkingKind",1,["nss_build_common::LinkingKind"]],["impl Send for NoNssDir",1,["nss_build_common::NoNssDir"]]], +"nss_sys":[["impl !Send for SECKEYPublicKeyStr",1,["nss_sys::bindings::keythi::SECKEYPublicKeyStr"]],["impl !Send for SECKEYPublicKeyStr_u",1,["nss_sys::bindings::keythi::SECKEYPublicKeyStr_u"]],["impl !Send for SECKEYPrivateKeyStr",1,["nss_sys::bindings::keythi::SECKEYPrivateKeyStr"]],["impl Send for KeyType",1,["nss_sys::bindings::keythi::KeyType"]],["impl !Send for SECKEYRSAPublicKeyStr",1,["nss_sys::bindings::keythi::SECKEYRSAPublicKeyStr"]],["impl !Send for SECKEYDSAPublicKeyStr",1,["nss_sys::bindings::keythi::SECKEYDSAPublicKeyStr"]],["impl !Send for SECKEYPQGParamsStr",1,["nss_sys::bindings::keythi::SECKEYPQGParamsStr"]],["impl !Send for SECKEYDHPublicKeyStr",1,["nss_sys::bindings::keythi::SECKEYDHPublicKeyStr"]],["impl !Send for SECKEYKEAPublicKeyStr",1,["nss_sys::bindings::keythi::SECKEYKEAPublicKeyStr"]],["impl !Send for SECKEYKEAParamsStr",1,["nss_sys::bindings::keythi::SECKEYKEAParamsStr"]],["impl !Send for SECKEYFortezzaPublicKeyStr",1,["nss_sys::bindings::keythi::SECKEYFortezzaPublicKeyStr"]],["impl !Send for SECKEYECPublicKeyStr",1,["nss_sys::bindings::keythi::SECKEYECPublicKeyStr"]],["impl Send for ECPointEncoding",1,["nss_sys::bindings::keythi::ECPointEncoding"]],["impl !Send for CK_GCM_PARAMS_V3",1,["nss_sys::bindings::pkcs11n::CK_GCM_PARAMS_V3"]],["impl !Send for CK_NSS_HKDFParams",1,["nss_sys::bindings::pkcs11n::CK_NSS_HKDFParams"]],["impl !Send for CK_ATTRIBUTE",1,["nss_sys::bindings::pkcs11t::CK_ATTRIBUTE"]],["impl !Send for PLArena",1,["nss_sys::bindings::plarena::PLArena"]],["impl !Send for PLArenaPool",1,["nss_sys::bindings::plarena::PLArenaPool"]],["impl Send for SECItemType",1,["nss_sys::bindings::seccomon::SECItemType"]],["impl !Send for SECItemStr",1,["nss_sys::bindings::seccomon::SECItemStr"]],["impl Send for _SECStatus",1,["nss_sys::bindings::seccomon::_SECStatus"]],["impl Send for PK11Origin",1,["nss_sys::bindings::secmodt::PK11Origin"]],["impl Send for PK11ObjectType",1,["nss_sys::bindings::secmodt::PK11ObjectType"]],["impl !Send for SECAlgorithmIDStr",1,["nss_sys::bindings::secoidt::SECAlgorithmIDStr"]],["impl !Send for SECOidDataStr",1,["nss_sys::bindings::secoidt::SECOidDataStr"]],["impl Send for SECSupportExtenTag",1,["nss_sys::bindings::secoidt::SECSupportExtenTag"]],["impl Send for SECOidTag",1,["nss_sys::bindings::secoidt::SECOidTag"]]], +"places":[["impl Send for AddablePlaceInfo",1,["places::api::history::AddablePlaceInfo"]],["impl Send for AddableVisit",1,["places::api::history::AddableVisit"]],["impl Send for RedirectSourceType",1,["places::api::history::RedirectSourceType"]],["impl Send for SearchParams",1,["places::api::matcher::SearchParams"]],["impl Send for SearchResult",1,["places::api::matcher::SearchResult"]],["impl Send for ConnectionType",1,["places::api::places_api::ConnectionType"]],["impl Send for SyncState",1,["places::api::places_api::SyncState"]],["impl Send for PlacesApi",1,["places::api::places_api::PlacesApi"]],["impl Send for PlacesApiError",1,["places::error::PlacesApiError"]],["impl Send for Error",1,["places::error::Error"]],["impl Send for InvalidPlaceInfo",1,["places::error::InvalidPlaceInfo"]],["impl Send for Corruption",1,["places::error::Corruption"]],["impl Send for InvalidMetadataObservation",1,["places::error::InvalidMetadataObservation"]],["impl Send for VisitTransitionSet",1,["places::types::visit_transition_set::VisitTransitionSet"]],["impl Send for InvalidVisitType",1,["places::types::InvalidVisitType"]],["impl Send for VisitType",1,["places::types::VisitType"]],["impl Send for BookmarkType",1,["places::types::BookmarkType"]],["impl Send for SyncStatus",1,["places::types::SyncStatus"]],["impl Send for BookmarksSyncEngine",1,["places::bookmark_sync::engine::BookmarksSyncEngine"]],["impl Send for BookmarkRecordId",1,["places::bookmark_sync::record::BookmarkRecordId"]],["impl Send for BookmarkRecord",1,["places::bookmark_sync::record::BookmarkRecord"]],["impl Send for QueryRecord",1,["places::bookmark_sync::record::QueryRecord"]],["impl Send for FolderRecord",1,["places::bookmark_sync::record::FolderRecord"]],["impl Send for LivemarkRecord",1,["places::bookmark_sync::record::LivemarkRecord"]],["impl Send for SeparatorRecord",1,["places::bookmark_sync::record::SeparatorRecord"]],["impl Send for BookmarkItemRecord",1,["places::bookmark_sync::record::BookmarkItemRecord"]],["impl Send for SyncedBookmarkKind",1,["places::bookmark_sync::SyncedBookmarkKind"]],["impl Send for SyncedBookmarkValidity",1,["places::bookmark_sync::SyncedBookmarkValidity"]],["impl Send for GLOBAL_BOOKMARK_CHANGE_COUNTERS",1,["places::db::db::GLOBAL_BOOKMARK_CHANGE_COUNTERS"]],["impl Send for PlacesInitializer",1,["places::db::db::PlacesInitializer"]],["impl Send for PlacesDb",1,["places::db::db::PlacesDb"]],["impl Send for SharedPlacesDb",1,["places::db::db::SharedPlacesDb"]],["impl Send for GlobalChangeCounterTracker",1,["places::db::db::GlobalChangeCounterTracker"]],["impl<'conn> !Send for PlacesTransaction<'conn>",1,["places::db::tx::PlacesTransaction"]],["impl Send for PlacesConnection",1,["places::ffi::PlacesConnection"]],["impl Send for HistoryVisitInfo",1,["places::ffi::HistoryVisitInfo"]],["impl Send for HistoryVisitInfosWithBound",1,["places::ffi::HistoryVisitInfosWithBound"]],["impl Send for TopFrecentSiteInfo",1,["places::ffi::TopFrecentSiteInfo"]],["impl Send for FrecencyThresholdOption",1,["places::ffi::FrecencyThresholdOption"]],["impl Send for SearchResult",1,["places::ffi::SearchResult"]],["impl Send for Dummy",1,["places::ffi::Dummy"]],["impl Send for FrecencySettings",1,["places::frecency::FrecencySettings"]],["impl Send for PrefixMode",1,["places::hash::PrefixMode"]],["impl Send for HistorySyncEngine",1,["places::history_sync::engine::HistorySyncEngine"]],["impl Send for HistoryRecordVisit",1,["places::history_sync::record::HistoryRecordVisit"]],["impl Send for HistoryRecord",1,["places::history_sync::record::HistoryRecord"]],["impl Send for ServerVisitTimestamp",1,["places::history_sync::ServerVisitTimestamp"]],["impl Send for NOW",1,["places::import::common::NOW"]],["impl<'a> !Send for ExecuteOnDrop<'a>",1,["places::import::common::ExecuteOnDrop"]],["impl Send for HistoryMigrationResult",1,["places::import::common::HistoryMigrationResult"]],["impl Send for MatchBehavior",1,["places::match_impl::MatchBehavior"]],["impl Send for SearchBehavior",1,["places::match_impl::SearchBehavior"]],["impl<'search, 'url, 'title, 'tags> Send for AutocompleteMatch<'search, 'url, 'title, 'tags>",1,["places::match_impl::AutocompleteMatch"]],["impl Send for VisitObservation",1,["places::observation::VisitObservation"]],["impl Send for BookmarkData",1,["places::storage::bookmarks::fetch::BookmarkData"]],["impl Send for Separator",1,["places::storage::bookmarks::fetch::Separator"]],["impl Send for Folder",1,["places::storage::bookmarks::fetch::Folder"]],["impl Send for Item",1,["places::storage::bookmarks::fetch::Item"]],["impl Send for SEARCH_QUERY",1,["places::storage::bookmarks::fetch::SEARCH_QUERY"]],["impl Send for RECENT_BOOKMARKS_QUERY",1,["places::storage::bookmarks::fetch::RECENT_BOOKMARKS_QUERY"]],["impl Send for BookmarkNode",1,["places::storage::bookmarks::json_tree::BookmarkNode"]],["impl Send for SeparatorNode",1,["places::storage::bookmarks::json_tree::SeparatorNode"]],["impl Send for FolderNode",1,["places::storage::bookmarks::json_tree::FolderNode"]],["impl Send for BookmarkTreeNode",1,["places::storage::bookmarks::json_tree::BookmarkTreeNode"]],["impl Send for FetchDepth",1,["places::storage::bookmarks::json_tree::FetchDepth"]],["impl Send for BookmarkRootGuid",1,["places::storage::bookmarks::root_guid::BookmarkRootGuid"]],["impl Send for BookmarkPosition",1,["places::storage::bookmarks::BookmarkPosition"]],["impl Send for InsertableBookmark",1,["places::storage::bookmarks::InsertableBookmark"]],["impl Send for InsertableSeparator",1,["places::storage::bookmarks::InsertableSeparator"]],["impl Send for InsertableFolder",1,["places::storage::bookmarks::InsertableFolder"]],["impl Send for InsertableItem",1,["places::storage::bookmarks::InsertableItem"]],["impl Send for UpdateTreeLocation",1,["places::storage::bookmarks::UpdateTreeLocation"]],["impl Send for UpdatableBookmark",1,["places::storage::bookmarks::UpdatableBookmark"]],["impl Send for UpdatableSeparator",1,["places::storage::bookmarks::UpdatableSeparator"]],["impl Send for UpdatableFolder",1,["places::storage::bookmarks::UpdatableFolder"]],["impl Send for UpdatableItem",1,["places::storage::bookmarks::UpdatableItem"]],["impl Send for BookmarkUpdateInfo",1,["places::storage::bookmarks::BookmarkUpdateInfo"]],["impl Send for FetchedVisit",1,["places::storage::history::history_sync::FetchedVisit"]],["impl Send for FetchedVisitPage",1,["places::storage::history::history_sync::FetchedVisitPage"]],["impl Send for DocumentType",1,["places::storage::history_metadata::DocumentType"]],["impl Send for HistoryHighlightWeights",1,["places::storage::history_metadata::HistoryHighlightWeights"]],["impl Send for HistoryHighlight",1,["places::storage::history_metadata::HistoryHighlight"]],["impl Send for HistoryMetadataObservation",1,["places::storage::history_metadata::HistoryMetadataObservation"]],["impl Send for HistoryMetadata",1,["places::storage::history_metadata::HistoryMetadata"]],["impl<'a> Send for ValidatedTag<'a>",1,["places::storage::tags::ValidatedTag"]],["impl Send for RowId",1,["places::storage::RowId"]],["impl Send for PageInfo",1,["places::storage::PageInfo"]],["impl Send for FetchedPageInfo",1,["places::storage::FetchedPageInfo"]],["impl Send for RunMaintenanceMetrics",1,["places::storage::RunMaintenanceMetrics"]]], +"protobuf_gen":[["impl Send for ProtobufOpts",1,["protobuf_gen::ProtobufOpts"]]], +"push":[["impl Send for BridgeType",1,["push::internal::config::BridgeType"]],["impl Send for PushConfiguration",1,["push::internal::config::PushConfiguration"]],["impl Send for Protocol",1,["push::internal::config::Protocol"]],["impl Send for PushApiError",1,["push::error::PushApiError"]],["impl Send for PushError",1,["push::error::PushError"]],["impl Send for PushManager",1,["push::PushManager"]],["impl Send for KeyInfo",1,["push::KeyInfo"]],["impl Send for SubscriptionInfo",1,["push::SubscriptionInfo"]],["impl Send for SubscriptionResponse",1,["push::SubscriptionResponse"]],["impl Send for PushSubscriptionChanged",1,["push::PushSubscriptionChanged"]]], +"rc_crypto":[["impl<'a> Send for Aad<'a>",1,["rc_crypto::aead::Aad"]],["impl Send for Nonce",1,["rc_crypto::aead::Nonce"]],["impl Send for OpeningKey",1,["rc_crypto::aead::OpeningKey"]],["impl Send for SealingKey",1,["rc_crypto::aead::SealingKey"]],["impl Send for Algorithm",1,["rc_crypto::aead::Algorithm"]],["impl Send for Algorithm",1,["rc_crypto::agreement::Algorithm"]],["impl Send for Ephemeral",1,["rc_crypto::agreement::Ephemeral"]],["impl Send for Static",1,["rc_crypto::agreement::Static"]],["impl<U> Send for KeyPair<U>where\n U: Send,",1,["rc_crypto::agreement::KeyPair"]],["impl Send for PublicKey",1,["rc_crypto::agreement::PublicKey"]],["impl<'a> Send for UnparsedPublicKey<'a>",1,["rc_crypto::agreement::UnparsedPublicKey"]],["impl<U> Send for PrivateKey<U>where\n U: Send,",1,["rc_crypto::agreement::PrivateKey"]],["impl Send for InputKeyMaterial",1,["rc_crypto::agreement::InputKeyMaterial"]],["impl Send for Digest",1,["rc_crypto::digest::Digest"]],["impl Send for RcCryptoLocalKeyPair",1,["rc_crypto::ece_crypto::RcCryptoLocalKeyPair"]],["impl Send for RcCryptoRemotePublicKey",1,["rc_crypto::ece_crypto::RcCryptoRemotePublicKey"]],["impl Send for ErrorKind",1,["rc_crypto::error::ErrorKind"]],["impl Send for Error",1,["rc_crypto::error::Error"]],["impl Send for Signature",1,["rc_crypto::hmac::Signature"]],["impl Send for SigningKey",1,["rc_crypto::hmac::SigningKey"]],["impl Send for VerificationKey",1,["rc_crypto::hmac::VerificationKey"]],["impl Send for VerificationAlgorithm",1,["rc_crypto::signature::VerificationAlgorithm"]],["impl<'a> Send for UnparsedPublicKey<'a>",1,["rc_crypto::signature::UnparsedPublicKey"]]], +"rc_log_ffi":[["impl Send for LogAdapterState",1,["rc_log_ffi::android::LogAdapterState"]],["impl Send for LogSink",1,["rc_log_ffi::android::LogSink"]],["impl Send for LogLevel",1,["rc_log_ffi::LogLevel"]]], +"remote_settings":[["impl Send for RemoteSettingsError",1,["remote_settings::error::RemoteSettingsError"]],["impl Send for Client",1,["remote_settings::client::Client"]],["impl Send for RemoteSettingsResponse",1,["remote_settings::client::RemoteSettingsResponse"]],["impl Send for RemoteSettingsRecord",1,["remote_settings::client::RemoteSettingsRecord"]],["impl Send for Attachment",1,["remote_settings::client::Attachment"]],["impl Send for GetItemsOptions",1,["remote_settings::client::GetItemsOptions"]],["impl Send for SortOrder",1,["remote_settings::client::SortOrder"]],["impl Send for RemoteSettingsConfig",1,["remote_settings::config::RemoteSettingsConfig"]],["impl Send for RemoteSettings",1,["remote_settings::RemoteSettings"]]], +"sql_support":[["impl Send for Conn",1,["sql_support::conn_ext::Conn"]],["impl<'conn> !Send for UncheckedTransaction<'conn>",1,["sql_support::conn_ext::UncheckedTransaction"]],["impl<'conn> !Send for MaybeCached<'conn>",1,["sql_support::maybe_cached::MaybeCached"]],["impl<CI> Send for MigratedDatabaseFile<CI>where\n CI: Send,",1,["sql_support::open_database::test_utils::MigratedDatabaseFile"]],["impl Send for Error",1,["sql_support::open_database::Error"]],["impl<'a, F> Send for RepeatDisplay<'a, F>where\n F: Send,",1,["sql_support::repeat::RepeatDisplay"]]], +"sync15":[["impl Send for IncomingEncryptedBso",1,["sync15::bso::crypto::IncomingEncryptedBso"]],["impl Send for OutgoingEncryptedBso",1,["sync15::bso::crypto::OutgoingEncryptedBso"]],["impl Send for IncomingEnvelope",1,["sync15::bso::IncomingEnvelope"]],["impl Send for OutgoingEnvelope",1,["sync15::bso::OutgoingEnvelope"]],["impl Send for IncomingBso",1,["sync15::bso::IncomingBso"]],["impl Send for OutgoingBso",1,["sync15::bso::OutgoingBso"]],["impl<T> Send for IncomingContent<T>where\n T: Send,",1,["sync15::bso::IncomingContent"]],["impl<T> Send for IncomingKind<T>where\n T: Send,",1,["sync15::bso::IncomingKind"]],["impl Send for ServiceStatus",1,["sync15::client::status::ServiceStatus"]],["impl Send for SyncResult",1,["sync15::client::status::SyncResult"]],["impl<T> Send for Sync15ClientResponse<T>where\n T: Send,",1,["sync15::client::storage_client::Sync15ClientResponse"]],["impl Send for Sync15StorageClientInit",1,["sync15::client::storage_client::Sync15StorageClientInit"]],["impl Send for Sync15StorageClient",1,["sync15::client::storage_client::Sync15StorageClient"]],["impl Send for MemoryCachedState",1,["sync15::client::sync_multiple::MemoryCachedState"]],["impl<'a> Send for SyncRequestInfo<'a>",1,["sync15::client::sync_multiple::SyncRequestInfo"]],["impl Send for DeviceType",1,["sync15::device_type::DeviceType"]],["impl Send for ClientData",1,["sync15::client_types::ClientData"]],["impl Send for RemoteClient",1,["sync15::client_types::RemoteClient"]],["impl<'a> !Send for Engine<'a>",1,["sync15::clients_engine::engine::Engine"]],["impl Send for CommandStatus",1,["sync15::clients_engine::CommandStatus"]],["impl Send for Settings",1,["sync15::clients_engine::Settings"]],["impl Send for Command",1,["sync15::clients_engine::Command"]],["impl Send for EncryptedPayload",1,["sync15::enc_payload::EncryptedPayload"]],["impl Send for ApplyResults",1,["sync15::engine::bridged_engine::ApplyResults"]],["impl Send for CollectionRequest",1,["sync15::engine::request::CollectionRequest"]],["impl Send for RequestOrder",1,["sync15::engine::request::RequestOrder"]],["impl Send for CollSyncIds",1,["sync15::engine::sync_engine::CollSyncIds"]],["impl Send for EngineSyncAssociation",1,["sync15::engine::sync_engine::EngineSyncAssociation"]],["impl Send for SyncEngineId",1,["sync15::engine::sync_engine::SyncEngineId"]],["impl Send for Error",1,["sync15::error::Error"]],["impl Send for KeyBundle",1,["sync15::key_bundle::KeyBundle"]],["impl Send for ServerTimestamp",1,["sync15::server_timestamp::ServerTimestamp"]],["impl Send for Event",1,["sync15::telemetry::Event"]],["impl Send for SyncFailure",1,["sync15::telemetry::SyncFailure"]],["impl Send for EngineIncoming",1,["sync15::telemetry::EngineIncoming"]],["impl Send for EngineOutgoing",1,["sync15::telemetry::EngineOutgoing"]],["impl Send for Engine",1,["sync15::telemetry::Engine"]],["impl Send for Validation",1,["sync15::telemetry::Validation"]],["impl Send for Problem",1,["sync15::telemetry::Problem"]],["impl Send for SyncTelemetry",1,["sync15::telemetry::SyncTelemetry"]],["impl Send for SyncTelemetryPing",1,["sync15::telemetry::SyncTelemetryPing"]]], +"sync_guid":[["impl Send for Guid",1,["sync_guid::Guid"]]], +"sync_manager":[["impl Send for SyncManagerError",1,["sync_manager::error::SyncManagerError"]],["impl Send for SyncManager",1,["sync_manager::manager::SyncManager"]],["impl Send for SyncParams",1,["sync_manager::types::SyncParams"]],["impl Send for SyncReason",1,["sync_manager::types::SyncReason"]],["impl Send for SyncEngineSelection",1,["sync_manager::types::SyncEngineSelection"]],["impl Send for SyncAuthInfo",1,["sync_manager::types::SyncAuthInfo"]],["impl Send for DeviceSettings",1,["sync_manager::types::DeviceSettings"]],["impl Send for SyncResult",1,["sync_manager::types::SyncResult"]],["impl Send for ServiceStatus",1,["sync_manager::types::ServiceStatus"]]], +"tabs":[["impl Send for TabsApiError",1,["tabs::error::TabsApiError"]],["impl Send for Error",1,["tabs::error::Error"]],["impl Send for ClientRemoteTabs",1,["tabs::storage::ClientRemoteTabs"]],["impl Send for TabsStore",1,["tabs::store::TabsStore"]],["impl Send for TabsBridgedEngine",1,["tabs::sync::bridge::TabsBridgedEngine"]],["impl Send for TabsEngine",1,["tabs::sync::engine::TabsEngine"]]], +"types":[["impl Send for Timestamp",1,["types::Timestamp"]]], +"viaduct":[["impl Send for HeaderName",1,["viaduct::headers::name::HeaderName"]],["impl Send for InvalidHeaderName",1,["viaduct::headers::name::InvalidHeaderName"]],["impl Send for Header",1,["viaduct::headers::Header"]],["impl Send for Headers",1,["viaduct::headers::Headers"]],["impl Send for Error",1,["viaduct::error::Error"]],["impl Send for UnexpectedStatus",1,["viaduct::error::UnexpectedStatus"]],["impl Send for Settings",1,["viaduct::settings::Settings"]],["impl Send for Method",1,["viaduct::Method"]],["impl Send for Request",1,["viaduct::Request"]],["impl Send for Response",1,["viaduct::Response"]]], +"viaduct_reqwest":[["impl Send for ReqwestBackend",1,["viaduct_reqwest::ReqwestBackend"]]], +"webext_storage":[["impl Send for UsageInfo",1,["webext_storage::api::UsageInfo"]],["impl Send for QuotaReason",1,["webext_storage::error::QuotaReason"]],["impl Send for ErrorKind",1,["webext_storage::error::ErrorKind"]],["impl Send for Error",1,["webext_storage::error::Error"]],["impl Send for MigrationInfo",1,["webext_storage::migration::MigrationInfo"]],["impl Send for Store",1,["webext_storage::store::Store"]]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/core/marker/trait.StructuralEq.js b/book/rust-docs/implementors/core/marker/trait.StructuralEq.js new file mode 100644 index 0000000000..975cccc8c6 --- /dev/null +++ b/book/rust-docs/implementors/core/marker/trait.StructuralEq.js @@ -0,0 +1,20 @@ +(function() {var implementors = { +"autofill":[["impl StructuralEq for Metadata"],["impl StructuralEq for Address"]], +"examples_fxa_client":[["impl StructuralEq for Server"]], +"fxa_client":[["impl StructuralEq for FxaRustAuthState"],["impl StructuralEq for DeviceCapability"]], +"logins":[["impl StructuralEq for LoginFields"],["impl StructuralEq for RecordFields"],["impl StructuralEq for Login"],["impl StructuralEq for EncryptedLogin"],["impl StructuralEq for SecureLoginFields"],["impl StructuralEq for LoginEntry"]], +"nimbus":[["impl StructuralEq for Branch"],["impl StructuralEq for BucketConfig"],["impl StructuralEq for EnrollmentStatus"],["impl StructuralEq for EnrolledFeature"],["impl StructuralEq for FeatureConfig"],["impl StructuralEq for RandomizationUnit"],["impl StructuralEq for Experiment"]], +"nimbus_fml":[["impl StructuralEq for FeatureManifest"],["impl StructuralEq for TargetLanguage"],["impl StructuralEq for VariantDef"],["impl StructuralEq for AboutBlock"],["impl StructuralEq for EnumDef"],["impl StructuralEq for FeatureDef"],["impl StructuralEq for KotlinAboutBlock"],["impl StructuralEq for FeatureMetadata"],["impl StructuralEq for DocumentationLink"],["impl StructuralEq for ObjectDef"],["impl StructuralEq for TypeRef"],["impl StructuralEq for SwiftAboutBlock"],["impl StructuralEq for PropDef"],["impl StructuralEq for ModuleId"],["impl StructuralEq for FromStringDef"]], +"nss":[["impl StructuralEq for Curve"]], +"nss_build_common":[["impl StructuralEq for LinkingKind"],["impl StructuralEq for NoNssDir"]], +"nss_sys":[["impl StructuralEq for _SECStatus"]], +"places":[["impl StructuralEq for LivemarkRecord"],["impl StructuralEq for HistoryRecordVisit"],["impl StructuralEq for MatchBehavior"],["impl StructuralEq for BookmarkItemRecord"],["impl StructuralEq for FolderRecord"],["impl StructuralEq for BookmarkRecord"],["impl StructuralEq for SyncedBookmarkKind"],["impl StructuralEq for BookmarkRootGuid"],["impl StructuralEq for QueryRecord"],["impl StructuralEq for SyncStatus"],["impl StructuralEq for SyncedBookmarkValidity"],["impl StructuralEq for FetchedVisit"],["impl StructuralEq for SeparatorRecord"],["impl StructuralEq for RedirectSourceType"],["impl StructuralEq for HistoryVisitInfosWithBound"],["impl StructuralEq for SearchResult"],["impl<'a> StructuralEq for ValidatedTag<'a>"],["impl StructuralEq for HistoryMigrationResult"],["impl StructuralEq for BookmarkType"],["impl StructuralEq for DocumentType"],["impl StructuralEq for ConnectionType"],["impl StructuralEq for HistoryMetadata"],["impl StructuralEq for HistoryRecord"],["impl StructuralEq for VisitTransitionSet"],["impl StructuralEq for RowId"],["impl StructuralEq for FrecencySettings"],["impl StructuralEq for VisitType"],["impl StructuralEq for InvalidVisitType"],["impl StructuralEq for PrefixMode"],["impl StructuralEq for BookmarkRecordId"],["impl StructuralEq for BookmarkUpdateInfo"],["impl StructuralEq for HistoryMetadataObservation"],["impl StructuralEq for SearchBehavior"],["impl StructuralEq for ServerVisitTimestamp"],["impl StructuralEq for HistoryVisitInfo"]], +"push":[["impl StructuralEq for SubscriptionInfo"],["impl StructuralEq for Protocol"],["impl StructuralEq for BridgeType"],["impl StructuralEq for SubscriptionResponse"],["impl StructuralEq for KeyInfo"]], +"rc_crypto":[["impl StructuralEq for Algorithm"]], +"rc_log_ffi":[["impl StructuralEq for LogLevel"]], +"remote_settings":[["impl StructuralEq for SortOrder"],["impl StructuralEq for RemoteSettingsResponse"],["impl StructuralEq for RemoteSettingsRecord"],["impl StructuralEq for Attachment"]], +"sync15":[["impl StructuralEq for DeviceType"],["impl StructuralEq for ServiceStatus"],["impl StructuralEq for Command"],["impl StructuralEq for CollSyncIds"],["impl StructuralEq for RemoteClient"],["impl StructuralEq for EngineSyncAssociation"],["impl StructuralEq for SyncEngineId"],["impl StructuralEq for CommandStatus"],["impl StructuralEq for CollectionRequest"],["impl StructuralEq for Sync15StorageClientInit"],["impl StructuralEq for ServerTimestamp"],["impl StructuralEq for RequestOrder"],["impl StructuralEq for KeyBundle"],["impl StructuralEq for ClientData"],["impl StructuralEq for Settings"]], +"types":[["impl StructuralEq for Timestamp"]], +"viaduct":[["impl StructuralEq for HeaderName"],["impl StructuralEq for Header"],["impl StructuralEq for InvalidHeaderName"],["impl StructuralEq for Method"],["impl StructuralEq for Headers"]], +"webext_storage":[["impl StructuralEq for UsageInfo"],["impl StructuralEq for MigrationInfo"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/core/marker/trait.StructuralPartialEq.js b/book/rust-docs/implementors/core/marker/trait.StructuralPartialEq.js new file mode 100644 index 0000000000..edcae47a86 --- /dev/null +++ b/book/rust-docs/implementors/core/marker/trait.StructuralPartialEq.js @@ -0,0 +1,21 @@ +(function() {var implementors = { +"autofill":[["impl StructuralPartialEq for Metadata"],["impl StructuralPartialEq for Address"]], +"examples_fxa_client":[["impl StructuralPartialEq for Server"]], +"fxa_client":[["impl StructuralPartialEq for FxaRustAuthState"],["impl StructuralPartialEq for DeviceCapability"]], +"logins":[["impl StructuralPartialEq for SecureLoginFields"],["impl StructuralPartialEq for RecordFields"],["impl StructuralPartialEq for LoginFields"],["impl StructuralPartialEq for EncryptedLogin"],["impl StructuralPartialEq for LoginEntry"],["impl StructuralPartialEq for Login"]], +"nimbus":[["impl StructuralPartialEq for RandomizationUnit"],["impl StructuralPartialEq for EnrollmentStatus"],["impl StructuralPartialEq for FeatureConfig"],["impl StructuralPartialEq for Branch"],["impl StructuralPartialEq for Experiment"],["impl StructuralPartialEq for BucketConfig"],["impl StructuralPartialEq for EnrolledFeature"]], +"nimbus_cli":[["impl StructuralPartialEq for ExperimentListSource"],["impl StructuralPartialEq for ExperimentListFilter"],["impl StructuralPartialEq for AppOpenArgs"],["impl StructuralPartialEq for ManifestSource"],["impl StructuralPartialEq for ExperimentSource"],["impl StructuralPartialEq for AppCommand"],["impl StructuralPartialEq for LaunchableApp"],["impl StructuralPartialEq for NimbusApp"]], +"nimbus_fml":[["impl StructuralPartialEq for KotlinAboutBlock"],["impl StructuralPartialEq for AboutBlock"],["impl StructuralPartialEq for ObjectDef"],["impl StructuralPartialEq for FeatureMetadata"],["impl StructuralPartialEq for TypeRef"],["impl StructuralPartialEq for SwiftAboutBlock"],["impl StructuralPartialEq for PropDef"],["impl StructuralPartialEq for TargetLanguage"],["impl StructuralPartialEq for FeatureDef"],["impl StructuralPartialEq for FromStringDef"],["impl StructuralPartialEq for FeatureManifest"],["impl StructuralPartialEq for ModuleId"],["impl StructuralPartialEq for VariantDef"],["impl StructuralPartialEq for DocumentationLink"],["impl StructuralPartialEq for EnumDef"]], +"nss":[["impl StructuralPartialEq for Curve"]], +"nss_build_common":[["impl StructuralPartialEq for LinkingKind"],["impl StructuralPartialEq for NoNssDir"]], +"nss_sys":[["impl StructuralPartialEq for _SECStatus"]], +"places":[["impl StructuralPartialEq for FolderRecord"],["impl StructuralPartialEq for QueryRecord"],["impl StructuralPartialEq for HistoryRecord"],["impl StructuralPartialEq for HistoryMetadataObservation"],["impl<'a> StructuralPartialEq for ValidatedTag<'a>"],["impl StructuralPartialEq for HistoryMetadata"],["impl StructuralPartialEq for HistoryVisitInfosWithBound"],["impl StructuralPartialEq for MatchBehavior"],["impl StructuralPartialEq for SeparatorRecord"],["impl StructuralPartialEq for SyncedBookmarkKind"],["impl StructuralPartialEq for ServerVisitTimestamp"],["impl StructuralPartialEq for VisitType"],["impl StructuralPartialEq for BookmarkType"],["impl StructuralPartialEq for HistoryRecordVisit"],["impl StructuralPartialEq for BookmarkUpdateInfo"],["impl StructuralPartialEq for RedirectSourceType"],["impl StructuralPartialEq for SearchBehavior"],["impl StructuralPartialEq for BookmarkItemRecord"],["impl StructuralPartialEq for HistoryVisitInfo"],["impl StructuralPartialEq for FetchedVisit"],["impl StructuralPartialEq for BookmarkRecord"],["impl StructuralPartialEq for BookmarkRecordId"],["impl StructuralPartialEq for VisitTransitionSet"],["impl StructuralPartialEq for SyncedBookmarkValidity"],["impl StructuralPartialEq for LivemarkRecord"],["impl StructuralPartialEq for SearchResult"],["impl StructuralPartialEq for HistoryMigrationResult"],["impl StructuralPartialEq for DocumentType"],["impl StructuralPartialEq for FrecencySettings"],["impl StructuralPartialEq for RowId"],["impl StructuralPartialEq for SyncStatus"],["impl StructuralPartialEq for ConnectionType"],["impl StructuralPartialEq for InvalidVisitType"],["impl StructuralPartialEq for PrefixMode"],["impl StructuralPartialEq for BookmarkRootGuid"]], +"push":[["impl StructuralPartialEq for Protocol"],["impl StructuralPartialEq for SubscriptionInfo"],["impl StructuralPartialEq for BridgeType"],["impl StructuralPartialEq for KeyInfo"],["impl StructuralPartialEq for SubscriptionResponse"]], +"rc_crypto":[["impl StructuralPartialEq for Algorithm"]], +"rc_log_ffi":[["impl StructuralPartialEq for LogLevel"]], +"remote_settings":[["impl StructuralPartialEq for RemoteSettingsRecord"],["impl StructuralPartialEq for SortOrder"],["impl StructuralPartialEq for RemoteSettingsResponse"],["impl StructuralPartialEq for Attachment"]], +"sync15":[["impl StructuralPartialEq for ClientData"],["impl StructuralPartialEq for SyncEngineId"],["impl StructuralPartialEq for CollSyncIds"],["impl StructuralPartialEq for Command"],["impl StructuralPartialEq for KeyBundle"],["impl StructuralPartialEq for ServerTimestamp"],["impl StructuralPartialEq for DeviceType"],["impl StructuralPartialEq for EngineSyncAssociation"],["impl StructuralPartialEq for RemoteClient"],["impl StructuralPartialEq for Sync15StorageClientInit"],["impl StructuralPartialEq for CollectionRequest"],["impl StructuralPartialEq for CommandStatus"],["impl StructuralPartialEq for ServiceStatus"],["impl StructuralPartialEq for RequestOrder"],["impl StructuralPartialEq for Settings"]], +"types":[["impl StructuralPartialEq for Timestamp"]], +"viaduct":[["impl StructuralPartialEq for Headers"],["impl StructuralPartialEq for HeaderName"],["impl StructuralPartialEq for InvalidHeaderName"],["impl StructuralPartialEq for Header"],["impl StructuralPartialEq for Method"]], +"webext_storage":[["impl StructuralPartialEq for MigrationInfo"],["impl StructuralPartialEq for UsageInfo"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/core/marker/trait.Sync.js b/book/rust-docs/implementors/core/marker/trait.Sync.js new file mode 100644 index 0000000000..efe0320ff0 --- /dev/null +++ b/book/rust-docs/implementors/core/marker/trait.Sync.js @@ -0,0 +1,32 @@ +(function() {var implementors = { +"as_ohttp_client":[["impl Sync for OhttpError",1,["as_ohttp_client::OhttpError"]],["impl Sync for OhttpSession",1,["as_ohttp_client::OhttpSession"]],["impl Sync for OhttpResponse",1,["as_ohttp_client::OhttpResponse"]],["impl Sync for OhttpTestServer",1,["as_ohttp_client::OhttpTestServer"]],["impl Sync for TestServerRequest",1,["as_ohttp_client::TestServerRequest"]]], +"autofill":[["impl Sync for UpdatableAddressFields",1,["autofill::db::models::address::UpdatableAddressFields"]],["impl Sync for Address",1,["autofill::db::models::address::Address"]],["impl Sync for InternalAddress",1,["autofill::db::models::address::InternalAddress"]],["impl Sync for UpdatableCreditCardFields",1,["autofill::db::models::credit_card::UpdatableCreditCardFields"]],["impl Sync for CreditCard",1,["autofill::db::models::credit_card::CreditCard"]],["impl Sync for InternalCreditCard",1,["autofill::db::models::credit_card::InternalCreditCard"]],["impl Sync for Metadata",1,["autofill::db::models::Metadata"]],["impl Sync for AutofillConnectionInitializer",1,["autofill::db::schema::AutofillConnectionInitializer"]],["impl Sync for Store",1,["autofill::db::store::Store"]],["impl !Sync for AutofillDb",1,["autofill::db::AutofillDb"]],["impl Sync for AutofillApiError",1,["autofill::error::AutofillApiError"]],["impl Sync for Error",1,["autofill::error::Error"]],["impl Sync for AddressPayload",1,["autofill::sync::address::AddressPayload"]],["impl Sync for EngineConfig",1,["autofill::sync::engine::EngineConfig"]],["impl<T> !Sync for ConfigSyncEngine<T>",1,["autofill::sync::engine::ConfigSyncEngine"]],["impl<T> Sync for MergeResult<T>where\n T: Sync,",1,["autofill::sync::MergeResult"]],["impl<T> Sync for IncomingState<T>where\n T: Sync,",1,["autofill::sync::IncomingState"]]], +"cli_support":[["impl Sync for CliFxa",1,["cli_support::fxa_creds::CliFxa"]]], +"crashtest":[["impl Sync for CrashTestError",1,["crashtest::CrashTestError"]]], +"error_support":[["impl Sync for ErrorReporting",1,["error_support::handling::ErrorReporting"]],["impl<E> Sync for ErrorHandling<E>where\n E: Sync,",1,["error_support::handling::ErrorHandling"]]], +"examples_fxa_client":[["impl Sync for DeviceArgs",1,["examples_fxa_client::devices::DeviceArgs"]],["impl Sync for Command",1,["examples_fxa_client::devices::Command"]],["impl Sync for SendTabArgs",1,["examples_fxa_client::send_tab::SendTabArgs"]],["impl Sync for Command",1,["examples_fxa_client::send_tab::Command"]],["impl Sync for Cli",1,["examples_fxa_client::Cli"]],["impl Sync for Server",1,["examples_fxa_client::Server"]],["impl Sync for Command",1,["examples_fxa_client::Command"]]], +"fxa_client":[["impl Sync for AuthorizationInfo",1,["fxa_client::auth::AuthorizationInfo"]],["impl Sync for MetricsParams",1,["fxa_client::auth::MetricsParams"]],["impl Sync for FxaRustAuthState",1,["fxa_client::auth::FxaRustAuthState"]],["impl Sync for LocalDevice",1,["fxa_client::device::LocalDevice"]],["impl Sync for Device",1,["fxa_client::device::Device"]],["impl Sync for DeviceCapability",1,["fxa_client::device::DeviceCapability"]],["impl Sync for AttachedClient",1,["fxa_client::device::AttachedClient"]],["impl Sync for FxaError",1,["fxa_client::error::FxaError"]],["impl Sync for Error",1,["fxa_client::error::Error"]],["impl Sync for Profile",1,["fxa_client::profile::Profile"]],["impl Sync for DevicePushSubscription",1,["fxa_client::push::DevicePushSubscription"]],["impl Sync for AccountEvent",1,["fxa_client::push::AccountEvent"]],["impl Sync for IncomingDeviceCommand",1,["fxa_client::push::IncomingDeviceCommand"]],["impl Sync for SendTabPayload",1,["fxa_client::push::SendTabPayload"]],["impl Sync for TabHistoryEntry",1,["fxa_client::push::TabHistoryEntry"]],["impl Sync for AccessTokenInfo",1,["fxa_client::token::AccessTokenInfo"]],["impl Sync for ScopedKey",1,["fxa_client::token::ScopedKey"]],["impl Sync for AuthorizationParameters",1,["fxa_client::token::AuthorizationParameters"]],["impl Sync for FirefoxAccount",1,["fxa_client::FirefoxAccount"]],["impl Sync for FxaConfig",1,["fxa_client::FxaConfig"]],["impl Sync for FxaServer",1,["fxa_client::FxaServer"]]], +"interrupt_support":[["impl Sync for Interrupted",1,["interrupt_support::error::Interrupted"]],["impl Sync for NeverInterrupts",1,["interrupt_support::interruptee::NeverInterrupts"]],["impl Sync for ShutdownInterruptee",1,["interrupt_support::shutdown::ShutdownInterruptee"]],["impl Sync for SqlInterruptHandle",1,["interrupt_support::sql::SqlInterruptHandle"]],["impl Sync for SqlInterruptScope",1,["interrupt_support::sql::SqlInterruptScope"]]], +"logins":[["impl Sync for LoginsApiError",1,["logins::error::LoginsApiError"]],["impl Sync for Error",1,["logins::error::Error"]],["impl Sync for InvalidLogin",1,["logins::error::InvalidLogin"]],["impl Sync for LoginFields",1,["logins::login::LoginFields"]],["impl Sync for SecureLoginFields",1,["logins::login::SecureLoginFields"]],["impl Sync for RecordFields",1,["logins::login::RecordFields"]],["impl Sync for LoginEntry",1,["logins::login::LoginEntry"]],["impl Sync for Login",1,["logins::login::Login"]],["impl Sync for EncryptedLogin",1,["logins::login::EncryptedLogin"]],["impl !Sync for LoginDb",1,["logins::db::LoginDb"]],["impl Sync for LoginStore",1,["logins::store::LoginStore"]],["impl !Sync for LoginsSyncEngine",1,["logins::sync::engine::LoginsSyncEngine"]]], +"nimbus":[["impl Sync for EnrollmentStatus",1,["nimbus::enrollment::EnrollmentStatus"]],["impl Sync for EnrolledFeature",1,["nimbus::enrollment::EnrolledFeature"]],["impl Sync for NimbusTargetingHelper",1,["nimbus::targeting::NimbusTargetingHelper"]],["impl Sync for NimbusError",1,["nimbus::error::NimbusError"]],["impl Sync for BehaviorError",1,["nimbus::error::BehaviorError"]],["impl Sync for EnrollmentStatusExtraDef",1,["nimbus::metrics::EnrollmentStatusExtraDef"]],["impl Sync for EnrolledExperiment",1,["nimbus::schema::EnrolledExperiment"]],["impl Sync for Experiment",1,["nimbus::schema::Experiment"]],["impl Sync for FeatureConfig",1,["nimbus::schema::FeatureConfig"]],["impl Sync for Branch",1,["nimbus::schema::Branch"]],["impl Sync for BucketConfig",1,["nimbus::schema::BucketConfig"]],["impl Sync for AvailableExperiment",1,["nimbus::schema::AvailableExperiment"]],["impl Sync for ExperimentBranch",1,["nimbus::schema::ExperimentBranch"]],["impl Sync for RandomizationUnit",1,["nimbus::schema::RandomizationUnit"]],["impl Sync for AvailableRandomizationUnits",1,["nimbus::schema::AvailableRandomizationUnits"]],["impl Sync for Version",1,["nimbus::versioning::Version"]],["impl Sync for Interval",1,["nimbus::stateful::behavior::Interval"]],["impl Sync for IntervalConfig",1,["nimbus::stateful::behavior::IntervalConfig"]],["impl Sync for IntervalData",1,["nimbus::stateful::behavior::IntervalData"]],["impl Sync for SingleIntervalCounter",1,["nimbus::stateful::behavior::SingleIntervalCounter"]],["impl Sync for MultiIntervalCounter",1,["nimbus::stateful::behavior::MultiIntervalCounter"]],["impl Sync for EventQueryType",1,["nimbus::stateful::behavior::EventQueryType"]],["impl Sync for EventStore",1,["nimbus::stateful::behavior::EventStore"]],["impl Sync for DatabaseCache",1,["nimbus::stateful::dbcache::DatabaseCache"]],["impl Sync for TargetingAttributes",1,["nimbus::stateful::evaluator::TargetingAttributes"]],["impl Sync for AppContext",1,["nimbus::stateful::matcher::AppContext"]],["impl Sync for InternalMutableState",1,["nimbus::stateful::nimbus_client::InternalMutableState"]],["impl Sync for NimbusClient",1,["nimbus::stateful::nimbus_client::NimbusClient"]],["impl Sync for NimbusStringHelper",1,["nimbus::stateful::nimbus_client::NimbusStringHelper"]],["impl Sync for StoreId",1,["nimbus::stateful::persistence::StoreId"]],["impl Sync for SingleStore",1,["nimbus::stateful::persistence::SingleStore"]],["impl Sync for Database",1,["nimbus::stateful::persistence::Database"]]], +"nimbus_cli":[["impl Sync for Cli",1,["nimbus_cli::cli::Cli"]],["impl Sync for CliCommand",1,["nimbus_cli::cli::CliCommand"]],["impl Sync for ManifestArgs",1,["nimbus_cli::cli::ManifestArgs"]],["impl Sync for OpenArgs",1,["nimbus_cli::cli::OpenArgs"]],["impl Sync for ExperimentArgs",1,["nimbus_cli::cli::ExperimentArgs"]],["impl Sync for ExperimentListArgs",1,["nimbus_cli::cli::ExperimentListArgs"]],["impl Sync for ExperimentListSourceArgs",1,["nimbus_cli::cli::ExperimentListSourceArgs"]],["impl Sync for ExperimentListFilterArgs",1,["nimbus_cli::cli::ExperimentListFilterArgs"]],["impl<'a> Sync for ExperimentInfo<'a>",1,["nimbus_cli::output::info::ExperimentInfo"]],["impl<'a> Sync for DateRange<'a>",1,["nimbus_cli::output::info::DateRange"]],["impl Sync for StartAppPostPayload",1,["nimbus_cli::output::server::StartAppPostPayload"]],["impl Sync for InMemoryDb",1,["nimbus_cli::output::server::InMemoryDb"]],["impl<'a> Sync for StartAppProtocol<'a>",1,["nimbus_cli::protocol::StartAppProtocol"]],["impl Sync for ExperimentSource",1,["nimbus_cli::sources::experiment::ExperimentSource"]],["impl Sync for FeatureDefaults",1,["nimbus_cli::sources::experiment::FeatureDefaults"]],["impl Sync for ExperimentListSource",1,["nimbus_cli::sources::experiment_list::ExperimentListSource"]],["impl Sync for ExperimentListFilter",1,["nimbus_cli::sources::filter::ExperimentListFilter"]],["impl Sync for ManifestSource",1,["nimbus_cli::sources::manifest::ManifestSource"]],["impl Sync for Response",1,["nimbus_cli::updater::taskcluster::Response"]],["impl Sync for TaskClusterRegistry",1,["nimbus_cli::updater::taskcluster::TaskClusterRegistry"]],["impl Sync for ReqwestGunzippingHttpClient",1,["nimbus_cli::updater::taskcluster::ReqwestGunzippingHttpClient"]],["impl Sync for LaunchableApp",1,["nimbus_cli::LaunchableApp"]],["impl Sync for NimbusApp",1,["nimbus_cli::NimbusApp"]],["impl Sync for AppCommand",1,["nimbus_cli::AppCommand"]],["impl Sync for AppOpenArgs",1,["nimbus_cli::AppOpenArgs"]]], +"nimbus_fml":[["impl Sync for ExperimenterFeature",1,["nimbus_fml::backends::experimenter_manifest::ExperimenterFeature"]],["impl Sync for ExperimenterFeatureProperty",1,["nimbus_fml::backends::experimenter_manifest::ExperimenterFeatureProperty"]],["impl Sync for ExperimentManifestPropType",1,["nimbus_fml::backends::experimenter_manifest::ExperimentManifestPropType"]],["impl Sync for TextCodeType",1,["nimbus_fml::backends::kotlin::gen_structs::bundled::TextCodeType"]],["impl Sync for ImageCodeType",1,["nimbus_fml::backends::kotlin::gen_structs::bundled::ImageCodeType"]],["impl Sync for EnumCodeType",1,["nimbus_fml::backends::kotlin::gen_structs::enum_::EnumCodeType"]],["impl Sync for EnumCodeDeclaration",1,["nimbus_fml::backends::kotlin::gen_structs::enum_::EnumCodeDeclaration"]],["impl Sync for FeatureCodeDeclaration",1,["nimbus_fml::backends::kotlin::gen_structs::feature::FeatureCodeDeclaration"]],["impl<'a> Sync for ImportedModuleInitialization<'a>",1,["nimbus_fml::backends::kotlin::gen_structs::imports::ImportedModuleInitialization"]],["impl Sync for ObjectRuntime",1,["nimbus_fml::backends::kotlin::gen_structs::object::ObjectRuntime"]],["impl Sync for ObjectCodeType",1,["nimbus_fml::backends::kotlin::gen_structs::object::ObjectCodeType"]],["impl Sync for ObjectCodeDeclaration",1,["nimbus_fml::backends::kotlin::gen_structs::object::ObjectCodeDeclaration"]],["impl Sync for BooleanCodeType",1,["nimbus_fml::backends::kotlin::gen_structs::primitives::BooleanCodeType"]],["impl Sync for IntCodeType",1,["nimbus_fml::backends::kotlin::gen_structs::primitives::IntCodeType"]],["impl Sync for StringCodeType",1,["nimbus_fml::backends::kotlin::gen_structs::primitives::StringCodeType"]],["impl Sync for OptionalCodeType",1,["nimbus_fml::backends::kotlin::gen_structs::structural::OptionalCodeType"]],["impl Sync for MapCodeType",1,["nimbus_fml::backends::kotlin::gen_structs::structural::MapCodeType"]],["impl Sync for ListCodeType",1,["nimbus_fml::backends::kotlin::gen_structs::structural::ListCodeType"]],["impl<'a> Sync for FeatureManifestDeclaration<'a>",1,["nimbus_fml::backends::kotlin::gen_structs::FeatureManifestDeclaration"]],["impl Sync for ConcreteCodeOracle",1,["nimbus_fml::backends::kotlin::gen_structs::ConcreteCodeOracle"]],["impl Sync for TextCodeType",1,["nimbus_fml::backends::swift::gen_structs::bundled::TextCodeType"]],["impl Sync for ImageCodeType",1,["nimbus_fml::backends::swift::gen_structs::bundled::ImageCodeType"]],["impl Sync for EnumCodeType",1,["nimbus_fml::backends::swift::gen_structs::enum_::EnumCodeType"]],["impl Sync for EnumCodeDeclaration",1,["nimbus_fml::backends::swift::gen_structs::enum_::EnumCodeDeclaration"]],["impl Sync for FeatureCodeDeclaration",1,["nimbus_fml::backends::swift::gen_structs::feature::FeatureCodeDeclaration"]],["impl<'a> Sync for ImportedModuleInitialization<'a>",1,["nimbus_fml::backends::swift::gen_structs::imports::ImportedModuleInitialization"]],["impl Sync for ObjectRuntime",1,["nimbus_fml::backends::swift::gen_structs::object::ObjectRuntime"]],["impl Sync for ObjectCodeType",1,["nimbus_fml::backends::swift::gen_structs::object::ObjectCodeType"]],["impl Sync for ObjectCodeDeclaration",1,["nimbus_fml::backends::swift::gen_structs::object::ObjectCodeDeclaration"]],["impl Sync for BooleanCodeType",1,["nimbus_fml::backends::swift::gen_structs::primitives::BooleanCodeType"]],["impl Sync for IntCodeType",1,["nimbus_fml::backends::swift::gen_structs::primitives::IntCodeType"]],["impl Sync for StringCodeType",1,["nimbus_fml::backends::swift::gen_structs::primitives::StringCodeType"]],["impl Sync for OptionalCodeType",1,["nimbus_fml::backends::swift::gen_structs::structural::OptionalCodeType"]],["impl Sync for MapCodeType",1,["nimbus_fml::backends::swift::gen_structs::structural::MapCodeType"]],["impl Sync for ListCodeType",1,["nimbus_fml::backends::swift::gen_structs::structural::ListCodeType"]],["impl<'a> Sync for FeatureManifestDeclaration<'a>",1,["nimbus_fml::backends::swift::gen_structs::FeatureManifestDeclaration"]],["impl Sync for ConcreteCodeOracle",1,["nimbus_fml::backends::swift::gen_structs::ConcreteCodeOracle"]],["impl Sync for VariablesType",1,["nimbus_fml::backends::VariablesType"]],["impl Sync for CliCmd",1,["nimbus_fml::command_line::commands::CliCmd"]],["impl Sync for GenerateStructCmd",1,["nimbus_fml::command_line::commands::GenerateStructCmd"]],["impl Sync for GenerateExperimenterManifestCmd",1,["nimbus_fml::command_line::commands::GenerateExperimenterManifestCmd"]],["impl Sync for GenerateSingleFileManifestCmd",1,["nimbus_fml::command_line::commands::GenerateSingleFileManifestCmd"]],["impl Sync for ValidateCmd",1,["nimbus_fml::command_line::commands::ValidateCmd"]],["impl Sync for PrintChannelsCmd",1,["nimbus_fml::command_line::commands::PrintChannelsCmd"]],["impl<'object> Sync for DefaultsMerger<'object>",1,["nimbus_fml::defaults_merger::DefaultsMerger"]],["impl Sync for FMLError",1,["nimbus_fml::error::FMLError"]],["impl Sync for ClientError",1,["nimbus_fml::error::ClientError"]],["impl Sync for EnumVariantBody",1,["nimbus_fml::frontend::EnumVariantBody"]],["impl Sync for EnumBody",1,["nimbus_fml::frontend::EnumBody"]],["impl Sync for FeatureFieldBody",1,["nimbus_fml::frontend::FeatureFieldBody"]],["impl Sync for FieldBody",1,["nimbus_fml::frontend::FieldBody"]],["impl Sync for ObjectBody",1,["nimbus_fml::frontend::ObjectBody"]],["impl Sync for Types",1,["nimbus_fml::frontend::Types"]],["impl Sync for AboutBlock",1,["nimbus_fml::frontend::AboutBlock"]],["impl Sync for SwiftAboutBlock",1,["nimbus_fml::frontend::SwiftAboutBlock"]],["impl Sync for KotlinAboutBlock",1,["nimbus_fml::frontend::KotlinAboutBlock"]],["impl Sync for ImportBlock",1,["nimbus_fml::frontend::ImportBlock"]],["impl Sync for FeatureBody",1,["nimbus_fml::frontend::FeatureBody"]],["impl Sync for FeatureMetadata",1,["nimbus_fml::frontend::FeatureMetadata"]],["impl Sync for DocumentationLink",1,["nimbus_fml::frontend::DocumentationLink"]],["impl Sync for ManifestFrontEnd",1,["nimbus_fml::frontend::ManifestFrontEnd"]],["impl Sync for DefaultBlock",1,["nimbus_fml::frontend::DefaultBlock"]],["impl Sync for TargetLanguage",1,["nimbus_fml::intermediate_representation::TargetLanguage"]],["impl Sync for TypeRef",1,["nimbus_fml::intermediate_representation::TypeRef"]],["impl Sync for ModuleId",1,["nimbus_fml::intermediate_representation::ModuleId"]],["impl Sync for FeatureManifest",1,["nimbus_fml::intermediate_representation::FeatureManifest"]],["impl Sync for FeatureDef",1,["nimbus_fml::intermediate_representation::FeatureDef"]],["impl Sync for EnumDef",1,["nimbus_fml::intermediate_representation::EnumDef"]],["impl Sync for FromStringDef",1,["nimbus_fml::intermediate_representation::FromStringDef"]],["impl Sync for VariantDef",1,["nimbus_fml::intermediate_representation::VariantDef"]],["impl Sync for ObjectDef",1,["nimbus_fml::intermediate_representation::ObjectDef"]],["impl Sync for PropDef",1,["nimbus_fml::intermediate_representation::PropDef"]],["impl<'a> Sync for ImportedModule<'a>",1,["nimbus_fml::intermediate_representation::ImportedModule"]],["impl Sync for Parser",1,["nimbus_fml::parser::Parser"]],["impl Sync for LoaderConfig",1,["nimbus_fml::util::loaders::LoaderConfig"]],["impl Sync for FilePath",1,["nimbus_fml::util::loaders::FilePath"]],["impl Sync for FileLoader",1,["nimbus_fml::util::loaders::FileLoader"]]], +"nss":[["impl Sync for Operation",1,["nss::aes::Operation"]],["impl Sync for Curve",1,["nss::ec::Curve"]],["impl Sync for EcKey",1,["nss::ec::EcKey"]],["impl !Sync for PrivateKey",1,["nss::ec::PrivateKey"]],["impl !Sync for PublicKey",1,["nss::ec::PublicKey"]],["impl Sync for ErrorKind",1,["nss::error::ErrorKind"]],["impl Sync for Error",1,["nss::error::Error"]],["impl Sync for HashAlgorithm",1,["nss::pk11::context::HashAlgorithm"]],["impl !Sync for SymKey",1,["nss::pk11::types::SymKey"]],["impl !Sync for PrivateKey",1,["nss::pk11::types::PrivateKey"]],["impl !Sync for PublicKey",1,["nss::pk11::types::PublicKey"]],["impl !Sync for GenericObject",1,["nss::pk11::types::GenericObject"]],["impl !Sync for Certificate",1,["nss::pk11::types::Certificate"]],["impl !Sync for Context",1,["nss::pk11::types::Context"]],["impl !Sync for Slot",1,["nss::pk11::types::Slot"]],["impl !Sync for AlgorithmID",1,["nss::pk11::types::AlgorithmID"]]], +"nss_build_common":[["impl Sync for LinkingKind",1,["nss_build_common::LinkingKind"]],["impl Sync for NoNssDir",1,["nss_build_common::NoNssDir"]]], +"nss_sys":[["impl !Sync for SECKEYPublicKeyStr",1,["nss_sys::bindings::keythi::SECKEYPublicKeyStr"]],["impl !Sync for SECKEYPublicKeyStr_u",1,["nss_sys::bindings::keythi::SECKEYPublicKeyStr_u"]],["impl !Sync for SECKEYPrivateKeyStr",1,["nss_sys::bindings::keythi::SECKEYPrivateKeyStr"]],["impl Sync for KeyType",1,["nss_sys::bindings::keythi::KeyType"]],["impl !Sync for SECKEYRSAPublicKeyStr",1,["nss_sys::bindings::keythi::SECKEYRSAPublicKeyStr"]],["impl !Sync for SECKEYDSAPublicKeyStr",1,["nss_sys::bindings::keythi::SECKEYDSAPublicKeyStr"]],["impl !Sync for SECKEYPQGParamsStr",1,["nss_sys::bindings::keythi::SECKEYPQGParamsStr"]],["impl !Sync for SECKEYDHPublicKeyStr",1,["nss_sys::bindings::keythi::SECKEYDHPublicKeyStr"]],["impl !Sync for SECKEYKEAPublicKeyStr",1,["nss_sys::bindings::keythi::SECKEYKEAPublicKeyStr"]],["impl !Sync for SECKEYKEAParamsStr",1,["nss_sys::bindings::keythi::SECKEYKEAParamsStr"]],["impl !Sync for SECKEYFortezzaPublicKeyStr",1,["nss_sys::bindings::keythi::SECKEYFortezzaPublicKeyStr"]],["impl !Sync for SECKEYECPublicKeyStr",1,["nss_sys::bindings::keythi::SECKEYECPublicKeyStr"]],["impl Sync for ECPointEncoding",1,["nss_sys::bindings::keythi::ECPointEncoding"]],["impl !Sync for CK_GCM_PARAMS_V3",1,["nss_sys::bindings::pkcs11n::CK_GCM_PARAMS_V3"]],["impl !Sync for CK_NSS_HKDFParams",1,["nss_sys::bindings::pkcs11n::CK_NSS_HKDFParams"]],["impl !Sync for CK_ATTRIBUTE",1,["nss_sys::bindings::pkcs11t::CK_ATTRIBUTE"]],["impl !Sync for PLArena",1,["nss_sys::bindings::plarena::PLArena"]],["impl !Sync for PLArenaPool",1,["nss_sys::bindings::plarena::PLArenaPool"]],["impl Sync for SECItemType",1,["nss_sys::bindings::seccomon::SECItemType"]],["impl !Sync for SECItemStr",1,["nss_sys::bindings::seccomon::SECItemStr"]],["impl Sync for _SECStatus",1,["nss_sys::bindings::seccomon::_SECStatus"]],["impl Sync for PK11Origin",1,["nss_sys::bindings::secmodt::PK11Origin"]],["impl Sync for PK11ObjectType",1,["nss_sys::bindings::secmodt::PK11ObjectType"]],["impl !Sync for SECAlgorithmIDStr",1,["nss_sys::bindings::secoidt::SECAlgorithmIDStr"]],["impl !Sync for SECOidDataStr",1,["nss_sys::bindings::secoidt::SECOidDataStr"]],["impl Sync for SECSupportExtenTag",1,["nss_sys::bindings::secoidt::SECSupportExtenTag"]],["impl Sync for SECOidTag",1,["nss_sys::bindings::secoidt::SECOidTag"]]], +"places":[["impl Sync for AddablePlaceInfo",1,["places::api::history::AddablePlaceInfo"]],["impl Sync for AddableVisit",1,["places::api::history::AddableVisit"]],["impl Sync for RedirectSourceType",1,["places::api::history::RedirectSourceType"]],["impl Sync for SearchParams",1,["places::api::matcher::SearchParams"]],["impl Sync for SearchResult",1,["places::api::matcher::SearchResult"]],["impl Sync for ConnectionType",1,["places::api::places_api::ConnectionType"]],["impl !Sync for SyncState",1,["places::api::places_api::SyncState"]],["impl Sync for PlacesApi",1,["places::api::places_api::PlacesApi"]],["impl Sync for PlacesApiError",1,["places::error::PlacesApiError"]],["impl Sync for Error",1,["places::error::Error"]],["impl Sync for InvalidPlaceInfo",1,["places::error::InvalidPlaceInfo"]],["impl Sync for Corruption",1,["places::error::Corruption"]],["impl Sync for InvalidMetadataObservation",1,["places::error::InvalidMetadataObservation"]],["impl Sync for VisitTransitionSet",1,["places::types::visit_transition_set::VisitTransitionSet"]],["impl Sync for InvalidVisitType",1,["places::types::InvalidVisitType"]],["impl Sync for VisitType",1,["places::types::VisitType"]],["impl Sync for BookmarkType",1,["places::types::BookmarkType"]],["impl Sync for SyncStatus",1,["places::types::SyncStatus"]],["impl Sync for BookmarksSyncEngine",1,["places::bookmark_sync::engine::BookmarksSyncEngine"]],["impl Sync for BookmarkRecordId",1,["places::bookmark_sync::record::BookmarkRecordId"]],["impl Sync for BookmarkRecord",1,["places::bookmark_sync::record::BookmarkRecord"]],["impl Sync for QueryRecord",1,["places::bookmark_sync::record::QueryRecord"]],["impl Sync for FolderRecord",1,["places::bookmark_sync::record::FolderRecord"]],["impl Sync for LivemarkRecord",1,["places::bookmark_sync::record::LivemarkRecord"]],["impl Sync for SeparatorRecord",1,["places::bookmark_sync::record::SeparatorRecord"]],["impl Sync for BookmarkItemRecord",1,["places::bookmark_sync::record::BookmarkItemRecord"]],["impl Sync for SyncedBookmarkKind",1,["places::bookmark_sync::SyncedBookmarkKind"]],["impl Sync for SyncedBookmarkValidity",1,["places::bookmark_sync::SyncedBookmarkValidity"]],["impl Sync for GLOBAL_BOOKMARK_CHANGE_COUNTERS",1,["places::db::db::GLOBAL_BOOKMARK_CHANGE_COUNTERS"]],["impl Sync for PlacesInitializer",1,["places::db::db::PlacesInitializer"]],["impl !Sync for PlacesDb",1,["places::db::db::PlacesDb"]],["impl Sync for SharedPlacesDb",1,["places::db::db::SharedPlacesDb"]],["impl Sync for GlobalChangeCounterTracker",1,["places::db::db::GlobalChangeCounterTracker"]],["impl<'conn> !Sync for PlacesTransaction<'conn>",1,["places::db::tx::PlacesTransaction"]],["impl Sync for PlacesConnection",1,["places::ffi::PlacesConnection"]],["impl Sync for HistoryVisitInfo",1,["places::ffi::HistoryVisitInfo"]],["impl Sync for HistoryVisitInfosWithBound",1,["places::ffi::HistoryVisitInfosWithBound"]],["impl Sync for TopFrecentSiteInfo",1,["places::ffi::TopFrecentSiteInfo"]],["impl Sync for FrecencyThresholdOption",1,["places::ffi::FrecencyThresholdOption"]],["impl Sync for SearchResult",1,["places::ffi::SearchResult"]],["impl Sync for Dummy",1,["places::ffi::Dummy"]],["impl Sync for FrecencySettings",1,["places::frecency::FrecencySettings"]],["impl Sync for PrefixMode",1,["places::hash::PrefixMode"]],["impl Sync for HistorySyncEngine",1,["places::history_sync::engine::HistorySyncEngine"]],["impl Sync for HistoryRecordVisit",1,["places::history_sync::record::HistoryRecordVisit"]],["impl Sync for HistoryRecord",1,["places::history_sync::record::HistoryRecord"]],["impl Sync for ServerVisitTimestamp",1,["places::history_sync::ServerVisitTimestamp"]],["impl Sync for NOW",1,["places::import::common::NOW"]],["impl<'a> !Sync for ExecuteOnDrop<'a>",1,["places::import::common::ExecuteOnDrop"]],["impl Sync for HistoryMigrationResult",1,["places::import::common::HistoryMigrationResult"]],["impl Sync for MatchBehavior",1,["places::match_impl::MatchBehavior"]],["impl Sync for SearchBehavior",1,["places::match_impl::SearchBehavior"]],["impl<'search, 'url, 'title, 'tags> Sync for AutocompleteMatch<'search, 'url, 'title, 'tags>",1,["places::match_impl::AutocompleteMatch"]],["impl Sync for VisitObservation",1,["places::observation::VisitObservation"]],["impl Sync for BookmarkData",1,["places::storage::bookmarks::fetch::BookmarkData"]],["impl Sync for Separator",1,["places::storage::bookmarks::fetch::Separator"]],["impl Sync for Folder",1,["places::storage::bookmarks::fetch::Folder"]],["impl Sync for Item",1,["places::storage::bookmarks::fetch::Item"]],["impl Sync for SEARCH_QUERY",1,["places::storage::bookmarks::fetch::SEARCH_QUERY"]],["impl Sync for RECENT_BOOKMARKS_QUERY",1,["places::storage::bookmarks::fetch::RECENT_BOOKMARKS_QUERY"]],["impl Sync for BookmarkNode",1,["places::storage::bookmarks::json_tree::BookmarkNode"]],["impl Sync for SeparatorNode",1,["places::storage::bookmarks::json_tree::SeparatorNode"]],["impl Sync for FolderNode",1,["places::storage::bookmarks::json_tree::FolderNode"]],["impl Sync for BookmarkTreeNode",1,["places::storage::bookmarks::json_tree::BookmarkTreeNode"]],["impl Sync for FetchDepth",1,["places::storage::bookmarks::json_tree::FetchDepth"]],["impl Sync for BookmarkRootGuid",1,["places::storage::bookmarks::root_guid::BookmarkRootGuid"]],["impl Sync for BookmarkPosition",1,["places::storage::bookmarks::BookmarkPosition"]],["impl Sync for InsertableBookmark",1,["places::storage::bookmarks::InsertableBookmark"]],["impl Sync for InsertableSeparator",1,["places::storage::bookmarks::InsertableSeparator"]],["impl Sync for InsertableFolder",1,["places::storage::bookmarks::InsertableFolder"]],["impl Sync for InsertableItem",1,["places::storage::bookmarks::InsertableItem"]],["impl Sync for UpdateTreeLocation",1,["places::storage::bookmarks::UpdateTreeLocation"]],["impl Sync for UpdatableBookmark",1,["places::storage::bookmarks::UpdatableBookmark"]],["impl Sync for UpdatableSeparator",1,["places::storage::bookmarks::UpdatableSeparator"]],["impl Sync for UpdatableFolder",1,["places::storage::bookmarks::UpdatableFolder"]],["impl Sync for UpdatableItem",1,["places::storage::bookmarks::UpdatableItem"]],["impl Sync for BookmarkUpdateInfo",1,["places::storage::bookmarks::BookmarkUpdateInfo"]],["impl Sync for FetchedVisit",1,["places::storage::history::history_sync::FetchedVisit"]],["impl Sync for FetchedVisitPage",1,["places::storage::history::history_sync::FetchedVisitPage"]],["impl Sync for DocumentType",1,["places::storage::history_metadata::DocumentType"]],["impl Sync for HistoryHighlightWeights",1,["places::storage::history_metadata::HistoryHighlightWeights"]],["impl Sync for HistoryHighlight",1,["places::storage::history_metadata::HistoryHighlight"]],["impl Sync for HistoryMetadataObservation",1,["places::storage::history_metadata::HistoryMetadataObservation"]],["impl Sync for HistoryMetadata",1,["places::storage::history_metadata::HistoryMetadata"]],["impl<'a> Sync for ValidatedTag<'a>",1,["places::storage::tags::ValidatedTag"]],["impl Sync for RowId",1,["places::storage::RowId"]],["impl Sync for PageInfo",1,["places::storage::PageInfo"]],["impl Sync for FetchedPageInfo",1,["places::storage::FetchedPageInfo"]],["impl Sync for RunMaintenanceMetrics",1,["places::storage::RunMaintenanceMetrics"]]], +"protobuf_gen":[["impl Sync for ProtobufOpts",1,["protobuf_gen::ProtobufOpts"]]], +"push":[["impl Sync for BridgeType",1,["push::internal::config::BridgeType"]],["impl Sync for PushConfiguration",1,["push::internal::config::PushConfiguration"]],["impl Sync for Protocol",1,["push::internal::config::Protocol"]],["impl Sync for PushApiError",1,["push::error::PushApiError"]],["impl Sync for PushError",1,["push::error::PushError"]],["impl Sync for PushManager",1,["push::PushManager"]],["impl Sync for KeyInfo",1,["push::KeyInfo"]],["impl Sync for SubscriptionInfo",1,["push::SubscriptionInfo"]],["impl Sync for SubscriptionResponse",1,["push::SubscriptionResponse"]],["impl Sync for PushSubscriptionChanged",1,["push::PushSubscriptionChanged"]]], +"rc_crypto":[["impl<'a> Sync for Aad<'a>",1,["rc_crypto::aead::Aad"]],["impl Sync for Nonce",1,["rc_crypto::aead::Nonce"]],["impl Sync for OpeningKey",1,["rc_crypto::aead::OpeningKey"]],["impl Sync for SealingKey",1,["rc_crypto::aead::SealingKey"]],["impl Sync for Algorithm",1,["rc_crypto::aead::Algorithm"]],["impl Sync for Algorithm",1,["rc_crypto::agreement::Algorithm"]],["impl Sync for Ephemeral",1,["rc_crypto::agreement::Ephemeral"]],["impl Sync for Static",1,["rc_crypto::agreement::Static"]],["impl<U> !Sync for KeyPair<U>",1,["rc_crypto::agreement::KeyPair"]],["impl !Sync for PublicKey",1,["rc_crypto::agreement::PublicKey"]],["impl<'a> Sync for UnparsedPublicKey<'a>",1,["rc_crypto::agreement::UnparsedPublicKey"]],["impl<U> !Sync for PrivateKey<U>",1,["rc_crypto::agreement::PrivateKey"]],["impl Sync for InputKeyMaterial",1,["rc_crypto::agreement::InputKeyMaterial"]],["impl Sync for Digest",1,["rc_crypto::digest::Digest"]],["impl Sync for RcCryptoRemotePublicKey",1,["rc_crypto::ece_crypto::RcCryptoRemotePublicKey"]],["impl Sync for ErrorKind",1,["rc_crypto::error::ErrorKind"]],["impl Sync for Error",1,["rc_crypto::error::Error"]],["impl Sync for Signature",1,["rc_crypto::hmac::Signature"]],["impl Sync for SigningKey",1,["rc_crypto::hmac::SigningKey"]],["impl Sync for VerificationKey",1,["rc_crypto::hmac::VerificationKey"]],["impl Sync for VerificationAlgorithm",1,["rc_crypto::signature::VerificationAlgorithm"]],["impl<'a> Sync for UnparsedPublicKey<'a>",1,["rc_crypto::signature::UnparsedPublicKey"]],["impl Sync for RcCryptoLocalKeyPair"]], +"rc_log_ffi":[["impl Sync for LogAdapterState",1,["rc_log_ffi::android::LogAdapterState"]],["impl Sync for LogSink",1,["rc_log_ffi::android::LogSink"]],["impl Sync for LogLevel",1,["rc_log_ffi::LogLevel"]]], +"remote_settings":[["impl Sync for RemoteSettingsError",1,["remote_settings::error::RemoteSettingsError"]],["impl Sync for Client",1,["remote_settings::client::Client"]],["impl Sync for RemoteSettingsResponse",1,["remote_settings::client::RemoteSettingsResponse"]],["impl Sync for RemoteSettingsRecord",1,["remote_settings::client::RemoteSettingsRecord"]],["impl Sync for Attachment",1,["remote_settings::client::Attachment"]],["impl Sync for GetItemsOptions",1,["remote_settings::client::GetItemsOptions"]],["impl Sync for SortOrder",1,["remote_settings::client::SortOrder"]],["impl Sync for RemoteSettingsConfig",1,["remote_settings::config::RemoteSettingsConfig"]],["impl Sync for RemoteSettings",1,["remote_settings::RemoteSettings"]]], +"sql_support":[["impl !Sync for Conn",1,["sql_support::conn_ext::Conn"]],["impl<'conn> !Sync for UncheckedTransaction<'conn>",1,["sql_support::conn_ext::UncheckedTransaction"]],["impl<'conn> !Sync for MaybeCached<'conn>",1,["sql_support::maybe_cached::MaybeCached"]],["impl<CI> Sync for MigratedDatabaseFile<CI>where\n CI: Sync,",1,["sql_support::open_database::test_utils::MigratedDatabaseFile"]],["impl Sync for Error",1,["sql_support::open_database::Error"]],["impl<'a, F> Sync for RepeatDisplay<'a, F>where\n F: Sync,",1,["sql_support::repeat::RepeatDisplay"]]], +"sync15":[["impl Sync for IncomingEncryptedBso",1,["sync15::bso::crypto::IncomingEncryptedBso"]],["impl Sync for OutgoingEncryptedBso",1,["sync15::bso::crypto::OutgoingEncryptedBso"]],["impl Sync for IncomingEnvelope",1,["sync15::bso::IncomingEnvelope"]],["impl Sync for OutgoingEnvelope",1,["sync15::bso::OutgoingEnvelope"]],["impl Sync for IncomingBso",1,["sync15::bso::IncomingBso"]],["impl Sync for OutgoingBso",1,["sync15::bso::OutgoingBso"]],["impl<T> Sync for IncomingContent<T>where\n T: Sync,",1,["sync15::bso::IncomingContent"]],["impl<T> Sync for IncomingKind<T>where\n T: Sync,",1,["sync15::bso::IncomingKind"]],["impl Sync for ServiceStatus",1,["sync15::client::status::ServiceStatus"]],["impl Sync for SyncResult",1,["sync15::client::status::SyncResult"]],["impl<T> Sync for Sync15ClientResponse<T>where\n T: Sync,",1,["sync15::client::storage_client::Sync15ClientResponse"]],["impl Sync for Sync15StorageClientInit",1,["sync15::client::storage_client::Sync15StorageClientInit"]],["impl !Sync for Sync15StorageClient",1,["sync15::client::storage_client::Sync15StorageClient"]],["impl !Sync for MemoryCachedState",1,["sync15::client::sync_multiple::MemoryCachedState"]],["impl<'a> Sync for SyncRequestInfo<'a>",1,["sync15::client::sync_multiple::SyncRequestInfo"]],["impl Sync for DeviceType",1,["sync15::device_type::DeviceType"]],["impl Sync for ClientData",1,["sync15::client_types::ClientData"]],["impl Sync for RemoteClient",1,["sync15::client_types::RemoteClient"]],["impl<'a> !Sync for Engine<'a>",1,["sync15::clients_engine::engine::Engine"]],["impl Sync for CommandStatus",1,["sync15::clients_engine::CommandStatus"]],["impl Sync for Settings",1,["sync15::clients_engine::Settings"]],["impl Sync for Command",1,["sync15::clients_engine::Command"]],["impl Sync for EncryptedPayload",1,["sync15::enc_payload::EncryptedPayload"]],["impl Sync for ApplyResults",1,["sync15::engine::bridged_engine::ApplyResults"]],["impl Sync for CollectionRequest",1,["sync15::engine::request::CollectionRequest"]],["impl Sync for RequestOrder",1,["sync15::engine::request::RequestOrder"]],["impl Sync for CollSyncIds",1,["sync15::engine::sync_engine::CollSyncIds"]],["impl Sync for EngineSyncAssociation",1,["sync15::engine::sync_engine::EngineSyncAssociation"]],["impl Sync for SyncEngineId",1,["sync15::engine::sync_engine::SyncEngineId"]],["impl Sync for Error",1,["sync15::error::Error"]],["impl Sync for KeyBundle",1,["sync15::key_bundle::KeyBundle"]],["impl Sync for ServerTimestamp",1,["sync15::server_timestamp::ServerTimestamp"]],["impl Sync for Event",1,["sync15::telemetry::Event"]],["impl Sync for SyncFailure",1,["sync15::telemetry::SyncFailure"]],["impl Sync for EngineIncoming",1,["sync15::telemetry::EngineIncoming"]],["impl Sync for EngineOutgoing",1,["sync15::telemetry::EngineOutgoing"]],["impl Sync for Engine",1,["sync15::telemetry::Engine"]],["impl Sync for Validation",1,["sync15::telemetry::Validation"]],["impl Sync for Problem",1,["sync15::telemetry::Problem"]],["impl Sync for SyncTelemetry",1,["sync15::telemetry::SyncTelemetry"]],["impl Sync for SyncTelemetryPing",1,["sync15::telemetry::SyncTelemetryPing"]]], +"sync_guid":[["impl Sync for Guid",1,["sync_guid::Guid"]]], +"sync_manager":[["impl Sync for SyncManagerError",1,["sync_manager::error::SyncManagerError"]],["impl Sync for SyncManager",1,["sync_manager::manager::SyncManager"]],["impl Sync for SyncParams",1,["sync_manager::types::SyncParams"]],["impl Sync for SyncReason",1,["sync_manager::types::SyncReason"]],["impl Sync for SyncEngineSelection",1,["sync_manager::types::SyncEngineSelection"]],["impl Sync for SyncAuthInfo",1,["sync_manager::types::SyncAuthInfo"]],["impl Sync for DeviceSettings",1,["sync_manager::types::DeviceSettings"]],["impl Sync for SyncResult",1,["sync_manager::types::SyncResult"]],["impl Sync for ServiceStatus",1,["sync_manager::types::ServiceStatus"]]], +"tabs":[["impl Sync for TabsApiError",1,["tabs::error::TabsApiError"]],["impl Sync for Error",1,["tabs::error::Error"]],["impl Sync for ClientRemoteTabs",1,["tabs::storage::ClientRemoteTabs"]],["impl Sync for TabsStore",1,["tabs::store::TabsStore"]],["impl Sync for TabsBridgedEngine",1,["tabs::sync::bridge::TabsBridgedEngine"]],["impl Sync for TabsEngine",1,["tabs::sync::engine::TabsEngine"]]], +"types":[["impl Sync for Timestamp",1,["types::Timestamp"]]], +"viaduct":[["impl Sync for HeaderName",1,["viaduct::headers::name::HeaderName"]],["impl Sync for InvalidHeaderName",1,["viaduct::headers::name::InvalidHeaderName"]],["impl Sync for Header",1,["viaduct::headers::Header"]],["impl Sync for Headers",1,["viaduct::headers::Headers"]],["impl Sync for Error",1,["viaduct::error::Error"]],["impl Sync for UnexpectedStatus",1,["viaduct::error::UnexpectedStatus"]],["impl Sync for Settings",1,["viaduct::settings::Settings"]],["impl Sync for Method",1,["viaduct::Method"]],["impl Sync for Request",1,["viaduct::Request"]],["impl Sync for Response",1,["viaduct::Response"]]], +"viaduct_reqwest":[["impl Sync for ReqwestBackend",1,["viaduct_reqwest::ReqwestBackend"]]], +"webext_storage":[["impl Sync for UsageInfo",1,["webext_storage::api::UsageInfo"]],["impl Sync for QuotaReason",1,["webext_storage::error::QuotaReason"]],["impl Sync for ErrorKind",1,["webext_storage::error::ErrorKind"]],["impl Sync for Error",1,["webext_storage::error::Error"]],["impl Sync for MigrationInfo",1,["webext_storage::migration::MigrationInfo"]],["impl Sync for Store",1,["webext_storage::store::Store"]]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/core/marker/trait.Unpin.js b/book/rust-docs/implementors/core/marker/trait.Unpin.js new file mode 100644 index 0000000000..822c3055a0 --- /dev/null +++ b/book/rust-docs/implementors/core/marker/trait.Unpin.js @@ -0,0 +1,32 @@ +(function() {var implementors = { +"as_ohttp_client":[["impl Unpin for OhttpError",1,["as_ohttp_client::OhttpError"]],["impl Unpin for OhttpSession",1,["as_ohttp_client::OhttpSession"]],["impl Unpin for OhttpResponse",1,["as_ohttp_client::OhttpResponse"]],["impl Unpin for OhttpTestServer",1,["as_ohttp_client::OhttpTestServer"]],["impl Unpin for TestServerRequest",1,["as_ohttp_client::TestServerRequest"]]], +"autofill":[["impl Unpin for UpdatableAddressFields",1,["autofill::db::models::address::UpdatableAddressFields"]],["impl Unpin for Address",1,["autofill::db::models::address::Address"]],["impl Unpin for InternalAddress",1,["autofill::db::models::address::InternalAddress"]],["impl Unpin for UpdatableCreditCardFields",1,["autofill::db::models::credit_card::UpdatableCreditCardFields"]],["impl Unpin for CreditCard",1,["autofill::db::models::credit_card::CreditCard"]],["impl Unpin for InternalCreditCard",1,["autofill::db::models::credit_card::InternalCreditCard"]],["impl Unpin for Metadata",1,["autofill::db::models::Metadata"]],["impl Unpin for AutofillConnectionInitializer",1,["autofill::db::schema::AutofillConnectionInitializer"]],["impl Unpin for Store",1,["autofill::db::store::Store"]],["impl Unpin for AutofillDb",1,["autofill::db::AutofillDb"]],["impl Unpin for AutofillApiError",1,["autofill::error::AutofillApiError"]],["impl Unpin for Error",1,["autofill::error::Error"]],["impl Unpin for AddressPayload",1,["autofill::sync::address::AddressPayload"]],["impl Unpin for EngineConfig",1,["autofill::sync::engine::EngineConfig"]],["impl<T> Unpin for ConfigSyncEngine<T>",1,["autofill::sync::engine::ConfigSyncEngine"]],["impl<T> Unpin for MergeResult<T>where\n T: Unpin,",1,["autofill::sync::MergeResult"]],["impl<T> Unpin for IncomingState<T>where\n T: Unpin,",1,["autofill::sync::IncomingState"]]], +"cli_support":[["impl Unpin for CliFxa",1,["cli_support::fxa_creds::CliFxa"]]], +"crashtest":[["impl Unpin for CrashTestError",1,["crashtest::CrashTestError"]]], +"error_support":[["impl Unpin for ErrorReporting",1,["error_support::handling::ErrorReporting"]],["impl<E> Unpin for ErrorHandling<E>where\n E: Unpin,",1,["error_support::handling::ErrorHandling"]]], +"examples_fxa_client":[["impl Unpin for DeviceArgs",1,["examples_fxa_client::devices::DeviceArgs"]],["impl Unpin for Command",1,["examples_fxa_client::devices::Command"]],["impl Unpin for SendTabArgs",1,["examples_fxa_client::send_tab::SendTabArgs"]],["impl Unpin for Command",1,["examples_fxa_client::send_tab::Command"]],["impl Unpin for Cli",1,["examples_fxa_client::Cli"]],["impl Unpin for Server",1,["examples_fxa_client::Server"]],["impl Unpin for Command",1,["examples_fxa_client::Command"]]], +"fxa_client":[["impl Unpin for AuthorizationInfo",1,["fxa_client::auth::AuthorizationInfo"]],["impl Unpin for MetricsParams",1,["fxa_client::auth::MetricsParams"]],["impl Unpin for FxaRustAuthState",1,["fxa_client::auth::FxaRustAuthState"]],["impl Unpin for LocalDevice",1,["fxa_client::device::LocalDevice"]],["impl Unpin for Device",1,["fxa_client::device::Device"]],["impl Unpin for DeviceCapability",1,["fxa_client::device::DeviceCapability"]],["impl Unpin for AttachedClient",1,["fxa_client::device::AttachedClient"]],["impl Unpin for FxaError",1,["fxa_client::error::FxaError"]],["impl Unpin for Error",1,["fxa_client::error::Error"]],["impl Unpin for Profile",1,["fxa_client::profile::Profile"]],["impl Unpin for DevicePushSubscription",1,["fxa_client::push::DevicePushSubscription"]],["impl Unpin for AccountEvent",1,["fxa_client::push::AccountEvent"]],["impl Unpin for IncomingDeviceCommand",1,["fxa_client::push::IncomingDeviceCommand"]],["impl Unpin for SendTabPayload",1,["fxa_client::push::SendTabPayload"]],["impl Unpin for TabHistoryEntry",1,["fxa_client::push::TabHistoryEntry"]],["impl Unpin for AccessTokenInfo",1,["fxa_client::token::AccessTokenInfo"]],["impl Unpin for ScopedKey",1,["fxa_client::token::ScopedKey"]],["impl Unpin for AuthorizationParameters",1,["fxa_client::token::AuthorizationParameters"]],["impl Unpin for FirefoxAccount",1,["fxa_client::FirefoxAccount"]],["impl Unpin for FxaConfig",1,["fxa_client::FxaConfig"]],["impl Unpin for FxaServer",1,["fxa_client::FxaServer"]]], +"interrupt_support":[["impl Unpin for Interrupted",1,["interrupt_support::error::Interrupted"]],["impl Unpin for NeverInterrupts",1,["interrupt_support::interruptee::NeverInterrupts"]],["impl Unpin for ShutdownInterruptee",1,["interrupt_support::shutdown::ShutdownInterruptee"]],["impl Unpin for SqlInterruptHandle",1,["interrupt_support::sql::SqlInterruptHandle"]],["impl Unpin for SqlInterruptScope",1,["interrupt_support::sql::SqlInterruptScope"]]], +"logins":[["impl Unpin for LoginsApiError",1,["logins::error::LoginsApiError"]],["impl Unpin for Error",1,["logins::error::Error"]],["impl Unpin for InvalidLogin",1,["logins::error::InvalidLogin"]],["impl Unpin for LoginFields",1,["logins::login::LoginFields"]],["impl Unpin for SecureLoginFields",1,["logins::login::SecureLoginFields"]],["impl Unpin for RecordFields",1,["logins::login::RecordFields"]],["impl Unpin for LoginEntry",1,["logins::login::LoginEntry"]],["impl Unpin for Login",1,["logins::login::Login"]],["impl Unpin for EncryptedLogin",1,["logins::login::EncryptedLogin"]],["impl Unpin for LoginDb",1,["logins::db::LoginDb"]],["impl Unpin for LoginStore",1,["logins::store::LoginStore"]],["impl Unpin for LoginsSyncEngine",1,["logins::sync::engine::LoginsSyncEngine"]]], +"nimbus":[["impl Unpin for EnrollmentStatus",1,["nimbus::enrollment::EnrollmentStatus"]],["impl Unpin for EnrolledFeature",1,["nimbus::enrollment::EnrolledFeature"]],["impl Unpin for NimbusTargetingHelper",1,["nimbus::targeting::NimbusTargetingHelper"]],["impl Unpin for NimbusError",1,["nimbus::error::NimbusError"]],["impl Unpin for BehaviorError",1,["nimbus::error::BehaviorError"]],["impl Unpin for EnrollmentStatusExtraDef",1,["nimbus::metrics::EnrollmentStatusExtraDef"]],["impl Unpin for EnrolledExperiment",1,["nimbus::schema::EnrolledExperiment"]],["impl Unpin for Experiment",1,["nimbus::schema::Experiment"]],["impl Unpin for FeatureConfig",1,["nimbus::schema::FeatureConfig"]],["impl Unpin for Branch",1,["nimbus::schema::Branch"]],["impl Unpin for BucketConfig",1,["nimbus::schema::BucketConfig"]],["impl Unpin for AvailableExperiment",1,["nimbus::schema::AvailableExperiment"]],["impl Unpin for ExperimentBranch",1,["nimbus::schema::ExperimentBranch"]],["impl Unpin for RandomizationUnit",1,["nimbus::schema::RandomizationUnit"]],["impl Unpin for AvailableRandomizationUnits",1,["nimbus::schema::AvailableRandomizationUnits"]],["impl Unpin for Version",1,["nimbus::versioning::Version"]],["impl Unpin for Interval",1,["nimbus::stateful::behavior::Interval"]],["impl Unpin for IntervalConfig",1,["nimbus::stateful::behavior::IntervalConfig"]],["impl Unpin for IntervalData",1,["nimbus::stateful::behavior::IntervalData"]],["impl Unpin for SingleIntervalCounter",1,["nimbus::stateful::behavior::SingleIntervalCounter"]],["impl Unpin for MultiIntervalCounter",1,["nimbus::stateful::behavior::MultiIntervalCounter"]],["impl Unpin for EventQueryType",1,["nimbus::stateful::behavior::EventQueryType"]],["impl Unpin for EventStore",1,["nimbus::stateful::behavior::EventStore"]],["impl Unpin for DatabaseCache",1,["nimbus::stateful::dbcache::DatabaseCache"]],["impl Unpin for TargetingAttributes",1,["nimbus::stateful::evaluator::TargetingAttributes"]],["impl Unpin for AppContext",1,["nimbus::stateful::matcher::AppContext"]],["impl Unpin for InternalMutableState",1,["nimbus::stateful::nimbus_client::InternalMutableState"]],["impl Unpin for NimbusClient",1,["nimbus::stateful::nimbus_client::NimbusClient"]],["impl Unpin for NimbusStringHelper",1,["nimbus::stateful::nimbus_client::NimbusStringHelper"]],["impl Unpin for StoreId",1,["nimbus::stateful::persistence::StoreId"]],["impl Unpin for SingleStore",1,["nimbus::stateful::persistence::SingleStore"]],["impl Unpin for Database",1,["nimbus::stateful::persistence::Database"]]], +"nimbus_cli":[["impl Unpin for Cli",1,["nimbus_cli::cli::Cli"]],["impl Unpin for CliCommand",1,["nimbus_cli::cli::CliCommand"]],["impl Unpin for ManifestArgs",1,["nimbus_cli::cli::ManifestArgs"]],["impl Unpin for OpenArgs",1,["nimbus_cli::cli::OpenArgs"]],["impl Unpin for ExperimentArgs",1,["nimbus_cli::cli::ExperimentArgs"]],["impl Unpin for ExperimentListArgs",1,["nimbus_cli::cli::ExperimentListArgs"]],["impl Unpin for ExperimentListSourceArgs",1,["nimbus_cli::cli::ExperimentListSourceArgs"]],["impl Unpin for ExperimentListFilterArgs",1,["nimbus_cli::cli::ExperimentListFilterArgs"]],["impl<'a> Unpin for ExperimentInfo<'a>",1,["nimbus_cli::output::info::ExperimentInfo"]],["impl<'a> Unpin for DateRange<'a>",1,["nimbus_cli::output::info::DateRange"]],["impl Unpin for StartAppPostPayload",1,["nimbus_cli::output::server::StartAppPostPayload"]],["impl Unpin for InMemoryDb",1,["nimbus_cli::output::server::InMemoryDb"]],["impl<'a> Unpin for StartAppProtocol<'a>",1,["nimbus_cli::protocol::StartAppProtocol"]],["impl Unpin for ExperimentSource",1,["nimbus_cli::sources::experiment::ExperimentSource"]],["impl Unpin for FeatureDefaults",1,["nimbus_cli::sources::experiment::FeatureDefaults"]],["impl Unpin for ExperimentListSource",1,["nimbus_cli::sources::experiment_list::ExperimentListSource"]],["impl Unpin for ExperimentListFilter",1,["nimbus_cli::sources::filter::ExperimentListFilter"]],["impl Unpin for ManifestSource",1,["nimbus_cli::sources::manifest::ManifestSource"]],["impl Unpin for Response",1,["nimbus_cli::updater::taskcluster::Response"]],["impl Unpin for TaskClusterRegistry",1,["nimbus_cli::updater::taskcluster::TaskClusterRegistry"]],["impl Unpin for ReqwestGunzippingHttpClient",1,["nimbus_cli::updater::taskcluster::ReqwestGunzippingHttpClient"]],["impl Unpin for LaunchableApp",1,["nimbus_cli::LaunchableApp"]],["impl Unpin for NimbusApp",1,["nimbus_cli::NimbusApp"]],["impl Unpin for AppCommand",1,["nimbus_cli::AppCommand"]],["impl Unpin for AppOpenArgs",1,["nimbus_cli::AppOpenArgs"]]], +"nimbus_fml":[["impl Unpin for ExperimenterFeature",1,["nimbus_fml::backends::experimenter_manifest::ExperimenterFeature"]],["impl Unpin for ExperimenterFeatureProperty",1,["nimbus_fml::backends::experimenter_manifest::ExperimenterFeatureProperty"]],["impl Unpin for ExperimentManifestPropType",1,["nimbus_fml::backends::experimenter_manifest::ExperimentManifestPropType"]],["impl Unpin for TextCodeType",1,["nimbus_fml::backends::kotlin::gen_structs::bundled::TextCodeType"]],["impl Unpin for ImageCodeType",1,["nimbus_fml::backends::kotlin::gen_structs::bundled::ImageCodeType"]],["impl Unpin for EnumCodeType",1,["nimbus_fml::backends::kotlin::gen_structs::enum_::EnumCodeType"]],["impl Unpin for EnumCodeDeclaration",1,["nimbus_fml::backends::kotlin::gen_structs::enum_::EnumCodeDeclaration"]],["impl Unpin for FeatureCodeDeclaration",1,["nimbus_fml::backends::kotlin::gen_structs::feature::FeatureCodeDeclaration"]],["impl<'a> Unpin for ImportedModuleInitialization<'a>",1,["nimbus_fml::backends::kotlin::gen_structs::imports::ImportedModuleInitialization"]],["impl Unpin for ObjectRuntime",1,["nimbus_fml::backends::kotlin::gen_structs::object::ObjectRuntime"]],["impl Unpin for ObjectCodeType",1,["nimbus_fml::backends::kotlin::gen_structs::object::ObjectCodeType"]],["impl Unpin for ObjectCodeDeclaration",1,["nimbus_fml::backends::kotlin::gen_structs::object::ObjectCodeDeclaration"]],["impl Unpin for BooleanCodeType",1,["nimbus_fml::backends::kotlin::gen_structs::primitives::BooleanCodeType"]],["impl Unpin for IntCodeType",1,["nimbus_fml::backends::kotlin::gen_structs::primitives::IntCodeType"]],["impl Unpin for StringCodeType",1,["nimbus_fml::backends::kotlin::gen_structs::primitives::StringCodeType"]],["impl Unpin for OptionalCodeType",1,["nimbus_fml::backends::kotlin::gen_structs::structural::OptionalCodeType"]],["impl Unpin for MapCodeType",1,["nimbus_fml::backends::kotlin::gen_structs::structural::MapCodeType"]],["impl Unpin for ListCodeType",1,["nimbus_fml::backends::kotlin::gen_structs::structural::ListCodeType"]],["impl<'a> Unpin for FeatureManifestDeclaration<'a>",1,["nimbus_fml::backends::kotlin::gen_structs::FeatureManifestDeclaration"]],["impl Unpin for ConcreteCodeOracle",1,["nimbus_fml::backends::kotlin::gen_structs::ConcreteCodeOracle"]],["impl Unpin for TextCodeType",1,["nimbus_fml::backends::swift::gen_structs::bundled::TextCodeType"]],["impl Unpin for ImageCodeType",1,["nimbus_fml::backends::swift::gen_structs::bundled::ImageCodeType"]],["impl Unpin for EnumCodeType",1,["nimbus_fml::backends::swift::gen_structs::enum_::EnumCodeType"]],["impl Unpin for EnumCodeDeclaration",1,["nimbus_fml::backends::swift::gen_structs::enum_::EnumCodeDeclaration"]],["impl Unpin for FeatureCodeDeclaration",1,["nimbus_fml::backends::swift::gen_structs::feature::FeatureCodeDeclaration"]],["impl<'a> Unpin for ImportedModuleInitialization<'a>",1,["nimbus_fml::backends::swift::gen_structs::imports::ImportedModuleInitialization"]],["impl Unpin for ObjectRuntime",1,["nimbus_fml::backends::swift::gen_structs::object::ObjectRuntime"]],["impl Unpin for ObjectCodeType",1,["nimbus_fml::backends::swift::gen_structs::object::ObjectCodeType"]],["impl Unpin for ObjectCodeDeclaration",1,["nimbus_fml::backends::swift::gen_structs::object::ObjectCodeDeclaration"]],["impl Unpin for BooleanCodeType",1,["nimbus_fml::backends::swift::gen_structs::primitives::BooleanCodeType"]],["impl Unpin for IntCodeType",1,["nimbus_fml::backends::swift::gen_structs::primitives::IntCodeType"]],["impl Unpin for StringCodeType",1,["nimbus_fml::backends::swift::gen_structs::primitives::StringCodeType"]],["impl Unpin for OptionalCodeType",1,["nimbus_fml::backends::swift::gen_structs::structural::OptionalCodeType"]],["impl Unpin for MapCodeType",1,["nimbus_fml::backends::swift::gen_structs::structural::MapCodeType"]],["impl Unpin for ListCodeType",1,["nimbus_fml::backends::swift::gen_structs::structural::ListCodeType"]],["impl<'a> Unpin for FeatureManifestDeclaration<'a>",1,["nimbus_fml::backends::swift::gen_structs::FeatureManifestDeclaration"]],["impl Unpin for ConcreteCodeOracle",1,["nimbus_fml::backends::swift::gen_structs::ConcreteCodeOracle"]],["impl Unpin for VariablesType",1,["nimbus_fml::backends::VariablesType"]],["impl Unpin for CliCmd",1,["nimbus_fml::command_line::commands::CliCmd"]],["impl Unpin for GenerateStructCmd",1,["nimbus_fml::command_line::commands::GenerateStructCmd"]],["impl Unpin for GenerateExperimenterManifestCmd",1,["nimbus_fml::command_line::commands::GenerateExperimenterManifestCmd"]],["impl Unpin for GenerateSingleFileManifestCmd",1,["nimbus_fml::command_line::commands::GenerateSingleFileManifestCmd"]],["impl Unpin for ValidateCmd",1,["nimbus_fml::command_line::commands::ValidateCmd"]],["impl Unpin for PrintChannelsCmd",1,["nimbus_fml::command_line::commands::PrintChannelsCmd"]],["impl<'object> Unpin for DefaultsMerger<'object>",1,["nimbus_fml::defaults_merger::DefaultsMerger"]],["impl Unpin for FMLError",1,["nimbus_fml::error::FMLError"]],["impl Unpin for ClientError",1,["nimbus_fml::error::ClientError"]],["impl Unpin for EnumVariantBody",1,["nimbus_fml::frontend::EnumVariantBody"]],["impl Unpin for EnumBody",1,["nimbus_fml::frontend::EnumBody"]],["impl Unpin for FeatureFieldBody",1,["nimbus_fml::frontend::FeatureFieldBody"]],["impl Unpin for FieldBody",1,["nimbus_fml::frontend::FieldBody"]],["impl Unpin for ObjectBody",1,["nimbus_fml::frontend::ObjectBody"]],["impl Unpin for Types",1,["nimbus_fml::frontend::Types"]],["impl Unpin for AboutBlock",1,["nimbus_fml::frontend::AboutBlock"]],["impl Unpin for SwiftAboutBlock",1,["nimbus_fml::frontend::SwiftAboutBlock"]],["impl Unpin for KotlinAboutBlock",1,["nimbus_fml::frontend::KotlinAboutBlock"]],["impl Unpin for ImportBlock",1,["nimbus_fml::frontend::ImportBlock"]],["impl Unpin for FeatureBody",1,["nimbus_fml::frontend::FeatureBody"]],["impl Unpin for FeatureMetadata",1,["nimbus_fml::frontend::FeatureMetadata"]],["impl Unpin for DocumentationLink",1,["nimbus_fml::frontend::DocumentationLink"]],["impl Unpin for ManifestFrontEnd",1,["nimbus_fml::frontend::ManifestFrontEnd"]],["impl Unpin for DefaultBlock",1,["nimbus_fml::frontend::DefaultBlock"]],["impl Unpin for TargetLanguage",1,["nimbus_fml::intermediate_representation::TargetLanguage"]],["impl Unpin for TypeRef",1,["nimbus_fml::intermediate_representation::TypeRef"]],["impl Unpin for ModuleId",1,["nimbus_fml::intermediate_representation::ModuleId"]],["impl Unpin for FeatureManifest",1,["nimbus_fml::intermediate_representation::FeatureManifest"]],["impl Unpin for FeatureDef",1,["nimbus_fml::intermediate_representation::FeatureDef"]],["impl Unpin for EnumDef",1,["nimbus_fml::intermediate_representation::EnumDef"]],["impl Unpin for FromStringDef",1,["nimbus_fml::intermediate_representation::FromStringDef"]],["impl Unpin for VariantDef",1,["nimbus_fml::intermediate_representation::VariantDef"]],["impl Unpin for ObjectDef",1,["nimbus_fml::intermediate_representation::ObjectDef"]],["impl Unpin for PropDef",1,["nimbus_fml::intermediate_representation::PropDef"]],["impl<'a> Unpin for ImportedModule<'a>",1,["nimbus_fml::intermediate_representation::ImportedModule"]],["impl Unpin for Parser",1,["nimbus_fml::parser::Parser"]],["impl Unpin for LoaderConfig",1,["nimbus_fml::util::loaders::LoaderConfig"]],["impl Unpin for FilePath",1,["nimbus_fml::util::loaders::FilePath"]],["impl Unpin for FileLoader",1,["nimbus_fml::util::loaders::FileLoader"]]], +"nss":[["impl Unpin for Operation",1,["nss::aes::Operation"]],["impl Unpin for Curve",1,["nss::ec::Curve"]],["impl Unpin for EcKey",1,["nss::ec::EcKey"]],["impl Unpin for PrivateKey",1,["nss::ec::PrivateKey"]],["impl Unpin for PublicKey",1,["nss::ec::PublicKey"]],["impl Unpin for ErrorKind",1,["nss::error::ErrorKind"]],["impl Unpin for Error",1,["nss::error::Error"]],["impl Unpin for HashAlgorithm",1,["nss::pk11::context::HashAlgorithm"]],["impl Unpin for SymKey",1,["nss::pk11::types::SymKey"]],["impl Unpin for PrivateKey",1,["nss::pk11::types::PrivateKey"]],["impl Unpin for PublicKey",1,["nss::pk11::types::PublicKey"]],["impl Unpin for GenericObject",1,["nss::pk11::types::GenericObject"]],["impl Unpin for Certificate",1,["nss::pk11::types::Certificate"]],["impl Unpin for Context",1,["nss::pk11::types::Context"]],["impl Unpin for Slot",1,["nss::pk11::types::Slot"]],["impl Unpin for AlgorithmID",1,["nss::pk11::types::AlgorithmID"]]], +"nss_build_common":[["impl Unpin for LinkingKind",1,["nss_build_common::LinkingKind"]],["impl Unpin for NoNssDir",1,["nss_build_common::NoNssDir"]]], +"nss_sys":[["impl Unpin for SECKEYPublicKeyStr",1,["nss_sys::bindings::keythi::SECKEYPublicKeyStr"]],["impl Unpin for SECKEYPublicKeyStr_u",1,["nss_sys::bindings::keythi::SECKEYPublicKeyStr_u"]],["impl Unpin for SECKEYPrivateKeyStr",1,["nss_sys::bindings::keythi::SECKEYPrivateKeyStr"]],["impl Unpin for KeyType",1,["nss_sys::bindings::keythi::KeyType"]],["impl Unpin for SECKEYRSAPublicKeyStr",1,["nss_sys::bindings::keythi::SECKEYRSAPublicKeyStr"]],["impl Unpin for SECKEYDSAPublicKeyStr",1,["nss_sys::bindings::keythi::SECKEYDSAPublicKeyStr"]],["impl Unpin for SECKEYPQGParamsStr",1,["nss_sys::bindings::keythi::SECKEYPQGParamsStr"]],["impl Unpin for SECKEYDHPublicKeyStr",1,["nss_sys::bindings::keythi::SECKEYDHPublicKeyStr"]],["impl Unpin for SECKEYKEAPublicKeyStr",1,["nss_sys::bindings::keythi::SECKEYKEAPublicKeyStr"]],["impl Unpin for SECKEYKEAParamsStr",1,["nss_sys::bindings::keythi::SECKEYKEAParamsStr"]],["impl Unpin for SECKEYFortezzaPublicKeyStr",1,["nss_sys::bindings::keythi::SECKEYFortezzaPublicKeyStr"]],["impl Unpin for SECKEYECPublicKeyStr",1,["nss_sys::bindings::keythi::SECKEYECPublicKeyStr"]],["impl Unpin for ECPointEncoding",1,["nss_sys::bindings::keythi::ECPointEncoding"]],["impl Unpin for CK_GCM_PARAMS_V3",1,["nss_sys::bindings::pkcs11n::CK_GCM_PARAMS_V3"]],["impl Unpin for CK_NSS_HKDFParams",1,["nss_sys::bindings::pkcs11n::CK_NSS_HKDFParams"]],["impl Unpin for CK_ATTRIBUTE",1,["nss_sys::bindings::pkcs11t::CK_ATTRIBUTE"]],["impl Unpin for PLArena",1,["nss_sys::bindings::plarena::PLArena"]],["impl Unpin for PLArenaPool",1,["nss_sys::bindings::plarena::PLArenaPool"]],["impl Unpin for SECItemType",1,["nss_sys::bindings::seccomon::SECItemType"]],["impl Unpin for SECItemStr",1,["nss_sys::bindings::seccomon::SECItemStr"]],["impl Unpin for _SECStatus",1,["nss_sys::bindings::seccomon::_SECStatus"]],["impl Unpin for PK11Origin",1,["nss_sys::bindings::secmodt::PK11Origin"]],["impl Unpin for PK11ObjectType",1,["nss_sys::bindings::secmodt::PK11ObjectType"]],["impl Unpin for SECAlgorithmIDStr",1,["nss_sys::bindings::secoidt::SECAlgorithmIDStr"]],["impl Unpin for SECOidDataStr",1,["nss_sys::bindings::secoidt::SECOidDataStr"]],["impl Unpin for SECSupportExtenTag",1,["nss_sys::bindings::secoidt::SECSupportExtenTag"]],["impl Unpin for SECOidTag",1,["nss_sys::bindings::secoidt::SECOidTag"]]], +"places":[["impl Unpin for AddablePlaceInfo",1,["places::api::history::AddablePlaceInfo"]],["impl Unpin for AddableVisit",1,["places::api::history::AddableVisit"]],["impl Unpin for RedirectSourceType",1,["places::api::history::RedirectSourceType"]],["impl Unpin for SearchParams",1,["places::api::matcher::SearchParams"]],["impl Unpin for SearchResult",1,["places::api::matcher::SearchResult"]],["impl Unpin for ConnectionType",1,["places::api::places_api::ConnectionType"]],["impl Unpin for SyncState",1,["places::api::places_api::SyncState"]],["impl Unpin for PlacesApi",1,["places::api::places_api::PlacesApi"]],["impl Unpin for PlacesApiError",1,["places::error::PlacesApiError"]],["impl Unpin for Error",1,["places::error::Error"]],["impl Unpin for InvalidPlaceInfo",1,["places::error::InvalidPlaceInfo"]],["impl Unpin for Corruption",1,["places::error::Corruption"]],["impl Unpin for InvalidMetadataObservation",1,["places::error::InvalidMetadataObservation"]],["impl Unpin for VisitTransitionSet",1,["places::types::visit_transition_set::VisitTransitionSet"]],["impl Unpin for InvalidVisitType",1,["places::types::InvalidVisitType"]],["impl Unpin for VisitType",1,["places::types::VisitType"]],["impl Unpin for BookmarkType",1,["places::types::BookmarkType"]],["impl Unpin for SyncStatus",1,["places::types::SyncStatus"]],["impl Unpin for BookmarksSyncEngine",1,["places::bookmark_sync::engine::BookmarksSyncEngine"]],["impl Unpin for BookmarkRecordId",1,["places::bookmark_sync::record::BookmarkRecordId"]],["impl Unpin for BookmarkRecord",1,["places::bookmark_sync::record::BookmarkRecord"]],["impl Unpin for QueryRecord",1,["places::bookmark_sync::record::QueryRecord"]],["impl Unpin for FolderRecord",1,["places::bookmark_sync::record::FolderRecord"]],["impl Unpin for LivemarkRecord",1,["places::bookmark_sync::record::LivemarkRecord"]],["impl Unpin for SeparatorRecord",1,["places::bookmark_sync::record::SeparatorRecord"]],["impl Unpin for BookmarkItemRecord",1,["places::bookmark_sync::record::BookmarkItemRecord"]],["impl Unpin for SyncedBookmarkKind",1,["places::bookmark_sync::SyncedBookmarkKind"]],["impl Unpin for SyncedBookmarkValidity",1,["places::bookmark_sync::SyncedBookmarkValidity"]],["impl Unpin for GLOBAL_BOOKMARK_CHANGE_COUNTERS",1,["places::db::db::GLOBAL_BOOKMARK_CHANGE_COUNTERS"]],["impl Unpin for PlacesInitializer",1,["places::db::db::PlacesInitializer"]],["impl Unpin for PlacesDb",1,["places::db::db::PlacesDb"]],["impl Unpin for SharedPlacesDb",1,["places::db::db::SharedPlacesDb"]],["impl Unpin for GlobalChangeCounterTracker",1,["places::db::db::GlobalChangeCounterTracker"]],["impl<'conn> Unpin for PlacesTransaction<'conn>",1,["places::db::tx::PlacesTransaction"]],["impl Unpin for PlacesConnection",1,["places::ffi::PlacesConnection"]],["impl Unpin for HistoryVisitInfo",1,["places::ffi::HistoryVisitInfo"]],["impl Unpin for HistoryVisitInfosWithBound",1,["places::ffi::HistoryVisitInfosWithBound"]],["impl Unpin for TopFrecentSiteInfo",1,["places::ffi::TopFrecentSiteInfo"]],["impl Unpin for FrecencyThresholdOption",1,["places::ffi::FrecencyThresholdOption"]],["impl Unpin for SearchResult",1,["places::ffi::SearchResult"]],["impl Unpin for Dummy",1,["places::ffi::Dummy"]],["impl Unpin for FrecencySettings",1,["places::frecency::FrecencySettings"]],["impl Unpin for PrefixMode",1,["places::hash::PrefixMode"]],["impl Unpin for HistorySyncEngine",1,["places::history_sync::engine::HistorySyncEngine"]],["impl Unpin for HistoryRecordVisit",1,["places::history_sync::record::HistoryRecordVisit"]],["impl Unpin for HistoryRecord",1,["places::history_sync::record::HistoryRecord"]],["impl Unpin for ServerVisitTimestamp",1,["places::history_sync::ServerVisitTimestamp"]],["impl Unpin for NOW",1,["places::import::common::NOW"]],["impl<'a> Unpin for ExecuteOnDrop<'a>",1,["places::import::common::ExecuteOnDrop"]],["impl Unpin for HistoryMigrationResult",1,["places::import::common::HistoryMigrationResult"]],["impl Unpin for MatchBehavior",1,["places::match_impl::MatchBehavior"]],["impl Unpin for SearchBehavior",1,["places::match_impl::SearchBehavior"]],["impl<'search, 'url, 'title, 'tags> Unpin for AutocompleteMatch<'search, 'url, 'title, 'tags>",1,["places::match_impl::AutocompleteMatch"]],["impl Unpin for VisitObservation",1,["places::observation::VisitObservation"]],["impl Unpin for BookmarkData",1,["places::storage::bookmarks::fetch::BookmarkData"]],["impl Unpin for Separator",1,["places::storage::bookmarks::fetch::Separator"]],["impl Unpin for Folder",1,["places::storage::bookmarks::fetch::Folder"]],["impl Unpin for Item",1,["places::storage::bookmarks::fetch::Item"]],["impl Unpin for SEARCH_QUERY",1,["places::storage::bookmarks::fetch::SEARCH_QUERY"]],["impl Unpin for RECENT_BOOKMARKS_QUERY",1,["places::storage::bookmarks::fetch::RECENT_BOOKMARKS_QUERY"]],["impl Unpin for BookmarkNode",1,["places::storage::bookmarks::json_tree::BookmarkNode"]],["impl Unpin for SeparatorNode",1,["places::storage::bookmarks::json_tree::SeparatorNode"]],["impl Unpin for FolderNode",1,["places::storage::bookmarks::json_tree::FolderNode"]],["impl Unpin for BookmarkTreeNode",1,["places::storage::bookmarks::json_tree::BookmarkTreeNode"]],["impl Unpin for FetchDepth",1,["places::storage::bookmarks::json_tree::FetchDepth"]],["impl Unpin for BookmarkRootGuid",1,["places::storage::bookmarks::root_guid::BookmarkRootGuid"]],["impl Unpin for BookmarkPosition",1,["places::storage::bookmarks::BookmarkPosition"]],["impl Unpin for InsertableBookmark",1,["places::storage::bookmarks::InsertableBookmark"]],["impl Unpin for InsertableSeparator",1,["places::storage::bookmarks::InsertableSeparator"]],["impl Unpin for InsertableFolder",1,["places::storage::bookmarks::InsertableFolder"]],["impl Unpin for InsertableItem",1,["places::storage::bookmarks::InsertableItem"]],["impl Unpin for UpdateTreeLocation",1,["places::storage::bookmarks::UpdateTreeLocation"]],["impl Unpin for UpdatableBookmark",1,["places::storage::bookmarks::UpdatableBookmark"]],["impl Unpin for UpdatableSeparator",1,["places::storage::bookmarks::UpdatableSeparator"]],["impl Unpin for UpdatableFolder",1,["places::storage::bookmarks::UpdatableFolder"]],["impl Unpin for UpdatableItem",1,["places::storage::bookmarks::UpdatableItem"]],["impl Unpin for BookmarkUpdateInfo",1,["places::storage::bookmarks::BookmarkUpdateInfo"]],["impl Unpin for FetchedVisit",1,["places::storage::history::history_sync::FetchedVisit"]],["impl Unpin for FetchedVisitPage",1,["places::storage::history::history_sync::FetchedVisitPage"]],["impl Unpin for DocumentType",1,["places::storage::history_metadata::DocumentType"]],["impl Unpin for HistoryHighlightWeights",1,["places::storage::history_metadata::HistoryHighlightWeights"]],["impl Unpin for HistoryHighlight",1,["places::storage::history_metadata::HistoryHighlight"]],["impl Unpin for HistoryMetadataObservation",1,["places::storage::history_metadata::HistoryMetadataObservation"]],["impl Unpin for HistoryMetadata",1,["places::storage::history_metadata::HistoryMetadata"]],["impl<'a> Unpin for ValidatedTag<'a>",1,["places::storage::tags::ValidatedTag"]],["impl Unpin for RowId",1,["places::storage::RowId"]],["impl Unpin for PageInfo",1,["places::storage::PageInfo"]],["impl Unpin for FetchedPageInfo",1,["places::storage::FetchedPageInfo"]],["impl Unpin for RunMaintenanceMetrics",1,["places::storage::RunMaintenanceMetrics"]]], +"protobuf_gen":[["impl Unpin for ProtobufOpts",1,["protobuf_gen::ProtobufOpts"]]], +"push":[["impl Unpin for BridgeType",1,["push::internal::config::BridgeType"]],["impl Unpin for PushConfiguration",1,["push::internal::config::PushConfiguration"]],["impl Unpin for Protocol",1,["push::internal::config::Protocol"]],["impl Unpin for PushApiError",1,["push::error::PushApiError"]],["impl Unpin for PushError",1,["push::error::PushError"]],["impl Unpin for PushManager",1,["push::PushManager"]],["impl Unpin for KeyInfo",1,["push::KeyInfo"]],["impl Unpin for SubscriptionInfo",1,["push::SubscriptionInfo"]],["impl Unpin for SubscriptionResponse",1,["push::SubscriptionResponse"]],["impl Unpin for PushSubscriptionChanged",1,["push::PushSubscriptionChanged"]]], +"rc_crypto":[["impl<'a> Unpin for Aad<'a>",1,["rc_crypto::aead::Aad"]],["impl Unpin for Nonce",1,["rc_crypto::aead::Nonce"]],["impl Unpin for OpeningKey",1,["rc_crypto::aead::OpeningKey"]],["impl Unpin for SealingKey",1,["rc_crypto::aead::SealingKey"]],["impl Unpin for Algorithm",1,["rc_crypto::aead::Algorithm"]],["impl Unpin for Algorithm",1,["rc_crypto::agreement::Algorithm"]],["impl Unpin for Ephemeral",1,["rc_crypto::agreement::Ephemeral"]],["impl Unpin for Static",1,["rc_crypto::agreement::Static"]],["impl<U> Unpin for KeyPair<U>where\n U: Unpin,",1,["rc_crypto::agreement::KeyPair"]],["impl Unpin for PublicKey",1,["rc_crypto::agreement::PublicKey"]],["impl<'a> Unpin for UnparsedPublicKey<'a>",1,["rc_crypto::agreement::UnparsedPublicKey"]],["impl<U> Unpin for PrivateKey<U>where\n U: Unpin,",1,["rc_crypto::agreement::PrivateKey"]],["impl Unpin for InputKeyMaterial",1,["rc_crypto::agreement::InputKeyMaterial"]],["impl Unpin for Digest",1,["rc_crypto::digest::Digest"]],["impl Unpin for RcCryptoLocalKeyPair",1,["rc_crypto::ece_crypto::RcCryptoLocalKeyPair"]],["impl Unpin for RcCryptoRemotePublicKey",1,["rc_crypto::ece_crypto::RcCryptoRemotePublicKey"]],["impl Unpin for ErrorKind",1,["rc_crypto::error::ErrorKind"]],["impl Unpin for Error",1,["rc_crypto::error::Error"]],["impl Unpin for Signature",1,["rc_crypto::hmac::Signature"]],["impl Unpin for SigningKey",1,["rc_crypto::hmac::SigningKey"]],["impl Unpin for VerificationKey",1,["rc_crypto::hmac::VerificationKey"]],["impl Unpin for VerificationAlgorithm",1,["rc_crypto::signature::VerificationAlgorithm"]],["impl<'a> Unpin for UnparsedPublicKey<'a>",1,["rc_crypto::signature::UnparsedPublicKey"]]], +"rc_log_ffi":[["impl Unpin for LogAdapterState",1,["rc_log_ffi::android::LogAdapterState"]],["impl Unpin for LogSink",1,["rc_log_ffi::android::LogSink"]],["impl Unpin for LogLevel",1,["rc_log_ffi::LogLevel"]]], +"remote_settings":[["impl Unpin for RemoteSettingsError",1,["remote_settings::error::RemoteSettingsError"]],["impl Unpin for Client",1,["remote_settings::client::Client"]],["impl Unpin for RemoteSettingsResponse",1,["remote_settings::client::RemoteSettingsResponse"]],["impl Unpin for RemoteSettingsRecord",1,["remote_settings::client::RemoteSettingsRecord"]],["impl Unpin for Attachment",1,["remote_settings::client::Attachment"]],["impl Unpin for GetItemsOptions",1,["remote_settings::client::GetItemsOptions"]],["impl Unpin for SortOrder",1,["remote_settings::client::SortOrder"]],["impl Unpin for RemoteSettingsConfig",1,["remote_settings::config::RemoteSettingsConfig"]],["impl Unpin for RemoteSettings",1,["remote_settings::RemoteSettings"]]], +"sql_support":[["impl Unpin for Conn",1,["sql_support::conn_ext::Conn"]],["impl<'conn> Unpin for UncheckedTransaction<'conn>",1,["sql_support::conn_ext::UncheckedTransaction"]],["impl<'conn> Unpin for MaybeCached<'conn>",1,["sql_support::maybe_cached::MaybeCached"]],["impl<CI> Unpin for MigratedDatabaseFile<CI>where\n CI: Unpin,",1,["sql_support::open_database::test_utils::MigratedDatabaseFile"]],["impl Unpin for Error",1,["sql_support::open_database::Error"]],["impl<'a, F> Unpin for RepeatDisplay<'a, F>where\n F: Unpin,",1,["sql_support::repeat::RepeatDisplay"]]], +"sync15":[["impl Unpin for IncomingEncryptedBso",1,["sync15::bso::crypto::IncomingEncryptedBso"]],["impl Unpin for OutgoingEncryptedBso",1,["sync15::bso::crypto::OutgoingEncryptedBso"]],["impl Unpin for IncomingEnvelope",1,["sync15::bso::IncomingEnvelope"]],["impl Unpin for OutgoingEnvelope",1,["sync15::bso::OutgoingEnvelope"]],["impl Unpin for IncomingBso",1,["sync15::bso::IncomingBso"]],["impl Unpin for OutgoingBso",1,["sync15::bso::OutgoingBso"]],["impl<T> Unpin for IncomingContent<T>where\n T: Unpin,",1,["sync15::bso::IncomingContent"]],["impl<T> Unpin for IncomingKind<T>where\n T: Unpin,",1,["sync15::bso::IncomingKind"]],["impl Unpin for ServiceStatus",1,["sync15::client::status::ServiceStatus"]],["impl Unpin for SyncResult",1,["sync15::client::status::SyncResult"]],["impl<T> Unpin for Sync15ClientResponse<T>where\n T: Unpin,",1,["sync15::client::storage_client::Sync15ClientResponse"]],["impl Unpin for Sync15StorageClientInit",1,["sync15::client::storage_client::Sync15StorageClientInit"]],["impl Unpin for Sync15StorageClient",1,["sync15::client::storage_client::Sync15StorageClient"]],["impl Unpin for MemoryCachedState",1,["sync15::client::sync_multiple::MemoryCachedState"]],["impl<'a> Unpin for SyncRequestInfo<'a>",1,["sync15::client::sync_multiple::SyncRequestInfo"]],["impl Unpin for DeviceType",1,["sync15::device_type::DeviceType"]],["impl Unpin for ClientData",1,["sync15::client_types::ClientData"]],["impl Unpin for RemoteClient",1,["sync15::client_types::RemoteClient"]],["impl<'a> Unpin for Engine<'a>",1,["sync15::clients_engine::engine::Engine"]],["impl Unpin for CommandStatus",1,["sync15::clients_engine::CommandStatus"]],["impl Unpin for Settings",1,["sync15::clients_engine::Settings"]],["impl Unpin for Command",1,["sync15::clients_engine::Command"]],["impl Unpin for EncryptedPayload",1,["sync15::enc_payload::EncryptedPayload"]],["impl Unpin for ApplyResults",1,["sync15::engine::bridged_engine::ApplyResults"]],["impl Unpin for CollectionRequest",1,["sync15::engine::request::CollectionRequest"]],["impl Unpin for RequestOrder",1,["sync15::engine::request::RequestOrder"]],["impl Unpin for CollSyncIds",1,["sync15::engine::sync_engine::CollSyncIds"]],["impl Unpin for EngineSyncAssociation",1,["sync15::engine::sync_engine::EngineSyncAssociation"]],["impl Unpin for SyncEngineId",1,["sync15::engine::sync_engine::SyncEngineId"]],["impl Unpin for Error",1,["sync15::error::Error"]],["impl Unpin for KeyBundle",1,["sync15::key_bundle::KeyBundle"]],["impl Unpin for ServerTimestamp",1,["sync15::server_timestamp::ServerTimestamp"]],["impl Unpin for Event",1,["sync15::telemetry::Event"]],["impl Unpin for SyncFailure",1,["sync15::telemetry::SyncFailure"]],["impl Unpin for EngineIncoming",1,["sync15::telemetry::EngineIncoming"]],["impl Unpin for EngineOutgoing",1,["sync15::telemetry::EngineOutgoing"]],["impl Unpin for Engine",1,["sync15::telemetry::Engine"]],["impl Unpin for Validation",1,["sync15::telemetry::Validation"]],["impl Unpin for Problem",1,["sync15::telemetry::Problem"]],["impl Unpin for SyncTelemetry",1,["sync15::telemetry::SyncTelemetry"]],["impl Unpin for SyncTelemetryPing",1,["sync15::telemetry::SyncTelemetryPing"]]], +"sync_guid":[["impl Unpin for Guid",1,["sync_guid::Guid"]]], +"sync_manager":[["impl Unpin for SyncManagerError",1,["sync_manager::error::SyncManagerError"]],["impl Unpin for SyncManager",1,["sync_manager::manager::SyncManager"]],["impl Unpin for SyncParams",1,["sync_manager::types::SyncParams"]],["impl Unpin for SyncReason",1,["sync_manager::types::SyncReason"]],["impl Unpin for SyncEngineSelection",1,["sync_manager::types::SyncEngineSelection"]],["impl Unpin for SyncAuthInfo",1,["sync_manager::types::SyncAuthInfo"]],["impl Unpin for DeviceSettings",1,["sync_manager::types::DeviceSettings"]],["impl Unpin for SyncResult",1,["sync_manager::types::SyncResult"]],["impl Unpin for ServiceStatus",1,["sync_manager::types::ServiceStatus"]]], +"tabs":[["impl Unpin for TabsApiError",1,["tabs::error::TabsApiError"]],["impl Unpin for Error",1,["tabs::error::Error"]],["impl Unpin for ClientRemoteTabs",1,["tabs::storage::ClientRemoteTabs"]],["impl Unpin for TabsStore",1,["tabs::store::TabsStore"]],["impl Unpin for TabsBridgedEngine",1,["tabs::sync::bridge::TabsBridgedEngine"]],["impl Unpin for TabsEngine",1,["tabs::sync::engine::TabsEngine"]]], +"types":[["impl Unpin for Timestamp",1,["types::Timestamp"]]], +"viaduct":[["impl Unpin for HeaderName",1,["viaduct::headers::name::HeaderName"]],["impl Unpin for InvalidHeaderName",1,["viaduct::headers::name::InvalidHeaderName"]],["impl Unpin for Header",1,["viaduct::headers::Header"]],["impl Unpin for Headers",1,["viaduct::headers::Headers"]],["impl Unpin for Error",1,["viaduct::error::Error"]],["impl Unpin for UnexpectedStatus",1,["viaduct::error::UnexpectedStatus"]],["impl Unpin for Settings",1,["viaduct::settings::Settings"]],["impl Unpin for Method",1,["viaduct::Method"]],["impl Unpin for Request",1,["viaduct::Request"]],["impl Unpin for Response",1,["viaduct::Response"]]], +"viaduct_reqwest":[["impl Unpin for ReqwestBackend",1,["viaduct_reqwest::ReqwestBackend"]]], +"webext_storage":[["impl Unpin for UsageInfo",1,["webext_storage::api::UsageInfo"]],["impl Unpin for QuotaReason",1,["webext_storage::error::QuotaReason"]],["impl Unpin for ErrorKind",1,["webext_storage::error::ErrorKind"]],["impl Unpin for Error",1,["webext_storage::error::Error"]],["impl Unpin for MigrationInfo",1,["webext_storage::migration::MigrationInfo"]],["impl Unpin for Store",1,["webext_storage::store::Store"]]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/core/ops/arith/trait.Sub.js b/book/rust-docs/implementors/core/ops/arith/trait.Sub.js new file mode 100644 index 0000000000..e1539e47bf --- /dev/null +++ b/book/rust-docs/implementors/core/ops/arith/trait.Sub.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"places":[["impl Sub<SearchBehavior> for SearchBehavior"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/core/ops/arith/trait.SubAssign.js b/book/rust-docs/implementors/core/ops/arith/trait.SubAssign.js new file mode 100644 index 0000000000..c5feb180b1 --- /dev/null +++ b/book/rust-docs/implementors/core/ops/arith/trait.SubAssign.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"places":[["impl SubAssign<SearchBehavior> for SearchBehavior"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/core/ops/bit/trait.BitAnd.js b/book/rust-docs/implementors/core/ops/bit/trait.BitAnd.js new file mode 100644 index 0000000000..419d8613b7 --- /dev/null +++ b/book/rust-docs/implementors/core/ops/bit/trait.BitAnd.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"places":[["impl BitAnd<SearchBehavior> for SearchBehavior"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/core/ops/bit/trait.BitAndAssign.js b/book/rust-docs/implementors/core/ops/bit/trait.BitAndAssign.js new file mode 100644 index 0000000000..71d55e004b --- /dev/null +++ b/book/rust-docs/implementors/core/ops/bit/trait.BitAndAssign.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"places":[["impl BitAndAssign<SearchBehavior> for SearchBehavior"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/core/ops/bit/trait.BitOr.js b/book/rust-docs/implementors/core/ops/bit/trait.BitOr.js new file mode 100644 index 0000000000..7619e5f382 --- /dev/null +++ b/book/rust-docs/implementors/core/ops/bit/trait.BitOr.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"places":[["impl BitOr<SearchBehavior> for SearchBehavior"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/core/ops/bit/trait.BitOrAssign.js b/book/rust-docs/implementors/core/ops/bit/trait.BitOrAssign.js new file mode 100644 index 0000000000..35be497f97 --- /dev/null +++ b/book/rust-docs/implementors/core/ops/bit/trait.BitOrAssign.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"places":[["impl BitOrAssign<SearchBehavior> for SearchBehavior"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/core/ops/bit/trait.BitXor.js b/book/rust-docs/implementors/core/ops/bit/trait.BitXor.js new file mode 100644 index 0000000000..784ce38b32 --- /dev/null +++ b/book/rust-docs/implementors/core/ops/bit/trait.BitXor.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"places":[["impl BitXor<SearchBehavior> for SearchBehavior"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/core/ops/bit/trait.BitXorAssign.js b/book/rust-docs/implementors/core/ops/bit/trait.BitXorAssign.js new file mode 100644 index 0000000000..c902438a7c --- /dev/null +++ b/book/rust-docs/implementors/core/ops/bit/trait.BitXorAssign.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"places":[["impl BitXorAssign<SearchBehavior> for SearchBehavior"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/core/ops/bit/trait.Not.js b/book/rust-docs/implementors/core/ops/bit/trait.Not.js new file mode 100644 index 0000000000..ddd9b4f470 --- /dev/null +++ b/book/rust-docs/implementors/core/ops/bit/trait.Not.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"places":[["impl Not for SearchBehavior"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/core/ops/deref/trait.Deref.js b/book/rust-docs/implementors/core/ops/deref/trait.Deref.js new file mode 100644 index 0000000000..d0e482722d --- /dev/null +++ b/book/rust-docs/implementors/core/ops/deref/trait.Deref.js @@ -0,0 +1,9 @@ +(function() {var implementors = { +"autofill":[["impl Deref for AutofillDb"]], +"logins":[["impl Deref for LoginDb"]], +"nss":[["impl Deref for PrivateKey"],["impl Deref for PublicKey"]], +"places":[["impl Deref for PlacesDb"],["impl Deref for NOW"],["impl<'conn> Deref for PlacesTransaction<'conn>"],["impl Deref for SEARCH_QUERY"],["impl Deref for SharedPlacesDb"],["impl Deref for GLOBAL_BOOKMARK_CHANGE_COUNTERS"],["impl Deref for RECENT_BOOKMARKS_QUERY"]], +"sql_support":[["impl<'conn> Deref for MaybeCached<'conn>"],["impl<'conn> Deref for UncheckedTransaction<'conn>"]], +"sync_guid":[["impl Deref for Guid"]], +"viaduct":[["impl Deref for HeaderName"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/core/ops/deref/trait.DerefMut.js b/book/rust-docs/implementors/core/ops/deref/trait.DerefMut.js new file mode 100644 index 0000000000..1e113d4ed6 --- /dev/null +++ b/book/rust-docs/implementors/core/ops/deref/trait.DerefMut.js @@ -0,0 +1,4 @@ +(function() {var implementors = { +"autofill":[["impl DerefMut for AutofillDb"]], +"sql_support":[["impl<'conn> DerefMut for MaybeCached<'conn>"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/core/ops/drop/trait.Drop.js b/book/rust-docs/implementors/core/ops/drop/trait.Drop.js new file mode 100644 index 0000000000..1daf314f2b --- /dev/null +++ b/book/rust-docs/implementors/core/ops/drop/trait.Drop.js @@ -0,0 +1,7 @@ +(function() {var implementors = { +"nimbus_fml":[["impl Drop for FileLoader"]], +"nss":[["impl Drop for Context"],["impl Drop for GenericObject"],["impl Drop for PublicKey"],["impl Drop for PrivateKey"],["impl Drop for SymKey"],["impl Drop for AlgorithmID"],["impl Drop for Slot"],["impl Drop for Certificate"]], +"places":[["impl Drop for ExecuteOnDrop<'_>"],["impl Drop for PlacesDb"]], +"rc_log_ffi":[["impl Drop for LogAdapterState"]], +"sql_support":[["impl<'conn> Drop for UncheckedTransaction<'conn>"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/core/panic/unwind_safe/trait.RefUnwindSafe.js b/book/rust-docs/implementors/core/panic/unwind_safe/trait.RefUnwindSafe.js new file mode 100644 index 0000000000..0d1aede1d6 --- /dev/null +++ b/book/rust-docs/implementors/core/panic/unwind_safe/trait.RefUnwindSafe.js @@ -0,0 +1,32 @@ +(function() {var implementors = { +"as_ohttp_client":[["impl RefUnwindSafe for OhttpError",1,["as_ohttp_client::OhttpError"]],["impl !RefUnwindSafe for OhttpSession",1,["as_ohttp_client::OhttpSession"]],["impl RefUnwindSafe for OhttpResponse",1,["as_ohttp_client::OhttpResponse"]],["impl !RefUnwindSafe for OhttpTestServer",1,["as_ohttp_client::OhttpTestServer"]],["impl RefUnwindSafe for TestServerRequest",1,["as_ohttp_client::TestServerRequest"]]], +"autofill":[["impl RefUnwindSafe for UpdatableAddressFields",1,["autofill::db::models::address::UpdatableAddressFields"]],["impl RefUnwindSafe for Address",1,["autofill::db::models::address::Address"]],["impl RefUnwindSafe for InternalAddress",1,["autofill::db::models::address::InternalAddress"]],["impl RefUnwindSafe for UpdatableCreditCardFields",1,["autofill::db::models::credit_card::UpdatableCreditCardFields"]],["impl RefUnwindSafe for CreditCard",1,["autofill::db::models::credit_card::CreditCard"]],["impl RefUnwindSafe for InternalCreditCard",1,["autofill::db::models::credit_card::InternalCreditCard"]],["impl RefUnwindSafe for Metadata",1,["autofill::db::models::Metadata"]],["impl RefUnwindSafe for AutofillConnectionInitializer",1,["autofill::db::schema::AutofillConnectionInitializer"]],["impl RefUnwindSafe for Store",1,["autofill::db::store::Store"]],["impl !RefUnwindSafe for AutofillDb",1,["autofill::db::AutofillDb"]],["impl RefUnwindSafe for AutofillApiError",1,["autofill::error::AutofillApiError"]],["impl !RefUnwindSafe for Error",1,["autofill::error::Error"]],["impl RefUnwindSafe for AddressPayload",1,["autofill::sync::address::AddressPayload"]],["impl RefUnwindSafe for EngineConfig",1,["autofill::sync::engine::EngineConfig"]],["impl<T> !RefUnwindSafe for ConfigSyncEngine<T>",1,["autofill::sync::engine::ConfigSyncEngine"]],["impl<T> RefUnwindSafe for MergeResult<T>where\n T: RefUnwindSafe,",1,["autofill::sync::MergeResult"]],["impl<T> RefUnwindSafe for IncomingState<T>where\n T: RefUnwindSafe,",1,["autofill::sync::IncomingState"]]], +"cli_support":[["impl !RefUnwindSafe for CliFxa",1,["cli_support::fxa_creds::CliFxa"]]], +"crashtest":[["impl RefUnwindSafe for CrashTestError",1,["crashtest::CrashTestError"]]], +"error_support":[["impl RefUnwindSafe for ErrorReporting",1,["error_support::handling::ErrorReporting"]],["impl<E> RefUnwindSafe for ErrorHandling<E>where\n E: RefUnwindSafe,",1,["error_support::handling::ErrorHandling"]]], +"examples_fxa_client":[["impl RefUnwindSafe for DeviceArgs",1,["examples_fxa_client::devices::DeviceArgs"]],["impl RefUnwindSafe for Command",1,["examples_fxa_client::devices::Command"]],["impl RefUnwindSafe for SendTabArgs",1,["examples_fxa_client::send_tab::SendTabArgs"]],["impl RefUnwindSafe for Command",1,["examples_fxa_client::send_tab::Command"]],["impl RefUnwindSafe for Cli",1,["examples_fxa_client::Cli"]],["impl RefUnwindSafe for Server",1,["examples_fxa_client::Server"]],["impl RefUnwindSafe for Command",1,["examples_fxa_client::Command"]]], +"fxa_client":[["impl RefUnwindSafe for AuthorizationInfo",1,["fxa_client::auth::AuthorizationInfo"]],["impl RefUnwindSafe for MetricsParams",1,["fxa_client::auth::MetricsParams"]],["impl RefUnwindSafe for FxaRustAuthState",1,["fxa_client::auth::FxaRustAuthState"]],["impl RefUnwindSafe for LocalDevice",1,["fxa_client::device::LocalDevice"]],["impl RefUnwindSafe for Device",1,["fxa_client::device::Device"]],["impl RefUnwindSafe for DeviceCapability",1,["fxa_client::device::DeviceCapability"]],["impl RefUnwindSafe for AttachedClient",1,["fxa_client::device::AttachedClient"]],["impl RefUnwindSafe for FxaError",1,["fxa_client::error::FxaError"]],["impl !RefUnwindSafe for Error",1,["fxa_client::error::Error"]],["impl RefUnwindSafe for Profile",1,["fxa_client::profile::Profile"]],["impl RefUnwindSafe for DevicePushSubscription",1,["fxa_client::push::DevicePushSubscription"]],["impl RefUnwindSafe for AccountEvent",1,["fxa_client::push::AccountEvent"]],["impl RefUnwindSafe for IncomingDeviceCommand",1,["fxa_client::push::IncomingDeviceCommand"]],["impl RefUnwindSafe for SendTabPayload",1,["fxa_client::push::SendTabPayload"]],["impl RefUnwindSafe for TabHistoryEntry",1,["fxa_client::push::TabHistoryEntry"]],["impl RefUnwindSafe for AccessTokenInfo",1,["fxa_client::token::AccessTokenInfo"]],["impl RefUnwindSafe for ScopedKey",1,["fxa_client::token::ScopedKey"]],["impl RefUnwindSafe for AuthorizationParameters",1,["fxa_client::token::AuthorizationParameters"]],["impl !RefUnwindSafe for FirefoxAccount",1,["fxa_client::FirefoxAccount"]],["impl RefUnwindSafe for FxaConfig",1,["fxa_client::FxaConfig"]],["impl RefUnwindSafe for FxaServer",1,["fxa_client::FxaServer"]]], +"interrupt_support":[["impl RefUnwindSafe for Interrupted",1,["interrupt_support::error::Interrupted"]],["impl RefUnwindSafe for NeverInterrupts",1,["interrupt_support::interruptee::NeverInterrupts"]],["impl RefUnwindSafe for ShutdownInterruptee",1,["interrupt_support::shutdown::ShutdownInterruptee"]],["impl RefUnwindSafe for SqlInterruptHandle",1,["interrupt_support::sql::SqlInterruptHandle"]],["impl RefUnwindSafe for SqlInterruptScope",1,["interrupt_support::sql::SqlInterruptScope"]]], +"logins":[["impl RefUnwindSafe for LoginsApiError",1,["logins::error::LoginsApiError"]],["impl !RefUnwindSafe for Error",1,["logins::error::Error"]],["impl RefUnwindSafe for InvalidLogin",1,["logins::error::InvalidLogin"]],["impl RefUnwindSafe for LoginFields",1,["logins::login::LoginFields"]],["impl RefUnwindSafe for SecureLoginFields",1,["logins::login::SecureLoginFields"]],["impl RefUnwindSafe for RecordFields",1,["logins::login::RecordFields"]],["impl RefUnwindSafe for LoginEntry",1,["logins::login::LoginEntry"]],["impl RefUnwindSafe for Login",1,["logins::login::Login"]],["impl RefUnwindSafe for EncryptedLogin",1,["logins::login::EncryptedLogin"]],["impl !RefUnwindSafe for LoginDb",1,["logins::db::LoginDb"]],["impl !RefUnwindSafe for LoginStore",1,["logins::store::LoginStore"]],["impl !RefUnwindSafe for LoginsSyncEngine",1,["logins::sync::engine::LoginsSyncEngine"]]], +"nimbus":[["impl RefUnwindSafe for EnrollmentStatus",1,["nimbus::enrollment::EnrollmentStatus"]],["impl RefUnwindSafe for EnrolledFeature",1,["nimbus::enrollment::EnrolledFeature"]],["impl RefUnwindSafe for NimbusTargetingHelper",1,["nimbus::targeting::NimbusTargetingHelper"]],["impl !RefUnwindSafe for NimbusError",1,["nimbus::error::NimbusError"]],["impl RefUnwindSafe for BehaviorError",1,["nimbus::error::BehaviorError"]],["impl RefUnwindSafe for EnrollmentStatusExtraDef",1,["nimbus::metrics::EnrollmentStatusExtraDef"]],["impl RefUnwindSafe for EnrolledExperiment",1,["nimbus::schema::EnrolledExperiment"]],["impl RefUnwindSafe for Experiment",1,["nimbus::schema::Experiment"]],["impl RefUnwindSafe for FeatureConfig",1,["nimbus::schema::FeatureConfig"]],["impl RefUnwindSafe for Branch",1,["nimbus::schema::Branch"]],["impl RefUnwindSafe for BucketConfig",1,["nimbus::schema::BucketConfig"]],["impl RefUnwindSafe for AvailableExperiment",1,["nimbus::schema::AvailableExperiment"]],["impl RefUnwindSafe for ExperimentBranch",1,["nimbus::schema::ExperimentBranch"]],["impl RefUnwindSafe for RandomizationUnit",1,["nimbus::schema::RandomizationUnit"]],["impl RefUnwindSafe for AvailableRandomizationUnits",1,["nimbus::schema::AvailableRandomizationUnits"]],["impl RefUnwindSafe for Version",1,["nimbus::versioning::Version"]],["impl RefUnwindSafe for Interval",1,["nimbus::stateful::behavior::Interval"]],["impl RefUnwindSafe for IntervalConfig",1,["nimbus::stateful::behavior::IntervalConfig"]],["impl RefUnwindSafe for IntervalData",1,["nimbus::stateful::behavior::IntervalData"]],["impl RefUnwindSafe for SingleIntervalCounter",1,["nimbus::stateful::behavior::SingleIntervalCounter"]],["impl RefUnwindSafe for MultiIntervalCounter",1,["nimbus::stateful::behavior::MultiIntervalCounter"]],["impl RefUnwindSafe for EventQueryType",1,["nimbus::stateful::behavior::EventQueryType"]],["impl RefUnwindSafe for EventStore",1,["nimbus::stateful::behavior::EventStore"]],["impl RefUnwindSafe for DatabaseCache",1,["nimbus::stateful::dbcache::DatabaseCache"]],["impl RefUnwindSafe for TargetingAttributes",1,["nimbus::stateful::evaluator::TargetingAttributes"]],["impl RefUnwindSafe for AppContext",1,["nimbus::stateful::matcher::AppContext"]],["impl RefUnwindSafe for InternalMutableState",1,["nimbus::stateful::nimbus_client::InternalMutableState"]],["impl !RefUnwindSafe for NimbusClient",1,["nimbus::stateful::nimbus_client::NimbusClient"]],["impl RefUnwindSafe for NimbusStringHelper",1,["nimbus::stateful::nimbus_client::NimbusStringHelper"]],["impl RefUnwindSafe for StoreId",1,["nimbus::stateful::persistence::StoreId"]],["impl RefUnwindSafe for SingleStore",1,["nimbus::stateful::persistence::SingleStore"]],["impl RefUnwindSafe for Database",1,["nimbus::stateful::persistence::Database"]]], +"nimbus_cli":[["impl RefUnwindSafe for Cli",1,["nimbus_cli::cli::Cli"]],["impl RefUnwindSafe for CliCommand",1,["nimbus_cli::cli::CliCommand"]],["impl RefUnwindSafe for ManifestArgs",1,["nimbus_cli::cli::ManifestArgs"]],["impl RefUnwindSafe for OpenArgs",1,["nimbus_cli::cli::OpenArgs"]],["impl RefUnwindSafe for ExperimentArgs",1,["nimbus_cli::cli::ExperimentArgs"]],["impl RefUnwindSafe for ExperimentListArgs",1,["nimbus_cli::cli::ExperimentListArgs"]],["impl RefUnwindSafe for ExperimentListSourceArgs",1,["nimbus_cli::cli::ExperimentListSourceArgs"]],["impl RefUnwindSafe for ExperimentListFilterArgs",1,["nimbus_cli::cli::ExperimentListFilterArgs"]],["impl<'a> RefUnwindSafe for ExperimentInfo<'a>",1,["nimbus_cli::output::info::ExperimentInfo"]],["impl<'a> RefUnwindSafe for DateRange<'a>",1,["nimbus_cli::output::info::DateRange"]],["impl RefUnwindSafe for StartAppPostPayload",1,["nimbus_cli::output::server::StartAppPostPayload"]],["impl !RefUnwindSafe for InMemoryDb",1,["nimbus_cli::output::server::InMemoryDb"]],["impl<'a> RefUnwindSafe for StartAppProtocol<'a>",1,["nimbus_cli::protocol::StartAppProtocol"]],["impl RefUnwindSafe for ExperimentSource",1,["nimbus_cli::sources::experiment::ExperimentSource"]],["impl RefUnwindSafe for FeatureDefaults",1,["nimbus_cli::sources::experiment::FeatureDefaults"]],["impl RefUnwindSafe for ExperimentListSource",1,["nimbus_cli::sources::experiment_list::ExperimentListSource"]],["impl RefUnwindSafe for ExperimentListFilter",1,["nimbus_cli::sources::filter::ExperimentListFilter"]],["impl RefUnwindSafe for ManifestSource",1,["nimbus_cli::sources::manifest::ManifestSource"]],["impl RefUnwindSafe for Response",1,["nimbus_cli::updater::taskcluster::Response"]],["impl RefUnwindSafe for TaskClusterRegistry",1,["nimbus_cli::updater::taskcluster::TaskClusterRegistry"]],["impl RefUnwindSafe for ReqwestGunzippingHttpClient",1,["nimbus_cli::updater::taskcluster::ReqwestGunzippingHttpClient"]],["impl RefUnwindSafe for LaunchableApp",1,["nimbus_cli::LaunchableApp"]],["impl RefUnwindSafe for NimbusApp",1,["nimbus_cli::NimbusApp"]],["impl RefUnwindSafe for AppCommand",1,["nimbus_cli::AppCommand"]],["impl RefUnwindSafe for AppOpenArgs",1,["nimbus_cli::AppOpenArgs"]]], +"nimbus_fml":[["impl RefUnwindSafe for ExperimenterFeature",1,["nimbus_fml::backends::experimenter_manifest::ExperimenterFeature"]],["impl RefUnwindSafe for ExperimenterFeatureProperty",1,["nimbus_fml::backends::experimenter_manifest::ExperimenterFeatureProperty"]],["impl RefUnwindSafe for ExperimentManifestPropType",1,["nimbus_fml::backends::experimenter_manifest::ExperimentManifestPropType"]],["impl RefUnwindSafe for TextCodeType",1,["nimbus_fml::backends::kotlin::gen_structs::bundled::TextCodeType"]],["impl RefUnwindSafe for ImageCodeType",1,["nimbus_fml::backends::kotlin::gen_structs::bundled::ImageCodeType"]],["impl RefUnwindSafe for EnumCodeType",1,["nimbus_fml::backends::kotlin::gen_structs::enum_::EnumCodeType"]],["impl RefUnwindSafe for EnumCodeDeclaration",1,["nimbus_fml::backends::kotlin::gen_structs::enum_::EnumCodeDeclaration"]],["impl RefUnwindSafe for FeatureCodeDeclaration",1,["nimbus_fml::backends::kotlin::gen_structs::feature::FeatureCodeDeclaration"]],["impl<'a> RefUnwindSafe for ImportedModuleInitialization<'a>",1,["nimbus_fml::backends::kotlin::gen_structs::imports::ImportedModuleInitialization"]],["impl RefUnwindSafe for ObjectRuntime",1,["nimbus_fml::backends::kotlin::gen_structs::object::ObjectRuntime"]],["impl RefUnwindSafe for ObjectCodeType",1,["nimbus_fml::backends::kotlin::gen_structs::object::ObjectCodeType"]],["impl RefUnwindSafe for ObjectCodeDeclaration",1,["nimbus_fml::backends::kotlin::gen_structs::object::ObjectCodeDeclaration"]],["impl RefUnwindSafe for BooleanCodeType",1,["nimbus_fml::backends::kotlin::gen_structs::primitives::BooleanCodeType"]],["impl RefUnwindSafe for IntCodeType",1,["nimbus_fml::backends::kotlin::gen_structs::primitives::IntCodeType"]],["impl RefUnwindSafe for StringCodeType",1,["nimbus_fml::backends::kotlin::gen_structs::primitives::StringCodeType"]],["impl RefUnwindSafe for OptionalCodeType",1,["nimbus_fml::backends::kotlin::gen_structs::structural::OptionalCodeType"]],["impl RefUnwindSafe for MapCodeType",1,["nimbus_fml::backends::kotlin::gen_structs::structural::MapCodeType"]],["impl RefUnwindSafe for ListCodeType",1,["nimbus_fml::backends::kotlin::gen_structs::structural::ListCodeType"]],["impl<'a> RefUnwindSafe for FeatureManifestDeclaration<'a>",1,["nimbus_fml::backends::kotlin::gen_structs::FeatureManifestDeclaration"]],["impl RefUnwindSafe for ConcreteCodeOracle",1,["nimbus_fml::backends::kotlin::gen_structs::ConcreteCodeOracle"]],["impl RefUnwindSafe for TextCodeType",1,["nimbus_fml::backends::swift::gen_structs::bundled::TextCodeType"]],["impl RefUnwindSafe for ImageCodeType",1,["nimbus_fml::backends::swift::gen_structs::bundled::ImageCodeType"]],["impl RefUnwindSafe for EnumCodeType",1,["nimbus_fml::backends::swift::gen_structs::enum_::EnumCodeType"]],["impl RefUnwindSafe for EnumCodeDeclaration",1,["nimbus_fml::backends::swift::gen_structs::enum_::EnumCodeDeclaration"]],["impl RefUnwindSafe for FeatureCodeDeclaration",1,["nimbus_fml::backends::swift::gen_structs::feature::FeatureCodeDeclaration"]],["impl<'a> RefUnwindSafe for ImportedModuleInitialization<'a>",1,["nimbus_fml::backends::swift::gen_structs::imports::ImportedModuleInitialization"]],["impl RefUnwindSafe for ObjectRuntime",1,["nimbus_fml::backends::swift::gen_structs::object::ObjectRuntime"]],["impl RefUnwindSafe for ObjectCodeType",1,["nimbus_fml::backends::swift::gen_structs::object::ObjectCodeType"]],["impl RefUnwindSafe for ObjectCodeDeclaration",1,["nimbus_fml::backends::swift::gen_structs::object::ObjectCodeDeclaration"]],["impl RefUnwindSafe for BooleanCodeType",1,["nimbus_fml::backends::swift::gen_structs::primitives::BooleanCodeType"]],["impl RefUnwindSafe for IntCodeType",1,["nimbus_fml::backends::swift::gen_structs::primitives::IntCodeType"]],["impl RefUnwindSafe for StringCodeType",1,["nimbus_fml::backends::swift::gen_structs::primitives::StringCodeType"]],["impl RefUnwindSafe for OptionalCodeType",1,["nimbus_fml::backends::swift::gen_structs::structural::OptionalCodeType"]],["impl RefUnwindSafe for MapCodeType",1,["nimbus_fml::backends::swift::gen_structs::structural::MapCodeType"]],["impl RefUnwindSafe for ListCodeType",1,["nimbus_fml::backends::swift::gen_structs::structural::ListCodeType"]],["impl<'a> RefUnwindSafe for FeatureManifestDeclaration<'a>",1,["nimbus_fml::backends::swift::gen_structs::FeatureManifestDeclaration"]],["impl RefUnwindSafe for ConcreteCodeOracle",1,["nimbus_fml::backends::swift::gen_structs::ConcreteCodeOracle"]],["impl RefUnwindSafe for VariablesType",1,["nimbus_fml::backends::VariablesType"]],["impl RefUnwindSafe for CliCmd",1,["nimbus_fml::command_line::commands::CliCmd"]],["impl RefUnwindSafe for GenerateStructCmd",1,["nimbus_fml::command_line::commands::GenerateStructCmd"]],["impl RefUnwindSafe for GenerateExperimenterManifestCmd",1,["nimbus_fml::command_line::commands::GenerateExperimenterManifestCmd"]],["impl RefUnwindSafe for GenerateSingleFileManifestCmd",1,["nimbus_fml::command_line::commands::GenerateSingleFileManifestCmd"]],["impl RefUnwindSafe for ValidateCmd",1,["nimbus_fml::command_line::commands::ValidateCmd"]],["impl RefUnwindSafe for PrintChannelsCmd",1,["nimbus_fml::command_line::commands::PrintChannelsCmd"]],["impl<'object> RefUnwindSafe for DefaultsMerger<'object>",1,["nimbus_fml::defaults_merger::DefaultsMerger"]],["impl !RefUnwindSafe for FMLError",1,["nimbus_fml::error::FMLError"]],["impl RefUnwindSafe for ClientError",1,["nimbus_fml::error::ClientError"]],["impl RefUnwindSafe for EnumVariantBody",1,["nimbus_fml::frontend::EnumVariantBody"]],["impl RefUnwindSafe for EnumBody",1,["nimbus_fml::frontend::EnumBody"]],["impl RefUnwindSafe for FeatureFieldBody",1,["nimbus_fml::frontend::FeatureFieldBody"]],["impl RefUnwindSafe for FieldBody",1,["nimbus_fml::frontend::FieldBody"]],["impl RefUnwindSafe for ObjectBody",1,["nimbus_fml::frontend::ObjectBody"]],["impl RefUnwindSafe for Types",1,["nimbus_fml::frontend::Types"]],["impl RefUnwindSafe for AboutBlock",1,["nimbus_fml::frontend::AboutBlock"]],["impl RefUnwindSafe for SwiftAboutBlock",1,["nimbus_fml::frontend::SwiftAboutBlock"]],["impl RefUnwindSafe for KotlinAboutBlock",1,["nimbus_fml::frontend::KotlinAboutBlock"]],["impl RefUnwindSafe for ImportBlock",1,["nimbus_fml::frontend::ImportBlock"]],["impl RefUnwindSafe for FeatureBody",1,["nimbus_fml::frontend::FeatureBody"]],["impl RefUnwindSafe for FeatureMetadata",1,["nimbus_fml::frontend::FeatureMetadata"]],["impl RefUnwindSafe for DocumentationLink",1,["nimbus_fml::frontend::DocumentationLink"]],["impl RefUnwindSafe for ManifestFrontEnd",1,["nimbus_fml::frontend::ManifestFrontEnd"]],["impl RefUnwindSafe for DefaultBlock",1,["nimbus_fml::frontend::DefaultBlock"]],["impl RefUnwindSafe for TargetLanguage",1,["nimbus_fml::intermediate_representation::TargetLanguage"]],["impl RefUnwindSafe for TypeRef",1,["nimbus_fml::intermediate_representation::TypeRef"]],["impl RefUnwindSafe for ModuleId",1,["nimbus_fml::intermediate_representation::ModuleId"]],["impl RefUnwindSafe for FeatureManifest",1,["nimbus_fml::intermediate_representation::FeatureManifest"]],["impl RefUnwindSafe for FeatureDef",1,["nimbus_fml::intermediate_representation::FeatureDef"]],["impl RefUnwindSafe for EnumDef",1,["nimbus_fml::intermediate_representation::EnumDef"]],["impl RefUnwindSafe for FromStringDef",1,["nimbus_fml::intermediate_representation::FromStringDef"]],["impl RefUnwindSafe for VariantDef",1,["nimbus_fml::intermediate_representation::VariantDef"]],["impl RefUnwindSafe for ObjectDef",1,["nimbus_fml::intermediate_representation::ObjectDef"]],["impl RefUnwindSafe for PropDef",1,["nimbus_fml::intermediate_representation::PropDef"]],["impl<'a> RefUnwindSafe for ImportedModule<'a>",1,["nimbus_fml::intermediate_representation::ImportedModule"]],["impl !RefUnwindSafe for Parser",1,["nimbus_fml::parser::Parser"]],["impl RefUnwindSafe for LoaderConfig",1,["nimbus_fml::util::loaders::LoaderConfig"]],["impl RefUnwindSafe for FilePath",1,["nimbus_fml::util::loaders::FilePath"]],["impl !RefUnwindSafe for FileLoader",1,["nimbus_fml::util::loaders::FileLoader"]]], +"nss":[["impl RefUnwindSafe for Operation",1,["nss::aes::Operation"]],["impl RefUnwindSafe for Curve",1,["nss::ec::Curve"]],["impl RefUnwindSafe for EcKey",1,["nss::ec::EcKey"]],["impl RefUnwindSafe for PrivateKey",1,["nss::ec::PrivateKey"]],["impl RefUnwindSafe for PublicKey",1,["nss::ec::PublicKey"]],["impl RefUnwindSafe for ErrorKind",1,["nss::error::ErrorKind"]],["impl RefUnwindSafe for Error",1,["nss::error::Error"]],["impl RefUnwindSafe for HashAlgorithm",1,["nss::pk11::context::HashAlgorithm"]],["impl RefUnwindSafe for SymKey",1,["nss::pk11::types::SymKey"]],["impl RefUnwindSafe for PrivateKey",1,["nss::pk11::types::PrivateKey"]],["impl RefUnwindSafe for PublicKey",1,["nss::pk11::types::PublicKey"]],["impl RefUnwindSafe for GenericObject",1,["nss::pk11::types::GenericObject"]],["impl RefUnwindSafe for Certificate",1,["nss::pk11::types::Certificate"]],["impl RefUnwindSafe for Context",1,["nss::pk11::types::Context"]],["impl RefUnwindSafe for Slot",1,["nss::pk11::types::Slot"]],["impl RefUnwindSafe for AlgorithmID",1,["nss::pk11::types::AlgorithmID"]]], +"nss_build_common":[["impl RefUnwindSafe for LinkingKind",1,["nss_build_common::LinkingKind"]],["impl RefUnwindSafe for NoNssDir",1,["nss_build_common::NoNssDir"]]], +"nss_sys":[["impl RefUnwindSafe for SECKEYPublicKeyStr",1,["nss_sys::bindings::keythi::SECKEYPublicKeyStr"]],["impl RefUnwindSafe for SECKEYPublicKeyStr_u",1,["nss_sys::bindings::keythi::SECKEYPublicKeyStr_u"]],["impl RefUnwindSafe for SECKEYPrivateKeyStr",1,["nss_sys::bindings::keythi::SECKEYPrivateKeyStr"]],["impl RefUnwindSafe for KeyType",1,["nss_sys::bindings::keythi::KeyType"]],["impl RefUnwindSafe for SECKEYRSAPublicKeyStr",1,["nss_sys::bindings::keythi::SECKEYRSAPublicKeyStr"]],["impl RefUnwindSafe for SECKEYDSAPublicKeyStr",1,["nss_sys::bindings::keythi::SECKEYDSAPublicKeyStr"]],["impl RefUnwindSafe for SECKEYPQGParamsStr",1,["nss_sys::bindings::keythi::SECKEYPQGParamsStr"]],["impl RefUnwindSafe for SECKEYDHPublicKeyStr",1,["nss_sys::bindings::keythi::SECKEYDHPublicKeyStr"]],["impl RefUnwindSafe for SECKEYKEAPublicKeyStr",1,["nss_sys::bindings::keythi::SECKEYKEAPublicKeyStr"]],["impl RefUnwindSafe for SECKEYKEAParamsStr",1,["nss_sys::bindings::keythi::SECKEYKEAParamsStr"]],["impl RefUnwindSafe for SECKEYFortezzaPublicKeyStr",1,["nss_sys::bindings::keythi::SECKEYFortezzaPublicKeyStr"]],["impl RefUnwindSafe for SECKEYECPublicKeyStr",1,["nss_sys::bindings::keythi::SECKEYECPublicKeyStr"]],["impl RefUnwindSafe for ECPointEncoding",1,["nss_sys::bindings::keythi::ECPointEncoding"]],["impl RefUnwindSafe for CK_GCM_PARAMS_V3",1,["nss_sys::bindings::pkcs11n::CK_GCM_PARAMS_V3"]],["impl RefUnwindSafe for CK_NSS_HKDFParams",1,["nss_sys::bindings::pkcs11n::CK_NSS_HKDFParams"]],["impl RefUnwindSafe for CK_ATTRIBUTE",1,["nss_sys::bindings::pkcs11t::CK_ATTRIBUTE"]],["impl RefUnwindSafe for PLArena",1,["nss_sys::bindings::plarena::PLArena"]],["impl RefUnwindSafe for PLArenaPool",1,["nss_sys::bindings::plarena::PLArenaPool"]],["impl RefUnwindSafe for SECItemType",1,["nss_sys::bindings::seccomon::SECItemType"]],["impl RefUnwindSafe for SECItemStr",1,["nss_sys::bindings::seccomon::SECItemStr"]],["impl RefUnwindSafe for _SECStatus",1,["nss_sys::bindings::seccomon::_SECStatus"]],["impl RefUnwindSafe for PK11Origin",1,["nss_sys::bindings::secmodt::PK11Origin"]],["impl RefUnwindSafe for PK11ObjectType",1,["nss_sys::bindings::secmodt::PK11ObjectType"]],["impl RefUnwindSafe for SECAlgorithmIDStr",1,["nss_sys::bindings::secoidt::SECAlgorithmIDStr"]],["impl RefUnwindSafe for SECOidDataStr",1,["nss_sys::bindings::secoidt::SECOidDataStr"]],["impl RefUnwindSafe for SECSupportExtenTag",1,["nss_sys::bindings::secoidt::SECSupportExtenTag"]],["impl RefUnwindSafe for SECOidTag",1,["nss_sys::bindings::secoidt::SECOidTag"]]], +"places":[["impl RefUnwindSafe for AddablePlaceInfo",1,["places::api::history::AddablePlaceInfo"]],["impl RefUnwindSafe for AddableVisit",1,["places::api::history::AddableVisit"]],["impl RefUnwindSafe for RedirectSourceType",1,["places::api::history::RedirectSourceType"]],["impl RefUnwindSafe for SearchParams",1,["places::api::matcher::SearchParams"]],["impl RefUnwindSafe for SearchResult",1,["places::api::matcher::SearchResult"]],["impl RefUnwindSafe for ConnectionType",1,["places::api::places_api::ConnectionType"]],["impl !RefUnwindSafe for SyncState",1,["places::api::places_api::SyncState"]],["impl !RefUnwindSafe for PlacesApi",1,["places::api::places_api::PlacesApi"]],["impl RefUnwindSafe for PlacesApiError",1,["places::error::PlacesApiError"]],["impl !RefUnwindSafe for Error",1,["places::error::Error"]],["impl RefUnwindSafe for InvalidPlaceInfo",1,["places::error::InvalidPlaceInfo"]],["impl RefUnwindSafe for Corruption",1,["places::error::Corruption"]],["impl RefUnwindSafe for InvalidMetadataObservation",1,["places::error::InvalidMetadataObservation"]],["impl RefUnwindSafe for VisitTransitionSet",1,["places::types::visit_transition_set::VisitTransitionSet"]],["impl RefUnwindSafe for InvalidVisitType",1,["places::types::InvalidVisitType"]],["impl RefUnwindSafe for VisitType",1,["places::types::VisitType"]],["impl RefUnwindSafe for BookmarkType",1,["places::types::BookmarkType"]],["impl RefUnwindSafe for SyncStatus",1,["places::types::SyncStatus"]],["impl !RefUnwindSafe for BookmarksSyncEngine",1,["places::bookmark_sync::engine::BookmarksSyncEngine"]],["impl RefUnwindSafe for BookmarkRecordId",1,["places::bookmark_sync::record::BookmarkRecordId"]],["impl RefUnwindSafe for BookmarkRecord",1,["places::bookmark_sync::record::BookmarkRecord"]],["impl RefUnwindSafe for QueryRecord",1,["places::bookmark_sync::record::QueryRecord"]],["impl RefUnwindSafe for FolderRecord",1,["places::bookmark_sync::record::FolderRecord"]],["impl RefUnwindSafe for LivemarkRecord",1,["places::bookmark_sync::record::LivemarkRecord"]],["impl RefUnwindSafe for SeparatorRecord",1,["places::bookmark_sync::record::SeparatorRecord"]],["impl RefUnwindSafe for BookmarkItemRecord",1,["places::bookmark_sync::record::BookmarkItemRecord"]],["impl RefUnwindSafe for SyncedBookmarkKind",1,["places::bookmark_sync::SyncedBookmarkKind"]],["impl RefUnwindSafe for SyncedBookmarkValidity",1,["places::bookmark_sync::SyncedBookmarkValidity"]],["impl RefUnwindSafe for GLOBAL_BOOKMARK_CHANGE_COUNTERS",1,["places::db::db::GLOBAL_BOOKMARK_CHANGE_COUNTERS"]],["impl RefUnwindSafe for PlacesInitializer",1,["places::db::db::PlacesInitializer"]],["impl !RefUnwindSafe for PlacesDb",1,["places::db::db::PlacesDb"]],["impl !RefUnwindSafe for SharedPlacesDb",1,["places::db::db::SharedPlacesDb"]],["impl RefUnwindSafe for GlobalChangeCounterTracker",1,["places::db::db::GlobalChangeCounterTracker"]],["impl<'conn> !RefUnwindSafe for PlacesTransaction<'conn>",1,["places::db::tx::PlacesTransaction"]],["impl !RefUnwindSafe for PlacesConnection",1,["places::ffi::PlacesConnection"]],["impl RefUnwindSafe for HistoryVisitInfo",1,["places::ffi::HistoryVisitInfo"]],["impl RefUnwindSafe for HistoryVisitInfosWithBound",1,["places::ffi::HistoryVisitInfosWithBound"]],["impl RefUnwindSafe for TopFrecentSiteInfo",1,["places::ffi::TopFrecentSiteInfo"]],["impl RefUnwindSafe for FrecencyThresholdOption",1,["places::ffi::FrecencyThresholdOption"]],["impl RefUnwindSafe for SearchResult",1,["places::ffi::SearchResult"]],["impl RefUnwindSafe for Dummy",1,["places::ffi::Dummy"]],["impl RefUnwindSafe for FrecencySettings",1,["places::frecency::FrecencySettings"]],["impl RefUnwindSafe for PrefixMode",1,["places::hash::PrefixMode"]],["impl !RefUnwindSafe for HistorySyncEngine",1,["places::history_sync::engine::HistorySyncEngine"]],["impl RefUnwindSafe for HistoryRecordVisit",1,["places::history_sync::record::HistoryRecordVisit"]],["impl RefUnwindSafe for HistoryRecord",1,["places::history_sync::record::HistoryRecord"]],["impl RefUnwindSafe for ServerVisitTimestamp",1,["places::history_sync::ServerVisitTimestamp"]],["impl RefUnwindSafe for NOW",1,["places::import::common::NOW"]],["impl<'a> !RefUnwindSafe for ExecuteOnDrop<'a>",1,["places::import::common::ExecuteOnDrop"]],["impl RefUnwindSafe for HistoryMigrationResult",1,["places::import::common::HistoryMigrationResult"]],["impl RefUnwindSafe for MatchBehavior",1,["places::match_impl::MatchBehavior"]],["impl RefUnwindSafe for SearchBehavior",1,["places::match_impl::SearchBehavior"]],["impl<'search, 'url, 'title, 'tags> RefUnwindSafe for AutocompleteMatch<'search, 'url, 'title, 'tags>",1,["places::match_impl::AutocompleteMatch"]],["impl RefUnwindSafe for VisitObservation",1,["places::observation::VisitObservation"]],["impl RefUnwindSafe for BookmarkData",1,["places::storage::bookmarks::fetch::BookmarkData"]],["impl RefUnwindSafe for Separator",1,["places::storage::bookmarks::fetch::Separator"]],["impl RefUnwindSafe for Folder",1,["places::storage::bookmarks::fetch::Folder"]],["impl RefUnwindSafe for Item",1,["places::storage::bookmarks::fetch::Item"]],["impl RefUnwindSafe for SEARCH_QUERY",1,["places::storage::bookmarks::fetch::SEARCH_QUERY"]],["impl RefUnwindSafe for RECENT_BOOKMARKS_QUERY",1,["places::storage::bookmarks::fetch::RECENT_BOOKMARKS_QUERY"]],["impl RefUnwindSafe for BookmarkNode",1,["places::storage::bookmarks::json_tree::BookmarkNode"]],["impl RefUnwindSafe for SeparatorNode",1,["places::storage::bookmarks::json_tree::SeparatorNode"]],["impl RefUnwindSafe for FolderNode",1,["places::storage::bookmarks::json_tree::FolderNode"]],["impl RefUnwindSafe for BookmarkTreeNode",1,["places::storage::bookmarks::json_tree::BookmarkTreeNode"]],["impl RefUnwindSafe for FetchDepth",1,["places::storage::bookmarks::json_tree::FetchDepth"]],["impl RefUnwindSafe for BookmarkRootGuid",1,["places::storage::bookmarks::root_guid::BookmarkRootGuid"]],["impl RefUnwindSafe for BookmarkPosition",1,["places::storage::bookmarks::BookmarkPosition"]],["impl RefUnwindSafe for InsertableBookmark",1,["places::storage::bookmarks::InsertableBookmark"]],["impl RefUnwindSafe for InsertableSeparator",1,["places::storage::bookmarks::InsertableSeparator"]],["impl RefUnwindSafe for InsertableFolder",1,["places::storage::bookmarks::InsertableFolder"]],["impl RefUnwindSafe for InsertableItem",1,["places::storage::bookmarks::InsertableItem"]],["impl RefUnwindSafe for UpdateTreeLocation",1,["places::storage::bookmarks::UpdateTreeLocation"]],["impl RefUnwindSafe for UpdatableBookmark",1,["places::storage::bookmarks::UpdatableBookmark"]],["impl RefUnwindSafe for UpdatableSeparator",1,["places::storage::bookmarks::UpdatableSeparator"]],["impl RefUnwindSafe for UpdatableFolder",1,["places::storage::bookmarks::UpdatableFolder"]],["impl RefUnwindSafe for UpdatableItem",1,["places::storage::bookmarks::UpdatableItem"]],["impl RefUnwindSafe for BookmarkUpdateInfo",1,["places::storage::bookmarks::BookmarkUpdateInfo"]],["impl RefUnwindSafe for FetchedVisit",1,["places::storage::history::history_sync::FetchedVisit"]],["impl RefUnwindSafe for FetchedVisitPage",1,["places::storage::history::history_sync::FetchedVisitPage"]],["impl RefUnwindSafe for DocumentType",1,["places::storage::history_metadata::DocumentType"]],["impl RefUnwindSafe for HistoryHighlightWeights",1,["places::storage::history_metadata::HistoryHighlightWeights"]],["impl RefUnwindSafe for HistoryHighlight",1,["places::storage::history_metadata::HistoryHighlight"]],["impl RefUnwindSafe for HistoryMetadataObservation",1,["places::storage::history_metadata::HistoryMetadataObservation"]],["impl RefUnwindSafe for HistoryMetadata",1,["places::storage::history_metadata::HistoryMetadata"]],["impl<'a> RefUnwindSafe for ValidatedTag<'a>",1,["places::storage::tags::ValidatedTag"]],["impl RefUnwindSafe for RowId",1,["places::storage::RowId"]],["impl RefUnwindSafe for PageInfo",1,["places::storage::PageInfo"]],["impl RefUnwindSafe for FetchedPageInfo",1,["places::storage::FetchedPageInfo"]],["impl RefUnwindSafe for RunMaintenanceMetrics",1,["places::storage::RunMaintenanceMetrics"]]], +"protobuf_gen":[["impl RefUnwindSafe for ProtobufOpts",1,["protobuf_gen::ProtobufOpts"]]], +"push":[["impl RefUnwindSafe for BridgeType",1,["push::internal::config::BridgeType"]],["impl RefUnwindSafe for PushConfiguration",1,["push::internal::config::PushConfiguration"]],["impl RefUnwindSafe for Protocol",1,["push::internal::config::Protocol"]],["impl RefUnwindSafe for PushApiError",1,["push::error::PushApiError"]],["impl !RefUnwindSafe for PushError",1,["push::error::PushError"]],["impl RefUnwindSafe for PushManager",1,["push::PushManager"]],["impl RefUnwindSafe for KeyInfo",1,["push::KeyInfo"]],["impl RefUnwindSafe for SubscriptionInfo",1,["push::SubscriptionInfo"]],["impl RefUnwindSafe for SubscriptionResponse",1,["push::SubscriptionResponse"]],["impl RefUnwindSafe for PushSubscriptionChanged",1,["push::PushSubscriptionChanged"]]], +"rc_crypto":[["impl<'a> RefUnwindSafe for Aad<'a>",1,["rc_crypto::aead::Aad"]],["impl RefUnwindSafe for Nonce",1,["rc_crypto::aead::Nonce"]],["impl RefUnwindSafe for OpeningKey",1,["rc_crypto::aead::OpeningKey"]],["impl RefUnwindSafe for SealingKey",1,["rc_crypto::aead::SealingKey"]],["impl RefUnwindSafe for Algorithm",1,["rc_crypto::aead::Algorithm"]],["impl RefUnwindSafe for Algorithm",1,["rc_crypto::agreement::Algorithm"]],["impl RefUnwindSafe for Ephemeral",1,["rc_crypto::agreement::Ephemeral"]],["impl RefUnwindSafe for Static",1,["rc_crypto::agreement::Static"]],["impl<U> RefUnwindSafe for KeyPair<U>where\n U: RefUnwindSafe,",1,["rc_crypto::agreement::KeyPair"]],["impl RefUnwindSafe for PublicKey",1,["rc_crypto::agreement::PublicKey"]],["impl<'a> RefUnwindSafe for UnparsedPublicKey<'a>",1,["rc_crypto::agreement::UnparsedPublicKey"]],["impl<U> RefUnwindSafe for PrivateKey<U>where\n U: RefUnwindSafe,",1,["rc_crypto::agreement::PrivateKey"]],["impl RefUnwindSafe for InputKeyMaterial",1,["rc_crypto::agreement::InputKeyMaterial"]],["impl RefUnwindSafe for Digest",1,["rc_crypto::digest::Digest"]],["impl RefUnwindSafe for RcCryptoLocalKeyPair",1,["rc_crypto::ece_crypto::RcCryptoLocalKeyPair"]],["impl RefUnwindSafe for RcCryptoRemotePublicKey",1,["rc_crypto::ece_crypto::RcCryptoRemotePublicKey"]],["impl RefUnwindSafe for ErrorKind",1,["rc_crypto::error::ErrorKind"]],["impl RefUnwindSafe for Error",1,["rc_crypto::error::Error"]],["impl RefUnwindSafe for Signature",1,["rc_crypto::hmac::Signature"]],["impl RefUnwindSafe for SigningKey",1,["rc_crypto::hmac::SigningKey"]],["impl RefUnwindSafe for VerificationKey",1,["rc_crypto::hmac::VerificationKey"]],["impl RefUnwindSafe for VerificationAlgorithm",1,["rc_crypto::signature::VerificationAlgorithm"]],["impl<'a> RefUnwindSafe for UnparsedPublicKey<'a>",1,["rc_crypto::signature::UnparsedPublicKey"]]], +"rc_log_ffi":[["impl !RefUnwindSafe for LogAdapterState",1,["rc_log_ffi::android::LogAdapterState"]],["impl RefUnwindSafe for LogSink",1,["rc_log_ffi::android::LogSink"]],["impl RefUnwindSafe for LogLevel",1,["rc_log_ffi::LogLevel"]]], +"remote_settings":[["impl !RefUnwindSafe for RemoteSettingsError",1,["remote_settings::error::RemoteSettingsError"]],["impl !RefUnwindSafe for Client",1,["remote_settings::client::Client"]],["impl RefUnwindSafe for RemoteSettingsResponse",1,["remote_settings::client::RemoteSettingsResponse"]],["impl RefUnwindSafe for RemoteSettingsRecord",1,["remote_settings::client::RemoteSettingsRecord"]],["impl RefUnwindSafe for Attachment",1,["remote_settings::client::Attachment"]],["impl RefUnwindSafe for GetItemsOptions",1,["remote_settings::client::GetItemsOptions"]],["impl RefUnwindSafe for SortOrder",1,["remote_settings::client::SortOrder"]],["impl RefUnwindSafe for RemoteSettingsConfig",1,["remote_settings::config::RemoteSettingsConfig"]],["impl !RefUnwindSafe for RemoteSettings",1,["remote_settings::RemoteSettings"]]], +"sql_support":[["impl !RefUnwindSafe for Conn",1,["sql_support::conn_ext::Conn"]],["impl<'conn> !RefUnwindSafe for UncheckedTransaction<'conn>",1,["sql_support::conn_ext::UncheckedTransaction"]],["impl<'conn> !RefUnwindSafe for MaybeCached<'conn>",1,["sql_support::maybe_cached::MaybeCached"]],["impl<CI> RefUnwindSafe for MigratedDatabaseFile<CI>where\n CI: RefUnwindSafe,",1,["sql_support::open_database::test_utils::MigratedDatabaseFile"]],["impl !RefUnwindSafe for Error",1,["sql_support::open_database::Error"]],["impl<'a, F> RefUnwindSafe for RepeatDisplay<'a, F>where\n F: RefUnwindSafe,",1,["sql_support::repeat::RepeatDisplay"]]], +"sync15":[["impl RefUnwindSafe for IncomingEncryptedBso",1,["sync15::bso::crypto::IncomingEncryptedBso"]],["impl RefUnwindSafe for OutgoingEncryptedBso",1,["sync15::bso::crypto::OutgoingEncryptedBso"]],["impl RefUnwindSafe for IncomingEnvelope",1,["sync15::bso::IncomingEnvelope"]],["impl RefUnwindSafe for OutgoingEnvelope",1,["sync15::bso::OutgoingEnvelope"]],["impl RefUnwindSafe for IncomingBso",1,["sync15::bso::IncomingBso"]],["impl RefUnwindSafe for OutgoingBso",1,["sync15::bso::OutgoingBso"]],["impl<T> RefUnwindSafe for IncomingContent<T>where\n T: RefUnwindSafe,",1,["sync15::bso::IncomingContent"]],["impl<T> RefUnwindSafe for IncomingKind<T>where\n T: RefUnwindSafe,",1,["sync15::bso::IncomingKind"]],["impl RefUnwindSafe for ServiceStatus",1,["sync15::client::status::ServiceStatus"]],["impl !RefUnwindSafe for SyncResult",1,["sync15::client::status::SyncResult"]],["impl<T> RefUnwindSafe for Sync15ClientResponse<T>where\n T: RefUnwindSafe,",1,["sync15::client::storage_client::Sync15ClientResponse"]],["impl RefUnwindSafe for Sync15StorageClientInit",1,["sync15::client::storage_client::Sync15StorageClientInit"]],["impl !RefUnwindSafe for Sync15StorageClient",1,["sync15::client::storage_client::Sync15StorageClient"]],["impl !RefUnwindSafe for MemoryCachedState",1,["sync15::client::sync_multiple::MemoryCachedState"]],["impl<'a> RefUnwindSafe for SyncRequestInfo<'a>",1,["sync15::client::sync_multiple::SyncRequestInfo"]],["impl RefUnwindSafe for DeviceType",1,["sync15::device_type::DeviceType"]],["impl RefUnwindSafe for ClientData",1,["sync15::client_types::ClientData"]],["impl RefUnwindSafe for RemoteClient",1,["sync15::client_types::RemoteClient"]],["impl<'a> !RefUnwindSafe for Engine<'a>",1,["sync15::clients_engine::engine::Engine"]],["impl RefUnwindSafe for CommandStatus",1,["sync15::clients_engine::CommandStatus"]],["impl RefUnwindSafe for Settings",1,["sync15::clients_engine::Settings"]],["impl RefUnwindSafe for Command",1,["sync15::clients_engine::Command"]],["impl RefUnwindSafe for EncryptedPayload",1,["sync15::enc_payload::EncryptedPayload"]],["impl RefUnwindSafe for ApplyResults",1,["sync15::engine::bridged_engine::ApplyResults"]],["impl RefUnwindSafe for CollectionRequest",1,["sync15::engine::request::CollectionRequest"]],["impl RefUnwindSafe for RequestOrder",1,["sync15::engine::request::RequestOrder"]],["impl RefUnwindSafe for CollSyncIds",1,["sync15::engine::sync_engine::CollSyncIds"]],["impl RefUnwindSafe for EngineSyncAssociation",1,["sync15::engine::sync_engine::EngineSyncAssociation"]],["impl RefUnwindSafe for SyncEngineId",1,["sync15::engine::sync_engine::SyncEngineId"]],["impl !RefUnwindSafe for Error",1,["sync15::error::Error"]],["impl RefUnwindSafe for KeyBundle",1,["sync15::key_bundle::KeyBundle"]],["impl RefUnwindSafe for ServerTimestamp",1,["sync15::server_timestamp::ServerTimestamp"]],["impl RefUnwindSafe for Event",1,["sync15::telemetry::Event"]],["impl RefUnwindSafe for SyncFailure",1,["sync15::telemetry::SyncFailure"]],["impl RefUnwindSafe for EngineIncoming",1,["sync15::telemetry::EngineIncoming"]],["impl RefUnwindSafe for EngineOutgoing",1,["sync15::telemetry::EngineOutgoing"]],["impl RefUnwindSafe for Engine",1,["sync15::telemetry::Engine"]],["impl RefUnwindSafe for Validation",1,["sync15::telemetry::Validation"]],["impl RefUnwindSafe for Problem",1,["sync15::telemetry::Problem"]],["impl RefUnwindSafe for SyncTelemetry",1,["sync15::telemetry::SyncTelemetry"]],["impl RefUnwindSafe for SyncTelemetryPing",1,["sync15::telemetry::SyncTelemetryPing"]]], +"sync_guid":[["impl RefUnwindSafe for Guid",1,["sync_guid::Guid"]]], +"sync_manager":[["impl !RefUnwindSafe for SyncManagerError",1,["sync_manager::error::SyncManagerError"]],["impl !RefUnwindSafe for SyncManager",1,["sync_manager::manager::SyncManager"]],["impl RefUnwindSafe for SyncParams",1,["sync_manager::types::SyncParams"]],["impl RefUnwindSafe for SyncReason",1,["sync_manager::types::SyncReason"]],["impl RefUnwindSafe for SyncEngineSelection",1,["sync_manager::types::SyncEngineSelection"]],["impl RefUnwindSafe for SyncAuthInfo",1,["sync_manager::types::SyncAuthInfo"]],["impl RefUnwindSafe for DeviceSettings",1,["sync_manager::types::DeviceSettings"]],["impl RefUnwindSafe for SyncResult",1,["sync_manager::types::SyncResult"]],["impl RefUnwindSafe for ServiceStatus",1,["sync_manager::types::ServiceStatus"]]], +"tabs":[["impl RefUnwindSafe for TabsApiError",1,["tabs::error::TabsApiError"]],["impl !RefUnwindSafe for Error",1,["tabs::error::Error"]],["impl RefUnwindSafe for ClientRemoteTabs",1,["tabs::storage::ClientRemoteTabs"]],["impl RefUnwindSafe for TabsStore",1,["tabs::store::TabsStore"]],["impl !RefUnwindSafe for TabsBridgedEngine",1,["tabs::sync::bridge::TabsBridgedEngine"]],["impl RefUnwindSafe for TabsEngine",1,["tabs::sync::engine::TabsEngine"]]], +"types":[["impl RefUnwindSafe for Timestamp",1,["types::Timestamp"]]], +"viaduct":[["impl RefUnwindSafe for HeaderName",1,["viaduct::headers::name::HeaderName"]],["impl RefUnwindSafe for InvalidHeaderName",1,["viaduct::headers::name::InvalidHeaderName"]],["impl RefUnwindSafe for Header",1,["viaduct::headers::Header"]],["impl RefUnwindSafe for Headers",1,["viaduct::headers::Headers"]],["impl RefUnwindSafe for Error",1,["viaduct::error::Error"]],["impl RefUnwindSafe for UnexpectedStatus",1,["viaduct::error::UnexpectedStatus"]],["impl RefUnwindSafe for Settings",1,["viaduct::settings::Settings"]],["impl RefUnwindSafe for Method",1,["viaduct::Method"]],["impl RefUnwindSafe for Request",1,["viaduct::Request"]],["impl RefUnwindSafe for Response",1,["viaduct::Response"]]], +"viaduct_reqwest":[["impl RefUnwindSafe for ReqwestBackend",1,["viaduct_reqwest::ReqwestBackend"]]], +"webext_storage":[["impl RefUnwindSafe for UsageInfo",1,["webext_storage::api::UsageInfo"]],["impl RefUnwindSafe for QuotaReason",1,["webext_storage::error::QuotaReason"]],["impl !RefUnwindSafe for ErrorKind",1,["webext_storage::error::ErrorKind"]],["impl !RefUnwindSafe for Error",1,["webext_storage::error::Error"]],["impl RefUnwindSafe for MigrationInfo",1,["webext_storage::migration::MigrationInfo"]],["impl !RefUnwindSafe for Store",1,["webext_storage::store::Store"]]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/core/panic/unwind_safe/trait.UnwindSafe.js b/book/rust-docs/implementors/core/panic/unwind_safe/trait.UnwindSafe.js new file mode 100644 index 0000000000..778b010e22 --- /dev/null +++ b/book/rust-docs/implementors/core/panic/unwind_safe/trait.UnwindSafe.js @@ -0,0 +1,32 @@ +(function() {var implementors = { +"as_ohttp_client":[["impl UnwindSafe for OhttpError",1,["as_ohttp_client::OhttpError"]],["impl UnwindSafe for OhttpSession",1,["as_ohttp_client::OhttpSession"]],["impl UnwindSafe for OhttpResponse",1,["as_ohttp_client::OhttpResponse"]],["impl UnwindSafe for OhttpTestServer",1,["as_ohttp_client::OhttpTestServer"]],["impl UnwindSafe for TestServerRequest",1,["as_ohttp_client::TestServerRequest"]]], +"autofill":[["impl UnwindSafe for UpdatableAddressFields",1,["autofill::db::models::address::UpdatableAddressFields"]],["impl UnwindSafe for Address",1,["autofill::db::models::address::Address"]],["impl UnwindSafe for InternalAddress",1,["autofill::db::models::address::InternalAddress"]],["impl UnwindSafe for UpdatableCreditCardFields",1,["autofill::db::models::credit_card::UpdatableCreditCardFields"]],["impl UnwindSafe for CreditCard",1,["autofill::db::models::credit_card::CreditCard"]],["impl UnwindSafe for InternalCreditCard",1,["autofill::db::models::credit_card::InternalCreditCard"]],["impl UnwindSafe for Metadata",1,["autofill::db::models::Metadata"]],["impl UnwindSafe for AutofillConnectionInitializer",1,["autofill::db::schema::AutofillConnectionInitializer"]],["impl UnwindSafe for Store",1,["autofill::db::store::Store"]],["impl !UnwindSafe for AutofillDb",1,["autofill::db::AutofillDb"]],["impl UnwindSafe for AutofillApiError",1,["autofill::error::AutofillApiError"]],["impl !UnwindSafe for Error",1,["autofill::error::Error"]],["impl UnwindSafe for AddressPayload",1,["autofill::sync::address::AddressPayload"]],["impl UnwindSafe for EngineConfig",1,["autofill::sync::engine::EngineConfig"]],["impl<T> !UnwindSafe for ConfigSyncEngine<T>",1,["autofill::sync::engine::ConfigSyncEngine"]],["impl<T> UnwindSafe for MergeResult<T>where\n T: UnwindSafe,",1,["autofill::sync::MergeResult"]],["impl<T> UnwindSafe for IncomingState<T>where\n T: UnwindSafe,",1,["autofill::sync::IncomingState"]]], +"cli_support":[["impl !UnwindSafe for CliFxa",1,["cli_support::fxa_creds::CliFxa"]]], +"crashtest":[["impl UnwindSafe for CrashTestError",1,["crashtest::CrashTestError"]]], +"error_support":[["impl UnwindSafe for ErrorReporting",1,["error_support::handling::ErrorReporting"]],["impl<E> UnwindSafe for ErrorHandling<E>where\n E: UnwindSafe,",1,["error_support::handling::ErrorHandling"]]], +"examples_fxa_client":[["impl UnwindSafe for DeviceArgs",1,["examples_fxa_client::devices::DeviceArgs"]],["impl UnwindSafe for Command",1,["examples_fxa_client::devices::Command"]],["impl UnwindSafe for SendTabArgs",1,["examples_fxa_client::send_tab::SendTabArgs"]],["impl UnwindSafe for Command",1,["examples_fxa_client::send_tab::Command"]],["impl UnwindSafe for Cli",1,["examples_fxa_client::Cli"]],["impl UnwindSafe for Server",1,["examples_fxa_client::Server"]],["impl UnwindSafe for Command",1,["examples_fxa_client::Command"]]], +"fxa_client":[["impl UnwindSafe for AuthorizationInfo",1,["fxa_client::auth::AuthorizationInfo"]],["impl UnwindSafe for MetricsParams",1,["fxa_client::auth::MetricsParams"]],["impl UnwindSafe for FxaRustAuthState",1,["fxa_client::auth::FxaRustAuthState"]],["impl UnwindSafe for LocalDevice",1,["fxa_client::device::LocalDevice"]],["impl UnwindSafe for Device",1,["fxa_client::device::Device"]],["impl UnwindSafe for DeviceCapability",1,["fxa_client::device::DeviceCapability"]],["impl UnwindSafe for AttachedClient",1,["fxa_client::device::AttachedClient"]],["impl UnwindSafe for FxaError",1,["fxa_client::error::FxaError"]],["impl !UnwindSafe for Error",1,["fxa_client::error::Error"]],["impl UnwindSafe for Profile",1,["fxa_client::profile::Profile"]],["impl UnwindSafe for DevicePushSubscription",1,["fxa_client::push::DevicePushSubscription"]],["impl UnwindSafe for AccountEvent",1,["fxa_client::push::AccountEvent"]],["impl UnwindSafe for IncomingDeviceCommand",1,["fxa_client::push::IncomingDeviceCommand"]],["impl UnwindSafe for SendTabPayload",1,["fxa_client::push::SendTabPayload"]],["impl UnwindSafe for TabHistoryEntry",1,["fxa_client::push::TabHistoryEntry"]],["impl UnwindSafe for AccessTokenInfo",1,["fxa_client::token::AccessTokenInfo"]],["impl UnwindSafe for ScopedKey",1,["fxa_client::token::ScopedKey"]],["impl UnwindSafe for AuthorizationParameters",1,["fxa_client::token::AuthorizationParameters"]],["impl !UnwindSafe for FirefoxAccount",1,["fxa_client::FirefoxAccount"]],["impl UnwindSafe for FxaConfig",1,["fxa_client::FxaConfig"]],["impl UnwindSafe for FxaServer",1,["fxa_client::FxaServer"]]], +"interrupt_support":[["impl UnwindSafe for Interrupted",1,["interrupt_support::error::Interrupted"]],["impl UnwindSafe for NeverInterrupts",1,["interrupt_support::interruptee::NeverInterrupts"]],["impl UnwindSafe for ShutdownInterruptee",1,["interrupt_support::shutdown::ShutdownInterruptee"]],["impl UnwindSafe for SqlInterruptHandle",1,["interrupt_support::sql::SqlInterruptHandle"]],["impl UnwindSafe for SqlInterruptScope",1,["interrupt_support::sql::SqlInterruptScope"]]], +"logins":[["impl UnwindSafe for LoginsApiError",1,["logins::error::LoginsApiError"]],["impl !UnwindSafe for Error",1,["logins::error::Error"]],["impl UnwindSafe for InvalidLogin",1,["logins::error::InvalidLogin"]],["impl UnwindSafe for LoginFields",1,["logins::login::LoginFields"]],["impl UnwindSafe for SecureLoginFields",1,["logins::login::SecureLoginFields"]],["impl UnwindSafe for RecordFields",1,["logins::login::RecordFields"]],["impl UnwindSafe for LoginEntry",1,["logins::login::LoginEntry"]],["impl UnwindSafe for Login",1,["logins::login::Login"]],["impl UnwindSafe for EncryptedLogin",1,["logins::login::EncryptedLogin"]],["impl !UnwindSafe for LoginDb",1,["logins::db::LoginDb"]],["impl !UnwindSafe for LoginStore",1,["logins::store::LoginStore"]],["impl !UnwindSafe for LoginsSyncEngine",1,["logins::sync::engine::LoginsSyncEngine"]]], +"nimbus":[["impl UnwindSafe for EnrollmentStatus",1,["nimbus::enrollment::EnrollmentStatus"]],["impl UnwindSafe for EnrolledFeature",1,["nimbus::enrollment::EnrolledFeature"]],["impl UnwindSafe for NimbusTargetingHelper",1,["nimbus::targeting::NimbusTargetingHelper"]],["impl !UnwindSafe for NimbusError",1,["nimbus::error::NimbusError"]],["impl UnwindSafe for BehaviorError",1,["nimbus::error::BehaviorError"]],["impl UnwindSafe for EnrollmentStatusExtraDef",1,["nimbus::metrics::EnrollmentStatusExtraDef"]],["impl UnwindSafe for EnrolledExperiment",1,["nimbus::schema::EnrolledExperiment"]],["impl UnwindSafe for Experiment",1,["nimbus::schema::Experiment"]],["impl UnwindSafe for FeatureConfig",1,["nimbus::schema::FeatureConfig"]],["impl UnwindSafe for Branch",1,["nimbus::schema::Branch"]],["impl UnwindSafe for BucketConfig",1,["nimbus::schema::BucketConfig"]],["impl UnwindSafe for AvailableExperiment",1,["nimbus::schema::AvailableExperiment"]],["impl UnwindSafe for ExperimentBranch",1,["nimbus::schema::ExperimentBranch"]],["impl UnwindSafe for RandomizationUnit",1,["nimbus::schema::RandomizationUnit"]],["impl UnwindSafe for AvailableRandomizationUnits",1,["nimbus::schema::AvailableRandomizationUnits"]],["impl UnwindSafe for Version",1,["nimbus::versioning::Version"]],["impl UnwindSafe for Interval",1,["nimbus::stateful::behavior::Interval"]],["impl UnwindSafe for IntervalConfig",1,["nimbus::stateful::behavior::IntervalConfig"]],["impl UnwindSafe for IntervalData",1,["nimbus::stateful::behavior::IntervalData"]],["impl UnwindSafe for SingleIntervalCounter",1,["nimbus::stateful::behavior::SingleIntervalCounter"]],["impl UnwindSafe for MultiIntervalCounter",1,["nimbus::stateful::behavior::MultiIntervalCounter"]],["impl UnwindSafe for EventQueryType",1,["nimbus::stateful::behavior::EventQueryType"]],["impl UnwindSafe for EventStore",1,["nimbus::stateful::behavior::EventStore"]],["impl UnwindSafe for DatabaseCache",1,["nimbus::stateful::dbcache::DatabaseCache"]],["impl UnwindSafe for TargetingAttributes",1,["nimbus::stateful::evaluator::TargetingAttributes"]],["impl UnwindSafe for AppContext",1,["nimbus::stateful::matcher::AppContext"]],["impl UnwindSafe for InternalMutableState",1,["nimbus::stateful::nimbus_client::InternalMutableState"]],["impl !UnwindSafe for NimbusClient",1,["nimbus::stateful::nimbus_client::NimbusClient"]],["impl UnwindSafe for NimbusStringHelper",1,["nimbus::stateful::nimbus_client::NimbusStringHelper"]],["impl UnwindSafe for StoreId",1,["nimbus::stateful::persistence::StoreId"]],["impl UnwindSafe for SingleStore",1,["nimbus::stateful::persistence::SingleStore"]],["impl UnwindSafe for Database",1,["nimbus::stateful::persistence::Database"]]], +"nimbus_cli":[["impl UnwindSafe for Cli",1,["nimbus_cli::cli::Cli"]],["impl UnwindSafe for CliCommand",1,["nimbus_cli::cli::CliCommand"]],["impl UnwindSafe for ManifestArgs",1,["nimbus_cli::cli::ManifestArgs"]],["impl UnwindSafe for OpenArgs",1,["nimbus_cli::cli::OpenArgs"]],["impl UnwindSafe for ExperimentArgs",1,["nimbus_cli::cli::ExperimentArgs"]],["impl UnwindSafe for ExperimentListArgs",1,["nimbus_cli::cli::ExperimentListArgs"]],["impl UnwindSafe for ExperimentListSourceArgs",1,["nimbus_cli::cli::ExperimentListSourceArgs"]],["impl UnwindSafe for ExperimentListFilterArgs",1,["nimbus_cli::cli::ExperimentListFilterArgs"]],["impl<'a> UnwindSafe for ExperimentInfo<'a>",1,["nimbus_cli::output::info::ExperimentInfo"]],["impl<'a> UnwindSafe for DateRange<'a>",1,["nimbus_cli::output::info::DateRange"]],["impl UnwindSafe for StartAppPostPayload",1,["nimbus_cli::output::server::StartAppPostPayload"]],["impl !UnwindSafe for InMemoryDb",1,["nimbus_cli::output::server::InMemoryDb"]],["impl<'a> UnwindSafe for StartAppProtocol<'a>",1,["nimbus_cli::protocol::StartAppProtocol"]],["impl UnwindSafe for ExperimentSource",1,["nimbus_cli::sources::experiment::ExperimentSource"]],["impl UnwindSafe for FeatureDefaults",1,["nimbus_cli::sources::experiment::FeatureDefaults"]],["impl UnwindSafe for ExperimentListSource",1,["nimbus_cli::sources::experiment_list::ExperimentListSource"]],["impl UnwindSafe for ExperimentListFilter",1,["nimbus_cli::sources::filter::ExperimentListFilter"]],["impl UnwindSafe for ManifestSource",1,["nimbus_cli::sources::manifest::ManifestSource"]],["impl UnwindSafe for Response",1,["nimbus_cli::updater::taskcluster::Response"]],["impl UnwindSafe for TaskClusterRegistry",1,["nimbus_cli::updater::taskcluster::TaskClusterRegistry"]],["impl UnwindSafe for ReqwestGunzippingHttpClient",1,["nimbus_cli::updater::taskcluster::ReqwestGunzippingHttpClient"]],["impl UnwindSafe for LaunchableApp",1,["nimbus_cli::LaunchableApp"]],["impl UnwindSafe for NimbusApp",1,["nimbus_cli::NimbusApp"]],["impl UnwindSafe for AppCommand",1,["nimbus_cli::AppCommand"]],["impl UnwindSafe for AppOpenArgs",1,["nimbus_cli::AppOpenArgs"]]], +"nimbus_fml":[["impl UnwindSafe for ExperimenterFeature",1,["nimbus_fml::backends::experimenter_manifest::ExperimenterFeature"]],["impl UnwindSafe for ExperimenterFeatureProperty",1,["nimbus_fml::backends::experimenter_manifest::ExperimenterFeatureProperty"]],["impl UnwindSafe for ExperimentManifestPropType",1,["nimbus_fml::backends::experimenter_manifest::ExperimentManifestPropType"]],["impl UnwindSafe for TextCodeType",1,["nimbus_fml::backends::kotlin::gen_structs::bundled::TextCodeType"]],["impl UnwindSafe for ImageCodeType",1,["nimbus_fml::backends::kotlin::gen_structs::bundled::ImageCodeType"]],["impl UnwindSafe for EnumCodeType",1,["nimbus_fml::backends::kotlin::gen_structs::enum_::EnumCodeType"]],["impl UnwindSafe for EnumCodeDeclaration",1,["nimbus_fml::backends::kotlin::gen_structs::enum_::EnumCodeDeclaration"]],["impl UnwindSafe for FeatureCodeDeclaration",1,["nimbus_fml::backends::kotlin::gen_structs::feature::FeatureCodeDeclaration"]],["impl<'a> UnwindSafe for ImportedModuleInitialization<'a>",1,["nimbus_fml::backends::kotlin::gen_structs::imports::ImportedModuleInitialization"]],["impl UnwindSafe for ObjectRuntime",1,["nimbus_fml::backends::kotlin::gen_structs::object::ObjectRuntime"]],["impl UnwindSafe for ObjectCodeType",1,["nimbus_fml::backends::kotlin::gen_structs::object::ObjectCodeType"]],["impl UnwindSafe for ObjectCodeDeclaration",1,["nimbus_fml::backends::kotlin::gen_structs::object::ObjectCodeDeclaration"]],["impl UnwindSafe for BooleanCodeType",1,["nimbus_fml::backends::kotlin::gen_structs::primitives::BooleanCodeType"]],["impl UnwindSafe for IntCodeType",1,["nimbus_fml::backends::kotlin::gen_structs::primitives::IntCodeType"]],["impl UnwindSafe for StringCodeType",1,["nimbus_fml::backends::kotlin::gen_structs::primitives::StringCodeType"]],["impl UnwindSafe for OptionalCodeType",1,["nimbus_fml::backends::kotlin::gen_structs::structural::OptionalCodeType"]],["impl UnwindSafe for MapCodeType",1,["nimbus_fml::backends::kotlin::gen_structs::structural::MapCodeType"]],["impl UnwindSafe for ListCodeType",1,["nimbus_fml::backends::kotlin::gen_structs::structural::ListCodeType"]],["impl<'a> UnwindSafe for FeatureManifestDeclaration<'a>",1,["nimbus_fml::backends::kotlin::gen_structs::FeatureManifestDeclaration"]],["impl UnwindSafe for ConcreteCodeOracle",1,["nimbus_fml::backends::kotlin::gen_structs::ConcreteCodeOracle"]],["impl UnwindSafe for TextCodeType",1,["nimbus_fml::backends::swift::gen_structs::bundled::TextCodeType"]],["impl UnwindSafe for ImageCodeType",1,["nimbus_fml::backends::swift::gen_structs::bundled::ImageCodeType"]],["impl UnwindSafe for EnumCodeType",1,["nimbus_fml::backends::swift::gen_structs::enum_::EnumCodeType"]],["impl UnwindSafe for EnumCodeDeclaration",1,["nimbus_fml::backends::swift::gen_structs::enum_::EnumCodeDeclaration"]],["impl UnwindSafe for FeatureCodeDeclaration",1,["nimbus_fml::backends::swift::gen_structs::feature::FeatureCodeDeclaration"]],["impl<'a> UnwindSafe for ImportedModuleInitialization<'a>",1,["nimbus_fml::backends::swift::gen_structs::imports::ImportedModuleInitialization"]],["impl UnwindSafe for ObjectRuntime",1,["nimbus_fml::backends::swift::gen_structs::object::ObjectRuntime"]],["impl UnwindSafe for ObjectCodeType",1,["nimbus_fml::backends::swift::gen_structs::object::ObjectCodeType"]],["impl UnwindSafe for ObjectCodeDeclaration",1,["nimbus_fml::backends::swift::gen_structs::object::ObjectCodeDeclaration"]],["impl UnwindSafe for BooleanCodeType",1,["nimbus_fml::backends::swift::gen_structs::primitives::BooleanCodeType"]],["impl UnwindSafe for IntCodeType",1,["nimbus_fml::backends::swift::gen_structs::primitives::IntCodeType"]],["impl UnwindSafe for StringCodeType",1,["nimbus_fml::backends::swift::gen_structs::primitives::StringCodeType"]],["impl UnwindSafe for OptionalCodeType",1,["nimbus_fml::backends::swift::gen_structs::structural::OptionalCodeType"]],["impl UnwindSafe for MapCodeType",1,["nimbus_fml::backends::swift::gen_structs::structural::MapCodeType"]],["impl UnwindSafe for ListCodeType",1,["nimbus_fml::backends::swift::gen_structs::structural::ListCodeType"]],["impl<'a> UnwindSafe for FeatureManifestDeclaration<'a>",1,["nimbus_fml::backends::swift::gen_structs::FeatureManifestDeclaration"]],["impl UnwindSafe for ConcreteCodeOracle",1,["nimbus_fml::backends::swift::gen_structs::ConcreteCodeOracle"]],["impl UnwindSafe for VariablesType",1,["nimbus_fml::backends::VariablesType"]],["impl UnwindSafe for CliCmd",1,["nimbus_fml::command_line::commands::CliCmd"]],["impl UnwindSafe for GenerateStructCmd",1,["nimbus_fml::command_line::commands::GenerateStructCmd"]],["impl UnwindSafe for GenerateExperimenterManifestCmd",1,["nimbus_fml::command_line::commands::GenerateExperimenterManifestCmd"]],["impl UnwindSafe for GenerateSingleFileManifestCmd",1,["nimbus_fml::command_line::commands::GenerateSingleFileManifestCmd"]],["impl UnwindSafe for ValidateCmd",1,["nimbus_fml::command_line::commands::ValidateCmd"]],["impl UnwindSafe for PrintChannelsCmd",1,["nimbus_fml::command_line::commands::PrintChannelsCmd"]],["impl<'object> UnwindSafe for DefaultsMerger<'object>",1,["nimbus_fml::defaults_merger::DefaultsMerger"]],["impl !UnwindSafe for FMLError",1,["nimbus_fml::error::FMLError"]],["impl UnwindSafe for ClientError",1,["nimbus_fml::error::ClientError"]],["impl UnwindSafe for EnumVariantBody",1,["nimbus_fml::frontend::EnumVariantBody"]],["impl UnwindSafe for EnumBody",1,["nimbus_fml::frontend::EnumBody"]],["impl UnwindSafe for FeatureFieldBody",1,["nimbus_fml::frontend::FeatureFieldBody"]],["impl UnwindSafe for FieldBody",1,["nimbus_fml::frontend::FieldBody"]],["impl UnwindSafe for ObjectBody",1,["nimbus_fml::frontend::ObjectBody"]],["impl UnwindSafe for Types",1,["nimbus_fml::frontend::Types"]],["impl UnwindSafe for AboutBlock",1,["nimbus_fml::frontend::AboutBlock"]],["impl UnwindSafe for SwiftAboutBlock",1,["nimbus_fml::frontend::SwiftAboutBlock"]],["impl UnwindSafe for KotlinAboutBlock",1,["nimbus_fml::frontend::KotlinAboutBlock"]],["impl UnwindSafe for ImportBlock",1,["nimbus_fml::frontend::ImportBlock"]],["impl UnwindSafe for FeatureBody",1,["nimbus_fml::frontend::FeatureBody"]],["impl UnwindSafe for FeatureMetadata",1,["nimbus_fml::frontend::FeatureMetadata"]],["impl UnwindSafe for DocumentationLink",1,["nimbus_fml::frontend::DocumentationLink"]],["impl UnwindSafe for ManifestFrontEnd",1,["nimbus_fml::frontend::ManifestFrontEnd"]],["impl UnwindSafe for DefaultBlock",1,["nimbus_fml::frontend::DefaultBlock"]],["impl UnwindSafe for TargetLanguage",1,["nimbus_fml::intermediate_representation::TargetLanguage"]],["impl UnwindSafe for TypeRef",1,["nimbus_fml::intermediate_representation::TypeRef"]],["impl UnwindSafe for ModuleId",1,["nimbus_fml::intermediate_representation::ModuleId"]],["impl UnwindSafe for FeatureManifest",1,["nimbus_fml::intermediate_representation::FeatureManifest"]],["impl UnwindSafe for FeatureDef",1,["nimbus_fml::intermediate_representation::FeatureDef"]],["impl UnwindSafe for EnumDef",1,["nimbus_fml::intermediate_representation::EnumDef"]],["impl UnwindSafe for FromStringDef",1,["nimbus_fml::intermediate_representation::FromStringDef"]],["impl UnwindSafe for VariantDef",1,["nimbus_fml::intermediate_representation::VariantDef"]],["impl UnwindSafe for ObjectDef",1,["nimbus_fml::intermediate_representation::ObjectDef"]],["impl UnwindSafe for PropDef",1,["nimbus_fml::intermediate_representation::PropDef"]],["impl<'a> UnwindSafe for ImportedModule<'a>",1,["nimbus_fml::intermediate_representation::ImportedModule"]],["impl !UnwindSafe for Parser",1,["nimbus_fml::parser::Parser"]],["impl UnwindSafe for LoaderConfig",1,["nimbus_fml::util::loaders::LoaderConfig"]],["impl UnwindSafe for FilePath",1,["nimbus_fml::util::loaders::FilePath"]],["impl !UnwindSafe for FileLoader",1,["nimbus_fml::util::loaders::FileLoader"]]], +"nss":[["impl UnwindSafe for Operation",1,["nss::aes::Operation"]],["impl UnwindSafe for Curve",1,["nss::ec::Curve"]],["impl UnwindSafe for EcKey",1,["nss::ec::EcKey"]],["impl UnwindSafe for PrivateKey",1,["nss::ec::PrivateKey"]],["impl UnwindSafe for PublicKey",1,["nss::ec::PublicKey"]],["impl UnwindSafe for ErrorKind",1,["nss::error::ErrorKind"]],["impl UnwindSafe for Error",1,["nss::error::Error"]],["impl UnwindSafe for HashAlgorithm",1,["nss::pk11::context::HashAlgorithm"]],["impl UnwindSafe for SymKey",1,["nss::pk11::types::SymKey"]],["impl UnwindSafe for PrivateKey",1,["nss::pk11::types::PrivateKey"]],["impl UnwindSafe for PublicKey",1,["nss::pk11::types::PublicKey"]],["impl UnwindSafe for GenericObject",1,["nss::pk11::types::GenericObject"]],["impl UnwindSafe for Certificate",1,["nss::pk11::types::Certificate"]],["impl UnwindSafe for Context",1,["nss::pk11::types::Context"]],["impl UnwindSafe for Slot",1,["nss::pk11::types::Slot"]],["impl UnwindSafe for AlgorithmID",1,["nss::pk11::types::AlgorithmID"]]], +"nss_build_common":[["impl UnwindSafe for LinkingKind",1,["nss_build_common::LinkingKind"]],["impl UnwindSafe for NoNssDir",1,["nss_build_common::NoNssDir"]]], +"nss_sys":[["impl UnwindSafe for SECKEYPublicKeyStr",1,["nss_sys::bindings::keythi::SECKEYPublicKeyStr"]],["impl UnwindSafe for SECKEYPublicKeyStr_u",1,["nss_sys::bindings::keythi::SECKEYPublicKeyStr_u"]],["impl UnwindSafe for SECKEYPrivateKeyStr",1,["nss_sys::bindings::keythi::SECKEYPrivateKeyStr"]],["impl UnwindSafe for KeyType",1,["nss_sys::bindings::keythi::KeyType"]],["impl UnwindSafe for SECKEYRSAPublicKeyStr",1,["nss_sys::bindings::keythi::SECKEYRSAPublicKeyStr"]],["impl UnwindSafe for SECKEYDSAPublicKeyStr",1,["nss_sys::bindings::keythi::SECKEYDSAPublicKeyStr"]],["impl UnwindSafe for SECKEYPQGParamsStr",1,["nss_sys::bindings::keythi::SECKEYPQGParamsStr"]],["impl UnwindSafe for SECKEYDHPublicKeyStr",1,["nss_sys::bindings::keythi::SECKEYDHPublicKeyStr"]],["impl UnwindSafe for SECKEYKEAPublicKeyStr",1,["nss_sys::bindings::keythi::SECKEYKEAPublicKeyStr"]],["impl UnwindSafe for SECKEYKEAParamsStr",1,["nss_sys::bindings::keythi::SECKEYKEAParamsStr"]],["impl UnwindSafe for SECKEYFortezzaPublicKeyStr",1,["nss_sys::bindings::keythi::SECKEYFortezzaPublicKeyStr"]],["impl UnwindSafe for SECKEYECPublicKeyStr",1,["nss_sys::bindings::keythi::SECKEYECPublicKeyStr"]],["impl UnwindSafe for ECPointEncoding",1,["nss_sys::bindings::keythi::ECPointEncoding"]],["impl UnwindSafe for CK_GCM_PARAMS_V3",1,["nss_sys::bindings::pkcs11n::CK_GCM_PARAMS_V3"]],["impl UnwindSafe for CK_NSS_HKDFParams",1,["nss_sys::bindings::pkcs11n::CK_NSS_HKDFParams"]],["impl UnwindSafe for CK_ATTRIBUTE",1,["nss_sys::bindings::pkcs11t::CK_ATTRIBUTE"]],["impl UnwindSafe for PLArena",1,["nss_sys::bindings::plarena::PLArena"]],["impl UnwindSafe for PLArenaPool",1,["nss_sys::bindings::plarena::PLArenaPool"]],["impl UnwindSafe for SECItemType",1,["nss_sys::bindings::seccomon::SECItemType"]],["impl UnwindSafe for SECItemStr",1,["nss_sys::bindings::seccomon::SECItemStr"]],["impl UnwindSafe for _SECStatus",1,["nss_sys::bindings::seccomon::_SECStatus"]],["impl UnwindSafe for PK11Origin",1,["nss_sys::bindings::secmodt::PK11Origin"]],["impl UnwindSafe for PK11ObjectType",1,["nss_sys::bindings::secmodt::PK11ObjectType"]],["impl UnwindSafe for SECAlgorithmIDStr",1,["nss_sys::bindings::secoidt::SECAlgorithmIDStr"]],["impl UnwindSafe for SECOidDataStr",1,["nss_sys::bindings::secoidt::SECOidDataStr"]],["impl UnwindSafe for SECSupportExtenTag",1,["nss_sys::bindings::secoidt::SECSupportExtenTag"]],["impl UnwindSafe for SECOidTag",1,["nss_sys::bindings::secoidt::SECOidTag"]]], +"places":[["impl UnwindSafe for AddablePlaceInfo",1,["places::api::history::AddablePlaceInfo"]],["impl UnwindSafe for AddableVisit",1,["places::api::history::AddableVisit"]],["impl UnwindSafe for RedirectSourceType",1,["places::api::history::RedirectSourceType"]],["impl UnwindSafe for SearchParams",1,["places::api::matcher::SearchParams"]],["impl UnwindSafe for SearchResult",1,["places::api::matcher::SearchResult"]],["impl UnwindSafe for ConnectionType",1,["places::api::places_api::ConnectionType"]],["impl !UnwindSafe for SyncState",1,["places::api::places_api::SyncState"]],["impl !UnwindSafe for PlacesApi",1,["places::api::places_api::PlacesApi"]],["impl UnwindSafe for PlacesApiError",1,["places::error::PlacesApiError"]],["impl !UnwindSafe for Error",1,["places::error::Error"]],["impl UnwindSafe for InvalidPlaceInfo",1,["places::error::InvalidPlaceInfo"]],["impl UnwindSafe for Corruption",1,["places::error::Corruption"]],["impl UnwindSafe for InvalidMetadataObservation",1,["places::error::InvalidMetadataObservation"]],["impl UnwindSafe for VisitTransitionSet",1,["places::types::visit_transition_set::VisitTransitionSet"]],["impl UnwindSafe for InvalidVisitType",1,["places::types::InvalidVisitType"]],["impl UnwindSafe for VisitType",1,["places::types::VisitType"]],["impl UnwindSafe for BookmarkType",1,["places::types::BookmarkType"]],["impl UnwindSafe for SyncStatus",1,["places::types::SyncStatus"]],["impl !UnwindSafe for BookmarksSyncEngine",1,["places::bookmark_sync::engine::BookmarksSyncEngine"]],["impl UnwindSafe for BookmarkRecordId",1,["places::bookmark_sync::record::BookmarkRecordId"]],["impl UnwindSafe for BookmarkRecord",1,["places::bookmark_sync::record::BookmarkRecord"]],["impl UnwindSafe for QueryRecord",1,["places::bookmark_sync::record::QueryRecord"]],["impl UnwindSafe for FolderRecord",1,["places::bookmark_sync::record::FolderRecord"]],["impl UnwindSafe for LivemarkRecord",1,["places::bookmark_sync::record::LivemarkRecord"]],["impl UnwindSafe for SeparatorRecord",1,["places::bookmark_sync::record::SeparatorRecord"]],["impl UnwindSafe for BookmarkItemRecord",1,["places::bookmark_sync::record::BookmarkItemRecord"]],["impl UnwindSafe for SyncedBookmarkKind",1,["places::bookmark_sync::SyncedBookmarkKind"]],["impl UnwindSafe for SyncedBookmarkValidity",1,["places::bookmark_sync::SyncedBookmarkValidity"]],["impl UnwindSafe for GLOBAL_BOOKMARK_CHANGE_COUNTERS",1,["places::db::db::GLOBAL_BOOKMARK_CHANGE_COUNTERS"]],["impl UnwindSafe for PlacesInitializer",1,["places::db::db::PlacesInitializer"]],["impl !UnwindSafe for PlacesDb",1,["places::db::db::PlacesDb"]],["impl !UnwindSafe for SharedPlacesDb",1,["places::db::db::SharedPlacesDb"]],["impl UnwindSafe for GlobalChangeCounterTracker",1,["places::db::db::GlobalChangeCounterTracker"]],["impl<'conn> !UnwindSafe for PlacesTransaction<'conn>",1,["places::db::tx::PlacesTransaction"]],["impl !UnwindSafe for PlacesConnection",1,["places::ffi::PlacesConnection"]],["impl UnwindSafe for HistoryVisitInfo",1,["places::ffi::HistoryVisitInfo"]],["impl UnwindSafe for HistoryVisitInfosWithBound",1,["places::ffi::HistoryVisitInfosWithBound"]],["impl UnwindSafe for TopFrecentSiteInfo",1,["places::ffi::TopFrecentSiteInfo"]],["impl UnwindSafe for FrecencyThresholdOption",1,["places::ffi::FrecencyThresholdOption"]],["impl UnwindSafe for SearchResult",1,["places::ffi::SearchResult"]],["impl UnwindSafe for Dummy",1,["places::ffi::Dummy"]],["impl UnwindSafe for FrecencySettings",1,["places::frecency::FrecencySettings"]],["impl UnwindSafe for PrefixMode",1,["places::hash::PrefixMode"]],["impl !UnwindSafe for HistorySyncEngine",1,["places::history_sync::engine::HistorySyncEngine"]],["impl UnwindSafe for HistoryRecordVisit",1,["places::history_sync::record::HistoryRecordVisit"]],["impl UnwindSafe for HistoryRecord",1,["places::history_sync::record::HistoryRecord"]],["impl UnwindSafe for ServerVisitTimestamp",1,["places::history_sync::ServerVisitTimestamp"]],["impl UnwindSafe for NOW",1,["places::import::common::NOW"]],["impl<'a> !UnwindSafe for ExecuteOnDrop<'a>",1,["places::import::common::ExecuteOnDrop"]],["impl UnwindSafe for HistoryMigrationResult",1,["places::import::common::HistoryMigrationResult"]],["impl UnwindSafe for MatchBehavior",1,["places::match_impl::MatchBehavior"]],["impl UnwindSafe for SearchBehavior",1,["places::match_impl::SearchBehavior"]],["impl<'search, 'url, 'title, 'tags> UnwindSafe for AutocompleteMatch<'search, 'url, 'title, 'tags>",1,["places::match_impl::AutocompleteMatch"]],["impl UnwindSafe for VisitObservation",1,["places::observation::VisitObservation"]],["impl UnwindSafe for BookmarkData",1,["places::storage::bookmarks::fetch::BookmarkData"]],["impl UnwindSafe for Separator",1,["places::storage::bookmarks::fetch::Separator"]],["impl UnwindSafe for Folder",1,["places::storage::bookmarks::fetch::Folder"]],["impl UnwindSafe for Item",1,["places::storage::bookmarks::fetch::Item"]],["impl UnwindSafe for SEARCH_QUERY",1,["places::storage::bookmarks::fetch::SEARCH_QUERY"]],["impl UnwindSafe for RECENT_BOOKMARKS_QUERY",1,["places::storage::bookmarks::fetch::RECENT_BOOKMARKS_QUERY"]],["impl UnwindSafe for BookmarkNode",1,["places::storage::bookmarks::json_tree::BookmarkNode"]],["impl UnwindSafe for SeparatorNode",1,["places::storage::bookmarks::json_tree::SeparatorNode"]],["impl UnwindSafe for FolderNode",1,["places::storage::bookmarks::json_tree::FolderNode"]],["impl UnwindSafe for BookmarkTreeNode",1,["places::storage::bookmarks::json_tree::BookmarkTreeNode"]],["impl UnwindSafe for FetchDepth",1,["places::storage::bookmarks::json_tree::FetchDepth"]],["impl UnwindSafe for BookmarkRootGuid",1,["places::storage::bookmarks::root_guid::BookmarkRootGuid"]],["impl UnwindSafe for BookmarkPosition",1,["places::storage::bookmarks::BookmarkPosition"]],["impl UnwindSafe for InsertableBookmark",1,["places::storage::bookmarks::InsertableBookmark"]],["impl UnwindSafe for InsertableSeparator",1,["places::storage::bookmarks::InsertableSeparator"]],["impl UnwindSafe for InsertableFolder",1,["places::storage::bookmarks::InsertableFolder"]],["impl UnwindSafe for InsertableItem",1,["places::storage::bookmarks::InsertableItem"]],["impl UnwindSafe for UpdateTreeLocation",1,["places::storage::bookmarks::UpdateTreeLocation"]],["impl UnwindSafe for UpdatableBookmark",1,["places::storage::bookmarks::UpdatableBookmark"]],["impl UnwindSafe for UpdatableSeparator",1,["places::storage::bookmarks::UpdatableSeparator"]],["impl UnwindSafe for UpdatableFolder",1,["places::storage::bookmarks::UpdatableFolder"]],["impl UnwindSafe for UpdatableItem",1,["places::storage::bookmarks::UpdatableItem"]],["impl UnwindSafe for BookmarkUpdateInfo",1,["places::storage::bookmarks::BookmarkUpdateInfo"]],["impl UnwindSafe for FetchedVisit",1,["places::storage::history::history_sync::FetchedVisit"]],["impl UnwindSafe for FetchedVisitPage",1,["places::storage::history::history_sync::FetchedVisitPage"]],["impl UnwindSafe for DocumentType",1,["places::storage::history_metadata::DocumentType"]],["impl UnwindSafe for HistoryHighlightWeights",1,["places::storage::history_metadata::HistoryHighlightWeights"]],["impl UnwindSafe for HistoryHighlight",1,["places::storage::history_metadata::HistoryHighlight"]],["impl UnwindSafe for HistoryMetadataObservation",1,["places::storage::history_metadata::HistoryMetadataObservation"]],["impl UnwindSafe for HistoryMetadata",1,["places::storage::history_metadata::HistoryMetadata"]],["impl<'a> UnwindSafe for ValidatedTag<'a>",1,["places::storage::tags::ValidatedTag"]],["impl UnwindSafe for RowId",1,["places::storage::RowId"]],["impl UnwindSafe for PageInfo",1,["places::storage::PageInfo"]],["impl UnwindSafe for FetchedPageInfo",1,["places::storage::FetchedPageInfo"]],["impl UnwindSafe for RunMaintenanceMetrics",1,["places::storage::RunMaintenanceMetrics"]]], +"protobuf_gen":[["impl UnwindSafe for ProtobufOpts",1,["protobuf_gen::ProtobufOpts"]]], +"push":[["impl UnwindSafe for BridgeType",1,["push::internal::config::BridgeType"]],["impl UnwindSafe for PushConfiguration",1,["push::internal::config::PushConfiguration"]],["impl UnwindSafe for Protocol",1,["push::internal::config::Protocol"]],["impl UnwindSafe for PushApiError",1,["push::error::PushApiError"]],["impl !UnwindSafe for PushError",1,["push::error::PushError"]],["impl UnwindSafe for PushManager",1,["push::PushManager"]],["impl UnwindSafe for KeyInfo",1,["push::KeyInfo"]],["impl UnwindSafe for SubscriptionInfo",1,["push::SubscriptionInfo"]],["impl UnwindSafe for SubscriptionResponse",1,["push::SubscriptionResponse"]],["impl UnwindSafe for PushSubscriptionChanged",1,["push::PushSubscriptionChanged"]]], +"rc_crypto":[["impl<'a> UnwindSafe for Aad<'a>",1,["rc_crypto::aead::Aad"]],["impl UnwindSafe for Nonce",1,["rc_crypto::aead::Nonce"]],["impl UnwindSafe for OpeningKey",1,["rc_crypto::aead::OpeningKey"]],["impl UnwindSafe for SealingKey",1,["rc_crypto::aead::SealingKey"]],["impl UnwindSafe for Algorithm",1,["rc_crypto::aead::Algorithm"]],["impl UnwindSafe for Algorithm",1,["rc_crypto::agreement::Algorithm"]],["impl UnwindSafe for Ephemeral",1,["rc_crypto::agreement::Ephemeral"]],["impl UnwindSafe for Static",1,["rc_crypto::agreement::Static"]],["impl<U> UnwindSafe for KeyPair<U>where\n U: UnwindSafe,",1,["rc_crypto::agreement::KeyPair"]],["impl UnwindSafe for PublicKey",1,["rc_crypto::agreement::PublicKey"]],["impl<'a> UnwindSafe for UnparsedPublicKey<'a>",1,["rc_crypto::agreement::UnparsedPublicKey"]],["impl<U> UnwindSafe for PrivateKey<U>where\n U: UnwindSafe,",1,["rc_crypto::agreement::PrivateKey"]],["impl UnwindSafe for InputKeyMaterial",1,["rc_crypto::agreement::InputKeyMaterial"]],["impl UnwindSafe for Digest",1,["rc_crypto::digest::Digest"]],["impl UnwindSafe for RcCryptoLocalKeyPair",1,["rc_crypto::ece_crypto::RcCryptoLocalKeyPair"]],["impl UnwindSafe for RcCryptoRemotePublicKey",1,["rc_crypto::ece_crypto::RcCryptoRemotePublicKey"]],["impl UnwindSafe for ErrorKind",1,["rc_crypto::error::ErrorKind"]],["impl UnwindSafe for Error",1,["rc_crypto::error::Error"]],["impl UnwindSafe for Signature",1,["rc_crypto::hmac::Signature"]],["impl UnwindSafe for SigningKey",1,["rc_crypto::hmac::SigningKey"]],["impl UnwindSafe for VerificationKey",1,["rc_crypto::hmac::VerificationKey"]],["impl UnwindSafe for VerificationAlgorithm",1,["rc_crypto::signature::VerificationAlgorithm"]],["impl<'a> UnwindSafe for UnparsedPublicKey<'a>",1,["rc_crypto::signature::UnparsedPublicKey"]]], +"rc_log_ffi":[["impl !UnwindSafe for LogAdapterState",1,["rc_log_ffi::android::LogAdapterState"]],["impl UnwindSafe for LogSink",1,["rc_log_ffi::android::LogSink"]],["impl UnwindSafe for LogLevel",1,["rc_log_ffi::LogLevel"]]], +"remote_settings":[["impl !UnwindSafe for RemoteSettingsError",1,["remote_settings::error::RemoteSettingsError"]],["impl UnwindSafe for Client",1,["remote_settings::client::Client"]],["impl UnwindSafe for RemoteSettingsResponse",1,["remote_settings::client::RemoteSettingsResponse"]],["impl UnwindSafe for RemoteSettingsRecord",1,["remote_settings::client::RemoteSettingsRecord"]],["impl UnwindSafe for Attachment",1,["remote_settings::client::Attachment"]],["impl UnwindSafe for GetItemsOptions",1,["remote_settings::client::GetItemsOptions"]],["impl UnwindSafe for SortOrder",1,["remote_settings::client::SortOrder"]],["impl UnwindSafe for RemoteSettingsConfig",1,["remote_settings::config::RemoteSettingsConfig"]],["impl UnwindSafe for RemoteSettings",1,["remote_settings::RemoteSettings"]]], +"sql_support":[["impl !UnwindSafe for Conn",1,["sql_support::conn_ext::Conn"]],["impl<'conn> !UnwindSafe for UncheckedTransaction<'conn>",1,["sql_support::conn_ext::UncheckedTransaction"]],["impl<'conn> !UnwindSafe for MaybeCached<'conn>",1,["sql_support::maybe_cached::MaybeCached"]],["impl<CI> UnwindSafe for MigratedDatabaseFile<CI>where\n CI: UnwindSafe,",1,["sql_support::open_database::test_utils::MigratedDatabaseFile"]],["impl !UnwindSafe for Error",1,["sql_support::open_database::Error"]],["impl<'a, F> UnwindSafe for RepeatDisplay<'a, F>where\n F: UnwindSafe,",1,["sql_support::repeat::RepeatDisplay"]]], +"sync15":[["impl UnwindSafe for IncomingEncryptedBso",1,["sync15::bso::crypto::IncomingEncryptedBso"]],["impl UnwindSafe for OutgoingEncryptedBso",1,["sync15::bso::crypto::OutgoingEncryptedBso"]],["impl UnwindSafe for IncomingEnvelope",1,["sync15::bso::IncomingEnvelope"]],["impl UnwindSafe for OutgoingEnvelope",1,["sync15::bso::OutgoingEnvelope"]],["impl UnwindSafe for IncomingBso",1,["sync15::bso::IncomingBso"]],["impl UnwindSafe for OutgoingBso",1,["sync15::bso::OutgoingBso"]],["impl<T> UnwindSafe for IncomingContent<T>where\n T: UnwindSafe,",1,["sync15::bso::IncomingContent"]],["impl<T> UnwindSafe for IncomingKind<T>where\n T: UnwindSafe,",1,["sync15::bso::IncomingKind"]],["impl UnwindSafe for ServiceStatus",1,["sync15::client::status::ServiceStatus"]],["impl !UnwindSafe for SyncResult",1,["sync15::client::status::SyncResult"]],["impl<T> UnwindSafe for Sync15ClientResponse<T>where\n T: UnwindSafe,",1,["sync15::client::storage_client::Sync15ClientResponse"]],["impl UnwindSafe for Sync15StorageClientInit",1,["sync15::client::storage_client::Sync15StorageClientInit"]],["impl !UnwindSafe for Sync15StorageClient",1,["sync15::client::storage_client::Sync15StorageClient"]],["impl !UnwindSafe for MemoryCachedState",1,["sync15::client::sync_multiple::MemoryCachedState"]],["impl<'a> UnwindSafe for SyncRequestInfo<'a>",1,["sync15::client::sync_multiple::SyncRequestInfo"]],["impl UnwindSafe for DeviceType",1,["sync15::device_type::DeviceType"]],["impl UnwindSafe for ClientData",1,["sync15::client_types::ClientData"]],["impl UnwindSafe for RemoteClient",1,["sync15::client_types::RemoteClient"]],["impl<'a> !UnwindSafe for Engine<'a>",1,["sync15::clients_engine::engine::Engine"]],["impl UnwindSafe for CommandStatus",1,["sync15::clients_engine::CommandStatus"]],["impl UnwindSafe for Settings",1,["sync15::clients_engine::Settings"]],["impl UnwindSafe for Command",1,["sync15::clients_engine::Command"]],["impl UnwindSafe for EncryptedPayload",1,["sync15::enc_payload::EncryptedPayload"]],["impl UnwindSafe for ApplyResults",1,["sync15::engine::bridged_engine::ApplyResults"]],["impl UnwindSafe for CollectionRequest",1,["sync15::engine::request::CollectionRequest"]],["impl UnwindSafe for RequestOrder",1,["sync15::engine::request::RequestOrder"]],["impl UnwindSafe for CollSyncIds",1,["sync15::engine::sync_engine::CollSyncIds"]],["impl UnwindSafe for EngineSyncAssociation",1,["sync15::engine::sync_engine::EngineSyncAssociation"]],["impl UnwindSafe for SyncEngineId",1,["sync15::engine::sync_engine::SyncEngineId"]],["impl !UnwindSafe for Error",1,["sync15::error::Error"]],["impl UnwindSafe for KeyBundle",1,["sync15::key_bundle::KeyBundle"]],["impl UnwindSafe for ServerTimestamp",1,["sync15::server_timestamp::ServerTimestamp"]],["impl UnwindSafe for Event",1,["sync15::telemetry::Event"]],["impl UnwindSafe for SyncFailure",1,["sync15::telemetry::SyncFailure"]],["impl UnwindSafe for EngineIncoming",1,["sync15::telemetry::EngineIncoming"]],["impl UnwindSafe for EngineOutgoing",1,["sync15::telemetry::EngineOutgoing"]],["impl UnwindSafe for Engine",1,["sync15::telemetry::Engine"]],["impl UnwindSafe for Validation",1,["sync15::telemetry::Validation"]],["impl UnwindSafe for Problem",1,["sync15::telemetry::Problem"]],["impl UnwindSafe for SyncTelemetry",1,["sync15::telemetry::SyncTelemetry"]],["impl UnwindSafe for SyncTelemetryPing",1,["sync15::telemetry::SyncTelemetryPing"]]], +"sync_guid":[["impl UnwindSafe for Guid",1,["sync_guid::Guid"]]], +"sync_manager":[["impl !UnwindSafe for SyncManagerError",1,["sync_manager::error::SyncManagerError"]],["impl !UnwindSafe for SyncManager",1,["sync_manager::manager::SyncManager"]],["impl UnwindSafe for SyncParams",1,["sync_manager::types::SyncParams"]],["impl UnwindSafe for SyncReason",1,["sync_manager::types::SyncReason"]],["impl UnwindSafe for SyncEngineSelection",1,["sync_manager::types::SyncEngineSelection"]],["impl UnwindSafe for SyncAuthInfo",1,["sync_manager::types::SyncAuthInfo"]],["impl UnwindSafe for DeviceSettings",1,["sync_manager::types::DeviceSettings"]],["impl UnwindSafe for SyncResult",1,["sync_manager::types::SyncResult"]],["impl UnwindSafe for ServiceStatus",1,["sync_manager::types::ServiceStatus"]]], +"tabs":[["impl UnwindSafe for TabsApiError",1,["tabs::error::TabsApiError"]],["impl !UnwindSafe for Error",1,["tabs::error::Error"]],["impl UnwindSafe for ClientRemoteTabs",1,["tabs::storage::ClientRemoteTabs"]],["impl UnwindSafe for TabsStore",1,["tabs::store::TabsStore"]],["impl !UnwindSafe for TabsBridgedEngine",1,["tabs::sync::bridge::TabsBridgedEngine"]],["impl UnwindSafe for TabsEngine",1,["tabs::sync::engine::TabsEngine"]]], +"types":[["impl UnwindSafe for Timestamp",1,["types::Timestamp"]]], +"viaduct":[["impl UnwindSafe for HeaderName",1,["viaduct::headers::name::HeaderName"]],["impl UnwindSafe for InvalidHeaderName",1,["viaduct::headers::name::InvalidHeaderName"]],["impl UnwindSafe for Header",1,["viaduct::headers::Header"]],["impl UnwindSafe for Headers",1,["viaduct::headers::Headers"]],["impl UnwindSafe for Error",1,["viaduct::error::Error"]],["impl UnwindSafe for UnexpectedStatus",1,["viaduct::error::UnexpectedStatus"]],["impl UnwindSafe for Settings",1,["viaduct::settings::Settings"]],["impl UnwindSafe for Method",1,["viaduct::Method"]],["impl UnwindSafe for Request",1,["viaduct::Request"]],["impl UnwindSafe for Response",1,["viaduct::Response"]]], +"viaduct_reqwest":[["impl UnwindSafe for ReqwestBackend",1,["viaduct_reqwest::ReqwestBackend"]]], +"webext_storage":[["impl UnwindSafe for UsageInfo",1,["webext_storage::api::UsageInfo"]],["impl UnwindSafe for QuotaReason",1,["webext_storage::error::QuotaReason"]],["impl !UnwindSafe for ErrorKind",1,["webext_storage::error::ErrorKind"]],["impl !UnwindSafe for Error",1,["webext_storage::error::Error"]],["impl UnwindSafe for MigrationInfo",1,["webext_storage::migration::MigrationInfo"]],["impl !UnwindSafe for Store",1,["webext_storage::store::Store"]]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/core/str/traits/trait.FromStr.js b/book/rust-docs/implementors/core/str/traits/trait.FromStr.js new file mode 100644 index 0000000000..ec4e0af89f --- /dev/null +++ b/book/rust-docs/implementors/core/str/traits/trait.FromStr.js @@ -0,0 +1,5 @@ +(function() {var implementors = { +"nimbus":[["impl FromStr for Interval"]], +"push":[["impl FromStr for Protocol"]], +"sync15":[["impl FromStr for ServerTimestamp"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/ece/crypto/trait.LocalKeyPair.js b/book/rust-docs/implementors/ece/crypto/trait.LocalKeyPair.js new file mode 100644 index 0000000000..a94d94cac0 --- /dev/null +++ b/book/rust-docs/implementors/ece/crypto/trait.LocalKeyPair.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"rc_crypto":[["impl LocalKeyPair for RcCryptoLocalKeyPair"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/ece/crypto/trait.RemotePublicKey.js b/book/rust-docs/implementors/ece/crypto/trait.RemotePublicKey.js new file mode 100644 index 0000000000..d42cbd22ec --- /dev/null +++ b/book/rust-docs/implementors/ece/crypto/trait.RemotePublicKey.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"rc_crypto":[["impl RemotePublicKey for RcCryptoRemotePublicKey"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/error_support/handling/trait.GetErrorHandling.js b/book/rust-docs/implementors/error_support/handling/trait.GetErrorHandling.js new file mode 100644 index 0000000000..68c8ec1b6c --- /dev/null +++ b/book/rust-docs/implementors/error_support/handling/trait.GetErrorHandling.js @@ -0,0 +1,8 @@ +(function() {var implementors = { +"autofill":[["impl GetErrorHandling for Error"]], +"fxa_client":[["impl GetErrorHandling for Error"]], +"logins":[["impl GetErrorHandling for Error"]], +"places":[["impl GetErrorHandling for Error"]], +"push":[["impl GetErrorHandling for PushError"]], +"tabs":[["impl GetErrorHandling for Error"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/ffi_support/into_ffi/trait.IntoFfi.js b/book/rust-docs/implementors/ffi_support/into_ffi/trait.IntoFfi.js new file mode 100644 index 0000000000..fb1fd35889 --- /dev/null +++ b/book/rust-docs/implementors/ffi_support/into_ffi/trait.IntoFfi.js @@ -0,0 +1,4 @@ +(function() {var implementors = { +"rc_log_ffi":[["impl IntoFfi for LogAdapterStatewhere\n LogAdapterState: Send,"]], +"sync15":[["impl IntoFfi for SyncTelemetryPingwhere\n SyncTelemetryPing: Serialize,"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/hawk/crypto/trait.HmacKey.js b/book/rust-docs/implementors/hawk/crypto/trait.HmacKey.js new file mode 100644 index 0000000000..163cba1a56 --- /dev/null +++ b/book/rust-docs/implementors/hawk/crypto/trait.HmacKey.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"rc_crypto":[["impl HmacKey for SigningKey"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/interrupt_support/interruptee/trait.Interruptee.js b/book/rust-docs/implementors/interrupt_support/interruptee/trait.Interruptee.js new file mode 100644 index 0000000000..30734c4e14 --- /dev/null +++ b/book/rust-docs/implementors/interrupt_support/interruptee/trait.Interruptee.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"interrupt_support":[] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/lazy_static/trait.LazyStatic.js b/book/rust-docs/implementors/lazy_static/trait.LazyStatic.js new file mode 100644 index 0000000000..84e8d55a6c --- /dev/null +++ b/book/rust-docs/implementors/lazy_static/trait.LazyStatic.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"places":[["impl LazyStatic for RECENT_BOOKMARKS_QUERY"],["impl LazyStatic for GLOBAL_BOOKMARK_CHANGE_COUNTERS"],["impl LazyStatic for SEARCH_QUERY"],["impl LazyStatic for NOW"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/log/trait.Log.js b/book/rust-docs/implementors/log/trait.Log.js new file mode 100644 index 0000000000..b9a1330f14 --- /dev/null +++ b/book/rust-docs/implementors/log/trait.Log.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"rc_log_ffi":[["impl Log for LogSink"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/logins/login/trait.ValidateAndFixup.js b/book/rust-docs/implementors/logins/login/trait.ValidateAndFixup.js new file mode 100644 index 0000000000..26b555af9a --- /dev/null +++ b/book/rust-docs/implementors/logins/login/trait.ValidateAndFixup.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"logins":[] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/nimbus/stateful/persistence/backend/trait.Readable.js b/book/rust-docs/implementors/nimbus/stateful/persistence/backend/trait.Readable.js new file mode 100644 index 0000000000..06390e410b --- /dev/null +++ b/book/rust-docs/implementors/nimbus/stateful/persistence/backend/trait.Readable.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"nimbus":[] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/nimbus_cli/value_utils/trait.CliUtils.js b/book/rust-docs/implementors/nimbus_cli/value_utils/trait.CliUtils.js new file mode 100644 index 0000000000..c08b0a4170 --- /dev/null +++ b/book/rust-docs/implementors/nimbus_cli/value_utils/trait.CliUtils.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"nimbus_cli":[] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/nimbus_cli/value_utils/trait.Patch.js b/book/rust-docs/implementors/nimbus_cli/value_utils/trait.Patch.js new file mode 100644 index 0000000000..c08b0a4170 --- /dev/null +++ b/book/rust-docs/implementors/nimbus_cli/value_utils/trait.Patch.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"nimbus_cli":[] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/nimbus_fml/backends/trait.CodeDeclaration.js b/book/rust-docs/implementors/nimbus_fml/backends/trait.CodeDeclaration.js new file mode 100644 index 0000000000..c61225999f --- /dev/null +++ b/book/rust-docs/implementors/nimbus_fml/backends/trait.CodeDeclaration.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"nimbus_fml":[] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/nimbus_fml/backends/trait.CodeOracle.js b/book/rust-docs/implementors/nimbus_fml/backends/trait.CodeOracle.js new file mode 100644 index 0000000000..c61225999f --- /dev/null +++ b/book/rust-docs/implementors/nimbus_fml/backends/trait.CodeOracle.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"nimbus_fml":[] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/nimbus_fml/backends/trait.CodeType.js b/book/rust-docs/implementors/nimbus_fml/backends/trait.CodeType.js new file mode 100644 index 0000000000..c61225999f --- /dev/null +++ b/book/rust-docs/implementors/nimbus_fml/backends/trait.CodeType.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"nimbus_fml":[] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/nimbus_fml/backends/trait.LiteralRenderer.js b/book/rust-docs/implementors/nimbus_fml/backends/trait.LiteralRenderer.js new file mode 100644 index 0000000000..c61225999f --- /dev/null +++ b/book/rust-docs/implementors/nimbus_fml/backends/trait.LiteralRenderer.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"nimbus_fml":[] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/nimbus_fml/intermediate_representation/trait.TypeFinder.js b/book/rust-docs/implementors/nimbus_fml/intermediate_representation/trait.TypeFinder.js new file mode 100644 index 0000000000..c61225999f --- /dev/null +++ b/book/rust-docs/implementors/nimbus_fml/intermediate_representation/trait.TypeFinder.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"nimbus_fml":[] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/rc_crypto/agreement/trait.Lifetime.js b/book/rust-docs/implementors/rc_crypto/agreement/trait.Lifetime.js new file mode 100644 index 0000000000..10f7bfd33d --- /dev/null +++ b/book/rust-docs/implementors/rc_crypto/agreement/trait.Lifetime.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"rc_crypto":[] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/rusqlite/types/from_sql/trait.FromSql.js b/book/rust-docs/implementors/rusqlite/types/from_sql/trait.FromSql.js new file mode 100644 index 0000000000..ec3984df0d --- /dev/null +++ b/book/rust-docs/implementors/rusqlite/types/from_sql/trait.FromSql.js @@ -0,0 +1,5 @@ +(function() {var implementors = { +"places":[["impl FromSql for SyncStatus"],["impl FromSql for DocumentType"],["impl FromSql for BookmarkType"],["impl FromSql for SearchBehavior"],["impl FromSql for MatchBehavior"],["impl FromSql for RowId"]], +"sync_guid":[["impl FromSql for Guid"]], +"types":[["impl FromSql for Timestamp"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/rusqlite/types/to_sql/trait.ToSql.js b/book/rust-docs/implementors/rusqlite/types/to_sql/trait.ToSql.js new file mode 100644 index 0000000000..08ec37e0a1 --- /dev/null +++ b/book/rust-docs/implementors/rusqlite/types/to_sql/trait.ToSql.js @@ -0,0 +1,5 @@ +(function() {var implementors = { +"places":[["impl ToSql for MatchBehavior"],["impl ToSql for BookmarkType"],["impl ToSql for VisitTransitionSet"],["impl ToSql for RowId"],["impl ToSql for SyncedBookmarkKind"],["impl ToSql for SyncStatus"],["impl ToSql for VisitType"],["impl ToSql for DocumentType"],["impl ToSql for SearchBehavior"],["impl ToSql for SyncedBookmarkValidity"]], +"sync_guid":[["impl ToSql for Guid"]], +"types":[["impl ToSql for Timestamp"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/serde/de/trait.Deserialize.js b/book/rust-docs/implementors/serde/de/trait.Deserialize.js new file mode 100644 index 0000000000..68774b69e3 --- /dev/null +++ b/book/rust-docs/implementors/serde/de/trait.Deserialize.js @@ -0,0 +1,17 @@ +(function() {var implementors = { +"autofill":[["impl<'de> Deserialize<'de> for AddressPayload"]], +"fxa_client":[["impl<'de> Deserialize<'de> for ScopedKey"],["impl<'de> Deserialize<'de> for DeviceCapability"],["impl<'de> Deserialize<'de> for DevicePushSubscription"],["impl<'de> Deserialize<'de> for LocalDevice"]], +"logins":[["impl<'de> Deserialize<'de> for SecureLoginFields"]], +"nimbus":[["impl<'de> Deserialize<'de> for TargetingAttributes"],["impl<'de> Deserialize<'de> for FeatureConfig"],["impl<'de> Deserialize<'de> for Interval"],["impl<'de> Deserialize<'de> for SingleIntervalCounter"],["impl<'de> Deserialize<'de> for EventStore"],["impl<'de> Deserialize<'de> for IntervalData"],["impl<'de> Deserialize<'de> for MultiIntervalCounter"],["impl<'de> Deserialize<'de> for BucketConfig"],["impl<'de> Deserialize<'de> for RandomizationUnit"],["impl<'de> Deserialize<'de> for EnrollmentStatusExtraDef"],["impl<'de> Deserialize<'de> for AppContext"],["impl<'de> Deserialize<'de> for IntervalConfig"],["impl<'de> Deserialize<'de> for EnrollmentStatus"],["impl<'de> Deserialize<'de> for Branch"],["impl<'de> Deserialize<'de> for Experiment"]], +"nimbus_cli":[["impl<'de> Deserialize<'de> for StartAppPostPayload"],["impl<'de> Deserialize<'de> for FeatureDefaults"],["impl<'de> Deserialize<'de> for Response"]], +"nimbus_fml":[["impl<'de> Deserialize<'de> for VariantDef"],["impl<'de> Deserialize<'de> for DocumentationLink"],["impl<'de> Deserialize<'de> for FieldBody"],["impl<'de> Deserialize<'de> for FeatureDef"],["impl<'de> Deserialize<'de> for FeatureManifest"],["impl<'de> Deserialize<'de> for AboutBlock"],["impl<'de> Deserialize<'de> for Types"],["impl<'de> Deserialize<'de> for ExperimenterFeature"],["impl<'de> Deserialize<'de> for TypeRef"],["impl<'de> Deserialize<'de> for ManifestFrontEnd"],["impl<'de> Deserialize<'de> for FeatureBody"],["impl<'de> Deserialize<'de> for PropDef"],["impl<'de> Deserialize<'de> for SwiftAboutBlock"],["impl<'de> Deserialize<'de> for KotlinAboutBlock"],["impl<'de> Deserialize<'de> for ImportBlock"],["impl<'de> Deserialize<'de> for ObjectDef"],["impl<'de> Deserialize<'de> for ExperimenterFeatureProperty"],["impl<'de> Deserialize<'de> for EnumBody"],["impl<'de> Deserialize<'de> for EnumVariantBody"],["impl<'de> Deserialize<'de> for FromStringDef"],["impl<'de> Deserialize<'de> for ModuleId"],["impl<'de> Deserialize<'de> for ObjectBody"],["impl<'de> Deserialize<'de> for FeatureMetadata"],["impl<'de> Deserialize<'de> for EnumDef"],["impl<'de> Deserialize<'de> for DefaultBlock"],["impl<'de> Deserialize<'de> for FeatureFieldBody"]], +"nss":[["impl<'de> Deserialize<'de> for Curve"],["impl<'de> Deserialize<'de> for EcKey"]], +"places":[["impl<'de> Deserialize<'de> for SeparatorRecord"],["impl<'de> Deserialize<'de> for BookmarkTreeNode"],["impl<'de> Deserialize<'de> for VisitType"],["impl<'de> Deserialize<'de> for QueryRecord"],["impl<'de> Deserialize<'de> for ServerVisitTimestamp"],["impl<'de> Deserialize<'de> for BookmarkRecordId"],["impl<'de> Deserialize<'de> for HistoryRecordVisit"],["impl<'de> Deserialize<'de> for LivemarkRecord"],["impl<'de> Deserialize<'de> for HistoryRecord"],["impl<'de> Deserialize<'de> for FolderRecord"],["impl<'de> Deserialize<'de> for RowId"],["impl<'de> Deserialize<'de> for BookmarkRecord"],["impl<'de> Deserialize<'de> for BookmarkItemRecord"]], +"protobuf_gen":[["impl<'de> Deserialize<'de> for ProtobufOpts"]], +"remote_settings":[["impl<'de> Deserialize<'de> for RemoteSettingsRecord"],["impl<'de> Deserialize<'de> for Attachment"]], +"sync15":[["impl<'de> Deserialize<'de> for RemoteClient"],["impl<'de> Deserialize<'de> for IncomingEnvelope"],["impl<'de> Deserialize<'de> for IncomingBso"],["impl<'de> Deserialize<'de> for IncomingEncryptedBsowhere\n EncryptedPayload: DeserializeOwned,"],["impl<'de> Deserialize<'de> for ServerTimestamp"],["impl<'de> Deserialize<'de> for DeviceType"],["impl<'de> Deserialize<'de> for EncryptedPayload"],["impl<'de> Deserialize<'de> for ClientData"]], +"sync_guid":[["impl<'de> Deserialize<'de> for Guid"]], +"tabs":[["impl<'de> Deserialize<'de> for ClientRemoteTabs"]], +"types":[["impl<'de> Deserialize<'de> for Timestamp"]], +"webext_storage":[["impl<'de> Deserialize<'de> for MigrationInfo"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/serde/ser/trait.Serialize.js b/book/rust-docs/implementors/serde/ser/trait.Serialize.js new file mode 100644 index 0000000000..edfd301749 --- /dev/null +++ b/book/rust-docs/implementors/serde/ser/trait.Serialize.js @@ -0,0 +1,15 @@ +(function() {var implementors = { +"autofill":[["impl Serialize for AddressPayload"]], +"fxa_client":[["impl Serialize for ScopedKey"],["impl Serialize for DevicePushSubscription"],["impl Serialize for LocalDevice"],["impl Serialize for DeviceCapability"]], +"logins":[["impl Serialize for SecureLoginFields"]], +"nimbus":[["impl Serialize for Interval"],["impl Serialize for FeatureConfig"],["impl Serialize for EnrollmentStatus"],["impl Serialize for AppContext"],["impl Serialize for RandomizationUnit"],["impl Serialize for MultiIntervalCounter"],["impl Serialize for IntervalData"],["impl Serialize for SingleIntervalCounter"],["impl Serialize for EventStore"],["impl Serialize for Experiment"],["impl Serialize for Branch"],["impl Serialize for EnrollmentStatusExtraDef"],["impl Serialize for TargetingAttributes"],["impl Serialize for BucketConfig"],["impl Serialize for IntervalConfig"]], +"nimbus_cli":[["impl<'a> Serialize for DateRange<'a>"],["impl<'a> Serialize for ExperimentInfo<'a>"],["impl Serialize for StartAppPostPayload"],["impl Serialize for FeatureDefaults"]], +"nimbus_fml":[["impl Serialize for FeatureBody"],["impl Serialize for KotlinAboutBlock"],["impl Serialize for FeatureManifest"],["impl Serialize for FieldBody"],["impl Serialize for AboutBlock"],["impl Serialize for SwiftAboutBlock"],["impl Serialize for ExperimenterFeatureProperty"],["impl Serialize for ImportBlock"],["impl Serialize for ManifestFrontEnd"],["impl Serialize for FeatureMetadata"],["impl Serialize for Types"],["impl Serialize for DefaultBlock"],["impl Serialize for FeatureDef"],["impl Serialize for FromStringDef"],["impl Serialize for ObjectBody"],["impl Serialize for PropDef"],["impl Serialize for ExperimenterFeature"],["impl Serialize for EnumBody"],["impl Serialize for ModuleId"],["impl Serialize for EnumVariantBody"],["impl Serialize for VariantDef"],["impl Serialize for FeatureFieldBody"],["impl Serialize for TypeRef"],["impl Serialize for ObjectDef"],["impl Serialize for EnumDef"],["impl Serialize for DocumentationLink"]], +"nss":[["impl Serialize for EcKey"],["impl Serialize for Curve"]], +"places":[["impl Serialize for HistoryRecord"],["impl Serialize for SearchResult"],["impl Serialize for VisitType"],["impl Serialize for FolderRecord"],["impl Serialize for QueryRecord"],["impl Serialize for BookmarkRecord"],["impl Serialize for ServerVisitTimestamp"],["impl Serialize for HistoryMigrationResult"],["impl Serialize for BookmarkRecordId"],["impl Serialize for LivemarkRecord"],["impl Serialize for BookmarkTreeNode"],["impl Serialize for SeparatorRecord"],["impl Serialize for RowId"],["impl Serialize for BookmarkItemRecord"],["impl Serialize for BookmarkType"],["impl Serialize for HistoryRecordVisit"]], +"sync15":[["impl Serialize for SyncTelemetryPing"],["impl Serialize for SyncTelemetry"],["impl Serialize for ServerTimestamp"],["impl Serialize for OutgoingEncryptedBsowhere\n EncryptedPayload: Serialize,"],["impl Serialize for EncryptedPayload"],["impl Serialize for Engine"],["impl Serialize for RemoteClient"],["impl Serialize for Event"],["impl Serialize for OutgoingEnvelope"],["impl Serialize for EngineIncoming"],["impl Serialize for OutgoingBso"],["impl Serialize for ClientData"],["impl Serialize for SyncFailure"],["impl Serialize for EngineOutgoing"],["impl Serialize for Validation"],["impl Serialize for Problem"],["impl Serialize for DeviceType"]], +"sync_guid":[["impl Serialize for Guid"]], +"tabs":[["impl Serialize for ClientRemoteTabs"]], +"types":[["impl Serialize for Timestamp"]], +"webext_storage":[["impl Serialize for MigrationInfo"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/sql_support/conn_ext/trait.ConnExt.js b/book/rust-docs/implementors/sql_support/conn_ext/trait.ConnExt.js new file mode 100644 index 0000000000..2ac539f03e --- /dev/null +++ b/book/rust-docs/implementors/sql_support/conn_ext/trait.ConnExt.js @@ -0,0 +1,5 @@ +(function() {var implementors = { +"logins":[["impl ConnExt for LoginDb"]], +"places":[["impl<'conn> ConnExt for PlacesTransaction<'conn>"],["impl ConnExt for PlacesDb"]], +"sql_support":[] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/sql_support/open_database/trait.ConnectionInitializer.js b/book/rust-docs/implementors/sql_support/open_database/trait.ConnectionInitializer.js new file mode 100644 index 0000000000..93eb769b92 --- /dev/null +++ b/book/rust-docs/implementors/sql_support/open_database/trait.ConnectionInitializer.js @@ -0,0 +1,4 @@ +(function() {var implementors = { +"autofill":[["impl ConnectionInitializer for AutofillConnectionInitializer"]], +"places":[["impl ConnectionInitializer for PlacesInitializer"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/sync15/client/storage_client/trait.SetupStorageClient.js b/book/rust-docs/implementors/sync15/client/storage_client/trait.SetupStorageClient.js new file mode 100644 index 0000000000..be1d8b4f0a --- /dev/null +++ b/book/rust-docs/implementors/sync15/client/storage_client/trait.SetupStorageClient.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"sync15":[] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/sync15/engine/bridged_engine/trait.BridgedEngine.js b/book/rust-docs/implementors/sync15/engine/bridged_engine/trait.BridgedEngine.js new file mode 100644 index 0000000000..be1d8b4f0a --- /dev/null +++ b/book/rust-docs/implementors/sync15/engine/bridged_engine/trait.BridgedEngine.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"sync15":[] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/sync15/engine/sync_engine/trait.SyncEngine.js b/book/rust-docs/implementors/sync15/engine/sync_engine/trait.SyncEngine.js new file mode 100644 index 0000000000..8abc2bbd5e --- /dev/null +++ b/book/rust-docs/implementors/sync15/engine/sync_engine/trait.SyncEngine.js @@ -0,0 +1,6 @@ +(function() {var implementors = { +"autofill":[["impl<T: SyncRecord + Debug> SyncEngine for ConfigSyncEngine<T>"]], +"logins":[["impl SyncEngine for LoginsSyncEngine"]], +"places":[["impl SyncEngine for BookmarksSyncEngine"],["impl SyncEngine for HistorySyncEngine"]], +"tabs":[["impl SyncEngine for TabsEngine"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/update_informer/http_client/trait.HttpClient.js b/book/rust-docs/implementors/update_informer/http_client/trait.HttpClient.js new file mode 100644 index 0000000000..b2ae0b9ee3 --- /dev/null +++ b/book/rust-docs/implementors/update_informer/http_client/trait.HttpClient.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"nimbus_cli":[["impl HttpClient for ReqwestGunzippingHttpClient"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/update_informer/registry/trait.Registry.js b/book/rust-docs/implementors/update_informer/registry/trait.Registry.js new file mode 100644 index 0000000000..79c02aa993 --- /dev/null +++ b/book/rust-docs/implementors/update_informer/registry/trait.Registry.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"nimbus_cli":[["impl Registry for TaskClusterRegistry"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/implementors/viaduct/backend/trait.Backend.js b/book/rust-docs/implementors/viaduct/backend/trait.Backend.js new file mode 100644 index 0000000000..07d46f82f2 --- /dev/null +++ b/book/rust-docs/implementors/viaduct/backend/trait.Backend.js @@ -0,0 +1,3 @@ +(function() {var implementors = { +"viaduct_reqwest":[["impl Backend for ReqwestBackend"]] +};if (window.register_implementors) {window.register_implementors(implementors);} else {window.pending_implementors = implementors;}})() \ No newline at end of file diff --git a/book/rust-docs/index.html b/book/rust-docs/index.html new file mode 100644 index 0000000000..2e8ea06749 --- /dev/null +++ b/book/rust-docs/index.html @@ -0,0 +1 @@ + diff --git a/book/rust-docs/interrupt_support/all.html b/book/rust-docs/interrupt_support/all.html new file mode 100644 index 0000000000..e196be2b34 --- /dev/null +++ b/book/rust-docs/interrupt_support/all.html @@ -0,0 +1 @@ +List of all items in this crate
\ No newline at end of file diff --git a/book/rust-docs/interrupt_support/error/struct.Interrupted.html b/book/rust-docs/interrupt_support/error/struct.Interrupted.html new file mode 100644 index 0000000000..692f9d6b0a --- /dev/null +++ b/book/rust-docs/interrupt_support/error/struct.Interrupted.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../interrupt_support/struct.Interrupted.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/interrupt_support/fn.in_shutdown.html b/book/rust-docs/interrupt_support/fn.in_shutdown.html new file mode 100644 index 0000000000..6f22a82fb3 --- /dev/null +++ b/book/rust-docs/interrupt_support/fn.in_shutdown.html @@ -0,0 +1,2 @@ +in_shutdown in interrupt_support - Rust
pub fn in_shutdown() -> bool
Expand description

Check if we’re currently in shutdown mode

+
\ No newline at end of file diff --git a/book/rust-docs/interrupt_support/fn.register_interrupt.html b/book/rust-docs/interrupt_support/fn.register_interrupt.html new file mode 100644 index 0000000000..23aa4dcdaf --- /dev/null +++ b/book/rust-docs/interrupt_support/fn.register_interrupt.html @@ -0,0 +1,6 @@ +register_interrupt in interrupt_support - Rust
pub fn register_interrupt(
+    interrupt: Weak<dyn AsRef<SqlInterruptHandle> + Send + Sync>
+)
Expand description

Register a ShutdownInterrupt implementation

+

Call this function to ensure that the SqlInterruptHandle::interrupt() method will be called +at shutdown.

+
\ No newline at end of file diff --git a/book/rust-docs/interrupt_support/fn.shutdown.html b/book/rust-docs/interrupt_support/fn.shutdown.html new file mode 100644 index 0000000000..2bc6094ffb --- /dev/null +++ b/book/rust-docs/interrupt_support/fn.shutdown.html @@ -0,0 +1,2 @@ +shutdown in interrupt_support - Rust
pub fn shutdown()
Expand description

Initiate shutdown mode

+
\ No newline at end of file diff --git a/book/rust-docs/interrupt_support/index.html b/book/rust-docs/interrupt_support/index.html new file mode 100644 index 0000000000..580fd5e8f3 --- /dev/null +++ b/book/rust-docs/interrupt_support/index.html @@ -0,0 +1,3 @@ +interrupt_support - Rust

Structs

Traits

  • Represents the state of something that may be interrupted. Decoupled from +the interrupt mechanics so that things which want to check if they have been +interrupted are simpler.

Functions

\ No newline at end of file diff --git a/book/rust-docs/interrupt_support/interruptee/struct.NeverInterrupts.html b/book/rust-docs/interrupt_support/interruptee/struct.NeverInterrupts.html new file mode 100644 index 0000000000..7b8bb3b5f7 --- /dev/null +++ b/book/rust-docs/interrupt_support/interruptee/struct.NeverInterrupts.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../interrupt_support/struct.NeverInterrupts.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/interrupt_support/interruptee/trait.Interruptee.html b/book/rust-docs/interrupt_support/interruptee/trait.Interruptee.html new file mode 100644 index 0000000000..892843a4fa --- /dev/null +++ b/book/rust-docs/interrupt_support/interruptee/trait.Interruptee.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../interrupt_support/trait.Interruptee.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/interrupt_support/shutdown/fn.in_shutdown.html b/book/rust-docs/interrupt_support/shutdown/fn.in_shutdown.html new file mode 100644 index 0000000000..8d919e6207 --- /dev/null +++ b/book/rust-docs/interrupt_support/shutdown/fn.in_shutdown.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../interrupt_support/fn.in_shutdown.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/interrupt_support/shutdown/fn.register_interrupt.html b/book/rust-docs/interrupt_support/shutdown/fn.register_interrupt.html new file mode 100644 index 0000000000..40982d6028 --- /dev/null +++ b/book/rust-docs/interrupt_support/shutdown/fn.register_interrupt.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../interrupt_support/fn.register_interrupt.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/interrupt_support/shutdown/fn.shutdown.html b/book/rust-docs/interrupt_support/shutdown/fn.shutdown.html new file mode 100644 index 0000000000..b1f59974b5 --- /dev/null +++ b/book/rust-docs/interrupt_support/shutdown/fn.shutdown.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../interrupt_support/fn.shutdown.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/interrupt_support/shutdown/struct.ShutdownInterruptee.html b/book/rust-docs/interrupt_support/shutdown/struct.ShutdownInterruptee.html new file mode 100644 index 0000000000..a1b521c6d0 --- /dev/null +++ b/book/rust-docs/interrupt_support/shutdown/struct.ShutdownInterruptee.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../interrupt_support/struct.ShutdownInterruptee.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/interrupt_support/sidebar-items.js b/book/rust-docs/interrupt_support/sidebar-items.js new file mode 100644 index 0000000000..1f0a6e1118 --- /dev/null +++ b/book/rust-docs/interrupt_support/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["in_shutdown","register_interrupt","shutdown"],"struct":["Interrupted","NeverInterrupts","ShutdownInterruptee","SqlInterruptHandle","SqlInterruptScope"],"trait":["Interruptee"]}; \ No newline at end of file diff --git a/book/rust-docs/interrupt_support/sql/struct.SqlInterruptHandle.html b/book/rust-docs/interrupt_support/sql/struct.SqlInterruptHandle.html new file mode 100644 index 0000000000..f7df58f95e --- /dev/null +++ b/book/rust-docs/interrupt_support/sql/struct.SqlInterruptHandle.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../interrupt_support/struct.SqlInterruptHandle.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/interrupt_support/sql/struct.SqlInterruptScope.html b/book/rust-docs/interrupt_support/sql/struct.SqlInterruptScope.html new file mode 100644 index 0000000000..c6ec48210f --- /dev/null +++ b/book/rust-docs/interrupt_support/sql/struct.SqlInterruptScope.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../interrupt_support/struct.SqlInterruptScope.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/interrupt_support/struct.Interrupted.html b/book/rust-docs/interrupt_support/struct.Interrupted.html new file mode 100644 index 0000000000..d4ba498ec5 --- /dev/null +++ b/book/rust-docs/interrupt_support/struct.Interrupted.html @@ -0,0 +1,16 @@ +Interrupted in interrupt_support - Rust
pub struct Interrupted;
Expand description

The error returned by err_if_interrupted.

+

Trait Implementations§

source§

impl Clone for Interrupted

source§

fn clone(&self) -> Interrupted

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for Interrupted

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Display for Interrupted

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Error for Interrupted

1.30.0 · source§

fn source(&self) -> Option<&(dyn Error + 'static)>

The lower-level source of this error, if any. Read more
1.0.0 · source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type based access to context intended for error reports. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<E> Provider for Ewhere + E: Error + ?Sized,

source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (provide_any)
Data providers should implement this method to provide all values they are able to +provide by using demand. Read more
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/interrupt_support/struct.NeverInterrupts.html b/book/rust-docs/interrupt_support/struct.NeverInterrupts.html new file mode 100644 index 0000000000..2f06053332 --- /dev/null +++ b/book/rust-docs/interrupt_support/struct.NeverInterrupts.html @@ -0,0 +1,12 @@ +NeverInterrupts in interrupt_support - Rust
pub struct NeverInterrupts;
Expand description

A convenience implementation, should only be used in tests.

+

Trait Implementations§

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/interrupt_support/struct.ShutdownInterruptee.html b/book/rust-docs/interrupt_support/struct.ShutdownInterruptee.html new file mode 100644 index 0000000000..2343d45a35 --- /dev/null +++ b/book/rust-docs/interrupt_support/struct.ShutdownInterruptee.html @@ -0,0 +1,11 @@ +ShutdownInterruptee in interrupt_support - Rust
pub struct ShutdownInterruptee;

Trait Implementations§

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/interrupt_support/struct.SqlInterruptHandle.html b/book/rust-docs/interrupt_support/struct.SqlInterruptHandle.html new file mode 100644 index 0000000000..602beb3a7d --- /dev/null +++ b/book/rust-docs/interrupt_support/struct.SqlInterruptHandle.html @@ -0,0 +1,28 @@ +SqlInterruptHandle in interrupt_support - Rust
pub struct SqlInterruptHandle { /* private fields */ }
Expand description

Interrupt operations that use SQL

+

Typical usage of this type:

+
    +
  • Components typically create a wrapper class around an rusqlite::Connection +(PlacesConnection, LoginStore, etc.)
  • +
  • The wrapper stores an Arc<SqlInterruptHandle>
  • +
  • The wrapper has a method that clones and returns that Arc. This allows passing the interrupt +handle to a different thread in order to interrupt a particular operation.
  • +
  • The wrapper calls begin_interrupt_scope() at the start of each operation. The code that +performs the operation periodically calls err_if_interrupted().
  • +
  • Finally, the wrapper class implements AsRef<SqlInterruptHandle> and calls +register_interrupt(). This causes all operations to be interrupted when we enter +shutdown mode.
  • +
+

Implementations§

source§

impl SqlInterruptHandle

source

pub fn new(conn: &Connection) -> Self

source

pub fn begin_interrupt_scope(&self) -> Result<SqlInterruptScope, Interrupted>

Begin an interrupt scope that will be interrupted by this handle

+

Returns Err(Interrupted) if we’re in shutdown mode

+
source

pub fn interrupt(&self)

Interrupt all interrupt scopes created by this handle

+

Trait Implementations§

source§

impl Debug for SqlInterruptHandle

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/interrupt_support/struct.SqlInterruptScope.html b/book/rust-docs/interrupt_support/struct.SqlInterruptScope.html new file mode 100644 index 0000000000..b62fcb3d8d --- /dev/null +++ b/book/rust-docs/interrupt_support/struct.SqlInterruptScope.html @@ -0,0 +1,16 @@ +SqlInterruptScope in interrupt_support - Rust
pub struct SqlInterruptScope { /* private fields */ }
Expand description

Check if an operation has been interrupted

+

This is used by the rust code to check if an operation should fail because it was interrupted. +It handles the case where we get interrupted outside of an SQL query.

+

Implementations§

source§

impl SqlInterruptScope

source

pub fn dummy() -> Self

source

pub fn was_interrupted(&self) -> bool

Check if scope has been interrupted

+
source

pub fn err_if_interrupted(&self) -> Result<(), Interrupted>

Return Err(Interrupted) if we were interrupted

+

Trait Implementations§

source§

impl Debug for SqlInterruptScope

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Interruptee for SqlInterruptScope

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/interrupt_support/trait.Interruptee.html b/book/rust-docs/interrupt_support/trait.Interruptee.html new file mode 100644 index 0000000000..3021338368 --- /dev/null +++ b/book/rust-docs/interrupt_support/trait.Interruptee.html @@ -0,0 +1,10 @@ +Interruptee in interrupt_support - Rust
pub trait Interruptee {
+    // Required method
+    fn was_interrupted(&self) -> bool;
+
+    // Provided method
+    fn err_if_interrupted(&self) -> Result<(), Interrupted> { ... }
+}
Expand description

Represents the state of something that may be interrupted. Decoupled from +the interrupt mechanics so that things which want to check if they have been +interrupted are simpler.

+

Required Methods§

Provided Methods§

Implementors§

\ No newline at end of file diff --git a/book/rust-docs/logins/all.html b/book/rust-docs/logins/all.html new file mode 100644 index 0000000000..12a9b8896c --- /dev/null +++ b/book/rust-docs/logins/all.html @@ -0,0 +1 @@ +List of all items in this crate
\ No newline at end of file diff --git a/book/rust-docs/logins/attr.handle_error.html b/book/rust-docs/logins/attr.handle_error.html new file mode 100644 index 0000000000..38a48da432 --- /dev/null +++ b/book/rust-docs/logins/attr.handle_error.html @@ -0,0 +1,50 @@ +handle_error in logins - Rust

Attribute Macro logins::handle_error

source ·
#[handle_error]
Expand description

A procedural macro that exposes internal errors to external errors the +consuming applications should handle. It requires that the internal error +implements [error_support::ErrorHandling].

+

Additionally, this procedural macro has side effects, including:

+
    +
  • It would log the error based on a pre-defined log level. The log level is defined +in the [error_support::ErrorHandling] implementation.
  • +
  • It would report some errors using an external error reporter, in practice, this +is implemented using Sentry in the app.
  • +
+

Example

+
use error_support::{handle_error, GetErrorHandling, ErrorHandling};
+use std::fmt::Display
+#[derive(Debug, thiserror::Error)]
+struct Error {}
+type Result<T, E = Error> = std::result::Result<T, E>;
+impl Display for Error {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "Internal Error!")
+    }
+}
+
+#[derive(Debug, thiserror::Error)]
+struct ExternalError {}
+
+impl Display for ExternalError {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "External Error!")
+    }
+}
+
+impl GetErrorHandling for Error {
+   type ExternalError = ExternalError;
+
+   fn get_error_handling(&self) -> ErrorHandling<Self::ExternalError> {
+       ErrorHandling::convert(ExternalError {})
+   }
+}
+
+// The `handle_error` macro maps from the error supplied in the mandatory argument
+// (ie, `Error` in this example) to the error returned by the function (`ExternalError`
+// in this example)
+#[handle_error(Error)]
+fn do_something() -> std::result::Result<String, ExternalError> {
+   Err(Error{})
+}
+
+// The error here is an `ExternalError`
+let _: ExternalError = do_something().unwrap_err();
+
\ No newline at end of file diff --git a/book/rust-docs/logins/db/struct.LoginDb.html b/book/rust-docs/logins/db/struct.LoginDb.html new file mode 100644 index 0000000000..432bacf7ba --- /dev/null +++ b/book/rust-docs/logins/db/struct.LoginDb.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../logins/struct.LoginDb.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/logins/encryption/fn.check_canary.html b/book/rust-docs/logins/encryption/fn.check_canary.html new file mode 100644 index 0000000000..7fc53467db --- /dev/null +++ b/book/rust-docs/logins/encryption/fn.check_canary.html @@ -0,0 +1 @@ +check_canary in logins::encryption - Rust
pub fn check_canary(canary: &str, text: &str, key: &str) -> ApiResult<bool>
\ No newline at end of file diff --git a/book/rust-docs/logins/encryption/fn.create_canary.html b/book/rust-docs/logins/encryption/fn.create_canary.html new file mode 100644 index 0000000000..66317838da --- /dev/null +++ b/book/rust-docs/logins/encryption/fn.create_canary.html @@ -0,0 +1 @@ +create_canary in logins::encryption - Rust
pub fn create_canary(text: &str, key: &str) -> ApiResult<String>
\ No newline at end of file diff --git a/book/rust-docs/logins/encryption/fn.create_key.html b/book/rust-docs/logins/encryption/fn.create_key.html new file mode 100644 index 0000000000..3e1bbc045d --- /dev/null +++ b/book/rust-docs/logins/encryption/fn.create_key.html @@ -0,0 +1 @@ +create_key in logins::encryption - Rust

Function logins::encryption::create_key

source ·
pub fn create_key() -> ApiResult<String>
\ No newline at end of file diff --git a/book/rust-docs/logins/encryption/index.html b/book/rust-docs/logins/encryption/index.html new file mode 100644 index 0000000000..9d78c574ec --- /dev/null +++ b/book/rust-docs/logins/encryption/index.html @@ -0,0 +1 @@ +logins::encryption - Rust
\ No newline at end of file diff --git a/book/rust-docs/logins/encryption/sidebar-items.js b/book/rust-docs/logins/encryption/sidebar-items.js new file mode 100644 index 0000000000..73c97f9f74 --- /dev/null +++ b/book/rust-docs/logins/encryption/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["check_canary","create_canary","create_key"],"type":["EncryptorDecryptor"]}; \ No newline at end of file diff --git a/book/rust-docs/logins/encryption/type.EncryptorDecryptor.html b/book/rust-docs/logins/encryption/type.EncryptorDecryptor.html new file mode 100644 index 0000000000..31f23673a9 --- /dev/null +++ b/book/rust-docs/logins/encryption/type.EncryptorDecryptor.html @@ -0,0 +1 @@ +EncryptorDecryptor in logins::encryption - Rust

Type Definition logins::encryption::EncryptorDecryptor

source ·
pub type EncryptorDecryptor = EncryptorDecryptor<Error>;
\ No newline at end of file diff --git a/book/rust-docs/logins/enum.Error.html b/book/rust-docs/logins/enum.Error.html new file mode 100644 index 0000000000..1e89279ade --- /dev/null +++ b/book/rust-docs/logins/enum.Error.html @@ -0,0 +1,35 @@ +Error in logins - Rust

Enum logins::Error

source ·
pub enum Error {
+
Show 16 variants MalformedIncomingRecord, + InvalidLogin(InvalidLogin), + BadSyncStatus(u8), + NoSuchRecord(String), + NonEmptyTable, + EncryptionKeyMissing, + SyncAdapterError(Error), + JsonError(Error), + SqlError(Error), + UrlParseError(ParseError), + InvalidPath(OsString), + InvalidDatabaseFile(String), + CryptoError(EncryptorDecryptorError), + Interrupted(Interrupted), + IOError(Error), + MigrationError(String), +
}
Expand description

Logins error type +These are “internal” errors used by the implementation. This error type +is never returned to the consumer.

+

Variants§

§

MalformedIncomingRecord

§

InvalidLogin(InvalidLogin)

§

BadSyncStatus(u8)

§

NoSuchRecord(String)

§

NonEmptyTable

§

EncryptionKeyMissing

§

SyncAdapterError(Error)

§

JsonError(Error)

§

SqlError(Error)

§

UrlParseError(ParseError)

§

InvalidPath(OsString)

§

InvalidDatabaseFile(String)

§

CryptoError(EncryptorDecryptorError)

§

Interrupted(Interrupted)

§

IOError(Error)

§

MigrationError(String)

Trait Implementations§

source§

impl Debug for Error

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Display for Error

source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Error for Error

source§

fn source(&self) -> Option<&(dyn Error + 'static)>

The lower-level source of this error, if any. Read more
1.0.0 · source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type based access to context intended for error reports. Read more
source§

impl From<EncryptorDecryptorError> for Error

source§

fn from(source: EncryptorDecryptorError) -> Self

Converts to this type from the input type.
source§

impl From<Error> for Error

source§

fn from(source: Error) -> Self

Converts to this type from the input type.
source§

impl From<Error> for Error

source§

fn from(source: Error) -> Self

Converts to this type from the input type.
source§

impl From<Error> for Error

source§

fn from(source: Error) -> Self

Converts to this type from the input type.
source§

impl From<Error> for Error

source§

fn from(source: Error) -> Self

Converts to this type from the input type.
source§

impl From<Interrupted> for Error

source§

fn from(source: Interrupted) -> Self

Converts to this type from the input type.
source§

impl From<InvalidLogin> for Error

source§

fn from(source: InvalidLogin) -> Self

Converts to this type from the input type.
source§

impl From<ParseError> for Error

source§

fn from(source: ParseError) -> Self

Converts to this type from the input type.
source§

impl GetErrorHandling for Error

§

type ExternalError = LoginsApiError

source§

fn get_error_handling(&self) -> ErrorHandling<Self::ExternalError>

Return how to handle our internal errors

Auto Trait Implementations§

§

impl !RefUnwindSafe for Error

§

impl Send for Error

§

impl Sync for Error

§

impl Unpin for Error

§

impl !UnwindSafe for Error

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<E> Provider for Ewhere + E: Error + ?Sized,

source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (provide_any)
Data providers should implement this method to provide all values they are able to +provide by using demand. Read more
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/logins/enum.InvalidLogin.html b/book/rust-docs/logins/enum.InvalidLogin.html new file mode 100644 index 0000000000..bd52638086 --- /dev/null +++ b/book/rust-docs/logins/enum.InvalidLogin.html @@ -0,0 +1,26 @@ +InvalidLogin in logins - Rust

Enum logins::InvalidLogin

source ·
pub enum InvalidLogin {
+    EmptyOrigin,
+    EmptyPassword,
+    DuplicateLogin,
+    BothTargets,
+    NoTarget,
+    IllegalOrigin,
+    IllegalFieldValue {
+        field_info: String,
+    },
+}
Expand description

Error::InvalidLogin subtypes

+

Variants§

§

EmptyOrigin

§

EmptyPassword

§

DuplicateLogin

§

BothTargets

§

NoTarget

§

IllegalOrigin

§

IllegalFieldValue

Fields

§field_info: String

Trait Implementations§

source§

impl Debug for InvalidLogin

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Display for InvalidLogin

source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Error for InvalidLogin

1.30.0 · source§

fn source(&self) -> Option<&(dyn Error + 'static)>

The lower-level source of this error, if any. Read more
1.0.0 · source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type based access to context intended for error reports. Read more
source§

impl From<InvalidLogin> for Error

source§

fn from(source: InvalidLogin) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<E> Provider for Ewhere + E: Error + ?Sized,

source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (provide_any)
Data providers should implement this method to provide all values they are able to +provide by using demand. Read more
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/logins/enum.LoginsApiError.html b/book/rust-docs/logins/enum.LoginsApiError.html new file mode 100644 index 0000000000..624014c09e --- /dev/null +++ b/book/rust-docs/logins/enum.LoginsApiError.html @@ -0,0 +1,32 @@ +LoginsApiError in logins - Rust
pub enum LoginsApiError {
+    InvalidRecord {
+        reason: String,
+    },
+    NoSuchRecord {
+        reason: String,
+    },
+    IncorrectKey,
+    Interrupted {
+        reason: String,
+    },
+    SyncAuthInvalid {
+        reason: String,
+    },
+    UnexpectedLoginsApiError {
+        reason: String,
+    },
+}

Variants§

§

InvalidRecord

Fields

§reason: String
§

NoSuchRecord

Fields

§reason: String
§

IncorrectKey

§

Interrupted

Fields

§reason: String
§

SyncAuthInvalid

Fields

§reason: String
§

UnexpectedLoginsApiError

Fields

§reason: String

Trait Implementations§

source§

impl Debug for LoginsApiError

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Display for LoginsApiError

source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Error for LoginsApiError

1.30.0 · source§

fn source(&self) -> Option<&(dyn Error + 'static)>

The lower-level source of this error, if any. Read more
1.0.0 · source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type based access to context intended for error reports. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<E> Provider for Ewhere + E: Error + ?Sized,

source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (provide_any)
Data providers should implement this method to provide all values they are able to +provide by using demand. Read more
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/logins/error/attr.handle_error.html b/book/rust-docs/logins/error/attr.handle_error.html new file mode 100644 index 0000000000..26eb635f6c --- /dev/null +++ b/book/rust-docs/logins/error/attr.handle_error.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../logins/attr.handle_error.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/logins/error/enum.Error.html b/book/rust-docs/logins/error/enum.Error.html new file mode 100644 index 0000000000..19edc9ec1d --- /dev/null +++ b/book/rust-docs/logins/error/enum.Error.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../logins/enum.Error.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/logins/error/enum.InvalidLogin.html b/book/rust-docs/logins/error/enum.InvalidLogin.html new file mode 100644 index 0000000000..62b205f3d5 --- /dev/null +++ b/book/rust-docs/logins/error/enum.InvalidLogin.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../logins/enum.InvalidLogin.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/logins/error/enum.LoginsApiError.html b/book/rust-docs/logins/error/enum.LoginsApiError.html new file mode 100644 index 0000000000..e3452c17f8 --- /dev/null +++ b/book/rust-docs/logins/error/enum.LoginsApiError.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../logins/enum.LoginsApiError.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/logins/error/macro.breadcrumb!.html b/book/rust-docs/logins/error/macro.breadcrumb!.html new file mode 100644 index 0000000000..c3540b9473 --- /dev/null +++ b/book/rust-docs/logins/error/macro.breadcrumb!.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to macro.breadcrumb.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/logins/error/macro.breadcrumb.html b/book/rust-docs/logins/error/macro.breadcrumb.html new file mode 100644 index 0000000000..a130eb436a --- /dev/null +++ b/book/rust-docs/logins/error/macro.breadcrumb.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../logins/macro.breadcrumb.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/logins/error/macro.report_error!.html b/book/rust-docs/logins/error/macro.report_error!.html new file mode 100644 index 0000000000..b70e520f0b --- /dev/null +++ b/book/rust-docs/logins/error/macro.report_error!.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to macro.report_error.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/logins/error/macro.report_error.html b/book/rust-docs/logins/error/macro.report_error.html new file mode 100644 index 0000000000..805779aee6 --- /dev/null +++ b/book/rust-docs/logins/error/macro.report_error.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../logins/macro.report_error.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/logins/error/type.ApiResult.html b/book/rust-docs/logins/error/type.ApiResult.html new file mode 100644 index 0000000000..23f9acc9ac --- /dev/null +++ b/book/rust-docs/logins/error/type.ApiResult.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../logins/type.ApiResult.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/logins/error/type.Result.html b/book/rust-docs/logins/error/type.Result.html new file mode 100644 index 0000000000..9d118e9230 --- /dev/null +++ b/book/rust-docs/logins/error/type.Result.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../logins/type.Result.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/logins/fn.get_registered_sync_engine.html b/book/rust-docs/logins/fn.get_registered_sync_engine.html new file mode 100644 index 0000000000..157b733367 --- /dev/null +++ b/book/rust-docs/logins/fn.get_registered_sync_engine.html @@ -0,0 +1,5 @@ +get_registered_sync_engine in logins - Rust
pub fn get_registered_sync_engine(
+    engine_id: &SyncEngineId
+) -> Option<Box<dyn SyncEngine>>
Expand description

Called by the sync manager to get a sync engine via the store previously +registered with the sync manager.

+
\ No newline at end of file diff --git a/book/rust-docs/logins/index.html b/book/rust-docs/logins/index.html new file mode 100644 index 0000000000..c1bc6e06a0 --- /dev/null +++ b/book/rust-docs/logins/index.html @@ -0,0 +1,6 @@ +logins - Rust

Crate logins

source ·

Modules

Macros

Structs

Enums

  • Logins error type +These are “internal” errors used by the implementation. This error type +is never returned to the consumer.
  • Error::InvalidLogin subtypes

Traits

Functions

  • Called by the sync manager to get a sync engine via the store previously +registered with the sync manager.

Type Definitions

Attribute Macros

  • A procedural macro that exposes internal errors to external errors the +consuming applications should handle. It requires that the internal error +implements [error_support::ErrorHandling].
\ No newline at end of file diff --git a/book/rust-docs/logins/login/struct.EncryptedLogin.html b/book/rust-docs/logins/login/struct.EncryptedLogin.html new file mode 100644 index 0000000000..87fbed8dea --- /dev/null +++ b/book/rust-docs/logins/login/struct.EncryptedLogin.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../logins/struct.EncryptedLogin.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/logins/login/struct.Login.html b/book/rust-docs/logins/login/struct.Login.html new file mode 100644 index 0000000000..b5137e82f7 --- /dev/null +++ b/book/rust-docs/logins/login/struct.Login.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../logins/struct.Login.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/logins/login/struct.LoginEntry.html b/book/rust-docs/logins/login/struct.LoginEntry.html new file mode 100644 index 0000000000..52d7f516e7 --- /dev/null +++ b/book/rust-docs/logins/login/struct.LoginEntry.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../logins/struct.LoginEntry.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/logins/login/struct.LoginFields.html b/book/rust-docs/logins/login/struct.LoginFields.html new file mode 100644 index 0000000000..b7b17a87bc --- /dev/null +++ b/book/rust-docs/logins/login/struct.LoginFields.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../logins/struct.LoginFields.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/logins/login/struct.RecordFields.html b/book/rust-docs/logins/login/struct.RecordFields.html new file mode 100644 index 0000000000..6c699f495e --- /dev/null +++ b/book/rust-docs/logins/login/struct.RecordFields.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../logins/struct.RecordFields.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/logins/login/struct.SecureLoginFields.html b/book/rust-docs/logins/login/struct.SecureLoginFields.html new file mode 100644 index 0000000000..1240e618bd --- /dev/null +++ b/book/rust-docs/logins/login/struct.SecureLoginFields.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../logins/struct.SecureLoginFields.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/logins/login/trait.ValidateAndFixup.html b/book/rust-docs/logins/login/trait.ValidateAndFixup.html new file mode 100644 index 0000000000..0aa27a33ba --- /dev/null +++ b/book/rust-docs/logins/login/trait.ValidateAndFixup.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../logins/trait.ValidateAndFixup.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/logins/macro.breadcrumb!.html b/book/rust-docs/logins/macro.breadcrumb!.html new file mode 100644 index 0000000000..c3540b9473 --- /dev/null +++ b/book/rust-docs/logins/macro.breadcrumb!.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to macro.breadcrumb.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/logins/macro.breadcrumb.html b/book/rust-docs/logins/macro.breadcrumb.html new file mode 100644 index 0000000000..5e7dc1d4ab --- /dev/null +++ b/book/rust-docs/logins/macro.breadcrumb.html @@ -0,0 +1,6 @@ +breadcrumb in logins - Rust

Macro logins::breadcrumb

macro_rules! breadcrumb {
+    ($($arg:tt)*) => { ... };
+}
Expand description

Tell the application to log a breadcrumb

+

Breadcrumbs are log-like entries that get tracked by the error reporting system. When we +report an error, recent breadcrumbs will be associated with it.

+
\ No newline at end of file diff --git a/book/rust-docs/logins/macro.report_error!.html b/book/rust-docs/logins/macro.report_error!.html new file mode 100644 index 0000000000..b70e520f0b --- /dev/null +++ b/book/rust-docs/logins/macro.report_error!.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to macro.report_error.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/logins/macro.report_error.html b/book/rust-docs/logins/macro.report_error.html new file mode 100644 index 0000000000..80583f47cc --- /dev/null +++ b/book/rust-docs/logins/macro.report_error.html @@ -0,0 +1,16 @@ +report_error in logins - Rust

Macro logins::report_error

macro_rules! report_error {
+    ($type_name:expr, $($arg:tt)*) => { ... };
+}
Expand description

Tell the application to report an error

+

If configured by the application, this sent to the application, which should report it to a +Sentry-like system. This should only be used for errors that we don’t expect to see and will +work on fixing if we see a non-trivial volume of them.

+

type_name identifies the error. It should be the main text that gets shown to the +user and also how the error system groups errors together. It should be in UpperCamelCase +form.

+

Good type_names require some trial and error, for example:

+
    +
  • Start with the error kind variant name
  • +
  • Add more text to distinguish errors more. For example an error code, or an extra word +based on inspecting the error details
  • +
+
\ No newline at end of file diff --git a/book/rust-docs/logins/sidebar-items.js b/book/rust-docs/logins/sidebar-items.js new file mode 100644 index 0000000000..f3671c8f45 --- /dev/null +++ b/book/rust-docs/logins/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"attr":["handle_error"],"enum":["Error","InvalidLogin","LoginsApiError"],"fn":["get_registered_sync_engine"],"macro":["breadcrumb","report_error"],"mod":["encryption"],"struct":["EncryptedLogin","Login","LoginDb","LoginEntry","LoginFields","LoginStore","LoginsSyncEngine","RecordFields","SecureLoginFields"],"trait":["ValidateAndFixup"],"type":["ApiResult","Result"]}; \ No newline at end of file diff --git a/book/rust-docs/logins/store/fn.get_registered_sync_engine.html b/book/rust-docs/logins/store/fn.get_registered_sync_engine.html new file mode 100644 index 0000000000..696fbc9937 --- /dev/null +++ b/book/rust-docs/logins/store/fn.get_registered_sync_engine.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../logins/fn.get_registered_sync_engine.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/logins/store/struct.LoginStore.html b/book/rust-docs/logins/store/struct.LoginStore.html new file mode 100644 index 0000000000..71e975be7e --- /dev/null +++ b/book/rust-docs/logins/store/struct.LoginStore.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../logins/struct.LoginStore.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/logins/struct.EncryptedLogin.html b/book/rust-docs/logins/struct.EncryptedLogin.html new file mode 100644 index 0000000000..56141484a8 --- /dev/null +++ b/book/rust-docs/logins/struct.EncryptedLogin.html @@ -0,0 +1,32 @@ +EncryptedLogin in logins - Rust

Struct logins::EncryptedLogin

source ·
pub struct EncryptedLogin {
+    pub record: RecordFields,
+    pub fields: LoginFields,
+    pub sec_fields: String,
+}
Expand description

A login stored in the database

+

Fields§

§record: RecordFields§fields: LoginFields§sec_fields: String

Implementations§

source§

impl EncryptedLogin

source

pub fn guid(&self) -> Guid

source

pub fn guid_str(&self) -> &str

source

pub fn decrypt(self, encdec: &EncryptorDecryptor) -> Result<Login>

source

pub fn decrypt_fields( + &self, + encdec: &EncryptorDecryptor +) -> Result<SecureLoginFields>

source§

impl EncryptedLogin

source

pub fn into_bso( + self, + encdec: &EncryptorDecryptor, + enc_unknown_fields: Option<String> +) -> Result<OutgoingBso>

Trait Implementations§

source§

impl Clone for EncryptedLogin

source§

fn clone(&self) -> EncryptedLogin

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for EncryptedLogin

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for EncryptedLogin

source§

fn default() -> EncryptedLogin

Returns the “default value” for a type. Read more
source§

impl Hash for EncryptedLogin

source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · source§

fn hash_slice<H>(data: &[Self], state: &mut H)where + H: Hasher, + Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
source§

impl PartialEq<EncryptedLogin> for EncryptedLogin

source§

fn eq(&self, other: &EncryptedLogin) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Eq for EncryptedLogin

source§

impl StructuralEq for EncryptedLogin

source§

impl StructuralPartialEq for EncryptedLogin

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> CallHasher for Twhere + T: Hash + ?Sized,

§

fn get_hash<H, B>(value: &H, build_hasher: &B) -> u64where + H: Hash + ?Sized, + B: BuildHasher,

source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/logins/struct.Login.html b/book/rust-docs/logins/struct.Login.html new file mode 100644 index 0000000000..e1b32f7631 --- /dev/null +++ b/book/rust-docs/logins/struct.Login.html @@ -0,0 +1,25 @@ +Login in logins - Rust

Struct logins::Login

source ·
pub struct Login {
+    pub record: RecordFields,
+    pub fields: LoginFields,
+    pub sec_fields: SecureLoginFields,
+}
Expand description

A login stored in the database

+

Fields§

§record: RecordFields§fields: LoginFields§sec_fields: SecureLoginFields

Implementations§

source§

impl Login

source

pub fn guid(&self) -> Guid

source

pub fn entry(&self) -> LoginEntry

source

pub fn encrypt(self, encdec: &EncryptorDecryptor) -> Result<EncryptedLogin>

Trait Implementations§

source§

impl Clone for Login

source§

fn clone(&self) -> Login

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for Login

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for Login

source§

fn default() -> Login

Returns the “default value” for a type. Read more
source§

impl Hash for Login

source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · source§

fn hash_slice<H>(data: &[Self], state: &mut H)where + H: Hasher, + Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
source§

impl PartialEq<Login> for Login

source§

fn eq(&self, other: &Login) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Eq for Login

source§

impl StructuralEq for Login

source§

impl StructuralPartialEq for Login

Auto Trait Implementations§

§

impl RefUnwindSafe for Login

§

impl Send for Login

§

impl Sync for Login

§

impl Unpin for Login

§

impl UnwindSafe for Login

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> CallHasher for Twhere + T: Hash + ?Sized,

§

fn get_hash<H, B>(value: &H, build_hasher: &B) -> u64where + H: Hash + ?Sized, + B: BuildHasher,

source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/logins/struct.LoginDb.html b/book/rust-docs/logins/struct.LoginDb.html new file mode 100644 index 0000000000..cc258e66ea --- /dev/null +++ b/book/rust-docs/logins/struct.LoginDb.html @@ -0,0 +1,517 @@ +LoginDb in logins - Rust

Struct logins::LoginDb

source ·
pub struct LoginDb {
+    pub db: Connection,
+    /* private fields */
+}

Fields§

§db: Connection

Implementations§

source§

impl LoginDb

source

pub fn with_connection(db: Connection) -> Result<Self>

source

pub fn open(path: impl AsRef<Path>) -> Result<Self>

source

pub fn open_in_memory() -> Result<Self>

source

pub fn new_interrupt_handle(&self) -> Arc<SqlInterruptHandle>

source

pub fn begin_interrupt_scope(&self) -> Result<SqlInterruptScope>

source§

impl LoginDb

source

pub fn get_all(&self) -> Result<Vec<EncryptedLogin>>

source

pub fn get_by_base_domain( + &self, + base_domain: &str +) -> Result<Vec<EncryptedLogin>>

source

pub fn get_by_id(&self, id: &str) -> Result<Option<EncryptedLogin>>

source

pub fn find_login_to_update( + &self, + look: LoginEntry, + encdec: &EncryptorDecryptor +) -> Result<Option<Login>>

source

pub fn touch(&self, id: &str) -> Result<()>

source

pub fn add( + &self, + entry: LoginEntry, + encdec: &EncryptorDecryptor +) -> Result<EncryptedLogin>

source

pub fn update( + &self, + sguid: &str, + entry: LoginEntry, + encdec: &EncryptorDecryptor +) -> Result<EncryptedLogin>

source

pub fn add_or_update( + &self, + entry: LoginEntry, + encdec: &EncryptorDecryptor +) -> Result<EncryptedLogin>

source

pub fn fixup_and_check_for_dupes( + &self, + guid: &Guid, + entry: LoginEntry, + encdec: &EncryptorDecryptor +) -> Result<LoginEntry>

source

pub fn check_for_dupes( + &self, + guid: &Guid, + entry: &LoginEntry, + encdec: &EncryptorDecryptor +) -> Result<()>

source

pub fn dupe_exists( + &self, + guid: &Guid, + entry: &LoginEntry, + encdec: &EncryptorDecryptor +) -> Result<bool>

source

pub fn find_dupe( + &self, + guid: &Guid, + entry: &LoginEntry, + encdec: &EncryptorDecryptor +) -> Result<Option<Guid>>

source

pub fn exists(&self, id: &str) -> Result<bool>

source

pub fn delete(&self, id: &str) -> Result<bool>

Delete the record with the provided id. Returns true if the record +existed already.

+
source

pub fn wipe_local(&self) -> Result<()>

Methods from Deref<Target = Connection>§

pub fn busy_timeout(&self, timeout: Duration) -> Result<(), Error>

Set a busy handler that sleeps for a specified amount of time when a +table is locked. The handler will sleep multiple times until at +least “ms” milliseconds of sleeping have accumulated.

+

Calling this routine with an argument equal to zero turns off all busy +handlers.

+

There can only be a single busy handler for a particular database +connection at any given moment. If another busy handler was defined +(using busy_handler) prior to calling this +routine, that other busy handler is cleared.

+

Newly created connections currently have a default busy timeout of +5000ms, but this may be subject to change.

+

pub fn busy_handler( + &self, + callback: Option<fn(_: i32) -> bool> +) -> Result<(), Error>

Register a callback to handle SQLITE_BUSY errors.

+

If the busy callback is None, then SQLITE_BUSY is returned +immediately upon encountering the lock. The argument to the busy +handler callback is the number of times that the +busy handler has been invoked previously for the +same locking event. If the busy callback returns false, then no +additional attempts are made to access the +database and SQLITE_BUSY is returned to the +application. If the callback returns true, then another attempt +is made to access the database and the cycle repeats.

+

There can only be a single busy handler defined for each database +connection. Setting a new busy handler clears any previously set +handler. Note that calling busy_timeout() +or evaluating PRAGMA busy_timeout=N will change the busy handler +and thus clear any previously set busy handler.

+

Newly created connections default to a +busy_timeout() handler with a timeout +of 5000ms, although this is subject to change.

+

pub fn prepare_cached(&self, sql: &str) -> Result<CachedStatement<'_>, Error>

Prepare a SQL statement for execution, returning a previously prepared +(but not currently in-use) statement if one is available. The +returned statement will be cached for reuse by future calls to +prepare_cached once it is dropped.

+ +
fn insert_new_people(conn: &Connection) -> Result<()> {
+    {
+        let mut stmt = conn.prepare_cached("INSERT INTO People (name) VALUES (?1)")?;
+        stmt.execute(["Joe Smith"])?;
+    }
+    {
+        // This will return the same underlying SQLite statement handle without
+        // having to prepare it again.
+        let mut stmt = conn.prepare_cached("INSERT INTO People (name) VALUES (?1)")?;
+        stmt.execute(["Bob Jones"])?;
+    }
+    Ok(())
+}
+
Failure
+

Will return Err if sql cannot be converted to a C-compatible string +or if the underlying SQLite call fails.

+

pub fn set_prepared_statement_cache_capacity(&self, capacity: usize)

Set the maximum number of cached prepared statements this connection +will hold. By default, a connection will hold a relatively small +number of cached statements. If you need more, or know that you +will not use cached statements, you +can set the capacity manually using this method.

+

pub fn flush_prepared_statement_cache(&self)

Remove/finalize all prepared statements currently in the cache.

+

pub fn db_config(&self, config: DbConfig) -> Result<bool, Error>

Returns the current value of a config.

+
    +
  • SQLITE_DBCONFIG_ENABLE_FKEY: return false or true to indicate +whether FK enforcement is off or on
  • +
  • SQLITE_DBCONFIG_ENABLE_TRIGGER: return false or true to indicate +whether triggers are disabled or enabled
  • +
  • SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER: return false or true to +indicate whether fts3_tokenizer are disabled or enabled
  • +
  • SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE: return false to indicate +checkpoints-on-close are not disabled or true if they are
  • +
  • SQLITE_DBCONFIG_ENABLE_QPSG: return false or true to indicate +whether the QPSG is disabled or enabled
  • +
  • SQLITE_DBCONFIG_TRIGGER_EQP: return false to indicate +output-for-trigger are not disabled or true if it is
  • +
+

pub fn set_db_config( + &self, + config: DbConfig, + new_val: bool +) -> Result<bool, Error>

Make configuration changes to a database connection

+
    +
  • SQLITE_DBCONFIG_ENABLE_FKEY: false to disable FK enforcement, +true to enable FK enforcement
  • +
  • SQLITE_DBCONFIG_ENABLE_TRIGGER: false to disable triggers, true +to enable triggers
  • +
  • SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER: false to disable +fts3_tokenizer(), true to enable fts3_tokenizer()
  • +
  • SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE: false (the default) to enable +checkpoints-on-close, true to disable them
  • +
  • SQLITE_DBCONFIG_ENABLE_QPSG: false to disable the QPSG, true to +enable QPSG
  • +
  • SQLITE_DBCONFIG_TRIGGER_EQP: false to disable output for trigger +programs, true to enable it
  • +
+

pub fn create_scalar_function<F, T>( + &self, + fn_name: &str, + n_arg: i32, + flags: FunctionFlags, + x_func: F +) -> Result<(), Error>where + F: FnMut(&Context<'_>) -> Result<T, Error> + Send + UnwindSafe + 'static, + T: ToSql,

Attach a user-defined scalar function to +this database connection.

+

fn_name is the name the function will be accessible from SQL. +n_arg is the number of arguments to the function. Use -1 for a +variable number. If the function always returns the same value +given the same input, deterministic should be true.

+

The function will remain available until the connection is closed or +until it is explicitly removed via +remove_function.

+
Example
+
fn scalar_function_example(db: Connection) -> Result<()> {
+    db.create_scalar_function(
+        "halve",
+        1,
+        FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
+        |ctx| {
+            let value = ctx.get::<f64>(0)?;
+            Ok(value / 2f64)
+        },
+    )?;
+
+    let six_halved: f64 = db.query_row("SELECT halve(6)", [], |r| r.get(0))?;
+    assert_eq!(six_halved, 3f64);
+    Ok(())
+}
+
Failure
+

Will return Err if the function could not be attached to the connection.

+

pub fn create_aggregate_function<A, D, T>( + &self, + fn_name: &str, + n_arg: i32, + flags: FunctionFlags, + aggr: D +) -> Result<(), Error>where + A: RefUnwindSafe + UnwindSafe, + D: Aggregate<A, T> + 'static, + T: ToSql,

Attach a user-defined aggregate function to this +database connection.

+
Failure
+

Will return Err if the function could not be attached to the connection.

+

pub fn create_window_function<A, W, T>( + &self, + fn_name: &str, + n_arg: i32, + flags: FunctionFlags, + aggr: W +) -> Result<(), Error>where + A: RefUnwindSafe + UnwindSafe, + W: WindowAggregate<A, T> + 'static, + T: ToSql,

Attach a user-defined aggregate window function to +this database connection.

+

See https://sqlite.org/windowfunctions.html#udfwinfunc for more +information.

+

pub fn remove_function(&self, fn_name: &str, n_arg: i32) -> Result<(), Error>

Removes a user-defined function from this +database connection.

+

fn_name and n_arg should match the name and number of arguments +given to create_scalar_function +or create_aggregate_function.

+
Failure
+

Will return Err if the function could not be removed.

+

pub fn limit(&self, limit: Limit) -> i32

Returns the current value of a [Limit].

+

pub fn set_limit(&self, limit: Limit, new_val: i32) -> i32

Changes the [Limit] to new_val, returning the prior +value of the limit.

+

pub fn pragma_query_value<T, F>( + &self, + schema_name: Option<DatabaseName<'_>>, + pragma_name: &str, + f: F +) -> Result<T, Error>where + F: FnOnce(&Row<'_>) -> Result<T, Error>,

Query the current value of pragma_name.

+

Some pragmas will return multiple rows/values which cannot be retrieved +with this method.

+

Prefer PRAGMA function introduced in SQLite 3.20: +SELECT user_version FROM pragma_user_version;

+

pub fn pragma_query<F>( + &self, + schema_name: Option<DatabaseName<'_>>, + pragma_name: &str, + f: F +) -> Result<(), Error>where + F: FnMut(&Row<'_>) -> Result<(), Error>,

Query the current rows/values of pragma_name.

+

Prefer PRAGMA function introduced in SQLite 3.20: +SELECT * FROM pragma_collation_list;

+

pub fn pragma<F, V>( + &self, + schema_name: Option<DatabaseName<'_>>, + pragma_name: &str, + pragma_value: V, + f: F +) -> Result<(), Error>where + F: FnMut(&Row<'_>) -> Result<(), Error>, + V: ToSql,

Query the current value(s) of pragma_name associated to +pragma_value.

+

This method can be used with query-only pragmas which need an argument +(e.g. table_info('one_tbl')) or pragmas which returns value(s) +(e.g. integrity_check).

+

Prefer PRAGMA function introduced in SQLite 3.20: +SELECT * FROM pragma_table_info(?1);

+

pub fn pragma_update<V>( + &self, + schema_name: Option<DatabaseName<'_>>, + pragma_name: &str, + pragma_value: V +) -> Result<(), Error>where + V: ToSql,

Set a new value to pragma_name.

+

Some pragmas will return the updated value which cannot be retrieved +with this method.

+

pub fn pragma_update_and_check<F, T, V>( + &self, + schema_name: Option<DatabaseName<'_>>, + pragma_name: &str, + pragma_value: V, + f: F +) -> Result<T, Error>where + F: FnOnce(&Row<'_>) -> Result<T, Error>, + V: ToSql,

Set a new value to pragma_name and return the updated value.

+

Only few pragmas automatically return the updated value.

+

pub fn unchecked_transaction(&self) -> Result<Transaction<'_>, Error>

Begin a new transaction with the default behavior (DEFERRED).

+

Attempt to open a nested transaction will result in a SQLite error. +Connection::transaction prevents this at compile time by taking &mut self, but Connection::unchecked_transaction() may be used to defer +the checking until runtime.

+

See [Connection::transaction] and [Transaction::new_unchecked] +(which can be used if the default transaction behavior is undesirable).

+
Example
+
fn perform_queries(conn: Rc<Connection>) -> Result<()> {
+    let tx = conn.unchecked_transaction()?;
+
+    do_queries_part_1(&tx)?; // tx causes rollback if this fails
+    do_queries_part_2(&tx)?; // tx causes rollback if this fails
+
+    tx.commit()
+}
+
Failure
+

Will return Err if the underlying SQLite call fails. The specific +error returned if transactions are nested is currently unspecified.

+

pub fn transaction_state( + &self, + db_name: Option<DatabaseName<'_>> +) -> Result<TransactionState, Error>

Determine the transaction state of a database

+

pub fn execute_batch(&self, sql: &str) -> Result<(), Error>

Convenience method to run multiple SQL statements (that cannot take any +parameters).

+
Example
+
fn create_tables(conn: &Connection) -> Result<()> {
+    conn.execute_batch(
+        "BEGIN;
+         CREATE TABLE foo(x INTEGER);
+         CREATE TABLE bar(y TEXT);
+         COMMIT;",
+    )
+}
+
Failure
+

Will return Err if sql cannot be converted to a C-compatible string +or if the underlying SQLite call fails.

+

pub fn execute<P>(&self, sql: &str, params: P) -> Result<usize, Error>where + P: Params,

Convenience method to prepare and execute a single SQL statement.

+

On success, returns the number of rows that were changed or inserted or +deleted (via sqlite3_changes).

+
Example
With positional params
+
fn update_rows(conn: &Connection) {
+    match conn.execute("UPDATE foo SET bar = 'baz' WHERE qux = ?1", [1i32]) {
+        Ok(updated) => println!("{} rows were updated", updated),
+        Err(err) => println!("update failed: {}", err),
+    }
+}
+
With positional params of varying types
+
fn update_rows(conn: &Connection) {
+    match conn.execute(
+        "UPDATE foo SET bar = 'baz' WHERE qux = ?1 AND quux = ?2",
+        params![1i32, 1.5f64],
+    ) {
+        Ok(updated) => println!("{} rows were updated", updated),
+        Err(err) => println!("update failed: {}", err),
+    }
+}
+
With named params
+
fn insert(conn: &Connection) -> Result<usize> {
+    conn.execute(
+        "INSERT INTO test (name) VALUES (:name)",
+        &[(":name", "one")],
+    )
+}
+
Failure
+

Will return Err if sql cannot be converted to a C-compatible string +or if the underlying SQLite call fails.

+

pub fn path(&self) -> Option<&str>

Returns the path to the database file, if one exists and is known.

+

Returns Some("") for a temporary or in-memory database.

+

Note that in some cases PRAGMA +database_list is +likely to be more robust.

+

pub fn last_insert_rowid(&self) -> i64

Get the SQLite rowid of the most recent successful INSERT.

+

Uses sqlite3_last_insert_rowid under +the hood.

+

pub fn query_row<T, P, F>(&self, sql: &str, params: P, f: F) -> Result<T, Error>where + P: Params, + F: FnOnce(&Row<'_>) -> Result<T, Error>,

Convenience method to execute a query that is expected to return a +single row.

+
Example
+
fn preferred_locale(conn: &Connection) -> Result<String> {
+    conn.query_row(
+        "SELECT value FROM preferences WHERE name='locale'",
+        [],
+        |row| row.get(0),
+    )
+}
+

If the query returns more than one row, all rows except the first are +ignored.

+

Returns Err(QueryReturnedNoRows) if no results are returned. If the +query truly is optional, you can call .optional() on the result of +this to get a Result<Option<T>>.

+
Failure
+

Will return Err if sql cannot be converted to a C-compatible string +or if the underlying SQLite call fails.

+

pub fn query_row_and_then<T, E, P, F>( + &self, + sql: &str, + params: P, + f: F +) -> Result<T, E>where + P: Params, + F: FnOnce(&Row<'_>) -> Result<T, E>, + E: From<Error>,

Convenience method to execute a query that is expected to return a +single row, and execute a mapping via f on that returned row with +the possibility of failure. The Result type of f must implement +std::convert::From<Error>.

+
Example
+
fn preferred_locale(conn: &Connection) -> Result<String> {
+    conn.query_row_and_then(
+        "SELECT value FROM preferences WHERE name='locale'",
+        [],
+        |row| row.get(0),
+    )
+}
+

If the query returns more than one row, all rows except the first are +ignored.

+
Failure
+

Will return Err if sql cannot be converted to a C-compatible string +or if the underlying SQLite call fails.

+

pub fn prepare(&self, sql: &str) -> Result<Statement<'_>, Error>

Prepare a SQL statement for execution.

+
Example
+
fn insert_new_people(conn: &Connection) -> Result<()> {
+    let mut stmt = conn.prepare("INSERT INTO People (name) VALUES (?1)")?;
+    stmt.execute(["Joe Smith"])?;
+    stmt.execute(["Bob Jones"])?;
+    Ok(())
+}
+
Failure
+

Will return Err if sql cannot be converted to a C-compatible string +or if the underlying SQLite call fails.

+

pub unsafe fn handle(&self) -> *mut sqlite3

Get access to the underlying SQLite database connection handle.

+
Warning
+

You should not need to use this function. If you do need to, please +open an issue on the rusqlite repository and describe +your use case.

+
Safety
+

This function is unsafe because it gives you raw access +to the SQLite connection, and what you do with it could impact the +safety of this Connection.

+

pub fn get_interrupt_handle(&self) -> InterruptHandle

Get access to a handle that can be used to interrupt long running +queries from another thread.

+

pub fn changes(&self) -> u64

Return the number of rows modified, inserted or deleted by the most +recently completed INSERT, UPDATE or DELETE statement on the database +connection.

+

See https://www.sqlite.org/c3ref/changes.html

+

pub fn is_autocommit(&self) -> bool

Test for auto-commit mode. +Autocommit mode is on by default.

+

pub fn is_busy(&self) -> bool

Determine if all associated prepared statements have been reset.

+

pub fn cache_flush(&self) -> Result<(), Error>

Flush caches to disk mid-transaction

+

pub fn is_readonly(&self, db_name: DatabaseName<'_>) -> Result<bool, Error>

Determine if a database is read-only

+

Trait Implementations§

source§

impl ConnExt for LoginDb

source§

fn conn(&self) -> &Connection

The method you need to implement to opt in to all of this.
§

fn set_pragma<T>( + &self, + pragma_name: &str, + pragma_value: T +) -> Result<&Self, Error>where + T: ToSql, + Self: Sized,

Set the value of the pragma on the main database. Returns the same object, for chaining.
§

fn prepare_maybe_cached<'conn>( + &'conn self, + sql: &str, + cache: bool +) -> Result<MaybeCached<'conn>, Error>

Get a cached or uncached statement based on a flag.
§

fn execute_all(&self, stmts: &[&str]) -> Result<(), Error>

Execute all the provided statements.
§

fn execute_one(&self, stmt: &str) -> Result<(), Error>

Execute a single statement.
§

fn execute_cached<P>(&self, sql: &str, params: P) -> Result<usize, Error>where + P: Params,

Equivalent to Connection::execute but caches the statement so that subsequent +calls to execute_cached will have improved performance.
§

fn query_one<T>(&self, sql: &str) -> Result<T, Error>where + T: FromSql,

Execute a query that returns a single result column, and return that result.
§

fn exists<P>(&self, sql: &str, params: P) -> Result<bool, Error>where + P: Params,

Return true if a query returns any rows
§

fn try_query_one<T, P>( + &self, + sql: &str, + params: P, + cache: bool +) -> Result<Option<T>, Error>where + T: FromSql, + P: Params, + Self: Sized,

Execute a query that returns 0 or 1 result columns, returning None +if there were no rows, or if the only result was NULL.
§

fn query_row_and_then_cachable<T, E, P, F>( + &self, + sql: &str, + params: P, + mapper: F, + cache: bool +) -> Result<T, E>where + Self: Sized, + P: Params, + E: From<Error>, + F: FnOnce(&Row<'_>) -> Result<T, E>,

Equivalent to rusqlite::Connection::query_row_and_then but allows +passing a flag to indicate that it’s cached.
§

fn query_rows_and_then<T, E, P, F>( + &self, + sql: &str, + params: P, + mapper: F +) -> Result<Vec<T, Global>, E>where + Self: Sized, + P: Params, + E: From<Error>, + F: FnMut(&Row<'_>) -> Result<T, E>,

Helper for when you’d like to get a Vec<T> of all the rows returned by a +query that takes named arguments. See also +query_rows_and_then_cached.
§

fn query_rows_and_then_cached<T, E, P, F>( + &self, + sql: &str, + params: P, + mapper: F +) -> Result<Vec<T, Global>, E>where + Self: Sized, + P: Params, + E: From<Error>, + F: FnMut(&Row<'_>) -> Result<T, E>,

Helper for when you’d like to get a Vec<T> of all the rows returned by a +query that takes named arguments.
§

fn query_rows_into<Coll, T, E, P, F>( + &self, + sql: &str, + params: P, + mapper: F +) -> Result<Coll, E>where + Self: Sized, + E: From<Error>, + F: FnMut(&Row<'_>) -> Result<T, E>, + Coll: FromIterator<T>, + P: Params,

Like query_rows_and_then_cachable, but works if you want a non-Vec as a result. Read more
§

fn query_rows_into_cached<Coll, T, E, P, F>( + &self, + sql: &str, + params: P, + mapper: F +) -> Result<Coll, E>where + Self: Sized, + P: Params, + E: From<Error>, + F: FnMut(&Row<'_>) -> Result<T, E>, + Coll: FromIterator<T>,

Same as query_rows_into, but caches the stmt if possible.
§

fn try_query_row<T, E, P, F>( + &self, + sql: &str, + params: P, + mapper: F, + cache: bool +) -> Result<Option<T>, E>where + Self: Sized, + P: Params, + E: From<Error>, + F: FnOnce(&Row<'_>) -> Result<T, E>,

Like query_row_and_then_cachable but returns None instead of erroring +if no such row exists.
§

fn unchecked_transaction(&self) -> Result<UncheckedTransaction<'_>, Error>

Caveat: This won’t actually get used most of the time, and calls will +usually invoke rusqlite’s method with the same name. See comment on +UncheckedTransaction for details (generally you probably don’t need to +care)
§

fn unchecked_transaction_imm(&self) -> Result<UncheckedTransaction<'_>, Error>

Begin unchecked_transaction with TransactionBehavior::Immediate. Use +when the first operation will be a read operation, that further writes +depend on for correctness.
§

fn get_db_size(&self) -> Result<u32, Error>

Get the DB size in bytes
source§

impl Deref for LoginDb

§

type Target = Connection

The resulting type after dereferencing.
source§

fn deref(&self) -> &Connection

Dereferences the value.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/logins/struct.LoginEntry.html b/book/rust-docs/logins/struct.LoginEntry.html new file mode 100644 index 0000000000..87e492547e --- /dev/null +++ b/book/rust-docs/logins/struct.LoginEntry.html @@ -0,0 +1,27 @@ +LoginEntry in logins - Rust

Struct logins::LoginEntry

source ·
pub struct LoginEntry {
+    pub fields: LoginFields,
+    pub sec_fields: SecureLoginFields,
+}
Expand description

A login entered by the user

+

Fields§

§fields: LoginFields§sec_fields: SecureLoginFields

Trait Implementations§

source§

impl Clone for LoginEntry

source§

fn clone(&self) -> LoginEntry

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for LoginEntry

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for LoginEntry

source§

fn default() -> LoginEntry

Returns the “default value” for a type. Read more
source§

impl Hash for LoginEntry

source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · source§

fn hash_slice<H>(data: &[Self], state: &mut H)where + H: Hasher, + Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
source§

impl PartialEq<LoginEntry> for LoginEntry

source§

fn eq(&self, other: &LoginEntry) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl ValidateAndFixup for LoginEntry

source§

fn validate_and_fixup(&self, fixup: bool) -> Result<Option<Self>>

source§

fn check_valid(&self) -> Result<()>where + Self: Sized,

source§

fn fixup(self) -> Result<Self>where + Self: Sized,

source§

fn maybe_fixup(&self) -> Result<Option<Self>>where + Self: Sized,

source§

impl Eq for LoginEntry

source§

impl StructuralEq for LoginEntry

source§

impl StructuralPartialEq for LoginEntry

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> CallHasher for Twhere + T: Hash + ?Sized,

§

fn get_hash<H, B>(value: &H, build_hasher: &B) -> u64where + H: Hash + ?Sized, + B: BuildHasher,

source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/logins/struct.LoginFields.html b/book/rust-docs/logins/struct.LoginFields.html new file mode 100644 index 0000000000..a3994b9d0e --- /dev/null +++ b/book/rust-docs/logins/struct.LoginFields.html @@ -0,0 +1,30 @@ +LoginFields in logins - Rust

Struct logins::LoginFields

source ·
pub struct LoginFields {
+    pub origin: String,
+    pub form_action_origin: Option<String>,
+    pub http_realm: Option<String>,
+    pub username_field: String,
+    pub password_field: String,
+}

Fields§

§origin: String§form_action_origin: Option<String>§http_realm: Option<String>§username_field: String§password_field: String

Trait Implementations§

source§

impl Clone for LoginFields

source§

fn clone(&self) -> LoginFields

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for LoginFields

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for LoginFields

source§

fn default() -> LoginFields

Returns the “default value” for a type. Read more
source§

impl Hash for LoginFields

source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · source§

fn hash_slice<H>(data: &[Self], state: &mut H)where + H: Hasher, + Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
source§

impl PartialEq<LoginFields> for LoginFields

source§

fn eq(&self, other: &LoginFields) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl ValidateAndFixup for LoginFields

source§

fn validate_and_fixup(&self, fixup: bool) -> Result<Option<Self>>

Internal helper for doing validation and fixups.

+
source§

fn check_valid(&self) -> Result<()>where + Self: Sized,

source§

fn fixup(self) -> Result<Self>where + Self: Sized,

source§

fn maybe_fixup(&self) -> Result<Option<Self>>where + Self: Sized,

source§

impl Eq for LoginFields

source§

impl StructuralEq for LoginFields

source§

impl StructuralPartialEq for LoginFields

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> CallHasher for Twhere + T: Hash + ?Sized,

§

fn get_hash<H, B>(value: &H, build_hasher: &B) -> u64where + H: Hash + ?Sized, + B: BuildHasher,

source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/logins/struct.LoginStore.html b/book/rust-docs/logins/struct.LoginStore.html new file mode 100644 index 0000000000..76304fa28e --- /dev/null +++ b/book/rust-docs/logins/struct.LoginStore.html @@ -0,0 +1,32 @@ +LoginStore in logins - Rust

Struct logins::LoginStore

source ·
pub struct LoginStore {
+    pub db: Mutex<LoginDb>,
+}

Fields§

§db: Mutex<LoginDb>

Implementations§

source§

impl LoginStore

source

pub fn new(path: impl AsRef<Path>) -> ApiResult<Self>

source

pub fn new_from_db(db: LoginDb) -> Self

source

pub fn new_in_memory() -> ApiResult<Self>

source

pub fn list(&self) -> ApiResult<Vec<EncryptedLogin>>

source

pub fn get(&self, id: &str) -> ApiResult<Option<EncryptedLogin>>

source

pub fn get_by_base_domain( + &self, + base_domain: &str +) -> ApiResult<Vec<EncryptedLogin>>

source

pub fn find_login_to_update( + &self, + entry: LoginEntry, + enc_key: &str +) -> ApiResult<Option<Login>>

source

pub fn touch(&self, id: &str) -> ApiResult<()>

source

pub fn delete(&self, id: &str) -> ApiResult<bool>

source

pub fn wipe(&self) -> ApiResult<()>

source

pub fn wipe_local(&self) -> ApiResult<()>

source

pub fn reset(self: Arc<Self>) -> ApiResult<()>

source

pub fn update( + &self, + id: &str, + entry: LoginEntry, + enc_key: &str +) -> ApiResult<EncryptedLogin>

source

pub fn add(&self, entry: LoginEntry, enc_key: &str) -> ApiResult<EncryptedLogin>

source

pub fn add_or_update( + &self, + entry: LoginEntry, + enc_key: &str +) -> ApiResult<EncryptedLogin>

source

pub fn register_with_sync_manager(self: Arc<Self>)

source

pub fn create_logins_sync_engine( + self: Arc<Self> +) -> ApiResult<Box<dyn SyncEngine>>

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/logins/struct.LoginsSyncEngine.html b/book/rust-docs/logins/struct.LoginsSyncEngine.html new file mode 100644 index 0000000000..f057bdff57 --- /dev/null +++ b/book/rust-docs/logins/struct.LoginsSyncEngine.html @@ -0,0 +1,61 @@ +LoginsSyncEngine in logins - Rust
pub struct LoginsSyncEngine {
+    pub store: Arc<LoginStore>,
+    pub scope: SqlInterruptScope,
+    pub staged: RefCell<Vec<IncomingBso>>,
+    /* private fields */
+}

Fields§

§store: Arc<LoginStore>§scope: SqlInterruptScope§staged: RefCell<Vec<IncomingBso>>

Implementations§

source§

impl LoginsSyncEngine

source

pub fn new(store: Arc<LoginStore>) -> Result<Self>

source

pub fn set_global_state(&self, state: &Option<String>) -> Result<()>

source

pub fn get_global_state(&self) -> Result<Option<String>>

source

pub fn do_reset(&self, assoc: &EngineSyncAssociation) -> Result<()>

Trait Implementations§

source§

impl SyncEngine for LoginsSyncEngine

source§

fn collection_name(&self) -> Cow<'static, str>

source§

fn set_local_encryption_key(&mut self, key: &str) -> Result<()>

Tells the engine what the local encryption key is for the data managed +by the engine. This is only used by collections that store data +encrypted locally and is unrelated to the encryption used by Sync. +The intent is that for such collections, this key can be used to +decrypt local data before it is re-encrypted by Sync and sent to the +storage servers, and similarly, data from the storage servers will be +decrypted by Sync, then encrypted by the local encryption key before +being added to the local database. Read more
source§

fn stage_incoming( + &self, + inbound: Vec<IncomingBso>, + _telem: &mut Engine +) -> Result<()>

Stage some incoming records. This might be called multiple times in the same sync +if we fetch the incoming records in batches. Read more
source§

fn apply( + &self, + timestamp: ServerTimestamp, + telem: &mut Engine +) -> Result<Vec<OutgoingBso>>

Apply the staged records, returning outgoing records. +Ideally we would adjust this model to better support batching of outgoing records +without needing to keep them all in memory (ie, an iterator or similar?)
source§

fn set_uploaded( + &self, + new_timestamp: ServerTimestamp, + ids: Vec<Guid> +) -> Result<()>

Indicates that the given record IDs were uploaded successfully to the server. +This may be called multiple times per sync, once for each batch. Batching is determined +dynamically based on payload sizes and counts via the server’s advertised limits.
source§

fn get_collection_request( + &self, + server_timestamp: ServerTimestamp +) -> Result<Option<CollectionRequest>>

The engine is responsible for building a single collection request. Engines +typically will store a lastModified timestamp and use that to build a +request saying “give me full records since that date” - however, other +engines might do something fancier. It can return None if the server timestamp +has not advanced since the last sync. +This could even later be extended to handle “backfills”, and we might end up +wanting one engine to use multiple collections (eg, as a “foreign key” via guid), etc.
source§

fn get_sync_assoc(&self) -> Result<EngineSyncAssociation>

Get persisted sync IDs. If they don’t match the global state we’ll be +reset() with the new IDs.
source§

fn reset(&self, assoc: &EngineSyncAssociation) -> Result<()>

Reset the engine (and associated store) without wiping local data, +ready for a “first sync”. +assoc defines how this store is to be associated with sync.
source§

fn wipe(&self) -> Result<()>

§

fn prepare_for_sync( + &self, + _get_client_data: &dyn Fn() -> ClientData +) -> Result<(), Error>

Prepares the engine for syncing. The tabs engine currently uses this to +store the current list of clients, which it uses to look up device names +and types. Read more
§

fn sync_finished(&self) -> Result<(), Error>

Called once the sync is finished. Not currently called if uploads fail (which +seems sad, but the other batching confusion there needs sorting out first). +Many engines will have nothing to do here, as most “post upload” work should be +done in set_uploaded()

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/logins/struct.RecordFields.html b/book/rust-docs/logins/struct.RecordFields.html new file mode 100644 index 0000000000..5c613e14eb --- /dev/null +++ b/book/rust-docs/logins/struct.RecordFields.html @@ -0,0 +1,27 @@ +RecordFields in logins - Rust

Struct logins::RecordFields

source ·
pub struct RecordFields {
+    pub id: String,
+    pub time_created: i64,
+    pub time_password_changed: i64,
+    pub time_last_used: i64,
+    pub times_used: i64,
+}
Expand description

Login data specific to database records

+

Fields§

§id: String§time_created: i64§time_password_changed: i64§time_last_used: i64§times_used: i64

Trait Implementations§

source§

impl Clone for RecordFields

source§

fn clone(&self) -> RecordFields

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for RecordFields

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for RecordFields

source§

fn default() -> RecordFields

Returns the “default value” for a type. Read more
source§

impl Hash for RecordFields

source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · source§

fn hash_slice<H>(data: &[Self], state: &mut H)where + H: Hasher, + Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
source§

impl PartialEq<RecordFields> for RecordFields

source§

fn eq(&self, other: &RecordFields) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Eq for RecordFields

source§

impl StructuralEq for RecordFields

source§

impl StructuralPartialEq for RecordFields

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> CallHasher for Twhere + T: Hash + ?Sized,

§

fn get_hash<H, B>(value: &H, build_hasher: &B) -> u64where + H: Hash + ?Sized, + B: BuildHasher,

source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/logins/struct.SecureLoginFields.html b/book/rust-docs/logins/struct.SecureLoginFields.html new file mode 100644 index 0000000000..d3ed8d97d8 --- /dev/null +++ b/book/rust-docs/logins/struct.SecureLoginFields.html @@ -0,0 +1,31 @@ +SecureLoginFields in logins - Rust
pub struct SecureLoginFields {
+    pub username: String,
+    pub password: String,
+}
Expand description

LoginEntry fields that are stored encrypted

+

Fields§

§username: String§password: String

Implementations§

source§

impl SecureLoginFields

source

pub fn encrypt(&self, encdec: &EncryptorDecryptor) -> Result<String>

source

pub fn decrypt(ciphertext: &str, encdec: &EncryptorDecryptor) -> Result<Self>

Trait Implementations§

source§

impl Clone for SecureLoginFields

source§

fn clone(&self) -> SecureLoginFields

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for SecureLoginFields

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for SecureLoginFields

source§

fn default() -> SecureLoginFields

Returns the “default value” for a type. Read more
source§

impl<'de> Deserialize<'de> for SecureLoginFields

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl Hash for SecureLoginFields

source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · source§

fn hash_slice<H>(data: &[Self], state: &mut H)where + H: Hasher, + Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
source§

impl PartialEq<SecureLoginFields> for SecureLoginFields

source§

fn eq(&self, other: &SecureLoginFields) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Serialize for SecureLoginFields

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more
source§

impl ValidateAndFixup for SecureLoginFields

source§

fn validate_and_fixup(&self, _fixup: bool) -> Result<Option<Self>>

We don’t actually have fixups.

+
source§

fn check_valid(&self) -> Result<()>where + Self: Sized,

source§

fn fixup(self) -> Result<Self>where + Self: Sized,

source§

fn maybe_fixup(&self) -> Result<Option<Self>>where + Self: Sized,

source§

impl Eq for SecureLoginFields

source§

impl StructuralEq for SecureLoginFields

source§

impl StructuralPartialEq for SecureLoginFields

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> CallHasher for Twhere + T: Hash + ?Sized,

§

fn get_hash<H, B>(value: &H, build_hasher: &B) -> u64where + H: Hash + ?Sized, + B: BuildHasher,

source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/logins/sync/engine/struct.LoginsSyncEngine.html b/book/rust-docs/logins/sync/engine/struct.LoginsSyncEngine.html new file mode 100644 index 0000000000..08507fad67 --- /dev/null +++ b/book/rust-docs/logins/sync/engine/struct.LoginsSyncEngine.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../logins/struct.LoginsSyncEngine.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/logins/trait.ValidateAndFixup.html b/book/rust-docs/logins/trait.ValidateAndFixup.html new file mode 100644 index 0000000000..6758be10db --- /dev/null +++ b/book/rust-docs/logins/trait.ValidateAndFixup.html @@ -0,0 +1,17 @@ +ValidateAndFixup in logins - Rust
pub trait ValidateAndFixup {
+    // Required method
+    fn validate_and_fixup(&self, fixup: bool) -> Result<Option<Self>>
+       where Self: Sized;
+
+    // Provided methods
+    fn check_valid(&self) -> Result<()>
+       where Self: Sized { ... }
+    fn fixup(self) -> Result<Self>
+       where Self: Sized { ... }
+    fn maybe_fixup(&self) -> Result<Option<Self>>
+       where Self: Sized { ... }
+}

Required Methods§

source

fn validate_and_fixup(&self, fixup: bool) -> Result<Option<Self>>where + Self: Sized,

Provided Methods§

source

fn check_valid(&self) -> Result<()>where + Self: Sized,

source

fn fixup(self) -> Result<Self>where + Self: Sized,

source

fn maybe_fixup(&self) -> Result<Option<Self>>where + Self: Sized,

Implementors§

\ No newline at end of file diff --git a/book/rust-docs/logins/type.ApiResult.html b/book/rust-docs/logins/type.ApiResult.html new file mode 100644 index 0000000000..c4b9ec2127 --- /dev/null +++ b/book/rust-docs/logins/type.ApiResult.html @@ -0,0 +1 @@ +ApiResult in logins - Rust

Type Definition logins::ApiResult

source ·
pub type ApiResult<T> = Result<T, LoginsApiError>;
\ No newline at end of file diff --git a/book/rust-docs/logins/type.Result.html b/book/rust-docs/logins/type.Result.html new file mode 100644 index 0000000000..6f03b32b9a --- /dev/null +++ b/book/rust-docs/logins/type.Result.html @@ -0,0 +1 @@ +Result in logins - Rust

Type Definition logins::Result

source ·
pub type Result<T> = Result<T, Error>;
\ No newline at end of file diff --git a/book/rust-docs/nimbus/all.html b/book/rust-docs/nimbus/all.html new file mode 100644 index 0000000000..c988b7fdef --- /dev/null +++ b/book/rust-docs/nimbus/all.html @@ -0,0 +1 @@ +List of all items in this crate

List of all items

Structs

Enums

Traits

Functions

Type Definitions

Constants

\ No newline at end of file diff --git a/book/rust-docs/nimbus/enrollment/enum.EnrollmentStatus.html b/book/rust-docs/nimbus/enrollment/enum.EnrollmentStatus.html new file mode 100644 index 0000000000..062c0e30c5 --- /dev/null +++ b/book/rust-docs/nimbus/enrollment/enum.EnrollmentStatus.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../nimbus/enum.EnrollmentStatus.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nimbus/enrollment/struct.EnrolledFeature.html b/book/rust-docs/nimbus/enrollment/struct.EnrolledFeature.html new file mode 100644 index 0000000000..8060b4b2b7 --- /dev/null +++ b/book/rust-docs/nimbus/enrollment/struct.EnrolledFeature.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../nimbus/struct.EnrolledFeature.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nimbus/enum.EnrollmentStatus.html b/book/rust-docs/nimbus/enum.EnrollmentStatus.html new file mode 100644 index 0000000000..c3bf20a1af --- /dev/null +++ b/book/rust-docs/nimbus/enum.EnrollmentStatus.html @@ -0,0 +1,39 @@ +EnrollmentStatus in nimbus - Rust
pub enum EnrollmentStatus {
+    Enrolled {
+        reason: EnrolledReason,
+        branch: String,
+    },
+    NotEnrolled {
+        reason: NotEnrolledReason,
+    },
+    Disqualified {
+        reason: DisqualifiedReason,
+        branch: String,
+    },
+    WasEnrolled {
+        branch: String,
+        experiment_ended_at: u64,
+    },
+    Error {
+        reason: String,
+    },
+}

Variants§

§

Enrolled

Fields

§reason: EnrolledReason
§branch: String
§

NotEnrolled

Fields

§reason: NotEnrolledReason
§

Disqualified

Fields

§reason: DisqualifiedReason
§branch: String
§

WasEnrolled

Fields

§branch: String
§experiment_ended_at: u64
§

Error

Fields

§reason: String

Implementations§

source§

impl EnrollmentStatus

source

pub fn name(&self) -> String

source§

impl EnrollmentStatus

source

pub fn new_enrolled(reason: EnrolledReason, branch: &str) -> Self

source

pub fn is_enrolled(&self) -> bool

Trait Implementations§

source§

impl Clone for EnrollmentStatus

source§

fn clone(&self) -> EnrollmentStatus

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for EnrollmentStatus

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl<'de> Deserialize<'de> for EnrollmentStatus

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl Hash for EnrollmentStatus

source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · source§

fn hash_slice<H>(data: &[Self], state: &mut H)where + H: Hasher, + Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
source§

impl PartialEq<EnrollmentStatus> for EnrollmentStatus

source§

fn eq(&self, other: &EnrollmentStatus) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Serialize for EnrollmentStatus

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more
source§

impl Eq for EnrollmentStatus

source§

impl StructuralEq for EnrollmentStatus

source§

impl StructuralPartialEq for EnrollmentStatus

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> EncodableKey for Twhere + T: Serialize,

§

fn to_bytes(&self) -> Result<Vec<u8, Global>, DataError>

source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> Same<T> for T

§

type Output = T

Should always be Self
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/nimbus/error/enum.BehaviorError.html b/book/rust-docs/nimbus/error/enum.BehaviorError.html new file mode 100644 index 0000000000..bdd405c2d8 --- /dev/null +++ b/book/rust-docs/nimbus/error/enum.BehaviorError.html @@ -0,0 +1,19 @@ +BehaviorError in nimbus::error - Rust
pub enum BehaviorError {
+    InvalidState(String),
+    InvalidDuration(String),
+    IntervalParseError(String),
+    MissingEventStore,
+}

Variants§

§

InvalidState(String)

§

InvalidDuration(String)

§

IntervalParseError(String)

§

MissingEventStore

Trait Implementations§

source§

impl Debug for BehaviorError

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Display for BehaviorError

source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Error for BehaviorError

1.30.0 · source§

fn source(&self) -> Option<&(dyn Error + 'static)>

The lower-level source of this error, if any. Read more
1.0.0 · source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type based access to context intended for error reports. Read more
source§

impl From<BehaviorError> for NimbusError

source§

fn from(source: BehaviorError) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<E> Provider for Ewhere + E: Error + ?Sized,

source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (provide_any)
Data providers should implement this method to provide all values they are able to +provide by using demand. Read more
source§

impl<T> Same<T> for T

§

type Output = T

Should always be Self
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/nimbus/error/enum.NimbusError.html b/book/rust-docs/nimbus/error/enum.NimbusError.html new file mode 100644 index 0000000000..a796259ee0 --- /dev/null +++ b/book/rust-docs/nimbus/error/enum.NimbusError.html @@ -0,0 +1,40 @@ +NimbusError in nimbus::error - Rust
pub enum NimbusError {
+
Show 25 variants InvalidPersistedData, + RkvError(StoreError), + IOError(Error), + JSONError(Error), + EvaluationError(String), + InvalidExpression, + InvalidFraction, + TryFromSliceError(TryFromSliceError), + EmptyRatiosError, + OutOfBoundsError, + UrlParsingError(ParseError), + UuidError(Error), + InvalidExperimentFormat, + InvalidPath(String), + InternalError(&'static str), + NoSuchExperiment(String), + NoSuchBranch(String, String), + DatabaseNotReady, + VersionParsingError(String), + BehaviorError(BehaviorError), + TryFromIntError(TryFromIntError), + ParseIntError(ParseIntError), + TransformParameterError(String), + ClientError(RemoteSettingsError), + UniFFICallbackError(UnexpectedUniFFICallbackError), +
}

Variants§

§

InvalidPersistedData

§

RkvError(StoreError)

§

IOError(Error)

§

JSONError(Error)

§

EvaluationError(String)

§

InvalidExpression

§

InvalidFraction

§

TryFromSliceError(TryFromSliceError)

§

EmptyRatiosError

§

OutOfBoundsError

§

UrlParsingError(ParseError)

§

UuidError(Error)

§

InvalidExperimentFormat

§

InvalidPath(String)

§

InternalError(&'static str)

§

NoSuchExperiment(String)

§

NoSuchBranch(String, String)

§

DatabaseNotReady

§

VersionParsingError(String)

§

BehaviorError(BehaviorError)

§

TryFromIntError(TryFromIntError)

§

ParseIntError(ParseIntError)

§

TransformParameterError(String)

§

ClientError(RemoteSettingsError)

§

UniFFICallbackError(UnexpectedUniFFICallbackError)

Trait Implementations§

source§

impl Debug for NimbusError

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Display for NimbusError

source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Error for NimbusError

source§

fn source(&self) -> Option<&(dyn Error + 'static)>

The lower-level source of this error, if any. Read more
1.0.0 · source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type based access to context intended for error reports. Read more
source§

impl From<BehaviorError> for NimbusError

source§

fn from(source: BehaviorError) -> Self

Converts to this type from the input type.
source§

impl From<Error> for NimbusError

source§

fn from(source: Error) -> Self

Converts to this type from the input type.
source§

impl From<Error> for NimbusError

source§

fn from(source: Error) -> Self

Converts to this type from the input type.
source§

impl From<Error> for NimbusError

source§

fn from(source: Error) -> Self

Converts to this type from the input type.
source§

impl<'a> From<EvaluationError<'a>> for NimbusError

source§

fn from(eval_error: EvaluationError<'a>) -> Self

Converts to this type from the input type.
source§

impl From<ParseError> for NimbusError

source§

fn from(source: ParseError) -> Self

Converts to this type from the input type.
source§

impl From<ParseIntError> for NimbusError

source§

fn from(source: ParseIntError) -> Self

Converts to this type from the input type.
source§

impl From<RemoteSettingsError> for NimbusError

source§

fn from(source: RemoteSettingsError) -> Self

Converts to this type from the input type.
source§

impl From<StoreError> for NimbusError

source§

fn from(source: StoreError) -> Self

Converts to this type from the input type.
source§

impl From<TryFromIntError> for NimbusError

source§

fn from(source: TryFromIntError) -> Self

Converts to this type from the input type.
source§

impl From<TryFromSliceError> for NimbusError

source§

fn from(source: TryFromSliceError) -> Self

Converts to this type from the input type.
source§

impl From<UnexpectedUniFFICallbackError> for NimbusError

source§

fn from(source: UnexpectedUniFFICallbackError) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<E> Provider for Ewhere + E: Error + ?Sized,

source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (provide_any)
Data providers should implement this method to provide all values they are able to +provide by using demand. Read more
source§

impl<T> Same<T> for T

§

type Output = T

Should always be Self
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/nimbus/error/index.html b/book/rust-docs/nimbus/error/index.html new file mode 100644 index 0000000000..4372fe532d --- /dev/null +++ b/book/rust-docs/nimbus/error/index.html @@ -0,0 +1,5 @@ +nimbus::error - Rust

Module nimbus::error

source ·
Expand description

Not complete yet +This is where the error definitions can go +TODO: Implement proper error handling, this would include defining the error enum, +impl std::error::Error using thiserror and ensuring all errors are handled appropriately

+

Enums

Type Definitions

\ No newline at end of file diff --git a/book/rust-docs/nimbus/error/sidebar-items.js b/book/rust-docs/nimbus/error/sidebar-items.js new file mode 100644 index 0000000000..9746d09b99 --- /dev/null +++ b/book/rust-docs/nimbus/error/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["BehaviorError","NimbusError"],"type":["Result"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus/error/type.Result.html b/book/rust-docs/nimbus/error/type.Result.html new file mode 100644 index 0000000000..6030ea13bc --- /dev/null +++ b/book/rust-docs/nimbus/error/type.Result.html @@ -0,0 +1 @@ +Result in nimbus::error - Rust

Type Definition nimbus::error::Result

source ·
pub type Result<T, E = NimbusError> = Result<T, E>;
\ No newline at end of file diff --git a/book/rust-docs/nimbus/evaluator/fn.evaluate_enrollment.html b/book/rust-docs/nimbus/evaluator/fn.evaluate_enrollment.html new file mode 100644 index 0000000000..ce663e1f00 --- /dev/null +++ b/book/rust-docs/nimbus/evaluator/fn.evaluate_enrollment.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../nimbus/fn.evaluate_enrollment.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nimbus/fn.evaluate_enrollment.html b/book/rust-docs/nimbus/fn.evaluate_enrollment.html new file mode 100644 index 0000000000..95514db33d --- /dev/null +++ b/book/rust-docs/nimbus/fn.evaluate_enrollment.html @@ -0,0 +1,21 @@ +evaluate_enrollment in nimbus - Rust
pub fn evaluate_enrollment(
+    available_randomization_units: &AvailableRandomizationUnits,
+    exp: &Experiment,
+    th: &NimbusTargetingHelper
+) -> Result<ExperimentEnrollment>
Expand description

Determine the enrolment status for an experiment.

+

Arguments:

+
    +
  • available_randomization_units The app provded available randomization units
  • +
  • targeting_attributes The attributes to use when evaluating targeting
  • +
  • exp The Experiment to evaluate.
  • +
+

Returns:

+

An ExperimentEnrollment - you need to inspect the EnrollmentStatus to +determine if the user is actually enrolled.

+

Errors:

+

The function can return errors in one of the following cases (but not limited to):

+
    +
  • If the bucket sampling failed (i.e we could not find if the user should or should not be enrolled in the experiment based on the bucketing)
  • +
  • If an error occurs while determining the branch the user should be enrolled in any of the experiments
  • +
+
\ No newline at end of file diff --git a/book/rust-docs/nimbus/index.html b/book/rust-docs/nimbus/index.html new file mode 100644 index 0000000000..ee3e08be72 --- /dev/null +++ b/book/rust-docs/nimbus/index.html @@ -0,0 +1,5 @@ +nimbus - Rust

Crate nimbus

source ·

Re-exports

Modules

  • Not complete yet +This is where the error definitions can go +TODO: Implement proper error handling, this would include defining the error enum, +impl std::error::Error using thiserror and ensuring all errors are handled appropriately
  • Nimbus SDK App Version Comparison

Structs

Enums

Functions

\ No newline at end of file diff --git a/book/rust-docs/nimbus/metrics/index.html b/book/rust-docs/nimbus/metrics/index.html new file mode 100644 index 0000000000..31825f4515 --- /dev/null +++ b/book/rust-docs/nimbus/metrics/index.html @@ -0,0 +1 @@ +nimbus::metrics - Rust
\ No newline at end of file diff --git a/book/rust-docs/nimbus/metrics/sidebar-items.js b/book/rust-docs/nimbus/metrics/sidebar-items.js new file mode 100644 index 0000000000..55f6dc34ea --- /dev/null +++ b/book/rust-docs/nimbus/metrics/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"struct":["EnrollmentStatusExtraDef"],"trait":["MetricsHandler"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus/metrics/struct.EnrollmentStatusExtraDef.html b/book/rust-docs/nimbus/metrics/struct.EnrollmentStatusExtraDef.html new file mode 100644 index 0000000000..ee778ff3b6 --- /dev/null +++ b/book/rust-docs/nimbus/metrics/struct.EnrollmentStatusExtraDef.html @@ -0,0 +1,23 @@ +EnrollmentStatusExtraDef in nimbus::metrics - Rust
pub struct EnrollmentStatusExtraDef {
+    pub branch: Option<String>,
+    pub conflict_slug: Option<String>,
+    pub error_string: Option<String>,
+    pub reason: Option<String>,
+    pub slug: Option<String>,
+    pub status: Option<String>,
+}

Fields§

§branch: Option<String>§conflict_slug: Option<String>§error_string: Option<String>§reason: Option<String>§slug: Option<String>§status: Option<String>

Trait Implementations§

source§

impl Clone for EnrollmentStatusExtraDef

source§

fn clone(&self) -> EnrollmentStatusExtraDef

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl<'de> Deserialize<'de> for EnrollmentStatusExtraDef

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl Serialize for EnrollmentStatusExtraDef

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> EncodableKey for Twhere + T: Serialize,

§

fn to_bytes(&self) -> Result<Vec<u8, Global>, DataError>

source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> Same<T> for T

§

type Output = T

Should always be Self
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/nimbus/metrics/trait.MetricsHandler.html b/book/rust-docs/nimbus/metrics/trait.MetricsHandler.html new file mode 100644 index 0000000000..4e4da42065 --- /dev/null +++ b/book/rust-docs/nimbus/metrics/trait.MetricsHandler.html @@ -0,0 +1,10 @@ +MetricsHandler in nimbus::metrics - Rust
pub trait MetricsHandler: Send + Sync {
+    // Required method
+    fn record_enrollment_statuses(
+        &self,
+        enrollment_status_extras: Vec<EnrollmentStatusExtraDef>
+    );
+}

Required Methods§

source

fn record_enrollment_statuses( + &self, + enrollment_status_extras: Vec<EnrollmentStatusExtraDef> +)

Implementors§

\ No newline at end of file diff --git a/book/rust-docs/nimbus/schema/enum.RandomizationUnit.html b/book/rust-docs/nimbus/schema/enum.RandomizationUnit.html new file mode 100644 index 0000000000..aa1973b58d --- /dev/null +++ b/book/rust-docs/nimbus/schema/enum.RandomizationUnit.html @@ -0,0 +1,22 @@ +RandomizationUnit in nimbus::schema - Rust
pub enum RandomizationUnit {
+    NimbusId,
+    ClientId,
+    UserId,
+}

Variants§

§

NimbusId

§

ClientId

§

UserId

Trait Implementations§

source§

impl Clone for RandomizationUnit

source§

fn clone(&self) -> RandomizationUnit

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for RandomizationUnit

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for RandomizationUnit

source§

fn default() -> Self

Returns the “default value” for a type. Read more
source§

impl<'de> Deserialize<'de> for RandomizationUnit

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl PartialEq<RandomizationUnit> for RandomizationUnit

source§

fn eq(&self, other: &RandomizationUnit) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Serialize for RandomizationUnit

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more
source§

impl Eq for RandomizationUnit

source§

impl StructuralEq for RandomizationUnit

source§

impl StructuralPartialEq for RandomizationUnit

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> EncodableKey for Twhere + T: Serialize,

§

fn to_bytes(&self) -> Result<Vec<u8, Global>, DataError>

source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> Same<T> for T

§

type Output = T

Should always be Self
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/nimbus/schema/fn.parse_experiments.html b/book/rust-docs/nimbus/schema/fn.parse_experiments.html new file mode 100644 index 0000000000..77b4ac25d1 --- /dev/null +++ b/book/rust-docs/nimbus/schema/fn.parse_experiments.html @@ -0,0 +1 @@ +parse_experiments in nimbus::schema - Rust
pub fn parse_experiments(payload: &str) -> Result<Vec<Experiment>>
\ No newline at end of file diff --git a/book/rust-docs/nimbus/schema/index.html b/book/rust-docs/nimbus/schema/index.html new file mode 100644 index 0000000000..bed3a6a141 --- /dev/null +++ b/book/rust-docs/nimbus/schema/index.html @@ -0,0 +1 @@ +nimbus::schema - Rust
\ No newline at end of file diff --git a/book/rust-docs/nimbus/schema/sidebar-items.js b/book/rust-docs/nimbus/schema/sidebar-items.js new file mode 100644 index 0000000000..9a1b446564 --- /dev/null +++ b/book/rust-docs/nimbus/schema/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["RandomizationUnit"],"fn":["parse_experiments"],"struct":["AvailableExperiment","AvailableRandomizationUnits","Branch","BucketConfig","EnrolledExperiment","Experiment","ExperimentBranch","FeatureConfig"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus/schema/struct.AvailableExperiment.html b/book/rust-docs/nimbus/schema/struct.AvailableExperiment.html new file mode 100644 index 0000000000..4fce50480a --- /dev/null +++ b/book/rust-docs/nimbus/schema/struct.AvailableExperiment.html @@ -0,0 +1,17 @@ +AvailableExperiment in nimbus::schema - Rust
pub struct AvailableExperiment {
+    pub slug: String,
+    pub user_facing_name: String,
+    pub user_facing_description: String,
+    pub branches: Vec<ExperimentBranch>,
+    pub reference_branch: Option<String>,
+}

Fields§

§slug: String§user_facing_name: String§user_facing_description: String§branches: Vec<ExperimentBranch>§reference_branch: Option<String>

Trait Implementations§

source§

impl From<Experiment> for AvailableExperiment

source§

fn from(exp: Experiment) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> Same<T> for T

§

type Output = T

Should always be Self
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/nimbus/schema/struct.AvailableRandomizationUnits.html b/book/rust-docs/nimbus/schema/struct.AvailableRandomizationUnits.html new file mode 100644 index 0000000000..0bd18224b8 --- /dev/null +++ b/book/rust-docs/nimbus/schema/struct.AvailableRandomizationUnits.html @@ -0,0 +1,16 @@ +AvailableRandomizationUnits in nimbus::schema - Rust
pub struct AvailableRandomizationUnits {
+    pub client_id: Option<String>,
+    pub user_id: Option<String>,
+    pub nimbus_id: Option<String>,
+    /* private fields */
+}

Fields§

§client_id: Option<String>§user_id: Option<String>§nimbus_id: Option<String>

Implementations§

source§

impl AvailableRandomizationUnits

source

pub fn with_client_id(client_id: &str) -> Self

source

pub fn with_user_id(user_id: &str) -> Self

source

pub fn with_nimbus_id(nimbus_id: &Uuid) -> Self

source

pub fn apply_nimbus_id(&self, nimbus_id: &Uuid) -> Self

source

pub fn get_value<'a>(&'a self, wanted: &'a RandomizationUnit) -> Option<&'a str>

Trait Implementations§

source§

impl Default for AvailableRandomizationUnits

source§

fn default() -> AvailableRandomizationUnits

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> Same<T> for T

§

type Output = T

Should always be Self
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/nimbus/schema/struct.Branch.html b/book/rust-docs/nimbus/schema/struct.Branch.html new file mode 100644 index 0000000000..82314ca975 --- /dev/null +++ b/book/rust-docs/nimbus/schema/struct.Branch.html @@ -0,0 +1,23 @@ +Branch in nimbus::schema - Rust

Struct nimbus::schema::Branch

source ·
pub struct Branch {
+    pub slug: String,
+    pub ratio: i32,
+    pub feature: Option<FeatureConfig>,
+    pub features: Option<Vec<FeatureConfig>>,
+}

Fields§

§slug: String§ratio: i32§feature: Option<FeatureConfig>§features: Option<Vec<FeatureConfig>>

Trait Implementations§

source§

impl Clone for Branch

source§

fn clone(&self) -> Branch

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for Branch

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for Branch

source§

fn default() -> Branch

Returns the “default value” for a type. Read more
source§

impl<'de> Deserialize<'de> for Branch

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl From<Branch> for ExperimentBranch

source§

fn from(branch: Branch) -> Self

Converts to this type from the input type.
source§

impl PartialEq<Branch> for Branch

source§

fn eq(&self, other: &Branch) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Serialize for Branch

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more
source§

impl Eq for Branch

source§

impl StructuralEq for Branch

source§

impl StructuralPartialEq for Branch

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> EncodableKey for Twhere + T: Serialize,

§

fn to_bytes(&self) -> Result<Vec<u8, Global>, DataError>

source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> Same<T> for T

§

type Output = T

Should always be Self
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/nimbus/schema/struct.BucketConfig.html b/book/rust-docs/nimbus/schema/struct.BucketConfig.html new file mode 100644 index 0000000000..1fb3270e8f --- /dev/null +++ b/book/rust-docs/nimbus/schema/struct.BucketConfig.html @@ -0,0 +1,24 @@ +BucketConfig in nimbus::schema - Rust

Struct nimbus::schema::BucketConfig

source ·
pub struct BucketConfig {
+    pub randomization_unit: RandomizationUnit,
+    pub namespace: String,
+    pub start: u32,
+    pub count: u32,
+    pub total: u32,
+}

Fields§

§randomization_unit: RandomizationUnit§namespace: String§start: u32§count: u32§total: u32

Trait Implementations§

source§

impl Clone for BucketConfig

source§

fn clone(&self) -> BucketConfig

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for BucketConfig

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for BucketConfig

source§

fn default() -> BucketConfig

Returns the “default value” for a type. Read more
source§

impl<'de> Deserialize<'de> for BucketConfig

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl PartialEq<BucketConfig> for BucketConfig

source§

fn eq(&self, other: &BucketConfig) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Serialize for BucketConfig

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more
source§

impl Eq for BucketConfig

source§

impl StructuralEq for BucketConfig

source§

impl StructuralPartialEq for BucketConfig

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> EncodableKey for Twhere + T: Serialize,

§

fn to_bytes(&self) -> Result<Vec<u8, Global>, DataError>

source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> Same<T> for T

§

type Output = T

Should always be Self
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/nimbus/schema/struct.EnrolledExperiment.html b/book/rust-docs/nimbus/schema/struct.EnrolledExperiment.html new file mode 100644 index 0000000000..a695c1c05e --- /dev/null +++ b/book/rust-docs/nimbus/schema/struct.EnrolledExperiment.html @@ -0,0 +1,18 @@ +EnrolledExperiment in nimbus::schema - Rust
pub struct EnrolledExperiment {
+    pub feature_ids: Vec<String>,
+    pub slug: String,
+    pub user_facing_name: String,
+    pub user_facing_description: String,
+    pub branch_slug: String,
+}

Fields§

§feature_ids: Vec<String>§slug: String§user_facing_name: String§user_facing_description: String§branch_slug: String

Trait Implementations§

source§

impl Clone for EnrolledExperiment

source§

fn clone(&self) -> EnrolledExperiment

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for EnrolledExperiment

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> Same<T> for T

§

type Output = T

Should always be Self
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/nimbus/schema/struct.Experiment.html b/book/rust-docs/nimbus/schema/struct.Experiment.html new file mode 100644 index 0000000000..177aa88713 --- /dev/null +++ b/book/rust-docs/nimbus/schema/struct.Experiment.html @@ -0,0 +1,37 @@ +Experiment in nimbus::schema - Rust

Struct nimbus::schema::Experiment

source ·
pub struct Experiment {
Show 18 fields + pub schema_version: String, + pub slug: String, + pub app_name: Option<String>, + pub app_id: Option<String>, + pub channel: Option<String>, + pub user_facing_name: String, + pub user_facing_description: String, + pub is_enrollment_paused: bool, + pub bucket_config: BucketConfig, + pub branches: Vec<Branch>, + pub feature_ids: Vec<String>, + pub targeting: Option<String>, + pub start_date: Option<String>, + pub end_date: Option<String>, + pub proposed_duration: Option<u32>, + pub proposed_enrollment: u32, + pub reference_branch: Option<String>, + pub is_rollout: bool, +
}

Fields§

§schema_version: String§slug: String§app_name: Option<String>§app_id: Option<String>§channel: Option<String>§user_facing_name: String§user_facing_description: String§is_enrollment_paused: bool§bucket_config: BucketConfig§branches: Vec<Branch>§feature_ids: Vec<String>§targeting: Option<String>§start_date: Option<String>§end_date: Option<String>§proposed_duration: Option<u32>§proposed_enrollment: u32§reference_branch: Option<String>§is_rollout: bool

Trait Implementations§

source§

impl Clone for Experiment

source§

fn clone(&self) -> Experiment

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for Experiment

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for Experiment

source§

fn default() -> Experiment

Returns the “default value” for a type. Read more
source§

impl<'de> Deserialize<'de> for Experiment

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl From<Experiment> for AvailableExperiment

source§

fn from(exp: Experiment) -> Self

Converts to this type from the input type.
source§

impl PartialEq<Experiment> for Experiment

source§

fn eq(&self, other: &Experiment) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Serialize for Experiment

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more
source§

impl Eq for Experiment

source§

impl StructuralEq for Experiment

source§

impl StructuralPartialEq for Experiment

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> EncodableKey for Twhere + T: Serialize,

§

fn to_bytes(&self) -> Result<Vec<u8, Global>, DataError>

source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> Same<T> for T

§

type Output = T

Should always be Self
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/nimbus/schema/struct.ExperimentBranch.html b/book/rust-docs/nimbus/schema/struct.ExperimentBranch.html new file mode 100644 index 0000000000..adf322f458 --- /dev/null +++ b/book/rust-docs/nimbus/schema/struct.ExperimentBranch.html @@ -0,0 +1,14 @@ +ExperimentBranch in nimbus::schema - Rust
pub struct ExperimentBranch {
+    pub slug: String,
+    pub ratio: i32,
+}

Fields§

§slug: String§ratio: i32

Trait Implementations§

source§

impl From<Branch> for ExperimentBranch

source§

fn from(branch: Branch) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> Same<T> for T

§

type Output = T

Should always be Self
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/nimbus/schema/struct.FeatureConfig.html b/book/rust-docs/nimbus/schema/struct.FeatureConfig.html new file mode 100644 index 0000000000..84c56968f2 --- /dev/null +++ b/book/rust-docs/nimbus/schema/struct.FeatureConfig.html @@ -0,0 +1,21 @@ +FeatureConfig in nimbus::schema - Rust
pub struct FeatureConfig {
+    pub feature_id: String,
+    pub value: Map<String, Value>,
+}

Fields§

§feature_id: String§value: Map<String, Value>

Trait Implementations§

source§

impl Clone for FeatureConfig

source§

fn clone(&self) -> FeatureConfig

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for FeatureConfig

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for FeatureConfig

source§

fn default() -> FeatureConfig

Returns the “default value” for a type. Read more
source§

impl<'de> Deserialize<'de> for FeatureConfig

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl PartialEq<FeatureConfig> for FeatureConfig

source§

fn eq(&self, other: &FeatureConfig) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Serialize for FeatureConfig

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more
source§

impl Eq for FeatureConfig

source§

impl StructuralEq for FeatureConfig

source§

impl StructuralPartialEq for FeatureConfig

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> EncodableKey for Twhere + T: Serialize,

§

fn to_bytes(&self) -> Result<Vec<u8, Global>, DataError>

source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> Same<T> for T

§

type Output = T

Should always be Self
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/nimbus/sidebar-items.js b/book/rust-docs/nimbus/sidebar-items.js new file mode 100644 index 0000000000..73fc0c46c2 --- /dev/null +++ b/book/rust-docs/nimbus/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["EnrollmentStatus"],"fn":["evaluate_enrollment"],"mod":["error","metrics","schema","stateful","versioning"],"struct":["EnrolledFeature","NimbusTargetingHelper","RemoteSettingsConfig"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus/stateful/behavior/enum.EventQueryType.html b/book/rust-docs/nimbus/stateful/behavior/enum.EventQueryType.html new file mode 100644 index 0000000000..00a8747a78 --- /dev/null +++ b/book/rust-docs/nimbus/stateful/behavior/enum.EventQueryType.html @@ -0,0 +1,25 @@ +EventQueryType in nimbus::stateful::behavior - Rust
pub enum EventQueryType {
+    Sum,
+    CountNonZero,
+    AveragePerInterval,
+    AveragePerNonZeroInterval,
+    LastSeen,
+}

Variants§

§

Sum

§

CountNonZero

§

AveragePerInterval

§

AveragePerNonZeroInterval

§

LastSeen

Implementations§

source§

impl EventQueryType

source

pub fn perform_query( + &self, + buckets: Iter<'_, u64>, + num_buckets: usize +) -> Result<f64>

source

pub fn validate_arguments( + &self, + args: &[Value] +) -> Result<(String, Interval, usize, usize)>

Trait Implementations§

source§

impl Debug for EventQueryType

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Display for EventQueryType

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> Same<T> for T

§

type Output = T

Should always be Self
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/nimbus/stateful/behavior/enum.Interval.html b/book/rust-docs/nimbus/stateful/behavior/enum.Interval.html new file mode 100644 index 0000000000..e83d0d0d1c --- /dev/null +++ b/book/rust-docs/nimbus/stateful/behavior/enum.Interval.html @@ -0,0 +1,32 @@ +Interval in nimbus::stateful::behavior - Rust
pub enum Interval {
+    Minutes,
+    Hours,
+    Days,
+    Weeks,
+    Months,
+    Years,
+}

Variants§

§

Minutes

§

Hours

§

Days

§

Weeks

§

Months

§

Years

Implementations§

source§

impl Interval

source

pub fn num_rotations( + &self, + then: DateTime<Utc>, + now: DateTime<Utc> +) -> Result<i32>

source

pub fn to_duration(&self, count: i64) -> Duration

Trait Implementations§

source§

impl Clone for Interval

source§

fn clone(&self) -> Interval

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for Interval

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl<'de> Deserialize<'de> for Interval

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl Display for Interval

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl FromStr for Interval

§

type Err = NimbusError

The associated error which can be returned from parsing.
source§

fn from_str(input: &str) -> Result<Self>

Parses a string s to return a value of this type. Read more
source§

impl Hash for Interval

source§

fn hash<H: Hasher>(&self, state: &mut H)

Feeds this value into the given Hasher. Read more
1.3.0 · source§

fn hash_slice<H>(data: &[Self], state: &mut H)where + H: Hasher, + Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
source§

impl PartialEq<Interval> for Interval

source§

fn eq(&self, other: &Self) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Serialize for Interval

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more
source§

impl Eq for Interval

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> EncodableKey for Twhere + T: Serialize,

§

fn to_bytes(&self) -> Result<Vec<u8, Global>, DataError>

source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> Same<T> for T

§

type Output = T

Should always be Self
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/nimbus/stateful/behavior/fn.query_event_store.html b/book/rust-docs/nimbus/stateful/behavior/fn.query_event_store.html new file mode 100644 index 0000000000..e02a4ed079 --- /dev/null +++ b/book/rust-docs/nimbus/stateful/behavior/fn.query_event_store.html @@ -0,0 +1,5 @@ +query_event_store in nimbus::stateful::behavior - Rust
pub fn query_event_store(
+    event_store: Arc<Mutex<EventStore>>,
+    query_type: EventQueryType,
+    args: &[Value]
+) -> Result<Value>
\ No newline at end of file diff --git a/book/rust-docs/nimbus/stateful/behavior/index.html b/book/rust-docs/nimbus/stateful/behavior/index.html new file mode 100644 index 0000000000..228e04aaf0 --- /dev/null +++ b/book/rust-docs/nimbus/stateful/behavior/index.html @@ -0,0 +1 @@ +nimbus::stateful::behavior - Rust
\ No newline at end of file diff --git a/book/rust-docs/nimbus/stateful/behavior/sidebar-items.js b/book/rust-docs/nimbus/stateful/behavior/sidebar-items.js new file mode 100644 index 0000000000..95d699fe31 --- /dev/null +++ b/book/rust-docs/nimbus/stateful/behavior/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["EventQueryType","Interval"],"fn":["query_event_store"],"struct":["EventStore","IntervalConfig","IntervalData","MultiIntervalCounter","SingleIntervalCounter"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus/stateful/behavior/struct.EventStore.html b/book/rust-docs/nimbus/stateful/behavior/struct.EventStore.html new file mode 100644 index 0000000000..17dad6f9c7 --- /dev/null +++ b/book/rust-docs/nimbus/stateful/behavior/struct.EventStore.html @@ -0,0 +1,34 @@ +EventStore in nimbus::stateful::behavior - Rust
pub struct EventStore { /* private fields */ }

Implementations§

source§

impl EventStore

source

pub fn new() -> Self

source

pub fn advance_datum(&mut self, duration: Duration)

source

pub fn read_from_db(&mut self, db: &Database) -> Result<()>

source

pub fn record_event( + &mut self, + count: u64, + event_id: &str, + now: Option<DateTime<Utc>> +) -> Result<()>

source

pub fn record_past_event( + &mut self, + count: u64, + event_id: &str, + now: Option<DateTime<Utc>>, + duration: Duration +) -> Result<()>

source

pub fn persist_data(&self, db: &Database) -> Result<()>

source

pub fn clear(&mut self, db: &Database) -> Result<()>

source

pub fn query( + &mut self, + event_id: &str, + interval: Interval, + num_buckets: usize, + starting_bucket: usize, + query_type: EventQueryType +) -> Result<f64>

Trait Implementations§

source§

impl Clone for EventStore

source§

fn clone(&self) -> EventStore

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for EventStore

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for EventStore

source§

fn default() -> EventStore

Returns the “default value” for a type. Read more
source§

impl<'de> Deserialize<'de> for EventStore

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl From<HashMap<String, MultiIntervalCounter, RandomState>> for EventStore

source§

fn from(event_store: HashMap<String, MultiIntervalCounter>) -> Self

Converts to this type from the input type.
source§

impl From<Vec<(String, MultiIntervalCounter), Global>> for EventStore

source§

fn from(event_store: Vec<(String, MultiIntervalCounter)>) -> Self

Converts to this type from the input type.
source§

impl Serialize for EventStore

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more
source§

impl TryFrom<&Database> for EventStore

§

type Error = NimbusError

The type returned in the event of a conversion error.
source§

fn try_from(db: &Database) -> Result<Self, NimbusError>

Performs the conversion.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> EncodableKey for Twhere + T: Serialize,

§

fn to_bytes(&self) -> Result<Vec<u8, Global>, DataError>

source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> Same<T> for T

§

type Output = T

Should always be Self
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/nimbus/stateful/behavior/struct.IntervalConfig.html b/book/rust-docs/nimbus/stateful/behavior/struct.IntervalConfig.html new file mode 100644 index 0000000000..4a3be5d058 --- /dev/null +++ b/book/rust-docs/nimbus/stateful/behavior/struct.IntervalConfig.html @@ -0,0 +1,16 @@ +IntervalConfig in nimbus::stateful::behavior - Rust
pub struct IntervalConfig { /* private fields */ }

Implementations§

source§

impl IntervalConfig

source

pub fn new(bucket_count: usize, interval: Interval) -> Self

Trait Implementations§

source§

impl Clone for IntervalConfig

source§

fn clone(&self) -> IntervalConfig

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for IntervalConfig

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for IntervalConfig

source§

fn default() -> Self

Returns the “default value” for a type. Read more
source§

impl<'de> Deserialize<'de> for IntervalConfig

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl Serialize for IntervalConfig

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> EncodableKey for Twhere + T: Serialize,

§

fn to_bytes(&self) -> Result<Vec<u8, Global>, DataError>

source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> Same<T> for T

§

type Output = T

Should always be Self
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/nimbus/stateful/behavior/struct.IntervalData.html b/book/rust-docs/nimbus/stateful/behavior/struct.IntervalData.html new file mode 100644 index 0000000000..e764f9cd67 --- /dev/null +++ b/book/rust-docs/nimbus/stateful/behavior/struct.IntervalData.html @@ -0,0 +1,16 @@ +IntervalData in nimbus::stateful::behavior - Rust
pub struct IntervalData { /* private fields */ }

Implementations§

source§

impl IntervalData

source

pub fn new(bucket_count: usize) -> Self

source

pub fn increment(&mut self, count: u64) -> Result<()>

source

pub fn increment_at(&mut self, index: usize, count: u64) -> Result<()>

source

pub fn rotate(&mut self, num_rotations: i32) -> Result<()>

Trait Implementations§

source§

impl Clone for IntervalData

source§

fn clone(&self) -> IntervalData

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for IntervalData

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for IntervalData

source§

fn default() -> Self

Returns the “default value” for a type. Read more
source§

impl<'de> Deserialize<'de> for IntervalData

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl Serialize for IntervalData

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> EncodableKey for Twhere + T: Serialize,

§

fn to_bytes(&self) -> Result<Vec<u8, Global>, DataError>

source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> Same<T> for T

§

type Output = T

Should always be Self
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/nimbus/stateful/behavior/struct.MultiIntervalCounter.html b/book/rust-docs/nimbus/stateful/behavior/struct.MultiIntervalCounter.html new file mode 100644 index 0000000000..d49ba6dee2 --- /dev/null +++ b/book/rust-docs/nimbus/stateful/behavior/struct.MultiIntervalCounter.html @@ -0,0 +1,18 @@ +MultiIntervalCounter in nimbus::stateful::behavior - Rust
pub struct MultiIntervalCounter {
+    pub intervals: HashMap<Interval, SingleIntervalCounter>,
+}

Fields§

§intervals: HashMap<Interval, SingleIntervalCounter>

Implementations§

source§

impl MultiIntervalCounter

source

pub fn new(intervals: Vec<SingleIntervalCounter>) -> Self

source

pub fn increment_then(&mut self, then: DateTime<Utc>, count: u64) -> Result<()>

source

pub fn increment(&mut self, count: u64) -> Result<()>

source

pub fn maybe_advance(&mut self, now: DateTime<Utc>) -> Result<()>

Trait Implementations§

source§

impl Clone for MultiIntervalCounter

source§

fn clone(&self) -> MultiIntervalCounter

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for MultiIntervalCounter

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for MultiIntervalCounter

source§

fn default() -> Self

Returns the “default value” for a type. Read more
source§

impl<'de> Deserialize<'de> for MultiIntervalCounter

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl Serialize for MultiIntervalCounter

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> EncodableKey for Twhere + T: Serialize,

§

fn to_bytes(&self) -> Result<Vec<u8, Global>, DataError>

source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> Same<T> for T

§

type Output = T

Should always be Self
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/nimbus/stateful/behavior/struct.SingleIntervalCounter.html b/book/rust-docs/nimbus/stateful/behavior/struct.SingleIntervalCounter.html new file mode 100644 index 0000000000..e8827a2b94 --- /dev/null +++ b/book/rust-docs/nimbus/stateful/behavior/struct.SingleIntervalCounter.html @@ -0,0 +1,19 @@ +SingleIntervalCounter in nimbus::stateful::behavior - Rust
pub struct SingleIntervalCounter {
+    pub data: IntervalData,
+    pub config: IntervalConfig,
+}

Fields§

§data: IntervalData§config: IntervalConfig

Implementations§

source§

impl SingleIntervalCounter

source

pub fn new(config: IntervalConfig) -> Self

source

pub fn from_config(bucket_count: usize, interval: Interval) -> Self

source

pub fn increment_then(&mut self, then: DateTime<Utc>, count: u64) -> Result<()>

source

pub fn increment(&mut self, count: u64) -> Result<()>

source

pub fn maybe_advance(&mut self, now: DateTime<Utc>) -> Result<()>

Trait Implementations§

source§

impl Clone for SingleIntervalCounter

source§

fn clone(&self) -> SingleIntervalCounter

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for SingleIntervalCounter

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl<'de> Deserialize<'de> for SingleIntervalCounter

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl Serialize for SingleIntervalCounter

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> EncodableKey for Twhere + T: Serialize,

§

fn to_bytes(&self) -> Result<Vec<u8, Global>, DataError>

source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> Same<T> for T

§

type Output = T

Should always be Self
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/nimbus/stateful/client/index.html b/book/rust-docs/nimbus/stateful/client/index.html new file mode 100644 index 0000000000..35b90f822f --- /dev/null +++ b/book/rust-docs/nimbus/stateful/client/index.html @@ -0,0 +1 @@ +nimbus::stateful::client - Rust

Module nimbus::stateful::client

source ·
\ No newline at end of file diff --git a/book/rust-docs/nimbus/stateful/client/sidebar-items.js b/book/rust-docs/nimbus/stateful/client/sidebar-items.js new file mode 100644 index 0000000000..5244ce01cc --- /dev/null +++ b/book/rust-docs/nimbus/stateful/client/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {}; \ No newline at end of file diff --git a/book/rust-docs/nimbus/stateful/dbcache/index.html b/book/rust-docs/nimbus/stateful/dbcache/index.html new file mode 100644 index 0000000000..3832700a43 --- /dev/null +++ b/book/rust-docs/nimbus/stateful/dbcache/index.html @@ -0,0 +1 @@ +nimbus::stateful::dbcache - Rust
\ No newline at end of file diff --git a/book/rust-docs/nimbus/stateful/dbcache/sidebar-items.js b/book/rust-docs/nimbus/stateful/dbcache/sidebar-items.js new file mode 100644 index 0000000000..eacc858e11 --- /dev/null +++ b/book/rust-docs/nimbus/stateful/dbcache/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"struct":["DatabaseCache"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus/stateful/dbcache/struct.DatabaseCache.html b/book/rust-docs/nimbus/stateful/dbcache/struct.DatabaseCache.html new file mode 100644 index 0000000000..62ba54f01f --- /dev/null +++ b/book/rust-docs/nimbus/stateful/dbcache/struct.DatabaseCache.html @@ -0,0 +1,22 @@ +DatabaseCache in nimbus::stateful::dbcache - Rust
pub struct DatabaseCache { /* private fields */ }

Implementations§

source§

impl DatabaseCache

source

pub fn commit_and_update( + &self, + db: &Database, + writer: Writer<'_>, + coenrolling_ids: &HashSet<&str> +) -> Result<()>

source

pub fn get_experiment_branch(&self, id: &str) -> Result<Option<String>>

source

pub fn get_feature_config_variables( + &self, + feature_id: &str +) -> Result<Option<String>>

source

pub fn get_enrollment_by_feature( + &self, + feature_id: &str +) -> Result<Option<EnrolledFeature>>

source

pub fn get_active_experiments(&self) -> Result<Vec<EnrolledExperiment>>

source

pub fn get_experiments(&self) -> Result<Vec<Experiment>>

source

pub fn get_enrollments(&self) -> Result<Vec<ExperimentEnrollment>>

Trait Implementations§

source§

impl Default for DatabaseCache

source§

fn default() -> DatabaseCache

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> Same<T> for T

§

type Output = T

Should always be Self
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/nimbus/stateful/enrollment/fn.get_enrollments.html b/book/rust-docs/nimbus/stateful/enrollment/fn.get_enrollments.html new file mode 100644 index 0000000000..59fe665442 --- /dev/null +++ b/book/rust-docs/nimbus/stateful/enrollment/fn.get_enrollments.html @@ -0,0 +1,6 @@ +get_enrollments in nimbus::stateful::enrollment - Rust
pub fn get_enrollments<'r>(
+    db: &Database,
+    reader: &'r impl Readable<'r>
+) -> Result<Vec<EnrolledExperiment>>
Expand description

Return information about all enrolled experiments. +Note this does not include rollouts

+
\ No newline at end of file diff --git a/book/rust-docs/nimbus/stateful/enrollment/fn.get_global_user_participation.html b/book/rust-docs/nimbus/stateful/enrollment/fn.get_global_user_participation.html new file mode 100644 index 0000000000..d4326e1825 --- /dev/null +++ b/book/rust-docs/nimbus/stateful/enrollment/fn.get_global_user_participation.html @@ -0,0 +1,4 @@ +get_global_user_participation in nimbus::stateful::enrollment - Rust
pub fn get_global_user_participation<'r>(
+    db: &Database,
+    reader: &'r impl Readable<'r>
+) -> Result<bool>
\ No newline at end of file diff --git a/book/rust-docs/nimbus/stateful/enrollment/fn.opt_in_with_branch.html b/book/rust-docs/nimbus/stateful/enrollment/fn.opt_in_with_branch.html new file mode 100644 index 0000000000..485b355bae --- /dev/null +++ b/book/rust-docs/nimbus/stateful/enrollment/fn.opt_in_with_branch.html @@ -0,0 +1,6 @@ +opt_in_with_branch in nimbus::stateful::enrollment - Rust
pub fn opt_in_with_branch(
+    db: &Database,
+    writer: &mut Writer<'_>,
+    experiment_slug: &str,
+    branch: &str
+) -> Result<Vec<EnrollmentChangeEvent>>
\ No newline at end of file diff --git a/book/rust-docs/nimbus/stateful/enrollment/fn.opt_out.html b/book/rust-docs/nimbus/stateful/enrollment/fn.opt_out.html new file mode 100644 index 0000000000..e78441b18c --- /dev/null +++ b/book/rust-docs/nimbus/stateful/enrollment/fn.opt_out.html @@ -0,0 +1,5 @@ +opt_out in nimbus::stateful::enrollment - Rust
pub fn opt_out(
+    db: &Database,
+    writer: &mut Writer<'_>,
+    experiment_slug: &str
+) -> Result<Vec<EnrollmentChangeEvent>>
\ No newline at end of file diff --git a/book/rust-docs/nimbus/stateful/enrollment/fn.reset_telemetry_identifiers.html b/book/rust-docs/nimbus/stateful/enrollment/fn.reset_telemetry_identifiers.html new file mode 100644 index 0000000000..694ed3666b --- /dev/null +++ b/book/rust-docs/nimbus/stateful/enrollment/fn.reset_telemetry_identifiers.html @@ -0,0 +1,5 @@ +reset_telemetry_identifiers in nimbus::stateful::enrollment - Rust
pub fn reset_telemetry_identifiers(
+    db: &Database,
+    writer: &mut Writer<'_>
+) -> Result<Vec<EnrollmentChangeEvent>>
Expand description

Reset unique identifiers in response to application-level telemetry reset.

+
\ No newline at end of file diff --git a/book/rust-docs/nimbus/stateful/enrollment/fn.set_global_user_participation.html b/book/rust-docs/nimbus/stateful/enrollment/fn.set_global_user_participation.html new file mode 100644 index 0000000000..9531f92391 --- /dev/null +++ b/book/rust-docs/nimbus/stateful/enrollment/fn.set_global_user_participation.html @@ -0,0 +1,5 @@ +set_global_user_participation in nimbus::stateful::enrollment - Rust
pub fn set_global_user_participation(
+    db: &Database,
+    writer: &mut Writer<'_>,
+    opt_in: bool
+) -> Result<()>
\ No newline at end of file diff --git a/book/rust-docs/nimbus/stateful/enrollment/index.html b/book/rust-docs/nimbus/stateful/enrollment/index.html new file mode 100644 index 0000000000..cdd025bf2a --- /dev/null +++ b/book/rust-docs/nimbus/stateful/enrollment/index.html @@ -0,0 +1,2 @@ +nimbus::stateful::enrollment - Rust

Module nimbus::stateful::enrollment

source ·

Functions

\ No newline at end of file diff --git a/book/rust-docs/nimbus/stateful/enrollment/sidebar-items.js b/book/rust-docs/nimbus/stateful/enrollment/sidebar-items.js new file mode 100644 index 0000000000..00290da90e --- /dev/null +++ b/book/rust-docs/nimbus/stateful/enrollment/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["get_enrollments","get_global_user_participation","opt_in_with_branch","opt_out","reset_telemetry_identifiers","set_global_user_participation"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus/stateful/evaluator/index.html b/book/rust-docs/nimbus/stateful/evaluator/index.html new file mode 100644 index 0000000000..4e89f2d12f --- /dev/null +++ b/book/rust-docs/nimbus/stateful/evaluator/index.html @@ -0,0 +1 @@ +nimbus::stateful::evaluator - Rust
\ No newline at end of file diff --git a/book/rust-docs/nimbus/stateful/evaluator/sidebar-items.js b/book/rust-docs/nimbus/stateful/evaluator/sidebar-items.js new file mode 100644 index 0000000000..41f8c0539c --- /dev/null +++ b/book/rust-docs/nimbus/stateful/evaluator/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"struct":["TargetingAttributes"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus/stateful/evaluator/struct.TargetingAttributes.html b/book/rust-docs/nimbus/stateful/evaluator/struct.TargetingAttributes.html new file mode 100644 index 0000000000..b08243732d --- /dev/null +++ b/book/rust-docs/nimbus/stateful/evaluator/struct.TargetingAttributes.html @@ -0,0 +1,26 @@ +TargetingAttributes in nimbus::stateful::evaluator - Rust
pub struct TargetingAttributes {
+    pub app_context: AppContext,
+    pub language: Option<String>,
+    pub region: Option<String>,
+    pub is_already_enrolled: bool,
+    pub days_since_install: Option<i32>,
+    pub days_since_update: Option<i32>,
+    pub active_experiments: HashSet<String>,
+    pub enrollments: HashSet<String>,
+    pub enrollments_map: HashMap<String, String>,
+}

Fields§

§app_context: AppContext§language: Option<String>§region: Option<String>§is_already_enrolled: bool§days_since_install: Option<i32>§days_since_update: Option<i32>§active_experiments: HashSet<String>§enrollments: HashSet<String>§enrollments_map: HashMap<String, String>

Trait Implementations§

source§

impl Clone for TargetingAttributes

source§

fn clone(&self) -> TargetingAttributes

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for TargetingAttributes

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for TargetingAttributes

source§

fn default() -> TargetingAttributes

Returns the “default value” for a type. Read more
source§

impl<'de> Deserialize<'de> for TargetingAttributes

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl From<AppContext> for TargetingAttributes

source§

fn from(app_context: AppContext) -> Self

Converts to this type from the input type.
source§

impl Serialize for TargetingAttributes

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> EncodableKey for Twhere + T: Serialize,

§

fn to_bytes(&self) -> Result<Vec<u8, Global>, DataError>

source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> Same<T> for T

§

type Output = T

Should always be Self
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/nimbus/stateful/index.html b/book/rust-docs/nimbus/stateful/index.html new file mode 100644 index 0000000000..1db133de5c --- /dev/null +++ b/book/rust-docs/nimbus/stateful/index.html @@ -0,0 +1,3 @@ +nimbus::stateful - Rust

Module nimbus::stateful

source ·

Modules

\ No newline at end of file diff --git a/book/rust-docs/nimbus/stateful/matcher/index.html b/book/rust-docs/nimbus/stateful/matcher/index.html new file mode 100644 index 0000000000..ab28cc533b --- /dev/null +++ b/book/rust-docs/nimbus/stateful/matcher/index.html @@ -0,0 +1,10 @@ +nimbus::stateful::matcher - Rust

Module nimbus::stateful::matcher

source ·
Expand description

This module defines all the information needed to match a user with an experiment. +Soon it will also include a match function of some sort that does the matching.

+

It contains the AppContext +provided by the consuming client.

+

Structs

  • The AppContext object represents the parameters and characteristics of the +consuming application that we are interested in for targeting purposes. The +app_name and channel fields are not optional as they are expected +to be provided by all consuming applications as they are used in the top-level +targeting that help to ensure that an experiment is only processed +by the correct application.
\ No newline at end of file diff --git a/book/rust-docs/nimbus/stateful/matcher/sidebar-items.js b/book/rust-docs/nimbus/stateful/matcher/sidebar-items.js new file mode 100644 index 0000000000..3e57743545 --- /dev/null +++ b/book/rust-docs/nimbus/stateful/matcher/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"struct":["AppContext"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus/stateful/matcher/struct.AppContext.html b/book/rust-docs/nimbus/stateful/matcher/struct.AppContext.html new file mode 100644 index 0000000000..0bb30b191f --- /dev/null +++ b/book/rust-docs/nimbus/stateful/matcher/struct.AppContext.html @@ -0,0 +1,58 @@ +AppContext in nimbus::stateful::matcher - Rust
pub struct AppContext {
Show 16 fields + pub app_name: String, + pub app_id: String, + pub channel: String, + pub app_version: Option<String>, + pub app_build: Option<String>, + pub architecture: Option<String>, + pub device_manufacturer: Option<String>, + pub device_model: Option<String>, + pub locale: Option<String>, + pub os: Option<String>, + pub os_version: Option<String>, + pub android_sdk_version: Option<String>, + pub debug_tag: Option<String>, + pub installation_date: Option<i64>, + pub home_directory: Option<String>, + pub custom_targeting_attributes: Option<Map<String, Value>>, +
}
Expand description

The AppContext object represents the parameters and characteristics of the +consuming application that we are interested in for targeting purposes. The +app_name and channel fields are not optional as they are expected +to be provided by all consuming applications as they are used in the top-level +targeting that help to ensure that an experiment is only processed +by the correct application.

+

Definitions of the fields are as follows:

+
    +
  • app_name: This is the name of the application (e.g. “Fenix” or “Firefox iOS”)
  • +
  • app_id: This is the application identifier, especially for mobile (e.g. “org.mozilla.fenix”)
  • +
  • channel: This is the delivery channel of the application (e.g “nightly”)
  • +
  • app_version: The user visible version string (e.g. “1.0.3”)
  • +
  • app_build: The build identifier generated by the CI system (e.g. “1234/A”)
  • +
  • architecture: The architecture of the device, (e.g. “arm”, “x86”)
  • +
  • device_manufacturer: The manufacturer of the device the application is running on
  • +
  • device_model: The model of the device the application is running on
  • +
  • locale: The locale of the application during initialization (e.g. “es-ES”)
  • +
  • os: The name of the operating system (e.g. “Android”, “iOS”, “Darwin”, “Windows”)
  • +
  • os_version: The user-visible version of the operating system (e.g. “1.2.3”)
  • +
  • android_sdk_version: Android specific for targeting specific sdk versions
  • +
  • debug_tag: Used for debug purposes as a way to match only developer builds, etc.
  • +
  • installation_date: The date the application installed the app
  • +
  • home_directory: The application’s home directory
  • +
  • custom_targeting_attributes: Contains attributes specific to the application, derived by the application
  • +
+

Fields§

§app_name: String§app_id: String§channel: String§app_version: Option<String>§app_build: Option<String>§architecture: Option<String>§device_manufacturer: Option<String>§device_model: Option<String>§locale: Option<String>§os: Option<String>§os_version: Option<String>§android_sdk_version: Option<String>§debug_tag: Option<String>§installation_date: Option<i64>§home_directory: Option<String>§custom_targeting_attributes: Option<Map<String, Value>>

Trait Implementations§

source§

impl Clone for AppContext

source§

fn clone(&self) -> AppContext

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for AppContext

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for AppContext

source§

fn default() -> AppContext

Returns the “default value” for a type. Read more
source§

impl<'de> Deserialize<'de> for AppContext

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl From<AppContext> for TargetingAttributes

source§

fn from(app_context: AppContext) -> Self

Converts to this type from the input type.
source§

impl Serialize for AppContext

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> EncodableKey for Twhere + T: Serialize,

§

fn to_bytes(&self) -> Result<Vec<u8, Global>, DataError>

source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> Same<T> for T

§

type Output = T

Should always be Self
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/nimbus/stateful/nimbus_client/constant.DB_KEY_APP_VERSION.html b/book/rust-docs/nimbus/stateful/nimbus_client/constant.DB_KEY_APP_VERSION.html new file mode 100644 index 0000000000..4d23aa2684 --- /dev/null +++ b/book/rust-docs/nimbus/stateful/nimbus_client/constant.DB_KEY_APP_VERSION.html @@ -0,0 +1 @@ +DB_KEY_APP_VERSION in nimbus::stateful::nimbus_client - Rust
pub const DB_KEY_APP_VERSION: &str = "app-version";
\ No newline at end of file diff --git a/book/rust-docs/nimbus/stateful/nimbus_client/constant.DB_KEY_FETCH_ENABLED.html b/book/rust-docs/nimbus/stateful/nimbus_client/constant.DB_KEY_FETCH_ENABLED.html new file mode 100644 index 0000000000..ff5b70b24b --- /dev/null +++ b/book/rust-docs/nimbus/stateful/nimbus_client/constant.DB_KEY_FETCH_ENABLED.html @@ -0,0 +1 @@ +DB_KEY_FETCH_ENABLED in nimbus::stateful::nimbus_client - Rust
pub const DB_KEY_FETCH_ENABLED: &str = "fetch-enabled";
\ No newline at end of file diff --git a/book/rust-docs/nimbus/stateful/nimbus_client/constant.DB_KEY_INSTALLATION_DATE.html b/book/rust-docs/nimbus/stateful/nimbus_client/constant.DB_KEY_INSTALLATION_DATE.html new file mode 100644 index 0000000000..540ef919c6 --- /dev/null +++ b/book/rust-docs/nimbus/stateful/nimbus_client/constant.DB_KEY_INSTALLATION_DATE.html @@ -0,0 +1 @@ +DB_KEY_INSTALLATION_DATE in nimbus::stateful::nimbus_client - Rust
pub const DB_KEY_INSTALLATION_DATE: &str = "installation-date";
\ No newline at end of file diff --git a/book/rust-docs/nimbus/stateful/nimbus_client/constant.DB_KEY_UPDATE_DATE.html b/book/rust-docs/nimbus/stateful/nimbus_client/constant.DB_KEY_UPDATE_DATE.html new file mode 100644 index 0000000000..942bb2d5de --- /dev/null +++ b/book/rust-docs/nimbus/stateful/nimbus_client/constant.DB_KEY_UPDATE_DATE.html @@ -0,0 +1 @@ +DB_KEY_UPDATE_DATE in nimbus::stateful::nimbus_client - Rust
pub const DB_KEY_UPDATE_DATE: &str = "update-date";
\ No newline at end of file diff --git a/book/rust-docs/nimbus/stateful/nimbus_client/index.html b/book/rust-docs/nimbus/stateful/nimbus_client/index.html new file mode 100644 index 0000000000..19b8d18260 --- /dev/null +++ b/book/rust-docs/nimbus/stateful/nimbus_client/index.html @@ -0,0 +1,3 @@ +nimbus::stateful::nimbus_client - Rust

Structs

Constants

\ No newline at end of file diff --git a/book/rust-docs/nimbus/stateful/nimbus_client/sidebar-items.js b/book/rust-docs/nimbus/stateful/nimbus_client/sidebar-items.js new file mode 100644 index 0000000000..699bd747cf --- /dev/null +++ b/book/rust-docs/nimbus/stateful/nimbus_client/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"constant":["DB_KEY_APP_VERSION","DB_KEY_FETCH_ENABLED","DB_KEY_INSTALLATION_DATE","DB_KEY_UPDATE_DATE"],"struct":["InternalMutableState","NimbusClient","NimbusStringHelper"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus/stateful/nimbus_client/struct.InternalMutableState.html b/book/rust-docs/nimbus/stateful/nimbus_client/struct.InternalMutableState.html new file mode 100644 index 0000000000..056e6bc5b7 --- /dev/null +++ b/book/rust-docs/nimbus/stateful/nimbus_client/struct.InternalMutableState.html @@ -0,0 +1,11 @@ +InternalMutableState in nimbus::stateful::nimbus_client - Rust
pub struct InternalMutableState { /* private fields */ }

Trait Implementations§

source§

impl Default for InternalMutableState

source§

fn default() -> InternalMutableState

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> Same<T> for T

§

type Output = T

Should always be Self
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/nimbus/stateful/nimbus_client/struct.NimbusClient.html b/book/rust-docs/nimbus/stateful/nimbus_client/struct.NimbusClient.html new file mode 100644 index 0000000000..5be2873730 --- /dev/null +++ b/book/rust-docs/nimbus/stateful/nimbus_client/struct.NimbusClient.html @@ -0,0 +1,75 @@ +NimbusClient in nimbus::stateful::nimbus_client - Rust
pub struct NimbusClient { /* private fields */ }
Expand description

Nimbus is the main struct representing the experiments state +It should hold all the information needed to communicate a specific user’s +experimentation status

+

Implementations§

source§

impl NimbusClient

source

pub fn new<P: Into<PathBuf>>( + app_context: AppContext, + coenrolling_feature_ids: Vec<String>, + db_path: P, + config: Option<RemoteSettingsConfig>, + available_randomization_units: AvailableRandomizationUnits, + metrics_handler: Box<dyn MetricsHandler> +) -> Result<Self>

source

pub fn with_targeting_attributes( + &mut self, + targeting_attributes: TargetingAttributes +)

source

pub fn get_targeting_attributes(&self) -> TargetingAttributes

source

pub fn initialize(&self) -> Result<()>

source

pub fn get_enrollment_by_feature( + &self, + feature_id: String +) -> Result<Option<EnrolledFeature>>

source

pub fn get_experiment_branch(&self, slug: String) -> Result<Option<String>>

source

pub fn get_feature_config_variables( + &self, + feature_id: String +) -> Result<Option<String>>

source

pub fn get_experiment_branches( + &self, + slug: String +) -> Result<Vec<ExperimentBranch>>

source

pub fn get_global_user_participation(&self) -> Result<bool>

source

pub fn set_global_user_participation( + &self, + user_participating: bool +) -> Result<Vec<EnrollmentChangeEvent>>

source

pub fn get_active_experiments(&self) -> Result<Vec<EnrolledExperiment>>

source

pub fn get_all_experiments(&self) -> Result<Vec<Experiment>>

source

pub fn get_available_experiments(&self) -> Result<Vec<AvailableExperiment>>

source

pub fn opt_in_with_branch( + &self, + experiment_slug: String, + branch: String +) -> Result<Vec<EnrollmentChangeEvent>>

source

pub fn opt_out( + &self, + experiment_slug: String +) -> Result<Vec<EnrollmentChangeEvent>>

source

pub fn fetch_experiments(&self) -> Result<()>

source

pub fn set_fetch_enabled(&self, allow: bool) -> Result<()>

source

pub fn apply_pending_experiments(&self) -> Result<Vec<EnrollmentChangeEvent>>

source

pub fn set_experiments_locally(&self, experiments_json: String) -> Result<()>

source

pub fn reset_enrollments(&self) -> Result<()>

Reset all enrollments and experiments in the database.

+

This should only be used in testing.

+
source

pub fn reset_telemetry_identifiers( + &self, + new_randomization_units: AvailableRandomizationUnits +) -> Result<Vec<EnrollmentChangeEvent>>

Reset internal state in response to application-level telemetry reset.

+

When the user resets their telemetry state in the consuming application, we need learn +the new values of any external randomization units, and we need to reset any unique +identifiers used internally by the SDK. If we don’t then we risk accidentally tracking +across the telemetry reset, since we could use Nimbus metrics to link their pings from +before and after the reset.

+
source

pub fn nimbus_id(&self) -> Result<Uuid>

source

pub fn set_nimbus_id(&self, uuid: &Uuid) -> Result<()>

source

pub fn create_targeting_helper( + &self, + additional_context: Option<Map<String, Value>> +) -> Result<Arc<NimbusTargetingHelper>>

source

pub fn create_string_helper( + &self, + additional_context: Option<Map<String, Value>> +) -> Result<Arc<NimbusStringHelper>>

source

pub fn record_event(&self, event_id: String, count: i64) -> Result<()>

Records an event for the purposes of behavioral targeting.

+

This function is used to record and persist data used for the behavioral +targeting such as “core-active” user targeting.

+
source

pub fn record_past_event( + &self, + event_id: String, + seconds_ago: i64, + count: i64 +) -> Result<()>

Records an event for the purposes of behavioral targeting.

+

This differs from the record_event method in that the event is recorded as if it were +recorded seconds_ago in the past. This makes it very useful for testing.

+
source

pub fn advance_event_time(&self, by_seconds: i64) -> Result<()>

Advances the event store’s concept of now artificially.

+

This works alongside record_event and record_past_event for testing purposes.

+
source

pub fn clear_events(&self) -> Result<()>

Clear all events in the Nimbus event store.

+

This should only be used in testing or cases where the previous event store is no longer viable.

+
source

pub fn event_store(&self) -> Arc<Mutex<EventStore>>

source

pub fn dump_state_to_log(&self) -> Result<()>

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> Same<T> for T

§

type Output = T

Should always be Self
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/nimbus/stateful/nimbus_client/struct.NimbusStringHelper.html b/book/rust-docs/nimbus/stateful/nimbus_client/struct.NimbusStringHelper.html new file mode 100644 index 0000000000..3e40eee9e8 --- /dev/null +++ b/book/rust-docs/nimbus/stateful/nimbus_client/struct.NimbusStringHelper.html @@ -0,0 +1,11 @@ +NimbusStringHelper in nimbus::stateful::nimbus_client - Rust
pub struct NimbusStringHelper { /* private fields */ }

Implementations§

source§

impl NimbusStringHelper

source

pub fn get_uuid(&self, template: String) -> Option<String>

source

pub fn string_format(&self, template: String, uuid: Option<String>) -> String

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> Same<T> for T

§

type Output = T

Should always be Self
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/nimbus/stateful/persistence/backend/trait.Readable.html b/book/rust-docs/nimbus/stateful/persistence/backend/trait.Readable.html new file mode 100644 index 0000000000..e02ade78e8 --- /dev/null +++ b/book/rust-docs/nimbus/stateful/persistence/backend/trait.Readable.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../../nimbus/stateful/persistence/trait.Readable.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nimbus/stateful/persistence/backend/type.Writer.html b/book/rust-docs/nimbus/stateful/persistence/backend/type.Writer.html new file mode 100644 index 0000000000..66649ab200 --- /dev/null +++ b/book/rust-docs/nimbus/stateful/persistence/backend/type.Writer.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../../nimbus/stateful/persistence/type.Writer.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nimbus/stateful/persistence/enum.StoreId.html b/book/rust-docs/nimbus/stateful/persistence/enum.StoreId.html new file mode 100644 index 0000000000..ac2ec887ee --- /dev/null +++ b/book/rust-docs/nimbus/stateful/persistence/enum.StoreId.html @@ -0,0 +1,55 @@ +StoreId in nimbus::stateful::persistence - Rust
pub enum StoreId {
+    Experiments,
+    Enrollments,
+    Meta,
+    Updates,
+    EventCounts,
+}
Expand description

Enumeration of the different stores within our database.

+

Our rkv database contains a number of different “stores”, and the items +in each store correspond to a particular type of object at the Rust level.

+

Variants§

§

Experiments

Store containing the set of known experiments, as read from the server.

+

Keys in the Experiments store are experiment identifier slugs, and their +corresponding values are serialized instances of the Experiment struct +representing the last known state of that experiment.

+
§

Enrollments

Store containing the set of known experiment enrollments.

+

Keys in the Enrollments store are experiment identifier slugs, and their +corresponding values are serialized instances of the [ExperimentEnrollment] +struct representing the current state of this client’s enrollment (or not) +in that experiment.

+
§

Meta

Store containing miscellaneous metadata about this client instance.

+

Keys in the Meta store are string constants, and their corresponding values +are serialized items whose type depends on the constant. Known constaints +include:

+
    +
  • “db_version”: u16, the version number of the most revent migration +applied to this database.
  • +
  • “nimbus-id”: String, the randomly-generated identifier for the +current client instance.
  • +
  • “user-opt-in”: bool, whether the user has explicitly opted in or out +of participating in experiments.
  • +
  • “installation-date”: a UTC DateTime string, defining the date the consuming app was +installed
  • +
  • “update-date”: a UTC DateTime string, defining the date the consuming app was +last updated
  • +
  • “app-version”: String, the version of the app last persisted
  • +
+
§

Updates

Store containing pending updates to experiment data.

+

The Updates store contains a single key “pending-experiment-updates”, whose +corresponding value is a serialized Vec<Experiment> of new experiment data +that has been received from the server but not yet processed by the application.

+
§

EventCounts

Store containing collected counts of behavior events for targeting purposes.

+

Keys in the EventCounts store are strings representing the identifier for +the event and their corresponding values represent a serialized instance of a +[MultiIntervalCounter] struct that contains a set of configurations and data +for the different time periods that the data will be aggregated on.

+

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> Same<T> for T

§

type Output = T

Should always be Self
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/nimbus/stateful/persistence/index.html b/book/rust-docs/nimbus/stateful/persistence/index.html new file mode 100644 index 0000000000..4999693876 --- /dev/null +++ b/book/rust-docs/nimbus/stateful/persistence/index.html @@ -0,0 +1,6 @@ +nimbus::stateful::persistence - Rust
Expand description

Our storage abstraction, currently backed by Rkv.

+

Structs

  • Database used to access persisted data +This an abstraction around an Rkv database +An instance on this database is created each time the component is loaded +if there is persisted data, the get functions should retrieve it
  • A wrapper for an Rkv store. Implemented to allow any value which supports +serde to be used.

Enums

  • Enumeration of the different stores within our database.

Traits

Type Definitions

\ No newline at end of file diff --git a/book/rust-docs/nimbus/stateful/persistence/sidebar-items.js b/book/rust-docs/nimbus/stateful/persistence/sidebar-items.js new file mode 100644 index 0000000000..0126163002 --- /dev/null +++ b/book/rust-docs/nimbus/stateful/persistence/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["StoreId"],"struct":["Database","SingleStore"],"trait":["Readable"],"type":["Writer"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus/stateful/persistence/struct.Database.html b/book/rust-docs/nimbus/stateful/persistence/struct.Database.html new file mode 100644 index 0000000000..a5046afe7b --- /dev/null +++ b/book/rust-docs/nimbus/stateful/persistence/struct.Database.html @@ -0,0 +1,27 @@ +Database in nimbus::stateful::persistence - Rust
pub struct Database { /* private fields */ }
Expand description

Database used to access persisted data +This an abstraction around an Rkv database +An instance on this database is created each time the component is loaded +if there is persisted data, the get functions should retrieve it

+

Implementations§

source§

impl Database

source

pub fn new<P: AsRef<Path>>(path: P) -> Result<Self>

Main constructor for a database +Initiates the Rkv database to be used to retreive persisted data

+
Arguments
+
    +
  • path: A path to the persisted data, this is provided by the consuming application
  • +
+
source

pub fn get_store(&self, store_id: StoreId) -> &SingleStore

Gets a Store object, which used with the writer returned by +self.write() to update the database in a transaction.

+
source

pub fn open_rkv<P: AsRef<Path>>(path: P) -> Result<Rkv<SafeModeEnvironment>>

source

pub fn read(&self) -> Result<Reader<SafeModeRoTransaction<'_>>>

Function used to obtain a “reader” which is used for read-only transactions.

+
source

pub fn write(&self) -> Result<Writer<'_>>

Function used to obtain a “writer” which is used for transactions. +The writer.commit(); must be called to commit data added via the +writer.

+

Trait Implementations§

source§

impl TryFrom<&Database> for EventStore

§

type Error = NimbusError

The type returned in the event of a conversion error.
source§

fn try_from(db: &Database) -> Result<Self, NimbusError>

Performs the conversion.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> Same<T> for T

§

type Output = T

Should always be Self
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/nimbus/stateful/persistence/struct.SingleStore.html b/book/rust-docs/nimbus/stateful/persistence/struct.SingleStore.html new file mode 100644 index 0000000000..9569b208c6 --- /dev/null +++ b/book/rust-docs/nimbus/stateful/persistence/struct.SingleStore.html @@ -0,0 +1,27 @@ +SingleStore in nimbus::stateful::persistence - Rust
pub struct SingleStore { /* private fields */ }
Expand description

A wrapper for an Rkv store. Implemented to allow any value which supports +serde to be used.

+

Implementations§

source§

impl SingleStore

source

pub fn new(store: SingleStore<SafeModeDatabase>) -> Self

source

pub fn put<T: Serialize + for<'de> Deserialize<'de>>( + &self, + writer: &mut Writer<'_>, + key: &str, + persisted_data: &T +) -> Result<()>

source

pub fn delete(&self, writer: &mut Writer<'_>, key: &str) -> Result<()>

source

pub fn clear(&self, writer: &mut Writer<'_>) -> Result<()>

source

pub fn get<'r, T, R>(&self, reader: &'r R, key: &str) -> Result<Option<T>>where + R: Readable<'r>, + T: Serialize + for<'de> Deserialize<'de>,

source

pub fn try_collect_all<'r, T, R>(&self, reader: &'r R) -> Result<Vec<T>>where + R: Readable<'r>, + T: Serialize + for<'de> Deserialize<'de>,

Fork of collect_all that simply drops records that fail to read +rather than simply returning an error up the stack. This likely +wants to be just a parameter to collect_all, but for now….

+
source

pub fn collect_all<'r, T, R>(&self, reader: &'r R) -> Result<Vec<T>>where + R: Readable<'r>, + T: Serialize + for<'de> Deserialize<'de>,

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> Same<T> for T

§

type Output = T

Should always be Self
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/nimbus/stateful/persistence/trait.Readable.html b/book/rust-docs/nimbus/stateful/persistence/trait.Readable.html new file mode 100644 index 0000000000..7da2ed1dba --- /dev/null +++ b/book/rust-docs/nimbus/stateful/persistence/trait.Readable.html @@ -0,0 +1 @@ +Readable in nimbus::stateful::persistence - Rust
pub trait Readable<'r>: Readable<'r, Database = SafeModeDatabase, RoCursor = SafeModeRoCursor<'r>> { }

Implementors§

source§

impl<'r, T: Readable<'r, Database = SafeModeDatabase, RoCursor = SafeModeRoCursor<'r>>> Readable<'r> for T

\ No newline at end of file diff --git a/book/rust-docs/nimbus/stateful/persistence/type.Writer.html b/book/rust-docs/nimbus/stateful/persistence/type.Writer.html new file mode 100644 index 0000000000..a5f1a6038b --- /dev/null +++ b/book/rust-docs/nimbus/stateful/persistence/type.Writer.html @@ -0,0 +1 @@ +Writer in nimbus::stateful::persistence - Rust

Type Definition nimbus::stateful::persistence::Writer

source ·
pub type Writer<'t> = Writer<SafeModeRwTransaction<'t>>;
\ No newline at end of file diff --git a/book/rust-docs/nimbus/stateful/sidebar-items.js b/book/rust-docs/nimbus/stateful/sidebar-items.js new file mode 100644 index 0000000000..32d2d35f4a --- /dev/null +++ b/book/rust-docs/nimbus/stateful/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"mod":["behavior","client","dbcache","enrollment","evaluator","matcher","nimbus_client","persistence","updating"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus/stateful/updating/fn.read_and_remove_pending_experiments.html b/book/rust-docs/nimbus/stateful/updating/fn.read_and_remove_pending_experiments.html new file mode 100644 index 0000000000..5d8956c73c --- /dev/null +++ b/book/rust-docs/nimbus/stateful/updating/fn.read_and_remove_pending_experiments.html @@ -0,0 +1,4 @@ +read_and_remove_pending_experiments in nimbus::stateful::updating - Rust
pub fn read_and_remove_pending_experiments(
+    db: &Database,
+    writer: &mut Writer<'_>
+) -> Result<Option<Vec<Experiment>>>
\ No newline at end of file diff --git a/book/rust-docs/nimbus/stateful/updating/fn.write_pending_experiments.html b/book/rust-docs/nimbus/stateful/updating/fn.write_pending_experiments.html new file mode 100644 index 0000000000..8dc9734562 --- /dev/null +++ b/book/rust-docs/nimbus/stateful/updating/fn.write_pending_experiments.html @@ -0,0 +1,5 @@ +write_pending_experiments in nimbus::stateful::updating - Rust
pub fn write_pending_experiments(
+    db: &Database,
+    writer: &mut Writer<'_>,
+    experiments: Vec<Experiment>
+) -> Result<()>
\ No newline at end of file diff --git a/book/rust-docs/nimbus/stateful/updating/index.html b/book/rust-docs/nimbus/stateful/updating/index.html new file mode 100644 index 0000000000..f548c6ddc1 --- /dev/null +++ b/book/rust-docs/nimbus/stateful/updating/index.html @@ -0,0 +1,3 @@ +nimbus::stateful::updating - Rust

Module nimbus::stateful::updating

source ·
Expand description

This module implements the primitive functions to implement +safe updating from the server.

+

Functions

\ No newline at end of file diff --git a/book/rust-docs/nimbus/stateful/updating/sidebar-items.js b/book/rust-docs/nimbus/stateful/updating/sidebar-items.js new file mode 100644 index 0000000000..05afbc9b23 --- /dev/null +++ b/book/rust-docs/nimbus/stateful/updating/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["read_and_remove_pending_experiments","write_pending_experiments"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus/struct.EnrolledFeature.html b/book/rust-docs/nimbus/struct.EnrolledFeature.html new file mode 100644 index 0000000000..21bba80195 --- /dev/null +++ b/book/rust-docs/nimbus/struct.EnrolledFeature.html @@ -0,0 +1,18 @@ +EnrolledFeature in nimbus - Rust

Struct nimbus::EnrolledFeature

source ·
pub struct EnrolledFeature {
+    pub slug: String,
+    pub branch: Option<String>,
+    pub feature_id: String,
+}

Fields§

§slug: String§branch: Option<String>§feature_id: String

Trait Implementations§

source§

impl Clone for EnrolledFeature

source§

fn clone(&self) -> EnrolledFeature

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for EnrolledFeature

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl PartialEq<EnrolledFeature> for EnrolledFeature

source§

fn eq(&self, other: &EnrolledFeature) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Eq for EnrolledFeature

source§

impl StructuralEq for EnrolledFeature

source§

impl StructuralPartialEq for EnrolledFeature

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> Same<T> for T

§

type Output = T

Should always be Self
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/nimbus/struct.NimbusTargetingHelper.html b/book/rust-docs/nimbus/struct.NimbusTargetingHelper.html new file mode 100644 index 0000000000..8f73487106 --- /dev/null +++ b/book/rust-docs/nimbus/struct.NimbusTargetingHelper.html @@ -0,0 +1,14 @@ +NimbusTargetingHelper in nimbus - Rust
pub struct NimbusTargetingHelper { /* private fields */ }

Implementations§

source§

impl NimbusTargetingHelper

source

pub fn new<C: Serialize>( + context: C, + event_store: Arc<Mutex<EventStore>> +) -> Self

source

pub fn eval_jexl(&self, expr: String) -> Result<bool>

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> Same<T> for T

§

type Output = T

Should always be Self
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/nimbus/struct.RemoteSettingsConfig.html b/book/rust-docs/nimbus/struct.RemoteSettingsConfig.html new file mode 100644 index 0000000000..7106bef6a5 --- /dev/null +++ b/book/rust-docs/nimbus/struct.RemoteSettingsConfig.html @@ -0,0 +1,23 @@ +RemoteSettingsConfig in nimbus - Rust
pub struct RemoteSettingsConfig {
+    pub server_url: Option<String>,
+    pub bucket_name: Option<String>,
+    pub collection_name: String,
+}
Expand description

Custom configuration for the client. +Currently includes the following:

+
    +
  • server_url: The optional url for the settings server. If not specified, the standard server will be used.
  • +
  • bucket_name: The optional name of the bucket containing the collection on the server. If not specified, the standard bucket will be used.
  • +
  • collection_name: The name of the collection for the settings server.
  • +
+

Fields§

§server_url: Option<String>§bucket_name: Option<String>§collection_name: String

Trait Implementations§

source§

impl Clone for RemoteSettingsConfig

source§

fn clone(&self) -> RemoteSettingsConfig

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for RemoteSettingsConfig

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> Same<T> for T

§

type Output = T

Should always be Self
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/nimbus/targeting/struct.NimbusTargetingHelper.html b/book/rust-docs/nimbus/targeting/struct.NimbusTargetingHelper.html new file mode 100644 index 0000000000..ae0791879a --- /dev/null +++ b/book/rust-docs/nimbus/targeting/struct.NimbusTargetingHelper.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../nimbus/struct.NimbusTargetingHelper.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nimbus/versioning/index.html b/book/rust-docs/nimbus/versioning/index.html new file mode 100644 index 0000000000..1ce0a03873 --- /dev/null +++ b/book/rust-docs/nimbus/versioning/index.html @@ -0,0 +1,79 @@ +nimbus::versioning - Rust

Module nimbus::versioning

source ·
Expand description

Nimbus SDK App Version Comparison

+

The Nimbus SDK supports comparing app versions that follow the Firefox versioning scheme. +This module was ported from the Firefox Desktop implementation. You can find the Desktop implementation +in this C++ file +There’s also some more documentation in the IDL

+

How versioning works

+

This module defines one main struct, the Version struct. A version is represented by a list of +dot separated Version Partss. When comparing two versions, we compare each version part in order. +If one of the versions has a version part, but the other has run out (i.e we have reached the end of the list of version parts) +we compare the existing version part with the default version part, which is the 0. For example, +1.0 is equivalent to 1.0.0.0.

+

For information what version parts are composed of, and how they are compared, read the next section.

+

Example Versions

+

The following are all valid versions:

+
    +
  • 1 (one version part, representing the 1)
  • +
  • `` (one version part, representing the empty string, which is equal to 0)
  • +
  • 12+ (one version part, representing 12+ which is equal to 13pre)
  • +
  • 98.1 (two version parts, one representing 98 and another 1)
  • +
  • 98.2pre1.0-beta (three version parts, one for 98, one for 2pre1 and one for 0-beta)
  • +
+

The Version Part

+

A version part is made from 4 elements that directly follow each other:

+
    +
  • num_a: A 32-bit base-10 formatted number that is at the start of the part
  • +
  • str_b: A non-numeric ascii-encoded string that starts after num_a
  • +
  • num_c: Another 32-bit base-10 formatted number that follows str_b
  • +
  • extra_d: The rest of the version part as an ascii-encoded string
  • +
+

When two version parts are compared, each of num_a, str_b, num_c and extra_d are compared +in order. num_a and num_c are compared by normal integer comparison, str_b and extra_b are compared +by normal byte string comparison.

+

Special values and cases

+

There two special characters that can be used in version parts:

+
    +
  1. The *. This can be used to represent the whole version part. If used, it will set the num_a to be +the maximum value possible (i32::MAX). This can only be used as the whole version part string. It will parsed +normally as the * ascii character if it is preceded or followed by any other characters.
  2. +
  3. The +. This can be used as the str_b. Whenever a + is used as a str_b, it increments the num_a by 1 and sets +the str_b to be equal to pre. For example, 2+ is the same as 3pre
  4. +
  5. An empty str_b is always greater than a str_b with a value. For example, 93 > 93pre
  6. +
+

Example version comparisons

+

The following comparisons are taken directly from the brief documentation in Mozilla-Central

+ +
use nimbus::versioning::Version;
+let v1 = Version::try_from("1.0pre1").unwrap();
+let v2 = Version::try_from("1.0pre2").unwrap();
+let v3 = Version::try_from("1.0").unwrap();
+let v4 = Version::try_from("1.0.0").unwrap();
+let v5 = Version::try_from("1.0.0.0").unwrap();
+let v6 = Version::try_from("1.1pre").unwrap();
+let v7 = Version::try_from("1.1pre0").unwrap();
+let v8 = Version::try_from("1.0+").unwrap();
+let v9 = Version::try_from("1.1pre1a").unwrap();
+let v10 = Version::try_from("1.1pre1").unwrap();
+let v11 = Version::try_from("1.1pre10a").unwrap();
+let v12 = Version::try_from("1.1pre10").unwrap();
+assert!(v1 < v2);
+assert!(v2 < v3);
+assert!(v3 == v4);
+assert!(v4 == v5);
+assert!(v5 < v6);
+assert!(v6 == v7);
+assert!(v7 == v8);
+assert!(v8 < v9);
+assert!(v9 < v10);
+assert!(v10 < v11);
+assert!(v11 < v12);
+

What the above is comparing is: +1.0pre1 +< 1.0pre2 +< 1.0 == 1.0.0 == 1.0.0.0 +< 1.1pre == 1.1pre0 == 1.0+ +< 1.1pre1a +< 1.1pre1 +< 1.1pre10a +< 1.1pre10

+

Structs

\ No newline at end of file diff --git a/book/rust-docs/nimbus/versioning/sidebar-items.js b/book/rust-docs/nimbus/versioning/sidebar-items.js new file mode 100644 index 0000000000..e9d8ce2f60 --- /dev/null +++ b/book/rust-docs/nimbus/versioning/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"struct":["Version"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus/versioning/struct.Version.html b/book/rust-docs/nimbus/versioning/struct.Version.html new file mode 100644 index 0000000000..cc91473560 --- /dev/null +++ b/book/rust-docs/nimbus/versioning/struct.Version.html @@ -0,0 +1,16 @@ +Version in nimbus::versioning - Rust

Struct nimbus::versioning::Version

source ·
pub struct Version(_);

Trait Implementations§

source§

impl Clone for Version

source§

fn clone(&self) -> Version

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for Version

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for Version

source§

fn default() -> Version

Returns the “default value” for a type. Read more
source§

impl PartialEq<Version> for Version

source§

fn eq(&self, other: &Self) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl PartialOrd<Version> for Version

source§

fn partial_cmp(&self, other: &Self) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · source§

fn lt(&self, other: &Rhs) -> bool

This method tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · source§

fn le(&self, other: &Rhs) -> bool

This method tests less than or equal to (for self and other) and is used by the <= +operator. Read more
1.0.0 · source§

fn gt(&self, other: &Rhs) -> bool

This method tests greater than (for self and other) and is used by the > operator. Read more
1.0.0 · source§

fn ge(&self, other: &Rhs) -> bool

This method tests greater than or equal to (for self and other) and is used by the >= +operator. Read more
source§

impl TryFrom<&str> for Version

§

type Error = NimbusError

The type returned in the event of a conversion error.
source§

fn try_from(value: &str) -> Result<Self, Self::Error>

Performs the conversion.
source§

impl TryFrom<String> for Version

§

type Error = NimbusError

The type returned in the event of a conversion error.
source§

fn try_from(curr_part: String) -> Result<Self, Self::Error>

Performs the conversion.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> Same<T> for T

§

type Output = T

Should always be Self
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/all.html b/book/rust-docs/nimbus_cli/all.html new file mode 100644 index 0000000000..6188706f70 --- /dev/null +++ b/book/rust-docs/nimbus_cli/all.html @@ -0,0 +1 @@ +List of all items in this crate

List of all items

Structs

Enums

Traits

Functions

Type Definitions

Statics

Constants

\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/cli/enum.CliCommand.html b/book/rust-docs/nimbus_cli/cli/enum.CliCommand.html new file mode 100644 index 0000000000..2f01b596eb --- /dev/null +++ b/book/rust-docs/nimbus_cli/cli/enum.CliCommand.html @@ -0,0 +1,175 @@ +CliCommand in nimbus_cli::cli - Rust
pub(crate) enum CliCommand {
+
Show 18 variants ApplyFile { + file: PathBuf, + preserve_nimbus_db: bool, + open: OpenArgs, + }, + CaptureLogs { + file: PathBuf, + }, + Defaults { + feature_id: Option<String>, + output: Option<PathBuf>, + manifest: ManifestArgs, + }, + Enroll { + experiment: ExperimentArgs, + branch: String, + rollouts: Vec<String>, + preserve_targeting: bool, + preserve_bucketing: bool, + open: OpenArgs, + preserve_nimbus_db: bool, + no_validate: bool, + manifest: ManifestArgs, + }, + Features { + manifest: ManifestArgs, + experiment: ExperimentArgs, + branch: String, + validate: bool, + feature_id: Option<String>, + multi: bool, + output: Option<PathBuf>, + }, + Fetch { + output: Option<PathBuf>, + experiment: ExperimentArgs, + recipes: Vec<String>, + }, + FetchList { + output: Option<PathBuf>, + list: ExperimentListArgs, + }, + Fml { + args: Vec<OsString>, + }, + Info { + experiment: ExperimentArgs, + output: Option<PathBuf>, + }, + List { + list: ExperimentListArgs, + }, + LogState { + open: OpenArgs, + }, + Open { + open: OpenArgs, + no_clobber: bool, + }, + StartServer, + ResetApp, + TailLogs, + TestFeature { + feature_id: String, + files: Vec<PathBuf>, + patch: Option<PathBuf>, + open: OpenArgs, + no_validate: bool, + manifest: ManifestArgs, + }, + Unenroll { + open: OpenArgs, + }, + Validate { + experiment: ExperimentArgs, + manifest: ManifestArgs, + }, +
}

Variants§

§

ApplyFile

Fields

§file: PathBuf

The filename to be loaded into the SDK.

+
§preserve_nimbus_db: bool

Keeps existing enrollments and experiments before enrolling.

+

This is unlikely what you want to do.

+

Send a complete JSON file to the Nimbus SDK and apply it immediately.

+
§

CaptureLogs

Fields

§file: PathBuf

The file to put the logs.

+

Capture the logs into a file.

+
§

Defaults

Fields

§feature_id: Option<String>

An optional feature-id

+
§output: Option<PathBuf>

An optional file to print the manifest defaults.

+
§manifest: ManifestArgs

Print the defaults for the manifest.

+
§

Enroll

Fields

§experiment: ExperimentArgs
§branch: String

The branch slug.

+
§rollouts: Vec<String>

Optional rollout slugs, including the server and collection.

+
§preserve_targeting: bool

Preserves the original experiment targeting

+
§preserve_bucketing: bool

Preserves the original experiment bucketing

+
§preserve_nimbus_db: bool

Keeps existing enrollments and experiments before enrolling.

+

This is unlikely what you want to do.

+
§no_validate: bool

Don’t validate the feature config files before enrolling

+
§manifest: ManifestArgs

Enroll into an experiment or a rollout.

+

The experiment slug is a combination of the actual slug, and the server it came from.

+
    +
  • +

    release/stage determines the server.

    +
  • +
  • +

    preview selects the preview collection.

    +
  • +
+

These can be further combined: e.g. $slug, preview/$slug, stage/$slug, stage/preview/$slug

+
§

Features

Fields

§manifest: ManifestArgs
§experiment: ExperimentArgs
§branch: String

The branch of the experiment

+
§validate: bool

If set, then merge the experimental configuration with the defaults from the manifest

+
§feature_id: Option<String>

An optional feature-id: if it exists in this branch, print this feature +on its own.

+
§multi: bool

Print out the features involved in this branch as in a format: +{ $feature_id: $value }.

+

Automated tools should use this, since the output is predictable.

+
§output: Option<PathBuf>

An optional file to print the output.

+

Print the feature configuration involved in the branch of an experiment.

+

This can be optionally merged with the defaults from the feature manifest.

+
§

Fetch

Fields

§output: Option<PathBuf>

The file to download the recipes to.

+
§experiment: ExperimentArgs
§recipes: Vec<String>

The recipe slugs, including server.

+

Use once per recipe to download. e.g. +fetch –output file.json preview/my-experiment my-rollout

+

Cannot be used with the server option: use fetch-list instead.

+

Fetch one or more named experiments and rollouts and put them in a file.

+
§

FetchList

Fields

§output: Option<PathBuf>

The file to download the recipes to.

+

Fetch a list of experiments and put it in a file.

+
§

Fml

Fields

Execute a nimbus-fml command. See

+

nimbus-cli fml – –help

+

for more.

+
§

Info

Fields

§experiment: ExperimentArgs
§output: Option<PathBuf>

An optional file to print the output.

+

Displays information about an experiment

+
§

List

List the experiments from a server

+
§

LogState

Fields

Print the state of the Nimbus database to logs.

+

This causes a restart of the app.

+
§

Open

Fields

§no_clobber: bool

By default, the app is terminated before sending the a deeplink.

+

If this flag is set, then do not terminate the app if it is already runnning.

+

Open the app without changing the state of experiment enrollments.

+
§

StartServer

Start a server

+
§

ResetApp

Reset the app back to its just installed state

+
§

TailLogs

Follow the logs for the given app.

+
§

TestFeature

Fields

§feature_id: String

The identifier of the feature to configure

+
§files: Vec<PathBuf>

One or more files containing a feature config for the feature.

+
§patch: Option<PathBuf>

An optional patch file, used to patch feature configurations

+

This is of the format that comes from the +features --multi or defaults commands.

+
§no_validate: bool

Don’t validate the feature config files before enrolling

+
§manifest: ManifestArgs

Configure an application feature with one or more feature config files.

+

One file per branch. The branch slugs will correspond to the file names.

+

By default, the files are validated against the manifest; this can be +overridden with --no-validate.

+
§

Unenroll

Fields

Unenroll from all experiments and rollouts

+
§

Validate

Fields

§experiment: ExperimentArgs
§manifest: ManifestArgs

Validate an experiment against a feature manifest

+

Implementations§

source§

impl CliCommand

source

pub(crate) fn check_valid(&self) -> Result<()>

source

pub(crate) fn open_args(&self) -> Option<&OpenArgs>

source

pub(crate) fn should_kill(&self) -> bool

source

pub(crate) fn should_reset(&self) -> bool

Trait Implementations§

source§

impl Clone for CliCommand

source§

fn clone(&self) -> CliCommand

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl FromArgMatches for CliCommand

source§

fn from_arg_matches(__clap_arg_matches: &ArgMatches) -> Result<Self, Error>

Instantiate Self from [ArgMatches], parsing the arguments as needed. Read more
source§

fn from_arg_matches_mut( + __clap_arg_matches: &mut ArgMatches +) -> Result<Self, Error>

Instantiate Self from [ArgMatches], parsing the arguments as needed. Read more
source§

fn update_from_arg_matches( + &mut self, + __clap_arg_matches: &ArgMatches +) -> Result<(), Error>

Assign values from ArgMatches to self.
source§

fn update_from_arg_matches_mut<'b>( + &mut self, + __clap_arg_matches: &mut ArgMatches +) -> Result<(), Error>

Assign values from ArgMatches to self.
source§

impl Subcommand for CliCommand

source§

fn augment_subcommands<'b>(__clap_app: Command) -> Command

Append to [Command] so it can instantiate Self. Read more
source§

fn augment_subcommands_for_update<'b>(__clap_app: Command) -> Command

Append to [Command] so it can update self. Read more
source§

fn has_subcommand(__clap_name: &str) -> bool

Test whether Self can parse a specific subcommand

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> FromRef<T> for Twhere + T: Clone,

§

fn from_ref(input: &T) -> T

Converts to this type from a reference to the input type.
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/cli/fn.validate_date.html b/book/rust-docs/nimbus_cli/cli/fn.validate_date.html new file mode 100644 index 0000000000..ccf372f3fc --- /dev/null +++ b/book/rust-docs/nimbus_cli/cli/fn.validate_date.html @@ -0,0 +1 @@ +validate_date in nimbus_cli::cli - Rust

Function nimbus_cli::cli::validate_date

source ·
fn validate_date(s: &str) -> Result<String, String>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/cli/fn.validate_date_parts.html b/book/rust-docs/nimbus_cli/cli/fn.validate_date_parts.html new file mode 100644 index 0000000000..e31962334d --- /dev/null +++ b/book/rust-docs/nimbus_cli/cli/fn.validate_date_parts.html @@ -0,0 +1,5 @@ +validate_date_parts in nimbus_cli::cli - Rust
fn validate_date_parts(
+    yyyy: &str,
+    mm: &str,
+    dd: &str
+) -> Result<(), &'static str>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/cli/fn.validate_num.html b/book/rust-docs/nimbus_cli/cli/fn.validate_num.html new file mode 100644 index 0000000000..cc647653b2 --- /dev/null +++ b/book/rust-docs/nimbus_cli/cli/fn.validate_num.html @@ -0,0 +1 @@ +validate_num in nimbus_cli::cli - Rust

Function nimbus_cli::cli::validate_num

source ·
fn validate_num(s: &str, l: usize) -> Result<(), &'static str>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/cli/index.html b/book/rust-docs/nimbus_cli/cli/index.html new file mode 100644 index 0000000000..4bf034c65c --- /dev/null +++ b/book/rust-docs/nimbus_cli/cli/index.html @@ -0,0 +1 @@ +nimbus_cli::cli - Rust
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/cli/sidebar-items.js b/book/rust-docs/nimbus_cli/cli/sidebar-items.js new file mode 100644 index 0000000000..67af2868cb --- /dev/null +++ b/book/rust-docs/nimbus_cli/cli/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["CliCommand"],"fn":["validate_date","validate_date_parts","validate_num"],"struct":["Cli","ExperimentArgs","ExperimentListArgs","ExperimentListFilterArgs","ExperimentListSourceArgs","ManifestArgs","OpenArgs"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/cli/struct.Cli.html b/book/rust-docs/nimbus_cli/cli/struct.Cli.html new file mode 100644 index 0000000000..832ccc8f29 --- /dev/null +++ b/book/rust-docs/nimbus_cli/cli/struct.Cli.html @@ -0,0 +1,40 @@ +Cli in nimbus_cli::cli - Rust

Struct nimbus_cli::cli::Cli

source ·
pub(crate) struct Cli {
+    pub(crate) app: Option<String>,
+    pub(crate) channel: Option<String>,
+    pub(crate) device_id: Option<String>,
+    pub(crate) command: CliCommand,
+}

Fields§

§app: Option<String>

The app name according to Nimbus.

+
§channel: Option<String>

The channel according to Nimbus. This determines which app to talk to.

+
§device_id: Option<String>

The device id of the simulator, emulator or device.

+
§command: CliCommand

Trait Implementations§

source§

impl Args for Cli

source§

fn group_id() -> Option<Id>

Report the [ArgGroup::id][crate::ArgGroup::id] for this set of arguments
source§

fn augment_args<'b>(__clap_app: Command) -> Command

Append to [Command] so it can instantiate Self. Read more
source§

fn augment_args_for_update<'b>(__clap_app: Command) -> Command

Append to [Command] so it can update self. Read more
source§

impl CommandFactory for Cli

source§

fn command<'b>() -> Command

Build a [Command] that can instantiate Self. Read more
source§

fn command_for_update<'b>() -> Command

Build a [Command] that can update self. Read more
source§

impl From<&Cli> for NimbusApp

source§

fn from(value: &Cli) -> Self

Converts to this type from the input type.
source§

impl FromArgMatches for Cli

source§

fn from_arg_matches(__clap_arg_matches: &ArgMatches) -> Result<Self, Error>

Instantiate Self from [ArgMatches], parsing the arguments as needed. Read more
source§

fn from_arg_matches_mut( + __clap_arg_matches: &mut ArgMatches +) -> Result<Self, Error>

Instantiate Self from [ArgMatches], parsing the arguments as needed. Read more
source§

fn update_from_arg_matches( + &mut self, + __clap_arg_matches: &ArgMatches +) -> Result<(), Error>

Assign values from ArgMatches to self.
source§

fn update_from_arg_matches_mut( + &mut self, + __clap_arg_matches: &mut ArgMatches +) -> Result<(), Error>

Assign values from ArgMatches to self.
source§

impl Parser for Cli

§

fn parse() -> Self

Parse from std::env::args_os(), exit on error
§

fn try_parse() -> Result<Self, Error<RichFormatter>>

Parse from std::env::args_os(), return Err on error.
§

fn parse_from<I, T>(itr: I) -> Selfwhere + I: IntoIterator<Item = T>, + T: Into<OsString> + Clone,

Parse from iterator, exit on error
§

fn try_parse_from<I, T>(itr: I) -> Result<Self, Error<RichFormatter>>where + I: IntoIterator<Item = T>, + T: Into<OsString> + Clone,

Parse from iterator, return Err on error.
§

fn update_from<I, T>(&mut self, itr: I)where + I: IntoIterator<Item = T>, + T: Into<OsString> + Clone,

Update from iterator, exit on error
§

fn try_update_from<I, T>(&mut self, itr: I) -> Result<(), Error<RichFormatter>>where + I: IntoIterator<Item = T>, + T: Into<OsString> + Clone,

Update from iterator, return Err on error.
source§

impl TryFrom<&Cli> for AppCommand

§

type Error = Error

The type returned in the event of a conversion error.
source§

fn try_from(cli: &Cli) -> Result<Self>

Performs the conversion.
source§

impl TryFrom<&Cli> for ExperimentListSource

§

type Error = Error

The type returned in the event of a conversion error.
source§

fn try_from(value: &Cli) -> Result<Self>

Performs the conversion.
source§

impl TryFrom<&Cli> for ExperimentSource

§

type Error = Error

The type returned in the event of a conversion error.
source§

fn try_from(value: &Cli) -> Result<Self>

Performs the conversion.
source§

impl TryFrom<&Cli> for LaunchableApp

§

type Error = Error

The type returned in the event of a conversion error.
source§

fn try_from(value: &Cli) -> Result<Self>

Performs the conversion.

Auto Trait Implementations§

§

impl RefUnwindSafe for Cli

§

impl Send for Cli

§

impl Sync for Cli

§

impl Unpin for Cli

§

impl UnwindSafe for Cli

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/cli/struct.ExperimentArgs.html b/book/rust-docs/nimbus_cli/cli/struct.ExperimentArgs.html new file mode 100644 index 0000000000..7e142d7cde --- /dev/null +++ b/book/rust-docs/nimbus_cli/cli/struct.ExperimentArgs.html @@ -0,0 +1,39 @@ +ExperimentArgs in nimbus_cli::cli - Rust
pub(crate) struct ExperimentArgs {
+    pub(crate) experiment: String,
+    pub(crate) file: Option<PathBuf>,
+    pub(crate) use_rs: bool,
+    pub(crate) patch: Option<PathBuf>,
+}

Fields§

§experiment: String

The experiment slug, including the server and collection.

+
§file: Option<PathBuf>

An optional file from which to get the experiment.

+

By default, the file is fetched from the server.

+
§use_rs: bool

Use remote settings to fetch the experiment recipe.

+

By default, the file is fetched from the v6 api of experimenter.

+
§patch: Option<PathBuf>

An optional patch file, used to patch feature configurations

+

This is of the format that comes from the +features --multi or defaults commands.

+

Trait Implementations§

source§

impl Args for ExperimentArgs

source§

fn group_id() -> Option<Id>

Report the [ArgGroup::id][crate::ArgGroup::id] for this set of arguments
source§

fn augment_args<'b>(__clap_app: Command) -> Command

Append to [Command] so it can instantiate Self. Read more
source§

fn augment_args_for_update<'b>(__clap_app: Command) -> Command

Append to [Command] so it can update self. Read more
source§

impl Clone for ExperimentArgs

source§

fn clone(&self) -> ExperimentArgs

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for ExperimentArgs

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for ExperimentArgs

source§

fn default() -> ExperimentArgs

Returns the “default value” for a type. Read more
source§

impl FromArgMatches for ExperimentArgs

source§

fn from_arg_matches(__clap_arg_matches: &ArgMatches) -> Result<Self, Error>

Instantiate Self from [ArgMatches], parsing the arguments as needed. Read more
source§

fn from_arg_matches_mut( + __clap_arg_matches: &mut ArgMatches +) -> Result<Self, Error>

Instantiate Self from [ArgMatches], parsing the arguments as needed. Read more
source§

fn update_from_arg_matches( + &mut self, + __clap_arg_matches: &ArgMatches +) -> Result<(), Error>

Assign values from ArgMatches to self.
source§

fn update_from_arg_matches_mut( + &mut self, + __clap_arg_matches: &mut ArgMatches +) -> Result<(), Error>

Assign values from ArgMatches to self.
source§

impl TryFrom<&ExperimentArgs> for ExperimentSource

§

type Error = Error

The type returned in the event of a conversion error.
source§

fn try_from(value: &ExperimentArgs) -> Result<Self>

Performs the conversion.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> FromRef<T> for Twhere + T: Clone,

§

fn from_ref(input: &T) -> T

Converts to this type from a reference to the input type.
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/cli/struct.ExperimentListArgs.html b/book/rust-docs/nimbus_cli/cli/struct.ExperimentListArgs.html new file mode 100644 index 0000000000..542158bedd --- /dev/null +++ b/book/rust-docs/nimbus_cli/cli/struct.ExperimentListArgs.html @@ -0,0 +1,29 @@ +ExperimentListArgs in nimbus_cli::cli - Rust
pub(crate) struct ExperimentListArgs {
+    pub(crate) source: ExperimentListSourceArgs,
+    pub(crate) filter: ExperimentListFilterArgs,
+}

Fields§

§source: ExperimentListSourceArgs§filter: ExperimentListFilterArgs

Trait Implementations§

source§

impl Args for ExperimentListArgs

source§

fn group_id() -> Option<Id>

Report the [ArgGroup::id][crate::ArgGroup::id] for this set of arguments
source§

fn augment_args<'b>(__clap_app: Command) -> Command

Append to [Command] so it can instantiate Self. Read more
source§

fn augment_args_for_update<'b>(__clap_app: Command) -> Command

Append to [Command] so it can update self. Read more
source§

impl Clone for ExperimentListArgs

source§

fn clone(&self) -> ExperimentListArgs

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for ExperimentListArgs

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for ExperimentListArgs

source§

fn default() -> ExperimentListArgs

Returns the “default value” for a type. Read more
source§

impl FromArgMatches for ExperimentListArgs

source§

fn from_arg_matches(__clap_arg_matches: &ArgMatches) -> Result<Self, Error>

Instantiate Self from [ArgMatches], parsing the arguments as needed. Read more
source§

fn from_arg_matches_mut( + __clap_arg_matches: &mut ArgMatches +) -> Result<Self, Error>

Instantiate Self from [ArgMatches], parsing the arguments as needed. Read more
source§

fn update_from_arg_matches( + &mut self, + __clap_arg_matches: &ArgMatches +) -> Result<(), Error>

Assign values from ArgMatches to self.
source§

fn update_from_arg_matches_mut( + &mut self, + __clap_arg_matches: &mut ArgMatches +) -> Result<(), Error>

Assign values from ArgMatches to self.
source§

impl TryFrom<&ExperimentListArgs> for ExperimentListSource

§

type Error = Error

The type returned in the event of a conversion error.
source§

fn try_from(value: &ExperimentListArgs) -> Result<Self>

Performs the conversion.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> FromRef<T> for Twhere + T: Clone,

§

fn from_ref(input: &T) -> T

Converts to this type from a reference to the input type.
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/cli/struct.ExperimentListFilterArgs.html b/book/rust-docs/nimbus_cli/cli/struct.ExperimentListFilterArgs.html new file mode 100644 index 0000000000..5c6e43b728 --- /dev/null +++ b/book/rust-docs/nimbus_cli/cli/struct.ExperimentListFilterArgs.html @@ -0,0 +1,33 @@ +ExperimentListFilterArgs in nimbus_cli::cli - Rust
pub(crate) struct ExperimentListFilterArgs {
+    pub(crate) slug: Option<String>,
+    pub(crate) feature: Option<String>,
+    pub(crate) active_on: Option<String>,
+    pub(crate) enrolling_on: Option<String>,
+    pub(crate) channel: Option<String>,
+    pub(crate) is_rollout: Option<bool>,
+}

Fields§

§slug: Option<String>§feature: Option<String>§active_on: Option<String>§enrolling_on: Option<String>§channel: Option<String>§is_rollout: Option<bool>

Trait Implementations§

source§

impl Args for ExperimentListFilterArgs

source§

fn group_id() -> Option<Id>

Report the [ArgGroup::id][crate::ArgGroup::id] for this set of arguments
source§

fn augment_args<'b>(__clap_app: Command) -> Command

Append to [Command] so it can instantiate Self. Read more
source§

fn augment_args_for_update<'b>(__clap_app: Command) -> Command

Append to [Command] so it can update self. Read more
source§

impl Clone for ExperimentListFilterArgs

source§

fn clone(&self) -> ExperimentListFilterArgs

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for ExperimentListFilterArgs

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for ExperimentListFilterArgs

source§

fn default() -> ExperimentListFilterArgs

Returns the “default value” for a type. Read more
source§

impl From<&ExperimentListFilterArgs> for ExperimentListFilter

source§

fn from(value: &ExperimentListFilterArgs) -> Self

Converts to this type from the input type.
source§

impl FromArgMatches for ExperimentListFilterArgs

source§

fn from_arg_matches(__clap_arg_matches: &ArgMatches) -> Result<Self, Error>

Instantiate Self from [ArgMatches], parsing the arguments as needed. Read more
source§

fn from_arg_matches_mut( + __clap_arg_matches: &mut ArgMatches +) -> Result<Self, Error>

Instantiate Self from [ArgMatches], parsing the arguments as needed. Read more
source§

fn update_from_arg_matches( + &mut self, + __clap_arg_matches: &ArgMatches +) -> Result<(), Error>

Assign values from ArgMatches to self.
source§

fn update_from_arg_matches_mut( + &mut self, + __clap_arg_matches: &mut ArgMatches +) -> Result<(), Error>

Assign values from ArgMatches to self.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> FromRef<T> for Twhere + T: Clone,

§

fn from_ref(input: &T) -> T

Converts to this type from a reference to the input type.
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/cli/struct.ExperimentListSourceArgs.html b/book/rust-docs/nimbus_cli/cli/struct.ExperimentListSourceArgs.html new file mode 100644 index 0000000000..6d23884089 --- /dev/null +++ b/book/rust-docs/nimbus_cli/cli/struct.ExperimentListSourceArgs.html @@ -0,0 +1,36 @@ +ExperimentListSourceArgs in nimbus_cli::cli - Rust
pub(crate) struct ExperimentListSourceArgs {
+    pub(crate) server: String,
+    pub(crate) file: Option<PathBuf>,
+    pub(crate) use_api: bool,
+}

Fields§

§server: String

A server slug e.g. preview, release, stage, stage/preview

+
§file: Option<PathBuf>

An optional file

+
§use_api: bool

Use the v6 API to fetch the experiment recipes.

+

By default, the file is fetched from the Remote Settings.

+

The API contains all launched experiments, past and present, +so this is considerably slower and longer than Remote Settings.

+

Trait Implementations§

source§

impl Args for ExperimentListSourceArgs

source§

fn group_id() -> Option<Id>

Report the [ArgGroup::id][crate::ArgGroup::id] for this set of arguments
source§

fn augment_args<'b>(__clap_app: Command) -> Command

Append to [Command] so it can instantiate Self. Read more
source§

fn augment_args_for_update<'b>(__clap_app: Command) -> Command

Append to [Command] so it can update self. Read more
source§

impl Clone for ExperimentListSourceArgs

source§

fn clone(&self) -> ExperimentListSourceArgs

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for ExperimentListSourceArgs

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for ExperimentListSourceArgs

source§

fn default() -> ExperimentListSourceArgs

Returns the “default value” for a type. Read more
source§

impl FromArgMatches for ExperimentListSourceArgs

source§

fn from_arg_matches(__clap_arg_matches: &ArgMatches) -> Result<Self, Error>

Instantiate Self from [ArgMatches], parsing the arguments as needed. Read more
source§

fn from_arg_matches_mut( + __clap_arg_matches: &mut ArgMatches +) -> Result<Self, Error>

Instantiate Self from [ArgMatches], parsing the arguments as needed. Read more
source§

fn update_from_arg_matches( + &mut self, + __clap_arg_matches: &ArgMatches +) -> Result<(), Error>

Assign values from ArgMatches to self.
source§

fn update_from_arg_matches_mut( + &mut self, + __clap_arg_matches: &mut ArgMatches +) -> Result<(), Error>

Assign values from ArgMatches to self.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> FromRef<T> for Twhere + T: Clone,

§

fn from_ref(input: &T) -> T

Converts to this type from a reference to the input type.
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/cli/struct.ManifestArgs.html b/book/rust-docs/nimbus_cli/cli/struct.ManifestArgs.html new file mode 100644 index 0000000000..42d0ba2cf4 --- /dev/null +++ b/book/rust-docs/nimbus_cli/cli/struct.ManifestArgs.html @@ -0,0 +1,37 @@ +ManifestArgs in nimbus_cli::cli - Rust
pub(crate) struct ManifestArgs {
+    pub(crate) manifest: Option<String>,
+    pub(crate) version: Option<String>,
+    pub(crate) ref_: String,
+}

Fields§

§manifest: Option<String>

An optional manifest file

+
§version: Option<String>

An optional version of the app. +If present, constructs the ref from an app specific template. +Due to inconsistencies in branching names, this isn’t always +reliable.

+
§ref_: String

The branch/tag/commit for the version of the manifest +to get from Github.

+

Trait Implementations§

source§

impl Args for ManifestArgs

source§

fn group_id() -> Option<Id>

Report the [ArgGroup::id][crate::ArgGroup::id] for this set of arguments
source§

fn augment_args<'b>(__clap_app: Command) -> Command

Append to [Command] so it can instantiate Self. Read more
source§

fn augment_args_for_update<'b>(__clap_app: Command) -> Command

Append to [Command] so it can update self. Read more
source§

impl Clone for ManifestArgs

source§

fn clone(&self) -> ManifestArgs

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for ManifestArgs

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for ManifestArgs

source§

fn default() -> ManifestArgs

Returns the “default value” for a type. Read more
source§

impl FromArgMatches for ManifestArgs

source§

fn from_arg_matches(__clap_arg_matches: &ArgMatches) -> Result<Self, Error>

Instantiate Self from [ArgMatches], parsing the arguments as needed. Read more
source§

fn from_arg_matches_mut( + __clap_arg_matches: &mut ArgMatches +) -> Result<Self, Error>

Instantiate Self from [ArgMatches], parsing the arguments as needed. Read more
source§

fn update_from_arg_matches( + &mut self, + __clap_arg_matches: &ArgMatches +) -> Result<(), Error>

Assign values from ArgMatches to self.
source§

fn update_from_arg_matches_mut( + &mut self, + __clap_arg_matches: &mut ArgMatches +) -> Result<(), Error>

Assign values from ArgMatches to self.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> FromRef<T> for Twhere + T: Clone,

§

fn from_ref(input: &T) -> T

Converts to this type from a reference to the input type.
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/cli/struct.OpenArgs.html b/book/rust-docs/nimbus_cli/cli/struct.OpenArgs.html new file mode 100644 index 0000000000..bd457ea02b --- /dev/null +++ b/book/rust-docs/nimbus_cli/cli/struct.OpenArgs.html @@ -0,0 +1,51 @@ +OpenArgs in nimbus_cli::cli - Rust

Struct nimbus_cli::cli::OpenArgs

source ·
pub(crate) struct OpenArgs {
+    pub(crate) deeplink: Option<String>,
+    pub(crate) reset_app: bool,
+    pub(crate) pbcopy: bool,
+    pub(crate) pbpaste: bool,
+    pub(crate) passthrough: Vec<String>,
+    pub(crate) output: Option<PathBuf>,
+}

Fields§

§deeplink: Option<String>

Optional deeplink. If present, launch with this link.

+
§reset_app: bool

Resets the app back to its initial state before launching

+
§pbcopy: bool

Instead of opening via adb or xcrun simctl, construct a deeplink +and put it into the pastebuffer.

+

If present, then the app is not launched, so this option does not work with +--reset-app or passthrough arguments.

+
§pbpaste: bool

Instead of opening via adb or xcrun simctl, construct a deeplink +and put it into the pastebuffer.

+

If present, then the app is not launched, so this option does not work with +--reset-app or passthrough arguments.

+
§passthrough: Vec<String>

Optionally, add platform specific arguments to the adb or xcrun command.

+

By default, arguments are added to the end of the command, likely to be passed +directly to the app.

+

Arguments before a special placeholder {} are passed to +adb am start or xcrun simctl launch commands directly.

+
§output: Option<PathBuf>

An optional file to dump experiments into.

+

If present, then the app is not launched, so this option does not work with +--reset-app or passthrough arguments.

+

Trait Implementations§

source§

impl Args for OpenArgs

source§

fn group_id() -> Option<Id>

Report the [ArgGroup::id][crate::ArgGroup::id] for this set of arguments
source§

fn augment_args<'b>(__clap_app: Command) -> Command

Append to [Command] so it can instantiate Self. Read more
source§

fn augment_args_for_update<'b>(__clap_app: Command) -> Command

Append to [Command] so it can update self. Read more
source§

impl Clone for OpenArgs

source§

fn clone(&self) -> OpenArgs

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for OpenArgs

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for OpenArgs

source§

fn default() -> OpenArgs

Returns the “default value” for a type. Read more
source§

impl From<OpenArgs> for AppOpenArgs

source§

fn from(value: OpenArgs) -> Self

Converts to this type from the input type.
source§

impl FromArgMatches for OpenArgs

source§

fn from_arg_matches(__clap_arg_matches: &ArgMatches) -> Result<Self, Error>

Instantiate Self from [ArgMatches], parsing the arguments as needed. Read more
source§

fn from_arg_matches_mut( + __clap_arg_matches: &mut ArgMatches +) -> Result<Self, Error>

Instantiate Self from [ArgMatches], parsing the arguments as needed. Read more
source§

fn update_from_arg_matches( + &mut self, + __clap_arg_matches: &ArgMatches +) -> Result<(), Error>

Assign values from ArgMatches to self.
source§

fn update_from_arg_matches_mut( + &mut self, + __clap_arg_matches: &mut ArgMatches +) -> Result<(), Error>

Assign values from ArgMatches to self.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> FromRef<T> for Twhere + T: Clone,

§

fn from_ref(input: &T) -> T

Converts to this type from a reference to the input type.
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/cmd/fn.logcat_args.html b/book/rust-docs/nimbus_cli/cmd/fn.logcat_args.html new file mode 100644 index 0000000000..726cc28a62 --- /dev/null +++ b/book/rust-docs/nimbus_cli/cmd/fn.logcat_args.html @@ -0,0 +1 @@ +logcat_args in nimbus_cli::cmd - Rust

Function nimbus_cli::cmd::logcat_args

source ·
fn logcat_args<'a>() -> Vec<&'a str>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/cmd/fn.output_err.html b/book/rust-docs/nimbus_cli/cmd/fn.output_err.html new file mode 100644 index 0000000000..7f4318d3d3 --- /dev/null +++ b/book/rust-docs/nimbus_cli/cmd/fn.output_err.html @@ -0,0 +1 @@ +output_err in nimbus_cli::cmd - Rust

Function nimbus_cli::cmd::output_err

source ·
fn output_err(term: &Term, title: &str, detail: &str) -> Result<()>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/cmd/fn.output_ok.html b/book/rust-docs/nimbus_cli/cmd/fn.output_ok.html new file mode 100644 index 0000000000..1a21fd5e3b --- /dev/null +++ b/book/rust-docs/nimbus_cli/cmd/fn.output_ok.html @@ -0,0 +1 @@ +output_ok in nimbus_cli::cmd - Rust

Function nimbus_cli::cmd::output_ok

source ·
fn output_ok(term: &Term, title: &str) -> Result<()>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/cmd/fn.process_cmd.html b/book/rust-docs/nimbus_cli/cmd/fn.process_cmd.html new file mode 100644 index 0000000000..fe805e59a2 --- /dev/null +++ b/book/rust-docs/nimbus_cli/cmd/fn.process_cmd.html @@ -0,0 +1 @@ +process_cmd in nimbus_cli::cmd - Rust

Function nimbus_cli::cmd::process_cmd

source ·
pub(crate) fn process_cmd(cmd: &AppCommand) -> Result<bool>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/cmd/fn.prompt.html b/book/rust-docs/nimbus_cli/cmd/fn.prompt.html new file mode 100644 index 0000000000..5f36291dc9 --- /dev/null +++ b/book/rust-docs/nimbus_cli/cmd/fn.prompt.html @@ -0,0 +1 @@ +prompt in nimbus_cli::cmd - Rust

Function nimbus_cli::cmd::prompt

source ·
fn prompt(term: &Term, command: &str) -> Result<()>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/cmd/index.html b/book/rust-docs/nimbus_cli/cmd/index.html new file mode 100644 index 0000000000..7dc861c6cf --- /dev/null +++ b/book/rust-docs/nimbus_cli/cmd/index.html @@ -0,0 +1 @@ +nimbus_cli::cmd - Rust

Module nimbus_cli::cmd

source ·

Functions

\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/cmd/sidebar-items.js b/book/rust-docs/nimbus_cli/cmd/sidebar-items.js new file mode 100644 index 0000000000..2232202df8 --- /dev/null +++ b/book/rust-docs/nimbus_cli/cmd/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["logcat_args","output_err","output_ok","process_cmd","prompt"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/config/fn.api_v6_production_server.html b/book/rust-docs/nimbus_cli/config/fn.api_v6_production_server.html new file mode 100644 index 0000000000..74381fdd88 --- /dev/null +++ b/book/rust-docs/nimbus_cli/config/fn.api_v6_production_server.html @@ -0,0 +1 @@ +api_v6_production_server in nimbus_cli::config - Rust
pub(crate) fn api_v6_production_server() -> String
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/config/fn.api_v6_stage_server.html b/book/rust-docs/nimbus_cli/config/fn.api_v6_stage_server.html new file mode 100644 index 0000000000..10e9ad7d66 --- /dev/null +++ b/book/rust-docs/nimbus_cli/config/fn.api_v6_stage_server.html @@ -0,0 +1 @@ +api_v6_stage_server in nimbus_cli::config - Rust
pub(crate) fn api_v6_stage_server() -> String
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/config/fn.manifest_cache_dir.html b/book/rust-docs/nimbus_cli/config/fn.manifest_cache_dir.html new file mode 100644 index 0000000000..6244ecfa01 --- /dev/null +++ b/book/rust-docs/nimbus_cli/config/fn.manifest_cache_dir.html @@ -0,0 +1 @@ +manifest_cache_dir in nimbus_cli::config - Rust
pub(crate) fn manifest_cache_dir() -> Option<PathBuf>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/config/fn.rs_production_server.html b/book/rust-docs/nimbus_cli/config/fn.rs_production_server.html new file mode 100644 index 0000000000..8e14efa4b2 --- /dev/null +++ b/book/rust-docs/nimbus_cli/config/fn.rs_production_server.html @@ -0,0 +1 @@ +rs_production_server in nimbus_cli::config - Rust
pub(crate) fn rs_production_server() -> String
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/config/fn.rs_stage_server.html b/book/rust-docs/nimbus_cli/config/fn.rs_stage_server.html new file mode 100644 index 0000000000..babd2e8bbb --- /dev/null +++ b/book/rust-docs/nimbus_cli/config/fn.rs_stage_server.html @@ -0,0 +1 @@ +rs_stage_server in nimbus_cli::config - Rust
pub(crate) fn rs_stage_server() -> String
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/config/fn.server_host.html b/book/rust-docs/nimbus_cli/config/fn.server_host.html new file mode 100644 index 0000000000..bd28611e5c --- /dev/null +++ b/book/rust-docs/nimbus_cli/config/fn.server_host.html @@ -0,0 +1 @@ +server_host in nimbus_cli::config - Rust

Function nimbus_cli::config::server_host

source ·
pub(crate) fn server_host() -> String
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/config/fn.server_port.html b/book/rust-docs/nimbus_cli/config/fn.server_port.html new file mode 100644 index 0000000000..8b552b10f6 --- /dev/null +++ b/book/rust-docs/nimbus_cli/config/fn.server_port.html @@ -0,0 +1 @@ +server_port in nimbus_cli::config - Rust

Function nimbus_cli::config::server_port

source ·
pub(crate) fn server_port() -> String
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/config/index.html b/book/rust-docs/nimbus_cli/config/index.html new file mode 100644 index 0000000000..e9b42d55d9 --- /dev/null +++ b/book/rust-docs/nimbus_cli/config/index.html @@ -0,0 +1 @@ +nimbus_cli::config - Rust
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/config/sidebar-items.js b/book/rust-docs/nimbus_cli/config/sidebar-items.js new file mode 100644 index 0000000000..ab7efcc305 --- /dev/null +++ b/book/rust-docs/nimbus_cli/config/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["api_v6_production_server","api_v6_stage_server","manifest_cache_dir","rs_production_server","rs_stage_server","server_host","server_port"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/enum.AppCommand.html b/book/rust-docs/nimbus_cli/enum.AppCommand.html new file mode 100644 index 0000000000..fe4c36b62b --- /dev/null +++ b/book/rust-docs/nimbus_cli/enum.AppCommand.html @@ -0,0 +1,97 @@ +AppCommand in nimbus_cli - Rust
pub(crate) enum AppCommand {
+
Show 18 variants ApplyFile { + app: LaunchableApp, + open: AppOpenArgs, + list: ExperimentListSource, + preserve_nimbus_db: bool, + }, + CaptureLogs { + app: LaunchableApp, + file: PathBuf, + }, + Defaults { + manifest: ManifestSource, + feature_id: Option<String>, + output: Option<PathBuf>, + }, + Enroll { + app: LaunchableApp, + params: NimbusApp, + experiment: ExperimentSource, + rollouts: Vec<ExperimentSource>, + branch: String, + preserve_targeting: bool, + preserve_bucketing: bool, + preserve_nimbus_db: bool, + open: AppOpenArgs, + }, + ExtractFeatures { + experiment: ExperimentSource, + branch: String, + manifest: ManifestSource, + feature_id: Option<String>, + validate: bool, + multi: bool, + output: Option<PathBuf>, + }, + FetchList { + list: ExperimentListSource, + file: Option<PathBuf>, + }, + FmlPassthrough { + args: Vec<OsString>, + cwd: PathBuf, + }, + Info { + experiment: ExperimentSource, + output: Option<PathBuf>, + }, + Kill { + app: LaunchableApp, + }, + List { + list: ExperimentListSource, + }, + LogState { + app: LaunchableApp, + open: AppOpenArgs, + }, + NoOp, + Open { + app: LaunchableApp, + open: AppOpenArgs, + }, + Reset { + app: LaunchableApp, + }, + StartServer, + TailLogs { + app: LaunchableApp, + }, + Unenroll { + app: LaunchableApp, + open: AppOpenArgs, + }, + ValidateExperiment { + params: NimbusApp, + manifest: ManifestSource, + experiment: ExperimentSource, + }, +
}

Variants§

§

ApplyFile

Fields

§preserve_nimbus_db: bool
§

CaptureLogs

Fields

§file: PathBuf
§

Defaults

Fields

§feature_id: Option<String>
§output: Option<PathBuf>
§

Enroll

Fields

§params: NimbusApp
§experiment: ExperimentSource
§branch: String
§preserve_targeting: bool
§preserve_bucketing: bool
§preserve_nimbus_db: bool
§

ExtractFeatures

Fields

§experiment: ExperimentSource
§branch: String
§feature_id: Option<String>
§validate: bool
§multi: bool
§output: Option<PathBuf>
§

FetchList

§

FmlPassthrough

Fields

§

Info

Fields

§experiment: ExperimentSource
§output: Option<PathBuf>
§

Kill

Fields

§

List

§

LogState

§

NoOp

§

Open

§

Reset

Fields

§

StartServer

§

TailLogs

Fields

§

Unenroll

§

ValidateExperiment

Fields

§params: NimbusApp
§experiment: ExperimentSource

Implementations§

source§

impl AppCommand

source

pub(crate) fn try_validate(cli: &Cli) -> Result<Self>

Trait Implementations§

source§

impl Debug for AppCommand

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl PartialEq<AppCommand> for AppCommand

source§

fn eq(&self, other: &AppCommand) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl TryFrom<&Cli> for AppCommand

§

type Error = Error

The type returned in the event of a conversion error.
source§

fn try_from(cli: &Cli) -> Result<Self>

Performs the conversion.
source§

impl StructuralPartialEq for AppCommand

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/enum.LaunchableApp.html b/book/rust-docs/nimbus_cli/enum.LaunchableApp.html new file mode 100644 index 0000000000..c8a7f8c871 --- /dev/null +++ b/book/rust-docs/nimbus_cli/enum.LaunchableApp.html @@ -0,0 +1,72 @@ +LaunchableApp in nimbus_cli - Rust
pub(crate) enum LaunchableApp {
+    Android {
+        package_name: String,
+        activity_name: String,
+        device_id: Option<String>,
+        scheme: Option<String>,
+        open_deeplink: Option<String>,
+    },
+    Ios {
+        device_id: String,
+        app_id: String,
+        scheme: Option<String>,
+    },
+}

Variants§

§

Android

Fields

§package_name: String
§activity_name: String
§device_id: Option<String>
§scheme: Option<String>
§open_deeplink: Option<String>
§

Ios

Fields

§device_id: String
§app_id: String
§scheme: Option<String>

Implementations§

source§

impl LaunchableApp

source

fn platform(&self) -> &str

source

fn exe(&self) -> Result<Command>

source

fn kill_app(&self) -> Result<bool>

source

fn unenroll_all(&self, open: &AppOpenArgs) -> Result<bool>

source

fn reset_app(&self) -> Result<bool>

source

fn tail_logs(&self) -> Result<bool>

source

fn capture_logs(&self, file: &PathBuf) -> Result<bool>

source

fn ios_log_file(&self) -> Result<PathBuf>

source

fn ios_log_file_command(&self) -> String

source

fn log_state(&self, open: &AppOpenArgs) -> Result<bool>

source

fn enroll( + &self, + params: &NimbusApp, + experiment: &ExperimentSource, + rollouts: &Vec<ExperimentSource>, + branch: &str, + preserve_targeting: &bool, + preserve_bucketing: &bool, + preserve_nimbus_db: &bool, + open: &AppOpenArgs +) -> Result<bool>

source

fn apply_list( + &self, + open: &AppOpenArgs, + list: &ExperimentListSource, + preserve_nimbus_db: &bool +) -> Result<bool>

source

fn ios_app_container(&self, container: &str) -> Result<String>

source

fn ios_reset(&self, data_dir: String, groups_string: String) -> Result<bool>

source

fn open(&self, open: &AppOpenArgs) -> Result<bool>

source

fn start_app( + &self, + app_protocol: StartAppProtocol<'_>, + open: &AppOpenArgs +) -> Result<bool>

source

fn android_start( + &self, + app_protocol: StartAppProtocol<'_>, + open: &AppOpenArgs +) -> Result<Command>

source

fn ios_start( + &self, + app_protocol: StartAppProtocol<'_>, + open: &AppOpenArgs +) -> Result<Command>

source§

impl LaunchableApp

source

pub(crate) fn try_from_app_channel_device( + app: Option<&str>, + channel: Option<&str>, + device_id: Option<&str> +) -> Result<Self>

source§

impl LaunchableApp

source

pub(crate) fn copy_to_clipboard( + &self, + app_protocol: &StartAppProtocol<'_>, + open: &AppOpenArgs +) -> Result<usize>

source

pub(crate) fn longform_url( + &self, + app_protocol: &StartAppProtocol<'_>, + open: &AppOpenArgs +) -> Result<String>

source

fn prepend_scheme(&self, deeplink: &str) -> Result<String>

source

fn mandatory_scheme(&self) -> Result<&str>

Trait Implementations§

source§

impl Clone for LaunchableApp

source§

fn clone(&self) -> LaunchableApp

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for LaunchableApp

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl PartialEq<LaunchableApp> for LaunchableApp

source§

fn eq(&self, other: &LaunchableApp) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl TryFrom<&Cli> for LaunchableApp

§

type Error = Error

The type returned in the event of a conversion error.
source§

fn try_from(value: &Cli) -> Result<Self>

Performs the conversion.
source§

impl StructuralPartialEq for LaunchableApp

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> FromRef<T> for Twhere + T: Clone,

§

fn from_ref(input: &T) -> T

Converts to this type from a reference to the input type.
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/feature_utils/fn.branch.html b/book/rust-docs/nimbus_cli/feature_utils/fn.branch.html new file mode 100644 index 0000000000..b82b794b06 --- /dev/null +++ b/book/rust-docs/nimbus_cli/feature_utils/fn.branch.html @@ -0,0 +1 @@ +branch in nimbus_cli::feature_utils - Rust
fn branch(feature_id: &str, file: &Path) -> Result<Value>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/feature_utils/fn.create_experiment.html b/book/rust-docs/nimbus_cli/feature_utils/fn.create_experiment.html new file mode 100644 index 0000000000..57aa6eeb0b --- /dev/null +++ b/book/rust-docs/nimbus_cli/feature_utils/fn.create_experiment.html @@ -0,0 +1,5 @@ +create_experiment in nimbus_cli::feature_utils - Rust
pub(crate) fn create_experiment(
+    app: &NimbusApp,
+    feature_id: &str,
+    files: &Vec<PathBuf>
+) -> Result<Value>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/feature_utils/fn.slug.html b/book/rust-docs/nimbus_cli/feature_utils/fn.slug.html new file mode 100644 index 0000000000..88f87e9814 --- /dev/null +++ b/book/rust-docs/nimbus_cli/feature_utils/fn.slug.html @@ -0,0 +1 @@ +slug in nimbus_cli::feature_utils - Rust

Function nimbus_cli::feature_utils::slug

source ·
pub(crate) fn slug(path: &Path) -> Result<String>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/feature_utils/index.html b/book/rust-docs/nimbus_cli/feature_utils/index.html new file mode 100644 index 0000000000..d546871d51 --- /dev/null +++ b/book/rust-docs/nimbus_cli/feature_utils/index.html @@ -0,0 +1 @@ +nimbus_cli::feature_utils - Rust
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/feature_utils/sidebar-items.js b/book/rust-docs/nimbus_cli/feature_utils/sidebar-items.js new file mode 100644 index 0000000000..cd881f7bbf --- /dev/null +++ b/book/rust-docs/nimbus_cli/feature_utils/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["branch","create_experiment","slug"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/fn.get_commands_from_cli.html b/book/rust-docs/nimbus_cli/fn.get_commands_from_cli.html new file mode 100644 index 0000000000..338e829524 --- /dev/null +++ b/book/rust-docs/nimbus_cli/fn.get_commands_from_cli.html @@ -0,0 +1,3 @@ +get_commands_from_cli in nimbus_cli - Rust
pub(crate) fn get_commands_from_cli<I, T>(args: I) -> Result<Vec<AppCommand>>where
+    I: IntoIterator<Item = T>,
+    T: Into<OsString> + Clone,
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/fn.main.html b/book/rust-docs/nimbus_cli/fn.main.html new file mode 100644 index 0000000000..13820058a8 --- /dev/null +++ b/book/rust-docs/nimbus_cli/fn.main.html @@ -0,0 +1 @@ +main in nimbus_cli - Rust

Function nimbus_cli::main

source ·
pub(crate) fn main() -> Result<()>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/index.html b/book/rust-docs/nimbus_cli/index.html new file mode 100644 index 0000000000..a5652e7dde --- /dev/null +++ b/book/rust-docs/nimbus_cli/index.html @@ -0,0 +1 @@ +nimbus_cli - Rust
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/output/deeplink/constant.QUERY.html b/book/rust-docs/nimbus_cli/output/deeplink/constant.QUERY.html new file mode 100644 index 0000000000..9d058c7c0d --- /dev/null +++ b/book/rust-docs/nimbus_cli/output/deeplink/constant.QUERY.html @@ -0,0 +1 @@ +QUERY in nimbus_cli::output::deeplink - Rust

Constant nimbus_cli::output::deeplink::QUERY

source ·
const QUERY: &AsciiSet;
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/output/deeplink/fn.join_query.html b/book/rust-docs/nimbus_cli/output/deeplink/fn.join_query.html new file mode 100644 index 0000000000..f8b420bb65 --- /dev/null +++ b/book/rust-docs/nimbus_cli/output/deeplink/fn.join_query.html @@ -0,0 +1 @@ +join_query in nimbus_cli::output::deeplink - Rust
fn join_query(url: &str, item: &str) -> String
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/output/deeplink/fn.longform_deeplink_url.html b/book/rust-docs/nimbus_cli/output/deeplink/fn.longform_deeplink_url.html new file mode 100644 index 0000000000..9e9337d24d --- /dev/null +++ b/book/rust-docs/nimbus_cli/output/deeplink/fn.longform_deeplink_url.html @@ -0,0 +1,5 @@ +longform_deeplink_url in nimbus_cli::output::deeplink - Rust
pub(crate) fn longform_deeplink_url(
+    deeplink: &str,
+    app_protocol: &StartAppProtocol<'_>
+) -> Result<String>
Expand description

Construct a URL from the deeplink and the protocol object.

+
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/output/deeplink/fn.set_clipboard.html b/book/rust-docs/nimbus_cli/output/deeplink/fn.set_clipboard.html new file mode 100644 index 0000000000..4ad9faa2a2 --- /dev/null +++ b/book/rust-docs/nimbus_cli/output/deeplink/fn.set_clipboard.html @@ -0,0 +1 @@ +set_clipboard in nimbus_cli::output::deeplink - Rust
fn set_clipboard(contents: String) -> Result<(), Box<dyn Error + Send + Sync>>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/output/deeplink/index.html b/book/rust-docs/nimbus_cli/output/deeplink/index.html new file mode 100644 index 0000000000..113d53797d --- /dev/null +++ b/book/rust-docs/nimbus_cli/output/deeplink/index.html @@ -0,0 +1 @@ +nimbus_cli::output::deeplink - Rust

Module nimbus_cli::output::deeplink

source ·

Constants

Functions

\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/output/deeplink/sidebar-items.js b/book/rust-docs/nimbus_cli/output/deeplink/sidebar-items.js new file mode 100644 index 0000000000..ab831c39c5 --- /dev/null +++ b/book/rust-docs/nimbus_cli/output/deeplink/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"constant":["QUERY"],"fn":["join_query","longform_deeplink_url","set_clipboard"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/output/features/index.html b/book/rust-docs/nimbus_cli/output/features/index.html new file mode 100644 index 0000000000..7fab6c4ed9 --- /dev/null +++ b/book/rust-docs/nimbus_cli/output/features/index.html @@ -0,0 +1 @@ +nimbus_cli::output::features - Rust
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/output/features/sidebar-items.js b/book/rust-docs/nimbus_cli/output/features/sidebar-items.js new file mode 100644 index 0000000000..5244ce01cc --- /dev/null +++ b/book/rust-docs/nimbus_cli/output/features/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {}; \ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/output/fetch/index.html b/book/rust-docs/nimbus_cli/output/fetch/index.html new file mode 100644 index 0000000000..a0236625d3 --- /dev/null +++ b/book/rust-docs/nimbus_cli/output/fetch/index.html @@ -0,0 +1 @@ +nimbus_cli::output::fetch - Rust

Module nimbus_cli::output::fetch

source ·
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/output/fetch/sidebar-items.js b/book/rust-docs/nimbus_cli/output/fetch/sidebar-items.js new file mode 100644 index 0000000000..5244ce01cc --- /dev/null +++ b/book/rust-docs/nimbus_cli/output/fetch/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {}; \ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/output/fml_cli/fn.fml_cli.html b/book/rust-docs/nimbus_cli/output/fml_cli/fn.fml_cli.html new file mode 100644 index 0000000000..6624d1cc9c --- /dev/null +++ b/book/rust-docs/nimbus_cli/output/fml_cli/fn.fml_cli.html @@ -0,0 +1 @@ +fml_cli in nimbus_cli::output::fml_cli - Rust

Function nimbus_cli::output::fml_cli::fml_cli

source ·
pub(crate) fn fml_cli(args: &Vec<OsString>, cwd: &Path) -> Result<bool>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/output/fml_cli/index.html b/book/rust-docs/nimbus_cli/output/fml_cli/index.html new file mode 100644 index 0000000000..c008fdff1c --- /dev/null +++ b/book/rust-docs/nimbus_cli/output/fml_cli/index.html @@ -0,0 +1 @@ +nimbus_cli::output::fml_cli - Rust
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/output/fml_cli/sidebar-items.js b/book/rust-docs/nimbus_cli/output/fml_cli/sidebar-items.js new file mode 100644 index 0000000000..d17a030d39 --- /dev/null +++ b/book/rust-docs/nimbus_cli/output/fml_cli/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["fml_cli"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/output/index.html b/book/rust-docs/nimbus_cli/output/index.html new file mode 100644 index 0000000000..1ed42752d0 --- /dev/null +++ b/book/rust-docs/nimbus_cli/output/index.html @@ -0,0 +1 @@ +nimbus_cli::output - Rust

Module nimbus_cli::output

source ·

Modules

\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/output/info/index.html b/book/rust-docs/nimbus_cli/output/info/index.html new file mode 100644 index 0000000000..75763364d7 --- /dev/null +++ b/book/rust-docs/nimbus_cli/output/info/index.html @@ -0,0 +1 @@ +nimbus_cli::output::info - Rust
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/output/info/sidebar-items.js b/book/rust-docs/nimbus_cli/output/info/sidebar-items.js new file mode 100644 index 0000000000..0cb4a25529 --- /dev/null +++ b/book/rust-docs/nimbus_cli/output/info/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"struct":["DateRange","ExperimentInfo"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/output/info/struct.DateRange.html b/book/rust-docs/nimbus_cli/output/info/struct.DateRange.html new file mode 100644 index 0000000000..6b6627d2e3 --- /dev/null +++ b/book/rust-docs/nimbus_cli/output/info/struct.DateRange.html @@ -0,0 +1,26 @@ +DateRange in nimbus_cli::output::info - Rust
pub(crate) struct DateRange<'a> {
+    start: Option<&'a str>,
+    end: Option<&'a str>,
+    proposed: Option<i64>,
+}

Fields§

§start: Option<&'a str>§end: Option<&'a str>§proposed: Option<i64>

Implementations§

source§

impl<'a> DateRange<'a>

source

fn new( + start: Option<&'a Value>, + end: Option<&'a Value>, + duration: Option<&'a Value> +) -> Self

source

pub(crate) fn contains(&self, date: &str) -> bool

Trait Implementations§

source§

impl<'a> Debug for DateRange<'a>

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl<'a> Default for DateRange<'a>

source§

fn default() -> DateRange<'a>

Returns the “default value” for a type. Read more
source§

impl Display for DateRange<'_>

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl<'a> Serialize for DateRange<'a>

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more

Auto Trait Implementations§

§

impl<'a> RefUnwindSafe for DateRange<'a>

§

impl<'a> Send for DateRange<'a>

§

impl<'a> Sync for DateRange<'a>

§

impl<'a> Unpin for DateRange<'a>

§

impl<'a> UnwindSafe for DateRange<'a>

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/output/info/struct.ExperimentInfo.html b/book/rust-docs/nimbus_cli/output/info/struct.ExperimentInfo.html new file mode 100644 index 0000000000..771282a369 --- /dev/null +++ b/book/rust-docs/nimbus_cli/output/info/struct.ExperimentInfo.html @@ -0,0 +1,31 @@ +ExperimentInfo in nimbus_cli::output::info - Rust
pub(crate) struct ExperimentInfo<'a> {
Show 13 fields + pub(crate) slug: &'a str, + pub(crate) app_name: &'a str, + pub(crate) channel: &'a str, + pub(crate) branches: Vec<&'a str>, + pub(crate) features: Vec<&'a str>, + pub(crate) targeting: &'a str, + pub(crate) bucketing: u64, + pub(crate) is_rollout: bool, + pub(crate) user_facing_name: &'a str, + pub(crate) user_facing_description: &'a str, + pub(crate) enrollment: DateRange<'a>, + pub(crate) is_enrollment_paused: bool, + pub(crate) duration: DateRange<'a>, +
}

Fields§

§slug: &'a str§app_name: &'a str§channel: &'a str§branches: Vec<&'a str>§features: Vec<&'a str>§targeting: &'a str§bucketing: u64§is_rollout: bool§user_facing_name: &'a str§user_facing_description: &'a str§enrollment: DateRange<'a>§is_enrollment_paused: bool§duration: DateRange<'a>

Implementations§

source§

impl<'a> ExperimentInfo<'a>

source

pub(crate) fn enrollment(&self) -> &DateRange<'a>

source

pub(crate) fn active(&self) -> &DateRange<'a>

source

fn bucketing_percent(&self) -> String

Trait Implementations§

source§

impl<'a> Debug for ExperimentInfo<'a>

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl<'a> Default for ExperimentInfo<'a>

source§

fn default() -> ExperimentInfo<'a>

Returns the “default value” for a type. Read more
source§

impl<'a> Serialize for ExperimentInfo<'a>

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more
source§

impl<'a> TryFrom<&'a Value> for ExperimentInfo<'a>

§

type Error = Error

The type returned in the event of a conversion error.
source§

fn try_from(exp: &'a Value) -> Result<Self>

Performs the conversion.

Auto Trait Implementations§

§

impl<'a> RefUnwindSafe for ExperimentInfo<'a>

§

impl<'a> Send for ExperimentInfo<'a>

§

impl<'a> Sync for ExperimentInfo<'a>

§

impl<'a> Unpin for ExperimentInfo<'a>

§

impl<'a> UnwindSafe for ExperimentInfo<'a>

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/output/server/fn.create_app.html b/book/rust-docs/nimbus_cli/output/server/fn.create_app.html new file mode 100644 index 0000000000..655fc5b0e1 --- /dev/null +++ b/book/rust-docs/nimbus_cli/output/server/fn.create_app.html @@ -0,0 +1,4 @@ +create_app in nimbus_cli::output::server - Rust
fn create_app(
+    livereload: LiveReloadLayer,
+    state: Arc<RwLock<InMemoryDb>>
+) -> Router
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/output/server/fn.create_server.html b/book/rust-docs/nimbus_cli/output/server/fn.create_server.html new file mode 100644 index 0000000000..c89596dcca --- /dev/null +++ b/book/rust-docs/nimbus_cli/output/server/fn.create_server.html @@ -0,0 +1,4 @@ +create_server in nimbus_cli::output::server - Rust
fn create_server(
+    livereload: LiveReloadLayer,
+    state: Arc<RwLock<InMemoryDb>>
+) -> Result<Server<AddrIncoming, IntoMakeService<Router>>, Error>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/output/server/fn.create_state.html b/book/rust-docs/nimbus_cli/output/server/fn.create_state.html new file mode 100644 index 0000000000..5aca75da60 --- /dev/null +++ b/book/rust-docs/nimbus_cli/output/server/fn.create_state.html @@ -0,0 +1 @@ +create_state in nimbus_cli::output::server - Rust
fn create_state(livereload: &LiveReloadLayer) -> Arc<RwLock<InMemoryDb>>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/output/server/fn.get_address.html b/book/rust-docs/nimbus_cli/output/server/fn.get_address.html new file mode 100644 index 0000000000..8fd49330b7 --- /dev/null +++ b/book/rust-docs/nimbus_cli/output/server/fn.get_address.html @@ -0,0 +1 @@ +get_address in nimbus_cli::output::server - Rust
pub(crate) fn get_address() -> Result<SocketAddr>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/output/server/fn.index.html b/book/rust-docs/nimbus_cli/output/server/fn.index.html new file mode 100644 index 0000000000..7fd7d98746 --- /dev/null +++ b/book/rust-docs/nimbus_cli/output/server/fn.index.html @@ -0,0 +1 @@ +index in nimbus_cli::output::server - Rust

Function nimbus_cli::output::server::index

source ·
async fn index(__arg0: State<Arc<RwLock<InMemoryDb>>>) -> Html<String>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/output/server/fn.no_cache_layer.html b/book/rust-docs/nimbus_cli/output/server/fn.no_cache_layer.html new file mode 100644 index 0000000000..fd43179a55 --- /dev/null +++ b/book/rust-docs/nimbus_cli/output/server/fn.no_cache_layer.html @@ -0,0 +1,2 @@ +no_cache_layer in nimbus_cli::output::server - Rust
fn no_cache_layer(
+) -> Stack<SetResponseHeaderLayer<HeaderValue>, Stack<SetResponseHeaderLayer<HeaderValue>, SetResponseHeaderLayer<HeaderValue>>>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/output/server/fn.post_deeplink.html b/book/rust-docs/nimbus_cli/output/server/fn.post_deeplink.html new file mode 100644 index 0000000000..6a3d0cf0e4 --- /dev/null +++ b/book/rust-docs/nimbus_cli/output/server/fn.post_deeplink.html @@ -0,0 +1,5 @@ +post_deeplink in nimbus_cli::output::server - Rust
pub(crate) fn post_deeplink(
+    platform: &str,
+    deeplink: &str,
+    experiments: Option<&Value>
+) -> Result<bool>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/output/server/fn.post_handler.html b/book/rust-docs/nimbus_cli/output/server/fn.post_handler.html new file mode 100644 index 0000000000..e9f3fb96af --- /dev/null +++ b/book/rust-docs/nimbus_cli/output/server/fn.post_handler.html @@ -0,0 +1,4 @@ +post_handler in nimbus_cli::output::server - Rust
async fn post_handler(
+    __arg0: State<Arc<RwLock<InMemoryDb>>>,
+    __arg1: Json<StartAppPostPayload>
+) -> impl IntoResponse
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/output/server/fn.post_payload.html b/book/rust-docs/nimbus_cli/output/server/fn.post_payload.html new file mode 100644 index 0000000000..bf9f107d5a --- /dev/null +++ b/book/rust-docs/nimbus_cli/output/server/fn.post_payload.html @@ -0,0 +1 @@ +post_payload in nimbus_cli::output::server - Rust
fn post_payload<T: Serialize>(payload: &T, addr: &str) -> Result<String>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/output/server/fn.rs.html b/book/rust-docs/nimbus_cli/output/server/fn.rs.html new file mode 100644 index 0000000000..f5ce396c06 --- /dev/null +++ b/book/rust-docs/nimbus_cli/output/server/fn.rs.html @@ -0,0 +1,4 @@ +rs in nimbus_cli::output::server - Rust

Function nimbus_cli::output::server::rs

source ·
async fn rs(
+    __arg0: State<Arc<RwLock<InMemoryDb>>>,
+    __arg1: Path<(String, String)>
+) -> impl IntoResponse
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/output/server/fn.script.html b/book/rust-docs/nimbus_cli/output/server/fn.script.html new file mode 100644 index 0000000000..86f7274cbe --- /dev/null +++ b/book/rust-docs/nimbus_cli/output/server/fn.script.html @@ -0,0 +1 @@ +script in nimbus_cli::output::server - Rust

Function nimbus_cli::output::server::script

source ·
async fn script(__arg0: State<Arc<RwLock<InMemoryDb>>>) -> &'static str
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/output/server/fn.start_server.html b/book/rust-docs/nimbus_cli/output/server/fn.start_server.html new file mode 100644 index 0000000000..298f833607 --- /dev/null +++ b/book/rust-docs/nimbus_cli/output/server/fn.start_server.html @@ -0,0 +1 @@ +start_server in nimbus_cli::output::server - Rust
pub(crate) fn start_server() -> Result<bool>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/output/server/fn.style.html b/book/rust-docs/nimbus_cli/output/server/fn.style.html new file mode 100644 index 0000000000..08af4b21f1 --- /dev/null +++ b/book/rust-docs/nimbus_cli/output/server/fn.style.html @@ -0,0 +1 @@ +style in nimbus_cli::output::server - Rust

Function nimbus_cli::output::server::style

source ·
async fn style(__arg0: State<Arc<RwLock<InMemoryDb>>>) -> &'static str
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/output/server/index.html b/book/rust-docs/nimbus_cli/output/server/index.html new file mode 100644 index 0000000000..e84035f973 --- /dev/null +++ b/book/rust-docs/nimbus_cli/output/server/index.html @@ -0,0 +1 @@ +nimbus_cli::output::server - Rust
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/output/server/sidebar-items.js b/book/rust-docs/nimbus_cli/output/server/sidebar-items.js new file mode 100644 index 0000000000..7af0f8765d --- /dev/null +++ b/book/rust-docs/nimbus_cli/output/server/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["create_app","create_server","create_state","get_address","index","no_cache_layer","post_deeplink","post_handler","post_payload","rs","script","start_server","style"],"struct":["InMemoryDb","StartAppPostPayload"],"type":["Db","Srhl"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/output/server/struct.InMemoryDb.html b/book/rust-docs/nimbus_cli/output/server/struct.InMemoryDb.html new file mode 100644 index 0000000000..ef558356a0 --- /dev/null +++ b/book/rust-docs/nimbus_cli/output/server/struct.InMemoryDb.html @@ -0,0 +1,20 @@ +InMemoryDb in nimbus_cli::output::server - Rust
struct InMemoryDb {
+    reloader: Reloader,
+    payloads: HashMap<String, StartAppPostPayload>,
+    latest: Option<String>,
+}

Fields§

§reloader: Reloader§payloads: HashMap<String, StartAppPostPayload>§latest: Option<String>

Implementations§

source§

impl InMemoryDb

source

fn new(reloader: Reloader) -> Self

source

fn url(&self, platform: &str) -> Option<&str>

source

fn update(&mut self, payload: StartAppPostPayload)

source

fn latest(&self) -> Option<&StartAppPostPayload>

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/output/server/struct.StartAppPostPayload.html b/book/rust-docs/nimbus_cli/output/server/struct.StartAppPostPayload.html new file mode 100644 index 0000000000..2348dfc6f4 --- /dev/null +++ b/book/rust-docs/nimbus_cli/output/server/struct.StartAppPostPayload.html @@ -0,0 +1,23 @@ +StartAppPostPayload in nimbus_cli::output::server - Rust
struct StartAppPostPayload {
+    platform: String,
+    url: String,
+    experiments: Option<Value>,
+}

Fields§

§platform: String§url: String§experiments: Option<Value>

Implementations§

source§

impl StartAppPostPayload

source

fn new(platform: &str, url: &str, experiments: Option<&Value>) -> Self

Trait Implementations§

source§

impl<'de> Deserialize<'de> for StartAppPostPayload

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl Serialize for StartAppPostPayload

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/output/server/type.Db.html b/book/rust-docs/nimbus_cli/output/server/type.Db.html new file mode 100644 index 0000000000..a52fe8b972 --- /dev/null +++ b/book/rust-docs/nimbus_cli/output/server/type.Db.html @@ -0,0 +1 @@ +Db in nimbus_cli::output::server - Rust

Type Definition nimbus_cli::output::server::Db

source ·
type Db = Arc<RwLock<InMemoryDb>>;
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/output/server/type.Srhl.html b/book/rust-docs/nimbus_cli/output/server/type.Srhl.html new file mode 100644 index 0000000000..532208713a --- /dev/null +++ b/book/rust-docs/nimbus_cli/output/server/type.Srhl.html @@ -0,0 +1 @@ +Srhl in nimbus_cli::output::server - Rust

Type Definition nimbus_cli::output::server::Srhl

source ·
type Srhl = SetResponseHeaderLayer<HeaderValue>;
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/output/sidebar-items.js b/book/rust-docs/nimbus_cli/output/sidebar-items.js new file mode 100644 index 0000000000..934a52e344 --- /dev/null +++ b/book/rust-docs/nimbus_cli/output/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"mod":["deeplink","features","fetch","fml_cli","info","server"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/protocol/index.html b/book/rust-docs/nimbus_cli/protocol/index.html new file mode 100644 index 0000000000..a2efcb337b --- /dev/null +++ b/book/rust-docs/nimbus_cli/protocol/index.html @@ -0,0 +1 @@ +nimbus_cli::protocol - Rust

Module nimbus_cli::protocol

source ·

Structs

\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/protocol/sidebar-items.js b/book/rust-docs/nimbus_cli/protocol/sidebar-items.js new file mode 100644 index 0000000000..89c9670a14 --- /dev/null +++ b/book/rust-docs/nimbus_cli/protocol/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"struct":["StartAppProtocol"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/protocol/struct.StartAppProtocol.html b/book/rust-docs/nimbus_cli/protocol/struct.StartAppProtocol.html new file mode 100644 index 0000000000..57a6940130 --- /dev/null +++ b/book/rust-docs/nimbus_cli/protocol/struct.StartAppProtocol.html @@ -0,0 +1,26 @@ +StartAppProtocol in nimbus_cli::protocol - Rust
pub(crate) struct StartAppProtocol<'a> {
+    pub(crate) reset_db: bool,
+    pub(crate) experiments: Option<&'a Value>,
+    pub(crate) log_state: bool,
+}
Expand description

This is the protocol that each app understands.

+

It is sent to the apps via the start_app command.

+

Any change to this protocol requires changing the Kotlin and Swift code, +and perhaps the app code itself.

+

Fields§

§reset_db: bool§experiments: Option<&'a Value>§log_state: bool

Trait Implementations§

source§

impl<'a> Clone for StartAppProtocol<'a>

source§

fn clone(&self) -> StartAppProtocol<'a>

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl<'a> Debug for StartAppProtocol<'a>

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl<'a> Default for StartAppProtocol<'a>

source§

fn default() -> StartAppProtocol<'a>

Returns the “default value” for a type. Read more

Auto Trait Implementations§

§

impl<'a> RefUnwindSafe for StartAppProtocol<'a>

§

impl<'a> Send for StartAppProtocol<'a>

§

impl<'a> Sync for StartAppProtocol<'a>

§

impl<'a> Unpin for StartAppProtocol<'a>

§

impl<'a> UnwindSafe for StartAppProtocol<'a>

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> FromRef<T> for Twhere + T: Clone,

§

fn from_ref(input: &T) -> T

Converts to this type from a reference to the input type.
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/sidebar-items.js b/book/rust-docs/nimbus_cli/sidebar-items.js new file mode 100644 index 0000000000..005f5370de --- /dev/null +++ b/book/rust-docs/nimbus_cli/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["AppCommand","LaunchableApp"],"fn":["get_commands_from_cli","main"],"mod":["cli","cmd","config","feature_utils","output","protocol","sources","updater","value_utils"],"static":["USER_AGENT"],"struct":["AppOpenArgs","NimbusApp"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/sources/experiment/enum.ExperimentSource.html b/book/rust-docs/nimbus_cli/sources/experiment/enum.ExperimentSource.html new file mode 100644 index 0000000000..e5a2f35fe7 --- /dev/null +++ b/book/rust-docs/nimbus_cli/sources/experiment/enum.ExperimentSource.html @@ -0,0 +1,60 @@ +ExperimentSource in nimbus_cli::sources::experiment - Rust
pub(crate) enum ExperimentSource {
+    FromList {
+        slug: String,
+        list: ExperimentListSource,
+    },
+    FromFeatureFiles {
+        app: NimbusApp,
+        feature_id: String,
+        files: Vec<PathBuf>,
+    },
+    FromApiV6 {
+        slug: String,
+        endpoint: String,
+    },
+    WithPatchFile {
+        patch: PathBuf,
+        inner: Box<ExperimentSource>,
+    },
+}

Variants§

§

FromList

§

FromFeatureFiles

Fields

§feature_id: String
§files: Vec<PathBuf>
§

FromApiV6

Fields

§slug: String
§endpoint: String
§

WithPatchFile

Fields

§patch: PathBuf

Implementations§

source§

impl ExperimentSource

source

pub(crate) fn print_features<P>( + &self, + branch: &String, + manifest_source: &ManifestSource, + feature_id: Option<&String>, + validate: bool, + multi: bool, + output: Option<P> +) -> Result<bool>where + P: AsRef<Path>,

source

fn get_features_json( + &self, + manifest_source: &ManifestSource, + feature_id: Option<&String>, + branch: &String, + validate: bool, + multi: bool +) -> Result<Value>

source§

impl ExperimentSource

source

pub(crate) fn print_info<P>(&self, output: Option<P>) -> Result<bool>where + P: AsRef<Path>,

source§

impl ExperimentSource

source

fn try_from_slug<'a>( + value: &'a str, + production: &'a str, + stage: &'a str +) -> Result<(&'a str, &'a str, bool)>

source

fn try_from_rs(value: &str) -> Result<Self>

source

fn try_from_url(value: &str) -> Result<Self>

source

fn try_from_api(value: &str) -> Result<Self>

source

pub(crate) fn try_from_file(file: &Path, slug: &str) -> Result<Self>

Trait Implementations§

source§

impl Clone for ExperimentSource

source§

fn clone(&self) -> ExperimentSource

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for ExperimentSource

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Display for ExperimentSource

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl PartialEq<ExperimentSource> for ExperimentSource

source§

fn eq(&self, other: &ExperimentSource) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl TryFrom<&Cli> for ExperimentSource

§

type Error = Error

The type returned in the event of a conversion error.
source§

fn try_from(value: &Cli) -> Result<Self>

Performs the conversion.
source§

impl TryFrom<&ExperimentArgs> for ExperimentSource

§

type Error = Error

The type returned in the event of a conversion error.
source§

fn try_from(value: &ExperimentArgs) -> Result<Self>

Performs the conversion.
source§

impl TryFrom<&ExperimentSource> for Value

§

type Error = Error

The type returned in the event of a conversion error.
source§

fn try_from(value: &ExperimentSource) -> Result<Value>

Performs the conversion.
source§

impl StructuralPartialEq for ExperimentSource

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> FromRef<T> for Twhere + T: Clone,

§

fn from_ref(input: &T) -> T

Converts to this type from a reference to the input type.
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/sources/experiment/fn.patch_experiment.html b/book/rust-docs/nimbus_cli/sources/experiment/fn.patch_experiment.html new file mode 100644 index 0000000000..d046540dbc --- /dev/null +++ b/book/rust-docs/nimbus_cli/sources/experiment/fn.patch_experiment.html @@ -0,0 +1,4 @@ +patch_experiment in nimbus_cli::sources::experiment - Rust
fn patch_experiment(
+    experiment: &ExperimentSource,
+    patch: &PathBuf
+) -> Result<Value>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/sources/experiment/index.html b/book/rust-docs/nimbus_cli/sources/experiment/index.html new file mode 100644 index 0000000000..1df279ad74 --- /dev/null +++ b/book/rust-docs/nimbus_cli/sources/experiment/index.html @@ -0,0 +1 @@ +nimbus_cli::sources::experiment - Rust
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/sources/experiment/sidebar-items.js b/book/rust-docs/nimbus_cli/sources/experiment/sidebar-items.js new file mode 100644 index 0000000000..ff02586270 --- /dev/null +++ b/book/rust-docs/nimbus_cli/sources/experiment/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["ExperimentSource"],"fn":["patch_experiment"],"struct":["FeatureDefaults"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/sources/experiment/struct.FeatureDefaults.html b/book/rust-docs/nimbus_cli/sources/experiment/struct.FeatureDefaults.html new file mode 100644 index 0000000000..13a6ffc0f9 --- /dev/null +++ b/book/rust-docs/nimbus_cli/sources/experiment/struct.FeatureDefaults.html @@ -0,0 +1,21 @@ +FeatureDefaults in nimbus_cli::sources::experiment - Rust
struct FeatureDefaults {
+    features: BTreeMap<String, Value>,
+}

Fields§

§features: BTreeMap<String, Value>

Trait Implementations§

source§

impl<'de> Deserialize<'de> for FeatureDefaults

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl Serialize for FeatureDefaults

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/sources/experiment_list/enum.ExperimentListSource.html b/book/rust-docs/nimbus_cli/sources/experiment_list/enum.ExperimentListSource.html new file mode 100644 index 0000000000..6934ce0b66 --- /dev/null +++ b/book/rust-docs/nimbus_cli/sources/experiment_list/enum.ExperimentListSource.html @@ -0,0 +1,44 @@ +ExperimentListSource in nimbus_cli::sources::experiment_list - Rust
pub(crate) enum ExperimentListSource {
+    Empty,
+    Filtered {
+        filter: ExperimentListFilter,
+        inner: Box<ExperimentListSource>,
+    },
+    FromApiV6 {
+        endpoint: String,
+    },
+    FromFile {
+        file: PathBuf,
+    },
+    FromRemoteSettings {
+        endpoint: String,
+        is_preview: bool,
+    },
+    FromRecipes {
+        recipes: Vec<ExperimentSource>,
+    },
+}

Variants§

§

Empty

§

Filtered

§

FromApiV6

Fields

§endpoint: String
§

FromFile

Fields

§file: PathBuf
§

FromRemoteSettings

Fields

§endpoint: String
§is_preview: bool
§

FromRecipes

Fields

Implementations§

source§

impl ExperimentListSource

source

pub(crate) fn fetch_list<P>(&self, file: Option<P>) -> Result<bool>where + P: AsRef<Path>,

source§

impl ExperimentListSource

source

pub(crate) fn print_list(&self) -> Result<bool>

source§

impl ExperimentListSource

source

fn try_from_slug<'a>( + slug: &'a str, + production: &'a str, + stage: &'a str +) -> Result<(&'a str, bool)>

source

pub(crate) fn try_from_rs(value: &str) -> Result<Self>

source

pub(crate) fn try_from_api(value: &str) -> Result<Self>

Trait Implementations§

source§

impl Clone for ExperimentListSource

source§

fn clone(&self) -> ExperimentListSource

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for ExperimentListSource

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl PartialEq<ExperimentListSource> for ExperimentListSource

source§

fn eq(&self, other: &ExperimentListSource) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl TryFrom<&Cli> for ExperimentListSource

§

type Error = Error

The type returned in the event of a conversion error.
source§

fn try_from(value: &Cli) -> Result<Self>

Performs the conversion.
source§

impl TryFrom<&ExperimentListArgs> for ExperimentListSource

§

type Error = Error

The type returned in the event of a conversion error.
source§

fn try_from(value: &ExperimentListArgs) -> Result<Self>

Performs the conversion.
source§

impl TryFrom<&ExperimentListSource> for Value

§

type Error = Error

The type returned in the event of a conversion error.
source§

fn try_from(value: &ExperimentListSource) -> Result<Value>

Performs the conversion.
source§

impl TryFrom<&Path> for ExperimentListSource

§

type Error = Error

The type returned in the event of a conversion error.
source§

fn try_from(value: &Path) -> Result<Self>

Performs the conversion.
source§

impl StructuralPartialEq for ExperimentListSource

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> FromRef<T> for Twhere + T: Clone,

§

fn from_ref(input: &T) -> T

Converts to this type from a reference to the input type.
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/sources/experiment_list/fn.decode_list_slug.html b/book/rust-docs/nimbus_cli/sources/experiment_list/fn.decode_list_slug.html new file mode 100644 index 0000000000..de483e923f --- /dev/null +++ b/book/rust-docs/nimbus_cli/sources/experiment_list/fn.decode_list_slug.html @@ -0,0 +1 @@ +decode_list_slug in nimbus_cli::sources::experiment_list - Rust
pub(crate) fn decode_list_slug(slug: &str) -> Result<(bool, bool)>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/sources/experiment_list/fn.filter_list.html b/book/rust-docs/nimbus_cli/sources/experiment_list/fn.filter_list.html new file mode 100644 index 0000000000..7a039580c4 --- /dev/null +++ b/book/rust-docs/nimbus_cli/sources/experiment_list/fn.filter_list.html @@ -0,0 +1,4 @@ +filter_list in nimbus_cli::sources::experiment_list - Rust
fn filter_list(
+    filter: &ExperimentListFilter,
+    inner: &ExperimentListSource
+) -> Result<Value>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/sources/experiment_list/fn.is_preview_collection.html b/book/rust-docs/nimbus_cli/sources/experiment_list/fn.is_preview_collection.html new file mode 100644 index 0000000000..1db0f8899d --- /dev/null +++ b/book/rust-docs/nimbus_cli/sources/experiment_list/fn.is_preview_collection.html @@ -0,0 +1 @@ +is_preview_collection in nimbus_cli::sources::experiment_list - Rust
fn is_preview_collection(slug: &str) -> Result<bool>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/sources/experiment_list/fn.is_production_server.html b/book/rust-docs/nimbus_cli/sources/experiment_list/fn.is_production_server.html new file mode 100644 index 0000000000..55b67a7ddd --- /dev/null +++ b/book/rust-docs/nimbus_cli/sources/experiment_list/fn.is_production_server.html @@ -0,0 +1 @@ +is_production_server in nimbus_cli::sources::experiment_list - Rust
fn is_production_server(slug: &str) -> Result<bool>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/sources/experiment_list/index.html b/book/rust-docs/nimbus_cli/sources/experiment_list/index.html new file mode 100644 index 0000000000..679f052514 --- /dev/null +++ b/book/rust-docs/nimbus_cli/sources/experiment_list/index.html @@ -0,0 +1 @@ +nimbus_cli::sources::experiment_list - Rust
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/sources/experiment_list/sidebar-items.js b/book/rust-docs/nimbus_cli/sources/experiment_list/sidebar-items.js new file mode 100644 index 0000000000..7022b188b7 --- /dev/null +++ b/book/rust-docs/nimbus_cli/sources/experiment_list/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["ExperimentListSource"],"fn":["decode_list_slug","filter_list","is_preview_collection","is_production_server"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/sources/filter/index.html b/book/rust-docs/nimbus_cli/sources/filter/index.html new file mode 100644 index 0000000000..5e4b7e3276 --- /dev/null +++ b/book/rust-docs/nimbus_cli/sources/filter/index.html @@ -0,0 +1 @@ +nimbus_cli::sources::filter - Rust
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/sources/filter/sidebar-items.js b/book/rust-docs/nimbus_cli/sources/filter/sidebar-items.js new file mode 100644 index 0000000000..5e00a6b801 --- /dev/null +++ b/book/rust-docs/nimbus_cli/sources/filter/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"struct":["ExperimentListFilter"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/sources/filter/struct.ExperimentListFilter.html b/book/rust-docs/nimbus_cli/sources/filter/struct.ExperimentListFilter.html new file mode 100644 index 0000000000..43a6b94d87 --- /dev/null +++ b/book/rust-docs/nimbus_cli/sources/filter/struct.ExperimentListFilter.html @@ -0,0 +1,28 @@ +ExperimentListFilter in nimbus_cli::sources::filter - Rust
pub(crate) struct ExperimentListFilter {
+    slug_pattern: Option<String>,
+    app: Option<String>,
+    feature_pattern: Option<String>,
+    active_on: Option<String>,
+    enrolling_on: Option<String>,
+    channel: Option<String>,
+    is_rollout: Option<bool>,
+}

Fields§

§slug_pattern: Option<String>§app: Option<String>§feature_pattern: Option<String>§active_on: Option<String>§enrolling_on: Option<String>§channel: Option<String>§is_rollout: Option<bool>

Implementations§

source§

impl ExperimentListFilter

source

pub(crate) fn for_app(app: &str) -> Self

source§

impl ExperimentListFilter

source

pub(crate) fn is_empty(&self) -> bool

source

pub(crate) fn matches(&self, value: &Value) -> Result<bool>

source

fn matches_info(&self, info: ExperimentInfo<'_>) -> bool

Trait Implementations§

source§

impl Clone for ExperimentListFilter

source§

fn clone(&self) -> ExperimentListFilter

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for ExperimentListFilter

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for ExperimentListFilter

source§

fn default() -> ExperimentListFilter

Returns the “default value” for a type. Read more
source§

impl From<&ExperimentListFilterArgs> for ExperimentListFilter

source§

fn from(value: &ExperimentListFilterArgs) -> Self

Converts to this type from the input type.
source§

impl PartialEq<ExperimentListFilter> for ExperimentListFilter

source§

fn eq(&self, other: &ExperimentListFilter) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl StructuralPartialEq for ExperimentListFilter

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> FromRef<T> for Twhere + T: Clone,

§

fn from_ref(input: &T) -> T

Converts to this type from a reference to the input type.
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/sources/index.html b/book/rust-docs/nimbus_cli/sources/index.html new file mode 100644 index 0000000000..f2ab54532f --- /dev/null +++ b/book/rust-docs/nimbus_cli/sources/index.html @@ -0,0 +1 @@ +nimbus_cli::sources - Rust
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/sources/manifest/enum.ManifestSource.html b/book/rust-docs/nimbus_cli/sources/manifest/enum.ManifestSource.html new file mode 100644 index 0000000000..65c4525628 --- /dev/null +++ b/book/rust-docs/nimbus_cli/sources/manifest/enum.ManifestSource.html @@ -0,0 +1,39 @@ +ManifestSource in nimbus_cli::sources::manifest - Rust
pub(crate) enum ManifestSource {
+    FromGithub {
+        channel: String,
+        github_repo: String,
+        ref_: String,
+        manifest_file: String,
+    },
+    FromFile {
+        channel: String,
+        manifest_file: String,
+    },
+}

Variants§

§

FromGithub

Fields

§channel: String
§github_repo: String
§ref_: String
§manifest_file: String
§

FromFile

Fields

§channel: String
§manifest_file: String

Implementations§

source§

impl ManifestSource

source

pub(crate) fn print_defaults<P>( + &self, + feature_id: Option<&String>, + output: Option<P> +) -> Result<bool>where + P: AsRef<Path>,

source

fn get_defaults_json( + &self, + fm: &FeatureManifest, + feature_id: Option<&String> +) -> Result<Value>

source§

impl ManifestSource

source

fn manifest_file(&self) -> &str

source

fn channel(&self) -> &str

source

fn manifest_loader(&self) -> Result<FileLoader>

source

pub(crate) fn try_from(params: &NimbusApp, value: &ManifestArgs) -> Result<Self>

Trait Implementations§

source§

impl Debug for ManifestSource

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Display for ManifestSource

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl PartialEq<ManifestSource> for ManifestSource

source§

fn eq(&self, other: &ManifestSource) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl TryFrom<&ManifestSource> for FeatureManifest

§

type Error = Error

The type returned in the event of a conversion error.
source§

fn try_from(value: &ManifestSource) -> Result<Self>

Performs the conversion.
source§

impl StructuralPartialEq for ManifestSource

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/sources/manifest/index.html b/book/rust-docs/nimbus_cli/sources/manifest/index.html new file mode 100644 index 0000000000..b5ee01b2ca --- /dev/null +++ b/book/rust-docs/nimbus_cli/sources/manifest/index.html @@ -0,0 +1 @@ +nimbus_cli::sources::manifest - Rust
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/sources/manifest/sidebar-items.js b/book/rust-docs/nimbus_cli/sources/manifest/sidebar-items.js new file mode 100644 index 0000000000..017e26b81e --- /dev/null +++ b/book/rust-docs/nimbus_cli/sources/manifest/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["ManifestSource"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/sources/sidebar-items.js b/book/rust-docs/nimbus_cli/sources/sidebar-items.js new file mode 100644 index 0000000000..d832b2b46e --- /dev/null +++ b/book/rust-docs/nimbus_cli/sources/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"mod":["experiment","experiment_list","filter","manifest"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/static.USER_AGENT.html b/book/rust-docs/nimbus_cli/static.USER_AGENT.html new file mode 100644 index 0000000000..1fb020dec7 --- /dev/null +++ b/book/rust-docs/nimbus_cli/static.USER_AGENT.html @@ -0,0 +1 @@ +USER_AGENT in nimbus_cli - Rust

Static nimbus_cli::USER_AGENT

source ·
pub(crate) static USER_AGENT: &str
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/struct.AppOpenArgs.html b/book/rust-docs/nimbus_cli/struct.AppOpenArgs.html new file mode 100644 index 0000000000..16b825d895 --- /dev/null +++ b/book/rust-docs/nimbus_cli/struct.AppOpenArgs.html @@ -0,0 +1,24 @@ +AppOpenArgs in nimbus_cli - Rust

Struct nimbus_cli::AppOpenArgs

source ·
pub(crate) struct AppOpenArgs {
+    pub(crate) deeplink: Option<String>,
+    pub(crate) passthrough: Vec<String>,
+    pub(crate) pbcopy: bool,
+    pub(crate) pbpaste: bool,
+    pub(crate) output: Option<PathBuf>,
+}

Fields§

§deeplink: Option<String>§passthrough: Vec<String>§pbcopy: bool§pbpaste: bool§output: Option<PathBuf>

Implementations§

source§

impl AppOpenArgs

source

pub(crate) fn args(&self) -> (&[String], &[String])

Trait Implementations§

source§

impl Debug for AppOpenArgs

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for AppOpenArgs

source§

fn default() -> AppOpenArgs

Returns the “default value” for a type. Read more
source§

impl From<OpenArgs> for AppOpenArgs

source§

fn from(value: OpenArgs) -> Self

Converts to this type from the input type.
source§

impl PartialEq<AppOpenArgs> for AppOpenArgs

source§

fn eq(&self, other: &AppOpenArgs) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl StructuralPartialEq for AppOpenArgs

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/struct.NimbusApp.html b/book/rust-docs/nimbus_cli/struct.NimbusApp.html new file mode 100644 index 0000000000..75bdb62ec5 --- /dev/null +++ b/book/rust-docs/nimbus_cli/struct.NimbusApp.html @@ -0,0 +1,31 @@ +NimbusApp in nimbus_cli - Rust

Struct nimbus_cli::NimbusApp

source ·
pub(crate) struct NimbusApp {
+    pub(crate) app_name: Option<String>,
+    pub(crate) channel: Option<String>,
+}

Fields§

§app_name: Option<String>§channel: Option<String>

Implementations§

source§

impl NimbusApp

source

fn validate_experiment( + &self, + manifest_source: &ManifestSource, + experiment: &ExperimentSource +) -> Result<bool>

source§

impl NimbusApp

source

pub(crate) fn ref_from_version( + &self, + version: &Option<String>, + ref_: &String +) -> Result<String>

source

pub(crate) fn github_repo<'a>(&self) -> Result<&'a str>

source

pub(crate) fn manifest_location<'a>(&self) -> Result<&'a str>

source§

impl NimbusApp

source

pub(crate) fn channel(&self) -> Option<String>

source

pub(crate) fn app_name(&self) -> Option<String>

Trait Implementations§

source§

impl Clone for NimbusApp

source§

fn clone(&self) -> NimbusApp

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for NimbusApp

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl From<&Cli> for NimbusApp

source§

fn from(value: &Cli) -> Self

Converts to this type from the input type.
source§

impl PartialEq<NimbusApp> for NimbusApp

source§

fn eq(&self, other: &NimbusApp) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl StructuralPartialEq for NimbusApp

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
§

impl<T> FromRef<T> for Twhere + T: Clone,

§

fn from_ref(input: &T) -> T

Converts to this type from a reference to the input type.
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/updater/fn.check_for_update.html b/book/rust-docs/nimbus_cli/updater/fn.check_for_update.html new file mode 100644 index 0000000000..2dde8b1e75 --- /dev/null +++ b/book/rust-docs/nimbus_cli/updater/fn.check_for_update.html @@ -0,0 +1 @@ +check_for_update in nimbus_cli::updater - Rust
pub(crate) fn check_for_update()
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/updater/index.html b/book/rust-docs/nimbus_cli/updater/index.html new file mode 100644 index 0000000000..06c558c087 --- /dev/null +++ b/book/rust-docs/nimbus_cli/updater/index.html @@ -0,0 +1 @@ +nimbus_cli::updater - Rust
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/updater/sidebar-items.js b/book/rust-docs/nimbus_cli/updater/sidebar-items.js new file mode 100644 index 0000000000..cbf0cb7e95 --- /dev/null +++ b/book/rust-docs/nimbus_cli/updater/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["check_for_update"],"mod":["taskcluster"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/updater/taskcluster/fn.check_taskcluster_for_update.html b/book/rust-docs/nimbus_cli/updater/taskcluster/fn.check_taskcluster_for_update.html new file mode 100644 index 0000000000..5e9cd61b3a --- /dev/null +++ b/book/rust-docs/nimbus_cli/updater/taskcluster/fn.check_taskcluster_for_update.html @@ -0,0 +1,4 @@ +check_taskcluster_for_update in nimbus_cli::updater::taskcluster - Rust
pub(crate) fn check_taskcluster_for_update<F>(message: F)where
+    F: Fn(&str, &str),
Expand description

Check the specifically crafted JSON file for this package to see if there has been a change in version. +This is done every hour.

+
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/updater/taskcluster/index.html b/book/rust-docs/nimbus_cli/updater/taskcluster/index.html new file mode 100644 index 0000000000..65f376e7a3 --- /dev/null +++ b/book/rust-docs/nimbus_cli/updater/taskcluster/index.html @@ -0,0 +1,2 @@ +nimbus_cli::updater::taskcluster - Rust

Structs

Functions

  • Check the specifically crafted JSON file for this package to see if there has been a change in version. +This is done every hour.
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/updater/taskcluster/sidebar-items.js b/book/rust-docs/nimbus_cli/updater/taskcluster/sidebar-items.js new file mode 100644 index 0000000000..b31937cb82 --- /dev/null +++ b/book/rust-docs/nimbus_cli/updater/taskcluster/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["check_taskcluster_for_update"],"struct":["ReqwestGunzippingHttpClient","Response","TaskClusterRegistry"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/updater/taskcluster/struct.ReqwestGunzippingHttpClient.html b/book/rust-docs/nimbus_cli/updater/taskcluster/struct.ReqwestGunzippingHttpClient.html new file mode 100644 index 0000000000..c3749c8c6e --- /dev/null +++ b/book/rust-docs/nimbus_cli/updater/taskcluster/struct.ReqwestGunzippingHttpClient.html @@ -0,0 +1,20 @@ +ReqwestGunzippingHttpClient in nimbus_cli::updater::taskcluster - Rust
pub struct ReqwestGunzippingHttpClient;

Trait Implementations§

source§

impl HttpClient for ReqwestGunzippingHttpClient

source§

fn get<T: DeserializeOwned>( + url: &str, + timeout: Duration, + headers: HeaderMap<'_> +) -> Result<T>

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/updater/taskcluster/struct.Response.html b/book/rust-docs/nimbus_cli/updater/taskcluster/struct.Response.html new file mode 100644 index 0000000000..f8af1a92f1 --- /dev/null +++ b/book/rust-docs/nimbus_cli/updater/taskcluster/struct.Response.html @@ -0,0 +1,20 @@ +Response in nimbus_cli::updater::taskcluster - Rust
struct Response {
+    version: String,
+}

Fields§

§version: String

Trait Implementations§

source§

impl<'de> Deserialize<'de> for Response

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/updater/taskcluster/struct.TaskClusterRegistry.html b/book/rust-docs/nimbus_cli/updater/taskcluster/struct.TaskClusterRegistry.html new file mode 100644 index 0000000000..5bd30d6eb5 --- /dev/null +++ b/book/rust-docs/nimbus_cli/updater/taskcluster/struct.TaskClusterRegistry.html @@ -0,0 +1,19 @@ +TaskClusterRegistry in nimbus_cli::updater::taskcluster - Rust
struct TaskClusterRegistry;

Trait Implementations§

source§

impl Registry for TaskClusterRegistry

source§

const NAME: &'static str = "taskcluster"

The name of the registry.
source§

fn get_latest_version<T: HttpClient>( + http_client: GenericHttpClient<'_, T>, + pkg: &Package<'_> +) -> Result<Option<String>>

Gets the latest version of a package from the registry. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/value_utils/fn.is_yaml.html b/book/rust-docs/nimbus_cli/value_utils/fn.is_yaml.html new file mode 100644 index 0000000000..b16df3e79d --- /dev/null +++ b/book/rust-docs/nimbus_cli/value_utils/fn.is_yaml.html @@ -0,0 +1,2 @@ +is_yaml in nimbus_cli::value_utils - Rust
fn is_yaml<P>(file: P) -> boolwhere
+    P: AsRef<Path>,
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/value_utils/fn.prepare_experiment.html b/book/rust-docs/nimbus_cli/value_utils/fn.prepare_experiment.html new file mode 100644 index 0000000000..1d8d028c60 --- /dev/null +++ b/book/rust-docs/nimbus_cli/value_utils/fn.prepare_experiment.html @@ -0,0 +1,7 @@ +prepare_experiment in nimbus_cli::value_utils - Rust
pub(crate) fn prepare_experiment(
+    recipe: &Value,
+    params: &NimbusApp,
+    branch: &str,
+    preserve_targeting: bool,
+    preserve_bucketing: bool
+) -> Result<Value>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/value_utils/fn.prepare_recipe.html b/book/rust-docs/nimbus_cli/value_utils/fn.prepare_recipe.html new file mode 100644 index 0000000000..432e9851f8 --- /dev/null +++ b/book/rust-docs/nimbus_cli/value_utils/fn.prepare_recipe.html @@ -0,0 +1,6 @@ +prepare_recipe in nimbus_cli::value_utils - Rust
fn prepare_recipe(
+    recipe: &Value,
+    params: &NimbusApp,
+    preserve_targeting: bool,
+    preserve_bucketing: bool
+) -> Result<Value>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/value_utils/fn.prepare_rollout.html b/book/rust-docs/nimbus_cli/value_utils/fn.prepare_rollout.html new file mode 100644 index 0000000000..065ac886d1 --- /dev/null +++ b/book/rust-docs/nimbus_cli/value_utils/fn.prepare_rollout.html @@ -0,0 +1,6 @@ +prepare_rollout in nimbus_cli::value_utils - Rust
pub(crate) fn prepare_rollout(
+    recipe: &Value,
+    params: &NimbusApp,
+    preserve_targeting: bool,
+    preserve_bucketing: bool
+) -> Result<Value>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/value_utils/fn.read_from_file.html b/book/rust-docs/nimbus_cli/value_utils/fn.read_from_file.html new file mode 100644 index 0000000000..d4f11232d5 --- /dev/null +++ b/book/rust-docs/nimbus_cli/value_utils/fn.read_from_file.html @@ -0,0 +1,3 @@ +read_from_file in nimbus_cli::value_utils - Rust
pub(crate) fn read_from_file<P, T>(file: P) -> Result<T>where
+    P: AsRef<Path>,
+    for<'a> T: Deserialize<'a>,
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/value_utils/fn.try_extract_data_list.html b/book/rust-docs/nimbus_cli/value_utils/fn.try_extract_data_list.html new file mode 100644 index 0000000000..bd522cda58 --- /dev/null +++ b/book/rust-docs/nimbus_cli/value_utils/fn.try_extract_data_list.html @@ -0,0 +1 @@ +try_extract_data_list in nimbus_cli::value_utils - Rust
pub(crate) fn try_extract_data_list(value: &Value) -> Result<Vec<Value>>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/value_utils/fn.try_find_branches_from_experiment.html b/book/rust-docs/nimbus_cli/value_utils/fn.try_find_branches_from_experiment.html new file mode 100644 index 0000000000..ceed471368 --- /dev/null +++ b/book/rust-docs/nimbus_cli/value_utils/fn.try_find_branches_from_experiment.html @@ -0,0 +1,3 @@ +try_find_branches_from_experiment in nimbus_cli::value_utils - Rust
pub(crate) fn try_find_branches_from_experiment(
+    value: &Value
+) -> Result<Vec<Value>>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/value_utils/fn.try_find_experiment.html b/book/rust-docs/nimbus_cli/value_utils/fn.try_find_experiment.html new file mode 100644 index 0000000000..62173004a8 --- /dev/null +++ b/book/rust-docs/nimbus_cli/value_utils/fn.try_find_experiment.html @@ -0,0 +1 @@ +try_find_experiment in nimbus_cli::value_utils - Rust
pub(crate) fn try_find_experiment(value: &Value, slug: &str) -> Result<Value>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/value_utils/fn.try_find_features_from_branch.html b/book/rust-docs/nimbus_cli/value_utils/fn.try_find_features_from_branch.html new file mode 100644 index 0000000000..14a9bb2c14 --- /dev/null +++ b/book/rust-docs/nimbus_cli/value_utils/fn.try_find_features_from_branch.html @@ -0,0 +1 @@ +try_find_features_from_branch in nimbus_cli::value_utils - Rust
pub(crate) fn try_find_features_from_branch(value: &Value) -> Result<Vec<Value>>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/value_utils/fn.try_find_mut_features_from_branch.html b/book/rust-docs/nimbus_cli/value_utils/fn.try_find_mut_features_from_branch.html new file mode 100644 index 0000000000..9b1b417f31 --- /dev/null +++ b/book/rust-docs/nimbus_cli/value_utils/fn.try_find_mut_features_from_branch.html @@ -0,0 +1,3 @@ +try_find_mut_features_from_branch in nimbus_cli::value_utils - Rust
pub(crate) fn try_find_mut_features_from_branch<'a>(
+    value: &'a mut Value
+) -> Result<HashMap<String, &'a mut Value>>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/value_utils/fn.write_to_file_or_print.html b/book/rust-docs/nimbus_cli/value_utils/fn.write_to_file_or_print.html new file mode 100644 index 0000000000..fc95fd4dae --- /dev/null +++ b/book/rust-docs/nimbus_cli/value_utils/fn.write_to_file_or_print.html @@ -0,0 +1,6 @@ +write_to_file_or_print in nimbus_cli::value_utils - Rust
pub(crate) fn write_to_file_or_print<P, T>(
+    file: Option<P>,
+    contents: &T
+) -> Result<()>where
+    P: AsRef<Path>,
+    T: Serialize,
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/value_utils/index.html b/book/rust-docs/nimbus_cli/value_utils/index.html new file mode 100644 index 0000000000..422cba3731 --- /dev/null +++ b/book/rust-docs/nimbus_cli/value_utils/index.html @@ -0,0 +1 @@ +nimbus_cli::value_utils - Rust
\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/value_utils/sidebar-items.js b/book/rust-docs/nimbus_cli/value_utils/sidebar-items.js new file mode 100644 index 0000000000..72f77cc3d8 --- /dev/null +++ b/book/rust-docs/nimbus_cli/value_utils/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["is_yaml","prepare_experiment","prepare_recipe","prepare_rollout","read_from_file","try_extract_data_list","try_find_branches_from_experiment","try_find_experiment","try_find_features_from_branch","try_find_mut_features_from_branch","write_to_file_or_print"],"trait":["CliUtils","Patch"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/value_utils/trait.CliUtils.html b/book/rust-docs/nimbus_cli/value_utils/trait.CliUtils.html new file mode 100644 index 0000000000..cc84189d63 --- /dev/null +++ b/book/rust-docs/nimbus_cli/value_utils/trait.CliUtils.html @@ -0,0 +1,15 @@ +CliUtils in nimbus_cli::value_utils - Rust
pub(crate) trait CliUtils {
+    // Required methods
+    fn get_str<'a>(&'a self, key: &str) -> Result<&'a str>;
+    fn get_bool(&self, key: &str) -> Result<bool>;
+    fn get_array<'a>(&'a self, key: &str) -> Result<&'a Vec<Value>>;
+    fn get_mut_array<'a>(&'a mut self, key: &str) -> Result<&'a mut Vec<Value>>;
+    fn get_mut_object<'a>(&'a mut self, key: &str) -> Result<&'a mut Value>;
+    fn get_object<'a>(&'a self, key: &str) -> Result<&'a Value>;
+    fn get_u64(&self, key: &str) -> Result<u64>;
+    fn has(&self, key: &str) -> bool;
+    fn set<V>(&mut self, key: &str, value: V) -> Result<()>
+       where V: Serialize;
+}

Required Methods§

source

fn get_str<'a>(&'a self, key: &str) -> Result<&'a str>

source

fn get_bool(&self, key: &str) -> Result<bool>

source

fn get_array<'a>(&'a self, key: &str) -> Result<&'a Vec<Value>>

source

fn get_mut_array<'a>(&'a mut self, key: &str) -> Result<&'a mut Vec<Value>>

source

fn get_mut_object<'a>(&'a mut self, key: &str) -> Result<&'a mut Value>

source

fn get_object<'a>(&'a self, key: &str) -> Result<&'a Value>

source

fn get_u64(&self, key: &str) -> Result<u64>

source

fn has(&self, key: &str) -> bool

source

fn set<V>(&mut self, key: &str, value: V) -> Result<()>where + V: Serialize,

Implementations on Foreign Types§

source§

impl CliUtils for Value

source§

fn get_str<'a>(&'a self, key: &str) -> Result<&'a str>

source§

fn get_bool(&self, key: &str) -> Result<bool>

source§

fn get_array<'a>(&'a self, key: &str) -> Result<&'a Vec<Value>>

source§

fn get_mut_array<'a>(&'a mut self, key: &str) -> Result<&'a mut Vec<Value>>

source§

fn get_object<'a>(&'a self, key: &str) -> Result<&'a Value>

source§

fn get_mut_object<'a>(&'a mut self, key: &str) -> Result<&'a mut Value>

source§

fn get_u64(&self, key: &str) -> Result<u64>

source§

fn set<V>(&mut self, key: &str, value: V) -> Result<()>where + V: Serialize,

source§

fn has(&self, key: &str) -> bool

Implementors§

\ No newline at end of file diff --git a/book/rust-docs/nimbus_cli/value_utils/trait.Patch.html b/book/rust-docs/nimbus_cli/value_utils/trait.Patch.html new file mode 100644 index 0000000000..8976de3d98 --- /dev/null +++ b/book/rust-docs/nimbus_cli/value_utils/trait.Patch.html @@ -0,0 +1,4 @@ +Patch in nimbus_cli::value_utils - Rust
pub(crate) trait Patch {
+    // Required method
+    fn patch(&mut self, patch: &Self) -> bool;
+}

Required Methods§

source

fn patch(&mut self, patch: &Self) -> bool

Implementations on Foreign Types§

source§

impl Patch for Map<String, Value>

source§

fn patch(&mut self, patch: &Self) -> bool

source§

impl Patch for Value

source§

fn patch(&mut self, patch: &Self) -> bool

Implementors§

\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/all.html b/book/rust-docs/nimbus_fml/all.html new file mode 100644 index 0000000000..aeff1a2b6c --- /dev/null +++ b/book/rust-docs/nimbus_fml/all.html @@ -0,0 +1 @@ +List of all items in this crate

List of all items

Structs

Enums

Traits

Functions

Type Definitions

Statics

Constants

\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/enum.VariablesType.html b/book/rust-docs/nimbus_fml/backends/enum.VariablesType.html new file mode 100644 index 0000000000..db2f2815b9 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/enum.VariablesType.html @@ -0,0 +1,29 @@ +VariablesType in nimbus_fml::backends - Rust
pub enum VariablesType {
+    Bool,
+    Image,
+    Int,
+    String,
+    Text,
+    Variables,
+}
Expand description

The generated code is running against hand written code to give type safe, error free access to JSON. +This is the Variables object. This enum gives the underlying types that the Variables object supports.

+

Variants§

§

Bool

§

Image

§

Int

§

String

§

Text

§

Variables

Trait Implementations§

source§

impl Display for VariablesType

The Variables objects use a naming convention to name its methods. e.g. getBool, getBoolList, getBoolMap. +In part this is to make generating code easier. +This is the mapping from type to identifier part that corresponds to its type.

+
source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/experimenter_manifest/enum.ExperimentManifestPropType.html b/book/rust-docs/nimbus_fml/backends/experimenter_manifest/enum.ExperimentManifestPropType.html new file mode 100644 index 0000000000..377aa98f82 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/experimenter_manifest/enum.ExperimentManifestPropType.html @@ -0,0 +1,22 @@ +ExperimentManifestPropType in nimbus_fml::backends::experimenter_manifest - Rust
enum ExperimentManifestPropType {
+    Json,
+    Boolean,
+    Int,
+    String,
+}

Variants§

§

Json

§

Boolean

§

Int

§

String

Trait Implementations§

source§

impl Display for ExperimentManifestPropType

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl From<Box<TypeRef, Global>> for ExperimentManifestPropType

source§

fn from(typ: Box<TypeRef>) -> Self

Converts to this type from the input type.
source§

impl From<TypeRef> for ExperimentManifestPropType

source§

fn from(typ: TypeRef) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/experimenter_manifest/fn.generate_manifest.html b/book/rust-docs/nimbus_fml/backends/experimenter_manifest/fn.generate_manifest.html new file mode 100644 index 0000000000..7c29a52673 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/experimenter_manifest/fn.generate_manifest.html @@ -0,0 +1,4 @@ +generate_manifest in nimbus_fml::backends::experimenter_manifest - Rust
pub(crate) fn generate_manifest(
+    ir: FeatureManifest,
+    cmd: &GenerateExperimenterManifestCmd
+) -> Result<(), FMLError>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/experimenter_manifest/index.html b/book/rust-docs/nimbus_fml/backends/experimenter_manifest/index.html new file mode 100644 index 0000000000..724662912a --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/experimenter_manifest/index.html @@ -0,0 +1 @@ +nimbus_fml::backends::experimenter_manifest - Rust
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/experimenter_manifest/sidebar-items.js b/book/rust-docs/nimbus_fml/backends/experimenter_manifest/sidebar-items.js new file mode 100644 index 0000000000..b7a6650bb4 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/experimenter_manifest/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["ExperimentManifestPropType"],"fn":["generate_manifest"],"struct":["ExperimenterFeature","ExperimenterFeatureProperty"],"type":["ExperimenterManifest"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/experimenter_manifest/struct.ExperimenterFeature.html b/book/rust-docs/nimbus_fml/backends/experimenter_manifest/struct.ExperimenterFeature.html new file mode 100644 index 0000000000..ac63d47d99 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/experimenter_manifest/struct.ExperimenterFeature.html @@ -0,0 +1,26 @@ +ExperimenterFeature in nimbus_fml::backends::experimenter_manifest - Rust
pub(crate) struct ExperimenterFeature {
+    description: String,
+    has_exposure: bool,
+    exposure_description: Option<String>,
+    is_early_startup: Option<bool>,
+    variables: BTreeMap<String, ExperimenterFeatureProperty>,
+}

Fields§

§description: String§has_exposure: bool§exposure_description: Option<String>§is_early_startup: Option<bool>§variables: BTreeMap<String, ExperimenterFeatureProperty>

Trait Implementations§

source§

impl Clone for ExperimenterFeature

source§

fn clone(&self) -> ExperimenterFeature

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for ExperimenterFeature

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for ExperimenterFeature

source§

fn default() -> ExperimenterFeature

Returns the “default value” for a type. Read more
source§

impl<'de> Deserialize<'de> for ExperimenterFeature

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl Serialize for ExperimenterFeature

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/experimenter_manifest/struct.ExperimenterFeatureProperty.html b/book/rust-docs/nimbus_fml/backends/experimenter_manifest/struct.ExperimenterFeatureProperty.html new file mode 100644 index 0000000000..6e24a285e9 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/experimenter_manifest/struct.ExperimenterFeatureProperty.html @@ -0,0 +1,24 @@ +ExperimenterFeatureProperty in nimbus_fml::backends::experimenter_manifest - Rust
pub(crate) struct ExperimenterFeatureProperty {
+    property_type: String,
+    description: String,
+    variants: Option<BTreeSet<String>>,
+}

Fields§

§property_type: String§description: String§variants: Option<BTreeSet<String>>

Trait Implementations§

source§

impl Clone for ExperimenterFeatureProperty

source§

fn clone(&self) -> ExperimenterFeatureProperty

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for ExperimenterFeatureProperty

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for ExperimenterFeatureProperty

source§

fn default() -> ExperimenterFeatureProperty

Returns the “default value” for a type. Read more
source§

impl<'de> Deserialize<'de> for ExperimenterFeatureProperty

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl Serialize for ExperimenterFeatureProperty

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/experimenter_manifest/type.ExperimenterManifest.html b/book/rust-docs/nimbus_fml/backends/experimenter_manifest/type.ExperimenterManifest.html new file mode 100644 index 0000000000..cab646f783 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/experimenter_manifest/type.ExperimenterManifest.html @@ -0,0 +1 @@ +ExperimenterManifest in nimbus_fml::backends::experimenter_manifest - Rust
pub(crate) type ExperimenterManifest = BTreeMap<String, ExperimenterFeature>;
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/frontend_manifest/fn.merge.html b/book/rust-docs/nimbus_fml/backends/frontend_manifest/fn.merge.html new file mode 100644 index 0000000000..92998fc8c9 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/frontend_manifest/fn.merge.html @@ -0,0 +1,9 @@ +merge in nimbus_fml::backends::frontend_manifest - Rust
fn merge<ListGetter, NameGetter, S, T>(
+    root: &FeatureManifest,
+    list_getter: ListGetter,
+    name_getter: NameGetter
+) -> BTreeMap<String, T>where
+    S: Clone,
+    T: From<S>,
+    ListGetter: Fn(&FeatureManifest) -> Vec<&S>,
+    NameGetter: Fn(&S) -> &str,
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/frontend_manifest/index.html b/book/rust-docs/nimbus_fml/backends/frontend_manifest/index.html new file mode 100644 index 0000000000..4e42f4a691 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/frontend_manifest/index.html @@ -0,0 +1 @@ +nimbus_fml::backends::frontend_manifest - Rust
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/frontend_manifest/sidebar-items.js b/book/rust-docs/nimbus_fml/backends/frontend_manifest/sidebar-items.js new file mode 100644 index 0000000000..40689fc957 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/frontend_manifest/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["merge"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/index.html b/book/rust-docs/nimbus_fml/backends/index.html new file mode 100644 index 0000000000..d238be55a3 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/index.html @@ -0,0 +1,29 @@ +nimbus_fml::backends - Rust

Module nimbus_fml::backends

source ·
Expand description

Backend traits

+

This module provides a number of traits useful for implementing a backend for FML structs.

+

A CodeType is needed for each type that is referred to in the feature definition (i.e. every TypeRef +instance should have corresponding CodeType instance). Helper code for types might include managing how merging/overriding of +defaults occur.

+

A CodeDeclaration is needed for each type that is declared in the manifest file: i.e. an Object classes, Enum classes and Feature classes. +This has access to intermediate structs of the crate::intermediate_representation::FeatureManifest so may want to do some additional lookups to help rendering.

+

CodeDeclarations provide the target language’s version of the type defined in the feature manifest. For objects and features, this would +be objects that have properties corresponding to the FML variables. For enums, this would mean the Enum class definition. In all cases, this will +likely be attached to an [askama::Template].

+

CodeDeclarations can also be used to conditionally include code: e.g. only include the CallbackInterfaceRuntime +if the user has used at least one callback interface.

+

Each backend has a wrapper template for each file it needs to generate. This should collect the CodeDeclarations that +the backend and FeatureManifest between them specify and use them to stitch together a file in the target language.

+

The CodeOracle provides methods to map the TypeRef values found in the FeatureManifest to the CodeTypes specified +by the backend.

+

Each backend will have its own filter module, which is used by the askama templates used in all CodeTypes and CodeDeclarations. +This filter provides methods to generate expressions and identifiers in the target language. These are all forwarded to the oracle.

+

Modules

Enums

  • The generated code is running against hand written code to give type safe, error free access to JSON. +This is the Variables object. This enum gives the underlying types that the Variables object supports.

Traits

  • A trait that is able to render a declaration about a particular member declared in +the FeatureManifest. +Like CodeType, it can render declaration code and imports. +All methods are optional, and there is no requirement that the trait be used for a particular +member. Thus, it can also be useful for conditionally rendering code.
  • An object to look up a foreign language code specific renderer for a given type used. +Every TypeRef referred to in the crate::intermediate_representation::FeatureManifest should map to a corresponding +CodeType.
  • A Trait to emit foreign language code to handle referenced types. +A type which is specified in the FML (i.e. a type that a variable declares itself of) +will have a CodeDeclaration as well, but for types used e.g. primitive types, Strings, etc +only a CodeType is needed.

Type Definitions

\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/kotlin/fn.generate_struct.html b/book/rust-docs/nimbus_fml/backends/kotlin/fn.generate_struct.html new file mode 100644 index 0000000000..38c3799892 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/kotlin/fn.generate_struct.html @@ -0,0 +1,4 @@ +generate_struct in nimbus_fml::backends::kotlin - Rust
pub(crate) fn generate_struct(
+    manifest: &FeatureManifest,
+    cmd: &GenerateStructCmd
+) -> Result<(), FMLError>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/bundled/fn.is_resource_id.html b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/bundled/fn.is_resource_id.html new file mode 100644 index 0000000000..f765263e9c --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/bundled/fn.is_resource_id.html @@ -0,0 +1 @@ +is_resource_id in nimbus_fml::backends::kotlin::gen_structs::bundled - Rust
fn is_resource_id(string: &str) -> bool
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/bundled/index.html b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/bundled/index.html new file mode 100644 index 0000000000..ee8c9e67a3 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/bundled/index.html @@ -0,0 +1 @@ +nimbus_fml::backends::kotlin::gen_structs::bundled - Rust
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/bundled/sidebar-items.js b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/bundled/sidebar-items.js new file mode 100644 index 0000000000..83c673cb33 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/bundled/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["is_resource_id"],"struct":["ImageCodeType","TextCodeType"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/bundled/struct.ImageCodeType.html b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/bundled/struct.ImageCodeType.html new file mode 100644 index 0000000000..151819de42 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/bundled/struct.ImageCodeType.html @@ -0,0 +1,61 @@ +ImageCodeType in nimbus_fml::backends::kotlin::gen_structs::bundled - Rust
pub(crate) struct ImageCodeType;

Trait Implementations§

source§

impl CodeType for ImageCodeType

source§

fn type_label(&self, _oracle: &dyn CodeOracle) -> String

The language specific label used to reference this type. This will be used in +method signatures and property declarations.

+
source§

fn variables_type(&self, _oracle: &dyn CodeOracle) -> VariablesType

The name of the type as it’s represented in the Variables object. +The string return may be used to combine with an indentifier, e.g. a Variables method name.

+
source§

fn literal( + &self, + _oracle: &dyn CodeOracle, + _ctx: &dyn Display, + _renderer: &dyn LiteralRenderer, + literal: &Value +) -> String

A representation of the given literal for this type. +N.B. Literal is aliased from serde_json::Value.

+
source§

fn property_getter( + &self, + oracle: &dyn CodeOracle, + vars: &dyn Display, + prop: &dyn Display, + default: &dyn Display +) -> String

The language specific expression that gets a value of the prop from the vars object, +and fallbacks to the default value. Read more
source§

fn value_getter( + &self, + oracle: &dyn CodeOracle, + vars: &dyn Display, + prop: &dyn Display +) -> String

The expression needed to get a value out of a Variables objectm with the prop key. Read more
source§

fn value_mapper(&self, oracle: &dyn CodeOracle) -> Option<String>

The method call here will use the create_transform to transform the value coming out of +the Variables object into the desired type. Read more
source§

fn defaults_type(&self, oracle: &dyn CodeOracle) -> String

source§

fn defaults_mapper( + &self, + _oracle: &dyn CodeOracle, + value: &dyn Display, + vars: &dyn Display +) -> Option<String>

source§

fn as_json_transform( + &self, + _oracle: &dyn CodeOracle, + prop: &dyn Display +) -> Option<String>

Implement these in different code types, and call recursively from different code types.
source§

fn is_resource_id(&self, literal: &Value) -> bool

source§

fn imports(&self, _oracle: &dyn CodeOracle) -> Option<Vec<String>>

A list of imports that are needed if this type is in use. +Classes are imported exactly once.
source§

fn value_merger( + &self, + _oracle: &dyn CodeOracle, + _default: &dyn Display +) -> Option<String>

The method call to merge the value with the defaults. Read more
source§

fn create_transform(&self, _oracle: &dyn CodeOracle) -> Option<String>

A function handle that is capable of turning the variables type to the TypeRef type.
source§

fn merge_transform(&self, _oracle: &dyn CodeOracle) -> Option<String>

A function handle that is capable of merging two instances of the same class. By default, this is None.
source§

fn preference_getter( + &self, + _oracle: &dyn CodeOracle, + _prefs: &dyn Display, + _pref_key: &dyn Display +) -> Option<String>

source§

fn as_json(&self, oracle: &dyn CodeOracle, prop: &dyn Display) -> String

Call from the template
source§

fn helper_code(&self, _oracle: &dyn CodeOracle) -> Option<String>

Optional helper code to make this type work. +This might include functions to patch a default value with another.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/bundled/struct.TextCodeType.html b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/bundled/struct.TextCodeType.html new file mode 100644 index 0000000000..5602cf6a5b --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/bundled/struct.TextCodeType.html @@ -0,0 +1,61 @@ +TextCodeType in nimbus_fml::backends::kotlin::gen_structs::bundled - Rust
pub(crate) struct TextCodeType;

Trait Implementations§

source§

impl CodeType for TextCodeType

source§

fn type_label(&self, _oracle: &dyn CodeOracle) -> String

The language specific label used to reference this type. This will be used in +method signatures and property declarations.

+
source§

fn variables_type(&self, _oracle: &dyn CodeOracle) -> VariablesType

The name of the type as it’s represented in the Variables object. +The string return may be used to combine with an indentifier, e.g. a Variables method name.

+
source§

fn literal( + &self, + _oracle: &dyn CodeOracle, + _ctx: &dyn Display, + _renderer: &dyn LiteralRenderer, + literal: &Value +) -> String

A representation of the given literal for this type. +N.B. Literal is aliased from serde_json::Value.

+
source§

fn property_getter( + &self, + oracle: &dyn CodeOracle, + vars: &dyn Display, + prop: &dyn Display, + default: &dyn Display +) -> String

The language specific expression that gets a value of the prop from the vars object, +and fallbacks to the default value. Read more
source§

fn value_getter( + &self, + oracle: &dyn CodeOracle, + vars: &dyn Display, + prop: &dyn Display +) -> String

The expression needed to get a value out of a Variables objectm with the prop key. Read more
source§

fn value_mapper(&self, oracle: &dyn CodeOracle) -> Option<String>

The method call here will use the create_transform to transform the value coming out of +the Variables object into the desired type. Read more
source§

fn defaults_type(&self, _oracle: &dyn CodeOracle) -> String

source§

fn defaults_mapper( + &self, + _oracle: &dyn CodeOracle, + value: &dyn Display, + vars: &dyn Display +) -> Option<String>

source§

fn preference_getter( + &self, + oracle: &dyn CodeOracle, + prefs: &dyn Display, + pref_key: &dyn Display +) -> Option<String>

source§

fn is_resource_id(&self, literal: &Value) -> bool

source§

fn imports(&self, _oracle: &dyn CodeOracle) -> Option<Vec<String>>

A list of imports that are needed if this type is in use. +Classes are imported exactly once.
source§

fn value_merger( + &self, + _oracle: &dyn CodeOracle, + _default: &dyn Display +) -> Option<String>

The method call to merge the value with the defaults. Read more
source§

fn create_transform(&self, _oracle: &dyn CodeOracle) -> Option<String>

A function handle that is capable of turning the variables type to the TypeRef type.
source§

fn merge_transform(&self, _oracle: &dyn CodeOracle) -> Option<String>

A function handle that is capable of merging two instances of the same class. By default, this is None.
source§

fn as_json(&self, oracle: &dyn CodeOracle, prop: &dyn Display) -> String

Call from the template
source§

fn as_json_transform( + &self, + _oracle: &dyn CodeOracle, + _prop: &dyn Display +) -> Option<String>

Implement these in different code types, and call recursively from different code types.
source§

fn helper_code(&self, _oracle: &dyn CodeOracle) -> Option<String>

Optional helper code to make this type work. +This might include functions to patch a default value with another.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/common/code_type/fn.property_getter.html b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/common/code_type/fn.property_getter.html new file mode 100644 index 0000000000..c4b6e36fee --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/common/code_type/fn.property_getter.html @@ -0,0 +1,8 @@ +property_getter in nimbus_fml::backends::kotlin::gen_structs::common::code_type - Rust
pub(crate) fn property_getter(
+    ct: &dyn CodeType,
+    oracle: &dyn CodeOracle,
+    vars: &dyn Display,
+    prop: &dyn Display,
+    default: &dyn Display
+) -> String
Expand description

The language specific expression that gets a value of the prop from the vars object.

+
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/common/code_type/fn.value_getter.html b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/common/code_type/fn.value_getter.html new file mode 100644 index 0000000000..cea63703f3 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/common/code_type/fn.value_getter.html @@ -0,0 +1,6 @@ +value_getter in nimbus_fml::backends::kotlin::gen_structs::common::code_type - Rust
pub(crate) fn value_getter(
+    ct: &dyn CodeType,
+    oracle: &dyn CodeOracle,
+    vars: &dyn Display,
+    prop: &dyn Display
+) -> String
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/common/code_type/fn.value_mapper.html b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/common/code_type/fn.value_mapper.html new file mode 100644 index 0000000000..11ab9147be --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/common/code_type/fn.value_mapper.html @@ -0,0 +1,4 @@ +value_mapper in nimbus_fml::backends::kotlin::gen_structs::common::code_type - Rust
pub(crate) fn value_mapper(
+    ct: &dyn CodeType,
+    oracle: &dyn CodeOracle
+) -> Option<String>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/common/code_type/index.html b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/common/code_type/index.html new file mode 100644 index 0000000000..a9149e3a98 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/common/code_type/index.html @@ -0,0 +1 @@ +nimbus_fml::backends::kotlin::gen_structs::common::code_type - Rust

Functions

\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/common/code_type/sidebar-items.js b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/common/code_type/sidebar-items.js new file mode 100644 index 0000000000..fd7e03e7dc --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/common/code_type/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["property_getter","value_getter","value_mapper"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/common/fn.class_name.html b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/common/fn.class_name.html new file mode 100644 index 0000000000..35faf3ab9a --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/common/fn.class_name.html @@ -0,0 +1,2 @@ +class_name in nimbus_fml::backends::kotlin::gen_structs::common - Rust
pub fn class_name(nm: &dyn Display) -> String
Expand description

Get the idiomatic Kotlin rendering of a class name (for enums, records, errors, etc).

+
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/common/fn.enum_variant_name.html b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/common/fn.enum_variant_name.html new file mode 100644 index 0000000000..10ac6501c1 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/common/fn.enum_variant_name.html @@ -0,0 +1,2 @@ +enum_variant_name in nimbus_fml::backends::kotlin::gen_structs::common - Rust
pub fn enum_variant_name(nm: &dyn Display) -> String
Expand description

Get the idiomatic Kotlin rendering of an individual enum variant.

+
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/common/fn.quoted.html b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/common/fn.quoted.html new file mode 100644 index 0000000000..a662bafa99 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/common/fn.quoted.html @@ -0,0 +1,4 @@ +quoted in nimbus_fml::backends::kotlin::gen_structs::common - Rust
pub fn quoted(string: &dyn Display) -> String
Expand description

Surrounds a string with quotes. +In Kotlin, you can “”“triple quote”“” multi-line strings +so you don’t have to escape “ and \n characters.

+
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/common/fn.var_name.html b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/common/fn.var_name.html new file mode 100644 index 0000000000..4f91eae5f0 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/common/fn.var_name.html @@ -0,0 +1,2 @@ +var_name in nimbus_fml::backends::kotlin::gen_structs::common - Rust
pub fn var_name(nm: &dyn Display) -> String
Expand description

Get the idiomatic Kotlin rendering of a variable name.

+
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/common/index.html b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/common/index.html new file mode 100644 index 0000000000..8b9db2b81e --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/common/index.html @@ -0,0 +1,3 @@ +nimbus_fml::backends::kotlin::gen_structs::common - Rust

Modules

Functions

  • Get the idiomatic Kotlin rendering of a class name (for enums, records, errors, etc).
  • Get the idiomatic Kotlin rendering of an individual enum variant.
  • Surrounds a string with quotes. +In Kotlin, you can “”“triple quote”“” multi-line strings +so you don’t have to escape “ and \n characters.
  • Get the idiomatic Kotlin rendering of a variable name.
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/common/sidebar-items.js b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/common/sidebar-items.js new file mode 100644 index 0000000000..91cb527f53 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/common/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["class_name","enum_variant_name","quoted","var_name"],"mod":["code_type"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/enum_/index.html b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/enum_/index.html new file mode 100644 index 0000000000..64fa160d77 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/enum_/index.html @@ -0,0 +1 @@ +nimbus_fml::backends::kotlin::gen_structs::enum_ - Rust
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/enum_/sidebar-items.js b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/enum_/sidebar-items.js new file mode 100644 index 0000000000..c9e9decf7d --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/enum_/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"struct":["EnumCodeDeclaration","EnumCodeType"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/enum_/struct.EnumCodeDeclaration.html b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/enum_/struct.EnumCodeDeclaration.html new file mode 100644 index 0000000000..c49d3ed7db --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/enum_/struct.EnumCodeDeclaration.html @@ -0,0 +1,23 @@ +EnumCodeDeclaration in nimbus_fml::backends::kotlin::gen_structs::enum_ - Rust
pub(crate) struct EnumCodeDeclaration {
+    inner: EnumDef,
+}

Fields§

§inner: EnumDef

Implementations§

source§

impl EnumCodeDeclaration

source

pub fn new(_fm: &FeatureManifest, inner: &EnumDef) -> Self

source

fn inner(&self) -> EnumDef

Trait Implementations§

source§

impl CodeDeclaration for EnumCodeDeclaration

source§

fn definition_code(&self, _oracle: &dyn CodeOracle) -> Option<String>

Code which represents this member. e.g. the foreign language class definition for +a given Object type.
source§

fn imports(&self, _oracle: &dyn CodeOracle) -> Option<Vec<String>>

A list of imports that are needed if this type is in use. +Classes are imported exactly once.
source§

fn initialization_code(&self, _oracle: &dyn CodeOracle) -> Option<String>

Code (one or more statements) that is run on start-up of the library, +but before the client code has access to it.
source§

impl Display for EnumCodeDeclaration

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Template for EnumCodeDeclaration

source§

fn render_into(&self, writer: &mut impl Write + ?Sized) -> Result<()>

Renders the template to the given writer fmt buffer
source§

const EXTENSION: Option<&'static str> = _

The template’s extension, if provided
source§

const SIZE_HINT: usize = 376usize

Provides a conservative estimate of the expanded length of the rendered template
source§

const MIME_TYPE: &'static str = _

The MIME type (Content-Type) of the data that gets rendered by this Template
§

fn render(&self) -> Result<String, Error>

Helper method which allocates a new String and renders into it
§

fn write_into(&self, writer: &mut impl Write) -> Result<(), Error>

Renders the template to the given writer io buffer

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> DynTemplate for Twhere + T: Template,

§

fn dyn_render(&self) -> Result<String, Error>

Helper method which allocates a new String and renders into it
§

fn dyn_render_into(&self, writer: &mut dyn Write) -> Result<(), Error>

Renders the template to the given writer fmt buffer
§

fn dyn_write_into(&self, writer: &mut dyn Write) -> Result<(), Error>

Renders the template to the given writer io buffer
§

fn extension(&self) -> Option<&'static str>

Helper function to inspect the template’s extension
§

fn size_hint(&self) -> usize

Provides a conservative estimate of the expanded length of the rendered template
§

fn mime_type(&self) -> &'static str

The MIME type (Content-Type) of the data that gets rendered by this Template
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/enum_/struct.EnumCodeType.html b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/enum_/struct.EnumCodeType.html new file mode 100644 index 0000000000..093dbeeabe --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/enum_/struct.EnumCodeType.html @@ -0,0 +1,64 @@ +EnumCodeType in nimbus_fml::backends::kotlin::gen_structs::enum_ - Rust
pub(crate) struct EnumCodeType {
+    id: String,
+}

Fields§

§id: String

Implementations§

source§

impl EnumCodeType

source

pub(crate) fn new(id: String) -> Self

Trait Implementations§

source§

impl CodeType for EnumCodeType

source§

fn type_label(&self, _oracle: &dyn CodeOracle) -> String

The language specific label used to reference this type. This will be used in +method signatures and property declarations.

+
source§

fn variables_type(&self, _oracle: &dyn CodeOracle) -> VariablesType

The name of the type as it’s represented in the Variables object. +The string return may be used to combine with an indentifier, e.g. a Variables method name.

+
source§

fn create_transform(&self, oracle: &dyn CodeOracle) -> Option<String>

A function handle that is capable of turning the variables type to the TypeRef type.

+
source§

fn literal( + &self, + oracle: &dyn CodeOracle, + _ctx: &dyn Display, + _renderer: &dyn LiteralRenderer, + literal: &Value +) -> String

A representation of the given literal for this type. +N.B. Literal is aliased from serde_json::Value.

+
source§

fn property_getter( + &self, + oracle: &dyn CodeOracle, + vars: &dyn Display, + prop: &dyn Display, + default: &dyn Display +) -> String

The language specific expression that gets a value of the prop from the vars object, +and fallbacks to the default value. Read more
source§

fn value_getter( + &self, + oracle: &dyn CodeOracle, + vars: &dyn Display, + prop: &dyn Display +) -> String

The expression needed to get a value out of a Variables objectm with the prop key. Read more
source§

fn value_mapper(&self, oracle: &dyn CodeOracle) -> Option<String>

The method call here will use the create_transform to transform the value coming out of +the Variables object into the desired type. Read more
source§

fn as_json_transform( + &self, + _oracle: &dyn CodeOracle, + prop: &dyn Display +) -> Option<String>

Implement these in different code types, and call recursively from different code types.
source§

fn value_merger( + &self, + _oracle: &dyn CodeOracle, + _default: &dyn Display +) -> Option<String>

The method call to merge the value with the defaults. Read more
source§

fn merge_transform(&self, _oracle: &dyn CodeOracle) -> Option<String>

A function handle that is capable of merging two instances of the same class. By default, this is None.
source§

fn defaults_type(&self, oracle: &dyn CodeOracle) -> String

source§

fn defaults_mapper( + &self, + _oracle: &dyn CodeOracle, + _value: &dyn Display, + _vars: &dyn Display +) -> Option<String>

source§

fn preference_getter( + &self, + _oracle: &dyn CodeOracle, + _prefs: &dyn Display, + _pref_key: &dyn Display +) -> Option<String>

source§

fn as_json(&self, oracle: &dyn CodeOracle, prop: &dyn Display) -> String

Call from the template
source§

fn is_resource_id(&self, _literal: &Value) -> bool

source§

fn helper_code(&self, _oracle: &dyn CodeOracle) -> Option<String>

Optional helper code to make this type work. +This might include functions to patch a default value with another.
source§

fn imports(&self, _oracle: &dyn CodeOracle) -> Option<Vec<String>>

A list of imports that are needed if this type is in use. +Classes are imported exactly once.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/feature/index.html b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/feature/index.html new file mode 100644 index 0000000000..c0eeeb72e7 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/feature/index.html @@ -0,0 +1 @@ +nimbus_fml::backends::kotlin::gen_structs::feature - Rust
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/feature/sidebar-items.js b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/feature/sidebar-items.js new file mode 100644 index 0000000000..d6b3a42b92 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/feature/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"struct":["FeatureCodeDeclaration"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/feature/struct.FeatureCodeDeclaration.html b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/feature/struct.FeatureCodeDeclaration.html new file mode 100644 index 0000000000..ab1b2ade77 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/feature/struct.FeatureCodeDeclaration.html @@ -0,0 +1,30 @@ +FeatureCodeDeclaration in nimbus_fml::backends::kotlin::gen_structs::feature - Rust
pub(crate) struct FeatureCodeDeclaration {
+    inner: FeatureDef,
+    fm: FeatureManifest,
+}

Fields§

§inner: FeatureDef§fm: FeatureManifest

Implementations§

Trait Implementations§

source§

impl CodeDeclaration for FeatureCodeDeclaration

source§

fn definition_code(&self, _oracle: &dyn CodeOracle) -> Option<String>

Code which represents this member. e.g. the foreign language class definition for +a given Object type.
source§

fn imports(&self, _oracle: &dyn CodeOracle) -> Option<Vec<String>>

A list of imports that are needed if this type is in use. +Classes are imported exactly once.
source§

fn initialization_code(&self, _oracle: &dyn CodeOracle) -> Option<String>

Code (one or more statements) that is run on start-up of the library, +but before the client code has access to it.
source§

impl Display for FeatureCodeDeclaration

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl LiteralRenderer for FeatureCodeDeclaration

source§

fn literal( + &self, + oracle: &dyn CodeOracle, + typ: &TypeRef, + value: &Value, + ctx: &dyn Display +) -> String

source§

impl Template for FeatureCodeDeclaration

source§

fn render_into(&self, writer: &mut impl Write + ?Sized) -> Result<()>

Renders the template to the given writer fmt buffer
source§

const EXTENSION: Option<&'static str> = _

The template’s extension, if provided
source§

const SIZE_HINT: usize = 1_342usize

Provides a conservative estimate of the expanded length of the rendered template
source§

const MIME_TYPE: &'static str = _

The MIME type (Content-Type) of the data that gets rendered by this Template
§

fn render(&self) -> Result<String, Error>

Helper method which allocates a new String and renders into it
§

fn write_into(&self, writer: &mut impl Write) -> Result<(), Error>

Renders the template to the given writer io buffer

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> DynTemplate for Twhere + T: Template,

§

fn dyn_render(&self) -> Result<String, Error>

Helper method which allocates a new String and renders into it
§

fn dyn_render_into(&self, writer: &mut dyn Write) -> Result<(), Error>

Renders the template to the given writer fmt buffer
§

fn dyn_write_into(&self, writer: &mut dyn Write) -> Result<(), Error>

Renders the template to the given writer io buffer
§

fn extension(&self) -> Option<&'static str>

Helper function to inspect the template’s extension
§

fn size_hint(&self) -> usize

Provides a conservative estimate of the expanded length of the rendered template
§

fn mime_type(&self) -> &'static str

The MIME type (Content-Type) of the data that gets rendered by this Template
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/filters/fn.class_name.html b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/filters/fn.class_name.html new file mode 100644 index 0000000000..904f6c1b54 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/filters/fn.class_name.html @@ -0,0 +1,2 @@ +class_name in nimbus_fml::backends::kotlin::gen_structs::filters - Rust
pub fn class_name(nm: impl Display) -> Result<String, Error>
Expand description

Get the idiomatic Kotlin rendering of a class name (for enums, records, errors, etc).

+
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/filters/fn.comment.html b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/filters/fn.comment.html new file mode 100644 index 0000000000..35e90464cf --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/filters/fn.comment.html @@ -0,0 +1 @@ +comment in nimbus_fml::backends::kotlin::gen_structs::filters - Rust
pub fn comment(txt: impl Display, spaces: &str) -> Result<String, Error>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/filters/fn.defaults_type_label.html b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/filters/fn.defaults_type_label.html new file mode 100644 index 0000000000..6601013312 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/filters/fn.defaults_type_label.html @@ -0,0 +1 @@ +defaults_type_label in nimbus_fml::backends::kotlin::gen_structs::filters - Rust
pub fn defaults_type_label(type_: impl Borrow<TypeRef>) -> Result<String, Error>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/filters/fn.enum_variant_name.html b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/filters/fn.enum_variant_name.html new file mode 100644 index 0000000000..e379a6b9e4 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/filters/fn.enum_variant_name.html @@ -0,0 +1,2 @@ +enum_variant_name in nimbus_fml::backends::kotlin::gen_structs::filters - Rust
pub fn enum_variant_name(nm: impl Display) -> Result<String, Error>
Expand description

Get the idiomatic Kotlin rendering of an individual enum variant.

+
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/filters/fn.literal.html b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/filters/fn.literal.html new file mode 100644 index 0000000000..3ba083ca4a --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/filters/fn.literal.html @@ -0,0 +1,6 @@ +literal in nimbus_fml::backends::kotlin::gen_structs::filters - Rust
pub fn literal(
+    type_: impl Borrow<TypeRef>,
+    renderer: impl LiteralRenderer,
+    literal: impl Borrow<Value>,
+    ctx: impl Display
+) -> Result<String, Error>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/filters/fn.preference_getter.html b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/filters/fn.preference_getter.html new file mode 100644 index 0000000000..ee02812e58 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/filters/fn.preference_getter.html @@ -0,0 +1,5 @@ +preference_getter in nimbus_fml::backends::kotlin::gen_structs::filters - Rust
pub fn preference_getter(
+    type_: impl Borrow<TypeRef>,
+    prefs: impl Display,
+    pref_key: impl Display
+) -> Result<String, Error>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/filters/fn.property.html b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/filters/fn.property.html new file mode 100644 index 0000000000..9e475eef97 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/filters/fn.property.html @@ -0,0 +1,6 @@ +property in nimbus_fml::backends::kotlin::gen_structs::filters - Rust
pub fn property(
+    type_: impl Borrow<TypeRef>,
+    prop: impl Display,
+    vars: impl Display,
+    default: impl Display
+) -> Result<String, Error>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/filters/fn.quoted.html b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/filters/fn.quoted.html new file mode 100644 index 0000000000..7fff4414c6 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/filters/fn.quoted.html @@ -0,0 +1 @@ +quoted in nimbus_fml::backends::kotlin::gen_structs::filters - Rust
pub fn quoted(txt: impl Display) -> Result<String, Error>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/filters/fn.to_json.html b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/filters/fn.to_json.html new file mode 100644 index 0000000000..3165d36719 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/filters/fn.to_json.html @@ -0,0 +1,4 @@ +to_json in nimbus_fml::backends::kotlin::gen_structs::filters - Rust
pub fn to_json(
+    prop: impl Display,
+    type_: impl Borrow<TypeRef>
+) -> Result<String, Error>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/filters/fn.type_label.html b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/filters/fn.type_label.html new file mode 100644 index 0000000000..731e61a022 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/filters/fn.type_label.html @@ -0,0 +1 @@ +type_label in nimbus_fml::backends::kotlin::gen_structs::filters - Rust
pub fn type_label(type_: impl Borrow<TypeRef>) -> Result<String, Error>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/filters/fn.var_name.html b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/filters/fn.var_name.html new file mode 100644 index 0000000000..0806c6d71f --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/filters/fn.var_name.html @@ -0,0 +1,2 @@ +var_name in nimbus_fml::backends::kotlin::gen_structs::filters - Rust
pub fn var_name(nm: impl Display) -> Result<String, Error>
Expand description

Get the idiomatic Kotlin rendering of a variable name.

+
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/filters/index.html b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/filters/index.html new file mode 100644 index 0000000000..b435393518 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/filters/index.html @@ -0,0 +1 @@ +nimbus_fml::backends::kotlin::gen_structs::filters - Rust

Functions

\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/filters/sidebar-items.js b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/filters/sidebar-items.js new file mode 100644 index 0000000000..7f2213ce8c --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/filters/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["class_name","comment","defaults_type_label","enum_variant_name","literal","preference_getter","property","quoted","to_json","type_label","var_name"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/imports/index.html b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/imports/index.html new file mode 100644 index 0000000000..619bd4ab8e --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/imports/index.html @@ -0,0 +1 @@ +nimbus_fml::backends::kotlin::gen_structs::imports - Rust
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/imports/sidebar-items.js b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/imports/sidebar-items.js new file mode 100644 index 0000000000..6a738da76e --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/imports/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"struct":["ImportedModuleInitialization"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/imports/struct.ImportedModuleInitialization.html b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/imports/struct.ImportedModuleInitialization.html new file mode 100644 index 0000000000..48f2841fe4 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/imports/struct.ImportedModuleInitialization.html @@ -0,0 +1,29 @@ +ImportedModuleInitialization in nimbus_fml::backends::kotlin::gen_structs::imports - Rust
pub(crate) struct ImportedModuleInitialization<'a> {
+    pub(crate) inner: ImportedModule<'a>,
+}

Fields§

§inner: ImportedModule<'a>

Implementations§

source§

impl<'a> ImportedModuleInitialization<'a>

source

pub(crate) fn new(inner: ImportedModule<'a>) -> Self

Trait Implementations§

source§

impl CodeDeclaration for ImportedModuleInitialization<'_>

source§

fn imports(&self, oracle: &dyn CodeOracle) -> Option<Vec<String>>

A list of imports that are needed if this type is in use. +Classes are imported exactly once.
source§

fn initialization_code(&self, _oracle: &dyn CodeOracle) -> Option<String>

Code (one or more statements) that is run on start-up of the library, +but before the client code has access to it.
source§

fn definition_code(&self, _oracle: &dyn CodeOracle) -> Option<String>

Code which represents this member. e.g. the foreign language class definition for +a given Object type.
source§

impl<'a> Display for ImportedModuleInitialization<'a>

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl LiteralRenderer for ImportedModuleInitialization<'_>

source§

fn literal( + &self, + oracle: &dyn CodeOracle, + typ: &TypeRef, + value: &Value, + ctx: &dyn Display +) -> String

source§

impl<'a> Template for ImportedModuleInitialization<'a>

source§

fn render_into(&self, writer: &mut impl Write + ?Sized) -> Result<()>

Renders the template to the given writer fmt buffer
source§

const EXTENSION: Option<&'static str> = _

The template’s extension, if provided
source§

const SIZE_HINT: usize = 402usize

Provides a conservative estimate of the expanded length of the rendered template
source§

const MIME_TYPE: &'static str = _

The MIME type (Content-Type) of the data that gets rendered by this Template
§

fn render(&self) -> Result<String, Error>

Helper method which allocates a new String and renders into it
§

fn write_into(&self, writer: &mut impl Write) -> Result<(), Error>

Renders the template to the given writer io buffer

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> DynTemplate for Twhere + T: Template,

§

fn dyn_render(&self) -> Result<String, Error>

Helper method which allocates a new String and renders into it
§

fn dyn_render_into(&self, writer: &mut dyn Write) -> Result<(), Error>

Renders the template to the given writer fmt buffer
§

fn dyn_write_into(&self, writer: &mut dyn Write) -> Result<(), Error>

Renders the template to the given writer io buffer
§

fn extension(&self) -> Option<&'static str>

Helper function to inspect the template’s extension
§

fn size_hint(&self) -> usize

Provides a conservative estimate of the expanded length of the rendered template
§

fn mime_type(&self) -> &'static str

The MIME type (Content-Type) of the data that gets rendered by this Template
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/index.html b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/index.html new file mode 100644 index 0000000000..252ee63ca2 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/index.html @@ -0,0 +1 @@ +nimbus_fml::backends::kotlin::gen_structs - Rust
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/object/fn.object_literal.html b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/object/fn.object_literal.html new file mode 100644 index 0000000000..1e112ac222 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/object/fn.object_literal.html @@ -0,0 +1,8 @@ +object_literal in nimbus_fml::backends::kotlin::gen_structs::object - Rust
pub(crate) fn object_literal(
+    fm: &FeatureManifest,
+    ctx: &dyn Display,
+    renderer: &dyn LiteralRenderer,
+    oracle: &dyn CodeOracle,
+    typ: &TypeRef,
+    value: &Value
+) -> String
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/object/index.html b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/object/index.html new file mode 100644 index 0000000000..2c9399e5c2 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/object/index.html @@ -0,0 +1 @@ +nimbus_fml::backends::kotlin::gen_structs::object - Rust
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/object/sidebar-items.js b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/object/sidebar-items.js new file mode 100644 index 0000000000..ff87dd8d26 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/object/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["object_literal"],"struct":["ObjectCodeDeclaration","ObjectCodeType","ObjectRuntime"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/object/struct.ObjectCodeDeclaration.html b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/object/struct.ObjectCodeDeclaration.html new file mode 100644 index 0000000000..4f82e71f41 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/object/struct.ObjectCodeDeclaration.html @@ -0,0 +1,30 @@ +ObjectCodeDeclaration in nimbus_fml::backends::kotlin::gen_structs::object - Rust
pub(crate) struct ObjectCodeDeclaration {
+    inner: ObjectDef,
+    fm: FeatureManifest,
+}

Fields§

§inner: ObjectDef§fm: FeatureManifest

Implementations§

source§

impl ObjectCodeDeclaration

source

pub fn new(fm: &FeatureManifest, inner: &ObjectDef) -> Self

source

pub fn inner(&self) -> ObjectDef

Trait Implementations§

source§

impl CodeDeclaration for ObjectCodeDeclaration

source§

fn definition_code(&self, _oracle: &dyn CodeOracle) -> Option<String>

Code which represents this member. e.g. the foreign language class definition for +a given Object type.
source§

fn imports(&self, _oracle: &dyn CodeOracle) -> Option<Vec<String>>

A list of imports that are needed if this type is in use. +Classes are imported exactly once.
source§

fn initialization_code(&self, _oracle: &dyn CodeOracle) -> Option<String>

Code (one or more statements) that is run on start-up of the library, +but before the client code has access to it.
source§

impl Display for ObjectCodeDeclaration

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl LiteralRenderer for ObjectCodeDeclaration

source§

fn literal( + &self, + oracle: &dyn CodeOracle, + typ: &TypeRef, + value: &Value, + ctx: &dyn Display +) -> String

source§

impl Template for ObjectCodeDeclaration

source§

fn render_into(&self, writer: &mut impl Write + ?Sized) -> Result<()>

Renders the template to the given writer fmt buffer
source§

const EXTENSION: Option<&'static str> = _

The template’s extension, if provided
source§

const SIZE_HINT: usize = 1_630usize

Provides a conservative estimate of the expanded length of the rendered template
source§

const MIME_TYPE: &'static str = _

The MIME type (Content-Type) of the data that gets rendered by this Template
§

fn render(&self) -> Result<String, Error>

Helper method which allocates a new String and renders into it
§

fn write_into(&self, writer: &mut impl Write) -> Result<(), Error>

Renders the template to the given writer io buffer

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> DynTemplate for Twhere + T: Template,

§

fn dyn_render(&self) -> Result<String, Error>

Helper method which allocates a new String and renders into it
§

fn dyn_render_into(&self, writer: &mut dyn Write) -> Result<(), Error>

Renders the template to the given writer fmt buffer
§

fn dyn_write_into(&self, writer: &mut dyn Write) -> Result<(), Error>

Renders the template to the given writer io buffer
§

fn extension(&self) -> Option<&'static str>

Helper function to inspect the template’s extension
§

fn size_hint(&self) -> usize

Provides a conservative estimate of the expanded length of the rendered template
§

fn mime_type(&self) -> &'static str

The MIME type (Content-Type) of the data that gets rendered by this Template
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/object/struct.ObjectCodeType.html b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/object/struct.ObjectCodeType.html new file mode 100644 index 0000000000..101ecc7884 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/object/struct.ObjectCodeType.html @@ -0,0 +1,62 @@ +ObjectCodeType in nimbus_fml::backends::kotlin::gen_structs::object - Rust
pub struct ObjectCodeType {
+    id: String,
+}

Fields§

§id: String

Implementations§

source§

impl ObjectCodeType

source

pub fn new(id: String) -> Self

Trait Implementations§

source§

impl CodeType for ObjectCodeType

source§

fn variables_type(&self, _oracle: &dyn CodeOracle) -> VariablesType

The language specific expression that gets a value of the prop from the vars object. +The name of the type as it’s represented in the Variables object. +The string return may be used to combine with an indentifier, e.g. a Variables method name.

+
source§

fn type_label(&self, _oracle: &dyn CodeOracle) -> String

The language specific label used to reference this type. This will be used in +method signatures and property declarations.
source§

fn property_getter( + &self, + oracle: &dyn CodeOracle, + vars: &dyn Display, + prop: &dyn Display, + default: &dyn Display +) -> String

The language specific expression that gets a value of the prop from the vars object, +and fallbacks to the default value. Read more
source§

fn value_getter( + &self, + oracle: &dyn CodeOracle, + vars: &dyn Display, + prop: &dyn Display +) -> String

The expression needed to get a value out of a Variables objectm with the prop key. Read more
source§

fn value_mapper(&self, oracle: &dyn CodeOracle) -> Option<String>

The method call here will use the create_transform to transform the value coming out of +the Variables object into the desired type. Read more
source§

fn create_transform(&self, oracle: &dyn CodeOracle) -> Option<String>

A function handle that is capable of turning the variables type to the TypeRef type.
source§

fn merge_transform(&self, oracle: &dyn CodeOracle) -> Option<String>

A function handle that is capable of merging two instances of the same class. By default, this is None.
source§

fn value_merger( + &self, + _oracle: &dyn CodeOracle, + default: &dyn Display +) -> Option<String>

The method call to merge the value with the defaults. Read more
source§

fn as_json_transform( + &self, + _oracle: &dyn CodeOracle, + prop: &dyn Display +) -> Option<String>

Implement these in different code types, and call recursively from different code types.
source§

fn literal( + &self, + oracle: &dyn CodeOracle, + ctx: &dyn Display, + renderer: &dyn LiteralRenderer, + literal: &Value +) -> String

A representation of the given literal for this type. +N.B. Literal is aliased from serde_json::Value.
source§

fn defaults_type(&self, oracle: &dyn CodeOracle) -> String

source§

fn defaults_mapper( + &self, + _oracle: &dyn CodeOracle, + _value: &dyn Display, + _vars: &dyn Display +) -> Option<String>

source§

fn preference_getter( + &self, + _oracle: &dyn CodeOracle, + _prefs: &dyn Display, + _pref_key: &dyn Display +) -> Option<String>

source§

fn as_json(&self, oracle: &dyn CodeOracle, prop: &dyn Display) -> String

Call from the template
source§

fn is_resource_id(&self, _literal: &Value) -> bool

source§

fn helper_code(&self, _oracle: &dyn CodeOracle) -> Option<String>

Optional helper code to make this type work. +This might include functions to patch a default value with another.
source§

fn imports(&self, _oracle: &dyn CodeOracle) -> Option<Vec<String>>

A list of imports that are needed if this type is in use. +Classes are imported exactly once.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/object/struct.ObjectRuntime.html b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/object/struct.ObjectRuntime.html new file mode 100644 index 0000000000..c6ec28351f --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/object/struct.ObjectRuntime.html @@ -0,0 +1,19 @@ +ObjectRuntime in nimbus_fml::backends::kotlin::gen_structs::object - Rust
pub struct ObjectRuntime;

Trait Implementations§

source§

impl CodeDeclaration for ObjectRuntime

source§

fn imports(&self, _oracle: &dyn CodeOracle) -> Option<Vec<String>>

A list of imports that are needed if this type is in use. +Classes are imported exactly once.
source§

fn initialization_code(&self, _oracle: &dyn CodeOracle) -> Option<String>

Code (one or more statements) that is run on start-up of the library, +but before the client code has access to it.
source§

fn definition_code(&self, _oracle: &dyn CodeOracle) -> Option<String>

Code which represents this member. e.g. the foreign language class definition for +a given Object type.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/primitives/index.html b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/primitives/index.html new file mode 100644 index 0000000000..2197729e5a --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/primitives/index.html @@ -0,0 +1 @@ +nimbus_fml::backends::kotlin::gen_structs::primitives - Rust
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/primitives/sidebar-items.js b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/primitives/sidebar-items.js new file mode 100644 index 0000000000..f28636616e --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/primitives/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"struct":["BooleanCodeType","IntCodeType","StringCodeType"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/primitives/struct.BooleanCodeType.html b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/primitives/struct.BooleanCodeType.html new file mode 100644 index 0000000000..1c82c00e76 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/primitives/struct.BooleanCodeType.html @@ -0,0 +1,61 @@ +BooleanCodeType in nimbus_fml::backends::kotlin::gen_structs::primitives - Rust
pub(crate) struct BooleanCodeType;

Trait Implementations§

source§

impl CodeType for BooleanCodeType

source§

fn type_label(&self, _oracle: &dyn CodeOracle) -> String

The language specific label used to reference this type. This will be used in +method signatures and property declarations.

+
source§

fn variables_type(&self, _oracle: &dyn CodeOracle) -> VariablesType

The name of the type as it’s represented in the Variables object. +The string return may be used to combine with an indentifier, e.g. a Variables method name.

+
source§

fn literal( + &self, + _oracle: &dyn CodeOracle, + _ctx: &dyn Display, + _renderer: &dyn LiteralRenderer, + literal: &Value +) -> String

A representation of the given literal for this type. +N.B. Literal is aliased from serde_json::Value.

+
source§

fn property_getter( + &self, + oracle: &dyn CodeOracle, + vars: &dyn Display, + prop: &dyn Display, + default: &dyn Display +) -> String

The language specific expression that gets a value of the prop from the vars object, +and fallbacks to the default value. Read more
source§

fn value_getter( + &self, + oracle: &dyn CodeOracle, + vars: &dyn Display, + prop: &dyn Display +) -> String

The expression needed to get a value out of a Variables objectm with the prop key. Read more
source§

fn value_mapper(&self, oracle: &dyn CodeOracle) -> Option<String>

The method call here will use the create_transform to transform the value coming out of +the Variables object into the desired type. Read more
source§

fn preference_getter( + &self, + _oracle: &dyn CodeOracle, + prefs: &dyn Display, + pref_key: &dyn Display +) -> Option<String>

source§

fn value_merger( + &self, + _oracle: &dyn CodeOracle, + _default: &dyn Display +) -> Option<String>

The method call to merge the value with the defaults. Read more
source§

fn create_transform(&self, _oracle: &dyn CodeOracle) -> Option<String>

A function handle that is capable of turning the variables type to the TypeRef type.
source§

fn merge_transform(&self, _oracle: &dyn CodeOracle) -> Option<String>

A function handle that is capable of merging two instances of the same class. By default, this is None.
source§

fn defaults_type(&self, oracle: &dyn CodeOracle) -> String

source§

fn defaults_mapper( + &self, + _oracle: &dyn CodeOracle, + _value: &dyn Display, + _vars: &dyn Display +) -> Option<String>

source§

fn as_json(&self, oracle: &dyn CodeOracle, prop: &dyn Display) -> String

Call from the template
source§

fn as_json_transform( + &self, + _oracle: &dyn CodeOracle, + _prop: &dyn Display +) -> Option<String>

Implement these in different code types, and call recursively from different code types.
source§

fn is_resource_id(&self, _literal: &Value) -> bool

source§

fn helper_code(&self, _oracle: &dyn CodeOracle) -> Option<String>

Optional helper code to make this type work. +This might include functions to patch a default value with another.
source§

fn imports(&self, _oracle: &dyn CodeOracle) -> Option<Vec<String>>

A list of imports that are needed if this type is in use. +Classes are imported exactly once.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/primitives/struct.IntCodeType.html b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/primitives/struct.IntCodeType.html new file mode 100644 index 0000000000..003812761d --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/primitives/struct.IntCodeType.html @@ -0,0 +1,61 @@ +IntCodeType in nimbus_fml::backends::kotlin::gen_structs::primitives - Rust
pub(crate) struct IntCodeType;

Trait Implementations§

source§

impl CodeType for IntCodeType

source§

fn type_label(&self, _oracle: &dyn CodeOracle) -> String

The language specific label used to reference this type. This will be used in +method signatures and property declarations.

+
source§

fn variables_type(&self, _oracle: &dyn CodeOracle) -> VariablesType

The name of the type as it’s represented in the Variables object. +The string return may be used to combine with an indentifier, e.g. a Variables method name.

+
source§

fn literal( + &self, + _oracle: &dyn CodeOracle, + _ctx: &dyn Display, + _renderer: &dyn LiteralRenderer, + literal: &Value +) -> String

A representation of the given literal for this type. +N.B. Literal is aliased from serde_json::Value.

+
source§

fn property_getter( + &self, + oracle: &dyn CodeOracle, + vars: &dyn Display, + prop: &dyn Display, + default: &dyn Display +) -> String

The language specific expression that gets a value of the prop from the vars object, +and fallbacks to the default value. Read more
source§

fn value_getter( + &self, + oracle: &dyn CodeOracle, + vars: &dyn Display, + prop: &dyn Display +) -> String

The expression needed to get a value out of a Variables objectm with the prop key. Read more
source§

fn value_mapper(&self, oracle: &dyn CodeOracle) -> Option<String>

The method call here will use the create_transform to transform the value coming out of +the Variables object into the desired type. Read more
source§

fn preference_getter( + &self, + _oracle: &dyn CodeOracle, + prefs: &dyn Display, + pref_key: &dyn Display +) -> Option<String>

source§

fn value_merger( + &self, + _oracle: &dyn CodeOracle, + _default: &dyn Display +) -> Option<String>

The method call to merge the value with the defaults. Read more
source§

fn create_transform(&self, _oracle: &dyn CodeOracle) -> Option<String>

A function handle that is capable of turning the variables type to the TypeRef type.
source§

fn merge_transform(&self, _oracle: &dyn CodeOracle) -> Option<String>

A function handle that is capable of merging two instances of the same class. By default, this is None.
source§

fn defaults_type(&self, oracle: &dyn CodeOracle) -> String

source§

fn defaults_mapper( + &self, + _oracle: &dyn CodeOracle, + _value: &dyn Display, + _vars: &dyn Display +) -> Option<String>

source§

fn as_json(&self, oracle: &dyn CodeOracle, prop: &dyn Display) -> String

Call from the template
source§

fn as_json_transform( + &self, + _oracle: &dyn CodeOracle, + _prop: &dyn Display +) -> Option<String>

Implement these in different code types, and call recursively from different code types.
source§

fn is_resource_id(&self, _literal: &Value) -> bool

source§

fn helper_code(&self, _oracle: &dyn CodeOracle) -> Option<String>

Optional helper code to make this type work. +This might include functions to patch a default value with another.
source§

fn imports(&self, _oracle: &dyn CodeOracle) -> Option<Vec<String>>

A list of imports that are needed if this type is in use. +Classes are imported exactly once.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/primitives/struct.StringCodeType.html b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/primitives/struct.StringCodeType.html new file mode 100644 index 0000000000..b2665d0241 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/primitives/struct.StringCodeType.html @@ -0,0 +1,61 @@ +StringCodeType in nimbus_fml::backends::kotlin::gen_structs::primitives - Rust
pub(crate) struct StringCodeType;

Trait Implementations§

source§

impl CodeType for StringCodeType

source§

fn type_label(&self, _oracle: &dyn CodeOracle) -> String

The language specific label used to reference this type. This will be used in +method signatures and property declarations.

+
source§

fn variables_type(&self, _oracle: &dyn CodeOracle) -> VariablesType

The name of the type as it’s represented in the Variables object. +The string return may be used to combine with an indentifier, e.g. a Variables method name.

+
source§

fn literal( + &self, + _oracle: &dyn CodeOracle, + _ctx: &dyn Display, + _renderer: &dyn LiteralRenderer, + literal: &Value +) -> String

A representation of the given literal for this type. +N.B. Literal is aliased from serde_json::Value.

+
source§

fn property_getter( + &self, + oracle: &dyn CodeOracle, + vars: &dyn Display, + prop: &dyn Display, + default: &dyn Display +) -> String

The language specific expression that gets a value of the prop from the vars object, +and fallbacks to the default value. Read more
source§

fn value_getter( + &self, + oracle: &dyn CodeOracle, + vars: &dyn Display, + prop: &dyn Display +) -> String

The expression needed to get a value out of a Variables objectm with the prop key. Read more
source§

fn value_mapper(&self, oracle: &dyn CodeOracle) -> Option<String>

The method call here will use the create_transform to transform the value coming out of +the Variables object into the desired type. Read more
source§

fn preference_getter( + &self, + _oracle: &dyn CodeOracle, + prefs: &dyn Display, + pref_key: &dyn Display +) -> Option<String>

source§

fn value_merger( + &self, + _oracle: &dyn CodeOracle, + _default: &dyn Display +) -> Option<String>

The method call to merge the value with the defaults. Read more
source§

fn create_transform(&self, _oracle: &dyn CodeOracle) -> Option<String>

A function handle that is capable of turning the variables type to the TypeRef type.
source§

fn merge_transform(&self, _oracle: &dyn CodeOracle) -> Option<String>

A function handle that is capable of merging two instances of the same class. By default, this is None.
source§

fn defaults_type(&self, oracle: &dyn CodeOracle) -> String

source§

fn defaults_mapper( + &self, + _oracle: &dyn CodeOracle, + _value: &dyn Display, + _vars: &dyn Display +) -> Option<String>

source§

fn as_json(&self, oracle: &dyn CodeOracle, prop: &dyn Display) -> String

Call from the template
source§

fn as_json_transform( + &self, + _oracle: &dyn CodeOracle, + _prop: &dyn Display +) -> Option<String>

Implement these in different code types, and call recursively from different code types.
source§

fn is_resource_id(&self, _literal: &Value) -> bool

source§

fn helper_code(&self, _oracle: &dyn CodeOracle) -> Option<String>

Optional helper code to make this type work. +This might include functions to patch a default value with another.
source§

fn imports(&self, _oracle: &dyn CodeOracle) -> Option<Vec<String>>

A list of imports that are needed if this type is in use. +Classes are imported exactly once.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/sidebar-items.js b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/sidebar-items.js new file mode 100644 index 0000000000..cd6e094190 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"mod":["bundled","common","enum_","feature","filters","imports","object","primitives","structural"],"struct":["ConcreteCodeOracle","FeatureManifestDeclaration"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/struct.ConcreteCodeOracle.html b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/struct.ConcreteCodeOracle.html new file mode 100644 index 0000000000..c63c72fb09 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/struct.ConcreteCodeOracle.html @@ -0,0 +1,17 @@ +ConcreteCodeOracle in nimbus_fml::backends::kotlin::gen_structs - Rust
pub struct ConcreteCodeOracle;

Implementations§

Trait Implementations§

source§

impl Clone for ConcreteCodeOracle

source§

fn clone(&self) -> ConcreteCodeOracle

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl CodeOracle for ConcreteCodeOracle

source§

fn find(&self, type_: &TypeRef) -> Box<dyn CodeType>

source§

impl Default for ConcreteCodeOracle

source§

fn default() -> ConcreteCodeOracle

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/struct.FeatureManifestDeclaration.html b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/struct.FeatureManifestDeclaration.html new file mode 100644 index 0000000000..ef4eb4d9f0 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/struct.FeatureManifestDeclaration.html @@ -0,0 +1,21 @@ +FeatureManifestDeclaration in nimbus_fml::backends::kotlin::gen_structs - Rust
pub struct FeatureManifestDeclaration<'a> {
+    fm: &'a FeatureManifest,
+    oracle: ConcreteCodeOracle,
+}

Fields§

§fm: &'a FeatureManifest§oracle: ConcreteCodeOracle

Implementations§

source§

impl<'a> FeatureManifestDeclaration<'a>

source

pub fn new(fm: &'a FeatureManifest) -> Self

source

pub fn members(&self) -> Vec<Box<dyn CodeDeclaration + 'a>>

source

pub fn feature_properties(&self) -> Vec<PropDef>

source

pub fn iter_feature_defs(&self) -> Vec<&FeatureDef>

source

pub fn initialization_code(&self) -> Vec<String>

source

pub fn declaration_code(&self) -> Vec<String>

source

pub fn imports(&self) -> Vec<String>

Trait Implementations§

source§

impl<'a> Display for FeatureManifestDeclaration<'a>

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl<'a> Template for FeatureManifestDeclaration<'a>

source§

fn render_into(&self, writer: &mut impl Write + ?Sized) -> Result<()>

Renders the template to the given writer fmt buffer
source§

const EXTENSION: Option<&'static str> = _

The template’s extension, if provided
source§

const SIZE_HINT: usize = 3_815usize

Provides a conservative estimate of the expanded length of the rendered template
source§

const MIME_TYPE: &'static str = _

The MIME type (Content-Type) of the data that gets rendered by this Template
§

fn render(&self) -> Result<String, Error>

Helper method which allocates a new String and renders into it
§

fn write_into(&self, writer: &mut impl Write) -> Result<(), Error>

Renders the template to the given writer io buffer

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> DynTemplate for Twhere + T: Template,

§

fn dyn_render(&self) -> Result<String, Error>

Helper method which allocates a new String and renders into it
§

fn dyn_render_into(&self, writer: &mut dyn Write) -> Result<(), Error>

Renders the template to the given writer fmt buffer
§

fn dyn_write_into(&self, writer: &mut dyn Write) -> Result<(), Error>

Renders the template to the given writer io buffer
§

fn extension(&self) -> Option<&'static str>

Helper function to inspect the template’s extension
§

fn size_hint(&self) -> usize

Provides a conservative estimate of the expanded length of the rendered template
§

fn mime_type(&self) -> &'static str

The MIME type (Content-Type) of the data that gets rendered by this Template
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/structural/fn.map_functions.html b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/structural/fn.map_functions.html new file mode 100644 index 0000000000..deb09417bd --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/structural/fn.map_functions.html @@ -0,0 +1 @@ +map_functions in nimbus_fml::backends::kotlin::gen_structs::structural - Rust
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/structural/index.html b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/structural/index.html new file mode 100644 index 0000000000..d83c0b702a --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/structural/index.html @@ -0,0 +1 @@ +nimbus_fml::backends::kotlin::gen_structs::structural - Rust
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/structural/sidebar-items.js b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/structural/sidebar-items.js new file mode 100644 index 0000000000..3a49f63c98 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/structural/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["map_functions"],"struct":["ListCodeType","MapCodeType","OptionalCodeType"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/structural/struct.ListCodeType.html b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/structural/struct.ListCodeType.html new file mode 100644 index 0000000000..0647a1794d --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/structural/struct.ListCodeType.html @@ -0,0 +1,63 @@ +ListCodeType in nimbus_fml::backends::kotlin::gen_structs::structural - Rust
pub(crate) struct ListCodeType {
+    inner: TypeRef,
+}

Fields§

§inner: TypeRef

Implementations§

source§

impl ListCodeType

source

pub(crate) fn new(inner: &TypeRef) -> Self

Trait Implementations§

source§

impl CodeType for ListCodeType

source§

fn type_label(&self, oracle: &dyn CodeOracle) -> String

The language specific label used to reference this type. This will be used in +method signatures and property declarations.

+
source§

fn variables_type(&self, _oracle: &dyn CodeOracle) -> VariablesType

The name of the type as it’s represented in the Variables object. +The string return may be used to combine with an indentifier, e.g. a Variables method name.

+
source§

fn literal( + &self, + oracle: &dyn CodeOracle, + ctx: &dyn Display, + renderer: &dyn LiteralRenderer, + literal: &Value +) -> String

A representation of the given literal for this type. +N.B. Literal is aliased from serde_json::Value.

+
source§

fn property_getter( + &self, + oracle: &dyn CodeOracle, + vars: &dyn Display, + prop: &dyn Display, + default: &dyn Display +) -> String

The language specific expression that gets a value of the prop from the vars object, +and fallbacks to the default value. Read more
source§

fn value_getter( + &self, + oracle: &dyn CodeOracle, + vars: &dyn Display, + prop: &dyn Display +) -> String

The expression needed to get a value out of a Variables objectm with the prop key. Read more
source§

fn value_mapper(&self, oracle: &dyn CodeOracle) -> Option<String>

The method call here will use the create_transform to transform the value coming out of +the Variables object into the desired type. Read more
source§

fn value_merger( + &self, + _oracle: &dyn CodeOracle, + _default: &dyn Display +) -> Option<String>

The method call to merge the value with the defaults. Read more
source§

fn defaults_type(&self, oracle: &dyn CodeOracle) -> String

source§

fn defaults_mapper( + &self, + oracle: &dyn CodeOracle, + value: &dyn Display, + vars: &dyn Display +) -> Option<String>

source§

fn as_json_transform( + &self, + oracle: &dyn CodeOracle, + prop: &dyn Display +) -> Option<String>

Implement these in different code types, and call recursively from different code types.
source§

fn create_transform(&self, _oracle: &dyn CodeOracle) -> Option<String>

A function handle that is capable of turning the variables type to the TypeRef type.
source§

fn merge_transform(&self, _oracle: &dyn CodeOracle) -> Option<String>

A function handle that is capable of merging two instances of the same class. By default, this is None.
source§

fn preference_getter( + &self, + _oracle: &dyn CodeOracle, + _prefs: &dyn Display, + _pref_key: &dyn Display +) -> Option<String>

source§

fn as_json(&self, oracle: &dyn CodeOracle, prop: &dyn Display) -> String

Call from the template
source§

fn is_resource_id(&self, _literal: &Value) -> bool

source§

fn helper_code(&self, _oracle: &dyn CodeOracle) -> Option<String>

Optional helper code to make this type work. +This might include functions to patch a default value with another.
source§

fn imports(&self, _oracle: &dyn CodeOracle) -> Option<Vec<String>>

A list of imports that are needed if this type is in use. +Classes are imported exactly once.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/structural/struct.MapCodeType.html b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/structural/struct.MapCodeType.html new file mode 100644 index 0000000000..97082efd89 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/structural/struct.MapCodeType.html @@ -0,0 +1,64 @@ +MapCodeType in nimbus_fml::backends::kotlin::gen_structs::structural - Rust
pub(crate) struct MapCodeType {
+    k_type: TypeRef,
+    v_type: TypeRef,
+}

Fields§

§k_type: TypeRef§v_type: TypeRef

Implementations§

source§

impl MapCodeType

source

pub(crate) fn new(k: &TypeRef, v: &TypeRef) -> Self

Trait Implementations§

source§

impl CodeType for MapCodeType

source§

fn type_label(&self, oracle: &dyn CodeOracle) -> String

The language specific label used to reference this type. This will be used in +method signatures and property declarations.

+
source§

fn variables_type(&self, _oracle: &dyn CodeOracle) -> VariablesType

The name of the type as it’s represented in the Variables object. +The string return may be used to combine with an indentifier, e.g. a Variables method name.

+
source§

fn literal( + &self, + oracle: &dyn CodeOracle, + ctx: &dyn Display, + renderer: &dyn LiteralRenderer, + literal: &Value +) -> String

A representation of the given literal for this type. +N.B. Literal is aliased from serde_json::Value.

+
source§

fn property_getter( + &self, + oracle: &dyn CodeOracle, + vars: &dyn Display, + prop: &dyn Display, + default: &dyn Display +) -> String

The language specific expression that gets a value of the prop from the vars object, +and fallbacks to the default value. Read more
source§

fn value_getter( + &self, + oracle: &dyn CodeOracle, + vars: &dyn Display, + prop: &dyn Display +) -> String

The expression needed to get a value out of a Variables objectm with the prop key. Read more
source§

fn value_mapper(&self, oracle: &dyn CodeOracle) -> Option<String>

The method call here will use the create_transform to transform the value coming out of +the Variables object into the desired type. Read more
source§

fn value_merger( + &self, + oracle: &dyn CodeOracle, + default: &dyn Display +) -> Option<String>

The method call to merge the value with the defaults. Read more
source§

fn create_transform(&self, oracle: &dyn CodeOracle) -> Option<String>

A function handle that is capable of turning the variables type to the TypeRef type.
source§

fn merge_transform(&self, oracle: &dyn CodeOracle) -> Option<String>

A function handle that is capable of merging two instances of the same class. By default, this is None.
source§

fn defaults_type(&self, oracle: &dyn CodeOracle) -> String

source§

fn defaults_mapper( + &self, + oracle: &dyn CodeOracle, + value: &dyn Display, + vars: &dyn Display +) -> Option<String>

source§

fn as_json_transform( + &self, + oracle: &dyn CodeOracle, + prop: &dyn Display +) -> Option<String>

Implement these in different code types, and call recursively from different code types.
source§

fn imports(&self, oracle: &dyn CodeOracle) -> Option<Vec<String>>

A list of imports that are needed if this type is in use. +Classes are imported exactly once.
source§

fn preference_getter( + &self, + _oracle: &dyn CodeOracle, + _prefs: &dyn Display, + _pref_key: &dyn Display +) -> Option<String>

source§

fn as_json(&self, oracle: &dyn CodeOracle, prop: &dyn Display) -> String

Call from the template
source§

fn is_resource_id(&self, _literal: &Value) -> bool

source§

fn helper_code(&self, _oracle: &dyn CodeOracle) -> Option<String>

Optional helper code to make this type work. +This might include functions to patch a default value with another.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/structural/struct.OptionalCodeType.html b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/structural/struct.OptionalCodeType.html new file mode 100644 index 0000000000..bb670646ad --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/kotlin/gen_structs/structural/struct.OptionalCodeType.html @@ -0,0 +1,70 @@ +OptionalCodeType in nimbus_fml::backends::kotlin::gen_structs::structural - Rust
pub(crate) struct OptionalCodeType {
+    inner: TypeRef,
+}

Fields§

§inner: TypeRef

Implementations§

source§

impl OptionalCodeType

source

pub(crate) fn new(inner: &TypeRef) -> Self

Trait Implementations§

source§

impl CodeType for OptionalCodeType

source§

fn type_label(&self, oracle: &dyn CodeOracle) -> String

The language specific label used to reference this type. This will be used in +method signatures and property declarations.

+
source§

fn property_getter( + &self, + oracle: &dyn CodeOracle, + vars: &dyn Display, + prop: &dyn Display, + default: &dyn Display +) -> String

The language specific expression that gets a value of the prop from the vars object.

+
source§

fn create_transform(&self, oracle: &dyn CodeOracle) -> Option<String>

The name of the type as it’s represented in the Variables object. +The string return may be used to combine with an indentifier, e.g. a Variables method name.

+
source§

fn variables_type(&self, oracle: &dyn CodeOracle) -> VariablesType

The name of the type as it’s represented in the Variables object. +The string return may be used to combine with an indentifier, e.g. a Variables method name.

+
source§

fn value_mapper(&self, oracle: &dyn CodeOracle) -> Option<String>

The method call here will use the create_transform to transform the value coming out of +the Variables object into the desired type.

+
source§

fn value_merger( + &self, + oracle: &dyn CodeOracle, + default: &dyn Display +) -> Option<String>

The method call to merge the value with the defaults.

+

This may use the merge_transform.

+

If this returns None, no merging happens, and implicit null replacement happens.

+
source§

fn as_json_transform( + &self, + oracle: &dyn CodeOracle, + prop: &dyn Display +) -> Option<String>

Implement these in different code types, and call recursively from different code types.

+
source§

fn literal( + &self, + oracle: &dyn CodeOracle, + ctx: &dyn Display, + renderer: &dyn LiteralRenderer, + literal: &Value +) -> String

A representation of the given literal for this type. +N.B. Literal is aliased from serde_json::Value.

+
source§

fn value_getter( + &self, + oracle: &dyn CodeOracle, + vars: &dyn Display, + prop: &dyn Display +) -> String

The expression needed to get a value out of a Variables objectm with the prop key. Read more
source§

fn defaults_type(&self, oracle: &dyn CodeOracle) -> String

source§

fn defaults_mapper( + &self, + oracle: &dyn CodeOracle, + value: &dyn Display, + vars: &dyn Display +) -> Option<String>

source§

fn merge_transform(&self, _oracle: &dyn CodeOracle) -> Option<String>

A function handle that is capable of merging two instances of the same class. By default, this is None.
source§

fn preference_getter( + &self, + _oracle: &dyn CodeOracle, + _prefs: &dyn Display, + _pref_key: &dyn Display +) -> Option<String>

source§

fn as_json(&self, oracle: &dyn CodeOracle, prop: &dyn Display) -> String

Call from the template
source§

fn is_resource_id(&self, _literal: &Value) -> bool

source§

fn helper_code(&self, _oracle: &dyn CodeOracle) -> Option<String>

Optional helper code to make this type work. +This might include functions to patch a default value with another.
source§

fn imports(&self, _oracle: &dyn CodeOracle) -> Option<Vec<String>>

A list of imports that are needed if this type is in use. +Classes are imported exactly once.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/kotlin/index.html b/book/rust-docs/nimbus_fml/backends/kotlin/index.html new file mode 100644 index 0000000000..07815d484b --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/kotlin/index.html @@ -0,0 +1 @@ +nimbus_fml::backends::kotlin - Rust
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/kotlin/sidebar-items.js b/book/rust-docs/nimbus_fml/backends/kotlin/sidebar-items.js new file mode 100644 index 0000000000..3e10ca38c8 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/kotlin/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["generate_struct"],"mod":["gen_structs"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/sidebar-items.js b/book/rust-docs/nimbus_fml/backends/sidebar-items.js new file mode 100644 index 0000000000..f60765042e --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["VariablesType"],"mod":["experimenter_manifest","frontend_manifest","kotlin","swift"],"trait":["CodeDeclaration","CodeOracle","CodeType","LiteralRenderer"],"type":["TypeIdentifier"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/swift/fn.generate_struct.html b/book/rust-docs/nimbus_fml/backends/swift/fn.generate_struct.html new file mode 100644 index 0000000000..1ca2a110ab --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/swift/fn.generate_struct.html @@ -0,0 +1,4 @@ +generate_struct in nimbus_fml::backends::swift - Rust
pub(crate) fn generate_struct(
+    manifest: &FeatureManifest,
+    cmd: &GenerateStructCmd
+) -> Result<(), FMLError>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/swift/gen_structs/bundled/index.html b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/bundled/index.html new file mode 100644 index 0000000000..1b23b628a6 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/bundled/index.html @@ -0,0 +1 @@ +nimbus_fml::backends::swift::gen_structs::bundled - Rust
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/swift/gen_structs/bundled/sidebar-items.js b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/bundled/sidebar-items.js new file mode 100644 index 0000000000..57bd7d569a --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/bundled/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"struct":["ImageCodeType","TextCodeType"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/swift/gen_structs/bundled/struct.ImageCodeType.html b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/bundled/struct.ImageCodeType.html new file mode 100644 index 0000000000..ba54d8f9f6 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/bundled/struct.ImageCodeType.html @@ -0,0 +1,61 @@ +ImageCodeType in nimbus_fml::backends::swift::gen_structs::bundled - Rust
pub(crate) struct ImageCodeType;

Trait Implementations§

source§

impl CodeType for ImageCodeType

source§

fn type_label(&self, _oracle: &dyn CodeOracle) -> String

The language specific label used to reference this type. This will be used in +method signatures and property declarations.

+
source§

fn variables_type(&self, _oracle: &dyn CodeOracle) -> VariablesType

The name of the type as it’s represented in the Variables object. +The string return may be used to combine with an indentifier, e.g. a Variables method name.

+
source§

fn literal( + &self, + _oracle: &dyn CodeOracle, + _ctx: &dyn Display, + _renderer: &dyn LiteralRenderer, + literal: &Value +) -> String

A representation of the given literal for this type. +N.B. Literal is aliased from serde_json::Value.

+
source§

fn property_getter( + &self, + oracle: &dyn CodeOracle, + vars: &dyn Display, + prop: &dyn Display, + default: &dyn Display +) -> String

The language specific expression that gets a value of the prop from the vars object, +and fallbacks to the default value. Read more
source§

fn value_getter( + &self, + oracle: &dyn CodeOracle, + vars: &dyn Display, + prop: &dyn Display +) -> String

The expression needed to get a value out of a Variables objectm with the prop key. Read more
source§

fn value_mapper(&self, oracle: &dyn CodeOracle) -> Option<String>

The method call here will use the create_transform to transform the value coming out of +the Variables object into the desired type. Read more
source§

fn defaults_type(&self, oracle: &dyn CodeOracle) -> String

source§

fn defaults_mapper( + &self, + _oracle: &dyn CodeOracle, + value: &dyn Display, + vars: &dyn Display +) -> Option<String>

source§

fn imports(&self, _oracle: &dyn CodeOracle) -> Option<Vec<String>>

A list of imports that are needed if this type is in use. +Classes are imported exactly once.
source§

fn value_merger( + &self, + _oracle: &dyn CodeOracle, + _default: &dyn Display +) -> Option<String>

The method call to merge the value with the defaults. Read more
source§

fn create_transform(&self, _oracle: &dyn CodeOracle) -> Option<String>

A function handle that is capable of turning the variables type to the TypeRef type.
source§

fn merge_transform(&self, _oracle: &dyn CodeOracle) -> Option<String>

A function handle that is capable of merging two instances of the same class. By default, this is None.
source§

fn preference_getter( + &self, + _oracle: &dyn CodeOracle, + _prefs: &dyn Display, + _pref_key: &dyn Display +) -> Option<String>

source§

fn as_json(&self, oracle: &dyn CodeOracle, prop: &dyn Display) -> String

Call from the template
source§

fn as_json_transform( + &self, + _oracle: &dyn CodeOracle, + _prop: &dyn Display +) -> Option<String>

Implement these in different code types, and call recursively from different code types.
source§

fn is_resource_id(&self, _literal: &Value) -> bool

source§

fn helper_code(&self, _oracle: &dyn CodeOracle) -> Option<String>

Optional helper code to make this type work. +This might include functions to patch a default value with another.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/swift/gen_structs/bundled/struct.TextCodeType.html b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/bundled/struct.TextCodeType.html new file mode 100644 index 0000000000..576bdb8df1 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/bundled/struct.TextCodeType.html @@ -0,0 +1,61 @@ +TextCodeType in nimbus_fml::backends::swift::gen_structs::bundled - Rust
pub(crate) struct TextCodeType;

Trait Implementations§

source§

impl CodeType for TextCodeType

source§

fn type_label(&self, _oracle: &dyn CodeOracle) -> String

The language specific label used to reference this type. This will be used in +method signatures and property declarations.

+
source§

fn variables_type(&self, _oracle: &dyn CodeOracle) -> VariablesType

The name of the type as it’s represented in the Variables object. +The string return may be used to combine with an indentifier, e.g. a Variables method name.

+
source§

fn literal( + &self, + _oracle: &dyn CodeOracle, + _ctx: &dyn Display, + _renderer: &dyn LiteralRenderer, + literal: &Value +) -> String

A representation of the given literal for this type. +N.B. Literal is aliased from serde_json::Value.

+
source§

fn property_getter( + &self, + oracle: &dyn CodeOracle, + vars: &dyn Display, + prop: &dyn Display, + default: &dyn Display +) -> String

The language specific expression that gets a value of the prop from the vars object, +and fallbacks to the default value. Read more
source§

fn value_getter( + &self, + oracle: &dyn CodeOracle, + vars: &dyn Display, + prop: &dyn Display +) -> String

The expression needed to get a value out of a Variables objectm with the prop key. Read more
source§

fn value_mapper(&self, oracle: &dyn CodeOracle) -> Option<String>

The method call here will use the create_transform to transform the value coming out of +the Variables object into the desired type. Read more
source§

fn defaults_type(&self, oracle: &dyn CodeOracle) -> String

source§

fn defaults_mapper( + &self, + _oracle: &dyn CodeOracle, + value: &dyn Display, + vars: &dyn Display +) -> Option<String>

source§

fn value_merger( + &self, + _oracle: &dyn CodeOracle, + _default: &dyn Display +) -> Option<String>

The method call to merge the value with the defaults. Read more
source§

fn create_transform(&self, _oracle: &dyn CodeOracle) -> Option<String>

A function handle that is capable of turning the variables type to the TypeRef type.
source§

fn merge_transform(&self, _oracle: &dyn CodeOracle) -> Option<String>

A function handle that is capable of merging two instances of the same class. By default, this is None.
source§

fn preference_getter( + &self, + _oracle: &dyn CodeOracle, + _prefs: &dyn Display, + _pref_key: &dyn Display +) -> Option<String>

source§

fn as_json(&self, oracle: &dyn CodeOracle, prop: &dyn Display) -> String

Call from the template
source§

fn as_json_transform( + &self, + _oracle: &dyn CodeOracle, + _prop: &dyn Display +) -> Option<String>

Implement these in different code types, and call recursively from different code types.
source§

fn is_resource_id(&self, _literal: &Value) -> bool

source§

fn helper_code(&self, _oracle: &dyn CodeOracle) -> Option<String>

Optional helper code to make this type work. +This might include functions to patch a default value with another.
source§

fn imports(&self, _oracle: &dyn CodeOracle) -> Option<Vec<String>>

A list of imports that are needed if this type is in use. +Classes are imported exactly once.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/swift/gen_structs/common/code_type/fn.property_getter.html b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/common/code_type/fn.property_getter.html new file mode 100644 index 0000000000..5452180b81 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/common/code_type/fn.property_getter.html @@ -0,0 +1,8 @@ +property_getter in nimbus_fml::backends::swift::gen_structs::common::code_type - Rust
pub(crate) fn property_getter(
+    ct: &dyn CodeType,
+    oracle: &dyn CodeOracle,
+    vars: &dyn Display,
+    prop: &dyn Display,
+    default: &dyn Display
+) -> String
Expand description

The language specific expression that gets a value of the prop from the vars object.

+
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/swift/gen_structs/common/code_type/fn.value_getter.html b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/common/code_type/fn.value_getter.html new file mode 100644 index 0000000000..f031f6e59a --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/common/code_type/fn.value_getter.html @@ -0,0 +1,6 @@ +value_getter in nimbus_fml::backends::swift::gen_structs::common::code_type - Rust
pub(crate) fn value_getter(
+    ct: &dyn CodeType,
+    oracle: &dyn CodeOracle,
+    vars: &dyn Display,
+    prop: &dyn Display
+) -> String
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/swift/gen_structs/common/code_type/fn.value_mapper.html b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/common/code_type/fn.value_mapper.html new file mode 100644 index 0000000000..ee4d5ed935 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/common/code_type/fn.value_mapper.html @@ -0,0 +1,4 @@ +value_mapper in nimbus_fml::backends::swift::gen_structs::common::code_type - Rust
pub(crate) fn value_mapper(
+    ct: &dyn CodeType,
+    oracle: &dyn CodeOracle
+) -> Option<String>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/swift/gen_structs/common/code_type/index.html b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/common/code_type/index.html new file mode 100644 index 0000000000..4d88102b38 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/common/code_type/index.html @@ -0,0 +1 @@ +nimbus_fml::backends::swift::gen_structs::common::code_type - Rust

Functions

\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/swift/gen_structs/common/code_type/sidebar-items.js b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/common/code_type/sidebar-items.js new file mode 100644 index 0000000000..fd7e03e7dc --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/common/code_type/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["property_getter","value_getter","value_mapper"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/swift/gen_structs/common/fn.class_name.html b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/common/fn.class_name.html new file mode 100644 index 0000000000..0617159de3 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/common/fn.class_name.html @@ -0,0 +1,2 @@ +class_name in nimbus_fml::backends::swift::gen_structs::common - Rust
pub fn class_name(nm: &dyn Display) -> String
Expand description

Get the idiomatic Swift rendering of a class name (for enums, records, errors, etc).

+
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/swift/gen_structs/common/fn.enum_variant_name.html b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/common/fn.enum_variant_name.html new file mode 100644 index 0000000000..56f58a3274 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/common/fn.enum_variant_name.html @@ -0,0 +1,2 @@ +enum_variant_name in nimbus_fml::backends::swift::gen_structs::common - Rust
pub fn enum_variant_name(nm: &dyn Display) -> String
Expand description

Get the idiomatic Swift rendering of an individual enum variant.

+
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/swift/gen_structs/common/fn.quoted.html b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/common/fn.quoted.html new file mode 100644 index 0000000000..4cbebb0677 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/common/fn.quoted.html @@ -0,0 +1,2 @@ +quoted in nimbus_fml::backends::swift::gen_structs::common - Rust
pub fn quoted(v: &dyn Display) -> String
Expand description

Surrounds a property name with quotes. It is assumed that property names do not need escaping.

+
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/swift/gen_structs/common/fn.var_name.html b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/common/fn.var_name.html new file mode 100644 index 0000000000..25865751b0 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/common/fn.var_name.html @@ -0,0 +1,2 @@ +var_name in nimbus_fml::backends::swift::gen_structs::common - Rust
pub fn var_name(nm: &dyn Display) -> String
Expand description

Get the idiomatic Swift rendering of a variable name.

+
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/swift/gen_structs/common/index.html b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/common/index.html new file mode 100644 index 0000000000..c4ee179688 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/common/index.html @@ -0,0 +1 @@ +nimbus_fml::backends::swift::gen_structs::common - Rust

Modules

Functions

  • Get the idiomatic Swift rendering of a class name (for enums, records, errors, etc).
  • Get the idiomatic Swift rendering of an individual enum variant.
  • Surrounds a property name with quotes. It is assumed that property names do not need escaping.
  • Get the idiomatic Swift rendering of a variable name.
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/swift/gen_structs/common/sidebar-items.js b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/common/sidebar-items.js new file mode 100644 index 0000000000..91cb527f53 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/common/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["class_name","enum_variant_name","quoted","var_name"],"mod":["code_type"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/swift/gen_structs/enum_/index.html b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/enum_/index.html new file mode 100644 index 0000000000..0ad15acc08 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/enum_/index.html @@ -0,0 +1 @@ +nimbus_fml::backends::swift::gen_structs::enum_ - Rust
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/swift/gen_structs/enum_/sidebar-items.js b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/enum_/sidebar-items.js new file mode 100644 index 0000000000..c9e9decf7d --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/enum_/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"struct":["EnumCodeDeclaration","EnumCodeType"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/swift/gen_structs/enum_/struct.EnumCodeDeclaration.html b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/enum_/struct.EnumCodeDeclaration.html new file mode 100644 index 0000000000..8d7f9a75a3 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/enum_/struct.EnumCodeDeclaration.html @@ -0,0 +1,23 @@ +EnumCodeDeclaration in nimbus_fml::backends::swift::gen_structs::enum_ - Rust
pub(crate) struct EnumCodeDeclaration {
+    inner: EnumDef,
+}

Fields§

§inner: EnumDef

Implementations§

source§

impl EnumCodeDeclaration

source

pub fn new(_fm: &FeatureManifest, inner: &EnumDef) -> Self

source

fn inner(&self) -> EnumDef

Trait Implementations§

source§

impl CodeDeclaration for EnumCodeDeclaration

source§

fn definition_code(&self, _oracle: &dyn CodeOracle) -> Option<String>

Code which represents this member. e.g. the foreign language class definition for +a given Object type.
source§

fn imports(&self, _oracle: &dyn CodeOracle) -> Option<Vec<String>>

A list of imports that are needed if this type is in use. +Classes are imported exactly once.
source§

fn initialization_code(&self, _oracle: &dyn CodeOracle) -> Option<String>

Code (one or more statements) that is run on start-up of the library, +but before the client code has access to it.
source§

impl Display for EnumCodeDeclaration

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Template for EnumCodeDeclaration

source§

fn render_into(&self, writer: &mut impl Write + ?Sized) -> Result<()>

Renders the template to the given writer fmt buffer
source§

const EXTENSION: Option<&'static str> = _

The template’s extension, if provided
source§

const SIZE_HINT: usize = 261usize

Provides a conservative estimate of the expanded length of the rendered template
source§

const MIME_TYPE: &'static str = _

The MIME type (Content-Type) of the data that gets rendered by this Template
§

fn render(&self) -> Result<String, Error>

Helper method which allocates a new String and renders into it
§

fn write_into(&self, writer: &mut impl Write) -> Result<(), Error>

Renders the template to the given writer io buffer

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> DynTemplate for Twhere + T: Template,

§

fn dyn_render(&self) -> Result<String, Error>

Helper method which allocates a new String and renders into it
§

fn dyn_render_into(&self, writer: &mut dyn Write) -> Result<(), Error>

Renders the template to the given writer fmt buffer
§

fn dyn_write_into(&self, writer: &mut dyn Write) -> Result<(), Error>

Renders the template to the given writer io buffer
§

fn extension(&self) -> Option<&'static str>

Helper function to inspect the template’s extension
§

fn size_hint(&self) -> usize

Provides a conservative estimate of the expanded length of the rendered template
§

fn mime_type(&self) -> &'static str

The MIME type (Content-Type) of the data that gets rendered by this Template
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/swift/gen_structs/enum_/struct.EnumCodeType.html b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/enum_/struct.EnumCodeType.html new file mode 100644 index 0000000000..d82665393a --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/enum_/struct.EnumCodeType.html @@ -0,0 +1,64 @@ +EnumCodeType in nimbus_fml::backends::swift::gen_structs::enum_ - Rust
pub(crate) struct EnumCodeType {
+    id: String,
+}

Fields§

§id: String

Implementations§

source§

impl EnumCodeType

source

pub(crate) fn new(id: String) -> Self

Trait Implementations§

source§

impl CodeType for EnumCodeType

source§

fn type_label(&self, _oracle: &dyn CodeOracle) -> String

The language specific label used to reference this type. This will be used in +method signatures and property declarations.

+
source§

fn variables_type(&self, _oracle: &dyn CodeOracle) -> VariablesType

The name of the type as it’s represented in the Variables object. +The string return may be used to combine with an indentifier, e.g. a Variables method name.

+
source§

fn create_transform(&self, oracle: &dyn CodeOracle) -> Option<String>

A function handle that is capable of turning the variables type to the TypeRef type.

+
source§

fn literal( + &self, + _oracle: &dyn CodeOracle, + _ctx: &dyn Display, + _renderer: &dyn LiteralRenderer, + literal: &Value +) -> String

A representation of the given literal for this type. +N.B. Literal is aliased from serde_json::Value.

+
source§

fn property_getter( + &self, + oracle: &dyn CodeOracle, + vars: &dyn Display, + prop: &dyn Display, + default: &dyn Display +) -> String

The language specific expression that gets a value of the prop from the vars object, +and fallbacks to the default value. Read more
source§

fn value_getter( + &self, + oracle: &dyn CodeOracle, + vars: &dyn Display, + prop: &dyn Display +) -> String

The expression needed to get a value out of a Variables objectm with the prop key. Read more
source§

fn value_mapper(&self, oracle: &dyn CodeOracle) -> Option<String>

The method call here will use the create_transform to transform the value coming out of +the Variables object into the desired type. Read more
source§

fn value_merger( + &self, + _oracle: &dyn CodeOracle, + _default: &dyn Display +) -> Option<String>

The method call to merge the value with the defaults. Read more
source§

fn merge_transform(&self, _oracle: &dyn CodeOracle) -> Option<String>

A function handle that is capable of merging two instances of the same class. By default, this is None.
source§

fn defaults_type(&self, oracle: &dyn CodeOracle) -> String

source§

fn defaults_mapper( + &self, + _oracle: &dyn CodeOracle, + _value: &dyn Display, + _vars: &dyn Display +) -> Option<String>

source§

fn preference_getter( + &self, + _oracle: &dyn CodeOracle, + _prefs: &dyn Display, + _pref_key: &dyn Display +) -> Option<String>

source§

fn as_json(&self, oracle: &dyn CodeOracle, prop: &dyn Display) -> String

Call from the template
source§

fn as_json_transform( + &self, + _oracle: &dyn CodeOracle, + _prop: &dyn Display +) -> Option<String>

Implement these in different code types, and call recursively from different code types.
source§

fn is_resource_id(&self, _literal: &Value) -> bool

source§

fn helper_code(&self, _oracle: &dyn CodeOracle) -> Option<String>

Optional helper code to make this type work. +This might include functions to patch a default value with another.
source§

fn imports(&self, _oracle: &dyn CodeOracle) -> Option<Vec<String>>

A list of imports that are needed if this type is in use. +Classes are imported exactly once.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/swift/gen_structs/feature/index.html b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/feature/index.html new file mode 100644 index 0000000000..3e5c3807db --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/feature/index.html @@ -0,0 +1 @@ +nimbus_fml::backends::swift::gen_structs::feature - Rust
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/swift/gen_structs/feature/sidebar-items.js b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/feature/sidebar-items.js new file mode 100644 index 0000000000..d6b3a42b92 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/feature/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"struct":["FeatureCodeDeclaration"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/swift/gen_structs/feature/struct.FeatureCodeDeclaration.html b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/feature/struct.FeatureCodeDeclaration.html new file mode 100644 index 0000000000..1f448c06a5 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/feature/struct.FeatureCodeDeclaration.html @@ -0,0 +1,30 @@ +FeatureCodeDeclaration in nimbus_fml::backends::swift::gen_structs::feature - Rust
pub(crate) struct FeatureCodeDeclaration {
+    inner: FeatureDef,
+    fm: FeatureManifest,
+}

Fields§

§inner: FeatureDef§fm: FeatureManifest

Implementations§

Trait Implementations§

source§

impl CodeDeclaration for FeatureCodeDeclaration

source§

fn definition_code(&self, _oracle: &dyn CodeOracle) -> Option<String>

Code which represents this member. e.g. the foreign language class definition for +a given Object type.
source§

fn imports(&self, _oracle: &dyn CodeOracle) -> Option<Vec<String>>

A list of imports that are needed if this type is in use. +Classes are imported exactly once.
source§

fn initialization_code(&self, _oracle: &dyn CodeOracle) -> Option<String>

Code (one or more statements) that is run on start-up of the library, +but before the client code has access to it.
source§

impl Display for FeatureCodeDeclaration

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl LiteralRenderer for FeatureCodeDeclaration

source§

fn literal( + &self, + oracle: &dyn CodeOracle, + typ: &TypeRef, + value: &Value, + ctx: &dyn Display +) -> String

source§

impl Template for FeatureCodeDeclaration

source§

fn render_into(&self, writer: &mut impl Write + ?Sized) -> Result<()>

Renders the template to the given writer fmt buffer
source§

const EXTENSION: Option<&'static str> = _

The template’s extension, if provided
source§

const SIZE_HINT: usize = 1_240usize

Provides a conservative estimate of the expanded length of the rendered template
source§

const MIME_TYPE: &'static str = _

The MIME type (Content-Type) of the data that gets rendered by this Template
§

fn render(&self) -> Result<String, Error>

Helper method which allocates a new String and renders into it
§

fn write_into(&self, writer: &mut impl Write) -> Result<(), Error>

Renders the template to the given writer io buffer

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> DynTemplate for Twhere + T: Template,

§

fn dyn_render(&self) -> Result<String, Error>

Helper method which allocates a new String and renders into it
§

fn dyn_render_into(&self, writer: &mut dyn Write) -> Result<(), Error>

Renders the template to the given writer fmt buffer
§

fn dyn_write_into(&self, writer: &mut dyn Write) -> Result<(), Error>

Renders the template to the given writer io buffer
§

fn extension(&self) -> Option<&'static str>

Helper function to inspect the template’s extension
§

fn size_hint(&self) -> usize

Provides a conservative estimate of the expanded length of the rendered template
§

fn mime_type(&self) -> &'static str

The MIME type (Content-Type) of the data that gets rendered by this Template
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/swift/gen_structs/filters/fn.class_name.html b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/filters/fn.class_name.html new file mode 100644 index 0000000000..4c6693f922 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/filters/fn.class_name.html @@ -0,0 +1,2 @@ +class_name in nimbus_fml::backends::swift::gen_structs::filters - Rust
pub fn class_name(nm: impl Display) -> Result<String, Error>
Expand description

Get the idiomatic Swift rendering of a class name (for enums, records, errors, etc).

+
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/swift/gen_structs/filters/fn.comment.html b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/filters/fn.comment.html new file mode 100644 index 0000000000..293e79a991 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/filters/fn.comment.html @@ -0,0 +1 @@ +comment in nimbus_fml::backends::swift::gen_structs::filters - Rust
pub fn comment(txt: impl Display, spaces: &str) -> Result<String, Error>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/swift/gen_structs/filters/fn.defaults_type_label.html b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/filters/fn.defaults_type_label.html new file mode 100644 index 0000000000..19e51ec2db --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/filters/fn.defaults_type_label.html @@ -0,0 +1 @@ +defaults_type_label in nimbus_fml::backends::swift::gen_structs::filters - Rust
pub fn defaults_type_label(type_: impl Borrow<TypeRef>) -> Result<String, Error>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/swift/gen_structs/filters/fn.enum_variant_name.html b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/filters/fn.enum_variant_name.html new file mode 100644 index 0000000000..2b69ab6571 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/filters/fn.enum_variant_name.html @@ -0,0 +1,2 @@ +enum_variant_name in nimbus_fml::backends::swift::gen_structs::filters - Rust
pub fn enum_variant_name(nm: impl Display) -> Result<String, Error>
Expand description

Get the idiomatic Swift rendering of an individual enum variant.

+
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/swift/gen_structs/filters/fn.literal.html b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/filters/fn.literal.html new file mode 100644 index 0000000000..af00541e56 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/filters/fn.literal.html @@ -0,0 +1,6 @@ +literal in nimbus_fml::backends::swift::gen_structs::filters - Rust
pub fn literal(
+    type_: impl Borrow<TypeRef>,
+    renderer: impl LiteralRenderer,
+    literal: impl Borrow<Value>,
+    ctx: impl Display
+) -> Result<String, Error>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/swift/gen_structs/filters/fn.property.html b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/filters/fn.property.html new file mode 100644 index 0000000000..dd3855d17a --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/filters/fn.property.html @@ -0,0 +1,6 @@ +property in nimbus_fml::backends::swift::gen_structs::filters - Rust
pub fn property(
+    type_: impl Borrow<TypeRef>,
+    prop: impl Display,
+    vars: impl Display,
+    default: impl Display
+) -> Result<String, Error>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/swift/gen_structs/filters/fn.quoted.html b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/filters/fn.quoted.html new file mode 100644 index 0000000000..36b0f9e398 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/filters/fn.quoted.html @@ -0,0 +1 @@ +quoted in nimbus_fml::backends::swift::gen_structs::filters - Rust
pub fn quoted(txt: impl Display) -> Result<String, Error>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/swift/gen_structs/filters/fn.type_label.html b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/filters/fn.type_label.html new file mode 100644 index 0000000000..3c298b4541 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/filters/fn.type_label.html @@ -0,0 +1 @@ +type_label in nimbus_fml::backends::swift::gen_structs::filters - Rust
pub fn type_label(type_: impl Borrow<TypeRef>) -> Result<String, Error>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/swift/gen_structs/filters/fn.var_name.html b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/filters/fn.var_name.html new file mode 100644 index 0000000000..f158599695 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/filters/fn.var_name.html @@ -0,0 +1,2 @@ +var_name in nimbus_fml::backends::swift::gen_structs::filters - Rust
pub fn var_name(nm: impl Display) -> Result<String, Error>
Expand description

Get the idiomatic Swift rendering of a variable name.

+
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/swift/gen_structs/filters/index.html b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/filters/index.html new file mode 100644 index 0000000000..659d009c42 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/filters/index.html @@ -0,0 +1 @@ +nimbus_fml::backends::swift::gen_structs::filters - Rust

Functions

\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/swift/gen_structs/filters/sidebar-items.js b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/filters/sidebar-items.js new file mode 100644 index 0000000000..1879833cfd --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/filters/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["class_name","comment","defaults_type_label","enum_variant_name","literal","property","quoted","type_label","var_name"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/swift/gen_structs/imports/index.html b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/imports/index.html new file mode 100644 index 0000000000..c44c04694a --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/imports/index.html @@ -0,0 +1 @@ +nimbus_fml::backends::swift::gen_structs::imports - Rust
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/swift/gen_structs/imports/sidebar-items.js b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/imports/sidebar-items.js new file mode 100644 index 0000000000..6a738da76e --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/imports/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"struct":["ImportedModuleInitialization"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/swift/gen_structs/imports/struct.ImportedModuleInitialization.html b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/imports/struct.ImportedModuleInitialization.html new file mode 100644 index 0000000000..a4e3fbd6d0 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/imports/struct.ImportedModuleInitialization.html @@ -0,0 +1,29 @@ +ImportedModuleInitialization in nimbus_fml::backends::swift::gen_structs::imports - Rust
pub(crate) struct ImportedModuleInitialization<'a> {
+    pub(crate) inner: ImportedModule<'a>,
+}

Fields§

§inner: ImportedModule<'a>

Implementations§

source§

impl<'a> ImportedModuleInitialization<'a>

source

pub(crate) fn new(inner: &ImportedModule<'a>) -> Self

Trait Implementations§

source§

impl CodeDeclaration for ImportedModuleInitialization<'_>

source§

fn imports(&self, oracle: &dyn CodeOracle) -> Option<Vec<String>>

A list of imports that are needed if this type is in use. +Classes are imported exactly once.
source§

fn initialization_code(&self, _oracle: &dyn CodeOracle) -> Option<String>

Code (one or more statements) that is run on start-up of the library, +but before the client code has access to it.
source§

fn definition_code(&self, _oracle: &dyn CodeOracle) -> Option<String>

Code which represents this member. e.g. the foreign language class definition for +a given Object type.
source§

impl<'a> Display for ImportedModuleInitialization<'a>

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl LiteralRenderer for ImportedModuleInitialization<'_>

source§

fn literal( + &self, + oracle: &dyn CodeOracle, + typ: &TypeRef, + value: &Value, + ctx: &dyn Display +) -> String

source§

impl<'a> Template for ImportedModuleInitialization<'a>

source§

fn render_into(&self, writer: &mut impl Write + ?Sized) -> Result<()>

Renders the template to the given writer fmt buffer
source§

const EXTENSION: Option<&'static str> = _

The template’s extension, if provided
source§

const SIZE_HINT: usize = 280usize

Provides a conservative estimate of the expanded length of the rendered template
source§

const MIME_TYPE: &'static str = _

The MIME type (Content-Type) of the data that gets rendered by this Template
§

fn render(&self) -> Result<String, Error>

Helper method which allocates a new String and renders into it
§

fn write_into(&self, writer: &mut impl Write) -> Result<(), Error>

Renders the template to the given writer io buffer

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> DynTemplate for Twhere + T: Template,

§

fn dyn_render(&self) -> Result<String, Error>

Helper method which allocates a new String and renders into it
§

fn dyn_render_into(&self, writer: &mut dyn Write) -> Result<(), Error>

Renders the template to the given writer fmt buffer
§

fn dyn_write_into(&self, writer: &mut dyn Write) -> Result<(), Error>

Renders the template to the given writer io buffer
§

fn extension(&self) -> Option<&'static str>

Helper function to inspect the template’s extension
§

fn size_hint(&self) -> usize

Provides a conservative estimate of the expanded length of the rendered template
§

fn mime_type(&self) -> &'static str

The MIME type (Content-Type) of the data that gets rendered by this Template
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/swift/gen_structs/index.html b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/index.html new file mode 100644 index 0000000000..fcc6bf2e81 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/index.html @@ -0,0 +1 @@ +nimbus_fml::backends::swift::gen_structs - Rust
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/swift/gen_structs/object/fn.object_literal.html b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/object/fn.object_literal.html new file mode 100644 index 0000000000..2d7636de5b --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/object/fn.object_literal.html @@ -0,0 +1,8 @@ +object_literal in nimbus_fml::backends::swift::gen_structs::object - Rust
pub(crate) fn object_literal(
+    fm: &FeatureManifest,
+    renderer: &dyn LiteralRenderer,
+    oracle: &dyn CodeOracle,
+    typ: &TypeRef,
+    value: &Value,
+    ctx: &dyn Display
+) -> String
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/swift/gen_structs/object/index.html b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/object/index.html new file mode 100644 index 0000000000..19689fb090 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/object/index.html @@ -0,0 +1 @@ +nimbus_fml::backends::swift::gen_structs::object - Rust
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/swift/gen_structs/object/sidebar-items.js b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/object/sidebar-items.js new file mode 100644 index 0000000000..ff87dd8d26 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/object/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["object_literal"],"struct":["ObjectCodeDeclaration","ObjectCodeType","ObjectRuntime"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/swift/gen_structs/object/struct.ObjectCodeDeclaration.html b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/object/struct.ObjectCodeDeclaration.html new file mode 100644 index 0000000000..8cd766f2f1 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/object/struct.ObjectCodeDeclaration.html @@ -0,0 +1,30 @@ +ObjectCodeDeclaration in nimbus_fml::backends::swift::gen_structs::object - Rust
pub(crate) struct ObjectCodeDeclaration {
+    inner: ObjectDef,
+    fm: FeatureManifest,
+}

Fields§

§inner: ObjectDef§fm: FeatureManifest

Implementations§

source§

impl ObjectCodeDeclaration

source

pub fn new(fm: &FeatureManifest, inner: &ObjectDef) -> Self

source

pub fn inner(&self) -> ObjectDef

Trait Implementations§

source§

impl CodeDeclaration for ObjectCodeDeclaration

source§

fn definition_code(&self, _oracle: &dyn CodeOracle) -> Option<String>

Code which represents this member. e.g. the foreign language class definition for +a given Object type.
source§

fn imports(&self, _oracle: &dyn CodeOracle) -> Option<Vec<String>>

A list of imports that are needed if this type is in use. +Classes are imported exactly once.
source§

fn initialization_code(&self, _oracle: &dyn CodeOracle) -> Option<String>

Code (one or more statements) that is run on start-up of the library, +but before the client code has access to it.
source§

impl Display for ObjectCodeDeclaration

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl LiteralRenderer for ObjectCodeDeclaration

source§

fn literal( + &self, + oracle: &dyn CodeOracle, + typ: &TypeRef, + value: &Value, + ctx: &dyn Display +) -> String

source§

impl Template for ObjectCodeDeclaration

source§

fn render_into(&self, writer: &mut impl Write + ?Sized) -> Result<()>

Renders the template to the given writer fmt buffer
source§

const EXTENSION: Option<&'static str> = _

The template’s extension, if provided
source§

const SIZE_HINT: usize = 1_502usize

Provides a conservative estimate of the expanded length of the rendered template
source§

const MIME_TYPE: &'static str = _

The MIME type (Content-Type) of the data that gets rendered by this Template
§

fn render(&self) -> Result<String, Error>

Helper method which allocates a new String and renders into it
§

fn write_into(&self, writer: &mut impl Write) -> Result<(), Error>

Renders the template to the given writer io buffer

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> DynTemplate for Twhere + T: Template,

§

fn dyn_render(&self) -> Result<String, Error>

Helper method which allocates a new String and renders into it
§

fn dyn_render_into(&self, writer: &mut dyn Write) -> Result<(), Error>

Renders the template to the given writer fmt buffer
§

fn dyn_write_into(&self, writer: &mut dyn Write) -> Result<(), Error>

Renders the template to the given writer io buffer
§

fn extension(&self) -> Option<&'static str>

Helper function to inspect the template’s extension
§

fn size_hint(&self) -> usize

Provides a conservative estimate of the expanded length of the rendered template
§

fn mime_type(&self) -> &'static str

The MIME type (Content-Type) of the data that gets rendered by this Template
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/swift/gen_structs/object/struct.ObjectCodeType.html b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/object/struct.ObjectCodeType.html new file mode 100644 index 0000000000..e82b560a8d --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/object/struct.ObjectCodeType.html @@ -0,0 +1,62 @@ +ObjectCodeType in nimbus_fml::backends::swift::gen_structs::object - Rust
pub struct ObjectCodeType {
+    id: String,
+}

Fields§

§id: String

Implementations§

source§

impl ObjectCodeType

source

pub fn new(id: String) -> Self

Trait Implementations§

source§

impl CodeType for ObjectCodeType

source§

fn variables_type(&self, _oracle: &dyn CodeOracle) -> VariablesType

The language specific expression that gets a value of the prop from the vars object. +The name of the type as it’s represented in the Variables object. +The string return may be used to combine with an indentifier, e.g. a Variables method name.

+
source§

fn type_label(&self, _oracle: &dyn CodeOracle) -> String

The language specific label used to reference this type. This will be used in +method signatures and property declarations.
source§

fn property_getter( + &self, + oracle: &dyn CodeOracle, + vars: &dyn Display, + prop: &dyn Display, + default: &dyn Display +) -> String

The language specific expression that gets a value of the prop from the vars object, +and fallbacks to the default value. Read more
source§

fn value_getter( + &self, + oracle: &dyn CodeOracle, + vars: &dyn Display, + prop: &dyn Display +) -> String

The expression needed to get a value out of a Variables objectm with the prop key. Read more
source§

fn value_mapper(&self, oracle: &dyn CodeOracle) -> Option<String>

The method call here will use the create_transform to transform the value coming out of +the Variables object into the desired type. Read more
source§

fn create_transform(&self, oracle: &dyn CodeOracle) -> Option<String>

A function handle that is capable of turning the variables type to the TypeRef type.
source§

fn merge_transform(&self, oracle: &dyn CodeOracle) -> Option<String>

A function handle that is capable of merging two instances of the same class. By default, this is None.
source§

fn value_merger( + &self, + _oracle: &dyn CodeOracle, + default: &dyn Display +) -> Option<String>

The method call to merge the value with the defaults. Read more
source§

fn literal( + &self, + oracle: &dyn CodeOracle, + ctx: &dyn Display, + renderer: &dyn LiteralRenderer, + literal: &Value +) -> String

A representation of the given literal for this type. +N.B. Literal is aliased from serde_json::Value.
source§

fn defaults_type(&self, oracle: &dyn CodeOracle) -> String

source§

fn defaults_mapper( + &self, + _oracle: &dyn CodeOracle, + _value: &dyn Display, + _vars: &dyn Display +) -> Option<String>

source§

fn preference_getter( + &self, + _oracle: &dyn CodeOracle, + _prefs: &dyn Display, + _pref_key: &dyn Display +) -> Option<String>

source§

fn as_json(&self, oracle: &dyn CodeOracle, prop: &dyn Display) -> String

Call from the template
source§

fn as_json_transform( + &self, + _oracle: &dyn CodeOracle, + _prop: &dyn Display +) -> Option<String>

Implement these in different code types, and call recursively from different code types.
source§

fn is_resource_id(&self, _literal: &Value) -> bool

source§

fn helper_code(&self, _oracle: &dyn CodeOracle) -> Option<String>

Optional helper code to make this type work. +This might include functions to patch a default value with another.
source§

fn imports(&self, _oracle: &dyn CodeOracle) -> Option<Vec<String>>

A list of imports that are needed if this type is in use. +Classes are imported exactly once.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/swift/gen_structs/object/struct.ObjectRuntime.html b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/object/struct.ObjectRuntime.html new file mode 100644 index 0000000000..698ea98e53 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/object/struct.ObjectRuntime.html @@ -0,0 +1,19 @@ +ObjectRuntime in nimbus_fml::backends::swift::gen_structs::object - Rust
pub struct ObjectRuntime;

Trait Implementations§

source§

impl CodeDeclaration for ObjectRuntime

source§

fn imports(&self, _oracle: &dyn CodeOracle) -> Option<Vec<String>>

A list of imports that are needed if this type is in use. +Classes are imported exactly once.
source§

fn initialization_code(&self, _oracle: &dyn CodeOracle) -> Option<String>

Code (one or more statements) that is run on start-up of the library, +but before the client code has access to it.
source§

fn definition_code(&self, _oracle: &dyn CodeOracle) -> Option<String>

Code which represents this member. e.g. the foreign language class definition for +a given Object type.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/swift/gen_structs/primitives/index.html b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/primitives/index.html new file mode 100644 index 0000000000..689402b4b5 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/primitives/index.html @@ -0,0 +1 @@ +nimbus_fml::backends::swift::gen_structs::primitives - Rust
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/swift/gen_structs/primitives/sidebar-items.js b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/primitives/sidebar-items.js new file mode 100644 index 0000000000..f28636616e --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/primitives/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"struct":["BooleanCodeType","IntCodeType","StringCodeType"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/swift/gen_structs/primitives/struct.BooleanCodeType.html b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/primitives/struct.BooleanCodeType.html new file mode 100644 index 0000000000..83ef0986bf --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/primitives/struct.BooleanCodeType.html @@ -0,0 +1,61 @@ +BooleanCodeType in nimbus_fml::backends::swift::gen_structs::primitives - Rust
pub(crate) struct BooleanCodeType;

Trait Implementations§

source§

impl CodeType for BooleanCodeType

source§

fn type_label(&self, _oracle: &dyn CodeOracle) -> String

The language specific label used to reference this type. This will be used in +method signatures and property declarations.

+
source§

fn variables_type(&self, _oracle: &dyn CodeOracle) -> VariablesType

The name of the type as it’s represented in the Variables object. +The string return may be used to combine with an indentifier, e.g. a Variables method name.

+
source§

fn literal( + &self, + _oracle: &dyn CodeOracle, + _ctx: &dyn Display, + _renderer: &dyn LiteralRenderer, + literal: &Value +) -> String

A representation of the given literal for this type. +N.B. Literal is aliased from serde_json::Value.

+
source§

fn property_getter( + &self, + oracle: &dyn CodeOracle, + vars: &dyn Display, + prop: &dyn Display, + default: &dyn Display +) -> String

The language specific expression that gets a value of the prop from the vars object, +and fallbacks to the default value. Read more
source§

fn value_getter( + &self, + oracle: &dyn CodeOracle, + vars: &dyn Display, + prop: &dyn Display +) -> String

The expression needed to get a value out of a Variables objectm with the prop key. Read more
source§

fn value_mapper(&self, oracle: &dyn CodeOracle) -> Option<String>

The method call here will use the create_transform to transform the value coming out of +the Variables object into the desired type. Read more
source§

fn value_merger( + &self, + _oracle: &dyn CodeOracle, + _default: &dyn Display +) -> Option<String>

The method call to merge the value with the defaults. Read more
source§

fn create_transform(&self, _oracle: &dyn CodeOracle) -> Option<String>

A function handle that is capable of turning the variables type to the TypeRef type.
source§

fn merge_transform(&self, _oracle: &dyn CodeOracle) -> Option<String>

A function handle that is capable of merging two instances of the same class. By default, this is None.
source§

fn defaults_type(&self, oracle: &dyn CodeOracle) -> String

source§

fn defaults_mapper( + &self, + _oracle: &dyn CodeOracle, + _value: &dyn Display, + _vars: &dyn Display +) -> Option<String>

source§

fn preference_getter( + &self, + _oracle: &dyn CodeOracle, + _prefs: &dyn Display, + _pref_key: &dyn Display +) -> Option<String>

source§

fn as_json(&self, oracle: &dyn CodeOracle, prop: &dyn Display) -> String

Call from the template
source§

fn as_json_transform( + &self, + _oracle: &dyn CodeOracle, + _prop: &dyn Display +) -> Option<String>

Implement these in different code types, and call recursively from different code types.
source§

fn is_resource_id(&self, _literal: &Value) -> bool

source§

fn helper_code(&self, _oracle: &dyn CodeOracle) -> Option<String>

Optional helper code to make this type work. +This might include functions to patch a default value with another.
source§

fn imports(&self, _oracle: &dyn CodeOracle) -> Option<Vec<String>>

A list of imports that are needed if this type is in use. +Classes are imported exactly once.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/swift/gen_structs/primitives/struct.IntCodeType.html b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/primitives/struct.IntCodeType.html new file mode 100644 index 0000000000..cdf6ceab4d --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/primitives/struct.IntCodeType.html @@ -0,0 +1,61 @@ +IntCodeType in nimbus_fml::backends::swift::gen_structs::primitives - Rust
pub(crate) struct IntCodeType;

Trait Implementations§

source§

impl CodeType for IntCodeType

source§

fn type_label(&self, _oracle: &dyn CodeOracle) -> String

The language specific label used to reference this type. This will be used in +method signatures and property declarations.

+
source§

fn variables_type(&self, _oracle: &dyn CodeOracle) -> VariablesType

The name of the type as it’s represented in the Variables object. +The string return may be used to combine with an indentifier, e.g. a Variables method name.

+
source§

fn literal( + &self, + _oracle: &dyn CodeOracle, + _ctx: &dyn Display, + _renderer: &dyn LiteralRenderer, + literal: &Value +) -> String

A representation of the given literal for this type. +N.B. Literal is aliased from serde_json::Value.

+
source§

fn property_getter( + &self, + oracle: &dyn CodeOracle, + vars: &dyn Display, + prop: &dyn Display, + default: &dyn Display +) -> String

The language specific expression that gets a value of the prop from the vars object, +and fallbacks to the default value. Read more
source§

fn value_getter( + &self, + oracle: &dyn CodeOracle, + vars: &dyn Display, + prop: &dyn Display +) -> String

The expression needed to get a value out of a Variables objectm with the prop key. Read more
source§

fn value_mapper(&self, oracle: &dyn CodeOracle) -> Option<String>

The method call here will use the create_transform to transform the value coming out of +the Variables object into the desired type. Read more
source§

fn value_merger( + &self, + _oracle: &dyn CodeOracle, + _default: &dyn Display +) -> Option<String>

The method call to merge the value with the defaults. Read more
source§

fn create_transform(&self, _oracle: &dyn CodeOracle) -> Option<String>

A function handle that is capable of turning the variables type to the TypeRef type.
source§

fn merge_transform(&self, _oracle: &dyn CodeOracle) -> Option<String>

A function handle that is capable of merging two instances of the same class. By default, this is None.
source§

fn defaults_type(&self, oracle: &dyn CodeOracle) -> String

source§

fn defaults_mapper( + &self, + _oracle: &dyn CodeOracle, + _value: &dyn Display, + _vars: &dyn Display +) -> Option<String>

source§

fn preference_getter( + &self, + _oracle: &dyn CodeOracle, + _prefs: &dyn Display, + _pref_key: &dyn Display +) -> Option<String>

source§

fn as_json(&self, oracle: &dyn CodeOracle, prop: &dyn Display) -> String

Call from the template
source§

fn as_json_transform( + &self, + _oracle: &dyn CodeOracle, + _prop: &dyn Display +) -> Option<String>

Implement these in different code types, and call recursively from different code types.
source§

fn is_resource_id(&self, _literal: &Value) -> bool

source§

fn helper_code(&self, _oracle: &dyn CodeOracle) -> Option<String>

Optional helper code to make this type work. +This might include functions to patch a default value with another.
source§

fn imports(&self, _oracle: &dyn CodeOracle) -> Option<Vec<String>>

A list of imports that are needed if this type is in use. +Classes are imported exactly once.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/swift/gen_structs/primitives/struct.StringCodeType.html b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/primitives/struct.StringCodeType.html new file mode 100644 index 0000000000..5f5d1ca665 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/primitives/struct.StringCodeType.html @@ -0,0 +1,61 @@ +StringCodeType in nimbus_fml::backends::swift::gen_structs::primitives - Rust
pub(crate) struct StringCodeType;

Trait Implementations§

source§

impl CodeType for StringCodeType

source§

fn type_label(&self, _oracle: &dyn CodeOracle) -> String

The language specific label used to reference this type. This will be used in +method signatures and property declarations.

+
source§

fn variables_type(&self, _oracle: &dyn CodeOracle) -> VariablesType

The name of the type as it’s represented in the Variables object. +The string return may be used to combine with an indentifier, e.g. a Variables method name.

+
source§

fn literal( + &self, + _oracle: &dyn CodeOracle, + _ctx: &dyn Display, + _renderer: &dyn LiteralRenderer, + literal: &Value +) -> String

A representation of the given literal for this type. +N.B. Literal is aliased from serde_json::Value.

+
source§

fn property_getter( + &self, + oracle: &dyn CodeOracle, + vars: &dyn Display, + prop: &dyn Display, + default: &dyn Display +) -> String

The language specific expression that gets a value of the prop from the vars object, +and fallbacks to the default value. Read more
source§

fn value_getter( + &self, + oracle: &dyn CodeOracle, + vars: &dyn Display, + prop: &dyn Display +) -> String

The expression needed to get a value out of a Variables objectm with the prop key. Read more
source§

fn value_mapper(&self, oracle: &dyn CodeOracle) -> Option<String>

The method call here will use the create_transform to transform the value coming out of +the Variables object into the desired type. Read more
source§

fn value_merger( + &self, + _oracle: &dyn CodeOracle, + _default: &dyn Display +) -> Option<String>

The method call to merge the value with the defaults. Read more
source§

fn create_transform(&self, _oracle: &dyn CodeOracle) -> Option<String>

A function handle that is capable of turning the variables type to the TypeRef type.
source§

fn merge_transform(&self, _oracle: &dyn CodeOracle) -> Option<String>

A function handle that is capable of merging two instances of the same class. By default, this is None.
source§

fn defaults_type(&self, oracle: &dyn CodeOracle) -> String

source§

fn defaults_mapper( + &self, + _oracle: &dyn CodeOracle, + _value: &dyn Display, + _vars: &dyn Display +) -> Option<String>

source§

fn preference_getter( + &self, + _oracle: &dyn CodeOracle, + _prefs: &dyn Display, + _pref_key: &dyn Display +) -> Option<String>

source§

fn as_json(&self, oracle: &dyn CodeOracle, prop: &dyn Display) -> String

Call from the template
source§

fn as_json_transform( + &self, + _oracle: &dyn CodeOracle, + _prop: &dyn Display +) -> Option<String>

Implement these in different code types, and call recursively from different code types.
source§

fn is_resource_id(&self, _literal: &Value) -> bool

source§

fn helper_code(&self, _oracle: &dyn CodeOracle) -> Option<String>

Optional helper code to make this type work. +This might include functions to patch a default value with another.
source§

fn imports(&self, _oracle: &dyn CodeOracle) -> Option<Vec<String>>

A list of imports that are needed if this type is in use. +Classes are imported exactly once.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/swift/gen_structs/sidebar-items.js b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/sidebar-items.js new file mode 100644 index 0000000000..cd6e094190 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"mod":["bundled","common","enum_","feature","filters","imports","object","primitives","structural"],"struct":["ConcreteCodeOracle","FeatureManifestDeclaration"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/swift/gen_structs/struct.ConcreteCodeOracle.html b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/struct.ConcreteCodeOracle.html new file mode 100644 index 0000000000..b0bb705d6b --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/struct.ConcreteCodeOracle.html @@ -0,0 +1,17 @@ +ConcreteCodeOracle in nimbus_fml::backends::swift::gen_structs - Rust
pub struct ConcreteCodeOracle;

Implementations§

Trait Implementations§

source§

impl Clone for ConcreteCodeOracle

source§

fn clone(&self) -> ConcreteCodeOracle

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl CodeOracle for ConcreteCodeOracle

source§

fn find(&self, type_: &TypeRef) -> Box<dyn CodeType>

source§

impl Default for ConcreteCodeOracle

source§

fn default() -> ConcreteCodeOracle

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/swift/gen_structs/struct.FeatureManifestDeclaration.html b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/struct.FeatureManifestDeclaration.html new file mode 100644 index 0000000000..5a3df96718 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/struct.FeatureManifestDeclaration.html @@ -0,0 +1,21 @@ +FeatureManifestDeclaration in nimbus_fml::backends::swift::gen_structs - Rust
pub struct FeatureManifestDeclaration<'a> {
+    fm: &'a FeatureManifest,
+    oracle: ConcreteCodeOracle,
+}

Fields§

§fm: &'a FeatureManifest§oracle: ConcreteCodeOracle

Implementations§

source§

impl<'a> FeatureManifestDeclaration<'a>

source

pub fn new(fm: &'a FeatureManifest) -> Self

source

pub fn members(&self) -> Vec<Box<dyn CodeDeclaration + 'a>>

source

pub fn iter_feature_defs(&self) -> Vec<&FeatureDef>

source

pub fn initialization_code(&self) -> Vec<String>

source

pub fn declaration_code(&self) -> Vec<String>

source

pub fn imports(&self) -> Vec<String>

Trait Implementations§

source§

impl<'a> Display for FeatureManifestDeclaration<'a>

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl<'a> Template for FeatureManifestDeclaration<'a>

source§

fn render_into(&self, writer: &mut impl Write + ?Sized) -> Result<()>

Renders the template to the given writer fmt buffer
source§

const EXTENSION: Option<&'static str> = _

The template’s extension, if provided
source§

const SIZE_HINT: usize = 2_691usize

Provides a conservative estimate of the expanded length of the rendered template
source§

const MIME_TYPE: &'static str = _

The MIME type (Content-Type) of the data that gets rendered by this Template
§

fn render(&self) -> Result<String, Error>

Helper method which allocates a new String and renders into it
§

fn write_into(&self, writer: &mut impl Write) -> Result<(), Error>

Renders the template to the given writer io buffer

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> DynTemplate for Twhere + T: Template,

§

fn dyn_render(&self) -> Result<String, Error>

Helper method which allocates a new String and renders into it
§

fn dyn_render_into(&self, writer: &mut dyn Write) -> Result<(), Error>

Renders the template to the given writer fmt buffer
§

fn dyn_write_into(&self, writer: &mut dyn Write) -> Result<(), Error>

Renders the template to the given writer io buffer
§

fn extension(&self) -> Option<&'static str>

Helper function to inspect the template’s extension
§

fn size_hint(&self) -> usize

Provides a conservative estimate of the expanded length of the rendered template
§

fn mime_type(&self) -> &'static str

The MIME type (Content-Type) of the data that gets rendered by this Template
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/swift/gen_structs/structural/index.html b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/structural/index.html new file mode 100644 index 0000000000..79d3288d89 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/structural/index.html @@ -0,0 +1 @@ +nimbus_fml::backends::swift::gen_structs::structural - Rust
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/swift/gen_structs/structural/sidebar-items.js b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/structural/sidebar-items.js new file mode 100644 index 0000000000..95cbe43e36 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/structural/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"struct":["ListCodeType","MapCodeType","OptionalCodeType"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/swift/gen_structs/structural/struct.ListCodeType.html b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/structural/struct.ListCodeType.html new file mode 100644 index 0000000000..6237af7b41 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/structural/struct.ListCodeType.html @@ -0,0 +1,63 @@ +ListCodeType in nimbus_fml::backends::swift::gen_structs::structural - Rust
pub(crate) struct ListCodeType {
+    inner: TypeRef,
+}

Fields§

§inner: TypeRef

Implementations§

source§

impl ListCodeType

source

pub(crate) fn new(inner: &TypeRef) -> Self

Trait Implementations§

source§

impl CodeType for ListCodeType

source§

fn type_label(&self, oracle: &dyn CodeOracle) -> String

The language specific label used to reference this type. This will be used in +method signatures and property declarations.

+
source§

fn variables_type(&self, _oracle: &dyn CodeOracle) -> VariablesType

The name of the type as it’s represented in the Variables object. +The string return may be used to combine with an indentifier, e.g. a Variables method name.

+
source§

fn literal( + &self, + oracle: &dyn CodeOracle, + ctx: &dyn Display, + renderer: &dyn LiteralRenderer, + literal: &Value +) -> String

A representation of the given literal for this type. +N.B. Literal is aliased from serde_json::Value.

+
source§

fn property_getter( + &self, + oracle: &dyn CodeOracle, + vars: &dyn Display, + prop: &dyn Display, + default: &dyn Display +) -> String

The language specific expression that gets a value of the prop from the vars object, +and fallbacks to the default value. Read more
source§

fn value_getter( + &self, + oracle: &dyn CodeOracle, + vars: &dyn Display, + prop: &dyn Display +) -> String

The expression needed to get a value out of a Variables objectm with the prop key. Read more
source§

fn value_mapper(&self, oracle: &dyn CodeOracle) -> Option<String>

The method call here will use the create_transform to transform the value coming out of +the Variables object into the desired type. Read more
source§

fn value_merger( + &self, + _oracle: &dyn CodeOracle, + _default: &dyn Display +) -> Option<String>

The method call to merge the value with the defaults. Read more
source§

fn defaults_type(&self, oracle: &dyn CodeOracle) -> String

source§

fn defaults_mapper( + &self, + oracle: &dyn CodeOracle, + value: &dyn Display, + vars: &dyn Display +) -> Option<String>

source§

fn create_transform(&self, _oracle: &dyn CodeOracle) -> Option<String>

A function handle that is capable of turning the variables type to the TypeRef type.
source§

fn merge_transform(&self, _oracle: &dyn CodeOracle) -> Option<String>

A function handle that is capable of merging two instances of the same class. By default, this is None.
source§

fn preference_getter( + &self, + _oracle: &dyn CodeOracle, + _prefs: &dyn Display, + _pref_key: &dyn Display +) -> Option<String>

source§

fn as_json(&self, oracle: &dyn CodeOracle, prop: &dyn Display) -> String

Call from the template
source§

fn as_json_transform( + &self, + _oracle: &dyn CodeOracle, + _prop: &dyn Display +) -> Option<String>

Implement these in different code types, and call recursively from different code types.
source§

fn is_resource_id(&self, _literal: &Value) -> bool

source§

fn helper_code(&self, _oracle: &dyn CodeOracle) -> Option<String>

Optional helper code to make this type work. +This might include functions to patch a default value with another.
source§

fn imports(&self, _oracle: &dyn CodeOracle) -> Option<Vec<String>>

A list of imports that are needed if this type is in use. +Classes are imported exactly once.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/swift/gen_structs/structural/struct.MapCodeType.html b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/structural/struct.MapCodeType.html new file mode 100644 index 0000000000..58011a2271 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/structural/struct.MapCodeType.html @@ -0,0 +1,64 @@ +MapCodeType in nimbus_fml::backends::swift::gen_structs::structural - Rust
pub(crate) struct MapCodeType {
+    k_type: TypeRef,
+    v_type: TypeRef,
+}

Fields§

§k_type: TypeRef§v_type: TypeRef

Implementations§

source§

impl MapCodeType

source

pub(crate) fn new(k: &TypeRef, v: &TypeRef) -> Self

Trait Implementations§

source§

impl CodeType for MapCodeType

source§

fn type_label(&self, oracle: &dyn CodeOracle) -> String

The language specific label used to reference this type. This will be used in +method signatures and property declarations.

+
source§

fn variables_type(&self, _oracle: &dyn CodeOracle) -> VariablesType

The name of the type as it’s represented in the Variables object. +The string return may be used to combine with an indentifier, e.g. a Variables method name.

+
source§

fn literal( + &self, + oracle: &dyn CodeOracle, + ctx: &dyn Display, + renderer: &dyn LiteralRenderer, + literal: &Value +) -> String

A representation of the given literal for this type. +N.B. Literal is aliased from serde_json::Value.

+
source§

fn property_getter( + &self, + oracle: &dyn CodeOracle, + vars: &dyn Display, + prop: &dyn Display, + default: &dyn Display +) -> String

The language specific expression that gets a value of the prop from the vars object, +and fallbacks to the default value. Read more
source§

fn value_getter( + &self, + oracle: &dyn CodeOracle, + vars: &dyn Display, + prop: &dyn Display +) -> String

The expression needed to get a value out of a Variables objectm with the prop key. Read more
source§

fn value_mapper(&self, oracle: &dyn CodeOracle) -> Option<String>

The method call here will use the create_transform to transform the value coming out of +the Variables object into the desired type. Read more
source§

fn value_merger( + &self, + oracle: &dyn CodeOracle, + default: &dyn Display +) -> Option<String>

The method call to merge the value with the defaults. Read more
source§

fn create_transform(&self, oracle: &dyn CodeOracle) -> Option<String>

A function handle that is capable of turning the variables type to the TypeRef type.
source§

fn merge_transform(&self, oracle: &dyn CodeOracle) -> Option<String>

A function handle that is capable of merging two instances of the same class. By default, this is None.
source§

fn defaults_type(&self, oracle: &dyn CodeOracle) -> String

source§

fn defaults_mapper( + &self, + oracle: &dyn CodeOracle, + value: &dyn Display, + vars: &dyn Display +) -> Option<String>

source§

fn preference_getter( + &self, + _oracle: &dyn CodeOracle, + _prefs: &dyn Display, + _pref_key: &dyn Display +) -> Option<String>

source§

fn as_json(&self, oracle: &dyn CodeOracle, prop: &dyn Display) -> String

Call from the template
source§

fn as_json_transform( + &self, + _oracle: &dyn CodeOracle, + _prop: &dyn Display +) -> Option<String>

Implement these in different code types, and call recursively from different code types.
source§

fn is_resource_id(&self, _literal: &Value) -> bool

source§

fn helper_code(&self, _oracle: &dyn CodeOracle) -> Option<String>

Optional helper code to make this type work. +This might include functions to patch a default value with another.
source§

fn imports(&self, _oracle: &dyn CodeOracle) -> Option<Vec<String>>

A list of imports that are needed if this type is in use. +Classes are imported exactly once.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/swift/gen_structs/structural/struct.OptionalCodeType.html b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/structural/struct.OptionalCodeType.html new file mode 100644 index 0000000000..6b7b41c1f3 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/swift/gen_structs/structural/struct.OptionalCodeType.html @@ -0,0 +1,69 @@ +OptionalCodeType in nimbus_fml::backends::swift::gen_structs::structural - Rust
pub(crate) struct OptionalCodeType {
+    inner: TypeRef,
+}

Fields§

§inner: TypeRef

Implementations§

source§

impl OptionalCodeType

source

pub(crate) fn new(inner: &TypeRef) -> Self

Trait Implementations§

source§

impl CodeType for OptionalCodeType

source§

fn type_label(&self, oracle: &dyn CodeOracle) -> String

The language specific label used to reference this type. This will be used in +method signatures and property declarations.

+
source§

fn property_getter( + &self, + oracle: &dyn CodeOracle, + vars: &dyn Display, + prop: &dyn Display, + default: &dyn Display +) -> String

The language specific expression that gets a value of the prop from the vars object.

+
source§

fn create_transform(&self, oracle: &dyn CodeOracle) -> Option<String>

The name of the type as it’s represented in the Variables object. +The string return may be used to combine with an indentifier, e.g. a Variables method name.

+
source§

fn variables_type(&self, oracle: &dyn CodeOracle) -> VariablesType

The name of the type as it’s represented in the Variables object. +The string return may be used to combine with an indentifier, e.g. a Variables method name.

+
source§

fn value_mapper(&self, oracle: &dyn CodeOracle) -> Option<String>

The method call here will use the create_transform to transform the value coming out of +the Variables object into the desired type.

+
source§

fn value_merger( + &self, + oracle: &dyn CodeOracle, + default: &dyn Display +) -> Option<String>

The method call to merge the value with the defaults.

+

This may use the merge_transform.

+

If this returns None, no merging happens, and implicit null replacement happens.

+
source§

fn literal( + &self, + oracle: &dyn CodeOracle, + ctx: &dyn Display, + renderer: &dyn LiteralRenderer, + literal: &Value +) -> String

A representation of the given literal for this type. +N.B. Literal is aliased from serde_json::Value.

+
source§

fn value_getter( + &self, + oracle: &dyn CodeOracle, + vars: &dyn Display, + prop: &dyn Display +) -> String

The expression needed to get a value out of a Variables objectm with the prop key. Read more
source§

fn defaults_type(&self, oracle: &dyn CodeOracle) -> String

source§

fn defaults_mapper( + &self, + oracle: &dyn CodeOracle, + value: &dyn Display, + vars: &dyn Display +) -> Option<String>

source§

fn merge_transform(&self, _oracle: &dyn CodeOracle) -> Option<String>

A function handle that is capable of merging two instances of the same class. By default, this is None.
source§

fn preference_getter( + &self, + _oracle: &dyn CodeOracle, + _prefs: &dyn Display, + _pref_key: &dyn Display +) -> Option<String>

source§

fn as_json(&self, oracle: &dyn CodeOracle, prop: &dyn Display) -> String

Call from the template
source§

fn as_json_transform( + &self, + _oracle: &dyn CodeOracle, + _prop: &dyn Display +) -> Option<String>

Implement these in different code types, and call recursively from different code types.
source§

fn is_resource_id(&self, _literal: &Value) -> bool

source§

fn helper_code(&self, _oracle: &dyn CodeOracle) -> Option<String>

Optional helper code to make this type work. +This might include functions to patch a default value with another.
source§

fn imports(&self, _oracle: &dyn CodeOracle) -> Option<Vec<String>>

A list of imports that are needed if this type is in use. +Classes are imported exactly once.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/swift/index.html b/book/rust-docs/nimbus_fml/backends/swift/index.html new file mode 100644 index 0000000000..c208b0af66 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/swift/index.html @@ -0,0 +1 @@ +nimbus_fml::backends::swift - Rust
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/swift/sidebar-items.js b/book/rust-docs/nimbus_fml/backends/swift/sidebar-items.js new file mode 100644 index 0000000000..3e10ca38c8 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/swift/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["generate_struct"],"mod":["gen_structs"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/trait.CodeDeclaration.html b/book/rust-docs/nimbus_fml/backends/trait.CodeDeclaration.html new file mode 100644 index 0000000000..d36020654b --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/trait.CodeDeclaration.html @@ -0,0 +1,17 @@ +CodeDeclaration in nimbus_fml::backends - Rust
pub trait CodeDeclaration {
+    // Provided methods
+    fn imports(&self, _oracle: &dyn CodeOracle) -> Option<Vec<String>> { ... }
+    fn initialization_code(&self, _oracle: &dyn CodeOracle) -> Option<String> { ... }
+    fn definition_code(&self, _oracle: &dyn CodeOracle) -> Option<String> { ... }
+}
Expand description

A trait that is able to render a declaration about a particular member declared in +the FeatureManifest. +Like CodeType, it can render declaration code and imports. +All methods are optional, and there is no requirement that the trait be used for a particular +member. Thus, it can also be useful for conditionally rendering code.

+

Provided Methods§

source

fn imports(&self, _oracle: &dyn CodeOracle) -> Option<Vec<String>>

A list of imports that are needed if this type is in use. +Classes are imported exactly once.

+
source

fn initialization_code(&self, _oracle: &dyn CodeOracle) -> Option<String>

Code (one or more statements) that is run on start-up of the library, +but before the client code has access to it.

+
source

fn definition_code(&self, _oracle: &dyn CodeOracle) -> Option<String>

Code which represents this member. e.g. the foreign language class definition for +a given Object type.

+

Implementors§

source§

impl CodeDeclaration for nimbus_fml::backends::kotlin::gen_structs::enum_::EnumCodeDeclaration

source§

impl CodeDeclaration for nimbus_fml::backends::kotlin::gen_structs::feature::FeatureCodeDeclaration

source§

impl CodeDeclaration for nimbus_fml::backends::kotlin::gen_structs::imports::ImportedModuleInitialization<'_>

source§

impl CodeDeclaration for nimbus_fml::backends::kotlin::gen_structs::object::ObjectCodeDeclaration

source§

impl CodeDeclaration for nimbus_fml::backends::kotlin::gen_structs::object::ObjectRuntime

source§

impl CodeDeclaration for nimbus_fml::backends::swift::gen_structs::enum_::EnumCodeDeclaration

source§

impl CodeDeclaration for nimbus_fml::backends::swift::gen_structs::feature::FeatureCodeDeclaration

source§

impl CodeDeclaration for nimbus_fml::backends::swift::gen_structs::imports::ImportedModuleInitialization<'_>

source§

impl CodeDeclaration for nimbus_fml::backends::swift::gen_structs::object::ObjectCodeDeclaration

source§

impl CodeDeclaration for nimbus_fml::backends::swift::gen_structs::object::ObjectRuntime

\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/trait.CodeOracle.html b/book/rust-docs/nimbus_fml/backends/trait.CodeOracle.html new file mode 100644 index 0000000000..c52d2a94ea --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/trait.CodeOracle.html @@ -0,0 +1,8 @@ +CodeOracle in nimbus_fml::backends - Rust
pub trait CodeOracle {
+    // Required method
+    fn find(&self, type_: &TypeRef) -> Box<dyn CodeType>;
+}
Expand description

An object to look up a foreign language code specific renderer for a given type used. +Every TypeRef referred to in the crate::intermediate_representation::FeatureManifest should map to a corresponding +CodeType.

+

The mapping may be opaque, but the oracle always knows the answer.

+

Required Methods§

source

fn find(&self, type_: &TypeRef) -> Box<dyn CodeType>

Implementors§

source§

impl CodeOracle for nimbus_fml::backends::kotlin::gen_structs::ConcreteCodeOracle

source§

impl CodeOracle for nimbus_fml::backends::swift::gen_structs::ConcreteCodeOracle

\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/trait.CodeType.html b/book/rust-docs/nimbus_fml/backends/trait.CodeType.html new file mode 100644 index 0000000000..69d3c5086a --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/trait.CodeType.html @@ -0,0 +1,134 @@ +CodeType in nimbus_fml::backends - Rust
pub trait CodeType {
+
Show 17 methods // Required methods + fn type_label(&self, oracle: &dyn CodeOracle) -> String; + fn property_getter( + &self, + oracle: &dyn CodeOracle, + vars: &dyn Display, + prop: &dyn Display, + default: &dyn Display + ) -> String; + fn value_getter( + &self, + oracle: &dyn CodeOracle, + vars: &dyn Display, + prop: &dyn Display + ) -> String; + fn variables_type(&self, _oracle: &dyn CodeOracle) -> VariablesType; + fn literal( + &self, + oracle: &dyn CodeOracle, + ctx: &dyn Display, + renderer: &dyn LiteralRenderer, + literal: &Value + ) -> String; + + // Provided methods + fn value_mapper(&self, _oracle: &dyn CodeOracle) -> Option<String> { ... } + fn value_merger( + &self, + _oracle: &dyn CodeOracle, + _default: &dyn Display + ) -> Option<String> { ... } + fn create_transform(&self, _oracle: &dyn CodeOracle) -> Option<String> { ... } + fn merge_transform(&self, _oracle: &dyn CodeOracle) -> Option<String> { ... } + fn defaults_type(&self, oracle: &dyn CodeOracle) -> String { ... } + fn defaults_mapper( + &self, + _oracle: &dyn CodeOracle, + _value: &dyn Display, + _vars: &dyn Display + ) -> Option<String> { ... } + fn preference_getter( + &self, + _oracle: &dyn CodeOracle, + _prefs: &dyn Display, + _pref_key: &dyn Display + ) -> Option<String> { ... } + fn as_json(&self, oracle: &dyn CodeOracle, prop: &dyn Display) -> String { ... } + fn as_json_transform( + &self, + _oracle: &dyn CodeOracle, + _prop: &dyn Display + ) -> Option<String> { ... } + fn is_resource_id(&self, _literal: &Value) -> bool { ... } + fn helper_code(&self, _oracle: &dyn CodeOracle) -> Option<String> { ... } + fn imports(&self, _oracle: &dyn CodeOracle) -> Option<Vec<String>> { ... } +
}
Expand description

A Trait to emit foreign language code to handle referenced types. +A type which is specified in the FML (i.e. a type that a variable declares itself of) +will have a CodeDeclaration as well, but for types used e.g. primitive types, Strings, etc +only a CodeType is needed.

+

This includes generating an literal of the type from the right type of JSON and +expressions to get a property from the JSON backed Variables object.

+

Required Methods§

source

fn type_label(&self, oracle: &dyn CodeOracle) -> String

The language specific label used to reference this type. This will be used in +method signatures and property declarations.

+
source

fn property_getter( + &self, + oracle: &dyn CodeOracle, + vars: &dyn Display, + prop: &dyn Display, + default: &dyn Display +) -> String

The language specific expression that gets a value of the prop from the vars object, +and fallbacks to the default value.

+

/// All the propertis follow this general pattern:

+
variables?.{{ value_getter }}
+        ?.{{ value_mapper }}
+        ?.{{ value_merger }}
+        ?: {{ default_fallback}}
+
+

In the case of structural types and objects, value_mapper and value_merger +become mutually recursive to generate quite complicated properties.

+
source

fn value_getter( + &self, + oracle: &dyn CodeOracle, + vars: &dyn Display, + prop: &dyn Display +) -> String

The expression needed to get a value out of a Variables objectm with the prop key.

+

This will almost certainly use the variables_type method to determine which method to use. +e.g. vars?.getString("prop")

+

The value_mapper will be used to transform this value into the required value.

+
source

fn variables_type(&self, _oracle: &dyn CodeOracle) -> VariablesType

The name of the type as it’s represented in the Variables object. +The string return may be used to combine with an indentifier, e.g. a Variables method name.

+
source

fn literal( + &self, + oracle: &dyn CodeOracle, + ctx: &dyn Display, + renderer: &dyn LiteralRenderer, + literal: &Value +) -> String

A representation of the given literal for this type. +N.B. Literal is aliased from serde_json::Value.

+

Provided Methods§

source

fn value_mapper(&self, _oracle: &dyn CodeOracle) -> Option<String>

The method call here will use the create_transform to transform the value coming out of +the Variables object into the desired type.

+

e.g. a string will need to be transformed into an enum, so the value mapper in Kotlin will be +let(Enum::enumValue).

+

If the value is None, then no mapper is used.

+
source

fn value_merger( + &self, + _oracle: &dyn CodeOracle, + _default: &dyn Display +) -> Option<String>

The method call to merge the value with the defaults.

+

This may use the merge_transform.

+

If this returns None, no merging happens, and implicit null replacement happens.

+
source

fn create_transform(&self, _oracle: &dyn CodeOracle) -> Option<String>

A function handle that is capable of turning the variables type to the TypeRef type.

+
source

fn merge_transform(&self, _oracle: &dyn CodeOracle) -> Option<String>

A function handle that is capable of merging two instances of the same class. By default, this is None.

+
source

fn defaults_type(&self, oracle: &dyn CodeOracle) -> String

source

fn defaults_mapper( + &self, + _oracle: &dyn CodeOracle, + _value: &dyn Display, + _vars: &dyn Display +) -> Option<String>

source

fn preference_getter( + &self, + _oracle: &dyn CodeOracle, + _prefs: &dyn Display, + _pref_key: &dyn Display +) -> Option<String>

source

fn as_json(&self, oracle: &dyn CodeOracle, prop: &dyn Display) -> String

Call from the template

+
source

fn as_json_transform( + &self, + _oracle: &dyn CodeOracle, + _prop: &dyn Display +) -> Option<String>

Implement these in different code types, and call recursively from different code types.

+
source

fn is_resource_id(&self, _literal: &Value) -> bool

source

fn helper_code(&self, _oracle: &dyn CodeOracle) -> Option<String>

Optional helper code to make this type work. +This might include functions to patch a default value with another.

+
source

fn imports(&self, _oracle: &dyn CodeOracle) -> Option<Vec<String>>

A list of imports that are needed if this type is in use. +Classes are imported exactly once.

+

Implementors§

source§

impl CodeType for nimbus_fml::backends::kotlin::gen_structs::bundled::ImageCodeType

source§

impl CodeType for nimbus_fml::backends::kotlin::gen_structs::bundled::TextCodeType

source§

impl CodeType for nimbus_fml::backends::kotlin::gen_structs::enum_::EnumCodeType

source§

impl CodeType for nimbus_fml::backends::kotlin::gen_structs::object::ObjectCodeType

source§

impl CodeType for nimbus_fml::backends::kotlin::gen_structs::primitives::BooleanCodeType

source§

impl CodeType for nimbus_fml::backends::kotlin::gen_structs::primitives::IntCodeType

source§

impl CodeType for nimbus_fml::backends::kotlin::gen_structs::primitives::StringCodeType

source§

impl CodeType for nimbus_fml::backends::kotlin::gen_structs::structural::ListCodeType

source§

impl CodeType for nimbus_fml::backends::kotlin::gen_structs::structural::MapCodeType

source§

impl CodeType for nimbus_fml::backends::kotlin::gen_structs::structural::OptionalCodeType

source§

impl CodeType for nimbus_fml::backends::swift::gen_structs::bundled::ImageCodeType

source§

impl CodeType for nimbus_fml::backends::swift::gen_structs::bundled::TextCodeType

source§

impl CodeType for nimbus_fml::backends::swift::gen_structs::enum_::EnumCodeType

source§

impl CodeType for nimbus_fml::backends::swift::gen_structs::object::ObjectCodeType

source§

impl CodeType for nimbus_fml::backends::swift::gen_structs::primitives::BooleanCodeType

source§

impl CodeType for nimbus_fml::backends::swift::gen_structs::primitives::IntCodeType

source§

impl CodeType for nimbus_fml::backends::swift::gen_structs::primitives::StringCodeType

source§

impl CodeType for nimbus_fml::backends::swift::gen_structs::structural::ListCodeType

source§

impl CodeType for nimbus_fml::backends::swift::gen_structs::structural::MapCodeType

source§

impl CodeType for nimbus_fml::backends::swift::gen_structs::structural::OptionalCodeType

\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/trait.LiteralRenderer.html b/book/rust-docs/nimbus_fml/backends/trait.LiteralRenderer.html new file mode 100644 index 0000000000..286a8abc6a --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/trait.LiteralRenderer.html @@ -0,0 +1,18 @@ +LiteralRenderer in nimbus_fml::backends - Rust
pub trait LiteralRenderer {
+    // Required method
+    fn literal(
+        &self,
+        _oracle: &dyn CodeOracle,
+        _typ: &TypeRef,
+        value: &Value,
+        ctx: &dyn Display
+    ) -> String;
+}

Required Methods§

source

fn literal( + &self, + _oracle: &dyn CodeOracle, + _typ: &TypeRef, + value: &Value, + ctx: &dyn Display +) -> String

Implementors§

source§

impl LiteralRenderer for nimbus_fml::backends::kotlin::gen_structs::feature::FeatureCodeDeclaration

source§

impl LiteralRenderer for nimbus_fml::backends::kotlin::gen_structs::imports::ImportedModuleInitialization<'_>

source§

impl LiteralRenderer for nimbus_fml::backends::kotlin::gen_structs::object::ObjectCodeDeclaration

source§

impl LiteralRenderer for nimbus_fml::backends::swift::gen_structs::feature::FeatureCodeDeclaration

source§

impl LiteralRenderer for nimbus_fml::backends::swift::gen_structs::imports::ImportedModuleInitialization<'_>

source§

impl LiteralRenderer for nimbus_fml::backends::swift::gen_structs::object::ObjectCodeDeclaration

source§

impl<T, C> LiteralRenderer for Twhere + T: Deref<Target = C>, + C: LiteralRenderer,

\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/backends/type.TypeIdentifier.html b/book/rust-docs/nimbus_fml/backends/type.TypeIdentifier.html new file mode 100644 index 0000000000..42fe53d0a2 --- /dev/null +++ b/book/rust-docs/nimbus_fml/backends/type.TypeIdentifier.html @@ -0,0 +1 @@ +TypeIdentifier in nimbus_fml::backends - Rust

Type Definition nimbus_fml::backends::TypeIdentifier

source ·
pub type TypeIdentifier = TypeRef;
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/command_line/commands/enum.CliCmd.html b/book/rust-docs/nimbus_fml/command_line/commands/enum.CliCmd.html new file mode 100644 index 0000000000..c8b3dca6fa --- /dev/null +++ b/book/rust-docs/nimbus_fml/command_line/commands/enum.CliCmd.html @@ -0,0 +1,23 @@ +CliCmd in nimbus_fml::command_line::commands - Rust
pub(crate) enum CliCmd {
+    Generate(GenerateStructCmd),
+    GenerateExperimenter(GenerateExperimenterManifestCmd),
+    GenerateSingleFileManifest(GenerateSingleFileManifestCmd),
+    FetchFile(LoaderConfig, String),
+    Validate(ValidateCmd),
+    PrintChannels(PrintChannelsCmd),
+}

Variants§

§

Generate(GenerateStructCmd)

§

GenerateExperimenter(GenerateExperimenterManifestCmd)

§

GenerateSingleFileManifest(GenerateSingleFileManifestCmd)

§

FetchFile(LoaderConfig, String)

§

Validate(ValidateCmd)

§

PrintChannels(PrintChannelsCmd)

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/command_line/commands/index.html b/book/rust-docs/nimbus_fml/command_line/commands/index.html new file mode 100644 index 0000000000..e2b3630406 --- /dev/null +++ b/book/rust-docs/nimbus_fml/command_line/commands/index.html @@ -0,0 +1 @@ +nimbus_fml::command_line::commands - Rust
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/command_line/commands/sidebar-items.js b/book/rust-docs/nimbus_fml/command_line/commands/sidebar-items.js new file mode 100644 index 0000000000..33fb273dc4 --- /dev/null +++ b/book/rust-docs/nimbus_fml/command_line/commands/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["CliCmd"],"struct":["GenerateExperimenterManifestCmd","GenerateSingleFileManifestCmd","GenerateStructCmd","PrintChannelsCmd","ValidateCmd"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/command_line/commands/struct.GenerateExperimenterManifestCmd.html b/book/rust-docs/nimbus_fml/command_line/commands/struct.GenerateExperimenterManifestCmd.html new file mode 100644 index 0000000000..2008bc481e --- /dev/null +++ b/book/rust-docs/nimbus_fml/command_line/commands/struct.GenerateExperimenterManifestCmd.html @@ -0,0 +1,22 @@ +GenerateExperimenterManifestCmd in nimbus_fml::command_line::commands - Rust
pub(crate) struct GenerateExperimenterManifestCmd {
+    pub(crate) manifest: String,
+    pub(crate) output: PathBuf,
+    pub(crate) language: TargetLanguage,
+    pub(crate) load_from_ir: bool,
+    pub(crate) loader: LoaderConfig,
+}

Fields§

§manifest: String§output: PathBuf§language: TargetLanguage§load_from_ir: bool§loader: LoaderConfig

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/command_line/commands/struct.GenerateSingleFileManifestCmd.html b/book/rust-docs/nimbus_fml/command_line/commands/struct.GenerateSingleFileManifestCmd.html new file mode 100644 index 0000000000..50351bff20 --- /dev/null +++ b/book/rust-docs/nimbus_fml/command_line/commands/struct.GenerateSingleFileManifestCmd.html @@ -0,0 +1,21 @@ +GenerateSingleFileManifestCmd in nimbus_fml::command_line::commands - Rust
pub(crate) struct GenerateSingleFileManifestCmd {
+    pub(crate) manifest: String,
+    pub(crate) output: PathBuf,
+    pub(crate) channel: String,
+    pub(crate) loader: LoaderConfig,
+}

Fields§

§manifest: String§output: PathBuf§channel: String§loader: LoaderConfig

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/command_line/commands/struct.GenerateStructCmd.html b/book/rust-docs/nimbus_fml/command_line/commands/struct.GenerateStructCmd.html new file mode 100644 index 0000000000..74e12cd4f1 --- /dev/null +++ b/book/rust-docs/nimbus_fml/command_line/commands/struct.GenerateStructCmd.html @@ -0,0 +1,24 @@ +GenerateStructCmd in nimbus_fml::command_line::commands - Rust
pub(crate) struct GenerateStructCmd {
+    pub(crate) manifest: String,
+    pub(crate) output: PathBuf,
+    pub(crate) language: TargetLanguage,
+    pub(crate) load_from_ir: bool,
+    pub(crate) channel: String,
+    pub(crate) loader: LoaderConfig,
+}

Fields§

§manifest: String§output: PathBuf§language: TargetLanguage§load_from_ir: bool§channel: String§loader: LoaderConfig

Trait Implementations§

source§

impl Clone for GenerateStructCmd

source§

fn clone(&self) -> GenerateStructCmd

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/command_line/commands/struct.PrintChannelsCmd.html b/book/rust-docs/nimbus_fml/command_line/commands/struct.PrintChannelsCmd.html new file mode 100644 index 0000000000..0b12ab1ba2 --- /dev/null +++ b/book/rust-docs/nimbus_fml/command_line/commands/struct.PrintChannelsCmd.html @@ -0,0 +1,20 @@ +PrintChannelsCmd in nimbus_fml::command_line::commands - Rust
pub(crate) struct PrintChannelsCmd {
+    pub(crate) manifest: String,
+    pub(crate) loader: LoaderConfig,
+    pub(crate) as_json: bool,
+}

Fields§

§manifest: String§loader: LoaderConfig§as_json: bool

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/command_line/commands/struct.ValidateCmd.html b/book/rust-docs/nimbus_fml/command_line/commands/struct.ValidateCmd.html new file mode 100644 index 0000000000..e4aa6ea370 --- /dev/null +++ b/book/rust-docs/nimbus_fml/command_line/commands/struct.ValidateCmd.html @@ -0,0 +1,19 @@ +ValidateCmd in nimbus_fml::command_line::commands - Rust
pub(crate) struct ValidateCmd {
+    pub(crate) manifest: String,
+    pub(crate) loader: LoaderConfig,
+}

Fields§

§manifest: String§loader: LoaderConfig

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/command_line/constant.RELEASE_CHANNEL.html b/book/rust-docs/nimbus_fml/command_line/constant.RELEASE_CHANNEL.html new file mode 100644 index 0000000000..adba68bdec --- /dev/null +++ b/book/rust-docs/nimbus_fml/command_line/constant.RELEASE_CHANNEL.html @@ -0,0 +1 @@ +RELEASE_CHANNEL in nimbus_fml::command_line - Rust
const RELEASE_CHANNEL: &str = "release";
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/command_line/fn.create_generate_command_experimenter_from_cli.html b/book/rust-docs/nimbus_fml/command_line/fn.create_generate_command_experimenter_from_cli.html new file mode 100644 index 0000000000..209e65d611 --- /dev/null +++ b/book/rust-docs/nimbus_fml/command_line/fn.create_generate_command_experimenter_from_cli.html @@ -0,0 +1,4 @@ +create_generate_command_experimenter_from_cli in nimbus_fml::command_line - Rust
fn create_generate_command_experimenter_from_cli(
+    matches: &ArgMatches<'_>,
+    cwd: &Path
+) -> Result<GenerateExperimenterManifestCmd>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/command_line/fn.create_generate_command_from_cli.html b/book/rust-docs/nimbus_fml/command_line/fn.create_generate_command_from_cli.html new file mode 100644 index 0000000000..22b99659e3 --- /dev/null +++ b/book/rust-docs/nimbus_fml/command_line/fn.create_generate_command_from_cli.html @@ -0,0 +1,4 @@ +create_generate_command_from_cli in nimbus_fml::command_line - Rust
fn create_generate_command_from_cli(
+    matches: &ArgMatches<'_>,
+    cwd: &Path
+) -> Result<GenerateStructCmd>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/command_line/fn.create_loader.html b/book/rust-docs/nimbus_fml/command_line/fn.create_loader.html new file mode 100644 index 0000000000..e554b7691d --- /dev/null +++ b/book/rust-docs/nimbus_fml/command_line/fn.create_loader.html @@ -0,0 +1 @@ +create_loader in nimbus_fml::command_line - Rust
fn create_loader(matches: &ArgMatches<'_>, cwd: &Path) -> Result<LoaderConfig>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/command_line/fn.create_print_channels_from_cli.html b/book/rust-docs/nimbus_fml/command_line/fn.create_print_channels_from_cli.html new file mode 100644 index 0000000000..8150ad073a --- /dev/null +++ b/book/rust-docs/nimbus_fml/command_line/fn.create_print_channels_from_cli.html @@ -0,0 +1,4 @@ +create_print_channels_from_cli in nimbus_fml::command_line - Rust
fn create_print_channels_from_cli(
+    matches: &ArgMatches<'_>,
+    cwd: &Path
+) -> Result<PrintChannelsCmd>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/command_line/fn.create_single_file_from_cli.html b/book/rust-docs/nimbus_fml/command_line/fn.create_single_file_from_cli.html new file mode 100644 index 0000000000..c439b8c8b7 --- /dev/null +++ b/book/rust-docs/nimbus_fml/command_line/fn.create_single_file_from_cli.html @@ -0,0 +1,4 @@ +create_single_file_from_cli in nimbus_fml::command_line - Rust
fn create_single_file_from_cli(
+    matches: &ArgMatches<'_>,
+    cwd: &Path
+) -> Result<GenerateSingleFileManifestCmd>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/command_line/fn.create_validate_command_from_cli.html b/book/rust-docs/nimbus_fml/command_line/fn.create_validate_command_from_cli.html new file mode 100644 index 0000000000..4c83a472ab --- /dev/null +++ b/book/rust-docs/nimbus_fml/command_line/fn.create_validate_command_from_cli.html @@ -0,0 +1,4 @@ +create_validate_command_from_cli in nimbus_fml::command_line - Rust
fn create_validate_command_from_cli(
+    matches: &ArgMatches<'_>,
+    cwd: &Path
+) -> Result<ValidateCmd>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/command_line/fn.do_main.html b/book/rust-docs/nimbus_fml/command_line/fn.do_main.html new file mode 100644 index 0000000000..e55ef2a7ec --- /dev/null +++ b/book/rust-docs/nimbus_fml/command_line/fn.do_main.html @@ -0,0 +1,3 @@ +do_main in nimbus_fml::command_line - Rust
pub fn do_main<I, T>(args: I, cwd: &Path) -> Result<()>where
+    I: IntoIterator<Item = T>,
+    T: Into<OsString> + Clone,
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/command_line/fn.file_path.html b/book/rust-docs/nimbus_fml/command_line/fn.file_path.html new file mode 100644 index 0000000000..ba6423604a --- /dev/null +++ b/book/rust-docs/nimbus_fml/command_line/fn.file_path.html @@ -0,0 +1 @@ +file_path in nimbus_fml::command_line - Rust
fn file_path(name: &str, args: &ArgMatches<'_>, cwd: &Path) -> Result<PathBuf>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/command_line/fn.get_command_from_cli.html b/book/rust-docs/nimbus_fml/command_line/fn.get_command_from_cli.html new file mode 100644 index 0000000000..1ff476a389 --- /dev/null +++ b/book/rust-docs/nimbus_fml/command_line/fn.get_command_from_cli.html @@ -0,0 +1,3 @@ +get_command_from_cli in nimbus_fml::command_line - Rust
fn get_command_from_cli<I, T>(args: I, cwd: &Path) -> Result<CliCmd>where
+    I: IntoIterator<Item = T>,
+    T: Into<OsString> + Clone,
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/command_line/fn.input_file.html b/book/rust-docs/nimbus_fml/command_line/fn.input_file.html new file mode 100644 index 0000000000..a13187387e --- /dev/null +++ b/book/rust-docs/nimbus_fml/command_line/fn.input_file.html @@ -0,0 +1 @@ +input_file in nimbus_fml::command_line - Rust
fn input_file(args: &ArgMatches<'_>) -> Result<String>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/command_line/fn.process_command.html b/book/rust-docs/nimbus_fml/command_line/fn.process_command.html new file mode 100644 index 0000000000..9d4fe6d0bf --- /dev/null +++ b/book/rust-docs/nimbus_fml/command_line/fn.process_command.html @@ -0,0 +1 @@ +process_command in nimbus_fml::command_line - Rust
fn process_command(cmd: &CliCmd) -> Result<()>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/command_line/index.html b/book/rust-docs/nimbus_fml/command_line/index.html new file mode 100644 index 0000000000..faec802454 --- /dev/null +++ b/book/rust-docs/nimbus_fml/command_line/index.html @@ -0,0 +1 @@ +nimbus_fml::command_line - Rust
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/command_line/sidebar-items.js b/book/rust-docs/nimbus_fml/command_line/sidebar-items.js new file mode 100644 index 0000000000..fcc1f7ba6e --- /dev/null +++ b/book/rust-docs/nimbus_fml/command_line/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"constant":["RELEASE_CHANNEL"],"fn":["create_generate_command_experimenter_from_cli","create_generate_command_from_cli","create_loader","create_print_channels_from_cli","create_single_file_from_cli","create_validate_command_from_cli","do_main","file_path","get_command_from_cli","input_file","process_command"],"mod":["commands","workflows"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/command_line/workflows/constant.MATCHING_FML_EXTENSION.html b/book/rust-docs/nimbus_fml/command_line/workflows/constant.MATCHING_FML_EXTENSION.html new file mode 100644 index 0000000000..a97a24afba --- /dev/null +++ b/book/rust-docs/nimbus_fml/command_line/workflows/constant.MATCHING_FML_EXTENSION.html @@ -0,0 +1,2 @@ +MATCHING_FML_EXTENSION in nimbus_fml::command_line::workflows - Rust
const MATCHING_FML_EXTENSION: &str = ".fml.yaml";
Expand description

Use this when recursively looking for files.

+
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/command_line/workflows/fn.fetch_file.html b/book/rust-docs/nimbus_fml/command_line/workflows/fn.fetch_file.html new file mode 100644 index 0000000000..824d44a550 --- /dev/null +++ b/book/rust-docs/nimbus_fml/command_line/workflows/fn.fetch_file.html @@ -0,0 +1 @@ +fetch_file in nimbus_fml::command_line::workflows - Rust
pub(crate) fn fetch_file(files: &LoaderConfig, nm: &str) -> Result<(), FMLError>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/command_line/workflows/fn.generate_experimenter_manifest.html b/book/rust-docs/nimbus_fml/command_line/workflows/fn.generate_experimenter_manifest.html new file mode 100644 index 0000000000..42f603bb79 --- /dev/null +++ b/book/rust-docs/nimbus_fml/command_line/workflows/fn.generate_experimenter_manifest.html @@ -0,0 +1,3 @@ +generate_experimenter_manifest in nimbus_fml::command_line::workflows - Rust
pub(crate) fn generate_experimenter_manifest(
+    cmd: &GenerateExperimenterManifestCmd
+) -> Result<(), FMLError>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/command_line/workflows/fn.generate_single_file_manifest.html b/book/rust-docs/nimbus_fml/command_line/workflows/fn.generate_single_file_manifest.html new file mode 100644 index 0000000000..4a0cfeeb9a --- /dev/null +++ b/book/rust-docs/nimbus_fml/command_line/workflows/fn.generate_single_file_manifest.html @@ -0,0 +1,3 @@ +generate_single_file_manifest in nimbus_fml::command_line::workflows - Rust
pub(crate) fn generate_single_file_manifest(
+    cmd: &GenerateSingleFileManifestCmd
+) -> Result<(), FMLError>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/command_line/workflows/fn.generate_struct.html b/book/rust-docs/nimbus_fml/command_line/workflows/fn.generate_struct.html new file mode 100644 index 0000000000..aa6768f6c5 --- /dev/null +++ b/book/rust-docs/nimbus_fml/command_line/workflows/fn.generate_struct.html @@ -0,0 +1 @@ +generate_struct in nimbus_fml::command_line::workflows - Rust
pub(crate) fn generate_struct(cmd: &GenerateStructCmd) -> Result<(), FMLError>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/command_line/workflows/fn.generate_struct_from_dir.html b/book/rust-docs/nimbus_fml/command_line/workflows/fn.generate_struct_from_dir.html new file mode 100644 index 0000000000..c5c2c4251d --- /dev/null +++ b/book/rust-docs/nimbus_fml/command_line/workflows/fn.generate_struct_from_dir.html @@ -0,0 +1,5 @@ +generate_struct_from_dir in nimbus_fml::command_line::workflows - Rust
fn generate_struct_from_dir(
+    files: &FileLoader,
+    cmd: &GenerateStructCmd,
+    cwd: &Path
+) -> Result<(), FMLError>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/command_line/workflows/fn.generate_struct_from_glob.html b/book/rust-docs/nimbus_fml/command_line/workflows/fn.generate_struct_from_glob.html new file mode 100644 index 0000000000..d5dbe4cf4e --- /dev/null +++ b/book/rust-docs/nimbus_fml/command_line/workflows/fn.generate_struct_from_glob.html @@ -0,0 +1,5 @@ +generate_struct_from_glob in nimbus_fml::command_line::workflows - Rust
fn generate_struct_from_glob(
+    files: &FileLoader,
+    cmd: &GenerateStructCmd,
+    pattern: &str
+) -> Result<(), FMLError>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/command_line/workflows/fn.generate_struct_from_ir.html b/book/rust-docs/nimbus_fml/command_line/workflows/fn.generate_struct_from_ir.html new file mode 100644 index 0000000000..0dfb05ede4 --- /dev/null +++ b/book/rust-docs/nimbus_fml/command_line/workflows/fn.generate_struct_from_ir.html @@ -0,0 +1,4 @@ +generate_struct_from_ir in nimbus_fml::command_line::workflows - Rust
fn generate_struct_from_ir(
+    ir: &FeatureManifest,
+    cmd: &GenerateStructCmd
+) -> Result<(), FMLError>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/command_line/workflows/fn.generate_struct_single.html b/book/rust-docs/nimbus_fml/command_line/workflows/fn.generate_struct_single.html new file mode 100644 index 0000000000..114ee3fb09 --- /dev/null +++ b/book/rust-docs/nimbus_fml/command_line/workflows/fn.generate_struct_single.html @@ -0,0 +1,5 @@ +generate_struct_single in nimbus_fml::command_line::workflows - Rust
fn generate_struct_single(
+    files: &FileLoader,
+    manifest_path: FilePath,
+    cmd: &GenerateStructCmd
+) -> Result<(), FMLError>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/command_line/workflows/fn.load_feature_manifest.html b/book/rust-docs/nimbus_fml/command_line/workflows/fn.load_feature_manifest.html new file mode 100644 index 0000000000..3bbb62406a --- /dev/null +++ b/book/rust-docs/nimbus_fml/command_line/workflows/fn.load_feature_manifest.html @@ -0,0 +1,6 @@ +load_feature_manifest in nimbus_fml::command_line::workflows - Rust
fn load_feature_manifest(
+    files: FileLoader,
+    path: FilePath,
+    load_from_ir: bool,
+    channel: Option<&str>
+) -> Result<FeatureManifest, FMLError>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/command_line/workflows/fn.output_err.html b/book/rust-docs/nimbus_fml/command_line/workflows/fn.output_err.html new file mode 100644 index 0000000000..3958f39a52 --- /dev/null +++ b/book/rust-docs/nimbus_fml/command_line/workflows/fn.output_err.html @@ -0,0 +1 @@ +output_err in nimbus_fml::command_line::workflows - Rust
fn output_err(term: &Term, title: &str, detail: &str) -> Result<(), FMLError>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/command_line/workflows/fn.output_note.html b/book/rust-docs/nimbus_fml/command_line/workflows/fn.output_note.html new file mode 100644 index 0000000000..8dbb16335d --- /dev/null +++ b/book/rust-docs/nimbus_fml/command_line/workflows/fn.output_note.html @@ -0,0 +1 @@ +output_note in nimbus_fml::command_line::workflows - Rust
fn output_note(term: &Term, title: &str) -> Result<(), FMLError>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/command_line/workflows/fn.output_ok.html b/book/rust-docs/nimbus_fml/command_line/workflows/fn.output_ok.html new file mode 100644 index 0000000000..1d28e8a5d2 --- /dev/null +++ b/book/rust-docs/nimbus_fml/command_line/workflows/fn.output_ok.html @@ -0,0 +1 @@ +output_ok in nimbus_fml::command_line::workflows - Rust
fn output_ok(term: &Term, title: &str) -> Result<(), FMLError>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/command_line/workflows/fn.output_warn.html b/book/rust-docs/nimbus_fml/command_line/workflows/fn.output_warn.html new file mode 100644 index 0000000000..c29b5ace09 --- /dev/null +++ b/book/rust-docs/nimbus_fml/command_line/workflows/fn.output_warn.html @@ -0,0 +1 @@ +output_warn in nimbus_fml::command_line::workflows - Rust
fn output_warn(term: &Term, title: &str, detail: &str) -> Result<(), FMLError>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/command_line/workflows/fn.print_channels.html b/book/rust-docs/nimbus_fml/command_line/workflows/fn.print_channels.html new file mode 100644 index 0000000000..ee6c5d52a2 --- /dev/null +++ b/book/rust-docs/nimbus_fml/command_line/workflows/fn.print_channels.html @@ -0,0 +1 @@ +print_channels in nimbus_fml::command_line::workflows - Rust
pub(crate) fn print_channels(cmd: &PrintChannelsCmd) -> Result<(), FMLError>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/command_line/workflows/fn.validate.html b/book/rust-docs/nimbus_fml/command_line/workflows/fn.validate.html new file mode 100644 index 0000000000..30eaa801fc --- /dev/null +++ b/book/rust-docs/nimbus_fml/command_line/workflows/fn.validate.html @@ -0,0 +1 @@ +validate in nimbus_fml::command_line::workflows - Rust
pub(crate) fn validate(cmd: &ValidateCmd) -> Result<(), FMLError>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/command_line/workflows/index.html b/book/rust-docs/nimbus_fml/command_line/workflows/index.html new file mode 100644 index 0000000000..d96502687f --- /dev/null +++ b/book/rust-docs/nimbus_fml/command_line/workflows/index.html @@ -0,0 +1 @@ +nimbus_fml::command_line::workflows - Rust
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/command_line/workflows/sidebar-items.js b/book/rust-docs/nimbus_fml/command_line/workflows/sidebar-items.js new file mode 100644 index 0000000000..decb5393b7 --- /dev/null +++ b/book/rust-docs/nimbus_fml/command_line/workflows/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"constant":["MATCHING_FML_EXTENSION"],"fn":["fetch_file","generate_experimenter_manifest","generate_single_file_manifest","generate_struct","generate_struct_from_dir","generate_struct_from_glob","generate_struct_from_ir","generate_struct_single","load_feature_manifest","output_err","output_note","output_ok","output_warn","print_channels","validate"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/constant.SUPPORT_URL_LOADING.html b/book/rust-docs/nimbus_fml/constant.SUPPORT_URL_LOADING.html new file mode 100644 index 0000000000..2be2aa6f8c --- /dev/null +++ b/book/rust-docs/nimbus_fml/constant.SUPPORT_URL_LOADING.html @@ -0,0 +1 @@ +SUPPORT_URL_LOADING in nimbus_fml - Rust
pub(crate) const SUPPORT_URL_LOADING: bool = true;
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/defaults_merger/fn.collect_channel_defaults.html b/book/rust-docs/nimbus_fml/defaults_merger/fn.collect_channel_defaults.html new file mode 100644 index 0000000000..35249bfd8a --- /dev/null +++ b/book/rust-docs/nimbus_fml/defaults_merger/fn.collect_channel_defaults.html @@ -0,0 +1,23 @@ +collect_channel_defaults in nimbus_fml::defaults_merger - Rust
fn collect_channel_defaults(
+    defaults: &[DefaultBlock],
+    channels: &[String],
+    no_channel: &str
+) -> Result<HashMap<String, Value>, FMLError>
Expand description

Collects the channel defaults of the feature manifest +and merges them by channel

+

NOTE: defaults with no channel apply to all channels

+

Arguments

+ +

Returns

+

Returns a std::collections::HashMap<String, serde_json::Value> representing +the merged defaults. The key is the name of the channel and the value is the +merged json.

+

Errors

+

Will return errors in the following cases (not exhaustive):

+
    +
  • The defaults argument is not an array
  • +
  • There is a channel in the defaults argument that doesn’t +exist in the channels argument
  • +
+
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/defaults_merger/fn.merge_two_defaults.html b/book/rust-docs/nimbus_fml/defaults_merger/fn.merge_two_defaults.html new file mode 100644 index 0000000000..f1edb5623e --- /dev/null +++ b/book/rust-docs/nimbus_fml/defaults_merger/fn.merge_two_defaults.html @@ -0,0 +1,12 @@ +merge_two_defaults in nimbus_fml::defaults_merger - Rust
fn merge_two_defaults(old_default: &Value, new_default: &Value) -> Value
Expand description

Merges two serde_json::Values into one

+

Arguments:

+
    +
  • old_default: a reference to a serde_json::Value, that represents the old default
  • +
  • new_default: a reference to a serde_json::Value, that represents the new default, this takes +precedence over the old_default if they have conflicting fields
  • +
+

Returns

+

A merged serde_json::Value that contains all fields from old_default and new_default, merging +where there is a conflict. If the old_default and new_default are not both objects, this function +returns the new_default

+
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/defaults_merger/index.html b/book/rust-docs/nimbus_fml/defaults_merger/index.html new file mode 100644 index 0000000000..4fa57e30ab --- /dev/null +++ b/book/rust-docs/nimbus_fml/defaults_merger/index.html @@ -0,0 +1,2 @@ +nimbus_fml::defaults_merger - Rust

Structs

Functions

\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/defaults_merger/sidebar-items.js b/book/rust-docs/nimbus_fml/defaults_merger/sidebar-items.js new file mode 100644 index 0000000000..63bc54778d --- /dev/null +++ b/book/rust-docs/nimbus_fml/defaults_merger/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["collect_channel_defaults","merge_two_defaults"],"struct":["DefaultsMerger"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/defaults_merger/struct.DefaultsMerger.html b/book/rust-docs/nimbus_fml/defaults_merger/struct.DefaultsMerger.html new file mode 100644 index 0000000000..755d29a0f4 --- /dev/null +++ b/book/rust-docs/nimbus_fml/defaults_merger/struct.DefaultsMerger.html @@ -0,0 +1,100 @@ +DefaultsMerger in nimbus_fml::defaults_merger - Rust
pub struct DefaultsMerger<'object> {
+    objects: &'object BTreeMap<String, ObjectDef>,
+    supported_channels: Vec<String>,
+    channel: Option<String>,
+}

Fields§

§objects: &'object BTreeMap<String, ObjectDef>§supported_channels: Vec<String>§channel: Option<String>

Implementations§

source§

impl<'object> DefaultsMerger<'object>

source

pub fn new( + objects: &'object BTreeMap<String, ObjectDef>, + supported_channels: Vec<String>, + channel: Option<String> +) -> Self

source

fn collect_feature_defaults( + &self, + feature: &FeatureDef +) -> Result<Value, FMLError>

source

fn collect_object_defaults(&self, nm: &str) -> Result<Value, FMLError>

source

fn collect_prop_defaults( + &self, + typ: &TypeRef, + v: &Value +) -> Result<Option<Value>, FMLError>

source

fn collect_map_defaults( + &self, + v_type: &TypeRef, + obj: &Value +) -> Result<Value, FMLError>

source

pub fn merge_feature_defaults( + &self, + feature_def: &mut FeatureDef, + defaults: &Option<Vec<DefaultBlock>> +) -> Result<(), FMLError>

Transforms a feature definition with unmerged defaults into a feature +definition with its defaults merged.

+
How the algorithm works:
+

There are two types of defaults:

+
    +
  1. Field level defaults
  2. +
  3. Feature level defaults, that are listed by channel
  4. +
+

The algorithm gathers the field level defaults first, they are the base +defaults. Then, it gathers the feature level defaults and merges them by +calling collect_channel_defaults. Finally, it overwrites any common +defaults between the merged feature level defaults and the field level defaults

+
Example:
+

Assume we have the following feature manifest

+
 variables:
+  positive:
+  description: This is a positive button
+  type: Button
+  default:
+    {
+      "label": "Ok then",
+      "color": "blue"
+    }
+ default:
+     - channel: release
+     value: {
+       "positive": {
+         "color": "green"
+       }
+     }
+     - value: {
+     "positive": {
+       "alt-text": "Go Ahead!"
+     }
+}
+
+

The result of the algorithm would be a default that looks like:

+
variables:
+    positive:
+    default:
+    {
+        "label": "Ok then",
+        "color": "green",
+        "alt-text": "Go Ahead!"
+    }
+
+
+
    +
  • The label comes from the original field level default
  • +
  • The color comes from the release channel feature level default
  • +
  • The alt-text comes from the feature level default with no channel (that applies to all channels)
  • +
+
Arguments
+
    +
  • feature_def: a FeatureDef representing the feature definition to transform
  • +
  • channel: a Option<&String> representing the channel to merge back into the field variables
  • +
  • supported_channels: a [&[String]] representing the channels that are supported by the manifest +If the channel is None we default to using the release channel
  • +
+
Returns
+

Returns a transformed FeatureDef with its defaults merged

+

Auto Trait Implementations§

§

impl<'object> RefUnwindSafe for DefaultsMerger<'object>

§

impl<'object> Send for DefaultsMerger<'object>

§

impl<'object> Sync for DefaultsMerger<'object>

§

impl<'object> Unpin for DefaultsMerger<'object>

§

impl<'object> UnwindSafe for DefaultsMerger<'object>

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/error/enum.ClientError.html b/book/rust-docs/nimbus_fml/error/enum.ClientError.html new file mode 100644 index 0000000000..11ed12c3ce --- /dev/null +++ b/book/rust-docs/nimbus_fml/error/enum.ClientError.html @@ -0,0 +1,24 @@ +ClientError in nimbus_fml::error - Rust
pub enum ClientError {
+    InvalidFeatureConfig(String),
+    InvalidFeatureId(String),
+    InvalidFeatureValue(String),
+    JsonMergeError(String),
+}

Variants§

§

InvalidFeatureConfig(String)

§

InvalidFeatureId(String)

§

InvalidFeatureValue(String)

§

JsonMergeError(String)

Trait Implementations§

source§

impl Debug for ClientError

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Display for ClientError

source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Error for ClientError

1.30.0 · source§

fn source(&self) -> Option<&(dyn Error + 'static)>

The lower-level source of this error, if any. Read more
1.0.0 · source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type based access to context intended for error reports. Read more
source§

impl From<ClientError> for FMLError

source§

fn from(source: ClientError) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<E> Provider for Ewhere + E: Error + ?Sized,

source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (provide_any)
Data providers should implement this method to provide all values they are able to +provide by using demand. Read more
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/error/enum.FMLError.html b/book/rust-docs/nimbus_fml/error/enum.FMLError.html new file mode 100644 index 0000000000..292d6a98c1 --- /dev/null +++ b/book/rust-docs/nimbus_fml/error/enum.FMLError.html @@ -0,0 +1,42 @@ +FMLError in nimbus_fml::error - Rust
pub enum FMLError {
+
Show 18 variants IOError(Error), + JSONError(Error), + YAMLError(Error), + UrlError(ParseError), + EmailError(Error), + FetchError(Error), + InvalidPath(String), + TemplateProblem(Error), + Fatal(Error), + InternalError(&'static str), + ValidationError(String, String), + FeatureValidationError { + literals: Vec<String>, + path: String, + message: String, + }, + TypeParsingError(String), + InvalidChannelError(String, Vec<String>), + FMLModuleError(ModuleId, String), + CliError(String), + ClientError(ClientError), + InvalidFeatureError(String), +
}

Variants§

§

IOError(Error)

§

JSONError(Error)

§

YAMLError(Error)

§

UrlError(ParseError)

§

EmailError(Error)

§

FetchError(Error)

§

InvalidPath(String)

§

TemplateProblem(Error)

§

Fatal(Error)

§

InternalError(&'static str)

§

ValidationError(String, String)

§

FeatureValidationError

Fields

§literals: Vec<String>
§path: String
§message: String
§

TypeParsingError(String)

§

InvalidChannelError(String, Vec<String>)

§

FMLModuleError(ModuleId, String)

§

CliError(String)

§

ClientError(ClientError)

§

InvalidFeatureError(String)

Trait Implementations§

source§

impl Debug for FMLError

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Display for FMLError

source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Error for FMLError

source§

fn source(&self) -> Option<&(dyn Error + 'static)>

The lower-level source of this error, if any. Read more
1.0.0 · source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type based access to context intended for error reports. Read more
source§

impl From<ClientError> for FMLError

source§

fn from(source: ClientError) -> Self

Converts to this type from the input type.
source§

impl From<Error> for FMLError

source§

fn from(source: Error) -> Self

Converts to this type from the input type.
source§

impl From<Error> for FMLError

source§

fn from(source: Error) -> Self

Converts to this type from the input type.
source§

impl From<Error> for FMLError

source§

fn from(source: Error) -> Self

Converts to this type from the input type.
source§

impl From<Error> for FMLError

source§

fn from(source: Error) -> Self

Converts to this type from the input type.
source§

impl From<Error> for FMLError

source§

fn from(source: Error) -> Self

Converts to this type from the input type.
source§

impl From<Error> for FMLError

source§

fn from(source: Error) -> Self

Converts to this type from the input type.
source§

impl From<Error> for FMLError

source§

fn from(source: Error) -> Self

Converts to this type from the input type.
source§

impl From<ParseError> for FMLError

source§

fn from(source: ParseError) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<E> Provider for Ewhere + E: Error + ?Sized,

source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (provide_any)
Data providers should implement this method to provide all values they are able to +provide by using demand. Read more
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/error/fn.did_you_mean.html b/book/rust-docs/nimbus_fml/error/fn.did_you_mean.html new file mode 100644 index 0000000000..21fad31a92 --- /dev/null +++ b/book/rust-docs/nimbus_fml/error/fn.did_you_mean.html @@ -0,0 +1 @@ +did_you_mean in nimbus_fml::error - Rust

Function nimbus_fml::error::did_you_mean

source ·
pub(crate) fn did_you_mean(words: HashSet<String>) -> String
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/error/index.html b/book/rust-docs/nimbus_fml/error/index.html new file mode 100644 index 0000000000..d3123bd3fc --- /dev/null +++ b/book/rust-docs/nimbus_fml/error/index.html @@ -0,0 +1 @@ +nimbus_fml::error - Rust
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/error/sidebar-items.js b/book/rust-docs/nimbus_fml/error/sidebar-items.js new file mode 100644 index 0000000000..cace30147a --- /dev/null +++ b/book/rust-docs/nimbus_fml/error/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["ClientError","FMLError"],"fn":["did_you_mean"],"type":["Result"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/error/type.Result.html b/book/rust-docs/nimbus_fml/error/type.Result.html new file mode 100644 index 0000000000..33ea2a145a --- /dev/null +++ b/book/rust-docs/nimbus_fml/error/type.Result.html @@ -0,0 +1 @@ +Result in nimbus_fml::error - Rust

Type Definition nimbus_fml::error::Result

source ·
pub type Result<T, E = FMLError> = Result<T, E>;
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/fn.main.html b/book/rust-docs/nimbus_fml/fn.main.html new file mode 100644 index 0000000000..ffdfb50e33 --- /dev/null +++ b/book/rust-docs/nimbus_fml/fn.main.html @@ -0,0 +1 @@ +main in nimbus_fml - Rust

Function nimbus_fml::main

source ·
pub(crate) fn main() -> Result<()>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/frontend/index.html b/book/rust-docs/nimbus_fml/frontend/index.html new file mode 100644 index 0000000000..472d8f4dca --- /dev/null +++ b/book/rust-docs/nimbus_fml/frontend/index.html @@ -0,0 +1 @@ +nimbus_fml::frontend - Rust
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/frontend/sidebar-items.js b/book/rust-docs/nimbus_fml/frontend/sidebar-items.js new file mode 100644 index 0000000000..03b4a50497 --- /dev/null +++ b/book/rust-docs/nimbus_fml/frontend/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"struct":["AboutBlock","DefaultBlock","DocumentationLink","EnumBody","EnumVariantBody","FeatureBody","FeatureFieldBody","FeatureMetadata","FieldBody","ImportBlock","KotlinAboutBlock","ManifestFrontEnd","ObjectBody","SwiftAboutBlock","Types"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/frontend/struct.AboutBlock.html b/book/rust-docs/nimbus_fml/frontend/struct.AboutBlock.html new file mode 100644 index 0000000000..4810e24e2a --- /dev/null +++ b/book/rust-docs/nimbus_fml/frontend/struct.AboutBlock.html @@ -0,0 +1,28 @@ +AboutBlock in nimbus_fml::frontend - Rust
pub(crate) struct AboutBlock {
+    pub(crate) description: String,
+    pub(crate) kotlin_about: Option<KotlinAboutBlock>,
+    pub(crate) swift_about: Option<SwiftAboutBlock>,
+}

Fields§

§description: String§kotlin_about: Option<KotlinAboutBlock>§swift_about: Option<SwiftAboutBlock>

Implementations§

Trait Implementations§

source§

impl Clone for AboutBlock

source§

fn clone(&self) -> AboutBlock

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for AboutBlock

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for AboutBlock

source§

fn default() -> AboutBlock

Returns the “default value” for a type. Read more
source§

impl<'de> Deserialize<'de> for AboutBlock

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl PartialEq<AboutBlock> for AboutBlock

source§

fn eq(&self, other: &AboutBlock) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Serialize for AboutBlock

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more
source§

impl Eq for AboutBlock

source§

impl StructuralEq for AboutBlock

source§

impl StructuralPartialEq for AboutBlock

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<Q, K> Equivalent<K> for Qwhere + Q: Eq + ?Sized, + K: Borrow<Q> + ?Sized,

source§

fn equivalent(&self, key: &K) -> bool

Compare self to key and return true if they are equal.
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/frontend/struct.DefaultBlock.html b/book/rust-docs/nimbus_fml/frontend/struct.DefaultBlock.html new file mode 100644 index 0000000000..2cbd89cfc1 --- /dev/null +++ b/book/rust-docs/nimbus_fml/frontend/struct.DefaultBlock.html @@ -0,0 +1,25 @@ +DefaultBlock in nimbus_fml::frontend - Rust
pub struct DefaultBlock {
+    pub(crate) channel: Option<String>,
+    pub(crate) channels: Option<Vec<String>>,
+    pub(crate) value: Value,
+    pub(crate) targeting: Option<String>,
+}

Fields§

§channel: Option<String>§channels: Option<Vec<String>>§value: Value§targeting: Option<String>

Implementations§

Trait Implementations§

source§

impl Clone for DefaultBlock

source§

fn clone(&self) -> DefaultBlock

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for DefaultBlock

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl<'de> Deserialize<'de> for DefaultBlock

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl From<Value> for DefaultBlock

source§

fn from(value: Value) -> Self

Converts to this type from the input type.
source§

impl Serialize for DefaultBlock

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/frontend/struct.DocumentationLink.html b/book/rust-docs/nimbus_fml/frontend/struct.DocumentationLink.html new file mode 100644 index 0000000000..1ec74a1484 --- /dev/null +++ b/book/rust-docs/nimbus_fml/frontend/struct.DocumentationLink.html @@ -0,0 +1,27 @@ +DocumentationLink in nimbus_fml::frontend - Rust
pub(crate) struct DocumentationLink {
+    pub(crate) name: String,
+    pub(crate) url: Url,
+}

Fields§

§name: String§url: Url

Trait Implementations§

source§

fn clone(&self) -> DocumentationLink

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

fn eq(&self, other: &DocumentationLink) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

fn equivalent(&self, key: &K) -> bool

Compare self to key and return true if they are equal.
source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/frontend/struct.EnumBody.html b/book/rust-docs/nimbus_fml/frontend/struct.EnumBody.html new file mode 100644 index 0000000000..f598389c2a --- /dev/null +++ b/book/rust-docs/nimbus_fml/frontend/struct.EnumBody.html @@ -0,0 +1,23 @@ +EnumBody in nimbus_fml::frontend - Rust
pub(crate) struct EnumBody {
+    pub(crate) description: String,
+    pub(crate) variants: BTreeMap<String, EnumVariantBody>,
+}

Fields§

§description: String§variants: BTreeMap<String, EnumVariantBody>

Trait Implementations§

source§

impl Clone for EnumBody

source§

fn clone(&self) -> EnumBody

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for EnumBody

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl<'de> Deserialize<'de> for EnumBody

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl From<EnumDef> for EnumBody

source§

fn from(value: EnumDef) -> Self

Converts to this type from the input type.
source§

impl Serialize for EnumBody

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/frontend/struct.EnumVariantBody.html b/book/rust-docs/nimbus_fml/frontend/struct.EnumVariantBody.html new file mode 100644 index 0000000000..2aaed28af5 --- /dev/null +++ b/book/rust-docs/nimbus_fml/frontend/struct.EnumVariantBody.html @@ -0,0 +1,22 @@ +EnumVariantBody in nimbus_fml::frontend - Rust
pub(crate) struct EnumVariantBody {
+    pub(crate) description: String,
+}

Fields§

§description: String

Trait Implementations§

source§

impl Clone for EnumVariantBody

source§

fn clone(&self) -> EnumVariantBody

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for EnumVariantBody

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl<'de> Deserialize<'de> for EnumVariantBody

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl From<VariantDef> for EnumVariantBody

source§

fn from(value: VariantDef) -> Self

Converts to this type from the input type.
source§

impl Serialize for EnumVariantBody

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/frontend/struct.FeatureBody.html b/book/rust-docs/nimbus_fml/frontend/struct.FeatureBody.html new file mode 100644 index 0000000000..3964f0264b --- /dev/null +++ b/book/rust-docs/nimbus_fml/frontend/struct.FeatureBody.html @@ -0,0 +1,25 @@ +FeatureBody in nimbus_fml::frontend - Rust
pub(crate) struct FeatureBody {
+    pub(crate) metadata: FeatureMetadata,
+    pub(crate) variables: BTreeMap<String, FeatureFieldBody>,
+    pub(crate) default: Option<Vec<DefaultBlock>>,
+    pub(crate) allow_coenrollment: bool,
+}

Fields§

§metadata: FeatureMetadata§variables: BTreeMap<String, FeatureFieldBody>§default: Option<Vec<DefaultBlock>>§allow_coenrollment: bool

Trait Implementations§

source§

impl Clone for FeatureBody

source§

fn clone(&self) -> FeatureBody

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for FeatureBody

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl<'de> Deserialize<'de> for FeatureBody

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl From<FeatureDef> for FeatureBody

source§

fn from(value: FeatureDef) -> Self

Converts to this type from the input type.
source§

impl Serialize for FeatureBody

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/frontend/struct.FeatureFieldBody.html b/book/rust-docs/nimbus_fml/frontend/struct.FeatureFieldBody.html new file mode 100644 index 0000000000..fd362b186f --- /dev/null +++ b/book/rust-docs/nimbus_fml/frontend/struct.FeatureFieldBody.html @@ -0,0 +1,23 @@ +FeatureFieldBody in nimbus_fml::frontend - Rust
pub(crate) struct FeatureFieldBody {
+    pub(crate) field: FieldBody,
+    pub(crate) pref_key: Option<String>,
+}

Fields§

§field: FieldBody§pref_key: Option<String>

Trait Implementations§

source§

impl Clone for FeatureFieldBody

source§

fn clone(&self) -> FeatureFieldBody

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for FeatureFieldBody

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl<'de> Deserialize<'de> for FeatureFieldBody

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl From<PropDef> for FeatureFieldBody

source§

fn from(value: PropDef) -> Self

Converts to this type from the input type.
source§

impl Serialize for FeatureFieldBody

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/frontend/struct.FeatureMetadata.html b/book/rust-docs/nimbus_fml/frontend/struct.FeatureMetadata.html new file mode 100644 index 0000000000..f79a7ae493 --- /dev/null +++ b/book/rust-docs/nimbus_fml/frontend/struct.FeatureMetadata.html @@ -0,0 +1,40 @@ +FeatureMetadata in nimbus_fml::frontend - Rust
pub(crate) struct FeatureMetadata {
+    pub(crate) description: String,
+    pub(crate) documentation: Vec<DocumentationLink>,
+    pub(crate) contacts: Vec<EmailAddress>,
+    pub(crate) meta_bug: Option<Url>,
+    pub(crate) events: Vec<Url>,
+    pub(crate) configurator: Option<Url>,
+}

Fields§

§description: String§documentation: Vec<DocumentationLink>

A list of named URLs to documentation for this feature.

+
§contacts: Vec<EmailAddress>

A list of contacts (engineers, product owners) who can be contacted for +help with this feature. Specifically for QA questions.

+
§meta_bug: Option<Url>

Where should QA file issues for this feature?

+
§events: Vec<Url>

What Glean events can the feature produce? +These should be links to a Glean dictionary.

+
§configurator: Option<Url>

A link to a Web based configuration UI for this feature. +This UI should produce the valid JSON instead of typing it +by hand.

+

Trait Implementations§

source§

impl Clone for FeatureMetadata

source§

fn clone(&self) -> FeatureMetadata

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for FeatureMetadata

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for FeatureMetadata

source§

fn default() -> FeatureMetadata

Returns the “default value” for a type. Read more
source§

impl<'de> Deserialize<'de> for FeatureMetadata

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl PartialEq<FeatureMetadata> for FeatureMetadata

source§

fn eq(&self, other: &FeatureMetadata) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Serialize for FeatureMetadata

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more
source§

impl Eq for FeatureMetadata

source§

impl StructuralEq for FeatureMetadata

source§

impl StructuralPartialEq for FeatureMetadata

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<Q, K> Equivalent<K> for Qwhere + Q: Eq + ?Sized, + K: Borrow<Q> + ?Sized,

source§

fn equivalent(&self, key: &K) -> bool

Compare self to key and return true if they are equal.
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/frontend/struct.FieldBody.html b/book/rust-docs/nimbus_fml/frontend/struct.FieldBody.html new file mode 100644 index 0000000000..47046ce440 --- /dev/null +++ b/book/rust-docs/nimbus_fml/frontend/struct.FieldBody.html @@ -0,0 +1,24 @@ +FieldBody in nimbus_fml::frontend - Rust
pub(crate) struct FieldBody {
+    pub(crate) description: String,
+    pub(crate) variable_type: String,
+    pub(crate) default: Option<Value>,
+}

Fields§

§description: String§variable_type: String§default: Option<Value>

Trait Implementations§

source§

impl Clone for FieldBody

source§

fn clone(&self) -> FieldBody

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for FieldBody

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl<'de> Deserialize<'de> for FieldBody

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl From<PropDef> for FieldBody

source§

fn from(value: PropDef) -> Self

Converts to this type from the input type.
source§

impl Serialize for FieldBody

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/frontend/struct.ImportBlock.html b/book/rust-docs/nimbus_fml/frontend/struct.ImportBlock.html new file mode 100644 index 0000000000..501c6d8e3b --- /dev/null +++ b/book/rust-docs/nimbus_fml/frontend/struct.ImportBlock.html @@ -0,0 +1,24 @@ +ImportBlock in nimbus_fml::frontend - Rust
pub(crate) struct ImportBlock {
+    pub(crate) path: String,
+    pub(crate) channel: String,
+    pub(crate) features: BTreeMap<String, Vec<DefaultBlock>>,
+}

Fields§

§path: String§channel: String§features: BTreeMap<String, Vec<DefaultBlock>>

Trait Implementations§

source§

impl Clone for ImportBlock

source§

fn clone(&self) -> ImportBlock

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for ImportBlock

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for ImportBlock

source§

fn default() -> ImportBlock

Returns the “default value” for a type. Read more
source§

impl<'de> Deserialize<'de> for ImportBlock

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl Serialize for ImportBlock

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/frontend/struct.KotlinAboutBlock.html b/book/rust-docs/nimbus_fml/frontend/struct.KotlinAboutBlock.html new file mode 100644 index 0000000000..23b838b646 --- /dev/null +++ b/book/rust-docs/nimbus_fml/frontend/struct.KotlinAboutBlock.html @@ -0,0 +1,27 @@ +KotlinAboutBlock in nimbus_fml::frontend - Rust
pub(crate) struct KotlinAboutBlock {
+    pub(crate) package: String,
+    pub(crate) class: String,
+}

Fields§

§package: String§class: String

Trait Implementations§

source§

impl Clone for KotlinAboutBlock

source§

fn clone(&self) -> KotlinAboutBlock

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for KotlinAboutBlock

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for KotlinAboutBlock

source§

fn default() -> KotlinAboutBlock

Returns the “default value” for a type. Read more
source§

impl<'de> Deserialize<'de> for KotlinAboutBlock

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl PartialEq<KotlinAboutBlock> for KotlinAboutBlock

source§

fn eq(&self, other: &KotlinAboutBlock) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Serialize for KotlinAboutBlock

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more
source§

impl Eq for KotlinAboutBlock

source§

impl StructuralEq for KotlinAboutBlock

source§

impl StructuralPartialEq for KotlinAboutBlock

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<Q, K> Equivalent<K> for Qwhere + Q: Eq + ?Sized, + K: Borrow<Q> + ?Sized,

source§

fn equivalent(&self, key: &K) -> bool

Compare self to key and return true if they are equal.
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/frontend/struct.ManifestFrontEnd.html b/book/rust-docs/nimbus_fml/frontend/struct.ManifestFrontEnd.html new file mode 100644 index 0000000000..f3f96aad1e --- /dev/null +++ b/book/rust-docs/nimbus_fml/frontend/struct.ManifestFrontEnd.html @@ -0,0 +1,61 @@ +ManifestFrontEnd in nimbus_fml::frontend - Rust
pub struct ManifestFrontEnd {
+    pub(crate) version: String,
+    pub(crate) about: Option<AboutBlock>,
+    pub(crate) channels: Vec<String>,
+    pub(crate) includes: Vec<String>,
+    pub(crate) imports: Vec<ImportBlock>,
+    pub(crate) features: BTreeMap<String, FeatureBody>,
+    pub(crate) legacy_types: Option<Types>,
+    pub(crate) types: Types,
+}

Fields§

§version: String§about: Option<AboutBlock>§channels: Vec<String>§includes: Vec<String>§imports: Vec<ImportBlock>§features: BTreeMap<String, FeatureBody>§legacy_types: Option<Types>§types: Types

Implementations§

source§

impl ManifestFrontEnd

source

pub fn channels(&self) -> Vec<String>

source

pub fn includes(&self) -> Vec<String>

source

fn get_types(&self) -> HashMap<String, TypeRef>

Retrieves all the types represented in the Manifest

+
Returns
+

Returns a std::collections::HashMap<String,TypeRef> where +the key is the name of the type, and the TypeRef represents the type itself

+
source

fn get_prop_def_from_feature_field( + &self, + nm: &str, + body: &FeatureFieldBody +) -> PropDef

source

fn get_prop_def_from_field(&self, nm: &str, body: &FieldBody) -> PropDef

Transforms a front-end field definition, a tuple of String and FieldBody, +into a PropDef

+
Arguments
+
    +
  • field: The [(&String, &FieldBody)] tuple to get the propdef from
  • +
+
Returns
+

return the IR PropDef

+
source

fn get_feature_defs( + &self, + merger: &DefaultsMerger<'_> +) -> Result<BTreeMap<String, FeatureDef>, FMLError>

Retrieves all the feature definitions represented in the manifest

+
Returns
+

Returns a std::collections::BTreeMap<String, FeatureDef>

+
source

fn get_objects(&self) -> BTreeMap<String, ObjectDef>

Retrieves all the Object type definitions represented in the manifest

+
Returns
+

Returns a [std::collections::BTreeMap<String. ObjectDef>]

+
source

fn get_enums(&self) -> BTreeMap<String, EnumDef>

Retrieves all the Enum type definitions represented in the manifest

+
Returns
+

Returns a std::collections::BTreeMap<String, EnumDef>

+
source

pub(crate) fn get_intermediate_representation( + &self, + id: &ModuleId, + channel: Option<&str> +) -> Result<FeatureManifest, FMLError>

Trait Implementations§

source§

impl Clone for ManifestFrontEnd

source§

fn clone(&self) -> ManifestFrontEnd

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for ManifestFrontEnd

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for ManifestFrontEnd

source§

fn default() -> ManifestFrontEnd

Returns the “default value” for a type. Read more
source§

impl<'de> Deserialize<'de> for ManifestFrontEnd

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl From<FeatureManifest> for ManifestFrontEnd

source§

fn from(value: FeatureManifest) -> Self

Converts to this type from the input type.
source§

impl Serialize for ManifestFrontEnd

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/frontend/struct.ObjectBody.html b/book/rust-docs/nimbus_fml/frontend/struct.ObjectBody.html new file mode 100644 index 0000000000..977a6dbbd7 --- /dev/null +++ b/book/rust-docs/nimbus_fml/frontend/struct.ObjectBody.html @@ -0,0 +1,23 @@ +ObjectBody in nimbus_fml::frontend - Rust
pub(crate) struct ObjectBody {
+    pub(crate) description: String,
+    pub(crate) fields: BTreeMap<String, FieldBody>,
+}

Fields§

§description: String§fields: BTreeMap<String, FieldBody>

Trait Implementations§

source§

impl Clone for ObjectBody

source§

fn clone(&self) -> ObjectBody

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for ObjectBody

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl<'de> Deserialize<'de> for ObjectBody

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl From<ObjectDef> for ObjectBody

source§

fn from(value: ObjectDef) -> Self

Converts to this type from the input type.
source§

impl Serialize for ObjectBody

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/frontend/struct.SwiftAboutBlock.html b/book/rust-docs/nimbus_fml/frontend/struct.SwiftAboutBlock.html new file mode 100644 index 0000000000..20e6f41ce9 --- /dev/null +++ b/book/rust-docs/nimbus_fml/frontend/struct.SwiftAboutBlock.html @@ -0,0 +1,27 @@ +SwiftAboutBlock in nimbus_fml::frontend - Rust
pub(crate) struct SwiftAboutBlock {
+    pub(crate) module: String,
+    pub(crate) class: String,
+}

Fields§

§module: String§class: String

Trait Implementations§

source§

impl Clone for SwiftAboutBlock

source§

fn clone(&self) -> SwiftAboutBlock

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for SwiftAboutBlock

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for SwiftAboutBlock

source§

fn default() -> SwiftAboutBlock

Returns the “default value” for a type. Read more
source§

impl<'de> Deserialize<'de> for SwiftAboutBlock

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl PartialEq<SwiftAboutBlock> for SwiftAboutBlock

source§

fn eq(&self, other: &SwiftAboutBlock) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Serialize for SwiftAboutBlock

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more
source§

impl Eq for SwiftAboutBlock

source§

impl StructuralEq for SwiftAboutBlock

source§

impl StructuralPartialEq for SwiftAboutBlock

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<Q, K> Equivalent<K> for Qwhere + Q: Eq + ?Sized, + K: Borrow<Q> + ?Sized,

source§

fn equivalent(&self, key: &K) -> bool

Compare self to key and return true if they are equal.
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/frontend/struct.Types.html b/book/rust-docs/nimbus_fml/frontend/struct.Types.html new file mode 100644 index 0000000000..9d15c3a004 --- /dev/null +++ b/book/rust-docs/nimbus_fml/frontend/struct.Types.html @@ -0,0 +1,23 @@ +Types in nimbus_fml::frontend - Rust

Struct nimbus_fml::frontend::Types

source ·
pub(crate) struct Types {
+    pub(crate) enums: BTreeMap<String, EnumBody>,
+    pub(crate) objects: BTreeMap<String, ObjectBody>,
+}

Fields§

§enums: BTreeMap<String, EnumBody>§objects: BTreeMap<String, ObjectBody>

Trait Implementations§

source§

impl Clone for Types

source§

fn clone(&self) -> Types

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for Types

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for Types

source§

fn default() -> Types

Returns the “default value” for a type. Read more
source§

impl<'de> Deserialize<'de> for Types

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl Serialize for Types

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more

Auto Trait Implementations§

§

impl RefUnwindSafe for Types

§

impl Send for Types

§

impl Sync for Types

§

impl Unpin for Types

§

impl UnwindSafe for Types

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/index.html b/book/rust-docs/nimbus_fml/index.html new file mode 100644 index 0000000000..cfd8887fff --- /dev/null +++ b/book/rust-docs/nimbus_fml/index.html @@ -0,0 +1 @@ +nimbus_fml - Rust
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/intermediate_representation/enum.ModuleId.html b/book/rust-docs/nimbus_fml/intermediate_representation/enum.ModuleId.html new file mode 100644 index 0000000000..ffcce90d9f --- /dev/null +++ b/book/rust-docs/nimbus_fml/intermediate_representation/enum.ModuleId.html @@ -0,0 +1,49 @@ +ModuleId in nimbus_fml::intermediate_representation - Rust
pub enum ModuleId {
+    Local(String),
+    Remote(String),
+}
Expand description

An identifier derived from a FilePath of a top-level or importable FML file.

+

An FML module is the conceptual FML file (and included FML files) that a single +Kotlin or Swift file. It can be imported by other FML modules.

+

It is somewhat distinct from the FilePath enum for three reasons:

+
    +
  • a file path can specify a non-canonical representation of the path
  • +
  • a file path is difficult to serialize/deserialize
  • +
  • a module identifies the cluster of FML files that map to a single generated +Kotlin or Swift file; this difference can be seen as: files can be included, +modules can be imported.
  • +
+

Variants§

§

Local(String)

§

Remote(String)

Trait Implementations§

source§

impl Clone for ModuleId

source§

fn clone(&self) -> ModuleId

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for ModuleId

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for ModuleId

source§

fn default() -> Self

Returns the “default value” for a type. Read more
source§

impl<'de> Deserialize<'de> for ModuleId

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl Display for ModuleId

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Hash for ModuleId

source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · source§

fn hash_slice<H>(data: &[Self], state: &mut H)where + H: Hasher, + Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
source§

impl Ord for ModuleId

source§

fn cmp(&self, other: &ModuleId) -> Ordering

This method returns an Ordering between self and other. Read more
1.21.0 · source§

fn max(self, other: Self) -> Selfwhere + Self: Sized,

Compares and returns the maximum of two values. Read more
1.21.0 · source§

fn min(self, other: Self) -> Selfwhere + Self: Sized,

Compares and returns the minimum of two values. Read more
1.50.0 · source§

fn clamp(self, min: Self, max: Self) -> Selfwhere + Self: Sized + PartialOrd<Self>,

Restrict a value to a certain interval. Read more
source§

impl PartialEq<ModuleId> for ModuleId

source§

fn eq(&self, other: &ModuleId) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl PartialOrd<ModuleId> for ModuleId

source§

fn partial_cmp(&self, other: &ModuleId) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · source§

fn lt(&self, other: &Rhs) -> bool

This method tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · source§

fn le(&self, other: &Rhs) -> bool

This method tests less than or equal to (for self and other) and is used by the <= +operator. Read more
1.0.0 · source§

fn gt(&self, other: &Rhs) -> bool

This method tests greater than (for self and other) and is used by the > operator. Read more
1.0.0 · source§

fn ge(&self, other: &Rhs) -> bool

This method tests greater than or equal to (for self and other) and is used by the >= +operator. Read more
source§

impl Serialize for ModuleId

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more
source§

impl TryFrom<&FilePath> for ModuleId

§

type Error = FMLError

The type returned in the event of a conversion error.
source§

fn try_from(path: &FilePath) -> Result<Self, FMLError>

Performs the conversion.
source§

impl Eq for ModuleId

source§

impl StructuralEq for ModuleId

source§

impl StructuralPartialEq for ModuleId

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> CallHasher for Twhere + T: Hash + ?Sized,

§

fn get_hash<H, B>(value: &H, build_hasher: &B) -> u64where + H: Hash + ?Sized, + B: BuildHasher,

source§

impl<Q, K> Equivalent<K> for Qwhere + Q: Eq + ?Sized, + K: Borrow<Q> + ?Sized,

source§

fn equivalent(&self, key: &K) -> bool

Compare self to key and return true if they are equal.
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/intermediate_representation/enum.TargetLanguage.html b/book/rust-docs/nimbus_fml/intermediate_representation/enum.TargetLanguage.html new file mode 100644 index 0000000000..325e4b8fb3 --- /dev/null +++ b/book/rust-docs/nimbus_fml/intermediate_representation/enum.TargetLanguage.html @@ -0,0 +1,32 @@ +TargetLanguage in nimbus_fml::intermediate_representation - Rust
pub enum TargetLanguage {
+    Kotlin,
+    Swift,
+    IR,
+    ExperimenterYAML,
+    ExperimenterJSON,
+}

Variants§

§

Kotlin

§

Swift

§

IR

§

ExperimenterYAML

§

ExperimenterJSON

Implementations§

Trait Implementations§

source§

impl Clone for TargetLanguage

source§

fn clone(&self) -> TargetLanguage

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for TargetLanguage

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Hash for TargetLanguage

source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · source§

fn hash_slice<H>(data: &[Self], state: &mut H)where + H: Hasher, + Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
source§

impl PartialEq<TargetLanguage> for TargetLanguage

source§

fn eq(&self, other: &TargetLanguage) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl TryFrom<&OsStr> for TargetLanguage

§

type Error = Error

The type returned in the event of a conversion error.
source§

fn try_from(value: &OsStr) -> Result<Self>

Performs the conversion.
source§

impl TryFrom<&Path> for TargetLanguage

§

type Error = Error

The type returned in the event of a conversion error.
source§

fn try_from(value: &Path) -> Result<Self>

Performs the conversion.
source§

impl TryFrom<&str> for TargetLanguage

§

type Error = Error

The type returned in the event of a conversion error.
source§

fn try_from(value: &str) -> AnyhowResult<Self>

Performs the conversion.
source§

impl TryFrom<String> for TargetLanguage

§

type Error = Error

The type returned in the event of a conversion error.
source§

fn try_from(value: String) -> Result<Self>

Performs the conversion.
source§

impl Eq for TargetLanguage

source§

impl StructuralEq for TargetLanguage

source§

impl StructuralPartialEq for TargetLanguage

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> CallHasher for Twhere + T: Hash + ?Sized,

§

fn get_hash<H, B>(value: &H, build_hasher: &B) -> u64where + H: Hash + ?Sized, + B: BuildHasher,

source§

impl<Q, K> Equivalent<K> for Qwhere + Q: Eq + ?Sized, + K: Borrow<Q> + ?Sized,

source§

fn equivalent(&self, key: &K) -> bool

Compare self to key and return true if they are equal.
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/intermediate_representation/enum.TypeRef.html b/book/rust-docs/nimbus_fml/intermediate_representation/enum.TypeRef.html new file mode 100644 index 0000000000..5aa78a5db8 --- /dev/null +++ b/book/rust-docs/nimbus_fml/intermediate_representation/enum.TypeRef.html @@ -0,0 +1,47 @@ +TypeRef in nimbus_fml::intermediate_representation - Rust
#[non_exhaustive]
pub enum TypeRef { + String, + Int, + Boolean, + BundleText(String), + BundleImage(String), + Enum(String), + Object(String), + StringMap(Box<TypeRef>), + EnumMap(Box<TypeRef>, Box<TypeRef>), + List(Box<TypeRef>), + Option(Box<TypeRef>), +}
Expand description

The TypeRef enum defines a reference to a type.

+

Other types will be defined in terms of these enum values.

+

They represent the types available via the current Variables API— +some primitives and structural types— and can be represented by +Kotlin, Swift and JSON Schema.

+

Variants (Non-exhaustive)§

This enum is marked as non-exhaustive
Non-exhaustive enums could have additional variants added in future. Therefore, when matching against variants of non-exhaustive enums, an extra wildcard arm must be added to account for any future variants.
§

String

§

Int

§

Boolean

§

BundleText(String)

§

BundleImage(String)

§

Enum(String)

§

Object(String)

§

StringMap(Box<TypeRef>)

§

EnumMap(Box<TypeRef>, Box<TypeRef>)

§

List(Box<TypeRef>)

§

Option(Box<TypeRef>)

Implementations§

source§

impl TypeRef

source

pub(crate) fn supports_prefs(&self) -> bool

Trait Implementations§

source§

impl Clone for TypeRef

source§

fn clone(&self) -> TypeRef

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for TypeRef

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl<'de> Deserialize<'de> for TypeRef

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl Display for TypeRef

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl From<TypeRef> for ExperimentManifestPropType

source§

fn from(typ: TypeRef) -> Self

Converts to this type from the input type.
source§

impl Hash for TypeRef

source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · source§

fn hash_slice<H>(data: &[Self], state: &mut H)where + H: Hasher, + Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
source§

impl PartialEq<TypeRef> for TypeRef

source§

fn eq(&self, other: &TypeRef) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Serialize for TypeRef

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more
source§

impl TypeFinder for TypeRef

source§

fn find_types(&self, types: &mut HashSet<TypeRef>)

source§

fn all_types(&self) -> HashSet<TypeRef>

source§

impl Eq for TypeRef

source§

impl StructuralEq for TypeRef

source§

impl StructuralPartialEq for TypeRef

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> CallHasher for Twhere + T: Hash + ?Sized,

§

fn get_hash<H, B>(value: &H, build_hasher: &B) -> u64where + H: Hash + ?Sized, + B: BuildHasher,

source§

impl<Q, K> Equivalent<K> for Qwhere + Q: Eq + ?Sized, + K: Borrow<Q> + ?Sized,

source§

fn equivalent(&self, key: &K) -> bool

Compare self to key and return true if they are equal.
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/intermediate_representation/index.html b/book/rust-docs/nimbus_fml/intermediate_representation/index.html new file mode 100644 index 0000000000..70c25eae24 --- /dev/null +++ b/book/rust-docs/nimbus_fml/intermediate_representation/index.html @@ -0,0 +1 @@ +nimbus_fml::intermediate_representation - Rust

Structs

Enums

  • An identifier derived from a FilePath of a top-level or importable FML file.
  • The TypeRef enum defines a reference to a type.

Traits

Type Definitions

\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/intermediate_representation/sidebar-items.js b/book/rust-docs/nimbus_fml/intermediate_representation/sidebar-items.js new file mode 100644 index 0000000000..d45e6a7a68 --- /dev/null +++ b/book/rust-docs/nimbus_fml/intermediate_representation/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["ModuleId","TargetLanguage","TypeRef"],"struct":["EnumDef","FeatureDef","FeatureManifest","FromStringDef","ImportedModule","ObjectDef","PropDef","VariantDef"],"trait":["TypeFinder"],"type":["Literal","StringId"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/intermediate_representation/struct.EnumDef.html b/book/rust-docs/nimbus_fml/intermediate_representation/struct.EnumDef.html new file mode 100644 index 0000000000..39fa13138b --- /dev/null +++ b/book/rust-docs/nimbus_fml/intermediate_representation/struct.EnumDef.html @@ -0,0 +1,28 @@ +EnumDef in nimbus_fml::intermediate_representation - Rust
pub struct EnumDef {
+    pub name: String,
+    pub doc: String,
+    pub variants: Vec<VariantDef>,
+}

Fields§

§name: String§doc: String§variants: Vec<VariantDef>

Implementations§

source§

impl EnumDef

source

pub fn name(&self) -> String

source

pub fn doc(&self) -> String

source

pub fn variants(&self) -> Vec<VariantDef>

Trait Implementations§

source§

impl Clone for EnumDef

source§

fn clone(&self) -> EnumDef

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for EnumDef

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for EnumDef

source§

fn default() -> EnumDef

Returns the “default value” for a type. Read more
source§

impl<'de> Deserialize<'de> for EnumDef

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl From<EnumDef> for EnumBody

source§

fn from(value: EnumDef) -> Self

Converts to this type from the input type.
source§

impl PartialEq<EnumDef> for EnumDef

source§

fn eq(&self, other: &EnumDef) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Serialize for EnumDef

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more
source§

impl TypeFinder for EnumDef

source§

fn find_types(&self, types: &mut HashSet<TypeRef>)

source§

fn all_types(&self) -> HashSet<TypeRef>

source§

impl Eq for EnumDef

source§

impl StructuralEq for EnumDef

source§

impl StructuralPartialEq for EnumDef

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<Q, K> Equivalent<K> for Qwhere + Q: Eq + ?Sized, + K: Borrow<Q> + ?Sized,

source§

fn equivalent(&self, key: &K) -> bool

Compare self to key and return true if they are equal.
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/intermediate_representation/struct.FeatureDef.html b/book/rust-docs/nimbus_fml/intermediate_representation/struct.FeatureDef.html new file mode 100644 index 0000000000..9a4b482b42 --- /dev/null +++ b/book/rust-docs/nimbus_fml/intermediate_representation/struct.FeatureDef.html @@ -0,0 +1,34 @@ +FeatureDef in nimbus_fml::intermediate_representation - Rust
pub struct FeatureDef {
+    pub(crate) name: String,
+    pub(crate) metadata: FeatureMetadata,
+    pub(crate) props: Vec<PropDef>,
+    pub(crate) allow_coenrollment: bool,
+}

Fields§

§name: String§metadata: FeatureMetadata§props: Vec<PropDef>§allow_coenrollment: bool

Implementations§

source§

impl FeatureDef

source

pub fn new( + name: &str, + doc: &str, + props: Vec<PropDef>, + allow_coenrollment: bool +) -> Self

source

pub fn name(&self) -> String

source

pub fn doc(&self) -> String

source

pub fn props(&self) -> Vec<PropDef>

source

pub fn allow_coenrollment(&self) -> bool

source

pub fn default_json(&self) -> Value

source

pub fn has_prefs(&self) -> bool

Trait Implementations§

source§

impl Clone for FeatureDef

source§

fn clone(&self) -> FeatureDef

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for FeatureDef

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for FeatureDef

source§

fn default() -> FeatureDef

Returns the “default value” for a type. Read more
source§

impl<'de> Deserialize<'de> for FeatureDef

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl From<FeatureDef> for FeatureBody

source§

fn from(value: FeatureDef) -> Self

Converts to this type from the input type.
source§

impl PartialEq<FeatureDef> for FeatureDef

source§

fn eq(&self, other: &FeatureDef) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Serialize for FeatureDef

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more
source§

impl TypeFinder for FeatureDef

source§

fn find_types(&self, types: &mut HashSet<TypeRef>)

source§

fn all_types(&self) -> HashSet<TypeRef>

source§

impl Eq for FeatureDef

source§

impl StructuralEq for FeatureDef

source§

impl StructuralPartialEq for FeatureDef

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<Q, K> Equivalent<K> for Qwhere + Q: Eq + ?Sized, + K: Borrow<Q> + ?Sized,

source§

fn equivalent(&self, key: &K) -> bool

Compare self to key and return true if they are equal.
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/intermediate_representation/struct.FeatureManifest.html b/book/rust-docs/nimbus_fml/intermediate_representation/struct.FeatureManifest.html new file mode 100644 index 0000000000..21dfe8e5d5 --- /dev/null +++ b/book/rust-docs/nimbus_fml/intermediate_representation/struct.FeatureManifest.html @@ -0,0 +1,95 @@ +FeatureManifest in nimbus_fml::intermediate_representation - Rust
pub struct FeatureManifest {
+    pub(crate) id: ModuleId,
+    pub(crate) channel: Option<String>,
+    pub(crate) enum_defs: BTreeMap<String, EnumDef>,
+    pub(crate) obj_defs: BTreeMap<String, ObjectDef>,
+    pub(crate) feature_defs: BTreeMap<String, FeatureDef>,
+    pub(crate) about: AboutBlock,
+    pub(crate) imported_features: HashMap<ModuleId, BTreeSet<String>>,
+    pub(crate) all_imports: HashMap<ModuleId, FeatureManifest>,
+}

Fields§

§id: ModuleId§channel: Option<String>§enum_defs: BTreeMap<String, EnumDef>§obj_defs: BTreeMap<String, ObjectDef>§feature_defs: BTreeMap<String, FeatureDef>§about: AboutBlock§imported_features: HashMap<ModuleId, BTreeSet<String>>§all_imports: HashMap<ModuleId, FeatureManifest>

Implementations§

source§

impl FeatureManifest

source§

impl FeatureManifest

source

pub(crate) fn new( + id: ModuleId, + channel: Option<&str>, + features: BTreeMap<String, FeatureDef>, + enums: BTreeMap<String, EnumDef>, + objects: BTreeMap<String, ObjectDef>, + about: AboutBlock +) -> Self

source

pub(crate) fn validate_manifest_for_lang( + &self, + lang: &TargetLanguage +) -> Result<(), FMLError>

source

pub fn validate_manifest(&self) -> Result<(), FMLError>

source

fn validate_type_ref( + path: &str, + type_ref: &TypeRef, + enum_names: &HashSet<String>, + obj_names: &HashSet<String> +) -> Result<(), FMLError>

source

fn validate_enum_defs( + &self, + enum_names: &mut HashSet<String> +) -> Result<(), FMLError>

source

fn validate_obj_defs( + &self, + obj_names: &mut HashSet<String> +) -> Result<(), FMLError>

source

fn validate_feature_defs( + &self, + feature_names: &mut HashSet<String> +) -> Result<(), FMLError>

source

fn validate_props( + &self, + feature_def: &FeatureDef, + prop_names: &mut HashSet<String> +) -> Result<(), FMLError>

source

fn validate_defaults(&self) -> Result<(), FMLError>

source

fn validate_feature_structure( + &self, + feature_def: &FeatureDef +) -> Result<(), FMLError>

source

fn validate_feature_def(&self, feature_def: &FeatureDef) -> Result<(), FMLError>

source

fn validate_prop_defaults( + &self, + path: &str, + prop: &PropDef +) -> Result<(), FMLError>

source

pub fn validate_default_by_typ( + &self, + path: &str, + literals: &Vec<String>, + type_ref: &TypeRef, + default: &Value +) -> Result<(), FMLError>

source

pub fn iter_enum_defs(&self) -> impl Iterator<Item = &EnumDef>

source

pub fn iter_all_enum_defs( + &self +) -> impl Iterator<Item = (&FeatureManifest, &EnumDef)>

source

pub fn iter_object_defs(&self) -> impl Iterator<Item = &ObjectDef>

source

pub fn iter_all_object_defs( + &self +) -> impl Iterator<Item = (&FeatureManifest, &ObjectDef)>

source

pub fn iter_feature_defs(&self) -> impl Iterator<Item = &FeatureDef>

source

pub fn iter_all_feature_defs( + &self +) -> impl Iterator<Item = (&FeatureManifest, &FeatureDef)>

source

pub(crate) fn iter_imported_files(&self) -> Vec<ImportedModule<'_>>

source

pub fn find_object(&self, nm: &str) -> Option<&ObjectDef>

source

pub fn find_enum(&self, nm: &str) -> Option<&EnumDef>

source

pub fn get_feature(&self, nm: &str) -> Option<&FeatureDef>

source

pub fn get_coenrolling_feature_ids(&self) -> Vec<String>

source

pub fn find_feature(&self, nm: &str) -> Option<(&FeatureManifest, &FeatureDef)>

source

pub fn find_import(&self, id: &ModuleId) -> Option<&FeatureManifest>

source

pub fn default_json(&self) -> Value

source

pub fn validate_feature_config( + &self, + feature_name: &str, + feature_value: Value +) -> Result<FeatureDef, FMLError>

This function is used to validate a new value for a feature. It accepts a feature name and +a feature value, and returns a Result containing a FeatureDef.

+

If the value is invalid for the feature, it will return an Err result.

+

If the value is valid for the feature, it will return an Ok result with a new FeatureDef +with the supplied feature value applied to the feature’s property defaults.

+

Trait Implementations§

source§

impl Clone for FeatureManifest

source§

fn clone(&self) -> FeatureManifest

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for FeatureManifest

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for FeatureManifest

source§

fn default() -> FeatureManifest

Returns the “default value” for a type. Read more
source§

impl<'de> Deserialize<'de> for FeatureManifest

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl From<FeatureManifest> for ManifestFrontEnd

source§

fn from(value: FeatureManifest) -> Self

Converts to this type from the input type.
source§

impl PartialEq<FeatureManifest> for FeatureManifest

source§

fn eq(&self, other: &FeatureManifest) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Serialize for FeatureManifest

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more
source§

impl TryFrom<FeatureManifest> for BTreeMap<String, ExperimenterFeature>

§

type Error = FMLError

The type returned in the event of a conversion error.
source§

fn try_from(fm: FeatureManifest) -> Result<Self, FMLError>

Performs the conversion.
source§

impl TypeFinder for FeatureManifest

source§

fn find_types(&self, types: &mut HashSet<TypeRef>)

source§

fn all_types(&self) -> HashSet<TypeRef>

source§

impl Eq for FeatureManifest

source§

impl StructuralEq for FeatureManifest

source§

impl StructuralPartialEq for FeatureManifest

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<Q, K> Equivalent<K> for Qwhere + Q: Eq + ?Sized, + K: Borrow<Q> + ?Sized,

source§

fn equivalent(&self, key: &K) -> bool

Compare self to key and return true if they are equal.
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/intermediate_representation/struct.FromStringDef.html b/book/rust-docs/nimbus_fml/intermediate_representation/struct.FromStringDef.html new file mode 100644 index 0000000000..7f32f85d3c --- /dev/null +++ b/book/rust-docs/nimbus_fml/intermediate_representation/struct.FromStringDef.html @@ -0,0 +1,28 @@ +FromStringDef in nimbus_fml::intermediate_representation - Rust
pub struct FromStringDef {
+    pub name: String,
+    pub doc: String,
+    pub variants: Vec<VariantDef>,
+}

Fields§

§name: String§doc: String§variants: Vec<VariantDef>

Trait Implementations§

source§

impl Clone for FromStringDef

source§

fn clone(&self) -> FromStringDef

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for FromStringDef

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl<'de> Deserialize<'de> for FromStringDef

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl PartialEq<FromStringDef> for FromStringDef

source§

fn eq(&self, other: &FromStringDef) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Serialize for FromStringDef

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more
source§

impl Eq for FromStringDef

source§

impl StructuralEq for FromStringDef

source§

impl StructuralPartialEq for FromStringDef

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<Q, K> Equivalent<K> for Qwhere + Q: Eq + ?Sized, + K: Borrow<Q> + ?Sized,

source§

fn equivalent(&self, key: &K) -> bool

Compare self to key and return true if they are equal.
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/intermediate_representation/struct.ImportedModule.html b/book/rust-docs/nimbus_fml/intermediate_representation/struct.ImportedModule.html new file mode 100644 index 0000000000..6b715e6728 --- /dev/null +++ b/book/rust-docs/nimbus_fml/intermediate_representation/struct.ImportedModule.html @@ -0,0 +1,23 @@ +ImportedModule in nimbus_fml::intermediate_representation - Rust
pub(crate) struct ImportedModule<'a> {
+    pub(crate) fm: &'a FeatureManifest,
+    features: &'a BTreeSet<String>,
+}

Fields§

§fm: &'a FeatureManifest§features: &'a BTreeSet<String>

Implementations§

source§

impl<'a> ImportedModule<'a>

source

pub(crate) fn new( + fm: &'a FeatureManifest, + features: &'a BTreeSet<String> +) -> Self

source

pub(crate) fn about(&self) -> &AboutBlock

source

pub(crate) fn features(&self) -> Vec<&'a FeatureDef>

Trait Implementations§

source§

impl<'a> Clone for ImportedModule<'a>

source§

fn clone(&self) -> ImportedModule<'a>

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl<'a> Debug for ImportedModule<'a>

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl<'a> RefUnwindSafe for ImportedModule<'a>

§

impl<'a> Send for ImportedModule<'a>

§

impl<'a> Sync for ImportedModule<'a>

§

impl<'a> Unpin for ImportedModule<'a>

§

impl<'a> UnwindSafe for ImportedModule<'a>

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/intermediate_representation/struct.ObjectDef.html b/book/rust-docs/nimbus_fml/intermediate_representation/struct.ObjectDef.html new file mode 100644 index 0000000000..f03a405962 --- /dev/null +++ b/book/rust-docs/nimbus_fml/intermediate_representation/struct.ObjectDef.html @@ -0,0 +1,28 @@ +ObjectDef in nimbus_fml::intermediate_representation - Rust
pub struct ObjectDef {
+    pub(crate) name: String,
+    pub(crate) doc: String,
+    pub(crate) props: Vec<PropDef>,
+}

Fields§

§name: String§doc: String§props: Vec<PropDef>

Implementations§

source§

impl ObjectDef

source

pub fn new(name: &str, doc: &str, props: Vec<PropDef>) -> Self

source

pub(crate) fn name(&self) -> String

source

pub(crate) fn doc(&self) -> String

source

pub fn props(&self) -> Vec<PropDef>

source

pub(crate) fn find_prop(&self, nm: &str) -> PropDef

Trait Implementations§

source§

impl Clone for ObjectDef

source§

fn clone(&self) -> ObjectDef

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for ObjectDef

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for ObjectDef

source§

fn default() -> ObjectDef

Returns the “default value” for a type. Read more
source§

impl<'de> Deserialize<'de> for ObjectDef

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl From<ObjectDef> for ObjectBody

source§

fn from(value: ObjectDef) -> Self

Converts to this type from the input type.
source§

impl PartialEq<ObjectDef> for ObjectDef

source§

fn eq(&self, other: &ObjectDef) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Serialize for ObjectDef

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more
source§

impl TypeFinder for ObjectDef

source§

fn find_types(&self, types: &mut HashSet<TypeRef>)

source§

fn all_types(&self) -> HashSet<TypeRef>

source§

impl Eq for ObjectDef

source§

impl StructuralEq for ObjectDef

source§

impl StructuralPartialEq for ObjectDef

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<Q, K> Equivalent<K> for Qwhere + Q: Eq + ?Sized, + K: Borrow<Q> + ?Sized,

source§

fn equivalent(&self, key: &K) -> bool

Compare self to key and return true if they are equal.
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/intermediate_representation/struct.PropDef.html b/book/rust-docs/nimbus_fml/intermediate_representation/struct.PropDef.html new file mode 100644 index 0000000000..c8a8b2d652 --- /dev/null +++ b/book/rust-docs/nimbus_fml/intermediate_representation/struct.PropDef.html @@ -0,0 +1,30 @@ +PropDef in nimbus_fml::intermediate_representation - Rust
pub struct PropDef {
+    pub(crate) name: String,
+    pub(crate) doc: String,
+    pub(crate) typ: TypeRef,
+    pub(crate) default: Value,
+    pub(crate) pref_key: Option<String>,
+}

Fields§

§name: String§doc: String§typ: TypeRef§default: Value§pref_key: Option<String>

Implementations§

source§

impl PropDef

source

pub fn name(&self) -> String

source

pub fn doc(&self) -> String

source

pub fn typ(&self) -> TypeRef

source

pub fn default(&self) -> Value

source

pub fn has_prefs(&self) -> bool

source

pub fn pref_key(&self) -> Option<String>

Trait Implementations§

source§

impl Clone for PropDef

source§

fn clone(&self) -> PropDef

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for PropDef

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl<'de> Deserialize<'de> for PropDef

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl From<PropDef> for FeatureFieldBody

source§

fn from(value: PropDef) -> Self

Converts to this type from the input type.
source§

impl From<PropDef> for FieldBody

source§

fn from(value: PropDef) -> Self

Converts to this type from the input type.
source§

impl PartialEq<PropDef> for PropDef

source§

fn eq(&self, other: &PropDef) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Serialize for PropDef

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more
source§

impl TypeFinder for PropDef

source§

fn find_types(&self, types: &mut HashSet<TypeRef>)

source§

fn all_types(&self) -> HashSet<TypeRef>

source§

impl Eq for PropDef

source§

impl StructuralEq for PropDef

source§

impl StructuralPartialEq for PropDef

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<Q, K> Equivalent<K> for Qwhere + Q: Eq + ?Sized, + K: Borrow<Q> + ?Sized,

source§

fn equivalent(&self, key: &K) -> bool

Compare self to key and return true if they are equal.
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/intermediate_representation/struct.VariantDef.html b/book/rust-docs/nimbus_fml/intermediate_representation/struct.VariantDef.html new file mode 100644 index 0000000000..507ef74367 --- /dev/null +++ b/book/rust-docs/nimbus_fml/intermediate_representation/struct.VariantDef.html @@ -0,0 +1,27 @@ +VariantDef in nimbus_fml::intermediate_representation - Rust
pub struct VariantDef {
+    pub(crate) name: String,
+    pub(crate) doc: String,
+}

Fields§

§name: String§doc: String

Implementations§

source§

impl VariantDef

source

pub fn new(name: &str, doc: &str) -> Self

source

pub fn name(&self) -> String

source

pub fn doc(&self) -> String

Trait Implementations§

source§

impl Clone for VariantDef

source§

fn clone(&self) -> VariantDef

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for VariantDef

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for VariantDef

source§

fn default() -> VariantDef

Returns the “default value” for a type. Read more
source§

impl<'de> Deserialize<'de> for VariantDef

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl From<VariantDef> for EnumVariantBody

source§

fn from(value: VariantDef) -> Self

Converts to this type from the input type.
source§

impl PartialEq<VariantDef> for VariantDef

source§

fn eq(&self, other: &VariantDef) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Serialize for VariantDef

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more
source§

impl Eq for VariantDef

source§

impl StructuralEq for VariantDef

source§

impl StructuralPartialEq for VariantDef

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<Q, K> Equivalent<K> for Qwhere + Q: Eq + ?Sized, + K: Borrow<Q> + ?Sized,

source§

fn equivalent(&self, key: &K) -> bool

Compare self to key and return true if they are equal.
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/intermediate_representation/trait.TypeFinder.html b/book/rust-docs/nimbus_fml/intermediate_representation/trait.TypeFinder.html new file mode 100644 index 0000000000..5622d95ee8 --- /dev/null +++ b/book/rust-docs/nimbus_fml/intermediate_representation/trait.TypeFinder.html @@ -0,0 +1,7 @@ +TypeFinder in nimbus_fml::intermediate_representation - Rust
pub trait TypeFinder {
+    // Required method
+    fn find_types(&self, types: &mut HashSet<TypeRef>);
+
+    // Provided method
+    fn all_types(&self) -> HashSet<TypeRef> { ... }
+}

Required Methods§

source

fn find_types(&self, types: &mut HashSet<TypeRef>)

Provided Methods§

Implementors§

\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/intermediate_representation/type.Literal.html b/book/rust-docs/nimbus_fml/intermediate_representation/type.Literal.html new file mode 100644 index 0000000000..b8c3f8fc29 --- /dev/null +++ b/book/rust-docs/nimbus_fml/intermediate_representation/type.Literal.html @@ -0,0 +1 @@ +Literal in nimbus_fml::intermediate_representation - Rust
pub type Literal = Value;
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/intermediate_representation/type.StringId.html b/book/rust-docs/nimbus_fml/intermediate_representation/type.StringId.html new file mode 100644 index 0000000000..93f041c9b4 --- /dev/null +++ b/book/rust-docs/nimbus_fml/intermediate_representation/type.StringId.html @@ -0,0 +1 @@ +StringId in nimbus_fml::intermediate_representation - Rust
pub(crate) type StringId = String;
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/parser/fn.check_can_import_list.html b/book/rust-docs/nimbus_fml/parser/fn.check_can_import_list.html new file mode 100644 index 0000000000..bacd1ccc4d --- /dev/null +++ b/book/rust-docs/nimbus_fml/parser/fn.check_can_import_list.html @@ -0,0 +1,6 @@ +check_can_import_list in nimbus_fml::parser - Rust
fn check_can_import_list(
+    parent: &FeatureManifest,
+    child: &FeatureManifest,
+    key: &str,
+    f: fn(_: &FeatureManifest) -> HashSet<&String>
+) -> Result<(), FMLError>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/parser/fn.check_can_import_manifest.html b/book/rust-docs/nimbus_fml/parser/fn.check_can_import_manifest.html new file mode 100644 index 0000000000..2a5583b68e --- /dev/null +++ b/book/rust-docs/nimbus_fml/parser/fn.check_can_import_manifest.html @@ -0,0 +1,5 @@ +check_can_import_manifest in nimbus_fml::parser - Rust
fn check_can_import_manifest(
+    parent: &FeatureManifest,
+    child: &FeatureManifest
+) -> Result<(), FMLError>
Expand description

Check if this parent can import this child.

+
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/parser/fn.get_typeref_from_string.html b/book/rust-docs/nimbus_fml/parser/fn.get_typeref_from_string.html new file mode 100644 index 0000000000..c658767e9c --- /dev/null +++ b/book/rust-docs/nimbus_fml/parser/fn.get_typeref_from_string.html @@ -0,0 +1,4 @@ +get_typeref_from_string in nimbus_fml::parser - Rust
pub(crate) fn get_typeref_from_string(
+    input: String,
+    types: Option<HashMap<String, TypeRef>>
+) -> Result<TypeRef, FMLError>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/parser/fn.merge_import_block.html b/book/rust-docs/nimbus_fml/parser/fn.merge_import_block.html new file mode 100644 index 0000000000..a8595387fc --- /dev/null +++ b/book/rust-docs/nimbus_fml/parser/fn.merge_import_block.html @@ -0,0 +1,4 @@ +merge_import_block in nimbus_fml::parser - Rust
fn merge_import_block(
+    a: &ImportBlock,
+    b: &ImportBlock
+) -> Result<ImportBlock, FMLError>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/parser/fn.merge_map.html b/book/rust-docs/nimbus_fml/parser/fn.merge_map.html new file mode 100644 index 0000000000..c9d0a998a7 --- /dev/null +++ b/book/rust-docs/nimbus_fml/parser/fn.merge_map.html @@ -0,0 +1,7 @@ +merge_map in nimbus_fml::parser - Rust

Function nimbus_fml::parser::merge_map

source ·
fn merge_map<T: Clone>(
+    a: &BTreeMap<String, T>,
+    b: &BTreeMap<String, T>,
+    display_key: &str,
+    key: &str,
+    child_path: &FilePath
+) -> Result<BTreeMap<String, T>, FMLError>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/parser/fn.parse_typeref_string.html b/book/rust-docs/nimbus_fml/parser/fn.parse_typeref_string.html new file mode 100644 index 0000000000..a94982b32e --- /dev/null +++ b/book/rust-docs/nimbus_fml/parser/fn.parse_typeref_string.html @@ -0,0 +1,3 @@ +parse_typeref_string in nimbus_fml::parser - Rust
fn parse_typeref_string(
+    input: String
+) -> Result<(String, Option<String>), FMLError>
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/parser/index.html b/book/rust-docs/nimbus_fml/parser/index.html new file mode 100644 index 0000000000..074426e81a --- /dev/null +++ b/book/rust-docs/nimbus_fml/parser/index.html @@ -0,0 +1 @@ +nimbus_fml::parser - Rust
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/parser/sidebar-items.js b/book/rust-docs/nimbus_fml/parser/sidebar-items.js new file mode 100644 index 0000000000..ec3e05c8fd --- /dev/null +++ b/book/rust-docs/nimbus_fml/parser/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["check_can_import_list","check_can_import_manifest","get_typeref_from_string","merge_import_block","merge_map","parse_typeref_string"],"struct":["Parser"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/parser/struct.Parser.html b/book/rust-docs/nimbus_fml/parser/struct.Parser.html new file mode 100644 index 0000000000..614d4f160a --- /dev/null +++ b/book/rust-docs/nimbus_fml/parser/struct.Parser.html @@ -0,0 +1,62 @@ +Parser in nimbus_fml::parser - Rust

Struct nimbus_fml::parser::Parser

source ·
pub struct Parser {
+    files: FileLoader,
+    source: FilePath,
+}

Fields§

§files: FileLoader§source: FilePath

Implementations§

source§

impl Parser

source

pub fn new(files: FileLoader, source: FilePath) -> Result<Parser, FMLError>

source

pub fn load_frontend( + files: FileLoader, + source: &str +) -> Result<ManifestFrontEnd, FMLError>

source

pub fn load_manifest( + &self, + path: &FilePath, + loading: &mut HashSet<ModuleId> +) -> Result<ManifestFrontEnd, FMLError>

source

fn merge_manifest( + &self, + parent_path: &FilePath, + parent: ManifestFrontEnd, + child_path: &FilePath, + child: ManifestFrontEnd +) -> Result<ManifestFrontEnd, FMLError>

source

fn load_imports( + &self, + current: &FilePath, + channel: Option<&str>, + imports: &mut HashMap<ModuleId, FeatureManifest> +) -> Result<ModuleId, FMLError>

Load a manifest and all its imports, recursively if necessary.

+

We populate a map of FileId to FeatureManifests, so to avoid unnecessary clones, +we return a FileId even when the file has already been imported.

+
source

pub fn get_intermediate_representation( + &self, + channel: Option<&str> +) -> Result<FeatureManifest, FMLError>

source§

impl Parser

source

fn check_can_merge_manifest( + &self, + parent_path: &FilePath, + parent: &ManifestFrontEnd, + child_path: &FilePath, + child: &ManifestFrontEnd +) -> Result<(), FMLError>

source

fn canonicalize_import_paths( + &self, + path: &FilePath, + blocks: &mut Vec<ImportBlock> +) -> Result<(), FMLError>

source

fn check_can_merge_imports( + &self, + path: &FilePath, + blocks: &Vec<ImportBlock>, + map: &mut HashMap<String, String> +) -> Result<(), FMLError>

source

fn merge_import_block_list( + &self, + parent: &[ImportBlock], + child: &[ImportBlock] +) -> Result<Vec<ImportBlock>, FMLError>

Trait Implementations§

source§

impl Debug for Parser

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl !RefUnwindSafe for Parser

§

impl Send for Parser

§

impl Sync for Parser

§

impl Unpin for Parser

§

impl !UnwindSafe for Parser

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/sidebar-items.js b/book/rust-docs/nimbus_fml/sidebar-items.js new file mode 100644 index 0000000000..c4212e5b53 --- /dev/null +++ b/book/rust-docs/nimbus_fml/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"constant":["SUPPORT_URL_LOADING"],"fn":["main"],"mod":["backends","command_line","defaults_merger","error","frontend","intermediate_representation","parser","util"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/util/fn.as_dir.html b/book/rust-docs/nimbus_fml/util/fn.as_dir.html new file mode 100644 index 0000000000..989bba42ba --- /dev/null +++ b/book/rust-docs/nimbus_fml/util/fn.as_dir.html @@ -0,0 +1 @@ +as_dir in nimbus_fml::util - Rust

Function nimbus_fml::util::as_dir

source ·
pub(crate) fn as_dir() -> String
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/util/fn.build_dir.html b/book/rust-docs/nimbus_fml/util/fn.build_dir.html new file mode 100644 index 0000000000..d50e6445d8 --- /dev/null +++ b/book/rust-docs/nimbus_fml/util/fn.build_dir.html @@ -0,0 +1 @@ +build_dir in nimbus_fml::util - Rust

Function nimbus_fml::util::build_dir

source ·
pub(crate) fn build_dir() -> String
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/util/fn.generated_src_dir.html b/book/rust-docs/nimbus_fml/util/fn.generated_src_dir.html new file mode 100644 index 0000000000..0cf894cc27 --- /dev/null +++ b/book/rust-docs/nimbus_fml/util/fn.generated_src_dir.html @@ -0,0 +1 @@ +generated_src_dir in nimbus_fml::util - Rust
pub(crate) fn generated_src_dir() -> String
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/util/fn.join.html b/book/rust-docs/nimbus_fml/util/fn.join.html new file mode 100644 index 0000000000..46d66fabb5 --- /dev/null +++ b/book/rust-docs/nimbus_fml/util/fn.join.html @@ -0,0 +1 @@ +join in nimbus_fml::util - Rust

Function nimbus_fml::util::join

source ·
pub(crate) fn join(base: String, suffix: &str) -> String
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/util/fn.pkg_dir.html b/book/rust-docs/nimbus_fml/util/fn.pkg_dir.html new file mode 100644 index 0000000000..e624b5b35e --- /dev/null +++ b/book/rust-docs/nimbus_fml/util/fn.pkg_dir.html @@ -0,0 +1 @@ +pkg_dir in nimbus_fml::util - Rust

Function nimbus_fml::util::pkg_dir

source ·
pub(crate) fn pkg_dir() -> String
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/util/fn.sdk_dir.html b/book/rust-docs/nimbus_fml/util/fn.sdk_dir.html new file mode 100644 index 0000000000..52cd5b36d6 --- /dev/null +++ b/book/rust-docs/nimbus_fml/util/fn.sdk_dir.html @@ -0,0 +1 @@ +sdk_dir in nimbus_fml::util - Rust

Function nimbus_fml::util::sdk_dir

source ·
pub(crate) fn sdk_dir() -> String
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/util/index.html b/book/rust-docs/nimbus_fml/util/index.html new file mode 100644 index 0000000000..10f6677dcc --- /dev/null +++ b/book/rust-docs/nimbus_fml/util/index.html @@ -0,0 +1 @@ +nimbus_fml::util - Rust
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/util/loaders/constant.GITHUB_USER_CONTENT_DOTCOM.html b/book/rust-docs/nimbus_fml/util/loaders/constant.GITHUB_USER_CONTENT_DOTCOM.html new file mode 100644 index 0000000000..1c94f16958 --- /dev/null +++ b/book/rust-docs/nimbus_fml/util/loaders/constant.GITHUB_USER_CONTENT_DOTCOM.html @@ -0,0 +1 @@ +GITHUB_USER_CONTENT_DOTCOM in nimbus_fml::util::loaders - Rust
pub(crate) const GITHUB_USER_CONTENT_DOTCOM: &str = "https://raw.githubusercontent.com";
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/util/loaders/enum.FilePath.html b/book/rust-docs/nimbus_fml/util/loaders/enum.FilePath.html new file mode 100644 index 0000000000..d44706b7d0 --- /dev/null +++ b/book/rust-docs/nimbus_fml/util/loaders/enum.FilePath.html @@ -0,0 +1,25 @@ +FilePath in nimbus_fml::util::loaders - Rust
pub enum FilePath {
+    Local(PathBuf),
+    Remote(Url),
+}
Expand description

A small enum for working with URLs and relative files

+

Variants§

§

Local(PathBuf)

§

Remote(Url)

Implementations§

source§

impl FilePath

source

pub fn new(cwd: &Path, file: &str) -> Result<Self, FMLError>

source

pub fn join(&self, file: &str) -> Result<Self, FMLError>

Appends a suffix to a path. +If the self is a local file and the suffix is an absolute URL, +then the return is the URL.

+
source

pub fn canonicalize(&self) -> Result<Self, FMLError>

Trait Implementations§

source§

impl Clone for FilePath

source§

fn clone(&self) -> FilePath

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for FilePath

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Display for FilePath

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl From<&Path> for FilePath

source§

fn from(path: &Path) -> Self

Converts to this type from the input type.
source§

impl TryFrom<&FilePath> for ModuleId

§

type Error = FMLError

The type returned in the event of a conversion error.
source§

fn try_from(path: &FilePath) -> Result<Self, FMLError>

Performs the conversion.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/util/loaders/fn.is_dir.html b/book/rust-docs/nimbus_fml/util/loaders/fn.is_dir.html new file mode 100644 index 0000000000..48071bcbf4 --- /dev/null +++ b/book/rust-docs/nimbus_fml/util/loaders/fn.is_dir.html @@ -0,0 +1 @@ +is_dir in nimbus_fml::util::loaders - Rust

Function nimbus_fml::util::loaders::is_dir

source ·
fn is_dir(path_buf: &Path) -> bool
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/util/loaders/index.html b/book/rust-docs/nimbus_fml/util/loaders/index.html new file mode 100644 index 0000000000..81d1c5f93b --- /dev/null +++ b/book/rust-docs/nimbus_fml/util/loaders/index.html @@ -0,0 +1 @@ +nimbus_fml::util::loaders - Rust

Module nimbus_fml::util::loaders

source ·

Structs

Enums

  • A small enum for working with URLs and relative files

Constants

Statics

Functions

\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/util/loaders/sidebar-items.js b/book/rust-docs/nimbus_fml/util/loaders/sidebar-items.js new file mode 100644 index 0000000000..88a0640c8a --- /dev/null +++ b/book/rust-docs/nimbus_fml/util/loaders/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"constant":["GITHUB_USER_CONTENT_DOTCOM"],"enum":["FilePath"],"fn":["is_dir"],"static":["USER_AGENT"],"struct":["FileLoader","LoaderConfig"]}; \ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/util/loaders/static.USER_AGENT.html b/book/rust-docs/nimbus_fml/util/loaders/static.USER_AGENT.html new file mode 100644 index 0000000000..8b0a5e9b8f --- /dev/null +++ b/book/rust-docs/nimbus_fml/util/loaders/static.USER_AGENT.html @@ -0,0 +1 @@ +USER_AGENT in nimbus_fml::util::loaders - Rust
static USER_AGENT: &str
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/util/loaders/struct.FileLoader.html b/book/rust-docs/nimbus_fml/util/loaders/struct.FileLoader.html new file mode 100644 index 0000000000..97cf01ce69 --- /dev/null +++ b/book/rust-docs/nimbus_fml/util/loaders/struct.FileLoader.html @@ -0,0 +1,88 @@ +FileLoader in nimbus_fml::util::loaders - Rust
pub struct FileLoader {
+    cache_dir: Option<PathBuf>,
+    fetch_client: Client,
+    config: BTreeMap<String, FilePath>,
+    cwd: PathBuf,
+}
Expand description

Utility class to abstract away the differences between loading from file and network.

+

With a nod to offline developer experience, files which come from the network +are cached on disk.

+

The cache directory should be in a directory that will get purged on a clean build.

+

This allows us to import files from another repository (via https) or include files +from a local files.

+

The loader is able to resolve a shortcut syntax similar to other package managers.

+

By default a prefix of @XXXX/YYYY: resolves to the main branch XXXX/YYYY Github repo.

+

The config is a map of repository names to paths, URLs or branches.

+

Config files can be loaded

+

Fields§

§cache_dir: Option<PathBuf>§fetch_client: Client§config: BTreeMap<String, FilePath>§cwd: PathBuf

Implementations§

source§

impl FileLoader

source

pub fn new( + cwd: PathBuf, + cache_dir: Option<PathBuf>, + config: BTreeMap<String, FilePath> +) -> Result<Self, FMLError>

source

pub fn add_repo_file(&mut self, file: &FilePath) -> Result<(), FMLError>

Load a file containing mapping of repo names to FilePaths. +Repo files can be JSON or YAML in format. +Files are simple key value pair mappings of repo_id to repository locations, +where:

+
    +
  • a repo id is of the format used on Github: $ORGANIZATION/$PROJECT, and
  • +
  • location can be +
      +
    • a path to a directory on disk, or
    • +
    • a ref/branch/tag/commit hash in the repo stored on Github.
    • +
    +
  • +
+

Relative paths to on disk directories will be taken as relative to this file.

+
source

pub fn add_repo(&mut self, repo_id: &str, loc: &str) -> Result<(), FMLError>

Add a repo and version/tag/ref/location. +repo_id is the github $ORGANIZATION/$PROJECT string, e.g. mozilla/application-services. +The loc string can be a:

+
    +
  1. A branch, commit hash or release tag on a remote repository, hosted on Github
  2. +
  3. A URL
  4. +
  5. A relative path (to the current working directory) to a directory on the local disk.
  6. +
  7. An absolute path to a directory on the local disk.
  8. +
+
source

fn add_repo_relative( + &mut self, + cwd: &FilePath, + repo_id: &str, + loc: &str +) -> Result<(), FMLError>

source

fn remote_file_path( + &self, + repo: &str, + branch_or_tag: &str +) -> Result<FilePath, FMLError>

source

fn default_remote_path(&self, key: String) -> FilePath

source

pub fn read_to_string(&self, file: &FilePath) -> Result<String, FMLError>

This loads a text file from disk or the network.

+

If it’s coming from the network, then cache the file to disk (based on the URL).

+

We don’t worry about cache invalidation, because a clean build should blow the cache +away.

+
source

fn fetch_and_cache(&self, url: &Url) -> Result<String, FMLError>

source

fn create_cache_path_buf(&self, url: &Url) -> PathBuf

source

fn cache_dir(&self) -> &Path

source

fn tmp_cache_dir<'a>(&self) -> &'a Path

source

pub fn join(&self, base: &FilePath, f: &str) -> Result<FilePath, FMLError>

Joins a path to a string, to make a new path.

+

We want to be able to support local and remote files. +We also want to be able to support a configurable short cut format. +Following a pattern common in other package managers, @XXXX/YYYY +is used as short hand for the main branch in github repos.

+

If f is a relative path, the result is relative to base.

+
source

pub fn file_path(&self, f: &str) -> Result<FilePath, FMLError>

Make a new path.

+

We want to be able to support local and remote files. +We also want to be able to support a configurable short cut format. +Following a pattern common in other package managers, @XXXX/YYYY +is used as short hand for the main branch in github repos.

+

If f is a relative path, the result is relative to self.cwd.

+
source

fn resolve_url_shortcut(&self, f: &str) -> Result<Option<FilePath>, FMLError>

Checks that the given string has a @organization/repo/ prefix. +If it does, then use that as a repo_id to look up the FilePath prefix +if it exists in the config, and use the main branch of the github repo if it +doesn’t exist.

+
source

fn lookup_repo_path(&self, user: &str, repo: &str) -> Option<&FilePath>

Trait Implementations§

source§

impl Clone for FileLoader

source§

fn clone(&self) -> FileLoader

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for FileLoader

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Drop for FileLoader

source§

fn drop(&mut self)

Executes the destructor for this type. Read more
source§

impl TryFrom<&LoaderConfig> for FileLoader

§

type Error = FMLError

The type returned in the event of a conversion error.
source§

fn try_from(value: &LoaderConfig) -> Result<Self, Self::Error>

Performs the conversion.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/util/loaders/struct.LoaderConfig.html b/book/rust-docs/nimbus_fml/util/loaders/struct.LoaderConfig.html new file mode 100644 index 0000000000..b21c575aca --- /dev/null +++ b/book/rust-docs/nimbus_fml/util/loaders/struct.LoaderConfig.html @@ -0,0 +1,22 @@ +LoaderConfig in nimbus_fml::util::loaders - Rust
pub struct LoaderConfig {
+    pub cwd: PathBuf,
+    pub repo_files: Vec<String>,
+    pub cache_dir: Option<PathBuf>,
+    pub refs: BTreeMap<String, String>,
+}

Fields§

§cwd: PathBuf§repo_files: Vec<String>§cache_dir: Option<PathBuf>§refs: BTreeMap<String, String>

Implementations§

Trait Implementations§

source§

impl Clone for LoaderConfig

source§

fn clone(&self) -> LoaderConfig

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Default for LoaderConfig

source§

fn default() -> Self

Returns the “default value” for a type. Read more
source§

impl TryFrom<&LoaderConfig> for FileLoader

§

type Error = FMLError

The type returned in the event of a conversion error.
source§

fn try_from(value: &LoaderConfig) -> Result<Self, Self::Error>

Performs the conversion.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/nimbus_fml/util/sidebar-items.js b/book/rust-docs/nimbus_fml/util/sidebar-items.js new file mode 100644 index 0000000000..325ea53dd2 --- /dev/null +++ b/book/rust-docs/nimbus_fml/util/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["as_dir","build_dir","generated_src_dir","join","pkg_dir","sdk_dir"],"mod":["loaders"]}; \ No newline at end of file diff --git a/book/rust-docs/nss/aes/enum.Operation.html b/book/rust-docs/nss/aes/enum.Operation.html new file mode 100644 index 0000000000..fba683e1d4 --- /dev/null +++ b/book/rust-docs/nss/aes/enum.Operation.html @@ -0,0 +1,15 @@ +Operation in nss::aes - Rust

Enum nss::aes::Operation

source ·
pub enum Operation {
+    Encrypt,
+    Decrypt,
+}

Variants§

§

Encrypt

§

Decrypt

Trait Implementations§

source§

impl Clone for Operation

source§

fn clone(&self) -> Operation

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for Operation

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Copy for Operation

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/nss/aes/fn.aes_cbc_crypt.html b/book/rust-docs/nss/aes/fn.aes_cbc_crypt.html new file mode 100644 index 0000000000..097e1ce3f6 --- /dev/null +++ b/book/rust-docs/nss/aes/fn.aes_cbc_crypt.html @@ -0,0 +1,6 @@ +aes_cbc_crypt in nss::aes - Rust

Function nss::aes::aes_cbc_crypt

source ·
pub fn aes_cbc_crypt(
+    key: &[u8],
+    nonce: &[u8],
+    data: &[u8],
+    operation: Operation
+) -> Result<Vec<u8>>
\ No newline at end of file diff --git a/book/rust-docs/nss/aes/fn.aes_gcm_crypt.html b/book/rust-docs/nss/aes/fn.aes_gcm_crypt.html new file mode 100644 index 0000000000..8efccbcdf7 --- /dev/null +++ b/book/rust-docs/nss/aes/fn.aes_gcm_crypt.html @@ -0,0 +1,7 @@ +aes_gcm_crypt in nss::aes - Rust

Function nss::aes::aes_gcm_crypt

source ·
pub fn aes_gcm_crypt(
+    key: &[u8],
+    nonce: &[u8],
+    aad: &[u8],
+    data: &[u8],
+    operation: Operation
+) -> Result<Vec<u8>>
\ No newline at end of file diff --git a/book/rust-docs/nss/aes/fn.common_crypt.html b/book/rust-docs/nss/aes/fn.common_crypt.html new file mode 100644 index 0000000000..120913489d --- /dev/null +++ b/book/rust-docs/nss/aes/fn.common_crypt.html @@ -0,0 +1,8 @@ +common_crypt in nss::aes - Rust

Function nss::aes::common_crypt

source ·
pub fn common_crypt(
+    mech: CK_MECHANISM_TYPE,
+    key: &[u8],
+    data: &[u8],
+    extra_data_len: usize,
+    params: &mut SECItem,
+    operation: Operation
+) -> Result<Vec<u8>>
\ No newline at end of file diff --git a/book/rust-docs/nss/aes/index.html b/book/rust-docs/nss/aes/index.html new file mode 100644 index 0000000000..ef176b1f26 --- /dev/null +++ b/book/rust-docs/nss/aes/index.html @@ -0,0 +1 @@ +nss::aes - Rust
\ No newline at end of file diff --git a/book/rust-docs/nss/aes/sidebar-items.js b/book/rust-docs/nss/aes/sidebar-items.js new file mode 100644 index 0000000000..7ba22c4a5d --- /dev/null +++ b/book/rust-docs/nss/aes/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["Operation"],"fn":["aes_cbc_crypt","aes_gcm_crypt","common_crypt"]}; \ No newline at end of file diff --git a/book/rust-docs/nss/all.html b/book/rust-docs/nss/all.html new file mode 100644 index 0000000000..f56ec4beb9 --- /dev/null +++ b/book/rust-docs/nss/all.html @@ -0,0 +1 @@ +List of all items in this crate
\ No newline at end of file diff --git a/book/rust-docs/nss/cert/fn.extract_ec_public_key.html b/book/rust-docs/nss/cert/fn.extract_ec_public_key.html new file mode 100644 index 0000000000..919befd3e4 --- /dev/null +++ b/book/rust-docs/nss/cert/fn.extract_ec_public_key.html @@ -0,0 +1 @@ +extract_ec_public_key in nss::cert - Rust
pub fn extract_ec_public_key(der: &[u8]) -> Result<Vec<u8>>
\ No newline at end of file diff --git a/book/rust-docs/nss/cert/index.html b/book/rust-docs/nss/cert/index.html new file mode 100644 index 0000000000..53755e6894 --- /dev/null +++ b/book/rust-docs/nss/cert/index.html @@ -0,0 +1 @@ +nss::cert - Rust
\ No newline at end of file diff --git a/book/rust-docs/nss/cert/sidebar-items.js b/book/rust-docs/nss/cert/sidebar-items.js new file mode 100644 index 0000000000..c80528ccc1 --- /dev/null +++ b/book/rust-docs/nss/cert/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["extract_ec_public_key"]}; \ No newline at end of file diff --git a/book/rust-docs/nss/ec/enum.Curve.html b/book/rust-docs/nss/ec/enum.Curve.html new file mode 100644 index 0000000000..265a8c681c --- /dev/null +++ b/book/rust-docs/nss/ec/enum.Curve.html @@ -0,0 +1,20 @@ +Curve in nss::ec - Rust

Enum nss::ec::Curve

source ·
#[repr(u8)]
pub enum Curve { + P256, + P384, +}

Variants§

§

P256

§

P384

Implementations§

Trait Implementations§

source§

impl Clone for Curve

source§

fn clone(&self) -> Curve

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for Curve

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl<'de> Deserialize<'de> for Curve

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl PartialEq<Curve> for Curve

source§

fn eq(&self, other: &Curve) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Serialize for Curve

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more
source§

impl Copy for Curve

source§

impl Eq for Curve

source§

impl StructuralEq for Curve

source§

impl StructuralPartialEq for Curve

Auto Trait Implementations§

§

impl RefUnwindSafe for Curve

§

impl Send for Curve

§

impl Sync for Curve

§

impl Unpin for Curve

§

impl UnwindSafe for Curve

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/nss/ec/fn.generate_keypair.html b/book/rust-docs/nss/ec/fn.generate_keypair.html new file mode 100644 index 0000000000..82cbbe1f2f --- /dev/null +++ b/book/rust-docs/nss/ec/fn.generate_keypair.html @@ -0,0 +1 @@ +generate_keypair in nss::ec - Rust

Function nss::ec::generate_keypair

source ·
pub fn generate_keypair(curve: Curve) -> Result<(PrivateKey, PublicKey)>
\ No newline at end of file diff --git a/book/rust-docs/nss/ec/index.html b/book/rust-docs/nss/ec/index.html new file mode 100644 index 0000000000..efb8a1693c --- /dev/null +++ b/book/rust-docs/nss/ec/index.html @@ -0,0 +1 @@ +nss::ec - Rust
\ No newline at end of file diff --git a/book/rust-docs/nss/ec/sidebar-items.js b/book/rust-docs/nss/ec/sidebar-items.js new file mode 100644 index 0000000000..0c3722a68b --- /dev/null +++ b/book/rust-docs/nss/ec/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["Curve"],"fn":["generate_keypair"],"struct":["EcKey","PrivateKey","PublicKey"]}; \ No newline at end of file diff --git a/book/rust-docs/nss/ec/struct.EcKey.html b/book/rust-docs/nss/ec/struct.EcKey.html new file mode 100644 index 0000000000..dbb6834780 --- /dev/null +++ b/book/rust-docs/nss/ec/struct.EcKey.html @@ -0,0 +1,20 @@ +EcKey in nss::ec - Rust

Struct nss::ec::EcKey

source ·
pub struct EcKey { /* private fields */ }

Implementations§

source§

impl EcKey

source

pub fn new(curve: Curve, private_key: &[u8], public_key: &[u8]) -> Self

source

pub fn from_coordinates( + curve: Curve, + d: &[u8], + x: &[u8], + y: &[u8] +) -> Result<Self>

source

pub fn curve(&self) -> Curve

source

pub fn public_key(&self) -> &[u8]

source

pub fn private_key(&self) -> &[u8]

Trait Implementations§

source§

impl Clone for EcKey

source§

fn clone(&self) -> EcKey

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for EcKey

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl<'de> Deserialize<'de> for EcKey

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl Serialize for EcKey

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more

Auto Trait Implementations§

§

impl RefUnwindSafe for EcKey

§

impl Send for EcKey

§

impl Sync for EcKey

§

impl Unpin for EcKey

§

impl UnwindSafe for EcKey

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/nss/ec/struct.PrivateKey.html b/book/rust-docs/nss/ec/struct.PrivateKey.html new file mode 100644 index 0000000000..5ea2e2ce5c --- /dev/null +++ b/book/rust-docs/nss/ec/struct.PrivateKey.html @@ -0,0 +1,11 @@ +PrivateKey in nss::ec - Rust

Struct nss::ec::PrivateKey

source ·
pub struct PrivateKey { /* private fields */ }

Implementations§

source§

impl PrivateKey

source

pub fn convert_to_public_key(&self) -> Result<PublicKey>

source

pub fn curve(&self) -> Curve

source

pub fn private_value(&self) -> Result<Vec<u8>>

source

pub fn import(ec_key: &EcKey) -> Result<Self>

source

pub fn export(&self) -> Result<EcKey>

Methods from Deref<Target = PK11PrivateKey>§

Trait Implementations§

source§

impl Deref for PrivateKey

§

type Target = PrivateKey

The resulting type after dereferencing.
source§

fn deref(&self) -> &PK11PrivateKey

Dereferences the value.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/nss/ec/struct.PublicKey.html b/book/rust-docs/nss/ec/struct.PublicKey.html new file mode 100644 index 0000000000..4142646f38 --- /dev/null +++ b/book/rust-docs/nss/ec/struct.PublicKey.html @@ -0,0 +1,17 @@ +PublicKey in nss::ec - Rust

Struct nss::ec::PublicKey

source ·
pub struct PublicKey { /* private fields */ }

Implementations§

source§

impl PublicKey

source

pub fn curve(&self) -> Curve

source

pub fn verify( + &self, + message: &[u8], + signature: &[u8], + hash_algorithm: HashAlgorithm +) -> Result<()>

ECDSA verify operation

+
source

pub fn to_bytes(&self) -> Result<Vec<u8>>

source

pub fn from_bytes(curve: Curve, bytes: &[u8]) -> Result<PublicKey>

Trait Implementations§

source§

impl Deref for PublicKey

§

type Target = PublicKey

The resulting type after dereferencing.
source§

fn deref(&self) -> &PK11PublicKey

Dereferences the value.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/nss/ecdh/fn.ecdh_agreement.html b/book/rust-docs/nss/ecdh/fn.ecdh_agreement.html new file mode 100644 index 0000000000..d82bcd133a --- /dev/null +++ b/book/rust-docs/nss/ecdh/fn.ecdh_agreement.html @@ -0,0 +1,4 @@ +ecdh_agreement in nss::ecdh - Rust

Function nss::ecdh::ecdh_agreement

source ·
pub fn ecdh_agreement(
+    priv_key: &PrivateKey,
+    pub_key: &PublicKey
+) -> Result<Vec<u8>>
\ No newline at end of file diff --git a/book/rust-docs/nss/ecdh/index.html b/book/rust-docs/nss/ecdh/index.html new file mode 100644 index 0000000000..16326f58e5 --- /dev/null +++ b/book/rust-docs/nss/ecdh/index.html @@ -0,0 +1 @@ +nss::ecdh - Rust
\ No newline at end of file diff --git a/book/rust-docs/nss/ecdh/sidebar-items.js b/book/rust-docs/nss/ecdh/sidebar-items.js new file mode 100644 index 0000000000..b7e12d334d --- /dev/null +++ b/book/rust-docs/nss/ecdh/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["ecdh_agreement"]}; \ No newline at end of file diff --git a/book/rust-docs/nss/enum.ErrorKind.html b/book/rust-docs/nss/enum.ErrorKind.html new file mode 100644 index 0000000000..d1e30a48dd --- /dev/null +++ b/book/rust-docs/nss/enum.ErrorKind.html @@ -0,0 +1,26 @@ +ErrorKind in nss - Rust

Enum nss::ErrorKind

source ·
pub enum ErrorKind {
+    NSSInitFailure,
+    NSSError(i32, String),
+    SSLError(i32, String),
+    PKIXError(i32, String),
+    InputError(String),
+    InternalError,
+    ConversionError(TryFromIntError),
+    Base64Decode(DecodeError),
+    CertificateIssuerError,
+    CertificateSubjectError,
+    CertificateValidityError,
+}

Variants§

§

NSSInitFailure

§

NSSError(i32, String)

§

SSLError(i32, String)

§

PKIXError(i32, String)

§

InputError(String)

§

InternalError

§

ConversionError(TryFromIntError)

§

Base64Decode(DecodeError)

§

CertificateIssuerError

§

CertificateSubjectError

§

CertificateValidityError

Trait Implementations§

source§

impl Debug for ErrorKind

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Display for ErrorKind

source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Error for ErrorKind

source§

fn source(&self) -> Option<&(dyn Error + 'static)>

The lower-level source of this error, if any. Read more
1.0.0 · source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type based access to context intended for error reports. Read more
source§

impl From<DecodeError> for ErrorKind

source§

fn from(source: DecodeError) -> Self

Converts to this type from the input type.
source§

impl From<ErrorKind> for Error

source§

fn from(ctx: ErrorKind) -> Error

Converts to this type from the input type.
source§

impl From<TryFromIntError> for ErrorKind

source§

fn from(source: TryFromIntError) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<E> Provider for Ewhere + E: Error + ?Sized,

source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (provide_any)
Data providers should implement this method to provide all values they are able to +provide by using demand. Read more
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/nss/error/enum.ErrorKind.html b/book/rust-docs/nss/error/enum.ErrorKind.html new file mode 100644 index 0000000000..a1a5a7a7f7 --- /dev/null +++ b/book/rust-docs/nss/error/enum.ErrorKind.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../nss/enum.ErrorKind.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss/error/struct.Error.html b/book/rust-docs/nss/error/struct.Error.html new file mode 100644 index 0000000000..2fdf84ab5c --- /dev/null +++ b/book/rust-docs/nss/error/struct.Error.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../nss/struct.Error.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss/error/type.Result.html b/book/rust-docs/nss/error/type.Result.html new file mode 100644 index 0000000000..3f5c8dce6e --- /dev/null +++ b/book/rust-docs/nss/error/type.Result.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../nss/type.Result.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss/fn.ensure_initialized.html b/book/rust-docs/nss/fn.ensure_initialized.html new file mode 100644 index 0000000000..506215769f --- /dev/null +++ b/book/rust-docs/nss/fn.ensure_initialized.html @@ -0,0 +1 @@ +ensure_initialized in nss - Rust

Function nss::ensure_initialized

source ·
pub fn ensure_initialized()
\ No newline at end of file diff --git a/book/rust-docs/nss/index.html b/book/rust-docs/nss/index.html new file mode 100644 index 0000000000..409bf123c4 --- /dev/null +++ b/book/rust-docs/nss/index.html @@ -0,0 +1 @@ +nss - Rust
\ No newline at end of file diff --git a/book/rust-docs/nss/macro.scoped_ptr!.html b/book/rust-docs/nss/macro.scoped_ptr!.html new file mode 100644 index 0000000000..261b4e60be --- /dev/null +++ b/book/rust-docs/nss/macro.scoped_ptr!.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to macro.scoped_ptr.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss/macro.scoped_ptr.html b/book/rust-docs/nss/macro.scoped_ptr.html new file mode 100644 index 0000000000..26961c5c5d --- /dev/null +++ b/book/rust-docs/nss/macro.scoped_ptr.html @@ -0,0 +1,3 @@ +scoped_ptr in nss - Rust

Macro nss::scoped_ptr

source ·
macro_rules! scoped_ptr {
+    ($scoped:ident, $target:ty, $dtor:path) => { ... };
+}
\ No newline at end of file diff --git a/book/rust-docs/nss/pbkdf2/fn.pbkdf2_key_derive.html b/book/rust-docs/nss/pbkdf2/fn.pbkdf2_key_derive.html new file mode 100644 index 0000000000..698cb6aa29 --- /dev/null +++ b/book/rust-docs/nss/pbkdf2/fn.pbkdf2_key_derive.html @@ -0,0 +1,7 @@ +pbkdf2_key_derive in nss::pbkdf2 - Rust

Function nss::pbkdf2::pbkdf2_key_derive

source ·
pub fn pbkdf2_key_derive(
+    password: &[u8],
+    salt: &[u8],
+    iterations: u32,
+    hash_algorithm: HashAlgorithm,
+    out: &mut [u8]
+) -> Result<()>
\ No newline at end of file diff --git a/book/rust-docs/nss/pbkdf2/index.html b/book/rust-docs/nss/pbkdf2/index.html new file mode 100644 index 0000000000..16835c159a --- /dev/null +++ b/book/rust-docs/nss/pbkdf2/index.html @@ -0,0 +1 @@ +nss::pbkdf2 - Rust

Module nss::pbkdf2

source ·

Re-exports

Functions

\ No newline at end of file diff --git a/book/rust-docs/nss/pbkdf2/sidebar-items.js b/book/rust-docs/nss/pbkdf2/sidebar-items.js new file mode 100644 index 0000000000..4837735c87 --- /dev/null +++ b/book/rust-docs/nss/pbkdf2/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["pbkdf2_key_derive"]}; \ No newline at end of file diff --git a/book/rust-docs/nss/pk11/context/enum.HashAlgorithm.html b/book/rust-docs/nss/pk11/context/enum.HashAlgorithm.html new file mode 100644 index 0000000000..05e654bac6 --- /dev/null +++ b/book/rust-docs/nss/pk11/context/enum.HashAlgorithm.html @@ -0,0 +1,15 @@ +HashAlgorithm in nss::pk11::context - Rust
#[repr(u8)]
pub enum HashAlgorithm { + SHA256, + SHA384, +}

Variants§

§

SHA256

§

SHA384

Trait Implementations§

source§

impl Clone for HashAlgorithm

source§

fn clone(&self) -> HashAlgorithm

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for HashAlgorithm

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl From<&HashAlgorithm> for SECOidTag

source§

fn from(alg: &HashAlgorithm) -> Self

Converts to this type from the input type.
source§

impl Copy for HashAlgorithm

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/nss/pk11/context/fn.create_context_by_sym_key.html b/book/rust-docs/nss/pk11/context/fn.create_context_by_sym_key.html new file mode 100644 index 0000000000..21ccfab912 --- /dev/null +++ b/book/rust-docs/nss/pk11/context/fn.create_context_by_sym_key.html @@ -0,0 +1,8 @@ +create_context_by_sym_key in nss::pk11::context - Rust
pub fn create_context_by_sym_key(
+    mechanism: CK_MECHANISM_TYPE,
+    operation: CK_ATTRIBUTE_TYPE,
+    sym_key: &SymKey
+) -> Result<Context>
Expand description

Safe wrapper around PK11_CreateContextBySymKey that +de-allocates memory when the context goes out of +scope.

+
\ No newline at end of file diff --git a/book/rust-docs/nss/pk11/context/fn.hash_buf.html b/book/rust-docs/nss/pk11/context/fn.hash_buf.html new file mode 100644 index 0000000000..1c08694309 --- /dev/null +++ b/book/rust-docs/nss/pk11/context/fn.hash_buf.html @@ -0,0 +1 @@ +hash_buf in nss::pk11::context - Rust

Function nss::pk11::context::hash_buf

source ·
pub fn hash_buf(algorithm: &HashAlgorithm, data: &[u8]) -> Result<Vec<u8>>
\ No newline at end of file diff --git a/book/rust-docs/nss/pk11/context/fn.hmac_sign.html b/book/rust-docs/nss/pk11/context/fn.hmac_sign.html new file mode 100644 index 0000000000..440eb706a2 --- /dev/null +++ b/book/rust-docs/nss/pk11/context/fn.hmac_sign.html @@ -0,0 +1,5 @@ +hmac_sign in nss::pk11::context - Rust

Function nss::pk11::context::hmac_sign

source ·
pub fn hmac_sign(
+    digest_alg: &HashAlgorithm,
+    sym_key_bytes: &[u8],
+    data: &[u8]
+) -> Result<Vec<u8>>
\ No newline at end of file diff --git a/book/rust-docs/nss/pk11/context/index.html b/book/rust-docs/nss/pk11/context/index.html new file mode 100644 index 0000000000..60aee5965b --- /dev/null +++ b/book/rust-docs/nss/pk11/context/index.html @@ -0,0 +1,3 @@ +nss::pk11::context - Rust

Module nss::pk11::context

source ·

Enums

Functions

\ No newline at end of file diff --git a/book/rust-docs/nss/pk11/context/sidebar-items.js b/book/rust-docs/nss/pk11/context/sidebar-items.js new file mode 100644 index 0000000000..310e6040c6 --- /dev/null +++ b/book/rust-docs/nss/pk11/context/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["HashAlgorithm"],"fn":["create_context_by_sym_key","hash_buf","hmac_sign"]}; \ No newline at end of file diff --git a/book/rust-docs/nss/pk11/index.html b/book/rust-docs/nss/pk11/index.html new file mode 100644 index 0000000000..e86ee80d79 --- /dev/null +++ b/book/rust-docs/nss/pk11/index.html @@ -0,0 +1 @@ +nss::pk11 - Rust
\ No newline at end of file diff --git a/book/rust-docs/nss/pk11/sidebar-items.js b/book/rust-docs/nss/pk11/sidebar-items.js new file mode 100644 index 0000000000..80329b5992 --- /dev/null +++ b/book/rust-docs/nss/pk11/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"mod":["context","slot","sym_key","types"]}; \ No newline at end of file diff --git a/book/rust-docs/nss/pk11/slot/fn.generate_random.html b/book/rust-docs/nss/pk11/slot/fn.generate_random.html new file mode 100644 index 0000000000..5858a1ec00 --- /dev/null +++ b/book/rust-docs/nss/pk11/slot/fn.generate_random.html @@ -0,0 +1 @@ +generate_random in nss::pk11::slot - Rust

Function nss::pk11::slot::generate_random

source ·
pub fn generate_random(data: &mut [u8]) -> Result<()>
\ No newline at end of file diff --git a/book/rust-docs/nss/pk11/slot/index.html b/book/rust-docs/nss/pk11/slot/index.html new file mode 100644 index 0000000000..095cf8340b --- /dev/null +++ b/book/rust-docs/nss/pk11/slot/index.html @@ -0,0 +1 @@ +nss::pk11::slot - Rust
\ No newline at end of file diff --git a/book/rust-docs/nss/pk11/slot/sidebar-items.js b/book/rust-docs/nss/pk11/slot/sidebar-items.js new file mode 100644 index 0000000000..63c46eb96a --- /dev/null +++ b/book/rust-docs/nss/pk11/slot/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["generate_random"]}; \ No newline at end of file diff --git a/book/rust-docs/nss/pk11/sym_key/fn.hkdf_expand.html b/book/rust-docs/nss/pk11/sym_key/fn.hkdf_expand.html new file mode 100644 index 0000000000..d5958bc2a9 --- /dev/null +++ b/book/rust-docs/nss/pk11/sym_key/fn.hkdf_expand.html @@ -0,0 +1,6 @@ +hkdf_expand in nss::pk11::sym_key - Rust

Function nss::pk11::sym_key::hkdf_expand

source ·
pub fn hkdf_expand(
+    digest_alg: &HashAlgorithm,
+    key_bytes: &[u8],
+    info: &[u8],
+    len: usize
+) -> Result<Vec<u8>>
\ No newline at end of file diff --git a/book/rust-docs/nss/pk11/sym_key/index.html b/book/rust-docs/nss/pk11/sym_key/index.html new file mode 100644 index 0000000000..ae8aaf6e12 --- /dev/null +++ b/book/rust-docs/nss/pk11/sym_key/index.html @@ -0,0 +1 @@ +nss::pk11::sym_key - Rust
\ No newline at end of file diff --git a/book/rust-docs/nss/pk11/sym_key/sidebar-items.js b/book/rust-docs/nss/pk11/sym_key/sidebar-items.js new file mode 100644 index 0000000000..d6147bef77 --- /dev/null +++ b/book/rust-docs/nss/pk11/sym_key/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["hkdf_expand"]}; \ No newline at end of file diff --git a/book/rust-docs/nss/pk11/types/index.html b/book/rust-docs/nss/pk11/types/index.html new file mode 100644 index 0000000000..cf6291abf2 --- /dev/null +++ b/book/rust-docs/nss/pk11/types/index.html @@ -0,0 +1 @@ +nss::pk11::types - Rust
\ No newline at end of file diff --git a/book/rust-docs/nss/pk11/types/sidebar-items.js b/book/rust-docs/nss/pk11/types/sidebar-items.js new file mode 100644 index 0000000000..a4f73688f0 --- /dev/null +++ b/book/rust-docs/nss/pk11/types/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"struct":["AlgorithmID","Certificate","Context","GenericObject","PrivateKey","PublicKey","Slot","SymKey"]}; \ No newline at end of file diff --git a/book/rust-docs/nss/pk11/types/struct.AlgorithmID.html b/book/rust-docs/nss/pk11/types/struct.AlgorithmID.html new file mode 100644 index 0000000000..99eb06be8a --- /dev/null +++ b/book/rust-docs/nss/pk11/types/struct.AlgorithmID.html @@ -0,0 +1,11 @@ +AlgorithmID in nss::pk11::types - Rust

Struct nss::pk11::types::AlgorithmID

source ·
pub struct AlgorithmID { /* private fields */ }

Trait Implementations§

source§

impl Drop for AlgorithmID

source§

fn drop(&mut self)

Executes the destructor for this type. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/nss/pk11/types/struct.Certificate.html b/book/rust-docs/nss/pk11/types/struct.Certificate.html new file mode 100644 index 0000000000..7401b8f15f --- /dev/null +++ b/book/rust-docs/nss/pk11/types/struct.Certificate.html @@ -0,0 +1,11 @@ +Certificate in nss::pk11::types - Rust

Struct nss::pk11::types::Certificate

source ·
pub struct Certificate { /* private fields */ }

Trait Implementations§

source§

impl Drop for Certificate

source§

fn drop(&mut self)

Executes the destructor for this type. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/nss/pk11/types/struct.Context.html b/book/rust-docs/nss/pk11/types/struct.Context.html new file mode 100644 index 0000000000..311373e221 --- /dev/null +++ b/book/rust-docs/nss/pk11/types/struct.Context.html @@ -0,0 +1,11 @@ +Context in nss::pk11::types - Rust

Struct nss::pk11::types::Context

source ·
pub struct Context { /* private fields */ }

Trait Implementations§

source§

impl Drop for Context

source§

fn drop(&mut self)

Executes the destructor for this type. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/nss/pk11/types/struct.GenericObject.html b/book/rust-docs/nss/pk11/types/struct.GenericObject.html new file mode 100644 index 0000000000..54c39e2e2f --- /dev/null +++ b/book/rust-docs/nss/pk11/types/struct.GenericObject.html @@ -0,0 +1,11 @@ +GenericObject in nss::pk11::types - Rust

Struct nss::pk11::types::GenericObject

source ·
pub struct GenericObject { /* private fields */ }

Trait Implementations§

source§

impl Drop for GenericObject

source§

fn drop(&mut self)

Executes the destructor for this type. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/nss/pk11/types/struct.PrivateKey.html b/book/rust-docs/nss/pk11/types/struct.PrivateKey.html new file mode 100644 index 0000000000..e8dbf6755e --- /dev/null +++ b/book/rust-docs/nss/pk11/types/struct.PrivateKey.html @@ -0,0 +1,11 @@ +PrivateKey in nss::pk11::types - Rust

Struct nss::pk11::types::PrivateKey

source ·
pub struct PrivateKey { /* private fields */ }

Implementations§

Trait Implementations§

source§

impl Drop for PrivateKey

source§

fn drop(&mut self)

Executes the destructor for this type. Read more
source§

impl Send for PrivateKey

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/nss/pk11/types/struct.PublicKey.html b/book/rust-docs/nss/pk11/types/struct.PublicKey.html new file mode 100644 index 0000000000..388b12fd6a --- /dev/null +++ b/book/rust-docs/nss/pk11/types/struct.PublicKey.html @@ -0,0 +1,11 @@ +PublicKey in nss::pk11::types - Rust

Struct nss::pk11::types::PublicKey

source ·
pub struct PublicKey { /* private fields */ }

Trait Implementations§

source§

impl Drop for PublicKey

source§

fn drop(&mut self)

Executes the destructor for this type. Read more
source§

impl Send for PublicKey

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/nss/pk11/types/struct.Slot.html b/book/rust-docs/nss/pk11/types/struct.Slot.html new file mode 100644 index 0000000000..0fe46dd052 --- /dev/null +++ b/book/rust-docs/nss/pk11/types/struct.Slot.html @@ -0,0 +1,11 @@ +Slot in nss::pk11::types - Rust

Struct nss::pk11::types::Slot

source ·
pub struct Slot { /* private fields */ }

Trait Implementations§

source§

impl Drop for Slot

source§

fn drop(&mut self)

Executes the destructor for this type. Read more

Auto Trait Implementations§

§

impl RefUnwindSafe for Slot

§

impl !Send for Slot

§

impl !Sync for Slot

§

impl Unpin for Slot

§

impl UnwindSafe for Slot

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/nss/pk11/types/struct.SymKey.html b/book/rust-docs/nss/pk11/types/struct.SymKey.html new file mode 100644 index 0000000000..3163392088 --- /dev/null +++ b/book/rust-docs/nss/pk11/types/struct.SymKey.html @@ -0,0 +1,11 @@ +SymKey in nss::pk11::types - Rust

Struct nss::pk11::types::SymKey

source ·
pub struct SymKey { /* private fields */ }

Trait Implementations§

source§

impl Drop for SymKey

source§

fn drop(&mut self)

Executes the destructor for this type. Read more

Auto Trait Implementations§

§

impl RefUnwindSafe for SymKey

§

impl !Send for SymKey

§

impl !Sync for SymKey

§

impl Unpin for SymKey

§

impl UnwindSafe for SymKey

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/nss/pkixc/fn.verify_code_signing_certificate_chain.html b/book/rust-docs/nss/pkixc/fn.verify_code_signing_certificate_chain.html new file mode 100644 index 0000000000..4dd1f8cdae --- /dev/null +++ b/book/rust-docs/nss/pkixc/fn.verify_code_signing_certificate_chain.html @@ -0,0 +1,6 @@ +verify_code_signing_certificate_chain in nss::pkixc - Rust
pub fn verify_code_signing_certificate_chain(
+    certificates: Vec<&[u8]>,
+    seconds_since_epoch: u64,
+    root_sha256_hash: &[u8],
+    hostname: &str
+) -> Result<()>
\ No newline at end of file diff --git a/book/rust-docs/nss/pkixc/index.html b/book/rust-docs/nss/pkixc/index.html new file mode 100644 index 0000000000..3042a28f01 --- /dev/null +++ b/book/rust-docs/nss/pkixc/index.html @@ -0,0 +1 @@ +nss::pkixc - Rust
\ No newline at end of file diff --git a/book/rust-docs/nss/pkixc/sidebar-items.js b/book/rust-docs/nss/pkixc/sidebar-items.js new file mode 100644 index 0000000000..38d0d0e64f --- /dev/null +++ b/book/rust-docs/nss/pkixc/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["verify_code_signing_certificate_chain"]}; \ No newline at end of file diff --git a/book/rust-docs/nss/secport/fn.secure_memcmp.html b/book/rust-docs/nss/secport/fn.secure_memcmp.html new file mode 100644 index 0000000000..0268efebb8 --- /dev/null +++ b/book/rust-docs/nss/secport/fn.secure_memcmp.html @@ -0,0 +1 @@ +secure_memcmp in nss::secport - Rust

Function nss::secport::secure_memcmp

source ·
pub fn secure_memcmp(a: &[u8], b: &[u8]) -> bool
\ No newline at end of file diff --git a/book/rust-docs/nss/secport/index.html b/book/rust-docs/nss/secport/index.html new file mode 100644 index 0000000000..506e99b2dc --- /dev/null +++ b/book/rust-docs/nss/secport/index.html @@ -0,0 +1 @@ +nss::secport - Rust
\ No newline at end of file diff --git a/book/rust-docs/nss/secport/sidebar-items.js b/book/rust-docs/nss/secport/sidebar-items.js new file mode 100644 index 0000000000..3f6a9cec22 --- /dev/null +++ b/book/rust-docs/nss/secport/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["secure_memcmp"]}; \ No newline at end of file diff --git a/book/rust-docs/nss/sidebar-items.js b/book/rust-docs/nss/sidebar-items.js new file mode 100644 index 0000000000..1da6213e70 --- /dev/null +++ b/book/rust-docs/nss/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["ErrorKind"],"fn":["ensure_initialized"],"macro":["scoped_ptr"],"mod":["aes","cert","ec","ecdh","pbkdf2","pk11","pkixc","secport"],"struct":["Error"],"type":["Result"]}; \ No newline at end of file diff --git a/book/rust-docs/nss/struct.Error.html b/book/rust-docs/nss/struct.Error.html new file mode 100644 index 0000000000..8152396409 --- /dev/null +++ b/book/rust-docs/nss/struct.Error.html @@ -0,0 +1,14 @@ +Error in nss - Rust

Struct nss::Error

source ·
pub struct Error(_);

Implementations§

source§

impl Error

source

pub fn kind(&self) -> &ErrorKind

source

pub fn backtrace(&self) -> Option<&Mutex<Backtrace>>

Trait Implementations§

source§

impl Debug for Error

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Display for Error

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Error for Error

1.30.0 · source§

fn source(&self) -> Option<&(dyn Error + 'static)>

The lower-level source of this error, if any. Read more
1.0.0 · source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type based access to context intended for error reports. Read more
source§

impl From<DecodeError> for Error

source§

fn from(e: DecodeError) -> Self

Converts to this type from the input type.
source§

impl From<ErrorKind> for Error

source§

fn from(ctx: ErrorKind) -> Error

Converts to this type from the input type.
source§

impl From<TryFromIntError> for Error

source§

fn from(e: TryFromIntError) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

§

impl RefUnwindSafe for Error

§

impl Send for Error

§

impl Sync for Error

§

impl Unpin for Error

§

impl UnwindSafe for Error

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<E> Provider for Ewhere + E: Error + ?Sized,

source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (provide_any)
Data providers should implement this method to provide all values they are able to +provide by using demand. Read more
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/nss/type.Result.html b/book/rust-docs/nss/type.Result.html new file mode 100644 index 0000000000..bc12806df3 --- /dev/null +++ b/book/rust-docs/nss/type.Result.html @@ -0,0 +1 @@ +Result in nss - Rust

Type Definition nss::Result

source ·
pub type Result<T, E = Error> = Result<T, E>;
\ No newline at end of file diff --git a/book/rust-docs/nss/util/fn.ensure_nss_initialized.html b/book/rust-docs/nss/util/fn.ensure_nss_initialized.html new file mode 100644 index 0000000000..328bf56f4a --- /dev/null +++ b/book/rust-docs/nss/util/fn.ensure_nss_initialized.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../nss/fn.ensure_initialized.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_build_common/all.html b/book/rust-docs/nss_build_common/all.html new file mode 100644 index 0000000000..743a432863 --- /dev/null +++ b/book/rust-docs/nss_build_common/all.html @@ -0,0 +1 @@ +List of all items in this crate

List of all items

Structs

Enums

Functions

\ No newline at end of file diff --git a/book/rust-docs/nss_build_common/enum.LinkingKind.html b/book/rust-docs/nss_build_common/enum.LinkingKind.html new file mode 100644 index 0000000000..68c6c2c768 --- /dev/null +++ b/book/rust-docs/nss_build_common/enum.LinkingKind.html @@ -0,0 +1,19 @@ +LinkingKind in nss_build_common - Rust
pub enum LinkingKind {
+    Dynamic {
+        folded_libs: bool,
+    },
+    Static,
+}

Variants§

§

Dynamic

Fields

§folded_libs: bool
§

Static

Trait Implementations§

source§

impl Clone for LinkingKind

source§

fn clone(&self) -> LinkingKind

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for LinkingKind

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl PartialEq<LinkingKind> for LinkingKind

source§

fn eq(&self, other: &LinkingKind) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Copy for LinkingKind

source§

impl Eq for LinkingKind

source§

impl StructuralEq for LinkingKind

source§

impl StructuralPartialEq for LinkingKind

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/nss_build_common/fn.env.html b/book/rust-docs/nss_build_common/fn.env.html new file mode 100644 index 0000000000..bce698ee2c --- /dev/null +++ b/book/rust-docs/nss_build_common/fn.env.html @@ -0,0 +1 @@ +env in nss_build_common - Rust

Function nss_build_common::env

source ·
pub fn env(name: &str) -> Option<OsString>
\ No newline at end of file diff --git a/book/rust-docs/nss_build_common/fn.env_flag.html b/book/rust-docs/nss_build_common/fn.env_flag.html new file mode 100644 index 0000000000..4aeb16a189 --- /dev/null +++ b/book/rust-docs/nss_build_common/fn.env_flag.html @@ -0,0 +1 @@ +env_flag in nss_build_common - Rust

Function nss_build_common::env_flag

source ·
pub fn env_flag(name: &str) -> bool
\ No newline at end of file diff --git a/book/rust-docs/nss_build_common/fn.env_str.html b/book/rust-docs/nss_build_common/fn.env_str.html new file mode 100644 index 0000000000..d082f0a808 --- /dev/null +++ b/book/rust-docs/nss_build_common/fn.env_str.html @@ -0,0 +1 @@ +env_str in nss_build_common - Rust

Function nss_build_common::env_str

source ·
pub fn env_str(name: &str) -> Option<String>
\ No newline at end of file diff --git a/book/rust-docs/nss_build_common/fn.link_nss.html b/book/rust-docs/nss_build_common/fn.link_nss.html new file mode 100644 index 0000000000..eb304e11ba --- /dev/null +++ b/book/rust-docs/nss_build_common/fn.link_nss.html @@ -0,0 +1 @@ +link_nss in nss_build_common - Rust

Function nss_build_common::link_nss

source ·
pub fn link_nss() -> Result<(), NoNssDir>
\ No newline at end of file diff --git a/book/rust-docs/nss_build_common/index.html b/book/rust-docs/nss_build_common/index.html new file mode 100644 index 0000000000..aeee824c8e --- /dev/null +++ b/book/rust-docs/nss_build_common/index.html @@ -0,0 +1,5 @@ +nss_build_common - Rust

Crate nss_build_common

source ·
Expand description

This shouldn’t exist, but does because if something isn’t going to link +against nss but has an nss-enabled sqlcipher turned on (for example, +by a cargo feature activated by something else in the workspace). +it might need to issue link commands for NSS.

+

Structs

Enums

Functions

\ No newline at end of file diff --git a/book/rust-docs/nss_build_common/sidebar-items.js b/book/rust-docs/nss_build_common/sidebar-items.js new file mode 100644 index 0000000000..0d3c8c2c05 --- /dev/null +++ b/book/rust-docs/nss_build_common/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["LinkingKind"],"fn":["env","env_flag","env_str","link_nss"],"struct":["NoNssDir"]}; \ No newline at end of file diff --git a/book/rust-docs/nss_build_common/struct.NoNssDir.html b/book/rust-docs/nss_build_common/struct.NoNssDir.html new file mode 100644 index 0000000000..9189d8f40d --- /dev/null +++ b/book/rust-docs/nss_build_common/struct.NoNssDir.html @@ -0,0 +1,14 @@ +NoNssDir in nss_build_common - Rust
pub struct NoNssDir;

Trait Implementations§

source§

impl Clone for NoNssDir

source§

fn clone(&self) -> NoNssDir

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for NoNssDir

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl PartialEq<NoNssDir> for NoNssDir

source§

fn eq(&self, other: &NoNssDir) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Eq for NoNssDir

source§

impl StructuralEq for NoNssDir

source§

impl StructuralPartialEq for NoNssDir

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/all.html b/book/rust-docs/nss_sys/all.html new file mode 100644 index 0000000000..60c1c859c2 --- /dev/null +++ b/book/rust-docs/nss_sys/all.html @@ -0,0 +1 @@ +List of all items in this crate

List of all items

Structs

Enums

Unions

Functions

Type Definitions

Constants

\ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/blapit/constant.AES_BLOCK_SIZE.html b/book/rust-docs/nss_sys/bindings/blapit/constant.AES_BLOCK_SIZE.html new file mode 100644 index 0000000000..2e63aacf46 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/blapit/constant.AES_BLOCK_SIZE.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/constant.AES_BLOCK_SIZE.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/blapit/constant.EC_POINT_FORM_UNCOMPRESSED.html b/book/rust-docs/nss_sys/bindings/blapit/constant.EC_POINT_FORM_UNCOMPRESSED.html new file mode 100644 index 0000000000..a4ced5efcb --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/blapit/constant.EC_POINT_FORM_UNCOMPRESSED.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/constant.EC_POINT_FORM_UNCOMPRESSED.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/blapit/constant.HASH_LENGTH_MAX.html b/book/rust-docs/nss_sys/bindings/blapit/constant.HASH_LENGTH_MAX.html new file mode 100644 index 0000000000..9c3cf313b6 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/blapit/constant.HASH_LENGTH_MAX.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/constant.HASH_LENGTH_MAX.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/blapit/constant.SHA256_LENGTH.html b/book/rust-docs/nss_sys/bindings/blapit/constant.SHA256_LENGTH.html new file mode 100644 index 0000000000..feef589fa8 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/blapit/constant.SHA256_LENGTH.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/constant.SHA256_LENGTH.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/blapit/constant.SHA384_LENGTH.html b/book/rust-docs/nss_sys/bindings/blapit/constant.SHA384_LENGTH.html new file mode 100644 index 0000000000..2099c9f2c2 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/blapit/constant.SHA384_LENGTH.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/constant.SHA384_LENGTH.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/certdb/fn.CERT_DestroyCertificate.html b/book/rust-docs/nss_sys/bindings/certdb/fn.CERT_DestroyCertificate.html new file mode 100644 index 0000000000..e757efe13f --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/certdb/fn.CERT_DestroyCertificate.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/fn.CERT_DestroyCertificate.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/certdb/fn.CERT_GetDefaultCertDB.html b/book/rust-docs/nss_sys/bindings/certdb/fn.CERT_GetDefaultCertDB.html new file mode 100644 index 0000000000..b75dd3fa8c --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/certdb/fn.CERT_GetDefaultCertDB.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/fn.CERT_GetDefaultCertDB.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/certdb/fn.CERT_NewTempCertificate.html b/book/rust-docs/nss_sys/bindings/certdb/fn.CERT_NewTempCertificate.html new file mode 100644 index 0000000000..0560e950f3 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/certdb/fn.CERT_NewTempCertificate.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/fn.CERT_NewTempCertificate.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/certdb/type.CERTCertDBHandle.html b/book/rust-docs/nss_sys/bindings/certdb/type.CERTCertDBHandle.html new file mode 100644 index 0000000000..54c3a6cd00 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/certdb/type.CERTCertDBHandle.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/type.CERTCertDBHandle.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/certdb/type.CERTCertificate.html b/book/rust-docs/nss_sys/bindings/certdb/type.CERTCertificate.html new file mode 100644 index 0000000000..ceaf39779f --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/certdb/type.CERTCertificate.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/type.CERTCertificate.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/keyhi/fn.SECKEY_ConvertToPublicKey.html b/book/rust-docs/nss_sys/bindings/keyhi/fn.SECKEY_ConvertToPublicKey.html new file mode 100644 index 0000000000..9205ff9f51 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/keyhi/fn.SECKEY_ConvertToPublicKey.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/fn.SECKEY_ConvertToPublicKey.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/keyhi/fn.SECKEY_CopyPublicKey.html b/book/rust-docs/nss_sys/bindings/keyhi/fn.SECKEY_CopyPublicKey.html new file mode 100644 index 0000000000..de09a3ff65 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/keyhi/fn.SECKEY_CopyPublicKey.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/fn.SECKEY_CopyPublicKey.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/keyhi/fn.SECKEY_DestroyPrivateKey.html b/book/rust-docs/nss_sys/bindings/keyhi/fn.SECKEY_DestroyPrivateKey.html new file mode 100644 index 0000000000..93bbe2a4e8 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/keyhi/fn.SECKEY_DestroyPrivateKey.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/fn.SECKEY_DestroyPrivateKey.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/keyhi/fn.SECKEY_DestroyPublicKey.html b/book/rust-docs/nss_sys/bindings/keyhi/fn.SECKEY_DestroyPublicKey.html new file mode 100644 index 0000000000..44180bb7db --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/keyhi/fn.SECKEY_DestroyPublicKey.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/fn.SECKEY_DestroyPublicKey.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/keythi/enum.ECPointEncoding.html b/book/rust-docs/nss_sys/bindings/keythi/enum.ECPointEncoding.html new file mode 100644 index 0000000000..30250eac6b --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/keythi/enum.ECPointEncoding.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/enum.ECPointEncoding.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/keythi/enum.KeyType.html b/book/rust-docs/nss_sys/bindings/keythi/enum.KeyType.html new file mode 100644 index 0000000000..cf039fca28 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/keythi/enum.KeyType.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/enum.KeyType.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/keythi/struct.SECKEYDHPublicKeyStr.html b/book/rust-docs/nss_sys/bindings/keythi/struct.SECKEYDHPublicKeyStr.html new file mode 100644 index 0000000000..87e64620b4 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/keythi/struct.SECKEYDHPublicKeyStr.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/struct.SECKEYDHPublicKeyStr.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/keythi/struct.SECKEYDSAPublicKeyStr.html b/book/rust-docs/nss_sys/bindings/keythi/struct.SECKEYDSAPublicKeyStr.html new file mode 100644 index 0000000000..3c4096549d --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/keythi/struct.SECKEYDSAPublicKeyStr.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/struct.SECKEYDSAPublicKeyStr.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/keythi/struct.SECKEYECPublicKeyStr.html b/book/rust-docs/nss_sys/bindings/keythi/struct.SECKEYECPublicKeyStr.html new file mode 100644 index 0000000000..595d3acdf6 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/keythi/struct.SECKEYECPublicKeyStr.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/struct.SECKEYECPublicKeyStr.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/keythi/struct.SECKEYFortezzaPublicKeyStr.html b/book/rust-docs/nss_sys/bindings/keythi/struct.SECKEYFortezzaPublicKeyStr.html new file mode 100644 index 0000000000..cc9b471c84 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/keythi/struct.SECKEYFortezzaPublicKeyStr.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/struct.SECKEYFortezzaPublicKeyStr.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/keythi/struct.SECKEYKEAParamsStr.html b/book/rust-docs/nss_sys/bindings/keythi/struct.SECKEYKEAParamsStr.html new file mode 100644 index 0000000000..d6fbd86a7a --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/keythi/struct.SECKEYKEAParamsStr.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/struct.SECKEYKEAParamsStr.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/keythi/struct.SECKEYKEAPublicKeyStr.html b/book/rust-docs/nss_sys/bindings/keythi/struct.SECKEYKEAPublicKeyStr.html new file mode 100644 index 0000000000..f18f0ba21c --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/keythi/struct.SECKEYKEAPublicKeyStr.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/struct.SECKEYKEAPublicKeyStr.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/keythi/struct.SECKEYPQGParamsStr.html b/book/rust-docs/nss_sys/bindings/keythi/struct.SECKEYPQGParamsStr.html new file mode 100644 index 0000000000..e36bd7ddb1 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/keythi/struct.SECKEYPQGParamsStr.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/struct.SECKEYPQGParamsStr.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/keythi/struct.SECKEYPrivateKeyStr.html b/book/rust-docs/nss_sys/bindings/keythi/struct.SECKEYPrivateKeyStr.html new file mode 100644 index 0000000000..0a403aab86 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/keythi/struct.SECKEYPrivateKeyStr.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/struct.SECKEYPrivateKeyStr.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/keythi/struct.SECKEYPublicKeyStr.html b/book/rust-docs/nss_sys/bindings/keythi/struct.SECKEYPublicKeyStr.html new file mode 100644 index 0000000000..e8e998c7c8 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/keythi/struct.SECKEYPublicKeyStr.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/struct.SECKEYPublicKeyStr.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/keythi/struct.SECKEYRSAPublicKeyStr.html b/book/rust-docs/nss_sys/bindings/keythi/struct.SECKEYRSAPublicKeyStr.html new file mode 100644 index 0000000000..2e4c464463 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/keythi/struct.SECKEYRSAPublicKeyStr.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/struct.SECKEYRSAPublicKeyStr.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/keythi/type.SECKEYDHPublicKey.html b/book/rust-docs/nss_sys/bindings/keythi/type.SECKEYDHPublicKey.html new file mode 100644 index 0000000000..f5acb71524 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/keythi/type.SECKEYDHPublicKey.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/type.SECKEYDHPublicKey.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/keythi/type.SECKEYDSAPublicKey.html b/book/rust-docs/nss_sys/bindings/keythi/type.SECKEYDSAPublicKey.html new file mode 100644 index 0000000000..dca0f782ea --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/keythi/type.SECKEYDSAPublicKey.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/type.SECKEYDSAPublicKey.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/keythi/type.SECKEYECParams.html b/book/rust-docs/nss_sys/bindings/keythi/type.SECKEYECParams.html new file mode 100644 index 0000000000..33e89c307e --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/keythi/type.SECKEYECParams.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/type.SECKEYECParams.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/keythi/type.SECKEYECPublicKey.html b/book/rust-docs/nss_sys/bindings/keythi/type.SECKEYECPublicKey.html new file mode 100644 index 0000000000..6b268bd9d9 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/keythi/type.SECKEYECPublicKey.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/type.SECKEYECPublicKey.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/keythi/type.SECKEYFortezzaPublicKey.html b/book/rust-docs/nss_sys/bindings/keythi/type.SECKEYFortezzaPublicKey.html new file mode 100644 index 0000000000..090a7e0da3 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/keythi/type.SECKEYFortezzaPublicKey.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/type.SECKEYFortezzaPublicKey.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/keythi/type.SECKEYKEAParams.html b/book/rust-docs/nss_sys/bindings/keythi/type.SECKEYKEAParams.html new file mode 100644 index 0000000000..dcf5ca5bae --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/keythi/type.SECKEYKEAParams.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/type.SECKEYKEAParams.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/keythi/type.SECKEYKEAPublicKey.html b/book/rust-docs/nss_sys/bindings/keythi/type.SECKEYKEAPublicKey.html new file mode 100644 index 0000000000..7467945e55 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/keythi/type.SECKEYKEAPublicKey.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/type.SECKEYKEAPublicKey.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/keythi/type.SECKEYPQGParams.html b/book/rust-docs/nss_sys/bindings/keythi/type.SECKEYPQGParams.html new file mode 100644 index 0000000000..cf1f638b93 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/keythi/type.SECKEYPQGParams.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/type.SECKEYPQGParams.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/keythi/type.SECKEYPrivateKey.html b/book/rust-docs/nss_sys/bindings/keythi/type.SECKEYPrivateKey.html new file mode 100644 index 0000000000..4aa9c8cc22 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/keythi/type.SECKEYPrivateKey.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/type.SECKEYPrivateKey.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/keythi/type.SECKEYPublicKey.html b/book/rust-docs/nss_sys/bindings/keythi/type.SECKEYPublicKey.html new file mode 100644 index 0000000000..c16c71f805 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/keythi/type.SECKEYPublicKey.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/type.SECKEYPublicKey.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/keythi/type.SECKEYRSAPublicKey.html b/book/rust-docs/nss_sys/bindings/keythi/type.SECKEYRSAPublicKey.html new file mode 100644 index 0000000000..b563a0197c --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/keythi/type.SECKEYRSAPublicKey.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/type.SECKEYRSAPublicKey.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/keythi/union.SECKEYPublicKeyStr_u.html b/book/rust-docs/nss_sys/bindings/keythi/union.SECKEYPublicKeyStr_u.html new file mode 100644 index 0000000000..a5e3592693 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/keythi/union.SECKEYPublicKeyStr_u.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/union.SECKEYPublicKeyStr_u.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/nss/constant.NSS_INIT_FORCEOPEN.html b/book/rust-docs/nss_sys/bindings/nss/constant.NSS_INIT_FORCEOPEN.html new file mode 100644 index 0000000000..e39fdf7807 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/nss/constant.NSS_INIT_FORCEOPEN.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/constant.NSS_INIT_FORCEOPEN.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/nss/constant.NSS_INIT_NOCERTDB.html b/book/rust-docs/nss_sys/bindings/nss/constant.NSS_INIT_NOCERTDB.html new file mode 100644 index 0000000000..487d619141 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/nss/constant.NSS_INIT_NOCERTDB.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/constant.NSS_INIT_NOCERTDB.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/nss/constant.NSS_INIT_NOMODDB.html b/book/rust-docs/nss_sys/bindings/nss/constant.NSS_INIT_NOMODDB.html new file mode 100644 index 0000000000..9a1b586a2c --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/nss/constant.NSS_INIT_NOMODDB.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/constant.NSS_INIT_NOMODDB.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/nss/constant.NSS_INIT_OPTIMIZESPACE.html b/book/rust-docs/nss_sys/bindings/nss/constant.NSS_INIT_OPTIMIZESPACE.html new file mode 100644 index 0000000000..9e16e274cf --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/nss/constant.NSS_INIT_OPTIMIZESPACE.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/constant.NSS_INIT_OPTIMIZESPACE.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/nss/constant.NSS_INIT_READONLY.html b/book/rust-docs/nss_sys/bindings/nss/constant.NSS_INIT_READONLY.html new file mode 100644 index 0000000000..314a6de34a --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/nss/constant.NSS_INIT_READONLY.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/constant.NSS_INIT_READONLY.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/nss/fn.NSS_InitContext.html b/book/rust-docs/nss_sys/bindings/nss/fn.NSS_InitContext.html new file mode 100644 index 0000000000..6ada99c262 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/nss/fn.NSS_InitContext.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/fn.NSS_InitContext.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/nss/fn.NSS_VersionCheck.html b/book/rust-docs/nss_sys/bindings/nss/fn.NSS_VersionCheck.html new file mode 100644 index 0000000000..540d69170c --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/nss/fn.NSS_VersionCheck.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/fn.NSS_VersionCheck.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/nss/type.NSSInitContext.html b/book/rust-docs/nss_sys/bindings/nss/type.NSSInitContext.html new file mode 100644 index 0000000000..69c040761a --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/nss/type.NSSInitContext.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/type.NSSInitContext.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/nss/type.NSSInitParameters.html b/book/rust-docs/nss_sys/bindings/nss/type.NSSInitParameters.html new file mode 100644 index 0000000000..1d651ab233 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/nss/type.NSSInitParameters.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/type.NSSInitParameters.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_CreateContextBySymKey.html b/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_CreateContextBySymKey.html new file mode 100644 index 0000000000..286fd152a0 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_CreateContextBySymKey.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/fn.PK11_CreateContextBySymKey.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_CreateGenericObject.html b/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_CreateGenericObject.html new file mode 100644 index 0000000000..194ea55490 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_CreateGenericObject.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/fn.PK11_CreateGenericObject.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_CreatePBEV2AlgorithmID.html b/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_CreatePBEV2AlgorithmID.html new file mode 100644 index 0000000000..b61d382ed9 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_CreatePBEV2AlgorithmID.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/fn.PK11_CreatePBEV2AlgorithmID.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_Decrypt.html b/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_Decrypt.html new file mode 100644 index 0000000000..e3ad1aa581 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_Decrypt.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/fn.PK11_Decrypt.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_Derive.html b/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_Derive.html new file mode 100644 index 0000000000..c661bf97bb --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_Derive.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/fn.PK11_Derive.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_DestroyContext.html b/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_DestroyContext.html new file mode 100644 index 0000000000..4bc3e27be0 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_DestroyContext.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/fn.PK11_DestroyContext.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_DestroyGenericObject.html b/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_DestroyGenericObject.html new file mode 100644 index 0000000000..c08a10a957 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_DestroyGenericObject.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/fn.PK11_DestroyGenericObject.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_DigestBegin.html b/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_DigestBegin.html new file mode 100644 index 0000000000..2cfd8243b3 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_DigestBegin.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/fn.PK11_DigestBegin.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_DigestFinal.html b/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_DigestFinal.html new file mode 100644 index 0000000000..cfc33385d7 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_DigestFinal.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/fn.PK11_DigestFinal.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_DigestOp.html b/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_DigestOp.html new file mode 100644 index 0000000000..f6601757dc --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_DigestOp.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/fn.PK11_DigestOp.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_Encrypt.html b/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_Encrypt.html new file mode 100644 index 0000000000..2b59cd9c56 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_Encrypt.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/fn.PK11_Encrypt.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_ExtractKeyValue.html b/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_ExtractKeyValue.html new file mode 100644 index 0000000000..de66ebc9d0 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_ExtractKeyValue.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/fn.PK11_ExtractKeyValue.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_FindKeyByKeyID.html b/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_FindKeyByKeyID.html new file mode 100644 index 0000000000..fa412e8b2d --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_FindKeyByKeyID.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/fn.PK11_FindKeyByKeyID.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_FreeSlot.html b/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_FreeSlot.html new file mode 100644 index 0000000000..d96e08c779 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_FreeSlot.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/fn.PK11_FreeSlot.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_FreeSymKey.html b/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_FreeSymKey.html new file mode 100644 index 0000000000..7770284458 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_FreeSymKey.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/fn.PK11_FreeSymKey.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_GenerateKeyPair.html b/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_GenerateKeyPair.html new file mode 100644 index 0000000000..b7824d4424 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_GenerateKeyPair.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/fn.PK11_GenerateKeyPair.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_GenerateRandom.html b/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_GenerateRandom.html new file mode 100644 index 0000000000..38b61c9d1b --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_GenerateRandom.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/fn.PK11_GenerateRandom.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_GetInternalSlot.html b/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_GetInternalSlot.html new file mode 100644 index 0000000000..7fcbabfec5 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_GetInternalSlot.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/fn.PK11_GetInternalSlot.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_GetKeyData.html b/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_GetKeyData.html new file mode 100644 index 0000000000..3d675548db --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_GetKeyData.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/fn.PK11_GetKeyData.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_HashBuf.html b/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_HashBuf.html new file mode 100644 index 0000000000..7eaa57371a --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_HashBuf.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/fn.PK11_HashBuf.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_ImportSymKey.html b/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_ImportSymKey.html new file mode 100644 index 0000000000..55021d94b2 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_ImportSymKey.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/fn.PK11_ImportSymKey.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_MapSignKeyType.html b/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_MapSignKeyType.html new file mode 100644 index 0000000000..5eb84ac6e3 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_MapSignKeyType.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/fn.PK11_MapSignKeyType.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_PBEKeyGen.html b/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_PBEKeyGen.html new file mode 100644 index 0000000000..adcaabb2f6 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_PBEKeyGen.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/fn.PK11_PBEKeyGen.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_PubDeriveWithKDF.html b/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_PubDeriveWithKDF.html new file mode 100644 index 0000000000..1ecd8f685a --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_PubDeriveWithKDF.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/fn.PK11_PubDeriveWithKDF.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_ReadRawAttribute.html b/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_ReadRawAttribute.html new file mode 100644 index 0000000000..1d4ea77a36 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_ReadRawAttribute.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/fn.PK11_ReadRawAttribute.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_VerifyWithMechanism.html b/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_VerifyWithMechanism.html new file mode 100644 index 0000000000..e8afc728fd --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pk11pub/fn.PK11_VerifyWithMechanism.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/fn.PK11_VerifyWithMechanism.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pkcs11n/constant.CKM_NSS.html b/book/rust-docs/nss_sys/bindings/pkcs11n/constant.CKM_NSS.html new file mode 100644 index 0000000000..a848ef3bac --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pkcs11n/constant.CKM_NSS.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/constant.CKM_NSS.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pkcs11n/constant.CKM_NSS_HKDF_SHA256.html b/book/rust-docs/nss_sys/bindings/pkcs11n/constant.CKM_NSS_HKDF_SHA256.html new file mode 100644 index 0000000000..76c383796a --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pkcs11n/constant.CKM_NSS_HKDF_SHA256.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/constant.CKM_NSS_HKDF_SHA256.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pkcs11n/constant.CKM_NSS_HKDF_SHA384.html b/book/rust-docs/nss_sys/bindings/pkcs11n/constant.CKM_NSS_HKDF_SHA384.html new file mode 100644 index 0000000000..24d5d1b01d --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pkcs11n/constant.CKM_NSS_HKDF_SHA384.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/constant.CKM_NSS_HKDF_SHA384.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pkcs11n/constant.NSSCK_VENDOR_NSS.html b/book/rust-docs/nss_sys/bindings/pkcs11n/constant.NSSCK_VENDOR_NSS.html new file mode 100644 index 0000000000..2c1a324912 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pkcs11n/constant.NSSCK_VENDOR_NSS.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/constant.NSSCK_VENDOR_NSS.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pkcs11n/struct.CK_GCM_PARAMS_V3.html b/book/rust-docs/nss_sys/bindings/pkcs11n/struct.CK_GCM_PARAMS_V3.html new file mode 100644 index 0000000000..8ef6898c85 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pkcs11n/struct.CK_GCM_PARAMS_V3.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/struct.CK_GCM_PARAMS_V3.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pkcs11n/struct.CK_NSS_HKDFParams.html b/book/rust-docs/nss_sys/bindings/pkcs11n/struct.CK_NSS_HKDFParams.html new file mode 100644 index 0000000000..74a8455136 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pkcs11n/struct.CK_NSS_HKDFParams.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/struct.CK_NSS_HKDFParams.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pkcs11n/type.CK_GCM_PARAMS.html b/book/rust-docs/nss_sys/bindings/pkcs11n/type.CK_GCM_PARAMS.html new file mode 100644 index 0000000000..a6535540bb --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pkcs11n/type.CK_GCM_PARAMS.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/type.CK_GCM_PARAMS.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKA_CLASS.html b/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKA_CLASS.html new file mode 100644 index 0000000000..c7eca7a343 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKA_CLASS.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/constant.CKA_CLASS.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKA_EC_PARAMS.html b/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKA_EC_PARAMS.html new file mode 100644 index 0000000000..40a5f9d61b --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKA_EC_PARAMS.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/constant.CKA_EC_PARAMS.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKA_EC_POINT.html b/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKA_EC_POINT.html new file mode 100644 index 0000000000..4d7c68af4d --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKA_EC_POINT.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/constant.CKA_EC_POINT.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKA_ENCRYPT.html b/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKA_ENCRYPT.html new file mode 100644 index 0000000000..438a646ec0 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKA_ENCRYPT.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/constant.CKA_ENCRYPT.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKA_ID.html b/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKA_ID.html new file mode 100644 index 0000000000..c7db5830ea --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKA_ID.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/constant.CKA_ID.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKA_KEY_TYPE.html b/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKA_KEY_TYPE.html new file mode 100644 index 0000000000..62b51f9518 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKA_KEY_TYPE.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/constant.CKA_KEY_TYPE.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKA_PRIVATE.html b/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKA_PRIVATE.html new file mode 100644 index 0000000000..1ababf76b1 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKA_PRIVATE.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/constant.CKA_PRIVATE.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKA_SENSITIVE.html b/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKA_SENSITIVE.html new file mode 100644 index 0000000000..ae67d72574 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKA_SENSITIVE.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/constant.CKA_SENSITIVE.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKA_SIGN.html b/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKA_SIGN.html new file mode 100644 index 0000000000..dc74ce9849 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKA_SIGN.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/constant.CKA_SIGN.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKA_TOKEN.html b/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKA_TOKEN.html new file mode 100644 index 0000000000..846889ad84 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKA_TOKEN.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/constant.CKA_TOKEN.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKA_VALUE.html b/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKA_VALUE.html new file mode 100644 index 0000000000..0ccd0ad35a --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKA_VALUE.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/constant.CKA_VALUE.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKA_WRAP.html b/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKA_WRAP.html new file mode 100644 index 0000000000..eefa992c34 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKA_WRAP.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/constant.CKA_WRAP.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKD_NULL.html b/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKD_NULL.html new file mode 100644 index 0000000000..9dee18ef7a --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKD_NULL.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/constant.CKD_NULL.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKK_EC.html b/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKK_EC.html new file mode 100644 index 0000000000..aed57f27b4 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKK_EC.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/constant.CKK_EC.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKM_AES_CBC_PAD.html b/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKM_AES_CBC_PAD.html new file mode 100644 index 0000000000..985552c528 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKM_AES_CBC_PAD.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/constant.CKM_AES_CBC_PAD.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKM_AES_GCM.html b/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKM_AES_GCM.html new file mode 100644 index 0000000000..e575343973 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKM_AES_GCM.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/constant.CKM_AES_GCM.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKM_ECDH1_DERIVE.html b/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKM_ECDH1_DERIVE.html new file mode 100644 index 0000000000..097ecde28e --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKM_ECDH1_DERIVE.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/constant.CKM_ECDH1_DERIVE.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKM_EC_KEY_PAIR_GEN.html b/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKM_EC_KEY_PAIR_GEN.html new file mode 100644 index 0000000000..b36d79984d --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKM_EC_KEY_PAIR_GEN.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/constant.CKM_EC_KEY_PAIR_GEN.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKM_SHA256_HMAC.html b/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKM_SHA256_HMAC.html new file mode 100644 index 0000000000..fee636f822 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKM_SHA256_HMAC.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/constant.CKM_SHA256_HMAC.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKM_SHA384_HMAC.html b/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKM_SHA384_HMAC.html new file mode 100644 index 0000000000..83fcb89d3f --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKM_SHA384_HMAC.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/constant.CKM_SHA384_HMAC.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKM_SHA512_HMAC.html b/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKM_SHA512_HMAC.html new file mode 100644 index 0000000000..889ac25a01 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKM_SHA512_HMAC.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/constant.CKM_SHA512_HMAC.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKM_VENDOR_DEFINED.html b/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKM_VENDOR_DEFINED.html new file mode 100644 index 0000000000..ff6dad8288 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKM_VENDOR_DEFINED.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/constant.CKM_VENDOR_DEFINED.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKO_PRIVATE_KEY.html b/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKO_PRIVATE_KEY.html new file mode 100644 index 0000000000..b7dc08b7b7 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CKO_PRIVATE_KEY.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/constant.CKO_PRIVATE_KEY.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CK_FALSE.html b/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CK_FALSE.html new file mode 100644 index 0000000000..fccae935f2 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CK_FALSE.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/constant.CK_FALSE.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CK_INVALID_HANDLE.html b/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CK_INVALID_HANDLE.html new file mode 100644 index 0000000000..25a3262057 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CK_INVALID_HANDLE.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/constant.CK_INVALID_HANDLE.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CK_TRUE.html b/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CK_TRUE.html new file mode 100644 index 0000000000..f3df3fc841 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pkcs11t/constant.CK_TRUE.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/constant.CK_TRUE.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pkcs11t/struct.CK_ATTRIBUTE.html b/book/rust-docs/nss_sys/bindings/pkcs11t/struct.CK_ATTRIBUTE.html new file mode 100644 index 0000000000..c26b2e28fd --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pkcs11t/struct.CK_ATTRIBUTE.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/struct.CK_ATTRIBUTE.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pkcs11t/type.CK_ATTRIBUTE_TYPE.html b/book/rust-docs/nss_sys/bindings/pkcs11t/type.CK_ATTRIBUTE_TYPE.html new file mode 100644 index 0000000000..1243e365a6 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pkcs11t/type.CK_ATTRIBUTE_TYPE.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/type.CK_ATTRIBUTE_TYPE.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pkcs11t/type.CK_BBOOL.html b/book/rust-docs/nss_sys/bindings/pkcs11t/type.CK_BBOOL.html new file mode 100644 index 0000000000..122a988c1c --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pkcs11t/type.CK_BBOOL.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/type.CK_BBOOL.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pkcs11t/type.CK_BYTE.html b/book/rust-docs/nss_sys/bindings/pkcs11t/type.CK_BYTE.html new file mode 100644 index 0000000000..0e023d4bbc --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pkcs11t/type.CK_BYTE.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/type.CK_BYTE.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pkcs11t/type.CK_BYTE_PTR.html b/book/rust-docs/nss_sys/bindings/pkcs11t/type.CK_BYTE_PTR.html new file mode 100644 index 0000000000..a09e1b5dab --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pkcs11t/type.CK_BYTE_PTR.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/type.CK_BYTE_PTR.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pkcs11t/type.CK_KEY_TYPE.html b/book/rust-docs/nss_sys/bindings/pkcs11t/type.CK_KEY_TYPE.html new file mode 100644 index 0000000000..f0d082aa50 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pkcs11t/type.CK_KEY_TYPE.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/type.CK_KEY_TYPE.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pkcs11t/type.CK_MECHANISM_TYPE.html b/book/rust-docs/nss_sys/bindings/pkcs11t/type.CK_MECHANISM_TYPE.html new file mode 100644 index 0000000000..083f17e28f --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pkcs11t/type.CK_MECHANISM_TYPE.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/type.CK_MECHANISM_TYPE.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pkcs11t/type.CK_OBJECT_CLASS.html b/book/rust-docs/nss_sys/bindings/pkcs11t/type.CK_OBJECT_CLASS.html new file mode 100644 index 0000000000..703b374fe7 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pkcs11t/type.CK_OBJECT_CLASS.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/type.CK_OBJECT_CLASS.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pkcs11t/type.CK_OBJECT_HANDLE.html b/book/rust-docs/nss_sys/bindings/pkcs11t/type.CK_OBJECT_HANDLE.html new file mode 100644 index 0000000000..264551f618 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pkcs11t/type.CK_OBJECT_HANDLE.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/type.CK_OBJECT_HANDLE.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pkcs11t/type.CK_ULONG.html b/book/rust-docs/nss_sys/bindings/pkcs11t/type.CK_ULONG.html new file mode 100644 index 0000000000..979f594430 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pkcs11t/type.CK_ULONG.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/type.CK_ULONG.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pkcs11t/type.CK_VOID_PTR.html b/book/rust-docs/nss_sys/bindings/pkcs11t/type.CK_VOID_PTR.html new file mode 100644 index 0000000000..a79c7d2634 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pkcs11t/type.CK_VOID_PTR.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/type.CK_VOID_PTR.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/pkixc/fn.VerifyCodeSigningCertificateChain.html b/book/rust-docs/nss_sys/bindings/pkixc/fn.VerifyCodeSigningCertificateChain.html new file mode 100644 index 0000000000..63798996d2 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/pkixc/fn.VerifyCodeSigningCertificateChain.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/fn.VerifyCodeSigningCertificateChain.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/plarena/struct.PLArena.html b/book/rust-docs/nss_sys/bindings/plarena/struct.PLArena.html new file mode 100644 index 0000000000..f0841d8d56 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/plarena/struct.PLArena.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/struct.PLArena.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/plarena/struct.PLArenaPool.html b/book/rust-docs/nss_sys/bindings/plarena/struct.PLArenaPool.html new file mode 100644 index 0000000000..50d665b4e0 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/plarena/struct.PLArenaPool.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/struct.PLArenaPool.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/prerror/fn.PR_GetError.html b/book/rust-docs/nss_sys/bindings/prerror/fn.PR_GetError.html new file mode 100644 index 0000000000..f36725b40f --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/prerror/fn.PR_GetError.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/fn.PR_GetError.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/prerror/fn.PR_GetErrorText.html b/book/rust-docs/nss_sys/bindings/prerror/fn.PR_GetErrorText.html new file mode 100644 index 0000000000..fb827f480b --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/prerror/fn.PR_GetErrorText.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/fn.PR_GetErrorText.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/prerror/fn.PR_GetErrorTextLength.html b/book/rust-docs/nss_sys/bindings/prerror/fn.PR_GetErrorTextLength.html new file mode 100644 index 0000000000..c534d39ca9 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/prerror/fn.PR_GetErrorTextLength.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/fn.PR_GetErrorTextLength.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/prerror/type.PRErrorCode.html b/book/rust-docs/nss_sys/bindings/prerror/type.PRErrorCode.html new file mode 100644 index 0000000000..e650efb82a --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/prerror/type.PRErrorCode.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/type.PRErrorCode.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/prtypes/constant.PR_FALSE.html b/book/rust-docs/nss_sys/bindings/prtypes/constant.PR_FALSE.html new file mode 100644 index 0000000000..276066880c --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/prtypes/constant.PR_FALSE.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/constant.PR_FALSE.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/prtypes/constant.PR_TRUE.html b/book/rust-docs/nss_sys/bindings/prtypes/constant.PR_TRUE.html new file mode 100644 index 0000000000..d75c975293 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/prtypes/constant.PR_TRUE.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/constant.PR_TRUE.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/prtypes/type.PRBool.html b/book/rust-docs/nss_sys/bindings/prtypes/type.PRBool.html new file mode 100644 index 0000000000..c878c63450 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/prtypes/type.PRBool.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/type.PRBool.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/prtypes/type.PRInt32.html b/book/rust-docs/nss_sys/bindings/prtypes/type.PRInt32.html new file mode 100644 index 0000000000..dc343f3914 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/prtypes/type.PRInt32.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/type.PRInt32.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/prtypes/type.PRIntn.html b/book/rust-docs/nss_sys/bindings/prtypes/type.PRIntn.html new file mode 100644 index 0000000000..494ee72efe --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/prtypes/type.PRIntn.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/type.PRIntn.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/prtypes/type.PRUint32.html b/book/rust-docs/nss_sys/bindings/prtypes/type.PRUint32.html new file mode 100644 index 0000000000..e53f64cf7d --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/prtypes/type.PRUint32.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/type.PRUint32.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/prtypes/type.PRUword.html b/book/rust-docs/nss_sys/bindings/prtypes/type.PRUword.html new file mode 100644 index 0000000000..7e7aa057f4 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/prtypes/type.PRUword.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/type.PRUword.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/secasn1t/constant.SEC_ASN1_OBJECT_ID.html b/book/rust-docs/nss_sys/bindings/secasn1t/constant.SEC_ASN1_OBJECT_ID.html new file mode 100644 index 0000000000..23c42a63d8 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/secasn1t/constant.SEC_ASN1_OBJECT_ID.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/constant.SEC_ASN1_OBJECT_ID.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/seccomon/enum.SECItemType.html b/book/rust-docs/nss_sys/bindings/seccomon/enum.SECItemType.html new file mode 100644 index 0000000000..2be51f51af --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/seccomon/enum.SECItemType.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/enum.SECItemType.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/seccomon/enum._SECStatus.html b/book/rust-docs/nss_sys/bindings/seccomon/enum._SECStatus.html new file mode 100644 index 0000000000..cbaf1fe76b --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/seccomon/enum._SECStatus.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/enum._SECStatus.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/seccomon/struct.SECItemStr.html b/book/rust-docs/nss_sys/bindings/seccomon/struct.SECItemStr.html new file mode 100644 index 0000000000..461c397532 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/seccomon/struct.SECItemStr.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/struct.SECItemStr.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/seccomon/type.SECItem.html b/book/rust-docs/nss_sys/bindings/seccomon/type.SECItem.html new file mode 100644 index 0000000000..ccbe688364 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/seccomon/type.SECItem.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/type.SECItem.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/secitem/fn.SECITEM_FreeItem.html b/book/rust-docs/nss_sys/bindings/secitem/fn.SECITEM_FreeItem.html new file mode 100644 index 0000000000..92c3de2485 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/secitem/fn.SECITEM_FreeItem.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/fn.SECITEM_FreeItem.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/seckey/fn.CERT_ExtractPublicKey.html b/book/rust-docs/nss_sys/bindings/seckey/fn.CERT_ExtractPublicKey.html new file mode 100644 index 0000000000..3ea83c43ba --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/seckey/fn.CERT_ExtractPublicKey.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/fn.CERT_ExtractPublicKey.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/secmodt/enum.PK11ObjectType.html b/book/rust-docs/nss_sys/bindings/secmodt/enum.PK11ObjectType.html new file mode 100644 index 0000000000..3124c8e2b8 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/secmodt/enum.PK11ObjectType.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/enum.PK11ObjectType.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/secmodt/enum.PK11Origin.html b/book/rust-docs/nss_sys/bindings/secmodt/enum.PK11Origin.html new file mode 100644 index 0000000000..e908fe4388 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/secmodt/enum.PK11Origin.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/enum.PK11Origin.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/secmodt/type.PK11Context.html b/book/rust-docs/nss_sys/bindings/secmodt/type.PK11Context.html new file mode 100644 index 0000000000..82197b25de --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/secmodt/type.PK11Context.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/type.PK11Context.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/secmodt/type.PK11GenericObject.html b/book/rust-docs/nss_sys/bindings/secmodt/type.PK11GenericObject.html new file mode 100644 index 0000000000..3e1a62249d --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/secmodt/type.PK11GenericObject.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/type.PK11GenericObject.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/secmodt/type.PK11SlotInfo.html b/book/rust-docs/nss_sys/bindings/secmodt/type.PK11SlotInfo.html new file mode 100644 index 0000000000..f954f5e60e --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/secmodt/type.PK11SlotInfo.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/type.PK11SlotInfo.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/secmodt/type.PK11SymKey.html b/book/rust-docs/nss_sys/bindings/secmodt/type.PK11SymKey.html new file mode 100644 index 0000000000..fa7ae5987b --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/secmodt/type.PK11SymKey.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/type.PK11SymKey.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/secoid/fn.SECOID_DestroyAlgorithmID.html b/book/rust-docs/nss_sys/bindings/secoid/fn.SECOID_DestroyAlgorithmID.html new file mode 100644 index 0000000000..3477e87469 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/secoid/fn.SECOID_DestroyAlgorithmID.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/fn.SECOID_DestroyAlgorithmID.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/secoid/fn.SECOID_FindOIDByTag.html b/book/rust-docs/nss_sys/bindings/secoid/fn.SECOID_FindOIDByTag.html new file mode 100644 index 0000000000..47f827a1a5 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/secoid/fn.SECOID_FindOIDByTag.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/fn.SECOID_FindOIDByTag.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/secoidt/enum.SECOidTag.html b/book/rust-docs/nss_sys/bindings/secoidt/enum.SECOidTag.html new file mode 100644 index 0000000000..4c8a9dd017 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/secoidt/enum.SECOidTag.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/enum.SECOidTag.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/secoidt/enum.SECSupportExtenTag.html b/book/rust-docs/nss_sys/bindings/secoidt/enum.SECSupportExtenTag.html new file mode 100644 index 0000000000..3aa42af45e --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/secoidt/enum.SECSupportExtenTag.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/enum.SECSupportExtenTag.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/secoidt/struct.SECAlgorithmIDStr.html b/book/rust-docs/nss_sys/bindings/secoidt/struct.SECAlgorithmIDStr.html new file mode 100644 index 0000000000..0cd130e1c6 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/secoidt/struct.SECAlgorithmIDStr.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/struct.SECAlgorithmIDStr.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/secoidt/struct.SECOidDataStr.html b/book/rust-docs/nss_sys/bindings/secoidt/struct.SECOidDataStr.html new file mode 100644 index 0000000000..85f074df9c --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/secoidt/struct.SECOidDataStr.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/struct.SECOidDataStr.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/secoidt/type.SECAlgorithmID.html b/book/rust-docs/nss_sys/bindings/secoidt/type.SECAlgorithmID.html new file mode 100644 index 0000000000..e14b45dd9a --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/secoidt/type.SECAlgorithmID.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/type.SECAlgorithmID.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/secoidt/type.SECOidData.html b/book/rust-docs/nss_sys/bindings/secoidt/type.SECOidData.html new file mode 100644 index 0000000000..3edd2245f2 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/secoidt/type.SECOidData.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/type.SECOidData.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/secport/fn.NSS_SecureMemcmp.html b/book/rust-docs/nss_sys/bindings/secport/fn.NSS_SecureMemcmp.html new file mode 100644 index 0000000000..a5bbb8a531 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/secport/fn.NSS_SecureMemcmp.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/fn.NSS_SecureMemcmp.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/secport/fn.PORT_FreeArena.html b/book/rust-docs/nss_sys/bindings/secport/fn.PORT_FreeArena.html new file mode 100644 index 0000000000..c636c56df4 --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/secport/fn.PORT_FreeArena.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/fn.PORT_FreeArena.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/bindings/secport/type.size_t.html b/book/rust-docs/nss_sys/bindings/secport/type.size_t.html new file mode 100644 index 0000000000..32fdf7b03f --- /dev/null +++ b/book/rust-docs/nss_sys/bindings/secport/type.size_t.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../nss_sys/type.size_t.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/nss_sys/constant.AES_BLOCK_SIZE.html b/book/rust-docs/nss_sys/constant.AES_BLOCK_SIZE.html new file mode 100644 index 0000000000..0998c65be5 --- /dev/null +++ b/book/rust-docs/nss_sys/constant.AES_BLOCK_SIZE.html @@ -0,0 +1 @@ +AES_BLOCK_SIZE in nss_sys - Rust

Constant nss_sys::AES_BLOCK_SIZE

source ·
pub const AES_BLOCK_SIZE: u32 = 16;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/constant.CKA_CLASS.html b/book/rust-docs/nss_sys/constant.CKA_CLASS.html new file mode 100644 index 0000000000..53963b9d28 --- /dev/null +++ b/book/rust-docs/nss_sys/constant.CKA_CLASS.html @@ -0,0 +1 @@ +CKA_CLASS in nss_sys - Rust

Constant nss_sys::CKA_CLASS

source ·
pub const CKA_CLASS: u32 = 0;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/constant.CKA_EC_PARAMS.html b/book/rust-docs/nss_sys/constant.CKA_EC_PARAMS.html new file mode 100644 index 0000000000..42788a237c --- /dev/null +++ b/book/rust-docs/nss_sys/constant.CKA_EC_PARAMS.html @@ -0,0 +1 @@ +CKA_EC_PARAMS in nss_sys - Rust

Constant nss_sys::CKA_EC_PARAMS

source ·
pub const CKA_EC_PARAMS: u32 = 384;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/constant.CKA_EC_POINT.html b/book/rust-docs/nss_sys/constant.CKA_EC_POINT.html new file mode 100644 index 0000000000..7bd7639608 --- /dev/null +++ b/book/rust-docs/nss_sys/constant.CKA_EC_POINT.html @@ -0,0 +1 @@ +CKA_EC_POINT in nss_sys - Rust

Constant nss_sys::CKA_EC_POINT

source ·
pub const CKA_EC_POINT: u32 = 385;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/constant.CKA_ENCRYPT.html b/book/rust-docs/nss_sys/constant.CKA_ENCRYPT.html new file mode 100644 index 0000000000..143b6083f7 --- /dev/null +++ b/book/rust-docs/nss_sys/constant.CKA_ENCRYPT.html @@ -0,0 +1 @@ +CKA_ENCRYPT in nss_sys - Rust

Constant nss_sys::CKA_ENCRYPT

source ·
pub const CKA_ENCRYPT: u32 = 260;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/constant.CKA_ID.html b/book/rust-docs/nss_sys/constant.CKA_ID.html new file mode 100644 index 0000000000..7517ee5a66 --- /dev/null +++ b/book/rust-docs/nss_sys/constant.CKA_ID.html @@ -0,0 +1 @@ +CKA_ID in nss_sys - Rust

Constant nss_sys::CKA_ID

source ·
pub const CKA_ID: u32 = 258;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/constant.CKA_KEY_TYPE.html b/book/rust-docs/nss_sys/constant.CKA_KEY_TYPE.html new file mode 100644 index 0000000000..9eeac65b5e --- /dev/null +++ b/book/rust-docs/nss_sys/constant.CKA_KEY_TYPE.html @@ -0,0 +1 @@ +CKA_KEY_TYPE in nss_sys - Rust

Constant nss_sys::CKA_KEY_TYPE

source ·
pub const CKA_KEY_TYPE: u32 = 256;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/constant.CKA_PRIVATE.html b/book/rust-docs/nss_sys/constant.CKA_PRIVATE.html new file mode 100644 index 0000000000..bb22b06719 --- /dev/null +++ b/book/rust-docs/nss_sys/constant.CKA_PRIVATE.html @@ -0,0 +1 @@ +CKA_PRIVATE in nss_sys - Rust

Constant nss_sys::CKA_PRIVATE

source ·
pub const CKA_PRIVATE: u32 = 2;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/constant.CKA_SENSITIVE.html b/book/rust-docs/nss_sys/constant.CKA_SENSITIVE.html new file mode 100644 index 0000000000..c974833e58 --- /dev/null +++ b/book/rust-docs/nss_sys/constant.CKA_SENSITIVE.html @@ -0,0 +1 @@ +CKA_SENSITIVE in nss_sys - Rust

Constant nss_sys::CKA_SENSITIVE

source ·
pub const CKA_SENSITIVE: u32 = 259;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/constant.CKA_SIGN.html b/book/rust-docs/nss_sys/constant.CKA_SIGN.html new file mode 100644 index 0000000000..302cb87554 --- /dev/null +++ b/book/rust-docs/nss_sys/constant.CKA_SIGN.html @@ -0,0 +1 @@ +CKA_SIGN in nss_sys - Rust

Constant nss_sys::CKA_SIGN

source ·
pub const CKA_SIGN: u32 = 264;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/constant.CKA_TOKEN.html b/book/rust-docs/nss_sys/constant.CKA_TOKEN.html new file mode 100644 index 0000000000..0d90fbb7b3 --- /dev/null +++ b/book/rust-docs/nss_sys/constant.CKA_TOKEN.html @@ -0,0 +1 @@ +CKA_TOKEN in nss_sys - Rust

Constant nss_sys::CKA_TOKEN

source ·
pub const CKA_TOKEN: u32 = 1;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/constant.CKA_VALUE.html b/book/rust-docs/nss_sys/constant.CKA_VALUE.html new file mode 100644 index 0000000000..04e5912945 --- /dev/null +++ b/book/rust-docs/nss_sys/constant.CKA_VALUE.html @@ -0,0 +1 @@ +CKA_VALUE in nss_sys - Rust

Constant nss_sys::CKA_VALUE

source ·
pub const CKA_VALUE: u32 = 17;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/constant.CKA_WRAP.html b/book/rust-docs/nss_sys/constant.CKA_WRAP.html new file mode 100644 index 0000000000..b1046ae238 --- /dev/null +++ b/book/rust-docs/nss_sys/constant.CKA_WRAP.html @@ -0,0 +1 @@ +CKA_WRAP in nss_sys - Rust

Constant nss_sys::CKA_WRAP

source ·
pub const CKA_WRAP: u32 = 262;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/constant.CKD_NULL.html b/book/rust-docs/nss_sys/constant.CKD_NULL.html new file mode 100644 index 0000000000..b6c798bce7 --- /dev/null +++ b/book/rust-docs/nss_sys/constant.CKD_NULL.html @@ -0,0 +1 @@ +CKD_NULL in nss_sys - Rust

Constant nss_sys::CKD_NULL

source ·
pub const CKD_NULL: u32 = 1;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/constant.CKK_EC.html b/book/rust-docs/nss_sys/constant.CKK_EC.html new file mode 100644 index 0000000000..6b9f0da199 --- /dev/null +++ b/book/rust-docs/nss_sys/constant.CKK_EC.html @@ -0,0 +1 @@ +CKK_EC in nss_sys - Rust

Constant nss_sys::CKK_EC

source ·
pub const CKK_EC: u32 = 3;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/constant.CKM_AES_CBC_PAD.html b/book/rust-docs/nss_sys/constant.CKM_AES_CBC_PAD.html new file mode 100644 index 0000000000..bb96ea64bc --- /dev/null +++ b/book/rust-docs/nss_sys/constant.CKM_AES_CBC_PAD.html @@ -0,0 +1 @@ +CKM_AES_CBC_PAD in nss_sys - Rust

Constant nss_sys::CKM_AES_CBC_PAD

source ·
pub const CKM_AES_CBC_PAD: u32 = 4229;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/constant.CKM_AES_GCM.html b/book/rust-docs/nss_sys/constant.CKM_AES_GCM.html new file mode 100644 index 0000000000..2a01d4f8ce --- /dev/null +++ b/book/rust-docs/nss_sys/constant.CKM_AES_GCM.html @@ -0,0 +1 @@ +CKM_AES_GCM in nss_sys - Rust

Constant nss_sys::CKM_AES_GCM

source ·
pub const CKM_AES_GCM: u32 = 4231;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/constant.CKM_ECDH1_DERIVE.html b/book/rust-docs/nss_sys/constant.CKM_ECDH1_DERIVE.html new file mode 100644 index 0000000000..53c739062d --- /dev/null +++ b/book/rust-docs/nss_sys/constant.CKM_ECDH1_DERIVE.html @@ -0,0 +1 @@ +CKM_ECDH1_DERIVE in nss_sys - Rust

Constant nss_sys::CKM_ECDH1_DERIVE

source ·
pub const CKM_ECDH1_DERIVE: u32 = 4176;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/constant.CKM_EC_KEY_PAIR_GEN.html b/book/rust-docs/nss_sys/constant.CKM_EC_KEY_PAIR_GEN.html new file mode 100644 index 0000000000..69730b09f1 --- /dev/null +++ b/book/rust-docs/nss_sys/constant.CKM_EC_KEY_PAIR_GEN.html @@ -0,0 +1 @@ +CKM_EC_KEY_PAIR_GEN in nss_sys - Rust
pub const CKM_EC_KEY_PAIR_GEN: u32 = 4160;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/constant.CKM_NSS.html b/book/rust-docs/nss_sys/constant.CKM_NSS.html new file mode 100644 index 0000000000..d820b3777b --- /dev/null +++ b/book/rust-docs/nss_sys/constant.CKM_NSS.html @@ -0,0 +1 @@ +CKM_NSS in nss_sys - Rust

Constant nss_sys::CKM_NSS

source ·
pub const CKM_NSS: u32 = _; // 3_461_563_216u32
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/constant.CKM_NSS_HKDF_SHA256.html b/book/rust-docs/nss_sys/constant.CKM_NSS_HKDF_SHA256.html new file mode 100644 index 0000000000..6577d912f6 --- /dev/null +++ b/book/rust-docs/nss_sys/constant.CKM_NSS_HKDF_SHA256.html @@ -0,0 +1 @@ +CKM_NSS_HKDF_SHA256 in nss_sys - Rust
pub const CKM_NSS_HKDF_SHA256: u32 = _; // 3_461_563_220u32
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/constant.CKM_NSS_HKDF_SHA384.html b/book/rust-docs/nss_sys/constant.CKM_NSS_HKDF_SHA384.html new file mode 100644 index 0000000000..f80f50feae --- /dev/null +++ b/book/rust-docs/nss_sys/constant.CKM_NSS_HKDF_SHA384.html @@ -0,0 +1 @@ +CKM_NSS_HKDF_SHA384 in nss_sys - Rust
pub const CKM_NSS_HKDF_SHA384: u32 = _; // 3_461_563_221u32
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/constant.CKM_SHA256_HMAC.html b/book/rust-docs/nss_sys/constant.CKM_SHA256_HMAC.html new file mode 100644 index 0000000000..a840cd0f8a --- /dev/null +++ b/book/rust-docs/nss_sys/constant.CKM_SHA256_HMAC.html @@ -0,0 +1 @@ +CKM_SHA256_HMAC in nss_sys - Rust

Constant nss_sys::CKM_SHA256_HMAC

source ·
pub const CKM_SHA256_HMAC: u32 = 593;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/constant.CKM_SHA384_HMAC.html b/book/rust-docs/nss_sys/constant.CKM_SHA384_HMAC.html new file mode 100644 index 0000000000..b2b1cf5dc1 --- /dev/null +++ b/book/rust-docs/nss_sys/constant.CKM_SHA384_HMAC.html @@ -0,0 +1 @@ +CKM_SHA384_HMAC in nss_sys - Rust

Constant nss_sys::CKM_SHA384_HMAC

source ·
pub const CKM_SHA384_HMAC: u32 = 609;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/constant.CKM_SHA512_HMAC.html b/book/rust-docs/nss_sys/constant.CKM_SHA512_HMAC.html new file mode 100644 index 0000000000..9a871d931c --- /dev/null +++ b/book/rust-docs/nss_sys/constant.CKM_SHA512_HMAC.html @@ -0,0 +1 @@ +CKM_SHA512_HMAC in nss_sys - Rust

Constant nss_sys::CKM_SHA512_HMAC

source ·
pub const CKM_SHA512_HMAC: u32 = 625;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/constant.CKM_VENDOR_DEFINED.html b/book/rust-docs/nss_sys/constant.CKM_VENDOR_DEFINED.html new file mode 100644 index 0000000000..6ff27427ec --- /dev/null +++ b/book/rust-docs/nss_sys/constant.CKM_VENDOR_DEFINED.html @@ -0,0 +1 @@ +CKM_VENDOR_DEFINED in nss_sys - Rust
pub const CKM_VENDOR_DEFINED: u32 = 0x80000000;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/constant.CKO_PRIVATE_KEY.html b/book/rust-docs/nss_sys/constant.CKO_PRIVATE_KEY.html new file mode 100644 index 0000000000..56dea0e374 --- /dev/null +++ b/book/rust-docs/nss_sys/constant.CKO_PRIVATE_KEY.html @@ -0,0 +1 @@ +CKO_PRIVATE_KEY in nss_sys - Rust

Constant nss_sys::CKO_PRIVATE_KEY

source ·
pub const CKO_PRIVATE_KEY: u32 = 3;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/constant.CK_FALSE.html b/book/rust-docs/nss_sys/constant.CK_FALSE.html new file mode 100644 index 0000000000..3b5ea46cf2 --- /dev/null +++ b/book/rust-docs/nss_sys/constant.CK_FALSE.html @@ -0,0 +1 @@ +CK_FALSE in nss_sys - Rust

Constant nss_sys::CK_FALSE

source ·
pub const CK_FALSE: CK_BBOOL = 0;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/constant.CK_INVALID_HANDLE.html b/book/rust-docs/nss_sys/constant.CK_INVALID_HANDLE.html new file mode 100644 index 0000000000..fd3799b282 --- /dev/null +++ b/book/rust-docs/nss_sys/constant.CK_INVALID_HANDLE.html @@ -0,0 +1 @@ +CK_INVALID_HANDLE in nss_sys - Rust

Constant nss_sys::CK_INVALID_HANDLE

source ·
pub const CK_INVALID_HANDLE: u32 = 0;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/constant.CK_TRUE.html b/book/rust-docs/nss_sys/constant.CK_TRUE.html new file mode 100644 index 0000000000..523b4168b0 --- /dev/null +++ b/book/rust-docs/nss_sys/constant.CK_TRUE.html @@ -0,0 +1 @@ +CK_TRUE in nss_sys - Rust

Constant nss_sys::CK_TRUE

source ·
pub const CK_TRUE: CK_BBOOL = 1;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/constant.EC_POINT_FORM_UNCOMPRESSED.html b/book/rust-docs/nss_sys/constant.EC_POINT_FORM_UNCOMPRESSED.html new file mode 100644 index 0000000000..7d0345f97e --- /dev/null +++ b/book/rust-docs/nss_sys/constant.EC_POINT_FORM_UNCOMPRESSED.html @@ -0,0 +1 @@ +EC_POINT_FORM_UNCOMPRESSED in nss_sys - Rust
pub const EC_POINT_FORM_UNCOMPRESSED: u32 = 4;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/constant.HASH_LENGTH_MAX.html b/book/rust-docs/nss_sys/constant.HASH_LENGTH_MAX.html new file mode 100644 index 0000000000..f113df4cc0 --- /dev/null +++ b/book/rust-docs/nss_sys/constant.HASH_LENGTH_MAX.html @@ -0,0 +1 @@ +HASH_LENGTH_MAX in nss_sys - Rust

Constant nss_sys::HASH_LENGTH_MAX

source ·
pub const HASH_LENGTH_MAX: u32 = 64;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/constant.NSSCK_VENDOR_NSS.html b/book/rust-docs/nss_sys/constant.NSSCK_VENDOR_NSS.html new file mode 100644 index 0000000000..d142779431 --- /dev/null +++ b/book/rust-docs/nss_sys/constant.NSSCK_VENDOR_NSS.html @@ -0,0 +1 @@ +NSSCK_VENDOR_NSS in nss_sys - Rust

Constant nss_sys::NSSCK_VENDOR_NSS

source ·
pub const NSSCK_VENDOR_NSS: u32 = 0x4E534350;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/constant.NSS_INIT_FORCEOPEN.html b/book/rust-docs/nss_sys/constant.NSS_INIT_FORCEOPEN.html new file mode 100644 index 0000000000..e36dd6cc32 --- /dev/null +++ b/book/rust-docs/nss_sys/constant.NSS_INIT_FORCEOPEN.html @@ -0,0 +1 @@ +NSS_INIT_FORCEOPEN in nss_sys - Rust
pub const NSS_INIT_FORCEOPEN: u32 = 8;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/constant.NSS_INIT_NOCERTDB.html b/book/rust-docs/nss_sys/constant.NSS_INIT_NOCERTDB.html new file mode 100644 index 0000000000..533a03b51e --- /dev/null +++ b/book/rust-docs/nss_sys/constant.NSS_INIT_NOCERTDB.html @@ -0,0 +1 @@ +NSS_INIT_NOCERTDB in nss_sys - Rust

Constant nss_sys::NSS_INIT_NOCERTDB

source ·
pub const NSS_INIT_NOCERTDB: u32 = 2;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/constant.NSS_INIT_NOMODDB.html b/book/rust-docs/nss_sys/constant.NSS_INIT_NOMODDB.html new file mode 100644 index 0000000000..b86ca62c0d --- /dev/null +++ b/book/rust-docs/nss_sys/constant.NSS_INIT_NOMODDB.html @@ -0,0 +1 @@ +NSS_INIT_NOMODDB in nss_sys - Rust

Constant nss_sys::NSS_INIT_NOMODDB

source ·
pub const NSS_INIT_NOMODDB: u32 = 4;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/constant.NSS_INIT_OPTIMIZESPACE.html b/book/rust-docs/nss_sys/constant.NSS_INIT_OPTIMIZESPACE.html new file mode 100644 index 0000000000..0fdd11d1f7 --- /dev/null +++ b/book/rust-docs/nss_sys/constant.NSS_INIT_OPTIMIZESPACE.html @@ -0,0 +1 @@ +NSS_INIT_OPTIMIZESPACE in nss_sys - Rust
pub const NSS_INIT_OPTIMIZESPACE: u32 = 32;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/constant.NSS_INIT_READONLY.html b/book/rust-docs/nss_sys/constant.NSS_INIT_READONLY.html new file mode 100644 index 0000000000..cb32f26944 --- /dev/null +++ b/book/rust-docs/nss_sys/constant.NSS_INIT_READONLY.html @@ -0,0 +1 @@ +NSS_INIT_READONLY in nss_sys - Rust

Constant nss_sys::NSS_INIT_READONLY

source ·
pub const NSS_INIT_READONLY: u32 = 1;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/constant.PR_FALSE.html b/book/rust-docs/nss_sys/constant.PR_FALSE.html new file mode 100644 index 0000000000..f30dcf9782 --- /dev/null +++ b/book/rust-docs/nss_sys/constant.PR_FALSE.html @@ -0,0 +1 @@ +PR_FALSE in nss_sys - Rust

Constant nss_sys::PR_FALSE

source ·
pub const PR_FALSE: PRBool = 0;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/constant.PR_TRUE.html b/book/rust-docs/nss_sys/constant.PR_TRUE.html new file mode 100644 index 0000000000..7ceae689c1 --- /dev/null +++ b/book/rust-docs/nss_sys/constant.PR_TRUE.html @@ -0,0 +1 @@ +PR_TRUE in nss_sys - Rust

Constant nss_sys::PR_TRUE

source ·
pub const PR_TRUE: PRBool = 1;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/constant.SEC_ASN1_OBJECT_ID.html b/book/rust-docs/nss_sys/constant.SEC_ASN1_OBJECT_ID.html new file mode 100644 index 0000000000..3e28138acf --- /dev/null +++ b/book/rust-docs/nss_sys/constant.SEC_ASN1_OBJECT_ID.html @@ -0,0 +1 @@ +SEC_ASN1_OBJECT_ID in nss_sys - Rust
pub const SEC_ASN1_OBJECT_ID: u32 = 6;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/constant.SHA256_LENGTH.html b/book/rust-docs/nss_sys/constant.SHA256_LENGTH.html new file mode 100644 index 0000000000..f5afaa2929 --- /dev/null +++ b/book/rust-docs/nss_sys/constant.SHA256_LENGTH.html @@ -0,0 +1 @@ +SHA256_LENGTH in nss_sys - Rust

Constant nss_sys::SHA256_LENGTH

source ·
pub const SHA256_LENGTH: u32 = 32;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/constant.SHA384_LENGTH.html b/book/rust-docs/nss_sys/constant.SHA384_LENGTH.html new file mode 100644 index 0000000000..b47e7eb882 --- /dev/null +++ b/book/rust-docs/nss_sys/constant.SHA384_LENGTH.html @@ -0,0 +1 @@ +SHA384_LENGTH in nss_sys - Rust

Constant nss_sys::SHA384_LENGTH

source ·
pub const SHA384_LENGTH: u32 = 48;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/enum.ECPointEncoding.html b/book/rust-docs/nss_sys/enum.ECPointEncoding.html new file mode 100644 index 0000000000..e2a1d990fe --- /dev/null +++ b/book/rust-docs/nss_sys/enum.ECPointEncoding.html @@ -0,0 +1,16 @@ +ECPointEncoding in nss_sys - Rust
#[repr(u32)]
pub enum ECPointEncoding { + ECPoint_Uncompressed, + ECPoint_XOnly, + ECPoint_Undefined, +}

Variants§

§

ECPoint_Uncompressed

§

ECPoint_XOnly

§

ECPoint_Undefined

Trait Implementations§

source§

impl Clone for ECPointEncoding

source§

fn clone(&self) -> ECPointEncoding

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Copy for ECPointEncoding

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/enum.KeyType.html b/book/rust-docs/nss_sys/enum.KeyType.html new file mode 100644 index 0000000000..58694108de --- /dev/null +++ b/book/rust-docs/nss_sys/enum.KeyType.html @@ -0,0 +1,21 @@ +KeyType in nss_sys - Rust

Enum nss_sys::KeyType

source ·
#[repr(u32)]
pub enum KeyType { + nullKey, + rsaKey, + dsaKey, + fortezzaKey, + dhKey, + keaKey, + ecKey, + rsaPssKey, + rsaOaepKey, +}

Variants§

§

nullKey

§

rsaKey

§

dsaKey

§

fortezzaKey

§

dhKey

§

keaKey

§

ecKey

§

rsaPssKey

§

rsaOaepKey

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/enum.PK11ObjectType.html b/book/rust-docs/nss_sys/enum.PK11ObjectType.html new file mode 100644 index 0000000000..a4c28973cb --- /dev/null +++ b/book/rust-docs/nss_sys/enum.PK11ObjectType.html @@ -0,0 +1,17 @@ +PK11ObjectType in nss_sys - Rust
#[repr(u32)]
pub enum PK11ObjectType { + PK11_TypeGeneric, + PK11_TypePrivKey, + PK11_TypePubKey, + PK11_TypeCert, + PK11_TypeSymKey, +}

Variants§

§

PK11_TypeGeneric

§

PK11_TypePrivKey

§

PK11_TypePubKey

§

PK11_TypeCert

§

PK11_TypeSymKey

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/enum.PK11Origin.html b/book/rust-docs/nss_sys/enum.PK11Origin.html new file mode 100644 index 0000000000..415ef0db8e --- /dev/null +++ b/book/rust-docs/nss_sys/enum.PK11Origin.html @@ -0,0 +1,17 @@ +PK11Origin in nss_sys - Rust

Enum nss_sys::PK11Origin

source ·
#[repr(u32)]
pub enum PK11Origin { + PK11_OriginNULL, + PK11_OriginDerive, + PK11_OriginGenerated, + PK11_OriginFortezzaHack, + PK11_OriginUnwrap, +}

Variants§

§

PK11_OriginNULL

§

PK11_OriginDerive

§

PK11_OriginGenerated

§

PK11_OriginFortezzaHack

§

PK11_OriginUnwrap

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/enum.SECItemType.html b/book/rust-docs/nss_sys/enum.SECItemType.html new file mode 100644 index 0000000000..8110197c66 --- /dev/null +++ b/book/rust-docs/nss_sys/enum.SECItemType.html @@ -0,0 +1,28 @@ +SECItemType in nss_sys - Rust

Enum nss_sys::SECItemType

source ·
#[repr(u32)]
pub enum SECItemType { +
Show 16 variants siBuffer, + siClearDataBuffer, + siCipherDataBuffer, + siDERCertBuffer, + siEncodedCertBuffer, + siDERNameBuffer, + siEncodedNameBuffer, + siAsciiNameString, + siAsciiString, + siDEROID, + siUnsignedInteger, + siUTCTime, + siGeneralizedTime, + siVisibleString, + siUTF8String, + siBMPString, +
}

Variants§

§

siBuffer

§

siClearDataBuffer

§

siCipherDataBuffer

§

siDERCertBuffer

§

siEncodedCertBuffer

§

siDERNameBuffer

§

siEncodedNameBuffer

§

siAsciiNameString

§

siAsciiString

§

siDEROID

§

siUnsignedInteger

§

siUTCTime

§

siGeneralizedTime

§

siVisibleString

§

siUTF8String

§

siBMPString

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/enum.SECOidTag.html b/book/rust-docs/nss_sys/enum.SECOidTag.html new file mode 100644 index 0000000000..3a06c7775c --- /dev/null +++ b/book/rust-docs/nss_sys/enum.SECOidTag.html @@ -0,0 +1,377 @@ +SECOidTag in nss_sys - Rust

Enum nss_sys::SECOidTag

source ·
#[repr(u32)]
pub enum SECOidTag { +
Show 365 variants SEC_OID_UNKNOWN, + SEC_OID_MD2, + SEC_OID_MD4, + SEC_OID_MD5, + SEC_OID_SHA1, + SEC_OID_RC2_CBC, + SEC_OID_RC4, + SEC_OID_DES_EDE3_CBC, + SEC_OID_RC5_CBC_PAD, + SEC_OID_DES_ECB, + SEC_OID_DES_CBC, + SEC_OID_DES_OFB, + SEC_OID_DES_CFB, + SEC_OID_DES_MAC, + SEC_OID_DES_EDE, + SEC_OID_ISO_SHA_WITH_RSA_SIGNATURE, + SEC_OID_PKCS1_RSA_ENCRYPTION, + SEC_OID_PKCS1_MD2_WITH_RSA_ENCRYPTION, + SEC_OID_PKCS1_MD4_WITH_RSA_ENCRYPTION, + SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION, + SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION, + SEC_OID_PKCS5_PBE_WITH_MD2_AND_DES_CBC, + SEC_OID_PKCS5_PBE_WITH_MD5_AND_DES_CBC, + SEC_OID_PKCS5_PBE_WITH_SHA1_AND_DES_CBC, + SEC_OID_PKCS7, + SEC_OID_PKCS7_DATA, + SEC_OID_PKCS7_SIGNED_DATA, + SEC_OID_PKCS7_ENVELOPED_DATA, + SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA, + SEC_OID_PKCS7_DIGESTED_DATA, + SEC_OID_PKCS7_ENCRYPTED_DATA, + SEC_OID_PKCS9_EMAIL_ADDRESS, + SEC_OID_PKCS9_UNSTRUCTURED_NAME, + SEC_OID_PKCS9_CONTENT_TYPE, + SEC_OID_PKCS9_MESSAGE_DIGEST, + SEC_OID_PKCS9_SIGNING_TIME, + SEC_OID_PKCS9_COUNTER_SIGNATURE, + SEC_OID_PKCS9_CHALLENGE_PASSWORD, + SEC_OID_PKCS9_UNSTRUCTURED_ADDRESS, + SEC_OID_PKCS9_EXTENDED_CERTIFICATE_ATTRIBUTES, + SEC_OID_PKCS9_SMIME_CAPABILITIES, + SEC_OID_AVA_COMMON_NAME, + SEC_OID_AVA_COUNTRY_NAME, + SEC_OID_AVA_LOCALITY, + SEC_OID_AVA_STATE_OR_PROVINCE, + SEC_OID_AVA_ORGANIZATION_NAME, + SEC_OID_AVA_ORGANIZATIONAL_UNIT_NAME, + SEC_OID_AVA_DN_QUALIFIER, + SEC_OID_AVA_DC, + SEC_OID_NS_TYPE_GIF, + SEC_OID_NS_TYPE_JPEG, + SEC_OID_NS_TYPE_URL, + SEC_OID_NS_TYPE_HTML, + SEC_OID_NS_TYPE_CERT_SEQUENCE, + SEC_OID_MISSI_KEA_DSS_OLD, + SEC_OID_MISSI_DSS_OLD, + SEC_OID_MISSI_KEA_DSS, + SEC_OID_MISSI_DSS, + SEC_OID_MISSI_KEA, + SEC_OID_MISSI_ALT_KEA, + SEC_OID_NS_CERT_EXT_NETSCAPE_OK, + SEC_OID_NS_CERT_EXT_ISSUER_LOGO, + SEC_OID_NS_CERT_EXT_SUBJECT_LOGO, + SEC_OID_NS_CERT_EXT_CERT_TYPE, + SEC_OID_NS_CERT_EXT_BASE_URL, + SEC_OID_NS_CERT_EXT_REVOCATION_URL, + SEC_OID_NS_CERT_EXT_CA_REVOCATION_URL, + SEC_OID_NS_CERT_EXT_CA_CRL_URL, + SEC_OID_NS_CERT_EXT_CA_CERT_URL, + SEC_OID_NS_CERT_EXT_CERT_RENEWAL_URL, + SEC_OID_NS_CERT_EXT_CA_POLICY_URL, + SEC_OID_NS_CERT_EXT_HOMEPAGE_URL, + SEC_OID_NS_CERT_EXT_ENTITY_LOGO, + SEC_OID_NS_CERT_EXT_USER_PICTURE, + SEC_OID_NS_CERT_EXT_SSL_SERVER_NAME, + SEC_OID_NS_CERT_EXT_COMMENT, + SEC_OID_NS_CERT_EXT_LOST_PASSWORD_URL, + SEC_OID_NS_CERT_EXT_CERT_RENEWAL_TIME, + SEC_OID_NS_KEY_USAGE_GOVT_APPROVED, + SEC_OID_X509_SUBJECT_DIRECTORY_ATTR, + SEC_OID_X509_SUBJECT_KEY_ID, + SEC_OID_X509_KEY_USAGE, + SEC_OID_X509_PRIVATE_KEY_USAGE_PERIOD, + SEC_OID_X509_SUBJECT_ALT_NAME, + SEC_OID_X509_ISSUER_ALT_NAME, + SEC_OID_X509_BASIC_CONSTRAINTS, + SEC_OID_X509_NAME_CONSTRAINTS, + SEC_OID_X509_CRL_DIST_POINTS, + SEC_OID_X509_CERTIFICATE_POLICIES, + SEC_OID_X509_POLICY_MAPPINGS, + SEC_OID_X509_POLICY_CONSTRAINTS, + SEC_OID_X509_AUTH_KEY_ID, + SEC_OID_X509_EXT_KEY_USAGE, + SEC_OID_X509_AUTH_INFO_ACCESS, + SEC_OID_X509_CRL_NUMBER, + SEC_OID_X509_REASON_CODE, + SEC_OID_X509_INVALID_DATE, + SEC_OID_X500_RSA_ENCRYPTION, + SEC_OID_RFC1274_UID, + SEC_OID_RFC1274_MAIL, + SEC_OID_PKCS12, + SEC_OID_PKCS12_MODE_IDS, + SEC_OID_PKCS12_ESPVK_IDS, + SEC_OID_PKCS12_BAG_IDS, + SEC_OID_PKCS12_CERT_BAG_IDS, + SEC_OID_PKCS12_OIDS, + SEC_OID_PKCS12_PBE_IDS, + SEC_OID_PKCS12_SIGNATURE_IDS, + SEC_OID_PKCS12_ENVELOPING_IDS, + SEC_OID_PKCS12_PKCS8_KEY_SHROUDING, + SEC_OID_PKCS12_KEY_BAG_ID, + SEC_OID_PKCS12_CERT_AND_CRL_BAG_ID, + SEC_OID_PKCS12_SECRET_BAG_ID, + SEC_OID_PKCS12_X509_CERT_CRL_BAG, + SEC_OID_PKCS12_SDSI_CERT_BAG, + SEC_OID_PKCS12_PBE_WITH_SHA1_AND_128_BIT_RC4, + SEC_OID_PKCS12_PBE_WITH_SHA1_AND_40_BIT_RC4, + SEC_OID_PKCS12_PBE_WITH_SHA1_AND_TRIPLE_DES_CBC, + SEC_OID_PKCS12_PBE_WITH_SHA1_AND_128_BIT_RC2_CBC, + SEC_OID_PKCS12_PBE_WITH_SHA1_AND_40_BIT_RC2_CBC, + SEC_OID_PKCS12_RSA_ENCRYPTION_WITH_128_BIT_RC4, + SEC_OID_PKCS12_RSA_ENCRYPTION_WITH_40_BIT_RC4, + SEC_OID_PKCS12_RSA_ENCRYPTION_WITH_TRIPLE_DES, + SEC_OID_PKCS12_RSA_SIGNATURE_WITH_SHA1_DIGEST, + SEC_OID_ANSIX9_DSA_SIGNATURE, + SEC_OID_ANSIX9_DSA_SIGNATURE_WITH_SHA1_DIGEST, + SEC_OID_BOGUS_DSA_SIGNATURE_WITH_SHA1_DIGEST, + SEC_OID_VERISIGN_USER_NOTICES, + SEC_OID_PKIX_CPS_POINTER_QUALIFIER, + SEC_OID_PKIX_USER_NOTICE_QUALIFIER, + SEC_OID_PKIX_OCSP, + SEC_OID_PKIX_OCSP_BASIC_RESPONSE, + SEC_OID_PKIX_OCSP_NONCE, + SEC_OID_PKIX_OCSP_CRL, + SEC_OID_PKIX_OCSP_RESPONSE, + SEC_OID_PKIX_OCSP_NO_CHECK, + SEC_OID_PKIX_OCSP_ARCHIVE_CUTOFF, + SEC_OID_PKIX_OCSP_SERVICE_LOCATOR, + SEC_OID_PKIX_REGCTRL_REGTOKEN, + SEC_OID_PKIX_REGCTRL_AUTHENTICATOR, + SEC_OID_PKIX_REGCTRL_PKIPUBINFO, + SEC_OID_PKIX_REGCTRL_PKI_ARCH_OPTIONS, + SEC_OID_PKIX_REGCTRL_OLD_CERT_ID, + SEC_OID_PKIX_REGCTRL_PROTOCOL_ENC_KEY, + SEC_OID_PKIX_REGINFO_UTF8_PAIRS, + SEC_OID_PKIX_REGINFO_CERT_REQUEST, + SEC_OID_EXT_KEY_USAGE_SERVER_AUTH, + SEC_OID_EXT_KEY_USAGE_CLIENT_AUTH, + SEC_OID_EXT_KEY_USAGE_CODE_SIGN, + SEC_OID_EXT_KEY_USAGE_EMAIL_PROTECT, + SEC_OID_EXT_KEY_USAGE_TIME_STAMP, + SEC_OID_OCSP_RESPONDER, + SEC_OID_NETSCAPE_SMIME_KEA, + SEC_OID_FORTEZZA_SKIPJACK, + SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_128_BIT_RC4, + SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_40_BIT_RC4, + SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_3KEY_TRIPLE_DES_CBC, + SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_2KEY_TRIPLE_DES_CBC, + SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_128_BIT_RC2_CBC, + SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_40_BIT_RC2_CBC, + SEC_OID_PKCS12_SAFE_CONTENTS_ID, + SEC_OID_PKCS12_PKCS8_SHROUDED_KEY_BAG_ID, + SEC_OID_PKCS12_V1_KEY_BAG_ID, + SEC_OID_PKCS12_V1_PKCS8_SHROUDED_KEY_BAG_ID, + SEC_OID_PKCS12_V1_CERT_BAG_ID, + SEC_OID_PKCS12_V1_CRL_BAG_ID, + SEC_OID_PKCS12_V1_SECRET_BAG_ID, + SEC_OID_PKCS12_V1_SAFE_CONTENTS_BAG_ID, + SEC_OID_PKCS9_X509_CERT, + SEC_OID_PKCS9_SDSI_CERT, + SEC_OID_PKCS9_X509_CRL, + SEC_OID_PKCS9_FRIENDLY_NAME, + SEC_OID_PKCS9_LOCAL_KEY_ID, + SEC_OID_BOGUS_KEY_USAGE, + SEC_OID_X942_DIFFIE_HELMAN_KEY, + SEC_OID_NETSCAPE_NICKNAME, + SEC_OID_NETSCAPE_RECOVERY_REQUEST, + SEC_OID_CERT_RENEWAL_LOCATOR, + SEC_OID_NS_CERT_EXT_SCOPE_OF_USE, + SEC_OID_CMS_EPHEMERAL_STATIC_DIFFIE_HELLMAN, + SEC_OID_CMS_3DES_KEY_WRAP, + SEC_OID_CMS_RC2_KEY_WRAP, + SEC_OID_SMIME_ENCRYPTION_KEY_PREFERENCE, + SEC_OID_AES_128_ECB, + SEC_OID_AES_128_CBC, + SEC_OID_AES_192_ECB, + SEC_OID_AES_192_CBC, + SEC_OID_AES_256_ECB, + SEC_OID_AES_256_CBC, + SEC_OID_SDN702_DSA_SIGNATURE, + SEC_OID_MS_SMIME_ENCRYPTION_KEY_PREFERENCE, + SEC_OID_SHA256, + SEC_OID_SHA384, + SEC_OID_SHA512, + SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION, + SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION, + SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION, + SEC_OID_AES_128_KEY_WRAP, + SEC_OID_AES_192_KEY_WRAP, + SEC_OID_AES_256_KEY_WRAP, + SEC_OID_ANSIX962_EC_PUBLIC_KEY, + SEC_OID_ANSIX962_ECDSA_SHA1_SIGNATURE, + SEC_OID_ANSIX962_EC_PRIME192V1, + SEC_OID_ANSIX962_EC_PRIME192V2, + SEC_OID_ANSIX962_EC_PRIME192V3, + SEC_OID_ANSIX962_EC_PRIME239V1, + SEC_OID_ANSIX962_EC_PRIME239V2, + SEC_OID_ANSIX962_EC_PRIME239V3, + SEC_OID_SECG_EC_SECP256R1, + SEC_OID_SECG_EC_SECP112R1, + SEC_OID_SECG_EC_SECP112R2, + SEC_OID_SECG_EC_SECP128R1, + SEC_OID_SECG_EC_SECP128R2, + SEC_OID_SECG_EC_SECP160K1, + SEC_OID_SECG_EC_SECP160R1, + SEC_OID_SECG_EC_SECP160R2, + SEC_OID_SECG_EC_SECP192K1, + SEC_OID_SECG_EC_SECP224K1, + SEC_OID_SECG_EC_SECP224R1, + SEC_OID_SECG_EC_SECP256K1, + SEC_OID_SECG_EC_SECP384R1, + SEC_OID_SECG_EC_SECP521R1, + SEC_OID_ANSIX962_EC_C2PNB163V1, + SEC_OID_ANSIX962_EC_C2PNB163V2, + SEC_OID_ANSIX962_EC_C2PNB163V3, + SEC_OID_ANSIX962_EC_C2PNB176V1, + SEC_OID_ANSIX962_EC_C2TNB191V1, + SEC_OID_ANSIX962_EC_C2TNB191V2, + SEC_OID_ANSIX962_EC_C2TNB191V3, + SEC_OID_ANSIX962_EC_C2ONB191V4, + SEC_OID_ANSIX962_EC_C2ONB191V5, + SEC_OID_ANSIX962_EC_C2PNB208W1, + SEC_OID_ANSIX962_EC_C2TNB239V1, + SEC_OID_ANSIX962_EC_C2TNB239V2, + SEC_OID_ANSIX962_EC_C2TNB239V3, + SEC_OID_ANSIX962_EC_C2ONB239V4, + SEC_OID_ANSIX962_EC_C2ONB239V5, + SEC_OID_ANSIX962_EC_C2PNB272W1, + SEC_OID_ANSIX962_EC_C2PNB304W1, + SEC_OID_ANSIX962_EC_C2TNB359V1, + SEC_OID_ANSIX962_EC_C2PNB368W1, + SEC_OID_ANSIX962_EC_C2TNB431R1, + SEC_OID_SECG_EC_SECT113R1, + SEC_OID_SECG_EC_SECT113R2, + SEC_OID_SECG_EC_SECT131R1, + SEC_OID_SECG_EC_SECT131R2, + SEC_OID_SECG_EC_SECT163K1, + SEC_OID_SECG_EC_SECT163R1, + SEC_OID_SECG_EC_SECT163R2, + SEC_OID_SECG_EC_SECT193R1, + SEC_OID_SECG_EC_SECT193R2, + SEC_OID_SECG_EC_SECT233K1, + SEC_OID_SECG_EC_SECT233R1, + SEC_OID_SECG_EC_SECT239K1, + SEC_OID_SECG_EC_SECT283K1, + SEC_OID_SECG_EC_SECT283R1, + SEC_OID_SECG_EC_SECT409K1, + SEC_OID_SECG_EC_SECT409R1, + SEC_OID_SECG_EC_SECT571K1, + SEC_OID_SECG_EC_SECT571R1, + SEC_OID_NETSCAPE_AOLSCREENNAME, + SEC_OID_AVA_SURNAME, + SEC_OID_AVA_SERIAL_NUMBER, + SEC_OID_AVA_STREET_ADDRESS, + SEC_OID_AVA_TITLE, + SEC_OID_AVA_POSTAL_ADDRESS, + SEC_OID_AVA_POSTAL_CODE, + SEC_OID_AVA_POST_OFFICE_BOX, + SEC_OID_AVA_GIVEN_NAME, + SEC_OID_AVA_INITIALS, + SEC_OID_AVA_GENERATION_QUALIFIER, + SEC_OID_AVA_HOUSE_IDENTIFIER, + SEC_OID_AVA_PSEUDONYM, + SEC_OID_PKIX_CA_ISSUERS, + SEC_OID_PKCS9_EXTENSION_REQUEST, + SEC_OID_ANSIX962_ECDSA_SIGNATURE_RECOMMENDED_DIGEST, + SEC_OID_ANSIX962_ECDSA_SIGNATURE_SPECIFIED_DIGEST, + SEC_OID_ANSIX962_ECDSA_SHA224_SIGNATURE, + SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE, + SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE, + SEC_OID_ANSIX962_ECDSA_SHA512_SIGNATURE, + SEC_OID_X509_HOLD_INSTRUCTION_CODE, + SEC_OID_X509_DELTA_CRL_INDICATOR, + SEC_OID_X509_ISSUING_DISTRIBUTION_POINT, + SEC_OID_X509_CERT_ISSUER, + SEC_OID_X509_FRESHEST_CRL, + SEC_OID_X509_INHIBIT_ANY_POLICY, + SEC_OID_X509_SUBJECT_INFO_ACCESS, + SEC_OID_CAMELLIA_128_CBC, + SEC_OID_CAMELLIA_192_CBC, + SEC_OID_CAMELLIA_256_CBC, + SEC_OID_PKCS5_PBKDF2, + SEC_OID_PKCS5_PBES2, + SEC_OID_PKCS5_PBMAC1, + SEC_OID_HMAC_SHA1, + SEC_OID_HMAC_SHA224, + SEC_OID_HMAC_SHA256, + SEC_OID_HMAC_SHA384, + SEC_OID_HMAC_SHA512, + SEC_OID_PKIX_TIMESTAMPING, + SEC_OID_PKIX_CA_REPOSITORY, + SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE, + SEC_OID_SEED_CBC, + SEC_OID_X509_ANY_POLICY, + SEC_OID_PKCS1_RSA_OAEP_ENCRYPTION, + SEC_OID_PKCS1_MGF1, + SEC_OID_PKCS1_PSPECIFIED, + SEC_OID_PKCS1_RSA_PSS_SIGNATURE, + SEC_OID_PKCS1_SHA224_WITH_RSA_ENCRYPTION, + SEC_OID_SHA224, + SEC_OID_EV_INCORPORATION_LOCALITY, + SEC_OID_EV_INCORPORATION_STATE, + SEC_OID_EV_INCORPORATION_COUNTRY, + SEC_OID_BUSINESS_CATEGORY, + SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA224_DIGEST, + SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA256_DIGEST, + SEC_OID_MS_EXT_KEY_USAGE_CTL_SIGNING, + SEC_OID_AVA_NAME, + SEC_OID_AES_128_GCM, + SEC_OID_AES_192_GCM, + SEC_OID_AES_256_GCM, + SEC_OID_IDEA_CBC, + SEC_OID_RC2_40_CBC, + SEC_OID_DES_40_CBC, + SEC_OID_RC4_40, + SEC_OID_RC4_56, + SEC_OID_NULL_CIPHER, + SEC_OID_HMAC_MD5, + SEC_OID_TLS_RSA, + SEC_OID_TLS_DHE_RSA, + SEC_OID_TLS_DHE_DSS, + SEC_OID_TLS_DH_RSA, + SEC_OID_TLS_DH_DSS, + SEC_OID_TLS_DH_ANON, + SEC_OID_TLS_ECDHE_ECDSA, + SEC_OID_TLS_ECDHE_RSA, + SEC_OID_TLS_ECDH_ECDSA, + SEC_OID_TLS_ECDH_RSA, + SEC_OID_TLS_ECDH_ANON, + SEC_OID_TLS_RSA_EXPORT, + SEC_OID_TLS_DHE_RSA_EXPORT, + SEC_OID_TLS_DHE_DSS_EXPORT, + SEC_OID_TLS_DH_RSA_EXPORT, + SEC_OID_TLS_DH_DSS_EXPORT, + SEC_OID_TLS_DH_ANON_EXPORT, + SEC_OID_APPLY_SSL_POLICY, + SEC_OID_CHACHA20_POLY1305, + SEC_OID_TLS_ECDHE_PSK, + SEC_OID_TLS_DHE_PSK, + SEC_OID_TLS_FFDHE_2048, + SEC_OID_TLS_FFDHE_3072, + SEC_OID_TLS_FFDHE_4096, + SEC_OID_TLS_FFDHE_6144, + SEC_OID_TLS_FFDHE_8192, + SEC_OID_TLS_DHE_CUSTOM, + SEC_OID_CURVE25519, + SEC_OID_TLS13_KEA_ANY, + SEC_OID_X509_ANY_EXT_KEY_USAGE, + SEC_OID_EXT_KEY_USAGE_IPSEC_IKE, + SEC_OID_IPSEC_IKE_END, + SEC_OID_IPSEC_IKE_INTERMEDIATE, + SEC_OID_EXT_KEY_USAGE_IPSEC_END, + SEC_OID_EXT_KEY_USAGE_IPSEC_TUNNEL, + SEC_OID_EXT_KEY_USAGE_IPSEC_USER, + SEC_OID_TOTAL, +
}

Variants§

§

SEC_OID_UNKNOWN

§

SEC_OID_MD2

§

SEC_OID_MD4

§

SEC_OID_MD5

§

SEC_OID_SHA1

§

SEC_OID_RC2_CBC

§

SEC_OID_RC4

§

SEC_OID_DES_EDE3_CBC

§

SEC_OID_RC5_CBC_PAD

§

SEC_OID_DES_ECB

§

SEC_OID_DES_CBC

§

SEC_OID_DES_OFB

§

SEC_OID_DES_CFB

§

SEC_OID_DES_MAC

§

SEC_OID_DES_EDE

§

SEC_OID_ISO_SHA_WITH_RSA_SIGNATURE

§

SEC_OID_PKCS1_RSA_ENCRYPTION

§

SEC_OID_PKCS1_MD2_WITH_RSA_ENCRYPTION

§

SEC_OID_PKCS1_MD4_WITH_RSA_ENCRYPTION

§

SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION

§

SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION

§

SEC_OID_PKCS5_PBE_WITH_MD2_AND_DES_CBC

§

SEC_OID_PKCS5_PBE_WITH_MD5_AND_DES_CBC

§

SEC_OID_PKCS5_PBE_WITH_SHA1_AND_DES_CBC

§

SEC_OID_PKCS7

§

SEC_OID_PKCS7_DATA

§

SEC_OID_PKCS7_SIGNED_DATA

§

SEC_OID_PKCS7_ENVELOPED_DATA

§

SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA

§

SEC_OID_PKCS7_DIGESTED_DATA

§

SEC_OID_PKCS7_ENCRYPTED_DATA

§

SEC_OID_PKCS9_EMAIL_ADDRESS

§

SEC_OID_PKCS9_UNSTRUCTURED_NAME

§

SEC_OID_PKCS9_CONTENT_TYPE

§

SEC_OID_PKCS9_MESSAGE_DIGEST

§

SEC_OID_PKCS9_SIGNING_TIME

§

SEC_OID_PKCS9_COUNTER_SIGNATURE

§

SEC_OID_PKCS9_CHALLENGE_PASSWORD

§

SEC_OID_PKCS9_UNSTRUCTURED_ADDRESS

§

SEC_OID_PKCS9_EXTENDED_CERTIFICATE_ATTRIBUTES

§

SEC_OID_PKCS9_SMIME_CAPABILITIES

§

SEC_OID_AVA_COMMON_NAME

§

SEC_OID_AVA_COUNTRY_NAME

§

SEC_OID_AVA_LOCALITY

§

SEC_OID_AVA_STATE_OR_PROVINCE

§

SEC_OID_AVA_ORGANIZATION_NAME

§

SEC_OID_AVA_ORGANIZATIONAL_UNIT_NAME

§

SEC_OID_AVA_DN_QUALIFIER

§

SEC_OID_AVA_DC

§

SEC_OID_NS_TYPE_GIF

§

SEC_OID_NS_TYPE_JPEG

§

SEC_OID_NS_TYPE_URL

§

SEC_OID_NS_TYPE_HTML

§

SEC_OID_NS_TYPE_CERT_SEQUENCE

§

SEC_OID_MISSI_KEA_DSS_OLD

§

SEC_OID_MISSI_DSS_OLD

§

SEC_OID_MISSI_KEA_DSS

§

SEC_OID_MISSI_DSS

§

SEC_OID_MISSI_KEA

§

SEC_OID_MISSI_ALT_KEA

§

SEC_OID_NS_CERT_EXT_NETSCAPE_OK

§

SEC_OID_NS_CERT_EXT_CERT_TYPE

§

SEC_OID_NS_CERT_EXT_BASE_URL

§

SEC_OID_NS_CERT_EXT_REVOCATION_URL

§

SEC_OID_NS_CERT_EXT_CA_REVOCATION_URL

§

SEC_OID_NS_CERT_EXT_CA_CRL_URL

§

SEC_OID_NS_CERT_EXT_CA_CERT_URL

§

SEC_OID_NS_CERT_EXT_CERT_RENEWAL_URL

§

SEC_OID_NS_CERT_EXT_CA_POLICY_URL

§

SEC_OID_NS_CERT_EXT_HOMEPAGE_URL

§

SEC_OID_NS_CERT_EXT_USER_PICTURE

§

SEC_OID_NS_CERT_EXT_SSL_SERVER_NAME

§

SEC_OID_NS_CERT_EXT_COMMENT

§

SEC_OID_NS_CERT_EXT_LOST_PASSWORD_URL

§

SEC_OID_NS_CERT_EXT_CERT_RENEWAL_TIME

§

SEC_OID_NS_KEY_USAGE_GOVT_APPROVED

§

SEC_OID_X509_SUBJECT_DIRECTORY_ATTR

§

SEC_OID_X509_SUBJECT_KEY_ID

§

SEC_OID_X509_KEY_USAGE

§

SEC_OID_X509_PRIVATE_KEY_USAGE_PERIOD

§

SEC_OID_X509_SUBJECT_ALT_NAME

§

SEC_OID_X509_ISSUER_ALT_NAME

§

SEC_OID_X509_BASIC_CONSTRAINTS

§

SEC_OID_X509_NAME_CONSTRAINTS

§

SEC_OID_X509_CRL_DIST_POINTS

§

SEC_OID_X509_CERTIFICATE_POLICIES

§

SEC_OID_X509_POLICY_MAPPINGS

§

SEC_OID_X509_POLICY_CONSTRAINTS

§

SEC_OID_X509_AUTH_KEY_ID

§

SEC_OID_X509_EXT_KEY_USAGE

§

SEC_OID_X509_AUTH_INFO_ACCESS

§

SEC_OID_X509_CRL_NUMBER

§

SEC_OID_X509_REASON_CODE

§

SEC_OID_X509_INVALID_DATE

§

SEC_OID_X500_RSA_ENCRYPTION

§

SEC_OID_RFC1274_UID

§

SEC_OID_RFC1274_MAIL

§

SEC_OID_PKCS12

§

SEC_OID_PKCS12_MODE_IDS

§

SEC_OID_PKCS12_ESPVK_IDS

§

SEC_OID_PKCS12_BAG_IDS

§

SEC_OID_PKCS12_CERT_BAG_IDS

§

SEC_OID_PKCS12_OIDS

§

SEC_OID_PKCS12_PBE_IDS

§

SEC_OID_PKCS12_SIGNATURE_IDS

§

SEC_OID_PKCS12_ENVELOPING_IDS

§

SEC_OID_PKCS12_PKCS8_KEY_SHROUDING

§

SEC_OID_PKCS12_KEY_BAG_ID

§

SEC_OID_PKCS12_CERT_AND_CRL_BAG_ID

§

SEC_OID_PKCS12_SECRET_BAG_ID

§

SEC_OID_PKCS12_X509_CERT_CRL_BAG

§

SEC_OID_PKCS12_SDSI_CERT_BAG

§

SEC_OID_PKCS12_PBE_WITH_SHA1_AND_128_BIT_RC4

§

SEC_OID_PKCS12_PBE_WITH_SHA1_AND_40_BIT_RC4

§

SEC_OID_PKCS12_PBE_WITH_SHA1_AND_TRIPLE_DES_CBC

§

SEC_OID_PKCS12_PBE_WITH_SHA1_AND_128_BIT_RC2_CBC

§

SEC_OID_PKCS12_PBE_WITH_SHA1_AND_40_BIT_RC2_CBC

§

SEC_OID_PKCS12_RSA_ENCRYPTION_WITH_128_BIT_RC4

§

SEC_OID_PKCS12_RSA_ENCRYPTION_WITH_40_BIT_RC4

§

SEC_OID_PKCS12_RSA_ENCRYPTION_WITH_TRIPLE_DES

§

SEC_OID_PKCS12_RSA_SIGNATURE_WITH_SHA1_DIGEST

§

SEC_OID_ANSIX9_DSA_SIGNATURE

§

SEC_OID_ANSIX9_DSA_SIGNATURE_WITH_SHA1_DIGEST

§

SEC_OID_BOGUS_DSA_SIGNATURE_WITH_SHA1_DIGEST

§

SEC_OID_VERISIGN_USER_NOTICES

§

SEC_OID_PKIX_CPS_POINTER_QUALIFIER

§

SEC_OID_PKIX_USER_NOTICE_QUALIFIER

§

SEC_OID_PKIX_OCSP

§

SEC_OID_PKIX_OCSP_BASIC_RESPONSE

§

SEC_OID_PKIX_OCSP_NONCE

§

SEC_OID_PKIX_OCSP_CRL

§

SEC_OID_PKIX_OCSP_RESPONSE

§

SEC_OID_PKIX_OCSP_NO_CHECK

§

SEC_OID_PKIX_OCSP_ARCHIVE_CUTOFF

§

SEC_OID_PKIX_OCSP_SERVICE_LOCATOR

§

SEC_OID_PKIX_REGCTRL_REGTOKEN

§

SEC_OID_PKIX_REGCTRL_AUTHENTICATOR

§

SEC_OID_PKIX_REGCTRL_PKIPUBINFO

§

SEC_OID_PKIX_REGCTRL_PKI_ARCH_OPTIONS

§

SEC_OID_PKIX_REGCTRL_OLD_CERT_ID

§

SEC_OID_PKIX_REGCTRL_PROTOCOL_ENC_KEY

§

SEC_OID_PKIX_REGINFO_UTF8_PAIRS

§

SEC_OID_PKIX_REGINFO_CERT_REQUEST

§

SEC_OID_EXT_KEY_USAGE_SERVER_AUTH

§

SEC_OID_EXT_KEY_USAGE_CLIENT_AUTH

§

SEC_OID_EXT_KEY_USAGE_CODE_SIGN

§

SEC_OID_EXT_KEY_USAGE_EMAIL_PROTECT

§

SEC_OID_EXT_KEY_USAGE_TIME_STAMP

§

SEC_OID_OCSP_RESPONDER

§

SEC_OID_NETSCAPE_SMIME_KEA

§

SEC_OID_FORTEZZA_SKIPJACK

§

SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_128_BIT_RC4

§

SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_40_BIT_RC4

§

SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_3KEY_TRIPLE_DES_CBC

§

SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_2KEY_TRIPLE_DES_CBC

§

SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_128_BIT_RC2_CBC

§

SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_40_BIT_RC2_CBC

§

SEC_OID_PKCS12_SAFE_CONTENTS_ID

§

SEC_OID_PKCS12_PKCS8_SHROUDED_KEY_BAG_ID

§

SEC_OID_PKCS12_V1_KEY_BAG_ID

§

SEC_OID_PKCS12_V1_PKCS8_SHROUDED_KEY_BAG_ID

§

SEC_OID_PKCS12_V1_CERT_BAG_ID

§

SEC_OID_PKCS12_V1_CRL_BAG_ID

§

SEC_OID_PKCS12_V1_SECRET_BAG_ID

§

SEC_OID_PKCS12_V1_SAFE_CONTENTS_BAG_ID

§

SEC_OID_PKCS9_X509_CERT

§

SEC_OID_PKCS9_SDSI_CERT

§

SEC_OID_PKCS9_X509_CRL

§

SEC_OID_PKCS9_FRIENDLY_NAME

§

SEC_OID_PKCS9_LOCAL_KEY_ID

§

SEC_OID_BOGUS_KEY_USAGE

§

SEC_OID_X942_DIFFIE_HELMAN_KEY

§

SEC_OID_NETSCAPE_NICKNAME

§

SEC_OID_NETSCAPE_RECOVERY_REQUEST

§

SEC_OID_CERT_RENEWAL_LOCATOR

§

SEC_OID_NS_CERT_EXT_SCOPE_OF_USE

§

SEC_OID_CMS_EPHEMERAL_STATIC_DIFFIE_HELLMAN

§

SEC_OID_CMS_3DES_KEY_WRAP

§

SEC_OID_CMS_RC2_KEY_WRAP

§

SEC_OID_SMIME_ENCRYPTION_KEY_PREFERENCE

§

SEC_OID_AES_128_ECB

§

SEC_OID_AES_128_CBC

§

SEC_OID_AES_192_ECB

§

SEC_OID_AES_192_CBC

§

SEC_OID_AES_256_ECB

§

SEC_OID_AES_256_CBC

§

SEC_OID_SDN702_DSA_SIGNATURE

§

SEC_OID_MS_SMIME_ENCRYPTION_KEY_PREFERENCE

§

SEC_OID_SHA256

§

SEC_OID_SHA384

§

SEC_OID_SHA512

§

SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION

§

SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION

§

SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION

§

SEC_OID_AES_128_KEY_WRAP

§

SEC_OID_AES_192_KEY_WRAP

§

SEC_OID_AES_256_KEY_WRAP

§

SEC_OID_ANSIX962_EC_PUBLIC_KEY

§

SEC_OID_ANSIX962_ECDSA_SHA1_SIGNATURE

§

SEC_OID_ANSIX962_EC_PRIME192V1

§

SEC_OID_ANSIX962_EC_PRIME192V2

§

SEC_OID_ANSIX962_EC_PRIME192V3

§

SEC_OID_ANSIX962_EC_PRIME239V1

§

SEC_OID_ANSIX962_EC_PRIME239V2

§

SEC_OID_ANSIX962_EC_PRIME239V3

§

SEC_OID_SECG_EC_SECP256R1

§

SEC_OID_SECG_EC_SECP112R1

§

SEC_OID_SECG_EC_SECP112R2

§

SEC_OID_SECG_EC_SECP128R1

§

SEC_OID_SECG_EC_SECP128R2

§

SEC_OID_SECG_EC_SECP160K1

§

SEC_OID_SECG_EC_SECP160R1

§

SEC_OID_SECG_EC_SECP160R2

§

SEC_OID_SECG_EC_SECP192K1

§

SEC_OID_SECG_EC_SECP224K1

§

SEC_OID_SECG_EC_SECP224R1

§

SEC_OID_SECG_EC_SECP256K1

§

SEC_OID_SECG_EC_SECP384R1

§

SEC_OID_SECG_EC_SECP521R1

§

SEC_OID_ANSIX962_EC_C2PNB163V1

§

SEC_OID_ANSIX962_EC_C2PNB163V2

§

SEC_OID_ANSIX962_EC_C2PNB163V3

§

SEC_OID_ANSIX962_EC_C2PNB176V1

§

SEC_OID_ANSIX962_EC_C2TNB191V1

§

SEC_OID_ANSIX962_EC_C2TNB191V2

§

SEC_OID_ANSIX962_EC_C2TNB191V3

§

SEC_OID_ANSIX962_EC_C2ONB191V4

§

SEC_OID_ANSIX962_EC_C2ONB191V5

§

SEC_OID_ANSIX962_EC_C2PNB208W1

§

SEC_OID_ANSIX962_EC_C2TNB239V1

§

SEC_OID_ANSIX962_EC_C2TNB239V2

§

SEC_OID_ANSIX962_EC_C2TNB239V3

§

SEC_OID_ANSIX962_EC_C2ONB239V4

§

SEC_OID_ANSIX962_EC_C2ONB239V5

§

SEC_OID_ANSIX962_EC_C2PNB272W1

§

SEC_OID_ANSIX962_EC_C2PNB304W1

§

SEC_OID_ANSIX962_EC_C2TNB359V1

§

SEC_OID_ANSIX962_EC_C2PNB368W1

§

SEC_OID_ANSIX962_EC_C2TNB431R1

§

SEC_OID_SECG_EC_SECT113R1

§

SEC_OID_SECG_EC_SECT113R2

§

SEC_OID_SECG_EC_SECT131R1

§

SEC_OID_SECG_EC_SECT131R2

§

SEC_OID_SECG_EC_SECT163K1

§

SEC_OID_SECG_EC_SECT163R1

§

SEC_OID_SECG_EC_SECT163R2

§

SEC_OID_SECG_EC_SECT193R1

§

SEC_OID_SECG_EC_SECT193R2

§

SEC_OID_SECG_EC_SECT233K1

§

SEC_OID_SECG_EC_SECT233R1

§

SEC_OID_SECG_EC_SECT239K1

§

SEC_OID_SECG_EC_SECT283K1

§

SEC_OID_SECG_EC_SECT283R1

§

SEC_OID_SECG_EC_SECT409K1

§

SEC_OID_SECG_EC_SECT409R1

§

SEC_OID_SECG_EC_SECT571K1

§

SEC_OID_SECG_EC_SECT571R1

§

SEC_OID_NETSCAPE_AOLSCREENNAME

§

SEC_OID_AVA_SURNAME

§

SEC_OID_AVA_SERIAL_NUMBER

§

SEC_OID_AVA_STREET_ADDRESS

§

SEC_OID_AVA_TITLE

§

SEC_OID_AVA_POSTAL_ADDRESS

§

SEC_OID_AVA_POSTAL_CODE

§

SEC_OID_AVA_POST_OFFICE_BOX

§

SEC_OID_AVA_GIVEN_NAME

§

SEC_OID_AVA_INITIALS

§

SEC_OID_AVA_GENERATION_QUALIFIER

§

SEC_OID_AVA_HOUSE_IDENTIFIER

§

SEC_OID_AVA_PSEUDONYM

§

SEC_OID_PKIX_CA_ISSUERS

§

SEC_OID_PKCS9_EXTENSION_REQUEST

§

SEC_OID_ANSIX962_ECDSA_SIGNATURE_SPECIFIED_DIGEST

§

SEC_OID_ANSIX962_ECDSA_SHA224_SIGNATURE

§

SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE

§

SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE

§

SEC_OID_ANSIX962_ECDSA_SHA512_SIGNATURE

§

SEC_OID_X509_HOLD_INSTRUCTION_CODE

§

SEC_OID_X509_DELTA_CRL_INDICATOR

§

SEC_OID_X509_ISSUING_DISTRIBUTION_POINT

§

SEC_OID_X509_CERT_ISSUER

§

SEC_OID_X509_FRESHEST_CRL

§

SEC_OID_X509_INHIBIT_ANY_POLICY

§

SEC_OID_X509_SUBJECT_INFO_ACCESS

§

SEC_OID_CAMELLIA_128_CBC

§

SEC_OID_CAMELLIA_192_CBC

§

SEC_OID_CAMELLIA_256_CBC

§

SEC_OID_PKCS5_PBKDF2

§

SEC_OID_PKCS5_PBES2

§

SEC_OID_PKCS5_PBMAC1

§

SEC_OID_HMAC_SHA1

§

SEC_OID_HMAC_SHA224

§

SEC_OID_HMAC_SHA256

§

SEC_OID_HMAC_SHA384

§

SEC_OID_HMAC_SHA512

§

SEC_OID_PKIX_TIMESTAMPING

§

SEC_OID_PKIX_CA_REPOSITORY

§

SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE

§

SEC_OID_SEED_CBC

§

SEC_OID_X509_ANY_POLICY

§

SEC_OID_PKCS1_RSA_OAEP_ENCRYPTION

§

SEC_OID_PKCS1_MGF1

§

SEC_OID_PKCS1_PSPECIFIED

§

SEC_OID_PKCS1_RSA_PSS_SIGNATURE

§

SEC_OID_PKCS1_SHA224_WITH_RSA_ENCRYPTION

§

SEC_OID_SHA224

§

SEC_OID_EV_INCORPORATION_LOCALITY

§

SEC_OID_EV_INCORPORATION_STATE

§

SEC_OID_EV_INCORPORATION_COUNTRY

§

SEC_OID_BUSINESS_CATEGORY

§

SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA224_DIGEST

§

SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA256_DIGEST

§

SEC_OID_MS_EXT_KEY_USAGE_CTL_SIGNING

§

SEC_OID_AVA_NAME

§

SEC_OID_AES_128_GCM

§

SEC_OID_AES_192_GCM

§

SEC_OID_AES_256_GCM

§

SEC_OID_IDEA_CBC

§

SEC_OID_RC2_40_CBC

§

SEC_OID_DES_40_CBC

§

SEC_OID_RC4_40

§

SEC_OID_RC4_56

§

SEC_OID_NULL_CIPHER

§

SEC_OID_HMAC_MD5

§

SEC_OID_TLS_RSA

§

SEC_OID_TLS_DHE_RSA

§

SEC_OID_TLS_DHE_DSS

§

SEC_OID_TLS_DH_RSA

§

SEC_OID_TLS_DH_DSS

§

SEC_OID_TLS_DH_ANON

§

SEC_OID_TLS_ECDHE_ECDSA

§

SEC_OID_TLS_ECDHE_RSA

§

SEC_OID_TLS_ECDH_ECDSA

§

SEC_OID_TLS_ECDH_RSA

§

SEC_OID_TLS_ECDH_ANON

§

SEC_OID_TLS_RSA_EXPORT

§

SEC_OID_TLS_DHE_RSA_EXPORT

§

SEC_OID_TLS_DHE_DSS_EXPORT

§

SEC_OID_TLS_DH_RSA_EXPORT

§

SEC_OID_TLS_DH_DSS_EXPORT

§

SEC_OID_TLS_DH_ANON_EXPORT

§

SEC_OID_APPLY_SSL_POLICY

§

SEC_OID_CHACHA20_POLY1305

§

SEC_OID_TLS_ECDHE_PSK

§

SEC_OID_TLS_DHE_PSK

§

SEC_OID_TLS_FFDHE_2048

§

SEC_OID_TLS_FFDHE_3072

§

SEC_OID_TLS_FFDHE_4096

§

SEC_OID_TLS_FFDHE_6144

§

SEC_OID_TLS_FFDHE_8192

§

SEC_OID_TLS_DHE_CUSTOM

§

SEC_OID_CURVE25519

§

SEC_OID_TLS13_KEA_ANY

§

SEC_OID_X509_ANY_EXT_KEY_USAGE

§

SEC_OID_EXT_KEY_USAGE_IPSEC_IKE

§

SEC_OID_IPSEC_IKE_END

§

SEC_OID_IPSEC_IKE_INTERMEDIATE

§

SEC_OID_EXT_KEY_USAGE_IPSEC_END

§

SEC_OID_EXT_KEY_USAGE_IPSEC_TUNNEL

§

SEC_OID_EXT_KEY_USAGE_IPSEC_USER

§

SEC_OID_TOTAL

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/enum.SECStatus.html b/book/rust-docs/nss_sys/enum.SECStatus.html new file mode 100644 index 0000000000..7af7ea7ad7 --- /dev/null +++ b/book/rust-docs/nss_sys/enum.SECStatus.html @@ -0,0 +1,17 @@ +SECStatus in nss_sys - Rust

Enum nss_sys::SECStatus

source ·
#[repr(i32)]
pub enum SECStatus { + SECWouldBlock, + SECFailure, + SECSuccess, +}

Variants§

§

SECWouldBlock

§

SECFailure

§

SECSuccess

Trait Implementations§

source§

impl PartialEq<_SECStatus> for _SECStatus

source§

fn eq(&self, other: &_SECStatus) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Eq for _SECStatus

source§

impl StructuralEq for _SECStatus

source§

impl StructuralPartialEq for _SECStatus

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/enum.SECSupportExtenTag.html b/book/rust-docs/nss_sys/enum.SECSupportExtenTag.html new file mode 100644 index 0000000000..eb219954fc --- /dev/null +++ b/book/rust-docs/nss_sys/enum.SECSupportExtenTag.html @@ -0,0 +1,15 @@ +SECSupportExtenTag in nss_sys - Rust
pub enum SECSupportExtenTag {
+    INVALID_CERT_EXTENSION,
+    UNSUPPORTED_CERT_EXTENSION,
+    SUPPORTED_CERT_EXTENSION,
+}

Variants§

§

INVALID_CERT_EXTENSION

§

UNSUPPORTED_CERT_EXTENSION

§

SUPPORTED_CERT_EXTENSION

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/enum._SECStatus.html b/book/rust-docs/nss_sys/enum._SECStatus.html new file mode 100644 index 0000000000..ce32828377 --- /dev/null +++ b/book/rust-docs/nss_sys/enum._SECStatus.html @@ -0,0 +1,17 @@ +_SECStatus in nss_sys - Rust

Enum nss_sys::_SECStatus

source ·
#[repr(i32)]
pub enum _SECStatus { + SECWouldBlock, + SECFailure, + SECSuccess, +}

Variants§

§

SECWouldBlock

§

SECFailure

§

SECSuccess

Trait Implementations§

source§

impl PartialEq<_SECStatus> for _SECStatus

source§

fn eq(&self, other: &_SECStatus) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Eq for _SECStatus

source§

impl StructuralEq for _SECStatus

source§

impl StructuralPartialEq for _SECStatus

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/fn.CERT_DestroyCertificate.html b/book/rust-docs/nss_sys/fn.CERT_DestroyCertificate.html new file mode 100644 index 0000000000..456a06e1a8 --- /dev/null +++ b/book/rust-docs/nss_sys/fn.CERT_DestroyCertificate.html @@ -0,0 +1,3 @@ +CERT_DestroyCertificate in nss_sys - Rust
pub unsafe extern "C" fn CERT_DestroyCertificate(
+    cert: *mut CERTCertificate
+)
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/fn.CERT_ExtractPublicKey.html b/book/rust-docs/nss_sys/fn.CERT_ExtractPublicKey.html new file mode 100644 index 0000000000..267fe1f6bf --- /dev/null +++ b/book/rust-docs/nss_sys/fn.CERT_ExtractPublicKey.html @@ -0,0 +1,3 @@ +CERT_ExtractPublicKey in nss_sys - Rust
pub unsafe extern "C" fn CERT_ExtractPublicKey(
+    cert: *mut CERTCertificate
+) -> *mut SECKEYPublicKey
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/fn.CERT_GetDefaultCertDB.html b/book/rust-docs/nss_sys/fn.CERT_GetDefaultCertDB.html new file mode 100644 index 0000000000..51d8334dc2 --- /dev/null +++ b/book/rust-docs/nss_sys/fn.CERT_GetDefaultCertDB.html @@ -0,0 +1,2 @@ +CERT_GetDefaultCertDB in nss_sys - Rust
pub unsafe extern "C" fn CERT_GetDefaultCertDB(
+) -> *mut CERTCertDBHandle
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/fn.CERT_NewTempCertificate.html b/book/rust-docs/nss_sys/fn.CERT_NewTempCertificate.html new file mode 100644 index 0000000000..7947efd3ca --- /dev/null +++ b/book/rust-docs/nss_sys/fn.CERT_NewTempCertificate.html @@ -0,0 +1,7 @@ +CERT_NewTempCertificate in nss_sys - Rust
pub unsafe extern "C" fn CERT_NewTempCertificate(
+    handle: *mut CERTCertDBHandle,
+    derCert: *mut SECItem,
+    nickname: *mut c_char,
+    isperm: PRBool,
+    copyDER: PRBool
+) -> *mut CERTCertificate
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/fn.NSS_InitContext.html b/book/rust-docs/nss_sys/fn.NSS_InitContext.html new file mode 100644 index 0000000000..3f2e62235b --- /dev/null +++ b/book/rust-docs/nss_sys/fn.NSS_InitContext.html @@ -0,0 +1,8 @@ +NSS_InitContext in nss_sys - Rust

Function nss_sys::NSS_InitContext

source ·
pub unsafe extern "C" fn NSS_InitContext(
+    configdir: *const c_char,
+    certPrefix: *const c_char,
+    keyPrefix: *const c_char,
+    secmodName: *const c_char,
+    initParams: *mut NSSInitParameters,
+    flags: PRUint32
+) -> *mut NSSInitContext
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/fn.NSS_SecureMemcmp.html b/book/rust-docs/nss_sys/fn.NSS_SecureMemcmp.html new file mode 100644 index 0000000000..ff704b52cf --- /dev/null +++ b/book/rust-docs/nss_sys/fn.NSS_SecureMemcmp.html @@ -0,0 +1,5 @@ +NSS_SecureMemcmp in nss_sys - Rust

Function nss_sys::NSS_SecureMemcmp

source ·
pub unsafe extern "C" fn NSS_SecureMemcmp(
+    a: *const c_void,
+    b: *const c_void,
+    n: size_t
+) -> c_int
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/fn.NSS_VersionCheck.html b/book/rust-docs/nss_sys/fn.NSS_VersionCheck.html new file mode 100644 index 0000000000..9d31c01c0c --- /dev/null +++ b/book/rust-docs/nss_sys/fn.NSS_VersionCheck.html @@ -0,0 +1,3 @@ +NSS_VersionCheck in nss_sys - Rust

Function nss_sys::NSS_VersionCheck

source ·
pub unsafe extern "C" fn NSS_VersionCheck(
+    importedVersion: *const c_char
+) -> PRBool
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/fn.PK11_CreateContextBySymKey.html b/book/rust-docs/nss_sys/fn.PK11_CreateContextBySymKey.html new file mode 100644 index 0000000000..d4783dde9c --- /dev/null +++ b/book/rust-docs/nss_sys/fn.PK11_CreateContextBySymKey.html @@ -0,0 +1,6 @@ +PK11_CreateContextBySymKey in nss_sys - Rust
pub unsafe extern "C" fn PK11_CreateContextBySymKey(
+    type_: CK_MECHANISM_TYPE,
+    operation: CK_ATTRIBUTE_TYPE,
+    symKey: *mut PK11SymKey,
+    param: *const SECItem
+) -> *mut PK11Context
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/fn.PK11_CreateGenericObject.html b/book/rust-docs/nss_sys/fn.PK11_CreateGenericObject.html new file mode 100644 index 0000000000..8627732c72 --- /dev/null +++ b/book/rust-docs/nss_sys/fn.PK11_CreateGenericObject.html @@ -0,0 +1,6 @@ +PK11_CreateGenericObject in nss_sys - Rust
pub unsafe extern "C" fn PK11_CreateGenericObject(
+    slot: *mut PK11SlotInfo,
+    pTemplate: *const CK_ATTRIBUTE,
+    count: c_int,
+    token: PRBool
+) -> *mut PK11GenericObject
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/fn.PK11_CreatePBEV2AlgorithmID.html b/book/rust-docs/nss_sys/fn.PK11_CreatePBEV2AlgorithmID.html new file mode 100644 index 0000000000..e80c9de182 --- /dev/null +++ b/book/rust-docs/nss_sys/fn.PK11_CreatePBEV2AlgorithmID.html @@ -0,0 +1,8 @@ +PK11_CreatePBEV2AlgorithmID in nss_sys - Rust
pub unsafe extern "C" fn PK11_CreatePBEV2AlgorithmID(
+    pbeAlgTag: u32,
+    cipherAlgTag: u32,
+    prfAlgTag: u32,
+    keyLength: c_int,
+    iteration: c_int,
+    salt: *mut SECItem
+) -> *mut SECAlgorithmID
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/fn.PK11_Decrypt.html b/book/rust-docs/nss_sys/fn.PK11_Decrypt.html new file mode 100644 index 0000000000..6cdd758ccf --- /dev/null +++ b/book/rust-docs/nss_sys/fn.PK11_Decrypt.html @@ -0,0 +1,10 @@ +PK11_Decrypt in nss_sys - Rust

Function nss_sys::PK11_Decrypt

source ·
pub unsafe extern "C" fn PK11_Decrypt(
+    symkey: *mut PK11SymKey,
+    mechanism: CK_MECHANISM_TYPE,
+    param: *mut SECItem,
+    out: *mut c_uchar,
+    outLen: *mut c_uint,
+    maxLen: c_uint,
+    enc: *const c_uchar,
+    encLen: c_uint
+) -> SECStatus
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/fn.PK11_Derive.html b/book/rust-docs/nss_sys/fn.PK11_Derive.html new file mode 100644 index 0000000000..bc070c63cf --- /dev/null +++ b/book/rust-docs/nss_sys/fn.PK11_Derive.html @@ -0,0 +1,8 @@ +PK11_Derive in nss_sys - Rust

Function nss_sys::PK11_Derive

source ·
pub unsafe extern "C" fn PK11_Derive(
+    baseKey: *mut PK11SymKey,
+    mechanism: CK_MECHANISM_TYPE,
+    param: *mut SECItem,
+    target: CK_MECHANISM_TYPE,
+    operation: CK_ATTRIBUTE_TYPE,
+    keySize: c_int
+) -> *mut PK11SymKey
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/fn.PK11_DestroyContext.html b/book/rust-docs/nss_sys/fn.PK11_DestroyContext.html new file mode 100644 index 0000000000..f5aa470b71 --- /dev/null +++ b/book/rust-docs/nss_sys/fn.PK11_DestroyContext.html @@ -0,0 +1,4 @@ +PK11_DestroyContext in nss_sys - Rust
pub unsafe extern "C" fn PK11_DestroyContext(
+    context: *mut PK11Context,
+    freeit: PRBool
+)
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/fn.PK11_DestroyGenericObject.html b/book/rust-docs/nss_sys/fn.PK11_DestroyGenericObject.html new file mode 100644 index 0000000000..d61146455e --- /dev/null +++ b/book/rust-docs/nss_sys/fn.PK11_DestroyGenericObject.html @@ -0,0 +1,3 @@ +PK11_DestroyGenericObject in nss_sys - Rust
pub unsafe extern "C" fn PK11_DestroyGenericObject(
+    object: *mut PK11GenericObject
+) -> SECStatus
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/fn.PK11_DigestBegin.html b/book/rust-docs/nss_sys/fn.PK11_DigestBegin.html new file mode 100644 index 0000000000..e2a19f3ab0 --- /dev/null +++ b/book/rust-docs/nss_sys/fn.PK11_DigestBegin.html @@ -0,0 +1,3 @@ +PK11_DigestBegin in nss_sys - Rust

Function nss_sys::PK11_DigestBegin

source ·
pub unsafe extern "C" fn PK11_DigestBegin(
+    cx: *mut PK11Context
+) -> SECStatus
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/fn.PK11_DigestFinal.html b/book/rust-docs/nss_sys/fn.PK11_DigestFinal.html new file mode 100644 index 0000000000..f1b5662ffa --- /dev/null +++ b/book/rust-docs/nss_sys/fn.PK11_DigestFinal.html @@ -0,0 +1,6 @@ +PK11_DigestFinal in nss_sys - Rust

Function nss_sys::PK11_DigestFinal

source ·
pub unsafe extern "C" fn PK11_DigestFinal(
+    context: *mut PK11Context,
+    data: *mut c_uchar,
+    outLen: *mut c_uint,
+    length: c_uint
+) -> SECStatus
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/fn.PK11_DigestOp.html b/book/rust-docs/nss_sys/fn.PK11_DigestOp.html new file mode 100644 index 0000000000..4b1ef6ca47 --- /dev/null +++ b/book/rust-docs/nss_sys/fn.PK11_DigestOp.html @@ -0,0 +1,5 @@ +PK11_DigestOp in nss_sys - Rust

Function nss_sys::PK11_DigestOp

source ·
pub unsafe extern "C" fn PK11_DigestOp(
+    context: *mut PK11Context,
+    in_: *const c_uchar,
+    len: c_uint
+) -> SECStatus
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/fn.PK11_Encrypt.html b/book/rust-docs/nss_sys/fn.PK11_Encrypt.html new file mode 100644 index 0000000000..259cbd69af --- /dev/null +++ b/book/rust-docs/nss_sys/fn.PK11_Encrypt.html @@ -0,0 +1,10 @@ +PK11_Encrypt in nss_sys - Rust

Function nss_sys::PK11_Encrypt

source ·
pub unsafe extern "C" fn PK11_Encrypt(
+    symKey: *mut PK11SymKey,
+    mechanism: CK_MECHANISM_TYPE,
+    param: *mut SECItem,
+    out: *mut c_uchar,
+    outLen: *mut c_uint,
+    maxLen: c_uint,
+    data: *const c_uchar,
+    dataLen: c_uint
+) -> SECStatus
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/fn.PK11_ExtractKeyValue.html b/book/rust-docs/nss_sys/fn.PK11_ExtractKeyValue.html new file mode 100644 index 0000000000..0c154541ec --- /dev/null +++ b/book/rust-docs/nss_sys/fn.PK11_ExtractKeyValue.html @@ -0,0 +1,3 @@ +PK11_ExtractKeyValue in nss_sys - Rust
pub unsafe extern "C" fn PK11_ExtractKeyValue(
+    symKey: *mut PK11SymKey
+) -> SECStatus
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/fn.PK11_FindKeyByKeyID.html b/book/rust-docs/nss_sys/fn.PK11_FindKeyByKeyID.html new file mode 100644 index 0000000000..031bf670fb --- /dev/null +++ b/book/rust-docs/nss_sys/fn.PK11_FindKeyByKeyID.html @@ -0,0 +1,5 @@ +PK11_FindKeyByKeyID in nss_sys - Rust
pub unsafe extern "C" fn PK11_FindKeyByKeyID(
+    slot: *mut PK11SlotInfo,
+    keyID: *mut SECItem,
+    wincx: *mut c_void
+) -> *mut SECKEYPrivateKey
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/fn.PK11_FreeSlot.html b/book/rust-docs/nss_sys/fn.PK11_FreeSlot.html new file mode 100644 index 0000000000..f9bf62681e --- /dev/null +++ b/book/rust-docs/nss_sys/fn.PK11_FreeSlot.html @@ -0,0 +1 @@ +PK11_FreeSlot in nss_sys - Rust

Function nss_sys::PK11_FreeSlot

source ·
pub unsafe extern "C" fn PK11_FreeSlot(slot: *mut PK11SlotInfo)
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/fn.PK11_FreeSymKey.html b/book/rust-docs/nss_sys/fn.PK11_FreeSymKey.html new file mode 100644 index 0000000000..4160a8b422 --- /dev/null +++ b/book/rust-docs/nss_sys/fn.PK11_FreeSymKey.html @@ -0,0 +1 @@ +PK11_FreeSymKey in nss_sys - Rust

Function nss_sys::PK11_FreeSymKey

source ·
pub unsafe extern "C" fn PK11_FreeSymKey(key: *mut PK11SymKey)
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/fn.PK11_GenerateKeyPair.html b/book/rust-docs/nss_sys/fn.PK11_GenerateKeyPair.html new file mode 100644 index 0000000000..cd3f6c632a --- /dev/null +++ b/book/rust-docs/nss_sys/fn.PK11_GenerateKeyPair.html @@ -0,0 +1,9 @@ +PK11_GenerateKeyPair in nss_sys - Rust
pub unsafe extern "C" fn PK11_GenerateKeyPair(
+    slot: *mut PK11SlotInfo,
+    type_: CK_MECHANISM_TYPE,
+    param: *mut c_void,
+    pubk: *mut *mut SECKEYPublicKey,
+    isPerm: PRBool,
+    isSensitive: PRBool,
+    wincx: *mut c_void
+) -> *mut SECKEYPrivateKey
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/fn.PK11_GenerateRandom.html b/book/rust-docs/nss_sys/fn.PK11_GenerateRandom.html new file mode 100644 index 0000000000..73eb1d7b8a --- /dev/null +++ b/book/rust-docs/nss_sys/fn.PK11_GenerateRandom.html @@ -0,0 +1,4 @@ +PK11_GenerateRandom in nss_sys - Rust
pub unsafe extern "C" fn PK11_GenerateRandom(
+    data: *mut c_uchar,
+    len: c_int
+) -> SECStatus
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/fn.PK11_GetInternalSlot.html b/book/rust-docs/nss_sys/fn.PK11_GetInternalSlot.html new file mode 100644 index 0000000000..684277b0cf --- /dev/null +++ b/book/rust-docs/nss_sys/fn.PK11_GetInternalSlot.html @@ -0,0 +1 @@ +PK11_GetInternalSlot in nss_sys - Rust
pub unsafe extern "C" fn PK11_GetInternalSlot() -> *mut PK11SlotInfo
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/fn.PK11_GetKeyData.html b/book/rust-docs/nss_sys/fn.PK11_GetKeyData.html new file mode 100644 index 0000000000..cdbf31d492 --- /dev/null +++ b/book/rust-docs/nss_sys/fn.PK11_GetKeyData.html @@ -0,0 +1,3 @@ +PK11_GetKeyData in nss_sys - Rust

Function nss_sys::PK11_GetKeyData

source ·
pub unsafe extern "C" fn PK11_GetKeyData(
+    symKey: *mut PK11SymKey
+) -> *mut SECItem
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/fn.PK11_HashBuf.html b/book/rust-docs/nss_sys/fn.PK11_HashBuf.html new file mode 100644 index 0000000000..22f04b50f6 --- /dev/null +++ b/book/rust-docs/nss_sys/fn.PK11_HashBuf.html @@ -0,0 +1,6 @@ +PK11_HashBuf in nss_sys - Rust

Function nss_sys::PK11_HashBuf

source ·
pub unsafe extern "C" fn PK11_HashBuf(
+    hashAlg: u32,
+    out: *mut c_uchar,
+    in_: *const c_uchar,
+    len: PRInt32
+) -> SECStatus
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/fn.PK11_ImportSymKey.html b/book/rust-docs/nss_sys/fn.PK11_ImportSymKey.html new file mode 100644 index 0000000000..e7af08b6c2 --- /dev/null +++ b/book/rust-docs/nss_sys/fn.PK11_ImportSymKey.html @@ -0,0 +1,8 @@ +PK11_ImportSymKey in nss_sys - Rust

Function nss_sys::PK11_ImportSymKey

source ·
pub unsafe extern "C" fn PK11_ImportSymKey(
+    slot: *mut PK11SlotInfo,
+    type_: CK_MECHANISM_TYPE,
+    origin: u32,
+    operation: CK_ATTRIBUTE_TYPE,
+    key: *mut SECItem,
+    wincx: *mut c_void
+) -> *mut PK11SymKey
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/fn.PK11_MapSignKeyType.html b/book/rust-docs/nss_sys/fn.PK11_MapSignKeyType.html new file mode 100644 index 0000000000..fdca7e820e --- /dev/null +++ b/book/rust-docs/nss_sys/fn.PK11_MapSignKeyType.html @@ -0,0 +1,3 @@ +PK11_MapSignKeyType in nss_sys - Rust
pub unsafe extern "C" fn PK11_MapSignKeyType(
+    keyType: u32
+) -> CK_MECHANISM_TYPE
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/fn.PK11_PBEKeyGen.html b/book/rust-docs/nss_sys/fn.PK11_PBEKeyGen.html new file mode 100644 index 0000000000..60ffd9b575 --- /dev/null +++ b/book/rust-docs/nss_sys/fn.PK11_PBEKeyGen.html @@ -0,0 +1,7 @@ +PK11_PBEKeyGen in nss_sys - Rust

Function nss_sys::PK11_PBEKeyGen

source ·
pub unsafe extern "C" fn PK11_PBEKeyGen(
+    slot: *mut PK11SlotInfo,
+    algid: *mut SECAlgorithmID,
+    pwitem: *mut SECItem,
+    faulty3DES: PRBool,
+    wincx: *mut c_void
+) -> *mut PK11SymKey
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/fn.PK11_PubDeriveWithKDF.html b/book/rust-docs/nss_sys/fn.PK11_PubDeriveWithKDF.html new file mode 100644 index 0000000000..c6e6c20d32 --- /dev/null +++ b/book/rust-docs/nss_sys/fn.PK11_PubDeriveWithKDF.html @@ -0,0 +1,14 @@ +PK11_PubDeriveWithKDF in nss_sys - Rust
pub unsafe extern "C" fn PK11_PubDeriveWithKDF(
+    privKey: *mut SECKEYPrivateKey,
+    pubKey: *mut SECKEYPublicKey,
+    isSender: PRBool,
+    randomA: *mut SECItem,
+    randomB: *mut SECItem,
+    derive: CK_MECHANISM_TYPE,
+    target: CK_MECHANISM_TYPE,
+    operation: CK_ATTRIBUTE_TYPE,
+    keySize: c_int,
+    kdf: CK_ULONG,
+    sharedData: *mut SECItem,
+    wincx: *mut c_void
+) -> *mut PK11SymKey
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/fn.PK11_ReadRawAttribute.html b/book/rust-docs/nss_sys/fn.PK11_ReadRawAttribute.html new file mode 100644 index 0000000000..b87cc12548 --- /dev/null +++ b/book/rust-docs/nss_sys/fn.PK11_ReadRawAttribute.html @@ -0,0 +1,6 @@ +PK11_ReadRawAttribute in nss_sys - Rust
pub unsafe extern "C" fn PK11_ReadRawAttribute(
+    type_: u32,
+    object: *mut c_void,
+    attr: CK_ATTRIBUTE_TYPE,
+    item: *mut SECItem
+) -> SECStatus
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/fn.PK11_VerifyWithMechanism.html b/book/rust-docs/nss_sys/fn.PK11_VerifyWithMechanism.html new file mode 100644 index 0000000000..3b55b5505c --- /dev/null +++ b/book/rust-docs/nss_sys/fn.PK11_VerifyWithMechanism.html @@ -0,0 +1,8 @@ +PK11_VerifyWithMechanism in nss_sys - Rust
pub unsafe extern "C" fn PK11_VerifyWithMechanism(
+    key: *mut SECKEYPublicKey,
+    mechanism: CK_MECHANISM_TYPE,
+    param: *const SECItem,
+    sig: *const SECItem,
+    hash: *const SECItem,
+    wincx: *mut c_void
+) -> SECStatus
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/fn.PORT_FreeArena.html b/book/rust-docs/nss_sys/fn.PORT_FreeArena.html new file mode 100644 index 0000000000..e7c33d00e4 --- /dev/null +++ b/book/rust-docs/nss_sys/fn.PORT_FreeArena.html @@ -0,0 +1,4 @@ +PORT_FreeArena in nss_sys - Rust

Function nss_sys::PORT_FreeArena

source ·
pub unsafe extern "C" fn PORT_FreeArena(
+    arena: *mut PLArenaPool,
+    zero: PRBool
+)
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/fn.PR_GetError.html b/book/rust-docs/nss_sys/fn.PR_GetError.html new file mode 100644 index 0000000000..b83024e83c --- /dev/null +++ b/book/rust-docs/nss_sys/fn.PR_GetError.html @@ -0,0 +1 @@ +PR_GetError in nss_sys - Rust

Function nss_sys::PR_GetError

source ·
pub unsafe extern "C" fn PR_GetError() -> PRErrorCode
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/fn.PR_GetErrorText.html b/book/rust-docs/nss_sys/fn.PR_GetErrorText.html new file mode 100644 index 0000000000..9907121bd5 --- /dev/null +++ b/book/rust-docs/nss_sys/fn.PR_GetErrorText.html @@ -0,0 +1 @@ +PR_GetErrorText in nss_sys - Rust

Function nss_sys::PR_GetErrorText

source ·
pub unsafe extern "C" fn PR_GetErrorText(text: *mut c_char) -> PRInt32
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/fn.PR_GetErrorTextLength.html b/book/rust-docs/nss_sys/fn.PR_GetErrorTextLength.html new file mode 100644 index 0000000000..bd0600d94c --- /dev/null +++ b/book/rust-docs/nss_sys/fn.PR_GetErrorTextLength.html @@ -0,0 +1 @@ +PR_GetErrorTextLength in nss_sys - Rust
pub unsafe extern "C" fn PR_GetErrorTextLength() -> PRInt32
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/fn.SECITEM_FreeItem.html b/book/rust-docs/nss_sys/fn.SECITEM_FreeItem.html new file mode 100644 index 0000000000..2805dafabc --- /dev/null +++ b/book/rust-docs/nss_sys/fn.SECITEM_FreeItem.html @@ -0,0 +1,4 @@ +SECITEM_FreeItem in nss_sys - Rust

Function nss_sys::SECITEM_FreeItem

source ·
pub unsafe extern "C" fn SECITEM_FreeItem(
+    zap: *mut SECItem,
+    freeit: PRBool
+)
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/fn.SECKEY_ConvertToPublicKey.html b/book/rust-docs/nss_sys/fn.SECKEY_ConvertToPublicKey.html new file mode 100644 index 0000000000..5b4d18c146 --- /dev/null +++ b/book/rust-docs/nss_sys/fn.SECKEY_ConvertToPublicKey.html @@ -0,0 +1,3 @@ +SECKEY_ConvertToPublicKey in nss_sys - Rust
pub unsafe extern "C" fn SECKEY_ConvertToPublicKey(
+    privateKey: *mut SECKEYPrivateKey
+) -> *mut SECKEYPublicKey
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/fn.SECKEY_CopyPublicKey.html b/book/rust-docs/nss_sys/fn.SECKEY_CopyPublicKey.html new file mode 100644 index 0000000000..9d7ef9c91a --- /dev/null +++ b/book/rust-docs/nss_sys/fn.SECKEY_CopyPublicKey.html @@ -0,0 +1,3 @@ +SECKEY_CopyPublicKey in nss_sys - Rust
pub unsafe extern "C" fn SECKEY_CopyPublicKey(
+    pubKey: *const SECKEYPublicKey
+) -> *mut SECKEYPublicKey
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/fn.SECKEY_DestroyPrivateKey.html b/book/rust-docs/nss_sys/fn.SECKEY_DestroyPrivateKey.html new file mode 100644 index 0000000000..be4cb4bb67 --- /dev/null +++ b/book/rust-docs/nss_sys/fn.SECKEY_DestroyPrivateKey.html @@ -0,0 +1,3 @@ +SECKEY_DestroyPrivateKey in nss_sys - Rust
pub unsafe extern "C" fn SECKEY_DestroyPrivateKey(
+    key: *mut SECKEYPrivateKey
+)
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/fn.SECKEY_DestroyPublicKey.html b/book/rust-docs/nss_sys/fn.SECKEY_DestroyPublicKey.html new file mode 100644 index 0000000000..247f906171 --- /dev/null +++ b/book/rust-docs/nss_sys/fn.SECKEY_DestroyPublicKey.html @@ -0,0 +1,3 @@ +SECKEY_DestroyPublicKey in nss_sys - Rust
pub unsafe extern "C" fn SECKEY_DestroyPublicKey(
+    key: *mut SECKEYPublicKey
+)
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/fn.SECOID_DestroyAlgorithmID.html b/book/rust-docs/nss_sys/fn.SECOID_DestroyAlgorithmID.html new file mode 100644 index 0000000000..c41cd96cca --- /dev/null +++ b/book/rust-docs/nss_sys/fn.SECOID_DestroyAlgorithmID.html @@ -0,0 +1,4 @@ +SECOID_DestroyAlgorithmID in nss_sys - Rust
pub unsafe extern "C" fn SECOID_DestroyAlgorithmID(
+    aid: *mut SECAlgorithmID,
+    freeit: PRBool
+)
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/fn.SECOID_FindOIDByTag.html b/book/rust-docs/nss_sys/fn.SECOID_FindOIDByTag.html new file mode 100644 index 0000000000..ec747ee13e --- /dev/null +++ b/book/rust-docs/nss_sys/fn.SECOID_FindOIDByTag.html @@ -0,0 +1,3 @@ +SECOID_FindOIDByTag in nss_sys - Rust
pub unsafe extern "C" fn SECOID_FindOIDByTag(
+    tagnum: u32
+) -> *mut SECOidData
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/fn.VerifyCodeSigningCertificateChain.html b/book/rust-docs/nss_sys/fn.VerifyCodeSigningCertificateChain.html new file mode 100644 index 0000000000..86dd2fc718 --- /dev/null +++ b/book/rust-docs/nss_sys/fn.VerifyCodeSigningCertificateChain.html @@ -0,0 +1,10 @@ +VerifyCodeSigningCertificateChain in nss_sys - Rust
pub unsafe extern "C" fn VerifyCodeSigningCertificateChain(
+    certificates: *mut *const u8,
+    certificateLengths: *const u16,
+    numCertificates: size_t,
+    secondsSinceEpoch: u64,
+    rootSHA256Hash: *const u8,
+    hostname: *const u8,
+    hostnameLength: size_t,
+    error: *mut PRErrorCode
+) -> bool
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/index.html b/book/rust-docs/nss_sys/index.html new file mode 100644 index 0000000000..3bf76a1c56 --- /dev/null +++ b/book/rust-docs/nss_sys/index.html @@ -0,0 +1 @@ +nss_sys - Rust

Crate nss_sys

source ·

Re-exports

Structs

Enums

Constants

Functions

Type Definitions

Unions

\ No newline at end of file diff --git a/book/rust-docs/nss_sys/sidebar-items.js b/book/rust-docs/nss_sys/sidebar-items.js new file mode 100644 index 0000000000..44288f3958 --- /dev/null +++ b/book/rust-docs/nss_sys/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"constant":["AES_BLOCK_SIZE","CKA_CLASS","CKA_EC_PARAMS","CKA_EC_POINT","CKA_ENCRYPT","CKA_ID","CKA_KEY_TYPE","CKA_PRIVATE","CKA_SENSITIVE","CKA_SIGN","CKA_TOKEN","CKA_VALUE","CKA_WRAP","CKD_NULL","CKK_EC","CKM_AES_CBC_PAD","CKM_AES_GCM","CKM_ECDH1_DERIVE","CKM_EC_KEY_PAIR_GEN","CKM_NSS","CKM_NSS_HKDF_SHA256","CKM_NSS_HKDF_SHA384","CKM_SHA256_HMAC","CKM_SHA384_HMAC","CKM_SHA512_HMAC","CKM_VENDOR_DEFINED","CKO_PRIVATE_KEY","CK_FALSE","CK_INVALID_HANDLE","CK_TRUE","EC_POINT_FORM_UNCOMPRESSED","HASH_LENGTH_MAX","NSSCK_VENDOR_NSS","NSS_INIT_FORCEOPEN","NSS_INIT_NOCERTDB","NSS_INIT_NOMODDB","NSS_INIT_OPTIMIZESPACE","NSS_INIT_READONLY","PR_FALSE","PR_TRUE","SEC_ASN1_OBJECT_ID","SHA256_LENGTH","SHA384_LENGTH"],"enum":["ECPointEncoding","KeyType","PK11ObjectType","PK11Origin","SECItemType","SECOidTag","SECStatus","SECSupportExtenTag","_SECStatus"],"fn":["CERT_DestroyCertificate","CERT_ExtractPublicKey","CERT_GetDefaultCertDB","CERT_NewTempCertificate","NSS_InitContext","NSS_SecureMemcmp","NSS_VersionCheck","PK11_CreateContextBySymKey","PK11_CreateGenericObject","PK11_CreatePBEV2AlgorithmID","PK11_Decrypt","PK11_Derive","PK11_DestroyContext","PK11_DestroyGenericObject","PK11_DigestBegin","PK11_DigestFinal","PK11_DigestOp","PK11_Encrypt","PK11_ExtractKeyValue","PK11_FindKeyByKeyID","PK11_FreeSlot","PK11_FreeSymKey","PK11_GenerateKeyPair","PK11_GenerateRandom","PK11_GetInternalSlot","PK11_GetKeyData","PK11_HashBuf","PK11_ImportSymKey","PK11_MapSignKeyType","PK11_PBEKeyGen","PK11_PubDeriveWithKDF","PK11_ReadRawAttribute","PK11_VerifyWithMechanism","PORT_FreeArena","PR_GetError","PR_GetErrorText","PR_GetErrorTextLength","SECITEM_FreeItem","SECKEY_ConvertToPublicKey","SECKEY_CopyPublicKey","SECKEY_DestroyPrivateKey","SECKEY_DestroyPublicKey","SECOID_DestroyAlgorithmID","SECOID_FindOIDByTag","VerifyCodeSigningCertificateChain"],"struct":["CK_ATTRIBUTE","CK_GCM_PARAMS_V3","CK_NSS_HKDFParams","PLArena","PLArenaPool","SECAlgorithmIDStr","SECItemStr","SECKEYDHPublicKeyStr","SECKEYDSAPublicKeyStr","SECKEYECPublicKeyStr","SECKEYFortezzaPublicKeyStr","SECKEYKEAParamsStr","SECKEYKEAPublicKeyStr","SECKEYPQGParamsStr","SECKEYPrivateKeyStr","SECKEYPublicKeyStr","SECKEYRSAPublicKeyStr","SECOidDataStr"],"type":["CERTCertDBHandle","CERTCertificate","CK_ATTRIBUTE_TYPE","CK_BBOOL","CK_BYTE","CK_BYTE_PTR","CK_GCM_PARAMS","CK_KEY_TYPE","CK_MECHANISM_TYPE","CK_OBJECT_CLASS","CK_OBJECT_HANDLE","CK_ULONG","CK_VOID_PTR","NSSInitContext","NSSInitParameters","PK11Context","PK11GenericObject","PK11SlotInfo","PK11SymKey","PRBool","PRErrorCode","PRInt32","PRIntn","PRUint32","PRUword","SECAlgorithmID","SECItem","SECKEYDHPublicKey","SECKEYDSAPublicKey","SECKEYECParams","SECKEYECPublicKey","SECKEYFortezzaPublicKey","SECKEYKEAParams","SECKEYKEAPublicKey","SECKEYPQGParams","SECKEYPrivateKey","SECKEYPublicKey","SECKEYRSAPublicKey","SECOidData","size_t"],"union":["SECKEYPublicKeyStr_u"]}; \ No newline at end of file diff --git a/book/rust-docs/nss_sys/struct.CK_ATTRIBUTE.html b/book/rust-docs/nss_sys/struct.CK_ATTRIBUTE.html new file mode 100644 index 0000000000..6a55a32af1 --- /dev/null +++ b/book/rust-docs/nss_sys/struct.CK_ATTRIBUTE.html @@ -0,0 +1,16 @@ +CK_ATTRIBUTE in nss_sys - Rust

Struct nss_sys::CK_ATTRIBUTE

source ·
#[repr(C)]
pub struct CK_ATTRIBUTE { + pub type_: CK_ATTRIBUTE_TYPE, + pub pValue: CK_VOID_PTR, + pub ulValueLen: CK_ULONG, +}

Fields§

§type_: CK_ATTRIBUTE_TYPE§pValue: CK_VOID_PTR§ulValueLen: CK_ULONG

Trait Implementations§

source§

impl Clone for CK_ATTRIBUTE

source§

fn clone(&self) -> CK_ATTRIBUTE

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Copy for CK_ATTRIBUTE

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/struct.CK_GCM_PARAMS_V3.html b/book/rust-docs/nss_sys/struct.CK_GCM_PARAMS_V3.html new file mode 100644 index 0000000000..e6d58f1247 --- /dev/null +++ b/book/rust-docs/nss_sys/struct.CK_GCM_PARAMS_V3.html @@ -0,0 +1,18 @@ +CK_GCM_PARAMS_V3 in nss_sys - Rust
#[repr(C)]
pub struct CK_GCM_PARAMS_V3 { + pub pIv: CK_BYTE_PTR, + pub ulIvLen: CK_ULONG, + pub ulIvBits: CK_ULONG, + pub pAAD: CK_BYTE_PTR, + pub ulAADLen: CK_ULONG, + pub ulTagBits: CK_ULONG, +}

Fields§

§pIv: CK_BYTE_PTR§ulIvLen: CK_ULONG§ulIvBits: CK_ULONG§pAAD: CK_BYTE_PTR§ulAADLen: CK_ULONG§ulTagBits: CK_ULONG

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/struct.CK_NSS_HKDFParams.html b/book/rust-docs/nss_sys/struct.CK_NSS_HKDFParams.html new file mode 100644 index 0000000000..e34daf9d53 --- /dev/null +++ b/book/rust-docs/nss_sys/struct.CK_NSS_HKDFParams.html @@ -0,0 +1,18 @@ +CK_NSS_HKDFParams in nss_sys - Rust
#[repr(C)]
pub struct CK_NSS_HKDFParams { + pub bExtract: CK_BBOOL, + pub pSalt: CK_BYTE_PTR, + pub ulSaltLen: CK_ULONG, + pub bExpand: CK_BBOOL, + pub pInfo: CK_BYTE_PTR, + pub ulInfoLen: CK_ULONG, +}

Fields§

§bExtract: CK_BBOOL§pSalt: CK_BYTE_PTR§ulSaltLen: CK_ULONG§bExpand: CK_BBOOL§pInfo: CK_BYTE_PTR§ulInfoLen: CK_ULONG

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/struct.PLArena.html b/book/rust-docs/nss_sys/struct.PLArena.html new file mode 100644 index 0000000000..4feb749a21 --- /dev/null +++ b/book/rust-docs/nss_sys/struct.PLArena.html @@ -0,0 +1,17 @@ +PLArena in nss_sys - Rust

Struct nss_sys::PLArena

source ·
#[repr(C)]
pub struct PLArena { + pub next: *mut PLArena, + pub base: PRUword, + pub limit: PRUword, + pub avail: PRUword, +}

Fields§

§next: *mut PLArena§base: PRUword§limit: PRUword§avail: PRUword

Trait Implementations§

source§

impl Clone for PLArena

source§

fn clone(&self) -> PLArena

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Copy for PLArena

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/struct.PLArenaPool.html b/book/rust-docs/nss_sys/struct.PLArenaPool.html new file mode 100644 index 0000000000..a0dd4499fe --- /dev/null +++ b/book/rust-docs/nss_sys/struct.PLArenaPool.html @@ -0,0 +1,17 @@ +PLArenaPool in nss_sys - Rust

Struct nss_sys::PLArenaPool

source ·
#[repr(C)]
pub struct PLArenaPool { + pub first: PLArena, + pub current: *mut PLArena, + pub arenasize: PRUint32, + pub mask: PRUword, +}

Fields§

§first: PLArena§current: *mut PLArena§arenasize: PRUint32§mask: PRUword

Trait Implementations§

source§

impl Clone for PLArenaPool

source§

fn clone(&self) -> PLArenaPool

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Copy for PLArenaPool

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/struct.SECAlgorithmIDStr.html b/book/rust-docs/nss_sys/struct.SECAlgorithmIDStr.html new file mode 100644 index 0000000000..76585c99d1 --- /dev/null +++ b/book/rust-docs/nss_sys/struct.SECAlgorithmIDStr.html @@ -0,0 +1,15 @@ +SECAlgorithmIDStr in nss_sys - Rust
#[repr(C)]
pub struct SECAlgorithmIDStr { + pub algorithm: SECItem, + pub parameters: SECItem, +}

Fields§

§algorithm: SECItem§parameters: SECItem

Trait Implementations§

source§

impl Clone for SECAlgorithmIDStr

source§

fn clone(&self) -> SECAlgorithmIDStr

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Copy for SECAlgorithmIDStr

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/struct.SECItemStr.html b/book/rust-docs/nss_sys/struct.SECItemStr.html new file mode 100644 index 0000000000..ed5e26bf28 --- /dev/null +++ b/book/rust-docs/nss_sys/struct.SECItemStr.html @@ -0,0 +1,16 @@ +SECItemStr in nss_sys - Rust

Struct nss_sys::SECItemStr

source ·
#[repr(C)]
pub struct SECItemStr { + pub type_: u32, + pub data: *mut c_uchar, + pub len: c_uint, +}

Fields§

§type_: u32§data: *mut c_uchar§len: c_uint

Trait Implementations§

source§

impl Clone for SECItemStr

source§

fn clone(&self) -> SECItemStr

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for SECItemStr

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Copy for SECItemStr

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/struct.SECKEYDHPublicKeyStr.html b/book/rust-docs/nss_sys/struct.SECKEYDHPublicKeyStr.html new file mode 100644 index 0000000000..5adee412cf --- /dev/null +++ b/book/rust-docs/nss_sys/struct.SECKEYDHPublicKeyStr.html @@ -0,0 +1,17 @@ +SECKEYDHPublicKeyStr in nss_sys - Rust
#[repr(C)]
pub struct SECKEYDHPublicKeyStr { + pub arena: *mut PLArenaPool, + pub prime: SECItem, + pub base: SECItem, + pub publicValue: SECItem, +}

Fields§

§arena: *mut PLArenaPool§prime: SECItem§base: SECItem§publicValue: SECItem

Trait Implementations§

source§

impl Clone for SECKEYDHPublicKeyStr

source§

fn clone(&self) -> SECKEYDHPublicKeyStr

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Copy for SECKEYDHPublicKeyStr

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/struct.SECKEYDSAPublicKeyStr.html b/book/rust-docs/nss_sys/struct.SECKEYDSAPublicKeyStr.html new file mode 100644 index 0000000000..05fa737c2f --- /dev/null +++ b/book/rust-docs/nss_sys/struct.SECKEYDSAPublicKeyStr.html @@ -0,0 +1,15 @@ +SECKEYDSAPublicKeyStr in nss_sys - Rust
#[repr(C)]
pub struct SECKEYDSAPublicKeyStr { + pub params: SECKEYPQGParams, + pub publicValue: SECItem, +}

Fields§

§params: SECKEYPQGParams§publicValue: SECItem

Trait Implementations§

source§

impl Clone for SECKEYDSAPublicKeyStr

source§

fn clone(&self) -> SECKEYDSAPublicKeyStr

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Copy for SECKEYDSAPublicKeyStr

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/struct.SECKEYECPublicKeyStr.html b/book/rust-docs/nss_sys/struct.SECKEYECPublicKeyStr.html new file mode 100644 index 0000000000..8ada5c089d --- /dev/null +++ b/book/rust-docs/nss_sys/struct.SECKEYECPublicKeyStr.html @@ -0,0 +1,17 @@ +SECKEYECPublicKeyStr in nss_sys - Rust
#[repr(C)]
pub struct SECKEYECPublicKeyStr { + pub DEREncodedParams: SECKEYECParams, + pub size: c_int, + pub publicValue: SECItem, + pub encoding: u32, +}

Fields§

§DEREncodedParams: SECKEYECParams§size: c_int§publicValue: SECItem§encoding: u32

Trait Implementations§

source§

impl Clone for SECKEYECPublicKeyStr

source§

fn clone(&self) -> SECKEYECPublicKeyStr

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Copy for SECKEYECPublicKeyStr

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/struct.SECKEYFortezzaPublicKeyStr.html b/book/rust-docs/nss_sys/struct.SECKEYFortezzaPublicKeyStr.html new file mode 100644 index 0000000000..567e0a9668 --- /dev/null +++ b/book/rust-docs/nss_sys/struct.SECKEYFortezzaPublicKeyStr.html @@ -0,0 +1,23 @@ +SECKEYFortezzaPublicKeyStr in nss_sys - Rust
#[repr(C)]
pub struct SECKEYFortezzaPublicKeyStr { + pub KEAversion: c_int, + pub DSSversion: c_int, + pub KMID: [c_uchar; 8], + pub clearance: SECItem, + pub KEApriviledge: SECItem, + pub DSSpriviledge: SECItem, + pub KEAKey: SECItem, + pub DSSKey: SECItem, + pub params: SECKEYPQGParams, + pub keaParams: SECKEYPQGParams, +}

Fields§

§KEAversion: c_int§DSSversion: c_int§KMID: [c_uchar; 8]§clearance: SECItem§KEApriviledge: SECItem§DSSpriviledge: SECItem§KEAKey: SECItem§DSSKey: SECItem§params: SECKEYPQGParams§keaParams: SECKEYPQGParams

Trait Implementations§

source§

impl Clone for SECKEYFortezzaPublicKeyStr

source§

fn clone(&self) -> SECKEYFortezzaPublicKeyStr

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Copy for SECKEYFortezzaPublicKeyStr

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/struct.SECKEYKEAParamsStr.html b/book/rust-docs/nss_sys/struct.SECKEYKEAParamsStr.html new file mode 100644 index 0000000000..f52099f5ce --- /dev/null +++ b/book/rust-docs/nss_sys/struct.SECKEYKEAParamsStr.html @@ -0,0 +1,15 @@ +SECKEYKEAParamsStr in nss_sys - Rust
#[repr(C)]
pub struct SECKEYKEAParamsStr { + pub arena: *mut PLArenaPool, + pub hash: SECItem, +}

Fields§

§arena: *mut PLArenaPool§hash: SECItem

Trait Implementations§

source§

impl Clone for SECKEYKEAParamsStr

source§

fn clone(&self) -> SECKEYKEAParamsStr

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Copy for SECKEYKEAParamsStr

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/struct.SECKEYKEAPublicKeyStr.html b/book/rust-docs/nss_sys/struct.SECKEYKEAPublicKeyStr.html new file mode 100644 index 0000000000..9e583d0d52 --- /dev/null +++ b/book/rust-docs/nss_sys/struct.SECKEYKEAPublicKeyStr.html @@ -0,0 +1,15 @@ +SECKEYKEAPublicKeyStr in nss_sys - Rust
#[repr(C)]
pub struct SECKEYKEAPublicKeyStr { + pub params: SECKEYKEAParams, + pub publicValue: SECItem, +}

Fields§

§params: SECKEYKEAParams§publicValue: SECItem

Trait Implementations§

source§

impl Clone for SECKEYKEAPublicKeyStr

source§

fn clone(&self) -> SECKEYKEAPublicKeyStr

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Copy for SECKEYKEAPublicKeyStr

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/struct.SECKEYPQGParamsStr.html b/book/rust-docs/nss_sys/struct.SECKEYPQGParamsStr.html new file mode 100644 index 0000000000..564a5c01c2 --- /dev/null +++ b/book/rust-docs/nss_sys/struct.SECKEYPQGParamsStr.html @@ -0,0 +1,17 @@ +SECKEYPQGParamsStr in nss_sys - Rust
#[repr(C)]
pub struct SECKEYPQGParamsStr { + pub arena: *mut PLArenaPool, + pub prime: SECItem, + pub subPrime: SECItem, + pub base: SECItem, +}

Fields§

§arena: *mut PLArenaPool§prime: SECItem§subPrime: SECItem§base: SECItem

Trait Implementations§

source§

impl Clone for SECKEYPQGParamsStr

source§

fn clone(&self) -> SECKEYPQGParamsStr

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Copy for SECKEYPQGParamsStr

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/struct.SECKEYPrivateKeyStr.html b/book/rust-docs/nss_sys/struct.SECKEYPrivateKeyStr.html new file mode 100644 index 0000000000..7eba7a40d3 --- /dev/null +++ b/book/rust-docs/nss_sys/struct.SECKEYPrivateKeyStr.html @@ -0,0 +1,19 @@ +SECKEYPrivateKeyStr in nss_sys - Rust
#[repr(C)]
pub struct SECKEYPrivateKeyStr { + pub arena: *mut PLArenaPool, + pub keyType: u32, + pub pkcs11Slot: *mut PK11SlotInfo, + pub pkcs11ID: CK_OBJECT_HANDLE, + pub pkcs11IsTemp: PRBool, + pub wincx: *mut c_void, + pub staticflags: PRUint32, +}

Fields§

§arena: *mut PLArenaPool§keyType: u32§pkcs11Slot: *mut PK11SlotInfo§pkcs11ID: CK_OBJECT_HANDLE§pkcs11IsTemp: PRBool§wincx: *mut c_void§staticflags: PRUint32

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/struct.SECKEYPublicKeyStr.html b/book/rust-docs/nss_sys/struct.SECKEYPublicKeyStr.html new file mode 100644 index 0000000000..f6c1c5d5ca --- /dev/null +++ b/book/rust-docs/nss_sys/struct.SECKEYPublicKeyStr.html @@ -0,0 +1,17 @@ +SECKEYPublicKeyStr in nss_sys - Rust
#[repr(C)]
pub struct SECKEYPublicKeyStr { + pub arena: *mut PLArenaPool, + pub keyType: u32, + pub pkcs11Slot: *mut PK11SlotInfo, + pub pkcs11ID: CK_OBJECT_HANDLE, + pub u: SECKEYPublicKeyStr_u, +}

Fields§

§arena: *mut PLArenaPool§keyType: u32§pkcs11Slot: *mut PK11SlotInfo§pkcs11ID: CK_OBJECT_HANDLE§u: SECKEYPublicKeyStr_u

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/struct.SECKEYRSAPublicKeyStr.html b/book/rust-docs/nss_sys/struct.SECKEYRSAPublicKeyStr.html new file mode 100644 index 0000000000..515cc8a8ae --- /dev/null +++ b/book/rust-docs/nss_sys/struct.SECKEYRSAPublicKeyStr.html @@ -0,0 +1,16 @@ +SECKEYRSAPublicKeyStr in nss_sys - Rust
#[repr(C)]
pub struct SECKEYRSAPublicKeyStr { + pub arena: *mut PLArenaPool, + pub modulus: SECItem, + pub publicExponent: SECItem, +}

Fields§

§arena: *mut PLArenaPool§modulus: SECItem§publicExponent: SECItem

Trait Implementations§

source§

impl Clone for SECKEYRSAPublicKeyStr

source§

fn clone(&self) -> SECKEYRSAPublicKeyStr

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Copy for SECKEYRSAPublicKeyStr

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/struct.SECOidDataStr.html b/book/rust-docs/nss_sys/struct.SECOidDataStr.html new file mode 100644 index 0000000000..af3632df3e --- /dev/null +++ b/book/rust-docs/nss_sys/struct.SECOidDataStr.html @@ -0,0 +1,18 @@ +SECOidDataStr in nss_sys - Rust

Struct nss_sys::SECOidDataStr

source ·
#[repr(C)]
pub struct SECOidDataStr { + pub oid: SECItem, + pub offset: u32, + pub desc: *const c_char, + pub mechanism: c_ulong, + pub supportedExtension: u32, +}

Fields§

§oid: SECItem§offset: u32§desc: *const c_char§mechanism: c_ulong§supportedExtension: u32

Trait Implementations§

source§

impl Clone for SECOidDataStr

source§

fn clone(&self) -> SECOidDataStr

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Copy for SECOidDataStr

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/type.CERTCertDBHandle.html b/book/rust-docs/nss_sys/type.CERTCertDBHandle.html new file mode 100644 index 0000000000..40081ab6fa --- /dev/null +++ b/book/rust-docs/nss_sys/type.CERTCertDBHandle.html @@ -0,0 +1 @@ +CERTCertDBHandle in nss_sys - Rust

Type Definition nss_sys::CERTCertDBHandle

source ·
pub type CERTCertDBHandle = u8;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/type.CERTCertificate.html b/book/rust-docs/nss_sys/type.CERTCertificate.html new file mode 100644 index 0000000000..3654a09040 --- /dev/null +++ b/book/rust-docs/nss_sys/type.CERTCertificate.html @@ -0,0 +1 @@ +CERTCertificate in nss_sys - Rust

Type Definition nss_sys::CERTCertificate

source ·
pub type CERTCertificate = u8;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/type.CK_ATTRIBUTE_TYPE.html b/book/rust-docs/nss_sys/type.CK_ATTRIBUTE_TYPE.html new file mode 100644 index 0000000000..b1f7f15788 --- /dev/null +++ b/book/rust-docs/nss_sys/type.CK_ATTRIBUTE_TYPE.html @@ -0,0 +1 @@ +CK_ATTRIBUTE_TYPE in nss_sys - Rust

Type Definition nss_sys::CK_ATTRIBUTE_TYPE

source ·
pub type CK_ATTRIBUTE_TYPE = CK_ULONG;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/type.CK_BBOOL.html b/book/rust-docs/nss_sys/type.CK_BBOOL.html new file mode 100644 index 0000000000..5a90880a18 --- /dev/null +++ b/book/rust-docs/nss_sys/type.CK_BBOOL.html @@ -0,0 +1 @@ +CK_BBOOL in nss_sys - Rust

Type Definition nss_sys::CK_BBOOL

source ·
pub type CK_BBOOL = CK_BYTE;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/type.CK_BYTE.html b/book/rust-docs/nss_sys/type.CK_BYTE.html new file mode 100644 index 0000000000..a0cdef8d8a --- /dev/null +++ b/book/rust-docs/nss_sys/type.CK_BYTE.html @@ -0,0 +1 @@ +CK_BYTE in nss_sys - Rust

Type Definition nss_sys::CK_BYTE

source ·
pub type CK_BYTE = c_uchar;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/type.CK_BYTE_PTR.html b/book/rust-docs/nss_sys/type.CK_BYTE_PTR.html new file mode 100644 index 0000000000..7086a34b1d --- /dev/null +++ b/book/rust-docs/nss_sys/type.CK_BYTE_PTR.html @@ -0,0 +1 @@ +CK_BYTE_PTR in nss_sys - Rust

Type Definition nss_sys::CK_BYTE_PTR

source ·
pub type CK_BYTE_PTR = *mut CK_BYTE;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/type.CK_GCM_PARAMS.html b/book/rust-docs/nss_sys/type.CK_GCM_PARAMS.html new file mode 100644 index 0000000000..40cccea150 --- /dev/null +++ b/book/rust-docs/nss_sys/type.CK_GCM_PARAMS.html @@ -0,0 +1 @@ +CK_GCM_PARAMS in nss_sys - Rust

Type Definition nss_sys::CK_GCM_PARAMS

source ·
pub type CK_GCM_PARAMS = CK_GCM_PARAMS_V3;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/type.CK_KEY_TYPE.html b/book/rust-docs/nss_sys/type.CK_KEY_TYPE.html new file mode 100644 index 0000000000..173a894c75 --- /dev/null +++ b/book/rust-docs/nss_sys/type.CK_KEY_TYPE.html @@ -0,0 +1 @@ +CK_KEY_TYPE in nss_sys - Rust

Type Definition nss_sys::CK_KEY_TYPE

source ·
pub type CK_KEY_TYPE = CK_ULONG;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/type.CK_MECHANISM_TYPE.html b/book/rust-docs/nss_sys/type.CK_MECHANISM_TYPE.html new file mode 100644 index 0000000000..d03bea08f4 --- /dev/null +++ b/book/rust-docs/nss_sys/type.CK_MECHANISM_TYPE.html @@ -0,0 +1 @@ +CK_MECHANISM_TYPE in nss_sys - Rust

Type Definition nss_sys::CK_MECHANISM_TYPE

source ·
pub type CK_MECHANISM_TYPE = CK_ULONG;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/type.CK_OBJECT_CLASS.html b/book/rust-docs/nss_sys/type.CK_OBJECT_CLASS.html new file mode 100644 index 0000000000..c708b968f7 --- /dev/null +++ b/book/rust-docs/nss_sys/type.CK_OBJECT_CLASS.html @@ -0,0 +1 @@ +CK_OBJECT_CLASS in nss_sys - Rust

Type Definition nss_sys::CK_OBJECT_CLASS

source ·
pub type CK_OBJECT_CLASS = CK_ULONG;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/type.CK_OBJECT_HANDLE.html b/book/rust-docs/nss_sys/type.CK_OBJECT_HANDLE.html new file mode 100644 index 0000000000..adc0dfa8f5 --- /dev/null +++ b/book/rust-docs/nss_sys/type.CK_OBJECT_HANDLE.html @@ -0,0 +1 @@ +CK_OBJECT_HANDLE in nss_sys - Rust

Type Definition nss_sys::CK_OBJECT_HANDLE

source ·
pub type CK_OBJECT_HANDLE = CK_ULONG;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/type.CK_ULONG.html b/book/rust-docs/nss_sys/type.CK_ULONG.html new file mode 100644 index 0000000000..b4ec2b0c25 --- /dev/null +++ b/book/rust-docs/nss_sys/type.CK_ULONG.html @@ -0,0 +1 @@ +CK_ULONG in nss_sys - Rust

Type Definition nss_sys::CK_ULONG

source ·
pub type CK_ULONG = c_ulong;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/type.CK_VOID_PTR.html b/book/rust-docs/nss_sys/type.CK_VOID_PTR.html new file mode 100644 index 0000000000..de98657ea4 --- /dev/null +++ b/book/rust-docs/nss_sys/type.CK_VOID_PTR.html @@ -0,0 +1 @@ +CK_VOID_PTR in nss_sys - Rust

Type Definition nss_sys::CK_VOID_PTR

source ·
pub type CK_VOID_PTR = *mut c_void;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/type.NSSInitContext.html b/book/rust-docs/nss_sys/type.NSSInitContext.html new file mode 100644 index 0000000000..5d9f9e79e9 --- /dev/null +++ b/book/rust-docs/nss_sys/type.NSSInitContext.html @@ -0,0 +1 @@ +NSSInitContext in nss_sys - Rust

Type Definition nss_sys::NSSInitContext

source ·
pub type NSSInitContext = u8;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/type.NSSInitParameters.html b/book/rust-docs/nss_sys/type.NSSInitParameters.html new file mode 100644 index 0000000000..0b9b7441b4 --- /dev/null +++ b/book/rust-docs/nss_sys/type.NSSInitParameters.html @@ -0,0 +1 @@ +NSSInitParameters in nss_sys - Rust

Type Definition nss_sys::NSSInitParameters

source ·
pub type NSSInitParameters = [u64; 10];
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/type.PK11Context.html b/book/rust-docs/nss_sys/type.PK11Context.html new file mode 100644 index 0000000000..e359f16ccf --- /dev/null +++ b/book/rust-docs/nss_sys/type.PK11Context.html @@ -0,0 +1 @@ +PK11Context in nss_sys - Rust

Type Definition nss_sys::PK11Context

source ·
pub type PK11Context = u8;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/type.PK11GenericObject.html b/book/rust-docs/nss_sys/type.PK11GenericObject.html new file mode 100644 index 0000000000..0eadc990ac --- /dev/null +++ b/book/rust-docs/nss_sys/type.PK11GenericObject.html @@ -0,0 +1 @@ +PK11GenericObject in nss_sys - Rust

Type Definition nss_sys::PK11GenericObject

source ·
pub type PK11GenericObject = u8;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/type.PK11SlotInfo.html b/book/rust-docs/nss_sys/type.PK11SlotInfo.html new file mode 100644 index 0000000000..3e16f44c50 --- /dev/null +++ b/book/rust-docs/nss_sys/type.PK11SlotInfo.html @@ -0,0 +1 @@ +PK11SlotInfo in nss_sys - Rust

Type Definition nss_sys::PK11SlotInfo

source ·
pub type PK11SlotInfo = u8;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/type.PK11SymKey.html b/book/rust-docs/nss_sys/type.PK11SymKey.html new file mode 100644 index 0000000000..ec58f76510 --- /dev/null +++ b/book/rust-docs/nss_sys/type.PK11SymKey.html @@ -0,0 +1 @@ +PK11SymKey in nss_sys - Rust

Type Definition nss_sys::PK11SymKey

source ·
pub type PK11SymKey = u8;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/type.PRBool.html b/book/rust-docs/nss_sys/type.PRBool.html new file mode 100644 index 0000000000..0ead8cd6a8 --- /dev/null +++ b/book/rust-docs/nss_sys/type.PRBool.html @@ -0,0 +1 @@ +PRBool in nss_sys - Rust

Type Definition nss_sys::PRBool

source ·
pub type PRBool = PRIntn;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/type.PRErrorCode.html b/book/rust-docs/nss_sys/type.PRErrorCode.html new file mode 100644 index 0000000000..44fc1e77d4 --- /dev/null +++ b/book/rust-docs/nss_sys/type.PRErrorCode.html @@ -0,0 +1 @@ +PRErrorCode in nss_sys - Rust

Type Definition nss_sys::PRErrorCode

source ·
pub type PRErrorCode = PRInt32;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/type.PRInt32.html b/book/rust-docs/nss_sys/type.PRInt32.html new file mode 100644 index 0000000000..5210f8caeb --- /dev/null +++ b/book/rust-docs/nss_sys/type.PRInt32.html @@ -0,0 +1 @@ +PRInt32 in nss_sys - Rust

Type Definition nss_sys::PRInt32

source ·
pub type PRInt32 = c_int;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/type.PRIntn.html b/book/rust-docs/nss_sys/type.PRIntn.html new file mode 100644 index 0000000000..485d90a199 --- /dev/null +++ b/book/rust-docs/nss_sys/type.PRIntn.html @@ -0,0 +1 @@ +PRIntn in nss_sys - Rust

Type Definition nss_sys::PRIntn

source ·
pub type PRIntn = c_int;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/type.PRUint32.html b/book/rust-docs/nss_sys/type.PRUint32.html new file mode 100644 index 0000000000..4a7452e720 --- /dev/null +++ b/book/rust-docs/nss_sys/type.PRUint32.html @@ -0,0 +1 @@ +PRUint32 in nss_sys - Rust

Type Definition nss_sys::PRUint32

source ·
pub type PRUint32 = c_uint;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/type.PRUword.html b/book/rust-docs/nss_sys/type.PRUword.html new file mode 100644 index 0000000000..56d44a1a1e --- /dev/null +++ b/book/rust-docs/nss_sys/type.PRUword.html @@ -0,0 +1 @@ +PRUword in nss_sys - Rust

Type Definition nss_sys::PRUword

source ·
pub type PRUword = usize;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/type.SECAlgorithmID.html b/book/rust-docs/nss_sys/type.SECAlgorithmID.html new file mode 100644 index 0000000000..dc4e04ddd6 --- /dev/null +++ b/book/rust-docs/nss_sys/type.SECAlgorithmID.html @@ -0,0 +1 @@ +SECAlgorithmID in nss_sys - Rust

Type Definition nss_sys::SECAlgorithmID

source ·
pub type SECAlgorithmID = SECAlgorithmIDStr;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/type.SECItem.html b/book/rust-docs/nss_sys/type.SECItem.html new file mode 100644 index 0000000000..4c7bfeda9d --- /dev/null +++ b/book/rust-docs/nss_sys/type.SECItem.html @@ -0,0 +1 @@ +SECItem in nss_sys - Rust

Type Definition nss_sys::SECItem

source ·
pub type SECItem = SECItemStr;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/type.SECKEYDHPublicKey.html b/book/rust-docs/nss_sys/type.SECKEYDHPublicKey.html new file mode 100644 index 0000000000..b2ef4a1fe8 --- /dev/null +++ b/book/rust-docs/nss_sys/type.SECKEYDHPublicKey.html @@ -0,0 +1 @@ +SECKEYDHPublicKey in nss_sys - Rust

Type Definition nss_sys::SECKEYDHPublicKey

source ·
pub type SECKEYDHPublicKey = SECKEYDHPublicKeyStr;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/type.SECKEYDSAPublicKey.html b/book/rust-docs/nss_sys/type.SECKEYDSAPublicKey.html new file mode 100644 index 0000000000..9dc334d235 --- /dev/null +++ b/book/rust-docs/nss_sys/type.SECKEYDSAPublicKey.html @@ -0,0 +1 @@ +SECKEYDSAPublicKey in nss_sys - Rust

Type Definition nss_sys::SECKEYDSAPublicKey

source ·
pub type SECKEYDSAPublicKey = SECKEYDSAPublicKeyStr;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/type.SECKEYECParams.html b/book/rust-docs/nss_sys/type.SECKEYECParams.html new file mode 100644 index 0000000000..9ac86b77f7 --- /dev/null +++ b/book/rust-docs/nss_sys/type.SECKEYECParams.html @@ -0,0 +1 @@ +SECKEYECParams in nss_sys - Rust

Type Definition nss_sys::SECKEYECParams

source ·
pub type SECKEYECParams = SECItem;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/type.SECKEYECPublicKey.html b/book/rust-docs/nss_sys/type.SECKEYECPublicKey.html new file mode 100644 index 0000000000..3f24ff97c1 --- /dev/null +++ b/book/rust-docs/nss_sys/type.SECKEYECPublicKey.html @@ -0,0 +1 @@ +SECKEYECPublicKey in nss_sys - Rust

Type Definition nss_sys::SECKEYECPublicKey

source ·
pub type SECKEYECPublicKey = SECKEYECPublicKeyStr;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/type.SECKEYFortezzaPublicKey.html b/book/rust-docs/nss_sys/type.SECKEYFortezzaPublicKey.html new file mode 100644 index 0000000000..217b3f804c --- /dev/null +++ b/book/rust-docs/nss_sys/type.SECKEYFortezzaPublicKey.html @@ -0,0 +1 @@ +SECKEYFortezzaPublicKey in nss_sys - Rust

Type Definition nss_sys::SECKEYFortezzaPublicKey

source ·
pub type SECKEYFortezzaPublicKey = SECKEYFortezzaPublicKeyStr;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/type.SECKEYKEAParams.html b/book/rust-docs/nss_sys/type.SECKEYKEAParams.html new file mode 100644 index 0000000000..3fb6f729f0 --- /dev/null +++ b/book/rust-docs/nss_sys/type.SECKEYKEAParams.html @@ -0,0 +1 @@ +SECKEYKEAParams in nss_sys - Rust

Type Definition nss_sys::SECKEYKEAParams

source ·
pub type SECKEYKEAParams = SECKEYKEAParamsStr;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/type.SECKEYKEAPublicKey.html b/book/rust-docs/nss_sys/type.SECKEYKEAPublicKey.html new file mode 100644 index 0000000000..42ed85f1c7 --- /dev/null +++ b/book/rust-docs/nss_sys/type.SECKEYKEAPublicKey.html @@ -0,0 +1 @@ +SECKEYKEAPublicKey in nss_sys - Rust

Type Definition nss_sys::SECKEYKEAPublicKey

source ·
pub type SECKEYKEAPublicKey = SECKEYKEAPublicKeyStr;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/type.SECKEYPQGParams.html b/book/rust-docs/nss_sys/type.SECKEYPQGParams.html new file mode 100644 index 0000000000..d6a0142746 --- /dev/null +++ b/book/rust-docs/nss_sys/type.SECKEYPQGParams.html @@ -0,0 +1 @@ +SECKEYPQGParams in nss_sys - Rust

Type Definition nss_sys::SECKEYPQGParams

source ·
pub type SECKEYPQGParams = SECKEYPQGParamsStr;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/type.SECKEYPrivateKey.html b/book/rust-docs/nss_sys/type.SECKEYPrivateKey.html new file mode 100644 index 0000000000..a45f0b161f --- /dev/null +++ b/book/rust-docs/nss_sys/type.SECKEYPrivateKey.html @@ -0,0 +1 @@ +SECKEYPrivateKey in nss_sys - Rust

Type Definition nss_sys::SECKEYPrivateKey

source ·
pub type SECKEYPrivateKey = SECKEYPrivateKeyStr;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/type.SECKEYPublicKey.html b/book/rust-docs/nss_sys/type.SECKEYPublicKey.html new file mode 100644 index 0000000000..fc198853f7 --- /dev/null +++ b/book/rust-docs/nss_sys/type.SECKEYPublicKey.html @@ -0,0 +1 @@ +SECKEYPublicKey in nss_sys - Rust

Type Definition nss_sys::SECKEYPublicKey

source ·
pub type SECKEYPublicKey = SECKEYPublicKeyStr;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/type.SECKEYRSAPublicKey.html b/book/rust-docs/nss_sys/type.SECKEYRSAPublicKey.html new file mode 100644 index 0000000000..f48cddc0bb --- /dev/null +++ b/book/rust-docs/nss_sys/type.SECKEYRSAPublicKey.html @@ -0,0 +1 @@ +SECKEYRSAPublicKey in nss_sys - Rust

Type Definition nss_sys::SECKEYRSAPublicKey

source ·
pub type SECKEYRSAPublicKey = SECKEYRSAPublicKeyStr;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/type.SECOidData.html b/book/rust-docs/nss_sys/type.SECOidData.html new file mode 100644 index 0000000000..565bb7bc0e --- /dev/null +++ b/book/rust-docs/nss_sys/type.SECOidData.html @@ -0,0 +1 @@ +SECOidData in nss_sys - Rust

Type Definition nss_sys::SECOidData

source ·
pub type SECOidData = SECOidDataStr;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/type.size_t.html b/book/rust-docs/nss_sys/type.size_t.html new file mode 100644 index 0000000000..51c407adbe --- /dev/null +++ b/book/rust-docs/nss_sys/type.size_t.html @@ -0,0 +1 @@ +size_t in nss_sys - Rust

Type Definition nss_sys::size_t

source ·
pub type size_t = usize;
\ No newline at end of file diff --git a/book/rust-docs/nss_sys/union.SECKEYPublicKeyStr_u.html b/book/rust-docs/nss_sys/union.SECKEYPublicKeyStr_u.html new file mode 100644 index 0000000000..3a66592fd2 --- /dev/null +++ b/book/rust-docs/nss_sys/union.SECKEYPublicKeyStr_u.html @@ -0,0 +1,19 @@ +SECKEYPublicKeyStr_u in nss_sys - Rust
#[repr(C)]
+pub union SECKEYPublicKeyStr_u {
+    pub rsa: SECKEYRSAPublicKey,
+    pub dsa: SECKEYDSAPublicKey,
+    pub dh: SECKEYDHPublicKey,
+    pub kea: SECKEYKEAPublicKey,
+    pub fortezza: SECKEYFortezzaPublicKey,
+    pub ec: SECKEYECPublicKey,
+}

Fields§

§rsa: SECKEYRSAPublicKey§dsa: SECKEYDSAPublicKey§dh: SECKEYDHPublicKey§kea: SECKEYKEAPublicKey§fortezza: SECKEYFortezzaPublicKey§ec: SECKEYECPublicKey

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/places/all.html b/book/rust-docs/places/all.html new file mode 100644 index 0000000000..f0db3e18da --- /dev/null +++ b/book/rust-docs/places/all.html @@ -0,0 +1 @@ +List of all items in this crate

List of all items

Structs

Enums

Functions

Type Definitions

Constants

\ No newline at end of file diff --git a/book/rust-docs/places/api/fn.apply_observation.html b/book/rust-docs/places/api/fn.apply_observation.html new file mode 100644 index 0000000000..e4f4bae4df --- /dev/null +++ b/book/rust-docs/places/api/fn.apply_observation.html @@ -0,0 +1,4 @@ +apply_observation in places::api - Rust

Function places::api::apply_observation

source ·
pub fn apply_observation(
+    conn: &mut PlacesDb,
+    visit_obs: VisitObservation
+) -> Result<()>
\ No newline at end of file diff --git a/book/rust-docs/places/api/history/enum.RedirectSourceType.html b/book/rust-docs/places/api/history/enum.RedirectSourceType.html new file mode 100644 index 0000000000..2eb48547d0 --- /dev/null +++ b/book/rust-docs/places/api/history/enum.RedirectSourceType.html @@ -0,0 +1,20 @@ +RedirectSourceType in places::api::history - Rust
pub enum RedirectSourceType {
+    Temporary,
+    Permanent,
+}

Variants§

§

Temporary

§

Permanent

Trait Implementations§

source§

impl Clone for RedirectSourceType

source§

fn clone(&self) -> RedirectSourceType

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for RedirectSourceType

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl PartialEq<RedirectSourceType> for RedirectSourceType

source§

fn eq(&self, other: &RedirectSourceType) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl PartialOrd<RedirectSourceType> for RedirectSourceType

source§

fn partial_cmp(&self, other: &RedirectSourceType) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · source§

fn lt(&self, other: &Rhs) -> bool

This method tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · source§

fn le(&self, other: &Rhs) -> bool

This method tests less than or equal to (for self and other) and is used by the <= +operator. Read more
1.0.0 · source§

fn gt(&self, other: &Rhs) -> bool

This method tests greater than (for self and other) and is used by the > operator. Read more
1.0.0 · source§

fn ge(&self, other: &Rhs) -> bool

This method tests greater than or equal to (for self and other) and is used by the >= +operator. Read more
source§

impl Copy for RedirectSourceType

source§

impl Eq for RedirectSourceType

source§

impl StructuralEq for RedirectSourceType

source§

impl StructuralPartialEq for RedirectSourceType

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/api/history/fn.can_add_url.html b/book/rust-docs/places/api/history/fn.can_add_url.html new file mode 100644 index 0000000000..5e3996b05b --- /dev/null +++ b/book/rust-docs/places/api/history/fn.can_add_url.html @@ -0,0 +1 @@ +can_add_url in places::api::history - Rust

Function places::api::history::can_add_url

source ·
pub fn can_add_url(_url: &Url) -> Result<bool>
\ No newline at end of file diff --git a/book/rust-docs/places/api/history/fn.insert.html b/book/rust-docs/places/api/history/fn.insert.html new file mode 100644 index 0000000000..2bada5973c --- /dev/null +++ b/book/rust-docs/places/api/history/fn.insert.html @@ -0,0 +1 @@ +insert in places::api::history - Rust

Function places::api::history::insert

source ·
pub fn insert(conn: &mut PlacesDb, place: AddablePlaceInfo) -> Result<()>
\ No newline at end of file diff --git a/book/rust-docs/places/api/history/fn.visit_uri.html b/book/rust-docs/places/api/history/fn.visit_uri.html new file mode 100644 index 0000000000..c181420d44 --- /dev/null +++ b/book/rust-docs/places/api/history/fn.visit_uri.html @@ -0,0 +1,8 @@ +visit_uri in places::api::history - Rust

Function places::api::history::visit_uri

source ·
pub fn visit_uri(
+    conn: &mut PlacesDb,
+    url: &Url,
+    last_url: Option<Url>,
+    transition: VisitType,
+    redirect_source: Option<RedirectSourceType>,
+    is_error_page: bool
+) -> Result<()>
\ No newline at end of file diff --git a/book/rust-docs/places/api/history/index.html b/book/rust-docs/places/api/history/index.html new file mode 100644 index 0000000000..a4a33531b6 --- /dev/null +++ b/book/rust-docs/places/api/history/index.html @@ -0,0 +1 @@ +places::api::history - Rust
\ No newline at end of file diff --git a/book/rust-docs/places/api/history/sidebar-items.js b/book/rust-docs/places/api/history/sidebar-items.js new file mode 100644 index 0000000000..d63004bb6d --- /dev/null +++ b/book/rust-docs/places/api/history/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["RedirectSourceType"],"fn":["can_add_url","insert","visit_uri"],"struct":["AddablePlaceInfo","AddableVisit"]}; \ No newline at end of file diff --git a/book/rust-docs/places/api/history/struct.AddablePlaceInfo.html b/book/rust-docs/places/api/history/struct.AddablePlaceInfo.html new file mode 100644 index 0000000000..1204d4240b --- /dev/null +++ b/book/rust-docs/places/api/history/struct.AddablePlaceInfo.html @@ -0,0 +1,16 @@ +AddablePlaceInfo in places::api::history - Rust
pub struct AddablePlaceInfo {
+    pub url: Url,
+    pub title: Option<String>,
+    pub visits: Vec<AddableVisit>,
+}

Fields§

§url: Url§title: Option<String>§visits: Vec<AddableVisit>

Trait Implementations§

source§

impl Debug for AddablePlaceInfo

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/api/history/struct.AddableVisit.html b/book/rust-docs/places/api/history/struct.AddableVisit.html new file mode 100644 index 0000000000..0295f65f5b --- /dev/null +++ b/book/rust-docs/places/api/history/struct.AddableVisit.html @@ -0,0 +1,17 @@ +AddableVisit in places::api::history - Rust
pub struct AddableVisit {
+    pub date: Timestamp,
+    pub transition: VisitType,
+    pub referrer: Option<Url>,
+    pub is_local: bool,
+}

Fields§

§date: Timestamp§transition: VisitType§referrer: Option<Url>§is_local: bool

Trait Implementations§

source§

impl Debug for AddableVisit

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/api/index.html b/book/rust-docs/places/api/index.html new file mode 100644 index 0000000000..46d55e4cce --- /dev/null +++ b/book/rust-docs/places/api/index.html @@ -0,0 +1 @@ +places::api - Rust
\ No newline at end of file diff --git a/book/rust-docs/places/api/matcher/fn.accept_result.html b/book/rust-docs/places/api/matcher/fn.accept_result.html new file mode 100644 index 0000000000..776b956327 --- /dev/null +++ b/book/rust-docs/places/api/matcher/fn.accept_result.html @@ -0,0 +1,7 @@ +accept_result in places::api::matcher - Rust

Function places::api::matcher::accept_result

source ·
pub fn accept_result(
+    conn: &PlacesDb,
+    search_string: &str,
+    url: &Url
+) -> Result<()>
Expand description

Records an accepted autocomplete match, recording the query string, +and chosen URL for subsequent matches.

+
\ No newline at end of file diff --git a/book/rust-docs/places/api/matcher/fn.match_url.html b/book/rust-docs/places/api/matcher/fn.match_url.html new file mode 100644 index 0000000000..d777702632 --- /dev/null +++ b/book/rust-docs/places/api/matcher/fn.match_url.html @@ -0,0 +1 @@ +match_url in places::api::matcher - Rust

Function places::api::matcher::match_url

source ·
pub fn match_url(conn: &PlacesDb, query: impl AsRef<str>) -> Result<Option<Url>>
\ No newline at end of file diff --git a/book/rust-docs/places/api/matcher/fn.search_frecent.html b/book/rust-docs/places/api/matcher/fn.search_frecent.html new file mode 100644 index 0000000000..a02d90b7ab --- /dev/null +++ b/book/rust-docs/places/api/matcher/fn.search_frecent.html @@ -0,0 +1,10 @@ +search_frecent in places::api::matcher - Rust

Function places::api::matcher::search_frecent

source ·
pub fn search_frecent(
+    conn: &PlacesDb,
+    params: SearchParams
+) -> Result<Vec<SearchResult>>
Expand description

Synchronously queries all providers for autocomplete matches, then filters +the matches. This isn’t cancelable yet; once a search is started, it can’t +be interrupted, even if the user moves on (see +https://github.com/mozilla/application-services/issues/265).

+

A provider can be anything that returns URL suggestions: Places history +and bookmarks, synced tabs, search engine suggestions, and search keywords.

+
\ No newline at end of file diff --git a/book/rust-docs/places/api/matcher/fn.split_after_host_and_port.html b/book/rust-docs/places/api/matcher/fn.split_after_host_and_port.html new file mode 100644 index 0000000000..307e1e7cb1 --- /dev/null +++ b/book/rust-docs/places/api/matcher/fn.split_after_host_and_port.html @@ -0,0 +1 @@ +split_after_host_and_port in places::api::matcher - Rust
pub fn split_after_host_and_port(href: &str) -> (&str, &str)
\ No newline at end of file diff --git a/book/rust-docs/places/api/matcher/fn.split_after_prefix.html b/book/rust-docs/places/api/matcher/fn.split_after_prefix.html new file mode 100644 index 0000000000..b5e3b6725b --- /dev/null +++ b/book/rust-docs/places/api/matcher/fn.split_after_prefix.html @@ -0,0 +1 @@ +split_after_prefix in places::api::matcher - Rust
pub fn split_after_prefix(href: &str) -> (&str, &str)
\ No newline at end of file diff --git a/book/rust-docs/places/api/matcher/index.html b/book/rust-docs/places/api/matcher/index.html new file mode 100644 index 0000000000..74409b3b70 --- /dev/null +++ b/book/rust-docs/places/api/matcher/index.html @@ -0,0 +1,5 @@ +places::api::matcher - Rust

Module places::api::matcher

source ·

Re-exports

Structs

Functions

  • Records an accepted autocomplete match, recording the query string, +and chosen URL for subsequent matches.
  • Synchronously queries all providers for autocomplete matches, then filters +the matches. This isn’t cancelable yet; once a search is started, it can’t +be interrupted, even if the user moves on (see +https://github.com/mozilla/application-services/issues/265).
\ No newline at end of file diff --git a/book/rust-docs/places/api/matcher/sidebar-items.js b/book/rust-docs/places/api/matcher/sidebar-items.js new file mode 100644 index 0000000000..e3b79f2baf --- /dev/null +++ b/book/rust-docs/places/api/matcher/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["accept_result","match_url","search_frecent","split_after_host_and_port","split_after_prefix"],"struct":["SearchParams","SearchResult"]}; \ No newline at end of file diff --git a/book/rust-docs/places/api/matcher/struct.SearchParams.html b/book/rust-docs/places/api/matcher/struct.SearchParams.html new file mode 100644 index 0000000000..d9102169f4 --- /dev/null +++ b/book/rust-docs/places/api/matcher/struct.SearchParams.html @@ -0,0 +1,16 @@ +SearchParams in places::api::matcher - Rust
pub struct SearchParams {
+    pub search_string: String,
+    pub limit: u32,
+}

Fields§

§search_string: String§limit: u32

Trait Implementations§

source§

impl Clone for SearchParams

source§

fn clone(&self) -> SearchParams

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for SearchParams

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/api/matcher/struct.SearchResult.html b/book/rust-docs/places/api/matcher/struct.SearchResult.html new file mode 100644 index 0000000000..0125b5c6bd --- /dev/null +++ b/book/rust-docs/places/api/matcher/struct.SearchResult.html @@ -0,0 +1,31 @@ +SearchResult in places::api::matcher - Rust
pub struct SearchResult {
+    pub search_string: String,
+    pub url: Url,
+    pub title: String,
+    pub icon_url: Option<Url>,
+    pub frecency: i64,
+}

Fields§

§search_string: String

The search string for this match.

+
§url: Url

The URL to open when the user confirms a match. This is +equivalent to nsIAutoCompleteResult.getFinalCompleteValueAt.

+
§title: String

The title of the autocompleted value, to show in the UI. This can be the +title of the bookmark or page, origin, URL, or URL fragment.

+
§icon_url: Option<Url>

The favicon URL.

+
§frecency: i64

A frecency score for this match.

+

Implementations§

source§

impl SearchResult

source

pub fn from_adaptive_row(row: &Row<'_>) -> Result<Self>

Default search behaviors from Desktop: HISTORY, BOOKMARK, OPENPAGE, SEARCHES. +Default match behavior: MATCH_BOUNDARY_ANYWHERE.

+
source

pub fn from_suggestion_row(row: &Row<'_>) -> Result<Self>

source

pub fn from_origin_row(row: &Row<'_>) -> Result<Self>

source

pub fn from_url_row(row: &Row<'_>) -> Result<Self>

Trait Implementations§

source§

impl Clone for SearchResult

source§

fn clone(&self) -> SearchResult

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for SearchResult

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl From<SearchResult> for FfiSearchResult

source§

fn from(res: SearchResult) -> Self

Converts to this type from the input type.
source§

impl PartialEq<SearchResult> for SearchResult

source§

fn eq(&self, other: &SearchResult) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Serialize for SearchResult

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more
source§

impl Eq for SearchResult

source§

impl StructuralEq for SearchResult

source§

impl StructuralPartialEq for SearchResult

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/api/places_api/constant.GLOBAL_STATE_META_KEY.html b/book/rust-docs/places/api/places_api/constant.GLOBAL_STATE_META_KEY.html new file mode 100644 index 0000000000..e200f48291 --- /dev/null +++ b/book/rust-docs/places/api/places_api/constant.GLOBAL_STATE_META_KEY.html @@ -0,0 +1 @@ +GLOBAL_STATE_META_KEY in places::api::places_api - Rust
pub const GLOBAL_STATE_META_KEY: &str = "global_sync_state_v2";
\ No newline at end of file diff --git a/book/rust-docs/places/api/places_api/enum.ConnectionType.html b/book/rust-docs/places/api/places_api/enum.ConnectionType.html new file mode 100644 index 0000000000..85810c32d1 --- /dev/null +++ b/book/rust-docs/places/api/places_api/enum.ConnectionType.html @@ -0,0 +1,19 @@ +ConnectionType in places::api::places_api - Rust
#[repr(u8)]
pub enum ConnectionType { + ReadOnly, + ReadWrite, + Sync, +}

Variants§

§

ReadOnly

§

ReadWrite

§

Sync

Implementations§

Trait Implementations§

source§

impl Clone for ConnectionType

source§

fn clone(&self) -> ConnectionType

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for ConnectionType

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl PartialEq<ConnectionType> for ConnectionType

source§

fn eq(&self, other: &ConnectionType) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Copy for ConnectionType

source§

impl Eq for ConnectionType

source§

impl StructuralEq for ConnectionType

source§

impl StructuralPartialEq for ConnectionType

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/api/places_api/fn.get_registered_sync_engine.html b/book/rust-docs/places/api/places_api/fn.get_registered_sync_engine.html new file mode 100644 index 0000000000..f9517e5792 --- /dev/null +++ b/book/rust-docs/places/api/places_api/fn.get_registered_sync_engine.html @@ -0,0 +1,3 @@ +get_registered_sync_engine in places::api::places_api - Rust
pub fn get_registered_sync_engine(
+    engine_id: &SyncEngineId
+) -> Option<Box<dyn SyncEngine>>
\ No newline at end of file diff --git a/book/rust-docs/places/api/places_api/fn.places_api_new.html b/book/rust-docs/places/api/places_api/fn.places_api_new.html new file mode 100644 index 0000000000..71781ff869 --- /dev/null +++ b/book/rust-docs/places/api/places_api/fn.places_api_new.html @@ -0,0 +1,4 @@ +places_api_new in places::api::places_api - Rust
pub fn places_api_new(db_name: impl AsRef<Path>) -> ApiResult<Arc<PlacesApi>>
Expand description

For uniffi we need to expose our Arc returning constructor as a global function :( +https://github.com/mozilla/uniffi-rs/pull/1063 would fix this, but got some pushback +meaning we are forced into this unfortunate workaround.

+
\ No newline at end of file diff --git a/book/rust-docs/places/api/places_api/index.html b/book/rust-docs/places/api/places_api/index.html new file mode 100644 index 0000000000..c7ad629049 --- /dev/null +++ b/book/rust-docs/places/api/places_api/index.html @@ -0,0 +1,5 @@ +places::api::places_api - Rust

Module places::api::places_api

source ·

Structs

  • The entry-point to the places API. This object gives access to database +connections and other helpers. It enforces that only 1 write connection +can exist to the database at once.

Enums

Constants

Functions

  • For uniffi we need to expose our Arc returning constructor as a global function :( +https://github.com/mozilla/uniffi-rs/pull/1063 would fix this, but got some pushback +meaning we are forced into this unfortunate workaround.
\ No newline at end of file diff --git a/book/rust-docs/places/api/places_api/sidebar-items.js b/book/rust-docs/places/api/places_api/sidebar-items.js new file mode 100644 index 0000000000..1340ed1396 --- /dev/null +++ b/book/rust-docs/places/api/places_api/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"constant":["GLOBAL_STATE_META_KEY"],"enum":["ConnectionType"],"fn":["get_registered_sync_engine","places_api_new"],"struct":["PlacesApi","SyncState"]}; \ No newline at end of file diff --git a/book/rust-docs/places/api/places_api/struct.PlacesApi.html b/book/rust-docs/places/api/places_api/struct.PlacesApi.html new file mode 100644 index 0000000000..b44d058b0e --- /dev/null +++ b/book/rust-docs/places/api/places_api/struct.PlacesApi.html @@ -0,0 +1,54 @@ +PlacesApi in places::api::places_api - Rust
pub struct PlacesApi { /* private fields */ }
Expand description

The entry-point to the places API. This object gives access to database +connections and other helpers. It enforces that only 1 write connection +can exist to the database at once.

+

Implementations§

source§

impl PlacesApi

source

pub fn new(db_name: impl AsRef<Path>) -> Result<Arc<Self>>

Create a new, or fetch an already open, PlacesApi backed by a file on disk.

+
source

pub fn new_memory(db_name: &str) -> Result<Arc<Self>>

Create a new, or fetch an already open, memory-based PlacesApi. You must +provide a name, but you are still able to have a single writer and many +reader connections to the same memory DB open.

+
source

pub fn open_connection(&self, conn_type: ConnectionType) -> Result<PlacesDb>

Open a connection to the database.

+
source

pub fn get_sync_connection(&self) -> Result<Arc<SharedPlacesDb>>

source

pub fn close_connection(&self, connection: PlacesDb) -> Result<()>

Close a connection to the database. If the connection is the write +connection, you can re-fetch it using open_connection.

+
source

pub fn register_with_sync_manager(self: Arc<Self>)

source

pub fn sync_history( + &self, + client_init: &Sync15StorageClientInit, + key_bundle: &KeyBundle +) -> Result<SyncTelemetryPing>

source

pub fn sync_bookmarks( + &self, + client_init: &Sync15StorageClientInit, + key_bundle: &KeyBundle +) -> Result<SyncTelemetryPing>

source

pub fn do_sync_one<F>( + &self, + name: &'static str, + syncer: F +) -> Result<SyncTelemetryPing>where + F: FnOnce(Arc<SharedPlacesDb>, &mut MemoryCachedState, &mut Option<String>) -> Result<SyncResult>,

source

pub fn sync( + &self, + client_init: &Sync15StorageClientInit, + key_bundle: &KeyBundle +) -> Result<SyncResult>

source

pub fn wipe_bookmarks(&self) -> Result<()>

source

pub fn reset_bookmarks(&self) -> Result<()>

source

pub fn reset_history(&self) -> ApiResult<()>

source§

impl PlacesApi

source

pub fn new_connection( + &self, + conn_type: ConnectionType +) -> ApiResult<Arc<PlacesConnection>>

source

pub fn history_sync( + &self, + key_id: String, + access_token: String, + sync_key: String, + tokenserver_url: Url +) -> ApiResult<String>

source

pub fn bookmarks_sync( + &self, + key_id: String, + access_token: String, + sync_key: String, + tokenserver_url: Url +) -> ApiResult<String>

source

pub fn bookmarks_reset(&self) -> ApiResult<()>

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/api/places_api/struct.SyncState.html b/book/rust-docs/places/api/places_api/struct.SyncState.html new file mode 100644 index 0000000000..f4e2f8e23b --- /dev/null +++ b/book/rust-docs/places/api/places_api/struct.SyncState.html @@ -0,0 +1,15 @@ +SyncState in places::api::places_api - Rust
pub struct SyncState {
+    pub mem_cached_state: Cell<MemoryCachedState>,
+    pub disk_cached_state: Cell<Option<String>>,
+}

Fields§

§mem_cached_state: Cell<MemoryCachedState>§disk_cached_state: Cell<Option<String>>

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/api/sidebar-items.js b/book/rust-docs/places/api/sidebar-items.js new file mode 100644 index 0000000000..46d1d7f4d1 --- /dev/null +++ b/book/rust-docs/places/api/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["apply_observation"],"mod":["history","matcher","places_api"]}; \ No newline at end of file diff --git a/book/rust-docs/places/bookmark_sync/engine/constant.COLLECTION_NAME.html b/book/rust-docs/places/bookmark_sync/engine/constant.COLLECTION_NAME.html new file mode 100644 index 0000000000..85324d6534 --- /dev/null +++ b/book/rust-docs/places/bookmark_sync/engine/constant.COLLECTION_NAME.html @@ -0,0 +1 @@ +COLLECTION_NAME in places::bookmark_sync::engine - Rust
pub const COLLECTION_NAME: &str = "bookmarks";
\ No newline at end of file diff --git a/book/rust-docs/places/bookmark_sync/engine/constant.COLLECTION_SYNCID_META_KEY.html b/book/rust-docs/places/bookmark_sync/engine/constant.COLLECTION_SYNCID_META_KEY.html new file mode 100644 index 0000000000..00d649bee6 --- /dev/null +++ b/book/rust-docs/places/bookmark_sync/engine/constant.COLLECTION_SYNCID_META_KEY.html @@ -0,0 +1 @@ +COLLECTION_SYNCID_META_KEY in places::bookmark_sync::engine - Rust
pub const COLLECTION_SYNCID_META_KEY: &str = "bookmarks_sync_id";
\ No newline at end of file diff --git a/book/rust-docs/places/bookmark_sync/engine/constant.GLOBAL_SYNCID_META_KEY.html b/book/rust-docs/places/bookmark_sync/engine/constant.GLOBAL_SYNCID_META_KEY.html new file mode 100644 index 0000000000..762370c22f --- /dev/null +++ b/book/rust-docs/places/bookmark_sync/engine/constant.GLOBAL_SYNCID_META_KEY.html @@ -0,0 +1 @@ +GLOBAL_SYNCID_META_KEY in places::bookmark_sync::engine - Rust
pub const GLOBAL_SYNCID_META_KEY: &str = "bookmarks_global_sync_id";
\ No newline at end of file diff --git a/book/rust-docs/places/bookmark_sync/engine/constant.LAST_SYNC_META_KEY.html b/book/rust-docs/places/bookmark_sync/engine/constant.LAST_SYNC_META_KEY.html new file mode 100644 index 0000000000..0972a351c8 --- /dev/null +++ b/book/rust-docs/places/bookmark_sync/engine/constant.LAST_SYNC_META_KEY.html @@ -0,0 +1 @@ +LAST_SYNC_META_KEY in places::bookmark_sync::engine - Rust
pub const LAST_SYNC_META_KEY: &str = "bookmarks_last_sync_time";
\ No newline at end of file diff --git a/book/rust-docs/places/bookmark_sync/engine/index.html b/book/rust-docs/places/bookmark_sync/engine/index.html new file mode 100644 index 0000000000..a0c78cdfcc --- /dev/null +++ b/book/rust-docs/places/bookmark_sync/engine/index.html @@ -0,0 +1 @@ +places::bookmark_sync::engine - Rust
\ No newline at end of file diff --git a/book/rust-docs/places/bookmark_sync/engine/sidebar-items.js b/book/rust-docs/places/bookmark_sync/engine/sidebar-items.js new file mode 100644 index 0000000000..30938c1a08 --- /dev/null +++ b/book/rust-docs/places/bookmark_sync/engine/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"constant":["COLLECTION_NAME","COLLECTION_SYNCID_META_KEY","GLOBAL_SYNCID_META_KEY","LAST_SYNC_META_KEY"],"struct":["BookmarksSyncEngine"]}; \ No newline at end of file diff --git a/book/rust-docs/places/bookmark_sync/engine/struct.BookmarksSyncEngine.html b/book/rust-docs/places/bookmark_sync/engine/struct.BookmarksSyncEngine.html new file mode 100644 index 0000000000..07f1bbd835 --- /dev/null +++ b/book/rust-docs/places/bookmark_sync/engine/struct.BookmarksSyncEngine.html @@ -0,0 +1,61 @@ +BookmarksSyncEngine in places::bookmark_sync::engine - Rust
pub struct BookmarksSyncEngine { /* private fields */ }

Implementations§

Trait Implementations§

source§

impl SyncEngine for BookmarksSyncEngine

source§

fn wipe(&self) -> Result<()>

Erases all local items. Unlike reset, this keeps all synced items +until the next sync, when they will be replaced with tombstones. This +also preserves the sync ID and last sync time.

+

Conceptually, the next sync will merge an empty local tree, and a full +remote tree.

+
source§

fn collection_name(&self) -> CollectionName

source§

fn stage_incoming( + &self, + inbound: Vec<IncomingBso>, + telem: &mut Engine +) -> Result<()>

Stage some incoming records. This might be called multiple times in the same sync +if we fetch the incoming records in batches. Read more
source§

fn apply( + &self, + timestamp: ServerTimestamp, + telem: &mut Engine +) -> Result<Vec<OutgoingBso>>

Apply the staged records, returning outgoing records. +Ideally we would adjust this model to better support batching of outgoing records +without needing to keep them all in memory (ie, an iterator or similar?)
source§

fn set_uploaded( + &self, + new_timestamp: ServerTimestamp, + ids: Vec<SyncGuid> +) -> Result<()>

Indicates that the given record IDs were uploaded successfully to the server. +This may be called multiple times per sync, once for each batch. Batching is determined +dynamically based on payload sizes and counts via the server’s advertised limits.
source§

fn sync_finished(&self) -> Result<()>

Called once the sync is finished. Not currently called if uploads fail (which +seems sad, but the other batching confusion there needs sorting out first). +Many engines will have nothing to do here, as most “post upload” work should be +done in set_uploaded()
source§

fn get_collection_request( + &self, + server_timestamp: ServerTimestamp +) -> Result<Option<CollectionRequest>>

The engine is responsible for building a single collection request. Engines +typically will store a lastModified timestamp and use that to build a +request saying “give me full records since that date” - however, other +engines might do something fancier. It can return None if the server timestamp +has not advanced since the last sync. +This could even later be extended to handle “backfills”, and we might end up +wanting one engine to use multiple collections (eg, as a “foreign key” via guid), etc.
source§

fn get_sync_assoc(&self) -> Result<EngineSyncAssociation>

Get persisted sync IDs. If they don’t match the global state we’ll be +reset() with the new IDs.
source§

fn reset(&self, assoc: &EngineSyncAssociation) -> Result<()>

Reset the engine (and associated store) without wiping local data, +ready for a “first sync”. +assoc defines how this store is to be associated with sync.
§

fn prepare_for_sync( + &self, + _get_client_data: &dyn Fn() -> ClientData +) -> Result<(), Error>

Prepares the engine for syncing. The tabs engine currently uses this to +store the current list of clients, which it uses to look up device names +and types. Read more
§

fn set_local_encryption_key(&mut self, _key: &str) -> Result<(), Error>

Tells the engine what the local encryption key is for the data managed +by the engine. This is only used by collections that store data +encrypted locally and is unrelated to the encryption used by Sync. +The intent is that for such collections, this key can be used to +decrypt local data before it is re-encrypted by Sync and sent to the +storage servers, and similarly, data from the storage servers will be +decrypted by Sync, then encrypted by the local encryption key before +being added to the local database. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/bookmark_sync/enum.SyncedBookmarkKind.html b/book/rust-docs/places/bookmark_sync/enum.SyncedBookmarkKind.html new file mode 100644 index 0000000000..59f5c8265e --- /dev/null +++ b/book/rust-docs/places/bookmark_sync/enum.SyncedBookmarkKind.html @@ -0,0 +1,33 @@ +SyncedBookmarkKind in places::bookmark_sync - Rust
#[repr(u8)]
pub enum SyncedBookmarkKind { + Bookmark, + Query, + Folder, + Livemark, + Separator, +}
Expand description

Synced item kinds. These are stored in moz_bookmarks_synced.kind and match +the definitions in mozISyncedBookmarksMerger.

+

Variants§

§

Bookmark

§

Query

§

Folder

§

Livemark

§

Separator

Implementations§

Trait Implementations§

source§

impl Clone for SyncedBookmarkKind

source§

fn clone(&self) -> SyncedBookmarkKind

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for SyncedBookmarkKind

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl From<Kind> for SyncedBookmarkKind

source§

fn from(kind: Kind) -> SyncedBookmarkKind

Converts to this type from the input type.
source§

impl From<SyncedBookmarkKind> for Kind

source§

fn from(kind: SyncedBookmarkKind) -> Kind

Converts to this type from the input type.
source§

impl Hash for SyncedBookmarkKind

source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · source§

fn hash_slice<H>(data: &[Self], state: &mut H)where + H: Hasher, + Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
source§

impl Ord for SyncedBookmarkKind

source§

fn cmp(&self, other: &SyncedBookmarkKind) -> Ordering

This method returns an Ordering between self and other. Read more
1.21.0 · source§

fn max(self, other: Self) -> Selfwhere + Self: Sized,

Compares and returns the maximum of two values. Read more
1.21.0 · source§

fn min(self, other: Self) -> Selfwhere + Self: Sized,

Compares and returns the minimum of two values. Read more
1.50.0 · source§

fn clamp(self, min: Self, max: Self) -> Selfwhere + Self: Sized + PartialOrd<Self>,

Restrict a value to a certain interval. Read more
source§

impl PartialEq<SyncedBookmarkKind> for SyncedBookmarkKind

source§

fn eq(&self, other: &SyncedBookmarkKind) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl PartialOrd<SyncedBookmarkKind> for SyncedBookmarkKind

source§

fn partial_cmp(&self, other: &SyncedBookmarkKind) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · source§

fn lt(&self, other: &Rhs) -> bool

This method tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · source§

fn le(&self, other: &Rhs) -> bool

This method tests less than or equal to (for self and other) and is used by the <= +operator. Read more
1.0.0 · source§

fn gt(&self, other: &Rhs) -> bool

This method tests greater than (for self and other) and is used by the > operator. Read more
1.0.0 · source§

fn ge(&self, other: &Rhs) -> bool

This method tests greater than or equal to (for self and other) and is used by the >= +operator. Read more
source§

impl ToSql for SyncedBookmarkKind

source§

fn to_sql(&self) -> RusqliteResult<ToSqlOutput<'_>>

Converts Rust value to SQLite value
source§

impl Copy for SyncedBookmarkKind

source§

impl Eq for SyncedBookmarkKind

source§

impl StructuralEq for SyncedBookmarkKind

source§

impl StructuralPartialEq for SyncedBookmarkKind

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> CallHasher for Twhere + T: Hash + ?Sized,

§

fn get_hash<H, B>(value: &H, build_hasher: &B) -> u64where + H: Hash + ?Sized, + B: BuildHasher,

source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/bookmark_sync/enum.SyncedBookmarkValidity.html b/book/rust-docs/places/bookmark_sync/enum.SyncedBookmarkValidity.html new file mode 100644 index 0000000000..49e5e685b0 --- /dev/null +++ b/book/rust-docs/places/bookmark_sync/enum.SyncedBookmarkValidity.html @@ -0,0 +1,40 @@ +SyncedBookmarkValidity in places::bookmark_sync - Rust
#[repr(u8)]
pub enum SyncedBookmarkValidity { + Valid, + Reupload, + Replace, +}
Expand description

Synced item validity states. These are stored in +moz_bookmarks_synced.validity, and match the definitions in +mozISyncedBookmarksMerger. In short:

+
    +
  • Valid means the record is valid and should be merged as usual.
  • +
  • Reupload means a remote item can be fixed up and applied, +and should be reuploaded.
  • +
  • Replace means a remote item isn’t valid at all, and should either be +replaced with a valid local copy, or deleted if a valid local copy +doesn’t exist.
  • +
+

Variants§

§

Valid

§

Reupload

§

Replace

Implementations§

Trait Implementations§

source§

impl Clone for SyncedBookmarkValidity

source§

fn clone(&self) -> SyncedBookmarkValidity

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for SyncedBookmarkValidity

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl From<SyncedBookmarkValidity> for Validity

source§

fn from(validity: SyncedBookmarkValidity) -> Validity

Converts to this type from the input type.
source§

impl Hash for SyncedBookmarkValidity

source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · source§

fn hash_slice<H>(data: &[Self], state: &mut H)where + H: Hasher, + Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
source§

impl Ord for SyncedBookmarkValidity

source§

fn cmp(&self, other: &SyncedBookmarkValidity) -> Ordering

This method returns an Ordering between self and other. Read more
1.21.0 · source§

fn max(self, other: Self) -> Selfwhere + Self: Sized,

Compares and returns the maximum of two values. Read more
1.21.0 · source§

fn min(self, other: Self) -> Selfwhere + Self: Sized,

Compares and returns the minimum of two values. Read more
1.50.0 · source§

fn clamp(self, min: Self, max: Self) -> Selfwhere + Self: Sized + PartialOrd<Self>,

Restrict a value to a certain interval. Read more
source§

impl PartialEq<SyncedBookmarkValidity> for SyncedBookmarkValidity

source§

fn eq(&self, other: &SyncedBookmarkValidity) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl PartialOrd<SyncedBookmarkValidity> for SyncedBookmarkValidity

source§

fn partial_cmp(&self, other: &SyncedBookmarkValidity) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · source§

fn lt(&self, other: &Rhs) -> bool

This method tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · source§

fn le(&self, other: &Rhs) -> bool

This method tests less than or equal to (for self and other) and is used by the <= +operator. Read more
1.0.0 · source§

fn gt(&self, other: &Rhs) -> bool

This method tests greater than (for self and other) and is used by the > operator. Read more
1.0.0 · source§

fn ge(&self, other: &Rhs) -> bool

This method tests greater than or equal to (for self and other) and is used by the >= +operator. Read more
source§

impl ToSql for SyncedBookmarkValidity

source§

fn to_sql(&self) -> RusqliteResult<ToSqlOutput<'_>>

Converts Rust value to SQLite value
source§

impl Copy for SyncedBookmarkValidity

source§

impl Eq for SyncedBookmarkValidity

source§

impl StructuralEq for SyncedBookmarkValidity

source§

impl StructuralPartialEq for SyncedBookmarkValidity

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> CallHasher for Twhere + T: Hash + ?Sized,

§

fn get_hash<H, B>(value: &H, build_hasher: &B) -> u64where + H: Hash + ?Sized, + B: BuildHasher,

source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/bookmark_sync/index.html b/book/rust-docs/places/bookmark_sync/index.html new file mode 100644 index 0000000000..15f640d555 --- /dev/null +++ b/book/rust-docs/places/bookmark_sync/index.html @@ -0,0 +1,4 @@ +places::bookmark_sync - Rust

Module places::bookmark_sync

source ·

Re-exports

Modules

Enums

  • Synced item kinds. These are stored in moz_bookmarks_synced.kind and match +the definitions in mozISyncedBookmarksMerger.
  • Synced item validity states. These are stored in +moz_bookmarks_synced.validity, and match the definitions in +mozISyncedBookmarksMerger. In short:
\ No newline at end of file diff --git a/book/rust-docs/places/bookmark_sync/record/enum.BookmarkItemRecord.html b/book/rust-docs/places/bookmark_sync/record/enum.BookmarkItemRecord.html new file mode 100644 index 0000000000..b61d6d84be --- /dev/null +++ b/book/rust-docs/places/bookmark_sync/record/enum.BookmarkItemRecord.html @@ -0,0 +1,24 @@ +BookmarkItemRecord in places::bookmark_sync::record - Rust
pub enum BookmarkItemRecord {
+    Bookmark(BookmarkRecord),
+    Query(QueryRecord),
+    Folder(FolderRecord),
+    Livemark(LivemarkRecord),
+    Separator(SeparatorRecord),
+}

Variants§

Implementations§

Trait Implementations§

source§

impl Clone for BookmarkItemRecord

source§

fn clone(&self) -> BookmarkItemRecord

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for BookmarkItemRecord

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl<'de> Deserialize<'de> for BookmarkItemRecord

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl From<BookmarkRecord> for BookmarkItemRecord

source§

fn from(b: BookmarkRecord) -> BookmarkItemRecord

Converts to this type from the input type.
source§

impl From<FolderRecord> for BookmarkItemRecord

source§

fn from(f: FolderRecord) -> BookmarkItemRecord

Converts to this type from the input type.
source§

impl From<LivemarkRecord> for BookmarkItemRecord

source§

fn from(l: LivemarkRecord) -> BookmarkItemRecord

Converts to this type from the input type.
source§

impl From<QueryRecord> for BookmarkItemRecord

source§

fn from(q: QueryRecord) -> BookmarkItemRecord

Converts to this type from the input type.
source§

impl From<SeparatorRecord> for BookmarkItemRecord

source§

fn from(s: SeparatorRecord) -> BookmarkItemRecord

Converts to this type from the input type.
source§

impl PartialEq<BookmarkItemRecord> for BookmarkItemRecord

source§

fn eq(&self, other: &BookmarkItemRecord) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Serialize for BookmarkItemRecord

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more
source§

impl Eq for BookmarkItemRecord

source§

impl StructuralEq for BookmarkItemRecord

source§

impl StructuralPartialEq for BookmarkItemRecord

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/places/bookmark_sync/record/index.html b/book/rust-docs/places/bookmark_sync/record/index.html new file mode 100644 index 0000000000..6a25da34ec --- /dev/null +++ b/book/rust-docs/places/bookmark_sync/record/index.html @@ -0,0 +1,2 @@ +places::bookmark_sync::record - Rust

Structs

Enums

\ No newline at end of file diff --git a/book/rust-docs/places/bookmark_sync/record/sidebar-items.js b/book/rust-docs/places/bookmark_sync/record/sidebar-items.js new file mode 100644 index 0000000000..d6471873ba --- /dev/null +++ b/book/rust-docs/places/bookmark_sync/record/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["BookmarkItemRecord"],"struct":["BookmarkRecord","BookmarkRecordId","FolderRecord","LivemarkRecord","QueryRecord","SeparatorRecord"]}; \ No newline at end of file diff --git a/book/rust-docs/places/bookmark_sync/record/struct.BookmarkRecord.html b/book/rust-docs/places/bookmark_sync/record/struct.BookmarkRecord.html new file mode 100644 index 0000000000..8f1244a500 --- /dev/null +++ b/book/rust-docs/places/bookmark_sync/record/struct.BookmarkRecord.html @@ -0,0 +1,29 @@ +BookmarkRecord in places::bookmark_sync::record - Rust
pub struct BookmarkRecord {
+    pub record_id: BookmarkRecordId,
+    pub parent_record_id: Option<BookmarkRecordId>,
+    pub parent_title: Option<String>,
+    pub date_added: Option<i64>,
+    pub has_dupe: bool,
+    pub title: Option<String>,
+    pub url: Option<String>,
+    pub keyword: Option<String>,
+    pub tags: Vec<String>,
+    pub unknown_fields: UnknownFields,
+}

Fields§

§record_id: BookmarkRecordId§parent_record_id: Option<BookmarkRecordId>§parent_title: Option<String>§date_added: Option<i64>§has_dupe: bool§title: Option<String>§url: Option<String>§keyword: Option<String>§tags: Vec<String>§unknown_fields: UnknownFields

Trait Implementations§

source§

impl Clone for BookmarkRecord

source§

fn clone(&self) -> BookmarkRecord

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for BookmarkRecord

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl<'de> Deserialize<'de> for BookmarkRecord

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl From<BookmarkRecord> for BookmarkItemRecord

source§

fn from(b: BookmarkRecord) -> BookmarkItemRecord

Converts to this type from the input type.
source§

impl PartialEq<BookmarkRecord> for BookmarkRecord

source§

fn eq(&self, other: &BookmarkRecord) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Serialize for BookmarkRecord

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more
source§

impl Eq for BookmarkRecord

source§

impl StructuralEq for BookmarkRecord

source§

impl StructuralPartialEq for BookmarkRecord

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/places/bookmark_sync/record/struct.BookmarkRecordId.html b/book/rust-docs/places/bookmark_sync/record/struct.BookmarkRecordId.html new file mode 100644 index 0000000000..42a1edf038 --- /dev/null +++ b/book/rust-docs/places/bookmark_sync/record/struct.BookmarkRecordId.html @@ -0,0 +1,41 @@ +BookmarkRecordId in places::bookmark_sync::record - Rust
pub struct BookmarkRecordId(_);
Expand description

A bookmark record ID. Bookmark record IDs are the same as Places GUIDs, +except for:

+
    +
  1. The Places root, which is “places”. Note that the Places root is not +synced, but is still referenced in the user content roots’ parentids.
  2. +
  3. The four user content roots, which omit trailing underscores.
  4. +
+

This wrapper helps avoid mix-ups like storing a record ID instead of a GUID, +or uploading a GUID instead of a record ID.

+

Internally, we convert record IDs to GUIDs when applying incoming records, +and only convert back to GUIDs during upload.

+

Implementations§

source§

impl BookmarkRecordId

source

pub fn from_payload_id(payload_id: SyncGuid) -> BookmarkRecordId

Creates a bookmark record ID from a Sync record payload ID.

+
source

pub fn as_payload_id(&self) -> &str

Returns a reference to the record payload ID. This is the borrowed +version of into_payload_id, and used for serialization.

+
source

pub fn into_payload_id(self) -> SyncGuid

Returns the record payload ID. This is the owned version of +as_payload_id, and exists to avoid copying strings when uploading +tombstones.

+
source

pub fn as_guid(&self) -> &SyncGuid

Returns a reference to the GUID for this record ID.

+

Trait Implementations§

source§

impl Clone for BookmarkRecordId

source§

fn clone(&self) -> BookmarkRecordId

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for BookmarkRecordId

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl<'de> Deserialize<'de> for BookmarkRecordId

source§

fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error>

Deserialize this value from the given Serde deserializer. Read more
source§

impl From<BookmarkRecordId> for SyncGuid

Converts a bookmark record ID into a Places GUID.

+
source§

fn from(record_id: BookmarkRecordId) -> SyncGuid

Converts to this type from the input type.
source§

impl From<Guid> for BookmarkRecordId

Converts a Places GUID into a bookmark record ID.

+
source§

fn from(guid: SyncGuid) -> BookmarkRecordId

Converts to this type from the input type.
source§

impl Hash for BookmarkRecordId

source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · source§

fn hash_slice<H>(data: &[Self], state: &mut H)where + H: Hasher, + Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
source§

impl PartialEq<BookmarkRecordId> for BookmarkRecordId

source§

fn eq(&self, other: &BookmarkRecordId) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Serialize for BookmarkRecordId

source§

fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error>

Serialize this value into the given Serde serializer. Read more
source§

impl Eq for BookmarkRecordId

source§

impl StructuralEq for BookmarkRecordId

source§

impl StructuralPartialEq for BookmarkRecordId

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> CallHasher for Twhere + T: Hash + ?Sized,

§

fn get_hash<H, B>(value: &H, build_hasher: &B) -> u64where + H: Hash + ?Sized, + B: BuildHasher,

source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/places/bookmark_sync/record/struct.FolderRecord.html b/book/rust-docs/places/bookmark_sync/record/struct.FolderRecord.html new file mode 100644 index 0000000000..3d58403954 --- /dev/null +++ b/book/rust-docs/places/bookmark_sync/record/struct.FolderRecord.html @@ -0,0 +1,27 @@ +FolderRecord in places::bookmark_sync::record - Rust
pub struct FolderRecord {
+    pub record_id: BookmarkRecordId,
+    pub parent_record_id: Option<BookmarkRecordId>,
+    pub parent_title: Option<String>,
+    pub date_added: Option<i64>,
+    pub has_dupe: bool,
+    pub title: Option<String>,
+    pub children: Vec<BookmarkRecordId>,
+    pub unknown_fields: UnknownFields,
+}

Fields§

§record_id: BookmarkRecordId§parent_record_id: Option<BookmarkRecordId>§parent_title: Option<String>§date_added: Option<i64>§has_dupe: bool§title: Option<String>§children: Vec<BookmarkRecordId>§unknown_fields: UnknownFields

Trait Implementations§

source§

impl Clone for FolderRecord

source§

fn clone(&self) -> FolderRecord

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for FolderRecord

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl<'de> Deserialize<'de> for FolderRecord

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl From<FolderRecord> for BookmarkItemRecord

source§

fn from(f: FolderRecord) -> BookmarkItemRecord

Converts to this type from the input type.
source§

impl PartialEq<FolderRecord> for FolderRecord

source§

fn eq(&self, other: &FolderRecord) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Serialize for FolderRecord

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more
source§

impl Eq for FolderRecord

source§

impl StructuralEq for FolderRecord

source§

impl StructuralPartialEq for FolderRecord

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/places/bookmark_sync/record/struct.LivemarkRecord.html b/book/rust-docs/places/bookmark_sync/record/struct.LivemarkRecord.html new file mode 100644 index 0000000000..3100969c1a --- /dev/null +++ b/book/rust-docs/places/bookmark_sync/record/struct.LivemarkRecord.html @@ -0,0 +1,28 @@ +LivemarkRecord in places::bookmark_sync::record - Rust
pub struct LivemarkRecord {
+    pub record_id: BookmarkRecordId,
+    pub parent_record_id: Option<BookmarkRecordId>,
+    pub parent_title: Option<String>,
+    pub date_added: Option<i64>,
+    pub has_dupe: bool,
+    pub title: Option<String>,
+    pub feed_url: Option<String>,
+    pub site_url: Option<String>,
+    pub unknown_fields: UnknownFields,
+}

Fields§

§record_id: BookmarkRecordId§parent_record_id: Option<BookmarkRecordId>§parent_title: Option<String>§date_added: Option<i64>§has_dupe: bool§title: Option<String>§feed_url: Option<String>§site_url: Option<String>§unknown_fields: UnknownFields

Trait Implementations§

source§

impl Clone for LivemarkRecord

source§

fn clone(&self) -> LivemarkRecord

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for LivemarkRecord

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl<'de> Deserialize<'de> for LivemarkRecord

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl From<LivemarkRecord> for BookmarkItemRecord

source§

fn from(l: LivemarkRecord) -> BookmarkItemRecord

Converts to this type from the input type.
source§

impl PartialEq<LivemarkRecord> for LivemarkRecord

source§

fn eq(&self, other: &LivemarkRecord) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Serialize for LivemarkRecord

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more
source§

impl Eq for LivemarkRecord

source§

impl StructuralEq for LivemarkRecord

source§

impl StructuralPartialEq for LivemarkRecord

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/places/bookmark_sync/record/struct.QueryRecord.html b/book/rust-docs/places/bookmark_sync/record/struct.QueryRecord.html new file mode 100644 index 0000000000..f670b7f76b --- /dev/null +++ b/book/rust-docs/places/bookmark_sync/record/struct.QueryRecord.html @@ -0,0 +1,28 @@ +QueryRecord in places::bookmark_sync::record - Rust
pub struct QueryRecord {
+    pub record_id: BookmarkRecordId,
+    pub parent_record_id: Option<BookmarkRecordId>,
+    pub parent_title: Option<String>,
+    pub date_added: Option<i64>,
+    pub has_dupe: bool,
+    pub title: Option<String>,
+    pub url: Option<String>,
+    pub tag_folder_name: Option<String>,
+    pub unknown_fields: UnknownFields,
+}

Fields§

§record_id: BookmarkRecordId§parent_record_id: Option<BookmarkRecordId>§parent_title: Option<String>§date_added: Option<i64>§has_dupe: bool§title: Option<String>§url: Option<String>§tag_folder_name: Option<String>§unknown_fields: UnknownFields

Trait Implementations§

source§

impl Clone for QueryRecord

source§

fn clone(&self) -> QueryRecord

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for QueryRecord

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl<'de> Deserialize<'de> for QueryRecord

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl From<QueryRecord> for BookmarkItemRecord

source§

fn from(q: QueryRecord) -> BookmarkItemRecord

Converts to this type from the input type.
source§

impl PartialEq<QueryRecord> for QueryRecord

source§

fn eq(&self, other: &QueryRecord) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Serialize for QueryRecord

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more
source§

impl Eq for QueryRecord

source§

impl StructuralEq for QueryRecord

source§

impl StructuralPartialEq for QueryRecord

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/places/bookmark_sync/record/struct.SeparatorRecord.html b/book/rust-docs/places/bookmark_sync/record/struct.SeparatorRecord.html new file mode 100644 index 0000000000..0f5a89995b --- /dev/null +++ b/book/rust-docs/places/bookmark_sync/record/struct.SeparatorRecord.html @@ -0,0 +1,26 @@ +SeparatorRecord in places::bookmark_sync::record - Rust
pub struct SeparatorRecord {
+    pub record_id: BookmarkRecordId,
+    pub parent_record_id: Option<BookmarkRecordId>,
+    pub parent_title: Option<String>,
+    pub date_added: Option<i64>,
+    pub has_dupe: bool,
+    pub position: Option<i64>,
+    pub unknown_fields: UnknownFields,
+}

Fields§

§record_id: BookmarkRecordId§parent_record_id: Option<BookmarkRecordId>§parent_title: Option<String>§date_added: Option<i64>§has_dupe: bool§position: Option<i64>§unknown_fields: UnknownFields

Trait Implementations§

source§

impl Clone for SeparatorRecord

source§

fn clone(&self) -> SeparatorRecord

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for SeparatorRecord

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl<'de> Deserialize<'de> for SeparatorRecord

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl From<SeparatorRecord> for BookmarkItemRecord

source§

fn from(s: SeparatorRecord) -> BookmarkItemRecord

Converts to this type from the input type.
source§

impl PartialEq<SeparatorRecord> for SeparatorRecord

source§

fn eq(&self, other: &SeparatorRecord) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Serialize for SeparatorRecord

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more
source§

impl Eq for SeparatorRecord

source§

impl StructuralEq for SeparatorRecord

source§

impl StructuralPartialEq for SeparatorRecord

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/places/bookmark_sync/sidebar-items.js b/book/rust-docs/places/bookmark_sync/sidebar-items.js new file mode 100644 index 0000000000..a9870ec93a --- /dev/null +++ b/book/rust-docs/places/bookmark_sync/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["SyncedBookmarkKind","SyncedBookmarkValidity"],"mod":["engine","record"]}; \ No newline at end of file diff --git a/book/rust-docs/places/db/db/constant.MAX_VARIABLE_NUMBER.html b/book/rust-docs/places/db/db/constant.MAX_VARIABLE_NUMBER.html new file mode 100644 index 0000000000..cb72ed6595 --- /dev/null +++ b/book/rust-docs/places/db/db/constant.MAX_VARIABLE_NUMBER.html @@ -0,0 +1 @@ +MAX_VARIABLE_NUMBER in places::db::db - Rust

Constant places::db::db::MAX_VARIABLE_NUMBER

source ·
pub const MAX_VARIABLE_NUMBER: usize = 999;
\ No newline at end of file diff --git a/book/rust-docs/places/db/db/index.html b/book/rust-docs/places/db/db/index.html new file mode 100644 index 0000000000..b93ccd8303 --- /dev/null +++ b/book/rust-docs/places/db/db/index.html @@ -0,0 +1,2 @@ +places::db::db - Rust

Module places::db::db

source ·

Structs

Constants

\ No newline at end of file diff --git a/book/rust-docs/places/db/db/sidebar-items.js b/book/rust-docs/places/db/db/sidebar-items.js new file mode 100644 index 0000000000..8cb098a2ef --- /dev/null +++ b/book/rust-docs/places/db/db/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"constant":["MAX_VARIABLE_NUMBER"],"struct":["GLOBAL_BOOKMARK_CHANGE_COUNTERS","GlobalChangeCounterTracker","PlacesDb","PlacesInitializer","SharedPlacesDb"]}; \ No newline at end of file diff --git a/book/rust-docs/places/db/db/struct.GLOBAL_BOOKMARK_CHANGE_COUNTERS.html b/book/rust-docs/places/db/db/struct.GLOBAL_BOOKMARK_CHANGE_COUNTERS.html new file mode 100644 index 0000000000..aa696a75c5 --- /dev/null +++ b/book/rust-docs/places/db/db/struct.GLOBAL_BOOKMARK_CHANGE_COUNTERS.html @@ -0,0 +1,161 @@ +GLOBAL_BOOKMARK_CHANGE_COUNTERS in places::db::db - Rust
pub struct GLOBAL_BOOKMARK_CHANGE_COUNTERS { /* private fields */ }

Methods from Deref<Target = RwLock<HashMap<usize, AtomicI64>>>§

1.0.0 · source

pub fn read( + &self +) -> Result<RwLockReadGuard<'_, T>, PoisonError<RwLockReadGuard<'_, T>>>

Locks this RwLock with shared read access, blocking the current thread +until it can be acquired.

+

The calling thread will be blocked until there are no more writers which +hold the lock. There may be other readers currently inside the lock when +this method returns. This method does not provide any guarantees with +respect to the ordering of whether contentious readers or writers will +acquire the lock first.

+

Returns an RAII guard which will release this thread’s shared access +once it is dropped.

+
Errors
+

This function will return an error if the RwLock is poisoned. An +RwLock is poisoned whenever a writer panics while holding an exclusive +lock. The failure will occur immediately after the lock has been +acquired.

+
Panics
+

This function might panic when called if the lock is already held by the current thread.

+
Examples
+
use std::sync::{Arc, RwLock};
+use std::thread;
+
+let lock = Arc::new(RwLock::new(1));
+let c_lock = Arc::clone(&lock);
+
+let n = lock.read().unwrap();
+assert_eq!(*n, 1);
+
+thread::spawn(move || {
+    let r = c_lock.read();
+    assert!(r.is_ok());
+}).join().unwrap();
+
1.0.0 · source

pub fn try_read( + &self +) -> Result<RwLockReadGuard<'_, T>, TryLockError<RwLockReadGuard<'_, T>>>

Attempts to acquire this RwLock with shared read access.

+

If the access could not be granted at this time, then Err is returned. +Otherwise, an RAII guard is returned which will release the shared access +when it is dropped.

+

This function does not block.

+

This function does not provide any guarantees with respect to the ordering +of whether contentious readers or writers will acquire the lock first.

+
Errors
+

This function will return the Poisoned error if the RwLock is +poisoned. An RwLock is poisoned whenever a writer panics while holding +an exclusive lock. Poisoned will only be returned if the lock would +have otherwise been acquired.

+

This function will return the WouldBlock error if the RwLock could +not be acquired because it was already locked exclusively.

+
Examples
+
use std::sync::RwLock;
+
+let lock = RwLock::new(1);
+
+match lock.try_read() {
+    Ok(n) => assert_eq!(*n, 1),
+    Err(_) => unreachable!(),
+};
+
1.0.0 · source

pub fn write( + &self +) -> Result<RwLockWriteGuard<'_, T>, PoisonError<RwLockWriteGuard<'_, T>>>

Locks this RwLock with exclusive write access, blocking the current +thread until it can be acquired.

+

This function will not return while other writers or other readers +currently have access to the lock.

+

Returns an RAII guard which will drop the write access of this RwLock +when dropped.

+
Errors
+

This function will return an error if the RwLock is poisoned. An +RwLock is poisoned whenever a writer panics while holding an exclusive +lock. An error will be returned when the lock is acquired.

+
Panics
+

This function might panic when called if the lock is already held by the current thread.

+
Examples
+
use std::sync::RwLock;
+
+let lock = RwLock::new(1);
+
+let mut n = lock.write().unwrap();
+*n = 2;
+
+assert!(lock.try_read().is_err());
+
1.0.0 · source

pub fn try_write( + &self +) -> Result<RwLockWriteGuard<'_, T>, TryLockError<RwLockWriteGuard<'_, T>>>

Attempts to lock this RwLock with exclusive write access.

+

If the lock could not be acquired at this time, then Err is returned. +Otherwise, an RAII guard is returned which will release the lock when +it is dropped.

+

This function does not block.

+

This function does not provide any guarantees with respect to the ordering +of whether contentious readers or writers will acquire the lock first.

+
Errors
+

This function will return the Poisoned error if the RwLock is +poisoned. An RwLock is poisoned whenever a writer panics while holding +an exclusive lock. Poisoned will only be returned if the lock would +have otherwise been acquired.

+

This function will return the WouldBlock error if the RwLock could +not be acquired because it was already locked exclusively.

+
Examples
+
use std::sync::RwLock;
+
+let lock = RwLock::new(1);
+
+let n = lock.read().unwrap();
+assert_eq!(*n, 1);
+
+assert!(lock.try_write().is_err());
+
1.2.0 · source

pub fn is_poisoned(&self) -> bool

Determines whether the lock is poisoned.

+

If another thread is active, the lock can still become poisoned at any +time. You should not trust a false value for program correctness +without additional synchronization.

+
Examples
+
use std::sync::{Arc, RwLock};
+use std::thread;
+
+let lock = Arc::new(RwLock::new(0));
+let c_lock = Arc::clone(&lock);
+
+let _ = thread::spawn(move || {
+    let _lock = c_lock.write().unwrap();
+    panic!(); // the lock gets poisoned
+}).join();
+assert_eq!(lock.is_poisoned(), true);
+
source

pub fn clear_poison(&self)

🔬This is a nightly-only experimental API. (mutex_unpoison)

Clear the poisoned state from a lock

+

If the lock is poisoned, it will remain poisoned until this function is called. This allows +recovering from a poisoned state and marking that it has recovered. For example, if the +value is overwritten by a known-good value, then the mutex can be marked as un-poisoned. Or +possibly, the value could be inspected to determine if it is in a consistent state, and if +so the poison is removed.

+
Examples
+
#![feature(mutex_unpoison)]
+
+use std::sync::{Arc, RwLock};
+use std::thread;
+
+let lock = Arc::new(RwLock::new(0));
+let c_lock = Arc::clone(&lock);
+
+let _ = thread::spawn(move || {
+    let _lock = c_lock.write().unwrap();
+    panic!(); // the mutex gets poisoned
+}).join();
+
+assert_eq!(lock.is_poisoned(), true);
+let guard = lock.write().unwrap_or_else(|mut e| {
+    **e.get_mut() = 1;
+    lock.clear_poison();
+    e.into_inner()
+});
+assert_eq!(lock.is_poisoned(), false);
+assert_eq!(*guard, 1);
+

Trait Implementations§

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/db/db/struct.GlobalChangeCounterTracker.html b/book/rust-docs/places/db/db/struct.GlobalChangeCounterTracker.html new file mode 100644 index 0000000000..a09604dd44 --- /dev/null +++ b/book/rust-docs/places/db/db/struct.GlobalChangeCounterTracker.html @@ -0,0 +1,14 @@ +GlobalChangeCounterTracker in places::db::db - Rust
pub struct GlobalChangeCounterTracker { /* private fields */ }
Expand description

An object that can tell you whether a bookmark changing operation has +happened since the object was created.

+

Implementations§

source§

impl GlobalChangeCounterTracker

source

pub fn new(api_id: usize) -> Self

source

pub fn changed(&self) -> bool

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/db/db/struct.PlacesDb.html b/book/rust-docs/places/db/db/struct.PlacesDb.html new file mode 100644 index 0000000000..372e0993fd --- /dev/null +++ b/book/rust-docs/places/db/db/struct.PlacesDb.html @@ -0,0 +1,490 @@ +PlacesDb in places::db::db - Rust

Struct places::db::db::PlacesDb

source ·
pub struct PlacesDb {
+    pub db: Connection,
+    /* private fields */
+}

Fields§

§db: Connection

Implementations§

source§

impl PlacesDb

source

pub fn open( + path: impl AsRef<Path>, + conn_type: ConnectionType, + api_id: usize, + coop_tx_lock: Arc<Mutex<()>> +) -> Result<Self>

source

pub fn new_interrupt_handle(&self) -> Arc<SqlInterruptHandle>

source

pub fn begin_interrupt_scope(&self) -> Result<SqlInterruptScope>

source

pub fn conn_type(&self) -> ConnectionType

source

pub fn global_bookmark_change_tracker(&self) -> GlobalChangeCounterTracker

Returns an object that can tell you whether any changes have been made +to bookmarks since this was called. +While this conceptually should live on the PlacesApi, the things that +need this typically only have a PlacesDb, so we expose it here.

+
source

pub fn api_id(&self) -> usize

source§

impl PlacesDb

source

pub fn begin_transaction(&self) -> Result<PlacesTransaction<'_>>

Begin the “correct” transaction type for this connection.

+
    +
  • For Sync connections, begins a chunked coop transaction.
  • +
  • for ReadWrite connections, begins a normal coop transaction
  • +
  • for ReadOnly connections, begins an unchecked transaction.
  • +
+

Methods from Deref<Target = Connection>§

pub fn busy_timeout(&self, timeout: Duration) -> Result<(), Error>

Set a busy handler that sleeps for a specified amount of time when a +table is locked. The handler will sleep multiple times until at +least “ms” milliseconds of sleeping have accumulated.

+

Calling this routine with an argument equal to zero turns off all busy +handlers.

+

There can only be a single busy handler for a particular database +connection at any given moment. If another busy handler was defined +(using busy_handler) prior to calling this +routine, that other busy handler is cleared.

+

Newly created connections currently have a default busy timeout of +5000ms, but this may be subject to change.

+

pub fn busy_handler( + &self, + callback: Option<fn(_: i32) -> bool> +) -> Result<(), Error>

Register a callback to handle SQLITE_BUSY errors.

+

If the busy callback is None, then SQLITE_BUSY is returned +immediately upon encountering the lock. The argument to the busy +handler callback is the number of times that the +busy handler has been invoked previously for the +same locking event. If the busy callback returns false, then no +additional attempts are made to access the +database and SQLITE_BUSY is returned to the +application. If the callback returns true, then another attempt +is made to access the database and the cycle repeats.

+

There can only be a single busy handler defined for each database +connection. Setting a new busy handler clears any previously set +handler. Note that calling busy_timeout() +or evaluating PRAGMA busy_timeout=N will change the busy handler +and thus clear any previously set busy handler.

+

Newly created connections default to a +busy_timeout() handler with a timeout +of 5000ms, although this is subject to change.

+

pub fn prepare_cached(&self, sql: &str) -> Result<CachedStatement<'_>, Error>

Prepare a SQL statement for execution, returning a previously prepared +(but not currently in-use) statement if one is available. The +returned statement will be cached for reuse by future calls to +prepare_cached once it is dropped.

+ +
fn insert_new_people(conn: &Connection) -> Result<()> {
+    {
+        let mut stmt = conn.prepare_cached("INSERT INTO People (name) VALUES (?1)")?;
+        stmt.execute(["Joe Smith"])?;
+    }
+    {
+        // This will return the same underlying SQLite statement handle without
+        // having to prepare it again.
+        let mut stmt = conn.prepare_cached("INSERT INTO People (name) VALUES (?1)")?;
+        stmt.execute(["Bob Jones"])?;
+    }
+    Ok(())
+}
+
Failure
+

Will return Err if sql cannot be converted to a C-compatible string +or if the underlying SQLite call fails.

+

pub fn set_prepared_statement_cache_capacity(&self, capacity: usize)

Set the maximum number of cached prepared statements this connection +will hold. By default, a connection will hold a relatively small +number of cached statements. If you need more, or know that you +will not use cached statements, you +can set the capacity manually using this method.

+

pub fn flush_prepared_statement_cache(&self)

Remove/finalize all prepared statements currently in the cache.

+

pub fn db_config(&self, config: DbConfig) -> Result<bool, Error>

Returns the current value of a config.

+
    +
  • SQLITE_DBCONFIG_ENABLE_FKEY: return false or true to indicate +whether FK enforcement is off or on
  • +
  • SQLITE_DBCONFIG_ENABLE_TRIGGER: return false or true to indicate +whether triggers are disabled or enabled
  • +
  • SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER: return false or true to +indicate whether fts3_tokenizer are disabled or enabled
  • +
  • SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE: return false to indicate +checkpoints-on-close are not disabled or true if they are
  • +
  • SQLITE_DBCONFIG_ENABLE_QPSG: return false or true to indicate +whether the QPSG is disabled or enabled
  • +
  • SQLITE_DBCONFIG_TRIGGER_EQP: return false to indicate +output-for-trigger are not disabled or true if it is
  • +
+

pub fn set_db_config( + &self, + config: DbConfig, + new_val: bool +) -> Result<bool, Error>

Make configuration changes to a database connection

+
    +
  • SQLITE_DBCONFIG_ENABLE_FKEY: false to disable FK enforcement, +true to enable FK enforcement
  • +
  • SQLITE_DBCONFIG_ENABLE_TRIGGER: false to disable triggers, true +to enable triggers
  • +
  • SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER: false to disable +fts3_tokenizer(), true to enable fts3_tokenizer()
  • +
  • SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE: false (the default) to enable +checkpoints-on-close, true to disable them
  • +
  • SQLITE_DBCONFIG_ENABLE_QPSG: false to disable the QPSG, true to +enable QPSG
  • +
  • SQLITE_DBCONFIG_TRIGGER_EQP: false to disable output for trigger +programs, true to enable it
  • +
+

pub fn create_scalar_function<F, T>( + &self, + fn_name: &str, + n_arg: i32, + flags: FunctionFlags, + x_func: F +) -> Result<(), Error>where + F: FnMut(&Context<'_>) -> Result<T, Error> + Send + UnwindSafe + 'static, + T: ToSql,

Attach a user-defined scalar function to +this database connection.

+

fn_name is the name the function will be accessible from SQL. +n_arg is the number of arguments to the function. Use -1 for a +variable number. If the function always returns the same value +given the same input, deterministic should be true.

+

The function will remain available until the connection is closed or +until it is explicitly removed via +remove_function.

+
Example
+
fn scalar_function_example(db: Connection) -> Result<()> {
+    db.create_scalar_function(
+        "halve",
+        1,
+        FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
+        |ctx| {
+            let value = ctx.get::<f64>(0)?;
+            Ok(value / 2f64)
+        },
+    )?;
+
+    let six_halved: f64 = db.query_row("SELECT halve(6)", [], |r| r.get(0))?;
+    assert_eq!(six_halved, 3f64);
+    Ok(())
+}
+
Failure
+

Will return Err if the function could not be attached to the connection.

+

pub fn create_aggregate_function<A, D, T>( + &self, + fn_name: &str, + n_arg: i32, + flags: FunctionFlags, + aggr: D +) -> Result<(), Error>where + A: RefUnwindSafe + UnwindSafe, + D: Aggregate<A, T> + 'static, + T: ToSql,

Attach a user-defined aggregate function to this +database connection.

+
Failure
+

Will return Err if the function could not be attached to the connection.

+

pub fn create_window_function<A, W, T>( + &self, + fn_name: &str, + n_arg: i32, + flags: FunctionFlags, + aggr: W +) -> Result<(), Error>where + A: RefUnwindSafe + UnwindSafe, + W: WindowAggregate<A, T> + 'static, + T: ToSql,

Attach a user-defined aggregate window function to +this database connection.

+

See https://sqlite.org/windowfunctions.html#udfwinfunc for more +information.

+

pub fn remove_function(&self, fn_name: &str, n_arg: i32) -> Result<(), Error>

Removes a user-defined function from this +database connection.

+

fn_name and n_arg should match the name and number of arguments +given to create_scalar_function +or create_aggregate_function.

+
Failure
+

Will return Err if the function could not be removed.

+

pub fn limit(&self, limit: Limit) -> i32

Returns the current value of a [Limit].

+

pub fn set_limit(&self, limit: Limit, new_val: i32) -> i32

Changes the [Limit] to new_val, returning the prior +value of the limit.

+

pub fn pragma_query_value<T, F>( + &self, + schema_name: Option<DatabaseName<'_>>, + pragma_name: &str, + f: F +) -> Result<T, Error>where + F: FnOnce(&Row<'_>) -> Result<T, Error>,

Query the current value of pragma_name.

+

Some pragmas will return multiple rows/values which cannot be retrieved +with this method.

+

Prefer PRAGMA function introduced in SQLite 3.20: +SELECT user_version FROM pragma_user_version;

+

pub fn pragma_query<F>( + &self, + schema_name: Option<DatabaseName<'_>>, + pragma_name: &str, + f: F +) -> Result<(), Error>where + F: FnMut(&Row<'_>) -> Result<(), Error>,

Query the current rows/values of pragma_name.

+

Prefer PRAGMA function introduced in SQLite 3.20: +SELECT * FROM pragma_collation_list;

+

pub fn pragma<F, V>( + &self, + schema_name: Option<DatabaseName<'_>>, + pragma_name: &str, + pragma_value: V, + f: F +) -> Result<(), Error>where + F: FnMut(&Row<'_>) -> Result<(), Error>, + V: ToSql,

Query the current value(s) of pragma_name associated to +pragma_value.

+

This method can be used with query-only pragmas which need an argument +(e.g. table_info('one_tbl')) or pragmas which returns value(s) +(e.g. integrity_check).

+

Prefer PRAGMA function introduced in SQLite 3.20: +SELECT * FROM pragma_table_info(?1);

+

pub fn pragma_update<V>( + &self, + schema_name: Option<DatabaseName<'_>>, + pragma_name: &str, + pragma_value: V +) -> Result<(), Error>where + V: ToSql,

Set a new value to pragma_name.

+

Some pragmas will return the updated value which cannot be retrieved +with this method.

+

pub fn pragma_update_and_check<F, T, V>( + &self, + schema_name: Option<DatabaseName<'_>>, + pragma_name: &str, + pragma_value: V, + f: F +) -> Result<T, Error>where + F: FnOnce(&Row<'_>) -> Result<T, Error>, + V: ToSql,

Set a new value to pragma_name and return the updated value.

+

Only few pragmas automatically return the updated value.

+

pub fn unchecked_transaction(&self) -> Result<Transaction<'_>, Error>

Begin a new transaction with the default behavior (DEFERRED).

+

Attempt to open a nested transaction will result in a SQLite error. +Connection::transaction prevents this at compile time by taking &mut self, but Connection::unchecked_transaction() may be used to defer +the checking until runtime.

+

See [Connection::transaction] and [Transaction::new_unchecked] +(which can be used if the default transaction behavior is undesirable).

+
Example
+
fn perform_queries(conn: Rc<Connection>) -> Result<()> {
+    let tx = conn.unchecked_transaction()?;
+
+    do_queries_part_1(&tx)?; // tx causes rollback if this fails
+    do_queries_part_2(&tx)?; // tx causes rollback if this fails
+
+    tx.commit()
+}
+
Failure
+

Will return Err if the underlying SQLite call fails. The specific +error returned if transactions are nested is currently unspecified.

+

pub fn transaction_state( + &self, + db_name: Option<DatabaseName<'_>> +) -> Result<TransactionState, Error>

Determine the transaction state of a database

+

pub fn execute_batch(&self, sql: &str) -> Result<(), Error>

Convenience method to run multiple SQL statements (that cannot take any +parameters).

+
Example
+
fn create_tables(conn: &Connection) -> Result<()> {
+    conn.execute_batch(
+        "BEGIN;
+         CREATE TABLE foo(x INTEGER);
+         CREATE TABLE bar(y TEXT);
+         COMMIT;",
+    )
+}
+
Failure
+

Will return Err if sql cannot be converted to a C-compatible string +or if the underlying SQLite call fails.

+

pub fn execute<P>(&self, sql: &str, params: P) -> Result<usize, Error>where + P: Params,

Convenience method to prepare and execute a single SQL statement.

+

On success, returns the number of rows that were changed or inserted or +deleted (via sqlite3_changes).

+
Example
With positional params
+
fn update_rows(conn: &Connection) {
+    match conn.execute("UPDATE foo SET bar = 'baz' WHERE qux = ?1", [1i32]) {
+        Ok(updated) => println!("{} rows were updated", updated),
+        Err(err) => println!("update failed: {}", err),
+    }
+}
+
With positional params of varying types
+
fn update_rows(conn: &Connection) {
+    match conn.execute(
+        "UPDATE foo SET bar = 'baz' WHERE qux = ?1 AND quux = ?2",
+        params![1i32, 1.5f64],
+    ) {
+        Ok(updated) => println!("{} rows were updated", updated),
+        Err(err) => println!("update failed: {}", err),
+    }
+}
+
With named params
+
fn insert(conn: &Connection) -> Result<usize> {
+    conn.execute(
+        "INSERT INTO test (name) VALUES (:name)",
+        &[(":name", "one")],
+    )
+}
+
Failure
+

Will return Err if sql cannot be converted to a C-compatible string +or if the underlying SQLite call fails.

+

pub fn path(&self) -> Option<&str>

Returns the path to the database file, if one exists and is known.

+

Returns Some("") for a temporary or in-memory database.

+

Note that in some cases PRAGMA +database_list is +likely to be more robust.

+

pub fn last_insert_rowid(&self) -> i64

Get the SQLite rowid of the most recent successful INSERT.

+

Uses sqlite3_last_insert_rowid under +the hood.

+

pub fn query_row<T, P, F>(&self, sql: &str, params: P, f: F) -> Result<T, Error>where + P: Params, + F: FnOnce(&Row<'_>) -> Result<T, Error>,

Convenience method to execute a query that is expected to return a +single row.

+
Example
+
fn preferred_locale(conn: &Connection) -> Result<String> {
+    conn.query_row(
+        "SELECT value FROM preferences WHERE name='locale'",
+        [],
+        |row| row.get(0),
+    )
+}
+

If the query returns more than one row, all rows except the first are +ignored.

+

Returns Err(QueryReturnedNoRows) if no results are returned. If the +query truly is optional, you can call .optional() on the result of +this to get a Result<Option<T>>.

+
Failure
+

Will return Err if sql cannot be converted to a C-compatible string +or if the underlying SQLite call fails.

+

pub fn query_row_and_then<T, E, P, F>( + &self, + sql: &str, + params: P, + f: F +) -> Result<T, E>where + P: Params, + F: FnOnce(&Row<'_>) -> Result<T, E>, + E: From<Error>,

Convenience method to execute a query that is expected to return a +single row, and execute a mapping via f on that returned row with +the possibility of failure. The Result type of f must implement +std::convert::From<Error>.

+
Example
+
fn preferred_locale(conn: &Connection) -> Result<String> {
+    conn.query_row_and_then(
+        "SELECT value FROM preferences WHERE name='locale'",
+        [],
+        |row| row.get(0),
+    )
+}
+

If the query returns more than one row, all rows except the first are +ignored.

+
Failure
+

Will return Err if sql cannot be converted to a C-compatible string +or if the underlying SQLite call fails.

+

pub fn prepare(&self, sql: &str) -> Result<Statement<'_>, Error>

Prepare a SQL statement for execution.

+
Example
+
fn insert_new_people(conn: &Connection) -> Result<()> {
+    let mut stmt = conn.prepare("INSERT INTO People (name) VALUES (?1)")?;
+    stmt.execute(["Joe Smith"])?;
+    stmt.execute(["Bob Jones"])?;
+    Ok(())
+}
+
Failure
+

Will return Err if sql cannot be converted to a C-compatible string +or if the underlying SQLite call fails.

+

pub unsafe fn handle(&self) -> *mut sqlite3

Get access to the underlying SQLite database connection handle.

+
Warning
+

You should not need to use this function. If you do need to, please +open an issue on the rusqlite repository and describe +your use case.

+
Safety
+

This function is unsafe because it gives you raw access +to the SQLite connection, and what you do with it could impact the +safety of this Connection.

+

pub fn get_interrupt_handle(&self) -> InterruptHandle

Get access to a handle that can be used to interrupt long running +queries from another thread.

+

pub fn changes(&self) -> u64

Return the number of rows modified, inserted or deleted by the most +recently completed INSERT, UPDATE or DELETE statement on the database +connection.

+

See https://www.sqlite.org/c3ref/changes.html

+

pub fn is_autocommit(&self) -> bool

Test for auto-commit mode. +Autocommit mode is on by default.

+

pub fn is_busy(&self) -> bool

Determine if all associated prepared statements have been reset.

+

pub fn cache_flush(&self) -> Result<(), Error>

Flush caches to disk mid-transaction

+

pub fn is_readonly(&self, db_name: DatabaseName<'_>) -> Result<bool, Error>

Determine if a database is read-only

+

Trait Implementations§

source§

impl ConnExt for PlacesDb

source§

fn conn(&self) -> &Connection

The method you need to implement to opt in to all of this.
§

fn set_pragma<T>( + &self, + pragma_name: &str, + pragma_value: T +) -> Result<&Self, Error>where + T: ToSql, + Self: Sized,

Set the value of the pragma on the main database. Returns the same object, for chaining.
§

fn prepare_maybe_cached<'conn>( + &'conn self, + sql: &str, + cache: bool +) -> Result<MaybeCached<'conn>, Error>

Get a cached or uncached statement based on a flag.
§

fn execute_all(&self, stmts: &[&str]) -> Result<(), Error>

Execute all the provided statements.
§

fn execute_one(&self, stmt: &str) -> Result<(), Error>

Execute a single statement.
§

fn execute_cached<P>(&self, sql: &str, params: P) -> Result<usize, Error>where + P: Params,

Equivalent to Connection::execute but caches the statement so that subsequent +calls to execute_cached will have improved performance.
§

fn query_one<T>(&self, sql: &str) -> Result<T, Error>where + T: FromSql,

Execute a query that returns a single result column, and return that result.
§

fn exists<P>(&self, sql: &str, params: P) -> Result<bool, Error>where + P: Params,

Return true if a query returns any rows
§

fn try_query_one<T, P>( + &self, + sql: &str, + params: P, + cache: bool +) -> Result<Option<T>, Error>where + T: FromSql, + P: Params, + Self: Sized,

Execute a query that returns 0 or 1 result columns, returning None +if there were no rows, or if the only result was NULL.
§

fn query_row_and_then_cachable<T, E, P, F>( + &self, + sql: &str, + params: P, + mapper: F, + cache: bool +) -> Result<T, E>where + Self: Sized, + P: Params, + E: From<Error>, + F: FnOnce(&Row<'_>) -> Result<T, E>,

Equivalent to rusqlite::Connection::query_row_and_then but allows +passing a flag to indicate that it’s cached.
§

fn query_rows_and_then<T, E, P, F>( + &self, + sql: &str, + params: P, + mapper: F +) -> Result<Vec<T, Global>, E>where + Self: Sized, + P: Params, + E: From<Error>, + F: FnMut(&Row<'_>) -> Result<T, E>,

Helper for when you’d like to get a Vec<T> of all the rows returned by a +query that takes named arguments. See also +query_rows_and_then_cached.
§

fn query_rows_and_then_cached<T, E, P, F>( + &self, + sql: &str, + params: P, + mapper: F +) -> Result<Vec<T, Global>, E>where + Self: Sized, + P: Params, + E: From<Error>, + F: FnMut(&Row<'_>) -> Result<T, E>,

Helper for when you’d like to get a Vec<T> of all the rows returned by a +query that takes named arguments.
§

fn query_rows_into<Coll, T, E, P, F>( + &self, + sql: &str, + params: P, + mapper: F +) -> Result<Coll, E>where + Self: Sized, + E: From<Error>, + F: FnMut(&Row<'_>) -> Result<T, E>, + Coll: FromIterator<T>, + P: Params,

Like query_rows_and_then_cachable, but works if you want a non-Vec as a result. Read more
§

fn query_rows_into_cached<Coll, T, E, P, F>( + &self, + sql: &str, + params: P, + mapper: F +) -> Result<Coll, E>where + Self: Sized, + P: Params, + E: From<Error>, + F: FnMut(&Row<'_>) -> Result<T, E>, + Coll: FromIterator<T>,

Same as query_rows_into, but caches the stmt if possible.
§

fn try_query_row<T, E, P, F>( + &self, + sql: &str, + params: P, + mapper: F, + cache: bool +) -> Result<Option<T>, E>where + Self: Sized, + P: Params, + E: From<Error>, + F: FnOnce(&Row<'_>) -> Result<T, E>,

Like query_row_and_then_cachable but returns None instead of erroring +if no such row exists.
§

fn unchecked_transaction(&self) -> Result<UncheckedTransaction<'_>, Error>

Caveat: This won’t actually get used most of the time, and calls will +usually invoke rusqlite’s method with the same name. See comment on +UncheckedTransaction for details (generally you probably don’t need to +care)
§

fn unchecked_transaction_imm(&self) -> Result<UncheckedTransaction<'_>, Error>

Begin unchecked_transaction with TransactionBehavior::Immediate. Use +when the first operation will be a read operation, that further writes +depend on for correctness.
§

fn get_db_size(&self) -> Result<u32, Error>

Get the DB size in bytes
source§

impl Debug for PlacesDb

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Deref for PlacesDb

§

type Target = Connection

The resulting type after dereferencing.
source§

fn deref(&self) -> &Connection

Dereferences the value.
source§

impl Drop for PlacesDb

source§

fn drop(&mut self)

Executes the destructor for this type. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/db/db/struct.PlacesInitializer.html b/book/rust-docs/places/db/db/struct.PlacesInitializer.html new file mode 100644 index 0000000000..bdc0204a2a --- /dev/null +++ b/book/rust-docs/places/db/db/struct.PlacesInitializer.html @@ -0,0 +1,12 @@ +PlacesInitializer in places::db::db - Rust

Struct places::db::db::PlacesInitializer

source ·
pub struct PlacesInitializer { /* private fields */ }

Trait Implementations§

source§

impl ConnectionInitializer for PlacesInitializer

source§

const NAME: &'static str = "places"

source§

const END_VERSION: u32 = 17u32

source§

fn init(&self, tx: &Transaction<'_>) -> Result<()>

source§

fn upgrade_from(&self, tx: &Transaction<'_>, version: u32) -> Result<()>

source§

fn prepare(&self, conn: &Connection, db_empty: bool) -> Result<()>

source§

fn finish(&self, conn: &Connection) -> Result<()>

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/db/db/struct.SharedPlacesDb.html b/book/rust-docs/places/db/db/struct.SharedPlacesDb.html new file mode 100644 index 0000000000..9baf523968 --- /dev/null +++ b/book/rust-docs/places/db/db/struct.SharedPlacesDb.html @@ -0,0 +1,13 @@ +SharedPlacesDb in places::db::db - Rust

Struct places::db::db::SharedPlacesDb

source ·
pub struct SharedPlacesDb { /* private fields */ }
Expand description

PlacesDB that’s behind a Mutex so it can be shared between threads

+

Implementations§

source§

impl SharedPlacesDb

source

pub fn new(db: PlacesDb) -> Self

source

pub fn begin_interrupt_scope(&self) -> Result<SqlInterruptScope>

Trait Implementations§

source§

impl AsRef<SqlInterruptHandle> for SharedPlacesDb

source§

fn as_ref(&self) -> &SqlInterruptHandle

Converts this type into a shared reference of the (usually inferred) input type.
source§

impl Deref for SharedPlacesDb

§

type Target = Mutex<RawMutex, PlacesDb>

The resulting type after dereferencing.
source§

fn deref(&self) -> &Mutex<PlacesDb>

Dereferences the value.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/db/index.html b/book/rust-docs/places/db/index.html new file mode 100644 index 0000000000..f97fb81f67 --- /dev/null +++ b/book/rust-docs/places/db/index.html @@ -0,0 +1,2 @@ +places::db - Rust

Module places::db

source ·

Re-exports

Modules

Structs

  • High level transaction type which “does the right thing” for you. +Construct one with PlacesDb::begin_transaction().
\ No newline at end of file diff --git a/book/rust-docs/places/db/sidebar-items.js b/book/rust-docs/places/db/sidebar-items.js new file mode 100644 index 0000000000..d47ad1d30a --- /dev/null +++ b/book/rust-docs/places/db/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"mod":["db"],"struct":["PlacesTransaction"]}; \ No newline at end of file diff --git a/book/rust-docs/places/db/struct.PlacesTransaction.html b/book/rust-docs/places/db/struct.PlacesTransaction.html new file mode 100644 index 0000000000..5872924145 --- /dev/null +++ b/book/rust-docs/places/db/struct.PlacesTransaction.html @@ -0,0 +1,487 @@ +PlacesTransaction in places::db - Rust
pub struct PlacesTransaction<'conn>(_);
Expand description

High level transaction type which “does the right thing” for you. +Construct one with PlacesDb::begin_transaction().

+

Implementations§

source§

impl<'conn> PlacesTransaction<'conn>

source

pub fn should_commit(&self) -> bool

Returns true if the current transaction should be committed at the +earliest opportunity.

+
source

pub fn maybe_commit(&mut self) -> Result<()>

    +
  • For transactions on sync connnections: Checks to see if we have held a +transaction for longer than the requested time, and if so, commits the +current transaction and opens another.
  • +
  • For transactions on other connections: debug_assert!s, or logs a +warning and does nothing.
  • +
+
source

pub fn commit(self) -> Result<()>

Consumes and commits a PlacesTransaction transaction.

+
source

pub fn rollback(self) -> Result<()>

Consumes and attempst to roll back a PlacesTransaction. Note that if +maybe_commit has been called, this may only roll back as far as that +call.

+

Methods from Deref<Target = Connection>§

pub fn busy_timeout(&self, timeout: Duration) -> Result<(), Error>

Set a busy handler that sleeps for a specified amount of time when a +table is locked. The handler will sleep multiple times until at +least “ms” milliseconds of sleeping have accumulated.

+

Calling this routine with an argument equal to zero turns off all busy +handlers.

+

There can only be a single busy handler for a particular database +connection at any given moment. If another busy handler was defined +(using busy_handler) prior to calling this +routine, that other busy handler is cleared.

+

Newly created connections currently have a default busy timeout of +5000ms, but this may be subject to change.

+

pub fn busy_handler( + &self, + callback: Option<fn(_: i32) -> bool> +) -> Result<(), Error>

Register a callback to handle SQLITE_BUSY errors.

+

If the busy callback is None, then SQLITE_BUSY is returned +immediately upon encountering the lock. The argument to the busy +handler callback is the number of times that the +busy handler has been invoked previously for the +same locking event. If the busy callback returns false, then no +additional attempts are made to access the +database and SQLITE_BUSY is returned to the +application. If the callback returns true, then another attempt +is made to access the database and the cycle repeats.

+

There can only be a single busy handler defined for each database +connection. Setting a new busy handler clears any previously set +handler. Note that calling busy_timeout() +or evaluating PRAGMA busy_timeout=N will change the busy handler +and thus clear any previously set busy handler.

+

Newly created connections default to a +busy_timeout() handler with a timeout +of 5000ms, although this is subject to change.

+

pub fn prepare_cached(&self, sql: &str) -> Result<CachedStatement<'_>, Error>

Prepare a SQL statement for execution, returning a previously prepared +(but not currently in-use) statement if one is available. The +returned statement will be cached for reuse by future calls to +prepare_cached once it is dropped.

+ +
fn insert_new_people(conn: &Connection) -> Result<()> {
+    {
+        let mut stmt = conn.prepare_cached("INSERT INTO People (name) VALUES (?1)")?;
+        stmt.execute(["Joe Smith"])?;
+    }
+    {
+        // This will return the same underlying SQLite statement handle without
+        // having to prepare it again.
+        let mut stmt = conn.prepare_cached("INSERT INTO People (name) VALUES (?1)")?;
+        stmt.execute(["Bob Jones"])?;
+    }
+    Ok(())
+}
+
Failure
+

Will return Err if sql cannot be converted to a C-compatible string +or if the underlying SQLite call fails.

+

pub fn set_prepared_statement_cache_capacity(&self, capacity: usize)

Set the maximum number of cached prepared statements this connection +will hold. By default, a connection will hold a relatively small +number of cached statements. If you need more, or know that you +will not use cached statements, you +can set the capacity manually using this method.

+

pub fn flush_prepared_statement_cache(&self)

Remove/finalize all prepared statements currently in the cache.

+

pub fn db_config(&self, config: DbConfig) -> Result<bool, Error>

Returns the current value of a config.

+
    +
  • SQLITE_DBCONFIG_ENABLE_FKEY: return false or true to indicate +whether FK enforcement is off or on
  • +
  • SQLITE_DBCONFIG_ENABLE_TRIGGER: return false or true to indicate +whether triggers are disabled or enabled
  • +
  • SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER: return false or true to +indicate whether fts3_tokenizer are disabled or enabled
  • +
  • SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE: return false to indicate +checkpoints-on-close are not disabled or true if they are
  • +
  • SQLITE_DBCONFIG_ENABLE_QPSG: return false or true to indicate +whether the QPSG is disabled or enabled
  • +
  • SQLITE_DBCONFIG_TRIGGER_EQP: return false to indicate +output-for-trigger are not disabled or true if it is
  • +
+

pub fn set_db_config( + &self, + config: DbConfig, + new_val: bool +) -> Result<bool, Error>

Make configuration changes to a database connection

+
    +
  • SQLITE_DBCONFIG_ENABLE_FKEY: false to disable FK enforcement, +true to enable FK enforcement
  • +
  • SQLITE_DBCONFIG_ENABLE_TRIGGER: false to disable triggers, true +to enable triggers
  • +
  • SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER: false to disable +fts3_tokenizer(), true to enable fts3_tokenizer()
  • +
  • SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE: false (the default) to enable +checkpoints-on-close, true to disable them
  • +
  • SQLITE_DBCONFIG_ENABLE_QPSG: false to disable the QPSG, true to +enable QPSG
  • +
  • SQLITE_DBCONFIG_TRIGGER_EQP: false to disable output for trigger +programs, true to enable it
  • +
+

pub fn create_scalar_function<F, T>( + &self, + fn_name: &str, + n_arg: i32, + flags: FunctionFlags, + x_func: F +) -> Result<(), Error>where + F: FnMut(&Context<'_>) -> Result<T, Error> + Send + UnwindSafe + 'static, + T: ToSql,

Attach a user-defined scalar function to +this database connection.

+

fn_name is the name the function will be accessible from SQL. +n_arg is the number of arguments to the function. Use -1 for a +variable number. If the function always returns the same value +given the same input, deterministic should be true.

+

The function will remain available until the connection is closed or +until it is explicitly removed via +remove_function.

+
Example
+
fn scalar_function_example(db: Connection) -> Result<()> {
+    db.create_scalar_function(
+        "halve",
+        1,
+        FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
+        |ctx| {
+            let value = ctx.get::<f64>(0)?;
+            Ok(value / 2f64)
+        },
+    )?;
+
+    let six_halved: f64 = db.query_row("SELECT halve(6)", [], |r| r.get(0))?;
+    assert_eq!(six_halved, 3f64);
+    Ok(())
+}
+
Failure
+

Will return Err if the function could not be attached to the connection.

+

pub fn create_aggregate_function<A, D, T>( + &self, + fn_name: &str, + n_arg: i32, + flags: FunctionFlags, + aggr: D +) -> Result<(), Error>where + A: RefUnwindSafe + UnwindSafe, + D: Aggregate<A, T> + 'static, + T: ToSql,

Attach a user-defined aggregate function to this +database connection.

+
Failure
+

Will return Err if the function could not be attached to the connection.

+

pub fn create_window_function<A, W, T>( + &self, + fn_name: &str, + n_arg: i32, + flags: FunctionFlags, + aggr: W +) -> Result<(), Error>where + A: RefUnwindSafe + UnwindSafe, + W: WindowAggregate<A, T> + 'static, + T: ToSql,

Attach a user-defined aggregate window function to +this database connection.

+

See https://sqlite.org/windowfunctions.html#udfwinfunc for more +information.

+

pub fn remove_function(&self, fn_name: &str, n_arg: i32) -> Result<(), Error>

Removes a user-defined function from this +database connection.

+

fn_name and n_arg should match the name and number of arguments +given to create_scalar_function +or create_aggregate_function.

+
Failure
+

Will return Err if the function could not be removed.

+

pub fn limit(&self, limit: Limit) -> i32

Returns the current value of a [Limit].

+

pub fn set_limit(&self, limit: Limit, new_val: i32) -> i32

Changes the [Limit] to new_val, returning the prior +value of the limit.

+

pub fn pragma_query_value<T, F>( + &self, + schema_name: Option<DatabaseName<'_>>, + pragma_name: &str, + f: F +) -> Result<T, Error>where + F: FnOnce(&Row<'_>) -> Result<T, Error>,

Query the current value of pragma_name.

+

Some pragmas will return multiple rows/values which cannot be retrieved +with this method.

+

Prefer PRAGMA function introduced in SQLite 3.20: +SELECT user_version FROM pragma_user_version;

+

pub fn pragma_query<F>( + &self, + schema_name: Option<DatabaseName<'_>>, + pragma_name: &str, + f: F +) -> Result<(), Error>where + F: FnMut(&Row<'_>) -> Result<(), Error>,

Query the current rows/values of pragma_name.

+

Prefer PRAGMA function introduced in SQLite 3.20: +SELECT * FROM pragma_collation_list;

+

pub fn pragma<F, V>( + &self, + schema_name: Option<DatabaseName<'_>>, + pragma_name: &str, + pragma_value: V, + f: F +) -> Result<(), Error>where + F: FnMut(&Row<'_>) -> Result<(), Error>, + V: ToSql,

Query the current value(s) of pragma_name associated to +pragma_value.

+

This method can be used with query-only pragmas which need an argument +(e.g. table_info('one_tbl')) or pragmas which returns value(s) +(e.g. integrity_check).

+

Prefer PRAGMA function introduced in SQLite 3.20: +SELECT * FROM pragma_table_info(?1);

+

pub fn pragma_update<V>( + &self, + schema_name: Option<DatabaseName<'_>>, + pragma_name: &str, + pragma_value: V +) -> Result<(), Error>where + V: ToSql,

Set a new value to pragma_name.

+

Some pragmas will return the updated value which cannot be retrieved +with this method.

+

pub fn pragma_update_and_check<F, T, V>( + &self, + schema_name: Option<DatabaseName<'_>>, + pragma_name: &str, + pragma_value: V, + f: F +) -> Result<T, Error>where + F: FnOnce(&Row<'_>) -> Result<T, Error>, + V: ToSql,

Set a new value to pragma_name and return the updated value.

+

Only few pragmas automatically return the updated value.

+

pub fn unchecked_transaction(&self) -> Result<Transaction<'_>, Error>

Begin a new transaction with the default behavior (DEFERRED).

+

Attempt to open a nested transaction will result in a SQLite error. +Connection::transaction prevents this at compile time by taking &mut self, but Connection::unchecked_transaction() may be used to defer +the checking until runtime.

+

See [Connection::transaction] and [Transaction::new_unchecked] +(which can be used if the default transaction behavior is undesirable).

+
Example
+
fn perform_queries(conn: Rc<Connection>) -> Result<()> {
+    let tx = conn.unchecked_transaction()?;
+
+    do_queries_part_1(&tx)?; // tx causes rollback if this fails
+    do_queries_part_2(&tx)?; // tx causes rollback if this fails
+
+    tx.commit()
+}
+
Failure
+

Will return Err if the underlying SQLite call fails. The specific +error returned if transactions are nested is currently unspecified.

+

pub fn transaction_state( + &self, + db_name: Option<DatabaseName<'_>> +) -> Result<TransactionState, Error>

Determine the transaction state of a database

+

pub fn execute_batch(&self, sql: &str) -> Result<(), Error>

Convenience method to run multiple SQL statements (that cannot take any +parameters).

+
Example
+
fn create_tables(conn: &Connection) -> Result<()> {
+    conn.execute_batch(
+        "BEGIN;
+         CREATE TABLE foo(x INTEGER);
+         CREATE TABLE bar(y TEXT);
+         COMMIT;",
+    )
+}
+
Failure
+

Will return Err if sql cannot be converted to a C-compatible string +or if the underlying SQLite call fails.

+

pub fn execute<P>(&self, sql: &str, params: P) -> Result<usize, Error>where + P: Params,

Convenience method to prepare and execute a single SQL statement.

+

On success, returns the number of rows that were changed or inserted or +deleted (via sqlite3_changes).

+
Example
With positional params
+
fn update_rows(conn: &Connection) {
+    match conn.execute("UPDATE foo SET bar = 'baz' WHERE qux = ?1", [1i32]) {
+        Ok(updated) => println!("{} rows were updated", updated),
+        Err(err) => println!("update failed: {}", err),
+    }
+}
+
With positional params of varying types
+
fn update_rows(conn: &Connection) {
+    match conn.execute(
+        "UPDATE foo SET bar = 'baz' WHERE qux = ?1 AND quux = ?2",
+        params![1i32, 1.5f64],
+    ) {
+        Ok(updated) => println!("{} rows were updated", updated),
+        Err(err) => println!("update failed: {}", err),
+    }
+}
+
With named params
+
fn insert(conn: &Connection) -> Result<usize> {
+    conn.execute(
+        "INSERT INTO test (name) VALUES (:name)",
+        &[(":name", "one")],
+    )
+}
+
Failure
+

Will return Err if sql cannot be converted to a C-compatible string +or if the underlying SQLite call fails.

+

pub fn path(&self) -> Option<&str>

Returns the path to the database file, if one exists and is known.

+

Returns Some("") for a temporary or in-memory database.

+

Note that in some cases PRAGMA +database_list is +likely to be more robust.

+

pub fn last_insert_rowid(&self) -> i64

Get the SQLite rowid of the most recent successful INSERT.

+

Uses sqlite3_last_insert_rowid under +the hood.

+

pub fn query_row<T, P, F>(&self, sql: &str, params: P, f: F) -> Result<T, Error>where + P: Params, + F: FnOnce(&Row<'_>) -> Result<T, Error>,

Convenience method to execute a query that is expected to return a +single row.

+
Example
+
fn preferred_locale(conn: &Connection) -> Result<String> {
+    conn.query_row(
+        "SELECT value FROM preferences WHERE name='locale'",
+        [],
+        |row| row.get(0),
+    )
+}
+

If the query returns more than one row, all rows except the first are +ignored.

+

Returns Err(QueryReturnedNoRows) if no results are returned. If the +query truly is optional, you can call .optional() on the result of +this to get a Result<Option<T>>.

+
Failure
+

Will return Err if sql cannot be converted to a C-compatible string +or if the underlying SQLite call fails.

+

pub fn query_row_and_then<T, E, P, F>( + &self, + sql: &str, + params: P, + f: F +) -> Result<T, E>where + P: Params, + F: FnOnce(&Row<'_>) -> Result<T, E>, + E: From<Error>,

Convenience method to execute a query that is expected to return a +single row, and execute a mapping via f on that returned row with +the possibility of failure. The Result type of f must implement +std::convert::From<Error>.

+
Example
+
fn preferred_locale(conn: &Connection) -> Result<String> {
+    conn.query_row_and_then(
+        "SELECT value FROM preferences WHERE name='locale'",
+        [],
+        |row| row.get(0),
+    )
+}
+

If the query returns more than one row, all rows except the first are +ignored.

+
Failure
+

Will return Err if sql cannot be converted to a C-compatible string +or if the underlying SQLite call fails.

+

pub fn prepare(&self, sql: &str) -> Result<Statement<'_>, Error>

Prepare a SQL statement for execution.

+
Example
+
fn insert_new_people(conn: &Connection) -> Result<()> {
+    let mut stmt = conn.prepare("INSERT INTO People (name) VALUES (?1)")?;
+    stmt.execute(["Joe Smith"])?;
+    stmt.execute(["Bob Jones"])?;
+    Ok(())
+}
+
Failure
+

Will return Err if sql cannot be converted to a C-compatible string +or if the underlying SQLite call fails.

+

pub unsafe fn handle(&self) -> *mut sqlite3

Get access to the underlying SQLite database connection handle.

+
Warning
+

You should not need to use this function. If you do need to, please +open an issue on the rusqlite repository and describe +your use case.

+
Safety
+

This function is unsafe because it gives you raw access +to the SQLite connection, and what you do with it could impact the +safety of this Connection.

+

pub fn get_interrupt_handle(&self) -> InterruptHandle

Get access to a handle that can be used to interrupt long running +queries from another thread.

+

pub fn changes(&self) -> u64

Return the number of rows modified, inserted or deleted by the most +recently completed INSERT, UPDATE or DELETE statement on the database +connection.

+

See https://www.sqlite.org/c3ref/changes.html

+

pub fn is_autocommit(&self) -> bool

Test for auto-commit mode. +Autocommit mode is on by default.

+

pub fn is_busy(&self) -> bool

Determine if all associated prepared statements have been reset.

+

pub fn cache_flush(&self) -> Result<(), Error>

Flush caches to disk mid-transaction

+

pub fn is_readonly(&self, db_name: DatabaseName<'_>) -> Result<bool, Error>

Determine if a database is read-only

+

Trait Implementations§

source§

impl<'conn> ConnExt for PlacesTransaction<'conn>

source§

fn conn(&self) -> &Connection

The method you need to implement to opt in to all of this.
§

fn set_pragma<T>( + &self, + pragma_name: &str, + pragma_value: T +) -> Result<&Self, Error>where + T: ToSql, + Self: Sized,

Set the value of the pragma on the main database. Returns the same object, for chaining.
§

fn prepare_maybe_cached<'conn>( + &'conn self, + sql: &str, + cache: bool +) -> Result<MaybeCached<'conn>, Error>

Get a cached or uncached statement based on a flag.
§

fn execute_all(&self, stmts: &[&str]) -> Result<(), Error>

Execute all the provided statements.
§

fn execute_one(&self, stmt: &str) -> Result<(), Error>

Execute a single statement.
§

fn execute_cached<P>(&self, sql: &str, params: P) -> Result<usize, Error>where + P: Params,

Equivalent to Connection::execute but caches the statement so that subsequent +calls to execute_cached will have improved performance.
§

fn query_one<T>(&self, sql: &str) -> Result<T, Error>where + T: FromSql,

Execute a query that returns a single result column, and return that result.
§

fn exists<P>(&self, sql: &str, params: P) -> Result<bool, Error>where + P: Params,

Return true if a query returns any rows
§

fn try_query_one<T, P>( + &self, + sql: &str, + params: P, + cache: bool +) -> Result<Option<T>, Error>where + T: FromSql, + P: Params, + Self: Sized,

Execute a query that returns 0 or 1 result columns, returning None +if there were no rows, or if the only result was NULL.
§

fn query_row_and_then_cachable<T, E, P, F>( + &self, + sql: &str, + params: P, + mapper: F, + cache: bool +) -> Result<T, E>where + Self: Sized, + P: Params, + E: From<Error>, + F: FnOnce(&Row<'_>) -> Result<T, E>,

Equivalent to rusqlite::Connection::query_row_and_then but allows +passing a flag to indicate that it’s cached.
§

fn query_rows_and_then<T, E, P, F>( + &self, + sql: &str, + params: P, + mapper: F +) -> Result<Vec<T, Global>, E>where + Self: Sized, + P: Params, + E: From<Error>, + F: FnMut(&Row<'_>) -> Result<T, E>,

Helper for when you’d like to get a Vec<T> of all the rows returned by a +query that takes named arguments. See also +query_rows_and_then_cached.
§

fn query_rows_and_then_cached<T, E, P, F>( + &self, + sql: &str, + params: P, + mapper: F +) -> Result<Vec<T, Global>, E>where + Self: Sized, + P: Params, + E: From<Error>, + F: FnMut(&Row<'_>) -> Result<T, E>,

Helper for when you’d like to get a Vec<T> of all the rows returned by a +query that takes named arguments.
§

fn query_rows_into<Coll, T, E, P, F>( + &self, + sql: &str, + params: P, + mapper: F +) -> Result<Coll, E>where + Self: Sized, + E: From<Error>, + F: FnMut(&Row<'_>) -> Result<T, E>, + Coll: FromIterator<T>, + P: Params,

Like query_rows_and_then_cachable, but works if you want a non-Vec as a result. Read more
§

fn query_rows_into_cached<Coll, T, E, P, F>( + &self, + sql: &str, + params: P, + mapper: F +) -> Result<Coll, E>where + Self: Sized, + P: Params, + E: From<Error>, + F: FnMut(&Row<'_>) -> Result<T, E>, + Coll: FromIterator<T>,

Same as query_rows_into, but caches the stmt if possible.
§

fn try_query_row<T, E, P, F>( + &self, + sql: &str, + params: P, + mapper: F, + cache: bool +) -> Result<Option<T>, E>where + Self: Sized, + P: Params, + E: From<Error>, + F: FnOnce(&Row<'_>) -> Result<T, E>,

Like query_row_and_then_cachable but returns None instead of erroring +if no such row exists.
§

fn unchecked_transaction(&self) -> Result<UncheckedTransaction<'_>, Error>

Caveat: This won’t actually get used most of the time, and calls will +usually invoke rusqlite’s method with the same name. See comment on +UncheckedTransaction for details (generally you probably don’t need to +care)
§

fn unchecked_transaction_imm(&self) -> Result<UncheckedTransaction<'_>, Error>

Begin unchecked_transaction with TransactionBehavior::Immediate. Use +when the first operation will be a read operation, that further writes +depend on for correctness.
§

fn get_db_size(&self) -> Result<u32, Error>

Get the DB size in bytes
source§

impl<'conn> Deref for PlacesTransaction<'conn>

§

type Target = Connection

The resulting type after dereferencing.
source§

fn deref(&self) -> &Connection

Dereferences the value.

Auto Trait Implementations§

§

impl<'conn> !RefUnwindSafe for PlacesTransaction<'conn>

§

impl<'conn> !Send for PlacesTransaction<'conn>

§

impl<'conn> !Sync for PlacesTransaction<'conn>

§

impl<'conn> Unpin for PlacesTransaction<'conn>

§

impl<'conn> !UnwindSafe for PlacesTransaction<'conn>

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/db/tx/struct.PlacesTransaction.html b/book/rust-docs/places/db/tx/struct.PlacesTransaction.html new file mode 100644 index 0000000000..2126cd7c7b --- /dev/null +++ b/book/rust-docs/places/db/tx/struct.PlacesTransaction.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../places/db/struct.PlacesTransaction.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/places/error/enum.Corruption.html b/book/rust-docs/places/error/enum.Corruption.html new file mode 100644 index 0000000000..77a0bd8ce4 --- /dev/null +++ b/book/rust-docs/places/error/enum.Corruption.html @@ -0,0 +1,20 @@ +Corruption in places::error - Rust

Enum places::error::Corruption

source ·
pub enum Corruption {
+    NoParent(String, String),
+    InvalidLocalRoots,
+    InvalidSyncedRoots,
+    NonRootWithoutParent(String),
+}

Variants§

§

NoParent(String, String)

§

InvalidLocalRoots

§

InvalidSyncedRoots

§

NonRootWithoutParent(String)

Trait Implementations§

source§

impl Debug for Corruption

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Display for Corruption

source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Error for Corruption

1.30.0 · source§

fn source(&self) -> Option<&(dyn Error + 'static)>

The lower-level source of this error, if any. Read more
1.0.0 · source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type based access to context intended for error reports. Read more
source§

impl From<Corruption> for Error

source§

fn from(source: Corruption) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<E> Provider for Ewhere + E: Error + ?Sized,

source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (provide_any)
Data providers should implement this method to provide all values they are able to +provide by using demand. Read more
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/error/enum.Error.html b/book/rust-docs/places/error/enum.Error.html new file mode 100644 index 0000000000..166852a3fd --- /dev/null +++ b/book/rust-docs/places/error/enum.Error.html @@ -0,0 +1,37 @@ +Error in places::error - Rust

Enum places::error::Error

source ·
pub enum Error {
+
Show 20 variants InvalidPlaceInfo(InvalidPlaceInfo), + Corruption(Corruption), + SyncAdapterError(Error), + MergeError(Error), + JsonError(Error), + SqlError(Error), + UrlParseError(ParseError), + ConnectionAlreadyOpen, + InvalidConnectionType, + IoError(Error), + InterruptedError(Interrupted), + WrongApiForClose, + MissingBookmarkKind, + UnsupportedSyncedBookmarkKind(u8), + UnsupportedSyncedBookmarkValidity(u8), + IllegalDatabasePath(PathBuf), + Utf8Error(Utf8Error), + UnsupportedDatabaseVersion(i64), + OpenDatabaseError(Error), + InvalidMetadataObservation(InvalidMetadataObservation), +
}
Expand description

Error enum used internally

+

Variants§

§

InvalidPlaceInfo(InvalidPlaceInfo)

§

Corruption(Corruption)

§

SyncAdapterError(Error)

§

MergeError(Error)

§

JsonError(Error)

§

SqlError(Error)

§

UrlParseError(ParseError)

§

ConnectionAlreadyOpen

§

InvalidConnectionType

§

IoError(Error)

§

InterruptedError(Interrupted)

§

WrongApiForClose

§

MissingBookmarkKind

§

UnsupportedSyncedBookmarkKind(u8)

§

UnsupportedSyncedBookmarkValidity(u8)

§

IllegalDatabasePath(PathBuf)

§

Utf8Error(Utf8Error)

§

UnsupportedDatabaseVersion(i64)

§

OpenDatabaseError(Error)

§

InvalidMetadataObservation(InvalidMetadataObservation)

Trait Implementations§

source§

impl Debug for Error

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Display for Error

source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Error for Error

source§

fn source(&self) -> Option<&(dyn Error + 'static)>

The lower-level source of this error, if any. Read more
1.0.0 · source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type based access to context intended for error reports. Read more
source§

impl From<Corruption> for Error

source§

fn from(source: Corruption) -> Self

Converts to this type from the input type.
source§

impl From<Error> for Error

source§

fn from(source: Error) -> Self

Converts to this type from the input type.
source§

impl From<Error> for Error

source§

fn from(source: Error) -> Self

Converts to this type from the input type.
source§

impl From<Error> for Error

source§

fn from(source: Error) -> Self

Converts to this type from the input type.
source§

impl From<Error> for Error

source§

fn from(source: Error) -> Self

Converts to this type from the input type.
source§

impl From<Error> for Error

source§

fn from(source: Error) -> Self

Converts to this type from the input type.
source§

impl From<Error> for Error

source§

fn from(source: Error) -> Self

Converts to this type from the input type.
source§

impl From<Interrupted> for Error

source§

fn from(source: Interrupted) -> Self

Converts to this type from the input type.
source§

impl From<InvalidMetadataObservation> for Error

source§

fn from(source: InvalidMetadataObservation) -> Self

Converts to this type from the input type.
source§

impl From<InvalidPlaceInfo> for Error

source§

fn from(source: InvalidPlaceInfo) -> Self

Converts to this type from the input type.
source§

impl From<ParseError> for Error

source§

fn from(source: ParseError) -> Self

Converts to this type from the input type.
source§

impl From<Utf8Error> for Error

source§

fn from(source: Utf8Error) -> Self

Converts to this type from the input type.
source§

impl GetErrorHandling for Error

§

type ExternalError = PlacesApiError

source§

fn get_error_handling(&self) -> ErrorHandling<Self::ExternalError>

Return how to handle our internal errors

Auto Trait Implementations§

§

impl !RefUnwindSafe for Error

§

impl Send for Error

§

impl Sync for Error

§

impl Unpin for Error

§

impl !UnwindSafe for Error

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<E> Provider for Ewhere + E: Error + ?Sized,

source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (provide_any)
Data providers should implement this method to provide all values they are able to +provide by using demand. Read more
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/error/enum.InvalidMetadataObservation.html b/book/rust-docs/places/error/enum.InvalidMetadataObservation.html new file mode 100644 index 0000000000..595c278415 --- /dev/null +++ b/book/rust-docs/places/error/enum.InvalidMetadataObservation.html @@ -0,0 +1,17 @@ +InvalidMetadataObservation in places::error - Rust
pub enum InvalidMetadataObservation {
+    ViewTimeTooLong,
+}

Variants§

§

ViewTimeTooLong

Trait Implementations§

source§

impl Debug for InvalidMetadataObservation

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Display for InvalidMetadataObservation

source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Error for InvalidMetadataObservation

1.30.0 · source§

fn source(&self) -> Option<&(dyn Error + 'static)>

The lower-level source of this error, if any. Read more
1.0.0 · source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type based access to context intended for error reports. Read more
source§

impl From<InvalidMetadataObservation> for Error

source§

fn from(source: InvalidMetadataObservation) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<E> Provider for Ewhere + E: Error + ?Sized,

source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (provide_any)
Data providers should implement this method to provide all values they are able to +provide by using demand. Read more
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/error/enum.InvalidPlaceInfo.html b/book/rust-docs/places/error/enum.InvalidPlaceInfo.html new file mode 100644 index 0000000000..40d3cb8fba --- /dev/null +++ b/book/rust-docs/places/error/enum.InvalidPlaceInfo.html @@ -0,0 +1,27 @@ +InvalidPlaceInfo in places::error - Rust
pub enum InvalidPlaceInfo {
+    NoUrl,
+    InvalidGuid,
+    InvalidParent(String),
+    InvalidChildGuid,
+    NoSuchGuid(String),
+    NoSuchUrl,
+    MismatchedBookmarkType(u8, u8),
+    UrlTooLong,
+    InvalidTag,
+    IllegalChange(&'static str, BookmarkType),
+    CannotUpdateRoot(BookmarkRootGuid),
+}

Variants§

§

NoUrl

§

InvalidGuid

§

InvalidParent(String)

§

InvalidChildGuid

§

NoSuchGuid(String)

§

NoSuchUrl

§

MismatchedBookmarkType(u8, u8)

§

UrlTooLong

§

InvalidTag

§

IllegalChange(&'static str, BookmarkType)

§

CannotUpdateRoot(BookmarkRootGuid)

Trait Implementations§

source§

impl Debug for InvalidPlaceInfo

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Display for InvalidPlaceInfo

source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Error for InvalidPlaceInfo

1.30.0 · source§

fn source(&self) -> Option<&(dyn Error + 'static)>

The lower-level source of this error, if any. Read more
1.0.0 · source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type based access to context intended for error reports. Read more
source§

impl From<InvalidPlaceInfo> for Error

source§

fn from(source: InvalidPlaceInfo) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<E> Provider for Ewhere + E: Error + ?Sized,

source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (provide_any)
Data providers should implement this method to provide all values they are able to +provide by using demand. Read more
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/error/enum.PlacesApiError.html b/book/rust-docs/places/error/enum.PlacesApiError.html new file mode 100644 index 0000000000..384765b29a --- /dev/null +++ b/book/rust-docs/places/error/enum.PlacesApiError.html @@ -0,0 +1,46 @@ +PlacesApiError in places::error - Rust
pub enum PlacesApiError {
+    UnexpectedPlacesException {
+        reason: String,
+    },
+    UrlParseFailed {
+        reason: String,
+    },
+    PlacesConnectionBusy {
+        reason: String,
+    },
+    OperationInterrupted {
+        reason: String,
+    },
+    UnknownBookmarkItem {
+        reason: String,
+    },
+    InvalidBookmarkOperation {
+        reason: String,
+    },
+}

Variants§

§

UnexpectedPlacesException

Fields

§reason: String
§

UrlParseFailed

Fields

§reason: String

Thrown for invalid URLs

+

This includes attempting to insert a URL greater than 65536 bytes +(after punycoding and percent encoding).

+
§

PlacesConnectionBusy

Fields

§reason: String
§

OperationInterrupted

Fields

§reason: String
§

UnknownBookmarkItem

Fields

§reason: String

Thrown when providing a guid to a create or update function +which does not refer to a known bookmark.

+
§

InvalidBookmarkOperation

Fields

§reason: String

Attempt to create/update/delete a bookmark item in an illegal way.

+

Some examples:

+
    +
  • Attempting to change the URL of a bookmark folder
  • +
  • Referring to a non-folder as the parentGUID parameter to a create or update
  • +
  • Attempting to insert a child under BookmarkRoot.Root,
  • +
+

Trait Implementations§

source§

impl Debug for PlacesApiError

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Display for PlacesApiError

source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Error for PlacesApiError

1.30.0 · source§

fn source(&self) -> Option<&(dyn Error + 'static)>

The lower-level source of this error, if any. Read more
1.0.0 · source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type based access to context intended for error reports. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<E> Provider for Ewhere + E: Error + ?Sized,

source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (provide_any)
Data providers should implement this method to provide all values they are able to +provide by using demand. Read more
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/error/index.html b/book/rust-docs/places/error/index.html new file mode 100644 index 0000000000..1c8bcc7f26 --- /dev/null +++ b/book/rust-docs/places/error/index.html @@ -0,0 +1 @@ +places::error - Rust
\ No newline at end of file diff --git a/book/rust-docs/places/error/sidebar-items.js b/book/rust-docs/places/error/sidebar-items.js new file mode 100644 index 0000000000..c9cab1dcf7 --- /dev/null +++ b/book/rust-docs/places/error/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["Corruption","Error","InvalidMetadataObservation","InvalidPlaceInfo","PlacesApiError"],"type":["ApiResult","Result"]}; \ No newline at end of file diff --git a/book/rust-docs/places/error/type.ApiResult.html b/book/rust-docs/places/error/type.ApiResult.html new file mode 100644 index 0000000000..d5fbc39855 --- /dev/null +++ b/book/rust-docs/places/error/type.ApiResult.html @@ -0,0 +1 @@ +ApiResult in places::error - Rust

Type Definition places::error::ApiResult

source ·
pub type ApiResult<T> = Result<T, PlacesApiError>;
\ No newline at end of file diff --git a/book/rust-docs/places/error/type.Result.html b/book/rust-docs/places/error/type.Result.html new file mode 100644 index 0000000000..d9f5fbf81d --- /dev/null +++ b/book/rust-docs/places/error/type.Result.html @@ -0,0 +1 @@ +Result in places::error - Rust

Type Definition places::error::Result

source ·
pub type Result<T> = Result<T, Error>;
\ No newline at end of file diff --git a/book/rust-docs/places/ffi/enum.FrecencyThresholdOption.html b/book/rust-docs/places/ffi/enum.FrecencyThresholdOption.html new file mode 100644 index 0000000000..8bc9328d5b --- /dev/null +++ b/book/rust-docs/places/ffi/enum.FrecencyThresholdOption.html @@ -0,0 +1,15 @@ +FrecencyThresholdOption in places::ffi - Rust
pub enum FrecencyThresholdOption {
+    None,
+    SkipOneTimePages,
+}

Variants§

§

None

§

SkipOneTimePages

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/ffi/index.html b/book/rust-docs/places/ffi/index.html new file mode 100644 index 0000000000..7a4750b05e --- /dev/null +++ b/book/rust-docs/places/ffi/index.html @@ -0,0 +1,2 @@ +places::ffi - Rust

Module places::ffi

source ·

Re-exports

Structs

Enums

Type Definitions

\ No newline at end of file diff --git a/book/rust-docs/places/ffi/sidebar-items.js b/book/rust-docs/places/ffi/sidebar-items.js new file mode 100644 index 0000000000..e2fe28dda6 --- /dev/null +++ b/book/rust-docs/places/ffi/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["FrecencyThresholdOption"],"struct":["Dummy","Guid","HistoryVisitInfo","HistoryVisitInfosWithBound","PlacesConnection","PlacesTimestamp","SearchResult","SqlInterruptHandle","TopFrecentSiteInfo","Url"],"type":["BookmarkFolder","BookmarkItem","BookmarkSeparator","InsertableBookmarkFolder","InsertableBookmarkItem","InsertableBookmarkSeparator"]}; \ No newline at end of file diff --git a/book/rust-docs/places/ffi/struct.Dummy.html b/book/rust-docs/places/ffi/struct.Dummy.html new file mode 100644 index 0000000000..f13c73281b --- /dev/null +++ b/book/rust-docs/places/ffi/struct.Dummy.html @@ -0,0 +1,14 @@ +Dummy in places::ffi - Rust

Struct places::ffi::Dummy

source ·
pub struct Dummy {
+    pub md: Option<Vec<HistoryMetadata>>,
+}

Fields§

§md: Option<Vec<HistoryMetadata>>

Auto Trait Implementations§

§

impl RefUnwindSafe for Dummy

§

impl Send for Dummy

§

impl Sync for Dummy

§

impl Unpin for Dummy

§

impl UnwindSafe for Dummy

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/ffi/struct.Guid.html b/book/rust-docs/places/ffi/struct.Guid.html new file mode 100644 index 0000000000..7e799dd34e --- /dev/null +++ b/book/rust-docs/places/ffi/struct.Guid.html @@ -0,0 +1,1274 @@ +Guid in places::ffi - Rust

Struct places::ffi::Guid

pub struct Guid(_);
Expand description

This is a type intended to be used to represent the guids used by sync. It +has several benefits over using a String:

+
    +
  1. +

    It’s more explicit about what is being stored, and could prevent bugs +where a Guid is passed to a function expecting text.

    +
  2. +
  3. +

    Guids are guaranteed to be immutable.

    +
  4. +
  5. +

    It’s optimized for the guids commonly used by sync. In particular, short guids +(including the guids which would meet PlacesUtils.isValidGuid) do not incur +any heap allocation, and are stored inline.

    +
  6. +
+

Implementations§

§

impl Guid

pub fn new(s: &str) -> Guid

Create a guid from a str.

+

pub const fn empty() -> Guid

Create an empty guid. Usable as a constant.

+

pub fn random() -> Guid

Create a random guid (of 12 base64url characters). Requires the random +feature.

+

pub fn from_string(s: String) -> Guid

Convert b into a Guid.

+

pub fn from_slice(b: &[u8]) -> Guid

Convert b into a Guid.

+

pub fn from_vec(v: Vec<u8, Global>) -> Guid

Convert v to a Guid, consuming it.

+

pub fn as_bytes(&self) -> &[u8]

Get the data backing this Guid as a &[u8].

+

pub fn as_str(&self) -> &str

Get the data backing this Guid as a &str.

+

pub fn into_string(self) -> String

Convert this Guid into a String, consuming it in the process.

+

pub fn is_valid_for_sync_server(&self) -> bool

Returns true for Guids that are deemed valid by the sync server. +See https://github.com/mozilla-services/server-syncstorage/blob/d92ef07877aebd05b92f87f6ade341d6a55bffc8/syncstorage/bso.py#L24

+

pub fn is_valid_for_places(&self) -> bool

Returns true for Guids that are valid places guids, and false for all others.

+

pub fn is_valid_places_byte(b: u8) -> bool

Returns true if the byte b is a valid base64url byte.

+

Methods from Deref<Target = str>§

1.0.0 · source

pub fn len(&self) -> usize

Returns the length of self.

+

This length is in bytes, not chars or graphemes. In other words, +it might not be what a human considers the length of the string.

+
Examples
+
let len = "foo".len();
+assert_eq!(3, len);
+
+assert_eq!("ƒoo".len(), 4); // fancy f!
+assert_eq!("ƒoo".chars().count(), 3);
+
1.0.0 · source

pub fn is_empty(&self) -> bool

Returns true if self has a length of zero bytes.

+
Examples
+
let s = "";
+assert!(s.is_empty());
+
+let s = "not empty";
+assert!(!s.is_empty());
+
1.9.0 · source

pub fn is_char_boundary(&self, index: usize) -> bool

Checks that index-th byte is the first byte in a UTF-8 code point +sequence or the end of the string.

+

The start and end of the string (when index == self.len()) are +considered to be boundaries.

+

Returns false if index is greater than self.len().

+
Examples
+
let s = "Löwe 老虎 Léopard";
+assert!(s.is_char_boundary(0));
+// start of `老`
+assert!(s.is_char_boundary(6));
+assert!(s.is_char_boundary(s.len()));
+
+// second byte of `ö`
+assert!(!s.is_char_boundary(2));
+
+// third byte of `老`
+assert!(!s.is_char_boundary(8));
+
source

pub fn floor_char_boundary(&self, index: usize) -> usize

🔬This is a nightly-only experimental API. (round_char_boundary)

Finds the closest x not exceeding index where is_char_boundary(x) is true.

+

This method can help you truncate a string so that it’s still valid UTF-8, but doesn’t +exceed a given number of bytes. Note that this is done purely at the character level +and can still visually split graphemes, even though the underlying characters aren’t +split. For example, the emoji 🧑‍🔬 (scientist) could be split so that the string only +includes 🧑 (person) instead.

+
Examples
+
#![feature(round_char_boundary)]
+let s = "❤️🧡💛💚💙💜";
+assert_eq!(s.len(), 26);
+assert!(!s.is_char_boundary(13));
+
+let closest = s.floor_char_boundary(13);
+assert_eq!(closest, 10);
+assert_eq!(&s[..closest], "❤️🧡");
+
source

pub fn ceil_char_boundary(&self, index: usize) -> usize

🔬This is a nightly-only experimental API. (round_char_boundary)

Finds the closest x not below index where is_char_boundary(x) is true.

+

This method is the natural complement to floor_char_boundary. See that method +for more details.

+
Panics
+

Panics if index > self.len().

+
Examples
+
#![feature(round_char_boundary)]
+let s = "❤️🧡💛💚💙💜";
+assert_eq!(s.len(), 26);
+assert!(!s.is_char_boundary(13));
+
+let closest = s.ceil_char_boundary(13);
+assert_eq!(closest, 14);
+assert_eq!(&s[..closest], "❤️🧡💛");
+
1.0.0 · source

pub fn as_bytes(&self) -> &[u8]

Converts a string slice to a byte slice. To convert the byte slice back +into a string slice, use the from_utf8 function.

+
Examples
+
let bytes = "bors".as_bytes();
+assert_eq!(b"bors", bytes);
+
1.0.0 · source

pub fn as_ptr(&self) -> *const u8

Converts a string slice to a raw pointer.

+

As string slices are a slice of bytes, the raw pointer points to a +u8. This pointer will be pointing to the first byte of the string +slice.

+

The caller must ensure that the returned pointer is never written to. +If you need to mutate the contents of the string slice, use as_mut_ptr.

+
Examples
+
let s = "Hello";
+let ptr = s.as_ptr();
+
1.20.0 · source

pub fn get<I>(&self, i: I) -> Option<&<I as SliceIndex<str>>::Output>where + I: SliceIndex<str>,

Returns a subslice of str.

+

This is the non-panicking alternative to indexing the str. Returns +None whenever equivalent indexing operation would panic.

+
Examples
+
let v = String::from("🗻∈🌏");
+
+assert_eq!(Some("🗻"), v.get(0..4));
+
+// indices not on UTF-8 sequence boundaries
+assert!(v.get(1..).is_none());
+assert!(v.get(..8).is_none());
+
+// out of bounds
+assert!(v.get(..42).is_none());
+
1.20.0 · source

pub unsafe fn get_unchecked<I>(&self, i: I) -> &<I as SliceIndex<str>>::Outputwhere + I: SliceIndex<str>,

Returns an unchecked subslice of str.

+

This is the unchecked alternative to indexing the str.

+
Safety
+

Callers of this function are responsible that these preconditions are +satisfied:

+
    +
  • The starting index must not exceed the ending index;
  • +
  • Indexes must be within bounds of the original slice;
  • +
  • Indexes must lie on UTF-8 sequence boundaries.
  • +
+

Failing that, the returned string slice may reference invalid memory or +violate the invariants communicated by the str type.

+
Examples
+
let v = "🗻∈🌏";
+unsafe {
+    assert_eq!("🗻", v.get_unchecked(0..4));
+    assert_eq!("∈", v.get_unchecked(4..7));
+    assert_eq!("🌏", v.get_unchecked(7..11));
+}
+
1.0.0 · source

pub unsafe fn slice_unchecked(&self, begin: usize, end: usize) -> &str

👎Deprecated since 1.29.0: use get_unchecked(begin..end) instead

Creates a string slice from another string slice, bypassing safety +checks.

+

This is generally not recommended, use with caution! For a safe +alternative see str and Index.

+

This new slice goes from begin to end, including begin but +excluding end.

+

To get a mutable string slice instead, see the +slice_mut_unchecked method.

+
Safety
+

Callers of this function are responsible that three preconditions are +satisfied:

+
    +
  • begin must not exceed end.
  • +
  • begin and end must be byte positions within the string slice.
  • +
  • begin and end must lie on UTF-8 sequence boundaries.
  • +
+
Examples
+
let s = "Löwe 老虎 Léopard";
+
+unsafe {
+    assert_eq!("Löwe 老虎 Léopard", s.slice_unchecked(0, 21));
+}
+
+let s = "Hello, world!";
+
+unsafe {
+    assert_eq!("world", s.slice_unchecked(7, 12));
+}
+
1.4.0 · source

pub fn split_at(&self, mid: usize) -> (&str, &str)

Divide one string slice into two at an index.

+

The argument, mid, should be a byte offset from the start of the +string. It must also be on the boundary of a UTF-8 code point.

+

The two slices returned go from the start of the string slice to mid, +and from mid to the end of the string slice.

+

To get mutable string slices instead, see the split_at_mut +method.

+
Panics
+

Panics if mid is not on a UTF-8 code point boundary, or if it is +past the end of the last code point of the string slice.

+
Examples
+
let s = "Per Martin-Löf";
+
+let (first, last) = s.split_at(3);
+
+assert_eq!("Per", first);
+assert_eq!(" Martin-Löf", last);
+
1.0.0 · source

pub fn chars(&self) -> Chars<'_>

Returns an iterator over the chars of a string slice.

+

As a string slice consists of valid UTF-8, we can iterate through a +string slice by char. This method returns such an iterator.

+

It’s important to remember that char represents a Unicode Scalar +Value, and might not match your idea of what a ‘character’ is. Iteration +over grapheme clusters may be what you actually want. This functionality +is not provided by Rust’s standard library, check crates.io instead.

+
Examples
+

Basic usage:

+ +
let word = "goodbye";
+
+let count = word.chars().count();
+assert_eq!(7, count);
+
+let mut chars = word.chars();
+
+assert_eq!(Some('g'), chars.next());
+assert_eq!(Some('o'), chars.next());
+assert_eq!(Some('o'), chars.next());
+assert_eq!(Some('d'), chars.next());
+assert_eq!(Some('b'), chars.next());
+assert_eq!(Some('y'), chars.next());
+assert_eq!(Some('e'), chars.next());
+
+assert_eq!(None, chars.next());
+

Remember, chars might not match your intuition about characters:

+ +
let y = "y̆";
+
+let mut chars = y.chars();
+
+assert_eq!(Some('y'), chars.next()); // not 'y̆'
+assert_eq!(Some('\u{0306}'), chars.next());
+
+assert_eq!(None, chars.next());
+
1.0.0 · source

pub fn char_indices(&self) -> CharIndices<'_>

Returns an iterator over the chars of a string slice, and their +positions.

+

As a string slice consists of valid UTF-8, we can iterate through a +string slice by char. This method returns an iterator of both +these chars, as well as their byte positions.

+

The iterator yields tuples. The position is first, the char is +second.

+
Examples
+

Basic usage:

+ +
let word = "goodbye";
+
+let count = word.char_indices().count();
+assert_eq!(7, count);
+
+let mut char_indices = word.char_indices();
+
+assert_eq!(Some((0, 'g')), char_indices.next());
+assert_eq!(Some((1, 'o')), char_indices.next());
+assert_eq!(Some((2, 'o')), char_indices.next());
+assert_eq!(Some((3, 'd')), char_indices.next());
+assert_eq!(Some((4, 'b')), char_indices.next());
+assert_eq!(Some((5, 'y')), char_indices.next());
+assert_eq!(Some((6, 'e')), char_indices.next());
+
+assert_eq!(None, char_indices.next());
+

Remember, chars might not match your intuition about characters:

+ +
let yes = "y̆es";
+
+let mut char_indices = yes.char_indices();
+
+assert_eq!(Some((0, 'y')), char_indices.next()); // not (0, 'y̆')
+assert_eq!(Some((1, '\u{0306}')), char_indices.next());
+
+// note the 3 here - the last character took up two bytes
+assert_eq!(Some((3, 'e')), char_indices.next());
+assert_eq!(Some((4, 's')), char_indices.next());
+
+assert_eq!(None, char_indices.next());
+
1.0.0 · source

pub fn bytes(&self) -> Bytes<'_>

An iterator over the bytes of a string slice.

+

As a string slice consists of a sequence of bytes, we can iterate +through a string slice by byte. This method returns such an iterator.

+
Examples
+
let mut bytes = "bors".bytes();
+
+assert_eq!(Some(b'b'), bytes.next());
+assert_eq!(Some(b'o'), bytes.next());
+assert_eq!(Some(b'r'), bytes.next());
+assert_eq!(Some(b's'), bytes.next());
+
+assert_eq!(None, bytes.next());
+
1.1.0 · source

pub fn split_whitespace(&self) -> SplitWhitespace<'_>

Splits a string slice by whitespace.

+

The iterator returned will return string slices that are sub-slices of +the original string slice, separated by any amount of whitespace.

+

‘Whitespace’ is defined according to the terms of the Unicode Derived +Core Property White_Space. If you only want to split on ASCII whitespace +instead, use split_ascii_whitespace.

+
Examples
+

Basic usage:

+ +
let mut iter = "A few words".split_whitespace();
+
+assert_eq!(Some("A"), iter.next());
+assert_eq!(Some("few"), iter.next());
+assert_eq!(Some("words"), iter.next());
+
+assert_eq!(None, iter.next());
+

All kinds of whitespace are considered:

+ +
let mut iter = " Mary   had\ta\u{2009}little  \n\t lamb".split_whitespace();
+assert_eq!(Some("Mary"), iter.next());
+assert_eq!(Some("had"), iter.next());
+assert_eq!(Some("a"), iter.next());
+assert_eq!(Some("little"), iter.next());
+assert_eq!(Some("lamb"), iter.next());
+
+assert_eq!(None, iter.next());
+

If the string is empty or all whitespace, the iterator yields no string slices:

+ +
assert_eq!("".split_whitespace().next(), None);
+assert_eq!("   ".split_whitespace().next(), None);
+
1.34.0 · source

pub fn split_ascii_whitespace(&self) -> SplitAsciiWhitespace<'_>

Splits a string slice by ASCII whitespace.

+

The iterator returned will return string slices that are sub-slices of +the original string slice, separated by any amount of ASCII whitespace.

+

To split by Unicode Whitespace instead, use split_whitespace.

+
Examples
+

Basic usage:

+ +
let mut iter = "A few words".split_ascii_whitespace();
+
+assert_eq!(Some("A"), iter.next());
+assert_eq!(Some("few"), iter.next());
+assert_eq!(Some("words"), iter.next());
+
+assert_eq!(None, iter.next());
+

All kinds of ASCII whitespace are considered:

+ +
let mut iter = " Mary   had\ta little  \n\t lamb".split_ascii_whitespace();
+assert_eq!(Some("Mary"), iter.next());
+assert_eq!(Some("had"), iter.next());
+assert_eq!(Some("a"), iter.next());
+assert_eq!(Some("little"), iter.next());
+assert_eq!(Some("lamb"), iter.next());
+
+assert_eq!(None, iter.next());
+

If the string is empty or all ASCII whitespace, the iterator yields no string slices:

+ +
assert_eq!("".split_ascii_whitespace().next(), None);
+assert_eq!("   ".split_ascii_whitespace().next(), None);
+
1.0.0 · source

pub fn lines(&self) -> Lines<'_>

An iterator over the lines of a string, as string slices.

+

Lines are split at line endings that are either newlines (\n) or +sequences of a carriage return followed by a line feed (\r\n).

+

Line terminators are not included in the lines returned by the iterator.

+

The final line ending is optional. A string that ends with a final line +ending will return the same lines as an otherwise identical string +without a final line ending.

+
Examples
+

Basic usage:

+ +
let text = "foo\r\nbar\n\nbaz\n";
+let mut lines = text.lines();
+
+assert_eq!(Some("foo"), lines.next());
+assert_eq!(Some("bar"), lines.next());
+assert_eq!(Some(""), lines.next());
+assert_eq!(Some("baz"), lines.next());
+
+assert_eq!(None, lines.next());
+

The final line ending isn’t required:

+ +
let text = "foo\nbar\n\r\nbaz";
+let mut lines = text.lines();
+
+assert_eq!(Some("foo"), lines.next());
+assert_eq!(Some("bar"), lines.next());
+assert_eq!(Some(""), lines.next());
+assert_eq!(Some("baz"), lines.next());
+
+assert_eq!(None, lines.next());
+
1.0.0 · source

pub fn lines_any(&self) -> LinesAny<'_>

👎Deprecated since 1.4.0: use lines() instead now

An iterator over the lines of a string.

+
1.8.0 · source

pub fn encode_utf16(&self) -> EncodeUtf16<'_>

Returns an iterator of u16 over the string encoded as UTF-16.

+
Examples
+
let text = "Zażółć gęślą jaźń";
+
+let utf8_len = text.len();
+let utf16_len = text.encode_utf16().count();
+
+assert!(utf16_len <= utf8_len);
+
1.0.0 · source

pub fn contains<'a, P>(&'a self, pat: P) -> boolwhere + P: Pattern<'a>,

Returns true if the given pattern matches a sub-slice of +this string slice.

+

Returns false if it does not.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Examples
+
let bananas = "bananas";
+
+assert!(bananas.contains("nana"));
+assert!(!bananas.contains("apples"));
+
1.0.0 · source

pub fn starts_with<'a, P>(&'a self, pat: P) -> boolwhere + P: Pattern<'a>,

Returns true if the given pattern matches a prefix of this +string slice.

+

Returns false if it does not.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Examples
+
let bananas = "bananas";
+
+assert!(bananas.starts_with("bana"));
+assert!(!bananas.starts_with("nana"));
+
1.0.0 · source

pub fn ends_with<'a, P>(&'a self, pat: P) -> boolwhere + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,

Returns true if the given pattern matches a suffix of this +string slice.

+

Returns false if it does not.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Examples
+
let bananas = "bananas";
+
+assert!(bananas.ends_with("anas"));
+assert!(!bananas.ends_with("nana"));
+
1.0.0 · source

pub fn find<'a, P>(&'a self, pat: P) -> Option<usize>where + P: Pattern<'a>,

Returns the byte index of the first character of this string slice that +matches the pattern.

+

Returns None if the pattern doesn’t match.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Examples
+

Simple patterns:

+ +
let s = "Löwe 老虎 Léopard Gepardi";
+
+assert_eq!(s.find('L'), Some(0));
+assert_eq!(s.find('é'), Some(14));
+assert_eq!(s.find("pard"), Some(17));
+

More complex patterns using point-free style and closures:

+ +
let s = "Löwe 老虎 Léopard";
+
+assert_eq!(s.find(char::is_whitespace), Some(5));
+assert_eq!(s.find(char::is_lowercase), Some(1));
+assert_eq!(s.find(|c: char| c.is_whitespace() || c.is_lowercase()), Some(1));
+assert_eq!(s.find(|c: char| (c < 'o') && (c > 'a')), Some(4));
+

Not finding the pattern:

+ +
let s = "Löwe 老虎 Léopard";
+let x: &[_] = &['1', '2'];
+
+assert_eq!(s.find(x), None);
+
1.0.0 · source

pub fn rfind<'a, P>(&'a self, pat: P) -> Option<usize>where + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,

Returns the byte index for the first character of the last match of the pattern in +this string slice.

+

Returns None if the pattern doesn’t match.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Examples
+

Simple patterns:

+ +
let s = "Löwe 老虎 Léopard Gepardi";
+
+assert_eq!(s.rfind('L'), Some(13));
+assert_eq!(s.rfind('é'), Some(14));
+assert_eq!(s.rfind("pard"), Some(24));
+

More complex patterns with closures:

+ +
let s = "Löwe 老虎 Léopard";
+
+assert_eq!(s.rfind(char::is_whitespace), Some(12));
+assert_eq!(s.rfind(char::is_lowercase), Some(20));
+

Not finding the pattern:

+ +
let s = "Löwe 老虎 Léopard";
+let x: &[_] = &['1', '2'];
+
+assert_eq!(s.rfind(x), None);
+
1.0.0 · source

pub fn split<'a, P>(&'a self, pat: P) -> Split<'a, P>where + P: Pattern<'a>,

An iterator over substrings of this string slice, separated by +characters matched by a pattern.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Iterator behavior
+

The returned iterator will be a DoubleEndedIterator if the pattern +allows a reverse search and forward/reverse search yields the same +elements. This is true for, e.g., char, but not for &str.

+

If the pattern allows a reverse search but its results might differ +from a forward search, the rsplit method can be used.

+
Examples
+

Simple patterns:

+ +
let v: Vec<&str> = "Mary had a little lamb".split(' ').collect();
+assert_eq!(v, ["Mary", "had", "a", "little", "lamb"]);
+
+let v: Vec<&str> = "".split('X').collect();
+assert_eq!(v, [""]);
+
+let v: Vec<&str> = "lionXXtigerXleopard".split('X').collect();
+assert_eq!(v, ["lion", "", "tiger", "leopard"]);
+
+let v: Vec<&str> = "lion::tiger::leopard".split("::").collect();
+assert_eq!(v, ["lion", "tiger", "leopard"]);
+
+let v: Vec<&str> = "abc1def2ghi".split(char::is_numeric).collect();
+assert_eq!(v, ["abc", "def", "ghi"]);
+
+let v: Vec<&str> = "lionXtigerXleopard".split(char::is_uppercase).collect();
+assert_eq!(v, ["lion", "tiger", "leopard"]);
+

If the pattern is a slice of chars, split on each occurrence of any of the characters:

+ +
let v: Vec<&str> = "2020-11-03 23:59".split(&['-', ' ', ':', '@'][..]).collect();
+assert_eq!(v, ["2020", "11", "03", "23", "59"]);
+

A more complex pattern, using a closure:

+ +
let v: Vec<&str> = "abc1defXghi".split(|c| c == '1' || c == 'X').collect();
+assert_eq!(v, ["abc", "def", "ghi"]);
+

If a string contains multiple contiguous separators, you will end up +with empty strings in the output:

+ +
let x = "||||a||b|c".to_string();
+let d: Vec<_> = x.split('|').collect();
+
+assert_eq!(d, &["", "", "", "", "a", "", "b", "c"]);
+

Contiguous separators are separated by the empty string.

+ +
let x = "(///)".to_string();
+let d: Vec<_> = x.split('/').collect();
+
+assert_eq!(d, &["(", "", "", ")"]);
+

Separators at the start or end of a string are neighbored +by empty strings.

+ +
let d: Vec<_> = "010".split("0").collect();
+assert_eq!(d, &["", "1", ""]);
+

When the empty string is used as a separator, it separates +every character in the string, along with the beginning +and end of the string.

+ +
let f: Vec<_> = "rust".split("").collect();
+assert_eq!(f, &["", "r", "u", "s", "t", ""]);
+

Contiguous separators can lead to possibly surprising behavior +when whitespace is used as the separator. This code is correct:

+ +
let x = "    a  b c".to_string();
+let d: Vec<_> = x.split(' ').collect();
+
+assert_eq!(d, &["", "", "", "", "a", "", "b", "c"]);
+

It does not give you:

+ +
assert_eq!(d, &["a", "b", "c"]);
+

Use split_whitespace for this behavior.

+
1.51.0 · source

pub fn split_inclusive<'a, P>(&'a self, pat: P) -> SplitInclusive<'a, P>where + P: Pattern<'a>,

An iterator over substrings of this string slice, separated by +characters matched by a pattern. Differs from the iterator produced by +split in that split_inclusive leaves the matched part as the +terminator of the substring.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Examples
+
let v: Vec<&str> = "Mary had a little lamb\nlittle lamb\nlittle lamb."
+    .split_inclusive('\n').collect();
+assert_eq!(v, ["Mary had a little lamb\n", "little lamb\n", "little lamb."]);
+

If the last element of the string is matched, +that element will be considered the terminator of the preceding substring. +That substring will be the last item returned by the iterator.

+ +
let v: Vec<&str> = "Mary had a little lamb\nlittle lamb\nlittle lamb.\n"
+    .split_inclusive('\n').collect();
+assert_eq!(v, ["Mary had a little lamb\n", "little lamb\n", "little lamb.\n"]);
+
1.0.0 · source

pub fn rsplit<'a, P>(&'a self, pat: P) -> RSplit<'a, P>where + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,

An iterator over substrings of the given string slice, separated by +characters matched by a pattern and yielded in reverse order.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Iterator behavior
+

The returned iterator requires that the pattern supports a reverse +search, and it will be a DoubleEndedIterator if a forward/reverse +search yields the same elements.

+

For iterating from the front, the split method can be used.

+
Examples
+

Simple patterns:

+ +
let v: Vec<&str> = "Mary had a little lamb".rsplit(' ').collect();
+assert_eq!(v, ["lamb", "little", "a", "had", "Mary"]);
+
+let v: Vec<&str> = "".rsplit('X').collect();
+assert_eq!(v, [""]);
+
+let v: Vec<&str> = "lionXXtigerXleopard".rsplit('X').collect();
+assert_eq!(v, ["leopard", "tiger", "", "lion"]);
+
+let v: Vec<&str> = "lion::tiger::leopard".rsplit("::").collect();
+assert_eq!(v, ["leopard", "tiger", "lion"]);
+

A more complex pattern, using a closure:

+ +
let v: Vec<&str> = "abc1defXghi".rsplit(|c| c == '1' || c == 'X').collect();
+assert_eq!(v, ["ghi", "def", "abc"]);
+
1.0.0 · source

pub fn split_terminator<'a, P>(&'a self, pat: P) -> SplitTerminator<'a, P>where + P: Pattern<'a>,

An iterator over substrings of the given string slice, separated by +characters matched by a pattern.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+

Equivalent to split, except that the trailing substring +is skipped if empty.

+

This method can be used for string data that is terminated, +rather than separated by a pattern.

+
Iterator behavior
+

The returned iterator will be a DoubleEndedIterator if the pattern +allows a reverse search and forward/reverse search yields the same +elements. This is true for, e.g., char, but not for &str.

+

If the pattern allows a reverse search but its results might differ +from a forward search, the rsplit_terminator method can be used.

+
Examples
+
let v: Vec<&str> = "A.B.".split_terminator('.').collect();
+assert_eq!(v, ["A", "B"]);
+
+let v: Vec<&str> = "A..B..".split_terminator(".").collect();
+assert_eq!(v, ["A", "", "B", ""]);
+
+let v: Vec<&str> = "A.B:C.D".split_terminator(&['.', ':'][..]).collect();
+assert_eq!(v, ["A", "B", "C", "D"]);
+
1.0.0 · source

pub fn rsplit_terminator<'a, P>(&'a self, pat: P) -> RSplitTerminator<'a, P>where + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,

An iterator over substrings of self, separated by characters +matched by a pattern and yielded in reverse order.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+

Equivalent to split, except that the trailing substring is +skipped if empty.

+

This method can be used for string data that is terminated, +rather than separated by a pattern.

+
Iterator behavior
+

The returned iterator requires that the pattern supports a +reverse search, and it will be double ended if a forward/reverse +search yields the same elements.

+

For iterating from the front, the split_terminator method can be +used.

+
Examples
+
let v: Vec<&str> = "A.B.".rsplit_terminator('.').collect();
+assert_eq!(v, ["B", "A"]);
+
+let v: Vec<&str> = "A..B..".rsplit_terminator(".").collect();
+assert_eq!(v, ["", "B", "", "A"]);
+
+let v: Vec<&str> = "A.B:C.D".rsplit_terminator(&['.', ':'][..]).collect();
+assert_eq!(v, ["D", "C", "B", "A"]);
+
1.0.0 · source

pub fn splitn<'a, P>(&'a self, n: usize, pat: P) -> SplitN<'a, P>where + P: Pattern<'a>,

An iterator over substrings of the given string slice, separated by a +pattern, restricted to returning at most n items.

+

If n substrings are returned, the last substring (the nth substring) +will contain the remainder of the string.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Iterator behavior
+

The returned iterator will not be double ended, because it is +not efficient to support.

+

If the pattern allows a reverse search, the rsplitn method can be +used.

+
Examples
+

Simple patterns:

+ +
let v: Vec<&str> = "Mary had a little lambda".splitn(3, ' ').collect();
+assert_eq!(v, ["Mary", "had", "a little lambda"]);
+
+let v: Vec<&str> = "lionXXtigerXleopard".splitn(3, "X").collect();
+assert_eq!(v, ["lion", "", "tigerXleopard"]);
+
+let v: Vec<&str> = "abcXdef".splitn(1, 'X').collect();
+assert_eq!(v, ["abcXdef"]);
+
+let v: Vec<&str> = "".splitn(1, 'X').collect();
+assert_eq!(v, [""]);
+

A more complex pattern, using a closure:

+ +
let v: Vec<&str> = "abc1defXghi".splitn(2, |c| c == '1' || c == 'X').collect();
+assert_eq!(v, ["abc", "defXghi"]);
+
1.0.0 · source

pub fn rsplitn<'a, P>(&'a self, n: usize, pat: P) -> RSplitN<'a, P>where + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,

An iterator over substrings of this string slice, separated by a +pattern, starting from the end of the string, restricted to returning +at most n items.

+

If n substrings are returned, the last substring (the nth substring) +will contain the remainder of the string.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Iterator behavior
+

The returned iterator will not be double ended, because it is not +efficient to support.

+

For splitting from the front, the splitn method can be used.

+
Examples
+

Simple patterns:

+ +
let v: Vec<&str> = "Mary had a little lamb".rsplitn(3, ' ').collect();
+assert_eq!(v, ["lamb", "little", "Mary had a"]);
+
+let v: Vec<&str> = "lionXXtigerXleopard".rsplitn(3, 'X').collect();
+assert_eq!(v, ["leopard", "tiger", "lionX"]);
+
+let v: Vec<&str> = "lion::tiger::leopard".rsplitn(2, "::").collect();
+assert_eq!(v, ["leopard", "lion::tiger"]);
+

A more complex pattern, using a closure:

+ +
let v: Vec<&str> = "abc1defXghi".rsplitn(2, |c| c == '1' || c == 'X').collect();
+assert_eq!(v, ["ghi", "abc1def"]);
+
1.52.0 · source

pub fn split_once<'a, P>(&'a self, delimiter: P) -> Option<(&'a str, &'a str)>where + P: Pattern<'a>,

Splits the string on the first occurrence of the specified delimiter and +returns prefix before delimiter and suffix after delimiter.

+
Examples
+
assert_eq!("cfg".split_once('='), None);
+assert_eq!("cfg=".split_once('='), Some(("cfg", "")));
+assert_eq!("cfg=foo".split_once('='), Some(("cfg", "foo")));
+assert_eq!("cfg=foo=bar".split_once('='), Some(("cfg", "foo=bar")));
+
1.52.0 · source

pub fn rsplit_once<'a, P>(&'a self, delimiter: P) -> Option<(&'a str, &'a str)>where + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,

Splits the string on the last occurrence of the specified delimiter and +returns prefix before delimiter and suffix after delimiter.

+
Examples
+
assert_eq!("cfg".rsplit_once('='), None);
+assert_eq!("cfg=foo".rsplit_once('='), Some(("cfg", "foo")));
+assert_eq!("cfg=foo=bar".rsplit_once('='), Some(("cfg=foo", "bar")));
+
1.2.0 · source

pub fn matches<'a, P>(&'a self, pat: P) -> Matches<'a, P>where + P: Pattern<'a>,

An iterator over the disjoint matches of a pattern within the given string +slice.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Iterator behavior
+

The returned iterator will be a DoubleEndedIterator if the pattern +allows a reverse search and forward/reverse search yields the same +elements. This is true for, e.g., char, but not for &str.

+

If the pattern allows a reverse search but its results might differ +from a forward search, the rmatches method can be used.

+
Examples
+
let v: Vec<&str> = "abcXXXabcYYYabc".matches("abc").collect();
+assert_eq!(v, ["abc", "abc", "abc"]);
+
+let v: Vec<&str> = "1abc2abc3".matches(char::is_numeric).collect();
+assert_eq!(v, ["1", "2", "3"]);
+
1.2.0 · source

pub fn rmatches<'a, P>(&'a self, pat: P) -> RMatches<'a, P>where + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,

An iterator over the disjoint matches of a pattern within this string slice, +yielded in reverse order.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Iterator behavior
+

The returned iterator requires that the pattern supports a reverse +search, and it will be a DoubleEndedIterator if a forward/reverse +search yields the same elements.

+

For iterating from the front, the matches method can be used.

+
Examples
+
let v: Vec<&str> = "abcXXXabcYYYabc".rmatches("abc").collect();
+assert_eq!(v, ["abc", "abc", "abc"]);
+
+let v: Vec<&str> = "1abc2abc3".rmatches(char::is_numeric).collect();
+assert_eq!(v, ["3", "2", "1"]);
+
1.5.0 · source

pub fn match_indices<'a, P>(&'a self, pat: P) -> MatchIndices<'a, P>where + P: Pattern<'a>,

An iterator over the disjoint matches of a pattern within this string +slice as well as the index that the match starts at.

+

For matches of pat within self that overlap, only the indices +corresponding to the first match are returned.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Iterator behavior
+

The returned iterator will be a DoubleEndedIterator if the pattern +allows a reverse search and forward/reverse search yields the same +elements. This is true for, e.g., char, but not for &str.

+

If the pattern allows a reverse search but its results might differ +from a forward search, the rmatch_indices method can be used.

+
Examples
+
let v: Vec<_> = "abcXXXabcYYYabc".match_indices("abc").collect();
+assert_eq!(v, [(0, "abc"), (6, "abc"), (12, "abc")]);
+
+let v: Vec<_> = "1abcabc2".match_indices("abc").collect();
+assert_eq!(v, [(1, "abc"), (4, "abc")]);
+
+let v: Vec<_> = "ababa".match_indices("aba").collect();
+assert_eq!(v, [(0, "aba")]); // only the first `aba`
+
1.5.0 · source

pub fn rmatch_indices<'a, P>(&'a self, pat: P) -> RMatchIndices<'a, P>where + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,

An iterator over the disjoint matches of a pattern within self, +yielded in reverse order along with the index of the match.

+

For matches of pat within self that overlap, only the indices +corresponding to the last match are returned.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Iterator behavior
+

The returned iterator requires that the pattern supports a reverse +search, and it will be a DoubleEndedIterator if a forward/reverse +search yields the same elements.

+

For iterating from the front, the match_indices method can be used.

+
Examples
+
let v: Vec<_> = "abcXXXabcYYYabc".rmatch_indices("abc").collect();
+assert_eq!(v, [(12, "abc"), (6, "abc"), (0, "abc")]);
+
+let v: Vec<_> = "1abcabc2".rmatch_indices("abc").collect();
+assert_eq!(v, [(4, "abc"), (1, "abc")]);
+
+let v: Vec<_> = "ababa".rmatch_indices("aba").collect();
+assert_eq!(v, [(2, "aba")]); // only the last `aba`
+
1.0.0 · source

pub fn trim(&self) -> &str

Returns a string slice with leading and trailing whitespace removed.

+

‘Whitespace’ is defined according to the terms of the Unicode Derived +Core Property White_Space, which includes newlines.

+
Examples
+
let s = "\n Hello\tworld\t\n";
+
+assert_eq!("Hello\tworld", s.trim());
+
1.30.0 · source

pub fn trim_start(&self) -> &str

Returns a string slice with leading whitespace removed.

+

‘Whitespace’ is defined according to the terms of the Unicode Derived +Core Property White_Space, which includes newlines.

+
Text directionality
+

A string is a sequence of bytes. start in this context means the first +position of that byte string; for a left-to-right language like English or +Russian, this will be left side, and for right-to-left languages like +Arabic or Hebrew, this will be the right side.

+
Examples
+

Basic usage:

+ +
let s = "\n Hello\tworld\t\n";
+assert_eq!("Hello\tworld\t\n", s.trim_start());
+

Directionality:

+ +
let s = "  English  ";
+assert!(Some('E') == s.trim_start().chars().next());
+
+let s = "  עברית  ";
+assert!(Some('ע') == s.trim_start().chars().next());
+
1.30.0 · source

pub fn trim_end(&self) -> &str

Returns a string slice with trailing whitespace removed.

+

‘Whitespace’ is defined according to the terms of the Unicode Derived +Core Property White_Space, which includes newlines.

+
Text directionality
+

A string is a sequence of bytes. end in this context means the last +position of that byte string; for a left-to-right language like English or +Russian, this will be right side, and for right-to-left languages like +Arabic or Hebrew, this will be the left side.

+
Examples
+

Basic usage:

+ +
let s = "\n Hello\tworld\t\n";
+assert_eq!("\n Hello\tworld", s.trim_end());
+

Directionality:

+ +
let s = "  English  ";
+assert!(Some('h') == s.trim_end().chars().rev().next());
+
+let s = "  עברית  ";
+assert!(Some('ת') == s.trim_end().chars().rev().next());
+
1.0.0 · source

pub fn trim_left(&self) -> &str

👎Deprecated since 1.33.0: superseded by trim_start

Returns a string slice with leading whitespace removed.

+

‘Whitespace’ is defined according to the terms of the Unicode Derived +Core Property White_Space.

+
Text directionality
+

A string is a sequence of bytes. ‘Left’ in this context means the first +position of that byte string; for a language like Arabic or Hebrew +which are ‘right to left’ rather than ‘left to right’, this will be +the right side, not the left.

+
Examples
+

Basic usage:

+ +
let s = " Hello\tworld\t";
+
+assert_eq!("Hello\tworld\t", s.trim_left());
+

Directionality:

+ +
let s = "  English";
+assert!(Some('E') == s.trim_left().chars().next());
+
+let s = "  עברית";
+assert!(Some('ע') == s.trim_left().chars().next());
+
1.0.0 · source

pub fn trim_right(&self) -> &str

👎Deprecated since 1.33.0: superseded by trim_end

Returns a string slice with trailing whitespace removed.

+

‘Whitespace’ is defined according to the terms of the Unicode Derived +Core Property White_Space.

+
Text directionality
+

A string is a sequence of bytes. ‘Right’ in this context means the last +position of that byte string; for a language like Arabic or Hebrew +which are ‘right to left’ rather than ‘left to right’, this will be +the left side, not the right.

+
Examples
+

Basic usage:

+ +
let s = " Hello\tworld\t";
+
+assert_eq!(" Hello\tworld", s.trim_right());
+

Directionality:

+ +
let s = "English  ";
+assert!(Some('h') == s.trim_right().chars().rev().next());
+
+let s = "עברית  ";
+assert!(Some('ת') == s.trim_right().chars().rev().next());
+
1.0.0 · source

pub fn trim_matches<'a, P>(&'a self, pat: P) -> &'a strwhere + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: DoubleEndedSearcher<'a>,

Returns a string slice with all prefixes and suffixes that match a +pattern repeatedly removed.

+

The pattern can be a char, a slice of chars, or a function +or closure that determines if a character matches.

+
Examples
+

Simple patterns:

+ +
assert_eq!("11foo1bar11".trim_matches('1'), "foo1bar");
+assert_eq!("123foo1bar123".trim_matches(char::is_numeric), "foo1bar");
+
+let x: &[_] = &['1', '2'];
+assert_eq!("12foo1bar12".trim_matches(x), "foo1bar");
+

A more complex pattern, using a closure:

+ +
assert_eq!("1foo1barXX".trim_matches(|c| c == '1' || c == 'X'), "foo1bar");
+
1.30.0 · source

pub fn trim_start_matches<'a, P>(&'a self, pat: P) -> &'a strwhere + P: Pattern<'a>,

Returns a string slice with all prefixes that match a pattern +repeatedly removed.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Text directionality
+

A string is a sequence of bytes. start in this context means the first +position of that byte string; for a left-to-right language like English or +Russian, this will be left side, and for right-to-left languages like +Arabic or Hebrew, this will be the right side.

+
Examples
+
assert_eq!("11foo1bar11".trim_start_matches('1'), "foo1bar11");
+assert_eq!("123foo1bar123".trim_start_matches(char::is_numeric), "foo1bar123");
+
+let x: &[_] = &['1', '2'];
+assert_eq!("12foo1bar12".trim_start_matches(x), "foo1bar12");
+
1.45.0 · source

pub fn strip_prefix<'a, P>(&'a self, prefix: P) -> Option<&'a str>where + P: Pattern<'a>,

Returns a string slice with the prefix removed.

+

If the string starts with the pattern prefix, returns substring after the prefix, wrapped +in Some. Unlike trim_start_matches, this method removes the prefix exactly once.

+

If the string does not start with prefix, returns None.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Examples
+
assert_eq!("foo:bar".strip_prefix("foo:"), Some("bar"));
+assert_eq!("foo:bar".strip_prefix("bar"), None);
+assert_eq!("foofoo".strip_prefix("foo"), Some("foo"));
+
1.45.0 · source

pub fn strip_suffix<'a, P>(&'a self, suffix: P) -> Option<&'a str>where + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,

Returns a string slice with the suffix removed.

+

If the string ends with the pattern suffix, returns the substring before the suffix, +wrapped in Some. Unlike trim_end_matches, this method removes the suffix exactly once.

+

If the string does not end with suffix, returns None.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Examples
+
assert_eq!("bar:foo".strip_suffix(":foo"), Some("bar"));
+assert_eq!("bar:foo".strip_suffix("bar"), None);
+assert_eq!("foofoo".strip_suffix("foo"), Some("foo"));
+
1.30.0 · source

pub fn trim_end_matches<'a, P>(&'a self, pat: P) -> &'a strwhere + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,

Returns a string slice with all suffixes that match a pattern +repeatedly removed.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Text directionality
+

A string is a sequence of bytes. end in this context means the last +position of that byte string; for a left-to-right language like English or +Russian, this will be right side, and for right-to-left languages like +Arabic or Hebrew, this will be the left side.

+
Examples
+

Simple patterns:

+ +
assert_eq!("11foo1bar11".trim_end_matches('1'), "11foo1bar");
+assert_eq!("123foo1bar123".trim_end_matches(char::is_numeric), "123foo1bar");
+
+let x: &[_] = &['1', '2'];
+assert_eq!("12foo1bar12".trim_end_matches(x), "12foo1bar");
+

A more complex pattern, using a closure:

+ +
assert_eq!("1fooX".trim_end_matches(|c| c == '1' || c == 'X'), "1foo");
+
1.0.0 · source

pub fn trim_left_matches<'a, P>(&'a self, pat: P) -> &'a strwhere + P: Pattern<'a>,

👎Deprecated since 1.33.0: superseded by trim_start_matches

Returns a string slice with all prefixes that match a pattern +repeatedly removed.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Text directionality
+

A string is a sequence of bytes. ‘Left’ in this context means the first +position of that byte string; for a language like Arabic or Hebrew +which are ‘right to left’ rather than ‘left to right’, this will be +the right side, not the left.

+
Examples
+
assert_eq!("11foo1bar11".trim_left_matches('1'), "foo1bar11");
+assert_eq!("123foo1bar123".trim_left_matches(char::is_numeric), "foo1bar123");
+
+let x: &[_] = &['1', '2'];
+assert_eq!("12foo1bar12".trim_left_matches(x), "foo1bar12");
+
1.0.0 · source

pub fn trim_right_matches<'a, P>(&'a self, pat: P) -> &'a strwhere + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,

👎Deprecated since 1.33.0: superseded by trim_end_matches

Returns a string slice with all suffixes that match a pattern +repeatedly removed.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Text directionality
+

A string is a sequence of bytes. ‘Right’ in this context means the last +position of that byte string; for a language like Arabic or Hebrew +which are ‘right to left’ rather than ‘left to right’, this will be +the left side, not the right.

+
Examples
+

Simple patterns:

+ +
assert_eq!("11foo1bar11".trim_right_matches('1'), "11foo1bar");
+assert_eq!("123foo1bar123".trim_right_matches(char::is_numeric), "123foo1bar");
+
+let x: &[_] = &['1', '2'];
+assert_eq!("12foo1bar12".trim_right_matches(x), "12foo1bar");
+

A more complex pattern, using a closure:

+ +
assert_eq!("1fooX".trim_right_matches(|c| c == '1' || c == 'X'), "1foo");
+
1.0.0 · source

pub fn parse<F>(&self) -> Result<F, <F as FromStr>::Err>where + F: FromStr,

Parses this string slice into another type.

+

Because parse is so general, it can cause problems with type +inference. As such, parse is one of the few times you’ll see +the syntax affectionately known as the ‘turbofish’: ::<>. This +helps the inference algorithm understand specifically which type +you’re trying to parse into.

+

parse can parse into any type that implements the FromStr trait.

+
Errors
+

Will return Err if it’s not possible to parse this string slice into +the desired type.

+
Examples
+

Basic usage

+ +
let four: u32 = "4".parse().unwrap();
+
+assert_eq!(4, four);
+

Using the ‘turbofish’ instead of annotating four:

+ +
let four = "4".parse::<u32>();
+
+assert_eq!(Ok(4), four);
+

Failing to parse:

+ +
let nope = "j".parse::<u32>();
+
+assert!(nope.is_err());
+
1.23.0 · source

pub fn is_ascii(&self) -> bool

Checks if all characters in this string are within the ASCII range.

+
Examples
+
let ascii = "hello!\n";
+let non_ascii = "Grüße, Jürgen ❤";
+
+assert!(ascii.is_ascii());
+assert!(!non_ascii.is_ascii());
+
source

pub fn as_ascii(&self) -> Option<&[AsciiChar]>

🔬This is a nightly-only experimental API. (ascii_char)

If this string slice is_ascii, returns it as a slice +of ASCII characters, otherwise returns None.

+
1.23.0 · source

pub fn eq_ignore_ascii_case(&self, other: &str) -> bool

Checks that two strings are an ASCII case-insensitive match.

+

Same as to_ascii_lowercase(a) == to_ascii_lowercase(b), +but without allocating and copying temporaries.

+
Examples
+
assert!("Ferris".eq_ignore_ascii_case("FERRIS"));
+assert!("Ferrös".eq_ignore_ascii_case("FERRöS"));
+assert!(!"Ferrös".eq_ignore_ascii_case("FERRÖS"));
+
1.34.0 · source

pub fn escape_debug(&self) -> EscapeDebug<'_>

Return an iterator that escapes each char in self with char::escape_debug.

+

Note: only extended grapheme codepoints that begin the string will be +escaped.

+
Examples
+

As an iterator:

+ +
for c in "❤\n!".escape_debug() {
+    print!("{c}");
+}
+println!();
+

Using println! directly:

+ +
println!("{}", "❤\n!".escape_debug());
+

Both are equivalent to:

+ +
println!("❤\\n!");
+

Using to_string:

+ +
assert_eq!("❤\n!".escape_debug().to_string(), "❤\\n!");
+
1.34.0 · source

pub fn escape_default(&self) -> EscapeDefault<'_>

Return an iterator that escapes each char in self with char::escape_default.

+
Examples
+

As an iterator:

+ +
for c in "❤\n!".escape_default() {
+    print!("{c}");
+}
+println!();
+

Using println! directly:

+ +
println!("{}", "❤\n!".escape_default());
+

Both are equivalent to:

+ +
println!("\\u{{2764}}\\n!");
+

Using to_string:

+ +
assert_eq!("❤\n!".escape_default().to_string(), "\\u{2764}\\n!");
+
1.34.0 · source

pub fn escape_unicode(&self) -> EscapeUnicode<'_>

Return an iterator that escapes each char in self with char::escape_unicode.

+
Examples
+

As an iterator:

+ +
for c in "❤\n!".escape_unicode() {
+    print!("{c}");
+}
+println!();
+

Using println! directly:

+ +
println!("{}", "❤\n!".escape_unicode());
+

Both are equivalent to:

+ +
println!("\\u{{2764}}\\u{{a}}\\u{{21}}");
+

Using to_string:

+ +
assert_eq!("❤\n!".escape_unicode().to_string(), "\\u{2764}\\u{a}\\u{21}");
+
1.0.0 · source

pub fn replace<'a, P>(&'a self, from: P, to: &str) -> Stringwhere + P: Pattern<'a>,

Replaces all matches of a pattern with another string.

+

replace creates a new String, and copies the data from this string slice into it. +While doing so, it attempts to find matches of a pattern. If it finds any, it +replaces them with the replacement string slice.

+
Examples
+

Basic usage:

+ +
let s = "this is old";
+
+assert_eq!("this is new", s.replace("old", "new"));
+assert_eq!("than an old", s.replace("is", "an"));
+

When the pattern doesn’t match, it returns this string slice as String:

+ +
let s = "this is old";
+assert_eq!(s, s.replace("cookie monster", "little lamb"));
+
1.16.0 · source

pub fn replacen<'a, P>(&'a self, pat: P, to: &str, count: usize) -> Stringwhere + P: Pattern<'a>,

Replaces first N matches of a pattern with another string.

+

replacen creates a new String, and copies the data from this string slice into it. +While doing so, it attempts to find matches of a pattern. If it finds any, it +replaces them with the replacement string slice at most count times.

+
Examples
+

Basic usage:

+ +
let s = "foo foo 123 foo";
+assert_eq!("new new 123 foo", s.replacen("foo", "new", 2));
+assert_eq!("faa fao 123 foo", s.replacen('o', "a", 3));
+assert_eq!("foo foo new23 foo", s.replacen(char::is_numeric, "new", 1));
+

When the pattern doesn’t match, it returns this string slice as String:

+ +
let s = "this is old";
+assert_eq!(s, s.replacen("cookie monster", "little lamb", 10));
+
1.2.0 · source

pub fn to_lowercase(&self) -> String

Returns the lowercase equivalent of this string slice, as a new String.

+

‘Lowercase’ is defined according to the terms of the Unicode Derived Core Property +Lowercase.

+

Since some characters can expand into multiple characters when changing +the case, this function returns a String instead of modifying the +parameter in-place.

+
Examples
+

Basic usage:

+ +
let s = "HELLO";
+
+assert_eq!("hello", s.to_lowercase());
+

A tricky example, with sigma:

+ +
let sigma = "Σ";
+
+assert_eq!("σ", sigma.to_lowercase());
+
+// but at the end of a word, it's ς, not σ:
+let odysseus = "ὈΔΥΣΣΕΎΣ";
+
+assert_eq!("ὀδυσσεύς", odysseus.to_lowercase());
+

Languages without case are not changed:

+ +
let new_year = "农历新年";
+
+assert_eq!(new_year, new_year.to_lowercase());
+
1.2.0 · source

pub fn to_uppercase(&self) -> String

Returns the uppercase equivalent of this string slice, as a new String.

+

‘Uppercase’ is defined according to the terms of the Unicode Derived Core Property +Uppercase.

+

Since some characters can expand into multiple characters when changing +the case, this function returns a String instead of modifying the +parameter in-place.

+
Examples
+

Basic usage:

+ +
let s = "hello";
+
+assert_eq!("HELLO", s.to_uppercase());
+

Scripts without case are not changed:

+ +
let new_year = "农历新年";
+
+assert_eq!(new_year, new_year.to_uppercase());
+

One character can become multiple:

+ +
let s = "tschüß";
+
+assert_eq!("TSCHÜSS", s.to_uppercase());
+
1.16.0 · source

pub fn repeat(&self, n: usize) -> String

Creates a new String by repeating a string n times.

+
Panics
+

This function will panic if the capacity would overflow.

+
Examples
+

Basic usage:

+ +
assert_eq!("abc".repeat(4), String::from("abcabcabcabc"));
+

A panic upon overflow:

+ +
// this will panic at runtime
+let huge = "0123456789abcdef".repeat(usize::MAX);
+
1.23.0 · source

pub fn to_ascii_uppercase(&self) -> String

Returns a copy of this string where each character is mapped to its +ASCII upper case equivalent.

+

ASCII letters ‘a’ to ‘z’ are mapped to ‘A’ to ‘Z’, +but non-ASCII letters are unchanged.

+

To uppercase the value in-place, use make_ascii_uppercase.

+

To uppercase ASCII characters in addition to non-ASCII characters, use +to_uppercase.

+
Examples
+
let s = "Grüße, Jürgen ❤";
+
+assert_eq!("GRüßE, JüRGEN ❤", s.to_ascii_uppercase());
+
1.23.0 · source

pub fn to_ascii_lowercase(&self) -> String

Returns a copy of this string where each character is mapped to its +ASCII lower case equivalent.

+

ASCII letters ‘A’ to ‘Z’ are mapped to ‘a’ to ‘z’, +but non-ASCII letters are unchanged.

+

To lowercase the value in-place, use make_ascii_lowercase.

+

To lowercase ASCII characters in addition to non-ASCII characters, use +to_lowercase.

+
Examples
+
let s = "Grüße, Jürgen ❤";
+
+assert_eq!("grüße, jürgen ❤", s.to_ascii_lowercase());
+

Trait Implementations§

§

impl AsRef<[u8]> for Guid

§

fn as_ref(&self) -> &[u8]

Converts this type into a shared reference of the (usually inferred) input type.
§

impl AsRef<str> for Guid

§

fn as_ref(&self) -> &str

Converts this type into a shared reference of the (usually inferred) input type.
§

impl Clone for Guid

§

fn clone(&self) -> Guid

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
§

impl Debug for Guid

§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
§

impl Default for Guid

§

fn default() -> Guid

Create a default guid by calling Guid::empty()

+
§

impl Deref for Guid

§

type Target = str

The resulting type after dereferencing.
§

fn deref(&self) -> &str

Dereferences the value.
§

impl<'de> Deserialize<'de> for Guid

§

fn deserialize<D>( + deserializer: D +) -> Result<Guid, <D as Deserializer<'de>>::Error>where + D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
§

impl Display for Guid

§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
§

impl<'a> From<&'a &str> for Guid

§

fn from(s: &'a &str) -> Guid

Converts to this type from the input type.
§

impl<'a> From<&'a [u8]> for Guid

§

fn from(s: &'a [u8]) -> Guid

Converts to this type from the input type.
§

impl<'a> From<&'a str> for Guid

§

fn from(s: &'a str) -> Guid

Converts to this type from the input type.
source§

impl From<BookmarkRecordId> for SyncGuid

Converts a bookmark record ID into a Places GUID.

+
source§

fn from(record_id: BookmarkRecordId) -> SyncGuid

Converts to this type from the input type.
source§

impl From<BookmarkRootGuid> for SyncGuid

source§

fn from(item: BookmarkRootGuid) -> SyncGuid

Converts to this type from the input type.
source§

impl From<Guid> for BookmarkRecordId

Converts a Places GUID into a bookmark record ID.

+
source§

fn from(guid: SyncGuid) -> BookmarkRecordId

Converts to this type from the input type.
§

impl From<Guid> for String

§

fn from(guid: Guid) -> String

Converts to this type from the input type.
§

impl From<Guid> for Vec<u8, Global>

§

fn from(guid: Guid) -> Vec<u8, Global>

Converts to this type from the input type.
§

impl From<String> for Guid

§

fn from(s: String) -> Guid

Converts to this type from the input type.
§

impl From<Vec<u8, Global>> for Guid

§

fn from(v: Vec<u8, Global>) -> Guid

Converts to this type from the input type.
§

impl FromSql for Guid

§

fn column_result(value: ValueRef<'_>) -> Result<Guid, FromSqlError>

Converts SQLite value into Rust value.
§

impl Hash for Guid

§

fn hash<H>(&self, state: &mut H)where + H: Hasher,

Feeds this value into the given Hasher. Read more
1.3.0 · source§

fn hash_slice<H>(data: &[Self], state: &mut H)where + H: Hasher, + Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
§

impl Ord for Guid

§

fn cmp(&self, other: &Guid) -> Ordering

This method returns an Ordering between self and other. Read more
1.21.0 · source§

fn max(self, other: Self) -> Selfwhere + Self: Sized,

Compares and returns the maximum of two values. Read more
1.21.0 · source§

fn min(self, other: Self) -> Selfwhere + Self: Sized,

Compares and returns the minimum of two values. Read more
1.50.0 · source§

fn clamp(self, min: Self, max: Self) -> Selfwhere + Self: Sized + PartialOrd<Self>,

Restrict a value to a certain interval. Read more
§

impl<'a> PartialEq<&'a [u8]> for Guid

§

fn eq(&self, other: &&'a [u8]) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl<'a> PartialEq<&'a Guid> for BookmarkRootGuid

source§

fn eq(&self, other: &&'a SyncGuid) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
§

impl<'a> PartialEq<&'a str> for Guid

§

fn eq(&self, other: &&'a str) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
§

impl<'a> PartialEq<[u8]> for Guid

§

fn eq(&self, other: &[u8]) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl<'a> PartialEq<BookmarkRootGuid> for &'a SyncGuid

source§

fn eq(&self, other: &BookmarkRootGuid) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl PartialEq<BookmarkRootGuid> for SyncGuid

source§

fn eq(&self, other: &BookmarkRootGuid) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
§

impl<'a> PartialEq<Guid> for &'a [u8]

§

fn eq(&self, other: &Guid) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
§

impl<'a> PartialEq<Guid> for &'a str

§

fn eq(&self, other: &Guid) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
§

impl<'a> PartialEq<Guid> for [u8]

§

fn eq(&self, other: &Guid) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl PartialEq<Guid> for BookmarkRootGuid

source§

fn eq(&self, other: &SyncGuid) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
§

impl PartialEq<Guid> for Guid

§

fn eq(&self, other: &Guid) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
§

impl<'a> PartialEq<Guid> for String

§

fn eq(&self, other: &Guid) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
§

impl<'a> PartialEq<Guid> for Vec<u8, Global>

§

fn eq(&self, other: &Guid) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
§

impl<'a> PartialEq<Guid> for str

§

fn eq(&self, other: &Guid) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
§

impl<'a> PartialEq<String> for Guid

§

fn eq(&self, other: &String) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
§

impl<'a> PartialEq<Vec<u8, Global>> for Guid

§

fn eq(&self, other: &Vec<u8, Global>) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
§

impl<'a> PartialEq<str> for Guid

§

fn eq(&self, other: &str) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
§

impl PartialOrd<Guid> for Guid

§

fn partial_cmp(&self, other: &Guid) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · source§

fn lt(&self, other: &Rhs) -> bool

This method tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · source§

fn le(&self, other: &Rhs) -> bool

This method tests less than or equal to (for self and other) and is used by the <= +operator. Read more
1.0.0 · source§

fn gt(&self, other: &Rhs) -> bool

This method tests greater than (for self and other) and is used by the > operator. Read more
1.0.0 · source§

fn ge(&self, other: &Rhs) -> bool

This method tests greater than or equal to (for self and other) and is used by the >= +operator. Read more
§

impl Serialize for Guid

§

fn serialize<S>( + &self, + serializer: S +) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>where + S: Serializer,

Serialize this value into the given Serde serializer. Read more
§

impl ToSql for Guid

§

fn to_sql(&self) -> Result<ToSqlOutput<'_>, Error>

Converts Rust value to SQLite value
§

impl Eq for Guid

Auto Trait Implementations§

§

impl RefUnwindSafe for Guid

§

impl Send for Guid

§

impl Sync for Guid

§

impl Unpin for Guid

§

impl UnwindSafe for Guid

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> CallHasher for Twhere + T: Hash + ?Sized,

§

fn get_hash<H, B>(value: &H, build_hasher: &B) -> u64where + H: Hash + ?Sized, + B: BuildHasher,

source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
§

impl<I> IntoResettable<String> for Iwhere + I: Into<String>,

§

fn into_resettable(self) -> Resettable<String>

Convert to the intended resettable type
§

impl<Ctx, T> MeasureWith<Ctx> for Twhere + T: AsRef<[u8]>,

§

fn measure_with(&self, _ctx: &Ctx) -> usize

How large is Self, given the ctx?
source§

impl<T> ToHex for Twhere + T: AsRef<[u8]>,

source§

fn encode_hex<U>(&self) -> Uwhere + U: FromIterator<char>,

Encode the hex strict representing self into the result. Lower case +letters are used (e.g. f9b4ca)
source§

fn encode_hex_upper<U>(&self) -> Uwhere + U: FromIterator<char>,

Encode the hex strict representing self into the result. Upper case +letters are used (e.g. F9B4CA)
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/places/ffi/struct.HistoryVisitInfo.html b/book/rust-docs/places/ffi/struct.HistoryVisitInfo.html new file mode 100644 index 0000000000..27f07460ed --- /dev/null +++ b/book/rust-docs/places/ffi/struct.HistoryVisitInfo.html @@ -0,0 +1,23 @@ +HistoryVisitInfo in places::ffi - Rust
pub struct HistoryVisitInfo {
+    pub url: Url,
+    pub title: Option<String>,
+    pub timestamp: PlacesTimestamp,
+    pub visit_type: VisitType,
+    pub is_hidden: bool,
+    pub preview_image_url: Option<Url>,
+    pub is_remote: bool,
+}

Fields§

§url: Url§title: Option<String>§timestamp: PlacesTimestamp§visit_type: VisitType§is_hidden: bool§preview_image_url: Option<Url>§is_remote: bool

Trait Implementations§

source§

impl Clone for HistoryVisitInfo

source§

fn clone(&self) -> HistoryVisitInfo

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl PartialEq<HistoryVisitInfo> for HistoryVisitInfo

source§

fn eq(&self, other: &HistoryVisitInfo) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Eq for HistoryVisitInfo

source§

impl StructuralEq for HistoryVisitInfo

source§

impl StructuralPartialEq for HistoryVisitInfo

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/ffi/struct.HistoryVisitInfosWithBound.html b/book/rust-docs/places/ffi/struct.HistoryVisitInfosWithBound.html new file mode 100644 index 0000000000..03cbef633b --- /dev/null +++ b/book/rust-docs/places/ffi/struct.HistoryVisitInfosWithBound.html @@ -0,0 +1,19 @@ +HistoryVisitInfosWithBound in places::ffi - Rust
pub struct HistoryVisitInfosWithBound {
+    pub infos: Vec<HistoryVisitInfo>,
+    pub bound: i64,
+    pub offset: i64,
+}

Fields§

§infos: Vec<HistoryVisitInfo>§bound: i64§offset: i64

Trait Implementations§

source§

impl Clone for HistoryVisitInfosWithBound

source§

fn clone(&self) -> HistoryVisitInfosWithBound

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl PartialEq<HistoryVisitInfosWithBound> for HistoryVisitInfosWithBound

source§

fn eq(&self, other: &HistoryVisitInfosWithBound) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Eq for HistoryVisitInfosWithBound

source§

impl StructuralEq for HistoryVisitInfosWithBound

source§

impl StructuralPartialEq for HistoryVisitInfosWithBound

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/ffi/struct.PlacesConnection.html b/book/rust-docs/places/ffi/struct.PlacesConnection.html new file mode 100644 index 0000000000..00e82d808e --- /dev/null +++ b/book/rust-docs/places/ffi/struct.PlacesConnection.html @@ -0,0 +1,109 @@ +PlacesConnection in places::ffi - Rust
pub struct PlacesConnection { /* private fields */ }

Implementations§

source§

impl PlacesConnection

source

pub fn new(db: PlacesDb) -> Self

source

pub fn new_interrupt_handle(&self) -> Arc<SqlInterruptHandle>

source

pub fn get_latest_history_metadata_for_url( + &self, + url: Url +) -> ApiResult<Option<HistoryMetadata>>

source

pub fn get_history_metadata_between( + &self, + start: PlacesTimestamp, + end: PlacesTimestamp +) -> ApiResult<Vec<HistoryMetadata>>

source

pub fn get_history_metadata_since( + &self, + start: PlacesTimestamp +) -> ApiResult<Vec<HistoryMetadata>>

source

pub fn query_history_metadata( + &self, + query: String, + limit: i32 +) -> ApiResult<Vec<HistoryMetadata>>

source

pub fn get_history_highlights( + &self, + weights: HistoryHighlightWeights, + limit: i32 +) -> ApiResult<Vec<HistoryHighlight>>

source

pub fn note_history_metadata_observation( + &self, + data: HistoryMetadataObservation +) -> ApiResult<()>

source

pub fn metadata_delete_older_than( + &self, + older_than: PlacesTimestamp +) -> ApiResult<()>

source

pub fn metadata_delete( + &self, + url: Url, + referrer_url: Option<Url>, + search_term: Option<String> +) -> ApiResult<()>

source

pub fn apply_observation(&self, visit: VisitObservation) -> ApiResult<()>

Add an observation to the database.

+
source

pub fn get_visited_urls_in_range( + &self, + start: PlacesTimestamp, + end: PlacesTimestamp, + include_remote: bool +) -> ApiResult<Vec<Url>>

source

pub fn get_visit_infos( + &self, + start_date: PlacesTimestamp, + end_date: PlacesTimestamp, + exclude_types: VisitTransitionSet +) -> ApiResult<Vec<HistoryVisitInfo>>

source

pub fn get_visit_count( + &self, + exclude_types: VisitTransitionSet +) -> ApiResult<i64>

source

pub fn get_visit_page( + &self, + offset: i64, + count: i64, + exclude_types: VisitTransitionSet +) -> ApiResult<Vec<HistoryVisitInfo>>

source

pub fn get_visit_page_with_bound( + &self, + bound: i64, + offset: i64, + count: i64, + exclude_types: VisitTransitionSet +) -> ApiResult<HistoryVisitInfosWithBound>

source

pub fn get_visited(&self, urls: Vec<String>) -> ApiResult<Vec<bool>>

source

pub fn delete_visits_for(&self, url: String) -> ApiResult<()>

source

pub fn delete_visits_between( + &self, + start: PlacesTimestamp, + end: PlacesTimestamp +) -> ApiResult<()>

source

pub fn delete_visit( + &self, + url: String, + timestamp: PlacesTimestamp +) -> ApiResult<()>

source

pub fn get_top_frecent_site_infos( + &self, + num_items: i32, + threshold_option: FrecencyThresholdOption +) -> ApiResult<Vec<TopFrecentSiteInfo>>

source

pub fn delete_everything_history(&self) -> ApiResult<()>

source

pub fn run_maintenance_prune( + &self, + db_size_limit: u32 +) -> ApiResult<RunMaintenanceMetrics>

source

pub fn run_maintenance_vacuum(&self) -> ApiResult<()>

source

pub fn run_maintenance_optimize(&self) -> ApiResult<()>

source

pub fn run_maintenance_checkpoint(&self) -> ApiResult<()>

source

pub fn query_autocomplete( + &self, + search: String, + limit: i32 +) -> ApiResult<Vec<SearchResult>>

source

pub fn accept_result(&self, search_string: String, url: String) -> ApiResult<()>

source

pub fn match_url(&self, query: String) -> ApiResult<Option<Url>>

source

pub fn bookmarks_get_tree( + &self, + item_guid: &Guid +) -> ApiResult<Option<BookmarkItem>>

source

pub fn bookmarks_get_by_guid( + &self, + guid: &Guid, + get_direct_children: bool +) -> ApiResult<Option<BookmarkItem>>

source

pub fn bookmarks_get_all_with_url( + &self, + url: String +) -> ApiResult<Vec<BookmarkItem>>

source

pub fn bookmarks_get_recent(&self, limit: i32) -> ApiResult<Vec<BookmarkItem>>

source

pub fn bookmarks_delete(&self, id: Guid) -> ApiResult<bool>

source

pub fn bookmarks_delete_everything(&self) -> ApiResult<()>

source

pub fn bookmarks_get_url_for_keyword( + &self, + keyword: String +) -> ApiResult<Option<Url>>

source

pub fn bookmarks_insert(&self, data: InsertableBookmarkItem) -> ApiResult<Guid>

source

pub fn bookmarks_update(&self, item: BookmarkUpdateInfo) -> ApiResult<()>

source

pub fn bookmarks_count_bookmarks_in_trees( + &self, + guids: &[Guid] +) -> ApiResult<u32>

source

pub fn places_history_import_from_ios( + &self, + db_path: String, + last_sync_timestamp: i64 +) -> ApiResult<HistoryMigrationResult>

Trait Implementations§

source§

impl AsRef<SqlInterruptHandle> for PlacesConnection

source§

fn as_ref(&self) -> &SqlInterruptHandle

Converts this type into a shared reference of the (usually inferred) input type.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/ffi/struct.PlacesTimestamp.html b/book/rust-docs/places/ffi/struct.PlacesTimestamp.html new file mode 100644 index 0000000000..9bf97d54e6 --- /dev/null +++ b/book/rust-docs/places/ffi/struct.PlacesTimestamp.html @@ -0,0 +1,43 @@ +PlacesTimestamp in places::ffi - Rust

Struct places::ffi::PlacesTimestamp

pub struct PlacesTimestamp(pub u64);

Tuple Fields§

§0: u64

Implementations§

§

impl Timestamp

pub fn now() -> Timestamp

pub fn duration_since(self, other: Timestamp) -> Option<Duration>

Returns None if other is later than self (Duration may not represent +negative timespans in rust).

+

pub fn checked_sub(self, d: Duration) -> Option<Timestamp>

pub fn checked_add(self, d: Duration) -> Option<Timestamp>

pub fn as_millis(self) -> u64

pub fn as_millis_i64(self) -> i64

pub const EARLIEST: Timestamp = Timestamp(727747200000)

In desktop sync, bookmarks are clamped to Jan 23, 1993 (which is 727747200000) +There’s no good reason history records could be older than that, so we do +the same here (even though desktop’s history currently doesn’t) +XXX - there’s probably a case to be made for this being, say, 5 years ago - +then all requests earlier than that are collapsed into a single visit at +this timestamp.

+

Trait Implementations§

§

impl Clone for Timestamp

§

fn clone(&self) -> Timestamp

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
§

impl Debug for Timestamp

§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
§

impl Default for Timestamp

§

fn default() -> Timestamp

Returns the “default value” for a type. Read more
§

impl<'de> Deserialize<'de> for Timestamp

§

fn deserialize<__D>( + __deserializer: __D +) -> Result<Timestamp, <__D as Deserializer<'de>>::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
§

impl Display for Timestamp

§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
source§

impl From<ServerVisitTimestamp> for Timestamp

source§

fn from(ts: ServerVisitTimestamp) -> Timestamp

Converts to this type from the input type.
§

impl From<SystemTime> for Timestamp

§

fn from(st: SystemTime) -> Timestamp

Converts to this type from the input type.
source§

impl From<Timestamp> for ServerVisitTimestamp

source§

fn from(ts: Timestamp) -> ServerVisitTimestamp

Converts to this type from the input type.
§

impl From<u64> for Timestamp

§

fn from(ts: u64) -> Timestamp

Converts to this type from the input type.
§

impl FromSql for Timestamp

§

fn column_result(value: ValueRef<'_>) -> Result<Timestamp, FromSqlError>

Converts SQLite value into Rust value.
§

impl Hash for Timestamp

§

fn hash<__H>(&self, state: &mut __H)where + __H: Hasher,

Feeds this value into the given Hasher. Read more
1.3.0 · source§

fn hash_slice<H>(data: &[Self], state: &mut H)where + H: Hasher, + Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
§

impl Ord for Timestamp

§

fn cmp(&self, other: &Timestamp) -> Ordering

This method returns an Ordering between self and other. Read more
1.21.0 · source§

fn max(self, other: Self) -> Selfwhere + Self: Sized,

Compares and returns the maximum of two values. Read more
1.21.0 · source§

fn min(self, other: Self) -> Selfwhere + Self: Sized,

Compares and returns the minimum of two values. Read more
1.50.0 · source§

fn clamp(self, min: Self, max: Self) -> Selfwhere + Self: Sized + PartialOrd<Self>,

Restrict a value to a certain interval. Read more
§

impl PartialEq<Timestamp> for Timestamp

§

fn eq(&self, other: &Timestamp) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
§

impl PartialOrd<Timestamp> for Timestamp

§

fn partial_cmp(&self, other: &Timestamp) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · source§

fn lt(&self, other: &Rhs) -> bool

This method tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · source§

fn le(&self, other: &Rhs) -> bool

This method tests less than or equal to (for self and other) and is used by the <= +operator. Read more
1.0.0 · source§

fn gt(&self, other: &Rhs) -> bool

This method tests greater than (for self and other) and is used by the > operator. Read more
1.0.0 · source§

fn ge(&self, other: &Rhs) -> bool

This method tests greater than or equal to (for self and other) and is used by the >= +operator. Read more
§

impl Serialize for Timestamp

§

fn serialize<__S>( + &self, + __serializer: __S +) -> Result<<__S as Serializer>::Ok, <__S as Serializer>::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more
§

impl ToSql for Timestamp

§

fn to_sql(&self) -> Result<ToSqlOutput<'_>, Error>

Converts Rust value to SQLite value
§

impl Copy for Timestamp

§

impl Eq for Timestamp

§

impl StructuralEq for Timestamp

§

impl StructuralPartialEq for Timestamp

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> CallHasher for Twhere + T: Hash + ?Sized,

§

fn get_hash<H, B>(value: &H, build_hasher: &B) -> u64where + H: Hash + ?Sized, + B: BuildHasher,

source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/places/ffi/struct.SearchResult.html b/book/rust-docs/places/ffi/struct.SearchResult.html new file mode 100644 index 0000000000..81eeb2e6b5 --- /dev/null +++ b/book/rust-docs/places/ffi/struct.SearchResult.html @@ -0,0 +1,16 @@ +SearchResult in places::ffi - Rust

Struct places::ffi::SearchResult

source ·
pub struct SearchResult {
+    pub url: Url,
+    pub title: String,
+    pub frecency: i64,
+}

Fields§

§url: Url§title: String§frecency: i64

Trait Implementations§

source§

impl From<SearchResult> for FfiSearchResult

source§

fn from(res: SearchResult) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/ffi/struct.SqlInterruptHandle.html b/book/rust-docs/places/ffi/struct.SqlInterruptHandle.html new file mode 100644 index 0000000000..2a0f641fc3 --- /dev/null +++ b/book/rust-docs/places/ffi/struct.SqlInterruptHandle.html @@ -0,0 +1,29 @@ +SqlInterruptHandle in places::ffi - Rust
pub struct SqlInterruptHandle { /* private fields */ }
Expand description

Interrupt operations that use SQL

+

Typical usage of this type:

+
    +
  • Components typically create a wrapper class around an rusqlite::Connection +(PlacesConnection, LoginStore, etc.)
  • +
  • The wrapper stores an Arc<SqlInterruptHandle>
  • +
  • The wrapper has a method that clones and returns that Arc. This allows passing the interrupt +handle to a different thread in order to interrupt a particular operation.
  • +
  • The wrapper calls begin_interrupt_scope() at the start of each operation. The code that +performs the operation periodically calls err_if_interrupted().
  • +
  • Finally, the wrapper class implements AsRef<SqlInterruptHandle> and calls +register_interrupt(). This causes all operations to be interrupted when we enter +shutdown mode.
  • +
+

Implementations§

§

impl SqlInterruptHandle

pub fn new(conn: &Connection) -> SqlInterruptHandle

pub fn begin_interrupt_scope(&self) -> Result<SqlInterruptScope, Interrupted>

Begin an interrupt scope that will be interrupted by this handle

+

Returns Err(Interrupted) if we’re in shutdown mode

+

pub fn interrupt(&self)

Interrupt all interrupt scopes created by this handle

+

Trait Implementations§

source§

impl AsRef<SqlInterruptHandle> for PlacesConnection

source§

fn as_ref(&self) -> &SqlInterruptHandle

Converts this type into a shared reference of the (usually inferred) input type.
source§

impl AsRef<SqlInterruptHandle> for SharedPlacesDb

source§

fn as_ref(&self) -> &SqlInterruptHandle

Converts this type into a shared reference of the (usually inferred) input type.
§

impl Debug for SqlInterruptHandle

§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/ffi/struct.TopFrecentSiteInfo.html b/book/rust-docs/places/ffi/struct.TopFrecentSiteInfo.html new file mode 100644 index 0000000000..6a10ce385c --- /dev/null +++ b/book/rust-docs/places/ffi/struct.TopFrecentSiteInfo.html @@ -0,0 +1,15 @@ +TopFrecentSiteInfo in places::ffi - Rust
pub struct TopFrecentSiteInfo {
+    pub url: Url,
+    pub title: Option<String>,
+}

Fields§

§url: Url§title: Option<String>

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/ffi/struct.Url.html b/book/rust-docs/places/ffi/struct.Url.html new file mode 100644 index 0000000000..dd3bcedb8c --- /dev/null +++ b/book/rust-docs/places/ffi/struct.Url.html @@ -0,0 +1,785 @@ +Url in places::ffi - Rust

Struct places::ffi::Url

source ·
pub struct Url { /* private fields */ }
Expand description

A parsed URL record.

+

Implementations§

source§

impl Url

source

pub fn parse(input: &str) -> Result<Url, ParseError>

Parse an absolute URL from a string.

+
Examples
+
use url::Url;
+
+let url = Url::parse("https://example.net")?;
+
Errors
+

If the function can not parse an absolute URL from the given string, +a ParseError variant will be returned.

+
source

pub fn parse_with_params<I, K, V>( + input: &str, + iter: I +) -> Result<Url, ParseError>where + I: IntoIterator, + <I as IntoIterator>::Item: Borrow<(K, V)>, + K: AsRef<str>, + V: AsRef<str>,

Parse an absolute URL from a string and add params to its query string.

+

Existing params are not removed.

+
Examples
+
use url::Url;
+
+let url = Url::parse_with_params("https://example.net?dont=clobberme",
+                                 &[("lang", "rust"), ("browser", "servo")])?;
+assert_eq!("https://example.net/?dont=clobberme&lang=rust&browser=servo", url.as_str());
+
Errors
+

If the function can not parse an absolute URL from the given string, +a ParseError variant will be returned.

+
source

pub fn join(&self, input: &str) -> Result<Url, ParseError>

Parse a string as an URL, with this URL as the base URL.

+

The inverse of this is make_relative.

+

Note: a trailing slash is significant. +Without it, the last path component is considered to be a “file” name +to be removed to get at the “directory” that is used as the base:

+
Examples
+
use url::Url;
+
+let base = Url::parse("https://example.net/a/b.html")?;
+let url = base.join("c.png")?;
+assert_eq!(url.as_str(), "https://example.net/a/c.png");  // Not /a/b.html/c.png
+
+let base = Url::parse("https://example.net/a/b/")?;
+let url = base.join("c.png")?;
+assert_eq!(url.as_str(), "https://example.net/a/b/c.png");
+
Errors
+

If the function can not parse an URL from the given string +with this URL as the base URL, a ParseError variant will be returned.

+
source

pub fn make_relative(&self, url: &Url) -> Option<String>

Creates a relative URL if possible, with this URL as the base URL.

+

This is the inverse of join.

+
Examples
+
use url::Url;
+
+let base = Url::parse("https://example.net/a/b.html")?;
+let url = Url::parse("https://example.net/a/c.png")?;
+let relative = base.make_relative(&url);
+assert_eq!(relative.as_ref().map(|s| s.as_str()), Some("c.png"));
+
+let base = Url::parse("https://example.net/a/b/")?;
+let url = Url::parse("https://example.net/a/b/c.png")?;
+let relative = base.make_relative(&url);
+assert_eq!(relative.as_ref().map(|s| s.as_str()), Some("c.png"));
+
+let base = Url::parse("https://example.net/a/b/")?;
+let url = Url::parse("https://example.net/a/d/c.png")?;
+let relative = base.make_relative(&url);
+assert_eq!(relative.as_ref().map(|s| s.as_str()), Some("../d/c.png"));
+
+let base = Url::parse("https://example.net/a/b.html?c=d")?;
+let url = Url::parse("https://example.net/a/b.html?e=f")?;
+let relative = base.make_relative(&url);
+assert_eq!(relative.as_ref().map(|s| s.as_str()), Some("?e=f"));
+
Errors
+

If this URL can’t be a base for the given URL, None is returned. +This is for example the case if the scheme, host or port are not the same.

+
source

pub fn options<'a>() -> ParseOptions<'a>

Return a default ParseOptions that can fully configure the URL parser.

+
Examples
+

Get default ParseOptions, then change base url

+ +
use url::Url;
+let options = Url::options();
+let api = Url::parse("https://api.example.com")?;
+let base_url = options.base_url(Some(&api));
+let version_url = base_url.parse("version.json")?;
+assert_eq!(version_url.as_str(), "https://api.example.com/version.json");
+
source

pub fn as_str(&self) -> &str

Return the serialization of this URL.

+

This is fast since that serialization is already stored in the Url struct.

+
Examples
+
use url::Url;
+
+let url_str = "https://example.net/";
+let url = Url::parse(url_str)?;
+assert_eq!(url.as_str(), url_str);
+
source

pub fn into_string(self) -> String

👎Deprecated since 2.3.0: use Into<String>

Return the serialization of this URL.

+

This consumes the Url and takes ownership of the String stored in it.

+
Examples
+
use url::Url;
+
+let url_str = "https://example.net/";
+let url = Url::parse(url_str)?;
+assert_eq!(String::from(url), url_str);
+
source

pub fn origin(&self) -> Origin

Return the origin of this URL (https://url.spec.whatwg.org/#origin)

+

Note: this returns an opaque origin for file: URLs, which causes +url.origin() != url.origin().

+
Examples
+

URL with ftp scheme:

+ +
use url::{Host, Origin, Url};
+
+let url = Url::parse("ftp://example.com/foo")?;
+assert_eq!(url.origin(),
+           Origin::Tuple("ftp".into(),
+                         Host::Domain("example.com".into()),
+                         21));
+

URL with blob scheme:

+ +
use url::{Host, Origin, Url};
+
+let url = Url::parse("blob:https://example.com/foo")?;
+assert_eq!(url.origin(),
+           Origin::Tuple("https".into(),
+                         Host::Domain("example.com".into()),
+                         443));
+

URL with file scheme:

+ +
use url::{Host, Origin, Url};
+
+let url = Url::parse("file:///tmp/foo")?;
+assert!(!url.origin().is_tuple());
+
+let other_url = Url::parse("file:///tmp/foo")?;
+assert!(url.origin() != other_url.origin());
+

URL with other scheme:

+ +
use url::{Host, Origin, Url};
+
+let url = Url::parse("foo:bar")?;
+assert!(!url.origin().is_tuple());
+
source

pub fn scheme(&self) -> &str

Return the scheme of this URL, lower-cased, as an ASCII string without the ‘:’ delimiter.

+
Examples
+
use url::Url;
+
+let url = Url::parse("file:///tmp/foo")?;
+assert_eq!(url.scheme(), "file");
+
source

pub fn has_authority(&self) -> bool

Return whether the URL has an ‘authority’, +which can contain a username, password, host, and port number.

+

URLs that do not are either path-only like unix:/run/foo.socket +or cannot-be-a-base like data:text/plain,Stuff.

+
Examples
+
use url::Url;
+
+let url = Url::parse("ftp://rms@example.com")?;
+assert!(url.has_authority());
+
+let url = Url::parse("unix:/run/foo.socket")?;
+assert!(!url.has_authority());
+
+let url = Url::parse("data:text/plain,Stuff")?;
+assert!(!url.has_authority());
+
source

pub fn cannot_be_a_base(&self) -> bool

Return whether this URL is a cannot-be-a-base URL, +meaning that parsing a relative URL string with this URL as the base will return an error.

+

This is the case if the scheme and : delimiter are not followed by a / slash, +as is typically the case of data: and mailto: URLs.

+
Examples
+
use url::Url;
+
+let url = Url::parse("ftp://rms@example.com")?;
+assert!(!url.cannot_be_a_base());
+
+let url = Url::parse("unix:/run/foo.socket")?;
+assert!(!url.cannot_be_a_base());
+
+let url = Url::parse("data:text/plain,Stuff")?;
+assert!(url.cannot_be_a_base());
+
source

pub fn username(&self) -> &str

Return the username for this URL (typically the empty string) +as a percent-encoded ASCII string.

+
Examples
+
use url::Url;
+
+let url = Url::parse("ftp://rms@example.com")?;
+assert_eq!(url.username(), "rms");
+
+let url = Url::parse("ftp://:secret123@example.com")?;
+assert_eq!(url.username(), "");
+
+let url = Url::parse("https://example.com")?;
+assert_eq!(url.username(), "");
+
source

pub fn password(&self) -> Option<&str>

Return the password for this URL, if any, as a percent-encoded ASCII string.

+
Examples
+
use url::Url;
+
+let url = Url::parse("ftp://rms:secret123@example.com")?;
+assert_eq!(url.password(), Some("secret123"));
+
+let url = Url::parse("ftp://:secret123@example.com")?;
+assert_eq!(url.password(), Some("secret123"));
+
+let url = Url::parse("ftp://rms@example.com")?;
+assert_eq!(url.password(), None);
+
+let url = Url::parse("https://example.com")?;
+assert_eq!(url.password(), None);
+
source

pub fn has_host(&self) -> bool

Equivalent to url.host().is_some().

+
Examples
+
use url::Url;
+
+let url = Url::parse("ftp://rms@example.com")?;
+assert!(url.has_host());
+
+let url = Url::parse("unix:/run/foo.socket")?;
+assert!(!url.has_host());
+
+let url = Url::parse("data:text/plain,Stuff")?;
+assert!(!url.has_host());
+
source

pub fn host_str(&self) -> Option<&str>

Return the string representation of the host (domain or IP address) for this URL, if any.

+

Non-ASCII domains are punycode-encoded per IDNA if this is the host +of a special URL, or percent encoded for non-special URLs. +IPv6 addresses are given between [ and ] brackets.

+

Cannot-be-a-base URLs (typical of data: and mailto:) and some file: URLs +don’t have a host.

+

See also the host method.

+
Examples
+
use url::Url;
+
+let url = Url::parse("https://127.0.0.1/index.html")?;
+assert_eq!(url.host_str(), Some("127.0.0.1"));
+
+let url = Url::parse("ftp://rms@example.com")?;
+assert_eq!(url.host_str(), Some("example.com"));
+
+let url = Url::parse("unix:/run/foo.socket")?;
+assert_eq!(url.host_str(), None);
+
+let url = Url::parse("data:text/plain,Stuff")?;
+assert_eq!(url.host_str(), None);
+
source

pub fn host(&self) -> Option<Host<&str>>

Return the parsed representation of the host for this URL. +Non-ASCII domain labels are punycode-encoded per IDNA if this is the host +of a special URL, or percent encoded for non-special URLs.

+

Cannot-be-a-base URLs (typical of data: and mailto:) and some file: URLs +don’t have a host.

+

See also the host_str method.

+
Examples
+
use url::Url;
+
+let url = Url::parse("https://127.0.0.1/index.html")?;
+assert!(url.host().is_some());
+
+let url = Url::parse("ftp://rms@example.com")?;
+assert!(url.host().is_some());
+
+let url = Url::parse("unix:/run/foo.socket")?;
+assert!(url.host().is_none());
+
+let url = Url::parse("data:text/plain,Stuff")?;
+assert!(url.host().is_none());
+
source

pub fn domain(&self) -> Option<&str>

If this URL has a host and it is a domain name (not an IP address), return it. +Non-ASCII domains are punycode-encoded per IDNA if this is the host +of a special URL, or percent encoded for non-special URLs.

+
Examples
+
use url::Url;
+
+let url = Url::parse("https://127.0.0.1/")?;
+assert_eq!(url.domain(), None);
+
+let url = Url::parse("mailto:rms@example.net")?;
+assert_eq!(url.domain(), None);
+
+let url = Url::parse("https://example.com/")?;
+assert_eq!(url.domain(), Some("example.com"));
+
source

pub fn port(&self) -> Option<u16>

Return the port number for this URL, if any.

+

Note that default port numbers are never reflected by the serialization, +use the port_or_known_default() method if you want a default port number returned.

+
Examples
+
use url::Url;
+
+let url = Url::parse("https://example.com")?;
+assert_eq!(url.port(), None);
+
+let url = Url::parse("https://example.com:443/")?;
+assert_eq!(url.port(), None);
+
+let url = Url::parse("ssh://example.com:22")?;
+assert_eq!(url.port(), Some(22));
+
source

pub fn port_or_known_default(&self) -> Option<u16>

Return the port number for this URL, or the default port number if it is known.

+

This method only knows the default port number +of the http, https, ws, wss and ftp schemes.

+

For URLs in these schemes, this method always returns Some(_). +For other schemes, it is the same as Url::port().

+
Examples
+
use url::Url;
+
+let url = Url::parse("foo://example.com")?;
+assert_eq!(url.port_or_known_default(), None);
+
+let url = Url::parse("foo://example.com:1456")?;
+assert_eq!(url.port_or_known_default(), Some(1456));
+
+let url = Url::parse("https://example.com")?;
+assert_eq!(url.port_or_known_default(), Some(443));
+
source

pub fn socket_addrs( + &self, + default_port_number: impl Fn() -> Option<u16> +) -> Result<Vec<SocketAddr, Global>, Error>

Resolve a URL’s host and port number to SocketAddr.

+

If the URL has the default port number of a scheme that is unknown to this library, +default_port_number provides an opportunity to provide the actual port number. +In non-example code this should be implemented either simply as || None, +or by matching on the URL’s .scheme().

+

If the host is a domain, it is resolved using the standard library’s DNS support.

+
Examples
+
let url = url::Url::parse("https://example.net/").unwrap();
+let addrs = url.socket_addrs(|| None).unwrap();
+std::net::TcpStream::connect(&*addrs)
+ +
/// With application-specific known default port numbers
+fn socket_addrs(url: url::Url) -> std::io::Result<Vec<std::net::SocketAddr>> {
+    url.socket_addrs(|| match url.scheme() {
+        "socks5" | "socks5h" => Some(1080),
+        _ => None,
+    })
+}
+
source

pub fn path(&self) -> &str

Return the path for this URL, as a percent-encoded ASCII string. +For cannot-be-a-base URLs, this is an arbitrary string that doesn’t start with ‘/’. +For other URLs, this starts with a ‘/’ slash +and continues with slash-separated path segments.

+
Examples
+
use url::{Url, ParseError};
+
+let url = Url::parse("https://example.com/api/versions?page=2")?;
+assert_eq!(url.path(), "/api/versions");
+
+let url = Url::parse("https://example.com")?;
+assert_eq!(url.path(), "/");
+
+let url = Url::parse("https://example.com/countries/việt nam")?;
+assert_eq!(url.path(), "/countries/vi%E1%BB%87t%20nam");
+
source

pub fn path_segments(&self) -> Option<Split<'_, char>>

Unless this URL is cannot-be-a-base, +return an iterator of ‘/’ slash-separated path segments, +each as a percent-encoded ASCII string.

+

Return None for cannot-be-a-base URLs.

+

When Some is returned, the iterator always contains at least one string +(which may be empty).

+
Examples
+
use url::Url;
+
+let url = Url::parse("https://example.com/foo/bar")?;
+let mut path_segments = url.path_segments().ok_or_else(|| "cannot be base")?;
+assert_eq!(path_segments.next(), Some("foo"));
+assert_eq!(path_segments.next(), Some("bar"));
+assert_eq!(path_segments.next(), None);
+
+let url = Url::parse("https://example.com")?;
+let mut path_segments = url.path_segments().ok_or_else(|| "cannot be base")?;
+assert_eq!(path_segments.next(), Some(""));
+assert_eq!(path_segments.next(), None);
+
+let url = Url::parse("data:text/plain,HelloWorld")?;
+assert!(url.path_segments().is_none());
+
+let url = Url::parse("https://example.com/countries/việt nam")?;
+let mut path_segments = url.path_segments().ok_or_else(|| "cannot be base")?;
+assert_eq!(path_segments.next(), Some("countries"));
+assert_eq!(path_segments.next(), Some("vi%E1%BB%87t%20nam"));
+
source

pub fn query(&self) -> Option<&str>

Return this URL’s query string, if any, as a percent-encoded ASCII string.

+
Examples
+
use url::Url;
+
+fn run() -> Result<(), ParseError> {
+let url = Url::parse("https://example.com/products?page=2")?;
+let query = url.query();
+assert_eq!(query, Some("page=2"));
+
+let url = Url::parse("https://example.com/products")?;
+let query = url.query();
+assert!(query.is_none());
+
+let url = Url::parse("https://example.com/?country=español")?;
+let query = url.query();
+assert_eq!(query, Some("country=espa%C3%B1ol"));
+
source

pub fn query_pairs(&self) -> Parse<'_>

Parse the URL’s query string, if any, as application/x-www-form-urlencoded +and return an iterator of (key, value) pairs.

+
Examples
+
use std::borrow::Cow;
+
+use url::Url;
+
+let url = Url::parse("https://example.com/products?page=2&sort=desc")?;
+let mut pairs = url.query_pairs();
+
+assert_eq!(pairs.count(), 2);
+
+assert_eq!(pairs.next(), Some((Cow::Borrowed("page"), Cow::Borrowed("2"))));
+assert_eq!(pairs.next(), Some((Cow::Borrowed("sort"), Cow::Borrowed("desc"))));
+
source

pub fn fragment(&self) -> Option<&str>

Return this URL’s fragment identifier, if any.

+

A fragment is the part of the URL after the # symbol. +The fragment is optional and, if present, contains a fragment identifier +that identifies a secondary resource, such as a section heading +of a document.

+

In HTML, the fragment identifier is usually the id attribute of a an element +that is scrolled to on load. Browsers typically will not send the fragment portion +of a URL to the server.

+

Note: the parser did not percent-encode this component, +but the input may have been percent-encoded already.

+
Examples
+
use url::Url;
+
+let url = Url::parse("https://example.com/data.csv#row=4")?;
+
+assert_eq!(url.fragment(), Some("row=4"));
+
+let url = Url::parse("https://example.com/data.csv#cell=4,1-6,2")?;
+
+assert_eq!(url.fragment(), Some("cell=4,1-6,2"));
+
source

pub fn set_fragment(&mut self, fragment: Option<&str>)

Change this URL’s fragment identifier.

+
Examples
+
use url::Url;
+
+let mut url = Url::parse("https://example.com/data.csv")?;
+assert_eq!(url.as_str(), "https://example.com/data.csv");
+url.set_fragment(Some("cell=4,1-6,2"));
+assert_eq!(url.as_str(), "https://example.com/data.csv#cell=4,1-6,2");
+assert_eq!(url.fragment(), Some("cell=4,1-6,2"));
+
+url.set_fragment(None);
+assert_eq!(url.as_str(), "https://example.com/data.csv");
+assert!(url.fragment().is_none());
+
source

pub fn set_query(&mut self, query: Option<&str>)

Change this URL’s query string.

+
Examples
+
use url::Url;
+
+let mut url = Url::parse("https://example.com/products")?;
+assert_eq!(url.as_str(), "https://example.com/products");
+
+url.set_query(Some("page=2"));
+assert_eq!(url.as_str(), "https://example.com/products?page=2");
+assert_eq!(url.query(), Some("page=2"));
+
source

pub fn query_pairs_mut(&mut self) -> Serializer<'_, UrlQuery<'_>>

Manipulate this URL’s query string, viewed as a sequence of name/value pairs +in application/x-www-form-urlencoded syntax.

+

The return value has a method-chaining API:

+ +

+let mut url = Url::parse("https://example.net?lang=fr#nav")?;
+assert_eq!(url.query(), Some("lang=fr"));
+
+url.query_pairs_mut().append_pair("foo", "bar");
+assert_eq!(url.query(), Some("lang=fr&foo=bar"));
+assert_eq!(url.as_str(), "https://example.net/?lang=fr&foo=bar#nav");
+
+url.query_pairs_mut()
+    .clear()
+    .append_pair("foo", "bar & baz")
+    .append_pair("saisons", "\u{00C9}t\u{00E9}+hiver");
+assert_eq!(url.query(), Some("foo=bar+%26+baz&saisons=%C3%89t%C3%A9%2Bhiver"));
+assert_eq!(url.as_str(),
+           "https://example.net/?foo=bar+%26+baz&saisons=%C3%89t%C3%A9%2Bhiver#nav");
+

Note: url.query_pairs_mut().clear(); is equivalent to url.set_query(Some("")), +not url.set_query(None).

+

The state of Url is unspecified if this return value is leaked without being dropped.

+
source

pub fn set_path(&mut self, path: &str)

Change this URL’s path.

+
Examples
+
use url::Url;
+
+let mut url = Url::parse("https://example.com")?;
+url.set_path("api/comments");
+assert_eq!(url.as_str(), "https://example.com/api/comments");
+assert_eq!(url.path(), "/api/comments");
+
+let mut url = Url::parse("https://example.com/api")?;
+url.set_path("data/report.csv");
+assert_eq!(url.as_str(), "https://example.com/data/report.csv");
+assert_eq!(url.path(), "/data/report.csv");
+
source

pub fn path_segments_mut(&mut self) -> Result<PathSegmentsMut<'_>, ()>

Return an object with methods to manipulate this URL’s path segments.

+

Return Err(()) if this URL is cannot-be-a-base.

+
source

pub fn set_port(&mut self, port: Option<u16>) -> Result<(), ()>

Change this URL’s port number.

+

Note that default port numbers are not reflected in the serialization.

+

If this URL is cannot-be-a-base, does not have a host, or has the file scheme; +do nothing and return Err.

+
Examples
+
use url::Url;
+
+let mut url = Url::parse("ssh://example.net:2048/")?;
+
+url.set_port(Some(4096)).map_err(|_| "cannot be base")?;
+assert_eq!(url.as_str(), "ssh://example.net:4096/");
+
+url.set_port(None).map_err(|_| "cannot be base")?;
+assert_eq!(url.as_str(), "ssh://example.net/");
+

Known default port numbers are not reflected:

+ +
use url::Url;
+
+let mut url = Url::parse("https://example.org/")?;
+
+url.set_port(Some(443)).map_err(|_| "cannot be base")?;
+assert!(url.port().is_none());
+

Cannot set port for cannot-be-a-base URLs:

+ +
use url::Url;
+
+let mut url = Url::parse("mailto:rms@example.net")?;
+
+let result = url.set_port(Some(80));
+assert!(result.is_err());
+
+let result = url.set_port(None);
+assert!(result.is_err());
+
source

pub fn set_host(&mut self, host: Option<&str>) -> Result<(), ParseError>

Change this URL’s host.

+

Removing the host (calling this with None) +will also remove any username, password, and port number.

+
Examples
+

Change host:

+ +
use url::Url;
+
+let mut url = Url::parse("https://example.net")?;
+let result = url.set_host(Some("rust-lang.org"));
+assert!(result.is_ok());
+assert_eq!(url.as_str(), "https://rust-lang.org/");
+

Remove host:

+ +
use url::Url;
+
+let mut url = Url::parse("foo://example.net")?;
+let result = url.set_host(None);
+assert!(result.is_ok());
+assert_eq!(url.as_str(), "foo:/");
+

Cannot remove host for ‘special’ schemes (e.g. http):

+ +
use url::Url;
+
+let mut url = Url::parse("https://example.net")?;
+let result = url.set_host(None);
+assert!(result.is_err());
+assert_eq!(url.as_str(), "https://example.net/");
+

Cannot change or remove host for cannot-be-a-base URLs:

+ +
use url::Url;
+
+let mut url = Url::parse("mailto:rms@example.net")?;
+
+let result = url.set_host(Some("rust-lang.org"));
+assert!(result.is_err());
+assert_eq!(url.as_str(), "mailto:rms@example.net");
+
+let result = url.set_host(None);
+assert!(result.is_err());
+assert_eq!(url.as_str(), "mailto:rms@example.net");
+
Errors
+

If this URL is cannot-be-a-base or there is an error parsing the given host, +a ParseError variant will be returned.

+
source

pub fn set_ip_host(&mut self, address: IpAddr) -> Result<(), ()>

Change this URL’s host to the given IP address.

+

If this URL is cannot-be-a-base, do nothing and return Err.

+

Compared to Url::set_host, this skips the host parser.

+
Examples
+
use url::{Url, ParseError};
+
+let mut url = Url::parse("http://example.com")?;
+url.set_ip_host("127.0.0.1".parse().unwrap());
+assert_eq!(url.host_str(), Some("127.0.0.1"));
+assert_eq!(url.as_str(), "http://127.0.0.1/");
+

Cannot change URL’s from mailto(cannot-be-base) to ip:

+ +
use url::{Url, ParseError};
+
+let mut url = Url::parse("mailto:rms@example.com")?;
+let result = url.set_ip_host("127.0.0.1".parse().unwrap());
+
+assert_eq!(url.as_str(), "mailto:rms@example.com");
+assert!(result.is_err());
+
source

pub fn set_password(&mut self, password: Option<&str>) -> Result<(), ()>

Change this URL’s password.

+

If this URL is cannot-be-a-base or does not have a host, do nothing and return Err.

+
Examples
+
use url::{Url, ParseError};
+
+let mut url = Url::parse("mailto:rmz@example.com")?;
+let result = url.set_password(Some("secret_password"));
+assert!(result.is_err());
+
+let mut url = Url::parse("ftp://user1:secret1@example.com")?;
+let result = url.set_password(Some("secret_password"));
+assert_eq!(url.password(), Some("secret_password"));
+
+let mut url = Url::parse("ftp://user2:@example.com")?;
+let result = url.set_password(Some("secret2"));
+assert!(result.is_ok());
+assert_eq!(url.password(), Some("secret2"));
+
source

pub fn set_username(&mut self, username: &str) -> Result<(), ()>

Change this URL’s username.

+

If this URL is cannot-be-a-base or does not have a host, do nothing and return Err.

+
Examples
+

Cannot setup username from mailto(cannot-be-base)

+ +
use url::{Url, ParseError};
+
+let mut url = Url::parse("mailto:rmz@example.com")?;
+let result = url.set_username("user1");
+assert_eq!(url.as_str(), "mailto:rmz@example.com");
+assert!(result.is_err());
+

Setup username to user1

+ +
use url::{Url, ParseError};
+
+let mut url = Url::parse("ftp://:secre1@example.com/")?;
+let result = url.set_username("user1");
+assert!(result.is_ok());
+assert_eq!(url.username(), "user1");
+assert_eq!(url.as_str(), "ftp://user1:secre1@example.com/");
+
source

pub fn set_scheme(&mut self, scheme: &str) -> Result<(), ()>

Change this URL’s scheme.

+

Do nothing and return Err under the following circumstances:

+
    +
  • If the new scheme is not in [a-zA-Z][a-zA-Z0-9+.-]+
  • +
  • If this URL is cannot-be-a-base and the new scheme is one of +http, https, ws, wss or ftp
  • +
  • If either the old or new scheme is http, https, ws, +wss or ftp and the other is not one of these
  • +
  • If the new scheme is file and this URL includes credentials +or has a non-null port
  • +
  • If this URL’s scheme is file and its host is empty or null
  • +
+

See also the URL specification’s section on legal scheme state +overrides.

+
Examples
+

Change the URL’s scheme from https to foo:

+ +
use url::Url;
+
+let mut url = Url::parse("https://example.net")?;
+let result = url.set_scheme("http");
+assert_eq!(url.as_str(), "http://example.net/");
+assert!(result.is_ok());
+

Change the URL’s scheme from foo to bar:

+ +
use url::Url;
+
+let mut url = Url::parse("foo://example.net")?;
+let result = url.set_scheme("bar");
+assert_eq!(url.as_str(), "bar://example.net");
+assert!(result.is_ok());
+

Cannot change URL’s scheme from https to foõ:

+ +
use url::Url;
+
+let mut url = Url::parse("https://example.net")?;
+let result = url.set_scheme("foõ");
+assert_eq!(url.as_str(), "https://example.net/");
+assert!(result.is_err());
+

Cannot change URL’s scheme from mailto (cannot-be-a-base) to https:

+ +
use url::Url;
+
+let mut url = Url::parse("mailto:rms@example.net")?;
+let result = url.set_scheme("https");
+assert_eq!(url.as_str(), "mailto:rms@example.net");
+assert!(result.is_err());
+

Cannot change the URL’s scheme from foo to https:

+ +
use url::Url;
+
+let mut url = Url::parse("foo://example.net")?;
+let result = url.set_scheme("https");
+assert_eq!(url.as_str(), "foo://example.net");
+assert!(result.is_err());
+

Cannot change the URL’s scheme from http to foo:

+ +
use url::Url;
+
+let mut url = Url::parse("http://example.net")?;
+let result = url.set_scheme("foo");
+assert_eq!(url.as_str(), "http://example.net/");
+assert!(result.is_err());
+
source

pub fn from_file_path<P>(path: P) -> Result<Url, ()>where + P: AsRef<Path>,

Convert a file name as std::path::Path into an URL in the file scheme.

+

This returns Err if the given path is not absolute or, +on Windows, if the prefix is not a disk prefix (e.g. C:) or a UNC prefix (\\).

+
Examples
+

On Unix-like platforms:

+ +
use url::Url;
+
+let url = Url::from_file_path("/tmp/foo.txt")?;
+assert_eq!(url.as_str(), "file:///tmp/foo.txt");
+
+let url = Url::from_file_path("../foo.txt");
+assert!(url.is_err());
+
+let url = Url::from_file_path("https://google.com/");
+assert!(url.is_err());
+
source

pub fn from_directory_path<P>(path: P) -> Result<Url, ()>where + P: AsRef<Path>,

Convert a directory name as std::path::Path into an URL in the file scheme.

+

This returns Err if the given path is not absolute or, +on Windows, if the prefix is not a disk prefix (e.g. C:) or a UNC prefix (\\).

+

Compared to from_file_path, this ensure that URL’s the path has a trailing slash +so that the entire path is considered when using this URL as a base URL.

+

For example:

+
    +
  • "index.html" parsed with Url::from_directory_path(Path::new("/var/www")) +as the base URL is file:///var/www/index.html
  • +
  • "index.html" parsed with Url::from_file_path(Path::new("/var/www")) +as the base URL is file:///var/index.html, which might not be what was intended.
  • +
+

Note that std::path does not consider trailing slashes significant +and usually does not include them (e.g. in Path::parent()).

+
source

pub fn serialize_internal<S>( + &self, + serializer: S +) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>where + S: Serializer,

Serialize with Serde using the internal representation of the Url struct.

+

The corresponding deserialize_internal method sacrifices some invariant-checking +for speed, compared to the Deserialize trait impl.

+

This method is only available if the serde Cargo feature is enabled.

+
source

pub fn deserialize_internal<'de, D>( + deserializer: D +) -> Result<Url, <D as Deserializer<'de>>::Error>where + D: Deserializer<'de>,

Serialize with Serde using the internal representation of the Url struct.

+

The corresponding deserialize_internal method sacrifices some invariant-checking +for speed, compared to the Deserialize trait impl.

+

This method is only available if the serde Cargo feature is enabled.

+
source

pub fn to_file_path(&self) -> Result<PathBuf, ()>

Assuming the URL is in the file scheme or similar, +convert its path to an absolute std::path::Path.

+

Note: This does not actually check the URL’s scheme, +and may give nonsensical results for other schemes. +It is the user’s responsibility to check the URL’s scheme before calling this.

+ +
let path = url.to_file_path();
+

Returns Err if the host is neither empty nor "localhost" (except on Windows, where +file: URLs may have a non-local host), +or if Path::new_opt() returns None. +(That is, if the percent-decoded path contains a NUL byte or, +for a Windows path, is not UTF-8.)

+

Trait Implementations§

source§

impl AsRef<str> for Url

Return the serialization of this URL.

+
source§

fn as_ref(&self) -> &str

Converts this type into a shared reference of the (usually inferred) input type.
source§

impl Clone for Url

source§

fn clone(&self) -> Url

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for Url

Debug the serialization of this URL.

+
source§

fn fmt(&self, formatter: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
source§

impl<'de> Deserialize<'de> for Url

Deserializes this URL from a serde stream.

+

This implementation is only available if the serde Cargo feature is enabled.

+
source§

fn deserialize<D>( + deserializer: D +) -> Result<Url, <D as Deserializer<'de>>::Error>where + D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl Display for Url

Display the serialization of this URL.

+
source§

fn fmt(&self, formatter: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
source§

impl From<Url> for String

String converstion.

+
source§

fn from(value: Url) -> String

Converts to this type from the input type.
source§

impl FromStr for Url

Parse a string as an URL, without a base URL or encoding override.

+
§

type Err = ParseError

The associated error which can be returned from parsing.
source§

fn from_str(input: &str) -> Result<Url, ParseError>

Parses a string s to return a value of this type. Read more
source§

impl Hash for Url

URLs hash like their serialization.

+
source§

fn hash<H>(&self, state: &mut H)where + H: Hasher,

Feeds this value into the given Hasher. Read more
1.3.0 · source§

fn hash_slice<H>(data: &[Self], state: &mut H)where + H: Hasher, + Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
source§

impl Index<Range<Position>> for Url

§

type Output = str

The returned type after indexing.
source§

fn index(&self, range: Range<Position>) -> &str

Performs the indexing (container[index]) operation. Read more
source§

impl Index<RangeFrom<Position>> for Url

§

type Output = str

The returned type after indexing.
source§

fn index(&self, range: RangeFrom<Position>) -> &str

Performs the indexing (container[index]) operation. Read more
source§

impl Index<RangeFull> for Url

§

type Output = str

The returned type after indexing.
source§

fn index(&self, _: RangeFull) -> &str

Performs the indexing (container[index]) operation. Read more
source§

impl Index<RangeTo<Position>> for Url

§

type Output = str

The returned type after indexing.
source§

fn index(&self, range: RangeTo<Position>) -> &str

Performs the indexing (container[index]) operation. Read more
source§

impl Ord for Url

URLs compare like their serialization.

+
source§

fn cmp(&self, other: &Url) -> Ordering

This method returns an Ordering between self and other. Read more
1.21.0 · source§

fn max(self, other: Self) -> Selfwhere + Self: Sized,

Compares and returns the maximum of two values. Read more
1.21.0 · source§

fn min(self, other: Self) -> Selfwhere + Self: Sized,

Compares and returns the minimum of two values. Read more
1.50.0 · source§

fn clamp(self, min: Self, max: Self) -> Selfwhere + Self: Sized + PartialOrd<Self>,

Restrict a value to a certain interval. Read more
source§

impl PartialEq<Url> for Url

URLs compare like their serialization.

+
source§

fn eq(&self, other: &Url) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl PartialOrd<Url> for Url

URLs compare like their serialization.

+
source§

fn partial_cmp(&self, other: &Url) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · source§

fn lt(&self, other: &Rhs) -> bool

This method tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · source§

fn le(&self, other: &Rhs) -> bool

This method tests less than or equal to (for self and other) and is used by the <= +operator. Read more
1.0.0 · source§

fn gt(&self, other: &Rhs) -> bool

This method tests greater than (for self and other) and is used by the > operator. Read more
1.0.0 · source§

fn ge(&self, other: &Rhs) -> bool

This method tests greater than or equal to (for self and other) and is used by the >= +operator. Read more
source§

impl Serialize for Url

Serializes this URL into a serde stream.

+

This implementation is only available if the serde Cargo feature is enabled.

+
source§

fn serialize<S>( + &self, + serializer: S +) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>where + S: Serializer,

Serialize this value into the given Serde serializer. Read more
source§

impl<'a> TryFrom<&'a str> for Url

§

type Error = ParseError

The type returned in the event of a conversion error.
source§

fn try_from(s: &'a str) -> Result<Url, <Url as TryFrom<&'a str>>::Error>

Performs the conversion.
source§

impl Eq for Url

URLs compare like their serialization.

+

Auto Trait Implementations§

§

impl RefUnwindSafe for Url

§

impl Send for Url

§

impl Sync for Url

§

impl Unpin for Url

§

impl UnwindSafe for Url

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> CallHasher for Twhere + T: Hash + ?Sized,

§

fn get_hash<H, B>(value: &H, build_hasher: &B) -> u64where + H: Hash + ?Sized, + B: BuildHasher,

§

impl<Ctx, I, R> Cread<Ctx, I> for Rwhere + Ctx: Copy, + R: Index<I> + Index<RangeFrom<I>> + ?Sized,

§

fn cread_with<N>(&self, offset: I, ctx: Ctx) -> Nwhere + N: FromCtx<Ctx, Self::Output>,

Reads a value from Self at offset with ctx. Cannot fail. +If the buffer is too small for the value requested, this will panic. Read more
§

fn cread<N>(&self, offset: I) -> Nwhere + N: FromCtx<Ctx, Self::Output>, + Ctx: Default,

Reads a value implementing FromCtx from Self at offset, +with the target machine’s endianness. +For the primitive types, this will be the target machine’s endianness. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
§

impl<I> IntoResettable<String> for Iwhere + I: Into<String>,

§

fn into_resettable(self) -> Resettable<String>

Convert to the intended resettable type
§

impl<S> SliceExt for Swhere + S: Index<RangeFull> + ?Sized,

§

fn utf8char_indices(&self) -> Utf8CharDecoder<'_>where + <S as Index<RangeFull>>::Output: Borrow<[u8]>,

Decode u8 slices as UTF-8 and iterate over the codepoints as Utf8Chars, Read more
§

fn utf16char_indices(&self) -> Utf16CharDecoder<'_>where + <S as Index<RangeFull>>::Output: Borrow<[u16]>,

Decode u16 slices as UTF-16 and iterate over the codepoints as Utf16Chars, Read more
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/places/ffi/type.BookmarkFolder.html b/book/rust-docs/places/ffi/type.BookmarkFolder.html new file mode 100644 index 0000000000..cd7fa063fe --- /dev/null +++ b/book/rust-docs/places/ffi/type.BookmarkFolder.html @@ -0,0 +1 @@ +BookmarkFolder in places::ffi - Rust

Type Definition places::ffi::BookmarkFolder

source ·
pub type BookmarkFolder = Folder;
\ No newline at end of file diff --git a/book/rust-docs/places/ffi/type.BookmarkItem.html b/book/rust-docs/places/ffi/type.BookmarkItem.html new file mode 100644 index 0000000000..9d028d25be --- /dev/null +++ b/book/rust-docs/places/ffi/type.BookmarkItem.html @@ -0,0 +1 @@ +BookmarkItem in places::ffi - Rust

Type Definition places::ffi::BookmarkItem

source ·
pub type BookmarkItem = Item;
\ No newline at end of file diff --git a/book/rust-docs/places/ffi/type.BookmarkSeparator.html b/book/rust-docs/places/ffi/type.BookmarkSeparator.html new file mode 100644 index 0000000000..5e32d1737b --- /dev/null +++ b/book/rust-docs/places/ffi/type.BookmarkSeparator.html @@ -0,0 +1 @@ +BookmarkSeparator in places::ffi - Rust

Type Definition places::ffi::BookmarkSeparator

source ·
pub type BookmarkSeparator = Separator;
\ No newline at end of file diff --git a/book/rust-docs/places/ffi/type.InsertableBookmarkFolder.html b/book/rust-docs/places/ffi/type.InsertableBookmarkFolder.html new file mode 100644 index 0000000000..fb3004bf37 --- /dev/null +++ b/book/rust-docs/places/ffi/type.InsertableBookmarkFolder.html @@ -0,0 +1 @@ +InsertableBookmarkFolder in places::ffi - Rust

Type Definition places::ffi::InsertableBookmarkFolder

source ·
pub type InsertableBookmarkFolder = InsertableFolder;
\ No newline at end of file diff --git a/book/rust-docs/places/ffi/type.InsertableBookmarkItem.html b/book/rust-docs/places/ffi/type.InsertableBookmarkItem.html new file mode 100644 index 0000000000..6a7049fdcb --- /dev/null +++ b/book/rust-docs/places/ffi/type.InsertableBookmarkItem.html @@ -0,0 +1 @@ +InsertableBookmarkItem in places::ffi - Rust

Type Definition places::ffi::InsertableBookmarkItem

source ·
pub type InsertableBookmarkItem = InsertableItem;
\ No newline at end of file diff --git a/book/rust-docs/places/ffi/type.InsertableBookmarkSeparator.html b/book/rust-docs/places/ffi/type.InsertableBookmarkSeparator.html new file mode 100644 index 0000000000..411b2928c0 --- /dev/null +++ b/book/rust-docs/places/ffi/type.InsertableBookmarkSeparator.html @@ -0,0 +1 @@ +InsertableBookmarkSeparator in places::ffi - Rust

Type Definition places::ffi::InsertableBookmarkSeparator

source ·
pub type InsertableBookmarkSeparator = InsertableSeparator;
\ No newline at end of file diff --git a/book/rust-docs/places/frecency/constant.DEFAULT_FRECENCY_SETTINGS.html b/book/rust-docs/places/frecency/constant.DEFAULT_FRECENCY_SETTINGS.html new file mode 100644 index 0000000000..54d0e52286 --- /dev/null +++ b/book/rust-docs/places/frecency/constant.DEFAULT_FRECENCY_SETTINGS.html @@ -0,0 +1 @@ +DEFAULT_FRECENCY_SETTINGS in places::frecency - Rust
pub const DEFAULT_FRECENCY_SETTINGS: FrecencySettings;
\ No newline at end of file diff --git a/book/rust-docs/places/frecency/fn.calculate_frecency.html b/book/rust-docs/places/frecency/fn.calculate_frecency.html new file mode 100644 index 0000000000..cafd2e7091 --- /dev/null +++ b/book/rust-docs/places/frecency/fn.calculate_frecency.html @@ -0,0 +1,6 @@ +calculate_frecency in places::frecency - Rust
pub fn calculate_frecency(
+    db: &Connection,
+    settings: &FrecencySettings,
+    page_id: i64,
+    is_redirect: Option<bool>
+) -> Result<i32>
\ No newline at end of file diff --git a/book/rust-docs/places/frecency/index.html b/book/rust-docs/places/frecency/index.html new file mode 100644 index 0000000000..e04345c0cb --- /dev/null +++ b/book/rust-docs/places/frecency/index.html @@ -0,0 +1 @@ +places::frecency - Rust
\ No newline at end of file diff --git a/book/rust-docs/places/frecency/sidebar-items.js b/book/rust-docs/places/frecency/sidebar-items.js new file mode 100644 index 0000000000..76f792f9ed --- /dev/null +++ b/book/rust-docs/places/frecency/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"constant":["DEFAULT_FRECENCY_SETTINGS"],"fn":["calculate_frecency"],"struct":["FrecencySettings"]}; \ No newline at end of file diff --git a/book/rust-docs/places/frecency/struct.FrecencySettings.html b/book/rust-docs/places/frecency/struct.FrecencySettings.html new file mode 100644 index 0000000000..956d65f355 --- /dev/null +++ b/book/rust-docs/places/frecency/struct.FrecencySettings.html @@ -0,0 +1,44 @@ +FrecencySettings in places::frecency - Rust
pub struct FrecencySettings {
Show 23 fields + pub num_visits: i32, + pub first_bucket_cutoff_days: i32, + pub second_bucket_cutoff_days: i32, + pub third_bucket_cutoff_days: i32, + pub fourth_bucket_cutoff_days: i32, + pub first_bucket_weight: i32, + pub second_bucket_weight: i32, + pub third_bucket_weight: i32, + pub fourth_bucket_weight: i32, + pub default_bucket_weight: i32, + pub embed_visit_bonus: i32, + pub framed_link_visit_bonus: i32, + pub link_visit_bonus: i32, + pub typed_visit_bonus: i32, + pub bookmark_visit_bonus: i32, + pub download_visit_bonus: i32, + pub permanent_redirect_visit_bonus: i32, + pub temporary_redirect_visit_bonus: i32, + pub redirect_source_visit_bonus: i32, + pub default_visit_bonus: i32, + pub unvisited_bookmark_bonus: i32, + pub unvisited_typed_bonus: i32, + pub reload_visit_bonus: i32, +
}

Fields§

§num_visits: i32§first_bucket_cutoff_days: i32§second_bucket_cutoff_days: i32§third_bucket_cutoff_days: i32§fourth_bucket_cutoff_days: i32§first_bucket_weight: i32§second_bucket_weight: i32§third_bucket_weight: i32§fourth_bucket_weight: i32§default_bucket_weight: i32§embed_visit_bonus: i32§framed_link_visit_bonus: i32§link_visit_bonus: i32§typed_visit_bonus: i32§bookmark_visit_bonus: i32§download_visit_bonus: i32§permanent_redirect_visit_bonus: i32§temporary_redirect_visit_bonus: i32§redirect_source_visit_bonus: i32§default_visit_bonus: i32§unvisited_bookmark_bonus: i32§unvisited_typed_bonus: i32§reload_visit_bonus: i32

Implementations§

source§

impl FrecencySettings

source

pub fn get_transition_bonus( + &self, + visit_type: Option<VisitType>, + visited: bool, + redirect: bool +) -> i32

Trait Implementations§

source§

impl Clone for FrecencySettings

source§

fn clone(&self) -> FrecencySettings

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for FrecencySettings

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for FrecencySettings

source§

fn default() -> Self

Returns the “default value” for a type. Read more
source§

impl PartialEq<FrecencySettings> for FrecencySettings

source§

fn eq(&self, other: &FrecencySettings) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Eq for FrecencySettings

source§

impl StructuralEq for FrecencySettings

source§

impl StructuralPartialEq for FrecencySettings

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/hash/enum.PrefixMode.html b/book/rust-docs/places/hash/enum.PrefixMode.html new file mode 100644 index 0000000000..4fdd00da74 --- /dev/null +++ b/book/rust-docs/places/hash/enum.PrefixMode.html @@ -0,0 +1,20 @@ +PrefixMode in places::hash - Rust

Enum places::hash::PrefixMode

source ·
pub enum PrefixMode {
+    Lo,
+    Hi,
+}

Variants§

§

Lo

Equivalent to "prefix_lo" in mozilla::places::HashURL

+
§

Hi

Equivalent to "prefix_hi" in mozilla::places::HashURL

+

Trait Implementations§

source§

impl Clone for PrefixMode

source§

fn clone(&self) -> PrefixMode

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for PrefixMode

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl PartialEq<PrefixMode> for PrefixMode

source§

fn eq(&self, other: &PrefixMode) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Copy for PrefixMode

source§

impl Eq for PrefixMode

source§

impl StructuralEq for PrefixMode

source§

impl StructuralPartialEq for PrefixMode

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/hash/fn.hash_string.html b/book/rust-docs/places/hash/fn.hash_string.html new file mode 100644 index 0000000000..8332b7ad0d --- /dev/null +++ b/book/rust-docs/places/hash/fn.hash_string.html @@ -0,0 +1,2 @@ +hash_string in places::hash - Rust

Function places::hash::hash_string

source ·
pub fn hash_string(string: &str) -> u32
Expand description

This should return identical results to mozilla::HashString!

+
\ No newline at end of file diff --git a/book/rust-docs/places/hash/fn.hash_url.html b/book/rust-docs/places/hash/fn.hash_url.html new file mode 100644 index 0000000000..6015bad5f1 --- /dev/null +++ b/book/rust-docs/places/hash/fn.hash_url.html @@ -0,0 +1,6 @@ +hash_url in places::hash - Rust

Function places::hash::hash_url

source ·
pub fn hash_url(spec: &str) -> u64
Expand description

This should be identical to the “real” mozilla::places::HashURL with no prefix arg +(see also hash_url_prefix for the version with one).

+

This returns a u64, but only the lower 48 bits should ever be set, so casting to +an i64 is totally safe and lossless. If the string has no ‘:’ in it, then the +returned hash will be a 32 bit hash.

+
\ No newline at end of file diff --git a/book/rust-docs/places/hash/fn.hash_url_prefix.html b/book/rust-docs/places/hash/fn.hash_url_prefix.html new file mode 100644 index 0000000000..63287aed80 --- /dev/null +++ b/book/rust-docs/places/hash/fn.hash_url_prefix.html @@ -0,0 +1,9 @@ +hash_url_prefix in places::hash - Rust

Function places::hash::hash_url_prefix

source ·
pub fn hash_url_prefix(spec_prefix: &str, mode: PrefixMode) -> u64
Expand description

This should be identical to the “real” mozilla::places::HashURL when given +a prefix arg. Specifically:

+
    +
  • hash_url_prefix(spec, PrefixMode::Lo) is identical to
  • +
  • hash_url_prefix(spec, PrefixMode::Hi) is identical to
  • +
+

As with hash_url, it returns a u64, but only the lower 48 bits should ever be set, so +casting to e.g. an i64 is lossless.

+
\ No newline at end of file diff --git a/book/rust-docs/places/hash/index.html b/book/rust-docs/places/hash/index.html new file mode 100644 index 0000000000..748bab71bc --- /dev/null +++ b/book/rust-docs/places/hash/index.html @@ -0,0 +1,3 @@ +places::hash - Rust

Module places::hash

source ·

Enums

Functions

  • This should return identical results to mozilla::HashString!
  • This should be identical to the “real” mozilla::places::HashURL with no prefix arg +(see also hash_url_prefix for the version with one).
  • This should be identical to the “real” mozilla::places::HashURL when given +a prefix arg. Specifically:
\ No newline at end of file diff --git a/book/rust-docs/places/hash/sidebar-items.js b/book/rust-docs/places/hash/sidebar-items.js new file mode 100644 index 0000000000..32e1713ec1 --- /dev/null +++ b/book/rust-docs/places/hash/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["PrefixMode"],"fn":["hash_string","hash_url","hash_url_prefix"]}; \ No newline at end of file diff --git a/book/rust-docs/places/history_sync/constant.HISTORY_TTL.html b/book/rust-docs/places/history_sync/constant.HISTORY_TTL.html new file mode 100644 index 0000000000..24da233f66 --- /dev/null +++ b/book/rust-docs/places/history_sync/constant.HISTORY_TTL.html @@ -0,0 +1 @@ +HISTORY_TTL in places::history_sync - Rust
pub const HISTORY_TTL: u32 = 5_184_000;
\ No newline at end of file diff --git a/book/rust-docs/places/history_sync/engine/constant.COLLECTION_SYNCID_META_KEY.html b/book/rust-docs/places/history_sync/engine/constant.COLLECTION_SYNCID_META_KEY.html new file mode 100644 index 0000000000..3c96ec1213 --- /dev/null +++ b/book/rust-docs/places/history_sync/engine/constant.COLLECTION_SYNCID_META_KEY.html @@ -0,0 +1 @@ +COLLECTION_SYNCID_META_KEY in places::history_sync::engine - Rust
pub const COLLECTION_SYNCID_META_KEY: &str = "history_sync_id";
\ No newline at end of file diff --git a/book/rust-docs/places/history_sync/engine/constant.GLOBAL_SYNCID_META_KEY.html b/book/rust-docs/places/history_sync/engine/constant.GLOBAL_SYNCID_META_KEY.html new file mode 100644 index 0000000000..5c570086ae --- /dev/null +++ b/book/rust-docs/places/history_sync/engine/constant.GLOBAL_SYNCID_META_KEY.html @@ -0,0 +1 @@ +GLOBAL_SYNCID_META_KEY in places::history_sync::engine - Rust
pub const GLOBAL_SYNCID_META_KEY: &str = "history_global_sync_id";
\ No newline at end of file diff --git a/book/rust-docs/places/history_sync/engine/constant.LAST_SYNC_META_KEY.html b/book/rust-docs/places/history_sync/engine/constant.LAST_SYNC_META_KEY.html new file mode 100644 index 0000000000..a2691a9786 --- /dev/null +++ b/book/rust-docs/places/history_sync/engine/constant.LAST_SYNC_META_KEY.html @@ -0,0 +1 @@ +LAST_SYNC_META_KEY in places::history_sync::engine - Rust
pub const LAST_SYNC_META_KEY: &str = "history_last_sync_time";
\ No newline at end of file diff --git a/book/rust-docs/places/history_sync/engine/index.html b/book/rust-docs/places/history_sync/engine/index.html new file mode 100644 index 0000000000..e1e8b4a3ca --- /dev/null +++ b/book/rust-docs/places/history_sync/engine/index.html @@ -0,0 +1 @@ +places::history_sync::engine - Rust
\ No newline at end of file diff --git a/book/rust-docs/places/history_sync/engine/sidebar-items.js b/book/rust-docs/places/history_sync/engine/sidebar-items.js new file mode 100644 index 0000000000..96899c636e --- /dev/null +++ b/book/rust-docs/places/history_sync/engine/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"constant":["COLLECTION_SYNCID_META_KEY","GLOBAL_SYNCID_META_KEY","LAST_SYNC_META_KEY"],"struct":["HistorySyncEngine"]}; \ No newline at end of file diff --git a/book/rust-docs/places/history_sync/engine/struct.HistorySyncEngine.html b/book/rust-docs/places/history_sync/engine/struct.HistorySyncEngine.html new file mode 100644 index 0000000000..02b87a5b9b --- /dev/null +++ b/book/rust-docs/places/history_sync/engine/struct.HistorySyncEngine.html @@ -0,0 +1,59 @@ +HistorySyncEngine in places::history_sync::engine - Rust
pub struct HistorySyncEngine {
+    pub db: Arc<SharedPlacesDb>,
+    /* private fields */
+}

Fields§

§db: Arc<SharedPlacesDb>

Implementations§

Trait Implementations§

source§

impl SyncEngine for HistorySyncEngine

source§

fn collection_name(&self) -> Cow<'static, str>

source§

fn stage_incoming( + &self, + inbound: Vec<IncomingBso>, + telem: &mut Engine +) -> Result<()>

Stage some incoming records. This might be called multiple times in the same sync +if we fetch the incoming records in batches. Read more
source§

fn apply( + &self, + timestamp: ServerTimestamp, + _telem: &mut Engine +) -> Result<Vec<OutgoingBso>>

Apply the staged records, returning outgoing records. +Ideally we would adjust this model to better support batching of outgoing records +without needing to keep them all in memory (ie, an iterator or similar?)
source§

fn set_uploaded( + &self, + new_timestamp: ServerTimestamp, + ids: Vec<Guid> +) -> Result<()>

Indicates that the given record IDs were uploaded successfully to the server. +This may be called multiple times per sync, once for each batch. Batching is determined +dynamically based on payload sizes and counts via the server’s advertised limits.
source§

fn sync_finished(&self) -> Result<()>

Called once the sync is finished. Not currently called if uploads fail (which +seems sad, but the other batching confusion there needs sorting out first). +Many engines will have nothing to do here, as most “post upload” work should be +done in set_uploaded()
source§

fn get_collection_request( + &self, + server_timestamp: ServerTimestamp +) -> Result<Option<CollectionRequest>>

The engine is responsible for building a single collection request. Engines +typically will store a lastModified timestamp and use that to build a +request saying “give me full records since that date” - however, other +engines might do something fancier. It can return None if the server timestamp +has not advanced since the last sync. +This could even later be extended to handle “backfills”, and we might end up +wanting one engine to use multiple collections (eg, as a “foreign key” via guid), etc.
source§

fn get_sync_assoc(&self) -> Result<EngineSyncAssociation>

Get persisted sync IDs. If they don’t match the global state we’ll be +reset() with the new IDs.
source§

fn reset(&self, assoc: &EngineSyncAssociation) -> Result<()>

Reset the engine (and associated store) without wiping local data, +ready for a “first sync”. +assoc defines how this store is to be associated with sync.
source§

fn wipe(&self) -> Result<()>

§

fn prepare_for_sync( + &self, + _get_client_data: &dyn Fn() -> ClientData +) -> Result<(), Error>

Prepares the engine for syncing. The tabs engine currently uses this to +store the current list of clients, which it uses to look up device names +and types. Read more
§

fn set_local_encryption_key(&mut self, _key: &str) -> Result<(), Error>

Tells the engine what the local encryption key is for the data managed +by the engine. This is only used by collections that store data +encrypted locally and is unrelated to the encryption used by Sync. +The intent is that for such collections, this key can be used to +decrypt local data before it is re-encrypted by Sync and sent to the +storage servers, and similarly, data from the storage servers will be +decrypted by Sync, then encrypted by the local encryption key before +being added to the local database. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/history_sync/index.html b/book/rust-docs/places/history_sync/index.html new file mode 100644 index 0000000000..37514750ef --- /dev/null +++ b/book/rust-docs/places/history_sync/index.html @@ -0,0 +1 @@ +places::history_sync - Rust

Module places::history_sync

source ·

Re-exports

Modules

Structs

Constants

\ No newline at end of file diff --git a/book/rust-docs/places/history_sync/record/index.html b/book/rust-docs/places/history_sync/record/index.html new file mode 100644 index 0000000000..0ba0953369 --- /dev/null +++ b/book/rust-docs/places/history_sync/record/index.html @@ -0,0 +1 @@ +places::history_sync::record - Rust
\ No newline at end of file diff --git a/book/rust-docs/places/history_sync/record/sidebar-items.js b/book/rust-docs/places/history_sync/record/sidebar-items.js new file mode 100644 index 0000000000..dc47593974 --- /dev/null +++ b/book/rust-docs/places/history_sync/record/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"struct":["HistoryRecord","HistoryRecordVisit"]}; \ No newline at end of file diff --git a/book/rust-docs/places/history_sync/record/struct.HistoryRecord.html b/book/rust-docs/places/history_sync/record/struct.HistoryRecord.html new file mode 100644 index 0000000000..db7c958000 --- /dev/null +++ b/book/rust-docs/places/history_sync/record/struct.HistoryRecord.html @@ -0,0 +1,24 @@ +HistoryRecord in places::history_sync::record - Rust
pub struct HistoryRecord {
+    pub id: SyncGuid,
+    pub title: String,
+    pub hist_uri: String,
+    pub visits: Vec<HistoryRecordVisit>,
+    pub unknown_fields: UnknownFields,
+}

Fields§

§id: SyncGuid§title: String§hist_uri: String§visits: Vec<HistoryRecordVisit>§unknown_fields: UnknownFields

Trait Implementations§

source§

impl Clone for HistoryRecord

source§

fn clone(&self) -> HistoryRecord

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for HistoryRecord

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl<'de> Deserialize<'de> for HistoryRecord

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl PartialEq<HistoryRecord> for HistoryRecord

source§

fn eq(&self, other: &HistoryRecord) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Serialize for HistoryRecord

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more
source§

impl Eq for HistoryRecord

source§

impl StructuralEq for HistoryRecord

source§

impl StructuralPartialEq for HistoryRecord

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/places/history_sync/record/struct.HistoryRecordVisit.html b/book/rust-docs/places/history_sync/record/struct.HistoryRecordVisit.html new file mode 100644 index 0000000000..dbeda2ac19 --- /dev/null +++ b/book/rust-docs/places/history_sync/record/struct.HistoryRecordVisit.html @@ -0,0 +1,22 @@ +HistoryRecordVisit in places::history_sync::record - Rust
pub struct HistoryRecordVisit {
+    pub date: ServerVisitTimestamp,
+    pub transition: u8,
+    pub unknown_fields: UnknownFields,
+}

Fields§

§date: ServerVisitTimestamp§transition: u8§unknown_fields: UnknownFields

Trait Implementations§

source§

impl Clone for HistoryRecordVisit

source§

fn clone(&self) -> HistoryRecordVisit

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for HistoryRecordVisit

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for HistoryRecordVisit

source§

fn default() -> HistoryRecordVisit

Returns the “default value” for a type. Read more
source§

impl<'de> Deserialize<'de> for HistoryRecordVisit

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl PartialEq<HistoryRecordVisit> for HistoryRecordVisit

source§

fn eq(&self, other: &HistoryRecordVisit) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Serialize for HistoryRecordVisit

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more
source§

impl Eq for HistoryRecordVisit

source§

impl StructuralEq for HistoryRecordVisit

source§

impl StructuralPartialEq for HistoryRecordVisit

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/places/history_sync/sidebar-items.js b/book/rust-docs/places/history_sync/sidebar-items.js new file mode 100644 index 0000000000..c3410c2330 --- /dev/null +++ b/book/rust-docs/places/history_sync/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"constant":["HISTORY_TTL"],"mod":["engine","record"],"struct":["ServerVisitTimestamp"]}; \ No newline at end of file diff --git a/book/rust-docs/places/history_sync/struct.ServerVisitTimestamp.html b/book/rust-docs/places/history_sync/struct.ServerVisitTimestamp.html new file mode 100644 index 0000000000..139ab5eeb8 --- /dev/null +++ b/book/rust-docs/places/history_sync/struct.ServerVisitTimestamp.html @@ -0,0 +1,30 @@ +ServerVisitTimestamp in places::history_sync - Rust
pub struct ServerVisitTimestamp(pub u64);
Expand description

Visit timestamps on the server are microseconds since the epoch.

+

Tuple Fields§

§0: u64

Trait Implementations§

source§

impl Clone for ServerVisitTimestamp

source§

fn clone(&self) -> ServerVisitTimestamp

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for ServerVisitTimestamp

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for ServerVisitTimestamp

source§

fn default() -> ServerVisitTimestamp

Returns the “default value” for a type. Read more
source§

impl<'de> Deserialize<'de> for ServerVisitTimestamp

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl Display for ServerVisitTimestamp

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl From<ServerVisitTimestamp> for Timestamp

source§

fn from(ts: ServerVisitTimestamp) -> Timestamp

Converts to this type from the input type.
source§

impl From<SystemTime> for ServerVisitTimestamp

source§

fn from(st: SystemTime) -> Self

Converts to this type from the input type.
source§

impl From<Timestamp> for ServerVisitTimestamp

source§

fn from(ts: Timestamp) -> ServerVisitTimestamp

Converts to this type from the input type.
source§

impl Hash for ServerVisitTimestamp

source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · source§

fn hash_slice<H>(data: &[Self], state: &mut H)where + H: Hasher, + Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
source§

impl Ord for ServerVisitTimestamp

source§

fn cmp(&self, other: &ServerVisitTimestamp) -> Ordering

This method returns an Ordering between self and other. Read more
1.21.0 · source§

fn max(self, other: Self) -> Selfwhere + Self: Sized,

Compares and returns the maximum of two values. Read more
1.21.0 · source§

fn min(self, other: Self) -> Selfwhere + Self: Sized,

Compares and returns the minimum of two values. Read more
1.50.0 · source§

fn clamp(self, min: Self, max: Self) -> Selfwhere + Self: Sized + PartialOrd<Self>,

Restrict a value to a certain interval. Read more
source§

impl PartialEq<ServerVisitTimestamp> for ServerVisitTimestamp

source§

fn eq(&self, other: &ServerVisitTimestamp) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl PartialOrd<ServerVisitTimestamp> for ServerVisitTimestamp

source§

fn partial_cmp(&self, other: &ServerVisitTimestamp) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · source§

fn lt(&self, other: &Rhs) -> bool

This method tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · source§

fn le(&self, other: &Rhs) -> bool

This method tests less than or equal to (for self and other) and is used by the <= +operator. Read more
1.0.0 · source§

fn gt(&self, other: &Rhs) -> bool

This method tests greater than (for self and other) and is used by the > operator. Read more
1.0.0 · source§

fn ge(&self, other: &Rhs) -> bool

This method tests greater than or equal to (for self and other) and is used by the >= +operator. Read more
source§

impl Serialize for ServerVisitTimestamp

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more
source§

impl Copy for ServerVisitTimestamp

source§

impl Eq for ServerVisitTimestamp

source§

impl StructuralEq for ServerVisitTimestamp

source§

impl StructuralPartialEq for ServerVisitTimestamp

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> CallHasher for Twhere + T: Hash + ?Sized,

§

fn get_hash<H, B>(value: &H, build_hasher: &B) -> u64where + H: Hash + ?Sized, + B: BuildHasher,

source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/places/import/common/fn.attached_database.html b/book/rust-docs/places/import/common/fn.attached_database.html new file mode 100644 index 0000000000..c30994993e --- /dev/null +++ b/book/rust-docs/places/import/common/fn.attached_database.html @@ -0,0 +1,5 @@ +attached_database in places::import::common - Rust
pub fn attached_database<'a>(
+    conn: &'a PlacesDb,
+    path: &Url,
+    db_alias: &'static str
+) -> Result<ExecuteOnDrop<'a>>
\ No newline at end of file diff --git a/book/rust-docs/places/import/common/fn.define_history_migration_functions.html b/book/rust-docs/places/import/common/fn.define_history_migration_functions.html new file mode 100644 index 0000000000..20193ab2c2 --- /dev/null +++ b/book/rust-docs/places/import/common/fn.define_history_migration_functions.html @@ -0,0 +1 @@ +define_history_migration_functions in places::import::common - Rust
pub fn define_history_migration_functions(c: &Connection) -> Result<()>
\ No newline at end of file diff --git a/book/rust-docs/places/import/common/fn.select_count.html b/book/rust-docs/places/import/common/fn.select_count.html new file mode 100644 index 0000000000..0ba9556c93 --- /dev/null +++ b/book/rust-docs/places/import/common/fn.select_count.html @@ -0,0 +1 @@ +select_count in places::import::common - Rust

Function places::import::common::select_count

source ·
pub fn select_count(conn: &PlacesDb, stmt: &str) -> Result<u32>
\ No newline at end of file diff --git a/book/rust-docs/places/import/common/index.html b/book/rust-docs/places/import/common/index.html new file mode 100644 index 0000000000..72248d025d --- /dev/null +++ b/book/rust-docs/places/import/common/index.html @@ -0,0 +1,2 @@ +places::import::common - Rust

Module places::import::common

source ·

Modules

Structs

Functions

\ No newline at end of file diff --git a/book/rust-docs/places/import/common/sidebar-items.js b/book/rust-docs/places/import/common/sidebar-items.js new file mode 100644 index 0000000000..bc6e9376b6 --- /dev/null +++ b/book/rust-docs/places/import/common/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["attached_database","define_history_migration_functions","select_count"],"mod":["sql_fns"],"struct":["ExecuteOnDrop","HistoryMigrationResult","NOW"]}; \ No newline at end of file diff --git a/book/rust-docs/places/import/common/sql_fns/fn.sanitize_float_timestamp.html b/book/rust-docs/places/import/common/sql_fns/fn.sanitize_float_timestamp.html new file mode 100644 index 0000000000..479db2bea8 --- /dev/null +++ b/book/rust-docs/places/import/common/sql_fns/fn.sanitize_float_timestamp.html @@ -0,0 +1 @@ +sanitize_float_timestamp in places::import::common::sql_fns - Rust
pub fn sanitize_float_timestamp(ctx: &Context<'_>) -> Result<Timestamp>
\ No newline at end of file diff --git a/book/rust-docs/places/import/common/sql_fns/fn.sanitize_integer_timestamp.html b/book/rust-docs/places/import/common/sql_fns/fn.sanitize_integer_timestamp.html new file mode 100644 index 0000000000..5e2ce7c61c --- /dev/null +++ b/book/rust-docs/places/import/common/sql_fns/fn.sanitize_integer_timestamp.html @@ -0,0 +1 @@ +sanitize_integer_timestamp in places::import::common::sql_fns - Rust
pub fn sanitize_integer_timestamp(ctx: &Context<'_>) -> Result<Timestamp>
\ No newline at end of file diff --git a/book/rust-docs/places/import/common/sql_fns/fn.sanitize_utf8.html b/book/rust-docs/places/import/common/sql_fns/fn.sanitize_utf8.html new file mode 100644 index 0000000000..16382429b9 --- /dev/null +++ b/book/rust-docs/places/import/common/sql_fns/fn.sanitize_utf8.html @@ -0,0 +1 @@ +sanitize_utf8 in places::import::common::sql_fns - Rust
pub fn sanitize_utf8(ctx: &Context<'_>) -> Result<Option<String>>
\ No newline at end of file diff --git a/book/rust-docs/places/import/common/sql_fns/fn.validate_url.html b/book/rust-docs/places/import/common/sql_fns/fn.validate_url.html new file mode 100644 index 0000000000..0154f4a7af --- /dev/null +++ b/book/rust-docs/places/import/common/sql_fns/fn.validate_url.html @@ -0,0 +1 @@ +validate_url in places::import::common::sql_fns - Rust
pub fn validate_url(ctx: &Context<'_>) -> Result<Option<String>>
\ No newline at end of file diff --git a/book/rust-docs/places/import/common/sql_fns/index.html b/book/rust-docs/places/import/common/sql_fns/index.html new file mode 100644 index 0000000000..6df06741a1 --- /dev/null +++ b/book/rust-docs/places/import/common/sql_fns/index.html @@ -0,0 +1 @@ +places::import::common::sql_fns - Rust
\ No newline at end of file diff --git a/book/rust-docs/places/import/common/sql_fns/sidebar-items.js b/book/rust-docs/places/import/common/sql_fns/sidebar-items.js new file mode 100644 index 0000000000..ca3b1b879d --- /dev/null +++ b/book/rust-docs/places/import/common/sql_fns/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["sanitize_float_timestamp","sanitize_integer_timestamp","sanitize_utf8","validate_url"]}; \ No newline at end of file diff --git a/book/rust-docs/places/import/common/struct.ExecuteOnDrop.html b/book/rust-docs/places/import/common/struct.ExecuteOnDrop.html new file mode 100644 index 0000000000..81407d14b1 --- /dev/null +++ b/book/rust-docs/places/import/common/struct.ExecuteOnDrop.html @@ -0,0 +1,17 @@ +ExecuteOnDrop in places::import::common - Rust
pub struct ExecuteOnDrop<'a> { /* private fields */ }
Expand description

We use/abuse the mirror to perform our import, but need to clean it up +afterwards. This is an RAII helper to do so.

+

Ideally, you should call execute_now rather than letting this drop +automatically, as we can’t report errors beyond logging when running +Drop.

+

Implementations§

source§

impl<'a> ExecuteOnDrop<'a>

source

pub fn new(conn: &'a PlacesDb, sql: String) -> Self

source

pub fn execute_now(self) -> Result<()>

Trait Implementations§

source§

impl Drop for ExecuteOnDrop<'_>

source§

fn drop(&mut self)

Executes the destructor for this type. Read more

Auto Trait Implementations§

§

impl<'a> !RefUnwindSafe for ExecuteOnDrop<'a>

§

impl<'a> !Send for ExecuteOnDrop<'a>

§

impl<'a> !Sync for ExecuteOnDrop<'a>

§

impl<'a> Unpin for ExecuteOnDrop<'a>

§

impl<'a> !UnwindSafe for ExecuteOnDrop<'a>

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/import/common/struct.HistoryMigrationResult.html b/book/rust-docs/places/import/common/struct.HistoryMigrationResult.html new file mode 100644 index 0000000000..25e7dbe7ab --- /dev/null +++ b/book/rust-docs/places/import/common/struct.HistoryMigrationResult.html @@ -0,0 +1,21 @@ +HistoryMigrationResult in places::import::common - Rust
pub struct HistoryMigrationResult {
+    pub num_total: u32,
+    pub num_succeeded: u32,
+    pub num_failed: u32,
+    pub total_duration: u64,
+}

Fields§

§num_total: u32§num_succeeded: u32§num_failed: u32§total_duration: u64

Trait Implementations§

source§

impl Clone for HistoryMigrationResult

source§

fn clone(&self) -> HistoryMigrationResult

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for HistoryMigrationResult

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for HistoryMigrationResult

source§

fn default() -> HistoryMigrationResult

Returns the “default value” for a type. Read more
source§

impl PartialEq<HistoryMigrationResult> for HistoryMigrationResult

source§

fn eq(&self, other: &HistoryMigrationResult) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Serialize for HistoryMigrationResult

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more
source§

impl Eq for HistoryMigrationResult

source§

impl StructuralEq for HistoryMigrationResult

source§

impl StructuralPartialEq for HistoryMigrationResult

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/import/common/struct.NOW.html b/book/rust-docs/places/import/common/struct.NOW.html new file mode 100644 index 0000000000..cc7e4c0464 --- /dev/null +++ b/book/rust-docs/places/import/common/struct.NOW.html @@ -0,0 +1,12 @@ +NOW in places::import::common - Rust

Struct places::import::common::NOW

source ·
pub struct NOW { /* private fields */ }

Methods from Deref<Target = Timestamp>§

pub const EARLIEST: Timestamp = Timestamp(727747200000)

Trait Implementations§

source§

impl Deref for NOW

§

type Target = Timestamp

The resulting type after dereferencing.
source§

fn deref(&self) -> &Timestamp

Dereferences the value.
source§

impl LazyStatic for NOW

Auto Trait Implementations§

§

impl RefUnwindSafe for NOW

§

impl Send for NOW

§

impl Sync for NOW

§

impl Unpin for NOW

§

impl UnwindSafe for NOW

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/import/index.html b/book/rust-docs/places/import/index.html new file mode 100644 index 0000000000..1962290c5a --- /dev/null +++ b/book/rust-docs/places/import/index.html @@ -0,0 +1 @@ +places::import - Rust

Module places::import

source ·

Re-exports

Modules

\ No newline at end of file diff --git a/book/rust-docs/places/import/ios/history/fn.import.html b/book/rust-docs/places/import/ios/history/fn.import.html new file mode 100644 index 0000000000..625d9733f7 --- /dev/null +++ b/book/rust-docs/places/import/ios/history/fn.import.html @@ -0,0 +1,23 @@ +import in places::import::ios::history - Rust

Function places::import::ios::history::import

source ·
pub fn import(
+    conn: &PlacesDb,
+    path: impl AsRef<Path>,
+    last_sync_timestamp: i64
+) -> Result<HistoryMigrationResult>
Expand description

This import is used for iOS users migrating from browser.db-based +history storage to the new rust-places store.

+

The goal of this import is to persist all local browser.db items into places database

+

Basic process

+
    +
  • Attach the iOS database.
  • +
  • Slurp records into a temp table “iOSHistoryStaging” from iOS database. +
      +
    • This is mostly done for convenience, to punycode the URLs and some performance benefits over +using a view or reading things into Rust
    • +
    +
  • +
  • Add any entries to moz_places that are needed (in practice, most are +needed, users in practice don’t have nearly as many bookmarks as history entries)
  • +
  • Use iosHistoryStaging and the browser.db to migrate visits to the places visits table.
  • +
  • Update frecency for new items.
  • +
  • Cleanup (detach iOS database, etc).
  • +
+
\ No newline at end of file diff --git a/book/rust-docs/places/import/ios/history/index.html b/book/rust-docs/places/import/ios/history/index.html new file mode 100644 index 0000000000..2cf2f78d6d --- /dev/null +++ b/book/rust-docs/places/import/ios/history/index.html @@ -0,0 +1,2 @@ +places::import::ios::history - Rust

Module places::import::ios::history

source ·

Functions

  • This import is used for iOS users migrating from browser.db-based +history storage to the new rust-places store.
\ No newline at end of file diff --git a/book/rust-docs/places/import/ios/history/sidebar-items.js b/book/rust-docs/places/import/ios/history/sidebar-items.js new file mode 100644 index 0000000000..72aa5b8ecb --- /dev/null +++ b/book/rust-docs/places/import/ios/history/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["import"]}; \ No newline at end of file diff --git a/book/rust-docs/places/import/ios/index.html b/book/rust-docs/places/import/ios/index.html new file mode 100644 index 0000000000..6524b6f5af --- /dev/null +++ b/book/rust-docs/places/import/ios/index.html @@ -0,0 +1 @@ +places::import::ios - Rust

Module places::import::ios

source ·

Re-exports

  • pub use history::import as import_history;

Modules

\ No newline at end of file diff --git a/book/rust-docs/places/import/ios/sidebar-items.js b/book/rust-docs/places/import/ios/sidebar-items.js new file mode 100644 index 0000000000..c8e2e106bd --- /dev/null +++ b/book/rust-docs/places/import/ios/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"mod":["history"]}; \ No newline at end of file diff --git a/book/rust-docs/places/import/sidebar-items.js b/book/rust-docs/places/import/sidebar-items.js new file mode 100644 index 0000000000..eaffb4a00d --- /dev/null +++ b/book/rust-docs/places/import/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"mod":["common","ios"]}; \ No newline at end of file diff --git a/book/rust-docs/places/index.html b/book/rust-docs/places/index.html new file mode 100644 index 0000000000..7cf91787ff --- /dev/null +++ b/book/rust-docs/places/index.html @@ -0,0 +1 @@ +places - Rust

Crate places

source ·

Re-exports

Modules

\ No newline at end of file diff --git a/book/rust-docs/places/match_impl/enum.MatchBehavior.html b/book/rust-docs/places/match_impl/enum.MatchBehavior.html new file mode 100644 index 0000000000..62ae29e09a --- /dev/null +++ b/book/rust-docs/places/match_impl/enum.MatchBehavior.html @@ -0,0 +1,30 @@ +MatchBehavior in places::match_impl - Rust
#[repr(u32)]
pub enum MatchBehavior { + Anywhere, + BoundaryAnywhere, + Boundary, + Beginning, + AnywhereUnmodified, + BeginningCaseSensitive, +}

Variants§

§

Anywhere

§

BoundaryAnywhere

Match first on word boundaries, and if we do not get enough results, then +match anywhere in each searchable term.

+
§

Boundary

Match on word boundaries in each searchable term.

+
§

Beginning

Match only the beginning of each search term.

+
§

AnywhereUnmodified

Match anywhere in each searchable term without doing any transformation +or stripping on the underlying data.

+
§

BeginningCaseSensitive

Match only the beginning of each search term using a case sensitive +comparator

+

Trait Implementations§

source§

impl Clone for MatchBehavior

source§

fn clone(&self) -> MatchBehavior

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for MatchBehavior

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl FromSql for MatchBehavior

source§

fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self>

Converts SQLite value into Rust value.
source§

impl PartialEq<MatchBehavior> for MatchBehavior

source§

fn eq(&self, other: &MatchBehavior) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl ToSql for MatchBehavior

source§

fn to_sql(&self) -> Result<ToSqlOutput<'_>>

Converts Rust value to SQLite value
source§

impl Copy for MatchBehavior

source§

impl Eq for MatchBehavior

source§

impl StructuralEq for MatchBehavior

source§

impl StructuralPartialEq for MatchBehavior

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/match_impl/fn.find_in_string.html b/book/rust-docs/places/match_impl/fn.find_in_string.html new file mode 100644 index 0000000000..ab0fd38e13 --- /dev/null +++ b/book/rust-docs/places/match_impl/fn.find_in_string.html @@ -0,0 +1 @@ +find_in_string in places::match_impl - Rust
pub fn find_in_string(token: &str, src: &str, only_boundary: bool) -> bool
\ No newline at end of file diff --git a/book/rust-docs/places/match_impl/index.html b/book/rust-docs/places/match_impl/index.html new file mode 100644 index 0000000000..26cd1f192a --- /dev/null +++ b/book/rust-docs/places/match_impl/index.html @@ -0,0 +1 @@ +places::match_impl - Rust
\ No newline at end of file diff --git a/book/rust-docs/places/match_impl/sidebar-items.js b/book/rust-docs/places/match_impl/sidebar-items.js new file mode 100644 index 0000000000..2ddacec82d --- /dev/null +++ b/book/rust-docs/places/match_impl/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["MatchBehavior"],"fn":["find_in_string"],"struct":["AutocompleteMatch","SearchBehavior"]}; \ No newline at end of file diff --git a/book/rust-docs/places/match_impl/struct.AutocompleteMatch.html b/book/rust-docs/places/match_impl/struct.AutocompleteMatch.html new file mode 100644 index 0000000000..8520b3ac72 --- /dev/null +++ b/book/rust-docs/places/match_impl/struct.AutocompleteMatch.html @@ -0,0 +1,23 @@ +AutocompleteMatch in places::match_impl - Rust
pub struct AutocompleteMatch<'search, 'url, 'title, 'tags> {
+    pub search_str: &'search str,
+    pub url_str: &'url str,
+    pub title_str: &'title str,
+    pub tags: &'tags str,
+    pub visit_count: u32,
+    pub typed: bool,
+    pub bookmarked: bool,
+    pub open_page_count: u32,
+    pub match_behavior: MatchBehavior,
+    pub search_behavior: SearchBehavior,
+}

Fields§

§search_str: &'search str§url_str: &'url str§title_str: &'title str§tags: &'tags str§visit_count: u32§typed: bool§bookmarked: bool§open_page_count: u32§match_behavior: MatchBehavior§search_behavior: SearchBehavior

Implementations§

source§

impl<'search, 'url, 'title, 'tags> AutocompleteMatch<'search, 'url, 'title, 'tags>

source

pub fn invoke(&self) -> bool

Auto Trait Implementations§

§

impl<'search, 'url, 'title, 'tags> RefUnwindSafe for AutocompleteMatch<'search, 'url, 'title, 'tags>

§

impl<'search, 'url, 'title, 'tags> Send for AutocompleteMatch<'search, 'url, 'title, 'tags>

§

impl<'search, 'url, 'title, 'tags> Sync for AutocompleteMatch<'search, 'url, 'title, 'tags>

§

impl<'search, 'url, 'title, 'tags> Unpin for AutocompleteMatch<'search, 'url, 'title, 'tags>

§

impl<'search, 'url, 'title, 'tags> UnwindSafe for AutocompleteMatch<'search, 'url, 'title, 'tags>

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/match_impl/struct.SearchBehavior.html b/book/rust-docs/places/match_impl/struct.SearchBehavior.html new file mode 100644 index 0000000000..4df6d64f6e --- /dev/null +++ b/book/rust-docs/places/match_impl/struct.SearchBehavior.html @@ -0,0 +1,104 @@ +SearchBehavior in places::match_impl - Rust
pub struct SearchBehavior { /* private fields */ }

Implementations§

source§

impl SearchBehavior

source

pub const HISTORY: Self = _

Search through history.

+
source

pub const BOOKMARK: Self = _

Search through bookmarks.

+
source

pub const TAG: Self = _

Search through tags.

+
source

pub const TITLE: Self = _

Search through the title of pages.

+
source

pub const URL: Self = _

Search the URL of pages.

+
source

pub const TYPED: Self = _

Search for typed pages

+
source

pub const JAVASCRIPT: Self = _

Search for javascript: urls

+
source

pub const OPENPAGE: Self = _

Search for open pages (currently not meaningfully implemented)

+
source

pub const RESTRICT: Self = _

Use intersection between history, typed, bookmark, tag and openpage +instead of union, when the restrict bit is set.

+
source

pub const SEARCHES: Self = _

Include search suggestions from the currently selected search provider +(currently not implemented)

+
source

pub const fn empty() -> Self

Returns an empty set of flags.

+
source

pub const fn all() -> Self

Returns the set containing all flags.

+
source

pub const fn bits(&self) -> u32

Returns the raw value of the flags currently stored.

+
source

pub const fn from_bits(bits: u32) -> Option<Self>

Convert from underlying bit representation, unless that +representation contains bits that do not correspond to a flag.

+
source

pub const fn from_bits_truncate(bits: u32) -> Self

Convert from underlying bit representation, dropping any bits +that do not correspond to flags.

+
source

pub const unsafe fn from_bits_unchecked(bits: u32) -> Self

Convert from underlying bit representation, preserving all +bits (even those not corresponding to a defined flag).

+
Safety
+

The caller of the bitflags! macro can chose to allow or +disallow extra bits for their bitflags type.

+

The caller of from_bits_unchecked() has to ensure that +all bits correspond to a defined flag or that extra bits +are valid for this bitflags type.

+
source

pub const fn is_empty(&self) -> bool

Returns true if no flags are currently stored.

+
source

pub const fn is_all(&self) -> bool

Returns true if all flags are currently set.

+
source

pub const fn intersects(&self, other: Self) -> bool

Returns true if there are flags common to both self and other.

+
source

pub const fn contains(&self, other: Self) -> bool

Returns true if all of the flags in other are contained within self.

+
source

pub fn insert(&mut self, other: Self)

Inserts the specified flags in-place.

+
source

pub fn remove(&mut self, other: Self)

Removes the specified flags in-place.

+
source

pub fn toggle(&mut self, other: Self)

Toggles the specified flags in-place.

+
source

pub fn set(&mut self, other: Self, value: bool)

Inserts or removes the specified flags depending on the passed value.

+
source

pub const fn intersection(self, other: Self) -> Self

Returns the intersection between the flags in self and +other.

+

Specifically, the returned set contains only the flags which are +present in both self and other.

+

This is equivalent to using the & operator (e.g. +ops::BitAnd), as in flags & other.

+
source

pub const fn union(self, other: Self) -> Self

Returns the union of between the flags in self and other.

+

Specifically, the returned set contains all flags which are +present in either self or other, including any which are +present in both (see Self::symmetric_difference if that +is undesirable).

+

This is equivalent to using the | operator (e.g. +ops::BitOr), as in flags | other.

+
source

pub const fn difference(self, other: Self) -> Self

Returns the difference between the flags in self and other.

+

Specifically, the returned set contains all flags present in +self, except for the ones present in other.

+

It is also conceptually equivalent to the “bit-clear” operation: +flags & !other (and this syntax is also supported).

+

This is equivalent to using the - operator (e.g. +ops::Sub), as in flags - other.

+
source

pub const fn symmetric_difference(self, other: Self) -> Self

Returns the symmetric difference between the flags +in self and other.

+

Specifically, the returned set contains the flags present which +are present in self or other, but that are not present in +both. Equivalently, it contains the flags present in exactly +one of the sets self and other.

+

This is equivalent to using the ^ operator (e.g. +ops::BitXor), as in flags ^ other.

+
source

pub const fn complement(self) -> Self

Returns the complement of this set of flags.

+

Specifically, the returned set contains all the flags which are +not set in self, but which are allowed for this type.

+

Alternatively, it can be thought of as the set difference +between Self::all() and self (e.g. Self::all() - self)

+

This is equivalent to using the ! operator (e.g. +ops::Not), as in !flags.

+
source§

impl SearchBehavior

source

pub fn any() -> Self

Trait Implementations§

source§

impl Binary for SearchBehavior

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter.
source§

impl BitAnd<SearchBehavior> for SearchBehavior

source§

fn bitand(self, other: Self) -> Self

Returns the intersection between the two sets of flags.

+
§

type Output = SearchBehavior

The resulting type after applying the & operator.
source§

impl BitAndAssign<SearchBehavior> for SearchBehavior

source§

fn bitand_assign(&mut self, other: Self)

Disables all flags disabled in the set.

+
source§

impl BitOr<SearchBehavior> for SearchBehavior

source§

fn bitor(self, other: SearchBehavior) -> Self

Returns the union of the two sets of flags.

+
§

type Output = SearchBehavior

The resulting type after applying the | operator.
source§

impl BitOrAssign<SearchBehavior> for SearchBehavior

source§

fn bitor_assign(&mut self, other: Self)

Adds the set of flags.

+
source§

impl BitXor<SearchBehavior> for SearchBehavior

source§

fn bitxor(self, other: Self) -> Self

Returns the left flags, but with all the right flags toggled.

+
§

type Output = SearchBehavior

The resulting type after applying the ^ operator.
source§

impl BitXorAssign<SearchBehavior> for SearchBehavior

source§

fn bitxor_assign(&mut self, other: Self)

Toggles the set of flags.

+
source§

impl Clone for SearchBehavior

source§

fn clone(&self) -> SearchBehavior

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for SearchBehavior

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for SearchBehavior

source§

fn default() -> SearchBehavior

Returns the “default value” for a type. Read more
source§

impl Extend<SearchBehavior> for SearchBehavior

source§

fn extend<T: IntoIterator<Item = Self>>(&mut self, iterator: T)

Extends a collection with the contents of an iterator. Read more
source§

fn extend_one(&mut self, item: A)

🔬This is a nightly-only experimental API. (extend_one)
Extends a collection with exactly one element.
source§

fn extend_reserve(&mut self, additional: usize)

🔬This is a nightly-only experimental API. (extend_one)
Reserves capacity in a collection for the given number of additional elements. Read more
source§

impl FromIterator<SearchBehavior> for SearchBehavior

source§

fn from_iter<T: IntoIterator<Item = Self>>(iterator: T) -> Self

Creates a value from an iterator. Read more
source§

impl FromSql for SearchBehavior

source§

fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self>

Converts SQLite value into Rust value.
source§

impl Hash for SearchBehavior

source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · source§

fn hash_slice<H>(data: &[Self], state: &mut H)where + H: Hasher, + Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
source§

impl LowerHex for SearchBehavior

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter.
source§

impl Not for SearchBehavior

source§

fn not(self) -> Self

Returns the complement of this set of flags.

+
§

type Output = SearchBehavior

The resulting type after applying the ! operator.
source§

impl Octal for SearchBehavior

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter.
source§

impl Ord for SearchBehavior

source§

fn cmp(&self, other: &SearchBehavior) -> Ordering

This method returns an Ordering between self and other. Read more
1.21.0 · source§

fn max(self, other: Self) -> Selfwhere + Self: Sized,

Compares and returns the maximum of two values. Read more
1.21.0 · source§

fn min(self, other: Self) -> Selfwhere + Self: Sized,

Compares and returns the minimum of two values. Read more
1.50.0 · source§

fn clamp(self, min: Self, max: Self) -> Selfwhere + Self: Sized + PartialOrd<Self>,

Restrict a value to a certain interval. Read more
source§

impl PartialEq<SearchBehavior> for SearchBehavior

source§

fn eq(&self, other: &SearchBehavior) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl PartialOrd<SearchBehavior> for SearchBehavior

source§

fn partial_cmp(&self, other: &SearchBehavior) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · source§

fn lt(&self, other: &Rhs) -> bool

This method tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · source§

fn le(&self, other: &Rhs) -> bool

This method tests less than or equal to (for self and other) and is used by the <= +operator. Read more
1.0.0 · source§

fn gt(&self, other: &Rhs) -> bool

This method tests greater than (for self and other) and is used by the > operator. Read more
1.0.0 · source§

fn ge(&self, other: &Rhs) -> bool

This method tests greater than or equal to (for self and other) and is used by the >= +operator. Read more
source§

impl Sub<SearchBehavior> for SearchBehavior

source§

fn sub(self, other: Self) -> Self

Returns the set difference of the two sets of flags.

+
§

type Output = SearchBehavior

The resulting type after applying the - operator.
source§

impl SubAssign<SearchBehavior> for SearchBehavior

source§

fn sub_assign(&mut self, other: Self)

Disables all flags enabled in the set.

+
source§

impl ToSql for SearchBehavior

source§

fn to_sql(&self) -> Result<ToSqlOutput<'_>>

Converts Rust value to SQLite value
source§

impl UpperHex for SearchBehavior

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter.
source§

impl Copy for SearchBehavior

source§

impl Eq for SearchBehavior

source§

impl StructuralEq for SearchBehavior

source§

impl StructuralPartialEq for SearchBehavior

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> CallHasher for Twhere + T: Hash + ?Sized,

§

fn get_hash<H, B>(value: &H, build_hasher: &B) -> u64where + H: Hash + ?Sized, + B: BuildHasher,

source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/observation/index.html b/book/rust-docs/places/observation/index.html new file mode 100644 index 0000000000..07d8c1c5ba --- /dev/null +++ b/book/rust-docs/places/observation/index.html @@ -0,0 +1,3 @@ +places::observation - Rust

Module places::observation

source ·

Structs

  • An “observation” based model for updating history. +You create a VisitObservation, call functions on it which correspond +with what you observed. The page will then be updated using this info.
\ No newline at end of file diff --git a/book/rust-docs/places/observation/sidebar-items.js b/book/rust-docs/places/observation/sidebar-items.js new file mode 100644 index 0000000000..25342a4f34 --- /dev/null +++ b/book/rust-docs/places/observation/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"struct":["VisitObservation"]}; \ No newline at end of file diff --git a/book/rust-docs/places/observation/struct.VisitObservation.html b/book/rust-docs/places/observation/struct.VisitObservation.html new file mode 100644 index 0000000000..1051b5fcc7 --- /dev/null +++ b/book/rust-docs/places/observation/struct.VisitObservation.html @@ -0,0 +1,38 @@ +VisitObservation in places::observation - Rust
pub struct VisitObservation {
+    pub url: Url,
+    pub title: Option<String>,
+    pub visit_type: Option<VisitType>,
+    pub is_error: Option<bool>,
+    pub is_redirect_source: Option<bool>,
+    pub is_permanent_redirect_source: Option<bool>,
+    pub at: Option<Timestamp>,
+    pub referrer: Option<Url>,
+    pub is_remote: Option<bool>,
+    pub preview_image_url: Option<Url>,
+}
Expand description

An “observation” based model for updating history. +You create a VisitObservation, call functions on it which correspond +with what you observed. The page will then be updated using this info.

+

It’s implemented such that the making of an “observation” is itself +significant - it records what specific changes should be made to storage. +For example, instead of simple bools with defaults (where you can’t +differentiate explicitly setting a value from the default value), we use +Option, with the idea being it’s better for a store shaped like Mentat.

+

It exposes a “builder api”, but for convenience, that API allows Options too. +So, eg, .with_title(None) or with_is_error(None) is allowed but records +no observation.

+

Fields§

§url: Url§title: Option<String>§visit_type: Option<VisitType>§is_error: Option<bool>§is_redirect_source: Option<bool>§is_permanent_redirect_source: Option<bool>§at: Option<Timestamp>§referrer: Option<Url>§is_remote: Option<bool>§preview_image_url: Option<Url>

Implementations§

source§

impl VisitObservation

source

pub fn new(url: Url) -> Self

source

pub fn with_title(self, t: impl Into<Option<String>>) -> Self

source

pub fn with_visit_type(self, t: impl Into<Option<VisitType>>) -> Self

source

pub fn with_is_error(self, v: impl Into<Option<bool>>) -> Self

source

pub fn with_is_redirect_source(self, v: impl Into<Option<bool>>) -> Self

source

pub fn with_is_permanent_redirect_source( + self, + v: impl Into<Option<bool>> +) -> Self

source

pub fn with_at(self, v: impl Into<Option<Timestamp>>) -> Self

source

pub fn with_is_remote(self, v: impl Into<Option<bool>>) -> Self

source

pub fn with_referrer(self, v: impl Into<Option<Url>>) -> Self

source

pub fn with_preview_image_url(self, v: impl Into<Option<Url>>) -> Self

source

pub fn get_redirect_frecency_boost(&self) -> bool

source

pub fn get_is_hidden(&self) -> bool

Trait Implementations§

source§

impl Clone for VisitObservation

source§

fn clone(&self) -> VisitObservation

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for VisitObservation

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/sidebar-items.js b/book/rust-docs/places/sidebar-items.js new file mode 100644 index 0000000000..b4e7a7b6dc --- /dev/null +++ b/book/rust-docs/places/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"mod":["api","bookmark_sync","db","error","ffi","frecency","hash","history_sync","import","match_impl","observation","storage","types"]}; \ No newline at end of file diff --git a/book/rust-docs/places/storage/bookmarks/bookmark_sync/fn.create_synced_bookmark_roots.html b/book/rust-docs/places/storage/bookmarks/bookmark_sync/fn.create_synced_bookmark_roots.html new file mode 100644 index 0000000000..b3ec44c720 --- /dev/null +++ b/book/rust-docs/places/storage/bookmarks/bookmark_sync/fn.create_synced_bookmark_roots.html @@ -0,0 +1,3 @@ +create_synced_bookmark_roots in places::storage::bookmarks::bookmark_sync - Rust
pub fn create_synced_bookmark_roots(db: &Connection) -> Result<()>
Expand description

Sets up the syncable roots. All items in moz_bookmarks_synced descend +from these roots.

+
\ No newline at end of file diff --git a/book/rust-docs/places/storage/bookmarks/bookmark_sync/index.html b/book/rust-docs/places/storage/bookmarks/bookmark_sync/index.html new file mode 100644 index 0000000000..3ab39f5ba7 --- /dev/null +++ b/book/rust-docs/places/storage/bookmarks/bookmark_sync/index.html @@ -0,0 +1,2 @@ +places::storage::bookmarks::bookmark_sync - Rust

Functions

\ No newline at end of file diff --git a/book/rust-docs/places/storage/bookmarks/bookmark_sync/sidebar-items.js b/book/rust-docs/places/storage/bookmarks/bookmark_sync/sidebar-items.js new file mode 100644 index 0000000000..dd3dfdec9e --- /dev/null +++ b/book/rust-docs/places/storage/bookmarks/bookmark_sync/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["create_synced_bookmark_roots"]}; \ No newline at end of file diff --git a/book/rust-docs/places/storage/bookmarks/constant.USER_CONTENT_ROOTS.html b/book/rust-docs/places/storage/bookmarks/constant.USER_CONTENT_ROOTS.html new file mode 100644 index 0000000000..a3361d324d --- /dev/null +++ b/book/rust-docs/places/storage/bookmarks/constant.USER_CONTENT_ROOTS.html @@ -0,0 +1 @@ +USER_CONTENT_ROOTS in places::storage::bookmarks - Rust
pub const USER_CONTENT_ROOTS: &[BookmarkRootGuid];
\ No newline at end of file diff --git a/book/rust-docs/places/storage/bookmarks/enum.BookmarkPosition.html b/book/rust-docs/places/storage/bookmarks/enum.BookmarkPosition.html new file mode 100644 index 0000000000..9a98692d8c --- /dev/null +++ b/book/rust-docs/places/storage/bookmarks/enum.BookmarkPosition.html @@ -0,0 +1,18 @@ +BookmarkPosition in places::storage::bookmarks - Rust
pub enum BookmarkPosition {
+    Specific {
+        pos: u32,
+    },
+    Append,
+}

Variants§

§

Specific

Fields

§pos: u32
§

Append

Trait Implementations§

source§

impl Clone for BookmarkPosition

source§

fn clone(&self) -> BookmarkPosition

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for BookmarkPosition

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Copy for BookmarkPosition

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/storage/bookmarks/enum.BookmarkRootGuid.html b/book/rust-docs/places/storage/bookmarks/enum.BookmarkRootGuid.html new file mode 100644 index 0000000000..6e22037a3b --- /dev/null +++ b/book/rust-docs/places/storage/bookmarks/enum.BookmarkRootGuid.html @@ -0,0 +1,42 @@ +BookmarkRootGuid in places::storage::bookmarks - Rust
#[repr(u8)]
pub enum BookmarkRootGuid { + Root, + Menu, + Toolbar, + Unfiled, + Mobile, +}
Expand description

Special GUIDs associated with bookmark roots. +It’s guaranteed that the roots will always have these guids.

+

Variants§

§

Root

§

Menu

§

Toolbar

§

Unfiled

§

Mobile

Implementations§

source§

impl BookmarkRootGuid

source

pub fn as_str(self) -> &'static str

source

pub fn guid(self) -> &'static SyncGuid

source

pub fn as_guid(self) -> SyncGuid

source

pub fn well_known(guid: &str) -> Option<Self>

source

pub fn from_guid(guid: &SyncGuid) -> Option<Self>

Trait Implementations§

source§

impl Clone for BookmarkRootGuid

source§

fn clone(&self) -> BookmarkRootGuid

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for BookmarkRootGuid

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl From<BookmarkRootGuid> for SyncGuid

source§

fn from(item: BookmarkRootGuid) -> SyncGuid

Converts to this type from the input type.
source§

impl Hash for BookmarkRootGuid

source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · source§

fn hash_slice<H>(data: &[Self], state: &mut H)where + H: Hasher, + Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
source§

impl<'a> PartialEq<&'a Guid> for BookmarkRootGuid

source§

fn eq(&self, other: &&'a SyncGuid) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl<'a> PartialEq<&'a str> for BookmarkRootGuid

source§

fn eq(&self, other: &&'a str) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl<'a> PartialEq<BookmarkRootGuid> for &'a SyncGuid

source§

fn eq(&self, other: &BookmarkRootGuid) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl<'a> PartialEq<BookmarkRootGuid> for &'a str

source§

fn eq(&self, other: &BookmarkRootGuid) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl PartialEq<BookmarkRootGuid> for BookmarkRootGuid

source§

fn eq(&self, other: &BookmarkRootGuid) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl PartialEq<BookmarkRootGuid> for SyncGuid

source§

fn eq(&self, other: &BookmarkRootGuid) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl PartialEq<Guid> for BookmarkRootGuid

source§

fn eq(&self, other: &SyncGuid) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl PartialOrd<BookmarkRootGuid> for BookmarkRootGuid

source§

fn partial_cmp(&self, other: &BookmarkRootGuid) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · source§

fn lt(&self, other: &Rhs) -> bool

This method tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · source§

fn le(&self, other: &Rhs) -> bool

This method tests less than or equal to (for self and other) and is used by the <= +operator. Read more
1.0.0 · source§

fn gt(&self, other: &Rhs) -> bool

This method tests greater than (for self and other) and is used by the > operator. Read more
1.0.0 · source§

fn ge(&self, other: &Rhs) -> bool

This method tests greater than or equal to (for self and other) and is used by the >= +operator. Read more
source§

impl Copy for BookmarkRootGuid

source§

impl Eq for BookmarkRootGuid

source§

impl StructuralEq for BookmarkRootGuid

source§

impl StructuralPartialEq for BookmarkRootGuid

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> CallHasher for Twhere + T: Hash + ?Sized,

§

fn get_hash<H, B>(value: &H, build_hasher: &B) -> u64where + H: Hash + ?Sized, + B: BuildHasher,

source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/storage/bookmarks/enum.InsertableItem.html b/book/rust-docs/places/storage/bookmarks/enum.InsertableItem.html new file mode 100644 index 0000000000..532c4fd39b --- /dev/null +++ b/book/rust-docs/places/storage/bookmarks/enum.InsertableItem.html @@ -0,0 +1,23 @@ +InsertableItem in places::storage::bookmarks - Rust
pub enum InsertableItem {
+    Bookmark {
+        b: InsertableBookmark,
+    },
+    Separator {
+        s: InsertableSeparator,
+    },
+    Folder {
+        f: InsertableFolder,
+    },
+}

Variants§

§

Bookmark

§

Separator

§

Folder

Trait Implementations§

source§

impl Clone for InsertableItem

source§

fn clone(&self) -> InsertableItem

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for InsertableItem

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl From<BookmarkTreeNode> for InsertableItem

source§

fn from(node: BookmarkTreeNode) -> Self

Converts to this type from the input type.
source§

impl From<InsertableBookmark> for InsertableItem

source§

fn from(b: InsertableBookmark) -> Self

Converts to this type from the input type.
source§

impl From<InsertableFolder> for InsertableItem

source§

fn from(f: InsertableFolder) -> Self

Converts to this type from the input type.
source§

impl From<InsertableSeparator> for InsertableItem

source§

fn from(s: InsertableSeparator) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/storage/bookmarks/enum.UpdatableItem.html b/book/rust-docs/places/storage/bookmarks/enum.UpdatableItem.html new file mode 100644 index 0000000000..17f5ef7d76 --- /dev/null +++ b/book/rust-docs/places/storage/bookmarks/enum.UpdatableItem.html @@ -0,0 +1,23 @@ +UpdatableItem in places::storage::bookmarks - Rust
pub enum UpdatableItem {
+    Bookmark {
+        b: UpdatableBookmark,
+    },
+    Separator {
+        s: UpdatableSeparator,
+    },
+    Folder {
+        f: UpdatableFolder,
+    },
+}

Variants§

§

Bookmark

§

Separator

§

Folder

Implementations§

Trait Implementations§

source§

impl Clone for UpdatableItem

source§

fn clone(&self) -> UpdatableItem

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for UpdatableItem

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl From<UpdatableBookmark> for UpdatableItem

source§

fn from(b: UpdatableBookmark) -> Self

Converts to this type from the input type.
source§

impl From<UpdatableFolder> for UpdatableItem

source§

fn from(f: UpdatableFolder) -> Self

Converts to this type from the input type.
source§

impl From<UpdatableSeparator> for UpdatableItem

source§

fn from(s: UpdatableSeparator) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/storage/bookmarks/enum.UpdateTreeLocation.html b/book/rust-docs/places/storage/bookmarks/enum.UpdateTreeLocation.html new file mode 100644 index 0000000000..587dfea74c --- /dev/null +++ b/book/rust-docs/places/storage/bookmarks/enum.UpdateTreeLocation.html @@ -0,0 +1,24 @@ +UpdateTreeLocation in places::storage::bookmarks - Rust
pub enum UpdateTreeLocation {
+    None,
+    Position {
+        pos: BookmarkPosition,
+    },
+    Parent {
+        guid: SyncGuid,
+        pos: BookmarkPosition,
+    },
+}
Expand description

Support for modifying bookmarks, including changing the location in +the tree.

+

Variants§

§

None

§

Position

§

Parent

Trait Implementations§

source§

impl Clone for UpdateTreeLocation

source§

fn clone(&self) -> UpdateTreeLocation

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for UpdateTreeLocation

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for UpdateTreeLocation

source§

fn default() -> UpdateTreeLocation

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/storage/bookmarks/fetch/enum.Item.html b/book/rust-docs/places/storage/bookmarks/fetch/enum.Item.html new file mode 100644 index 0000000000..caba1d5aec --- /dev/null +++ b/book/rust-docs/places/storage/bookmarks/fetch/enum.Item.html @@ -0,0 +1,23 @@ +Item in places::storage::bookmarks::fetch - Rust
pub enum Item {
+    Bookmark {
+        b: BookmarkData,
+    },
+    Separator {
+        s: Separator,
+    },
+    Folder {
+        f: Folder,
+    },
+}

Variants§

§

Bookmark

§

Separator

Fields

§

Folder

Fields

Implementations§

source§

impl Item

source

pub fn guid(&self) -> &SyncGuid

source

pub fn position(&self) -> &u32

source

pub fn date_added(&self) -> &Timestamp

source

pub fn last_modified(&self) -> &Timestamp

source

pub fn parent_guid(&self) -> Option<&SyncGuid>

Trait Implementations§

source§

impl Clone for Item

source§

fn clone(&self) -> Item

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for Item

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl From<BookmarkData> for Item

source§

fn from(b: BookmarkData) -> Self

Converts to this type from the input type.
source§

impl From<Folder> for Item

source§

fn from(f: Folder) -> Self

Converts to this type from the input type.
source§

impl From<Separator> for Item

source§

fn from(s: Separator) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

§

impl RefUnwindSafe for Item

§

impl Send for Item

§

impl Sync for Item

§

impl Unpin for Item

§

impl UnwindSafe for Item

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/storage/bookmarks/fetch/fn.fetch_bookmark.html b/book/rust-docs/places/storage/bookmarks/fetch/fn.fetch_bookmark.html new file mode 100644 index 0000000000..e479c8d254 --- /dev/null +++ b/book/rust-docs/places/storage/bookmarks/fetch/fn.fetch_bookmark.html @@ -0,0 +1,9 @@ +fetch_bookmark in places::storage::bookmarks::fetch - Rust
pub fn fetch_bookmark(
+    db: &PlacesDb,
+    item_guid: &SyncGuid,
+    get_direct_children: bool
+) -> Result<Option<Item>>
Expand description

This is similar to fetch_tree, but does not recursively fetch children of +folders.

+

If get_direct_children is true, it will return 1 level of folder children, +otherwise it returns just their guids.

+
\ No newline at end of file diff --git a/book/rust-docs/places/storage/bookmarks/fetch/fn.fetch_bookmarks_by_url.html b/book/rust-docs/places/storage/bookmarks/fetch/fn.fetch_bookmarks_by_url.html new file mode 100644 index 0000000000..5acf6d6471 --- /dev/null +++ b/book/rust-docs/places/storage/bookmarks/fetch/fn.fetch_bookmarks_by_url.html @@ -0,0 +1,4 @@ +fetch_bookmarks_by_url in places::storage::bookmarks::fetch - Rust
pub fn fetch_bookmarks_by_url(
+    db: &PlacesDb,
+    url: &Url
+) -> Result<Vec<BookmarkData>>
\ No newline at end of file diff --git a/book/rust-docs/places/storage/bookmarks/fetch/fn.fetch_tree.html b/book/rust-docs/places/storage/bookmarks/fetch/fn.fetch_tree.html new file mode 100644 index 0000000000..39bfb4b16b --- /dev/null +++ b/book/rust-docs/places/storage/bookmarks/fetch/fn.fetch_tree.html @@ -0,0 +1,3 @@ +fetch_tree in places::storage::bookmarks::fetch - Rust
pub fn fetch_tree(db: &PlacesDb, item_guid: &SyncGuid) -> Result<Option<Item>>
Expand description

Call fetch_tree_with_depth with FetchDepth::Deepest. +This is the function called by the FFI when requesting the tree.

+
\ No newline at end of file diff --git a/book/rust-docs/places/storage/bookmarks/fetch/fn.fetch_tree_with_depth.html b/book/rust-docs/places/storage/bookmarks/fetch/fn.fetch_tree_with_depth.html new file mode 100644 index 0000000000..acd5982cee --- /dev/null +++ b/book/rust-docs/places/storage/bookmarks/fetch/fn.fetch_tree_with_depth.html @@ -0,0 +1,7 @@ +fetch_tree_with_depth in places::storage::bookmarks::fetch - Rust
pub fn fetch_tree_with_depth(
+    db: &PlacesDb,
+    item_guid: &SyncGuid,
+    target_depth: &FetchDepth
+) -> Result<Option<Item>>
Expand description

Call fetch_tree with a depth parameter and convert the result +to an Item.

+
\ No newline at end of file diff --git a/book/rust-docs/places/storage/bookmarks/fetch/fn.recent_bookmarks.html b/book/rust-docs/places/storage/bookmarks/fetch/fn.recent_bookmarks.html new file mode 100644 index 0000000000..09a4ca8c7e --- /dev/null +++ b/book/rust-docs/places/storage/bookmarks/fetch/fn.recent_bookmarks.html @@ -0,0 +1 @@ +recent_bookmarks in places::storage::bookmarks::fetch - Rust
pub fn recent_bookmarks(db: &PlacesDb, limit: u32) -> Result<Vec<BookmarkData>>
\ No newline at end of file diff --git a/book/rust-docs/places/storage/bookmarks/fetch/fn.search_bookmarks.html b/book/rust-docs/places/storage/bookmarks/fetch/fn.search_bookmarks.html new file mode 100644 index 0000000000..1a4952a992 --- /dev/null +++ b/book/rust-docs/places/storage/bookmarks/fetch/fn.search_bookmarks.html @@ -0,0 +1,5 @@ +search_bookmarks in places::storage::bookmarks::fetch - Rust
pub fn search_bookmarks(
+    db: &PlacesDb,
+    search: &str,
+    limit: u32
+) -> Result<Vec<BookmarkData>>
\ No newline at end of file diff --git a/book/rust-docs/places/storage/bookmarks/fetch/index.html b/book/rust-docs/places/storage/bookmarks/fetch/index.html new file mode 100644 index 0000000000..51f4ed6b48 --- /dev/null +++ b/book/rust-docs/places/storage/bookmarks/fetch/index.html @@ -0,0 +1,4 @@ +places::storage::bookmarks::fetch - Rust

Module places::storage::bookmarks::fetch

source ·

Structs

Enums

Functions

\ No newline at end of file diff --git a/book/rust-docs/places/storage/bookmarks/fetch/sidebar-items.js b/book/rust-docs/places/storage/bookmarks/fetch/sidebar-items.js new file mode 100644 index 0000000000..92ed4ad195 --- /dev/null +++ b/book/rust-docs/places/storage/bookmarks/fetch/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["Item"],"fn":["fetch_bookmark","fetch_bookmarks_by_url","fetch_tree","fetch_tree_with_depth","recent_bookmarks","search_bookmarks"],"struct":["BookmarkData","Folder","RECENT_BOOKMARKS_QUERY","SEARCH_QUERY","Separator"]}; \ No newline at end of file diff --git a/book/rust-docs/places/storage/bookmarks/fetch/struct.BookmarkData.html b/book/rust-docs/places/storage/bookmarks/fetch/struct.BookmarkData.html new file mode 100644 index 0000000000..5bbac6edec --- /dev/null +++ b/book/rust-docs/places/storage/bookmarks/fetch/struct.BookmarkData.html @@ -0,0 +1,22 @@ +BookmarkData in places::storage::bookmarks::fetch - Rust
pub struct BookmarkData {
+    pub guid: SyncGuid,
+    pub parent_guid: SyncGuid,
+    pub position: u32,
+    pub date_added: Timestamp,
+    pub last_modified: Timestamp,
+    pub url: Url,
+    pub title: Option<String>,
+}
Expand description

Structs we return when reading bookmarks

+

Fields§

§guid: SyncGuid§parent_guid: SyncGuid§position: u32§date_added: Timestamp§last_modified: Timestamp§url: Url§title: Option<String>

Trait Implementations§

source§

impl Clone for BookmarkData

source§

fn clone(&self) -> BookmarkData

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for BookmarkData

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl From<BookmarkData> for Item

source§

fn from(b: BookmarkData) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/storage/bookmarks/fetch/struct.Folder.html b/book/rust-docs/places/storage/bookmarks/fetch/struct.Folder.html new file mode 100644 index 0000000000..e2607a6ae8 --- /dev/null +++ b/book/rust-docs/places/storage/bookmarks/fetch/struct.Folder.html @@ -0,0 +1,22 @@ +Folder in places::storage::bookmarks::fetch - Rust
pub struct Folder {
+    pub guid: SyncGuid,
+    pub date_added: Timestamp,
+    pub last_modified: Timestamp,
+    pub parent_guid: Option<SyncGuid>,
+    pub position: u32,
+    pub title: Option<String>,
+    pub child_guids: Option<Vec<SyncGuid>>,
+    pub child_nodes: Option<Vec<Item>>,
+}

Fields§

§guid: SyncGuid§date_added: Timestamp§last_modified: Timestamp§parent_guid: Option<SyncGuid>§position: u32§title: Option<String>§child_guids: Option<Vec<SyncGuid>>§child_nodes: Option<Vec<Item>>

Trait Implementations§

source§

impl Clone for Folder

source§

fn clone(&self) -> Folder

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for Folder

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for Folder

source§

fn default() -> Folder

Returns the “default value” for a type. Read more
source§

impl From<Folder> for Item

source§

fn from(f: Folder) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/storage/bookmarks/fetch/struct.RECENT_BOOKMARKS_QUERY.html b/book/rust-docs/places/storage/bookmarks/fetch/struct.RECENT_BOOKMARKS_QUERY.html new file mode 100644 index 0000000000..eb1277c20f --- /dev/null +++ b/book/rust-docs/places/storage/bookmarks/fetch/struct.RECENT_BOOKMARKS_QUERY.html @@ -0,0 +1,1362 @@ +RECENT_BOOKMARKS_QUERY in places::storage::bookmarks::fetch - Rust
pub struct RECENT_BOOKMARKS_QUERY { /* private fields */ }

Methods from Deref<Target = String>§

1.7.0 · source

pub fn as_str(&self) -> &str

Extracts a string slice containing the entire String.

+
Examples
+

Basic usage:

+ +
let s = String::from("foo");
+
+assert_eq!("foo", s.as_str());
+
1.0.0 · source

pub fn capacity(&self) -> usize

Returns this String’s capacity, in bytes.

+
Examples
+

Basic usage:

+ +
let s = String::with_capacity(10);
+
+assert!(s.capacity() >= 10);
+
1.0.0 · source

pub fn as_bytes(&self) -> &[u8]

Returns a byte slice of this String’s contents.

+

The inverse of this method is from_utf8.

+
Examples
+

Basic usage:

+ +
let s = String::from("hello");
+
+assert_eq!(&[104, 101, 108, 108, 111], s.as_bytes());
+
1.0.0 · source

pub fn len(&self) -> usize

Returns the length of this String, in bytes, not chars or +graphemes. In other words, it might not be what a human considers the +length of the string.

+
Examples
+

Basic usage:

+ +
let a = String::from("foo");
+assert_eq!(a.len(), 3);
+
+let fancy_f = String::from("ƒoo");
+assert_eq!(fancy_f.len(), 4);
+assert_eq!(fancy_f.chars().count(), 3);
+
1.0.0 · source

pub fn is_empty(&self) -> bool

Returns true if this String has a length of zero, and false otherwise.

+
Examples
+

Basic usage:

+ +
let mut v = String::new();
+assert!(v.is_empty());
+
+v.push('a');
+assert!(!v.is_empty());
+

Methods from Deref<Target = str>§

1.0.0 · source

pub fn len(&self) -> usize

Returns the length of self.

+

This length is in bytes, not chars or graphemes. In other words, +it might not be what a human considers the length of the string.

+
Examples
+
let len = "foo".len();
+assert_eq!(3, len);
+
+assert_eq!("ƒoo".len(), 4); // fancy f!
+assert_eq!("ƒoo".chars().count(), 3);
+
1.0.0 · source

pub fn is_empty(&self) -> bool

Returns true if self has a length of zero bytes.

+
Examples
+
let s = "";
+assert!(s.is_empty());
+
+let s = "not empty";
+assert!(!s.is_empty());
+
1.9.0 · source

pub fn is_char_boundary(&self, index: usize) -> bool

Checks that index-th byte is the first byte in a UTF-8 code point +sequence or the end of the string.

+

The start and end of the string (when index == self.len()) are +considered to be boundaries.

+

Returns false if index is greater than self.len().

+
Examples
+
let s = "Löwe 老虎 Léopard";
+assert!(s.is_char_boundary(0));
+// start of `老`
+assert!(s.is_char_boundary(6));
+assert!(s.is_char_boundary(s.len()));
+
+// second byte of `ö`
+assert!(!s.is_char_boundary(2));
+
+// third byte of `老`
+assert!(!s.is_char_boundary(8));
+
source

pub fn floor_char_boundary(&self, index: usize) -> usize

🔬This is a nightly-only experimental API. (round_char_boundary)

Finds the closest x not exceeding index where is_char_boundary(x) is true.

+

This method can help you truncate a string so that it’s still valid UTF-8, but doesn’t +exceed a given number of bytes. Note that this is done purely at the character level +and can still visually split graphemes, even though the underlying characters aren’t +split. For example, the emoji 🧑‍🔬 (scientist) could be split so that the string only +includes 🧑 (person) instead.

+
Examples
+
#![feature(round_char_boundary)]
+let s = "❤️🧡💛💚💙💜";
+assert_eq!(s.len(), 26);
+assert!(!s.is_char_boundary(13));
+
+let closest = s.floor_char_boundary(13);
+assert_eq!(closest, 10);
+assert_eq!(&s[..closest], "❤️🧡");
+
source

pub fn ceil_char_boundary(&self, index: usize) -> usize

🔬This is a nightly-only experimental API. (round_char_boundary)

Finds the closest x not below index where is_char_boundary(x) is true.

+

This method is the natural complement to floor_char_boundary. See that method +for more details.

+
Panics
+

Panics if index > self.len().

+
Examples
+
#![feature(round_char_boundary)]
+let s = "❤️🧡💛💚💙💜";
+assert_eq!(s.len(), 26);
+assert!(!s.is_char_boundary(13));
+
+let closest = s.ceil_char_boundary(13);
+assert_eq!(closest, 14);
+assert_eq!(&s[..closest], "❤️🧡💛");
+
1.0.0 · source

pub fn as_bytes(&self) -> &[u8]

Converts a string slice to a byte slice. To convert the byte slice back +into a string slice, use the from_utf8 function.

+
Examples
+
let bytes = "bors".as_bytes();
+assert_eq!(b"bors", bytes);
+
1.20.0 · source

pub unsafe fn as_bytes_mut(&mut self) -> &mut [u8]

Converts a mutable string slice to a mutable byte slice.

+
Safety
+

The caller must ensure that the content of the slice is valid UTF-8 +before the borrow ends and the underlying str is used.

+

Use of a str whose contents are not valid UTF-8 is undefined behavior.

+
Examples
+

Basic usage:

+ +
let mut s = String::from("Hello");
+let bytes = unsafe { s.as_bytes_mut() };
+
+assert_eq!(b"Hello", bytes);
+

Mutability:

+ +
let mut s = String::from("🗻∈🌏");
+
+unsafe {
+    let bytes = s.as_bytes_mut();
+
+    bytes[0] = 0xF0;
+    bytes[1] = 0x9F;
+    bytes[2] = 0x8D;
+    bytes[3] = 0x94;
+}
+
+assert_eq!("🍔∈🌏", s);
+
1.0.0 · source

pub fn as_ptr(&self) -> *const u8

Converts a string slice to a raw pointer.

+

As string slices are a slice of bytes, the raw pointer points to a +u8. This pointer will be pointing to the first byte of the string +slice.

+

The caller must ensure that the returned pointer is never written to. +If you need to mutate the contents of the string slice, use as_mut_ptr.

+
Examples
+
let s = "Hello";
+let ptr = s.as_ptr();
+
1.36.0 · source

pub fn as_mut_ptr(&mut self) -> *mut u8

Converts a mutable string slice to a raw pointer.

+

As string slices are a slice of bytes, the raw pointer points to a +u8. This pointer will be pointing to the first byte of the string +slice.

+

It is your responsibility to make sure that the string slice only gets +modified in a way that it remains valid UTF-8.

+
1.20.0 · source

pub fn get<I>(&self, i: I) -> Option<&<I as SliceIndex<str>>::Output>where + I: SliceIndex<str>,

Returns a subslice of str.

+

This is the non-panicking alternative to indexing the str. Returns +None whenever equivalent indexing operation would panic.

+
Examples
+
let v = String::from("🗻∈🌏");
+
+assert_eq!(Some("🗻"), v.get(0..4));
+
+// indices not on UTF-8 sequence boundaries
+assert!(v.get(1..).is_none());
+assert!(v.get(..8).is_none());
+
+// out of bounds
+assert!(v.get(..42).is_none());
+
1.20.0 · source

pub fn get_mut<I>( + &mut self, + i: I +) -> Option<&mut <I as SliceIndex<str>>::Output>where + I: SliceIndex<str>,

Returns a mutable subslice of str.

+

This is the non-panicking alternative to indexing the str. Returns +None whenever equivalent indexing operation would panic.

+
Examples
+
let mut v = String::from("hello");
+// correct length
+assert!(v.get_mut(0..5).is_some());
+// out of bounds
+assert!(v.get_mut(..42).is_none());
+assert_eq!(Some("he"), v.get_mut(0..2).map(|v| &*v));
+
+assert_eq!("hello", v);
+{
+    let s = v.get_mut(0..2);
+    let s = s.map(|s| {
+        s.make_ascii_uppercase();
+        &*s
+    });
+    assert_eq!(Some("HE"), s);
+}
+assert_eq!("HEllo", v);
+
1.20.0 · source

pub unsafe fn get_unchecked<I>(&self, i: I) -> &<I as SliceIndex<str>>::Outputwhere + I: SliceIndex<str>,

Returns an unchecked subslice of str.

+

This is the unchecked alternative to indexing the str.

+
Safety
+

Callers of this function are responsible that these preconditions are +satisfied:

+
    +
  • The starting index must not exceed the ending index;
  • +
  • Indexes must be within bounds of the original slice;
  • +
  • Indexes must lie on UTF-8 sequence boundaries.
  • +
+

Failing that, the returned string slice may reference invalid memory or +violate the invariants communicated by the str type.

+
Examples
+
let v = "🗻∈🌏";
+unsafe {
+    assert_eq!("🗻", v.get_unchecked(0..4));
+    assert_eq!("∈", v.get_unchecked(4..7));
+    assert_eq!("🌏", v.get_unchecked(7..11));
+}
+
1.20.0 · source

pub unsafe fn get_unchecked_mut<I>( + &mut self, + i: I +) -> &mut <I as SliceIndex<str>>::Outputwhere + I: SliceIndex<str>,

Returns a mutable, unchecked subslice of str.

+

This is the unchecked alternative to indexing the str.

+
Safety
+

Callers of this function are responsible that these preconditions are +satisfied:

+
    +
  • The starting index must not exceed the ending index;
  • +
  • Indexes must be within bounds of the original slice;
  • +
  • Indexes must lie on UTF-8 sequence boundaries.
  • +
+

Failing that, the returned string slice may reference invalid memory or +violate the invariants communicated by the str type.

+
Examples
+
let mut v = String::from("🗻∈🌏");
+unsafe {
+    assert_eq!("🗻", v.get_unchecked_mut(0..4));
+    assert_eq!("∈", v.get_unchecked_mut(4..7));
+    assert_eq!("🌏", v.get_unchecked_mut(7..11));
+}
+
1.0.0 · source

pub unsafe fn slice_unchecked(&self, begin: usize, end: usize) -> &str

👎Deprecated since 1.29.0: use get_unchecked(begin..end) instead

Creates a string slice from another string slice, bypassing safety +checks.

+

This is generally not recommended, use with caution! For a safe +alternative see str and Index.

+

This new slice goes from begin to end, including begin but +excluding end.

+

To get a mutable string slice instead, see the +slice_mut_unchecked method.

+
Safety
+

Callers of this function are responsible that three preconditions are +satisfied:

+
    +
  • begin must not exceed end.
  • +
  • begin and end must be byte positions within the string slice.
  • +
  • begin and end must lie on UTF-8 sequence boundaries.
  • +
+
Examples
+
let s = "Löwe 老虎 Léopard";
+
+unsafe {
+    assert_eq!("Löwe 老虎 Léopard", s.slice_unchecked(0, 21));
+}
+
+let s = "Hello, world!";
+
+unsafe {
+    assert_eq!("world", s.slice_unchecked(7, 12));
+}
+
1.5.0 · source

pub unsafe fn slice_mut_unchecked( + &mut self, + begin: usize, + end: usize +) -> &mut str

👎Deprecated since 1.29.0: use get_unchecked_mut(begin..end) instead

Creates a string slice from another string slice, bypassing safety +checks. +This is generally not recommended, use with caution! For a safe +alternative see str and IndexMut.

+

This new slice goes from begin to end, including begin but +excluding end.

+

To get an immutable string slice instead, see the +slice_unchecked method.

+
Safety
+

Callers of this function are responsible that three preconditions are +satisfied:

+
    +
  • begin must not exceed end.
  • +
  • begin and end must be byte positions within the string slice.
  • +
  • begin and end must lie on UTF-8 sequence boundaries.
  • +
+
1.4.0 · source

pub fn split_at(&self, mid: usize) -> (&str, &str)

Divide one string slice into two at an index.

+

The argument, mid, should be a byte offset from the start of the +string. It must also be on the boundary of a UTF-8 code point.

+

The two slices returned go from the start of the string slice to mid, +and from mid to the end of the string slice.

+

To get mutable string slices instead, see the split_at_mut +method.

+
Panics
+

Panics if mid is not on a UTF-8 code point boundary, or if it is +past the end of the last code point of the string slice.

+
Examples
+
let s = "Per Martin-Löf";
+
+let (first, last) = s.split_at(3);
+
+assert_eq!("Per", first);
+assert_eq!(" Martin-Löf", last);
+
1.4.0 · source

pub fn split_at_mut(&mut self, mid: usize) -> (&mut str, &mut str)

Divide one mutable string slice into two at an index.

+

The argument, mid, should be a byte offset from the start of the +string. It must also be on the boundary of a UTF-8 code point.

+

The two slices returned go from the start of the string slice to mid, +and from mid to the end of the string slice.

+

To get immutable string slices instead, see the split_at method.

+
Panics
+

Panics if mid is not on a UTF-8 code point boundary, or if it is +past the end of the last code point of the string slice.

+
Examples
+
let mut s = "Per Martin-Löf".to_string();
+{
+    let (first, last) = s.split_at_mut(3);
+    first.make_ascii_uppercase();
+    assert_eq!("PER", first);
+    assert_eq!(" Martin-Löf", last);
+}
+assert_eq!("PER Martin-Löf", s);
+
1.0.0 · source

pub fn chars(&self) -> Chars<'_>

Returns an iterator over the chars of a string slice.

+

As a string slice consists of valid UTF-8, we can iterate through a +string slice by char. This method returns such an iterator.

+

It’s important to remember that char represents a Unicode Scalar +Value, and might not match your idea of what a ‘character’ is. Iteration +over grapheme clusters may be what you actually want. This functionality +is not provided by Rust’s standard library, check crates.io instead.

+
Examples
+

Basic usage:

+ +
let word = "goodbye";
+
+let count = word.chars().count();
+assert_eq!(7, count);
+
+let mut chars = word.chars();
+
+assert_eq!(Some('g'), chars.next());
+assert_eq!(Some('o'), chars.next());
+assert_eq!(Some('o'), chars.next());
+assert_eq!(Some('d'), chars.next());
+assert_eq!(Some('b'), chars.next());
+assert_eq!(Some('y'), chars.next());
+assert_eq!(Some('e'), chars.next());
+
+assert_eq!(None, chars.next());
+

Remember, chars might not match your intuition about characters:

+ +
let y = "y̆";
+
+let mut chars = y.chars();
+
+assert_eq!(Some('y'), chars.next()); // not 'y̆'
+assert_eq!(Some('\u{0306}'), chars.next());
+
+assert_eq!(None, chars.next());
+
1.0.0 · source

pub fn char_indices(&self) -> CharIndices<'_>

Returns an iterator over the chars of a string slice, and their +positions.

+

As a string slice consists of valid UTF-8, we can iterate through a +string slice by char. This method returns an iterator of both +these chars, as well as their byte positions.

+

The iterator yields tuples. The position is first, the char is +second.

+
Examples
+

Basic usage:

+ +
let word = "goodbye";
+
+let count = word.char_indices().count();
+assert_eq!(7, count);
+
+let mut char_indices = word.char_indices();
+
+assert_eq!(Some((0, 'g')), char_indices.next());
+assert_eq!(Some((1, 'o')), char_indices.next());
+assert_eq!(Some((2, 'o')), char_indices.next());
+assert_eq!(Some((3, 'd')), char_indices.next());
+assert_eq!(Some((4, 'b')), char_indices.next());
+assert_eq!(Some((5, 'y')), char_indices.next());
+assert_eq!(Some((6, 'e')), char_indices.next());
+
+assert_eq!(None, char_indices.next());
+

Remember, chars might not match your intuition about characters:

+ +
let yes = "y̆es";
+
+let mut char_indices = yes.char_indices();
+
+assert_eq!(Some((0, 'y')), char_indices.next()); // not (0, 'y̆')
+assert_eq!(Some((1, '\u{0306}')), char_indices.next());
+
+// note the 3 here - the last character took up two bytes
+assert_eq!(Some((3, 'e')), char_indices.next());
+assert_eq!(Some((4, 's')), char_indices.next());
+
+assert_eq!(None, char_indices.next());
+
1.0.0 · source

pub fn bytes(&self) -> Bytes<'_>

An iterator over the bytes of a string slice.

+

As a string slice consists of a sequence of bytes, we can iterate +through a string slice by byte. This method returns such an iterator.

+
Examples
+
let mut bytes = "bors".bytes();
+
+assert_eq!(Some(b'b'), bytes.next());
+assert_eq!(Some(b'o'), bytes.next());
+assert_eq!(Some(b'r'), bytes.next());
+assert_eq!(Some(b's'), bytes.next());
+
+assert_eq!(None, bytes.next());
+
1.1.0 · source

pub fn split_whitespace(&self) -> SplitWhitespace<'_>

Splits a string slice by whitespace.

+

The iterator returned will return string slices that are sub-slices of +the original string slice, separated by any amount of whitespace.

+

‘Whitespace’ is defined according to the terms of the Unicode Derived +Core Property White_Space. If you only want to split on ASCII whitespace +instead, use split_ascii_whitespace.

+
Examples
+

Basic usage:

+ +
let mut iter = "A few words".split_whitespace();
+
+assert_eq!(Some("A"), iter.next());
+assert_eq!(Some("few"), iter.next());
+assert_eq!(Some("words"), iter.next());
+
+assert_eq!(None, iter.next());
+

All kinds of whitespace are considered:

+ +
let mut iter = " Mary   had\ta\u{2009}little  \n\t lamb".split_whitespace();
+assert_eq!(Some("Mary"), iter.next());
+assert_eq!(Some("had"), iter.next());
+assert_eq!(Some("a"), iter.next());
+assert_eq!(Some("little"), iter.next());
+assert_eq!(Some("lamb"), iter.next());
+
+assert_eq!(None, iter.next());
+

If the string is empty or all whitespace, the iterator yields no string slices:

+ +
assert_eq!("".split_whitespace().next(), None);
+assert_eq!("   ".split_whitespace().next(), None);
+
1.34.0 · source

pub fn split_ascii_whitespace(&self) -> SplitAsciiWhitespace<'_>

Splits a string slice by ASCII whitespace.

+

The iterator returned will return string slices that are sub-slices of +the original string slice, separated by any amount of ASCII whitespace.

+

To split by Unicode Whitespace instead, use split_whitespace.

+
Examples
+

Basic usage:

+ +
let mut iter = "A few words".split_ascii_whitespace();
+
+assert_eq!(Some("A"), iter.next());
+assert_eq!(Some("few"), iter.next());
+assert_eq!(Some("words"), iter.next());
+
+assert_eq!(None, iter.next());
+

All kinds of ASCII whitespace are considered:

+ +
let mut iter = " Mary   had\ta little  \n\t lamb".split_ascii_whitespace();
+assert_eq!(Some("Mary"), iter.next());
+assert_eq!(Some("had"), iter.next());
+assert_eq!(Some("a"), iter.next());
+assert_eq!(Some("little"), iter.next());
+assert_eq!(Some("lamb"), iter.next());
+
+assert_eq!(None, iter.next());
+

If the string is empty or all ASCII whitespace, the iterator yields no string slices:

+ +
assert_eq!("".split_ascii_whitespace().next(), None);
+assert_eq!("   ".split_ascii_whitespace().next(), None);
+
1.0.0 · source

pub fn lines(&self) -> Lines<'_>

An iterator over the lines of a string, as string slices.

+

Lines are split at line endings that are either newlines (\n) or +sequences of a carriage return followed by a line feed (\r\n).

+

Line terminators are not included in the lines returned by the iterator.

+

The final line ending is optional. A string that ends with a final line +ending will return the same lines as an otherwise identical string +without a final line ending.

+
Examples
+

Basic usage:

+ +
let text = "foo\r\nbar\n\nbaz\n";
+let mut lines = text.lines();
+
+assert_eq!(Some("foo"), lines.next());
+assert_eq!(Some("bar"), lines.next());
+assert_eq!(Some(""), lines.next());
+assert_eq!(Some("baz"), lines.next());
+
+assert_eq!(None, lines.next());
+

The final line ending isn’t required:

+ +
let text = "foo\nbar\n\r\nbaz";
+let mut lines = text.lines();
+
+assert_eq!(Some("foo"), lines.next());
+assert_eq!(Some("bar"), lines.next());
+assert_eq!(Some(""), lines.next());
+assert_eq!(Some("baz"), lines.next());
+
+assert_eq!(None, lines.next());
+
1.0.0 · source

pub fn lines_any(&self) -> LinesAny<'_>

👎Deprecated since 1.4.0: use lines() instead now

An iterator over the lines of a string.

+
1.8.0 · source

pub fn encode_utf16(&self) -> EncodeUtf16<'_>

Returns an iterator of u16 over the string encoded as UTF-16.

+
Examples
+
let text = "Zażółć gęślą jaźń";
+
+let utf8_len = text.len();
+let utf16_len = text.encode_utf16().count();
+
+assert!(utf16_len <= utf8_len);
+
1.0.0 · source

pub fn contains<'a, P>(&'a self, pat: P) -> boolwhere + P: Pattern<'a>,

Returns true if the given pattern matches a sub-slice of +this string slice.

+

Returns false if it does not.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Examples
+
let bananas = "bananas";
+
+assert!(bananas.contains("nana"));
+assert!(!bananas.contains("apples"));
+
1.0.0 · source

pub fn starts_with<'a, P>(&'a self, pat: P) -> boolwhere + P: Pattern<'a>,

Returns true if the given pattern matches a prefix of this +string slice.

+

Returns false if it does not.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Examples
+
let bananas = "bananas";
+
+assert!(bananas.starts_with("bana"));
+assert!(!bananas.starts_with("nana"));
+
1.0.0 · source

pub fn ends_with<'a, P>(&'a self, pat: P) -> boolwhere + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,

Returns true if the given pattern matches a suffix of this +string slice.

+

Returns false if it does not.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Examples
+
let bananas = "bananas";
+
+assert!(bananas.ends_with("anas"));
+assert!(!bananas.ends_with("nana"));
+
1.0.0 · source

pub fn find<'a, P>(&'a self, pat: P) -> Option<usize>where + P: Pattern<'a>,

Returns the byte index of the first character of this string slice that +matches the pattern.

+

Returns None if the pattern doesn’t match.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Examples
+

Simple patterns:

+ +
let s = "Löwe 老虎 Léopard Gepardi";
+
+assert_eq!(s.find('L'), Some(0));
+assert_eq!(s.find('é'), Some(14));
+assert_eq!(s.find("pard"), Some(17));
+

More complex patterns using point-free style and closures:

+ +
let s = "Löwe 老虎 Léopard";
+
+assert_eq!(s.find(char::is_whitespace), Some(5));
+assert_eq!(s.find(char::is_lowercase), Some(1));
+assert_eq!(s.find(|c: char| c.is_whitespace() || c.is_lowercase()), Some(1));
+assert_eq!(s.find(|c: char| (c < 'o') && (c > 'a')), Some(4));
+

Not finding the pattern:

+ +
let s = "Löwe 老虎 Léopard";
+let x: &[_] = &['1', '2'];
+
+assert_eq!(s.find(x), None);
+
1.0.0 · source

pub fn rfind<'a, P>(&'a self, pat: P) -> Option<usize>where + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,

Returns the byte index for the first character of the last match of the pattern in +this string slice.

+

Returns None if the pattern doesn’t match.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Examples
+

Simple patterns:

+ +
let s = "Löwe 老虎 Léopard Gepardi";
+
+assert_eq!(s.rfind('L'), Some(13));
+assert_eq!(s.rfind('é'), Some(14));
+assert_eq!(s.rfind("pard"), Some(24));
+

More complex patterns with closures:

+ +
let s = "Löwe 老虎 Léopard";
+
+assert_eq!(s.rfind(char::is_whitespace), Some(12));
+assert_eq!(s.rfind(char::is_lowercase), Some(20));
+

Not finding the pattern:

+ +
let s = "Löwe 老虎 Léopard";
+let x: &[_] = &['1', '2'];
+
+assert_eq!(s.rfind(x), None);
+
1.0.0 · source

pub fn split<'a, P>(&'a self, pat: P) -> Split<'a, P>where + P: Pattern<'a>,

An iterator over substrings of this string slice, separated by +characters matched by a pattern.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Iterator behavior
+

The returned iterator will be a DoubleEndedIterator if the pattern +allows a reverse search and forward/reverse search yields the same +elements. This is true for, e.g., char, but not for &str.

+

If the pattern allows a reverse search but its results might differ +from a forward search, the rsplit method can be used.

+
Examples
+

Simple patterns:

+ +
let v: Vec<&str> = "Mary had a little lamb".split(' ').collect();
+assert_eq!(v, ["Mary", "had", "a", "little", "lamb"]);
+
+let v: Vec<&str> = "".split('X').collect();
+assert_eq!(v, [""]);
+
+let v: Vec<&str> = "lionXXtigerXleopard".split('X').collect();
+assert_eq!(v, ["lion", "", "tiger", "leopard"]);
+
+let v: Vec<&str> = "lion::tiger::leopard".split("::").collect();
+assert_eq!(v, ["lion", "tiger", "leopard"]);
+
+let v: Vec<&str> = "abc1def2ghi".split(char::is_numeric).collect();
+assert_eq!(v, ["abc", "def", "ghi"]);
+
+let v: Vec<&str> = "lionXtigerXleopard".split(char::is_uppercase).collect();
+assert_eq!(v, ["lion", "tiger", "leopard"]);
+

If the pattern is a slice of chars, split on each occurrence of any of the characters:

+ +
let v: Vec<&str> = "2020-11-03 23:59".split(&['-', ' ', ':', '@'][..]).collect();
+assert_eq!(v, ["2020", "11", "03", "23", "59"]);
+

A more complex pattern, using a closure:

+ +
let v: Vec<&str> = "abc1defXghi".split(|c| c == '1' || c == 'X').collect();
+assert_eq!(v, ["abc", "def", "ghi"]);
+

If a string contains multiple contiguous separators, you will end up +with empty strings in the output:

+ +
let x = "||||a||b|c".to_string();
+let d: Vec<_> = x.split('|').collect();
+
+assert_eq!(d, &["", "", "", "", "a", "", "b", "c"]);
+

Contiguous separators are separated by the empty string.

+ +
let x = "(///)".to_string();
+let d: Vec<_> = x.split('/').collect();
+
+assert_eq!(d, &["(", "", "", ")"]);
+

Separators at the start or end of a string are neighbored +by empty strings.

+ +
let d: Vec<_> = "010".split("0").collect();
+assert_eq!(d, &["", "1", ""]);
+

When the empty string is used as a separator, it separates +every character in the string, along with the beginning +and end of the string.

+ +
let f: Vec<_> = "rust".split("").collect();
+assert_eq!(f, &["", "r", "u", "s", "t", ""]);
+

Contiguous separators can lead to possibly surprising behavior +when whitespace is used as the separator. This code is correct:

+ +
let x = "    a  b c".to_string();
+let d: Vec<_> = x.split(' ').collect();
+
+assert_eq!(d, &["", "", "", "", "a", "", "b", "c"]);
+

It does not give you:

+ +
assert_eq!(d, &["a", "b", "c"]);
+

Use split_whitespace for this behavior.

+
1.51.0 · source

pub fn split_inclusive<'a, P>(&'a self, pat: P) -> SplitInclusive<'a, P>where + P: Pattern<'a>,

An iterator over substrings of this string slice, separated by +characters matched by a pattern. Differs from the iterator produced by +split in that split_inclusive leaves the matched part as the +terminator of the substring.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Examples
+
let v: Vec<&str> = "Mary had a little lamb\nlittle lamb\nlittle lamb."
+    .split_inclusive('\n').collect();
+assert_eq!(v, ["Mary had a little lamb\n", "little lamb\n", "little lamb."]);
+

If the last element of the string is matched, +that element will be considered the terminator of the preceding substring. +That substring will be the last item returned by the iterator.

+ +
let v: Vec<&str> = "Mary had a little lamb\nlittle lamb\nlittle lamb.\n"
+    .split_inclusive('\n').collect();
+assert_eq!(v, ["Mary had a little lamb\n", "little lamb\n", "little lamb.\n"]);
+
1.0.0 · source

pub fn rsplit<'a, P>(&'a self, pat: P) -> RSplit<'a, P>where + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,

An iterator over substrings of the given string slice, separated by +characters matched by a pattern and yielded in reverse order.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Iterator behavior
+

The returned iterator requires that the pattern supports a reverse +search, and it will be a DoubleEndedIterator if a forward/reverse +search yields the same elements.

+

For iterating from the front, the split method can be used.

+
Examples
+

Simple patterns:

+ +
let v: Vec<&str> = "Mary had a little lamb".rsplit(' ').collect();
+assert_eq!(v, ["lamb", "little", "a", "had", "Mary"]);
+
+let v: Vec<&str> = "".rsplit('X').collect();
+assert_eq!(v, [""]);
+
+let v: Vec<&str> = "lionXXtigerXleopard".rsplit('X').collect();
+assert_eq!(v, ["leopard", "tiger", "", "lion"]);
+
+let v: Vec<&str> = "lion::tiger::leopard".rsplit("::").collect();
+assert_eq!(v, ["leopard", "tiger", "lion"]);
+

A more complex pattern, using a closure:

+ +
let v: Vec<&str> = "abc1defXghi".rsplit(|c| c == '1' || c == 'X').collect();
+assert_eq!(v, ["ghi", "def", "abc"]);
+
1.0.0 · source

pub fn split_terminator<'a, P>(&'a self, pat: P) -> SplitTerminator<'a, P>where + P: Pattern<'a>,

An iterator over substrings of the given string slice, separated by +characters matched by a pattern.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+

Equivalent to split, except that the trailing substring +is skipped if empty.

+

This method can be used for string data that is terminated, +rather than separated by a pattern.

+
Iterator behavior
+

The returned iterator will be a DoubleEndedIterator if the pattern +allows a reverse search and forward/reverse search yields the same +elements. This is true for, e.g., char, but not for &str.

+

If the pattern allows a reverse search but its results might differ +from a forward search, the rsplit_terminator method can be used.

+
Examples
+
let v: Vec<&str> = "A.B.".split_terminator('.').collect();
+assert_eq!(v, ["A", "B"]);
+
+let v: Vec<&str> = "A..B..".split_terminator(".").collect();
+assert_eq!(v, ["A", "", "B", ""]);
+
+let v: Vec<&str> = "A.B:C.D".split_terminator(&['.', ':'][..]).collect();
+assert_eq!(v, ["A", "B", "C", "D"]);
+
1.0.0 · source

pub fn rsplit_terminator<'a, P>(&'a self, pat: P) -> RSplitTerminator<'a, P>where + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,

An iterator over substrings of self, separated by characters +matched by a pattern and yielded in reverse order.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+

Equivalent to split, except that the trailing substring is +skipped if empty.

+

This method can be used for string data that is terminated, +rather than separated by a pattern.

+
Iterator behavior
+

The returned iterator requires that the pattern supports a +reverse search, and it will be double ended if a forward/reverse +search yields the same elements.

+

For iterating from the front, the split_terminator method can be +used.

+
Examples
+
let v: Vec<&str> = "A.B.".rsplit_terminator('.').collect();
+assert_eq!(v, ["B", "A"]);
+
+let v: Vec<&str> = "A..B..".rsplit_terminator(".").collect();
+assert_eq!(v, ["", "B", "", "A"]);
+
+let v: Vec<&str> = "A.B:C.D".rsplit_terminator(&['.', ':'][..]).collect();
+assert_eq!(v, ["D", "C", "B", "A"]);
+
1.0.0 · source

pub fn splitn<'a, P>(&'a self, n: usize, pat: P) -> SplitN<'a, P>where + P: Pattern<'a>,

An iterator over substrings of the given string slice, separated by a +pattern, restricted to returning at most n items.

+

If n substrings are returned, the last substring (the nth substring) +will contain the remainder of the string.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Iterator behavior
+

The returned iterator will not be double ended, because it is +not efficient to support.

+

If the pattern allows a reverse search, the rsplitn method can be +used.

+
Examples
+

Simple patterns:

+ +
let v: Vec<&str> = "Mary had a little lambda".splitn(3, ' ').collect();
+assert_eq!(v, ["Mary", "had", "a little lambda"]);
+
+let v: Vec<&str> = "lionXXtigerXleopard".splitn(3, "X").collect();
+assert_eq!(v, ["lion", "", "tigerXleopard"]);
+
+let v: Vec<&str> = "abcXdef".splitn(1, 'X').collect();
+assert_eq!(v, ["abcXdef"]);
+
+let v: Vec<&str> = "".splitn(1, 'X').collect();
+assert_eq!(v, [""]);
+

A more complex pattern, using a closure:

+ +
let v: Vec<&str> = "abc1defXghi".splitn(2, |c| c == '1' || c == 'X').collect();
+assert_eq!(v, ["abc", "defXghi"]);
+
1.0.0 · source

pub fn rsplitn<'a, P>(&'a self, n: usize, pat: P) -> RSplitN<'a, P>where + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,

An iterator over substrings of this string slice, separated by a +pattern, starting from the end of the string, restricted to returning +at most n items.

+

If n substrings are returned, the last substring (the nth substring) +will contain the remainder of the string.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Iterator behavior
+

The returned iterator will not be double ended, because it is not +efficient to support.

+

For splitting from the front, the splitn method can be used.

+
Examples
+

Simple patterns:

+ +
let v: Vec<&str> = "Mary had a little lamb".rsplitn(3, ' ').collect();
+assert_eq!(v, ["lamb", "little", "Mary had a"]);
+
+let v: Vec<&str> = "lionXXtigerXleopard".rsplitn(3, 'X').collect();
+assert_eq!(v, ["leopard", "tiger", "lionX"]);
+
+let v: Vec<&str> = "lion::tiger::leopard".rsplitn(2, "::").collect();
+assert_eq!(v, ["leopard", "lion::tiger"]);
+

A more complex pattern, using a closure:

+ +
let v: Vec<&str> = "abc1defXghi".rsplitn(2, |c| c == '1' || c == 'X').collect();
+assert_eq!(v, ["ghi", "abc1def"]);
+
1.52.0 · source

pub fn split_once<'a, P>(&'a self, delimiter: P) -> Option<(&'a str, &'a str)>where + P: Pattern<'a>,

Splits the string on the first occurrence of the specified delimiter and +returns prefix before delimiter and suffix after delimiter.

+
Examples
+
assert_eq!("cfg".split_once('='), None);
+assert_eq!("cfg=".split_once('='), Some(("cfg", "")));
+assert_eq!("cfg=foo".split_once('='), Some(("cfg", "foo")));
+assert_eq!("cfg=foo=bar".split_once('='), Some(("cfg", "foo=bar")));
+
1.52.0 · source

pub fn rsplit_once<'a, P>(&'a self, delimiter: P) -> Option<(&'a str, &'a str)>where + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,

Splits the string on the last occurrence of the specified delimiter and +returns prefix before delimiter and suffix after delimiter.

+
Examples
+
assert_eq!("cfg".rsplit_once('='), None);
+assert_eq!("cfg=foo".rsplit_once('='), Some(("cfg", "foo")));
+assert_eq!("cfg=foo=bar".rsplit_once('='), Some(("cfg=foo", "bar")));
+
1.2.0 · source

pub fn matches<'a, P>(&'a self, pat: P) -> Matches<'a, P>where + P: Pattern<'a>,

An iterator over the disjoint matches of a pattern within the given string +slice.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Iterator behavior
+

The returned iterator will be a DoubleEndedIterator if the pattern +allows a reverse search and forward/reverse search yields the same +elements. This is true for, e.g., char, but not for &str.

+

If the pattern allows a reverse search but its results might differ +from a forward search, the rmatches method can be used.

+
Examples
+
let v: Vec<&str> = "abcXXXabcYYYabc".matches("abc").collect();
+assert_eq!(v, ["abc", "abc", "abc"]);
+
+let v: Vec<&str> = "1abc2abc3".matches(char::is_numeric).collect();
+assert_eq!(v, ["1", "2", "3"]);
+
1.2.0 · source

pub fn rmatches<'a, P>(&'a self, pat: P) -> RMatches<'a, P>where + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,

An iterator over the disjoint matches of a pattern within this string slice, +yielded in reverse order.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Iterator behavior
+

The returned iterator requires that the pattern supports a reverse +search, and it will be a DoubleEndedIterator if a forward/reverse +search yields the same elements.

+

For iterating from the front, the matches method can be used.

+
Examples
+
let v: Vec<&str> = "abcXXXabcYYYabc".rmatches("abc").collect();
+assert_eq!(v, ["abc", "abc", "abc"]);
+
+let v: Vec<&str> = "1abc2abc3".rmatches(char::is_numeric).collect();
+assert_eq!(v, ["3", "2", "1"]);
+
1.5.0 · source

pub fn match_indices<'a, P>(&'a self, pat: P) -> MatchIndices<'a, P>where + P: Pattern<'a>,

An iterator over the disjoint matches of a pattern within this string +slice as well as the index that the match starts at.

+

For matches of pat within self that overlap, only the indices +corresponding to the first match are returned.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Iterator behavior
+

The returned iterator will be a DoubleEndedIterator if the pattern +allows a reverse search and forward/reverse search yields the same +elements. This is true for, e.g., char, but not for &str.

+

If the pattern allows a reverse search but its results might differ +from a forward search, the rmatch_indices method can be used.

+
Examples
+
let v: Vec<_> = "abcXXXabcYYYabc".match_indices("abc").collect();
+assert_eq!(v, [(0, "abc"), (6, "abc"), (12, "abc")]);
+
+let v: Vec<_> = "1abcabc2".match_indices("abc").collect();
+assert_eq!(v, [(1, "abc"), (4, "abc")]);
+
+let v: Vec<_> = "ababa".match_indices("aba").collect();
+assert_eq!(v, [(0, "aba")]); // only the first `aba`
+
1.5.0 · source

pub fn rmatch_indices<'a, P>(&'a self, pat: P) -> RMatchIndices<'a, P>where + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,

An iterator over the disjoint matches of a pattern within self, +yielded in reverse order along with the index of the match.

+

For matches of pat within self that overlap, only the indices +corresponding to the last match are returned.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Iterator behavior
+

The returned iterator requires that the pattern supports a reverse +search, and it will be a DoubleEndedIterator if a forward/reverse +search yields the same elements.

+

For iterating from the front, the match_indices method can be used.

+
Examples
+
let v: Vec<_> = "abcXXXabcYYYabc".rmatch_indices("abc").collect();
+assert_eq!(v, [(12, "abc"), (6, "abc"), (0, "abc")]);
+
+let v: Vec<_> = "1abcabc2".rmatch_indices("abc").collect();
+assert_eq!(v, [(4, "abc"), (1, "abc")]);
+
+let v: Vec<_> = "ababa".rmatch_indices("aba").collect();
+assert_eq!(v, [(2, "aba")]); // only the last `aba`
+
1.0.0 · source

pub fn trim(&self) -> &str

Returns a string slice with leading and trailing whitespace removed.

+

‘Whitespace’ is defined according to the terms of the Unicode Derived +Core Property White_Space, which includes newlines.

+
Examples
+
let s = "\n Hello\tworld\t\n";
+
+assert_eq!("Hello\tworld", s.trim());
+
1.30.0 · source

pub fn trim_start(&self) -> &str

Returns a string slice with leading whitespace removed.

+

‘Whitespace’ is defined according to the terms of the Unicode Derived +Core Property White_Space, which includes newlines.

+
Text directionality
+

A string is a sequence of bytes. start in this context means the first +position of that byte string; for a left-to-right language like English or +Russian, this will be left side, and for right-to-left languages like +Arabic or Hebrew, this will be the right side.

+
Examples
+

Basic usage:

+ +
let s = "\n Hello\tworld\t\n";
+assert_eq!("Hello\tworld\t\n", s.trim_start());
+

Directionality:

+ +
let s = "  English  ";
+assert!(Some('E') == s.trim_start().chars().next());
+
+let s = "  עברית  ";
+assert!(Some('ע') == s.trim_start().chars().next());
+
1.30.0 · source

pub fn trim_end(&self) -> &str

Returns a string slice with trailing whitespace removed.

+

‘Whitespace’ is defined according to the terms of the Unicode Derived +Core Property White_Space, which includes newlines.

+
Text directionality
+

A string is a sequence of bytes. end in this context means the last +position of that byte string; for a left-to-right language like English or +Russian, this will be right side, and for right-to-left languages like +Arabic or Hebrew, this will be the left side.

+
Examples
+

Basic usage:

+ +
let s = "\n Hello\tworld\t\n";
+assert_eq!("\n Hello\tworld", s.trim_end());
+

Directionality:

+ +
let s = "  English  ";
+assert!(Some('h') == s.trim_end().chars().rev().next());
+
+let s = "  עברית  ";
+assert!(Some('ת') == s.trim_end().chars().rev().next());
+
1.0.0 · source

pub fn trim_left(&self) -> &str

👎Deprecated since 1.33.0: superseded by trim_start

Returns a string slice with leading whitespace removed.

+

‘Whitespace’ is defined according to the terms of the Unicode Derived +Core Property White_Space.

+
Text directionality
+

A string is a sequence of bytes. ‘Left’ in this context means the first +position of that byte string; for a language like Arabic or Hebrew +which are ‘right to left’ rather than ‘left to right’, this will be +the right side, not the left.

+
Examples
+

Basic usage:

+ +
let s = " Hello\tworld\t";
+
+assert_eq!("Hello\tworld\t", s.trim_left());
+

Directionality:

+ +
let s = "  English";
+assert!(Some('E') == s.trim_left().chars().next());
+
+let s = "  עברית";
+assert!(Some('ע') == s.trim_left().chars().next());
+
1.0.0 · source

pub fn trim_right(&self) -> &str

👎Deprecated since 1.33.0: superseded by trim_end

Returns a string slice with trailing whitespace removed.

+

‘Whitespace’ is defined according to the terms of the Unicode Derived +Core Property White_Space.

+
Text directionality
+

A string is a sequence of bytes. ‘Right’ in this context means the last +position of that byte string; for a language like Arabic or Hebrew +which are ‘right to left’ rather than ‘left to right’, this will be +the left side, not the right.

+
Examples
+

Basic usage:

+ +
let s = " Hello\tworld\t";
+
+assert_eq!(" Hello\tworld", s.trim_right());
+

Directionality:

+ +
let s = "English  ";
+assert!(Some('h') == s.trim_right().chars().rev().next());
+
+let s = "עברית  ";
+assert!(Some('ת') == s.trim_right().chars().rev().next());
+
1.0.0 · source

pub fn trim_matches<'a, P>(&'a self, pat: P) -> &'a strwhere + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: DoubleEndedSearcher<'a>,

Returns a string slice with all prefixes and suffixes that match a +pattern repeatedly removed.

+

The pattern can be a char, a slice of chars, or a function +or closure that determines if a character matches.

+
Examples
+

Simple patterns:

+ +
assert_eq!("11foo1bar11".trim_matches('1'), "foo1bar");
+assert_eq!("123foo1bar123".trim_matches(char::is_numeric), "foo1bar");
+
+let x: &[_] = &['1', '2'];
+assert_eq!("12foo1bar12".trim_matches(x), "foo1bar");
+

A more complex pattern, using a closure:

+ +
assert_eq!("1foo1barXX".trim_matches(|c| c == '1' || c == 'X'), "foo1bar");
+
1.30.0 · source

pub fn trim_start_matches<'a, P>(&'a self, pat: P) -> &'a strwhere + P: Pattern<'a>,

Returns a string slice with all prefixes that match a pattern +repeatedly removed.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Text directionality
+

A string is a sequence of bytes. start in this context means the first +position of that byte string; for a left-to-right language like English or +Russian, this will be left side, and for right-to-left languages like +Arabic or Hebrew, this will be the right side.

+
Examples
+
assert_eq!("11foo1bar11".trim_start_matches('1'), "foo1bar11");
+assert_eq!("123foo1bar123".trim_start_matches(char::is_numeric), "foo1bar123");
+
+let x: &[_] = &['1', '2'];
+assert_eq!("12foo1bar12".trim_start_matches(x), "foo1bar12");
+
1.45.0 · source

pub fn strip_prefix<'a, P>(&'a self, prefix: P) -> Option<&'a str>where + P: Pattern<'a>,

Returns a string slice with the prefix removed.

+

If the string starts with the pattern prefix, returns substring after the prefix, wrapped +in Some. Unlike trim_start_matches, this method removes the prefix exactly once.

+

If the string does not start with prefix, returns None.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Examples
+
assert_eq!("foo:bar".strip_prefix("foo:"), Some("bar"));
+assert_eq!("foo:bar".strip_prefix("bar"), None);
+assert_eq!("foofoo".strip_prefix("foo"), Some("foo"));
+
1.45.0 · source

pub fn strip_suffix<'a, P>(&'a self, suffix: P) -> Option<&'a str>where + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,

Returns a string slice with the suffix removed.

+

If the string ends with the pattern suffix, returns the substring before the suffix, +wrapped in Some. Unlike trim_end_matches, this method removes the suffix exactly once.

+

If the string does not end with suffix, returns None.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Examples
+
assert_eq!("bar:foo".strip_suffix(":foo"), Some("bar"));
+assert_eq!("bar:foo".strip_suffix("bar"), None);
+assert_eq!("foofoo".strip_suffix("foo"), Some("foo"));
+
1.30.0 · source

pub fn trim_end_matches<'a, P>(&'a self, pat: P) -> &'a strwhere + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,

Returns a string slice with all suffixes that match a pattern +repeatedly removed.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Text directionality
+

A string is a sequence of bytes. end in this context means the last +position of that byte string; for a left-to-right language like English or +Russian, this will be right side, and for right-to-left languages like +Arabic or Hebrew, this will be the left side.

+
Examples
+

Simple patterns:

+ +
assert_eq!("11foo1bar11".trim_end_matches('1'), "11foo1bar");
+assert_eq!("123foo1bar123".trim_end_matches(char::is_numeric), "123foo1bar");
+
+let x: &[_] = &['1', '2'];
+assert_eq!("12foo1bar12".trim_end_matches(x), "12foo1bar");
+

A more complex pattern, using a closure:

+ +
assert_eq!("1fooX".trim_end_matches(|c| c == '1' || c == 'X'), "1foo");
+
1.0.0 · source

pub fn trim_left_matches<'a, P>(&'a self, pat: P) -> &'a strwhere + P: Pattern<'a>,

👎Deprecated since 1.33.0: superseded by trim_start_matches

Returns a string slice with all prefixes that match a pattern +repeatedly removed.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Text directionality
+

A string is a sequence of bytes. ‘Left’ in this context means the first +position of that byte string; for a language like Arabic or Hebrew +which are ‘right to left’ rather than ‘left to right’, this will be +the right side, not the left.

+
Examples
+
assert_eq!("11foo1bar11".trim_left_matches('1'), "foo1bar11");
+assert_eq!("123foo1bar123".trim_left_matches(char::is_numeric), "foo1bar123");
+
+let x: &[_] = &['1', '2'];
+assert_eq!("12foo1bar12".trim_left_matches(x), "foo1bar12");
+
1.0.0 · source

pub fn trim_right_matches<'a, P>(&'a self, pat: P) -> &'a strwhere + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,

👎Deprecated since 1.33.0: superseded by trim_end_matches

Returns a string slice with all suffixes that match a pattern +repeatedly removed.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Text directionality
+

A string is a sequence of bytes. ‘Right’ in this context means the last +position of that byte string; for a language like Arabic or Hebrew +which are ‘right to left’ rather than ‘left to right’, this will be +the left side, not the right.

+
Examples
+

Simple patterns:

+ +
assert_eq!("11foo1bar11".trim_right_matches('1'), "11foo1bar");
+assert_eq!("123foo1bar123".trim_right_matches(char::is_numeric), "123foo1bar");
+
+let x: &[_] = &['1', '2'];
+assert_eq!("12foo1bar12".trim_right_matches(x), "12foo1bar");
+

A more complex pattern, using a closure:

+ +
assert_eq!("1fooX".trim_right_matches(|c| c == '1' || c == 'X'), "1foo");
+
1.0.0 · source

pub fn parse<F>(&self) -> Result<F, <F as FromStr>::Err>where + F: FromStr,

Parses this string slice into another type.

+

Because parse is so general, it can cause problems with type +inference. As such, parse is one of the few times you’ll see +the syntax affectionately known as the ‘turbofish’: ::<>. This +helps the inference algorithm understand specifically which type +you’re trying to parse into.

+

parse can parse into any type that implements the FromStr trait.

+
Errors
+

Will return Err if it’s not possible to parse this string slice into +the desired type.

+
Examples
+

Basic usage

+ +
let four: u32 = "4".parse().unwrap();
+
+assert_eq!(4, four);
+

Using the ‘turbofish’ instead of annotating four:

+ +
let four = "4".parse::<u32>();
+
+assert_eq!(Ok(4), four);
+

Failing to parse:

+ +
let nope = "j".parse::<u32>();
+
+assert!(nope.is_err());
+
1.23.0 · source

pub fn is_ascii(&self) -> bool

Checks if all characters in this string are within the ASCII range.

+
Examples
+
let ascii = "hello!\n";
+let non_ascii = "Grüße, Jürgen ❤";
+
+assert!(ascii.is_ascii());
+assert!(!non_ascii.is_ascii());
+
source

pub fn as_ascii(&self) -> Option<&[AsciiChar]>

🔬This is a nightly-only experimental API. (ascii_char)

If this string slice is_ascii, returns it as a slice +of ASCII characters, otherwise returns None.

+
1.23.0 · source

pub fn eq_ignore_ascii_case(&self, other: &str) -> bool

Checks that two strings are an ASCII case-insensitive match.

+

Same as to_ascii_lowercase(a) == to_ascii_lowercase(b), +but without allocating and copying temporaries.

+
Examples
+
assert!("Ferris".eq_ignore_ascii_case("FERRIS"));
+assert!("Ferrös".eq_ignore_ascii_case("FERRöS"));
+assert!(!"Ferrös".eq_ignore_ascii_case("FERRÖS"));
+
1.23.0 · source

pub fn make_ascii_uppercase(&mut self)

Converts this string to its ASCII upper case equivalent in-place.

+

ASCII letters ‘a’ to ‘z’ are mapped to ‘A’ to ‘Z’, +but non-ASCII letters are unchanged.

+

To return a new uppercased value without modifying the existing one, use +to_ascii_uppercase().

+
Examples
+
let mut s = String::from("Grüße, Jürgen ❤");
+
+s.make_ascii_uppercase();
+
+assert_eq!("GRüßE, JüRGEN ❤", s);
+
1.23.0 · source

pub fn make_ascii_lowercase(&mut self)

Converts this string to its ASCII lower case equivalent in-place.

+

ASCII letters ‘A’ to ‘Z’ are mapped to ‘a’ to ‘z’, +but non-ASCII letters are unchanged.

+

To return a new lowercased value without modifying the existing one, use +to_ascii_lowercase().

+
Examples
+
let mut s = String::from("GRÜßE, JÜRGEN ❤");
+
+s.make_ascii_lowercase();
+
+assert_eq!("grÜße, jÜrgen ❤", s);
+
1.34.0 · source

pub fn escape_debug(&self) -> EscapeDebug<'_>

Return an iterator that escapes each char in self with char::escape_debug.

+

Note: only extended grapheme codepoints that begin the string will be +escaped.

+
Examples
+

As an iterator:

+ +
for c in "❤\n!".escape_debug() {
+    print!("{c}");
+}
+println!();
+

Using println! directly:

+ +
println!("{}", "❤\n!".escape_debug());
+

Both are equivalent to:

+ +
println!("❤\\n!");
+

Using to_string:

+ +
assert_eq!("❤\n!".escape_debug().to_string(), "❤\\n!");
+
1.34.0 · source

pub fn escape_default(&self) -> EscapeDefault<'_>

Return an iterator that escapes each char in self with char::escape_default.

+
Examples
+

As an iterator:

+ +
for c in "❤\n!".escape_default() {
+    print!("{c}");
+}
+println!();
+

Using println! directly:

+ +
println!("{}", "❤\n!".escape_default());
+

Both are equivalent to:

+ +
println!("\\u{{2764}}\\n!");
+

Using to_string:

+ +
assert_eq!("❤\n!".escape_default().to_string(), "\\u{2764}\\n!");
+
1.34.0 · source

pub fn escape_unicode(&self) -> EscapeUnicode<'_>

Return an iterator that escapes each char in self with char::escape_unicode.

+
Examples
+

As an iterator:

+ +
for c in "❤\n!".escape_unicode() {
+    print!("{c}");
+}
+println!();
+

Using println! directly:

+ +
println!("{}", "❤\n!".escape_unicode());
+

Both are equivalent to:

+ +
println!("\\u{{2764}}\\u{{a}}\\u{{21}}");
+

Using to_string:

+ +
assert_eq!("❤\n!".escape_unicode().to_string(), "\\u{2764}\\u{a}\\u{21}");
+
1.0.0 · source

pub fn replace<'a, P>(&'a self, from: P, to: &str) -> Stringwhere + P: Pattern<'a>,

Replaces all matches of a pattern with another string.

+

replace creates a new String, and copies the data from this string slice into it. +While doing so, it attempts to find matches of a pattern. If it finds any, it +replaces them with the replacement string slice.

+
Examples
+

Basic usage:

+ +
let s = "this is old";
+
+assert_eq!("this is new", s.replace("old", "new"));
+assert_eq!("than an old", s.replace("is", "an"));
+

When the pattern doesn’t match, it returns this string slice as String:

+ +
let s = "this is old";
+assert_eq!(s, s.replace("cookie monster", "little lamb"));
+
1.16.0 · source

pub fn replacen<'a, P>(&'a self, pat: P, to: &str, count: usize) -> Stringwhere + P: Pattern<'a>,

Replaces first N matches of a pattern with another string.

+

replacen creates a new String, and copies the data from this string slice into it. +While doing so, it attempts to find matches of a pattern. If it finds any, it +replaces them with the replacement string slice at most count times.

+
Examples
+

Basic usage:

+ +
let s = "foo foo 123 foo";
+assert_eq!("new new 123 foo", s.replacen("foo", "new", 2));
+assert_eq!("faa fao 123 foo", s.replacen('o', "a", 3));
+assert_eq!("foo foo new23 foo", s.replacen(char::is_numeric, "new", 1));
+

When the pattern doesn’t match, it returns this string slice as String:

+ +
let s = "this is old";
+assert_eq!(s, s.replacen("cookie monster", "little lamb", 10));
+
1.2.0 · source

pub fn to_lowercase(&self) -> String

Returns the lowercase equivalent of this string slice, as a new String.

+

‘Lowercase’ is defined according to the terms of the Unicode Derived Core Property +Lowercase.

+

Since some characters can expand into multiple characters when changing +the case, this function returns a String instead of modifying the +parameter in-place.

+
Examples
+

Basic usage:

+ +
let s = "HELLO";
+
+assert_eq!("hello", s.to_lowercase());
+

A tricky example, with sigma:

+ +
let sigma = "Σ";
+
+assert_eq!("σ", sigma.to_lowercase());
+
+// but at the end of a word, it's ς, not σ:
+let odysseus = "ὈΔΥΣΣΕΎΣ";
+
+assert_eq!("ὀδυσσεύς", odysseus.to_lowercase());
+

Languages without case are not changed:

+ +
let new_year = "农历新年";
+
+assert_eq!(new_year, new_year.to_lowercase());
+
1.2.0 · source

pub fn to_uppercase(&self) -> String

Returns the uppercase equivalent of this string slice, as a new String.

+

‘Uppercase’ is defined according to the terms of the Unicode Derived Core Property +Uppercase.

+

Since some characters can expand into multiple characters when changing +the case, this function returns a String instead of modifying the +parameter in-place.

+
Examples
+

Basic usage:

+ +
let s = "hello";
+
+assert_eq!("HELLO", s.to_uppercase());
+

Scripts without case are not changed:

+ +
let new_year = "农历新年";
+
+assert_eq!(new_year, new_year.to_uppercase());
+

One character can become multiple:

+ +
let s = "tschüß";
+
+assert_eq!("TSCHÜSS", s.to_uppercase());
+
1.16.0 · source

pub fn repeat(&self, n: usize) -> String

Creates a new String by repeating a string n times.

+
Panics
+

This function will panic if the capacity would overflow.

+
Examples
+

Basic usage:

+ +
assert_eq!("abc".repeat(4), String::from("abcabcabcabc"));
+

A panic upon overflow:

+ +
// this will panic at runtime
+let huge = "0123456789abcdef".repeat(usize::MAX);
+
1.23.0 · source

pub fn to_ascii_uppercase(&self) -> String

Returns a copy of this string where each character is mapped to its +ASCII upper case equivalent.

+

ASCII letters ‘a’ to ‘z’ are mapped to ‘A’ to ‘Z’, +but non-ASCII letters are unchanged.

+

To uppercase the value in-place, use make_ascii_uppercase.

+

To uppercase ASCII characters in addition to non-ASCII characters, use +to_uppercase.

+
Examples
+
let s = "Grüße, Jürgen ❤";
+
+assert_eq!("GRüßE, JüRGEN ❤", s.to_ascii_uppercase());
+
1.23.0 · source

pub fn to_ascii_lowercase(&self) -> String

Returns a copy of this string where each character is mapped to its +ASCII lower case equivalent.

+

ASCII letters ‘A’ to ‘Z’ are mapped to ‘a’ to ‘z’, +but non-ASCII letters are unchanged.

+

To lowercase the value in-place, use make_ascii_lowercase.

+

To lowercase ASCII characters in addition to non-ASCII characters, use +to_lowercase.

+
Examples
+
let s = "Grüße, Jürgen ❤";
+
+assert_eq!("grüße, jürgen ❤", s.to_ascii_lowercase());
+

Trait Implementations§

source§

impl Deref for RECENT_BOOKMARKS_QUERY

§

type Target = String

The resulting type after dereferencing.
source§

fn deref(&self) -> &String

Dereferences the value.
source§

impl LazyStatic for RECENT_BOOKMARKS_QUERY

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/storage/bookmarks/fetch/struct.SEARCH_QUERY.html b/book/rust-docs/places/storage/bookmarks/fetch/struct.SEARCH_QUERY.html new file mode 100644 index 0000000000..284fa9a4e2 --- /dev/null +++ b/book/rust-docs/places/storage/bookmarks/fetch/struct.SEARCH_QUERY.html @@ -0,0 +1,1362 @@ +SEARCH_QUERY in places::storage::bookmarks::fetch - Rust
pub struct SEARCH_QUERY { /* private fields */ }

Methods from Deref<Target = String>§

1.7.0 · source

pub fn as_str(&self) -> &str

Extracts a string slice containing the entire String.

+
Examples
+

Basic usage:

+ +
let s = String::from("foo");
+
+assert_eq!("foo", s.as_str());
+
1.0.0 · source

pub fn capacity(&self) -> usize

Returns this String’s capacity, in bytes.

+
Examples
+

Basic usage:

+ +
let s = String::with_capacity(10);
+
+assert!(s.capacity() >= 10);
+
1.0.0 · source

pub fn as_bytes(&self) -> &[u8]

Returns a byte slice of this String’s contents.

+

The inverse of this method is from_utf8.

+
Examples
+

Basic usage:

+ +
let s = String::from("hello");
+
+assert_eq!(&[104, 101, 108, 108, 111], s.as_bytes());
+
1.0.0 · source

pub fn len(&self) -> usize

Returns the length of this String, in bytes, not chars or +graphemes. In other words, it might not be what a human considers the +length of the string.

+
Examples
+

Basic usage:

+ +
let a = String::from("foo");
+assert_eq!(a.len(), 3);
+
+let fancy_f = String::from("ƒoo");
+assert_eq!(fancy_f.len(), 4);
+assert_eq!(fancy_f.chars().count(), 3);
+
1.0.0 · source

pub fn is_empty(&self) -> bool

Returns true if this String has a length of zero, and false otherwise.

+
Examples
+

Basic usage:

+ +
let mut v = String::new();
+assert!(v.is_empty());
+
+v.push('a');
+assert!(!v.is_empty());
+

Methods from Deref<Target = str>§

1.0.0 · source

pub fn len(&self) -> usize

Returns the length of self.

+

This length is in bytes, not chars or graphemes. In other words, +it might not be what a human considers the length of the string.

+
Examples
+
let len = "foo".len();
+assert_eq!(3, len);
+
+assert_eq!("ƒoo".len(), 4); // fancy f!
+assert_eq!("ƒoo".chars().count(), 3);
+
1.0.0 · source

pub fn is_empty(&self) -> bool

Returns true if self has a length of zero bytes.

+
Examples
+
let s = "";
+assert!(s.is_empty());
+
+let s = "not empty";
+assert!(!s.is_empty());
+
1.9.0 · source

pub fn is_char_boundary(&self, index: usize) -> bool

Checks that index-th byte is the first byte in a UTF-8 code point +sequence or the end of the string.

+

The start and end of the string (when index == self.len()) are +considered to be boundaries.

+

Returns false if index is greater than self.len().

+
Examples
+
let s = "Löwe 老虎 Léopard";
+assert!(s.is_char_boundary(0));
+// start of `老`
+assert!(s.is_char_boundary(6));
+assert!(s.is_char_boundary(s.len()));
+
+// second byte of `ö`
+assert!(!s.is_char_boundary(2));
+
+// third byte of `老`
+assert!(!s.is_char_boundary(8));
+
source

pub fn floor_char_boundary(&self, index: usize) -> usize

🔬This is a nightly-only experimental API. (round_char_boundary)

Finds the closest x not exceeding index where is_char_boundary(x) is true.

+

This method can help you truncate a string so that it’s still valid UTF-8, but doesn’t +exceed a given number of bytes. Note that this is done purely at the character level +and can still visually split graphemes, even though the underlying characters aren’t +split. For example, the emoji 🧑‍🔬 (scientist) could be split so that the string only +includes 🧑 (person) instead.

+
Examples
+
#![feature(round_char_boundary)]
+let s = "❤️🧡💛💚💙💜";
+assert_eq!(s.len(), 26);
+assert!(!s.is_char_boundary(13));
+
+let closest = s.floor_char_boundary(13);
+assert_eq!(closest, 10);
+assert_eq!(&s[..closest], "❤️🧡");
+
source

pub fn ceil_char_boundary(&self, index: usize) -> usize

🔬This is a nightly-only experimental API. (round_char_boundary)

Finds the closest x not below index where is_char_boundary(x) is true.

+

This method is the natural complement to floor_char_boundary. See that method +for more details.

+
Panics
+

Panics if index > self.len().

+
Examples
+
#![feature(round_char_boundary)]
+let s = "❤️🧡💛💚💙💜";
+assert_eq!(s.len(), 26);
+assert!(!s.is_char_boundary(13));
+
+let closest = s.ceil_char_boundary(13);
+assert_eq!(closest, 14);
+assert_eq!(&s[..closest], "❤️🧡💛");
+
1.0.0 · source

pub fn as_bytes(&self) -> &[u8]

Converts a string slice to a byte slice. To convert the byte slice back +into a string slice, use the from_utf8 function.

+
Examples
+
let bytes = "bors".as_bytes();
+assert_eq!(b"bors", bytes);
+
1.20.0 · source

pub unsafe fn as_bytes_mut(&mut self) -> &mut [u8]

Converts a mutable string slice to a mutable byte slice.

+
Safety
+

The caller must ensure that the content of the slice is valid UTF-8 +before the borrow ends and the underlying str is used.

+

Use of a str whose contents are not valid UTF-8 is undefined behavior.

+
Examples
+

Basic usage:

+ +
let mut s = String::from("Hello");
+let bytes = unsafe { s.as_bytes_mut() };
+
+assert_eq!(b"Hello", bytes);
+

Mutability:

+ +
let mut s = String::from("🗻∈🌏");
+
+unsafe {
+    let bytes = s.as_bytes_mut();
+
+    bytes[0] = 0xF0;
+    bytes[1] = 0x9F;
+    bytes[2] = 0x8D;
+    bytes[3] = 0x94;
+}
+
+assert_eq!("🍔∈🌏", s);
+
1.0.0 · source

pub fn as_ptr(&self) -> *const u8

Converts a string slice to a raw pointer.

+

As string slices are a slice of bytes, the raw pointer points to a +u8. This pointer will be pointing to the first byte of the string +slice.

+

The caller must ensure that the returned pointer is never written to. +If you need to mutate the contents of the string slice, use as_mut_ptr.

+
Examples
+
let s = "Hello";
+let ptr = s.as_ptr();
+
1.36.0 · source

pub fn as_mut_ptr(&mut self) -> *mut u8

Converts a mutable string slice to a raw pointer.

+

As string slices are a slice of bytes, the raw pointer points to a +u8. This pointer will be pointing to the first byte of the string +slice.

+

It is your responsibility to make sure that the string slice only gets +modified in a way that it remains valid UTF-8.

+
1.20.0 · source

pub fn get<I>(&self, i: I) -> Option<&<I as SliceIndex<str>>::Output>where + I: SliceIndex<str>,

Returns a subslice of str.

+

This is the non-panicking alternative to indexing the str. Returns +None whenever equivalent indexing operation would panic.

+
Examples
+
let v = String::from("🗻∈🌏");
+
+assert_eq!(Some("🗻"), v.get(0..4));
+
+// indices not on UTF-8 sequence boundaries
+assert!(v.get(1..).is_none());
+assert!(v.get(..8).is_none());
+
+// out of bounds
+assert!(v.get(..42).is_none());
+
1.20.0 · source

pub fn get_mut<I>( + &mut self, + i: I +) -> Option<&mut <I as SliceIndex<str>>::Output>where + I: SliceIndex<str>,

Returns a mutable subslice of str.

+

This is the non-panicking alternative to indexing the str. Returns +None whenever equivalent indexing operation would panic.

+
Examples
+
let mut v = String::from("hello");
+// correct length
+assert!(v.get_mut(0..5).is_some());
+// out of bounds
+assert!(v.get_mut(..42).is_none());
+assert_eq!(Some("he"), v.get_mut(0..2).map(|v| &*v));
+
+assert_eq!("hello", v);
+{
+    let s = v.get_mut(0..2);
+    let s = s.map(|s| {
+        s.make_ascii_uppercase();
+        &*s
+    });
+    assert_eq!(Some("HE"), s);
+}
+assert_eq!("HEllo", v);
+
1.20.0 · source

pub unsafe fn get_unchecked<I>(&self, i: I) -> &<I as SliceIndex<str>>::Outputwhere + I: SliceIndex<str>,

Returns an unchecked subslice of str.

+

This is the unchecked alternative to indexing the str.

+
Safety
+

Callers of this function are responsible that these preconditions are +satisfied:

+
    +
  • The starting index must not exceed the ending index;
  • +
  • Indexes must be within bounds of the original slice;
  • +
  • Indexes must lie on UTF-8 sequence boundaries.
  • +
+

Failing that, the returned string slice may reference invalid memory or +violate the invariants communicated by the str type.

+
Examples
+
let v = "🗻∈🌏";
+unsafe {
+    assert_eq!("🗻", v.get_unchecked(0..4));
+    assert_eq!("∈", v.get_unchecked(4..7));
+    assert_eq!("🌏", v.get_unchecked(7..11));
+}
+
1.20.0 · source

pub unsafe fn get_unchecked_mut<I>( + &mut self, + i: I +) -> &mut <I as SliceIndex<str>>::Outputwhere + I: SliceIndex<str>,

Returns a mutable, unchecked subslice of str.

+

This is the unchecked alternative to indexing the str.

+
Safety
+

Callers of this function are responsible that these preconditions are +satisfied:

+
    +
  • The starting index must not exceed the ending index;
  • +
  • Indexes must be within bounds of the original slice;
  • +
  • Indexes must lie on UTF-8 sequence boundaries.
  • +
+

Failing that, the returned string slice may reference invalid memory or +violate the invariants communicated by the str type.

+
Examples
+
let mut v = String::from("🗻∈🌏");
+unsafe {
+    assert_eq!("🗻", v.get_unchecked_mut(0..4));
+    assert_eq!("∈", v.get_unchecked_mut(4..7));
+    assert_eq!("🌏", v.get_unchecked_mut(7..11));
+}
+
1.0.0 · source

pub unsafe fn slice_unchecked(&self, begin: usize, end: usize) -> &str

👎Deprecated since 1.29.0: use get_unchecked(begin..end) instead

Creates a string slice from another string slice, bypassing safety +checks.

+

This is generally not recommended, use with caution! For a safe +alternative see str and Index.

+

This new slice goes from begin to end, including begin but +excluding end.

+

To get a mutable string slice instead, see the +slice_mut_unchecked method.

+
Safety
+

Callers of this function are responsible that three preconditions are +satisfied:

+
    +
  • begin must not exceed end.
  • +
  • begin and end must be byte positions within the string slice.
  • +
  • begin and end must lie on UTF-8 sequence boundaries.
  • +
+
Examples
+
let s = "Löwe 老虎 Léopard";
+
+unsafe {
+    assert_eq!("Löwe 老虎 Léopard", s.slice_unchecked(0, 21));
+}
+
+let s = "Hello, world!";
+
+unsafe {
+    assert_eq!("world", s.slice_unchecked(7, 12));
+}
+
1.5.0 · source

pub unsafe fn slice_mut_unchecked( + &mut self, + begin: usize, + end: usize +) -> &mut str

👎Deprecated since 1.29.0: use get_unchecked_mut(begin..end) instead

Creates a string slice from another string slice, bypassing safety +checks. +This is generally not recommended, use with caution! For a safe +alternative see str and IndexMut.

+

This new slice goes from begin to end, including begin but +excluding end.

+

To get an immutable string slice instead, see the +slice_unchecked method.

+
Safety
+

Callers of this function are responsible that three preconditions are +satisfied:

+
    +
  • begin must not exceed end.
  • +
  • begin and end must be byte positions within the string slice.
  • +
  • begin and end must lie on UTF-8 sequence boundaries.
  • +
+
1.4.0 · source

pub fn split_at(&self, mid: usize) -> (&str, &str)

Divide one string slice into two at an index.

+

The argument, mid, should be a byte offset from the start of the +string. It must also be on the boundary of a UTF-8 code point.

+

The two slices returned go from the start of the string slice to mid, +and from mid to the end of the string slice.

+

To get mutable string slices instead, see the split_at_mut +method.

+
Panics
+

Panics if mid is not on a UTF-8 code point boundary, or if it is +past the end of the last code point of the string slice.

+
Examples
+
let s = "Per Martin-Löf";
+
+let (first, last) = s.split_at(3);
+
+assert_eq!("Per", first);
+assert_eq!(" Martin-Löf", last);
+
1.4.0 · source

pub fn split_at_mut(&mut self, mid: usize) -> (&mut str, &mut str)

Divide one mutable string slice into two at an index.

+

The argument, mid, should be a byte offset from the start of the +string. It must also be on the boundary of a UTF-8 code point.

+

The two slices returned go from the start of the string slice to mid, +and from mid to the end of the string slice.

+

To get immutable string slices instead, see the split_at method.

+
Panics
+

Panics if mid is not on a UTF-8 code point boundary, or if it is +past the end of the last code point of the string slice.

+
Examples
+
let mut s = "Per Martin-Löf".to_string();
+{
+    let (first, last) = s.split_at_mut(3);
+    first.make_ascii_uppercase();
+    assert_eq!("PER", first);
+    assert_eq!(" Martin-Löf", last);
+}
+assert_eq!("PER Martin-Löf", s);
+
1.0.0 · source

pub fn chars(&self) -> Chars<'_>

Returns an iterator over the chars of a string slice.

+

As a string slice consists of valid UTF-8, we can iterate through a +string slice by char. This method returns such an iterator.

+

It’s important to remember that char represents a Unicode Scalar +Value, and might not match your idea of what a ‘character’ is. Iteration +over grapheme clusters may be what you actually want. This functionality +is not provided by Rust’s standard library, check crates.io instead.

+
Examples
+

Basic usage:

+ +
let word = "goodbye";
+
+let count = word.chars().count();
+assert_eq!(7, count);
+
+let mut chars = word.chars();
+
+assert_eq!(Some('g'), chars.next());
+assert_eq!(Some('o'), chars.next());
+assert_eq!(Some('o'), chars.next());
+assert_eq!(Some('d'), chars.next());
+assert_eq!(Some('b'), chars.next());
+assert_eq!(Some('y'), chars.next());
+assert_eq!(Some('e'), chars.next());
+
+assert_eq!(None, chars.next());
+

Remember, chars might not match your intuition about characters:

+ +
let y = "y̆";
+
+let mut chars = y.chars();
+
+assert_eq!(Some('y'), chars.next()); // not 'y̆'
+assert_eq!(Some('\u{0306}'), chars.next());
+
+assert_eq!(None, chars.next());
+
1.0.0 · source

pub fn char_indices(&self) -> CharIndices<'_>

Returns an iterator over the chars of a string slice, and their +positions.

+

As a string slice consists of valid UTF-8, we can iterate through a +string slice by char. This method returns an iterator of both +these chars, as well as their byte positions.

+

The iterator yields tuples. The position is first, the char is +second.

+
Examples
+

Basic usage:

+ +
let word = "goodbye";
+
+let count = word.char_indices().count();
+assert_eq!(7, count);
+
+let mut char_indices = word.char_indices();
+
+assert_eq!(Some((0, 'g')), char_indices.next());
+assert_eq!(Some((1, 'o')), char_indices.next());
+assert_eq!(Some((2, 'o')), char_indices.next());
+assert_eq!(Some((3, 'd')), char_indices.next());
+assert_eq!(Some((4, 'b')), char_indices.next());
+assert_eq!(Some((5, 'y')), char_indices.next());
+assert_eq!(Some((6, 'e')), char_indices.next());
+
+assert_eq!(None, char_indices.next());
+

Remember, chars might not match your intuition about characters:

+ +
let yes = "y̆es";
+
+let mut char_indices = yes.char_indices();
+
+assert_eq!(Some((0, 'y')), char_indices.next()); // not (0, 'y̆')
+assert_eq!(Some((1, '\u{0306}')), char_indices.next());
+
+// note the 3 here - the last character took up two bytes
+assert_eq!(Some((3, 'e')), char_indices.next());
+assert_eq!(Some((4, 's')), char_indices.next());
+
+assert_eq!(None, char_indices.next());
+
1.0.0 · source

pub fn bytes(&self) -> Bytes<'_>

An iterator over the bytes of a string slice.

+

As a string slice consists of a sequence of bytes, we can iterate +through a string slice by byte. This method returns such an iterator.

+
Examples
+
let mut bytes = "bors".bytes();
+
+assert_eq!(Some(b'b'), bytes.next());
+assert_eq!(Some(b'o'), bytes.next());
+assert_eq!(Some(b'r'), bytes.next());
+assert_eq!(Some(b's'), bytes.next());
+
+assert_eq!(None, bytes.next());
+
1.1.0 · source

pub fn split_whitespace(&self) -> SplitWhitespace<'_>

Splits a string slice by whitespace.

+

The iterator returned will return string slices that are sub-slices of +the original string slice, separated by any amount of whitespace.

+

‘Whitespace’ is defined according to the terms of the Unicode Derived +Core Property White_Space. If you only want to split on ASCII whitespace +instead, use split_ascii_whitespace.

+
Examples
+

Basic usage:

+ +
let mut iter = "A few words".split_whitespace();
+
+assert_eq!(Some("A"), iter.next());
+assert_eq!(Some("few"), iter.next());
+assert_eq!(Some("words"), iter.next());
+
+assert_eq!(None, iter.next());
+

All kinds of whitespace are considered:

+ +
let mut iter = " Mary   had\ta\u{2009}little  \n\t lamb".split_whitespace();
+assert_eq!(Some("Mary"), iter.next());
+assert_eq!(Some("had"), iter.next());
+assert_eq!(Some("a"), iter.next());
+assert_eq!(Some("little"), iter.next());
+assert_eq!(Some("lamb"), iter.next());
+
+assert_eq!(None, iter.next());
+

If the string is empty or all whitespace, the iterator yields no string slices:

+ +
assert_eq!("".split_whitespace().next(), None);
+assert_eq!("   ".split_whitespace().next(), None);
+
1.34.0 · source

pub fn split_ascii_whitespace(&self) -> SplitAsciiWhitespace<'_>

Splits a string slice by ASCII whitespace.

+

The iterator returned will return string slices that are sub-slices of +the original string slice, separated by any amount of ASCII whitespace.

+

To split by Unicode Whitespace instead, use split_whitespace.

+
Examples
+

Basic usage:

+ +
let mut iter = "A few words".split_ascii_whitespace();
+
+assert_eq!(Some("A"), iter.next());
+assert_eq!(Some("few"), iter.next());
+assert_eq!(Some("words"), iter.next());
+
+assert_eq!(None, iter.next());
+

All kinds of ASCII whitespace are considered:

+ +
let mut iter = " Mary   had\ta little  \n\t lamb".split_ascii_whitespace();
+assert_eq!(Some("Mary"), iter.next());
+assert_eq!(Some("had"), iter.next());
+assert_eq!(Some("a"), iter.next());
+assert_eq!(Some("little"), iter.next());
+assert_eq!(Some("lamb"), iter.next());
+
+assert_eq!(None, iter.next());
+

If the string is empty or all ASCII whitespace, the iterator yields no string slices:

+ +
assert_eq!("".split_ascii_whitespace().next(), None);
+assert_eq!("   ".split_ascii_whitespace().next(), None);
+
1.0.0 · source

pub fn lines(&self) -> Lines<'_>

An iterator over the lines of a string, as string slices.

+

Lines are split at line endings that are either newlines (\n) or +sequences of a carriage return followed by a line feed (\r\n).

+

Line terminators are not included in the lines returned by the iterator.

+

The final line ending is optional. A string that ends with a final line +ending will return the same lines as an otherwise identical string +without a final line ending.

+
Examples
+

Basic usage:

+ +
let text = "foo\r\nbar\n\nbaz\n";
+let mut lines = text.lines();
+
+assert_eq!(Some("foo"), lines.next());
+assert_eq!(Some("bar"), lines.next());
+assert_eq!(Some(""), lines.next());
+assert_eq!(Some("baz"), lines.next());
+
+assert_eq!(None, lines.next());
+

The final line ending isn’t required:

+ +
let text = "foo\nbar\n\r\nbaz";
+let mut lines = text.lines();
+
+assert_eq!(Some("foo"), lines.next());
+assert_eq!(Some("bar"), lines.next());
+assert_eq!(Some(""), lines.next());
+assert_eq!(Some("baz"), lines.next());
+
+assert_eq!(None, lines.next());
+
1.0.0 · source

pub fn lines_any(&self) -> LinesAny<'_>

👎Deprecated since 1.4.0: use lines() instead now

An iterator over the lines of a string.

+
1.8.0 · source

pub fn encode_utf16(&self) -> EncodeUtf16<'_>

Returns an iterator of u16 over the string encoded as UTF-16.

+
Examples
+
let text = "Zażółć gęślą jaźń";
+
+let utf8_len = text.len();
+let utf16_len = text.encode_utf16().count();
+
+assert!(utf16_len <= utf8_len);
+
1.0.0 · source

pub fn contains<'a, P>(&'a self, pat: P) -> boolwhere + P: Pattern<'a>,

Returns true if the given pattern matches a sub-slice of +this string slice.

+

Returns false if it does not.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Examples
+
let bananas = "bananas";
+
+assert!(bananas.contains("nana"));
+assert!(!bananas.contains("apples"));
+
1.0.0 · source

pub fn starts_with<'a, P>(&'a self, pat: P) -> boolwhere + P: Pattern<'a>,

Returns true if the given pattern matches a prefix of this +string slice.

+

Returns false if it does not.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Examples
+
let bananas = "bananas";
+
+assert!(bananas.starts_with("bana"));
+assert!(!bananas.starts_with("nana"));
+
1.0.0 · source

pub fn ends_with<'a, P>(&'a self, pat: P) -> boolwhere + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,

Returns true if the given pattern matches a suffix of this +string slice.

+

Returns false if it does not.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Examples
+
let bananas = "bananas";
+
+assert!(bananas.ends_with("anas"));
+assert!(!bananas.ends_with("nana"));
+
1.0.0 · source

pub fn find<'a, P>(&'a self, pat: P) -> Option<usize>where + P: Pattern<'a>,

Returns the byte index of the first character of this string slice that +matches the pattern.

+

Returns None if the pattern doesn’t match.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Examples
+

Simple patterns:

+ +
let s = "Löwe 老虎 Léopard Gepardi";
+
+assert_eq!(s.find('L'), Some(0));
+assert_eq!(s.find('é'), Some(14));
+assert_eq!(s.find("pard"), Some(17));
+

More complex patterns using point-free style and closures:

+ +
let s = "Löwe 老虎 Léopard";
+
+assert_eq!(s.find(char::is_whitespace), Some(5));
+assert_eq!(s.find(char::is_lowercase), Some(1));
+assert_eq!(s.find(|c: char| c.is_whitespace() || c.is_lowercase()), Some(1));
+assert_eq!(s.find(|c: char| (c < 'o') && (c > 'a')), Some(4));
+

Not finding the pattern:

+ +
let s = "Löwe 老虎 Léopard";
+let x: &[_] = &['1', '2'];
+
+assert_eq!(s.find(x), None);
+
1.0.0 · source

pub fn rfind<'a, P>(&'a self, pat: P) -> Option<usize>where + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,

Returns the byte index for the first character of the last match of the pattern in +this string slice.

+

Returns None if the pattern doesn’t match.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Examples
+

Simple patterns:

+ +
let s = "Löwe 老虎 Léopard Gepardi";
+
+assert_eq!(s.rfind('L'), Some(13));
+assert_eq!(s.rfind('é'), Some(14));
+assert_eq!(s.rfind("pard"), Some(24));
+

More complex patterns with closures:

+ +
let s = "Löwe 老虎 Léopard";
+
+assert_eq!(s.rfind(char::is_whitespace), Some(12));
+assert_eq!(s.rfind(char::is_lowercase), Some(20));
+

Not finding the pattern:

+ +
let s = "Löwe 老虎 Léopard";
+let x: &[_] = &['1', '2'];
+
+assert_eq!(s.rfind(x), None);
+
1.0.0 · source

pub fn split<'a, P>(&'a self, pat: P) -> Split<'a, P>where + P: Pattern<'a>,

An iterator over substrings of this string slice, separated by +characters matched by a pattern.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Iterator behavior
+

The returned iterator will be a DoubleEndedIterator if the pattern +allows a reverse search and forward/reverse search yields the same +elements. This is true for, e.g., char, but not for &str.

+

If the pattern allows a reverse search but its results might differ +from a forward search, the rsplit method can be used.

+
Examples
+

Simple patterns:

+ +
let v: Vec<&str> = "Mary had a little lamb".split(' ').collect();
+assert_eq!(v, ["Mary", "had", "a", "little", "lamb"]);
+
+let v: Vec<&str> = "".split('X').collect();
+assert_eq!(v, [""]);
+
+let v: Vec<&str> = "lionXXtigerXleopard".split('X').collect();
+assert_eq!(v, ["lion", "", "tiger", "leopard"]);
+
+let v: Vec<&str> = "lion::tiger::leopard".split("::").collect();
+assert_eq!(v, ["lion", "tiger", "leopard"]);
+
+let v: Vec<&str> = "abc1def2ghi".split(char::is_numeric).collect();
+assert_eq!(v, ["abc", "def", "ghi"]);
+
+let v: Vec<&str> = "lionXtigerXleopard".split(char::is_uppercase).collect();
+assert_eq!(v, ["lion", "tiger", "leopard"]);
+

If the pattern is a slice of chars, split on each occurrence of any of the characters:

+ +
let v: Vec<&str> = "2020-11-03 23:59".split(&['-', ' ', ':', '@'][..]).collect();
+assert_eq!(v, ["2020", "11", "03", "23", "59"]);
+

A more complex pattern, using a closure:

+ +
let v: Vec<&str> = "abc1defXghi".split(|c| c == '1' || c == 'X').collect();
+assert_eq!(v, ["abc", "def", "ghi"]);
+

If a string contains multiple contiguous separators, you will end up +with empty strings in the output:

+ +
let x = "||||a||b|c".to_string();
+let d: Vec<_> = x.split('|').collect();
+
+assert_eq!(d, &["", "", "", "", "a", "", "b", "c"]);
+

Contiguous separators are separated by the empty string.

+ +
let x = "(///)".to_string();
+let d: Vec<_> = x.split('/').collect();
+
+assert_eq!(d, &["(", "", "", ")"]);
+

Separators at the start or end of a string are neighbored +by empty strings.

+ +
let d: Vec<_> = "010".split("0").collect();
+assert_eq!(d, &["", "1", ""]);
+

When the empty string is used as a separator, it separates +every character in the string, along with the beginning +and end of the string.

+ +
let f: Vec<_> = "rust".split("").collect();
+assert_eq!(f, &["", "r", "u", "s", "t", ""]);
+

Contiguous separators can lead to possibly surprising behavior +when whitespace is used as the separator. This code is correct:

+ +
let x = "    a  b c".to_string();
+let d: Vec<_> = x.split(' ').collect();
+
+assert_eq!(d, &["", "", "", "", "a", "", "b", "c"]);
+

It does not give you:

+ +
assert_eq!(d, &["a", "b", "c"]);
+

Use split_whitespace for this behavior.

+
1.51.0 · source

pub fn split_inclusive<'a, P>(&'a self, pat: P) -> SplitInclusive<'a, P>where + P: Pattern<'a>,

An iterator over substrings of this string slice, separated by +characters matched by a pattern. Differs from the iterator produced by +split in that split_inclusive leaves the matched part as the +terminator of the substring.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Examples
+
let v: Vec<&str> = "Mary had a little lamb\nlittle lamb\nlittle lamb."
+    .split_inclusive('\n').collect();
+assert_eq!(v, ["Mary had a little lamb\n", "little lamb\n", "little lamb."]);
+

If the last element of the string is matched, +that element will be considered the terminator of the preceding substring. +That substring will be the last item returned by the iterator.

+ +
let v: Vec<&str> = "Mary had a little lamb\nlittle lamb\nlittle lamb.\n"
+    .split_inclusive('\n').collect();
+assert_eq!(v, ["Mary had a little lamb\n", "little lamb\n", "little lamb.\n"]);
+
1.0.0 · source

pub fn rsplit<'a, P>(&'a self, pat: P) -> RSplit<'a, P>where + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,

An iterator over substrings of the given string slice, separated by +characters matched by a pattern and yielded in reverse order.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Iterator behavior
+

The returned iterator requires that the pattern supports a reverse +search, and it will be a DoubleEndedIterator if a forward/reverse +search yields the same elements.

+

For iterating from the front, the split method can be used.

+
Examples
+

Simple patterns:

+ +
let v: Vec<&str> = "Mary had a little lamb".rsplit(' ').collect();
+assert_eq!(v, ["lamb", "little", "a", "had", "Mary"]);
+
+let v: Vec<&str> = "".rsplit('X').collect();
+assert_eq!(v, [""]);
+
+let v: Vec<&str> = "lionXXtigerXleopard".rsplit('X').collect();
+assert_eq!(v, ["leopard", "tiger", "", "lion"]);
+
+let v: Vec<&str> = "lion::tiger::leopard".rsplit("::").collect();
+assert_eq!(v, ["leopard", "tiger", "lion"]);
+

A more complex pattern, using a closure:

+ +
let v: Vec<&str> = "abc1defXghi".rsplit(|c| c == '1' || c == 'X').collect();
+assert_eq!(v, ["ghi", "def", "abc"]);
+
1.0.0 · source

pub fn split_terminator<'a, P>(&'a self, pat: P) -> SplitTerminator<'a, P>where + P: Pattern<'a>,

An iterator over substrings of the given string slice, separated by +characters matched by a pattern.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+

Equivalent to split, except that the trailing substring +is skipped if empty.

+

This method can be used for string data that is terminated, +rather than separated by a pattern.

+
Iterator behavior
+

The returned iterator will be a DoubleEndedIterator if the pattern +allows a reverse search and forward/reverse search yields the same +elements. This is true for, e.g., char, but not for &str.

+

If the pattern allows a reverse search but its results might differ +from a forward search, the rsplit_terminator method can be used.

+
Examples
+
let v: Vec<&str> = "A.B.".split_terminator('.').collect();
+assert_eq!(v, ["A", "B"]);
+
+let v: Vec<&str> = "A..B..".split_terminator(".").collect();
+assert_eq!(v, ["A", "", "B", ""]);
+
+let v: Vec<&str> = "A.B:C.D".split_terminator(&['.', ':'][..]).collect();
+assert_eq!(v, ["A", "B", "C", "D"]);
+
1.0.0 · source

pub fn rsplit_terminator<'a, P>(&'a self, pat: P) -> RSplitTerminator<'a, P>where + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,

An iterator over substrings of self, separated by characters +matched by a pattern and yielded in reverse order.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+

Equivalent to split, except that the trailing substring is +skipped if empty.

+

This method can be used for string data that is terminated, +rather than separated by a pattern.

+
Iterator behavior
+

The returned iterator requires that the pattern supports a +reverse search, and it will be double ended if a forward/reverse +search yields the same elements.

+

For iterating from the front, the split_terminator method can be +used.

+
Examples
+
let v: Vec<&str> = "A.B.".rsplit_terminator('.').collect();
+assert_eq!(v, ["B", "A"]);
+
+let v: Vec<&str> = "A..B..".rsplit_terminator(".").collect();
+assert_eq!(v, ["", "B", "", "A"]);
+
+let v: Vec<&str> = "A.B:C.D".rsplit_terminator(&['.', ':'][..]).collect();
+assert_eq!(v, ["D", "C", "B", "A"]);
+
1.0.0 · source

pub fn splitn<'a, P>(&'a self, n: usize, pat: P) -> SplitN<'a, P>where + P: Pattern<'a>,

An iterator over substrings of the given string slice, separated by a +pattern, restricted to returning at most n items.

+

If n substrings are returned, the last substring (the nth substring) +will contain the remainder of the string.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Iterator behavior
+

The returned iterator will not be double ended, because it is +not efficient to support.

+

If the pattern allows a reverse search, the rsplitn method can be +used.

+
Examples
+

Simple patterns:

+ +
let v: Vec<&str> = "Mary had a little lambda".splitn(3, ' ').collect();
+assert_eq!(v, ["Mary", "had", "a little lambda"]);
+
+let v: Vec<&str> = "lionXXtigerXleopard".splitn(3, "X").collect();
+assert_eq!(v, ["lion", "", "tigerXleopard"]);
+
+let v: Vec<&str> = "abcXdef".splitn(1, 'X').collect();
+assert_eq!(v, ["abcXdef"]);
+
+let v: Vec<&str> = "".splitn(1, 'X').collect();
+assert_eq!(v, [""]);
+

A more complex pattern, using a closure:

+ +
let v: Vec<&str> = "abc1defXghi".splitn(2, |c| c == '1' || c == 'X').collect();
+assert_eq!(v, ["abc", "defXghi"]);
+
1.0.0 · source

pub fn rsplitn<'a, P>(&'a self, n: usize, pat: P) -> RSplitN<'a, P>where + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,

An iterator over substrings of this string slice, separated by a +pattern, starting from the end of the string, restricted to returning +at most n items.

+

If n substrings are returned, the last substring (the nth substring) +will contain the remainder of the string.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Iterator behavior
+

The returned iterator will not be double ended, because it is not +efficient to support.

+

For splitting from the front, the splitn method can be used.

+
Examples
+

Simple patterns:

+ +
let v: Vec<&str> = "Mary had a little lamb".rsplitn(3, ' ').collect();
+assert_eq!(v, ["lamb", "little", "Mary had a"]);
+
+let v: Vec<&str> = "lionXXtigerXleopard".rsplitn(3, 'X').collect();
+assert_eq!(v, ["leopard", "tiger", "lionX"]);
+
+let v: Vec<&str> = "lion::tiger::leopard".rsplitn(2, "::").collect();
+assert_eq!(v, ["leopard", "lion::tiger"]);
+

A more complex pattern, using a closure:

+ +
let v: Vec<&str> = "abc1defXghi".rsplitn(2, |c| c == '1' || c == 'X').collect();
+assert_eq!(v, ["ghi", "abc1def"]);
+
1.52.0 · source

pub fn split_once<'a, P>(&'a self, delimiter: P) -> Option<(&'a str, &'a str)>where + P: Pattern<'a>,

Splits the string on the first occurrence of the specified delimiter and +returns prefix before delimiter and suffix after delimiter.

+
Examples
+
assert_eq!("cfg".split_once('='), None);
+assert_eq!("cfg=".split_once('='), Some(("cfg", "")));
+assert_eq!("cfg=foo".split_once('='), Some(("cfg", "foo")));
+assert_eq!("cfg=foo=bar".split_once('='), Some(("cfg", "foo=bar")));
+
1.52.0 · source

pub fn rsplit_once<'a, P>(&'a self, delimiter: P) -> Option<(&'a str, &'a str)>where + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,

Splits the string on the last occurrence of the specified delimiter and +returns prefix before delimiter and suffix after delimiter.

+
Examples
+
assert_eq!("cfg".rsplit_once('='), None);
+assert_eq!("cfg=foo".rsplit_once('='), Some(("cfg", "foo")));
+assert_eq!("cfg=foo=bar".rsplit_once('='), Some(("cfg=foo", "bar")));
+
1.2.0 · source

pub fn matches<'a, P>(&'a self, pat: P) -> Matches<'a, P>where + P: Pattern<'a>,

An iterator over the disjoint matches of a pattern within the given string +slice.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Iterator behavior
+

The returned iterator will be a DoubleEndedIterator if the pattern +allows a reverse search and forward/reverse search yields the same +elements. This is true for, e.g., char, but not for &str.

+

If the pattern allows a reverse search but its results might differ +from a forward search, the rmatches method can be used.

+
Examples
+
let v: Vec<&str> = "abcXXXabcYYYabc".matches("abc").collect();
+assert_eq!(v, ["abc", "abc", "abc"]);
+
+let v: Vec<&str> = "1abc2abc3".matches(char::is_numeric).collect();
+assert_eq!(v, ["1", "2", "3"]);
+
1.2.0 · source

pub fn rmatches<'a, P>(&'a self, pat: P) -> RMatches<'a, P>where + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,

An iterator over the disjoint matches of a pattern within this string slice, +yielded in reverse order.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Iterator behavior
+

The returned iterator requires that the pattern supports a reverse +search, and it will be a DoubleEndedIterator if a forward/reverse +search yields the same elements.

+

For iterating from the front, the matches method can be used.

+
Examples
+
let v: Vec<&str> = "abcXXXabcYYYabc".rmatches("abc").collect();
+assert_eq!(v, ["abc", "abc", "abc"]);
+
+let v: Vec<&str> = "1abc2abc3".rmatches(char::is_numeric).collect();
+assert_eq!(v, ["3", "2", "1"]);
+
1.5.0 · source

pub fn match_indices<'a, P>(&'a self, pat: P) -> MatchIndices<'a, P>where + P: Pattern<'a>,

An iterator over the disjoint matches of a pattern within this string +slice as well as the index that the match starts at.

+

For matches of pat within self that overlap, only the indices +corresponding to the first match are returned.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Iterator behavior
+

The returned iterator will be a DoubleEndedIterator if the pattern +allows a reverse search and forward/reverse search yields the same +elements. This is true for, e.g., char, but not for &str.

+

If the pattern allows a reverse search but its results might differ +from a forward search, the rmatch_indices method can be used.

+
Examples
+
let v: Vec<_> = "abcXXXabcYYYabc".match_indices("abc").collect();
+assert_eq!(v, [(0, "abc"), (6, "abc"), (12, "abc")]);
+
+let v: Vec<_> = "1abcabc2".match_indices("abc").collect();
+assert_eq!(v, [(1, "abc"), (4, "abc")]);
+
+let v: Vec<_> = "ababa".match_indices("aba").collect();
+assert_eq!(v, [(0, "aba")]); // only the first `aba`
+
1.5.0 · source

pub fn rmatch_indices<'a, P>(&'a self, pat: P) -> RMatchIndices<'a, P>where + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,

An iterator over the disjoint matches of a pattern within self, +yielded in reverse order along with the index of the match.

+

For matches of pat within self that overlap, only the indices +corresponding to the last match are returned.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Iterator behavior
+

The returned iterator requires that the pattern supports a reverse +search, and it will be a DoubleEndedIterator if a forward/reverse +search yields the same elements.

+

For iterating from the front, the match_indices method can be used.

+
Examples
+
let v: Vec<_> = "abcXXXabcYYYabc".rmatch_indices("abc").collect();
+assert_eq!(v, [(12, "abc"), (6, "abc"), (0, "abc")]);
+
+let v: Vec<_> = "1abcabc2".rmatch_indices("abc").collect();
+assert_eq!(v, [(4, "abc"), (1, "abc")]);
+
+let v: Vec<_> = "ababa".rmatch_indices("aba").collect();
+assert_eq!(v, [(2, "aba")]); // only the last `aba`
+
1.0.0 · source

pub fn trim(&self) -> &str

Returns a string slice with leading and trailing whitespace removed.

+

‘Whitespace’ is defined according to the terms of the Unicode Derived +Core Property White_Space, which includes newlines.

+
Examples
+
let s = "\n Hello\tworld\t\n";
+
+assert_eq!("Hello\tworld", s.trim());
+
1.30.0 · source

pub fn trim_start(&self) -> &str

Returns a string slice with leading whitespace removed.

+

‘Whitespace’ is defined according to the terms of the Unicode Derived +Core Property White_Space, which includes newlines.

+
Text directionality
+

A string is a sequence of bytes. start in this context means the first +position of that byte string; for a left-to-right language like English or +Russian, this will be left side, and for right-to-left languages like +Arabic or Hebrew, this will be the right side.

+
Examples
+

Basic usage:

+ +
let s = "\n Hello\tworld\t\n";
+assert_eq!("Hello\tworld\t\n", s.trim_start());
+

Directionality:

+ +
let s = "  English  ";
+assert!(Some('E') == s.trim_start().chars().next());
+
+let s = "  עברית  ";
+assert!(Some('ע') == s.trim_start().chars().next());
+
1.30.0 · source

pub fn trim_end(&self) -> &str

Returns a string slice with trailing whitespace removed.

+

‘Whitespace’ is defined according to the terms of the Unicode Derived +Core Property White_Space, which includes newlines.

+
Text directionality
+

A string is a sequence of bytes. end in this context means the last +position of that byte string; for a left-to-right language like English or +Russian, this will be right side, and for right-to-left languages like +Arabic or Hebrew, this will be the left side.

+
Examples
+

Basic usage:

+ +
let s = "\n Hello\tworld\t\n";
+assert_eq!("\n Hello\tworld", s.trim_end());
+

Directionality:

+ +
let s = "  English  ";
+assert!(Some('h') == s.trim_end().chars().rev().next());
+
+let s = "  עברית  ";
+assert!(Some('ת') == s.trim_end().chars().rev().next());
+
1.0.0 · source

pub fn trim_left(&self) -> &str

👎Deprecated since 1.33.0: superseded by trim_start

Returns a string slice with leading whitespace removed.

+

‘Whitespace’ is defined according to the terms of the Unicode Derived +Core Property White_Space.

+
Text directionality
+

A string is a sequence of bytes. ‘Left’ in this context means the first +position of that byte string; for a language like Arabic or Hebrew +which are ‘right to left’ rather than ‘left to right’, this will be +the right side, not the left.

+
Examples
+

Basic usage:

+ +
let s = " Hello\tworld\t";
+
+assert_eq!("Hello\tworld\t", s.trim_left());
+

Directionality:

+ +
let s = "  English";
+assert!(Some('E') == s.trim_left().chars().next());
+
+let s = "  עברית";
+assert!(Some('ע') == s.trim_left().chars().next());
+
1.0.0 · source

pub fn trim_right(&self) -> &str

👎Deprecated since 1.33.0: superseded by trim_end

Returns a string slice with trailing whitespace removed.

+

‘Whitespace’ is defined according to the terms of the Unicode Derived +Core Property White_Space.

+
Text directionality
+

A string is a sequence of bytes. ‘Right’ in this context means the last +position of that byte string; for a language like Arabic or Hebrew +which are ‘right to left’ rather than ‘left to right’, this will be +the left side, not the right.

+
Examples
+

Basic usage:

+ +
let s = " Hello\tworld\t";
+
+assert_eq!(" Hello\tworld", s.trim_right());
+

Directionality:

+ +
let s = "English  ";
+assert!(Some('h') == s.trim_right().chars().rev().next());
+
+let s = "עברית  ";
+assert!(Some('ת') == s.trim_right().chars().rev().next());
+
1.0.0 · source

pub fn trim_matches<'a, P>(&'a self, pat: P) -> &'a strwhere + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: DoubleEndedSearcher<'a>,

Returns a string slice with all prefixes and suffixes that match a +pattern repeatedly removed.

+

The pattern can be a char, a slice of chars, or a function +or closure that determines if a character matches.

+
Examples
+

Simple patterns:

+ +
assert_eq!("11foo1bar11".trim_matches('1'), "foo1bar");
+assert_eq!("123foo1bar123".trim_matches(char::is_numeric), "foo1bar");
+
+let x: &[_] = &['1', '2'];
+assert_eq!("12foo1bar12".trim_matches(x), "foo1bar");
+

A more complex pattern, using a closure:

+ +
assert_eq!("1foo1barXX".trim_matches(|c| c == '1' || c == 'X'), "foo1bar");
+
1.30.0 · source

pub fn trim_start_matches<'a, P>(&'a self, pat: P) -> &'a strwhere + P: Pattern<'a>,

Returns a string slice with all prefixes that match a pattern +repeatedly removed.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Text directionality
+

A string is a sequence of bytes. start in this context means the first +position of that byte string; for a left-to-right language like English or +Russian, this will be left side, and for right-to-left languages like +Arabic or Hebrew, this will be the right side.

+
Examples
+
assert_eq!("11foo1bar11".trim_start_matches('1'), "foo1bar11");
+assert_eq!("123foo1bar123".trim_start_matches(char::is_numeric), "foo1bar123");
+
+let x: &[_] = &['1', '2'];
+assert_eq!("12foo1bar12".trim_start_matches(x), "foo1bar12");
+
1.45.0 · source

pub fn strip_prefix<'a, P>(&'a self, prefix: P) -> Option<&'a str>where + P: Pattern<'a>,

Returns a string slice with the prefix removed.

+

If the string starts with the pattern prefix, returns substring after the prefix, wrapped +in Some. Unlike trim_start_matches, this method removes the prefix exactly once.

+

If the string does not start with prefix, returns None.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Examples
+
assert_eq!("foo:bar".strip_prefix("foo:"), Some("bar"));
+assert_eq!("foo:bar".strip_prefix("bar"), None);
+assert_eq!("foofoo".strip_prefix("foo"), Some("foo"));
+
1.45.0 · source

pub fn strip_suffix<'a, P>(&'a self, suffix: P) -> Option<&'a str>where + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,

Returns a string slice with the suffix removed.

+

If the string ends with the pattern suffix, returns the substring before the suffix, +wrapped in Some. Unlike trim_end_matches, this method removes the suffix exactly once.

+

If the string does not end with suffix, returns None.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Examples
+
assert_eq!("bar:foo".strip_suffix(":foo"), Some("bar"));
+assert_eq!("bar:foo".strip_suffix("bar"), None);
+assert_eq!("foofoo".strip_suffix("foo"), Some("foo"));
+
1.30.0 · source

pub fn trim_end_matches<'a, P>(&'a self, pat: P) -> &'a strwhere + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,

Returns a string slice with all suffixes that match a pattern +repeatedly removed.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Text directionality
+

A string is a sequence of bytes. end in this context means the last +position of that byte string; for a left-to-right language like English or +Russian, this will be right side, and for right-to-left languages like +Arabic or Hebrew, this will be the left side.

+
Examples
+

Simple patterns:

+ +
assert_eq!("11foo1bar11".trim_end_matches('1'), "11foo1bar");
+assert_eq!("123foo1bar123".trim_end_matches(char::is_numeric), "123foo1bar");
+
+let x: &[_] = &['1', '2'];
+assert_eq!("12foo1bar12".trim_end_matches(x), "12foo1bar");
+

A more complex pattern, using a closure:

+ +
assert_eq!("1fooX".trim_end_matches(|c| c == '1' || c == 'X'), "1foo");
+
1.0.0 · source

pub fn trim_left_matches<'a, P>(&'a self, pat: P) -> &'a strwhere + P: Pattern<'a>,

👎Deprecated since 1.33.0: superseded by trim_start_matches

Returns a string slice with all prefixes that match a pattern +repeatedly removed.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Text directionality
+

A string is a sequence of bytes. ‘Left’ in this context means the first +position of that byte string; for a language like Arabic or Hebrew +which are ‘right to left’ rather than ‘left to right’, this will be +the right side, not the left.

+
Examples
+
assert_eq!("11foo1bar11".trim_left_matches('1'), "foo1bar11");
+assert_eq!("123foo1bar123".trim_left_matches(char::is_numeric), "foo1bar123");
+
+let x: &[_] = &['1', '2'];
+assert_eq!("12foo1bar12".trim_left_matches(x), "foo1bar12");
+
1.0.0 · source

pub fn trim_right_matches<'a, P>(&'a self, pat: P) -> &'a strwhere + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,

👎Deprecated since 1.33.0: superseded by trim_end_matches

Returns a string slice with all suffixes that match a pattern +repeatedly removed.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Text directionality
+

A string is a sequence of bytes. ‘Right’ in this context means the last +position of that byte string; for a language like Arabic or Hebrew +which are ‘right to left’ rather than ‘left to right’, this will be +the left side, not the right.

+
Examples
+

Simple patterns:

+ +
assert_eq!("11foo1bar11".trim_right_matches('1'), "11foo1bar");
+assert_eq!("123foo1bar123".trim_right_matches(char::is_numeric), "123foo1bar");
+
+let x: &[_] = &['1', '2'];
+assert_eq!("12foo1bar12".trim_right_matches(x), "12foo1bar");
+

A more complex pattern, using a closure:

+ +
assert_eq!("1fooX".trim_right_matches(|c| c == '1' || c == 'X'), "1foo");
+
1.0.0 · source

pub fn parse<F>(&self) -> Result<F, <F as FromStr>::Err>where + F: FromStr,

Parses this string slice into another type.

+

Because parse is so general, it can cause problems with type +inference. As such, parse is one of the few times you’ll see +the syntax affectionately known as the ‘turbofish’: ::<>. This +helps the inference algorithm understand specifically which type +you’re trying to parse into.

+

parse can parse into any type that implements the FromStr trait.

+
Errors
+

Will return Err if it’s not possible to parse this string slice into +the desired type.

+
Examples
+

Basic usage

+ +
let four: u32 = "4".parse().unwrap();
+
+assert_eq!(4, four);
+

Using the ‘turbofish’ instead of annotating four:

+ +
let four = "4".parse::<u32>();
+
+assert_eq!(Ok(4), four);
+

Failing to parse:

+ +
let nope = "j".parse::<u32>();
+
+assert!(nope.is_err());
+
1.23.0 · source

pub fn is_ascii(&self) -> bool

Checks if all characters in this string are within the ASCII range.

+
Examples
+
let ascii = "hello!\n";
+let non_ascii = "Grüße, Jürgen ❤";
+
+assert!(ascii.is_ascii());
+assert!(!non_ascii.is_ascii());
+
source

pub fn as_ascii(&self) -> Option<&[AsciiChar]>

🔬This is a nightly-only experimental API. (ascii_char)

If this string slice is_ascii, returns it as a slice +of ASCII characters, otherwise returns None.

+
1.23.0 · source

pub fn eq_ignore_ascii_case(&self, other: &str) -> bool

Checks that two strings are an ASCII case-insensitive match.

+

Same as to_ascii_lowercase(a) == to_ascii_lowercase(b), +but without allocating and copying temporaries.

+
Examples
+
assert!("Ferris".eq_ignore_ascii_case("FERRIS"));
+assert!("Ferrös".eq_ignore_ascii_case("FERRöS"));
+assert!(!"Ferrös".eq_ignore_ascii_case("FERRÖS"));
+
1.23.0 · source

pub fn make_ascii_uppercase(&mut self)

Converts this string to its ASCII upper case equivalent in-place.

+

ASCII letters ‘a’ to ‘z’ are mapped to ‘A’ to ‘Z’, +but non-ASCII letters are unchanged.

+

To return a new uppercased value without modifying the existing one, use +to_ascii_uppercase().

+
Examples
+
let mut s = String::from("Grüße, Jürgen ❤");
+
+s.make_ascii_uppercase();
+
+assert_eq!("GRüßE, JüRGEN ❤", s);
+
1.23.0 · source

pub fn make_ascii_lowercase(&mut self)

Converts this string to its ASCII lower case equivalent in-place.

+

ASCII letters ‘A’ to ‘Z’ are mapped to ‘a’ to ‘z’, +but non-ASCII letters are unchanged.

+

To return a new lowercased value without modifying the existing one, use +to_ascii_lowercase().

+
Examples
+
let mut s = String::from("GRÜßE, JÜRGEN ❤");
+
+s.make_ascii_lowercase();
+
+assert_eq!("grÜße, jÜrgen ❤", s);
+
1.34.0 · source

pub fn escape_debug(&self) -> EscapeDebug<'_>

Return an iterator that escapes each char in self with char::escape_debug.

+

Note: only extended grapheme codepoints that begin the string will be +escaped.

+
Examples
+

As an iterator:

+ +
for c in "❤\n!".escape_debug() {
+    print!("{c}");
+}
+println!();
+

Using println! directly:

+ +
println!("{}", "❤\n!".escape_debug());
+

Both are equivalent to:

+ +
println!("❤\\n!");
+

Using to_string:

+ +
assert_eq!("❤\n!".escape_debug().to_string(), "❤\\n!");
+
1.34.0 · source

pub fn escape_default(&self) -> EscapeDefault<'_>

Return an iterator that escapes each char in self with char::escape_default.

+
Examples
+

As an iterator:

+ +
for c in "❤\n!".escape_default() {
+    print!("{c}");
+}
+println!();
+

Using println! directly:

+ +
println!("{}", "❤\n!".escape_default());
+

Both are equivalent to:

+ +
println!("\\u{{2764}}\\n!");
+

Using to_string:

+ +
assert_eq!("❤\n!".escape_default().to_string(), "\\u{2764}\\n!");
+
1.34.0 · source

pub fn escape_unicode(&self) -> EscapeUnicode<'_>

Return an iterator that escapes each char in self with char::escape_unicode.

+
Examples
+

As an iterator:

+ +
for c in "❤\n!".escape_unicode() {
+    print!("{c}");
+}
+println!();
+

Using println! directly:

+ +
println!("{}", "❤\n!".escape_unicode());
+

Both are equivalent to:

+ +
println!("\\u{{2764}}\\u{{a}}\\u{{21}}");
+

Using to_string:

+ +
assert_eq!("❤\n!".escape_unicode().to_string(), "\\u{2764}\\u{a}\\u{21}");
+
1.0.0 · source

pub fn replace<'a, P>(&'a self, from: P, to: &str) -> Stringwhere + P: Pattern<'a>,

Replaces all matches of a pattern with another string.

+

replace creates a new String, and copies the data from this string slice into it. +While doing so, it attempts to find matches of a pattern. If it finds any, it +replaces them with the replacement string slice.

+
Examples
+

Basic usage:

+ +
let s = "this is old";
+
+assert_eq!("this is new", s.replace("old", "new"));
+assert_eq!("than an old", s.replace("is", "an"));
+

When the pattern doesn’t match, it returns this string slice as String:

+ +
let s = "this is old";
+assert_eq!(s, s.replace("cookie monster", "little lamb"));
+
1.16.0 · source

pub fn replacen<'a, P>(&'a self, pat: P, to: &str, count: usize) -> Stringwhere + P: Pattern<'a>,

Replaces first N matches of a pattern with another string.

+

replacen creates a new String, and copies the data from this string slice into it. +While doing so, it attempts to find matches of a pattern. If it finds any, it +replaces them with the replacement string slice at most count times.

+
Examples
+

Basic usage:

+ +
let s = "foo foo 123 foo";
+assert_eq!("new new 123 foo", s.replacen("foo", "new", 2));
+assert_eq!("faa fao 123 foo", s.replacen('o', "a", 3));
+assert_eq!("foo foo new23 foo", s.replacen(char::is_numeric, "new", 1));
+

When the pattern doesn’t match, it returns this string slice as String:

+ +
let s = "this is old";
+assert_eq!(s, s.replacen("cookie monster", "little lamb", 10));
+
1.2.0 · source

pub fn to_lowercase(&self) -> String

Returns the lowercase equivalent of this string slice, as a new String.

+

‘Lowercase’ is defined according to the terms of the Unicode Derived Core Property +Lowercase.

+

Since some characters can expand into multiple characters when changing +the case, this function returns a String instead of modifying the +parameter in-place.

+
Examples
+

Basic usage:

+ +
let s = "HELLO";
+
+assert_eq!("hello", s.to_lowercase());
+

A tricky example, with sigma:

+ +
let sigma = "Σ";
+
+assert_eq!("σ", sigma.to_lowercase());
+
+// but at the end of a word, it's ς, not σ:
+let odysseus = "ὈΔΥΣΣΕΎΣ";
+
+assert_eq!("ὀδυσσεύς", odysseus.to_lowercase());
+

Languages without case are not changed:

+ +
let new_year = "农历新年";
+
+assert_eq!(new_year, new_year.to_lowercase());
+
1.2.0 · source

pub fn to_uppercase(&self) -> String

Returns the uppercase equivalent of this string slice, as a new String.

+

‘Uppercase’ is defined according to the terms of the Unicode Derived Core Property +Uppercase.

+

Since some characters can expand into multiple characters when changing +the case, this function returns a String instead of modifying the +parameter in-place.

+
Examples
+

Basic usage:

+ +
let s = "hello";
+
+assert_eq!("HELLO", s.to_uppercase());
+

Scripts without case are not changed:

+ +
let new_year = "农历新年";
+
+assert_eq!(new_year, new_year.to_uppercase());
+

One character can become multiple:

+ +
let s = "tschüß";
+
+assert_eq!("TSCHÜSS", s.to_uppercase());
+
1.16.0 · source

pub fn repeat(&self, n: usize) -> String

Creates a new String by repeating a string n times.

+
Panics
+

This function will panic if the capacity would overflow.

+
Examples
+

Basic usage:

+ +
assert_eq!("abc".repeat(4), String::from("abcabcabcabc"));
+

A panic upon overflow:

+ +
// this will panic at runtime
+let huge = "0123456789abcdef".repeat(usize::MAX);
+
1.23.0 · source

pub fn to_ascii_uppercase(&self) -> String

Returns a copy of this string where each character is mapped to its +ASCII upper case equivalent.

+

ASCII letters ‘a’ to ‘z’ are mapped to ‘A’ to ‘Z’, +but non-ASCII letters are unchanged.

+

To uppercase the value in-place, use make_ascii_uppercase.

+

To uppercase ASCII characters in addition to non-ASCII characters, use +to_uppercase.

+
Examples
+
let s = "Grüße, Jürgen ❤";
+
+assert_eq!("GRüßE, JüRGEN ❤", s.to_ascii_uppercase());
+
1.23.0 · source

pub fn to_ascii_lowercase(&self) -> String

Returns a copy of this string where each character is mapped to its +ASCII lower case equivalent.

+

ASCII letters ‘A’ to ‘Z’ are mapped to ‘a’ to ‘z’, +but non-ASCII letters are unchanged.

+

To lowercase the value in-place, use make_ascii_lowercase.

+

To lowercase ASCII characters in addition to non-ASCII characters, use +to_lowercase.

+
Examples
+
let s = "Grüße, Jürgen ❤";
+
+assert_eq!("grüße, jürgen ❤", s.to_ascii_lowercase());
+

Trait Implementations§

source§

impl Deref for SEARCH_QUERY

§

type Target = String

The resulting type after dereferencing.
source§

fn deref(&self) -> &String

Dereferences the value.
source§

impl LazyStatic for SEARCH_QUERY

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/storage/bookmarks/fetch/struct.Separator.html b/book/rust-docs/places/storage/bookmarks/fetch/struct.Separator.html new file mode 100644 index 0000000000..43cad42fe4 --- /dev/null +++ b/book/rust-docs/places/storage/bookmarks/fetch/struct.Separator.html @@ -0,0 +1,19 @@ +Separator in places::storage::bookmarks::fetch - Rust
pub struct Separator {
+    pub guid: SyncGuid,
+    pub date_added: Timestamp,
+    pub last_modified: Timestamp,
+    pub parent_guid: SyncGuid,
+    pub position: u32,
+}

Fields§

§guid: SyncGuid§date_added: Timestamp§last_modified: Timestamp§parent_guid: SyncGuid§position: u32

Trait Implementations§

source§

impl Clone for Separator

source§

fn clone(&self) -> Separator

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for Separator

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl From<Separator> for Item

source§

fn from(s: Separator) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/storage/bookmarks/fn.bookmarks_get_url_for_keyword.html b/book/rust-docs/places/storage/bookmarks/fn.bookmarks_get_url_for_keyword.html new file mode 100644 index 0000000000..74f9d1490f --- /dev/null +++ b/book/rust-docs/places/storage/bookmarks/fn.bookmarks_get_url_for_keyword.html @@ -0,0 +1,5 @@ +bookmarks_get_url_for_keyword in places::storage::bookmarks - Rust
pub fn bookmarks_get_url_for_keyword(
+    db: &PlacesDb,
+    keyword: &str
+) -> Result<Option<Url>>
Expand description

Get the URL of the bookmark matching a keyword

+
\ No newline at end of file diff --git a/book/rust-docs/places/storage/bookmarks/fn.count_bookmarks_in_trees.html b/book/rust-docs/places/storage/bookmarks/fn.count_bookmarks_in_trees.html new file mode 100644 index 0000000000..b1cc066b72 --- /dev/null +++ b/book/rust-docs/places/storage/bookmarks/fn.count_bookmarks_in_trees.html @@ -0,0 +1,4 @@ +count_bookmarks_in_trees in places::storage::bookmarks - Rust
pub fn count_bookmarks_in_trees(
+    db: &PlacesDb,
+    item_guids: &[SyncGuid]
+) -> Result<u32>
\ No newline at end of file diff --git a/book/rust-docs/places/storage/bookmarks/fn.create_bookmark_roots.html b/book/rust-docs/places/storage/bookmarks/fn.create_bookmark_roots.html new file mode 100644 index 0000000000..a308b1d3c3 --- /dev/null +++ b/book/rust-docs/places/storage/bookmarks/fn.create_bookmark_roots.html @@ -0,0 +1 @@ +create_bookmark_roots in places::storage::bookmarks - Rust
pub fn create_bookmark_roots(db: &Connection) -> Result<()>
\ No newline at end of file diff --git a/book/rust-docs/places/storage/bookmarks/fn.delete_bookmark.html b/book/rust-docs/places/storage/bookmarks/fn.delete_bookmark.html new file mode 100644 index 0000000000..0fcb5e75a4 --- /dev/null +++ b/book/rust-docs/places/storage/bookmarks/fn.delete_bookmark.html @@ -0,0 +1,3 @@ +delete_bookmark in places::storage::bookmarks - Rust
pub fn delete_bookmark(db: &PlacesDb, guid: &SyncGuid) -> Result<bool>
Expand description

Delete the specified bookmark. Returns true if a bookmark with the guid +existed and was deleted, false otherwise.

+
\ No newline at end of file diff --git a/book/rust-docs/places/storage/bookmarks/fn.delete_everything.html b/book/rust-docs/places/storage/bookmarks/fn.delete_everything.html new file mode 100644 index 0000000000..5f2f5d4200 --- /dev/null +++ b/book/rust-docs/places/storage/bookmarks/fn.delete_everything.html @@ -0,0 +1,2 @@ +delete_everything in places::storage::bookmarks - Rust
pub fn delete_everything(db: &PlacesDb) -> Result<()>
Expand description

Erases all bookmarks and resets all Sync metadata.

+
\ No newline at end of file diff --git a/book/rust-docs/places/storage/bookmarks/fn.insert_bookmark.html b/book/rust-docs/places/storage/bookmarks/fn.insert_bookmark.html new file mode 100644 index 0000000000..1bb94a8974 --- /dev/null +++ b/book/rust-docs/places/storage/bookmarks/fn.insert_bookmark.html @@ -0,0 +1 @@ +insert_bookmark in places::storage::bookmarks - Rust
pub fn insert_bookmark(db: &PlacesDb, bm: InsertableItem) -> Result<SyncGuid>
\ No newline at end of file diff --git a/book/rust-docs/places/storage/bookmarks/fn.maybe_truncate_title.html b/book/rust-docs/places/storage/bookmarks/fn.maybe_truncate_title.html new file mode 100644 index 0000000000..f1b54afaaf --- /dev/null +++ b/book/rust-docs/places/storage/bookmarks/fn.maybe_truncate_title.html @@ -0,0 +1 @@ +maybe_truncate_title in places::storage::bookmarks - Rust
pub fn maybe_truncate_title<'a>(t: &Option<&'a str>) -> Option<&'a str>
\ No newline at end of file diff --git a/book/rust-docs/places/storage/bookmarks/fn.update_bookmark.html b/book/rust-docs/places/storage/bookmarks/fn.update_bookmark.html new file mode 100644 index 0000000000..f1c066d08c --- /dev/null +++ b/book/rust-docs/places/storage/bookmarks/fn.update_bookmark.html @@ -0,0 +1,5 @@ +update_bookmark in places::storage::bookmarks - Rust
pub fn update_bookmark(
+    db: &PlacesDb,
+    guid: &SyncGuid,
+    item: &UpdatableItem
+) -> Result<()>
\ No newline at end of file diff --git a/book/rust-docs/places/storage/bookmarks/fn.update_bookmark_from_info.html b/book/rust-docs/places/storage/bookmarks/fn.update_bookmark_from_info.html new file mode 100644 index 0000000000..1d78eb4696 --- /dev/null +++ b/book/rust-docs/places/storage/bookmarks/fn.update_bookmark_from_info.html @@ -0,0 +1,4 @@ +update_bookmark_from_info in places::storage::bookmarks - Rust
pub fn update_bookmark_from_info(
+    db: &PlacesDb,
+    info: BookmarkUpdateInfo
+) -> Result<()>
\ No newline at end of file diff --git a/book/rust-docs/places/storage/bookmarks/index.html b/book/rust-docs/places/storage/bookmarks/index.html new file mode 100644 index 0000000000..a46e128830 --- /dev/null +++ b/book/rust-docs/places/storage/bookmarks/index.html @@ -0,0 +1,10 @@ +places::storage::bookmarks - Rust

Module places::storage::bookmarks

source ·

Modules

Structs

  • We don’t require bookmark type for updates on the other side of the FFI, +since the type is immutable, and iOS wants to be able to move bookmarks by +GUID. We also don’t/can’t enforce as much in the Kotlin/Swift type system +as we can/do in Rust.
  • Structures which can be used to insert a bookmark, folder or separator.
  • Structures which can be used to update a bookmark, folder or separator. +Almost all fields are Option<>-like, with None meaning “do not change”. +Many fields which can’t be changed by our public API are omitted (eg, +guid, date_added, last_modified, etc)

Enums

Constants

Functions

\ No newline at end of file diff --git a/book/rust-docs/places/storage/bookmarks/json_tree/enum.BookmarkTreeNode.html b/book/rust-docs/places/storage/bookmarks/json_tree/enum.BookmarkTreeNode.html new file mode 100644 index 0000000000..8f79636007 --- /dev/null +++ b/book/rust-docs/places/storage/bookmarks/json_tree/enum.BookmarkTreeNode.html @@ -0,0 +1,25 @@ +BookmarkTreeNode in places::storage::bookmarks::json_tree - Rust
pub enum BookmarkTreeNode {
+    Bookmark {
+        b: BookmarkNode,
+    },
+    Separator {
+        s: SeparatorNode,
+    },
+    Folder {
+        f: FolderNode,
+    },
+}

Variants§

§

Bookmark

§

Separator

§

Folder

Fields

Implementations§

Trait Implementations§

source§

impl Debug for BookmarkTreeNode

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl<'de> Deserialize<'de> for BookmarkTreeNode

source§

fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>where + D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl From<BookmarkNode> for BookmarkTreeNode

source§

fn from(b: BookmarkNode) -> Self

Converts to this type from the input type.
source§

impl From<BookmarkTreeNode> for InsertableItem

source§

fn from(node: BookmarkTreeNode) -> Self

Converts to this type from the input type.
source§

impl From<FolderNode> for BookmarkTreeNode

source§

fn from(f: FolderNode) -> Self

Converts to this type from the input type.
source§

impl From<SeparatorNode> for BookmarkTreeNode

source§

fn from(s: SeparatorNode) -> Self

Converts to this type from the input type.
source§

impl Serialize for BookmarkTreeNode

source§

fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>where + S: Serializer,

Serialize this value into the given Serde serializer. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/places/storage/bookmarks/json_tree/enum.FetchDepth.html b/book/rust-docs/places/storage/bookmarks/json_tree/enum.FetchDepth.html new file mode 100644 index 0000000000..3efe0fffae --- /dev/null +++ b/book/rust-docs/places/storage/bookmarks/json_tree/enum.FetchDepth.html @@ -0,0 +1,18 @@ +FetchDepth in places::storage::bookmarks::json_tree - Rust
pub enum FetchDepth {
+    Specific(usize),
+    Deepest,
+}
Expand description

Fetch the tree starting at the specified guid. +Returns a BookmarkTreeNode, its parent’s guid (if any), and +position inside its parent.

+

Variants§

§

Specific(usize)

§

Deepest

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/storage/bookmarks/json_tree/fn.fetch_tree.html b/book/rust-docs/places/storage/bookmarks/json_tree/fn.fetch_tree.html new file mode 100644 index 0000000000..3f3385b851 --- /dev/null +++ b/book/rust-docs/places/storage/bookmarks/json_tree/fn.fetch_tree.html @@ -0,0 +1,5 @@ +fetch_tree in places::storage::bookmarks::json_tree - Rust
pub fn fetch_tree(
+    db: &PlacesDb,
+    item_guid: &SyncGuid,
+    target_depth: &FetchDepth
+) -> Result<Option<(BookmarkTreeNode, Option<SyncGuid>, u32)>>
\ No newline at end of file diff --git a/book/rust-docs/places/storage/bookmarks/json_tree/fn.insert_tree.html b/book/rust-docs/places/storage/bookmarks/json_tree/fn.insert_tree.html new file mode 100644 index 0000000000..f893bf5bcf --- /dev/null +++ b/book/rust-docs/places/storage/bookmarks/json_tree/fn.insert_tree.html @@ -0,0 +1 @@ +insert_tree in places::storage::bookmarks::json_tree - Rust
pub fn insert_tree(db: &PlacesDb, tree: FolderNode) -> Result<()>
\ No newline at end of file diff --git a/book/rust-docs/places/storage/bookmarks/json_tree/index.html b/book/rust-docs/places/storage/bookmarks/json_tree/index.html new file mode 100644 index 0000000000..451ac40109 --- /dev/null +++ b/book/rust-docs/places/storage/bookmarks/json_tree/index.html @@ -0,0 +1,3 @@ +places::storage::bookmarks::json_tree - Rust

Structs

Enums

  • Fetch the tree starting at the specified guid. +Returns a BookmarkTreeNode, its parent’s guid (if any), and +position inside its parent.

Functions

\ No newline at end of file diff --git a/book/rust-docs/places/storage/bookmarks/json_tree/sidebar-items.js b/book/rust-docs/places/storage/bookmarks/json_tree/sidebar-items.js new file mode 100644 index 0000000000..be1803e6eb --- /dev/null +++ b/book/rust-docs/places/storage/bookmarks/json_tree/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["BookmarkTreeNode","FetchDepth"],"fn":["fetch_tree","insert_tree"],"struct":["BookmarkNode","FolderNode","SeparatorNode"]}; \ No newline at end of file diff --git a/book/rust-docs/places/storage/bookmarks/json_tree/struct.BookmarkNode.html b/book/rust-docs/places/storage/bookmarks/json_tree/struct.BookmarkNode.html new file mode 100644 index 0000000000..6ee5f37eec --- /dev/null +++ b/book/rust-docs/places/storage/bookmarks/json_tree/struct.BookmarkNode.html @@ -0,0 +1,18 @@ +BookmarkNode in places::storage::bookmarks::json_tree - Rust
pub struct BookmarkNode {
+    pub guid: Option<SyncGuid>,
+    pub date_added: Option<Timestamp>,
+    pub last_modified: Option<Timestamp>,
+    pub title: Option<String>,
+    pub url: Url,
+}

Fields§

§guid: Option<SyncGuid>§date_added: Option<Timestamp>§last_modified: Option<Timestamp>§title: Option<String>§url: Url

Trait Implementations§

source§

impl Debug for BookmarkNode

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl From<BookmarkNode> for BookmarkTreeNode

source§

fn from(b: BookmarkNode) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/storage/bookmarks/json_tree/struct.FolderNode.html b/book/rust-docs/places/storage/bookmarks/json_tree/struct.FolderNode.html new file mode 100644 index 0000000000..ffe4486049 --- /dev/null +++ b/book/rust-docs/places/storage/bookmarks/json_tree/struct.FolderNode.html @@ -0,0 +1,18 @@ +FolderNode in places::storage::bookmarks::json_tree - Rust
pub struct FolderNode {
+    pub guid: Option<SyncGuid>,
+    pub date_added: Option<Timestamp>,
+    pub last_modified: Option<Timestamp>,
+    pub title: Option<String>,
+    pub children: Vec<BookmarkTreeNode>,
+}

Fields§

§guid: Option<SyncGuid>§date_added: Option<Timestamp>§last_modified: Option<Timestamp>§title: Option<String>§children: Vec<BookmarkTreeNode>

Trait Implementations§

source§

impl Debug for FolderNode

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for FolderNode

source§

fn default() -> FolderNode

Returns the “default value” for a type. Read more
source§

impl From<FolderNode> for BookmarkTreeNode

source§

fn from(f: FolderNode) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/storage/bookmarks/json_tree/struct.SeparatorNode.html b/book/rust-docs/places/storage/bookmarks/json_tree/struct.SeparatorNode.html new file mode 100644 index 0000000000..aa8569b4d9 --- /dev/null +++ b/book/rust-docs/places/storage/bookmarks/json_tree/struct.SeparatorNode.html @@ -0,0 +1,16 @@ +SeparatorNode in places::storage::bookmarks::json_tree - Rust
pub struct SeparatorNode {
+    pub guid: Option<SyncGuid>,
+    pub date_added: Option<Timestamp>,
+    pub last_modified: Option<Timestamp>,
+}

Fields§

§guid: Option<SyncGuid>§date_added: Option<Timestamp>§last_modified: Option<Timestamp>

Trait Implementations§

source§

impl Debug for SeparatorNode

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for SeparatorNode

source§

fn default() -> SeparatorNode

Returns the “default value” for a type. Read more
source§

impl From<SeparatorNode> for BookmarkTreeNode

source§

fn from(s: SeparatorNode) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/storage/bookmarks/root_guid/constant.USER_CONTENT_ROOTS.html b/book/rust-docs/places/storage/bookmarks/root_guid/constant.USER_CONTENT_ROOTS.html new file mode 100644 index 0000000000..9d9dc0f321 --- /dev/null +++ b/book/rust-docs/places/storage/bookmarks/root_guid/constant.USER_CONTENT_ROOTS.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../../places/storage/bookmarks/constant.USER_CONTENT_ROOTS.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/places/storage/bookmarks/root_guid/enum.BookmarkRootGuid.html b/book/rust-docs/places/storage/bookmarks/root_guid/enum.BookmarkRootGuid.html new file mode 100644 index 0000000000..bebd6fa0cb --- /dev/null +++ b/book/rust-docs/places/storage/bookmarks/root_guid/enum.BookmarkRootGuid.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../../places/storage/bookmarks/enum.BookmarkRootGuid.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/places/storage/bookmarks/sidebar-items.js b/book/rust-docs/places/storage/bookmarks/sidebar-items.js new file mode 100644 index 0000000000..8ddb3ae5ff --- /dev/null +++ b/book/rust-docs/places/storage/bookmarks/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"constant":["USER_CONTENT_ROOTS"],"enum":["BookmarkPosition","BookmarkRootGuid","InsertableItem","UpdatableItem","UpdateTreeLocation"],"fn":["bookmarks_get_url_for_keyword","count_bookmarks_in_trees","create_bookmark_roots","delete_bookmark","delete_everything","insert_bookmark","maybe_truncate_title","update_bookmark","update_bookmark_from_info"],"mod":["bookmark_sync","fetch","json_tree"],"struct":["BookmarkUpdateInfo","InsertableBookmark","InsertableFolder","InsertableSeparator","UpdatableBookmark","UpdatableFolder","UpdatableSeparator"]}; \ No newline at end of file diff --git a/book/rust-docs/places/storage/bookmarks/struct.BookmarkUpdateInfo.html b/book/rust-docs/places/storage/bookmarks/struct.BookmarkUpdateInfo.html new file mode 100644 index 0000000000..97945c56f9 --- /dev/null +++ b/book/rust-docs/places/storage/bookmarks/struct.BookmarkUpdateInfo.html @@ -0,0 +1,38 @@ +BookmarkUpdateInfo in places::storage::bookmarks - Rust
pub struct BookmarkUpdateInfo {
+    pub guid: SyncGuid,
+    pub title: Option<String>,
+    pub url: Option<String>,
+    pub parent_guid: Option<SyncGuid>,
+    pub position: Option<u32>,
+}
Expand description

We don’t require bookmark type for updates on the other side of the FFI, +since the type is immutable, and iOS wants to be able to move bookmarks by +GUID. We also don’t/can’t enforce as much in the Kotlin/Swift type system +as we can/do in Rust.

+

This is a type that represents the data we get from the FFI, which we then +turn into a UpdatableItem that we can actually use (we do this by +reading the type out of the DB, but we can do that transactionally, so it’s +not a problem).

+

It’s basically an intermediate between the protobuf message format and +UpdatableItem, used to avoid needing to pass in the type to update, and +to give us a place to check things that we can’t enforce in Swift/Kotlin’s +type system, but that we do in Rust’s.

+

Fields§

§guid: SyncGuid§title: Option<String>§url: Option<String>§parent_guid: Option<SyncGuid>§position: Option<u32>

Implementations§

source§

impl BookmarkUpdateInfo

source

pub fn into_updatable( + self, + ty: BookmarkType +) -> Result<(SyncGuid, UpdatableItem)>

The functions exposed over the FFI use the same type for all inserts. +This function converts that into the type our update API uses.

+

Trait Implementations§

source§

impl Clone for BookmarkUpdateInfo

source§

fn clone(&self) -> BookmarkUpdateInfo

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for BookmarkUpdateInfo

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl PartialEq<BookmarkUpdateInfo> for BookmarkUpdateInfo

source§

fn eq(&self, other: &BookmarkUpdateInfo) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Eq for BookmarkUpdateInfo

source§

impl StructuralEq for BookmarkUpdateInfo

source§

impl StructuralPartialEq for BookmarkUpdateInfo

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/storage/bookmarks/struct.InsertableBookmark.html b/book/rust-docs/places/storage/bookmarks/struct.InsertableBookmark.html new file mode 100644 index 0000000000..943244f1c0 --- /dev/null +++ b/book/rust-docs/places/storage/bookmarks/struct.InsertableBookmark.html @@ -0,0 +1,22 @@ +InsertableBookmark in places::storage::bookmarks - Rust
pub struct InsertableBookmark {
+    pub parent_guid: SyncGuid,
+    pub position: BookmarkPosition,
+    pub date_added: Option<Timestamp>,
+    pub last_modified: Option<Timestamp>,
+    pub guid: Option<SyncGuid>,
+    pub url: Url,
+    pub title: Option<String>,
+}
Expand description

Structures which can be used to insert a bookmark, folder or separator.

+

Fields§

§parent_guid: SyncGuid§position: BookmarkPosition§date_added: Option<Timestamp>§last_modified: Option<Timestamp>§guid: Option<SyncGuid>§url: Url§title: Option<String>

Trait Implementations§

source§

impl Clone for InsertableBookmark

source§

fn clone(&self) -> InsertableBookmark

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for InsertableBookmark

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl From<InsertableBookmark> for InsertableItem

source§

fn from(b: InsertableBookmark) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/storage/bookmarks/struct.InsertableFolder.html b/book/rust-docs/places/storage/bookmarks/struct.InsertableFolder.html new file mode 100644 index 0000000000..921ee9077f --- /dev/null +++ b/book/rust-docs/places/storage/bookmarks/struct.InsertableFolder.html @@ -0,0 +1,21 @@ +InsertableFolder in places::storage::bookmarks - Rust
pub struct InsertableFolder {
+    pub parent_guid: SyncGuid,
+    pub position: BookmarkPosition,
+    pub date_added: Option<Timestamp>,
+    pub last_modified: Option<Timestamp>,
+    pub guid: Option<SyncGuid>,
+    pub title: Option<String>,
+    pub children: Vec<InsertableItem>,
+}

Fields§

§parent_guid: SyncGuid§position: BookmarkPosition§date_added: Option<Timestamp>§last_modified: Option<Timestamp>§guid: Option<SyncGuid>§title: Option<String>§children: Vec<InsertableItem>

Trait Implementations§

source§

impl Clone for InsertableFolder

source§

fn clone(&self) -> InsertableFolder

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for InsertableFolder

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl From<InsertableFolder> for InsertableItem

source§

fn from(f: InsertableFolder) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/storage/bookmarks/struct.InsertableSeparator.html b/book/rust-docs/places/storage/bookmarks/struct.InsertableSeparator.html new file mode 100644 index 0000000000..0c50bd18ef --- /dev/null +++ b/book/rust-docs/places/storage/bookmarks/struct.InsertableSeparator.html @@ -0,0 +1,19 @@ +InsertableSeparator in places::storage::bookmarks - Rust
pub struct InsertableSeparator {
+    pub parent_guid: SyncGuid,
+    pub position: BookmarkPosition,
+    pub date_added: Option<Timestamp>,
+    pub last_modified: Option<Timestamp>,
+    pub guid: Option<SyncGuid>,
+}

Fields§

§parent_guid: SyncGuid§position: BookmarkPosition§date_added: Option<Timestamp>§last_modified: Option<Timestamp>§guid: Option<SyncGuid>

Trait Implementations§

source§

impl Clone for InsertableSeparator

source§

fn clone(&self) -> InsertableSeparator

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for InsertableSeparator

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl From<InsertableSeparator> for InsertableItem

source§

fn from(s: InsertableSeparator) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/storage/bookmarks/struct.UpdatableBookmark.html b/book/rust-docs/places/storage/bookmarks/struct.UpdatableBookmark.html new file mode 100644 index 0000000000..6254a10c02 --- /dev/null +++ b/book/rust-docs/places/storage/bookmarks/struct.UpdatableBookmark.html @@ -0,0 +1,21 @@ +UpdatableBookmark in places::storage::bookmarks - Rust
pub struct UpdatableBookmark {
+    pub location: UpdateTreeLocation,
+    pub url: Option<Url>,
+    pub title: Option<String>,
+}
Expand description

Structures which can be used to update a bookmark, folder or separator. +Almost all fields are Option<>-like, with None meaning “do not change”. +Many fields which can’t be changed by our public API are omitted (eg, +guid, date_added, last_modified, etc)

+

Fields§

§location: UpdateTreeLocation§url: Option<Url>§title: Option<String>

Trait Implementations§

source§

impl Clone for UpdatableBookmark

source§

fn clone(&self) -> UpdatableBookmark

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for UpdatableBookmark

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for UpdatableBookmark

source§

fn default() -> UpdatableBookmark

Returns the “default value” for a type. Read more
source§

impl From<UpdatableBookmark> for UpdatableItem

source§

fn from(b: UpdatableBookmark) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/storage/bookmarks/struct.UpdatableFolder.html b/book/rust-docs/places/storage/bookmarks/struct.UpdatableFolder.html new file mode 100644 index 0000000000..7f60b9ba49 --- /dev/null +++ b/book/rust-docs/places/storage/bookmarks/struct.UpdatableFolder.html @@ -0,0 +1,16 @@ +UpdatableFolder in places::storage::bookmarks - Rust
pub struct UpdatableFolder {
+    pub location: UpdateTreeLocation,
+    pub title: Option<String>,
+}

Fields§

§location: UpdateTreeLocation§title: Option<String>

Trait Implementations§

source§

impl Clone for UpdatableFolder

source§

fn clone(&self) -> UpdatableFolder

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for UpdatableFolder

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for UpdatableFolder

source§

fn default() -> UpdatableFolder

Returns the “default value” for a type. Read more
source§

impl From<UpdatableFolder> for UpdatableItem

source§

fn from(f: UpdatableFolder) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/storage/bookmarks/struct.UpdatableSeparator.html b/book/rust-docs/places/storage/bookmarks/struct.UpdatableSeparator.html new file mode 100644 index 0000000000..f7894b8926 --- /dev/null +++ b/book/rust-docs/places/storage/bookmarks/struct.UpdatableSeparator.html @@ -0,0 +1,15 @@ +UpdatableSeparator in places::storage::bookmarks - Rust
pub struct UpdatableSeparator {
+    pub location: UpdateTreeLocation,
+}

Fields§

§location: UpdateTreeLocation

Trait Implementations§

source§

impl Clone for UpdatableSeparator

source§

fn clone(&self) -> UpdatableSeparator

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for UpdatableSeparator

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl From<UpdatableSeparator> for UpdatableItem

source§

fn from(s: UpdatableSeparator) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/storage/constant.TAG_LENGTH_MAX.html b/book/rust-docs/places/storage/constant.TAG_LENGTH_MAX.html new file mode 100644 index 0000000000..2d5361816c --- /dev/null +++ b/book/rust-docs/places/storage/constant.TAG_LENGTH_MAX.html @@ -0,0 +1 @@ +TAG_LENGTH_MAX in places::storage - Rust

Constant places::storage::TAG_LENGTH_MAX

source ·
pub const TAG_LENGTH_MAX: usize = 100;
\ No newline at end of file diff --git a/book/rust-docs/places/storage/constant.TITLE_LENGTH_MAX.html b/book/rust-docs/places/storage/constant.TITLE_LENGTH_MAX.html new file mode 100644 index 0000000000..b8cbcb2e42 --- /dev/null +++ b/book/rust-docs/places/storage/constant.TITLE_LENGTH_MAX.html @@ -0,0 +1 @@ +TITLE_LENGTH_MAX in places::storage - Rust
pub const TITLE_LENGTH_MAX: usize = 4096;
\ No newline at end of file diff --git a/book/rust-docs/places/storage/constant.URL_LENGTH_MAX.html b/book/rust-docs/places/storage/constant.URL_LENGTH_MAX.html new file mode 100644 index 0000000000..77bf74d1cc --- /dev/null +++ b/book/rust-docs/places/storage/constant.URL_LENGTH_MAX.html @@ -0,0 +1,2 @@ +URL_LENGTH_MAX in places::storage - Rust

Constant places::storage::URL_LENGTH_MAX

source ·
pub const URL_LENGTH_MAX: usize = 65536;
Expand description

From https://searchfox.org/mozilla-central/rev/93905b660f/toolkit/components/places/PlacesUtils.jsm#189

+
\ No newline at end of file diff --git a/book/rust-docs/places/storage/fn.delete_pending_temp_tables.html b/book/rust-docs/places/storage/fn.delete_pending_temp_tables.html new file mode 100644 index 0000000000..d2ee47e8a1 --- /dev/null +++ b/book/rust-docs/places/storage/fn.delete_pending_temp_tables.html @@ -0,0 +1,2 @@ +delete_pending_temp_tables in places::storage - Rust
pub fn delete_pending_temp_tables(conn: &PlacesDb) -> Result<()>
Expand description

Delete all items in the temp tables we use for staging changes.

+
\ No newline at end of file diff --git a/book/rust-docs/places/storage/fn.fetch_page_info.html b/book/rust-docs/places/storage/fn.fetch_page_info.html new file mode 100644 index 0000000000..14f54276f4 --- /dev/null +++ b/book/rust-docs/places/storage/fn.fetch_page_info.html @@ -0,0 +1,4 @@ +fetch_page_info in places::storage - Rust
pub fn fetch_page_info(
+    db: &PlacesDb,
+    url: &Url
+) -> Result<Option<FetchedPageInfo>>
\ No newline at end of file diff --git a/book/rust-docs/places/storage/fn.run_maintenance_checkpoint.html b/book/rust-docs/places/storage/fn.run_maintenance_checkpoint.html new file mode 100644 index 0000000000..4c10b0c390 --- /dev/null +++ b/book/rust-docs/places/storage/fn.run_maintenance_checkpoint.html @@ -0,0 +1,6 @@ +run_maintenance_checkpoint in places::storage - Rust
pub fn run_maintenance_checkpoint(conn: &PlacesDb) -> Result<()>
Expand description

Run maintenance on the places DB (checkpoint step)

+

The run_maintenance_*() functions are intended to be run during idle time and will take steps +to clean up / shrink the database. They’re split up so that we can time each one in the +Kotlin wrapper code (This is needed because we only have access to the Glean API in Kotlin and +it supports a stop-watch style API, not recording specific values).

+
\ No newline at end of file diff --git a/book/rust-docs/places/storage/fn.run_maintenance_optimize.html b/book/rust-docs/places/storage/fn.run_maintenance_optimize.html new file mode 100644 index 0000000000..0991a8cb28 --- /dev/null +++ b/book/rust-docs/places/storage/fn.run_maintenance_optimize.html @@ -0,0 +1,6 @@ +run_maintenance_optimize in places::storage - Rust
pub fn run_maintenance_optimize(conn: &PlacesDb) -> Result<()>
Expand description

Run maintenance on the places DB (optimize step)

+

The run_maintenance_*() functions are intended to be run during idle time and will take steps +to clean up / shrink the database. They’re split up so that we can time each one in the +Kotlin wrapper code (This is needed because we only have access to the Glean API in Kotlin and +it supports a stop-watch style API, not recording specific values).

+
\ No newline at end of file diff --git a/book/rust-docs/places/storage/fn.run_maintenance_prune.html b/book/rust-docs/places/storage/fn.run_maintenance_prune.html new file mode 100644 index 0000000000..8c73f003de --- /dev/null +++ b/book/rust-docs/places/storage/fn.run_maintenance_prune.html @@ -0,0 +1,11 @@ +run_maintenance_prune in places::storage - Rust
pub fn run_maintenance_prune(
+    conn: &PlacesDb,
+    db_size_limit: u32
+) -> Result<RunMaintenanceMetrics>
Expand description

Run maintenance on the places DB (prune step)

+

The run_maintenance_*() functions are intended to be run during idle time and will take steps +to clean up / shrink the database. They’re split up so that we can time each one in the +Kotlin wrapper code (This is needed because we only have access to the Glean API in Kotlin and +it supports a stop-watch style API, not recording specific values).

+

db_size_limit is the approximate storage limit in bytes. If the database is using more space +than this, some older visits will be deleted to free up space. Pass in a 0 to skip this.

+
\ No newline at end of file diff --git a/book/rust-docs/places/storage/fn.run_maintenance_vacuum.html b/book/rust-docs/places/storage/fn.run_maintenance_vacuum.html new file mode 100644 index 0000000000..97e0e74d34 --- /dev/null +++ b/book/rust-docs/places/storage/fn.run_maintenance_vacuum.html @@ -0,0 +1,6 @@ +run_maintenance_vacuum in places::storage - Rust
pub fn run_maintenance_vacuum(conn: &PlacesDb) -> Result<()>
Expand description

Run maintenance on the places DB (vacuum step)

+

The run_maintenance_*() functions are intended to be run during idle time and will take steps +to clean up / shrink the database. They’re split up so that we can time each one in the +Kotlin wrapper code (This is needed because we only have access to the Glean API in Kotlin and +it supports a stop-watch style API, not recording specific values).

+
\ No newline at end of file diff --git a/book/rust-docs/places/storage/fn.update_all_frecencies_at_once.html b/book/rust-docs/places/storage/fn.update_all_frecencies_at_once.html new file mode 100644 index 0000000000..e95f7489ee --- /dev/null +++ b/book/rust-docs/places/storage/fn.update_all_frecencies_at_once.html @@ -0,0 +1,4 @@ +update_all_frecencies_at_once in places::storage - Rust
pub fn update_all_frecencies_at_once(
+    db: &PlacesDb,
+    scope: &SqlInterruptScope
+) -> Result<()>
\ No newline at end of file diff --git a/book/rust-docs/places/storage/history/fn.apply_observation.html b/book/rust-docs/places/storage/history/fn.apply_observation.html new file mode 100644 index 0000000000..b9815cfe58 --- /dev/null +++ b/book/rust-docs/places/storage/history/fn.apply_observation.html @@ -0,0 +1,5 @@ +apply_observation in places::storage::history - Rust
pub fn apply_observation(
+    db: &PlacesDb,
+    visit_ob: VisitObservation
+) -> Result<Option<RowId>>
Expand description

Returns the RowId of a new visit in moz_historyvisits, or None if no new visit was added.

+
\ No newline at end of file diff --git a/book/rust-docs/places/storage/history/fn.apply_observation_direct.html b/book/rust-docs/places/storage/history/fn.apply_observation_direct.html new file mode 100644 index 0000000000..caef6924e5 --- /dev/null +++ b/book/rust-docs/places/storage/history/fn.apply_observation_direct.html @@ -0,0 +1,5 @@ +apply_observation_direct in places::storage::history - Rust
pub fn apply_observation_direct(
+    db: &PlacesDb,
+    visit_ob: VisitObservation
+) -> Result<Option<RowId>>
Expand description

Returns the RowId of a new visit in moz_historyvisits, or None if no new visit was added.

+
\ No newline at end of file diff --git a/book/rust-docs/places/storage/history/fn.delete_everything.html b/book/rust-docs/places/storage/history/fn.delete_everything.html new file mode 100644 index 0000000000..faa6343507 --- /dev/null +++ b/book/rust-docs/places/storage/history/fn.delete_everything.html @@ -0,0 +1 @@ +delete_everything in places::storage::history - Rust
pub fn delete_everything(db: &PlacesDb) -> Result<()>
\ No newline at end of file diff --git a/book/rust-docs/places/storage/history/fn.delete_place_visit_at_time.html b/book/rust-docs/places/storage/history/fn.delete_place_visit_at_time.html new file mode 100644 index 0000000000..58233e130c --- /dev/null +++ b/book/rust-docs/places/storage/history/fn.delete_place_visit_at_time.html @@ -0,0 +1,5 @@ +delete_place_visit_at_time in places::storage::history - Rust
pub fn delete_place_visit_at_time(
+    db: &PlacesDb,
+    place: &Url,
+    visit: Timestamp
+) -> Result<()>
\ No newline at end of file diff --git a/book/rust-docs/places/storage/history/fn.delete_place_visit_at_time_by_href.html b/book/rust-docs/places/storage/history/fn.delete_place_visit_at_time_by_href.html new file mode 100644 index 0000000000..868210836a --- /dev/null +++ b/book/rust-docs/places/storage/history/fn.delete_place_visit_at_time_by_href.html @@ -0,0 +1,5 @@ +delete_place_visit_at_time_by_href in places::storage::history - Rust
pub fn delete_place_visit_at_time_by_href(
+    db: &PlacesDb,
+    place: &str,
+    visit: Timestamp
+) -> Result<()>
\ No newline at end of file diff --git a/book/rust-docs/places/storage/history/fn.delete_visits_between.html b/book/rust-docs/places/storage/history/fn.delete_visits_between.html new file mode 100644 index 0000000000..d293c42205 --- /dev/null +++ b/book/rust-docs/places/storage/history/fn.delete_visits_between.html @@ -0,0 +1,6 @@ +delete_visits_between in places::storage::history - Rust
pub fn delete_visits_between(
+    db: &PlacesDb,
+    start: Timestamp,
+    end: Timestamp
+) -> Result<()>
Expand description

Delete all visits in a date range.

+
\ No newline at end of file diff --git a/book/rust-docs/places/storage/history/fn.delete_visits_between_in_tx.html b/book/rust-docs/places/storage/history/fn.delete_visits_between_in_tx.html new file mode 100644 index 0000000000..924329bc7d --- /dev/null +++ b/book/rust-docs/places/storage/history/fn.delete_visits_between_in_tx.html @@ -0,0 +1,5 @@ +delete_visits_between_in_tx in places::storage::history - Rust
pub fn delete_visits_between_in_tx(
+    db: &PlacesDb,
+    start: Timestamp,
+    end: Timestamp
+) -> Result<()>
\ No newline at end of file diff --git a/book/rust-docs/places/storage/history/fn.delete_visits_for.html b/book/rust-docs/places/storage/history/fn.delete_visits_for.html new file mode 100644 index 0000000000..adc37d9af3 --- /dev/null +++ b/book/rust-docs/places/storage/history/fn.delete_visits_for.html @@ -0,0 +1,3 @@ +delete_visits_for in places::storage::history - Rust
pub fn delete_visits_for(db: &PlacesDb, guid: &SyncGuid) -> Result<()>
Expand description

Deletes all visits for a page given its GUID, creating tombstones if +necessary.

+
\ No newline at end of file diff --git a/book/rust-docs/places/storage/history/fn.frecency_stale_at.html b/book/rust-docs/places/storage/history/fn.frecency_stale_at.html new file mode 100644 index 0000000000..61fc095b94 --- /dev/null +++ b/book/rust-docs/places/storage/history/fn.frecency_stale_at.html @@ -0,0 +1,2 @@ +frecency_stale_at in places::storage::history - Rust
pub fn frecency_stale_at(db: &PlacesDb, url: &Url) -> Result<Option<Timestamp>>
Expand description

Indicates if and when a URL’s frecency was marked as stale.

+
\ No newline at end of file diff --git a/book/rust-docs/places/storage/history/fn.get_top_frecent_site_infos.html b/book/rust-docs/places/storage/history/fn.get_top_frecent_site_infos.html new file mode 100644 index 0000000000..be89d69199 --- /dev/null +++ b/book/rust-docs/places/storage/history/fn.get_top_frecent_site_infos.html @@ -0,0 +1,5 @@ +get_top_frecent_site_infos in places::storage::history - Rust
pub fn get_top_frecent_site_infos(
+    db: &PlacesDb,
+    num_items: i32,
+    frecency_threshold: i64
+) -> Result<Vec<TopFrecentSiteInfo>>
\ No newline at end of file diff --git a/book/rust-docs/places/storage/history/fn.get_visit_count.html b/book/rust-docs/places/storage/history/fn.get_visit_count.html new file mode 100644 index 0000000000..48efa0aa75 --- /dev/null +++ b/book/rust-docs/places/storage/history/fn.get_visit_count.html @@ -0,0 +1,4 @@ +get_visit_count in places::storage::history - Rust
pub fn get_visit_count(
+    db: &PlacesDb,
+    exclude_types: VisitTransitionSet
+) -> Result<i64>
\ No newline at end of file diff --git a/book/rust-docs/places/storage/history/fn.get_visit_infos.html b/book/rust-docs/places/storage/history/fn.get_visit_infos.html new file mode 100644 index 0000000000..d7e8a2d1da --- /dev/null +++ b/book/rust-docs/places/storage/history/fn.get_visit_infos.html @@ -0,0 +1,6 @@ +get_visit_infos in places::storage::history - Rust
pub fn get_visit_infos(
+    db: &PlacesDb,
+    start: Timestamp,
+    end: Timestamp,
+    exclude_types: VisitTransitionSet
+) -> Result<Vec<HistoryVisitInfo>>
\ No newline at end of file diff --git a/book/rust-docs/places/storage/history/fn.get_visit_page.html b/book/rust-docs/places/storage/history/fn.get_visit_page.html new file mode 100644 index 0000000000..a7567d5afe --- /dev/null +++ b/book/rust-docs/places/storage/history/fn.get_visit_page.html @@ -0,0 +1,6 @@ +get_visit_page in places::storage::history - Rust
pub fn get_visit_page(
+    db: &PlacesDb,
+    offset: i64,
+    count: i64,
+    exclude_types: VisitTransitionSet
+) -> Result<Vec<HistoryVisitInfo>>
\ No newline at end of file diff --git a/book/rust-docs/places/storage/history/fn.get_visit_page_with_bound.html b/book/rust-docs/places/storage/history/fn.get_visit_page_with_bound.html new file mode 100644 index 0000000000..c9af114c86 --- /dev/null +++ b/book/rust-docs/places/storage/history/fn.get_visit_page_with_bound.html @@ -0,0 +1,7 @@ +get_visit_page_with_bound in places::storage::history - Rust
pub fn get_visit_page_with_bound(
+    db: &PlacesDb,
+    bound: i64,
+    offset: i64,
+    count: i64,
+    exclude_types: VisitTransitionSet
+) -> Result<HistoryVisitInfosWithBound>
\ No newline at end of file diff --git a/book/rust-docs/places/storage/history/fn.get_visited.html b/book/rust-docs/places/storage/history/fn.get_visited.html new file mode 100644 index 0000000000..6eda732aad --- /dev/null +++ b/book/rust-docs/places/storage/history/fn.get_visited.html @@ -0,0 +1,3 @@ +get_visited in places::storage::history - Rust
pub fn get_visited<I>(db: &PlacesDb, urls: I) -> Result<Vec<bool>>where
+    I: IntoIterator<Item = Url>,
+    I::IntoIter: ExactSizeIterator,
\ No newline at end of file diff --git a/book/rust-docs/places/storage/history/fn.get_visited_into.html b/book/rust-docs/places/storage/history/fn.get_visited_into.html new file mode 100644 index 0000000000..62cc9174f1 --- /dev/null +++ b/book/rust-docs/places/storage/history/fn.get_visited_into.html @@ -0,0 +1,10 @@ +get_visited_into in places::storage::history - Rust
pub fn get_visited_into(
+    db: &PlacesDb,
+    urls_idxs: &[(usize, Url)],
+    result: &mut [bool]
+) -> Result<()>
Expand description

Low level api used to implement both get_visited and the FFI get_visited call. +Takes a slice where we should output the results, as well as a slice of +index/url pairs.

+

This is done so that the FFI can more easily support returning +false when asked if it’s visited an invalid URL.

+
\ No newline at end of file diff --git a/book/rust-docs/places/storage/history/fn.get_visited_urls.html b/book/rust-docs/places/storage/history/fn.get_visited_urls.html new file mode 100644 index 0000000000..955ce36d47 --- /dev/null +++ b/book/rust-docs/places/storage/history/fn.get_visited_urls.html @@ -0,0 +1,8 @@ +get_visited_urls in places::storage::history - Rust
pub fn get_visited_urls(
+    db: &PlacesDb,
+    start: Timestamp,
+    end: Timestamp,
+    include_remote: bool
+) -> Result<Vec<String>>
Expand description

Get the set of urls that were visited between start and end. Only considers local visits +unless you pass in include_remote.

+
\ No newline at end of file diff --git a/book/rust-docs/places/storage/history/fn.href_to_guid.html b/book/rust-docs/places/storage/history/fn.href_to_guid.html new file mode 100644 index 0000000000..4e5eb95e2c --- /dev/null +++ b/book/rust-docs/places/storage/history/fn.href_to_guid.html @@ -0,0 +1,2 @@ +href_to_guid in places::storage::history - Rust
pub fn href_to_guid(db: &PlacesDb, url: &str) -> Result<Option<SyncGuid>>
Expand description

Returns the GUID for the specified Url String, or None if it doesn’t exist.

+
\ No newline at end of file diff --git a/book/rust-docs/places/storage/history/fn.prune_older_visits.html b/book/rust-docs/places/storage/history/fn.prune_older_visits.html new file mode 100644 index 0000000000..edde51c6f0 --- /dev/null +++ b/book/rust-docs/places/storage/history/fn.prune_older_visits.html @@ -0,0 +1 @@ +prune_older_visits in places::storage::history - Rust
pub fn prune_older_visits(db: &PlacesDb) -> Result<()>
\ No newline at end of file diff --git a/book/rust-docs/places/storage/history/fn.update_frecency.html b/book/rust-docs/places/storage/history/fn.update_frecency.html new file mode 100644 index 0000000000..16ec06669b --- /dev/null +++ b/book/rust-docs/places/storage/history/fn.update_frecency.html @@ -0,0 +1,5 @@ +update_frecency in places::storage::history - Rust
pub fn update_frecency(
+    db: &PlacesDb,
+    id: RowId,
+    redirect_boost: Option<bool>
+) -> Result<()>
\ No newline at end of file diff --git a/book/rust-docs/places/storage/history/fn.url_to_guid.html b/book/rust-docs/places/storage/history/fn.url_to_guid.html new file mode 100644 index 0000000000..0306135497 --- /dev/null +++ b/book/rust-docs/places/storage/history/fn.url_to_guid.html @@ -0,0 +1,2 @@ +url_to_guid in places::storage::history - Rust
pub fn url_to_guid(db: &PlacesDb, url: &Url) -> Result<Option<SyncGuid>>
Expand description

Returns the GUID for the specified Url, or None if it doesn’t exist.

+
\ No newline at end of file diff --git a/book/rust-docs/places/storage/history/history_sync/fn.apply_synced_deletion.html b/book/rust-docs/places/storage/history/history_sync/fn.apply_synced_deletion.html new file mode 100644 index 0000000000..425f692b29 --- /dev/null +++ b/book/rust-docs/places/storage/history/history_sync/fn.apply_synced_deletion.html @@ -0,0 +1 @@ +apply_synced_deletion in places::storage::history::history_sync - Rust
pub fn apply_synced_deletion(db: &PlacesDb, guid: &SyncGuid) -> Result<()>
\ No newline at end of file diff --git a/book/rust-docs/places/storage/history/history_sync/fn.apply_synced_reconciliation.html b/book/rust-docs/places/storage/history/history_sync/fn.apply_synced_reconciliation.html new file mode 100644 index 0000000000..53b59d0446 --- /dev/null +++ b/book/rust-docs/places/storage/history/history_sync/fn.apply_synced_reconciliation.html @@ -0,0 +1 @@ +apply_synced_reconciliation in places::storage::history::history_sync - Rust
pub fn apply_synced_reconciliation(db: &PlacesDb, guid: &SyncGuid) -> Result<()>
\ No newline at end of file diff --git a/book/rust-docs/places/storage/history/history_sync/fn.apply_synced_visits.html b/book/rust-docs/places/storage/history/history_sync/fn.apply_synced_visits.html new file mode 100644 index 0000000000..273c8aac7d --- /dev/null +++ b/book/rust-docs/places/storage/history/history_sync/fn.apply_synced_visits.html @@ -0,0 +1,10 @@ +apply_synced_visits in places::storage::history::history_sync - Rust
pub fn apply_synced_visits(
+    db: &PlacesDb,
+    incoming_guid: &SyncGuid,
+    url: &Url,
+    title: &Option<String>,
+    visits: &[HistoryRecordVisit],
+    unknown_fields: &UnknownFields
+) -> Result<()>
Expand description

Apply history visit from sync. This assumes they have all been +validated, deduped, etc - it’s just the storage we do here.

+
\ No newline at end of file diff --git a/book/rust-docs/places/storage/history/history_sync/fn.fetch_outgoing.html b/book/rust-docs/places/storage/history/history_sync/fn.fetch_outgoing.html new file mode 100644 index 0000000000..468fca3ab6 --- /dev/null +++ b/book/rust-docs/places/storage/history/history_sync/fn.fetch_outgoing.html @@ -0,0 +1,5 @@ +fetch_outgoing in places::storage::history::history_sync - Rust
pub fn fetch_outgoing(
+    db: &PlacesDb,
+    max_places: usize,
+    max_visits: usize
+) -> Result<Vec<OutgoingBso>>
\ No newline at end of file diff --git a/book/rust-docs/places/storage/history/history_sync/fn.fetch_visits.html b/book/rust-docs/places/storage/history/history_sync/fn.fetch_visits.html new file mode 100644 index 0000000000..bae9cd889b --- /dev/null +++ b/book/rust-docs/places/storage/history/history_sync/fn.fetch_visits.html @@ -0,0 +1,5 @@ +fetch_visits in places::storage::history::history_sync - Rust
pub fn fetch_visits(
+    db: &PlacesDb,
+    url: &Url,
+    limit: usize
+) -> Result<Option<(FetchedVisitPage, Vec<FetchedVisit>)>>
\ No newline at end of file diff --git a/book/rust-docs/places/storage/history/history_sync/fn.finish_outgoing.html b/book/rust-docs/places/storage/history/history_sync/fn.finish_outgoing.html new file mode 100644 index 0000000000..5ca7ee87f1 --- /dev/null +++ b/book/rust-docs/places/storage/history/history_sync/fn.finish_outgoing.html @@ -0,0 +1 @@ +finish_outgoing in places::storage::history::history_sync - Rust
pub fn finish_outgoing(db: &PlacesDb) -> Result<()>
\ No newline at end of file diff --git a/book/rust-docs/places/storage/history/history_sync/index.html b/book/rust-docs/places/storage/history/history_sync/index.html new file mode 100644 index 0000000000..597d5079e8 --- /dev/null +++ b/book/rust-docs/places/storage/history/history_sync/index.html @@ -0,0 +1,2 @@ +places::storage::history::history_sync - Rust

Structs

Functions

\ No newline at end of file diff --git a/book/rust-docs/places/storage/history/history_sync/sidebar-items.js b/book/rust-docs/places/storage/history/history_sync/sidebar-items.js new file mode 100644 index 0000000000..e1a358694f --- /dev/null +++ b/book/rust-docs/places/storage/history/history_sync/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["apply_synced_deletion","apply_synced_reconciliation","apply_synced_visits","fetch_outgoing","fetch_visits","finish_outgoing"],"struct":["FetchedVisit","FetchedVisitPage"]}; \ No newline at end of file diff --git a/book/rust-docs/places/storage/history/history_sync/struct.FetchedVisit.html b/book/rust-docs/places/storage/history/history_sync/struct.FetchedVisit.html new file mode 100644 index 0000000000..a232cb30ea --- /dev/null +++ b/book/rust-docs/places/storage/history/history_sync/struct.FetchedVisit.html @@ -0,0 +1,19 @@ +FetchedVisit in places::storage::history::history_sync - Rust
pub struct FetchedVisit {
+    pub is_local: bool,
+    pub visit_date: Timestamp,
+    pub visit_type: Option<VisitType>,
+}

Fields§

§is_local: bool§visit_date: Timestamp§visit_type: Option<VisitType>

Implementations§

source§

impl FetchedVisit

source

pub fn from_row(row: &Row<'_>) -> Result<Self>

Trait Implementations§

source§

impl Clone for FetchedVisit

source§

fn clone(&self) -> FetchedVisit

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for FetchedVisit

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl PartialEq<FetchedVisit> for FetchedVisit

source§

fn eq(&self, other: &FetchedVisit) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Eq for FetchedVisit

source§

impl StructuralEq for FetchedVisit

source§

impl StructuralPartialEq for FetchedVisit

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/storage/history/history_sync/struct.FetchedVisitPage.html b/book/rust-docs/places/storage/history/history_sync/struct.FetchedVisitPage.html new file mode 100644 index 0000000000..cc26c23a6b --- /dev/null +++ b/book/rust-docs/places/storage/history/history_sync/struct.FetchedVisitPage.html @@ -0,0 +1,18 @@ +FetchedVisitPage in places::storage::history::history_sync - Rust
pub struct FetchedVisitPage {
+    pub url: Url,
+    pub guid: SyncGuid,
+    pub row_id: RowId,
+    pub title: String,
+    pub unknown_fields: UnknownFields,
+}

Fields§

§url: Url§guid: SyncGuid§row_id: RowId§title: String§unknown_fields: UnknownFields

Implementations§

source§

impl FetchedVisitPage

source

pub fn from_row(row: &Row<'_>) -> Result<Self>

Trait Implementations§

source§

impl Debug for FetchedVisitPage

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/storage/history/index.html b/book/rust-docs/places/storage/history/index.html new file mode 100644 index 0000000000..a5bf90d824 --- /dev/null +++ b/book/rust-docs/places/storage/history/index.html @@ -0,0 +1,5 @@ +places::storage::history - Rust

Module places::storage::history

source ·

Modules

Functions

\ No newline at end of file diff --git a/book/rust-docs/places/storage/history/sidebar-items.js b/book/rust-docs/places/storage/history/sidebar-items.js new file mode 100644 index 0000000000..ac94052798 --- /dev/null +++ b/book/rust-docs/places/storage/history/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["apply_observation","apply_observation_direct","delete_everything","delete_place_visit_at_time","delete_place_visit_at_time_by_href","delete_visits_between","delete_visits_between_in_tx","delete_visits_for","frecency_stale_at","get_top_frecent_site_infos","get_visit_count","get_visit_infos","get_visit_page","get_visit_page_with_bound","get_visited","get_visited_into","get_visited_urls","href_to_guid","prune_older_visits","update_frecency","url_to_guid"],"mod":["history_sync"]}; \ No newline at end of file diff --git a/book/rust-docs/places/storage/history_metadata/enum.DocumentType.html b/book/rust-docs/places/storage/history_metadata/enum.DocumentType.html new file mode 100644 index 0000000000..031f6609a3 --- /dev/null +++ b/book/rust-docs/places/storage/history_metadata/enum.DocumentType.html @@ -0,0 +1,18 @@ +DocumentType in places::storage::history_metadata - Rust
pub enum DocumentType {
+    Regular,
+    Media,
+}

Variants§

§

Regular

§

Media

Trait Implementations§

source§

impl Clone for DocumentType

source§

fn clone(&self) -> DocumentType

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for DocumentType

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl FromSql for DocumentType

source§

fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self>

Converts SQLite value into Rust value.
source§

impl PartialEq<DocumentType> for DocumentType

source§

fn eq(&self, other: &DocumentType) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl ToSql for DocumentType

source§

fn to_sql(&self) -> Result<ToSqlOutput<'_>>

Converts Rust value to SQLite value
source§

impl Copy for DocumentType

source§

impl Eq for DocumentType

source§

impl StructuralEq for DocumentType

source§

impl StructuralPartialEq for DocumentType

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/storage/history_metadata/fn.apply_metadata_observation.html b/book/rust-docs/places/storage/history_metadata/fn.apply_metadata_observation.html new file mode 100644 index 0000000000..64a6692ba0 --- /dev/null +++ b/book/rust-docs/places/storage/history_metadata/fn.apply_metadata_observation.html @@ -0,0 +1,4 @@ +apply_metadata_observation in places::storage::history_metadata - Rust
pub fn apply_metadata_observation(
+    db: &PlacesDb,
+    observation: HistoryMetadataObservation
+) -> Result<()>
\ No newline at end of file diff --git a/book/rust-docs/places/storage/history_metadata/fn.delete_all_metadata_for_page.html b/book/rust-docs/places/storage/history_metadata/fn.delete_all_metadata_for_page.html new file mode 100644 index 0000000000..894757daba --- /dev/null +++ b/book/rust-docs/places/storage/history_metadata/fn.delete_all_metadata_for_page.html @@ -0,0 +1,5 @@ +delete_all_metadata_for_page in places::storage::history_metadata - Rust
pub fn delete_all_metadata_for_page(
+    db: &PlacesDb,
+    place_id: RowId
+) -> Result<()>
Expand description

Delete all metadata for the specified place id.

+
\ No newline at end of file diff --git a/book/rust-docs/places/storage/history_metadata/fn.delete_between.html b/book/rust-docs/places/storage/history_metadata/fn.delete_between.html new file mode 100644 index 0000000000..33e94c4d05 --- /dev/null +++ b/book/rust-docs/places/storage/history_metadata/fn.delete_between.html @@ -0,0 +1 @@ +delete_between in places::storage::history_metadata - Rust
pub fn delete_between(db: &PlacesDb, start: i64, end: i64) -> Result<()>
\ No newline at end of file diff --git a/book/rust-docs/places/storage/history_metadata/fn.delete_metadata.html b/book/rust-docs/places/storage/history_metadata/fn.delete_metadata.html new file mode 100644 index 0000000000..5ed01d1ec4 --- /dev/null +++ b/book/rust-docs/places/storage/history_metadata/fn.delete_metadata.html @@ -0,0 +1,6 @@ +delete_metadata in places::storage::history_metadata - Rust
pub fn delete_metadata(
+    db: &PlacesDb,
+    url: &Url,
+    referrer_url: Option<&Url>,
+    search_term: Option<&str>
+) -> Result<()>
\ No newline at end of file diff --git a/book/rust-docs/places/storage/history_metadata/fn.delete_older_than.html b/book/rust-docs/places/storage/history_metadata/fn.delete_older_than.html new file mode 100644 index 0000000000..02d3736aad --- /dev/null +++ b/book/rust-docs/places/storage/history_metadata/fn.delete_older_than.html @@ -0,0 +1 @@ +delete_older_than in places::storage::history_metadata - Rust
pub fn delete_older_than(db: &PlacesDb, older_than: i64) -> Result<()>
\ No newline at end of file diff --git a/book/rust-docs/places/storage/history_metadata/fn.get_between.html b/book/rust-docs/places/storage/history_metadata/fn.get_between.html new file mode 100644 index 0000000000..c35f45a0f1 --- /dev/null +++ b/book/rust-docs/places/storage/history_metadata/fn.get_between.html @@ -0,0 +1,5 @@ +get_between in places::storage::history_metadata - Rust
pub fn get_between(
+    db: &PlacesDb,
+    start: i64,
+    end: i64
+) -> Result<Vec<HistoryMetadata>>
\ No newline at end of file diff --git a/book/rust-docs/places/storage/history_metadata/fn.get_highlights.html b/book/rust-docs/places/storage/history_metadata/fn.get_highlights.html new file mode 100644 index 0000000000..6ab6988951 --- /dev/null +++ b/book/rust-docs/places/storage/history_metadata/fn.get_highlights.html @@ -0,0 +1,5 @@ +get_highlights in places::storage::history_metadata - Rust
pub fn get_highlights(
+    db: &PlacesDb,
+    weights: HistoryHighlightWeights,
+    limit: i32
+) -> Result<Vec<HistoryHighlight>>
\ No newline at end of file diff --git a/book/rust-docs/places/storage/history_metadata/fn.get_latest_for_url.html b/book/rust-docs/places/storage/history_metadata/fn.get_latest_for_url.html new file mode 100644 index 0000000000..ddd448ea04 --- /dev/null +++ b/book/rust-docs/places/storage/history_metadata/fn.get_latest_for_url.html @@ -0,0 +1,4 @@ +get_latest_for_url in places::storage::history_metadata - Rust
pub fn get_latest_for_url(
+    db: &PlacesDb,
+    url: &Url
+) -> Result<Option<HistoryMetadata>>
\ No newline at end of file diff --git a/book/rust-docs/places/storage/history_metadata/fn.get_since.html b/book/rust-docs/places/storage/history_metadata/fn.get_since.html new file mode 100644 index 0000000000..13ab742275 --- /dev/null +++ b/book/rust-docs/places/storage/history_metadata/fn.get_since.html @@ -0,0 +1 @@ +get_since in places::storage::history_metadata - Rust
pub fn get_since(db: &PlacesDb, start: i64) -> Result<Vec<HistoryMetadata>>
\ No newline at end of file diff --git a/book/rust-docs/places/storage/history_metadata/fn.query.html b/book/rust-docs/places/storage/history_metadata/fn.query.html new file mode 100644 index 0000000000..426bf67216 --- /dev/null +++ b/book/rust-docs/places/storage/history_metadata/fn.query.html @@ -0,0 +1,5 @@ +query in places::storage::history_metadata - Rust
pub fn query(
+    db: &PlacesDb,
+    query: &str,
+    limit: i32
+) -> Result<Vec<HistoryMetadata>>
\ No newline at end of file diff --git a/book/rust-docs/places/storage/history_metadata/index.html b/book/rust-docs/places/storage/history_metadata/index.html new file mode 100644 index 0000000000..6ec939a872 --- /dev/null +++ b/book/rust-docs/places/storage/history_metadata/index.html @@ -0,0 +1 @@ +places::storage::history_metadata - Rust
\ No newline at end of file diff --git a/book/rust-docs/places/storage/history_metadata/sidebar-items.js b/book/rust-docs/places/storage/history_metadata/sidebar-items.js new file mode 100644 index 0000000000..e1af55bee7 --- /dev/null +++ b/book/rust-docs/places/storage/history_metadata/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["DocumentType"],"fn":["apply_metadata_observation","delete_all_metadata_for_page","delete_between","delete_metadata","delete_older_than","get_between","get_highlights","get_latest_for_url","get_since","query"],"struct":["HistoryHighlight","HistoryHighlightWeights","HistoryMetadata","HistoryMetadataObservation"]}; \ No newline at end of file diff --git a/book/rust-docs/places/storage/history_metadata/struct.HistoryHighlight.html b/book/rust-docs/places/storage/history_metadata/struct.HistoryHighlight.html new file mode 100644 index 0000000000..4c87041933 --- /dev/null +++ b/book/rust-docs/places/storage/history_metadata/struct.HistoryHighlight.html @@ -0,0 +1,19 @@ +HistoryHighlight in places::storage::history_metadata - Rust
pub struct HistoryHighlight {
+    pub score: f64,
+    pub place_id: i32,
+    pub url: String,
+    pub title: Option<String>,
+    pub preview_image_url: Option<String>,
+}

Fields§

§score: f64§place_id: i32§url: String§title: Option<String>§preview_image_url: Option<String>

Trait Implementations§

source§

impl Clone for HistoryHighlight

source§

fn clone(&self) -> HistoryHighlight

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/storage/history_metadata/struct.HistoryHighlightWeights.html b/book/rust-docs/places/storage/history_metadata/struct.HistoryHighlightWeights.html new file mode 100644 index 0000000000..ab570946c7 --- /dev/null +++ b/book/rust-docs/places/storage/history_metadata/struct.HistoryHighlightWeights.html @@ -0,0 +1,16 @@ +HistoryHighlightWeights in places::storage::history_metadata - Rust
pub struct HistoryHighlightWeights {
+    pub view_time: f64,
+    pub frequency: f64,
+}

Fields§

§view_time: f64§frequency: f64

Trait Implementations§

source§

impl Clone for HistoryHighlightWeights

source§

fn clone(&self) -> HistoryHighlightWeights

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/storage/history_metadata/struct.HistoryMetadata.html b/book/rust-docs/places/storage/history_metadata/struct.HistoryMetadata.html new file mode 100644 index 0000000000..5a256893f3 --- /dev/null +++ b/book/rust-docs/places/storage/history_metadata/struct.HistoryMetadata.html @@ -0,0 +1,25 @@ +HistoryMetadata in places::storage::history_metadata - Rust
pub struct HistoryMetadata {
+    pub url: String,
+    pub title: Option<String>,
+    pub preview_image_url: Option<String>,
+    pub created_at: i64,
+    pub updated_at: i64,
+    pub total_view_time: i32,
+    pub search_term: Option<String>,
+    pub document_type: DocumentType,
+    pub referrer_url: Option<String>,
+}

Fields§

§url: String§title: Option<String>§preview_image_url: Option<String>§created_at: i64§updated_at: i64§total_view_time: i32§search_term: Option<String>§document_type: DocumentType§referrer_url: Option<String>

Trait Implementations§

source§

impl Clone for HistoryMetadata

source§

fn clone(&self) -> HistoryMetadata

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for HistoryMetadata

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl PartialEq<HistoryMetadata> for HistoryMetadata

source§

fn eq(&self, other: &HistoryMetadata) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Eq for HistoryMetadata

source§

impl StructuralEq for HistoryMetadata

source§

impl StructuralPartialEq for HistoryMetadata

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/storage/history_metadata/struct.HistoryMetadataObservation.html b/book/rust-docs/places/storage/history_metadata/struct.HistoryMetadataObservation.html new file mode 100644 index 0000000000..7cda2f7dd5 --- /dev/null +++ b/book/rust-docs/places/storage/history_metadata/struct.HistoryMetadataObservation.html @@ -0,0 +1,22 @@ +HistoryMetadataObservation in places::storage::history_metadata - Rust
pub struct HistoryMetadataObservation {
+    pub url: String,
+    pub view_time: Option<i32>,
+    pub search_term: Option<String>,
+    pub document_type: Option<DocumentType>,
+    pub referrer_url: Option<String>,
+    pub title: Option<String>,
+}

Fields§

§url: String§view_time: Option<i32>§search_term: Option<String>§document_type: Option<DocumentType>§referrer_url: Option<String>§title: Option<String>

Trait Implementations§

source§

impl Clone for HistoryMetadataObservation

source§

fn clone(&self) -> HistoryMetadataObservation

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for HistoryMetadataObservation

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl PartialEq<HistoryMetadataObservation> for HistoryMetadataObservation

source§

fn eq(&self, other: &HistoryMetadataObservation) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Eq for HistoryMetadataObservation

source§

impl StructuralEq for HistoryMetadataObservation

source§

impl StructuralPartialEq for HistoryMetadataObservation

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/storage/index.html b/book/rust-docs/places/storage/index.html new file mode 100644 index 0000000000..1a6a2084fa --- /dev/null +++ b/book/rust-docs/places/storage/index.html @@ -0,0 +1 @@ +places::storage - Rust

Module places::storage

source ·

Modules

Structs

Constants

Functions

\ No newline at end of file diff --git a/book/rust-docs/places/storage/sidebar-items.js b/book/rust-docs/places/storage/sidebar-items.js new file mode 100644 index 0000000000..e617a02a63 --- /dev/null +++ b/book/rust-docs/places/storage/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"constant":["TAG_LENGTH_MAX","TITLE_LENGTH_MAX","URL_LENGTH_MAX"],"fn":["delete_pending_temp_tables","fetch_page_info","run_maintenance_checkpoint","run_maintenance_optimize","run_maintenance_prune","run_maintenance_vacuum","update_all_frecencies_at_once"],"mod":["bookmarks","history","history_metadata","tags"],"struct":["FetchedPageInfo","PageInfo","RowId","RunMaintenanceMetrics"]}; \ No newline at end of file diff --git a/book/rust-docs/places/storage/struct.FetchedPageInfo.html b/book/rust-docs/places/storage/struct.FetchedPageInfo.html new file mode 100644 index 0000000000..b7c2d103d8 --- /dev/null +++ b/book/rust-docs/places/storage/struct.FetchedPageInfo.html @@ -0,0 +1,15 @@ +FetchedPageInfo in places::storage - Rust
pub struct FetchedPageInfo {
+    pub page: PageInfo,
+    pub last_visit_id: Option<RowId>,
+}

Fields§

§page: PageInfo§last_visit_id: Option<RowId>

Implementations§

source§

impl FetchedPageInfo

source

pub fn from_row(row: &Row<'_>) -> Result<Self>

Trait Implementations§

source§

impl Debug for FetchedPageInfo

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/storage/struct.PageInfo.html b/book/rust-docs/places/storage/struct.PageInfo.html new file mode 100644 index 0000000000..6c73a83ba7 --- /dev/null +++ b/book/rust-docs/places/storage/struct.PageInfo.html @@ -0,0 +1,28 @@ +PageInfo in places::storage - Rust

Struct places::storage::PageInfo

source ·
pub struct PageInfo {
Show 15 fields + pub url: Url, + pub guid: SyncGuid, + pub row_id: RowId, + pub title: String, + pub hidden: bool, + pub preview_image_url: Option<Url>, + pub typed: u32, + pub frecency: i32, + pub visit_count_local: i32, + pub visit_count_remote: i32, + pub last_visit_date_local: Timestamp, + pub last_visit_date_remote: Timestamp, + pub sync_status: SyncStatus, + pub sync_change_counter: u32, + pub unknown_fields: UnknownFields, +
}

Fields§

§url: Url§guid: SyncGuid§row_id: RowId§title: String§hidden: bool§preview_image_url: Option<Url>§typed: u32§frecency: i32§visit_count_local: i32§visit_count_remote: i32§last_visit_date_local: Timestamp§last_visit_date_remote: Timestamp§sync_status: SyncStatus§sync_change_counter: u32§unknown_fields: UnknownFields

Implementations§

source§

impl PageInfo

source

pub fn from_row(row: &Row<'_>) -> Result<Self>

Trait Implementations§

source§

impl Debug for PageInfo

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/storage/struct.RowId.html b/book/rust-docs/places/storage/struct.RowId.html new file mode 100644 index 0000000000..770679bcb4 --- /dev/null +++ b/book/rust-docs/places/storage/struct.RowId.html @@ -0,0 +1,29 @@ +RowId in places::storage - Rust

Struct places::storage::RowId

source ·
pub struct RowId(pub i64);

Tuple Fields§

§0: i64

Trait Implementations§

source§

impl Clone for RowId

source§

fn clone(&self) -> RowId

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for RowId

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for RowId

source§

fn default() -> RowId

Returns the “default value” for a type. Read more
source§

impl<'de> Deserialize<'de> for RowId

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl Display for RowId

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl From<RowId> for i64

source§

fn from(id: RowId) -> Self

Converts to this type from the input type.
source§

impl FromSql for RowId

source§

fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self>

Converts SQLite value into Rust value.
source§

impl Hash for RowId

source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · source§

fn hash_slice<H>(data: &[Self], state: &mut H)where + H: Hasher, + Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
source§

impl Ord for RowId

source§

fn cmp(&self, other: &RowId) -> Ordering

This method returns an Ordering between self and other. Read more
1.21.0 · source§

fn max(self, other: Self) -> Selfwhere + Self: Sized,

Compares and returns the maximum of two values. Read more
1.21.0 · source§

fn min(self, other: Self) -> Selfwhere + Self: Sized,

Compares and returns the minimum of two values. Read more
1.50.0 · source§

fn clamp(self, min: Self, max: Self) -> Selfwhere + Self: Sized + PartialOrd<Self>,

Restrict a value to a certain interval. Read more
source§

impl PartialEq<RowId> for RowId

source§

fn eq(&self, other: &RowId) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl PartialOrd<RowId> for RowId

source§

fn partial_cmp(&self, other: &RowId) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · source§

fn lt(&self, other: &Rhs) -> bool

This method tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · source§

fn le(&self, other: &Rhs) -> bool

This method tests less than or equal to (for self and other) and is used by the <= +operator. Read more
1.0.0 · source§

fn gt(&self, other: &Rhs) -> bool

This method tests greater than (for self and other) and is used by the > operator. Read more
1.0.0 · source§

fn ge(&self, other: &Rhs) -> bool

This method tests greater than or equal to (for self and other) and is used by the >= +operator. Read more
source§

impl Serialize for RowId

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more
source§

impl ToSql for RowId

source§

fn to_sql(&self) -> RusqliteResult<ToSqlOutput<'_>>

Converts Rust value to SQLite value
source§

impl Copy for RowId

source§

impl Eq for RowId

source§

impl StructuralEq for RowId

source§

impl StructuralPartialEq for RowId

Auto Trait Implementations§

§

impl RefUnwindSafe for RowId

§

impl Send for RowId

§

impl Sync for RowId

§

impl Unpin for RowId

§

impl UnwindSafe for RowId

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> CallHasher for Twhere + T: Hash + ?Sized,

§

fn get_hash<H, B>(value: &H, build_hasher: &B) -> u64where + H: Hash + ?Sized, + B: BuildHasher,

source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/places/storage/struct.RunMaintenanceMetrics.html b/book/rust-docs/places/storage/struct.RunMaintenanceMetrics.html new file mode 100644 index 0000000000..37586800a6 --- /dev/null +++ b/book/rust-docs/places/storage/struct.RunMaintenanceMetrics.html @@ -0,0 +1,16 @@ +RunMaintenanceMetrics in places::storage - Rust
pub struct RunMaintenanceMetrics {
+    pub pruned_visits: bool,
+    pub db_size_before: u32,
+    pub db_size_after: u32,
+}

Fields§

§pruned_visits: bool§db_size_before: u32§db_size_after: u32

Trait Implementations§

source§

impl Debug for RunMaintenanceMetrics

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/storage/tags/enum.ValidatedTag.html b/book/rust-docs/places/storage/tags/enum.ValidatedTag.html new file mode 100644 index 0000000000..21999dd970 --- /dev/null +++ b/book/rust-docs/places/storage/tags/enum.ValidatedTag.html @@ -0,0 +1,28 @@ +ValidatedTag in places::storage::tags - Rust
pub enum ValidatedTag<'a> {
+    Invalid(&'a str),
+    Normalized(&'a str),
+    Original(&'a str),
+}
Expand description

The validity of a tag.

+

Variants§

§

Invalid(&'a str)

The tag is invalid.

+
§

Normalized(&'a str)

The tag is valid, but normalized to remove leading and trailing +whitespace.

+
§

Original(&'a str)

The original tag is valid.

+

Implementations§

source§

impl<'a> ValidatedTag<'a>

source

pub fn is_original(&self) -> bool

Returns true if the original tag is valid; false if it’s invalid or +normalized.

+
source

pub fn ensure_valid(&self) -> Result<&'a str>

Returns the tag string if the tag is valid or normalized, or an error +if the tag is invalid.

+

Trait Implementations§

source§

impl<'a> Clone for ValidatedTag<'a>

source§

fn clone(&self) -> ValidatedTag<'a>

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl<'a> Debug for ValidatedTag<'a>

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl<'a> PartialEq<ValidatedTag<'a>> for ValidatedTag<'a>

source§

fn eq(&self, other: &ValidatedTag<'a>) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl<'a> Copy for ValidatedTag<'a>

source§

impl<'a> Eq for ValidatedTag<'a>

source§

impl<'a> StructuralEq for ValidatedTag<'a>

source§

impl<'a> StructuralPartialEq for ValidatedTag<'a>

Auto Trait Implementations§

§

impl<'a> RefUnwindSafe for ValidatedTag<'a>

§

impl<'a> Send for ValidatedTag<'a>

§

impl<'a> Sync for ValidatedTag<'a>

§

impl<'a> Unpin for ValidatedTag<'a>

§

impl<'a> UnwindSafe for ValidatedTag<'a>

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/storage/tags/fn.get_tags_for_url.html b/book/rust-docs/places/storage/tags/fn.get_tags_for_url.html new file mode 100644 index 0000000000..2f5954a1f4 --- /dev/null +++ b/book/rust-docs/places/storage/tags/fn.get_tags_for_url.html @@ -0,0 +1,16 @@ +get_tags_for_url in places::storage::tags - Rust
pub fn get_tags_for_url(db: &PlacesDb, url: &Url) -> Result<Vec<String>>
Expand description

Retrieves a list of tags for the specified URL.

+

Arguments

+
    +
  • +

    conn - A database connection on which to operate.

    +
  • +
  • +

    url - The URL to query.

    +
  • +
+

Returns

+
    +
  • A Vec with all tags for the URL, sorted by the last modified +date of the tag (latest to oldest)
  • +
+
\ No newline at end of file diff --git a/book/rust-docs/places/storage/tags/fn.get_urls_with_tag.html b/book/rust-docs/places/storage/tags/fn.get_urls_with_tag.html new file mode 100644 index 0000000000..e68d12f7f8 --- /dev/null +++ b/book/rust-docs/places/storage/tags/fn.get_urls_with_tag.html @@ -0,0 +1,16 @@ +get_urls_with_tag in places::storage::tags - Rust
pub fn get_urls_with_tag(db: &PlacesDb, tag: &str) -> Result<Vec<Url>>
Expand description

Retrieves a list of URLs which have the specified tag.

+

Arguments

+
    +
  • +

    conn - A database connection on which to operate.

    +
  • +
  • +

    tag - The tag to query.

    +
  • +
+

Returns

+
    +
  • A Vec with all URLs which have the tag, ordered by the frecency of +the URLs.
  • +
+
\ No newline at end of file diff --git a/book/rust-docs/places/storage/tags/fn.remove_all_tags_from_url.html b/book/rust-docs/places/storage/tags/fn.remove_all_tags_from_url.html new file mode 100644 index 0000000000..68c65dc7a7 --- /dev/null +++ b/book/rust-docs/places/storage/tags/fn.remove_all_tags_from_url.html @@ -0,0 +1,13 @@ +remove_all_tags_from_url in places::storage::tags - Rust
pub fn remove_all_tags_from_url(db: &PlacesDb, url: &Url) -> Result<()>
Expand description

Remove all tags from the specified URL.

+

Arguments

+
    +
  • +

    conn - A database connection on which to operate.

    +
  • +
  • +

    url - The URL for which all tags should be removed.

    +
  • +
+

Returns

+

There is no success return value.

+
\ No newline at end of file diff --git a/book/rust-docs/places/storage/tags/fn.remove_tag.html b/book/rust-docs/places/storage/tags/fn.remove_tag.html new file mode 100644 index 0000000000..e3d70735ca --- /dev/null +++ b/book/rust-docs/places/storage/tags/fn.remove_tag.html @@ -0,0 +1,13 @@ +remove_tag in places::storage::tags - Rust

Function places::storage::tags::remove_tag

source ·
pub fn remove_tag(db: &PlacesDb, tag: &str) -> Result<()>
Expand description

Remove the specified tag from all URLs.

+

Arguments

+
    +
  • +

    conn - A database connection on which to operate.

    +
  • +
  • +

    tag - The tag to remove.

    +
  • +
+

Returns

+

There is no success return value.

+
\ No newline at end of file diff --git a/book/rust-docs/places/storage/tags/fn.tag_url.html b/book/rust-docs/places/storage/tags/fn.tag_url.html new file mode 100644 index 0000000000..407fb42b74 --- /dev/null +++ b/book/rust-docs/places/storage/tags/fn.tag_url.html @@ -0,0 +1,16 @@ +tag_url in places::storage::tags - Rust

Function places::storage::tags::tag_url

source ·
pub fn tag_url(db: &PlacesDb, url: &Url, tag: &str) -> Result<()>
Expand description

Tags the specified URL.

+

Arguments

+
    +
  • +

    conn - A database connection on which to operate.

    +
  • +
  • +

    url - The URL to tag.

    +
  • +
  • +

    tag - The tag to add for the URL.

    +
  • +
+

Returns

+

There is no success return value.

+
\ No newline at end of file diff --git a/book/rust-docs/places/storage/tags/fn.untag_url.html b/book/rust-docs/places/storage/tags/fn.untag_url.html new file mode 100644 index 0000000000..5943be1553 --- /dev/null +++ b/book/rust-docs/places/storage/tags/fn.untag_url.html @@ -0,0 +1,17 @@ +untag_url in places::storage::tags - Rust

Function places::storage::tags::untag_url

source ·
pub fn untag_url(db: &PlacesDb, url: &Url, tag: &str) -> Result<()>
Expand description

Remove the specified tag from the specified URL.

+

Arguments

+
    +
  • +

    conn - A database connection on which to operate.

    +
  • +
  • +

    url - The URL from which the tag should be removed.

    +
  • +
  • +

    tag - The tag to remove from the URL.

    +
  • +
+

Returns

+

There is no success return value - the operation is ignored if the URL +does not have the tag.

+
\ No newline at end of file diff --git a/book/rust-docs/places/storage/tags/fn.validate_tag.html b/book/rust-docs/places/storage/tags/fn.validate_tag.html new file mode 100644 index 0000000000..8febda0f5a --- /dev/null +++ b/book/rust-docs/places/storage/tags/fn.validate_tag.html @@ -0,0 +1,2 @@ +validate_tag in places::storage::tags - Rust

Function places::storage::tags::validate_tag

source ·
pub fn validate_tag(tag: &str) -> ValidatedTag<'_>
Expand description

Checks the validity of the specified tag.

+
\ No newline at end of file diff --git a/book/rust-docs/places/storage/tags/index.html b/book/rust-docs/places/storage/tags/index.html new file mode 100644 index 0000000000..89ff1a311c --- /dev/null +++ b/book/rust-docs/places/storage/tags/index.html @@ -0,0 +1 @@ +places::storage::tags - Rust

Module places::storage::tags

source ·

Enums

Functions

\ No newline at end of file diff --git a/book/rust-docs/places/storage/tags/sidebar-items.js b/book/rust-docs/places/storage/tags/sidebar-items.js new file mode 100644 index 0000000000..6b3a2c3b15 --- /dev/null +++ b/book/rust-docs/places/storage/tags/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["ValidatedTag"],"fn":["get_tags_for_url","get_urls_with_tag","remove_all_tags_from_url","remove_tag","tag_url","untag_url","validate_tag"]}; \ No newline at end of file diff --git a/book/rust-docs/places/types/enum.BookmarkType.html b/book/rust-docs/places/types/enum.BookmarkType.html new file mode 100644 index 0000000000..89b643d938 --- /dev/null +++ b/book/rust-docs/places/types/enum.BookmarkType.html @@ -0,0 +1,31 @@ +BookmarkType in places::types - Rust
#[repr(u8)]
pub enum BookmarkType { + Bookmark, + Folder, + Separator, +}
Expand description

Bookmark types.

+

Variants§

§

Bookmark

§

Folder

§

Separator

Implementations§

source§

impl BookmarkType

source

pub fn from_u8(v: u8) -> Option<Self>

source

pub fn from_u8_with_valid_url<F: Fn() -> bool>(v: u8, has_valid_url: F) -> Self

Trait Implementations§

source§

impl Clone for BookmarkType

source§

fn clone(&self) -> BookmarkType

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for BookmarkType

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl FromSql for BookmarkType

source§

fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self>

Converts SQLite value into Rust value.
source§

impl Hash for BookmarkType

source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · source§

fn hash_slice<H>(data: &[Self], state: &mut H)where + H: Hasher, + Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
source§

impl Ord for BookmarkType

source§

fn cmp(&self, other: &BookmarkType) -> Ordering

This method returns an Ordering between self and other. Read more
1.21.0 · source§

fn max(self, other: Self) -> Selfwhere + Self: Sized,

Compares and returns the maximum of two values. Read more
1.21.0 · source§

fn min(self, other: Self) -> Selfwhere + Self: Sized,

Compares and returns the minimum of two values. Read more
1.50.0 · source§

fn clamp(self, min: Self, max: Self) -> Selfwhere + Self: Sized + PartialOrd<Self>,

Restrict a value to a certain interval. Read more
source§

impl PartialEq<BookmarkType> for BookmarkType

source§

fn eq(&self, other: &BookmarkType) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl PartialOrd<BookmarkType> for BookmarkType

source§

fn partial_cmp(&self, other: &BookmarkType) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · source§

fn lt(&self, other: &Rhs) -> bool

This method tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · source§

fn le(&self, other: &Rhs) -> bool

This method tests less than or equal to (for self and other) and is used by the <= +operator. Read more
1.0.0 · source§

fn gt(&self, other: &Rhs) -> bool

This method tests greater than (for self and other) and is used by the > operator. Read more
1.0.0 · source§

fn ge(&self, other: &Rhs) -> bool

This method tests greater than or equal to (for self and other) and is used by the >= +operator. Read more
source§

impl Serialize for BookmarkType

source§

fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>where + S: Serializer,

Serialize this value into the given Serde serializer. Read more
source§

impl ToSql for BookmarkType

source§

fn to_sql(&self) -> RusqliteResult<ToSqlOutput<'_>>

Converts Rust value to SQLite value
source§

impl Copy for BookmarkType

source§

impl Eq for BookmarkType

source§

impl StructuralEq for BookmarkType

source§

impl StructuralPartialEq for BookmarkType

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> CallHasher for Twhere + T: Hash + ?Sized,

§

fn get_hash<H, B>(value: &H, build_hasher: &B) -> u64where + H: Hash + ?Sized, + B: BuildHasher,

source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/types/enum.SyncStatus.html b/book/rust-docs/places/types/enum.SyncStatus.html new file mode 100644 index 0000000000..9e651a9d19 --- /dev/null +++ b/book/rust-docs/places/types/enum.SyncStatus.html @@ -0,0 +1,44 @@ +SyncStatus in places::types - Rust

Enum places::types::SyncStatus

source ·
#[repr(u8)]
pub enum SyncStatus { + Unknown, + New, + Normal, +}
Expand description

Re SyncStatus - note that:

+
    +
  • logins has synced=0, changed=1, new=2
  • +
  • desktop bookmarks has unknown=0, new=1, normal=2 +This is “places”, so eventually bookmarks will have a status - should history +and bookmarks share this enum? +Note that history specifically needs neither (a) login’s “changed” (the +changeCounter works there), nor (b) bookmark’s “unknown” (as that’s only +used after a restore). +History only needs a distinction between “synced” and “new” so it doesn’t +accumulate never-to-be-synced tombstones - so we basically copy bookmarks +and treat unknown as new. +Which means we get the “bonus side-effect” ;) of ::Unknown replacing Option<>!
  • +
+

Note that some of these values are in schema.rs

+

Variants§

§

Unknown

§

New

§

Normal

Implementations§

source§

impl SyncStatus

source

pub fn from_u8(v: u8) -> Self

Trait Implementations§

source§

impl Clone for SyncStatus

source§

fn clone(&self) -> SyncStatus

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for SyncStatus

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl FromSql for SyncStatus

source§

fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self>

Converts SQLite value into Rust value.
source§

impl Hash for SyncStatus

source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · source§

fn hash_slice<H>(data: &[Self], state: &mut H)where + H: Hasher, + Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
source§

impl Ord for SyncStatus

source§

fn cmp(&self, other: &SyncStatus) -> Ordering

This method returns an Ordering between self and other. Read more
1.21.0 · source§

fn max(self, other: Self) -> Selfwhere + Self: Sized,

Compares and returns the maximum of two values. Read more
1.21.0 · source§

fn min(self, other: Self) -> Selfwhere + Self: Sized,

Compares and returns the minimum of two values. Read more
1.50.0 · source§

fn clamp(self, min: Self, max: Self) -> Selfwhere + Self: Sized + PartialOrd<Self>,

Restrict a value to a certain interval. Read more
source§

impl PartialEq<SyncStatus> for SyncStatus

source§

fn eq(&self, other: &SyncStatus) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl PartialOrd<SyncStatus> for SyncStatus

source§

fn partial_cmp(&self, other: &SyncStatus) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · source§

fn lt(&self, other: &Rhs) -> bool

This method tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · source§

fn le(&self, other: &Rhs) -> bool

This method tests less than or equal to (for self and other) and is used by the <= +operator. Read more
1.0.0 · source§

fn gt(&self, other: &Rhs) -> bool

This method tests greater than (for self and other) and is used by the > operator. Read more
1.0.0 · source§

fn ge(&self, other: &Rhs) -> bool

This method tests greater than or equal to (for self and other) and is used by the >= +operator. Read more
source§

impl ToSql for SyncStatus

source§

fn to_sql(&self) -> RusqliteResult<ToSqlOutput<'_>>

Converts Rust value to SQLite value
source§

impl Copy for SyncStatus

source§

impl Eq for SyncStatus

source§

impl StructuralEq for SyncStatus

source§

impl StructuralPartialEq for SyncStatus

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> CallHasher for Twhere + T: Hash + ?Sized,

§

fn get_hash<H, B>(value: &H, build_hasher: &B) -> u64where + H: Hash + ?Sized, + B: BuildHasher,

source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/types/enum.VisitType.html b/book/rust-docs/places/types/enum.VisitType.html new file mode 100644 index 0000000000..fc37ea209b --- /dev/null +++ b/book/rust-docs/places/types/enum.VisitType.html @@ -0,0 +1,34 @@ +VisitType in places::types - Rust

Enum places::types::VisitType

source ·
#[repr(u8)]
pub enum VisitType { + Link, + Typed, + Bookmark, + Embed, + RedirectPermanent, + RedirectTemporary, + Download, + FramedLink, + Reload, + UpdatePlace, +}

Variants§

§

Typed

§

Bookmark

§

Embed

§

RedirectPermanent

§

RedirectTemporary

§

Download

§

Reload

§

UpdatePlace

Implementations§

Trait Implementations§

source§

impl Clone for VisitType

source§

fn clone(&self) -> VisitType

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for VisitType

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl<'de> Deserialize<'de> for VisitType

source§

fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error>

Deserialize this value from the given Serde deserializer. Read more
source§

impl Extend<VisitType> for VisitTransitionSet

source§

fn extend<I>(&mut self, iter: I)where + I: IntoIterator<Item = VisitType>,

Extends a collection with the contents of an iterator. Read more
source§

fn extend_one(&mut self, item: A)

🔬This is a nightly-only experimental API. (extend_one)
Extends a collection with exactly one element.
source§

fn extend_reserve(&mut self, additional: usize)

🔬This is a nightly-only experimental API. (extend_one)
Reserves capacity in a collection for the given number of additional elements. Read more
source§

impl FromIterator<VisitType> for VisitTransitionSet

source§

fn from_iter<I>(iterator: I) -> Selfwhere + I: IntoIterator<Item = VisitType>,

Creates a value from an iterator. Read more
source§

impl Hash for VisitType

source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · source§

fn hash_slice<H>(data: &[Self], state: &mut H)where + H: Hasher, + Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
source§

impl PartialEq<VisitType> for VisitType

source§

fn eq(&self, other: &VisitType) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Serialize for VisitType

source§

fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error>

Serialize this value into the given Serde serializer. Read more
source§

impl ToSql for VisitType

source§

fn to_sql(&self) -> RusqliteResult<ToSqlOutput<'_>>

Converts Rust value to SQLite value
source§

impl TryFrom<u8> for VisitType

§

type Error = InvalidVisitType

The type returned in the event of a conversion error.
source§

fn try_from(p: u8) -> Result<Self, Self::Error>

Performs the conversion.
source§

impl Copy for VisitType

source§

impl Eq for VisitType

source§

impl StructuralEq for VisitType

source§

impl StructuralPartialEq for VisitType

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> CallHasher for Twhere + T: Hash + ?Sized,

§

fn get_hash<H, B>(value: &H, build_hasher: &B) -> u64where + H: Hash + ?Sized, + B: BuildHasher,

source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/places/types/index.html b/book/rust-docs/places/types/index.html new file mode 100644 index 0000000000..5d2149e632 --- /dev/null +++ b/book/rust-docs/places/types/index.html @@ -0,0 +1 @@ +places::types - Rust
\ No newline at end of file diff --git a/book/rust-docs/places/types/sidebar-items.js b/book/rust-docs/places/types/sidebar-items.js new file mode 100644 index 0000000000..1f6102c3ef --- /dev/null +++ b/book/rust-docs/places/types/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["BookmarkType","SyncStatus","VisitType"],"struct":["InvalidVisitType","VisitTransitionSet"],"type":["UnknownFields"]}; \ No newline at end of file diff --git a/book/rust-docs/places/types/struct.InvalidVisitType.html b/book/rust-docs/places/types/struct.InvalidVisitType.html new file mode 100644 index 0000000000..29d8a95454 --- /dev/null +++ b/book/rust-docs/places/types/struct.InvalidVisitType.html @@ -0,0 +1,23 @@ +InvalidVisitType in places::types - Rust
pub struct InvalidVisitType;

Trait Implementations§

source§

impl Clone for InvalidVisitType

source§

fn clone(&self) -> InvalidVisitType

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for InvalidVisitType

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Display for InvalidVisitType

source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Error for InvalidVisitType

1.30.0 · source§

fn source(&self) -> Option<&(dyn Error + 'static)>

The lower-level source of this error, if any. Read more
1.0.0 · source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type based access to context intended for error reports. Read more
source§

impl Ord for InvalidVisitType

source§

fn cmp(&self, other: &InvalidVisitType) -> Ordering

This method returns an Ordering between self and other. Read more
1.21.0 · source§

fn max(self, other: Self) -> Selfwhere + Self: Sized,

Compares and returns the maximum of two values. Read more
1.21.0 · source§

fn min(self, other: Self) -> Selfwhere + Self: Sized,

Compares and returns the minimum of two values. Read more
1.50.0 · source§

fn clamp(self, min: Self, max: Self) -> Selfwhere + Self: Sized + PartialOrd<Self>,

Restrict a value to a certain interval. Read more
source§

impl PartialEq<InvalidVisitType> for InvalidVisitType

source§

fn eq(&self, other: &InvalidVisitType) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl PartialOrd<InvalidVisitType> for InvalidVisitType

source§

fn partial_cmp(&self, other: &InvalidVisitType) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · source§

fn lt(&self, other: &Rhs) -> bool

This method tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · source§

fn le(&self, other: &Rhs) -> bool

This method tests less than or equal to (for self and other) and is used by the <= +operator. Read more
1.0.0 · source§

fn gt(&self, other: &Rhs) -> bool

This method tests greater than (for self and other) and is used by the > operator. Read more
1.0.0 · source§

fn ge(&self, other: &Rhs) -> bool

This method tests greater than or equal to (for self and other) and is used by the >= +operator. Read more
source§

impl Copy for InvalidVisitType

source§

impl Eq for InvalidVisitType

source§

impl StructuralEq for InvalidVisitType

source§

impl StructuralPartialEq for InvalidVisitType

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<E> Provider for Ewhere + E: Error + ?Sized,

source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (provide_any)
Data providers should implement this method to provide all values they are able to +provide by using demand. Read more
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/types/struct.VisitTransitionSet.html b/book/rust-docs/places/types/struct.VisitTransitionSet.html new file mode 100644 index 0000000000..d9c59609a9 --- /dev/null +++ b/book/rust-docs/places/types/struct.VisitTransitionSet.html @@ -0,0 +1,22 @@ +VisitTransitionSet in places::types - Rust
pub struct VisitTransitionSet { /* private fields */ }

Implementations§

source§

impl VisitTransitionSet

source

pub const fn new() -> Self

source

pub const fn empty() -> Self

source

pub const fn all() -> Self

source

pub const fn single(ty: VisitType) -> Self

source

pub fn for_specific(tys: &[VisitType]) -> Self

source

pub fn into_u16(self) -> u16

source

pub fn from_u16(v: u16) -> Result<VisitTransitionSet, InvalidVisitType>

source

pub fn contains(self, t: VisitType) -> bool

source

pub fn insert(&mut self, t: VisitType)

source

pub fn remove(&mut self, t: VisitType)

source

pub fn complement(self) -> VisitTransitionSet

source

pub fn len(self) -> usize

source

pub fn is_empty(self) -> bool

Trait Implementations§

source§

impl Clone for VisitTransitionSet

source§

fn clone(&self) -> VisitTransitionSet

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for VisitTransitionSet

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for VisitTransitionSet

source§

fn default() -> VisitTransitionSet

Returns the “default value” for a type. Read more
source§

impl Extend<VisitType> for VisitTransitionSet

source§

fn extend<I>(&mut self, iter: I)where + I: IntoIterator<Item = VisitType>,

Extends a collection with the contents of an iterator. Read more
source§

fn extend_one(&mut self, item: A)

🔬This is a nightly-only experimental API. (extend_one)
Extends a collection with exactly one element.
source§

fn extend_reserve(&mut self, additional: usize)

🔬This is a nightly-only experimental API. (extend_one)
Reserves capacity in a collection for the given number of additional elements. Read more
source§

impl From<VisitTransitionSet> for u16

source§

fn from(vts: VisitTransitionSet) -> Self

Converts to this type from the input type.
source§

impl FromIterator<VisitType> for VisitTransitionSet

source§

fn from_iter<I>(iterator: I) -> Selfwhere + I: IntoIterator<Item = VisitType>,

Creates a value from an iterator. Read more
source§

impl Hash for VisitTransitionSet

source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · source§

fn hash_slice<H>(data: &[Self], state: &mut H)where + H: Hasher, + Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
source§

impl IntoIterator for VisitTransitionSet

§

type Item = VisitType

The type of the elements being iterated over.
§

type IntoIter = VisitTransitionSetIter

Which kind of iterator are we turning this into?
source§

fn into_iter(self) -> VisitTransitionSetIter

Creates an iterator from a value. Read more
source§

impl PartialEq<VisitTransitionSet> for VisitTransitionSet

source§

fn eq(&self, other: &VisitTransitionSet) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl ToSql for VisitTransitionSet

source§

fn to_sql(&self) -> Result<ToSqlOutput<'_>>

Converts Rust value to SQLite value
source§

impl TryFrom<u16> for VisitTransitionSet

§

type Error = InvalidVisitType

The type returned in the event of a conversion error.
source§

fn try_from(bits: u16) -> Result<Self, InvalidVisitType>

Performs the conversion.
source§

impl Copy for VisitTransitionSet

source§

impl Eq for VisitTransitionSet

source§

impl StructuralEq for VisitTransitionSet

source§

impl StructuralPartialEq for VisitTransitionSet

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> CallHasher for Twhere + T: Hash + ?Sized,

§

fn get_hash<H, B>(value: &H, build_hasher: &B) -> u64where + H: Hash + ?Sized, + B: BuildHasher,

source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/places/types/type.UnknownFields.html b/book/rust-docs/places/types/type.UnknownFields.html new file mode 100644 index 0000000000..67b43e6855 --- /dev/null +++ b/book/rust-docs/places/types/type.UnknownFields.html @@ -0,0 +1 @@ +UnknownFields in places::types - Rust

Type Definition places::types::UnknownFields

source ·
pub type UnknownFields = Map<String, Value>;
\ No newline at end of file diff --git a/book/rust-docs/places/types/visit_transition_set/struct.VisitTransitionSet.html b/book/rust-docs/places/types/visit_transition_set/struct.VisitTransitionSet.html new file mode 100644 index 0000000000..14ecea644d --- /dev/null +++ b/book/rust-docs/places/types/visit_transition_set/struct.VisitTransitionSet.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../places/types/struct.VisitTransitionSet.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/protobuf_gen/all.html b/book/rust-docs/protobuf_gen/all.html new file mode 100644 index 0000000000..66ff106094 --- /dev/null +++ b/book/rust-docs/protobuf_gen/all.html @@ -0,0 +1 @@ +List of all items in this crate

List of all items

Structs

Functions

\ No newline at end of file diff --git a/book/rust-docs/protobuf_gen/fn.main.html b/book/rust-docs/protobuf_gen/fn.main.html new file mode 100644 index 0000000000..f189ab67ce --- /dev/null +++ b/book/rust-docs/protobuf_gen/fn.main.html @@ -0,0 +1 @@ +main in protobuf_gen - Rust

Function protobuf_gen::main

source ·
pub(crate) fn main()
\ No newline at end of file diff --git a/book/rust-docs/protobuf_gen/index.html b/book/rust-docs/protobuf_gen/index.html new file mode 100644 index 0000000000..2f6fc4ad4f --- /dev/null +++ b/book/rust-docs/protobuf_gen/index.html @@ -0,0 +1 @@ +protobuf_gen - Rust
\ No newline at end of file diff --git a/book/rust-docs/protobuf_gen/sidebar-items.js b/book/rust-docs/protobuf_gen/sidebar-items.js new file mode 100644 index 0000000000..fae17aadeb --- /dev/null +++ b/book/rust-docs/protobuf_gen/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["main"],"struct":["ProtobufOpts"]}; \ No newline at end of file diff --git a/book/rust-docs/protobuf_gen/struct.ProtobufOpts.html b/book/rust-docs/protobuf_gen/struct.ProtobufOpts.html new file mode 100644 index 0000000000..9ba2a43c85 --- /dev/null +++ b/book/rust-docs/protobuf_gen/struct.ProtobufOpts.html @@ -0,0 +1,16 @@ +ProtobufOpts in protobuf_gen - Rust
pub(crate) struct ProtobufOpts {
+    pub(crate) dir: String,
+    pub(crate) out_dir: Option<String>,
+}

Fields§

§dir: String§out_dir: Option<String>

Trait Implementations§

source§

impl Debug for ProtobufOpts

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl<'de> Deserialize<'de> for ProtobufOpts

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/push/all.html b/book/rust-docs/push/all.html new file mode 100644 index 0000000000..108d87b1bc --- /dev/null +++ b/book/rust-docs/push/all.html @@ -0,0 +1 @@ +List of all items in this crate
\ No newline at end of file diff --git a/book/rust-docs/push/enum.BridgeType.html b/book/rust-docs/push/enum.BridgeType.html new file mode 100644 index 0000000000..84cce53f75 --- /dev/null +++ b/book/rust-docs/push/enum.BridgeType.html @@ -0,0 +1,29 @@ +BridgeType in push - Rust

Enum push::BridgeType

source ·
pub enum BridgeType {
+    Fcm,
+    Adm,
+    Apns,
+}
Expand description

The types of supported native bridges.

+

FCM = Google Android Firebase Cloud Messaging +ADM = Amazon Device Messaging for FireTV +APNS = Apple Push Notification System for iOS

+

Please contact services back-end for any additional bridge protocols.

+

Variants§

§

Fcm

§

Adm

§

Apns

Trait Implementations§

source§

impl Clone for BridgeType

source§

fn clone(&self) -> BridgeType

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for BridgeType

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Display for BridgeType

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Ord for BridgeType

source§

fn cmp(&self, other: &BridgeType) -> Ordering

This method returns an Ordering between self and other. Read more
1.21.0 · source§

fn max(self, other: Self) -> Selfwhere + Self: Sized,

Compares and returns the maximum of two values. Read more
1.21.0 · source§

fn min(self, other: Self) -> Selfwhere + Self: Sized,

Compares and returns the minimum of two values. Read more
1.50.0 · source§

fn clamp(self, min: Self, max: Self) -> Selfwhere + Self: Sized + PartialOrd<Self>,

Restrict a value to a certain interval. Read more
source§

impl PartialEq<BridgeType> for BridgeType

source§

fn eq(&self, other: &BridgeType) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl PartialOrd<BridgeType> for BridgeType

source§

fn partial_cmp(&self, other: &BridgeType) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · source§

fn lt(&self, other: &Rhs) -> bool

This method tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · source§

fn le(&self, other: &Rhs) -> bool

This method tests less than or equal to (for self and other) and is used by the <= +operator. Read more
1.0.0 · source§

fn gt(&self, other: &Rhs) -> bool

This method tests greater than (for self and other) and is used by the > operator. Read more
1.0.0 · source§

fn ge(&self, other: &Rhs) -> bool

This method tests greater than or equal to (for self and other) and is used by the >= +operator. Read more
source§

impl Copy for BridgeType

source§

impl Eq for BridgeType

source§

impl StructuralEq for BridgeType

source§

impl StructuralPartialEq for BridgeType

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/push/enum.PushApiError.html b/book/rust-docs/push/enum.PushApiError.html new file mode 100644 index 0000000000..5b0c639ee9 --- /dev/null +++ b/book/rust-docs/push/enum.PushApiError.html @@ -0,0 +1,21 @@ +PushApiError in push - Rust

Enum push::PushApiError

source ·
pub enum PushApiError {
+    UAIDNotRecognizedError(String),
+    RecordNotFoundError(String),
+    InternalError(String),
+}

Variants§

§

UAIDNotRecognizedError(String)

The UAID was not recognized by the server

+
§

RecordNotFoundError(String)

Record not found for the given chid

+
§

InternalError(String)

Internal Error

+

Trait Implementations§

source§

impl Debug for PushApiError

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Display for PushApiError

source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Error for PushApiError

1.30.0 · source§

fn source(&self) -> Option<&(dyn Error + 'static)>

The lower-level source of this error, if any. Read more
1.0.0 · source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type based access to context intended for error reports. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<E> Provider for Ewhere + E: Error + ?Sized,

source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (provide_any)
Data providers should implement this method to provide all values they are able to +provide by using demand. Read more
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/push/enum.PushError.html b/book/rust-docs/push/enum.PushError.html new file mode 100644 index 0000000000..46ec8dabb7 --- /dev/null +++ b/book/rust-docs/push/enum.PushError.html @@ -0,0 +1,40 @@ +PushError in push - Rust

Enum push::PushError

source ·
pub enum PushError {
+
Show 14 variants GeneralError(String), + CryptoError(String), + CommunicationError(String), + CommunicationServerError(String), + AlreadyRegisteredError, + StorageError(String), + RecordNotFoundError(String), + StorageSqlError(Error), + TranscodingError(String), + UrlParseError(ParseError), + JSONDeserializeError(Error), + UAIDNotRecognizedError(String), + RequestError(Error), + OpenDatabaseError(Error), +
}

Variants§

§

GeneralError(String)

An unspecified general error has occured

+
§

CryptoError(String)

§

CommunicationError(String)

A Client communication error

+
§

CommunicationServerError(String)

An error returned from the registration Server

+
§

AlreadyRegisteredError

Channel is already registered, generate new channelID

+
§

StorageError(String)

An error with Storage

+
§

RecordNotFoundError(String)

§

StorageSqlError(Error)

A failure to encode data to/from storage.

+
§

TranscodingError(String)

§

UrlParseError(ParseError)

A failure to parse a URL.

+
§

JSONDeserializeError(Error)

A failure deserializing json.

+
§

UAIDNotRecognizedError(String)

The UAID was not recognized by the server

+
§

RequestError(Error)

Was unable to send request to server

+
§

OpenDatabaseError(Error)

Was unable to open the database

+

Trait Implementations§

source§

impl Debug for PushError

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Display for PushError

source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Error for PushError

source§

fn source(&self) -> Option<&(dyn Error + 'static)>

The lower-level source of this error, if any. Read more
1.0.0 · source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type based access to context intended for error reports. Read more
source§

impl From<Box<ErrorKind, Global>> for PushError

source§

fn from(value: Error) -> Self

Converts to this type from the input type.
source§

impl From<DecodeError> for PushError

source§

fn from(value: DecodeError) -> Self

Converts to this type from the input type.
source§

impl From<Error> for PushError

source§

fn from(source: Error) -> Self

Converts to this type from the input type.
source§

impl From<Error> for PushError

source§

fn from(source: Error) -> Self

Converts to this type from the input type.
source§

impl From<Error> for PushError

source§

fn from(source: Error) -> Self

Converts to this type from the input type.
source§

impl From<Error> for PushError

source§

fn from(value: Error) -> Self

Converts to this type from the input type.
source§

impl From<Error> for PushError

source§

fn from(source: Error) -> Self

Converts to this type from the input type.
source§

impl From<ParseError> for PushError

source§

fn from(source: ParseError) -> Self

Converts to this type from the input type.
source§

impl GetErrorHandling for PushError

§

type ExternalError = PushApiError

source§

fn get_error_handling(&self) -> ErrorHandling<Self::ExternalError>

Return how to handle our internal errors

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<E> Provider for Ewhere + E: Error + ?Sized,

source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (provide_any)
Data providers should implement this method to provide all values they are able to +provide by using demand. Read more
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/push/enum.PushHttpProtocol.html b/book/rust-docs/push/enum.PushHttpProtocol.html new file mode 100644 index 0000000000..9e0b03802e --- /dev/null +++ b/book/rust-docs/push/enum.PushHttpProtocol.html @@ -0,0 +1,23 @@ +PushHttpProtocol in push - Rust
pub enum PushHttpProtocol {
+    Https,
+    Http,
+}

Variants§

§

Https

§

Http

Trait Implementations§

source§

impl Clone for Protocol

source§

fn clone(&self) -> Protocol

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for Protocol

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for Protocol

source§

fn default() -> Protocol

Returns the “default value” for a type. Read more
source§

impl Display for Protocol

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl FromStr for Protocol

§

type Err = PushError

The associated error which can be returned from parsing.
source§

fn from_str(s: &str) -> Result<Self, Self::Err>

Parses a string s to return a value of this type. Read more
source§

impl Ord for Protocol

source§

fn cmp(&self, other: &Protocol) -> Ordering

This method returns an Ordering between self and other. Read more
1.21.0 · source§

fn max(self, other: Self) -> Selfwhere + Self: Sized,

Compares and returns the maximum of two values. Read more
1.21.0 · source§

fn min(self, other: Self) -> Selfwhere + Self: Sized,

Compares and returns the minimum of two values. Read more
1.50.0 · source§

fn clamp(self, min: Self, max: Self) -> Selfwhere + Self: Sized + PartialOrd<Self>,

Restrict a value to a certain interval. Read more
source§

impl PartialEq<Protocol> for Protocol

source§

fn eq(&self, other: &Protocol) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl PartialOrd<Protocol> for Protocol

source§

fn partial_cmp(&self, other: &Protocol) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · source§

fn lt(&self, other: &Rhs) -> bool

This method tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · source§

fn le(&self, other: &Rhs) -> bool

This method tests less than or equal to (for self and other) and is used by the <= +operator. Read more
1.0.0 · source§

fn gt(&self, other: &Rhs) -> bool

This method tests greater than (for self and other) and is used by the > operator. Read more
1.0.0 · source§

fn ge(&self, other: &Rhs) -> bool

This method tests greater than or equal to (for self and other) and is used by the >= +operator. Read more
source§

impl Copy for Protocol

source§

impl Eq for Protocol

source§

impl StructuralEq for Protocol

source§

impl StructuralPartialEq for Protocol

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/push/error/enum.PushApiError.html b/book/rust-docs/push/error/enum.PushApiError.html new file mode 100644 index 0000000000..dcc524e91c --- /dev/null +++ b/book/rust-docs/push/error/enum.PushApiError.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../push/enum.PushApiError.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/push/error/enum.PushError.html b/book/rust-docs/push/error/enum.PushError.html new file mode 100644 index 0000000000..f341b5ca45 --- /dev/null +++ b/book/rust-docs/push/error/enum.PushError.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../push/enum.PushError.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/push/error/type.ApiResult.html b/book/rust-docs/push/error/type.ApiResult.html new file mode 100644 index 0000000000..ef8b29f6f7 --- /dev/null +++ b/book/rust-docs/push/error/type.ApiResult.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../push/type.ApiResult.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/push/index.html b/book/rust-docs/push/index.html new file mode 100644 index 0000000000..29f4137f1d --- /dev/null +++ b/book/rust-docs/push/index.html @@ -0,0 +1,140 @@ +push - Rust

Crate push

source ·
Expand description

Rust Push Component

+

This component helps an application to manage WebPush subscriptions, +acting as an intermediary between Mozilla’s autopush service +and platform native push infrastructure such as Firebase Cloud Messaging or Amazon Device Messaging.

+

Background Concepts

WebPush Subscriptions

+

A WebPush client manages a number of subscriptions, each of which is used to deliver push +notifications to a different part of the app. For example, a web browser might manage a separate +subscription for each website that has registered a service worker, and an application that includes Firefox Accounts would manage +a dedicated subscription on which to receive account state updates.

+

Each subscription is identified by a unique channel id, which is a randomly-generated identifier. +It’s the responsibility of the application to know how to map a channel id to an appropriate function +in the app to receive push notifications. Subscriptions also have an associated scope which is something +to do which service workers that your humble author doesn’t really understand :-/.

+

When a subscription is created for a channel id, we allocate subscription info consisting of:

+
    +
  • An HTTP endpoint URL at which push messages can be submitted.
  • +
  • A cryptographic key and authentication secret with which push messages can be encrypted.
  • +
+

This subscription info is distributed to other services that want to send push messages to +the application.

+

The HTTP endpoint is provided by Mozilla’s autopush service, +and we use the rust-ece to manage encryption with the cryptographic keys.

+

Here’s a helpful diagram of how the subscription flow works at a high level across the moving parts: +A Sequence diagram showing how the different parts of push interact

+

AutoPush Bridging

+

Our target consumer platforms each have their own proprietary push-notification infrastructure, +such as Firebase Cloud Messaging for Android +and the Apple Push Notification Service for iOS. +Mozilla’s autopush service provides a bridge between +these different mechanisms and the WebPush standard so that they can be used with a consistent +interface.

+

This component acts a client of the Push Service Bridge HTTP Interface.

+

We assume two things about the consuming application:

+
    +
  • It has registered with the autopush service and received a unique app_id identifying this registration.
  • +
  • It has registred with whatever platform-specific notification infrastructure is appropriate, and is +able to obtain a token corresponding to its native push notification state.
  • +
+

On first use, this component will register itself as an application instance with the autopush service, providing the app_id and token and receiving a unique uaid (“user-agent id”) to identify its +connection to the server.

+

As the application adds or removes subscriptions using the API of this component, it will:

+
    +
  • Manage a local database of subscriptions and the corresponding cryptographic material.
  • +
  • Make corresponding HTTP API calls to update the state associated with its uaid on the autopush server.
  • +
+

Periodically, the application should call a special verify_connection method to check whether +the state on the autopush server matches the local state and take any corrective action if it +differs.

+

For local development and debugging, it is possible to run a local instance of the autopush +bridge service; see this google doc for details.

+

API

Initialization

+

Calls are handled by the PushManager, which provides a handle for future calls.

+

example:

+

+import mozilla.appservices.push.(PushManager, BridgeTypes)
+
+// The following are mock calls for fetching application level configuration options.
+// "SenderID" is the native OS push message application identifier. See Native
+// messaging documentation for details.
+val sender_id = SystemConfigurationOptions.get("SenderID")
+
+// The "bridge type" is the identifier for the native OS push message system.
+// (e.g. FCM for Google Firebase Cloud Messaging, ADM for Amazon Direct Messaging,
+// etc.)
+val bridge_type = BridgeTypes.FCM
+
+// The "registration_id" is the native OS push message user registration number.
+// Native push message registration usually happens at application start, and returns
+// an opaque user identifier string. See Native messaging documentation for details.
+val registration_id = NativeMessagingSystem.register(sender_id)
+
+val push_manager = PushManager(
+    sender_id,
+    bridge_type,
+    registration_id
+)
+
+// It is strongly encouraged that the connection is verified at least once a day.
+// This will ensure that the server and UA have matching information regarding
+// subscriptions. This call usually returns quickly, but may take longer if the
+// UA has a large number of subscriptions and things have fallen out of sync.
+
+for change in push_manager.verify_connection() {
+    // fetch the subscriber from storage using the change[0] and
+    // notify them with a `pushsubscriptionchange` message containing the new
+    // endpoint change[1]
+}
+
+

New subscription

+

Before messages can be delivered, a new subscription must be requested. The subscription info block contains all the information a remote subscription provider service will need to encrypt and transmit a message to this user agent.

+

example:

+

+// Each new request must have a unique "channel" identifier. This channel helps
+// later identify recipients and aid in routing. A ChannelID is a UUID4 value.
+// the "scope" is the ServiceWorkerRegistration scope. This will be used
+// later for push notification management.
+val channelID = GUID.randomUUID()
+
+val subscription_info = push_manager.subscribe(channelID, endpoint_scope)
+
+// the published subscription info has the following JSON format:
+// {"endpoint": subscription_info.endpoint,
+//  "keys": {
+//      "auth": subscription_info.keys.auth,
+//      "p256dh": subscription_info.keys.p256dh
+//  }}
+

End a subscription

+

A user may decide to no longer receive a given subscription. To remove a given subscription, pass the associated channelID

+
push_manager.unsubscribe(channelID)  // Terminate a single subscription
+
+

If the user wishes to terminate all subscriptions, send and empty string for channelID

+
push_manager.unsubscribe("")        // Terminate all subscriptions for a user
+
+

If this function returns false the subsequent verify_connection may result in new channel endpoints.

+

Decrypt an incoming subscription message

+

An incoming subscription body will contain a number of metadata elements along with the body of the message. Due to platform differences, how that metadata is provided may //! vary, however the most common form is that the messages “payload” looks like.

+
{"chid": "...",         // ChannelID
+ "con": "...",          // Encoding form
+ "enc": "...",          // Optional encryption header
+ "crypto-key": "...",   // Optional crypto key header
+ "body": "...",         // Encrypted message body
+}
+
+

These fields may be included as a sub-hash, or may be intermingled with other data fields. If you have doubts or concerns, please contact the Application Services team guidance

+

Based on the above payload, an example call might look like:

+
    val result = manager.decrypt(
+        channelID = payload["chid"].toString(),
+        body = payload["body"].toString(),
+        encoding = payload["con"].toString(),
+        salt = payload.getOrElse("enc", "").toString(),
+        dh = payload.getOrElse("dh", "").toString()
+    )
+    // result returns a byte array. You may need to convert to a string
+    return result.toString(Charset.forName("UTF-8"))
+

Structs

Enums

Type Definitions

\ No newline at end of file diff --git a/book/rust-docs/push/internal/config/enum.BridgeType.html b/book/rust-docs/push/internal/config/enum.BridgeType.html new file mode 100644 index 0000000000..e42c64029a --- /dev/null +++ b/book/rust-docs/push/internal/config/enum.BridgeType.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../push/enum.BridgeType.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/push/internal/config/enum.Protocol.html b/book/rust-docs/push/internal/config/enum.Protocol.html new file mode 100644 index 0000000000..972b5a370d --- /dev/null +++ b/book/rust-docs/push/internal/config/enum.Protocol.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../push/enum.PushHttpProtocol.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/push/internal/config/struct.PushConfiguration.html b/book/rust-docs/push/internal/config/struct.PushConfiguration.html new file mode 100644 index 0000000000..70c631df54 --- /dev/null +++ b/book/rust-docs/push/internal/config/struct.PushConfiguration.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../push/struct.PushConfiguration.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/push/sidebar-items.js b/book/rust-docs/push/sidebar-items.js new file mode 100644 index 0000000000..fec1480b53 --- /dev/null +++ b/book/rust-docs/push/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["BridgeType","PushApiError","PushError","PushHttpProtocol"],"struct":["KeyInfo","PushConfiguration","PushManager","PushSubscriptionChanged","SubscriptionInfo","SubscriptionResponse"],"type":["ApiResult"]}; \ No newline at end of file diff --git a/book/rust-docs/push/struct.KeyInfo.html b/book/rust-docs/push/struct.KeyInfo.html new file mode 100644 index 0000000000..62cc1bb742 --- /dev/null +++ b/book/rust-docs/push/struct.KeyInfo.html @@ -0,0 +1,24 @@ +KeyInfo in push - Rust

Struct push::KeyInfo

source ·
pub struct KeyInfo {
+    pub auth: String,
+    pub p256dh: String,
+}
Expand description

Key Information that can be used to encrypt payloads. These are encoded as base64 +so will need to be decoded before they can actually be used as keys.

+

Fields§

§auth: String§p256dh: String

Trait Implementations§

source§

impl Clone for KeyInfo

source§

fn clone(&self) -> KeyInfo

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for KeyInfo

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Ord for KeyInfo

source§

fn cmp(&self, other: &KeyInfo) -> Ordering

This method returns an Ordering between self and other. Read more
1.21.0 · source§

fn max(self, other: Self) -> Selfwhere + Self: Sized,

Compares and returns the maximum of two values. Read more
1.21.0 · source§

fn min(self, other: Self) -> Selfwhere + Self: Sized,

Compares and returns the minimum of two values. Read more
1.50.0 · source§

fn clamp(self, min: Self, max: Self) -> Selfwhere + Self: Sized + PartialOrd<Self>,

Restrict a value to a certain interval. Read more
source§

impl PartialEq<KeyInfo> for KeyInfo

source§

fn eq(&self, other: &KeyInfo) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl PartialOrd<KeyInfo> for KeyInfo

source§

fn partial_cmp(&self, other: &KeyInfo) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · source§

fn lt(&self, other: &Rhs) -> bool

This method tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · source§

fn le(&self, other: &Rhs) -> bool

This method tests less than or equal to (for self and other) and is used by the <= +operator. Read more
1.0.0 · source§

fn gt(&self, other: &Rhs) -> bool

This method tests greater than (for self and other) and is used by the > operator. Read more
1.0.0 · source§

fn ge(&self, other: &Rhs) -> bool

This method tests greater than or equal to (for self and other) and is used by the >= +operator. Read more
source§

impl Eq for KeyInfo

source§

impl StructuralEq for KeyInfo

source§

impl StructuralPartialEq for KeyInfo

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/push/struct.PushConfiguration.html b/book/rust-docs/push/struct.PushConfiguration.html new file mode 100644 index 0000000000..d77368eb23 --- /dev/null +++ b/book/rust-docs/push/struct.PushConfiguration.html @@ -0,0 +1,27 @@ +PushConfiguration in push - Rust

Struct push::PushConfiguration

source ·
pub struct PushConfiguration {
+    pub server_host: String,
+    pub http_protocol: Protocol,
+    pub bridge_type: BridgeType,
+    pub sender_id: String,
+    pub database_path: String,
+    pub verify_connection_rate_limiter: Option<u64>,
+}

Fields§

§server_host: String

host name:port

+
§http_protocol: Protocol

http protocol (for mobile, bridged connections “https”)

+
§bridge_type: BridgeType

bridge protocol (“fcm”)

+
§sender_id: String

Sender/Application ID value

+
§database_path: String

OS Path to the database

+
§verify_connection_rate_limiter: Option<u64>

Number of seconds between to rate limit +the verify connection call +defaults to 24 hours

+

Trait Implementations§

source§

impl Clone for PushConfiguration

source§

fn clone(&self) -> PushConfiguration

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for PushConfiguration

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/push/struct.PushManager.html b/book/rust-docs/push/struct.PushManager.html new file mode 100644 index 0000000000..3d5a7312ba --- /dev/null +++ b/book/rust-docs/push/struct.PushManager.html @@ -0,0 +1,157 @@ +PushManager in push - Rust

Struct push::PushManager

source ·
pub struct PushManager { /* private fields */ }
Expand description

Object representing the PushManager used to manage subscriptions

+

The PushManager object is the main interface provided by this crate +it allow consumers to manage push subscriptions. It exposes methods that +interact with the autopush server +and persists state representing subscriptions.

+

Implementations§

source§

impl PushManager

source

pub fn new(config: PushConfiguration) -> ApiResult<Self>

Creates a new PushManager object, not subscribed to any +channels

+
Arguments
+ +
Errors
+

Returns an error in the following cases:

+
    +
  • PushManager is unable to open the database_path given
  • +
  • PushManager is unable to establish a connection to the autopush server
  • +
+
source

pub fn subscribe( + &self, + scope: &str, + server_key: &Option<String> +) -> ApiResult<SubscriptionResponse>

Subscribes to a new channel and gets the Subscription Info block

+
Arguments
+
    +
  • channel_id - Channel ID (UUID4) for new subscription, either pre-generated or “” and one will be created.
  • +
  • scope - Site scope string (defaults to “” for no site scope string).
  • +
  • server_key - optional VAPID public key to “lock” subscriptions (defaults to “” for no key)
  • +
+
Returns
+

A Subscription response that includes the following:

+
    +
  • A URL that can be used to deliver push messages
  • +
  • A cryptographic key that can be used to encrypt messages +that would then be decrypted using the PushManager::decrypt function
  • +
+
Errors
+

Returns an error in the following cases:

+
    +
  • PushManager was unable to access its persisted storage
  • +
  • An error occurred sending a subscription request to the autopush server
  • +
  • An error occurred generating or deserializing the cryptographic keys
  • +
+
source

pub fn get_subscription( + &self, + scope: &str +) -> ApiResult<Option<SubscriptionResponse>>

Retrieves an existing push subscription

+
Arguments
+
    +
  • scope - Site scope string
  • +
+
Returns
+

A Subscription response that includes the following:

+
    +
  • A URL that can be used to deliver push messages
  • +
  • A cryptographic key that can be used to encrypt messages +that would then be decrypted using the PushManager::decrypt function
  • +
+
Errors
+

Returns an error in the following cases:

+
    +
  • PushManager was unable to access its persisted storage
  • +
  • An error occurred generating or deserializing the cryptographic keys
  • +
+
source

pub fn unsubscribe(&self, channel_id: &str) -> ApiResult<bool>

Unsubscribe from given channelID, ending that subscription for the user.

+
Arguments
+
    +
  • channel_id - Channel ID (UUID) for subscription to remove
  • +
+
Returns
+

Returns a boolean. Boolean is False if the subscription was already +terminated in the past.

+
Errors
+

Returns an error in the following cases:

+
    +
  • The PushManager does not contain a valid UAID
  • +
  • An error occurred sending an unsubscribe request to the autopush server
  • +
  • An error occurred accessing the PushManager’s persisted storage
  • +
+
source

pub fn unsubscribe_all(&self) -> ApiResult<()>

Unsubscribe all channels for the user

+
Errors
+

Returns an error in the following cases:

+
    +
  • The PushManager does not contain a valid UAID
  • +
  • An error occurred sending an unsubscribe request to the autopush server
  • +
  • An error occurred accessing the PushManager’s persisted storage
  • +
+
source

pub fn update(&self, new_token: &str) -> ApiResult<()>

Updates the Native OS push registration ID.

+
Arguments:
+
    +
  • new_token - the new Native OS push registration ID
  • +
+
Errors
+

Return an error in the following cases:

+
    +
  • The PushManager does not contain a valid UAID
  • +
  • An error occurred sending an update request to the autopush server
  • +
  • An error occurred accessing the PushManager’s persisted storage
  • +
+
source

pub fn verify_connection( + &self, + force_verify: bool +) -> ApiResult<Vec<PushSubscriptionChanged>>

Verifies the connection state

+

NOTE: This does not resubscribe to any channels +it only returns the list of channels that the client should +re-subscribe to.

+
Arguments
+
    +
  • force_verify: Force verification and ignore the rate limiter
  • +
+
Returns
+

Returns a list of PushSubscriptionChanged +indicating the channels the consumer the client should re-subscribe +to. If the list is empty, the client’s connection was verified +successfully, and the client does not need to resubscribe

+
Errors
+

Return an error in the following cases:

+
    +
  • The PushManager does not contain a valid UAID
  • +
  • An error occurred sending an channel list retrieval request to the autopush server
  • +
  • An error occurred accessing the PushManager’s persisted storage
  • +
+
source

pub fn decrypt( + &self, + payload: HashMap<String, String> +) -> ApiResult<DecryptResponse>

Decrypts a raw push message.

+

This accepts the content of a Push Message (from websocket or via Native Push systems).

+
Arguments:
+
    +
  • channel_id - the ChannelID (included in the envelope of the message)
  • +
  • body - The encrypted body of the message
  • +
  • encoding - The Content Encoding “enc” field of the message (defaults to “aes128gcm”)
  • +
  • salt - The “salt” field (if present in the raw message, defaults to “”)
  • +
  • dh - The “dh” field (if present in the raw message, defaults to “”)
  • +
+
Returns
+

Decrypted message body as a signed byte array +they byte array is signed to allow consumers (Kotlin only at the time of this documentation) +to work easily with the message. (They can directly call .toByteArray on it)

+
Errors
+

Returns an error in the following cases:

+
    +
  • The PushManager does not contain a valid UAID
  • +
  • There are no records associated with the UAID the PushManager contains
  • +
  • An error occurred while decrypting the message
  • +
  • An error occurred accessing the PushManager’s persisted storage
  • +
+

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/push/struct.PushSubscriptionChanged.html b/book/rust-docs/push/struct.PushSubscriptionChanged.html new file mode 100644 index 0000000000..9b9262ecd0 --- /dev/null +++ b/book/rust-docs/push/struct.PushSubscriptionChanged.html @@ -0,0 +1,19 @@ +PushSubscriptionChanged in push - Rust
pub struct PushSubscriptionChanged {
+    pub channel_id: String,
+    pub scope: String,
+}
Expand description

An dictionary describing the push subscription that changed, the caller +will receive a list of PushSubscriptionChanged when calling +PushManager::verify_connection, one entry for each channel that the +caller should resubscribe to

+

Fields§

§channel_id: String§scope: String

Trait Implementations§

source§

impl Clone for PushSubscriptionChanged

source§

fn clone(&self) -> PushSubscriptionChanged

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for PushSubscriptionChanged

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/push/struct.SubscriptionInfo.html b/book/rust-docs/push/struct.SubscriptionInfo.html new file mode 100644 index 0000000000..81bdb5d1aa --- /dev/null +++ b/book/rust-docs/push/struct.SubscriptionInfo.html @@ -0,0 +1,24 @@ +SubscriptionInfo in push - Rust

Struct push::SubscriptionInfo

source ·
pub struct SubscriptionInfo {
+    pub endpoint: String,
+    pub keys: KeyInfo,
+}
Expand description

Subscription Information, the endpoint to send push messages to and +the key information that can be used to encrypt payloads

+

Fields§

§endpoint: String§keys: KeyInfo

Trait Implementations§

source§

impl Clone for SubscriptionInfo

source§

fn clone(&self) -> SubscriptionInfo

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for SubscriptionInfo

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Ord for SubscriptionInfo

source§

fn cmp(&self, other: &SubscriptionInfo) -> Ordering

This method returns an Ordering between self and other. Read more
1.21.0 · source§

fn max(self, other: Self) -> Selfwhere + Self: Sized,

Compares and returns the maximum of two values. Read more
1.21.0 · source§

fn min(self, other: Self) -> Selfwhere + Self: Sized,

Compares and returns the minimum of two values. Read more
1.50.0 · source§

fn clamp(self, min: Self, max: Self) -> Selfwhere + Self: Sized + PartialOrd<Self>,

Restrict a value to a certain interval. Read more
source§

impl PartialEq<SubscriptionInfo> for SubscriptionInfo

source§

fn eq(&self, other: &SubscriptionInfo) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl PartialOrd<SubscriptionInfo> for SubscriptionInfo

source§

fn partial_cmp(&self, other: &SubscriptionInfo) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · source§

fn lt(&self, other: &Rhs) -> bool

This method tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · source§

fn le(&self, other: &Rhs) -> bool

This method tests less than or equal to (for self and other) and is used by the <= +operator. Read more
1.0.0 · source§

fn gt(&self, other: &Rhs) -> bool

This method tests greater than (for self and other) and is used by the > operator. Read more
1.0.0 · source§

fn ge(&self, other: &Rhs) -> bool

This method tests greater than or equal to (for self and other) and is used by the >= +operator. Read more
source§

impl Eq for SubscriptionInfo

source§

impl StructuralEq for SubscriptionInfo

source§

impl StructuralPartialEq for SubscriptionInfo

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/push/struct.SubscriptionResponse.html b/book/rust-docs/push/struct.SubscriptionResponse.html new file mode 100644 index 0000000000..6e935fd12e --- /dev/null +++ b/book/rust-docs/push/struct.SubscriptionResponse.html @@ -0,0 +1,23 @@ +SubscriptionResponse in push - Rust
pub struct SubscriptionResponse {
+    pub channel_id: String,
+    pub subscription_info: SubscriptionInfo,
+}
Expand description

The subscription response object returned from PushManager::subscribe

+

Fields§

§channel_id: String§subscription_info: SubscriptionInfo

Trait Implementations§

source§

impl Clone for SubscriptionResponse

source§

fn clone(&self) -> SubscriptionResponse

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for SubscriptionResponse

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Ord for SubscriptionResponse

source§

fn cmp(&self, other: &SubscriptionResponse) -> Ordering

This method returns an Ordering between self and other. Read more
1.21.0 · source§

fn max(self, other: Self) -> Selfwhere + Self: Sized,

Compares and returns the maximum of two values. Read more
1.21.0 · source§

fn min(self, other: Self) -> Selfwhere + Self: Sized,

Compares and returns the minimum of two values. Read more
1.50.0 · source§

fn clamp(self, min: Self, max: Self) -> Selfwhere + Self: Sized + PartialOrd<Self>,

Restrict a value to a certain interval. Read more
source§

impl PartialEq<SubscriptionResponse> for SubscriptionResponse

source§

fn eq(&self, other: &SubscriptionResponse) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl PartialOrd<SubscriptionResponse> for SubscriptionResponse

source§

fn partial_cmp(&self, other: &SubscriptionResponse) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · source§

fn lt(&self, other: &Rhs) -> bool

This method tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · source§

fn le(&self, other: &Rhs) -> bool

This method tests less than or equal to (for self and other) and is used by the <= +operator. Read more
1.0.0 · source§

fn gt(&self, other: &Rhs) -> bool

This method tests greater than (for self and other) and is used by the > operator. Read more
1.0.0 · source§

fn ge(&self, other: &Rhs) -> bool

This method tests greater than or equal to (for self and other) and is used by the >= +operator. Read more
source§

impl Eq for SubscriptionResponse

source§

impl StructuralEq for SubscriptionResponse

source§

impl StructuralPartialEq for SubscriptionResponse

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/push/type.ApiResult.html b/book/rust-docs/push/type.ApiResult.html new file mode 100644 index 0000000000..8720650731 --- /dev/null +++ b/book/rust-docs/push/type.ApiResult.html @@ -0,0 +1 @@ +ApiResult in push - Rust

Type Definition push::ApiResult

source ·
pub type ApiResult<T, E = PushApiError> = Result<T, E>;
\ No newline at end of file diff --git a/book/rust-docs/rc_crypto/aead/aes_cbc/static.LEGACY_SYNC_AES_256_CBC_HMAC_SHA256.html b/book/rust-docs/rc_crypto/aead/aes_cbc/static.LEGACY_SYNC_AES_256_CBC_HMAC_SHA256.html new file mode 100644 index 0000000000..28fa451c63 --- /dev/null +++ b/book/rust-docs/rc_crypto/aead/aes_cbc/static.LEGACY_SYNC_AES_256_CBC_HMAC_SHA256.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../rc_crypto/aead/static.LEGACY_SYNC_AES_256_CBC_HMAC_SHA256.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/rc_crypto/aead/aes_gcm/static.AES_128_GCM.html b/book/rust-docs/rc_crypto/aead/aes_gcm/static.AES_128_GCM.html new file mode 100644 index 0000000000..6eb0a20aae --- /dev/null +++ b/book/rust-docs/rc_crypto/aead/aes_gcm/static.AES_128_GCM.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../rc_crypto/aead/static.AES_128_GCM.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/rc_crypto/aead/aes_gcm/static.AES_256_GCM.html b/book/rust-docs/rc_crypto/aead/aes_gcm/static.AES_256_GCM.html new file mode 100644 index 0000000000..6f01172b41 --- /dev/null +++ b/book/rust-docs/rc_crypto/aead/aes_gcm/static.AES_256_GCM.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../rc_crypto/aead/static.AES_256_GCM.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/rc_crypto/aead/fn.open.html b/book/rust-docs/rc_crypto/aead/fn.open.html new file mode 100644 index 0000000000..7b3a85f282 --- /dev/null +++ b/book/rust-docs/rc_crypto/aead/fn.open.html @@ -0,0 +1,6 @@ +open in rc_crypto::aead - Rust

Function rc_crypto::aead::open

source ·
pub fn open(
+    key: &OpeningKey,
+    nonce: Nonce,
+    aad: Aad<'_>,
+    ciphertext_and_tag: &[u8]
+) -> Result<Vec<u8>>
\ No newline at end of file diff --git a/book/rust-docs/rc_crypto/aead/fn.seal.html b/book/rust-docs/rc_crypto/aead/fn.seal.html new file mode 100644 index 0000000000..eea688e71b --- /dev/null +++ b/book/rust-docs/rc_crypto/aead/fn.seal.html @@ -0,0 +1,6 @@ +seal in rc_crypto::aead - Rust

Function rc_crypto::aead::seal

source ·
pub fn seal(
+    key: &SealingKey,
+    nonce: Nonce,
+    aad: Aad<'_>,
+    plaintext: &[u8]
+) -> Result<Vec<u8>>
\ No newline at end of file diff --git a/book/rust-docs/rc_crypto/aead/index.html b/book/rust-docs/rc_crypto/aead/index.html new file mode 100644 index 0000000000..7398f289cb --- /dev/null +++ b/book/rust-docs/rc_crypto/aead/index.html @@ -0,0 +1,14 @@ +rc_crypto::aead - Rust

Module rc_crypto::aead

source ·
Expand description

This crate provides all the cryptographic primitives required by +this workspace, backed by the NSS library. +The exposed API is pretty much the same as the ring crate.

+

Structs

  • The additional authenticated data (AAD) for an opening or sealing +operation. This data is authenticated but is not encrypted. +This is a type-safe wrapper around the raw bytes designed to encourage +correct use of the API.
  • The nonce for an opening or sealing operation. +This is a type-safe wrapper around the raw bytes designed to encourage +correct use of the API.

Statics

  • AES-128 in GCM mode with 128-bit tags and 96 bit nonces.
  • AES-256 in GCM mode with 128-bit tags and 96 bit nonces.
  • AES-256 in CBC mode with HMAC-SHA256 tags and 128 bit nonces. +This is a Sync 1.5 specific encryption scheme, do not use for new +applications, there are better options out there nowadays. +Important note: The HMAC tag verification is done against the +base64 representation of the ciphertext. +More details here: https://mozilla-services.readthedocs.io/en/latest/sync/storageformat5.html#record-encryption

Functions

\ No newline at end of file diff --git a/book/rust-docs/rc_crypto/aead/sidebar-items.js b/book/rust-docs/rc_crypto/aead/sidebar-items.js new file mode 100644 index 0000000000..40674fdf97 --- /dev/null +++ b/book/rust-docs/rc_crypto/aead/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["open","seal"],"static":["AES_128_GCM","AES_256_GCM","LEGACY_SYNC_AES_256_CBC_HMAC_SHA256"],"struct":["Aad","Algorithm","Nonce","OpeningKey","SealingKey"]}; \ No newline at end of file diff --git a/book/rust-docs/rc_crypto/aead/static.AES_128_GCM.html b/book/rust-docs/rc_crypto/aead/static.AES_128_GCM.html new file mode 100644 index 0000000000..b9e3011964 --- /dev/null +++ b/book/rust-docs/rc_crypto/aead/static.AES_128_GCM.html @@ -0,0 +1,2 @@ +AES_128_GCM in rc_crypto::aead - Rust

Static rc_crypto::aead::AES_128_GCM

source ·
pub static AES_128_GCM: Algorithm
Expand description

AES-128 in GCM mode with 128-bit tags and 96 bit nonces.

+
\ No newline at end of file diff --git a/book/rust-docs/rc_crypto/aead/static.AES_256_GCM.html b/book/rust-docs/rc_crypto/aead/static.AES_256_GCM.html new file mode 100644 index 0000000000..a677e6ed41 --- /dev/null +++ b/book/rust-docs/rc_crypto/aead/static.AES_256_GCM.html @@ -0,0 +1,2 @@ +AES_256_GCM in rc_crypto::aead - Rust

Static rc_crypto::aead::AES_256_GCM

source ·
pub static AES_256_GCM: Algorithm
Expand description

AES-256 in GCM mode with 128-bit tags and 96 bit nonces.

+
\ No newline at end of file diff --git a/book/rust-docs/rc_crypto/aead/static.LEGACY_SYNC_AES_256_CBC_HMAC_SHA256.html b/book/rust-docs/rc_crypto/aead/static.LEGACY_SYNC_AES_256_CBC_HMAC_SHA256.html new file mode 100644 index 0000000000..48e99c195f --- /dev/null +++ b/book/rust-docs/rc_crypto/aead/static.LEGACY_SYNC_AES_256_CBC_HMAC_SHA256.html @@ -0,0 +1,7 @@ +LEGACY_SYNC_AES_256_CBC_HMAC_SHA256 in rc_crypto::aead - Rust
pub static LEGACY_SYNC_AES_256_CBC_HMAC_SHA256: Algorithm
Expand description

AES-256 in CBC mode with HMAC-SHA256 tags and 128 bit nonces. +This is a Sync 1.5 specific encryption scheme, do not use for new +applications, there are better options out there nowadays. +Important note: The HMAC tag verification is done against the +base64 representation of the ciphertext. +More details here: https://mozilla-services.readthedocs.io/en/latest/sync/storageformat5.html#record-encryption

+
\ No newline at end of file diff --git a/book/rust-docs/rc_crypto/aead/struct.Aad.html b/book/rust-docs/rc_crypto/aead/struct.Aad.html new file mode 100644 index 0000000000..2dc736076d --- /dev/null +++ b/book/rust-docs/rc_crypto/aead/struct.Aad.html @@ -0,0 +1,17 @@ +Aad in rc_crypto::aead - Rust

Struct rc_crypto::aead::Aad

source ·
#[repr(transparent)]
pub struct Aad<'a>(_);
Expand description

The additional authenticated data (AAD) for an opening or sealing +operation. This data is authenticated but is not encrypted. +This is a type-safe wrapper around the raw bytes designed to encourage +correct use of the API.

+

Implementations§

source§

impl<'a> Aad<'a>

source

pub fn from(aad: &'a [u8]) -> Self

Construct the Aad by borrowing a contiguous sequence of bytes.

+
source§

impl Aad<'static>

source

pub fn empty() -> Self

Construct an empty Aad.

+

Auto Trait Implementations§

§

impl<'a> RefUnwindSafe for Aad<'a>

§

impl<'a> Send for Aad<'a>

§

impl<'a> Sync for Aad<'a>

§

impl<'a> Unpin for Aad<'a>

§

impl<'a> UnwindSafe for Aad<'a>

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/rc_crypto/aead/struct.Algorithm.html b/book/rust-docs/rc_crypto/aead/struct.Algorithm.html new file mode 100644 index 0000000000..f222b1a9d1 --- /dev/null +++ b/book/rust-docs/rc_crypto/aead/struct.Algorithm.html @@ -0,0 +1,14 @@ +Algorithm in rc_crypto::aead - Rust

Struct rc_crypto::aead::Algorithm

source ·
pub struct Algorithm { /* private fields */ }

Implementations§

source§

impl Algorithm

source

pub const fn key_len(&self) -> usize

The length of the key.

+
source

pub const fn tag_len(&self) -> usize

The length of a tag.

+
source

pub const fn nonce_len(&self) -> usize

The length of the nonces.

+

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/rc_crypto/aead/struct.Nonce.html b/book/rust-docs/rc_crypto/aead/struct.Nonce.html new file mode 100644 index 0000000000..5ed785e940 --- /dev/null +++ b/book/rust-docs/rc_crypto/aead/struct.Nonce.html @@ -0,0 +1,17 @@ +Nonce in rc_crypto::aead - Rust

Struct rc_crypto::aead::Nonce

source ·
pub struct Nonce(_);
Expand description

The nonce for an opening or sealing operation. +This is a type-safe wrapper around the raw bytes designed to encourage +correct use of the API.

+

Implementations§

source§

impl Nonce

source

pub fn try_assume_unique_for_key( + algorithm: &'static Algorithm, + value: &[u8] +) -> Result<Self>

Auto Trait Implementations§

§

impl RefUnwindSafe for Nonce

§

impl Send for Nonce

§

impl Sync for Nonce

§

impl Unpin for Nonce

§

impl UnwindSafe for Nonce

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/rc_crypto/aead/struct.OpeningKey.html b/book/rust-docs/rc_crypto/aead/struct.OpeningKey.html new file mode 100644 index 0000000000..5f74bbc2cb --- /dev/null +++ b/book/rust-docs/rc_crypto/aead/struct.OpeningKey.html @@ -0,0 +1,14 @@ +OpeningKey in rc_crypto::aead - Rust

Struct rc_crypto::aead::OpeningKey

source ·
pub struct OpeningKey { /* private fields */ }

Implementations§

source§

impl OpeningKey

source

pub fn new(algorithm: &'static Algorithm, key_bytes: &[u8]) -> Result<Self>

Create a new opening key.

+

key_bytes must be exactly algorithm.key_len bytes long.

+
source

pub fn algorithm(&self) -> &'static Algorithm

The key’s AEAD algorithm.

+

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/rc_crypto/aead/struct.SealingKey.html b/book/rust-docs/rc_crypto/aead/struct.SealingKey.html new file mode 100644 index 0000000000..cc0619d618 --- /dev/null +++ b/book/rust-docs/rc_crypto/aead/struct.SealingKey.html @@ -0,0 +1,14 @@ +SealingKey in rc_crypto::aead - Rust

Struct rc_crypto::aead::SealingKey

source ·
pub struct SealingKey { /* private fields */ }

Implementations§

source§

impl SealingKey

source

pub fn new(algorithm: &'static Algorithm, key_bytes: &[u8]) -> Result<Self>

Create a new sealing key.

+

key_bytes must be exactly algorithm.key_len bytes long.

+
source

pub fn algorithm(&self) -> &'static Algorithm

The key’s AEAD algorithm.

+

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/rc_crypto/agreement/enum.Curve.html b/book/rust-docs/rc_crypto/agreement/enum.Curve.html new file mode 100644 index 0000000000..59830479b2 --- /dev/null +++ b/book/rust-docs/rc_crypto/agreement/enum.Curve.html @@ -0,0 +1,25 @@ +Curve in rc_crypto::agreement - Rust
#[repr(u8)]
pub enum Curve { + P256, + P384, +}

Variants§

§

P256

§

P384

Implementations§

§

impl Curve

pub fn get_field_len(&self) -> u32

Trait Implementations§

§

impl Clone for Curve

§

fn clone(&self) -> Curve

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
§

impl Debug for Curve

§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
§

impl<'de> Deserialize<'de> for Curve

§

fn deserialize<__D>( + __deserializer: __D +) -> Result<Curve, <__D as Deserializer<'de>>::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
§

impl PartialEq<Curve> for Curve

§

fn eq(&self, other: &Curve) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
§

impl Serialize for Curve

§

fn serialize<__S>( + &self, + __serializer: __S +) -> Result<<__S as Serializer>::Ok, <__S as Serializer>::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more
§

impl Copy for Curve

§

impl Eq for Curve

§

impl StructuralEq for Curve

§

impl StructuralPartialEq for Curve

Auto Trait Implementations§

§

impl RefUnwindSafe for Curve

§

impl Send for Curve

§

impl Sync for Curve

§

impl Unpin for Curve

§

impl UnwindSafe for Curve

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/rc_crypto/agreement/index.html b/book/rust-docs/rc_crypto/agreement/index.html new file mode 100644 index 0000000000..cc074fefbc --- /dev/null +++ b/book/rust-docs/rc_crypto/agreement/index.html @@ -0,0 +1 @@ +rc_crypto::agreement - Rust

Module rc_crypto::agreement

source ·

Structs

Enums

Statics

Traits

  • How many times the key may be used.

Type Definitions

\ No newline at end of file diff --git a/book/rust-docs/rc_crypto/agreement/sidebar-items.js b/book/rust-docs/rc_crypto/agreement/sidebar-items.js new file mode 100644 index 0000000000..3770c9bff8 --- /dev/null +++ b/book/rust-docs/rc_crypto/agreement/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["Curve"],"static":["ECDH_P256","ECDH_P384"],"struct":["Algorithm","EcKey","Ephemeral","InputKeyMaterial","KeyPair","PrivateKey","PublicKey","Static","UnparsedPublicKey"],"trait":["Lifetime"],"type":["EphemeralKeyPair"]}; \ No newline at end of file diff --git a/book/rust-docs/rc_crypto/agreement/static.ECDH_P256.html b/book/rust-docs/rc_crypto/agreement/static.ECDH_P256.html new file mode 100644 index 0000000000..c762cc364b --- /dev/null +++ b/book/rust-docs/rc_crypto/agreement/static.ECDH_P256.html @@ -0,0 +1 @@ +ECDH_P256 in rc_crypto::agreement - Rust
pub static ECDH_P256: Algorithm
\ No newline at end of file diff --git a/book/rust-docs/rc_crypto/agreement/static.ECDH_P384.html b/book/rust-docs/rc_crypto/agreement/static.ECDH_P384.html new file mode 100644 index 0000000000..110d05ea32 --- /dev/null +++ b/book/rust-docs/rc_crypto/agreement/static.ECDH_P384.html @@ -0,0 +1 @@ +ECDH_P384 in rc_crypto::agreement - Rust
pub static ECDH_P384: Algorithm
\ No newline at end of file diff --git a/book/rust-docs/rc_crypto/agreement/struct.Algorithm.html b/book/rust-docs/rc_crypto/agreement/struct.Algorithm.html new file mode 100644 index 0000000000..25489054ca --- /dev/null +++ b/book/rust-docs/rc_crypto/agreement/struct.Algorithm.html @@ -0,0 +1,14 @@ +Algorithm in rc_crypto::agreement - Rust
pub struct Algorithm { /* private fields */ }
Expand description

A key agreement algorithm.

+

Trait Implementations§

source§

impl PartialEq<Algorithm> for Algorithm

source§

fn eq(&self, other: &Algorithm) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Eq for Algorithm

source§

impl StructuralEq for Algorithm

source§

impl StructuralPartialEq for Algorithm

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/rc_crypto/agreement/struct.EcKey.html b/book/rust-docs/rc_crypto/agreement/struct.EcKey.html new file mode 100644 index 0000000000..496955dd17 --- /dev/null +++ b/book/rust-docs/rc_crypto/agreement/struct.EcKey.html @@ -0,0 +1,25 @@ +EcKey in rc_crypto::agreement - Rust

Struct rc_crypto::agreement::EcKey

pub struct EcKey { /* private fields */ }

Implementations§

§

impl EcKey

pub fn new(curve: Curve, private_key: &[u8], public_key: &[u8]) -> EcKey

pub fn from_coordinates( + curve: Curve, + d: &[u8], + x: &[u8], + y: &[u8] +) -> Result<EcKey, Error>

pub fn curve(&self) -> Curve

pub fn public_key(&self) -> &[u8]

pub fn private_key(&self) -> &[u8]

Trait Implementations§

§

impl Clone for EcKey

§

fn clone(&self) -> EcKey

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
§

impl Debug for EcKey

§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
§

impl<'de> Deserialize<'de> for EcKey

§

fn deserialize<__D>( + __deserializer: __D +) -> Result<EcKey, <__D as Deserializer<'de>>::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
§

impl Serialize for EcKey

§

fn serialize<__S>( + &self, + __serializer: __S +) -> Result<<__S as Serializer>::Ok, <__S as Serializer>::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more

Auto Trait Implementations§

§

impl RefUnwindSafe for EcKey

§

impl Send for EcKey

§

impl Sync for EcKey

§

impl Unpin for EcKey

§

impl UnwindSafe for EcKey

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/rc_crypto/agreement/struct.Ephemeral.html b/book/rust-docs/rc_crypto/agreement/struct.Ephemeral.html new file mode 100644 index 0000000000..f561b83048 --- /dev/null +++ b/book/rust-docs/rc_crypto/agreement/struct.Ephemeral.html @@ -0,0 +1,12 @@ +Ephemeral in rc_crypto::agreement - Rust
pub struct Ephemeral {}
Expand description

The key may be used at most once.

+

Trait Implementations§

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/rc_crypto/agreement/struct.InputKeyMaterial.html b/book/rust-docs/rc_crypto/agreement/struct.InputKeyMaterial.html new file mode 100644 index 0000000000..4673ae8655 --- /dev/null +++ b/book/rust-docs/rc_crypto/agreement/struct.InputKeyMaterial.html @@ -0,0 +1,16 @@ +InputKeyMaterial in rc_crypto::agreement - Rust
pub struct InputKeyMaterial { /* private fields */ }
Expand description

The result of a key agreement operation, to be fed into a KDF.

+

Implementations§

source§

impl InputKeyMaterial

source

pub fn derive<F, R>(self, kdf: F) -> Rwhere + F: FnOnce(&[u8]) -> R,

Calls kdf with the raw key material and then returns what kdf +returns, consuming Self so that the key material can only be used +once.

+

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/rc_crypto/agreement/struct.KeyPair.html b/book/rust-docs/rc_crypto/agreement/struct.KeyPair.html new file mode 100644 index 0000000000..53ff784432 --- /dev/null +++ b/book/rust-docs/rc_crypto/agreement/struct.KeyPair.html @@ -0,0 +1,20 @@ +KeyPair in rc_crypto::agreement - Rust
pub struct KeyPair<U: Lifetime> { /* private fields */ }
Expand description

A key pair for key agreement.

+

Implementations§

source§

impl<U: Lifetime> KeyPair<U>

source

pub fn generate(alg: &'static Algorithm) -> Result<Self>

Generate a new key pair for the given algorithm.

+
source

pub fn from_private_key(private_key: PrivateKey<U>) -> Result<Self>

source

pub fn private_key(&self) -> &PrivateKey<U>

The private key.

+
source

pub fn public_key(&self) -> &PublicKey

The public key.

+
source

pub fn split(self) -> (PrivateKey<U>, PublicKey)

Split the key pair apart.

+
source§

impl KeyPair<Static>

source

pub fn from(private_key: PrivateKey<Static>) -> Result<Self>

Auto Trait Implementations§

§

impl<U> RefUnwindSafe for KeyPair<U>where + U: RefUnwindSafe,

§

impl<U> Send for KeyPair<U>where + U: Send,

§

impl<U> !Sync for KeyPair<U>

§

impl<U> Unpin for KeyPair<U>where + U: Unpin,

§

impl<U> UnwindSafe for KeyPair<U>where + U: UnwindSafe,

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/rc_crypto/agreement/struct.PrivateKey.html b/book/rust-docs/rc_crypto/agreement/struct.PrivateKey.html new file mode 100644 index 0000000000..a13f3839a1 --- /dev/null +++ b/book/rust-docs/rc_crypto/agreement/struct.PrivateKey.html @@ -0,0 +1,33 @@ +PrivateKey in rc_crypto::agreement - Rust
pub struct PrivateKey<U: Lifetime> { /* private fields */ }
Expand description

A private key for key agreement.

+

Implementations§

source§

impl<U: Lifetime> PrivateKey<U>

source

pub fn algorithm(&self) -> &'static Algorithm

source

pub fn compute_public_key(&self) -> Result<PublicKey>

source

pub fn agree( + self, + peer_public_key: &UnparsedPublicKey<'_> +) -> Result<InputKeyMaterial>

Ephemeral agreement. +This consumes self, ensuring that the private key can +only be used for a single agreement operation.

+
source§

impl PrivateKey<Static>

source

pub fn agree_static( + &self, + peer_public_key: &UnparsedPublicKey<'_> +) -> Result<InputKeyMaterial>

Static agreement. +This borrows self, allowing the private key to +be used for a multiple agreement operations.

+
source

pub fn import(ec_key: &EcKey) -> Result<Self>

source

pub fn export(&self) -> Result<EcKey>

source

pub fn _tests_only_dangerously_convert_to_ephemeral( + self +) -> PrivateKey<Ephemeral>

The whole point of having Ephemeral and Static lifetimes is to use the type +system to avoid re-using the same ephemeral key. However for tests we might need +to create a “static” ephemeral key.

+

Auto Trait Implementations§

§

impl<U> RefUnwindSafe for PrivateKey<U>where + U: RefUnwindSafe,

§

impl<U> Send for PrivateKey<U>where + U: Send,

§

impl<U> !Sync for PrivateKey<U>

§

impl<U> Unpin for PrivateKey<U>where + U: Unpin,

§

impl<U> UnwindSafe for PrivateKey<U>where + U: UnwindSafe,

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/rc_crypto/agreement/struct.PublicKey.html b/book/rust-docs/rc_crypto/agreement/struct.PublicKey.html new file mode 100644 index 0000000000..9d3bc3b216 --- /dev/null +++ b/book/rust-docs/rc_crypto/agreement/struct.PublicKey.html @@ -0,0 +1,12 @@ +PublicKey in rc_crypto::agreement - Rust
pub struct PublicKey { /* private fields */ }
Expand description

A public key for key agreement.

+

Implementations§

source§

impl PublicKey

source

pub fn to_bytes(&self) -> Result<Vec<u8>>

source

pub fn algorithm(&self) -> &'static Algorithm

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/rc_crypto/agreement/struct.Static.html b/book/rust-docs/rc_crypto/agreement/struct.Static.html new file mode 100644 index 0000000000..a3ae33c6ad --- /dev/null +++ b/book/rust-docs/rc_crypto/agreement/struct.Static.html @@ -0,0 +1,12 @@ +Static in rc_crypto::agreement - Rust

Struct rc_crypto::agreement::Static

source ·
pub struct Static {}
Expand description

The key may be used more than once.

+

Trait Implementations§

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/rc_crypto/agreement/struct.UnparsedPublicKey.html b/book/rust-docs/rc_crypto/agreement/struct.UnparsedPublicKey.html new file mode 100644 index 0000000000..015ff20478 --- /dev/null +++ b/book/rust-docs/rc_crypto/agreement/struct.UnparsedPublicKey.html @@ -0,0 +1,12 @@ +UnparsedPublicKey in rc_crypto::agreement - Rust
pub struct UnparsedPublicKey<'a> { /* private fields */ }
Expand description

An unparsed public key for key agreement.

+

Implementations§

source§

impl<'a> UnparsedPublicKey<'a>

source

pub fn new(algorithm: &'static Algorithm, bytes: &'a [u8]) -> Self

source

pub fn algorithm(&self) -> &'static Algorithm

source

pub fn bytes(&self) -> &'a [u8]

Auto Trait Implementations§

§

impl<'a> RefUnwindSafe for UnparsedPublicKey<'a>

§

impl<'a> Send for UnparsedPublicKey<'a>

§

impl<'a> Sync for UnparsedPublicKey<'a>

§

impl<'a> Unpin for UnparsedPublicKey<'a>

§

impl<'a> UnwindSafe for UnparsedPublicKey<'a>

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/rc_crypto/agreement/trait.Lifetime.html b/book/rust-docs/rc_crypto/agreement/trait.Lifetime.html new file mode 100644 index 0000000000..3447c2f10e --- /dev/null +++ b/book/rust-docs/rc_crypto/agreement/trait.Lifetime.html @@ -0,0 +1,2 @@ +Lifetime in rc_crypto::agreement - Rust
pub trait Lifetime { }
Expand description

How many times the key may be used.

+

Implementors§

\ No newline at end of file diff --git a/book/rust-docs/rc_crypto/agreement/type.EphemeralKeyPair.html b/book/rust-docs/rc_crypto/agreement/type.EphemeralKeyPair.html new file mode 100644 index 0000000000..61166df904 --- /dev/null +++ b/book/rust-docs/rc_crypto/agreement/type.EphemeralKeyPair.html @@ -0,0 +1 @@ +EphemeralKeyPair in rc_crypto::agreement - Rust

Type Definition rc_crypto::agreement::EphemeralKeyPair

source ·
pub type EphemeralKeyPair = KeyPair<Ephemeral>;
\ No newline at end of file diff --git a/book/rust-docs/rc_crypto/all.html b/book/rust-docs/rc_crypto/all.html new file mode 100644 index 0000000000..cbb82568df --- /dev/null +++ b/book/rust-docs/rc_crypto/all.html @@ -0,0 +1 @@ +List of all items in this crate
\ No newline at end of file diff --git a/book/rust-docs/rc_crypto/constant_time/fn.verify_slices_are_equal.html b/book/rust-docs/rc_crypto/constant_time/fn.verify_slices_are_equal.html new file mode 100644 index 0000000000..4b8662beb4 --- /dev/null +++ b/book/rust-docs/rc_crypto/constant_time/fn.verify_slices_are_equal.html @@ -0,0 +1,5 @@ +verify_slices_are_equal in rc_crypto::constant_time - Rust
pub fn verify_slices_are_equal(a: &[u8], b: &[u8]) -> Result<()>
Expand description

Returns Ok(()) if a == b and Error otherwise. +The comparison of a and b is done in constant time with respect to the +contents of each, but NOT in constant time with respect to the lengths of +a and b.

+
\ No newline at end of file diff --git a/book/rust-docs/rc_crypto/constant_time/index.html b/book/rust-docs/rc_crypto/constant_time/index.html new file mode 100644 index 0000000000..0f8aff6271 --- /dev/null +++ b/book/rust-docs/rc_crypto/constant_time/index.html @@ -0,0 +1,4 @@ +rc_crypto::constant_time - Rust

Functions

  • Returns Ok(()) if a == b and Error otherwise. +The comparison of a and b is done in constant time with respect to the +contents of each, but NOT in constant time with respect to the lengths of +a and b.
\ No newline at end of file diff --git a/book/rust-docs/rc_crypto/constant_time/sidebar-items.js b/book/rust-docs/rc_crypto/constant_time/sidebar-items.js new file mode 100644 index 0000000000..8ce464a05d --- /dev/null +++ b/book/rust-docs/rc_crypto/constant_time/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["verify_slices_are_equal"]}; \ No newline at end of file diff --git a/book/rust-docs/rc_crypto/contentsignature/fn.verify.html b/book/rust-docs/rc_crypto/contentsignature/fn.verify.html new file mode 100644 index 0000000000..0b97058cc6 --- /dev/null +++ b/book/rust-docs/rc_crypto/contentsignature/fn.verify.html @@ -0,0 +1,14 @@ +verify in rc_crypto::contentsignature - Rust
pub fn verify(
+    input: &[u8],
+    signature: &[u8],
+    pem_bytes: &[u8],
+    seconds_since_epoch: u64,
+    root_sha256_hash: &str,
+    hostname: &str
+) -> Result<()>
Expand description

Verify that the signature matches the input data.

+

The data must be prefixed with Content-Signature:\u{0}. +The signature must be provided as base 64 url-safe encoded. +The certificate chain, provided as PEM, must be valid at the provided current time. +The root certificate content must match the provided root hash, and the leaf +subject name must match the provided hostname.

+
\ No newline at end of file diff --git a/book/rust-docs/rc_crypto/contentsignature/index.html b/book/rust-docs/rc_crypto/contentsignature/index.html new file mode 100644 index 0000000000..0ec3795c7c --- /dev/null +++ b/book/rust-docs/rc_crypto/contentsignature/index.html @@ -0,0 +1 @@ +rc_crypto::contentsignature - Rust

Functions

  • Verify that the signature matches the input data.
\ No newline at end of file diff --git a/book/rust-docs/rc_crypto/contentsignature/sidebar-items.js b/book/rust-docs/rc_crypto/contentsignature/sidebar-items.js new file mode 100644 index 0000000000..6010651ab1 --- /dev/null +++ b/book/rust-docs/rc_crypto/contentsignature/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["verify"]}; \ No newline at end of file diff --git a/book/rust-docs/rc_crypto/digest/enum.Algorithm.html b/book/rust-docs/rc_crypto/digest/enum.Algorithm.html new file mode 100644 index 0000000000..66d86241b5 --- /dev/null +++ b/book/rust-docs/rc_crypto/digest/enum.Algorithm.html @@ -0,0 +1,15 @@ +Algorithm in rc_crypto::digest - Rust
#[repr(u8)]
pub enum Algorithm { + SHA256, + SHA384, +}

Variants§

§

SHA256

§

SHA384

Trait Implementations§

§

impl Clone for HashAlgorithm

§

fn clone(&self) -> HashAlgorithm

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
§

impl Debug for HashAlgorithm

§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
§

impl Copy for HashAlgorithm

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/rc_crypto/digest/fn.digest.html b/book/rust-docs/rc_crypto/digest/fn.digest.html new file mode 100644 index 0000000000..94a96ca076 --- /dev/null +++ b/book/rust-docs/rc_crypto/digest/fn.digest.html @@ -0,0 +1,2 @@ +digest in rc_crypto::digest - Rust

Function rc_crypto::digest::digest

source ·
pub fn digest(algorithm: &Algorithm, data: &[u8]) -> Result<Digest>
Expand description

Returns the digest of data using the given digest algorithm.

+
\ No newline at end of file diff --git a/book/rust-docs/rc_crypto/digest/index.html b/book/rust-docs/rc_crypto/digest/index.html new file mode 100644 index 0000000000..f505c31f08 --- /dev/null +++ b/book/rust-docs/rc_crypto/digest/index.html @@ -0,0 +1 @@ +rc_crypto::digest - Rust

Module rc_crypto::digest

source ·

Re-exports

Structs

  • A calculated digest value.

Enums

Functions

  • Returns the digest of data using the given digest algorithm.
\ No newline at end of file diff --git a/book/rust-docs/rc_crypto/digest/sidebar-items.js b/book/rust-docs/rc_crypto/digest/sidebar-items.js new file mode 100644 index 0000000000..d863034a54 --- /dev/null +++ b/book/rust-docs/rc_crypto/digest/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["Algorithm"],"fn":["digest"],"struct":["Digest"]}; \ No newline at end of file diff --git a/book/rust-docs/rc_crypto/digest/struct.Digest.html b/book/rust-docs/rc_crypto/digest/struct.Digest.html new file mode 100644 index 0000000000..7f8e060934 --- /dev/null +++ b/book/rust-docs/rc_crypto/digest/struct.Digest.html @@ -0,0 +1,19 @@ +Digest in rc_crypto::digest - Rust

Struct rc_crypto::digest::Digest

source ·
pub struct Digest { /* private fields */ }
Expand description

A calculated digest value.

+

Implementations§

Trait Implementations§

source§

impl AsRef<[u8]> for Digest

source§

fn as_ref(&self) -> &[u8]

Converts this type into a shared reference of the (usually inferred) input type.
source§

impl Clone for Digest

source§

fn clone(&self) -> Digest

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
§

impl<Ctx, T> MeasureWith<Ctx> for Twhere + T: AsRef<[u8]>,

§

fn measure_with(&self, _ctx: &Ctx) -> usize

How large is Self, given the ctx?
source§

impl<T> ToHex for Twhere + T: AsRef<[u8]>,

source§

fn encode_hex<U>(&self) -> Uwhere + U: FromIterator<char>,

Encode the hex strict representing self into the result. Lower case +letters are used (e.g. f9b4ca)
source§

fn encode_hex_upper<U>(&self) -> Uwhere + U: FromIterator<char>,

Encode the hex strict representing self into the result. Upper case +letters are used (e.g. F9B4CA)
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/rc_crypto/ece_crypto/index.html b/book/rust-docs/rc_crypto/ece_crypto/index.html new file mode 100644 index 0000000000..0e892ea1c1 --- /dev/null +++ b/book/rust-docs/rc_crypto/ece_crypto/index.html @@ -0,0 +1 @@ +rc_crypto::ece_crypto - Rust
\ No newline at end of file diff --git a/book/rust-docs/rc_crypto/ece_crypto/sidebar-items.js b/book/rust-docs/rc_crypto/ece_crypto/sidebar-items.js new file mode 100644 index 0000000000..c7f9cffe51 --- /dev/null +++ b/book/rust-docs/rc_crypto/ece_crypto/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"struct":["RcCryptoLocalKeyPair","RcCryptoRemotePublicKey"]}; \ No newline at end of file diff --git a/book/rust-docs/rc_crypto/ece_crypto/struct.RcCryptoLocalKeyPair.html b/book/rust-docs/rc_crypto/ece_crypto/struct.RcCryptoLocalKeyPair.html new file mode 100644 index 0000000000..80afb6e3d6 --- /dev/null +++ b/book/rust-docs/rc_crypto/ece_crypto/struct.RcCryptoLocalKeyPair.html @@ -0,0 +1,12 @@ +RcCryptoLocalKeyPair in rc_crypto::ece_crypto - Rust
pub struct RcCryptoLocalKeyPair { /* private fields */ }

Implementations§

source§

impl RcCryptoLocalKeyPair

source

pub fn from_raw_components(components: &EcKeyComponents) -> Result<Self, Error>

source

pub fn generate_random() -> Result<Self, Error>

Trait Implementations§

source§

impl LocalKeyPair for RcCryptoLocalKeyPair

source§

fn raw_components(&self) -> Result<EcKeyComponents, Error>

Export the raw components of the keypair.
source§

fn pub_as_raw(&self) -> Result<Vec<u8>, Error>

Export the public key component in the +binary uncompressed point representation.
source§

fn as_any(&self) -> &dyn Any

For downcasting purposes.
source§

impl Sync for RcCryptoLocalKeyPair

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/rc_crypto/ece_crypto/struct.RcCryptoRemotePublicKey.html b/book/rust-docs/rc_crypto/ece_crypto/struct.RcCryptoRemotePublicKey.html new file mode 100644 index 0000000000..066dc9dd47 --- /dev/null +++ b/book/rust-docs/rc_crypto/ece_crypto/struct.RcCryptoRemotePublicKey.html @@ -0,0 +1,12 @@ +RcCryptoRemotePublicKey in rc_crypto::ece_crypto - Rust
pub struct RcCryptoRemotePublicKey { /* private fields */ }

Implementations§

Trait Implementations§

source§

impl RemotePublicKey for RcCryptoRemotePublicKey

source§

fn as_raw(&self) -> Result<Vec<u8>, Error>

Export the key component in the +binary uncompressed point representation.
source§

fn as_any(&self) -> &dyn Any

For downcasting purposes.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/rc_crypto/enum.ErrorKind.html b/book/rust-docs/rc_crypto/enum.ErrorKind.html new file mode 100644 index 0000000000..187959f1ec --- /dev/null +++ b/book/rust-docs/rc_crypto/enum.ErrorKind.html @@ -0,0 +1,27 @@ +ErrorKind in rc_crypto - Rust

Enum rc_crypto::ErrorKind

source ·
pub enum ErrorKind {
+    NSSError(Error),
+    InternalError,
+    ConversionError(TryFromIntError),
+    RootHashFormatError(String),
+    PEMFormatError(String),
+    CertificateContentError(String),
+    CertificateValidityError,
+    CertificateSubjectError,
+    CertificateIssuerError,
+    CertificateChainError(String),
+    SignatureContentError(String),
+    SignatureMismatchError(String),
+}

Variants§

§

NSSError(Error)

§

InternalError

§

ConversionError(TryFromIntError)

§

RootHashFormatError(String)

§

PEMFormatError(String)

§

CertificateContentError(String)

§

CertificateValidityError

§

CertificateSubjectError

§

CertificateIssuerError

§

CertificateChainError(String)

§

SignatureContentError(String)

§

SignatureMismatchError(String)

Trait Implementations§

source§

impl Debug for ErrorKind

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Display for ErrorKind

source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Error for ErrorKind

source§

fn source(&self) -> Option<&(dyn Error + 'static)>

The lower-level source of this error, if any. Read more
1.0.0 · source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type based access to context intended for error reports. Read more
source§

impl From<Error> for ErrorKind

source§

fn from(source: Error) -> Self

Converts to this type from the input type.
source§

impl From<ErrorKind> for Error

source§

fn from(ctx: ErrorKind) -> Error

Converts to this type from the input type.
source§

impl From<TryFromIntError> for ErrorKind

source§

fn from(source: TryFromIntError) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<E> Provider for Ewhere + E: Error + ?Sized,

source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (provide_any)
Data providers should implement this method to provide all values they are able to +provide by using demand. Read more
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/rc_crypto/error/enum.ErrorKind.html b/book/rust-docs/rc_crypto/error/enum.ErrorKind.html new file mode 100644 index 0000000000..a20de66676 --- /dev/null +++ b/book/rust-docs/rc_crypto/error/enum.ErrorKind.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../rc_crypto/enum.ErrorKind.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/rc_crypto/error/struct.Error.html b/book/rust-docs/rc_crypto/error/struct.Error.html new file mode 100644 index 0000000000..bc8ecca725 --- /dev/null +++ b/book/rust-docs/rc_crypto/error/struct.Error.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../rc_crypto/struct.Error.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/rc_crypto/error/type.Result.html b/book/rust-docs/rc_crypto/error/type.Result.html new file mode 100644 index 0000000000..8a75e72ba9 --- /dev/null +++ b/book/rust-docs/rc_crypto/error/type.Result.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../rc_crypto/type.Result.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/rc_crypto/fn.ensure_initialized.html b/book/rust-docs/rc_crypto/fn.ensure_initialized.html new file mode 100644 index 0000000000..b46b2a27f5 --- /dev/null +++ b/book/rust-docs/rc_crypto/fn.ensure_initialized.html @@ -0,0 +1,3 @@ +ensure_initialized in rc_crypto - Rust
pub fn ensure_initialized()
Expand description

Only required to be called if you intend to use this library in conjunction +with the hawk or the ece crate.

+
\ No newline at end of file diff --git a/book/rust-docs/rc_crypto/hkdf/fn.expand.html b/book/rust-docs/rc_crypto/hkdf/fn.expand.html new file mode 100644 index 0000000000..0b322bcc66 --- /dev/null +++ b/book/rust-docs/rc_crypto/hkdf/fn.expand.html @@ -0,0 +1 @@ +expand in rc_crypto::hkdf - Rust

Function rc_crypto::hkdf::expand

source ·
pub fn expand(prk: &SigningKey, info: &[u8], out: &mut [u8]) -> Result<()>
\ No newline at end of file diff --git a/book/rust-docs/rc_crypto/hkdf/fn.extract.html b/book/rust-docs/rc_crypto/hkdf/fn.extract.html new file mode 100644 index 0000000000..50701d3f3d --- /dev/null +++ b/book/rust-docs/rc_crypto/hkdf/fn.extract.html @@ -0,0 +1 @@ +extract in rc_crypto::hkdf - Rust

Function rc_crypto::hkdf::extract

source ·
pub fn extract(salt: &SigningKey, secret: &[u8]) -> Result<SigningKey>
\ No newline at end of file diff --git a/book/rust-docs/rc_crypto/hkdf/fn.extract_and_expand.html b/book/rust-docs/rc_crypto/hkdf/fn.extract_and_expand.html new file mode 100644 index 0000000000..961a9b16ab --- /dev/null +++ b/book/rust-docs/rc_crypto/hkdf/fn.extract_and_expand.html @@ -0,0 +1,6 @@ +extract_and_expand in rc_crypto::hkdf - Rust
pub fn extract_and_expand(
+    salt: &SigningKey,
+    secret: &[u8],
+    info: &[u8],
+    out: &mut [u8]
+) -> Result<()>
\ No newline at end of file diff --git a/book/rust-docs/rc_crypto/hkdf/index.html b/book/rust-docs/rc_crypto/hkdf/index.html new file mode 100644 index 0000000000..a8133cca58 --- /dev/null +++ b/book/rust-docs/rc_crypto/hkdf/index.html @@ -0,0 +1 @@ +rc_crypto::hkdf - Rust
\ No newline at end of file diff --git a/book/rust-docs/rc_crypto/hkdf/sidebar-items.js b/book/rust-docs/rc_crypto/hkdf/sidebar-items.js new file mode 100644 index 0000000000..2fd74d558b --- /dev/null +++ b/book/rust-docs/rc_crypto/hkdf/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["expand","extract","extract_and_expand"]}; \ No newline at end of file diff --git a/book/rust-docs/rc_crypto/hmac/fn.sign.html b/book/rust-docs/rc_crypto/hmac/fn.sign.html new file mode 100644 index 0000000000..af17dfa42c --- /dev/null +++ b/book/rust-docs/rc_crypto/hmac/fn.sign.html @@ -0,0 +1,2 @@ +sign in rc_crypto::hmac - Rust

Function rc_crypto::hmac::sign

source ·
pub fn sign(key: &SigningKey, data: &[u8]) -> Result<Signature>
Expand description

Calculate the HMAC of data using key.

+
\ No newline at end of file diff --git a/book/rust-docs/rc_crypto/hmac/fn.verify.html b/book/rust-docs/rc_crypto/hmac/fn.verify.html new file mode 100644 index 0000000000..93b58d41d7 --- /dev/null +++ b/book/rust-docs/rc_crypto/hmac/fn.verify.html @@ -0,0 +1,6 @@ +verify in rc_crypto::hmac - Rust

Function rc_crypto::hmac::verify

source ·
pub fn verify(
+    key: &VerificationKey,
+    data: &[u8],
+    signature: &[u8]
+) -> Result<()>
Expand description

Calculate the HMAC of data using key and verify it corresponds to the provided signature.

+
\ No newline at end of file diff --git a/book/rust-docs/rc_crypto/hmac/fn.verify_with_own_key.html b/book/rust-docs/rc_crypto/hmac/fn.verify_with_own_key.html new file mode 100644 index 0000000000..fdc95b22be --- /dev/null +++ b/book/rust-docs/rc_crypto/hmac/fn.verify_with_own_key.html @@ -0,0 +1,6 @@ +verify_with_own_key in rc_crypto::hmac - Rust
pub fn verify_with_own_key(
+    key: &SigningKey,
+    data: &[u8],
+    signature: &[u8]
+) -> Result<()>
Expand description

Equivalent to verify but allows the consumer to pass a SigningKey.

+
\ No newline at end of file diff --git a/book/rust-docs/rc_crypto/hmac/index.html b/book/rust-docs/rc_crypto/hmac/index.html new file mode 100644 index 0000000000..0e1faaf18a --- /dev/null +++ b/book/rust-docs/rc_crypto/hmac/index.html @@ -0,0 +1,3 @@ +rc_crypto::hmac - Rust

Module rc_crypto::hmac

source ·

Structs

  • A calculated signature value. +This is a type-safe wrappper that discourages attempts at comparing signatures +for equality, which might naively be done using a non-constant-time comparison.
  • A key to use for HMAC signing.
  • A key to use for HMAC authentication.

Functions

  • Calculate the HMAC of data using key.
  • Calculate the HMAC of data using key and verify it corresponds to the provided signature.
  • Equivalent to verify but allows the consumer to pass a SigningKey.
\ No newline at end of file diff --git a/book/rust-docs/rc_crypto/hmac/sidebar-items.js b/book/rust-docs/rc_crypto/hmac/sidebar-items.js new file mode 100644 index 0000000000..a6f1280800 --- /dev/null +++ b/book/rust-docs/rc_crypto/hmac/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["sign","verify","verify_with_own_key"],"struct":["Signature","SigningKey","VerificationKey"]}; \ No newline at end of file diff --git a/book/rust-docs/rc_crypto/hmac/struct.Signature.html b/book/rust-docs/rc_crypto/hmac/struct.Signature.html new file mode 100644 index 0000000000..2a123f0c6e --- /dev/null +++ b/book/rust-docs/rc_crypto/hmac/struct.Signature.html @@ -0,0 +1,21 @@ +Signature in rc_crypto::hmac - Rust

Struct rc_crypto::hmac::Signature

source ·
pub struct Signature(_);
Expand description

A calculated signature value. +This is a type-safe wrappper that discourages attempts at comparing signatures +for equality, which might naively be done using a non-constant-time comparison.

+

Trait Implementations§

source§

impl AsRef<[u8]> for Signature

source§

fn as_ref(&self) -> &[u8]

Converts this type into a shared reference of the (usually inferred) input type.
source§

impl Clone for Signature

source§

fn clone(&self) -> Signature

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
§

impl<Ctx, T> MeasureWith<Ctx> for Twhere + T: AsRef<[u8]>,

§

fn measure_with(&self, _ctx: &Ctx) -> usize

How large is Self, given the ctx?
source§

impl<T> ToHex for Twhere + T: AsRef<[u8]>,

source§

fn encode_hex<U>(&self) -> Uwhere + U: FromIterator<char>,

Encode the hex strict representing self into the result. Lower case +letters are used (e.g. f9b4ca)
source§

fn encode_hex_upper<U>(&self) -> Uwhere + U: FromIterator<char>,

Encode the hex strict representing self into the result. Upper case +letters are used (e.g. F9B4CA)
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/rc_crypto/hmac/struct.SigningKey.html b/book/rust-docs/rc_crypto/hmac/struct.SigningKey.html new file mode 100644 index 0000000000..735fe4e8ac --- /dev/null +++ b/book/rust-docs/rc_crypto/hmac/struct.SigningKey.html @@ -0,0 +1,12 @@ +SigningKey in rc_crypto::hmac - Rust

Struct rc_crypto::hmac::SigningKey

source ·
pub struct SigningKey { /* private fields */ }
Expand description

A key to use for HMAC signing.

+

Implementations§

source§

impl SigningKey

source

pub fn new(digest_alg: &'static Algorithm, key_value: &[u8]) -> Self

source

pub fn digest_algorithm(&self) -> &'static Algorithm

Trait Implementations§

source§

impl HmacKey for SigningKey

source§

fn sign(&self, data: &[u8]) -> Result<Vec<u8>, CryptoError>

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/rc_crypto/hmac/struct.VerificationKey.html b/book/rust-docs/rc_crypto/hmac/struct.VerificationKey.html new file mode 100644 index 0000000000..5b3d6fbac0 --- /dev/null +++ b/book/rust-docs/rc_crypto/hmac/struct.VerificationKey.html @@ -0,0 +1,12 @@ +VerificationKey in rc_crypto::hmac - Rust
pub struct VerificationKey { /* private fields */ }
Expand description

A key to use for HMAC authentication.

+

Implementations§

source§

impl VerificationKey

source

pub fn new(digest_alg: &'static Algorithm, key_value: &[u8]) -> Self

source

pub fn digest_algorithm(&self) -> &'static Algorithm

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/rc_crypto/index.html b/book/rust-docs/rc_crypto/index.html new file mode 100644 index 0000000000..7ed9b311c7 --- /dev/null +++ b/book/rust-docs/rc_crypto/index.html @@ -0,0 +1,4 @@ +rc_crypto - Rust

Crate rc_crypto

source ·

Re-exports

  • pub use hawk;
  • pub use ece;

Modules

Structs

Enums

Functions

  • Only required to be called if you intend to use this library in conjunction +with the hawk or the ece crate.

Type Definitions

\ No newline at end of file diff --git a/book/rust-docs/rc_crypto/pbkdf2/enum.HashAlgorithm.html b/book/rust-docs/rc_crypto/pbkdf2/enum.HashAlgorithm.html new file mode 100644 index 0000000000..94f753d7a5 --- /dev/null +++ b/book/rust-docs/rc_crypto/pbkdf2/enum.HashAlgorithm.html @@ -0,0 +1,15 @@ +HashAlgorithm in rc_crypto::pbkdf2 - Rust
#[repr(u8)]
pub enum HashAlgorithm { + SHA256, + SHA384, +}

Variants§

§

SHA256

§

SHA384

Trait Implementations§

§

impl Clone for HashAlgorithm

§

fn clone(&self) -> HashAlgorithm

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
§

impl Debug for HashAlgorithm

§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
§

impl Copy for HashAlgorithm

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/rc_crypto/pbkdf2/fn.derive.html b/book/rust-docs/rc_crypto/pbkdf2/fn.derive.html new file mode 100644 index 0000000000..ae55c57fb4 --- /dev/null +++ b/book/rust-docs/rc_crypto/pbkdf2/fn.derive.html @@ -0,0 +1,26 @@ +derive in rc_crypto::pbkdf2 - Rust

Function rc_crypto::pbkdf2::derive

source ·
pub fn derive(
+    passphrase: &[u8],
+    salt: &[u8],
+    iterations: u32,
+    hash_algorithm: HashAlgorithm,
+    out: &mut [u8]
+) -> Result<()>
Expand description

Extend passwords using pbkdf2, based on the following rfc it runs the NSS implementation

+

Arguments

+
    +
  • passphrase - The password to stretch
  • +
  • salt - A salt to use in the generation process
  • +
  • iterations - The number of iterations the hashing algorithm will run on each section of the key
  • +
  • hash_algorithm - The hash algorithm to use
  • +
  • out - The slice the algorithm will populate
  • +
+

Examples

+
use rc_crypto::pbkdf2;
+let password = b"password";
+let salt = b"salt";
+let mut out = vec![0u8; 32];
+let iterations = 2; // Real code should have a MUCH higher number of iterations (Think 1000+)
+pbkdf2::derive(password, salt, iterations, pbkdf2::HashAlgorithm::SHA256, &mut out).unwrap(); // Oh oh should handle the error!
+assert_eq!(hex::encode(out), "ae4d0c95af6b46d32d0adff928f06dd02a303f8ef3c251dfd6e2d85a95474c43");
+

Errors

+

Could possibly return an error if the HMAC algorithm fails, or if the NSS algorithm returns an error

+
\ No newline at end of file diff --git a/book/rust-docs/rc_crypto/pbkdf2/index.html b/book/rust-docs/rc_crypto/pbkdf2/index.html new file mode 100644 index 0000000000..fbae4f6131 --- /dev/null +++ b/book/rust-docs/rc_crypto/pbkdf2/index.html @@ -0,0 +1 @@ +rc_crypto::pbkdf2 - Rust

Module rc_crypto::pbkdf2

source ·

Enums

Functions

  • Extend passwords using pbkdf2, based on the following rfc it runs the NSS implementation
\ No newline at end of file diff --git a/book/rust-docs/rc_crypto/pbkdf2/sidebar-items.js b/book/rust-docs/rc_crypto/pbkdf2/sidebar-items.js new file mode 100644 index 0000000000..ec9cab076c --- /dev/null +++ b/book/rust-docs/rc_crypto/pbkdf2/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["HashAlgorithm"],"fn":["derive"]}; \ No newline at end of file diff --git a/book/rust-docs/rc_crypto/rand/fn.fill.html b/book/rust-docs/rc_crypto/rand/fn.fill.html new file mode 100644 index 0000000000..84901fa750 --- /dev/null +++ b/book/rust-docs/rc_crypto/rand/fn.fill.html @@ -0,0 +1,2 @@ +fill in rc_crypto::rand - Rust

Function rc_crypto::rand::fill

source ·
pub fn fill(dest: &mut [u8]) -> Result<()>
Expand description

Fill a buffer with cryptographically secure pseudo-random data.

+
\ No newline at end of file diff --git a/book/rust-docs/rc_crypto/rand/index.html b/book/rust-docs/rc_crypto/rand/index.html new file mode 100644 index 0000000000..8ca8f3f643 --- /dev/null +++ b/book/rust-docs/rc_crypto/rand/index.html @@ -0,0 +1 @@ +rc_crypto::rand - Rust

Module rc_crypto::rand

source ·

Functions

  • Fill a buffer with cryptographically secure pseudo-random data.
\ No newline at end of file diff --git a/book/rust-docs/rc_crypto/rand/sidebar-items.js b/book/rust-docs/rc_crypto/rand/sidebar-items.js new file mode 100644 index 0000000000..4d234e200c --- /dev/null +++ b/book/rust-docs/rc_crypto/rand/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["fill"]}; \ No newline at end of file diff --git a/book/rust-docs/rc_crypto/sidebar-items.js b/book/rust-docs/rc_crypto/sidebar-items.js new file mode 100644 index 0000000000..2a08640daa --- /dev/null +++ b/book/rust-docs/rc_crypto/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["ErrorKind"],"fn":["ensure_initialized"],"mod":["aead","agreement","constant_time","contentsignature","digest","ece_crypto","hkdf","hmac","pbkdf2","rand","signature"],"struct":["Error"],"type":["Result"]}; \ No newline at end of file diff --git a/book/rust-docs/rc_crypto/signature/index.html b/book/rust-docs/rc_crypto/signature/index.html new file mode 100644 index 0000000000..b28b5a302b --- /dev/null +++ b/book/rust-docs/rc_crypto/signature/index.html @@ -0,0 +1 @@ +rc_crypto::signature - Rust

Module rc_crypto::signature

source ·

Structs

Statics

\ No newline at end of file diff --git a/book/rust-docs/rc_crypto/signature/sidebar-items.js b/book/rust-docs/rc_crypto/signature/sidebar-items.js new file mode 100644 index 0000000000..423ee6e076 --- /dev/null +++ b/book/rust-docs/rc_crypto/signature/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"static":["ECDSA_P256_SHA256","ECDSA_P384_SHA384"],"struct":["UnparsedPublicKey","VerificationAlgorithm"]}; \ No newline at end of file diff --git a/book/rust-docs/rc_crypto/signature/static.ECDSA_P256_SHA256.html b/book/rust-docs/rc_crypto/signature/static.ECDSA_P256_SHA256.html new file mode 100644 index 0000000000..a4ecea798a --- /dev/null +++ b/book/rust-docs/rc_crypto/signature/static.ECDSA_P256_SHA256.html @@ -0,0 +1 @@ +ECDSA_P256_SHA256 in rc_crypto::signature - Rust
pub static ECDSA_P256_SHA256: VerificationAlgorithm
\ No newline at end of file diff --git a/book/rust-docs/rc_crypto/signature/static.ECDSA_P384_SHA384.html b/book/rust-docs/rc_crypto/signature/static.ECDSA_P384_SHA384.html new file mode 100644 index 0000000000..144298b22a --- /dev/null +++ b/book/rust-docs/rc_crypto/signature/static.ECDSA_P384_SHA384.html @@ -0,0 +1 @@ +ECDSA_P384_SHA384 in rc_crypto::signature - Rust
pub static ECDSA_P384_SHA384: VerificationAlgorithm
\ No newline at end of file diff --git a/book/rust-docs/rc_crypto/signature/struct.UnparsedPublicKey.html b/book/rust-docs/rc_crypto/signature/struct.UnparsedPublicKey.html new file mode 100644 index 0000000000..4d5a4b97bd --- /dev/null +++ b/book/rust-docs/rc_crypto/signature/struct.UnparsedPublicKey.html @@ -0,0 +1,12 @@ +UnparsedPublicKey in rc_crypto::signature - Rust
pub struct UnparsedPublicKey<'a> { /* private fields */ }
Expand description

An unparsed public key for signature operations.

+

Implementations§

source§

impl<'a> UnparsedPublicKey<'a>

source

pub fn new(algorithm: &'static VerificationAlgorithm, bytes: &'a [u8]) -> Self

source

pub fn verify(&self, message: &[u8], signature: &[u8]) -> Result<()>

source

pub fn algorithm(&self) -> &'static VerificationAlgorithm

source

pub fn bytes(&self) -> &'a [u8]

Auto Trait Implementations§

§

impl<'a> RefUnwindSafe for UnparsedPublicKey<'a>

§

impl<'a> Send for UnparsedPublicKey<'a>

§

impl<'a> Sync for UnparsedPublicKey<'a>

§

impl<'a> Unpin for UnparsedPublicKey<'a>

§

impl<'a> UnwindSafe for UnparsedPublicKey<'a>

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/rc_crypto/signature/struct.VerificationAlgorithm.html b/book/rust-docs/rc_crypto/signature/struct.VerificationAlgorithm.html new file mode 100644 index 0000000000..02f8cd8a8a --- /dev/null +++ b/book/rust-docs/rc_crypto/signature/struct.VerificationAlgorithm.html @@ -0,0 +1,12 @@ +VerificationAlgorithm in rc_crypto::signature - Rust
pub struct VerificationAlgorithm { /* private fields */ }
Expand description

A signature verification algorithm.

+

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/rc_crypto/struct.Error.html b/book/rust-docs/rc_crypto/struct.Error.html new file mode 100644 index 0000000000..918568ede9 --- /dev/null +++ b/book/rust-docs/rc_crypto/struct.Error.html @@ -0,0 +1,14 @@ +Error in rc_crypto - Rust

Struct rc_crypto::Error

source ·
pub struct Error(_);

Implementations§

source§

impl Error

source

pub fn kind(&self) -> &ErrorKind

source

pub fn backtrace(&self) -> Option<&Mutex<Backtrace>>

Trait Implementations§

source§

impl Debug for Error

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Display for Error

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Error for Error

1.30.0 · source§

fn source(&self) -> Option<&(dyn Error + 'static)>

The lower-level source of this error, if any. Read more
1.0.0 · source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type based access to context intended for error reports. Read more
source§

impl From<Error> for CryptoError

source§

fn from(e: Error) -> Self

Converts to this type from the input type.
source§

impl From<Error> for Error

source§

fn from(_: Error) -> Self

Converts to this type from the input type.
source§

impl From<Error> for Error

source§

fn from(e: Error) -> Self

Converts to this type from the input type.
source§

impl From<ErrorKind> for Error

source§

fn from(ctx: ErrorKind) -> Error

Converts to this type from the input type.
source§

impl From<TryFromIntError> for Error

source§

fn from(e: TryFromIntError) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

§

impl RefUnwindSafe for Error

§

impl Send for Error

§

impl Sync for Error

§

impl Unpin for Error

§

impl UnwindSafe for Error

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<E> Provider for Ewhere + E: Error + ?Sized,

source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (provide_any)
Data providers should implement this method to provide all values they are able to +provide by using demand. Read more
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/rc_crypto/type.Result.html b/book/rust-docs/rc_crypto/type.Result.html new file mode 100644 index 0000000000..9d93753955 --- /dev/null +++ b/book/rust-docs/rc_crypto/type.Result.html @@ -0,0 +1 @@ +Result in rc_crypto - Rust

Type Definition rc_crypto::Result

source ·
pub type Result<T, E = Error> = Result<T, E>;
\ No newline at end of file diff --git a/book/rust-docs/rc_log_ffi/all.html b/book/rust-docs/rc_log_ffi/all.html new file mode 100644 index 0000000000..26049cf6fb --- /dev/null +++ b/book/rust-docs/rc_log_ffi/all.html @@ -0,0 +1 @@ +List of all items in this crate
\ No newline at end of file diff --git a/book/rust-docs/rc_log_ffi/android/index.html b/book/rust-docs/rc_log_ffi/android/index.html new file mode 100644 index 0000000000..2f3835110f --- /dev/null +++ b/book/rust-docs/rc_log_ffi/android/index.html @@ -0,0 +1,31 @@ +rc_log_ffi::android - Rust

Module rc_log_ffi::android

source ·
Expand description

This is the android backend for rc_log. It has a decent amount of +complexity, as Rust logs can be emitted by any thread, regardless of whether +or not they have an associated JVM thread. JNA’s Callback class helps us +here, by providing a way for mapping native threads to JVM threads. +Unfortunately, naive usage of this class in a multithreaded context will be +very suboptimal in terms of memory and thread usage.

+

To avoid this, we only call into the JVM from a single thread, which we +launch when initializing the logger. This thread just polls a channel +listening for log messages, where a log message is an enum (LogMessage) +that either tells it to log an item, or to stop logging all together.

+
    +
  1. +

    We cannot guarantee that the callback from android lives past when the +android code tells us to stop logging, so in order to be memory safe, we +need to stop logging immediately when this happens. We do this using an +Arc<AtomicBool>, used to indicate that we should stop logging.

    +
  2. +
  3. +

    There’s no safe way to terminate a thread in Rust (for good reason), so +the background thread must close willingly. To make sure this happens +promptly (e.g. to avoid a case where we’re blocked until some thread +somewhere else happens to log something), we need to add something onto +the log channel, hence the existence of LogMessage::Stop.

    +

    It’s important to note that because of point 1, the polling thread may +have to stop prior to getting LogMessage::Stop. We do not want to wait +for it to process whatever log messages were sent prior to being told to +stop.

    +
  4. +
+

Structs

Type Definitions

  • Type of the log callback provided to us by java/swift. Takes the following +arguments:
\ No newline at end of file diff --git a/book/rust-docs/rc_log_ffi/android/sidebar-items.js b/book/rust-docs/rc_log_ffi/android/sidebar-items.js new file mode 100644 index 0000000000..8c35d7b0cc --- /dev/null +++ b/book/rust-docs/rc_log_ffi/android/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"struct":["LogAdapterState","LogSink"],"type":["LogCallback"]}; \ No newline at end of file diff --git a/book/rust-docs/rc_log_ffi/android/struct.LogAdapterState.html b/book/rust-docs/rc_log_ffi/android/struct.LogAdapterState.html new file mode 100644 index 0000000000..fb9a1e6119 --- /dev/null +++ b/book/rust-docs/rc_log_ffi/android/struct.LogAdapterState.html @@ -0,0 +1,14 @@ +LogAdapterState in rc_log_ffi::android - Rust
pub struct LogAdapterState { /* private fields */ }

Implementations§

source§

impl LogAdapterState

source

pub fn init(callback: LogCallback) -> Self

Trait Implementations§

source§

impl Drop for LogAdapterState

source§

fn drop(&mut self)

Executes the destructor for this type. Read more
source§

impl IntoFfi for LogAdapterStatewhere + LogAdapterState: Send,

§

type Value = *mut LogAdapterState

This type must be: Read more
source§

fn ffi_default() -> *mut LogAdapterState

Return an ‘empty’ value. This is what’s passed back to C in the case of an error, +so it doesn’t actually need to be “empty”, so much as “ignorable”. Note that this +is also used when an empty Option<T> is returned.
source§

fn into_ffi_value(self) -> *mut LogAdapterState

Convert ourselves into a value we can pass back to C with confidence.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/rc_log_ffi/android/struct.LogSink.html b/book/rust-docs/rc_log_ffi/android/struct.LogSink.html new file mode 100644 index 0000000000..e4551fd3bf --- /dev/null +++ b/book/rust-docs/rc_log_ffi/android/struct.LogSink.html @@ -0,0 +1,12 @@ +LogSink in rc_log_ffi::android - Rust

Struct rc_log_ffi::android::LogSink

source ·
pub struct LogSink { /* private fields */ }

Trait Implementations§

source§

impl Log for LogSink

source§

fn enabled(&self, _metadata: &Metadata<'_>) -> bool

Determines if a log message with the specified metadata would be +logged. Read more
source§

fn flush(&self)

Flushes any buffered records.
source§

fn log(&self, record: &Record<'_>)

Logs the Record. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/rc_log_ffi/android/type.LogCallback.html b/book/rust-docs/rc_log_ffi/android/type.LogCallback.html new file mode 100644 index 0000000000..794c361214 --- /dev/null +++ b/book/rust-docs/rc_log_ffi/android/type.LogCallback.html @@ -0,0 +1,21 @@ +LogCallback in rc_log_ffi::android - Rust

Type Definition rc_log_ffi::android::LogCallback

source ·
pub type LogCallback = unsafe extern "C" fn(_: i32, _: *const c_char, _: *const c_char) -> u8;
Expand description

Type of the log callback provided to us by java/swift. Takes the following +arguments:

+
    +
  • +

    Log level (an i32).

    +
  • +
  • +

    Tag: a (nullable) nul terminated c string. The callback must not free this +string, which is only valid until the the callback returns. If you need +it past that, you must copy it into an internal buffer!

    +
  • +
  • +

    Message: a (non-nullable) nul terminated c string. The callback must not free this +string, which is only valid until the the callback returns. If you need +it past that, you must copy it into an internal buffer!

    +
  • +
+

and returns 0 if we should close the thread, and 1 otherwise. This is done +because attempting to call disable from within the log callback will +deadlock.

+
\ No newline at end of file diff --git a/book/rust-docs/rc_log_ffi/enum.LogLevel.html b/book/rust-docs/rc_log_ffi/enum.LogLevel.html new file mode 100644 index 0000000000..81e8445113 --- /dev/null +++ b/book/rust-docs/rc_log_ffi/enum.LogLevel.html @@ -0,0 +1,20 @@ +LogLevel in rc_log_ffi - Rust

Enum rc_log_ffi::LogLevel

source ·
#[repr(i32)]
pub enum LogLevel { + VERBOSE, + DEBUG, + INFO, + WARN, + ERROR, +}

Variants§

§

VERBOSE

§

DEBUG

§

INFO

§

WARN

§

ERROR

Trait Implementations§

source§

impl Clone for LogLevel

source§

fn clone(&self) -> LogLevel

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for LogLevel

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl From<Level> for LogLevel

source§

fn from(l: Level) -> Self

Converts to this type from the input type.
source§

impl PartialEq<LogLevel> for LogLevel

source§

fn eq(&self, other: &LogLevel) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Copy for LogLevel

source§

impl Eq for LogLevel

source§

impl StructuralEq for LogLevel

source§

impl StructuralPartialEq for LogLevel

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/rc_log_ffi/fn.rc_log_adapter_create.html b/book/rust-docs/rc_log_ffi/fn.rc_log_adapter_create.html new file mode 100644 index 0000000000..afe1dfa909 --- /dev/null +++ b/book/rust-docs/rc_log_ffi/fn.rc_log_adapter_create.html @@ -0,0 +1,5 @@ +rc_log_adapter_create in rc_log_ffi - Rust
#[no_mangle]
+pub extern "C" fn rc_log_adapter_create(
+    callback: LogCallback,
+    out_err: &mut ExternError
+) -> *mut LogAdapterState
\ No newline at end of file diff --git a/book/rust-docs/rc_log_ffi/fn.rc_log_adapter_destroy.html b/book/rust-docs/rc_log_ffi/fn.rc_log_adapter_destroy.html new file mode 100644 index 0000000000..7912305070 --- /dev/null +++ b/book/rust-docs/rc_log_ffi/fn.rc_log_adapter_destroy.html @@ -0,0 +1,6 @@ +rc_log_adapter_destroy in rc_log_ffi - Rust
#[no_mangle]
+pub unsafe extern "C" fn rc_log_adapter_destroy(
+    to_destroy: *mut LogAdapterState
+)
Expand description

Safety

+

Unsafe because it frees it’s argument.

+
\ No newline at end of file diff --git a/book/rust-docs/rc_log_ffi/fn.rc_log_adapter_destroy_string.html b/book/rust-docs/rc_log_ffi/fn.rc_log_adapter_destroy_string.html new file mode 100644 index 0000000000..d1664e003c --- /dev/null +++ b/book/rust-docs/rc_log_ffi/fn.rc_log_adapter_destroy_string.html @@ -0,0 +1,8 @@ +rc_log_adapter_destroy_string in rc_log_ffi - Rust
#[no_mangle]
+pub unsafe extern "C" fn rc_log_adapter_destroy_string(s: *mut c_char)
Expand description

Public destructor for strings managed by the other side of the FFI.

+

Safety

+

This will free the string pointer it gets passed in as an argument, +and thus can be wildly unsafe if misused.

+

See the documentation of ffi_support::destroy_c_string and +ffi_support::define_string_destructor! for further info.

+
\ No newline at end of file diff --git a/book/rust-docs/rc_log_ffi/fn.rc_log_adapter_set_max_level.html b/book/rust-docs/rc_log_ffi/fn.rc_log_adapter_set_max_level.html new file mode 100644 index 0000000000..15605fc382 --- /dev/null +++ b/book/rust-docs/rc_log_ffi/fn.rc_log_adapter_set_max_level.html @@ -0,0 +1,5 @@ +rc_log_adapter_set_max_level in rc_log_ffi - Rust
#[no_mangle]
+pub extern "C" fn rc_log_adapter_set_max_level(
+    level: i32,
+    out_err: &mut ExternError
+)
\ No newline at end of file diff --git a/book/rust-docs/rc_log_ffi/fn.rc_log_adapter_test__log_msg.html b/book/rust-docs/rc_log_ffi/fn.rc_log_adapter_test__log_msg.html new file mode 100644 index 0000000000..3445da431d --- /dev/null +++ b/book/rust-docs/rc_log_ffi/fn.rc_log_adapter_test__log_msg.html @@ -0,0 +1,2 @@ +rc_log_adapter_test__log_msg in rc_log_ffi - Rust
#[no_mangle]
+pub extern "C" fn rc_log_adapter_test__log_msg(msg: FfiStr<'_>)
\ No newline at end of file diff --git a/book/rust-docs/rc_log_ffi/index.html b/book/rust-docs/rc_log_ffi/index.html new file mode 100644 index 0000000000..ef3b3028e0 --- /dev/null +++ b/book/rust-docs/rc_log_ffi/index.html @@ -0,0 +1,15 @@ +rc_log_ffi - Rust

Crate rc_log_ffi

source ·
Expand description

This crate allows users from the other side of the FFI to hook into Rust’s +log crate, which is used by us and several of our dependencies. The +primary use case is providing logs to Android and iOS in a way that is more +flexible than writing to liblog (which goes to logcat, which cannot be +accessed by programs on the device, short of rooting it), or stdout/stderr.

+

See the header comment in android.rs and fallback.rs for details.

+

It’s worth noting that the log crate is rather inflexable, in that +it does not allow users to change loggers after the first initialization. We +work around this using our settable_log module.

+

Modules

  • This is the android backend for rc_log. It has a decent amount of +complexity, as Rust logs can be emitted by any thread, regardless of whether +or not they have an associated JVM thread. JNA’s Callback class helps us +here, by providing a way for mapping native threads to JVM threads. +Unfortunately, naive usage of this class in a multithreaded context will be +very suboptimal in terms of memory and thread usage.

Enums

Functions

\ No newline at end of file diff --git a/book/rust-docs/rc_log_ffi/sidebar-items.js b/book/rust-docs/rc_log_ffi/sidebar-items.js new file mode 100644 index 0000000000..dff7a60dab --- /dev/null +++ b/book/rust-docs/rc_log_ffi/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["LogLevel"],"fn":["rc_log_adapter_create","rc_log_adapter_destroy","rc_log_adapter_destroy_string","rc_log_adapter_set_max_level","rc_log_adapter_test__log_msg"],"mod":["android"]}; \ No newline at end of file diff --git a/book/rust-docs/remote_settings/all.html b/book/rust-docs/remote_settings/all.html new file mode 100644 index 0000000000..f198e04996 --- /dev/null +++ b/book/rust-docs/remote_settings/all.html @@ -0,0 +1 @@ +List of all items in this crate
\ No newline at end of file diff --git a/book/rust-docs/remote_settings/client/enum.SortOrder.html b/book/rust-docs/remote_settings/client/enum.SortOrder.html new file mode 100644 index 0000000000..5599032e70 --- /dev/null +++ b/book/rust-docs/remote_settings/client/enum.SortOrder.html @@ -0,0 +1,22 @@ +SortOrder in remote_settings::client - Rust
pub enum SortOrder {
+    Ascending,
+    Descending,
+}
Expand description

The order in which to return items.

+

Variants§

§

Ascending

Smaller values first.

+
§

Descending

Larger values first.

+

Trait Implementations§

source§

impl Clone for SortOrder

source§

fn clone(&self) -> SortOrder

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for SortOrder

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Hash for SortOrder

source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · source§

fn hash_slice<H>(data: &[Self], state: &mut H)where + H: Hasher, + Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
source§

impl PartialEq<SortOrder> for SortOrder

source§

fn eq(&self, other: &SortOrder) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Copy for SortOrder

source§

impl Eq for SortOrder

source§

impl StructuralEq for SortOrder

source§

impl StructuralPartialEq for SortOrder

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/remote_settings/client/index.html b/book/rust-docs/remote_settings/client/index.html new file mode 100644 index 0000000000..1dad2dc4b8 --- /dev/null +++ b/book/rust-docs/remote_settings/client/index.html @@ -0,0 +1,6 @@ +remote_settings::client - Rust

Module remote_settings::client

source ·

Structs

  • Attachment metadata that can be optionally attached to a [Record]. The [location] should +included in calls to Client::get_attachment.
  • A simple HTTP client that can retrieve Remote Settings data using the properties by [ClientConfig]. +Methods defined on this will fetch data from +<base_url>/v1/buckets/<bucket_name>/collections/<collection_name>/
  • Options for requests to endpoints that return multiple items.
  • A parsed Remote Settings record. Records can contain arbitrary fields, so clients +are required to further extract expected values from the [fields] member.
  • Data structure representing the top-level response from the Remote Settings. +[last_modified] will be extracted from the etag header of the response.

Enums

Type Definitions

\ No newline at end of file diff --git a/book/rust-docs/remote_settings/client/sidebar-items.js b/book/rust-docs/remote_settings/client/sidebar-items.js new file mode 100644 index 0000000000..eeb209a9eb --- /dev/null +++ b/book/rust-docs/remote_settings/client/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["SortOrder"],"struct":["Attachment","Client","GetItemsOptions","RemoteSettingsRecord","RemoteSettingsResponse"],"type":["RsJsonObject"]}; \ No newline at end of file diff --git a/book/rust-docs/remote_settings/client/struct.Attachment.html b/book/rust-docs/remote_settings/client/struct.Attachment.html new file mode 100644 index 0000000000..ead91a8e31 --- /dev/null +++ b/book/rust-docs/remote_settings/client/struct.Attachment.html @@ -0,0 +1,24 @@ +Attachment in remote_settings::client - Rust
pub struct Attachment {
+    pub filename: String,
+    pub mimetype: String,
+    pub location: String,
+    pub hash: String,
+    pub size: u64,
+}
Expand description

Attachment metadata that can be optionally attached to a [Record]. The [location] should +included in calls to Client::get_attachment.

+

Fields§

§filename: String§mimetype: String§location: String§hash: String§size: u64

Trait Implementations§

source§

impl Clone for Attachment

source§

fn clone(&self) -> Attachment

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for Attachment

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl<'de> Deserialize<'de> for Attachment

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl PartialEq<Attachment> for Attachment

source§

fn eq(&self, other: &Attachment) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Eq for Attachment

source§

impl StructuralEq for Attachment

source§

impl StructuralPartialEq for Attachment

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/remote_settings/client/struct.Client.html b/book/rust-docs/remote_settings/client/struct.Client.html new file mode 100644 index 0000000000..47d6548525 --- /dev/null +++ b/book/rust-docs/remote_settings/client/struct.Client.html @@ -0,0 +1,40 @@ +Client in remote_settings::client - Rust
pub struct Client { /* private fields */ }
Expand description

A simple HTTP client that can retrieve Remote Settings data using the properties by [ClientConfig]. +Methods defined on this will fetch data from +<base_url>/v1/buckets/<bucket_name>/collections/<collection_name>/

+

Implementations§

source§

impl Client

source

pub fn new(config: RemoteSettingsConfig) -> Result<Self>

Create a new Client with properties matching config.

+
source

pub fn get_records(&self) -> Result<RemoteSettingsResponse>

Fetches all records for a collection that can be found in the server, +bucket, and collection defined by the [ClientConfig] used to generate +this Client.

+
source

pub fn get_records_raw(&self) -> Result<Response>

Fetches all records for a collection that can be found in the server, +bucket, and collection defined by the [ClientConfig] used to generate +this Client. This function will return the raw network [Response].

+
source

pub fn get_records_since( + &self, + timestamp: u64 +) -> Result<RemoteSettingsResponse>

Fetches all records that have been published since provided timestamp +for a collection that can be found in the server, bucket, and +collection defined by the [ClientConfig] used to generate this Client.

+
source

pub fn get_records_with_options( + &self, + options: &GetItemsOptions +) -> Result<RemoteSettingsResponse>

Fetches records from this client’s collection with the given options.

+
source

pub fn get_records_raw_with_options( + &self, + options: &GetItemsOptions +) -> Result<Response>

Fetches a raw network [Response] for records from this client’s +collection with the given options.

+
source

pub fn get_attachment(&self, attachment_location: &str) -> Result<Vec<u8>>

Downloads an attachment from [attachment_location]. NOTE: there are no +guarantees about a maximum size, so use care when fetching potentially +large attachments.

+
source

pub fn get_attachment_raw(&self, attachment_location: &str) -> Result<Response>

Fetches a raw network [Response] for an attachment.

+

Auto Trait Implementations§

§

impl !RefUnwindSafe for Client

§

impl Send for Client

§

impl Sync for Client

§

impl Unpin for Client

§

impl UnwindSafe for Client

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/remote_settings/client/struct.GetItemsOptions.html b/book/rust-docs/remote_settings/client/struct.GetItemsOptions.html new file mode 100644 index 0000000000..cf2f11dcde --- /dev/null +++ b/book/rust-docs/remote_settings/client/struct.GetItemsOptions.html @@ -0,0 +1,76 @@ +GetItemsOptions in remote_settings::client - Rust
pub struct GetItemsOptions { /* private fields */ }
Expand description

Options for requests to endpoints that return multiple items.

+

Implementations§

source§

impl GetItemsOptions

source

pub fn new() -> Self

Creates an empty option set.

+
source

pub fn eq( + &mut self, + field: impl Into<String>, + value: impl Into<String> +) -> &mut Self

Sets an option to only return items whose field is equal to the given +value.

+

field can be a simple or dotted field name, like author or +author.name. value can be a bare number or string (like +2 or Ben), or a stringified JSON value ("2.0", [1, 2], +{"checked": true}).

+
source

pub fn not( + &mut self, + field: impl Into<String>, + value: impl Into<String> +) -> &mut Self

Sets an option to only return items whose field is not equal to the +given value.

+
source

pub fn contains( + &mut self, + field: impl Into<String>, + value: impl Into<String> +) -> &mut Self

Sets an option to only return items whose field is an array that +contains the given value. If value is a stringified JSON array, the +field must contain all its elements.

+
source

pub fn lt( + &mut self, + field: impl Into<String>, + value: impl Into<String> +) -> &mut Self

Sets an option to only return items whose field is strictly less +than the given value.

+
source

pub fn gt( + &mut self, + field: impl Into<String>, + value: impl Into<String> +) -> &mut Self

Sets an option to only return items whose field is strictly greater +than the given value.

+
source

pub fn max( + &mut self, + field: impl Into<String>, + value: impl Into<String> +) -> &mut Self

Sets an option to only return items whose field is less than or equal +to the given value.

+
source

pub fn min( + &mut self, + field: impl Into<String>, + value: impl Into<String> +) -> &mut Self

Sets an option to only return items whose field is greater than or +equal to the given value.

+
source

pub fn like( + &mut self, + field: impl Into<String>, + value: impl Into<String> +) -> &mut Self

Sets an option to only return items whose field is a string that +contains the substring value. value can contain * wildcards.

+
source

pub fn has(&mut self, field: impl Into<String>) -> &mut Self

Sets an option to only return items that have the given field.

+
source

pub fn has_not(&mut self, field: impl Into<String>) -> &mut Self

Sets an option to only return items that do not have the given field.

+
source

pub fn sort(&mut self, field: impl Into<String>, order: SortOrder) -> &mut Self

Sets an option to return items in order for the given field.

+
source

pub fn field(&mut self, field: impl Into<String>) -> &mut Self

Sets an option to only return the given field of each item.

+

The special id and last_modified fields are always returned.

+
source

pub fn limit(&mut self, count: u64) -> &mut Self

Sets the option to return at most count items.

+
source

pub fn iter_query_pairs( + &self +) -> impl Iterator<Item = (Cow<'_, str>, Cow<'_, str>)>

Returns an iterator of (name, value) query pairs for these options.

+

Trait Implementations§

source§

impl Clone for GetItemsOptions

source§

fn clone(&self) -> GetItemsOptions

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for GetItemsOptions

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for GetItemsOptions

source§

fn default() -> GetItemsOptions

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/remote_settings/client/struct.RemoteSettingsRecord.html b/book/rust-docs/remote_settings/client/struct.RemoteSettingsRecord.html new file mode 100644 index 0000000000..79ce68ac8e --- /dev/null +++ b/book/rust-docs/remote_settings/client/struct.RemoteSettingsRecord.html @@ -0,0 +1,24 @@ +RemoteSettingsRecord in remote_settings::client - Rust
pub struct RemoteSettingsRecord {
+    pub id: String,
+    pub last_modified: u64,
+    pub deleted: bool,
+    pub attachment: Option<Attachment>,
+    pub fields: RsJsonObject,
+}
Expand description

A parsed Remote Settings record. Records can contain arbitrary fields, so clients +are required to further extract expected values from the [fields] member.

+

Fields§

§id: String§last_modified: u64§deleted: bool§attachment: Option<Attachment>§fields: RsJsonObject

Trait Implementations§

source§

impl Clone for RemoteSettingsRecord

source§

fn clone(&self) -> RemoteSettingsRecord

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for RemoteSettingsRecord

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl<'de> Deserialize<'de> for RemoteSettingsRecord

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl PartialEq<RemoteSettingsRecord> for RemoteSettingsRecord

source§

fn eq(&self, other: &RemoteSettingsRecord) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Eq for RemoteSettingsRecord

source§

impl StructuralEq for RemoteSettingsRecord

source§

impl StructuralPartialEq for RemoteSettingsRecord

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/remote_settings/client/struct.RemoteSettingsResponse.html b/book/rust-docs/remote_settings/client/struct.RemoteSettingsResponse.html new file mode 100644 index 0000000000..d723b31c66 --- /dev/null +++ b/book/rust-docs/remote_settings/client/struct.RemoteSettingsResponse.html @@ -0,0 +1,19 @@ +RemoteSettingsResponse in remote_settings::client - Rust
pub struct RemoteSettingsResponse {
+    pub records: Vec<RemoteSettingsRecord>,
+    pub last_modified: u64,
+}
Expand description

Data structure representing the top-level response from the Remote Settings. +[last_modified] will be extracted from the etag header of the response.

+

Fields§

§records: Vec<RemoteSettingsRecord>§last_modified: u64

Trait Implementations§

source§

impl Clone for RemoteSettingsResponse

source§

fn clone(&self) -> RemoteSettingsResponse

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for RemoteSettingsResponse

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl PartialEq<RemoteSettingsResponse> for RemoteSettingsResponse

source§

fn eq(&self, other: &RemoteSettingsResponse) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Eq for RemoteSettingsResponse

source§

impl StructuralEq for RemoteSettingsResponse

source§

impl StructuralPartialEq for RemoteSettingsResponse

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/remote_settings/client/type.RsJsonObject.html b/book/rust-docs/remote_settings/client/type.RsJsonObject.html new file mode 100644 index 0000000000..c8acfa8e7e --- /dev/null +++ b/book/rust-docs/remote_settings/client/type.RsJsonObject.html @@ -0,0 +1 @@ +RsJsonObject in remote_settings::client - Rust

Type Definition remote_settings::client::RsJsonObject

source ·
pub type RsJsonObject = Map<String, Value>;
\ No newline at end of file diff --git a/book/rust-docs/remote_settings/config/index.html b/book/rust-docs/remote_settings/config/index.html new file mode 100644 index 0000000000..93de2c43e2 --- /dev/null +++ b/book/rust-docs/remote_settings/config/index.html @@ -0,0 +1,7 @@ +remote_settings::config - Rust

Module remote_settings::config

source ·
Expand description

This module defines the custom configurations that consumers can set. +Those configurations override default values and can be used to set a custom server url, +collection name, and bucket name. +The purpose of the configuration parameters are to allow consumers an easy debugging option, +and the ability to be explicit about the server.

+

Structs

\ No newline at end of file diff --git a/book/rust-docs/remote_settings/config/sidebar-items.js b/book/rust-docs/remote_settings/config/sidebar-items.js new file mode 100644 index 0000000000..d6cbffd4a7 --- /dev/null +++ b/book/rust-docs/remote_settings/config/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"struct":["RemoteSettingsConfig"]}; \ No newline at end of file diff --git a/book/rust-docs/remote_settings/config/struct.RemoteSettingsConfig.html b/book/rust-docs/remote_settings/config/struct.RemoteSettingsConfig.html new file mode 100644 index 0000000000..f33736087c --- /dev/null +++ b/book/rust-docs/remote_settings/config/struct.RemoteSettingsConfig.html @@ -0,0 +1,23 @@ +RemoteSettingsConfig in remote_settings::config - Rust
pub struct RemoteSettingsConfig {
+    pub server_url: Option<String>,
+    pub bucket_name: Option<String>,
+    pub collection_name: String,
+}
Expand description

Custom configuration for the client. +Currently includes the following:

+
    +
  • server_url: The optional url for the settings server. If not specified, the standard server will be used.
  • +
  • bucket_name: The optional name of the bucket containing the collection on the server. If not specified, the standard bucket will be used.
  • +
  • collection_name: The name of the collection for the settings server.
  • +
+

Fields§

§server_url: Option<String>§bucket_name: Option<String>§collection_name: String

Trait Implementations§

source§

impl Clone for RemoteSettingsConfig

source§

fn clone(&self) -> RemoteSettingsConfig

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for RemoteSettingsConfig

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/remote_settings/error/enum.RemoteSettingsError.html b/book/rust-docs/remote_settings/error/enum.RemoteSettingsError.html new file mode 100644 index 0000000000..72c4fb54b3 --- /dev/null +++ b/book/rust-docs/remote_settings/error/enum.RemoteSettingsError.html @@ -0,0 +1,26 @@ +RemoteSettingsError in remote_settings::error - Rust
pub enum RemoteSettingsError {
+    JSONError(Error),
+    FileError(Error),
+    RequestError(Error),
+    UrlParsingError(ParseError),
+    BackoffError(u64),
+    ResponseError(String),
+    AttachmentsUnsupportedError,
+}

Variants§

§

JSONError(Error)

§

FileError(Error)

§

RequestError(Error)

An error has occured while sending a request.

+
§

UrlParsingError(ParseError)

An error has occured while parsing an URL.

+
§

BackoffError(u64)

The server has asked the client to backoff.

+
§

ResponseError(String)

The server returned an error code or the response was unexpected.

+
§

AttachmentsUnsupportedError

Trait Implementations§

source§

impl Debug for RemoteSettingsError

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Display for RemoteSettingsError

source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Error for RemoteSettingsError

source§

fn source(&self) -> Option<&(dyn Error + 'static)>

The lower-level source of this error, if any. Read more
1.0.0 · source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type based access to context intended for error reports. Read more
source§

impl From<Error> for RemoteSettingsError

source§

fn from(source: Error) -> Self

Converts to this type from the input type.
source§

impl From<Error> for RemoteSettingsError

source§

fn from(source: Error) -> Self

Converts to this type from the input type.
source§

impl From<Error> for RemoteSettingsError

source§

fn from(source: Error) -> Self

Converts to this type from the input type.
source§

impl From<ParseError> for RemoteSettingsError

source§

fn from(source: ParseError) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<E> Provider for Ewhere + E: Error + ?Sized,

source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (provide_any)
Data providers should implement this method to provide all values they are able to +provide by using demand. Read more
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/remote_settings/error/index.html b/book/rust-docs/remote_settings/error/index.html new file mode 100644 index 0000000000..1805627fd7 --- /dev/null +++ b/book/rust-docs/remote_settings/error/index.html @@ -0,0 +1 @@ +remote_settings::error - Rust
\ No newline at end of file diff --git a/book/rust-docs/remote_settings/error/sidebar-items.js b/book/rust-docs/remote_settings/error/sidebar-items.js new file mode 100644 index 0000000000..960a2e2eac --- /dev/null +++ b/book/rust-docs/remote_settings/error/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["RemoteSettingsError"],"type":["Result"]}; \ No newline at end of file diff --git a/book/rust-docs/remote_settings/error/type.Result.html b/book/rust-docs/remote_settings/error/type.Result.html new file mode 100644 index 0000000000..77a8e92f1e --- /dev/null +++ b/book/rust-docs/remote_settings/error/type.Result.html @@ -0,0 +1 @@ +Result in remote_settings::error - Rust

Type Definition remote_settings::error::Result

source ·
pub type Result<T, E = RemoteSettingsError> = Result<T, E>;
\ No newline at end of file diff --git a/book/rust-docs/remote_settings/index.html b/book/rust-docs/remote_settings/index.html new file mode 100644 index 0000000000..17b621c075 --- /dev/null +++ b/book/rust-docs/remote_settings/index.html @@ -0,0 +1,5 @@ +remote_settings - Rust

Crate remote_settings

source ·

Re-exports

Modules

  • This module defines the custom configurations that consumers can set. +Those configurations override default values and can be used to set a custom server url, +collection name, and bucket name. +The purpose of the configuration parameters are to allow consumers an easy debugging option, +and the ability to be explicit about the server.

Structs

\ No newline at end of file diff --git a/book/rust-docs/remote_settings/sidebar-items.js b/book/rust-docs/remote_settings/sidebar-items.js new file mode 100644 index 0000000000..4517046a9e --- /dev/null +++ b/book/rust-docs/remote_settings/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"mod":["client","config","error"],"struct":["RemoteSettings"]}; \ No newline at end of file diff --git a/book/rust-docs/remote_settings/struct.RemoteSettings.html b/book/rust-docs/remote_settings/struct.RemoteSettings.html new file mode 100644 index 0000000000..a1ffbace01 --- /dev/null +++ b/book/rust-docs/remote_settings/struct.RemoteSettings.html @@ -0,0 +1,21 @@ +RemoteSettings in remote_settings - Rust
pub struct RemoteSettings {
+    pub config: RemoteSettingsConfig,
+    /* private fields */
+}

Fields§

§config: RemoteSettingsConfig

Implementations§

source§

impl RemoteSettings

source

pub fn new(config: RemoteSettingsConfig) -> Result<Self>

source

pub fn get_records(&self) -> Result<RemoteSettingsResponse>

source

pub fn get_records_since( + &self, + timestamp: u64 +) -> Result<RemoteSettingsResponse>

source

pub fn download_attachment_to_path( + &self, + attachment_location: String, + path: String +) -> Result<()>

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/restmail_client/all.html b/book/rust-docs/restmail_client/all.html new file mode 100644 index 0000000000..9547ca5d69 --- /dev/null +++ b/book/rust-docs/restmail_client/all.html @@ -0,0 +1 @@ +List of all items in this crate

List of all items

Functions

\ No newline at end of file diff --git a/book/rust-docs/restmail_client/fn.clear_mailbox.html b/book/rust-docs/restmail_client/fn.clear_mailbox.html new file mode 100644 index 0000000000..d2107b4f87 --- /dev/null +++ b/book/rust-docs/restmail_client/fn.clear_mailbox.html @@ -0,0 +1 @@ +clear_mailbox in restmail_client - Rust
pub fn clear_mailbox(email: &str) -> Result<(), RestmailClientError>
\ No newline at end of file diff --git a/book/rust-docs/restmail_client/fn.find_email.html b/book/rust-docs/restmail_client/fn.find_email.html new file mode 100644 index 0000000000..cbcd51db6b --- /dev/null +++ b/book/rust-docs/restmail_client/fn.find_email.html @@ -0,0 +1,9 @@ +find_email in restmail_client - Rust
pub fn find_email<F>(
+    email: &str,
+    predicate: F,
+    max_tries: u8
+) -> Result<EmailJson, RestmailClientError>where
+    F: Fn(&EmailJson) -> bool,
Expand description

For a given restmail email, find the first email that satisfies the given predicate. +If no email is found, this function sleeps for a few seconds then tries again, up +to max_tries times.

+
\ No newline at end of file diff --git a/book/rust-docs/restmail_client/index.html b/book/rust-docs/restmail_client/index.html new file mode 100644 index 0000000000..ba79f40d45 --- /dev/null +++ b/book/rust-docs/restmail_client/index.html @@ -0,0 +1,3 @@ +restmail_client - Rust

Crate restmail_client

source ·

Functions

  • For a given restmail email, find the first email that satisfies the given predicate. +If no email is found, this function sleeps for a few seconds then tries again, up +to max_tries times.
\ No newline at end of file diff --git a/book/rust-docs/restmail_client/sidebar-items.js b/book/rust-docs/restmail_client/sidebar-items.js new file mode 100644 index 0000000000..fd573a1bf6 --- /dev/null +++ b/book/rust-docs/restmail_client/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["clear_mailbox","find_email"]}; \ No newline at end of file diff --git a/book/rust-docs/search-index.js b/book/rust-docs/search-index.js new file mode 100644 index 0000000000..e6baf0dde3 --- /dev/null +++ b/book/rust-docs/search-index.js @@ -0,0 +1,37 @@ +var searchIndex = JSON.parse('{\ +"as_ohttp_client":{"doc":"","t":"NNNNNNEDDDNDNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL","n":["CannotEncodeMessage","DuplicateHeaders","InvalidSession","KeyFetchFailed","MalformedKeyConfig","MalformedMessage","OhttpError","OhttpResponse","OhttpSession","OhttpTestServer","RelayFailed","TestServerRequest","UnsupportedKeyConfig","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","decapsulate","encapsulate","fmt","fmt","from","from","from","from","from","into","into","into","into","into","new","provide","to_string","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id"],"q":[[0,"as_ohttp_client"]],"d":["","","","","","","","","","","","","","","","","","","","","","","","Decode an OHTTP response returned in response to a request …","Encode an HTTP request in Binary HTTP format and then …","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Create a new encryption session for use with specific key …","","","","","","","","","","","","","","","","",""],"i":[5,5,5,5,5,5,0,0,0,0,5,0,5,1,4,15,16,5,1,4,15,16,5,1,1,5,5,1,4,15,16,5,1,4,15,16,5,1,5,5,1,4,15,16,5,1,4,15,16,5,1,4,15,16,5],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[1,[3,[2]]],[[6,[4,5]]]],[[1,7,7,7,7,[9,[8,8]],[3,[2]]],[[6,[[10,[2]],5]]]],[[5,11],12],[[5,11],12],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[[3,[2]]],[[6,[1,5]]]],[13],[[],8],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],6],[[],14],[[],14],[[],14],[[],14],[[],14]],"c":[],"p":[[3,"OhttpSession"],[15,"u8"],[15,"slice"],[3,"OhttpResponse"],[4,"OhttpError"],[4,"Result"],[15,"str"],[3,"String"],[3,"HashMap"],[3,"Vec"],[3,"Formatter"],[6,"Result"],[3,"Demand"],[3,"TypeId"],[3,"OhttpTestServer"],[3,"TestServerRequest"]]},\ +"autofill":{"doc":"","t":"CCCCAAACAODALLLALLLLALLAALLLLMFFFFFDALLLLALLLLLLLMMMMMLLLLLDDDMMMMMMMMMMMMLLLLLLLLLLLLMMMLLLMMMLLMMMLLLLLLLLLMMMMMLLLLLLLMLMMMMMMLMMMMMMMMMMLLLLLLLLLLLLLLLDDDLLLLLLMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLMMLLLLLLLMLLMMMMLLLLLLLLLLLLLLLRRDRRLLFLLLLLLLLLLDLLLLLLLLLLLLLFLLLLLLLLLLLLLGFFFGENNENNNNNNNNNNGNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMMMMNDENIIQQIALLLLKAAKKKKLLLLKKKLLKKKKKKKLLLLLLKLLMMDLLLLLALALLLLLAARDDRRILLLLLLLLLKKLLLLLLLKLLLLLLLLLLLL","n":["ApiResult","AutofillApiError","Error","Result","db","encryption","error","get_registered_sync_engine","sync","sync_merge_field_check","AutofillDb","addresses","begin_interrupt_scope","borrow","borrow_mut","credit_cards","deref","deref_mut","from","into","models","new","new_memory","schema","store","try_from","try_into","type_id","vzip","writer","touch","delete_credit_card","scrub_encrypted_credit_card_data","touch","update_credit_card","Metadata","address","borrow","borrow_mut","clone","clone_into","credit_card","default","eq","equivalent","fmt","from","into","merge","sync_change_counter","time_created","time_last_modified","time_last_used","times_used","to_owned","try_from","try_into","type_id","vzip","Address","InternalAddress","UpdatableAddressFields","additional_name","additional_name","additional_name","address_level1","address_level1","address_level1","address_level2","address_level2","address_level2","address_level3","address_level3","address_level3","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","clone_into","clone_into","clone_into","country","country","country","default","default","default","email","email","email","eq","equivalent","family_name","family_name","family_name","fmt","fmt","fmt","from","from","from","from","from_row","get_hash","given_name","given_name","given_name","guid","guid","hash","id","into","into","into","merge","metadata","metadata","metadata_mut","organization","organization","organization","postal_code","postal_code","postal_code","record_name","street_address","street_address","street_address","tel","tel","tel","time_created","time_last_modified","time_last_used","times_used","to_owned","to_owned","to_owned","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","vzip","vzip","vzip","CreditCard","InternalCreditCard","UpdatableCreditCardFields","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","cc_exp_month","cc_exp_month","cc_exp_month","cc_exp_year","cc_exp_year","cc_exp_year","cc_name","cc_name","cc_name","cc_number_enc","cc_number_enc","cc_number_enc","cc_number_last_4","cc_number_last_4","cc_number_last_4","cc_type","cc_type","cc_type","clone","clone","clone","clone_into","clone_into","clone_into","default","default","default","fmt","fmt","fmt","from","from","from","from","from_row","guid","guid","has_scrubbed_data","id","into","into","into","merge","metadata","metadata","metadata_mut","record_name","time_created","time_last_modified","time_last_used","times_used","to_owned","to_owned","to_owned","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","vzip","vzip","vzip","ADDRESS_COMMON_COLS","ADDRESS_COMMON_VALS","AutofillConnectionInitializer","CREDIT_CARD_COMMON_COLS","CREDIT_CARD_COMMON_VALS","borrow","borrow_mut","create_empty_sync_temp_tables","finish","from","init","into","prepare","try_from","try_into","type_id","upgrade_from","vzip","Store","add_address","add_credit_card","borrow","borrow_mut","create_addresses_sync_engine","create_credit_cards_sync_engine","delete_address","delete_credit_card","from","get_address","get_all_addresses","get_all_credit_cards","get_credit_card","get_registered_sync_engine","into","new","new_shared_memory","register_with_sync_manager","scrub_encrypted_data","touch_address","touch_credit_card","try_from","try_into","type_id","update_address","update_credit_card","vzip","EncryptorDecryptor","create_autofill_key","decrypt_string","encrypt_string","ApiResult","AutofillApiError","CryptoError","CryptoError","Error","IllegalDatabasePath","InterruptedError","InterruptedError","InvalidSyncPayload","IoError","JsonError","MissingEncryptionKey","NoSuchRecord","NoSuchRecord","OpenDatabaseError","Result","SqlError","SqlError","UnexpectedAutofillApiError","borrow","borrow","borrow_mut","borrow_mut","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","get_error_handling","into","into","provide","provide","source","to_string","to_string","try_from","try_from","try_into","try_into","type_id","type_id","vzip","vzip","guid","reason","reason","reason","Forked","IncomingState","MergeResult","Merged","ProcessIncomingRecordImpl","ProcessOutgoingRecordImpl","Record","Record","SyncRecord","address","borrow","borrow","borrow_mut","borrow_mut","change_record_guid","credit_card","engine","fetch_incoming_states","fetch_outgoing_records","finish_incoming","finish_synced_items","fmt","fmt","from","from","get_local_dupe","id","insert_local_record","into","into","merge","metadata","metadata_mut","record_name","remove_record","remove_tombstone","stage_incoming","try_from","try_from","try_into","try_into","type_id","type_id","update_local_record","vzip","vzip","forked","merged","AddressPayload","borrow","borrow_mut","default","deserialize","from","incoming","into","outgoing","serialize","try_from","try_into","type_id","vzip","incoming","outgoing","COLLECTION_SYNCID_META_KEY","ConfigSyncEngine","EngineConfig","GLOBAL_SYNCID_META_KEY","LAST_SYNC_META_KEY","SyncEngineStorageImpl","apply","borrow","borrow","borrow_mut","borrow_mut","collection_name","from","from","get_collection_request","get_incoming_impl","get_outgoing_impl","get_sync_assoc","into","into","new","prepare_for_sync","reset","reset_local_sync_data","reset_storage","set_local_encryption_key","set_uploaded","stage_incoming","try_from","try_from","try_into","try_into","type_id","type_id","vzip","vzip","wipe"],"q":[[0,"autofill"],[10,"autofill::db"],[30,"autofill::db::addresses"],[31,"autofill::db::credit_cards"],[35,"autofill::db::models"],[59,"autofill::db::models::address"],[155,"autofill::db::models::credit_card"],[230,"autofill::db::schema"],[248,"autofill::db::store"],[276,"autofill::encryption"],[280,"autofill::error"],[331,"autofill::error::AutofillApiError"],[335,"autofill::sync"],[381,"autofill::sync::MergeResult"],[383,"autofill::sync::address"],[397,"autofill::sync::credit_card"],[399,"autofill::sync::engine"]],"d":["","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Calls U::from(self).","","","","","","","","","","","","","","","","Metadata that’s common between the records.","","","","","","","","","","","Returns the argument unchanged.","Calls U::from(self).","Merge the metadata from other, and possibly mirror, into …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","","Returns the argument unchanged.","Returns the argument unchanged.","","","","","","","","","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Performs a three-way merge between an incoming, local, and …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","","Returns the argument unchanged.","Returns the argument unchanged.","","","","","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Performs a three-way merge between an incoming, local, and …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","","Calls U::from(self).","","","","","","","","","","","","","","","","Returns the argument unchanged.","","","","","Called by the sync manager to get a sync engine via the …","Calls U::from(self).","","Creates a store backed by an in-memory database that …","","","","","","","","","","","","","","","Result enum for the public API","","","","","","","","","","","","","","","Result enum for internal functions","","","","","","","","","","","","Returns the argument unchanged.","","","","","","","Returns the argument unchanged.","","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Finish the incoming phase. This will typically caused …","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns a local record that has the same values as the …","","","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","","","","","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","",""],"i":[0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,1,1,1,1,0,1,1,0,0,1,1,1,1,1,0,0,0,0,0,0,0,13,13,13,13,0,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,0,0,0,17,18,19,17,18,19,17,18,19,17,18,19,17,18,19,17,18,19,17,18,19,17,18,19,17,18,19,17,18,19,17,18,19,18,18,17,18,19,17,18,19,17,18,18,19,19,18,17,18,19,18,19,18,19,17,18,19,19,19,19,19,17,18,19,17,18,19,19,17,18,19,17,18,19,18,18,18,18,17,18,19,17,18,19,17,18,19,17,18,19,17,18,19,0,0,0,12,28,29,12,28,29,12,28,29,12,28,29,12,28,29,12,28,29,12,28,29,12,28,29,12,28,29,12,28,29,12,28,29,12,28,29,12,28,28,29,29,28,29,29,29,12,28,29,29,29,29,29,29,28,28,28,28,12,28,29,12,28,29,12,28,29,12,28,29,12,28,29,0,0,0,0,0,31,31,0,31,31,31,31,31,31,31,31,31,31,0,34,34,34,34,34,34,34,34,34,34,34,34,34,0,34,34,34,34,34,34,34,34,34,34,34,34,34,0,0,0,0,0,0,42,43,0,43,42,43,43,43,43,43,42,43,43,0,42,43,42,42,43,42,43,42,42,43,43,42,43,43,43,43,43,43,43,43,42,43,42,43,43,42,43,42,43,42,43,42,43,42,43,73,74,75,76,27,0,0,27,0,0,67,68,0,0,27,52,27,52,67,0,0,67,68,67,68,27,52,27,52,67,61,67,27,52,61,61,61,61,67,67,67,27,52,27,52,27,52,67,27,52,77,78,0,58,58,58,58,58,0,58,0,58,58,58,58,58,0,0,0,0,0,0,0,0,62,70,62,70,62,62,70,62,62,71,71,62,70,62,62,62,62,62,71,62,62,62,70,62,70,62,70,62,70,62,62],"f":[0,0,0,0,0,0,0,0,0,0,0,0,[1,[[3,[2]]]],[[]],[[]],0,[1],[1],[[]],[[]],0,[[[5,[4]]],[[3,[1]]]],[6,[[3,[1]]]],0,0,[[],7],[[],7],[[],8],[[]],0,[[9,10],3],[[9,10],[[3,[11]]]],[9,3],[[9,10],3],[[9,10,12],3],0,0,[[]],[[]],[13,13],[[]],0,[[],13],[[13,13],11],[[],11],[[13,14],15],[[]],[[]],[[13,13,[16,[13]]]],0,0,0,0,0,[[]],[[],7],[[],7],[[],8],[[]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[17,17],[18,18],[19,19],[[]],[[]],[[]],0,0,0,[[],17],[[],18],[[],19],0,0,0,[[18,18],11],[[],11],0,0,0,[[17,14],15],[[18,14],15],[[19,14],15],[[]],[19,18],[[]],[[]],[20,[[7,[19,21]]]],[[[0,[22,23]],24],25],0,0,0,0,0,[[18,26]],[19,10],[[]],[[]],[[]],[[19,19,[16,[19]]],[[27,[19]]]],[19,13],0,[19,13],0,0,0,0,0,0,[[],6],0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],8],[[],8],[[],8],[[]],[[]],[[]],0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[12,12],[28,28],[29,29],[[]],[[]],[[]],[[],12],[[],28],[[],29],[[12,14],15],[[28,14],15],[[29,14],15],[[]],[29,28],[[]],[[]],[20,[[7,[29,21]]]],0,0,[29,11],[29,10],[[]],[[]],[[]],[[29,29,[16,[29]]],[[27,[29]]]],[29,13],0,[29,13],[[],6],0,0,0,0,[[]],[[]],[[]],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],8],[[],8],[[],8],[[]],[[]],[[]],0,0,0,0,0,[[]],[[]],[9,30],[[31,9],30],[[]],[[31,32],30],[[]],[[31,9,11],30],[[],7],[[],7],[[],8],[[31,32,33],30],[[]],0,[[34,17],[[35,[18]]]],[[34,12],[[35,[28]]]],[[]],[[]],[[[36,[34]]],[[38,[37]]]],[[[36,[34]]],[[38,[37]]]],[[34,39],[[35,[11]]]],[[34,39],[[35,[11]]]],[[]],[[34,39],[[35,[18]]]],[34,[[35,[[40,[18]]]]]],[34,[[35,[[40,[28]]]]]],[[34,39],[[35,[28]]]],[41,[[16,[[38,[37]]]]]],[[]],[[[5,[4]]],[[35,[34]]]],[6,[[35,[34]]]],[[[36,[34]]]],[[[36,[34]]],35],[[34,39],35],[[34,39],35],[[],7],[[],7],[[],8],[[34,39,17],35],[[34,39,12],35],[[]],0,[[],[[35,[39]]]],[[39,39],[[35,[39]]]],[[39,39],[[35,[39]]]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[42,14],15],[[42,14],15],[[43,14],15],[[43,14],15],[[]],[44,43],[45,43],[46,43],[21,43],[47,43],[48,43],[[]],[43,49],[[]],[[]],[50],[50],[43,[[16,[51]]]],[[],39],[[],39],[[],7],[[],7],[[],7],[[],7],[[],8],[[],8],[[]],[[]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[32,10,10],3],0,0,[32,[[3,[[40,[52]]]]]],[32,[[54,[[40,[53]]]]]],[32,3],[[32,[40,[10]]],54],[[[27,[55]],14],15],[[[52,[55]],14],15],[[]],[[]],[32,[[3,[16]]]],[[],10],[32,3],[[]],[[]],[[23,23,[16,[23]]],[[27,[23]]]],[[],13],[[],13],[[],6],[[32,10],3],[[32,10],3],[[32,[40,[56]],57],3],[[],7],[[],7],[[],7],[[],7],[[],8],[[],8],[[32,11],3],[[]],[[]],0,0,0,[[]],[[]],[[],58],[59,[[7,[58]]]],[[]],0,[[]],0,[[58,60],7],[[],7],[[],7],[[],8],[[]],0,0,0,0,0,0,0,0,[[[62,[[0,[61,55]]]],63,64],[[54,[[40,[53]]]]]],[[]],[[]],[[]],[[]],[[[62,[[0,[61,55]]]]],65],[[]],[[]],[[[62,[[0,[61,55]]]],63],[[54,[[16,[66]]]]]],[[[16,[39]]],[[3,[[38,[67]]]]]],[[[16,[39]]],[[3,[[38,[68]]]]]],[[[62,[[0,[61,55]]]]],[[54,[69]]]],[[]],[[]],[[70,[36,[34]],[38,[71]]],62],[[[62,[[0,[61,55]]]],72],54],[[[62,[[0,[61,55]]]],69],54],[62,3],[32,3],[[[62,[[0,[61,55]]]],6],54],[[[62,[[0,[61,55]]]],63,[40,[10]]],54],[[[62,[[0,[61,55]]]],[40,[56]],64],54],[[],7],[[],7],[[],7],[[],7],[[],8],[[],8],[[]],[[]],[[[62,[[0,[61,55]]]]],54]],"c":[],"p":[[3,"AutofillDb"],[3,"SqlInterruptScope"],[6,"Result"],[3,"Path"],[8,"AsRef"],[15,"str"],[4,"Result"],[3,"TypeId"],[3,"Connection"],[3,"Guid"],[15,"bool"],[3,"UpdatableCreditCardFields"],[3,"Metadata"],[3,"Formatter"],[6,"Result"],[4,"Option"],[3,"UpdatableAddressFields"],[3,"Address"],[3,"InternalAddress"],[3,"Row"],[4,"Error"],[8,"Hash"],[8,"Sized"],[8,"BuildHasher"],[15,"u64"],[8,"Hasher"],[4,"MergeResult"],[3,"CreditCard"],[3,"InternalCreditCard"],[6,"Result"],[3,"AutofillConnectionInitializer"],[3,"Transaction"],[15,"u32"],[3,"Store"],[6,"ApiResult"],[3,"Arc"],[8,"SyncEngine"],[3,"Box"],[3,"String"],[3,"Vec"],[4,"SyncEngineId"],[4,"AutofillApiError"],[4,"Error"],[4,"Error"],[3,"Interrupted"],[3,"Error"],[3,"EncryptorDecryptorError"],[3,"Error"],[3,"ErrorHandling"],[3,"Demand"],[8,"Error"],[3,"IncomingState"],[3,"OutgoingBso"],[6,"Result"],[8,"Debug"],[3,"IncomingBso"],[8,"Interruptee"],[3,"AddressPayload"],[8,"Deserializer"],[8,"Serializer"],[8,"SyncRecord"],[3,"ConfigSyncEngine"],[3,"ServerTimestamp"],[3,"Engine"],[6,"CollectionName"],[3,"CollectionRequest"],[8,"ProcessIncomingRecordImpl"],[8,"ProcessOutgoingRecordImpl"],[4,"EngineSyncAssociation"],[3,"EngineConfig"],[8,"SyncEngineStorageImpl"],[8,"Fn"],[13,"NoSuchRecord"],[13,"SqlError"],[13,"CryptoError"],[13,"UnexpectedAutofillApiError"],[13,"Forked"],[13,"Merged"]]},\ +"cli_support":{"doc":"","t":"CAFFFADMLLLLMLFFFLMMLLLLFFF","n":["env_logger","fxa_creds","init_logging","init_logging_with","init_trace_logging","prompt","CliFxa","account","as_auth_info","as_key_bundle","borrow","borrow_mut","client_init","from","get_account_and_token","get_cli_fxa","get_default_fxa_config","into","token_info","tokenserver_url","try_from","try_into","type_id","vzip","prompt_char","prompt_string","prompt_usize"],"q":[[0,"cli_support"],[6,"cli_support::fxa_creds"],[24,"cli_support::prompt"]],"d":["","","","","","","","","","","","","","Returns the argument unchanged.","","","","Calls U::from(self).","","","","","","","","",""],"i":[0,0,0,0,0,0,0,2,2,2,2,2,2,2,0,0,0,2,2,2,2,2,2,2,0,0,0],"f":[0,0,[[]],[1],[[]],0,0,0,[2,3],[2,[[5,[4]]]],[[]],[[]],0,[[]],[[6,1],5],[[6,1],[[5,[2]]]],[[],6],[[]],0,0,[[],7],[[],7],[[],8],[[]],[1,[[10,[9]]]],[[[11,[1]]],[[10,[12]]]],[[[11,[1]]],[[10,[13]]]]],"c":[],"p":[[15,"str"],[3,"CliFxa"],[3,"SyncAuthInfo"],[3,"KeyBundle"],[6,"Result"],[3,"FxaConfig"],[4,"Result"],[3,"TypeId"],[15,"char"],[4,"Option"],[8,"AsRef"],[3,"String"],[15,"usize"]]},\ +"crashtest":{"doc":"Crash Test Helper APIs","t":"ENLLLLLLLLFFFLLL","n":["CrashTestError","ErrorFromTheRustCode","borrow","borrow_mut","fmt","fmt","from","into","provide","to_string","trigger_rust_abort","trigger_rust_error","trigger_rust_panic","try_from","try_into","type_id"],"q":[[0,"crashtest"]],"d":["An error that can be returned from Rust code.","","","","","","Returns the argument unchanged.","Calls U::from(self).","","","Trigger a hard abort inside the Rust code.","Trigger an error inside the Rust code.","Trigger a panic inside the Rust code.","","",""],"i":[0,1,1,1,1,1,1,1,1,1,0,0,0,1,1,1],"f":[0,0,[[]],[[]],[[1,2],3],[[1,2],3],[[]],[[]],[4],[[],5],[[]],[[],[[6,[1]]]],[[]],[[],6],[[],6],[[],7]],"c":[],"p":[[4,"CrashTestError"],[3,"Formatter"],[6,"Result"],[3,"Demand"],[3,"String"],[4,"Result"],[3,"TypeId"]]},\ +"embedded_uniffi_bindgen":{"doc":"","t":"F","n":["main"],"q":[[0,"embedded_uniffi_bindgen"]],"d":[""],"i":[0],"f":[[[]]],"c":[],"p":[]},\ +"error_support":{"doc":"","t":"IDDQICLLLLOLFLOOOMLLLKXLLLLLFFLFKKLOFMFOLLLLLLF","n":["ApplicationErrorReporter","ErrorHandling","ErrorReporting","ExternalError","GetErrorHandling","backtrace","borrow","borrow","borrow_mut","borrow_mut","breadcrumb","convert","convert_log_report_error","default","define_error","define_error_conversions","define_error_wrapper","err","fmt","from","from","get_error_handling","handle_error","into","into","log","log_info","log_warning","redact_compact_jwe","redact_url","report","report_breadcrumb","report_breadcrumb","report_error","report_error","report_error","report_error_to_app","reporting","set_application_error_reporter","trace_error","try_from","try_from","try_into","try_into","type_id","type_id","unset_application_error_reporter"],"q":[[0,"error_support"]],"d":["Application error reporting trait","Specifies how an “internal” error is converted to an …","Describes what error reporting action should be taken.","","A trait to define how errors are converted and reported.","Re-export of the backtrace crate for use in macros and to …","","","","","Tell the application to log a breadcrumb","Create an ErrorHandling instance with an error conversion.","Handle the specified “internal” error, taking any …","","All the error boilerplate (okay, with a couple exceptions …","Define a set of conversions from external error types into …","XXX - Most of this is now considered deprecated - only FxA …","The external error that should be returned.","","Returns the argument unchanged.","Returns the argument unchanged.","Return how to handle our internal errors","A procedural macro that exposes internal errors to …","Calls U::from(self).","Calls U::from(self).","Add logging to an ErrorHandling instance","log an info","log a warning","Redact compact jwe string (Five base64 segments, separated …","Redact a URL.","Add reporting to an ErrorHandling instance","","Send a breadcrumb to a Sentry-like error reporting system","Send an error report to a Sentry-like error reporting …","Add reporting to an ErrorHandling instance and also log an …","Tell the application to report an error","","How the error should be reported.","","Log a breadcrumb if we see an Result::Err value","","","","","","",""],"i":[0,0,0,2,0,0,1,4,1,4,0,1,0,4,0,0,0,1,4,1,4,2,0,1,4,1,1,1,0,0,1,0,12,12,1,0,0,1,0,0,1,4,1,4,1,4,0],"f":[0,0,0,0,0,0,[[]],[[]],[[]],[[]],0,[[],1],[[[0,[2,3]]],3],[[],4],0,0,0,0,[[4,5],6],[[]],[[]],[[],1],0,[[]],[[]],[[1,7],1],[1,1],[1,1],[8,9],[8,9],[[1,[10,[9]]],1],[[9,9,11,11]],[[9,9,11,11]],[[9,9]],[[1,[10,[9]]],1],0,[[9,9]],0,[[[13,[12]]]],0,[[],14],[[],14],[[],14],[[],14],[[],15],[[],15],[[]]],"c":[],"p":[[3,"ErrorHandling"],[8,"GetErrorHandling"],[8,"Error"],[3,"ErrorReporting"],[3,"Formatter"],[6,"Result"],[4,"Level"],[15,"str"],[3,"String"],[8,"Into"],[15,"u32"],[8,"ApplicationErrorReporter"],[3,"Box"],[4,"Result"],[3,"TypeId"]]},\ +"error_support_macros":{"doc":"","t":"X","n":["handle_error"],"q":[[0,"error_support_macros"]],"d":["A procedural macro that exposes internal errors to …"],"i":[0],"f":[0],"c":[],"p":[]},\ +"examples_fxa_client":{"doc":"","t":"HHNDENNNHNNENNLLLLLLLLLLLLLLMLALLLLLLLLLLLLLLLLFFLFAMLLLLLLLLLLLLLLLLLLLEDNNLLLLLLLLMLLLLLLLLLLFFFLLLLLLLLLLLLMENNDLLLLLLLLMLLLLLLLLLLFFFLLLLLLLLLLLLMMM","n":["CLIENT_ID","CREDENTIALS_PATH","China","Cli","Command","Devices","Disconnect","LocalDev","REDIRECT_URI","Release","SendTab","Server","Stable","Stage","augment_args","augment_args_for_update","augment_subcommands","augment_subcommands_for_update","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","clone","clone_into","cmp","command","command","command_for_update","devices","eq","equivalent","equivalent","fmt","from","from","from","from_arg_matches","from_arg_matches","from_arg_matches_mut","from_arg_matches_mut","group_id","has_subcommand","into","into","into","load_account","main","partial_cmp","persist_fxa_state","send_tab","server","to_owned","to_possible_value","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","update_from_arg_matches","update_from_arg_matches","update_from_arg_matches_mut","update_from_arg_matches_mut","value_variants","vzip","vzip","vzip","Command","DeviceArgs","List","SetName","augment_args","augment_args_for_update","augment_subcommands","augment_subcommands_for_update","borrow","borrow","borrow_mut","borrow_mut","command","from","from","from_arg_matches","from_arg_matches","from_arg_matches_mut","from_arg_matches_mut","group_id","has_subcommand","into","into","list","run","set_name","try_from","try_from","try_into","try_into","type_id","type_id","update_from_arg_matches","update_from_arg_matches","update_from_arg_matches_mut","update_from_arg_matches_mut","vzip","vzip","name","Command","Poll","Send","SendTabArgs","augment_args","augment_args_for_update","augment_subcommands","augment_subcommands_for_update","borrow","borrow","borrow_mut","borrow_mut","command","from","from","from_arg_matches","from_arg_matches","from_arg_matches_mut","from_arg_matches_mut","group_id","has_subcommand","into","into","poll","run","send","try_from","try_from","try_into","try_into","type_id","type_id","update_from_arg_matches","update_from_arg_matches","update_from_arg_matches_mut","update_from_arg_matches_mut","vzip","vzip","device_id","title","url"],"q":[[0,"examples_fxa_client"],[72,"examples_fxa_client::devices"],[110,"examples_fxa_client::devices::Command"],[111,"examples_fxa_client::send_tab"],[149,"examples_fxa_client::send_tab::Command"]],"d":["","","China server","","","","","local dev sever","","Official server","","","stable dev sever","staging dev sever","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","","","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","The FxA server to use","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","","","","","","","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","","","Perform a single poll for tabs sent to this device","Send a tab to another device","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","","","","","","","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","Device ID (use the devices command to list)","",""],"i":[0,0,2,0,0,11,11,2,0,2,11,0,2,2,8,8,11,11,8,2,11,8,2,11,2,2,2,8,8,8,0,2,2,2,2,8,2,11,8,11,8,11,8,11,8,2,11,0,0,2,0,0,8,2,2,8,2,11,8,2,11,8,2,11,8,11,8,11,2,8,2,11,0,0,21,21,20,20,21,21,20,21,20,21,20,20,21,20,21,20,21,20,21,20,21,0,0,0,20,21,20,21,20,21,20,21,20,21,20,21,25,0,24,24,0,23,23,24,24,23,24,23,24,23,23,24,23,24,23,24,23,24,23,24,0,0,0,23,24,23,24,23,24,23,24,23,24,23,24,26,26,26],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,[1,1],[1,1],[1,1],[1,1],[[]],[[]],[[]],[[]],[[]],[[]],[2,2],[[]],[[2,2],3],[[],1],0,[[],1],0,[[2,2],4],[[],4],[[],4],[[2,5],6],[[]],[[]],[[]],[7,[[10,[8,9]]]],[7,[[10,[11,9]]]],[7,[[10,[8,9]]]],[7,[[10,[11,9]]]],[[],[[13,[12]]]],[14,4],[[]],[[]],[[]],[8,[[16,[15]]]],[[],16],[[2,2],[[13,[3]]]],[15,16],0,0,[[]],[2,[[13,[17]]]],[[],10],[[],10],[[],10],[[],10],[[],10],[[],10],[[],18],[[],18],[[],18],[[8,7],[[10,[9]]]],[[11,7],[[10,[9]]]],[[8,7],[[10,[9]]]],[[11,7],[[10,[9]]]],[[],[[19,[2]]]],[[]],[[]],[[]],0,0,0,0,[1,1],[1,1],[1,1],[1,1],[[]],[[]],[[]],[[]],0,[[]],[[]],[7,[[10,[20,9]]]],[7,[[10,[21,9]]]],[7,[[10,[20,9]]]],[7,[[10,[21,9]]]],[[],[[13,[12]]]],[14,4],[[]],[[]],[15,16],[[15,20],16],[[15,22],16],[[],10],[[],10],[[],10],[[],10],[[],18],[[],18],[[20,7],[[10,[9]]]],[[21,7],[[10,[9]]]],[[20,7],[[10,[9]]]],[[21,7],[[10,[9]]]],[[]],[[]],0,0,0,0,0,[1,1],[1,1],[1,1],[1,1],[[]],[[]],[[]],[[]],0,[[]],[[]],[7,[[10,[23,9]]]],[7,[[10,[24,9]]]],[7,[[10,[23,9]]]],[7,[[10,[24,9]]]],[[],[[13,[12]]]],[14,4],[[]],[[]],[15,16],[[15,23],16],[[15,22,22,22],16],[[],10],[[],10],[[],10],[[],10],[[],18],[[],18],[[23,7],[[10,[9]]]],[[24,7],[[10,[9]]]],[[23,7],[[10,[9]]]],[[24,7],[[10,[9]]]],[[]],[[]],0,0,0],"c":[],"p":[[3,"Command"],[4,"Server"],[4,"Ordering"],[15,"bool"],[3,"Formatter"],[6,"Result"],[3,"ArgMatches"],[3,"Cli"],[6,"Error"],[4,"Result"],[4,"Command"],[3,"Id"],[4,"Option"],[15,"str"],[3,"FirefoxAccount"],[6,"Result"],[3,"PossibleValue"],[3,"TypeId"],[15,"slice"],[3,"DeviceArgs"],[4,"Command"],[3,"String"],[3,"SendTabArgs"],[4,"Command"],[13,"SetName"],[13,"Send"]]},\ +"fxa_client":{"doc":"Firefox Accounts Client","t":"DNNENGDNNNDDNNNNNNNNNDENNDENNEDDEEENNNENNNNNNDNDNNNNNNNNNNNNNNNNDNNNNGNDNNDNNNNNNDNNNNNNNNNNNNNMMMLMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMMLLLLMMMLLLLLLLLLLLLLLMMLMLLLLLLLMMMMLMMMMMLMLLLLLLMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMMLLLLLLLLLLLLLLLLLLLLLLLMMMMMLMMMMMMLLMLLLMMMMMMLMMMMLLLLLLMLLLLLLLMMMLLLLLLLLLLMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMMLLLLLLLLLLLLLLLLLLLLLLMMMMMMMMMMMM","n":["AccessTokenInfo","AccountAuthStateChanged","AccountDestroyed","AccountEvent","ApiClientError","ApiResult","AttachedClient","AuthCircuitBreakerError","AuthIssues","Authentication","AuthorizationInfo","AuthorizationParameters","BackoffError","Base64Decode","China","CommandNotFound","CommandReceived","Connected","CryptoError","Custom","Desktop","Device","DeviceCapability","DeviceConnected","DeviceDisconnected","DevicePushSubscription","DeviceType","Disconnected","EceError","Error","FirefoxAccount","FxaConfig","FxaError","FxaRustAuthState","FxaServer","HawkError","HexDecodeError","IllegalState","IncomingDeviceCommand","IntegerConversionError","InvalidBufferLength","InvalidPushEvent","JsonError","JwCryptoError","LocalDev","LocalDevice","MalformedUrl","MetricsParams","MismatchedKeys","MissingUrlParameter","Mobile","MultipleScopesRequested","Network","NoCachedToken","NoCurrentDeviceId","NoExistingAuthFlow","NoMigrationData","NoRefreshToken","NoScopedKey","NoSessionToken","NullPointer","OriginMismatch","Other","Panic","Profile","ProfileUpdated","Release","RemoteError","RequestError","Result","ScopeNotAllowed","ScopedKey","SendTab","SendTabDiagnosisError","SendTabPayload","Stable","Stage","SyncError","SyncScopedKeyMissingInServerResponse","SyncScopedKeyMissingInServerResponse","TV","TabHistoryEntry","TabReceived","Tablet","UTF8DecodeError","UnexpectedStatus","Unknown","Unknown","UnknownCommand","UnknownOAuthState","UnknownTargetDevice","UnsupportedCommand","VR","WrongAuthFlow","XorLengthMismatch","access_type","active","auth_key","authorize_code_using_session_token","avatar","begin_oauth_flow","begin_pairing_flow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","capabilities","capabilities","check_authorization_status","china","clear_access_token_cache","clear_device_name","client_id","client_id","client_id","clone","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","code_challenge","code_challenge_method","complete_oauth_flow","created_time","default","deserialize","deserialize","deserialize","deserialize","deserialize","dev","device_id","device_type","device_type","device_type","disconnect","display_name","display_name","display_name","email","endpoint","ensure_capabilities","entries","eq","eq","eq","equivalent","equivalent","equivalent","expires_at","flow_id","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from_json","gather_telemetry","get_access_token","get_attached_clients","get_auth_state","get_connection_success_url","get_current_device_id","get_devices","get_error_handling","get_hash","get_hash","get_manage_account_url","get_manage_devices_url","get_pairing_authority_url","get_profile","get_session_token","get_token_server_endpoint_url","handle_push_message","handle_session_token_change","hash","hash","id","id","initialize_device","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","is_current_device","is_current_session","is_default_avatar","k","key","key_bytes","keys_jwk","kid","kty","last_access_time","last_access_time","name","new","on_auth_issues","parameters","poll_device_commands","provide","provide","public_key","push_endpoint_expired","push_endpoint_expired","push_subscription","push_subscription","redirect_uri","release","scope","scope","scope","scope","send_single_tab","serialize","serialize","serialize","serialize","serialize","server","set_device_name","set_push_subscription","simulate_permanent_auth_token_issue","simulate_temporary_auth_token_issue","source","stable","stage","state","stream_id","title","to_json","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_string","to_string","token","token_server_url_override","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","uid","url","vzip","vzip","vzip","vzip","vzip","vzip","vzip","vzip","vzip","vzip","vzip","vzip","vzip","vzip","vzip","vzip","vzip","vzip","vzip","vzip","vzip","vzip","command","device_id","device_name","is_local_device","code","errno","error","info","message","url","payload","sender"],"q":[[0,"fxa_client"],[449,"fxa_client::AccountEvent"],[453,"fxa_client::Error"],[458,"fxa_client::FxaServer"],[459,"fxa_client::IncomingDeviceCommand"]],"d":["An OAuth access token, with its associated keys and …","Sent when when there has been a change in authorization …","Sent when the user deletes their Firefox Account.","An event that happened on the user’s account.","","Result returned by public-facing API functions","A client connected to the user’s account.","","","Thrown when there was a problem with the authentication …","Information about the authorization state of the …","Parameters provided in an incoming OAuth request.","","","","","Sent when another device has invoked a command for this …","","","","","A device connected to the user’s account.","A “capability” offered by a device.","Sent when a new device connects to the user’s account.","Sent when a device disconnects from the user’s account.","Details of a web-push subscription endpoint.","Enumeration for the different types of device.","","","FxA internal error type These are used in the internal …","Object representing the signed-in state of an application.","","Public error type thrown by many [FirefoxAccount] …","High-level view of the authorization state","","","","","A command invoked by another device.","","","","","","","Local device that’s connecting to FxA","","Additional metrics tracking parameters to include in an …","","","","","Thrown if an operation fails due to network access …","","","Thrown if the application attempts to complete an OAuth …","","","","","","","A catch-all for other unspecified errors.","Thrown if there is a panic in the underlying Rust code.","Information about the user that controls a Firefox Account.","Sent when the user has modified their account profile …","","","","Result returned by internal functions","","A cryptograpic key associated with an OAuth scope.","","","The payload sent when invoking a “send tab” command.","","","","A scoped key was missing in the server response when …","","","An individual entry in the navigation history of a sent …","Indicates that a tab has been sent to this device.","","","","","An unknown event, most likely an event the client doesn’…","","","","","","Thrown if the application attempts to complete an OAuth …","","","","","Create a new OAuth authorization code using the stored …","The URL of a profile picture representing the user.","Initiate a web-based OAuth sign-in flow.","Initiate a device-pairing sign-in flow.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Check authorization status for this application.","","Clear the access token cache in response to an auth …","Clear any custom display name used for this application …","","","registered OAuth client id of the application.","","","","","","","","","","","","","","","","","Complete an OAuth flow.","","","","","","","","","","","","","Disconnect from the user’s account.","The user’s preferred textual display name.","","","The user’s current primary email address.","","Ensure that the device record has a specific set of …","The navigation history of the sent tab.","","","","","","","The expiry time of the token, in seconds.","A unique identifier to be included in send-tab metrics.","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","","","","Returns the argument unchanged.","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Restore a FirefoxAccount instance from serialized state.","Collect and return telemetry about send-tab attempts.","Get a short-lived OAuth access token for the user’s …","Get the list of all client applications attached to the …","Get the high-level authentication state of the client","Get a URL which shows a “successfully connected!” …","Get the device id registered for this application.","Get the list of devices registered on the user’s account.","","","","Get a URL at which the user can manage their account and …","Get a URL at which the user can manage the devices …","Get the URL at which to begin a device-pairing signin flow.","Get profile information for the signed-in user, if any.","Get the session token for the user’s account, if one is …","Get the token server URL","Process and respond to a server-delivered account update …","Update the stored session token for the user’s account.","","","","","Create a new device record for this application.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","Whether the avatar URL represents the default avatar image.","The key material, as base64-url-encoded bytes.","The client-side encryption key associated with this scope.","","","An opaque unique identifier for this key.","The type of key.","","","","Create a new FirefoxAccount instance, not connected to any …","Update the state based on authentication issues.","","Poll the server for any pending device commands.","","","","","","","","redirect_uri - the registered OAuth redirect URI of the …","","","","The scope of access granted by token.","The OAuth scope with which this key is associated.","Use device commands to send a single tab to another device.","","","","","","FxaServer to connect with","Update the display name used for this application instance.","Set or update a push subscription endpoint for this device.","Used by the application to test auth token issues","Used by the application to test auth token issues","","","","","A unique identifier to be included in send-tab metrics.","","Save current state to a JSON string.","","","","","","","","","","The access token itself.","URL for the user’s Sync Tokenserver. This can be used to …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","The user’s account uid","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"i":[0,30,30,0,28,0,0,28,22,27,0,0,28,28,18,28,30,22,28,18,15,0,0,30,30,0,0,22,28,0,0,0,0,0,0,28,28,28,0,28,28,28,28,28,18,0,28,0,28,28,15,28,27,28,28,27,28,28,28,28,28,28,27,27,0,30,18,28,28,0,28,0,14,28,0,18,18,28,27,28,15,0,31,15,28,28,15,30,28,28,28,28,15,27,28,2,10,16,1,55,1,1,10,8,49,55,2,1,22,13,26,14,27,28,15,16,30,31,32,33,34,17,12,18,10,8,49,55,2,1,22,13,26,14,27,28,15,16,30,31,32,33,34,17,12,18,13,26,1,12,1,1,49,2,12,13,14,15,16,17,12,18,13,14,15,16,17,12,18,2,2,1,49,15,13,14,15,16,17,12,49,49,13,26,1,55,13,26,55,16,1,32,22,14,15,22,14,15,34,32,22,13,26,14,27,27,28,28,15,16,30,31,32,33,34,17,12,18,10,8,49,55,2,1,22,13,26,14,27,28,28,28,28,28,28,28,28,28,28,28,28,28,28,15,16,30,31,32,33,34,17,12,18,1,1,1,1,1,1,1,1,28,14,15,1,1,1,1,1,1,1,1,14,15,13,26,1,10,8,49,55,2,1,22,13,26,14,27,28,15,16,30,31,32,33,34,17,12,18,26,49,55,17,34,17,2,17,17,49,26,49,1,1,8,1,27,28,16,13,26,13,26,12,12,49,2,34,17,1,13,14,15,16,17,12,1,1,1,1,28,12,12,2,32,33,1,13,14,15,16,17,12,18,27,28,34,12,10,8,49,55,2,2,1,22,13,26,14,14,27,28,15,16,30,31,32,33,34,17,12,18,10,8,49,55,2,1,22,13,26,14,27,28,15,16,30,31,32,33,34,17,12,18,10,8,49,55,2,1,22,13,26,14,27,28,15,16,30,31,32,33,34,17,12,18,55,33,10,8,49,55,2,1,22,13,26,14,27,28,15,16,30,31,32,33,34,17,12,18,64,65,66,65,67,67,67,67,67,68,69,69],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[1,2],[[4,[3]]]],0,[[1,[7,[[6,[5]]]],5,[9,[8]]],[[4,[3]]]],[[1,5,[7,[3]],5,[9,[8]]],[[4,[3]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,[1,[[4,[10]]]],[[11,11],12],[1],[1,4],0,0,0,[13,13],[14,14],[15,15],[16,16],[17,17],[12,12],[18,18],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,[[1,5,5],4],0,[[],15],[19,[[20,[13]]]],[19,[[20,[14]]]],[19,[[20,[15]]]],[19,[[20,[16]]]],[19,[[20,[17]]]],[[11,11],12],0,0,0,0,[1],0,0,0,0,0,[[1,[21,[14]]],[[4,[13]]]],0,[[22,22],23],[[14,14],23],[[15,15],23],[[],23],[[],23],[[],23],0,0,[[22,24],25],[[13,24],25],[[26,24],25],[[14,24],25],[[27,24],25],[[27,24],25],[[28,24],25],[[28,24],25],[[15,24],[[20,[29]]]],[[16,24],25],[[30,24],25],[[31,24],25],[[32,24],25],[[33,24],25],[[34,24],25],[[17,24],25],[[12,24],25],[[18,24],25],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[35,28],[36,28],[37,28],[38,28],[39,28],[40,28],[[]],[41,28],[42,28],[43,28],[44,28],[45,28],[46,28],[47,28],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[5,[[4,[1]]]],[1,[[4,[3]]]],[[1,5,[9,[48]]],[[4,[34]]]],[1,[[4,[[21,[49]]]]]],[1,22],[1,[[4,[3]]]],[1,[[4,[3]]]],[[1,23],[[4,[[21,[26]]]]]],[28,50],[[[0,[51,52]],53],54],[[[0,[51,52]],53],54],[[1,5],[[4,[3]]]],[[1,5],[[4,[3]]]],[1,[[4,[3]]]],[[1,23],[[4,[55]]]],[1,[[4,[3]]]],[1,[[4,[3]]]],[[1,5],[[4,[30]]]],[[1,5],4],[[14,56]],[[15,56]],0,0,[[1,5,15,[21,[14]]],[[4,[13]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,0,0,0,[17,[[58,[[21,[57]]]]]],0,0,0,0,0,0,[12,1],[1],0,[1,[[4,[[21,[31]]]]]],[59],[59],0,0,0,0,0,0,[[11,11],12],0,0,0,0,[[1,5,5,5],4],[[13,60],20],[[14,60],20],[[15,60],20],[[16,60],20],[[17,60],20],0,[[1,5],[[4,[13]]]],[[1,16],[[4,[13]]]],[1],[1],[28,[[9,[61]]]],[[11,11],12],[[11,11],12],0,0,0,[1,[[4,[3]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],3],[[],3],0,0,[[],20],[[],20],[[],20],[[],20],[[],20],[62,[[58,[2]]]],[[],20],[[],20],[[],20],[[],20],[[],20],[3,[[58,[14]]]],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],63],[[],63],[[],63],[[],63],[[],63],[[],63],[[],63],[[],63],[[],63],[[],63],[[],63],[[],63],[[],63],[[],63],[[],63],[[],63],[[],63],[[],63],[[],63],[[],63],[[],63],[[],63],0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,0,0,0,0,0,0,0,0,0,0],"c":[],"p":[[3,"FirefoxAccount"],[3,"AuthorizationParameters"],[3,"String"],[6,"ApiResult"],[15,"str"],[8,"AsRef"],[15,"slice"],[3,"MetricsParams"],[4,"Option"],[3,"AuthorizationInfo"],[8,"ToString"],[3,"FxaConfig"],[3,"LocalDevice"],[4,"DeviceCapability"],[4,"DeviceType"],[3,"DevicePushSubscription"],[3,"ScopedKey"],[4,"FxaServer"],[8,"Deserializer"],[4,"Result"],[3,"Vec"],[4,"FxaRustAuthState"],[15,"bool"],[3,"Formatter"],[6,"Result"],[3,"Device"],[4,"FxaError"],[4,"Error"],[3,"Error"],[4,"AccountEvent"],[4,"IncomingDeviceCommand"],[3,"SendTabPayload"],[3,"TabHistoryEntry"],[3,"AccessTokenInfo"],[3,"TryFromIntError"],[3,"UnexpectedStatus"],[4,"Error"],[3,"FromUtf8Error"],[4,"JwCryptoError"],[4,"ParseError"],[4,"DecodeError"],[4,"Error"],[3,"Error"],[4,"FromHexError"],[3,"Error"],[4,"Error"],[4,"Error"],[15,"i64"],[3,"AttachedClient"],[3,"ErrorHandling"],[8,"Hash"],[8,"Sized"],[8,"BuildHasher"],[15,"u64"],[3,"Profile"],[8,"Hasher"],[15,"u8"],[6,"Result"],[3,"Demand"],[8,"Serializer"],[8,"Error"],[3,"Url"],[3,"TypeId"],[13,"CommandReceived"],[13,"DeviceDisconnected"],[13,"DeviceConnected"],[13,"RemoteError"],[13,"Custom"],[13,"TabReceived"]]},\ +"interrupt_support":{"doc":"","t":"DIDDDDLLLLLLLLLLLLLLLLLLLLLLLLLLFLLLLLLLLFFLLLLLLLLLLLLLLLLLKLLLL","n":["Interrupted","Interruptee","NeverInterrupts","ShutdownInterruptee","SqlInterruptHandle","SqlInterruptScope","begin_interrupt_scope","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone_into","dummy","err_if_interrupted","err_if_interrupted","err_if_interrupted","fmt","fmt","fmt","fmt","from","from","from","from","from","in_shutdown","interrupt","into","into","into","into","into","new","provide","register_interrupt","shutdown","to_owned","to_string","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","was_interrupted","was_interrupted","was_interrupted","was_interrupted","was_interrupted"],"q":[[0,"interrupt_support"]],"d":["The error returned by err_if_interrupted.","Represents the state of something that may be interrupted. …","A convenience implementation, should only be used in tests.","","Interrupt operations that use SQL","Check if an operation has been interrupted","Begin an interrupt scope that will be interrupted by this …","","","","","","","","","","","","","","","","Return Err(Interrupted) if we were interrupted","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Check if we’re currently in shutdown mode","Interrupt all interrupt scopes created by this handle","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","Register a ShutdownInterrupt implementation","Initiate shutdown mode","","","","","","","","","","","","","","","","","","","","","Check if scope has been interrupted",""],"i":[0,0,0,0,0,0,1,14,15,1,3,2,14,15,1,3,2,3,3,2,16,16,2,1,3,3,2,14,15,1,3,2,0,1,14,15,1,3,2,1,3,0,0,3,3,14,15,1,3,2,14,15,1,3,2,14,15,1,3,2,16,14,15,2,2],"f":[0,0,0,0,0,0,[1,[[4,[2,3]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[3,3],[[]],[[],2],[[],[[4,[3]]]],[[],[[4,[3]]]],[2,[[4,[3]]]],[[1,5],6],[[3,5],6],[[3,5],6],[[2,5],6],[[]],[[]],[[]],[[]],[[]],[[],7],[1],[[]],[[]],[[]],[[]],[[]],[8,1],[9],[[[11,[10]]]],[[]],[[]],[[],12],[[],4],[[],4],[[],4],[[],4],[[],4],[[],4],[[],4],[[],4],[[],4],[[],4],[[],13],[[],13],[[],13],[[],13],[[],13],[[],7],[14,7],[15,7],[2,7],[2,7]],"c":[],"p":[[3,"SqlInterruptHandle"],[3,"SqlInterruptScope"],[3,"Interrupted"],[4,"Result"],[3,"Formatter"],[6,"Result"],[15,"bool"],[3,"Connection"],[3,"Demand"],[8,"AsRef"],[3,"Weak"],[3,"String"],[3,"TypeId"],[3,"NeverInterrupts"],[3,"ShutdownInterruptee"],[8,"Interruptee"]]},\ +"logins":{"doc":"","t":"GNNNNNNDNENNNNNNNENNNNDDDDDEDNNNNNNDGDNNNNNILLLLLLLLLLLLLLLLLLLLLLLLLLLLLLOLLLLLLLLLLLLLLLLLLMMLLLLLLLLLLLLLLLLLALLLLLLLLLLLLLLMMMLLLLLLLLLLLLLLLLLLMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLFLLLLXLLLLLLMMLLLLLLLLLLLLLLLLLLLLLLLMMMLLLMMLOLLMMMMLLLLLLMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMMKLLLLLLLLLLLLLLLLLLLLMMMMMMGFFF","n":["ApiResult","BadSyncStatus","BothTargets","CryptoError","DuplicateLogin","EmptyOrigin","EmptyPassword","EncryptedLogin","EncryptionKeyMissing","Error","IOError","IllegalFieldValue","IllegalOrigin","IncorrectKey","Interrupted","Interrupted","InvalidDatabaseFile","InvalidLogin","InvalidLogin","InvalidPath","InvalidRecord","JsonError","Login","LoginDb","LoginEntry","LoginFields","LoginStore","LoginsApiError","LoginsSyncEngine","MalformedIncomingRecord","MigrationError","NoSuchRecord","NoSuchRecord","NoTarget","NonEmptyTable","RecordFields","Result","SecureLoginFields","SqlError","SyncAdapterError","SyncAuthInvalid","UnexpectedLoginsApiError","UrlParseError","ValidateAndFixup","add","add","add_or_update","add_or_update","apply","begin_interrupt_scope","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","breadcrumb","check_for_dupes","check_valid","check_valid","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","collection_name","conn","create_logins_sync_engine","db","db","decrypt","decrypt","decrypt_fields","default","default","default","default","default","default","delete","delete","deref","deserialize","do_reset","dupe_exists","encrypt","encrypt","encryption","entry","eq","eq","eq","eq","eq","eq","equivalent","equivalent","equivalent","equivalent","equivalent","equivalent","exists","fields","fields","fields","find_dupe","find_login_to_update","find_login_to_update","fixup","fixup","fixup_and_check_for_dupes","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","form_action_origin","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","get","get_all","get_by_base_domain","get_by_base_domain","get_by_id","get_collection_request","get_error_handling","get_global_state","get_hash","get_hash","get_hash","get_hash","get_hash","get_hash","get_registered_sync_engine","get_sync_assoc","guid","guid","guid_str","handle_error","hash","hash","hash","hash","hash","hash","http_realm","id","into","into","into","into","into","into","into","into","into","into","into","into","into_bso","list","maybe_fixup","maybe_fixup","new","new","new_from_db","new_in_memory","new_interrupt_handle","open","open_in_memory","origin","password","password_field","provide","provide","provide","record","record","register_with_sync_manager","report_error","reset","reset","scope","sec_fields","sec_fields","sec_fields","serialize","set_global_state","set_local_encryption_key","set_uploaded","source","stage_incoming","staged","store","time_created","time_last_used","time_password_changed","times_used","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_string","to_string","to_string","touch","touch","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","update","update","username","username_field","validate_and_fixup","validate_and_fixup","validate_and_fixup","validate_and_fixup","vzip","vzip","vzip","vzip","vzip","vzip","vzip","vzip","vzip","vzip","vzip","vzip","wipe","wipe","wipe_local","wipe_local","with_connection","field_info","reason","reason","reason","reason","reason","EncryptorDecryptor","check_canary","create_canary","create_key"],"q":[[0,"logins"],[320,"logins::InvalidLogin"],[321,"logins::LoginsApiError"],[326,"logins::encryption"]],"d":["","","","","","","","A login stored in the database","","Logins error type These are “internal” errors used by …","","","","","","","","Error::InvalidLogin subtypes","","","","","A login stored in the database","","A login entered by the user","","","","","","","","","","","Login data specific to database records","","LoginEntry fields that are stored encrypted","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Tell the application to log a breadcrumb","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Delete the record with the provided id. Returns true if …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","","Returns the argument unchanged.","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","","","","","","","","","","","","Called by the sync manager to get a sync engine via the …","","","","","A procedural macro that exposes internal errors to …","","","","","","","","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","","Tell the application to report an error","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Internal helper for doing validation and fixups.","We don’t actually have fixups.","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"i":[0,36,37,36,37,37,37,0,36,0,36,37,37,33,33,36,36,0,36,36,33,36,0,0,0,0,0,0,0,36,36,33,36,37,36,0,0,0,36,36,33,33,36,0,1,6,1,6,9,1,1,6,9,33,36,37,18,19,20,2,21,4,1,6,9,33,36,37,18,19,20,2,21,4,0,1,60,60,18,19,20,2,21,4,18,19,20,2,21,4,9,1,6,1,6,19,4,4,18,19,20,2,21,4,1,6,1,19,9,1,19,21,0,21,18,19,20,2,21,4,18,19,20,2,21,4,1,2,21,4,1,1,6,60,60,1,33,33,36,36,37,37,18,19,20,2,21,4,18,1,6,9,33,36,36,36,36,36,36,36,36,36,37,18,19,20,2,21,4,6,1,1,6,1,9,36,9,18,19,20,2,21,4,0,9,21,4,4,0,18,19,20,2,21,4,18,20,1,6,9,33,36,37,18,19,20,2,21,4,4,6,60,60,6,9,6,6,1,1,1,18,19,18,33,36,37,21,4,6,0,6,9,9,2,21,4,19,9,9,9,36,9,9,9,20,20,20,20,18,19,20,2,21,4,33,36,37,1,6,1,6,9,33,36,37,18,19,20,2,21,4,1,6,9,33,36,37,18,19,20,2,21,4,1,6,9,33,36,37,18,19,20,2,21,4,1,6,19,18,60,18,19,2,1,6,9,33,36,37,18,19,20,2,21,4,6,9,1,6,1,61,62,63,64,65,66,0,0,0,0],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[1,2,3],[[5,[4]]]],[[6,2,7],[[8,[4]]]],[[1,2,3],[[5,[4]]]],[[6,2,7],[[8,[4]]]],[[9,10,11],[[14,[[13,[12]]]]]],[1,[[5,[15]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,[[1,16,2,3],5],[17,5],[17,5],[18,18],[19,19],[20,20],[2,2],[21,21],[4,4],[[]],[[]],[[]],[[]],[[]],[[]],[9,[[22,[7]]]],[1,23],[[[24,[6]]],[[8,[[26,[25]]]]]],0,0,[[7,3],[[5,[19]]]],[[4,3],[[5,[21]]]],[[4,3],[[5,[19]]]],[[],18],[[],19],[[],20],[[],2],[[],21],[[],4],[[1,7],[[5,[27]]]],[[6,7],[[8,[27]]]],[1,23],[28,[[29,[19]]]],[[9,30],5],[[1,16,2,3],[[5,[27]]]],[[19,3],[[5,[31]]]],[[21,3],[[5,[4]]]],0,[21,2],[[18,18],27],[[19,19],27],[[20,20],27],[[2,2],27],[[21,21],27],[[4,4],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[1,7],[[5,[27]]]],0,0,0,[[1,16,2,3],[[5,[[32,[16]]]]]],[[1,2,3],[[5,[[32,[21]]]]]],[[6,2,7],[[8,[[32,[21]]]]]],[17,[[5,[17]]]],[17,[[5,[17]]]],[[1,16,2,3],[[5,[2]]]],[[33,34],35],[[33,34],35],[[36,34],35],[[36,34],35],[[37,34],35],[[37,34],35],[[18,34],35],[[19,34],35],[[20,34],35],[[2,34],35],[[21,34],35],[[4,34],35],0,[[]],[[]],[[]],[[]],[37,36],[38,36],[39,36],[40,36],[[]],[41,36],[42,36],[43,36],[44,36],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[6,7],[[8,[[32,[4]]]]]],[1,[[5,[[13,[4]]]]]],[[1,7],[[5,[[13,[4]]]]]],[[6,7],[[8,[[13,[4]]]]]],[[1,7],[[5,[[32,[4]]]]]],[[9,10],[[14,[[32,[45]]]]]],[36,46],[9,[[5,[[32,[31]]]]]],[[[0,[47,17]],48],49],[[[0,[47,17]],48],49],[[[0,[47,17]],48],49],[[[0,[47,17]],48],49],[[[0,[47,17]],48],49],[[[0,[47,17]],48],49],[50,[[32,[[26,[25]]]]]],[9,[[14,[30]]]],[21,16],[4,16],[4,7],0,[[18,51]],[[19,51]],[[20,51]],[[2,51]],[[21,51]],[[4,51]],0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[4,3,[32,[31]]],[[5,[12]]]],[6,[[8,[[13,[4]]]]]],[17,[[5,[[32,[17]]]]]],[17,[[5,[[32,[17]]]]]],[[[53,[52]]],[[8,[6]]]],[[[24,[6]]],[[5,[9]]]],[1,6],[[],[[8,[6]]]],[1,[[24,[54]]]],[[[53,[52]]],[[5,[1]]]],[[],[[5,[1]]]],0,0,0,[55],[55],[55],0,0,[[[24,[6]]]],0,[[[24,[6]]],8],[[9,30],14],0,0,0,0,[[19,56],29],[[9,[32,[31]]],5],[[9,7],14],[[9,10,[13,[16]]],14],[36,[[32,[57]]]],[[9,[13,[58]],11],14],0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[],31],[[],31],[[],31],[[1,7],5],[[6,7],8],[[],29],[[],29],[[],29],[[],29],[[],29],[[],29],[[],29],[[],29],[[],29],[[],29],[[],29],[[],29],[[],29],[[],29],[[],29],[[],29],[[],29],[[],29],[[],29],[[],29],[[],29],[[],29],[[],29],[[],29],[[],59],[[],59],[[],59],[[],59],[[],59],[[],59],[[],59],[[],59],[[],59],[[],59],[[],59],[[],59],[[1,7,2,3],[[5,[4]]]],[[6,7,2,7],[[8,[4]]]],0,0,[[17,27],[[5,[[32,[17]]]]]],[[18,27],[[5,[[32,[18]]]]]],[[19,27],[[5,[[32,[19]]]]]],[[2,27],[[5,[[32,[2]]]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[6,8],[9,14],[1,5],[6,8],[23,[[5,[1]]]],0,0,0,0,0,0,0,[[7,7,7],[[8,[27]]]],[[7,7],[[8,[31]]]],[[],[[8,[31]]]]],"c":[],"p":[[3,"LoginDb"],[3,"LoginEntry"],[6,"EncryptorDecryptor"],[3,"EncryptedLogin"],[6,"Result"],[3,"LoginStore"],[15,"str"],[6,"ApiResult"],[3,"LoginsSyncEngine"],[3,"ServerTimestamp"],[3,"Engine"],[3,"OutgoingBso"],[3,"Vec"],[6,"Result"],[3,"SqlInterruptScope"],[3,"Guid"],[8,"Sized"],[3,"LoginFields"],[3,"SecureLoginFields"],[3,"RecordFields"],[3,"Login"],[4,"Cow"],[3,"Connection"],[3,"Arc"],[8,"SyncEngine"],[3,"Box"],[15,"bool"],[8,"Deserializer"],[4,"Result"],[4,"EngineSyncAssociation"],[3,"String"],[4,"Option"],[4,"LoginsApiError"],[3,"Formatter"],[6,"Result"],[4,"Error"],[4,"InvalidLogin"],[3,"Interrupted"],[4,"Error"],[4,"Error"],[3,"EncryptorDecryptorError"],[4,"ParseError"],[3,"Error"],[3,"Error"],[3,"CollectionRequest"],[3,"ErrorHandling"],[8,"Hash"],[8,"BuildHasher"],[15,"u64"],[4,"SyncEngineId"],[8,"Hasher"],[3,"Path"],[8,"AsRef"],[3,"SqlInterruptHandle"],[3,"Demand"],[8,"Serializer"],[8,"Error"],[3,"IncomingBso"],[3,"TypeId"],[8,"ValidateAndFixup"],[13,"IllegalFieldValue"],[13,"InvalidRecord"],[13,"NoSuchRecord"],[13,"Interrupted"],[13,"SyncAuthInvalid"],[13,"UnexpectedLoginsApiError"]]},\ +"nimbus":{"doc":"","t":"CNNDENCDNDCCNLLLLLLLLMMLLLLLLMLLLLLALFMLLLLLLLLLLLLLALLLALMMALLLLLLLLLLLLLLLLAMMMMMMMMENNNNNNNNNNNNNNNNNENNNNGNNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLDILLMLLMLMLLMKLMMLLLLLDDDDNDDDDNENMMLLLLLLLLLLLLLLLLLLLMMMMMMLLLLLLLLLLLLMLLLLLLLLLLLMLLLLLLLLLLMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLMMMMFMMMMMMMMLLLLLMMMMMMMMLLLLLLLLLLLMLLLLLLLLLLLLLLLLLLLLLLLLLLLMMMMMMMMLLLAAAAAAAAANNNNEDNEDDNNNDDNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMLLLLLLLLLLLLLLLLLLFLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLDLLLLLLLLLLLLLLLFFFFFFDMMLLLLMMLLMMLLLLMMMLLLLLLDMMMMMMLLMLLMMLLMMLLMMLMMMLLLLLLRRRRDDDLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLDNNNNIDENGLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLFFDLLLLLLLLLLLLLLLL","n":["AppContext","Disqualified","Enrolled","EnrolledFeature","EnrollmentStatus","Error","NimbusError","NimbusTargetingHelper","NotEnrolled","RemoteSettingsConfig","Result","TargetingAttributes","WasEnrolled","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","branch","bucket_name","clone","clone","clone","clone_into","clone_into","clone_into","collection_name","deserialize","eq","eq","equivalent","equivalent","error","eval_jexl","evaluate_enrollment","feature_id","fmt","fmt","fmt","from","from","from","from","hash","into","into","into","into","is_enrolled","metrics","name","new","new_enrolled","schema","serialize","server_url","slug","stateful","to_bytes","to_owned","to_owned","to_owned","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","versioning","branch","branch","branch","experiment_ended_at","reason","reason","reason","reason","BehaviorError","BehaviorError","ClientError","DatabaseNotReady","EmptyRatiosError","EvaluationError","IOError","InternalError","IntervalParseError","InvalidDuration","InvalidExperimentFormat","InvalidExpression","InvalidFraction","InvalidPath","InvalidPersistedData","InvalidState","JSONError","MissingEventStore","NimbusError","NoSuchBranch","NoSuchExperiment","OutOfBoundsError","ParseIntError","Result","RkvError","TransformParameterError","TryFromIntError","TryFromSliceError","UniFFICallbackError","UrlParsingError","UuidError","VersionParsingError","borrow","borrow","borrow_mut","borrow_mut","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from","from","from","into","into","provide","provide","source","to_string","to_string","try_from","try_from","try_into","try_into","type_id","type_id","EnrollmentStatusExtraDef","MetricsHandler","borrow","borrow_mut","branch","clone","clone_into","conflict_slug","deserialize","error_string","from","into","reason","record_enrollment_statuses","serialize","slug","status","to_bytes","to_owned","try_from","try_into","type_id","AvailableExperiment","AvailableRandomizationUnits","Branch","BucketConfig","ClientId","EnrolledExperiment","Experiment","ExperimentBranch","FeatureConfig","NimbusId","RandomizationUnit","UserId","app_id","app_name","apply_nimbus_id","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","branch_slug","branches","branches","bucket_config","channel","client_id","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","count","default","default","default","default","default","default","deserialize","deserialize","deserialize","deserialize","deserialize","end_date","eq","eq","eq","eq","eq","equivalent","equivalent","equivalent","equivalent","equivalent","feature","feature_id","feature_ids","feature_ids","features","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","get_value","into","into","into","into","into","into","into","into","into","is_enrollment_paused","is_rollout","namespace","nimbus_id","parse_experiments","proposed_duration","proposed_enrollment","randomization_unit","ratio","ratio","reference_branch","reference_branch","schema_version","serialize","serialize","serialize","serialize","serialize","slug","slug","slug","slug","slug","start","start_date","targeting","to_bytes","to_bytes","to_bytes","to_bytes","to_bytes","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","total","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","user_facing_description","user_facing_description","user_facing_description","user_facing_name","user_facing_name","user_facing_name","user_id","value","with_client_id","with_nimbus_id","with_user_id","behavior","client","dbcache","enrollment","evaluator","matcher","nimbus_client","persistence","updating","AveragePerInterval","AveragePerNonZeroInterval","CountNonZero","Days","EventQueryType","EventStore","Hours","Interval","IntervalConfig","IntervalData","LastSeen","Minutes","Months","MultiIntervalCounter","SingleIntervalCounter","Sum","Weeks","Years","advance_datum","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clear","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","config","data","default","default","default","default","deserialize","deserialize","deserialize","deserialize","deserialize","deserialize","eq","equivalent","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from_config","from_str","hash","increment","increment","increment","increment_at","increment_then","increment_then","intervals","into","into","into","into","into","into","into","maybe_advance","maybe_advance","new","new","new","new","new","num_rotations","perform_query","persist_data","query","query_event_store","read_from_db","record_event","record_past_event","rotate","serialize","serialize","serialize","serialize","serialize","serialize","to_bytes","to_bytes","to_bytes","to_bytes","to_bytes","to_bytes","to_duration","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_string","to_string","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","validate_arguments","DatabaseCache","borrow","borrow_mut","commit_and_update","default","from","get_active_experiments","get_enrollment_by_feature","get_enrollments","get_experiment_branch","get_experiments","get_feature_config_variables","into","try_from","try_into","type_id","get_enrollments","get_global_user_participation","opt_in_with_branch","opt_out","reset_telemetry_identifiers","set_global_user_participation","TargetingAttributes","active_experiments","app_context","borrow","borrow_mut","clone","clone_into","days_since_install","days_since_update","default","deserialize","enrollments","enrollments_map","fmt","from","from","into","is_already_enrolled","language","region","serialize","to_bytes","to_owned","try_from","try_into","type_id","AppContext","android_sdk_version","app_build","app_id","app_name","app_version","architecture","borrow","borrow_mut","channel","clone","clone_into","custom_targeting_attributes","debug_tag","default","deserialize","device_manufacturer","device_model","fmt","from","home_directory","installation_date","into","locale","os","os_version","serialize","to_bytes","to_owned","try_from","try_into","type_id","DB_KEY_APP_VERSION","DB_KEY_FETCH_ENABLED","DB_KEY_INSTALLATION_DATE","DB_KEY_UPDATE_DATE","InternalMutableState","NimbusClient","NimbusStringHelper","advance_event_time","apply_pending_experiments","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","clear_events","create_string_helper","create_targeting_helper","default","dump_state_to_log","event_store","fetch_experiments","from","from","from","get_active_experiments","get_all_experiments","get_available_experiments","get_enrollment_by_feature","get_experiment_branch","get_experiment_branches","get_feature_config_variables","get_global_user_participation","get_targeting_attributes","get_uuid","initialize","into","into","into","new","nimbus_id","opt_in_with_branch","opt_out","record_event","record_past_event","reset_enrollments","reset_telemetry_identifiers","set_experiments_locally","set_fetch_enabled","set_global_user_participation","set_nimbus_id","string_format","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","with_targeting_attributes","Database","Enrollments","EventCounts","Experiments","Meta","Readable","SingleStore","StoreId","Updates","Writer","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","clear","collect_all","delete","from","from","from","get","get_store","into","into","into","new","new","open_rkv","put","read","try_collect_all","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","write","read_and_remove_pending_experiments","write_pending_experiments","Version","borrow","borrow_mut","clone","clone_into","default","eq","fmt","from","into","partial_cmp","to_owned","try_from","try_from","try_from","try_into","type_id"],"q":[[0,"nimbus"],[78,"nimbus::EnrollmentStatus"],[86,"nimbus::error"],[153,"nimbus::metrics"],[175,"nimbus::schema"],[357,"nimbus::stateful"],[366,"nimbus::stateful::behavior"],[521,"nimbus::stateful::dbcache"],[537,"nimbus::stateful::enrollment"],[543,"nimbus::stateful::evaluator"],[569,"nimbus::stateful::matcher"],[601,"nimbus::stateful::nimbus_client"],[663,"nimbus::stateful::persistence"],[706,"nimbus::stateful::updating"],[708,"nimbus::versioning"]],"d":["","","","","","","","","","Custom configuration for the client. Currently includes …","","","","","","","","","","","","","","","","","","","","","","","","","","Not complete yet This is where the error definitions can go","","Determine the enrolment status for an experiment.","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","","","","","","","","Nimbus SDK App Version Comparison","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","This module defines all the information needed to match a …","","Our storage abstraction, currently backed by Rkv.","This module implements the primitive functions to implement","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","","","","","","","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","","","","","","","Calls U::from(self).","","","","Return information about all enrolled experiments. Note …","","","","Reset unique identifiers in response to application-level …","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Calls U::from(self).","","","","","","","","","","The AppContext object represents the parameters and …","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","","","Calls U::from(self).","","","","","","","","","","","","","","","Nimbus is the main struct representing the experiments …","","Advances the event store’s concept of now artificially.","","","","","","","","Clear all events in the Nimbus event store.","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","","","","","","","","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","Records an event for the purposes of behavioral targeting.","Records an event for the purposes of behavioral targeting.","Reset all enrollments and experiments in the database.","Reset internal state in response to application-level …","","","","","","","","","","","","","","","","Database used to access persisted data This an abstraction …","Store containing the set of known experiment enrollments.","Store containing collected counts of behavior events for …","Store containing the set of known experiments, as read …","Store containing miscellaneous metadata about this client …","","A wrapper for an Rkv store. Implemented to allow any value …","Enumeration of the different stores within our database.","Store containing pending updates to experiment data.","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","Gets a Store object, which used with the writer returned by","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","Main constructor for a database Initiates the Rkv database …","","","Function used to obtain a “reader” which is used for …","Fork of collect_all that simply drops records that fail to …","","","","","","","","","","Function used to obtain a “writer” which is used for …","","","","","","","","","","","Returns the argument unchanged.","Calls U::from(self).","","","","","","",""],"i":[0,2,2,0,0,2,0,0,2,0,0,0,2,7,1,2,3,7,1,2,3,3,1,1,2,3,1,2,3,1,2,2,3,2,3,0,7,0,3,1,2,3,7,1,2,3,2,7,1,2,3,2,0,2,7,2,0,2,1,3,0,2,1,2,3,7,1,2,3,7,1,2,3,7,1,2,3,0,98,99,100,100,98,101,99,102,0,27,27,27,27,27,27,27,28,28,27,27,27,27,27,28,27,28,0,27,27,27,27,0,27,27,27,27,27,27,27,27,27,28,27,28,27,27,28,28,27,27,27,27,27,27,27,27,27,27,27,27,27,28,27,28,27,28,27,27,28,27,28,27,28,27,28,0,0,43,43,43,43,43,43,43,43,43,43,43,83,43,43,43,43,43,43,43,43,0,0,0,0,49,0,0,0,0,49,0,49,11,11,10,50,51,45,11,46,47,48,49,10,50,51,45,11,46,47,48,49,10,45,50,11,11,11,10,45,11,46,47,48,49,45,11,46,47,48,49,48,11,46,47,48,49,10,11,46,47,48,49,11,11,46,47,48,49,11,46,47,48,49,47,46,45,11,47,45,11,46,47,48,49,50,50,51,51,45,11,46,47,48,49,10,10,50,51,45,11,46,47,48,49,10,11,11,48,10,0,11,11,48,51,47,50,11,11,11,46,47,48,49,50,51,45,11,47,48,11,11,11,46,47,48,49,45,11,46,47,48,49,48,50,51,45,11,46,47,48,49,10,50,51,45,11,46,47,48,49,10,50,51,45,11,46,47,48,49,10,50,45,11,50,45,11,10,46,10,10,10,0,0,0,0,0,0,0,0,0,59,59,59,58,0,0,58,0,0,0,59,58,58,0,0,59,58,58,17,59,17,54,55,56,57,58,59,17,54,55,56,57,58,17,17,54,55,56,57,58,17,54,55,56,57,58,55,55,17,54,56,57,17,54,55,56,57,58,58,58,59,59,17,54,55,56,57,58,58,59,17,17,17,54,55,56,57,58,55,58,58,54,55,56,56,54,55,54,59,17,54,55,56,57,58,54,55,17,54,55,56,57,58,59,17,17,0,17,17,17,56,17,54,55,56,57,58,17,54,55,56,57,58,58,17,54,55,56,57,58,59,58,59,17,17,54,55,56,57,58,59,17,54,55,56,57,58,59,17,54,55,56,57,58,59,0,71,71,71,71,71,71,71,71,71,71,71,71,71,71,71,0,0,0,0,0,0,0,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,75,0,76,76,76,76,76,76,76,76,76,76,76,76,76,76,76,76,76,76,76,76,76,76,76,76,76,76,76,76,76,76,76,0,0,0,0,0,0,0,77,77,77,79,80,77,79,80,77,77,77,80,77,77,77,77,79,80,77,77,77,77,77,77,77,77,77,79,77,77,79,80,77,77,77,77,77,77,77,77,77,77,77,77,79,77,79,80,77,79,80,77,79,80,77,0,87,87,87,87,0,0,0,87,0,87,85,53,87,85,53,85,85,85,87,85,53,85,53,87,85,53,85,53,53,85,53,85,87,85,53,87,85,53,87,85,53,53,0,0,0,96,96,96,96,96,96,96,96,96,96,96,96,96,96,96,96],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,[1,1],[2,2],[3,3],[[]],[[]],[[]],0,[4,[[5,[2]]]],[[2,2],6],[[3,3],6],[[],6],[[],6],0,[[7,8],[[9,[6]]]],[[10,11,7],[[9,[0]]]],0,[[1,12],[[5,[13]]]],[[2,12],14],[[3,12],14],[[]],[[]],[[]],[[]],[[2,15]],[[]],[[]],[[]],[[]],[2,6],0,[2,8],[[16,[19,[[18,[17]]]]],7],0,0,[[2,21],5],0,0,0,[[],[[5,[[24,[22,23]],25]]]],[[]],[[]],[[]],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],26],[[],26],[[],26],[[],26],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[27,12],14],[[27,12],14],[[28,12],14],[[28,12],14],[29,27],[30,27],[31,27],[32,27],[28,27],[33,27],[34,27],[35,27],[36,27],[37,27],[38,27],[39,27],[[]],[[]],[[]],[[]],[40],[40],[27,[[42,[41]]]],[[],8],[[],8],[[],5],[[],5],[[],5],[[],5],[[],26],[[],26],0,0,[[]],[[]],0,[43,43],[[]],0,[4,[[5,[43]]]],0,[[]],[[]],0,[[[24,[43]]]],[[43,21],5],0,0,[[],[[5,[[24,[22,23]],25]]]],[[]],[[],5],[[],5],[[],26],0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[10,44],10],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,0,0,0,0,[45,45],[11,11],[46,46],[47,47],[48,48],[49,49],[[]],[[]],[[]],[[]],[[]],[[]],0,[[],11],[[],46],[[],47],[[],48],[[],49],[[],10],[4,[[5,[11]]]],[4,[[5,[46]]]],[4,[[5,[47]]]],[4,[[5,[48]]]],[4,[[5,[49]]]],0,[[11,11],6],[[46,46],6],[[47,47],6],[[48,48],6],[[49,49],6],[[],6],[[],6],[[],6],[[],6],[[],6],0,0,0,0,0,[[45,12],14],[[11,12],14],[[46,12],14],[[47,12],14],[[48,12],14],[[49,12],14],[11,50],[[]],[47,51],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[10,49],[[42,[20]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,0,0,[20,[[9,[[24,[11]]]]]],0,0,0,0,0,0,0,0,[[11,21],5],[[46,21],5],[[47,21],5],[[48,21],5],[[49,21],5],0,0,0,0,0,0,0,0,[[],[[5,[[24,[22,23]],25]]]],[[],[[5,[[24,[22,23]],25]]]],[[],[[5,[[24,[22,23]],25]]]],[[],[[5,[[24,[22,23]],25]]]],[[],[[5,[[24,[22,23]],25]]]],[[]],[[]],[[]],[[]],[[]],[[]],0,[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],0,0,0,0,0,0,0,0,[20,10],[44,10],[20,10],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[17,52]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[17,53],9],[17,17],[54,54],[55,55],[56,56],[57,57],[58,58],[[]],[[]],[[]],[[]],[[]],[[]],0,0,[[],17],[[],54],[[],56],[[],57],[4,[[5,[17]]]],[4,[[5,[54]]]],[4,[[5,[55]]]],[4,[[5,[56]]]],[4,[[5,[57]]]],[4,[[5,[58]]]],[[58,58],6],[[],6],[[59,12],14],[[59,12],14],[[17,12],14],[[54,12],14],[[55,12],14],[[56,12],14],[[57,12],14],[[58,12],14],[[58,12],14],[[]],[[[60,[8,54]]],17],[24,17],[[]],[[]],[[]],[[]],[[]],[[]],[[61,58],55],[20,[[9,[58]]]],[[58,15]],[[54,62],9],[[55,62],9],[[56,62],9],[[56,61,62],9],[[54,[64,[63]],62],9],[[55,[64,[63]],62],9],0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[54,[64,[63]]],9],[[55,[64,[63]]],9],[[],17],[[[24,[55]]],54],[57,55],[61,56],[[61,58],57],[[58,[64,[63]],[64,[63]]],[[9,[65]]]],[[59,[66,[62]],61],[[9,[67]]]],[[17,53],9],[[17,20,58,61,61,59],[[9,[67]]]],[[[19,[[18,[17]]]],59,[69,[68]]],[[9,[68]]]],[[17,53],9],[[17,62,20,[42,[[64,[63]]]]],9],[[17,62,20,[42,[[64,[63]]]],52],9],[[56,65],9],[[17,21],5],[[54,21],5],[[55,21],5],[[56,21],5],[[57,21],5],[[58,21],5],[[],[[5,[[24,[22,23]],25]]]],[[],[[5,[[24,[22,23]],25]]]],[[],[[5,[[24,[22,23]],25]]]],[[],[[5,[[24,[22,23]],25]]]],[[],[[5,[[24,[22,23]],25]]]],[[],[[5,[[24,[22,23]],25]]]],[[58,70],52],[[]],[[]],[[]],[[]],[[]],[[]],[[],8],[[],8],[[],5],[53,[[9,[17,27]]]],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[[],26],[[59,[69,[68]]],9],0,[[]],[[]],[[71,53,72,[73,[20]]],9],[[],71],[[]],[71,[[9,[[24,[45]]]]]],[[71,20],[[9,[[42,[3]]]]]],[71,[[9,[[24,[0]]]]]],[[71,20],[[9,[[42,[8]]]]]],[71,[[9,[[24,[11]]]]]],[[71,20],[[9,[[42,[8]]]]]],[[]],[[],5],[[],5],[[],26],[[53,74],[[9,[[24,[45]]]]]],[[53,74],[[9,[6]]]],[[53,72,20,20],[[9,[[24,[0]]]]]],[[53,72,20],[[9,[[24,[0]]]]]],[[53,72],[[9,[[24,[0]]]]]],[[53,72,6],9],0,0,0,[[]],[[]],[75,75],[[]],0,0,[[],75],[4,[[5,[75]]]],0,0,[[75,12],14],[76,75],[[]],[[]],0,0,0,[[75,21],5],[[],[[5,[[24,[22,23]],25]]]],[[]],[[],5],[[],5],[[],26],0,0,0,0,0,0,0,[[]],[[]],0,[76,76],[[]],0,0,[[],76],[4,[[5,[76]]]],0,0,[[76,12],14],[[]],0,0,[[]],0,0,0,[[76,21],5],[[],[[5,[[24,[22,23]],25]]]],[[]],[[],5],[[],5],[[],26],0,0,0,0,0,0,0,[[77,70],9],[77,[[9,[[24,[0]]]]]],[[]],[[]],[[]],[[]],[[]],[[]],[77,9],[[77,[42,[[78,[8,68]]]]],[[9,[[19,[79]]]]]],[[77,[42,[[78,[8,68]]]]],[[9,[[19,[7]]]]]],[[],80],[77,9],[77,[[19,[[18,[17]]]]]],[77,9],[[]],[[]],[[]],[77,[[9,[[24,[45]]]]]],[77,[[9,[[24,[11]]]]]],[77,[[9,[[24,[50]]]]]],[[77,8],[[9,[[42,[3]]]]]],[[77,8],[[9,[[42,[8]]]]]],[[77,8],[[9,[[24,[51]]]]]],[[77,8],[[9,[[42,[8]]]]]],[77,[[9,[6]]]],[77,75],[[79,8],[[42,[8]]]],[77,9],[[]],[[]],[[]],[[76,[24,[8]],[82,[81]],[42,[1]],10,[84,[83]]],[[9,[77]]]],[77,[[9,[44]]]],[[77,8,8],[[9,[[24,[0]]]]]],[[77,8],[[9,[[24,[0]]]]]],[[77,8,70],9],[[77,8,70,70],9],[77,9],[[77,10],[[9,[[24,[0]]]]]],[[77,8],9],[[77,6],9],[[77,6],[[9,[[24,[0]]]]]],[[77,44],9],[[79,8,[42,[8]]],8],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],26],[[],26],[[],26],[[77,75]],0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[85,72],9],[[85,74],[[9,[[24,[[0,[16,86]]]]]]]],[[85,72,20],9],[[]],[[]],[[]],[[85,74,20],[[9,[[42,[[0,[16,86]]]]]]]],[[53,87],85],[[]],[[]],[[]],[[[89,[88]]],85],[[[91,[90]]],[[9,[53]]]],[[[91,[90]]],[[9,[[93,[92]]]]]],[[85,72,20,[0,[16,86]]],9],[53,[[9,[[95,[94]]]]]],[[85,74],[[9,[[24,[[0,[16,86]]]]]]]],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],26],[[],26],[[],26],[53,[[9,[72]]]],[[53,72],[[9,[[42,[[24,[11]]]]]]]],[[53,72,[24,[11]]],9],0,[[]],[[]],[96,96],[[]],[[],96],[[96,96],6],[[96,12],14],[[]],[[]],[[96,96],[[42,[97]]]],[[]],[[],5],[20,[[5,[96]]]],[8,[[5,[96]]]],[[],5],[[],26]],"c":[],"p":[[3,"RemoteSettingsConfig"],[4,"EnrollmentStatus"],[3,"EnrolledFeature"],[8,"Deserializer"],[4,"Result"],[15,"bool"],[3,"NimbusTargetingHelper"],[3,"String"],[6,"Result"],[3,"AvailableRandomizationUnits"],[3,"Experiment"],[3,"Formatter"],[3,"Error"],[6,"Result"],[8,"Hasher"],[8,"Serialize"],[3,"EventStore"],[3,"Mutex"],[3,"Arc"],[15,"str"],[8,"Serializer"],[15,"u8"],[3,"Global"],[3,"Vec"],[4,"DataError"],[3,"TypeId"],[4,"NimbusError"],[4,"BehaviorError"],[4,"EvaluationError"],[3,"Error"],[4,"ParseError"],[4,"RemoteSettingsError"],[3,"ParseIntError"],[3,"Error"],[3,"TryFromSliceError"],[3,"UnexpectedUniFFICallbackError"],[3,"TryFromIntError"],[3,"Error"],[4,"StoreError"],[3,"Demand"],[8,"Error"],[4,"Option"],[3,"EnrollmentStatusExtraDef"],[3,"Uuid"],[3,"EnrolledExperiment"],[3,"FeatureConfig"],[3,"Branch"],[3,"BucketConfig"],[4,"RandomizationUnit"],[3,"AvailableExperiment"],[3,"ExperimentBranch"],[3,"Duration"],[3,"Database"],[3,"MultiIntervalCounter"],[3,"SingleIntervalCounter"],[3,"IntervalData"],[3,"IntervalConfig"],[4,"Interval"],[4,"EventQueryType"],[3,"HashMap"],[15,"usize"],[15,"u64"],[3,"Utc"],[3,"DateTime"],[15,"i32"],[3,"Iter"],[15,"f64"],[4,"Value"],[15,"slice"],[15,"i64"],[3,"DatabaseCache"],[6,"Writer"],[3,"HashSet"],[8,"Readable"],[3,"TargetingAttributes"],[3,"AppContext"],[3,"NimbusClient"],[3,"Map"],[3,"NimbusStringHelper"],[3,"InternalMutableState"],[3,"PathBuf"],[8,"Into"],[8,"MetricsHandler"],[3,"Box"],[3,"SingleStore"],[8,"Deserialize"],[4,"StoreId"],[3,"DatabaseImpl"],[3,"SingleStore"],[3,"Path"],[8,"AsRef"],[3,"EnvironmentImpl"],[3,"Rkv"],[3,"RoTransactionImpl"],[3,"Reader"],[3,"Version"],[4,"Ordering"],[13,"Enrolled"],[13,"Disqualified"],[13,"WasEnrolled"],[13,"NotEnrolled"],[13,"Error"]]},\ +"nimbus_cli":{"doc":"","t":"NEDNNNNNNNNNNENNDNNNNNHNNLLMLLLLLLLLLLLLLMALLLLAALLMLLLLLLLALLLLLLLLLLLLFLLLLLLLLLLLLLFLLLAMMMMLLALLALLLLLLLLLLLLLLLLLLLLLALAMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMNNDENNDDDDNNNNNNNDNDNNNNNNMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMMLLLLLLLLLLLLLLLLMLMLLLLLLMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMMLMMMMMMMMLLMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMMFFFMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMFFFFFFFFFFFFFFFAAAAAARFFFFDDLMLLLLMMLMLLLMMLMMLLLLLLLMMLMLLMMMLLLLLLLLMMGDGDLLLLFFFLMLLFFLLLMLLFMMFFFMFFLFFLLLLLLLLMDLLLLLMLLLLMMLLLLAAAAEDNNNNLLLLLLLLMLLLLLLLLFLLLLLLLLLLLLLLLLLLMMMMMMMMMNENNNNNLLLLFLLFLLLLFFLLLLLLLLLLLMMMMMMMDMMLLMLLLMLMLLLLLLLMLLMLLLLNNELLLLLLLLLLLLLLLLLMMMMMMFADDDLLLLLLFLLLLLLLLLLLLLLLLLLMIIKKKKKKKKFKFFFFKFFFFFF","n":["Android","AppCommand","AppOpenArgs","ApplyFile","CaptureLogs","Defaults","Enroll","ExtractFeatures","FetchList","FmlPassthrough","Info","Ios","Kill","LaunchableApp","List","LogState","NimbusApp","NoOp","Open","Reset","StartServer","TailLogs","USER_AGENT","Unenroll","ValidateExperiment","android_start","app_name","app_name","app_opening_deeplink","apply_list","args","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","capture_logs","channel","channel","cli","clone","clone","clone_into","clone_into","cmd","config","copy_to_clipboard","deeplink","deeplink","default","enroll","eq","eq","eq","eq","exe","feature_utils","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from_ref","from_ref","get_commands_from_cli","github_repo","into","into","into","into","ios_app_container","ios_log_file","ios_log_file_command","ios_reset","ios_start","kill_app","log_state","longform_url","main","mandatory_scheme","manifest_location","open","output","output","passthrough","pbcopy","pbpaste","platform","prepend_scheme","protocol","ref_from_version","reset_app","sources","start_app","tail_logs","to_owned","to_owned","try_from","try_from","try_from","try_from","try_from","try_from","try_from_app_channel_device","try_into","try_into","try_into","try_into","try_validate","type_id","type_id","type_id","type_id","unenroll_all","updater","validate_experiment","value_utils","app","app","app","app","app","app","app","app","app","args","branch","branch","cwd","experiment","experiment","experiment","experiment","feature_id","feature_id","file","file","list","list","list","manifest","manifest","manifest","multi","open","open","open","open","open","output","output","output","params","params","preserve_bucketing","preserve_nimbus_db","preserve_nimbus_db","preserve_targeting","rollouts","validate","activity_name","app_id","device_id","device_id","open_deeplink","package_name","scheme","scheme","ApplyFile","CaptureLogs","Cli","CliCommand","Defaults","Enroll","ExperimentArgs","ExperimentListArgs","ExperimentListFilterArgs","ExperimentListSourceArgs","Features","Fetch","FetchList","Fml","Info","List","LogState","ManifestArgs","Open","OpenArgs","ResetApp","StartServer","TailLogs","TestFeature","Unenroll","Validate","active_on","app","augment_args","augment_args","augment_args","augment_args","augment_args","augment_args","augment_args","augment_args_for_update","augment_args_for_update","augment_args_for_update","augment_args_for_update","augment_args_for_update","augment_args_for_update","augment_args_for_update","augment_subcommands","augment_subcommands_for_update","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","channel","channel","check_valid","clone","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","command","command","command_for_update","deeplink","default","default","default","default","default","default","device_id","enrolling_on","experiment","feature","file","file","filter","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from_arg_matches","from_arg_matches","from_arg_matches","from_arg_matches","from_arg_matches","from_arg_matches","from_arg_matches","from_arg_matches","from_arg_matches_mut","from_arg_matches_mut","from_arg_matches_mut","from_arg_matches_mut","from_arg_matches_mut","from_arg_matches_mut","from_arg_matches_mut","from_arg_matches_mut","from_ref","from_ref","from_ref","from_ref","from_ref","from_ref","from_ref","group_id","group_id","group_id","group_id","group_id","group_id","group_id","has_subcommand","into","into","into","into","into","into","into","into","is_rollout","manifest","open_args","output","passthrough","patch","pbcopy","pbpaste","ref_","reset_app","server","should_kill","should_reset","slug","source","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","update_from_arg_matches","update_from_arg_matches","update_from_arg_matches","update_from_arg_matches","update_from_arg_matches","update_from_arg_matches","update_from_arg_matches","update_from_arg_matches","update_from_arg_matches_mut","update_from_arg_matches_mut","update_from_arg_matches_mut","update_from_arg_matches_mut","update_from_arg_matches_mut","update_from_arg_matches_mut","update_from_arg_matches_mut","update_from_arg_matches_mut","use_api","use_rs","validate_date","validate_date_parts","validate_num","version","args","branch","branch","experiment","experiment","experiment","experiment","experiment","feature_id","feature_id","feature_id","file","file","files","list","list","manifest","manifest","manifest","manifest","manifest","multi","no_clobber","no_validate","no_validate","open","open","open","open","open","open","output","output","output","output","output","patch","preserve_bucketing","preserve_nimbus_db","preserve_nimbus_db","preserve_targeting","recipes","rollouts","validate","logcat_args","output_err","output_ok","process_cmd","prompt","api_v6_production_server","api_v6_stage_server","manifest_cache_dir","rs_production_server","rs_stage_server","server_host","server_port","branch","create_experiment","slug","deeplink","features","fetch","fml_cli","info","server","QUERY","join_query","longform_deeplink_url","set_clipboard","fml_cli","DateRange","ExperimentInfo","active","app_name","borrow","borrow","borrow_mut","borrow_mut","branches","bucketing","bucketing_percent","channel","contains","default","default","duration","end","enrollment","enrollment","features","fmt","fmt","fmt","from","from","into","into","is_enrollment_paused","is_rollout","new","proposed","serialize","serialize","slug","start","targeting","to_string","try_from","try_from","try_from","try_into","try_into","type_id","type_id","user_facing_description","user_facing_name","Db","InMemoryDb","Srhl","StartAppPostPayload","borrow","borrow","borrow_mut","borrow_mut","create_app","create_server","create_state","deserialize","experiments","from","from","get_address","index","into","into","latest","latest","new","new","no_cache_layer","payloads","platform","post_deeplink","post_handler","post_payload","reloader","rs","script","serialize","start_server","style","try_from","try_from","try_into","try_into","type_id","type_id","update","url","url","StartAppProtocol","borrow","borrow_mut","clone","clone_into","default","experiments","fmt","from","from_ref","into","log_state","reset_db","to_owned","try_from","try_into","type_id","experiment","experiment_list","filter","manifest","ExperimentSource","FeatureDefaults","FromApiV6","FromFeatureFiles","FromList","WithPatchFile","borrow","borrow","borrow_mut","borrow_mut","clone","clone_into","deserialize","eq","features","fmt","fmt","from","from","from_ref","get_features_json","into","into","patch_experiment","print_features","print_info","serialize","to_owned","to_string","try_from","try_from","try_from","try_from","try_from_api","try_from_file","try_from_rs","try_from_slug","try_from_url","try_into","try_into","type_id","type_id","app","endpoint","feature_id","files","inner","list","patch","slug","slug","Empty","ExperimentListSource","Filtered","FromApiV6","FromFile","FromRecipes","FromRemoteSettings","borrow","borrow_mut","clone","clone_into","decode_list_slug","eq","fetch_list","filter_list","fmt","from","from_ref","into","is_preview_collection","is_production_server","print_list","to_owned","try_from","try_from","try_from","try_from","try_from_api","try_from_rs","try_from_slug","try_into","type_id","endpoint","endpoint","file","filter","inner","is_preview","recipes","ExperimentListFilter","active_on","app","borrow","borrow_mut","channel","clone","clone_into","default","enrolling_on","eq","feature_pattern","fmt","for_app","from","from","from_ref","into","is_empty","is_rollout","matches","matches_info","slug_pattern","to_owned","try_from","try_into","type_id","FromFile","FromGithub","ManifestSource","borrow","borrow_mut","channel","eq","fmt","fmt","from","get_defaults_json","into","manifest_file","manifest_loader","print_defaults","to_string","try_from","try_from","try_into","type_id","channel","channel","github_repo","manifest_file","manifest_file","ref_","check_for_update","taskcluster","ReqwestGunzippingHttpClient","Response","TaskClusterRegistry","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","check_taskcluster_for_update","deserialize","from","from","from","get","get_latest_version","into","into","into","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","version","CliUtils","Patch","get_array","get_bool","get_mut_array","get_mut_object","get_object","get_str","get_u64","has","is_yaml","patch","prepare_experiment","prepare_recipe","prepare_rollout","read_from_file","set","try_extract_data_list","try_find_branches_from_experiment","try_find_experiment","try_find_features_from_branch","try_find_mut_features_from_branch","write_to_file_or_print"],"q":[[0,"nimbus_cli"],[125,"nimbus_cli::AppCommand"],[169,"nimbus_cli::LaunchableApp"],[177,"nimbus_cli::cli"],[392,"nimbus_cli::cli::CliCommand"],[436,"nimbus_cli::cmd"],[441,"nimbus_cli::config"],[448,"nimbus_cli::feature_utils"],[451,"nimbus_cli::output"],[457,"nimbus_cli::output::deeplink"],[461,"nimbus_cli::output::fml_cli"],[462,"nimbus_cli::output::info"],[508,"nimbus_cli::output::server"],[552,"nimbus_cli::protocol"],[569,"nimbus_cli::sources"],[573,"nimbus_cli::sources::experiment"],[615,"nimbus_cli::sources::experiment::ExperimentSource"],[624,"nimbus_cli::sources::experiment_list"],[656,"nimbus_cli::sources::experiment_list::ExperimentListSource"],[663,"nimbus_cli::sources::filter"],[690,"nimbus_cli::sources::manifest"],[710,"nimbus_cli::sources::manifest::ManifestSource"],[716,"nimbus_cli::updater"],[718,"nimbus_cli::updater::taskcluster"],[747,"nimbus_cli::value_utils"]],"d":["","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","","Returns the argument unchanged.","Returns the argument unchanged.","","","","","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Send a complete JSON file to the Nimbus SDK and apply it …","Capture the logs into a file.","","","Print the defaults for the manifest.","Enroll into an experiment or a rollout.","","","","","Print the feature configuration involved in the branch of …","Fetch one or more named experiments and rollouts and put …","Fetch a list of experiments and put it in a file.","Execute a nimbus-fml command. See","Displays information about an experiment","List the experiments from a server","Print the state of the Nimbus database to logs.","","Open the app without changing the state of experiment …","","Reset the app back to its just installed state","Start a server","Follow the logs for the given app.","Configure an application feature with one or more feature …","Unenroll from all experiments and rollouts","Validate an experiment against a feature manifest","","The app name according to Nimbus.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","The channel according to Nimbus. This determines which app …","","","","","","","","","","","","","","","","","","","","Optional deeplink. If present, launch with this link.","","","","","","","The device id of the simulator, emulator or device.","","The experiment slug, including the server and collection.","","An optional file from which to get the experiment.","An optional file","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","An optional manifest file","","An optional file to dump experiments into.","Optionally, add platform specific arguments to the adb or …","An optional patch file, used to patch feature …","Instead of opening via adb or xcrun simctl, construct a …","Instead of opening via adb or xcrun simctl, construct a …","The branch/tag/commit for the version of the manifest to …","Resets the app back to its initial state before launching","A server slug e.g. preview, release, stage, stage/preview","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Use the v6 API to fetch the experiment recipes.","Use remote settings to fetch the experiment recipe.","","","","An optional version of the app. If present, constructs the …","","The branch slug.","The branch of the experiment","","","","","","An optional feature-id","An optional feature-id: if it exists in this branch, print …","The identifier of the feature to configure","The filename to be loaded into the SDK.","The file to put the logs.","One or more files containing a feature config for the …","","","","","","","","Print out the features involved in this branch as in a …","By default, the app is terminated before sending the a …","Don’t validate the feature config files before enrolling","Don’t validate the feature config files before enrolling","","","","","","","An optional file to print the manifest defaults.","An optional file to print the output.","The file to download the recipes to.","The file to download the recipes to.","An optional file to print the output.","An optional patch file, used to patch feature …","Preserves the original experiment bucketing","Keeps existing enrollments and experiments before …","Keeps existing enrollments and experiments before …","Preserves the original experiment targeting","The recipe slugs, including server.","Optional rollout slugs, including the server and …","If set, then merge the experimental configuration with the …","","","","","","","","","","","","","","","","","","","","","","","","Construct a URL from the deeplink and the protocol object.","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","","","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","","","","","","","This is the protocol that each app understands.","","","","","","","","Returns the argument unchanged.","","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","","","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","","Calls U::from(self).","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","","","","","","","Check the specifically crafted JSON file for this package …","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"i":[1,0,0,16,16,16,16,16,16,16,16,1,16,0,16,16,0,16,16,16,16,16,0,16,16,1,6,6,1,1,3,1,6,16,3,1,6,16,3,1,6,6,0,1,6,1,6,0,0,1,1,3,3,1,1,6,16,3,1,0,1,6,16,3,1,6,6,16,3,3,1,6,0,6,1,6,16,3,1,1,1,1,1,1,1,1,0,1,6,1,0,3,3,3,3,1,1,0,6,1,0,1,1,1,6,1,1,6,16,16,3,1,1,6,16,3,16,1,6,16,3,1,0,6,0,83,84,85,86,87,88,89,90,91,92,85,93,92,85,93,94,95,96,93,84,97,83,97,98,96,93,95,93,83,85,87,88,91,96,93,94,85,95,85,83,85,85,85,93,99,100,99,100,99,99,99,100,26,26,0,0,26,26,0,0,0,0,26,26,26,26,26,26,26,0,26,0,26,26,26,26,26,26,31,19,19,27,20,28,29,30,31,19,27,20,28,29,30,31,26,26,19,26,27,20,28,29,30,31,19,26,27,20,28,29,30,31,19,31,26,26,27,20,28,29,30,31,26,27,20,28,29,30,31,19,19,19,20,27,20,28,29,30,31,19,31,28,31,28,30,29,27,20,28,29,30,31,19,26,27,20,28,29,30,31,19,26,27,20,28,29,30,31,19,26,27,20,28,29,30,31,26,27,20,28,29,30,31,19,27,20,28,29,30,31,26,19,26,27,20,28,29,30,31,31,27,26,20,20,28,20,20,27,20,30,26,26,31,29,26,27,20,28,29,30,31,19,26,27,20,28,29,30,31,19,26,27,20,28,29,30,31,19,26,27,20,28,29,30,31,19,26,27,20,28,29,30,31,19,26,27,20,28,29,30,31,30,28,0,0,0,27,101,102,103,102,103,104,105,106,107,103,108,109,110,108,111,112,107,102,103,108,106,103,113,102,108,109,102,114,113,108,115,107,103,104,111,105,108,102,109,102,102,104,102,103,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,41,41,41,42,41,42,41,41,41,41,42,41,42,41,42,41,41,41,41,42,42,41,42,41,42,41,41,42,42,41,42,41,42,41,42,41,41,42,41,42,41,42,41,41,0,0,0,0,45,54,45,54,0,0,0,54,54,45,54,0,0,45,54,45,45,45,54,0,45,54,0,0,0,45,0,0,54,0,0,45,54,45,54,45,54,45,45,54,0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,0,0,0,0,0,14,14,14,14,14,66,14,66,14,14,66,14,66,14,14,14,66,14,14,14,66,0,14,14,66,14,14,14,14,14,66,14,14,14,14,14,14,66,14,66,116,117,116,116,118,119,118,119,117,10,0,10,10,10,10,10,10,10,10,10,0,10,10,0,10,10,10,10,0,0,10,10,10,10,10,10,10,10,10,10,10,120,121,122,123,123,121,124,0,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,24,24,0,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,125,126,125,125,126,125,0,0,0,0,0,127,128,72,127,128,72,0,72,127,128,72,128,127,127,128,72,127,128,72,127,128,72,127,128,72,72,0,0,129,129,129,129,129,129,129,129,0,130,0,0,0,0,129,0,0,0,0,0,0],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[1,2,3],[[5,[4]]]],[6,[[8,[7]]]],0,[1,[[8,[9]]]],[[1,3,10,11],[[5,[11]]]],[3],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[1,12],[[5,[11]]]],[6,[[8,[7]]]],0,0,[1,1],[6,6],[[]],[[]],0,0,[[1,2,3],[[5,[13]]]],[[1,3],[[5,[[8,[7]]]]]],0,[[],3],[[1,6,14,[15,[14]],9,11,11,11,3],[[5,[11]]]],[[1,1],11],[[6,6],11],[[16,16],11],[[3,3],11],[1,[[5,[4]]]],0,[[1,17],18],[[6,17],18],[[16,17],18],[[3,17],18],[[]],[[]],[19,6],[[]],[[]],[20,3],[[]],[[]],[21,[[5,[[15,[16]]]]]],[6,[[5,[9]]]],[[]],[[]],[[]],[[]],[[1,9],[[5,[7]]]],[1,[[5,[12]]]],[1,7],[[1,7,7],[[5,[11]]]],[[1,2,3],[[5,[4]]]],[1,[[5,[11]]]],[[1,3],[[5,[11]]]],[[1,2,3],[[5,[7]]]],[[],5],[1,[[5,[9]]]],[6,[[5,[9]]]],[[1,3],[[5,[11]]]],0,0,0,0,0,[1,9],[[1,9],[[5,[7]]]],0,[[6,[8,[7]],7],[[5,[7]]]],[1,[[5,[11]]]],0,[[1,2,3],[[5,[11]]]],[1,[[5,[11]]]],[[]],[[]],[19,[[5,[1]]]],[[],22],[[],22],[19,[[5,[16]]]],[[],22],[[],22],[[[8,[9]],[8,[9]],[8,[9]]],[[5,[1]]]],[[],22],[[],22],[[],22],[[],22],[19,[[5,[16]]]],[[],23],[[],23],[[],23],[[],23],[[1,3],[[5,[11]]]],0,[[6,24,14],[[5,[11]]]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[25,25],[25,25],[25,25],[25,25],[25,25],[25,25],[25,25],[25,25],[25,25],[25,25],[25,25],[25,25],[25,25],[25,25],[25,25],[25,25],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,[26,5],[26,26],[27,27],[20,20],[28,28],[29,29],[30,30],[31,31],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],25],0,[[],25],0,[[],27],[[],20],[[],28],[[],29],[[],30],[[],31],0,0,0,0,0,0,0,[[27,17],18],[[20,17],18],[[28,17],18],[[29,17],18],[[30,17],18],[[31,17],18],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[32,[[22,[19,33]]]],[32,[[22,[26,33]]]],[32,[[22,[27,33]]]],[32,[[22,[20,33]]]],[32,[[22,[28,33]]]],[32,[[22,[29,33]]]],[32,[[22,[30,33]]]],[32,[[22,[31,33]]]],[32,[[22,[19,33]]]],[32,[[22,[26,33]]]],[32,[[22,[27,33]]]],[32,[[22,[20,33]]]],[32,[[22,[28,33]]]],[32,[[22,[29,33]]]],[32,[[22,[30,33]]]],[32,[[22,[31,33]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],[[8,[34]]]],[[],[[8,[34]]]],[[],[[8,[34]]]],[[],[[8,[34]]]],[[],[[8,[34]]]],[[],[[8,[34]]]],[[],[[8,[34]]]],[9,11],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,[26,[[8,[20]]]],0,0,0,0,0,0,0,0,[26,11],[26,11],0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],23],[[],23],[[],23],[[],23],[[],23],[[],23],[[],23],[[],23],[[19,32],[[22,[33]]]],[[26,32],[[22,[33]]]],[[27,32],[[22,[33]]]],[[20,32],[[22,[33]]]],[[28,32],[[22,[33]]]],[[29,32],[[22,[33]]]],[[30,32],[[22,[33]]]],[[31,32],[[22,[33]]]],[[19,32],[[22,[33]]]],[[26,32],[[22,[33]]]],[[27,32],[[22,[33]]]],[[20,32],[[22,[33]]]],[[28,32],[[22,[33]]]],[[29,32],[[22,[33]]]],[[30,32],[[22,[33]]]],[[31,32],[[22,[33]]]],0,0,[9,[[22,[7,7]]]],[[9,9,9],[[22,[9]]]],[[9,13],[[22,[9]]]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[],[[15,[9]]]],[[35,9,9],5],[[35,9],5],[16,[[5,[11]]]],[[35,9],5],[[],7],[[],7],[[],[[8,[12]]]],[[],7],[[],7],[[],7],[[],7],[[9,36],[[5,[37]]]],[[6,9,[15,[12]]],[[5,[37]]]],[36,[[5,[7]]]],0,0,0,0,0,0,0,[[9,9],7],[[9,2],[[5,[7]]]],[7,[[5,[[39,[38]]]]]],[[[15,[40]],36],[[5,[11]]]],0,0,[41,42],0,[[]],[[]],[[]],[[]],0,0,[41,7],0,[[42,9],11],[[],41],[[],42],0,0,[41,42],0,0,[[41,17],18],[[42,17],18],[[42,17],18],[[]],[[]],[[]],[[]],0,0,[[[8,[37]],[8,[37]],[8,[37]]],42],0,[[41,43],22],[[42,43],22],0,0,0,[[],7],[37,[[5,[41]]]],[[],22],[[],22],[[],22],[[],22],[[],23],[[],23],0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[44,[47,[[46,[45]]]]],48],[[44,[47,[[46,[45]]]]],[[5,[[51,[49,[50,[48]]]],52]]]],[44,[[47,[[46,[45]]]]]],[53,[[22,[54]]]],0,[[]],[[]],[[],[[5,[55]]]],[[[56,[[47,[[46,[45]]]]]]],[[57,[7]]]],[[]],[[]],[45,[[8,[54]]]],0,[58,45],[[9,9,[8,[37]]],54],[[],[[61,[[60,[59]],[61,[[60,[59]],[60,[59]]]]]]]],0,0,[[9,9,[8,[37]]],[[5,[11]]]],[[[56,[[47,[[46,[45]]]]]],[62,[54]]],63],[[64,9],[[5,[7]]]],0,[[[56,[[47,[[46,[45]]]]]],65],63],[[[56,[[47,[[46,[45]]]]]]],9],[[54,43],22],[[],[[5,[11]]]],[[[56,[[47,[[46,[45]]]]]]],9],[[],22],[[],22],[[],22],[[],22],[[],23],[[],23],[[45,54]],[[45,9],[[8,[9]]]],0,0,[[]],[[]],[2,2],[[]],[[],2],0,[[2,17],18],[[]],[[]],[[]],0,0,[[]],[[],22],[[],22],[[],23],0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[14,14],[[]],[53,[[22,[66]]]],[[14,14],11],0,[[14,17],18],[[14,17],18],[[]],[[]],[[]],[[14,24,[8,[7]],7,11,11],[[5,[37]]]],[[]],[[]],[[14,12],[[5,[37]]]],[[14,7,24,[8,[7]],11,11,[8,[[67,[36]]]]],[[5,[11]]]],[[14,[8,[[67,[36]]]]],[[5,[11]]]],[[66,43],22],[[]],[[],7],[[],22],[28,[[5,[14]]]],[19,[[5,[14]]]],[[],22],[9,[[5,[14]]]],[[36,9],[[5,[14]]]],[9,[[5,[14]]]],[[9,9,9],5],[9,[[5,[14]]]],[[],22],[[],22],[[],23],[[],23],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[10,10],[[]],[9,5],[[10,10],11],[[10,[8,[[67,[36]]]]],[[5,[11]]]],[[68,10],[[5,[37]]]],[[10,17],18],[[]],[[]],[[]],[9,[[5,[11]]]],[9,[[5,[11]]]],[10,[[5,[11]]]],[[]],[36,[[5,[10]]]],[[],22],[29,[[5,[10]]]],[19,[[5,[10]]]],[9,[[5,[10]]]],[9,[[5,[10]]]],[[9,9,9],5],[[],22],[[],23],0,0,0,0,0,0,0,0,0,0,[[]],[[]],0,[68,68],[[]],[[],68],0,[[68,68],11],0,[[68,17],18],[9,68],[31,68],[[]],[[]],[[]],[68,11],0,[[68,37],[[5,[11]]]],[[68,41],11],0,[[]],[[],22],[[],22],[[],23],0,0,0,[[]],[[]],[24,9],[[24,24],11],[[24,17],18],[[24,17],18],[[]],[[24,69,[8,[7]]],[[5,[37]]]],[[]],[24,9],[24,[[5,[70]]]],[[24,[8,[7]],[8,[[67,[36]]]]],[[5,[11]]]],[[],7],[[],22],[[6,27],[[5,[24]]]],[[],22],[[],23],0,0,0,0,0,0,[[]],0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[71],[53,[[22,[72]]]],[[]],[[]],[[]],[[9,73,74],[[76,[75]]]],[[[78,[77]],79],[[76,[[8,[7]]]]]],[[]],[[]],[[]],[[],22],[[],22],[[],22],[[],22],[[],22],[[],22],[[],23],[[],23],[[],23],0,0,0,[9,[[5,[[15,[37]]]]]],[9,[[5,[11]]]],[9,[[5,[[15,[37]]]]]],[9,[[5,[37]]]],[9,[[5,[37]]]],[9,[[5,[9]]]],[9,[[5,[80]]]],[9,11],[[[67,[36]]],11],[[],11],[[37,6,9,11,11],[[5,[37]]]],[[37,6,11,11],[[5,[37]]]],[[37,6,11,11],[[5,[37]]]],[[[67,[36]]],[[5,[81]]]],[[9,64],5],[37,[[5,[[15,[37]]]]]],[37,[[5,[[15,[37]]]]]],[[37,9],[[5,[37]]]],[37,[[5,[[15,[37]]]]]],[37,[[5,[[82,[7,37]]]]]],[[[8,[[67,[36]]]],64],5]],"c":[],"p":[[4,"LaunchableApp"],[3,"StartAppProtocol"],[3,"AppOpenArgs"],[3,"Command"],[6,"Result"],[3,"NimbusApp"],[3,"String"],[4,"Option"],[15,"str"],[4,"ExperimentListSource"],[15,"bool"],[3,"PathBuf"],[15,"usize"],[4,"ExperimentSource"],[3,"Vec"],[4,"AppCommand"],[3,"Formatter"],[6,"Result"],[3,"Cli"],[3,"OpenArgs"],[8,"IntoIterator"],[4,"Result"],[3,"TypeId"],[4,"ManifestSource"],[3,"Command"],[4,"CliCommand"],[3,"ManifestArgs"],[3,"ExperimentArgs"],[3,"ExperimentListArgs"],[3,"ExperimentListSourceArgs"],[3,"ExperimentListFilterArgs"],[3,"ArgMatches"],[6,"Error"],[3,"Id"],[3,"Term"],[3,"Path"],[4,"Value"],[8,"Error"],[3,"Box"],[3,"OsString"],[3,"ExperimentInfo"],[3,"DateRange"],[8,"Serializer"],[3,"LiveReloadLayer"],[3,"InMemoryDb"],[3,"RwLock"],[3,"Arc"],[3,"Router"],[3,"AddrIncoming"],[3,"IntoMakeService"],[3,"Server"],[3,"Error"],[8,"Deserializer"],[3,"StartAppPostPayload"],[4,"SocketAddr"],[3,"State"],[3,"Html"],[3,"Reloader"],[3,"HeaderValue"],[3,"SetResponseHeaderLayer"],[3,"Stack"],[3,"Json"],[8,"IntoResponse"],[8,"Serialize"],[3,"Path"],[3,"FeatureDefaults"],[8,"AsRef"],[3,"ExperimentListFilter"],[3,"FeatureManifest"],[3,"FileLoader"],[8,"Fn"],[3,"Response"],[3,"Duration"],[3,"HeaderMap"],[8,"DeserializeOwned"],[6,"Result"],[8,"HttpClient"],[3,"GenericHttpClient"],[3,"Package"],[15,"u64"],[8,"Deserialize"],[3,"HashMap"],[13,"ApplyFile"],[13,"CaptureLogs"],[13,"Enroll"],[13,"Kill"],[13,"LogState"],[13,"Open"],[13,"Reset"],[13,"TailLogs"],[13,"Unenroll"],[13,"FmlPassthrough"],[13,"ExtractFeatures"],[13,"Info"],[13,"ValidateExperiment"],[13,"Defaults"],[13,"FetchList"],[13,"List"],[13,"Android"],[13,"Ios"],[13,"Fml"],[13,"Enroll"],[13,"Features"],[13,"Fetch"],[13,"Info"],[13,"Validate"],[13,"Defaults"],[13,"TestFeature"],[13,"ApplyFile"],[13,"CaptureLogs"],[13,"FetchList"],[13,"List"],[13,"Open"],[13,"LogState"],[13,"Unenroll"],[13,"FromFeatureFiles"],[13,"FromApiV6"],[13,"WithPatchFile"],[13,"FromList"],[13,"FromApiV6"],[13,"FromRemoteSettings"],[13,"FromFile"],[13,"Filtered"],[13,"FromRecipes"],[13,"FromGithub"],[13,"FromFile"],[3,"TaskClusterRegistry"],[3,"ReqwestGunzippingHttpClient"],[8,"CliUtils"],[8,"Patch"]]},\ +"nimbus_fml":{"doc":"","t":"RAAAAAAFAANIIINNINNGNELLLLLLLLAKLLALLLLLLAKKLLKALLLLKKLLKNEDDGNNNLLLLLLLLLLLLMMLLMLLLLLLLLFMLLLMMLLLLLLLLLLLLLLMMFAFDDLLLLALLALLLLLLALALALMLLLALLLLLLLLAMALLALLLLLLLLDDLLLLLLLLLLLLLLLFLLLLLLLLLLLLLLLLLLLLLFAFFFFFFDDLLLLLLLLLLLLLLMLMLLLLLLLLLLLLLLLLLLLLDLLLLLLLMLLLLMLLLLLLLLLLFFFFFFFFFFFDLLLLLLLLLLLMLLLLLLLLLLDDDLLLLLLLLLLLLLMLLLLMLLMLLLLLLLLLFLLLLLLLLLLLLLLLLLLDDDLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLDDDLLLLLLLLLLLLLLLLLLLLLMMLLLMLLLFLLLLLLLLLLLLLLLLLLLMLLLLLLLLLLLLAFDDLLLLALLALLLLLLALAALMLLLALLLLLLLLAMALLALLLLLLLLDDLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLFAFFFFFFDDLLLLLLLLLLLLLMLMLLLLLLLLLLLLLLLLLLLLDLLLLLLLMLLLMLLLLLLLLLLFFFFFFFFFDLLLLLLLLLLLMLLLLLLLLLLDDDLLLLLLLLLLLLMLLLLMLMLLLLLLLLLFLLLLLLLLLLLLLLLLLLDDDLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLDDDLLLLLLLLLLLLLLLLLMMLLLMLLLLLLLLLLLLLLLLLLLLLLMLLLLLLLLLLLLRAFFFFFFFFFFFAENNNDNDDNDNDMLLLLLLLLLLLLMMLLLLLLLLLLLLLLMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLRFFFFFFFFFFFFFFFDLLMFLLLLLLLFLMMLLLNENNENNNNNNNNNNNNNNGNNNNNLLLLFLLLLLLLLLLLLLLLLLLLLLLLLLLLLMMMDDDDDDDDDDDDDDDMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMMLMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMMLLLLLLLMMMMMMMMLLLLLLLLLLLLLLLLMMLLLLLLLLLLLLLLLMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMLMLLLLLLLLLLLLLLLLMMLMMMMLLLLLMMMMLLLLLLLLLLLLLLLLLMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMMMMMMMNNNNDNNNDDDNDNNNGNENDNDNNGNNEIEDLMMLLMLLLLLLLLLLLLLLLLLLLLLLMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMLLLLLLLLLLLLLLLLMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMLMLLLLLKLLLLLLMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMMLLLLLLLLLLLLLLLLLLMLLLLLMMMMMMLLLLLMLLMLLMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMLLLLLLLLLLLLLLLLLLLLLLLLLMMDLLLFFLLMLLLFLLLLFLLFLFMLLLFFFFAFFDERDNNHLLLLLLLLLLMMLLLLLLLMLMMLLLLMLLLLLLLLLLLFLLLLLLMLLMLLLLLLLLLLLLLLLL","n":["SUPPORT_URL_LOADING","backends","command_line","defaults_merger","error","frontend","intermediate_representation","main","parser","util","Bool","CodeDeclaration","CodeOracle","CodeType","Image","Int","LiteralRenderer","String","Text","TypeIdentifier","Variables","VariablesType","as_json","as_json_transform","borrow","borrow_mut","create_transform","defaults_mapper","defaults_type","definition_code","experimenter_manifest","find","fmt","from","frontend_manifest","helper_code","imports","imports","initialization_code","into","is_resource_id","kotlin","literal","literal","merge_transform","preference_getter","property_getter","swift","to_string","try_from","try_into","type_id","type_label","value_getter","value_mapper","value_merger","variables_type","Boolean","ExperimentManifestPropType","ExperimenterFeature","ExperimenterFeatureProperty","ExperimenterManifest","Int","Json","String","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone_into","clone_into","default","default","description","description","deserialize","deserialize","exposure_description","fmt","fmt","fmt","from","from","from","from","from","generate_manifest","has_exposure","into","into","into","is_early_startup","property_type","serialize","serialize","to_owned","to_owned","to_string","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","variables","variants","merge","gen_structs","generate_struct","ConcreteCodeOracle","FeatureManifestDeclaration","borrow","borrow","borrow_mut","borrow_mut","bundled","clone","clone_into","common","create_code_type","declaration_code","default","dyn_render","dyn_render_into","dyn_write_into","enum_","extension","feature","feature_properties","filters","find","fm","fmt","from","from","imports","imports","initialization_code","into","into","iter_feature_defs","members","mime_type","new","object","oracle","primitives","render_into","size_hint","structural","to_owned","to_string","try_from","try_from","try_into","try_into","type_id","type_id","ImageCodeType","TextCodeType","as_json_transform","borrow","borrow","borrow_mut","borrow_mut","defaults_mapper","defaults_mapper","defaults_type","defaults_type","from","from","imports","imports","into","into","is_resource_id","is_resource_id","is_resource_id","literal","literal","preference_getter","property_getter","property_getter","try_from","try_from","try_into","try_into","type_id","type_id","type_label","type_label","value_getter","value_getter","value_mapper","value_mapper","variables_type","variables_type","class_name","code_type","enum_variant_name","quoted","var_name","property_getter","value_getter","value_mapper","EnumCodeDeclaration","EnumCodeType","as_json_transform","borrow","borrow","borrow_mut","borrow_mut","create_transform","definition_code","dyn_render","dyn_render_into","dyn_write_into","extension","fmt","from","from","id","inner","inner","into","into","literal","mime_type","new","new","property_getter","render_into","size_hint","to_string","try_from","try_from","try_into","try_into","type_id","type_id","type_label","value_getter","value_mapper","variables_type","FeatureCodeDeclaration","borrow","borrow_mut","definition_code","dyn_render","dyn_render_into","dyn_write_into","extension","fm","fmt","from","imports","inner","inner","into","literal","mime_type","new","render_into","size_hint","to_string","try_from","try_into","type_id","class_name","comment","defaults_type_label","enum_variant_name","literal","preference_getter","property","quoted","to_json","type_label","var_name","ImportedModuleInitialization","borrow","borrow_mut","definition_code","dyn_render","dyn_render_into","dyn_write_into","extension","fmt","from","imports","initialization_code","inner","into","literal","mime_type","new","render_into","size_hint","to_string","try_from","try_into","type_id","ObjectCodeDeclaration","ObjectCodeType","ObjectRuntime","as_json_transform","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","create_transform","definition_code","dyn_render","dyn_render_into","dyn_write_into","extension","fm","fmt","from","from","from","id","imports","inner","inner","into","into","into","literal","literal","merge_transform","mime_type","new","new","object_literal","property_getter","render_into","size_hint","to_string","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","type_label","value_getter","value_mapper","value_merger","variables_type","BooleanCodeType","IntCodeType","StringCodeType","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","from","from","from","into","into","into","literal","literal","literal","preference_getter","preference_getter","preference_getter","property_getter","property_getter","property_getter","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","type_label","type_label","type_label","value_getter","value_getter","value_getter","value_mapper","value_mapper","value_mapper","variables_type","variables_type","variables_type","ListCodeType","MapCodeType","OptionalCodeType","as_json_transform","as_json_transform","as_json_transform","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","create_transform","create_transform","defaults_mapper","defaults_mapper","defaults_mapper","defaults_type","defaults_type","defaults_type","from","from","from","imports","inner","inner","into","into","into","k_type","literal","literal","literal","map_functions","merge_transform","new","new","new","property_getter","property_getter","property_getter","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","type_label","type_label","type_label","v_type","value_getter","value_getter","value_getter","value_mapper","value_mapper","value_mapper","value_merger","value_merger","value_merger","variables_type","variables_type","variables_type","gen_structs","generate_struct","ConcreteCodeOracle","FeatureManifestDeclaration","borrow","borrow","borrow_mut","borrow_mut","bundled","clone","clone_into","common","create_code_type","declaration_code","default","dyn_render","dyn_render_into","dyn_write_into","enum_","extension","feature","filters","find","fm","fmt","from","from","imports","imports","initialization_code","into","into","iter_feature_defs","members","mime_type","new","object","oracle","primitives","render_into","size_hint","structural","to_owned","to_string","try_from","try_from","try_into","try_into","type_id","type_id","ImageCodeType","TextCodeType","borrow","borrow","borrow_mut","borrow_mut","defaults_mapper","defaults_mapper","defaults_type","defaults_type","from","from","imports","into","into","literal","literal","property_getter","property_getter","try_from","try_from","try_into","try_into","type_id","type_id","type_label","type_label","value_getter","value_getter","value_mapper","value_mapper","variables_type","variables_type","class_name","code_type","enum_variant_name","quoted","var_name","property_getter","value_getter","value_mapper","EnumCodeDeclaration","EnumCodeType","borrow","borrow","borrow_mut","borrow_mut","create_transform","definition_code","dyn_render","dyn_render_into","dyn_write_into","extension","fmt","from","from","id","inner","inner","into","into","literal","mime_type","new","new","property_getter","render_into","size_hint","to_string","try_from","try_from","try_into","try_into","type_id","type_id","type_label","value_getter","value_mapper","variables_type","FeatureCodeDeclaration","borrow","borrow_mut","definition_code","dyn_render","dyn_render_into","dyn_write_into","extension","fm","fmt","from","inner","inner","into","literal","mime_type","new","render_into","size_hint","to_string","try_from","try_into","type_id","class_name","comment","defaults_type_label","enum_variant_name","literal","property","quoted","type_label","var_name","ImportedModuleInitialization","borrow","borrow_mut","definition_code","dyn_render","dyn_render_into","dyn_write_into","extension","fmt","from","imports","initialization_code","inner","into","literal","mime_type","new","render_into","size_hint","to_string","try_from","try_into","type_id","ObjectCodeDeclaration","ObjectCodeType","ObjectRuntime","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","create_transform","definition_code","dyn_render","dyn_render_into","dyn_write_into","extension","fm","fmt","from","from","from","id","inner","inner","into","into","into","literal","literal","merge_transform","mime_type","new","new","object_literal","property_getter","render_into","size_hint","to_string","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","type_label","value_getter","value_mapper","value_merger","variables_type","BooleanCodeType","IntCodeType","StringCodeType","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","from","from","from","into","into","into","literal","literal","literal","property_getter","property_getter","property_getter","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","type_label","type_label","type_label","value_getter","value_getter","value_getter","value_mapper","value_mapper","value_mapper","variables_type","variables_type","variables_type","ListCodeType","MapCodeType","OptionalCodeType","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","create_transform","create_transform","defaults_mapper","defaults_mapper","defaults_mapper","defaults_type","defaults_type","defaults_type","from","from","from","inner","inner","into","into","into","k_type","literal","literal","literal","merge_transform","new","new","new","property_getter","property_getter","property_getter","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","type_label","type_label","type_label","v_type","value_getter","value_getter","value_getter","value_mapper","value_mapper","value_mapper","value_merger","value_merger","value_merger","variables_type","variables_type","variables_type","RELEASE_CHANNEL","commands","create_generate_command_experimenter_from_cli","create_generate_command_from_cli","create_loader","create_print_channels_from_cli","create_single_file_from_cli","create_validate_command_from_cli","do_main","file_path","get_command_from_cli","input_file","process_command","workflows","CliCmd","FetchFile","Generate","GenerateExperimenter","GenerateExperimenterManifestCmd","GenerateSingleFileManifest","GenerateSingleFileManifestCmd","GenerateStructCmd","PrintChannels","PrintChannelsCmd","Validate","ValidateCmd","as_json","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","channel","channel","clone","clone_into","from","from","from","from","from","from","into","into","into","into","into","into","language","language","load_from_ir","load_from_ir","loader","loader","loader","loader","loader","manifest","manifest","manifest","manifest","manifest","output","output","output","to_owned","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","MATCHING_FML_EXTENSION","fetch_file","generate_experimenter_manifest","generate_single_file_manifest","generate_struct","generate_struct_from_dir","generate_struct_from_glob","generate_struct_from_ir","generate_struct_single","load_feature_manifest","output_err","output_note","output_ok","output_warn","print_channels","validate","DefaultsMerger","borrow","borrow_mut","channel","collect_channel_defaults","collect_feature_defaults","collect_map_defaults","collect_object_defaults","collect_prop_defaults","from","into","merge_feature_defaults","merge_two_defaults","new","objects","supported_channels","try_from","try_into","type_id","CliError","ClientError","ClientError","EmailError","FMLError","FMLModuleError","Fatal","FeatureValidationError","FetchError","IOError","InternalError","InvalidChannelError","InvalidFeatureConfig","InvalidFeatureError","InvalidFeatureId","InvalidFeatureValue","InvalidPath","JSONError","JsonMergeError","Result","TemplateProblem","TypeParsingError","UrlError","ValidationError","YAMLError","borrow","borrow","borrow_mut","borrow_mut","did_you_mean","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","into","into","provide","provide","source","to_string","to_string","try_from","try_from","try_into","try_into","type_id","type_id","literals","message","path","AboutBlock","DefaultBlock","DocumentationLink","EnumBody","EnumVariantBody","FeatureBody","FeatureFieldBody","FeatureMetadata","FieldBody","ImportBlock","KotlinAboutBlock","ManifestFrontEnd","ObjectBody","SwiftAboutBlock","Types","about","allow_coenrollment","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","channel","channel","channels","channels","channels","class","class","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","configurator","contacts","default","default","default","default","default","default","default","default","default","description","description","description","description","description","description","description_only","deserialize","deserialize","deserialize","deserialize","deserialize","deserialize","deserialize","deserialize","deserialize","deserialize","deserialize","deserialize","deserialize","deserialize","deserialize","documentation","enums","eq","eq","eq","eq","eq","equivalent","equivalent","equivalent","equivalent","equivalent","equivalent","equivalent","equivalent","equivalent","equivalent","events","features","features","field","fields","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","get_enums","get_feature_defs","get_intermediate_representation","get_objects","get_prop_def_from_feature_field","get_prop_def_from_field","get_types","imports","includes","includes","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","is_includable","kotlin_about","legacy_types","merge_channels","meta_bug","metadata","module","name","nimbus_fully_qualified_name","nimbus_module_name","nimbus_object_name_kt","nimbus_object_name_swift","nimbus_package_name","objects","package","path","pref_key","resource_package_name","serialize","serialize","serialize","serialize","serialize","serialize","serialize","serialize","serialize","serialize","serialize","serialize","serialize","serialize","serialize","supports","swift_about","targeting","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","types","url","value","variable_type","variables","variants","version","Boolean","BundleImage","BundleText","Enum","EnumDef","EnumMap","ExperimenterJSON","ExperimenterYAML","FeatureDef","FeatureManifest","FromStringDef","IR","ImportedModule","Int","Kotlin","List","Literal","Local","ModuleId","Object","ObjectDef","Option","PropDef","Remote","String","StringId","StringMap","Swift","TargetLanguage","TypeFinder","TypeRef","VariantDef","about","about","all_imports","all_types","allow_coenrollment","allow_coenrollment","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","channel","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","cmp","create_experimenter_feature","default","default","default","default","default","default","default","default","default_json","default_json","deserialize","deserialize","deserialize","deserialize","deserialize","deserialize","deserialize","deserialize","deserialize","doc","doc","doc","doc","doc","doc","doc","doc","doc","doc","enum_defs","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","equivalent","equivalent","equivalent","equivalent","equivalent","equivalent","equivalent","equivalent","equivalent","equivalent","equivalent","equivalent","equivalent","equivalent","equivalent","equivalent","equivalent","equivalent","equivalent","equivalent","extension","feature_defs","features","features","find_enum","find_feature","find_import","find_object","find_prop","find_types","find_types","find_types","find_types","find_types","find_types","find_types","fm","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from_extension","get_coenrolling_feature_ids","get_feature","get_hash","get_hash","get_hash","has_prefs","has_prefs","hash","hash","hash","id","imported_features","into","into","into","into","into","into","into","into","into","into","into","iter_all_enum_defs","iter_all_feature_defs","iter_all_object_defs","iter_enum_defs","iter_feature_defs","iter_imported_files","iter_object_defs","metadata","name","name","name","name","name","name","name","name","name","name","name","new","new","new","new","new","obj_defs","partial_cmp","pref_key","pref_key","props","props","props","props","props_to_variables","serialize","serialize","serialize","serialize","serialize","serialize","serialize","serialize","serialize","supports_prefs","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_string","to_string","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","typ","typ","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","validate_default_by_typ","validate_defaults","validate_enum_defs","validate_feature_config","validate_feature_def","validate_feature_defs","validate_feature_structure","validate_manifest","validate_manifest_for_lang","validate_obj_defs","validate_prop_defaults","validate_props","validate_type_ref","variants","variants","variants","Parser","borrow","borrow_mut","canonicalize_import_paths","check_can_import_list","check_can_import_manifest","check_can_merge_imports","check_can_merge_manifest","files","fmt","from","get_intermediate_representation","get_typeref_from_string","into","load_frontend","load_imports","load_manifest","merge_import_block","merge_import_block_list","merge_manifest","merge_map","new","parse_typeref_string","source","try_from","try_into","type_id","as_dir","build_dir","generated_src_dir","join","loaders","pkg_dir","sdk_dir","FileLoader","FilePath","GITHUB_USER_CONTENT_DOTCOM","LoaderConfig","Local","Remote","USER_AGENT","add_repo","add_repo_file","add_repo_relative","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","cache_dir","cache_dir","cache_dir","canonicalize","clone","clone","clone","clone_into","clone_into","clone_into","config","create_cache_path_buf","cwd","cwd","default","default_remote_path","drop","fetch_and_cache","fetch_client","file_path","fmt","fmt","fmt","from","from","from","from","into","into","into","is_dir","join","join","lookup_repo_path","new","new","read_to_string","refs","remote_file_path","repo_and_path","repo_files","resolve_url_shortcut","tmp_cache_dir","to_owned","to_owned","to_owned","to_string","try_from","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id"],"q":[[0,"nimbus_fml"],[10,"nimbus_fml::backends"],[57,"nimbus_fml::backends::experimenter_manifest"],[113,"nimbus_fml::backends::frontend_manifest"],[114,"nimbus_fml::backends::kotlin"],[116,"nimbus_fml::backends::kotlin::gen_structs"],[165,"nimbus_fml::backends::kotlin::gen_structs::bundled"],[204,"nimbus_fml::backends::kotlin::gen_structs::common"],[209,"nimbus_fml::backends::kotlin::gen_structs::common::code_type"],[212,"nimbus_fml::backends::kotlin::gen_structs::enum_"],[251,"nimbus_fml::backends::kotlin::gen_structs::feature"],[275,"nimbus_fml::backends::kotlin::gen_structs::filters"],[286,"nimbus_fml::backends::kotlin::gen_structs::imports"],[309,"nimbus_fml::backends::kotlin::gen_structs::object"],[362,"nimbus_fml::backends::kotlin::gen_structs::primitives"],[407,"nimbus_fml::backends::kotlin::gen_structs::structural"],[473,"nimbus_fml::backends::swift"],[475,"nimbus_fml::backends::swift::gen_structs"],[523,"nimbus_fml::backends::swift::gen_structs::bundled"],[556,"nimbus_fml::backends::swift::gen_structs::common"],[561,"nimbus_fml::backends::swift::gen_structs::common::code_type"],[564,"nimbus_fml::backends::swift::gen_structs::enum_"],[602,"nimbus_fml::backends::swift::gen_structs::feature"],[625,"nimbus_fml::backends::swift::gen_structs::filters"],[634,"nimbus_fml::backends::swift::gen_structs::imports"],[657,"nimbus_fml::backends::swift::gen_structs::object"],[708,"nimbus_fml::backends::swift::gen_structs::primitives"],[750,"nimbus_fml::backends::swift::gen_structs::structural"],[811,"nimbus_fml::command_line"],[825,"nimbus_fml::command_line::commands"],[902,"nimbus_fml::command_line::workflows"],[918,"nimbus_fml::defaults_merger"],[937,"nimbus_fml::error"],[995,"nimbus_fml::error::FMLError"],[998,"nimbus_fml::frontend"],[1303,"nimbus_fml::intermediate_representation"],[1625,"nimbus_fml::parser"],[1652,"nimbus_fml::util"],[1659,"nimbus_fml::util::loaders"]],"d":["","Backend traits","","","","","","","","","","A trait that is able to render a declaration about a …","An object to look up a foreign language code specific …","A Trait to emit foreign language code to handle referenced …","","","","","","","","The generated code is running against hand written code to …","Call from the template","Implement these in different code types, and call …","","","A function handle that is capable of turning the variables …","","","Code which represents this member. e.g. the foreign …","","","","Returns the argument unchanged.","","Optional helper code to make this type work. This might …","A list of imports that are needed if this type is in use. …","A list of imports that are needed if this type is in use. …","Code (one or more statements) that is run on start-up of …","Calls U::from(self).","","","A representation of the given literal for this type. N.B. …","","A function handle that is capable of merging two instances …","","The language specific expression that gets a value of the …","","","","","","The language specific label used to reference this type. …","The expression needed to get a value out of a Variables …","The method call here will use the create_transform to …","The method call to merge the value with the defaults.","The name of the type as it’s represented in the Variables…","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","","","","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","","","Calls U::from(self).","Calls U::from(self).","","","","A representation of the given literal for this type. N.B. …","A representation of the given literal for this type. N.B. …","","","","","","","","","","The language specific label used to reference this type. …","The language specific label used to reference this type. …","","","","","The name of the type as it’s represented in the Variables…","The name of the type as it’s represented in the Variables…","Get the idiomatic Kotlin rendering of a class name (for …","","Get the idiomatic Kotlin rendering of an individual enum …","Surrounds a string with quotes. In Kotlin, you can “”…","Get the idiomatic Kotlin rendering of a variable name.","The language specific expression that gets a value of the …","","","","","","","","","","A function handle that is capable of turning the variables …","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","","","","Calls U::from(self).","Calls U::from(self).","A representation of the given literal for this type. N.B. …","","","","","","","","","","","","","","The language specific label used to reference this type. …","","","The name of the type as it’s represented in the Variables…","","","","","","","","","","","Returns the argument unchanged.","","","","Calls U::from(self).","","","","","","","","","","Get the idiomatic Kotlin rendering of a class name (for …","","","Get the idiomatic Kotlin rendering of an individual enum …","","","","","","","Get the idiomatic Kotlin rendering of a variable name.","","","","","","","","","","Returns the argument unchanged.","","","","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","","","","","","The language specific expression that gets a value of the …","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","A representation of the given literal for this type. N.B. …","A representation of the given literal for this type. N.B. …","A representation of the given literal for this type. N.B. …","","","","","","","","","","","","","","","","The language specific label used to reference this type. …","The language specific label used to reference this type. …","The language specific label used to reference this type. …","","","","","","","The name of the type as it’s represented in the Variables…","The name of the type as it’s represented in the Variables…","The name of the type as it’s represented in the Variables…","","","","Implement these in different code types, and call …","","","","","","","","","The name of the type as it’s represented in the Variables…","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","A representation of the given literal for this type. N.B. …","A representation of the given literal for this type. N.B. …","A representation of the given literal for this type. N.B. …","","","","","","The language specific expression that gets a value of the …","","","","","","","","","","","","The language specific label used to reference this type. …","The language specific label used to reference this type. …","The language specific label used to reference this type. …","","","","","The method call here will use the create_transform to …","","","The method call to merge the value with the defaults.","","","The name of the type as it’s represented in the Variables…","The name of the type as it’s represented in the Variables…","The name of the type as it’s represented in the Variables…","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","","","","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","","Calls U::from(self).","Calls U::from(self).","A representation of the given literal for this type. N.B. …","A representation of the given literal for this type. N.B. …","","","","","","","","","The language specific label used to reference this type. …","The language specific label used to reference this type. …","","","","","The name of the type as it’s represented in the Variables…","The name of the type as it’s represented in the Variables…","Get the idiomatic Swift rendering of a class name (for …","","Get the idiomatic Swift rendering of an individual enum …","Surrounds a property name with quotes. It is assumed that …","Get the idiomatic Swift rendering of a variable name.","The language specific expression that gets a value of the …","","","","","","","","","A function handle that is capable of turning the variables …","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","","","","Calls U::from(self).","Calls U::from(self).","A representation of the given literal for this type. N.B. …","","","","","","","","","","","","","","The language specific label used to reference this type. …","","","The name of the type as it’s represented in the Variables…","","","","","","","","","","","Returns the argument unchanged.","","","Calls U::from(self).","","","","","","","","","","Get the idiomatic Swift rendering of a class name (for …","","","Get the idiomatic Swift rendering of an individual enum …","","","","","Get the idiomatic Swift rendering of a variable name.","","","","","","","","","","Returns the argument unchanged.","","","","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","","","","","","The language specific expression that gets a value of the …","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","A representation of the given literal for this type. N.B. …","A representation of the given literal for this type. N.B. …","A representation of the given literal for this type. N.B. …","","","","","","","","","","","","","The language specific label used to reference this type. …","The language specific label used to reference this type. …","The language specific label used to reference this type. …","","","","","","","The name of the type as it’s represented in the Variables…","The name of the type as it’s represented in the Variables…","The name of the type as it’s represented in the Variables…","","","","","","","","","","The name of the type as it’s represented in the Variables…","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","A representation of the given literal for this type. N.B. …","A representation of the given literal for this type. N.B. …","A representation of the given literal for this type. N.B. …","","","","","The language specific expression that gets a value of the …","","","","","","","","","","","","The language specific label used to reference this type. …","The language specific label used to reference this type. …","The language specific label used to reference this type. …","","","","","The method call here will use the create_transform to …","","","The method call to merge the value with the defaults.","","","The name of the type as it’s represented in the Variables…","The name of the type as it’s represented in the Variables…","The name of the type as it’s represented in the Variables…","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Use this when recursively looking for files.","","","","","","","","","","","","","","","","","","","","Collects the channel defaults of the feature manifest and …","","","","","Returns the argument unchanged.","Calls U::from(self).","Transforms a feature definition with unmerged defaults …","Merges two serde_json::Values into one","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","","","","","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","A link to a Web based configuration UI for this feature. …","A list of contacts (engineers, product owners) who can be …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","A list of named URLs to documentation for this feature.","","","","","","","","","","","","","","","","","What Glean events can the feature produce? These should be …","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","","Returns the argument unchanged.","","Returns the argument unchanged.","Returns the argument unchanged.","","Returns the argument unchanged.","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","Returns the argument unchanged.","Retrieves all the Enum type definitions represented in the …","Retrieves all the feature definitions represented in the …","","Retrieves all the Object type definitions represented in …","","Transforms a front-end field definition, a tuple of String …","Retrieves all the types represented in the Manifest","","","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","Where should QA file issues for this feature?","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","An identifier derived from a FilePath of a top-level or …","","","","","","","","","","","","The TypeRef enum defines a reference to a type.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","","","","","","","","","","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","This function is used to validate a new value for a …","","","","","","","","","","","","","","","","","","Check if this parent can import this child.","","","","","Returns the argument unchanged.","","","Calls U::from(self).","","Load a manifest and all its imports, recursively if …","","","","","","","","","","","","","","","","","","","Utility class to abstract away the differences between …","A small enum for working with URLs and relative files","","","","","","Add a repo and version/tag/ref/location. repo_id is the …","Load a file containing mapping of repo names to FilePaths. …","","","","","","","","","","","","","","","","","","","","","","","","","","","Make a new path.","","","","Returns the argument unchanged.","Returns the argument unchanged.","","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","Appends a suffix to a path. If the self is a local file …","Joins a path to a string, to make a new path.","","","","This loads a text file from disk or the network.","","","","","Checks that the given string has a @organization/repo/ …","","","","","","","","","","","","","","",""],"i":[0,0,0,0,0,0,0,0,0,0,9,0,0,0,9,9,0,9,9,0,9,0,7,7,9,9,7,7,7,40,0,2,9,9,0,7,7,40,40,9,7,0,7,15,7,7,7,0,9,9,9,9,7,7,7,7,7,21,0,0,0,0,21,21,21,21,18,19,21,18,19,18,19,18,19,18,19,18,19,18,19,18,21,18,19,21,21,21,18,19,0,18,21,18,19,18,19,18,19,18,19,21,21,18,19,21,18,19,21,18,19,18,19,0,0,0,0,0,32,31,32,31,0,31,31,0,31,32,31,32,32,32,0,32,0,32,0,31,32,32,32,31,0,32,32,32,31,32,32,32,32,0,32,0,32,32,0,31,32,32,31,32,31,32,31,0,0,44,45,44,45,44,45,44,45,44,45,44,45,44,45,44,0,45,44,45,44,45,45,44,45,44,45,44,45,44,45,44,45,44,45,44,45,44,0,0,0,0,0,0,0,0,0,0,46,46,47,46,47,46,47,47,47,47,47,47,46,47,46,47,47,46,47,46,47,46,47,46,47,47,47,46,47,46,47,46,47,46,46,46,46,0,49,49,49,49,49,49,49,49,49,49,49,49,49,49,49,49,49,49,49,49,49,49,49,0,0,0,0,0,0,0,0,0,0,0,0,51,51,51,51,51,51,51,51,51,51,51,51,51,51,51,51,51,51,51,51,51,51,0,0,0,53,132,53,54,132,53,54,53,54,54,54,54,54,54,54,132,53,54,53,54,54,54,132,53,54,53,54,53,54,53,54,0,53,54,54,54,132,53,54,132,53,54,132,53,54,53,53,53,53,53,0,0,0,56,57,58,56,57,58,56,57,58,56,57,58,56,57,58,56,57,58,56,57,58,56,57,58,56,57,58,56,57,58,56,57,58,56,57,58,56,57,58,56,57,58,0,0,0,59,60,61,59,60,61,59,60,61,59,60,59,60,61,59,60,61,59,60,61,60,59,61,59,60,61,60,59,60,61,0,60,59,60,61,59,60,61,59,60,61,59,60,61,59,60,61,59,60,61,60,59,60,61,59,60,61,59,60,61,59,60,61,0,0,0,0,63,62,63,62,0,62,62,0,62,63,62,63,63,63,0,63,0,0,62,63,63,63,62,0,63,63,63,62,63,63,63,63,0,63,0,63,63,0,62,63,63,62,63,62,63,62,0,0,64,65,64,65,64,65,64,65,64,65,65,64,65,64,65,64,65,64,65,64,65,64,65,64,65,64,65,64,65,64,65,0,0,0,0,0,0,0,0,0,0,66,67,66,67,66,67,67,67,67,67,67,66,67,66,67,67,66,67,66,67,66,67,66,67,67,67,66,67,66,67,66,67,66,66,66,66,0,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,68,0,0,0,0,0,0,0,0,0,0,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,0,0,0,133,70,71,133,70,71,70,71,71,71,71,71,71,71,133,70,71,70,71,71,133,70,71,70,71,70,71,70,71,0,70,71,71,71,133,70,71,133,70,71,133,70,71,70,70,70,70,70,0,0,0,72,73,74,72,73,74,72,73,74,72,73,74,72,73,74,72,73,74,72,73,74,72,73,74,72,73,74,72,73,74,72,73,74,72,73,74,72,73,74,0,0,0,75,76,77,75,76,77,75,76,75,76,77,75,76,77,75,76,77,75,77,75,76,77,76,75,76,77,76,75,76,77,75,76,77,75,76,77,75,76,77,75,76,77,75,76,77,76,75,76,77,75,76,77,75,76,77,75,76,77,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,86,86,86,0,86,0,0,86,0,86,0,81,86,23,82,83,81,30,86,23,82,83,81,30,82,30,30,30,86,23,82,83,81,30,86,23,82,83,81,30,23,30,23,30,23,82,83,81,30,23,82,83,81,30,23,82,30,30,86,23,82,83,81,30,86,23,82,83,81,30,86,23,82,83,81,30,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,93,93,93,0,93,93,93,93,93,93,93,0,93,93,93,93,93,93,24,0,24,24,0,24,24,24,24,24,24,24,95,24,95,95,24,24,95,0,24,24,24,24,24,24,95,24,95,0,24,24,95,95,24,24,24,24,24,24,24,24,24,24,95,24,95,24,95,24,24,95,24,95,24,95,24,95,134,134,134,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,104,115,105,106,107,108,109,110,111,112,113,114,115,116,117,104,90,105,106,107,108,109,110,111,112,113,114,115,116,117,104,90,114,90,104,104,90,112,113,105,106,107,108,109,110,111,112,113,114,115,116,117,104,90,105,106,107,108,109,110,111,112,113,114,115,116,117,104,90,116,116,110,111,112,113,114,116,104,108,115,105,106,108,109,111,116,111,105,106,107,108,109,110,111,112,113,114,115,116,117,104,90,116,110,111,112,113,116,117,111,111,112,112,113,113,116,116,117,117,116,114,104,107,109,105,106,107,108,109,110,111,112,113,114,115,116,117,104,90,105,105,106,106,107,107,108,108,109,109,110,111,112,113,114,115,115,116,117,104,104,90,90,104,104,104,104,104,104,104,104,104,104,105,106,107,108,109,110,111,112,113,114,115,116,117,104,90,111,111,104,90,116,115,112,117,111,111,111,111,111,110,113,114,107,111,105,106,107,108,109,110,111,112,113,114,115,116,117,104,90,111,111,90,105,106,107,108,109,110,111,112,113,114,115,116,117,104,90,105,106,107,108,109,110,111,112,113,114,115,116,117,104,90,105,106,107,108,109,110,111,112,113,114,115,116,117,104,90,105,106,107,108,109,110,111,112,113,114,115,116,117,104,90,104,117,90,108,115,106,104,6,6,6,6,0,6,120,120,0,0,0,120,0,6,120,6,0,119,0,6,0,6,0,119,6,0,6,120,0,0,0,0,52,22,22,135,39,39,120,6,119,22,39,48,121,118,55,38,52,120,6,119,22,39,48,121,118,55,38,52,22,120,6,119,22,39,48,121,118,55,38,52,120,6,119,22,39,48,121,118,55,38,52,119,22,119,22,39,48,118,55,38,38,22,39,6,119,22,39,48,121,118,55,38,39,48,118,55,38,48,121,118,55,38,22,120,6,119,22,39,48,121,118,55,38,120,120,6,6,119,119,22,22,39,39,48,48,121,121,118,118,55,55,38,38,120,22,52,52,22,22,22,22,55,135,6,22,39,48,55,38,52,120,6,6,119,119,22,39,48,121,118,55,38,52,120,6,119,22,39,48,121,118,55,38,52,120,22,22,120,6,119,39,38,120,6,119,22,22,120,6,119,22,39,48,121,118,55,38,52,22,22,22,22,22,22,22,39,39,48,118,55,38,39,48,121,118,55,38,22,39,118,55,52,22,119,38,38,39,55,39,55,22,6,119,22,39,48,121,118,55,38,6,120,6,119,22,39,48,121,118,55,38,52,6,119,120,120,120,120,120,6,119,119,22,39,48,121,118,55,38,52,120,6,119,22,39,48,121,118,55,38,52,38,38,120,6,119,22,39,48,121,118,55,38,52,22,22,22,22,22,22,22,22,22,22,22,22,22,48,48,121,0,130,130,130,0,0,130,130,130,130,130,130,0,130,130,130,130,0,130,130,0,130,0,130,130,130,130,0,0,0,0,0,0,0,0,0,0,0,88,88,0,87,87,87,80,88,87,80,88,87,87,80,87,88,80,88,87,80,88,87,87,87,80,87,80,87,87,87,87,87,88,88,87,80,88,88,87,80,88,87,0,88,87,87,88,87,87,80,87,80,80,87,87,80,88,87,88,80,88,87,87,80,88,87,80,88,87],"f":[0,0,0,0,0,0,0,[[],1],0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[2,3],4],[[2,3],[[5,[4]]]],[[]],[[]],[2,[[5,[4]]]],[[2,3,3],[[5,[4]]]],[2,4],[2,[[5,[4]]]],0,[6,[[8,[7]]]],[[9,10],11],[[]],0,[2,[[5,[4]]]],[2,[[5,[[12,[4]]]]]],[2,[[5,[[12,[4]]]]]],[2,[[5,[4]]]],[[]],[13,14],0,[[2,3,15,13],4],[[2,6,13,3],4],[2,[[5,[4]]]],[[2,3,3],[[5,[4]]]],[[2,3,3,3],4],0,[[],4],[[],16],[[],16],[[],17],[2,4],[[2,3,3],4],[2,[[5,[4]]]],[[2,3],[[5,[4]]]],[2,9],0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[18,18],[19,19],[[]],[[]],[[],18],[[],19],0,0,[20,[[16,[18]]]],[20,[[16,[19]]]],0,[[21,10],11],[[18,10],11],[[19,10],11],[6,21],[[[8,[6]]],21],[[]],[[]],[[]],[[22,23],[[16,[24]]]],0,[[]],[[]],[[]],0,0,[[18,25],16],[[19,25],16],[[]],[[]],[[],4],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],17],[[],17],[[],17],0,0,[[22,26,26],[[29,[4,[28,[27]]]]]],0,[[22,30],[[16,[24]]]],0,0,[[]],[[]],[[]],[[]],0,[31,31],[[]],0,[[31,6],[[8,[7]]]],[32,[[12,[4]]]],[[],31],[[],[[16,[4,33]]]],[34,[[16,[33]]]],[35,[[16,[36]]]],0,[[],[[5,[37]]]],0,[32,[[12,[38]]]],0,[[31,6],[[8,[7]]]],0,[[32,10],11],[[]],[[]],0,[32,[[12,[4]]]],[32,[[12,[4]]]],[[]],[[]],[32,[[12,[39]]]],[32,[[12,[[8,[40]]]]]],[[],37],[22,32],0,0,0,[[32,[0,[34,41]]],42],[[],43],0,[[]],[[],4],[[],16],[[],16],[[],16],[[],16],[[],17],[[],17],0,0,[[44,2,3],[[5,[4]]]],[[]],[[]],[[]],[[]],[[45,2,3,3],[[5,[4]]]],[[44,2,3,3],[[5,[4]]]],[[45,2],4],[[44,2],4],[[]],[[]],[[45,2],[[5,[[12,[4]]]]]],[[44,2],[[5,[[12,[4]]]]]],[[]],[[]],[37,14],[[45,13],14],[[44,13],14],[[45,2,3,15,13],4],[[44,2,3,15,13],4],[[45,2,3,3],[[5,[4]]]],[[45,2,3,3,3],4],[[44,2,3,3,3],4],[[],16],[[],16],[[],16],[[],16],[[],17],[[],17],[[45,2],4],[[44,2],4],[[45,2,3,3],4],[[44,2,3,3],4],[[45,2],[[5,[4]]]],[[44,2],[[5,[4]]]],[[45,2],9],[[44,2],9],[3,4],0,[3,4],[3,4],[3,4],[[7,2,3,3,3],4],[[7,2,3,3],4],[[7,2],[[5,[4]]]],0,0,[[46,2,3],[[5,[4]]]],[[]],[[]],[[]],[[]],[[46,2],[[5,[4]]]],[[47,2],[[5,[4]]]],[[],[[16,[4,33]]]],[34,[[16,[33]]]],[35,[[16,[36]]]],[[],[[5,[37]]]],[[47,10],11],[[]],[[]],0,[47,48],0,[[]],[[]],[[46,2,3,15,13],4],[[],37],[4,46],[[22,48],47],[[46,2,3,3,3],4],[[47,[0,[34,41]]],42],[[],43],[[],4],[[],16],[[],16],[[],16],[[],16],[[],17],[[],17],[[46,2],4],[[46,2,3,3],4],[[46,2],[[5,[4]]]],[[46,2],9],0,[[]],[[]],[[49,2],[[5,[4]]]],[[],[[16,[4,33]]]],[34,[[16,[33]]]],[35,[[16,[36]]]],[[],[[5,[37]]]],0,[[49,10],11],[[]],[[49,2],[[5,[[12,[4]]]]]],[49,39],0,[[]],[[49,2,6,13,3],4],[[],37],[[22,39],49],[[49,[0,[34,41]]],42],[[],43],[[],4],[[],16],[[],16],[[],17],[3,[[16,[4,33]]]],[[3,37],[[16,[4,33]]]],[[[50,[6]]],[[16,[4,33]]]],[3,[[16,[4,33]]]],[[[50,[6]],15,[50,[13]],3],[[16,[4,33]]]],[[[50,[6]],3,3],[[16,[4,33]]]],[[[50,[6]],3,3,3],[[16,[4,33]]]],[3,[[16,[4,33]]]],[[3,[50,[6]]],[[16,[4,33]]]],[[[50,[6]]],[[16,[4,33]]]],[3,[[16,[4,33]]]],0,[[]],[[]],[[51,2],[[5,[4]]]],[[],[[16,[4,33]]]],[34,[[16,[33]]]],[35,[[16,[36]]]],[[],[[5,[37]]]],[[51,10],11],[[]],[[51,2],[[5,[[12,[4]]]]]],[[51,2],[[5,[4]]]],0,[[]],[[51,2,6,13,3],4],[[],37],[52,51],[[51,[0,[34,41]]],42],[[],43],[[],4],[[],16],[[],16],[[],17],0,0,0,[[53,2,3],[[5,[4]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[53,2],[[5,[4]]]],[[54,2],[[5,[4]]]],[[],[[16,[4,33]]]],[34,[[16,[33]]]],[35,[[16,[36]]]],[[],[[5,[37]]]],0,[[54,10],11],[[]],[[]],[[]],0,[[54,2],[[5,[[12,[4]]]]]],[54,55],0,[[]],[[]],[[]],[[53,2,3,15,13],4],[[54,2,6,13,3],4],[[53,2],[[5,[4]]]],[[],37],[4,53],[[22,55],54],[[22,3,15,2,6,13],4],[[53,2,3,3,3],4],[[54,[0,[34,41]]],42],[[],43],[[],4],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],17],[[],17],[[],17],[[53,2],4],[[53,2,3,3],4],[[53,2],[[5,[4]]]],[[53,2,3],[[5,[4]]]],[[53,2],9],0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[56,2,3,15,13],4],[[57,2,3,15,13],4],[[58,2,3,15,13],4],[[56,2,3,3],[[5,[4]]]],[[57,2,3,3],[[5,[4]]]],[[58,2,3,3],[[5,[4]]]],[[56,2,3,3,3],4],[[57,2,3,3,3],4],[[58,2,3,3,3],4],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],17],[[],17],[[],17],[[56,2],4],[[57,2],4],[[58,2],4],[[56,2,3,3],4],[[57,2,3,3],4],[[58,2,3,3],4],[[56,2],[[5,[4]]]],[[57,2],[[5,[4]]]],[[58,2],[[5,[4]]]],[[56,2],9],[[57,2],9],[[58,2],9],0,0,0,[[59,2,3],[[5,[4]]]],[[60,2,3],[[5,[4]]]],[[61,2,3],[[5,[4]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[59,2],[[5,[4]]]],[[60,2],[[5,[4]]]],[[59,2,3,3],[[5,[4]]]],[[60,2,3,3],[[5,[4]]]],[[61,2,3,3],[[5,[4]]]],[[59,2],4],[[60,2],4],[[61,2],4],[[]],[[]],[[]],[[60,2],[[5,[[12,[4]]]]]],0,0,[[]],[[]],[[]],0,[[59,2,3,15,13],4],[[60,2,3,15,13],4],[[61,2,3,15,13],4],[[[5,[4]],[5,[4]]],[[5,[4]]]],[[60,2],[[5,[4]]]],[6,59],[[6,6],60],[6,61],[[59,2,3,3,3],4],[[60,2,3,3,3],4],[[61,2,3,3,3],4],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],17],[[],17],[[],17],[[59,2],4],[[60,2],4],[[61,2],4],0,[[59,2,3,3],4],[[60,2,3,3],4],[[61,2,3,3],4],[[59,2],[[5,[4]]]],[[60,2],[[5,[4]]]],[[61,2],[[5,[4]]]],[[59,2,3],[[5,[4]]]],[[60,2,3],[[5,[4]]]],[[61,2,3],[[5,[4]]]],[[59,2],9],[[60,2],9],[[61,2],9],0,[[22,30],[[16,[24]]]],0,0,[[]],[[]],[[]],[[]],0,[62,62],[[]],0,[[62,6],[[8,[7]]]],[63,[[12,[4]]]],[[],62],[[],[[16,[4,33]]]],[34,[[16,[33]]]],[35,[[16,[36]]]],0,[[],[[5,[37]]]],0,0,[[62,6],[[8,[7]]]],0,[[63,10],11],[[]],[[]],0,[63,[[12,[4]]]],[63,[[12,[4]]]],[[]],[[]],[63,[[12,[39]]]],[63,[[12,[[8,[40]]]]]],[[],37],[22,63],0,0,0,[[63,[0,[34,41]]],42],[[],43],0,[[]],[[],4],[[],16],[[],16],[[],16],[[],16],[[],17],[[],17],0,0,[[]],[[]],[[]],[[]],[[64,2,3,3],[[5,[4]]]],[[65,2,3,3],[[5,[4]]]],[[64,2],4],[[65,2],4],[[]],[[]],[[65,2],[[5,[[12,[4]]]]]],[[]],[[]],[[64,2,3,15,13],4],[[65,2,3,15,13],4],[[64,2,3,3,3],4],[[65,2,3,3,3],4],[[],16],[[],16],[[],16],[[],16],[[],17],[[],17],[[64,2],4],[[65,2],4],[[64,2,3,3],4],[[65,2,3,3],4],[[64,2],[[5,[4]]]],[[65,2],[[5,[4]]]],[[64,2],9],[[65,2],9],[3,4],0,[3,4],[3,4],[3,4],[[7,2,3,3,3],4],[[7,2,3,3],4],[[7,2],[[5,[4]]]],0,0,[[]],[[]],[[]],[[]],[[66,2],[[5,[4]]]],[[67,2],[[5,[4]]]],[[],[[16,[4,33]]]],[34,[[16,[33]]]],[35,[[16,[36]]]],[[],[[5,[37]]]],[[67,10],11],[[]],[[]],0,[67,48],0,[[]],[[]],[[66,2,3,15,13],4],[[],37],[4,66],[[22,48],67],[[66,2,3,3,3],4],[[67,[0,[34,41]]],42],[[],43],[[],4],[[],16],[[],16],[[],16],[[],16],[[],17],[[],17],[[66,2],4],[[66,2,3,3],4],[[66,2],[[5,[4]]]],[[66,2],9],0,[[]],[[]],[[68,2],[[5,[4]]]],[[],[[16,[4,33]]]],[34,[[16,[33]]]],[35,[[16,[36]]]],[[],[[5,[37]]]],0,[[68,10],11],[[]],[68,39],0,[[]],[[68,2,6,13,3],4],[[],37],[[22,39],68],[[68,[0,[34,41]]],42],[[],43],[[],4],[[],16],[[],16],[[],17],[3,[[16,[4,33]]]],[[3,37],[[16,[4,33]]]],[[[50,[6]]],[[16,[4,33]]]],[3,[[16,[4,33]]]],[[[50,[6]],15,[50,[13]],3],[[16,[4,33]]]],[[[50,[6]],3,3,3],[[16,[4,33]]]],[3,[[16,[4,33]]]],[[[50,[6]]],[[16,[4,33]]]],[3,[[16,[4,33]]]],0,[[]],[[]],[[69,2],[[5,[4]]]],[[],[[16,[4,33]]]],[34,[[16,[33]]]],[35,[[16,[36]]]],[[],[[5,[37]]]],[[69,10],11],[[]],[[69,2],[[5,[[12,[4]]]]]],[[69,2],[[5,[4]]]],0,[[]],[[69,2,6,13,3],4],[[],37],[52,69],[[69,[0,[34,41]]],42],[[],43],[[],4],[[],16],[[],16],[[],17],0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[70,2],[[5,[4]]]],[[71,2],[[5,[4]]]],[[],[[16,[4,33]]]],[34,[[16,[33]]]],[35,[[16,[36]]]],[[],[[5,[37]]]],0,[[71,10],11],[[]],[[]],[[]],0,[71,55],0,[[]],[[]],[[]],[[70,2,3,15,13],4],[[71,2,6,13,3],4],[[70,2],[[5,[4]]]],[[],37],[4,70],[[22,55],71],[[22,15,2,6,13,3],4],[[70,2,3,3,3],4],[[71,[0,[34,41]]],42],[[],43],[[],4],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],17],[[],17],[[],17],[[70,2],4],[[70,2,3,3],4],[[70,2],[[5,[4]]]],[[70,2,3],[[5,[4]]]],[[70,2],9],0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[72,2,3,15,13],4],[[73,2,3,15,13],4],[[74,2,3,15,13],4],[[72,2,3,3,3],4],[[73,2,3,3,3],4],[[74,2,3,3,3],4],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],17],[[],17],[[],17],[[72,2],4],[[73,2],4],[[74,2],4],[[72,2,3,3],4],[[73,2,3,3],4],[[74,2,3,3],4],[[72,2],[[5,[4]]]],[[73,2],[[5,[4]]]],[[74,2],[[5,[4]]]],[[72,2],9],[[73,2],9],[[74,2],9],0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[75,2],[[5,[4]]]],[[76,2],[[5,[4]]]],[[75,2,3,3],[[5,[4]]]],[[76,2,3,3],[[5,[4]]]],[[77,2,3,3],[[5,[4]]]],[[75,2],4],[[76,2],4],[[77,2],4],[[]],[[]],[[]],0,0,[[]],[[]],[[]],0,[[75,2,3,15,13],4],[[76,2,3,15,13],4],[[77,2,3,15,13],4],[[76,2],[[5,[4]]]],[6,75],[[6,6],76],[6,77],[[75,2,3,3,3],4],[[76,2,3,3,3],4],[[77,2,3,3,3],4],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],17],[[],17],[[],17],[[75,2],4],[[76,2],4],[[77,2],4],0,[[75,2,3,3],4],[[76,2,3,3],4],[[77,2,3,3],4],[[75,2],[[5,[4]]]],[[76,2],[[5,[4]]]],[[77,2],[[5,[4]]]],[[75,2,3],[[5,[4]]]],[[76,2,3],[[5,[4]]]],[[77,2,3],[[5,[4]]]],[[75,2],9],[[76,2],9],[[77,2],9],0,0,[[78,79],[[1,[23]]]],[[78,79],[[1,[30]]]],[[78,79],[[1,[80]]]],[[78,79],[[1,[81]]]],[[78,79],[[1,[82]]]],[[78,79],[[1,[83]]]],[[84,79],1],[[37,78,79],[[1,[85]]]],[[84,79],[[1,[86]]]],[78,[[1,[4]]]],[86,1],0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,[30,30],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],17],[[],17],[[],17],[[],17],[[],17],[[],17],0,[[80,37],[[16,[24]]]],[23,[[16,[24]]]],[82,[[16,[24]]]],[30,[[16,[24]]]],[[87,30,79],[[16,[24]]]],[[87,30,37],[[16,[24]]]],[[22,30],[[16,[24]]]],[[87,88,30],[[16,[24]]]],[[87,88,14,[5,[37]]],[[16,[22,24]]]],[[89,37,37],[[16,[24]]]],[[89,37],[[16,[24]]]],[[89,37],[[16,[24]]]],[[89,37,37],[[16,[24]]]],[81,[[16,[24]]]],[83,[[16,[24]]]],0,[[]],[[]],0,[[[91,[90]],[91,[4]],37],[[16,[[92,[4,13]],24]]]],[[93,39],[[16,[13,24]]]],[[93,6,13],[[16,[13,24]]]],[[93,37],[[16,[13,24]]]],[[93,6,13],[[16,[[5,[13]],24]]]],[[]],[[]],[[93,39,[5,[[12,[90]]]]],[[16,[24]]]],[[13,13],13],[[[29,[4,55]],[12,[4]],[5,[4]]],93],0,0,[[],16],[[],16],[[],17],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[[94,[4]]],4],[[24,10],11],[[24,10],11],[[95,10],11],[[95,10],11],[96,24],[97,24],[98,24],[36,24],[99,24],[[]],[100,24],[101,24],[95,24],[33,24],[[]],[[]],[[]],[102],[102],[24,[[5,[103]]]],[[],4],[[],4],[[],16],[[],16],[[],16],[[],16],[[],17],[[],17],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,[104,[[12,[4]]]],0,0,0,0,[105,105],[106,106],[107,107],[108,108],[109,109],[110,110],[111,111],[112,112],[113,113],[114,114],[115,115],[116,116],[117,117],[104,104],[90,90],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,[[],110],[[],111],[[],112],[[],113],[[],114],[[],116],[[],104],0,0,0,0,0,0,0,0,[111,111],[20,[[16,[105]]]],[20,[[16,[106]]]],[20,[[16,[107]]]],[20,[[16,[108]]]],[20,[[16,[109]]]],[20,[[16,[110]]]],[20,[[16,[111]]]],[20,[[16,[112]]]],[20,[[16,[113]]]],[20,[[16,[114]]]],[20,[[16,[115]]]],[20,[[16,[116]]]],[20,[[16,[117]]]],[20,[[16,[104]]]],[20,[[16,[90]]]],0,0,[[111,111],14],[[112,112],14],[[113,113],14],[[116,116],14],[[117,117],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],0,0,0,0,0,[[105,10],11],[[106,10],11],[[107,10],11],[[108,10],11],[[109,10],11],[[110,10],11],[[111,10],11],[[112,10],11],[[113,10],11],[[114,10],11],[[115,10],11],[[116,10],11],[[117,10],11],[[104,10],11],[[90,10],11],[118,105],[[]],[48,106],[[]],[38,107],[[]],[[]],[38,108],[[]],[55,109],[[]],[[]],[[]],[[]],[[]],[[]],[39,115],[[]],[[]],[[]],[22,104],[13,90],[[]],[104,[[29,[4,48]]]],[[104,93],[[16,[[29,[4,39]],24]]]],[[104,119,[5,[37]]],[[16,[22,24]]]],[104,[[29,[4,55]]]],[[104,37,107],38],[[104,37,108],38],[104,[[92,[4,6]]]],0,[104,[[12,[4]]]],0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[111,14],0,0,[90,[[5,[[12,[4]]]]]],0,0,0,0,[111,4],[111,4],[111,4],[111,4],[111,[[5,[4]]]],0,0,0,0,[111,4],[[105,25],16],[[106,25],16],[[107,25],16],[[108,25],16],[[109,25],16],[[110,25],16],[[111,25],16],[[112,25],16],[[113,25],16],[[114,25],16],[[115,25],16],[[116,25],16],[[117,25],16],[[104,25],16],[[90,25],16],[[111,120],14],0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],17],[[],17],[[],17],[[],17],[[],17],[[],17],[[],17],[[],17],[[],17],[[],17],[[],17],[[],17],[[],17],[[],17],[[],17],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[52,111],0,0,[[],[[94,[6]]]],[39,14],0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,[120,120],[6,6],[119,119],[22,22],[39,39],[48,48],[121,121],[118,118],[55,55],[38,38],[52,52],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[119,119],122],[[22,39],[[16,[18,24]]]],[[],119],[[],22],[[],39],[[],48],[[],118],[[],55],[38,13],0,[22,13],[39,13],[20,[[16,[6]]]],[20,[[16,[119]]]],[20,[[16,[22]]]],[20,[[16,[39]]]],[20,[[16,[48]]]],[20,[[16,[121]]]],[20,[[16,[118]]]],[20,[[16,[55]]]],[20,[[16,[38]]]],[39,4],[48,4],[118,4],[55,4],[38,4],0,0,0,0,0,0,[[120,120],14],[[6,6],14],[[119,119],14],[[22,22],14],[[39,39],14],[[48,48],14],[[121,121],14],[[118,118],14],[[55,55],14],[[38,38],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[120,37],0,[52,[[12,[39]]]],0,[[22,37],[[5,[48]]]],[[22,37],5],[[22,119],[[5,[22]]]],[[22,37],[[5,[55]]]],[[55,37],38],[[[94,[6]]]],[[6,[94,[6]]]],[[22,[94,[6]]]],[[39,[94,[6]]]],[[48,[94,[6]]]],[[55,[94,[6]]]],[[38,[94,[6]]]],0,[[120,10],11],[[6,10],11],[[6,10],11],[[119,10],11],[[119,10],11],[[22,10],11],[[39,10],11],[[48,10],11],[[121,10],11],[[118,10],11],[[55,10],11],[[38,10],11],[[52,10],11],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[37,[[1,[120]]]],[22,[[12,[4]]]],[[22,37],[[5,[39]]]],[[[0,[123,41]],124],125],[[[0,[123,41]],124],125],[[[0,[123,41]],124],125],[39,14],[38,14],[[120,126]],[[6,126]],[[119,126]],0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[22,127],[22,127],[22,127],[22,127],[22,127],[22,[[12,[52]]]],[22,127],0,[39,4],[48,4],[118,4],[55,4],[38,4],0,0,0,0,0,0,[[119,[5,[37]],[29,[4,39]],[29,[4,48]],[29,[4,55]],111],22],[[37,37,[12,[38]],14],39],[[37,37],118],[[37,37,[12,[38]]],55],[[22,[128,[4]]],52],0,[[119,119],[[5,[122]]]],[38,[[5,[4]]]],0,[39,[[12,[38]]]],[55,[[12,[38]]]],0,0,[[22,[91,[38]]],[[16,[[29,[4,19]],24]]]],[[6,25],16],[[119,25],16],[[22,25],16],[[39,25],16],[[48,25],16],[[121,25],16],[[118,25],16],[[55,25],16],[[38,25],16],[6,14],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],4],[[],4],[37,[[1,[120]]]],[[],16],[4,[[1,[120]]]],[79,[[1,[120]]]],[129,[[1,[120]]]],[[],16],[88,[[16,[119,24]]]],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[[],16],[38,6],0,[[],17],[[],17],[[],17],[[],17],[[],17],[[],17],[[],17],[[],17],[[],17],[[],17],[[],17],[[22,37,[12,[4]],6,13],[[16,[24]]]],[22,[[16,[24]]]],[[22,[94,[4]]],[[16,[24]]]],[[22,37,13],[[16,[39,24]]]],[[22,39],[[16,[24]]]],[[22,[94,[4]]],[[16,[24]]]],[[22,39],[[16,[24]]]],[22,[[16,[24]]]],[[22,120],[[16,[24]]]],[[22,[94,[4]]],[[16,[24]]]],[[22,37,38],[[16,[24]]]],[[22,39,[94,[4]]],[[16,[24]]]],[[37,6,[94,[4]],[94,[4]]],[[16,[24]]]],[48,[[12,[118]]]],0,0,0,[[]],[[]],[[130,88,[12,[114]]],[[16,[24]]]],[[22,22,37],[[16,[24]]]],[[22,22],[[16,[24]]]],[[130,88,[12,[114]],[92,[4,4]]],[[16,[24]]]],[[130,88,104,88,104],[[16,[24]]]],0,[[130,10],11],[[]],[[130,[5,[37]]],[[16,[22,24]]]],[[4,[5,[[92,[4,6]]]]],[[16,[6,24]]]],[[]],[[87,37],[[16,[104,24]]]],[[130,88,[5,[37]],[92,[119,22]]],[[16,[119,24]]]],[[130,88,[94,[119]]],[[16,[104,24]]]],[[114,114],[[16,[114,24]]]],[[130,[91,[114]],[91,[114]]],[[16,[[12,[114]],24]]]],[[130,88,104,88,104],[[16,[104,24]]]],[[[29,[4,27]],[29,[4,27]],37,37,88],[[16,[[29,[4,27]],24]]]],[[87,88],[[16,[130,24]]]],[4,[[16,[24]]]],0,[[],16],[[],16],[[],17],[[],4],[[],4],[[],4],[[4,37],4],0,[[],4],[[],4],0,0,0,0,0,0,0,[[87,37,37],[[16,[24]]]],[[87,88],[[16,[24]]]],[[87,88,37,37],[[16,[24]]]],[[]],[[]],[[]],[[]],[[]],[[]],[87,79],0,0,[88,[[16,[88,24]]]],[80,80],[88,88],[87,87],[[]],[[]],[[]],0,[[87,131],85],0,0,[[],80],[[87,4],88],[87],[[87,131],[[16,[4,24]]]],0,[[87,37],[[16,[88,24]]]],[[88,10],11],[[88,10],11],[[87,10],11],[[]],[[]],[79,88],[[]],[[]],[[]],[[]],[79,14],[[88,37],[[16,[88,24]]]],[[87,88,37],[[16,[88,24]]]],[[87,37,37],[[5,[88]]]],[[79,37],[[16,[88,24]]]],[[85,[5,[85]],[29,[4,88]]],[[16,[87,24]]]],[[87,88],[[16,[4,24]]]],0,[[87,37,37],[[16,[88,24]]]],[37,5],0,[[87,37],[[16,[[5,[88]],24]]]],[87,79],[[]],[[]],[[]],[[],4],[[],16],[[],16],[[],16],[80,[[16,[87]]]],[[],16],[[],16],[[],16],[[],17],[[],17],[[],17]],"c":[],"p":[[6,"Result"],[8,"CodeOracle"],[8,"Display"],[3,"String"],[4,"Option"],[4,"TypeRef"],[8,"CodeType"],[3,"Box"],[4,"VariablesType"],[3,"Formatter"],[6,"Result"],[3,"Vec"],[4,"Value"],[15,"bool"],[8,"LiteralRenderer"],[4,"Result"],[3,"TypeId"],[3,"ExperimenterFeature"],[3,"ExperimenterFeatureProperty"],[8,"Deserializer"],[4,"ExperimentManifestPropType"],[3,"FeatureManifest"],[3,"GenerateExperimenterManifestCmd"],[4,"FMLError"],[8,"Serializer"],[8,"Fn"],[8,"Clone"],[8,"From"],[3,"BTreeMap"],[3,"GenerateStructCmd"],[3,"ConcreteCodeOracle"],[3,"FeatureManifestDeclaration"],[4,"Error"],[8,"Write"],[8,"Write"],[3,"Error"],[15,"str"],[3,"PropDef"],[3,"FeatureDef"],[8,"CodeDeclaration"],[8,"Sized"],[6,"Result"],[15,"usize"],[3,"ImageCodeType"],[3,"TextCodeType"],[3,"EnumCodeType"],[3,"EnumCodeDeclaration"],[3,"EnumDef"],[3,"FeatureCodeDeclaration"],[8,"Borrow"],[3,"ImportedModuleInitialization"],[3,"ImportedModule"],[3,"ObjectCodeType"],[3,"ObjectCodeDeclaration"],[3,"ObjectDef"],[3,"BooleanCodeType"],[3,"IntCodeType"],[3,"StringCodeType"],[3,"OptionalCodeType"],[3,"MapCodeType"],[3,"ListCodeType"],[3,"ConcreteCodeOracle"],[3,"FeatureManifestDeclaration"],[3,"TextCodeType"],[3,"ImageCodeType"],[3,"EnumCodeType"],[3,"EnumCodeDeclaration"],[3,"FeatureCodeDeclaration"],[3,"ImportedModuleInitialization"],[3,"ObjectCodeType"],[3,"ObjectCodeDeclaration"],[3,"BooleanCodeType"],[3,"IntCodeType"],[3,"StringCodeType"],[3,"OptionalCodeType"],[3,"MapCodeType"],[3,"ListCodeType"],[3,"ArgMatches"],[3,"Path"],[3,"LoaderConfig"],[3,"PrintChannelsCmd"],[3,"GenerateSingleFileManifestCmd"],[3,"ValidateCmd"],[8,"IntoIterator"],[3,"PathBuf"],[4,"CliCmd"],[3,"FileLoader"],[4,"FilePath"],[3,"Term"],[3,"DefaultBlock"],[15,"slice"],[3,"HashMap"],[3,"DefaultsMerger"],[3,"HashSet"],[4,"ClientError"],[4,"ParseError"],[3,"Error"],[4,"Error"],[3,"Error"],[3,"Error"],[3,"Error"],[3,"Demand"],[8,"Error"],[3,"ManifestFrontEnd"],[3,"EnumVariantBody"],[3,"EnumBody"],[3,"FeatureFieldBody"],[3,"FieldBody"],[3,"ObjectBody"],[3,"Types"],[3,"AboutBlock"],[3,"SwiftAboutBlock"],[3,"KotlinAboutBlock"],[3,"ImportBlock"],[3,"FeatureBody"],[3,"FeatureMetadata"],[3,"DocumentationLink"],[3,"VariantDef"],[4,"ModuleId"],[4,"TargetLanguage"],[3,"FromStringDef"],[4,"Ordering"],[8,"Hash"],[8,"BuildHasher"],[15,"u64"],[8,"Hasher"],[8,"Iterator"],[3,"BTreeSet"],[3,"OsStr"],[3,"Parser"],[3,"Url"],[3,"ObjectRuntime"],[3,"ObjectRuntime"],[13,"FeatureValidationError"],[8,"TypeFinder"]]},\ +"nss":{"doc":"","t":"NNNNNDENNNNNGNALLLLLAAAFLLLLLLLLLLLLLLAAALLOALLLLLLLLLNNEFFLLLLFLLLLLLLFEDNNDDLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLFLLLLLLLLLLLLLLLLLLLLLLLLLLLLFCFAAAAENNLLLLFLLFFLLLLLFFDDDDDDDDLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLFF","n":["Base64Decode","CertificateIssuerError","CertificateSubjectError","CertificateValidityError","ConversionError","Error","ErrorKind","InputError","InternalError","NSSError","NSSInitFailure","PKIXError","Result","SSLError","aes","backtrace","borrow","borrow","borrow_mut","borrow_mut","cert","ec","ecdh","ensure_initialized","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","into","into","kind","pbkdf2","pk11","pkixc","provide","provide","scoped_ptr","secport","source","to_string","to_string","try_from","try_from","try_into","try_into","type_id","type_id","Decrypt","Encrypt","Operation","aes_cbc_crypt","aes_gcm_crypt","borrow","borrow_mut","clone","clone_into","common_crypt","fmt","from","into","to_owned","try_from","try_into","type_id","extract_ec_public_key","Curve","EcKey","P256","P384","PrivateKey","PublicKey","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone_into","clone_into","convert_to_public_key","curve","curve","curve","deref","deref","deserialize","deserialize","eq","equivalent","export","fmt","fmt","from","from","from","from","from_bytes","from_coordinates","generate_keypair","get_field_len","import","into","into","into","into","new","private_key","private_value","public_key","serialize","serialize","to_bytes","to_owned","to_owned","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","verify","ecdh_agreement","HashAlgorithm","pbkdf2_key_derive","context","slot","sym_key","types","HashAlgorithm","SHA256","SHA384","borrow","borrow_mut","clone","clone_into","create_context_by_sym_key","fmt","from","hash_buf","hmac_sign","into","to_owned","try_from","try_into","type_id","generate_random","hkdf_expand","AlgorithmID","Certificate","Context","GenericObject","PrivateKey","PublicKey","Slot","SymKey","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","convert_to_public_key","drop","drop","drop","drop","drop","drop","drop","drop","from","from","from","from","from","from","from","from","into","into","into","into","into","into","into","into","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","verify_code_signing_certificate_chain","secure_memcmp"],"q":[[0,"nss"],[54,"nss::aes"],[71,"nss::cert"],[72,"nss::ec"],[138,"nss::ecdh"],[139,"nss::pbkdf2"],[141,"nss::pk11"],[145,"nss::pk11::context"],[162,"nss::pk11::slot"],[163,"nss::pk11::sym_key"],[164,"nss::pk11::types"],[237,"nss::pkixc"],[238,"nss::secport"]],"d":["","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","","","Returns the argument unchanged.","","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","","","ECDSA verify operation","","","","","","","","","","","","","","","Safe wrapper around PK11_CreateContextBySymKey that …","","Returns the argument unchanged.","","","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","","","","","","",""],"i":[5,5,5,5,5,0,0,5,5,5,5,5,0,5,0,1,5,1,5,1,0,0,0,0,5,5,1,1,5,5,5,1,1,1,1,5,1,1,0,0,0,5,1,0,0,5,5,1,5,1,5,1,5,1,17,17,0,0,0,17,17,17,17,0,17,17,17,17,17,17,17,0,0,0,23,23,0,0,25,26,23,24,25,26,23,24,23,24,23,24,25,25,26,24,25,26,23,24,23,23,25,23,24,25,26,23,24,26,24,0,23,25,25,26,23,24,24,24,25,24,23,24,26,23,24,25,26,23,24,25,26,23,24,25,26,23,24,26,0,0,0,0,0,0,0,0,33,33,33,33,33,33,0,33,33,0,0,33,33,33,33,33,0,0,0,0,0,0,0,0,0,0,35,27,28,37,38,36,39,40,35,27,28,37,38,36,39,40,27,35,27,28,37,38,36,39,40,35,27,28,37,38,36,39,40,35,27,28,37,38,36,39,40,35,27,28,37,38,36,39,40,35,27,28,37,38,36,39,40,35,27,28,37,38,36,39,40,0,0],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[1,[[4,[[3,[2]]]]]],[[]],[[]],[[]],[[]],0,0,0,[[]],[[5,6],7],[[5,6],7],[[1,6],7],[[1,6],7],[8,5],[9,5],[[]],[8,1],[9,1],[[]],[5,1],[[]],[[]],[1,5],0,0,0,[10],[10],0,0,[5,[[4,[11]]]],[[],12],[[],12],[[],13],[[],13],[[],13],[[],13],[[],14],[[],14],0,0,0,[[[16,[15]],[16,[15]],[16,[15]],17],[[19,[[18,[15]]]]]],[[[16,[15]],[16,[15]],[16,[15]],[16,[15]],17],[[19,[[18,[15]]]]]],[[]],[[]],[17,17],[[]],[[20,[16,[15]],[16,[15]],21,22,17],[[19,[[18,[15]]]]]],[[17,6],7],[[]],[[]],[[]],[[],13],[[],13],[[],14],[[[16,[15]]],[[19,[[18,[15]]]]]],0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[23,23],[24,24],[[]],[[]],[25,[[19,[26]]]],[25,23],[26,23],[24,23],[25,27],[26,28],[29,[[13,[23]]]],[29,[[13,[24]]]],[[23,23],30],[[],30],[25,[[19,[24]]]],[[23,6],7],[[24,6],7],[[]],[[]],[[]],[[]],[[23,[16,[15]]],[[19,[26]]]],[[23,[16,[15]],[16,[15]],[16,[15]]],[[19,[24]]]],[23,19],[23,31],[24,[[19,[25]]]],[[]],[[]],[[]],[[]],[[23,[16,[15]],[16,[15]]],24],[24,[[16,[15]]]],[25,[[19,[[18,[15]]]]]],[24,[[16,[15]]]],[[23,32],13],[[24,32],13],[26,[[19,[[18,[15]]]]]],[[]],[[]],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],14],[[],14],[[],14],[[],14],[[26,[16,[15]],[16,[15]],33],19],[[25,26],[[19,[[18,[15]]]]]],0,[[[16,[15]],[16,[15]],31,33,[16,[15]]],19],0,0,0,0,0,0,0,[[]],[[]],[33,33],[[]],[[20,34,35],[[19,[36]]]],[[33,6],7],[[]],[[33,[16,[15]]],[[19,[[18,[15]]]]]],[[33,[16,[15]],[16,[15]]],[[19,[[18,[15]]]]]],[[]],[[]],[[],13],[[],13],[[],14],[[[16,[15]]],19],[[33,[16,[15]],[16,[15]],21],[[19,[[18,[15]]]]]],0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[27,[[19,[28]]]],[35],[27],[28],[37],[38],[36],[39],[40],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[[18,[[16,[15]]]],41,[16,[15]],42],19],[[[16,[15]],[16,[15]]],30]],"c":[],"p":[[3,"Error"],[3,"Backtrace"],[3,"Mutex"],[4,"Option"],[4,"ErrorKind"],[3,"Formatter"],[6,"Result"],[4,"DecodeError"],[3,"TryFromIntError"],[3,"Demand"],[8,"Error"],[3,"String"],[4,"Result"],[3,"TypeId"],[15,"u8"],[15,"slice"],[4,"Operation"],[3,"Vec"],[6,"Result"],[6,"CK_MECHANISM_TYPE"],[15,"usize"],[6,"SECItem"],[4,"Curve"],[3,"EcKey"],[3,"PrivateKey"],[3,"PublicKey"],[3,"PrivateKey"],[3,"PublicKey"],[8,"Deserializer"],[15,"bool"],[15,"u32"],[8,"Serializer"],[4,"HashAlgorithm"],[6,"CK_ATTRIBUTE_TYPE"],[3,"SymKey"],[3,"Context"],[3,"GenericObject"],[3,"Certificate"],[3,"Slot"],[3,"AlgorithmID"],[15,"u64"],[15,"str"]]},\ +"nss_build_common":{"doc":"This shouldn’t exist, but does because if something isn…","t":"NEDNLLLLLLLLFFFLLLLLLLLLLFLLLLLLLLM","n":["Dynamic","LinkingKind","NoNssDir","Static","borrow","borrow","borrow_mut","borrow_mut","clone","clone","clone_into","clone_into","env","env_flag","env_str","eq","eq","equivalent","equivalent","fmt","fmt","from","from","into","into","link_nss","to_owned","to_owned","try_from","try_from","try_into","try_into","type_id","type_id","folded_libs"],"q":[[0,"nss_build_common"],[34,"nss_build_common::LinkingKind"]],"d":["","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","","","","","","","","","",""],"i":[1,0,0,1,1,2,1,2,1,2,1,2,0,0,0,1,2,1,2,1,2,1,2,1,2,0,1,2,1,2,1,2,1,2,12],"f":[0,0,0,0,[[]],[[]],[[]],[[]],[1,1],[2,2],[[]],[[]],[3,[[5,[4]]]],[3,6],[3,[[5,[7]]]],[[1,1],6],[[2,2],6],[[],6],[[],6],[[1,8],9],[[2,8],9],[[]],[[]],[[]],[[]],[[],[[10,[2]]]],[[]],[[]],[[],10],[[],10],[[],10],[[],10],[[],11],[[],11],0],"c":[],"p":[[4,"LinkingKind"],[3,"NoNssDir"],[15,"str"],[3,"OsString"],[4,"Option"],[15,"bool"],[3,"String"],[3,"Formatter"],[6,"Result"],[4,"Result"],[3,"TypeId"],[13,"Dynamic"]]},\ +"nss_sys":{"doc":"","t":"RGGFFFFRRRRRRRRRRRRRRRRRRRRRRRRRRDGGGGRGDRGGDGGRGGMMMMENNNRRNMMMMERGGRRRRRFFFGGEEGGFFFFFFFFFFFFFFFFFFFFFFNNNNNFFFNNNNNFDDFGGGGGGRFFFRGDNNFGDEGDGDGGDGDGDGDGDGDGDTGDFFFFFFGDEENNENNRNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNRRNNFEMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMMMMNMNMNMLLMLMNLLLLLLLLLLLLLLLLLLLLLLLLLLLMLLLLLLLLLLLLLLLLLLLLLLLLLLLMNMMMMMMMMMNMMMMMMMMMMMMMMMMMMMMMMMMNNNNNNNNNNNNNNNNNNNMGMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMMLLLLLLLLLLLLLLLLLLLLLLLLLLLMMMMMMMMM","n":["AES_BLOCK_SIZE","CERTCertDBHandle","CERTCertificate","CERT_DestroyCertificate","CERT_ExtractPublicKey","CERT_GetDefaultCertDB","CERT_NewTempCertificate","CKA_CLASS","CKA_EC_PARAMS","CKA_EC_POINT","CKA_ENCRYPT","CKA_ID","CKA_KEY_TYPE","CKA_PRIVATE","CKA_SENSITIVE","CKA_SIGN","CKA_TOKEN","CKA_VALUE","CKA_WRAP","CKD_NULL","CKK_EC","CKM_AES_CBC_PAD","CKM_AES_GCM","CKM_ECDH1_DERIVE","CKM_EC_KEY_PAIR_GEN","CKM_NSS","CKM_NSS_HKDF_SHA256","CKM_NSS_HKDF_SHA384","CKM_SHA256_HMAC","CKM_SHA384_HMAC","CKM_SHA512_HMAC","CKM_VENDOR_DEFINED","CKO_PRIVATE_KEY","CK_ATTRIBUTE","CK_ATTRIBUTE_TYPE","CK_BBOOL","CK_BYTE","CK_BYTE_PTR","CK_FALSE","CK_GCM_PARAMS","CK_GCM_PARAMS_V3","CK_INVALID_HANDLE","CK_KEY_TYPE","CK_MECHANISM_TYPE","CK_NSS_HKDFParams","CK_OBJECT_CLASS","CK_OBJECT_HANDLE","CK_TRUE","CK_ULONG","CK_VOID_PTR","DEREncodedParams","DSSKey","DSSpriviledge","DSSversion","ECPointEncoding","ECPoint_Uncompressed","ECPoint_Undefined","ECPoint_XOnly","EC_POINT_FORM_UNCOMPRESSED","HASH_LENGTH_MAX","INVALID_CERT_EXTENSION","KEAKey","KEApriviledge","KEAversion","KMID","KeyType","NSSCK_VENDOR_NSS","NSSInitContext","NSSInitParameters","NSS_INIT_FORCEOPEN","NSS_INIT_NOCERTDB","NSS_INIT_NOMODDB","NSS_INIT_OPTIMIZESPACE","NSS_INIT_READONLY","NSS_InitContext","NSS_SecureMemcmp","NSS_VersionCheck","PK11Context","PK11GenericObject","PK11ObjectType","PK11Origin","PK11SlotInfo","PK11SymKey","PK11_CreateContextBySymKey","PK11_CreateGenericObject","PK11_CreatePBEV2AlgorithmID","PK11_Decrypt","PK11_Derive","PK11_DestroyContext","PK11_DestroyGenericObject","PK11_DigestBegin","PK11_DigestFinal","PK11_DigestOp","PK11_Encrypt","PK11_ExtractKeyValue","PK11_FindKeyByKeyID","PK11_FreeSlot","PK11_FreeSymKey","PK11_GenerateKeyPair","PK11_GenerateRandom","PK11_GetInternalSlot","PK11_GetKeyData","PK11_HashBuf","PK11_ImportSymKey","PK11_MapSignKeyType","PK11_OriginDerive","PK11_OriginFortezzaHack","PK11_OriginGenerated","PK11_OriginNULL","PK11_OriginUnwrap","PK11_PBEKeyGen","PK11_PubDeriveWithKDF","PK11_ReadRawAttribute","PK11_TypeCert","PK11_TypeGeneric","PK11_TypePrivKey","PK11_TypePubKey","PK11_TypeSymKey","PK11_VerifyWithMechanism","PLArena","PLArenaPool","PORT_FreeArena","PRBool","PRErrorCode","PRInt32","PRIntn","PRUint32","PRUword","PR_FALSE","PR_GetError","PR_GetErrorText","PR_GetErrorTextLength","PR_TRUE","SECAlgorithmID","SECAlgorithmIDStr","SECFailure","SECFailure","SECITEM_FreeItem","SECItem","SECItemStr","SECItemType","SECKEYDHPublicKey","SECKEYDHPublicKeyStr","SECKEYDSAPublicKey","SECKEYDSAPublicKeyStr","SECKEYECParams","SECKEYECPublicKey","SECKEYECPublicKeyStr","SECKEYFortezzaPublicKey","SECKEYFortezzaPublicKeyStr","SECKEYKEAParams","SECKEYKEAParamsStr","SECKEYKEAPublicKey","SECKEYKEAPublicKeyStr","SECKEYPQGParams","SECKEYPQGParamsStr","SECKEYPrivateKey","SECKEYPrivateKeyStr","SECKEYPublicKey","SECKEYPublicKeyStr","SECKEYPublicKeyStr_u","SECKEYRSAPublicKey","SECKEYRSAPublicKeyStr","SECKEY_ConvertToPublicKey","SECKEY_CopyPublicKey","SECKEY_DestroyPrivateKey","SECKEY_DestroyPublicKey","SECOID_DestroyAlgorithmID","SECOID_FindOIDByTag","SECOidData","SECOidDataStr","SECOidTag","SECStatus","SECSuccess","SECSuccess","SECSupportExtenTag","SECWouldBlock","SECWouldBlock","SEC_ASN1_OBJECT_ID","SEC_OID_AES_128_CBC","SEC_OID_AES_128_ECB","SEC_OID_AES_128_GCM","SEC_OID_AES_128_KEY_WRAP","SEC_OID_AES_192_CBC","SEC_OID_AES_192_ECB","SEC_OID_AES_192_GCM","SEC_OID_AES_192_KEY_WRAP","SEC_OID_AES_256_CBC","SEC_OID_AES_256_ECB","SEC_OID_AES_256_GCM","SEC_OID_AES_256_KEY_WRAP","SEC_OID_ANSIX962_ECDSA_SHA1_SIGNATURE","SEC_OID_ANSIX962_ECDSA_SHA224_SIGNATURE","SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE","SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE","SEC_OID_ANSIX962_ECDSA_SHA512_SIGNATURE","SEC_OID_ANSIX962_ECDSA_SIGNATURE_RECOMMENDED_DIGEST","SEC_OID_ANSIX962_ECDSA_SIGNATURE_SPECIFIED_DIGEST","SEC_OID_ANSIX962_EC_C2ONB191V4","SEC_OID_ANSIX962_EC_C2ONB191V5","SEC_OID_ANSIX962_EC_C2ONB239V4","SEC_OID_ANSIX962_EC_C2ONB239V5","SEC_OID_ANSIX962_EC_C2PNB163V1","SEC_OID_ANSIX962_EC_C2PNB163V2","SEC_OID_ANSIX962_EC_C2PNB163V3","SEC_OID_ANSIX962_EC_C2PNB176V1","SEC_OID_ANSIX962_EC_C2PNB208W1","SEC_OID_ANSIX962_EC_C2PNB272W1","SEC_OID_ANSIX962_EC_C2PNB304W1","SEC_OID_ANSIX962_EC_C2PNB368W1","SEC_OID_ANSIX962_EC_C2TNB191V1","SEC_OID_ANSIX962_EC_C2TNB191V2","SEC_OID_ANSIX962_EC_C2TNB191V3","SEC_OID_ANSIX962_EC_C2TNB239V1","SEC_OID_ANSIX962_EC_C2TNB239V2","SEC_OID_ANSIX962_EC_C2TNB239V3","SEC_OID_ANSIX962_EC_C2TNB359V1","SEC_OID_ANSIX962_EC_C2TNB431R1","SEC_OID_ANSIX962_EC_PRIME192V1","SEC_OID_ANSIX962_EC_PRIME192V2","SEC_OID_ANSIX962_EC_PRIME192V3","SEC_OID_ANSIX962_EC_PRIME239V1","SEC_OID_ANSIX962_EC_PRIME239V2","SEC_OID_ANSIX962_EC_PRIME239V3","SEC_OID_ANSIX962_EC_PUBLIC_KEY","SEC_OID_ANSIX9_DSA_SIGNATURE","SEC_OID_ANSIX9_DSA_SIGNATURE_WITH_SHA1_DIGEST","SEC_OID_APPLY_SSL_POLICY","SEC_OID_AVA_COMMON_NAME","SEC_OID_AVA_COUNTRY_NAME","SEC_OID_AVA_DC","SEC_OID_AVA_DN_QUALIFIER","SEC_OID_AVA_GENERATION_QUALIFIER","SEC_OID_AVA_GIVEN_NAME","SEC_OID_AVA_HOUSE_IDENTIFIER","SEC_OID_AVA_INITIALS","SEC_OID_AVA_LOCALITY","SEC_OID_AVA_NAME","SEC_OID_AVA_ORGANIZATIONAL_UNIT_NAME","SEC_OID_AVA_ORGANIZATION_NAME","SEC_OID_AVA_POSTAL_ADDRESS","SEC_OID_AVA_POSTAL_CODE","SEC_OID_AVA_POST_OFFICE_BOX","SEC_OID_AVA_PSEUDONYM","SEC_OID_AVA_SERIAL_NUMBER","SEC_OID_AVA_STATE_OR_PROVINCE","SEC_OID_AVA_STREET_ADDRESS","SEC_OID_AVA_SURNAME","SEC_OID_AVA_TITLE","SEC_OID_BOGUS_DSA_SIGNATURE_WITH_SHA1_DIGEST","SEC_OID_BOGUS_KEY_USAGE","SEC_OID_BUSINESS_CATEGORY","SEC_OID_CAMELLIA_128_CBC","SEC_OID_CAMELLIA_192_CBC","SEC_OID_CAMELLIA_256_CBC","SEC_OID_CERT_RENEWAL_LOCATOR","SEC_OID_CHACHA20_POLY1305","SEC_OID_CMS_3DES_KEY_WRAP","SEC_OID_CMS_EPHEMERAL_STATIC_DIFFIE_HELLMAN","SEC_OID_CMS_RC2_KEY_WRAP","SEC_OID_CURVE25519","SEC_OID_DES_40_CBC","SEC_OID_DES_CBC","SEC_OID_DES_CFB","SEC_OID_DES_ECB","SEC_OID_DES_EDE","SEC_OID_DES_EDE3_CBC","SEC_OID_DES_MAC","SEC_OID_DES_OFB","SEC_OID_EV_INCORPORATION_COUNTRY","SEC_OID_EV_INCORPORATION_LOCALITY","SEC_OID_EV_INCORPORATION_STATE","SEC_OID_EXT_KEY_USAGE_CLIENT_AUTH","SEC_OID_EXT_KEY_USAGE_CODE_SIGN","SEC_OID_EXT_KEY_USAGE_EMAIL_PROTECT","SEC_OID_EXT_KEY_USAGE_IPSEC_END","SEC_OID_EXT_KEY_USAGE_IPSEC_IKE","SEC_OID_EXT_KEY_USAGE_IPSEC_TUNNEL","SEC_OID_EXT_KEY_USAGE_IPSEC_USER","SEC_OID_EXT_KEY_USAGE_SERVER_AUTH","SEC_OID_EXT_KEY_USAGE_TIME_STAMP","SEC_OID_FORTEZZA_SKIPJACK","SEC_OID_HMAC_MD5","SEC_OID_HMAC_SHA1","SEC_OID_HMAC_SHA224","SEC_OID_HMAC_SHA256","SEC_OID_HMAC_SHA384","SEC_OID_HMAC_SHA512","SEC_OID_IDEA_CBC","SEC_OID_IPSEC_IKE_END","SEC_OID_IPSEC_IKE_INTERMEDIATE","SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE","SEC_OID_ISO_SHA_WITH_RSA_SIGNATURE","SEC_OID_MD2","SEC_OID_MD4","SEC_OID_MD5","SEC_OID_MISSI_ALT_KEA","SEC_OID_MISSI_DSS","SEC_OID_MISSI_DSS_OLD","SEC_OID_MISSI_KEA","SEC_OID_MISSI_KEA_DSS","SEC_OID_MISSI_KEA_DSS_OLD","SEC_OID_MS_EXT_KEY_USAGE_CTL_SIGNING","SEC_OID_MS_SMIME_ENCRYPTION_KEY_PREFERENCE","SEC_OID_NETSCAPE_AOLSCREENNAME","SEC_OID_NETSCAPE_NICKNAME","SEC_OID_NETSCAPE_RECOVERY_REQUEST","SEC_OID_NETSCAPE_SMIME_KEA","SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA224_DIGEST","SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA256_DIGEST","SEC_OID_NS_CERT_EXT_BASE_URL","SEC_OID_NS_CERT_EXT_CA_CERT_URL","SEC_OID_NS_CERT_EXT_CA_CRL_URL","SEC_OID_NS_CERT_EXT_CA_POLICY_URL","SEC_OID_NS_CERT_EXT_CA_REVOCATION_URL","SEC_OID_NS_CERT_EXT_CERT_RENEWAL_TIME","SEC_OID_NS_CERT_EXT_CERT_RENEWAL_URL","SEC_OID_NS_CERT_EXT_CERT_TYPE","SEC_OID_NS_CERT_EXT_COMMENT","SEC_OID_NS_CERT_EXT_ENTITY_LOGO","SEC_OID_NS_CERT_EXT_HOMEPAGE_URL","SEC_OID_NS_CERT_EXT_ISSUER_LOGO","SEC_OID_NS_CERT_EXT_LOST_PASSWORD_URL","SEC_OID_NS_CERT_EXT_NETSCAPE_OK","SEC_OID_NS_CERT_EXT_REVOCATION_URL","SEC_OID_NS_CERT_EXT_SCOPE_OF_USE","SEC_OID_NS_CERT_EXT_SSL_SERVER_NAME","SEC_OID_NS_CERT_EXT_SUBJECT_LOGO","SEC_OID_NS_CERT_EXT_USER_PICTURE","SEC_OID_NS_KEY_USAGE_GOVT_APPROVED","SEC_OID_NS_TYPE_CERT_SEQUENCE","SEC_OID_NS_TYPE_GIF","SEC_OID_NS_TYPE_HTML","SEC_OID_NS_TYPE_JPEG","SEC_OID_NS_TYPE_URL","SEC_OID_NULL_CIPHER","SEC_OID_OCSP_RESPONDER","SEC_OID_PKCS12","SEC_OID_PKCS12_BAG_IDS","SEC_OID_PKCS12_CERT_AND_CRL_BAG_ID","SEC_OID_PKCS12_CERT_BAG_IDS","SEC_OID_PKCS12_ENVELOPING_IDS","SEC_OID_PKCS12_ESPVK_IDS","SEC_OID_PKCS12_KEY_BAG_ID","SEC_OID_PKCS12_MODE_IDS","SEC_OID_PKCS12_OIDS","SEC_OID_PKCS12_PBE_IDS","SEC_OID_PKCS12_PBE_WITH_SHA1_AND_128_BIT_RC2_CBC","SEC_OID_PKCS12_PBE_WITH_SHA1_AND_128_BIT_RC4","SEC_OID_PKCS12_PBE_WITH_SHA1_AND_40_BIT_RC2_CBC","SEC_OID_PKCS12_PBE_WITH_SHA1_AND_40_BIT_RC4","SEC_OID_PKCS12_PBE_WITH_SHA1_AND_TRIPLE_DES_CBC","SEC_OID_PKCS12_PKCS8_KEY_SHROUDING","SEC_OID_PKCS12_PKCS8_SHROUDED_KEY_BAG_ID","SEC_OID_PKCS12_RSA_ENCRYPTION_WITH_128_BIT_RC4","SEC_OID_PKCS12_RSA_ENCRYPTION_WITH_40_BIT_RC4","SEC_OID_PKCS12_RSA_ENCRYPTION_WITH_TRIPLE_DES","SEC_OID_PKCS12_RSA_SIGNATURE_WITH_SHA1_DIGEST","SEC_OID_PKCS12_SAFE_CONTENTS_ID","SEC_OID_PKCS12_SDSI_CERT_BAG","SEC_OID_PKCS12_SECRET_BAG_ID","SEC_OID_PKCS12_SIGNATURE_IDS","SEC_OID_PKCS12_V1_CERT_BAG_ID","SEC_OID_PKCS12_V1_CRL_BAG_ID","SEC_OID_PKCS12_V1_KEY_BAG_ID","SEC_OID_PKCS12_V1_PKCS8_SHROUDED_KEY_BAG_ID","SEC_OID_PKCS12_V1_SAFE_CONTENTS_BAG_ID","SEC_OID_PKCS12_V1_SECRET_BAG_ID","SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_128_BIT_RC2_CBC","SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_128_BIT_RC4","SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_2KEY_TRIPLE_DES_CBC","SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_3KEY_TRIPLE_DES_CBC","SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_40_BIT_RC2_CBC","SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_40_BIT_RC4","SEC_OID_PKCS12_X509_CERT_CRL_BAG","SEC_OID_PKCS1_MD2_WITH_RSA_ENCRYPTION","SEC_OID_PKCS1_MD4_WITH_RSA_ENCRYPTION","SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION","SEC_OID_PKCS1_MGF1","SEC_OID_PKCS1_PSPECIFIED","SEC_OID_PKCS1_RSA_ENCRYPTION","SEC_OID_PKCS1_RSA_OAEP_ENCRYPTION","SEC_OID_PKCS1_RSA_PSS_SIGNATURE","SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION","SEC_OID_PKCS1_SHA224_WITH_RSA_ENCRYPTION","SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION","SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION","SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION","SEC_OID_PKCS5_PBES2","SEC_OID_PKCS5_PBE_WITH_MD2_AND_DES_CBC","SEC_OID_PKCS5_PBE_WITH_MD5_AND_DES_CBC","SEC_OID_PKCS5_PBE_WITH_SHA1_AND_DES_CBC","SEC_OID_PKCS5_PBKDF2","SEC_OID_PKCS5_PBMAC1","SEC_OID_PKCS7","SEC_OID_PKCS7_DATA","SEC_OID_PKCS7_DIGESTED_DATA","SEC_OID_PKCS7_ENCRYPTED_DATA","SEC_OID_PKCS7_ENVELOPED_DATA","SEC_OID_PKCS7_SIGNED_DATA","SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA","SEC_OID_PKCS9_CHALLENGE_PASSWORD","SEC_OID_PKCS9_CONTENT_TYPE","SEC_OID_PKCS9_COUNTER_SIGNATURE","SEC_OID_PKCS9_EMAIL_ADDRESS","SEC_OID_PKCS9_EXTENDED_CERTIFICATE_ATTRIBUTES","SEC_OID_PKCS9_EXTENSION_REQUEST","SEC_OID_PKCS9_FRIENDLY_NAME","SEC_OID_PKCS9_LOCAL_KEY_ID","SEC_OID_PKCS9_MESSAGE_DIGEST","SEC_OID_PKCS9_SDSI_CERT","SEC_OID_PKCS9_SIGNING_TIME","SEC_OID_PKCS9_SMIME_CAPABILITIES","SEC_OID_PKCS9_UNSTRUCTURED_ADDRESS","SEC_OID_PKCS9_UNSTRUCTURED_NAME","SEC_OID_PKCS9_X509_CERT","SEC_OID_PKCS9_X509_CRL","SEC_OID_PKIX_CA_ISSUERS","SEC_OID_PKIX_CA_REPOSITORY","SEC_OID_PKIX_CPS_POINTER_QUALIFIER","SEC_OID_PKIX_OCSP","SEC_OID_PKIX_OCSP_ARCHIVE_CUTOFF","SEC_OID_PKIX_OCSP_BASIC_RESPONSE","SEC_OID_PKIX_OCSP_CRL","SEC_OID_PKIX_OCSP_NONCE","SEC_OID_PKIX_OCSP_NO_CHECK","SEC_OID_PKIX_OCSP_RESPONSE","SEC_OID_PKIX_OCSP_SERVICE_LOCATOR","SEC_OID_PKIX_REGCTRL_AUTHENTICATOR","SEC_OID_PKIX_REGCTRL_OLD_CERT_ID","SEC_OID_PKIX_REGCTRL_PKIPUBINFO","SEC_OID_PKIX_REGCTRL_PKI_ARCH_OPTIONS","SEC_OID_PKIX_REGCTRL_PROTOCOL_ENC_KEY","SEC_OID_PKIX_REGCTRL_REGTOKEN","SEC_OID_PKIX_REGINFO_CERT_REQUEST","SEC_OID_PKIX_REGINFO_UTF8_PAIRS","SEC_OID_PKIX_TIMESTAMPING","SEC_OID_PKIX_USER_NOTICE_QUALIFIER","SEC_OID_RC2_40_CBC","SEC_OID_RC2_CBC","SEC_OID_RC4","SEC_OID_RC4_40","SEC_OID_RC4_56","SEC_OID_RC5_CBC_PAD","SEC_OID_RFC1274_MAIL","SEC_OID_RFC1274_UID","SEC_OID_SDN702_DSA_SIGNATURE","SEC_OID_SECG_EC_SECP112R1","SEC_OID_SECG_EC_SECP112R2","SEC_OID_SECG_EC_SECP128R1","SEC_OID_SECG_EC_SECP128R2","SEC_OID_SECG_EC_SECP160K1","SEC_OID_SECG_EC_SECP160R1","SEC_OID_SECG_EC_SECP160R2","SEC_OID_SECG_EC_SECP192K1","SEC_OID_SECG_EC_SECP224K1","SEC_OID_SECG_EC_SECP224R1","SEC_OID_SECG_EC_SECP256K1","SEC_OID_SECG_EC_SECP256R1","SEC_OID_SECG_EC_SECP384R1","SEC_OID_SECG_EC_SECP521R1","SEC_OID_SECG_EC_SECT113R1","SEC_OID_SECG_EC_SECT113R2","SEC_OID_SECG_EC_SECT131R1","SEC_OID_SECG_EC_SECT131R2","SEC_OID_SECG_EC_SECT163K1","SEC_OID_SECG_EC_SECT163R1","SEC_OID_SECG_EC_SECT163R2","SEC_OID_SECG_EC_SECT193R1","SEC_OID_SECG_EC_SECT193R2","SEC_OID_SECG_EC_SECT233K1","SEC_OID_SECG_EC_SECT233R1","SEC_OID_SECG_EC_SECT239K1","SEC_OID_SECG_EC_SECT283K1","SEC_OID_SECG_EC_SECT283R1","SEC_OID_SECG_EC_SECT409K1","SEC_OID_SECG_EC_SECT409R1","SEC_OID_SECG_EC_SECT571K1","SEC_OID_SECG_EC_SECT571R1","SEC_OID_SEED_CBC","SEC_OID_SHA1","SEC_OID_SHA224","SEC_OID_SHA256","SEC_OID_SHA384","SEC_OID_SHA512","SEC_OID_SMIME_ENCRYPTION_KEY_PREFERENCE","SEC_OID_TLS13_KEA_ANY","SEC_OID_TLS_DHE_CUSTOM","SEC_OID_TLS_DHE_DSS","SEC_OID_TLS_DHE_DSS_EXPORT","SEC_OID_TLS_DHE_PSK","SEC_OID_TLS_DHE_RSA","SEC_OID_TLS_DHE_RSA_EXPORT","SEC_OID_TLS_DH_ANON","SEC_OID_TLS_DH_ANON_EXPORT","SEC_OID_TLS_DH_DSS","SEC_OID_TLS_DH_DSS_EXPORT","SEC_OID_TLS_DH_RSA","SEC_OID_TLS_DH_RSA_EXPORT","SEC_OID_TLS_ECDHE_ECDSA","SEC_OID_TLS_ECDHE_PSK","SEC_OID_TLS_ECDHE_RSA","SEC_OID_TLS_ECDH_ANON","SEC_OID_TLS_ECDH_ECDSA","SEC_OID_TLS_ECDH_RSA","SEC_OID_TLS_FFDHE_2048","SEC_OID_TLS_FFDHE_3072","SEC_OID_TLS_FFDHE_4096","SEC_OID_TLS_FFDHE_6144","SEC_OID_TLS_FFDHE_8192","SEC_OID_TLS_RSA","SEC_OID_TLS_RSA_EXPORT","SEC_OID_TOTAL","SEC_OID_UNKNOWN","SEC_OID_VERISIGN_USER_NOTICES","SEC_OID_X500_RSA_ENCRYPTION","SEC_OID_X509_ANY_EXT_KEY_USAGE","SEC_OID_X509_ANY_POLICY","SEC_OID_X509_AUTH_INFO_ACCESS","SEC_OID_X509_AUTH_KEY_ID","SEC_OID_X509_BASIC_CONSTRAINTS","SEC_OID_X509_CERTIFICATE_POLICIES","SEC_OID_X509_CERT_ISSUER","SEC_OID_X509_CRL_DIST_POINTS","SEC_OID_X509_CRL_NUMBER","SEC_OID_X509_DELTA_CRL_INDICATOR","SEC_OID_X509_EXT_KEY_USAGE","SEC_OID_X509_FRESHEST_CRL","SEC_OID_X509_HOLD_INSTRUCTION_CODE","SEC_OID_X509_INHIBIT_ANY_POLICY","SEC_OID_X509_INVALID_DATE","SEC_OID_X509_ISSUER_ALT_NAME","SEC_OID_X509_ISSUING_DISTRIBUTION_POINT","SEC_OID_X509_KEY_USAGE","SEC_OID_X509_NAME_CONSTRAINTS","SEC_OID_X509_POLICY_CONSTRAINTS","SEC_OID_X509_POLICY_MAPPINGS","SEC_OID_X509_PRIVATE_KEY_USAGE_PERIOD","SEC_OID_X509_REASON_CODE","SEC_OID_X509_SUBJECT_ALT_NAME","SEC_OID_X509_SUBJECT_DIRECTORY_ATTR","SEC_OID_X509_SUBJECT_INFO_ACCESS","SEC_OID_X509_SUBJECT_KEY_ID","SEC_OID_X942_DIFFIE_HELMAN_KEY","SHA256_LENGTH","SHA384_LENGTH","SUPPORTED_CERT_EXTENSION","UNSUPPORTED_CERT_EXTENSION","VerifyCodeSigningCertificateChain","_SECStatus","algorithm","arena","arena","arena","arena","arena","arena","arenasize","avail","bExpand","bExtract","base","base","base","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clearance","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","current","data","desc","dh","dhKey","dsa","dsaKey","ec","ecKey","encoding","eq","equivalent","first","fmt","fortezza","fortezzaKey","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","hash","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","into","kea","keaKey","keaParams","keyType","keyType","len","limit","mask","mechanism","modulus","next","nullKey","offset","oid","pAAD","pInfo","pIv","pSalt","pValue","parameters","params","params","params","pkcs11ID","pkcs11ID","pkcs11IsTemp","pkcs11Slot","pkcs11Slot","prime","prime","publicExponent","publicValue","publicValue","publicValue","publicValue","rsa","rsaKey","rsaOaepKey","rsaPssKey","siAsciiNameString","siAsciiString","siBMPString","siBuffer","siCipherDataBuffer","siClearDataBuffer","siDERCertBuffer","siDERNameBuffer","siDEROID","siEncodedCertBuffer","siEncodedNameBuffer","siGeneralizedTime","siUTCTime","siUTF8String","siUnsignedInteger","siVisibleString","size","size_t","staticflags","subPrime","supportedExtension","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_","type_","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","u","ulAADLen","ulInfoLen","ulIvBits","ulIvLen","ulSaltLen","ulTagBits","ulValueLen","wincx"],"q":[[0,"nss_sys"]],"d":["","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"i":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,7,7,7,0,9,9,9,0,0,22,7,7,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23,23,23,23,23,0,0,0,24,24,24,24,24,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,16,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,16,16,0,16,16,0,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,0,0,22,22,0,0,14,26,27,1,3,4,6,12,11,28,28,3,4,11,26,29,27,30,31,28,32,23,24,22,25,1,2,3,4,5,6,7,8,9,10,11,12,13,16,14,15,26,29,27,30,31,28,32,23,24,22,25,1,2,3,4,5,6,7,8,9,10,11,12,13,16,14,15,7,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,12,13,15,29,30,29,30,29,30,8,16,16,12,13,29,30,26,29,27,30,31,28,32,23,24,22,25,1,2,3,4,5,6,7,8,9,10,11,12,13,16,14,15,6,26,29,27,30,31,28,32,23,24,22,25,1,2,3,4,5,6,7,8,9,10,11,12,13,16,14,15,29,30,7,26,27,13,11,12,15,1,11,30,15,15,31,28,31,28,10,14,2,5,7,26,27,27,26,27,3,4,1,2,4,5,8,29,30,30,30,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,8,0,27,3,15,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,26,29,27,30,31,28,32,23,24,22,25,1,2,3,4,5,6,7,8,9,10,11,12,13,16,14,15,26,29,27,30,31,28,32,23,24,22,25,1,2,3,4,5,6,7,8,9,10,11,12,13,16,14,15,10,13,26,29,27,30,31,28,32,23,24,22,25,1,2,3,4,5,6,7,8,9,10,11,12,13,16,14,15,26,31,28,31,31,28,31,10,27],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,[1,1],[2,2],[3,3],[4,4],[5,5],[6,6],[7,7],[8,8],[9,9],[10,10],[11,11],[12,12],[13,13],[14,14],[15,15],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,0,0,0,0,0,0,0,0,[[16,16],17],[[],17],0,[[13,18],19],0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],[[],20],0,0,[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],[[],21],0,0,0,0,0,0,0,0,0],"c":[],"p":[[3,"SECKEYRSAPublicKeyStr"],[3,"SECKEYDSAPublicKeyStr"],[3,"SECKEYPQGParamsStr"],[3,"SECKEYDHPublicKeyStr"],[3,"SECKEYKEAPublicKeyStr"],[3,"SECKEYKEAParamsStr"],[3,"SECKEYFortezzaPublicKeyStr"],[3,"SECKEYECPublicKeyStr"],[4,"ECPointEncoding"],[3,"CK_ATTRIBUTE"],[3,"PLArena"],[3,"PLArenaPool"],[3,"SECItemStr"],[3,"SECAlgorithmIDStr"],[3,"SECOidDataStr"],[4,"_SECStatus"],[15,"bool"],[3,"Formatter"],[6,"Result"],[4,"Result"],[3,"TypeId"],[4,"SECSupportExtenTag"],[4,"PK11Origin"],[4,"PK11ObjectType"],[4,"SECOidTag"],[3,"SECKEYPublicKeyStr"],[3,"SECKEYPrivateKeyStr"],[3,"CK_NSS_HKDFParams"],[19,"SECKEYPublicKeyStr_u"],[4,"KeyType"],[3,"CK_GCM_PARAMS_V3"],[4,"SECItemType"]]},\ +"places":{"doc":"","t":"CCCCCACAAAAACAAAAAAAFAAADDNENLLLLLLFLLMLLLLLLLLFLLLMLMMLMLLLLLLLLLMFMLLLCCDDFLLLLLLLLLLLLMLLLLLLMLLMFFMMLFFMLLLLLLLLMLLERDNNNDLLLLLLLLLLLMLLLLLLLLFLLLLLMLLLLFLLLLLLLLLLLLLLLLLLLLLNCNNNNNNEENLLLLLLLLLLALLLLLLLLLLLLLLLLLLLALLLLLLLLLLLLDRRRRLLLLLLLLLLLLLLLLLLNEDDNDNDNDNDLLLLLLLLLLLLLLLLMLLLLLLLLLLLLLLMMMMMLLLLLLLLLLLLLLLLLLLLLMLLLLLLLLLLLLLLLLLLLLLLMMMMMLLLLLLLLLMMMMMMMMMMMMLMMMMMLLLLLLLMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMMMMMMMLLLLLLLCCDCLLLLALLLLLLLLLLDDRDDDLLLLLLLLLLLLLLLLLLMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLGNNENENNNNNNNNENNENNNNNNNNNNNNNNNENGNNNNNNNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMMMMMMCCGGCGCCDSEDCCCCCDDCGGGNCDDCCDNDDDLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMLLLLLLLLLLLLLLLLMMLLLLLLMLLLLLLLLLMLLLLLLLLLLLCLLLMLLLLLLLLLLLLLLLLLLLLLLLLLMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMMMLLLMLLLLLLLLLLLRDMLLFLLLMMMMLLMMLMMMLLLMMMMMMMMMMLLLLMMMLNNELLLLLLLLFFFLLLLLLRCDLLLLLLLALLLLLLLLLLLALLLLLLLRRDRLLLLMLLLLLLLLLLLLLLDDLLLLLLLLMLLLLLLLLLLLMMLLLLMLLMLLLLLLMMMLLACADDDFLLLLLLLLLFLLLLLLLLLLLLLMMMFLALMLLLLLLLLLLLLFFFFACFNNDSNNNNSSESSSDSSSSLLLLLLLLLMLLLLLLLLLLLLLLLLLLLLLLLFLLLLLLLLLLLLLLLLLLLLLLLLMLMLLMMLLLLMMLLLLLLLLLLLLLLMLMMLLLDMLLLLLLLLLMMMMLMMMLLLLMMLLLLLLLLLLDDDDRRRALLLLLLLLLLLLMMLFLLLFLLLLLMLLLLLLLMLMAALLLLMMMMLMMMFFFFLMMAMLLLLLLLLLLLLLLLMMFMMMLLLLNNNEEDNNDDEDNNNNNNNNNNRNDDEDELLAFLLLLLLLLLLLLLLLLLLLLLLLLMLLLLLLLLLLLLLLLLLLLLLLLLFFMMMLLLFFLLLLLLLALLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMMMMLFLLLLLLLLLLLLLAMMMLMMMFMMMMLMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLFFMMMLLLLLLLLLLLLLMMMMMMMMMMFNDDNEDDDNLLLLLLLLLLLLMMLLLLLLLLLMMMLLLFFFFLLLLLLLLLLLLLLMMMLLLLLLLMMMLMMMLMMMFFMMLLLLLLLLLLLLLLLLLLLLLLMLLLLLLMMMNDENENDNDNLLLLLLLLLLMLMMMLLLFLLLLLLLLLLLLLMMMFLLLLLMMMLLMMLLLLLLLLLLLLLLLMLLLLLMMMFFFFFFFFFFFFFFFFFAFFFFDDFFFLLLLLLLLFFFLLLLLLMLLMMMLLLLLLLMMMMLLEDDDDNNFLLLLLLLLLLLLLLLLLLLLLMFFFFMMLLLLLLLLLMLLLLLFFFFLLLLLMMMFMMMMMMMMLLLLLLMLLLLLLLLLLLLLLLMMMMMMLLLLLNNNELLLLLLLLLFFLLFFFLLLLFFLNNENNNNDNNNNNNNENNGNDELLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL","n":["ConnectionType","PageInfo","PlacesApi","PlacesDb","RowId","api","apply_observation","bookmark_sync","db","error","ffi","frecency","get_registered_sync_engine","hash","history_sync","import","match_impl","observation","storage","types","apply_observation","history","matcher","places_api","AddablePlaceInfo","AddableVisit","Permanent","RedirectSourceType","Temporary","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","can_add_url","clone","clone_into","date","eq","equivalent","fmt","fmt","fmt","from","from","from","insert","into","into","into","is_local","partial_cmp","referrer","title","to_owned","transition","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","url","visit_uri","visits","vzip","vzip","vzip","MatchBehavior","SearchBehavior","SearchParams","SearchResult","accept_result","borrow","borrow","borrow_mut","borrow_mut","clone","clone","clone_into","clone_into","eq","equivalent","fmt","fmt","frecency","from","from","from_adaptive_row","from_origin_row","from_suggestion_row","from_url_row","icon_url","into","into","limit","match_url","search_frecent","search_string","search_string","serialize","split_after_host_and_port","split_after_prefix","title","to_owned","to_owned","try_from","try_from","try_into","try_into","type_id","type_id","url","vzip","vzip","ConnectionType","GLOBAL_STATE_META_KEY","PlacesApi","ReadOnly","ReadWrite","Sync","SyncState","bookmarks_reset","bookmarks_sync","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","clone","clone_into","close_connection","disk_cached_state","do_sync_one","eq","equivalent","fmt","from","from","from","from_primitive","get_registered_sync_engine","get_sync_connection","history_sync","into","into","into","mem_cached_state","new","new_connection","new_memory","open_connection","places_api_new","register_with_sync_manager","reset_bookmarks","reset_history","rusqlite_flags","sync","sync_bookmarks","sync_history","to_owned","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","vzip","vzip","vzip","wipe_bookmarks","Bookmark","BookmarksSyncEngine","Folder","Livemark","Query","Replace","Reupload","Separator","SyncedBookmarkKind","SyncedBookmarkValidity","Valid","borrow","borrow","borrow_mut","borrow_mut","clone","clone","clone_into","clone_into","cmp","cmp","engine","eq","eq","equivalent","equivalent","fmt","fmt","from","from","from","from_u8","from_u8","get_hash","get_hash","hash","hash","into","into","partial_cmp","partial_cmp","record","to_owned","to_owned","to_sql","to_sql","try_from","try_from","try_into","try_into","type_id","type_id","vzip","vzip","BookmarksSyncEngine","COLLECTION_NAME","COLLECTION_SYNCID_META_KEY","GLOBAL_SYNCID_META_KEY","LAST_SYNC_META_KEY","apply","borrow","borrow_mut","collection_name","from","get_collection_request","get_sync_assoc","into","new","reset","set_uploaded","stage_incoming","sync_finished","try_from","try_into","type_id","vzip","wipe","Bookmark","BookmarkItemRecord","BookmarkRecord","BookmarkRecordId","Folder","FolderRecord","Livemark","LivemarkRecord","Query","QueryRecord","Separator","SeparatorRecord","as_guid","as_payload_id","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","children","clone","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","date_added","date_added","date_added","date_added","date_added","deserialize","deserialize","deserialize","deserialize","deserialize","deserialize","deserialize","eq","eq","eq","eq","eq","eq","eq","equivalent","equivalent","equivalent","equivalent","equivalent","equivalent","equivalent","feed_url","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from","from","from_payload_id","get_hash","has_dupe","has_dupe","has_dupe","has_dupe","has_dupe","hash","into","into","into","into","into","into","into","into_payload_id","keyword","parent_record_id","parent_record_id","parent_record_id","parent_record_id","parent_record_id","parent_title","parent_title","parent_title","parent_title","parent_title","position","record_id","record_id","record_id","record_id","record_id","record_id","serialize","serialize","serialize","serialize","serialize","serialize","serialize","site_url","tag_folder_name","tags","title","title","title","title","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","unknown_fields","unknown_fields","unknown_fields","unknown_fields","unknown_fields","unknown_fields","url","url","vzip","vzip","vzip","vzip","vzip","vzip","vzip","GlobalChangeCounterTracker","PlacesDb","PlacesTransaction","SharedPlacesDb","borrow","borrow_mut","commit","conn","db","deref","from","into","maybe_commit","rollback","should_commit","try_from","try_into","type_id","vzip","GLOBAL_BOOKMARK_CHANGE_COUNTERS","GlobalChangeCounterTracker","MAX_VARIABLE_NUMBER","PlacesDb","PlacesInitializer","SharedPlacesDb","api_id","as_ref","begin_interrupt_scope","begin_interrupt_scope","begin_transaction","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","changed","conn","conn_type","db","deref","deref","deref","drop","finish","fmt","from","from","from","from","from","global_bookmark_change_tracker","init","into","into","into","into","into","new","new","new_interrupt_handle","open","prepare","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","upgrade_from","vzip","vzip","vzip","vzip","vzip","ApiResult","CannotUpdateRoot","ConnectionAlreadyOpen","Corruption","Corruption","Error","IllegalChange","IllegalDatabasePath","InterruptedError","InvalidBookmarkOperation","InvalidChildGuid","InvalidConnectionType","InvalidGuid","InvalidLocalRoots","InvalidMetadataObservation","InvalidMetadataObservation","InvalidParent","InvalidPlaceInfo","InvalidPlaceInfo","InvalidSyncedRoots","InvalidTag","IoError","JsonError","MergeError","MismatchedBookmarkType","MissingBookmarkKind","NoParent","NoSuchGuid","NoSuchUrl","NoUrl","NonRootWithoutParent","OpenDatabaseError","OperationInterrupted","PlacesApiError","PlacesConnectionBusy","Result","SqlError","SyncAdapterError","UnexpectedPlacesException","UnknownBookmarkItem","UnsupportedDatabaseVersion","UnsupportedSyncedBookmarkKind","UnsupportedSyncedBookmarkValidity","UrlParseError","UrlParseFailed","UrlTooLong","Utf8Error","ViewTimeTooLong","WrongApiForClose","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","get_error_handling","into","into","into","into","into","provide","provide","provide","provide","provide","source","to_string","to_string","to_string","to_string","to_string","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","vzip","vzip","vzip","vzip","vzip","reason","reason","reason","reason","reason","reason","ApiResult","BookmarkData","BookmarkFolder","BookmarkItem","BookmarkPosition","BookmarkSeparator","BookmarkUpdateInfo","DocumentType","Dummy","EARLIEST","FrecencyThresholdOption","Guid","HistoryHighlight","HistoryHighlightWeights","HistoryMetadata","HistoryMetadataObservation","HistoryMigrationResult","HistoryVisitInfo","HistoryVisitInfosWithBound","InsertableBookmark","InsertableBookmarkFolder","InsertableBookmarkItem","InsertableBookmarkSeparator","None","PlacesApiError","PlacesConnection","PlacesTimestamp","Result","RunMaintenanceMetrics","SearchResult","SkipOneTimePages","SqlInterruptHandle","TopFrecentSiteInfo","Url","accept_result","apply_observation","as_bytes","as_millis","as_millis_i64","as_ref","as_ref","as_ref","as_ref","as_str","as_str","begin_interrupt_scope","bookmarks_count_bookmarks_in_trees","bookmarks_delete","bookmarks_delete_everything","bookmarks_get_all_with_url","bookmarks_get_by_guid","bookmarks_get_recent","bookmarks_get_tree","bookmarks_get_url_for_keyword","bookmarks_insert","bookmarks_search","bookmarks_update","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","bound","cannot_be_a_base","checked_add","checked_sub","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","cmp","cmp","cmp","column_result","column_result","default","default","delete_everything_history","delete_visit","delete_visits_between","delete_visits_for","deref","deserialize","deserialize","deserialize","deserialize_internal","domain","duration_since","empty","encode_hex","encode_hex_upper","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","equivalent","equivalent","equivalent","equivalent","equivalent","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fragment","frecency","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from_directory_path","from_file_path","from_slice","from_str","from_string","from_vec","get_hash","get_hash","get_hash","get_history_highlights","get_history_metadata_between","get_history_metadata_since","get_latest_history_metadata_for_url","get_top_frecent_site_infos","get_visit_count","get_visit_infos","get_visit_page","get_visit_page_with_bound","get_visited","get_visited_urls_in_range","has_authority","has_host","hash","hash","hash","host","host_str","index","index","index","index","infos","interrupt","into","into","into","into","into","into","into","into","into","into","into","into_resettable","into_resettable","into_string","into_string","is_hidden","is_remote","is_valid_for_places","is_valid_for_sync_server","is_valid_places_byte","join","make_relative","match_url","md","measure_with","metadata_delete","metadata_delete_older_than","new","new","new","new_interrupt_handle","note_history_metadata_observation","now","offset","options","origin","parse","parse_with_params","partial_cmp","partial_cmp","partial_cmp","password","path","path_segments","path_segments_mut","places_api_new","places_history_import_from_ios","port","port_or_known_default","preview_image_url","query","query_autocomplete","query_history_metadata","query_pairs","query_pairs_mut","random","run_maintenance_checkpoint","run_maintenance_optimize","run_maintenance_prune","run_maintenance_vacuum","scheme","serialize","serialize","serialize","serialize_internal","set_fragment","set_host","set_ip_host","set_password","set_path","set_port","set_query","set_scheme","set_username","socket_addrs","timestamp","title","title","title","to_file_path","to_owned","to_owned","to_owned","to_owned","to_owned","to_sql","to_sql","to_string","to_string","to_string","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","url","url","url","username","utf16char_indices","utf8char_indices","visit_type","vzip","vzip","vzip","vzip","vzip","vzip","vzip","vzip","vzip","vzip","vzip","DEFAULT_FRECENCY_SETTINGS","FrecencySettings","bookmark_visit_bonus","borrow","borrow_mut","calculate_frecency","clone","clone_into","default","default_bucket_weight","default_visit_bonus","download_visit_bonus","embed_visit_bonus","eq","equivalent","first_bucket_cutoff_days","first_bucket_weight","fmt","fourth_bucket_cutoff_days","fourth_bucket_weight","framed_link_visit_bonus","from","get_transition_bonus","into","link_visit_bonus","num_visits","permanent_redirect_visit_bonus","redirect_source_visit_bonus","reload_visit_bonus","second_bucket_cutoff_days","second_bucket_weight","temporary_redirect_visit_bonus","third_bucket_cutoff_days","third_bucket_weight","to_owned","try_from","try_into","type_id","typed_visit_bonus","unvisited_bookmark_bonus","unvisited_typed_bonus","vzip","Hi","Lo","PrefixMode","borrow","borrow_mut","clone","clone_into","eq","equivalent","fmt","from","hash_string","hash_url","hash_url_prefix","into","to_owned","try_from","try_into","type_id","vzip","HISTORY_TTL","HistorySyncEngine","ServerVisitTimestamp","borrow","borrow_mut","clone","clone_into","cmp","default","deserialize","engine","eq","equivalent","fmt","fmt","from","from","from","get_hash","hash","into","partial_cmp","record","serialize","to_owned","to_string","try_from","try_into","type_id","vzip","COLLECTION_SYNCID_META_KEY","GLOBAL_SYNCID_META_KEY","HistorySyncEngine","LAST_SYNC_META_KEY","apply","borrow","borrow_mut","collection_name","db","from","get_collection_request","get_sync_assoc","into","new","reset","set_uploaded","stage_incoming","sync_finished","try_from","try_into","type_id","vzip","wipe","HistoryRecord","HistoryRecordVisit","borrow","borrow","borrow_mut","borrow_mut","clone","clone","clone_into","clone_into","date","default","deserialize","deserialize","eq","eq","equivalent","equivalent","fmt","fmt","from","from","hist_uri","id","into","into","serialize","serialize","title","to_owned","to_owned","transition","try_from","try_from","try_into","try_into","type_id","type_id","unknown_fields","unknown_fields","visits","vzip","vzip","common","import_ios_history","ios","ExecuteOnDrop","HistoryMigrationResult","NOW","attached_database","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","clone","clone_into","default","define_history_migration_functions","deref","drop","eq","equivalent","execute_now","fmt","from","from","from","into","into","into","new","num_failed","num_succeeded","num_total","select_count","serialize","sql_fns","to_owned","total_duration","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","vzip","vzip","vzip","sanitize_float_timestamp","sanitize_integer_timestamp","sanitize_utf8","validate_url","history","import_history","import","Anywhere","AnywhereUnmodified","AutocompleteMatch","BOOKMARK","Beginning","BeginningCaseSensitive","Boundary","BoundaryAnywhere","HISTORY","JAVASCRIPT","MatchBehavior","OPENPAGE","RESTRICT","SEARCHES","SearchBehavior","TAG","TITLE","TYPED","URL","all","any","bitand","bitand_assign","bitor","bitor_assign","bits","bitxor","bitxor_assign","bookmarked","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone_into","clone_into","cmp","column_result","column_result","complement","contains","default","difference","empty","eq","eq","equivalent","equivalent","extend","find_in_string","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from_bits","from_bits_truncate","from_bits_unchecked","from_iter","get_hash","hash","insert","intersection","intersects","into","into","into","invoke","is_all","is_empty","match_behavior","not","open_page_count","partial_cmp","remove","search_behavior","search_str","set","sub","sub_assign","symmetric_difference","tags","title_str","to_owned","to_owned","to_sql","to_sql","toggle","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","typed","union","url_str","visit_count","vzip","vzip","vzip","VisitObservation","at","borrow","borrow_mut","clone","clone_into","fmt","from","get_is_hidden","get_redirect_frecency_boost","into","is_error","is_permanent_redirect_source","is_redirect_source","is_remote","new","preview_image_url","referrer","title","to_owned","try_from","try_into","type_id","url","visit_type","vzip","with_at","with_is_error","with_is_permanent_redirect_source","with_is_redirect_source","with_is_remote","with_preview_image_url","with_referrer","with_title","with_visit_type","FetchedPageInfo","PageInfo","RowId","RunMaintenanceMetrics","TAG_LENGTH_MAX","TITLE_LENGTH_MAX","URL_LENGTH_MAX","bookmarks","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone_into","cmp","column_result","db_size_after","db_size_before","default","delete_pending_temp_tables","deserialize","eq","equivalent","fetch_page_info","fmt","fmt","fmt","fmt","fmt","frecency","from","from","from","from","from_row","from_row","get_hash","guid","hash","hidden","history","history_metadata","into","into","into","into","last_visit_date_local","last_visit_date_remote","last_visit_id","page","partial_cmp","preview_image_url","pruned_visits","row_id","run_maintenance_checkpoint","run_maintenance_optimize","run_maintenance_prune","run_maintenance_vacuum","serialize","sync_change_counter","sync_status","tags","title","to_owned","to_sql","to_string","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","typed","unknown_fields","update_all_frecencies_at_once","url","visit_count_local","visit_count_remote","vzip","vzip","vzip","vzip","Append","Bookmark","Bookmark","BookmarkPosition","BookmarkRootGuid","BookmarkUpdateInfo","Folder","Folder","InsertableBookmark","InsertableFolder","InsertableItem","InsertableSeparator","Menu","Mobile","None","Parent","Position","Root","Separator","Separator","Specific","Toolbar","USER_CONTENT_ROOTS","Unfiled","UpdatableBookmark","UpdatableFolder","UpdatableItem","UpdatableSeparator","UpdateTreeLocation","as_guid","as_str","bookmark_sync","bookmarks_get_url_for_keyword","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","children","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","count_bookmarks_in_trees","create_bookmark_roots","date_added","date_added","date_added","default","default","default","delete_bookmark","delete_everything","eq","eq","eq","eq","eq","equivalent","equivalent","fetch","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from_guid","get_hash","guid","guid","guid","guid","guid","hash","insert_bookmark","into","into","into","into","into","into","into","into","into","into","into","into","into_updatable","json_tree","last_modified","last_modified","last_modified","location","location","location","location","maybe_truncate_title","parent_guid","parent_guid","parent_guid","parent_guid","partial_cmp","position","position","position","position","title","title","title","title","title","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","update_bookmark","update_bookmark_from_info","url","url","url","vzip","vzip","vzip","vzip","vzip","vzip","vzip","vzip","vzip","vzip","vzip","vzip","well_known","pos","b","f","s","b","f","s","guid","pos","pos","create_synced_bookmark_roots","Bookmark","BookmarkData","Folder","Folder","Item","RECENT_BOOKMARKS_QUERY","SEARCH_QUERY","Separator","Separator","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","child_guids","child_nodes","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","date_added","date_added","date_added","date_added","default","deref","deref","fetch_bookmark","fetch_bookmarks_by_url","fetch_tree","fetch_tree_with_depth","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","guid","guid","guid","guid","into","into","into","into","into","into","last_modified","last_modified","last_modified","last_modified","parent_guid","parent_guid","parent_guid","parent_guid","position","position","position","position","recent_bookmarks","search_bookmarks","title","title","to_owned","to_owned","to_owned","to_owned","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","url","vzip","vzip","vzip","vzip","vzip","vzip","b","f","s","Bookmark","BookmarkNode","BookmarkTreeNode","Deepest","FetchDepth","Folder","FolderNode","Separator","SeparatorNode","Specific","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","children","created_modified","date_added","date_added","date_added","default","default","deserialize","fetch_tree","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","guid","guid","guid","guid","insert_tree","into","into","into","into","into","last_modified","last_modified","last_modified","node_type","serialize","title","title","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","url","vzip","vzip","vzip","vzip","vzip","b","f","s","apply_observation","apply_observation_direct","delete_everything","delete_place_visit_at_time","delete_place_visit_at_time_by_href","delete_visits_between","delete_visits_between_in_tx","delete_visits_for","frecency_stale_at","get_top_frecent_site_infos","get_visit_count","get_visit_infos","get_visit_page","get_visit_page_with_bound","get_visited","get_visited_into","get_visited_urls","history_sync","href_to_guid","prune_older_visits","update_frecency","url_to_guid","FetchedVisit","FetchedVisitPage","apply_synced_deletion","apply_synced_reconciliation","apply_synced_visits","borrow","borrow","borrow_mut","borrow_mut","clone","clone_into","eq","equivalent","fetch_outgoing","fetch_visits","finish_outgoing","fmt","fmt","from","from","from_row","from_row","guid","into","into","is_local","row_id","title","to_owned","try_from","try_from","try_into","try_into","type_id","type_id","unknown_fields","url","visit_date","visit_type","vzip","vzip","DocumentType","HistoryHighlight","HistoryHighlightWeights","HistoryMetadata","HistoryMetadataObservation","Media","Regular","apply_metadata_observation","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","column_result","created_at","delete_all_metadata_for_page","delete_between","delete_metadata","delete_older_than","document_type","document_type","eq","eq","eq","equivalent","equivalent","equivalent","fmt","fmt","fmt","frequency","from","from","from","from","from","get_between","get_highlights","get_latest_for_url","get_since","into","into","into","into","into","place_id","preview_image_url","preview_image_url","query","referrer_url","referrer_url","score","search_term","search_term","title","title","title","to_owned","to_owned","to_owned","to_owned","to_owned","to_sql","total_view_time","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","updated_at","url","url","url","view_time","view_time","vzip","vzip","vzip","vzip","vzip","Invalid","Normalized","Original","ValidatedTag","borrow","borrow_mut","clone","clone_into","ensure_valid","eq","equivalent","fmt","from","get_tags_for_url","get_urls_with_tag","into","is_original","remove_all_tags_from_url","remove_tag","tag_url","to_owned","try_from","try_into","type_id","untag_url","validate_tag","vzip","Bookmark","Bookmark","BookmarkType","Download","Embed","Folder","FramedLink","InvalidVisitType","Link","New","Normal","RedirectPermanent","RedirectTemporary","Reload","Separator","SyncStatus","Typed","Unknown","UnknownFields","UpdatePlace","VisitTransitionSet","VisitType","all","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","cmp","cmp","cmp","column_result","column_result","complement","contains","default","deserialize","empty","eq","eq","eq","eq","eq","equivalent","equivalent","equivalent","equivalent","equivalent","extend","fmt","fmt","fmt","fmt","fmt","fmt","for_specific","from","from","from","from","from","from_iter","from_primitive","from_u16","from_u8","from_u8","from_u8_with_valid_url","get_hash","get_hash","get_hash","get_hash","hash","hash","hash","hash","insert","into","into","into","into","into","into_iter","into_u16","is_empty","len","new","partial_cmp","partial_cmp","partial_cmp","provide","remove","serialize","serialize","single","to_owned","to_owned","to_owned","to_owned","to_owned","to_sql","to_sql","to_sql","to_sql","to_string","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","vzip","vzip","vzip","vzip","vzip"],"q":[[0,"places"],[20,"places::api"],[24,"places::api::history"],[72,"places::api::matcher"],[119,"places::api::places_api"],[179,"places::bookmark_sync"],[233,"places::bookmark_sync::engine"],[256,"places::bookmark_sync::record"],[437,"places::db"],[456,"places::db::db"],[525,"places::error"],[648,"places::error::PlacesApiError"],[654,"places::ffi"],[992,"places::frecency"],[1034,"places::hash"],[1054,"places::history_sync"],[1084,"places::history_sync::engine"],[1107,"places::history_sync::record"],[1150,"places::import"],[1153,"places::import::common"],[1200,"places::import::common::sql_fns"],[1204,"places::import::ios"],[1206,"places::import::ios::history"],[1207,"places::match_impl"],[1318,"places::observation"],[1353,"places::storage"],[1445,"places::storage::bookmarks"],[1687,"places::storage::bookmarks::BookmarkPosition"],[1688,"places::storage::bookmarks::InsertableItem"],[1691,"places::storage::bookmarks::UpdatableItem"],[1694,"places::storage::bookmarks::UpdateTreeLocation"],[1697,"places::storage::bookmarks::bookmark_sync"],[1698,"places::storage::bookmarks::fetch"],[1808,"places::storage::bookmarks::fetch::Item"],[1811,"places::storage::bookmarks::json_tree"],[1890,"places::storage::bookmarks::json_tree::BookmarkTreeNode"],[1893,"places::storage::history"],[1915,"places::storage::history::history_sync"],[1956,"places::storage::history_metadata"],[2061,"places::storage::tags"],[2088,"places::types"]],"d":["","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","","","","","","","Records an accepted autocomplete match, recording the …","","","","","","","","","","","","","A frecency score for this match.","Returns the argument unchanged.","Returns the argument unchanged.","Default search behaviors from Desktop: HISTORY, BOOKMARK, …","","","","The favicon URL.","Calls U::from(self).","Calls U::from(self).","","","Synchronously queries all providers for autocomplete …","","The search string for this match.","","","","The title of the autocompleted value, to show in the UI. …","","","","","","","","","The URL to open when the user confirms a match. This is …","","","","","The entry-point to the places API. This object gives …","","","","","","","","","","","","","","","Close a connection to the database. If the connection is …","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","Create a new, or fetch an already open, PlacesApi backed …","","Create a new, or fetch an already open, memory-based …","Open a connection to the database.","For uniffi we need to expose our Arc returning constructor …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Synced item kinds. These are stored in …","Synced item validity states. These are stored in …","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","","Returns the argument unchanged.","","","","","","","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","","","Calls U::from(self).","","","","","","","","","","Erases all local items. Unlike reset, this keeps all …","","","","A bookmark record ID. Bookmark record IDs are the same as …","","","","","","","","","Returns a reference to the GUID for this record ID.","Returns a reference to the record payload ID. This is the …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","","","Returns the argument unchanged.","Creates a bookmark record ID from a Sync record payload ID.","","","","","","","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Returns the record payload ID. This is the owned version of","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","High level transaction type which “does the right thing…","","","","Consumes and commits a PlacesTransaction transaction.","","","","Returns the argument unchanged.","Calls U::from(self).","For transactions on sync connnections: Checks to see if we …","Consumes and attempst to roll back a PlacesTransaction. …","Returns true if the current transaction should be …","","","","","","An object that can tell you whether a bookmark changing …","","","","PlacesDB that’s behind a Mutex so it can be shared …","","","","","Begin the “correct” transaction type for this …","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns an object that can tell you whether any changes …","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Error enum used internally","","","","Attempt to create/update/delete a bookmark item in an …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Thrown when providing a guid to a create or update function","","","","","Thrown for invalid URLs","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","","","","","","","","","Returns the argument unchanged.","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","In desktop sync, bookmarks are clamped to Jan 23, 1993 …","","This is a type intended to be used to represent the guids …","","","","","","","","","","","","","","","","","","","","Interrupt operations that use SQL","","A parsed URL record.","","Add an observation to the database.","Get the data backing this Guid as a &[u8].","","","","","","","Get the data backing this Guid as a &str.","Return the serialization of this URL.","Begin an interrupt scope that will be interrupted by this …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Return whether this URL is a cannot-be-a-base URL, meaning …","","","","","","","","","","","","","","","","","","","Create a default guid by calling Guid::empty()","","","","","","","","","Serialize with Serde using the internal representation of …","If this URL has a host and it is a domain name (not an IP …","Returns None if other is later than self (Duration may not …","Create an empty guid. Usable as a constant.","","","","","","","","","","","","","","","","","","","","","","","","","","","","Return this URL’s fragment identifier, if any.","","","","Returns the argument unchanged.","","Returns the argument unchanged.","","","Returns the argument unchanged.","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Convert a directory name as std::path::Path into an URL in …","Convert a file name as std::path::Path into an URL in the …","Convert b into a Guid.","","Convert b into a Guid.","Convert v to a Guid, consuming it.","","","","","","","","","","","","","","","Return whether the URL has an ‘authority’, which can …","Equivalent to url.host().is_some().","","","","Return the parsed representation of the host for this URL. …","Return the string representation of the host (domain or IP …","","","","","","Interrupt all interrupt scopes created by this handle","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","Convert this Guid into a String, consuming it in the …","Return the serialization of this URL.","","","Returns true for Guids that are valid places guids, and …","Returns true for Guids that are deemed valid by the sync …","Returns true if the byte b is a valid base64url byte.","Parse a string as an URL, with this URL as the base URL.","Creates a relative URL if possible, with this URL as the …","","","","","","","Create a guid from a str.","","","","","","Return a default ParseOptions that can fully configure the …","Return the origin of this URL (…","Parse an absolute URL from a string.","Parse an absolute URL from a string and add params to its …","","","","Return the password for this URL, if any, as a …","Return the path for this URL, as a percent-encoded ASCII …","Unless this URL is cannot-be-a-base, return an iterator of …","Return an object with methods to manipulate this URL’s …","","","Return the port number for this URL, if any.","Return the port number for this URL, or the default port …","","Return this URL’s query string, if any, as a …","","","Parse the URL’s query string, if any, as …","Manipulate this URL’s query string, viewed as a sequence …","Create a random guid (of 12 base64url characters). …","","","","","Return the scheme of this URL, lower-cased, as an ASCII …","","","","Serialize with Serde using the internal representation of …","Change this URL’s fragment identifier.","Change this URL’s host.","Change this URL’s host to the given IP address.","Change this URL’s password.","Change this URL’s path.","Change this URL’s port number.","Change this URL’s query string.","Change this URL’s scheme.","Change this URL’s username.","Resolve a URL’s host and port number to SocketAddr.","","","","","Assuming the URL is in the file scheme or similar, convert …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Return the username for this URL (typically the empty …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","","Calls U::from(self).","","","","","","","","","","","","","","","","","","","Equivalent to "prefix_hi" in mozilla::places::HashURL","Equivalent to "prefix_lo" in mozilla::places::HashURL","","","","","","","","","Returns the argument unchanged.","This should return identical results to mozilla::HashString…","This should be identical to the “real” …","This should be identical to the “real” …","Calls U::from(self).","","","","","","","","Visit timestamps on the server are microseconds since the …","","","","","","","","","","","","","Returns the argument unchanged.","","","","","Calls U::from(self).","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","","","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","","","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","","We use/abuse the mirror to perform our import, but need to …","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","","","","","","","","","This import is used for iOS users migrating from browser.db…","","Match anywhere in each searchable term without doing any …","","Search through bookmarks.","Match only the beginning of each search term.","Match only the beginning of each search term using a case …","Match on word boundaries in each searchable term.","Match first on word boundaries, and if we do not get …","Search through history.","Search for javascript: urls","","Search for open pages (currently not meaningfully …","Use intersection between history, typed, bookmark, tag and …","Include search suggestions from the currently selected …","","Search through tags.","Search through the title of pages.","Search for typed pages","Search the URL of pages.","Returns the set containing all flags.","","Returns the intersection between the two sets of flags.","Disables all flags disabled in the set.","Returns the union of the two sets of flags.","Adds the set of flags.","Returns the raw value of the flags currently stored.","Returns the left flags, but with all the right flags …","Toggles the set of flags.","","","","","","","","","","","","","","","Returns the complement of this set of flags.","Returns true if all of the flags in other are contained …","","Returns the difference between the flags in self and other.","Returns an empty set of flags.","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Convert from underlying bit representation, unless that …","Convert from underlying bit representation, dropping any …","Convert from underlying bit representation, preserving all …","","","","Inserts the specified flags in-place.","Returns the intersection between the flags in self and …","Returns true if there are flags common to both self and …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","Returns true if all flags are currently set.","Returns true if no flags are currently stored.","","Returns the complement of this set of flags.","","","Removes the specified flags in-place.","","","Inserts or removes the specified flags depending on the …","Returns the set difference of the two sets of flags.","Disables all flags enabled in the set.","Returns the symmetric difference between the flags in self …","","","","","","","Toggles the specified flags in-place.","","","","","","","","","","","Returns the union of between the flags in self and other.","","","","","","An “observation” based model for updating history. You …","","","","","","","Returns the argument unchanged.","","","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","From …","","","","","","","","","","","","","","","","","Delete all items in the temp tables we use for staging …","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","","","","","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","","","Run maintenance on the places DB (checkpoint step)","Run maintenance on the places DB (optimize step)","Run maintenance on the places DB (prune step)","Run maintenance on the places DB (vacuum step)","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Special GUIDs associated with bookmark roots. It’s …","We don’t require bookmark type for updates on the other …","","","Structures which can be used to insert a bookmark, folder …","","","","","","","","","","","","","","","","Structures which can be used to update a bookmark, folder …","","","","Support for modifying bookmarks, including changing the …","","","","Get the URL of the bookmark matching a keyword","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Delete the specified bookmark. Returns true if a bookmark …","Erases all bookmarks and resets all Sync metadata.","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","Returns the argument unchanged.","Returns the argument unchanged.","","","","","","","","","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","The functions exposed over the FFI use the same type for …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Sets up the syncable roots. All items in …","","Structs we return when reading bookmarks","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","This is similar to fetch_tree, but does not recursively …","","Call fetch_tree_with_depth with FetchDepth::Deepest. This …","Call fetch_tree with a depth parameter and convert the …","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","Returns the argument unchanged.","Returns the argument unchanged.","","","","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Fetch the tree starting at the specified guid. Returns a …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","Returns the argument unchanged.","","","","","","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the RowId of a new visit in moz_historyvisits, or …","Returns the RowId of a new visit in moz_historyvisits, or …","","","","Delete all visits in a date range.","","Deletes all visits for a page given its GUID, creating …","Indicates if and when a URL’s frecency was marked as …","","","","","","","Low level api used to implement both get_visited and the …","Get the set of urls that were visited between start and end…","","Returns the GUID for the specified Url String, or None if …","","","Returns the GUID for the specified Url, or None if it doesn…","","","","","Apply history visit from sync. This assumes they have all …","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","","","","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Delete all metadata for the specified place id.","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","The tag is invalid.","The tag is valid, but normalized to remove leading and …","The original tag is valid.","The validity of a tag.","","","","","Returns the tag string if the tag is valid or normalized, …","","","","Returns the argument unchanged.","Retrieves a list of tags for the specified URL.","Retrieves a list of URLs which have the specified tag.","Calls U::from(self).","Returns true if the original tag is valid; false if it’s …","Remove all tags from the specified URL.","Remove the specified tag from all URLs.","Tags the specified URL.","","","","","Remove the specified tag from the specified URL.","Checks the validity of the specified tag.","","","","Bookmark types.","","","","","","","","","","","","","Re SyncStatus - note that:","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","","","","","","","","","","","","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"i":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,6,0,6,7,10,6,7,10,6,0,6,6,10,6,6,7,10,6,7,10,6,0,7,10,6,10,6,10,7,6,10,7,10,6,7,10,6,7,10,6,7,0,7,7,10,6,0,0,0,0,0,17,18,17,18,17,18,17,18,18,18,17,18,18,17,18,18,18,18,18,18,17,18,17,0,0,17,18,18,0,0,18,17,18,17,18,17,18,17,18,18,17,18,0,0,0,26,26,26,0,23,23,198,23,26,198,23,26,26,26,23,198,23,26,26,26,198,23,26,26,0,23,23,198,23,26,198,23,23,23,23,0,23,23,23,26,23,23,23,26,198,23,26,198,23,26,198,23,26,198,23,26,23,41,0,41,41,41,42,42,41,0,0,42,41,42,41,42,41,42,41,42,41,42,0,41,42,41,42,41,42,41,41,42,41,42,41,42,41,42,41,42,41,42,0,41,42,41,42,41,42,41,42,41,42,41,42,0,0,0,0,0,51,51,51,51,51,51,51,51,51,51,51,51,51,51,51,51,51,51,67,0,0,0,67,0,67,0,67,0,67,0,61,61,61,62,63,64,65,66,67,61,62,63,64,65,66,67,64,61,62,63,64,65,66,67,61,62,63,64,65,66,67,62,63,64,65,66,61,62,63,64,65,66,67,61,62,63,64,65,66,67,61,62,63,64,65,66,67,65,61,62,63,64,65,66,67,61,61,62,63,64,65,66,67,67,67,67,67,67,61,61,62,63,64,65,66,61,61,62,63,64,65,66,67,61,62,62,63,64,65,66,62,63,64,65,66,66,67,62,63,64,65,66,61,62,63,64,65,66,67,65,63,62,62,63,64,65,61,62,63,64,65,66,67,61,62,63,64,65,66,67,61,62,63,64,65,66,67,61,62,63,64,65,66,67,67,62,63,64,65,66,62,63,61,62,63,64,65,66,67,0,0,0,0,70,70,70,70,0,70,70,70,70,70,70,70,70,70,70,0,0,0,0,0,0,1,33,33,1,1,81,33,75,77,1,81,33,75,77,1,75,1,1,1,33,77,1,1,81,1,81,33,75,77,1,1,81,81,33,75,77,1,33,75,1,1,81,81,33,75,77,1,81,33,75,77,1,81,33,75,77,1,81,81,33,75,77,1,0,87,86,0,86,0,87,86,86,85,87,86,87,88,0,86,87,0,86,88,87,86,86,86,87,86,88,87,87,87,88,86,85,0,85,0,86,86,85,85,86,86,86,86,85,87,86,89,86,85,86,87,88,89,85,86,87,88,89,85,85,86,86,87,87,88,88,89,89,85,86,86,86,86,86,86,86,86,86,86,86,86,86,87,88,89,86,85,86,87,88,89,85,86,87,88,89,86,85,86,87,88,89,85,86,87,88,89,85,86,87,88,89,85,86,87,88,89,85,86,87,88,89,199,200,201,202,203,204,0,0,0,0,0,0,0,0,0,103,0,0,0,0,0,0,0,0,0,0,0,0,0,125,0,0,0,0,0,0,125,0,0,0,36,36,59,103,103,59,59,4,36,59,4,73,36,36,36,36,36,36,36,36,36,36,36,103,73,59,4,36,126,125,121,205,110,111,103,73,59,4,36,126,125,121,205,110,111,111,4,103,103,103,59,4,110,111,103,59,4,110,111,103,59,4,103,59,103,59,36,36,36,36,59,103,59,4,4,4,103,59,59,59,103,59,59,59,59,59,59,59,59,59,4,110,111,103,59,4,110,111,103,103,73,59,59,4,4,4,121,103,103,103,103,73,59,59,59,59,59,59,59,59,4,36,126,125,121,121,205,110,111,4,4,59,4,59,59,103,59,4,36,36,36,36,36,36,36,36,36,36,36,4,4,103,59,4,4,4,4,4,4,4,111,73,103,73,59,4,36,126,125,121,205,110,111,59,4,59,4,110,110,59,59,59,4,4,36,205,59,36,36,73,59,36,36,36,103,111,4,4,4,4,103,59,4,4,4,4,4,0,36,4,4,110,4,36,36,4,4,59,36,36,36,36,4,103,59,4,4,4,4,4,4,4,4,4,4,4,4,110,126,121,110,4,103,59,4,110,111,103,59,103,59,4,103,73,59,4,4,36,126,125,121,205,110,111,103,73,59,4,36,126,125,121,205,110,111,103,73,59,4,36,126,125,121,205,110,111,126,121,110,4,4,4,110,103,73,59,4,36,126,125,121,205,110,111,0,0,153,153,153,0,153,153,153,153,153,153,153,153,153,153,153,153,153,153,153,153,153,153,153,153,153,153,153,153,153,153,153,153,153,153,153,153,153,153,153,153,154,154,0,154,154,154,154,154,154,154,154,0,0,0,154,154,154,154,154,154,0,0,0,119,119,119,119,119,119,119,0,119,119,119,119,119,119,119,119,119,119,119,0,119,119,119,119,119,119,119,0,0,0,0,155,155,155,155,155,155,155,155,155,155,155,155,155,155,155,155,155,155,155,0,0,157,158,157,158,157,158,157,158,157,157,157,158,157,158,157,158,157,158,157,158,158,158,157,158,157,158,158,157,158,157,157,158,157,158,157,158,157,158,158,157,158,0,0,0,0,0,0,0,159,160,141,159,160,141,141,141,141,0,160,159,141,141,159,141,159,160,141,159,160,141,159,141,141,141,0,141,0,141,141,159,160,141,159,160,141,159,160,141,159,160,141,0,0,0,0,0,0,0,163,163,0,162,163,163,163,163,162,162,0,162,162,162,0,162,162,162,162,162,162,162,162,162,162,162,162,162,165,165,163,162,165,163,162,163,162,163,162,162,163,162,162,162,162,162,162,163,162,163,162,162,0,163,162,162,162,162,162,165,163,162,162,162,162,162,162,162,162,162,162,165,163,162,165,162,162,165,162,165,162,162,165,165,162,162,162,162,165,165,163,162,163,162,162,165,163,162,165,163,162,165,163,162,165,162,165,165,165,163,162,0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,0,0,0,0,0,0,0,0,167,169,168,146,167,169,168,146,167,167,167,167,146,146,167,0,167,167,167,0,167,167,169,168,146,169,167,169,168,146,169,168,167,169,167,169,0,0,167,169,168,146,169,169,168,168,167,169,146,169,0,0,0,0,167,169,169,0,169,167,167,167,167,169,168,146,167,169,168,146,167,169,168,146,169,169,0,169,169,169,167,169,168,146,170,174,179,0,0,0,174,179,0,0,0,0,116,116,175,175,175,116,174,179,170,116,0,116,0,0,0,0,0,116,116,0,0,116,170,171,172,173,174,175,176,177,178,179,108,116,170,171,172,173,174,175,176,177,178,179,108,173,116,170,171,172,173,174,175,176,177,178,179,108,116,170,171,172,173,174,175,176,177,178,179,108,0,0,171,172,173,175,176,178,0,0,116,116,116,116,108,116,108,0,116,170,171,172,173,174,175,176,177,178,179,108,116,170,171,172,173,174,174,174,174,174,175,176,177,178,179,179,179,179,108,116,116,116,171,172,173,108,116,0,116,170,171,172,173,174,175,176,177,178,179,108,108,0,171,172,173,179,176,177,178,0,171,172,173,108,116,171,172,173,108,171,173,176,178,108,116,170,171,172,173,174,175,176,177,178,179,108,116,170,171,172,173,174,175,176,177,178,179,108,116,170,171,172,173,174,175,176,177,178,179,108,116,170,171,172,173,174,175,176,177,178,179,108,0,0,171,176,108,116,170,171,172,173,174,175,176,177,178,179,108,116,206,207,208,209,210,211,212,213,214,213,0,185,0,0,185,0,0,0,0,185,182,183,184,185,186,187,182,183,184,185,186,187,184,184,182,183,184,185,182,183,184,185,185,182,183,184,184,186,187,0,0,0,0,182,183,184,185,182,183,184,185,185,185,185,186,187,185,182,183,184,182,183,184,185,186,187,185,182,183,184,185,182,183,184,185,182,183,184,0,0,182,184,182,183,184,185,182,183,184,185,186,187,182,183,184,185,186,187,182,183,184,185,186,187,182,182,183,184,185,186,187,215,216,217,180,0,0,188,0,180,0,180,0,188,188,191,189,190,180,188,191,189,190,180,190,180,191,189,190,189,190,180,0,191,189,190,180,188,191,189,190,180,180,180,180,180,191,189,190,0,188,191,189,190,180,191,189,190,180,180,191,190,188,191,189,190,180,188,191,189,190,180,188,191,189,190,180,191,188,191,189,190,180,218,219,220,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,192,193,192,193,192,192,192,192,0,0,0,192,193,192,193,192,193,193,192,193,192,193,193,192,192,193,192,193,192,193,193,193,192,192,192,193,0,0,0,0,0,194,194,0,194,122,123,135,124,194,122,123,135,124,194,122,123,135,124,194,122,123,135,124,194,124,0,0,0,0,135,124,194,135,124,194,135,124,194,135,124,122,194,122,123,135,124,0,0,0,0,194,122,123,135,124,123,123,124,0,135,124,123,135,124,123,135,124,194,122,123,135,124,194,124,194,122,123,135,124,194,122,123,135,124,194,122,123,135,124,124,123,135,124,122,135,194,122,123,135,124,195,195,195,0,195,195,195,195,195,195,195,195,195,0,0,195,195,0,0,0,195,195,195,195,0,0,195,15,181,0,15,15,181,15,0,15,197,197,15,15,15,181,0,15,197,0,15,0,0,127,127,196,15,181,197,127,196,15,181,197,127,196,15,181,197,127,196,15,181,197,196,181,197,181,197,127,127,127,15,127,127,196,15,181,197,127,196,15,181,197,127,127,196,196,15,181,197,127,127,196,15,181,197,127,15,127,181,197,181,127,15,181,197,127,15,181,197,127,127,196,15,181,197,127,127,127,127,127,196,181,197,196,127,15,181,127,127,196,15,181,197,127,15,181,197,196,127,127,196,15,15,181,197,127,196,15,181,197,127,196,15,181,197,127,196,15,181,197],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[1,2],3],0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[4,[[3,[5]]]],[6,6],[[]],0,[[6,6],5],[[],5],[[7,8],9],[[10,8],9],[[6,8],9],[[]],[[]],[[]],[[1,7],3],[[]],[[]],[[]],0,[[6,6],[[12,[11]]]],0,0,[[]],0,[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],14],[[],14],[[],14],0,[[1,4,[12,[4]],15,[12,[6]],5],3],0,[[]],[[]],[[]],0,0,0,0,[[1,16,4],3],[[]],[[]],[[]],[[]],[17,17],[18,18],[[]],[[]],[[18,18],5],[[],5],[[17,8],9],[[18,8],9],0,[[]],[[]],[19,[[3,[18]]]],[19,[[3,[18]]]],[19,[[3,[18]]]],[19,[[3,[18]]]],0,[[]],[[]],0,[[1,[20,[16]]],[[3,[[12,[4]]]]]],[[1,17],[[3,[[21,[18]]]]]],0,0,[[18,22],13],[16],[16],0,[[]],[[]],[[],13],[[],13],[[],13],[[],13],[[],14],[[],14],0,[[]],[[]],0,0,0,0,0,0,0,[23,24],[[23,25,25,25,4],[[24,[25]]]],[[]],[[]],[[]],[[]],[[]],[[]],[26,26],[[]],[[23,1],3],0,[[23,16,27],[[3,[28]]]],[[26,26],5],[[],5],[[26,8],9],[[]],[[]],[[]],[29,[[12,[26]]]],[30,[[12,[[32,[31]]]]]],[23,[[3,[[34,[33]]]]]],[[23,25,25,25,4],[[24,[25]]]],[[]],[[]],[[]],0,[[[20,[35]]],[[3,[[34,[23]]]]]],[[23,26],[[24,[[34,[36]]]]]],[16,[[3,[[34,[23]]]]]],[[23,26],[[3,[1]]]],[[[20,[35]]],[[24,[[34,[23]]]]]],[[[34,[23]]]],[23,3],[23,24],[26,37],[[23,38,39],[[3,[40]]]],[[23,38,39],[[3,[28]]]],[[23,38,39],[[3,[28]]]],[[]],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],14],[[],14],[[],14],[[]],[[]],[[]],[23,3],0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[41,41],[42,42],[[]],[[]],[[41,41],11],[[42,42],11],0,[[41,41],5],[[42,42],5],[[],5],[[],5],[[41,8],9],[[42,8],9],[[]],[43,41],[[]],[29,[[3,[41]]]],[29,[[3,[42]]]],[[[0,[44,45]],46],47],[[[0,[44,45]],46],47],[[41,48]],[[42,48]],[[]],[[]],[[41,41],[[12,[11]]]],[[42,42],[[12,[11]]]],0,[[]],[[]],[41,[[50,[49]]]],[42,[[50,[49]]]],[[],13],[[],13],[[],13],[[],13],[[],14],[[],14],[[]],[[]],0,0,0,0,0,[[51,52,53],[[55,[[21,[54]]]]]],[[]],[[]],[51,56],[[]],[[51,52],[[55,[[12,[57]]]]]],[51,[[55,[58]]]],[[]],[[[34,[33]]],[[3,[51]]]],[[51,58],55],[[51,52,[21,[59]]],55],[[51,[21,[60]],53],55],[51,55],[[],13],[[],13],[[],14],[[]],[51,55],0,0,0,0,0,0,0,0,0,0,0,0,[61,59],[61,16],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,[61,61],[62,62],[63,63],[64,64],[65,65],[66,66],[67,67],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,0,0,0,[68,[[13,[61]]]],[68,[[13,[62]]]],[68,[[13,[63]]]],[68,[[13,[64]]]],[68,[[13,[65]]]],[68,[[13,[66]]]],[68,[[13,[67]]]],[[61,61],5],[[62,62],5],[[63,63],5],[[64,64],5],[[65,65],5],[[66,66],5],[[67,67],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[],5],0,[[61,8],9],[[62,8],9],[[63,8],9],[[64,8],9],[[65,8],9],[[66,8],9],[[67,8],9],[[]],[59,61],[[]],[[]],[[]],[[]],[[]],[64,67],[66,67],[63,67],[62,67],[65,67],[[]],[59,61],[[[0,[44,45]],46],47],0,0,0,0,0,[[61,48]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[61,59],0,0,0,0,0,0,0,0,0,0,0,0,[67,61],0,0,0,0,0,[[61,22],13],[[62,22],13],[[63,22],13],[[64,22],13],[[65,22],13],[[66,22],13],[[67,22],13],0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[67,69],0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,0,0,[[]],[[]],[70,3],[70,71],0,[70,71],[[]],[[]],[70,3],[70,3],[70,5],[[],13],[[],13],[[],14],[[]],0,0,0,0,0,0,[1,72],[33,73],[33,[[3,[74]]]],[1,[[3,[74]]]],[1,[[3,[70]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[75,5],[1,71],[1,26],0,[33,[[76,[1]]]],[77,[[80,[[79,[72,78]]]]]],[1,71],[1],[[81,71],82],[[1,8],9],[[]],[[]],[[]],[[]],[[]],[1,75],[[81,83],82],[[]],[[]],[[]],[[]],[[]],[1,33],[72,75],[1,[[34,[73]]]],[[[20,[35]],26,72,[34,[76]]],[[3,[1]]]],[[81,71,5],82],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],14],[[],14],[[],14],[[],14],[[],14],[[81,83,84],82],[[]],[[]],[[]],[[]],[[]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[85,8],9],[[85,8],9],[[86,8],9],[[86,8],9],[[87,8],9],[[87,8],9],[[88,8],9],[[88,8],9],[[89,8],9],[[89,8],9],[[]],[90,86],[91,86],[92,86],[87,86],[93,86],[94,86],[95,86],[96,86],[[]],[89,86],[88,86],[97,86],[98,86],[[]],[[]],[[]],[86,99],[[]],[[]],[[]],[[]],[[]],[100],[100],[100],[100],[100],[86,[[12,[101]]]],[[],25],[[],25],[[],25],[[],25],[[],25],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],14],[[],14],[[],14],[[],14],[[],14],[[]],[[]],[[]],[[]],[[]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[36,25,25],24],[[36,2],24],[59,[[102,[29]]]],[103,47],[103,104],[59,16],[59,[[102,[29]]]],[4,16],[36,73],[59,16],[4,16],[73,[[13,[74,95]]]],[[36,[102,[59]]],[[24,[84]]]],[[36,59],[[24,[5]]]],[36,24],[[36,25],[[24,[[21,[105]]]]]],[[36,59,5],[[24,[[12,[105]]]]]],[[36,106],[[24,[[21,[105]]]]]],[[36,59],[[24,[[12,[105]]]]]],[[36,25],[[24,[[12,[4]]]]]],[[36,107],[[24,[59]]]],[[36,25,106],[[24,[[21,[105]]]]]],[[36,108],24],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,[4,5],[[103,109],[[12,[103]]]],[[103,109],[[12,[103]]]],[103,103],[59,59],[4,4],[110,110],[111,111],[[]],[[]],[[]],[[]],[[]],[[103,103],11],[[59,59],11],[[4,4],11],[112,[[13,[103,113]]]],[112,[[13,[59,113]]]],[[],103],[[],59],[36,24],[[36,25,103],24],[[36,103,103],24],[[36,25],24],[59,16],[68,[[13,[103]]]],[68,[[13,[59]]]],[68,[[13,[4]]]],[68,[[13,[4]]]],[4,[[12,[16]]]],[[103,103],[[12,[109]]]],[[],59],[[],[[115,[114]]]],[[],[[115,[114]]]],[[103,103],5],[[59,16],5],[[59,116],5],[[59,[102,[29]]],5],[[59,59],5],[[59,[21,[29,117]]],5],[[59,25],5],[[59,[102,[29]]],5],[[59,16],5],[[59,116],5],[[4,4],5],[[110,110],5],[[111,111],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[103,8],[[13,[118]]]],[[103,8],[[13,[118]]]],[[73,8],[[13,[118]]]],[[59,8],[[13,[118]]]],[[59,8],[[13,[118]]]],[[4,8],[[13,[118]]]],[[4,8],[[13,[118]]]],[4,[[12,[16]]]],0,[119,103],[120,103],[[]],[47,103],[[]],[[[21,[29,117]]],59],[16,59],[[]],[[[102,[29]]],59],[16,59],[25,59],[61,59],[116,59],[[]],[[]],[[]],[[]],[[]],[18,121],[[]],[[]],[[]],[[[20,[35]]],[[13,[4]]]],[[[20,[35]]],[[13,[4]]]],[[[102,[29]]],59],[16,[[13,[4,97]]]],[25,59],[[[21,[29,117]]],59],[[[0,[44,45]],46],47],[[[0,[44,45]],46],47],[[[0,[44,45]],46],47],[[36,122,106],[[24,[[21,[123]]]]]],[[36,103,103],[[24,[[21,[124]]]]]],[[36,103],[[24,[[21,[124]]]]]],[[36,4],[[24,[[12,[124]]]]]],[[36,106,125],[[24,[[21,[126]]]]]],[[36,127],[[24,[104]]]],[[36,103,103,127],[[24,[[21,[110]]]]]],[[36,104,104,127],[[24,[[21,[110]]]]]],[[36,104,104,104,127],[[24,[111]]]],[[36,[21,[25]]],[[24,[[21,[5]]]]]],[[36,103,103,5],[[24,[[21,[4]]]]]],[4,5],[4,5],[[103,48]],[[59,48]],[[4,48]],[4,[[12,[[128,[16]]]]]],[4,[[12,[16]]]],[[4,[130,[129]]],16],[[4,[131,[129]]],16],[[4,132],16],[[4,[133,[129]]],16],0,[73],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],[[134,[25]]]],[[],[[134,[25]]]],[59,25],[4,25],0,0,[59,5],[59,5],[29,5],[[4,16],[[13,[4,97]]]],[[4,4],[[12,[25]]]],[[36,25],[[24,[[12,[4]]]]]],0,[[],72],[[36,4,[12,[4]],[12,[25]]],24],[[36,103],24],[71,73],[16,59],[1,36],[36,[[34,[73]]]],[[36,135],24],[[],103],0,[[],136],[4,137],[16,[[13,[4,97]]]],[[16,138],[[13,[4,97]]]],[[103,103],[[12,[11]]]],[[59,59],[[12,[11]]]],[[4,4],[[12,[11]]]],[4,[[12,[16]]]],[4,16],[4,[[12,[[139,[114]]]]]],[4,[[13,[140]]]],0,[[36,25,104],[[24,[141]]]],[4,[[12,[142]]]],[4,[[12,[142]]]],0,[4,[[12,[16]]]],[[36,25,106],[[24,[[21,[121]]]]]],[[36,25,106],[[24,[[21,[124]]]]]],[4,143],[4,[[145,[144]]]],[[],59],[36,24],[36,24],[[36,84],[[24,[146]]]],[36,24],[4,16],[[103,22],13],[[59,22],13],[[4,22],13],[[4,22],13],[[4,[12,[16]]]],[[4,[12,[16]]],[[13,[97]]]],[[4,147],13],[[4,[12,[16]]],13],[[4,16]],[[4,[12,[142]]],13],[[4,[12,[16]]]],[[4,16],13],[[4,16],13],[[4,148],[[13,[[21,[149,117]],96]]]],0,0,0,0,[4,[[13,[150]]]],[[]],[[]],[[]],[[]],[[]],[103,[[13,[49,90]]]],[59,[[13,[49,90]]]],[[],25],[[],25],[[],25],[[],13],[[],13],[[],13],[[],13],[16,[[13,[4]]]],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],0,0,0,[4,16],[[],151],[[],152],0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,0,[[]],[[]],[[71,153,104,[12,[5]]],[[3,[106]]]],[153,153],[[]],[[],153],0,0,0,0,[[153,153],5],[[],5],0,0,[[153,8],9],0,0,0,[[]],[[153,[12,[15]],5,5],106],[[]],0,0,0,0,0,0,0,0,0,0,[[]],[[],13],[[],13],[[],14],0,0,0,[[]],0,0,0,[[]],[[]],[154,154],[[]],[[154,154],5],[[],5],[[154,8],9],[[]],[16,84],[16,47],[[16,154],47],[[]],[[]],[[],13],[[],13],[[],14],[[]],0,0,0,[[]],[[]],[119,119],[[]],[[119,119],11],[[],119],[68,[[13,[119]]]],0,[[119,119],5],[[],5],[[119,8],9],[[119,8],9],[[]],[103,119],[120,119],[[[0,[44,45]],46],47],[[119,48]],[[]],[[119,119],[[12,[11]]]],0,[[119,22],13],[[]],[[],25],[[],13],[[],13],[[],14],[[]],0,0,0,0,[[155,52,53],[[55,[[21,[54]]]]]],[[]],[[]],[155,[[156,[16]]]],0,[[]],[[155,52],[[55,[[12,[57]]]]]],[155,[[55,[58]]]],[[]],[[[34,[33]]],[[3,[155]]]],[[155,58],55],[[155,52,[21,[59]]],55],[[155,[21,[60]],53],55],[155,55],[[],13],[[],13],[[],14],[[]],[155,55],0,0,[[]],[[]],[[]],[[]],[157,157],[158,158],[[]],[[]],0,[[],157],[68,[[13,[157]]]],[68,[[13,[158]]]],[[157,157],5],[[158,158],5],[[],5],[[],5],[[157,8],9],[[158,8],9],[[]],[[]],0,0,[[]],[[]],[[157,22],13],[[158,22],13],0,[[]],[[]],0,[[],13],[[],13],[[],13],[[],13],[[],14],[[],14],0,0,0,[[]],[[]],0,0,0,0,0,0,[[1,4,16],[[3,[159]]]],[[]],[[]],[[]],[[]],[[]],[[]],[141,141],[[]],[[],141],[71,3],[160,103],[159],[[141,141],5],[[],5],[159,3],[[141,8],9],[[]],[[]],[[]],[[]],[[]],[[]],[[1,25],159],0,0,0,[[1,16],[[3,[84]]]],[[141,22],13],0,[[]],0,[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],14],[[],14],[[],14],[[]],[[]],[[]],[161,[[50,[103]]]],[161,[[50,[103]]]],[161,[[50,[[12,[25]]]]]],[161,[[50,[[12,[25]]]]]],0,0,[[1,[20,[35]],104],[[3,[141]]]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[],162],[[],162],[[162,162],162],[[162,162]],[[162,162],162],[[162,162]],[162,84],[[162,162],162],[[162,162]],0,[[]],[[]],[[]],[[]],[[]],[[]],[163,163],[162,162],[[]],[[]],[[162,162],11],[112,[[164,[163]]]],[112,[[164,[162]]]],[162,162],[[162,162],5],[[],162],[[162,162],162],[[],162],[[163,163],5],[[162,162],5],[[],5],[[],5],[[162,138]],[[16,16,5],5],[[163,8],9],[[162,8],9],[[162,8],9],[[162,8],9],[[162,8],9],[[162,8],9],[[]],[[]],[[]],[84,[[12,[162]]]],[84,162],[84,162],[138,162],[[[0,[44,45]],46],47],[[162,48]],[[162,162]],[[162,162],162],[[162,162],5],[[]],[[]],[[]],[165,5],[162,5],[162,5],0,[162,162],0,[[162,162],[[12,[11]]]],[[162,162]],0,0,[[162,162,5]],[[162,162],162],[[162,162]],[[162,162],162],0,0,[[]],[[]],[163,[[50,[49]]]],[162,[[50,[49]]]],[[162,162]],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],14],[[],14],[[],14],0,[[162,162],162],0,0,[[]],[[]],[[]],0,0,[[]],[[]],[2,2],[[]],[[2,8],9],[[]],[2,5],[2,5],[[]],0,0,0,0,[4,2],0,0,0,[[]],[[],13],[[],13],[[],14],0,0,[[]],[[2,[166,[[12,[103]]]]],2],[[2,[166,[[12,[5]]]]],2],[[2,[166,[[12,[5]]]]],2],[[2,[166,[[12,[5]]]]],2],[[2,[166,[[12,[5]]]]],2],[[2,[166,[[12,[4]]]]],2],[[2,[166,[[12,[4]]]]],2],[[2,[166,[[12,[25]]]]],2],[[2,[166,[[12,[15]]]]],2],0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[167,167],[[]],[[167,167],11],[112,[[164,[167]]]],0,0,[[],167],[1,3],[68,[[13,[167]]]],[[167,167],5],[[],5],[[1,4],[[3,[[12,[168]]]]]],[[167,8],9],[[167,8],9],[[169,8],9],[[168,8],9],[[146,8],9],0,[[]],[[]],[[]],[[]],[19,[[3,[169]]]],[19,[[3,[168]]]],[[[0,[44,45]],46],47],0,[[167,48]],0,0,0,[[]],[[]],[[]],[[]],0,0,0,0,[[167,167],[[12,[11]]]],0,0,0,[1,3],[1,3],[[1,84],[[3,[146]]]],[1,3],[[167,22],13],0,0,0,0,[[]],[167,[[50,[49]]]],[[],25],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],14],[[],14],[[],14],[[],14],0,0,[[1,74],3],0,0,0,[[]],[[]],[[]],[[]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[116,59],[116,16],0,[[1,16],[[3,[[12,[4]]]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,[116,116],[170,170],[171,171],[172,172],[173,173],[174,174],[175,175],[176,176],[177,177],[178,178],[179,179],[108,108],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[1,[102,[59]]],[[3,[84]]]],[71,50],0,0,0,[[],175],[[],176],[[],178],[[1,59],[[3,[5]]]],[1,3],[[116,59],5],[[116,116],5],[[116,16],5],[[116,59],5],[[108,108],5],[[],5],[[],5],0,[[116,8],9],[[170,8],9],[[171,8],9],[[172,8],9],[[173,8],9],[[174,8],9],[[175,8],9],[[176,8],9],[[177,8],9],[[178,8],9],[[179,8],9],[[108,8],9],[[]],[[]],[[]],[[]],[[]],[[]],[171,174],[180,174],[173,174],[172,174],[[]],[[]],[[]],[[]],[177,179],[176,179],[178,179],[[]],[[]],[59,[[12,[116]]]],[[[0,[44,45]],46],47],[116,59],0,0,0,0,[[116,48]],[[1,174],[[3,[59]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[108,181],3],0,0,0,0,[179,175],0,0,0,[[[12,[16]]],[[12,[16]]]],0,0,0,0,[[116,116],[[12,[11]]]],0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[1,59,179],3],[[1,108],3],0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[16,[[12,[116]]]],0,0,0,0,0,0,0,0,0,0,[71,50],0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,[182,182],[183,183],[184,184],[185,185],[[]],[[]],[[]],[[]],[185,103],0,0,0,[[],184],[186,25],[187,25],[[1,59,5],[[3,[[12,[185]]]]]],[[1,4],[[3,[[21,[182]]]]]],[[1,59],[[3,[[12,[185]]]]]],[[1,59,188],[[3,[[12,[185]]]]]],[[182,8],9],[[183,8],9],[[184,8],9],[[185,8],9],[[]],[[]],[[]],[[]],[182,185],[184,185],[183,185],[[]],[[]],[185,59],0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[185,103],0,0,0,[185,[[12,[59]]]],0,0,0,[185,84],0,0,0,[[1,84],[[3,[[21,[182]]]]]],[[1,16,84],[[3,[[21,[182]]]]]],0,0,[[]],[[]],[[]],[[]],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],0,[[]],[[]],[[]],[[]],[[]],[[]],0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,[180],0,0,0,[[],189],[[],190],[68,[[13,[180]]]],[[1,59,188],[[3,[12]]]],[[191,8],9],[[189,8],9],[[190,8],9],[[180,8],9],[[]],[[]],[[]],[[]],[190,180],[191,180],[[]],[189,180],[180,59],0,0,0,[[1,190],3],[[]],[[]],[[]],[[]],[[]],0,0,0,[180,181],[[180,22],13],0,0,[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],14],[[],14],[[],14],[[],14],[[],14],0,[[]],[[]],[[]],[[]],[[]],0,0,0,[[1,2],[[3,[[12,[167]]]]]],[[1,2],[[3,[[12,[167]]]]]],[1,3],[[1,4,103],3],[[1,16,103],3],[[1,103,103],3],[[1,103,103],3],[[1,59],3],[[1,4],[[3,[[12,[103]]]]]],[[1,106,104],[[3,[[21,[126]]]]]],[[1,127],[[3,[104]]]],[[1,103,103,127],[[3,[[21,[110]]]]]],[[1,104,104,127],[[3,[[21,[110]]]]]],[[1,104,104,104,127],[[3,[111]]]],[[1,138],[[3,[[21,[5]]]]]],[[1,102,[102,[5]]],3],[[1,103,103,5],[[3,[[21,[25]]]]]],0,[[1,16],[[3,[[12,[59]]]]]],[1,3],[[1,167,[12,[5]]],3],[[1,4],[[3,[[12,[59]]]]]],0,0,[[1,59],3],[[1,59],3],[[1,59,4,[12,[25]],[102,[157]],69],3],[[]],[[]],[[]],[[]],[192,192],[[]],[[192,192],5],[[],5],[[1,72,72],[[3,[[21,[54]]]]]],[[1,4,72],[[3,[12]]]],[1,3],[[192,8],9],[[193,8],9],[[]],[[]],[19,[[3,[192]]]],[19,[[3,[193]]]],0,[[]],[[]],0,0,0,[[]],[[],13],[[],13],[[],13],[[],13],[[],14],[[],14],0,0,0,0,[[]],[[]],0,0,0,0,0,0,0,[[1,135],3],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[194,194],[122,122],[123,123],[135,135],[124,124],[[]],[[]],[[]],[[]],[[]],[112,[[164,[194]]]],0,[[1,167],3],[[1,104,104],3],[[1,4,[12,[4]],[12,[16]]],3],[[1,104],3],0,0,[[194,194],5],[[135,135],5],[[124,124],5],[[],5],[[],5],[[],5],[[194,8],9],[[135,8],9],[[124,8],9],0,[[]],[[]],[[]],[[]],[[]],[[1,104,104],[[3,[[21,[124]]]]]],[[1,122,106],[[3,[[21,[123]]]]]],[[1,4],[[3,[[12,[124]]]]]],[[1,104],[[3,[[21,[124]]]]]],[[]],[[]],[[]],[[]],[[]],0,0,0,[[1,16,106],[[3,[[21,[124]]]]]],0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[194,[[50,[49]]]],0,[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],14],[[],14],[[],14],[[],14],[[],14],0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],0,0,0,0,[[]],[[]],[195,195],[[]],[195,[[3,[16]]]],[[195,195],5],[[],5],[[195,8],9],[[]],[[1,4],[[3,[[21,[25]]]]]],[[1,16],[[3,[[21,[4]]]]]],[[]],[195,5],[[1,4],3],[[1,16],3],[[1,4,16],3],[[]],[[],13],[[],13],[[],14],[[1,4,16],3],[16,195],[[]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[],127],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[127,127],[196,196],[15,15],[181,181],[197,197],[[]],[[]],[[]],[[]],[[]],[[196,196],11],[[181,181],11],[[197,197],11],[112,[[164,[181]]]],[112,[[164,[197]]]],[127,127],[[127,15],5],[[],127],[68,[[13,[15]]]],[[],127],[[127,127],5],[[196,196],5],[[15,15],5],[[181,181],5],[[197,197],5],[[],5],[[],5],[[],5],[[],5],[[],5],[[127,138]],[[127,8],9],[[196,8],9],[[196,8],9],[[15,8],9],[[181,8],9],[[197,8],9],[[[102,[15]]],127],[[]],[[]],[[]],[[]],[[]],[138,127],[29,[[12,[15]]]],[142,[[13,[127,196]]]],[29,[[12,[181]]]],[29,197],[[29,148],181],[[[0,[44,45]],46],47],[[[0,[44,45]],46],47],[[[0,[44,45]],46],47],[[[0,[44,45]],46],47],[[127,48]],[[15,48]],[[181,48]],[[197,48]],[[127,15]],[[]],[[]],[[]],[[]],[[]],0,[127,142],[127,5],[127,72],[[],127],[[196,196],[[12,[11]]]],[[181,181],[[12,[11]]]],[[197,197],[[12,[11]]]],[100],[[127,15]],[[15,22],13],[[181,22],13],[15,127],[[]],[[]],[[]],[[]],[[]],[127,[[50,[49]]]],[15,[[50,[49]]]],[181,[[50,[49]]]],[197,[[50,[49]]]],[[],25],[142,[[13,[127,196]]]],[[],13],[[],13],[[],13],[29,[[13,[15]]]],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],14],[[],14],[[],14],[[],14],[[],14],[[]],[[]],[[]],[[]],[[]]],"c":[864],"p":[[3,"PlacesDb"],[3,"VisitObservation"],[6,"Result"],[3,"Url"],[15,"bool"],[4,"RedirectSourceType"],[3,"AddablePlaceInfo"],[3,"Formatter"],[6,"Result"],[3,"AddableVisit"],[4,"Ordering"],[4,"Option"],[4,"Result"],[3,"TypeId"],[4,"VisitType"],[15,"str"],[3,"SearchParams"],[3,"SearchResult"],[3,"Row"],[8,"AsRef"],[3,"Vec"],[8,"Serializer"],[3,"PlacesApi"],[6,"ApiResult"],[3,"String"],[4,"ConnectionType"],[8,"FnOnce"],[3,"SyncTelemetryPing"],[15,"u8"],[4,"SyncEngineId"],[8,"SyncEngine"],[3,"Box"],[3,"SharedPlacesDb"],[3,"Arc"],[3,"Path"],[3,"PlacesConnection"],[3,"OpenFlags"],[3,"Sync15StorageClientInit"],[3,"KeyBundle"],[3,"SyncResult"],[4,"SyncedBookmarkKind"],[4,"SyncedBookmarkValidity"],[4,"Kind"],[8,"Hash"],[8,"Sized"],[8,"BuildHasher"],[15,"u64"],[8,"Hasher"],[4,"ToSqlOutput"],[6,"Result"],[3,"BookmarksSyncEngine"],[3,"ServerTimestamp"],[3,"Engine"],[3,"OutgoingBso"],[6,"Result"],[6,"CollectionName"],[3,"CollectionRequest"],[4,"EngineSyncAssociation"],[3,"Guid"],[3,"IncomingBso"],[3,"BookmarkRecordId"],[3,"BookmarkRecord"],[3,"QueryRecord"],[3,"FolderRecord"],[3,"LivemarkRecord"],[3,"SeparatorRecord"],[4,"BookmarkItemRecord"],[8,"Deserializer"],[6,"UnknownFields"],[3,"PlacesTransaction"],[3,"Connection"],[15,"usize"],[3,"SqlInterruptHandle"],[3,"SqlInterruptScope"],[3,"GlobalChangeCounterTracker"],[6,"Mutex"],[3,"GLOBAL_BOOKMARK_CHANGE_COUNTERS"],[3,"AtomicI64"],[3,"HashMap"],[3,"RwLock"],[3,"PlacesInitializer"],[6,"Result"],[3,"Transaction"],[15,"u32"],[4,"PlacesApiError"],[4,"Error"],[4,"InvalidPlaceInfo"],[4,"Corruption"],[4,"InvalidMetadataObservation"],[4,"Error"],[4,"Error"],[3,"Error"],[4,"Error"],[3,"Utf8Error"],[3,"Interrupted"],[3,"Error"],[4,"ParseError"],[3,"Error"],[3,"ErrorHandling"],[3,"Demand"],[8,"Error"],[15,"slice"],[3,"PlacesTimestamp"],[15,"i64"],[6,"BookmarkItem"],[15,"i32"],[6,"InsertableBookmarkItem"],[3,"BookmarkUpdateInfo"],[3,"Duration"],[3,"HistoryVisitInfo"],[3,"HistoryVisitInfosWithBound"],[4,"ValueRef"],[4,"FromSqlError"],[15,"char"],[8,"FromIterator"],[4,"BookmarkRootGuid"],[3,"Global"],[3,"Error"],[3,"ServerVisitTimestamp"],[3,"SystemTime"],[3,"SearchResult"],[3,"HistoryHighlightWeights"],[3,"HistoryHighlight"],[3,"HistoryMetadata"],[4,"FrecencyThresholdOption"],[3,"TopFrecentSiteInfo"],[3,"VisitTransitionSet"],[4,"Host"],[4,"Position"],[3,"Range"],[3,"RangeTo"],[3,"RangeFull"],[3,"RangeFrom"],[4,"Resettable"],[3,"HistoryMetadataObservation"],[3,"ParseOptions"],[4,"Origin"],[8,"IntoIterator"],[3,"Split"],[3,"PathSegmentsMut"],[3,"HistoryMigrationResult"],[15,"u16"],[3,"Parse"],[3,"UrlQuery"],[3,"Serializer"],[3,"RunMaintenanceMetrics"],[4,"IpAddr"],[8,"Fn"],[4,"SocketAddr"],[3,"PathBuf"],[3,"Utf16CharDecoder"],[3,"Utf8CharDecoder"],[3,"FrecencySettings"],[4,"PrefixMode"],[3,"HistorySyncEngine"],[4,"Cow"],[3,"HistoryRecordVisit"],[3,"HistoryRecord"],[3,"ExecuteOnDrop"],[3,"NOW"],[3,"Context"],[3,"SearchBehavior"],[4,"MatchBehavior"],[6,"FromSqlResult"],[3,"AutocompleteMatch"],[8,"Into"],[3,"RowId"],[3,"FetchedPageInfo"],[3,"PageInfo"],[4,"BookmarkPosition"],[3,"InsertableBookmark"],[3,"InsertableSeparator"],[3,"InsertableFolder"],[4,"InsertableItem"],[4,"UpdateTreeLocation"],[3,"UpdatableBookmark"],[3,"UpdatableSeparator"],[3,"UpdatableFolder"],[4,"UpdatableItem"],[4,"BookmarkTreeNode"],[4,"BookmarkType"],[3,"BookmarkData"],[3,"Separator"],[3,"Folder"],[4,"Item"],[3,"SEARCH_QUERY"],[3,"RECENT_BOOKMARKS_QUERY"],[4,"FetchDepth"],[3,"SeparatorNode"],[3,"FolderNode"],[3,"BookmarkNode"],[3,"FetchedVisit"],[3,"FetchedVisitPage"],[4,"DocumentType"],[4,"ValidatedTag"],[3,"InvalidVisitType"],[4,"SyncStatus"],[3,"SyncState"],[13,"UnexpectedPlacesException"],[13,"UrlParseFailed"],[13,"PlacesConnectionBusy"],[13,"OperationInterrupted"],[13,"UnknownBookmarkItem"],[13,"InvalidBookmarkOperation"],[3,"Dummy"],[13,"Specific"],[13,"Bookmark"],[13,"Folder"],[13,"Separator"],[13,"Bookmark"],[13,"Folder"],[13,"Separator"],[13,"Parent"],[13,"Position"],[13,"Bookmark"],[13,"Folder"],[13,"Separator"],[13,"Bookmark"],[13,"Folder"],[13,"Separator"]]},\ +"protobuf_gen":{"doc":"","t":"DLLLMLLLFMLLL","n":["ProtobufOpts","borrow","borrow_mut","deserialize","dir","fmt","from","into","main","out_dir","try_from","try_into","type_id"],"q":[[0,"protobuf_gen"]],"d":["","","","","","","Returns the argument unchanged.","Calls U::from(self).","","","","",""],"i":[0,2,2,2,2,2,2,2,0,2,2,2,2],"f":[0,[[]],[[]],[1,[[3,[2]]]],0,[[2,4],5],[[]],[[]],[[]],0,[[],3],[[],3],[[],6]],"c":[],"p":[[8,"Deserializer"],[3,"ProtobufOpts"],[4,"Result"],[3,"Formatter"],[6,"Result"],[3,"TypeId"]]},\ +"push":{"doc":"Rust Push Component","t":"NNGNENNNNNNNNNDNEDEEDDNNNNNDDNNNNMLLLLLLLLLLLLLLLLLLLLMMMLLLLLLLLLLLLLLLLLLLMLLMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMLLLLLLLLLLMLMLLLLLLLMMMLLMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLM","n":["Adm","AlreadyRegisteredError","ApiResult","Apns","BridgeType","CommunicationError","CommunicationServerError","CryptoError","Fcm","GeneralError","Http","Https","InternalError","JSONDeserializeError","KeyInfo","OpenDatabaseError","PushApiError","PushConfiguration","PushError","PushHttpProtocol","PushManager","PushSubscriptionChanged","RecordNotFoundError","RecordNotFoundError","RequestError","StorageError","StorageSqlError","SubscriptionInfo","SubscriptionResponse","TranscodingError","UAIDNotRecognizedError","UAIDNotRecognizedError","UrlParseError","auth","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","bridge_type","channel_id","channel_id","clone","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","cmp","cmp","cmp","cmp","cmp","database_path","decrypt","default","endpoint","eq","eq","eq","eq","eq","equivalent","equivalent","equivalent","equivalent","equivalent","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from_str","get_error_handling","get_subscription","http_protocol","into","into","into","into","into","into","into","into","into","into","keys","new","p256dh","partial_cmp","partial_cmp","partial_cmp","partial_cmp","partial_cmp","provide","provide","scope","sender_id","server_host","source","subscribe","subscription_info","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_string","to_string","to_string","to_string","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","unsubscribe","unsubscribe_all","update","verify_connection","verify_connection_rate_limiter"],"q":[[0,"push"]],"d":["","Channel is already registered, generate new channelID","","","The types of supported native bridges.","A Client communication error","An error returned from the registration Server","","","An unspecified general error has occured","","","Internal Error","A failure deserializing json.","Key Information that can be used to encrypt payloads. …","Was unable to open the database","","","","","Object representing the PushManager used to manage …","An dictionary describing the push subscription that …","Record not found for the given chid","","Was unable to send request to server","An error with Storage","A failure to encode data to/from storage.","Subscription Information, the endpoint to send push …","The subscription response object returned from …","","The UAID was not recognized by the server","The UAID was not recognized by the server","A failure to parse a URL.","","","","","","","","","","","","","","","","","","","","","","bridge protocol (“fcm”)","","","","","","","","","","","","","","","","","","","","","","OS Path to the database","Decrypts a raw push message.","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","Returns the argument unchanged.","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","Retrieves an existing push subscription","http protocol (for mobile, bridged connections “https”)","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","Creates a new PushManager object, not subscribed to any …","","","","","","","","","","Sender/Application ID value","host name:port","","Subscribes to a new channel and gets the Subscription Info …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Unsubscribe from given channelID, ending that subscription …","Unsubscribe all channels for the user","Updates the Native OS push registration ID.","Verifies the connection state","Number of seconds between to rate limit the verify …"],"i":[1,17,0,1,0,17,17,17,1,17,3,3,16,17,0,17,0,0,0,0,0,0,16,17,17,17,17,0,0,17,16,17,17,4,9,1,2,3,16,17,4,5,6,7,9,1,2,3,16,17,4,5,6,7,2,6,7,1,2,3,4,5,6,7,1,2,3,4,5,6,7,1,3,4,5,6,2,9,3,5,1,3,4,5,6,1,3,4,5,6,1,1,2,3,3,16,16,17,17,4,5,6,7,9,1,2,3,16,17,17,17,17,17,17,17,17,17,4,5,6,7,3,17,9,2,9,1,2,3,16,17,4,5,6,7,5,9,4,1,3,4,5,6,16,17,7,2,2,17,9,6,1,2,3,4,5,6,7,1,3,16,17,9,1,2,3,16,17,4,5,6,7,9,1,2,3,16,17,4,5,6,7,9,1,2,3,16,17,4,5,6,7,9,9,9,9,2],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,0,[1,1],[2,2],[3,3],[4,4],[5,5],[6,6],[7,7],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[1,1],8],[[3,3],8],[[4,4],8],[[5,5],8],[[6,6],8],0,[[9,[11,[10,10]]],[[12,[0]]]],[[],3],0,[[1,1],13],[[3,3],13],[[4,4],13],[[5,5],13],[[6,6],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[1,14],15],[[1,14],15],[[2,14],15],[[3,14],15],[[3,14],15],[[16,14],15],[[16,14],15],[[17,14],15],[[17,14],15],[[4,14],15],[[5,14],15],[[6,14],15],[[7,14],15],[[]],[[]],[[]],[[]],[[]],[18,17],[[]],[19,17],[20,17],[21,17],[22,17],[23,17],[24,17],[25,17],[[]],[[]],[[]],[[]],[26,[[27,[3]]]],[17,28],[[9,26],[[12,[[29,[6]]]]]],0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,[2,[[12,[9]]]],0,[[1,1],[[29,[8]]]],[[3,3],[[29,[8]]]],[[4,4],[[29,[8]]]],[[5,5],[[29,[8]]]],[[6,6],[[29,[8]]]],[30],[30],0,0,0,[17,[[29,[31]]]],[[9,26,[29,[10]]],[[12,[6]]]],0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],10],[[],10],[[],10],[[],10],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],27],[[],32],[[],32],[[],32],[[],32],[[],32],[[],32],[[],32],[[],32],[[],32],[[],32],[[9,26],[[12,[13]]]],[9,12],[[9,26],12],[[9,13],[[12,[[33,[7]]]]]],0],"c":[],"p":[[4,"BridgeType"],[3,"PushConfiguration"],[4,"PushHttpProtocol"],[3,"KeyInfo"],[3,"SubscriptionInfo"],[3,"SubscriptionResponse"],[3,"PushSubscriptionChanged"],[4,"Ordering"],[3,"PushManager"],[3,"String"],[3,"HashMap"],[6,"ApiResult"],[15,"bool"],[3,"Formatter"],[6,"Result"],[4,"PushApiError"],[4,"PushError"],[6,"Error"],[4,"Error"],[4,"Error"],[4,"Error"],[3,"Error"],[4,"ParseError"],[4,"DecodeError"],[4,"Error"],[15,"str"],[4,"Result"],[3,"ErrorHandling"],[4,"Option"],[3,"Demand"],[8,"Error"],[3,"TypeId"],[3,"Vec"]]},\ +"rc_crypto":{"doc":"","t":"NNNNNNDENNNGNNNAALLLLLAAACAFLLLLLLLLLLLCAALLLALLAALLLLLLLLLHHDDHDDDLLLLLLLLLLLLLLLLLLLLLLLLLLLLFFLLLLLLLLLLLLLLLLLDEHHDDGDDINNDDDDLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLFFEDNNLLLLLLFLLLLLLLLLDDLLLLLLLLLLLLLLLLLLLLLLFFFDDDLLLLLLLLLLLLLLLLLLLLLLFLLLLLLLLLLLFFENNLLLLFLLLLLLLFHHDDLLLLLLLLLLLLLLLLLL","n":["CertificateChainError","CertificateContentError","CertificateIssuerError","CertificateSubjectError","CertificateValidityError","ConversionError","Error","ErrorKind","InternalError","NSSError","PEMFormatError","Result","RootHashFormatError","SignatureContentError","SignatureMismatchError","aead","agreement","backtrace","borrow","borrow","borrow_mut","borrow_mut","constant_time","contentsignature","digest","ece","ece_crypto","ensure_initialized","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","hawk","hkdf","hmac","into","into","kind","pbkdf2","provide","provide","rand","signature","source","to_string","to_string","try_from","try_from","try_into","try_into","type_id","type_id","AES_128_GCM","AES_256_GCM","Aad","Algorithm","LEGACY_SYNC_AES_256_CBC_HMAC_SHA256","Nonce","OpeningKey","SealingKey","algorithm","algorithm","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","empty","from","from","from","from","from","from","into","into","into","into","into","key_len","new","new","nonce_len","open","seal","tag_len","try_assume_unique_for_key","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","Algorithm","Curve","ECDH_P256","ECDH_P384","EcKey","Ephemeral","EphemeralKeyPair","InputKeyMaterial","KeyPair","Lifetime","P256","P384","PrivateKey","PublicKey","Static","UnparsedPublicKey","_tests_only_dangerously_convert_to_ephemeral","agree","agree_static","algorithm","algorithm","algorithm","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","bytes","clone","clone","clone_into","clone_into","compute_public_key","curve","derive","deserialize","deserialize","eq","eq","equivalent","equivalent","export","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from_coordinates","from_private_key","generate","get_field_len","import","into","into","into","into","into","into","into","into","into","into","new","new","private_key","private_key","public_key","public_key","serialize","serialize","split","to_bytes","to_owned","to_owned","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","verify_slices_are_equal","verify","Algorithm","Digest","SHA256","SHA384","algorithm","as_ref","borrow","borrow_mut","clone","clone_into","digest","encode_hex","encode_hex_upper","from","into","measure_with","to_owned","try_from","try_into","type_id","RcCryptoLocalKeyPair","RcCryptoRemotePublicKey","as_any","as_any","as_raw","borrow","borrow","borrow_mut","borrow_mut","from","from","from_raw","from_raw_components","generate_random","into","into","pub_as_raw","raw_components","try_from","try_from","try_into","try_into","type_id","type_id","expand","extract","extract_and_expand","Signature","SigningKey","VerificationKey","as_ref","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","clone","clone_into","digest_algorithm","digest_algorithm","encode_hex","encode_hex_upper","from","from","from","into","into","into","measure_with","new","new","sign","sign","to_owned","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","verify","verify_with_own_key","HashAlgorithm","SHA256","SHA384","borrow","borrow_mut","clone","clone_into","derive","fmt","from","into","to_owned","try_from","try_into","type_id","fill","ECDSA_P256_SHA256","ECDSA_P384_SHA384","UnparsedPublicKey","VerificationAlgorithm","algorithm","borrow","borrow","borrow_mut","borrow_mut","bytes","from","from","into","into","new","try_from","try_from","try_into","try_into","type_id","type_id","verify"],"q":[[0,"rc_crypto"],[59,"rc_crypto::aead"],[114,"rc_crypto::agreement"],[241,"rc_crypto::constant_time"],[242,"rc_crypto::contentsignature"],[243,"rc_crypto::digest"],[263,"rc_crypto::ece_crypto"],[287,"rc_crypto::hkdf"],[290,"rc_crypto::hmac"],[329,"rc_crypto::pbkdf2"],[344,"rc_crypto::rand"],[345,"rc_crypto::signature"]],"d":["","","","","","","","","","","","","","","","This crate provides all the cryptographic primitives …","","","","","","","","","","","","Only required to be called if you intend to use this …","","","","","Returns the argument unchanged.","","","","","Returns the argument unchanged.","","","","","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","AES-128 in GCM mode with 128-bit tags and 96 bit nonces.","AES-256 in GCM mode with 128-bit tags and 96 bit nonces.","The additional authenticated data (AAD) for an opening or …","","AES-256 in CBC mode with HMAC-SHA256 tags and 128 bit …","The nonce for an opening or sealing operation. This is a …","","","The key’s AEAD algorithm.","The key’s AEAD algorithm.","","","","","","","","","","","Construct an empty Aad.","Construct the Aad by borrowing a contiguous sequence of …","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","The length of the key.","Create a new opening key.","Create a new sealing key.","The length of the nonces.","","","The length of a tag.","","","","","","","","","","","","","","","","","A key agreement algorithm.","","","","","The key may be used at most once.","","The result of a key agreement operation, to be fed into a …","A key pair for key agreement.","How many times the key may be used.","","","A private key for key agreement.","A public key for key agreement.","The key may be used more than once.","An unparsed public key for key agreement.","The whole point of having Ephemeral and Static lifetimes …","Ephemeral agreement. This consumes self, ensuring that the …","Static agreement. This borrows self, allowing the private …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Calls kdf with the raw key material and then returns what …","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","Generate a new key pair for the given algorithm.","","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","The private key.","","The public key.","","","","Split the key pair apart.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns Ok(()) if a == b and Error otherwise. The …","Verify that the signature matches the input data.","","A calculated digest value.","","","","","","","","","Returns the digest of data using the given digest …","","","Returns the argument unchanged.","Calls U::from(self).","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","","","","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","A calculated signature value. This is a type-safe wrappper …","A key to use for HMAC signing.","A key to use for HMAC authentication.","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","Calculate the HMAC of data using key.","","","","","","","","","","","","Calculate the HMAC of data using key and verify it …","Equivalent to verify but allows the consumer to pass a …","","","","","","","","Extend passwords using pbkdf2, based on the following rfc …","","Returns the argument unchanged.","Calls U::from(self).","","","","","Fill a buffer with cryptographically secure pseudo-random …","","","An unparsed public key for signature operations.","A signature verification algorithm.","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","","","","","","","",""],"i":[5,5,5,5,5,5,0,0,5,5,5,0,5,5,5,0,0,1,5,1,5,1,0,0,0,0,0,0,5,5,1,1,5,5,5,1,1,1,1,0,0,0,5,1,1,0,5,1,0,0,5,5,1,5,1,5,1,5,1,0,0,0,0,0,0,0,0,15,17,18,23,15,17,16,18,23,15,17,16,18,18,18,23,15,17,16,18,23,15,17,16,16,15,17,16,0,0,16,23,18,23,15,17,16,18,23,15,17,16,18,23,15,17,16,0,0,0,0,0,0,0,0,0,0,33,33,0,0,0,0,26,26,26,31,29,26,27,25,39,31,29,26,30,33,32,34,27,25,39,31,29,26,30,33,32,34,29,33,34,33,34,26,34,30,33,34,33,32,33,32,26,33,34,27,25,39,39,31,29,26,30,33,32,34,34,39,39,33,26,27,25,39,31,29,26,30,33,32,34,29,34,39,34,39,34,33,34,39,31,33,34,27,25,39,31,29,26,30,33,32,34,27,25,39,31,29,26,30,33,32,34,27,25,39,31,29,26,30,33,32,34,0,0,0,0,45,45,44,44,44,44,44,44,0,44,44,44,44,44,44,44,44,44,0,0,48,50,50,48,50,48,50,48,50,50,48,48,48,50,48,48,48,50,48,50,48,50,0,0,0,0,0,0,54,53,55,54,53,55,54,54,54,53,55,54,54,53,55,54,53,55,54,54,53,55,0,53,54,53,55,54,53,55,54,53,55,54,0,0,0,45,45,45,45,45,45,0,45,45,45,45,45,45,45,0,0,0,0,0,57,58,57,58,57,57,58,57,58,57,57,58,57,58,57,58,57,57],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[1,[[4,[[3,[2]]]]]],[[]],[[]],[[]],[[]],0,0,0,0,0,[[]],[[5,6],7],[[5,6],7],[[1,6],7],[[1,6],7],[[]],[8,5],[9,5],[8,1],[5,1],[[]],[9,1],0,0,0,[[]],[[]],[1,5],0,[10],[10],0,0,[5,[[4,[11]]]],[[],12],[[],12],[[],13],[[],13],[[],13],[[],13],[[],14],[[],14],0,0,0,0,0,0,0,0,[15,16],[17,16],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],18],[[[20,[19]]],18],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[16,21],[[16,[20,[19]]],[[22,[15]]]],[[16,[20,[19]]],[[22,[17]]]],[16,21],[[15,23,18,[20,[19]]],[[22,[[24,[19]]]]]],[[17,23,18,[20,[19]]],[[22,[[24,[19]]]]]],[16,21],[[16,[20,[19]]],[[22,[23]]]],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],14],[[],14],[[],14],[[],14],[[],14],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[[26,[25]]],[[26,[27]]]],[[[26,[28]],29],[[22,[30]]]],[[[26,[25]],29],[[22,[30]]]],[31,32],[29,32],[[[26,[28]]],32],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[29,[[20,[19]]]],[33,33],[34,34],[[]],[[]],[[[26,[28]]],[[22,[31]]]],[34,33],[[30,35]],[36,[[13,[33]]]],[36,[[13,[34]]]],[[33,33],37],[[32,32],37],[[],37],[[],37],[[[26,[25]]],[[22,[34]]]],[[33,6],[[13,[38]]]],[[34,6],[[13,[38]]]],[[]],[[]],[[[26,[25]]],[[22,[[39,[25]]]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[33,[20,[19]],[20,[19]],[20,[19]]],[[13,[34,9]]]],[[[26,[28]]],[[22,[[39,[28]]]]]],[32,[[22,[[39,[28]]]]]],[33,40],[34,[[22,[[26,[25]]]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[32,[20,[19]]],29],[[33,[20,[19]],[20,[19]]],34],[[[39,[28]]],[[26,[28]]]],[34,[[20,[19]]]],[[[39,[28]]],31],[34,[[20,[19]]]],[[33,41],13],[[34,41],13],[[[39,[28]]]],[31,[[22,[[24,[19]]]]]],[[]],[[]],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[[20,[19]],[20,[19]]],22],[[[20,[19]],[20,[19]],[20,[19]],42,43,43],22],0,0,0,0,[44,45],[44,[[20,[19]]]],[[]],[[]],[44,44],[[]],[[45,[20,[19]]],[[22,[44]]]],[[],[[47,[46]]]],[[],[[47,[46]]]],[[]],[[]],[[],21],[[]],[[],13],[[],13],[[],14],0,0,[48,49],[50,49],[50,[[13,[[24,[19]],51]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[[20,[19]]],[[13,[50,51]]]],[52,[[13,[48,51]]]],[[],[[13,[48,51]]]],[[]],[[]],[48,[[13,[[24,[19]],51]]]],[48,[[13,[52,51]]]],[[],13],[[],13],[[],13],[[],13],[[],14],[[],14],[[53,[20,[19]],[20,[19]]],22],[[53,[20,[19]]],[[22,[53]]]],[[53,[20,[19]],[20,[19]],[20,[19]]],22],0,0,0,[54,[[20,[19]]]],[[]],[[]],[[]],[[]],[[]],[[]],[54,54],[[]],[53,45],[55,45],[[],[[47,[46]]]],[[],[[47,[46]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[],21],[[45,[20,[19]]],53],[[45,[20,[19]]],55],[[53,[20,[19]]],[[22,[54]]]],[[53,[20,[19]]],[[13,[[24,[19]],56]]]],[[]],[[],13],[[],13],[[],13],[[],13],[[],13],[[],13],[[],14],[[],14],[[],14],[[55,[20,[19]],[20,[19]]],22],[[53,[20,[19]],[20,[19]]],22],0,0,0,[[]],[[]],[45,45],[[]],[[[20,[19]],[20,[19]],40,45,[20,[19]]],22],[[45,6],[[13,[38]]]],[[]],[[]],[[]],[[],13],[[],13],[[],14],[[[20,[19]]],22],0,0,0,0,[57,58],[[]],[[]],[[]],[[]],[57,[[20,[19]]]],[[]],[[]],[[]],[[]],[[58,[20,[19]]],57],[[],13],[[],13],[[],13],[[],13],[[],14],[[],14],[[57,[20,[19]],[20,[19]]],22]],"c":[],"p":[[3,"Error"],[3,"Backtrace"],[3,"Mutex"],[4,"Option"],[4,"ErrorKind"],[3,"Formatter"],[6,"Result"],[3,"TryFromIntError"],[3,"Error"],[3,"Demand"],[8,"Error"],[3,"String"],[4,"Result"],[3,"TypeId"],[3,"OpeningKey"],[3,"Algorithm"],[3,"SealingKey"],[3,"Aad"],[15,"u8"],[15,"slice"],[15,"usize"],[6,"Result"],[3,"Nonce"],[3,"Vec"],[3,"Static"],[3,"PrivateKey"],[3,"Ephemeral"],[8,"Lifetime"],[3,"UnparsedPublicKey"],[3,"InputKeyMaterial"],[3,"PublicKey"],[3,"Algorithm"],[4,"Curve"],[3,"EcKey"],[8,"FnOnce"],[8,"Deserializer"],[15,"bool"],[3,"Error"],[3,"KeyPair"],[15,"u32"],[8,"Serializer"],[15,"u64"],[15,"str"],[3,"Digest"],[4,"HashAlgorithm"],[15,"char"],[8,"FromIterator"],[3,"RcCryptoLocalKeyPair"],[8,"Any"],[3,"RcCryptoRemotePublicKey"],[4,"Error"],[3,"EcKeyComponents"],[3,"SigningKey"],[3,"Signature"],[3,"VerificationKey"],[4,"CryptoError"],[3,"UnparsedPublicKey"],[3,"VerificationAlgorithm"]]},\ +"rc_log_ffi":{"doc":"This crate allows users from the other side of the FFI to …","t":"NNNENNALLLLLLLLLLFFFFFLLLLDGDLLLLLLLLLLLLLLLLLLLLL","n":["DEBUG","ERROR","INFO","LogLevel","VERBOSE","WARN","android","borrow","borrow_mut","clone","clone_into","eq","equivalent","fmt","from","from","into","rc_log_adapter_create","rc_log_adapter_destroy","rc_log_adapter_destroy_string","rc_log_adapter_set_max_level","rc_log_adapter_test__log_msg","to_owned","try_from","try_into","type_id","LogAdapterState","LogCallback","LogSink","borrow","borrow","borrow_mut","borrow_mut","drop","enabled","ffi_default","flush","from","from","init","into","into","into_ffi_value","log","try_from","try_from","try_into","try_into","type_id","type_id"],"q":[[0,"rc_log_ffi"],[26,"rc_log_ffi::android"]],"d":["","","","","","","This is the android backend for rc_log. It has a decent …","","","","","","","","","Returns the argument unchanged.","Calls U::from(self).","","Safety","Public destructor for strings managed by the other side of …","","","","","","","","Type of the log callback provided to us by java/swift. …","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","","Calls U::from(self).","Calls U::from(self).","","","","","","","",""],"i":[1,1,1,0,1,1,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,1,1,1,1,0,0,0,8,14,8,14,8,14,8,14,8,14,8,8,14,8,14,8,14,8,14,8,14],"f":[0,0,0,0,0,0,0,[[]],[[]],[1,1],[[]],[[1,1],2],[[],2],[[1,3],4],[5,1],[[]],[[]],[[6,7],8],[8],[9],[[10,7]],[11],[[]],[[],12],[[],12],[[],13],0,0,0,[[]],[[]],[[]],[[]],[8],[[14,15],2],[[],8],[14],[[]],[[]],[6,8],[[]],[[]],[8,8],[[14,16]],[[],12],[[],12],[[],12],[[],12],[[],13],[[],13]],"c":[],"p":[[4,"LogLevel"],[15,"bool"],[3,"Formatter"],[6,"Result"],[4,"Level"],[6,"LogCallback"],[3,"ExternError"],[3,"LogAdapterState"],[6,"c_char"],[15,"i32"],[3,"FfiStr"],[4,"Result"],[3,"TypeId"],[3,"LogSink"],[3,"Metadata"],[3,"Record"]]},\ +"remote_settings":{"doc":"","t":"CCCDCCCCCCCLLAAMLALLLLLLLLNDDNDDDGEMLLLLLLLLLLLLLLLLLLLLLLLLMLLLLLLLLLLLLMMLLLLLLLLLLLLLLLLLLLLLLMMLLLLLLLMMLLMLLMLLLLMMLLLLLLLLLLLLLLLLLLLLLLLLDLLMLLMLLLMLLLLNNNNENNGNLLLLLLLLLLLLLLLL","n":["Attachment","Client","GetItemsOptions","RemoteSettings","RemoteSettingsConfig","RemoteSettingsError","RemoteSettingsRecord","RemoteSettingsResponse","Result","RsJsonObject","SortOrder","borrow","borrow_mut","client","config","config","download_attachment_to_path","error","from","get_records","get_records_since","into","new","try_from","try_into","type_id","Ascending","Attachment","Client","Descending","GetItemsOptions","RemoteSettingsRecord","RemoteSettingsResponse","RsJsonObject","SortOrder","attachment","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","contains","default","deleted","deserialize","deserialize","eq","eq","eq","eq","eq","equivalent","equivalent","equivalent","equivalent","field","fields","filename","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","get_attachment","get_attachment_raw","get_records","get_records_raw","get_records_raw_with_options","get_records_since","get_records_with_options","gt","has","has_not","hash","hash","id","into","into","into","into","into","into","iter_query_pairs","last_modified","last_modified","like","limit","location","lt","max","mimetype","min","new","new","not","records","size","sort","to_owned","to_owned","to_owned","to_owned","to_owned","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","RemoteSettingsConfig","borrow","borrow_mut","bucket_name","clone","clone_into","collection_name","fmt","from","into","server_url","to_owned","try_from","try_into","type_id","AttachmentsUnsupportedError","BackoffError","FileError","JSONError","RemoteSettingsError","RequestError","ResponseError","Result","UrlParsingError","borrow","borrow_mut","fmt","fmt","from","from","from","from","from","into","provide","source","to_string","try_from","try_into","type_id"],"q":[[0,"remote_settings"],[26,"remote_settings::client"],[144,"remote_settings::config"],[159,"remote_settings::error"]],"d":["","","","","","","","","","","","","","","This module defines the custom configurations that …","","","","Returns the argument unchanged.","","","Calls U::from(self).","","","","","Smaller values first.","Attachment metadata that can be optionally attached to a […","A simple HTTP client that can retrieve Remote Settings …","Larger values first.","Options for requests to endpoints that return multiple …","A parsed Remote Settings record. Records can contain …","Data structure representing the top-level response from …","","The order in which to return items.","","","","","","","","","","","","","","","","","","","","","","","","Sets an option to only return items whose field is an …","","","","","","","","Sets an option to only return items whose field is equal …","","","","","","Sets an option to only return the given field of each item.","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Downloads an attachment from [attachment_location]. NOTE: …","Fetches a raw network Response for an attachment.","Fetches all records for a collection that can be found in …","Fetches all records for a collection that can be found in …","Fetches a raw network Response for records from this client…","Fetches all records that have been published since …","Fetches records from this client’s collection with the …","Sets an option to only return items whose field is …","Sets an option to only return items that have the given …","Sets an option to only return items that do not have the …","","","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Returns an iterator of (name, value) query pairs for these …","","","Sets an option to only return items whose field is a …","Sets the option to return at most count items.","","Sets an option to only return items whose field is …","Sets an option to only return items whose field is less …","","Sets an option to only return items whose field is greater …","Create a new Client with properties matching config.","Creates an empty option set.","Sets an option to only return items whose field is not …","","","Sets an option to return items in order for the given field…","","","","","","","","","","","","","","","","","","","","","","","","Custom configuration for the client. Currently includes …","","","","","","","","Returns the argument unchanged.","Calls U::from(self).","","","","","","","The server has asked the client to backoff.","","","","An error has occured while sending a request.","The server returned an error code or the response was …","","An error has occured while parsing an URL.","","","","","Returns the argument unchanged.","","","","","Calls U::from(self).","","","","","",""],"i":[0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,1,1,0,1,1,1,1,1,1,1,1,12,0,0,12,0,0,0,0,0,9,18,4,9,10,11,12,18,4,9,10,11,12,4,9,10,11,12,4,9,10,11,12,11,11,9,9,10,4,9,10,11,12,4,9,10,12,11,9,10,4,9,10,11,12,18,4,9,10,11,12,18,18,18,18,18,18,18,11,11,11,12,10,9,18,4,9,10,11,12,11,4,9,11,11,10,11,11,10,11,18,11,11,4,10,11,4,9,10,11,12,18,4,9,10,11,12,18,4,9,10,11,12,18,4,9,10,11,12,0,6,6,6,6,6,6,6,6,6,6,6,6,6,6,25,25,25,25,0,25,25,0,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25],"f":[0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],0,0,0,[[1,2,2],3],0,[[]],[1,[[3,[4]]]],[[1,5],[[3,[4]]]],[[]],[6,[[3,[1]]]],[[],7],[[],7],[[],8],0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[4,4],[9,9],[10,10],[11,11],[12,12],[[]],[[]],[[]],[[]],[[]],[[11,[13,[2]],[13,[2]]],11],[[],11],0,[14,[[7,[9]]]],[14,[[7,[10]]]],[[4,4],15],[[9,9],15],[[10,10],15],[[11,[13,[2]],[13,[2]]],11],[[12,12],15],[[],15],[[],15],[[],15],[[],15],[[11,[13,[2]]],11],0,0,[[4,16],17],[[9,16],17],[[10,16],17],[[11,16],17],[[12,16],17],[[]],[[]],[[]],[[]],[[]],[[]],[[18,19],[[3,[[21,[20]]]]]],[[18,19],[[3,[22]]]],[18,[[3,[4]]]],[18,[[3,[22]]]],[[18,11],[[3,[22]]]],[[18,5],[[3,[4]]]],[[18,11],[[3,[4]]]],[[11,[13,[2]],[13,[2]]],11],[[11,[13,[2]]],11],[[11,[13,[2]]],11],[[12,23]],0,0,[[]],[[]],[[]],[[]],[[]],[[]],[11,24],0,0,[[11,[13,[2]],[13,[2]]],11],[[11,5],11],0,[[11,[13,[2]],[13,[2]]],11],[[11,[13,[2]],[13,[2]]],11],0,[[11,[13,[2]],[13,[2]]],11],[6,[[3,[18]]]],[[],11],[[11,[13,[2]],[13,[2]]],11],0,0,[[11,[13,[2]],12],11],[[]],[[]],[[]],[[]],[[]],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],7],[[],8],[[],8],[[],8],[[],8],[[],8],[[],8],0,[[]],[[]],0,[6,6],[[]],0,[[6,16],17],[[]],[[]],0,[[]],[[],7],[[],7],[[],8],0,0,0,0,0,0,0,0,0,[[]],[[]],[[25,16],17],[[25,16],17],[[]],[26,25],[27,25],[28,25],[29,25],[[]],[30],[25,[[32,[31]]]],[[],2],[[],7],[[],7],[[],8]],"c":[],"p":[[3,"RemoteSettings"],[3,"String"],[6,"Result"],[3,"RemoteSettingsResponse"],[15,"u64"],[3,"RemoteSettingsConfig"],[4,"Result"],[3,"TypeId"],[3,"RemoteSettingsRecord"],[3,"Attachment"],[3,"GetItemsOptions"],[4,"SortOrder"],[8,"Into"],[8,"Deserializer"],[15,"bool"],[3,"Formatter"],[6,"Result"],[3,"Client"],[15,"str"],[15,"u8"],[3,"Vec"],[3,"Response"],[8,"Hasher"],[8,"Iterator"],[4,"RemoteSettingsError"],[4,"Error"],[4,"ParseError"],[3,"Error"],[3,"Error"],[3,"Demand"],[8,"Error"],[4,"Option"]]},\ +"restmail_client":{"doc":"","t":"FF","n":["clear_mailbox","find_email"],"q":[[0,"restmail_client"]],"d":["","For a given restmail email, find the first email that …"],"i":[0,0],"f":[[1,[[2,[0]]]],[[1,3,4],[[2,[5,0]]]]],"c":[],"p":[[15,"str"],[4,"Result"],[8,"Fn"],[15,"u8"],[4,"Value"]]},\ +"sql_support":{"doc":"A crate with various sql/sqlcipher helpers.","t":"NDIEDNDLLLLLLLLLLLKLMAFLLLLFFFFFLLLLLLLLMLLLLLLLLLLLLLLLALLLLLLLLLLLLLLLFFFFLLLMLLLLLLLLLLLLLLLLLLLLLLFFINSENSNGNLLLLLLLKLFFFFLLALLLLKDLLMLLLLLMLLLLL","n":["Cached","Conn","ConnExt","MaybeCached","RepeatDisplay","Uncached","UncheckedTransaction","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone_into","commit","conn","conn","conn","debug_tools","default_max_variable_number","deref","deref","deref_mut","drop","each_chunk","each_chunk_mapped","each_sized_chunk","each_sized_chunk_mapped","escape_string_for_pragma","execute_all","execute_all","execute_cached","execute_cached","execute_one","execute_one","exists","exists","finished","fmt","fmt","from","from","from","from","from","from","get_db_size","get_db_size","into","into","into","into","new","open_database","prepare","prepare_maybe_cached","prepare_maybe_cached","query_one","query_one","query_row_and_then_cachable","query_row_and_then_cachable","query_rows_and_then","query_rows_and_then","query_rows_and_then_cached","query_rows_and_then_cached","query_rows_into","query_rows_into","query_rows_into_cached","query_rows_into_cached","repeat_display","repeat_multi_values","repeat_sql_values","repeat_sql_vars","rollback","set_pragma","set_pragma","started_at","to_owned","to_string","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_query_one","try_query_one","try_query_row","try_query_row","type_id","type_id","type_id","type_id","unchecked_transaction","unchecked_transaction","unchecked_transaction_imm","unchecked_transaction_imm","define_debug_functions","print_query","ConnectionInitializer","Corrupt","END_VERSION","Error","IncompatibleVersion","NAME","RecoveryError","Result","SqlError","borrow","borrow_mut","finish","fmt","fmt","from","from","init","into","open_database","open_database_with_flags","open_memory_database","open_memory_database_with_flags","prepare","provide","test_utils","to_string","try_from","try_into","type_id","upgrade_from","MigratedDatabaseFile","borrow","borrow_mut","connection_initializer","from","into","new","new_with_flags","open","path","run_all_upgrades","try_from","try_into","type_id","upgrade_to"],"q":[[0,"sql_support"],[102,"sql_support::debug_tools"],[104,"sql_support::open_database"],[134,"sql_support::open_database::test_utils"]],"d":["","","This trait exists so that we can use these helpers on …","MaybeCached is a type that can be used to help abstract …","Helper type for printing repeated strings more …","","rusqlite, in an attempt to save us from ourselves, needs a …","","","","","","","","","","","Consumes and commits an unchecked transaction.","The method you need to implement to opt in to all of this.","","","","Returns SQLITE_LIMIT_VARIABLE_NUMBER as read from an …","","","","","Helper for the case where you have a &[impl ToSql] of …","A version of each_chunk for the case when the conversion …","","Utility to help perform batched updates, inserts, queries, …","In PRAGMA foo=‘bar’, 'bar' must be a constant string …","Execute all the provided statements.","Execute all the provided statements.","Equivalent to Connection::execute but caches the statement …","Equivalent to Connection::execute but caches the statement …","Execute a single statement.","Execute a single statement.","Return true if a query returns any rows","Return true if a query returns any rows","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","Returns the argument unchanged.","Get the DB size in bytes","Get the DB size in bytes","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Begin a new unchecked transaction. Cannot be nested, but …","","","Get a cached or uncached statement based on a flag.","Get a cached or uncached statement based on a flag.","Execute a query that returns a single result column, and …","Execute a query that returns a single result column, and …","Equivalent to rusqlite::Connection::query_row_and_then but …","Equivalent to rusqlite::Connection::query_row_and_then but …","Helper for when you’d like to get a Vec<T> of all the …","Helper for when you’d like to get a Vec<T> of all the …","Helper for when you’d like to get a Vec<T> of all the …","Helper for when you’d like to get a Vec<T> of all the …","Like query_rows_and_then_cachable, but works if you want a …","Like query_rows_and_then_cachable, but works if you want a …","Same as query_rows_into, but caches the stmt if possible.","Same as query_rows_into, but caches the stmt if possible.","Construct a RepeatDisplay that will repeatedly call fmt_one…","Returns a value that formats as num_values instances of …","Returns a value that formats as count instances of (?) …","Returns a value that formats as count instances of ? …","Consumes and rolls back an unchecked transaction.","Set the value of the pragma on the main database. Returns …","Set the value of the pragma on the main database. Returns …","","","","","","","","","","","","Execute a query that returns 0 or 1 result columns, …","Execute a query that returns 0 or 1 result columns, …","Like query_row_and_then_cachable but returns None instead …","Like query_row_and_then_cachable but returns None instead …","","","","","Caveat: This won’t actually get used most of the time, …","Caveat: This won’t actually get used most of the time, …","Begin unchecked_transaction with …","Begin unchecked_transaction with …","You can call this function to add all sql functions …","Print the entire contents of an arbitrary query. A common …","","","","","","","","","","","","","","","Returns the argument unchanged.","","","Calls U::from(self).","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Calls U::from(self).","","","","","","","","",""],"i":[7,0,0,0,0,7,0,43,3,7,2,43,3,7,2,2,2,3,44,3,3,0,0,3,7,7,3,0,0,0,0,0,44,44,44,44,44,44,44,44,3,2,2,43,3,7,7,7,2,44,44,43,3,7,2,3,0,7,44,44,44,44,44,44,44,44,44,44,44,44,44,44,0,0,0,0,3,44,44,3,2,2,43,3,7,2,43,3,7,2,44,44,44,44,43,3,7,2,44,44,44,44,0,0,0,35,39,0,35,39,35,0,35,35,35,39,35,35,35,35,39,35,0,0,0,0,39,35,0,35,35,35,35,39,0,42,42,42,42,42,42,42,42,42,42,42,42,42,42],"f":[0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[[2,[1]]],[[2,[1]]]],[[]],[3,4],[[],5],[3,5],0,0,[[],6],[3,5],[7,8],[7,8],[3],[[9,10],11],[[9,12,10],11],[[9,6,10],11],[[9,6,12,10],11],[13,14],[[[9,[13]]],4],[[[9,[13]]],4],[[13,15],[[4,[6]]]],[[13,15],[[4,[6]]]],[13,4],[13,4],[[13,15],[[4,[16]]]],[[13,15],[[4,[16]]]],0,[[[2,[12]],17],18],[[[2,[19]],17],18],[[]],[[]],[[]],[8,7],[20,7],[[]],[[],[[11,[21,22]]]],[[],[[11,[21,22]]]],[[]],[[]],[[]],[[]],[[5,23],[[4,[3]]]],0,[[5,13,16],[[4,[7]]]],[[13,16],[[4,[7]]]],[[13,16],[[4,[7]]]],[13,[[4,[24]]]],[13,[[4,[24]]]],[[25,13,15,26,16],[[11,[[27,[22]]]]]],[[25,13,15,26,16],[[11,[[27,[22]]]]]],[[25,13,15,10],[[11,[28,[27,[22]]]]]],[[25,13,15,10],[[11,[28,[27,[22]]]]]],[[25,13,15,10],[[11,[28,[27,[22]]]]]],[[25,13,15,10],[[11,[28,[27,[22]]]]]],[[25,13,15,10],[[11,[29,[27,[22]]]]]],[[25,13,15,10],[[11,[29,[27,[22]]]]]],[[25,13,15,10],[[11,[29,[27,[22]]]]]],[[25,13,15,10],[[11,[29,[27,[22]]]]]],[[6,13,12],[[2,[12]]]],[[6,6],30],[6,30],[6,30],[3,4],[[25,13,31],[[4,[25]]]],[[25,13,31],[[4,[25]]]],0,[[]],[[],14],[[],11],[[],11],[[],11],[[],11],[[],11],[[],11],[[],11],[[],11],[[25,13,15,16],[[4,[[32,[24]]]]]],[[25,13,15,16],[[4,[[32,[24]]]]]],[[25,13,15,26,16],[[11,[32,[27,[22]]]]]],[[25,13,15,26,16],[[11,[32,[27,[22]]]]]],[[],33],[[],33],[[],33],[[],33],[[],[[4,[3]]]],[[],[[4,[3]]]],[[],[[4,[3]]]],[[],[[4,[3]]]],[5,4],[[5,13],4],0,0,0,0,0,0,0,0,0,[[]],[[]],[5,34],[[35,17],18],[[35,17],18],[[]],[22,35],[36,34],[[]],[[[38,[37]],39],[[34,[5]]]],[[[38,[37]],40,39],[[34,[5]]]],[39,[[34,[5]]]],[[40,39],[[34,[5]]]],[[5,16],34],[41],0,[[],14],[[],11],[[],11],[[],33],[[36,21],34],0,[[]],[[]],0,[[]],[[]],[[39,13],[[42,[39]]]],[[39,13,40],[[42,[39]]]],[[[42,[39]]],5],0,[[[42,[39]]]],[[],11],[[],11],[[],33],[[[42,[39]],21]]],"c":[],"p":[[8,"Clone"],[3,"RepeatDisplay"],[3,"UncheckedTransaction"],[6,"Result"],[3,"Connection"],[15,"usize"],[4,"MaybeCached"],[3,"Statement"],[15,"slice"],[8,"FnMut"],[4,"Result"],[8,"Fn"],[15,"str"],[3,"String"],[8,"Params"],[15,"bool"],[3,"Formatter"],[6,"Result"],[8,"Debug"],[3,"CachedStatement"],[15,"u32"],[4,"Error"],[4,"TransactionBehavior"],[8,"FromSql"],[8,"Sized"],[8,"FnOnce"],[8,"From"],[3,"Vec"],[8,"FromIterator"],[8,"Display"],[8,"ToSql"],[4,"Option"],[3,"TypeId"],[6,"Result"],[4,"Error"],[3,"Transaction"],[3,"Path"],[8,"AsRef"],[8,"ConnectionInitializer"],[3,"OpenFlags"],[3,"Demand"],[3,"MigratedDatabaseFile"],[3,"Conn"],[8,"ConnExt"]]},\ +"sync15":{"doc":"","t":"NNNNDNGNNESDEDNNNNDNNNNNDNGNDNNNNNNNNNNNNLLLLLLLLLLLLLLLLLLLLLAMAALLLLLLLLLLLLLLLLLLLLLLLLLLLLLMMLLLLLLLLLALLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMLLLLLLLLMLLLLLLLLLLLLLLMMLLLLLLLLMLLLLLLLLALLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNDDDDENDDDNLLLLLLLLLLLLLLLLLLLLLLLLLMMMMMLLLLLLLLLLLLLLLLLLLLLMMLLLLLLLLLLLLMMLLLLLLMMLLLLMMALLLLLLLLLLLLLLLLLLLLLMMLLLLLLLLLLLLLLLLNNNNDNNNNEINEDDDDMLLLLLLLLLLLLLLLLLLLLLLLMLLMMLLLLKLKLKLKLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMMLLMLLKLKLMMLFFMLLLMLLLLLLLLLLLLLLLLLLLLLLLLLLLLKLMMMMNEIEDNNNDNNKLLLLLLLLLLLLLLLMMMLLLLLLKLLLLLLLMLLLLLLLMLLLLLLLMKLLLLLLLLLLLLLLLLLLLLNDNIIDDNNNENNNNNEIENKKLLLLLLLLLLLLLLLLLLLLLLLLLMMKLLKKLLLLLLLLLLLLLLLLLLLLLLLLLLMKLLKMLLLMLLLLLLLKKLMLLLMLMMLLLLLLLMKKKKKLLKKKKKLLKKLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKNDDDDNNDNEDDNDLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMMMM","n":["BackoffError","BadCleartextUtf8","BadKeyLength","Base64Decode","ClientData","ClientUpgradeRequired","CollectionName","CryptoError","Desktop","DeviceType","EPOCH","EncryptedPayload","Error","Guid","HawkError","HmacMismatch","Interrupted","JsonError","KeyBundle","MalformedUrl","MissingServerTimestamp","Mobile","RecordTooLargeError","RecordUploadFailed","RemoteClient","RequestError","Result","ServerBatchProblem","ServerTimestamp","SetupRace","SetupRequired","StorageHttpError","StorageResetError","StoreError","TV","Tablet","TokenserverHttpError","UnacceptableUrl","UnexpectedStatus","Unknown","VR","as_bytes","as_millis","as_ref","as_ref","as_str","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","bso","ciphertext","client","clients_engine","clone","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","cmp","column_result","decrypt","decrypt","decrypt_into","default","default","default","deref","deserialize","deserialize","deserialize","deserialize","deserialize","deserialize","device_name","device_type","duration_since","empty","encode_hex","encode_hex_upper","encrypt_bytes_rand_iv","encrypt_bytes_with_iv","encrypt_rand_iv","encrypt_with_iv","encryption_key","engine","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","equivalent","equivalent","equivalent","equivalent","equivalent","equivalent","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from_base64","from_cleartext","from_cleartext_payload","from_float_seconds","from_ksync_base64","from_ksync_bytes","from_millis","from_slice","from_str","from_string","from_vec","fxa_device_id","get_hash","get_hash","get_hash","get_hash","hash","hash","hash","hash","hmac","hmac_key","into","into","into","into","into","into","into","into","into_resettable","into_string","is_valid_for_places","is_valid_for_sync_server","is_valid_places_byte","iv","local_client_id","measure_with","new","new","new_random","partial_cmp","partial_cmp","provide","random","recent_clients","serialize","serialize","serialize","serialize","serialize","serialize","serialized_len","source","telemetry","to_b64_array","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_sql","to_string","to_string","to_string","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","vzip","vzip","vzip","vzip","vzip","vzip","vzip","vzip","Content","IncomingBso","IncomingContent","IncomingEncryptedBso","IncomingEnvelope","IncomingKind","Malformed","OutgoingBso","OutgoingEncryptedBso","OutgoingEnvelope","Tombstone","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone_into","clone_into","content","default","deserialize","deserialize","deserialize","envelope","envelope","envelope","envelope","envelope","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from_content","from_content_with_id","from_test_content","from_test_content_ts","id","id","into","into","into","into","into","into","into","into","into_content","into_content_with_fixup","into_decrypted","into_encrypted","kind","modified","new","new","new","new","new_test_tombstone","new_tombstone","payload","payload","serialize","serialize","serialize","serialized_payload_len","sortindex","sortindex","test_utils","to_owned","to_owned","to_test_incoming","to_test_incoming_t","to_test_incoming_ts","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","ttl","ttl","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","vzip","vzip","vzip","vzip","vzip","vzip","vzip","vzip","AuthenticationError","BackedOff","Error","Interrupted","MemoryCachedState","NetworkError","Ok","OtherError","ServiceError","ServiceStatus","SetupStorageClient","Success","Sync15ClientResponse","Sync15StorageClient","Sync15StorageClientInit","SyncRequestInfo","SyncResult","access_token","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clear_sensitive_info","clone","clone","clone","clone_into","clone_into","clone_into","cmp","create_storage_error","declined","default","default","engine_results","engines_to_state_change","eq","eq","equivalent","equivalent","fetch_crypto_keys","fetch_crypto_keys","fetch_info_collections","fetch_info_collections","fetch_info_configuration","fetch_info_configuration","fetch_meta_global","fetch_meta_global","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from_err","from_response","get_encrypted_records","get_hash","get_next_sync_after","hash","hashed_uid","into","into","into","into","into","into","into","is_user_action","key_id","new","new_post_queue","next_sync_after","note_client_refresh","partial_cmp","put_crypto_keys","put_crypto_keys","put_meta_global","put_meta_global","result","service_status","should_refresh_client","sync_multiple","sync_multiple_with_command_processor","telemetry","to_owned","to_owned","to_owned","tokenserver_url","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","vzip","vzip","vzip","vzip","vzip","vzip","vzip","wipe_all_remote","wipe_all_remote","last_modified","record","route","status","Applied","Command","CommandProcessor","CommandStatus","Engine","Ignored","Reset","ResetAll","Settings","Unsupported","Wipe","apply_incoming_command","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","clone_into","clone_into","clone_into","cmp","command_processor","device_name","device_type","eq","eq","eq","equivalent","equivalent","equivalent","fetch_outgoing_commands","fmt","fmt","fmt","from","from","from","from","fxa_device_id","get_client_data","get_hash","get_hash","get_hash","hash","hash","hash","interruptee","into","into","into","into","local_client_id","new","partial_cmp","recent_clients","settings","sync","to_owned","to_owned","to_owned","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","vzip","vzip","vzip","vzip","Addresses","ApplyResults","Bookmarks","BridgedEngine","BridgedEngineAdaptor","CollSyncIds","CollectionRequest","Connected","CreditCards","Disconnected","EngineSyncAssociation","History","Index","Newest","Oldest","Passwords","RequestOrder","SyncEngine","SyncEngineId","Tabs","apply","apply","as_str","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","cmp","cmp","coll","collection","collection_name","default","default","engine","ensure_current_sync_id","eq","eq","eq","eq","eq","equivalent","equivalent","equivalent","equivalent","equivalent","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","full","full","get_collection_request","get_hash","get_hash","get_sync_assoc","global","hash","hash","ids","ids","into","into","into","into","into","into","iter","last_sync","last_sync","limit","limit","name","new","new","newer","newer_than","num_reconciled","older","older_than","partial_cmp","partial_cmp","prepare_for_sync","prepare_for_sync","prepare_for_sync","prepare_for_sync","records","reset","reset","reset_sync_id","set_last_sync","set_last_sync","set_local_encryption_key","set_local_encryption_key","set_uploaded","set_uploaded","stage_incoming","store_incoming","sync_finished","sync_finished","sync_finished","sync_id","sync_started","sync_started","sync_started","to_owned","to_owned","to_owned","to_owned","to_owned","to_string","to_string","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","vzip","vzip","vzip","vzip","vzip","vzip","wipe","wipe","Auth","Engine","EngineIncoming","EngineOutgoing","Event","Http","Other","Problem","Shutdown","SyncFailure","SyncTelemetry","SyncTelemetryPing","Unexpected","Validation","applied","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","default","default","default","default","default","default","engine","event","extra","failed","failed","failure","failure","ffi_default","finished","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","get_applied","get_failed","get_incoming","get_new_failed","get_reconciled","incoming","into","into","into","into","into","into","into","into","into","into_ffi_value","new","new","new","new","new","new","new_failed","outgoing","problem","reconciled","sent","serialize","serialize","serialize","serialize","serialize","serialize","serialize","serialize","serialize","sync","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","uid","validation","value","vzip","vzip","vzip","vzip","vzip","vzip","vzip","vzip","vzip","with_version","code","error","error","from"],"q":[[0,"sync15"],[258,"sync15::bso"],[390,"sync15::client"],[527,"sync15::client::Sync15ClientResponse"],[531,"sync15::clients_engine"],[613,"sync15::engine"],[781,"sync15::telemetry"],[925,"sync15::telemetry::SyncFailure"]],"d":["","","","","Argument to Store::prepare_for_sync. See comment there for …","","","","","Enumeration for the different types of device.","","A representation of an encrypted payload. Used as the …","","This is a type intended to be used to represent the guids …","","","","","","","","","","","Information about a remote client in the clients …","","","","Typesafe way to manage server timestamps without …","","","","Used for things like a node reassignment or an unexpected …","","","","","","","","","Get the data backing this Guid as a &[u8].","Get the milliseconds for the timestamp.","","","Get the data backing this Guid as a &str.","","","","","","","","","","","","","","","","","","","A module for everything needed to be a “sync client” - …","The client engine is a crate::engine(Sync Engine) used to …","","","","","","","","","","","","","","","","","","Decrypt the provided ciphertext with the given iv, and …","","Create a default guid by calling Guid::empty()","","","","","","","","","","","","Returns None if other is later than self (Duration may not …","Create an empty guid. Usable as a constant.","","","Generate a random iv and encrypt with it. Return both the …","Encrypt using the provided IV.","","","","This module is used by crates which need to implement a “…","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","Returns the argument unchanged.","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","","","","","","","","Convert b into a Guid.","","Convert b into a Guid.","Convert v to a Guid, consuming it.","","","","","","","","","","","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","Convert this Guid into a String, consuming it in the …","Returns true for Guids that are valid places guids, and …","Returns true for Guids that are deemed valid by the sync …","Returns true if the byte b is a valid base64url byte.","","","","Create a guid from a str.","Construct a key bundle from the already-decoded encrypt …","","","","","Create a random guid (of 12 base64url characters). …","A hashmap of records in the clients collection. Key is the …","","","","","","","","","Manage recording sync telemetry. Assumes some external …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","A good, live T.","IncomingBso’s can come from:","We also have the concept of “content”, which helps …","","An envelope for an incoming item. Envelopes carry all the …","The “kind” of incoming content after deserializing it.","Either not JSON, or can’t be made into a T.","","","An envelope for an outgoing item. This is conceptually …","A record that used to be a T but has been replaced with a …","","","","","","","","","","","","","","","","","","","","","Returns Some(content) if [self.kind] is …","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Create an Outgoing record with an explicit envelope. Will …","Creates a outgoing record from some , which can be made …","When a test has an T and wants it as an IncomingBso","When a test has an T and wants it as an IncomingBso with a …","The ID of the record.","The ID of the record.","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Convert an IncomingBso to an IncomingContent possibly …","Like into_content, but adds an additional fixup step where …","Decrypt a BSO, consuming it into a clear-text version.","","","","","","","Most consumers will use self.from_content and …","When a test wants a new incoming tombstone.","Creates a new tombstone record. Not all collections expect …","","","","","","","","","Utilities for tests to make IncomingBsos and Content from …","","","When a test has an OutgoingBso and wants it as an …","When a test has an OutgoingBso and wants it as an …","When a test has an OutgoingBso and wants it as an …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Some external FxA action needs to be taken.","We declined to do anything for backoff or rate-limiting …","","We were interrupted.","Info we want callers to engine in memory for us so that …","Some general network issue.","Everything is fine.","Something else - you need to check the logs for more …","Some apparent issue with the servers.","The general status of sync - should probably be moved to …","A trait containing the methods required to run through the …","","A response from a GET request on a Sync15StorageClient, …","","","This is essentially a bag of information that the sync …","The result of a sync request. This too is from the “sync …","","","","","","","","","","","","","","","","","","","","","","","","","The set of declined engines, if we know them.","","","The result for each engine. Note that we expect the String …","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","","","","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","The result of the sync.","The general health.","","Sync multiple engines","Like sync_multiple, but specifies an optional command …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","A command processor applies incoming commands like wipes …","Indicates if a command was applied successfully, ignored, …","","","Resets local sync state for a specific engine.","Resets local sync state for all engines.","Information about this device to include in its client …","","Erases all local data for a specific engine.","Applies a command sent to this client from another client. …","","","","","","","","","","","","","","","","","The name of this client. This should match the client’s …","The type of this client: mobile, tablet, desktop, or other.","","","","","","","Fetches commands to send to other clients. An error return …","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","The FxA device ID of this client, also used as this client…","","","","","","","","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","Creates a new clients engine that delegates to the given …","","","","Syncs the clients collection. This works a little …","","","","","","","","","","","","","","","","","","","","","","","A BridgedEngine acts as a bridge between …","","","","Sync is connected, and has the following sync IDs.","","This store is disconnected (although it may be connected …","Defines how an engine is associated with a particular set …","","","","","","","A “sync engine” is a thing that knows how to sync. It…","The concrete SyncEngine implementations","","Applies all staged records, reconciling changes on both …","Apply the staged records, returning outgoing records. …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Ensures that the locally stored sync ID for this engine’…","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","The engine is responsible for building a single collection …","","","Get persisted sync IDs. If they don’t match the global …","","","","","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","Returns the last sync time, in milliseconds, for this …","","","","","","","","","The number of incoming records whose contents were merged …","","","","","Tells the tabs engine about recent FxA devices. A bit of a …","Tells the tabs engine about recent FxA devices. A bit of a …","Prepares the engine for syncing. The tabs engine currently …","Prepares the engine for syncing. The tabs engine currently …","List of records","Resets all local Sync state, including any change flags, …","Reset the engine (and associated store) without wiping …","Resets the sync ID for this engine’s collection, …","Sets the last sync time, in milliseconds. This is called …","","Tells the engine what the local encryption key is for the …","Tells the engine what the local encryption key is for the …","Indicates that the given record IDs were uploaded …","Indicates that the given record IDs were uploaded …","Stage some incoming records. This might be called multiple …","Stages a batch of incoming Sync records. This is called …","Indicates that all records have been uploaded. At this …","Called once the sync is finished. Not currently called if …","Called once the sync is finished. Not currently called if …","Returns the sync ID for this engine’s collection. This …","Indicates that the engine is about to start syncing. This …","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Erases all local user data for this collection, and any …","","","One engine’s sync.","Incoming record for an engine’s sync","Outgoing record for an engine’s sync.","A generic “Event” - suitable for all kinds of pings …","","","","","A Sync failure.","A single sync. May have many engines, may have its own …","The Sync ping payload, as documented at …","","","Increment the value of applied by n.","","","","","","","","","","","","","","","","","","","","","","","","","","","","Increment the value of failed by n.","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Get the value of applied. Mostly useful for testing.","Get the value of failed. Mostly useful for testing.","","Get the value of new_failed. Mostly useful for testing.","Get the value of reconciled. Mostly useful for testing.","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","","Increment the value of new_failed by n.","","","Increment the value of reconciled by n.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""],"i":[30,30,30,30,0,30,0,30,7,0,4,0,0,0,30,30,30,30,0,30,30,7,30,30,0,30,0,30,0,30,30,30,30,30,7,7,30,30,30,7,7,1,4,1,1,1,1,7,8,9,10,30,11,4,1,7,8,9,10,30,11,4,0,10,0,0,1,7,8,9,10,11,4,1,7,8,9,10,11,4,1,1,10,11,10,1,7,4,1,1,7,8,9,10,4,9,9,4,1,1,1,11,11,11,11,11,0,1,1,1,1,1,1,1,7,8,9,11,4,1,7,8,9,11,4,1,1,7,8,9,10,30,30,11,4,4,1,1,1,1,1,1,7,8,9,10,30,30,30,30,30,30,30,30,30,30,30,11,4,11,10,10,4,11,11,4,1,4,1,1,9,1,7,9,11,1,7,9,11,10,11,1,7,8,9,10,30,11,4,1,1,1,1,1,10,8,1,1,11,11,1,4,30,1,8,1,7,8,9,10,4,10,30,0,11,1,7,8,9,10,11,4,1,1,30,4,1,7,8,9,10,30,11,4,1,7,8,9,10,30,11,4,1,7,8,9,10,30,11,4,1,7,8,9,10,30,11,4,63,0,0,0,0,0,63,0,0,0,63,63,60,64,57,58,61,65,59,63,60,64,57,58,61,65,59,57,58,57,58,59,58,60,57,61,60,64,61,65,59,63,60,64,57,58,61,65,59,63,60,64,57,58,58,61,65,59,65,65,61,61,57,58,63,60,64,57,58,61,65,59,61,61,60,65,59,57,60,64,61,65,61,65,61,65,64,58,65,64,57,58,0,57,58,65,65,65,63,60,64,57,58,61,65,59,63,60,64,57,58,61,65,59,57,58,63,60,64,57,58,61,65,59,63,60,64,57,58,61,65,59,68,68,70,68,0,68,68,68,68,0,0,70,0,0,0,0,0,71,68,74,70,71,73,67,72,68,74,70,71,73,67,72,67,68,70,71,68,70,71,71,70,74,67,72,74,72,68,71,68,71,109,73,109,73,109,73,109,73,68,74,70,71,73,67,72,68,74,70,71,73,67,72,68,70,73,71,67,71,73,68,74,70,71,73,67,72,72,71,73,73,74,67,71,109,73,109,73,74,74,67,0,0,74,68,70,71,71,68,74,70,71,73,67,72,68,74,70,71,73,67,72,68,74,70,71,73,67,72,68,74,70,71,73,67,72,109,73,110,110,110,110,84,0,0,0,0,84,83,83,0,84,83,82,88,84,86,83,88,84,86,83,84,86,83,84,86,83,83,88,86,86,84,86,83,84,86,83,82,84,86,83,88,84,86,83,86,88,84,86,83,84,86,83,88,88,84,86,83,88,88,83,88,82,88,84,86,83,88,84,86,83,88,84,86,83,88,84,86,83,88,84,86,83,94,0,94,0,0,0,0,93,94,93,0,94,91,91,91,94,0,0,0,94,111,80,91,89,77,91,92,93,94,89,77,91,92,93,94,77,91,92,93,94,77,91,92,93,94,91,94,92,77,80,89,77,112,111,77,91,92,93,94,77,91,92,93,94,89,77,91,91,92,93,94,94,89,89,77,91,92,93,94,77,77,80,91,94,80,92,91,94,77,77,89,77,91,92,93,94,94,111,112,77,77,94,89,77,77,77,89,77,77,91,94,111,111,80,80,89,111,80,111,111,112,80,80,111,80,80,111,111,80,80,111,111,112,112,77,91,92,93,94,91,94,89,77,91,92,93,94,94,89,77,91,92,93,94,89,77,91,92,93,94,89,77,91,92,93,94,111,80,107,0,0,0,0,107,107,0,107,0,0,0,107,0,99,106,107,99,101,90,102,103,104,105,106,107,99,101,90,102,103,104,105,99,101,102,103,104,105,104,105,106,99,101,90,104,105,104,106,107,99,101,90,102,103,104,105,106,107,107,99,101,90,102,103,104,105,99,99,90,99,99,90,106,107,99,101,90,102,103,104,105,105,106,99,101,90,104,105,99,90,102,99,101,106,107,99,101,90,102,103,104,105,105,106,107,99,101,90,102,103,104,105,106,107,99,101,90,102,103,104,105,106,107,99,101,90,102,103,104,105,105,90,106,106,107,99,101,90,102,103,104,105,102,113,114,115,116],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[1,[[3,[2]]]],[4,5],[1,[[3,[2]]]],[1,6],[1,6],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,0,0,[1,1],[7,7],[8,8],[9,9],[10,10],[11,11],[4,4],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[1,1],12],[13,[[15,[1,14]]]],[[10,11],[[17,[16]]]],[[11,6,6,6],[[17,[16]]]],[[10,11],[[17,[18]]]],[[],1],[[],7],[[],4],[1,6],[19,[[15,[1]]]],[19,[[15,[7]]]],[19,[[15,[8]]]],[19,[[15,[9]]]],[19,[[15,[10]]]],[19,[[15,[4]]]],0,0,[[4,4],[[21,[20]]]],[[],1],[[],[[23,[22]]]],[[],[[23,[22]]]],[[11,[3,[2]]],17],[[11,[3,[2]],[3,[2]]],17],[[11,6],17],[[11,6,[3,[2]]],17],[11,[[3,[2]]]],0,[[1,[3,[2]]],24],[[1,[26,[2,25]]],24],[[1,16],24],[[1,[3,[2]]],24],[[1,6],24],[[1,6],24],[[1,1],24],[[7,7],24],[[8,8],24],[[9,9],24],[[11,11],24],[[4,4],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[1,27],[[15,[28]]]],[[1,27],[[15,[28]]]],[[7,27],29],[[8,27],29],[[9,27],29],[[10,27],29],[[30,27],29],[[30,27],29],[[11,27],29],[[4,27],29],[[4,27],29],[[]],[[[3,[2]]],1],[6,1],[16,1],[6,1],[[[26,[2,25]]],1],[[]],[[]],[[]],[[]],[31,30],[[]],[32,30],[33,30],[34,30],[35,30],[36,30],[37,30],[38,30],[39,30],[40,30],[[]],[[]],[[6,6],[[17,[11]]]],[[11,16],[[17,[10]]]],[[11,41],[[17,[10]]]],[42,4],[6,[[17,[11]]]],[[[3,[2]]],[[17,[11]]]],[5,4],[[[3,[2]]],1],[6,[[15,[4]]]],[16,1],[[[26,[2,25]]],1],0,[[[0,[43,44]],45],46],[[[0,[43,44]],45],46],[[[0,[43,44]],45],46],[[[0,[43,44]],45],46],[[1,47]],[[7,47]],[[9,47]],[[11,47]],0,[11,[[3,[2]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],[[48,[16]]]],[1,16],[1,24],[1,24],[2,24],0,0,[[],49],[6,1],[[[26,[2]],[26,[2]]],[[17,[11]]]],[[],[[17,[11]]]],[[1,1],[[21,[12]]]],[[4,4],[[21,[12]]]],[50],[[],1],0,[[1,51],15],[[7,51],15],[[8,51],15],[[9,51],15],[[10,51],15],[[4,51],15],[10,49],[30,[[21,[52]]]],0,[11,[[53,[16]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[1,[[15,[54,55]]]],[[],16],[[],16],[[],16],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],56],[[],56],[[],56],[[],56],[[],56],[[],56],[[],56],[[],56],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[57,57],[58,58],[[]],[[]],[59,21],[[],58],[19,[[15,[60]]]],[19,[[15,[57]]]],[19,[[15,[61]]]],0,0,0,0,0,[[[63,[62]],27],29],[[60,27],29],[[64,27],29],[[57,27],29],[[58,27],29],[[61,27],29],[[65,27],29],[[[59,[62]],27],29],[[]],[[]],[[]],[[]],[1,58],[[]],[[]],[[]],[[]],[[58,41],[[15,[65,33]]]],[41,[[15,[65,33]]]],[41,61],[[41,4],61],0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[61,[[59,[18]]]],[[61,66],[[59,[18]]]],[[60,11],[[17,[61]]]],[[65,11],[[17,[64]]]],0,0,[[57,10],60],[[58,10],64],[[57,16],61],[[58,41],[[15,[65,33]]]],[1,61],[58,65],0,0,[[64,51],15],[[58,51],15],[[65,51],15],[64,49],0,0,0,[[]],[[]],[65,61],[65,18],[[65,4],61],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],0,0,[[],56],[[],56],[[],56],[[],56],[[],56],[[],56],[[],56],[[],56],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[67],[68,68],[[[70,[69]]],[[70,[69]]]],[71,71],[[]],[[]],[[]],[[71,71],12],[70,30],0,[[],67],[[],72],0,0,[[68,68],24],[[71,71],24],[[],24],[[],24],[[],[[17,[[70,[60]]]]]],[73,[[17,[[70,[60]]]]]],[[],[[17,[[70,[0]]]]]],[73,[[17,[[70,[0]]]]]],[[],[[17,[[70,[0]]]]]],[73,[[17,[[70,[0]]]]]],[[],[[17,[[70,[0]]]]]],[73,[[17,[[70,[0]]]]]],[[68,27],29],[[74,27],29],[[[70,[62]],27],29],[[71,27],29],[[73,27],29],[[67,27],29],[[72,27],29],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[30,68],[[75,[76,[0]]],[[17,[[70,[18]]]]]],[[73,77],[[17,[[70,[[26,[60]]]]]]]],[[[0,[43,44]],45],46],[67,[[21,[78]]]],[[71,47]],[73,[[17,[16]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],0,0,[71,[[17,[73]]]],0,0,[67],[[71,71],[[21,[12]]]],[[4,64],17],[[73,4,64],17],0,0,0,0,[67,24],[[[3,[80]],[21,[16]],67,71,11,81,[21,[72]]],74],[[[21,[82]],[3,[80]],[21,[16]],67,71,11,81,[21,[72]]],74],0,[[]],[[]],[[]],0,[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],56],[[],56],[[],56],[[],56],[[],56],[[],56],[[],56],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],17],[73,17],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[83,[[85,[84]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[84,84],[86,86],[83,83],[[]],[[]],[[]],[[83,83],12],0,0,0,[[84,84],24],[[86,86],24],[[83,83],24],[[],24],[[],24],[[],24],[[],[[85,[[87,[83]]]]]],[[84,27],29],[[86,27],29],[[83,27],29],[[]],[[]],[[]],[[]],0,[88,8],[[[0,[43,44]],45],46],[[[0,[43,44]],45],46],[[[0,[43,44]],45],46],[[84,47]],[[86,47]],[[83,47]],0,[[]],[[]],[[]],[[]],[88,16],[[82,81],88],[[83,83],[[21,[12]]]],0,[[],86],0,[[]],[[]],[[]],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],56],[[],56],[[],56],[[],56],[[]],[[]],[[]],[[]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[],[[85,[89]]]],[[4,90],[[85,[[26,[65]]]]]],[91,6],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[77,77],[91,91],[92,92],[93,93],[94,94],[[]],[[]],[[]],[[]],[[]],[[91,91],12],[[94,94],12],0,0,[[],79],[[],89],[[],77],[[],80],[6,[[85,[16]]]],[[77,77],24],[[91,91],24],[[92,92],24],[[93,93],24],[[94,94],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[89,27],29],[[77,27],29],[[91,27],29],[[91,27],29],[[92,27],29],[[93,27],29],[[94,27],29],[[94,27],29],[[]],[[[26,[65]]],89],[[]],[[]],[[]],[[]],[[]],[77,77],0,[4,[[85,[[21,[77]]]]]],[[[0,[43,44]],45],46],[[[0,[43,44]],45],46],[[],[[85,[93]]]],0,[[91,47]],[[94,47]],[[77,95],77],0,[[]],[[]],[[]],[[]],[[]],[[]],[[],96],[[],[[85,[5]]]],[[],[[85,[5]]]],[[77,49,91],77],0,[94,6],[[[26,[65]],[97,[[21,[49]]]]],89],[79,77],0,[[77,4],77],0,0,[[77,4],77],[[91,91],[[21,[12]]]],[[94,94],[[21,[12]]]],[6,85],[6,85],[98,85],[98,85],0,[[],85],[93,85],[[],[[85,[16]]]],[5,85],[5,85],[6,85],[6,85],[[5,[3,[1]]],85],[[4,[26,[1]]],85],[[[26,[61]],90],85],[[[26,[61]]],85],[[],85],[[],85],[[],85],[[],[[85,[[21,[16]]]]]],[[],85],[[],85],[[],85],[[]],[[]],[[]],[[]],[[]],[[],16],[[],16],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[6,[[15,[94]]]],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],56],[[],56],[[],56],[[],56],[[],56],[[],56],[[]],[[]],[[]],[[]],[[]],[[]],[[],85],[[],85],0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[99,100]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],99],[[],101],[[],102],[[],103],[[],104],[[],105],[[104,90]],[[105,106]],[[106,6,16],106],[[99,100]],[[101,49]],[[90,[97,[107]]]],[[104,107]],[[],108],[104],[[106,27],29],[[107,27],29],[[99,27],29],[[101,27],29],[[90,27],29],[[102,27],29],[[103,27],29],[[104,27],29],[[105,27],29],[[]],[[]],[30,107],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[99,100],[99,100],[90,[[21,[99]]]],[99,100],[99,100],[[90,99]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[105,108],[[6,6],106],[[],99],[[],101],[[[97,[16]]],90],[[],104],[[],105],[[99,100]],[[90,101]],[[102,6,49],102],[[99,100]],[[101,49]],[[106,51],15],[[107,51],15],[[99,51],15],[[101,51],15],[[90,51],15],[[102,51],15],[[103,51],15],[[104,51],15],[[105,51],15],[[105,104]],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],15],[[],56],[[],56],[[],56],[[],56],[[],56],[[],56],[[],56],[[],56],[[],56],[[105,16]],[[90,102]],[[106,6],106],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[100,102],0,0,0,0],"c":[],"p":[[3,"Guid"],[15,"u8"],[15,"slice"],[3,"ServerTimestamp"],[15,"i64"],[15,"str"],[4,"DeviceType"],[3,"ClientData"],[3,"RemoteClient"],[3,"EncryptedPayload"],[3,"KeyBundle"],[4,"Ordering"],[4,"ValueRef"],[4,"FromSqlError"],[4,"Result"],[3,"String"],[6,"Result"],[8,"Deserialize"],[8,"Deserializer"],[3,"Duration"],[4,"Option"],[15,"char"],[8,"FromIterator"],[15,"bool"],[3,"Global"],[3,"Vec"],[3,"Formatter"],[3,"Error"],[6,"Result"],[4,"Error"],[4,"DecodeError"],[3,"UnexpectedStatus"],[3,"Error"],[3,"Interrupted"],[4,"Error"],[3,"FromUtf8Error"],[4,"Error"],[3,"Error"],[4,"ParseError"],[3,"Error"],[8,"Serialize"],[15,"f64"],[8,"Hash"],[8,"Sized"],[8,"BuildHasher"],[15,"u64"],[8,"Hasher"],[4,"Resettable"],[15,"usize"],[3,"Demand"],[8,"Serializer"],[8,"Error"],[15,"array"],[4,"ToSqlOutput"],[4,"Error"],[3,"TypeId"],[3,"IncomingEnvelope"],[3,"OutgoingEnvelope"],[3,"IncomingContent"],[3,"IncomingEncryptedBso"],[3,"IncomingBso"],[8,"Debug"],[4,"IncomingKind"],[3,"OutgoingEncryptedBso"],[3,"OutgoingBso"],[8,"FnOnce"],[3,"MemoryCachedState"],[4,"ServiceStatus"],[8,"Clone"],[4,"Sync15ClientResponse"],[3,"Sync15StorageClientInit"],[3,"SyncRequestInfo"],[3,"Sync15StorageClient"],[3,"SyncResult"],[3,"Response"],[3,"Arc"],[3,"CollectionRequest"],[3,"SystemTime"],[6,"CollectionName"],[8,"SyncEngine"],[8,"Interruptee"],[8,"CommandProcessor"],[4,"Command"],[4,"CommandStatus"],[6,"Result"],[3,"Settings"],[3,"HashSet"],[3,"Engine"],[3,"ApplyResults"],[3,"Engine"],[4,"RequestOrder"],[3,"CollSyncIds"],[4,"EngineSyncAssociation"],[4,"SyncEngineId"],[8,"IntoIterator"],[8,"Iterator"],[8,"Into"],[8,"Fn"],[3,"EngineIncoming"],[15,"u32"],[3,"EngineOutgoing"],[3,"Validation"],[3,"Problem"],[3,"SyncTelemetry"],[3,"SyncTelemetryPing"],[3,"Event"],[4,"SyncFailure"],[6,"c_char"],[8,"SetupStorageClient"],[13,"Success"],[8,"BridgedEngine"],[8,"BridgedEngineAdaptor"],[13,"Http"],[13,"Other"],[13,"Unexpected"],[13,"Auth"]]},\ +"sync_guid":{"doc":"","t":"DLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL","n":["Guid","as_bytes","as_ref","as_ref","as_str","borrow","borrow_mut","clone","clone_into","cmp","column_result","default","deref","deserialize","empty","eq","eq","eq","eq","eq","eq","eq","equivalent","fmt","fmt","from","from","from","from","from","from","from_slice","from_string","from_vec","get_hash","hash","into","into_string","is_valid_for_places","is_valid_for_sync_server","is_valid_places_byte","new","partial_cmp","random","serialize","to_owned","to_sql","to_string","try_from","try_into","type_id","vzip"],"q":[[0,"sync_guid"]],"d":["This is a type intended to be used to represent the guids …","Get the data backing this Guid as a &[u8].","","","Get the data backing this Guid as a &str.","","","","","","","Create a default guid by calling Guid::empty()","","","Create an empty guid. Usable as a constant.","","","","","","","","","","","","","","","","Returns the argument unchanged.","Convert b into a Guid.","Convert b into a Guid.","Convert v to a Guid, consuming it.","","","Calls U::from(self).","Convert this Guid into a String, consuming it in the …","Returns true for Guids that are valid places guids, and …","Returns true for Guids that are deemed valid by the sync …","Returns true if the byte b is a valid base64url byte.","Create a guid from a str.","","Create a random guid (of 12 base64url characters). …","","","","","","","",""],"i":[0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],"f":[0,[1,[[3,[2]]]],[1,4],[1,[[3,[2]]]],[1,4],[[]],[[]],[1,1],[[]],[[1,1],5],[6,[[7,[1]]]],[[],1],[1,4],[8,[[9,[1]]]],[[],1],[[1,10],11],[[1,4],11],[[1,[3,[2]]],11],[[1,[3,[2]]],11],[[1,4],11],[[1,[12,[2]]],11],[[1,1],11],[[],11],[[1,13],14],[[1,13],14],[4,1],[4,1],[10,1],[[[12,[2]]],1],[[[3,[2]]],1],[[]],[[[3,[2]]],1],[10,1],[[[12,[2]]],1],[[[0,[15,16]],17],18],[[1,19]],[[]],[1,10],[1,11],[1,11],[2,11],[4,1],[[1,1],[[20,[5]]]],[[],1],[[1,21],9],[[]],[1,[[23,[22]]]],[[],10],[[],9],[[],9],[[],24],[[]]],"c":[],"p":[[3,"Guid"],[15,"u8"],[15,"slice"],[15,"str"],[4,"Ordering"],[4,"ValueRef"],[6,"FromSqlResult"],[8,"Deserializer"],[4,"Result"],[3,"String"],[15,"bool"],[3,"Vec"],[3,"Formatter"],[6,"Result"],[8,"Hash"],[8,"Sized"],[8,"BuildHasher"],[15,"u64"],[8,"Hasher"],[4,"Option"],[8,"Serializer"],[4,"ToSqlOutput"],[6,"Result"],[3,"TypeId"]]},\ +"sync_manager":{"doc":"","t":"NNNNNDENNNNNNCNNENNDECDEDNNNNNMLLLLLLLLLLLLLLLLLLMLLMFMMLLAMLLLLLLLLLLLLLLLLLMMLLLLLLLLLLLMMMAMMMMMFFLMMFMMLMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLFMNNNNNGNENNNLLLLLLLLLLLLLLLLLLLLDLLLLLLLLLLLLLLLL","n":["All","AuthError","BackedOff","Backgrounded","Desktop","DeviceSettings","DeviceType","EnabledChange","Mobile","NetworkError","Ok","OtherError","PreSleep","Result","Scheduled","ServiceError","ServiceStatus","Some","Startup","SyncAuthInfo","SyncEngineSelection","SyncManagerError","SyncParams","SyncReason","SyncResult","TV","Tablet","Unknown","User","VR","auth_info","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clone","clone_into","declined","default","deserialize","device_settings","disconnect","enabled_changes","engines","eq","equivalent","error","failures","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","fxa_access_token","fxa_device_id","get_hash","hash","into","into","into","into","into","into","into","into","is_ok","kid","kind","local_encryption_keys","manager","name","next_sync_allowed_at","persisted_state","persisted_state","reason","reset","reset_all","serialize","status","successful","sync","sync_key","telemetry_json","to_owned","tokenserver_url","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","type_id","vzip","vzip","vzip","vzip","vzip","vzip","vzip","vzip","wipe","engines","AnyhowError","InterruptedError","JsonError","LoginsError","PlacesError","Result","Sync15Error","SyncManagerError","UnknownEngine","UnsupportedFeature","UrlParseError","borrow","borrow_mut","fmt","fmt","from","from","from","from","from","from","from","from","into","provide","source","to_string","try_from","try_into","type_id","vzip","SyncManager","borrow","borrow_mut","default","disconnect","from","get_available_engines","into","new","reset","reset_all","sync","try_from","try_into","type_id","vzip","wipe"],"q":[[0,"sync_manager"],[142,"sync_manager::SyncEngineSelection"],[143,"sync_manager::error"],[174,"sync_manager::manager"]],"d":["","","","","","","Enumeration for the different types of device.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","Returns the argument unchanged.","","","","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","","","","","","","","Calls U::from(self).","","","","","","","","","","","","Disconnect engines from sync, deleting/resetting the …","Returns the argument unchanged.","","Calls U::from(self).","","","","Perform a sync. See SyncParams and SyncResult for details …","","","","",""],"i":[9,13,13,8,1,0,0,8,1,13,13,13,8,0,8,13,0,9,8,0,0,0,0,0,0,1,1,1,8,1,5,5,8,9,10,11,12,13,1,5,8,9,10,11,12,13,1,1,1,12,1,1,5,0,5,5,1,1,0,12,5,8,9,10,11,12,13,1,5,8,9,10,11,12,13,13,1,10,11,1,1,5,8,9,10,11,12,13,1,13,10,11,5,0,11,12,5,12,5,0,0,1,12,12,0,10,12,1,10,5,8,9,10,11,12,13,1,5,8,9,10,11,12,13,1,5,8,9,10,11,12,13,1,5,8,9,10,11,12,13,1,0,39,25,25,25,25,25,0,25,0,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,25,0,37,37,37,37,37,37,37,37,37,37,37,37,37,37,37,37],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[1,1],[[]],0,[[],1],[2,[[3,[1]]]],0,[[]],0,0,[[1,1],4],[[],4],0,0,[[5,6],7],[[8,6],7],[[9,6],7],[[10,6],7],[[11,6],7],[[12,6],7],[[13,6],7],[[1,6],[[3,[14]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[15,13],[[]],0,0,[[[0,[16,17]],18],19],[[1,20]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[13,4],0,0,0,0,0,0,0,0,0,[21,22],[[],22],[[1,23],3],0,0,[5,[[22,[12]]]],0,0,[[]],0,[[],3],[[],3],[[],3],[[],3],[[],3],[[],3],[[],3],[[],3],[[],3],[[],3],[[],3],[[],3],[[],3],[[],3],[[],3],[[],3],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[21,22],0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[25,6],7],[[25,6],7],[[]],[26,25],[27,25],[28,25],[29,25],[30,25],[31,25],[32,25],[[]],[33],[25,[[35,[34]]]],[[],36],[[],3],[[],3],[[],24],[[]],0,[[]],[[]],[[],37],[37],[[]],[37,[[38,[36]]]],[[]],[[],37],[[37,21],22],[37,22],[[37,5],[[22,[12]]]],[[],3],[[],3],[[],24],[[]],[[37,21],22]],"c":[],"p":[[4,"DeviceType"],[8,"Deserializer"],[4,"Result"],[15,"bool"],[3,"SyncParams"],[3,"Formatter"],[6,"Result"],[4,"SyncReason"],[4,"SyncEngineSelection"],[3,"SyncAuthInfo"],[3,"DeviceSettings"],[3,"SyncResult"],[4,"ServiceStatus"],[3,"Error"],[4,"ServiceStatus"],[8,"Hash"],[8,"Sized"],[8,"BuildHasher"],[15,"u64"],[8,"Hasher"],[15,"str"],[6,"Result"],[8,"Serializer"],[3,"TypeId"],[4,"SyncManagerError"],[4,"Error"],[3,"Interrupted"],[4,"Error"],[3,"Error"],[4,"Error"],[4,"ParseError"],[3,"Error"],[3,"Demand"],[8,"Error"],[4,"Option"],[3,"String"],[3,"SyncManager"],[3,"Vec"],[13,"Some"]]},\ +"tabs":{"doc":"","t":"CDCGCCDGDDLLLLLLLLLLLMMLLLLMLALLLLLLLLFLLLLLMLMLLLLLLLLMLLLLLLLLLLMLLLLLLLLLLLLLLLLLLLLLLLGENNNGNNNNENNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMMM","n":["ApiResult","ClientRemoteTabs","Error","RemoteTabRecord","Result","TabsApiError","TabsBridgedEngine","TabsDeviceType","TabsEngine","TabsStore","apply","apply","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","bridged_engine","client_id","client_name","clone","clone_into","collection_name","deserialize","device_type","ensure_current_sync_id","error","fmt","from","from","from","from","get_all","get_collection_request","get_last_sync","get_registered_sync_engine","get_sync_assoc","into","into","into","into","last_modified","last_sync","local_id","new","new","new","new_with_mem_path","prepare_for_sync","prepare_for_sync","register_with_sync_manager","remote_tabs","remote_tabs","reset","reset","reset_sync_id","serialize","set_last_sync","set_last_sync","set_local_tabs","set_uploaded","set_uploaded","stage_incoming","storage","store_incoming","sync_finished","sync_id","sync_started","to_owned","try_from","try_from","try_from","try_from","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","vzip","vzip","vzip","vzip","wipe","wipe","ApiResult","Error","JsonError","MissingLocalIdError","OpenDatabaseError","Result","SqlError","SqlError","SyncAdapterError","SyncError","TabsApiError","UnexpectedTabsError","UrlParseError","borrow","borrow","borrow_mut","borrow_mut","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","get_error_handling","into","into","provide","provide","source","to_string","to_string","try_from","try_from","try_into","try_into","type_id","type_id","vzip","vzip","reason","reason","reason"],"q":[[0,"tabs"],[90,"tabs::error"],[134,"tabs::error::TabsApiError"]],"d":["","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","","","Called by the sync manager to get a sync engine via the …","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Result enum for the public interface","","","","","Result enum for internal functions","","","","","","","","","","","","","","","","Returns the argument unchanged.","","","","","","Returns the argument unchanged.","","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","",""],"i":[0,0,0,0,0,0,0,0,0,0,1,5,9,1,5,11,9,1,5,11,9,11,11,11,11,5,11,11,1,0,11,9,1,5,11,9,5,5,0,5,9,1,5,11,11,1,5,9,1,5,9,1,5,9,9,11,1,5,1,11,1,5,9,1,5,5,9,1,1,1,1,11,9,1,5,11,9,1,5,11,9,1,5,11,9,1,5,11,1,5,0,0,34,34,34,0,33,34,34,33,0,33,34,33,34,33,34,33,33,34,34,33,33,34,34,34,34,34,34,33,34,33,34,34,33,34,33,34,33,34,33,34,33,34,43,44,45],"f":[0,0,0,0,0,0,0,0,0,0,[1,[[4,[[3,[2]]]]]],[[5,6,7],[[4,[[3,[8]]]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[[10,[9]]],[[10,[1]]]],0,0,[11,11],[[]],[5,12],[13,[[14,[11]]]],0,[[1,15],[[4,[2]]]],0,[[11,16],17],[[]],[[]],[[]],[[]],[9,[[3,[11]]]],[[5,6],[[4,[[19,[18]]]]]],[5,[[4,[[19,[6]]]]]],[20,[[19,[[22,[21]]]]]],[5,[[4,[23]]]],[[]],[[]],[[]],[[]],0,[1,[[4,[24]]]],0,[[[26,[25]]],9],[[[22,[27]]],1],[[[10,[9]]],5],[15,9],[[1,15],4],[[5,28],4],[[[10,[9]]]],[9,[[19,[[3,[11]]]]]],0,[1,4],[[5,23],4],[1,[[4,[2]]]],[[11,29],14],[[1,24],4],[[5,6],4],[[9,[3,[0]]]],[[1,24,[3,[30]]],4],[[5,6,[3,[30]]],4],[[5,[3,[31]],7],4],0,[[1,[3,[2]]],4],[1,4],[1,[[4,[[19,[2]]]]]],[1,4],[[]],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],14],[[],32],[[],32],[[],32],[[],32],[[]],[[]],[[]],[[]],[1,4],[5,4],0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[[33,16],17],[[33,16],17],[[34,16],17],[[34,16],17],[[]],[35,33],[36,34],[37,34],[38,34],[39,34],[[]],[34,40],[[]],[[]],[41],[41],[34,[[19,[42]]]],[[],2],[[],2],[[],14],[[],14],[[],14],[[],14],[[],32],[[],32],[[]],[[]],0,0,0],"c":[],"p":[[3,"TabsBridgedEngine"],[3,"String"],[3,"Vec"],[6,"Result"],[3,"TabsEngine"],[3,"ServerTimestamp"],[3,"Engine"],[3,"OutgoingBso"],[3,"TabsStore"],[3,"Arc"],[3,"ClientRemoteTabs"],[6,"CollectionName"],[8,"Deserializer"],[4,"Result"],[15,"str"],[3,"Formatter"],[6,"Result"],[3,"CollectionRequest"],[4,"Option"],[4,"SyncEngineId"],[8,"SyncEngine"],[3,"Box"],[4,"EngineSyncAssociation"],[15,"i64"],[3,"Path"],[8,"AsRef"],[8,"BridgedEngine"],[8,"Fn"],[8,"Serializer"],[3,"Guid"],[3,"IncomingBso"],[3,"TypeId"],[4,"TabsApiError"],[4,"Error"],[3,"Error"],[3,"Error"],[4,"Error"],[4,"Error"],[4,"ParseError"],[3,"ErrorHandling"],[3,"Demand"],[8,"Error"],[13,"SyncError"],[13,"SqlError"],[13,"UnexpectedTabsError"]]},\ +"types":{"doc":"","t":"SDLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL","n":["EARLIEST","Timestamp","as_millis","as_millis_i64","borrow","borrow_mut","checked_add","checked_sub","clone","clone_into","cmp","column_result","default","deserialize","duration_since","eq","equivalent","fmt","fmt","from","from","from","get_hash","hash","into","now","partial_cmp","serialize","to_owned","to_sql","to_string","try_from","try_into","type_id"],"q":[[0,"types"]],"d":["In desktop sync, bookmarks are clamped to Jan 23, 1993 …","","","","","","","","","","","","","","Returns None if other is later than self (Duration may not …","","","","","","Returns the argument unchanged.","","","","Calls U::from(self).","","","","","","","","",""],"i":[1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],"f":[0,0,[1,2],[1,3],[[]],[[]],[[1,4],[[5,[1]]]],[[1,4],[[5,[1]]]],[1,1],[[]],[[1,1],6],[7,[[8,[1]]]],[[],1],[9,[[10,[1]]]],[[1,1],[[5,[4]]]],[[1,1],11],[[],11],[[1,12],13],[[1,12],13],[2,1],[[]],[14,1],[[[0,[15,16]],17],2],[[1,18]],[[]],[[],1],[[1,1],[[5,[6]]]],[[1,19],10],[[]],[1,[[21,[20]]]],[[],22],[[],10],[[],10],[[],23]],"c":[],"p":[[3,"Timestamp"],[15,"u64"],[15,"i64"],[3,"Duration"],[4,"Option"],[4,"Ordering"],[4,"ValueRef"],[6,"FromSqlResult"],[8,"Deserializer"],[4,"Result"],[15,"bool"],[3,"Formatter"],[6,"Result"],[3,"SystemTime"],[8,"Hash"],[8,"Sized"],[8,"BuildHasher"],[8,"Hasher"],[8,"Serializer"],[4,"ToSqlOutput"],[6,"Result"],[3,"String"],[3,"TypeId"]]},\ +"viaduct":{"doc":"","t":"INNCNNDDDDENNNNDDNLLLLLMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLALLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLALMMLLLLLLLLLLLLLLLLLLLLLMLLLLLLLFLLLLLLLLMLKLFLAMALLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMMLLNNENNNNDNLLLLLLLLLLLLLLLMLLLMLLLLLLLLLMRRRRRRRRRRRRRRRRHDMLLMLMLLMLLLMRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRFFF","n":["Backend","Connect","Delete","GLOBAL_SETTINGS","Get","Head","Header","HeaderName","Headers","InvalidHeaderName","Method","Options","Patch","Post","Put","Request","Response","Trace","as_ref","as_ref","as_str","as_str","body","body","body","borrow","borrow","borrow","borrow","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","borrow_mut","clear","clone","clone","clone","clone","clone","clone","clone","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","clone_into","cmp","cmp","cmp","default","delete","deref","eq","eq","eq","eq","eq","eq","eq","eq","eq","eq","equivalent","equivalent","equivalent","equivalent","equivalent","error","extend","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from_iter","get","get","get_as","get_header","hash","hash","hash","header","header_names","headers","headers","headers","insert","insert_header","insert_if_missing","into","into","into","into","into","into","into","into_iter","into_iter","into_vec","is_client_error","is_empty","is_server_error","is_success","iter","json","json","len","method","name","name","new","new","new","new","new_unchecked","note_backend","partial_cmp","partial_cmp","partial_cmp","patch","post","provide","put","query","request_method","require_success","send","send","set_backend","set_query","settings","status","status_codes","text","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_owned","to_string","to_string","to_string","to_string","try_extend","try_from","try_from","try_from","try_from","try_from","try_from","try_from","try_get","try_into","try_into","try_into","try_into","try_into","try_into","try_into","type_id","type_id","type_id","type_id","type_id","type_id","type_id","url","url","value","with_capacity","BackendError","BackendNotInitialized","Error","NetworkError","NonTlsUrl","RequestHeaderError","SetBackendError","UnexpectedStatus","UrlError","borrow","borrow","borrow_mut","borrow_mut","clone","clone_into","fmt","fmt","fmt","fmt","from","from","from","into","into","method","provide","provide","source","status","to_owned","to_string","to_string","try_from","try_from","try_into","try_into","type_id","type_id","url","ACCEPT","ACCEPT_ENCODING","AUTHORIZATION","CONTENT_TYPE","ETAG","IF_NONE_MATCH","RETRY_AFTER","USER_AGENT","X_IF_UNMODIFIED_SINCE","X_KEYID","X_LAST_MODIFIED","X_TIMESTAMP","X_WEAVE_BACKOFF","X_WEAVE_NEXT_OFFSET","X_WEAVE_RECORDS","X_WEAVE_TIMESTAMP","GLOBAL_SETTINGS","Settings","addn_allowed_insecure_url","borrow","borrow_mut","connect_timeout","fmt","follow_redirects","from","into","read_timeout","try_from","try_into","type_id","use_caches","ACCEPTED","BAD_GATEWAY","BAD_REQUEST","CONFLICT","CONTINUE","CREATED","EXPECTATION_FAILED","FORBIDDEN","FOUND","GATEWAY_TIMEOUT","GONE","HTTP_VERSION_NOT_SUPPORTED","INTERNAL_SERVER_ERROR","LENGTH_REQUIRED","METHOD_NOT_ALLOWED","MOVED_PERMANENTLY","MULTIPLE_CHOICES","NONAUTHORITATIVE_INFORMATION","NOT_ACCEPTABLE","NOT_FOUND","NOT_IMPLEMENTED","NOT_MODIFIED","NO_CONTENT","OK","PARTIAL_CONTENT","PAYMENT_REQUIRED","PRECONDITION_FAILED","PROXY_AUTHENTICATION_REQUIRED","REQUESTED_RANGE_NOT_SATISFIABLE","REQUEST_ENTITY_TOO_LARGE","REQUEST_TIMEOUT","REQUEST_URI_TOO_LONG","RESET_CONTENT","SEE_OTHER","SERVICE_UNAVAILABLE","SWITCHING_PROTOCOLS","TEMPORARY_REDIRECT","TOO_MANY_REQUESTS","UNAUTHORIZED","UNSUPPORTED_MEDIA_TYPE","USE_PROXY","is_client_error_code","is_server_error_code","is_success_code"],"q":[[0,"viaduct"],[197,"viaduct::error"],[236,"viaduct::header_names"],[252,"viaduct::settings"],[267,"viaduct::status_codes"]],"d":["","","","","","","A single header. Headers have a name (case insensitive) …","Represents a header name that we know to be both valid and …","A list of headers.","Indicates an invalid header name. Note that we only emit …","HTTP Methods.","","","","","","A response from the server.","","","","","","Set this request’s body.","","The body of the response.","","","","","","","","","","","","","","","Clear this set of headers.","","","","","","","","","","","","","","","","","","","Alias for Request::new(Method::Delete, url), for …","","","","","","","","","","","","","","","","","","Add all the headers in the provided iterator to this list …","","","","","","","","","","","","Returns the argument unchanged.","","","","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","Returns the argument unchanged.","","Get the value of the header with the provided name.","Alias for Request::new(Method::Get, url), for convenience.","Get the value of the header with the provided name, and …","Get the header object with the requested name. Usually, …","","","","Add the provided header to the list of headers to send …","","Add all the provided headers to the list of headers to …","","The headers returned with this response.","Insert or update a new header.","Insert or update a header directly. Typically you will …","Insert the provided header unless a header is already …","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","Convert this list of headers to a Vec","Returns true if the status code is in the interval …","Returns true if len() is zero.","Returns true if the status code is in the interval …","Returns true if the status code is in the interval …","Get an iterator over the headers in no particular order.","Set body to the result of serializing val, and, unless it …","Parse the body as JSON.","Returns the number of headers.","","","","Create a new header. In general you likely want to use …","","Initialize an empty list of headers.","Construct a new request to the given url using the given …","","","","","","Alias for Request::new(Method::Patch, url), for …","Alias for Request::new(Method::Post, url), for convenience.","","Alias for Request::new(Method::Put, url), for convenience.","Append the provided query parameters to the URL","The method used to request this response.","Returns an UnexpectedStatus error if self.is_success() is …","","","","Set the query string of the URL. Note that …","","The HTTP Status code of this response.","A module containing constants for all HTTP status codes.","Get the body as a string. Assumes UTF-8 encoding. Any …","","","","","","","","","","","","Add all the headers in the provided iterator, unless any …","","","","","","","","Get the value of the header with the provided name, and …","","","","","","","","","","","","","","","","The URL of this response.","","Initialize an empty list of headers backed by a vector …","","","","","","","","This error is returned as the Err result from […","Note: we return this if the server returns a bad URL with …","","","","","","","","","","","","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Note: reqwest allows these only to be specified …","","","","","","","Returns the argument unchanged.","Calls U::from(self).","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Is it a 4xx error?","Is it a 5xx error?","Is it a 2xx status?"],"i":[0,5,5,0,5,5,0,0,0,0,0,5,5,5,5,0,0,5,1,1,1,5,6,6,12,1,10,11,9,5,6,12,1,10,11,9,5,6,12,9,1,10,11,9,5,6,12,1,10,11,9,5,6,12,1,11,5,9,6,1,1,1,1,1,1,1,10,11,9,5,1,10,11,9,5,0,9,1,1,10,10,11,11,9,5,5,6,12,1,1,1,1,10,11,9,5,6,12,9,9,6,9,9,1,11,5,6,0,6,6,12,9,9,9,1,10,11,9,5,6,12,9,9,9,12,9,12,12,9,6,12,9,6,10,11,1,11,9,6,11,0,1,11,5,6,6,10,6,6,12,12,35,6,0,6,0,12,0,12,1,10,11,9,5,6,12,1,10,11,5,9,1,10,11,9,5,6,12,9,1,10,11,9,5,6,12,1,10,11,9,5,6,12,6,12,11,9,27,27,0,27,27,27,27,0,27,27,34,27,34,34,34,27,27,34,34,27,27,34,27,34,34,27,34,27,34,34,27,34,27,34,27,34,27,34,34,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,39,39,39,39,39,39,39,39,39,39,39,39,39,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"f":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[1,2],[1,[[4,[3]]]],[1,2],[5,2],[[6,[8,[[7,[3]]]]],6],0,0,[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[9],[1,1],[10,10],[11,11],[9,9],[5,5],[6,6],[12,12],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[1,1],13],[[11,11],13],[[5,5],13],[[],9],[14,6],[1,2],[[1,15],16],[[1,2],16],[[1,2],16],[[1,15],16],[[1,1],16],[[1,[17,[2]]],16],[[10,10],16],[[11,11],16],[[9,9],16],[[5,5],16],[[],16],[[],16],[[],16],[[],16],[[],16],0,[[9,18],9],[[1,19],20],[[1,19],20],[[10,19],20],[[10,19],20],[[11,19],20],[[11,19],20],[[9,19],20],[[5,19],20],[[5,19],20],[[6,19],20],[[12,19],20],[[]],[15,1],[[[17,[2]]],1],[2,1],[[]],[[]],[[]],[[]],[[]],[[]],[18,9],[[9,[21,[1]]],[[22,[2]]]],[14,6],[[9,[21,[1]]],[[22,[[24,[23]]]]]],[[9,[21,[1]]],[[22,[11]]]],[[1,25]],[[11,25]],[[5,25]],[[6,[0,[[8,[1]],[21,[1]]]],[0,[[8,[15]],[26,[2]]]]],[[24,[6,27]]]],0,[[6,18],6],0,0,[[9,[0,[[8,[1]],[21,[1]]]],[0,[[8,[15]],[26,[2]]]]],[[24,[9,27]]]],[[9,11],9],[[9,[0,[[8,[1]],[21,[1]]]],[0,[[8,[15]],[26,[2]]]]],[[24,[9,27]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[9],[9],[9,[[7,[11]]]],[12,16],[9,16],[12,16],[12,16],[9],[[6,[0,[28,29]]],6],[12,[[24,[30,31]]]],[9,32],0,[10,2],[11,1],[[[8,[[17,[2]]]]],[[24,[1,10]]]],[[[8,[1]],[0,[[26,[2]],[8,[15]]]]],[[24,[11,27]]]],[[],9],[[5,14],6],[[1,[0,[[26,[2]],[8,[15]]]]],11],[2],[[1,1],[[22,[13]]]],[[11,11],[[22,[13]]]],[[5,5],[[22,[13]]]],[14,6],[14,6],[33],[14,6],[[6,4],6],0,[12,[[24,[12,34]]]],[6,[[24,[12,27]]]],[6,[[24,[12,27]]]],[35,[[24,[27]]]],[[6,[8,[[22,[2]]]]],6],0,0,0,[12,[[17,[2]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[]],[[],15],[[],15],[[],15],[[],15],[[9,18],[[24,[9]]]],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[9,[21,[1]]],[[22,[23]]]],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],24],[[],36],[[],36],[[],36],[[],36],[[],36],[[],36],[[],36],0,0,[11,2],[32,9],0,0,0,0,0,0,0,0,0,[[]],[[]],[[]],[[]],[34,34],[[]],[[27,19],20],[[27,19],20],[[34,19],20],[[34,19],20],[37,27],[[]],[[]],[[]],[[]],0,[33],[33],[27,[[22,[38]]]],0,[[]],[[],15],[[],15],[[],24],[[],24],[[],24],[[],24],[[],36],[[],36],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[[]],[[]],0,[[39,19],20],0,[[]],[[]],0,[[],24],[[],24],[[],36],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[40,16],[40,16],[40,16]],"c":[],"p":[[3,"HeaderName"],[15,"str"],[15,"u8"],[15,"slice"],[4,"Method"],[3,"Request"],[3,"Vec"],[8,"Into"],[3,"Headers"],[3,"InvalidHeaderName"],[3,"Header"],[3,"Response"],[4,"Ordering"],[3,"Url"],[3,"String"],[15,"bool"],[4,"Cow"],[8,"IntoIterator"],[3,"Formatter"],[6,"Result"],[8,"PartialEq"],[4,"Option"],[8,"FromStr"],[4,"Result"],[8,"Hasher"],[8,"AsRef"],[4,"Error"],[8,"Sized"],[8,"Serialize"],[8,"Deserialize"],[3,"Error"],[15,"usize"],[3,"Demand"],[3,"UnexpectedStatus"],[8,"Backend"],[3,"TypeId"],[4,"ParseError"],[8,"Error"],[3,"Settings"],[15,"u16"]]},\ +"viaduct_reqwest":{"doc":"","t":"DLLLLLLLLFF","n":["ReqwestBackend","borrow","borrow_mut","from","into","send","try_from","try_into","type_id","use_reqwest_backend","viaduct_detect_reqwest_backend"],"q":[[0,"viaduct_reqwest"]],"d":["","","","Returns the argument unchanged.","Calls U::from(self).","","","","","","A dummy symbol we include so that we can detect whether or …"],"i":[0,1,1,1,1,1,1,1,1,0,0],"f":[0,[[]],[[]],[[]],[[]],[[1,2],[[5,[3,4]]]],[[],5],[[],5],[[],6],[[]],[[]]],"c":[],"p":[[3,"ReqwestBackend"],[3,"Request"],[3,"Response"],[4,"Error"],[4,"Result"],[3,"TypeId"]]},\ +"webext_storage":{"doc":"","t":"DRRRRDLLLLLLLLLLMMLLLLAMMMLLLLLLMMMLALLLLLLLLLLNNDENNNNNNNNNNEGNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLDLLLLLLLLLLLLLLLLLLLLL","n":["MigrationInfo","STORAGE_VERSION","SYNC_MAX_ITEMS","SYNC_QUOTA_BYTES","SYNC_QUOTA_BYTES_PER_ITEM","UsageInfo","borrow","borrow","borrow_mut","borrow_mut","clone","clone","clone_into","clone_into","default","deserialize","entries","entries_successful","eq","eq","equivalent","equivalent","error","ext_id","extensions","extensions_successful","fmt","fmt","from","from","into","into","num_bytes","num_keys","open_failure","serialize","store","to_owned","to_owned","try_from","try_from","try_into","try_into","type_id","type_id","vzip","vzip","ConnectionAlreadyOpen","DatabaseConnectionClosed","Error","ErrorKind","IllegalDatabasePath","InterruptedError","InvalidConnectionType","IoError","ItemBytes","JsonError","MaxItems","OpenDatabaseError","OtherConnectionReferencesExist","QuotaError","QuotaReason","Result","SqlError","SyncError","TotalBytes","Utf8Error","WrongApiForClose","backtrace","borrow","borrow","borrow","borrow_mut","borrow_mut","borrow_mut","fmt","fmt","fmt","fmt","fmt","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","from","into","into","into","kind","provide","provide","source","to_string","to_string","try_from","try_from","try_from","try_into","try_into","try_into","type_id","type_id","type_id","vzip","vzip","vzip","Store","borrow","borrow_mut","bridged_engine","clear","close","from","get","get_bytes_in_use","get_synced_changes","interrupt_handle","into","migrate","new","remove","set","take_migration_info","try_from","try_into","type_id","usage","vzip"],"q":[[0,"webext_storage"],[47,"webext_storage::error"],[118,"webext_storage::store"]],"d":["","","","","","Information about the usage of a single extension.","","","","","","","","","","","The number of entries (rows in the original table) we …","The number of records we successfully migrated (equal to …","","","","","","The extension id.","The number of extensions (distinct extension ids) in the …","The number of extensions we successfully migrated","","","Returns the argument unchanged.","Returns the argument unchanged.","Calls U::from(self).","Calls U::from(self).","The number of bytes used by the extension. This result is …","The number of keys the extension uses.","True iff we failed to open the source DB at all.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","Returns the argument unchanged.","","","","","Returns the argument unchanged.","","","","Returns the argument unchanged.","","","","","","","","Calls U::from(self).","Calls U::from(self).","Calls U::from(self).","","","","","","","","","","","","","","","","","","","A store is used to access storage.sync data. It manages an …","","","Returns a bridged sync engine for Desktop for this store.","Deletes all key-value pairs for the extension. As with …","Closes the store and its database connection. See the docs …","Returns the argument unchanged.","Returns the values for one or more keys keys can be:","Returns the bytes in use for the specified items (which …","Gets the changes which the current sync applied. Should be …","Returns an interrupt handle for this store.","Calls U::from(self).","Migrates data from a database in the format of the “old…","Creates a store backed by a database at db_path. The path …","Deletes the values for one or more keys. As with get, keys …","Sets one or more JSON key-value pairs for an extension ID. …","Read-and-delete (e.g. take in rust parlance, see …","","","","Returns information about per-extension usage",""],"i":[0,0,0,0,0,0,1,2,1,2,1,2,1,2,2,2,2,2,1,2,1,2,0,1,2,2,1,2,1,2,1,2,1,1,2,2,0,1,2,1,2,1,2,1,2,1,2,15,15,0,0,15,15,15,15,14,15,14,15,15,15,0,0,15,15,14,15,15,10,14,15,10,14,15,10,14,15,15,10,10,14,15,15,15,15,15,15,15,10,10,10,10,10,10,10,10,10,14,15,10,10,15,10,15,15,10,14,15,10,14,15,10,14,15,10,14,15,10,0,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26,26],"f":[0,0,0,0,0,0,[[]],[[]],[[]],[[]],[1,1],[2,2],[[]],[[]],[[],2],[3,[[4,[2]]]],0,0,[[1,1],5],[[2,2],5],[[],5],[[],5],0,0,0,0,[[1,6],7],[[2,6],7],[[]],[[]],[[]],[[]],0,0,0,[[2,8],4],0,[[]],[[]],[[],4],[[],4],[[],4],[[],4],[[],9],[[],9],[[]],[[]],0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,[10,[[13,[[12,[11]]]]]],[[]],[[]],[[]],[[]],[[]],[[]],[[14,6],7],[[15,6],7],[[15,6],7],[[10,6],7],[[10,6],7],[[]],[16,15],[17,15],[18,15],[19,15],[[]],[20,15],[21,15],[22,10],[[]],[17,10],[16,10],[21,10],[18,10],[19,10],[15,10],[20,10],[[]],[[]],[[]],[10,15],[23],[23],[15,[[13,[24]]]],[[],25],[[],25],[[],4],[[],4],[[],4],[[],4],[[],4],[[],4],[[],9],[[],9],[[],9],[[]],[[]],[[]],0,[[]],[[]],0,[[26,27],[[28,[0]]]],[26,28],[[]],[[26,27,29],[[28,[29]]]],[[26,27,29],[[28,[30]]]],[26,[[28,[[31,[0]]]]]],[26,[[33,[32]]]],[[]],[[26,[35,[34]]],28],[[[35,[34]]],[[28,[26]]]],[[26,27,29],[[28,[0]]]],[[26,27,29],[[28,[0]]]],[26,[[28,[[13,[2]]]]]],[[],4],[[],4],[[],9],[26,[[28,[[31,[1]]]]]],[[]]],"c":[],"p":[[3,"UsageInfo"],[3,"MigrationInfo"],[8,"Deserializer"],[4,"Result"],[15,"bool"],[3,"Formatter"],[6,"Result"],[8,"Serializer"],[3,"TypeId"],[3,"Error"],[3,"Backtrace"],[3,"Mutex"],[4,"Option"],[4,"QuotaReason"],[4,"ErrorKind"],[3,"Error"],[3,"Utf8Error"],[4,"Error"],[4,"Error"],[3,"Error"],[3,"Interrupted"],[3,"Error"],[3,"Demand"],[8,"Error"],[3,"String"],[3,"Store"],[15,"str"],[6,"Result"],[4,"Value"],[15,"usize"],[3,"Vec"],[3,"SqlInterruptHandle"],[3,"Arc"],[3,"Path"],[8,"AsRef"]]}\ +}'); +if (typeof window !== 'undefined' && window.initSearch) {window.initSearch(searchIndex)}; +if (typeof exports !== 'undefined') {exports.searchIndex = searchIndex}; diff --git a/book/rust-docs/settings.html b/book/rust-docs/settings.html new file mode 100644 index 0000000000..3dd3eee653 --- /dev/null +++ b/book/rust-docs/settings.html @@ -0,0 +1 @@ +Rustdoc settings

Rustdoc settings

Back
\ No newline at end of file diff --git a/book/rust-docs/source-files.js b/book/rust-docs/source-files.js new file mode 100644 index 0000000000..b45699e571 --- /dev/null +++ b/book/rust-docs/source-files.js @@ -0,0 +1,36 @@ +var sourcesIndex = JSON.parse('{\ +"as_ohttp_client":["",[],["lib.rs"]],\ +"autofill":["",[["db",[["models",[],["address.rs","credit_card.rs","mod.rs"]]],["addresses.rs","credit_cards.rs","mod.rs","schema.rs","store.rs"]],["sync",[["address",[],["incoming.rs","mod.rs","outgoing.rs"]],["credit_card",[],["incoming.rs","mod.rs","outgoing.rs"]]],["common.rs","engine.rs","mod.rs"]]],["encryption.rs","error.rs","lib.rs"]],\ +"cli_support":["",[],["fxa_creds.rs","lib.rs","prompt.rs"]],\ +"crashtest":["",[],["lib.rs"]],\ +"embedded_uniffi_bindgen":["",[],["main.rs"]],\ +"error_support":["",[],["handling.rs","lib.rs","macros.rs","redact.rs","reporting.rs"]],\ +"error_support_macros":["",[],["lib.rs"]],\ +"examples_fxa_client":["",[],["devices.rs","main.rs","send_tab.rs"]],\ +"fxa_client":["",[["internal",[["commands",[],["mod.rs","send_tab.rs"]],["oauth",[],["attached_clients.rs"]]],["config.rs","device.rs","http_client.rs","mod.rs","oauth.rs","profile.rs","push.rs","scoped_keys.rs","scopes.rs","send_tab.rs","state_manager.rs","state_persistence.rs","telemetry.rs","util.rs"]]],["account.rs","auth.rs","device.rs","error.rs","lib.rs","profile.rs","push.rs","storage.rs","telemetry.rs","token.rs"]],\ +"interrupt_support":["",[],["error.rs","interruptee.rs","lib.rs","shutdown.rs","sql.rs"]],\ +"logins":["",[["sync",[],["engine.rs","merge.rs","mod.rs","payload.rs","update_plan.rs"]]],["db.rs","encryption.rs","error.rs","lib.rs","login.rs","schema.rs","store.rs","util.rs"]],\ +"nimbus":["",[["stateful",[["client",[],["fs_client.rs","http_client.rs","mod.rs","null_client.rs"]]],["behavior.rs","dbcache.rs","enrollment.rs","evaluator.rs","matcher.rs","mod.rs","nimbus_client.rs","persistence.rs","updating.rs"]]],["defaults.rs","enrollment.rs","error.rs","evaluator.rs","json.rs","lib.rs","metrics.rs","sampling.rs","schema.rs","strings.rs","targeting.rs","versioning.rs"]],\ +"nimbus_cli":["",[["output",[],["deeplink.rs","features.rs","fetch.rs","fml_cli.rs","info.rs","mod.rs","server.rs"]],["sources",[],["experiment.rs","experiment_list.rs","filter.rs","manifest.rs","mod.rs"]],["updater",[],["mod.rs","taskcluster.rs"]]],["cli.rs","cmd.rs","config.rs","feature_utils.rs","main.rs","protocol.rs","value_utils.rs"]],\ +"nimbus_fml":["",[["backends",[["kotlin",[["gen_structs",[],["bundled.rs","common.rs","enum_.rs","feature.rs","filters.rs","imports.rs","mod.rs","object.rs","primitives.rs","structural.rs"]]],["mod.rs"]],["swift",[["gen_structs",[],["bundled.rs","common.rs","enum_.rs","feature.rs","filters.rs","imports.rs","mod.rs","object.rs","primitives.rs","structural.rs"]]],["mod.rs"]]],["experimenter_manifest.rs","frontend_manifest.rs","mod.rs"]],["command_line",[],["commands.rs","mod.rs","workflows.rs"]],["util",[],["loaders.rs","mod.rs"]]],["defaults_merger.rs","error.rs","frontend.rs","intermediate_representation.rs","main.rs","parser.rs"]],\ +"nss":["",[["pk11",[],["context.rs","mod.rs","slot.rs","sym_key.rs","types.rs"]]],["aes.rs","cert.rs","ec.rs","ecdh.rs","error.rs","lib.rs","pbkdf2.rs","pkixc.rs","secport.rs","util.rs"]],\ +"nss_build_common":["",[],["lib.rs"]],\ +"nss_sys":["",[["bindings",[],["blapit.rs","certdb.rs","keyhi.rs","keythi.rs","mod.rs","nss.rs","pk11pub.rs","pkcs11n.rs","pkcs11t.rs","pkixc.rs","plarena.rs","prerror.rs","prtypes.rs","secasn1t.rs","seccomon.rs","secitem.rs","seckey.rs","secmodt.rs","secoid.rs","secoidt.rs","secport.rs"]]],["lib.rs"]],\ +"places":["",[["api",[],["history.rs","matcher.rs","mod.rs","places_api.rs"]],["bookmark_sync",[],["engine.rs","incoming.rs","mod.rs","record.rs"]],["db",[["tx",[],["coop_transaction.rs","mod.rs"]]],["db.rs","mod.rs","schema.rs"]],["history_sync",[],["engine.rs","mod.rs","plan.rs","record.rs"]],["import",[["ios",[],["history.rs"]]],["common.rs","ios.rs","mod.rs"]],["storage",[["bookmarks",[],["conversions.rs","fetch.rs","json_tree.rs","root_guid.rs"]],["history",[],["actions.rs"]]],["bookmarks.rs","history.rs","history_metadata.rs","mod.rs","tags.rs"]],["types",[],["visit_transition_set.rs"]]],["error.rs","ffi.rs","frecency.rs","hash.rs","lib.rs","match_impl.rs","observation.rs","types.rs","util.rs"]],\ +"protobuf_gen":["",[],["main.rs"]],\ +"push":["",[["internal",[["communications",[],["rate_limiter.rs"]],["storage",[],["db.rs","mod.rs","record.rs","schema.rs"]]],["communications.rs","config.rs","crypto.rs","mod.rs","push_manager.rs"]]],["error.rs","lib.rs"]],\ +"rc_crypto":["",[["aead",[],["aes_cbc.rs","aes_gcm.rs"]]],["aead.rs","agreement.rs","constant_time.rs","contentsignature.rs","digest.rs","ece_crypto.rs","error.rs","hawk_crypto.rs","hkdf.rs","hmac.rs","lib.rs","pbkdf2.rs","rand.rs","signature.rs"]],\ +"rc_log_ffi":["",[],["android.rs","lib.rs","settable_log.rs"]],\ +"remote_settings":["",[],["client.rs","config.rs","error.rs","lib.rs"]],\ +"restmail_client":["",[],["error.rs","lib.rs"]],\ +"sql_support":["",[],["conn_ext.rs","debug_tools.rs","each_chunk.rs","lib.rs","maybe_cached.rs","open_database.rs","repeat.rs"]],\ +"sync15":["",[["bso",[],["content.rs","crypto.rs","mod.rs","test_utils.rs"]],["client",[],["coll_state.rs","coll_update.rs","collection_keys.rs","mod.rs","request.rs","state.rs","status.rs","storage_client.rs","sync.rs","sync_multiple.rs","token.rs","util.rs"]],["clients_engine",[],["engine.rs","mod.rs","record.rs","ser.rs"]],["engine",[],["bridged_engine.rs","mod.rs","request.rs","sync_engine.rs"]]],["client_types.rs","device_type.rs","enc_payload.rs","error.rs","key_bundle.rs","lib.rs","record_types.rs","server_timestamp.rs","telemetry.rs"]],\ +"sync_guid":["",[],["lib.rs","rusqlite_support.rs","serde_support.rs"]],\ +"sync_manager":["",[],["error.rs","lib.rs","manager.rs","types.rs"]],\ +"tabs":["",[["sync",[],["bridge.rs","engine.rs","mod.rs","record.rs"]]],["error.rs","lib.rs","schema.rs","storage.rs","store.rs"]],\ +"types":["",[],["lib.rs"]],\ +"viaduct":["",[["backend",[],["ffi.rs"]],["headers",[],["name.rs"]]],["backend.rs","error.rs","headers.rs","lib.rs","mozilla.appservices.httpconfig.protobuf.rs","settings.rs"]],\ +"viaduct_reqwest":["",[],["lib.rs"]],\ +"webext_storage":["",[["sync",[],["bridge.rs","incoming.rs","mod.rs","outgoing.rs"]]],["api.rs","db.rs","error.rs","ffi.rs","lib.rs","migration.rs","schema.rs","store.rs"]]\ +}'); +createSourceSidebar(); diff --git a/book/rust-docs/sql_support/all.html b/book/rust-docs/sql_support/all.html new file mode 100644 index 0000000000..78d079347f --- /dev/null +++ b/book/rust-docs/sql_support/all.html @@ -0,0 +1 @@ +List of all items in this crate
\ No newline at end of file diff --git a/book/rust-docs/sql_support/conn_ext/struct.Conn.html b/book/rust-docs/sql_support/conn_ext/struct.Conn.html new file mode 100644 index 0000000000..ae08597133 --- /dev/null +++ b/book/rust-docs/sql_support/conn_ext/struct.Conn.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../sql_support/struct.Conn.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/sql_support/conn_ext/struct.UncheckedTransaction.html b/book/rust-docs/sql_support/conn_ext/struct.UncheckedTransaction.html new file mode 100644 index 0000000000..717abd809b --- /dev/null +++ b/book/rust-docs/sql_support/conn_ext/struct.UncheckedTransaction.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../sql_support/struct.UncheckedTransaction.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/sql_support/conn_ext/trait.ConnExt.html b/book/rust-docs/sql_support/conn_ext/trait.ConnExt.html new file mode 100644 index 0000000000..caf6c3cfd1 --- /dev/null +++ b/book/rust-docs/sql_support/conn_ext/trait.ConnExt.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../sql_support/trait.ConnExt.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/sql_support/debug_tools/fn.define_debug_functions.html b/book/rust-docs/sql_support/debug_tools/fn.define_debug_functions.html new file mode 100644 index 0000000000..d5d1a765e3 --- /dev/null +++ b/book/rust-docs/sql_support/debug_tools/fn.define_debug_functions.html @@ -0,0 +1,18 @@ +define_debug_functions in sql_support::debug_tools - Rust
pub fn define_debug_functions(c: &Connection) -> Result<()>
Expand description

You can call this function to add all sql functions provided by this module +to your connection. The list of supported functions is described below.

+

Note: you must enable the debug-tools feature for these functions to perform +as described. If this feature is not enabled, functions of the same name will still +exist, but will be no-ops.

+

dbg

+

dbg() is a simple sql function that prints all args to stderr and returns the last argument. +It’s useful for instrumenting existing WHERE statements - eg, changing a statement

+
WHERE a.bar = foo
+
+

to

+
WHERE dbg("a bar", a.bar) = foo
+
+

will print the literal a bar followed by the value of a.bar, then return a.bar, +so the semantics of the WHERE statement do not change. +If you have a series of statements being executed (ie, statements separated by a ;), +adding a new statement like SELECT dbg("hello"); is also possible.

+
\ No newline at end of file diff --git a/book/rust-docs/sql_support/debug_tools/fn.print_query.html b/book/rust-docs/sql_support/debug_tools/fn.print_query.html new file mode 100644 index 0000000000..f5d9d84c1f --- /dev/null +++ b/book/rust-docs/sql_support/debug_tools/fn.print_query.html @@ -0,0 +1,3 @@ +print_query in sql_support::debug_tools - Rust
pub fn print_query(conn: &Connection, query: &str) -> Result<()>
Expand description

Print the entire contents of an arbitrary query. A common usage would be to pass +SELECT * FROM table

+
\ No newline at end of file diff --git a/book/rust-docs/sql_support/debug_tools/index.html b/book/rust-docs/sql_support/debug_tools/index.html new file mode 100644 index 0000000000..cc9bb21976 --- /dev/null +++ b/book/rust-docs/sql_support/debug_tools/index.html @@ -0,0 +1,3 @@ +sql_support::debug_tools - Rust

Functions

  • You can call this function to add all sql functions provided by this module +to your connection. The list of supported functions is described below.
  • Print the entire contents of an arbitrary query. A common usage would be to pass +SELECT * FROM table
\ No newline at end of file diff --git a/book/rust-docs/sql_support/debug_tools/sidebar-items.js b/book/rust-docs/sql_support/debug_tools/sidebar-items.js new file mode 100644 index 0000000000..950e53d952 --- /dev/null +++ b/book/rust-docs/sql_support/debug_tools/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["define_debug_functions","print_query"]}; \ No newline at end of file diff --git a/book/rust-docs/sql_support/each_chunk/fn.default_max_variable_number.html b/book/rust-docs/sql_support/each_chunk/fn.default_max_variable_number.html new file mode 100644 index 0000000000..796b47bbe0 --- /dev/null +++ b/book/rust-docs/sql_support/each_chunk/fn.default_max_variable_number.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../sql_support/fn.default_max_variable_number.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/sql_support/each_chunk/fn.each_chunk.html b/book/rust-docs/sql_support/each_chunk/fn.each_chunk.html new file mode 100644 index 0000000000..2f1397dba7 --- /dev/null +++ b/book/rust-docs/sql_support/each_chunk/fn.each_chunk.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../sql_support/fn.each_chunk.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/sql_support/each_chunk/fn.each_chunk_mapped.html b/book/rust-docs/sql_support/each_chunk/fn.each_chunk_mapped.html new file mode 100644 index 0000000000..5db74f71fe --- /dev/null +++ b/book/rust-docs/sql_support/each_chunk/fn.each_chunk_mapped.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../sql_support/fn.each_chunk_mapped.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/sql_support/each_chunk/fn.each_sized_chunk.html b/book/rust-docs/sql_support/each_chunk/fn.each_sized_chunk.html new file mode 100644 index 0000000000..f9241aadb9 --- /dev/null +++ b/book/rust-docs/sql_support/each_chunk/fn.each_sized_chunk.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../sql_support/fn.each_sized_chunk.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/sql_support/each_chunk/fn.each_sized_chunk_mapped.html b/book/rust-docs/sql_support/each_chunk/fn.each_sized_chunk_mapped.html new file mode 100644 index 0000000000..99bf114c5e --- /dev/null +++ b/book/rust-docs/sql_support/each_chunk/fn.each_sized_chunk_mapped.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../sql_support/fn.each_sized_chunk_mapped.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/sql_support/enum.MaybeCached.html b/book/rust-docs/sql_support/enum.MaybeCached.html new file mode 100644 index 0000000000..e3861c2d06 --- /dev/null +++ b/book/rust-docs/sql_support/enum.MaybeCached.html @@ -0,0 +1,369 @@ +MaybeCached in sql_support - Rust
pub enum MaybeCached<'conn> {
+    Uncached(Statement<'conn>),
+    Cached(CachedStatement<'conn>),
+}
Expand description

MaybeCached is a type that can be used to help abstract +over cached and uncached rusqlite statements in a transparent manner.

+

Variants§

§

Uncached(Statement<'conn>)

§

Cached(CachedStatement<'conn>)

Implementations§

source§

impl<'conn> MaybeCached<'conn>

source

pub fn prepare( + conn: &'conn Connection, + sql: &str, + cached: bool +) -> Result<MaybeCached<'conn>>

Methods from Deref<Target = Statement<'conn>>§

pub fn column_names(&self) -> Vec<&str, Global>

Get all the column names in the result set of the prepared statement.

+

If associated DB schema can be altered concurrently, you should make +sure that current statement has already been stepped once before +calling this method.

+

pub fn column_count(&self) -> usize

Return the number of columns in the result set returned by the prepared +statement.

+

If associated DB schema can be altered concurrently, you should make +sure that current statement has already been stepped once before +calling this method.

+

pub fn column_name(&self, col: usize) -> Result<&str, Error>

Returns the name assigned to a particular column in the result set +returned by the prepared statement.

+

If associated DB schema can be altered concurrently, you should make +sure that current statement has already been stepped once before +calling this method.

+
Failure
+

Returns an Error::InvalidColumnIndex if idx is outside the valid +column range for this row.

+

Panics when column name is not valid UTF-8.

+

pub fn column_index(&self, name: &str) -> Result<usize, Error>

Returns the column index in the result set for a given column name.

+

If there is no AS clause then the name of the column is unspecified and +may change from one release of SQLite to the next.

+

If associated DB schema can be altered concurrently, you should make +sure that current statement has already been stepped once before +calling this method.

+
Failure
+

Will return an Error::InvalidColumnName when there is no column with +the specified name.

+

pub fn columns(&self) -> Vec<Column<'_>, Global>

Returns a slice describing the columns of the result of the query.

+

If associated DB schema can be altered concurrently, you should make +sure that current statement has already been stepped once before +calling this method.

+

pub fn execute<P>(&mut self, params: P) -> Result<usize, Error>where + P: Params,

Execute the prepared statement.

+

On success, returns the number of rows that were changed or inserted or +deleted (via sqlite3_changes).

+
Example
Use with positional parameters
+
fn update_rows(conn: &Connection) -> Result<()> {
+    let mut stmt = conn.prepare("UPDATE foo SET bar = ?1 WHERE qux = ?2")?;
+    // For a single parameter, or a parameter where all the values have
+    // the same type, just passing an array is simplest.
+    stmt.execute([2i32])?;
+    // The `rusqlite::params!` macro is mostly useful when the parameters do not
+    // all have the same type, or if there are more than 32 parameters
+    // at once, but it can be used in other cases.
+    stmt.execute(params![1i32])?;
+    // However, it's not required, many cases are fine as:
+    stmt.execute(&[&2i32])?;
+    // Or even:
+    stmt.execute([2i32])?;
+    // If you really want to, this is an option as well.
+    stmt.execute((2i32,))?;
+    Ok(())
+}
+
Heterogeneous positional parameters
+
use rusqlite::{Connection, Result};
+fn store_file(conn: &Connection, path: &str, data: &[u8]) -> Result<()> {
+    let query = "INSERT OR REPLACE INTO files(path, hash, data) VALUES (?1, ?2, ?3)";
+    let mut stmt = conn.prepare_cached(query)?;
+    let hash: [u8; 32] = sha256(data);
+    // The easiest way to pass positional parameters of have several
+    // different types is by using a tuple.
+    stmt.execute((path, hash, data))?;
+    // Using the `params!` macro also works, and supports longer parameter lists:
+    stmt.execute(rusqlite::params![path, hash, data])?;
+    Ok(())
+}
+
Use with named parameters
+
fn insert(conn: &Connection) -> Result<()> {
+    let mut stmt = conn.prepare("INSERT INTO test (key, value) VALUES (:key, :value)")?;
+    // The `rusqlite::named_params!` macro (like `params!`) is useful for heterogeneous
+    // sets of parameters (where all parameters are not the same type), or for queries
+    // with many (more than 32) statically known parameters.
+    stmt.execute(named_params! { ":key": "one", ":val": 2 })?;
+    // However, named parameters can also be passed like:
+    stmt.execute(&[(":key", "three"), (":val", "four")])?;
+    // Or even: (note that a &T is required for the value type, currently)
+    stmt.execute(&[(":key", &100), (":val", &200)])?;
+    Ok(())
+}
+
Use without parameters
+
fn delete_all(conn: &Connection) -> Result<()> {
+    let mut stmt = conn.prepare("DELETE FROM users")?;
+    stmt.execute([])?;
+    Ok(())
+}
+
Failure
+

Will return Err if binding parameters fails, the executed statement +returns rows (in which case query should be used instead), or the +underlying SQLite call fails.

+

pub fn insert<P>(&mut self, params: P) -> Result<i64, Error>where + P: Params,

Execute an INSERT and return the ROWID.

+
Note
+

This function is a convenience wrapper around +execute() intended for queries that insert a +single item. It is possible to misuse this function in a way that it +cannot detect, such as by calling it on a statement which updates +a single item rather than inserting one. Please don’t do that.

+
Failure
+

Will return Err if no row is inserted or many rows are inserted.

+

pub fn query<P>(&mut self, params: P) -> Result<Rows<'_>, Error>where + P: Params,

Execute the prepared statement, returning a handle to the resulting +rows.

+

Due to lifetime restrictions, the rows handle returned by query does +not implement the Iterator trait. Consider using +query_map or +query_and_then instead, which do.

+
Example
Use without parameters
+
fn get_names(conn: &Connection) -> Result<Vec<String>> {
+    let mut stmt = conn.prepare("SELECT name FROM people")?;
+    let mut rows = stmt.query([])?;
+
+    let mut names = Vec::new();
+    while let Some(row) = rows.next()? {
+        names.push(row.get(0)?);
+    }
+
+    Ok(names)
+}
+
Use with positional parameters
+
fn query(conn: &Connection, name: &str) -> Result<()> {
+    let mut stmt = conn.prepare("SELECT * FROM test where name = ?1")?;
+    let mut rows = stmt.query(rusqlite::params![name])?;
+    while let Some(row) = rows.next()? {
+        // ...
+    }
+    Ok(())
+}
+

Or, equivalently (but without the [crate::params!] macro).

+ +
fn query(conn: &Connection, name: &str) -> Result<()> {
+    let mut stmt = conn.prepare("SELECT * FROM test where name = ?1")?;
+    let mut rows = stmt.query([name])?;
+    while let Some(row) = rows.next()? {
+        // ...
+    }
+    Ok(())
+}
+
Use with named parameters
+
fn query(conn: &Connection) -> Result<()> {
+    let mut stmt = conn.prepare("SELECT * FROM test where name = :name")?;
+    let mut rows = stmt.query(&[(":name", "one")])?;
+    while let Some(row) = rows.next()? {
+        // ...
+    }
+    Ok(())
+}
+

Note, the named_params! macro is provided for syntactic convenience, +and so the above example could also be written as:

+ +
fn query(conn: &Connection) -> Result<()> {
+    let mut stmt = conn.prepare("SELECT * FROM test where name = :name")?;
+    let mut rows = stmt.query(named_params! { ":name": "one" })?;
+    while let Some(row) = rows.next()? {
+        // ...
+    }
+    Ok(())
+}
+
Failure
+

Will return Err if binding parameters fails.

+

pub fn query_map<T, P, F>( + &mut self, + params: P, + f: F +) -> Result<MappedRows<'_, F>, Error>where + P: Params, + F: FnMut(&Row<'_>) -> Result<T, Error>,

Executes the prepared statement and maps a function over the resulting +rows, returning an iterator over the mapped function results.

+

f is used to transform the streaming iterator into a standard +iterator.

+

This is equivalent to stmt.query(params)?.mapped(f).

+
Example
Use with positional params
+
fn get_names(conn: &Connection) -> Result<Vec<String>> {
+    let mut stmt = conn.prepare("SELECT name FROM people")?;
+    let rows = stmt.query_map([], |row| row.get(0))?;
+
+    let mut names = Vec::new();
+    for name_result in rows {
+        names.push(name_result?);
+    }
+
+    Ok(names)
+}
+
Use with named params
+
fn get_names(conn: &Connection) -> Result<Vec<String>> {
+    let mut stmt = conn.prepare("SELECT name FROM people WHERE id = :id")?;
+    let rows = stmt.query_map(&[(":id", &"one")], |row| row.get(0))?;
+
+    let mut names = Vec::new();
+    for name_result in rows {
+        names.push(name_result?);
+    }
+
+    Ok(names)
+}
+
Failure
+

Will return Err if binding parameters fails.

+

pub fn query_and_then<T, E, P, F>( + &mut self, + params: P, + f: F +) -> Result<AndThenRows<'_, F>, Error>where + P: Params, + E: From<Error>, + F: FnMut(&Row<'_>) -> Result<T, E>,

Executes the prepared statement and maps a function over the resulting +rows, where the function returns a Result with Error type +implementing std::convert::From<Error> (so errors can be unified).

+

This is equivalent to stmt.query(params)?.and_then(f).

+
Example
Use with named params
+
struct Person {
+    name: String,
+};
+
+fn name_to_person(name: String) -> Result<Person> {
+    // ... check for valid name
+    Ok(Person { name })
+}
+
+fn get_names(conn: &Connection) -> Result<Vec<Person>> {
+    let mut stmt = conn.prepare("SELECT name FROM people WHERE id = :id")?;
+    let rows = stmt.query_and_then(&[(":id", "one")], |row| name_to_person(row.get(0)?))?;
+
+    let mut persons = Vec::new();
+    for person_result in rows {
+        persons.push(person_result?);
+    }
+
+    Ok(persons)
+}
+
Use with positional params
+
fn get_names(conn: &Connection) -> Result<Vec<String>> {
+    let mut stmt = conn.prepare("SELECT name FROM people WHERE id = ?1")?;
+    let rows = stmt.query_and_then(["one"], |row| row.get::<_, String>(0))?;
+
+    let mut persons = Vec::new();
+    for person_result in rows {
+        persons.push(person_result?);
+    }
+
+    Ok(persons)
+}
+
Failure
+

Will return Err if binding parameters fails.

+

pub fn exists<P>(&mut self, params: P) -> Result<bool, Error>where + P: Params,

Return true if a query in the SQL statement it executes returns one +or more rows and false if the SQL returns an empty set.

+

pub fn query_row<T, P, F>(&mut self, params: P, f: F) -> Result<T, Error>where + P: Params, + F: FnOnce(&Row<'_>) -> Result<T, Error>,

Convenience method to execute a query that is expected to return a +single row.

+

If the query returns more than one row, all rows except the first are +ignored.

+

Returns Err(QueryReturnedNoRows) if no results are returned. If the +query truly is optional, you can call +.optional() on the result of +this to get a Result<Option<T>> (requires that the trait +rusqlite::OptionalExtension is imported).

+
Failure
+

Will return Err if the underlying SQLite call fails.

+

pub fn parameter_index(&self, name: &str) -> Result<Option<usize>, Error>

Return the (one-based) index of an SQL parameter given its name.

+

Note that the initial “:” or “$” or “@” or “?” used to specify the +parameter is included as part of the name.

+ +
fn example(conn: &Connection) -> Result<()> {
+    let stmt = conn.prepare("SELECT * FROM test WHERE name = :example")?;
+    let index = stmt.parameter_index(":example")?;
+    assert_eq!(index, Some(1));
+    Ok(())
+}
+
Failure
+

Will return Err if name is invalid. Will return Ok(None) if the name +is valid but not a bound parameter of this statement.

+

pub fn parameter_name(&self, index: usize) -> Option<&str>

Return the SQL parameter name given its (one-based) index (the inverse +of [Statement::parameter_index]).

+ +
fn example(conn: &Connection) -> Result<()> {
+    let stmt = conn.prepare("SELECT * FROM test WHERE name = :example")?;
+    let index = stmt.parameter_name(1);
+    assert_eq!(index, Some(":example"));
+    Ok(())
+}
+
Failure
+

Will return None if the column index is out of bounds or if the +parameter is positional.

+

pub fn parameter_count(&self) -> usize

Return the number of parameters that can be bound to this statement.

+

pub fn raw_bind_parameter<T>( + &mut self, + one_based_col_index: usize, + param: T +) -> Result<(), Error>where + T: ToSql,

Low level API to directly bind a parameter to a given index.

+

Note that the index is one-based, that is, the first parameter index is +1 and not 0. This is consistent with the SQLite API and the values given +to parameters bound as ?NNN.

+

The valid values for one_based_col_index begin at 1, and end at +[Statement::parameter_count], inclusive.

+
Caveats
+

This should not generally be used, but is available for special cases +such as:

+
    +
  • binding parameters where a gap exists.
  • +
  • binding named and positional parameters in the same query.
  • +
  • separating parameter binding from query execution.
  • +
+

In general, statements that have had any parameters bound this way +should have all parameters bound this way, and be queried or executed +by [Statement::raw_query] or [Statement::raw_execute], other usage +is unsupported and will likely, probably in surprising ways.

+

That is: Do not mix the “raw” statement functions with the rest of the +API, or the results may be surprising, and may even change in future +versions without comment.

+
Example
+
fn query(conn: &Connection) -> Result<()> {
+    let mut stmt = conn.prepare("SELECT * FROM test WHERE name = :name AND value > ?2")?;
+    let name_index = stmt.parameter_index(":name")?.expect("No such parameter");
+    stmt.raw_bind_parameter(name_index, "foo")?;
+    stmt.raw_bind_parameter(2, 100)?;
+    let mut rows = stmt.raw_query();
+    while let Some(row) = rows.next()? {
+        // ...
+    }
+    Ok(())
+}
+

pub fn raw_execute(&mut self) -> Result<usize, Error>

Low level API to execute a statement given that all parameters were +bound explicitly with the [Statement::raw_bind_parameter] API.

+
Caveats
+

Any unbound parameters will have NULL as their value.

+

This should not generally be used outside of special cases, and +functions in the [Statement::execute] family should be preferred.

+
Failure
+

Will return Err if the executed statement returns rows (in which case +query should be used instead), or the underlying SQLite call fails.

+

pub fn raw_query(&mut self) -> Rows<'_>

Low level API to get Rows for this query given that all parameters +were bound explicitly with the [Statement::raw_bind_parameter] API.

+
Caveats
+

Any unbound parameters will have NULL as their value.

+

This should not generally be used outside of special cases, and +functions in the [Statement::query] family should be preferred.

+

Note that if the SQL does not return results, [Statement::raw_execute] +should be used instead.

+

pub fn expanded_sql(&self) -> Option<String>

Returns a string containing the SQL text of prepared statement with +bound parameters expanded.

+

pub fn get_status(&self, status: StatementStatus) -> i32

Get the value for one of the status counters for this statement.

+

pub fn reset_status(&self, status: StatementStatus) -> i32

Reset the value of one of the status counters for this statement, +returning the value it had before resetting.

+

pub fn is_explain(&self) -> i32

Returns 1 if the prepared statement is an EXPLAIN statement, +or 2 if the statement is an EXPLAIN QUERY PLAN, +or 0 if it is an ordinary statement or a NULL pointer.

+

pub fn readonly(&self) -> bool

Returns true if the statement is read only.

+

pub fn clear_bindings(&mut self)

Reset all bindings

+

Trait Implementations§

source§

impl<'conn> Deref for MaybeCached<'conn>

§

type Target = Statement<'conn>

The resulting type after dereferencing.
source§

fn deref(&self) -> &Statement<'conn>

Dereferences the value.
source§

impl<'conn> DerefMut for MaybeCached<'conn>

source§

fn deref_mut(&mut self) -> &mut Statement<'conn>

Mutably dereferences the value.
source§

impl<'conn> From<CachedStatement<'conn>> for MaybeCached<'conn>

source§

fn from(stmt: CachedStatement<'conn>) -> Self

Converts to this type from the input type.
source§

impl<'conn> From<Statement<'conn>> for MaybeCached<'conn>

source§

fn from(stmt: Statement<'conn>) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

§

impl<'conn> !RefUnwindSafe for MaybeCached<'conn>

§

impl<'conn> !Send for MaybeCached<'conn>

§

impl<'conn> !Sync for MaybeCached<'conn>

§

impl<'conn> Unpin for MaybeCached<'conn>

§

impl<'conn> !UnwindSafe for MaybeCached<'conn>

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/sql_support/fn.default_max_variable_number.html b/book/rust-docs/sql_support/fn.default_max_variable_number.html new file mode 100644 index 0000000000..4d441c3a6f --- /dev/null +++ b/book/rust-docs/sql_support/fn.default_max_variable_number.html @@ -0,0 +1,8 @@ +default_max_variable_number in sql_support - Rust
pub fn default_max_variable_number() -> usize
Expand description

Returns SQLITE_LIMIT_VARIABLE_NUMBER as read from an in-memory connection and cached. +connection and cached. That means this will return the wrong value if it’s set to a lower +value for a connection using this will return the wrong thing, but doing so is rare enough +that we explicitly don’t support it (why would you want to lower this at runtime?).

+

If you call this and the actual value was set to a negative number or zero (nothing prevents +this beyond a warning in the SQLite documentation), we panic. However, it’s unlikely you can +run useful queries if this happened anyway.

+
\ No newline at end of file diff --git a/book/rust-docs/sql_support/fn.each_chunk.html b/book/rust-docs/sql_support/fn.each_chunk.html new file mode 100644 index 0000000000..61220df0b2 --- /dev/null +++ b/book/rust-docs/sql_support/fn.each_chunk.html @@ -0,0 +1,10 @@ +each_chunk in sql_support - Rust

Function sql_support::each_chunk

source ·
pub fn each_chunk<'a, T, E, F>(items: &'a [T], do_chunk: F) -> Result<(), E>where
+    T: 'a,
+    F: FnMut(&'a [T], usize) -> Result<(), E>,
Expand description

Helper for the case where you have a &[impl ToSql] of arbitrary length, but need one +of no more than the connection’s MAX_VARIABLE_NUMBER (rather, +default_max_variable_number()). This is useful when performing batched updates.

+

The do_chunk callback is called with a slice of no more than default_max_variable_number() +items as it’s first argument, and the offset from the start as it’s second.

+

See each_chunk_mapped for the case where T doesn’t implement ToSql, but can be +converted to something that does.

+
\ No newline at end of file diff --git a/book/rust-docs/sql_support/fn.each_chunk_mapped.html b/book/rust-docs/sql_support/fn.each_chunk_mapped.html new file mode 100644 index 0000000000..1b910ca537 --- /dev/null +++ b/book/rust-docs/sql_support/fn.each_chunk_mapped.html @@ -0,0 +1,11 @@ +each_chunk_mapped in sql_support - Rust
pub fn each_chunk_mapped<'a, T, U, E, Mapper, DoChunk>(
+    items: &'a [T],
+    to_sql: Mapper,
+    do_chunk: DoChunk
+) -> Result<(), E>where
+    T: 'a,
+    U: ToSql + 'a,
+    Mapper: Fn(&'a T) -> U,
+    DoChunk: FnMut(Map<Iter<'a, T>, &Mapper>, usize) -> Result<(), E>,
Expand description

A version of each_chunk for the case when the conversion to to_sql requires an custom +intermediate step. For example, you might want to grab a property off of an arrray of records

+
\ No newline at end of file diff --git a/book/rust-docs/sql_support/fn.each_sized_chunk.html b/book/rust-docs/sql_support/fn.each_sized_chunk.html new file mode 100644 index 0000000000..3acdfb5c26 --- /dev/null +++ b/book/rust-docs/sql_support/fn.each_sized_chunk.html @@ -0,0 +1,7 @@ +each_sized_chunk in sql_support - Rust
pub fn each_sized_chunk<'a, T, E, F>(
+    items: &'a [T],
+    chunk_size: usize,
+    do_chunk: F
+) -> Result<(), E>where
+    T: 'a,
+    F: FnMut(&'a [T], usize) -> Result<(), E>,
\ No newline at end of file diff --git a/book/rust-docs/sql_support/fn.each_sized_chunk_mapped.html b/book/rust-docs/sql_support/fn.each_sized_chunk_mapped.html new file mode 100644 index 0000000000..db7572499f --- /dev/null +++ b/book/rust-docs/sql_support/fn.each_sized_chunk_mapped.html @@ -0,0 +1,16 @@ +each_sized_chunk_mapped in sql_support - Rust
pub fn each_sized_chunk_mapped<'a, T, U, E, Mapper, DoChunk>(
+    items: &'a [T],
+    chunk_size: usize,
+    to_sql: Mapper,
+    do_chunk: DoChunk
+) -> Result<(), E>where
+    T: 'a,
+    U: ToSql + 'a,
+    Mapper: Fn(&'a T) -> U,
+    DoChunk: FnMut(Map<Iter<'a, T>, &Mapper>, usize) -> Result<(), E>,
Expand description

Utility to help perform batched updates, inserts, queries, etc. This is the low-level version +of this utility which is wrapped by each_chunk and each_chunk_mapped, and it allows you to +provide both the mapping function, and the chunk size.

+

Note: mapped basically just refers to the translating of T to some U where U: ToSql +using the to_sql function. This is useful for e.g. inserting the IDs of a large list +of records.

+
\ No newline at end of file diff --git a/book/rust-docs/sql_support/fn.escape_string_for_pragma.html b/book/rust-docs/sql_support/fn.escape_string_for_pragma.html new file mode 100644 index 0000000000..d62d3ff13d --- /dev/null +++ b/book/rust-docs/sql_support/fn.escape_string_for_pragma.html @@ -0,0 +1,5 @@ +escape_string_for_pragma in sql_support - Rust
pub fn escape_string_for_pragma(s: &str) -> String
Expand description

In PRAGMA foo=‘bar’, 'bar' must be a constant string (it cannot be a +bound parameter), so we need to escape manually. According to +https://www.sqlite.org/faq.html, the only character that must be escaped is +the single quote, which is escaped by placing two single quotes in a row.

+
\ No newline at end of file diff --git a/book/rust-docs/sql_support/fn.repeat_display.html b/book/rust-docs/sql_support/fn.repeat_display.html new file mode 100644 index 0000000000..3a53178430 --- /dev/null +++ b/book/rust-docs/sql_support/fn.repeat_display.html @@ -0,0 +1,15 @@ +repeat_display in sql_support - Rust
pub fn repeat_display<F>(
+    count: usize,
+    sep: &str,
+    fmt_one: F
+) -> RepeatDisplay<'_, F>where
+    F: Fn(usize, &mut Formatter<'_>) -> Result,
Expand description

Construct a RepeatDisplay that will repeatedly call fmt_one with a formatter count times, +separated by sep.

+

Example

+
assert_eq!(format!("{}", repeat_display(1, ",", |i, f| write!(f, "({},?)", i))),
+           "(0,?)");
+assert_eq!(format!("{}", repeat_display(2, ",", |i, f| write!(f, "({},?)", i))),
+           "(0,?),(1,?)");
+assert_eq!(format!("{}", repeat_display(3, ",", |i, f| write!(f, "({},?)", i))),
+           "(0,?),(1,?),(2,?)");
+
\ No newline at end of file diff --git a/book/rust-docs/sql_support/fn.repeat_multi_values.html b/book/rust-docs/sql_support/fn.repeat_multi_values.html new file mode 100644 index 0000000000..ac4f8189dc --- /dev/null +++ b/book/rust-docs/sql_support/fn.repeat_multi_values.html @@ -0,0 +1,12 @@ +repeat_multi_values in sql_support - Rust
pub fn repeat_multi_values(
+    num_values: usize,
+    vars_per_value: usize
+) -> impl Display
Expand description

Returns a value that formats as num_values instances of (?,?,?,...) (where there are +vars_per_value question marks separated by commas in between the ?s).

+

Panics if vars_per_value is zero (however, num_values is allowed to be zero).

+

Example

+
assert_eq!(format!("{}", repeat_multi_values(0, 2)), "");
+assert_eq!(format!("{}", repeat_multi_values(1, 5)), "(?,?,?,?,?)");
+assert_eq!(format!("{}", repeat_multi_values(2, 3)), "(?,?,?),(?,?,?)");
+assert_eq!(format!("{}", repeat_multi_values(3, 1)), "(?),(?),(?)");
+
\ No newline at end of file diff --git a/book/rust-docs/sql_support/fn.repeat_sql_values.html b/book/rust-docs/sql_support/fn.repeat_sql_values.html new file mode 100644 index 0000000000..78f8f50437 --- /dev/null +++ b/book/rust-docs/sql_support/fn.repeat_sql_values.html @@ -0,0 +1,7 @@ +repeat_sql_values in sql_support - Rust
pub fn repeat_sql_values(count: usize) -> impl Display
Expand description

Returns a value that formats as count instances of (?) separated by commas.

+

Example

+
assert_eq!(format!("{}", repeat_sql_values(0)), "");
+assert_eq!(format!("{}", repeat_sql_values(1)), "(?)");
+assert_eq!(format!("{}", repeat_sql_values(2)), "(?),(?)");
+assert_eq!(format!("{}", repeat_sql_values(3)), "(?),(?),(?)");
+
\ No newline at end of file diff --git a/book/rust-docs/sql_support/fn.repeat_sql_vars.html b/book/rust-docs/sql_support/fn.repeat_sql_vars.html new file mode 100644 index 0000000000..9e299c6b0f --- /dev/null +++ b/book/rust-docs/sql_support/fn.repeat_sql_vars.html @@ -0,0 +1,7 @@ +repeat_sql_vars in sql_support - Rust
pub fn repeat_sql_vars(count: usize) -> impl Display
Expand description

Returns a value that formats as count instances of ? separated by commas.

+

Example

+
assert_eq!(format!("{}", repeat_sql_vars(0)), "");
+assert_eq!(format!("{}", repeat_sql_vars(1)), "?");
+assert_eq!(format!("{}", repeat_sql_vars(2)), "?,?");
+assert_eq!(format!("{}", repeat_sql_vars(3)), "?,?,?");
+
\ No newline at end of file diff --git a/book/rust-docs/sql_support/index.html b/book/rust-docs/sql_support/index.html new file mode 100644 index 0000000000..491ce9aaf3 --- /dev/null +++ b/book/rust-docs/sql_support/index.html @@ -0,0 +1,22 @@ +sql_support - Rust

Crate sql_support

source ·
Expand description

A crate with various sql/sqlcipher helpers.

+

Modules

Structs

  • Helper type for printing repeated strings more efficiently. You should use +repeat_display or one of the repeat_sql_* helpers to +construct it.
  • rusqlite, in an attempt to save us from ourselves, needs a mutable ref to a +connection to start a transaction. That is a bit of a PITA in some cases, so +we offer this as an alternative - but the responsibility of ensuring there +are no concurrent transactions is on our head.

Enums

  • MaybeCached is a type that can be used to help abstract +over cached and uncached rusqlite statements in a transparent manner.

Traits

  • This trait exists so that we can use these helpers on rusqlite::{Transaction, Connection}. +Note that you must import ConnExt in order to call these methods on anything.

Functions

  • Returns SQLITE_LIMIT_VARIABLE_NUMBER as read from an in-memory connection and cached. +connection and cached. That means this will return the wrong value if it’s set to a lower +value for a connection using this will return the wrong thing, but doing so is rare enough +that we explicitly don’t support it (why would you want to lower this at runtime?).
  • Helper for the case where you have a &[impl ToSql] of arbitrary length, but need one +of no more than the connection’s MAX_VARIABLE_NUMBER (rather, +default_max_variable_number()). This is useful when performing batched updates.
  • A version of each_chunk for the case when the conversion to to_sql requires an custom +intermediate step. For example, you might want to grab a property off of an arrray of records
  • Utility to help perform batched updates, inserts, queries, etc. This is the low-level version +of this utility which is wrapped by each_chunk and each_chunk_mapped, and it allows you to +provide both the mapping function, and the chunk size.
  • In PRAGMA foo=‘bar’, 'bar' must be a constant string (it cannot be a +bound parameter), so we need to escape manually. According to +https://www.sqlite.org/faq.html, the only character that must be escaped is +the single quote, which is escaped by placing two single quotes in a row.
  • Construct a RepeatDisplay that will repeatedly call fmt_one with a formatter count times, +separated by sep.
  • Returns a value that formats as num_values instances of (?,?,?,...) (where there are +vars_per_value question marks separated by commas in between the ?s).
  • Returns a value that formats as count instances of (?) separated by commas.
  • Returns a value that formats as count instances of ? separated by commas.
\ No newline at end of file diff --git a/book/rust-docs/sql_support/maybe_cached/enum.MaybeCached.html b/book/rust-docs/sql_support/maybe_cached/enum.MaybeCached.html new file mode 100644 index 0000000000..497682a787 --- /dev/null +++ b/book/rust-docs/sql_support/maybe_cached/enum.MaybeCached.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../sql_support/enum.MaybeCached.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/sql_support/open_database/enum.Error.html b/book/rust-docs/sql_support/open_database/enum.Error.html new file mode 100644 index 0000000000..203cfb8698 --- /dev/null +++ b/book/rust-docs/sql_support/open_database/enum.Error.html @@ -0,0 +1,19 @@ +Error in sql_support::open_database - Rust
pub enum Error {
+    IncompatibleVersion(u32),
+    Corrupt,
+    SqlError(Error),
+    RecoveryError(Error),
+}

Variants§

§

IncompatibleVersion(u32)

§

Corrupt

§

SqlError(Error)

§

RecoveryError(Error)

Trait Implementations§

source§

impl Debug for Error

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Display for Error

source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Error for Error

1.30.0 · source§

fn source(&self) -> Option<&(dyn Error + 'static)>

The lower-level source of this error, if any. Read more
1.0.0 · source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type based access to context intended for error reports. Read more
source§

impl From<Error> for Error

source§

fn from(value: Error) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

§

impl !RefUnwindSafe for Error

§

impl Send for Error

§

impl Sync for Error

§

impl Unpin for Error

§

impl !UnwindSafe for Error

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<E> Provider for Ewhere + E: Error + ?Sized,

source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (provide_any)
Data providers should implement this method to provide all values they are able to +provide by using demand. Read more
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/sql_support/open_database/fn.open_database.html b/book/rust-docs/sql_support/open_database/fn.open_database.html new file mode 100644 index 0000000000..15b4df4a07 --- /dev/null +++ b/book/rust-docs/sql_support/open_database/fn.open_database.html @@ -0,0 +1,4 @@ +open_database in sql_support::open_database - Rust
pub fn open_database<CI: ConnectionInitializer, P: AsRef<Path>>(
+    path: P,
+    connection_initializer: &CI
+) -> Result<Connection>
\ No newline at end of file diff --git a/book/rust-docs/sql_support/open_database/fn.open_database_with_flags.html b/book/rust-docs/sql_support/open_database/fn.open_database_with_flags.html new file mode 100644 index 0000000000..3aec402680 --- /dev/null +++ b/book/rust-docs/sql_support/open_database/fn.open_database_with_flags.html @@ -0,0 +1,5 @@ +open_database_with_flags in sql_support::open_database - Rust
pub fn open_database_with_flags<CI: ConnectionInitializer, P: AsRef<Path>>(
+    path: P,
+    open_flags: OpenFlags,
+    connection_initializer: &CI
+) -> Result<Connection>
\ No newline at end of file diff --git a/book/rust-docs/sql_support/open_database/fn.open_memory_database.html b/book/rust-docs/sql_support/open_database/fn.open_memory_database.html new file mode 100644 index 0000000000..daae1edf34 --- /dev/null +++ b/book/rust-docs/sql_support/open_database/fn.open_memory_database.html @@ -0,0 +1,3 @@ +open_memory_database in sql_support::open_database - Rust
pub fn open_memory_database<CI: ConnectionInitializer>(
+    conn_initializer: &CI
+) -> Result<Connection>
\ No newline at end of file diff --git a/book/rust-docs/sql_support/open_database/fn.open_memory_database_with_flags.html b/book/rust-docs/sql_support/open_database/fn.open_memory_database_with_flags.html new file mode 100644 index 0000000000..c435321d0b --- /dev/null +++ b/book/rust-docs/sql_support/open_database/fn.open_memory_database_with_flags.html @@ -0,0 +1,4 @@ +open_memory_database_with_flags in sql_support::open_database - Rust
pub fn open_memory_database_with_flags<CI: ConnectionInitializer>(
+    flags: OpenFlags,
+    conn_initializer: &CI
+) -> Result<Connection>
\ No newline at end of file diff --git a/book/rust-docs/sql_support/open_database/index.html b/book/rust-docs/sql_support/open_database/index.html new file mode 100644 index 0000000000..ae92bb4b7b --- /dev/null +++ b/book/rust-docs/sql_support/open_database/index.html @@ -0,0 +1 @@ +sql_support::open_database - Rust
\ No newline at end of file diff --git a/book/rust-docs/sql_support/open_database/sidebar-items.js b/book/rust-docs/sql_support/open_database/sidebar-items.js new file mode 100644 index 0000000000..756ad6dc65 --- /dev/null +++ b/book/rust-docs/sql_support/open_database/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["Error"],"fn":["open_database","open_database_with_flags","open_memory_database","open_memory_database_with_flags"],"mod":["test_utils"],"trait":["ConnectionInitializer"],"type":["Result"]}; \ No newline at end of file diff --git a/book/rust-docs/sql_support/open_database/test_utils/index.html b/book/rust-docs/sql_support/open_database/test_utils/index.html new file mode 100644 index 0000000000..924ec7ab62 --- /dev/null +++ b/book/rust-docs/sql_support/open_database/test_utils/index.html @@ -0,0 +1 @@ +sql_support::open_database::test_utils - Rust
\ No newline at end of file diff --git a/book/rust-docs/sql_support/open_database/test_utils/sidebar-items.js b/book/rust-docs/sql_support/open_database/test_utils/sidebar-items.js new file mode 100644 index 0000000000..8c5f3888c2 --- /dev/null +++ b/book/rust-docs/sql_support/open_database/test_utils/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"struct":["MigratedDatabaseFile"]}; \ No newline at end of file diff --git a/book/rust-docs/sql_support/open_database/test_utils/struct.MigratedDatabaseFile.html b/book/rust-docs/sql_support/open_database/test_utils/struct.MigratedDatabaseFile.html new file mode 100644 index 0000000000..4107279816 --- /dev/null +++ b/book/rust-docs/sql_support/open_database/test_utils/struct.MigratedDatabaseFile.html @@ -0,0 +1,24 @@ +MigratedDatabaseFile in sql_support::open_database::test_utils - Rust
pub struct MigratedDatabaseFile<CI: ConnectionInitializer> {
+    pub connection_initializer: CI,
+    pub path: PathBuf,
+    /* private fields */
+}

Fields§

§connection_initializer: CI§path: PathBuf

Implementations§

source§

impl<CI: ConnectionInitializer> MigratedDatabaseFile<CI>

source

pub fn new(connection_initializer: CI, init_sql: &str) -> Self

source

pub fn new_with_flags( + connection_initializer: CI, + init_sql: &str, + open_flags: OpenFlags +) -> Self

source

pub fn upgrade_to(&self, version: u32)

source

pub fn run_all_upgrades(&self)

source

pub fn open(&self) -> Connection

Auto Trait Implementations§

§

impl<CI> RefUnwindSafe for MigratedDatabaseFile<CI>where + CI: RefUnwindSafe,

§

impl<CI> Send for MigratedDatabaseFile<CI>where + CI: Send,

§

impl<CI> Sync for MigratedDatabaseFile<CI>where + CI: Sync,

§

impl<CI> Unpin for MigratedDatabaseFile<CI>where + CI: Unpin,

§

impl<CI> UnwindSafe for MigratedDatabaseFile<CI>where + CI: UnwindSafe,

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/sql_support/open_database/trait.ConnectionInitializer.html b/book/rust-docs/sql_support/open_database/trait.ConnectionInitializer.html new file mode 100644 index 0000000000..86d022b761 --- /dev/null +++ b/book/rust-docs/sql_support/open_database/trait.ConnectionInitializer.html @@ -0,0 +1,12 @@ +ConnectionInitializer in sql_support::open_database - Rust
pub trait ConnectionInitializer {
+    const NAME: &'static str;
+    const END_VERSION: u32;
+
+    // Required methods
+    fn init(&self, tx: &Transaction<'_>) -> Result<()>;
+    fn upgrade_from(&self, conn: &Transaction<'_>, version: u32) -> Result<()>;
+
+    // Provided methods
+    fn prepare(&self, _conn: &Connection, _db_empty: bool) -> Result<()> { ... }
+    fn finish(&self, _conn: &Connection) -> Result<()> { ... }
+}

Required Associated Constants§

source

const NAME: &'static str

source

const END_VERSION: u32

Required Methods§

source

fn init(&self, tx: &Transaction<'_>) -> Result<()>

source

fn upgrade_from(&self, conn: &Transaction<'_>, version: u32) -> Result<()>

Provided Methods§

source

fn prepare(&self, _conn: &Connection, _db_empty: bool) -> Result<()>

source

fn finish(&self, _conn: &Connection) -> Result<()>

Implementors§

\ No newline at end of file diff --git a/book/rust-docs/sql_support/open_database/type.Result.html b/book/rust-docs/sql_support/open_database/type.Result.html new file mode 100644 index 0000000000..5f94ce635a --- /dev/null +++ b/book/rust-docs/sql_support/open_database/type.Result.html @@ -0,0 +1 @@ +Result in sql_support::open_database - Rust

Type Definition sql_support::open_database::Result

source ·
pub type Result<T> = Result<T, Error>;
\ No newline at end of file diff --git a/book/rust-docs/sql_support/repeat/fn.repeat_display.html b/book/rust-docs/sql_support/repeat/fn.repeat_display.html new file mode 100644 index 0000000000..05af88123a --- /dev/null +++ b/book/rust-docs/sql_support/repeat/fn.repeat_display.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../sql_support/fn.repeat_display.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/sql_support/repeat/fn.repeat_multi_values.html b/book/rust-docs/sql_support/repeat/fn.repeat_multi_values.html new file mode 100644 index 0000000000..e15bdff8a9 --- /dev/null +++ b/book/rust-docs/sql_support/repeat/fn.repeat_multi_values.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../sql_support/fn.repeat_multi_values.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/sql_support/repeat/fn.repeat_sql_values.html b/book/rust-docs/sql_support/repeat/fn.repeat_sql_values.html new file mode 100644 index 0000000000..1d99cd8293 --- /dev/null +++ b/book/rust-docs/sql_support/repeat/fn.repeat_sql_values.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../sql_support/fn.repeat_sql_values.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/sql_support/repeat/fn.repeat_sql_vars.html b/book/rust-docs/sql_support/repeat/fn.repeat_sql_vars.html new file mode 100644 index 0000000000..31ff0e136a --- /dev/null +++ b/book/rust-docs/sql_support/repeat/fn.repeat_sql_vars.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../sql_support/fn.repeat_sql_vars.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/sql_support/repeat/struct.RepeatDisplay.html b/book/rust-docs/sql_support/repeat/struct.RepeatDisplay.html new file mode 100644 index 0000000000..6720d69b70 --- /dev/null +++ b/book/rust-docs/sql_support/repeat/struct.RepeatDisplay.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../sql_support/struct.RepeatDisplay.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/sql_support/sidebar-items.js b/book/rust-docs/sql_support/sidebar-items.js new file mode 100644 index 0000000000..65d9a512f2 --- /dev/null +++ b/book/rust-docs/sql_support/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["MaybeCached"],"fn":["default_max_variable_number","each_chunk","each_chunk_mapped","each_sized_chunk","each_sized_chunk_mapped","escape_string_for_pragma","repeat_display","repeat_multi_values","repeat_sql_values","repeat_sql_vars"],"mod":["debug_tools","open_database"],"struct":["Conn","RepeatDisplay","UncheckedTransaction"],"trait":["ConnExt"]}; \ No newline at end of file diff --git a/book/rust-docs/sql_support/struct.Conn.html b/book/rust-docs/sql_support/struct.Conn.html new file mode 100644 index 0000000000..9f2b25c21c --- /dev/null +++ b/book/rust-docs/sql_support/struct.Conn.html @@ -0,0 +1,11 @@ +Conn in sql_support - Rust

Struct sql_support::Conn

source ·
pub struct Conn(_);

Auto Trait Implementations§

§

impl !RefUnwindSafe for Conn

§

impl Send for Conn

§

impl !Sync for Conn

§

impl Unpin for Conn

§

impl !UnwindSafe for Conn

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/sql_support/struct.RepeatDisplay.html b/book/rust-docs/sql_support/struct.RepeatDisplay.html new file mode 100644 index 0000000000..04d56050e0 --- /dev/null +++ b/book/rust-docs/sql_support/struct.RepeatDisplay.html @@ -0,0 +1,22 @@ +RepeatDisplay in sql_support - Rust
pub struct RepeatDisplay<'a, F> { /* private fields */ }
Expand description

Helper type for printing repeated strings more efficiently. You should use +repeat_display or one of the repeat_sql_* helpers to +construct it.

+

Trait Implementations§

source§

impl<'a, F: Clone> Clone for RepeatDisplay<'a, F>

source§

fn clone(&self) -> RepeatDisplay<'a, F>

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl<'a, F: Debug> Debug for RepeatDisplay<'a, F>

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl<'a, F> Display for RepeatDisplay<'a, F>where + F: Fn(usize, &mut Formatter<'_>) -> Result,

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl<'a, F> RefUnwindSafe for RepeatDisplay<'a, F>where + F: RefUnwindSafe,

§

impl<'a, F> Send for RepeatDisplay<'a, F>where + F: Send,

§

impl<'a, F> Sync for RepeatDisplay<'a, F>where + F: Sync,

§

impl<'a, F> Unpin for RepeatDisplay<'a, F>where + F: Unpin,

§

impl<'a, F> UnwindSafe for RepeatDisplay<'a, F>where + F: UnwindSafe,

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/sql_support/struct.UncheckedTransaction.html b/book/rust-docs/sql_support/struct.UncheckedTransaction.html new file mode 100644 index 0000000000..13e6a8b3db --- /dev/null +++ b/book/rust-docs/sql_support/struct.UncheckedTransaction.html @@ -0,0 +1,508 @@ +UncheckedTransaction in sql_support - Rust
pub struct UncheckedTransaction<'conn> {
+    pub conn: &'conn Connection,
+    pub started_at: Instant,
+    pub finished: bool,
+}
Expand description

rusqlite, in an attempt to save us from ourselves, needs a mutable ref to a +connection to start a transaction. That is a bit of a PITA in some cases, so +we offer this as an alternative - but the responsibility of ensuring there +are no concurrent transactions is on our head.

+

This is very similar to the rusqlite Transaction - it doesn’t prevent +against nested transactions but does allow you to use an immutable +Connection.

+

FIXME: This currently won’t actually be used most of the time, because +rusqlite added [Connection::unchecked_transaction] (and +Transaction::new_unchecked, which can be used to reimplement +unchecked_transaction_imm), which will be preferred in a call to +c.unchecked_transaction(), because inherent methods have precedence over +methods on extension traits. The exception here is that this will still be +used by code which takes &impl ConnExt (I believe it would also be used if +you attempted to call unchecked_transaction() on a non-Connection that +implements ConnExt, such as a Safepoint, UncheckedTransaction, or +Transaction itself, but such code is clearly broken, so is not worth +considering).

+

The difference is that rusqlite’s version returns a normal +rusqlite::Transaction, rather than the UncheckedTransaction from this +crate. Aside from type’s name and location (and the fact that rusqlite’s +detects slightly more misuse at compile time, and has more features), the +main difference is: rusqlite’s does not track when a transaction began, +which unfortunatly seems to be used by the coop-transaction management in +places in some fashion.

+

There are at least two options for how to fix this:

+
    +
  1. Decide we don’t need this version, and delete it, and moving the +transaction timing into the coop-transaction code directly (or something +like this).
  2. +
  3. Decide this difference is important, and rename +ConnExt::unchecked_transaction to something like +ConnExt::transaction_unchecked.
  4. +
+

Fields§

§conn: &'conn Connection§started_at: Instant§finished: bool

Implementations§

source§

impl<'conn> UncheckedTransaction<'conn>

source

pub fn new( + conn: &'conn Connection, + behavior: TransactionBehavior +) -> SqlResult<Self>

Begin a new unchecked transaction. Cannot be nested, but this is not +enforced by Rust (hence ‘unchecked’) - however, it is enforced by +SQLite; use a rusqlite savepoint for nested transactions.

+
source

pub fn commit(self) -> SqlResult<()>

Consumes and commits an unchecked transaction.

+
source

pub fn rollback(self) -> SqlResult<()>

Consumes and rolls back an unchecked transaction.

+

Methods from Deref<Target = Connection>§

pub fn busy_timeout(&self, timeout: Duration) -> Result<(), Error>

Set a busy handler that sleeps for a specified amount of time when a +table is locked. The handler will sleep multiple times until at +least “ms” milliseconds of sleeping have accumulated.

+

Calling this routine with an argument equal to zero turns off all busy +handlers.

+

There can only be a single busy handler for a particular database +connection at any given moment. If another busy handler was defined +(using busy_handler) prior to calling this +routine, that other busy handler is cleared.

+

Newly created connections currently have a default busy timeout of +5000ms, but this may be subject to change.

+

pub fn busy_handler( + &self, + callback: Option<fn(_: i32) -> bool> +) -> Result<(), Error>

Register a callback to handle SQLITE_BUSY errors.

+

If the busy callback is None, then SQLITE_BUSY is returned +immediately upon encountering the lock. The argument to the busy +handler callback is the number of times that the +busy handler has been invoked previously for the +same locking event. If the busy callback returns false, then no +additional attempts are made to access the +database and SQLITE_BUSY is returned to the +application. If the callback returns true, then another attempt +is made to access the database and the cycle repeats.

+

There can only be a single busy handler defined for each database +connection. Setting a new busy handler clears any previously set +handler. Note that calling busy_timeout() +or evaluating PRAGMA busy_timeout=N will change the busy handler +and thus clear any previously set busy handler.

+

Newly created connections default to a +busy_timeout() handler with a timeout +of 5000ms, although this is subject to change.

+

pub fn prepare_cached(&self, sql: &str) -> Result<CachedStatement<'_>, Error>

Prepare a SQL statement for execution, returning a previously prepared +(but not currently in-use) statement if one is available. The +returned statement will be cached for reuse by future calls to +prepare_cached once it is dropped.

+ +
fn insert_new_people(conn: &Connection) -> Result<()> {
+    {
+        let mut stmt = conn.prepare_cached("INSERT INTO People (name) VALUES (?1)")?;
+        stmt.execute(["Joe Smith"])?;
+    }
+    {
+        // This will return the same underlying SQLite statement handle without
+        // having to prepare it again.
+        let mut stmt = conn.prepare_cached("INSERT INTO People (name) VALUES (?1)")?;
+        stmt.execute(["Bob Jones"])?;
+    }
+    Ok(())
+}
+
Failure
+

Will return Err if sql cannot be converted to a C-compatible string +or if the underlying SQLite call fails.

+

pub fn set_prepared_statement_cache_capacity(&self, capacity: usize)

Set the maximum number of cached prepared statements this connection +will hold. By default, a connection will hold a relatively small +number of cached statements. If you need more, or know that you +will not use cached statements, you +can set the capacity manually using this method.

+

pub fn flush_prepared_statement_cache(&self)

Remove/finalize all prepared statements currently in the cache.

+

pub fn db_config(&self, config: DbConfig) -> Result<bool, Error>

Returns the current value of a config.

+
    +
  • SQLITE_DBCONFIG_ENABLE_FKEY: return false or true to indicate +whether FK enforcement is off or on
  • +
  • SQLITE_DBCONFIG_ENABLE_TRIGGER: return false or true to indicate +whether triggers are disabled or enabled
  • +
  • SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER: return false or true to +indicate whether fts3_tokenizer are disabled or enabled
  • +
  • SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE: return false to indicate +checkpoints-on-close are not disabled or true if they are
  • +
  • SQLITE_DBCONFIG_ENABLE_QPSG: return false or true to indicate +whether the QPSG is disabled or enabled
  • +
  • SQLITE_DBCONFIG_TRIGGER_EQP: return false to indicate +output-for-trigger are not disabled or true if it is
  • +
+

pub fn set_db_config( + &self, + config: DbConfig, + new_val: bool +) -> Result<bool, Error>

Make configuration changes to a database connection

+
    +
  • SQLITE_DBCONFIG_ENABLE_FKEY: false to disable FK enforcement, +true to enable FK enforcement
  • +
  • SQLITE_DBCONFIG_ENABLE_TRIGGER: false to disable triggers, true +to enable triggers
  • +
  • SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER: false to disable +fts3_tokenizer(), true to enable fts3_tokenizer()
  • +
  • SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE: false (the default) to enable +checkpoints-on-close, true to disable them
  • +
  • SQLITE_DBCONFIG_ENABLE_QPSG: false to disable the QPSG, true to +enable QPSG
  • +
  • SQLITE_DBCONFIG_TRIGGER_EQP: false to disable output for trigger +programs, true to enable it
  • +
+

pub fn create_scalar_function<F, T>( + &self, + fn_name: &str, + n_arg: i32, + flags: FunctionFlags, + x_func: F +) -> Result<(), Error>where + F: FnMut(&Context<'_>) -> Result<T, Error> + Send + UnwindSafe + 'static, + T: ToSql,

Attach a user-defined scalar function to +this database connection.

+

fn_name is the name the function will be accessible from SQL. +n_arg is the number of arguments to the function. Use -1 for a +variable number. If the function always returns the same value +given the same input, deterministic should be true.

+

The function will remain available until the connection is closed or +until it is explicitly removed via +remove_function.

+
Example
+
fn scalar_function_example(db: Connection) -> Result<()> {
+    db.create_scalar_function(
+        "halve",
+        1,
+        FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
+        |ctx| {
+            let value = ctx.get::<f64>(0)?;
+            Ok(value / 2f64)
+        },
+    )?;
+
+    let six_halved: f64 = db.query_row("SELECT halve(6)", [], |r| r.get(0))?;
+    assert_eq!(six_halved, 3f64);
+    Ok(())
+}
+
Failure
+

Will return Err if the function could not be attached to the connection.

+

pub fn create_aggregate_function<A, D, T>( + &self, + fn_name: &str, + n_arg: i32, + flags: FunctionFlags, + aggr: D +) -> Result<(), Error>where + A: RefUnwindSafe + UnwindSafe, + D: Aggregate<A, T> + 'static, + T: ToSql,

Attach a user-defined aggregate function to this +database connection.

+
Failure
+

Will return Err if the function could not be attached to the connection.

+

pub fn create_window_function<A, W, T>( + &self, + fn_name: &str, + n_arg: i32, + flags: FunctionFlags, + aggr: W +) -> Result<(), Error>where + A: RefUnwindSafe + UnwindSafe, + W: WindowAggregate<A, T> + 'static, + T: ToSql,

Attach a user-defined aggregate window function to +this database connection.

+

See https://sqlite.org/windowfunctions.html#udfwinfunc for more +information.

+

pub fn remove_function(&self, fn_name: &str, n_arg: i32) -> Result<(), Error>

Removes a user-defined function from this +database connection.

+

fn_name and n_arg should match the name and number of arguments +given to create_scalar_function +or create_aggregate_function.

+
Failure
+

Will return Err if the function could not be removed.

+

pub fn limit(&self, limit: Limit) -> i32

Returns the current value of a [Limit].

+

pub fn set_limit(&self, limit: Limit, new_val: i32) -> i32

Changes the [Limit] to new_val, returning the prior +value of the limit.

+

pub fn pragma_query_value<T, F>( + &self, + schema_name: Option<DatabaseName<'_>>, + pragma_name: &str, + f: F +) -> Result<T, Error>where + F: FnOnce(&Row<'_>) -> Result<T, Error>,

Query the current value of pragma_name.

+

Some pragmas will return multiple rows/values which cannot be retrieved +with this method.

+

Prefer PRAGMA function introduced in SQLite 3.20: +SELECT user_version FROM pragma_user_version;

+

pub fn pragma_query<F>( + &self, + schema_name: Option<DatabaseName<'_>>, + pragma_name: &str, + f: F +) -> Result<(), Error>where + F: FnMut(&Row<'_>) -> Result<(), Error>,

Query the current rows/values of pragma_name.

+

Prefer PRAGMA function introduced in SQLite 3.20: +SELECT * FROM pragma_collation_list;

+

pub fn pragma<F, V>( + &self, + schema_name: Option<DatabaseName<'_>>, + pragma_name: &str, + pragma_value: V, + f: F +) -> Result<(), Error>where + F: FnMut(&Row<'_>) -> Result<(), Error>, + V: ToSql,

Query the current value(s) of pragma_name associated to +pragma_value.

+

This method can be used with query-only pragmas which need an argument +(e.g. table_info('one_tbl')) or pragmas which returns value(s) +(e.g. integrity_check).

+

Prefer PRAGMA function introduced in SQLite 3.20: +SELECT * FROM pragma_table_info(?1);

+

pub fn pragma_update<V>( + &self, + schema_name: Option<DatabaseName<'_>>, + pragma_name: &str, + pragma_value: V +) -> Result<(), Error>where + V: ToSql,

Set a new value to pragma_name.

+

Some pragmas will return the updated value which cannot be retrieved +with this method.

+

pub fn pragma_update_and_check<F, T, V>( + &self, + schema_name: Option<DatabaseName<'_>>, + pragma_name: &str, + pragma_value: V, + f: F +) -> Result<T, Error>where + F: FnOnce(&Row<'_>) -> Result<T, Error>, + V: ToSql,

Set a new value to pragma_name and return the updated value.

+

Only few pragmas automatically return the updated value.

+

pub fn unchecked_transaction(&self) -> Result<Transaction<'_>, Error>

Begin a new transaction with the default behavior (DEFERRED).

+

Attempt to open a nested transaction will result in a SQLite error. +Connection::transaction prevents this at compile time by taking &mut self, but Connection::unchecked_transaction() may be used to defer +the checking until runtime.

+

See [Connection::transaction] and [Transaction::new_unchecked] +(which can be used if the default transaction behavior is undesirable).

+
Example
+
fn perform_queries(conn: Rc<Connection>) -> Result<()> {
+    let tx = conn.unchecked_transaction()?;
+
+    do_queries_part_1(&tx)?; // tx causes rollback if this fails
+    do_queries_part_2(&tx)?; // tx causes rollback if this fails
+
+    tx.commit()
+}
+
Failure
+

Will return Err if the underlying SQLite call fails. The specific +error returned if transactions are nested is currently unspecified.

+

pub fn transaction_state( + &self, + db_name: Option<DatabaseName<'_>> +) -> Result<TransactionState, Error>

Determine the transaction state of a database

+

pub fn execute_batch(&self, sql: &str) -> Result<(), Error>

Convenience method to run multiple SQL statements (that cannot take any +parameters).

+
Example
+
fn create_tables(conn: &Connection) -> Result<()> {
+    conn.execute_batch(
+        "BEGIN;
+         CREATE TABLE foo(x INTEGER);
+         CREATE TABLE bar(y TEXT);
+         COMMIT;",
+    )
+}
+
Failure
+

Will return Err if sql cannot be converted to a C-compatible string +or if the underlying SQLite call fails.

+

pub fn execute<P>(&self, sql: &str, params: P) -> Result<usize, Error>where + P: Params,

Convenience method to prepare and execute a single SQL statement.

+

On success, returns the number of rows that were changed or inserted or +deleted (via sqlite3_changes).

+
Example
With positional params
+
fn update_rows(conn: &Connection) {
+    match conn.execute("UPDATE foo SET bar = 'baz' WHERE qux = ?1", [1i32]) {
+        Ok(updated) => println!("{} rows were updated", updated),
+        Err(err) => println!("update failed: {}", err),
+    }
+}
+
With positional params of varying types
+
fn update_rows(conn: &Connection) {
+    match conn.execute(
+        "UPDATE foo SET bar = 'baz' WHERE qux = ?1 AND quux = ?2",
+        params![1i32, 1.5f64],
+    ) {
+        Ok(updated) => println!("{} rows were updated", updated),
+        Err(err) => println!("update failed: {}", err),
+    }
+}
+
With named params
+
fn insert(conn: &Connection) -> Result<usize> {
+    conn.execute(
+        "INSERT INTO test (name) VALUES (:name)",
+        &[(":name", "one")],
+    )
+}
+
Failure
+

Will return Err if sql cannot be converted to a C-compatible string +or if the underlying SQLite call fails.

+

pub fn path(&self) -> Option<&str>

Returns the path to the database file, if one exists and is known.

+

Returns Some("") for a temporary or in-memory database.

+

Note that in some cases PRAGMA +database_list is +likely to be more robust.

+

pub fn last_insert_rowid(&self) -> i64

Get the SQLite rowid of the most recent successful INSERT.

+

Uses sqlite3_last_insert_rowid under +the hood.

+

pub fn query_row<T, P, F>(&self, sql: &str, params: P, f: F) -> Result<T, Error>where + P: Params, + F: FnOnce(&Row<'_>) -> Result<T, Error>,

Convenience method to execute a query that is expected to return a +single row.

+
Example
+
fn preferred_locale(conn: &Connection) -> Result<String> {
+    conn.query_row(
+        "SELECT value FROM preferences WHERE name='locale'",
+        [],
+        |row| row.get(0),
+    )
+}
+

If the query returns more than one row, all rows except the first are +ignored.

+

Returns Err(QueryReturnedNoRows) if no results are returned. If the +query truly is optional, you can call .optional() on the result of +this to get a Result<Option<T>>.

+
Failure
+

Will return Err if sql cannot be converted to a C-compatible string +or if the underlying SQLite call fails.

+

pub fn query_row_and_then<T, E, P, F>( + &self, + sql: &str, + params: P, + f: F +) -> Result<T, E>where + P: Params, + F: FnOnce(&Row<'_>) -> Result<T, E>, + E: From<Error>,

Convenience method to execute a query that is expected to return a +single row, and execute a mapping via f on that returned row with +the possibility of failure. The Result type of f must implement +std::convert::From<Error>.

+
Example
+
fn preferred_locale(conn: &Connection) -> Result<String> {
+    conn.query_row_and_then(
+        "SELECT value FROM preferences WHERE name='locale'",
+        [],
+        |row| row.get(0),
+    )
+}
+

If the query returns more than one row, all rows except the first are +ignored.

+
Failure
+

Will return Err if sql cannot be converted to a C-compatible string +or if the underlying SQLite call fails.

+

pub fn prepare(&self, sql: &str) -> Result<Statement<'_>, Error>

Prepare a SQL statement for execution.

+
Example
+
fn insert_new_people(conn: &Connection) -> Result<()> {
+    let mut stmt = conn.prepare("INSERT INTO People (name) VALUES (?1)")?;
+    stmt.execute(["Joe Smith"])?;
+    stmt.execute(["Bob Jones"])?;
+    Ok(())
+}
+
Failure
+

Will return Err if sql cannot be converted to a C-compatible string +or if the underlying SQLite call fails.

+

pub unsafe fn handle(&self) -> *mut sqlite3

Get access to the underlying SQLite database connection handle.

+
Warning
+

You should not need to use this function. If you do need to, please +open an issue on the rusqlite repository and describe +your use case.

+
Safety
+

This function is unsafe because it gives you raw access +to the SQLite connection, and what you do with it could impact the +safety of this Connection.

+

pub fn get_interrupt_handle(&self) -> InterruptHandle

Get access to a handle that can be used to interrupt long running +queries from another thread.

+

pub fn changes(&self) -> u64

Return the number of rows modified, inserted or deleted by the most +recently completed INSERT, UPDATE or DELETE statement on the database +connection.

+

See https://www.sqlite.org/c3ref/changes.html

+

pub fn is_autocommit(&self) -> bool

Test for auto-commit mode. +Autocommit mode is on by default.

+

pub fn is_busy(&self) -> bool

Determine if all associated prepared statements have been reset.

+

pub fn cache_flush(&self) -> Result<(), Error>

Flush caches to disk mid-transaction

+

pub fn is_readonly(&self, db_name: DatabaseName<'_>) -> Result<bool, Error>

Determine if a database is read-only

+

Trait Implementations§

source§

impl<'conn> ConnExt for UncheckedTransaction<'conn>

source§

fn conn(&self) -> &Connection

The method you need to implement to opt in to all of this.
source§

fn set_pragma<T>(&self, pragma_name: &str, pragma_value: T) -> SqlResult<&Self>where + T: ToSql, + Self: Sized,

Set the value of the pragma on the main database. Returns the same object, for chaining.
source§

fn prepare_maybe_cached<'conn>( + &'conn self, + sql: &str, + cache: bool +) -> SqlResult<MaybeCached<'conn>>

Get a cached or uncached statement based on a flag.
source§

fn execute_all(&self, stmts: &[&str]) -> SqlResult<()>

Execute all the provided statements.
source§

fn execute_one(&self, stmt: &str) -> SqlResult<()>

Execute a single statement.
source§

fn execute_cached<P: Params>(&self, sql: &str, params: P) -> SqlResult<usize>

Equivalent to Connection::execute but caches the statement so that subsequent +calls to execute_cached will have improved performance.
source§

fn query_one<T: FromSql>(&self, sql: &str) -> SqlResult<T>

Execute a query that returns a single result column, and return that result.
source§

fn exists<P: Params>(&self, sql: &str, params: P) -> SqlResult<bool>

Return true if a query returns any rows
source§

fn try_query_one<T: FromSql, P: Params>( + &self, + sql: &str, + params: P, + cache: bool +) -> SqlResult<Option<T>>where + Self: Sized,

Execute a query that returns 0 or 1 result columns, returning None +if there were no rows, or if the only result was NULL.
source§

fn query_row_and_then_cachable<T, E, P, F>( + &self, + sql: &str, + params: P, + mapper: F, + cache: bool +) -> Result<T, E>where + Self: Sized, + P: Params, + E: From<Error>, + F: FnOnce(&Row<'_>) -> Result<T, E>,

Equivalent to rusqlite::Connection::query_row_and_then but allows +passing a flag to indicate that it’s cached.
source§

fn query_rows_and_then<T, E, P, F>( + &self, + sql: &str, + params: P, + mapper: F +) -> Result<Vec<T>, E>where + Self: Sized, + P: Params, + E: From<Error>, + F: FnMut(&Row<'_>) -> Result<T, E>,

Helper for when you’d like to get a Vec<T> of all the rows returned by a +query that takes named arguments. See also +query_rows_and_then_cached.
source§

fn query_rows_and_then_cached<T, E, P, F>( + &self, + sql: &str, + params: P, + mapper: F +) -> Result<Vec<T>, E>where + Self: Sized, + P: Params, + E: From<Error>, + F: FnMut(&Row<'_>) -> Result<T, E>,

Helper for when you’d like to get a Vec<T> of all the rows returned by a +query that takes named arguments.
source§

fn query_rows_into<Coll, T, E, P, F>( + &self, + sql: &str, + params: P, + mapper: F +) -> Result<Coll, E>where + Self: Sized, + E: From<Error>, + F: FnMut(&Row<'_>) -> Result<T, E>, + Coll: FromIterator<T>, + P: Params,

Like query_rows_and_then_cachable, but works if you want a non-Vec as a result. Read more
source§

fn query_rows_into_cached<Coll, T, E, P, F>( + &self, + sql: &str, + params: P, + mapper: F +) -> Result<Coll, E>where + Self: Sized, + P: Params, + E: From<Error>, + F: FnMut(&Row<'_>) -> Result<T, E>, + Coll: FromIterator<T>,

Same as query_rows_into, but caches the stmt if possible.
source§

fn try_query_row<T, E, P, F>( + &self, + sql: &str, + params: P, + mapper: F, + cache: bool +) -> Result<Option<T>, E>where + Self: Sized, + P: Params, + E: From<Error>, + F: FnOnce(&Row<'_>) -> Result<T, E>,

Like query_row_and_then_cachable but returns None instead of erroring +if no such row exists.
source§

fn unchecked_transaction(&self) -> SqlResult<UncheckedTransaction<'_>>

Caveat: This won’t actually get used most of the time, and calls will +usually invoke rusqlite’s method with the same name. See comment on +UncheckedTransaction for details (generally you probably don’t need to +care)
source§

fn unchecked_transaction_imm(&self) -> SqlResult<UncheckedTransaction<'_>>

Begin unchecked_transaction with TransactionBehavior::Immediate. Use +when the first operation will be a read operation, that further writes +depend on for correctness.
source§

fn get_db_size(&self) -> Result<u32, Error>

Get the DB size in bytes
source§

impl<'conn> Deref for UncheckedTransaction<'conn>

§

type Target = Connection

The resulting type after dereferencing.
source§

fn deref(&self) -> &Connection

Dereferences the value.
source§

impl<'conn> Drop for UncheckedTransaction<'conn>

source§

fn drop(&mut self)

Executes the destructor for this type. Read more

Auto Trait Implementations§

§

impl<'conn> !RefUnwindSafe for UncheckedTransaction<'conn>

§

impl<'conn> !Send for UncheckedTransaction<'conn>

§

impl<'conn> !Sync for UncheckedTransaction<'conn>

§

impl<'conn> Unpin for UncheckedTransaction<'conn>

§

impl<'conn> !UnwindSafe for UncheckedTransaction<'conn>

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/sql_support/trait.ConnExt.html b/book/rust-docs/sql_support/trait.ConnExt.html new file mode 100644 index 0000000000..35b071e034 --- /dev/null +++ b/book/rust-docs/sql_support/trait.ConnExt.html @@ -0,0 +1,213 @@ +ConnExt in sql_support - Rust

Trait sql_support::ConnExt

source ·
pub trait ConnExt {
+
Show 18 methods // Required method + fn conn(&self) -> &Connection; + + // Provided methods + fn set_pragma<T>( + &self, + pragma_name: &str, + pragma_value: T + ) -> SqlResult<&Self> + where T: ToSql, + Self: Sized { ... } + fn prepare_maybe_cached<'conn>( + &'conn self, + sql: &str, + cache: bool + ) -> SqlResult<MaybeCached<'conn>> { ... } + fn execute_all(&self, stmts: &[&str]) -> SqlResult<()> { ... } + fn execute_one(&self, stmt: &str) -> SqlResult<()> { ... } + fn execute_cached<P: Params>( + &self, + sql: &str, + params: P + ) -> SqlResult<usize> { ... } + fn query_one<T: FromSql>(&self, sql: &str) -> SqlResult<T> { ... } + fn exists<P: Params>(&self, sql: &str, params: P) -> SqlResult<bool> { ... } + fn try_query_one<T: FromSql, P: Params>( + &self, + sql: &str, + params: P, + cache: bool + ) -> SqlResult<Option<T>> + where Self: Sized { ... } + fn query_row_and_then_cachable<T, E, P, F>( + &self, + sql: &str, + params: P, + mapper: F, + cache: bool + ) -> Result<T, E> + where Self: Sized, + P: Params, + E: From<Error>, + F: FnOnce(&Row<'_>) -> Result<T, E> { ... } + fn query_rows_and_then<T, E, P, F>( + &self, + sql: &str, + params: P, + mapper: F + ) -> Result<Vec<T>, E> + where Self: Sized, + P: Params, + E: From<Error>, + F: FnMut(&Row<'_>) -> Result<T, E> { ... } + fn query_rows_and_then_cached<T, E, P, F>( + &self, + sql: &str, + params: P, + mapper: F + ) -> Result<Vec<T>, E> + where Self: Sized, + P: Params, + E: From<Error>, + F: FnMut(&Row<'_>) -> Result<T, E> { ... } + fn query_rows_into<Coll, T, E, P, F>( + &self, + sql: &str, + params: P, + mapper: F + ) -> Result<Coll, E> + where Self: Sized, + E: From<Error>, + F: FnMut(&Row<'_>) -> Result<T, E>, + Coll: FromIterator<T>, + P: Params { ... } + fn query_rows_into_cached<Coll, T, E, P, F>( + &self, + sql: &str, + params: P, + mapper: F + ) -> Result<Coll, E> + where Self: Sized, + P: Params, + E: From<Error>, + F: FnMut(&Row<'_>) -> Result<T, E>, + Coll: FromIterator<T> { ... } + fn try_query_row<T, E, P, F>( + &self, + sql: &str, + params: P, + mapper: F, + cache: bool + ) -> Result<Option<T>, E> + where Self: Sized, + P: Params, + E: From<Error>, + F: FnOnce(&Row<'_>) -> Result<T, E> { ... } + fn unchecked_transaction(&self) -> SqlResult<UncheckedTransaction<'_>> { ... } + fn unchecked_transaction_imm(&self) -> SqlResult<UncheckedTransaction<'_>> { ... } + fn get_db_size(&self) -> Result<u32, Error> { ... } +
}
Expand description

This trait exists so that we can use these helpers on rusqlite::{Transaction, Connection}. +Note that you must import ConnExt in order to call these methods on anything.

+

Required Methods§

source

fn conn(&self) -> &Connection

The method you need to implement to opt in to all of this.

+

Provided Methods§

source

fn set_pragma<T>(&self, pragma_name: &str, pragma_value: T) -> SqlResult<&Self>where + T: ToSql, + Self: Sized,

Set the value of the pragma on the main database. Returns the same object, for chaining.

+
source

fn prepare_maybe_cached<'conn>( + &'conn self, + sql: &str, + cache: bool +) -> SqlResult<MaybeCached<'conn>>

Get a cached or uncached statement based on a flag.

+
source

fn execute_all(&self, stmts: &[&str]) -> SqlResult<()>

Execute all the provided statements.

+
source

fn execute_one(&self, stmt: &str) -> SqlResult<()>

Execute a single statement.

+
source

fn execute_cached<P: Params>(&self, sql: &str, params: P) -> SqlResult<usize>

Equivalent to Connection::execute but caches the statement so that subsequent +calls to execute_cached will have improved performance.

+
source

fn query_one<T: FromSql>(&self, sql: &str) -> SqlResult<T>

Execute a query that returns a single result column, and return that result.

+
source

fn exists<P: Params>(&self, sql: &str, params: P) -> SqlResult<bool>

Return true if a query returns any rows

+
source

fn try_query_one<T: FromSql, P: Params>( + &self, + sql: &str, + params: P, + cache: bool +) -> SqlResult<Option<T>>where + Self: Sized,

Execute a query that returns 0 or 1 result columns, returning None +if there were no rows, or if the only result was NULL.

+
source

fn query_row_and_then_cachable<T, E, P, F>( + &self, + sql: &str, + params: P, + mapper: F, + cache: bool +) -> Result<T, E>where + Self: Sized, + P: Params, + E: From<Error>, + F: FnOnce(&Row<'_>) -> Result<T, E>,

Equivalent to rusqlite::Connection::query_row_and_then but allows +passing a flag to indicate that it’s cached.

+
source

fn query_rows_and_then<T, E, P, F>( + &self, + sql: &str, + params: P, + mapper: F +) -> Result<Vec<T>, E>where + Self: Sized, + P: Params, + E: From<Error>, + F: FnMut(&Row<'_>) -> Result<T, E>,

Helper for when you’d like to get a Vec<T> of all the rows returned by a +query that takes named arguments. See also +query_rows_and_then_cached.

+
source

fn query_rows_and_then_cached<T, E, P, F>( + &self, + sql: &str, + params: P, + mapper: F +) -> Result<Vec<T>, E>where + Self: Sized, + P: Params, + E: From<Error>, + F: FnMut(&Row<'_>) -> Result<T, E>,

Helper for when you’d like to get a Vec<T> of all the rows returned by a +query that takes named arguments.

+
source

fn query_rows_into<Coll, T, E, P, F>( + &self, + sql: &str, + params: P, + mapper: F +) -> Result<Coll, E>where + Self: Sized, + E: From<Error>, + F: FnMut(&Row<'_>) -> Result<T, E>, + Coll: FromIterator<T>, + P: Params,

Like query_rows_and_then_cachable, but works if you want a non-Vec as a result.

+
Example:
+
fn get_visit_tombstones(conn: &Connection, id: i64) -> rusqlite::Result<HashSet<i64>> {
+    Ok(conn.query_rows_into(
+        "SELECT visit_date FROM moz_historyvisit_tombstones
+         WHERE place_id = :place_id",
+        &[(":place_id", &id)],
+        |row| row.get::<_, i64>(0))?)
+}
+

Note if the type isn’t inferred, you’ll have to do something gross like +conn.query_rows_into::<HashSet<_>, _, _, _>(...).

+
source

fn query_rows_into_cached<Coll, T, E, P, F>( + &self, + sql: &str, + params: P, + mapper: F +) -> Result<Coll, E>where + Self: Sized, + P: Params, + E: From<Error>, + F: FnMut(&Row<'_>) -> Result<T, E>, + Coll: FromIterator<T>,

Same as query_rows_into, but caches the stmt if possible.

+
source

fn try_query_row<T, E, P, F>( + &self, + sql: &str, + params: P, + mapper: F, + cache: bool +) -> Result<Option<T>, E>where + Self: Sized, + P: Params, + E: From<Error>, + F: FnOnce(&Row<'_>) -> Result<T, E>,

Like query_row_and_then_cachable but returns None instead of erroring +if no such row exists.

+
source

fn unchecked_transaction(&self) -> SqlResult<UncheckedTransaction<'_>>

Caveat: This won’t actually get used most of the time, and calls will +usually invoke rusqlite’s method with the same name. See comment on +UncheckedTransaction for details (generally you probably don’t need to +care)

+
source

fn unchecked_transaction_imm(&self) -> SqlResult<UncheckedTransaction<'_>>

Begin unchecked_transaction with TransactionBehavior::Immediate. Use +when the first operation will be a read operation, that further writes +depend on for correctness.

+
source

fn get_db_size(&self) -> Result<u32, Error>

Get the DB size in bytes

+

Implementations on Foreign Types§

source§

impl ConnExt for Connection

source§

fn conn(&self) -> &Connection

source§

impl<'conn> ConnExt for Savepoint<'conn>

source§

fn conn(&self) -> &Connection

source§

impl<'conn> ConnExt for Transaction<'conn>

source§

fn conn(&self) -> &Connection

Implementors§

source§

impl<'conn> ConnExt for UncheckedTransaction<'conn>

\ No newline at end of file diff --git a/book/rust-docs/src/as_ohttp_client/lib.rs.html b/book/rust-docs/src/as_ohttp_client/lib.rs.html new file mode 100644 index 0000000000..6e0c95538d --- /dev/null +++ b/book/rust-docs/src/as_ohttp_client/lib.rs.html @@ -0,0 +1,607 @@ +lib.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+
extern crate bhttp;
+extern crate ohttp;
+extern crate rusqlite;
+
+use parking_lot::Mutex;
+use std::collections::HashMap;
+
+#[derive(Debug, thiserror::Error)]
+pub enum OhttpError {
+    #[error("Failed to fetch encryption key")]
+    KeyFetchFailed,
+
+    #[error("OHTTP key config is malformed")]
+    MalformedKeyConfig,
+
+    #[error("Unsupported OHTTP encryption algorithm")]
+    UnsupportedKeyConfig,
+
+    #[error("OhttpSession is in invalid state")]
+    InvalidSession,
+
+    #[error("Network errors communicating with Relay / Gateway")]
+    RelayFailed,
+
+    #[error("Cannot encode message as BHTTP/OHTTP")]
+    CannotEncodeMessage,
+
+    #[error("Cannot decode OHTTP/BHTTP message")]
+    MalformedMessage,
+
+    #[error("Duplicate HTTP response headers")]
+    DuplicateHeaders,
+}
+
+#[derive(Default)]
+enum ExchangeState {
+    #[default]
+    Invalid,
+    Request(ohttp::ClientRequest),
+    Response(ohttp::ClientResponse),
+}
+
+pub struct OhttpSession {
+    state: Mutex<ExchangeState>,
+}
+
+pub struct OhttpResponse {
+    status_code: u16,
+    headers: HashMap<String, String>,
+    payload: Vec<u8>,
+}
+
+/// Transform the headers from a BHTTP message into a HashMap for use from Swift
+/// later. If there are duplicate errors, we currently raise an error.
+fn headers_to_map(message: &bhttp::Message) -> Result<HashMap<String, String>, OhttpError> {
+    let mut headers = HashMap::new();
+
+    for field in message.header().iter() {
+        if headers
+            .insert(
+                std::str::from_utf8(field.name())
+                    .map_err(|_| OhttpError::MalformedMessage)?
+                    .into(),
+                std::str::from_utf8(field.value())
+                    .map_err(|_| OhttpError::MalformedMessage)?
+                    .into(),
+            )
+            .is_some()
+        {
+            return Err(OhttpError::DuplicateHeaders);
+        }
+    }
+
+    Ok(headers)
+}
+
+impl OhttpSession {
+    /// Create a new encryption session for use with specific key configuration
+    pub fn new(config: &[u8]) -> Result<Self, OhttpError> {
+        ohttp::init();
+
+        let request = ohttp::ClientRequest::from_encoded_config(config).map_err(|e| match e {
+            ohttp::Error::Unsupported => OhttpError::UnsupportedKeyConfig,
+            _ => OhttpError::MalformedKeyConfig,
+        })?;
+
+        let state = Mutex::new(ExchangeState::Request(request));
+        Ok(OhttpSession { state })
+    }
+
+    /// Encode an HTTP request in Binary HTTP format and then encrypt it into an
+    /// Oblivious HTTP request message.
+    pub fn encapsulate(
+        &self,
+        method: &str,
+        scheme: &str,
+        server: &str,
+        endpoint: &str,
+        mut headers: HashMap<String, String>,
+        payload: &[u8],
+    ) -> Result<Vec<u8>, OhttpError> {
+        let mut message =
+            bhttp::Message::request(method.into(), scheme.into(), server.into(), endpoint.into());
+
+        for (k, v) in headers.drain() {
+            message.put_header(k, v);
+        }
+
+        message.write_content(payload);
+
+        let mut encoded = vec![];
+        message
+            .write_bhttp(bhttp::Mode::KnownLength, &mut encoded)
+            .map_err(|_| OhttpError::CannotEncodeMessage)?;
+
+        let mut state = self.state.lock();
+        let request = match std::mem::take(&mut *state) {
+            ExchangeState::Request(request) => request,
+            _ => return Err(OhttpError::InvalidSession),
+        };
+        let (capsule, response) = request
+            .encapsulate(&encoded)
+            .map_err(|_| OhttpError::CannotEncodeMessage)?;
+        *state = ExchangeState::Response(response);
+
+        Ok(capsule)
+    }
+
+    /// Decode an OHTTP response returned in response to a request encoded on
+    /// this session.
+    pub fn decapsulate(&self, encoded: &[u8]) -> Result<OhttpResponse, OhttpError> {
+        let mut state = self.state.lock();
+        let decoder = match std::mem::take(&mut *state) {
+            ExchangeState::Response(response) => response,
+            _ => return Err(OhttpError::InvalidSession),
+        };
+        let binary = decoder
+            .decapsulate(encoded)
+            .map_err(|_| OhttpError::MalformedMessage)?;
+
+        let mut cursor = std::io::Cursor::new(binary);
+        let message =
+            bhttp::Message::read_bhttp(&mut cursor).map_err(|_| OhttpError::MalformedMessage)?;
+
+        let headers = headers_to_map(&message)?;
+
+        Ok(OhttpResponse {
+            status_code: match message.control() {
+                bhttp::ControlData::Response(sc) => *sc,
+                _ => return Err(OhttpError::InvalidSession),
+            },
+            headers,
+            payload: message.content().into(),
+        })
+    }
+}
+
+pub struct OhttpTestServer {
+    server: Mutex<ohttp::Server>,
+    state: Mutex<Option<ohttp::ServerResponse>>,
+    config: Vec<u8>,
+}
+
+pub struct TestServerRequest {
+    method: String,
+    scheme: String,
+    server: String,
+    endpoint: String,
+    headers: HashMap<String, String>,
+    payload: Vec<u8>,
+}
+
+impl OhttpTestServer {
+    /// Create a simple OHTTP server to decrypt and respond to OHTTP messages in
+    /// testing. The key is randomly generated.
+    fn new() -> Self {
+        ohttp::init();
+
+        let key = ohttp::KeyConfig::new(
+            0x01,
+            ohttp::hpke::Kem::X25519Sha256,
+            vec![ohttp::SymmetricSuite::new(
+                ohttp::hpke::Kdf::HkdfSha256,
+                ohttp::hpke::Aead::Aes128Gcm,
+            )],
+        )
+        .unwrap();
+
+        let config = key.encode().unwrap();
+        let server = ohttp::Server::new(key).unwrap();
+
+        OhttpTestServer {
+            server: Mutex::new(server),
+            state: Mutex::new(Option::None),
+            config,
+        }
+    }
+
+    /// Return a copy of the key config for clients to use.
+    fn get_config(&self) -> Vec<u8> {
+        self.config.clone()
+    }
+
+    /// Decode an OHTTP request message and return the cleartext contents. This
+    /// also updates the internal server state so that a response message can be
+    /// generated.
+    fn receive(&self, message: &[u8]) -> Result<TestServerRequest, OhttpError> {
+        let (encoded, response) = self
+            .server
+            .lock()
+            .decapsulate(message)
+            .map_err(|_| OhttpError::MalformedMessage)?;
+        let mut cursor = std::io::Cursor::new(encoded);
+        let message =
+            bhttp::Message::read_bhttp(&mut cursor).map_err(|_| OhttpError::MalformedMessage)?;
+
+        *self.state.lock() = Some(response);
+
+        let headers = headers_to_map(&message)?;
+
+        match message.control() {
+            bhttp::ControlData::Request {
+                method,
+                scheme,
+                authority,
+                path,
+            } => Ok(TestServerRequest {
+                method: String::from_utf8_lossy(method).into(),
+                scheme: String::from_utf8_lossy(scheme).into(),
+                server: String::from_utf8_lossy(authority).into(),
+                endpoint: String::from_utf8_lossy(path).into(),
+                headers,
+                payload: message.content().into(),
+            }),
+            _ => Err(OhttpError::MalformedMessage),
+        }
+    }
+
+    /// Encode an OHTTP response keyed to the last message received.
+    fn respond(&self, response: OhttpResponse) -> Result<Vec<u8>, OhttpError> {
+        let state = self.state.lock().take().unwrap();
+
+        let mut message = bhttp::Message::response(response.status_code);
+        message.write_content(&response.payload);
+
+        for (k, v) in response.headers {
+            message.put_header(k, v);
+        }
+
+        let mut encoded = vec![];
+        message
+            .write_bhttp(bhttp::Mode::KnownLength, &mut encoded)
+            .map_err(|_| OhttpError::CannotEncodeMessage)?;
+
+        state
+            .encapsulate(&encoded)
+            .map_err(|_| OhttpError::CannotEncodeMessage)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_smoke() {
+        let server = OhttpTestServer::new();
+        let config = server.get_config();
+
+        let body: Vec<u8> = vec![0x00, 0x01, 0x02];
+        let header = HashMap::from([
+            ("Content-Type".into(), "application/octet-stream".into()),
+            ("X-Header".into(), "value".into()),
+        ]);
+
+        let session = OhttpSession::new(&config).unwrap();
+        let mut message = session
+            .encapsulate("GET", "https", "example.com", "/api", header.clone(), &body)
+            .unwrap();
+
+        let request = server.receive(&message).unwrap();
+        assert_eq!(request.method, "GET");
+        assert_eq!(request.scheme, "https");
+        assert_eq!(request.server, "example.com");
+        assert_eq!(request.endpoint, "/api");
+        assert_eq!(request.headers, header);
+
+        message = server
+            .respond(OhttpResponse {
+                status_code: 200,
+                headers: header.clone(),
+                payload: body.clone(),
+            })
+            .unwrap();
+
+        let response = session.decapsulate(&message).unwrap();
+        assert_eq!(response.status_code, 200);
+        assert_eq!(response.headers, header);
+        assert_eq!(response.payload, body);
+    }
+}
+
+uniffi::include_scaffolding!("as_ohttp_client");
+
\ No newline at end of file diff --git a/book/rust-docs/src/autofill/db/addresses.rs.html b/book/rust-docs/src/autofill/db/addresses.rs.html new file mode 100644 index 0000000000..70a25eb69b --- /dev/null +++ b/book/rust-docs/src/autofill/db/addresses.rs.html @@ -0,0 +1,1381 @@ +addresses.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
+540
+541
+542
+543
+544
+545
+546
+547
+548
+549
+550
+551
+552
+553
+554
+555
+556
+557
+558
+559
+560
+561
+562
+563
+564
+565
+566
+567
+568
+569
+570
+571
+572
+573
+574
+575
+576
+577
+578
+579
+580
+581
+582
+583
+584
+585
+586
+587
+588
+589
+590
+591
+592
+593
+594
+595
+596
+597
+598
+599
+600
+601
+602
+603
+604
+605
+606
+607
+608
+609
+610
+611
+612
+613
+614
+615
+616
+617
+618
+619
+620
+621
+622
+623
+624
+625
+626
+627
+628
+629
+630
+631
+632
+633
+634
+635
+636
+637
+638
+639
+640
+641
+642
+643
+644
+645
+646
+647
+648
+649
+650
+651
+652
+653
+654
+655
+656
+657
+658
+659
+660
+661
+662
+663
+664
+665
+666
+667
+668
+669
+670
+671
+672
+673
+674
+675
+676
+677
+678
+679
+680
+681
+682
+683
+684
+685
+686
+687
+688
+689
+690
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/.
+*/
+
+use crate::db::{
+    models::{
+        address::{InternalAddress, UpdatableAddressFields},
+        Metadata,
+    },
+    schema::{ADDRESS_COMMON_COLS, ADDRESS_COMMON_VALS},
+};
+use crate::error::*;
+
+use rusqlite::{Connection, Transaction};
+use sync_guid::Guid;
+use types::Timestamp;
+
+pub(crate) fn add_address(
+    conn: &Connection,
+    new: UpdatableAddressFields,
+) -> Result<InternalAddress> {
+    let tx = conn.unchecked_transaction()?;
+    let now = Timestamp::now();
+
+    // We return an InternalAddress, so set it up first, including the missing
+    // fields, before we insert it.
+    let address = InternalAddress {
+        guid: Guid::random(),
+        given_name: new.given_name,
+        additional_name: new.additional_name,
+        family_name: new.family_name,
+        organization: new.organization,
+        street_address: new.street_address,
+        address_level3: new.address_level3,
+        address_level2: new.address_level2,
+        address_level1: new.address_level1,
+        postal_code: new.postal_code,
+        country: new.country,
+        tel: new.tel,
+        email: new.email,
+        metadata: Metadata {
+            time_created: now,
+            time_last_modified: now,
+            ..Default::default()
+        },
+    };
+    add_internal_address(&tx, &address)?;
+    tx.commit()?;
+    Ok(address)
+}
+
+pub(crate) fn add_internal_address(tx: &Transaction<'_>, address: &InternalAddress) -> Result<()> {
+    tx.execute(
+        &format!(
+            "INSERT INTO addresses_data (
+                {common_cols},
+                sync_change_counter
+            ) VALUES (
+                {common_vals},
+                :sync_change_counter
+            )",
+            common_cols = ADDRESS_COMMON_COLS,
+            common_vals = ADDRESS_COMMON_VALS,
+        ),
+        rusqlite::named_params! {
+            ":guid": address.guid,
+            ":given_name": address.given_name,
+            ":additional_name": address.additional_name,
+            ":family_name": address.family_name,
+            ":organization": address.organization,
+            ":street_address": address.street_address,
+            ":address_level3": address.address_level3,
+            ":address_level2": address.address_level2,
+            ":address_level1": address.address_level1,
+            ":postal_code": address.postal_code,
+            ":country": address.country,
+            ":tel": address.tel,
+            ":email": address.email,
+            ":time_created": address.metadata.time_created,
+            ":time_last_used": address.metadata.time_last_used,
+            ":time_last_modified": address.metadata.time_last_modified,
+            ":times_used": address.metadata.times_used,
+            ":sync_change_counter": address.metadata.sync_change_counter,
+        },
+    )?;
+    Ok(())
+}
+
+pub(crate) fn get_address(conn: &Connection, guid: &Guid) -> Result<InternalAddress> {
+    let sql = format!(
+        "SELECT
+            {common_cols},
+            sync_change_counter
+        FROM addresses_data
+        WHERE guid = :guid",
+        common_cols = ADDRESS_COMMON_COLS
+    );
+    conn.query_row(&sql, [guid], InternalAddress::from_row)
+        .map_err(|e| match e {
+            rusqlite::Error::QueryReturnedNoRows => Error::NoSuchRecord(guid.to_string()),
+            e => e.into(),
+        })
+}
+
+pub(crate) fn get_all_addresses(conn: &Connection) -> Result<Vec<InternalAddress>> {
+    let sql = format!(
+        "SELECT
+            {common_cols},
+            sync_change_counter
+        FROM addresses_data",
+        common_cols = ADDRESS_COMMON_COLS
+    );
+
+    let mut stmt = conn.prepare(&sql)?;
+    let addresses = stmt
+        .query_map([], InternalAddress::from_row)?
+        .collect::<std::result::Result<Vec<InternalAddress>, _>>()?;
+    Ok(addresses)
+}
+
+/// Updates just the "updatable" columns - suitable for exposure as a public
+/// API.
+pub(crate) fn update_address(
+    conn: &Connection,
+    guid: &Guid,
+    address: &UpdatableAddressFields,
+) -> Result<()> {
+    let tx = conn.unchecked_transaction()?;
+    tx.execute(
+        "UPDATE addresses_data
+        SET given_name         = :given_name,
+            additional_name     = :additional_name,
+            family_name         = :family_name,
+            organization        = :organization,
+            street_address      = :street_address,
+            address_level3      = :address_level3,
+            address_level2      = :address_level2,
+            address_level1      = :address_level1,
+            postal_code         = :postal_code,
+            country             = :country,
+            tel                 = :tel,
+            email               = :email,
+            sync_change_counter = sync_change_counter + 1
+        WHERE guid              = :guid",
+        rusqlite::named_params! {
+            ":given_name": address.given_name,
+            ":additional_name": address.additional_name,
+            ":family_name": address.family_name,
+            ":organization": address.organization,
+            ":street_address": address.street_address,
+            ":address_level3": address.address_level3,
+            ":address_level2": address.address_level2,
+            ":address_level1": address.address_level1,
+            ":postal_code": address.postal_code,
+            ":country": address.country,
+            ":tel": address.tel,
+            ":email": address.email,
+            ":guid": guid,
+        },
+    )?;
+
+    tx.commit()?;
+    Ok(())
+}
+
+/// Updates all fields including metadata - although the change counter gets
+/// slighly special treatment (eg, when called by Sync we don't want the
+/// change counter incremented)
+pub(crate) fn update_internal_address(
+    tx: &Transaction<'_>,
+    address: &InternalAddress,
+    flag_as_changed: bool,
+) -> Result<()> {
+    let change_counter_increment = flag_as_changed as u32; // will be 1 or 0
+    let rows_changed = tx.execute(
+        "UPDATE addresses_data SET
+            given_name          = :given_name,
+            additional_name     = :additional_name,
+            family_name         = :family_name,
+            organization        = :organization,
+            street_address      = :street_address,
+            address_level3      = :address_level3,
+            address_level2      = :address_level2,
+            address_level1      = :address_level1,
+            postal_code         = :postal_code,
+            country             = :country,
+            tel                 = :tel,
+            email               = :email,
+            time_created        = :time_created,
+            time_last_used      = :time_last_used,
+            time_last_modified  = :time_last_modified,
+            times_used          = :times_used,
+            sync_change_counter = sync_change_counter + :change_incr
+        WHERE guid              = :guid",
+        rusqlite::named_params! {
+            ":given_name": address.given_name,
+            ":additional_name": address.additional_name,
+            ":family_name": address.family_name,
+            ":organization": address.organization,
+            ":street_address": address.street_address,
+            ":address_level3": address.address_level3,
+            ":address_level2": address.address_level2,
+            ":address_level1": address.address_level1,
+            ":postal_code": address.postal_code,
+            ":country": address.country,
+            ":tel": address.tel,
+            ":email": address.email,
+            ":time_created": address.metadata.time_created,
+            ":time_last_used": address.metadata.time_last_used,
+            ":time_last_modified": address.metadata.time_last_modified,
+            ":times_used": address.metadata.times_used,
+            ":change_incr": change_counter_increment,
+            ":guid": address.guid,
+        },
+    )?;
+    // Something went badly wrong if we are asking to update a row that doesn't
+    // exist, or somehow we updated more than 1!
+    assert_eq!(rows_changed, 1);
+    Ok(())
+}
+
+pub(crate) fn delete_address(conn: &Connection, guid: &Guid) -> Result<bool> {
+    let tx = conn.unchecked_transaction()?;
+
+    // execute returns how many rows were affected.
+    let exists = tx.execute(
+        "DELETE FROM addresses_data
+            WHERE guid = :guid",
+        rusqlite::named_params! {
+            ":guid": guid,
+        },
+    )? != 0;
+    tx.commit()?;
+    Ok(exists)
+}
+
+pub fn touch(conn: &Connection, guid: &Guid) -> Result<()> {
+    let tx = conn.unchecked_transaction()?;
+    let now_ms = Timestamp::now();
+
+    tx.execute(
+        "UPDATE addresses_data
+        SET time_last_used              = :time_last_used,
+            times_used                  = times_used + 1,
+            sync_change_counter         = sync_change_counter + 1
+        WHERE guid                      = :guid",
+        rusqlite::named_params! {
+            ":time_last_used": now_ms,
+            ":guid": guid,
+        },
+    )?;
+
+    tx.commit()?;
+    Ok(())
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::db::{schema::create_empty_sync_temp_tables, test::new_mem_db};
+    use sync_guid::Guid;
+    use types::Timestamp;
+
+    #[allow(dead_code)]
+    fn get_all(
+        conn: &Connection,
+        table_name: String,
+    ) -> rusqlite::Result<Vec<String>, rusqlite::Error> {
+        let mut stmt = conn.prepare(&format!(
+            "SELECT guid FROM {table_name}",
+            table_name = table_name
+        ))?;
+        let rows = stmt.query_map([], |row| row.get(0))?;
+
+        let mut guids = Vec::new();
+        for guid_result in rows {
+            guids.push(guid_result?);
+        }
+
+        Ok(guids)
+    }
+
+    fn insert_tombstone_record(
+        conn: &Connection,
+        guid: String,
+    ) -> rusqlite::Result<usize, rusqlite::Error> {
+        conn.execute(
+            "INSERT INTO addresses_tombstones (
+                guid,
+                time_deleted
+            ) VALUES (
+                :guid,
+                :time_deleted
+            )",
+            rusqlite::named_params! {
+                ":guid": guid,
+                ":time_deleted": Timestamp::now(),
+            },
+        )
+    }
+
+    #[test]
+    fn test_address_create_and_read() {
+        let db = new_mem_db();
+
+        let saved_address = add_address(
+            &db,
+            UpdatableAddressFields {
+                given_name: "jane".to_string(),
+                family_name: "doe".to_string(),
+                street_address: "123 Main Street".to_string(),
+                address_level2: "Seattle, WA".to_string(),
+                country: "United States".to_string(),
+
+                ..UpdatableAddressFields::default()
+            },
+        )
+        .expect("should contain saved address");
+
+        // check that the add function populated the guid field
+        assert_ne!(Guid::default(), saved_address.guid);
+
+        // check that the time created and time last modified were set
+        assert_ne!(0, saved_address.metadata.time_created.as_millis());
+        assert_ne!(0, saved_address.metadata.time_last_modified.as_millis());
+
+        assert_eq!(0, saved_address.metadata.sync_change_counter);
+
+        // get created address
+        let retrieved_address = get_address(&db, &saved_address.guid)
+            .expect("should contain optional retrieved address");
+        assert_eq!(saved_address.guid, retrieved_address.guid);
+        assert_eq!(saved_address.given_name, retrieved_address.given_name);
+        assert_eq!(saved_address.family_name, retrieved_address.family_name);
+        assert_eq!(
+            saved_address.street_address,
+            retrieved_address.street_address
+        );
+        assert_eq!(
+            saved_address.address_level2,
+            retrieved_address.address_level2
+        );
+        assert_eq!(saved_address.country, retrieved_address.country);
+
+        // converting the created record into a tombstone to check that it's not returned on a second `get_address` call
+        let delete_result = delete_address(&db, &saved_address.guid);
+        assert!(delete_result.is_ok());
+        assert!(delete_result.unwrap());
+
+        assert!(get_address(&db, &saved_address.guid).is_err());
+    }
+
+    #[test]
+    fn test_address_missing_guid() {
+        let db = new_mem_db();
+        let guid = Guid::random();
+        let result = get_address(&db, &guid);
+
+        assert_eq!(
+            result.unwrap_err().to_string(),
+            Error::NoSuchRecord(guid.to_string()).to_string()
+        );
+    }
+
+    #[test]
+    fn test_address_read_all() {
+        let db = new_mem_db();
+
+        let saved_address = add_address(
+            &db,
+            UpdatableAddressFields {
+                given_name: "jane".to_string(),
+                family_name: "doe".to_string(),
+                street_address: "123 Second Avenue".to_string(),
+                address_level2: "Chicago, IL".to_string(),
+                country: "United States".to_string(),
+
+                ..UpdatableAddressFields::default()
+            },
+        )
+        .expect("should contain saved address");
+
+        let saved_address2 = add_address(
+            &db,
+            UpdatableAddressFields {
+                given_name: "john".to_string(),
+                family_name: "deer".to_string(),
+                street_address: "123 First Avenue".to_string(),
+                address_level2: "Los Angeles, CA".to_string(),
+                country: "United States".to_string(),
+
+                ..UpdatableAddressFields::default()
+            },
+        )
+        .expect("should contain saved address");
+
+        // creating a third address with a tombstone to ensure it's not retunred
+        let saved_address3 = add_address(
+            &db,
+            UpdatableAddressFields {
+                given_name: "abraham".to_string(),
+                family_name: "lincoln".to_string(),
+                street_address: "1600 Pennsylvania Ave NW".to_string(),
+                address_level2: "Washington, DC".to_string(),
+                country: "United States".to_string(),
+
+                ..UpdatableAddressFields::default()
+            },
+        )
+        .expect("should contain saved address");
+
+        let delete_result = delete_address(&db, &saved_address3.guid);
+        assert!(delete_result.is_ok());
+        assert!(delete_result.unwrap());
+
+        let retrieved_addresses =
+            get_all_addresses(&db).expect("Should contain all saved addresses");
+
+        assert!(!retrieved_addresses.is_empty());
+        let expected_number_of_addresses = 2;
+        assert_eq!(expected_number_of_addresses, retrieved_addresses.len());
+
+        let retrieved_address_guids = [
+            retrieved_addresses[0].guid.as_str(),
+            retrieved_addresses[1].guid.as_str(),
+        ];
+        assert!(retrieved_address_guids.contains(&saved_address.guid.as_str()));
+        assert!(retrieved_address_guids.contains(&saved_address2.guid.as_str()));
+    }
+
+    #[test]
+    fn test_address_update() {
+        let db = new_mem_db();
+
+        let saved_address = add_address(
+            &db,
+            UpdatableAddressFields {
+                given_name: "john".to_string(),
+                family_name: "doe".to_string(),
+                street_address: "1300 Broadway".to_string(),
+                address_level2: "New York, NY".to_string(),
+                country: "United States".to_string(),
+
+                ..UpdatableAddressFields::default()
+            },
+        )
+        .expect("should contain saved address");
+        // change_counter starts at 0
+        assert_eq!(0, saved_address.metadata.sync_change_counter);
+
+        let expected_additional_name = "paul".to_string();
+        let update_result = update_address(
+            &db,
+            &saved_address.guid,
+            &UpdatableAddressFields {
+                given_name: "john".to_string(),
+                additional_name: expected_additional_name.clone(),
+                family_name: "deer".to_string(),
+                organization: "".to_string(),
+                street_address: "123 First Avenue".to_string(),
+                address_level3: "".to_string(),
+                address_level2: "Denver, CO".to_string(),
+                address_level1: "".to_string(),
+                postal_code: "".to_string(),
+                country: "United States".to_string(),
+                tel: "".to_string(),
+                email: "".to_string(),
+            },
+        );
+        assert!(update_result.is_ok());
+
+        let updated_address =
+            get_address(&db, &saved_address.guid).expect("should contain optional updated address");
+
+        assert_eq!(saved_address.guid, updated_address.guid);
+        assert_eq!(expected_additional_name, updated_address.additional_name);
+
+        //check that the sync_change_counter was incremented
+        assert_eq!(1, updated_address.metadata.sync_change_counter);
+    }
+
+    #[test]
+    fn test_address_update_internal_address() -> Result<()> {
+        let mut db = new_mem_db();
+        let tx = db.transaction()?;
+
+        let guid = Guid::random();
+        add_internal_address(
+            &tx,
+            &InternalAddress {
+                guid: guid.clone(),
+                given_name: "john".to_string(),
+                additional_name: "paul".to_string(),
+                family_name: "deer".to_string(),
+                organization: "".to_string(),
+                street_address: "123 First Avenue".to_string(),
+                address_level3: "".to_string(),
+                address_level2: "Denver, CO".to_string(),
+                address_level1: "".to_string(),
+                postal_code: "".to_string(),
+                country: "United States".to_string(),
+                tel: "".to_string(),
+                email: "".to_string(),
+                ..Default::default()
+            },
+        )?;
+
+        let expected_family_name = "dear";
+        update_internal_address(
+            &tx,
+            &InternalAddress {
+                guid: guid.clone(),
+                given_name: "john".to_string(),
+                additional_name: "paul".to_string(),
+                family_name: expected_family_name.to_string(),
+                organization: "".to_string(),
+                street_address: "123 First Avenue".to_string(),
+                address_level3: "".to_string(),
+                address_level2: "Denver, CO".to_string(),
+                address_level1: "".to_string(),
+                postal_code: "".to_string(),
+                country: "United States".to_string(),
+                tel: "".to_string(),
+                email: "".to_string(),
+                ..Default::default()
+            },
+            false,
+        )?;
+
+        let record_exists: bool = tx.query_row(
+            "SELECT EXISTS (
+                SELECT 1
+                FROM addresses_data
+                WHERE guid = :guid
+                AND family_name = :family_name
+                AND sync_change_counter = 0
+            )",
+            [&guid.to_string(), &expected_family_name.to_string()],
+            |row| row.get(0),
+        )?;
+        assert!(record_exists);
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_address_delete() {
+        fn num_tombstones(conn: &Connection) -> u32 {
+            let stmt = "SELECT COUNT(*) from addresses_tombstones";
+            conn.query_row(stmt, [], |row| Ok(row.get::<_, u32>(0).unwrap()))
+                .unwrap()
+        }
+
+        let db = new_mem_db();
+        create_empty_sync_temp_tables(&db).expect("should create temp tables");
+
+        let saved_address = add_address(
+            &db,
+            UpdatableAddressFields {
+                given_name: "jane".to_string(),
+                family_name: "doe".to_string(),
+                street_address: "123 Second Avenue".to_string(),
+                address_level2: "Chicago, IL".to_string(),
+                country: "United States".to_string(),
+                ..UpdatableAddressFields::default()
+            },
+        )
+        .expect("first create should work");
+
+        delete_address(&db, &saved_address.guid).expect("delete should work");
+        // should be no tombstone as it wasn't in the mirror.
+        assert_eq!(num_tombstones(&db), 0);
+
+        // do it again, but with it in the mirror.
+        let saved_address = add_address(
+            &db,
+            UpdatableAddressFields {
+                given_name: "jane".to_string(),
+                family_name: "doe".to_string(),
+                street_address: "123 Second Avenue".to_string(),
+                address_level2: "Chicago, IL".to_string(),
+                country: "United States".to_string(),
+                ..UpdatableAddressFields::default()
+            },
+        )
+        .expect("create 2nd address should work");
+        db.execute(
+            &format!(
+                "INSERT INTO addresses_mirror (guid, payload) VALUES ('{}', 'whatever')",
+                saved_address.guid,
+            ),
+            [],
+        )
+        .expect("manual insert into mirror");
+        delete_address(&db, &saved_address.guid).expect("2nd delete");
+        assert_eq!(num_tombstones(&db), 1);
+    }
+
+    #[test]
+    fn test_address_trigger_on_create() {
+        let db = new_mem_db();
+        let tx = db.unchecked_transaction().expect("should get a tx");
+        let guid = Guid::random();
+
+        // create a tombstone record
+        let tombstone_result = insert_tombstone_record(&db, guid.to_string());
+        assert!(tombstone_result.is_ok());
+
+        // create a new address with the tombstone's guid
+        let address = InternalAddress {
+            guid,
+            given_name: "jane".to_string(),
+            family_name: "doe".to_string(),
+            street_address: "123 Second Avenue".to_string(),
+            address_level2: "Chicago, IL".to_string(),
+            country: "United States".to_string(),
+            ..Default::default()
+        };
+
+        let add_address_result = add_internal_address(&tx, &address);
+        assert!(add_address_result.is_err());
+
+        let expected_error_message = "guid exists in `addresses_tombstones`";
+        assert!(add_address_result
+            .unwrap_err()
+            .to_string()
+            .contains(expected_error_message))
+    }
+
+    #[test]
+    fn test_address_trigger_on_delete() {
+        let db = new_mem_db();
+        let tx = db.unchecked_transaction().expect("should get a tx");
+        let guid = Guid::random();
+
+        // create an address
+        let address = InternalAddress {
+            guid,
+            given_name: "jane".to_string(),
+            family_name: "doe".to_string(),
+            street_address: "123 Second Avenue".to_string(),
+            address_level2: "Chicago, IL".to_string(),
+            country: "United States".to_string(),
+            ..Default::default()
+        };
+
+        let add_address_result = add_internal_address(&tx, &address);
+        assert!(add_address_result.is_ok());
+
+        // create a tombstone record with the same guid
+        let tombstone_result = insert_tombstone_record(&db, address.guid.to_string());
+        assert!(tombstone_result.is_err());
+
+        let expected_error_message = "guid exists in `addresses_data`";
+        assert_eq!(
+            expected_error_message,
+            tombstone_result.unwrap_err().to_string()
+        );
+    }
+
+    #[test]
+    fn test_address_touch() -> Result<()> {
+        let db = new_mem_db();
+        let saved_address = add_address(
+            &db,
+            UpdatableAddressFields {
+                given_name: "jane".to_string(),
+                family_name: "doe".to_string(),
+                street_address: "123 Second Avenue".to_string(),
+                address_level2: "Chicago, IL".to_string(),
+                country: "United States".to_string(),
+
+                ..UpdatableAddressFields::default()
+            },
+        )?;
+
+        assert_eq!(saved_address.metadata.sync_change_counter, 0);
+        assert_eq!(saved_address.metadata.times_used, 0);
+
+        touch(&db, &saved_address.guid)?;
+
+        let touched_address = get_address(&db, &saved_address.guid)?;
+
+        assert_eq!(touched_address.metadata.sync_change_counter, 1);
+        assert_eq!(touched_address.metadata.times_used, 1);
+
+        Ok(())
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/autofill/db/credit_cards.rs.html b/book/rust-docs/src/autofill/db/credit_cards.rs.html new file mode 100644 index 0000000000..eb1081ce4e --- /dev/null +++ b/book/rust-docs/src/autofill/db/credit_cards.rs.html @@ -0,0 +1,1421 @@ +credit_cards.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
+540
+541
+542
+543
+544
+545
+546
+547
+548
+549
+550
+551
+552
+553
+554
+555
+556
+557
+558
+559
+560
+561
+562
+563
+564
+565
+566
+567
+568
+569
+570
+571
+572
+573
+574
+575
+576
+577
+578
+579
+580
+581
+582
+583
+584
+585
+586
+587
+588
+589
+590
+591
+592
+593
+594
+595
+596
+597
+598
+599
+600
+601
+602
+603
+604
+605
+606
+607
+608
+609
+610
+611
+612
+613
+614
+615
+616
+617
+618
+619
+620
+621
+622
+623
+624
+625
+626
+627
+628
+629
+630
+631
+632
+633
+634
+635
+636
+637
+638
+639
+640
+641
+642
+643
+644
+645
+646
+647
+648
+649
+650
+651
+652
+653
+654
+655
+656
+657
+658
+659
+660
+661
+662
+663
+664
+665
+666
+667
+668
+669
+670
+671
+672
+673
+674
+675
+676
+677
+678
+679
+680
+681
+682
+683
+684
+685
+686
+687
+688
+689
+690
+691
+692
+693
+694
+695
+696
+697
+698
+699
+700
+701
+702
+703
+704
+705
+706
+707
+708
+709
+710
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/.
+*/
+
+use crate::db::{
+    models::{
+        credit_card::{InternalCreditCard, UpdatableCreditCardFields},
+        Metadata,
+    },
+    schema::{CREDIT_CARD_COMMON_COLS, CREDIT_CARD_COMMON_VALS},
+};
+use crate::error::*;
+
+use rusqlite::{Connection, Transaction};
+use sync_guid::Guid;
+use types::Timestamp;
+
+pub(crate) fn add_credit_card(
+    conn: &Connection,
+    new_credit_card_fields: UpdatableCreditCardFields,
+) -> Result<InternalCreditCard> {
+    let now = Timestamp::now();
+
+    // We return an InternalCreditCard, so set it up first, including the
+    // missing fields, before we insert it.
+    let credit_card = InternalCreditCard {
+        guid: Guid::random(),
+        cc_name: new_credit_card_fields.cc_name,
+        cc_number_enc: new_credit_card_fields.cc_number_enc,
+        cc_number_last_4: new_credit_card_fields.cc_number_last_4,
+        cc_exp_month: new_credit_card_fields.cc_exp_month,
+        cc_exp_year: new_credit_card_fields.cc_exp_year,
+        // Credit card types are a fixed set of strings as defined in the link below
+        // (https://searchfox.org/mozilla-central/rev/7ef5cefd0468b8f509efe38e0212de2398f4c8b3/toolkit/modules/CreditCard.jsm#9-22)
+        cc_type: new_credit_card_fields.cc_type,
+        metadata: Metadata {
+            time_created: now,
+            time_last_modified: now,
+            ..Default::default()
+        },
+    };
+
+    let tx = conn.unchecked_transaction()?;
+    add_internal_credit_card(&tx, &credit_card)?;
+    tx.commit()?;
+    Ok(credit_card)
+}
+
+pub(crate) fn add_internal_credit_card(
+    tx: &Transaction<'_>,
+    card: &InternalCreditCard,
+) -> Result<()> {
+    tx.execute(
+        &format!(
+            "INSERT INTO credit_cards_data (
+                {common_cols},
+                sync_change_counter
+            ) VALUES (
+                {common_vals},
+                :sync_change_counter
+            )",
+            common_cols = CREDIT_CARD_COMMON_COLS,
+            common_vals = CREDIT_CARD_COMMON_VALS,
+        ),
+        rusqlite::named_params! {
+            ":guid": card.guid,
+            ":cc_name": card.cc_name,
+            ":cc_number_enc": card.cc_number_enc,
+            ":cc_number_last_4": card.cc_number_last_4,
+            ":cc_exp_month": card.cc_exp_month,
+            ":cc_exp_year": card.cc_exp_year,
+            ":cc_type": card.cc_type,
+            ":time_created": card.metadata.time_created,
+            ":time_last_used": card.metadata.time_last_used,
+            ":time_last_modified": card.metadata.time_last_modified,
+            ":times_used": card.metadata.times_used,
+            ":sync_change_counter": card.metadata.sync_change_counter,
+        },
+    )?;
+    Ok(())
+}
+
+pub(crate) fn get_credit_card(conn: &Connection, guid: &Guid) -> Result<InternalCreditCard> {
+    let sql = format!(
+        "SELECT
+            {common_cols},
+            sync_change_counter
+        FROM credit_cards_data
+        WHERE guid = :guid",
+        common_cols = CREDIT_CARD_COMMON_COLS
+    );
+
+    conn.query_row(&sql, [guid], InternalCreditCard::from_row)
+        .map_err(|e| match e {
+            rusqlite::Error::QueryReturnedNoRows => Error::NoSuchRecord(guid.to_string()),
+            e => e.into(),
+        })
+}
+
+pub(crate) fn get_all_credit_cards(conn: &Connection) -> Result<Vec<InternalCreditCard>> {
+    let sql = format!(
+        "SELECT
+            {common_cols},
+            sync_change_counter
+        FROM credit_cards_data",
+        common_cols = CREDIT_CARD_COMMON_COLS
+    );
+
+    let mut stmt = conn.prepare(&sql)?;
+    let credit_cards = stmt
+        .query_map([], InternalCreditCard::from_row)?
+        .collect::<std::result::Result<Vec<InternalCreditCard>, _>>()?;
+    Ok(credit_cards)
+}
+
+pub fn update_credit_card(
+    conn: &Connection,
+    guid: &Guid,
+    credit_card: &UpdatableCreditCardFields,
+) -> Result<()> {
+    let tx = conn.unchecked_transaction()?;
+    tx.execute(
+        "UPDATE credit_cards_data
+        SET cc_name                     = :cc_name,
+            cc_number_enc               = :cc_number_enc,
+            cc_number_last_4            = :cc_number_last_4,
+            cc_exp_month                = :cc_exp_month,
+            cc_exp_year                 = :cc_exp_year,
+            cc_type                     = :cc_type,
+            time_last_modified          = :time_last_modified,
+            sync_change_counter         = sync_change_counter + 1
+        WHERE guid                      = :guid",
+        rusqlite::named_params! {
+            ":cc_name": credit_card.cc_name,
+            ":cc_number_enc": credit_card.cc_number_enc,
+            ":cc_number_last_4": credit_card.cc_number_last_4,
+            ":cc_exp_month": credit_card.cc_exp_month,
+            ":cc_exp_year": credit_card.cc_exp_year,
+            ":cc_type": credit_card.cc_type,
+            ":time_last_modified": Timestamp::now(),
+            ":guid": guid,
+        },
+    )?;
+
+    tx.commit()?;
+    Ok(())
+}
+
+/// Updates all fields including metadata - although the change counter gets
+/// slightly special treatment (eg, when called by Sync we don't want the
+/// change counter incremented).
+pub(crate) fn update_internal_credit_card(
+    tx: &Transaction<'_>,
+    card: &InternalCreditCard,
+    flag_as_changed: bool,
+) -> Result<()> {
+    let change_counter_increment = flag_as_changed as u32; // will be 1 or 0
+    tx.execute(
+        "UPDATE credit_cards_data
+        SET cc_name                     = :cc_name,
+            cc_number_enc               = :cc_number_enc,
+            cc_number_last_4            = :cc_number_last_4,
+            cc_exp_month                = :cc_exp_month,
+            cc_exp_year                 = :cc_exp_year,
+            cc_type                     = :cc_type,
+            time_created                = :time_created,
+            time_last_used              = :time_last_used,
+            time_last_modified          = :time_last_modified,
+            times_used                  = :times_used,
+            sync_change_counter         = sync_change_counter + :change_incr
+        WHERE guid                      = :guid",
+        rusqlite::named_params! {
+            ":cc_name": card.cc_name,
+            ":cc_number_enc": card.cc_number_enc,
+            ":cc_number_last_4": card.cc_number_last_4,
+            ":cc_exp_month": card.cc_exp_month,
+            ":cc_exp_year": card.cc_exp_year,
+            ":cc_type": card.cc_type,
+            ":time_created": card.metadata.time_created,
+            ":time_last_used": card.metadata.time_last_used,
+            ":time_last_modified": card.metadata.time_last_modified,
+            ":times_used": card.metadata.times_used,
+            ":change_incr": change_counter_increment,
+            ":guid": card.guid,
+        },
+    )?;
+    Ok(())
+}
+
+pub fn delete_credit_card(conn: &Connection, guid: &Guid) -> Result<bool> {
+    let tx = conn.unchecked_transaction()?;
+
+    // execute returns how many rows were affected.
+    let exists = tx.execute(
+        "DELETE FROM credit_cards_data
+        WHERE guid = :guid",
+        rusqlite::named_params! {
+            ":guid": guid.as_str(),
+        },
+    )? != 0;
+
+    tx.commit()?;
+    Ok(exists)
+}
+
+pub fn scrub_encrypted_credit_card_data(conn: &Connection) -> Result<()> {
+    let tx = conn.unchecked_transaction()?;
+    tx.execute("UPDATE credit_cards_data SET cc_number_enc = ''", [])?;
+    tx.commit()?;
+    Ok(())
+}
+
+pub fn touch(conn: &Connection, guid: &Guid) -> Result<()> {
+    let tx = conn.unchecked_transaction()?;
+    let now_ms = Timestamp::now();
+
+    tx.execute(
+        "UPDATE credit_cards_data
+        SET time_last_used              = :time_last_used,
+            times_used                  = times_used + 1,
+            sync_change_counter         = sync_change_counter + 1
+        WHERE guid                      = :guid",
+        rusqlite::named_params! {
+            ":time_last_used": now_ms,
+            ":guid": guid.as_str(),
+        },
+    )?;
+
+    tx.commit()?;
+    Ok(())
+}
+
+#[cfg(test)]
+pub(crate) mod tests {
+    use super::*;
+    use crate::db::test::new_mem_db;
+    use crate::encryption::EncryptorDecryptor;
+    use sync15::bso::IncomingBso;
+
+    pub fn get_all(
+        conn: &Connection,
+        table_name: String,
+    ) -> rusqlite::Result<Vec<String>, rusqlite::Error> {
+        let mut stmt = conn.prepare(&format!(
+            "SELECT guid FROM {table_name}",
+            table_name = table_name
+        ))?;
+        let rows = stmt.query_map([], |row| row.get(0))?;
+
+        let mut guids = Vec::new();
+        for guid_result in rows {
+            guids.push(guid_result?);
+        }
+
+        Ok(guids)
+    }
+
+    pub fn insert_tombstone_record(
+        conn: &Connection,
+        guid: String,
+    ) -> rusqlite::Result<usize, rusqlite::Error> {
+        conn.execute(
+            "INSERT INTO credit_cards_tombstones (
+                guid,
+                time_deleted
+            ) VALUES (
+                :guid,
+                :time_deleted
+            )",
+            rusqlite::named_params! {
+                ":guid": guid,
+                ":time_deleted": Timestamp::now(),
+            },
+        )
+    }
+
+    pub(crate) fn test_insert_mirror_record(conn: &Connection, bso: IncomingBso) {
+        // This test function is a bit suspect, because credit-cards always
+        // store encrypted records, which this ignores entirely, and stores the
+        // raw payload with a cleartext cc_number.
+        // It's OK for all current test consumers, but it's a bit of a smell...
+        conn.execute(
+            "INSERT INTO credit_cards_mirror (guid, payload)
+             VALUES (:guid, :payload)",
+            rusqlite::named_params! {
+                ":guid": &bso.envelope.id,
+                ":payload": &bso.payload,
+            },
+        )
+        .expect("should insert");
+    }
+
+    #[test]
+    fn test_credit_card_create_and_read() -> Result<()> {
+        let db = new_mem_db();
+
+        let saved_credit_card = add_credit_card(
+            &db,
+            UpdatableCreditCardFields {
+                cc_name: "jane doe".to_string(),
+                cc_number_enc: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX".to_string(),
+                cc_number_last_4: "1234".to_string(),
+                cc_exp_month: 3,
+                cc_exp_year: 2022,
+                cc_type: "visa".to_string(),
+            },
+        )?;
+
+        // check that the add function populated the guid field
+        assert_ne!(Guid::default(), saved_credit_card.guid);
+
+        // check that the time created and time last modified were set
+        assert_ne!(0, saved_credit_card.metadata.time_created.as_millis());
+        assert_ne!(0, saved_credit_card.metadata.time_last_modified.as_millis());
+
+        // check that sync_change_counter was set to 0.
+        assert_eq!(0, saved_credit_card.metadata.sync_change_counter);
+
+        // get created credit card
+        let retrieved_credit_card = get_credit_card(&db, &saved_credit_card.guid)?;
+
+        assert_eq!(saved_credit_card.guid, retrieved_credit_card.guid);
+        assert_eq!(saved_credit_card.cc_name, retrieved_credit_card.cc_name);
+        assert_eq!(
+            saved_credit_card.cc_number_enc,
+            retrieved_credit_card.cc_number_enc
+        );
+        assert_eq!(
+            saved_credit_card.cc_number_last_4,
+            retrieved_credit_card.cc_number_last_4
+        );
+        assert_eq!(
+            saved_credit_card.cc_exp_month,
+            retrieved_credit_card.cc_exp_month
+        );
+        assert_eq!(
+            saved_credit_card.cc_exp_year,
+            retrieved_credit_card.cc_exp_year
+        );
+        assert_eq!(saved_credit_card.cc_type, retrieved_credit_card.cc_type);
+
+        // converting the created record into a tombstone to check that it's not returned on a second `get_credit_card` call
+        let delete_result = delete_credit_card(&db, &saved_credit_card.guid);
+        assert!(delete_result.is_ok());
+        assert!(delete_result?);
+
+        assert!(get_credit_card(&db, &saved_credit_card.guid).is_err());
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_credit_card_missing_guid() {
+        let db = new_mem_db();
+        let guid = Guid::random();
+        let result = get_credit_card(&db, &guid);
+
+        assert_eq!(
+            result.unwrap_err().to_string(),
+            Error::NoSuchRecord(guid.to_string()).to_string()
+        );
+    }
+
+    #[test]
+    fn test_credit_card_read_all() -> Result<()> {
+        let db = new_mem_db();
+
+        let saved_credit_card = add_credit_card(
+            &db,
+            UpdatableCreditCardFields {
+                cc_name: "jane doe".to_string(),
+                cc_number_enc: "YYYYYYYYYYYYYYYYYYYYYYYYYYYYY".to_string(),
+                cc_number_last_4: "4321".to_string(),
+                cc_exp_month: 3,
+                cc_exp_year: 2022,
+                cc_type: "visa".to_string(),
+            },
+        )?;
+
+        let saved_credit_card2 = add_credit_card(
+            &db,
+            UpdatableCreditCardFields {
+                cc_name: "john deer".to_string(),
+                cc_number_enc: "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ".to_string(),
+                cc_number_last_4: "6543".to_string(),
+                cc_exp_month: 10,
+                cc_exp_year: 2025,
+                cc_type: "mastercard".to_string(),
+            },
+        )?;
+
+        // creating a third credit card with a tombstone to ensure it's not retunred
+        let saved_credit_card3 = add_credit_card(
+            &db,
+            UpdatableCreditCardFields {
+                cc_name: "abraham lincoln".to_string(),
+                cc_number_enc: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA".to_string(),
+                cc_number_last_4: "9876".to_string(),
+                cc_exp_month: 1,
+                cc_exp_year: 2024,
+                cc_type: "amex".to_string(),
+            },
+        )?;
+
+        let delete_result = delete_credit_card(&db, &saved_credit_card3.guid);
+        assert!(delete_result.is_ok());
+        assert!(delete_result?);
+
+        let retrieved_credit_cards = get_all_credit_cards(&db)?;
+
+        assert!(!retrieved_credit_cards.is_empty());
+        let expected_number_of_credit_cards = 2;
+        assert_eq!(
+            expected_number_of_credit_cards,
+            retrieved_credit_cards.len()
+        );
+
+        let retrieved_credit_card_guids = [
+            retrieved_credit_cards[0].guid.as_str(),
+            retrieved_credit_cards[1].guid.as_str(),
+        ];
+        assert!(retrieved_credit_card_guids.contains(&saved_credit_card.guid.as_str()));
+        assert!(retrieved_credit_card_guids.contains(&saved_credit_card2.guid.as_str()));
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_credit_card_update() -> Result<()> {
+        let db = new_mem_db();
+
+        let saved_credit_card = add_credit_card(
+            &db,
+            UpdatableCreditCardFields {
+                cc_name: "john deer".to_string(),
+                cc_number_enc: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA".to_string(),
+                cc_number_last_4: "4321".to_string(),
+                cc_exp_month: 10,
+                cc_exp_year: 2025,
+                cc_type: "mastercard".to_string(),
+            },
+        )?;
+
+        let expected_cc_name = "john doe".to_string();
+        let update_result = update_credit_card(
+            &db,
+            &saved_credit_card.guid,
+            &UpdatableCreditCardFields {
+                cc_name: expected_cc_name.clone(),
+                cc_number_enc: "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBB".to_string(),
+                cc_number_last_4: "1234".to_string(),
+                cc_type: "mastercard".to_string(),
+                cc_exp_month: 10,
+                cc_exp_year: 2025,
+            },
+        );
+        assert!(update_result.is_ok());
+
+        let updated_credit_card = get_credit_card(&db, &saved_credit_card.guid)?;
+
+        assert_eq!(saved_credit_card.guid, updated_credit_card.guid);
+        assert_eq!(expected_cc_name, updated_credit_card.cc_name);
+
+        //check that the sync_change_counter was incremented
+        assert_eq!(1, updated_credit_card.metadata.sync_change_counter);
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_credit_card_update_internal_credit_card() -> Result<()> {
+        let mut db = new_mem_db();
+        let tx = db.transaction()?;
+
+        let guid = Guid::random();
+        add_internal_credit_card(
+            &tx,
+            &InternalCreditCard {
+                guid: guid.clone(),
+                cc_name: "john deer".to_string(),
+                cc_number_enc: "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBB".to_string(),
+                cc_number_last_4: "1234".to_string(),
+                cc_exp_month: 10,
+                cc_exp_year: 2025,
+                cc_type: "mastercard".to_string(),
+                ..Default::default()
+            },
+        )?;
+
+        let expected_cc_exp_month = 11;
+        update_internal_credit_card(
+            &tx,
+            &InternalCreditCard {
+                guid: guid.clone(),
+                cc_name: "john deer".to_string(),
+                cc_number_enc: "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBB".to_string(),
+                cc_number_last_4: "1234".to_string(),
+                cc_exp_month: expected_cc_exp_month,
+                cc_exp_year: 2025,
+                cc_type: "mastercard".to_string(),
+                ..Default::default()
+            },
+            false,
+        )?;
+
+        let record_exists: bool = tx.query_row(
+            "SELECT EXISTS (
+                SELECT 1
+                FROM credit_cards_data
+                WHERE guid = :guid
+                AND cc_exp_month = :cc_exp_month
+                AND sync_change_counter = 0
+            )",
+            [&guid.to_string(), &expected_cc_exp_month.to_string()],
+            |row| row.get(0),
+        )?;
+        assert!(record_exists);
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_credit_card_delete() -> Result<()> {
+        let db = new_mem_db();
+        let encdec = EncryptorDecryptor::new_with_random_key().unwrap();
+
+        let saved_credit_card = add_credit_card(
+            &db,
+            UpdatableCreditCardFields {
+                cc_name: "john deer".to_string(),
+                cc_number_enc: encdec.encrypt("1234567812345678", "cc_number")?,
+                cc_number_last_4: "5678".to_string(),
+                cc_exp_month: 10,
+                cc_exp_year: 2025,
+                cc_type: "mastercard".to_string(),
+            },
+        )?;
+
+        let delete_result = delete_credit_card(&db, &saved_credit_card.guid);
+        assert!(delete_result.is_ok());
+        assert!(delete_result?);
+
+        let saved_credit_card2 = add_credit_card(
+            &db,
+            UpdatableCreditCardFields {
+                cc_name: "john doe".to_string(),
+                cc_number_enc: encdec.encrypt("1234123412341234", "cc_number")?,
+                cc_number_last_4: "1234".to_string(),
+                cc_exp_month: 5,
+                cc_exp_year: 2024,
+                cc_type: "visa".to_string(),
+            },
+        )?;
+
+        // create a mirror record to check that a tombstone record is created upon deletion
+        let cc2_guid = saved_credit_card2.guid.clone();
+        let payload = saved_credit_card2.into_test_incoming_bso(&encdec, Default::default());
+
+        test_insert_mirror_record(&db, payload);
+
+        let delete_result2 = delete_credit_card(&db, &cc2_guid);
+        assert!(delete_result2.is_ok());
+        assert!(delete_result2?);
+
+        // check that a tombstone record exists since the record existed in the mirror
+        let tombstone_exists: bool = db.query_row(
+            "SELECT EXISTS (
+                SELECT 1
+                FROM credit_cards_tombstones
+                WHERE guid = :guid
+            )",
+            [&cc2_guid],
+            |row| row.get(0),
+        )?;
+        assert!(tombstone_exists);
+
+        // remove the tombstone record
+        db.execute(
+            "DELETE FROM credit_cards_tombstones
+            WHERE guid = :guid",
+            rusqlite::named_params! {
+                ":guid": cc2_guid,
+            },
+        )?;
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_scrub_encrypted_credit_card_data() -> Result<()> {
+        let db = new_mem_db();
+        let encdec = EncryptorDecryptor::new_with_random_key().unwrap();
+        let mut saved_credit_cards = Vec::with_capacity(10);
+        for _ in 0..5 {
+            saved_credit_cards.push(add_credit_card(
+                &db,
+                UpdatableCreditCardFields {
+                    cc_name: "john deer".to_string(),
+                    cc_number_enc: encdec.encrypt("1234567812345678", "cc_number")?,
+                    cc_number_last_4: "5678".to_string(),
+                    cc_exp_month: 10,
+                    cc_exp_year: 2025,
+                    cc_type: "mastercard".to_string(),
+                },
+            )?);
+        }
+
+        scrub_encrypted_credit_card_data(&db)?;
+        for saved_credit_card in saved_credit_cards.into_iter() {
+            let retrieved_credit_card = get_credit_card(&db, &saved_credit_card.guid)?;
+            assert_eq!(retrieved_credit_card.cc_number_enc, "");
+        }
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_credit_card_trigger_on_create() -> Result<()> {
+        let db = new_mem_db();
+        let tx = db.unchecked_transaction()?;
+        let guid = Guid::random();
+
+        // create a tombstone record
+        insert_tombstone_record(&db, guid.to_string())?;
+
+        // create a new credit card with the tombstone's guid
+        let credit_card = InternalCreditCard {
+            guid,
+            cc_name: "john deer".to_string(),
+            cc_number_enc: "WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW".to_string(),
+            cc_number_last_4: "6543".to_string(),
+            cc_exp_month: 10,
+            cc_exp_year: 2025,
+            cc_type: "mastercard".to_string(),
+
+            ..Default::default()
+        };
+
+        let add_credit_card_result = add_internal_credit_card(&tx, &credit_card);
+        assert!(add_credit_card_result.is_err());
+
+        let expected_error_message = "guid exists in `credit_cards_tombstones`";
+        assert!(add_credit_card_result
+            .unwrap_err()
+            .to_string()
+            .contains(expected_error_message));
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_credit_card_trigger_on_delete() -> Result<()> {
+        let db = new_mem_db();
+        let tx = db.unchecked_transaction()?;
+        let guid = Guid::random();
+
+        // create an credit card
+        let credit_card = InternalCreditCard {
+            guid,
+            cc_name: "jane doe".to_string(),
+            cc_number_enc: "WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW".to_string(),
+            cc_number_last_4: "6543".to_string(),
+            cc_exp_month: 3,
+            cc_exp_year: 2022,
+            cc_type: "visa".to_string(),
+            ..Default::default()
+        };
+        add_internal_credit_card(&tx, &credit_card)?;
+
+        // create a tombstone record with the same guid
+        let tombstone_result = insert_tombstone_record(&db, credit_card.guid.to_string());
+
+        let expected_error_message = "guid exists in `credit_cards_data`";
+        assert!(tombstone_result
+            .unwrap_err()
+            .to_string()
+            .contains(expected_error_message));
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_credit_card_touch() -> Result<()> {
+        let db = new_mem_db();
+        let saved_credit_card = add_credit_card(
+            &db,
+            UpdatableCreditCardFields {
+                cc_name: "john doe".to_string(),
+                cc_number_enc: "WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW".to_string(),
+                cc_number_last_4: "6543".to_string(),
+                cc_exp_month: 5,
+                cc_exp_year: 2024,
+                cc_type: "visa".to_string(),
+            },
+        )?;
+
+        assert_eq!(saved_credit_card.metadata.sync_change_counter, 0);
+        assert_eq!(saved_credit_card.metadata.times_used, 0);
+
+        touch(&db, &saved_credit_card.guid)?;
+
+        let touched_credit_card = get_credit_card(&db, &saved_credit_card.guid)?;
+
+        assert_eq!(touched_credit_card.metadata.sync_change_counter, 1);
+        assert_eq!(touched_credit_card.metadata.times_used, 1);
+
+        Ok(())
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/autofill/db/mod.rs.html b/book/rust-docs/src/autofill/db/mod.rs.html new file mode 100644 index 0000000000..f3c0b34971 --- /dev/null +++ b/book/rust-docs/src/autofill/db/mod.rs.html @@ -0,0 +1,305 @@ +mod.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+pub mod addresses;
+pub mod credit_cards;
+pub mod models;
+pub mod schema;
+pub mod store;
+
+use crate::error::*;
+
+use interrupt_support::{SqlInterruptHandle, SqlInterruptScope};
+use rusqlite::{Connection, OpenFlags};
+use sql_support::open_database;
+use std::sync::Arc;
+use std::{
+    ops::{Deref, DerefMut},
+    path::{Path, PathBuf},
+};
+use url::Url;
+
+pub struct AutofillDb {
+    pub writer: Connection,
+    interrupt_handle: Arc<SqlInterruptHandle>,
+}
+
+impl AutofillDb {
+    pub fn new(db_path: impl AsRef<Path>) -> Result<Self> {
+        let db_path = normalize_path(db_path)?;
+        Self::new_named(db_path)
+    }
+
+    pub fn new_memory(db_path: &str) -> Result<Self> {
+        let name = PathBuf::from(format!("file:{}?mode=memory&cache=shared", db_path));
+        Self::new_named(name)
+    }
+
+    fn new_named(db_path: PathBuf) -> Result<Self> {
+        // We always create the read-write connection for an initial open so
+        // we can create the schema and/or do version upgrades.
+        let flags = OpenFlags::SQLITE_OPEN_NO_MUTEX
+            | OpenFlags::SQLITE_OPEN_URI
+            | OpenFlags::SQLITE_OPEN_CREATE
+            | OpenFlags::SQLITE_OPEN_READ_WRITE;
+
+        let conn = open_database::open_database_with_flags(
+            db_path,
+            flags,
+            &schema::AutofillConnectionInitializer,
+        )?;
+
+        Ok(Self {
+            interrupt_handle: Arc::new(SqlInterruptHandle::new(&conn)),
+            writer: conn,
+        })
+    }
+
+    #[inline]
+    pub fn begin_interrupt_scope(&self) -> Result<SqlInterruptScope> {
+        Ok(self.interrupt_handle.begin_interrupt_scope()?)
+    }
+}
+
+impl Deref for AutofillDb {
+    type Target = Connection;
+
+    fn deref(&self) -> &Self::Target {
+        &self.writer
+    }
+}
+
+impl DerefMut for AutofillDb {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.writer
+    }
+}
+
+fn unurl_path(p: impl AsRef<Path>) -> PathBuf {
+    p.as_ref()
+        .to_str()
+        .and_then(|s| Url::parse(s).ok())
+        .and_then(|u| {
+            if u.scheme() == "file" {
+                u.to_file_path().ok()
+            } else {
+                None
+            }
+        })
+        .unwrap_or_else(|| p.as_ref().to_owned())
+}
+
+fn normalize_path(p: impl AsRef<Path>) -> Result<PathBuf> {
+    let path = unurl_path(p);
+    if let Ok(canonical) = path.canonicalize() {
+        return Ok(canonical);
+    }
+    // It probably doesn't exist yet. This is an error, although it seems to
+    // work on some systems.
+    //
+    // We resolve this by trying to canonicalize the parent directory, and
+    // appending the requested file name onto that. If we can't canonicalize
+    // the parent, we return an error.
+    //
+    // Also, we return errors if the path ends in "..", if there is no
+    // parent directory, etc.
+    let file_name = path
+        .file_name()
+        .ok_or_else(|| Error::IllegalDatabasePath(path.clone()))?;
+
+    let parent = path
+        .parent()
+        .ok_or_else(|| Error::IllegalDatabasePath(path.clone()))?;
+
+    let mut canonical = parent.canonicalize()?;
+    canonical.push(file_name);
+    Ok(canonical)
+}
+
+pub(crate) mod sql_fns {
+    use rusqlite::{functions::Context, Result};
+    use sync_guid::Guid as SyncGuid;
+    use types::Timestamp;
+
+    #[inline(never)]
+    #[allow(dead_code)]
+    pub fn generate_guid(_ctx: &Context<'_>) -> Result<SyncGuid> {
+        Ok(SyncGuid::random())
+    }
+
+    #[inline(never)]
+    pub fn now(_ctx: &Context<'_>) -> Result<Timestamp> {
+        Ok(Timestamp::now())
+    }
+}
+
+// Helpers for tests
+#[cfg(test)]
+pub mod test {
+    use super::*;
+    use std::sync::atomic::{AtomicUsize, Ordering};
+
+    // A helper for our tests to get their own memory Api.
+    static ATOMIC_COUNTER: AtomicUsize = AtomicUsize::new(0);
+
+    pub fn new_mem_db() -> AutofillDb {
+        let _ = env_logger::try_init();
+        let counter = ATOMIC_COUNTER.fetch_add(1, Ordering::Relaxed);
+        AutofillDb::new_memory(&format!("test_autofill-api-{}", counter))
+            .expect("should get an API")
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/autofill/db/models/address.rs.html b/book/rust-docs/src/autofill/db/models/address.rs.html new file mode 100644 index 0000000000..35d2348378 --- /dev/null +++ b/book/rust-docs/src/autofill/db/models/address.rs.html @@ -0,0 +1,265 @@ +address.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/.
+*/
+
+use super::Metadata;
+use rusqlite::Row;
+use sync_guid::Guid;
+
+// UpdatableAddressFields contains the fields we support for creating a new
+// address or updating an existing one. It's missing the guid, our "internal"
+// meta fields (such as the change counter) and "external" meta fields
+// (such as timeCreated) because it doesn't make sense for these things to be
+// specified as an item is created - any meta fields which can be updated
+// have special methods for doing so.
+#[derive(Debug, Clone, Default)]
+pub struct UpdatableAddressFields {
+    pub given_name: String,
+    pub additional_name: String,
+    pub family_name: String,
+    pub organization: String,
+    pub street_address: String,
+    pub address_level3: String,
+    pub address_level2: String,
+    pub address_level1: String,
+    pub postal_code: String,
+    pub country: String,
+    pub tel: String,
+    pub email: String,
+}
+
+// "Address" is what we return to consumers and has most of the metadata.
+#[derive(Debug, Clone, Hash, PartialEq, Eq, Default)]
+pub struct Address {
+    pub guid: String,
+    pub given_name: String,
+    pub additional_name: String,
+    pub family_name: String,
+    pub organization: String,
+    pub street_address: String,
+    pub address_level3: String,
+    pub address_level2: String,
+    pub address_level1: String,
+    pub postal_code: String,
+    pub country: String,
+    pub tel: String,
+    pub email: String,
+    // We expose some of the metadata
+    pub time_created: i64,
+    pub time_last_used: Option<i64>,
+    pub time_last_modified: i64,
+    pub times_used: i64,
+}
+
+// This is used to "externalize" an address, suitable for handing back to
+// consumers.
+impl From<InternalAddress> for Address {
+    fn from(ia: InternalAddress) -> Self {
+        Address {
+            guid: ia.guid.to_string(),
+            given_name: ia.given_name,
+            additional_name: ia.additional_name,
+            family_name: ia.family_name,
+            organization: ia.organization,
+            street_address: ia.street_address,
+            address_level3: ia.address_level3,
+            address_level2: ia.address_level2,
+            address_level1: ia.address_level1,
+            postal_code: ia.postal_code,
+            country: ia.country,
+            tel: ia.tel,
+            email: ia.email,
+            // note we can't use u64 in uniffi
+            time_created: u64::from(ia.metadata.time_created) as i64,
+            time_last_used: if ia.metadata.time_last_used.0 == 0 {
+                None
+            } else {
+                Some(ia.metadata.time_last_used.0 as i64)
+            },
+            time_last_modified: u64::from(ia.metadata.time_last_modified) as i64,
+            times_used: ia.metadata.times_used,
+        }
+    }
+}
+
+// An "internal" address is used by the public APIs and by sync. No `PartialEq`
+// because it's impossible to do it meaningfully for credit-cards and we'd like
+// to keep the API symmetric
+#[derive(Default, Debug, Clone)]
+pub struct InternalAddress {
+    pub guid: Guid,
+    pub given_name: String,
+    pub additional_name: String,
+    pub family_name: String,
+    pub organization: String,
+    pub street_address: String,
+    pub address_level3: String,
+    pub address_level2: String,
+    pub address_level1: String,
+    pub postal_code: String,
+    pub country: String,
+    pub tel: String,
+    pub email: String,
+    pub metadata: Metadata,
+}
+
+impl InternalAddress {
+    pub fn from_row(row: &Row<'_>) -> Result<InternalAddress, rusqlite::Error> {
+        Ok(Self {
+            guid: row.get("guid")?,
+            given_name: row.get("given_name")?,
+            additional_name: row.get("additional_name")?,
+            family_name: row.get("family_name")?,
+            organization: row.get("organization")?,
+            street_address: row.get("street_address")?,
+            address_level3: row.get("address_level3")?,
+            address_level2: row.get("address_level2")?,
+            address_level1: row.get("address_level1")?,
+            postal_code: row.get("postal_code")?,
+            country: row.get("country")?,
+            tel: row.get("tel")?,
+            email: row.get("email")?,
+            metadata: Metadata {
+                time_created: row.get("time_created")?,
+                time_last_used: row.get("time_last_used")?,
+                time_last_modified: row.get("time_last_modified")?,
+                times_used: row.get("times_used")?,
+                sync_change_counter: row.get("sync_change_counter")?,
+            },
+        })
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/autofill/db/models/credit_card.rs.html b/book/rust-docs/src/autofill/db/models/credit_card.rs.html new file mode 100644 index 0000000000..274977bd18 --- /dev/null +++ b/book/rust-docs/src/autofill/db/models/credit_card.rs.html @@ -0,0 +1,213 @@ +credit_card.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/.
+*/
+
+use super::Metadata;
+use rusqlite::Row;
+use sync_guid::Guid;
+
+#[derive(Debug, Clone, Default)]
+pub struct UpdatableCreditCardFields {
+    pub cc_name: String,
+    pub cc_number_enc: String,
+    pub cc_number_last_4: String,
+    pub cc_exp_month: i64,
+    pub cc_exp_year: i64,
+    // Credit card types are a fixed set of strings as defined in the link below
+    // (https://searchfox.org/mozilla-central/rev/7ef5cefd0468b8f509efe38e0212de2398f4c8b3/toolkit/modules/CreditCard.jsm#9-22)
+    pub cc_type: String,
+}
+
+#[derive(Debug, Clone, Default)]
+pub struct CreditCard {
+    pub guid: String,
+    pub cc_name: String,
+    pub cc_number_enc: String,
+    pub cc_number_last_4: String,
+    pub cc_exp_month: i64,
+    pub cc_exp_year: i64,
+
+    // Credit card types are a fixed set of strings as defined in the link below
+    // (https://searchfox.org/mozilla-central/rev/7ef5cefd0468b8f509efe38e0212de2398f4c8b3/toolkit/modules/CreditCard.jsm#9-22)
+    pub cc_type: String,
+
+    // The metadata
+    pub time_created: i64,
+    pub time_last_used: Option<i64>,
+    pub time_last_modified: i64,
+    pub times_used: i64,
+}
+
+// This is used to "externalize" a credit-card, suitable for handing back to
+// consumers.
+impl From<InternalCreditCard> for CreditCard {
+    fn from(icc: InternalCreditCard) -> Self {
+        CreditCard {
+            guid: icc.guid.to_string(),
+            cc_name: icc.cc_name,
+            cc_number_enc: icc.cc_number_enc,
+            cc_number_last_4: icc.cc_number_last_4,
+            cc_exp_month: icc.cc_exp_month,
+            cc_exp_year: icc.cc_exp_year,
+            cc_type: icc.cc_type,
+            // note we can't use u64 in uniffi
+            time_created: u64::from(icc.metadata.time_created) as i64,
+            time_last_used: if icc.metadata.time_last_used.0 == 0 {
+                None
+            } else {
+                Some(icc.metadata.time_last_used.0 as i64)
+            },
+            time_last_modified: u64::from(icc.metadata.time_last_modified) as i64,
+            times_used: icc.metadata.times_used,
+        }
+    }
+}
+
+// NOTE: No `PartialEq` here because the same card number will encrypt to a
+// different value each time it is encrypted, making it meaningless to compare.
+#[derive(Debug, Clone, Default)]
+pub struct InternalCreditCard {
+    pub guid: Guid,
+    pub cc_name: String,
+    pub cc_number_enc: String,
+    pub cc_number_last_4: String,
+    pub cc_exp_month: i64,
+    pub cc_exp_year: i64,
+    // Credit card types are a fixed set of strings as defined in the link below
+    // (https://searchfox.org/mozilla-central/rev/7ef5cefd0468b8f509efe38e0212de2398f4c8b3/toolkit/modules/CreditCard.jsm#9-22)
+    pub cc_type: String,
+    pub metadata: Metadata,
+}
+
+impl InternalCreditCard {
+    pub fn from_row(row: &Row<'_>) -> Result<InternalCreditCard, rusqlite::Error> {
+        Ok(Self {
+            guid: Guid::from_string(row.get("guid")?),
+            cc_name: row.get("cc_name")?,
+            cc_number_enc: row.get("cc_number_enc")?,
+            cc_number_last_4: row.get("cc_number_last_4")?,
+            cc_exp_month: row.get("cc_exp_month")?,
+            cc_exp_year: row.get("cc_exp_year")?,
+            cc_type: row.get("cc_type")?,
+            metadata: Metadata {
+                time_created: row.get("time_created")?,
+                time_last_used: row.get("time_last_used")?,
+                time_last_modified: row.get("time_last_modified")?,
+                times_used: row.get("times_used")?,
+                sync_change_counter: row.get("sync_change_counter")?,
+            },
+        })
+    }
+
+    pub fn has_scrubbed_data(&self) -> bool {
+        self.cc_number_enc.is_empty()
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/autofill/db/models/mod.rs.html b/book/rust-docs/src/autofill/db/models/mod.rs.html new file mode 100644 index 0000000000..ee0879c5de --- /dev/null +++ b/book/rust-docs/src/autofill/db/models/mod.rs.html @@ -0,0 +1,37 @@ +mod.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/.
+*/
+
+pub mod address;
+pub mod credit_card;
+use types::Timestamp;
+
+/// Metadata that's common between the records.
+#[derive(Debug, PartialEq, Eq, Copy, Clone, Default)]
+pub struct Metadata {
+    pub time_created: Timestamp,
+    pub time_last_used: Timestamp,
+    pub time_last_modified: Timestamp,
+    pub times_used: i64,
+    pub sync_change_counter: i64,
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/autofill/db/schema.rs.html b/book/rust-docs/src/autofill/db/schema.rs.html new file mode 100644 index 0000000000..49f07092e3 --- /dev/null +++ b/book/rust-docs/src/autofill/db/schema.rs.html @@ -0,0 +1,605 @@ +schema.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::db::sql_fns;
+use rusqlite::{functions::FunctionFlags, Connection, Transaction};
+use sql_support::open_database::{ConnectionInitializer, Error, Result};
+
+pub const ADDRESS_COMMON_COLS: &str = "
+    guid,
+    given_name,
+    additional_name,
+    family_name,
+    organization,
+    street_address,
+    address_level3,
+    address_level2,
+    address_level1,
+    postal_code,
+    country,
+    tel,
+    email,
+    time_created,
+    time_last_used,
+    time_last_modified,
+    times_used";
+
+pub const ADDRESS_COMMON_VALS: &str = "
+    :guid,
+    :given_name,
+    :additional_name,
+    :family_name,
+    :organization,
+    :street_address,
+    :address_level3,
+    :address_level2,
+    :address_level1,
+    :postal_code,
+    :country,
+    :tel,
+    :email,
+    :time_created,
+    :time_last_used,
+    :time_last_modified,
+    :times_used";
+
+pub const CREDIT_CARD_COMMON_COLS: &str = "
+    guid,
+    cc_name,
+    cc_number_enc,
+    cc_number_last_4,
+    cc_exp_month,
+    cc_exp_year,
+    cc_type,
+    time_created,
+    time_last_used,
+    time_last_modified,
+    times_used";
+
+pub const CREDIT_CARD_COMMON_VALS: &str = "
+    :guid,
+    :cc_name,
+    :cc_number_enc,
+    :cc_number_last_4,
+    :cc_exp_month,
+    :cc_exp_year,
+    :cc_type,
+    :time_created,
+    :time_last_used,
+    :time_last_modified,
+    :times_used";
+
+const CREATE_SHARED_SCHEMA_SQL: &str = include_str!("../../sql/create_shared_schema.sql");
+const CREATE_SHARED_TRIGGERS_SQL: &str = include_str!("../../sql/create_shared_triggers.sql");
+const CREATE_SYNC_TEMP_TABLES_SQL: &str = include_str!("../../sql/create_sync_temp_tables.sql");
+
+pub struct AutofillConnectionInitializer;
+
+impl ConnectionInitializer for AutofillConnectionInitializer {
+    const NAME: &'static str = "autofill db";
+    const END_VERSION: u32 = 2;
+
+    fn prepare(&self, conn: &Connection, _db_empty: bool) -> Result<()> {
+        define_functions(conn)?;
+
+        let initial_pragmas = "
+            -- use in-memory storage
+            PRAGMA temp_store = 2;
+            -- use write-ahead logging
+            PRAGMA journal_mode = WAL;
+            -- autofill does not use foreign keys at present but this is probably a good pragma to set
+            PRAGMA foreign_keys = ON;
+        ";
+        conn.execute_batch(initial_pragmas)?;
+
+        conn.set_prepared_statement_cache_capacity(128);
+        Ok(())
+    }
+
+    fn init(&self, db: &Transaction<'_>) -> Result<()> {
+        Ok(db.execute_batch(CREATE_SHARED_SCHEMA_SQL)?)
+    }
+
+    fn upgrade_from(&self, db: &Transaction<'_>, version: u32) -> Result<()> {
+        match version {
+            // AutofillDB has a slightly strange version history, so we start on v0.  See
+            // upgrade_from_v0() for more details.
+            0 => upgrade_from_v0(db),
+            1 => upgrade_from_v1(db),
+            _ => Err(Error::IncompatibleVersion(version)),
+        }
+    }
+
+    fn finish(&self, db: &Connection) -> Result<()> {
+        Ok(db.execute_batch(CREATE_SHARED_TRIGGERS_SQL)?)
+    }
+}
+
+fn define_functions(c: &Connection) -> Result<()> {
+    c.create_scalar_function(
+        "generate_guid",
+        0,
+        FunctionFlags::SQLITE_UTF8,
+        sql_fns::generate_guid,
+    )?;
+    c.create_scalar_function("now", 0, FunctionFlags::SQLITE_UTF8, sql_fns::now)?;
+
+    Ok(())
+}
+
+fn upgrade_from_v0(db: &Connection) -> Result<()> {
+    // This is a bit painful - there are (probably 3) databases out there
+    // that have a schema of 0.
+    // These databases have a `cc_number` but we need them to have a
+    // `cc_number_enc` and `cc_number_last_4`.
+    // This was so very early in the Fenix nightly cycle, and before any
+    // real UI existed to create cards, so we don't bother trying to
+    // migrate them, we just drop the table and re-create it with the
+    // correct schema.
+    db.execute_batch(
+        "
+        DROP TABLE IF EXISTS credit_cards_data;
+        CREATE TABLE credit_cards_data (
+            guid                TEXT NOT NULL PRIMARY KEY CHECK(length(guid) != 0),
+            cc_name             TEXT NOT NULL,
+            cc_number_enc       TEXT NOT NULL CHECK(length(cc_number_enc) > 20),
+            cc_number_last_4    TEXT NOT NULL CHECK(length(cc_number_last_4) <= 4),
+            cc_exp_month        INTEGER,
+            cc_exp_year         INTEGER,
+            cc_type             TEXT NOT NULL,
+            time_created        INTEGER NOT NULL,
+            time_last_used      INTEGER,
+            time_last_modified  INTEGER NOT NULL,
+            times_used          INTEGER NOT NULL,
+            sync_change_counter INTEGER NOT NULL
+        );
+        ",
+    )?;
+    Ok(())
+}
+
+fn upgrade_from_v1(db: &Connection) -> Result<()> {
+    // Alter cc_number_enc using the 12-step generalized procedure described here:
+    // https://sqlite.org/lang_altertable.html
+    // Note that all our triggers are TEMP triggers so do not exist when
+    // this is called (except possibly by tests which do things like
+    // downgrade the version after they are created etc.)
+    db.execute_batch(
+        "
+        CREATE TABLE new_credit_cards_data (
+            guid                TEXT NOT NULL PRIMARY KEY CHECK(length(guid) != 0),
+            cc_name             TEXT NOT NULL,
+            cc_number_enc       TEXT NOT NULL CHECK(length(cc_number_enc) > 20 OR cc_number_enc == ''),
+            cc_number_last_4    TEXT NOT NULL CHECK(length(cc_number_last_4) <= 4),
+            cc_exp_month        INTEGER,
+            cc_exp_year         INTEGER,
+            cc_type             TEXT NOT NULL,
+            time_created        INTEGER NOT NULL,
+            time_last_used      INTEGER,
+            time_last_modified  INTEGER NOT NULL,
+            times_used          INTEGER NOT NULL,
+            sync_change_counter INTEGER NOT NULL
+        );
+        INSERT INTO new_credit_cards_data(guid, cc_name, cc_number_enc, cc_number_last_4, cc_exp_month,
+        cc_exp_year, cc_type, time_created, time_last_used, time_last_modified, times_used,
+        sync_change_counter)
+        SELECT guid, cc_name, cc_number_enc, cc_number_last_4, cc_exp_month, cc_exp_year, cc_type,
+            time_created, time_last_used, time_last_modified, times_used, sync_change_counter
+        FROM credit_cards_data;
+        DROP TABLE credit_cards_data;
+        ALTER TABLE new_credit_cards_data RENAME to credit_cards_data;
+        ")?;
+    Ok(())
+}
+
+pub fn create_empty_sync_temp_tables(db: &Connection) -> Result<()> {
+    log::debug!("Initializing sync temp tables");
+    db.execute_batch(CREATE_SYNC_TEMP_TABLES_SQL)?;
+    Ok(())
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::db::addresses::get_address;
+    use crate::db::credit_cards::get_credit_card;
+    use crate::db::test::new_mem_db;
+    use sql_support::open_database::test_utils::MigratedDatabaseFile;
+    use sync_guid::Guid;
+    use types::Timestamp;
+
+    const CREATE_V0_DB: &str = include_str!("../../sql/tests/create_v0_db.sql");
+    const CREATE_V1_DB: &str = include_str!("../../sql/tests/create_v1_db.sql");
+
+    #[test]
+    fn test_create_schema_twice() {
+        let db = new_mem_db();
+        db.execute_batch(CREATE_SHARED_SCHEMA_SQL)
+            .expect("should allow running main schema creation twice");
+        // sync tables aren't created by default, so do it twice here.
+        db.execute_batch(CREATE_SYNC_TEMP_TABLES_SQL)
+            .expect("should allow running sync temp tables first time");
+        db.execute_batch(CREATE_SYNC_TEMP_TABLES_SQL)
+            .expect("should allow running sync temp tables second time");
+    }
+
+    #[test]
+    fn test_all_upgrades() {
+        // Let's start with v1, since the v0 upgrade deletes data
+        let db_file = MigratedDatabaseFile::new(AutofillConnectionInitializer, CREATE_V1_DB);
+        db_file.run_all_upgrades();
+        let conn = db_file.open();
+
+        // Test that the data made it through
+        let cc = get_credit_card(&conn, &Guid::new("A")).unwrap();
+        assert_eq!(cc.guid, "A");
+        assert_eq!(cc.cc_name, "Jane Doe");
+        assert_eq!(cc.cc_number_enc, "012345678901234567890");
+        assert_eq!(cc.cc_number_last_4, "1234");
+        assert_eq!(cc.cc_exp_month, 1);
+        assert_eq!(cc.cc_exp_year, 2020);
+        assert_eq!(cc.cc_type, "visa");
+        assert_eq!(cc.metadata.time_created, Timestamp(0));
+        assert_eq!(cc.metadata.time_last_used, Timestamp(1));
+        assert_eq!(cc.metadata.time_last_modified, Timestamp(2));
+        assert_eq!(cc.metadata.times_used, 3);
+        assert_eq!(cc.metadata.sync_change_counter, 0);
+
+        let address = get_address(&conn, &Guid::new("A")).unwrap();
+        assert_eq!(address.guid, "A");
+        assert_eq!(address.given_name, "Jane");
+        assert_eq!(address.family_name, "Doe");
+        assert_eq!(address.additional_name, "JaneDoe2");
+        assert_eq!(address.organization, "Mozilla");
+        assert_eq!(address.street_address, "123 Maple lane");
+        assert_eq!(address.address_level3, "Shelbyville");
+        assert_eq!(address.address_level2, "Springfield");
+        assert_eq!(address.address_level1, "MA");
+        assert_eq!(address.postal_code, "12345");
+        assert_eq!(address.country, "US");
+        assert_eq!(address.tel, "01-234-567-8000");
+        assert_eq!(address.email, "jane@hotmail.com");
+        assert_eq!(address.metadata.time_created, Timestamp(0));
+        assert_eq!(address.metadata.time_last_used, Timestamp(1));
+        assert_eq!(address.metadata.time_last_modified, Timestamp(2));
+        assert_eq!(address.metadata.times_used, 3);
+        assert_eq!(address.metadata.sync_change_counter, 0);
+    }
+
+    #[test]
+    fn test_upgrade_version_0() {
+        let db_file = MigratedDatabaseFile::new(AutofillConnectionInitializer, CREATE_V0_DB);
+        // Just to test what we think we are testing, select a field that
+        // doesn't exist now but will after we recreate the table.
+        let select_cc_number_enc = "SELECT cc_number_enc from credit_cards_data";
+        db_file
+            .open()
+            .execute_batch(select_cc_number_enc)
+            .expect_err("select should fail due to bad field name");
+
+        db_file.upgrade_to(1);
+
+        db_file
+            .open()
+            .execute_batch(select_cc_number_enc)
+            .expect("select should now work");
+    }
+
+    #[test]
+    fn test_upgrade_version_1() {
+        let db_file = MigratedDatabaseFile::new(AutofillConnectionInitializer, CREATE_V1_DB);
+
+        db_file.upgrade_to(2);
+        let db = db_file.open();
+
+        // Test the upgraded check constraint
+        db.execute("UPDATE credit_cards_data SET cc_number_enc=''", [])
+            .expect("blank cc_number_enc should be valid");
+        db.execute("UPDATE credit_cards_data SET cc_number_enc='x'", [])
+            .expect_err("cc_number_enc should be invalid");
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/autofill/db/store.rs.html b/book/rust-docs/src/autofill/db/store.rs.html new file mode 100644 index 0000000000..1f1de37c54 --- /dev/null +++ b/book/rust-docs/src/autofill/db/store.rs.html @@ -0,0 +1,549 @@ +store.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::db::models::address::{Address, UpdatableAddressFields};
+use crate::db::models::credit_card::{CreditCard, UpdatableCreditCardFields};
+use crate::db::{addresses, credit_cards, AutofillDb};
+use crate::error::*;
+use error_support::handle_error;
+use rusqlite::{
+    types::{FromSql, ToSql},
+    Connection,
+};
+use sql_support::{self, ConnExt};
+use std::path::Path;
+use std::sync::{Arc, Mutex, Weak};
+use sync15::engine::{SyncEngine, SyncEngineId};
+use sync_guid::Guid;
+
+// Our "sync manager" will use whatever is stashed here.
+lazy_static::lazy_static! {
+    // Mutex: just taken long enough to update the contents - needed to wrap
+    //        the Weak as it isn't `Sync`
+    // [Arc/Weak]<Store>: What the sync manager actually needs.
+    static ref STORE_FOR_MANAGER: Mutex<Weak<Store>> = Mutex::new(Weak::new());
+}
+
+/// Called by the sync manager to get a sync engine via the store previously
+/// registered with the sync manager.
+pub fn get_registered_sync_engine(engine_id: &SyncEngineId) -> Option<Box<dyn SyncEngine>> {
+    let weak = STORE_FOR_MANAGER.lock().unwrap();
+    match weak.upgrade() {
+        None => None,
+        Some(store) => match engine_id {
+            SyncEngineId::Addresses => Some(Box::new(crate::sync::address::create_engine(store))),
+            SyncEngineId::CreditCards => {
+                Some(Box::new(crate::sync::credit_card::create_engine(store)))
+            }
+            // panicing here seems reasonable - it's a static error if this
+            // it hit, not something that runtime conditions can influence.
+            _ => unreachable!("can't provide unknown engine: {}", engine_id),
+        },
+    }
+}
+
+// This is the type that uniffi exposes.
+pub struct Store {
+    pub(crate) db: Mutex<AutofillDb>,
+}
+
+impl Store {
+    #[handle_error(Error)]
+    pub fn new(db_path: impl AsRef<Path>) -> ApiResult<Self> {
+        Ok(Self {
+            db: Mutex::new(AutofillDb::new(db_path)?),
+        })
+    }
+
+    /// Creates a store backed by an in-memory database with its own memory API (required for unit tests).
+    #[cfg(test)]
+    pub fn new_memory() -> Self {
+        Self {
+            db: Mutex::new(crate::db::test::new_mem_db()),
+        }
+    }
+
+    /// Creates a store backed by an in-memory database that shares its memory API (required for autofill sync tests).
+    #[handle_error(Error)]
+    pub fn new_shared_memory(db_name: &str) -> ApiResult<Self> {
+        Ok(Self {
+            db: Mutex::new(AutofillDb::new_memory(db_name)?),
+        })
+    }
+
+    #[handle_error(Error)]
+    pub fn add_credit_card(&self, fields: UpdatableCreditCardFields) -> ApiResult<CreditCard> {
+        let credit_card = credit_cards::add_credit_card(&self.db.lock().unwrap().writer, fields)?;
+        Ok(credit_card.into())
+    }
+
+    #[handle_error(Error)]
+    pub fn get_credit_card(&self, guid: String) -> ApiResult<CreditCard> {
+        let credit_card =
+            credit_cards::get_credit_card(&self.db.lock().unwrap().writer, &Guid::new(&guid))?;
+        Ok(credit_card.into())
+    }
+
+    #[handle_error(Error)]
+    pub fn get_all_credit_cards(&self) -> ApiResult<Vec<CreditCard>> {
+        let credit_cards = credit_cards::get_all_credit_cards(&self.db.lock().unwrap().writer)?
+            .into_iter()
+            .map(|x| x.into())
+            .collect();
+        Ok(credit_cards)
+    }
+
+    #[handle_error(Error)]
+    pub fn update_credit_card(
+        &self,
+        guid: String,
+        credit_card: UpdatableCreditCardFields,
+    ) -> ApiResult<()> {
+        credit_cards::update_credit_card(
+            &self.db.lock().unwrap().writer,
+            &Guid::new(&guid),
+            &credit_card,
+        )
+    }
+
+    #[handle_error(Error)]
+    pub fn delete_credit_card(&self, guid: String) -> ApiResult<bool> {
+        credit_cards::delete_credit_card(&self.db.lock().unwrap().writer, &Guid::new(&guid))
+    }
+
+    #[handle_error(Error)]
+    pub fn touch_credit_card(&self, guid: String) -> ApiResult<()> {
+        credit_cards::touch(&self.db.lock().unwrap().writer, &Guid::new(&guid))
+    }
+
+    #[handle_error(Error)]
+    pub fn add_address(&self, new_address: UpdatableAddressFields) -> ApiResult<Address> {
+        Ok(addresses::add_address(&self.db.lock().unwrap().writer, new_address)?.into())
+    }
+
+    #[handle_error(Error)]
+    pub fn get_address(&self, guid: String) -> ApiResult<Address> {
+        Ok(addresses::get_address(&self.db.lock().unwrap().writer, &Guid::new(&guid))?.into())
+    }
+
+    #[handle_error(Error)]
+    pub fn get_all_addresses(&self) -> ApiResult<Vec<Address>> {
+        let addresses = addresses::get_all_addresses(&self.db.lock().unwrap().writer)?
+            .into_iter()
+            .map(|x| x.into())
+            .collect();
+        Ok(addresses)
+    }
+
+    #[handle_error(Error)]
+    pub fn update_address(&self, guid: String, address: UpdatableAddressFields) -> ApiResult<()> {
+        addresses::update_address(&self.db.lock().unwrap().writer, &Guid::new(&guid), &address)
+    }
+
+    #[handle_error(Error)]
+    pub fn delete_address(&self, guid: String) -> ApiResult<bool> {
+        addresses::delete_address(&self.db.lock().unwrap().writer, &Guid::new(&guid))
+    }
+
+    #[handle_error(Error)]
+    pub fn touch_address(&self, guid: String) -> ApiResult<()> {
+        addresses::touch(&self.db.lock().unwrap().writer, &Guid::new(&guid))
+    }
+
+    #[handle_error(Error)]
+    pub fn scrub_encrypted_data(self: Arc<Self>) -> ApiResult<()> {
+        // scrub the data on disk
+        // Currently only credit cards have encrypted data
+        credit_cards::scrub_encrypted_credit_card_data(&self.db.lock().unwrap().writer)?;
+        // Force the sync engine to refetch data (only need to do this for the credit cards, since the
+        // addresses engine doesn't store encrypted data).
+        crate::sync::credit_card::create_engine(self).reset_local_sync_data()?;
+        Ok(())
+    }
+
+    // This allows the embedding app to say "make this instance available to
+    // the sync manager". The implementation is more like "offer to sync mgr"
+    // (thereby avoiding us needing to link with the sync manager) but
+    // `register_with_sync_manager()` is logically what's happening so that's
+    // the name it gets.
+    pub fn register_with_sync_manager(self: Arc<Self>) {
+        let mut state = STORE_FOR_MANAGER.lock().unwrap();
+        *state = Arc::downgrade(&self);
+    }
+
+    // These 2 are a little odd - they aren't exposed by uniffi - currently the
+    // only consumer of this is our "example" (and hence why they
+    // are `pub` and not `pub(crate)`).
+    // We could probably make the example work with the sync manager - but then
+    // our example would link with places and logins etc, and it's not a big
+    // deal really.
+    pub fn create_credit_cards_sync_engine(self: Arc<Self>) -> Box<dyn SyncEngine> {
+        Box::new(crate::sync::credit_card::create_engine(self))
+    }
+
+    pub fn create_addresses_sync_engine(self: Arc<Self>) -> Box<dyn SyncEngine> {
+        Box::new(crate::sync::address::create_engine(self))
+    }
+}
+
+pub(crate) fn put_meta(conn: &Connection, key: &str, value: &dyn ToSql) -> Result<()> {
+    conn.execute_cached(
+        "REPLACE INTO moz_meta (key, value) VALUES (:key, :value)",
+        &[(":key", &key as &dyn ToSql), (":value", value)],
+    )?;
+    Ok(())
+}
+
+pub(crate) fn get_meta<T: FromSql>(conn: &Connection, key: &str) -> Result<Option<T>> {
+    let res = conn.try_query_one(
+        "SELECT value FROM moz_meta WHERE key = :key",
+        &[(":key", &key)],
+        true,
+    )?;
+    Ok(res)
+}
+
+pub(crate) fn delete_meta(conn: &Connection, key: &str) -> Result<()> {
+    conn.execute_cached("DELETE FROM moz_meta WHERE key = :key", &[(":key", &key)])?;
+    Ok(())
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::db::test::new_mem_db;
+
+    #[test]
+    fn test_autofill_meta() -> Result<()> {
+        let db = new_mem_db();
+        let test_key = "TEST KEY A";
+        let test_value = "TEST VALUE A";
+        let test_key2 = "TEST KEY B";
+        let test_value2 = "TEST VALUE B";
+
+        put_meta(&db, test_key, &test_value)?;
+        put_meta(&db, test_key2, &test_value2)?;
+
+        let retrieved_value: String = get_meta(&db, test_key)?.expect("test value");
+        let retrieved_value2: String = get_meta(&db, test_key2)?.expect("test value 2");
+
+        assert_eq!(retrieved_value, test_value);
+        assert_eq!(retrieved_value2, test_value2);
+
+        // check that the value of an existing key can be updated
+        let test_value3 = "TEST VALUE C";
+        put_meta(&db, test_key, &test_value3)?;
+
+        let retrieved_value3: String = get_meta(&db, test_key)?.expect("test value 3");
+
+        assert_eq!(retrieved_value3, test_value3);
+
+        // check that a deleted key is not retrieved
+        delete_meta(&db, test_key)?;
+        let retrieved_value4: Option<String> = get_meta(&db, test_key)?;
+        assert!(retrieved_value4.is_none());
+
+        db.writer.execute("DELETE FROM moz_meta", [])?;
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_sync_manager_registration() {
+        let store = Arc::new(Store::new_shared_memory("sync-mgr-test").unwrap());
+        assert_eq!(Arc::strong_count(&store), 1);
+        assert_eq!(Arc::weak_count(&store), 0);
+        Arc::clone(&store).register_with_sync_manager();
+        assert_eq!(Arc::strong_count(&store), 1);
+        assert_eq!(Arc::weak_count(&store), 1);
+        let registered = STORE_FOR_MANAGER
+            .lock()
+            .unwrap()
+            .upgrade()
+            .expect("should upgrade");
+        assert!(Arc::ptr_eq(&store, &registered));
+        drop(registered);
+        // should be no new references
+        assert_eq!(Arc::strong_count(&store), 1);
+        assert_eq!(Arc::weak_count(&store), 1);
+        // dropping the registered object should drop the registration.
+        drop(store);
+        assert!(STORE_FOR_MANAGER.lock().unwrap().upgrade().is_none());
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/autofill/encryption.rs.html b/book/rust-docs/src/autofill/encryption.rs.html new file mode 100644 index 0000000000..b07b774991 --- /dev/null +++ b/book/rust-docs/src/autofill/encryption.rs.html @@ -0,0 +1,179 @@ +encryption.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+// This is the *local* encryption support - it has nothing to do with the
+// encryption used by sync.
+
+// For context, what "local encryption" means in this context is:
+// * We use regular sqlite, but want to ensure the credit-card numbers are
+//   encrypted in the DB - so we store the number encrypted, and the key
+//   is managed by the app.
+// * The credit-card API always just accepts and returns the encrypted string,
+//   so we also expose encryption and decryption public functions that take
+//   the key and text. The core storage API never knows the unencrypted number.
+//
+// This makes life tricky for Sync - sync has its own encryption and its own
+// management of sync keys. The entire records are encrypted on the server -
+// so the record on the server has the plain-text number (which is then
+// encrypted as part of the entire record), so:
+// * When transforming a record from the DB into a Sync record, we need to
+//   *decrypt* the field.
+// * When transforming a record from Sync into a DB record, we need to *encrypt*
+//   the field.
+//
+// So Sync needs to know the key etc, and that needs to get passed down
+// multiple layers, from the app saying "sync now" all the way down to the
+// low level sync code.
+// To make life a little easier, we do that via a struct.
+
+use crate::error::*;
+use error_support::handle_error;
+
+pub type EncryptorDecryptor = jwcrypto::EncryptorDecryptor<Error>;
+
+// public functions we expose over the FFI (which is why they take `String`
+// rather than the `&str` you'd otherwise expect)
+#[handle_error(Error)]
+pub fn encrypt_string(key: String, cleartext: String) -> ApiResult<String> {
+    // It would be nice to have more detailed error messages, but that would require the consumer
+    // to pass them in.  Let's not change the API yet.
+    EncryptorDecryptor::new(&key)?.encrypt(&cleartext, "single string field")
+}
+
+#[handle_error(Error)]
+pub fn decrypt_string(key: String, ciphertext: String) -> ApiResult<String> {
+    // It would be nice to have more detailed error messages, but that would require the consumer
+    // to pass them in.  Let's not change the API yet.
+    EncryptorDecryptor::new(&key)?.decrypt(&ciphertext, "single string field")
+}
+
+#[handle_error(Error)]
+pub fn create_autofill_key() -> ApiResult<String> {
+    EncryptorDecryptor::create_key()
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn test_encrypt() {
+        let ed = EncryptorDecryptor::new(&create_autofill_key().unwrap()).unwrap();
+        let cleartext = "secret";
+        let ciphertext = ed.encrypt(cleartext, "secret").unwrap();
+        assert_eq!(ed.decrypt(&ciphertext, "secret").unwrap(), cleartext);
+        let ed2 = EncryptorDecryptor::new(&create_autofill_key().unwrap()).unwrap();
+        assert!(matches!(
+            ed2.decrypt(&ciphertext, "secret"),
+            Err(Error::CryptoError(_))
+        ));
+    }
+
+    #[test]
+    fn test_decryption_errors() {
+        let ed = EncryptorDecryptor::new(&create_autofill_key().unwrap()).unwrap();
+        assert!(matches!(
+            ed.decrypt("invalid-ciphertext", "invalid").unwrap_err(),
+            Error::CryptoError(_)
+        ));
+        assert!(matches!(
+            ed.decrypt("", "empty").unwrap_err(),
+            Error::CryptoError(jwcrypto::EncryptorDecryptorError {
+                from: jwcrypto::JwCryptoError::EmptyCyphertext,
+                ..
+            })
+        ));
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/autofill/error.rs.html b/book/rust-docs/src/autofill/error.rs.html new file mode 100644 index 0000000000..f417797028 --- /dev/null +++ b/book/rust-docs/src/autofill/error.rs.html @@ -0,0 +1,263 @@ +error.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/.
+*/
+
+use error_support::{ErrorHandling, GetErrorHandling};
+use interrupt_support::Interrupted;
+
+/// Result enum for the public API
+pub type ApiResult<T> = std::result::Result<T, AutofillApiError>;
+
+/// Result enum for internal functions
+pub type Result<T> = std::result::Result<T, Error>;
+
+// Errors we return via the public interface.
+#[derive(Debug, thiserror::Error)]
+pub enum AutofillApiError {
+    #[error("Error executing SQL: {reason}")]
+    SqlError { reason: String },
+
+    #[error("Operation interrupted")]
+    InterruptedError,
+
+    #[error("Crypto Error: {reason}")]
+    CryptoError { reason: String },
+
+    #[error("No record with guid exists: {guid}")]
+    NoSuchRecord { guid: String },
+
+    #[error("Unexpected Error: {reason}")]
+    UnexpectedAutofillApiError { reason: String },
+}
+
+#[derive(Debug, thiserror::Error)]
+pub enum Error {
+    #[error("Error opening database: {0}")]
+    OpenDatabaseError(#[from] sql_support::open_database::Error),
+
+    #[error("Error executing SQL: {0}")]
+    SqlError(#[from] rusqlite::Error),
+
+    #[error("IO error: {0}")]
+    IoError(#[from] std::io::Error),
+
+    #[error("Operation interrupted")]
+    InterruptedError(#[from] Interrupted),
+
+    // This will happen if you provide something absurd like
+    // "/" or "" as your database path. For more subtley broken paths,
+    // we'll likely return an IoError.
+    #[error("Illegal database path: {0:?}")]
+    IllegalDatabasePath(std::path::PathBuf),
+
+    #[error("JSON Error: {0}")]
+    JsonError(#[from] serde_json::Error),
+
+    #[error("Invalid sync payload: {0}")]
+    InvalidSyncPayload(String),
+
+    #[error("Crypto Error: {0}")]
+    CryptoError(#[from] jwcrypto::EncryptorDecryptorError),
+
+    #[error("Missing local encryption key")]
+    MissingEncryptionKey,
+
+    #[error("No record with guid exists: {0}")]
+    NoSuchRecord(String),
+}
+
+// Define how our internal errors are handled and converted to external errors
+// See `support/error/README.md` for how this works, especially the warning about PII.
+impl GetErrorHandling for Error {
+    type ExternalError = AutofillApiError;
+
+    fn get_error_handling(&self) -> ErrorHandling<Self::ExternalError> {
+        match self {
+            Self::OpenDatabaseError(e) => ErrorHandling::convert(AutofillApiError::SqlError {
+                reason: e.to_string(),
+            })
+            .report_error("autofill-open-database-error"),
+
+            Self::SqlError(e) => ErrorHandling::convert(AutofillApiError::SqlError {
+                reason: e.to_string(),
+            })
+            .report_error("autofill-sql-error"),
+
+            Self::IoError(e) => {
+                ErrorHandling::convert(AutofillApiError::UnexpectedAutofillApiError {
+                    reason: e.to_string(),
+                })
+                .report_error("autofill-io-error")
+            }
+
+            Self::InterruptedError(_) => ErrorHandling::convert(AutofillApiError::InterruptedError),
+
+            Self::IllegalDatabasePath(path) => ErrorHandling::convert(AutofillApiError::SqlError {
+                reason: format!("Path not found: {}", path.to_string_lossy()),
+            })
+            .report_error("autofill-illegal-database-path"),
+
+            Self::JsonError(e) => {
+                ErrorHandling::convert(AutofillApiError::UnexpectedAutofillApiError {
+                    reason: e.to_string(),
+                })
+                .report_error("autofill-json-error")
+            }
+
+            Self::InvalidSyncPayload(reason) => {
+                ErrorHandling::convert(AutofillApiError::UnexpectedAutofillApiError {
+                    reason: reason.clone(),
+                })
+                .report_error("autofill-invalid-sync-payload")
+            }
+
+            Self::CryptoError(e) => ErrorHandling::convert(AutofillApiError::CryptoError {
+                reason: e.to_string(),
+            })
+            .report_error("autofill-crypto-error"),
+
+            Self::MissingEncryptionKey => ErrorHandling::convert(AutofillApiError::CryptoError {
+                reason: "Missing encryption key".to_string(),
+            })
+            .report_error("autofill-missing-encryption-key"),
+
+            Self::NoSuchRecord(guid) => {
+                ErrorHandling::convert(AutofillApiError::NoSuchRecord { guid: guid.clone() })
+                    .log_warning()
+            }
+        }
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/autofill/lib.rs.html b/book/rust-docs/src/autofill/lib.rs.html new file mode 100644 index 0000000000..d0baf033a0 --- /dev/null +++ b/book/rust-docs/src/autofill/lib.rs.html @@ -0,0 +1,49 @@ +lib.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#![allow(unknown_lints)]
+#![warn(rust_2018_idioms)]
+
+pub mod db;
+pub mod encryption;
+pub mod error;
+pub mod sync;
+
+// Re-export stuff the sync manager needs.
+pub use crate::db::store::get_registered_sync_engine;
+
+// Expose stuff needed by the uniffi generated code.
+use crate::db::models::address::*;
+use crate::db::models::credit_card::*;
+use crate::db::store::Store;
+use crate::encryption::{create_autofill_key, decrypt_string, encrypt_string};
+pub use error::{ApiResult, AutofillApiError, Error, Result};
+
+uniffi::include_scaffolding!("autofill");
+
\ No newline at end of file diff --git a/book/rust-docs/src/autofill/sync/address/incoming.rs.html b/book/rust-docs/src/autofill/sync/address/incoming.rs.html new file mode 100644 index 0000000000..31f0611e33 --- /dev/null +++ b/book/rust-docs/src/autofill/sync/address/incoming.rs.html @@ -0,0 +1,991 @@ +incoming.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/.
+*/
+
+use super::AddressPayload;
+use crate::db::addresses::{add_internal_address, update_internal_address};
+use crate::db::models::address::InternalAddress;
+use crate::db::schema::ADDRESS_COMMON_COLS;
+use crate::error::*;
+use crate::sync::common::*;
+use crate::sync::{
+    IncomingBso, IncomingContent, IncomingEnvelope, IncomingKind, IncomingState, LocalRecordInfo,
+    ProcessIncomingRecordImpl, ServerTimestamp, SyncRecord,
+};
+use interrupt_support::Interruptee;
+use rusqlite::{named_params, Transaction};
+use sql_support::ConnExt;
+use sync_guid::Guid as SyncGuid;
+
+// Takes a raw payload, as stored in our database, and returns an InternalAddress
+// or a tombstone. Addresses store the raw payload as cleartext json.
+fn raw_payload_to_incoming(id: SyncGuid, raw: String) -> Result<IncomingContent<InternalAddress>> {
+    // Make an IncomingBso from the payload.
+    let bso = IncomingBso {
+        envelope: IncomingEnvelope {
+            id,
+            modified: ServerTimestamp::default(),
+            sortindex: None,
+            ttl: None,
+        },
+        payload: raw,
+    };
+    // For hysterical raisins, we use an IncomingContent<AddressPayload> to convert
+    // to an IncomingContent<InternalAddress>
+    let payload_content = bso.into_content::<AddressPayload>();
+    Ok(match payload_content.kind {
+        IncomingKind::Content(content) => IncomingContent {
+            envelope: payload_content.envelope,
+            kind: IncomingKind::Content(InternalAddress::from_payload(content)?),
+        },
+        IncomingKind::Tombstone => IncomingContent {
+            envelope: payload_content.envelope,
+            kind: IncomingKind::Tombstone,
+        },
+        IncomingKind::Malformed => IncomingContent {
+            envelope: payload_content.envelope,
+            kind: IncomingKind::Malformed,
+        },
+    })
+}
+
+pub(super) struct IncomingAddressesImpl {}
+
+impl ProcessIncomingRecordImpl for IncomingAddressesImpl {
+    type Record = InternalAddress;
+
+    /// The first step in the "apply incoming" process - stage the records
+    fn stage_incoming(
+        &self,
+        tx: &Transaction<'_>,
+        incoming: Vec<IncomingBso>,
+        signal: &dyn Interruptee,
+    ) -> Result<()> {
+        let to_stage = incoming
+            .into_iter()
+            // We persist the entire payload as cleartext - which it already is!
+            .map(|bso| (bso.envelope.id, bso.payload, bso.envelope.modified))
+            .collect();
+        common_stage_incoming_records(tx, "addresses_sync_staging", to_stage, signal)
+    }
+
+    fn finish_incoming(&self, tx: &Transaction<'_>) -> Result<()> {
+        common_mirror_staged_records(tx, "addresses_sync_staging", "addresses_mirror")
+    }
+
+    /// The second step in the "apply incoming" process for syncing autofill address records.
+    /// Incoming items are retrieved from the temp tables, deserialized, and
+    /// assigned `IncomingState` values.
+    fn fetch_incoming_states(
+        &self,
+        tx: &Transaction<'_>,
+    ) -> Result<Vec<IncomingState<Self::Record>>> {
+        let sql = "
+        SELECT
+            s.guid as guid,
+            l.guid as l_guid,
+            t.guid as t_guid,
+            s.payload as s_payload,
+            m.payload as m_payload,
+            l.given_name,
+            l.additional_name,
+            l.family_name,
+            l.organization,
+            l.street_address,
+            l.address_level3,
+            l.address_level2,
+            l.address_level1,
+            l.postal_code,
+            l.country,
+            l.tel,
+            l.email,
+            l.time_created,
+            l.time_last_used,
+            l.time_last_modified,
+            l.times_used,
+            l.sync_change_counter
+        FROM temp.addresses_sync_staging s
+        LEFT JOIN addresses_mirror m ON s.guid = m.guid
+        LEFT JOIN addresses_data l ON s.guid = l.guid
+        LEFT JOIN addresses_tombstones t ON s.guid = t.guid";
+
+        tx.query_rows_and_then(sql, [], |row| -> Result<IncomingState<Self::Record>> {
+            // the 'guid' and 's_payload' rows must be non-null.
+            let guid: SyncGuid = row.get("guid")?;
+            // turn it into a sync15::Payload
+            let incoming = raw_payload_to_incoming(guid.clone(), row.get("s_payload")?)?;
+            Ok(IncomingState {
+                incoming,
+                local: match row.get_unwrap::<_, Option<String>>("l_guid") {
+                    Some(l_guid) => {
+                        assert_eq!(l_guid, guid);
+                        // local record exists, check the state.
+                        let record = InternalAddress::from_row(row)?;
+                        let has_changes = record.metadata().sync_change_counter != 0;
+                        if has_changes {
+                            LocalRecordInfo::Modified { record }
+                        } else {
+                            LocalRecordInfo::Unmodified { record }
+                        }
+                    }
+                    None => {
+                        // no local record - maybe a tombstone?
+                        match row.get::<_, Option<String>>("t_guid")? {
+                            Some(t_guid) => {
+                                assert_eq!(guid, t_guid);
+                                LocalRecordInfo::Tombstone { guid: guid.clone() }
+                            }
+                            None => LocalRecordInfo::Missing,
+                        }
+                    }
+                },
+                mirror: {
+                    match row.get::<_, Option<String>>("m_payload")? {
+                        Some(m_payload) => {
+                            // a tombstone in the mirror can be treated as though it's missing.
+                            raw_payload_to_incoming(guid, m_payload)?.content()
+                        }
+                        None => None,
+                    }
+                },
+            })
+        })
+    }
+
+    /// Returns a local record that has the same values as the given incoming record (with the exception
+    /// of the `guid` values which should differ) that will be used as a local duplicate record for
+    /// syncing.
+    fn get_local_dupe(
+        &self,
+        tx: &Transaction<'_>,
+        incoming: &Self::Record,
+    ) -> Result<Option<Self::Record>> {
+        let sql = format!("
+            SELECT
+                {common_cols},
+                sync_change_counter
+            FROM addresses_data
+            WHERE
+                -- `guid <> :guid` is a pre-condition for this being called, but...
+                guid <> :guid
+                -- only non-synced records are candidates, which means can't already be in the mirror.
+                AND guid NOT IN (
+                    SELECT guid
+                    FROM addresses_mirror
+                )
+                -- and sql can check the field values.
+                AND given_name == :given_name
+                AND additional_name == :additional_name
+                AND family_name == :family_name
+                AND organization == :organization
+                AND street_address == :street_address
+                AND address_level3 == :address_level3
+                AND address_level2 == :address_level2
+                AND address_level1 == :address_level1
+                AND postal_code == :postal_code
+                AND country == :country
+                AND tel == :tel
+                AND email == :email", common_cols = ADDRESS_COMMON_COLS);
+
+        let params = named_params! {
+            ":guid": incoming.guid,
+            ":given_name": incoming.given_name,
+            ":additional_name": incoming.additional_name,
+            ":family_name": incoming.family_name,
+            ":organization": incoming.organization,
+            ":street_address": incoming.street_address,
+            ":address_level3": incoming.address_level3,
+            ":address_level2": incoming.address_level2,
+            ":address_level1": incoming.address_level1,
+            ":postal_code": incoming.postal_code,
+            ":country": incoming.country,
+            ":tel": incoming.tel,
+            ":email": incoming.email,
+        };
+
+        let result = tx.query_row(&sql, params, |row| {
+            Ok(Self::Record::from_row(row).expect("wtf? '?' doesn't work :("))
+        });
+
+        match result {
+            Ok(r) => Ok(Some(r)),
+            Err(e) => match e {
+                rusqlite::Error::QueryReturnedNoRows => Ok(None),
+                _ => Err(Error::SqlError(e)),
+            },
+        }
+    }
+
+    fn update_local_record(
+        &self,
+        tx: &Transaction<'_>,
+        new_record: Self::Record,
+        flag_as_changed: bool,
+    ) -> Result<()> {
+        update_internal_address(tx, &new_record, flag_as_changed)?;
+        Ok(())
+    }
+
+    fn insert_local_record(&self, tx: &Transaction<'_>, new_record: Self::Record) -> Result<()> {
+        add_internal_address(tx, &new_record)?;
+        Ok(())
+    }
+
+    /// Changes the guid of the local record for the given `old_guid` to the given `new_guid` used
+    /// for the `HasLocalDupe` incoming state, and mark the item as dirty.
+    /// We also update the mirror record if it exists in forking scenarios
+    fn change_record_guid(
+        &self,
+        tx: &Transaction<'_>,
+        old_guid: &SyncGuid,
+        new_guid: &SyncGuid,
+    ) -> Result<()> {
+        common_change_guid(tx, "addresses_data", "addresses_mirror", old_guid, new_guid)
+    }
+
+    fn remove_record(&self, tx: &Transaction<'_>, guid: &SyncGuid) -> Result<()> {
+        common_remove_record(tx, "addresses_data", guid)
+    }
+
+    fn remove_tombstone(&self, tx: &Transaction<'_>, guid: &SyncGuid) -> Result<()> {
+        common_remove_record(tx, "addresses_tombstones", guid)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::super::super::test::new_syncable_mem_db;
+    use super::*;
+    use crate::db::addresses::get_address;
+    use crate::sync::common::tests::*;
+
+    use interrupt_support::NeverInterrupts;
+    use serde_json::{json, Map, Value};
+    use sql_support::ConnExt;
+
+    impl InternalAddress {
+        fn into_test_incoming_bso(self) -> IncomingBso {
+            IncomingBso::from_test_content(self.into_payload().expect("is json"))
+        }
+    }
+
+    lazy_static::lazy_static! {
+        static ref TEST_JSON_RECORDS: Map<String, Value> = {
+            // NOTE: the JSON here is the same as stored on the sync server -
+            // the superfluous `entry` is unfortunate but from desktop.
+            // JSON from the server is kebab-style, EXCEPT the times{X} fields
+            // see PayloadEntry struct
+            let val = json! {{
+                "A" : {
+                    "id": expand_test_guid('A'),
+                    "entry": {
+                        "given-name": "john",
+                        "family-name": "doe",
+                        "street-address": "1300 Broadway",
+                        "address-level2": "New York, NY",
+                        "country": "United States",
+                        "version": 1,
+                    }
+                },
+                "C" : {
+                    "id": expand_test_guid('C'),
+                    "entry": {
+                        "given-name": "jane",
+                        "family-name": "doe",
+                        "street-address": "3050 South La Brea Ave",
+                        "address-level2": "Los Angeles, CA",
+                        "country": "United States",
+                        "timeCreated": 0,
+                        "timeLastUsed": 0,
+                        "timeLastModified": 0,
+                        "timesUsed": 0,
+                        "version": 1,
+                    }
+                },
+                "D" : {
+                    "id": expand_test_guid('D'),
+                    "entry": {
+                        "given-name": "test1",
+                        "family-name": "test2",
+                        "street-address": "85 Pike St",
+                        "address-level2": "Seattle, WA",
+                        "country": "United States",
+                        "foo": "bar",
+                        "baz": "qux",
+                        "version": 1,
+                    }
+                }
+            }};
+            val.as_object().expect("literal is an object").clone()
+        };
+    }
+
+    fn test_json_record(guid_prefix: char) -> Value {
+        TEST_JSON_RECORDS
+            .get(&guid_prefix.to_string())
+            .expect("should exist")
+            .clone()
+    }
+
+    fn test_record(guid_prefix: char) -> InternalAddress {
+        let json = test_json_record(guid_prefix);
+        let address_payload = serde_json::from_value(json).unwrap();
+        InternalAddress::from_payload(address_payload).expect("should be valid")
+    }
+
+    #[test]
+    fn test_stage_incoming() -> Result<()> {
+        let _ = env_logger::try_init();
+        let mut db = new_syncable_mem_db();
+        struct TestCase {
+            incoming_records: Vec<Value>,
+            mirror_records: Vec<Value>,
+            expected_record_count: usize,
+            expected_tombstone_count: usize,
+        }
+
+        let test_cases = vec![
+            TestCase {
+                incoming_records: vec![test_json_record('A')],
+                mirror_records: vec![],
+                expected_record_count: 1,
+                expected_tombstone_count: 0,
+            },
+            TestCase {
+                incoming_records: vec![test_json_tombstone('A')],
+                mirror_records: vec![],
+                expected_record_count: 0,
+                expected_tombstone_count: 1,
+            },
+            TestCase {
+                incoming_records: vec![
+                    test_json_record('A'),
+                    test_json_record('C'),
+                    test_json_tombstone('B'),
+                ],
+                mirror_records: vec![],
+                expected_record_count: 2,
+                expected_tombstone_count: 1,
+            },
+            // incoming tombstone with existing tombstone in the mirror
+            TestCase {
+                incoming_records: vec![test_json_tombstone('B')],
+                mirror_records: vec![test_json_tombstone('B')],
+                expected_record_count: 0,
+                expected_tombstone_count: 1,
+            },
+        ];
+
+        for tc in test_cases {
+            log::info!("starting new testcase");
+            let tx = db.transaction()?;
+
+            // Add required items to the mirrors.
+            let mirror_sql = "INSERT OR REPLACE INTO addresses_mirror (guid, payload)
+                              VALUES (:guid, :payload)";
+            for payload in tc.mirror_records {
+                tx.execute(
+                    mirror_sql,
+                    rusqlite::named_params! {
+                        ":guid": payload["id"].as_str().unwrap(),
+                        ":payload": payload.to_string(),
+                    },
+                )
+                .expect("should insert mirror record");
+            }
+
+            let ri = IncomingAddressesImpl {};
+            ri.stage_incoming(
+                &tx,
+                array_to_incoming(tc.incoming_records),
+                &NeverInterrupts,
+            )?;
+
+            let records = tx.conn().query_rows_and_then(
+                "SELECT * FROM temp.addresses_sync_staging;",
+                [],
+                |row| -> Result<IncomingContent<InternalAddress>> {
+                    let guid: SyncGuid = row.get_unwrap("guid");
+                    let payload: String = row.get_unwrap("payload");
+                    raw_payload_to_incoming(guid, payload)
+                },
+            )?;
+
+            let record_count = records
+                .iter()
+                .filter(|p| !matches!(p.kind, IncomingKind::Tombstone))
+                .count();
+            let tombstone_count = records.len() - record_count;
+
+            assert_eq!(record_count, tc.expected_record_count);
+            assert_eq!(tombstone_count, tc.expected_tombstone_count);
+
+            ri.fetch_incoming_states(&tx)?;
+
+            tx.execute("DELETE FROM temp.addresses_sync_staging;", [])?;
+        }
+        Ok(())
+    }
+
+    #[test]
+    fn test_change_record_guid() -> Result<()> {
+        let mut db = new_syncable_mem_db();
+        let tx = db.transaction()?;
+        let ri = IncomingAddressesImpl {};
+
+        ri.insert_local_record(&tx, test_record('C'))?;
+
+        ri.change_record_guid(
+            &tx,
+            &SyncGuid::new(&expand_test_guid('C')),
+            &SyncGuid::new(&expand_test_guid('B')),
+        )?;
+        tx.commit()?;
+        assert!(get_address(&db.writer, &expand_test_guid('C').into()).is_err());
+        assert!(get_address(&db.writer, &expand_test_guid('B').into()).is_ok());
+        Ok(())
+    }
+
+    #[test]
+    fn test_get_incoming() {
+        let mut db = new_syncable_mem_db();
+        let tx = db.transaction().expect("should get tx");
+        let ai = IncomingAddressesImpl {};
+        let record = test_record('C');
+        let bso = record.clone().into_test_incoming_bso();
+        do_test_incoming_same(&ai, &tx, record, bso);
+    }
+
+    #[test]
+    fn test_get_incoming_unknown_fields() {
+        let json = test_json_record('D');
+        let address_payload = serde_json::from_value::<AddressPayload>(json).unwrap();
+        // The incoming payload should've correctly deserialized any unknown_fields into a Map<String,Value>
+        assert_eq!(address_payload.entry.unknown_fields.len(), 2);
+        assert_eq!(
+            address_payload
+                .entry
+                .unknown_fields
+                .get("foo")
+                .unwrap()
+                .as_str()
+                .unwrap(),
+            "bar"
+        );
+    }
+
+    #[test]
+    fn test_incoming_tombstone() {
+        let mut db = new_syncable_mem_db();
+        let tx = db.transaction().expect("should get tx");
+        let ai = IncomingAddressesImpl {};
+        do_test_incoming_tombstone(&ai, &tx, test_record('C'));
+    }
+
+    #[test]
+    fn test_staged_to_mirror() {
+        let mut db = new_syncable_mem_db();
+        let tx = db.transaction().expect("should get tx");
+        let ai = IncomingAddressesImpl {};
+        let record = test_record('C');
+        let bso = record.clone().into_test_incoming_bso();
+        do_test_staged_to_mirror(&ai, &tx, record, bso, "addresses_mirror");
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/autofill/sync/address/mod.rs.html b/book/rust-docs/src/autofill/sync/address/mod.rs.html new file mode 100644 index 0000000000..f31e97a85d --- /dev/null +++ b/book/rust-docs/src/autofill/sync/address/mod.rs.html @@ -0,0 +1,479 @@ +mod.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/.
+*/
+
+pub mod incoming;
+pub mod outgoing;
+
+use super::engine::{ConfigSyncEngine, EngineConfig, SyncEngineStorageImpl};
+use super::{
+    MergeResult, Metadata, ProcessIncomingRecordImpl, ProcessOutgoingRecordImpl, SyncRecord,
+    UnknownFields,
+};
+use crate::db::models::address::InternalAddress;
+use crate::error::*;
+use crate::sync_merge_field_check;
+use incoming::IncomingAddressesImpl;
+use outgoing::OutgoingAddressesImpl;
+use rusqlite::Transaction;
+use serde::{Deserialize, Serialize};
+use std::sync::Arc;
+use sync_guid::Guid;
+use types::Timestamp;
+
+// The engine.
+pub(crate) fn create_engine(store: Arc<crate::Store>) -> ConfigSyncEngine<InternalAddress> {
+    ConfigSyncEngine::new(
+        EngineConfig {
+            namespace: "addresses".to_string(),
+            collection: "addresses".into(),
+        },
+        store,
+        Box::new(AddressesEngineStorageImpl {}),
+    )
+}
+
+pub(super) struct AddressesEngineStorageImpl {}
+
+impl SyncEngineStorageImpl<InternalAddress> for AddressesEngineStorageImpl {
+    fn get_incoming_impl(
+        &self,
+        enc_key: &Option<String>,
+    ) -> Result<Box<dyn ProcessIncomingRecordImpl<Record = InternalAddress>>> {
+        assert!(enc_key.is_none());
+        Ok(Box::new(IncomingAddressesImpl {}))
+    }
+
+    fn reset_storage(&self, tx: &Transaction<'_>) -> Result<()> {
+        tx.execute_batch(
+            "DELETE FROM addresses_mirror;
+            DELETE FROM addresses_tombstones;",
+        )?;
+        Ok(())
+    }
+
+    fn get_outgoing_impl(
+        &self,
+        enc_key: &Option<String>,
+    ) -> Result<Box<dyn ProcessOutgoingRecordImpl<Record = InternalAddress>>> {
+        assert!(enc_key.is_none());
+        Ok(Box::new(OutgoingAddressesImpl {}))
+    }
+}
+
+// These structs are a representation of what's stored on the sync server for non-tombstone records.
+// (The actual server doesn't have `id` in the payload but instead in the envelope)
+#[derive(Default, Deserialize, Serialize)]
+pub struct AddressPayload {
+    id: Guid,
+    // For some historical reason and unlike most other sync records, addresses
+    // are serialized with this explicit 'entry' object.
+    entry: PayloadEntry,
+}
+
+#[derive(Default, Deserialize, Serialize)]
+#[serde(default, rename_all = "kebab-case")]
+struct PayloadEntry {
+    pub given_name: String,
+    pub additional_name: String,
+    pub family_name: String,
+    pub organization: String,
+    pub street_address: String,
+    pub address_level3: String,
+    pub address_level2: String,
+    pub address_level1: String,
+    pub postal_code: String,
+    pub country: String,
+    pub tel: String,
+    pub email: String,
+    // metadata (which isn't kebab-case for some historical reason...)
+    #[serde(rename = "timeCreated")]
+    pub time_created: Timestamp,
+    #[serde(rename = "timeLastUsed")]
+    pub time_last_used: Timestamp,
+    #[serde(rename = "timeLastModified")]
+    pub time_last_modified: Timestamp,
+    #[serde(rename = "timesUsed")]
+    pub times_used: i64,
+    pub version: u32, // always 3 for credit-cards
+    // Fields that the current schema did not expect, we store them only internally
+    // to round-trip them back to sync without processing them in any way
+    #[serde(flatten)]
+    unknown_fields: UnknownFields,
+}
+
+impl InternalAddress {
+    fn from_payload(p: AddressPayload) -> Result<Self> {
+        if p.entry.version != 1 {
+            // Always been version 1
+            return Err(Error::InvalidSyncPayload(format!(
+                "invalid version - {}",
+                p.entry.version
+            )));
+        }
+
+        Ok(InternalAddress {
+            guid: p.id,
+            given_name: p.entry.given_name,
+            additional_name: p.entry.additional_name,
+            family_name: p.entry.family_name,
+            organization: p.entry.organization,
+            street_address: p.entry.street_address,
+            address_level3: p.entry.address_level3,
+            address_level2: p.entry.address_level2,
+            address_level1: p.entry.address_level1,
+            postal_code: p.entry.postal_code,
+            country: p.entry.country,
+            tel: p.entry.tel,
+            email: p.entry.email,
+            metadata: Metadata {
+                time_created: p.entry.time_created,
+                time_last_used: p.entry.time_last_used,
+                time_last_modified: p.entry.time_last_modified,
+                times_used: p.entry.times_used,
+                sync_change_counter: 0,
+            },
+        })
+    }
+
+    fn into_payload(self) -> Result<AddressPayload> {
+        Ok(AddressPayload {
+            id: self.guid,
+            entry: PayloadEntry {
+                given_name: self.given_name,
+                additional_name: self.additional_name,
+                family_name: self.family_name,
+                organization: self.organization,
+                street_address: self.street_address,
+                address_level3: self.address_level3,
+                address_level2: self.address_level2,
+                address_level1: self.address_level1,
+                postal_code: self.postal_code,
+                country: self.country,
+                tel: self.tel,
+                email: self.email,
+                time_created: self.metadata.time_created,
+                time_last_used: self.metadata.time_last_used,
+                time_last_modified: self.metadata.time_last_modified,
+                times_used: self.metadata.times_used,
+                unknown_fields: Default::default(),
+                version: 1,
+            },
+        })
+    }
+}
+
+impl SyncRecord for InternalAddress {
+    fn record_name() -> &'static str {
+        "Address"
+    }
+
+    fn id(&self) -> &Guid {
+        &self.guid
+    }
+
+    fn metadata(&self) -> &Metadata {
+        &self.metadata
+    }
+
+    fn metadata_mut(&mut self) -> &mut Metadata {
+        &mut self.metadata
+    }
+
+    /// Performs a three-way merge between an incoming, local, and mirror record.
+    /// If a merge cannot be successfully completed (ie, if we find the same
+    /// field has changed both locally and remotely since the last sync), the
+    /// local record data is returned with a new guid and updated sync metadata.
+    /// Note that mirror being None is an edge-case and typically means first
+    /// sync since a "reset" (eg, disconnecting and reconnecting.
+    #[allow(clippy::cognitive_complexity)] // Looks like clippy considers this after macro-expansion...
+    fn merge(incoming: &Self, local: &Self, mirror: &Option<Self>) -> MergeResult<Self> {
+        let mut merged_record: Self = Default::default();
+        // guids must be identical
+        assert_eq!(incoming.guid, local.guid);
+
+        match mirror {
+            Some(m) => assert_eq!(incoming.guid, m.guid),
+            None => {}
+        };
+
+        merged_record.guid = incoming.guid.clone();
+
+        sync_merge_field_check!(given_name, incoming, local, mirror, merged_record);
+        sync_merge_field_check!(additional_name, incoming, local, mirror, merged_record);
+        sync_merge_field_check!(family_name, incoming, local, mirror, merged_record);
+        sync_merge_field_check!(organization, incoming, local, mirror, merged_record);
+        sync_merge_field_check!(street_address, incoming, local, mirror, merged_record);
+        sync_merge_field_check!(address_level3, incoming, local, mirror, merged_record);
+        sync_merge_field_check!(address_level2, incoming, local, mirror, merged_record);
+        sync_merge_field_check!(address_level1, incoming, local, mirror, merged_record);
+        sync_merge_field_check!(postal_code, incoming, local, mirror, merged_record);
+        sync_merge_field_check!(country, incoming, local, mirror, merged_record);
+        sync_merge_field_check!(tel, incoming, local, mirror, merged_record);
+        sync_merge_field_check!(email, incoming, local, mirror, merged_record);
+
+        merged_record.metadata = incoming.metadata;
+        merged_record
+            .metadata
+            .merge(&local.metadata, mirror.as_ref().map(|m| m.metadata()));
+
+        MergeResult::Merged {
+            merged: merged_record,
+        }
+    }
+}
+
+/// Returns a with the given local record's data but with a new guid and
+/// fresh sync metadata.
+fn get_forked_record(local_record: InternalAddress) -> InternalAddress {
+    let mut local_record_data = local_record;
+    local_record_data.guid = Guid::random();
+    local_record_data.metadata.time_created = Timestamp::now();
+    local_record_data.metadata.time_last_used = Timestamp::now();
+    local_record_data.metadata.time_last_modified = Timestamp::now();
+    local_record_data.metadata.times_used = 0;
+    local_record_data.metadata.sync_change_counter = 1;
+
+    local_record_data
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/autofill/sync/address/outgoing.rs.html b/book/rust-docs/src/autofill/sync/address/outgoing.rs.html new file mode 100644 index 0000000000..383769e1cc --- /dev/null +++ b/book/rust-docs/src/autofill/sync/address/outgoing.rs.html @@ -0,0 +1,649 @@ +outgoing.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/.
+*/
+
+use crate::db::models::address::InternalAddress;
+use crate::db::schema::ADDRESS_COMMON_COLS;
+use crate::error::*;
+use crate::sync::{address::AddressPayload, common::*};
+use crate::sync::{OutgoingBso, ProcessOutgoingRecordImpl};
+use rusqlite::{Row, Transaction};
+use sync_guid::Guid as SyncGuid;
+
+const DATA_TABLE_NAME: &str = "addresses_data";
+const MIRROR_TABLE_NAME: &str = "addresses_mirror";
+const STAGING_TABLE_NAME: &str = "addresses_sync_outgoing_staging";
+
+pub(super) struct OutgoingAddressesImpl {}
+
+impl ProcessOutgoingRecordImpl for OutgoingAddressesImpl {
+    type Record = InternalAddress;
+
+    /// Gets the local records that have unsynced changes or don't have corresponding mirror
+    /// records and upserts them to the mirror table
+    fn fetch_outgoing_records(&self, tx: &Transaction<'_>) -> anyhow::Result<Vec<OutgoingBso>> {
+        // We left join the mirror table since we'll need to know if
+        // there were any unknown fields from the server we need to roundtrip
+        let data_sql = format!(
+            "SELECT
+                l.{common_cols},
+                m.payload,
+                l.sync_change_counter
+            FROM addresses_data l
+            LEFT JOIN addresses_mirror m
+            ON l.guid = m.guid
+            WHERE sync_change_counter > 0
+                OR l.guid NOT IN (
+                    SELECT m.guid
+                    FROM addresses_mirror m
+                )",
+            common_cols = ADDRESS_COMMON_COLS,
+        );
+        let record_from_data_row: &dyn Fn(&Row<'_>) -> Result<(OutgoingBso, i64)> = &|row| {
+            let mut record = InternalAddress::from_row(row)?.into_payload()?;
+            // If the server had unknown fields we fetch it and add it to the record
+            // we'll be uploading
+            if let Some(s) = row.get::<_, Option<String>>("payload")? {
+                let mirror_payload: AddressPayload = serde_json::from_str(&s)?;
+                record.entry.unknown_fields = mirror_payload.entry.unknown_fields;
+            };
+
+            Ok((
+                OutgoingBso::from_content_with_id(record)?,
+                row.get::<_, i64>("sync_change_counter")?,
+            ))
+        };
+
+        let tombstones_sql = "SELECT guid FROM addresses_tombstones";
+
+        // save outgoing records to the mirror table.
+        // unlike credit-cards, which stores records encrypted as they are
+        // on the server to protect the sensitive fields, we just store the
+        // plaintext payload.
+        let staging_records = common_get_outgoing_staging_records(
+            tx,
+            &data_sql,
+            tombstones_sql,
+            record_from_data_row,
+        )?
+        .into_iter()
+        .map(|(bso, change_counter)| (bso.envelope.id, bso.payload, change_counter))
+        .collect::<Vec<_>>();
+        common_save_outgoing_records(tx, STAGING_TABLE_NAME, staging_records)?;
+
+        // return outgoing changes
+        Ok(
+            common_get_outgoing_records(tx, &data_sql, tombstones_sql, record_from_data_row)?
+                .into_iter()
+                .map(|(bso, _change_counter)| bso)
+                .collect::<Vec<OutgoingBso>>(),
+        )
+    }
+
+    fn finish_synced_items(
+        &self,
+        tx: &Transaction<'_>,
+        records_synced: Vec<SyncGuid>,
+    ) -> anyhow::Result<()> {
+        common_finish_synced_items(
+            tx,
+            DATA_TABLE_NAME,
+            MIRROR_TABLE_NAME,
+            STAGING_TABLE_NAME,
+            records_synced,
+        )?;
+        Ok(())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::db::{addresses::add_internal_address, models::address::InternalAddress};
+    use crate::sync::{common::tests::*, test::new_syncable_mem_db, UnknownFields};
+    use rusqlite::Connection;
+    use serde_json::{json, Map, Value};
+    use types::Timestamp;
+
+    fn test_insert_mirror_record(
+        conn: &Connection,
+        address: InternalAddress,
+        unknown_fields: UnknownFields,
+    ) {
+        // This should probably be in the sync module, but it's used here.
+        let guid = address.guid.clone();
+        let mut addr_payload = address.into_payload().unwrap();
+        addr_payload.entry.unknown_fields = unknown_fields;
+        let payload = serde_json::to_string(&addr_payload).expect("is json");
+        conn.execute(
+            "INSERT OR IGNORE INTO addresses_mirror (guid, payload)
+             VALUES (:guid, :payload)",
+            rusqlite::named_params! {
+                ":guid": guid,
+                ":payload": &payload,
+            },
+        )
+        .expect("should insert");
+    }
+
+    lazy_static::lazy_static! {
+        static ref TEST_JSON_RECORDS: Map<String, Value> = {
+            // NOTE: the JSON here is the same as stored on the sync server -
+            // the superfluous `entry` is unfortunate but from desktop.
+            let val = json! {{
+                "C" : {
+                    "id": expand_test_guid('C'),
+                    "entry": {
+                        "givenName": "jane",
+                        "familyName": "doe",
+                        "streetAddress": "3050 South La Brea Ave",
+                        "addressLevel2": "Los Angeles, CA",
+                        "country": "United States",
+                        "timeCreated": 0,
+                        "timeLastUsed": 0,
+                        "timeLastModified": 0,
+                        "timesUsed": 0,
+                        "version": 1,
+                    }
+                },
+                "D" : {
+                    "id": expand_test_guid('D'),
+                    "entry": {
+                        "given-name": "john",
+                        "family-name": "doe",
+                        "street-address": "85 Pike St",
+                        "address-level2": "Seattle, WA",
+                        "country": "United States",
+                        "timeCreated": 0,
+                        "timeLastUsed": 0,
+                        "timeLastModified": 0,
+                        "timesUsed": 0,
+                        "version": 1,
+                        // Fields we don't understand from the server
+                        "foo": "bar",
+                        "baz": "qux",
+                    }
+                }
+            }};
+            val.as_object().expect("literal is an object").clone()
+        };
+    }
+
+    fn test_json_record(guid_prefix: char) -> Value {
+        TEST_JSON_RECORDS
+            .get(&guid_prefix.to_string())
+            .expect("should exist")
+            .clone()
+    }
+
+    fn test_record(guid_prefix: char) -> InternalAddress {
+        let json = test_json_record(guid_prefix);
+        let payload = serde_json::from_value(json).unwrap();
+        InternalAddress::from_payload(payload).expect("should be valid")
+    }
+
+    #[test]
+    fn test_outgoing_never_synced() {
+        let mut db = new_syncable_mem_db();
+        let tx = db.transaction().expect("should get tx");
+        let ao = OutgoingAddressesImpl {};
+        let test_record = test_record('C');
+
+        // create data record
+        assert!(add_internal_address(&tx, &test_record).is_ok());
+        do_test_outgoing_never_synced(
+            &tx,
+            &ao,
+            &test_record.guid,
+            DATA_TABLE_NAME,
+            MIRROR_TABLE_NAME,
+            STAGING_TABLE_NAME,
+        );
+    }
+
+    #[test]
+    fn test_outgoing_tombstone() {
+        let mut db = new_syncable_mem_db();
+        let tx = db.transaction().expect("should get tx");
+        let ao = OutgoingAddressesImpl {};
+        let test_record = test_record('C');
+
+        // create tombstone record
+        assert!(tx
+            .execute(
+                "INSERT INTO addresses_tombstones (
+                    guid,
+                    time_deleted
+                ) VALUES (
+                    :guid,
+                    :time_deleted
+                )",
+                rusqlite::named_params! {
+                    ":guid": test_record.guid,
+                    ":time_deleted": Timestamp::now(),
+                },
+            )
+            .is_ok());
+        do_test_outgoing_tombstone(
+            &tx,
+            &ao,
+            &test_record.guid,
+            DATA_TABLE_NAME,
+            MIRROR_TABLE_NAME,
+            STAGING_TABLE_NAME,
+        );
+    }
+
+    #[test]
+    fn test_outgoing_synced_with_local_change() {
+        let mut db = new_syncable_mem_db();
+        let tx = db.transaction().expect("should get tx");
+        let ao = OutgoingAddressesImpl {};
+
+        // create synced record with non-zero sync_change_counter
+        let mut test_record = test_record('C');
+        let initial_change_counter_val = 2;
+        test_record.metadata.sync_change_counter = initial_change_counter_val;
+        assert!(add_internal_address(&tx, &test_record).is_ok());
+        test_insert_mirror_record(&tx, test_record.clone(), Default::default());
+        exists_with_counter_value_in_table(
+            &tx,
+            DATA_TABLE_NAME,
+            &test_record.guid,
+            initial_change_counter_val,
+        );
+
+        do_test_outgoing_synced_with_local_change(
+            &tx,
+            &ao,
+            &test_record.guid,
+            DATA_TABLE_NAME,
+            MIRROR_TABLE_NAME,
+            STAGING_TABLE_NAME,
+        );
+    }
+
+    #[test]
+    fn test_outgoing_synced_with_no_change() {
+        let mut db = new_syncable_mem_db();
+        let tx = db.transaction().expect("should get tx");
+        let ao = OutgoingAddressesImpl {};
+
+        // create synced record with no changes (sync_change_counter = 0)
+        let test_record = test_record('C');
+        assert!(add_internal_address(&tx, &test_record).is_ok());
+        test_insert_mirror_record(&tx, test_record.clone(), Default::default());
+
+        do_test_outgoing_synced_with_no_change(
+            &tx,
+            &ao,
+            &test_record.guid,
+            DATA_TABLE_NAME,
+            STAGING_TABLE_NAME,
+        );
+    }
+
+    #[test]
+    fn test_outgoing_roundtrip_unknown() {
+        let mut db = new_syncable_mem_db();
+        let tx = db.transaction().expect("should get tx");
+        let ao = OutgoingAddressesImpl {};
+
+        // create synced record with non-zero sync_change_counter
+        let mut test_record = test_record('D');
+        let initial_change_counter_val = 2;
+        test_record.metadata.sync_change_counter = initial_change_counter_val;
+        assert!(add_internal_address(&tx, &test_record).is_ok());
+        // put "unknown_fields" into the mirror payload to imitate the server
+        let unknown_fields: UnknownFields =
+            serde_json::from_value(json! {{ "foo": "bar", "baz": "qux"}}).unwrap();
+        test_insert_mirror_record(&tx, test_record.clone(), unknown_fields);
+        exists_with_counter_value_in_table(
+            &tx,
+            DATA_TABLE_NAME,
+            &test_record.guid,
+            initial_change_counter_val,
+        );
+
+        let outgoing = &ao.fetch_outgoing_records(&tx).unwrap();
+        // Ensure we have our unknown values for the roundtrip
+        let bso_payload: Map<String, Value> = serde_json::from_str(&outgoing[0].payload).unwrap();
+        let entry = bso_payload.get("entry").unwrap();
+        assert_eq!(entry.get("foo").unwrap(), "bar");
+        assert_eq!(entry.get("baz").unwrap(), "qux");
+        do_test_outgoing_synced_with_local_change(
+            &tx,
+            &ao,
+            &test_record.guid,
+            DATA_TABLE_NAME,
+            MIRROR_TABLE_NAME,
+            STAGING_TABLE_NAME,
+        );
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/autofill/sync/common.rs.html b/book/rust-docs/src/autofill/sync/common.rs.html new file mode 100644 index 0000000000..f970559ccc --- /dev/null +++ b/book/rust-docs/src/autofill/sync/common.rs.html @@ -0,0 +1,1177 @@ +common.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
+540
+541
+542
+543
+544
+545
+546
+547
+548
+549
+550
+551
+552
+553
+554
+555
+556
+557
+558
+559
+560
+561
+562
+563
+564
+565
+566
+567
+568
+569
+570
+571
+572
+573
+574
+575
+576
+577
+578
+579
+580
+581
+582
+583
+584
+585
+586
+587
+588
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/.
+*/
+
+// This contains sync functionality we've managed to share between addresses
+// and credit-cards. It's not "generic" in the way that traits are, it's
+// literally just code we can share.
+// For example, this code doesn't abstract storage away - it knows we are
+// using a sql database and knows that the schemas for addresses and cards are
+// very similar.
+
+use crate::error::*;
+use interrupt_support::Interruptee;
+use rusqlite::{types::ToSql, Connection, Row};
+use sync15::bso::OutgoingBso;
+use sync15::ServerTimestamp;
+use sync_guid::Guid;
+
+/// Stages incoming records (excluding incoming tombstones) in preparation for
+/// applying incoming changes for the syncing autofill records.
+/// The incoming record is a String, because for credit-cards it is the encrypted
+/// version of a JSON record.
+pub(super) fn common_stage_incoming_records(
+    conn: &Connection,
+    table_name: &str,
+    incoming: Vec<(Guid, String, ServerTimestamp)>,
+    signal: &dyn Interruptee,
+) -> Result<()> {
+    log::info!(
+        "staging {} incoming records into {}",
+        incoming.len(),
+        table_name
+    );
+    let chunk_size = 2;
+    let vals: Vec<(Guid, String)> = incoming
+        .into_iter()
+        .map(|(guid, data, _)| (guid, data))
+        .collect();
+    sql_support::each_sized_chunk(
+        &vals,
+        sql_support::default_max_variable_number() / chunk_size,
+        |chunk, _| -> Result<()> {
+            signal.err_if_interrupted()?;
+            let sql = format!(
+                "INSERT OR REPLACE INTO temp.{table_name} (guid, payload)
+                 VALUES {vals}",
+                table_name = table_name,
+                vals = sql_support::repeat_multi_values(chunk.len(), 2)
+            );
+            let mut params = Vec::with_capacity(chunk.len() * chunk_size);
+            for (guid, json) in chunk {
+                params.push(guid as &dyn ToSql);
+                params.push(json);
+            }
+            conn.execute(&sql, rusqlite::params_from_iter(params))?;
+            Ok(())
+        },
+    )?;
+    log::trace!("staged");
+    Ok(())
+}
+
+pub(super) fn common_remove_record(conn: &Connection, table_name: &str, guid: &Guid) -> Result<()> {
+    conn.execute(
+        &format!(
+            "DELETE FROM {}
+            WHERE guid = :guid",
+            table_name
+        ),
+        rusqlite::named_params! {
+            ":guid": guid,
+        },
+    )?;
+    Ok(())
+}
+
+// This optionally takes a mirror table name to update if
+// there is a corresponding record with the same guid we'd like to update
+pub(super) fn common_change_guid(
+    conn: &Connection,
+    table_name: &str,
+    mirror_table_name: &str,
+    old_guid: &Guid,
+    new_guid: &Guid,
+) -> Result<()> {
+    assert_ne!(old_guid, new_guid);
+    let nrows = conn.execute(
+        &format!(
+            "UPDATE {}
+            SET guid = :new_guid,
+            sync_change_counter = sync_change_counter + 1
+            WHERE guid = :old_guid",
+            table_name
+        ),
+        rusqlite::named_params! {
+            ":old_guid": old_guid,
+            ":new_guid": new_guid,
+        },
+    )?;
+    // something's gone badly wrong if this didn't affect exactly 1 row.
+    assert_eq!(nrows, 1);
+
+    // If there is also a corresponding mirror row (e.g forking situations), update aswell
+    conn.execute(
+        &format!(
+            "UPDATE {}
+                SET guid = :new_guid
+                WHERE guid = :old_guid",
+            mirror_table_name
+        ),
+        rusqlite::named_params! {
+            ":old_guid": old_guid,
+            ":new_guid": new_guid,
+        },
+    )?;
+
+    Ok(())
+}
+
+/// Records in the incoming staging table need to end up in the mirror.
+pub(super) fn common_mirror_staged_records(
+    conn: &Connection,
+    staging_table_name: &str,
+    mirror_table_name: &str,
+) -> Result<()> {
+    conn.execute(
+        &format!(
+            "INSERT OR REPLACE INTO {} (guid, payload)
+             SELECT guid, payload FROM temp.{}",
+            mirror_table_name, staging_table_name,
+        ),
+        [],
+    )?;
+    Ok(())
+}
+
+// A macro for our record merge implementation.
+// We allow all "common" fields from the sub-types to be getters on the
+// InsertableItem type.
+// Macros don't have fine-grained visibility and is visible to the entire
+// crate, so we give it a very specific name.
+#[macro_export]
+macro_rules! sync_merge_field_check {
+    ($field_name:ident,
+    $incoming:ident,
+    $local:ident,
+    $mirror:ident,
+    $merged_record:ident
+    ) => {
+        let incoming_field = &$incoming.$field_name;
+        let local_field = &$local.$field_name;
+        let is_local_same;
+        let is_incoming_same;
+
+        match &$mirror {
+            Some(m) => {
+                let mirror_field = &m.$field_name;
+                is_local_same = mirror_field == local_field;
+                is_incoming_same = mirror_field == incoming_field;
+            }
+            None => {
+                is_local_same = true;
+                is_incoming_same = local_field == incoming_field;
+            }
+        };
+
+        let should_use_local = is_incoming_same || local_field == incoming_field;
+
+        if is_local_same && !is_incoming_same {
+            $merged_record.$field_name = incoming_field.clone();
+        } else if should_use_local {
+            $merged_record.$field_name = local_field.clone();
+        } else {
+            // There are conflicting differences, so we "fork" the record - we
+            // will end up giving the local one a new guid and save the remote
+            // one with its incoming ID.
+            return MergeResult::Forked {
+                forked: get_forked_record($local.clone()),
+            };
+        }
+    };
+}
+
+pub(super) fn common_get_outgoing_staging_records(
+    conn: &Connection,
+    data_sql: &str,
+    tombstones_sql: &str,
+    payload_from_data_row: &dyn Fn(&Row<'_>) -> Result<(OutgoingBso, i64)>,
+) -> anyhow::Result<Vec<(OutgoingBso, i64)>> {
+    let outgoing_records =
+        common_get_outgoing_records(conn, data_sql, tombstones_sql, payload_from_data_row)?;
+    Ok(outgoing_records.into_iter().collect::<Vec<_>>())
+}
+
+fn get_outgoing_records(
+    conn: &Connection,
+    sql: &str,
+    record_from_data_row: &dyn Fn(&Row<'_>) -> Result<(OutgoingBso, i64)>,
+) -> anyhow::Result<Vec<(OutgoingBso, i64)>> {
+    Ok(conn
+        .prepare(sql)?
+        .query_map([], |row| {
+            Ok(record_from_data_row(row).unwrap()) // XXX - this unwrap()!
+        })?
+        .collect::<std::result::Result<_, _>>()?)
+}
+
+pub(super) fn common_get_outgoing_records(
+    conn: &Connection,
+    data_sql: &str,
+    tombstone_sql: &str,
+    record_from_data_row: &dyn Fn(&Row<'_>) -> Result<(OutgoingBso, i64)>,
+) -> anyhow::Result<Vec<(OutgoingBso, i64)>> {
+    let mut payload = get_outgoing_records(conn, data_sql, record_from_data_row)?;
+
+    payload.append(&mut get_outgoing_records(conn, tombstone_sql, &|row| {
+        Ok((
+            OutgoingBso::new_tombstone(Guid::from_string(row.get("guid")?).into()),
+            0,
+        ))
+    })?);
+
+    Ok(payload)
+}
+
+pub(super) fn common_save_outgoing_records(
+    conn: &Connection,
+    table_name: &str,
+    staging_records: Vec<(Guid, String, i64)>,
+) -> anyhow::Result<()> {
+    let chunk_size = 3;
+    sql_support::each_sized_chunk(
+        &staging_records,
+        sql_support::default_max_variable_number() / chunk_size,
+        |chunk, _| -> anyhow::Result<()> {
+            let sql = format!(
+                "INSERT OR REPLACE INTO temp.{table_name} (guid, payload, sync_change_counter)
+                VALUES {staging_records}",
+                table_name = table_name,
+                staging_records = sql_support::repeat_multi_values(chunk.len(), chunk_size)
+            );
+            let mut params = Vec::with_capacity(chunk.len() * chunk_size);
+            for (guid, json, sync_change_counter) in chunk {
+                params.push(guid as &dyn ToSql);
+                params.push(json);
+                params.push(sync_change_counter);
+            }
+            conn.execute(&sql, rusqlite::params_from_iter(params))?;
+            Ok(())
+        },
+    )?;
+    Ok(())
+}
+
+pub(super) fn common_finish_synced_items(
+    conn: &Connection,
+    data_table_name: &str,
+    mirror_table_name: &str,
+    outgoing_staging_table_name: &str,
+    records_synced: Vec<Guid>,
+) -> anyhow::Result<()> {
+    // Update the local change counter for uploaded items.
+    reset_sync_change_counter(
+        conn,
+        data_table_name,
+        outgoing_staging_table_name,
+        records_synced,
+    )?;
+    // Copy from the outgoing staging table into the mirror.
+    let sql = format!(
+        "INSERT OR REPLACE INTO {mirror_table_name}
+            SELECT guid, payload FROM temp.{outgoing_staging_table_name}",
+        mirror_table_name = mirror_table_name,
+        outgoing_staging_table_name = outgoing_staging_table_name,
+    );
+    conn.execute(&sql, [])?;
+    Ok(())
+}
+
+// When we started syncing, we saved `sync_change_counter` in the staging
+// table for every record. Now that we've uploaded the server, we need to
+// decrement that value from the current value - anything that ends up with
+// a non-zero value must have changed since while we were uploading so remains
+// dirty. This does that decrement.
+fn reset_sync_change_counter(
+    conn: &Connection,
+    data_table_name: &str,
+    outgoing_table_name: &str,
+    records_synced: Vec<Guid>,
+) -> anyhow::Result<()> {
+    sql_support::each_chunk(&records_synced, |chunk, _| -> anyhow::Result<()> {
+        conn.execute(
+            &format!(
+                // We're making two checks that in practice should be redundant. First we're limiting the
+                // number of records that we're pulling from the outgoing staging table to one. Lastly we're
+                // ensuring that the updated local records are also in `records_synced` which should be the
+                // case since the sync will fail entirely if the server rejects individual records.
+                "UPDATE {data_table_name} AS data
+                SET sync_change_counter = sync_change_counter -
+                    (
+                        SELECT outgoing.sync_change_counter
+                        FROM temp.{outgoing_table_name} AS outgoing
+                        WHERE outgoing.guid = data.guid LIMIT 1
+                    )
+                WHERE guid IN ({values})",
+                data_table_name = data_table_name,
+                outgoing_table_name = outgoing_table_name,
+                values = sql_support::repeat_sql_values(chunk.len())
+            ),
+            rusqlite::params_from_iter(chunk),
+        )?;
+        Ok(())
+    })?;
+
+    Ok(())
+}
+
+// And common helpers for tests (although no actual tests!)
+#[cfg(test)]
+pub(super) mod tests {
+    use super::super::*;
+    use interrupt_support::NeverInterrupts;
+    use serde_json::{json, Value};
+
+    pub(in crate::sync) fn array_to_incoming(vals: Vec<Value>) -> Vec<IncomingBso> {
+        vals.into_iter()
+            .map(IncomingBso::from_test_content)
+            .collect()
+    }
+
+    pub(in crate::sync) fn expand_test_guid(c: char) -> String {
+        c.to_string().repeat(12)
+    }
+
+    pub(in crate::sync) fn test_json_tombstone(guid_prefix: char) -> Value {
+        let t = json! {
+            {
+                "id": expand_test_guid(guid_prefix),
+                "deleted": true,
+            }
+        };
+        t
+    }
+
+    // Incoming record is identical to a local record.
+    pub(in crate::sync) fn do_test_incoming_same<T: SyncRecord + std::fmt::Debug + Clone>(
+        ri: &dyn ProcessIncomingRecordImpl<Record = T>,
+        tx: &Transaction<'_>,
+        record: T,
+        bso: IncomingBso,
+    ) {
+        ri.insert_local_record(tx, record)
+            .expect("insert should work");
+        ri.stage_incoming(tx, vec![bso], &NeverInterrupts)
+            .expect("stage should work");
+        let mut states = ri.fetch_incoming_states(tx).expect("fetch should work");
+        assert_eq!(states.len(), 1, "1 records == 1 state!");
+        let action =
+            crate::sync::plan_incoming(ri, tx, states.pop().unwrap()).expect("plan should work");
+        // Even though the records are identical, we still merged the metadata
+        // so treat this as an Update.
+        assert!(matches!(action, crate::sync::IncomingAction::Update { .. }));
+    }
+
+    // Incoming tombstone for an existing local record.
+    pub(in crate::sync) fn do_test_incoming_tombstone<T: SyncRecord + std::fmt::Debug + Clone>(
+        ri: &dyn ProcessIncomingRecordImpl<Record = T>,
+        tx: &Transaction<'_>,
+        record: T,
+    ) {
+        let guid = record.id().clone();
+        ri.insert_local_record(tx, record)
+            .expect("insert should work");
+        ri.stage_incoming(
+            tx,
+            vec![IncomingBso::new_test_tombstone(guid)],
+            &NeverInterrupts,
+        )
+        .expect("stage should work");
+        let mut states = ri.fetch_incoming_states(tx).expect("fetch should work");
+        assert_eq!(states.len(), 1, "1 records == 1 state!");
+        let action =
+            crate::sync::plan_incoming(ri, tx, states.pop().unwrap()).expect("plan should work");
+        // Even though the records are identical, we still merged the metadata
+        // so treat this as an Update.
+        assert!(matches!(
+            action,
+            crate::sync::IncomingAction::DeleteLocalRecord { .. }
+        ));
+    }
+
+    // local record was scrubbed of encrypted data -- we should update it using server data
+    pub(in crate::sync) fn do_test_scrubbed_local_data<T: SyncRecord + std::fmt::Debug + Clone>(
+        ri: &dyn ProcessIncomingRecordImpl<Record = T>,
+        tx: &Transaction<'_>,
+        record: T,
+        bso: IncomingBso,
+    ) {
+        ri.insert_local_record(tx, record)
+            .expect("insert should work");
+        ri.stage_incoming(tx, vec![bso], &NeverInterrupts)
+            .expect("stage should work");
+        let mut states = ri.fetch_incoming_states(tx).expect("fetch should work");
+        assert_eq!(states.len(), 1, "1 records == 1 state!");
+        assert!(
+            matches!(states[0].local, LocalRecordInfo::Scrubbed { .. }),
+            "state should be LocalRecordInfo::Scubbed but it is: {:?}",
+            states[0].local
+        );
+
+        let action =
+            crate::sync::plan_incoming(ri, tx, states.pop().unwrap()).expect("plan should work");
+        assert!(matches!(action, crate::sync::IncomingAction::Update { .. }));
+    }
+
+    // "Staged" records are moved to the mirror by finish_incoming().
+    pub(in crate::sync) fn do_test_staged_to_mirror<T: SyncRecord + std::fmt::Debug + Clone>(
+        ri: &dyn ProcessIncomingRecordImpl<Record = T>,
+        tx: &Transaction<'_>,
+        record: T,
+        bso1: IncomingBso,
+        mirror_table_name: &str,
+    ) {
+        let guid1 = record.id().clone();
+        let guid2 = Guid::random();
+        let bso2 = IncomingBso::new_test_tombstone(guid2.clone());
+
+        ri.stage_incoming(tx, vec![bso1, bso2], &NeverInterrupts)
+            .expect("stage should work");
+
+        ri.finish_incoming(tx).expect("finish should work");
+
+        let sql = format!(
+            "SELECT COUNT(*) FROM {} where guid = '{}' OR guid = '{}'",
+            mirror_table_name, guid1, guid2
+        );
+        let num_rows = tx
+            .query_row(&sql, [], |row| Ok(row.get::<_, u32>(0).unwrap()))
+            .unwrap();
+        assert_eq!(num_rows, 2);
+    }
+
+    fn exists_in_table(tx: &Transaction<'_>, table_name: &str, guid: &Guid) {
+        let sql = format!(
+            "SELECT COUNT(*) FROM {} where guid = '{}'",
+            table_name, guid
+        );
+        let num_rows = tx
+            .query_row(&sql, [], |row| Ok(row.get::<_, u32>(0).unwrap()))
+            .unwrap();
+        assert_eq!(num_rows, 1);
+    }
+
+    pub(in crate::sync) fn exists_with_counter_value_in_table(
+        tx: &Transaction<'_>,
+        table_name: &str,
+        guid: &Guid,
+        expected_counter_value: i64,
+    ) {
+        let sql = format!(
+            "SELECT COUNT(*)
+            FROM {table_name}
+            WHERE sync_change_counter = {expected_counter_value}
+                AND guid = :guid",
+            table_name = table_name,
+            expected_counter_value = expected_counter_value,
+        );
+
+        let num_rows = tx
+            .query_row(&sql, [guid], |row| Ok(row.get::<_, u32>(0).unwrap()))
+            .unwrap();
+        assert_eq!(num_rows, 1);
+    }
+
+    pub(in crate::sync) fn do_test_outgoing_never_synced<
+        T: SyncRecord + std::fmt::Debug + Clone,
+    >(
+        tx: &Transaction<'_>,
+        ro: &dyn ProcessOutgoingRecordImpl<Record = T>,
+        guid: &Guid,
+        data_table_name: &str,
+        mirror_table_name: &str,
+        staging_table_name: &str,
+    ) {
+        // call fetch outgoing records
+        assert!(ro.fetch_outgoing_records(tx).is_ok());
+
+        // check that the record is in the outgoing table
+        exists_in_table(tx, &format!("temp.{}", staging_table_name), guid);
+
+        // call push synced items
+        assert!(ro.finish_synced_items(tx, vec![guid.clone()]).is_ok());
+
+        // check that the sync change counter
+        exists_with_counter_value_in_table(tx, data_table_name, guid, 0);
+
+        // check that the outgoing record is in the mirror
+        exists_in_table(tx, mirror_table_name, guid);
+    }
+
+    pub(in crate::sync) fn do_test_outgoing_tombstone<T: SyncRecord + std::fmt::Debug + Clone>(
+        tx: &Transaction<'_>,
+        ro: &dyn ProcessOutgoingRecordImpl<Record = T>,
+        guid: &Guid,
+        data_table_name: &str,
+        mirror_table_name: &str,
+        staging_table_name: &str,
+    ) {
+        // call fetch outgoing records
+        assert!(ro.fetch_outgoing_records(tx).is_ok());
+
+        // check that the record is in the outgoing table
+        exists_in_table(tx, &format!("temp.{}", staging_table_name), guid);
+
+        // call push synced items
+        assert!(ro.finish_synced_items(tx, vec![guid.clone()]).is_ok());
+
+        // check that the record wasn't copied to the data table
+        let sql = format!(
+            "SELECT COUNT(*) FROM {} where guid = '{}'",
+            data_table_name, guid
+        );
+        let num_rows = tx
+            .query_row(&sql, [], |row| Ok(row.get::<_, u32>(0).unwrap()))
+            .unwrap();
+        assert_eq!(num_rows, 0);
+
+        // check that the outgoing record is in the mirror
+        exists_in_table(tx, mirror_table_name, guid);
+    }
+
+    pub(in crate::sync) fn do_test_outgoing_synced_with_local_change<
+        T: SyncRecord + std::fmt::Debug + Clone,
+    >(
+        tx: &Transaction<'_>,
+        ro: &dyn ProcessOutgoingRecordImpl<Record = T>,
+        guid: &Guid,
+        data_table_name: &str,
+        mirror_table_name: &str,
+        staging_table_name: &str,
+    ) {
+        // call fetch outgoing records
+        assert!(ro.fetch_outgoing_records(tx).is_ok());
+
+        // check that the record is in the outgoing table
+        exists_in_table(tx, &format!("temp.{}", staging_table_name), guid);
+
+        // call push synced items
+        assert!(ro.finish_synced_items(tx, vec![guid.clone()]).is_ok());
+
+        // check that the sync change counter
+        exists_with_counter_value_in_table(tx, data_table_name, guid, 0);
+
+        // check that the outgoing record is in the mirror
+        exists_in_table(tx, mirror_table_name, guid);
+    }
+
+    pub(in crate::sync) fn do_test_outgoing_synced_with_no_change<
+        T: SyncRecord + std::fmt::Debug + Clone,
+    >(
+        tx: &Transaction<'_>,
+        ro: &dyn ProcessOutgoingRecordImpl<Record = T>,
+        guid: &Guid,
+        data_table_name: &str,
+        staging_table_name: &str,
+    ) {
+        // call fetch outgoing records
+        assert!(ro.fetch_outgoing_records(tx).is_ok());
+
+        // check that the record is not in the outgoing table
+        let sql = format!(
+            "SELECT COUNT(*) FROM {} where guid = '{}'",
+            &format!("temp.{}", staging_table_name),
+            guid
+        );
+        let num_rows = tx
+            .query_row(&sql, [], |row| Ok(row.get::<_, u32>(0).unwrap()))
+            .unwrap();
+        assert_eq!(num_rows, 0);
+
+        // call push synced items
+        assert!(ro.finish_synced_items(tx, Vec::<Guid>::new()).is_ok());
+
+        // check that the sync change counter is unchanged
+        exists_with_counter_value_in_table(tx, data_table_name, guid, 0);
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/autofill/sync/credit_card/incoming.rs.html b/book/rust-docs/src/autofill/sync/credit_card/incoming.rs.html new file mode 100644 index 0000000000..d8b8410d6d --- /dev/null +++ b/book/rust-docs/src/autofill/sync/credit_card/incoming.rs.html @@ -0,0 +1,1219 @@ +incoming.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
+540
+541
+542
+543
+544
+545
+546
+547
+548
+549
+550
+551
+552
+553
+554
+555
+556
+557
+558
+559
+560
+561
+562
+563
+564
+565
+566
+567
+568
+569
+570
+571
+572
+573
+574
+575
+576
+577
+578
+579
+580
+581
+582
+583
+584
+585
+586
+587
+588
+589
+590
+591
+592
+593
+594
+595
+596
+597
+598
+599
+600
+601
+602
+603
+604
+605
+606
+607
+608
+609
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/.
+*/
+
+use super::CreditCardPayload;
+use crate::db::credit_cards::{add_internal_credit_card, update_internal_credit_card};
+use crate::db::models::credit_card::InternalCreditCard;
+use crate::db::schema::CREDIT_CARD_COMMON_COLS;
+use crate::encryption::EncryptorDecryptor;
+use crate::error::*;
+use crate::sync::common::*;
+use crate::sync::{
+    IncomingBso, IncomingContent, IncomingEnvelope, IncomingKind, IncomingState, LocalRecordInfo,
+    ProcessIncomingRecordImpl, ServerTimestamp, SyncRecord,
+};
+use interrupt_support::Interruptee;
+use rusqlite::{named_params, Transaction};
+use sql_support::ConnExt;
+use sync_guid::Guid as SyncGuid;
+
+// Takes a raw payload, as stored in our database, and returns an InternalCreditCard
+// or a tombstone. Credit-cards store the payload as an encrypted string, so we
+// decrypt before conversion.
+fn raw_payload_to_incoming(
+    id: SyncGuid,
+    raw: String,
+    encdec: &EncryptorDecryptor,
+) -> Result<IncomingContent<InternalCreditCard>> {
+    let payload = encdec.decrypt(&raw, "raw payload")?;
+    // Turn it into a BSO
+    let bso = IncomingBso {
+        envelope: IncomingEnvelope {
+            id,
+            modified: ServerTimestamp::default(),
+            sortindex: None,
+            ttl: None,
+        },
+        payload,
+    };
+    // For hysterical raisins, we use an IncomingContent<CCPayload> to convert
+    // to an IncomingContent<InternalCC>
+    let payload_content = bso.into_content::<CreditCardPayload>();
+    Ok(match payload_content.kind {
+        IncomingKind::Content(content) => IncomingContent {
+            envelope: payload_content.envelope,
+            kind: IncomingKind::Content(InternalCreditCard::from_payload(content, encdec)?),
+        },
+        IncomingKind::Tombstone => IncomingContent {
+            envelope: payload_content.envelope,
+            kind: IncomingKind::Tombstone,
+        },
+        IncomingKind::Malformed => IncomingContent {
+            envelope: payload_content.envelope,
+            kind: IncomingKind::Malformed,
+        },
+    })
+}
+
+pub(super) struct IncomingCreditCardsImpl {
+    pub(super) encdec: EncryptorDecryptor,
+}
+
+impl ProcessIncomingRecordImpl for IncomingCreditCardsImpl {
+    type Record = InternalCreditCard;
+
+    /// The first step in the "apply incoming" process - stage the records
+    fn stage_incoming(
+        &self,
+        tx: &Transaction<'_>,
+        incoming: Vec<IncomingBso>,
+        signal: &dyn Interruptee,
+    ) -> Result<()> {
+        // Convert the sync15::Payloads to encrypted strings.
+        let to_stage = incoming
+            .into_iter()
+            .map(|bso| {
+                // consider turning this into malformed?
+                let encrypted = self.encdec.encrypt(&bso.payload, "bso payload")?;
+                Ok((bso.envelope.id, encrypted, bso.envelope.modified))
+            })
+            .collect::<Result<_>>()?;
+        common_stage_incoming_records(tx, "credit_cards_sync_staging", to_stage, signal)
+    }
+
+    fn finish_incoming(&self, tx: &Transaction<'_>) -> Result<()> {
+        common_mirror_staged_records(tx, "credit_cards_sync_staging", "credit_cards_mirror")
+    }
+
+    /// The second step in the "apply incoming" process for syncing autofill CC records.
+    /// Incoming items are retrieved from the temp tables, deserialized, and
+    /// assigned `IncomingState` values.
+    fn fetch_incoming_states(
+        &self,
+        tx: &Transaction<'_>,
+    ) -> Result<Vec<IncomingState<Self::Record>>> {
+        let sql = "
+        SELECT
+            s.guid as guid,
+            l.guid as l_guid,
+            t.guid as t_guid,
+            s.payload as s_payload,
+            m.payload as m_payload,
+            l.cc_name,
+            l.cc_number_enc,
+            l.cc_number_last_4,
+            l.cc_exp_month,
+            l.cc_exp_year,
+            l.cc_type,
+            l.time_created,
+            l.time_last_used,
+            l.time_last_modified,
+            l.times_used,
+            l.sync_change_counter
+        FROM temp.credit_cards_sync_staging s
+        LEFT JOIN credit_cards_mirror m ON s.guid = m.guid
+        LEFT JOIN credit_cards_data l ON s.guid = l.guid
+        LEFT JOIN credit_cards_tombstones t ON s.guid = t.guid";
+
+        tx.query_rows_and_then(sql, [], |row| -> Result<IncomingState<Self::Record>> {
+            // the 'guid' and 's_payload' rows must be non-null.
+            let guid: SyncGuid = row.get("guid")?;
+            let incoming =
+                raw_payload_to_incoming(guid.clone(), row.get("s_payload")?, &self.encdec)?;
+            Ok(IncomingState {
+                incoming,
+                local: match row.get_unwrap::<_, Option<String>>("l_guid") {
+                    Some(l_guid) => {
+                        assert_eq!(l_guid, guid);
+                        // local record exists, check the state.
+                        let record = InternalCreditCard::from_row(row)?;
+                        if record.has_scrubbed_data() {
+                            LocalRecordInfo::Scrubbed { record }
+                        } else {
+                            let has_changes = record.metadata().sync_change_counter != 0;
+                            if has_changes {
+                                LocalRecordInfo::Modified { record }
+                            } else {
+                                LocalRecordInfo::Unmodified { record }
+                            }
+                        }
+                    }
+                    None => {
+                        // no local record - maybe a tombstone?
+                        match row.get::<_, Option<String>>("t_guid")? {
+                            Some(t_guid) => {
+                                assert_eq!(guid, t_guid);
+                                LocalRecordInfo::Tombstone { guid: guid.clone() }
+                            }
+                            None => LocalRecordInfo::Missing,
+                        }
+                    }
+                },
+                mirror: {
+                    match row.get::<_, Option<String>>("m_payload")? {
+                        Some(m_payload) => {
+                            // a tombstone in the mirror can be treated as though it's missing.
+                            raw_payload_to_incoming(guid, m_payload, &self.encdec)?.content()
+                        }
+                        None => None,
+                    }
+                },
+            })
+        })
+    }
+
+    /// Returns a local record that has the same values as the given incoming record (with the exception
+    /// of the `guid` values which should differ) that will be used as a local duplicate record for
+    /// syncing.
+    fn get_local_dupe(
+        &self,
+        tx: &Transaction<'_>,
+        incoming: &Self::Record,
+    ) -> Result<Option<Self::Record>> {
+        let sql = format!("
+            SELECT
+                {common_cols},
+                sync_change_counter
+            FROM credit_cards_data
+            WHERE
+                -- `guid <> :guid` is a pre-condition for this being called, but...
+                guid <> :guid
+                -- only non-synced records are candidates, which means can't already be in the mirror.
+                AND guid NOT IN (
+                    SELECT guid
+                    FROM credit_cards_mirror
+                )
+                -- and sql can check the field values (but note we can not meaningfully
+                -- check the encrypted value, as it's different each time it is encrypted)
+                AND cc_name == :cc_name
+                AND cc_number_last_4 == :cc_number_last_4
+                AND cc_exp_month == :cc_exp_month
+                AND cc_exp_year == :cc_exp_year
+                AND cc_type == :cc_type", common_cols = CREDIT_CARD_COMMON_COLS);
+
+        let params = named_params! {
+            ":guid": incoming.guid,
+            ":cc_name": incoming.cc_name,
+            ":cc_number_last_4": incoming.cc_number_last_4,
+            ":cc_exp_month": incoming.cc_exp_month,
+            ":cc_exp_year": incoming.cc_exp_year,
+            ":cc_type": incoming.cc_type,
+        };
+
+        // Because we can't check the number in the sql, we fetch all matching
+        // rows and decrypt the numbers here.
+        let records = tx.query_rows_and_then(&sql, params, |row| -> Result<Self::Record> {
+            Ok(Self::Record::from_row(row)?)
+        })?;
+
+        let incoming_cc_number = self.encdec.decrypt(&incoming.cc_number_enc, "cc_number")?;
+        for record in records {
+            if self.encdec.decrypt(&record.cc_number_enc, "cc_number")? == incoming_cc_number {
+                return Ok(Some(record));
+            }
+        }
+        Ok(None)
+    }
+
+    fn update_local_record(
+        &self,
+        tx: &Transaction<'_>,
+        new_record: Self::Record,
+        flag_as_changed: bool,
+    ) -> Result<()> {
+        update_internal_credit_card(tx, &new_record, flag_as_changed)?;
+        Ok(())
+    }
+
+    fn insert_local_record(&self, tx: &Transaction<'_>, new_record: Self::Record) -> Result<()> {
+        add_internal_credit_card(tx, &new_record)?;
+        Ok(())
+    }
+
+    /// Changes the guid of the local record for the given `old_guid` to the given `new_guid` used
+    /// for the `HasLocalDupe` incoming state, and mark the item as dirty.
+    /// We also update the mirror record if it exists in forking scenarios
+    fn change_record_guid(
+        &self,
+        tx: &Transaction<'_>,
+        old_guid: &SyncGuid,
+        new_guid: &SyncGuid,
+    ) -> Result<()> {
+        common_change_guid(
+            tx,
+            "credit_cards_data",
+            "credit_cards_mirror",
+            old_guid,
+            new_guid,
+        )
+    }
+
+    fn remove_record(&self, tx: &Transaction<'_>, guid: &SyncGuid) -> Result<()> {
+        common_remove_record(tx, "credit_cards_data", guid)
+    }
+
+    fn remove_tombstone(&self, tx: &Transaction<'_>, guid: &SyncGuid) -> Result<()> {
+        common_remove_record(tx, "credit_cards_tombstones", guid)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::super::super::test::new_syncable_mem_db;
+    use super::*;
+    use crate::db::credit_cards::get_credit_card;
+    use crate::sync::common::tests::*;
+
+    use interrupt_support::NeverInterrupts;
+    use serde_json::{json, Map, Value};
+    use sql_support::ConnExt;
+
+    lazy_static::lazy_static! {
+        static ref TEST_JSON_RECORDS: Map<String, Value> = {
+            // NOTE: the JSON here is the same as stored on the sync server -
+            // the superfluous `entry` is unfortunate but from desktop.
+            let val = json! {{
+                "A" : {
+                    "id": expand_test_guid('A'),
+                    "entry": {
+                        "cc-name": "Mr Me A Person",
+                        "cc-number": "1234567812345678",
+                        "cc-exp_month": 12,
+                        "cc-exp_year": 2021,
+                        "cc-type": "Cash!",
+                        "version": 3,
+                    }
+                },
+                "C" : {
+                    "id": expand_test_guid('C'),
+                    "entry": {
+                        "cc-name": "Mr Me Another Person",
+                        "cc-number": "8765432112345678",
+                        "cc-exp-month": 1,
+                        "cc-exp-year": 2020,
+                        "cc-type": "visa",
+                        "timeCreated": 0,
+                        "timeLastUsed": 0,
+                        "timeLastModified": 0,
+                        "timesUsed": 0,
+                        "version": 3,
+                    }
+                },
+                "D" : {
+                    "id": expand_test_guid('D'),
+                    "entry": {
+                        "cc-name": "Mr Me Another Person",
+                        "cc-number": "8765432112345678",
+                        "cc-exp-month": 1,
+                        "cc-exp-year": 2020,
+                        "cc-type": "visa",
+                        "timeCreated": 0,
+                        "timeLastUsed": 0,
+                        "timeLastModified": 0,
+                        "timesUsed": 0,
+                        "version": 3,
+                        "foo": "bar",
+                        "baz": "qux",
+                    }
+                }
+            }};
+            val.as_object().expect("literal is an object").clone()
+        };
+    }
+
+    fn test_json_record(guid_prefix: char) -> Value {
+        TEST_JSON_RECORDS
+            .get(&guid_prefix.to_string())
+            .expect("should exist")
+            .clone()
+    }
+
+    fn test_record(guid_prefix: char, encdec: &EncryptorDecryptor) -> InternalCreditCard {
+        let json = test_json_record(guid_prefix);
+        let payload = serde_json::from_value(json).unwrap();
+        InternalCreditCard::from_payload(payload, encdec).expect("should be valid")
+    }
+
+    #[test]
+    fn test_stage_incoming() -> Result<()> {
+        let _ = env_logger::try_init();
+        let mut db = new_syncable_mem_db();
+        struct TestCase {
+            incoming_records: Vec<Value>,
+            mirror_records: Vec<Value>,
+            expected_record_count: usize,
+            expected_tombstone_count: usize,
+        }
+
+        let test_cases = vec![
+            TestCase {
+                incoming_records: vec![test_json_record('A')],
+                mirror_records: vec![],
+                expected_record_count: 1,
+                expected_tombstone_count: 0,
+            },
+            TestCase {
+                incoming_records: vec![test_json_tombstone('A')],
+                mirror_records: vec![],
+                expected_record_count: 0,
+                expected_tombstone_count: 1,
+            },
+            TestCase {
+                incoming_records: vec![
+                    test_json_record('A'),
+                    test_json_record('C'),
+                    test_json_tombstone('B'),
+                ],
+                mirror_records: vec![],
+                expected_record_count: 2,
+                expected_tombstone_count: 1,
+            },
+            // incoming tombstone with existing tombstone in the mirror
+            TestCase {
+                incoming_records: vec![test_json_tombstone('B')],
+                mirror_records: vec![test_json_tombstone('B')],
+                expected_record_count: 0,
+                expected_tombstone_count: 1,
+            },
+        ];
+
+        for tc in test_cases {
+            log::info!("starting new testcase");
+            let tx = db.transaction().unwrap();
+            let encdec = EncryptorDecryptor::new_with_random_key().unwrap();
+
+            // Add required items to the mirrors.
+            let mirror_sql = "INSERT OR REPLACE INTO credit_cards_mirror (guid, payload)
+                              VALUES (:guid, :payload)";
+            for payload in tc.mirror_records {
+                tx.execute(
+                    mirror_sql,
+                    rusqlite::named_params! {
+                        ":guid": payload["id"].as_str().unwrap(),
+                        ":payload": encdec.encrypt(&payload.to_string(), "payload")?,
+                    },
+                )
+                .expect("should insert mirror record");
+            }
+
+            let ri = IncomingCreditCardsImpl { encdec };
+            ri.stage_incoming(
+                &tx,
+                array_to_incoming(tc.incoming_records),
+                &NeverInterrupts,
+            )?;
+
+            let records = tx.conn().query_rows_and_then(
+                "SELECT * FROM temp.credit_cards_sync_staging;",
+                [],
+                |row| -> Result<IncomingContent<InternalCreditCard>> {
+                    let guid: SyncGuid = row.get_unwrap("guid");
+                    let enc_payload: String = row.get_unwrap("payload");
+                    raw_payload_to_incoming(guid, enc_payload, &ri.encdec)
+                },
+            )?;
+
+            let record_count = records
+                .iter()
+                .filter(|p| !matches!(p.kind, IncomingKind::Tombstone))
+                .count();
+            let tombstone_count = records.len() - record_count;
+            log::trace!("record count: {record_count}, tombstone count: {tombstone_count}");
+
+            assert_eq!(record_count, tc.expected_record_count);
+            assert_eq!(tombstone_count, tc.expected_tombstone_count);
+
+            ri.fetch_incoming_states(&tx)?;
+
+            tx.execute("DELETE FROM temp.credit_cards_sync_staging;", [])?;
+        }
+        Ok(())
+    }
+
+    #[test]
+    fn test_change_record_guid() -> Result<()> {
+        let mut db = new_syncable_mem_db();
+        let tx = db.transaction()?;
+        let ri = IncomingCreditCardsImpl {
+            encdec: EncryptorDecryptor::new_with_random_key().unwrap(),
+        };
+
+        ri.insert_local_record(&tx, test_record('C', &ri.encdec))?;
+
+        ri.change_record_guid(
+            &tx,
+            &SyncGuid::new(&expand_test_guid('C')),
+            &SyncGuid::new(&expand_test_guid('B')),
+        )?;
+        tx.commit()?;
+        assert!(get_credit_card(&db.writer, &expand_test_guid('C').into()).is_err());
+        assert!(get_credit_card(&db.writer, &expand_test_guid('B').into()).is_ok());
+        Ok(())
+    }
+
+    #[test]
+    fn test_get_incoming() {
+        let mut db = new_syncable_mem_db();
+        let tx = db.transaction().expect("should get tx");
+        let ci = IncomingCreditCardsImpl {
+            encdec: EncryptorDecryptor::new_with_random_key().unwrap(),
+        };
+        let record = test_record('C', &ci.encdec);
+        let bso = record
+            .clone()
+            .into_test_incoming_bso(&ci.encdec, Default::default());
+        do_test_incoming_same(&ci, &tx, record, bso);
+    }
+
+    #[test]
+    fn test_incoming_tombstone() {
+        let mut db = new_syncable_mem_db();
+        let tx = db.transaction().expect("should get tx");
+        let ci = IncomingCreditCardsImpl {
+            encdec: EncryptorDecryptor::new_with_random_key().unwrap(),
+        };
+        do_test_incoming_tombstone(&ci, &tx, test_record('C', &ci.encdec));
+    }
+
+    #[test]
+    fn test_local_data_scrubbed() {
+        let mut db = new_syncable_mem_db();
+        let tx = db.transaction().expect("should get tx");
+        let ci = IncomingCreditCardsImpl {
+            encdec: EncryptorDecryptor::new_with_random_key().unwrap(),
+        };
+        let mut scrubbed_record = test_record('A', &ci.encdec);
+        let bso = scrubbed_record
+            .clone()
+            .into_test_incoming_bso(&ci.encdec, Default::default());
+        scrubbed_record.cc_number_enc = "".to_string();
+        do_test_scrubbed_local_data(&ci, &tx, scrubbed_record, bso);
+    }
+
+    #[test]
+    fn test_staged_to_mirror() {
+        let mut db = new_syncable_mem_db();
+        let tx = db.transaction().expect("should get tx");
+        let ci = IncomingCreditCardsImpl {
+            encdec: EncryptorDecryptor::new_with_random_key().unwrap(),
+        };
+        let record = test_record('C', &ci.encdec);
+        let bso = record
+            .clone()
+            .into_test_incoming_bso(&ci.encdec, Default::default());
+        do_test_staged_to_mirror(&ci, &tx, record, bso, "credit_cards_mirror");
+    }
+
+    #[test]
+    fn test_find_dupe() {
+        let mut db = new_syncable_mem_db();
+        let tx = db.transaction().expect("should get tx");
+        let encdec = EncryptorDecryptor::new_with_random_key().unwrap();
+        let ci = IncomingCreditCardsImpl { encdec };
+        let local_record = test_record('C', &ci.encdec);
+        let local_guid = local_record.guid.clone();
+        ci.insert_local_record(&tx, local_record.clone()).unwrap();
+
+        // Now the same record incoming - it should find the one we just added
+        // above as a dupe.
+        let mut incoming_record = test_record('C', &ci.encdec);
+        // sanity check that the encrypted numbers are different even though
+        // the decrypted numbers are identical.
+        assert_ne!(local_record.cc_number_enc, incoming_record.cc_number_enc);
+        // but the other fields the sql checks are
+        assert_eq!(local_record.cc_name, incoming_record.cc_name);
+        assert_eq!(
+            local_record.cc_number_last_4,
+            incoming_record.cc_number_last_4
+        );
+        assert_eq!(local_record.cc_exp_month, incoming_record.cc_exp_month);
+        assert_eq!(local_record.cc_exp_year, incoming_record.cc_exp_year);
+        assert_eq!(local_record.cc_type, incoming_record.cc_type);
+        // change the incoming guid so we don't immediately think they are the same.
+        incoming_record.guid = SyncGuid::random();
+
+        // expect `Ok(Some(record))`
+        let dupe = ci.get_local_dupe(&tx, &incoming_record).unwrap().unwrap();
+        assert_eq!(dupe.guid, local_guid);
+    }
+
+    // largely the same test as above, but going through the entire plan + apply
+    // cycle.
+    #[test]
+    fn test_find_dupe_applied() {
+        let mut db = new_syncable_mem_db();
+        let tx = db.transaction().expect("should get tx");
+        let encdec = EncryptorDecryptor::new_with_random_key().unwrap();
+        let ci = IncomingCreditCardsImpl { encdec };
+        let local_record = test_record('C', &ci.encdec);
+        let local_guid = local_record.guid.clone();
+        ci.insert_local_record(&tx, local_record.clone()).unwrap();
+
+        // Now the same record incoming, but with a different guid. It should
+        // find the local one we just added above as a dupe.
+        let incoming_guid = SyncGuid::new(&expand_test_guid('I'));
+        let mut incoming = local_record;
+        incoming.guid = incoming_guid.clone();
+
+        let incoming_state = IncomingState {
+            incoming: IncomingContent {
+                envelope: IncomingEnvelope {
+                    id: incoming_guid.clone(),
+                    modified: ServerTimestamp::default(),
+                    sortindex: None,
+                    ttl: None,
+                },
+                kind: IncomingKind::Content(incoming),
+            },
+            // LocalRecordInfo::Missing because we don't have a local record with
+            // the incoming GUID.
+            local: LocalRecordInfo::Missing,
+            mirror: None,
+        };
+
+        let incoming_action =
+            crate::sync::plan_incoming(&ci, &tx, incoming_state).expect("should get action");
+        // We should have found the local as a dupe.
+        assert!(
+            matches!(incoming_action, crate::sync::IncomingAction::UpdateLocalGuid { ref old_guid, record: ref incoming } if *old_guid == local_guid && incoming.guid == incoming_guid)
+        );
+
+        // and apply it.
+        crate::sync::apply_incoming_action(&ci, &tx, incoming_action).expect("should apply");
+
+        // and the local record should now have the incoming guid.
+        tx.commit().expect("should commit");
+        assert!(get_credit_card(&db.writer, &local_guid).is_err());
+        assert!(get_credit_card(&db.writer, &incoming_guid).is_ok());
+    }
+
+    #[test]
+    fn test_get_incoming_unknown_fields() {
+        let json = test_json_record('D');
+        let cc_payload = serde_json::from_value::<CreditCardPayload>(json).unwrap();
+        // The incoming payload should've correctly deserialized any unknown_fields into a Map<String,Value>
+        assert_eq!(cc_payload.entry.unknown_fields.len(), 2);
+        assert_eq!(
+            cc_payload
+                .entry
+                .unknown_fields
+                .get("foo")
+                .unwrap()
+                .as_str()
+                .unwrap(),
+            "bar"
+        );
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/autofill/sync/credit_card/mod.rs.html b/book/rust-docs/src/autofill/sync/credit_card/mod.rs.html new file mode 100644 index 0000000000..c5235106cd --- /dev/null +++ b/book/rust-docs/src/autofill/sync/credit_card/mod.rs.html @@ -0,0 +1,599 @@ +mod.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/.
+*/
+
+pub mod incoming;
+pub mod outgoing;
+
+use super::engine::{ConfigSyncEngine, EngineConfig, SyncEngineStorageImpl};
+use super::{
+    MergeResult, Metadata, ProcessIncomingRecordImpl, ProcessOutgoingRecordImpl, SyncRecord,
+    UnknownFields,
+};
+use crate::db::models::credit_card::InternalCreditCard;
+use crate::encryption::EncryptorDecryptor;
+use crate::error::*;
+use crate::sync_merge_field_check;
+use incoming::IncomingCreditCardsImpl;
+use outgoing::OutgoingCreditCardsImpl;
+use rusqlite::Transaction;
+use serde::{Deserialize, Serialize};
+use std::sync::Arc;
+use sync_guid::Guid;
+use types::Timestamp;
+
+// The engine.
+pub(crate) fn create_engine(store: Arc<crate::Store>) -> ConfigSyncEngine<InternalCreditCard> {
+    ConfigSyncEngine::new(
+        EngineConfig {
+            namespace: "credit_cards".to_string(),
+            collection: "creditcards".into(),
+        },
+        store,
+        Box::new(CreditCardsEngineStorageImpl {}),
+    )
+}
+
+pub(super) struct CreditCardsEngineStorageImpl {}
+
+impl SyncEngineStorageImpl<InternalCreditCard> for CreditCardsEngineStorageImpl {
+    fn get_incoming_impl(
+        &self,
+        enc_key: &Option<String>,
+    ) -> Result<Box<dyn ProcessIncomingRecordImpl<Record = InternalCreditCard>>> {
+        let enc_key = match enc_key {
+            None => return Err(Error::MissingEncryptionKey),
+            Some(enc_key) => enc_key,
+        };
+        let encdec = EncryptorDecryptor::new(enc_key)?;
+        Ok(Box::new(IncomingCreditCardsImpl { encdec }))
+    }
+
+    fn reset_storage(&self, tx: &Transaction<'_>) -> Result<()> {
+        tx.execute_batch(
+            "DELETE FROM credit_cards_mirror;
+            DELETE FROM credit_cards_tombstones;",
+        )?;
+        Ok(())
+    }
+
+    fn get_outgoing_impl(
+        &self,
+        enc_key: &Option<String>,
+    ) -> Result<Box<dyn ProcessOutgoingRecordImpl<Record = InternalCreditCard>>> {
+        let enc_key = match enc_key {
+            None => return Err(Error::MissingEncryptionKey),
+            Some(enc_key) => enc_key,
+        };
+        let encdec = EncryptorDecryptor::new(enc_key)?;
+        Ok(Box::new(OutgoingCreditCardsImpl { encdec }))
+    }
+}
+
+// These structs are a representation of what's stored on the sync server for non-tombstone records.
+// (The actual server doesn't have `id` in the payload but instead in the envelope)
+#[derive(Default, Debug, Deserialize, Serialize)]
+pub(crate) struct CreditCardPayload {
+    id: Guid,
+
+    // For some historical reason and unlike most other sync records, creditcards
+    // are serialized with this explicit 'entry' object.
+    pub(super) entry: PayloadEntry,
+}
+
+// Note that the sync payload contains the "unencrypted" cc_number - but our
+// internal structs have the cc_number_enc/cc_number_last_4 pair, so we need to
+// take care going to and from.
+// (The scare-quotes around "unencrypted" are to reflect that, obviously, the
+// payload itself *is* encrypted by sync, but the number is plain-text in that
+// payload)
+#[derive(Default, Debug, Deserialize, Serialize)]
+#[serde(default, rename_all = "kebab-case")]
+pub(super) struct PayloadEntry {
+    pub cc_name: String,
+    pub cc_number: String,
+    pub cc_exp_month: i64,
+    pub cc_exp_year: i64,
+    pub cc_type: String,
+    // metadata (which isn't kebab-case for some historical reason...)
+    #[serde(rename = "timeCreated")]
+    pub time_created: Timestamp,
+    #[serde(rename = "timeLastUsed")]
+    pub time_last_used: Timestamp,
+    #[serde(rename = "timeLastModified")]
+    pub time_last_modified: Timestamp,
+    #[serde(rename = "timesUsed")]
+    pub times_used: i64,
+    pub version: u32, // always 3 for credit-cards
+    // Fields that the current schema did not expect, we store them only internally
+    // to round-trip them back to sync without processing them in any way
+    #[serde(flatten)]
+    pub unknown_fields: UnknownFields,
+}
+
+impl InternalCreditCard {
+    fn from_payload(p: CreditCardPayload, encdec: &EncryptorDecryptor) -> Result<Self> {
+        if p.entry.version != 3 {
+            // when new versions are introduced we will start accepting and
+            // converting old ones - but 3 is the lowest we support.
+            return Err(Error::InvalidSyncPayload(format!(
+                "invalid version - {}",
+                p.entry.version
+            )));
+        }
+        // need to encrypt the cleartext in the sync record.
+        let cc_number_enc = encdec.encrypt(&p.entry.cc_number, "cc_number")?;
+        let cc_number_last_4 = get_last_4(&p.entry.cc_number);
+
+        Ok(InternalCreditCard {
+            guid: p.id,
+            cc_name: p.entry.cc_name,
+            cc_number_enc,
+            cc_number_last_4,
+            cc_exp_month: p.entry.cc_exp_month,
+            cc_exp_year: p.entry.cc_exp_year,
+            cc_type: p.entry.cc_type,
+            metadata: Metadata {
+                time_created: p.entry.time_created,
+                time_last_used: p.entry.time_last_used,
+                time_last_modified: p.entry.time_last_modified,
+                times_used: p.entry.times_used,
+                sync_change_counter: 0,
+            },
+        })
+    }
+
+    pub(crate) fn into_payload(self, encdec: &EncryptorDecryptor) -> Result<CreditCardPayload> {
+        let cc_number = encdec.decrypt(&self.cc_number_enc, "cc_number")?;
+        Ok(CreditCardPayload {
+            id: self.guid,
+            entry: PayloadEntry {
+                cc_name: self.cc_name,
+                cc_number,
+                cc_exp_month: self.cc_exp_month,
+                cc_exp_year: self.cc_exp_year,
+                cc_type: self.cc_type,
+                time_created: self.metadata.time_created,
+                time_last_used: self.metadata.time_last_used,
+                time_last_modified: self.metadata.time_last_modified,
+                times_used: self.metadata.times_used,
+                version: 3,
+                unknown_fields: Default::default(),
+            },
+        })
+    }
+}
+
+impl SyncRecord for InternalCreditCard {
+    fn record_name() -> &'static str {
+        "CreditCard"
+    }
+
+    fn id(&self) -> &Guid {
+        &self.guid
+    }
+
+    fn metadata(&self) -> &Metadata {
+        &self.metadata
+    }
+
+    fn metadata_mut(&mut self) -> &mut Metadata {
+        &mut self.metadata
+    }
+
+    /// Performs a three-way merge between an incoming, local, and mirror record.
+    /// If a merge cannot be successfully completed (ie, if we find the same
+    /// field has changed both locally and remotely since the last sync), the
+    /// local record data is returned with a new guid and updated sync metadata.
+    /// Note that mirror being None is an edge-case and typically means first
+    /// sync since a "reset" (eg, disconnecting and reconnecting.
+    #[allow(clippy::cognitive_complexity)] // Looks like clippy considers this after macro-expansion...
+    fn merge(incoming: &Self, local: &Self, mirror: &Option<Self>) -> MergeResult<Self> {
+        let mut merged_record: Self = Default::default();
+        // guids must be identical
+        assert_eq!(incoming.guid, local.guid);
+
+        match mirror {
+            Some(m) => assert_eq!(incoming.guid, m.guid),
+            None => {}
+        };
+
+        merged_record.guid = incoming.guid.clone();
+
+        sync_merge_field_check!(cc_name, incoming, local, mirror, merged_record);
+        // XXX - It looks like this will allow us to merge a locally changed
+        // cc_number_enc and remotely changed cc_number_last_4, which is nonsensical.
+        // Given sync itself is populating this it needs more thought.
+        sync_merge_field_check!(cc_number_enc, incoming, local, mirror, merged_record);
+        sync_merge_field_check!(cc_number_last_4, incoming, local, mirror, merged_record);
+        sync_merge_field_check!(cc_exp_month, incoming, local, mirror, merged_record);
+        sync_merge_field_check!(cc_exp_year, incoming, local, mirror, merged_record);
+        sync_merge_field_check!(cc_type, incoming, local, mirror, merged_record);
+
+        merged_record.metadata = incoming.metadata;
+        merged_record
+            .metadata
+            .merge(&local.metadata, mirror.as_ref().map(|m| m.metadata()));
+
+        MergeResult::Merged {
+            merged: merged_record,
+        }
+    }
+}
+
+/// Returns a with the given local record's data but with a new guid and
+/// fresh sync metadata.
+fn get_forked_record(local_record: InternalCreditCard) -> InternalCreditCard {
+    let mut local_record_data = local_record;
+    local_record_data.guid = Guid::random();
+    local_record_data.metadata.time_created = Timestamp::now();
+    local_record_data.metadata.time_last_used = Timestamp::now();
+    local_record_data.metadata.time_last_modified = Timestamp::now();
+    local_record_data.metadata.times_used = 0;
+    local_record_data.metadata.sync_change_counter = 1;
+
+    local_record_data
+}
+
+// Wow - strings are hard! credit-card sync is the only thing that needs to
+// get the last 4 chars of a string.
+fn get_last_4(v: &str) -> String {
+    v.chars()
+        .rev()
+        .take(4)
+        .collect::<Vec<_>>()
+        .into_iter()
+        .rev()
+        .collect::<String>()
+}
+#[test]
+fn test_last_4() {
+    assert_eq!(get_last_4("testing"), "ting".to_string());
+    assert_eq!(get_last_4("abc"), "abc".to_string());
+    assert_eq!(get_last_4(""), "".to_string());
+}
+
+#[test]
+fn test_to_from_payload() {
+    let key = crate::encryption::create_autofill_key().unwrap();
+    let cc_number = "1234567812345678";
+    let cc_number_enc =
+        crate::encryption::encrypt_string(key.clone(), cc_number.to_string()).unwrap();
+    let cc = InternalCreditCard {
+        cc_name: "Shaggy".to_string(),
+        cc_number_enc,
+        cc_number_last_4: "5678".to_string(),
+        cc_exp_month: 12,
+        cc_exp_year: 2021,
+        cc_type: "foo".to_string(),
+        ..Default::default()
+    };
+    let encdec = EncryptorDecryptor::new(&key).unwrap();
+    let payload: CreditCardPayload = cc.clone().into_payload(&encdec).unwrap();
+
+    assert_eq!(payload.id, cc.guid);
+    assert_eq!(payload.entry.cc_name, "Shaggy".to_string());
+    assert_eq!(payload.entry.cc_number, cc_number.to_string());
+    assert_eq!(payload.entry.cc_exp_month, 12);
+    assert_eq!(payload.entry.cc_exp_year, 2021);
+    assert_eq!(payload.entry.cc_type, "foo".to_string());
+
+    // and back.
+    let cc2 = InternalCreditCard::from_payload(payload, &encdec).unwrap();
+    // sadly we can't just check equality because the encrypted value will be
+    // different even if the card number is identical.
+    assert_eq!(cc2.guid, cc.guid);
+    assert_eq!(cc2.cc_name, "Shaggy".to_string());
+    assert_eq!(cc2.cc_number_last_4, cc.cc_number_last_4);
+    assert_eq!(cc2.cc_exp_month, cc.cc_exp_month);
+    assert_eq!(cc2.cc_exp_year, cc.cc_exp_year);
+    assert_eq!(cc2.cc_type, cc.cc_type);
+    // The decrypted number should be the same.
+    assert_eq!(
+        crate::encryption::decrypt_string(key, cc2.cc_number_enc.clone()).unwrap(),
+        cc_number
+    );
+    // But the encrypted value should not.
+    assert_ne!(cc2.cc_number_enc, cc.cc_number_enc);
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/autofill/sync/credit_card/outgoing.rs.html b/book/rust-docs/src/autofill/sync/credit_card/outgoing.rs.html new file mode 100644 index 0000000000..1ab74dc7aa --- /dev/null +++ b/book/rust-docs/src/autofill/sync/credit_card/outgoing.rs.html @@ -0,0 +1,657 @@ +outgoing.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/.
+*/
+
+use crate::db::models::credit_card::InternalCreditCard;
+use crate::db::schema::CREDIT_CARD_COMMON_COLS;
+use crate::encryption::EncryptorDecryptor;
+use crate::error::*;
+use crate::sync::common::*;
+use crate::sync::{credit_card::CreditCardPayload, OutgoingBso, ProcessOutgoingRecordImpl};
+use rusqlite::{Row, Transaction};
+use sync_guid::Guid as SyncGuid;
+
+const DATA_TABLE_NAME: &str = "credit_cards_data";
+const MIRROR_TABLE_NAME: &str = "credit_cards_mirror";
+const STAGING_TABLE_NAME: &str = "credit_cards_sync_outgoing_staging";
+
+pub(super) struct OutgoingCreditCardsImpl {
+    pub(super) encdec: EncryptorDecryptor,
+}
+
+impl ProcessOutgoingRecordImpl for OutgoingCreditCardsImpl {
+    type Record = InternalCreditCard;
+
+    /// Gets the local records that have unsynced changes or don't have corresponding mirror
+    /// records and upserts them to the mirror table
+    fn fetch_outgoing_records(&self, tx: &Transaction<'_>) -> anyhow::Result<Vec<OutgoingBso>> {
+        let data_sql = format!(
+            "SELECT
+                l.{common_cols},
+                m.payload,
+                l.sync_change_counter
+            FROM credit_cards_data l
+            LEFT JOIN credit_cards_mirror m
+            ON l.guid = m.guid
+            WHERE sync_change_counter > 0
+                OR l.guid NOT IN (
+                    SELECT m.guid
+                    FROM credit_cards_mirror m
+                )",
+            common_cols = CREDIT_CARD_COMMON_COLS,
+        );
+        let record_from_data_row: &dyn Fn(&Row<'_>) -> Result<(OutgoingBso, i64)> = &|row| {
+            let mut record = InternalCreditCard::from_row(row)?.into_payload(&self.encdec)?;
+            // If the server had unknown fields we fetch it and add it to the record
+            if let Some(enc_s) = row.get::<_, Option<String>>("payload")? {
+                // The full payload in the credit cards mirror is encrypted
+                let mirror_payload: CreditCardPayload =
+                    serde_json::from_str(&self.encdec.decrypt(&enc_s, "cc payload")?)?;
+                record.entry.unknown_fields = mirror_payload.entry.unknown_fields;
+            };
+
+            Ok((
+                OutgoingBso::from_content_with_id(record)?,
+                row.get::<_, i64>("sync_change_counter")?,
+            ))
+        };
+
+        let tombstones_sql = "SELECT guid FROM credit_cards_tombstones";
+
+        // save outgoing records to the mirror table
+        let staging_records = common_get_outgoing_staging_records(
+            tx,
+            &data_sql,
+            tombstones_sql,
+            record_from_data_row,
+        )?
+        .into_iter()
+        .map(|(bso, change_counter)| {
+            // Turn the record into an encrypted repr to save in the mirror.
+            let encrypted = self.encdec.encrypt(&bso.payload, "bso payload")?;
+            Ok((bso.envelope.id, encrypted, change_counter))
+        })
+        .collect::<Result<_>>()?;
+        common_save_outgoing_records(tx, STAGING_TABLE_NAME, staging_records)?;
+
+        // return outgoing changes
+        Ok(
+            common_get_outgoing_records(tx, &data_sql, tombstones_sql, record_from_data_row)?
+                .into_iter()
+                .map(|(bso, _change_counter)| bso)
+                .collect::<Vec<OutgoingBso>>(),
+        )
+    }
+
+    fn finish_synced_items(
+        &self,
+        tx: &Transaction<'_>,
+        records_synced: Vec<SyncGuid>,
+    ) -> anyhow::Result<()> {
+        common_finish_synced_items(
+            tx,
+            DATA_TABLE_NAME,
+            MIRROR_TABLE_NAME,
+            STAGING_TABLE_NAME,
+            records_synced,
+        )?;
+
+        Ok(())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::db::credit_cards::{add_internal_credit_card, tests::test_insert_mirror_record};
+    use crate::sync::{common::tests::*, test::new_syncable_mem_db, UnknownFields};
+    use serde_json::{json, Map, Value};
+    use types::Timestamp;
+
+    lazy_static::lazy_static! {
+        static ref TEST_JSON_RECORDS: Map<String, Value> = {
+            // NOTE: the JSON here is the same as stored on the sync server -
+            // the superfluous `entry` is unfortunate but from desktop.
+            let val = json! {{
+                "C" : {
+                    "id": expand_test_guid('C'),
+                    "entry": {
+                        "cc-name": "Mr Me Another Person",
+                        "cc-number": "8765432112345678",
+                        "cc-exp-month": 1,
+                        "cc-exp-year": 2020,
+                        "cc-type": "visa",
+                        "timeCreated": 0,
+                        "timeLastUsed": 0,
+                        "timeLastModified": 0,
+                        "timesUsed": 0,
+                        "version": 3,
+                    }
+                },
+                "D" : {
+                    "id": expand_test_guid('D'),
+                    "entry": {
+                        "cc-name": "Mr Me Another Person",
+                        "cc-number": "8765432112345678",
+                        "cc-exp-month": 1,
+                        "cc-exp-year": 2020,
+                        "cc-type": "visa",
+                        "timeCreated": 0,
+                        "timeLastUsed": 0,
+                        "timeLastModified": 0,
+                        "timesUsed": 0,
+                        "version": 3,
+                        // Fields we don't understand from the server
+                        "foo": "bar",
+                        "baz": "qux",
+                    }
+                }
+            }};
+            val.as_object().expect("literal is an object").clone()
+        };
+    }
+
+    fn test_json_record(guid_prefix: char) -> Value {
+        TEST_JSON_RECORDS
+            .get(&guid_prefix.to_string())
+            .expect("should exist")
+            .clone()
+    }
+
+    fn test_record(guid_prefix: char, encdec: &EncryptorDecryptor) -> InternalCreditCard {
+        let json = test_json_record(guid_prefix);
+        let payload = serde_json::from_value(json).unwrap();
+        InternalCreditCard::from_payload(payload, encdec).expect("should be valid")
+    }
+
+    #[test]
+    fn test_outgoing_never_synced() {
+        let mut db = new_syncable_mem_db();
+        let tx = db.transaction().expect("should get tx");
+        let co = OutgoingCreditCardsImpl {
+            encdec: EncryptorDecryptor::new_with_random_key().unwrap(),
+        };
+        let test_record = test_record('C', &co.encdec);
+
+        // create date record
+        assert!(add_internal_credit_card(&tx, &test_record).is_ok());
+        do_test_outgoing_never_synced(
+            &tx,
+            &co,
+            &test_record.guid,
+            DATA_TABLE_NAME,
+            MIRROR_TABLE_NAME,
+            STAGING_TABLE_NAME,
+        );
+    }
+
+    #[test]
+    fn test_outgoing_tombstone() {
+        let mut db = new_syncable_mem_db();
+        let tx = db.transaction().expect("should get tx");
+        let co = OutgoingCreditCardsImpl {
+            encdec: EncryptorDecryptor::new_with_random_key().unwrap(),
+        };
+        let test_record = test_record('C', &co.encdec);
+
+        // create tombstone record
+        assert!(tx
+            .execute(
+                "INSERT INTO credit_cards_tombstones (
+                    guid,
+                    time_deleted
+                ) VALUES (
+                    :guid,
+                    :time_deleted
+                )",
+                rusqlite::named_params! {
+                    ":guid": test_record.guid,
+                    ":time_deleted": Timestamp::now(),
+                },
+            )
+            .is_ok());
+        do_test_outgoing_tombstone(
+            &tx,
+            &co,
+            &test_record.guid,
+            DATA_TABLE_NAME,
+            MIRROR_TABLE_NAME,
+            STAGING_TABLE_NAME,
+        );
+    }
+
+    #[test]
+    fn test_outgoing_synced_with_local_change() {
+        let mut db = new_syncable_mem_db();
+        let tx = db.transaction().expect("should get tx");
+        let co = OutgoingCreditCardsImpl {
+            encdec: EncryptorDecryptor::new_with_random_key().unwrap(),
+        };
+
+        // create synced record with non-zero sync_change_counter
+        let mut test_record = test_record('C', &co.encdec);
+        let initial_change_counter_val = 2;
+        test_record.metadata.sync_change_counter = initial_change_counter_val;
+        assert!(add_internal_credit_card(&tx, &test_record).is_ok());
+        let guid = test_record.guid.clone();
+        //test_insert_mirror_record doesn't encrypt the mirror payload, but in reality we do
+        // so we encrypt here so our fetch_outgoing_records doesn't break
+        let mut bso = test_record.into_test_incoming_bso(&co.encdec, Default::default());
+        bso.payload = co.encdec.encrypt(&bso.payload, "bso payload").unwrap();
+        test_insert_mirror_record(&tx, bso);
+        exists_with_counter_value_in_table(&tx, DATA_TABLE_NAME, &guid, initial_change_counter_val);
+
+        do_test_outgoing_synced_with_local_change(
+            &tx,
+            &co,
+            &guid,
+            DATA_TABLE_NAME,
+            MIRROR_TABLE_NAME,
+            STAGING_TABLE_NAME,
+        );
+    }
+
+    #[test]
+    fn test_outgoing_synced_with_no_change() {
+        let mut db = new_syncable_mem_db();
+        let tx = db.transaction().expect("should get tx");
+        let co = OutgoingCreditCardsImpl {
+            encdec: EncryptorDecryptor::new_with_random_key().unwrap(),
+        };
+
+        // create synced record with no changes (sync_change_counter = 0)
+        let test_record = test_record('C', &co.encdec);
+        let guid = test_record.guid.clone();
+        assert!(add_internal_credit_card(&tx, &test_record).is_ok());
+        test_insert_mirror_record(
+            &tx,
+            test_record.into_test_incoming_bso(&co.encdec, Default::default()),
+        );
+
+        do_test_outgoing_synced_with_no_change(
+            &tx,
+            &co,
+            &guid,
+            DATA_TABLE_NAME,
+            STAGING_TABLE_NAME,
+        );
+    }
+
+    #[test]
+    fn test_outgoing_roundtrip_unknown() {
+        let mut db = new_syncable_mem_db();
+        let tx = db.transaction().expect("should get tx");
+        let co = OutgoingCreditCardsImpl {
+            encdec: EncryptorDecryptor::new_with_random_key().unwrap(),
+        };
+
+        // create synced record with non-zero sync_change_counter
+        let mut test_record = test_record('D', &co.encdec);
+        let initial_change_counter_val = 2;
+        test_record.metadata.sync_change_counter = initial_change_counter_val;
+        assert!(add_internal_credit_card(&tx, &test_record).is_ok());
+
+        let unknown_fields: UnknownFields =
+            serde_json::from_value(json! {{ "foo": "bar", "baz": "qux"}}).unwrap();
+
+        //test_insert_mirror_record doesn't encrypt the mirror payload, but in reality we do
+        // so we encrypt here so our fetch_outgoing_records doesn't break
+        let mut bso = test_record
+            .clone()
+            .into_test_incoming_bso(&co.encdec, unknown_fields);
+        bso.payload = co.encdec.encrypt(&bso.payload, "bso payload").unwrap();
+        test_insert_mirror_record(&tx, bso);
+        exists_with_counter_value_in_table(
+            &tx,
+            DATA_TABLE_NAME,
+            &test_record.guid,
+            initial_change_counter_val,
+        );
+
+        let outgoing = &co.fetch_outgoing_records(&tx).unwrap();
+        // Unknown fields are: {"foo": "bar", "baz": "qux"}
+        // Ensure we have our unknown values for the roundtrip
+        let bso_payload: Map<String, Value> = serde_json::from_str(&outgoing[0].payload).unwrap();
+        let entry = bso_payload.get("entry").unwrap();
+        assert_eq!(entry.get("foo").unwrap(), "bar");
+        assert_eq!(entry.get("baz").unwrap(), "qux");
+        do_test_outgoing_synced_with_local_change(
+            &tx,
+            &co,
+            &test_record.guid,
+            DATA_TABLE_NAME,
+            MIRROR_TABLE_NAME,
+            STAGING_TABLE_NAME,
+        );
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/autofill/sync/engine.rs.html b/book/rust-docs/src/autofill/sync/engine.rs.html new file mode 100644 index 0000000000..f6a4b94d96 --- /dev/null +++ b/book/rust-docs/src/autofill/sync/engine.rs.html @@ -0,0 +1,875 @@ +engine.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use super::{plan_incoming, ProcessIncomingRecordImpl, ProcessOutgoingRecordImpl, SyncRecord};
+use crate::error::*;
+use crate::Store;
+use rusqlite::{
+    types::{FromSql, ToSql},
+    Connection, Transaction,
+};
+use std::sync::Arc;
+use sync15::bso::{IncomingBso, OutgoingBso};
+use sync15::engine::{CollSyncIds, CollectionRequest, EngineSyncAssociation, SyncEngine};
+use sync15::{telemetry, CollectionName, ServerTimestamp};
+use sync_guid::Guid;
+
+// We have 2 engines in this crate and they are identical except for stuff
+// abstracted here!
+pub struct EngineConfig {
+    pub(crate) namespace: String,          // prefix for meta keys, etc.
+    pub(crate) collection: CollectionName, // collection name on the server.
+}
+
+// meta keys, will be prefixed by the "namespace"
+pub const LAST_SYNC_META_KEY: &str = "last_sync_time";
+pub const GLOBAL_SYNCID_META_KEY: &str = "global_sync_id";
+pub const COLLECTION_SYNCID_META_KEY: &str = "sync_id";
+
+// A trait to abstract the broader sync processes.
+pub trait SyncEngineStorageImpl<T> {
+    fn get_incoming_impl(
+        &self,
+        enc_key: &Option<String>,
+    ) -> Result<Box<dyn ProcessIncomingRecordImpl<Record = T>>>;
+    fn reset_storage(&self, conn: &Transaction<'_>) -> Result<()>;
+    fn get_outgoing_impl(
+        &self,
+        enc_key: &Option<String>,
+    ) -> Result<Box<dyn ProcessOutgoingRecordImpl<Record = T>>>;
+}
+
+// A sync engine that gets functionality from an EngineConfig.
+pub struct ConfigSyncEngine<T> {
+    pub(crate) config: EngineConfig,
+    pub(crate) store: Arc<Store>,
+    pub(crate) storage_impl: Box<dyn SyncEngineStorageImpl<T>>,
+    local_enc_key: Option<String>,
+}
+
+impl<T> ConfigSyncEngine<T> {
+    pub fn new(
+        config: EngineConfig,
+        store: Arc<Store>,
+        storage_impl: Box<dyn SyncEngineStorageImpl<T>>,
+    ) -> Self {
+        Self {
+            config,
+            store,
+            storage_impl,
+            local_enc_key: None,
+        }
+    }
+    fn put_meta(&self, conn: &Connection, tail: &str, value: &dyn ToSql) -> Result<()> {
+        let key = format!("{}.{}", self.config.namespace, tail);
+        crate::db::store::put_meta(conn, &key, value)
+    }
+    fn get_meta<V: FromSql>(&self, conn: &Connection, tail: &str) -> Result<Option<V>> {
+        let key = format!("{}.{}", self.config.namespace, tail);
+        crate::db::store::get_meta(conn, &key)
+    }
+    fn delete_meta(&self, conn: &Connection, tail: &str) -> Result<()> {
+        let key = format!("{}.{}", self.config.namespace, tail);
+        crate::db::store::delete_meta(conn, &key)
+    }
+    // Reset the local sync data so the next server request fetches all records.
+    pub fn reset_local_sync_data(&self) -> Result<()> {
+        let db = &self.store.db.lock().unwrap();
+        let tx = db.unchecked_transaction()?;
+        self.storage_impl.reset_storage(&tx)?;
+        self.put_meta(&tx, LAST_SYNC_META_KEY, &0)?;
+        tx.commit()?;
+        Ok(())
+    }
+}
+
+impl<T: SyncRecord + std::fmt::Debug> SyncEngine for ConfigSyncEngine<T> {
+    fn collection_name(&self) -> CollectionName {
+        self.config.collection.clone()
+    }
+
+    fn set_local_encryption_key(&mut self, key: &str) -> anyhow::Result<()> {
+        self.local_enc_key = Some(key.to_string());
+        Ok(())
+    }
+
+    fn prepare_for_sync(
+        &self,
+        _get_client_data: &dyn Fn() -> sync15::ClientData,
+    ) -> anyhow::Result<()> {
+        let db = &self.store.db.lock().unwrap();
+        let signal = db.begin_interrupt_scope()?;
+        crate::db::schema::create_empty_sync_temp_tables(&db.writer)?;
+        signal.err_if_interrupted()?;
+        Ok(())
+    }
+
+    fn stage_incoming(
+        &self,
+        inbound: Vec<IncomingBso>,
+        telem: &mut telemetry::Engine,
+    ) -> anyhow::Result<()> {
+        let db = &self.store.db.lock().unwrap();
+        let signal = db.begin_interrupt_scope()?;
+
+        // Stage all incoming items.
+        let mut incoming_telemetry = telemetry::EngineIncoming::new();
+        incoming_telemetry.applied(inbound.len() as u32);
+        telem.incoming(incoming_telemetry);
+        let tx = db.writer.unchecked_transaction()?;
+        let incoming_impl = self.storage_impl.get_incoming_impl(&self.local_enc_key)?;
+
+        incoming_impl.stage_incoming(&tx, inbound, &signal)?;
+        tx.commit()?;
+        Ok(())
+    }
+
+    fn apply(
+        &self,
+        timestamp: ServerTimestamp,
+        _telem: &mut telemetry::Engine,
+    ) -> anyhow::Result<Vec<OutgoingBso>> {
+        let db = &self.store.db.lock().unwrap();
+        let signal = db.begin_interrupt_scope()?;
+        let tx = db.writer.unchecked_transaction()?;
+        let incoming_impl = self.storage_impl.get_incoming_impl(&self.local_enc_key)?;
+        let outgoing_impl = self.storage_impl.get_outgoing_impl(&self.local_enc_key)?;
+
+        // Get "states" for each record...
+        for state in incoming_impl.fetch_incoming_states(&tx)? {
+            signal.err_if_interrupted()?;
+            // Finally get a "plan" and apply it.
+            let action = plan_incoming(&*incoming_impl, &tx, state)?;
+            super::apply_incoming_action(&*incoming_impl, &tx, action)?;
+        }
+
+        // write the timestamp now, so if we are interrupted merging or
+        // creating outgoing changesets we don't need to re-download the same
+        // records.
+        self.put_meta(&tx, LAST_SYNC_META_KEY, &timestamp.as_millis())?;
+
+        incoming_impl.finish_incoming(&tx)?;
+
+        // Finally, stage outgoing items.
+        let outgoing = outgoing_impl.fetch_outgoing_records(&tx)?;
+        // we're committing now because it may take a long time to actually perform the upload
+        // and we've already staged everything we need to complete the sync in a way that
+        // doesn't require the transaction to stay alive, so we commit now and start a new
+        // transaction once complete
+        tx.commit()?;
+        Ok(outgoing)
+    }
+
+    fn set_uploaded(&self, new_timestamp: ServerTimestamp, ids: Vec<Guid>) -> anyhow::Result<()> {
+        let db = &self.store.db.lock().unwrap();
+        self.put_meta(&db.writer, LAST_SYNC_META_KEY, &new_timestamp.as_millis())?;
+        let tx = db.writer.unchecked_transaction()?;
+        let outgoing_impl = self.storage_impl.get_outgoing_impl(&self.local_enc_key)?;
+        outgoing_impl.finish_synced_items(&tx, ids)?;
+        tx.commit()?;
+        Ok(())
+    }
+
+    fn get_collection_request(
+        &self,
+        server_timestamp: ServerTimestamp,
+    ) -> anyhow::Result<Option<CollectionRequest>> {
+        let db = &self.store.db.lock().unwrap();
+        let since = ServerTimestamp(
+            self.get_meta::<i64>(&db.writer, LAST_SYNC_META_KEY)?
+                .unwrap_or_default(),
+        );
+        Ok(if since == server_timestamp {
+            None
+        } else {
+            Some(
+                CollectionRequest::new(self.collection_name())
+                    .full()
+                    .newer_than(since),
+            )
+        })
+    }
+
+    fn get_sync_assoc(&self) -> anyhow::Result<EngineSyncAssociation> {
+        let db = &self.store.db.lock().unwrap();
+        let global = self.get_meta(&db.writer, GLOBAL_SYNCID_META_KEY)?;
+        let coll = self.get_meta(&db.writer, COLLECTION_SYNCID_META_KEY)?;
+        Ok(if let (Some(global), Some(coll)) = (global, coll) {
+            EngineSyncAssociation::Connected(CollSyncIds { global, coll })
+        } else {
+            EngineSyncAssociation::Disconnected
+        })
+    }
+
+    fn reset(&self, assoc: &EngineSyncAssociation) -> anyhow::Result<()> {
+        let db = &self.store.db.lock().unwrap();
+        let tx = db.unchecked_transaction()?;
+        self.storage_impl.reset_storage(&tx)?;
+        // Reset the last sync time, so that the next sync fetches fresh records
+        // from the server.
+        self.put_meta(&tx, LAST_SYNC_META_KEY, &0)?;
+
+        // Clear the sync ID if we're signing out, or set it to whatever the
+        // server gave us if we're signing in.
+        match assoc {
+            EngineSyncAssociation::Disconnected => {
+                self.delete_meta(&tx, GLOBAL_SYNCID_META_KEY)?;
+                self.delete_meta(&tx, COLLECTION_SYNCID_META_KEY)?;
+            }
+            EngineSyncAssociation::Connected(ids) => {
+                self.put_meta(&tx, GLOBAL_SYNCID_META_KEY, &ids.global)?;
+                self.put_meta(&tx, COLLECTION_SYNCID_META_KEY, &ids.coll)?;
+            }
+        }
+
+        tx.commit()?;
+        Ok(())
+    }
+
+    fn wipe(&self) -> anyhow::Result<()> {
+        log::warn!("not implemented as there isn't a valid use case for it");
+        Ok(())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::db::credit_cards::add_internal_credit_card;
+    use crate::db::credit_cards::tests::{
+        get_all, insert_tombstone_record, test_insert_mirror_record,
+    };
+    use crate::db::models::credit_card::InternalCreditCard;
+    use crate::db::schema::create_empty_sync_temp_tables;
+    use crate::encryption::EncryptorDecryptor;
+    use crate::sync::{IncomingBso, UnknownFields};
+    use sql_support::ConnExt;
+
+    impl InternalCreditCard {
+        pub fn into_test_incoming_bso(
+            self,
+            encdec: &EncryptorDecryptor,
+            unknown_fields: UnknownFields,
+        ) -> IncomingBso {
+            let mut payload = self.into_payload(encdec).expect("is json");
+            payload.entry.unknown_fields = unknown_fields;
+            IncomingBso::from_test_content(payload)
+        }
+    }
+
+    // We use the credit-card engine here.
+    fn create_engine() -> ConfigSyncEngine<InternalCreditCard> {
+        let store = crate::db::store::Store::new_memory();
+        crate::sync::credit_card::create_engine(Arc::new(store))
+    }
+
+    pub fn clear_cc_tables(conn: &Connection) -> rusqlite::Result<(), rusqlite::Error> {
+        conn.execute_all(&[
+            "DELETE FROM credit_cards_data;",
+            "DELETE FROM credit_cards_mirror;",
+            "DELETE FROM credit_cards_tombstones;",
+            "DELETE FROM moz_meta;",
+        ])
+    }
+
+    #[test]
+    fn test_credit_card_engine_apply_timestamp() -> Result<()> {
+        let mut credit_card_engine = create_engine();
+        let test_key = crate::encryption::create_autofill_key().unwrap();
+        credit_card_engine
+            .set_local_encryption_key(&test_key)
+            .unwrap();
+        {
+            create_empty_sync_temp_tables(&credit_card_engine.store.db.lock().unwrap())?;
+        }
+
+        let mut telem = telemetry::Engine::new("whatever");
+        let last_sync = 24;
+        let result = credit_card_engine.apply(ServerTimestamp::from_millis(last_sync), &mut telem);
+        assert!(result.is_ok());
+
+        // check that last sync metadata was set
+        let conn = &credit_card_engine.store.db.lock().unwrap().writer;
+
+        assert_eq!(
+            credit_card_engine.get_meta::<i64>(conn, LAST_SYNC_META_KEY)?,
+            Some(last_sync)
+        );
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_credit_card_engine_get_sync_assoc() -> Result<()> {
+        let credit_card_engine = create_engine();
+
+        let result = credit_card_engine.get_sync_assoc();
+        assert!(result.is_ok());
+
+        // check that we disconnect if sync IDs not found
+        assert_eq!(result.unwrap(), EngineSyncAssociation::Disconnected);
+
+        // create sync metadata
+        let global_guid = Guid::new("AAAA");
+        let coll_guid = Guid::new("AAAA");
+        let ids = CollSyncIds {
+            global: global_guid,
+            coll: coll_guid,
+        };
+        {
+            let conn = &credit_card_engine.store.db.lock().unwrap().writer;
+            credit_card_engine.put_meta(conn, GLOBAL_SYNCID_META_KEY, &ids.global)?;
+            credit_card_engine.put_meta(conn, COLLECTION_SYNCID_META_KEY, &ids.coll)?;
+        }
+
+        let result = credit_card_engine.get_sync_assoc();
+        assert!(result.is_ok());
+
+        // check that we return the metadata
+        assert_eq!(result.unwrap(), EngineSyncAssociation::Connected(ids));
+        Ok(())
+    }
+
+    #[test]
+    fn test_engine_sync_reset() -> Result<()> {
+        let engine = create_engine();
+        let encdec = EncryptorDecryptor::new_with_random_key().unwrap();
+
+        let cc = InternalCreditCard {
+            guid: Guid::random(),
+            cc_name: "Ms Jane Doe".to_string(),
+            cc_number_enc: encdec.encrypt("12341232412341234", "cc_number")?,
+            cc_number_last_4: "1234".to_string(),
+            cc_exp_month: 12,
+            cc_exp_year: 2021,
+            cc_type: "visa".to_string(),
+            ..Default::default()
+        };
+
+        {
+            // temp scope for the mutex lock.
+            let db = &engine.store.db.lock().unwrap();
+            let tx = db.writer.unchecked_transaction()?;
+            // create a normal record, a mirror record and a tombstone.
+            add_internal_credit_card(&tx, &cc)?;
+            test_insert_mirror_record(
+                &tx,
+                cc.clone()
+                    .into_test_incoming_bso(&encdec, Default::default()),
+            );
+            insert_tombstone_record(&tx, Guid::random().to_string())?;
+            tx.commit()?;
+        }
+
+        // create sync metadata
+        let global_guid = Guid::new("AAAA");
+        let coll_guid = Guid::new("AAAA");
+        let ids = CollSyncIds {
+            global: global_guid.clone(),
+            coll: coll_guid.clone(),
+        };
+        {
+            let conn = &engine.store.db.lock().unwrap().writer;
+            engine.put_meta(conn, GLOBAL_SYNCID_META_KEY, &ids.global)?;
+            engine.put_meta(conn, COLLECTION_SYNCID_META_KEY, &ids.coll)?;
+        }
+
+        // call reset for sign out
+        engine
+            .reset(&EngineSyncAssociation::Disconnected)
+            .expect("should work");
+
+        {
+            let conn = &engine.store.db.lock().unwrap().writer;
+
+            // check that the mirror and tombstone tables have no records
+            assert!(get_all(conn, "credit_cards_mirror".to_string())?.is_empty());
+            assert!(get_all(conn, "credit_cards_tombstones".to_string())?.is_empty());
+
+            // check that the last sync time was reset to 0
+            let expected_sync_time = 0;
+            assert_eq!(
+                engine
+                    .get_meta::<i64>(conn, LAST_SYNC_META_KEY)?
+                    .unwrap_or(1),
+                expected_sync_time
+            );
+
+            // check that the meta records were deleted
+            assert!(engine
+                .get_meta::<String>(conn, GLOBAL_SYNCID_META_KEY)?
+                .is_none());
+            assert!(engine
+                .get_meta::<String>(conn, COLLECTION_SYNCID_META_KEY)?
+                .is_none());
+
+            clear_cc_tables(conn)?;
+
+            // re-populating the tables
+            let tx = conn.unchecked_transaction()?;
+            add_internal_credit_card(&tx, &cc)?;
+            test_insert_mirror_record(&tx, cc.into_test_incoming_bso(&encdec, Default::default()));
+            insert_tombstone_record(&tx, Guid::random().to_string())?;
+            tx.commit()?;
+        }
+
+        // call reset for sign in
+        engine
+            .reset(&EngineSyncAssociation::Connected(ids))
+            .expect("should work");
+
+        let conn = &engine.store.db.lock().unwrap().writer;
+        // check that the meta records were set
+        let retrieved_global_sync_id = engine.get_meta::<String>(conn, GLOBAL_SYNCID_META_KEY)?;
+        assert_eq!(
+            retrieved_global_sync_id.unwrap_or_default(),
+            global_guid.to_string()
+        );
+
+        let retrieved_coll_sync_id = engine.get_meta::<String>(conn, COLLECTION_SYNCID_META_KEY)?;
+        assert_eq!(
+            retrieved_coll_sync_id.unwrap_or_default(),
+            coll_guid.to_string()
+        );
+        Ok(())
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/autofill/sync/mod.rs.html b/book/rust-docs/src/autofill/sync/mod.rs.html new file mode 100644 index 0000000000..97f8a0fd7c --- /dev/null +++ b/book/rust-docs/src/autofill/sync/mod.rs.html @@ -0,0 +1,843 @@ +mod.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/.
+*/
+
+pub mod address;
+mod common;
+pub mod credit_card;
+pub mod engine;
+
+pub(crate) use crate::db::models::Metadata;
+use crate::error::Result;
+use interrupt_support::Interruptee;
+use rusqlite::Transaction;
+use sync15::bso::{IncomingBso, IncomingContent, IncomingEnvelope, IncomingKind, OutgoingBso};
+use sync15::ServerTimestamp;
+use sync_guid::Guid;
+use types::Timestamp;
+
+// This type is used as a snazzy way to capture all unknown fields from the payload
+// upon deserialization without having to work with a concrete type
+type UnknownFields = serde_json::Map<String, serde_json::Value>;
+
+// The fact that credit-card numbers are encrypted makes things a little tricky
+// for sync in various ways - and one non-obvious way is that the tables that
+// store sync payloads can't just store them directly as they are not encrypted
+// in that form.
+// ie, in the database, an address record's "payload" column looks like:
+// > '{"entry":{"address-level1":"VIC", "street-address":"2/25 Somewhere St","timeCreated":1497567116554, "version":1},"id":"29ac67adae7d"}'
+// or a tombstone: '{"deleted":true,"id":"6544992973e6"}'
+// > (Note a number of fields have been removed from 'entry' for clarity)
+// and in the database a credit-card's "payload" looks like:
+// > 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIn0..<snip>-<snip>.<snip lots more>'
+// > while a tombstone here remains encrypted but has the 'deleted' entry after decryption.
+// (Note also that the address entry, and the decrypted credit-card json both have an "id" in
+// the JSON, but we ignore that when deserializing and will stop persisting that soon)
+
+// Some traits that help us abstract away much of the sync functionality.
+
+// A trait that abstracts the *storage* implementation of the specific record
+// types, and must be implemented by the concrete record owners.
+// Note that it doesn't assume a SQL database or anything concrete about the
+// storage, although objects implementing this trait will live only long enough
+// to perform the sync "incoming" steps - ie, a transaction is likely to live
+// exactly as long as this object.
+// XXX - *sob* - although each method has a `&Transaction` param, which in
+// theory could be avoided if the concrete impls could keep the ref (ie, if
+// it was held behind `self`), but markh failed to make this work due to
+// lifetime woes.
+pub trait ProcessIncomingRecordImpl {
+    type Record;
+
+    fn stage_incoming(
+        &self,
+        tx: &Transaction<'_>,
+        incoming: Vec<IncomingBso>,
+        signal: &dyn Interruptee,
+    ) -> Result<()>;
+
+    /// Finish the incoming phase. This will typically caused staged records
+    // to be written to the mirror.
+    fn finish_incoming(&self, tx: &Transaction<'_>) -> Result<()>;
+
+    fn fetch_incoming_states(
+        &self,
+        tx: &Transaction<'_>,
+    ) -> Result<Vec<IncomingState<Self::Record>>>;
+
+    /// Returns a local record that has the same values as the given incoming record (with the exception
+    /// of the `guid` values which should differ) that will be used as a local duplicate record for
+    /// syncing.
+    fn get_local_dupe(
+        &self,
+        tx: &Transaction<'_>,
+        incoming: &Self::Record,
+    ) -> Result<Option<Self::Record>>;
+
+    fn update_local_record(
+        &self,
+        tx: &Transaction<'_>,
+        record: Self::Record,
+        was_merged: bool,
+    ) -> Result<()>;
+
+    fn insert_local_record(&self, tx: &Transaction<'_>, record: Self::Record) -> Result<()>;
+
+    fn change_record_guid(
+        &self,
+        tx: &Transaction<'_>,
+        old_guid: &Guid,
+        new_guid: &Guid,
+    ) -> Result<()>;
+
+    fn remove_record(&self, tx: &Transaction<'_>, guid: &Guid) -> Result<()>;
+
+    fn remove_tombstone(&self, tx: &Transaction<'_>, guid: &Guid) -> Result<()>;
+}
+
+pub trait ProcessOutgoingRecordImpl {
+    type Record;
+
+    fn fetch_outgoing_records(&self, tx: &Transaction<'_>) -> anyhow::Result<Vec<OutgoingBso>>;
+
+    fn finish_synced_items(
+        &self,
+        tx: &Transaction<'_>,
+        records_synced: Vec<Guid>,
+    ) -> anyhow::Result<()>;
+}
+
+// A trait that abstracts the functionality in the record itself.
+pub trait SyncRecord {
+    fn record_name() -> &'static str; // "addresses" or similar, for logging/debuging.
+    fn id(&self) -> &Guid;
+    fn metadata(&self) -> &Metadata;
+    fn metadata_mut(&mut self) -> &mut Metadata;
+    // Merge or fork multiple copies of the same record. The resulting record
+    // might have the same guid as the inputs, meaning it was truly merged, or
+    // a different guid, in which case it was forked due to conflicting changes.
+    fn merge(incoming: &Self, local: &Self, mirror: &Option<Self>) -> MergeResult<Self>
+    where
+        Self: Sized;
+}
+
+impl Metadata {
+    /// Merge the metadata from `other`, and possibly `mirror`, into `self`
+    /// (which must already have valid metadata).
+    /// Note that mirror being None is an edge-case and typically means first
+    /// sync since a "reset" (eg, disconnecting and reconnecting.
+    pub fn merge(&mut self, other: &Metadata, mirror: Option<&Metadata>) {
+        match mirror {
+            Some(m) => {
+                fn get_latest_time(t1: Timestamp, t2: Timestamp, t3: Timestamp) -> Timestamp {
+                    std::cmp::max(t1, std::cmp::max(t2, t3))
+                }
+                fn get_earliest_time(t1: Timestamp, t2: Timestamp, t3: Timestamp) -> Timestamp {
+                    std::cmp::min(t1, std::cmp::min(t2, t3))
+                }
+                self.time_created =
+                    get_earliest_time(self.time_created, other.time_created, m.time_created);
+                self.time_last_used =
+                    get_latest_time(self.time_last_used, other.time_last_used, m.time_last_used);
+                self.time_last_modified = get_latest_time(
+                    self.time_last_modified,
+                    other.time_last_modified,
+                    m.time_last_modified,
+                );
+
+                self.times_used = m.times_used
+                    + std::cmp::max(other.times_used - m.times_used, 0)
+                    + std::cmp::max(self.times_used - m.times_used, 0);
+            }
+            None => {
+                fn get_latest_time(t1: Timestamp, t2: Timestamp) -> Timestamp {
+                    std::cmp::max(t1, t2)
+                }
+                fn get_earliest_time(t1: Timestamp, t2: Timestamp) -> Timestamp {
+                    std::cmp::min(t1, t2)
+                }
+                self.time_created = get_earliest_time(self.time_created, other.time_created);
+                self.time_last_used = get_latest_time(self.time_last_used, other.time_last_used);
+                self.time_last_modified =
+                    get_latest_time(self.time_last_modified, other.time_last_modified);
+                // No mirror is an edge-case that almost certainly means the
+                // client was disconnected and this is the first sync after
+                // reconnection. So we can't really do a simple sum() of the
+                // times_used values as if the disconnection was recent, it will
+                // be double the expected value.
+                // So we just take the largest.
+                self.times_used = std::cmp::max(other.times_used, self.times_used);
+            }
+        }
+    }
+}
+
+// A local record can be in any of these 5 states.
+#[derive(Debug)]
+enum LocalRecordInfo<T> {
+    Unmodified { record: T },
+    Modified { record: T },
+    // encrypted data was scrubbed from the local record and needs to be resynced from the server
+    Scrubbed { record: T },
+    Tombstone { guid: Guid },
+    Missing,
+}
+
+// An enum for the return value from our "merge" function, which might either
+// update the record, or might fork it.
+#[derive(Debug)]
+pub enum MergeResult<T> {
+    Merged { merged: T },
+    Forked { forked: T },
+}
+
+// This ties the 3 possible records together and is what we expect the
+// implementations to put together for us.
+#[derive(Debug)]
+pub struct IncomingState<T> {
+    incoming: IncomingContent<T>,
+    local: LocalRecordInfo<T>,
+    // We don't have an enum for the mirror - an Option<> is fine because
+    // although we do store tombstones there, we ignore them when reconciling
+    // (ie, we ignore tombstones in the mirror)
+    // don't store tombstones there.
+    mirror: Option<T>,
+}
+
+/// The distinct incoming sync actions to be performed for incoming records.
+#[derive(Debug, PartialEq)]
+enum IncomingAction<T> {
+    // Remove the local record with this GUID.
+    DeleteLocalRecord { guid: Guid },
+    // Insert a new record.
+    Insert { record: T },
+    // Update an existing record. If `was_merged` was true, then the updated
+    // record isn't identical to the incoming one, so needs to be flagged as
+    // dirty.
+    Update { record: T, was_merged: bool },
+    // We forked a record because we couldn't merge it. `forked` will have
+    // a new guid, while `incoming` is the unmodified version of the incoming
+    // record which we need to apply.
+    Fork { forked: T, incoming: T },
+    // An existing record with old_guid needs to be replaced with this record.
+    UpdateLocalGuid { old_guid: Guid, record: T },
+    // There's a remote tombstone, but our copy of the record is dirty. The
+    // remote tombstone should be replaced with this.
+    ResurrectRemoteTombstone { record: T },
+    // There's a local tombstone - it should be removed and replaced with this.
+    ResurrectLocalTombstone { record: T },
+    // Nothing to do.
+    DoNothing,
+}
+
+/// Convert a IncomingState to an IncomingAction - this is where the "policy"
+/// lives for when we resurrect, or merge etc.
+fn plan_incoming<T: std::fmt::Debug + SyncRecord>(
+    rec_impl: &dyn ProcessIncomingRecordImpl<Record = T>,
+    tx: &Transaction<'_>,
+    staged_info: IncomingState<T>,
+) -> Result<IncomingAction<T>> {
+    log::trace!("plan_incoming: {:?}", staged_info);
+    let IncomingState {
+        incoming,
+        local,
+        mirror,
+    } = staged_info;
+
+    let state = match incoming.kind {
+        IncomingKind::Tombstone => {
+            match local {
+                LocalRecordInfo::Unmodified { .. } | LocalRecordInfo::Scrubbed { .. } => {
+                    // Note: On desktop, when there's a local record for an incoming tombstone, a local tombstone
+                    // would created. But we don't actually need to create a local tombstone here. If we did it would
+                    // immediately be deleted after being uploaded to the server.
+                    IncomingAction::DeleteLocalRecord {
+                        guid: incoming.envelope.id,
+                    }
+                }
+                LocalRecordInfo::Modified { record } => {
+                    // Incoming tombstone with local changes should cause us to "resurrect" the local.
+                    // At a minimum, the implementation will need to ensure the record is marked as
+                    // dirty so it's uploaded, overwriting the server's tombstone.
+                    IncomingAction::ResurrectRemoteTombstone { record }
+                }
+                LocalRecordInfo::Tombstone {
+                    guid: tombstone_guid,
+                } => {
+                    assert_eq!(incoming.envelope.id, tombstone_guid);
+                    IncomingAction::DoNothing
+                }
+                LocalRecordInfo::Missing => IncomingAction::DoNothing,
+            }
+        }
+        IncomingKind::Content(mut incoming_record) => {
+            match local {
+                LocalRecordInfo::Unmodified {
+                    record: local_record,
+                }
+                | LocalRecordInfo::Scrubbed {
+                    record: local_record,
+                } => {
+                    // The local record was either unmodified, or scrubbed of its encrypted data.
+                    // Either way we want to:
+                    //   - Merge the metadata
+                    //   - Update the local record using data from the server
+                    //   - Don't flag the local item as dirty.  We don't want to reupload for just
+                    //     metadata changes.
+                    let metadata = incoming_record.metadata_mut();
+                    metadata.merge(
+                        local_record.metadata(),
+                        mirror.as_ref().map(|m| m.metadata()),
+                    );
+                    // a micro-optimization here would be to `::DoNothing` if
+                    // the metadata was actually identical and the local data wasn't scrubbed, but
+                    // this seems like an edge-case on an edge-case?
+                    IncomingAction::Update {
+                        record: incoming_record,
+                        was_merged: false,
+                    }
+                }
+                LocalRecordInfo::Modified {
+                    record: local_record,
+                } => {
+                    match SyncRecord::merge(&incoming_record, &local_record, &mirror) {
+                        MergeResult::Merged { merged } => {
+                            // The record we save locally has material differences
+                            // from the incoming one, so we are going to need to
+                            // reupload it.
+                            IncomingAction::Update {
+                                record: merged,
+                                was_merged: true,
+                            }
+                        }
+                        MergeResult::Forked { forked } => IncomingAction::Fork {
+                            forked,
+                            incoming: incoming_record,
+                        },
+                    }
+                }
+                LocalRecordInfo::Tombstone { .. } => IncomingAction::ResurrectLocalTombstone {
+                    record: incoming_record,
+                },
+                LocalRecordInfo::Missing => {
+                    match rec_impl.get_local_dupe(tx, &incoming_record)? {
+                        None => IncomingAction::Insert {
+                            record: incoming_record,
+                        },
+                        Some(local_dupe) => {
+                            // local record is missing but we found a dupe - so
+                            // the dupe must have a different guid (or we wouldn't
+                            // consider the local record missing!)
+                            assert_ne!(incoming_record.id(), local_dupe.id());
+                            // The existing item is identical except for the metadata, so
+                            // we still merge that metadata.
+                            let metadata = incoming_record.metadata_mut();
+                            metadata.merge(
+                                local_dupe.metadata(),
+                                mirror.as_ref().map(|m| m.metadata()),
+                            );
+                            IncomingAction::UpdateLocalGuid {
+                                old_guid: local_dupe.id().clone(),
+                                record: incoming_record,
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        IncomingKind::Malformed => {
+            log::warn!("skipping incoming record: {}", incoming.envelope.id);
+            IncomingAction::DoNothing
+        }
+    };
+    log::trace!("plan_incoming resulted in {:?}", state);
+    Ok(state)
+}
+
+/// Apply the incoming action
+fn apply_incoming_action<T: std::fmt::Debug + SyncRecord>(
+    rec_impl: &dyn ProcessIncomingRecordImpl<Record = T>,
+    tx: &Transaction<'_>,
+    action: IncomingAction<T>,
+) -> Result<()> {
+    log::trace!("applying action: {:?}", action);
+    match action {
+        IncomingAction::Update { record, was_merged } => {
+            rec_impl.update_local_record(tx, record, was_merged)?;
+        }
+        IncomingAction::Fork { forked, incoming } => {
+            // `forked` exists in the DB with the same guid as `incoming`, so fix that.
+            // change_record_guid will also update the mirror (if it exists) to prevent
+            // the server from overriding the forked mirror record (and losing any unknown fields)
+            rec_impl.change_record_guid(tx, incoming.id(), forked.id())?;
+            // `incoming` has the correct new guid.
+            rec_impl.insert_local_record(tx, incoming)?;
+        }
+        IncomingAction::Insert { record } => {
+            rec_impl.insert_local_record(tx, record)?;
+        }
+        IncomingAction::UpdateLocalGuid { old_guid, record } => {
+            // expect record to have the new guid.
+            assert_ne!(old_guid, *record.id());
+            rec_impl.change_record_guid(tx, &old_guid, record.id())?;
+            // the item is identical with the item with the new guid
+            // *except* for the metadata - so we still need to update, but
+            // don't need to treat the item as dirty.
+            rec_impl.update_local_record(tx, record, false)?;
+        }
+        IncomingAction::ResurrectLocalTombstone { record } => {
+            rec_impl.remove_tombstone(tx, record.id())?;
+            rec_impl.insert_local_record(tx, record)?;
+        }
+        IncomingAction::ResurrectRemoteTombstone { record } => {
+            // This is just "ensure local record dirty", which
+            // update_local_record conveniently does.
+            rec_impl.update_local_record(tx, record, true)?;
+        }
+        IncomingAction::DeleteLocalRecord { guid } => {
+            rec_impl.remove_record(tx, &guid)?;
+        }
+        IncomingAction::DoNothing => {}
+    }
+    Ok(())
+}
+
+// Helpers for tests
+#[cfg(test)]
+mod tests; // pull in our integration tests
+
+// and a module for unit test utilities.
+#[cfg(test)]
+pub mod test {
+    use crate::db::{schema::create_empty_sync_temp_tables, test::new_mem_db, AutofillDb};
+
+    pub fn new_syncable_mem_db() -> AutofillDb {
+        let _ = env_logger::try_init();
+        let db = new_mem_db();
+        create_empty_sync_temp_tables(&db).expect("should work");
+        db
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/cli_support/fxa_creds.rs.html b/book/rust-docs/src/cli_support/fxa_creds.rs.html new file mode 100644 index 0000000000..33a17b6c1e --- /dev/null +++ b/book/rust-docs/src/cli_support/fxa_creds.rs.html @@ -0,0 +1,301 @@ +fxa_creds.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/// Utilities for command-line utilities which want to use fxa credentials.
+use std::{
+    collections::HashMap,
+    fs,
+    io::{Read, Write},
+};
+
+use anyhow::Result;
+use url::Url;
+
+// This crate awkardly uses some internal implementation details of the fxa-client crate,
+// because we haven't worked on exposing those test-only features via UniFFI.
+use fxa_client::{AccessTokenInfo, FirefoxAccount, FxaConfig, FxaError};
+use sync15::client::Sync15StorageClientInit;
+use sync15::KeyBundle;
+
+use crate::prompt::prompt_string;
+
+// Defaults - not clear they are the best option, but they are a currently
+// working option.
+const CLIENT_ID: &str = "3c49430b43dfba77";
+const REDIRECT_URI: &str = "https://accounts.firefox.com/oauth/success/3c49430b43dfba77";
+const SYNC_SCOPE: &str = "https://identity.mozilla.com/apps/oldsync";
+
+fn load_fxa_creds(path: &str) -> Result<FirefoxAccount> {
+    let mut file = fs::File::open(path)?;
+    let mut s = String::new();
+    file.read_to_string(&mut s)?;
+    Ok(FirefoxAccount::from_json(&s)?)
+}
+
+fn load_or_create_fxa_creds(path: &str, cfg: FxaConfig) -> Result<FirefoxAccount> {
+    load_fxa_creds(path).or_else(|e| {
+        log::info!(
+            "Failed to load existing FxA credentials from {:?} (error: {}), launching OAuth flow",
+            path,
+            e
+        );
+        create_fxa_creds(path, cfg)
+    })
+}
+
+fn create_fxa_creds(path: &str, cfg: FxaConfig) -> Result<FirefoxAccount> {
+    let acct = FirefoxAccount::new(cfg);
+    let oauth_uri = acct.begin_oauth_flow(&[SYNC_SCOPE], "fxa_creds", None)?;
+
+    if webbrowser::open(oauth_uri.as_ref()).is_err() {
+        log::warn!("Failed to open a web browser D:");
+        println!("Please visit this URL, sign in, and then copy-paste the final URL below.");
+        println!("\n    {}\n", oauth_uri);
+    } else {
+        println!("Please paste the final URL below:\n");
+    }
+
+    let final_url = url::Url::parse(&prompt_string("Final URL").unwrap_or_default())?;
+    let query_params = final_url
+        .query_pairs()
+        .into_owned()
+        .collect::<HashMap<String, String>>();
+
+    acct.complete_oauth_flow(&query_params["code"], &query_params["state"])?;
+    // Device registration.
+    acct.initialize_device("CLI Device", sync15::DeviceType::Desktop, vec![])?;
+    let mut file = fs::File::create(path)?;
+    write!(file, "{}", acct.to_json()?)?;
+    file.flush()?;
+    Ok(acct)
+}
+
+// Our public functions. It would be awesome if we could somehow integrate
+// better with clap, so we could automagically support various args (such as
+// the config to use or filenames to read), but this will do for now.
+pub fn get_default_fxa_config() -> FxaConfig {
+    FxaConfig::release(CLIENT_ID, REDIRECT_URI)
+}
+
+pub fn get_account_and_token(
+    config: FxaConfig,
+    cred_file: &str,
+) -> Result<(FirefoxAccount, AccessTokenInfo)> {
+    // TODO: we should probably set a persist callback on acct?
+    let mut acct = load_or_create_fxa_creds(cred_file, config.clone())?;
+    // `scope` could be a param, but I can't see it changing.
+    match acct.get_access_token(SYNC_SCOPE, None) {
+        Ok(t) => Ok((acct, t)),
+        Err(e) => {
+            match e {
+                // We can retry an auth error.
+                FxaError::Authentication => {
+                    println!("Saw an auth error using stored credentials - recreating them...");
+                    acct = create_fxa_creds(cred_file, config)?;
+                    let token = acct.get_access_token(SYNC_SCOPE, None)?;
+                    Ok((acct, token))
+                }
+                _ => Err(e.into()),
+            }
+        }
+    }
+}
+
+pub fn get_cli_fxa(config: FxaConfig, cred_file: &str) -> Result<CliFxa> {
+    let (account, token_info) = match get_account_and_token(config, cred_file) {
+        Ok(v) => v,
+        Err(e) => anyhow::bail!("Failed to use saved credentials. {}", e),
+    };
+    let tokenserver_url = Url::parse(&account.get_token_server_endpoint_url()?)?;
+
+    let client_init = Sync15StorageClientInit {
+        key_id: token_info.key.as_ref().unwrap().kid.clone(),
+        access_token: token_info.token.clone(),
+        tokenserver_url: tokenserver_url.clone(),
+    };
+
+    Ok(CliFxa {
+        account,
+        client_init,
+        tokenserver_url,
+        token_info,
+    })
+}
+
+pub struct CliFxa {
+    pub account: FirefoxAccount,
+    pub client_init: Sync15StorageClientInit,
+    pub tokenserver_url: Url,
+    pub token_info: AccessTokenInfo,
+}
+
+impl CliFxa {
+    // A helper for consumers who use this with the sync manager.
+    pub fn as_auth_info(&self) -> sync_manager::SyncAuthInfo {
+        let scoped_key = self.token_info.key.as_ref().unwrap();
+        sync_manager::SyncAuthInfo {
+            kid: scoped_key.kid.clone(),
+            sync_key: scoped_key.k.clone(),
+            fxa_access_token: self.token_info.token.clone(),
+            tokenserver_url: self.tokenserver_url.to_string(),
+        }
+    }
+
+    // A helper for consumers who use this directly with sync15
+    pub fn as_key_bundle(&self) -> Result<KeyBundle> {
+        let scoped_key = self.token_info.key.as_ref().unwrap();
+        Ok(KeyBundle::from_ksync_bytes(&scoped_key.key_bytes()?)?)
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/cli_support/lib.rs.html b/book/rust-docs/src/cli_support/lib.rs.html new file mode 100644 index 0000000000..250e8d2a90 --- /dev/null +++ b/book/rust-docs/src/cli_support/lib.rs.html @@ -0,0 +1,59 @@ +lib.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#![allow(unknown_lints)]
+#![warn(rust_2018_idioms)]
+
+pub mod fxa_creds;
+pub mod prompt;
+
+pub use env_logger;
+
+pub fn init_logging_with(s: &str) {
+    let noisy = "tokio_threadpool=warn,tokio_reactor=warn,tokio_core=warn,tokio=warn,hyper=warn,want=warn,mio=warn,reqwest=warn";
+    let spec = format!("{},{}", s, noisy);
+    env_logger::init_from_env(env_logger::Env::default().filter_or("RUST_LOG", spec));
+}
+
+pub fn init_trace_logging() {
+    init_logging_with("trace")
+}
+
+pub fn init_logging() {
+    init_logging_with(if cfg!(debug_assertions) {
+        "debug"
+    } else {
+        "info"
+    })
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/cli_support/prompt.rs.html b/book/rust-docs/src/cli_support/prompt.rs.html new file mode 100644 index 0000000000..93b8b8774d --- /dev/null +++ b/book/rust-docs/src/cli_support/prompt.rs.html @@ -0,0 +1,85 @@ +prompt.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+use std::io::{self, Write};
+
+pub fn prompt_string<S: AsRef<str>>(prompt: S) -> Option<String> {
+    print!("{}: ", prompt.as_ref());
+    let _ = io::stdout().flush(); // Don't care if flush fails really.
+    let mut s = String::new();
+    io::stdin()
+        .read_line(&mut s)
+        .expect("Failed to read line...");
+    if let Some('\n') = s.chars().next_back() {
+        s.pop();
+    }
+    if let Some('\r') = s.chars().next_back() {
+        s.pop();
+    }
+    if s.is_empty() {
+        None
+    } else {
+        Some(s)
+    }
+}
+
+pub fn prompt_char(msg: &str) -> Option<char> {
+    prompt_string(msg).and_then(|r| r.chars().next())
+}
+
+pub fn prompt_usize<S: AsRef<str>>(prompt: S) -> Option<usize> {
+    if let Some(s) = prompt_string(prompt) {
+        match s.parse::<usize>() {
+            Ok(n) => Some(n),
+            Err(_) => {
+                println!("Couldn't parse!");
+                None
+            }
+        }
+    } else {
+        None
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/crashtest/lib.rs.html b/book/rust-docs/src/crashtest/lib.rs.html new file mode 100644 index 0000000000..172706662f --- /dev/null +++ b/book/rust-docs/src/crashtest/lib.rs.html @@ -0,0 +1,133 @@ +lib.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! # Crash Test Helper APIs
+//!
+//! The `crashtest` component offers a little helper API that lets you deliberately
+//! crash the application. It's intended to help developers test the crash-handling
+//! and crash-reporting capabilities of their app.
+
+// Temporary, to work around a clippy lint in generated code.
+// https://github.com/mozilla/uniffi-rs/issues/1018
+#![allow(clippy::redundant_closure)]
+
+use thiserror::Error;
+
+#[cfg(test)]
+mod tests;
+
+uniffi::include_scaffolding!("crashtest");
+
+/// Trigger a hard abort inside the Rust code.
+///
+/// This function simulates some kind of uncatchable illegal operation
+/// performed inside the Rust code. After calling this function you should
+/// expect your application to be halted with e.g. a `SIGABRT` or similar.
+///
+pub fn trigger_rust_abort() {
+    log::error!("Now triggering an abort inside the Rust code");
+    std::process::abort();
+}
+
+/// Trigger a panic inside the Rust code.
+///
+/// This function simulates the occurence of an unexpected state inside
+/// the Rust code that causes it to panic. We build our Rust components to
+/// unwind on panic, so after calling this function through the foreign
+/// language bindings, you should expect it to intercept the panic translate
+/// it into some foreign-language-appropriate equivalent:
+///
+///  - In Kotlin, it will throw an exception.
+///  - In Swift, it will fail with a `try!` runtime error.
+///
+pub fn trigger_rust_panic() {
+    log::error!("Now triggering a panic inside the Rust code");
+    panic!("Panic! In The Rust Code.");
+}
+
+/// Trigger an error inside the Rust code.
+///
+/// This function simulates the occurence of an expected error inside
+/// the Rust code. You should expect calling this function to throw the
+/// foreign-language representation of the [`CrashTestError`] class.
+///
+pub fn trigger_rust_error() -> Result<(), CrashTestError> {
+    log::error!("Now triggering an error inside the Rust code");
+    Err(CrashTestError::ErrorFromTheRustCode)
+}
+
+/// An error that can be returned from Rust code.
+///
+#[derive(Debug, Error)]
+pub enum CrashTestError {
+    #[error("Error! From The Rust Code.")]
+    ErrorFromTheRustCode,
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/embedded_uniffi_bindgen/main.rs.html b/book/rust-docs/src/embedded_uniffi_bindgen/main.rs.html new file mode 100644 index 0000000000..f48ac3fcfd --- /dev/null +++ b/book/rust-docs/src/embedded_uniffi_bindgen/main.rs.html @@ -0,0 +1,15 @@ +main.rs - source
1
+2
+3
+4
+5
+6
+7
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+fn main() {
+    uniffi::uniffi_bindgen_main()
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/error_support/handling.rs.html b/book/rust-docs/src/error_support/handling.rs.html new file mode 100644 index 0000000000..5cc2b74b4a --- /dev/null +++ b/book/rust-docs/src/error_support/handling.rs.html @@ -0,0 +1,225 @@ +handling.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Helpers for components to "handle" errors.
+
+/// Describes what error reporting action should be taken.
+#[derive(Debug, Default)]
+pub struct ErrorReporting {
+    /// If Some(level), will write a log message at that level.
+    log_level: Option<log::Level>,
+    /// If Some(report_class) will call the error reporter with details.
+    report_class: Option<String>,
+}
+
+/// Specifies how an "internal" error is converted to an "external" public error and
+/// any logging or reporting that should happen.
+pub struct ErrorHandling<E> {
+    /// The external error that should be returned.
+    pub err: E,
+    /// How the error should be reported.
+    pub reporting: ErrorReporting,
+}
+
+impl<E> ErrorHandling<E> {
+    /// Create an ErrorHandling instance with an error conversion.
+    ///
+    /// ErrorHandling instance are created using a builder-style API.  This is always the first
+    /// function in the chain, optionally followed by `log()`, `report()`, etc.
+    pub fn convert(err: E) -> Self {
+        Self {
+            err,
+            reporting: ErrorReporting::default(),
+        }
+    }
+
+    /// Add logging to an ErrorHandling instance
+    pub fn log(self, level: log::Level) -> Self {
+        Self {
+            err: self.err,
+            reporting: ErrorReporting {
+                log_level: Some(level),
+                ..self.reporting
+            },
+        }
+    }
+
+    /// Add reporting to an ErrorHandling instance
+    pub fn report(self, report_class: impl Into<String>) -> Self {
+        Self {
+            err: self.err,
+            reporting: ErrorReporting {
+                report_class: Some(report_class.into()),
+                ..self.reporting
+            },
+        }
+    }
+
+    // Convenience functions for the most common error reports
+
+    /// log a warning
+    pub fn log_warning(self) -> Self {
+        self.log(log::Level::Warn)
+    }
+
+    /// log an info
+    pub fn log_info(self) -> Self {
+        self.log(log::Level::Info)
+    }
+
+    /// Add reporting to an ErrorHandling instance and also log an Error
+    pub fn report_error(self, report_class: impl Into<String>) -> Self {
+        Self {
+            err: self.err,
+            reporting: ErrorReporting {
+                log_level: Some(log::Level::Error),
+                report_class: Some(report_class.into()),
+            },
+        }
+    }
+}
+
+/// A trait to define how errors are converted and reported.
+pub trait GetErrorHandling {
+    type ExternalError;
+
+    /// Return how to handle our internal errors
+    fn get_error_handling(&self) -> ErrorHandling<Self::ExternalError>;
+}
+
+/// Handle the specified "internal" error, taking any logging or error
+/// reporting actions and converting the error to the public error.
+/// Called by our `handle_error` macro so needs to be public.
+pub fn convert_log_report_error<IE, EE>(e: IE) -> EE
+where
+    IE: GetErrorHandling<ExternalError = EE> + std::error::Error,
+    EE: std::error::Error,
+{
+    let handling = e.get_error_handling();
+    let reporting = handling.reporting;
+    if let Some(level) = reporting.log_level {
+        log::log!(level, "{}", e.to_string());
+    }
+    if let Some(report_class) = reporting.report_class {
+        // notify the error reporter if the feature is enabled.
+        // XXX - should we arrange for the `report_class` to have the
+        // original crate calling this as a prefix, or will we still be
+        // able to identify that?
+        crate::report_error_to_app(report_class, e.to_string());
+    }
+    handling.err
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/error_support/lib.rs.html b/book/rust-docs/src/error_support/lib.rs.html new file mode 100644 index 0000000000..49405ace0e --- /dev/null +++ b/book/rust-docs/src/error_support/lib.rs.html @@ -0,0 +1,325 @@ +lib.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+mod macros;
+
+#[cfg(feature = "backtrace")]
+/// Re-export of the `backtrace` crate for use in macros and
+/// to ensure the needed version is kept in sync in dependents.
+pub use backtrace;
+
+#[cfg(not(feature = "backtrace"))]
+/// A compatibility shim for `backtrace`.
+pub mod backtrace {
+    use std::fmt;
+
+    pub struct Backtrace;
+
+    impl fmt::Debug for Backtrace {
+        #[cold]
+        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+            write!(f, "Not available")
+        }
+    }
+}
+
+mod redact;
+pub use redact::*;
+
+mod reporting;
+pub use reporting::{
+    report_breadcrumb, report_error_to_app, set_application_error_reporter,
+    unset_application_error_reporter, ApplicationErrorReporter,
+};
+
+pub use error_support_macros::handle_error;
+
+mod handling;
+pub use handling::{convert_log_report_error, ErrorHandling, ErrorReporting, GetErrorHandling};
+
+/// XXX - Most of this is now considered deprecated - only FxA uses it, and
+/// should be replaced with the facilities in the `handling` module.
+
+/// Define a wrapper around the the provided ErrorKind type.
+/// See also `define_error` which is more likely to be what you want.
+#[macro_export]
+macro_rules! define_error_wrapper {
+    ($Kind:ty) => {
+        pub type Result<T, E = Error> = std::result::Result<T, E>;
+        struct ErrorData {
+            kind: $Kind,
+            backtrace: Option<std::sync::Mutex<$crate::backtrace::Backtrace>>,
+        }
+
+        impl ErrorData {
+            #[cold]
+            fn new(kind: $Kind) -> Self {
+                ErrorData {
+                    kind,
+                    #[cfg(feature = "backtrace")]
+                    backtrace: Some(std::sync::Mutex::new(
+                        $crate::backtrace::Backtrace::new_unresolved(),
+                    )),
+                    #[cfg(not(feature = "backtrace"))]
+                    backtrace: None,
+                }
+            }
+
+            #[cfg(feature = "backtrace")]
+            #[cold]
+            fn get_backtrace(&self) -> Option<&std::sync::Mutex<$crate::backtrace::Backtrace>> {
+                self.backtrace.as_ref().map(|mutex| {
+                    mutex.lock().unwrap().resolve();
+                    mutex
+                })
+            }
+
+            #[cfg(not(feature = "backtrace"))]
+            #[cold]
+            fn get_backtrace(&self) -> Option<&std::sync::Mutex<$crate::backtrace::Backtrace>> {
+                None
+            }
+        }
+
+        impl std::fmt::Debug for ErrorData {
+            #[cfg(feature = "backtrace")]
+            #[cold]
+            fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+                let mut bt = self.backtrace.unwrap().lock().unwrap();
+                bt.resolve();
+                write!(f, "{:?}\n\n{}", bt, self.kind)
+            }
+
+            #[cfg(not(feature = "backtrace"))]
+            #[cold]
+            fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+                write!(f, "{}", self.kind)
+            }
+        }
+
+        #[derive(Debug, thiserror::Error)]
+        pub struct Error(Box<ErrorData>);
+        impl Error {
+            #[cold]
+            pub fn kind(&self) -> &$Kind {
+                &self.0.kind
+            }
+
+            #[cold]
+            pub fn backtrace(&self) -> Option<&std::sync::Mutex<$crate::backtrace::Backtrace>> {
+                self.0.get_backtrace()
+            }
+        }
+
+        impl std::fmt::Display for Error {
+            #[cold]
+            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+                std::fmt::Display::fmt(self.kind(), f)
+            }
+        }
+
+        impl From<$Kind> for Error {
+            // Cold to optimize in favor of non-error cases.
+            #[cold]
+            fn from(ctx: $Kind) -> Error {
+                Error(Box::new(ErrorData::new(ctx)))
+            }
+        }
+    };
+}
+
+/// Define a set of conversions from external error types into the provided
+/// error kind. Use `define_error` to do this at the same time as
+/// `define_error_wrapper`.
+#[macro_export]
+macro_rules! define_error_conversions {
+    ($Kind:ident { $(($variant:ident, $type:ty)),* $(,)? }) => ($(
+        impl From<$type> for Error {
+            // Cold to optimize in favor of non-error cases.
+            #[cold]
+            fn from(e: $type) -> Self {
+                Error::from($Kind::$variant(e))
+            }
+        }
+    )*);
+}
+
+/// All the error boilerplate (okay, with a couple exceptions in some cases) in
+/// one place.
+#[macro_export]
+macro_rules! define_error {
+    ($Kind:ident { $(($variant:ident, $type:ty)),* $(,)? }) => {
+        $crate::define_error_wrapper!($Kind);
+        $crate::define_error_conversions! {
+            $Kind {
+                $(($variant, $type)),*
+            }
+        }
+    };
+}
+
+uniffi::include_scaffolding!("errorsupport");
+
\ No newline at end of file diff --git a/book/rust-docs/src/error_support/macros.rs.html b/book/rust-docs/src/error_support/macros.rs.html new file mode 100644 index 0000000000..4ed6b674d8 --- /dev/null +++ b/book/rust-docs/src/error_support/macros.rs.html @@ -0,0 +1,125 @@ +macros.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/// Tell the application to report an error
+///
+/// If configured by the application, this sent to the application, which should report it to a
+/// Sentry-like system. This should only be used for errors that we don't expect to see and will
+/// work on fixing if we see a non-trivial volume of them.
+///
+/// type_name identifies the error.  It should be the main text that gets shown to the
+/// user and also how the error system groups errors together.  It should be in UpperCamelCase
+/// form.
+///
+/// Good type_names require some trial and error, for example:
+///   - Start with the error kind variant name
+///   - Add more text to distinguish errors more.  For example an error code, or an extra word
+///     based on inspecting the error details
+#[macro_export]
+macro_rules! report_error {
+    ($type_name:expr, $($arg:tt)*) => {
+        let message = std::format!($($arg)*);
+        ::log::warn!("report {}: {}", $type_name, message);
+        $crate::report_error_to_app($type_name.to_string(), message.to_string());
+    };
+}
+
+/// Log a breadcrumb if we see an `Result::Err` value
+///
+/// Use this macro to wrap a function call that returns a `Result<>`.  If that call returns an
+/// error, then we will log a breadcrumb for it.  This can be used to track down the codepath where
+/// an error happened.
+#[macro_export]
+macro_rules! trace_error {
+    ($result:expr) => {{
+        let result = $result;
+        if let Err(e) = &result {
+            $crate::breadcrumb!("Saw error: {}", e);
+        };
+        result
+    }};
+}
+
+/// Tell the application to log a breadcrumb
+///
+/// Breadcrumbs are log-like entries that get tracked by the error reporting system.  When we
+/// report an error, recent breadcrumbs will be associated with it.
+#[macro_export]
+macro_rules! breadcrumb {
+    ($($arg:tt)*) => {
+        {
+            let message = std::format!($($arg)*);
+            ::log::info!("breadcrumb: {}", message);
+            $crate::report_breadcrumb(
+                message,
+                std::module_path!().to_string(),
+                std::line!(),
+                std::column!(),
+            );
+        }
+    };
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/error_support/redact.rs.html b/book/rust-docs/src/error_support/redact.rs.html new file mode 100644 index 0000000000..05711a594a --- /dev/null +++ b/book/rust-docs/src/error_support/redact.rs.html @@ -0,0 +1,151 @@ +redact.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Functions to redact strings to remove PII before logging them
+
+/// Redact a URL.
+///
+/// It's tricky to redact an URL without revealing PII.  We check for various known bad URL forms
+/// and report them, otherwise we just log "<URL>".
+pub fn redact_url(url: &str) -> String {
+    if url.is_empty() {
+        return "<URL (empty)>".to_string();
+    }
+    match url.find(':') {
+        None => "<URL (no scheme)>".to_string(),
+        Some(n) => {
+            let mut chars = url[0..n].chars();
+            match chars.next() {
+                // No characters in the scheme
+                None => return "<URL (empty scheme)>".to_string(),
+                Some(c) => {
+                    // First character must be alphabetic
+                    if !c.is_ascii_alphabetic() {
+                        return "<URL (invalid scheme)>".to_string();
+                    }
+                }
+            }
+            for c in chars {
+                // Subsequent characters must be in the set ( alpha | digit | "+" | "-" | "." )
+                if !(c.is_ascii_alphanumeric() || c == '+' || c == '-' || c == '.') {
+                    return "<URL (invalid scheme)>".to_string();
+                }
+            }
+            "<URL>".to_string()
+        }
+    }
+}
+
+/// Redact compact jwe string (Five base64 segments, separated by `.` chars)
+pub fn redact_compact_jwe(url: &str) -> String {
+    url.replace(|ch| ch != '.', "x")
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn test_redact_url() {
+        assert_eq!(redact_url("http://some.website.com/index.html"), "<URL>");
+        assert_eq!(redact_url("about:config"), "<URL>");
+        assert_eq!(redact_url(""), "<URL (empty)>");
+        assert_eq!(redact_url("://some.website.com/"), "<URL (empty scheme)>");
+        assert_eq!(redact_url("some.website.com/"), "<URL (no scheme)>");
+        assert_eq!(redact_url("some.website.com/"), "<URL (no scheme)>");
+        assert_eq!(
+            redact_url("abc%@=://some.website.com/"),
+            "<URL (invalid scheme)>"
+        );
+        assert_eq!(
+            redact_url("0https://some.website.com/"),
+            "<URL (invalid scheme)>"
+        );
+        assert_eq!(
+            redact_url("a+weird-but.lega1-SCHEME://some.website.com/"),
+            "<URL>"
+        );
+    }
+
+    #[test]
+    fn test_redact_compact_jwe() {
+        assert_eq!(redact_compact_jwe("abc.1234.x3243"), "xxx.xxxx.xxxxx")
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/error_support/reporting.rs.html b/book/rust-docs/src/error_support/reporting.rs.html new file mode 100644 index 0000000000..1cb73c04bb --- /dev/null +++ b/book/rust-docs/src/error_support/reporting.rs.html @@ -0,0 +1,143 @@ +reporting.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use parking_lot::RwLock;
+use std::sync::atomic::{AtomicU32, Ordering};
+
+/// Counter for breadcrumb messages
+///
+/// We are currently seeing breadcrumbs that may indicate that the reporting is unreliable.  In
+/// some reports, the breadcrumbs seem like they may be duplicated and/or out of order.  This
+/// counter is a temporary measure to check out that theory.
+static BREADCRUMB_COUNTER: AtomicU32 = AtomicU32::new(0);
+
+fn get_breadcrumb_counter_value() -> u32 {
+    // Notes:
+    //   - fetch_add is specified to wrap around in case of overflow, which seems okay.
+    //   - By itself, this does not guarentee that breadcrumb logs will be ordered the same way as
+    //     the counter values.  If two threads are running at the same time, it's very possible
+    //     that thread A gets the lower breadcrumb value, but thread B wins the race to report its
+    //     breadcrumb. However, if we expect operations to be synchronized, like with places DB,
+    //     then the breadcrumb counter values should always increase by 1.
+    BREADCRUMB_COUNTER.fetch_add(1, Ordering::Relaxed)
+}
+
+/// Application error reporting trait
+///
+/// The application that's consuming application-services implements this via a UniFFI callback
+/// interface, then calls `set_application_error_reporter()` to setup a global
+/// ApplicationErrorReporter.
+pub trait ApplicationErrorReporter: Sync + Send {
+    /// Send an error report to a Sentry-like error reporting system
+    ///
+    /// type_name should be used to group errors together
+    fn report_error(&self, type_name: String, message: String);
+    /// Send a breadcrumb to a Sentry-like error reporting system
+    fn report_breadcrumb(&self, message: String, module: String, line: u32, column: u32);
+}
+
+// ApplicationErrorReporter to use if the app doesn't set one
+struct DefaultApplicationErrorReporter;
+impl ApplicationErrorReporter for DefaultApplicationErrorReporter {
+    fn report_error(&self, _type_name: String, _message: String) {}
+    fn report_breadcrumb(&self, _message: String, _module: String, _line: u32, _column: u32) {}
+}
+
+lazy_static::lazy_static! {
+    // RwLock rather than a Mutex, since we only expect to set this once.
+    pub(crate) static ref APPLICATION_ERROR_REPORTER: RwLock<Box<dyn ApplicationErrorReporter>> = RwLock::new(Box::new(DefaultApplicationErrorReporter));
+}
+
+pub fn set_application_error_reporter(reporter: Box<dyn ApplicationErrorReporter>) {
+    *APPLICATION_ERROR_REPORTER.write() = reporter;
+}
+
+pub fn unset_application_error_reporter() {
+    *APPLICATION_ERROR_REPORTER.write() = Box::new(DefaultApplicationErrorReporter)
+}
+
+pub fn report_error_to_app(type_name: String, message: String) {
+    APPLICATION_ERROR_REPORTER
+        .read()
+        .report_error(type_name, message);
+}
+
+pub fn report_breadcrumb(message: String, module: String, line: u32, column: u32) {
+    let message = format!("{} ({})", message, get_breadcrumb_counter_value());
+    APPLICATION_ERROR_REPORTER
+        .read()
+        .report_breadcrumb(message, module, line, column);
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/error_support_macros/lib.rs.html b/book/rust-docs/src/error_support_macros/lib.rs.html new file mode 100644 index 0000000000..d8d5ff58f0 --- /dev/null +++ b/book/rust-docs/src/error_support_macros/lib.rs.html @@ -0,0 +1,219 @@ +lib.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+use proc_macro::TokenStream;
+use quote::quote;
+use syn::{parse_quote, spanned::Spanned};
+
+const ERR_MSG: &str = "Expected #[handle_error(path::to::Error)]";
+
+/// A procedural macro that exposes internal errors to external errors the
+/// consuming applications should handle. It requires that the internal error
+/// implements [`error_support::ErrorHandling`].
+///
+/// Additionally, this procedural macro has side effects, including:
+/// * It would log the error based on a pre-defined log level. The log level is defined
+///  in the [`error_support::ErrorHandling`] implementation.
+/// * It would report some errors using an external error reporter, in practice, this
+///   is implemented using Sentry in the app.
+///
+/// # Example
+/// ```ignore
+/// use error_support::{handle_error, GetErrorHandling, ErrorHandling};
+/// use std::fmt::Display
+///#[derive(Debug, thiserror::Error)]
+/// struct Error {}
+/// type Result<T, E = Error> = std::result::Result<T, E>;
+
+/// impl Display for Error {
+///     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+///         write!(f, "Internal Error!")
+///     }
+/// }
+///
+/// #[derive(Debug, thiserror::Error)]
+/// struct ExternalError {}
+///
+/// impl Display for ExternalError {
+///     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+///         write!(f, "External Error!")
+///     }
+/// }
+///
+/// impl GetErrorHandling for Error {
+///    type ExternalError = ExternalError;
+///
+///    fn get_error_handling(&self) -> ErrorHandling<Self::ExternalError> {
+///        ErrorHandling::convert(ExternalError {})
+///    }
+/// }
+///
+/// // The `handle_error` macro maps from the error supplied in the mandatory argument
+/// // (ie, `Error` in this example) to the error returned by the function (`ExternalError`
+/// // in this example)
+/// #[handle_error(Error)]
+/// fn do_something() -> std::result::Result<String, ExternalError> {
+///    Err(Error{})
+/// }
+///
+/// // The error here is an `ExternalError`
+/// let _: ExternalError = do_something().unwrap_err();
+/// ```
+#[proc_macro_attribute]
+pub fn handle_error(args: TokenStream, input: TokenStream) -> TokenStream {
+    let mut err_path = None;
+    let parser = syn::meta::parser(|meta| {
+        if meta.input.is_empty() && err_path.replace(meta.path).is_none() {
+            Ok(())
+        } else {
+            Err(syn::Error::new(meta.input.span(), ERR_MSG))
+        }
+    });
+    TokenStream::from(
+        match syn::parse::Parser::parse(parser, args)
+            .map_err(|e| syn::Error::new(e.span(), ERR_MSG))
+            .and_then(|()| syn::parse::<syn::Item>(input))
+            .and_then(|parsed| impl_handle_error(&parsed, err_path.unwrap()))
+        {
+            Ok(res) => res,
+            Err(e) => e.to_compile_error(),
+        },
+    )
+}
+
+fn impl_handle_error(
+    input: &syn::Item,
+    err_path: syn::Path,
+) -> syn::Result<proc_macro2::TokenStream> {
+    if let syn::Item::Fn(item_fn) = input {
+        let original_body = &item_fn.block;
+
+        let mut new_fn = item_fn.clone();
+        new_fn.block = parse_quote! {
+            {
+                (|| -> ::std::result::Result<_, #err_path> {
+                    #original_body
+                })().map_err(::error_support::convert_log_report_error)
+            }
+        };
+
+        Ok(quote! {
+            #new_fn
+        })
+    } else {
+        Err(syn::Error::new(
+            input.span(),
+            "#[handle_error(..)] can only be used on functions",
+        ))
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/examples_fxa_client/devices.rs.html b/book/rust-docs/src/examples_fxa_client/devices.rs.html new file mode 100644 index 0000000000..9cc5cda932 --- /dev/null +++ b/book/rust-docs/src/examples_fxa_client/devices.rs.html @@ -0,0 +1,83 @@ +devices.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use clap::{Args, Subcommand};
+use fxa_client::FirefoxAccount;
+
+use crate::{persist_fxa_state, Result};
+
+#[derive(Args)]
+pub struct DeviceArgs {
+    #[command(subcommand)]
+    command: Option<Command>,
+}
+
+#[derive(Subcommand)]
+enum Command {
+    List,
+    SetName { name: String },
+}
+
+pub fn run(account: &FirefoxAccount, args: DeviceArgs) -> Result<()> {
+    match args.command.unwrap_or(Command::List) {
+        Command::List => list(account),
+        Command::SetName { name } => set_name(account, name),
+    }
+}
+
+fn list(account: &FirefoxAccount) -> Result<()> {
+    for device in account.get_devices(false)? {
+        println!("{}: {}", device.id, device.display_name);
+    }
+    Ok(())
+}
+
+fn set_name(account: &FirefoxAccount, name: String) -> Result<()> {
+    account.set_device_name(&name)?;
+    println!("Display name set to {name}");
+    persist_fxa_state(account)?;
+    Ok(())
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/examples_fxa_client/main.rs.html b/book/rust-docs/src/examples_fxa_client/main.rs.html new file mode 100644 index 0000000000..5f4c4a2150 --- /dev/null +++ b/book/rust-docs/src/examples_fxa_client/main.rs.html @@ -0,0 +1,179 @@ +main.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+mod devices;
+mod send_tab;
+
+use std::fs;
+
+use clap::{Parser, Subcommand, ValueEnum};
+use cli_support::fxa_creds::get_cli_fxa;
+use fxa_client::{FirefoxAccount, FxaConfig, FxaServer};
+
+static CREDENTIALS_PATH: &str = "credentials.json";
+static CLIENT_ID: &str = "a2270f727f45f648";
+static REDIRECT_URI: &str = "https://accounts.firefox.com/oauth/success/a2270f727f45f648";
+
+use anyhow::Result;
+
+#[derive(Parser)]
+#[command(about, long_about = None)]
+struct Cli {
+    /// The FxA server to use
+    #[arg(value_enum, default_value_t = Server::Release)]
+    server: Server,
+
+    #[command(subcommand)]
+    command: Command,
+}
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
+enum Server {
+    /// Official server
+    Release,
+    /// China server
+    China,
+    /// stable dev sever
+    Stable,
+    /// staging dev sever
+    Stage,
+    /// local dev sever
+    LocalDev,
+}
+
+#[derive(Subcommand)]
+enum Command {
+    Devices(devices::DeviceArgs),
+    SendTab(send_tab::SendTabArgs),
+    Disconnect,
+}
+
+fn main() -> Result<()> {
+    let cli = Cli::parse();
+    viaduct_reqwest::use_reqwest_backend();
+
+    println!();
+    let account = load_account(&cli)?;
+    match cli.command {
+        Command::Devices(args) => devices::run(&account, args),
+        Command::SendTab(args) => send_tab::run(&account, args),
+        Command::Disconnect => {
+            account.disconnect();
+            Ok(())
+        }
+    }?;
+
+    Ok(())
+}
+
+fn load_account(cli: &Cli) -> Result<FirefoxAccount> {
+    let config = FxaConfig {
+        server: match cli.server {
+            Server::Release => FxaServer::Release,
+            Server::Stable => FxaServer::Stable,
+            Server::Stage => FxaServer::Stage,
+            Server::China => FxaServer::China,
+            Server::LocalDev => FxaServer::LocalDev,
+        },
+        redirect_uri: REDIRECT_URI.into(),
+        client_id: CLIENT_ID.into(),
+        token_server_url_override: None,
+    };
+    get_cli_fxa(config, CREDENTIALS_PATH).map(|cli| cli.account)
+}
+
+pub fn persist_fxa_state(acct: &FirefoxAccount) -> Result<()> {
+    let json = acct.to_json().unwrap();
+    Ok(fs::write(CREDENTIALS_PATH, json)?)
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/examples_fxa_client/send_tab.rs.html b/book/rust-docs/src/examples_fxa_client/send_tab.rs.html new file mode 100644 index 0000000000..8b0945f643 --- /dev/null +++ b/book/rust-docs/src/examples_fxa_client/send_tab.rs.html @@ -0,0 +1,135 @@ +send_tab.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use clap::{Args, Subcommand};
+use fxa_client::{FirefoxAccount, IncomingDeviceCommand};
+
+use crate::{persist_fxa_state, Result};
+
+#[derive(Args)]
+pub struct SendTabArgs {
+    #[command(subcommand)]
+    command: Command,
+}
+
+#[derive(Subcommand)]
+enum Command {
+    /// Perform a single poll for tabs sent to this device
+    Poll,
+    /// Send a tab to another device
+    Send {
+        /// Device ID (use the `devices` command to list)
+        device_id: String,
+        title: String,
+        url: String,
+    },
+}
+
+pub fn run(account: &FirefoxAccount, args: SendTabArgs) -> Result<()> {
+    match args.command {
+        Command::Poll => poll(account),
+        Command::Send {
+            device_id,
+            title,
+            url,
+        } => send(account, device_id, title, url),
+    }
+}
+
+fn poll(account: &FirefoxAccount) -> Result<()> {
+    println!("Polling for send-tab events.  Ctrl-C to cancel");
+    loop {
+        let events = account.poll_device_commands().unwrap_or_default(); // Ignore 404 errors for now.
+        persist_fxa_state(account)?;
+        if !events.is_empty() {
+            for e in events {
+                match e {
+                    IncomingDeviceCommand::TabReceived { sender, payload } => {
+                        let tab = &payload.entries[0];
+                        match sender {
+                            Some(ref d) => {
+                                println!("Tab received from {}: {}", d.display_name, tab.url)
+                            }
+                            None => println!("Tab received: {}", tab.url),
+                        };
+                    }
+                }
+            }
+        }
+    }
+}
+
+fn send(account: &FirefoxAccount, device_id: String, title: String, url: String) -> Result<()> {
+    account.send_single_tab(&device_id, &title, &url)?;
+    println!("Tab sent!");
+    Ok(())
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/fxa_client/account.rs.html b/book/rust-docs/src/fxa_client/account.rs.html new file mode 100644 index 0000000000..da86b44991 --- /dev/null +++ b/book/rust-docs/src/fxa_client/account.rs.html @@ -0,0 +1,151 @@ +account.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! # Account Management URLs
+//!
+//! Signed-in applications should not attempt to perform an account-level management
+//! (such as changing profile data or managing devices) using native UI. Instead, they
+//! should offer the user the opportunity to visit their account management pages on the
+//! web.
+//!
+//! The methods in this section provide URLs at which the user can perform various
+//! account-management activities.
+
+use crate::{ApiResult, Error, FirefoxAccount};
+use error_support::handle_error;
+
+impl FirefoxAccount {
+    /// Get the token server URL
+    ///
+    /// The token server URL can be used to get the URL and access token for the user's sync data.
+    ///
+    /// **💾 This method alters the persisted account state.**
+    #[handle_error(Error)]
+    pub fn get_token_server_endpoint_url(&self) -> ApiResult<String> {
+        self.internal.lock().get_token_server_endpoint_url()
+    }
+
+    /// Get a URL which shows a "successfully connected!" message.
+    ///
+    /// **💾 This method alters the persisted account state.**
+    ///
+    /// Applications can use this method after a successful signin, to redirect the
+    /// user to a success message displayed in web content rather than having to
+    /// implement their own native success UI.
+    #[handle_error(Error)]
+    pub fn get_connection_success_url(&self) -> ApiResult<String> {
+        self.internal.lock().get_connection_success_url()
+    }
+
+    /// Get a URL at which the user can manage their account and profile data.
+    ///
+    /// **💾 This method alters the persisted account state.**
+    ///
+    /// Applications should link the user out to this URL from an appropriate place
+    /// in their signed-in settings UI.
+    ///
+    /// # Arguments
+    ///
+    ///   - `entrypoint` - metrics identifier for UX entrypoint.
+    ///       - This parameter is used for metrics purposes, to identify the
+    ///         UX entrypoint from which the user followed the link.
+    #[handle_error(Error)]
+    pub fn get_manage_account_url(&self, entrypoint: &str) -> ApiResult<String> {
+        self.internal.lock().get_manage_account_url(entrypoint)
+    }
+
+    /// Get a URL at which the user can manage the devices connected to their account.
+    ///
+    /// **💾 This method alters the persisted account state.**
+    ///
+    /// Applications should link the user out to this URL from an appropriate place
+    /// in their signed-in settings UI. For example, "Manage your devices..." may be
+    /// a useful link to place somewhere near the device list in the send-tab UI.
+    ///
+    /// # Arguments
+    ///
+    ///   - `entrypoint` - metrics identifier for UX entrypoint.
+    ///       - This parameter is used for metrics purposes, to identify the
+    ///         UX entrypoint from which the user followed the link.
+    #[handle_error(Error)]
+    pub fn get_manage_devices_url(&self, entrypoint: &str) -> ApiResult<String> {
+        self.internal.lock().get_manage_devices_url(entrypoint)
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/fxa_client/auth.rs.html b/book/rust-docs/src/fxa_client/auth.rs.html new file mode 100644 index 0000000000..04b470579f --- /dev/null +++ b/book/rust-docs/src/fxa_client/auth.rs.html @@ -0,0 +1,429 @@ +auth.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! # Signing in and out
+//!
+//! These are methods for managing the signed-in state, such as authenticating via
+//! an OAuth flow or disconnecting from the user's account.
+//!
+//! The Firefox Accounts system supports two methods for connecting an application
+//! to a user's account:
+//!
+//!    - A traditional OAuth flow, where the user is directed to a webpage to enter
+//!      their account credentials and then redirected back to the application.
+//!      This is exposed by the [`begin_oauth_flow`](FirefoxAccount::begin_oauth_flow)
+//!      method.
+//!
+//!    - A device pairing flow, where the user scans a QRCode presented by another
+//!      app that is already connected to the account, which then directs them to
+//!      a webpage for a simplified signing flow. This is exposed by the
+//!      [`begin_pairing_flow`](FirefoxAccount::begin_pairing_flow) method.
+//!
+//! Technical details of the pairing flow can be found in the [Firefox Accounts
+//! documentation hub](https://mozilla.github.io/ecosystem-platform/docs/features/firefox-accounts/pairing).
+
+use crate::{ApiResult, Error, FirefoxAccount};
+use error_support::handle_error;
+use std::collections::HashMap;
+
+impl FirefoxAccount {
+    /// Get the high-level authentication state of the client
+    pub fn get_auth_state(&self) -> FxaRustAuthState {
+        self.internal.lock().get_auth_state()
+    }
+
+    /// Initiate a web-based OAuth sign-in flow.
+    ///
+    /// This method initializes some internal state and then returns a URL at which the
+    /// user may perform a web-based authorization flow to connect the application to
+    /// their account. The application should direct the user to the provided URL.
+    ///
+    /// When the resulting OAuth flow redirects back to the configured `redirect_uri`,
+    /// the query parameters should be extracting from the URL and passed to the
+    /// [`complete_oauth_flow`](FirefoxAccount::complete_oauth_flow) method to finalize
+    /// the signin.
+    ///
+    /// # Arguments
+    ///
+    ///   - `scopes` - list of OAuth scopes to request.
+    ///       - The requested scopes will determine what account-related data
+    ///         the application is able to access.
+    ///   - `entrypoint` - metrics identifier for UX entrypoint.
+    ///       - This parameter is used for metrics purposes, to identify the
+    ///         UX entrypoint from which the user triggered the signin request.
+    ///         For example, the application toolbar, on the onboarding flow.
+    ///   - `metrics` - optionally, additional metrics tracking parameters.
+    ///       - These will be included as query parameters in the resulting URL.
+    #[handle_error(Error)]
+    pub fn begin_oauth_flow<T: AsRef<str>>(
+        &self,
+        // Allow both &[String] and &[&str] since UniFFI can't represent `&[&str]` yet,
+        scopes: &[T],
+        entrypoint: &str,
+        metrics: Option<MetricsParams>,
+    ) -> ApiResult<String> {
+        let scopes = scopes.iter().map(T::as_ref).collect::<Vec<_>>();
+        self.internal
+            .lock()
+            .begin_oauth_flow(&scopes, entrypoint, metrics)
+    }
+
+    /// Get the URL at which to begin a device-pairing signin flow.
+    ///
+    /// If the user wants to sign in using device pairing, call this method and then
+    /// direct them to visit the resulting URL on an already-signed-in device. Doing
+    /// so will trigger the other device to show a QR code to be scanned, and the result
+    /// from said QR code can be passed to [`begin_pairing_flow`](FirefoxAccount::begin_pairing_flow).
+    #[handle_error(Error)]
+    pub fn get_pairing_authority_url(&self) -> ApiResult<String> {
+        self.internal.lock().get_pairing_authority_url()
+    }
+
+    /// Initiate a device-pairing sign-in flow.
+    ///
+    /// Once the user has scanned a pairing QR code, pass the scanned value to this
+    /// method. It will return a URL to which the application should redirect the user
+    /// in order to continue the sign-in flow.
+    ///
+    /// When the resulting flow redirects back to the configured `redirect_uri`,
+    /// the resulting OAuth parameters should be extracting from the URL and passed
+    /// to [`complete_oauth_flow`](FirefoxAccount::complete_oauth_flow) to finalize
+    /// the signin.
+    ///
+    /// # Arguments
+    ///
+    ///   - `pairing_url` - the URL scanned from a QR code on another device.
+    ///   - `scopes` - list of OAuth scopes to request.
+    ///       - The requested scopes will determine what account-related data
+    ///         the application is able to access.
+    ///   - `entrypoint` - metrics identifier for UX entrypoint.
+    ///       - This parameter is used for metrics purposes, to identify the
+    ///         UX entrypoint from which the user triggered the signin request.
+    ///         For example, the application toolbar, on the onboarding flow.
+    ///   - `metrics` - optionally, additional metrics tracking parameters.
+    ///       - These will be included as query parameters in the resulting URL.
+    #[handle_error(Error)]
+    pub fn begin_pairing_flow(
+        &self,
+        pairing_url: &str,
+        scopes: &[String],
+        entrypoint: &str,
+        metrics: Option<MetricsParams>,
+    ) -> ApiResult<String> {
+        // UniFFI can't represent `&[&str]` yet, so convert it internally here.
+        let scopes = scopes.iter().map(String::as_str).collect::<Vec<_>>();
+        self.internal
+            .lock()
+            .begin_pairing_flow(pairing_url, &scopes, entrypoint, metrics)
+    }
+
+    /// Complete an OAuth flow.
+    ///
+    /// **💾 This method alters the persisted account state.**
+    ///
+    /// At the conclusion of an OAuth flow, the user will be redirect to the
+    /// application's registered `redirect_uri`. It should extract the `code`
+    /// and `state` parameters from the resulting URL and pass them to this
+    /// method in order to complete the sign-in.
+    ///
+    /// # Arguments
+    ///
+    ///   - `code` - the OAuth authorization code obtained from the redirect URI.
+    ///   - `state` - the OAuth state parameter obtained from the redirect URI.
+    #[handle_error(Error)]
+    pub fn complete_oauth_flow(&self, code: &str, state: &str) -> ApiResult<()> {
+        self.internal.lock().complete_oauth_flow(code, state)
+    }
+
+    /// Check authorization status for this application.
+    ///
+    /// **💾 This method alters the persisted account state.**
+    ///
+    /// Applications may call this method to check with the FxA server about the status
+    /// of their authentication tokens. It returns an [`AuthorizationInfo`] struct
+    /// with details about whether the tokens are still active.
+    #[handle_error(Error)]
+    pub fn check_authorization_status(&self) -> ApiResult<AuthorizationInfo> {
+        Ok(self.internal.lock().check_authorization_status()?.into())
+    }
+
+    /// Disconnect from the user's account.
+    ///
+    /// **💾 This method alters the persisted account state.**
+    ///
+    /// This method destroys any tokens held by the client, effectively disconnecting
+    /// from the user's account. Applications should call this when the user opts to
+    /// sign out.
+    ///
+    /// The persisted account state after calling this method will contain only the
+    /// user's last-seen profile information, if any. This may be useful in helping
+    /// the user to reconnect to their account. If reconnecting to the same account
+    /// is not desired then the application should discard the persisted account state.
+    pub fn disconnect(&self) {
+        self.internal.lock().disconnect()
+    }
+
+    /// Update the state based on authentication issues.
+    ///
+    /// **💾 This method alters the persisted account state.**
+    ///
+    /// Call this if you know there's an authentication / authorization issue that requires the
+    /// user to re-authenticated.  It transitions the user to the [FxaRustAuthState.AuthIssues] state.
+    pub fn on_auth_issues(&self) {
+        self.internal.lock().on_auth_issues()
+    }
+
+    /// Used by the application to test auth token issues
+    pub fn simulate_temporary_auth_token_issue(&self) {
+        self.internal.lock().simulate_temporary_auth_token_issue()
+    }
+
+    /// Used by the application to test auth token issues
+    pub fn simulate_permanent_auth_token_issue(&self) {
+        self.internal.lock().simulate_permanent_auth_token_issue()
+    }
+}
+
+/// Information about the authorization state of the application.
+///
+/// This struct represents metadata about whether the application is currently
+/// connected to the user's account.
+pub struct AuthorizationInfo {
+    pub active: bool,
+}
+
+/// Additional metrics tracking parameters to include in an OAuth request.
+pub struct MetricsParams {
+    pub parameters: HashMap<String, String>,
+}
+
+/// High-level view of the authorization state
+///
+/// This is named `FxaRustAuthState` because it doesn't track all the states we want yet and needs
+/// help from the wrapper code.  The wrapper code defines the actual `FxaAuthState` type based on
+/// this, adding the extra data.
+///
+/// In the long-term, we should track that data in Rust, remove the wrapper, and rename this to
+/// `FxaAuthState`.
+#[derive(Debug, PartialEq, Eq)]
+pub enum FxaRustAuthState {
+    Disconnected,
+    Connected,
+    AuthIssues,
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/fxa_client/device.rs.html b/book/rust-docs/src/fxa_client/device.rs.html new file mode 100644 index 0000000000..a752018698 --- /dev/null +++ b/book/rust-docs/src/fxa_client/device.rs.html @@ -0,0 +1,523 @@ +device.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! # Device Management
+//!
+//! Applications that connect to a user's account may register additional information
+//! about themselves via a "device record", which allows them to:
+//!
+//!    - customize how they appear in the user's account management page
+//!    - receive push notifications about events that happen on the account
+//!    - participate in the FxA "device commands" ecosystem
+//!
+//! For more details on FxA device registration and management, consult the
+//! [Firefox Accounts Device Registration docs](
+//! https://github.com/mozilla/fxa/blob/main/packages/fxa-auth-server/docs/device_registration.md).
+
+use error_support::handle_error;
+use serde::{Deserialize, Serialize};
+use sync15::DeviceType;
+
+use crate::{ApiResult, DevicePushSubscription, Error, FirefoxAccount};
+
+impl FirefoxAccount {
+    /// Create a new device record for this application.
+    ///
+    /// **💾 This method alters the persisted account state.**
+    ///
+    /// This method register a device record for the application, providing basic metadata for
+    /// the device along with a list of supported [Device Capabilities](DeviceCapability) for
+    /// participating in the "device commands" ecosystem.
+    ///
+    /// Applications should call this method soon after a successful sign-in, to ensure
+    /// they they appear correctly in the user's account-management pages and when discovered
+    /// by other devices connected to the account.
+    ///
+    /// # Arguments
+    ///
+    ///    - `name` - human-readable display name to use for this application
+    ///    - `device_type` - the [type](DeviceType) of device the application is installed on
+    ///    - `supported_capabilities` - the set of [capabilities](DeviceCapability) to register
+    ///       for this device in the "device commands" ecosystem.
+    ///
+    /// # Notes
+    ///
+    ///    - Device registration is only available to applications that have been
+    ///      granted the `https://identity.mozilla.com/apps/oldsync` scope.
+    #[handle_error(Error)]
+    pub fn initialize_device(
+        &self,
+        name: &str,
+        device_type: DeviceType,
+        supported_capabilities: Vec<DeviceCapability>,
+    ) -> ApiResult<LocalDevice> {
+        // UniFFI doesn't have good handling of lists of references, work around it.
+        let supported_capabilities: Vec<_> =
+            supported_capabilities.into_iter().map(Into::into).collect();
+        self.internal
+            .lock()
+            .initialize_device(name, device_type, &supported_capabilities)
+    }
+
+    /// Get the device id registered for this application.
+    ///
+    /// # Notes
+    ///
+    ///    - If the application has not registered a device record, this method will
+    ///      throw an [`Other`](FxaError::Other) error.
+    ///        - (Yeah...sorry. This should be changed to do something better.)
+    ///    - Device metadata is only visible to applications that have been
+    ///      granted the `https://identity.mozilla.com/apps/oldsync` scope.
+    #[handle_error(Error)]
+    pub fn get_current_device_id(&self) -> ApiResult<String> {
+        self.internal.lock().get_current_device_id()
+    }
+
+    /// Get the list of devices registered on the user's account.
+    ///
+    /// **💾 This method alters the persisted account state.**
+    ///
+    /// This method returns a list of [`Device`] structs representing all the devices
+    /// currently attached to the user's account (including the current device).
+    /// The application might use this information to e.g. display a list of appropriate
+    /// send-tab targets.
+    ///
+    /// # Arguments
+    ///
+    ///    - `ignore_cache` - if true, always hit the server for fresh profile information.
+    ///
+    /// # Notes
+    ///
+    ///    - Device metadata is only visible to applications that have been
+    ///      granted the `https://identity.mozilla.com/apps/oldsync` scope.
+    #[handle_error(Error)]
+    pub fn get_devices(&self, ignore_cache: bool) -> ApiResult<Vec<Device>> {
+        self.internal
+            .lock()
+            .get_devices(ignore_cache)?
+            .into_iter()
+            .map(TryInto::try_into)
+            .collect::<Result<_, _>>()
+    }
+
+    /// Get the list of all client applications attached to the user's account.
+    ///
+    /// This method returns a list of [`AttachedClient`] structs representing all the applications
+    /// connected to the user's account. This includes applications that are registered as a device
+    /// as well as server-side services that the user has connected.
+    ///
+    /// This information is really only useful for targeted messaging or marketing purposes,
+    /// e.g. if the application wants to advertise a related product, but first wants to check
+    /// whether the user is already using that product.
+    ///
+    /// # Notes
+    ///
+    ///    - Attached client metadata is only visible to applications that have been
+    ///      granted the `https://identity.mozilla.com/apps/oldsync` scope.
+    #[handle_error(Error)]
+    pub fn get_attached_clients(&self) -> ApiResult<Vec<AttachedClient>> {
+        self.internal
+            .lock()
+            .get_attached_clients()?
+            .into_iter()
+            .map(TryInto::try_into)
+            .collect::<Result<_, _>>()
+    }
+
+    /// Update the display name used for this application instance.
+    ///
+    /// **💾 This method alters the persisted account state.**
+    ///
+    /// This method modifies the name of the current application's device record, as seen by
+    /// other applications and in the user's account management pages.
+    ///
+    /// # Arguments
+    ///
+    ///    - `display_name` - the new name for the current device.
+    ///
+    /// # Notes
+    ///
+    ///    - Device registration is only available to applications that have been
+    ///      granted the `https://identity.mozilla.com/apps/oldsync` scope.
+    #[handle_error(Error)]
+    pub fn set_device_name(&self, display_name: &str) -> ApiResult<LocalDevice> {
+        self.internal.lock().set_device_name(display_name)
+    }
+
+    /// Clear any custom display name used for this application instance.
+    ///
+    /// **💾 This method alters the persisted account state.**
+    ///
+    /// This method clears the name of the current application's device record, causing other
+    /// applications or the user's account management pages to have to fill in some sort of
+    /// default name when displaying this device.
+    ///
+    /// # Notes
+    ///
+    ///    - Device registration is only available to applications that have been
+    ///      granted the `https://identity.mozilla.com/apps/oldsync` scope.
+    #[handle_error(Error)]
+    pub fn clear_device_name(&self) -> ApiResult<()> {
+        self.internal.lock().clear_device_name()
+    }
+
+    /// Ensure that the device record has a specific set of capabilities.
+    ///
+    /// **💾 This method alters the persisted account state.**
+    ///
+    /// This method checks that the currently-registered device record is advertising the
+    /// given set of capabilities in the FxA "device commands" ecosystem. If not, then it
+    /// updates the device record to do so.
+    ///
+    /// Applications should call this method on each startup as a way to ensure that their
+    /// expected set of capabilities is being accurately reflected on the FxA server, and
+    /// to handle the rollout of new capabilities over time.
+    ///
+    /// # Arguments
+    ///
+    ///    - `supported_capabilities` - the set of [capabilities](DeviceCapability) to register
+    ///       for this device in the "device commands" ecosystem.
+    ///
+    /// # Notes
+    ///
+    ///    - Device registration is only available to applications that have been
+    ///      granted the `https://identity.mozilla.com/apps/oldsync` scope.
+    #[handle_error(Error)]
+    pub fn ensure_capabilities(
+        &self,
+        supported_capabilities: Vec<DeviceCapability>,
+    ) -> ApiResult<LocalDevice> {
+        let supported_capabilities: Vec<_> =
+            supported_capabilities.into_iter().map(Into::into).collect();
+        self.internal
+            .lock()
+            .ensure_capabilities(&supported_capabilities)
+    }
+}
+
+/// Local device that's connecting to FxA
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct LocalDevice {
+    pub id: String,
+    pub display_name: String,
+    pub device_type: sync15::DeviceType,
+    pub capabilities: Vec<DeviceCapability>,
+    pub push_subscription: Option<DevicePushSubscription>,
+    pub push_endpoint_expired: bool,
+}
+
+/// A device connected to the user's account.
+///
+/// This struct provides metadata about a device connected to the user's account.
+/// This data would typically be used to display e.g. the list of candidate devices
+/// in a "send tab" menu.
+#[derive(Debug)]
+pub struct Device {
+    pub id: String,
+    pub display_name: String,
+    pub device_type: sync15::DeviceType,
+    pub capabilities: Vec<DeviceCapability>,
+    pub push_subscription: Option<DevicePushSubscription>,
+    pub push_endpoint_expired: bool,
+    pub is_current_device: bool,
+    pub last_access_time: Option<i64>,
+}
+
+/// A "capability" offered by a device.
+///
+/// In the FxA ecosystem, connected devices may advertise their ability to respond
+/// to various "commands" that can be invoked by other devices. The details of
+/// executing these commands are encapsulated as part of the FxA Client component,
+/// so consumers simply need to select which ones they want to support, and can
+/// use the variants of this enum to do so.
+///
+/// In practice, the only currently-supported command is the ability to receive a tab.
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
+pub enum DeviceCapability {
+    SendTab,
+}
+
+/// A client connected to the user's account.
+///
+/// This struct provides metadata about a client connected to the user's account.
+/// Unlike the [`Device`] struct, "clients" encompasses both client-side and server-side
+/// applications - basically anything where the user is able to sign in with their
+/// Firefox Account.
+///
+///
+/// This data would typically be used for targeted messaging purposes, catering the
+/// contents of the message to what other applications the user has on their account.
+///
+pub struct AttachedClient {
+    pub client_id: Option<String>,
+    pub device_id: Option<String>,
+    pub device_type: DeviceType,
+    pub is_current_session: bool,
+    pub name: Option<String>,
+    pub created_time: Option<i64>,
+    pub last_access_time: Option<i64>,
+    pub scope: Option<Vec<String>>,
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/fxa_client/error.rs.html b/book/rust-docs/src/fxa_client/error.rs.html new file mode 100644 index 0000000000..7a44e4a84a --- /dev/null +++ b/book/rust-docs/src/fxa_client/error.rs.html @@ -0,0 +1,421 @@ +error.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use error_support::{ErrorHandling, GetErrorHandling};
+use rc_crypto::hawk;
+use std::string;
+
+/// Public error type thrown by many [`FirefoxAccount`] operations.
+///
+/// Precise details of the error are hidden from consumers. The type of the error indicates how the
+/// calling code should respond.
+#[derive(Debug, thiserror::Error)]
+pub enum FxaError {
+    /// Thrown when there was a problem with the authentication status of the account,
+    /// such as an expired token. The application should [check its authorization status](
+    /// FirefoxAccount::check_authorization_status) to see whether it has been disconnected,
+    /// or retry the operation with a freshly-generated token.
+    #[error("authentication error")]
+    Authentication,
+    /// Thrown if an operation fails due to network access problems.
+    /// The application may retry at a later time once connectivity is restored.
+    #[error("network error")]
+    Network,
+    /// Thrown if the application attempts to complete an OAuth flow when no OAuth flow
+    /// has been initiated. This may indicate a user who navigated directly to the OAuth
+    /// `redirect_uri` for the application.
+    ///
+    /// **Note:** This error is currently only thrown in the Swift language bindings.
+    #[error("no authentication flow was active")]
+    NoExistingAuthFlow,
+    /// Thrown if the application attempts to complete an OAuth flow, but the state
+    /// tokens returned from the Firefox Account server do not match with the ones
+    /// expected by the client.
+    /// This may indicate a stale OAuth flow, or potentially an attempted hijacking
+    /// of the flow by an attacker. The signin attempt cannot be completed.
+    ///
+    /// **Note:** This error is currently only thrown in the Swift language bindings.
+    #[error("the requested authentication flow was not active")]
+    WrongAuthFlow,
+    /// A scoped key was missing in the server response when requesting the OLD_SYNC scope.
+    #[error("The sync scoped key was missing")]
+    SyncScopedKeyMissingInServerResponse,
+    /// Thrown if there is a panic in the underlying Rust code.
+    ///
+    /// **Note:** This error is currently only thrown in the Kotlin language bindings.
+    #[error("panic in native code")]
+    Panic,
+    /// A catch-all for other unspecified errors.
+    #[error("other error")]
+    Other,
+}
+
+/// FxA internal error type
+/// These are used in the internal code. This error type is never returned to the consumer.
+#[derive(Debug, thiserror::Error)]
+pub enum Error {
+    #[error("Server asked the client to back off, please wait {0} seconds to try again")]
+    BackoffError(u64),
+
+    #[error("Unknown OAuth State")]
+    UnknownOAuthState,
+
+    #[error("Multiple OAuth scopes requested")]
+    MultipleScopesRequested,
+
+    #[error("No cached token for scope {0}")]
+    NoCachedToken(String),
+
+    #[error("No cached scoped keys for scope {0}")]
+    NoScopedKey(String),
+
+    #[error("No stored refresh token")]
+    NoRefreshToken,
+
+    #[error("No stored session token")]
+    NoSessionToken,
+
+    #[error("No stored migration data")]
+    NoMigrationData,
+
+    #[error("No stored current device id")]
+    NoCurrentDeviceId,
+
+    #[error("Device target is unknown (Device ID: {0})")]
+    UnknownTargetDevice(String),
+
+    #[error("Api client error {0}")]
+    ApiClientError(&'static str),
+
+    #[error("Illegal state: {0}")]
+    IllegalState(&'static str),
+
+    #[error("Unknown command: {0}")]
+    UnknownCommand(String),
+
+    #[error("Send Tab diagnosis error: {0}")]
+    SendTabDiagnosisError(&'static str),
+
+    #[error("Cannot xor arrays with different lengths: {0} and {1}")]
+    XorLengthMismatch(usize, usize),
+
+    #[error("Origin mismatch")]
+    OriginMismatch,
+
+    #[error("Remote key and local key mismatch")]
+    MismatchedKeys,
+
+    #[error("The sync scoped key was missing in the server response")]
+    SyncScopedKeyMissingInServerResponse,
+
+    #[error("Client: {0} is not allowed to request scope: {1}")]
+    ScopeNotAllowed(String, String),
+
+    #[error("Unsupported command: {0}")]
+    UnsupportedCommand(&'static str),
+
+    #[error("Missing URL parameter: {0}")]
+    MissingUrlParameter(&'static str),
+
+    #[error("Null pointer passed to FFI")]
+    NullPointer,
+
+    #[error("Invalid buffer length: {0}")]
+    InvalidBufferLength(i32),
+
+    #[error("Too many calls to auth introspection endpoint")]
+    AuthCircuitBreakerError,
+
+    #[error("Remote server error: '{code}' '{errno}' '{error}' '{message}' '{info}'")]
+    RemoteError {
+        code: u64,
+        errno: u64,
+        error: String,
+        message: String,
+        info: String,
+    },
+
+    // Basically reimplement error_chain's foreign_links. (Ugh, this sucks).
+    #[error("Crypto/NSS error: {0}")]
+    CryptoError(#[from] rc_crypto::Error),
+
+    #[error("http-ece encryption error: {0}")]
+    EceError(#[from] rc_crypto::ece::Error),
+
+    #[error("Hex decode error: {0}")]
+    HexDecodeError(#[from] hex::FromHexError),
+
+    #[error("Base64 decode error: {0}")]
+    Base64Decode(#[from] base64::DecodeError),
+
+    #[error("JSON error: {0}")]
+    JsonError(#[from] serde_json::Error),
+
+    #[error("JWCrypto error: {0}")]
+    JwCryptoError(#[from] jwcrypto::JwCryptoError),
+
+    #[error("UTF8 decode error: {0}")]
+    UTF8DecodeError(#[from] string::FromUtf8Error),
+
+    #[error("Network error: {0}")]
+    RequestError(#[from] viaduct::Error),
+
+    #[error("Malformed URL error: {0}")]
+    MalformedUrl(#[from] url::ParseError),
+
+    #[error("Unexpected HTTP status: {0}")]
+    UnexpectedStatus(#[from] viaduct::UnexpectedStatus),
+
+    #[error("Sync15 error: {0}")]
+    SyncError(#[from] sync15::Error),
+
+    #[error("HAWK error: {0}")]
+    HawkError(#[from] hawk::Error),
+
+    #[error("Integer conversion error: {0}")]
+    IntegerConversionError(#[from] std::num::TryFromIntError),
+
+    #[error("Command not found by fxa")]
+    CommandNotFound,
+
+    #[error("Invalid Push Event")]
+    InvalidPushEvent,
+}
+
+// Define how our internal errors are handled and converted to external errors
+// See `support/error/README.md` for how this works, especially the warning about PII.
+impl GetErrorHandling for Error {
+    type ExternalError = FxaError;
+
+    fn get_error_handling(&self) -> ErrorHandling<Self::ExternalError> {
+        match self {
+            Error::RemoteError { code: 401, .. }
+            | Error::NoRefreshToken
+            | Error::NoScopedKey(_)
+            | Error::NoCachedToken(_) => {
+                ErrorHandling::convert(crate::FxaError::Authentication).log_warning()
+            }
+            Error::RequestError(_) => {
+                ErrorHandling::convert(crate::FxaError::Network).log_warning()
+            }
+            Error::SyncScopedKeyMissingInServerResponse => {
+                ErrorHandling::convert(crate::FxaError::SyncScopedKeyMissingInServerResponse)
+                    .report_error("fxa-client-scoped-key-missing")
+            }
+            _ => ErrorHandling::convert(crate::FxaError::Other)
+                .report_error("fxa-client-other-error"),
+        }
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/fxa_client/internal/commands/mod.rs.html b/book/rust-docs/src/fxa_client/internal/commands/mod.rs.html new file mode 100644 index 0000000000..8b76876f8e --- /dev/null +++ b/book/rust-docs/src/fxa_client/internal/commands/mod.rs.html @@ -0,0 +1,65 @@ +mod.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+pub mod send_tab;
+pub use send_tab::SendTabPayload;
+
+use super::device::Device;
+use crate::{Error, Result};
+
+// Currently public for use by example crates, but should be made private eventually.
+#[derive(Clone, Debug)]
+pub enum IncomingDeviceCommand {
+    TabReceived {
+        sender: Option<Device>,
+        payload: SendTabPayload,
+    },
+}
+
+impl TryFrom<IncomingDeviceCommand> for crate::IncomingDeviceCommand {
+    type Error = Error;
+    fn try_from(cmd: IncomingDeviceCommand) -> Result<Self> {
+        Ok(match cmd {
+            IncomingDeviceCommand::TabReceived { sender, payload } => {
+                crate::IncomingDeviceCommand::TabReceived {
+                    sender: sender.map(crate::Device::try_from).transpose()?,
+                    payload: payload.into(),
+                }
+            }
+        })
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/fxa_client/internal/commands/send_tab.rs.html b/book/rust-docs/src/fxa_client/internal/commands/send_tab.rs.html new file mode 100644 index 0000000000..7b8d7bcf07 --- /dev/null +++ b/book/rust-docs/src/fxa_client/internal/commands/send_tab.rs.html @@ -0,0 +1,535 @@ +send_tab.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/// The Send Tab functionality is backed by Firefox Accounts device commands.
+/// A device shows it can handle "Send Tab" commands by advertising the "open-uri"
+/// command in its on own device record.
+/// This command data bundle contains a one-time generated `PublicSendTabKeys`
+/// (while keeping locally `PrivateSendTabKeys` containing the private key),
+/// wrapped by the account oldsync scope `kSync` to form a `SendTabKeysPayload`.
+///
+/// When a device sends a tab to another, it decrypts that `SendTabKeysPayload` using `kSync`,
+/// uses the obtained public key to encrypt the `SendTabPayload` it created that
+/// contains the tab to send and finally forms the `EncryptedSendTabPayload` that is
+/// then sent to the target device.
+use serde_derive::*;
+
+use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
+use rc_crypto::ece::{self, EcKeyComponents};
+use sync15::{EncryptedPayload, KeyBundle};
+
+use super::super::{device::Device, scopes, telemetry};
+use crate::{Error, Result, ScopedKey};
+
+pub const COMMAND_NAME: &str = "https://identity.mozilla.com/cmd/open-uri";
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct EncryptedSendTabPayload {
+    /// URL Safe Base 64 encrypted send-tab payload.
+    encrypted: String,
+}
+
+impl EncryptedSendTabPayload {
+    pub(crate) fn decrypt(self, keys: &PrivateSendTabKeysV1) -> Result<SendTabPayload> {
+        rc_crypto::ensure_initialized();
+        let encrypted = URL_SAFE_NO_PAD.decode(self.encrypted)?;
+        let decrypted = ece::decrypt(&keys.p256key, &keys.auth_secret, &encrypted)?;
+        Ok(serde_json::from_slice(&decrypted)?)
+    }
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize)]
+pub struct SendTabPayload {
+    pub entries: Vec<TabHistoryEntry>,
+    #[serde(rename = "flowID", default)]
+    pub flow_id: String,
+    #[serde(rename = "streamID", default)]
+    pub stream_id: String,
+}
+
+impl From<SendTabPayload> for crate::SendTabPayload {
+    fn from(payload: SendTabPayload) -> Self {
+        crate::SendTabPayload {
+            entries: payload.entries.into_iter().map(From::from).collect(),
+            flow_id: payload.flow_id,
+            stream_id: payload.stream_id,
+        }
+    }
+}
+
+impl SendTabPayload {
+    pub fn single_tab(title: &str, url: &str) -> (Self, telemetry::SentCommand) {
+        let sent_telemetry: telemetry::SentCommand = Default::default();
+        (
+            SendTabPayload {
+                entries: vec![TabHistoryEntry {
+                    title: title.to_string(),
+                    url: url.to_string(),
+                }],
+                flow_id: sent_telemetry.flow_id.clone(),
+                stream_id: sent_telemetry.stream_id.clone(),
+            },
+            sent_telemetry,
+        )
+    }
+    fn encrypt(&self, keys: PublicSendTabKeys) -> Result<EncryptedSendTabPayload> {
+        rc_crypto::ensure_initialized();
+        let bytes = serde_json::to_vec(&self)?;
+        let public_key = URL_SAFE_NO_PAD.decode(&keys.public_key)?;
+        let auth_secret = URL_SAFE_NO_PAD.decode(&keys.auth_secret)?;
+        let encrypted = ece::encrypt(&public_key, &auth_secret, &bytes)?;
+        let encrypted = URL_SAFE_NO_PAD.encode(encrypted);
+        Ok(EncryptedSendTabPayload { encrypted })
+    }
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize)]
+pub struct TabHistoryEntry {
+    pub title: String,
+    pub url: String,
+}
+
+impl From<TabHistoryEntry> for crate::TabHistoryEntry {
+    fn from(e: TabHistoryEntry) -> Self {
+        crate::TabHistoryEntry {
+            title: e.title,
+            url: e.url,
+        }
+    }
+}
+
+#[derive(Serialize, Deserialize, Clone)]
+pub(crate) enum VersionnedPrivateSendTabKeys {
+    V1(PrivateSendTabKeysV1),
+}
+
+#[derive(Serialize, Deserialize, Clone)]
+pub(crate) struct PrivateSendTabKeysV1 {
+    p256key: EcKeyComponents,
+    auth_secret: Vec<u8>,
+}
+pub(crate) type PrivateSendTabKeys = PrivateSendTabKeysV1;
+
+impl PrivateSendTabKeys {
+    // We define this method so the type-checker prevents us from
+    // trying to serialize `PrivateSendTabKeys` directly since
+    // `serde_json::to_string` would compile because both types derive
+    // `Serialize`.
+    pub(crate) fn serialize(&self) -> Result<String> {
+        Ok(serde_json::to_string(&VersionnedPrivateSendTabKeys::V1(
+            self.clone(),
+        ))?)
+    }
+
+    pub(crate) fn deserialize(s: &str) -> Result<Self> {
+        let versionned: VersionnedPrivateSendTabKeys = serde_json::from_str(s)?;
+        match versionned {
+            VersionnedPrivateSendTabKeys::V1(prv_key) => Ok(prv_key),
+        }
+    }
+}
+
+impl PrivateSendTabKeys {
+    pub fn from_random() -> Result<Self> {
+        rc_crypto::ensure_initialized();
+        let (key_pair, auth_secret) = ece::generate_keypair_and_auth_secret()?;
+        Ok(Self {
+            p256key: key_pair.raw_components()?,
+            auth_secret: auth_secret.to_vec(),
+        })
+    }
+}
+
+#[derive(Serialize, Deserialize)]
+pub(crate) struct SendTabKeysPayload {
+    /// Hex encoded kid.
+    kid: String,
+    /// Base 64 encoded IV.
+    #[serde(rename = "IV")]
+    iv: String,
+    /// Hex encoded hmac.
+    hmac: String,
+    /// Base 64 encoded ciphertext.
+    ciphertext: String,
+}
+
+impl SendTabKeysPayload {
+    pub(crate) fn decrypt(self, scoped_key: &ScopedKey) -> Result<PublicSendTabKeys> {
+        let (ksync, kxcs) = extract_oldsync_key_components(scoped_key)?;
+        if hex::decode(self.kid)? != kxcs {
+            return Err(Error::MismatchedKeys);
+        }
+        let key = KeyBundle::from_ksync_bytes(&ksync)?;
+        let encrypted_payload = EncryptedPayload {
+            iv: self.iv,
+            hmac: self.hmac,
+            ciphertext: self.ciphertext,
+        };
+        Ok(encrypted_payload.decrypt_into(&key)?)
+    }
+}
+
+#[derive(Serialize, Deserialize)]
+pub struct PublicSendTabKeys {
+    /// URL Safe Base 64 encoded push public key.
+    #[serde(rename = "publicKey")]
+    public_key: String,
+    /// URL Safe Base 64 encoded auth secret.
+    #[serde(rename = "authSecret")]
+    auth_secret: String,
+}
+
+impl PublicSendTabKeys {
+    fn encrypt(&self, scoped_key: &ScopedKey) -> Result<SendTabKeysPayload> {
+        let (ksync, kxcs) = extract_oldsync_key_components(scoped_key)?;
+        let key = KeyBundle::from_ksync_bytes(&ksync)?;
+        let encrypted_payload = EncryptedPayload::from_cleartext_payload(&key, &self)?;
+        Ok(SendTabKeysPayload {
+            kid: hex::encode(kxcs),
+            iv: encrypted_payload.iv,
+            hmac: encrypted_payload.hmac,
+            ciphertext: encrypted_payload.ciphertext,
+        })
+    }
+    pub fn as_command_data(&self, scoped_key: &ScopedKey) -> Result<String> {
+        let encrypted_public_keys = self.encrypt(scoped_key)?;
+        Ok(serde_json::to_string(&encrypted_public_keys)?)
+    }
+    pub(crate) fn public_key(&self) -> &str {
+        &self.public_key
+    }
+    pub(crate) fn auth_secret(&self) -> &str {
+        &self.auth_secret
+    }
+}
+
+impl From<PrivateSendTabKeys> for PublicSendTabKeys {
+    fn from(internal: PrivateSendTabKeys) -> Self {
+        Self {
+            public_key: URL_SAFE_NO_PAD.encode(internal.p256key.public_key()),
+            auth_secret: URL_SAFE_NO_PAD.encode(&internal.auth_secret),
+        }
+    }
+}
+
+pub fn build_send_command(
+    scoped_key: &ScopedKey,
+    target: &Device,
+    send_tab_payload: &SendTabPayload,
+) -> Result<serde_json::Value> {
+    let command = target
+        .available_commands
+        .get(COMMAND_NAME)
+        .ok_or(Error::UnsupportedCommand(COMMAND_NAME))?;
+    let bundle: SendTabKeysPayload = serde_json::from_str(command)?;
+    let public_keys = bundle.decrypt(scoped_key)?;
+    let encrypted_payload = send_tab_payload.encrypt(public_keys)?;
+    Ok(serde_json::to_value(encrypted_payload)?)
+}
+
+fn extract_oldsync_key_components(oldsync_key: &ScopedKey) -> Result<(Vec<u8>, Vec<u8>)> {
+    if oldsync_key.scope != scopes::OLD_SYNC {
+        return Err(Error::IllegalState(
+            "Only oldsync scoped keys are supported at the moment.",
+        ));
+    }
+    let kxcs: &str = oldsync_key.kid.splitn(2, '-').collect::<Vec<_>>()[1];
+    let kxcs = URL_SAFE_NO_PAD.decode(kxcs)?;
+    let ksync = oldsync_key.key_bytes()?;
+    Ok((ksync, kxcs))
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_minimal_parse_payload() {
+        let minimal = r#"{ "entries": []}"#;
+        let payload: SendTabPayload = serde_json::from_str(minimal).expect("should work");
+        assert_eq!(payload.flow_id, "".to_string());
+    }
+
+    #[test]
+    fn test_payload() {
+        let (payload, telem) = SendTabPayload::single_tab("title", "http://example.com");
+        let json = serde_json::to_string(&payload).expect("should work");
+        assert_eq!(telem.flow_id.len(), 12);
+        assert_eq!(telem.stream_id.len(), 12);
+        assert_ne!(telem.flow_id, telem.stream_id);
+        let p2: SendTabPayload = serde_json::from_str(&json).expect("should work");
+        // no 'PartialEq' derived so check each field individually...
+        assert_eq!(payload.entries[0].url, "http://example.com".to_string());
+        assert_eq!(payload.flow_id, p2.flow_id);
+        assert_eq!(payload.stream_id, p2.stream_id);
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/fxa_client/internal/config.rs.html b/book/rust-docs/src/fxa_client/internal/config.rs.html new file mode 100644 index 0000000000..2a2d9add18 --- /dev/null +++ b/book/rust-docs/src/fxa_client/internal/config.rs.html @@ -0,0 +1,727 @@ +config.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use super::http_client;
+use crate::{FxaConfig, Result};
+use serde_derive::{Deserialize, Serialize};
+use std::{cell::RefCell, sync::Arc};
+use url::Url;
+
+#[derive(Clone, Debug, Serialize, Deserialize)]
+pub struct Config {
+    content_url: String,
+    token_server_url_override: Option<String>,
+    pub client_id: String,
+    pub redirect_uri: String,
+    // RemoteConfig is lazily fetched from the server.
+    #[serde(skip)]
+    remote_config: RefCell<Option<Arc<RemoteConfig>>>,
+}
+
+/// `RemoteConfig` struct stores configuration values from the FxA
+/// `/.well-known/fxa-client-configuration` and the
+/// `/.well-known/openid-configuration` endpoints.
+#[derive(Debug)]
+// allow(dead_code) since we want the struct to match the API data, even if some fields aren't
+// currently used.
+#[allow(dead_code)]
+pub struct RemoteConfig {
+    auth_url: String,
+    oauth_url: String,
+    profile_url: String,
+    token_server_endpoint_url: String,
+    authorization_endpoint: String,
+    issuer: String,
+    jwks_uri: String,
+    token_endpoint: String,
+    userinfo_endpoint: String,
+    introspection_endpoint: String,
+}
+
+pub(crate) const CONTENT_URL_RELEASE: &str = "https://accounts.firefox.com";
+pub(crate) const CONTENT_URL_CHINA: &str = "https://accounts.firefox.com.cn";
+
+impl Config {
+    fn remote_config(&self) -> Result<Arc<RemoteConfig>> {
+        if let Some(remote_config) = self.remote_config.borrow().clone() {
+            return Ok(remote_config);
+        }
+
+        let client_config = http_client::fxa_client_configuration(self.client_config_url()?)?;
+        let openid_config = http_client::openid_configuration(self.openid_config_url()?)?;
+
+        let remote_config = self.set_remote_config(RemoteConfig {
+            auth_url: format!("{}/", client_config.auth_server_base_url),
+            oauth_url: format!("{}/", client_config.oauth_server_base_url),
+            profile_url: format!("{}/", client_config.profile_server_base_url),
+            token_server_endpoint_url: format!("{}/", client_config.sync_tokenserver_base_url),
+            authorization_endpoint: openid_config.authorization_endpoint,
+            issuer: openid_config.issuer,
+            jwks_uri: openid_config.jwks_uri,
+            // TODO: bring back openid token endpoint once https://github.com/mozilla/fxa/issues/453 has been resolved
+            // and the openid reponse has been switched to the new endpoint.
+            // token_endpoint: openid_config.token_endpoint,
+            token_endpoint: format!("{}/v1/oauth/token", client_config.auth_server_base_url),
+            userinfo_endpoint: openid_config.userinfo_endpoint,
+            introspection_endpoint: openid_config.introspection_endpoint,
+        });
+        Ok(remote_config)
+    }
+
+    fn set_remote_config(&self, remote_config: RemoteConfig) -> Arc<RemoteConfig> {
+        let rc = Arc::new(remote_config);
+        let result = rc.clone();
+        self.remote_config.replace(Some(rc));
+        result
+    }
+
+    pub fn content_url(&self) -> Result<Url> {
+        Url::parse(&self.content_url).map_err(Into::into)
+    }
+
+    pub fn content_url_path(&self, path: &str) -> Result<Url> {
+        self.content_url()?.join(path).map_err(Into::into)
+    }
+
+    pub fn client_config_url(&self) -> Result<Url> {
+        self.content_url_path(".well-known/fxa-client-configuration")
+    }
+
+    pub fn openid_config_url(&self) -> Result<Url> {
+        self.content_url_path(".well-known/openid-configuration")
+    }
+
+    pub fn connect_another_device_url(&self) -> Result<Url> {
+        self.content_url_path("connect_another_device")
+            .map_err(Into::into)
+    }
+
+    pub fn pair_url(&self) -> Result<Url> {
+        self.content_url_path("pair").map_err(Into::into)
+    }
+
+    pub fn pair_supp_url(&self) -> Result<Url> {
+        self.content_url_path("pair/supp").map_err(Into::into)
+    }
+
+    pub fn oauth_force_auth_url(&self) -> Result<Url> {
+        self.content_url_path("oauth/force_auth")
+            .map_err(Into::into)
+    }
+
+    pub fn settings_url(&self) -> Result<Url> {
+        self.content_url_path("settings").map_err(Into::into)
+    }
+
+    pub fn settings_clients_url(&self) -> Result<Url> {
+        self.content_url_path("settings/clients")
+            .map_err(Into::into)
+    }
+
+    pub fn auth_url(&self) -> Result<Url> {
+        Url::parse(&self.remote_config()?.auth_url).map_err(Into::into)
+    }
+
+    pub fn auth_url_path(&self, path: &str) -> Result<Url> {
+        self.auth_url()?.join(path).map_err(Into::into)
+    }
+
+    pub fn oauth_url(&self) -> Result<Url> {
+        Url::parse(&self.remote_config()?.oauth_url).map_err(Into::into)
+    }
+
+    pub fn oauth_url_path(&self, path: &str) -> Result<Url> {
+        self.oauth_url()?.join(path).map_err(Into::into)
+    }
+
+    pub fn token_server_endpoint_url(&self) -> Result<Url> {
+        if let Some(token_server_url_override) = &self.token_server_url_override {
+            return Ok(Url::parse(token_server_url_override)?);
+        }
+        Ok(Url::parse(
+            &self.remote_config()?.token_server_endpoint_url,
+        )?)
+    }
+
+    pub fn authorization_endpoint(&self) -> Result<Url> {
+        Url::parse(&self.remote_config()?.authorization_endpoint).map_err(Into::into)
+    }
+
+    pub fn token_endpoint(&self) -> Result<Url> {
+        Url::parse(&self.remote_config()?.token_endpoint).map_err(Into::into)
+    }
+
+    pub fn introspection_endpoint(&self) -> Result<Url> {
+        Url::parse(&self.remote_config()?.introspection_endpoint).map_err(Into::into)
+    }
+
+    pub fn userinfo_endpoint(&self) -> Result<Url> {
+        Url::parse(&self.remote_config()?.userinfo_endpoint).map_err(Into::into)
+    }
+
+    fn normalize_token_server_url(token_server_url_override: &str) -> String {
+        // In self-hosting setups it is common to specify the `/1.0/sync/1.5` suffix on the
+        // tokenserver URL. Accept and strip this form as a convenience for users.
+        // (ideally we'd use `strip_suffix`, but we currently target a rust version
+        // where this doesn't exist - `trim_end_matches` will repeatedly remove
+        // the suffix, but that seems fine for this use-case)
+        token_server_url_override
+            .trim_end_matches("/1.0/sync/1.5")
+            .to_owned()
+    }
+}
+
+impl From<FxaConfig> for Config {
+    fn from(fxa_config: FxaConfig) -> Self {
+        let content_url = fxa_config.server.content_url().to_string();
+        let token_server_url_override = fxa_config
+            .token_server_url_override
+            .as_deref()
+            .map(Self::normalize_token_server_url);
+
+        Self {
+            content_url,
+            client_id: fxa_config.client_id,
+            redirect_uri: fxa_config.redirect_uri,
+            token_server_url_override,
+            remote_config: RefCell::new(None),
+        }
+    }
+}
+
+#[cfg(test)]
+/// Testing functionality
+impl Config {
+    pub fn release(client_id: &str, redirect_uri: &str) -> Self {
+        Self::new(CONTENT_URL_RELEASE, client_id, redirect_uri)
+    }
+
+    pub fn stable_dev(client_id: &str, redirect_uri: &str) -> Self {
+        Self::new("https://stable.dev.lcip.org", client_id, redirect_uri)
+    }
+
+    pub fn china(client_id: &str, redirect_uri: &str) -> Self {
+        Self::new(CONTENT_URL_CHINA, client_id, redirect_uri)
+    }
+
+    pub fn new(content_url: &str, client_id: &str, redirect_uri: &str) -> Self {
+        Self {
+            content_url: content_url.to_string(),
+            client_id: client_id.to_string(),
+            redirect_uri: redirect_uri.to_string(),
+            remote_config: RefCell::new(None),
+            token_server_url_override: None,
+        }
+    }
+
+    /// Override the token server URL that would otherwise be provided by the
+    /// FxA .well-known/fxa-client-configuration endpoint.
+    /// This is used by self-hosters that still use the product FxA servers
+    /// for authentication purposes but use their own Sync storage backend.
+    pub fn override_token_server_url<'a>(
+        &'a mut self,
+        token_server_url_override: &str,
+    ) -> &'a mut Self {
+        self.token_server_url_override =
+            Some(Self::normalize_token_server_url(token_server_url_override));
+        self
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_paths() {
+        let remote_config = RemoteConfig {
+            auth_url: "https://stable.dev.lcip.org/auth/".to_string(),
+            oauth_url: "https://oauth-stable.dev.lcip.org/".to_string(),
+            profile_url: "https://stable.dev.lcip.org/profile/".to_string(),
+            token_server_endpoint_url: "https://stable.dev.lcip.org/syncserver/token/1.0/sync/1.5"
+                .to_string(),
+            authorization_endpoint: "https://oauth-stable.dev.lcip.org/v1/authorization"
+                .to_string(),
+            issuer: "https://dev.lcip.org/".to_string(),
+            jwks_uri: "https://oauth-stable.dev.lcip.org/v1/jwks".to_string(),
+            token_endpoint: "https://stable.dev.lcip.org/auth/v1/oauth/token".to_string(),
+            introspection_endpoint: "https://oauth-stable.dev.lcip.org/v1/introspect".to_string(),
+            userinfo_endpoint: "https://stable.dev.lcip.org/profile/v1/profile".to_string(),
+        };
+
+        let config = Config {
+            content_url: "https://stable.dev.lcip.org/".to_string(),
+            remote_config: RefCell::new(Some(Arc::new(remote_config))),
+            client_id: "263ceaa5546dce83".to_string(),
+            redirect_uri: "https://127.0.0.1:8080".to_string(),
+            token_server_url_override: None,
+        };
+        assert_eq!(
+            config.auth_url_path("v1/account/keys").unwrap().to_string(),
+            "https://stable.dev.lcip.org/auth/v1/account/keys"
+        );
+        assert_eq!(
+            config.oauth_url_path("v1/token").unwrap().to_string(),
+            "https://oauth-stable.dev.lcip.org/v1/token"
+        );
+        assert_eq!(
+            config.content_url_path("oauth/signin").unwrap().to_string(),
+            "https://stable.dev.lcip.org/oauth/signin"
+        );
+        assert_eq!(
+            config.token_server_endpoint_url().unwrap().to_string(),
+            "https://stable.dev.lcip.org/syncserver/token/1.0/sync/1.5"
+        );
+
+        assert_eq!(
+            config.token_endpoint().unwrap().to_string(),
+            "https://stable.dev.lcip.org/auth/v1/oauth/token"
+        );
+
+        assert_eq!(
+            config.introspection_endpoint().unwrap().to_string(),
+            "https://oauth-stable.dev.lcip.org/v1/introspect"
+        );
+    }
+
+    #[test]
+    fn test_tokenserver_url_override() {
+        let remote_config = RemoteConfig {
+            auth_url: "https://stable.dev.lcip.org/auth/".to_string(),
+            oauth_url: "https://oauth-stable.dev.lcip.org/".to_string(),
+            profile_url: "https://stable.dev.lcip.org/profile/".to_string(),
+            token_server_endpoint_url: "https://stable.dev.lcip.org/syncserver/token/1.0/sync/1.5"
+                .to_string(),
+            authorization_endpoint: "https://oauth-stable.dev.lcip.org/v1/authorization"
+                .to_string(),
+            issuer: "https://dev.lcip.org/".to_string(),
+            jwks_uri: "https://oauth-stable.dev.lcip.org/v1/jwks".to_string(),
+            token_endpoint: "https://stable.dev.lcip.org/auth/v1/oauth/token".to_string(),
+            introspection_endpoint: "https://oauth-stable.dev.lcip.org/v1/introspect".to_string(),
+            userinfo_endpoint: "https://stable.dev.lcip.org/profile/v1/profile".to_string(),
+        };
+
+        let mut config = Config {
+            content_url: "https://stable.dev.lcip.org/".to_string(),
+            remote_config: RefCell::new(Some(Arc::new(remote_config))),
+            client_id: "263ceaa5546dce83".to_string(),
+            redirect_uri: "https://127.0.0.1:8080".to_string(),
+            token_server_url_override: None,
+        };
+
+        config.override_token_server_url("https://foo.bar");
+
+        assert_eq!(
+            config.token_server_endpoint_url().unwrap().to_string(),
+            "https://foo.bar/"
+        );
+    }
+
+    #[test]
+    fn test_tokenserver_url_override_strips_sync_service_prefix() {
+        let remote_config = RemoteConfig {
+            auth_url: "https://stable.dev.lcip.org/auth/".to_string(),
+            oauth_url: "https://oauth-stable.dev.lcip.org/".to_string(),
+            profile_url: "https://stable.dev.lcip.org/profile/".to_string(),
+            token_server_endpoint_url: "https://stable.dev.lcip.org/syncserver/token/".to_string(),
+            authorization_endpoint: "https://oauth-stable.dev.lcip.org/v1/authorization"
+                .to_string(),
+            issuer: "https://dev.lcip.org/".to_string(),
+            jwks_uri: "https://oauth-stable.dev.lcip.org/v1/jwks".to_string(),
+            token_endpoint: "https://stable.dev.lcip.org/auth/v1/oauth/token".to_string(),
+            introspection_endpoint: "https://oauth-stable.dev.lcip.org/v1/introspect".to_string(),
+            userinfo_endpoint: "https://stable.dev.lcip.org/profile/v1/profile".to_string(),
+        };
+
+        let mut config = Config {
+            content_url: "https://stable.dev.lcip.org/".to_string(),
+            remote_config: RefCell::new(Some(Arc::new(remote_config))),
+            client_id: "263ceaa5546dce83".to_string(),
+            redirect_uri: "https://127.0.0.1:8080".to_string(),
+            token_server_url_override: None,
+        };
+
+        config.override_token_server_url("https://foo.bar/prefix/1.0/sync/1.5");
+        assert_eq!(
+            config.token_server_endpoint_url().unwrap().to_string(),
+            "https://foo.bar/prefix"
+        );
+
+        config.override_token_server_url("https://foo.bar/prefix-1.0/sync/1.5");
+        assert_eq!(
+            config.token_server_endpoint_url().unwrap().to_string(),
+            "https://foo.bar/prefix-1.0/sync/1.5"
+        );
+
+        config.override_token_server_url("https://foo.bar/1.0/sync/1.5/foobar");
+        assert_eq!(
+            config.token_server_endpoint_url().unwrap().to_string(),
+            "https://foo.bar/1.0/sync/1.5/foobar"
+        );
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/fxa_client/internal/device.rs.html b/book/rust-docs/src/fxa_client/internal/device.rs.html new file mode 100644 index 0000000000..39630d3647 --- /dev/null +++ b/book/rust-docs/src/fxa_client/internal/device.rs.html @@ -0,0 +1,1703 @@ +device.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
+540
+541
+542
+543
+544
+545
+546
+547
+548
+549
+550
+551
+552
+553
+554
+555
+556
+557
+558
+559
+560
+561
+562
+563
+564
+565
+566
+567
+568
+569
+570
+571
+572
+573
+574
+575
+576
+577
+578
+579
+580
+581
+582
+583
+584
+585
+586
+587
+588
+589
+590
+591
+592
+593
+594
+595
+596
+597
+598
+599
+600
+601
+602
+603
+604
+605
+606
+607
+608
+609
+610
+611
+612
+613
+614
+615
+616
+617
+618
+619
+620
+621
+622
+623
+624
+625
+626
+627
+628
+629
+630
+631
+632
+633
+634
+635
+636
+637
+638
+639
+640
+641
+642
+643
+644
+645
+646
+647
+648
+649
+650
+651
+652
+653
+654
+655
+656
+657
+658
+659
+660
+661
+662
+663
+664
+665
+666
+667
+668
+669
+670
+671
+672
+673
+674
+675
+676
+677
+678
+679
+680
+681
+682
+683
+684
+685
+686
+687
+688
+689
+690
+691
+692
+693
+694
+695
+696
+697
+698
+699
+700
+701
+702
+703
+704
+705
+706
+707
+708
+709
+710
+711
+712
+713
+714
+715
+716
+717
+718
+719
+720
+721
+722
+723
+724
+725
+726
+727
+728
+729
+730
+731
+732
+733
+734
+735
+736
+737
+738
+739
+740
+741
+742
+743
+744
+745
+746
+747
+748
+749
+750
+751
+752
+753
+754
+755
+756
+757
+758
+759
+760
+761
+762
+763
+764
+765
+766
+767
+768
+769
+770
+771
+772
+773
+774
+775
+776
+777
+778
+779
+780
+781
+782
+783
+784
+785
+786
+787
+788
+789
+790
+791
+792
+793
+794
+795
+796
+797
+798
+799
+800
+801
+802
+803
+804
+805
+806
+807
+808
+809
+810
+811
+812
+813
+814
+815
+816
+817
+818
+819
+820
+821
+822
+823
+824
+825
+826
+827
+828
+829
+830
+831
+832
+833
+834
+835
+836
+837
+838
+839
+840
+841
+842
+843
+844
+845
+846
+847
+848
+849
+850
+851
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use std::collections::{HashMap, HashSet};
+
+pub use super::http_client::{
+    DeviceLocation as Location, GetDeviceResponse as Device, PushSubscription,
+};
+use super::{
+    commands::{self, IncomingDeviceCommand},
+    http_client::{
+        DeviceUpdateRequest, DeviceUpdateRequestBuilder, PendingCommand, UpdateDeviceResponse,
+    },
+    telemetry, util, CachedResponse, FirefoxAccount,
+};
+use crate::{DeviceCapability, Error, LocalDevice, Result};
+use sync15::DeviceType;
+
+// An devices response is considered fresh for `DEVICES_FRESHNESS_THRESHOLD` ms.
+const DEVICES_FRESHNESS_THRESHOLD: u64 = 60_000; // 1 minute
+
+/// The reason we are fetching commands.
+#[derive(Clone, Copy)]
+pub enum CommandFetchReason {
+    /// We are polling in-case we've missed some.
+    Poll,
+    /// We got a push notification with the index of the message.
+    Push(u64),
+}
+
+impl FirefoxAccount {
+    /// Fetches the list of devices from the current account including
+    /// the current one.
+    ///
+    /// * `ignore_cache` - If set to true, bypass the in-memory cache
+    /// and fetch devices from the server.
+    pub fn get_devices(&mut self, ignore_cache: bool) -> Result<Vec<Device>> {
+        if let Some(d) = &self.devices_cache {
+            if !ignore_cache && util::now() < d.cached_at + DEVICES_FRESHNESS_THRESHOLD {
+                return Ok(d.response.clone());
+            }
+        }
+
+        let refresh_token = self.get_refresh_token()?;
+        let response = self
+            .client
+            .get_devices(self.state.config(), refresh_token)?;
+
+        self.devices_cache = Some(CachedResponse {
+            response: response.clone(),
+            cached_at: util::now(),
+            etag: "".into(),
+        });
+
+        Ok(response)
+    }
+
+    pub fn get_current_device(&mut self) -> Result<Option<Device>> {
+        Ok(self
+            .get_devices(false)?
+            .into_iter()
+            .find(|d| d.is_current_device))
+    }
+
+    /// Replaces the internal set of "tracked" device capabilities by re-registering
+    /// new capabilities and returns a set of device commands to register with the
+    /// server.
+    fn register_capabilities(
+        &mut self,
+        capabilities: &[DeviceCapability],
+    ) -> Result<HashMap<String, String>> {
+        let mut capabilities_set = HashSet::new();
+        let mut commands = HashMap::new();
+        for capability in capabilities {
+            match capability {
+                DeviceCapability::SendTab => {
+                    let send_tab_command = self.generate_send_tab_command_data()?;
+                    commands.insert(
+                        commands::send_tab::COMMAND_NAME.to_owned(),
+                        send_tab_command.to_owned(),
+                    );
+                    capabilities_set.insert(DeviceCapability::SendTab);
+                }
+            }
+        }
+        Ok(commands)
+    }
+
+    /// Initalizes our own device, most of the time this will be called right after logging-in
+    /// for the first time.
+    ///
+    /// **💾 This method alters the persisted account state.**
+    pub fn initialize_device(
+        &mut self,
+        name: &str,
+        device_type: DeviceType,
+        capabilities: &[DeviceCapability],
+    ) -> Result<LocalDevice> {
+        self.state
+            .set_device_capabilities(capabilities.iter().cloned());
+        let commands = self.register_capabilities(capabilities)?;
+        let update = DeviceUpdateRequestBuilder::new()
+            .display_name(name)
+            .device_type(&device_type)
+            .available_commands(&commands)
+            .build();
+        self.update_device(update)
+    }
+
+    /// Register a set of device capabilities against the current device.
+    ///
+    /// As the only capability is Send Tab now, its command is registered with the server.
+    /// Don't forget to also call this if the Sync Keys change as they
+    /// encrypt the Send Tab command data.
+    ///
+    /// **💾 This method alters the persisted account state.**
+    pub fn ensure_capabilities(
+        &mut self,
+        capabilities: &[DeviceCapability],
+    ) -> Result<LocalDevice> {
+        self.state
+            .set_device_capabilities(capabilities.iter().cloned());
+        // Don't re-register if we already have exactly those capabilities.
+        if let Some(local_device) = self.state.server_local_device_info() {
+            if capabilities == local_device.capabilities {
+                return Ok(local_device.clone());
+            }
+        }
+        let commands = self.register_capabilities(capabilities)?;
+        let update = DeviceUpdateRequestBuilder::new()
+            .available_commands(&commands)
+            .build();
+        self.update_device(update)
+    }
+
+    /// Re-register the device capabilities, this should only be used internally.
+    pub(crate) fn reregister_current_capabilities(&mut self) -> Result<()> {
+        let capabilities: Vec<_> = self.state.device_capabilities().iter().cloned().collect();
+        let commands = self.register_capabilities(&capabilities)?;
+        let update = DeviceUpdateRequestBuilder::new()
+            .available_commands(&commands)
+            .build();
+        self.update_device(update)?;
+        Ok(())
+    }
+
+    pub(crate) fn invoke_command(
+        &self,
+        command: &str,
+        target: &Device,
+        payload: &serde_json::Value,
+    ) -> Result<()> {
+        let refresh_token = self.get_refresh_token()?;
+        self.client.invoke_command(
+            self.state.config(),
+            refresh_token,
+            command,
+            &target.id,
+            payload,
+        )
+    }
+
+    /// Poll and parse any pending available command for our device.
+    /// This should be called semi-regularly as the main method of
+    /// commands delivery (push) can sometimes be unreliable on mobile devices.
+    /// Typically called even when a push notification is received, so that
+    /// any prior messages for which a push didn't arrive are still handled.
+    ///
+    /// **💾 This method alters the persisted account state.**
+    pub fn poll_device_commands(
+        &mut self,
+        reason: CommandFetchReason,
+    ) -> Result<Vec<IncomingDeviceCommand>> {
+        let last_command_index = self.state.last_handled_command_index().unwrap_or(0);
+        // We increment last_command_index by 1 because the server response includes the current index.
+        self.fetch_and_parse_commands(last_command_index + 1, None, reason)
+    }
+
+    pub fn get_command_for_index(&mut self, index: u64) -> Result<IncomingDeviceCommand> {
+        let refresh_token = self.get_refresh_token()?;
+        let pending_commands =
+            self.client
+                .get_pending_commands(self.state.config(), refresh_token, index, Some(1))?;
+        self.parse_commands_messages(pending_commands.messages, CommandFetchReason::Push(index))?
+            .into_iter()
+            .next()
+            .ok_or_else(|| Error::CommandNotFound)
+    }
+
+    fn fetch_and_parse_commands(
+        &mut self,
+        index: u64,
+        limit: Option<u64>,
+        reason: CommandFetchReason,
+    ) -> Result<Vec<IncomingDeviceCommand>> {
+        let refresh_token = self.get_refresh_token()?;
+        let pending_commands =
+            self.client
+                .get_pending_commands(self.state.config(), refresh_token, index, limit)?;
+        if pending_commands.messages.is_empty() {
+            return Ok(Vec::new());
+        }
+        log::info!("Handling {} messages", pending_commands.messages.len());
+        let device_commands = self.parse_commands_messages(pending_commands.messages, reason)?;
+        self.state
+            .set_last_handled_command_index(pending_commands.index);
+        Ok(device_commands)
+    }
+
+    fn parse_commands_messages(
+        &mut self,
+        messages: Vec<PendingCommand>,
+        reason: CommandFetchReason,
+    ) -> Result<Vec<IncomingDeviceCommand>> {
+        let devices = self.get_devices(false)?;
+        let parsed_commands = messages
+            .into_iter()
+            .filter_map(|msg| match self.parse_command(msg, &devices, reason) {
+                Ok(device_command) => Some(device_command),
+                Err(e) => {
+                    error_support::report_error!(
+                        "fxaclient-command",
+                        "Error while processing command: {}",
+                        e
+                    );
+                    None
+                }
+            })
+            .collect();
+        Ok(parsed_commands)
+    }
+
+    fn parse_command(
+        &mut self,
+        command: PendingCommand,
+        devices: &[Device],
+        reason: CommandFetchReason,
+    ) -> Result<IncomingDeviceCommand> {
+        let telem_reason = match reason {
+            CommandFetchReason::Poll => telemetry::ReceivedReason::Poll,
+            CommandFetchReason::Push(index) if command.index < index => {
+                telemetry::ReceivedReason::PushMissed
+            }
+            _ => telemetry::ReceivedReason::Push,
+        };
+        let command_data = command.data;
+        let sender = command_data
+            .sender
+            .and_then(|s| devices.iter().find(|i| i.id == s).cloned());
+        match command_data.command.as_str() {
+            commands::send_tab::COMMAND_NAME => {
+                self.handle_send_tab_command(sender, command_data.payload, telem_reason)
+            }
+            _ => Err(Error::UnknownCommand(command_data.command)),
+        }
+    }
+
+    pub fn set_device_name(&mut self, name: &str) -> Result<LocalDevice> {
+        let update = DeviceUpdateRequestBuilder::new().display_name(name).build();
+        self.update_device(update)
+    }
+
+    pub fn clear_device_name(&mut self) -> Result<()> {
+        let update = DeviceUpdateRequestBuilder::new()
+            .clear_display_name()
+            .build();
+        self.update_device(update)?;
+        Ok(())
+    }
+
+    pub fn set_push_subscription(
+        &mut self,
+        push_subscription: PushSubscription,
+    ) -> Result<LocalDevice> {
+        let update = DeviceUpdateRequestBuilder::new()
+            .push_subscription(&push_subscription)
+            .build();
+        self.update_device(update)
+    }
+
+    pub(crate) fn replace_device(
+        &mut self,
+        display_name: &str,
+        device_type: &DeviceType,
+        push_subscription: &Option<PushSubscription>,
+        commands: &HashMap<String, String>,
+    ) -> Result<()> {
+        self.state.clear_server_local_device_info();
+        let mut builder = DeviceUpdateRequestBuilder::new()
+            .display_name(display_name)
+            .device_type(device_type)
+            .available_commands(commands);
+        if let Some(push_subscription) = push_subscription {
+            builder = builder.push_subscription(push_subscription)
+        }
+        self.update_device(builder.build())?;
+        Ok(())
+    }
+
+    fn update_device(&mut self, update: DeviceUpdateRequest<'_>) -> Result<LocalDevice> {
+        let refresh_token = self.get_refresh_token()?;
+        let res = self
+            .client
+            .update_device_record(self.state.config(), refresh_token, update);
+        match res {
+            Ok(resp) => {
+                self.state.set_current_device_id(resp.id.clone());
+                let local_device = LocalDevice::from(resp);
+                self.state
+                    .update_server_local_device_info(local_device.clone());
+                Ok(local_device)
+            }
+            Err(err) => {
+                // We failed to write an update to the server.
+                // Clear local state so that we'll be sure to retry later.
+                self.state.clear_server_local_device_info();
+                Err(err)
+            }
+        }
+    }
+
+    /// Retrieve the current device id from state
+    pub fn get_current_device_id(&mut self) -> Result<String> {
+        match self.state.current_device_id() {
+            Some(ref device_id) => Ok(device_id.to_string()),
+            None => Err(Error::NoCurrentDeviceId),
+        }
+    }
+}
+
+impl TryFrom<String> for DeviceCapability {
+    type Error = Error;
+
+    fn try_from(command: String) -> Result<Self> {
+        match command.as_str() {
+            commands::send_tab::COMMAND_NAME => Ok(DeviceCapability::SendTab),
+            _ => Err(Error::UnknownCommand(command)),
+        }
+    }
+}
+
+impl From<UpdateDeviceResponse> for LocalDevice {
+    fn from(resp: UpdateDeviceResponse) -> Self {
+        Self {
+            id: resp.id,
+            display_name: resp.display_name,
+            device_type: resp.device_type,
+            capabilities: resp
+                .available_commands
+                .into_keys()
+                .filter_map(|command| match command.try_into() {
+                    Ok(capability) => Some(capability),
+                    Err(e) => {
+                        log::warn!("While parsing UpdateDeviceResponse: {e}");
+                        None
+                    }
+                })
+                .collect(),
+            push_subscription: resp.push_subscription.map(Into::into),
+            push_endpoint_expired: resp.push_endpoint_expired,
+        }
+    }
+}
+
+impl TryFrom<Device> for crate::Device {
+    type Error = Error;
+    fn try_from(d: Device) -> Result<Self> {
+        let capabilities: Vec<_> = d
+            .available_commands
+            .keys()
+            .filter_map(|k| match k.as_str() {
+                commands::send_tab::COMMAND_NAME => Some(DeviceCapability::SendTab),
+                _ => None,
+            })
+            .map(Into::into)
+            .collect();
+        Ok(crate::Device {
+            id: d.common.id,
+            display_name: d.common.display_name,
+            device_type: d.common.device_type,
+            capabilities,
+            push_subscription: d.common.push_subscription.map(Into::into),
+            push_endpoint_expired: d.common.push_endpoint_expired,
+            is_current_device: d.is_current_device,
+            last_access_time: d.last_access_time.map(TryFrom::try_from).transpose()?,
+        })
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::internal::http_client::*;
+    use crate::internal::oauth::RefreshToken;
+    use crate::internal::Config;
+    use crate::ScopedKey;
+    use mockall::predicate::always;
+    use mockall::predicate::eq;
+    use std::collections::HashSet;
+    use std::sync::Arc;
+
+    fn setup() -> FirefoxAccount {
+        // I'd love to be able to configure a single mocked client here,
+        // but can't work out how to do that within the typesystem.
+        let config = Config::stable_dev("12345678", "https://foo.bar");
+        let mut fxa = FirefoxAccount::with_config(config);
+        fxa.state.force_refresh_token(RefreshToken {
+            token: "refreshtok".to_string(),
+            scopes: HashSet::default(),
+        });
+        fxa.state.insert_scoped_key("https://identity.mozilla.com/apps/oldsync", ScopedKey {
+            kty: "oct".to_string(),
+            scope: "https://identity.mozilla.com/apps/oldsync".to_string(),
+            k: "kMtwpVC0ZaYFJymPza8rXK_0CgCp3KMwRStwGfBRBDtL6hXRDVJgQFaoOQ2dimw0Bko5WVv2gNTy7RX5zFYZHg".to_string(),
+            kid: "1542236016429-Ox1FbJfFfwTe5t-xq4v2hQ".to_string(),
+        });
+        fxa
+    }
+
+    #[test]
+    fn test_ensure_capabilities_does_not_hit_the_server_if_nothing_has_changed() {
+        let mut fxa = setup();
+
+        // Do an initial call to ensure_capabilities().
+        let mut client = MockFxAClient::new();
+        client
+            .expect_update_device_record()
+            .with(always(), eq("refreshtok"), always())
+            .times(1)
+            .returning(|_, _, _| {
+                Ok(UpdateDeviceResponse {
+                    id: "device1".to_string(),
+                    display_name: "".to_string(),
+                    device_type: DeviceType::Desktop,
+                    push_subscription: None,
+                    available_commands: HashMap::from([(
+                        commands::send_tab::COMMAND_NAME.to_owned(),
+                        "fake-command-data".to_owned(),
+                    )]),
+                    push_endpoint_expired: false,
+                })
+            });
+        fxa.set_client(Arc::new(client));
+        fxa.ensure_capabilities(&[DeviceCapability::SendTab])
+            .unwrap();
+        let saved = fxa.to_json().unwrap();
+
+        // Do another call with the same capabilities.
+        // The MockFxAClient will panic if it tries to hit the network again, which it shouldn't.
+        fxa.ensure_capabilities(&[DeviceCapability::SendTab])
+            .unwrap();
+
+        // Do another call with the same capabilities , after restoring from disk.
+        // The MockFxAClient will panic if it tries to hit the network, which it shouldn't.
+        let mut restored = FirefoxAccount::from_json(&saved).unwrap();
+        restored.set_client(Arc::new(MockFxAClient::new()));
+        restored
+            .ensure_capabilities(&[DeviceCapability::SendTab])
+            .unwrap();
+    }
+
+    #[test]
+    fn test_ensure_capabilities_updates_the_server_if_capabilities_increase() {
+        let mut fxa = setup();
+
+        // Do an initial call to ensure_capabilities().
+        let mut client = MockFxAClient::new();
+        client
+            .expect_update_device_record()
+            .with(always(), eq("refreshtok"), always())
+            .times(1)
+            .returning(|_, _, _| {
+                Ok(UpdateDeviceResponse {
+                    id: "device1".to_string(),
+                    display_name: "".to_string(),
+                    device_type: DeviceType::Desktop,
+                    push_subscription: None,
+                    available_commands: HashMap::default(),
+                    push_endpoint_expired: false,
+                })
+            });
+        fxa.set_client(Arc::new(client));
+
+        fxa.ensure_capabilities(&[]).unwrap();
+        let saved = fxa.to_json().unwrap();
+
+        // Do another call with reduced capabilities.
+        let mut client = MockFxAClient::new();
+        client
+            .expect_update_device_record()
+            .with(always(), eq("refreshtok"), always())
+            .times(1)
+            .returning(|_, _, _| {
+                Ok(UpdateDeviceResponse {
+                    id: "device1".to_string(),
+                    display_name: "".to_string(),
+                    device_type: DeviceType::Desktop,
+                    push_subscription: None,
+                    available_commands: HashMap::from([(
+                        commands::send_tab::COMMAND_NAME.to_owned(),
+                        "fake-command-data".to_owned(),
+                    )]),
+                    push_endpoint_expired: false,
+                })
+            });
+        fxa.set_client(Arc::new(client));
+
+        fxa.ensure_capabilities(&[DeviceCapability::SendTab])
+            .unwrap();
+
+        // Do another call with the same capabilities , after restoring from disk.
+        // The MockFxAClient will panic if it tries to hit the network, which it shouldn't.
+        let mut restored = FirefoxAccount::from_json(&saved).unwrap();
+        let mut client = MockFxAClient::new();
+        client
+            .expect_update_device_record()
+            .with(always(), eq("refreshtok"), always())
+            .returning(|_, _, _| {
+                Ok(UpdateDeviceResponse {
+                    id: "device1".to_string(),
+                    display_name: "".to_string(),
+                    device_type: DeviceType::Desktop,
+                    push_subscription: None,
+                    available_commands: HashMap::from([(
+                        commands::send_tab::COMMAND_NAME.to_owned(),
+                        "fake-command-data".to_owned(),
+                    )]),
+                    push_endpoint_expired: false,
+                })
+            });
+        restored.set_client(Arc::new(client));
+
+        restored
+            .ensure_capabilities(&[DeviceCapability::SendTab])
+            .unwrap();
+    }
+
+    #[test]
+    fn test_ensure_capabilities_updates_the_server_if_capabilities_reduce() {
+        let mut fxa = setup();
+
+        // Do an initial call to ensure_capabilities().
+        let mut client = MockFxAClient::new();
+        client
+            .expect_update_device_record()
+            .with(always(), eq("refreshtok"), always())
+            .times(1)
+            .returning(|_, _, _| {
+                Ok(UpdateDeviceResponse {
+                    id: "device1".to_string(),
+                    display_name: "".to_string(),
+                    device_type: DeviceType::Desktop,
+                    push_subscription: None,
+                    available_commands: HashMap::from([(
+                        commands::send_tab::COMMAND_NAME.to_owned(),
+                        "fake-command-data".to_owned(),
+                    )]),
+                    push_endpoint_expired: false,
+                })
+            });
+        fxa.set_client(Arc::new(client));
+
+        fxa.ensure_capabilities(&[DeviceCapability::SendTab])
+            .unwrap();
+        let saved = fxa.to_json().unwrap();
+
+        // Do another call with reduced capabilities.
+        let mut client = MockFxAClient::new();
+        client
+            .expect_update_device_record()
+            .with(always(), eq("refreshtok"), always())
+            .times(1)
+            .returning(|_, _, _| {
+                Ok(UpdateDeviceResponse {
+                    id: "device1".to_string(),
+                    display_name: "".to_string(),
+                    device_type: DeviceType::Desktop,
+                    push_subscription: None,
+                    available_commands: HashMap::default(),
+                    push_endpoint_expired: false,
+                })
+            });
+        fxa.set_client(Arc::new(client));
+
+        fxa.ensure_capabilities(&[]).unwrap();
+
+        // Do another call with the same capabilities , after restoring from disk.
+        // The MockFxAClient will panic if it tries to hit the network, which it shouldn't.
+        let mut restored = FirefoxAccount::from_json(&saved).unwrap();
+        let mut client = MockFxAClient::new();
+        client
+            .expect_update_device_record()
+            .with(always(), eq("refreshtok"), always())
+            .times(1)
+            .returning(|_, _, _| {
+                Ok(UpdateDeviceResponse {
+                    id: "device1".to_string(),
+                    display_name: "".to_string(),
+                    device_type: DeviceType::Desktop,
+                    push_subscription: None,
+                    available_commands: HashMap::default(),
+                    push_endpoint_expired: false,
+                })
+            });
+        restored.set_client(Arc::new(client));
+
+        restored.ensure_capabilities(&[]).unwrap();
+    }
+
+    #[test]
+    fn test_ensure_capabilities_will_reregister_after_new_login_flow() {
+        let mut fxa = setup();
+
+        // Do an initial call to ensure_capabilities().
+        let mut client = MockFxAClient::new();
+        client
+            .expect_update_device_record()
+            .with(always(), eq("refreshtok"), always())
+            .times(1)
+            .returning(|_, _, _| {
+                Ok(UpdateDeviceResponse {
+                    id: "device1".to_string(),
+                    display_name: "".to_string(),
+                    device_type: DeviceType::Desktop,
+                    push_subscription: None,
+                    available_commands: HashMap::from([(
+                        commands::send_tab::COMMAND_NAME.to_owned(),
+                        "fake-command-data".to_owned(),
+                    )]),
+                    push_endpoint_expired: false,
+                })
+            });
+        fxa.set_client(Arc::new(client));
+        fxa.ensure_capabilities(&[DeviceCapability::SendTab])
+            .unwrap();
+
+        // Fake that we've completed a new login flow.
+        // (which annoyingly makes a bunch of network requests)
+        let mut client = MockFxAClient::new();
+        client
+            .expect_destroy_access_token()
+            .with(always(), always())
+            .times(1)
+            .returning(|_, _| {
+                Err(Error::RemoteError {
+                    code: 500,
+                    errno: 999,
+                    error: "server error".to_string(),
+                    message: "this will be ignored anyway".to_string(),
+                    info: "".to_string(),
+                })
+            });
+        client
+            .expect_get_devices()
+            .with(always(), always())
+            .times(1)
+            .returning(|_, _| {
+                Err(Error::RemoteError {
+                    code: 500,
+                    errno: 999,
+                    error: "server error".to_string(),
+                    message: "this will be ignored anyway".to_string(),
+                    info: "".to_string(),
+                })
+            });
+        client
+            .expect_destroy_refresh_token()
+            .with(always(), always())
+            .times(1)
+            .returning(|_, _| {
+                Err(Error::RemoteError {
+                    code: 500,
+                    errno: 999,
+                    error: "server error".to_string(),
+                    message: "this will be ignored anyway".to_string(),
+                    info: "".to_string(),
+                })
+            });
+        fxa.set_client(Arc::new(client));
+
+        fxa.handle_oauth_response(
+            OAuthTokenResponse {
+                keys_jwe: None,
+                refresh_token: Some("newRefreshTok".to_string()),
+                session_token: None,
+                expires_in: 12345,
+                scope: "profile".to_string(),
+                access_token: "accesstok".to_string(),
+            },
+            None,
+        )
+        .unwrap();
+
+        assert!(fxa.state.server_local_device_info().is_none());
+
+        // Do another call with the same capabilities.
+        // It should re-register, as server-side state may have changed.
+        let mut client = MockFxAClient::new();
+        client
+            .expect_update_device_record()
+            .with(always(), eq("newRefreshTok"), always())
+            .times(1)
+            .returning(|_, _, _| {
+                Ok(UpdateDeviceResponse {
+                    id: "device1".to_string(),
+                    display_name: "".to_string(),
+                    device_type: DeviceType::Desktop,
+                    push_subscription: None,
+                    available_commands: HashMap::from([(
+                        commands::send_tab::COMMAND_NAME.to_owned(),
+                        "fake-command-data".to_owned(),
+                    )]),
+                    push_endpoint_expired: false,
+                })
+            });
+        fxa.set_client(Arc::new(client));
+        fxa.ensure_capabilities(&[DeviceCapability::SendTab])
+            .unwrap();
+    }
+
+    #[test]
+    fn test_ensure_capabilities_updates_the_server_if_previous_attempt_failed() {
+        let mut fxa = setup();
+
+        // Do an initial call to ensure_capabilities(), that fails.
+        let mut client = MockFxAClient::new();
+        client
+            .expect_update_device_record()
+            .with(always(), eq("refreshtok"), always())
+            .times(1)
+            .returning(|_, _, _| {
+                Err(Error::RemoteError {
+                    code: 500,
+                    errno: 999,
+                    error: "server error".to_string(),
+                    message: "this will be ignored anyway".to_string(),
+                    info: "".to_string(),
+                })
+            });
+        fxa.set_client(Arc::new(client));
+
+        fxa.ensure_capabilities(&[DeviceCapability::SendTab])
+            .unwrap_err();
+
+        // Do another call, which should re-attempt the update.
+        let mut client = MockFxAClient::new();
+        client
+            .expect_update_device_record()
+            .with(always(), eq("refreshtok"), always())
+            .times(1)
+            .returning(|_, _, _| {
+                Ok(UpdateDeviceResponse {
+                    id: "device1".to_string(),
+                    display_name: "".to_string(),
+                    device_type: DeviceType::Desktop,
+                    push_subscription: None,
+                    available_commands: HashMap::from([(
+                        commands::send_tab::COMMAND_NAME.to_owned(),
+                        "fake-command-data".to_owned(),
+                    )]),
+                    push_endpoint_expired: false,
+                })
+            });
+        fxa.set_client(Arc::new(client));
+
+        fxa.ensure_capabilities(&[DeviceCapability::SendTab])
+            .unwrap();
+    }
+
+    #[test]
+    fn test_get_devices() {
+        let mut fxa = setup();
+        let mut client = MockFxAClient::new();
+        client
+            .expect_get_devices()
+            .with(always(), always())
+            .times(1)
+            .returning(|_, _| {
+                Ok(vec![Device {
+                    common: DeviceResponseCommon {
+                        id: "device1".into(),
+                        display_name: "".to_string(),
+                        device_type: DeviceType::Desktop,
+                        push_subscription: None,
+                        available_commands: HashMap::new(),
+                        push_endpoint_expired: true,
+                    },
+                    is_current_device: true,
+                    location: DeviceLocation {
+                        city: None,
+                        country: None,
+                        state: None,
+                        state_code: None,
+                    },
+                    last_access_time: None,
+                }])
+            });
+
+        fxa.set_client(Arc::new(client));
+        assert!(fxa.devices_cache.is_none());
+
+        assert!(fxa.get_devices(false).is_ok());
+        assert!(fxa.devices_cache.is_some());
+
+        let cache = fxa.devices_cache.clone().unwrap();
+        assert!(!cache.response.is_empty());
+        assert!(cache.cached_at > 0);
+
+        let cached_devices = cache.response;
+        assert_eq!(cached_devices[0].id, "device1".to_string());
+
+        // Check that a second call to get_devices doesn't hit the server
+        assert!(fxa.get_devices(false).is_ok());
+        assert!(fxa.devices_cache.is_some());
+
+        let cache2 = fxa.devices_cache.unwrap();
+        let cached_devices2 = cache2.response;
+
+        assert_eq!(cache.cached_at, cache2.cached_at);
+        assert_eq!(cached_devices.len(), cached_devices2.len());
+        assert_eq!(cached_devices[0].id, cached_devices2[0].id);
+    }
+
+    #[test]
+    fn test_get_devices_network_errors() {
+        let mut fxa = setup();
+        let mut client = MockFxAClient::new();
+        client
+            .expect_get_devices()
+            .with(always(), always())
+            .times(1)
+            .returning(|_, _| {
+                Err(Error::RemoteError {
+                    code: 500,
+                    errno: 101,
+                    error: "Did not work!".to_owned(),
+                    message: "Did not work!".to_owned(),
+                    info: "Did not work!".to_owned(),
+                })
+            });
+
+        fxa.set_client(Arc::new(client));
+        assert!(fxa.devices_cache.is_none());
+
+        let res = fxa.get_devices(false);
+
+        assert!(res.is_err());
+        assert!(fxa.devices_cache.is_none());
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/fxa_client/internal/http_client.rs.html b/book/rust-docs/src/fxa_client/internal/http_client.rs.html new file mode 100644 index 0000000000..2733c09fcb --- /dev/null +++ b/book/rust-docs/src/fxa_client/internal/http_client.rs.html @@ -0,0 +1,2211 @@ +http_client.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
+540
+541
+542
+543
+544
+545
+546
+547
+548
+549
+550
+551
+552
+553
+554
+555
+556
+557
+558
+559
+560
+561
+562
+563
+564
+565
+566
+567
+568
+569
+570
+571
+572
+573
+574
+575
+576
+577
+578
+579
+580
+581
+582
+583
+584
+585
+586
+587
+588
+589
+590
+591
+592
+593
+594
+595
+596
+597
+598
+599
+600
+601
+602
+603
+604
+605
+606
+607
+608
+609
+610
+611
+612
+613
+614
+615
+616
+617
+618
+619
+620
+621
+622
+623
+624
+625
+626
+627
+628
+629
+630
+631
+632
+633
+634
+635
+636
+637
+638
+639
+640
+641
+642
+643
+644
+645
+646
+647
+648
+649
+650
+651
+652
+653
+654
+655
+656
+657
+658
+659
+660
+661
+662
+663
+664
+665
+666
+667
+668
+669
+670
+671
+672
+673
+674
+675
+676
+677
+678
+679
+680
+681
+682
+683
+684
+685
+686
+687
+688
+689
+690
+691
+692
+693
+694
+695
+696
+697
+698
+699
+700
+701
+702
+703
+704
+705
+706
+707
+708
+709
+710
+711
+712
+713
+714
+715
+716
+717
+718
+719
+720
+721
+722
+723
+724
+725
+726
+727
+728
+729
+730
+731
+732
+733
+734
+735
+736
+737
+738
+739
+740
+741
+742
+743
+744
+745
+746
+747
+748
+749
+750
+751
+752
+753
+754
+755
+756
+757
+758
+759
+760
+761
+762
+763
+764
+765
+766
+767
+768
+769
+770
+771
+772
+773
+774
+775
+776
+777
+778
+779
+780
+781
+782
+783
+784
+785
+786
+787
+788
+789
+790
+791
+792
+793
+794
+795
+796
+797
+798
+799
+800
+801
+802
+803
+804
+805
+806
+807
+808
+809
+810
+811
+812
+813
+814
+815
+816
+817
+818
+819
+820
+821
+822
+823
+824
+825
+826
+827
+828
+829
+830
+831
+832
+833
+834
+835
+836
+837
+838
+839
+840
+841
+842
+843
+844
+845
+846
+847
+848
+849
+850
+851
+852
+853
+854
+855
+856
+857
+858
+859
+860
+861
+862
+863
+864
+865
+866
+867
+868
+869
+870
+871
+872
+873
+874
+875
+876
+877
+878
+879
+880
+881
+882
+883
+884
+885
+886
+887
+888
+889
+890
+891
+892
+893
+894
+895
+896
+897
+898
+899
+900
+901
+902
+903
+904
+905
+906
+907
+908
+909
+910
+911
+912
+913
+914
+915
+916
+917
+918
+919
+920
+921
+922
+923
+924
+925
+926
+927
+928
+929
+930
+931
+932
+933
+934
+935
+936
+937
+938
+939
+940
+941
+942
+943
+944
+945
+946
+947
+948
+949
+950
+951
+952
+953
+954
+955
+956
+957
+958
+959
+960
+961
+962
+963
+964
+965
+966
+967
+968
+969
+970
+971
+972
+973
+974
+975
+976
+977
+978
+979
+980
+981
+982
+983
+984
+985
+986
+987
+988
+989
+990
+991
+992
+993
+994
+995
+996
+997
+998
+999
+1000
+1001
+1002
+1003
+1004
+1005
+1006
+1007
+1008
+1009
+1010
+1011
+1012
+1013
+1014
+1015
+1016
+1017
+1018
+1019
+1020
+1021
+1022
+1023
+1024
+1025
+1026
+1027
+1028
+1029
+1030
+1031
+1032
+1033
+1034
+1035
+1036
+1037
+1038
+1039
+1040
+1041
+1042
+1043
+1044
+1045
+1046
+1047
+1048
+1049
+1050
+1051
+1052
+1053
+1054
+1055
+1056
+1057
+1058
+1059
+1060
+1061
+1062
+1063
+1064
+1065
+1066
+1067
+1068
+1069
+1070
+1071
+1072
+1073
+1074
+1075
+1076
+1077
+1078
+1079
+1080
+1081
+1082
+1083
+1084
+1085
+1086
+1087
+1088
+1089
+1090
+1091
+1092
+1093
+1094
+1095
+1096
+1097
+1098
+1099
+1100
+1101
+1102
+1103
+1104
+1105
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Low-level API for talking to the FxA server.
+//!
+//! This module is responsible for talking to the FxA server over HTTP,
+//! serializing request bodies and deserializing response payloads into
+//! live objects that can be inspected by other parts of the code.
+
+use super::{config::Config, util};
+use crate::{Error, Result};
+use parking_lot::Mutex;
+use rc_crypto::{
+    digest,
+    hawk::{Credentials, Key, PayloadHasher, RequestBuilder, SHA256},
+    hkdf, hmac,
+};
+use serde_derive::{Deserialize, Serialize};
+use serde_json::json;
+use std::{
+    collections::HashMap,
+    time::{Duration, Instant},
+};
+use sync15::DeviceType;
+use url::Url;
+use viaduct::{header_names, status_codes, Method, Request, Response};
+
+const HAWK_HKDF_SALT: [u8; 32] = [0b0; 32];
+const HAWK_KEY_LENGTH: usize = 32;
+const RETRY_AFTER_DEFAULT_SECONDS: u64 = 10;
+// Devices older than this many days will not appear in the devices list
+const DEVICES_FILTER_DAYS: u64 = 21;
+
+/// Trait defining the low-level API for talking to the FxA server.
+///
+/// These are all the methods and datatypes used for interacting with
+/// the FxA server. It's defined as a trait mostly to make it mockable
+/// for testing purposes.
+///
+/// The default live implementation of this trait can be found in
+/// the [`Client`] struct, and you should consult that struct for
+/// documentation on specific methods.
+///
+/// A brief note on names: you'll see that the method names on this trait
+/// try to follow a pattern of `<verb>_<noun>[_<additional info>]()`.
+/// Please try to keep to this pattern if you add methods to this trait.
+///
+/// Consistent names are helpful at the best of times, but they're
+/// particularly important here because so many of the nouns in OAuth
+/// contain embedded verbs! For example: at a glance, does a method
+/// named `refresh_token` refresh something called a "token", or does
+/// it return something called a "refresh token"? Using unambiguous
+/// verbs to start each method helps avoid confusion here.
+///
+// Due to limitations in mockall, we have to add explicit lifetimes that the Rust compiler would have been happy to infer.
+#[allow(clippy::needless_lifetimes)]
+#[cfg_attr(test, mockall::automock)]
+pub(crate) trait FxAClient {
+    fn create_refresh_token_using_authorization_code(
+        &self,
+        config: &Config,
+        code: &str,
+        code_verifier: &str,
+    ) -> Result<OAuthTokenResponse>;
+    fn create_refresh_token_using_session_token<'a>(
+        &self,
+        config: &Config,
+        session_token: &str,
+        scopes: &[&'a str],
+    ) -> Result<OAuthTokenResponse>;
+    fn check_refresh_token_status(
+        &self,
+        config: &Config,
+        refresh_token: &str,
+    ) -> Result<IntrospectResponse>;
+    fn create_access_token_using_refresh_token<'a>(
+        &self,
+        config: &Config,
+        refresh_token: &str,
+        ttl: Option<u64>,
+        scopes: &[&'a str],
+    ) -> Result<OAuthTokenResponse>;
+    fn create_access_token_using_session_token<'a>(
+        &self,
+        config: &Config,
+        session_token: &str,
+        scopes: &[&'a str],
+    ) -> Result<OAuthTokenResponse>;
+    fn create_authorization_code_using_session_token(
+        &self,
+        config: &Config,
+        session_token: &str,
+        auth_params: AuthorizationRequestParameters,
+    ) -> Result<OAuthAuthResponse>;
+    fn duplicate_session_token(
+        &self,
+        config: &Config,
+        session_token: &str,
+    ) -> Result<DuplicateTokenResponse>;
+    fn destroy_access_token(&self, config: &Config, token: &str) -> Result<()>;
+    fn destroy_refresh_token(&self, config: &Config, token: &str) -> Result<()>;
+    fn get_profile(
+        &self,
+        config: &Config,
+        profile_access_token: &str,
+        etag: Option<String>,
+    ) -> Result<Option<ResponseAndETag<ProfileResponse>>>;
+    fn get_pending_commands(
+        &self,
+        config: &Config,
+        refresh_token: &str,
+        index: u64,
+        limit: Option<u64>,
+    ) -> Result<PendingCommandsResponse>;
+    fn invoke_command(
+        &self,
+        config: &Config,
+        refresh_token: &str,
+        command: &str,
+        target: &str,
+        payload: &serde_json::Value,
+    ) -> Result<()>;
+    fn update_device_record<'a>(
+        &self,
+        config: &Config,
+        refresh_token: &str,
+        update: DeviceUpdateRequest<'a>,
+    ) -> Result<UpdateDeviceResponse>;
+    fn destroy_device_record(&self, config: &Config, refresh_token: &str, id: &str) -> Result<()>;
+    fn get_devices(&self, config: &Config, refresh_token: &str) -> Result<Vec<GetDeviceResponse>>;
+    fn get_attached_clients(
+        &self,
+        config: &Config,
+        session_token: &str,
+    ) -> Result<Vec<GetAttachedClientResponse>>;
+    fn get_scoped_key_data(
+        &self,
+        config: &Config,
+        session_token: &str,
+        client_id: &str,
+        scope: &str,
+    ) -> Result<HashMap<String, ScopedKeyDataResponse>>;
+    fn get_fxa_client_configuration(&self, config: &Config) -> Result<ClientConfigurationResponse>;
+    fn get_openid_configuration(&self, config: &Config) -> Result<OpenIdConfigurationResponse>;
+}
+
+enum HttpClientState {
+    Ok,
+    Backoff {
+        backoff_end_duration: Duration,
+        time_since_backoff: Instant,
+    },
+}
+
+pub struct Client {
+    state: Mutex<HashMap<String, HttpClientState>>,
+}
+impl FxAClient for Client {
+    fn get_fxa_client_configuration(&self, config: &Config) -> Result<ClientConfigurationResponse> {
+        // Why go through two-levels of indirection? It looks kinda dumb.
+        // Well, `config:Config` also needs to fetch the config, but does not have access
+        // to an instance of `http_client`, so it calls the helper function directly.
+        fxa_client_configuration(config.client_config_url()?)
+    }
+    fn get_openid_configuration(&self, config: &Config) -> Result<OpenIdConfigurationResponse> {
+        openid_configuration(config.openid_config_url()?)
+    }
+
+    fn get_profile(
+        &self,
+        config: &Config,
+        access_token: &str,
+        etag: Option<String>,
+    ) -> Result<Option<ResponseAndETag<ProfileResponse>>> {
+        let url = config.userinfo_endpoint()?;
+        let mut request =
+            Request::get(url).header(header_names::AUTHORIZATION, bearer_token(access_token))?;
+        if let Some(etag) = etag {
+            request = request.header(header_names::IF_NONE_MATCH, format!("\"{}\"", etag))?;
+        }
+        let resp = self.make_request(request)?;
+        if resp.status == status_codes::NOT_MODIFIED {
+            return Ok(None);
+        }
+        let etag = resp
+            .headers
+            .get(header_names::ETAG)
+            .map(ToString::to_string);
+        Ok(Some(ResponseAndETag {
+            etag,
+            response: resp.json()?,
+        }))
+    }
+
+    fn create_refresh_token_using_authorization_code(
+        &self,
+        config: &Config,
+        code: &str,
+        code_verifier: &str,
+    ) -> Result<OAuthTokenResponse> {
+        let req_body = OAauthTokenRequest::UsingCode {
+            code: code.to_string(),
+            client_id: config.client_id.to_string(),
+            code_verifier: code_verifier.to_string(),
+            ttl: None,
+        };
+        self.make_oauth_token_request(config, serde_json::to_value(req_body).unwrap())
+    }
+
+    fn create_refresh_token_using_session_token(
+        &self,
+        config: &Config,
+        session_token: &str,
+        scopes: &[&str],
+    ) -> Result<OAuthTokenResponse> {
+        let url = config.token_endpoint()?;
+        let key = derive_auth_key_from_session_token(session_token)?;
+        let body = json!({
+            "client_id": config.client_id,
+            "scope": scopes.join(" "),
+            "grant_type": "fxa-credentials",
+            "access_type": "offline",
+        });
+        let request = HawkRequestBuilder::new(Method::Post, url, &key)
+            .body(body)
+            .build()?;
+        Ok(self.make_request(request)?.json()?)
+    }
+
+    // For the regular generation of an `access_token` from long-lived credentials.
+
+    fn create_access_token_using_refresh_token(
+        &self,
+        config: &Config,
+        refresh_token: &str,
+        ttl: Option<u64>,
+        scopes: &[&str],
+    ) -> Result<OAuthTokenResponse> {
+        let req = OAauthTokenRequest::UsingRefreshToken {
+            client_id: config.client_id.clone(),
+            refresh_token: refresh_token.to_string(),
+            scope: Some(scopes.join(" ")),
+            ttl,
+        };
+        self.make_oauth_token_request(config, serde_json::to_value(req).unwrap())
+    }
+
+    fn create_access_token_using_session_token(
+        &self,
+        config: &Config,
+        session_token: &str,
+        scopes: &[&str],
+    ) -> Result<OAuthTokenResponse> {
+        let parameters = json!({
+            "client_id": config.client_id,
+            "grant_type": "fxa-credentials",
+            "scope": scopes.join(" ")
+        });
+        let key = derive_auth_key_from_session_token(session_token)?;
+        let url = config.token_endpoint()?;
+        let request = HawkRequestBuilder::new(Method::Post, url, &key)
+            .body(parameters)
+            .build()?;
+        self.make_request(request)?.json().map_err(Into::into)
+    }
+
+    fn create_authorization_code_using_session_token(
+        &self,
+        config: &Config,
+        session_token: &str,
+        auth_params: AuthorizationRequestParameters,
+    ) -> Result<OAuthAuthResponse> {
+        let parameters = serde_json::to_value(auth_params)?;
+        let key = derive_auth_key_from_session_token(session_token)?;
+        let url = config.auth_url_path("v1/oauth/authorization")?;
+        let request = HawkRequestBuilder::new(Method::Post, url, &key)
+            .body(parameters)
+            .build()?;
+
+        Ok(self.make_request(request)?.json()?)
+    }
+
+    fn check_refresh_token_status(
+        &self,
+        config: &Config,
+        refresh_token: &str,
+    ) -> Result<IntrospectResponse> {
+        let body = json!({
+            "token_type_hint": "refresh_token",
+            "token": refresh_token,
+        });
+        let url = config.introspection_endpoint()?;
+        Ok(self.make_request(Request::post(url).json(&body))?.json()?)
+    }
+
+    fn duplicate_session_token(
+        &self,
+        config: &Config,
+        session_token: &str,
+    ) -> Result<DuplicateTokenResponse> {
+        let url = config.auth_url_path("v1/session/duplicate")?;
+        let key = derive_auth_key_from_session_token(session_token)?;
+        let duplicate_body = json!({
+            "reason": "migration"
+        });
+        let request = HawkRequestBuilder::new(Method::Post, url, &key)
+            .body(duplicate_body)
+            .build()?;
+
+        Ok(self.make_request(request)?.json()?)
+    }
+
+    fn destroy_access_token(&self, config: &Config, access_token: &str) -> Result<()> {
+        let body = json!({
+            "token": access_token,
+        });
+        self.destroy_token_helper(config, &body)
+    }
+
+    fn destroy_refresh_token(&self, config: &Config, refresh_token: &str) -> Result<()> {
+        let body = json!({
+            "refresh_token": refresh_token,
+        });
+        self.destroy_token_helper(config, &body)
+    }
+
+    fn get_pending_commands(
+        &self,
+        config: &Config,
+        refresh_token: &str,
+        index: u64,
+        limit: Option<u64>,
+    ) -> Result<PendingCommandsResponse> {
+        let url = config.auth_url_path("v1/account/device/commands")?;
+        let mut request = Request::get(url)
+            .header(header_names::AUTHORIZATION, bearer_token(refresh_token))?
+            .query(&[("index", &index.to_string())]);
+        if let Some(limit) = limit {
+            request = request.query(&[("limit", &limit.to_string())])
+        }
+        Ok(self.make_request(request)?.json()?)
+    }
+
+    fn invoke_command(
+        &self,
+        config: &Config,
+        refresh_token: &str,
+        command: &str,
+        target: &str,
+        payload: &serde_json::Value,
+    ) -> Result<()> {
+        let body = json!({
+            "command": command,
+            "target": target,
+            "payload": payload
+        });
+        let url = config.auth_url_path("v1/account/devices/invoke_command")?;
+        let request = Request::post(url)
+            .header(header_names::AUTHORIZATION, bearer_token(refresh_token))?
+            .header(header_names::CONTENT_TYPE, "application/json")?
+            .body(body.to_string());
+        self.make_request(request)?;
+        Ok(())
+    }
+
+    fn get_devices(&self, config: &Config, refresh_token: &str) -> Result<Vec<GetDeviceResponse>> {
+        let url = config.auth_url_path("v1/account/devices")?;
+        let request = Request::get(url)
+            .header(header_names::AUTHORIZATION, bearer_token(refresh_token))?
+            .query(&[(
+                "filterIdleDevicesTimestamp",
+                &util::past_timestamp(DEVICES_FILTER_DAYS).to_string(),
+            )]);
+        Ok(self.make_request(request)?.json()?)
+    }
+
+    fn update_device_record(
+        &self,
+        config: &Config,
+        refresh_token: &str,
+        update: DeviceUpdateRequest<'_>,
+    ) -> Result<UpdateDeviceResponse> {
+        let url = config.auth_url_path("v1/account/device")?;
+        let request = Request::post(url)
+            .header(header_names::AUTHORIZATION, bearer_token(refresh_token))?
+            .header(header_names::CONTENT_TYPE, "application/json")?
+            .body(serde_json::to_string(&update)?);
+        Ok(self.make_request(request)?.json()?)
+    }
+
+    fn destroy_device_record(&self, config: &Config, refresh_token: &str, id: &str) -> Result<()> {
+        let body = json!({
+            "id": id,
+        });
+        let url = config.auth_url_path("v1/account/device/destroy")?;
+        let request = Request::post(url)
+            .header(header_names::AUTHORIZATION, bearer_token(refresh_token))?
+            .header(header_names::CONTENT_TYPE, "application/json")?
+            .body(body.to_string());
+
+        self.make_request(request)?;
+        Ok(())
+    }
+
+    fn get_attached_clients(
+        &self,
+        config: &Config,
+        session_token: &str,
+    ) -> Result<Vec<GetAttachedClientResponse>> {
+        let url = config.auth_url_path("v1/account/attached_clients")?;
+        let key = derive_auth_key_from_session_token(session_token)?;
+        let request = HawkRequestBuilder::new(Method::Get, url, &key).build()?;
+        Ok(self.make_request(request)?.json()?)
+    }
+
+    fn get_scoped_key_data(
+        &self,
+        config: &Config,
+        session_token: &str,
+        client_id: &str,
+        scope: &str,
+    ) -> Result<HashMap<String, ScopedKeyDataResponse>> {
+        let body = json!({
+            "client_id": client_id,
+            "scope": scope,
+        });
+        let url = config.auth_url_path("v1/account/scoped-key-data")?;
+        let key = derive_auth_key_from_session_token(session_token)?;
+        let request = HawkRequestBuilder::new(Method::Post, url, &key)
+            .body(body)
+            .build()?;
+        self.make_request(request)?.json().map_err(|e| e.into())
+    }
+}
+
+macro_rules! fetch {
+    ($url:expr) => {
+        viaduct::Request::get($url)
+            .send()?
+            .require_success()?
+            .json()?
+    };
+}
+
+#[inline]
+pub(crate) fn fxa_client_configuration(url: Url) -> Result<ClientConfigurationResponse> {
+    Ok(fetch!(url))
+}
+#[inline]
+pub(crate) fn openid_configuration(url: Url) -> Result<OpenIdConfigurationResponse> {
+    Ok(fetch!(url))
+}
+
+impl Client {
+    pub fn new() -> Self {
+        Self {
+            state: Mutex::new(HashMap::new()),
+        }
+    }
+
+    fn destroy_token_helper(&self, config: &Config, body: &serde_json::Value) -> Result<()> {
+        let url = config.oauth_url_path("v1/destroy")?;
+        self.make_request(Request::post(url).json(body))?;
+        Ok(())
+    }
+
+    fn make_oauth_token_request(
+        &self,
+        config: &Config,
+        body: serde_json::Value,
+    ) -> Result<OAuthTokenResponse> {
+        let url = config.token_endpoint()?;
+        Ok(self.make_request(Request::post(url).json(&body))?.json()?)
+    }
+
+    fn handle_too_many_requests(&self, resp: Response) -> Result<Response> {
+        let path = resp.url.path().to_string();
+        if let Some(retry_after) = resp.headers.get_as::<u64, _>(header_names::RETRY_AFTER) {
+            let retry_after = retry_after.unwrap_or(RETRY_AFTER_DEFAULT_SECONDS);
+            let time_out_state = HttpClientState::Backoff {
+                backoff_end_duration: Duration::from_secs(retry_after),
+                time_since_backoff: Instant::now(),
+            };
+            self.state.lock().insert(path, time_out_state);
+            return Err(Error::BackoffError(retry_after));
+        }
+        Self::default_handle_response_error(resp)
+    }
+
+    fn default_handle_response_error(resp: Response) -> Result<Response> {
+        let json: std::result::Result<serde_json::Value, _> = resp.json();
+        match json {
+            Ok(json) => Err(Error::RemoteError {
+                code: json["code"].as_u64().unwrap_or(0),
+                errno: json["errno"].as_u64().unwrap_or(0),
+                error: json["error"].as_str().unwrap_or("").to_string(),
+                message: json["message"].as_str().unwrap_or("").to_string(),
+                info: json["info"].as_str().unwrap_or("").to_string(),
+            }),
+            Err(_) => Err(resp.require_success().unwrap_err().into()),
+        }
+    }
+
+    fn make_request(&self, request: Request) -> Result<Response> {
+        let url = request.url.path().to_string();
+        if let HttpClientState::Backoff {
+            backoff_end_duration,
+            time_since_backoff,
+        } = self.state.lock().get(&url).unwrap_or(&HttpClientState::Ok)
+        {
+            let elapsed_time = time_since_backoff.elapsed();
+            if elapsed_time < *backoff_end_duration {
+                let remaining = *backoff_end_duration - elapsed_time;
+                return Err(Error::BackoffError(remaining.as_secs()));
+            }
+        }
+        self.state.lock().insert(url, HttpClientState::Ok);
+        let resp = request.send()?;
+        if resp.is_success() || resp.status == status_codes::NOT_MODIFIED {
+            Ok(resp)
+        } else {
+            match resp.status {
+                status_codes::TOO_MANY_REQUESTS => self.handle_too_many_requests(resp),
+                _ => Self::default_handle_response_error(resp),
+            }
+        }
+    }
+}
+
+fn bearer_token(token: &str) -> String {
+    format!("Bearer {}", token)
+}
+
+fn kw(name: &str) -> Vec<u8> {
+    format!("identity.mozilla.com/picl/v1/{}", name)
+        .as_bytes()
+        .to_vec()
+}
+
+pub fn derive_auth_key_from_session_token(session_token: &str) -> Result<Vec<u8>> {
+    let session_token_bytes = hex::decode(session_token)?;
+    let context_info = kw("sessionToken");
+    let salt = hmac::SigningKey::new(&digest::SHA256, &HAWK_HKDF_SALT);
+    let mut out = vec![0u8; HAWK_KEY_LENGTH * 2];
+    hkdf::extract_and_expand(&salt, &session_token_bytes, &context_info, &mut out)?;
+    Ok(out)
+}
+
+#[derive(Serialize, Deserialize)]
+pub struct AuthorizationRequestParameters {
+    pub client_id: String,
+    pub scope: String,
+    pub state: String,
+    pub access_type: String,
+    pub code_challenge: Option<String>,
+    pub code_challenge_method: Option<String>,
+    pub keys_jwe: Option<String>,
+}
+
+struct HawkRequestBuilder<'a> {
+    url: Url,
+    method: Method,
+    body: Option<String>,
+    hkdf_sha256_key: &'a [u8],
+}
+
+impl<'a> HawkRequestBuilder<'a> {
+    pub fn new(method: Method, url: Url, hkdf_sha256_key: &'a [u8]) -> Self {
+        rc_crypto::ensure_initialized();
+        HawkRequestBuilder {
+            url,
+            method,
+            body: None,
+            hkdf_sha256_key,
+        }
+    }
+
+    // This class assumes that the content being sent it always of the type
+    // application/json.
+    pub fn body(mut self, body: serde_json::Value) -> Self {
+        self.body = Some(body.to_string());
+        self
+    }
+
+    fn make_hawk_header(&self) -> Result<String> {
+        // Make sure we de-allocate the hash after hawk_request_builder.
+        let hash;
+        let method = format!("{}", self.method);
+        let mut hawk_request_builder = RequestBuilder::from_url(method.as_str(), &self.url)?;
+        if let Some(ref body) = self.body {
+            hash = PayloadHasher::hash("application/json", SHA256, body)?;
+            hawk_request_builder = hawk_request_builder.hash(&hash[..]);
+        }
+        let hawk_request = hawk_request_builder.request();
+        let token_id = hex::encode(&self.hkdf_sha256_key[0..HAWK_KEY_LENGTH]);
+        let hmac_key = &self.hkdf_sha256_key[HAWK_KEY_LENGTH..(2 * HAWK_KEY_LENGTH)];
+        let hawk_credentials = Credentials {
+            id: token_id,
+            key: Key::new(hmac_key, SHA256)?,
+        };
+        let header = hawk_request.make_header(&hawk_credentials)?;
+        Ok(format!("Hawk {}", header))
+    }
+
+    pub fn build(self) -> Result<Request> {
+        let hawk_header = self.make_hawk_header()?;
+        let mut request =
+            Request::new(self.method, self.url).header(header_names::AUTHORIZATION, hawk_header)?;
+        if let Some(body) = self.body {
+            request = request
+                .header(header_names::CONTENT_TYPE, "application/json")?
+                .body(body);
+        }
+        Ok(request)
+    }
+}
+
+#[derive(Deserialize)]
+pub(crate) struct ClientConfigurationResponse {
+    pub(crate) auth_server_base_url: String,
+    pub(crate) oauth_server_base_url: String,
+    pub(crate) profile_server_base_url: String,
+    pub(crate) sync_tokenserver_base_url: String,
+}
+
+#[derive(Deserialize)]
+pub(crate) struct OpenIdConfigurationResponse {
+    pub(crate) authorization_endpoint: String,
+    pub(crate) introspection_endpoint: String,
+    pub(crate) issuer: String,
+    pub(crate) jwks_uri: String,
+    #[allow(dead_code)]
+    pub(crate) token_endpoint: String,
+    pub(crate) userinfo_endpoint: String,
+}
+
+#[derive(Clone)]
+pub struct ResponseAndETag<T> {
+    pub response: T,
+    pub etag: Option<String>,
+}
+
+#[derive(Deserialize)]
+pub struct PendingCommandsResponse {
+    pub index: u64,
+    pub last: Option<bool>,
+    pub messages: Vec<PendingCommand>,
+}
+
+#[derive(Deserialize)]
+pub struct PendingCommand {
+    pub index: u64,
+    pub data: CommandData,
+}
+
+#[derive(Debug, Deserialize)]
+pub struct CommandData {
+    pub command: String,
+    pub payload: serde_json::Value, // Need https://github.com/serde-rs/serde/issues/912 to make payload an enum instead.
+    pub sender: Option<String>,
+}
+
+#[derive(Clone, Debug, Deserialize, Serialize)]
+pub struct PushSubscription {
+    #[serde(rename = "pushCallback")]
+    pub endpoint: String,
+    #[serde(rename = "pushPublicKey")]
+    pub public_key: String,
+    #[serde(rename = "pushAuthKey")]
+    pub auth_key: String,
+}
+
+impl From<crate::DevicePushSubscription> for PushSubscription {
+    fn from(sub: crate::DevicePushSubscription) -> Self {
+        PushSubscription {
+            endpoint: sub.endpoint,
+            public_key: sub.public_key,
+            auth_key: sub.auth_key,
+        }
+    }
+}
+
+impl From<PushSubscription> for crate::DevicePushSubscription {
+    fn from(sub: PushSubscription) -> Self {
+        crate::DevicePushSubscription {
+            endpoint: sub.endpoint,
+            public_key: sub.public_key,
+            auth_key: sub.auth_key,
+        }
+    }
+}
+
+/// We use the double Option pattern in this struct.
+/// The outer option represents the existence of the field
+/// and the inner option its value or null.
+/// TL;DR:
+/// `None`: the field will not be present in the JSON body.
+/// `Some(None)`: the field will have a `null` value.
+/// `Some(Some(T))`: the field will have the serialized value of T.
+#[derive(Serialize)]
+#[allow(clippy::option_option)]
+pub struct DeviceUpdateRequest<'a> {
+    #[serde(skip_serializing_if = "Option::is_none")]
+    #[serde(rename = "name")]
+    display_name: Option<Option<&'a str>>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    #[serde(rename = "type")]
+    device_type: Option<&'a DeviceType>,
+    #[serde(flatten)]
+    push_subscription: Option<&'a PushSubscription>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    #[serde(rename = "availableCommands")]
+    available_commands: Option<Option<&'a HashMap<String, String>>>,
+}
+
+#[allow(clippy::option_option)]
+pub struct DeviceUpdateRequestBuilder<'a> {
+    device_type: Option<&'a DeviceType>,
+    display_name: Option<Option<&'a str>>,
+    push_subscription: Option<&'a PushSubscription>,
+    available_commands: Option<Option<&'a HashMap<String, String>>>,
+}
+
+impl<'a> DeviceUpdateRequestBuilder<'a> {
+    pub fn new() -> Self {
+        Self {
+            device_type: None,
+            display_name: None,
+            push_subscription: None,
+            available_commands: None,
+        }
+    }
+
+    pub fn push_subscription(mut self, push_subscription: &'a PushSubscription) -> Self {
+        self.push_subscription = Some(push_subscription);
+        self
+    }
+
+    pub fn available_commands(mut self, available_commands: &'a HashMap<String, String>) -> Self {
+        self.available_commands = Some(Some(available_commands));
+        self
+    }
+
+    pub fn display_name(mut self, display_name: &'a str) -> Self {
+        self.display_name = Some(Some(display_name));
+        self
+    }
+
+    pub fn clear_display_name(mut self) -> Self {
+        self.display_name = Some(None);
+        self
+    }
+
+    pub fn device_type(mut self, device_type: &'a DeviceType) -> Self {
+        self.device_type = Some(device_type);
+        self
+    }
+
+    pub fn build(self) -> DeviceUpdateRequest<'a> {
+        DeviceUpdateRequest {
+            display_name: self.display_name,
+            device_type: self.device_type,
+            push_subscription: self.push_subscription,
+            available_commands: self.available_commands,
+        }
+    }
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize)]
+pub struct DeviceLocation {
+    pub city: Option<String>,
+    pub country: Option<String>,
+    pub state: Option<String>,
+    #[serde(rename = "stateCode")]
+    pub state_code: Option<String>,
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize)]
+pub struct GetDeviceResponse {
+    #[serde(flatten)]
+    pub common: DeviceResponseCommon,
+    #[serde(rename = "isCurrentDevice")]
+    pub is_current_device: bool,
+    pub location: DeviceLocation,
+    #[serde(rename = "lastAccessTime")]
+    pub last_access_time: Option<u64>,
+}
+
+impl std::ops::Deref for GetDeviceResponse {
+    type Target = DeviceResponseCommon;
+    fn deref(&self) -> &DeviceResponseCommon {
+        &self.common
+    }
+}
+
+pub type UpdateDeviceResponse = DeviceResponseCommon;
+
+#[derive(Clone, Debug, Serialize, Deserialize)]
+pub struct DeviceResponseCommon {
+    pub id: String,
+    #[serde(rename = "name")]
+    pub display_name: String,
+    #[serde(rename = "type")]
+    pub device_type: DeviceType,
+    #[serde(flatten)]
+    pub push_subscription: Option<PushSubscription>,
+    #[serde(rename = "availableCommands")]
+    pub available_commands: HashMap<String, String>,
+    #[serde(rename = "pushEndpointExpired")]
+    pub push_endpoint_expired: bool,
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct GetAttachedClientResponse {
+    pub client_id: Option<String>,
+    pub session_token_id: Option<String>,
+    pub refresh_token_id: Option<String>,
+    pub device_id: Option<String>,
+    pub device_type: DeviceType,
+    pub is_current_session: bool,
+    pub name: Option<String>,
+    pub created_time: Option<u64>,
+    pub last_access_time: Option<u64>,
+    pub scope: Option<Vec<String>>,
+    pub user_agent: String,
+    pub os: Option<String>,
+}
+
+// We model the OAuthTokenRequest according to the up to date
+// definition on
+// https://github.com/mozilla/fxa/blob/8ae0e6876a50c7f386a9ec5b6df9ebb54ccdf1b5/packages/fxa-auth-server/lib/oauth/routes/token.js#L70-L152
+
+#[derive(Serialize)]
+#[serde(tag = "grant_type")]
+enum OAauthTokenRequest {
+    #[serde(rename = "refresh_token")]
+    UsingRefreshToken {
+        client_id: String,
+        refresh_token: String,
+        #[serde(skip_serializing_if = "Option::is_none")]
+        scope: Option<String>,
+        #[serde(skip_serializing_if = "Option::is_none")]
+        ttl: Option<u64>,
+    },
+    #[serde(rename = "authorization_code")]
+    UsingCode {
+        client_id: String,
+        code: String,
+        code_verifier: String,
+        #[serde(skip_serializing_if = "Option::is_none")]
+        ttl: Option<u64>,
+    },
+}
+
+#[derive(Deserialize)]
+pub struct OAuthTokenResponse {
+    pub keys_jwe: Option<String>,
+    pub refresh_token: Option<String>,
+    pub session_token: Option<String>,
+    pub expires_in: u64,
+    pub scope: String,
+    pub access_token: String,
+}
+
+#[derive(Deserialize, Debug)]
+pub struct OAuthAuthResponse {
+    pub redirect: String,
+    pub code: String,
+    pub state: String,
+}
+
+#[derive(Deserialize)]
+pub struct IntrospectResponse {
+    pub active: bool,
+    // Technically the response has a lot of other fields,
+    // but in practice we only use `active`.
+}
+
+#[derive(Clone, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct ProfileResponse {
+    pub uid: String,
+    pub email: String,
+    pub display_name: Option<String>,
+    pub avatar: String,
+    pub avatar_default: bool,
+}
+
+impl From<ProfileResponse> for crate::Profile {
+    fn from(p: ProfileResponse) -> Self {
+        crate::Profile {
+            uid: p.uid,
+            email: p.email,
+            display_name: p.display_name,
+            avatar: p.avatar,
+            is_default_avatar: p.avatar_default,
+        }
+    }
+}
+
+#[derive(Deserialize)]
+pub struct ScopedKeyDataResponse {
+    pub identifier: String,
+    #[serde(rename = "keyRotationSecret")]
+    pub key_rotation_secret: String,
+    #[serde(rename = "keyRotationTimestamp")]
+    pub key_rotation_timestamp: u64,
+}
+
+#[derive(Deserialize, Clone, Debug, PartialEq, Eq)]
+pub struct DuplicateTokenResponse {
+    pub uid: String,
+    #[serde(rename = "sessionToken")]
+    pub session_token: String,
+    pub verified: bool,
+    #[serde(rename = "authAt")]
+    pub auth_at: u64,
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use mockito::mock;
+    #[test]
+    #[allow(non_snake_case)]
+    fn check_OAauthTokenRequest_serialization() {
+        // Ensure OAauthTokenRequest serializes to what the server expects.
+        let using_code = OAauthTokenRequest::UsingCode {
+            code: "foo".to_owned(),
+            client_id: "bar".to_owned(),
+            code_verifier: "bobo".to_owned(),
+            ttl: None,
+        };
+        assert_eq!("{\"grant_type\":\"authorization_code\",\"client_id\":\"bar\",\"code\":\"foo\",\"code_verifier\":\"bobo\"}", serde_json::to_string(&using_code).unwrap());
+        let using_code = OAauthTokenRequest::UsingRefreshToken {
+            client_id: "bar".to_owned(),
+            refresh_token: "foo".to_owned(),
+            scope: Some("bobo".to_owned()),
+            ttl: Some(123),
+        };
+        assert_eq!("{\"grant_type\":\"refresh_token\",\"client_id\":\"bar\",\"refresh_token\":\"foo\",\"scope\":\"bobo\",\"ttl\":123}", serde_json::to_string(&using_code).unwrap());
+    }
+
+    #[test]
+    fn test_backoff() {
+        viaduct_reqwest::use_reqwest_backend();
+        let m = mock("POST", "/v1/account/devices/invoke_command")
+            .with_status(429)
+            .with_header("Content-Type", "application/json")
+            .with_header("retry-after", "1000000")
+            .with_body(
+                r#"{
+                "code": 429,
+                "errno": 120,
+                "error": "Too many requests",
+                "message": "Too many requests",
+                "retryAfter": 1000000,
+                "info": "Some information"
+            }"#,
+            )
+            .create();
+        let client = Client::new();
+        let path = format!(
+            "{}/{}",
+            mockito::server_url(),
+            "v1/account/devices/invoke_command"
+        );
+        let url = Url::parse(&path).unwrap();
+        let path = url.path().to_string();
+        let request = Request::post(url);
+        assert!(client.make_request(request.clone()).is_err());
+        let state = client.state.lock();
+        if let HttpClientState::Backoff {
+            backoff_end_duration,
+            time_since_backoff: _,
+        } = state.get(&path).unwrap()
+        {
+            assert_eq!(*backoff_end_duration, Duration::from_secs(1_000_000));
+            // Hacky way to drop the mutex gaurd, so that the next call to
+            // client.make_request doesn't hang or panic
+            std::mem::drop(state);
+            assert!(client.make_request(request).is_err());
+            // We should be backed off, the second "make_request" should not
+            // send a request to the server
+            m.expect(1).assert();
+        } else {
+            panic!("HttpClientState should be a timeout!");
+        }
+    }
+
+    #[test]
+    fn test_backoff_then_ok() {
+        viaduct_reqwest::use_reqwest_backend();
+        let m = mock("POST", "/v1/account/devices/invoke_command")
+            .with_status(429)
+            .with_header("Content-Type", "application/json")
+            .with_header("retry-after", "1")
+            .with_body(
+                r#"{
+                "code": 429,
+                "errno": 120,
+                "error": "Too many requests",
+                "message": "Too many requests",
+                "retryAfter": 1,
+                "info": "Some information"
+            }"#,
+            )
+            .create();
+        let client = Client::new();
+        let path = format!(
+            "{}/{}",
+            mockito::server_url(),
+            "v1/account/devices/invoke_command"
+        );
+        let url = Url::parse(&path).unwrap();
+        let path = url.path().to_string();
+        let request = Request::post(url);
+        assert!(client.make_request(request.clone()).is_err());
+        let state = client.state.lock();
+        if let HttpClientState::Backoff {
+            backoff_end_duration,
+            time_since_backoff: _,
+        } = state.get(&path).unwrap()
+        {
+            assert_eq!(*backoff_end_duration, Duration::from_secs(1));
+            // We sleep for 1 second, so pass the backoff timeout
+            std::thread::sleep(*backoff_end_duration);
+
+            // Hacky way to drop the mutex gaurd, so that the next call to
+            // client.make_request doesn't hang or panic
+            std::mem::drop(state);
+            assert!(client.make_request(request).is_err());
+            // We backed off, but the time has passed, the second request should have
+            // went to the server
+            m.expect(2).assert();
+        } else {
+            panic!("HttpClientState should be a timeout!");
+        }
+    }
+
+    #[test]
+    fn test_backoff_per_path() {
+        viaduct_reqwest::use_reqwest_backend();
+        let m1 = mock("POST", "/v1/account/devices/invoke_command")
+            .with_status(429)
+            .with_header("Content-Type", "application/json")
+            .with_header("retry-after", "1000000")
+            .with_body(
+                r#"{
+                "code": 429,
+                "errno": 120,
+                "error": "Too many requests",
+                "message": "Too many requests",
+                "retryAfter": 1000000,
+                "info": "Some information"
+            }"#,
+            )
+            .create();
+        let m2 = mock("GET", "/v1/account/device/commands")
+            .with_status(200)
+            .with_header("Content-Type", "application/json")
+            .with_body(
+                r#"
+        {
+         "index": 3,
+         "last": true,
+         "messages": []
+        }"#,
+            )
+            .create();
+        let client = Client::new();
+        let path = format!(
+            "{}/{}",
+            mockito::server_url(),
+            "v1/account/devices/invoke_command"
+        );
+        let url = Url::parse(&path).unwrap();
+        let path = url.path().to_string();
+        let request = Request::post(url);
+        assert!(client.make_request(request).is_err());
+        let state = client.state.lock();
+        if let HttpClientState::Backoff {
+            backoff_end_duration,
+            time_since_backoff: _,
+        } = state.get(&path).unwrap()
+        {
+            assert_eq!(*backoff_end_duration, Duration::from_secs(1_000_000));
+
+            let path2 = format!("{}/{}", mockito::server_url(), "v1/account/device/commands");
+            // Hacky way to drop the mutex guard, so that the next call to
+            // client.make_request doesn't hang or panic
+            std::mem::drop(state);
+            let second_request = Request::get(Url::parse(&path2).unwrap());
+            assert!(client.make_request(second_request).is_ok());
+            // The first endpoint is backed off, but the second one is not
+            // Both endpoint should be hit
+            m1.expect(1).assert();
+            m2.expect(1).assert();
+        } else {
+            panic!("HttpClientState should be a timeout!");
+        }
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/fxa_client/internal/mod.rs.html b/book/rust-docs/src/fxa_client/internal/mod.rs.html new file mode 100644 index 0000000000..9d4f7626df --- /dev/null +++ b/book/rust-docs/src/fxa_client/internal/mod.rs.html @@ -0,0 +1,1221 @@ +mod.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
+540
+541
+542
+543
+544
+545
+546
+547
+548
+549
+550
+551
+552
+553
+554
+555
+556
+557
+558
+559
+560
+561
+562
+563
+564
+565
+566
+567
+568
+569
+570
+571
+572
+573
+574
+575
+576
+577
+578
+579
+580
+581
+582
+583
+584
+585
+586
+587
+588
+589
+590
+591
+592
+593
+594
+595
+596
+597
+598
+599
+600
+601
+602
+603
+604
+605
+606
+607
+608
+609
+610
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! # Internal implementation details for the fxa_client crate.
+
+use self::{
+    config::Config,
+    oauth::{AuthCircuitBreaker, OAuthFlow, OAUTH_WEBCHANNEL_REDIRECT},
+    state_manager::StateManager,
+    state_persistence::PersistedState,
+    telemetry::FxaTelemetry,
+};
+use crate::{Error, FxaConfig, FxaRustAuthState, Result};
+use serde_derive::*;
+use std::{
+    collections::{HashMap, HashSet},
+    sync::Arc,
+};
+use url::Url;
+
+#[cfg(feature = "integration_test")]
+pub mod auth;
+mod commands;
+pub mod config;
+pub mod device;
+mod http_client;
+mod oauth;
+mod profile;
+mod push;
+mod scoped_keys;
+mod scopes;
+mod send_tab;
+mod state_manager;
+mod state_persistence;
+mod telemetry;
+mod util;
+
+type FxAClient = dyn http_client::FxAClient + Sync + Send;
+
+// FIXME: https://github.com/myelin-ai/mockiato/issues/106.
+#[cfg(test)]
+unsafe impl Send for http_client::MockFxAClient {}
+#[cfg(test)]
+unsafe impl Sync for http_client::MockFxAClient {}
+
+// It this struct is modified, please check if the
+// `FirefoxAccount.start_over` function also needs
+// to be modified.
+pub struct FirefoxAccount {
+    client: Arc<FxAClient>,
+    state: StateManager,
+    attached_clients_cache: Option<CachedResponse<Vec<http_client::GetAttachedClientResponse>>>,
+    devices_cache: Option<CachedResponse<Vec<http_client::GetDeviceResponse>>>,
+    auth_circuit_breaker: AuthCircuitBreaker,
+    telemetry: FxaTelemetry,
+}
+
+impl FirefoxAccount {
+    fn from_state(state: PersistedState) -> Self {
+        Self {
+            client: Arc::new(http_client::Client::new()),
+            state: StateManager::new(state),
+            attached_clients_cache: None,
+            devices_cache: None,
+            auth_circuit_breaker: Default::default(),
+            telemetry: FxaTelemetry::new(),
+        }
+    }
+
+    /// Create a new `FirefoxAccount` instance using a `Config`.
+    pub fn with_config(config: Config) -> Self {
+        Self::from_state(PersistedState {
+            config,
+            refresh_token: None,
+            scoped_keys: HashMap::new(),
+            last_handled_command: None,
+            commands_data: HashMap::new(),
+            device_capabilities: HashSet::new(),
+            server_local_device_info: None,
+            session_token: None,
+            current_device_id: None,
+            last_seen_profile: None,
+            access_token_cache: HashMap::new(),
+            logged_out_from_auth_issues: false,
+        })
+    }
+
+    /// Create a new `FirefoxAccount` instance.
+    pub fn new(config: FxaConfig) -> Self {
+        Self::with_config(config.into())
+    }
+
+    #[cfg(test)]
+    pub(crate) fn set_client(&mut self, client: Arc<FxAClient>) {
+        self.client = client;
+    }
+
+    /// Restore a `FirefoxAccount` instance from a serialized state
+    /// created using `to_json`.
+    pub fn from_json(data: &str) -> Result<Self> {
+        let state = state_persistence::state_from_json(data)?;
+        Ok(Self::from_state(state))
+    }
+
+    /// Serialize a `FirefoxAccount` instance internal state
+    /// to be restored later using `from_json`.
+    pub fn to_json(&self) -> Result<String> {
+        self.state.serialize_persisted_state()
+    }
+
+    /// Clear the attached clients and devices cache
+    pub fn clear_devices_and_attached_clients_cache(&mut self) {
+        self.attached_clients_cache = None;
+        self.devices_cache = None;
+    }
+
+    /// Get the Sync Token Server endpoint URL.
+    pub fn get_token_server_endpoint_url(&self) -> Result<String> {
+        Ok(self.state.config().token_server_endpoint_url()?.into())
+    }
+
+    /// Get the pairing URL to navigate to on the Auth side (typically
+    /// a computer).
+    pub fn get_pairing_authority_url(&self) -> Result<String> {
+        // Special case for the production server, we use the shorter firefox.com/pair URL.
+        if self.state.config().content_url()? == Url::parse(config::CONTENT_URL_RELEASE)? {
+            return Ok("https://firefox.com/pair".to_owned());
+        }
+        // Similarly special case for the China server.
+        if self.state.config().content_url()? == Url::parse(config::CONTENT_URL_CHINA)? {
+            return Ok("https://firefox.com.cn/pair".to_owned());
+        }
+        Ok(self.state.config().pair_url()?.into())
+    }
+
+    /// Get the "connection succeeded" page URL.
+    /// It is typically used to redirect the user after
+    /// having intercepted the OAuth login-flow state/code
+    /// redirection.
+    pub fn get_connection_success_url(&self) -> Result<String> {
+        let mut url = self.state.config().connect_another_device_url()?;
+        url.query_pairs_mut()
+            .append_pair("showSuccessMessage", "true");
+        Ok(url.into())
+    }
+
+    /// Get the "manage account" page URL.
+    /// It is typically used in the application's account status UI,
+    /// to link the user out to a webpage where they can manage
+    /// all the details of their account.
+    ///
+    /// * `entrypoint` - Application-provided string identifying the UI touchpoint
+    ///                  through which the page was accessed, for metrics purposes.
+    pub fn get_manage_account_url(&mut self, entrypoint: &str) -> Result<String> {
+        let mut url = self.state.config().settings_url()?;
+        url.query_pairs_mut().append_pair("entrypoint", entrypoint);
+        if self.state.config().redirect_uri == OAUTH_WEBCHANNEL_REDIRECT {
+            url.query_pairs_mut()
+                .append_pair("context", "oauth_webchannel_v1");
+        }
+        self.add_account_identifiers_to_url(url)
+    }
+
+    /// Get the "manage devices" page URL.
+    /// It is typically used in the application's account status UI,
+    /// to link the user out to a webpage where they can manage
+    /// the devices connected to their account.
+    ///
+    /// * `entrypoint` - Application-provided string identifying the UI touchpoint
+    ///                  through which the page was accessed, for metrics purposes.
+    pub fn get_manage_devices_url(&mut self, entrypoint: &str) -> Result<String> {
+        let mut url = self.state.config().settings_clients_url()?;
+        url.query_pairs_mut().append_pair("entrypoint", entrypoint);
+        self.add_account_identifiers_to_url(url)
+    }
+
+    fn add_account_identifiers_to_url(&mut self, mut url: Url) -> Result<String> {
+        let profile = self.get_profile(false)?;
+        url.query_pairs_mut()
+            .append_pair("uid", &profile.uid)
+            .append_pair("email", &profile.email);
+        Ok(url.into())
+    }
+
+    fn get_refresh_token(&self) -> Result<&str> {
+        match self.state.refresh_token() {
+            Some(token_info) => Ok(&token_info.token),
+            None => Err(Error::NoRefreshToken),
+        }
+    }
+
+    pub fn get_auth_state(&self) -> FxaRustAuthState {
+        self.state.get_auth_state()
+    }
+
+    /// Disconnect from the account and optionally destroy our device record. This will
+    /// leave the account object in a state where it can eventually reconnect to the same user.
+    /// This is a "best effort" infallible method: e.g. if the network is unreachable,
+    /// the device could still be in the FxA devices manager.
+    ///
+    /// **💾 This method alters the persisted account state.**
+    pub fn disconnect(&mut self) {
+        let current_device_result;
+        {
+            current_device_result = self.get_current_device();
+        }
+
+        if let Some(refresh_token) = self.state.refresh_token() {
+            // Delete the current device (which deletes the refresh token), or
+            // the refresh token directly if we don't have a device.
+            let destroy_result = match current_device_result {
+                // If we get an error trying to fetch our device record we'll at least
+                // still try to delete the refresh token itself.
+                Ok(Some(device)) => self.client.destroy_device_record(
+                    self.state.config(),
+                    &refresh_token.token,
+                    &device.id,
+                ),
+                _ => self
+                    .client
+                    .destroy_refresh_token(self.state.config(), &refresh_token.token),
+            };
+            if let Err(e) = destroy_result {
+                log::warn!("Error while destroying the device: {}", e);
+            }
+        }
+        self.state.disconnect();
+        self.clear_devices_and_attached_clients_cache();
+        self.telemetry = FxaTelemetry::new();
+    }
+
+    /// Update the state based on authentication issues.
+    ///
+    /// **💾 This method alters the persisted account state.**
+    ///
+    /// Call this if you know there's an authentication / authorization issue that requires the
+    /// user to re-authenticated.  It transitions the user to the [FxaRustAuthState.AuthIssues] state.
+    pub fn on_auth_issues(&mut self) {
+        self.state.on_auth_issues();
+        self.clear_devices_and_attached_clients_cache();
+        self.telemetry = FxaTelemetry::new();
+    }
+
+    pub fn simulate_temporary_auth_token_issue(&mut self) {
+        self.state.simulate_temporary_auth_token_issue()
+    }
+
+    pub fn simulate_permanent_auth_token_issue(&mut self) {
+        self.state.simulate_permanent_auth_token_issue()
+    }
+}
+
+#[derive(Debug, Clone, Deserialize, Serialize)]
+pub(crate) struct CachedResponse<T> {
+    response: T,
+    cached_at: u64,
+    etag: String,
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::internal::device::*;
+    use crate::internal::http_client::MockFxAClient;
+    use crate::internal::oauth::*;
+    use mockall::predicate::always;
+    use mockall::predicate::eq;
+
+    #[test]
+    fn test_fxa_is_send() {
+        fn is_send<T: Send>() {}
+        is_send::<FirefoxAccount>();
+    }
+
+    #[test]
+    fn test_serialize_deserialize() {
+        let config = Config::stable_dev("12345678", "https://foo.bar");
+        let fxa1 = FirefoxAccount::with_config(config);
+        let fxa1_json = fxa1.to_json().unwrap();
+        drop(fxa1);
+        let fxa2 = FirefoxAccount::from_json(&fxa1_json).unwrap();
+        let fxa2_json = fxa2.to_json().unwrap();
+        assert_eq!(fxa1_json, fxa2_json);
+    }
+
+    #[test]
+    fn test_get_connection_success_url() {
+        let config = Config::new("https://stable.dev.lcip.org", "12345678", "https://foo.bar");
+        let fxa = FirefoxAccount::with_config(config);
+        let url = fxa.get_connection_success_url().unwrap();
+        assert_eq!(
+            url,
+            "https://stable.dev.lcip.org/connect_another_device?showSuccessMessage=true"
+                .to_string()
+        );
+    }
+
+    #[test]
+    fn test_get_manage_account_url() {
+        let config = Config::new("https://stable.dev.lcip.org", "12345678", "https://foo.bar");
+        let mut fxa = FirefoxAccount::with_config(config);
+        // No current user -> Error.
+        match fxa.get_manage_account_url("test").unwrap_err() {
+            Error::NoCachedToken(_) => {}
+            _ => panic!("error not NoCachedToken"),
+        };
+        // With current user -> expected Url.
+        fxa.add_cached_profile("123", "test@example.com");
+        let url = fxa.get_manage_account_url("test").unwrap();
+        assert_eq!(
+            url,
+            "https://stable.dev.lcip.org/settings?entrypoint=test&uid=123&email=test%40example.com"
+                .to_string()
+        );
+    }
+
+    #[test]
+    fn test_get_manage_account_url_with_webchannel_redirect() {
+        let config = Config::new(
+            "https://stable.dev.lcip.org",
+            "12345678",
+            OAUTH_WEBCHANNEL_REDIRECT,
+        );
+        let mut fxa = FirefoxAccount::with_config(config);
+        fxa.add_cached_profile("123", "test@example.com");
+        let url = fxa.get_manage_account_url("test").unwrap();
+        assert_eq!(
+            url,
+            "https://stable.dev.lcip.org/settings?entrypoint=test&context=oauth_webchannel_v1&uid=123&email=test%40example.com"
+                .to_string()
+        );
+    }
+
+    #[test]
+    fn test_get_manage_devices_url() {
+        let config = Config::new("https://stable.dev.lcip.org", "12345678", "https://foo.bar");
+        let mut fxa = FirefoxAccount::with_config(config);
+        // No current user -> Error.
+        match fxa.get_manage_devices_url("test").unwrap_err() {
+            Error::NoCachedToken(_) => {}
+            _ => panic!("error not NoCachedToken"),
+        };
+        // With current user -> expected Url.
+        fxa.add_cached_profile("123", "test@example.com");
+        let url = fxa.get_manage_devices_url("test").unwrap();
+        assert_eq!(
+            url,
+            "https://stable.dev.lcip.org/settings/clients?entrypoint=test&uid=123&email=test%40example.com"
+                .to_string()
+        );
+    }
+
+    #[test]
+    fn test_disconnect_no_refresh_token() {
+        let config = Config::new("https://stable.dev.lcip.org", "12345678", "https://foo.bar");
+        let mut fxa = FirefoxAccount::with_config(config);
+
+        fxa.add_cached_token(
+            "profile",
+            AccessTokenInfo {
+                scope: "profile".to_string(),
+                token: "profiletok".to_string(),
+                key: None,
+                expires_at: u64::max_value(),
+            },
+        );
+
+        let client = MockFxAClient::new();
+        fxa.set_client(Arc::new(client));
+
+        assert!(!fxa.state.is_access_token_cache_empty());
+        fxa.disconnect();
+        assert!(fxa.state.is_access_token_cache_empty());
+    }
+
+    #[test]
+    fn test_disconnect_device() {
+        let config = Config::stable_dev("12345678", "https://foo.bar");
+        let mut fxa = FirefoxAccount::with_config(config);
+
+        fxa.state.force_refresh_token(RefreshToken {
+            token: "refreshtok".to_string(),
+            scopes: HashSet::default(),
+        });
+
+        let mut client = MockFxAClient::new();
+        client
+            .expect_get_devices()
+            .with(always(), eq("refreshtok"))
+            .times(1)
+            .returning(|_, _| {
+                Ok(vec![
+                    Device {
+                        common: http_client::DeviceResponseCommon {
+                            id: "1234a".to_owned(),
+                            display_name: "My Device".to_owned(),
+                            device_type: sync15::DeviceType::Mobile,
+                            push_subscription: None,
+                            available_commands: HashMap::default(),
+                            push_endpoint_expired: false,
+                        },
+                        is_current_device: true,
+                        location: http_client::DeviceLocation {
+                            city: None,
+                            country: None,
+                            state: None,
+                            state_code: None,
+                        },
+                        last_access_time: None,
+                    },
+                    Device {
+                        common: http_client::DeviceResponseCommon {
+                            id: "a4321".to_owned(),
+                            display_name: "My Other Device".to_owned(),
+                            device_type: sync15::DeviceType::Desktop,
+                            push_subscription: None,
+                            available_commands: HashMap::default(),
+                            push_endpoint_expired: false,
+                        },
+                        is_current_device: false,
+                        location: http_client::DeviceLocation {
+                            city: None,
+                            country: None,
+                            state: None,
+                            state_code: None,
+                        },
+                        last_access_time: None,
+                    },
+                ])
+            });
+        client
+            .expect_destroy_device_record()
+            .with(always(), eq("refreshtok"), eq("1234a"))
+            .times(1)
+            .returning(|_, _, _| Ok(()));
+        fxa.set_client(Arc::new(client));
+
+        assert!(fxa.state.refresh_token().is_some());
+        fxa.disconnect();
+        assert!(fxa.state.refresh_token().is_none());
+    }
+
+    #[test]
+    fn test_disconnect_no_device() {
+        let config = Config::stable_dev("12345678", "https://foo.bar");
+        let mut fxa = FirefoxAccount::with_config(config);
+
+        fxa.state.force_refresh_token(RefreshToken {
+            token: "refreshtok".to_string(),
+            scopes: HashSet::default(),
+        });
+
+        let mut client = MockFxAClient::new();
+        client
+            .expect_get_devices()
+            .with(always(), eq("refreshtok"))
+            .times(1)
+            .returning(|_, _| {
+                Ok(vec![Device {
+                    common: http_client::DeviceResponseCommon {
+                        id: "a4321".to_owned(),
+                        display_name: "My Other Device".to_owned(),
+                        device_type: sync15::DeviceType::Desktop,
+                        push_subscription: None,
+                        available_commands: HashMap::default(),
+                        push_endpoint_expired: false,
+                    },
+                    is_current_device: false,
+                    location: http_client::DeviceLocation {
+                        city: None,
+                        country: None,
+                        state: None,
+                        state_code: None,
+                    },
+                    last_access_time: None,
+                }])
+            });
+        client
+            .expect_destroy_refresh_token()
+            .with(always(), eq("refreshtok"))
+            .times(1)
+            .returning(|_, _| Ok(()));
+        fxa.set_client(Arc::new(client));
+
+        assert!(fxa.state.refresh_token().is_some());
+        fxa.disconnect();
+        assert!(fxa.state.refresh_token().is_none());
+    }
+
+    #[test]
+    fn test_disconnect_network_errors() {
+        let config = Config::stable_dev("12345678", "https://foo.bar");
+        let mut fxa = FirefoxAccount::with_config(config);
+
+        fxa.state.force_refresh_token(RefreshToken {
+            token: "refreshtok".to_string(),
+            scopes: HashSet::default(),
+        });
+
+        let mut client = MockFxAClient::new();
+        client
+            .expect_get_devices()
+            .with(always(), eq("refreshtok"))
+            .times(1)
+            .returning(|_, _| Ok(vec![]));
+        client
+            .expect_destroy_refresh_token()
+            .with(always(), eq("refreshtok"))
+            .times(1)
+            .returning(|_, _| {
+                Err(Error::RemoteError {
+                    code: 500,
+                    errno: 101,
+                    error: "Did not work!".to_owned(),
+                    message: "Did not work!".to_owned(),
+                    info: "Did not work!".to_owned(),
+                })
+            });
+        fxa.set_client(Arc::new(client));
+
+        assert!(fxa.state.refresh_token().is_some());
+        fxa.disconnect();
+        assert!(fxa.state.refresh_token().is_none());
+    }
+
+    #[test]
+    fn test_on_auth_issues() {
+        let config = Config::new("https://stable.dev.lcip.org", "12345678", "https://foo.bar");
+        let mut fxa = FirefoxAccount::with_config(config);
+
+        fxa.state.force_refresh_token(RefreshToken {
+            token: "refresh_token".to_owned(),
+            scopes: HashSet::new(),
+        });
+        fxa.state.force_current_device_id("original-device-id");
+        assert_eq!(fxa.get_auth_state(), FxaRustAuthState::Connected);
+
+        fxa.on_auth_issues();
+        assert_eq!(fxa.get_auth_state(), FxaRustAuthState::AuthIssues);
+
+        fxa.state.complete_oauth_flow(
+            vec![],
+            RefreshToken {
+                token: "refreshtok".to_owned(),
+                scopes: HashSet::default(),
+            },
+            None,
+        );
+        assert_eq!(fxa.get_auth_state(), FxaRustAuthState::Connected);
+
+        // The device ID should be the same as before `on_auth_issues` was called.  This
+        // way, methods like `ensure_capabilities` and `set_device_name`, can re-use it and we
+        // won't try to create a new device record.
+        assert_eq!(fxa.state.current_device_id(), Some("original-device-id"));
+    }
+
+    #[test]
+    fn test_get_auth_state() {
+        let config = Config::new("https://stable.dev.lcip.org", "12345678", "https://foo.bar");
+        let mut fxa = FirefoxAccount::with_config(config);
+
+        fn assert_auth_state(fxa: &FirefoxAccount, correct_state: FxaRustAuthState) {
+            assert_eq!(fxa.get_auth_state(), correct_state);
+
+            let persisted = FirefoxAccount::from_json(&fxa.to_json().unwrap()).unwrap();
+            assert_eq!(persisted.get_auth_state(), correct_state);
+        }
+
+        // The state starts as disconnected
+        assert_auth_state(&fxa, FxaRustAuthState::Disconnected);
+
+        // When we get the refresh tokens the state changes to connected
+        fxa.state.force_refresh_token(RefreshToken {
+            token: "refresh_token".to_owned(),
+            scopes: HashSet::new(),
+        });
+        assert_auth_state(&fxa, FxaRustAuthState::Connected);
+
+        fxa.disconnect();
+        assert_auth_state(&fxa, FxaRustAuthState::Disconnected);
+
+        fxa.disconnect();
+        assert_auth_state(&fxa, FxaRustAuthState::Disconnected);
+    }
+
+    #[test]
+    fn test_get_pairing_authority_url() {
+        let config = Config::new("https://foo.bar", "12345678", "https://foo.bar");
+        let fxa = FirefoxAccount::with_config(config);
+        assert_eq!(
+            fxa.get_pairing_authority_url().unwrap().as_str(),
+            "https://foo.bar/pair"
+        );
+
+        let config = Config::release("12345678", "https://foo.bar");
+        let fxa = FirefoxAccount::with_config(config);
+        assert_eq!(
+            fxa.get_pairing_authority_url().unwrap().as_str(),
+            "https://firefox.com/pair"
+        );
+
+        let config = Config::china("12345678", "https://foo.bar");
+        let fxa = FirefoxAccount::with_config(config);
+        assert_eq!(
+            fxa.get_pairing_authority_url().unwrap().as_str(),
+            "https://firefox.com.cn/pair"
+        )
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/fxa_client/internal/oauth.rs.html b/book/rust-docs/src/fxa_client/internal/oauth.rs.html new file mode 100644 index 0000000000..1576f63bb4 --- /dev/null +++ b/book/rust-docs/src/fxa_client/internal/oauth.rs.html @@ -0,0 +1,2185 @@ +oauth.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
+540
+541
+542
+543
+544
+545
+546
+547
+548
+549
+550
+551
+552
+553
+554
+555
+556
+557
+558
+559
+560
+561
+562
+563
+564
+565
+566
+567
+568
+569
+570
+571
+572
+573
+574
+575
+576
+577
+578
+579
+580
+581
+582
+583
+584
+585
+586
+587
+588
+589
+590
+591
+592
+593
+594
+595
+596
+597
+598
+599
+600
+601
+602
+603
+604
+605
+606
+607
+608
+609
+610
+611
+612
+613
+614
+615
+616
+617
+618
+619
+620
+621
+622
+623
+624
+625
+626
+627
+628
+629
+630
+631
+632
+633
+634
+635
+636
+637
+638
+639
+640
+641
+642
+643
+644
+645
+646
+647
+648
+649
+650
+651
+652
+653
+654
+655
+656
+657
+658
+659
+660
+661
+662
+663
+664
+665
+666
+667
+668
+669
+670
+671
+672
+673
+674
+675
+676
+677
+678
+679
+680
+681
+682
+683
+684
+685
+686
+687
+688
+689
+690
+691
+692
+693
+694
+695
+696
+697
+698
+699
+700
+701
+702
+703
+704
+705
+706
+707
+708
+709
+710
+711
+712
+713
+714
+715
+716
+717
+718
+719
+720
+721
+722
+723
+724
+725
+726
+727
+728
+729
+730
+731
+732
+733
+734
+735
+736
+737
+738
+739
+740
+741
+742
+743
+744
+745
+746
+747
+748
+749
+750
+751
+752
+753
+754
+755
+756
+757
+758
+759
+760
+761
+762
+763
+764
+765
+766
+767
+768
+769
+770
+771
+772
+773
+774
+775
+776
+777
+778
+779
+780
+781
+782
+783
+784
+785
+786
+787
+788
+789
+790
+791
+792
+793
+794
+795
+796
+797
+798
+799
+800
+801
+802
+803
+804
+805
+806
+807
+808
+809
+810
+811
+812
+813
+814
+815
+816
+817
+818
+819
+820
+821
+822
+823
+824
+825
+826
+827
+828
+829
+830
+831
+832
+833
+834
+835
+836
+837
+838
+839
+840
+841
+842
+843
+844
+845
+846
+847
+848
+849
+850
+851
+852
+853
+854
+855
+856
+857
+858
+859
+860
+861
+862
+863
+864
+865
+866
+867
+868
+869
+870
+871
+872
+873
+874
+875
+876
+877
+878
+879
+880
+881
+882
+883
+884
+885
+886
+887
+888
+889
+890
+891
+892
+893
+894
+895
+896
+897
+898
+899
+900
+901
+902
+903
+904
+905
+906
+907
+908
+909
+910
+911
+912
+913
+914
+915
+916
+917
+918
+919
+920
+921
+922
+923
+924
+925
+926
+927
+928
+929
+930
+931
+932
+933
+934
+935
+936
+937
+938
+939
+940
+941
+942
+943
+944
+945
+946
+947
+948
+949
+950
+951
+952
+953
+954
+955
+956
+957
+958
+959
+960
+961
+962
+963
+964
+965
+966
+967
+968
+969
+970
+971
+972
+973
+974
+975
+976
+977
+978
+979
+980
+981
+982
+983
+984
+985
+986
+987
+988
+989
+990
+991
+992
+993
+994
+995
+996
+997
+998
+999
+1000
+1001
+1002
+1003
+1004
+1005
+1006
+1007
+1008
+1009
+1010
+1011
+1012
+1013
+1014
+1015
+1016
+1017
+1018
+1019
+1020
+1021
+1022
+1023
+1024
+1025
+1026
+1027
+1028
+1029
+1030
+1031
+1032
+1033
+1034
+1035
+1036
+1037
+1038
+1039
+1040
+1041
+1042
+1043
+1044
+1045
+1046
+1047
+1048
+1049
+1050
+1051
+1052
+1053
+1054
+1055
+1056
+1057
+1058
+1059
+1060
+1061
+1062
+1063
+1064
+1065
+1066
+1067
+1068
+1069
+1070
+1071
+1072
+1073
+1074
+1075
+1076
+1077
+1078
+1079
+1080
+1081
+1082
+1083
+1084
+1085
+1086
+1087
+1088
+1089
+1090
+1091
+1092
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+pub mod attached_clients;
+use super::scopes;
+use super::{
+    http_client::{
+        AuthorizationRequestParameters, IntrospectResponse as IntrospectInfo, OAuthTokenResponse,
+    },
+    scoped_keys::ScopedKeysFlow,
+    util, FirefoxAccount,
+};
+use crate::{AuthorizationParameters, Error, MetricsParams, Result, ScopedKey};
+use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
+use jwcrypto::{EncryptionAlgorithm, EncryptionParameters};
+use rate_limiter::RateLimiter;
+use rc_crypto::digest;
+use serde_derive::*;
+use std::{
+    collections::{HashMap, HashSet},
+    iter::FromIterator,
+    time::{SystemTime, UNIX_EPOCH},
+};
+use url::Url;
+// If a cached token has less than `OAUTH_MIN_TIME_LEFT` seconds left to live,
+// it will be considered already expired.
+const OAUTH_MIN_TIME_LEFT: u64 = 60;
+// Special redirect urn based on the OAuth native spec, signals that the
+// WebChannel flow is used
+pub const OAUTH_WEBCHANNEL_REDIRECT: &str = "urn:ietf:wg:oauth:2.0:oob:oauth-redirect-webchannel";
+
+impl FirefoxAccount {
+    /// Fetch a short-lived access token using the saved refresh token.
+    /// If there is no refresh token held or if it is not authorized for some of the requested
+    /// scopes, this method will error-out and a login flow will need to be initiated
+    /// using `begin_oauth_flow`.
+    ///
+    /// * `scopes` - Space-separated list of requested scopes.
+    /// * `ttl` - the ttl in seconds of the token requested from the server.
+    ///
+    /// **💾 This method may alter the persisted account state.**
+    pub fn get_access_token(&mut self, scope: &str, ttl: Option<u64>) -> Result<AccessTokenInfo> {
+        if scope.contains(' ') {
+            return Err(Error::MultipleScopesRequested);
+        }
+        if let Some(oauth_info) = self.state.get_cached_access_token(scope) {
+            if oauth_info.expires_at > util::now_secs() + OAUTH_MIN_TIME_LEFT {
+                // If the cached key is missing the required sync scoped key, try to fetch it again
+                if oauth_info.check_missing_sync_scoped_key().is_ok() {
+                    return Ok(oauth_info.clone());
+                }
+            }
+        }
+        let resp = match self.state.refresh_token() {
+            Some(refresh_token) => {
+                if refresh_token.scopes.contains(scope) {
+                    self.client.create_access_token_using_refresh_token(
+                        self.state.config(),
+                        &refresh_token.token,
+                        ttl,
+                        &[scope],
+                    )?
+                } else {
+                    return Err(Error::NoCachedToken(scope.to_string()));
+                }
+            }
+            None => match self.state.session_token() {
+                Some(session_token) => self.client.create_access_token_using_session_token(
+                    self.state.config(),
+                    session_token,
+                    &[scope],
+                )?,
+                None => return Err(Error::NoCachedToken(scope.to_string())),
+            },
+        };
+        let since_epoch = SystemTime::now()
+            .duration_since(UNIX_EPOCH)
+            .map_err(|_| Error::IllegalState("Current date before Unix Epoch."))?;
+        let expires_at = since_epoch.as_secs() + resp.expires_in;
+        let token_info = AccessTokenInfo {
+            scope: resp.scope,
+            token: resp.access_token,
+            key: self.state.get_scoped_key(scope).cloned(),
+            expires_at,
+        };
+        self.state
+            .add_cached_access_token(scope, token_info.clone());
+        token_info.check_missing_sync_scoped_key()?;
+        Ok(token_info)
+    }
+
+    /// Retrieve the current session token from state
+    pub fn get_session_token(&self) -> Result<String> {
+        match self.state.session_token() {
+            Some(session_token) => Ok(session_token.to_string()),
+            None => Err(Error::NoSessionToken),
+        }
+    }
+
+    /// Check whether user is authorized using our refresh token.
+    pub fn check_authorization_status(&mut self) -> Result<IntrospectInfo> {
+        let resp = match self.state.refresh_token() {
+            Some(refresh_token) => {
+                self.auth_circuit_breaker.check()?;
+                self.client
+                    .check_refresh_token_status(self.state.config(), &refresh_token.token)?
+            }
+            None => return Err(Error::NoRefreshToken),
+        };
+        Ok(IntrospectInfo {
+            active: resp.active,
+        })
+    }
+
+    /// Initiate a pairing flow and return a URL that should be navigated to.
+    ///
+    /// * `pairing_url` - A pairing URL obtained by scanning a QR code produced by
+    /// the pairing authority.
+    /// * `scopes` - Space-separated list of requested scopes by the pairing supplicant.
+    /// * `entrypoint` - The entrypoint to be used for data collection
+    /// * `metrics` - Optional parameters for metrics
+    pub fn begin_pairing_flow(
+        &mut self,
+        pairing_url: &str,
+        scopes: &[&str],
+        entrypoint: &str,
+        metrics: Option<MetricsParams>,
+    ) -> Result<String> {
+        let mut url = self.state.config().pair_supp_url()?;
+        url.query_pairs_mut().append_pair("entrypoint", entrypoint);
+        if let Some(metrics) = metrics {
+            metrics.append_params_to_url(&mut url);
+        }
+        let pairing_url = Url::parse(pairing_url)?;
+        if url.host_str() != pairing_url.host_str() {
+            return Err(Error::OriginMismatch);
+        }
+        url.set_fragment(pairing_url.fragment());
+        self.oauth_flow(url, scopes)
+    }
+
+    /// Initiate an OAuth login flow and return a URL that should be navigated to.
+    ///
+    /// * `scopes` - Space-separated list of requested scopes.
+    /// * `entrypoint` - The entrypoint to be used for metrics
+    /// * `metrics` - Optional metrics parameters
+    pub fn begin_oauth_flow(
+        &mut self,
+        scopes: &[&str],
+        entrypoint: &str,
+        metrics: Option<MetricsParams>,
+    ) -> Result<String> {
+        let mut url = if self.state.last_seen_profile().is_some() {
+            self.state.config().oauth_force_auth_url()?
+        } else {
+            self.state.config().authorization_endpoint()?
+        };
+
+        url.query_pairs_mut()
+            .append_pair("action", "email")
+            .append_pair("response_type", "code")
+            .append_pair("entrypoint", entrypoint);
+        if let Some(metrics) = metrics {
+            metrics.append_params_to_url(&mut url);
+        }
+
+        if let Some(cached_profile) = self.state.last_seen_profile() {
+            url.query_pairs_mut()
+                .append_pair("email", &cached_profile.response.email);
+        }
+
+        let scopes: Vec<String> = match self.state.refresh_token() {
+            Some(refresh_token) => {
+                // Union of the already held scopes and the one requested.
+                let mut all_scopes: Vec<String> = vec![];
+                all_scopes.extend(scopes.iter().map(ToString::to_string));
+                let existing_scopes = refresh_token.scopes.clone();
+                all_scopes.extend(existing_scopes);
+                HashSet::<String>::from_iter(all_scopes)
+                    .into_iter()
+                    .collect()
+            }
+            None => scopes.iter().map(ToString::to_string).collect(),
+        };
+        let scopes: Vec<&str> = scopes.iter().map(<_>::as_ref).collect();
+        self.oauth_flow(url, &scopes)
+    }
+
+    /// Fetch an OAuth code for a particular client using a session token from the account state.
+    ///
+    /// * `auth_params` Authorization parameters  which includes:
+    ///     *  `client_id` - OAuth client id.
+    ///     *  `scope` - list of requested scopes.
+    ///     *  `state` - OAuth state.
+    ///     *  `access_type` - Type of OAuth access, can be "offline" and "online"
+    ///     *  `pkce_params` - Optional PKCE parameters for public clients (`code_challenge` and `code_challenge_method`)
+    ///     *  `keys_jwk` - Optional JWK used to encrypt scoped keys
+    pub fn authorize_code_using_session_token(
+        &self,
+        auth_params: AuthorizationParameters,
+    ) -> Result<String> {
+        let session_token = self.get_session_token()?;
+
+        // Validate request to ensure that the client is actually allowed to request
+        // the scopes they requested
+        let allowed_scopes = self.client.get_scoped_key_data(
+            self.state.config(),
+            &session_token,
+            &auth_params.client_id,
+            &auth_params.scope.join(" "),
+        )?;
+
+        if let Some(not_allowed_scope) = auth_params
+            .scope
+            .iter()
+            .find(|scope| !allowed_scopes.contains_key(*scope))
+        {
+            return Err(Error::ScopeNotAllowed(
+                auth_params.client_id.clone(),
+                not_allowed_scope.clone(),
+            ));
+        }
+
+        let keys_jwe = if let Some(keys_jwk) = auth_params.keys_jwk {
+            let mut scoped_keys = HashMap::new();
+            allowed_scopes
+                .iter()
+                .try_for_each(|(scope, _)| -> Result<()> {
+                    scoped_keys.insert(
+                        scope,
+                        self.state
+                            .get_scoped_key(scope)
+                            .ok_or_else(|| Error::NoScopedKey(scope.clone()))?,
+                    );
+                    Ok(())
+                })?;
+            let scoped_keys = serde_json::to_string(&scoped_keys)?;
+            let keys_jwk = URL_SAFE_NO_PAD.decode(keys_jwk)?;
+            let jwk = serde_json::from_slice(&keys_jwk)?;
+            Some(jwcrypto::encrypt_to_jwe(
+                scoped_keys.as_bytes(),
+                EncryptionParameters::ECDH_ES {
+                    enc: EncryptionAlgorithm::A256GCM,
+                    peer_jwk: &jwk,
+                },
+            )?)
+        } else {
+            None
+        };
+        let auth_request_params = AuthorizationRequestParameters {
+            client_id: auth_params.client_id,
+            scope: auth_params.scope.join(" "),
+            state: auth_params.state,
+            access_type: auth_params.access_type,
+            code_challenge: auth_params.code_challenge,
+            code_challenge_method: auth_params.code_challenge_method,
+            keys_jwe,
+        };
+
+        let resp = self.client.create_authorization_code_using_session_token(
+            self.state.config(),
+            &session_token,
+            auth_request_params,
+        )?;
+
+        Ok(resp.code)
+    }
+
+    fn oauth_flow(&mut self, mut url: Url, scopes: &[&str]) -> Result<String> {
+        self.clear_access_token_cache();
+        let state = util::random_base64_url_string(16)?;
+        let code_verifier = util::random_base64_url_string(43)?;
+        let code_challenge = digest::digest(&digest::SHA256, code_verifier.as_bytes())?;
+        let code_challenge = URL_SAFE_NO_PAD.encode(code_challenge);
+        let scoped_keys_flow = ScopedKeysFlow::with_random_key()?;
+        let jwk = scoped_keys_flow.get_public_key_jwk()?;
+        let jwk_json = serde_json::to_string(&jwk)?;
+        let keys_jwk = URL_SAFE_NO_PAD.encode(jwk_json);
+        url.query_pairs_mut()
+            .append_pair("client_id", &self.state.config().client_id)
+            .append_pair("scope", &scopes.join(" "))
+            .append_pair("state", &state)
+            .append_pair("code_challenge_method", "S256")
+            .append_pair("code_challenge", &code_challenge)
+            .append_pair("access_type", "offline")
+            .append_pair("keys_jwk", &keys_jwk);
+
+        if self.state.config().redirect_uri == OAUTH_WEBCHANNEL_REDIRECT {
+            url.query_pairs_mut()
+                .append_pair("context", "oauth_webchannel_v1");
+        } else {
+            url.query_pairs_mut()
+                .append_pair("redirect_uri", &self.state.config().redirect_uri);
+        }
+
+        self.state.begin_oauth_flow(
+            state,
+            OAuthFlow {
+                scoped_keys_flow: Some(scoped_keys_flow),
+                code_verifier,
+            },
+        );
+        Ok(url.to_string())
+    }
+
+    /// Complete an OAuth flow initiated in `begin_oauth_flow` or `begin_pairing_flow`.
+    /// The `code` and `state` parameters can be obtained by parsing out the
+    /// redirect URL after a successful login.
+    ///
+    /// **💾 This method alters the persisted account state.**
+    pub fn complete_oauth_flow(&mut self, code: &str, state: &str) -> Result<()> {
+        self.clear_access_token_cache();
+        let oauth_flow = match self.state.pop_oauth_flow(state) {
+            Some(oauth_flow) => oauth_flow,
+            None => return Err(Error::UnknownOAuthState),
+        };
+        let resp = self.client.create_refresh_token_using_authorization_code(
+            self.state.config(),
+            code,
+            &oauth_flow.code_verifier,
+        )?;
+        self.handle_oauth_response(resp, oauth_flow.scoped_keys_flow)
+    }
+
+    pub(crate) fn handle_oauth_response(
+        &mut self,
+        resp: OAuthTokenResponse,
+        scoped_keys_flow: Option<ScopedKeysFlow>,
+    ) -> Result<()> {
+        let sync_scope_granted = resp.scope.split(' ').any(|s| s == scopes::OLD_SYNC);
+        let scoped_keys = match resp.keys_jwe {
+            Some(ref jwe) => {
+                let scoped_keys_flow = scoped_keys_flow.ok_or(Error::ApiClientError(
+                    "Got a JWE but have no JWK to decrypt it.",
+                ))?;
+                let decrypted_keys = scoped_keys_flow.decrypt_keys_jwe(jwe)?;
+                let scoped_keys: serde_json::Map<String, serde_json::Value> =
+                    serde_json::from_str(&decrypted_keys)?;
+                if sync_scope_granted && !scoped_keys.contains_key(scopes::OLD_SYNC) {
+                    error_support::report_error!(
+                        "fxaclient-scoped-key",
+                        "Sync scope granted, but no sync scoped key (scope granted: {}, key scopes: {})",
+                        resp.scope,
+                        scoped_keys.keys().map(|s| s.as_ref()).collect::<Vec<&str>>().join(", ")
+                    );
+                }
+                scoped_keys
+                    .into_iter()
+                    .map(|(scope, key)| Ok((scope, serde_json::from_value(key)?)))
+                    .collect::<Result<Vec<_>>>()?
+            }
+            None => {
+                if sync_scope_granted {
+                    error_support::report_error!(
+                        "fxaclient-scoped-key",
+                        "Sync scope granted, but keys_jwe is None"
+                    );
+                }
+                vec![]
+            }
+        };
+
+        // We are only interested in the refresh token at this time because we
+        // don't want to return an over-scoped access token.
+        // Let's be good citizens and destroy this access token.
+        if let Err(err) = self
+            .client
+            .destroy_access_token(self.state.config(), &resp.access_token)
+        {
+            log::warn!("Access token destruction failure: {:?}", err);
+        }
+        let old_refresh_token = self.state.refresh_token().cloned();
+        let new_refresh_token = resp
+            .refresh_token
+            .ok_or(Error::ApiClientError("No refresh token in response"))?;
+        // Destroying a refresh token also destroys its associated device,
+        // grab the device information for replication later.
+        let old_device_info = match old_refresh_token {
+            Some(_) => match self.get_current_device() {
+                Ok(maybe_device) => maybe_device,
+                Err(err) => {
+                    log::warn!("Error while getting previous device information: {:?}", err);
+                    None
+                }
+            },
+            None => None,
+        };
+        // In order to keep 1 and only 1 refresh token alive per client instance,
+        // we also destroy the existing refresh token.
+        if let Some(ref refresh_token) = old_refresh_token {
+            if let Err(err) = self
+                .client
+                .destroy_refresh_token(self.state.config(), &refresh_token.token)
+            {
+                log::warn!("Refresh token destruction failure: {:?}", err);
+            }
+        }
+        if let Some(ref device_info) = old_device_info {
+            if let Err(err) = self.replace_device(
+                &device_info.display_name,
+                &device_info.device_type,
+                &device_info.push_subscription,
+                &device_info.available_commands,
+            ) {
+                log::warn!("Device information restoration failed: {:?}", err);
+            }
+        }
+        self.state.complete_oauth_flow(
+            scoped_keys,
+            RefreshToken {
+                token: new_refresh_token,
+                scopes: resp.scope.split(' ').map(ToString::to_string).collect(),
+            },
+            resp.session_token,
+        );
+        Ok(())
+    }
+
+    /// Typically called during a password change flow.
+    /// Invalidates all tokens and fetches a new refresh token.
+    /// Because the old refresh token is not valid anymore, we can't do like `handle_oauth_response`
+    /// and re-create the device, so it is the responsibility of the caller to do so after we're
+    /// done.
+    ///
+    /// **💾 This method alters the persisted account state.**
+    pub fn handle_session_token_change(&mut self, session_token: &str) -> Result<()> {
+        let old_refresh_token = self.state.refresh_token().ok_or(Error::NoRefreshToken)?;
+        let scopes: Vec<&str> = old_refresh_token.scopes.iter().map(AsRef::as_ref).collect();
+        let resp = self.client.create_refresh_token_using_session_token(
+            self.state.config(),
+            session_token,
+            &scopes,
+        )?;
+        let new_refresh_token = resp
+            .refresh_token
+            .ok_or(Error::ApiClientError("No refresh token in response"))?;
+        self.state.update_tokens(
+            session_token.to_owned(),
+            RefreshToken {
+                token: new_refresh_token,
+                scopes: resp.scope.split(' ').map(ToString::to_string).collect(),
+            },
+        );
+        self.clear_devices_and_attached_clients_cache();
+        Ok(())
+    }
+
+    /// **💾 This method may alter the persisted account state.**
+    pub fn clear_access_token_cache(&mut self) {
+        self.state.clear_access_token_cache();
+    }
+}
+
+const AUTH_CIRCUIT_BREAKER_CAPACITY: u8 = 5;
+const AUTH_CIRCUIT_BREAKER_RENEWAL_RATE: f32 = 3.0 / 60.0 / 1000.0; // 3 tokens every minute.
+
+#[derive(Clone, Copy)]
+pub(crate) struct AuthCircuitBreaker {
+    rate_limiter: RateLimiter,
+}
+
+impl Default for AuthCircuitBreaker {
+    fn default() -> Self {
+        AuthCircuitBreaker {
+            rate_limiter: RateLimiter::new(
+                AUTH_CIRCUIT_BREAKER_CAPACITY,
+                AUTH_CIRCUIT_BREAKER_RENEWAL_RATE,
+            ),
+        }
+    }
+}
+
+impl AuthCircuitBreaker {
+    pub(crate) fn check(&mut self) -> Result<()> {
+        if !self.rate_limiter.check() {
+            return Err(Error::AuthCircuitBreakerError);
+        }
+        Ok(())
+    }
+}
+
+impl TryFrom<Url> for AuthorizationParameters {
+    type Error = Error;
+
+    fn try_from(url: Url) -> Result<Self> {
+        let query_map: HashMap<String, String> = url.query_pairs().into_owned().collect();
+        let scope = query_map
+            .get("scope")
+            .cloned()
+            .ok_or(Error::MissingUrlParameter("scope"))?;
+        let client_id = query_map
+            .get("client_id")
+            .cloned()
+            .ok_or(Error::MissingUrlParameter("client_id"))?;
+        let state = query_map
+            .get("state")
+            .cloned()
+            .ok_or(Error::MissingUrlParameter("state"))?;
+        let access_type = query_map
+            .get("access_type")
+            .cloned()
+            .ok_or(Error::MissingUrlParameter("access_type"))?;
+        let code_challenge = query_map.get("code_challenge").cloned();
+        let code_challenge_method = query_map.get("code_challenge_method").cloned();
+        let keys_jwk = query_map.get("keys_jwk").cloned();
+        Ok(Self {
+            client_id,
+            scope: scope.split_whitespace().map(|s| s.to_string()).collect(),
+            state,
+            access_type,
+            code_challenge,
+            code_challenge_method,
+            keys_jwk,
+        })
+    }
+}
+
+impl MetricsParams {
+    fn append_params_to_url(&self, url: &mut Url) {
+        self.parameters
+            .iter()
+            .for_each(|(parameter_name, parameter_value)| {
+                url.query_pairs_mut()
+                    .append_pair(parameter_name, parameter_value);
+            });
+    }
+}
+
+#[derive(Clone, Serialize, Deserialize)]
+pub struct RefreshToken {
+    pub token: String,
+    pub scopes: HashSet<String>,
+}
+
+impl std::fmt::Debug for RefreshToken {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.debug_struct("RefreshToken")
+            .field("scopes", &self.scopes)
+            .finish()
+    }
+}
+
+pub struct OAuthFlow {
+    pub scoped_keys_flow: Option<ScopedKeysFlow>,
+    pub code_verifier: String,
+}
+
+#[derive(Clone, Serialize, Deserialize)]
+pub struct AccessTokenInfo {
+    pub scope: String,
+    pub token: String,
+    pub key: Option<ScopedKey>,
+    pub expires_at: u64, // seconds since epoch
+}
+
+impl AccessTokenInfo {
+    pub fn check_missing_sync_scoped_key(&self) -> Result<()> {
+        if self.scope == scopes::OLD_SYNC && self.key.is_none() {
+            Err(Error::SyncScopedKeyMissingInServerResponse)
+        } else {
+            Ok(())
+        }
+    }
+}
+
+impl TryFrom<AccessTokenInfo> for crate::AccessTokenInfo {
+    type Error = Error;
+    fn try_from(info: AccessTokenInfo) -> Result<Self> {
+        Ok(crate::AccessTokenInfo {
+            scope: info.scope,
+            token: info.token,
+            key: info.key.map(ScopedKey::into),
+            expires_at: info.expires_at.try_into()?,
+        })
+    }
+}
+
+impl std::fmt::Debug for AccessTokenInfo {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.debug_struct("AccessTokenInfo")
+            .field("scope", &self.scope)
+            .field("key", &self.key)
+            .field("expires_at", &self.expires_at)
+            .finish()
+    }
+}
+
+impl From<IntrospectInfo> for crate::AuthorizationInfo {
+    fn from(r: IntrospectInfo) -> Self {
+        crate::AuthorizationInfo { active: r.active }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::super::{http_client::*, Config};
+    use super::*;
+    use mockall::predicate::always;
+    use mockall::predicate::eq;
+    use std::borrow::Cow;
+    use std::collections::HashMap;
+    use std::sync::Arc;
+
+    impl FirefoxAccount {
+        pub fn add_cached_token(&mut self, scope: &str, token_info: AccessTokenInfo) {
+            self.state.add_cached_access_token(scope, token_info);
+        }
+
+        pub fn set_session_token(&mut self, session_token: &str) {
+            self.state.force_session_token(session_token.to_owned());
+        }
+    }
+
+    #[test]
+    fn test_oauth_flow_url() {
+        // FIXME: this test shouldn't make network requests.
+        viaduct_reqwest::use_reqwest_backend();
+        let config = Config::new(
+            "https://accounts.firefox.com",
+            "12345678",
+            "https://foo.bar",
+        );
+        let mut params = HashMap::new();
+        params.insert("flow_id".to_string(), "87654321".to_string());
+        let metrics_params = MetricsParams { parameters: params };
+        let mut fxa = FirefoxAccount::with_config(config);
+        let url = fxa
+            .begin_oauth_flow(&["profile"], "test_oauth_flow_url", Some(metrics_params))
+            .unwrap();
+        let flow_url = Url::parse(&url).unwrap();
+
+        assert_eq!(flow_url.host_str(), Some("accounts.firefox.com"));
+        assert_eq!(flow_url.path(), "/authorization");
+
+        let mut pairs = flow_url.query_pairs();
+        assert_eq!(pairs.count(), 12);
+        assert_eq!(
+            pairs.next(),
+            Some((Cow::Borrowed("action"), Cow::Borrowed("email")))
+        );
+        assert_eq!(
+            pairs.next(),
+            Some((Cow::Borrowed("response_type"), Cow::Borrowed("code")))
+        );
+        assert_eq!(
+            pairs.next(),
+            Some((
+                Cow::Borrowed("entrypoint"),
+                Cow::Borrowed("test_oauth_flow_url")
+            ))
+        );
+        assert_eq!(
+            pairs.next(),
+            Some((Cow::Borrowed("flow_id"), Cow::Borrowed("87654321")))
+        );
+        assert_eq!(
+            pairs.next(),
+            Some((Cow::Borrowed("client_id"), Cow::Borrowed("12345678")))
+        );
+
+        assert_eq!(
+            pairs.next(),
+            Some((Cow::Borrowed("scope"), Cow::Borrowed("profile")))
+        );
+        let state_param = pairs.next().unwrap();
+        assert_eq!(state_param.0, Cow::Borrowed("state"));
+        assert_eq!(state_param.1.len(), 22);
+        assert_eq!(
+            pairs.next(),
+            Some((
+                Cow::Borrowed("code_challenge_method"),
+                Cow::Borrowed("S256")
+            ))
+        );
+        let code_challenge_param = pairs.next().unwrap();
+        assert_eq!(code_challenge_param.0, Cow::Borrowed("code_challenge"));
+        assert_eq!(code_challenge_param.1.len(), 43);
+        assert_eq!(
+            pairs.next(),
+            Some((Cow::Borrowed("access_type"), Cow::Borrowed("offline")))
+        );
+        let keys_jwk = pairs.next().unwrap();
+        assert_eq!(keys_jwk.0, Cow::Borrowed("keys_jwk"));
+        assert_eq!(keys_jwk.1.len(), 168);
+
+        assert_eq!(
+            pairs.next(),
+            Some((
+                Cow::Borrowed("redirect_uri"),
+                Cow::Borrowed("https://foo.bar")
+            ))
+        );
+    }
+
+    #[test]
+    fn test_force_auth_url() {
+        let config = Config::stable_dev("12345678", "https://foo.bar");
+        let mut fxa = FirefoxAccount::with_config(config);
+        let email = "test@example.com";
+        fxa.add_cached_profile("123", email);
+        let url = fxa
+            .begin_oauth_flow(&["profile"], "test_force_auth_url", None)
+            .unwrap();
+        let url = Url::parse(&url).unwrap();
+        assert_eq!(url.path(), "/oauth/force_auth");
+        let mut pairs = url.query_pairs();
+        assert_eq!(
+            pairs.find(|e| e.0 == "email"),
+            Some((Cow::Borrowed("email"), Cow::Borrowed(email),))
+        );
+    }
+
+    #[test]
+    fn test_webchannel_context_url() {
+        // FIXME: this test shouldn't make network requests.
+        viaduct_reqwest::use_reqwest_backend();
+        const SCOPES: &[&str] = &["https://identity.mozilla.com/apps/oldsync"];
+        let config = Config::new(
+            "https://accounts.firefox.com",
+            "12345678",
+            "urn:ietf:wg:oauth:2.0:oob:oauth-redirect-webchannel",
+        );
+        let mut fxa = FirefoxAccount::with_config(config);
+        let url = fxa
+            .begin_oauth_flow(SCOPES, "test_webchannel_context_url", None)
+            .unwrap();
+        let url = Url::parse(&url).unwrap();
+        let query_params: HashMap<_, _> = url.query_pairs().into_owned().collect();
+        let context = &query_params["context"];
+        assert_eq!(context, "oauth_webchannel_v1");
+        assert_eq!(query_params.get("redirect_uri"), None);
+    }
+
+    #[test]
+    fn test_webchannel_pairing_context_url() {
+        const SCOPES: &[&str] = &["https://identity.mozilla.com/apps/oldsync"];
+        const PAIRING_URL: &str = "https://accounts.firefox.com/pair#channel_id=658db7fe98b249a5897b884f98fb31b7&channel_key=1hIDzTj5oY2HDeSg_jA2DhcOcAn5Uqq0cAYlZRNUIo4";
+
+        let config = Config::new(
+            "https://accounts.firefox.com",
+            "12345678",
+            "urn:ietf:wg:oauth:2.0:oob:oauth-redirect-webchannel",
+        );
+        let mut fxa = FirefoxAccount::with_config(config);
+        let url = fxa
+            .begin_pairing_flow(
+                PAIRING_URL,
+                SCOPES,
+                "test_webchannel_pairing_context_url",
+                None,
+            )
+            .unwrap();
+        let url = Url::parse(&url).unwrap();
+        let query_params: HashMap<_, _> = url.query_pairs().into_owned().collect();
+        let context = &query_params["context"];
+        assert_eq!(context, "oauth_webchannel_v1");
+        assert_eq!(query_params.get("redirect_uri"), None);
+    }
+
+    #[test]
+    fn test_pairing_flow_url() {
+        const SCOPES: &[&str] = &["https://identity.mozilla.com/apps/oldsync"];
+        const PAIRING_URL: &str = "https://accounts.firefox.com/pair#channel_id=658db7fe98b249a5897b884f98fb31b7&channel_key=1hIDzTj5oY2HDeSg_jA2DhcOcAn5Uqq0cAYlZRNUIo4";
+        const EXPECTED_URL: &str = "https://accounts.firefox.com/pair/supp?client_id=12345678&redirect_uri=https%3A%2F%2Ffoo.bar&scope=https%3A%2F%2Fidentity.mozilla.com%2Fapps%2Foldsync&state=SmbAA_9EA5v1R2bgIPeWWw&code_challenge_method=S256&code_challenge=ZgHLPPJ8XYbXpo7VIb7wFw0yXlTa6MUOVfGiADt0JSM&access_type=offline&keys_jwk=eyJjcnYiOiJQLTI1NiIsImt0eSI6IkVDIiwieCI6Ing5LUltQjJveDM0LTV6c1VmbW5sNEp0Ti14elV2eFZlZXJHTFRXRV9BT0kiLCJ5IjoiNXBKbTB3WGQ4YXdHcm0zREl4T1pWMl9qdl9tZEx1TWlMb1RkZ1RucWJDZyJ9#channel_id=658db7fe98b249a5897b884f98fb31b7&channel_key=1hIDzTj5oY2HDeSg_jA2DhcOcAn5Uqq0cAYlZRNUIo4";
+
+        let config = Config::new(
+            "https://accounts.firefox.com",
+            "12345678",
+            "https://foo.bar",
+        );
+        let mut params = HashMap::new();
+        params.insert("flow_id".to_string(), "87654321".to_string());
+        let metrics_params = MetricsParams { parameters: params };
+
+        let mut fxa = FirefoxAccount::with_config(config);
+        let url = fxa
+            .begin_pairing_flow(
+                PAIRING_URL,
+                SCOPES,
+                "test_pairing_flow_url",
+                Some(metrics_params),
+            )
+            .unwrap();
+        let flow_url = Url::parse(&url).unwrap();
+        let expected_parsed_url = Url::parse(EXPECTED_URL).unwrap();
+
+        assert_eq!(flow_url.host_str(), Some("accounts.firefox.com"));
+        assert_eq!(flow_url.path(), "/pair/supp");
+        assert_eq!(flow_url.fragment(), expected_parsed_url.fragment());
+
+        let mut pairs = flow_url.query_pairs();
+        assert_eq!(pairs.count(), 10);
+        assert_eq!(
+            pairs.next(),
+            Some((
+                Cow::Borrowed("entrypoint"),
+                Cow::Borrowed("test_pairing_flow_url")
+            ))
+        );
+        assert_eq!(
+            pairs.next(),
+            Some((Cow::Borrowed("flow_id"), Cow::Borrowed("87654321")))
+        );
+        assert_eq!(
+            pairs.next(),
+            Some((Cow::Borrowed("client_id"), Cow::Borrowed("12345678")))
+        );
+        assert_eq!(
+            pairs.next(),
+            Some((
+                Cow::Borrowed("scope"),
+                Cow::Borrowed("https://identity.mozilla.com/apps/oldsync")
+            ))
+        );
+
+        let state_param = pairs.next().unwrap();
+        assert_eq!(state_param.0, Cow::Borrowed("state"));
+        assert_eq!(state_param.1.len(), 22);
+        assert_eq!(
+            pairs.next(),
+            Some((
+                Cow::Borrowed("code_challenge_method"),
+                Cow::Borrowed("S256")
+            ))
+        );
+        let code_challenge_param = pairs.next().unwrap();
+        assert_eq!(code_challenge_param.0, Cow::Borrowed("code_challenge"));
+        assert_eq!(code_challenge_param.1.len(), 43);
+        assert_eq!(
+            pairs.next(),
+            Some((Cow::Borrowed("access_type"), Cow::Borrowed("offline")))
+        );
+        let keys_jwk = pairs.next().unwrap();
+        assert_eq!(keys_jwk.0, Cow::Borrowed("keys_jwk"));
+        assert_eq!(keys_jwk.1.len(), 168);
+
+        assert_eq!(
+            pairs.next(),
+            Some((
+                Cow::Borrowed("redirect_uri"),
+                Cow::Borrowed("https://foo.bar")
+            ))
+        );
+    }
+
+    #[test]
+    fn test_pairing_flow_origin_mismatch() {
+        static PAIRING_URL: &str = "https://bad.origin.com/pair#channel_id=foo&channel_key=bar";
+        let config = Config::stable_dev("12345678", "https://foo.bar");
+        let mut fxa = FirefoxAccount::with_config(config);
+        let url = fxa.begin_pairing_flow(
+            PAIRING_URL,
+            &["https://identity.mozilla.com/apps/oldsync"],
+            "test_pairiong_flow_origin_mismatch",
+            None,
+        );
+
+        assert!(url.is_err());
+
+        match url {
+            Ok(_) => {
+                panic!("should have error");
+            }
+            Err(err) => match err {
+                Error::OriginMismatch { .. } => {}
+                _ => panic!("error not OriginMismatch"),
+            },
+        }
+    }
+
+    #[test]
+    fn test_check_authorization_status() {
+        let config = Config::stable_dev("12345678", "https://foo.bar");
+        let mut fxa = FirefoxAccount::with_config(config);
+
+        let refresh_token_scopes = std::collections::HashSet::new();
+        fxa.state.force_refresh_token(RefreshToken {
+            token: "refresh_token".to_owned(),
+            scopes: refresh_token_scopes,
+        });
+
+        let mut client = MockFxAClient::new();
+        client
+            .expect_check_refresh_token_status()
+            .with(always(), eq("refresh_token"))
+            .times(1)
+            .returning(|_, _| Ok(IntrospectResponse { active: true }));
+        fxa.set_client(Arc::new(client));
+
+        let auth_status = fxa.check_authorization_status().unwrap();
+        assert!(auth_status.active);
+    }
+
+    #[test]
+    fn test_check_authorization_status_circuit_breaker() {
+        let config = Config::stable_dev("12345678", "https://foo.bar");
+        let mut fxa = FirefoxAccount::with_config(config);
+
+        let refresh_token_scopes = std::collections::HashSet::new();
+        fxa.state.force_refresh_token(RefreshToken {
+            token: "refresh_token".to_owned(),
+            scopes: refresh_token_scopes,
+        });
+
+        let mut client = MockFxAClient::new();
+        // This copy-pasta (equivalent to `.returns(..).times(5)`) is there
+        // because `Error` is not cloneable :/
+        client
+            .expect_check_refresh_token_status()
+            .with(always(), eq("refresh_token"))
+            .returning(|_, _| Ok(IntrospectResponse { active: true }));
+        client
+            .expect_check_refresh_token_status()
+            .with(always(), eq("refresh_token"))
+            .returning(|_, _| Ok(IntrospectResponse { active: true }));
+        client
+            .expect_check_refresh_token_status()
+            .with(always(), eq("refresh_token"))
+            .returning(|_, _| Ok(IntrospectResponse { active: true }));
+        client
+            .expect_check_refresh_token_status()
+            .with(always(), eq("refresh_token"))
+            .returning(|_, _| Ok(IntrospectResponse { active: true }));
+        client
+            .expect_check_refresh_token_status()
+            .with(always(), eq("refresh_token"))
+            .returning(|_, _| Ok(IntrospectResponse { active: true }));
+        //mockall expects calls to be processed in the order they are registered. So, no need for to use a method like expect_check_refresh_token_status_calls_in_order()
+        fxa.set_client(Arc::new(client));
+
+        for _ in 0..5 {
+            assert!(fxa.check_authorization_status().is_ok());
+        }
+        match fxa.check_authorization_status() {
+            Ok(_) => unreachable!("should not happen"),
+            Err(err) => assert!(matches!(err, Error::AuthCircuitBreakerError)),
+        }
+    }
+
+    use crate::internal::scopes;
+
+    #[test]
+    fn test_auth_code_pair_valid_not_allowed_scope() {
+        let config = Config::stable_dev("12345678", "https://foo.bar");
+        let mut fxa = FirefoxAccount::with_config(config);
+        fxa.set_session_token("session");
+        let mut client = MockFxAClient::new();
+        let not_allowed_scope = "https://identity.mozilla.com/apps/lockbox";
+        let expected_scopes = scopes::OLD_SYNC
+            .chars()
+            .chain(std::iter::once(' '))
+            .chain(not_allowed_scope.chars())
+            .collect::<String>();
+        client
+            .expect_get_scoped_key_data()
+            .with(always(), eq("session"), eq("12345678"), eq(expected_scopes))
+            .times(1)
+            .returning(|_, _, _, _| {
+                Err(Error::RemoteError {
+                    code: 400,
+                    errno: 163,
+                    error: "Invalid Scopes".to_string(),
+                    message: "Not allowed to request scopes".to_string(),
+                    info: "fyi, there was a server error".to_string(),
+                })
+            });
+        fxa.set_client(Arc::new(client));
+        let auth_params = AuthorizationParameters {
+            client_id: "12345678".to_string(),
+            scope: vec![scopes::OLD_SYNC.to_string(), not_allowed_scope.to_string()],
+            state: "somestate".to_string(),
+            access_type: "offline".to_string(),
+            code_challenge: None,
+            code_challenge_method: None,
+            keys_jwk: None,
+        };
+        let res = fxa.authorize_code_using_session_token(auth_params);
+        assert!(res.is_err());
+        let err = res.unwrap_err();
+        if let Error::RemoteError {
+            code,
+            errno,
+            error: _,
+            message: _,
+            info: _,
+        } = err
+        {
+            assert_eq!(code, 400);
+            assert_eq!(errno, 163); // Requested scopes not allowed
+        } else {
+            panic!("Should return an error from the server specifying that the requested scopes are not allowed");
+        }
+    }
+
+    #[test]
+    fn test_auth_code_pair_invalid_scope_not_allowed() {
+        let config = Config::stable_dev("12345678", "https://foo.bar");
+        let mut fxa = FirefoxAccount::with_config(config);
+        fxa.set_session_token("session");
+        let mut client = MockFxAClient::new();
+        let invalid_scope = "IamAnInvalidScope";
+        let expected_scopes = scopes::OLD_SYNC
+            .chars()
+            .chain(std::iter::once(' '))
+            .chain(invalid_scope.chars())
+            .collect::<String>();
+        client
+            .expect_get_scoped_key_data()
+            .with(always(), eq("session"), eq("12345678"), eq(expected_scopes))
+            .times(1)
+            .returning(|_, _, _, _| {
+                let mut server_ret = HashMap::new();
+                server_ret.insert(
+                    scopes::OLD_SYNC.to_string(),
+                    ScopedKeyDataResponse {
+                        key_rotation_secret: "IamASecret".to_string(),
+                        key_rotation_timestamp: 100,
+                        identifier: "".to_string(),
+                    },
+                );
+                Ok(server_ret)
+            });
+        fxa.set_client(Arc::new(client));
+
+        let auth_params = AuthorizationParameters {
+            client_id: "12345678".to_string(),
+            scope: vec![scopes::OLD_SYNC.to_string(), invalid_scope.to_string()],
+            state: "somestate".to_string(),
+            access_type: "offline".to_string(),
+            code_challenge: None,
+            code_challenge_method: None,
+            keys_jwk: None,
+        };
+        let res = fxa.authorize_code_using_session_token(auth_params);
+        assert!(res.is_err());
+        let err = res.unwrap_err();
+        if let Error::ScopeNotAllowed(client_id, scope) = err {
+            assert_eq!(client_id, "12345678");
+            assert_eq!(scope, "IamAnInvalidScope");
+        } else {
+            panic!("Should return an error that specifies the scope that is not allowed");
+        }
+    }
+
+    #[test]
+    fn test_auth_code_pair_scope_not_in_state() {
+        let config = Config::stable_dev("12345678", "https://foo.bar");
+        let mut fxa = FirefoxAccount::with_config(config);
+        fxa.set_session_token("session");
+        let mut client = MockFxAClient::new();
+        client
+            .expect_get_scoped_key_data()
+            .with(
+                always(),
+                eq("session"),
+                eq("12345678"),
+                eq(scopes::OLD_SYNC),
+            )
+            .times(1)
+            .returning(|_, _, _, _| {
+                let mut server_ret = HashMap::new();
+                server_ret.insert(
+                    scopes::OLD_SYNC.to_string(),
+                    ScopedKeyDataResponse {
+                        key_rotation_secret: "IamASecret".to_string(),
+                        key_rotation_timestamp: 100,
+                        identifier: "".to_string(),
+                    },
+                );
+                Ok(server_ret)
+            });
+        fxa.set_client(Arc::new(client));
+        let auth_params = AuthorizationParameters {
+            client_id: "12345678".to_string(),
+            scope: vec![scopes::OLD_SYNC.to_string()],
+            state: "somestate".to_string(),
+            access_type: "offline".to_string(),
+            code_challenge: None,
+            code_challenge_method: None,
+            keys_jwk: Some("IAmAVerySecretKeysJWkInBase64".to_string()),
+        };
+        let res = fxa.authorize_code_using_session_token(auth_params);
+        assert!(res.is_err());
+        let err = res.unwrap_err();
+        if let Error::NoScopedKey(scope) = err {
+            assert_eq!(scope, scopes::OLD_SYNC.to_string());
+        } else {
+            panic!("Should return an error that specifies the scope that is not in the state");
+        }
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/fxa_client/internal/oauth/attached_clients.rs.html b/book/rust-docs/src/fxa_client/internal/oauth/attached_clients.rs.html new file mode 100644 index 0000000000..cf15d60cc0 --- /dev/null +++ b/book/rust-docs/src/fxa_client/internal/oauth/attached_clients.rs.html @@ -0,0 +1,271 @@ +attached_clients.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+pub use super::super::http_client::GetAttachedClientResponse as AttachedClient;
+use super::super::{util, CachedResponse, FirefoxAccount};
+use crate::{Error, Result};
+
+// An attached clients response is considered fresh for `ATTACHED_CLIENTS_FRESHNESS_THRESHOLD` ms.
+const ATTACHED_CLIENTS_FRESHNESS_THRESHOLD: u64 = 60_000; // 1 minute
+
+impl FirefoxAccount {
+    /// Fetches the list of attached clients connected to the current account.
+    pub fn get_attached_clients(&mut self) -> Result<Vec<AttachedClient>> {
+        if let Some(a) = &self.attached_clients_cache {
+            if util::now() < a.cached_at + ATTACHED_CLIENTS_FRESHNESS_THRESHOLD {
+                return Ok(a.response.clone());
+            }
+        }
+        let session_token = self.get_session_token()?;
+        let response = self
+            .client
+            .get_attached_clients(self.state.config(), &session_token)?;
+
+        self.attached_clients_cache = Some(CachedResponse {
+            response: response.clone(),
+            cached_at: util::now(),
+            etag: "".into(),
+        });
+
+        Ok(response)
+    }
+}
+
+impl TryFrom<AttachedClient> for crate::AttachedClient {
+    type Error = Error;
+    fn try_from(c: AttachedClient) -> Result<Self> {
+        Ok(crate::AttachedClient {
+            client_id: c.client_id,
+            device_id: c.device_id,
+            device_type: c.device_type,
+            is_current_session: c.is_current_session,
+            name: c.name,
+            created_time: c.created_time.map(TryInto::try_into).transpose()?,
+            last_access_time: c.last_access_time.map(TryInto::try_into).transpose()?,
+            scope: c.scope,
+        })
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::internal::{config::Config, http_client::MockFxAClient};
+    use mockall::predicate::always;
+    use mockall::predicate::eq;
+    use std::sync::Arc;
+    use sync15::DeviceType;
+
+    #[test]
+    fn test_get_attached_clients() {
+        let config = Config::stable_dev("12345678", "https://foo.bar");
+        let mut fxa = FirefoxAccount::with_config(config);
+        fxa.set_session_token("session");
+
+        let mut client = MockFxAClient::new();
+        client
+            .expect_get_attached_clients()
+            .with(always(), eq("session"))
+            .times(1)
+            .returning(|_, _| {
+                Ok(vec![AttachedClient {
+                    client_id: Some("12345678".into()),
+                    session_token_id: None,
+                    refresh_token_id: None,
+                    device_id: None,
+                    device_type: DeviceType::Desktop,
+                    is_current_session: true,
+                    name: None,
+                    created_time: None,
+                    last_access_time: None,
+                    scope: None,
+                    user_agent: "attachedClientsUserAgent".into(),
+                    os: None,
+                }])
+            });
+
+        fxa.set_client(Arc::new(client));
+        assert!(fxa.attached_clients_cache.is_none());
+
+        let res = fxa.get_attached_clients();
+
+        assert!(res.is_ok());
+        assert!(fxa.attached_clients_cache.is_some());
+
+        let cached_attached_clients_res = fxa.attached_clients_cache.unwrap();
+        assert!(!cached_attached_clients_res.response.is_empty());
+        assert!(cached_attached_clients_res.cached_at > 0);
+
+        let cached_attached_clients = &cached_attached_clients_res.response[0];
+        assert_eq!(
+            cached_attached_clients.clone().client_id.unwrap(),
+            "12345678".to_string()
+        );
+    }
+
+    #[test]
+    fn test_get_attached_clients_network_errors() {
+        let config = Config::stable_dev("12345678", "https://foo.bar");
+        let mut fxa = FirefoxAccount::with_config(config);
+        fxa.set_session_token("session");
+
+        let mut client = MockFxAClient::new();
+        client
+            .expect_get_attached_clients()
+            .with(always(), eq("session"))
+            .times(1)
+            .returning(|_, _| {
+                Err(Error::RemoteError {
+                    code: 500,
+                    errno: 101,
+                    error: "Did not work!".to_owned(),
+                    message: "Did not work!".to_owned(),
+                    info: "Did not work!".to_owned(),
+                })
+            });
+
+        fxa.set_client(Arc::new(client));
+        assert!(fxa.attached_clients_cache.is_none());
+
+        let res = fxa.get_attached_clients();
+        assert!(res.is_err());
+        assert!(fxa.attached_clients_cache.is_none());
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/fxa_client/internal/profile.rs.html b/book/rust-docs/src/fxa_client/internal/profile.rs.html new file mode 100644 index 0000000000..7fff409a90 --- /dev/null +++ b/book/rust-docs/src/fxa_client/internal/profile.rs.html @@ -0,0 +1,445 @@ +profile.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+pub use super::http_client::ProfileResponse as Profile;
+use super::{scopes, util, CachedResponse, FirefoxAccount};
+use crate::{Error, Result};
+
+// A cached profile response is considered fresh for `PROFILE_FRESHNESS_THRESHOLD` ms.
+const PROFILE_FRESHNESS_THRESHOLD: u64 = 120_000; // 2 minutes
+
+impl FirefoxAccount {
+    /// Fetch the profile for the user.
+    /// This method will error-out if the `profile` scope is not
+    /// authorized for the current refresh token or or if we do
+    /// not have a valid refresh token.
+    ///
+    /// * `ignore_cache` - If set to true, bypass the in-memory cache
+    /// and fetch the entire profile data from the server.
+    ///
+    /// **💾 This method alters the persisted account state.**
+    pub fn get_profile(&mut self, ignore_cache: bool) -> Result<Profile> {
+        match self.get_profile_helper(ignore_cache) {
+            Ok(res) => Ok(res),
+            Err(e) => match e {
+                Error::RemoteError { code: 401, .. } => {
+                    log::warn!(
+                        "Access token rejected, clearing the tokens cache and trying again."
+                    );
+                    self.clear_access_token_cache();
+                    self.clear_devices_and_attached_clients_cache();
+                    self.get_profile_helper(ignore_cache)
+                }
+                _ => Err(e),
+            },
+        }
+    }
+
+    fn get_profile_helper(&mut self, ignore_cache: bool) -> Result<Profile> {
+        let mut etag = None;
+        if let Some(cached_profile) = self.state.last_seen_profile() {
+            if !ignore_cache && util::now() < cached_profile.cached_at + PROFILE_FRESHNESS_THRESHOLD
+            {
+                return Ok(cached_profile.response.clone());
+            }
+            etag = Some(cached_profile.etag.clone());
+        }
+        let profile_access_token = self.get_access_token(scopes::PROFILE, None)?.token;
+        match self
+            .client
+            .get_profile(self.state.config(), &profile_access_token, etag)?
+        {
+            Some(response_and_etag) => {
+                if let Some(etag) = response_and_etag.etag {
+                    self.state.set_last_seen_profile(CachedResponse {
+                        response: response_and_etag.response.clone(),
+                        cached_at: util::now(),
+                        etag,
+                    });
+                }
+                Ok(response_and_etag.response)
+            }
+            None => {
+                match self.state.last_seen_profile() {
+                    Some(cached_profile) => {
+                        let response = cached_profile.response.clone();
+                        // Update `cached_at` timestamp.
+                        let new_cached_profile = CachedResponse {
+                            response: cached_profile.response.clone(),
+                            cached_at: util::now(),
+                            etag: cached_profile.etag.clone(),
+                        };
+                        self.state.set_last_seen_profile(new_cached_profile);
+                        Ok(response)
+                    }
+                    None => Err(Error::ApiClientError(
+                        "Got a 304 without having sent an eTag.",
+                    )),
+                }
+            }
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::internal::{
+        http_client::*,
+        oauth::{AccessTokenInfo, RefreshToken},
+        Config,
+    };
+    use mockall::predicate::always;
+    use mockall::predicate::eq;
+    use std::sync::Arc;
+
+    impl FirefoxAccount {
+        pub fn add_cached_profile(&mut self, uid: &str, email: &str) {
+            self.state.set_last_seen_profile(CachedResponse {
+                response: Profile {
+                    uid: uid.into(),
+                    email: email.into(),
+                    display_name: None,
+                    avatar: "".into(),
+                    avatar_default: true,
+                },
+                cached_at: util::now(),
+                etag: "fake etag".into(),
+            });
+        }
+    }
+
+    #[test]
+    fn test_fetch_profile() {
+        let config = Config::stable_dev("12345678", "https://foo.bar");
+        let mut fxa = FirefoxAccount::with_config(config);
+
+        fxa.add_cached_token(
+            "profile",
+            AccessTokenInfo {
+                scope: "profile".to_string(),
+                token: "profiletok".to_string(),
+                key: None,
+                expires_at: u64::max_value(),
+            },
+        );
+
+        let mut client = MockFxAClient::new();
+        client
+            .expect_get_profile()
+            .with(always(), eq("profiletok"), always())
+            .times(1)
+            .returning(|_, _, _| {
+                Ok(Some(ResponseAndETag {
+                    response: ProfileResponse {
+                        uid: "12345ab".to_string(),
+                        email: "foo@bar.com".to_string(),
+                        display_name: None,
+                        avatar: "https://foo.avatar".to_string(),
+                        avatar_default: true,
+                    },
+                    etag: None,
+                }))
+            });
+        fxa.set_client(Arc::new(client));
+
+        let p = fxa.get_profile(false).unwrap();
+        assert_eq!(p.email, "foo@bar.com");
+    }
+
+    #[test]
+    fn test_expired_access_token_refetch() {
+        let config = Config::stable_dev("12345678", "https://foo.bar");
+        let mut fxa = FirefoxAccount::with_config(config);
+
+        fxa.add_cached_token(
+            "profile",
+            AccessTokenInfo {
+                scope: "profile".to_string(),
+                token: "bad_access_token".to_string(),
+                key: None,
+                expires_at: u64::max_value(),
+            },
+        );
+        let mut refresh_token_scopes = std::collections::HashSet::new();
+        refresh_token_scopes.insert("profile".to_owned());
+        fxa.state.force_refresh_token(RefreshToken {
+            token: "refreshtok".to_owned(),
+            scopes: refresh_token_scopes,
+        });
+
+        let mut client = MockFxAClient::new();
+        // First call to profile() we fail with 401.
+        client
+            .expect_get_profile()
+            .with(always(), eq("bad_access_token"), always())
+            .times(1)
+            .returning(|_, _, _| Err(Error::RemoteError{
+                code: 401,
+                errno: 110,
+                error: "Unauthorized".to_owned(),
+                message: "Invalid authentication token in request signature".to_owned(),
+                info: "https://github.com/mozilla/fxa-auth-server/blob/master/docs/api.md#response-format".to_owned(),
+            }));
+        // Then we'll try to get a new access token.
+        client
+            .expect_create_access_token_using_refresh_token()
+            .with(always(), eq("refreshtok"), always(), always())
+            .times(1)
+            .returning(|_, _, _, _| {
+                Ok(OAuthTokenResponse {
+                    keys_jwe: None,
+                    refresh_token: None,
+                    expires_in: 6_000_000,
+                    scope: "profile".to_owned(),
+                    access_token: "good_profile_token".to_owned(),
+                    session_token: None,
+                })
+            });
+        // Then hooray it works!
+        client
+            .expect_get_profile()
+            .with(always(), eq("good_profile_token"), always())
+            .times(1)
+            .returning(|_, _, _| {
+                Ok(Some(ResponseAndETag {
+                    response: ProfileResponse {
+                        uid: "12345ab".to_string(),
+                        email: "foo@bar.com".to_string(),
+                        display_name: None,
+                        avatar: "https://foo.avatar".to_string(),
+                        avatar_default: true,
+                    },
+                    etag: None,
+                }))
+            });
+        fxa.set_client(Arc::new(client));
+
+        let p = fxa.get_profile(false).unwrap();
+        assert_eq!(p.email, "foo@bar.com");
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/fxa_client/internal/push.rs.html b/book/rust-docs/src/fxa_client/internal/push.rs.html new file mode 100644 index 0000000000..6139a5f9e9 --- /dev/null +++ b/book/rust-docs/src/fxa_client/internal/push.rs.html @@ -0,0 +1,595 @@ +push.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use std::convert::TryInto;
+
+use super::FirefoxAccount;
+use crate::{AccountEvent, Error, Result};
+use serde_derive::Deserialize;
+
+impl FirefoxAccount {
+    /// Handles a push message and returns a single [`AccountEvent`]
+    ///
+    /// This API is useful for when the app would like to get the AccountEvent associated
+    /// with the push message, but would **not** like to retrieve missed commands while doing so.
+    ///
+    /// **💾 This method alters the persisted account state.**
+    ///
+    /// **⚠️ This API does not increment the command index if a command was received**
+    pub fn handle_push_message(&mut self, payload: &str) -> Result<AccountEvent> {
+        let payload = serde_json::from_str(payload).or_else(|err| {
+            let v: serde_json::Value = serde_json::from_str(payload)?;
+            match v.get("command") {
+                Some(_) => Ok(PushPayload::Unknown),
+                None => Err(err),
+            }
+        })?;
+        match payload {
+            PushPayload::CommandReceived(CommandReceivedPushPayload { index, .. }) => {
+                let cmd = self.get_command_for_index(index)?;
+                Ok(AccountEvent::CommandReceived {
+                    command: cmd.try_into()?,
+                })
+            }
+            PushPayload::ProfileUpdated => {
+                self.state.clear_last_seen_profile();
+                Ok(AccountEvent::ProfileUpdated)
+            }
+            PushPayload::DeviceConnected(DeviceConnectedPushPayload { device_name }) => {
+                self.clear_devices_and_attached_clients_cache();
+                Ok(AccountEvent::DeviceConnected { device_name })
+            }
+            PushPayload::DeviceDisconnected(DeviceDisconnectedPushPayload { device_id }) => {
+                let local_device = self.get_current_device_id();
+                let is_local_device = match local_device {
+                    Err(_) => false,
+                    Ok(id) => id == device_id,
+                };
+                if is_local_device {
+                    // Note: self.disconnect calls self.start_over which clears the state for the FirefoxAccount instance
+                    self.disconnect();
+                }
+                Ok(AccountEvent::DeviceDisconnected {
+                    device_id,
+                    is_local_device,
+                })
+            }
+            PushPayload::AccountDestroyed(AccountDestroyedPushPayload { account_uid }) => {
+                let is_local_account = match self.state.last_seen_profile() {
+                    None => false,
+                    Some(profile) => profile.response.uid == account_uid,
+                };
+                Ok(if is_local_account {
+                    AccountEvent::AccountDestroyed
+                } else {
+                    return Err(Error::InvalidPushEvent);
+                })
+            }
+            PushPayload::PasswordChanged | PushPayload::PasswordReset => {
+                let status = self.check_authorization_status()?;
+                // clear any device or client data due to password change.
+                self.clear_devices_and_attached_clients_cache();
+                Ok(if !status.active {
+                    AccountEvent::AccountAuthStateChanged
+                } else {
+                    log::info!("Password change event, but no action required");
+                    AccountEvent::Unknown
+                })
+            }
+            PushPayload::Unknown => {
+                log::info!("Unknown Push command.");
+                Ok(AccountEvent::Unknown)
+            }
+        }
+    }
+}
+
+#[derive(Debug, Deserialize)]
+#[serde(tag = "command", content = "data")]
+pub enum PushPayload {
+    #[serde(rename = "fxaccounts:command_received")]
+    CommandReceived(CommandReceivedPushPayload),
+    #[serde(rename = "fxaccounts:profile_updated")]
+    ProfileUpdated,
+    #[serde(rename = "fxaccounts:device_connected")]
+    DeviceConnected(DeviceConnectedPushPayload),
+    #[serde(rename = "fxaccounts:device_disconnected")]
+    DeviceDisconnected(DeviceDisconnectedPushPayload),
+    #[serde(rename = "fxaccounts:password_changed")]
+    PasswordChanged,
+    #[serde(rename = "fxaccounts:password_reset")]
+    PasswordReset,
+    #[serde(rename = "fxaccounts:account_destroyed")]
+    AccountDestroyed(AccountDestroyedPushPayload),
+    #[serde(other)]
+    Unknown,
+}
+
+// Some of this structs fields are not read, except
+// when deserialized, we mark them as dead_code
+#[allow(dead_code)]
+#[derive(Debug, Deserialize)]
+pub struct CommandReceivedPushPayload {
+    command: String,
+    index: u64,
+    sender: String,
+    url: String,
+}
+
+#[derive(Debug, Deserialize)]
+pub struct DeviceConnectedPushPayload {
+    #[serde(rename = "deviceName")]
+    device_name: String,
+}
+
+#[derive(Debug, Deserialize)]
+pub struct DeviceDisconnectedPushPayload {
+    #[serde(rename = "id")]
+    device_id: String,
+}
+
+#[derive(Debug, Deserialize)]
+pub struct AccountDestroyedPushPayload {
+    #[serde(rename = "uid")]
+    account_uid: String,
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::internal::http_client::IntrospectResponse;
+    use crate::internal::http_client::MockFxAClient;
+    use crate::internal::oauth::RefreshToken;
+    use crate::internal::CachedResponse;
+    use crate::internal::Config;
+    use mockall::predicate::always;
+    use mockall::predicate::eq;
+    use std::sync::Arc;
+
+    #[test]
+    fn test_deserialize_send_tab_command() {
+        let json = "{\"version\":1,\"command\":\"fxaccounts:command_received\",\"data\":{\"command\":\"send-tab-recv\",\"index\":1,\"sender\":\"bobo\",\"url\":\"https://mozilla.org\"}}";
+        let _: PushPayload = serde_json::from_str(json).unwrap();
+    }
+
+    #[test]
+    fn test_push_profile_updated() {
+        let mut fxa = FirefoxAccount::with_config(crate::internal::Config::stable_dev(
+            "12345678",
+            "https://foo.bar",
+        ));
+        fxa.add_cached_profile("123", "test@example.com");
+        let json = "{\"version\":1,\"command\":\"fxaccounts:profile_updated\"}";
+        let event = fxa.handle_push_message(json).unwrap();
+        assert!(fxa.state.last_seen_profile().is_none());
+        assert!(matches!(event, AccountEvent::ProfileUpdated));
+    }
+
+    #[test]
+    fn test_push_device_disconnected_local() {
+        let mut fxa =
+            FirefoxAccount::with_config(Config::stable_dev("12345678", "https://foo.bar"));
+        let refresh_token_scopes = std::collections::HashSet::new();
+        fxa.state
+            .force_refresh_token(crate::internal::oauth::RefreshToken {
+                token: "refresh_token".to_owned(),
+                scopes: refresh_token_scopes,
+            });
+        fxa.state.force_current_device_id("my_id");
+        let json = "{\"version\":1,\"command\":\"fxaccounts:device_disconnected\",\"data\":{\"id\":\"my_id\"}}";
+        let event = fxa.handle_push_message(json).unwrap();
+        assert!(fxa.state.refresh_token().is_none());
+        match event {
+            AccountEvent::DeviceDisconnected {
+                device_id,
+                is_local_device,
+            } => {
+                assert!(is_local_device);
+                assert_eq!(device_id, "my_id");
+            }
+            _ => unreachable!(),
+        };
+    }
+
+    #[test]
+    fn test_push_password_reset() {
+        let mut fxa =
+            FirefoxAccount::with_config(Config::stable_dev("12345678", "https://foo.bar"));
+        let mut client = MockFxAClient::new();
+        client
+            .expect_check_refresh_token_status()
+            .with(always(), eq("refresh_token"))
+            .times(1)
+            .returning(|_, _| Ok(IntrospectResponse { active: false }));
+        fxa.set_client(Arc::new(client));
+        let refresh_token_scopes = std::collections::HashSet::new();
+        fxa.state.force_refresh_token(RefreshToken {
+            token: "refresh_token".to_owned(),
+            scopes: refresh_token_scopes,
+        });
+        fxa.state.force_current_device_id("my_id");
+        fxa.devices_cache = Some(CachedResponse {
+            response: vec![],
+            cached_at: 0,
+            etag: "".to_string(),
+        });
+        let json = "{\"version\":1,\"command\":\"fxaccounts:password_reset\"}";
+        assert!(fxa.devices_cache.is_some());
+        let event = fxa.handle_push_message(json).unwrap();
+        assert!(matches!(event, AccountEvent::AccountAuthStateChanged));
+        assert!(fxa.devices_cache.is_none());
+    }
+
+    #[test]
+    fn test_push_password_change() {
+        let mut fxa =
+            FirefoxAccount::with_config(Config::stable_dev("12345678", "https://foo.bar"));
+        let mut client = MockFxAClient::new();
+        client
+            .expect_check_refresh_token_status()
+            .with(always(), eq("refresh_token"))
+            .times(1)
+            .returning(|_, _| Ok(IntrospectResponse { active: true }));
+        fxa.set_client(Arc::new(client));
+        let refresh_token_scopes = std::collections::HashSet::new();
+        fxa.state.force_refresh_token(RefreshToken {
+            token: "refresh_token".to_owned(),
+            scopes: refresh_token_scopes,
+        });
+        fxa.state.force_current_device_id("my_id");
+        fxa.devices_cache = Some(CachedResponse {
+            response: vec![],
+            cached_at: 0,
+            etag: "".to_string(),
+        });
+        let json = "{\"version\":1,\"command\":\"fxaccounts:password_changed\"}";
+        assert!(fxa.devices_cache.is_some());
+        let event = fxa.handle_push_message(json).unwrap();
+        assert!(matches!(event, AccountEvent::Unknown));
+        assert!(fxa.devices_cache.is_none());
+    }
+    #[test]
+    fn test_push_device_disconnected_remote() {
+        let mut fxa = FirefoxAccount::with_config(crate::internal::Config::stable_dev(
+            "12345678",
+            "https://foo.bar",
+        ));
+        let json = "{\"version\":1,\"command\":\"fxaccounts:device_disconnected\",\"data\":{\"id\":\"remote_id\"}}";
+        let event = fxa.handle_push_message(json).unwrap();
+        match event {
+            AccountEvent::DeviceDisconnected {
+                device_id,
+                is_local_device,
+            } => {
+                assert!(!is_local_device);
+                assert_eq!(device_id, "remote_id");
+            }
+            _ => unreachable!(),
+        };
+    }
+
+    #[test]
+    fn test_handle_push_message_ignores_unknown_command() {
+        let mut fxa =
+            FirefoxAccount::with_config(Config::stable_dev("12345678", "https://foo.bar"));
+        let json = "{\"version\":1,\"command\":\"huh\"}";
+        let event = fxa.handle_push_message(json).unwrap();
+        assert!(matches!(event, AccountEvent::Unknown));
+    }
+
+    #[test]
+    fn test_handle_push_message_ignores_unknown_command_with_data() {
+        let mut fxa =
+            FirefoxAccount::with_config(Config::stable_dev("12345678", "https://foo.bar"));
+        let json = "{\"version\":1,\"command\":\"huh\",\"data\":{\"value\":42}}";
+        let event = fxa.handle_push_message(json).unwrap();
+        assert!(matches!(event, AccountEvent::Unknown));
+    }
+
+    #[test]
+    fn test_handle_push_message_errors_on_garbage_data() {
+        let mut fxa =
+            FirefoxAccount::with_config(Config::stable_dev("12345678", "https://foo.bar"));
+        let json = "{\"wtf\":\"bbq\"}";
+        fxa.handle_push_message(json).unwrap_err();
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/fxa_client/internal/scoped_keys.rs.html b/book/rust-docs/src/fxa_client/internal/scoped_keys.rs.html new file mode 100644 index 0000000000..fb38720b6e --- /dev/null +++ b/book/rust-docs/src/fxa_client/internal/scoped_keys.rs.html @@ -0,0 +1,215 @@ +scoped_keys.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
+use jwcrypto::{self, DecryptionParameters, Jwk};
+use rc_crypto::{agreement, agreement::EphemeralKeyPair};
+
+use super::FirefoxAccount;
+use crate::{Error, Result, ScopedKey};
+
+impl FirefoxAccount {
+    pub(crate) fn get_scoped_key(&self, scope: &str) -> Result<&ScopedKey> {
+        self.state
+            .get_scoped_key(scope)
+            .ok_or_else(|| Error::NoScopedKey(scope.to_string()))
+    }
+}
+
+impl ScopedKey {
+    pub fn key_bytes(&self) -> Result<Vec<u8>> {
+        Ok(URL_SAFE_NO_PAD.decode(&self.k)?)
+    }
+}
+
+impl std::fmt::Debug for ScopedKey {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.debug_struct("ScopedKey")
+            .field("kty", &self.kty)
+            .field("scope", &self.scope)
+            .field("kid", &self.kid)
+            .finish()
+    }
+}
+
+pub struct ScopedKeysFlow {
+    key_pair: EphemeralKeyPair,
+}
+
+impl ScopedKeysFlow {
+    pub fn with_random_key() -> Result<Self> {
+        let key_pair = EphemeralKeyPair::generate(&agreement::ECDH_P256)?;
+        Ok(Self { key_pair })
+    }
+
+    #[cfg(test)]
+    pub fn from_static_key_pair(key_pair: agreement::KeyPair<agreement::Static>) -> Result<Self> {
+        let (private_key, _) = key_pair.split();
+        let ephemeral_prv_key = private_key._tests_only_dangerously_convert_to_ephemeral();
+        let key_pair = agreement::KeyPair::from_private_key(ephemeral_prv_key)?;
+        Ok(Self { key_pair })
+    }
+
+    pub fn get_public_key_jwk(&self) -> Result<Jwk> {
+        Ok(jwcrypto::ec::extract_pub_key_jwk(&self.key_pair)?)
+    }
+
+    pub fn decrypt_keys_jwe(self, jwe: &str) -> Result<String> {
+        let params = DecryptionParameters::ECDH_ES {
+            local_key_pair: self.key_pair,
+        };
+        Ok(jwcrypto::decrypt_jwe(jwe, params)?)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use jwcrypto::JwkKeyParameters;
+    use rc_crypto::agreement::{KeyPair, PrivateKey};
+
+    #[test]
+    fn test_flow() {
+        let x = URL_SAFE_NO_PAD
+            .decode("ARvGIPJ5eIFdp6YTM-INVDqwfun2R9FfCUvXbH7QCIU")
+            .unwrap();
+        let y = URL_SAFE_NO_PAD
+            .decode("hk8gP0Po8nBh-WSiTsvsyesC5c1L6fGOEVuX8FHsvTs")
+            .unwrap();
+        let d = URL_SAFE_NO_PAD
+            .decode("UayD4kn_4QHvLvLLSSaANfDUp9AcQndQu_TohQKoyn8")
+            .unwrap();
+        let ec_key =
+            agreement::EcKey::from_coordinates(agreement::Curve::P256, &d, &x, &y).unwrap();
+        let private_key = PrivateKey::<rc_crypto::agreement::Static>::import(&ec_key).unwrap();
+        let key_pair = KeyPair::from(private_key).unwrap();
+        let flow = ScopedKeysFlow::from_static_key_pair(key_pair).unwrap();
+        let jwk = flow.get_public_key_jwk().unwrap();
+        let ec_key_params = match jwk.key_parameters {
+            JwkKeyParameters::EC(ref ec_key_params) => ec_key_params,
+            _ => unreachable!("test only does EC"),
+        };
+        assert_eq!(ec_key_params.crv, "P-256");
+        assert_eq!(
+            ec_key_params.x,
+            "ARvGIPJ5eIFdp6YTM-INVDqwfun2R9FfCUvXbH7QCIU"
+        );
+        assert_eq!(
+            ec_key_params.y,
+            "hk8gP0Po8nBh-WSiTsvsyesC5c1L6fGOEVuX8FHsvTs"
+        );
+
+        let jwe = "eyJhbGciOiJFQ0RILUVTIiwia2lkIjoiNFBKTTl5dGVGeUtsb21ILWd2UUtyWGZ0a0N3ak9HNHRfTmpYVXhLM1VqSSIsImVwayI6eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6IlB3eG9Na1RjSVZ2TFlKWU4wM2R0Y3o2TEJrR0FHaU1hZWlNQ3lTZXEzb2MiLCJ5IjoiLUYtTllRRDZwNUdSQ2ZoYm1hN3NvNkhxdExhVlNub012S0pFcjFBeWlaSSJ9LCJlbmMiOiJBMjU2R0NNIn0..b9FPhjjpmAmo_rP8.ur9jTry21Y2trvtcanSFmAtiRfF6s6qqyg6ruRal7PCwa7PxDzAuMN6DZW5BiK8UREOH08-FyRcIgdDOm5Zq8KwVAn56PGfcH30aNDGQNkA_mpfjx5Tj2z8kI6ryLWew4PGZb-PsL1g-_eyXhktq7dAhetjNYttKwSREWQFokv7N3nJGpukBqnwL1ost-MjDXlINZLVJKAiMHDcu-q7Epitwid2c2JVGOSCJjbZ4-zbxVmZ4o9xhFb2lbvdiaMygH6bPlrjEK99uT6XKtaIZmyDwftbD6G3x4On-CqA2TNL6ILRaJMtmyX--ctL0IrngUIHg_F0Wz94v.zBD8NACkUcZTPLH0tceGnA";
+        let keys = flow.decrypt_keys_jwe(jwe).unwrap();
+        assert_eq!(keys, "{\"https://identity.mozilla.com/apps/oldsync\":{\"kty\":\"oct\",\"scope\":\"https://identity.mozilla.com/apps/oldsync\",\"k\":\"8ek1VNk4sjrNP0DhGC4crzQtwmpoR64zHuFMHb4Tw-exR70Z2SSIfMSrJDTLEZid9lD05-hbA3n2Q4Esjlu1tA\",\"kid\":\"1526414944666-zgTjf5oXmPmBjxwXWFsDWg\"}}");
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/fxa_client/internal/scopes.rs.html b/book/rust-docs/src/fxa_client/internal/scopes.rs.html new file mode 100644 index 0000000000..046d7db241 --- /dev/null +++ b/book/rust-docs/src/fxa_client/internal/scopes.rs.html @@ -0,0 +1,13 @@ +scopes.rs - source
1
+2
+3
+4
+5
+6
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+pub const PROFILE: &str = "profile";
+pub const OLD_SYNC: &str = "https://identity.mozilla.com/apps/oldsync";
+
\ No newline at end of file diff --git a/book/rust-docs/src/fxa_client/internal/send_tab.rs.html b/book/rust-docs/src/fxa_client/internal/send_tab.rs.html new file mode 100644 index 0000000000..4e973b932b --- /dev/null +++ b/book/rust-docs/src/fxa_client/internal/send_tab.rs.html @@ -0,0 +1,323 @@ +send_tab.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use super::{
+    commands::{
+        send_tab::{
+            self, EncryptedSendTabPayload, PrivateSendTabKeys, PublicSendTabKeys,
+            SendTabKeysPayload, SendTabPayload,
+        },
+        IncomingDeviceCommand,
+    },
+    http_client::GetDeviceResponse,
+    scopes, telemetry, FirefoxAccount,
+};
+use crate::{Error, Result};
+
+impl FirefoxAccount {
+    /// Generate the Send Tab command to be registered with the server.
+    ///
+    /// **💾 This method alters the persisted account state.**
+    pub(crate) fn generate_send_tab_command_data(&mut self) -> Result<String> {
+        let own_keys = self.load_or_generate_keys()?;
+        let public_keys: PublicSendTabKeys = own_keys.into();
+        let oldsync_key = self.get_scoped_key(scopes::OLD_SYNC)?;
+        public_keys.as_command_data(oldsync_key)
+    }
+
+    fn load_or_generate_keys(&mut self) -> Result<PrivateSendTabKeys> {
+        if let Some(s) = self.send_tab_key() {
+            match PrivateSendTabKeys::deserialize(s) {
+                Ok(keys) => return Ok(keys),
+                Err(_) => {
+                    error_support::report_error!(
+                        "fxaclient-send-tab-key-deserialize",
+                        "Could not deserialize Send Tab keys. Re-creating them."
+                    );
+                }
+            }
+        }
+        let keys = PrivateSendTabKeys::from_random()?;
+        self.set_send_tab_key(keys.serialize()?);
+        Ok(keys)
+    }
+
+    /// Send a single tab to another device designated by its device ID.
+    /// XXX - We need a new send_tabs_to_devices() so we can correctly record
+    /// telemetry for these cases.
+    /// This probably requires a new "Tab" struct with the title and url.
+    /// android-components has SendToAllUseCase(), so this isn't just theoretical.
+    /// See <https://github.com/mozilla/application-services/issues/3402>
+    pub fn send_single_tab(
+        &mut self,
+        target_device_id: &str,
+        title: &str,
+        url: &str,
+    ) -> Result<()> {
+        let devices = self.get_devices(false)?;
+        let target = devices
+            .iter()
+            .find(|d| d.id == target_device_id)
+            .ok_or_else(|| Error::UnknownTargetDevice(target_device_id.to_owned()))?;
+        let (payload, sent_telemetry) = SendTabPayload::single_tab(title, url);
+        let oldsync_key = self.get_scoped_key(scopes::OLD_SYNC)?;
+        let command_payload = send_tab::build_send_command(oldsync_key, target, &payload)?;
+        self.invoke_command(send_tab::COMMAND_NAME, target, &command_payload)?;
+        self.telemetry.record_tab_sent(sent_telemetry);
+        Ok(())
+    }
+
+    pub(crate) fn handle_send_tab_command(
+        &mut self,
+        sender: Option<GetDeviceResponse>,
+        payload: serde_json::Value,
+        reason: telemetry::ReceivedReason,
+    ) -> Result<IncomingDeviceCommand> {
+        let send_tab_key: PrivateSendTabKeys = match self.send_tab_key() {
+            Some(s) => PrivateSendTabKeys::deserialize(s)?,
+            None => {
+                return Err(Error::IllegalState(
+                    "Cannot find send-tab keys. Has initialize_device been called before?",
+                ));
+            }
+        };
+        let encrypted_payload: EncryptedSendTabPayload = serde_json::from_value(payload)?;
+        match encrypted_payload.decrypt(&send_tab_key) {
+            Ok(payload) => {
+                // It's an incoming tab, which we record telemetry for.
+                let recd_telemetry = telemetry::ReceivedCommand {
+                    flow_id: payload.flow_id.clone(),
+                    stream_id: payload.stream_id.clone(),
+                    reason,
+                };
+                self.telemetry.record_tab_received(recd_telemetry);
+                // The telemetry IDs escape to the consumer, but that's OK...
+                Ok(IncomingDeviceCommand::TabReceived { sender, payload })
+            }
+            Err(e) => {
+                // XXX - this seems ripe for telemetry collection!?
+                // It also seems like it might be possible to recover - ie, one
+                // of the reasons is that there are key mismatches. Doesn't that
+                // mean the "other" key might work?
+                log::warn!("Could not decrypt Send Tab payload. Diagnosing then resetting the Send Tab keys.");
+                match self.diagnose_remote_keys(send_tab_key) {
+                    Ok(_) => {
+                        error_support::report_error!(
+                            "fxaclient-send-tab-decrypt",
+                            "Could not find the cause of the Send Tab keys issue."
+                        );
+                    }
+                    Err(e) => {
+                        error_support::report_error!("fxaclient-send-tab-decrypt", "{}", e);
+                    }
+                };
+                // Reset the Send Tab keys.
+                self.clear_send_tab_key();
+                self.reregister_current_capabilities()?;
+                Err(e)
+            }
+        }
+    }
+
+    fn send_tab_key(&self) -> Option<&str> {
+        self.state.get_commands_data(send_tab::COMMAND_NAME)
+    }
+
+    fn set_send_tab_key(&mut self, key: String) {
+        self.state.set_commands_data(send_tab::COMMAND_NAME, key)
+    }
+
+    fn clear_send_tab_key(&mut self) {
+        self.state.clear_commands_data(send_tab::COMMAND_NAME);
+    }
+
+    fn diagnose_remote_keys(&mut self, local_send_tab_key: PrivateSendTabKeys) -> Result<()> {
+        let own_device = &mut self
+            .get_current_device()?
+            .ok_or(Error::SendTabDiagnosisError("No remote device."))?;
+
+        let command = own_device
+            .available_commands
+            .get(send_tab::COMMAND_NAME)
+            .ok_or(Error::SendTabDiagnosisError("No remote command."))?;
+        let bundle: SendTabKeysPayload = serde_json::from_str(command)?;
+        let oldsync_key = self.get_scoped_key(scopes::OLD_SYNC)?;
+        let public_keys_remote = bundle
+            .decrypt(oldsync_key)
+            .map_err(|_| Error::SendTabDiagnosisError("Unable to decrypt public key bundle."))?;
+
+        let public_keys_local: PublicSendTabKeys = local_send_tab_key.into();
+
+        if public_keys_local.public_key() != public_keys_remote.public_key() {
+            return Err(Error::SendTabDiagnosisError("Mismatch in public key."));
+        }
+
+        if public_keys_local.auth_secret() != public_keys_remote.auth_secret() {
+            return Err(Error::SendTabDiagnosisError("Mismatch in auth secret."));
+        }
+        Ok(())
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/fxa_client/internal/state_manager.rs.html b/book/rust-docs/src/fxa_client/internal/state_manager.rs.html new file mode 100644 index 0000000000..055caade02 --- /dev/null +++ b/book/rust-docs/src/fxa_client/internal/state_manager.rs.html @@ -0,0 +1,551 @@ +state_manager.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use std::collections::{HashMap, HashSet};
+
+use crate::{
+    internal::{
+        oauth::{AccessTokenInfo, RefreshToken},
+        profile::Profile,
+        state_persistence::state_to_json,
+        CachedResponse, Config, OAuthFlow, PersistedState,
+    },
+    DeviceCapability, FxaRustAuthState, LocalDevice, Result, ScopedKey,
+};
+
+/// Stores and manages the current state of the FxA client
+///
+/// All fields are private, which means that all state mutations must go through this module.  This
+/// makes it easier to reason about state changes.
+pub struct StateManager {
+    /// State that's persisted to disk
+    persisted_state: PersistedState,
+    /// In-progress OAuth flows
+    flow_store: HashMap<String, OAuthFlow>,
+}
+
+impl StateManager {
+    pub(crate) fn new(persisted_state: PersistedState) -> Self {
+        Self {
+            persisted_state,
+            flow_store: HashMap::new(),
+        }
+    }
+
+    pub fn serialize_persisted_state(&self) -> Result<String> {
+        state_to_json(&self.persisted_state)
+    }
+
+    pub fn config(&self) -> &Config {
+        &self.persisted_state.config
+    }
+
+    pub fn refresh_token(&self) -> Option<&RefreshToken> {
+        self.persisted_state.refresh_token.as_ref()
+    }
+
+    pub fn session_token(&self) -> Option<&str> {
+        self.persisted_state.session_token.as_deref()
+    }
+
+    /// Get our device capabilities
+    ///
+    /// This is the last set of capabilities passed to `initialize_device` or `ensure_capabilities`
+    pub fn device_capabilities(&self) -> &HashSet<DeviceCapability> {
+        &self.persisted_state.device_capabilities
+    }
+
+    /// Set our device capabilities
+    pub fn set_device_capabilities(
+        &mut self,
+        capabilities_set: impl IntoIterator<Item = DeviceCapability>,
+    ) {
+        self.persisted_state.device_capabilities = HashSet::from_iter(capabilities_set);
+    }
+
+    /// Get the last known LocalDevice info sent back from the server
+    pub fn server_local_device_info(&self) -> Option<&LocalDevice> {
+        self.persisted_state.server_local_device_info.as_ref()
+    }
+
+    /// Update the last known LocalDevice info when getting one back from the server
+    pub fn update_server_local_device_info(&mut self, local_device: LocalDevice) {
+        self.persisted_state.server_local_device_info = Some(local_device)
+    }
+
+    /// Clear out the last known LocalDevice info. This means that the next call to
+    /// `ensure_capabilities()` will re-send our capabilities to the server
+    ///
+    /// This is typically called when something may invalidate the server's knowledge of our
+    /// local device capabilities, for example replacing our device info.
+    pub fn clear_server_local_device_info(&mut self) {
+        self.persisted_state.server_local_device_info = None
+    }
+
+    pub fn get_commands_data(&self, key: &str) -> Option<&str> {
+        self.persisted_state
+            .commands_data
+            .get(key)
+            .map(String::as_str)
+    }
+
+    pub fn set_commands_data(&mut self, key: &str, data: String) {
+        self.persisted_state
+            .commands_data
+            .insert(key.to_string(), data);
+    }
+
+    pub fn clear_commands_data(&mut self, key: &str) {
+        self.persisted_state.commands_data.remove(key);
+    }
+
+    pub fn last_handled_command_index(&self) -> Option<u64> {
+        self.persisted_state.last_handled_command
+    }
+
+    pub fn set_last_handled_command_index(&mut self, idx: u64) {
+        self.persisted_state.last_handled_command = Some(idx)
+    }
+
+    pub fn current_device_id(&self) -> Option<&str> {
+        self.persisted_state.current_device_id.as_deref()
+    }
+
+    pub fn set_current_device_id(&mut self, device_id: String) {
+        self.persisted_state.current_device_id = Some(device_id);
+    }
+
+    pub fn get_scoped_key(&self, scope: &str) -> Option<&ScopedKey> {
+        self.persisted_state.scoped_keys.get(scope)
+    }
+
+    pub(crate) fn last_seen_profile(&self) -> Option<&CachedResponse<Profile>> {
+        self.persisted_state.last_seen_profile.as_ref()
+    }
+
+    pub(crate) fn set_last_seen_profile(&mut self, profile: CachedResponse<Profile>) {
+        self.persisted_state.last_seen_profile = Some(profile)
+    }
+
+    pub fn clear_last_seen_profile(&mut self) {
+        self.persisted_state.last_seen_profile = None
+    }
+
+    pub fn get_cached_access_token(&mut self, scope: &str) -> Option<&AccessTokenInfo> {
+        self.persisted_state.access_token_cache.get(scope)
+    }
+
+    pub fn add_cached_access_token(&mut self, scope: impl Into<String>, token: AccessTokenInfo) {
+        self.persisted_state
+            .access_token_cache
+            .insert(scope.into(), token);
+    }
+
+    pub fn clear_access_token_cache(&mut self) {
+        self.persisted_state.access_token_cache.clear()
+    }
+
+    /// Begin an OAuth flow.  This saves the OAuthFlow for later.  `state` must be unique to this
+    /// oauth flow process.
+    pub fn begin_oauth_flow(&mut self, state: impl Into<String>, flow: OAuthFlow) {
+        self.flow_store.insert(state.into(), flow);
+    }
+
+    /// Get an OAuthFlow from a previous `begin_oauth_flow()` call
+    ///
+    /// This operation removes the OAuthFlow from the our internal map.  It can only be called once
+    /// per `state` value.
+    pub fn pop_oauth_flow(&mut self, state: &str) -> Option<OAuthFlow> {
+        self.flow_store.remove(state)
+    }
+
+    /// Complete an OAuth flow.
+    pub fn complete_oauth_flow(
+        &mut self,
+        scoped_keys: Vec<(String, ScopedKey)>,
+        refresh_token: RefreshToken,
+        session_token: Option<String>,
+    ) {
+        // When our keys change, we might need to re-register device capabilities with the server.
+        // Ensure that this happens on the next call to ensure_capabilities.
+        self.clear_server_local_device_info();
+
+        for (scope, key) in scoped_keys {
+            self.persisted_state.scoped_keys.insert(scope, key);
+        }
+        self.persisted_state.refresh_token = Some(refresh_token);
+        self.persisted_state.session_token = session_token;
+        self.persisted_state.logged_out_from_auth_issues = false;
+        self.flow_store.clear();
+    }
+
+    /// Called when the account is disconnected.  This clears most of the auth state, but keeps
+    /// some information in order to eventually reconnect to the same user account later.
+    pub fn disconnect(&mut self) {
+        self.persisted_state.current_device_id = None;
+        self.persisted_state.refresh_token = None;
+        self.persisted_state.scoped_keys = HashMap::new();
+        self.persisted_state.last_handled_command = None;
+        self.persisted_state.commands_data = HashMap::new();
+        self.persisted_state.access_token_cache = HashMap::new();
+        self.persisted_state.device_capabilities = HashSet::new();
+        self.persisted_state.server_local_device_info = None;
+        self.persisted_state.session_token = None;
+        self.persisted_state.logged_out_from_auth_issues = false;
+        self.flow_store.clear();
+    }
+
+    /// Called when we notice authentication issues with the account state.
+    ///
+    /// This clears the auth state, but leaves some fields untouched. That way, if the user
+    /// re-authenticates they can continue using the account without unexpected behavior.  The
+    /// fields that don't change compared to `disconnect()` are:
+    ///
+    ///   * `current_device_id`
+    ///   * `device_capabilities`
+    ///   * `last_handled_command`
+    pub fn on_auth_issues(&mut self) {
+        self.persisted_state.refresh_token = None;
+        self.persisted_state.scoped_keys = HashMap::new();
+        self.persisted_state.commands_data = HashMap::new();
+        self.persisted_state.access_token_cache = HashMap::new();
+        self.persisted_state.server_local_device_info = None;
+        self.persisted_state.session_token = None;
+        self.persisted_state.logged_out_from_auth_issues = true;
+        self.flow_store.clear();
+    }
+
+    pub fn get_auth_state(&self) -> FxaRustAuthState {
+        if self.persisted_state.refresh_token.is_some() {
+            FxaRustAuthState::Connected
+        } else if self.persisted_state.logged_out_from_auth_issues {
+            FxaRustAuthState::AuthIssues
+        } else {
+            FxaRustAuthState::Disconnected
+        }
+    }
+
+    /// Handle the auth tokens changing
+    ///
+    /// This method updates the token data and clears out data that may be invalidated with the
+    /// token changes.
+    pub fn update_tokens(&mut self, session_token: String, refresh_token: RefreshToken) {
+        self.persisted_state.session_token = Some(session_token);
+        self.persisted_state.refresh_token = Some(refresh_token);
+        self.persisted_state.access_token_cache.clear();
+        self.persisted_state.server_local_device_info = None;
+    }
+
+    pub fn simulate_temporary_auth_token_issue(&mut self) {
+        for (_, access_token) in self.persisted_state.access_token_cache.iter_mut() {
+            access_token.token = "invalid-data".to_owned()
+        }
+    }
+
+    /// Used by the application to test auth token issues
+    pub fn simulate_permanent_auth_token_issue(&mut self) {
+        self.persisted_state.session_token = None;
+        self.persisted_state.refresh_token = None;
+        self.persisted_state.access_token_cache.clear();
+    }
+}
+
+#[cfg(test)]
+impl StateManager {
+    pub fn is_access_token_cache_empty(&self) -> bool {
+        self.persisted_state.access_token_cache.is_empty()
+    }
+
+    pub fn force_refresh_token(&mut self, token: RefreshToken) {
+        self.persisted_state.refresh_token = Some(token)
+    }
+
+    pub fn force_session_token(&mut self, token: String) {
+        self.persisted_state.session_token = Some(token)
+    }
+
+    pub fn force_current_device_id(&mut self, device_id: impl Into<String>) {
+        self.persisted_state.current_device_id = Some(device_id.into())
+    }
+
+    pub fn insert_scoped_key(&mut self, scope: impl Into<String>, key: ScopedKey) {
+        self.persisted_state.scoped_keys.insert(scope.into(), key);
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/fxa_client/internal/state_persistence.rs.html b/book/rust-docs/src/fxa_client/internal/state_persistence.rs.html new file mode 100644 index 0000000000..dfb29e2d91 --- /dev/null +++ b/book/rust-docs/src/fxa_client/internal/state_persistence.rs.html @@ -0,0 +1,303 @@ +state_persistence.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Serialization of `FirefoxAccount` state to/from a JSON string.
+//!
+//! This module implements the ability to serialize a `FirefoxAccount` struct to and from
+//! a JSON string. The idea is that calling code will use this to persist the account state
+//! to storage.
+//!
+//! Many of the details here are a straightforward use of `serde`, with all persisted data being
+//! a field on a `State` struct. This is, however, some additional complexity around handling data
+//! migrations - we need to be able to evolve the internal details of the `State` struct while
+//! gracefully handing users who are upgrading from an older version of a consuming app, which has
+//! stored account state from an older version of this component.
+//!
+//! Data migration is handled by explicitly naming different versions of the state struct to
+//! correspond to different incompatible changes to the data representation, e.g. `StateV1` and
+//! `StateV2`. We then wrap this in a `PersistedStateTagged` enum whose serialization gets explicitly
+//! tagged with the corresponding state version number.
+//!
+//! For backwards-compatible changes to the data (such as adding a new field that has a sensible
+//! default) we keep the current `State` struct, but modify it in such a way that `serde` knows
+//! how to do the right thing.
+//!
+//! For backwards-incompatible changes to the data (such as removing or significantly refactoring
+//! fields) we define a new `StateV{X+1}` struct, and use the `From` trait to define how to update
+//! from older struct versions.
+//! For an example how the conversion works, [we can look at `StateV1` which was deliberately removed](https://github.com/mozilla/application-services/issues/3912)
+//! The code that was deleted demonstrates how we can implement the migration
+
+use serde_derive::*;
+use std::collections::{HashMap, HashSet};
+
+use super::{
+    config::Config,
+    oauth::{AccessTokenInfo, RefreshToken},
+    profile::Profile,
+    CachedResponse, Result,
+};
+use crate::{DeviceCapability, LocalDevice, ScopedKey};
+
+// These are the public API for working with the persisted state.
+
+pub(crate) type PersistedState = StateV2;
+
+/// Parse a `State` from a JSON string, performing migrations if necessary.
+///
+pub(crate) fn state_from_json(data: &str) -> Result<PersistedState> {
+    let stored_state: PersistedStateTagged = serde_json::from_str(data)?;
+    upgrade_state(stored_state)
+}
+
+/// Serialize a `State` to a JSON string.
+///
+pub(crate) fn state_to_json(state: &PersistedState) -> Result<String> {
+    let state = PersistedStateTagged::V2(state.clone());
+    serde_json::to_string(&state).map_err(Into::into)
+}
+
+fn upgrade_state(in_state: PersistedStateTagged) -> Result<PersistedState> {
+    match in_state {
+        PersistedStateTagged::V2(state) => Ok(state),
+    }
+}
+
+/// `PersistedStateTagged` is a tagged container for one of the state versions.
+/// Serde picks the right `StructVX` to deserialized based on the schema_version tag.
+///
+#[derive(Serialize, Deserialize)]
+#[serde(tag = "schema_version")]
+#[allow(clippy::large_enum_variant)]
+enum PersistedStateTagged {
+    V2(StateV2),
+}
+
+/// `StateV2` is the current state schema. It and its fields all need to be public
+/// so that they can be used directly elsewhere in the crate.
+///
+/// If you want to modify what gets stored in the state, consider the following:
+///
+///   * Is the change backwards-compatible with previously-serialized data?
+///     If so then you'll need to tell serde how to fill in a suitable default.
+///     If not then you'll need to make a new `StateV3` and implement an explicit migration.
+///
+///   * How does the new field need to be modified when the user disconnects from the account or is
+///     logged out from auth issues? Update [state_manager.disconnect] and
+///     [state_manager.on_auth_issues].
+///
+#[derive(Clone, Serialize, Deserialize)]
+pub(crate) struct StateV2 {
+    pub(crate) config: Config,
+    pub(crate) current_device_id: Option<String>,
+    pub(crate) refresh_token: Option<RefreshToken>,
+    pub(crate) scoped_keys: HashMap<String, ScopedKey>,
+    pub(crate) last_handled_command: Option<u64>,
+    // Everything below here was added after `StateV2` was initially defined,
+    // and hence needs to have a suitable default value.
+    // We can remove serde(default) when we define a `StateV3`.
+    #[serde(default)]
+    pub(crate) commands_data: HashMap<String, String>,
+    #[serde(default)]
+    pub(crate) device_capabilities: HashSet<DeviceCapability>,
+    #[serde(default)]
+    pub(crate) access_token_cache: HashMap<String, AccessTokenInfo>,
+    pub(crate) session_token: Option<String>, // Hex-formatted string.
+    pub(crate) last_seen_profile: Option<CachedResponse<Profile>>,
+    // The last LocalDevice info sent back from the server
+    #[serde(default)]
+    pub(crate) server_local_device_info: Option<LocalDevice>,
+    #[serde(default)]
+    pub(crate) logged_out_from_auth_issues: bool,
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_invalid_schema_version() {
+        let state_v1_json = "{\"schema_version\":\"V1\",\"client_id\":\"98adfa37698f255b\",\"redirect_uri\":\"https://lockbox.firefox.com/fxa/ios-redirect.html\",\"config\":{\"content_url\":\"https://accounts.firefox.com\",\"auth_url\":\"https://api.accounts.firefox.com/\",\"oauth_url\":\"https://oauth.accounts.firefox.com/\",\"profile_url\":\"https://profile.accounts.firefox.com/\",\"token_server_endpoint_url\":\"https://token.services.mozilla.com/1.0/sync/1.5\",\"authorization_endpoint\":\"https://accounts.firefox.com/authorization\",\"issuer\":\"https://accounts.firefox.com\",\"jwks_uri\":\"https://oauth.accounts.firefox.com/v1/jwks\",\"token_endpoint\":\"https://oauth.accounts.firefox.com/v1/token\",\"userinfo_endpoint\":\"https://profile.accounts.firefox.com/v1/profile\"},\"oauth_cache\":{\"https://identity.mozilla.com/apps/oldsync https://identity.mozilla.com/apps/lockbox profile\":{\"access_token\":\"bef37ec0340783356bcac67a86c4efa23a56f2ddd0c7a6251d19988bab7bdc99\",\"keys\":\"{\\\"https://identity.mozilla.com/apps/oldsync\\\":{\\\"kty\\\":\\\"oct\\\",\\\"scope\\\":\\\"https://identity.mozilla.com/apps/oldsync\\\",\\\"k\\\":\\\"kMtwpVC0ZaYFJymPza8rXK_0CgCp3KMwRStwGfBRBDtL6hXRDVJgQFaoOQ2dimw0Bko5WVv2gNTy7RX5zFYZHg\\\",\\\"kid\\\":\\\"1542236016429-Ox1FbJfFfwTe5t-xq4v2hQ\\\"},\\\"https://identity.mozilla.com/apps/lockbox\\\":{\\\"kty\\\":\\\"oct\\\",\\\"scope\\\":\\\"https://identity.mozilla.com/apps/lockbox\\\",\\\"k\\\":\\\"Qk4K4xF2PgQ6XvBXW8X7B7AWwWgW2bHQov9NHNd4v-k\\\",\\\"kid\\\":\\\"1231014287-KDVj0DFaO3wGpPJD8oPwVg\\\"}}\",\"refresh_token\":\"bed5532f4fea7e39c5c4f609f53603ee7518fd1c103cc4034da3618f786ed188\",\"expires_at\":1543474657,\"scopes\":[\"https://identity.mozilla.com/apps/oldsync\",\"https://identity.mozilla.com/apps/lockbox\",\"profile\"]}}}";
+        if state_from_json(state_v1_json).is_ok() {
+            panic!("Invalid schema passed the conversion from json")
+        }
+    }
+
+    #[test]
+    fn test_v2_ignores_unknown_fields_introduced_by_future_changes_to_the_schema() {
+        // This is a snapshot of what some persisted StateV2 data would look before any backwards-compatible changes
+        // were made. It's very important that you don't modify this string, which would defeat the point of the test!
+        let state_v2_json = "{\"schema_version\":\"V2\",\"config\":{\"client_id\":\"98adfa37698f255b\",\"redirect_uri\":\"https://lockbox.firefox.com/fxa/ios-redirect.html\",\"content_url\":\"https://accounts.firefox.com\",\"remote_config\":{\"auth_url\":\"https://api.accounts.firefox.com/\",\"oauth_url\":\"https://oauth.accounts.firefox.com/\",\"profile_url\":\"https://profile.accounts.firefox.com/\",\"token_server_endpoint_url\":\"https://token.services.mozilla.com/1.0/sync/1.5\",\"authorization_endpoint\":\"https://accounts.firefox.com/authorization\",\"issuer\":\"https://accounts.firefox.com\",\"jwks_uri\":\"https://oauth.accounts.firefox.com/v1/jwks\",\"token_endpoint\":\"https://oauth.accounts.firefox.com/v1/token\",\"userinfo_endpoint\":\"https://profile.accounts.firefox.com/v1/profile\"}},\"refresh_token\":{\"token\":\"bed5532f4fea7e39c5c4f609f53603ee7518fd1c103cc4034da3618f786ed188\",\"scopes\":[\"https://identity.mozilla.com/apps/oldysnc\"]},\"scoped_keys\":{\"https://identity.mozilla.com/apps/oldsync\":{\"kty\":\"oct\",\"scope\":\"https://identity.mozilla.com/apps/oldsync\",\"k\":\"kMtwpVC0ZaYFJymPza8rXK_0CgCp3KMwRStwGfBRBDtL6hXRDVJgQFaoOQ2dimw0Bko5WVv2gNTy7RX5zFYZHg\",\"kid\":\"1542236016429-Ox1FbJfFfwTe5t-xq4v2hQ\"}},\"login_state\":{\"Unknown\":null},\"a_new_field\":42}";
+        let state = state_from_json(state_v2_json).unwrap();
+        let refresh_token = state.refresh_token.unwrap();
+        assert_eq!(
+            refresh_token.token,
+            "bed5532f4fea7e39c5c4f609f53603ee7518fd1c103cc4034da3618f786ed188"
+        );
+    }
+
+    #[test]
+    fn test_v2_creates_an_empty_access_token_cache_if_its_missing() {
+        let state_v2_json = "{\"schema_version\":\"V2\",\"config\":{\"client_id\":\"98adfa37698f255b\",\"redirect_uri\":\"https://lockbox.firefox.com/fxa/ios-redirect.html\",\"content_url\":\"https://accounts.firefox.com\"},\"refresh_token\":{\"token\":\"bed5532f4fea7e39c5c4f609f53603ee7518fd1c103cc4034da3618f786ed188\",\"scopes\":[\"https://identity.mozilla.com/apps/oldysnc\"]},\"scoped_keys\":{\"https://identity.mozilla.com/apps/oldsync\":{\"kty\":\"oct\",\"scope\":\"https://identity.mozilla.com/apps/oldsync\",\"k\":\"kMtwpVC0ZaYFJymPza8rXK_0CgCp3KMwRStwGfBRBDtL6hXRDVJgQFaoOQ2dimw0Bko5WVv2gNTy7RX5zFYZHg\",\"kid\":\"1542236016429-Ox1FbJfFfwTe5t-xq4v2hQ\"}},\"login_state\":{\"Unknown\":null}}";
+        let state = state_from_json(state_v2_json).unwrap();
+        let refresh_token = state.refresh_token.unwrap();
+        assert_eq!(
+            refresh_token.token,
+            "bed5532f4fea7e39c5c4f609f53603ee7518fd1c103cc4034da3618f786ed188"
+        );
+        assert_eq!(state.access_token_cache.len(), 0);
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/fxa_client/internal/telemetry.rs.html b/book/rust-docs/src/fxa_client/internal/telemetry.rs.html new file mode 100644 index 0000000000..644ac57ecd --- /dev/null +++ b/book/rust-docs/src/fxa_client/internal/telemetry.rs.html @@ -0,0 +1,199 @@ +telemetry.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use super::FirefoxAccount;
+use crate::Result;
+use serde_derive::*;
+use sync_guid::Guid;
+
+impl FirefoxAccount {
+    /// Gathers and resets telemetry for this account instance.
+    /// This should be considered a short-term solution to telemetry gathering
+    /// and should called whenever consumers expect there might be telemetry,
+    /// and it should submit the telemetry to whatever telemetry system is in
+    /// use (probably glean).
+    ///
+    /// The data is returned as a JSON string, which consumers should parse
+    /// forgivingly (eg, be tolerant of things not existing) to try and avoid
+    /// too many changes as telemetry comes and goes.
+    pub fn gather_telemetry(&mut self) -> Result<String> {
+        let telem = std::mem::replace(&mut self.telemetry, FxaTelemetry::new());
+        Ok(serde_json::to_string(&telem)?)
+    }
+}
+
+// A somewhat mixed-bag of all telemetry we want to collect. The idea is that
+// the app will "pull" telemetry via a new API whenever it thinks there might
+// be something to record.
+// It's considered a temporary solution until either we can record it directly
+// (eg, via glean) or we come up with something better.
+// Note that this means we'll lose telemetry if we crash between gathering it
+// here and the app submitting it, but that should be rare (in practice,
+// apps will submit it directly after an operation that generated telememtry)
+
+/// The reason a tab/command was received.
+#[derive(Debug, Serialize)]
+#[serde(rename_all = "kebab-case")]
+pub enum ReceivedReason {
+    /// A push notification for the command was received.
+    Push,
+    /// Discovered while handling a push notification for a later message.
+    PushMissed,
+    /// Explicit polling for missed commands.
+    Poll,
+}
+
+#[derive(Debug, Serialize)]
+pub struct SentCommand {
+    pub flow_id: String,
+    pub stream_id: String,
+}
+
+impl Default for SentCommand {
+    fn default() -> Self {
+        Self {
+            flow_id: Guid::random().to_string(),
+            stream_id: Guid::random().to_string(),
+        }
+    }
+}
+
+#[derive(Debug, Serialize)]
+pub struct ReceivedCommand {
+    pub flow_id: String,
+    pub stream_id: String,
+    pub reason: ReceivedReason,
+}
+
+// We have a naive strategy to avoid unbounded memory growth - the intention
+// is that if any platform lets things grow to hit these limits, it's probably
+// never going to consume anything - so it doesn't matter what we discard (ie,
+// there's no good reason to have a smarter circular buffer etc)
+const MAX_TAB_EVENTS: usize = 200;
+
+#[derive(Debug, Default, Serialize)]
+pub struct FxaTelemetry {
+    commands_sent: Vec<SentCommand>,
+    commands_received: Vec<ReceivedCommand>,
+}
+
+impl FxaTelemetry {
+    pub fn new() -> Self {
+        FxaTelemetry {
+            ..Default::default()
+        }
+    }
+
+    pub fn record_tab_sent(&mut self, sent: SentCommand) {
+        if self.commands_sent.len() < MAX_TAB_EVENTS {
+            self.commands_sent.push(sent);
+        }
+    }
+
+    pub fn record_tab_received(&mut self, recd: ReceivedCommand) {
+        if self.commands_received.len() < MAX_TAB_EVENTS {
+            self.commands_received.push(recd);
+        }
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/fxa_client/internal/util.rs.html b/book/rust-docs/src/fxa_client/internal/util.rs.html new file mode 100644 index 0000000000..ce578a1bf4 --- /dev/null +++ b/book/rust-docs/src/fxa_client/internal/util.rs.html @@ -0,0 +1,107 @@ +util.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::{Error, Result};
+use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
+use rc_crypto::rand;
+use std::time::{SystemTime, UNIX_EPOCH};
+
+// Gets the unix epoch in ms.
+pub fn now() -> u64 {
+    let since_epoch = SystemTime::now()
+        .duration_since(UNIX_EPOCH)
+        .expect("Something is very wrong.");
+    since_epoch.as_secs() * 1000 + u64::from(since_epoch.subsec_nanos()) / 1_000_000
+}
+
+pub fn now_secs() -> u64 {
+    let since_epoch = SystemTime::now()
+        .duration_since(UNIX_EPOCH)
+        .expect("Something is very wrong.");
+    since_epoch.as_secs()
+}
+
+/// Gets unix timestamp at `days` days ago
+pub fn past_timestamp(days: u64) -> u64 {
+    // 1000 milliseconds, 60 seconds, 60 minutes, 24 hours
+    now() - (1000 * 60 * 60 * 24 * days)
+}
+
+pub fn random_base64_url_string(len: usize) -> Result<String> {
+    let mut out = vec![0u8; len];
+    rand::fill(&mut out)?;
+    Ok(URL_SAFE_NO_PAD.encode(&out))
+}
+
+pub trait Xorable {
+    fn xored_with(&self, other: &[u8]) -> Result<Vec<u8>>;
+}
+
+impl Xorable for [u8] {
+    fn xored_with(&self, other: &[u8]) -> Result<Vec<u8>> {
+        if self.len() != other.len() {
+            Err(Error::XorLengthMismatch(self.len(), other.len()))
+        } else {
+            Ok(self
+                .iter()
+                .zip(other.iter())
+                .map(|(&x, &y)| x ^ y)
+                .collect())
+        }
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/fxa_client/lib.rs.html b/book/rust-docs/src/fxa_client/lib.rs.html new file mode 100644 index 0000000000..a5c8377a30 --- /dev/null +++ b/book/rust-docs/src/fxa_client/lib.rs.html @@ -0,0 +1,365 @@ +lib.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! # Firefox Accounts Client
+//!
+//! The fxa-client component lets applications integrate with the
+//! [Firefox Accounts](https://mozilla.github.io/ecosystem-platform/docs/features/firefox-accounts/fxa-overview)
+//! identity service. The shape of a typical integration would look
+//! something like:
+//!
+//! * Out-of-band, register your application with the Firefox Accounts service,
+//!   providing an OAuth `redirect_uri` controlled by your application and
+//!   obtaining an OAuth `client_id`.
+//!
+//! * On application startup, create a [`FirefoxAccount`] object to represent the
+//!   signed-in state of the application.
+//!     * On first startup, a new [`FirefoxAccount`] can be created by calling
+//!       [`FirefoxAccount::new`] and passing the application's `client_id`.
+//!     * For subsequent startups the object can be persisted using the
+//!       [`to_json`](FirefoxAccount::to_json) method and re-created by
+//!       calling [`FirefoxAccount::from_json`].
+//!
+//! * When the user wants to sign in to your application, direct them through
+//!   a web-based OAuth flow using [`begin_oauth_flow`](FirefoxAccount::begin_oauth_flow)
+//!   or [`begin_pairing_flow`](FirefoxAccount::begin_pairing_flow); when they return
+//!   to your registered `redirect_uri`, pass the resulting authorization state back to
+//!   [`complete_oauth_flow`](FirefoxAccount::complete_oauth_flow) to sign them in.
+//!
+//! * Display information about the signed-in user by using the data from
+//!   [`get_profile`](FirefoxAccount::get_profile).
+//!
+//! * Access account-related services on behalf of the user by obtaining OAuth
+//!   access tokens via [`get_access_token`](FirefoxAccount::get_access_token).
+//!
+//! * If the user opts to sign out of the application, calling [`disconnect`](FirefoxAccount::disconnect)
+//!   and then discarding any persisted account data.
+
+mod account;
+mod auth;
+mod device;
+mod error;
+mod internal;
+mod profile;
+mod push;
+mod storage;
+mod telemetry;
+mod token;
+
+pub use sync15::DeviceType;
+
+pub use auth::{AuthorizationInfo, FxaRustAuthState, MetricsParams};
+pub use device::{AttachedClient, Device, DeviceCapability, LocalDevice};
+pub use error::{Error, FxaError};
+use parking_lot::Mutex;
+pub use profile::Profile;
+pub use push::{
+    AccountEvent, DevicePushSubscription, IncomingDeviceCommand, SendTabPayload, TabHistoryEntry,
+};
+pub use token::{AccessTokenInfo, AuthorizationParameters, ScopedKey};
+
+/// Result returned by internal functions
+pub type Result<T> = std::result::Result<T, Error>;
+/// Result returned by public-facing API functions
+pub type ApiResult<T> = std::result::Result<T, FxaError>;
+
+/// Object representing the signed-in state of an application.
+///
+/// The `FirefoxAccount` object is the main interface provided by this crate.
+/// It represents the signed-in state of an application that may be connected to
+/// user's Firefox Account, and provides methods for inspecting the state of the
+/// account and accessing other services on behalf of the user.
+///
+pub struct FirefoxAccount {
+    // For now, we serialize all access on a single `Mutex` for thread safety across
+    // the FFI. We should make the locking more granular in future.
+    internal: Mutex<internal::FirefoxAccount>,
+}
+
+impl FirefoxAccount {
+    /// Create a new [`FirefoxAccount`] instance, not connected to any account.
+    ///
+    /// **💾 This method alters the persisted account state.**
+    ///
+    /// This method constructs as new [`FirefoxAccount`] instance configured to connect
+    /// the application to a user's account.
+    pub fn new(config: FxaConfig) -> FirefoxAccount {
+        FirefoxAccount {
+            internal: Mutex::new(internal::FirefoxAccount::new(config)),
+        }
+    }
+}
+
+#[derive(Clone, Debug)]
+pub struct FxaConfig {
+    /// FxaServer to connect with
+    pub server: FxaServer,
+    /// registered OAuth client id of the application.
+    pub client_id: String,
+    /// `redirect_uri` - the registered OAuth redirect URI of the application.
+    pub redirect_uri: String,
+    ///  URL for the user's Sync Tokenserver. This can be used to support users who self-host their
+    ///  sync data. If `None` then it will default to the Mozilla-hosted Sync server.
+    ///
+    ///  Note: this lives here for historical reasons, but probably shouldn't.  Applications pass
+    ///  the token server URL they get from `fxa-client` to `SyncManager`.  It would be simpler to
+    ///  cut out `fxa-client` out of the middle and have applications send the overridden URL
+    ///  directly to `SyncManager`.
+    pub token_server_url_override: Option<String>,
+}
+
+#[derive(Clone, Debug)]
+pub enum FxaServer {
+    Release,
+    Stable,
+    Stage,
+    China,
+    LocalDev,
+    Custom { url: String },
+}
+
+impl FxaServer {
+    fn content_url(&self) -> &str {
+        match self {
+            Self::Release => "https://accounts.firefox.com",
+            Self::Stable => "https://stable.dev.lcip.org",
+            Self::Stage => "https://accounts.stage.mozaws.net",
+            Self::China => "https://accounts.firefox.com.cn",
+            Self::LocalDev => "http://127.0.0.1:3030",
+            Self::Custom { url } => url,
+        }
+    }
+}
+
+impl FxaConfig {
+    pub fn release(client_id: impl ToString, redirect_uri: impl ToString) -> Self {
+        Self {
+            server: FxaServer::Release,
+            client_id: client_id.to_string(),
+            redirect_uri: redirect_uri.to_string(),
+            token_server_url_override: None,
+        }
+    }
+
+    pub fn stable(client_id: impl ToString, redirect_uri: impl ToString) -> Self {
+        Self {
+            server: FxaServer::Stable,
+            client_id: client_id.to_string(),
+            redirect_uri: redirect_uri.to_string(),
+            token_server_url_override: None,
+        }
+    }
+
+    pub fn stage(client_id: impl ToString, redirect_uri: impl ToString) -> Self {
+        Self {
+            server: FxaServer::Stage,
+            client_id: client_id.to_string(),
+            redirect_uri: redirect_uri.to_string(),
+            token_server_url_override: None,
+        }
+    }
+
+    pub fn china(client_id: impl ToString, redirect_uri: impl ToString) -> Self {
+        Self {
+            server: FxaServer::China,
+            client_id: client_id.to_string(),
+            redirect_uri: redirect_uri.to_string(),
+            token_server_url_override: None,
+        }
+    }
+
+    pub fn dev(client_id: impl ToString, redirect_uri: impl ToString) -> Self {
+        Self {
+            server: FxaServer::LocalDev,
+            client_id: client_id.to_string(),
+            redirect_uri: redirect_uri.to_string(),
+            token_server_url_override: None,
+        }
+    }
+}
+
+uniffi::include_scaffolding!("fxa_client");
+
\ No newline at end of file diff --git a/book/rust-docs/src/fxa_client/profile.rs.html b/book/rust-docs/src/fxa_client/profile.rs.html new file mode 100644 index 0000000000..ca9893ec2d --- /dev/null +++ b/book/rust-docs/src/fxa_client/profile.rs.html @@ -0,0 +1,125 @@ +profile.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! # User Profile info
+//!
+//! These methods can be used to find out information about the connected user.
+
+use crate::{ApiResult, Error, FirefoxAccount};
+use error_support::handle_error;
+
+impl FirefoxAccount {
+    /// Get profile information for the signed-in user, if any.
+    ///
+    /// **💾 This method alters the persisted account state.**
+    ///
+    /// This method fetches a [`Profile`] struct with information about the currently-signed-in
+    /// user, either by using locally-cached profile information or by fetching fresh data from
+    /// the server.
+    ///
+    /// # Arguments
+    ///
+    ///    - `ignore_cache` - if true, always hit the server for fresh profile information.
+    ///
+    /// # Notes
+    ///
+    ///    - Profile information is only available to applications that have been
+    ///      granted the `profile` scope.
+    ///    - There is currently no API for fetching cached profile information without
+    ///      potentially hitting the server.
+    ///    - If there is no signed-in user, this method will throw an
+    ///      [`Authentication`](FxaError::Authentication) error.
+    #[handle_error(Error)]
+    pub fn get_profile(&self, ignore_cache: bool) -> ApiResult<Profile> {
+        Ok(self.internal.lock().get_profile(ignore_cache)?.into())
+    }
+}
+
+/// Information about the user that controls a Firefox Account.
+///
+/// This struct represents details about the user themselves, and would typically be
+/// used to customize account-related UI in the browser so that it is personalize
+/// for the current user.
+pub struct Profile {
+    /// The user's account uid
+    ///
+    /// This is an opaque immutable unique identifier for their account.
+    pub uid: String,
+    /// The user's current primary email address.
+    ///
+    /// Note that unlike the `uid` field, the email address may change over time.
+    pub email: String,
+    /// The user's preferred textual display name.
+    pub display_name: Option<String>,
+    /// The URL of a profile picture representing the user.
+    ///
+    /// All accounts have a corresponding profile picture. If the user has not
+    /// provided one then a default image is used.
+    pub avatar: String,
+    /// Whether the `avatar` URL represents the default avatar image.
+    pub is_default_avatar: bool,
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/fxa_client/push.rs.html b/book/rust-docs/src/fxa_client/push.rs.html new file mode 100644 index 0000000000..8e7209c7f2 --- /dev/null +++ b/book/rust-docs/src/fxa_client/push.rs.html @@ -0,0 +1,437 @@ +push.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use error_support::handle_error;
+use serde::{Deserialize, Serialize};
+
+use crate::{internal, ApiResult, Device, Error, FirefoxAccount, LocalDevice};
+
+impl FirefoxAccount {
+    /// Set or update a push subscription endpoint for this device.
+    ///
+    /// **💾 This method alters the persisted account state.**
+    ///
+    /// This method registers the given webpush subscription with the FxA server, requesting
+    /// that is send notifications in the event of any significant changes to the user's
+    /// account. When the application receives a push message at the registered subscription
+    /// endpoint, it should decrypt the payload and pass it to the [`handle_push_message`](
+    /// FirefoxAccount::handle_push_message) method for processing.
+    ///
+    /// # Arguments
+    ///
+    ///    - `subscription` - the [`DevicePushSubscription`] details to register with the server.
+    ///
+    /// # Notes
+    ///
+    ///    - Device registration is only available to applications that have been
+    ///      granted the `https://identity.mozilla.com/apps/oldsync` scope.
+    #[handle_error(Error)]
+    pub fn set_push_subscription(
+        &self,
+        subscription: DevicePushSubscription,
+    ) -> ApiResult<LocalDevice> {
+        self.internal
+            .lock()
+            .set_push_subscription(subscription.into())
+    }
+
+    /// Process and respond to a server-delivered account update message
+    ///
+    /// **💾 This method alters the persisted account state.**
+    ///
+    /// Applications should call this method whenever they receive a push notification from the Firefox Accounts server.
+    /// Such messages typically indicate a noteworthy change of state on the user's account, such as an update to their profile information
+    /// or the disconnection of a client. The [`FirefoxAccount`] struct will update its internal state
+    /// accordingly and return an individual [`AccountEvent`] struct describing the event, which the application
+    /// may use for further processing.
+    ///
+    /// It's important to note if the event is [`AccountEvent::CommandReceived`], the caller should call
+    /// [`FirefoxAccount::poll_device_commands`]
+    #[handle_error(Error)]
+    pub fn handle_push_message(&self, payload: &str) -> ApiResult<AccountEvent> {
+        self.internal.lock().handle_push_message(payload)
+    }
+
+    /// Poll the server for any pending device commands.
+    ///
+    /// **💾 This method alters the persisted account state.**
+    ///
+    /// Applications that have registered one or more [`DeviceCapability`]s with the server can use
+    /// this method to check whether other devices on the account have sent them any commands.
+    /// It will return a list of [`IncomingDeviceCommand`] structs for the application to process.
+    ///
+    /// # Notes
+    ///
+    ///    - Device commands are typically delivered via push message and the [`CommandReceived`](
+    ///      AccountEvent::CommandReceived) event. Polling should only be used as a backup delivery
+    ///      mechanism, f the application has reason to believe that push messages may have been missed.
+    ///    - Device commands functionality is only available to applications that have been
+    ///      granted the `https://identity.mozilla.com/apps/oldsync` scope.
+    #[handle_error(Error)]
+    pub fn poll_device_commands(&self) -> ApiResult<Vec<IncomingDeviceCommand>> {
+        self.internal
+            .lock()
+            .poll_device_commands(internal::device::CommandFetchReason::Poll)?
+            .into_iter()
+            .map(TryFrom::try_from)
+            .collect::<Result<_, _>>()
+    }
+
+    /// Use device commands to send a single tab to another device.
+    ///
+    /// **💾 This method alters the persisted account state.**
+    ///
+    /// If a device on the account has registered the [`SendTab`](DeviceCapability::SendTab)
+    /// capability, this method can be used to send it a tab.
+    ///
+    /// # Notes
+    ///
+    ///    - If the given device id does not existing or is not capable of receiving tabs,
+    ///      this method will throw an [`Other`](FxaError::Other) error.
+    ///        - (Yeah...sorry. This should be changed to do something better.)
+    ///    - It is not currently possible to send a full [`SendTabPayload`] to another device,
+    ///      but that's purely an API limitation that should go away in future.
+    ///    - Device commands functionality is only available to applications that have been
+    ///      granted the `https://identity.mozilla.com/apps/oldsync` scope.
+    #[handle_error(Error)]
+    pub fn send_single_tab(&self, target_device_id: &str, title: &str, url: &str) -> ApiResult<()> {
+        self.internal
+            .lock()
+            .send_single_tab(target_device_id, title, url)
+    }
+}
+
+/// Details of a web-push subscription endpoint.
+///
+/// This struct encapsulates the details of a web-push subscription endpoint,
+/// including all the information necessary to send a notification to its owner.
+/// Devices attached to the user's account may register one of these in order
+/// to receive timely updates about account-related events.
+///
+/// Managing a web-push subscription is outside of the scope of this component.
+///
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct DevicePushSubscription {
+    pub endpoint: String,
+    pub public_key: String,
+    pub auth_key: String,
+}
+
+/// An event that happened on the user's account.
+///
+/// If the application has registered a [`DevicePushSubscription`] as part of its
+/// device record, then the Firefox Accounts server can send push notifications
+/// about important events that happen on the user's account. This enum represents
+/// the different kinds of event that can occur.
+///
+// Clippy suggests we Box<> the CommandReceiver variant here,
+// but UniFFI isn't able to look through boxes yet, so we
+// disable the warning.
+#[allow(clippy::large_enum_variant)]
+#[derive(Debug)]
+pub enum AccountEvent {
+    /// Sent when another device has invoked a command for this device to execute.
+    ///
+    /// When receiving this event, the application should inspect the contained
+    /// command and react appropriately.
+    CommandReceived { command: IncomingDeviceCommand },
+    /// Sent when the user has modified their account profile information.
+    ///
+    /// When receiving this event, the application should request fresh profile
+    /// information by calling [`get_profile`](FirefoxAccount::get_profile) with
+    /// `ignore_cache` set to true, and update any profile information displayed
+    /// in its UI.
+    ///
+    ProfileUpdated,
+    /// Sent when when there has been a change in authorization status.
+    ///
+    /// When receiving this event, the application should check whether it is
+    /// still connected to the user's account by calling [`check_authorization_status`](
+    /// FirefoxAccount::check_authorization_status), and updating its UI as appropriate.
+    ///
+    AccountAuthStateChanged,
+    /// Sent when the user deletes their Firefox Account.
+    ///
+    /// When receiving this event, the application should act as though the user had
+    /// signed out, discarding any persisted account state.
+    AccountDestroyed,
+    /// Sent when a new device connects to the user's account.
+    ///
+    /// When receiving this event, the application may use it to trigger an update
+    /// of any UI that shows the list of connected devices. It may also show the
+    /// user an informational notice about the new device, as a security measure.
+    DeviceConnected { device_name: String },
+    /// Sent when a device disconnects from the user's account.
+    ///
+    /// When receiving this event, the application may use it to trigger an update
+    /// of any UI that shows the list of connected devices.
+    DeviceDisconnected {
+        device_id: String,
+        is_local_device: bool,
+    },
+
+    /// An unknown event, most likely an event the client doesn't support yet.
+    ///
+    /// When receiving this event, the application should gracefully ignore it.
+    Unknown,
+}
+
+/// A command invoked by another device.
+///
+/// This enum represents all possible commands that can be invoked on
+/// the device. It is the responsibility of the application to interpret
+/// each command.
+#[derive(Debug)]
+pub enum IncomingDeviceCommand {
+    /// Indicates that a tab has been sent to this device.
+    TabReceived {
+        sender: Option<Device>,
+        payload: SendTabPayload,
+    },
+}
+
+/// The payload sent when invoking a "send tab" command.
+#[derive(Debug)]
+pub struct SendTabPayload {
+    /// The navigation history of the sent tab.
+    ///
+    /// The last item in this list represents the page to be displayed,
+    /// while earlier items may be included in the navigation history
+    /// as a convenience to the user.
+    pub entries: Vec<TabHistoryEntry>,
+    /// A unique identifier to be included in send-tab metrics.
+    ///
+    /// The application should treat this as opaque.
+    pub flow_id: String,
+    /// A unique identifier to be included in send-tab metrics.
+    ///
+    /// The application should treat this as opaque.
+    pub stream_id: String,
+}
+
+/// An individual entry in the navigation history of a sent tab.
+#[derive(Debug)]
+pub struct TabHistoryEntry {
+    pub title: String,
+    pub url: String,
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/fxa_client/storage.rs.html b/book/rust-docs/src/fxa_client/storage.rs.html new file mode 100644 index 0000000000..640918ad3a --- /dev/null +++ b/book/rust-docs/src/fxa_client/storage.rs.html @@ -0,0 +1,113 @@ +storage.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! # State management
+//!
+//! These are methods for managing the signed-in state of the application,
+//! either by restoring a previously-saved state via [`FirefoxAccount::from_json`]
+//! or by starting afresh with [`FirefoxAccount::new`].
+//!
+//! The application must persist the signed-in state after calling any methods
+//! that may alter it. Such methods are marked in the documentation as follows:
+//!
+//! **💾 This method alters the persisted account state.**
+//!
+//! After calling any such method, use [`FirefoxAccount::to_json`] to serialize
+//! the modified account state and persist the resulting string in application
+//! settings.
+
+use crate::{internal, ApiResult, Error, FirefoxAccount};
+use error_support::handle_error;
+use parking_lot::Mutex;
+
+impl FirefoxAccount {
+    /// Restore a [`FirefoxAccount`] instance from serialized state.
+    ///
+    /// Given a JSON string previously obtained from [`FirefoxAccount::to_json`], this
+    /// method will deserialize it and return a live [`FirefoxAccount`] instance.
+    ///
+    /// **⚠️ Warning:** since the serialized state contains access tokens, you should
+    /// not call `from_json` multiple times on the same data. This would result
+    /// in multiple live objects sharing the same access tokens and is likely to
+    /// produce unexpected behaviour.
+    #[handle_error(Error)]
+    pub fn from_json(data: &str) -> ApiResult<FirefoxAccount> {
+        Ok(FirefoxAccount {
+            internal: Mutex::new(internal::FirefoxAccount::from_json(data)?),
+        })
+    }
+
+    /// Save current state to a JSON string.
+    ///
+    /// This method serializes the current account state into a JSON string, which
+    /// the application can use to persist the user's signed-in state across restarts.
+    /// The application should call this method and update its persisted state after
+    /// any potentially-state-changing operation.
+    ///
+    /// **⚠️ Warning:** the serialized state may contain encryption keys and access
+    /// tokens that let anyone holding them access the user's data in Firefox Sync
+    /// and/or other FxA services. Applications should take care to store the resulting
+    /// data in a secure fashion, as appropriate for their target platform.
+    #[handle_error(Error)]
+    pub fn to_json(&self) -> ApiResult<String> {
+        self.internal.lock().to_json()
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/fxa_client/telemetry.rs.html b/book/rust-docs/src/fxa_client/telemetry.rs.html new file mode 100644 index 0000000000..b92759227b --- /dev/null +++ b/book/rust-docs/src/fxa_client/telemetry.rs.html @@ -0,0 +1,57 @@ +telemetry.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! # Telemetry Methods
+//!
+//! This component does not currently submit telemetry via Glean, but it *does* gather
+//! a small amount of telemetry about send-tab that the application may submit on its
+//! behalf.
+
+use crate::{ApiResult, Error, FirefoxAccount};
+use error_support::handle_error;
+
+impl FirefoxAccount {
+    /// Collect and return telemetry about send-tab attempts.
+    ///
+    /// Applications that register the [`SendTab`](DeviceCapability::SendTab) capability
+    /// should also arrange to submit "sync ping" telemetry. Calling this method will
+    /// return a JSON string of telemetry data that can be incorporated into that ping.
+    ///
+    /// Sorry, this is not particularly carefully documented because it is intended
+    /// as a stop-gap until we get native Glean support. If you know how to submit
+    /// a sync ping, you'll know what to do with the contents of the JSON string.
+    #[handle_error(Error)]
+    pub fn gather_telemetry(&self) -> ApiResult<String> {
+        self.internal.lock().gather_telemetry()
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/fxa_client/token.rs.html b/book/rust-docs/src/fxa_client/token.rs.html new file mode 100644 index 0000000000..58d7b1e70d --- /dev/null +++ b/book/rust-docs/src/fxa_client/token.rs.html @@ -0,0 +1,401 @@ +token.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! # Token Management
+//!
+//! A signed-in application will typically hold a number of different *tokens* associated with the
+//! user's account, including:
+//!
+//!    - An OAuth `refresh_token`, representing their ongoing connection to the account
+//!      and the scopes that have been granted.
+//!    - Short-lived OAuth `access_token`s that can be used to access resources on behalf
+//!      of the user.
+//!    - Optionally, a `session_token` that gives full control over the user's account,
+//!      typically managed on behalf of web content that runs within the context
+//!      of the application.
+
+use crate::{ApiResult, Error, FirefoxAccount};
+use error_support::handle_error;
+use serde_derive::*;
+use std::convert::{TryFrom, TryInto};
+
+impl FirefoxAccount {
+    /// Get a short-lived OAuth access token for the user's account.
+    ///
+    /// **💾 This method alters the persisted account state.**
+    ///
+    /// Applications that need to access resources on behalf of the user must obtain an
+    /// `access_token` in order to do so. For example, an access token is required when
+    /// fetching the user's profile data, or when accessing their data stored in Firefox Sync.
+    ///
+    /// This method will obtain and return an access token bearing the requested scopes, either
+    /// from a local cache of previously-issued tokens, or by creating a new one from the server.
+    ///
+    /// # Arguments
+    ///
+    ///    - `scope` - the OAuth scope to be granted by the token.
+    ///        - This must be one of the scopes requested during the signin flow.
+    ///        - Only a single scope is supported; for multiple scopes request multiple tokens.
+    ///    - `ttl` - optionally, the time for which the token should be valid, in seconds.
+    ///
+    /// # Notes
+    ///
+    ///    - If the application receives an authorization error when trying to use the resulting
+    ///      token, it should call [`clear_access_token_cache`](FirefoxAccount::clear_access_token_cache)
+    ///      before requesting a fresh token.
+    #[handle_error(Error)]
+    pub fn get_access_token(&self, scope: &str, ttl: Option<i64>) -> ApiResult<AccessTokenInfo> {
+        // Signedness converstion for Kotlin compatibility :-/
+        let ttl = ttl.map(|ttl| u64::try_from(ttl).unwrap_or_default());
+        self.internal
+            .lock()
+            .get_access_token(scope, ttl)?
+            .try_into()
+    }
+
+    /// Get the session token for the user's account, if one is available.
+    ///
+    /// **💾 This method alters the persisted account state.**
+    ///
+    /// Applications that function as a web browser may need to hold on to a session token
+    /// on behalf of Firefox Accounts web content. This method exists so that they can retreive
+    /// it an pass it back to said web content when required.
+    ///
+    /// # Notes
+    ///
+    ///    - Please do not attempt to use the resulting token to directly make calls to the
+    ///      Firefox Accounts servers! All account management functionality should be performed
+    ///      in web content.
+    ///    - A session token is only available to applications that have requested the
+    ///      `https://identity.mozilla.com/tokens/session` scope.
+    #[handle_error(Error)]
+    pub fn get_session_token(&self) -> ApiResult<String> {
+        self.internal.lock().get_session_token()
+    }
+
+    /// Update the stored session token for the user's account.
+    ///
+    /// **💾 This method alters the persisted account state.**
+    ///
+    /// Applications that function as a web browser may need to hold on to a session token
+    /// on behalf of Firefox Accounts web content. This method exists so that said web content
+    /// signals that it has generated a new session token, the stored value can be updated
+    /// to match.
+    ///
+    /// # Arguments
+    ///
+    ///    - `session_token` - the new session token value provided from web content.
+    #[handle_error(Error)]
+    pub fn handle_session_token_change(&self, session_token: &str) -> ApiResult<()> {
+        self.internal
+            .lock()
+            .handle_session_token_change(session_token)
+    }
+
+    /// Create a new OAuth authorization code using the stored session token.
+    ///
+    /// When a signed-in application receives an incoming device pairing request, it can
+    /// use this method to grant the request and generate a corresponding OAuth authorization
+    /// code. This code would then be passed back to the connecting device over the
+    /// pairing channel (a process which is not currently supported by any code in this
+    /// component).
+    ///
+    /// # Arguments
+    ///
+    ///    - `params` - the OAuth parameters from the incoming authorization request
+    #[handle_error(Error)]
+    pub fn authorize_code_using_session_token(
+        &self,
+        params: AuthorizationParameters,
+    ) -> ApiResult<String> {
+        self.internal
+            .lock()
+            .authorize_code_using_session_token(params)
+    }
+
+    /// Clear the access token cache in response to an auth failure.
+    ///
+    /// **💾 This method alters the persisted account state.**
+    ///
+    /// Applications that receive an authentication error when trying to use an access token,
+    /// should call this method before creating a new token and retrying the failed operation.
+    /// It ensures that the expired token is removed and a fresh one generated.
+    pub fn clear_access_token_cache(&self) {
+        self.internal.lock().clear_access_token_cache()
+    }
+}
+
+/// An OAuth access token, with its associated keys and metadata.
+///
+/// This struct represents an FxA OAuth access token, which can be used to access a resource
+/// or service on behalf of the user. For example, accessing the user's data in Firefox Sync
+/// an access token for the scope `https://identity.mozilla.com/apps/sync` along with the
+/// associated encryption key.
+#[derive(Debug)]
+pub struct AccessTokenInfo {
+    /// The scope of access granted by token.
+    pub scope: String,
+    /// The access token itself.
+    ///
+    /// This is the value that should be included in the `Authorization` header when
+    /// accessing an OAuth protected resource on behalf of the user.
+    pub token: String,
+    /// The client-side encryption key associated with this scope.
+    ///
+    /// **⚠️ Warning:** the value of this field should never be revealed outside of the
+    /// application. For example, it should never to sent to a server or logged in a log file.
+    pub key: Option<ScopedKey>,
+    /// The expiry time of the token, in seconds.
+    ///
+    /// This is the timestamp at which the token is set to expire, in seconds since
+    /// unix epoch. Note that it is a signed integer, for compatibility with languages
+    /// that do not have an unsigned integer type.
+    ///
+    /// This timestamp is for guidance only. Access tokens are not guaranteed to remain
+    /// value for any particular lengthof time, and consumers should be prepared to handle
+    /// auth failures even if the token has not yet expired.
+    pub expires_at: i64,
+}
+
+/// A cryptograpic key associated with an OAuth scope.
+///
+/// Some OAuth scopes have a corresponding client-side encryption key that is required
+/// in order to access protected data. This struct represents such key material in a
+/// format compatible with the common "JWK" standard.
+///
+#[derive(Clone, Serialize, Deserialize)]
+pub struct ScopedKey {
+    /// The type of key.
+    ///
+    /// In practice for FxA, this will always be string string "oct" (short for "octal")
+    /// to represent a raw symmetric key.
+    pub kty: String,
+    /// The OAuth scope with which this key is associated.
+    pub scope: String,
+    /// The key material, as base64-url-encoded bytes.
+    ///
+    /// **⚠️ Warning:** the value of this field should never be revealed outside of the
+    /// application. For example, it should never to sent to a server or logged in a log file.
+    pub k: String,
+    /// An opaque unique identifier for this key.
+    ///
+    /// Unlike the `k` field, this value is not secret and may be revealed to the server.
+    pub kid: String,
+}
+
+/// Parameters provided in an incoming OAuth request.
+///
+/// This struct represents parameters obtained from an incoming OAuth request - that is,
+/// the values that an OAuth client would append to the authorization URL when initiating
+/// an OAuth sign-in flow.
+pub struct AuthorizationParameters {
+    pub client_id: String,
+    pub scope: Vec<String>,
+    pub state: String,
+    pub access_type: String,
+    pub code_challenge: Option<String>,
+    pub code_challenge_method: Option<String>,
+    pub keys_jwk: Option<String>,
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/interrupt_support/error.rs.html b/book/rust-docs/src/interrupt_support/error.rs.html new file mode 100644 index 0000000000..768f9c193d --- /dev/null +++ b/book/rust-docs/src/interrupt_support/error.rs.html @@ -0,0 +1,31 @@ +error.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/// The error returned by err_if_interrupted.
+#[derive(Debug, Clone)]
+pub struct Interrupted;
+
+impl std::fmt::Display for Interrupted {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.write_str("The operation was interrupted")
+    }
+}
+
+impl std::error::Error for Interrupted {}
+
\ No newline at end of file diff --git a/book/rust-docs/src/interrupt_support/interruptee.rs.html b/book/rust-docs/src/interrupt_support/interruptee.rs.html new file mode 100644 index 0000000000..c4fcf8db51 --- /dev/null +++ b/book/rust-docs/src/interrupt_support/interruptee.rs.html @@ -0,0 +1,59 @@ +interruptee.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::Interrupted;
+
+/// Represents the state of something that may be interrupted. Decoupled from
+/// the interrupt mechanics so that things which want to check if they have been
+/// interrupted are simpler.
+pub trait Interruptee {
+    fn was_interrupted(&self) -> bool;
+
+    fn err_if_interrupted(&self) -> Result<(), Interrupted> {
+        if self.was_interrupted() {
+            return Err(Interrupted);
+        }
+        Ok(())
+    }
+}
+
+/// A convenience implementation, should only be used in tests.
+pub struct NeverInterrupts;
+
+impl Interruptee for NeverInterrupts {
+    #[inline]
+    fn was_interrupted(&self) -> bool {
+        false
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/interrupt_support/lib.rs.html b/book/rust-docs/src/interrupt_support/lib.rs.html new file mode 100644 index 0000000000..5c8f1b8159 --- /dev/null +++ b/book/rust-docs/src/interrupt_support/lib.rs.html @@ -0,0 +1,33 @@ +lib.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#![allow(unknown_lints)]
+#![warn(rust_2018_idioms)]
+
+mod error;
+mod interruptee;
+mod shutdown;
+mod sql;
+
+pub use error::Interrupted;
+pub use interruptee::*;
+pub use shutdown::*;
+pub use sql::*;
+
\ No newline at end of file diff --git a/book/rust-docs/src/interrupt_support/shutdown.rs.html b/book/rust-docs/src/interrupt_support/shutdown.rs.html new file mode 100644 index 0000000000..0500f8089e --- /dev/null +++ b/book/rust-docs/src/interrupt_support/shutdown.rs.html @@ -0,0 +1,163 @@ +shutdown.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/// Shutdown handling for database operations
+///
+/// This module allows us to enter shutdown mode, causing all `SqlInterruptScope` instances that opt-in to
+/// to be permanently interrupted.  This means:
+///
+///   - All current scopes will be interrupted
+///   - Any attempt to create a new scope will be interrupted
+///
+/// Here's how add shutdown support to a component:
+///
+///   - Use `SqlInterruptScope::new_with_shutdown_check()` to create a new
+///     `SqlInterruptScope`
+///   - Database connections need to be wrapped in a type that:
+///      - Implements `AsRef<SqlInterruptHandle>`.
+///      - Gets wrapped in an `Arc<>`.  This is needed so the shutdown code can get a weak reference to
+///        the instance.
+///      - Calls `register_interrupt()` on creation
+///
+///  See `PlacesDb::begin_interrupt_scope()` and `PlacesApi::new_connection()` for an example of
+///  how this works.
+use crate::Interruptee;
+use parking_lot::Mutex;
+use std::sync::atomic::{AtomicBool, Ordering};
+use std::sync::Weak;
+
+use crate::SqlInterruptHandle;
+
+// Bool that tracks if we're in shutdown mode or not.  We use Ordering::Relaxed to read/write to
+// variable.  It's just a flag so we don't need stronger synchronization guarentees.
+static IN_SHUTDOWN: AtomicBool = AtomicBool::new(false);
+
+// `SqlInterruptHandle` instances to interrupt when we shutdown
+lazy_static::lazy_static! {
+   static ref REGISTERED_INTERRUPTS: Mutex<Vec<Weak<dyn AsRef<SqlInterruptHandle> + Send + Sync>>> = Mutex::new(Vec::new());
+}
+
+/// Initiate shutdown mode
+pub fn shutdown() {
+    IN_SHUTDOWN.store(true, Ordering::Relaxed);
+    for weak in REGISTERED_INTERRUPTS.lock().iter() {
+        if let Some(interrupt) = weak.upgrade() {
+            interrupt.as_ref().as_ref().interrupt()
+        }
+    }
+}
+
+/// Check if we're currently in shutdown mode
+pub fn in_shutdown() -> bool {
+    IN_SHUTDOWN.load(Ordering::Relaxed)
+}
+
+/// Register a ShutdownInterrupt implementation
+///
+/// Call this function to ensure that the `SqlInterruptHandle::interrupt()` method will be called
+/// at shutdown.
+pub fn register_interrupt(interrupt: Weak<dyn AsRef<SqlInterruptHandle> + Send + Sync>) {
+    // Try to find an existing entry that's been dropped to replace.  This keeps the vector growth
+    // in check
+    let mut interrupts = REGISTERED_INTERRUPTS.lock();
+    for weak in interrupts.iter_mut() {
+        if weak.strong_count() == 0 {
+            *weak = interrupt;
+            return;
+        }
+    }
+    // No empty slots, push the new value
+    interrupts.push(interrupt);
+}
+
+// Implements Interruptee by checking if we've entered shutdown mode
+pub struct ShutdownInterruptee;
+impl Interruptee for ShutdownInterruptee {
+    #[inline]
+    fn was_interrupted(&self) -> bool {
+        in_shutdown()
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/interrupt_support/sql.rs.html b/book/rust-docs/src/interrupt_support/sql.rs.html new file mode 100644 index 0000000000..956fc8891c --- /dev/null +++ b/book/rust-docs/src/interrupt_support/sql.rs.html @@ -0,0 +1,245 @@ +sql.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::{in_shutdown, Interrupted, Interruptee};
+use rusqlite::{Connection, InterruptHandle};
+use std::fmt;
+use std::sync::{
+    atomic::{AtomicUsize, Ordering},
+    Arc,
+};
+
+/// Interrupt operations that use SQL
+///
+/// Typical usage of this type:
+///   - Components typically create a wrapper class around an `rusqlite::Connection`
+///     (`PlacesConnection`, `LoginStore`, etc.)
+///   - The wrapper stores an `Arc<SqlInterruptHandle>`
+///   - The wrapper has a method that clones and returns that `Arc`.  This allows passing the interrupt
+///     handle to a different thread in order to interrupt a particular operation.
+///   - The wrapper calls `begin_interrupt_scope()` at the start of each operation.  The code that
+///     performs the operation periodically calls `err_if_interrupted()`.
+///   - Finally, the wrapper class implements `AsRef<SqlInterruptHandle>` and calls
+///     `register_interrupt()`.  This causes all operations to be interrupted when we enter
+///     shutdown mode.
+pub struct SqlInterruptHandle {
+    db_handle: InterruptHandle,
+    // Counter that we increment on each interrupt() call.
+    // We use Ordering::Relaxed to read/write to this variable.  This is safe because we're
+    // basically using it as a flag and don't need stronger synchronization guarentees.
+    interrupt_counter: Arc<AtomicUsize>,
+}
+
+impl SqlInterruptHandle {
+    #[inline]
+    pub fn new(conn: &Connection) -> Self {
+        Self {
+            db_handle: conn.get_interrupt_handle(),
+            interrupt_counter: Arc::new(AtomicUsize::new(0)),
+        }
+    }
+
+    /// Begin an interrupt scope that will be interrupted by this handle
+    ///
+    /// Returns Err(Interrupted) if we're in shutdown mode
+    #[inline]
+    pub fn begin_interrupt_scope(&self) -> Result<SqlInterruptScope, Interrupted> {
+        if in_shutdown() {
+            Err(Interrupted)
+        } else {
+            Ok(SqlInterruptScope::new(Arc::clone(&self.interrupt_counter)))
+        }
+    }
+
+    /// Interrupt all interrupt scopes created by this handle
+    #[inline]
+    pub fn interrupt(&self) {
+        self.interrupt_counter.fetch_add(1, Ordering::Relaxed);
+        self.db_handle.interrupt();
+    }
+}
+
+impl fmt::Debug for SqlInterruptHandle {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.debug_struct("SqlInterruptHandle")
+            .field(
+                "interrupt_counter",
+                &self.interrupt_counter.load(Ordering::Relaxed),
+            )
+            .finish()
+    }
+}
+
+/// Check if an operation has been interrupted
+///
+/// This is used by the rust code to check if an operation should fail because it was interrupted.
+/// It handles the case where we get interrupted outside of an SQL query.
+#[derive(Debug)]
+pub struct SqlInterruptScope {
+    start_value: usize,
+    interrupt_counter: Arc<AtomicUsize>,
+}
+
+impl SqlInterruptScope {
+    fn new(interrupt_counter: Arc<AtomicUsize>) -> Self {
+        let start_value = interrupt_counter.load(Ordering::Relaxed);
+        Self {
+            start_value,
+            interrupt_counter,
+        }
+    }
+
+    // Create an `SqlInterruptScope` that's never interrupted.
+    //
+    // This should only be used for testing purposes.
+    pub fn dummy() -> Self {
+        Self::new(Arc::new(AtomicUsize::new(0)))
+    }
+
+    /// Check if scope has been interrupted
+    #[inline]
+    pub fn was_interrupted(&self) -> bool {
+        self.interrupt_counter.load(Ordering::Relaxed) != self.start_value
+    }
+
+    /// Return Err(Interrupted) if we were interrupted
+    #[inline]
+    pub fn err_if_interrupted(&self) -> Result<(), Interrupted> {
+        if self.was_interrupted() {
+            Err(Interrupted)
+        } else {
+            Ok(())
+        }
+    }
+}
+
+impl Interruptee for SqlInterruptScope {
+    #[inline]
+    fn was_interrupted(&self) -> bool {
+        self.was_interrupted()
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/logins/db.rs.html b/book/rust-docs/src/logins/db.rs.html new file mode 100644 index 0000000000..d5f60716bd --- /dev/null +++ b/book/rust-docs/src/logins/db.rs.html @@ -0,0 +1,2927 @@ +db.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
+540
+541
+542
+543
+544
+545
+546
+547
+548
+549
+550
+551
+552
+553
+554
+555
+556
+557
+558
+559
+560
+561
+562
+563
+564
+565
+566
+567
+568
+569
+570
+571
+572
+573
+574
+575
+576
+577
+578
+579
+580
+581
+582
+583
+584
+585
+586
+587
+588
+589
+590
+591
+592
+593
+594
+595
+596
+597
+598
+599
+600
+601
+602
+603
+604
+605
+606
+607
+608
+609
+610
+611
+612
+613
+614
+615
+616
+617
+618
+619
+620
+621
+622
+623
+624
+625
+626
+627
+628
+629
+630
+631
+632
+633
+634
+635
+636
+637
+638
+639
+640
+641
+642
+643
+644
+645
+646
+647
+648
+649
+650
+651
+652
+653
+654
+655
+656
+657
+658
+659
+660
+661
+662
+663
+664
+665
+666
+667
+668
+669
+670
+671
+672
+673
+674
+675
+676
+677
+678
+679
+680
+681
+682
+683
+684
+685
+686
+687
+688
+689
+690
+691
+692
+693
+694
+695
+696
+697
+698
+699
+700
+701
+702
+703
+704
+705
+706
+707
+708
+709
+710
+711
+712
+713
+714
+715
+716
+717
+718
+719
+720
+721
+722
+723
+724
+725
+726
+727
+728
+729
+730
+731
+732
+733
+734
+735
+736
+737
+738
+739
+740
+741
+742
+743
+744
+745
+746
+747
+748
+749
+750
+751
+752
+753
+754
+755
+756
+757
+758
+759
+760
+761
+762
+763
+764
+765
+766
+767
+768
+769
+770
+771
+772
+773
+774
+775
+776
+777
+778
+779
+780
+781
+782
+783
+784
+785
+786
+787
+788
+789
+790
+791
+792
+793
+794
+795
+796
+797
+798
+799
+800
+801
+802
+803
+804
+805
+806
+807
+808
+809
+810
+811
+812
+813
+814
+815
+816
+817
+818
+819
+820
+821
+822
+823
+824
+825
+826
+827
+828
+829
+830
+831
+832
+833
+834
+835
+836
+837
+838
+839
+840
+841
+842
+843
+844
+845
+846
+847
+848
+849
+850
+851
+852
+853
+854
+855
+856
+857
+858
+859
+860
+861
+862
+863
+864
+865
+866
+867
+868
+869
+870
+871
+872
+873
+874
+875
+876
+877
+878
+879
+880
+881
+882
+883
+884
+885
+886
+887
+888
+889
+890
+891
+892
+893
+894
+895
+896
+897
+898
+899
+900
+901
+902
+903
+904
+905
+906
+907
+908
+909
+910
+911
+912
+913
+914
+915
+916
+917
+918
+919
+920
+921
+922
+923
+924
+925
+926
+927
+928
+929
+930
+931
+932
+933
+934
+935
+936
+937
+938
+939
+940
+941
+942
+943
+944
+945
+946
+947
+948
+949
+950
+951
+952
+953
+954
+955
+956
+957
+958
+959
+960
+961
+962
+963
+964
+965
+966
+967
+968
+969
+970
+971
+972
+973
+974
+975
+976
+977
+978
+979
+980
+981
+982
+983
+984
+985
+986
+987
+988
+989
+990
+991
+992
+993
+994
+995
+996
+997
+998
+999
+1000
+1001
+1002
+1003
+1004
+1005
+1006
+1007
+1008
+1009
+1010
+1011
+1012
+1013
+1014
+1015
+1016
+1017
+1018
+1019
+1020
+1021
+1022
+1023
+1024
+1025
+1026
+1027
+1028
+1029
+1030
+1031
+1032
+1033
+1034
+1035
+1036
+1037
+1038
+1039
+1040
+1041
+1042
+1043
+1044
+1045
+1046
+1047
+1048
+1049
+1050
+1051
+1052
+1053
+1054
+1055
+1056
+1057
+1058
+1059
+1060
+1061
+1062
+1063
+1064
+1065
+1066
+1067
+1068
+1069
+1070
+1071
+1072
+1073
+1074
+1075
+1076
+1077
+1078
+1079
+1080
+1081
+1082
+1083
+1084
+1085
+1086
+1087
+1088
+1089
+1090
+1091
+1092
+1093
+1094
+1095
+1096
+1097
+1098
+1099
+1100
+1101
+1102
+1103
+1104
+1105
+1106
+1107
+1108
+1109
+1110
+1111
+1112
+1113
+1114
+1115
+1116
+1117
+1118
+1119
+1120
+1121
+1122
+1123
+1124
+1125
+1126
+1127
+1128
+1129
+1130
+1131
+1132
+1133
+1134
+1135
+1136
+1137
+1138
+1139
+1140
+1141
+1142
+1143
+1144
+1145
+1146
+1147
+1148
+1149
+1150
+1151
+1152
+1153
+1154
+1155
+1156
+1157
+1158
+1159
+1160
+1161
+1162
+1163
+1164
+1165
+1166
+1167
+1168
+1169
+1170
+1171
+1172
+1173
+1174
+1175
+1176
+1177
+1178
+1179
+1180
+1181
+1182
+1183
+1184
+1185
+1186
+1187
+1188
+1189
+1190
+1191
+1192
+1193
+1194
+1195
+1196
+1197
+1198
+1199
+1200
+1201
+1202
+1203
+1204
+1205
+1206
+1207
+1208
+1209
+1210
+1211
+1212
+1213
+1214
+1215
+1216
+1217
+1218
+1219
+1220
+1221
+1222
+1223
+1224
+1225
+1226
+1227
+1228
+1229
+1230
+1231
+1232
+1233
+1234
+1235
+1236
+1237
+1238
+1239
+1240
+1241
+1242
+1243
+1244
+1245
+1246
+1247
+1248
+1249
+1250
+1251
+1252
+1253
+1254
+1255
+1256
+1257
+1258
+1259
+1260
+1261
+1262
+1263
+1264
+1265
+1266
+1267
+1268
+1269
+1270
+1271
+1272
+1273
+1274
+1275
+1276
+1277
+1278
+1279
+1280
+1281
+1282
+1283
+1284
+1285
+1286
+1287
+1288
+1289
+1290
+1291
+1292
+1293
+1294
+1295
+1296
+1297
+1298
+1299
+1300
+1301
+1302
+1303
+1304
+1305
+1306
+1307
+1308
+1309
+1310
+1311
+1312
+1313
+1314
+1315
+1316
+1317
+1318
+1319
+1320
+1321
+1322
+1323
+1324
+1325
+1326
+1327
+1328
+1329
+1330
+1331
+1332
+1333
+1334
+1335
+1336
+1337
+1338
+1339
+1340
+1341
+1342
+1343
+1344
+1345
+1346
+1347
+1348
+1349
+1350
+1351
+1352
+1353
+1354
+1355
+1356
+1357
+1358
+1359
+1360
+1361
+1362
+1363
+1364
+1365
+1366
+1367
+1368
+1369
+1370
+1371
+1372
+1373
+1374
+1375
+1376
+1377
+1378
+1379
+1380
+1381
+1382
+1383
+1384
+1385
+1386
+1387
+1388
+1389
+1390
+1391
+1392
+1393
+1394
+1395
+1396
+1397
+1398
+1399
+1400
+1401
+1402
+1403
+1404
+1405
+1406
+1407
+1408
+1409
+1410
+1411
+1412
+1413
+1414
+1415
+1416
+1417
+1418
+1419
+1420
+1421
+1422
+1423
+1424
+1425
+1426
+1427
+1428
+1429
+1430
+1431
+1432
+1433
+1434
+1435
+1436
+1437
+1438
+1439
+1440
+1441
+1442
+1443
+1444
+1445
+1446
+1447
+1448
+1449
+1450
+1451
+1452
+1453
+1454
+1455
+1456
+1457
+1458
+1459
+1460
+1461
+1462
+1463
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/// Logins DB handling
+///
+/// The logins database works differently than other components because "mirror" and "local" mean
+/// different things.  At some point we should probably refactor to make it match them, but here's
+/// how it works for now:
+///
+///   - loginsM is the mirror table, which means it stores what we believe is on the server.  This
+///     means either the last record we fetched from the server or the last record we uploaded.
+///   - loginsL is the local table, which means it stores local changes that have not been sent to
+///     the server.
+///   - When we want to fetch a record, we need to look in both loginsL and loginsM for the data.
+///     If a record is in both tables, then we prefer the loginsL data.  GET_BY_GUID_SQL contains a
+///     clever UNION query to accomplish this.
+///   - If a record is in both the local and mirror tables, we call the local record the "overlay"
+///     and set the is_overridden flag on the mirror record.
+///   - When we sync, the presence of a record in loginsL means that there was a local change that
+///     we need to send to the the server and/or reconcile it with incoming changes from the
+///     server.
+///   - After we sync, we move all records from loginsL to loginsM, overwriting any previous data.
+///     loginsL will be an empty table after this.  See mark_as_synchronized() for the details.
+use crate::encryption::EncryptorDecryptor;
+use crate::error::*;
+use crate::login::*;
+use crate::schema;
+use crate::sync::SyncStatus;
+use crate::util;
+use interrupt_support::{SqlInterruptHandle, SqlInterruptScope};
+use lazy_static::lazy_static;
+use rusqlite::{
+    named_params,
+    types::{FromSql, ToSql},
+    Connection,
+};
+use sql_support::ConnExt;
+use std::ops::Deref;
+use std::path::Path;
+use std::sync::Arc;
+use std::time::SystemTime;
+use sync_guid::Guid;
+use url::{Host, Url};
+
+pub struct LoginDb {
+    pub db: Connection,
+    interrupt_handle: Arc<SqlInterruptHandle>,
+}
+
+impl LoginDb {
+    pub fn with_connection(db: Connection) -> Result<Self> {
+        #[cfg(test)]
+        {
+            util::init_test_logging();
+        }
+
+        // `temp_store = 2` is required on Android to force the DB to keep temp
+        // files in memory, since on Android there's no tmp partition. See
+        // https://github.com/mozilla/mentat/issues/505. Ideally we'd only
+        // do this on Android, or allow caller to configure it.
+        db.set_pragma("temp_store", 2)?;
+
+        let mut logins = Self {
+            interrupt_handle: Arc::new(SqlInterruptHandle::new(&db)),
+            db,
+        };
+        let tx = logins.db.transaction()?;
+        schema::init(&tx)?;
+        tx.commit()?;
+        Ok(logins)
+    }
+
+    pub fn open(path: impl AsRef<Path>) -> Result<Self> {
+        Self::with_connection(Connection::open(path)?)
+    }
+
+    pub fn open_in_memory() -> Result<Self> {
+        Self::with_connection(Connection::open_in_memory()?)
+    }
+
+    pub fn new_interrupt_handle(&self) -> Arc<SqlInterruptHandle> {
+        Arc::clone(&self.interrupt_handle)
+    }
+
+    #[inline]
+    pub fn begin_interrupt_scope(&self) -> Result<SqlInterruptScope> {
+        Ok(self.interrupt_handle.begin_interrupt_scope()?)
+    }
+}
+
+impl ConnExt for LoginDb {
+    #[inline]
+    fn conn(&self) -> &Connection {
+        &self.db
+    }
+}
+
+impl Deref for LoginDb {
+    type Target = Connection;
+    #[inline]
+    fn deref(&self) -> &Connection {
+        &self.db
+    }
+}
+
+// login specific stuff.
+
+impl LoginDb {
+    pub(crate) fn put_meta(&self, key: &str, value: &dyn ToSql) -> Result<()> {
+        self.execute_cached(
+            "REPLACE INTO loginsSyncMeta (key, value) VALUES (:key, :value)",
+            named_params! { ":key": key, ":value": value },
+        )?;
+        Ok(())
+    }
+
+    pub(crate) fn get_meta<T: FromSql>(&self, key: &str) -> Result<Option<T>> {
+        self.try_query_row(
+            "SELECT value FROM loginsSyncMeta WHERE key = :key",
+            named_params! { ":key": key },
+            |row| Ok::<_, Error>(row.get(0)?),
+            true,
+        )
+    }
+
+    pub(crate) fn delete_meta(&self, key: &str) -> Result<()> {
+        self.execute_cached(
+            "DELETE FROM loginsSyncMeta WHERE key = :key",
+            named_params! { ":key": key },
+        )?;
+        Ok(())
+    }
+
+    pub fn get_all(&self) -> Result<Vec<EncryptedLogin>> {
+        let mut stmt = self.db.prepare_cached(&GET_ALL_SQL)?;
+        let rows = stmt.query_and_then([], EncryptedLogin::from_row)?;
+        rows.collect::<Result<_>>()
+    }
+
+    pub fn get_by_base_domain(&self, base_domain: &str) -> Result<Vec<EncryptedLogin>> {
+        // We first parse the input string as a host so it is normalized.
+        let base_host = match Host::parse(base_domain) {
+            Ok(d) => d,
+            Err(e) => {
+                // don't log the input string as it's PII.
+                log::warn!("get_by_base_domain was passed an invalid domain: {}", e);
+                return Ok(vec![]);
+            }
+        };
+        // We just do a linear scan. Another option is to have an indexed
+        // reverse-host column or similar, but current thinking is that it's
+        // extra complexity for (probably) zero actual benefit given the record
+        // counts are expected to be so low.
+        // A regex would probably make this simpler, but we don't want to drag
+        // in a regex lib just for this.
+        let mut stmt = self.db.prepare_cached(&GET_ALL_SQL)?;
+        let rows = stmt
+            .query_and_then([], EncryptedLogin::from_row)?
+            .filter(|r| {
+                let login = r
+                    .as_ref()
+                    .ok()
+                    .and_then(|login| Url::parse(&login.fields.origin).ok());
+                let this_host = login.as_ref().and_then(|url| url.host());
+                match (&base_host, this_host) {
+                    (Host::Domain(base), Some(Host::Domain(look))) => {
+                        // a fairly long-winded way of saying
+                        // `login.fields.origin == base_domain ||
+                        //  login.fields.origin.ends_with('.' + base_domain);`
+                        let mut rev_input = base.chars().rev();
+                        let mut rev_host = look.chars().rev();
+                        loop {
+                            match (rev_input.next(), rev_host.next()) {
+                                (Some(ref a), Some(ref b)) if a == b => continue,
+                                (None, None) => return true, // exactly equal
+                                (None, Some(ref h)) => return *h == '.',
+                                _ => return false,
+                            }
+                        }
+                    }
+                    // ip addresses must match exactly.
+                    (Host::Ipv4(base), Some(Host::Ipv4(look))) => *base == look,
+                    (Host::Ipv6(base), Some(Host::Ipv6(look))) => *base == look,
+                    // all "mismatches" in domain types are false.
+                    _ => false,
+                }
+            });
+        rows.collect::<Result<_>>()
+    }
+
+    pub fn get_by_id(&self, id: &str) -> Result<Option<EncryptedLogin>> {
+        self.try_query_row(
+            &GET_BY_GUID_SQL,
+            &[(":guid", &id as &dyn ToSql)],
+            EncryptedLogin::from_row,
+            true,
+        )
+    }
+
+    // Match a `LoginEntry` being saved to existing logins in the DB
+    //
+    // When a user is saving new login, there are several cases for how we want to save the data:
+    //
+    //  - Adding a new login: `None` will be returned
+    //  - Updating an existing login: `Some(login)` will be returned and the username will match
+    //    the one for look.
+    //  - Filling in a blank username for an existing login: `Some(login)` will be returned
+    //    with a blank username.
+    //
+    //  Returns an Err if the new login is not valid and could not be fixed up
+    pub fn find_login_to_update(
+        &self,
+        look: LoginEntry,
+        encdec: &EncryptorDecryptor,
+    ) -> Result<Option<Login>> {
+        let look = look.fixup()?;
+        let logins = self
+            .get_by_entry_target(&look)?
+            .into_iter()
+            .map(|enc_login| enc_login.decrypt(encdec))
+            .collect::<Result<Vec<Login>>>()?;
+        Ok(logins
+            // First, try to match the username
+            .iter()
+            .find(|login| login.sec_fields.username == look.sec_fields.username)
+            // Fall back on a blank username
+            .or_else(|| {
+                logins
+                    .iter()
+                    .find(|login| login.sec_fields.username.is_empty())
+            })
+            // Clone the login to avoid ref issues when returning across the FFI
+            .cloned())
+    }
+
+    pub fn touch(&self, id: &str) -> Result<()> {
+        let tx = self.unchecked_transaction()?;
+        self.ensure_local_overlay_exists(id)?;
+        self.mark_mirror_overridden(id)?;
+        let now_ms = util::system_time_ms_i64(SystemTime::now());
+        // As on iOS, just using a record doesn't flip it's status to changed.
+        // TODO: this might be wrong for lockbox!
+        self.execute_cached(
+            "UPDATE loginsL
+             SET timeLastUsed = :now_millis,
+                 timesUsed = timesUsed + 1,
+                 local_modified = :now_millis
+             WHERE guid = :guid
+                 AND is_deleted = 0",
+            named_params! {
+                ":now_millis": now_ms,
+                ":guid": id,
+            },
+        )?;
+        tx.commit()?;
+        Ok(())
+    }
+
+    // The single place we insert new rows or update existing local rows.
+    // just the SQL - no validation or anything.
+    fn insert_new_login(&self, login: &EncryptedLogin) -> Result<()> {
+        let sql = format!(
+            "INSERT INTO loginsL (
+                origin,
+                httpRealm,
+                formActionOrigin,
+                usernameField,
+                passwordField,
+                timesUsed,
+                secFields,
+                guid,
+                timeCreated,
+                timeLastUsed,
+                timePasswordChanged,
+                local_modified,
+                is_deleted,
+                sync_status
+            ) VALUES (
+                :origin,
+                :http_realm,
+                :form_action_origin,
+                :username_field,
+                :password_field,
+                :times_used,
+                :sec_fields,
+                :guid,
+                :time_created,
+                :time_last_used,
+                :time_password_changed,
+                :local_modified,
+                0, -- is_deleted
+                {new} -- sync_status
+            )",
+            new = SyncStatus::New as u8
+        );
+
+        self.execute(
+            &sql,
+            named_params! {
+                ":origin": login.fields.origin,
+                ":http_realm": login.fields.http_realm,
+                ":form_action_origin": login.fields.form_action_origin,
+                ":username_field": login.fields.username_field,
+                ":password_field": login.fields.password_field,
+                ":time_created": login.record.time_created,
+                ":times_used": login.record.times_used,
+                ":time_last_used": login.record.time_last_used,
+                ":time_password_changed": login.record.time_password_changed,
+                ":local_modified": login.record.time_created,
+                ":sec_fields": login.sec_fields,
+                ":guid": login.guid(),
+            },
+        )?;
+        Ok(())
+    }
+
+    fn update_existing_login(&self, login: &EncryptedLogin) -> Result<()> {
+        // assumes the "local overlay" exists, so the guid must too.
+        let sql = format!(
+            "UPDATE loginsL
+             SET local_modified      = :now_millis,
+                 timeLastUsed        = :time_last_used,
+                 timePasswordChanged = :time_password_changed,
+                 httpRealm           = :http_realm,
+                 formActionOrigin    = :form_action_origin,
+                 usernameField       = :username_field,
+                 passwordField       = :password_field,
+                 timesUsed           = :times_used,
+                 secFields           = :sec_fields,
+                 origin              = :origin,
+                 -- leave New records as they are, otherwise update them to `changed`
+                 sync_status         = max(sync_status, {changed})
+             WHERE guid = :guid",
+            changed = SyncStatus::Changed as u8
+        );
+
+        self.db.execute(
+            &sql,
+            named_params! {
+                ":origin": login.fields.origin,
+                ":http_realm": login.fields.http_realm,
+                ":form_action_origin": login.fields.form_action_origin,
+                ":username_field": login.fields.username_field,
+                ":password_field": login.fields.password_field,
+                ":time_last_used": login.record.time_last_used,
+                ":times_used": login.record.times_used,
+                ":time_password_changed": login.record.time_password_changed,
+                ":sec_fields": login.sec_fields,
+                ":guid": &login.record.id,
+                // time_last_used has been set to now.
+                ":now_millis": login.record.time_last_used,
+            },
+        )?;
+        Ok(())
+    }
+
+    pub fn add(&self, entry: LoginEntry, encdec: &EncryptorDecryptor) -> Result<EncryptedLogin> {
+        let guid = Guid::random();
+        let now_ms = util::system_time_ms_i64(SystemTime::now());
+
+        let new_entry = self.fixup_and_check_for_dupes(&guid, entry, encdec)?;
+        let result = EncryptedLogin {
+            record: RecordFields {
+                id: guid.to_string(),
+                time_created: now_ms,
+                time_password_changed: now_ms,
+                time_last_used: now_ms,
+                times_used: 1,
+            },
+            fields: new_entry.fields,
+            sec_fields: new_entry.sec_fields.encrypt(encdec)?,
+        };
+        let tx = self.unchecked_transaction()?;
+        self.insert_new_login(&result)?;
+        tx.commit()?;
+        Ok(result)
+    }
+
+    pub fn update(
+        &self,
+        sguid: &str,
+        entry: LoginEntry,
+        encdec: &EncryptorDecryptor,
+    ) -> Result<EncryptedLogin> {
+        let guid = Guid::new(sguid);
+        let now_ms = util::system_time_ms_i64(SystemTime::now());
+        let tx = self.unchecked_transaction()?;
+
+        let entry = entry.fixup()?;
+
+        // Check if there's an existing login that's the dupe of this login.  That indicates that
+        // something has gone wrong with our underlying logic.  However, if we do see a dupe login,
+        // just log an error and continue.  This avoids a crash on android-components
+        // (mozilla-mobile/android-components#11251).
+
+        if self.check_for_dupes(&guid, &entry, encdec).is_err() {
+            // Try to detect if sync is enabled by checking if there are any mirror logins
+            let has_mirror_row: bool =
+                self.db.query_one("SELECT EXISTS (SELECT 1 FROM loginsM)")?;
+            let has_http_realm = entry.fields.http_realm.is_some();
+            let has_form_action_origin = entry.fields.form_action_origin.is_some();
+            report_error!(
+                "logins-duplicate-in-update",
+                "(mirror: {has_mirror_row}, realm: {has_http_realm}, form_origin: {has_form_action_origin})");
+        }
+
+        // Note: This fail with NoSuchRecord if the record doesn't exist.
+        self.ensure_local_overlay_exists(&guid)?;
+        self.mark_mirror_overridden(&guid)?;
+
+        // We must read the existing record so we can correctly manage timePasswordChanged.
+        let existing = match self.get_by_id(sguid)? {
+            Some(e) => e,
+            None => return Err(Error::NoSuchRecord(sguid.to_owned())),
+        };
+        let time_password_changed =
+            if existing.decrypt_fields(encdec)?.password == entry.sec_fields.password {
+                existing.record.time_password_changed
+            } else {
+                now_ms
+            };
+
+        // Make the final object here - every column will be updated.
+        let result = EncryptedLogin {
+            record: RecordFields {
+                id: existing.record.id,
+                time_created: existing.record.time_created,
+                time_password_changed,
+                time_last_used: now_ms,
+                times_used: existing.record.times_used + 1,
+            },
+            fields: entry.fields,
+            sec_fields: entry.sec_fields.encrypt(encdec)?,
+        };
+
+        self.update_existing_login(&result)?;
+        tx.commit()?;
+        Ok(result)
+    }
+
+    pub fn add_or_update(
+        &self,
+        entry: LoginEntry,
+        encdec: &EncryptorDecryptor,
+    ) -> Result<EncryptedLogin> {
+        // Make sure to fixup the entry first, in case that changes the username
+        let entry = entry.fixup()?;
+        match self.find_login_to_update(entry.clone(), encdec)? {
+            Some(login) => self.update(&login.record.id, entry, encdec),
+            None => self.add(entry, encdec),
+        }
+    }
+
+    pub fn fixup_and_check_for_dupes(
+        &self,
+        guid: &Guid,
+        entry: LoginEntry,
+        encdec: &EncryptorDecryptor,
+    ) -> Result<LoginEntry> {
+        let entry = entry.fixup()?;
+        self.check_for_dupes(guid, &entry, encdec)?;
+        Ok(entry)
+    }
+
+    pub fn check_for_dupes(
+        &self,
+        guid: &Guid,
+        entry: &LoginEntry,
+        encdec: &EncryptorDecryptor,
+    ) -> Result<()> {
+        if self.dupe_exists(guid, entry, encdec)? {
+            return Err(InvalidLogin::DuplicateLogin.into());
+        }
+        Ok(())
+    }
+
+    pub fn dupe_exists(
+        &self,
+        guid: &Guid,
+        entry: &LoginEntry,
+        encdec: &EncryptorDecryptor,
+    ) -> Result<bool> {
+        Ok(self.find_dupe(guid, entry, encdec)?.is_some())
+    }
+
+    pub fn find_dupe(
+        &self,
+        guid: &Guid,
+        entry: &LoginEntry,
+        encdec: &EncryptorDecryptor,
+    ) -> Result<Option<Guid>> {
+        for possible in self.get_by_entry_target(entry)? {
+            if possible.guid() != *guid {
+                let pos_sec_fields = possible.decrypt_fields(encdec)?;
+                if pos_sec_fields.username == entry.sec_fields.username {
+                    return Ok(Some(possible.guid()));
+                }
+            }
+        }
+        Ok(None)
+    }
+
+    // Find saved logins that match the target for a `LoginEntry`
+    //
+    // This means that:
+    //   - `origin` matches
+    //   - Either `form_action_origin` or `http_realm` matches, depending on which one is non-null
+    //
+    // This is used for dupe-checking and `find_login_to_update()`
+    fn get_by_entry_target(&self, entry: &LoginEntry) -> Result<Vec<EncryptedLogin>> {
+        // Could be lazy_static-ed...
+        lazy_static::lazy_static! {
+            static ref GET_BY_FORM_ACTION_ORIGIN: String = format!(
+                "SELECT {common_cols} FROM loginsL
+                WHERE is_deleted = 0
+                    AND origin = :origin
+                    AND formActionOrigin = :form_action_origin
+
+                UNION ALL
+
+                SELECT {common_cols} FROM loginsM
+                WHERE is_overridden = 0
+                    AND origin = :origin
+                    AND formActionOrigin = :form_action_origin
+                ",
+                common_cols = schema::COMMON_COLS
+            );
+            static ref GET_BY_HTTP_REALM: String = format!(
+                "SELECT {common_cols} FROM loginsL
+                WHERE is_deleted = 0
+                    AND origin = :origin
+                    AND httpRealm = :http_realm
+
+                UNION ALL
+
+                SELECT {common_cols} FROM loginsM
+                WHERE is_overridden = 0
+                    AND origin = :origin
+                    AND httpRealm = :http_realm
+                ",
+                common_cols = schema::COMMON_COLS
+            );
+        }
+        match (
+            entry.fields.form_action_origin.as_ref(),
+            entry.fields.http_realm.as_ref(),
+        ) {
+            (Some(form_action_origin), None) => {
+                let params = named_params! {
+                    ":origin": &entry.fields.origin,
+                    ":form_action_origin": form_action_origin,
+                };
+                self.db
+                    .prepare_cached(&GET_BY_FORM_ACTION_ORIGIN)?
+                    .query_and_then(params, EncryptedLogin::from_row)?
+                    .collect()
+            }
+            (None, Some(http_realm)) => {
+                let params = named_params! {
+                    ":origin": &entry.fields.origin,
+                    ":http_realm": http_realm,
+                };
+                self.db
+                    .prepare_cached(&GET_BY_HTTP_REALM)?
+                    .query_and_then(params, EncryptedLogin::from_row)?
+                    .collect()
+            }
+            (Some(_), Some(_)) => Err(InvalidLogin::BothTargets.into()),
+            (None, None) => Err(InvalidLogin::NoTarget.into()),
+        }
+    }
+
+    pub fn exists(&self, id: &str) -> Result<bool> {
+        Ok(self.db.query_row(
+            "SELECT EXISTS(
+                 SELECT 1 FROM loginsL
+                 WHERE guid = :guid AND is_deleted = 0
+                 UNION ALL
+                 SELECT 1 FROM loginsM
+                 WHERE guid = :guid AND is_overridden IS NOT 1
+             )",
+            named_params! { ":guid": id },
+            |row| row.get(0),
+        )?)
+    }
+
+    /// Delete the record with the provided id. Returns true if the record
+    /// existed already.
+    pub fn delete(&self, id: &str) -> Result<bool> {
+        let tx = self.unchecked_transaction_imm()?;
+        let exists = self.exists(id)?;
+        let now_ms = util::system_time_ms_i64(SystemTime::now());
+
+        // For IDs that have, mark is_deleted and clear sensitive fields
+        self.execute(
+            &format!(
+                "UPDATE loginsL
+                 SET local_modified = :now_ms,
+                     sync_status = {status_changed},
+                     is_deleted = 1,
+                     secFields = '',
+                     origin = '', 
+                     httpRealm = NULL,
+                     formActionOrigin = NULL
+                 WHERE guid = :guid",
+                status_changed = SyncStatus::Changed as u8
+            ),
+            named_params! { ":now_ms": now_ms, ":guid": id },
+        )?;
+
+        // Mark the mirror as overridden
+        self.execute(
+            "UPDATE loginsM SET is_overridden = 1 WHERE guid = :guid",
+            named_params! { ":guid": id },
+        )?;
+
+        // If we don't have a local record for this ID, but do have it in the mirror
+        // insert a tombstone.
+        self.execute(&format!("
+            INSERT OR IGNORE INTO loginsL
+                    (guid, local_modified, is_deleted, sync_status, origin, timeCreated, timePasswordChanged, secFields)
+            SELECT   guid, :now_ms,        1,          {changed},   '',     timeCreated, :now_ms,             ''
+            FROM loginsM
+            WHERE guid = :guid",
+            changed = SyncStatus::Changed as u8),
+            named_params! { ":now_ms": now_ms, ":guid": id })?;
+        tx.commit()?;
+        Ok(exists)
+    }
+
+    fn mark_mirror_overridden(&self, guid: &str) -> Result<()> {
+        self.execute_cached(
+            "UPDATE loginsM SET is_overridden = 1 WHERE guid = :guid",
+            named_params! { ":guid": guid },
+        )?;
+        Ok(())
+    }
+
+    fn ensure_local_overlay_exists(&self, guid: &str) -> Result<()> {
+        let already_have_local: bool = self.db.query_row(
+            "SELECT EXISTS(SELECT 1 FROM loginsL WHERE guid = :guid)",
+            named_params! { ":guid": guid },
+            |row| row.get(0),
+        )?;
+
+        if already_have_local {
+            return Ok(());
+        }
+
+        log::debug!("No overlay; cloning one for {:?}.", guid);
+        let changed = self.clone_mirror_to_overlay(guid)?;
+        if changed == 0 {
+            report_error!(
+                "logins-local-overlay-error",
+                "Failed to create local overlay for GUID {guid:?}."
+            );
+            return Err(Error::NoSuchRecord(guid.to_owned()));
+        }
+        Ok(())
+    }
+
+    fn clone_mirror_to_overlay(&self, guid: &str) -> Result<usize> {
+        Ok(self.execute_cached(&CLONE_SINGLE_MIRROR_SQL, &[(":guid", &guid as &dyn ToSql)])?)
+    }
+
+    // Wipe is called both by Sync and also exposed publically, so it's
+    // implemented here.
+    pub(crate) fn wipe(&self, scope: &SqlInterruptScope) -> Result<()> {
+        let tx = self.unchecked_transaction()?;
+        log::info!("Executing wipe on password engine!");
+        let now_ms = util::system_time_ms_i64(SystemTime::now());
+        scope.err_if_interrupted()?;
+        self.execute(
+            &format!(
+                "
+                UPDATE loginsL
+                SET local_modified = :now_ms,
+                    sync_status = {changed},
+                    is_deleted = 1,
+                    secFields = '',
+                    origin = ''
+                WHERE is_deleted = 0",
+                changed = SyncStatus::Changed as u8
+            ),
+            named_params! { ":now_ms": now_ms },
+        )?;
+        scope.err_if_interrupted()?;
+
+        self.execute("UPDATE loginsM SET is_overridden = 1", [])?;
+        scope.err_if_interrupted()?;
+
+        self.execute(
+            &format!("
+                INSERT OR IGNORE INTO loginsL
+                      (guid, local_modified, is_deleted, sync_status, origin, timeCreated, timePasswordChanged, secFields)
+                SELECT guid, :now_ms,        1,          {changed},   '',     timeCreated, :now_ms,             ''
+                FROM loginsM",
+                changed = SyncStatus::Changed as u8),
+            named_params! { ":now_ms": now_ms })?;
+        scope.err_if_interrupted()?;
+        tx.commit()?;
+        Ok(())
+    }
+
+    pub fn wipe_local(&self) -> Result<()> {
+        log::info!("Executing wipe_local on password engine!");
+        let tx = self.unchecked_transaction()?;
+        self.execute_all(&[
+            "DELETE FROM loginsL",
+            "DELETE FROM loginsM",
+            "DELETE FROM loginsSyncMeta",
+        ])?;
+        tx.commit()?;
+        Ok(())
+    }
+}
+
+lazy_static! {
+    static ref GET_ALL_SQL: String = format!(
+        "SELECT {common_cols} FROM loginsL WHERE is_deleted = 0
+         UNION ALL
+         SELECT {common_cols} FROM loginsM WHERE is_overridden = 0",
+        common_cols = schema::COMMON_COLS,
+    );
+    static ref GET_BY_GUID_SQL: String = format!(
+        "SELECT {common_cols}
+         FROM loginsL
+         WHERE is_deleted = 0
+           AND guid = :guid
+
+         UNION ALL
+
+         SELECT {common_cols}
+         FROM loginsM
+         WHERE is_overridden IS NOT 1
+           AND guid = :guid
+         ORDER BY origin ASC
+
+         LIMIT 1",
+        common_cols = schema::COMMON_COLS,
+    );
+    pub static ref CLONE_ENTIRE_MIRROR_SQL: String = format!(
+        "INSERT OR IGNORE INTO loginsL ({common_cols}, local_modified, is_deleted, sync_status)
+         SELECT {common_cols}, NULL AS local_modified, 0 AS is_deleted, 0 AS sync_status
+         FROM loginsM",
+        common_cols = schema::COMMON_COLS,
+    );
+    static ref CLONE_SINGLE_MIRROR_SQL: String =
+        format!("{} WHERE guid = :guid", &*CLONE_ENTIRE_MIRROR_SQL,);
+}
+
+#[cfg(test)]
+pub mod test_utils {
+    use super::*;
+    use crate::encryption::test_utils::decrypt_struct;
+    use crate::login::test_utils::enc_login;
+    use crate::SecureLoginFields;
+    use sync15::ServerTimestamp;
+
+    // Insert a login into the local and/or mirror tables.
+    //
+    // local_login and mirror_login are specifed as Some(password_string)
+    pub fn insert_login(
+        db: &LoginDb,
+        guid: &str,
+        local_login: Option<&str>,
+        mirror_login: Option<&str>,
+    ) {
+        if let Some(password) = mirror_login {
+            add_mirror(
+                db,
+                &enc_login(guid, password),
+                &ServerTimestamp(util::system_time_ms_i64(std::time::SystemTime::now())),
+                local_login.is_some(),
+            )
+            .unwrap();
+        }
+        if let Some(password) = local_login {
+            db.insert_new_login(&enc_login(guid, password)).unwrap();
+        }
+    }
+
+    pub fn add_mirror(
+        db: &LoginDb,
+        login: &EncryptedLogin,
+        server_modified: &ServerTimestamp,
+        is_overridden: bool,
+    ) -> Result<()> {
+        let sql = "
+            INSERT OR IGNORE INTO loginsM (
+                is_overridden,
+                server_modified,
+
+                httpRealm,
+                formActionOrigin,
+                usernameField,
+                passwordField,
+                secFields,
+                origin,
+
+                timesUsed,
+                timeLastUsed,
+                timePasswordChanged,
+                timeCreated,
+
+                guid
+            ) VALUES (
+                :is_overridden,
+                :server_modified,
+
+                :http_realm,
+                :form_action_origin,
+                :username_field,
+                :password_field,
+                :sec_fields,
+                :origin,
+
+                :times_used,
+                :time_last_used,
+                :time_password_changed,
+                :time_created,
+
+                :guid
+            )";
+        let mut stmt = db.prepare_cached(sql)?;
+
+        stmt.execute(named_params! {
+            ":is_overridden": is_overridden,
+            ":server_modified": server_modified.as_millis(),
+            ":http_realm": login.fields.http_realm,
+            ":form_action_origin": login.fields.form_action_origin,
+            ":username_field": login.fields.username_field,
+            ":password_field": login.fields.password_field,
+            ":origin": login.fields.origin,
+            ":sec_fields": login.sec_fields,
+            ":times_used": login.record.times_used,
+            ":time_last_used": login.record.time_last_used,
+            ":time_password_changed": login.record.time_password_changed,
+            ":time_created": login.record.time_created,
+            ":guid": login.guid_str(),
+        })?;
+        Ok(())
+    }
+
+    pub fn get_local_guids(db: &LoginDb) -> Vec<String> {
+        get_guids(db, "SELECT guid FROM loginsL")
+    }
+
+    pub fn get_mirror_guids(db: &LoginDb) -> Vec<String> {
+        get_guids(db, "SELECT guid FROM loginsM")
+    }
+
+    fn get_guids(db: &LoginDb, sql: &str) -> Vec<String> {
+        let mut stmt = db.prepare_cached(sql).unwrap();
+        let mut res: Vec<String> = stmt
+            .query_map([], |r| r.get(0))
+            .unwrap()
+            .map(|r| r.unwrap())
+            .collect();
+        res.sort();
+        res
+    }
+
+    pub fn get_server_modified(db: &LoginDb, guid: &str) -> i64 {
+        db.query_one(&format!(
+            "SELECT server_modified FROM loginsM WHERE guid='{}'",
+            guid
+        ))
+        .unwrap()
+    }
+
+    pub fn check_local_login(db: &LoginDb, guid: &str, password: &str, local_modified_gte: i64) {
+        let row: (String, i64, bool) = db
+            .query_row(
+                "SELECT secFields, local_modified, is_deleted FROM loginsL WHERE guid=?",
+                [guid],
+                |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)),
+            )
+            .unwrap();
+        let enc: SecureLoginFields = decrypt_struct(row.0);
+        assert_eq!(enc.password, password);
+        assert!(row.1 >= local_modified_gte);
+        assert!(!row.2);
+    }
+
+    pub fn check_mirror_login(
+        db: &LoginDb,
+        guid: &str,
+        password: &str,
+        server_modified: i64,
+        is_overridden: bool,
+    ) {
+        let row: (String, i64, bool) = db
+            .query_row(
+                "SELECT secFields, server_modified, is_overridden FROM loginsM WHERE guid=?",
+                [guid],
+                |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)),
+            )
+            .unwrap();
+        let enc: SecureLoginFields = decrypt_struct(row.0);
+        assert_eq!(enc.password, password);
+        assert_eq!(row.1, server_modified);
+        assert_eq!(row.2, is_overridden);
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::encryption::test_utils::TEST_ENCRYPTOR;
+    use crate::sync::merge::LocalLogin;
+    use crate::SecureLoginFields;
+    use std::{thread, time};
+
+    #[test]
+    fn test_username_dupe_semantics() {
+        let mut login = LoginEntry {
+            fields: LoginFields {
+                origin: "https://www.example.com".into(),
+                http_realm: Some("https://www.example.com".into()),
+                ..LoginFields::default()
+            },
+            sec_fields: SecureLoginFields {
+                username: "test".into(),
+                password: "sekret".into(),
+            },
+        };
+
+        let db = LoginDb::open_in_memory().unwrap();
+        db.add(login.clone(), &TEST_ENCRYPTOR)
+            .expect("should be able to add first login");
+
+        // We will reject new logins with the same username value...
+        let exp_err = "Invalid login: Login already exists";
+        assert_eq!(
+            db.add(login.clone(), &TEST_ENCRYPTOR)
+                .unwrap_err()
+                .to_string(),
+            exp_err
+        );
+
+        // Add one with an empty username - not a dupe.
+        login.sec_fields.username = "".to_string();
+        db.add(login.clone(), &TEST_ENCRYPTOR)
+            .expect("empty login isn't a dupe");
+
+        assert_eq!(
+            db.add(login, &TEST_ENCRYPTOR).unwrap_err().to_string(),
+            exp_err
+        );
+
+        // one with a username, 1 without.
+        assert_eq!(db.get_all().unwrap().len(), 2);
+    }
+
+    #[test]
+    fn test_unicode_submit() {
+        let db = LoginDb::open_in_memory().unwrap();
+        let added = db
+            .add(
+                LoginEntry {
+                    fields: LoginFields {
+                        form_action_origin: Some("http://😍.com".into()),
+                        origin: "http://😍.com".into(),
+                        http_realm: None,
+                        username_field: "😍".into(),
+                        password_field: "😍".into(),
+                    },
+                    sec_fields: SecureLoginFields {
+                        username: "😍".into(),
+                        password: "😍".into(),
+                    },
+                },
+                &TEST_ENCRYPTOR,
+            )
+            .unwrap();
+        let fetched = db
+            .get_by_id(&added.record.id)
+            .expect("should work")
+            .expect("should get a record");
+        assert_eq!(added, fetched);
+        assert_eq!(fetched.fields.origin, "http://xn--r28h.com");
+        assert_eq!(
+            fetched.fields.form_action_origin,
+            Some("http://xn--r28h.com".to_string())
+        );
+        assert_eq!(fetched.fields.username_field, "😍");
+        assert_eq!(fetched.fields.password_field, "😍");
+        let sec_fields = fetched.decrypt_fields(&TEST_ENCRYPTOR).unwrap();
+        assert_eq!(sec_fields.username, "😍");
+        assert_eq!(sec_fields.password, "😍");
+    }
+
+    #[test]
+    fn test_unicode_realm() {
+        let db = LoginDb::open_in_memory().unwrap();
+        let added = db
+            .add(
+                LoginEntry {
+                    fields: LoginFields {
+                        form_action_origin: None,
+                        origin: "http://😍.com".into(),
+                        http_realm: Some("😍😍".into()),
+                        ..Default::default()
+                    },
+                    sec_fields: SecureLoginFields {
+                        username: "😍".into(),
+                        password: "😍".into(),
+                    },
+                },
+                &TEST_ENCRYPTOR,
+            )
+            .unwrap();
+        let fetched = db
+            .get_by_id(&added.record.id)
+            .expect("should work")
+            .expect("should get a record");
+        assert_eq!(added, fetched);
+        assert_eq!(fetched.fields.origin, "http://xn--r28h.com");
+        assert_eq!(fetched.fields.http_realm.unwrap(), "😍😍");
+    }
+
+    fn check_matches(db: &LoginDb, query: &str, expected: &[&str]) {
+        let mut results = db
+            .get_by_base_domain(query)
+            .unwrap()
+            .into_iter()
+            .map(|l| l.fields.origin)
+            .collect::<Vec<String>>();
+        results.sort_unstable();
+        let mut sorted = expected.to_owned();
+        sorted.sort_unstable();
+        assert_eq!(sorted, results);
+    }
+
+    fn check_good_bad(
+        good: Vec<&str>,
+        bad: Vec<&str>,
+        good_queries: Vec<&str>,
+        zero_queries: Vec<&str>,
+    ) {
+        let db = LoginDb::open_in_memory().unwrap();
+        for h in good.iter().chain(bad.iter()) {
+            db.add(
+                LoginEntry {
+                    fields: LoginFields {
+                        origin: (*h).into(),
+                        http_realm: Some((*h).into()),
+                        ..Default::default()
+                    },
+                    sec_fields: SecureLoginFields {
+                        password: "test".into(),
+                        ..Default::default()
+                    },
+                },
+                &TEST_ENCRYPTOR,
+            )
+            .unwrap();
+        }
+        for query in good_queries {
+            check_matches(&db, query, &good);
+        }
+        for query in zero_queries {
+            check_matches(&db, query, &[]);
+        }
+    }
+
+    #[test]
+    fn test_get_by_base_domain_invalid() {
+        check_good_bad(
+            vec!["https://example.com"],
+            vec![],
+            vec![],
+            vec!["invalid query"],
+        );
+    }
+
+    #[test]
+    fn test_get_by_base_domain() {
+        check_good_bad(
+            vec![
+                "https://example.com",
+                "https://www.example.com",
+                "http://www.example.com",
+                "http://www.example.com:8080",
+                "http://sub.example.com:8080",
+                "https://sub.example.com:8080",
+                "https://sub.sub.example.com",
+                "ftp://sub.example.com",
+            ],
+            vec![
+                "https://badexample.com",
+                "https://example.co",
+                "https://example.com.au",
+            ],
+            vec!["example.com"],
+            vec!["foo.com"],
+        );
+        // punycode! This is likely to need adjusting once we normalize
+        // on insert.
+        check_good_bad(
+            vec![
+                "http://xn--r28h.com", // punycoded version of "http://😍.com"
+            ],
+            vec!["http://💖.com"],
+            vec!["😍.com", "xn--r28h.com"],
+            vec![],
+        );
+    }
+
+    #[test]
+    fn test_get_by_base_domain_ipv4() {
+        check_good_bad(
+            vec!["http://127.0.0.1", "https://127.0.0.1:8000"],
+            vec!["https://127.0.0.0", "https://example.com"],
+            vec!["127.0.0.1"],
+            vec!["127.0.0.2"],
+        );
+    }
+
+    #[test]
+    fn test_get_by_base_domain_ipv6() {
+        check_good_bad(
+            vec!["http://[::1]", "https://[::1]:8000"],
+            vec!["https://[0:0:0:0:0:0:1:1]", "https://example.com"],
+            vec!["[::1]", "[0:0:0:0:0:0:0:1]"],
+            vec!["[0:0:0:0:0:0:1:2]"],
+        );
+    }
+
+    #[test]
+    fn test_add() {
+        let db = LoginDb::open_in_memory().unwrap();
+        let to_add = LoginEntry {
+            fields: LoginFields {
+                origin: "https://www.example.com".into(),
+                http_realm: Some("https://www.example.com".into()),
+                ..Default::default()
+            },
+            sec_fields: SecureLoginFields {
+                username: "test_user".into(),
+                password: "test_password".into(),
+            },
+        };
+        let login = db.add(to_add, &TEST_ENCRYPTOR).unwrap();
+        let login2 = db.get_by_id(&login.record.id).unwrap().unwrap();
+
+        assert_eq!(login.fields.origin, login2.fields.origin);
+        assert_eq!(login.fields.http_realm, login2.fields.http_realm);
+        assert_eq!(login.sec_fields, login2.sec_fields);
+    }
+
+    #[test]
+    fn test_update() {
+        let db = LoginDb::open_in_memory().unwrap();
+        let login = db
+            .add(
+                LoginEntry {
+                    fields: LoginFields {
+                        origin: "https://www.example.com".into(),
+                        http_realm: Some("https://www.example.com".into()),
+                        ..Default::default()
+                    },
+                    sec_fields: SecureLoginFields {
+                        username: "user1".into(),
+                        password: "password1".into(),
+                    },
+                },
+                &TEST_ENCRYPTOR,
+            )
+            .unwrap();
+        db.update(
+            &login.record.id,
+            LoginEntry {
+                fields: LoginFields {
+                    origin: "https://www.example2.com".into(),
+                    http_realm: Some("https://www.example2.com".into()),
+                    ..login.fields
+                },
+                sec_fields: SecureLoginFields {
+                    username: "user2".into(),
+                    password: "password2".into(),
+                },
+            },
+            &TEST_ENCRYPTOR,
+        )
+        .unwrap();
+
+        let login2 = db.get_by_id(&login.record.id).unwrap().unwrap();
+
+        assert_eq!(login2.fields.origin, "https://www.example2.com");
+        assert_eq!(
+            login2.fields.http_realm,
+            Some("https://www.example2.com".into())
+        );
+        let sec_fields = login2.decrypt_fields(&TEST_ENCRYPTOR).unwrap();
+        assert_eq!(sec_fields.username, "user2");
+        assert_eq!(sec_fields.password, "password2");
+    }
+
+    #[test]
+    fn test_touch() {
+        let db = LoginDb::open_in_memory().unwrap();
+        let login = db
+            .add(
+                LoginEntry {
+                    fields: LoginFields {
+                        origin: "https://www.example.com".into(),
+                        http_realm: Some("https://www.example.com".into()),
+                        ..Default::default()
+                    },
+                    sec_fields: SecureLoginFields {
+                        username: "user1".into(),
+                        password: "password1".into(),
+                    },
+                },
+                &TEST_ENCRYPTOR,
+            )
+            .unwrap();
+        // Simulate touch happening at another "time"
+        thread::sleep(time::Duration::from_millis(50));
+        db.touch(&login.record.id).unwrap();
+        let login2 = db.get_by_id(&login.record.id).unwrap().unwrap();
+        assert!(login2.record.time_last_used > login.record.time_last_used);
+        assert_eq!(login2.record.times_used, login.record.times_used + 1);
+    }
+
+    #[test]
+    fn test_delete() {
+        let db = LoginDb::open_in_memory().unwrap();
+        let login = db
+            .add(
+                LoginEntry {
+                    fields: LoginFields {
+                        origin: "https://www.example.com".into(),
+                        http_realm: Some("https://www.example.com".into()),
+                        ..Default::default()
+                    },
+                    sec_fields: SecureLoginFields {
+                        username: "test_user".into(),
+                        password: "test_password".into(),
+                    },
+                },
+                &TEST_ENCRYPTOR,
+            )
+            .unwrap();
+
+        assert!(db.delete(login.guid_str()).unwrap());
+
+        let local_login = db
+            .query_row(
+                "SELECT * FROM loginsL WHERE guid = :guid",
+                named_params! { ":guid": login.guid_str() },
+                |row| Ok(LocalLogin::from_row(row).unwrap()),
+            )
+            .unwrap();
+        assert_eq!(local_login.login.fields.http_realm, None);
+        assert_eq!(local_login.login.fields.form_action_origin, None);
+
+        assert!(!db.exists(login.guid_str()).unwrap());
+    }
+
+    #[test]
+    fn test_wipe() {
+        let db = LoginDb::open_in_memory().unwrap();
+        let login1 = db
+            .add(
+                LoginEntry {
+                    fields: LoginFields {
+                        origin: "https://www.example.com".into(),
+                        http_realm: Some("https://www.example.com".into()),
+                        ..Default::default()
+                    },
+                    sec_fields: SecureLoginFields {
+                        username: "test_user_1".into(),
+                        password: "test_password_1".into(),
+                    },
+                },
+                &TEST_ENCRYPTOR,
+            )
+            .unwrap();
+
+        let login2 = db
+            .add(
+                LoginEntry {
+                    fields: LoginFields {
+                        origin: "https://www.example2.com".into(),
+                        http_realm: Some("https://www.example2.com".into()),
+                        ..Default::default()
+                    },
+                    sec_fields: SecureLoginFields {
+                        username: "test_user_1".into(),
+                        password: "test_password_2".into(),
+                    },
+                },
+                &TEST_ENCRYPTOR,
+            )
+            .unwrap();
+
+        db.wipe(&db.begin_interrupt_scope().unwrap())
+            .expect("wipe should work");
+
+        let expected_tombstone_count = 2;
+        let actual_tombstone_count: i32 = db
+            .query_row(
+                "SELECT COUNT(guid)
+                    FROM loginsL
+                    WHERE guid IN (:guid1,:guid2)
+                        AND is_deleted = 1",
+                named_params! {
+                    ":guid1": login1.guid_str(),
+                    ":guid2": login2.guid_str(),
+                },
+                |row| row.get(0),
+            )
+            .unwrap();
+
+        assert_eq!(expected_tombstone_count, actual_tombstone_count);
+        assert!(!db.exists(login1.guid_str()).unwrap());
+        assert!(!db.exists(login2.guid_str()).unwrap());
+    }
+
+    mod test_find_login_to_update {
+        use super::*;
+
+        fn make_entry(username: &str, password: &str) -> LoginEntry {
+            LoginEntry {
+                fields: LoginFields {
+                    origin: "https://www.example.com".into(),
+                    http_realm: Some("the website".into()),
+                    ..Default::default()
+                },
+                sec_fields: SecureLoginFields {
+                    username: username.into(),
+                    password: password.into(),
+                },
+            }
+        }
+
+        fn make_saved_login(db: &LoginDb, username: &str, password: &str) -> Login {
+            db.add(make_entry(username, password), &TEST_ENCRYPTOR)
+                .unwrap()
+                .decrypt(&TEST_ENCRYPTOR)
+                .unwrap()
+        }
+
+        #[test]
+        fn test_match() {
+            let db = LoginDb::open_in_memory().unwrap();
+            let login = make_saved_login(&db, "user", "pass");
+            assert_eq!(
+                Some(login),
+                db.find_login_to_update(make_entry("user", "pass"), &TEST_ENCRYPTOR)
+                    .unwrap(),
+            );
+        }
+
+        #[test]
+        fn test_non_matches() {
+            let db = LoginDb::open_in_memory().unwrap();
+            // Non-match because the username is different
+            make_saved_login(&db, "other-user", "pass");
+            // Non-match because the http_realm is different
+            db.add(
+                LoginEntry {
+                    fields: LoginFields {
+                        origin: "https://www.example.com".into(),
+                        http_realm: Some("the other website".into()),
+                        ..Default::default()
+                    },
+                    sec_fields: SecureLoginFields {
+                        username: "user".into(),
+                        password: "pass".into(),
+                    },
+                },
+                &TEST_ENCRYPTOR,
+            )
+            .unwrap();
+            // Non-match because it uses form_action_origin instead of http_realm
+            db.add(
+                LoginEntry {
+                    fields: LoginFields {
+                        origin: "https://www.example.com".into(),
+                        form_action_origin: Some("https://www.example.com/".into()),
+                        ..Default::default()
+                    },
+                    sec_fields: SecureLoginFields {
+                        username: "user".into(),
+                        password: "pass".into(),
+                    },
+                },
+                &TEST_ENCRYPTOR,
+            )
+            .unwrap();
+            assert_eq!(
+                None,
+                db.find_login_to_update(make_entry("user", "pass"), &TEST_ENCRYPTOR)
+                    .unwrap(),
+            );
+        }
+
+        #[test]
+        fn test_match_blank_password() {
+            let db = LoginDb::open_in_memory().unwrap();
+            let login = make_saved_login(&db, "", "pass");
+            assert_eq!(
+                Some(login),
+                db.find_login_to_update(make_entry("user", "pass"), &TEST_ENCRYPTOR)
+                    .unwrap(),
+            );
+        }
+
+        #[test]
+        fn test_username_match_takes_precedence_over_blank_username() {
+            let db = LoginDb::open_in_memory().unwrap();
+            make_saved_login(&db, "", "pass");
+            let username_match = make_saved_login(&db, "user", "pass");
+            assert_eq!(
+                Some(username_match),
+                db.find_login_to_update(make_entry("user", "pass"), &TEST_ENCRYPTOR)
+                    .unwrap(),
+            );
+        }
+
+        #[test]
+        fn test_invalid_login() {
+            let db = LoginDb::open_in_memory().unwrap();
+            assert!(db
+                .find_login_to_update(
+                    LoginEntry {
+                        fields: LoginFields {
+                            http_realm: None,
+                            form_action_origin: None,
+                            ..LoginFields::default()
+                        },
+                        ..LoginEntry::default()
+                    },
+                    &TEST_ENCRYPTOR
+                )
+                .is_err());
+        }
+
+        #[test]
+        fn test_update_with_duplicate_login() {
+            // If we have duplicate logins in the database, it should be possible to update them
+            // without triggering a DuplicateLogin error
+            let db = LoginDb::open_in_memory().unwrap();
+            let login = make_saved_login(&db, "user", "pass");
+            let mut dupe = login.clone().encrypt(&TEST_ENCRYPTOR).unwrap();
+            dupe.record.id = "different-guid".to_string();
+            db.insert_new_login(&dupe).unwrap();
+
+            let mut entry = login.entry();
+            entry.sec_fields.password = "pass2".to_string();
+            db.update(&login.record.id, entry, &TEST_ENCRYPTOR).unwrap();
+
+            let mut entry = login.entry();
+            entry.sec_fields.password = "pass3".to_string();
+            db.add_or_update(entry, &TEST_ENCRYPTOR).unwrap();
+        }
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/logins/encryption.rs.html b/book/rust-docs/src/logins/encryption.rs.html new file mode 100644 index 0000000000..2ba044770c --- /dev/null +++ b/book/rust-docs/src/logins/encryption.rs.html @@ -0,0 +1,243 @@ +encryption.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+// This is the *local* encryption support - it has nothing to do with the
+// encryption used by sync.
+
+// For context, what "local encryption" means in this context is:
+// * We use regular sqlite, but ensure that sensitive data is encrypted in the DB in the
+//   `secure_fields` column.  The encryption key is managed by the app.
+// * The `decrypt_struct` and `encrypt_struct` functions are used to convert between an encrypted
+//   `secure_fields` string and a decrypted `SecureFields` struct
+// * Most API functions return `EncryptedLogin` which has its data encrypted.
+//
+// This makes life tricky for Sync - sync has its own encryption and its own
+// management of sync keys. The entire records are encrypted on the server -
+// so the record on the server has the plain-text data (which is then
+// encrypted as part of the entire record), so:
+// * When transforming a record from the DB into a Sync record, we need to
+//   *decrypt* the data.
+// * When transforming a record from Sync into a DB record, we need to *encrypt*
+//   the data.
+//
+// So Sync needs to know the key etc, and that needs to get passed down
+// multiple layers, from the app saying "sync now" all the way down to the
+// low level sync code.
+// To make life a little easier, we do that via a struct.
+
+use crate::error::*;
+
+pub type EncryptorDecryptor = jwcrypto::EncryptorDecryptor<Error>;
+
+#[handle_error(Error)]
+pub fn create_canary(text: &str, key: &str) -> ApiResult<String> {
+    EncryptorDecryptor::new(key)?.create_canary(text)
+}
+
+#[handle_error(Error)]
+pub fn check_canary(canary: &str, text: &str, key: &str) -> ApiResult<bool> {
+    EncryptorDecryptor::new(key)?.check_canary(canary, text)
+}
+
+#[handle_error(Error)]
+pub fn create_key() -> ApiResult<String> {
+    EncryptorDecryptor::create_key()
+}
+
+#[cfg(test)]
+pub mod test_utils {
+    use super::*;
+    use serde::{de::DeserializeOwned, Serialize};
+
+    lazy_static::lazy_static! {
+        pub static ref TEST_ENCRYPTION_KEY: String = serde_json::to_string(&jwcrypto::Jwk::new_direct_key(Some("test-key".to_string())).unwrap()).unwrap();
+        pub static ref TEST_ENCRYPTOR: EncryptorDecryptor = EncryptorDecryptor::new(&TEST_ENCRYPTION_KEY).unwrap();
+    }
+    pub fn encrypt(value: &str) -> String {
+        TEST_ENCRYPTOR.encrypt(value, "test encrypt").unwrap()
+    }
+    pub fn decrypt(value: &str) -> String {
+        TEST_ENCRYPTOR.decrypt(value, "test decrypt").unwrap()
+    }
+    pub fn encrypt_struct<T: Serialize>(fields: &T) -> String {
+        TEST_ENCRYPTOR
+            .encrypt_struct(fields, "test encrypt struct")
+            .unwrap()
+    }
+    pub fn decrypt_struct<T: DeserializeOwned>(ciphertext: String) -> T {
+        TEST_ENCRYPTOR
+            .decrypt_struct(&ciphertext, "test decrypt struct")
+            .unwrap()
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn test_encrypt() {
+        let ed = EncryptorDecryptor::new(&create_key().unwrap()).unwrap();
+        let cleartext = "secret";
+        let ciphertext = ed.encrypt(cleartext, "test encrypt").unwrap();
+        assert_eq!(ed.decrypt(&ciphertext, "test decrypt").unwrap(), cleartext);
+        let ed2 = EncryptorDecryptor::new(&create_key().unwrap()).unwrap();
+        assert!(matches!(
+            ed2.decrypt(&ciphertext, "test decrypt").err().unwrap(),
+            Error::CryptoError(jwcrypto::EncryptorDecryptorError { description, .. })
+            if description == "test decrypt"
+        ));
+    }
+
+    #[test]
+    fn test_key_error() {
+        let storage_err = EncryptorDecryptor::new("bad-key").err().unwrap();
+        assert!(matches!(
+            storage_err,
+            Error::CryptoError(jwcrypto::EncryptorDecryptorError {
+                from: jwcrypto::JwCryptoError::InvalidKey,
+                ..
+            })
+        ));
+    }
+
+    #[test]
+    fn test_canary_functionality() {
+        const CANARY_TEXT: &str = "Arbitrary sequence of text";
+        let key = create_key().unwrap();
+        let canary = create_canary(CANARY_TEXT, &key).unwrap();
+        assert!(check_canary(&canary, CANARY_TEXT, &key).unwrap());
+
+        let different_key = create_key().unwrap();
+        assert!(matches!(
+            check_canary(&canary, CANARY_TEXT, &different_key)
+                .err()
+                .unwrap(),
+            LoginsApiError::IncorrectKey
+        ));
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/logins/error.rs.html b/book/rust-docs/src/logins/error.rs.html new file mode 100644 index 0000000000..84cca3d5ee --- /dev/null +++ b/book/rust-docs/src/logins/error.rs.html @@ -0,0 +1,375 @@ +error.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use std::ffi::OsString;
+pub type Result<T> = std::result::Result<T, Error>;
+// Functions which are part of the public API should use this Result.
+pub type ApiResult<T> = std::result::Result<T, LoginsApiError>;
+
+pub use error_support::{breadcrumb, handle_error, report_error};
+use error_support::{ErrorHandling, GetErrorHandling};
+use jwcrypto::EncryptorDecryptorError;
+use sync15::Error as Sync15Error;
+
+// Errors we return via the public interface.
+#[derive(Debug, thiserror::Error)]
+pub enum LoginsApiError {
+    #[error("Invalid login: {reason}")]
+    InvalidRecord { reason: String },
+
+    #[error("No record with guid exists (when one was required): {reason:?}")]
+    NoSuchRecord { reason: String },
+
+    #[error("Encryption key is in the correct format, but is not the correct key.")]
+    IncorrectKey,
+
+    #[error("{reason}")]
+    Interrupted { reason: String },
+
+    #[error("SyncAuthInvalid error {reason}")]
+    SyncAuthInvalid { reason: String },
+
+    #[error("Unexpected Error: {reason}")]
+    UnexpectedLoginsApiError { reason: String },
+}
+
+/// Logins error type
+/// These are "internal" errors used by the implementation. This error type
+/// is never returned to the consumer.
+#[derive(Debug, thiserror::Error)]
+pub enum Error {
+    #[error("Malformed incoming record")]
+    MalformedIncomingRecord,
+
+    #[error("Invalid login: {0}")]
+    InvalidLogin(#[from] InvalidLogin),
+
+    #[error("The `sync_status` column in DB has an illegal value: {0}")]
+    BadSyncStatus(u8),
+
+    #[error("No record with guid exists (when one was required): {0:?}")]
+    NoSuchRecord(String),
+
+    // Fennec import only works on empty logins tables.
+    #[error("The logins tables are not empty")]
+    NonEmptyTable,
+
+    #[error("local encryption key not set")]
+    EncryptionKeyMissing,
+
+    #[error("Error synchronizing: {0}")]
+    SyncAdapterError(#[from] sync15::Error),
+
+    #[error("Error parsing JSON data: {0}")]
+    JsonError(#[from] serde_json::Error),
+
+    #[error("Error executing SQL: {0}")]
+    SqlError(#[from] rusqlite::Error),
+
+    #[error("Error parsing URL: {0}")]
+    UrlParseError(#[from] url::ParseError),
+
+    #[error("Invalid path: {0:?}")]
+    InvalidPath(OsString),
+
+    #[error("Invalid database file: {0}")]
+    InvalidDatabaseFile(String),
+
+    #[error("CryptoError({0})")]
+    CryptoError(#[from] EncryptorDecryptorError),
+
+    #[error("{0}")]
+    Interrupted(#[from] interrupt_support::Interrupted),
+
+    #[error("IOError: {0}")]
+    IOError(#[from] std::io::Error),
+
+    #[error("Migration Error: {0}")]
+    MigrationError(String),
+}
+
+/// Error::InvalidLogin subtypes
+#[derive(Debug, thiserror::Error)]
+pub enum InvalidLogin {
+    // EmptyOrigin error occurs when the login's origin field is empty.
+    #[error("Origin is empty")]
+    EmptyOrigin,
+    #[error("Password is empty")]
+    EmptyPassword,
+    #[error("Login already exists")]
+    DuplicateLogin,
+    #[error("Both `formActionOrigin` and `httpRealm` are present")]
+    BothTargets,
+    #[error("Neither `formActionOrigin` or `httpRealm` are present")]
+    NoTarget,
+    // Login has an illegal origin field, split off from IllegalFieldValue since this is a known
+    // issue with the Desktop logins and we don't want to report it to Sentry (see #5233).
+    #[error("Login has illegal origin")]
+    IllegalOrigin,
+    #[error("Login has illegal field: {field_info}")]
+    IllegalFieldValue { field_info: String },
+}
+
+// Define how our internal errors are handled and converted to external errors
+// See `support/error/README.md` for how this works, especially the warning about PII.
+impl GetErrorHandling for Error {
+    type ExternalError = LoginsApiError;
+
+    fn get_error_handling(&self) -> ErrorHandling<Self::ExternalError> {
+        match self {
+            Self::InvalidLogin(why) => ErrorHandling::convert(LoginsApiError::InvalidRecord {
+                reason: why.to_string(),
+            }),
+            Self::MalformedIncomingRecord => {
+                ErrorHandling::convert(LoginsApiError::InvalidRecord {
+                    reason: "invalid incoming record".to_string(),
+                })
+            }
+            // Our internal "no such record" error is converted to our public "no such record" error, with no logging and no error reporting.
+            Self::NoSuchRecord(guid) => ErrorHandling::convert(LoginsApiError::NoSuchRecord {
+                reason: guid.to_string(),
+            }),
+            // NonEmptyTable error is just a sanity check to ensure we aren't asked to migrate into an
+            // existing DB - consumers should never actually do this, and will never expect to handle this as a specific
+            // error - so it gets reported to the error reporter and converted to an "internal" error.
+            Self::NonEmptyTable => {
+                ErrorHandling::convert(LoginsApiError::UnexpectedLoginsApiError {
+                    reason: "must be an empty DB to migrate".to_string(),
+                })
+                .report_error("logins-migration")
+            }
+            Self::CryptoError { .. } => ErrorHandling::convert(LoginsApiError::IncorrectKey)
+                .report_error("logins-crypto-error"),
+            Self::Interrupted(_) => ErrorHandling::convert(LoginsApiError::Interrupted {
+                reason: self.to_string(),
+            }),
+            Self::SyncAdapterError(e) => match e {
+                Sync15Error::TokenserverHttpError(401) | Sync15Error::BadKeyLength(..) => {
+                    ErrorHandling::convert(LoginsApiError::SyncAuthInvalid {
+                        reason: e.to_string(),
+                    })
+                    .log_warning()
+                }
+                Sync15Error::RequestError(_) => {
+                    ErrorHandling::convert(LoginsApiError::UnexpectedLoginsApiError {
+                        reason: e.to_string(),
+                    })
+                    .log_warning()
+                }
+                _ => ErrorHandling::convert(LoginsApiError::UnexpectedLoginsApiError {
+                    reason: self.to_string(),
+                })
+                .report_error("logins-sync"),
+            },
+            // Errors that are unexpected in the sense that they are too rare to deserve a
+            // LoginsApiError variant, but we still don't need to report them to Sentry.
+            //
+            // For now, just log a warning.  Eventually, it would be nice to count these with
+            // telemetry.
+            Self::InvalidDatabaseFile(_) => {
+                ErrorHandling::convert(LoginsApiError::UnexpectedLoginsApiError {
+                    reason: self.to_string(),
+                })
+                .log_warning()
+            }
+            // Unexpected errors that we report to Sentry.  We should watch the reports for these
+            // and do one or more of these things if we see them:
+            //   - Fix the underlying issue
+            //   - Add breadcrumbs or other context to help uncover the issue
+            //   - Decide that these are expected errors and move them to the above case
+            _ => ErrorHandling::convert(LoginsApiError::UnexpectedLoginsApiError {
+                reason: self.to_string(),
+            })
+            .report_error("logins-unexpected"),
+        }
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/logins/lib.rs.html b/book/rust-docs/src/logins/lib.rs.html new file mode 100644 index 0000000000..5844607330 --- /dev/null +++ b/book/rust-docs/src/logins/lib.rs.html @@ -0,0 +1,105 @@ +lib.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#![allow(unknown_lints)]
+#![warn(rust_2018_idioms)]
+
+#[macro_use]
+mod error;
+mod login;
+
+mod db;
+pub mod encryption;
+mod schema;
+mod store;
+mod sync;
+mod util;
+
+uniffi::include_scaffolding!("logins");
+
+pub use crate::db::LoginDb;
+use crate::encryption::{check_canary, create_canary, create_key};
+pub use crate::error::*;
+pub use crate::login::*;
+pub use crate::store::*;
+pub use crate::sync::LoginsSyncEngine;
+
+// Public encryption functions.  We publish these as top-level functions to expose them across
+// UniFFI
+#[handle_error(Error)]
+fn encrypt_login(login: Login, enc_key: &str) -> ApiResult<EncryptedLogin> {
+    let encdec = encryption::EncryptorDecryptor::new(enc_key)?;
+    login.encrypt(&encdec)
+}
+
+#[handle_error(Error)]
+fn decrypt_login(login: EncryptedLogin, enc_key: &str) -> ApiResult<Login> {
+    let encdec = encryption::EncryptorDecryptor::new(enc_key)?;
+    login.decrypt(&encdec)
+}
+
+#[handle_error(Error)]
+fn encrypt_fields(sec_fields: SecureLoginFields, enc_key: &str) -> ApiResult<String> {
+    let encdec = encryption::EncryptorDecryptor::new(enc_key)?;
+    sec_fields.encrypt(&encdec)
+}
+
+#[handle_error(Error)]
+fn decrypt_fields(sec_fields: String, enc_key: &str) -> ApiResult<SecureLoginFields> {
+    let encdec = encryption::EncryptorDecryptor::new(enc_key)?;
+    SecureLoginFields::decrypt(&sec_fields, &encdec)
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/logins/login.rs.html b/book/rust-docs/src/logins/login.rs.html new file mode 100644 index 0000000000..56ea1f9fcd --- /dev/null +++ b/book/rust-docs/src/logins/login.rs.html @@ -0,0 +1,2655 @@ +login.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
+540
+541
+542
+543
+544
+545
+546
+547
+548
+549
+550
+551
+552
+553
+554
+555
+556
+557
+558
+559
+560
+561
+562
+563
+564
+565
+566
+567
+568
+569
+570
+571
+572
+573
+574
+575
+576
+577
+578
+579
+580
+581
+582
+583
+584
+585
+586
+587
+588
+589
+590
+591
+592
+593
+594
+595
+596
+597
+598
+599
+600
+601
+602
+603
+604
+605
+606
+607
+608
+609
+610
+611
+612
+613
+614
+615
+616
+617
+618
+619
+620
+621
+622
+623
+624
+625
+626
+627
+628
+629
+630
+631
+632
+633
+634
+635
+636
+637
+638
+639
+640
+641
+642
+643
+644
+645
+646
+647
+648
+649
+650
+651
+652
+653
+654
+655
+656
+657
+658
+659
+660
+661
+662
+663
+664
+665
+666
+667
+668
+669
+670
+671
+672
+673
+674
+675
+676
+677
+678
+679
+680
+681
+682
+683
+684
+685
+686
+687
+688
+689
+690
+691
+692
+693
+694
+695
+696
+697
+698
+699
+700
+701
+702
+703
+704
+705
+706
+707
+708
+709
+710
+711
+712
+713
+714
+715
+716
+717
+718
+719
+720
+721
+722
+723
+724
+725
+726
+727
+728
+729
+730
+731
+732
+733
+734
+735
+736
+737
+738
+739
+740
+741
+742
+743
+744
+745
+746
+747
+748
+749
+750
+751
+752
+753
+754
+755
+756
+757
+758
+759
+760
+761
+762
+763
+764
+765
+766
+767
+768
+769
+770
+771
+772
+773
+774
+775
+776
+777
+778
+779
+780
+781
+782
+783
+784
+785
+786
+787
+788
+789
+790
+791
+792
+793
+794
+795
+796
+797
+798
+799
+800
+801
+802
+803
+804
+805
+806
+807
+808
+809
+810
+811
+812
+813
+814
+815
+816
+817
+818
+819
+820
+821
+822
+823
+824
+825
+826
+827
+828
+829
+830
+831
+832
+833
+834
+835
+836
+837
+838
+839
+840
+841
+842
+843
+844
+845
+846
+847
+848
+849
+850
+851
+852
+853
+854
+855
+856
+857
+858
+859
+860
+861
+862
+863
+864
+865
+866
+867
+868
+869
+870
+871
+872
+873
+874
+875
+876
+877
+878
+879
+880
+881
+882
+883
+884
+885
+886
+887
+888
+889
+890
+891
+892
+893
+894
+895
+896
+897
+898
+899
+900
+901
+902
+903
+904
+905
+906
+907
+908
+909
+910
+911
+912
+913
+914
+915
+916
+917
+918
+919
+920
+921
+922
+923
+924
+925
+926
+927
+928
+929
+930
+931
+932
+933
+934
+935
+936
+937
+938
+939
+940
+941
+942
+943
+944
+945
+946
+947
+948
+949
+950
+951
+952
+953
+954
+955
+956
+957
+958
+959
+960
+961
+962
+963
+964
+965
+966
+967
+968
+969
+970
+971
+972
+973
+974
+975
+976
+977
+978
+979
+980
+981
+982
+983
+984
+985
+986
+987
+988
+989
+990
+991
+992
+993
+994
+995
+996
+997
+998
+999
+1000
+1001
+1002
+1003
+1004
+1005
+1006
+1007
+1008
+1009
+1010
+1011
+1012
+1013
+1014
+1015
+1016
+1017
+1018
+1019
+1020
+1021
+1022
+1023
+1024
+1025
+1026
+1027
+1028
+1029
+1030
+1031
+1032
+1033
+1034
+1035
+1036
+1037
+1038
+1039
+1040
+1041
+1042
+1043
+1044
+1045
+1046
+1047
+1048
+1049
+1050
+1051
+1052
+1053
+1054
+1055
+1056
+1057
+1058
+1059
+1060
+1061
+1062
+1063
+1064
+1065
+1066
+1067
+1068
+1069
+1070
+1071
+1072
+1073
+1074
+1075
+1076
+1077
+1078
+1079
+1080
+1081
+1082
+1083
+1084
+1085
+1086
+1087
+1088
+1089
+1090
+1091
+1092
+1093
+1094
+1095
+1096
+1097
+1098
+1099
+1100
+1101
+1102
+1103
+1104
+1105
+1106
+1107
+1108
+1109
+1110
+1111
+1112
+1113
+1114
+1115
+1116
+1117
+1118
+1119
+1120
+1121
+1122
+1123
+1124
+1125
+1126
+1127
+1128
+1129
+1130
+1131
+1132
+1133
+1134
+1135
+1136
+1137
+1138
+1139
+1140
+1141
+1142
+1143
+1144
+1145
+1146
+1147
+1148
+1149
+1150
+1151
+1152
+1153
+1154
+1155
+1156
+1157
+1158
+1159
+1160
+1161
+1162
+1163
+1164
+1165
+1166
+1167
+1168
+1169
+1170
+1171
+1172
+1173
+1174
+1175
+1176
+1177
+1178
+1179
+1180
+1181
+1182
+1183
+1184
+1185
+1186
+1187
+1188
+1189
+1190
+1191
+1192
+1193
+1194
+1195
+1196
+1197
+1198
+1199
+1200
+1201
+1202
+1203
+1204
+1205
+1206
+1207
+1208
+1209
+1210
+1211
+1212
+1213
+1214
+1215
+1216
+1217
+1218
+1219
+1220
+1221
+1222
+1223
+1224
+1225
+1226
+1227
+1228
+1229
+1230
+1231
+1232
+1233
+1234
+1235
+1236
+1237
+1238
+1239
+1240
+1241
+1242
+1243
+1244
+1245
+1246
+1247
+1248
+1249
+1250
+1251
+1252
+1253
+1254
+1255
+1256
+1257
+1258
+1259
+1260
+1261
+1262
+1263
+1264
+1265
+1266
+1267
+1268
+1269
+1270
+1271
+1272
+1273
+1274
+1275
+1276
+1277
+1278
+1279
+1280
+1281
+1282
+1283
+1284
+1285
+1286
+1287
+1288
+1289
+1290
+1291
+1292
+1293
+1294
+1295
+1296
+1297
+1298
+1299
+1300
+1301
+1302
+1303
+1304
+1305
+1306
+1307
+1308
+1309
+1310
+1311
+1312
+1313
+1314
+1315
+1316
+1317
+1318
+1319
+1320
+1321
+1322
+1323
+1324
+1325
+1326
+1327
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//  N.B. if you're making a documentation change here, you might also want to make it in:
+//
+//    * The API docs in ../ios/Logins/LoginRecord.swift
+//    * The API docs in ../android/src/main/java/mozilla/appservices/logins/ServerPassword.kt
+//    * The android-components docs at
+//      https://github.com/mozilla-mobile/android-components/tree/master/components/service/sync-logins
+//
+//  We'll figure out a more scalable approach to maintaining all those docs at some point...
+
+//! # Login Structs
+//!
+//! This module defines a number of core structs for Logins. They are:
+//! * [`LoginEntry`] A login entry by the user.  This includes the username/password, the site it
+//!   was submitted to, etc.  [`LoginEntry`] does not store data specific to a DB record.
+//! * [`Login`] - A [`LoginEntry`] plus DB record information.  This includes the GUID and metadata
+//!   like time_last_used.
+//! * [`EncryptedLogin`] -- A Login above with the username/password data encrypted.
+//! * [`LoginFields`], [`SecureLoginFields`], [`RecordFields`] -- These group the common fields in the
+//!   structs above.
+//!
+//! Why so many structs for similar data?  Consider some common use cases in a hypothetical browser
+//! (currently no browsers act exactly like this, although Fenix/android-components comes close):
+//!
+//! - User visits a page with a login form.
+//!   - We inform the user if there are saved logins that can be autofilled.  We use the
+//!     `LoginDb.get_by_base_domain()` which returns a `Vec<EncryptedLogin>`.  We don't decrypt the
+//!     logins because we want to avoid requiring the encryption key at this point, which would
+//!     force the user to authenticate.  Note: this is aspirational at this point, no actual
+//!     implementations follow this flow.  Still, we want application-services to support it.
+//!   - If the user chooses to autofill, we decrypt the logins into a `Vec<Login>`.  We need to
+//!     decrypt at this point to display the username and autofill the password if they select one.
+//!   - When the user selects a login, we can use the already decrypted data from `Login` to fill
+//!     in the form.
+//! - User chooses to save a login for autofilling later.
+//!    - We present the user with a dialog that:
+//!       - Displays a header that differentiates between different types of save: adding a new
+//!         login, updating an existing login, filling in a blank username, etc.
+//!       - Allows the user to tweak the username, in case we failed to detect the form field
+//!         correctly.  This may affect which header should be shown.
+//!    - Here we use `find_login_to_update()` which returns an `Option<Login>`.  Returning a login
+//!      that has decrypted data avoids forcing the consumer code to decrypt the username again.
+//!
+//! # Login
+//! This has the complete set of data about a login. Very closely related is the
+//! "sync payload", defined in sync/payload.rs, which handles all aspects of the JSON serialization.
+//! It contains the following fields:
+//! - `record`: A [`RecordFields`] struct.
+//! - fields: A [`LoginFields`] struct.
+//! - sec_fields: A [`SecureLoginFields`] struct.
+//!
+//! # LoginEntry
+//! The struct used to add or update logins. This has the plain-text version of the fields that are
+//! stored encrypted, so almost all uses of an LoginEntry struct will also require the
+//! encryption key to be known and passed in.    [LoginDB] methods that save data typically input
+//! [LoginEntry] instances.  This allows the DB code to handle dupe-checking issues like
+//! determining which login record should be updated for a newly sumbitted [LoginEntry].
+//! It contains the following fields:
+//! - fields: A [`LoginFields`] struct.
+//! - sec_fields: A [`SecureLoginFields`] struct.
+//!
+//! # EncryptedLogin
+//! Encrypted version of [`Login`].  [LoginDB] methods that return data typically return [EncryptedLogin]
+//! this allows deferring decryption, and therefore user authentication, until the secure data is needed.
+//! It contains the following fields
+//! - `record`: A [`RecordFields`] struct.
+//! - `fields`: A [`LoginFields`] struct.
+//! - `sec_fields`: The secure fields as an encrypted string
+//!
+//! # SecureLoginFields
+//! The struct used to hold the fields which are stored encrypted. It contains:
+//! - username: A string.
+//! - password: A string.
+//!
+//! # LoginFields
+//!
+//! The core set of fields, use by both [`Login`] and [`LoginEntry`]
+//! It contains the following fields:
+//!
+//! - `origin`:  The origin at which this login can be used, as a string.
+//!
+//!   The login should only be used on sites that match this origin (for whatever definition
+//!   of "matches" makes sense at the application level, e.g. eTLD+1 matching).
+//!   This field is required, must be a valid origin in punycode format, and must not be
+//!   set to the empty string.
+//!
+//!   Examples of valid `origin` values include:
+//!   - "https://site.com"
+//!   - "http://site.com:1234"
+//!   - "ftp://ftp.site.com"
+//!   - "moz-proxy://127.0.0.1:8888"
+//!   - "chrome://MyLegacyExtension"
+//!   - "file://"
+//!   - "https://\[::1\]"
+//!
+//!   If invalid data is received in this field (either from the application, or via sync)
+//!   then the logins store will attempt to coerce it into valid data by:
+//!   - truncating full URLs to just their origin component, if it is not an opaque origin
+//!   - converting values with non-ascii characters into punycode
+//!
+//!   **XXX TODO:**
+//!   - Add a field with the original unicode versions of the URLs instead of punycode?
+//!
+//! - `sec_fields`: The `username` and `password` for the site, stored as a encrypted JSON
+//!    representation of an `SecureLoginFields`.
+//!
+//!   This field is required and usually encrypted.  There are two different value types:
+//!       - Plantext empty string: Used for deleted records
+//!       - Encrypted value: The credentials associated with the login.
+//!
+//! - `http_realm`:  The challenge string for HTTP Basic authentication, if any.
+//!
+//!   If present, the login should only be used in response to a HTTP Basic Auth
+//!   challenge that specifies a matching realm. For legacy reasons this string may not
+//!   contain null bytes, carriage returns or newlines.
+//!
+//!   If this field is set to the empty string, this indicates a wildcard match on realm.
+//!
+//!   This field must not be present if `form_action_origin` is set, since they indicate different types
+//!   of login (HTTP-Auth based versus form-based). Exactly one of `http_realm` and `form_action_origin`
+//!   must be present.
+//!
+//! - `form_action_origin`:  The target origin of forms in which this login can be used, if any, as a string.
+//!
+//!   If present, the login should only be used in forms whose target submission URL matches this origin.
+//!   This field must be a valid origin or one of the following special cases:
+//!   - An empty string, which is a wildcard match for any origin.
+//!   - The single character ".", which is equivalent to the empty string
+//!   - The string "javascript:", which matches any form with javascript target URL.
+//!
+//!   This field must not be present if `http_realm` is set, since they indicate different types of login
+//!   (HTTP-Auth based versus form-based). Exactly one of `http_realm` and `form_action_origin` must be present.
+//!
+//!   If invalid data is received in this field (either from the application, or via sync) then the
+//!   logins store will attempt to coerce it into valid data by:
+//!   - truncating full URLs to just their origin component
+//!   - converting origins with non-ascii characters into punycode
+//!   - replacing invalid values with null if a valid 'http_realm' field is present
+//!
+//! - `username_field`:  The name of the form field into which the 'username' should be filled, if any.
+//!
+//!   This value is stored if provided by the application, but does not imply any restrictions on
+//!   how the login may be used in practice. For legacy reasons this string may not contain null
+//!   bytes, carriage returns or newlines. This field must be empty unless `form_action_origin` is set.
+//!
+//!   If invalid data is received in this field (either from the application, or via sync)
+//!   then the logins store will attempt to coerce it into valid data by:
+//!   - setting to the empty string if 'form_action_origin' is not present
+//!
+//! - `password_field`:  The name of the form field into which the 'password' should be filled, if any.
+//!
+//!   This value is stored if provided by the application, but does not imply any restrictions on
+//!   how the login may be used in practice. For legacy reasons this string may not contain null
+//!   bytes, carriage returns or newlines. This field must be empty unless `form_action_origin` is set.
+//!
+//!   If invalid data is received in this field (either from the application, or via sync)
+//!   then the logins store will attempt to coerce it into valid data by:
+//!   - setting to the empty string if 'form_action_origin' is not present
+//!
+//! # RecordFields
+//!
+//! This contains data relating to the login database record -- both on the local instance and
+//! synced to other browsers.
+//! It contains the following fields:
+//! - `id`:  A unique string identifier for this record.
+//!
+//!   Consumers may assume that `id` contains only "safe" ASCII characters but should otherwise
+//!   treat this it as an opaque identifier. These are generated as needed.
+//!
+//! - `timesUsed`:  A lower bound on the number of times the password from this record has been used, as an integer.
+//!
+//!   Applications should use the `touch()` method of the logins store to indicate when a password
+//!   has been used, and should ensure that they only count uses of the actual `password` field
+//!   (so for example, copying the `password` field to the clipboard should count as a "use", but
+//!   copying just the `username` field should not).
+//!
+//!   This number may not record uses that occurred on other devices, since some legacy
+//!   sync clients do not record this information. It may be zero for records obtained
+//!   via sync that have never been used locally.
+//!
+//!   When merging duplicate records, the two usage counts are summed.
+//!
+//!   This field is managed internally by the logins store by default and does not need to
+//!   be set explicitly, although any application-provided value will be preserved when creating
+//!   a new record.
+//!
+//!   If invalid data is received in this field (either from the application, or via sync)
+//!   then the logins store will attempt to coerce it into valid data by:
+//!   - replacing missing or negative values with 0
+//!
+//!   **XXX TODO:**
+//!   - test that we prevent this counter from moving backwards.
+//!   - test fixups of missing or negative values
+//!   - test that we correctly merge dupes
+//!
+//! - `time_created`: An upper bound on the time of creation of this login, in integer milliseconds from the unix epoch.
+//!
+//!   This is an upper bound because some legacy sync clients do not record this information.
+//!
+//!   Note that this field is typically a timestamp taken from the local machine clock, so it
+//!   may be wildly inaccurate if the client does not have an accurate clock.
+//!
+//!   This field is managed internally by the logins store by default and does not need to
+//!   be set explicitly, although any application-provided value will be preserved when creating
+//!   a new record.
+//!
+//!   When merging duplicate records, the smallest non-zero value is taken.
+//!
+//!   If invalid data is received in this field (either from the application, or via sync)
+//!   then the logins store will attempt to coerce it into valid data by:
+//!   - replacing missing or negative values with the current time
+//!
+//!   **XXX TODO:**
+//!   - test that we prevent this timestamp from moving backwards.
+//!   - test fixups of missing or negative values
+//!   - test that we correctly merge dupes
+//!
+//! - `time_last_used`: A lower bound on the time of last use of this login, in integer milliseconds from the unix epoch.
+//!
+//!   This is a lower bound because some legacy sync clients do not record this information;
+//!   in that case newer clients set `timeLastUsed` when they use the record for the first time.
+//!
+//!   Note that this field is typically a timestamp taken from the local machine clock, so it
+//!   may be wildly inaccurate if the client does not have an accurate clock.
+//!
+//!   This field is managed internally by the logins store by default and does not need to
+//!   be set explicitly, although any application-provided value will be preserved when creating
+//!   a new record.
+//!
+//!   When merging duplicate records, the largest non-zero value is taken.
+//!
+//!   If invalid data is received in this field (either from the application, or via sync)
+//!   then the logins store will attempt to coerce it into valid data by:
+//!   - removing negative values
+//!
+//!   **XXX TODO:**
+//!   - test that we prevent this timestamp from moving backwards.
+//!   - test fixups of missing or negative values
+//!   - test that we correctly merge dupes
+//!
+//! - `time_password_changed`: A lower bound on the time that the `password` field was last changed, in integer
+//!                          milliseconds from the unix epoch.
+//!
+//!   Changes to other fields (such as `username`) are not reflected in this timestamp.
+//!   This is a lower bound because some legacy sync clients do not record this information;
+//!   in that case newer clients set `time_password_changed` when they change the `password` field.
+//!
+//!   Note that this field is typically a timestamp taken from the local machine clock, so it
+//!   may be wildly inaccurate if the client does not have an accurate clock.
+//!
+//!   This field is managed internally by the logins store by default and does not need to
+//!   be set explicitly, although any application-provided value will be preserved when creating
+//!   a new record.
+//!
+//!   When merging duplicate records, the largest non-zero value is taken.
+//!
+//!   If invalid data is received in this field (either from the application, or via sync)
+//!   then the logins store will attempt to coerce it into valid data by:
+//!   - removing negative values
+//!
+//!   **XXX TODO:**
+//!   - test that we prevent this timestamp from moving backwards.
+//!   - test that we don't set this for changes to other fields.
+//!   - test that we correctly merge dupes
+//!
+//!
+//! In order to deal with data from legacy clients in a robust way, it is necessary to be able to build
+//! and manipulate all these `Login` structs that contain invalid data.  The non-encrypted structs
+//! implement the `ValidateAndFixup` trait, providing the following methods which can be used by
+//! callers to ensure that they're only working with valid records:
+//!
+//! - `Login::check_valid()`:    Checks valdity of a login record, returning `()` if it is valid
+//!                              or an error if it is not.
+//!
+//! - `Login::fixup()`:   Returns either the existing login if it is valid, a clone with invalid fields
+//!                       fixed up if it was safe to do so, or an error if the login is irreparably invalid.
+
+use crate::{encryption::EncryptorDecryptor, error::*};
+use rusqlite::Row;
+use serde_derive::*;
+use sync_guid::Guid;
+use url::Url;
+
+// LoginEntry fields that are stored in cleartext
+#[derive(Debug, Clone, Hash, PartialEq, Eq, Default)]
+pub struct LoginFields {
+    pub origin: String,
+    pub form_action_origin: Option<String>,
+    pub http_realm: Option<String>,
+    pub username_field: String,
+    pub password_field: String,
+}
+
+impl LoginFields {
+    /// Internal helper for validation and fixups of an "origin" stored as
+    /// a string.
+    fn validate_and_fixup_origin(origin: &str) -> Result<Option<String>> {
+        // Check we can parse the origin, then use the normalized version of it.
+        match Url::parse(origin) {
+            Ok(mut u) => {
+                // Presumably this is a faster path than always setting?
+                if u.path() != "/"
+                    || u.fragment().is_some()
+                    || u.query().is_some()
+                    || u.username() != "/"
+                    || u.password().is_some()
+                {
+                    // Not identical - we only want the origin part, so kill
+                    // any other parts which may exist.
+                    // But first special case `file://` URLs which always
+                    // resolve to `file://`
+                    if u.scheme() == "file" {
+                        return Ok(if origin == "file://" {
+                            None
+                        } else {
+                            Some("file://".into())
+                        });
+                    }
+                    u.set_path("");
+                    u.set_fragment(None);
+                    u.set_query(None);
+                    let _ = u.set_username("");
+                    let _ = u.set_password(None);
+                    let mut href = String::from(u);
+                    // We always store without the trailing "/" which Urls have.
+                    if href.ends_with('/') {
+                        href.pop().expect("url must have a length");
+                    }
+                    if origin != href {
+                        // Needs to be fixed up.
+                        return Ok(Some(href));
+                    }
+                }
+                Ok(None)
+            }
+            Err(e) => {
+                breadcrumb!(
+                    "Error parsing login origin: {e:?} ({})",
+                    error_support::redact_url(origin)
+                );
+                // We can't fixup completely invalid records, so always throw.
+                Err(InvalidLogin::IllegalOrigin.into())
+            }
+        }
+    }
+}
+
+/// LoginEntry fields that are stored encrypted
+#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize, Default)]
+pub struct SecureLoginFields {
+    // - Username cannot be null, use the empty string instead
+    // - Password can't be empty or null (enforced in the ValidateAndFixup code)
+    //
+    // This matches the desktop behavior:
+    // https://searchfox.org/mozilla-central/rev/d3683dbb252506400c71256ef3994cdbdfb71ada/toolkit/components/passwordmgr/LoginManager.jsm#260-267
+
+    // Because we store the json version of this in the DB, and that's the only place the json
+    // is used, we rename the fields to short names, just to reduce the overhead in the DB.
+    #[serde(rename = "u")]
+    pub username: String,
+    #[serde(rename = "p")]
+    pub password: String,
+}
+
+impl SecureLoginFields {
+    pub fn encrypt(&self, encdec: &EncryptorDecryptor) -> Result<String> {
+        encdec.encrypt_struct(&self, "encrypt SecureLoginFields")
+    }
+
+    pub fn decrypt(ciphertext: &str, encdec: &EncryptorDecryptor) -> Result<Self> {
+        encdec.decrypt_struct(ciphertext, "decrypt SecureLoginFields")
+    }
+}
+
+/// Login data specific to database records
+#[derive(Debug, Clone, Hash, PartialEq, Eq, Default)]
+pub struct RecordFields {
+    pub id: String,
+    pub time_created: i64,
+    pub time_password_changed: i64,
+    pub time_last_used: i64,
+    pub times_used: i64,
+}
+
+/// A login entered by the user
+#[derive(Debug, Clone, Hash, PartialEq, Eq, Default)]
+pub struct LoginEntry {
+    pub fields: LoginFields,
+    pub sec_fields: SecureLoginFields,
+}
+
+/// A login stored in the database
+#[derive(Debug, Clone, Hash, PartialEq, Eq, Default)]
+pub struct Login {
+    pub record: RecordFields,
+    pub fields: LoginFields,
+    pub sec_fields: SecureLoginFields,
+}
+
+impl Login {
+    #[inline]
+    pub fn guid(&self) -> Guid {
+        Guid::from_string(self.record.id.clone())
+    }
+
+    pub fn entry(&self) -> LoginEntry {
+        LoginEntry {
+            fields: self.fields.clone(),
+            sec_fields: self.sec_fields.clone(),
+        }
+    }
+
+    pub fn encrypt(self, encdec: &EncryptorDecryptor) -> Result<EncryptedLogin> {
+        Ok(EncryptedLogin {
+            record: self.record,
+            fields: self.fields,
+            sec_fields: self.sec_fields.encrypt(encdec)?,
+        })
+    }
+}
+
+/// A login stored in the database
+#[derive(Debug, Clone, Hash, PartialEq, Eq, Default)]
+pub struct EncryptedLogin {
+    pub record: RecordFields,
+    pub fields: LoginFields,
+    pub sec_fields: String,
+}
+
+impl EncryptedLogin {
+    #[inline]
+    pub fn guid(&self) -> Guid {
+        Guid::from_string(self.record.id.clone())
+    }
+
+    // TODO: Remove this: https://github.com/mozilla/application-services/issues/4185
+    #[inline]
+    pub fn guid_str(&self) -> &str {
+        &self.record.id
+    }
+
+    pub fn decrypt(self, encdec: &EncryptorDecryptor) -> Result<Login> {
+        Ok(Login {
+            record: self.record,
+            fields: self.fields,
+            sec_fields: SecureLoginFields::decrypt(&self.sec_fields, encdec)?,
+        })
+    }
+
+    pub fn decrypt_fields(&self, encdec: &EncryptorDecryptor) -> Result<SecureLoginFields> {
+        SecureLoginFields::decrypt(&self.sec_fields, encdec)
+    }
+
+    pub(crate) fn from_row(row: &Row<'_>) -> Result<EncryptedLogin> {
+        let login = EncryptedLogin {
+            record: RecordFields {
+                id: row.get("guid")?,
+                time_created: row.get("timeCreated")?,
+                // Might be null
+                time_last_used: row
+                    .get::<_, Option<i64>>("timeLastUsed")?
+                    .unwrap_or_default(),
+
+                time_password_changed: row.get("timePasswordChanged")?,
+                times_used: row.get("timesUsed")?,
+            },
+            fields: LoginFields {
+                origin: row.get("origin")?,
+                http_realm: row.get("httpRealm")?,
+
+                form_action_origin: row.get("formActionOrigin")?,
+
+                username_field: string_or_default(row, "usernameField")?,
+                password_field: string_or_default(row, "passwordField")?,
+            },
+            sec_fields: row.get("secFields")?,
+        };
+        // XXX - we used to perform a fixup here, but that seems heavy-handed
+        // and difficult - we now only do that on add/insert when we have the
+        // encryption key.
+        Ok(login)
+    }
+}
+
+fn string_or_default(row: &Row<'_>, col: &str) -> Result<String> {
+    Ok(row.get::<_, Option<String>>(col)?.unwrap_or_default())
+}
+
+pub trait ValidateAndFixup {
+    // Our validate and fixup functions.
+    fn check_valid(&self) -> Result<()>
+    where
+        Self: Sized,
+    {
+        self.validate_and_fixup(false)?;
+        Ok(())
+    }
+
+    fn fixup(self) -> Result<Self>
+    where
+        Self: Sized,
+    {
+        match self.maybe_fixup()? {
+            None => Ok(self),
+            Some(login) => Ok(login),
+        }
+    }
+
+    fn maybe_fixup(&self) -> Result<Option<Self>>
+    where
+        Self: Sized,
+    {
+        self.validate_and_fixup(true)
+    }
+
+    // validates, and optionally fixes, a struct. If fixup is false and there is a validation
+    // issue, an `Err` is returned. If fixup is true and a problem was fixed, and `Ok(Some<Self>)`
+    // is returned with the fixed version. If there was no validation problem, `Ok(None)` is
+    // returned.
+    fn validate_and_fixup(&self, fixup: bool) -> Result<Option<Self>>
+    where
+        Self: Sized;
+}
+
+impl ValidateAndFixup for LoginFields {
+    /// Internal helper for doing validation and fixups.
+    fn validate_and_fixup(&self, fixup: bool) -> Result<Option<Self>> {
+        // XXX TODO: we've definitely got more validation and fixups to add here!
+
+        let mut maybe_fixed = None;
+
+        /// A little helper to magic a Some(self.clone()) into existence when needed.
+        macro_rules! get_fixed_or_throw {
+            ($err:expr) => {
+                // This is a block expression returning a local variable,
+                // entirely so we can give it an explicit type declaration.
+                {
+                    if !fixup {
+                        return Err($err.into());
+                    }
+                    log::warn!("Fixing login record {:?}", $err);
+                    let fixed: Result<&mut Self> =
+                        Ok(maybe_fixed.get_or_insert_with(|| self.clone()));
+                    fixed
+                }
+            };
+        }
+
+        if self.origin.is_empty() {
+            return Err(InvalidLogin::EmptyOrigin.into());
+        }
+
+        if self.form_action_origin.is_some() && self.http_realm.is_some() {
+            get_fixed_or_throw!(InvalidLogin::BothTargets)?.http_realm = None;
+        }
+
+        if self.form_action_origin.is_none() && self.http_realm.is_none() {
+            return Err(InvalidLogin::NoTarget.into());
+        }
+
+        let form_action_origin = self.form_action_origin.clone().unwrap_or_default();
+        let http_realm = maybe_fixed
+            .as_ref()
+            .unwrap_or(self)
+            .http_realm
+            .clone()
+            .unwrap_or_default();
+
+        let field_data = [
+            ("form_action_origin", &form_action_origin),
+            ("http_realm", &http_realm),
+            ("origin", &self.origin),
+            ("username_field", &self.username_field),
+            ("password_field", &self.password_field),
+        ];
+
+        for (field_name, field_value) in &field_data {
+            // Nuls are invalid.
+            if field_value.contains('\0') {
+                return Err(InvalidLogin::IllegalFieldValue {
+                    field_info: format!("`{}` contains Nul", field_name),
+                }
+                .into());
+            }
+
+            // Newlines are invalid in Desktop for all the fields here.
+            if field_value.contains('\n') || field_value.contains('\r') {
+                return Err(InvalidLogin::IllegalFieldValue {
+                    field_info: format!("`{}` contains newline", field_name),
+                }
+                .into());
+            }
+        }
+
+        // Desktop doesn't like fields with the below patterns
+        if self.username_field == "." {
+            return Err(InvalidLogin::IllegalFieldValue {
+                field_info: "`username_field` is a period".into(),
+            }
+            .into());
+        }
+
+        // Check we can parse the origin, then use the normalized version of it.
+        if let Some(fixed) = Self::validate_and_fixup_origin(&self.origin)? {
+            get_fixed_or_throw!(InvalidLogin::IllegalFieldValue {
+                field_info: "Origin is not normalized".into()
+            })?
+            .origin = fixed;
+        }
+
+        match &maybe_fixed.as_ref().unwrap_or(self).form_action_origin {
+            None => {
+                if !self.username_field.is_empty() {
+                    get_fixed_or_throw!(InvalidLogin::IllegalFieldValue {
+                        field_info: "username_field must be empty when form_action_origin is null"
+                            .into()
+                    })?
+                    .username_field
+                    .clear();
+                }
+                if !self.password_field.is_empty() {
+                    get_fixed_or_throw!(InvalidLogin::IllegalFieldValue {
+                        field_info: "password_field must be empty when form_action_origin is null"
+                            .into()
+                    })?
+                    .password_field
+                    .clear();
+                }
+            }
+            Some(href) => {
+                // "", ".", and "javascript:" are special cases documented at the top of this file.
+                if href == "." {
+                    // A bit of a special case - if we are being asked to fixup, we replace
+                    // "." with an empty string - but if not fixing up we don't complain.
+                    if fixup {
+                        maybe_fixed
+                            .get_or_insert_with(|| self.clone())
+                            .form_action_origin = Some("".into());
+                    }
+                } else if !href.is_empty() && href != "javascript:" {
+                    if let Some(fixed) = Self::validate_and_fixup_origin(href)? {
+                        get_fixed_or_throw!(InvalidLogin::IllegalFieldValue {
+                            field_info: "form_action_origin is not normalized".into()
+                        })?
+                        .form_action_origin = Some(fixed);
+                    }
+                }
+            }
+        }
+
+        Ok(maybe_fixed)
+    }
+}
+
+impl ValidateAndFixup for SecureLoginFields {
+    /// We don't actually have fixups.
+    fn validate_and_fixup(&self, _fixup: bool) -> Result<Option<Self>> {
+        // \r\n chars are valid in desktop for some reason, so we allow them here too.
+        if self.username.contains('\0') {
+            return Err(InvalidLogin::IllegalFieldValue {
+                field_info: "`username` contains Nul".into(),
+            }
+            .into());
+        }
+        if self.password.is_empty() {
+            return Err(InvalidLogin::EmptyPassword.into());
+        }
+        if self.password.contains('\0') {
+            return Err(InvalidLogin::IllegalFieldValue {
+                field_info: "`password` contains Nul".into(),
+            }
+            .into());
+        }
+        Ok(None)
+    }
+}
+
+impl ValidateAndFixup for LoginEntry {
+    fn validate_and_fixup(&self, fixup: bool) -> Result<Option<Self>> {
+        let new_fields = self.fields.validate_and_fixup(fixup)?;
+        let new_sec_fields = self.sec_fields.validate_and_fixup(fixup)?;
+        Ok(match (new_fields, new_sec_fields) {
+            (Some(fields), Some(sec_fields)) => Some(Self { fields, sec_fields }),
+            (Some(fields), None) => Some(Self {
+                fields,
+                sec_fields: self.sec_fields.clone(),
+            }),
+            (None, Some(sec_fields)) => Some(Self {
+                fields: self.fields.clone(),
+                sec_fields,
+            }),
+            (None, None) => None,
+        })
+    }
+}
+
+#[cfg(test)]
+pub mod test_utils {
+    use super::*;
+    use crate::encryption::test_utils::encrypt_struct;
+
+    // Factory function to make a new login
+    //
+    // It uses the guid to create a unique origin/form_action_origin
+    pub fn enc_login(id: &str, password: &str) -> EncryptedLogin {
+        let sec_fields = SecureLoginFields {
+            username: "user".to_string(),
+            password: password.to_string(),
+        };
+        EncryptedLogin {
+            record: RecordFields {
+                id: id.to_string(),
+                ..Default::default()
+            },
+            fields: LoginFields {
+                form_action_origin: Some(format!("https://{}.example.com", id)),
+                origin: format!("https://{}.example.com", id),
+                ..Default::default()
+            },
+            sec_fields: encrypt_struct(&sec_fields),
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_url_fixups() -> Result<()> {
+        // Start with URLs which are all valid and already normalized.
+        for input in &[
+            // The list of valid origins documented at the top of this file.
+            "https://site.com",
+            "http://site.com:1234",
+            "ftp://ftp.site.com",
+            "moz-proxy://127.0.0.1:8888",
+            "chrome://MyLegacyExtension",
+            "file://",
+            "https://[::1]",
+        ] {
+            assert_eq!(LoginFields::validate_and_fixup_origin(input)?, None);
+        }
+
+        // And URLs which get normalized.
+        for (input, output) in &[
+            ("https://site.com/", "https://site.com"),
+            ("http://site.com:1234/", "http://site.com:1234"),
+            ("http://example.com/foo?query=wtf#bar", "http://example.com"),
+            ("http://example.com/foo#bar", "http://example.com"),
+            (
+                "http://username:password@example.com/",
+                "http://example.com",
+            ),
+            ("http://😍.com/", "http://xn--r28h.com"),
+            ("https://[0:0:0:0:0:0:0:1]", "https://[::1]"),
+            // All `file://` URLs normalize to exactly `file://`. See #2384 for
+            // why we might consider changing that later.
+            ("file:///", "file://"),
+            ("file://foo/bar", "file://"),
+            ("file://foo/bar/", "file://"),
+            ("moz-proxy://127.0.0.1:8888/", "moz-proxy://127.0.0.1:8888"),
+            (
+                "moz-proxy://127.0.0.1:8888/foo",
+                "moz-proxy://127.0.0.1:8888",
+            ),
+            ("chrome://MyLegacyExtension/", "chrome://MyLegacyExtension"),
+            (
+                "chrome://MyLegacyExtension/foo",
+                "chrome://MyLegacyExtension",
+            ),
+        ] {
+            assert_eq!(
+                LoginFields::validate_and_fixup_origin(input)?,
+                Some((*output).into())
+            );
+        }
+        Ok(())
+    }
+
+    #[test]
+    fn test_check_valid() {
+        #[derive(Debug, Clone)]
+        struct TestCase {
+            login: LoginEntry,
+            should_err: bool,
+            expected_err: &'static str,
+        }
+
+        let valid_login = LoginEntry {
+            fields: LoginFields {
+                origin: "https://www.example.com".into(),
+                http_realm: Some("https://www.example.com".into()),
+                ..Default::default()
+            },
+            sec_fields: SecureLoginFields {
+                username: "test".into(),
+                password: "test".into(),
+            },
+        };
+
+        let login_with_empty_origin = LoginEntry {
+            fields: LoginFields {
+                origin: "".into(),
+                http_realm: Some("https://www.example.com".into()),
+                ..Default::default()
+            },
+            sec_fields: SecureLoginFields {
+                username: "test".into(),
+                password: "test".into(),
+            },
+        };
+
+        let login_with_empty_password = LoginEntry {
+            fields: LoginFields {
+                origin: "https://www.example.com".into(),
+                http_realm: Some("https://www.example.com".into()),
+                ..Default::default()
+            },
+            sec_fields: SecureLoginFields {
+                username: "test".into(),
+                password: "".into(),
+            },
+        };
+
+        let login_with_form_submit_and_http_realm = LoginEntry {
+            fields: LoginFields {
+                origin: "https://www.example.com".into(),
+                http_realm: Some("https://www.example.com".into()),
+                form_action_origin: Some("https://www.example.com".into()),
+                ..Default::default()
+            },
+            sec_fields: SecureLoginFields {
+                username: "".into(),
+                password: "test".into(),
+            },
+        };
+
+        let login_without_form_submit_or_http_realm = LoginEntry {
+            fields: LoginFields {
+                origin: "https://www.example.com".into(),
+                ..Default::default()
+            },
+            sec_fields: SecureLoginFields {
+                username: "".into(),
+                password: "test".into(),
+            },
+        };
+
+        let login_with_legacy_form_submit_and_http_realm = LoginEntry {
+            fields: LoginFields {
+                origin: "https://www.example.com".into(),
+                form_action_origin: Some("".into()),
+                ..Default::default()
+            },
+            sec_fields: SecureLoginFields {
+                username: "".into(),
+                password: "test".into(),
+            },
+        };
+
+        let login_with_null_http_realm = LoginEntry {
+            fields: LoginFields {
+                origin: "https://www.example.com".into(),
+                http_realm: Some("https://www.example.\0com".into()),
+                ..Default::default()
+            },
+            sec_fields: SecureLoginFields {
+                username: "test".into(),
+                password: "test".into(),
+            },
+        };
+
+        let login_with_null_username = LoginEntry {
+            fields: LoginFields {
+                origin: "https://www.example.com".into(),
+                http_realm: Some("https://www.example.com".into()),
+                ..Default::default()
+            },
+            sec_fields: SecureLoginFields {
+                username: "\0".into(),
+                password: "test".into(),
+            },
+        };
+
+        let login_with_null_password = LoginEntry {
+            fields: LoginFields {
+                origin: "https://www.example.com".into(),
+                http_realm: Some("https://www.example.com".into()),
+                ..Default::default()
+            },
+            sec_fields: SecureLoginFields {
+                username: "username".into(),
+                password: "test\0".into(),
+            },
+        };
+
+        let login_with_newline_origin = LoginEntry {
+            fields: LoginFields {
+                origin: "\rhttps://www.example.com".into(),
+                http_realm: Some("https://www.example.com".into()),
+                ..Default::default()
+            },
+            sec_fields: SecureLoginFields {
+                username: "test".into(),
+                password: "test".into(),
+            },
+        };
+
+        let login_with_newline_username_field = LoginEntry {
+            fields: LoginFields {
+                origin: "https://www.example.com".into(),
+                http_realm: Some("https://www.example.com".into()),
+                username_field: "\n".into(),
+                ..Default::default()
+            },
+            sec_fields: SecureLoginFields {
+                username: "test".into(),
+                password: "test".into(),
+            },
+        };
+
+        let login_with_newline_realm = LoginEntry {
+            fields: LoginFields {
+                origin: "https://www.example.com".into(),
+                http_realm: Some("foo\nbar".into()),
+                ..Default::default()
+            },
+            sec_fields: SecureLoginFields {
+                username: "test".into(),
+                password: "test".into(),
+            },
+        };
+
+        let login_with_newline_password = LoginEntry {
+            fields: LoginFields {
+                origin: "https://www.example.com".into(),
+                http_realm: Some("https://www.example.com".into()),
+                ..Default::default()
+            },
+            sec_fields: SecureLoginFields {
+                username: "test".into(),
+                password: "test\n".into(),
+            },
+        };
+
+        let login_with_period_username_field = LoginEntry {
+            fields: LoginFields {
+                origin: "https://www.example.com".into(),
+                http_realm: Some("https://www.example.com".into()),
+                username_field: ".".into(),
+                ..Default::default()
+            },
+            sec_fields: SecureLoginFields {
+                username: "test".into(),
+                password: "test".into(),
+            },
+        };
+
+        let login_with_period_form_action_origin = LoginEntry {
+            fields: LoginFields {
+                form_action_origin: Some(".".into()),
+                origin: "https://www.example.com".into(),
+                ..Default::default()
+            },
+            sec_fields: SecureLoginFields {
+                username: "test".into(),
+                password: "test".into(),
+            },
+        };
+
+        let login_with_javascript_form_action_origin = LoginEntry {
+            fields: LoginFields {
+                form_action_origin: Some("javascript:".into()),
+                origin: "https://www.example.com".into(),
+                ..Default::default()
+            },
+            sec_fields: SecureLoginFields {
+                username: "test".into(),
+                password: "test".into(),
+            },
+        };
+
+        let login_with_malformed_origin_parens = LoginEntry {
+            fields: LoginFields {
+                origin: " (".into(),
+                http_realm: Some("https://www.example.com".into()),
+                ..Default::default()
+            },
+            sec_fields: SecureLoginFields {
+                username: "test".into(),
+                password: "test".into(),
+            },
+        };
+
+        let login_with_host_unicode = LoginEntry {
+            fields: LoginFields {
+                origin: "http://💖.com".into(),
+                http_realm: Some("https://www.example.com".into()),
+                ..Default::default()
+            },
+            sec_fields: SecureLoginFields {
+                username: "test".into(),
+                password: "test".into(),
+            },
+        };
+
+        let login_with_origin_trailing_slash = LoginEntry {
+            fields: LoginFields {
+                origin: "https://www.example.com/".into(),
+                http_realm: Some("https://www.example.com".into()),
+                ..Default::default()
+            },
+            sec_fields: SecureLoginFields {
+                username: "test".into(),
+                password: "test".into(),
+            },
+        };
+
+        let login_with_origin_expanded_ipv6 = LoginEntry {
+            fields: LoginFields {
+                origin: "https://[0:0:0:0:0:0:1:1]".into(),
+                http_realm: Some("https://www.example.com".into()),
+                ..Default::default()
+            },
+            sec_fields: SecureLoginFields {
+                username: "test".into(),
+                password: "test".into(),
+            },
+        };
+
+        let login_with_unknown_protocol = LoginEntry {
+            fields: LoginFields {
+                origin: "moz-proxy://127.0.0.1:8888".into(),
+                http_realm: Some("https://www.example.com".into()),
+                ..Default::default()
+            },
+            sec_fields: SecureLoginFields {
+                username: "test".into(),
+                password: "test".into(),
+            },
+        };
+
+        let test_cases = [
+            TestCase {
+                login: valid_login,
+                should_err: false,
+                expected_err: "",
+            },
+            TestCase {
+                login: login_with_empty_origin,
+                should_err: true,
+                expected_err: "Invalid login: Origin is empty",
+            },
+            TestCase {
+                login: login_with_empty_password,
+                should_err: true,
+                expected_err: "Invalid login: Password is empty",
+            },
+            TestCase {
+                login: login_with_form_submit_and_http_realm,
+                should_err: true,
+                expected_err: "Invalid login: Both `formActionOrigin` and `httpRealm` are present",
+            },
+            TestCase {
+                login: login_without_form_submit_or_http_realm,
+                should_err: true,
+                expected_err:
+                    "Invalid login: Neither `formActionOrigin` or `httpRealm` are present",
+            },
+            TestCase {
+                login: login_with_null_http_realm,
+                should_err: true,
+                expected_err: "Invalid login: Login has illegal field: `http_realm` contains Nul",
+            },
+            TestCase {
+                login: login_with_null_username,
+                should_err: true,
+                expected_err: "Invalid login: Login has illegal field: `username` contains Nul",
+            },
+            TestCase {
+                login: login_with_null_password,
+                should_err: true,
+                expected_err: "Invalid login: Login has illegal field: `password` contains Nul",
+            },
+            TestCase {
+                login: login_with_newline_origin,
+                should_err: true,
+                expected_err: "Invalid login: Login has illegal field: `origin` contains newline",
+            },
+            TestCase {
+                login: login_with_newline_realm,
+                should_err: true,
+                expected_err:
+                    "Invalid login: Login has illegal field: `http_realm` contains newline",
+            },
+            TestCase {
+                login: login_with_newline_username_field,
+                should_err: true,
+                expected_err:
+                    "Invalid login: Login has illegal field: `username_field` contains newline",
+            },
+            TestCase {
+                login: login_with_newline_password,
+                should_err: false,
+                expected_err: "",
+            },
+            TestCase {
+                login: login_with_period_username_field,
+                should_err: true,
+                expected_err:
+                    "Invalid login: Login has illegal field: `username_field` is a period",
+            },
+            TestCase {
+                login: login_with_period_form_action_origin,
+                should_err: false,
+                expected_err: "",
+            },
+            TestCase {
+                login: login_with_javascript_form_action_origin,
+                should_err: false,
+                expected_err: "",
+            },
+            TestCase {
+                login: login_with_malformed_origin_parens,
+                should_err: true,
+                expected_err: "Invalid login: Login has illegal origin",
+            },
+            TestCase {
+                login: login_with_host_unicode,
+                should_err: true,
+                expected_err: "Invalid login: Login has illegal field: Origin is not normalized",
+            },
+            TestCase {
+                login: login_with_origin_trailing_slash,
+                should_err: true,
+                expected_err: "Invalid login: Login has illegal field: Origin is not normalized",
+            },
+            TestCase {
+                login: login_with_origin_expanded_ipv6,
+                should_err: true,
+                expected_err: "Invalid login: Login has illegal field: Origin is not normalized",
+            },
+            TestCase {
+                login: login_with_unknown_protocol,
+                should_err: false,
+                expected_err: "",
+            },
+            TestCase {
+                login: login_with_legacy_form_submit_and_http_realm,
+                should_err: false,
+                expected_err: "",
+            },
+        ];
+
+        for tc in &test_cases {
+            let actual = tc.login.check_valid();
+
+            if tc.should_err {
+                assert!(actual.is_err(), "{:#?}", tc);
+                assert_eq!(
+                    tc.expected_err,
+                    actual.unwrap_err().to_string(),
+                    "{:#?}",
+                    tc,
+                );
+            } else {
+                assert!(actual.is_ok(), "{:#?}", tc);
+                assert!(
+                    tc.login.clone().fixup().is_ok(),
+                    "Fixup failed after check_valid passed: {:#?}",
+                    &tc,
+                );
+            }
+        }
+    }
+
+    #[test]
+    fn test_fixup() {
+        #[derive(Debug, Default)]
+        struct TestCase {
+            login: LoginEntry,
+            fixedup_host: Option<&'static str>,
+            fixedup_form_action_origin: Option<String>,
+        }
+
+        // Note that most URL fixups are tested above, but we have one or 2 here.
+        let login_with_full_url = LoginEntry {
+            fields: LoginFields {
+                origin: "http://example.com/foo?query=wtf#bar".into(),
+                form_action_origin: Some("http://example.com/foo?query=wtf#bar".into()),
+                ..Default::default()
+            },
+            sec_fields: SecureLoginFields {
+                username: "test".into(),
+                password: "test".into(),
+            },
+        };
+
+        let login_with_host_unicode = LoginEntry {
+            fields: LoginFields {
+                origin: "http://😍.com".into(),
+                form_action_origin: Some("http://😍.com".into()),
+                ..Default::default()
+            },
+            sec_fields: SecureLoginFields {
+                username: "test".into(),
+                password: "test".into(),
+            },
+        };
+
+        let login_with_period_fsu = LoginEntry {
+            fields: LoginFields {
+                origin: "https://example.com".into(),
+                form_action_origin: Some(".".into()),
+                ..Default::default()
+            },
+            sec_fields: SecureLoginFields {
+                username: "test".into(),
+                password: "test".into(),
+            },
+        };
+        let login_with_empty_fsu = LoginEntry {
+            fields: LoginFields {
+                origin: "https://example.com".into(),
+                form_action_origin: Some("".into()),
+                ..Default::default()
+            },
+            sec_fields: SecureLoginFields {
+                username: "test".into(),
+                password: "test".into(),
+            },
+        };
+
+        let login_with_form_submit_and_http_realm = LoginEntry {
+            fields: LoginFields {
+                origin: "https://www.example.com".into(),
+                form_action_origin: Some("https://www.example.com".into()),
+                // If both http_realm and form_action_origin are specified, we drop
+                // the former when fixing up. So for this test we must have an
+                // invalid value in http_realm to ensure we don't validate a value
+                // we end up dropping.
+                http_realm: Some("\n".into()),
+                ..Default::default()
+            },
+            sec_fields: SecureLoginFields {
+                username: "".into(),
+                password: "test".into(),
+            },
+        };
+
+        let test_cases = [
+            TestCase {
+                login: login_with_full_url,
+                fixedup_host: "http://example.com".into(),
+                fixedup_form_action_origin: Some("http://example.com".into()),
+            },
+            TestCase {
+                login: login_with_host_unicode,
+                fixedup_host: "http://xn--r28h.com".into(),
+                fixedup_form_action_origin: Some("http://xn--r28h.com".into()),
+            },
+            TestCase {
+                login: login_with_period_fsu,
+                fixedup_form_action_origin: Some("".into()),
+                ..TestCase::default()
+            },
+            TestCase {
+                login: login_with_form_submit_and_http_realm,
+                fixedup_form_action_origin: Some("https://www.example.com".into()),
+                ..TestCase::default()
+            },
+            TestCase {
+                login: login_with_empty_fsu,
+                // Should still be empty.
+                fixedup_form_action_origin: Some("".into()),
+                ..TestCase::default()
+            },
+        ];
+
+        for tc in &test_cases {
+            let login = tc.login.clone().fixup().expect("should work");
+            if let Some(expected) = tc.fixedup_host {
+                assert_eq!(
+                    login.fields.origin, expected,
+                    "origin not fixed in {:#?}",
+                    tc
+                );
+            }
+            assert_eq!(
+                login.fields.form_action_origin, tc.fixedup_form_action_origin,
+                "form_action_origin not fixed in {:#?}",
+                tc,
+            );
+            login.check_valid().unwrap_or_else(|e| {
+                panic!("Fixup produces invalid record: {:#?}", (e, &tc, &login));
+            });
+            assert_eq!(
+                login.clone().fixup().unwrap(),
+                login,
+                "fixup did not reach fixed point for testcase: {:#?}",
+                tc,
+            );
+        }
+    }
+
+    #[test]
+    fn test_secure_fields_serde() {
+        let sf = SecureLoginFields {
+            username: "foo".into(),
+            password: "pwd".into(),
+        };
+        assert_eq!(
+            serde_json::to_string(&sf).unwrap(),
+            r#"{"u":"foo","p":"pwd"}"#
+        );
+        let got: SecureLoginFields = serde_json::from_str(r#"{"u": "user", "p": "p"}"#).unwrap();
+        let expected = SecureLoginFields {
+            username: "user".into(),
+            password: "p".into(),
+        };
+        assert_eq!(got, expected);
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/logins/schema.rs.html b/book/rust-docs/src/logins/schema.rs.html new file mode 100644 index 0000000000..fa14ca7e03 --- /dev/null +++ b/book/rust-docs/src/logins/schema.rs.html @@ -0,0 +1,633 @@ +schema.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Logins Schema v4
+//! ================
+//!
+//! The schema we use is a evolution of the firefox-ios logins database format.
+//! There are three tables:
+//!
+//! - `loginsL`: The local table.
+//! - `loginsM`: The mirror table.
+//! - `loginsSyncMeta`: The table used to to store various sync metadata.
+//!
+//! ## `loginsL`
+//!
+//! This stores local login information, also known as the "overlay".
+//!
+//! `loginsL` is essentially unchanged from firefox-ios, however note the
+//! semantic change v4 makes to timestamp fields (which is explained in more
+//! detail in the [COMMON_COLS] documentation).
+//!
+//! It is important to note that `loginsL` is not guaranteed to be present for
+//! all records. Synced records may only exist in `loginsM` (although this is
+//! not guaranteed). In either case, queries should read from both `loginsL` and
+//! `loginsM`.
+//!
+//! ### `loginsL` Columns
+//!
+//! Contains all fields in [COMMON_COLS], as well as the following additional
+//! columns:
+//!
+//! - `local_modified`: A millisecond local timestamp indicating when the record
+//!   was changed locally, or NULL if the record has never been changed locally.
+//!
+//! - `is_deleted`: A boolean indicating whether or not this record is a
+//!   tombstone.
+//!
+//! - `sync_status`: A `SyncStatus` enum value, one of
+//!
+//!     - `0` (`SyncStatus::Synced`): Indicating that the record has been synced
+//!
+//!     - `1` (`SyncStatus::Changed`): Indicating that the record should be
+//!       has changed locally and is known to exist on the server.
+//!
+//!     - `2` (`SyncStatus::New`): Indicating that the record has never been
+//!       synced, or we have been reset since the last time it synced.
+//!
+//! ## `loginsM`
+//!
+//! This stores server-side login information, also known as the "mirror".
+//!
+//! Like `loginsL`, `loginM` has not changed from firefox-ios, beyond the
+//! change to store timestamps as milliseconds explained in [COMMON_COLS].
+//!
+//! Also like `loginsL`, `loginsM` is not guaranteed to have rows for all
+//! records. It should not have rows for records which were not synced!
+//!
+//! It is important to note that `loginsL` is not guaranteed to be present for
+//! all records. Synced records may only exist in `loginsM`! Queries should
+//! test against both!
+//!
+//! ### `loginsM` Columns
+//!
+//! Contains all fields in [COMMON_COLS], as well as the following additional
+//! columns:
+//!
+//! - `server_modified`: the most recent server-modification timestamp
+//!   ([sync15::ServerTimestamp]) we've seen for this record. Stored as
+//!   a millisecond value.
+//!
+//! - `is_overridden`: A boolean indicating whether or not the mirror contents
+//!   are invalid, and that we should defer to the data stored in `loginsL`.
+//!
+//! ## `loginsSyncMeta`
+//!
+//! This is a simple key-value table based on the `moz_meta` table in places.
+//! This table was added (by this rust crate) in version 4, and so is not
+//! present in firefox-ios.
+//!
+//! Currently it is used to store two items:
+//!
+//! 1. The last sync timestamp is stored under [LAST_SYNC_META_KEY], a
+//!    `sync15::ServerTimestamp` stored in integer milliseconds.
+//!
+//! 2. The persisted sync state machine information is stored under
+//!    [GLOBAL_STATE_META_KEY]. This is a `sync15::GlobalState` stored as
+//!    JSON.
+//!
+
+use crate::error::*;
+use lazy_static::lazy_static;
+use rusqlite::Connection;
+use sql_support::ConnExt;
+
+/// Version 1: SQLCipher -> plaintext migration.
+/// Version 2: addition of `loginsM.enc_unknown_fields`.
+pub(super) const VERSION: i64 = 2;
+
+/// Every column shared by both tables except for `id`
+///
+/// Note: `timeCreated`, `timeLastUsed`, and `timePasswordChanged` are in
+/// milliseconds. This is in line with how the server and Desktop handle it, but
+/// counter to how firefox-ios handles it (hence needing to fix them up
+/// firefox-ios on schema upgrade from 3, the last firefox-ios password schema
+/// version).
+///
+/// The reason for breaking from how firefox-ios does things is just because it
+/// complicates the code to have multiple kinds of timestamps, for very little
+/// benefit. It also makes it unclear what's stored on the server, leading to
+/// further confusion.
+///
+/// However, note that the `local_modified` (of `loginsL`) and `server_modified`
+/// (of `loginsM`) are stored as milliseconds as well both on firefox-ios and
+/// here (and so they do not need to be updated with the `timeLastUsed`/
+/// `timePasswordChanged`/`timeCreated` timestamps.
+pub const COMMON_COLS: &str = "
+    guid,
+    secFields,
+    origin,
+    httpRealm,
+    formActionOrigin,
+    usernameField,
+    passwordField,
+    timeCreated,
+    timeLastUsed,
+    timePasswordChanged,
+    timesUsed
+";
+
+const COMMON_SQL: &str = "
+    id                  INTEGER PRIMARY KEY AUTOINCREMENT,
+    origin              TEXT NOT NULL,
+    -- Exactly one of httpRealm or formActionOrigin should be set
+    httpRealm           TEXT,
+    formActionOrigin    TEXT,
+    usernameField       TEXT,
+    passwordField       TEXT,
+    timesUsed           INTEGER NOT NULL DEFAULT 0,
+    timeCreated         INTEGER NOT NULL,
+    timeLastUsed        INTEGER,
+    timePasswordChanged INTEGER NOT NULL,
+    secFields           TEXT,
+    guid                TEXT NOT NULL UNIQUE
+";
+
+lazy_static! {
+    static ref CREATE_LOCAL_TABLE_SQL: String = format!(
+        "CREATE TABLE IF NOT EXISTS loginsL (
+            {common_sql},
+            -- Milliseconds, or NULL if never modified locally.
+            local_modified INTEGER,
+
+            is_deleted     TINYINT NOT NULL DEFAULT 0,
+            sync_status    TINYINT NOT NULL DEFAULT 0
+        )",
+        common_sql = COMMON_SQL
+    );
+    static ref CREATE_MIRROR_TABLE_SQL: String = format!(
+        "CREATE TABLE IF NOT EXISTS loginsM (
+            {common_sql},
+            -- Milliseconds (a sync15::ServerTimestamp multiplied by
+            -- 1000 and truncated)
+            server_modified INTEGER NOT NULL,
+            is_overridden   TINYINT NOT NULL DEFAULT 0,
+            -- fields on incoming records we don't know about and roundtrip.
+            -- a serde_json::Value::Object as an encrypted string.
+            enc_unknown_fields   TEXT
+        )",
+        common_sql = COMMON_SQL
+    );
+    static ref SET_VERSION_SQL: String =
+        format!("PRAGMA user_version = {version}", version = VERSION);
+}
+
+const CREATE_META_TABLE_SQL: &str = "
+    CREATE TABLE IF NOT EXISTS loginsSyncMeta (
+        key TEXT PRIMARY KEY,
+        value NOT NULL
+    )
+";
+
+const CREATE_OVERRIDE_ORIGIN_INDEX_SQL: &str = "
+    CREATE INDEX IF NOT EXISTS idx_loginsM_is_overridden_origin
+    ON loginsM (is_overridden, origin)
+";
+
+const CREATE_DELETED_ORIGIN_INDEX_SQL: &str = "
+    CREATE INDEX IF NOT EXISTS idx_loginsL_is_deleted_origin
+    ON loginsL (is_deleted, origin)
+";
+
+pub(crate) static LAST_SYNC_META_KEY: &str = "last_sync_time";
+pub(crate) static GLOBAL_STATE_META_KEY: &str = "global_state_v2";
+pub(crate) static GLOBAL_SYNCID_META_KEY: &str = "global_sync_id";
+pub(crate) static COLLECTION_SYNCID_META_KEY: &str = "passwords_sync_id";
+
+pub(crate) fn init(db: &Connection) -> Result<()> {
+    let user_version = db.query_one::<i64>("PRAGMA user_version")?;
+    log::warn!("user_version: {}", user_version);
+    if user_version == 0 {
+        return create(db);
+    }
+    if user_version != VERSION {
+        if user_version < VERSION {
+            upgrade(db, user_version)?;
+        } else {
+            log::warn!(
+                "Loaded future schema version {} (we only understand version {}). \
+                 Optimistically ",
+                user_version,
+                VERSION
+            )
+        }
+    }
+    Ok(())
+}
+
+// Allow the redundant Ok() here.  It will make more sense once we have an actual upgrade function.
+#[allow(clippy::unnecessary_wraps)]
+fn upgrade(db: &Connection, from: i64) -> Result<()> {
+    log::debug!("Upgrading schema from {} to {}", from, VERSION);
+    if from == VERSION {
+        return Ok(());
+    }
+    assert_ne!(
+        from, 0,
+        "Upgrading from user_version = 0 should already be handled (in `init`)"
+    );
+
+    // Schema upgrades.
+    if from == 1 {
+        // Just one new nullable column makes this fairly easy
+        db.execute_batch("ALTER TABLE loginsM ADD enc_unknown_fields TEXT;")?;
+    }
+    // XXX - next migration, be sure to:
+    // from = 2;
+    // if from == 2 ...
+    db.execute_batch(&SET_VERSION_SQL)?;
+    Ok(())
+}
+
+pub(crate) fn create(db: &Connection) -> Result<()> {
+    log::debug!("Creating schema");
+    db.execute_all(&[
+        &*CREATE_LOCAL_TABLE_SQL,
+        &*CREATE_MIRROR_TABLE_SQL,
+        CREATE_OVERRIDE_ORIGIN_INDEX_SQL,
+        CREATE_DELETED_ORIGIN_INDEX_SQL,
+        CREATE_META_TABLE_SQL,
+        &*SET_VERSION_SQL,
+    ])?;
+    Ok(())
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::LoginDb;
+    use rusqlite::Connection;
+
+    #[test]
+    fn test_create_schema() {
+        let db = LoginDb::open_in_memory().unwrap();
+        // should be VERSION.
+        let version = db.query_one::<i64>("PRAGMA user_version").unwrap();
+        assert_eq!(version, VERSION);
+    }
+
+    #[test]
+    fn test_upgrade_v1() {
+        // manually setup a V1 schema.
+        let connection = Connection::open_in_memory().unwrap();
+        connection
+            .execute_batch(
+                "
+                CREATE TABLE IF NOT EXISTS loginsM (
+                    -- this was common_sql as at v1
+                    id                  INTEGER PRIMARY KEY AUTOINCREMENT,
+                    origin              TEXT NOT NULL,
+                    httpRealm           TEXT,
+                    formActionOrigin    TEXT,
+                    usernameField       TEXT,
+                    passwordField       TEXT,
+                    timesUsed           INTEGER NOT NULL DEFAULT 0,
+                    timeCreated         INTEGER NOT NULL,
+                    timeLastUsed        INTEGER,
+                    timePasswordChanged INTEGER NOT NULL,
+                    secFields           TEXT,
+                    guid                TEXT NOT NULL UNIQUE,
+                    server_modified     INTEGER NOT NULL,
+                    is_overridden       TINYINT NOT NULL DEFAULT 0
+                    -- note enc_unknown_fields missing
+                );
+            ",
+            )
+            .unwrap();
+        // Call `create` to create the rest of the schema - the "if not exists" means loginsM
+        // will remain as v1.
+        create(&connection).unwrap();
+        // but that set the version to VERSION - set it back to 1 so our upgrade code runs.
+        connection
+            .execute_batch("PRAGMA user_version = 1;")
+            .unwrap();
+
+        // Now open the DB - it will create loginsL for us and migrate loginsM.
+        let db = LoginDb::with_connection(connection).unwrap();
+        // all migrations should have succeeded.
+        let version = db.query_one::<i64>("PRAGMA user_version").unwrap();
+        assert_eq!(version, VERSION);
+
+        // and ensure sql selecting the new column works.
+        db.execute_batch("SELECT enc_unknown_fields FROM loginsM")
+            .unwrap();
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/logins/store.rs.html b/book/rust-docs/src/logins/store.rs.html new file mode 100644 index 0000000000..220086a690 --- /dev/null +++ b/book/rust-docs/src/logins/store.rs.html @@ -0,0 +1,675 @@ +store.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+use crate::db::LoginDb;
+use crate::encryption::EncryptorDecryptor;
+use crate::error::*;
+use crate::login::{EncryptedLogin, Login, LoginEntry};
+use crate::LoginsSyncEngine;
+use parking_lot::Mutex;
+use std::path::Path;
+use std::sync::{Arc, Weak};
+use sync15::engine::{EngineSyncAssociation, SyncEngine, SyncEngineId};
+
+// Our "sync manager" will use whatever is stashed here.
+lazy_static::lazy_static! {
+    // Mutex: just taken long enough to update the inner stuff - needed
+    //        to wrap the RefCell as they aren't `Sync`
+    static ref STORE_FOR_MANAGER: Mutex<Weak<LoginStore>> = Mutex::new(Weak::new());
+}
+
+/// Called by the sync manager to get a sync engine via the store previously
+/// registered with the sync manager.
+pub fn get_registered_sync_engine(engine_id: &SyncEngineId) -> Option<Box<dyn SyncEngine>> {
+    let weak = STORE_FOR_MANAGER.lock();
+    match weak.upgrade() {
+        None => None,
+        Some(store) => match create_sync_engine(store, engine_id) {
+            Ok(engine) => Some(engine),
+            Err(e) => {
+                report_error!("logins-sync-engine-create-error", "{e}");
+                None
+            }
+        },
+    }
+}
+
+fn create_sync_engine(
+    store: Arc<LoginStore>,
+    engine_id: &SyncEngineId,
+) -> Result<Box<dyn SyncEngine>> {
+    match engine_id {
+        SyncEngineId::Passwords => Ok(Box::new(LoginsSyncEngine::new(Arc::clone(&store))?)),
+        // panicing here seems reasonable - it's a static error if this
+        // it hit, not something that runtime conditions can influence.
+        _ => unreachable!("can't provide unknown engine: {}", engine_id),
+    }
+}
+
+pub struct LoginStore {
+    pub db: Mutex<LoginDb>,
+}
+
+impl LoginStore {
+    #[handle_error(Error)]
+    pub fn new(path: impl AsRef<Path>) -> ApiResult<Self> {
+        let db = Mutex::new(LoginDb::open(path)?);
+        Ok(Self { db })
+    }
+
+    pub fn new_from_db(db: LoginDb) -> Self {
+        Self { db: Mutex::new(db) }
+    }
+
+    #[handle_error(Error)]
+    pub fn new_in_memory() -> ApiResult<Self> {
+        let db = Mutex::new(LoginDb::open_in_memory()?);
+        Ok(Self { db })
+    }
+
+    #[handle_error(Error)]
+    pub fn list(&self) -> ApiResult<Vec<EncryptedLogin>> {
+        self.db.lock().get_all()
+    }
+
+    #[handle_error(Error)]
+    pub fn get(&self, id: &str) -> ApiResult<Option<EncryptedLogin>> {
+        self.db.lock().get_by_id(id)
+    }
+
+    #[handle_error(Error)]
+    pub fn get_by_base_domain(&self, base_domain: &str) -> ApiResult<Vec<EncryptedLogin>> {
+        self.db.lock().get_by_base_domain(base_domain)
+    }
+
+    #[handle_error(Error)]
+    pub fn find_login_to_update(
+        &self,
+        entry: LoginEntry,
+        enc_key: &str,
+    ) -> ApiResult<Option<Login>> {
+        let encdec = EncryptorDecryptor::new(enc_key)?;
+        self.db.lock().find_login_to_update(entry, &encdec)
+    }
+
+    #[handle_error(Error)]
+    pub fn touch(&self, id: &str) -> ApiResult<()> {
+        self.db.lock().touch(id)
+    }
+
+    #[handle_error(Error)]
+    pub fn delete(&self, id: &str) -> ApiResult<bool> {
+        self.db.lock().delete(id)
+    }
+
+    #[handle_error(Error)]
+    pub fn wipe(&self) -> ApiResult<()> {
+        // This should not be exposed - it wipes the server too and there's
+        // no good reason to expose that to consumers. wipe_local makes some
+        // sense though.
+        // TODO: this is exposed to android-components consumers - we should
+        // check if anyone actually calls it.
+        let db = self.db.lock();
+        let scope = db.begin_interrupt_scope()?;
+        db.wipe(&scope)?;
+        Ok(())
+    }
+
+    #[handle_error(Error)]
+    pub fn wipe_local(&self) -> ApiResult<()> {
+        self.db.lock().wipe_local()?;
+        Ok(())
+    }
+
+    #[handle_error(Error)]
+    pub fn reset(self: Arc<Self>) -> ApiResult<()> {
+        // Reset should not exist here - all resets should be done via the
+        // sync manager. It seems that actual consumers don't use this, but
+        // some tests do, so it remains for now.
+        let engine = LoginsSyncEngine::new(Arc::clone(&self))?;
+        engine.do_reset(&EngineSyncAssociation::Disconnected)?;
+        Ok(())
+    }
+
+    #[handle_error(Error)]
+    pub fn update(&self, id: &str, entry: LoginEntry, enc_key: &str) -> ApiResult<EncryptedLogin> {
+        let encdec = EncryptorDecryptor::new(enc_key)?;
+        self.db.lock().update(id, entry, &encdec)
+    }
+
+    #[handle_error(Error)]
+    pub fn add(&self, entry: LoginEntry, enc_key: &str) -> ApiResult<EncryptedLogin> {
+        let encdec = EncryptorDecryptor::new(enc_key)?;
+        self.db.lock().add(entry, &encdec)
+    }
+
+    #[handle_error(Error)]
+    pub fn add_or_update(&self, entry: LoginEntry, enc_key: &str) -> ApiResult<EncryptedLogin> {
+        let encdec = EncryptorDecryptor::new(enc_key)?;
+        self.db.lock().add_or_update(entry, &encdec)
+    }
+
+    // This allows the embedding app to say "make this instance available to
+    // the sync manager". The implementation is more like "offer to sync mgr"
+    // (thereby avoiding us needing to link with the sync manager) but
+    // `register_with_sync_manager()` is logically what's happening so that's
+    // the name it gets.
+    pub fn register_with_sync_manager(self: Arc<Self>) {
+        let mut state = STORE_FOR_MANAGER.lock();
+        *state = Arc::downgrade(&self);
+    }
+
+    // this isn't exposed by uniffi - currently the
+    // only consumer of this is our "example" (and hence why they
+    // are `pub` and not `pub(crate)`).
+    // We could probably make the example work with the sync manager - but then
+    // our example would link with places and logins etc, and it's not a big
+    // deal really.
+    #[handle_error(Error)]
+    pub fn create_logins_sync_engine(self: Arc<Self>) -> ApiResult<Box<dyn SyncEngine>> {
+        Ok(Box::new(LoginsSyncEngine::new(self)?) as Box<dyn SyncEngine>)
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+    use crate::encryption::test_utils::{TEST_ENCRYPTION_KEY, TEST_ENCRYPTOR};
+    use crate::util;
+    use crate::{LoginFields, SecureLoginFields};
+    use more_asserts::*;
+    use std::cmp::Reverse;
+    use std::time::SystemTime;
+
+    fn assert_logins_equiv(a: &LoginEntry, b: &EncryptedLogin) {
+        let b_e = b.decrypt_fields(&TEST_ENCRYPTOR).unwrap();
+        assert_eq!(a.fields, b.fields);
+        assert_eq!(b_e.username, a.sec_fields.username);
+        assert_eq!(b_e.password, a.sec_fields.password);
+    }
+
+    #[test]
+    fn test_general() {
+        let store = LoginStore::new_in_memory().unwrap();
+        let list = store.list().expect("Grabbing Empty list to work");
+        assert_eq!(list.len(), 0);
+        let start_us = util::system_time_ms_i64(SystemTime::now());
+
+        let a = LoginEntry {
+            fields: LoginFields {
+                origin: "https://www.example.com".into(),
+                form_action_origin: Some("https://www.example.com".into()),
+                username_field: "user_input".into(),
+                password_field: "pass_input".into(),
+                ..Default::default()
+            },
+            sec_fields: SecureLoginFields {
+                username: "coolperson21".into(),
+                password: "p4ssw0rd".into(),
+            },
+        };
+
+        let b = LoginEntry {
+            fields: LoginFields {
+                origin: "https://www.example2.com".into(),
+                http_realm: Some("Some String Here".into()),
+                ..Default::default()
+            },
+            sec_fields: SecureLoginFields {
+                username: "asdf".into(),
+                password: "fdsa".into(),
+            },
+        };
+        let a_id = store
+            .add(a.clone(), &TEST_ENCRYPTION_KEY)
+            .expect("added a")
+            .record
+            .id;
+        let b_id = store
+            .add(b.clone(), &TEST_ENCRYPTION_KEY)
+            .expect("added b")
+            .record
+            .id;
+
+        let a_from_db = store
+            .get(&a_id)
+            .expect("Not to error getting a")
+            .expect("a to exist");
+
+        assert_logins_equiv(&a, &a_from_db);
+        assert_ge!(a_from_db.record.time_created, start_us);
+        assert_ge!(a_from_db.record.time_password_changed, start_us);
+        assert_ge!(a_from_db.record.time_last_used, start_us);
+        assert_eq!(a_from_db.record.times_used, 1);
+
+        let b_from_db = store
+            .get(&b_id)
+            .expect("Not to error getting b")
+            .expect("b to exist");
+
+        assert_logins_equiv(&LoginEntry { ..b.clone() }, &b_from_db);
+        assert_ge!(b_from_db.record.time_created, start_us);
+        assert_ge!(b_from_db.record.time_password_changed, start_us);
+        assert_ge!(b_from_db.record.time_last_used, start_us);
+        assert_eq!(b_from_db.record.times_used, 1);
+
+        let mut list = store.list().expect("Grabbing list to work");
+        assert_eq!(list.len(), 2);
+
+        let mut expect = vec![a_from_db, b_from_db.clone()];
+
+        list.sort_by_key(|b| Reverse(b.guid()));
+        expect.sort_by_key(|b| Reverse(b.guid()));
+        assert_eq!(list, expect);
+
+        store.delete(&a_id).expect("Successful delete");
+        assert!(store
+            .get(&a_id)
+            .expect("get after delete should still work")
+            .is_none());
+
+        let list = store.list().expect("Grabbing list to work");
+        assert_eq!(list.len(), 1);
+        assert_eq!(list[0], b_from_db);
+
+        let list = store
+            .get_by_base_domain("example2.com")
+            .expect("Expect a list for this origin");
+        assert_eq!(list.len(), 1);
+        assert_eq!(list[0], b_from_db);
+
+        let list = store
+            .get_by_base_domain("www.example.com")
+            .expect("Expect an empty list");
+        assert_eq!(list.len(), 0);
+
+        let now_us = util::system_time_ms_i64(SystemTime::now());
+        let b2 = LoginEntry {
+            sec_fields: SecureLoginFields {
+                username: b.sec_fields.username.to_owned(),
+                password: "newpass".into(),
+            },
+            ..b
+        };
+
+        store
+            .update(&b_id, b2.clone(), &TEST_ENCRYPTION_KEY)
+            .expect("update b should work");
+
+        let b_after_update = store
+            .get(&b_id)
+            .expect("Not to error getting b")
+            .expect("b to exist");
+
+        assert_logins_equiv(&b2, &b_after_update);
+        assert_ge!(b_after_update.record.time_created, start_us);
+        assert_le!(b_after_update.record.time_created, now_us);
+        assert_ge!(b_after_update.record.time_password_changed, now_us);
+        assert_ge!(b_after_update.record.time_last_used, now_us);
+        // Should be two even though we updated twice
+        assert_eq!(b_after_update.record.times_used, 2);
+    }
+
+    #[test]
+    fn test_sync_manager_registration() {
+        let store = Arc::new(LoginStore::new_in_memory().unwrap());
+        assert_eq!(Arc::strong_count(&store), 1);
+        assert_eq!(Arc::weak_count(&store), 0);
+        Arc::clone(&store).register_with_sync_manager();
+        assert_eq!(Arc::strong_count(&store), 1);
+        assert_eq!(Arc::weak_count(&store), 1);
+        let registered = STORE_FOR_MANAGER.lock().upgrade().expect("should upgrade");
+        assert!(Arc::ptr_eq(&store, &registered));
+        drop(registered);
+        // should be no new references
+        assert_eq!(Arc::strong_count(&store), 1);
+        assert_eq!(Arc::weak_count(&store), 1);
+        // dropping the registered object should drop the registration.
+        drop(store);
+        assert!(STORE_FOR_MANAGER.lock().upgrade().is_none());
+    }
+}
+
+#[test]
+fn test_send() {
+    fn ensure_send<T: Send>() {}
+    ensure_send::<LoginStore>();
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/logins/sync/engine.rs.html b/book/rust-docs/src/logins/sync/engine.rs.html new file mode 100644 index 0000000000..88d390a93e --- /dev/null +++ b/book/rust-docs/src/logins/sync/engine.rs.html @@ -0,0 +1,1907 @@ +engine.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
+540
+541
+542
+543
+544
+545
+546
+547
+548
+549
+550
+551
+552
+553
+554
+555
+556
+557
+558
+559
+560
+561
+562
+563
+564
+565
+566
+567
+568
+569
+570
+571
+572
+573
+574
+575
+576
+577
+578
+579
+580
+581
+582
+583
+584
+585
+586
+587
+588
+589
+590
+591
+592
+593
+594
+595
+596
+597
+598
+599
+600
+601
+602
+603
+604
+605
+606
+607
+608
+609
+610
+611
+612
+613
+614
+615
+616
+617
+618
+619
+620
+621
+622
+623
+624
+625
+626
+627
+628
+629
+630
+631
+632
+633
+634
+635
+636
+637
+638
+639
+640
+641
+642
+643
+644
+645
+646
+647
+648
+649
+650
+651
+652
+653
+654
+655
+656
+657
+658
+659
+660
+661
+662
+663
+664
+665
+666
+667
+668
+669
+670
+671
+672
+673
+674
+675
+676
+677
+678
+679
+680
+681
+682
+683
+684
+685
+686
+687
+688
+689
+690
+691
+692
+693
+694
+695
+696
+697
+698
+699
+700
+701
+702
+703
+704
+705
+706
+707
+708
+709
+710
+711
+712
+713
+714
+715
+716
+717
+718
+719
+720
+721
+722
+723
+724
+725
+726
+727
+728
+729
+730
+731
+732
+733
+734
+735
+736
+737
+738
+739
+740
+741
+742
+743
+744
+745
+746
+747
+748
+749
+750
+751
+752
+753
+754
+755
+756
+757
+758
+759
+760
+761
+762
+763
+764
+765
+766
+767
+768
+769
+770
+771
+772
+773
+774
+775
+776
+777
+778
+779
+780
+781
+782
+783
+784
+785
+786
+787
+788
+789
+790
+791
+792
+793
+794
+795
+796
+797
+798
+799
+800
+801
+802
+803
+804
+805
+806
+807
+808
+809
+810
+811
+812
+813
+814
+815
+816
+817
+818
+819
+820
+821
+822
+823
+824
+825
+826
+827
+828
+829
+830
+831
+832
+833
+834
+835
+836
+837
+838
+839
+840
+841
+842
+843
+844
+845
+846
+847
+848
+849
+850
+851
+852
+853
+854
+855
+856
+857
+858
+859
+860
+861
+862
+863
+864
+865
+866
+867
+868
+869
+870
+871
+872
+873
+874
+875
+876
+877
+878
+879
+880
+881
+882
+883
+884
+885
+886
+887
+888
+889
+890
+891
+892
+893
+894
+895
+896
+897
+898
+899
+900
+901
+902
+903
+904
+905
+906
+907
+908
+909
+910
+911
+912
+913
+914
+915
+916
+917
+918
+919
+920
+921
+922
+923
+924
+925
+926
+927
+928
+929
+930
+931
+932
+933
+934
+935
+936
+937
+938
+939
+940
+941
+942
+943
+944
+945
+946
+947
+948
+949
+950
+951
+952
+953
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use super::merge::{LocalLogin, MirrorLogin, SyncLoginData};
+use super::update_plan::UpdatePlan;
+use super::SyncStatus;
+use crate::db::CLONE_ENTIRE_MIRROR_SQL;
+use crate::encryption::EncryptorDecryptor;
+use crate::error::*;
+use crate::login::EncryptedLogin;
+use crate::schema;
+use crate::util;
+use crate::LoginDb;
+use crate::LoginStore;
+use interrupt_support::SqlInterruptScope;
+use rusqlite::named_params;
+use sql_support::ConnExt;
+use std::cell::RefCell;
+use std::collections::HashSet;
+use std::sync::Arc;
+use sync15::bso::{IncomingBso, OutgoingBso, OutgoingEnvelope};
+use sync15::engine::{CollSyncIds, CollectionRequest, EngineSyncAssociation, SyncEngine};
+use sync15::{telemetry, ServerTimestamp};
+use sync_guid::Guid;
+
+// The sync engine.
+pub struct LoginsSyncEngine {
+    pub store: Arc<LoginStore>,
+    pub scope: SqlInterruptScope,
+    pub staged: RefCell<Vec<IncomingBso>>,
+    // It's unfortunate this is an Option<>, but tricky to change because sometimes we construct
+    // an engine for, say, a `reset()` where this isn't needed or known.
+    encdec: Option<EncryptorDecryptor>,
+}
+
+impl LoginsSyncEngine {
+    fn encdec(&self) -> Result<&EncryptorDecryptor> {
+        match &self.encdec {
+            Some(encdec) => Ok(encdec),
+            None => Err(Error::EncryptionKeyMissing),
+        }
+    }
+
+    pub fn new(store: Arc<LoginStore>) -> Result<Self> {
+        let scope = store.db.lock().begin_interrupt_scope()?;
+        Ok(Self {
+            store,
+            scope,
+            staged: RefCell::new(vec![]),
+            encdec: None,
+        })
+    }
+
+    fn reconcile(
+        &self,
+        records: Vec<SyncLoginData>,
+        server_now: ServerTimestamp,
+        telem: &mut telemetry::EngineIncoming,
+    ) -> Result<UpdatePlan> {
+        let mut plan = UpdatePlan::default();
+        let encdec = self.encdec()?;
+
+        for mut record in records {
+            self.scope.err_if_interrupted()?;
+            log::debug!("Processing remote change {}", record.guid());
+            let upstream = if let Some(inbound) = record.inbound.take() {
+                inbound
+            } else {
+                log::debug!("Processing inbound deletion (always prefer)");
+                plan.plan_delete(record.guid.clone());
+                continue;
+            };
+            let upstream_time = record.inbound_ts;
+            match (record.mirror.take(), record.local.take()) {
+                (Some(mirror), Some(local)) => {
+                    log::debug!("  Conflict between remote and local, Resolving with 3WM");
+                    plan.plan_three_way_merge(
+                        local,
+                        mirror,
+                        upstream,
+                        upstream_time,
+                        server_now,
+                        encdec,
+                    )?;
+                    telem.reconciled(1);
+                }
+                (Some(_mirror), None) => {
+                    log::debug!("  Forwarding mirror to remote");
+                    plan.plan_mirror_update(upstream, upstream_time);
+                    telem.applied(1);
+                }
+                (None, Some(local)) => {
+                    log::debug!("  Conflicting record without shared parent, using newer");
+                    plan.plan_two_way_merge(&local.login, (upstream, upstream_time));
+                    telem.reconciled(1);
+                }
+                (None, None) => {
+                    if let Some(dupe) = self.find_dupe_login(&upstream.login)? {
+                        log::debug!(
+                            "  Incoming record {} was is a dupe of local record {}",
+                            upstream.guid(),
+                            dupe.guid()
+                        );
+                        plan.plan_two_way_merge(&dupe, (upstream, upstream_time));
+                    } else {
+                        log::debug!("  No dupe found, inserting into mirror");
+                        plan.plan_mirror_insert(upstream, upstream_time, false);
+                    }
+                    telem.applied(1);
+                }
+            }
+        }
+        Ok(plan)
+    }
+
+    fn execute_plan(&self, plan: UpdatePlan) -> Result<()> {
+        // Because rusqlite want a mutable reference to create a transaction
+        // (as a way to save us from ourselves), we side-step that by creating
+        // it manually.
+        let db = self.store.db.lock();
+        let tx = db.unchecked_transaction()?;
+        plan.execute(&tx, &self.scope)?;
+        tx.commit()?;
+        Ok(())
+    }
+
+    // Fetch all the data for the provided IDs.
+    // TODO: Might be better taking a fn instead of returning all of it... But that func will likely
+    // want to insert stuff while we're doing this so ugh.
+    fn fetch_login_data(
+        &self,
+        records: Vec<IncomingBso>,
+        telem: &mut telemetry::EngineIncoming,
+    ) -> Result<Vec<SyncLoginData>> {
+        let mut sync_data = Vec::with_capacity(records.len());
+        {
+            let mut seen_ids: HashSet<Guid> = HashSet::with_capacity(records.len());
+            for incoming in records.into_iter() {
+                let id = incoming.envelope.id.clone();
+                match SyncLoginData::from_bso(incoming, self.encdec()?) {
+                    Ok(v) => sync_data.push(v),
+                    Err(e) => {
+                        match e {
+                            // This is a known error with Deskop logins (see #5233), just log it
+                            // rather than reporting to sentry
+                            Error::InvalidLogin(InvalidLogin::IllegalOrigin) => {
+                                log::warn!("logins-deserialize-error: {e}");
+                            }
+                            // For all other errors, report them to Sentry
+                            _ => {
+                                report_error!(
+                                    "logins-deserialize-error",
+                                    "Failed to deserialize record {:?}: {e}",
+                                    id
+                                );
+                            }
+                        };
+                        // Ideally we'd track new_failed, but it's unclear how
+                        // much value it has.
+                        telem.failed(1);
+                    }
+                }
+                seen_ids.insert(id);
+            }
+        }
+        self.scope.err_if_interrupted()?;
+
+        sql_support::each_chunk(
+            &sync_data
+                .iter()
+                .map(|s| s.guid.as_str().to_string())
+                .collect::<Vec<String>>(),
+            |chunk, offset| -> Result<()> {
+                // pairs the bound parameter for the guid with an integer index.
+                let values_with_idx = sql_support::repeat_display(chunk.len(), ",", |i, f| {
+                    write!(f, "({},?)", i + offset)
+                });
+                let query = format!(
+                    "WITH to_fetch(guid_idx, fetch_guid) AS (VALUES {vals})
+                     SELECT
+                         {common_cols},
+                         is_overridden,
+                         server_modified,
+                         NULL as local_modified,
+                         NULL as is_deleted,
+                         NULL as sync_status,
+                         1 as is_mirror,
+                         to_fetch.guid_idx as guid_idx
+                     FROM loginsM
+                     JOIN to_fetch
+                         ON loginsM.guid = to_fetch.fetch_guid
+
+                     UNION ALL
+
+                     SELECT
+                         {common_cols},
+                         NULL as is_overridden,
+                         NULL as server_modified,
+                         local_modified,
+                         is_deleted,
+                         sync_status,
+                         0 as is_mirror,
+                         to_fetch.guid_idx as guid_idx
+                     FROM loginsL
+                     JOIN to_fetch
+                         ON loginsL.guid = to_fetch.fetch_guid",
+                    // give each VALUES item 2 entries, an index and the parameter.
+                    vals = values_with_idx,
+                    common_cols = schema::COMMON_COLS,
+                );
+
+                let db = &self.store.db.lock();
+                let mut stmt = db.prepare(&query)?;
+
+                let rows = stmt.query_and_then(rusqlite::params_from_iter(chunk), |row| {
+                    let guid_idx_i = row.get::<_, i64>("guid_idx")?;
+                    // Hitting this means our math is wrong...
+                    assert!(guid_idx_i >= 0);
+
+                    let guid_idx = guid_idx_i as usize;
+                    let is_mirror: bool = row.get("is_mirror")?;
+                    if is_mirror {
+                        sync_data[guid_idx].set_mirror(MirrorLogin::from_row(row)?)?;
+                    } else {
+                        sync_data[guid_idx].set_local(LocalLogin::from_row(row)?)?;
+                    }
+                    self.scope.err_if_interrupted()?;
+                    Ok(())
+                })?;
+                // `rows` is an Iterator<Item = Result<()>>, so we need to collect to handle the errors.
+                rows.collect::<Result<_>>()?;
+                Ok(())
+            },
+        )?;
+        Ok(sync_data)
+    }
+
+    fn fetch_outgoing(&self) -> Result<Vec<OutgoingBso>> {
+        // Taken from iOS. Arbitrarily large, so that clients that want to
+        // process deletions first can; for us it doesn't matter.
+        const TOMBSTONE_SORTINDEX: i32 = 5_000_000;
+        const DEFAULT_SORTINDEX: i32 = 1;
+        let db = self.store.db.lock();
+        let mut stmt = db.prepare_cached(&format!(
+            "SELECT L.*, M.enc_unknown_fields
+             FROM loginsL L LEFT JOIN loginsM M ON L.guid = M.guid
+             WHERE sync_status IS NOT {synced}",
+            synced = SyncStatus::Synced as u8
+        ))?;
+        let bsos = stmt.query_and_then([], |row| {
+            self.scope.err_if_interrupted()?;
+            Ok(if row.get::<_, bool>("is_deleted")? {
+                let envelope = OutgoingEnvelope {
+                    id: row.get::<_, String>("guid")?.into(),
+                    sortindex: Some(TOMBSTONE_SORTINDEX),
+                    ..Default::default()
+                };
+                OutgoingBso::new_tombstone(envelope)
+            } else {
+                let unknown = row.get::<_, Option<String>>("enc_unknown_fields")?;
+                let mut bso = EncryptedLogin::from_row(row)?.into_bso(self.encdec()?, unknown)?;
+                bso.envelope.sortindex = Some(DEFAULT_SORTINDEX);
+                bso
+            })
+        })?;
+        bsos.collect::<Result<_>>()
+    }
+
+    fn do_apply_incoming(
+        &self,
+        inbound: Vec<IncomingBso>,
+        timestamp: ServerTimestamp,
+        telem: &mut telemetry::Engine,
+    ) -> Result<Vec<OutgoingBso>> {
+        let mut incoming_telemetry = telemetry::EngineIncoming::new();
+        let data = self.fetch_login_data(inbound, &mut incoming_telemetry)?;
+        let plan = {
+            let result = self.reconcile(data, timestamp, &mut incoming_telemetry);
+            telem.incoming(incoming_telemetry);
+            result
+        }?;
+        self.execute_plan(plan)?;
+        self.fetch_outgoing()
+    }
+
+    fn set_last_sync(&self, db: &LoginDb, last_sync: ServerTimestamp) -> Result<()> {
+        log::debug!("Updating last sync to {}", last_sync);
+        let last_sync_millis = last_sync.as_millis();
+        db.put_meta(schema::LAST_SYNC_META_KEY, &last_sync_millis)
+    }
+
+    fn get_last_sync(&self, db: &LoginDb) -> Result<Option<ServerTimestamp>> {
+        let millis = db.get_meta::<i64>(schema::LAST_SYNC_META_KEY)?.unwrap();
+        Ok(Some(ServerTimestamp(millis)))
+    }
+
+    pub fn set_global_state(&self, state: &Option<String>) -> Result<()> {
+        let to_write = match state {
+            Some(ref s) => s,
+            None => "",
+        };
+        let db = self.store.db.lock();
+        db.put_meta(schema::GLOBAL_STATE_META_KEY, &to_write)
+    }
+
+    pub fn get_global_state(&self) -> Result<Option<String>> {
+        let db = self.store.db.lock();
+        db.get_meta::<String>(schema::GLOBAL_STATE_META_KEY)
+    }
+
+    fn mark_as_synchronized(&self, guids: &[&str], ts: ServerTimestamp) -> Result<()> {
+        let db = self.store.db.lock();
+        let tx = db.unchecked_transaction()?;
+        sql_support::each_chunk(guids, |chunk, _| -> Result<()> {
+            db.execute(
+                &format!(
+                    "DELETE FROM loginsM WHERE guid IN ({vars})",
+                    vars = sql_support::repeat_sql_vars(chunk.len())
+                ),
+                rusqlite::params_from_iter(chunk),
+            )?;
+            self.scope.err_if_interrupted()?;
+
+            db.execute(
+                &format!(
+                    "INSERT OR IGNORE INTO loginsM (
+                         {common_cols}, is_overridden, server_modified
+                     )
+                     SELECT {common_cols}, 0, {modified_ms_i64}
+                     FROM loginsL
+                     WHERE is_deleted = 0 AND guid IN ({vars})",
+                    common_cols = schema::COMMON_COLS,
+                    modified_ms_i64 = ts.as_millis(),
+                    vars = sql_support::repeat_sql_vars(chunk.len())
+                ),
+                rusqlite::params_from_iter(chunk),
+            )?;
+            self.scope.err_if_interrupted()?;
+
+            db.execute(
+                &format!(
+                    "DELETE FROM loginsL WHERE guid IN ({vars})",
+                    vars = sql_support::repeat_sql_vars(chunk.len())
+                ),
+                rusqlite::params_from_iter(chunk),
+            )?;
+            self.scope.err_if_interrupted()?;
+            Ok(())
+        })?;
+        self.set_last_sync(&db, ts)?;
+        tx.commit()?;
+        Ok(())
+    }
+
+    // This exists here as a public function so the store can call it. Ideally
+    // the store would not do that :) Then it can go back into the sync trait
+    // and return an anyhow::Result
+    pub fn do_reset(&self, assoc: &EngineSyncAssociation) -> Result<()> {
+        log::info!("Executing reset on password engine!");
+        let db = self.store.db.lock();
+        let tx = db.unchecked_transaction()?;
+        db.execute_all(&[
+            &CLONE_ENTIRE_MIRROR_SQL,
+            "DELETE FROM loginsM",
+            &format!("UPDATE loginsL SET sync_status = {}", SyncStatus::New as u8),
+        ])?;
+        self.set_last_sync(&db, ServerTimestamp(0))?;
+        match assoc {
+            EngineSyncAssociation::Disconnected => {
+                db.delete_meta(schema::GLOBAL_SYNCID_META_KEY)?;
+                db.delete_meta(schema::COLLECTION_SYNCID_META_KEY)?;
+            }
+            EngineSyncAssociation::Connected(ids) => {
+                db.put_meta(schema::GLOBAL_SYNCID_META_KEY, &ids.global)?;
+                db.put_meta(schema::COLLECTION_SYNCID_META_KEY, &ids.coll)?;
+            }
+        };
+        db.delete_meta(schema::GLOBAL_STATE_META_KEY)?;
+        tx.commit()?;
+        Ok(())
+    }
+
+    // It would be nice if this were a batch-ish api (e.g. takes a slice of records and finds dupes
+    // for each one if they exist)... I can't think of how to write that query, though.
+    // This is subtly different from dupe handling by the main API and maybe
+    // could be consolidated, but for now it remains sync specific.
+    pub(crate) fn find_dupe_login(&self, l: &EncryptedLogin) -> Result<Option<EncryptedLogin>> {
+        let form_submit_host_port = l
+            .fields
+            .form_action_origin
+            .as_ref()
+            .and_then(|s| util::url_host_port(s));
+        let encdec = self.encdec()?;
+        let enc_fields = l.decrypt_fields(encdec)?;
+        let args = named_params! {
+            ":origin": l.fields.origin,
+            ":http_realm": l.fields.http_realm,
+            ":form_submit": form_submit_host_port,
+        };
+        let mut query = format!(
+            "SELECT {common}
+             FROM loginsL
+             WHERE origin IS :origin
+               AND httpRealm IS :http_realm",
+            common = schema::COMMON_COLS,
+        );
+        if form_submit_host_port.is_some() {
+            // Stolen from iOS
+            query += " AND (formActionOrigin = '' OR (instr(formActionOrigin, :form_submit) > 0))";
+        } else {
+            query += " AND formActionOrigin IS :form_submit"
+        }
+        let db = self.store.db.lock();
+        let mut stmt = db.prepare_cached(&query)?;
+        for login in stmt
+            .query_and_then(args, EncryptedLogin::from_row)?
+            .collect::<Result<Vec<EncryptedLogin>>>()?
+        {
+            let this_enc_fields = login.decrypt_fields(encdec)?;
+            if enc_fields.username == this_enc_fields.username {
+                return Ok(Some(login));
+            }
+        }
+        Ok(None)
+    }
+}
+
+impl SyncEngine for LoginsSyncEngine {
+    fn collection_name(&self) -> std::borrow::Cow<'static, str> {
+        "passwords".into()
+    }
+
+    fn set_local_encryption_key(&mut self, key: &str) -> anyhow::Result<()> {
+        self.encdec = Some(EncryptorDecryptor::new(key)?);
+        Ok(())
+    }
+
+    fn stage_incoming(
+        &self,
+        mut inbound: Vec<IncomingBso>,
+        _telem: &mut telemetry::Engine,
+    ) -> anyhow::Result<()> {
+        // We don't have cross-item dependencies like bookmarks does, so we can
+        // just apply now instead of "staging"
+        self.staged.borrow_mut().append(&mut inbound);
+        Ok(())
+    }
+
+    fn apply(
+        &self,
+        timestamp: ServerTimestamp,
+        telem: &mut telemetry::Engine,
+    ) -> anyhow::Result<Vec<OutgoingBso>> {
+        let inbound = (*self.staged.borrow_mut()).drain(..).collect();
+        Ok(self.do_apply_incoming(inbound, timestamp, telem)?)
+    }
+
+    fn set_uploaded(&self, new_timestamp: ServerTimestamp, ids: Vec<Guid>) -> anyhow::Result<()> {
+        Ok(self.mark_as_synchronized(
+            &ids.iter().map(Guid::as_str).collect::<Vec<_>>(),
+            new_timestamp,
+        )?)
+    }
+
+    fn get_collection_request(
+        &self,
+        server_timestamp: ServerTimestamp,
+    ) -> anyhow::Result<Option<CollectionRequest>> {
+        let db = self.store.db.lock();
+        let since = self.get_last_sync(&db)?.unwrap_or_default();
+        Ok(if since == server_timestamp {
+            None
+        } else {
+            Some(
+                CollectionRequest::new("passwords".into())
+                    .full()
+                    .newer_than(since),
+            )
+        })
+    }
+
+    fn get_sync_assoc(&self) -> anyhow::Result<EngineSyncAssociation> {
+        let db = self.store.db.lock();
+        let global = db.get_meta(schema::GLOBAL_SYNCID_META_KEY)?;
+        let coll = db.get_meta(schema::COLLECTION_SYNCID_META_KEY)?;
+        Ok(if let (Some(global), Some(coll)) = (global, coll) {
+            EngineSyncAssociation::Connected(CollSyncIds { global, coll })
+        } else {
+            EngineSyncAssociation::Disconnected
+        })
+    }
+
+    fn reset(&self, assoc: &EngineSyncAssociation) -> anyhow::Result<()> {
+        self.do_reset(assoc)?;
+        Ok(())
+    }
+
+    fn wipe(&self) -> anyhow::Result<()> {
+        let db = self.store.db.lock();
+        db.wipe(&self.scope)?;
+        Ok(())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::db::test_utils::insert_login;
+    use crate::encryption::test_utils::{TEST_ENCRYPTION_KEY, TEST_ENCRYPTOR};
+    use crate::login::test_utils::enc_login;
+    use crate::{LoginEntry, LoginFields, RecordFields, SecureLoginFields};
+    use std::collections::HashMap;
+    use std::sync::Arc;
+
+    // Wrap sync functions for easier testing
+    fn run_fetch_login_data(
+        store: LoginStore,
+        records: Vec<IncomingBso>,
+    ) -> (Vec<SyncLoginData>, telemetry::EngineIncoming) {
+        let mut engine = LoginsSyncEngine::new(Arc::new(store)).unwrap();
+        engine
+            .set_local_encryption_key(&TEST_ENCRYPTION_KEY)
+            .unwrap();
+        let mut telem = sync15::telemetry::EngineIncoming::new();
+        (engine.fetch_login_data(records, &mut telem).unwrap(), telem)
+    }
+
+    fn run_fetch_outgoing(store: LoginStore) -> Vec<OutgoingBso> {
+        let mut engine = LoginsSyncEngine::new(Arc::new(store)).unwrap();
+        engine
+            .set_local_encryption_key(&TEST_ENCRYPTION_KEY)
+            .unwrap();
+        engine.fetch_outgoing().unwrap()
+    }
+
+    #[test]
+    fn test_fetch_login_data() {
+        // Test some common cases with fetch_login data
+        let store = LoginStore::new_in_memory().unwrap();
+        insert_login(&store.db.lock(), "updated_remotely", None, Some("password"));
+        insert_login(&store.db.lock(), "deleted_remotely", None, Some("password"));
+        insert_login(
+            &store.db.lock(),
+            "three_way_merge",
+            Some("new-local-password"),
+            Some("password"),
+        );
+
+        let (res, _) = run_fetch_login_data(
+            store,
+            vec![
+                IncomingBso::new_test_tombstone(Guid::new("deleted_remotely")),
+                enc_login("added_remotely", "password")
+                    .into_bso(&TEST_ENCRYPTOR, None)
+                    .unwrap()
+                    .to_test_incoming(),
+                enc_login("updated_remotely", "new-password")
+                    .into_bso(&TEST_ENCRYPTOR, None)
+                    .unwrap()
+                    .to_test_incoming(),
+                enc_login("three_way_merge", "new-remote-password")
+                    .into_bso(&TEST_ENCRYPTOR, None)
+                    .unwrap()
+                    .to_test_incoming(),
+            ],
+        );
+        // For simpler testing, extract/decrypt passwords and put them in a hash map
+        #[derive(Debug, PartialEq)]
+        struct SyncPasswords {
+            local: Option<String>,
+            mirror: Option<String>,
+            inbound: Option<String>,
+        }
+        let extracted_passwords: HashMap<String, SyncPasswords> = res
+            .into_iter()
+            .map(|sync_login_data| {
+                let mut guids_seen = HashSet::new();
+                let passwords = SyncPasswords {
+                    local: sync_login_data.local.map(|local_login| {
+                        guids_seen.insert(local_login.login.record.id.clone());
+                        local_login
+                            .login
+                            .decrypt_fields(&TEST_ENCRYPTOR)
+                            .unwrap()
+                            .password
+                    }),
+                    mirror: sync_login_data.mirror.map(|mirror_login| {
+                        guids_seen.insert(mirror_login.login.record.id.clone());
+                        mirror_login
+                            .login
+                            .decrypt_fields(&TEST_ENCRYPTOR)
+                            .unwrap()
+                            .password
+                    }),
+                    inbound: sync_login_data.inbound.map(|incoming| {
+                        guids_seen.insert(incoming.login.record.id.clone());
+                        incoming
+                            .login
+                            .decrypt_fields(&TEST_ENCRYPTOR)
+                            .unwrap()
+                            .password
+                    }),
+                };
+                (guids_seen.into_iter().next().unwrap(), passwords)
+            })
+            .collect();
+
+        assert_eq!(extracted_passwords.len(), 4);
+        assert_eq!(
+            extracted_passwords.get("added_remotely").unwrap(),
+            &SyncPasswords {
+                local: None,
+                mirror: None,
+                inbound: Some("password".into()),
+            }
+        );
+        assert_eq!(
+            extracted_passwords.get("updated_remotely").unwrap(),
+            &SyncPasswords {
+                local: None,
+                mirror: Some("password".into()),
+                inbound: Some("new-password".into()),
+            }
+        );
+        assert_eq!(
+            extracted_passwords.get("deleted_remotely").unwrap(),
+            &SyncPasswords {
+                local: None,
+                mirror: Some("password".into()),
+                inbound: None,
+            }
+        );
+        assert_eq!(
+            extracted_passwords.get("three_way_merge").unwrap(),
+            &SyncPasswords {
+                local: Some("new-local-password".into()),
+                mirror: Some("password".into()),
+                inbound: Some("new-remote-password".into()),
+            }
+        );
+    }
+
+    #[test]
+    fn test_fetch_outgoing() {
+        let store = LoginStore::new_in_memory().unwrap();
+        insert_login(
+            &store.db.lock(),
+            "changed",
+            Some("new-password"),
+            Some("password"),
+        );
+        insert_login(&store.db.lock(), "unchanged", None, Some("password"));
+        insert_login(&store.db.lock(), "added", Some("password"), None);
+        insert_login(&store.db.lock(), "deleted", None, Some("password"));
+        store.db.lock().delete("deleted").unwrap();
+
+        let changeset = run_fetch_outgoing(store);
+        let changes: HashMap<String, serde_json::Value> = changeset
+            .into_iter()
+            .map(|b| {
+                (
+                    b.envelope.id.to_string(),
+                    serde_json::from_str(&b.payload).unwrap(),
+                )
+            })
+            .collect();
+        assert_eq!(changes.len(), 3);
+        assert_eq!(changes["added"].get("password").unwrap(), "password");
+        assert_eq!(changes["changed"].get("password").unwrap(), "new-password");
+        assert!(changes["deleted"].get("deleted").is_some());
+        assert!(changes["added"].get("deleted").is_none());
+        assert!(changes["changed"].get("deleted").is_none());
+    }
+
+    #[test]
+    fn test_bad_record() {
+        let store = LoginStore::new_in_memory().unwrap();
+        for id in ["dummy_000001", "dummy_000002", "dummy_000003"] {
+            insert_login(&store.db.lock(), id, Some("password"), Some("password"));
+        }
+        let (res, telem) = run_fetch_login_data(
+            store,
+            vec![
+                IncomingBso::new_test_tombstone(Guid::new("dummy_000001")),
+                // invalid
+                IncomingBso::from_test_content(serde_json::json!({
+                    "id": "dummy_000002",
+                    "garbage": "data",
+                    "etc": "not a login"
+                })),
+                // valid
+                IncomingBso::from_test_content(serde_json::json!({
+                    "id": "dummy_000003",
+                    "formSubmitURL": "https://www.example.com/submit",
+                    "hostname": "https://www.example.com",
+                    "username": "test",
+                    "password": "test",
+                })),
+            ],
+        );
+        assert_eq!(telem.get_failed(), 1);
+        assert_eq!(res.len(), 2);
+        assert_eq!(res[0].guid, "dummy_000001");
+        assert_eq!(res[1].guid, "dummy_000003");
+    }
+
+    fn make_enc_login(
+        username: &str,
+        password: &str,
+        fao: Option<String>,
+        realm: Option<String>,
+    ) -> EncryptedLogin {
+        EncryptedLogin {
+            record: RecordFields {
+                id: Guid::random().to_string(),
+                ..Default::default()
+            },
+            fields: LoginFields {
+                form_action_origin: fao,
+                http_realm: realm,
+                origin: "http://not-relevant-here.com".into(),
+                ..Default::default()
+            },
+            sec_fields: SecureLoginFields {
+                username: username.into(),
+                password: password.into(),
+            }
+            .encrypt(&TEST_ENCRYPTOR)
+            .unwrap(),
+        }
+    }
+
+    #[test]
+    fn find_dupe_login() {
+        let store = LoginStore::new_in_memory().unwrap();
+
+        let to_add = LoginEntry {
+            fields: LoginFields {
+                form_action_origin: Some("https://www.example.com".into()),
+                origin: "http://not-relevant-here.com".into(),
+                ..Default::default()
+            },
+            sec_fields: SecureLoginFields {
+                username: "test".into(),
+                password: "test".into(),
+            },
+        };
+        let first_id = store
+            .add(to_add, &TEST_ENCRYPTION_KEY)
+            .expect("should insert first")
+            .record
+            .id;
+
+        let to_add = LoginEntry {
+            fields: LoginFields {
+                form_action_origin: Some("https://www.example1.com".into()),
+                origin: "http://not-relevant-here.com".into(),
+                ..Default::default()
+            },
+            sec_fields: SecureLoginFields {
+                username: "test1".into(),
+                password: "test1".into(),
+            },
+        };
+        let second_id = store
+            .add(to_add, &TEST_ENCRYPTION_KEY)
+            .expect("should insert second")
+            .record
+            .id;
+
+        let to_add = LoginEntry {
+            fields: LoginFields {
+                http_realm: Some("http://some-realm.com".into()),
+                origin: "http://not-relevant-here.com".into(),
+                ..Default::default()
+            },
+            sec_fields: SecureLoginFields {
+                username: "test1".into(),
+                password: "test1".into(),
+            },
+        };
+        let no_form_origin_id = store
+            .add(to_add, &TEST_ENCRYPTION_KEY)
+            .expect("should insert second")
+            .record
+            .id;
+
+        let mut engine = LoginsSyncEngine::new(Arc::new(store)).unwrap();
+        engine
+            .set_local_encryption_key(&TEST_ENCRYPTION_KEY)
+            .unwrap();
+
+        let to_find = make_enc_login("test", "test", Some("https://www.example.com".into()), None);
+        assert_eq!(
+            engine
+                .find_dupe_login(&to_find)
+                .expect("should work")
+                .expect("should be Some()")
+                .record
+                .id,
+            first_id
+        );
+
+        let to_find = make_enc_login(
+            "test",
+            "test",
+            Some("https://something-else.com".into()),
+            None,
+        );
+        assert!(engine
+            .find_dupe_login(&to_find)
+            .expect("should work")
+            .is_none());
+
+        let to_find = make_enc_login(
+            "test1",
+            "test1",
+            Some("https://www.example1.com".into()),
+            None,
+        );
+        assert_eq!(
+            engine
+                .find_dupe_login(&to_find)
+                .expect("should work")
+                .expect("should be Some()")
+                .record
+                .id,
+            second_id
+        );
+
+        let to_find = make_enc_login(
+            "other",
+            "other",
+            Some("https://www.example1.com".into()),
+            None,
+        );
+        assert!(engine
+            .find_dupe_login(&to_find)
+            .expect("should work")
+            .is_none());
+
+        // no form origin.
+        let to_find = make_enc_login("test1", "test1", None, Some("http://some-realm.com".into()));
+        assert_eq!(
+            engine
+                .find_dupe_login(&to_find)
+                .expect("should work")
+                .expect("should be Some()")
+                .record
+                .id,
+            no_form_origin_id
+        );
+    }
+
+    #[test]
+    fn test_roundtrip_unknown() {
+        // A couple of helpers
+        fn apply_incoming_payload(engine: &LoginsSyncEngine, payload: serde_json::Value) {
+            let bso = IncomingBso::from_test_content(payload);
+            let mut telem = sync15::telemetry::Engine::new(engine.collection_name());
+            engine.stage_incoming(vec![bso], &mut telem).unwrap();
+            engine
+                .apply(ServerTimestamp::from_millis(0), &mut telem)
+                .unwrap();
+        }
+
+        fn get_outgoing_payload(engine: &LoginsSyncEngine) -> serde_json::Value {
+            // Edit it so it's considered outgoing.
+            engine
+                .store
+                .update(
+                    "dummy_000001",
+                    LoginEntry {
+                        fields: LoginFields {
+                            origin: "https://www.example2.com".into(),
+                            http_realm: Some("https://www.example2.com".into()),
+                            ..Default::default()
+                        },
+                        sec_fields: SecureLoginFields {
+                            username: "test".into(),
+                            password: "test".into(),
+                        },
+                    },
+                    &TEST_ENCRYPTION_KEY,
+                )
+                .unwrap();
+            let changeset = engine.fetch_outgoing().unwrap();
+            assert_eq!(changeset.len(), 1);
+            serde_json::from_str::<serde_json::Value>(&changeset[0].payload).unwrap()
+        }
+
+        // The test itself...
+        let store = LoginStore::new_in_memory().unwrap();
+        let mut engine = LoginsSyncEngine::new(Arc::new(store)).unwrap();
+        engine
+            .set_local_encryption_key(&TEST_ENCRYPTION_KEY)
+            .unwrap();
+
+        apply_incoming_payload(
+            &engine,
+            serde_json::json!({
+                "id": "dummy_000001",
+                "formSubmitURL": "https://www.example.com/submit",
+                "hostname": "https://www.example.com",
+                "username": "test",
+                "password": "test",
+                "unknown1": "?",
+                "unknown2": {"sub": "object"},
+            }),
+        );
+
+        let payload = get_outgoing_payload(&engine);
+
+        // The outgoing payload for our item should have the unknown fields.
+        assert_eq!(payload.get("unknown1").unwrap().as_str().unwrap(), "?");
+        assert_eq!(
+            payload.get("unknown2").unwrap(),
+            &serde_json::json!({"sub": "object"})
+        );
+
+        // test mirror updates - record is already in our mirror, but now it's
+        // incoming with different unknown fields.
+        apply_incoming_payload(
+            &engine,
+            serde_json::json!({
+                "id": "dummy_000001",
+                "formSubmitURL": "https://www.example.com/submit",
+                "hostname": "https://www.example.com",
+                "username": "test",
+                "password": "test",
+                "unknown2": 99,
+                "unknown3": {"something": "else"},
+            }),
+        );
+        let payload = get_outgoing_payload(&engine);
+        // old unknown values were replaced.
+        assert!(payload.get("unknown1").is_none());
+        assert_eq!(payload.get("unknown2").unwrap().as_u64().unwrap(), 99);
+        assert_eq!(
+            payload
+                .get("unknown3")
+                .unwrap()
+                .as_object()
+                .unwrap()
+                .get("something")
+                .unwrap()
+                .as_str()
+                .unwrap(),
+            "else"
+        );
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/logins/sync/merge.rs.html b/book/rust-docs/src/logins/sync/merge.rs.html new file mode 100644 index 0000000000..93d280c2ea --- /dev/null +++ b/book/rust-docs/src/logins/sync/merge.rs.html @@ -0,0 +1,805 @@ +merge.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Merging for Sync.
+use super::{IncomingLogin, LoginPayload};
+use crate::encryption::EncryptorDecryptor;
+use crate::error::*;
+use crate::login::EncryptedLogin;
+use crate::util;
+use rusqlite::Row;
+use std::time::{self, SystemTime};
+use sync15::bso::{IncomingBso, IncomingKind};
+use sync15::ServerTimestamp;
+use sync_guid::Guid;
+
+#[derive(Clone, Debug)]
+pub(crate) struct MirrorLogin {
+    pub login: EncryptedLogin,
+    pub server_modified: ServerTimestamp,
+}
+
+impl MirrorLogin {
+    #[inline]
+    pub fn guid_str(&self) -> &str {
+        &self.login.record.id
+    }
+
+    pub(crate) fn from_row(row: &Row<'_>) -> Result<MirrorLogin> {
+        Ok(MirrorLogin {
+            login: EncryptedLogin::from_row(row)?,
+            server_modified: ServerTimestamp(row.get::<_, i64>("server_modified")?),
+        })
+    }
+}
+
+#[derive(Clone, Debug)]
+pub(crate) struct LocalLogin {
+    pub login: EncryptedLogin,
+    pub local_modified: SystemTime,
+}
+
+impl LocalLogin {
+    #[inline]
+    pub fn guid_str(&self) -> &str {
+        self.login.guid_str()
+    }
+
+    pub(crate) fn from_row(row: &Row<'_>) -> Result<LocalLogin> {
+        Ok(LocalLogin {
+            login: EncryptedLogin::from_row(row)?,
+            local_modified: util::system_time_millis_from_row(row, "local_modified")?,
+        })
+    }
+}
+
+macro_rules! impl_login {
+    ($ty:ty { $($fields:tt)* }) => {
+        impl AsRef<EncryptedLogin> for $ty {
+            #[inline]
+            fn as_ref(&self) -> &EncryptedLogin {
+                &self.login
+            }
+        }
+
+        impl AsMut<EncryptedLogin> for $ty {
+            #[inline]
+            fn as_mut(&mut self) -> &mut EncryptedLogin {
+                &mut self.login
+            }
+        }
+
+        impl From<$ty> for EncryptedLogin {
+            #[inline]
+            fn from(l: $ty) -> Self {
+                l.login
+            }
+        }
+
+        impl From<EncryptedLogin> for $ty {
+            #[inline]
+            fn from(login: EncryptedLogin) -> Self {
+                Self { login, $($fields)* }
+            }
+        }
+    };
+}
+
+impl_login!(LocalLogin {
+    local_modified: time::UNIX_EPOCH
+});
+
+impl_login!(MirrorLogin {
+    server_modified: ServerTimestamp(0)
+});
+
+// Stores data needed to do a 3-way merge
+#[derive(Debug)]
+pub(super) struct SyncLoginData {
+    pub guid: Guid,
+    pub local: Option<LocalLogin>,
+    pub mirror: Option<MirrorLogin>,
+    // None means it's a deletion
+    pub inbound: Option<IncomingLogin>,
+    pub inbound_ts: ServerTimestamp,
+}
+
+impl SyncLoginData {
+    #[inline]
+    pub fn guid_str(&self) -> &str {
+        self.guid.as_str()
+    }
+
+    #[inline]
+    pub fn guid(&self) -> &Guid {
+        &self.guid
+    }
+
+    pub fn from_bso(bso: IncomingBso, encdec: &EncryptorDecryptor) -> Result<Self> {
+        let guid = bso.envelope.id.clone();
+        let inbound_ts = bso.envelope.modified;
+        let inbound = match bso.into_content::<LoginPayload>().kind {
+            IncomingKind::Content(p) => Some(IncomingLogin::from_incoming_payload(p, encdec)?),
+            IncomingKind::Tombstone => None,
+            // Before the IncomingKind refactor we returned an error. We could probably just
+            // treat it as a tombstone but should check that's sane, so for now, we also err.
+            IncomingKind::Malformed => return Err(Error::MalformedIncomingRecord),
+        };
+        Ok(Self {
+            guid,
+            local: None,
+            mirror: None,
+            inbound,
+            inbound_ts,
+        })
+    }
+}
+
+macro_rules! impl_login_setter {
+    ($setter_name:ident, $field:ident, $Login:ty) => {
+        impl SyncLoginData {
+            pub(crate) fn $setter_name(&mut self, record: $Login) -> Result<()> {
+                // TODO: We probably shouldn't panic in this function!
+                if self.$field.is_some() {
+                    // Shouldn't be possible (only could happen if UNIQUE fails in sqlite, or if we
+                    // get duplicate guids somewhere,but we check).
+                    panic!(
+                        "SyncLoginData::{} called on object that already has {} data",
+                        stringify!($setter_name),
+                        stringify!($field)
+                    );
+                }
+
+                if self.guid_str() != record.guid_str() {
+                    // This is almost certainly a bug in our code.
+                    panic!(
+                        "Wrong guid on login in {}: {:?} != {:?}",
+                        stringify!($setter_name),
+                        self.guid_str(),
+                        record.guid_str()
+                    );
+                }
+
+                self.$field = Some(record);
+                Ok(())
+            }
+        }
+    };
+}
+
+impl_login_setter!(set_local, local, LocalLogin);
+impl_login_setter!(set_mirror, mirror, MirrorLogin);
+
+#[derive(Debug, Default, Clone)]
+pub(crate) struct LoginDelta {
+    // "non-commutative" fields
+    pub origin: Option<String>,
+    pub password: Option<String>,
+    pub username: Option<String>,
+    pub http_realm: Option<String>,
+    pub form_action_origin: Option<String>,
+
+    pub time_created: Option<i64>,
+    pub time_last_used: Option<i64>,
+    pub time_password_changed: Option<i64>,
+
+    // "non-conflicting" fields (which are the same)
+    pub password_field: Option<String>,
+    pub username_field: Option<String>,
+
+    // Commutative field
+    pub times_used: i64,
+}
+
+macro_rules! merge_field {
+    ($merged:ident, $b:ident, $prefer_b:expr, $field:ident) => {
+        if let Some($field) = $b.$field.take() {
+            if $merged.$field.is_some() {
+                log::warn!("Collision merging login field {}", stringify!($field));
+                if $prefer_b {
+                    $merged.$field = Some($field);
+                }
+            } else {
+                $merged.$field = Some($field);
+            }
+        }
+    };
+}
+
+impl LoginDelta {
+    #[allow(clippy::cognitive_complexity)] // Looks like clippy considers this after macro-expansion...
+    pub fn merge(self, mut b: LoginDelta, b_is_newer: bool) -> LoginDelta {
+        let mut merged = self;
+        merge_field!(merged, b, b_is_newer, origin);
+        merge_field!(merged, b, b_is_newer, password);
+        merge_field!(merged, b, b_is_newer, username);
+        merge_field!(merged, b, b_is_newer, http_realm);
+        merge_field!(merged, b, b_is_newer, form_action_origin);
+
+        merge_field!(merged, b, b_is_newer, time_created);
+        merge_field!(merged, b, b_is_newer, time_last_used);
+        merge_field!(merged, b, b_is_newer, time_password_changed);
+
+        merge_field!(merged, b, b_is_newer, password_field);
+        merge_field!(merged, b, b_is_newer, username_field);
+
+        // commutative fields
+        merged.times_used += b.times_used;
+
+        merged
+    }
+}
+
+macro_rules! apply_field {
+    ($login:ident, $delta:ident, $field:ident) => {
+        if let Some($field) = $delta.$field.take() {
+            $login.fields.$field = $field.into();
+        }
+    };
+}
+
+macro_rules! apply_metadata_field {
+    ($login:ident, $delta:ident, $field:ident) => {
+        if let Some($field) = $delta.$field.take() {
+            $login.record.$field = $field.into();
+        }
+    };
+}
+
+impl EncryptedLogin {
+    pub(crate) fn apply_delta(
+        &mut self,
+        mut delta: LoginDelta,
+        encdec: &EncryptorDecryptor,
+    ) -> Result<()> {
+        apply_field!(self, delta, origin);
+
+        apply_metadata_field!(self, delta, time_created);
+        apply_metadata_field!(self, delta, time_last_used);
+        apply_metadata_field!(self, delta, time_password_changed);
+
+        apply_field!(self, delta, password_field);
+        apply_field!(self, delta, username_field);
+
+        let mut sec_fields = self.decrypt_fields(encdec)?;
+        if let Some(password) = delta.password.take() {
+            sec_fields.password = password;
+        }
+        if let Some(username) = delta.username.take() {
+            sec_fields.username = username;
+        }
+        self.sec_fields = sec_fields.encrypt(encdec)?;
+
+        // Use Some("") to indicate that it should be changed to be None (hacky...)
+        if let Some(realm) = delta.http_realm.take() {
+            self.fields.http_realm = if realm.is_empty() { None } else { Some(realm) };
+        }
+
+        if let Some(url) = delta.form_action_origin.take() {
+            self.fields.form_action_origin = if url.is_empty() { None } else { Some(url) };
+        }
+
+        self.record.times_used += delta.times_used;
+        Ok(())
+    }
+
+    pub(crate) fn delta(
+        &self,
+        older: &EncryptedLogin,
+        encdec: &EncryptorDecryptor,
+    ) -> Result<LoginDelta> {
+        let mut delta = LoginDelta::default();
+
+        if self.fields.form_action_origin != older.fields.form_action_origin {
+            delta.form_action_origin =
+                Some(self.fields.form_action_origin.clone().unwrap_or_default());
+        }
+
+        if self.fields.http_realm != older.fields.http_realm {
+            delta.http_realm = Some(self.fields.http_realm.clone().unwrap_or_default());
+        }
+
+        if self.fields.origin != older.fields.origin {
+            delta.origin = Some(self.fields.origin.clone());
+        }
+        let older_sec_fields = older.decrypt_fields(encdec)?;
+        let self_sec_fields = self.decrypt_fields(encdec)?;
+        if self_sec_fields.username != older_sec_fields.username {
+            delta.username = Some(self_sec_fields.username.clone());
+        }
+        if self_sec_fields.password != older_sec_fields.password {
+            delta.password = Some(self_sec_fields.password);
+        }
+        if self.fields.password_field != older.fields.password_field {
+            delta.password_field = Some(self.fields.password_field.clone());
+        }
+        if self.fields.username_field != older.fields.username_field {
+            delta.username_field = Some(self.fields.username_field.clone());
+        }
+
+        // We discard zero (and negative numbers) for timestamps so that a
+        // record that doesn't contain this information (these are
+        // `#[serde(default)]`) doesn't skew our records.
+        //
+        // Arguably, we should also also ignore values later than our
+        // `time_created`, or earlier than our `time_last_used` or
+        // `time_password_changed`. Doing this properly would probably require
+        // a scheme analogous to Desktop's weak-reupload system, so I'm punting
+        // on it for now.
+        if self.record.time_created > 0 && self.record.time_created != older.record.time_created {
+            delta.time_created = Some(self.record.time_created);
+        }
+        if self.record.time_last_used > 0
+            && self.record.time_last_used != older.record.time_last_used
+        {
+            delta.time_last_used = Some(self.record.time_last_used);
+        }
+        if self.record.time_password_changed > 0
+            && self.record.time_password_changed != older.record.time_password_changed
+        {
+            delta.time_password_changed = Some(self.record.time_password_changed);
+        }
+
+        if self.record.times_used > 0 && self.record.times_used != older.record.times_used {
+            delta.times_used = self.record.times_used - older.record.times_used;
+        }
+
+        Ok(delta)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::encryption::test_utils::TEST_ENCRYPTOR;
+
+    #[test]
+    fn test_invalid_payload_timestamps() {
+        #[allow(clippy::unreadable_literal)]
+        let bad_timestamp = 18446732429235952000u64;
+        let bad_payload = IncomingBso::from_test_content(serde_json::json!({
+            "id": "123412341234",
+            "formSubmitURL": "https://www.example.com/submit",
+            "hostname": "https://www.example.com",
+            "username": "test",
+            "password": "test",
+            "timeCreated": bad_timestamp,
+            "timeLastUsed": "some other garbage",
+            "timePasswordChanged": -30, // valid i64 but negative
+        }));
+        let login = SyncLoginData::from_bso(bad_payload, &TEST_ENCRYPTOR)
+            .unwrap()
+            .inbound
+            .unwrap()
+            .login;
+        assert_eq!(login.record.time_created, 0);
+        assert_eq!(login.record.time_last_used, 0);
+        assert_eq!(login.record.time_password_changed, 0);
+
+        let now64 = util::system_time_ms_i64(std::time::SystemTime::now());
+        let good_payload = IncomingBso::from_test_content(serde_json::json!({
+            "id": "123412341234",
+            "formSubmitURL": "https://www.example.com/submit",
+            "hostname": "https://www.example.com",
+            "username": "test",
+            "password": "test",
+            "timeCreated": now64 - 100,
+            "timeLastUsed": now64 - 50,
+            "timePasswordChanged": now64 - 25,
+        }));
+
+        let login = SyncLoginData::from_bso(good_payload, &TEST_ENCRYPTOR)
+            .unwrap()
+            .inbound
+            .unwrap()
+            .login;
+
+        assert_eq!(login.record.time_created, now64 - 100);
+        assert_eq!(login.record.time_last_used, now64 - 50);
+        assert_eq!(login.record.time_password_changed, now64 - 25);
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/logins/sync/mod.rs.html b/book/rust-docs/src/logins/sync/mod.rs.html new file mode 100644 index 0000000000..d458f196c4 --- /dev/null +++ b/book/rust-docs/src/logins/sync/mod.rs.html @@ -0,0 +1,39 @@ +mod.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+mod engine;
+pub(crate) mod merge;
+mod payload;
+mod update_plan;
+
+pub use engine::LoginsSyncEngine;
+use payload::{IncomingLogin, LoginPayload};
+
+#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
+#[repr(u8)]
+pub(crate) enum SyncStatus {
+    Synced = 0,
+    Changed = 1,
+    New = 2,
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/logins/sync/payload.rs.html b/book/rust-docs/src/logins/sync/payload.rs.html new file mode 100644 index 0000000000..d86b1ebd65 --- /dev/null +++ b/book/rust-docs/src/logins/sync/payload.rs.html @@ -0,0 +1,831 @@ +payload.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Login entry from a server payload
+//
+// This struct is used for fetching/sending login records to the server.  There are a number
+// of differences between this and the top-level Login struct; some fields are renamed, some are
+// locally encrypted, etc.
+use crate::encryption::EncryptorDecryptor;
+use crate::error::*;
+use crate::login::ValidateAndFixup;
+use crate::SecureLoginFields;
+use crate::{EncryptedLogin, LoginFields, RecordFields};
+use serde_derive::*;
+use sync15::bso::OutgoingBso;
+use sync_guid::Guid;
+
+type UnknownFields = serde_json::Map<String, serde_json::Value>;
+
+trait UnknownFieldsExt {
+    fn encrypt(&self, encdec: &EncryptorDecryptor) -> Result<String>;
+    fn decrypt(ciphertext: &str, encdec: &EncryptorDecryptor) -> Result<Self>
+    where
+        Self: Sized;
+}
+
+impl UnknownFieldsExt for UnknownFields {
+    fn encrypt(&self, encdec: &EncryptorDecryptor) -> Result<String> {
+        encdec.encrypt_struct(&self, "encrypt unknown fields")
+    }
+
+    fn decrypt(ciphertext: &str, encdec: &EncryptorDecryptor) -> Result<Self> {
+        encdec.decrypt_struct(ciphertext, "decrypt unknown fields")
+    }
+}
+
+/// What we get from the server after parsing the payload. We need to round-trip "unknown"
+/// fields, but don't want to carry them around in `EncryptedLogin`.
+#[derive(Debug)]
+pub(super) struct IncomingLogin {
+    pub login: EncryptedLogin,
+    // An encrypted UnknownFields, or None if there are none.
+    pub unknown: Option<String>,
+}
+
+impl IncomingLogin {
+    pub fn guid(&self) -> Guid {
+        self.login.guid()
+    }
+
+    pub(super) fn from_incoming_payload(
+        p: LoginPayload,
+        encdec: &EncryptorDecryptor,
+    ) -> Result<Self> {
+        let fields = LoginFields {
+            origin: p.hostname,
+            form_action_origin: p.form_submit_url,
+            http_realm: p.http_realm,
+            username_field: p.username_field,
+            password_field: p.password_field,
+        };
+        let sec_fields = SecureLoginFields {
+            username: p.username,
+            password: p.password,
+        };
+        // We handle NULL in the DB for migrated databases and it's wasteful
+        // to encrypt the common case of an empty map, so...
+        let unknown = if p.unknown_fields.is_empty() {
+            None
+        } else {
+            Some(p.unknown_fields.encrypt(encdec)?)
+        };
+
+        // If we can't fix the parts we keep the invalid bits.
+        Ok(Self {
+            login: EncryptedLogin {
+                record: RecordFields {
+                    id: p.guid.into(),
+                    time_created: p.time_created,
+                    time_password_changed: p.time_password_changed,
+                    time_last_used: p.time_last_used,
+                    times_used: p.times_used,
+                },
+                fields: fields.maybe_fixup()?.unwrap_or(fields),
+                sec_fields: sec_fields
+                    .maybe_fixup()?
+                    .unwrap_or(sec_fields)
+                    .encrypt(encdec)?,
+            },
+            unknown,
+        })
+    }
+}
+
+/// The JSON payload that lives on the storage servers.
+#[derive(Debug, Clone, Serialize, Deserialize, Default)]
+#[serde(rename_all = "camelCase")]
+pub struct LoginPayload {
+    #[serde(rename = "id")]
+    pub guid: Guid,
+
+    // This is 'origin' in our Login struct.
+    pub hostname: String,
+
+    // This is 'form_action_origin' in our Login struct.
+    // rename_all = "camelCase" by default will do formSubmitUrl, but we can just
+    // override this one field.
+    #[serde(rename = "formSubmitURL")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub form_submit_url: Option<String>,
+
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub http_realm: Option<String>,
+
+    #[serde(default)]
+    pub username: String,
+
+    pub password: String,
+
+    #[serde(default)]
+    pub username_field: String,
+
+    #[serde(default)]
+    pub password_field: String,
+
+    #[serde(default)]
+    #[serde(deserialize_with = "deserialize_timestamp")]
+    pub time_created: i64,
+
+    #[serde(default)]
+    #[serde(deserialize_with = "deserialize_timestamp")]
+    pub time_password_changed: i64,
+
+    #[serde(default)]
+    #[serde(deserialize_with = "deserialize_timestamp")]
+    pub time_last_used: i64,
+
+    #[serde(default)]
+    pub times_used: i64,
+
+    // Additional "unknown" round-tripped fields.
+    #[serde(flatten)]
+    unknown_fields: UnknownFields,
+}
+
+// These probably should be on the payload itself, but one refactor at a time!
+impl EncryptedLogin {
+    pub fn into_bso(
+        self,
+        encdec: &EncryptorDecryptor,
+        enc_unknown_fields: Option<String>,
+    ) -> Result<OutgoingBso> {
+        let unknown_fields = match enc_unknown_fields {
+            Some(s) => UnknownFields::decrypt(&s, encdec)?,
+            None => Default::default(),
+        };
+        let sec_fields = SecureLoginFields::decrypt(&self.sec_fields, encdec)?;
+        Ok(OutgoingBso::from_content_with_id(
+            crate::sync::LoginPayload {
+                guid: self.guid(),
+                hostname: self.fields.origin,
+                form_submit_url: self.fields.form_action_origin,
+                http_realm: self.fields.http_realm,
+                username_field: self.fields.username_field,
+                password_field: self.fields.password_field,
+                username: sec_fields.username,
+                password: sec_fields.password,
+                time_created: self.record.time_created,
+                time_password_changed: self.record.time_password_changed,
+                time_last_used: self.record.time_last_used,
+                times_used: self.record.times_used,
+                unknown_fields,
+            },
+        )?)
+    }
+}
+
+// Quiet clippy, since this function is passed to deserialiaze_with...
+#[allow(clippy::unnecessary_wraps)]
+fn deserialize_timestamp<'de, D>(deserializer: D) -> std::result::Result<i64, D::Error>
+where
+    D: serde::de::Deserializer<'de>,
+{
+    use serde::de::Deserialize;
+    // Invalid and negative timestamps are all replaced with 0. Eventually we
+    // should investigate replacing values that are unreasonable but still fit
+    // in an i64 (a date 1000 years in the future, for example), but
+    // appropriately handling that is complex.
+    Ok(i64::deserialize(deserializer).unwrap_or_default().max(0))
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::encryption::test_utils::{encrypt_struct, TEST_ENCRYPTOR};
+    use crate::sync::merge::SyncLoginData;
+    use crate::{EncryptedLogin, LoginFields, RecordFields, SecureLoginFields};
+    use sync15::bso::IncomingBso;
+
+    #[test]
+    fn test_payload_to_login() {
+        let bso = IncomingBso::from_test_content(serde_json::json!({
+            "id": "123412341234",
+            "httpRealm": "test",
+            "hostname": "https://www.example.com",
+            "username": "user",
+            "password": "password",
+        }));
+        let login = IncomingLogin::from_incoming_payload(
+            bso.into_content::<LoginPayload>().content().unwrap(),
+            &TEST_ENCRYPTOR,
+        )
+        .unwrap()
+        .login;
+        assert_eq!(login.record.id, "123412341234");
+        assert_eq!(login.fields.http_realm, Some("test".to_string()));
+        assert_eq!(login.fields.origin, "https://www.example.com");
+        assert_eq!(login.fields.form_action_origin, None);
+        let sec_fields = login.decrypt_fields(&TEST_ENCRYPTOR).unwrap();
+        assert_eq!(sec_fields.username, "user");
+        assert_eq!(sec_fields.password, "password");
+    }
+
+    // formSubmitURL (now formActionOrigin) being an empty string is a valid
+    // legacy case that is supported on desktop, we should ensure we are as well
+    // https://searchfox.org/mozilla-central/rev/32c74afbb24dce4b5dd6b33be71197e615631d71/toolkit/components/passwordmgr/test/unit/test_logins_change.js#183-184
+    #[test]
+    fn test_payload_empty_form_action_to_login() {
+        let bso = IncomingBso::from_test_content(serde_json::json!({
+            "id": "123412341234",
+            "formSubmitURL": "",
+            "hostname": "https://www.example.com",
+            "username": "user",
+            "password": "password",
+        }));
+        let login = IncomingLogin::from_incoming_payload(
+            bso.into_content::<LoginPayload>().content().unwrap(),
+            &TEST_ENCRYPTOR,
+        )
+        .unwrap()
+        .login;
+        assert_eq!(login.record.id, "123412341234");
+        assert_eq!(login.fields.form_action_origin, Some("".to_string()));
+        assert_eq!(login.fields.http_realm, None);
+        assert_eq!(login.fields.origin, "https://www.example.com");
+        let sec_fields = login.decrypt_fields(&TEST_ENCRYPTOR).unwrap();
+        assert_eq!(sec_fields.username, "user");
+        assert_eq!(sec_fields.password, "password");
+
+        let bso = login.into_bso(&TEST_ENCRYPTOR, None).unwrap();
+        assert_eq!(bso.envelope.id, "123412341234");
+        let payload_data: serde_json::Value = serde_json::from_str(&bso.payload).unwrap();
+        assert_eq!(payload_data["httpRealm"], serde_json::Value::Null);
+        assert_eq!(payload_data["formSubmitURL"], "".to_string());
+    }
+
+    #[test]
+    fn test_payload_unknown_fields() {
+        // No "unknown" fields.
+        let bso = IncomingBso::from_test_content(serde_json::json!({
+            "id": "123412341234",
+            "httpRealm": "test",
+            "hostname": "https://www.example.com",
+            "username": "user",
+            "password": "password",
+        }));
+        let payload = bso.into_content::<LoginPayload>().content().unwrap();
+        assert!(payload.unknown_fields.is_empty());
+
+        // An unknown "foo"
+        let bso = IncomingBso::from_test_content(serde_json::json!({
+            "id": "123412341234",
+            "httpRealm": "test",
+            "hostname": "https://www.example.com",
+            "username": "user",
+            "password": "password",
+            "foo": "bar",
+        }));
+        let payload = bso.into_content::<LoginPayload>().content().unwrap();
+        assert_eq!(payload.unknown_fields.len(), 1);
+        assert_eq!(
+            payload.unknown_fields.get("foo").unwrap().as_str().unwrap(),
+            "bar"
+        );
+        // re-serialize it.
+        let unknown = Some(
+            TEST_ENCRYPTOR
+                .encrypt_struct::<UnknownFields>(
+                    &payload.unknown_fields,
+                    "test encrypt unknown fields",
+                )
+                .unwrap(),
+        );
+        let login = IncomingLogin::from_incoming_payload(payload, &TEST_ENCRYPTOR)
+            .unwrap()
+            .login;
+        // The raw outgoing payload should have it back.
+        let outgoing = login.into_bso(&TEST_ENCRYPTOR, unknown).unwrap();
+        let json =
+            serde_json::from_str::<serde_json::Map<String, serde_json::Value>>(&outgoing.payload)
+                .unwrap();
+        assert_eq!(json.get("foo").unwrap().as_str().unwrap(), "bar");
+    }
+
+    #[test]
+    fn test_form_submit_payload_to_login() {
+        let bso = IncomingBso::from_test_content(serde_json::json!({
+            "id": "123412341234",
+            "hostname": "https://www.example.com",
+            "formSubmitURL": "https://www.example.com",
+            "usernameField": "username-field",
+            "username": "user",
+            "password": "password",
+        }));
+        let login = IncomingLogin::from_incoming_payload(
+            bso.into_content::<LoginPayload>().content().unwrap(),
+            &TEST_ENCRYPTOR,
+        )
+        .unwrap()
+        .login;
+        assert_eq!(login.record.id, "123412341234");
+        assert_eq!(login.fields.http_realm, None);
+        assert_eq!(login.fields.origin, "https://www.example.com");
+        assert_eq!(
+            login.fields.form_action_origin,
+            Some("https://www.example.com".to_string())
+        );
+        assert_eq!(login.fields.username_field, "username-field");
+        let sec_fields = login.decrypt_fields(&TEST_ENCRYPTOR).unwrap();
+        assert_eq!(sec_fields.username, "user");
+        assert_eq!(sec_fields.password, "password");
+    }
+
+    #[test]
+    fn test_login_into_payload() {
+        let login = EncryptedLogin {
+            record: RecordFields {
+                id: "123412341234".into(),
+                ..Default::default()
+            },
+            fields: LoginFields {
+                http_realm: Some("test".into()),
+                origin: "https://www.example.com".into(),
+                ..Default::default()
+            },
+            sec_fields: encrypt_struct(&SecureLoginFields {
+                username: "user".into(),
+                password: "password".into(),
+            }),
+        };
+        let bso = login.into_bso(&TEST_ENCRYPTOR, None).unwrap();
+        assert_eq!(bso.envelope.id, "123412341234");
+        let payload_data: serde_json::Value = serde_json::from_str(&bso.payload).unwrap();
+        assert_eq!(payload_data["httpRealm"], "test".to_string());
+        assert_eq!(payload_data["hostname"], "https://www.example.com");
+        assert_eq!(payload_data["username"], "user");
+        assert_eq!(payload_data["password"], "password");
+        assert!(matches!(
+            payload_data["formActionOrigin"],
+            serde_json::Value::Null
+        ));
+    }
+
+    #[test]
+    fn test_username_field_requires_a_form_target() {
+        let bad_json = serde_json::json!({
+            "id": "123412341234",
+            "httpRealm": "test",
+            "hostname": "https://www.example.com",
+            "username": "test",
+            "password": "test",
+            "usernameField": "invalid"
+        });
+        let bad_bso = IncomingBso::from_test_content(bad_json.clone());
+
+        // Incoming sync data gets fixed automatically.
+        let login = IncomingLogin::from_incoming_payload(
+            bad_bso.into_content::<LoginPayload>().content().unwrap(),
+            &TEST_ENCRYPTOR,
+        )
+        .unwrap()
+        .login;
+        assert_eq!(login.fields.username_field, "");
+
+        // SyncLoginData::from_payload also fixes up.
+        let bad_bso = IncomingBso::from_test_content(bad_json);
+        let login = SyncLoginData::from_bso(bad_bso, &TEST_ENCRYPTOR)
+            .unwrap()
+            .inbound
+            .unwrap()
+            .login;
+        assert_eq!(login.fields.username_field, "");
+    }
+
+    #[test]
+    fn test_password_field_requires_a_form_target() {
+        let bad_bso = IncomingBso::from_test_content(serde_json::json!({
+            "id": "123412341234",
+            "httpRealm": "test",
+            "hostname": "https://www.example.com",
+            "username": "test",
+            "password": "test",
+            "passwordField": "invalid"
+        }));
+
+        let login = IncomingLogin::from_incoming_payload(
+            bad_bso.into_content::<LoginPayload>().content().unwrap(),
+            &TEST_ENCRYPTOR,
+        )
+        .unwrap()
+        .login;
+        assert_eq!(login.fields.password_field, "");
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/logins/sync/update_plan.rs.html b/book/rust-docs/src/logins/sync/update_plan.rs.html new file mode 100644 index 0000000000..f5cc52323e --- /dev/null +++ b/book/rust-docs/src/logins/sync/update_plan.rs.html @@ -0,0 +1,773 @@ +update_plan.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use super::merge::{LocalLogin, MirrorLogin};
+use super::{IncomingLogin, SyncStatus};
+use crate::encryption::EncryptorDecryptor;
+use crate::error::*;
+use crate::login::EncryptedLogin;
+use crate::util;
+use interrupt_support::SqlInterruptScope;
+use rusqlite::{named_params, Connection};
+use std::time::SystemTime;
+use sync15::ServerTimestamp;
+use sync_guid::Guid;
+
+#[derive(Default, Debug)]
+pub(super) struct UpdatePlan {
+    pub delete_mirror: Vec<Guid>,
+    pub delete_local: Vec<Guid>,
+    pub local_updates: Vec<MirrorLogin>,
+    // the bool is the `is_overridden` flag, the i64 is ServerTimestamp in millis
+    pub mirror_inserts: Vec<(IncomingLogin, i64, bool)>,
+    pub mirror_updates: Vec<(IncomingLogin, i64)>,
+}
+
+impl UpdatePlan {
+    pub fn plan_two_way_merge(
+        &mut self,
+        local: &EncryptedLogin,
+        upstream: (IncomingLogin, ServerTimestamp),
+    ) {
+        let is_override =
+            local.record.time_password_changed > upstream.0.login.record.time_password_changed;
+        self.mirror_inserts
+            .push((upstream.0, upstream.1.as_millis(), is_override));
+        if !is_override {
+            self.delete_local.push(local.guid());
+        }
+    }
+
+    pub fn plan_three_way_merge(
+        &mut self,
+        local: LocalLogin,
+        shared: MirrorLogin,
+        upstream: IncomingLogin,
+        upstream_time: ServerTimestamp,
+        server_now: ServerTimestamp,
+        encdec: &EncryptorDecryptor,
+    ) -> Result<()> {
+        let local_age = SystemTime::now()
+            .duration_since(local.local_modified)
+            .unwrap_or_default();
+        let remote_age = server_now.duration_since(upstream_time).unwrap_or_default();
+
+        let local_delta = local.login.delta(&shared.login, encdec)?;
+        let upstream_delta = upstream.login.delta(&shared.login, encdec)?;
+
+        let merged_delta = local_delta.merge(upstream_delta, remote_age < local_age);
+
+        // Update mirror to upstream
+        self.mirror_updates
+            .push((upstream, upstream_time.as_millis()));
+        let mut new = shared;
+
+        new.login.apply_delta(merged_delta, encdec)?;
+        new.server_modified = upstream_time;
+        self.local_updates.push(new);
+        Ok(())
+    }
+
+    pub fn plan_delete(&mut self, id: Guid) {
+        self.delete_local.push(id.clone());
+        self.delete_mirror.push(id);
+    }
+
+    pub fn plan_mirror_update(&mut self, upstream: IncomingLogin, time: ServerTimestamp) {
+        self.mirror_updates.push((upstream, time.as_millis()));
+    }
+
+    pub fn plan_mirror_insert(
+        &mut self,
+        upstream: IncomingLogin,
+        time: ServerTimestamp,
+        is_override: bool,
+    ) {
+        self.mirror_inserts
+            .push((upstream, time.as_millis(), is_override));
+    }
+
+    fn perform_deletes(&self, conn: &Connection, scope: &SqlInterruptScope) -> Result<()> {
+        sql_support::each_chunk(&self.delete_local, |chunk, _| -> Result<()> {
+            conn.execute(
+                &format!(
+                    "DELETE FROM loginsL WHERE guid IN ({vars})",
+                    vars = sql_support::repeat_sql_vars(chunk.len())
+                ),
+                rusqlite::params_from_iter(chunk),
+            )?;
+            scope.err_if_interrupted()?;
+            Ok(())
+        })?;
+
+        sql_support::each_chunk(&self.delete_mirror, |chunk, _| {
+            conn.execute(
+                &format!(
+                    "DELETE FROM loginsM WHERE guid IN ({vars})",
+                    vars = sql_support::repeat_sql_vars(chunk.len())
+                ),
+                rusqlite::params_from_iter(chunk),
+            )?;
+            Ok(())
+        })
+    }
+
+    // These aren't batched but probably should be.
+    fn perform_mirror_updates(&self, conn: &Connection, scope: &SqlInterruptScope) -> Result<()> {
+        let sql = "
+            UPDATE loginsM
+            SET server_modified  = :server_modified,
+                enc_unknown_fields = :enc_unknown_fields,
+                httpRealm        = :http_realm,
+                formActionOrigin = :form_action_origin,
+                usernameField    = :username_field,
+                passwordField    = :password_field,
+                origin           = :origin,
+                secFields        = :sec_fields,
+                -- Avoid zeroes if the remote has been overwritten by an older client.
+                timesUsed           = coalesce(nullif(:times_used,            0), timesUsed),
+                timeLastUsed        = coalesce(nullif(:time_last_used,        0), timeLastUsed),
+                timePasswordChanged = coalesce(nullif(:time_password_changed, 0), timePasswordChanged),
+                timeCreated         = coalesce(nullif(:time_created,          0), timeCreated)
+            WHERE guid = :guid
+        ";
+        let mut stmt = conn.prepare_cached(sql)?;
+        for (upstream, timestamp) in &self.mirror_updates {
+            let login = &upstream.login;
+            log::trace!("Updating mirror {:?}", login.guid_str());
+            stmt.execute(named_params! {
+                ":server_modified": *timestamp,
+                ":enc_unknown_fields": upstream.unknown,
+                ":http_realm": login.fields.http_realm,
+                ":form_action_origin": login.fields.form_action_origin,
+                ":username_field": login.fields.username_field,
+                ":password_field": login.fields.password_field,
+                ":origin": login.fields.origin,
+                ":times_used": login.record.times_used,
+                ":time_last_used": login.record.time_last_used,
+                ":time_password_changed": login.record.time_password_changed,
+                ":time_created": login.record.time_created,
+                ":guid": login.guid_str(),
+                ":sec_fields": login.sec_fields,
+            })?;
+            scope.err_if_interrupted()?;
+        }
+        Ok(())
+    }
+
+    fn perform_mirror_inserts(&self, conn: &Connection, scope: &SqlInterruptScope) -> Result<()> {
+        let sql = "
+            INSERT OR IGNORE INTO loginsM (
+                is_overridden,
+                server_modified,
+                enc_unknown_fields,
+
+                httpRealm,
+                formActionOrigin,
+                usernameField,
+                passwordField,
+                origin,
+                secFields,
+
+                timesUsed,
+                timeLastUsed,
+                timePasswordChanged,
+                timeCreated,
+
+                guid
+            ) VALUES (
+                :is_overridden,
+                :server_modified,
+                :enc_unknown_fields,
+
+                :http_realm,
+                :form_action_origin,
+                :username_field,
+                :password_field,
+                :origin,
+                :sec_fields,
+
+                :times_used,
+                :time_last_used,
+                :time_password_changed,
+                :time_created,
+
+                :guid
+            )";
+        let mut stmt = conn.prepare_cached(sql)?;
+
+        for (upstream, timestamp, is_overridden) in &self.mirror_inserts {
+            let login = &upstream.login;
+            log::trace!("Inserting mirror {:?}", login.guid_str());
+            stmt.execute(named_params! {
+                ":is_overridden": *is_overridden,
+                ":server_modified": *timestamp,
+                ":enc_unknown_fields": upstream.unknown,
+                ":http_realm": login.fields.http_realm,
+                ":form_action_origin": login.fields.form_action_origin,
+                ":username_field": login.fields.username_field,
+                ":password_field": login.fields.password_field,
+                ":origin": login.fields.origin,
+                ":times_used": login.record.times_used,
+                ":time_last_used": login.record.time_last_used,
+                ":time_password_changed": login.record.time_password_changed,
+                ":time_created": login.record.time_created,
+                ":guid": login.guid_str(),
+                ":sec_fields": login.sec_fields,
+            })?;
+            scope.err_if_interrupted()?;
+        }
+        Ok(())
+    }
+
+    fn perform_local_updates(&self, conn: &Connection, scope: &SqlInterruptScope) -> Result<()> {
+        let sql = format!(
+            "UPDATE loginsL
+             SET local_modified      = :local_modified,
+                 httpRealm           = :http_realm,
+                 formActionOrigin    = :form_action_origin,
+                 usernameField       = :username_field,
+                 passwordField       = :password_field,
+                 timeLastUsed        = :time_last_used,
+                 timePasswordChanged = :time_password_changed,
+                 timesUsed           = :times_used,
+                 origin              = :origin,
+                 secFields     = :sec_fields,
+                 sync_status         = {changed}
+             WHERE guid = :guid",
+            changed = SyncStatus::Changed as u8
+        );
+        let mut stmt = conn.prepare_cached(&sql)?;
+        // XXX OutgoingChangeset should no longer have timestamp.
+        let local_ms: i64 = util::system_time_ms_i64(SystemTime::now());
+        for l in &self.local_updates {
+            log::trace!("Updating local {:?}", l.guid_str());
+            stmt.execute(named_params! {
+                ":local_modified": local_ms,
+                ":http_realm": l.login.fields.http_realm,
+                ":form_action_origin": l.login.fields.form_action_origin,
+                ":username_field": l.login.fields.username_field,
+                ":password_field": l.login.fields.password_field,
+                ":origin": l.login.fields.origin,
+                ":time_last_used": l.login.record.time_last_used,
+                ":time_password_changed": l.login.record.time_password_changed,
+                ":times_used": l.login.record.times_used,
+                ":guid": l.guid_str(),
+                ":sec_fields": l.login.sec_fields,
+            })?;
+            scope.err_if_interrupted()?;
+        }
+        Ok(())
+    }
+
+    pub fn execute(&self, conn: &Connection, scope: &SqlInterruptScope) -> Result<()> {
+        log::debug!(
+            "UpdatePlan: deleting {} records...",
+            self.delete_local.len()
+        );
+        self.perform_deletes(conn, scope)?;
+        log::debug!(
+            "UpdatePlan: Updating {} existing mirror records...",
+            self.mirror_updates.len()
+        );
+        self.perform_mirror_updates(conn, scope)?;
+        log::debug!(
+            "UpdatePlan: Inserting {} new mirror records...",
+            self.mirror_inserts.len()
+        );
+        self.perform_mirror_inserts(conn, scope)?;
+        log::debug!(
+            "UpdatePlan: Updating {} reconciled local records...",
+            self.local_updates.len()
+        );
+        self.perform_local_updates(conn, scope)?;
+        Ok(())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::db::test_utils::{
+        check_local_login, check_mirror_login, get_local_guids, get_mirror_guids,
+        get_server_modified, insert_login,
+    };
+    use crate::db::LoginDb;
+    use crate::login::test_utils::enc_login;
+
+    fn inc_login(id: &str, password: &str) -> crate::sync::IncomingLogin {
+        IncomingLogin {
+            login: enc_login(id, password),
+            unknown: Default::default(),
+        }
+    }
+
+    #[test]
+    fn test_deletes() {
+        let db = LoginDb::open_in_memory().unwrap();
+        insert_login(&db, "login1", Some("password"), Some("password"));
+        insert_login(&db, "login2", Some("password"), Some("password"));
+        insert_login(&db, "login3", Some("password"), Some("password"));
+        insert_login(&db, "login4", Some("password"), Some("password"));
+
+        UpdatePlan {
+            delete_mirror: vec![Guid::new("login1"), Guid::new("login2")],
+            delete_local: vec![Guid::new("login2"), Guid::new("login3")],
+            ..UpdatePlan::default()
+        }
+        .execute(&db, &db.begin_interrupt_scope().unwrap())
+        .unwrap();
+
+        assert_eq!(get_local_guids(&db), vec!["login1", "login4"]);
+        assert_eq!(get_mirror_guids(&db), vec!["login3", "login4"]);
+    }
+
+    #[test]
+    fn test_mirror_updates() {
+        let db = LoginDb::open_in_memory().unwrap();
+        insert_login(&db, "unchanged", None, Some("password"));
+        insert_login(&db, "changed", None, Some("password"));
+        insert_login(
+            &db,
+            "changed2",
+            Some("new-local-password"),
+            Some("password"),
+        );
+        let initial_modified = get_server_modified(&db, "unchanged");
+
+        UpdatePlan {
+            mirror_updates: vec![
+                (inc_login("changed", "new-password"), 20000),
+                (inc_login("changed2", "new-password2"), 21000),
+            ],
+            ..UpdatePlan::default()
+        }
+        .execute(&db, &db.begin_interrupt_scope().unwrap())
+        .unwrap();
+        check_mirror_login(&db, "unchanged", "password", initial_modified, false);
+        check_mirror_login(&db, "changed", "new-password", 20000, false);
+        check_mirror_login(&db, "changed2", "new-password2", 21000, true);
+    }
+
+    #[test]
+    fn test_mirror_inserts() {
+        let db = LoginDb::open_in_memory().unwrap();
+        UpdatePlan {
+            mirror_inserts: vec![
+                (inc_login("login1", "new-password"), 20000, false),
+                (inc_login("login2", "new-password2"), 21000, true),
+            ],
+            ..UpdatePlan::default()
+        }
+        .execute(&db, &db.begin_interrupt_scope().unwrap())
+        .unwrap();
+        check_mirror_login(&db, "login1", "new-password", 20000, false);
+        check_mirror_login(&db, "login2", "new-password2", 21000, true);
+    }
+
+    #[test]
+    fn test_local_updates() {
+        let db = LoginDb::open_in_memory().unwrap();
+        insert_login(&db, "login", Some("password"), Some("password"));
+        let before_update = util::system_time_ms_i64(SystemTime::now());
+
+        UpdatePlan {
+            local_updates: vec![MirrorLogin {
+                login: enc_login("login", "new-password"),
+                server_modified: ServerTimestamp(10000),
+            }],
+            ..UpdatePlan::default()
+        }
+        .execute(&db, &db.begin_interrupt_scope().unwrap())
+        .unwrap();
+        check_local_login(&db, "login", "new-password", before_update);
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/logins/util.rs.html b/book/rust-docs/src/logins/util.rs.html new file mode 100644 index 0000000000..61f439fa2b --- /dev/null +++ b/book/rust-docs/src/logins/util.rs.html @@ -0,0 +1,83 @@ +util.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::error::*;
+use rusqlite::Row;
+use std::time;
+use url::Url;
+
+pub fn url_host_port(url_str: &str) -> Option<String> {
+    let url = Url::parse(url_str).ok()?;
+    let host = url.host_str()?;
+    Some(if let Some(p) = url.port() {
+        format!("{}:{}", host, p)
+    } else {
+        host.to_string()
+    })
+}
+
+pub fn system_time_millis_from_row(row: &Row<'_>, col_name: &str) -> Result<time::SystemTime> {
+    let time_ms = row.get::<_, Option<i64>>(col_name)?.unwrap_or_default() as u64;
+    Ok(time::UNIX_EPOCH + time::Duration::from_millis(time_ms))
+}
+
+pub fn duration_ms_i64(d: time::Duration) -> i64 {
+    (d.as_secs() as i64) * 1000 + (i64::from(d.subsec_nanos()) / 1_000_000)
+}
+
+pub fn system_time_ms_i64(t: time::SystemTime) -> i64 {
+    duration_ms_i64(t.duration_since(time::UNIX_EPOCH).unwrap_or_default())
+}
+
+// Unfortunately, there's not a better way to turn on logging in tests AFAICT
+#[cfg(test)]
+pub(crate) fn init_test_logging() {
+    use std::sync::Once;
+    static INIT_LOGGING: Once = Once::new();
+    INIT_LOGGING.call_once(|| {
+        env_logger::init_from_env(env_logger::Env::default().filter_or("RUST_LOG", "trace"));
+    });
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus/defaults.rs.html b/book/rust-docs/src/nimbus/defaults.rs.html new file mode 100644 index 0000000000..1f8ecbba12 --- /dev/null +++ b/book/rust-docs/src/nimbus/defaults.rs.html @@ -0,0 +1,211 @@ +defaults.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+use crate::error::Result;
+
+/// Simple trait to allow merging of similar objects.
+///
+/// Different names might be more applicable: merging, defaulting, patching.
+///
+/// In all cases: the `defaults` method takes a reference to a `self` and
+/// a `fallback`. The `self` acts as a patch over the `fallback`, and a new
+/// version is the result.
+///
+/// Implementations of the trait can error. In the case of recursive implementations,
+/// other implementations may catch and recover from the error, or propagate it.
+///
+/// Context: Feature JSON is used to configure a application feature.
+/// If a value is needed, the application provides a default.
+/// A rollout changes this default.
+pub trait Defaults {
+    fn defaults(&self, fallback: &Self) -> Result<Self>
+    where
+        Self: Sized;
+}
+
+impl<T: Defaults + Clone> Defaults for Option<T> {
+    fn defaults(&self, fallback: &Self) -> Result<Self> {
+        Ok(match (self, fallback) {
+            (Some(a), Some(b)) => Some(a.defaults(b)?),
+            (Some(_), None) => self.clone(),
+            _ => fallback.clone(),
+        })
+    }
+}
+
+use serde_json::{Map, Value};
+/// We implement https://datatracker.ietf.org/doc/html/rfc7396
+/// such that self is patching the fallback.
+/// The result is the patched object.
+///
+/// * If a self value is null, we take that to equivalent to a delete.
+/// * If both self and fallback are objects, we recursively patch.
+/// * If it exists in either in self or clone, then it is included
+/// * in the result.
+/// * If it exists in both, then we take the self version.
+impl Defaults for Value {
+    fn defaults(&self, fallback: &Self) -> Result<Self> {
+        Ok(match (self, fallback) {
+            (Value::Object(a), Value::Object(b)) => Value::Object(a.defaults(b)?),
+            (Value::Null, _) => fallback.to_owned(),
+            _ => self.to_owned(),
+        })
+    }
+}
+
+impl Defaults for Map<String, Value> {
+    fn defaults(&self, fallback: &Self) -> Result<Self> {
+        let mut map = self.clone();
+        for (k, fb) in fallback {
+            match map.get(k) {
+                Some(existing) if existing.is_null() => {
+                    map.remove(k);
+                }
+                Some(existing) => {
+                    // JSON merging shoudln't error, so there'll be
+                    // nothing to propagate.
+                    map[k] = existing.defaults(fb)?;
+                }
+                _ => {
+                    map.insert(k.clone(), fb.clone());
+                }
+            };
+        }
+        Ok(map)
+    }
+}
+
+use std::collections::HashMap;
+/// Merge the two `HashMap`s, with self acting as the dominant
+/// of the two.
+///
+/// Nimbus' use case is to be merging data coming from the outside,
+/// we should not allow a bad merge to bring the whole system down.
+///
+/// Where values merging fails, we go with the newest version.
+impl<T: Defaults + Clone> Defaults for HashMap<String, T> {
+    fn defaults(&self, fallback: &Self) -> Result<Self> {
+        let mut map = self.clone();
+        for (k, fb) in fallback {
+            match map.get(k) {
+                Some(existing) => {
+                    // if we merged with fb without errors,
+                    if let Ok(v) = existing.defaults(fb) {
+                        map.insert(k.clone(), v);
+                    } // otherwise use the self value, without merging.
+                }
+                _ => {
+                    map.insert(k.clone(), fb.clone());
+                }
+            }
+        }
+        Ok(map)
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus/enrollment.rs.html b/book/rust-docs/src/nimbus/enrollment.rs.html new file mode 100644 index 0000000000..e10f6d70ca --- /dev/null +++ b/book/rust-docs/src/nimbus/enrollment.rs.html @@ -0,0 +1,2403 @@ +enrollment.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
+540
+541
+542
+543
+544
+545
+546
+547
+548
+549
+550
+551
+552
+553
+554
+555
+556
+557
+558
+559
+560
+561
+562
+563
+564
+565
+566
+567
+568
+569
+570
+571
+572
+573
+574
+575
+576
+577
+578
+579
+580
+581
+582
+583
+584
+585
+586
+587
+588
+589
+590
+591
+592
+593
+594
+595
+596
+597
+598
+599
+600
+601
+602
+603
+604
+605
+606
+607
+608
+609
+610
+611
+612
+613
+614
+615
+616
+617
+618
+619
+620
+621
+622
+623
+624
+625
+626
+627
+628
+629
+630
+631
+632
+633
+634
+635
+636
+637
+638
+639
+640
+641
+642
+643
+644
+645
+646
+647
+648
+649
+650
+651
+652
+653
+654
+655
+656
+657
+658
+659
+660
+661
+662
+663
+664
+665
+666
+667
+668
+669
+670
+671
+672
+673
+674
+675
+676
+677
+678
+679
+680
+681
+682
+683
+684
+685
+686
+687
+688
+689
+690
+691
+692
+693
+694
+695
+696
+697
+698
+699
+700
+701
+702
+703
+704
+705
+706
+707
+708
+709
+710
+711
+712
+713
+714
+715
+716
+717
+718
+719
+720
+721
+722
+723
+724
+725
+726
+727
+728
+729
+730
+731
+732
+733
+734
+735
+736
+737
+738
+739
+740
+741
+742
+743
+744
+745
+746
+747
+748
+749
+750
+751
+752
+753
+754
+755
+756
+757
+758
+759
+760
+761
+762
+763
+764
+765
+766
+767
+768
+769
+770
+771
+772
+773
+774
+775
+776
+777
+778
+779
+780
+781
+782
+783
+784
+785
+786
+787
+788
+789
+790
+791
+792
+793
+794
+795
+796
+797
+798
+799
+800
+801
+802
+803
+804
+805
+806
+807
+808
+809
+810
+811
+812
+813
+814
+815
+816
+817
+818
+819
+820
+821
+822
+823
+824
+825
+826
+827
+828
+829
+830
+831
+832
+833
+834
+835
+836
+837
+838
+839
+840
+841
+842
+843
+844
+845
+846
+847
+848
+849
+850
+851
+852
+853
+854
+855
+856
+857
+858
+859
+860
+861
+862
+863
+864
+865
+866
+867
+868
+869
+870
+871
+872
+873
+874
+875
+876
+877
+878
+879
+880
+881
+882
+883
+884
+885
+886
+887
+888
+889
+890
+891
+892
+893
+894
+895
+896
+897
+898
+899
+900
+901
+902
+903
+904
+905
+906
+907
+908
+909
+910
+911
+912
+913
+914
+915
+916
+917
+918
+919
+920
+921
+922
+923
+924
+925
+926
+927
+928
+929
+930
+931
+932
+933
+934
+935
+936
+937
+938
+939
+940
+941
+942
+943
+944
+945
+946
+947
+948
+949
+950
+951
+952
+953
+954
+955
+956
+957
+958
+959
+960
+961
+962
+963
+964
+965
+966
+967
+968
+969
+970
+971
+972
+973
+974
+975
+976
+977
+978
+979
+980
+981
+982
+983
+984
+985
+986
+987
+988
+989
+990
+991
+992
+993
+994
+995
+996
+997
+998
+999
+1000
+1001
+1002
+1003
+1004
+1005
+1006
+1007
+1008
+1009
+1010
+1011
+1012
+1013
+1014
+1015
+1016
+1017
+1018
+1019
+1020
+1021
+1022
+1023
+1024
+1025
+1026
+1027
+1028
+1029
+1030
+1031
+1032
+1033
+1034
+1035
+1036
+1037
+1038
+1039
+1040
+1041
+1042
+1043
+1044
+1045
+1046
+1047
+1048
+1049
+1050
+1051
+1052
+1053
+1054
+1055
+1056
+1057
+1058
+1059
+1060
+1061
+1062
+1063
+1064
+1065
+1066
+1067
+1068
+1069
+1070
+1071
+1072
+1073
+1074
+1075
+1076
+1077
+1078
+1079
+1080
+1081
+1082
+1083
+1084
+1085
+1086
+1087
+1088
+1089
+1090
+1091
+1092
+1093
+1094
+1095
+1096
+1097
+1098
+1099
+1100
+1101
+1102
+1103
+1104
+1105
+1106
+1107
+1108
+1109
+1110
+1111
+1112
+1113
+1114
+1115
+1116
+1117
+1118
+1119
+1120
+1121
+1122
+1123
+1124
+1125
+1126
+1127
+1128
+1129
+1130
+1131
+1132
+1133
+1134
+1135
+1136
+1137
+1138
+1139
+1140
+1141
+1142
+1143
+1144
+1145
+1146
+1147
+1148
+1149
+1150
+1151
+1152
+1153
+1154
+1155
+1156
+1157
+1158
+1159
+1160
+1161
+1162
+1163
+1164
+1165
+1166
+1167
+1168
+1169
+1170
+1171
+1172
+1173
+1174
+1175
+1176
+1177
+1178
+1179
+1180
+1181
+1182
+1183
+1184
+1185
+1186
+1187
+1188
+1189
+1190
+1191
+1192
+1193
+1194
+1195
+1196
+1197
+1198
+1199
+1200
+1201
+
// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+use crate::{
+    defaults::Defaults,
+    error::{NimbusError, Result},
+    evaluator::evaluate_enrollment,
+    json, AvailableRandomizationUnits, Experiment, FeatureConfig, NimbusTargetingHelper,
+    SLUG_REPLACEMENT_PATTERN,
+};
+use serde_derive::*;
+use std::{
+    collections::{HashMap, HashSet},
+    fmt::{Display, Formatter, Result as FmtResult},
+    time::{Duration, SystemTime, UNIX_EPOCH},
+};
+
+pub(crate) const PREVIOUS_ENROLLMENTS_GC_TIME: Duration = Duration::from_secs(365 * 24 * 3600);
+
+// These are types we use internally for managing enrollments.
+// ⚠️ Attention : Changes to this type should be accompanied by a new test  ⚠️
+// ⚠️ in `mod test_schema_bw_compat` below, and may require a DB migration. ⚠️
+#[derive(Deserialize, Serialize, Debug, Clone, Hash, Eq, PartialEq)]
+pub enum EnrolledReason {
+    /// A normal enrollment as per the experiment's rules.
+    Qualified,
+    /// Explicit opt-in.
+    OptIn,
+}
+
+impl Display for EnrolledReason {
+    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
+        Display::fmt(
+            match self {
+                EnrolledReason::Qualified => "Qualified",
+                EnrolledReason::OptIn => "OptIn",
+            },
+            f,
+        )
+    }
+}
+
+// These are types we use internally for managing non-enrollments.
+
+// ⚠️ Attention : Changes to this type should be accompanied by a new test  ⚠️
+// ⚠️ in `mod test_schema_bw_compat` below, and may require a DB migration. ⚠️
+#[derive(Deserialize, Serialize, Debug, Clone, Hash, Eq, PartialEq)]
+pub enum NotEnrolledReason {
+    /// The user opted-out of experiments before we ever got enrolled to this one.
+    OptOut,
+    /// The evaluator bucketing did not choose us.
+    NotSelected,
+    /// We are not being targeted for this experiment.
+    NotTargeted,
+    /// The experiment enrollment is paused.
+    EnrollmentsPaused,
+    /// The experiment used a feature that was already under experiment.
+    FeatureConflict,
+}
+
+impl Display for NotEnrolledReason {
+    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
+        Display::fmt(
+            match self {
+                NotEnrolledReason::OptOut => "OptOut",
+                NotEnrolledReason::NotSelected => "NotSelected",
+                NotEnrolledReason::NotTargeted => "NotTargeted",
+                NotEnrolledReason::EnrollmentsPaused => "EnrollmentsPaused",
+                NotEnrolledReason::FeatureConflict => "FeatureConflict",
+            },
+            f,
+        )
+    }
+}
+
+// These are types we use internally for managing disqualifications.
+
+// ⚠️ Attention : Changes to this type should be accompanied by a new test  ⚠️
+// ⚠️ in `mod test_schema_bw_compat` below, and may require a DB migration. ⚠️
+#[derive(Deserialize, Serialize, Debug, Clone, Hash, Eq, PartialEq)]
+pub enum DisqualifiedReason {
+    /// There was an error.
+    Error,
+    /// The user opted-out from this experiment or experiments in general.
+    OptOut,
+    /// The targeting has changed for an experiment.
+    NotTargeted,
+    /// The bucketing has changed for an experiment.
+    NotSelected,
+}
+
+impl Display for DisqualifiedReason {
+    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
+        Display::fmt(
+            match self {
+                DisqualifiedReason::Error => "Error",
+                DisqualifiedReason::OptOut => "OptOut",
+                DisqualifiedReason::NotSelected => "NotSelected",
+                DisqualifiedReason::NotTargeted => "NotTargeted",
+            },
+            f,
+        )
+    }
+}
+
+// Every experiment has an ExperimentEnrollment, even when we aren't enrolled.
+
+// ⚠️ Attention : Changes to this type should be accompanied by a new test  ⚠️
+// ⚠️ in `mod test_schema_bw_compat` below, and may require a DB migration. ⚠️
+#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
+pub struct ExperimentEnrollment {
+    pub slug: String,
+    pub status: EnrollmentStatus,
+}
+
+impl ExperimentEnrollment {
+    /// Evaluate an experiment enrollment for an experiment
+    /// we are seeing for the first time.
+    fn from_new_experiment(
+        is_user_participating: bool,
+        available_randomization_units: &AvailableRandomizationUnits,
+        experiment: &Experiment,
+        targeting_helper: &NimbusTargetingHelper,
+        out_enrollment_events: &mut Vec<EnrollmentChangeEvent>,
+    ) -> Result<Self> {
+        Ok(if !is_user_participating {
+            Self {
+                slug: experiment.slug.clone(),
+                status: EnrollmentStatus::NotEnrolled {
+                    reason: NotEnrolledReason::OptOut,
+                },
+            }
+        } else if experiment.is_enrollment_paused {
+            Self {
+                slug: experiment.slug.clone(),
+                status: EnrollmentStatus::NotEnrolled {
+                    reason: NotEnrolledReason::EnrollmentsPaused,
+                },
+            }
+        } else {
+            let enrollment =
+                evaluate_enrollment(available_randomization_units, experiment, targeting_helper)?;
+            log::debug!(
+                "Experiment '{}' is new - enrollment status is {:?}",
+                &enrollment.slug,
+                &enrollment
+            );
+            if matches!(enrollment.status, EnrollmentStatus::Enrolled { .. }) {
+                out_enrollment_events.push(enrollment.get_change_event())
+            }
+            enrollment
+        })
+    }
+
+    /// Force enroll ourselves in an experiment.
+    #[cfg_attr(not(feature = "stateful"), allow(unused))]
+    pub(crate) fn from_explicit_opt_in(
+        experiment: &Experiment,
+        branch_slug: &str,
+        out_enrollment_events: &mut Vec<EnrollmentChangeEvent>,
+    ) -> Result<Self> {
+        if !experiment.has_branch(branch_slug) {
+            out_enrollment_events.push(EnrollmentChangeEvent {
+                experiment_slug: experiment.slug.to_string(),
+                branch_slug: branch_slug.to_string(),
+                reason: Some("does-not-exist".to_string()),
+                change: EnrollmentChangeEventType::EnrollFailed,
+            });
+
+            return Err(NimbusError::NoSuchBranch(
+                branch_slug.to_owned(),
+                experiment.slug.clone(),
+            ));
+        }
+        let enrollment = Self {
+            slug: experiment.slug.clone(),
+            status: EnrollmentStatus::new_enrolled(EnrolledReason::OptIn, branch_slug),
+        };
+        out_enrollment_events.push(enrollment.get_change_event());
+        Ok(enrollment)
+    }
+
+    /// Update our enrollment to an experiment we have seen before.
+    #[allow(clippy::too_many_arguments)]
+    fn on_experiment_updated(
+        &self,
+        is_user_participating: bool,
+        available_randomization_units: &AvailableRandomizationUnits,
+        updated_experiment: &Experiment,
+        targeting_helper: &NimbusTargetingHelper,
+        out_enrollment_events: &mut Vec<EnrollmentChangeEvent>,
+    ) -> Result<Self> {
+        Ok(match &self.status {
+            EnrollmentStatus::NotEnrolled { .. } | EnrollmentStatus::Error { .. } => {
+                if !is_user_participating || updated_experiment.is_enrollment_paused {
+                    self.clone()
+                } else {
+                    let updated_enrollment = evaluate_enrollment(
+                        available_randomization_units,
+                        updated_experiment,
+                        targeting_helper,
+                    )?;
+                    log::debug!(
+                        "Experiment '{}' with enrollment {:?} is now {:?}",
+                        &self.slug,
+                        &self,
+                        updated_enrollment
+                    );
+                    if matches!(updated_enrollment.status, EnrollmentStatus::Enrolled { .. }) {
+                        out_enrollment_events.push(updated_enrollment.get_change_event());
+                    }
+                    updated_enrollment
+                }
+            }
+
+            EnrollmentStatus::Enrolled {
+                ref branch,
+                ref reason,
+                ..
+            } => {
+                if !is_user_participating {
+                    log::debug!(
+                        "Existing experiment enrollment '{}' is now disqualified (global opt-out)",
+                        &self.slug
+                    );
+                    let updated_enrollment =
+                        self.disqualify_from_enrolled(DisqualifiedReason::OptOut);
+                    out_enrollment_events.push(updated_enrollment.get_change_event());
+                    updated_enrollment
+                } else if !updated_experiment.has_branch(branch) {
+                    // The branch we were in disappeared!
+                    let updated_enrollment =
+                        self.disqualify_from_enrolled(DisqualifiedReason::Error);
+                    out_enrollment_events.push(updated_enrollment.get_change_event());
+                    updated_enrollment
+                } else if matches!(reason, EnrolledReason::OptIn) {
+                    // we check if we opted-in an experiment, if so
+                    // we don't need to update our enrollment
+                    self.clone()
+                } else {
+                    let evaluated_enrollment = evaluate_enrollment(
+                        available_randomization_units,
+                        updated_experiment,
+                        targeting_helper,
+                    )?;
+                    match evaluated_enrollment.status {
+                        EnrollmentStatus::Error { .. } => {
+                            let updated_enrollment =
+                                self.disqualify_from_enrolled(DisqualifiedReason::Error);
+                            out_enrollment_events.push(updated_enrollment.get_change_event());
+                            updated_enrollment
+                        }
+                        EnrollmentStatus::NotEnrolled {
+                            reason: NotEnrolledReason::NotTargeted,
+                        } => {
+                            log::debug!("Existing experiment enrollment '{}' is now disqualified (targeting change)", &self.slug);
+                            let updated_enrollment =
+                                self.disqualify_from_enrolled(DisqualifiedReason::NotTargeted);
+                            out_enrollment_events.push(updated_enrollment.get_change_event());
+                            updated_enrollment
+                        }
+                        EnrollmentStatus::NotEnrolled {
+                            reason: NotEnrolledReason::NotSelected,
+                        } => {
+                            // In the case of a rollout being scaled back, we should be disqualified with NotSelected.
+                            //
+                            let updated_enrollment =
+                                self.disqualify_from_enrolled(DisqualifiedReason::NotSelected);
+                            out_enrollment_events.push(updated_enrollment.get_change_event());
+                            updated_enrollment
+                        }
+                        EnrollmentStatus::NotEnrolled { .. }
+                        | EnrollmentStatus::Enrolled { .. }
+                        | EnrollmentStatus::Disqualified { .. }
+                        | EnrollmentStatus::WasEnrolled { .. } => self.clone(),
+                    }
+                }
+            }
+            EnrollmentStatus::Disqualified {
+                ref branch, reason, ..
+            } => {
+                if !is_user_participating {
+                    log::debug!(
+                        "Disqualified experiment enrollment '{}' has been reset to not-enrolled (global opt-out)",
+                        &self.slug
+                    );
+                    Self {
+                        slug: self.slug.clone(),
+                        status: EnrollmentStatus::Disqualified {
+                            reason: DisqualifiedReason::OptOut,
+                            branch: branch.clone(),
+                        },
+                    }
+                } else if updated_experiment.is_rollout
+                    && matches!(
+                        reason,
+                        DisqualifiedReason::NotSelected | DisqualifiedReason::NotTargeted,
+                    )
+                {
+                    let evaluated_enrollment = evaluate_enrollment(
+                        available_randomization_units,
+                        updated_experiment,
+                        targeting_helper,
+                    )?;
+                    match evaluated_enrollment.status {
+                        EnrollmentStatus::Enrolled { .. } => evaluated_enrollment,
+                        _ => self.clone(),
+                    }
+                } else {
+                    self.clone()
+                }
+            }
+            EnrollmentStatus::WasEnrolled { .. } => self.clone(),
+        })
+    }
+
+    /// Transition our enrollment to WasEnrolled (Option::Some) or delete it (Option::None)
+    /// after an experiment has disappeared from the server.
+    ///
+    /// If we transitioned to WasEnrolled, our enrollment will be garbage collected
+    /// from the database after `PREVIOUS_ENROLLMENTS_GC_TIME`.
+    fn on_experiment_ended(
+        &self,
+        out_enrollment_events: &mut Vec<EnrollmentChangeEvent>,
+    ) -> Option<Self> {
+        log::debug!(
+            "Experiment '{}' vanished while we had enrollment status of {:?}",
+            self.slug,
+            self
+        );
+        let branch = match self.status {
+            EnrollmentStatus::Enrolled { ref branch, .. }
+            | EnrollmentStatus::Disqualified { ref branch, .. } => branch,
+            EnrollmentStatus::NotEnrolled { .. }
+            | EnrollmentStatus::WasEnrolled { .. }
+            | EnrollmentStatus::Error { .. } => return None, // We were never enrolled anyway, simply delete the enrollment record from the DB.
+        };
+        let enrollment = Self {
+            slug: self.slug.clone(),
+            status: EnrollmentStatus::WasEnrolled {
+                branch: branch.to_owned(),
+                experiment_ended_at: now_secs(),
+            },
+        };
+        out_enrollment_events.push(enrollment.get_change_event());
+        Some(enrollment)
+    }
+
+    /// Force unenroll ourselves from an experiment.
+    #[allow(clippy::unnecessary_wraps)]
+    #[cfg_attr(not(feature = "stateful"), allow(unused))]
+    pub(crate) fn on_explicit_opt_out(
+        &self,
+        out_enrollment_events: &mut Vec<EnrollmentChangeEvent>,
+    ) -> ExperimentEnrollment {
+        match self.status {
+            EnrollmentStatus::Enrolled { .. } => {
+                let enrollment = self.disqualify_from_enrolled(DisqualifiedReason::OptOut);
+                out_enrollment_events.push(enrollment.get_change_event());
+                enrollment
+            }
+            EnrollmentStatus::NotEnrolled { .. } => Self {
+                slug: self.slug.to_string(),
+                status: EnrollmentStatus::NotEnrolled {
+                    reason: NotEnrolledReason::OptOut, // Explicitly set the reason to OptOut.
+                },
+            },
+            EnrollmentStatus::Disqualified { .. }
+            | EnrollmentStatus::WasEnrolled { .. }
+            | EnrollmentStatus::Error { .. } => {
+                // Nothing to do here.
+                self.clone()
+            }
+        }
+    }
+
+    /// Reset identifiers in response to application-level telemetry reset.
+    ///
+    /// We move any enrolled experiments to the "disqualified" state, since their further
+    /// partipation would submit partial data that could skew analysis.
+    ///
+    #[cfg_attr(not(feature = "stateful"), allow(unused))]
+    pub fn reset_telemetry_identifiers(
+        &self,
+        out_enrollment_events: &mut Vec<EnrollmentChangeEvent>,
+    ) -> Self {
+        let updated = match self.status {
+            EnrollmentStatus::Enrolled { .. } => {
+                let disqualified = self.disqualify_from_enrolled(DisqualifiedReason::OptOut);
+                out_enrollment_events.push(disqualified.get_change_event());
+                disqualified
+            }
+            EnrollmentStatus::NotEnrolled { .. }
+            | EnrollmentStatus::Disqualified { .. }
+            | EnrollmentStatus::WasEnrolled { .. }
+            | EnrollmentStatus::Error { .. } => self.clone(),
+        };
+        ExperimentEnrollment {
+            status: updated.status.clone(),
+            ..updated
+        }
+    }
+
+    /// Garbage collect old experiments we've kept a WasEnrolled enrollment from.
+    /// Returns Option::None if the enrollment should be nuked from the db.
+    fn maybe_garbage_collect(&self) -> Option<Self> {
+        if let EnrollmentStatus::WasEnrolled {
+            experiment_ended_at,
+            ..
+        } = self.status
+        {
+            let time_since_transition = Duration::from_secs(now_secs() - experiment_ended_at);
+            if time_since_transition < PREVIOUS_ENROLLMENTS_GC_TIME {
+                return Some(self.clone());
+            }
+        }
+        log::debug!("Garbage collecting enrollment '{}'", self.slug);
+        None
+    }
+
+    // Create a telemetry event describing the transition
+    // to the current enrollment state.
+    fn get_change_event(&self) -> EnrollmentChangeEvent {
+        match &self.status {
+            EnrollmentStatus::Enrolled { branch, .. } => EnrollmentChangeEvent::new(
+                &self.slug,
+                branch,
+                None,
+                EnrollmentChangeEventType::Enrollment,
+            ),
+            EnrollmentStatus::WasEnrolled { branch, .. } => EnrollmentChangeEvent::new(
+                &self.slug,
+                branch,
+                None,
+                EnrollmentChangeEventType::Unenrollment,
+            ),
+            EnrollmentStatus::Disqualified { branch, reason, .. } => EnrollmentChangeEvent::new(
+                &self.slug,
+                branch,
+                match reason {
+                    DisqualifiedReason::NotSelected => Some("bucketing"),
+                    DisqualifiedReason::NotTargeted => Some("targeting"),
+                    DisqualifiedReason::OptOut => Some("optout"),
+                    DisqualifiedReason::Error => Some("error"),
+                },
+                EnrollmentChangeEventType::Disqualification,
+            ),
+            EnrollmentStatus::NotEnrolled { .. } | EnrollmentStatus::Error { .. } => {
+                unreachable!()
+            }
+        }
+    }
+
+    /// If the current state is `Enrolled`, move to `Disqualified` with the given reason.
+    fn disqualify_from_enrolled(&self, reason: DisqualifiedReason) -> Self {
+        match self.status {
+            EnrollmentStatus::Enrolled { ref branch, .. } => ExperimentEnrollment {
+                status: EnrollmentStatus::Disqualified {
+                    reason,
+                    branch: branch.to_owned(),
+                },
+                ..self.clone()
+            },
+            EnrollmentStatus::NotEnrolled { .. }
+            | EnrollmentStatus::Disqualified { .. }
+            | EnrollmentStatus::WasEnrolled { .. }
+            | EnrollmentStatus::Error { .. } => self.clone(),
+        }
+    }
+}
+
+// ⚠️ Attention : Changes to this type should be accompanied by a new test  ⚠️
+// ⚠️ in `mod test_schema_bw_compat` below, and may require a DB migration. ⚠️
+#[derive(Deserialize, Serialize, Debug, Clone, Hash, Eq, PartialEq)]
+pub enum EnrollmentStatus {
+    Enrolled {
+        reason: EnrolledReason,
+        branch: String,
+    },
+    NotEnrolled {
+        reason: NotEnrolledReason,
+    },
+    Disqualified {
+        reason: DisqualifiedReason,
+        branch: String,
+    },
+    WasEnrolled {
+        branch: String,
+        experiment_ended_at: u64, // unix timestamp in sec, used to GC old enrollments
+    },
+    // There was some error opting in.
+    Error {
+        // Ideally this would be an Error, but then we'd need to make Error
+        // serde compatible, which isn't trivial nor desirable.
+        reason: String,
+    },
+}
+
+impl EnrollmentStatus {
+    pub fn name(&self) -> String {
+        match self {
+            EnrollmentStatus::Enrolled { .. } => "Enrolled",
+            EnrollmentStatus::NotEnrolled { .. } => "NotEnrolled",
+            EnrollmentStatus::Disqualified { .. } => "Disqualified",
+            EnrollmentStatus::WasEnrolled { .. } => "WasEnrolled",
+            EnrollmentStatus::Error { .. } => "Error",
+        }
+        .into()
+    }
+}
+
+impl EnrollmentStatus {
+    // Note that for now, we only support a single feature_id per experiment,
+    // so this code is expected to shift once we start supporting multiple.
+    pub fn new_enrolled(reason: EnrolledReason, branch: &str) -> Self {
+        EnrollmentStatus::Enrolled {
+            reason,
+            branch: branch.to_owned(),
+        }
+    }
+
+    // This is used in examples, but not in the main dylib, and
+    // triggers a dead code warning when building with `--release`.
+    pub fn is_enrolled(&self) -> bool {
+        matches!(self, EnrollmentStatus::Enrolled { .. })
+    }
+}
+
+pub(crate) trait ExperimentMetadata {
+    fn get_slug(&self) -> String;
+
+    fn is_rollout(&self) -> bool;
+}
+
+pub(crate) struct EnrollmentsEvolver<'a> {
+    available_randomization_units: &'a AvailableRandomizationUnits,
+    targeting_helper: &'a NimbusTargetingHelper,
+    coenrolling_feature_ids: &'a HashSet<&'a str>,
+}
+
+impl<'a> EnrollmentsEvolver<'a> {
+    pub(crate) fn new(
+        available_randomization_units: &'a AvailableRandomizationUnits,
+        targeting_helper: &'a NimbusTargetingHelper,
+        coenrolling_feature_ids: &'a HashSet<&str>,
+    ) -> Self {
+        Self {
+            available_randomization_units,
+            targeting_helper,
+            coenrolling_feature_ids,
+        }
+    }
+
+    pub(crate) fn evolve_enrollments<E>(
+        &self,
+        is_user_participating: bool,
+        prev_experiments: &[E],
+        next_experiments: &[Experiment],
+        prev_enrollments: &[ExperimentEnrollment],
+    ) -> Result<(Vec<ExperimentEnrollment>, Vec<EnrollmentChangeEvent>)>
+    where
+        E: ExperimentMetadata + Clone,
+    {
+        let mut enrollments: Vec<ExperimentEnrollment> = Default::default();
+        let mut events: Vec<EnrollmentChangeEvent> = Default::default();
+
+        // Do rollouts first.
+        // At the moment, we only allow one rollout per feature, so we can re-use the same machinery as experiments
+        let (prev_rollouts, ro_enrollments) = filter_experiments_and_enrollments(
+            prev_experiments,
+            prev_enrollments,
+            ExperimentMetadata::is_rollout,
+        );
+        let next_rollouts = filter_experiments(next_experiments, ExperimentMetadata::is_rollout);
+
+        let (next_ro_enrollments, ro_events) = self.evolve_enrollment_recipes(
+            is_user_participating,
+            &prev_rollouts,
+            &next_rollouts,
+            &ro_enrollments,
+        )?;
+
+        enrollments.extend(next_ro_enrollments);
+        events.extend(ro_events);
+
+        let ro_slugs: HashSet<String> = ro_enrollments.iter().map(|e| e.slug.clone()).collect();
+
+        // Now we do the experiments.
+        // We need to mop up all the enrollments that aren't rollouts (not just belonging to experiments that aren't rollouts)
+        // because some of them don't belong to any experiments recipes, and evolve_enrollment_recipes will handle the error
+        // states for us.
+        let prev_experiments = filter_experiments(prev_experiments, |exp| !exp.is_rollout());
+        let next_experiments = filter_experiments(next_experiments, |exp| !exp.is_rollout());
+        let prev_enrollments: Vec<ExperimentEnrollment> = prev_enrollments
+            .iter()
+            .filter(|e| !ro_slugs.contains(&e.slug))
+            .map(|e| e.to_owned())
+            .collect();
+
+        let (next_exp_enrollments, exp_events) = self.evolve_enrollment_recipes(
+            is_user_participating,
+            &prev_experiments,
+            &next_experiments,
+            &prev_enrollments,
+        )?;
+
+        enrollments.extend(next_exp_enrollments);
+        events.extend(exp_events);
+
+        Ok((enrollments, events))
+    }
+
+    /// Evolve and calculate the new set of enrollments, using the
+    /// previous and current state of experiments and current enrollments.
+    pub(crate) fn evolve_enrollment_recipes<E>(
+        &self,
+        is_user_participating: bool,
+        prev_experiments: &[E],
+        next_experiments: &[Experiment],
+        prev_enrollments: &[ExperimentEnrollment],
+    ) -> Result<(Vec<ExperimentEnrollment>, Vec<EnrollmentChangeEvent>)>
+    where
+        E: ExperimentMetadata + Clone,
+    {
+        let mut enrollment_events = vec![];
+        let prev_experiments_map = map_experiments(prev_experiments);
+        let next_experiments_map = map_experiments(next_experiments);
+        let prev_enrollments_map = map_enrollments(prev_enrollments);
+
+        // Step 1. Build an initial active_features to keep track of
+        // the features that are being experimented upon.
+        let mut enrolled_features = HashMap::with_capacity(next_experiments.len());
+        let mut coenrolling_features = HashMap::with_capacity(next_experiments.len());
+
+        let mut next_enrollments = Vec::with_capacity(next_experiments.len());
+
+        // Step 2.
+        // Evolve the experiments with previous enrollments first (except for
+        // those that already have a feature conflict).  While we're doing so,
+        // start building up active_features, the map of feature_ids under
+        // experiment to EnrolledFeatureConfigs, and next_enrollments.
+
+        for prev_enrollment in prev_enrollments {
+            if matches!(
+                prev_enrollment.status,
+                EnrollmentStatus::NotEnrolled {
+                    reason: NotEnrolledReason::FeatureConflict
+                }
+            ) {
+                continue;
+            }
+            let slug = &prev_enrollment.slug;
+
+            let next_enrollment = match self.evolve_enrollment(
+                is_user_participating,
+                prev_experiments_map.get(slug).copied(),
+                next_experiments_map.get(slug).copied(),
+                Some(prev_enrollment),
+                &mut enrollment_events,
+            ) {
+                Ok(enrollment) => enrollment,
+                Err(e) => {
+                    // It would be a fine thing if we had counters that
+                    // collected the number of errors here, and at the
+                    // place in this function where enrollments could be
+                    // dropped.  We could then send those errors to
+                    // telemetry so that they could be monitored (SDK-309)
+                    log::warn!("{} in evolve_enrollment (with prev_enrollment) returned None; (slug: {}, prev_enrollment: {:?}); ", e, slug, prev_enrollment);
+                    None
+                }
+            };
+
+            self.reserve_enrolled_features(
+                next_enrollment,
+                &next_experiments_map,
+                &mut enrolled_features,
+                &mut coenrolling_features,
+                &mut next_enrollments,
+            );
+        }
+
+        // Step 3. Evolve the remaining enrollments with the previous and
+        // next data.
+        for next_experiment in next_experiments {
+            let slug = &next_experiment.slug;
+
+            // Check that the feature ids that this experiment needs are available.  If not, then declare
+            // the enrollment as NotEnrolled; and we continue to the next
+            // experiment.
+            // `needed_features_in_use` are the features needed for this experiment, but already in use.
+            // If this is not empty, then the experiment is either already enrolled, or cannot be enrolled.
+            let needed_features_in_use: Vec<&EnrolledFeatureConfig> = next_experiment
+                .get_feature_ids()
+                .iter()
+                .filter_map(|id| enrolled_features.get(id))
+                .collect();
+            if !needed_features_in_use.is_empty() {
+                let is_our_experiment = needed_features_in_use.iter().any(|f| &f.slug == slug);
+                if is_our_experiment {
+                    // At least one of these conflicted features are in use by this experiment.
+                    // Unless the experiment has changed midflight, all the features will be from
+                    // this experiment.
+                    assert!(needed_features_in_use.iter().all(|f| &f.slug == slug));
+                    // N.B. If this experiment is enrolled already, then we called
+                    // evolve_enrollment() on this enrollment and this experiment above.
+                } else {
+                    // At least one feature needed for this experiment is already in use by another experiment.
+                    // Thus, we cannot proceed with an enrollment other than as a `FeatureConflict`.
+                    next_enrollments.push(ExperimentEnrollment {
+                        slug: slug.clone(),
+                        status: EnrollmentStatus::NotEnrolled {
+                            reason: NotEnrolledReason::FeatureConflict,
+                        },
+                    });
+
+                    enrollment_events.push(EnrollmentChangeEvent {
+                        experiment_slug: slug.clone(),
+                        branch_slug: "N/A".to_string(),
+                        reason: Some("feature-conflict".to_string()),
+                        change: EnrollmentChangeEventType::EnrollFailed,
+                    })
+                }
+                // Whether it's our experiment or not that is using these features, no further enrollment can
+                // happen.
+                // Because no change has happened to this experiment's enrollment status, we don't need
+                // to log an enrollment event.
+                // All we can do is continue to the next experiment.
+                continue;
+            }
+
+            // If we got here, then the features are not already active.
+            // But we evolved all the existing enrollments in step 2,
+            // (except the feature conflicted ones)
+            // so we should be mindful that we don't evolve them a second time.
+            let prev_enrollment = prev_enrollments_map.get(slug).copied();
+
+            if prev_enrollment.is_none()
+                || matches!(
+                    prev_enrollment.unwrap().status,
+                    EnrollmentStatus::NotEnrolled {
+                        reason: NotEnrolledReason::FeatureConflict
+                    }
+                )
+            {
+                let next_enrollment = match self.evolve_enrollment(
+                    is_user_participating,
+                    prev_experiments_map.get(slug).copied(),
+                    Some(next_experiment),
+                    prev_enrollment,
+                    &mut enrollment_events,
+                ) {
+                    Ok(enrollment) => enrollment,
+                    Err(e) => {
+                        // It would be a fine thing if we had counters that
+                        // collected the number of errors here, and at the
+                        // place in this function where enrollments could be
+                        // dropped.  We could then send those errors to
+                        // telemetry so that they could be monitored (SDK-309)
+                        log::warn!("{} in evolve_enrollment (with no feature conflict) returned None; (slug: {}, prev_enrollment: {:?}); ", e, slug, prev_enrollment);
+                        None
+                    }
+                };
+
+                self.reserve_enrolled_features(
+                    next_enrollment,
+                    &next_experiments_map,
+                    &mut enrolled_features,
+                    &mut coenrolling_features,
+                    &mut next_enrollments,
+                );
+            }
+        }
+
+        enrolled_features.extend(coenrolling_features);
+
+        // Check that we generate the enrolled feature map from the new
+        // enrollments and new experiments.  Perhaps this should just be an
+        // assert.
+        let updated_enrolled_features = map_features(
+            &next_enrollments,
+            &next_experiments_map,
+            self.coenrolling_feature_ids,
+        );
+        if enrolled_features != updated_enrolled_features {
+            Err(NimbusError::InternalError(
+                "Next enrollment calculation error",
+            ))
+        } else {
+            Ok((next_enrollments, enrollment_events))
+        }
+    }
+
+    // Book-keeping method used in evolve_enrollments.
+    fn reserve_enrolled_features(
+        &self,
+        latest_enrollment: Option<ExperimentEnrollment>,
+        experiments: &HashMap<String, &Experiment>,
+        enrolled_features: &mut HashMap<String, EnrolledFeatureConfig>,
+        coenrolling_features: &mut HashMap<String, EnrolledFeatureConfig>,
+        enrollments: &mut Vec<ExperimentEnrollment>,
+    ) {
+        if let Some(enrollment) = latest_enrollment {
+            // Now we have an enrollment object!
+            // If it's an enrolled enrollment, then get the FeatureConfigs
+            // from the experiment and store them in the enrolled_features or coenrolling_features maps.
+            for enrolled_feature in get_enrolled_feature_configs(&enrollment, experiments) {
+                populate_feature_maps(
+                    enrolled_feature,
+                    self.coenrolling_feature_ids,
+                    enrolled_features,
+                    coenrolling_features,
+                );
+            }
+            // Also, record the enrollment for our return value
+            enrollments.push(enrollment);
+        }
+    }
+
+    /// Evolve a single enrollment using the previous and current state of an
+    /// experiment and maybe garbage collect at least a subset of invalid
+    /// experiments.
+    ///
+    /// XXX need to verify the exact set of gc-related side-effects and
+    /// document them here.
+    ///
+    /// Returns an Option-wrapped version of the updated enrollment.  None
+    /// means that the enrollment has been/should be discarded.
+    pub(crate) fn evolve_enrollment<E>(
+        &self,
+        is_user_participating: bool,
+        prev_experiment: Option<&E>,
+        next_experiment: Option<&Experiment>,
+        prev_enrollment: Option<&ExperimentEnrollment>,
+        out_enrollment_events: &mut Vec<EnrollmentChangeEvent>, // out param containing the events we'd like to emit to glean.
+    ) -> Result<Option<ExperimentEnrollment>>
+    where
+        E: ExperimentMetadata + Clone,
+    {
+        let is_already_enrolled = if let Some(enrollment) = prev_enrollment {
+            enrollment.status.is_enrolled()
+        } else {
+            false
+        };
+
+        // XXX This is not pretty, however, we need to re-write the way sticky targeting strings are generated in
+        // experimenter. Once https://github.com/mozilla/experimenter/issues/8661 is fixed, we can remove the calculation
+        // for `is_already_enrolled` above, the `put` call here and the `put` method declaration, and replace it with
+        // let th = self.targeting_helper;
+        let th = self
+            .targeting_helper
+            .put("is_already_enrolled", is_already_enrolled);
+
+        Ok(match (prev_experiment, next_experiment, prev_enrollment) {
+            // New experiment.
+            (None, Some(experiment), None) => Some(ExperimentEnrollment::from_new_experiment(
+                is_user_participating,
+                self.available_randomization_units,
+                experiment,
+                &th,
+                out_enrollment_events,
+            )?),
+            // Experiment deleted remotely.
+            (Some(_), None, Some(enrollment)) => {
+                enrollment.on_experiment_ended(out_enrollment_events)
+            }
+            // Known experiment.
+            (Some(_), Some(experiment), Some(enrollment)) => {
+                Some(enrollment.on_experiment_updated(
+                    is_user_participating,
+                    self.available_randomization_units,
+                    experiment,
+                    &th,
+                    out_enrollment_events,
+                )?)
+            }
+            (None, None, Some(enrollment)) => enrollment.maybe_garbage_collect(),
+            (None, Some(_), Some(_)) => {
+                return Err(NimbusError::InternalError(
+                    "New experiment but enrollment already exists.",
+                ))
+            }
+            (Some(_), None, None) | (Some(_), Some(_), None) => {
+                return Err(NimbusError::InternalError(
+                    "Experiment in the db did not have an associated enrollment record.",
+                ))
+            }
+            (None, None, None) => {
+                return Err(NimbusError::InternalError(
+                    "evolve_experiment called with nothing that could evolve or be evolved",
+                ))
+            }
+        })
+    }
+}
+
+fn map_experiments<E>(experiments: &[E]) -> HashMap<String, &E>
+where
+    E: ExperimentMetadata + Clone,
+{
+    let mut map_experiments = HashMap::with_capacity(experiments.len());
+    for e in experiments {
+        map_experiments.insert(e.get_slug(), e);
+    }
+    map_experiments
+}
+
+pub fn map_enrollments(
+    enrollments: &[ExperimentEnrollment],
+) -> HashMap<String, &ExperimentEnrollment> {
+    let mut map_enrollments = HashMap::with_capacity(enrollments.len());
+    for e in enrollments {
+        map_enrollments.insert(e.slug.clone(), e);
+    }
+    map_enrollments
+}
+
+pub(crate) fn filter_experiments_and_enrollments<E>(
+    experiments: &[E],
+    enrollments: &[ExperimentEnrollment],
+    filter_fn: fn(&E) -> bool,
+) -> (Vec<E>, Vec<ExperimentEnrollment>)
+where
+    E: ExperimentMetadata + Clone,
+{
+    let experiments: Vec<E> = filter_experiments(experiments, filter_fn);
+
+    let slugs: HashSet<String> = experiments.iter().map(|e| e.get_slug()).collect();
+
+    let enrollments: Vec<ExperimentEnrollment> = enrollments
+        .iter()
+        .filter(|e| slugs.contains(&e.slug))
+        .map(|e| e.to_owned())
+        .collect();
+
+    (experiments, enrollments)
+}
+
+fn filter_experiments<E>(experiments: &[E], filter_fn: fn(&E) -> bool) -> Vec<E>
+where
+    E: ExperimentMetadata + Clone,
+{
+    experiments
+        .iter()
+        .filter(|e| filter_fn(e))
+        .cloned()
+        .collect()
+}
+
+/// Take a list of enrollments and a map of experiments, and generate mapping of `feature_id` to
+/// `EnrolledFeatureConfig` structs.
+fn map_features(
+    enrollments: &[ExperimentEnrollment],
+    experiments: &HashMap<String, &Experiment>,
+    coenrolling_ids: &HashSet<&str>,
+) -> HashMap<String, EnrolledFeatureConfig> {
+    let mut colliding_features = HashMap::with_capacity(enrollments.len());
+    let mut coenrolling_features = HashMap::with_capacity(enrollments.len());
+    for enrolled_feature_config in enrollments
+        .iter()
+        .flat_map(|e| get_enrolled_feature_configs(e, experiments))
+    {
+        populate_feature_maps(
+            enrolled_feature_config,
+            coenrolling_ids,
+            &mut colliding_features,
+            &mut coenrolling_features,
+        );
+    }
+    colliding_features.extend(coenrolling_features.drain());
+
+    colliding_features
+}
+
+pub fn map_features_by_feature_id(
+    enrollments: &[ExperimentEnrollment],
+    experiments: &[Experiment],
+    coenrolling_ids: &HashSet<&str>,
+) -> HashMap<String, EnrolledFeatureConfig> {
+    let (rollouts, ro_enrollments) = filter_experiments_and_enrollments(
+        experiments,
+        enrollments,
+        ExperimentMetadata::is_rollout,
+    );
+    let (experiments, exp_enrollments) =
+        filter_experiments_and_enrollments(experiments, enrollments, |exp| !exp.is_rollout());
+
+    let features_under_rollout = map_features(
+        &ro_enrollments,
+        &map_experiments(&rollouts),
+        coenrolling_ids,
+    );
+    let features_under_experiment = map_features(
+        &exp_enrollments,
+        &map_experiments(&experiments),
+        coenrolling_ids,
+    );
+
+    features_under_experiment
+        .defaults(&features_under_rollout)
+        .unwrap()
+}
+
+pub(crate) fn populate_feature_maps(
+    enrolled_feature: EnrolledFeatureConfig,
+    coenrolling_feature_ids: &HashSet<&str>,
+    colliding_features: &mut HashMap<String, EnrolledFeatureConfig>,
+    coenrolling_features: &mut HashMap<String, EnrolledFeatureConfig>,
+) {
+    let feature_id = &enrolled_feature.feature_id;
+    if !coenrolling_feature_ids.contains(feature_id.as_str()) {
+        // If we're not allowing co-enrollment for this feature, then add it to enrolled_features.
+        // We'll use this map to prevent collisions.
+        colliding_features.insert(feature_id.clone(), enrolled_feature);
+    } else if let Some(existing) = coenrolling_features.get(feature_id) {
+        // Otherwise, we'll add to the coenrolling_features map.
+        // In this branch, we've enrolled in one experiment already before this one.
+        // We take care to merge this one with the existing one.
+        let merged = enrolled_feature
+            .defaults(existing)
+            .expect("A feature config hasn't been able to merge; this is a bug in Nimbus");
+
+        // We change the branch to None, so we don't send exposure events from this feature.
+        // This is the subject of the ADR for https://mozilla-hub.atlassian.net/browse/EXP-3630.
+        let merged = EnrolledFeatureConfig {
+            // We make up the slug by appending. This is only for debugging reasons.
+            slug: format!("{}+{}", &existing.slug, &enrolled_feature.slug),
+            branch: None,
+            ..merged
+        };
+        coenrolling_features.insert(feature_id.clone(), merged);
+    } else {
+        // In this branch, this is the first time we've added this feature to the coenrolling_features map.
+        coenrolling_features.insert(feature_id.clone(), enrolled_feature);
+    }
+}
+
+fn get_enrolled_feature_configs(
+    enrollment: &ExperimentEnrollment,
+    experiments: &HashMap<String, &Experiment>,
+) -> Vec<EnrolledFeatureConfig> {
+    // If status is not enrolled, then we can leave early.
+    let branch_slug = match &enrollment.status {
+        EnrollmentStatus::Enrolled { branch, .. } => branch,
+        _ => return Vec::new(),
+    };
+
+    let experiment_slug = &enrollment.slug;
+
+    let experiment = match experiments.get(experiment_slug).copied() {
+        Some(exp) => exp,
+        _ => return Vec::new(),
+    };
+
+    // Get the branch from the experiment, and then get the feature configs
+    // from there.
+    let mut branch_features = match &experiment.get_branch(branch_slug) {
+        Some(branch) => branch.get_feature_configs(),
+        _ => Default::default(),
+    };
+
+    branch_features.iter_mut().for_each(|f| {
+        json::replace_str_in_map(&mut f.value, SLUG_REPLACEMENT_PATTERN, experiment_slug);
+    });
+
+    let branch_feature_ids = &branch_features
+        .iter()
+        .map(|f| &f.feature_id)
+        .collect::<HashSet<_>>();
+
+    // The experiment might have other branches that deal with different features.
+    // We don't want them getting involved in other experiments, so we'll make default
+    // FeatureConfigs.
+    let non_branch_features: Vec<FeatureConfig> = experiment
+        .get_feature_ids()
+        .into_iter()
+        .filter(|feature_id| !branch_feature_ids.contains(feature_id))
+        .map(|feature_id| FeatureConfig {
+            feature_id,
+            ..Default::default()
+        })
+        .collect();
+
+    // Now we've got the feature configs for all features in this experiment,
+    // we can make EnrolledFeatureConfigs with them.
+    branch_features
+        .iter()
+        .chain(non_branch_features.iter())
+        .map(|f| EnrolledFeatureConfig {
+            feature: f.to_owned(),
+            slug: experiment_slug.clone(),
+            branch: if !experiment.is_rollout() {
+                Some(branch_slug.clone())
+            } else {
+                None
+            },
+            feature_id: f.feature_id.clone(),
+        })
+        .collect()
+}
+
+/// Small transitory struct to contain all the information needed to configure a feature with the Feature API.
+/// By design, we don't want to store it on the disk. Instead we calculate it from experiments
+/// and enrollments.
+#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
+#[serde(rename_all = "camelCase")]
+pub struct EnrolledFeatureConfig {
+    pub feature: FeatureConfig,
+    pub slug: String,
+    pub branch: Option<String>,
+    pub feature_id: String,
+}
+
+impl Defaults for EnrolledFeatureConfig {
+    fn defaults(&self, fallback: &Self) -> Result<Self> {
+        if self.feature_id != fallback.feature_id {
+            // This is unlikely to happen, but if it does it's a bug in Nimbus
+            Err(NimbusError::InternalError(
+                "Cannot merge enrolled feature configs from different features",
+            ))
+        } else {
+            Ok(Self {
+                slug: self.slug.to_owned(),
+                feature_id: self.feature_id.to_owned(),
+                // Merge the actual feature config.
+                feature: self.feature.defaults(&fallback.feature)?,
+                // If this is an experiment, then this will be Some(_).
+                // The feature is involved in zero or one experiments, and 0 or more rollouts.
+                // So we can clone this Option safely.
+                branch: self.branch.to_owned(),
+            })
+        }
+    }
+}
+
+impl ExperimentMetadata for EnrolledFeatureConfig {
+    fn get_slug(&self) -> String {
+        self.slug.clone()
+    }
+
+    fn is_rollout(&self) -> bool {
+        self.branch.is_none()
+    }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct EnrolledFeature {
+    pub slug: String,
+    pub branch: Option<String>,
+    pub feature_id: String,
+}
+
+impl From<&EnrolledFeatureConfig> for EnrolledFeature {
+    fn from(value: &EnrolledFeatureConfig) -> Self {
+        Self {
+            slug: value.slug.clone(),
+            branch: value.branch.clone(),
+            feature_id: value.feature_id.clone(),
+        }
+    }
+}
+
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub struct EnrollmentChangeEvent {
+    pub experiment_slug: String,
+    pub branch_slug: String,
+    pub reason: Option<String>,
+    pub change: EnrollmentChangeEventType,
+}
+
+impl EnrollmentChangeEvent {
+    pub(crate) fn new(
+        slug: &str,
+        branch: &str,
+        reason: Option<&str>,
+        change: EnrollmentChangeEventType,
+    ) -> Self {
+        Self {
+            experiment_slug: slug.to_owned(),
+            branch_slug: branch.to_owned(),
+            reason: reason.map(|s| s.to_owned()),
+            change,
+        }
+    }
+}
+
+#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
+pub enum EnrollmentChangeEventType {
+    Enrollment,
+    EnrollFailed,
+    Disqualification,
+    Unenrollment,
+    #[cfg_attr(not(feature = "stateful"), allow(unused))]
+    UnenrollFailed,
+}
+
+pub(crate) fn now_secs() -> u64 {
+    SystemTime::now()
+        .duration_since(UNIX_EPOCH)
+        .expect("Current date before Unix Epoch.")
+        .as_secs()
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus/error.rs.html b/book/rust-docs/src/nimbus/error.rs.html new file mode 100644 index 0000000000..18521b0765 --- /dev/null +++ b/book/rust-docs/src/nimbus/error.rs.html @@ -0,0 +1,199 @@ +error.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ * */
+
+//! Not complete yet
+//! This is where the error definitions can go
+//! TODO: Implement proper error handling, this would include defining the error enum,
+//! impl std::error::Error using `thiserror` and ensuring all errors are handled appropriately
+
+use std::num::{ParseIntError, TryFromIntError};
+
+#[derive(Debug, thiserror::Error)]
+pub enum NimbusError {
+    #[error("Invalid persisted data")]
+    InvalidPersistedData,
+    #[cfg(feature = "stateful")]
+    #[error("Rkv error: {0}")]
+    RkvError(#[from] rkv::StoreError),
+    #[error("IO error: {0}")]
+    IOError(#[from] std::io::Error),
+    #[error("JSON Error: {0}")]
+    JSONError(#[from] serde_json::Error),
+    #[error("EvaluationError: {0}")]
+    EvaluationError(String),
+    #[error("Invalid Expression - didn't evaluate to a bool")]
+    InvalidExpression,
+    #[error("InvalidFractionError: Should be between 0 and 1")]
+    InvalidFraction,
+    #[error("TryInto error: {0}")]
+    TryFromSliceError(#[from] std::array::TryFromSliceError),
+    #[error("Empty ratios!")]
+    EmptyRatiosError,
+    #[error("Attempt to access an element that is out of bounds")]
+    OutOfBoundsError,
+    #[error("Error parsing URL: {0}")]
+    UrlParsingError(#[from] url::ParseError),
+    #[error("UUID parsing error: {0}")]
+    UuidError(#[from] uuid::Error),
+    #[error("Invalid experiment data received")]
+    InvalidExperimentFormat,
+    #[error("Invalid path: {0}")]
+    InvalidPath(String),
+    #[error("Internal error: {0}")]
+    InternalError(&'static str),
+    #[error("The experiment {0} does not exist")]
+    NoSuchExperiment(String),
+    #[error("The branch {0} does not exist for the experiment {1}")]
+    NoSuchBranch(String, String),
+    #[error("Initialization of the database is not yet complete")]
+    DatabaseNotReady,
+    #[error("Error parsing a string into a version {0}")]
+    VersionParsingError(String),
+    #[cfg(feature = "stateful")]
+    #[error("Behavior error: {0}")]
+    BehaviorError(#[from] BehaviorError),
+    #[error("TryFromIntError: {0}")]
+    TryFromIntError(#[from] TryFromIntError),
+    #[error("ParseIntError: {0}")]
+    ParseIntError(#[from] ParseIntError),
+    #[error("Transform parameter error: {0}")]
+    TransformParameterError(String),
+    #[cfg(feature = "stateful")]
+    #[error("Error with Remote Settings client: {0}")]
+    ClientError(#[from] remote_settings::RemoteSettingsError),
+    #[cfg(not(feature = "stateful"))]
+    #[error("Error in Cirrus: {0}")]
+    CirrusError(#[from] CirrusClientError),
+    #[error("UniFFI callback error: {0}")]
+    UniFFICallbackError(#[from] uniffi::UnexpectedUniFFICallbackError),
+}
+
+#[cfg(feature = "stateful")]
+#[derive(Debug, thiserror::Error)]
+pub enum BehaviorError {
+    #[error("Invalid state: {0}")]
+    InvalidState(String),
+    #[error("Invalid duration: {0}")]
+    InvalidDuration(String),
+    #[error("IntervalParseError: {0} is not a valid Interval")]
+    IntervalParseError(String),
+    #[error("The event store is not available on the targeting attributes")]
+    MissingEventStore,
+}
+
+#[cfg(not(feature = "stateful"))]
+#[derive(Debug, thiserror::Error)]
+pub enum CirrusClientError {
+    #[error("Request missing parameter: {0}")]
+    RequestMissingParameter(String),
+}
+
+impl<'a> From<jexl_eval::error::EvaluationError<'a>> for NimbusError {
+    fn from(eval_error: jexl_eval::error::EvaluationError<'a>) -> Self {
+        NimbusError::EvaluationError(eval_error.to_string())
+    }
+}
+
+pub type Result<T, E = NimbusError> = std::result::Result<T, E>;
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus/evaluator.rs.html b/book/rust-docs/src/nimbus/evaluator.rs.html new file mode 100644 index 0000000000..ef805de3e6 --- /dev/null +++ b/book/rust-docs/src/nimbus/evaluator.rs.html @@ -0,0 +1,539 @@ +evaluator.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+use crate::{
+    enrollment::{EnrolledReason, EnrollmentStatus, ExperimentEnrollment, NotEnrolledReason},
+    error::{NimbusError, Result},
+    sampling, AvailableRandomizationUnits, Branch, Experiment, NimbusTargetingHelper,
+};
+use serde_derive::*;
+use serde_json::Value;
+
+cfg_if::cfg_if! {
+    if #[cfg(feature = "stateful")] {
+        pub use crate::stateful::evaluator::*;
+    } else {
+        pub use crate::stateless::evaluator::*;
+    }
+}
+
+#[derive(Serialize, Deserialize, Debug, Clone, Default)]
+pub struct Bucket {}
+
+impl Bucket {
+    #[allow(unused)]
+    pub fn new() -> Self {
+        unimplemented!();
+    }
+}
+
+fn prefer_none_to_empty(s: Option<&str>) -> Option<String> {
+    let s = s?;
+    if s.is_empty() {
+        None
+    } else {
+        Some(s.to_string())
+    }
+}
+
+pub fn split_locale(locale: String) -> (Option<String>, Option<String>) {
+    if locale.contains('-') {
+        let mut parts = locale.split('-');
+        (
+            prefer_none_to_empty(parts.next()),
+            prefer_none_to_empty(parts.next()),
+        )
+    } else {
+        (Some(locale), None)
+    }
+}
+
+/// Determine the enrolment status for an experiment.
+///
+/// # Arguments:
+/// - `available_randomization_units` The app provded available randomization units
+/// - `targeting_attributes` The attributes to use when evaluating targeting
+/// - `exp` The `Experiment` to evaluate.
+///
+/// # Returns:
+/// An `ExperimentEnrollment` -  you need to inspect the EnrollmentStatus to
+/// determine if the user is actually enrolled.
+///
+/// # Errors:
+///
+/// The function can return errors in one of the following cases (but not limited to):
+///
+/// - If the bucket sampling failed (i.e we could not find if the user should or should not be enrolled in the experiment based on the bucketing)
+/// - If an error occurs while determining the branch the user should be enrolled in any of the experiments
+pub fn evaluate_enrollment(
+    available_randomization_units: &AvailableRandomizationUnits,
+    exp: &Experiment,
+    th: &NimbusTargetingHelper,
+) -> Result<ExperimentEnrollment> {
+    if !is_experiment_available(th, exp, true) {
+        return Ok(ExperimentEnrollment {
+            slug: exp.slug.clone(),
+            status: EnrollmentStatus::NotEnrolled {
+                reason: NotEnrolledReason::NotTargeted,
+            },
+        });
+    }
+
+    // Get targeting out of the way - "if let chains" are experimental,
+    // otherwise we could improve this.
+    if let Some(expr) = &exp.targeting {
+        if let Some(status) = targeting(expr, th) {
+            return Ok(ExperimentEnrollment {
+                slug: exp.slug.clone(),
+                status,
+            });
+        }
+    }
+    Ok(ExperimentEnrollment {
+        slug: exp.slug.clone(),
+        status: {
+            let bucket_config = exp.bucket_config.clone();
+            match available_randomization_units.get_value(&bucket_config.randomization_unit) {
+                Some(id) => {
+                    if sampling::bucket_sample(
+                        vec![id.to_owned(), bucket_config.namespace],
+                        bucket_config.start,
+                        bucket_config.count,
+                        bucket_config.total,
+                    )? {
+                        EnrollmentStatus::new_enrolled(
+                            EnrolledReason::Qualified,
+                            &choose_branch(&exp.slug, &exp.branches, id)?.clone().slug,
+                        )
+                    } else {
+                        EnrollmentStatus::NotEnrolled {
+                            reason: NotEnrolledReason::NotSelected,
+                        }
+                    }
+                }
+                None => {
+                    // XXX: When we link in glean, it would be nice if we could emit
+                    // a failure telemetry event here.
+                    log::info!(
+                        "Could not find a suitable randomization unit for {}. Skipping experiment.",
+                        &exp.slug
+                    );
+                    EnrollmentStatus::Error {
+                        reason: "No randomization unit".into(),
+                    }
+                }
+            }
+        },
+    })
+}
+
+/// Check if an experiment is available for this app defined by this `AppContext`.
+///
+/// # Arguments:
+/// - `app_context` The application parameters to use for targeting purposes
+/// - `exp` The `Experiment` to evaluate
+/// - `is_release` Supports two modes:
+///     if `true`, available means available for enrollment: i.e. does the `app_name` and `channel` match.
+///     if `false`, available means available for testing: i.e. does only the `app_name` match.
+///
+/// # Returns:
+/// Returns `true` if the experiment matches the targeting
+pub fn is_experiment_available(
+    th: &NimbusTargetingHelper,
+    exp: &Experiment,
+    is_release: bool,
+) -> bool {
+    // Verify the app_name matches the application being targeted
+    // by the experiment.
+    match (&exp.app_name, th.context.get("app_name".to_string())) {
+        (Some(exp), Some(Value::String(mine))) => {
+            if !exp.eq(mine) {
+                return false;
+            }
+        }
+        (_, _) => log::debug!("Experiment missing app_name, skipping it as a targeting parameter"),
+    }
+
+    if !is_release {
+        return true;
+    }
+
+    // Verify the channel matches the application being targeted
+    // by the experiment.  Note, we are intentionally comparing in a case-insensitive way.
+    // See https://jira.mozilla.com/browse/SDK-246 for more info.
+    match (&exp.channel, th.context.get("channel".to_string())) {
+        (Some(exp), Some(Value::String(mine))) => {
+            if !exp.to_lowercase().eq(&mine.to_lowercase()) {
+                return false;
+            }
+        }
+        (_, _) => log::debug!("Experiment missing channel, skipping it as a targeting parameter"),
+    }
+    true
+}
+
+/// Chooses a branch randomly from a set of branches
+/// based on the ratios set in the branches
+///
+/// It is important that the input to the sampling algorithm be:
+/// - Unique per-user (no one is bucketed alike)
+/// - Unique per-experiment (bucketing differs across multiple experiments)
+/// - Differs from the input used for sampling the recipe (otherwise only
+///   branches that contain the same buckets as the recipe sampling will
+///   receive users)
+///
+/// # Arguments:
+/// - `slug` the slug associated with the experiment
+/// - `branches` the branches to pick from
+/// - `id` the user id used to pick a branch
+///
+/// # Returns:
+/// Returns the slug for the selected branch
+///
+/// # Errors:
+///
+/// An error could occur if something goes wrong while sampling the ratios
+pub(crate) fn choose_branch<'a>(
+    slug: &str,
+    branches: &'a [Branch],
+    id: &str,
+) -> Result<&'a Branch> {
+    // convert from i32 to u32 to work around SDK-175.
+    let ratios = branches.iter().map(|b| b.ratio as u32).collect::<Vec<_>>();
+    // Note: The "experiment-manager" here comes from
+    // https://searchfox.org/mozilla-central/rev/1843375acbbca68127713e402be222350ac99301/toolkit/components/messaging-system/experiments/ExperimentManager.jsm#469
+    // TODO: Change it to be something more related to the SDK if it is needed
+    let input = format!("{:}-{:}-{:}-branch", "experimentmanager", id, slug);
+    let index = sampling::ratio_sample(input, &ratios)?;
+    branches.get(index).ok_or(NimbusError::OutOfBoundsError)
+}
+
+/// Checks if the client is targeted by an experiment
+/// This api evaluates the JEXL statement retrieved from the server
+/// against the application context provided by the client
+///
+/// # Arguments
+/// - `expression_statement`: The JEXL statement provided by the server
+/// - `targeting_attributes`: The client attributes to target against
+///
+/// If this app can not be targeted, returns an EnrollmentStatus to indicate
+/// why. Returns None if we should continue to evaluate the enrollment status.
+///
+/// In practice, if this returns an EnrollmentStatus, it will be either
+/// EnrollmentStatus::NotEnrolled, or EnrollmentStatus::Error in the following
+/// cases (But not limited to):
+/// - The `expression_statement` is not a valid JEXL statement
+/// - The `expression_statement` expects fields that do not exist in the AppContext definition
+/// - The result of evaluating the statement against the context is not a boolean
+/// - jexl-rs returned an error
+pub(crate) fn targeting(
+    expression_statement: &str,
+    targeting_helper: &NimbusTargetingHelper,
+) -> Option<EnrollmentStatus> {
+    match targeting_helper.eval_jexl(expression_statement.to_string()) {
+        Ok(res) => match res {
+            true => None,
+            false => Some(EnrollmentStatus::NotEnrolled {
+                reason: NotEnrolledReason::NotTargeted,
+            }),
+        },
+        Err(e) => Some(EnrollmentStatus::Error {
+            reason: e.to_string(),
+        }),
+    }
+}
+
+#[cfg(test)]
+mod unit_tests {
+    use super::*;
+
+    #[test]
+    fn test_splitting_locale() -> Result<()> {
+        assert_eq!(
+            split_locale("en-US".to_string()),
+            (Some("en".to_string()), Some("US".to_string()))
+        );
+        assert_eq!(
+            split_locale("es".to_string()),
+            (Some("es".to_string()), None)
+        );
+
+        assert_eq!(
+            split_locale("-unknown".to_string()),
+            (None, Some("unknown".to_string()))
+        );
+        Ok(())
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus/json.rs.html b/book/rust-docs/src/nimbus/json.rs.html new file mode 100644 index 0000000000..59ed10cde3 --- /dev/null +++ b/book/rust-docs/src/nimbus/json.rs.html @@ -0,0 +1,363 @@ +json.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+
// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+use serde_json::{Map, Value};
+use std::collections::HashMap;
+
+/// Replace any instance of [from] with [to] in any string within the [serde_json::Value].
+///
+/// This recursively descends into the object, looking at string values and keys.
+#[allow(dead_code)]
+pub(crate) fn replace_str(value: &mut Value, from: &str, to: &str) {
+    let replacer = create_str_replacer(from, to);
+    replace_str_with(value, &replacer);
+}
+
+/// Replace any instance of [from] with [to] in any string within the [serde_json::Value::Map].
+///
+/// This recursively descends into the object, looking at string values and keys.
+pub(crate) fn replace_str_in_map(map: &mut Map<String, Value>, from: &str, to: &str) {
+    let replacer = create_str_replacer(from, to);
+    replace_str_in_map_with(map, &replacer);
+}
+
+fn replace_str_with<F>(value: &mut Value, replacer: &F)
+where
+    F: Fn(&str) -> Option<String> + ?Sized,
+{
+    match value {
+        Value::String(s) => {
+            if let Some(r) = replacer(s) {
+                *s = r;
+            }
+        }
+
+        Value::Array(list) => {
+            for item in list.iter_mut() {
+                replace_str_with(item, replacer);
+            }
+        }
+
+        Value::Object(map) => {
+            replace_str_in_map_with(map, replacer);
+        }
+
+        _ => (),
+    };
+}
+
+pub(crate) fn replace_str_in_map_with<F>(map: &mut Map<String, Value>, replacer: &F)
+where
+    F: Fn(&str) -> Option<String> + ?Sized,
+{
+    // Replace values in place.
+    for v in map.values_mut() {
+        replace_str_with(v, replacer);
+    }
+
+    // Replacing keys in place is a little trickier.
+    let mut changes = HashMap::new();
+    for k in map.keys() {
+        if let Some(new) = replacer(k) {
+            changes.insert(k.to_owned(), new);
+        }
+    }
+
+    for (k, new) in changes {
+        let v = map.remove(&k).unwrap();
+        _ = map.insert(new, v);
+    }
+}
+
+fn create_str_replacer<'a>(from: &'a str, to: &'a str) -> impl Fn(&str) -> Option<String> + 'a {
+    move |s: &str| -> Option<String> {
+        if s.contains(from) {
+            Some(s.replace(from, to))
+        } else {
+            None
+        }
+    }
+}
+
+#[cfg(test)]
+mod unit_tests {
+    use super::*;
+    use serde_json::json;
+
+    #[test]
+    fn test_replace_str() {
+        let mut value = json!("{test}");
+        replace_str(&mut value, "{test}", "success");
+        assert_eq!(value, json!("success"));
+
+        let mut value = json!("{test}-postfix");
+        replace_str(&mut value, "{test}", "success");
+        assert_eq!(value, json!("success-postfix"));
+
+        let mut value = json!("prefix-{test}");
+        replace_str(&mut value, "{test}", "success");
+        assert_eq!(value, json!("prefix-success"));
+
+        let mut value = json!("prefix-{test}-postfix");
+        replace_str(&mut value, "{test}", "success");
+        assert_eq!(value, json!("prefix-success-postfix"));
+
+        let mut value = json!("prefix-{test}-multi-{test}-postfix");
+        replace_str(&mut value, "{test}", "success");
+        assert_eq!(value, json!("prefix-success-multi-success-postfix"));
+    }
+
+    #[test]
+    fn test_replace_str_in_array() {
+        let mut value = json!(["alice", "bob", "{placeholder}", "daphne"]);
+        replace_str(&mut value, "{placeholder}", "charlie");
+        assert_eq!(value, json!(["alice", "bob", "charlie", "daphne"]));
+    }
+
+    #[test]
+    fn test_replace_str_in_map() {
+        let mut value = json!({
+            "key": "{test}",
+            "not": true,
+            "or": 2,
+        });
+        replace_str(&mut value, "{test}", "success");
+        assert_eq!(
+            value,
+            json!({
+                "key": "success",
+                "not": true,
+                "or": 2,
+            })
+        );
+    }
+
+    #[test]
+    fn test_replace_str_in_map_keys() {
+        let mut value = json!({
+            "{test}-en-US": "{test}",
+            "not": true,
+            "or": 2,
+        });
+        replace_str(&mut value, "{test}", "success");
+        assert_eq!(
+            value,
+            json!({
+                "success-en-US": "success",
+                "not": true,
+                "or": 2,
+            })
+        );
+    }
+
+    #[test]
+    fn test_replace_str_mixed() {
+        let mut value = json!({
+            "messages": {
+                "{test}-en-US": {
+                    "test": "{test}"
+                },
+                "{test}{test}": {
+                    "test": "{test}{test}"
+                }
+            }
+        });
+        replace_str(&mut value, "{test}", "success");
+        assert_eq!(
+            value,
+            json!({
+                "messages": {
+                    "success-en-US": {
+                        "test": "success"
+                    },
+                    "successsuccess": {
+                        "test": "successsuccess"
+                    }
+                }
+            })
+        );
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus/lib.rs.html b/book/rust-docs/src/nimbus/lib.rs.html new file mode 100644 index 0000000000..529d80a5e8 --- /dev/null +++ b/book/rust-docs/src/nimbus/lib.rs.html @@ -0,0 +1,99 @@ +lib.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+
// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+extern crate core;
+
+mod defaults;
+mod enrollment;
+mod evaluator;
+mod json;
+mod sampling;
+mod strings;
+mod targeting;
+
+pub mod error;
+pub mod metrics;
+pub mod schema;
+pub mod versioning;
+
+pub use enrollment::{EnrolledFeature, EnrollmentStatus};
+pub use error::{NimbusError, Result};
+#[cfg(debug_assertions)]
+pub use evaluator::evaluate_enrollment;
+pub use schema::*;
+pub use targeting::NimbusTargetingHelper;
+
+cfg_if::cfg_if! {
+    if #[cfg(feature = "stateful")] {
+
+        pub mod stateful;
+
+        pub use stateful::nimbus_client::*;
+        pub use stateful::matcher::AppContext;
+        pub use remote_settings::RemoteSettingsConfig;
+    } else {
+        pub mod stateless;
+
+        pub use stateless::cirrus_client::*;
+        pub use stateless::matcher::AppContext;
+    }
+}
+
+// Exposed for Example only
+pub use evaluator::TargetingAttributes;
+
+pub(crate) const SLUG_REPLACEMENT_PATTERN: &str = "{experiment}";
+
+#[cfg(test)]
+mod tests;
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus/metrics.rs.html b/book/rust-docs/src/nimbus/metrics.rs.html new file mode 100644 index 0000000000..c3b24e7c9e --- /dev/null +++ b/book/rust-docs/src/nimbus/metrics.rs.html @@ -0,0 +1,177 @@ +metrics.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+
use crate::{enrollment::ExperimentEnrollment, EnrollmentStatus};
+use serde_derive::{Deserialize, Serialize};
+
+pub trait MetricsHandler: Send + Sync {
+    fn record_enrollment_statuses(&self, enrollment_status_extras: Vec<EnrollmentStatusExtraDef>);
+
+    // #[cfg(feature = "stateful")]
+    // fn record_exposure_event(&self);
+}
+
+#[derive(Serialize, Deserialize, Clone)]
+pub struct EnrollmentStatusExtraDef {
+    pub branch: Option<String>,
+    pub conflict_slug: Option<String>,
+    pub error_string: Option<String>,
+    pub reason: Option<String>,
+    pub slug: Option<String>,
+    pub status: Option<String>,
+    #[cfg(not(feature = "stateful"))]
+    pub user_id: Option<String>,
+}
+
+#[cfg(test)]
+impl EnrollmentStatusExtraDef {
+    pub fn branch(&self) -> &str {
+        self.branch.as_ref().unwrap()
+    }
+
+    pub fn conflict_slug(&self) -> &str {
+        self.conflict_slug.as_ref().unwrap()
+    }
+
+    pub fn error_string(&self) -> &str {
+        self.error_string.as_ref().unwrap()
+    }
+
+    pub fn reason(&self) -> &str {
+        self.reason.as_ref().unwrap()
+    }
+
+    pub fn slug(&self) -> &str {
+        self.slug.as_ref().unwrap()
+    }
+
+    pub fn status(&self) -> &str {
+        self.status.as_ref().unwrap()
+    }
+
+    #[cfg(not(feature = "stateful"))]
+    pub fn user_id(&self) -> &str {
+        self.user_id.as_ref().unwrap()
+    }
+}
+
+impl From<ExperimentEnrollment> for EnrollmentStatusExtraDef {
+    fn from(enrollment: ExperimentEnrollment) -> Self {
+        let mut branch_value: Option<String> = None;
+        let mut reason_value: Option<String> = None;
+        let mut error_value: Option<String> = None;
+        match &enrollment.status {
+            EnrollmentStatus::Enrolled { reason, branch, .. } => {
+                branch_value = Some(branch.to_owned());
+                reason_value = Some(reason.to_string());
+            }
+            EnrollmentStatus::Disqualified { reason, branch, .. } => {
+                branch_value = Some(branch.to_owned());
+                reason_value = Some(reason.to_string());
+            }
+            EnrollmentStatus::NotEnrolled { reason } => {
+                reason_value = Some(reason.to_string());
+            }
+            EnrollmentStatus::WasEnrolled { branch, .. } => branch_value = Some(branch.to_owned()),
+            EnrollmentStatus::Error { reason } => {
+                error_value = Some(reason.to_owned());
+            }
+        }
+        EnrollmentStatusExtraDef {
+            branch: branch_value,
+            conflict_slug: None,
+            error_string: error_value,
+            reason: reason_value,
+            slug: Some(enrollment.slug),
+            status: Some(enrollment.status.name()),
+            #[cfg(not(feature = "stateful"))]
+            user_id: None,
+        }
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus/sampling.rs.html b/book/rust-docs/src/nimbus/sampling.rs.html new file mode 100644 index 0000000000..2889d130c2 --- /dev/null +++ b/book/rust-docs/src/nimbus/sampling.rs.html @@ -0,0 +1,313 @@ +sampling.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+//! This module implements the sampling logic required to hash,
+//! randomize and pick branches using pre-set ratios.
+
+use crate::error::{NimbusError, Result};
+use sha2::{Digest, Sha256};
+
+const HASH_BITS: u32 = 48;
+const HASH_LENGTH: u32 = HASH_BITS / 4;
+
+/// Sample by splitting the input space into a series of buckets, checking
+/// if the given input is in a range of buckets
+///
+/// The range to check is defined by a start point and length, and can wrap around
+/// the input space. For example, if there are 100 buckets, and we ask to check 50 buckets
+/// starting from bucket 70, then buckets 70-99 and 0-19 will be checked
+///
+/// # Arguments:
+///
+/// - `input` What will be hashed and matched against the range of the buckets
+/// - `start` the index of the bucket to start checking
+/// - `count` then number of buckets to check
+/// - `total` The total number of buckets to group inputs into
+///
+/// # Returns:
+///
+/// Returns true if the hash generated from the input belongs within the range
+/// otherwise false
+///
+/// # Errors:
+///
+/// Could error in the following cases (but not limited to)
+/// - An error occured in the hashing process
+/// - an error occured while checking if the hash belongs in the bucket
+pub(crate) fn bucket_sample<T: serde::Serialize>(
+    input: T,
+    start: u32,
+    count: u32,
+    total: u32,
+) -> Result<bool> {
+    let input_hash = hex::encode(truncated_hash(input)?);
+    let wrapped_start = start % total;
+    let end = wrapped_start + count;
+
+    Ok(if end > total {
+        is_hash_in_bucket(&input_hash, 0, end % total, total)?
+            || is_hash_in_bucket(&input_hash, wrapped_start, total, total)?
+    } else {
+        is_hash_in_bucket(&input_hash, wrapped_start, end, total)?
+    })
+}
+
+/// Sample over a list of ratios such that, over the input space, each
+/// ratio has a number of matches in correct proportion to the other ratios
+///
+/// # Arguments:
+/// - `input`: the input used in the sampling process
+/// - `ratios`: The list of ratios associated with each option
+///
+/// # Example:
+///
+/// Assuming the ratios: `[1, 2, 3, 4]`
+/// 10% of all inputs will return 0, 20% will return 1 and so on
+///
+/// # Returns
+/// Returns an index of the ratio that matched the input
+///
+/// # Errors
+/// Could return an error if the input couldn't be hashed
+pub(crate) fn ratio_sample<T: serde::Serialize>(input: T, ratios: &[u32]) -> Result<usize> {
+    if ratios.is_empty() {
+        return Err(NimbusError::EmptyRatiosError);
+    }
+    let input_hash = hex::encode(truncated_hash(input)?);
+    let ratio_total: u32 = ratios.iter().sum();
+    let mut sample_point = 0;
+    for (i, ratio) in ratios.iter().enumerate() {
+        sample_point += ratio;
+        if input_hash <= fraction_to_key(sample_point as f64 / ratio_total as f64)? {
+            return Ok(i);
+        }
+    }
+    Ok(ratios.len() - 1)
+}
+
+/// Provides a hash of `data`, truncated to the 6 most significant bytes
+/// For consistency with: https://searchfox.org/mozilla-central/source/toolkit/components/utils/Sampling.jsm#79
+/// # Arguments:
+/// - `data`: The data to be hashed
+///
+/// # Returns:
+/// Returns the 6 bytes associted with the SHA-256 of the data
+///
+/// # Errors:
+/// Would return an error if the hashing function fails to generate a hash
+/// that is larger than 6 bytes (Should never occur)
+pub(crate) fn truncated_hash<T: serde::Serialize>(data: T) -> Result<[u8; 6]> {
+    let mut hasher = Sha256::new();
+    let data_str = serde_json::to_string(&data)?;
+    hasher.update(data_str.as_bytes());
+    Ok(hasher.finalize()[0..6].try_into()?)
+}
+
+/// Checks if a given hash (represented as a 6 byte hex string) fits within a bucket range
+///
+/// # Arguments:
+/// - `input_hash_num`: The hash as a 6 byte hex string (12 hex digits)
+/// - `min_bucket`: The minimum bucket number
+/// - `max_bucket`: The maximum bucket number
+/// - `bucket_count`: The number of buckets
+///
+/// # Returns
+/// Returns true if the has fits in the bucket range,
+/// otherwise false
+///
+/// # Errors:
+///
+/// Could return an error if bucket numbers are higher than the bucket count
+fn is_hash_in_bucket(
+    input_hash_num: &str,
+    min_bucket: u32,
+    max_bucket: u32,
+    bucket_count: u32,
+) -> Result<bool> {
+    let min_hash = fraction_to_key(min_bucket as f64 / bucket_count as f64)?;
+    let max_hash = fraction_to_key(max_bucket as f64 / bucket_count as f64)?;
+    Ok(min_hash.as_str() <= input_hash_num && input_hash_num < max_hash.as_str())
+}
+
+/// Maps from the range [0, 1] to [0, 2^48]
+///
+/// # Argument:
+/// - `fraction`: float in the range 0-1
+///
+/// # Returns
+/// returns a hex string representing the fraction multiplied to be within the
+/// [0, 2^48] range
+///
+/// # Errors
+/// returns an error if the fraction not within the 0-1 range
+fn fraction_to_key(fraction: f64) -> Result<String> {
+    if !(0.0..=1.0).contains(&fraction) {
+        return Err(NimbusError::InvalidFraction);
+    }
+    let multiplied = (fraction * (2u64.pow(HASH_BITS) - 1) as f64).floor();
+    let multiplied = format!("{:x}", multiplied as u64);
+    let padding = vec!['0'; HASH_LENGTH as usize - multiplied.len()];
+    let res = padding
+        .into_iter()
+        .chain(multiplied.chars())
+        .collect::<String>();
+    Ok(res)
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus/schema.rs.html b/book/rust-docs/src/nimbus/schema.rs.html new file mode 100644 index 0000000000..81a9a5c4af --- /dev/null +++ b/book/rust-docs/src/nimbus/schema.rs.html @@ -0,0 +1,631 @@ +schema.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+
// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+use crate::{defaults::Defaults, enrollment::ExperimentMetadata, NimbusError, Result};
+use serde_derive::*;
+use serde_json::{Map, Value};
+use std::collections::HashSet;
+use uuid::Uuid;
+
+const DEFAULT_TOTAL_BUCKETS: u32 = 10000;
+
+#[derive(Debug, Clone)]
+pub struct EnrolledExperiment {
+    pub feature_ids: Vec<String>,
+    pub slug: String,
+    pub user_facing_name: String,
+    pub user_facing_description: String,
+    pub branch_slug: String,
+}
+
+// ⚠️ Attention : Changes to this type should be accompanied by a new test  ⚠️
+// ⚠️ in `test_lib_bw_compat.rs`, and may require a DB migration. ⚠️
+#[derive(Deserialize, Serialize, Debug, Default, Clone, PartialEq, Eq)]
+#[serde(rename_all = "camelCase")]
+pub struct Experiment {
+    pub schema_version: String,
+    pub slug: String,
+    pub app_name: Option<String>,
+    pub app_id: Option<String>,
+    pub channel: Option<String>,
+    pub user_facing_name: String,
+    pub user_facing_description: String,
+    pub is_enrollment_paused: bool,
+    pub bucket_config: BucketConfig,
+    pub branches: Vec<Branch>,
+    // The `feature_ids` field was added later. For compatibility with exising experiments
+    // and to avoid a db migration, we default it to an empty list when it is missing.
+    #[serde(default)]
+    pub feature_ids: Vec<String>,
+    pub targeting: Option<String>,
+    pub start_date: Option<String>, // TODO: Use a date format here
+    pub end_date: Option<String>,   // TODO: Use a date format here
+    pub proposed_duration: Option<u32>,
+    pub proposed_enrollment: u32,
+    pub reference_branch: Option<String>,
+    #[serde(default)]
+    pub is_rollout: bool,
+    // N.B. records in RemoteSettings will have `id` and `filter_expression` fields,
+    // but we ignore them because they're for internal use by RemoteSettings.
+}
+
+#[cfg_attr(not(feature = "stateful"), allow(unused))]
+impl Experiment {
+    pub(crate) fn has_branch(&self, branch_slug: &str) -> bool {
+        self.branches
+            .iter()
+            .any(|branch| branch.slug == branch_slug)
+    }
+
+    pub(crate) fn get_branch(&self, branch_slug: &str) -> Option<&Branch> {
+        self.branches.iter().find(|b| b.slug == branch_slug)
+    }
+
+    pub(crate) fn get_feature_ids(&self) -> Vec<String> {
+        let branches = &self.branches;
+        let feature_ids = branches
+            .iter()
+            .flat_map(|b| {
+                b.get_feature_configs()
+                    .iter()
+                    .map(|f| f.to_owned().feature_id)
+                    .collect::<Vec<_>>()
+            })
+            .collect::<HashSet<_>>();
+
+        feature_ids.into_iter().collect()
+    }
+}
+
+impl ExperimentMetadata for Experiment {
+    fn get_slug(&self) -> String {
+        self.slug.clone()
+    }
+
+    fn is_rollout(&self) -> bool {
+        self.is_rollout
+    }
+}
+
+pub fn parse_experiments(payload: &str) -> Result<Vec<Experiment>> {
+    // We first encode the response into a `serde_json::Value`
+    // to allow us to deserialize each experiment individually,
+    // omitting any malformed experiments
+    let value: Value = serde_json::from_str(payload)?;
+    let data = value
+        .get("data")
+        .ok_or(NimbusError::InvalidExperimentFormat)?;
+    let mut res = Vec::new();
+    for exp in data
+        .as_array()
+        .ok_or(NimbusError::InvalidExperimentFormat)?
+    {
+        // XXX: In the future it would be nice if this lived in its own versioned crate so that
+        // the schema could be decoupled from the sdk so that it can be iterated on while the
+        // sdk depends on a particular version of the schema through the Cargo.toml.
+        match serde_json::from_value::<Experiment>(exp.clone()) {
+            Ok(exp) => res.push(exp),
+            Err(e) => {
+                log::trace!("Malformed experiment data: {:#?}", exp);
+                log::warn!(
+                    "Malformed experiment found! Experiment {},  Error: {}",
+                    exp.get("id").unwrap_or(&serde_json::json!("ID_NOT_FOUND")),
+                    e
+                );
+            }
+        }
+    }
+    Ok(res)
+}
+
+#[derive(Deserialize, Serialize, Debug, Default, Clone, PartialEq, Eq)]
+#[serde(rename_all = "camelCase")]
+pub struct FeatureConfig {
+    pub feature_id: String,
+    // There is a nullable `value` field that can contain key-value config options
+    // that modify the behaviour of an application feature. Uniffi doesn't quite support
+    // serde_json yet.
+    #[serde(default)]
+    pub value: Map<String, Value>,
+}
+
+impl Defaults for FeatureConfig {
+    fn defaults(&self, fallback: &Self) -> Result<Self> {
+        if self.feature_id != fallback.feature_id {
+            // This is unlikely to happen, but if it does it's a bug in Nimbus
+            Err(NimbusError::InternalError(
+                "Cannot merge feature configs from different features",
+            ))
+        } else {
+            Ok(FeatureConfig {
+                feature_id: self.feature_id.clone(),
+                value: self.value.defaults(&fallback.value)?,
+            })
+        }
+    }
+}
+
+// ⚠️ Attention : Changes to this type should be accompanied by a new test  ⚠️
+// ⚠️ in `test_lib_bw_compat.rs`, and may require a DB migration. ⚠️
+#[derive(Deserialize, Serialize, Debug, Default, Clone, PartialEq, Eq)]
+pub struct Branch {
+    pub slug: String,
+    pub ratio: i32,
+    // we skip serializing the `feature` and `features`
+    // fields if they are `None`, to stay aligned
+    // with the schema, where only one of them
+    // will exist
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub feature: Option<FeatureConfig>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub features: Option<Vec<FeatureConfig>>,
+}
+
+impl Branch {
+    pub(crate) fn get_feature_configs(&self) -> Vec<FeatureConfig> {
+        // Some versions of desktop need both, but features should be prioritized
+        // (https://mozilla-hub.atlassian.net/browse/SDK-440).
+        match (&self.features, &self.feature) {
+            (Some(features), _) => features.clone(),
+            (None, Some(feature)) => vec![feature.clone()],
+            _ => Default::default(),
+        }
+    }
+}
+
+fn default_buckets() -> u32 {
+    DEFAULT_TOTAL_BUCKETS
+}
+
+// ⚠️ Attention : Changes to this type should be accompanied by a new test  ⚠️
+// ⚠️ in `test_lib_bw_compat.rs`, and may require a DB migration. ⚠️
+#[derive(Deserialize, Serialize, Debug, Default, Clone, PartialEq, Eq)]
+#[serde(rename_all = "camelCase")]
+pub struct BucketConfig {
+    pub randomization_unit: RandomizationUnit,
+    pub namespace: String,
+    pub start: u32,
+    pub count: u32,
+    #[serde(default = "default_buckets")]
+    pub total: u32,
+}
+
+#[allow(unused)]
+#[cfg(test)]
+impl BucketConfig {
+    pub(crate) fn always() -> Self {
+        Self {
+            start: 0,
+            count: default_buckets(),
+            total: default_buckets(),
+            ..Default::default()
+        }
+    }
+}
+
+// This type is passed across the FFI to client consumers, e.g. UI for testing tooling.
+pub struct AvailableExperiment {
+    pub slug: String,
+    pub user_facing_name: String,
+    pub user_facing_description: String,
+    pub branches: Vec<ExperimentBranch>,
+    pub reference_branch: Option<String>,
+}
+
+pub struct ExperimentBranch {
+    pub slug: String,
+    pub ratio: i32,
+}
+
+impl From<Experiment> for AvailableExperiment {
+    fn from(exp: Experiment) -> Self {
+        Self {
+            slug: exp.slug,
+            user_facing_name: exp.user_facing_name,
+            user_facing_description: exp.user_facing_description,
+            branches: exp.branches.into_iter().map(|b| b.into()).collect(),
+            reference_branch: exp.reference_branch,
+        }
+    }
+}
+
+impl From<Branch> for ExperimentBranch {
+    fn from(branch: Branch) -> Self {
+        Self {
+            slug: branch.slug,
+            ratio: branch.ratio,
+        }
+    }
+}
+
+// ⚠️ Attention : Changes to this type should be accompanied by a new test  ⚠️
+// ⚠️ in `test_lib_bw_compat`, and may require a DB migration. ⚠️
+#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
+#[serde(rename_all = "snake_case")]
+pub enum RandomizationUnit {
+    NimbusId,
+    ClientId,
+    UserId,
+}
+
+impl Default for RandomizationUnit {
+    fn default() -> Self {
+        Self::NimbusId
+    }
+}
+
+#[derive(Default)]
+pub struct AvailableRandomizationUnits {
+    pub client_id: Option<String>,
+    pub user_id: Option<String>,
+    pub nimbus_id: Option<String>,
+    #[allow(dead_code)]
+    pub(crate) dummy: i8, // See comments in nimbus.udl for why this hacky item exists.
+}
+
+impl AvailableRandomizationUnits {
+    // Use ::with_client_id when you want to specify one, or use
+    // Default::default if you don't!
+    pub fn with_client_id(client_id: &str) -> Self {
+        Self {
+            client_id: Some(client_id.to_string()),
+            user_id: None,
+            nimbus_id: None,
+            dummy: 0,
+        }
+    }
+
+    // Use ::with_user_id when you want to specify one, or use
+    // Default::default if you don't!
+    pub fn with_user_id(user_id: &str) -> Self {
+        Self {
+            client_id: None,
+            user_id: Some(user_id.to_string()),
+            nimbus_id: None,
+            dummy: 0,
+        }
+    }
+
+    pub fn with_nimbus_id(nimbus_id: &Uuid) -> Self {
+        Self {
+            client_id: None,
+            user_id: None,
+            nimbus_id: Some(nimbus_id.to_string()),
+            dummy: 0,
+        }
+    }
+
+    pub fn apply_nimbus_id(&self, nimbus_id: &Uuid) -> Self {
+        Self {
+            client_id: self.client_id.clone(),
+            user_id: self.user_id.clone(),
+            nimbus_id: Some(nimbus_id.to_string()),
+            dummy: 0,
+        }
+    }
+
+    pub fn get_value<'a>(&'a self, wanted: &'a RandomizationUnit) -> Option<&'a str> {
+        match wanted {
+            RandomizationUnit::NimbusId => self.nimbus_id.as_deref(),
+            RandomizationUnit::ClientId => self.client_id.as_deref(),
+            RandomizationUnit::UserId => self.user_id.as_deref(),
+        }
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus/stateful/behavior.rs.html b/book/rust-docs/src/nimbus/stateful/behavior.rs.html new file mode 100644 index 0000000000..9cc3670602 --- /dev/null +++ b/book/rust-docs/src/nimbus/stateful/behavior.rs.html @@ -0,0 +1,1181 @@ +behavior.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
+540
+541
+542
+543
+544
+545
+546
+547
+548
+549
+550
+551
+552
+553
+554
+555
+556
+557
+558
+559
+560
+561
+562
+563
+564
+565
+566
+567
+568
+569
+570
+571
+572
+573
+574
+575
+576
+577
+578
+579
+580
+581
+582
+583
+584
+585
+586
+587
+588
+589
+590
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+use crate::{
+    error::{BehaviorError, NimbusError, Result},
+    stateful::persistence::{Database, StoreId},
+};
+use chrono::{DateTime, Datelike, Duration, TimeZone, Utc};
+use serde::{Deserialize, Serialize};
+use serde_json::{json, Value};
+use std::collections::vec_deque::Iter;
+use std::collections::{HashMap, VecDeque};
+use std::fmt;
+use std::hash::{Hash, Hasher};
+use std::str::FromStr;
+use std::sync::{Arc, Mutex};
+
+#[derive(Clone, Debug, Serialize, Deserialize)]
+pub enum Interval {
+    Minutes,
+    Hours,
+    Days,
+    Weeks,
+    Months,
+    Years,
+}
+
+impl Interval {
+    pub fn num_rotations(&self, then: DateTime<Utc>, now: DateTime<Utc>) -> Result<i32> {
+        let date_diff = now - then;
+        Ok(i32::try_from(match self {
+            Interval::Minutes => date_diff.num_minutes(),
+            Interval::Hours => date_diff.num_hours(),
+            Interval::Days => date_diff.num_days(),
+            Interval::Weeks => date_diff.num_weeks(),
+            Interval::Months => date_diff.num_days() / 28,
+            Interval::Years => date_diff.num_days() / 365,
+        })?)
+    }
+
+    pub fn to_duration(&self, count: i64) -> Duration {
+        match self {
+            Interval::Minutes => Duration::minutes(count),
+            Interval::Hours => Duration::hours(count),
+            Interval::Days => Duration::days(count),
+            Interval::Weeks => Duration::weeks(count),
+            Interval::Months => Duration::days(28 * count),
+            Interval::Years => Duration::days(365 * count),
+        }
+    }
+}
+
+impl fmt::Display for Interval {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        fmt::Debug::fmt(self, f)
+    }
+}
+
+impl PartialEq for Interval {
+    fn eq(&self, other: &Self) -> bool {
+        self.to_string() == other.to_string()
+    }
+}
+impl Eq for Interval {}
+
+impl Hash for Interval {
+    fn hash<H: Hasher>(&self, state: &mut H) {
+        self.to_string().as_bytes().hash(state);
+    }
+}
+
+impl FromStr for Interval {
+    type Err = NimbusError;
+
+    fn from_str(input: &str) -> Result<Self> {
+        Ok(match input {
+            "Minutes" => Self::Minutes,
+            "Hours" => Self::Hours,
+            "Days" => Self::Days,
+            "Weeks" => Self::Weeks,
+            "Months" => Self::Months,
+            "Years" => Self::Years,
+            _ => {
+                return Err(NimbusError::BehaviorError(
+                    BehaviorError::IntervalParseError(input.to_string()),
+                ))
+            }
+        })
+    }
+}
+
+#[derive(Clone, Serialize, Deserialize, Debug)]
+pub struct IntervalConfig {
+    bucket_count: usize,
+    interval: Interval,
+}
+
+impl Default for IntervalConfig {
+    fn default() -> Self {
+        Self::new(7, Interval::Days)
+    }
+}
+
+impl IntervalConfig {
+    pub fn new(bucket_count: usize, interval: Interval) -> Self {
+        Self {
+            bucket_count,
+            interval,
+        }
+    }
+}
+
+#[derive(Clone, Serialize, Deserialize, Debug)]
+pub struct IntervalData {
+    pub(crate) buckets: VecDeque<u64>,
+    pub(crate) bucket_count: usize,
+    pub(crate) starting_instant: DateTime<Utc>,
+}
+
+impl Default for IntervalData {
+    fn default() -> Self {
+        Self::new(1)
+    }
+}
+
+impl IntervalData {
+    pub fn new(bucket_count: usize) -> Self {
+        let mut buckets = VecDeque::with_capacity(bucket_count);
+        buckets.push_front(0);
+        // Set the starting instant to Jan 1 00:00:00 in order to sync rotations
+        let starting_instant = Utc.from_utc_datetime(
+            &Utc::now()
+                .with_month(1)
+                .unwrap()
+                .with_day(1)
+                .unwrap()
+                .date_naive()
+                .and_hms_opt(0, 0, 0)
+                .unwrap(),
+        );
+        Self {
+            buckets,
+            bucket_count,
+            starting_instant,
+        }
+    }
+
+    pub fn increment(&mut self, count: u64) -> Result<()> {
+        self.increment_at(0, count)
+    }
+
+    pub fn increment_at(&mut self, index: usize, count: u64) -> Result<()> {
+        if index < self.bucket_count {
+            let buckets = &mut self.buckets;
+            match buckets.get_mut(index) {
+                Some(x) => *x += count,
+                None => {
+                    for _ in buckets.len()..index {
+                        buckets.push_back(0);
+                    }
+                    self.buckets.insert(index, count)
+                }
+            };
+        }
+        Ok(())
+    }
+
+    pub fn rotate(&mut self, num_rotations: i32) -> Result<()> {
+        let num_rotations = usize::min(self.bucket_count, num_rotations as usize);
+        if num_rotations + self.buckets.len() > self.bucket_count {
+            self.buckets.drain((self.bucket_count - num_rotations)..);
+        }
+        for _ in 1..=num_rotations {
+            self.buckets.push_front(0);
+        }
+        Ok(())
+    }
+}
+
+#[derive(Clone, Serialize, Deserialize, Debug)]
+pub struct SingleIntervalCounter {
+    pub data: IntervalData,
+    pub config: IntervalConfig,
+}
+
+impl SingleIntervalCounter {
+    pub fn new(config: IntervalConfig) -> Self {
+        Self {
+            data: IntervalData::new(config.bucket_count),
+            config,
+        }
+    }
+
+    pub fn from_config(bucket_count: usize, interval: Interval) -> Self {
+        let config = IntervalConfig {
+            bucket_count,
+            interval,
+        };
+        Self::new(config)
+    }
+
+    pub fn increment_then(&mut self, then: DateTime<Utc>, count: u64) -> Result<()> {
+        use std::cmp::Ordering;
+        let now = self.data.starting_instant;
+        let rotations = self.config.interval.num_rotations(then, now)?;
+        match rotations.cmp(&0) {
+            Ordering::Less => {
+                /* We can't increment in the future */
+                return Err(NimbusError::BehaviorError(BehaviorError::InvalidState(
+                    "Cannot increment events far into the future".to_string(),
+                )));
+            }
+            Ordering::Equal => {
+                if now < then {
+                    self.data.increment_at(0, count)?;
+                } else {
+                    self.data.increment_at(1, count)?;
+                }
+            }
+            Ordering::Greater => self.data.increment_at(1 + rotations as usize, count)?,
+        }
+        Ok(())
+    }
+
+    pub fn increment(&mut self, count: u64) -> Result<()> {
+        self.data.increment(count)
+    }
+
+    pub fn maybe_advance(&mut self, now: DateTime<Utc>) -> Result<()> {
+        let rotations = self
+            .config
+            .interval
+            .num_rotations(self.data.starting_instant, now)?;
+        if rotations > 0 {
+            self.data.starting_instant += self.config.interval.to_duration(rotations.into());
+            return self.data.rotate(rotations);
+        }
+        Ok(())
+    }
+}
+
+#[derive(Serialize, Deserialize, Clone, Debug)]
+pub struct MultiIntervalCounter {
+    pub intervals: HashMap<Interval, SingleIntervalCounter>,
+}
+
+impl MultiIntervalCounter {
+    pub fn new(intervals: Vec<SingleIntervalCounter>) -> Self {
+        Self {
+            intervals: intervals
+                .into_iter()
+                .map(|v| (v.config.interval.clone(), v))
+                .collect::<HashMap<Interval, SingleIntervalCounter>>(),
+        }
+    }
+
+    pub fn increment_then(&mut self, then: DateTime<Utc>, count: u64) -> Result<()> {
+        self.intervals
+            .iter_mut()
+            .try_for_each(|(_, v)| v.increment_then(then, count))
+    }
+
+    pub fn increment(&mut self, count: u64) -> Result<()> {
+        self.intervals
+            .iter_mut()
+            .try_for_each(|(_, v)| v.increment(count))
+    }
+
+    pub fn maybe_advance(&mut self, now: DateTime<Utc>) -> Result<()> {
+        self.intervals
+            .iter_mut()
+            .try_for_each(|(_, v)| v.maybe_advance(now))
+    }
+}
+
+impl Default for MultiIntervalCounter {
+    fn default() -> Self {
+        Self::new(vec![
+            SingleIntervalCounter::new(IntervalConfig {
+                bucket_count: 60,
+                interval: Interval::Minutes,
+            }),
+            SingleIntervalCounter::new(IntervalConfig {
+                bucket_count: 72,
+                interval: Interval::Hours,
+            }),
+            SingleIntervalCounter::new(IntervalConfig {
+                bucket_count: 56,
+                interval: Interval::Days,
+            }),
+            SingleIntervalCounter::new(IntervalConfig {
+                bucket_count: 52,
+                interval: Interval::Weeks,
+            }),
+            SingleIntervalCounter::new(IntervalConfig {
+                bucket_count: 12,
+                interval: Interval::Months,
+            }),
+            SingleIntervalCounter::new(IntervalConfig {
+                bucket_count: 4,
+                interval: Interval::Years,
+            }),
+        ])
+    }
+}
+
+#[derive(Debug)]
+pub enum EventQueryType {
+    Sum,
+    CountNonZero,
+    AveragePerInterval,
+    AveragePerNonZeroInterval,
+    LastSeen,
+}
+
+impl fmt::Display for EventQueryType {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        fmt::Debug::fmt(self, f)
+    }
+}
+
+impl EventQueryType {
+    pub fn perform_query(&self, buckets: Iter<u64>, num_buckets: usize) -> Result<f64> {
+        Ok(match self {
+            Self::Sum => buckets.sum::<u64>() as f64,
+            Self::CountNonZero => buckets.filter(|v| v > &&0u64).count() as f64,
+            Self::AveragePerInterval => buckets.sum::<u64>() as f64 / num_buckets as f64,
+            Self::AveragePerNonZeroInterval => {
+                let values = buckets.fold((0, 0), |accum, item| {
+                    (
+                        accum.0 + item,
+                        if item > &0 { accum.1 + 1 } else { accum.1 },
+                    )
+                });
+                if values.1 == 0 {
+                    0.0
+                } else {
+                    values.0 as f64 / values.1 as f64
+                }
+            }
+            Self::LastSeen => match buckets.into_iter().position(|v| v > &0) {
+                Some(v) => v as f64,
+                None => f64::MAX,
+            },
+        })
+    }
+
+    fn validate_counting_arguments(
+        &self,
+        args: &[Value],
+    ) -> Result<(String, Interval, usize, usize)> {
+        if args.len() < 3 || args.len() > 4 {
+            return Err(NimbusError::TransformParameterError(format!(
+                "event transform {} requires 2-3 parameters",
+                self
+            )));
+        }
+        let event = serde_json::from_value::<String>(args.get(0).unwrap().clone())?;
+        let interval = serde_json::from_value::<String>(args.get(1).unwrap().clone())?;
+        let interval = Interval::from_str(&interval)?;
+        let num_buckets = match args.get(2).unwrap().as_f64() {
+            Some(v) => v,
+            None => {
+                return Err(NimbusError::TransformParameterError(format!(
+                    "event transform {} requires a positive number as the second parameter",
+                    self
+                )))
+            }
+        } as usize;
+        let zero = &Value::from(0);
+        let starting_bucket = match args.get(3).unwrap_or(zero).as_f64() {
+            Some(v) => v,
+            None => {
+                return Err(NimbusError::TransformParameterError(format!(
+                    "event transform {} requires a positive number as the third parameter",
+                    self
+                )))
+            }
+        } as usize;
+
+        Ok((event, interval, num_buckets, starting_bucket))
+    }
+
+    fn validate_last_seen_arguments(
+        &self,
+        args: &[Value],
+    ) -> Result<(String, Interval, usize, usize)> {
+        if args.len() < 2 || args.len() > 3 {
+            return Err(NimbusError::TransformParameterError(format!(
+                "event transform {} requires 1-2 parameters",
+                self
+            )));
+        }
+        let event = serde_json::from_value::<String>(args.get(0).unwrap().clone())?;
+        let interval = serde_json::from_value::<String>(args.get(1).unwrap().clone())?;
+        let interval = Interval::from_str(&interval)?;
+        let zero = &Value::from(0);
+        let starting_bucket = match args.get(2).unwrap_or(zero).as_f64() {
+            Some(v) => v,
+            None => {
+                return Err(NimbusError::TransformParameterError(format!(
+                    "event transform {} requires a positive number as the second parameter",
+                    self
+                )))
+            }
+        } as usize;
+
+        Ok((event, interval, usize::MAX, starting_bucket))
+    }
+
+    pub fn validate_arguments(&self, args: &[Value]) -> Result<(String, Interval, usize, usize)> {
+        // `args` is an array of values sent by the evaluator for a JEXL transform.
+        // The first parameter will always be the event_id, and subsequent parameters are up to the developer's discretion.
+        // All parameters should be validated, and a `TransformParameterError` should be sent when there is an error.
+        Ok(match self {
+            Self::Sum
+            | Self::CountNonZero
+            | Self::AveragePerInterval
+            | Self::AveragePerNonZeroInterval => self.validate_counting_arguments(args)?,
+            Self::LastSeen => self.validate_last_seen_arguments(args)?,
+        })
+    }
+
+    fn error_value(&self) -> f64 {
+        match self {
+            Self::LastSeen => f64::MAX,
+            _ => 0.0,
+        }
+    }
+}
+
+#[derive(Default, Serialize, Deserialize, Debug, Clone)]
+pub struct EventStore {
+    pub(crate) events: HashMap<String, MultiIntervalCounter>,
+    datum: Option<DateTime<Utc>>,
+}
+
+impl From<Vec<(String, MultiIntervalCounter)>> for EventStore {
+    fn from(event_store: Vec<(String, MultiIntervalCounter)>) -> Self {
+        Self {
+            events: HashMap::from_iter(event_store),
+            datum: None,
+        }
+    }
+}
+
+impl From<HashMap<String, MultiIntervalCounter>> for EventStore {
+    fn from(event_store: HashMap<String, MultiIntervalCounter>) -> Self {
+        Self {
+            events: event_store,
+            datum: None,
+        }
+    }
+}
+
+impl TryFrom<&Database> for EventStore {
+    type Error = NimbusError;
+
+    fn try_from(db: &Database) -> Result<Self, NimbusError> {
+        let reader = db.read()?;
+        let events = db
+            .get_store(StoreId::EventCounts)
+            .collect_all::<(String, MultiIntervalCounter), _>(&reader)?;
+        Ok(EventStore::from(events))
+    }
+}
+
+impl EventStore {
+    pub fn new() -> Self {
+        Self {
+            events: HashMap::<String, MultiIntervalCounter>::new(),
+            datum: None,
+        }
+    }
+
+    fn now(&self) -> DateTime<Utc> {
+        self.datum.unwrap_or_else(Utc::now)
+    }
+
+    pub fn advance_datum(&mut self, duration: Duration) {
+        self.datum = Some(self.now() + duration);
+    }
+
+    pub fn read_from_db(&mut self, db: &Database) -> Result<()> {
+        let reader = db.read()?;
+
+        self.events =
+            HashMap::from_iter(
+                db.get_store(StoreId::EventCounts)
+                    .collect_all::<(String, MultiIntervalCounter), _>(&reader)?,
+            );
+
+        Ok(())
+    }
+
+    pub fn record_event(
+        &mut self,
+        count: u64,
+        event_id: &str,
+        now: Option<DateTime<Utc>>,
+    ) -> Result<()> {
+        let now = now.unwrap_or_else(|| self.now());
+        let counter = self.get_or_create_counter(event_id);
+        counter.maybe_advance(now)?;
+        counter.increment(count)
+    }
+
+    pub fn record_past_event(
+        &mut self,
+        count: u64,
+        event_id: &str,
+        now: Option<DateTime<Utc>>,
+        duration: Duration,
+    ) -> Result<()> {
+        let now = now.unwrap_or_else(|| self.now());
+        let then = now - duration;
+        let counter = self.get_or_create_counter(event_id);
+        counter.maybe_advance(now)?;
+        counter.increment_then(then, count)
+    }
+
+    fn get_or_create_counter(&mut self, event_id: &str) -> &mut MultiIntervalCounter {
+        if !self.events.contains_key(event_id) {
+            let new_counter = Default::default();
+            self.events.insert(event_id.to_string(), new_counter);
+        }
+        return self.events.get_mut(event_id).unwrap();
+    }
+
+    pub fn persist_data(&self, db: &Database) -> Result<()> {
+        let mut writer = db.write()?;
+        self.events.iter().try_for_each(|(key, value)| {
+            db.get_store(StoreId::EventCounts)
+                .put(&mut writer, key, &(key.clone(), value.clone()))
+        })?;
+        writer.commit()?;
+        Ok(())
+    }
+
+    pub fn clear(&mut self, db: &Database) -> Result<()> {
+        self.events = HashMap::<String, MultiIntervalCounter>::new();
+        self.datum = None;
+        self.persist_data(db)?;
+        Ok(())
+    }
+
+    pub fn query(
+        &mut self,
+        event_id: &str,
+        interval: Interval,
+        num_buckets: usize,
+        starting_bucket: usize,
+        query_type: EventQueryType,
+    ) -> Result<f64> {
+        let now = self.now();
+        if let Some(counter) = self.events.get_mut(event_id) {
+            counter.maybe_advance(now)?;
+            if let Some(single_counter) = counter.intervals.get(&interval) {
+                let safe_range = 0..single_counter.data.buckets.len();
+                if !safe_range.contains(&starting_bucket) {
+                    return Ok(query_type.error_value());
+                }
+                let max = usize::min(
+                    num_buckets + starting_bucket,
+                    single_counter.data.buckets.len(),
+                );
+                let buckets = single_counter.data.buckets.range(starting_bucket..max);
+                return query_type.perform_query(buckets, num_buckets);
+            }
+        }
+        Ok(query_type.error_value())
+    }
+}
+
+pub fn query_event_store(
+    event_store: Arc<Mutex<EventStore>>,
+    query_type: EventQueryType,
+    args: &[Value],
+) -> Result<Value> {
+    let (event, interval, num_buckets, starting_bucket) = query_type.validate_arguments(args)?;
+
+    Ok(json!(event_store.lock().unwrap().query(
+        &event,
+        interval,
+        num_buckets,
+        starting_bucket,
+        query_type,
+    )?))
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus/stateful/client/fs_client.rs.html b/book/rust-docs/src/nimbus/stateful/client/fs_client.rs.html new file mode 100644 index 0000000000..f99d830528 --- /dev/null +++ b/book/rust-docs/src/nimbus/stateful/client/fs_client.rs.html @@ -0,0 +1,123 @@ +fs_client.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+//! A SettingsClient that uses the file-system. Used for developer ergonomics
+//! (eg, for testing against experiments which are not deployed anywhere) and
+//! for tests.
+
+use crate::error::Result;
+use crate::stateful::client::SettingsClient;
+use crate::Experiment;
+use std::ffi::OsStr;
+use std::fs::File;
+use std::io::BufReader;
+use std::path::{Path, PathBuf};
+
+pub struct FileSystemClient {
+    path: PathBuf,
+}
+
+impl FileSystemClient {
+    pub fn new<P: AsRef<Path>>(path: P) -> Result<Self> {
+        Ok(Self {
+            path: path.as_ref().into(),
+        })
+    }
+}
+
+impl SettingsClient for FileSystemClient {
+    fn get_experiments_metadata(&self) -> Result<String> {
+        unimplemented!();
+    }
+
+    fn fetch_experiments(&self) -> Result<Vec<Experiment>> {
+        log::info!("reading experiments in {}", self.path.display());
+        let mut res = Vec::new();
+        // Skip directories and non .json files (eg, READMEs)
+        let json_ext = Some(OsStr::new("json"));
+        let filenames = self
+            .path
+            .read_dir()?
+            .filter_map(Result::ok)
+            .map(|c| c.path())
+            .filter(|f| f.is_file() && f.extension() == json_ext);
+        for child_path in filenames {
+            let file = File::open(child_path.clone())?;
+            let reader = BufReader::new(file);
+            match serde_json::from_reader::<_, Experiment>(reader) {
+                Ok(exp) => res.push(exp),
+                Err(e) => {
+                    log::warn!(
+                        "Malformed experiment found! File {},  Error: {}",
+                        child_path.display(),
+                        e
+                    );
+                }
+            }
+        }
+        Ok(res)
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus/stateful/client/http_client.rs.html b/book/rust-docs/src/nimbus/stateful/client/http_client.rs.html new file mode 100644 index 0000000000..b2d31e2a6e --- /dev/null +++ b/book/rust-docs/src/nimbus/stateful/client/http_client.rs.html @@ -0,0 +1,61 @@ +http_client.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+//! This is a simple HTTP client that uses viaduct to retrieve experiment data from the server.
+//! Currently configured to use Kinto and the old schema, although that would change once we start
+//! working on the real Nimbus schema.
+//!
+//! In the future we might replace this with a more fully-feature Remote Settings client, such as:
+//!
+//!   https://github.com/mozilla-services/remote-settings-client
+//!   Issue: https://github.com/mozilla/application-services/issues/3475
+//!
+//! But the simple subset implemented here meets our needs for now.
+
+use crate::error::Result;
+use crate::schema::parse_experiments;
+use crate::stateful::client::{Experiment, SettingsClient};
+use remote_settings::Client;
+
+impl SettingsClient for Client {
+    fn get_experiments_metadata(&self) -> Result<String> {
+        unimplemented!();
+    }
+
+    fn fetch_experiments(&self) -> Result<Vec<Experiment>> {
+        let resp = self.get_records_raw()?;
+        parse_experiments(&resp.text())
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus/stateful/client/mod.rs.html b/book/rust-docs/src/nimbus/stateful/client/mod.rs.html new file mode 100644 index 0000000000..78503c469e --- /dev/null +++ b/book/rust-docs/src/nimbus/stateful/client/mod.rs.html @@ -0,0 +1,101 @@ +mod.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+mod fs_client;
+pub(crate) mod http_client;
+pub(crate) mod null_client;
+use crate::error::{NimbusError, Result};
+use crate::Experiment;
+use fs_client::FileSystemClient;
+use null_client::NullClient;
+use remote_settings::Client;
+use remote_settings::RemoteSettingsConfig;
+use url::Url;
+
+pub(crate) fn create_client(
+    config: Option<RemoteSettingsConfig>,
+) -> Result<Box<dyn SettingsClient + Send>> {
+    Ok(match config {
+        Some(config) => {
+            // XXX - double-parsing the URL here if it's not a file:// URL - ideally
+            // config would already be holding a Url and we wouldn't parse here at all.
+            let url = match &config.server_url {
+                Some(server_url) => Url::parse(server_url)?,
+                None => return Ok(Box::new(Client::new(config)?)),
+            };
+            if url.scheme() == "file" {
+                // Everything in `config` other than the url/path is ignored for the
+                // file-system - we could insist on a sub-directory, but that doesn't
+                // seem valuable for the use-cases we care about here.
+                let path = match url.to_file_path() {
+                    Ok(path) => path,
+                    _ => return Err(NimbusError::InvalidPath(url.into())),
+                };
+                Box::new(FileSystemClient::new(path)?)
+            } else {
+                Box::new(Client::new(config)?)
+            }
+        }
+        // If no server is provided, then we still want Nimbus to work, but serving
+        // an empty list of experiments.
+        None => Box::new(NullClient::new()),
+    })
+}
+
+// The trait used to fetch experiments.
+pub(crate) trait SettingsClient {
+    fn get_experiments_metadata(&self) -> Result<String>;
+    fn fetch_experiments(&self) -> Result<Vec<Experiment>>;
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus/stateful/client/null_client.rs.html b/book/rust-docs/src/nimbus/stateful/client/null_client.rs.html new file mode 100644 index 0000000000..2f30bc4314 --- /dev/null +++ b/book/rust-docs/src/nimbus/stateful/client/null_client.rs.html @@ -0,0 +1,53 @@ +null_client.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+use crate::error::Result;
+use crate::stateful::client::{Experiment, SettingsClient};
+
+/// This is a client for use when no server is provided.
+/// Its primary use is for non-Mozilla forks of apps that are not using their
+/// own server infrastructure.
+pub struct NullClient;
+
+impl NullClient {
+    pub fn new() -> Self {
+        NullClient
+    }
+}
+
+impl SettingsClient for NullClient {
+    fn get_experiments_metadata(&self) -> Result<String> {
+        unimplemented!();
+    }
+    fn fetch_experiments(&self) -> Result<Vec<Experiment>> {
+        Ok(Default::default())
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus/stateful/dbcache.rs.html b/book/rust-docs/src/nimbus/stateful/dbcache.rs.html new file mode 100644 index 0000000000..65e33ca742 --- /dev/null +++ b/book/rust-docs/src/nimbus/stateful/dbcache.rs.html @@ -0,0 +1,333 @@ +dbcache.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+use crate::{
+    enrollment::{
+        map_features_by_feature_id, EnrolledFeature, EnrolledFeatureConfig, ExperimentEnrollment,
+    },
+    error::{NimbusError, Result},
+    stateful::{
+        enrollment::get_enrollments,
+        persistence::{Database, StoreId, Writer},
+    },
+    EnrolledExperiment, Experiment,
+};
+use std::collections::{HashMap, HashSet};
+use std::sync::RwLock;
+
+// This module manages an in-memory cache of the database, so that some
+// functions exposed by nimbus can return results without blocking on any
+// IO. Consumers are expected to call our public `update()` function whenever
+// the database might have changed.
+
+// This struct is the cached data. This is never mutated, but instead
+// recreated every time the cache is updated.
+struct CachedData {
+    pub experiments: Vec<Experiment>,
+    pub enrollments: Vec<ExperimentEnrollment>,
+    pub experiments_by_slug: HashMap<String, EnrolledExperiment>,
+    pub features_by_feature_id: HashMap<String, EnrolledFeatureConfig>,
+}
+
+// This is the public cache API. Each NimbusClient can create one of these and
+// it lives as long as the client - it encapsulates the synchronization needed
+// to allow the cache to work correctly.
+#[derive(Default)]
+pub struct DatabaseCache {
+    data: RwLock<Option<CachedData>>,
+}
+
+impl DatabaseCache {
+    // Call this function whenever it's possible that anything cached by this
+    // struct (eg, our enrollments) might have changed.
+    //
+    // This function must be passed a `&Database` and a `Writer`, which it
+    // will commit before updating the in-memory cache. This is a slightly weird
+    // API but it helps encorce two important properties:
+    //
+    //  * By requiring a `Writer`, we ensure mutual exclusion of other db writers
+    //    and thus prevent the possibility of caching stale data.
+    //  * By taking ownership of the `Writer`, we ensure that the calling code
+    //    updates the cache after all of its writes have been performed.
+    pub fn commit_and_update(
+        &self,
+        db: &Database,
+        writer: Writer,
+        coenrolling_ids: &HashSet<&str>,
+    ) -> Result<()> {
+        // By passing in the active `writer` we read the state of enrollments
+        // as written by the calling code, before it's committed to the db.
+        let enrollments = get_enrollments(db, &writer)?;
+
+        // Build a lookup table for experiments by experiment slug.
+        // This will be used for get_experiment_branch() and get_active_experiments()
+        let mut experiments_by_slug = HashMap::with_capacity(enrollments.len());
+        for e in enrollments {
+            experiments_by_slug.insert(e.slug.clone(), e);
+        }
+
+        let enrollments: Vec<ExperimentEnrollment> =
+            db.get_store(StoreId::Enrollments).collect_all(&writer)?;
+        let experiments: Vec<Experiment> =
+            db.get_store(StoreId::Experiments).collect_all(&writer)?;
+
+        let features_by_feature_id =
+            map_features_by_feature_id(&enrollments, &experiments, coenrolling_ids);
+
+        // This is where testing tools would override i.e. replace experimental feature configurations.
+        // i.e. testing tools would cause custom feature configs to be stored in a Store.
+        // Here, we get those overrides out of the store, and merge it with this map.
+
+        // This is where rollouts (promoted experiments on a given feature) will be merged in to the feature variables.
+
+        let data = CachedData {
+            experiments,
+            enrollments,
+            experiments_by_slug,
+            features_by_feature_id,
+        };
+
+        // Try to commit the change to disk and update the cache as close
+        // together in time as possible. This leaves a small window where another
+        // thread could read new data from disk but see old data in the cache,
+        // but that seems benign in practice given the way we use the cache.
+        // The alternative would be to lock the cache while we commit to disk,
+        // and we don't want to risk blocking the main thread.
+        writer.commit()?;
+        let mut cached = self.data.write().unwrap();
+        cached.replace(data);
+        Ok(())
+    }
+
+    // Abstracts safely referencing our cached data.
+    //
+    // WARNING: because this manages locking, the callers of this need to be
+    // careful regarding deadlocks - if the callback takes other own locks then
+    // there's a risk of locks being taken in an inconsistent order. However,
+    // there's nothing this code specifically can do about that.
+    fn get_data<T, F>(&self, func: F) -> Result<T>
+    where
+        F: FnOnce(&CachedData) -> T,
+    {
+        match *self.data.read().unwrap() {
+            None => {
+                log::warn!(
+                    "DatabaseCache attempting to read data before initialization is completed"
+                );
+                Err(NimbusError::DatabaseNotReady)
+            }
+            Some(ref data) => Ok(func(data)),
+        }
+    }
+
+    pub fn get_experiment_branch(&self, id: &str) -> Result<Option<String>> {
+        self.get_data(|data| -> Option<String> {
+            data.experiments_by_slug
+                .get(id)
+                .map(|experiment| experiment.branch_slug.clone())
+        })
+    }
+
+    // This gives access to the feature JSON. We pass it as a string because uniffi doesn't
+    // support JSON yet.
+    pub fn get_feature_config_variables(&self, feature_id: &str) -> Result<Option<String>> {
+        self.get_data(|data| {
+            let enrolled_feature = data.features_by_feature_id.get(feature_id)?;
+            let string = serde_json::to_string(&enrolled_feature.feature.value).unwrap();
+            Some(string)
+        })
+    }
+
+    pub fn get_enrollment_by_feature(&self, feature_id: &str) -> Result<Option<EnrolledFeature>> {
+        self.get_data(|data| {
+            data.features_by_feature_id
+                .get(feature_id)
+                .map(|feature| feature.into())
+        })
+    }
+
+    pub fn get_active_experiments(&self) -> Result<Vec<EnrolledExperiment>> {
+        self.get_data(|data| {
+            data.experiments_by_slug
+                .values()
+                .map(|e| e.to_owned())
+                .collect::<Vec<EnrolledExperiment>>()
+        })
+    }
+
+    pub fn get_experiments(&self) -> Result<Vec<Experiment>> {
+        self.get_data(|data| data.experiments.to_vec())
+    }
+
+    pub fn get_enrollments(&self) -> Result<Vec<ExperimentEnrollment>> {
+        self.get_data(|data| data.enrollments.to_owned())
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus/stateful/enrollment.rs.html b/book/rust-docs/src/nimbus/stateful/enrollment.rs.html new file mode 100644 index 0000000000..b1298bea27 --- /dev/null +++ b/book/rust-docs/src/nimbus/stateful/enrollment.rs.html @@ -0,0 +1,369 @@ +enrollment.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+use crate::{
+    enrollment::{
+        map_enrollments, EnrollmentChangeEvent, EnrollmentChangeEventType, EnrollmentsEvolver,
+        ExperimentEnrollment,
+    },
+    error::Result,
+    stateful::persistence::{Database, Readable, StoreId, Writer},
+    EnrolledExperiment, EnrollmentStatus, Experiment,
+};
+
+const DB_KEY_GLOBAL_USER_PARTICIPATION: &str = "user-opt-in";
+const DEFAULT_GLOBAL_USER_PARTICIPATION: bool = true;
+
+impl<'a> EnrollmentsEvolver<'a> {
+    /// Convenient wrapper around `evolve_enrollments` that fetches the current state of experiments,
+    /// enrollments and user participation from the database.
+    pub(crate) fn evolve_enrollments_in_db(
+        &self,
+        db: &Database,
+        writer: &mut Writer,
+        next_experiments: &[Experiment],
+    ) -> Result<Vec<EnrollmentChangeEvent>> {
+        // Get the state from the db.
+        let is_user_participating = get_global_user_participation(db, writer)?;
+        let experiments_store = db.get_store(StoreId::Experiments);
+        let enrollments_store = db.get_store(StoreId::Enrollments);
+        let prev_experiments: Vec<Experiment> = experiments_store.collect_all(writer)?;
+        let prev_enrollments: Vec<ExperimentEnrollment> = enrollments_store.collect_all(writer)?;
+        // Calculate the changes.
+        let (next_enrollments, enrollments_change_events) = self.evolve_enrollments(
+            is_user_participating,
+            &prev_experiments,
+            next_experiments,
+            &prev_enrollments,
+        )?;
+        let next_enrollments = map_enrollments(&next_enrollments);
+        // Write the changes to the Database.
+        enrollments_store.clear(writer)?;
+        for enrollment in next_enrollments.values() {
+            enrollments_store.put(writer, &enrollment.slug, *enrollment)?;
+        }
+        experiments_store.clear(writer)?;
+        for experiment in next_experiments {
+            // Sanity check.
+            if !next_enrollments.contains_key(&experiment.slug) {
+                error_support::report_error!("nimbus-evolve-enrollments", "evolve_enrollments_in_db: experiment '{}' has no enrollment, dropping to keep database consistent", &experiment.slug);
+                continue;
+            }
+            experiments_store.put(writer, &experiment.slug, experiment)?;
+        }
+        Ok(enrollments_change_events)
+    }
+}
+
+/// Return information about all enrolled experiments.
+/// Note this does not include rollouts
+pub fn get_enrollments<'r>(
+    db: &Database,
+    reader: &'r impl Readable<'r>,
+) -> Result<Vec<EnrolledExperiment>> {
+    let enrollments: Vec<ExperimentEnrollment> =
+        db.get_store(StoreId::Enrollments).collect_all(reader)?;
+    let mut result = Vec::with_capacity(enrollments.len());
+    for enrollment in enrollments {
+        log::debug!("Have enrollment: {:?}", enrollment);
+        if let EnrollmentStatus::Enrolled { branch, .. } = &enrollment.status {
+            match db
+                .get_store(StoreId::Experiments)
+                .get::<Experiment, _>(reader, &enrollment.slug)?
+            {
+                Some(experiment) => {
+                    result.push(EnrolledExperiment {
+                        feature_ids: experiment.get_feature_ids(),
+                        slug: experiment.slug,
+                        user_facing_name: experiment.user_facing_name,
+                        user_facing_description: experiment.user_facing_description,
+                        branch_slug: branch.to_string(),
+                    });
+                }
+                _ => {
+                    log::warn!(
+                        "Have enrollment {:?} but no matching experiment!",
+                        enrollment
+                    );
+                }
+            };
+        }
+    }
+    Ok(result)
+}
+
+pub fn opt_in_with_branch(
+    db: &Database,
+    writer: &mut Writer,
+    experiment_slug: &str,
+    branch: &str,
+) -> Result<Vec<EnrollmentChangeEvent>> {
+    let mut events = vec![];
+    if let Ok(Some(exp)) = db
+        .get_store(StoreId::Experiments)
+        .get::<Experiment, Writer>(writer, experiment_slug)
+    {
+        let enrollment = ExperimentEnrollment::from_explicit_opt_in(&exp, branch, &mut events);
+        db.get_store(StoreId::Enrollments)
+            .put(writer, experiment_slug, &enrollment.unwrap())?;
+    } else {
+        events.push(EnrollmentChangeEvent {
+            experiment_slug: experiment_slug.to_string(),
+            branch_slug: branch.to_string(),
+            reason: Some("does-not-exist".to_string()),
+            change: EnrollmentChangeEventType::EnrollFailed,
+        });
+    }
+
+    Ok(events)
+}
+
+pub fn opt_out(
+    db: &Database,
+    writer: &mut Writer,
+    experiment_slug: &str,
+) -> Result<Vec<EnrollmentChangeEvent>> {
+    let mut events = vec![];
+    let enr_store = db.get_store(StoreId::Enrollments);
+    if let Ok(Some(existing_enrollment)) =
+        enr_store.get::<ExperimentEnrollment, Writer>(writer, experiment_slug)
+    {
+        let updated_enrollment = &existing_enrollment.on_explicit_opt_out(&mut events);
+        enr_store.put(writer, experiment_slug, updated_enrollment)?;
+    } else {
+        events.push(EnrollmentChangeEvent {
+            experiment_slug: experiment_slug.to_string(),
+            branch_slug: "N/A".to_string(),
+            reason: Some("does-not-exist".to_string()),
+            change: EnrollmentChangeEventType::UnenrollFailed,
+        });
+    }
+
+    Ok(events)
+}
+
+pub fn get_global_user_participation<'r>(
+    db: &Database,
+    reader: &'r impl Readable<'r>,
+) -> Result<bool> {
+    let store = db.get_store(StoreId::Meta);
+    let opted_in = store.get::<bool, _>(reader, DB_KEY_GLOBAL_USER_PARTICIPATION)?;
+    if let Some(opted_in) = opted_in {
+        Ok(opted_in)
+    } else {
+        Ok(DEFAULT_GLOBAL_USER_PARTICIPATION)
+    }
+}
+
+pub fn set_global_user_participation(
+    db: &Database,
+    writer: &mut Writer,
+    opt_in: bool,
+) -> Result<()> {
+    let store = db.get_store(StoreId::Meta);
+    store.put(writer, DB_KEY_GLOBAL_USER_PARTICIPATION, &opt_in)
+}
+
+/// Reset unique identifiers in response to application-level telemetry reset.
+///
+pub fn reset_telemetry_identifiers(
+    db: &Database,
+    writer: &mut Writer,
+) -> Result<Vec<EnrollmentChangeEvent>> {
+    let mut events = vec![];
+    let store = db.get_store(StoreId::Enrollments);
+    let enrollments: Vec<ExperimentEnrollment> = store.collect_all(writer)?;
+    let updated_enrollments = enrollments
+        .iter()
+        .map(|enrollment| enrollment.reset_telemetry_identifiers(&mut events));
+    store.clear(writer)?;
+    for enrollment in updated_enrollments {
+        store.put(writer, &enrollment.slug, &enrollment)?;
+    }
+    Ok(events)
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus/stateful/evaluator.rs.html b/book/rust-docs/src/nimbus/stateful/evaluator.rs.html new file mode 100644 index 0000000000..7da6c91095 --- /dev/null +++ b/book/rust-docs/src/nimbus/stateful/evaluator.rs.html @@ -0,0 +1,79 @@ +evaluator.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+use crate::{evaluator::split_locale, stateful::matcher::AppContext};
+use serde_derive::*;
+use std::collections::{HashMap, HashSet};
+
+#[derive(Serialize, Deserialize, Debug, Clone, Default)]
+pub struct TargetingAttributes {
+    #[serde(flatten)]
+    pub app_context: AppContext,
+    pub language: Option<String>,
+    pub region: Option<String>,
+    pub is_already_enrolled: bool,
+    pub days_since_install: Option<i32>,
+    pub days_since_update: Option<i32>,
+    pub active_experiments: HashSet<String>,
+    pub enrollments: HashSet<String>,
+    pub enrollments_map: HashMap<String, String>,
+}
+
+#[cfg(feature = "stateful")]
+impl From<AppContext> for TargetingAttributes {
+    fn from(app_context: AppContext) -> Self {
+        let (language, region) = app_context
+            .locale
+            .clone()
+            .map(split_locale)
+            .unwrap_or_else(|| (None, None));
+
+        Self {
+            app_context,
+            language,
+            region,
+            ..Default::default()
+        }
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus/stateful/matcher.rs.html b/book/rust-docs/src/nimbus/stateful/matcher.rs.html new file mode 100644 index 0000000000..a7cb4db44f --- /dev/null +++ b/book/rust-docs/src/nimbus/stateful/matcher.rs.html @@ -0,0 +1,117 @@ +matcher.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+//! This module defines all the information needed to match a user with an experiment.
+//! Soon it will also include a `match` function of some sort that does the matching.
+//!
+//! It contains the `AppContext`
+//! provided by the consuming client.
+//!
+use serde_derive::{Deserialize, Serialize};
+use serde_json::{Map, Value};
+
+/// The `AppContext` object represents the parameters and characteristics of the
+/// consuming application that we are interested in for targeting purposes. The
+/// `app_name` and `channel` fields are not optional as they are expected
+/// to be provided by all consuming applications as they are used in the top-level
+/// targeting that help to ensure that an experiment is only processed
+/// by the correct application.
+///
+/// Definitions of the fields are as follows:
+/// - `app_name`: This is the name of the application (e.g. "Fenix" or "Firefox iOS")
+/// - `app_id`: This is the application identifier, especially for mobile (e.g. "org.mozilla.fenix")
+/// - `channel`: This is the delivery channel of the application (e.g "nightly")
+/// - `app_version`: The user visible version string (e.g. "1.0.3")
+/// - `app_build`: The build identifier generated by the CI system (e.g. "1234/A")
+/// - `architecture`: The architecture of the device, (e.g. "arm", "x86")
+/// - `device_manufacturer`: The manufacturer of the device the application is running on
+/// - `device_model`: The model of the device the application is running on
+/// - `locale`: The locale of the application during initialization (e.g. "es-ES")
+/// - `os`: The name of the operating system (e.g. "Android", "iOS", "Darwin", "Windows")
+/// - `os_version`: The user-visible version of the operating system (e.g. "1.2.3")
+/// - `android_sdk_version`: Android specific for targeting specific sdk versions
+/// - `debug_tag`: Used for debug purposes as a way to match only developer builds, etc.
+/// - `installation_date`: The date the application installed the app
+/// - `home_directory`: The application's home directory
+/// - `custom_targeting_attributes`: Contains attributes specific to the application, derived by the application
+#[cfg(feature = "stateful")]
+#[derive(Deserialize, Serialize, Debug, Clone, Default)]
+pub struct AppContext {
+    pub app_name: String,
+    pub app_id: String,
+    pub channel: String,
+    pub app_version: Option<String>,
+    pub app_build: Option<String>,
+    pub architecture: Option<String>,
+    pub device_manufacturer: Option<String>,
+    pub device_model: Option<String>,
+    pub locale: Option<String>,
+    pub os: Option<String>,
+    pub os_version: Option<String>,
+    pub android_sdk_version: Option<String>,
+    pub debug_tag: Option<String>,
+    pub installation_date: Option<i64>,
+    pub home_directory: Option<String>,
+    #[serde(flatten)]
+    pub custom_targeting_attributes: Option<Map<String, Value>>,
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus/stateful/mod.rs.html b/book/rust-docs/src/nimbus/stateful/mod.rs.html new file mode 100644 index 0000000000..853b3ef181 --- /dev/null +++ b/book/rust-docs/src/nimbus/stateful/mod.rs.html @@ -0,0 +1,27 @@ +mod.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+pub mod behavior;
+pub mod client;
+pub mod dbcache;
+pub mod enrollment;
+pub mod evaluator;
+pub mod matcher;
+pub mod nimbus_client;
+pub mod persistence;
+pub mod updating;
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus/stateful/nimbus_client.rs.html b/book/rust-docs/src/nimbus/stateful/nimbus_client.rs.html new file mode 100644 index 0000000000..6aa99cce4e --- /dev/null +++ b/book/rust-docs/src/nimbus/stateful/nimbus_client.rs.html @@ -0,0 +1,1547 @@ +nimbus_client.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
+540
+541
+542
+543
+544
+545
+546
+547
+548
+549
+550
+551
+552
+553
+554
+555
+556
+557
+558
+559
+560
+561
+562
+563
+564
+565
+566
+567
+568
+569
+570
+571
+572
+573
+574
+575
+576
+577
+578
+579
+580
+581
+582
+583
+584
+585
+586
+587
+588
+589
+590
+591
+592
+593
+594
+595
+596
+597
+598
+599
+600
+601
+602
+603
+604
+605
+606
+607
+608
+609
+610
+611
+612
+613
+614
+615
+616
+617
+618
+619
+620
+621
+622
+623
+624
+625
+626
+627
+628
+629
+630
+631
+632
+633
+634
+635
+636
+637
+638
+639
+640
+641
+642
+643
+644
+645
+646
+647
+648
+649
+650
+651
+652
+653
+654
+655
+656
+657
+658
+659
+660
+661
+662
+663
+664
+665
+666
+667
+668
+669
+670
+671
+672
+673
+674
+675
+676
+677
+678
+679
+680
+681
+682
+683
+684
+685
+686
+687
+688
+689
+690
+691
+692
+693
+694
+695
+696
+697
+698
+699
+700
+701
+702
+703
+704
+705
+706
+707
+708
+709
+710
+711
+712
+713
+714
+715
+716
+717
+718
+719
+720
+721
+722
+723
+724
+725
+726
+727
+728
+729
+730
+731
+732
+733
+734
+735
+736
+737
+738
+739
+740
+741
+742
+743
+744
+745
+746
+747
+748
+749
+750
+751
+752
+753
+754
+755
+756
+757
+758
+759
+760
+761
+762
+763
+764
+765
+766
+767
+768
+769
+770
+771
+772
+773
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+use crate::{
+    defaults::Defaults,
+    enrollment::{
+        EnrolledFeature, EnrollmentChangeEvent, EnrollmentChangeEventType, EnrollmentStatus,
+        EnrollmentsEvolver, ExperimentEnrollment,
+    },
+    error::BehaviorError,
+    evaluator::{is_experiment_available, TargetingAttributes},
+    metrics::{EnrollmentStatusExtraDef, MetricsHandler},
+    schema::parse_experiments,
+    stateful::{
+        behavior::EventStore,
+        client::{create_client, SettingsClient},
+        dbcache::DatabaseCache,
+        enrollment::{
+            get_global_user_participation, opt_in_with_branch, opt_out,
+            reset_telemetry_identifiers, set_global_user_participation,
+        },
+        matcher::AppContext,
+        persistence::{Database, StoreId, Writer},
+        updating::{read_and_remove_pending_experiments, write_pending_experiments},
+    },
+    strings::fmt_with_map,
+    AvailableExperiment, AvailableRandomizationUnits, EnrolledExperiment, Experiment,
+    ExperimentBranch, NimbusError, NimbusTargetingHelper, Result,
+};
+use chrono::{DateTime, NaiveDateTime, Utc};
+use once_cell::sync::OnceCell;
+use remote_settings::RemoteSettingsConfig;
+use serde_json::{Map, Value};
+use std::collections::{HashMap, HashSet};
+use std::fmt::Debug;
+use std::path::{Path, PathBuf};
+use std::sync::{Arc, Mutex, MutexGuard};
+use uuid::Uuid;
+
+const DB_KEY_NIMBUS_ID: &str = "nimbus-id";
+pub const DB_KEY_INSTALLATION_DATE: &str = "installation-date";
+pub const DB_KEY_UPDATE_DATE: &str = "update-date";
+pub const DB_KEY_APP_VERSION: &str = "app-version";
+pub const DB_KEY_FETCH_ENABLED: &str = "fetch-enabled";
+
+// The main `NimbusClient` struct must not expose any methods that make an `&mut self`,
+// in order to be compatible with the uniffi's requirements on objects. This is a helper
+// struct to contain the bits that do actually need to be mutable, so they can be
+// protected by a Mutex.
+#[derive(Default)]
+pub struct InternalMutableState {
+    pub(crate) available_randomization_units: AvailableRandomizationUnits,
+    // Application level targeting attributes
+    targeting_attributes: TargetingAttributes,
+}
+
+/// Nimbus is the main struct representing the experiments state
+/// It should hold all the information needed to communicate a specific user's
+/// experimentation status
+pub struct NimbusClient {
+    settings_client: Mutex<Box<dyn SettingsClient + Send>>,
+    pub(crate) mutable_state: Mutex<InternalMutableState>,
+    app_context: AppContext,
+    pub(crate) db: OnceCell<Database>,
+    // Manages an in-memory cache so that we can answer certain requests
+    // without doing (or waiting for) IO.
+    database_cache: DatabaseCache,
+    db_path: PathBuf,
+    coenrolling_feature_ids: Vec<String>,
+    event_store: Arc<Mutex<EventStore>>,
+    metrics_handler: Arc<Box<dyn MetricsHandler>>,
+}
+
+impl NimbusClient {
+    // This constructor *must* not do any kind of I/O since it might be called on the main
+    // thread in the gecko Javascript stack, hence the use of OnceCell for the db.
+    pub fn new<P: Into<PathBuf>>(
+        app_context: AppContext,
+        coenrolling_feature_ids: Vec<String>,
+        db_path: P,
+        config: Option<RemoteSettingsConfig>,
+        available_randomization_units: AvailableRandomizationUnits,
+        metrics_handler: Box<dyn MetricsHandler>,
+    ) -> Result<Self> {
+        let settings_client = Mutex::new(create_client(config)?);
+
+        let mutable_state = Mutex::new(InternalMutableState {
+            available_randomization_units,
+            targeting_attributes: app_context.clone().into(),
+        });
+
+        Ok(Self {
+            settings_client,
+            mutable_state,
+            app_context,
+            database_cache: Default::default(),
+            db_path: db_path.into(),
+            coenrolling_feature_ids,
+            db: OnceCell::default(),
+            event_store: Arc::default(),
+            metrics_handler: Arc::new(metrics_handler),
+        })
+    }
+
+    pub fn with_targeting_attributes(&mut self, targeting_attributes: TargetingAttributes) {
+        let mut state = self.mutable_state.lock().unwrap();
+        state.targeting_attributes = targeting_attributes;
+    }
+
+    pub fn get_targeting_attributes(&self) -> TargetingAttributes {
+        let state = self.mutable_state.lock().unwrap();
+        state.targeting_attributes.clone()
+    }
+
+    pub fn initialize(&self) -> Result<()> {
+        let db = self.db()?;
+        // We're not actually going to write, we just want to exclude concurrent writers.
+        let mut writer = db.write()?;
+
+        let mut state = self.mutable_state.lock().unwrap();
+        self.begin_initialize(db, &mut writer, &mut state)?;
+        self.end_initialize(db, writer, &mut state)?;
+
+        Ok(())
+    }
+
+    // These are tasks which should be in the initialize and apply_pending_experiments
+    // but should happen before the enrollment calculations are done.
+    fn begin_initialize(
+        &self,
+        db: &Database,
+        writer: &mut Writer,
+        state: &mut MutexGuard<InternalMutableState>,
+    ) -> Result<()> {
+        let id = self.read_or_create_nimbus_id(db, writer)?;
+        state.available_randomization_units.nimbus_id = Some(id.to_string());
+        self.update_ta_install_dates(db, writer, state)?;
+        self.event_store.lock().unwrap().read_from_db(db)?;
+        Ok(())
+    }
+
+    // These are tasks which should be in the initialize and apply_pending_experiments
+    // but should happen after the enrollment calculations are done.
+    fn end_initialize(
+        &self,
+        db: &Database,
+        writer: Writer,
+        state: &mut MutexGuard<InternalMutableState>,
+    ) -> Result<()> {
+        self.update_ta_active_experiments(db, &writer, state)?;
+        let coenrolling_ids = self
+            .coenrolling_feature_ids
+            .iter()
+            .map(|s| s.as_str())
+            .collect();
+        self.database_cache
+            .commit_and_update(db, writer, &coenrolling_ids)?;
+        self.record_enrollment_status_telemetry()?;
+        Ok(())
+    }
+
+    pub fn get_enrollment_by_feature(&self, feature_id: String) -> Result<Option<EnrolledFeature>> {
+        self.database_cache.get_enrollment_by_feature(&feature_id)
+    }
+
+    // Note: the contract for this function is that it never blocks on IO.
+    pub fn get_experiment_branch(&self, slug: String) -> Result<Option<String>> {
+        self.database_cache.get_experiment_branch(&slug)
+    }
+
+    pub fn get_feature_config_variables(&self, feature_id: String) -> Result<Option<String>> {
+        self.database_cache
+            .get_feature_config_variables(&feature_id)
+    }
+
+    pub fn get_experiment_branches(&self, slug: String) -> Result<Vec<ExperimentBranch>> {
+        self.get_all_experiments()?
+            .into_iter()
+            .find(|e| e.slug == slug)
+            .map(|e| e.branches.into_iter().map(|b| b.into()).collect())
+            .ok_or(NimbusError::NoSuchExperiment(slug))
+    }
+
+    pub fn get_global_user_participation(&self) -> Result<bool> {
+        let db = self.db()?;
+        let reader = db.read()?;
+        get_global_user_participation(db, &reader)
+    }
+
+    pub fn set_global_user_participation(
+        &self,
+        user_participating: bool,
+    ) -> Result<Vec<EnrollmentChangeEvent>> {
+        let db = self.db()?;
+        let mut writer = db.write()?;
+        let mut state = self.mutable_state.lock().unwrap();
+        set_global_user_participation(db, &mut writer, user_participating)?;
+
+        let existing_experiments: Vec<Experiment> =
+            db.get_store(StoreId::Experiments).collect_all(&writer)?;
+        // We pass the existing experiments as "updated experiments"
+        // to the evolver.
+        let events = self.evolve_experiments(db, &mut writer, &mut state, &existing_experiments)?;
+        self.end_initialize(db, writer, &mut state)?;
+        Ok(events)
+    }
+
+    pub fn get_active_experiments(&self) -> Result<Vec<EnrolledExperiment>> {
+        self.database_cache.get_active_experiments()
+    }
+
+    pub fn get_all_experiments(&self) -> Result<Vec<Experiment>> {
+        let db = self.db()?;
+        let reader = db.read()?;
+        db.get_store(StoreId::Experiments)
+            .collect_all::<Experiment, _>(&reader)
+    }
+
+    pub fn get_available_experiments(&self) -> Result<Vec<AvailableExperiment>> {
+        let th = self.create_targeting_helper(None)?;
+        Ok(self
+            .get_all_experiments()?
+            .into_iter()
+            .filter(|exp| is_experiment_available(&th, exp, false))
+            .map(|exp| exp.into())
+            .collect())
+    }
+
+    pub fn opt_in_with_branch(
+        &self,
+        experiment_slug: String,
+        branch: String,
+    ) -> Result<Vec<EnrollmentChangeEvent>> {
+        let db = self.db()?;
+        let mut writer = db.write()?;
+        let result = opt_in_with_branch(db, &mut writer, &experiment_slug, &branch)?;
+        let mut state = self.mutable_state.lock().unwrap();
+        self.end_initialize(db, writer, &mut state)?;
+        Ok(result)
+    }
+
+    pub fn opt_out(&self, experiment_slug: String) -> Result<Vec<EnrollmentChangeEvent>> {
+        let db = self.db()?;
+        let mut writer = db.write()?;
+        let result = opt_out(db, &mut writer, &experiment_slug)?;
+        let mut state = self.mutable_state.lock().unwrap();
+        self.end_initialize(db, writer, &mut state)?;
+        Ok(result)
+    }
+
+    pub fn fetch_experiments(&self) -> Result<()> {
+        if !self.is_fetch_enabled()? {
+            return Ok(());
+        }
+        log::info!("fetching experiments");
+        let settings_client = self.settings_client.lock().unwrap();
+        let new_experiments = settings_client.fetch_experiments()?;
+        let db = self.db()?;
+        let mut writer = db.write()?;
+        write_pending_experiments(db, &mut writer, new_experiments)?;
+        writer.commit()?;
+        Ok(())
+    }
+
+    pub fn set_fetch_enabled(&self, allow: bool) -> Result<()> {
+        let db = self.db()?;
+        let mut writer = db.write()?;
+        db.get_store(StoreId::Meta)
+            .put(&mut writer, DB_KEY_FETCH_ENABLED, &allow)?;
+        writer.commit()?;
+        Ok(())
+    }
+
+    pub(crate) fn is_fetch_enabled(&self) -> Result<bool> {
+        let db = self.db()?;
+        let reader = db.read()?;
+        let enabled = db
+            .get_store(StoreId::Meta)
+            .get(&reader, DB_KEY_FETCH_ENABLED)?
+            .unwrap_or(true);
+        Ok(enabled)
+    }
+
+    /**
+     * Calculate the days since install and days since update on the targeting_attributes.
+     */
+    fn update_ta_install_dates(
+        &self,
+        db: &Database,
+        writer: &mut Writer,
+        state: &mut MutexGuard<InternalMutableState>,
+    ) -> Result<()> {
+        let installation_date = self.get_installation_date(db, writer)?;
+        log::info!("[Nimbus] Installation Date: {}", installation_date);
+        let update_date = self.get_update_date(db, writer)?;
+        log::info!("[Nimbus] Update Date: {}", update_date);
+        let now = Utc::now();
+        let duration_since_install = now - installation_date;
+        log::info!(
+            "[Nimbus] Days since install: {}",
+            duration_since_install.num_days()
+        );
+        let duration_since_update = now - update_date;
+        log::info!(
+            "[Nimbus] Days since update: {}",
+            duration_since_update.num_days()
+        );
+        if state.targeting_attributes.days_since_install.is_none() {
+            state.targeting_attributes.days_since_install =
+                Some(duration_since_install.num_days() as i32);
+        }
+        if state.targeting_attributes.days_since_update.is_none() {
+            state.targeting_attributes.days_since_update =
+                Some(duration_since_update.num_days() as i32);
+        }
+
+        Ok(())
+    }
+
+    /**
+     * Calculates the active_experiments based on current enrollments for the targeting attributes.
+     */
+    fn update_ta_active_experiments(
+        &self,
+        db: &Database,
+        writer: &Writer,
+        state: &mut MutexGuard<InternalMutableState>,
+    ) -> Result<()> {
+        let enrollments_store = db.get_store(StoreId::Enrollments);
+        let prev_enrollments: Vec<ExperimentEnrollment> = enrollments_store.collect_all(writer)?;
+
+        let mut is_enrolled_set = HashSet::<String>::new();
+        let mut all_enrolled_set = HashSet::<String>::new();
+        let mut enrollments_map = HashMap::<String, String>::new();
+        for ee in prev_enrollments {
+            match ee.status {
+                EnrollmentStatus::Enrolled { branch, .. } => {
+                    is_enrolled_set.insert(ee.slug.clone());
+                    all_enrolled_set.insert(ee.slug.clone());
+                    enrollments_map.insert(ee.slug.clone(), branch.clone());
+                }
+                EnrollmentStatus::WasEnrolled { branch, .. }
+                | EnrollmentStatus::Disqualified { branch, .. } => {
+                    all_enrolled_set.insert(ee.slug.clone());
+                    enrollments_map.insert(ee.slug.clone(), branch.clone());
+                }
+                _ => {}
+            }
+        }
+
+        state.targeting_attributes.active_experiments = is_enrolled_set;
+        state.targeting_attributes.enrollments = all_enrolled_set;
+        state.targeting_attributes.enrollments_map = enrollments_map;
+
+        Ok(())
+    }
+
+    fn evolve_experiments(
+        &self,
+        db: &Database,
+        writer: &mut Writer,
+        state: &mut InternalMutableState,
+        experiments: &[Experiment],
+    ) -> Result<Vec<EnrollmentChangeEvent>> {
+        let targeting_helper =
+            NimbusTargetingHelper::new(&state.targeting_attributes, self.event_store.clone());
+        let coenrolling_feature_ids = self
+            .coenrolling_feature_ids
+            .iter()
+            .map(|s| s.as_str())
+            .collect();
+        let evolver = EnrollmentsEvolver::new(
+            &state.available_randomization_units,
+            &targeting_helper,
+            &coenrolling_feature_ids,
+        );
+        evolver.evolve_enrollments_in_db(db, writer, experiments)
+    }
+
+    pub fn apply_pending_experiments(&self) -> Result<Vec<EnrollmentChangeEvent>> {
+        log::info!("updating experiment list");
+        let db = self.db()?;
+        let mut writer = db.write()?;
+
+        // We'll get the pending experiments which were stored for us, either by fetch_experiments
+        // or by set_experiments_locally.
+        let pending_updates = read_and_remove_pending_experiments(db, &mut writer)?;
+        let mut state = self.mutable_state.lock().unwrap();
+        self.begin_initialize(db, &mut writer, &mut state)?;
+
+        let res = match pending_updates {
+            Some(new_experiments) => {
+                self.update_ta_active_experiments(db, &writer, &mut state)?;
+                // Perform the enrollment calculations if there are pending experiments.
+                self.evolve_experiments(db, &mut writer, &mut state, &new_experiments)?
+            }
+            None => vec![],
+        };
+
+        // Finish up any cleanup, e.g. copying from database in to memory.
+        self.end_initialize(db, writer, &mut state)?;
+        Ok(res)
+    }
+
+    fn get_installation_date(&self, db: &Database, writer: &mut Writer) -> Result<DateTime<Utc>> {
+        // we first check our context
+        if let Some(context_installation_date) = self.app_context.installation_date {
+            let res = DateTime::<Utc>::from_utc(
+                NaiveDateTime::from_timestamp_opt(context_installation_date / 1_000, 0).unwrap(),
+                Utc,
+            );
+            log::info!("[Nimbus] Retrieved date from Context: {}", res);
+            return Ok(res);
+        }
+        let store = db.get_store(StoreId::Meta);
+        let persisted_installation_date: Option<DateTime<Utc>> =
+            store.get(writer, DB_KEY_INSTALLATION_DATE)?;
+        Ok(
+            if let Some(installation_date) = persisted_installation_date {
+                installation_date
+            } else if let Some(home_directory) = &self.app_context.home_directory {
+                let installation_date = match self.get_creation_date_from_path(home_directory) {
+                    Ok(installation_date) => installation_date,
+                    Err(e) => {
+                        log::warn!("[Nimbus] Unable to get installation date from path, defaulting to today: {:?}", e);
+                        Utc::now()
+                    }
+                };
+                let store = db.get_store(StoreId::Meta);
+                store.put(writer, DB_KEY_INSTALLATION_DATE, &installation_date)?;
+                installation_date
+            } else {
+                Utc::now()
+            },
+        )
+    }
+
+    fn get_update_date(&self, db: &Database, writer: &mut Writer) -> Result<DateTime<Utc>> {
+        let store = db.get_store(StoreId::Meta);
+
+        let persisted_app_version: Option<String> = store.get(writer, DB_KEY_APP_VERSION)?;
+        let update_date: Option<DateTime<Utc>> = store.get(writer, DB_KEY_UPDATE_DATE)?;
+        Ok(
+            match (
+                persisted_app_version,
+                &self.app_context.app_version,
+                update_date,
+            ) {
+                // The app been run before, but has not just been updated.
+                (Some(persisted), Some(current), Some(date)) if persisted == *current => date,
+                // The app has been run before, and just been updated.
+                (Some(persisted), Some(current), _) if persisted != *current => {
+                    let now = Utc::now();
+                    store.put(writer, DB_KEY_APP_VERSION, current)?;
+                    store.put(writer, DB_KEY_UPDATE_DATE, &now)?;
+                    now
+                }
+                // The app has just been installed
+                (None, Some(current), _) => {
+                    let now = Utc::now();
+                    store.put(writer, DB_KEY_APP_VERSION, current)?;
+                    store.put(writer, DB_KEY_UPDATE_DATE, &now)?;
+                    now
+                }
+                // The current version is not available, or the persisted date is not available.
+                (_, _, Some(date)) => date,
+                // Either way, this doesn't appear to be a good production environment.
+                _ => Utc::now(),
+            },
+        )
+    }
+
+    #[cfg(not(test))]
+    fn get_creation_date_from_path<P: AsRef<Path>>(&self, path: P) -> Result<DateTime<Utc>> {
+        log::info!("[Nimbus] Getting creation date from path");
+        let metadata = std::fs::metadata(path)?;
+        let system_time_created = metadata.created()?;
+        let date_time_created = DateTime::<Utc>::from(system_time_created);
+        log::info!(
+            "[Nimbus] Creation date retrieved form path successfully: {}",
+            date_time_created
+        );
+        Ok(date_time_created)
+    }
+
+    #[cfg(test)]
+    fn get_creation_date_from_path<P: AsRef<Path>>(&self, path: P) -> Result<DateTime<Utc>> {
+        use std::io::Read;
+        let test_path = path.as_ref().with_file_name("test.json");
+        let mut file = std::fs::File::open(test_path)?;
+        let mut buf = String::new();
+        file.read_to_string(&mut buf)?;
+
+        let res = serde_json::from_str::<DateTime<Utc>>(&buf)?;
+        Ok(res)
+    }
+
+    pub fn set_experiments_locally(&self, experiments_json: String) -> Result<()> {
+        let new_experiments = parse_experiments(&experiments_json)?;
+        let db = self.db()?;
+        let mut writer = db.write()?;
+        write_pending_experiments(db, &mut writer, new_experiments)?;
+        writer.commit()?;
+        Ok(())
+    }
+
+    /// Reset all enrollments and experiments in the database.
+    ///
+    /// This should only be used in testing.
+    pub fn reset_enrollments(&self) -> Result<()> {
+        let db = self.db()?;
+        let mut writer = db.write()?;
+        let mut state = self.mutable_state.lock().unwrap();
+        db.clear_experiments_and_enrollments(&mut writer)?;
+        self.end_initialize(db, writer, &mut state)?;
+        Ok(())
+    }
+
+    /// Reset internal state in response to application-level telemetry reset.
+    ///
+    /// When the user resets their telemetry state in the consuming application, we need learn
+    /// the new values of any external randomization units, and we need to reset any unique
+    /// identifiers used internally by the SDK. If we don't then we risk accidentally tracking
+    /// across the telemetry reset, since we could use Nimbus metrics to link their pings from
+    /// before and after the reset.
+    ///
+    pub fn reset_telemetry_identifiers(
+        &self,
+        new_randomization_units: AvailableRandomizationUnits,
+    ) -> Result<Vec<EnrollmentChangeEvent>> {
+        let mut events = vec![];
+        let db = self.db()?;
+        let mut writer = db.write()?;
+        let mut state = self.mutable_state.lock().unwrap();
+        // If we have no `nimbus_id` when we can safely assume that there's
+        // no other experiment state that needs to be reset.
+        let store = db.get_store(StoreId::Meta);
+        if store.get::<String, _>(&writer, DB_KEY_NIMBUS_ID)?.is_some() {
+            // Each enrollment state now opts out because we don't want to leak information between resets.
+            events = reset_telemetry_identifiers(db, &mut writer)?;
+
+            // Remove any stored event counts
+            db.clear_event_count_data(&mut writer)?;
+
+            // The `nimbus_id` itself is a unique identifier.
+            // N.B. we do this last, as a signal that all data has been reset.
+            store.delete(&mut writer, DB_KEY_NIMBUS_ID)?;
+            self.end_initialize(db, writer, &mut state)?;
+        }
+
+        // (No need to commit `writer` if the above check was false, since we didn't change anything)
+        state.available_randomization_units = new_randomization_units;
+
+        Ok(events)
+    }
+
+    pub fn nimbus_id(&self) -> Result<Uuid> {
+        let db = self.db()?;
+        let mut writer = db.write()?;
+        let uuid = self.read_or_create_nimbus_id(db, &mut writer)?;
+        // We don't know whether we needed to generate and save the uuid, so
+        // we commit just in case - this is hopefully close to a noop in that
+        // case!
+        writer.commit()?;
+        Ok(uuid)
+    }
+
+    fn read_or_create_nimbus_id(&self, db: &Database, writer: &mut Writer) -> Result<Uuid> {
+        let store = db.get_store(StoreId::Meta);
+        Ok(match store.get(writer, DB_KEY_NIMBUS_ID)? {
+            Some(nimbus_id) => nimbus_id,
+            None => {
+                let nimbus_id = Uuid::new_v4();
+                store.put(writer, DB_KEY_NIMBUS_ID, &nimbus_id)?;
+                nimbus_id
+            }
+        })
+    }
+
+    // Sets the nimbus ID - TEST ONLY - should not be exposed to real clients.
+    // (Useful for testing so you can have some control over what experiments
+    // are enrolled)
+    pub fn set_nimbus_id(&self, uuid: &Uuid) -> Result<()> {
+        let db = self.db()?;
+        let mut writer = db.write()?;
+        db.get_store(StoreId::Meta)
+            .put(&mut writer, DB_KEY_NIMBUS_ID, uuid)?;
+        writer.commit()?;
+        Ok(())
+    }
+
+    pub(crate) fn db(&self) -> Result<&Database> {
+        self.db.get_or_try_init(|| Database::new(&self.db_path))
+    }
+
+    fn merge_additional_context(&self, context: Option<JsonObject>) -> Result<Value> {
+        let context = context.map(Value::Object);
+        let targeting = serde_json::to_value(self.get_targeting_attributes())?;
+        let context = match context {
+            Some(v) => v.defaults(&targeting)?,
+            None => targeting,
+        };
+
+        Ok(context)
+    }
+
+    pub fn create_targeting_helper(
+        &self,
+        additional_context: Option<JsonObject>,
+    ) -> Result<Arc<NimbusTargetingHelper>> {
+        let context = self.merge_additional_context(additional_context)?;
+        let helper = NimbusTargetingHelper::new(context, self.event_store.clone());
+        Ok(Arc::new(helper))
+    }
+
+    pub fn create_string_helper(
+        &self,
+        additional_context: Option<JsonObject>,
+    ) -> Result<Arc<NimbusStringHelper>> {
+        let context = self.merge_additional_context(additional_context)?;
+        let helper = NimbusStringHelper::new(context.as_object().unwrap().to_owned());
+        Ok(Arc::new(helper))
+    }
+
+    /// Records an event for the purposes of behavioral targeting.
+    ///
+    /// This function is used to record and persist data used for the behavioral
+    /// targeting such as "core-active" user targeting.
+    pub fn record_event(&self, event_id: String, count: i64) -> Result<()> {
+        let mut event_store = self.event_store.lock().unwrap();
+        event_store.record_event(count as u64, &event_id, None)?;
+        event_store.persist_data(self.db()?)?;
+        Ok(())
+    }
+
+    /// Records an event for the purposes of behavioral targeting.
+    ///
+    /// This differs from the `record_event` method in that the event is recorded as if it were
+    /// recorded `seconds_ago` in the past. This makes it very useful for testing.
+    pub fn record_past_event(&self, event_id: String, seconds_ago: i64, count: i64) -> Result<()> {
+        if seconds_ago < 0 {
+            return Err(NimbusError::BehaviorError(BehaviorError::InvalidDuration(
+                "Time duration in the past must be positive".to_string(),
+            )));
+        }
+        let mut event_store = self.event_store.lock().unwrap();
+        event_store.record_past_event(
+            count as u64,
+            &event_id,
+            None,
+            chrono::Duration::seconds(seconds_ago),
+        )?;
+        event_store.persist_data(self.db()?)?;
+        Ok(())
+    }
+
+    /// Advances the event store's concept of `now` artificially.
+    ///
+    /// This works alongside `record_event` and `record_past_event` for testing purposes.
+    pub fn advance_event_time(&self, by_seconds: i64) -> Result<()> {
+        if by_seconds < 0 {
+            return Err(NimbusError::BehaviorError(BehaviorError::InvalidDuration(
+                "Time duration in the future must be positive".to_string(),
+            )));
+        }
+        let mut event_store = self.event_store.lock().unwrap();
+        event_store.advance_datum(chrono::Duration::seconds(by_seconds));
+        Ok(())
+    }
+
+    /// Clear all events in the Nimbus event store.
+    ///
+    /// This should only be used in testing or cases where the previous event store is no longer viable.
+    pub fn clear_events(&self) -> Result<()> {
+        let mut event_store = self.event_store.lock().unwrap();
+        event_store.clear(self.db()?)?;
+        Ok(())
+    }
+
+    pub fn event_store(&self) -> Arc<Mutex<EventStore>> {
+        self.event_store.clone()
+    }
+
+    pub fn dump_state_to_log(&self) -> Result<()> {
+        let experiments = self.get_active_experiments()?;
+        log::info!("{0: <65}| {1: <30}| {2}", "Slug", "Features", "Branch");
+        for exp in &experiments {
+            log::info!(
+                "{0: <65}| {1: <30}| {2}",
+                &exp.slug,
+                &exp.feature_ids.join(", "),
+                &exp.branch_slug
+            );
+        }
+        Ok(())
+    }
+
+    fn record_enrollment_status_telemetry(&self) -> Result<()> {
+        let experiments = self
+            .database_cache
+            .get_experiments()?
+            .iter()
+            .map(|exp| exp.slug.clone())
+            .collect::<HashSet<String>>();
+        self.metrics_handler.record_enrollment_statuses(
+            self.database_cache
+                .get_enrollments()?
+                .into_iter()
+                .filter_map(|e| match experiments.contains(&e.slug) {
+                    true => Some(e.into()),
+                    false => None,
+                })
+                .collect(),
+        );
+        Ok(())
+    }
+}
+
+pub struct NimbusStringHelper {
+    context: JsonObject,
+}
+
+impl NimbusStringHelper {
+    fn new(context: JsonObject) -> Self {
+        Self { context }
+    }
+
+    pub fn get_uuid(&self, template: String) -> Option<String> {
+        if template.contains("{uuid}") {
+            let uuid = Uuid::new_v4();
+            Some(uuid.to_string())
+        } else {
+            None
+        }
+    }
+
+    pub fn string_format(&self, template: String, uuid: Option<String>) -> String {
+        match uuid {
+            Some(uuid) => {
+                let mut map = self.context.clone();
+                map.insert("uuid".to_string(), Value::String(uuid));
+                fmt_with_map(&template, &map)
+            }
+            _ => fmt_with_map(&template, &self.context),
+        }
+    }
+}
+
+type JsonObject = Map<String, Value>;
+
+#[cfg(feature = "stateful-uniffi-bindings")]
+impl UniffiCustomTypeConverter for JsonObject {
+    type Builtin = String;
+
+    fn into_custom(val: Self::Builtin) -> uniffi::Result<Self> {
+        let json: Value = serde_json::from_str(&val)?;
+
+        match json.as_object() {
+            Some(obj) => Ok(obj.clone()),
+            _ => Err(uniffi::deps::anyhow::anyhow!(
+                "Unexpected JSON-non-object in the bagging area"
+            )),
+        }
+    }
+
+    fn from_custom(obj: Self) -> Self::Builtin {
+        serde_json::Value::Object(obj).to_string()
+    }
+}
+
+#[cfg(feature = "stateful-uniffi-bindings")]
+uniffi::include_scaffolding!("nimbus");
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus/stateful/persistence.rs.html b/book/rust-docs/src/nimbus/stateful/persistence.rs.html new file mode 100644 index 0000000000..94ec4da1b8 --- /dev/null +++ b/book/rust-docs/src/nimbus/stateful/persistence.rs.html @@ -0,0 +1,1083 @@ +persistence.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
+540
+541
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+//! Our storage abstraction, currently backed by Rkv.
+
+use crate::error::{NimbusError, Result};
+// This uses the lmdb backend for rkv, which is unstable.
+// We use it for now since glean didn't seem to have trouble with it (although
+// it must be noted that the rkv documentation explicitly says "To use rkv in
+// production/release environments at Mozilla, you may do so with the "SafeMode"
+// backend", so we really should get more guidance here.)
+use crate::enrollment::ExperimentEnrollment;
+use crate::Experiment;
+use core::iter::Iterator;
+use rkv::{StoreError, StoreOptions};
+use std::collections::HashSet;
+use std::fs;
+use std::path::Path;
+
+// We use an incrementing integer to manage database migrations.
+// If you need to make a backwards-incompatible change to the data schema,
+// increment `DB_VERSION` and implement some migration logic in `maybe_upgrade`.
+//
+// ⚠️ Warning : Altering the type of `DB_VERSION` would itself require a DB migration. ⚠️
+pub(crate) const DB_KEY_DB_VERSION: &str = "db_version";
+pub(crate) const DB_VERSION: u16 = 2;
+const RKV_MAX_DBS: u32 = 6;
+
+// Inspired by Glean - use a feature to choose between the backends.
+// Select the LMDB-powered storage backend when the feature is not activated.
+#[cfg(not(feature = "rkv-safe-mode"))]
+mod backend {
+    use rkv::backend::{
+        Lmdb, LmdbDatabase, LmdbEnvironment, LmdbRoCursor, LmdbRoTransaction, LmdbRwTransaction,
+    };
+    use std::path::Path;
+
+    use super::RKV_MAX_DBS;
+
+    pub type Rkv = rkv::Rkv<LmdbEnvironment>;
+    pub type RkvSingleStore = rkv::SingleStore<LmdbDatabase>;
+    pub type Reader<'t> = rkv::Reader<LmdbRoTransaction<'t>>;
+    pub type Writer<'t> = rkv::Writer<LmdbRwTransaction<'t>>;
+    pub trait Readable<'r>:
+        rkv::Readable<'r, Database = LmdbDatabase, RoCursor = LmdbRoCursor<'r>>
+    {
+    }
+    impl<'r, T: rkv::Readable<'r, Database = LmdbDatabase, RoCursor = LmdbRoCursor<'r>>>
+        Readable<'r> for T
+    {
+    }
+
+    pub fn rkv_new(path: &Path) -> Result<Rkv, rkv::StoreError> {
+        Rkv::with_capacity::<Lmdb>(path, RKV_MAX_DBS)
+    }
+}
+
+// Select the "safe mode" storage backend when the feature is activated.
+#[cfg(feature = "rkv-safe-mode")]
+mod backend {
+    use rkv::backend::{
+        SafeMode, SafeModeDatabase, SafeModeEnvironment, SafeModeRoCursor, SafeModeRoTransaction,
+        SafeModeRwTransaction,
+    };
+    use std::path::Path;
+
+    use super::RKV_MAX_DBS;
+
+    pub type Rkv = rkv::Rkv<SafeModeEnvironment>;
+    pub type RkvSingleStore = rkv::SingleStore<SafeModeDatabase>;
+    pub type Reader<'t> = rkv::Reader<SafeModeRoTransaction<'t>>;
+    pub type Writer<'t> = rkv::Writer<SafeModeRwTransaction<'t>>;
+    pub trait Readable<'r>:
+        rkv::Readable<'r, Database = SafeModeDatabase, RoCursor = SafeModeRoCursor<'r>>
+    {
+    }
+    impl<
+            'r,
+            T: rkv::Readable<'r, Database = SafeModeDatabase, RoCursor = SafeModeRoCursor<'r>>,
+        > Readable<'r> for T
+    {
+    }
+
+    pub fn rkv_new(path: &Path) -> Result<Rkv, rkv::StoreError> {
+        Rkv::with_capacity::<SafeMode>(path, RKV_MAX_DBS)
+    }
+}
+
+use backend::*;
+pub use backend::{Readable, Writer};
+
+/// Enumeration of the different stores within our database.
+///
+/// Our rkv database contains a number of different "stores", and the items
+/// in each store correspond to a particular type of object at the Rust level.
+pub enum StoreId {
+    /// Store containing the set of known experiments, as read from the server.
+    ///
+    /// Keys in the `Experiments` store are experiment identifier slugs, and their
+    /// corresponding values are  serialized instances of the [`Experiment`] struct
+    /// representing the last known state of that experiment.
+    Experiments,
+    /// Store containing the set of known experiment enrollments.
+    ///
+    /// Keys in the `Enrollments` store are experiment identifier slugs, and their
+    /// corresponding values are serialized instances of the [`ExperimentEnrollment`]
+    /// struct representing the current state of this client's enrollment (or not)
+    /// in that experiment.
+    Enrollments,
+    /// Store containing miscellaneous metadata about this client instance.
+    ///
+    /// Keys in the `Meta` store are string constants, and their corresponding values
+    /// are serialized items whose type depends on the constant. Known constaints
+    /// include:
+    ///   * "db_version":   u16, the version number of the most revent migration
+    ///                     applied to this database.
+    ///   * "nimbus-id":    String, the randomly-generated identifier for the
+    ///                     current client instance.
+    ///   * "user-opt-in":  bool, whether the user has explicitly opted in or out
+    ///                     of participating in experiments.
+    ///   * "installation-date": a UTC DateTime string, defining the date the consuming app was
+    ///                     installed
+    ///   * "update-date": a UTC DateTime string, defining the date the consuming app was
+    ///                     last updated
+    ///   * "app-version": String, the version of the app last persisted
+    Meta,
+    /// Store containing pending updates to experiment data.
+    ///
+    /// The `Updates` store contains a single key "pending-experiment-updates", whose
+    /// corresponding value is a serialized `Vec<Experiment>` of new experiment data
+    /// that has been received from the server but not yet processed by the application.
+    Updates,
+    /// Store containing collected counts of behavior events for targeting purposes.
+    ///
+    /// Keys in the `EventCounts` store are strings representing the identifier for
+    /// the event and their corresponding values represent a serialized instance of a
+    /// [`MultiIntervalCounter`] struct that contains a set of configurations and data
+    /// for the different time periods that the data will be aggregated on.
+    EventCounts,
+}
+
+/// A wrapper for an Rkv store. Implemented to allow any value which supports
+/// serde to be used.
+pub struct SingleStore {
+    store: RkvSingleStore,
+}
+
+impl SingleStore {
+    pub fn new(store: RkvSingleStore) -> Self {
+        SingleStore { store }
+    }
+
+    pub fn put<T: serde::Serialize + for<'de> serde::Deserialize<'de>>(
+        &self,
+        writer: &mut Writer,
+        key: &str,
+        persisted_data: &T,
+    ) -> Result<()> {
+        let persisted_json = serde_json::to_string(persisted_data)?;
+        self.store
+            .put(writer, key, &rkv::Value::Json(&persisted_json))?;
+        Ok(())
+    }
+
+    #[allow(dead_code)]
+    pub fn delete(&self, writer: &mut Writer, key: &str) -> Result<()> {
+        self.store.delete(writer, key)?;
+        Ok(())
+    }
+
+    pub fn clear(&self, writer: &mut Writer) -> Result<()> {
+        self.store.clear(writer)?;
+        Ok(())
+    }
+
+    // Some "get" functions that cooperate with transactions (ie, so we can
+    // get what we've written to the transaction before it's committed).
+    // It's unfortunate that these are duplicated with the DB itself, but the
+    // traits used by rkv make this tricky.
+    pub fn get<'r, T, R>(&self, reader: &'r R, key: &str) -> Result<Option<T>>
+    where
+        R: Readable<'r>,
+        T: serde::Serialize + for<'de> serde::Deserialize<'de>,
+    {
+        let persisted_data = self.store.get(reader, key)?;
+        match persisted_data {
+            Some(data) => {
+                if let rkv::Value::Json(data) = data {
+                    Ok(Some(serde_json::from_str::<T>(data)?))
+                } else {
+                    Err(NimbusError::InvalidPersistedData)
+                }
+            }
+            None => Ok(None),
+        }
+    }
+
+    /// Fork of collect_all that simply drops records that fail to read
+    /// rather than simply returning an error up the stack.  This likely
+    /// wants to be just a parameter to collect_all, but for now....
+    pub fn try_collect_all<'r, T, R>(&self, reader: &'r R) -> Result<Vec<T>>
+    where
+        R: Readable<'r>,
+        T: serde::Serialize + for<'de> serde::Deserialize<'de>,
+    {
+        let mut result = Vec::new();
+        let mut iter = self.store.iter_start(reader)?;
+        while let Some(Ok((_, data))) = iter.next() {
+            if let rkv::Value::Json(data) = data {
+                let unserialized = serde_json::from_str::<T>(data);
+                match unserialized {
+                    Ok(value) => result.push(value),
+                    Err(e) => {
+                        // If there is an error, we won't push this onto the
+                        // result Vec, but we won't blow up the entire
+                        // deserialization either.
+                        log::warn!(
+                            "try_collect_all: discarded a record while deserializing with: {:?}",
+                            e
+                        );
+                        log::warn!(
+                            "try_collect_all:   data that failed to deserialize: {:?}",
+                            data
+                        );
+                    }
+                };
+            }
+        }
+        Ok(result)
+    }
+
+    pub fn collect_all<'r, T, R>(&self, reader: &'r R) -> Result<Vec<T>>
+    where
+        R: Readable<'r>,
+        T: serde::Serialize + for<'de> serde::Deserialize<'de>,
+    {
+        let mut result = Vec::new();
+        let mut iter = self.store.iter_start(reader)?;
+        while let Some(Ok((_, data))) = iter.next() {
+            if let rkv::Value::Json(data) = data {
+                result.push(serde_json::from_str::<T>(data)?);
+            }
+        }
+        Ok(result)
+    }
+}
+
+/// Database used to access persisted data
+/// This an abstraction around an Rkv database
+/// An instance on this database is created each time the component is loaded
+/// if there is persisted data, the `get` functions should retrieve it
+pub struct Database {
+    rkv: Rkv,
+    meta_store: SingleStore,
+    experiment_store: SingleStore,
+    enrollment_store: SingleStore,
+    updates_store: SingleStore,
+    event_count_store: SingleStore,
+}
+
+impl Database {
+    /// Main constructor for a database
+    /// Initiates the Rkv database to be used to retreive persisted data
+    /// # Arguments
+    /// - `path`: A path to the persisted data, this is provided by the consuming application
+    pub fn new<P: AsRef<Path>>(path: P) -> Result<Self> {
+        let rkv = Self::open_rkv(path)?;
+        let meta_store = rkv.open_single("meta", StoreOptions::create())?;
+        let experiment_store = rkv.open_single("experiments", StoreOptions::create())?;
+        let enrollment_store = rkv.open_single("enrollments", StoreOptions::create())?;
+        let updates_store = rkv.open_single("updates", StoreOptions::create())?;
+        let event_count_store = rkv.open_single("event_counts", StoreOptions::create())?;
+        let db = Self {
+            rkv,
+            meta_store: SingleStore::new(meta_store),
+            experiment_store: SingleStore::new(experiment_store),
+            enrollment_store: SingleStore::new(enrollment_store),
+            updates_store: SingleStore::new(updates_store),
+            event_count_store: SingleStore::new(event_count_store),
+        };
+        db.maybe_upgrade()?;
+        Ok(db)
+    }
+
+    fn maybe_upgrade(&self) -> Result<()> {
+        log::debug!("entered maybe upgrade");
+        let mut writer = self.rkv.write()?;
+        let db_version = self.meta_store.get::<u16, _>(&writer, DB_KEY_DB_VERSION)?;
+        match db_version {
+            Some(DB_VERSION) => {
+                // Already at the current version, no migration required.
+                log::info!("Already at version {}, no upgrade needed", DB_VERSION);
+                return Ok(());
+            }
+            Some(1) => {
+                log::info!("Migrating database from v1 to v2");
+                match self.migrate_v1_to_v2(&mut writer) {
+                    Ok(_) => (),
+                    Err(e) => {
+                        // The idea here is that it's better to leave an
+                        // individual install with a clean empty database
+                        // than in an unknown inconsistent state, because it
+                        // allows them to start participating in experiments
+                        // again, rather than potentially repeating the upgrade
+                        // over and over at each embedding client restart.
+                        error_support::report_error!(
+                            "nimbus-database-migration",
+                            "Error migrating database v1 to v2: {:?}.  Wiping experiments and enrollments",
+                            e
+                        );
+                        self.clear_experiments_and_enrollments(&mut writer)?;
+                    }
+                };
+            }
+            None => {
+                log::info!("maybe_upgrade: no version number; wiping most stores");
+                // The "first" version of the database (= no version number) had un-migratable data
+                // for experiments and enrollments, start anew.
+                // XXX: We can most likely remove this behaviour once enough time has passed,
+                // since nimbus wasn't really shipped to production at the time anyway.
+                self.clear_experiments_and_enrollments(&mut writer)?;
+            }
+            _ => {
+                error_support::report_error!(
+                    "nimbus-unknown-database-version",
+                    "Unknown database version. Wiping all stores."
+                );
+                self.clear_experiments_and_enrollments(&mut writer)?;
+                self.meta_store.clear(&mut writer)?;
+            }
+        }
+        // It is safe to clear the update store (i.e. the pending experiments) on all schema upgrades
+        // as it will be re-filled from the server on the next `fetch_experiments()`.
+        // The current contents of the update store may cause experiments to not load, or worse,
+        // accidentally unenroll.
+        self.updates_store.clear(&mut writer)?;
+        self.meta_store
+            .put(&mut writer, DB_KEY_DB_VERSION, &DB_VERSION)?;
+        writer.commit()?;
+        log::debug!("maybe_upgrade: transaction committed");
+        Ok(())
+    }
+
+    pub(crate) fn clear_experiments_and_enrollments(
+        &self,
+        writer: &mut Writer,
+    ) -> Result<(), NimbusError> {
+        self.experiment_store.clear(writer)?;
+        self.enrollment_store.clear(writer)?;
+        Ok(())
+    }
+
+    pub(crate) fn clear_event_count_data(&self, writer: &mut Writer) -> Result<(), NimbusError> {
+        self.event_count_store.clear(writer)?;
+        Ok(())
+    }
+
+    /// Migrates a v1 database to v2
+    ///
+    /// Note that any Err returns from this function (including stuff
+    /// propagated up via the ? operator) will cause maybe_update (our caller)
+    /// to assume that this is unrecoverable and wipe the database, removing
+    /// people from any existing enrollments and blowing away their experiment
+    /// history, so that they don't get left in an inconsistent state.
+    fn migrate_v1_to_v2(&self, writer: &mut Writer) -> Result<()> {
+        log::info!("Upgrading from version 1 to version 2");
+
+        // use try_collect_all to read everything except records that serde
+        // returns deserialization errors on.  Some logging of those errors
+        // happens, but it's not ideal.
+        let reader = self.read()?;
+
+        // XXX write a test to verify that we don't need to gc any
+        // enrollments that don't have experiments because the experiments
+        // were discarded either during try_collect_all (these wouldn't have been
+        // detected during the filtering phase) or during the filtering phase
+        // itself.  The test needs to run evolve_experiments, as that should
+        // correctly drop any orphans, even if the migrators aren't perfect.
+
+        let enrollments: Vec<ExperimentEnrollment> =
+            self.enrollment_store.try_collect_all(&reader)?;
+        let experiments: Vec<Experiment> = self.experiment_store.try_collect_all(&reader)?;
+
+        // figure out which experiments have records that need to be dropped
+        // and log that we're going to drop them and why
+        let empty_string = "".to_string();
+        let slugs_with_experiment_issues: HashSet<String> = experiments
+            .iter()
+            .filter_map(
+                    |e| {
+                let branch_with_empty_feature_ids =
+                    e.branches.iter().find(|b| b.feature.is_none() || b.feature.as_ref().unwrap().feature_id.is_empty());
+                if branch_with_empty_feature_ids.is_some() {
+                    log::warn!("{:?} experiment has branch missing a feature prop; experiment & enrollment will be discarded", &e.slug);
+                    Some(e.slug.to_owned())
+                } else if e.feature_ids.is_empty() || e.feature_ids.contains(&empty_string) {
+                    log::warn!("{:?} experiment has invalid feature_ids array; experiment & enrollment will be discarded", &e.slug);
+                    Some(e.slug.to_owned())
+                } else {
+                    None
+                }
+            })
+            .collect();
+        let slugs_to_discard: HashSet<_> = slugs_with_experiment_issues;
+
+        // filter out experiments to be dropped
+        let updated_experiments: Vec<Experiment> = experiments
+            .into_iter()
+            .filter(|e| !slugs_to_discard.contains(&e.slug))
+            .collect();
+        log::debug!("updated experiments = {:?}", updated_experiments);
+
+        // filter out enrollments to be dropped
+        let updated_enrollments: Vec<ExperimentEnrollment> = enrollments
+            .into_iter()
+            .filter(|e| !slugs_to_discard.contains(&e.slug))
+            .collect();
+        log::debug!("updated enrollments = {:?}", updated_enrollments);
+
+        // rewrite both stores
+        self.experiment_store.clear(writer)?;
+        for experiment in updated_experiments {
+            self.experiment_store
+                .put(writer, &experiment.slug, &experiment)?;
+        }
+
+        self.enrollment_store.clear(writer)?;
+        for enrollment in updated_enrollments {
+            self.enrollment_store
+                .put(writer, &enrollment.slug, &enrollment)?;
+        }
+        log::debug!("exiting migrate_v1_to_v2");
+
+        Ok(())
+    }
+
+    /// Gets a Store object, which used with the writer returned by
+    /// `self.write()` to update the database in a transaction.
+    pub fn get_store(&self, store_id: StoreId) -> &SingleStore {
+        match store_id {
+            StoreId::Meta => &self.meta_store,
+            StoreId::Experiments => &self.experiment_store,
+            StoreId::Enrollments => &self.enrollment_store,
+            StoreId::Updates => &self.updates_store,
+            StoreId::EventCounts => &self.event_count_store,
+        }
+    }
+
+    pub fn open_rkv<P: AsRef<Path>>(path: P) -> Result<Rkv> {
+        let path = std::path::Path::new(path.as_ref()).join("db");
+        log::debug!("open_rkv: path =  {:?}", path.display());
+        fs::create_dir_all(&path)?;
+        let rkv = match rkv_new(&path) {
+            Ok(rkv) => Ok(rkv),
+            Err(rkv_error) => {
+                match rkv_error {
+                    // For some errors we just delete the DB and start again.
+                    StoreError::DatabaseCorrupted | StoreError::FileInvalid => {
+                        // On one hand this seems a little dangerous, but on
+                        // the other hand avoids us knowing about the
+                        // underlying implementation (ie, how do we know what
+                        // files might exist in all cases?)
+                        log::warn!(
+                            "Database at '{}' appears corrupt - removing and recreating",
+                            path.display()
+                        );
+                        fs::remove_dir_all(&path)?;
+                        fs::create_dir_all(&path)?;
+                        // TODO: Once we have glean integration we want to
+                        // record telemetry here.
+                        rkv_new(&path)
+                    }
+                    // All other errors are fatal.
+                    _ => Err(rkv_error),
+                }
+            }
+        }?;
+        log::debug!("Database initialized");
+        Ok(rkv)
+    }
+
+    /// Function used to obtain a "reader" which is used for read-only transactions.
+    pub fn read(&self) -> Result<Reader> {
+        Ok(self.rkv.read()?)
+    }
+
+    /// Function used to obtain a "writer" which is used for transactions.
+    /// The `writer.commit();` must be called to commit data added via the
+    /// writer.
+    pub fn write(&self) -> Result<Writer> {
+        Ok(self.rkv.write()?)
+    }
+
+    /// Function used to retrieve persisted data outside of a transaction.
+    /// It allows retrieval of any serializable and deserializable data
+    /// Currently only supports JSON data
+    // Only available for tests; product code should always be using transactions.
+    ///
+    /// # Arguments
+    /// - `key`: A key for the data stored in the underlying database
+    #[cfg(test)]
+    pub fn get<T: serde::Serialize + for<'de> serde::Deserialize<'de>>(
+        &self,
+        store_id: StoreId,
+        key: &str,
+    ) -> Result<Option<T>> {
+        let reader = self.rkv.read()?;
+        let persisted_data = self.get_store(store_id).store.get(&reader, key)?;
+        match persisted_data {
+            Some(data) => {
+                if let rkv::Value::Json(data) = data {
+                    Ok(Some(serde_json::from_str::<T>(data)?))
+                } else {
+                    Err(NimbusError::InvalidPersistedData)
+                }
+            }
+            None => Ok(None),
+        }
+    }
+
+    // Function for collecting all items in a store outside of a transaction.
+    // Only available for tests; product code should always be using transactions.
+    // Iters are a bit tricky - would be nice to make them generic, but this will
+    // do for our use-case.
+    #[cfg(test)]
+    pub fn collect_all<T: serde::Serialize + for<'de> serde::Deserialize<'de>>(
+        &self,
+        store_id: StoreId,
+    ) -> Result<Vec<T>> {
+        let mut result = Vec::new();
+        let reader = self.rkv.read()?;
+        let mut iter = self.get_store(store_id).store.iter_start(&reader)?;
+        while let Some(Ok((_, data))) = iter.next() {
+            if let rkv::Value::Json(data) = data {
+                result.push(serde_json::from_str::<T>(data)?);
+            }
+        }
+        Ok(result)
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus/stateful/updating.rs.html b/book/rust-docs/src/nimbus/stateful/updating.rs.html new file mode 100644 index 0000000000..ae10887676 --- /dev/null +++ b/book/rust-docs/src/nimbus/stateful/updating.rs.html @@ -0,0 +1,81 @@ +updating.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+//! This module implements the primitive functions to implement
+//! safe updating from the server.
+
+use crate::error::Result;
+use crate::stateful::persistence::{Database, StoreId, Writer};
+use crate::Experiment;
+
+const KEY_PENDING_UPDATES: &str = "pending-experiment-updates";
+
+pub fn write_pending_experiments(
+    db: &Database,
+    writer: &mut Writer,
+    experiments: Vec<Experiment>,
+) -> Result<()> {
+    db.get_store(StoreId::Updates)
+        .put(writer, KEY_PENDING_UPDATES, &experiments)
+}
+
+pub fn read_and_remove_pending_experiments(
+    db: &Database,
+    writer: &mut Writer,
+) -> Result<Option<Vec<Experiment>>> {
+    let store = db.get_store(StoreId::Updates);
+    let experiments = store.get::<Vec<Experiment>, _>(writer, KEY_PENDING_UPDATES)?;
+
+    // Only clear the store if there's updates available.
+    // If we're accidentally called from the main thread,
+    // we don't want to be writing unless we absolutely have to.
+    if experiments.is_some() {
+        store.clear(writer)?;
+    }
+
+    // An empty Some(vec![]) is "updates of an empty list" i.e. unenrolling from all experiments
+    // None is "there are no pending updates".
+    Ok(experiments)
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus/strings.rs.html b/book/rust-docs/src/nimbus/strings.rs.html new file mode 100644 index 0000000000..c7946ac1c5 --- /dev/null +++ b/book/rust-docs/src/nimbus/strings.rs.html @@ -0,0 +1,247 @@ +strings.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+use crate::{NimbusError, Result};
+use serde_json::{value::Value, Map};
+
+#[allow(dead_code)]
+pub fn fmt<T: serde::Serialize>(template: &str, context: &T) -> Result<String> {
+    let obj: Value = serde_json::to_value(context)?;
+
+    fmt_with_value(template, &obj)
+}
+
+#[allow(dead_code)]
+pub fn fmt_with_value(template: &str, value: &Value) -> Result<String> {
+    if let Value::Object(map) = value {
+        Ok(fmt_with_map(template, map))
+    } else {
+        Err(NimbusError::EvaluationError(
+            "Can only format json objects".to_string(),
+        ))
+    }
+}
+
+pub fn fmt_with_map(input: &str, context: &Map<String, Value>) -> String {
+    use unicode_segmentation::UnicodeSegmentation;
+    let mut output = String::with_capacity(input.len());
+
+    let mut iter = input.grapheme_indices(true);
+    let mut last_index = 0;
+
+    // This is exceedingly simple; never refer to this as a parser.
+    while let Some((index, c)) = iter.next() {
+        if c == "{" {
+            let open_index = index;
+            for (index, c) in iter.by_ref() {
+                if c == "}" {
+                    let close_index = index;
+                    let field_name = &input[open_index + 1..close_index];
+
+                    // If we decided to embed JEXL into this templating language,
+                    // this would be the place to put it.
+                    // However, we'd likely want to make this be able to detect balanced braces,
+                    // which this does not.
+                    let replace_string = match context.get(field_name) {
+                        Some(Value::Bool(v)) => v.to_string(),
+                        Some(Value::String(v)) => v.to_string(),
+                        Some(Value::Number(v)) => v.to_string(),
+                        _ => format!("{{{v}}}", v = field_name),
+                    };
+
+                    output.push_str(&input[last_index..open_index]);
+                    output.push_str(&replace_string);
+
+                    // +1 skips the closing }
+                    last_index = close_index + 1;
+                    break;
+                }
+            }
+        }
+    }
+
+    output.push_str(&input[last_index..input.len()]);
+
+    output
+}
+
+#[cfg(test)]
+mod unit_tests {
+    use serde_json::json;
+
+    use super::*;
+
+    #[test]
+    fn smoke_tests() {
+        let c = json!({
+            "string": "STRING".to_string(),
+            "number": 42,
+            "boolean": true,
+        });
+        let c = c.as_object().unwrap();
+
+        assert_eq!(
+            fmt_with_map("A {string}, a {number}, a {boolean}.", c),
+            "A STRING, a 42, a true.".to_string()
+        );
+    }
+
+    #[test]
+    fn test_unicode_boundaries() {
+        let c = json!({
+            "empty": "".to_string(),
+            "unicode": "a̐éö̲".to_string(),
+            "a̐éö̲": "unicode".to_string(),
+        });
+        let c = c.as_object().unwrap();
+
+        assert_eq!(fmt_with_map("fîré{empty}ƒøüX", c), "fîréƒøüX".to_string());
+        assert_eq!(fmt_with_map("a̐éö̲{unicode}a̐éö̲", c), "a̐éö̲a̐éö̲a̐éö̲".to_string());
+        assert_eq!(
+            fmt_with_map("is this {a̐éö̲}?", c),
+            "is this unicode?".to_string()
+        );
+    }
+
+    #[test]
+    fn test_pathological_cases() {
+        let c = json!({
+            "empty": "".to_string(),
+        });
+        let c = c.as_object().unwrap();
+
+        assert_eq!(
+            fmt_with_map("A {notthere}.", c),
+            "A {notthere}.".to_string()
+        );
+        assert_eq!(
+            fmt_with_map("aa { unclosed", c),
+            "aa { unclosed".to_string()
+        );
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus/targeting.rs.html b/book/rust-docs/src/nimbus/targeting.rs.html new file mode 100644 index 0000000000..93a8fabcfe --- /dev/null +++ b/book/rust-docs/src/nimbus/targeting.rs.html @@ -0,0 +1,287 @@ +targeting.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+
// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+use crate::{versioning::Version, NimbusError, Result};
+use jexl_eval::Evaluator;
+use serde::Serialize;
+use serde_json::{json, Value};
+
+cfg_if::cfg_if! {
+    if #[cfg(feature = "stateful")] {
+        use crate::stateful::behavior::{EventStore, EventQueryType, query_event_store};
+        use std::sync::{Arc, Mutex};
+    }
+}
+
+pub struct NimbusTargetingHelper {
+    pub(crate) context: Value,
+    #[cfg(feature = "stateful")]
+    pub(crate) event_store: Arc<Mutex<EventStore>>,
+}
+
+impl NimbusTargetingHelper {
+    pub fn new<C: Serialize>(
+        context: C,
+        #[cfg(feature = "stateful")] event_store: Arc<Mutex<EventStore>>,
+    ) -> Self {
+        Self {
+            context: serde_json::to_value(context).unwrap(),
+            #[cfg(feature = "stateful")]
+            event_store,
+        }
+    }
+
+    pub fn eval_jexl(&self, expr: String) -> Result<bool> {
+        cfg_if::cfg_if! {
+            if #[cfg(feature = "stateful")] {
+                jexl_eval(&expr, &self.context, self.event_store.clone())
+            } else {
+                jexl_eval(&expr, &self.context)
+            }
+        }
+    }
+
+    pub(crate) fn put(&self, key: &str, value: bool) -> Self {
+        let context = if let Value::Object(map) = &self.context {
+            let mut map = map.clone();
+            map.insert(key.to_string(), Value::Bool(value));
+            Value::Object(map)
+        } else {
+            self.context.clone()
+        };
+
+        #[cfg(feature = "stateful")]
+        let event_store = self.event_store.clone();
+        Self {
+            context,
+            #[cfg(feature = "stateful")]
+            event_store,
+        }
+    }
+}
+
+// This is the common entry point to JEXL evaluation.
+// The targeting attributes and additional context should have been merged and calculated before
+// getting here.
+// Any additional transforms should be added here.
+pub fn jexl_eval<Context: serde::Serialize>(
+    expression_statement: &str,
+    context: &Context,
+    #[cfg(feature = "stateful")] event_store: Arc<Mutex<EventStore>>,
+) -> Result<bool> {
+    let evaluator =
+        Evaluator::new().with_transform("versionCompare", |args| Ok(version_compare(args)?));
+
+    #[cfg(feature = "stateful")]
+    let evaluator = evaluator
+        .with_transform("eventSum", |args| {
+            Ok(query_event_store(
+                event_store.clone(),
+                EventQueryType::Sum,
+                args,
+            )?)
+        })
+        .with_transform("eventCountNonZero", |args| {
+            Ok(query_event_store(
+                event_store.clone(),
+                EventQueryType::CountNonZero,
+                args,
+            )?)
+        })
+        .with_transform("eventAveragePerInterval", |args| {
+            Ok(query_event_store(
+                event_store.clone(),
+                EventQueryType::AveragePerInterval,
+                args,
+            )?)
+        })
+        .with_transform("eventAveragePerNonZeroInterval", |args| {
+            Ok(query_event_store(
+                event_store.clone(),
+                EventQueryType::AveragePerNonZeroInterval,
+                args,
+            )?)
+        })
+        .with_transform("eventLastSeen", |args| {
+            Ok(query_event_store(
+                event_store.clone(),
+                EventQueryType::LastSeen,
+                args,
+            )?)
+        });
+
+    let res = evaluator.eval_in_context(expression_statement, context)?;
+    match res.as_bool() {
+        Some(v) => Ok(v),
+        None => Err(NimbusError::InvalidExpression),
+    }
+}
+
+fn version_compare(args: &[Value]) -> Result<Value> {
+    let curr_version = args.get(0).ok_or_else(|| {
+        NimbusError::VersionParsingError("current version doesn't exist in jexl transform".into())
+    })?;
+    let curr_version = curr_version.as_str().ok_or_else(|| {
+        NimbusError::VersionParsingError("current version in jexl transform is not a string".into())
+    })?;
+    let min_version = args.get(1).ok_or_else(|| {
+        NimbusError::VersionParsingError("minimum version doesn't exist in jexl transform".into())
+    })?;
+    let min_version = min_version.as_str().ok_or_else(|| {
+        NimbusError::VersionParsingError("minium version is not a string in jexl transform".into())
+    })?;
+    let min_version = Version::try_from(min_version)?;
+    let curr_version = Version::try_from(curr_version)?;
+    Ok(json!(if curr_version > min_version {
+        1
+    } else if curr_version < min_version {
+        -1
+    } else {
+        0
+    }))
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus/versioning.rs.html b/book/rust-docs/src/nimbus/versioning.rs.html new file mode 100644 index 0000000000..2a31b244f5 --- /dev/null +++ b/book/rust-docs/src/nimbus/versioning.rs.html @@ -0,0 +1,597 @@ +versioning.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+//! ## Nimbus SDK App Version Comparison
+//! The Nimbus SDK supports comparing app versions that follow the Firefox versioning scheme.
+//! This module was ported from the Firefox Desktop implementation. You can find the Desktop implementation
+//! in [this C++ file](https://searchfox.org/mozilla-central/rev/468a65168dd0bc3c7d602211a566c16e66416cce/xpcom/base/nsVersionComparator.cpp)
+//! There's also some more documentation in the [IDL](https://searchfox.org/mozilla-central/rev/468a65168dd0bc3c7d602211a566c16e66416cce/xpcom/base/nsIVersionComparator.idl#9-31)
+//!
+//! ## How versioning works
+//! This module defines one main struct, the [`Version`] struct. A version is represented by a list of
+//! dot separated **Version Parts**s. When comparing two versions, we compare each version part in order.
+//! If one of the versions has a version part, but the other has run out (i.e we have reached the end of the list of version parts)
+//! we compare the existing version part with the default version part, which is the `0`. For example,
+//! `1.0` is equivalent to `1.0.0.0`.
+//!
+//! For information what version parts are composed of, and how they are compared, read the [next section](#the-version-part).
+//!
+//! ### Example Versions
+//! The following are all valid versions:
+//! - `1` (one version part, representing the `1`)
+//! - `` (one version part, representing the empty string, which is equal to `0`)
+//! - `12+` (one version part, representing `12+` which is equal to `13pre`)
+//! - `98.1` (two version parts, one representing `98` and another `1`)
+//! - `98.2pre1.0-beta` (three version parts, one for `98`, one for `2pre1` and one for `0-beta`)
+//!
+//!
+//! ## The Version Part
+//! A version part is made from 4 elements that directly follow each other:
+//! - `num_a`: A 32-bit base-10 formatted number that is at the start of the part
+//! - `str_b`: A non-numeric ascii-encoded string that starts after `num_a`
+//! - `num_c`: Another 32-bit base-10 formatted number that follows `str_b`
+//! - `extra_d`: The rest of the version part as an ascii-encoded string
+//!
+//! When two version parts are compared, each of `num_a`, `str_b`, `num_c` and `extra_d` are compared
+//! in order. `num_a` and `num_c` are compared by normal integer comparison, `str_b` and `extra_b` are compared
+//! by normal byte string comparison.
+//!
+//! ### Special values and cases
+//! There two special characters that can be used in version parts:
+//! 1. The `*`. This can be used to represent the whole version part. If used, it will set the `num_a` to be
+//!     the maximum value possible ([`i32::MAX`]). This can only be used as the whole version part string. It will parsed
+//!     normally as the `*` ascii character if it is preceded or followed by any other characters.
+//! 1. The `+`. This can be used as the `str_b`. Whenever a `+` is used as a `str_b`, it increments the `num_a` by 1 and sets
+//!     the `str_b` to be equal to `pre`. For example, `2+` is the same as `3pre`
+//! 1. An empty `str_b` is always **greater** than a `str_b` with a value. For example, `93` > `93pre`
+//!
+//! ## Example version comparisons
+//! The following comparisons are taken directly from [the brief documentation in Mozilla-Central](https://searchfox.org/mozilla-central/rev/468a65168dd0bc3c7d602211a566c16e66416cce/xpcom/base/nsIVersionComparator.idl#9-31)
+//! ```
+//! use nimbus::versioning::Version;
+//! let v1 = Version::try_from("1.0pre1").unwrap();
+//! let v2 = Version::try_from("1.0pre2").unwrap();
+//! let v3 = Version::try_from("1.0").unwrap();
+//! let v4 = Version::try_from("1.0.0").unwrap();
+//! let v5 = Version::try_from("1.0.0.0").unwrap();
+//! let v6 = Version::try_from("1.1pre").unwrap();
+//! let v7 = Version::try_from("1.1pre0").unwrap();
+//! let v8 = Version::try_from("1.0+").unwrap();
+//! let v9 = Version::try_from("1.1pre1a").unwrap();
+//! let v10 = Version::try_from("1.1pre1").unwrap();
+//! let v11 = Version::try_from("1.1pre10a").unwrap();
+//! let v12 = Version::try_from("1.1pre10").unwrap();
+//! assert!(v1 < v2);
+//! assert!(v2 < v3);
+//! assert!(v3 == v4);
+//! assert!(v4 == v5);
+//! assert!(v5 < v6);
+//! assert!(v6 == v7);
+//! assert!(v7 == v8);
+//! assert!(v8 < v9);
+//! assert!(v9 < v10);
+//! assert!(v10 < v11);
+//! assert!(v11 < v12);
+//! ```
+//! What the above is comparing is:
+//! 1.0pre1
+//! < 1.0pre2
+//!   < 1.0 == 1.0.0 == 1.0.0.0
+//!     < 1.1pre == 1.1pre0 == 1.0+
+//!       < 1.1pre1a
+//!         < 1.1pre1
+//!           < 1.1pre10a
+//!             < 1.1pre10
+
+use crate::NimbusError;
+use std::cmp::Ordering;
+
+#[derive(Debug, Default, Clone, PartialEq)]
+pub(crate) struct VersionPart {
+    pub(crate) num_a: i32,
+    pub(crate) str_b: String,
+    pub(crate) num_c: i32,
+    pub(crate) extra_d: String,
+}
+
+#[derive(Debug, Default, Clone)]
+pub struct Version(pub(crate) Vec<VersionPart>);
+
+impl PartialEq for Version {
+    fn eq(&self, other: &Self) -> bool {
+        let default_version_part: VersionPart = Default::default();
+        let mut curr_idx = 0;
+        while curr_idx < self.0.len() || curr_idx < other.0.len() {
+            let version_part = self.0.get(curr_idx).unwrap_or(&default_version_part);
+            let other_version_part = other.0.get(curr_idx).unwrap_or(&default_version_part);
+            if !version_part.eq(other_version_part) {
+                return false;
+            }
+            curr_idx += 1
+        }
+        true
+    }
+}
+
+impl PartialOrd for Version {
+    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+        let mut idx = 0;
+        let default_version: VersionPart = Default::default();
+        while idx < self.0.len() || idx < other.0.len() {
+            let version_part = self.0.get(idx).unwrap_or(&default_version);
+            let other_version_part = other.0.get(idx).unwrap_or(&default_version);
+            let ord = version_part.partial_cmp(other_version_part);
+            match ord {
+                Some(Ordering::Greater) | Some(Ordering::Less) => return ord,
+                _ => (),
+            }
+            idx += 1;
+        }
+        Some(Ordering::Equal)
+    }
+}
+
+impl PartialOrd for VersionPart {
+    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+        let num_a_ord = self.num_a.partial_cmp(&other.num_a);
+        match num_a_ord {
+            Some(Ordering::Greater) | Some(Ordering::Less) => return num_a_ord,
+            _ => (),
+        };
+
+        if self.str_b.is_empty() && !other.str_b.is_empty() {
+            return Some(Ordering::Greater);
+        } else if other.str_b.is_empty() && !self.str_b.is_empty() {
+            return Some(Ordering::Less);
+        }
+        let str_b_ord = self.str_b.partial_cmp(&other.str_b);
+        match str_b_ord {
+            Some(Ordering::Greater) | Some(Ordering::Less) => return str_b_ord,
+            _ => (),
+        };
+
+        let num_c_ord = self.num_c.partial_cmp(&other.num_c);
+        match num_c_ord {
+            Some(Ordering::Greater) | Some(Ordering::Less) => return num_c_ord,
+            _ => (),
+        };
+
+        if self.extra_d.is_empty() && !other.extra_d.is_empty() {
+            return Some(Ordering::Greater);
+        } else if other.extra_d.is_empty() && !self.extra_d.is_empty() {
+            return Some(Ordering::Less);
+        }
+        let extra_d_ord = self.extra_d.partial_cmp(&other.extra_d);
+        match extra_d_ord {
+            Some(Ordering::Greater) | Some(Ordering::Less) => return extra_d_ord,
+            _ => (),
+        };
+        Some(Ordering::Equal)
+    }
+}
+
+impl TryFrom<&'_ str> for Version {
+    type Error = NimbusError;
+    fn try_from(value: &'_ str) -> Result<Self, Self::Error> {
+        let versions = value
+            .split('.')
+            .map(TryInto::try_into)
+            .collect::<Result<Vec<_>, _>>()?;
+        Ok(Version(versions))
+    }
+}
+
+impl TryFrom<String> for Version {
+    type Error = NimbusError;
+    fn try_from(curr_part: String) -> std::result::Result<Self, Self::Error> {
+        curr_part.as_str().try_into()
+    }
+}
+
+fn char_at(value: &str, idx: usize) -> Result<char, NimbusError> {
+    value.chars().nth(idx).ok_or_else(|| {
+        NimbusError::VersionParsingError(format!(
+            "Tried to access character {} in string {}, but it has size {}",
+            idx,
+            value,
+            value.len()
+        ))
+    })
+}
+
+fn is_num_c(c: char) -> bool {
+    // TODO: why is the dash here?
+    // this makes `1-beta` end up
+    // having num_a = 1, str_b = "", num_c = 0 and extra_d = "-beta"
+    // is that correct?
+    // Taken from: https://searchfox.org/mozilla-central/rev/77efe87174ee82dad43da56d71a717139b9f19ee/xpcom/base/nsVersionComparator.cpp#107
+    c.is_numeric() || c == '+' || c == '-'
+}
+
+fn parse_version_num(val: i32, res: &mut i32) -> Result<(), NimbusError> {
+    if *res == 0 {
+        *res = val;
+    } else {
+        let res_l = *res as i64;
+        if (res_l * 10) + val as i64 > i32::MAX as i64 {
+            return Err(NimbusError::VersionParsingError(
+                "Number parsing overflows an i32".into(),
+            ));
+        }
+        *res *= 10;
+        *res += val;
+    }
+    Ok(())
+}
+
+impl TryFrom<&'_ str> for VersionPart {
+    type Error = NimbusError;
+
+    fn try_from(value: &'_ str) -> Result<Self, Self::Error> {
+        if value.chars().any(|c| !c.is_ascii()) {
+            return Err(NimbusError::VersionParsingError(format!(
+                "version string {} contains non-ascii characters",
+                value
+            )));
+        }
+        if value.is_empty() {
+            return Ok(Default::default());
+        }
+
+        let mut res: VersionPart = Default::default();
+        // if the string value is the special "*",
+        // then we set the num_a to be the highest possible value
+        // handle that case before we start
+        if value == "*" {
+            res.num_a = i32::MAX;
+            return Ok(res);
+        }
+        // Step 1: Parse the num_a, it's guaranteed to be
+        // a base-10 number, if it exists
+        let mut curr_idx = 0;
+        while curr_idx < value.len() && char_at(value, curr_idx)?.is_numeric() {
+            parse_version_num(
+                char_at(value, curr_idx)?.to_digit(10).unwrap() as i32,
+                &mut res.num_a,
+            )?;
+            curr_idx += 1;
+        }
+        if curr_idx >= value.len() {
+            return Ok(res);
+        }
+        // Step 2: Parse the str_b. If str_b starts with a "+"
+        // then we increment num_a, and set str_b to be "pre"
+        let first_char = char_at(value, curr_idx)?;
+        if first_char == '+' {
+            res.num_a += 1;
+            res.str_b = "pre".into();
+            return Ok(res);
+        }
+        // otherwise, we parse until we either finish the string
+        // or we find a numeric number, indicating the start of num_c
+        while curr_idx < value.len() && !is_num_c(char_at(value, curr_idx)?) {
+            res.str_b.push(char_at(value, curr_idx)?);
+            curr_idx += 1;
+        }
+
+        if curr_idx >= value.len() {
+            return Ok(res);
+        }
+
+        // Step 3: Parse the num_c, similar to how we parsed num_a
+        while curr_idx < value.len() && char_at(value, curr_idx)?.is_numeric() {
+            parse_version_num(
+                char_at(value, curr_idx)?.to_digit(10).unwrap() as i32,
+                &mut res.num_c,
+            )?;
+            curr_idx += 1;
+        }
+        if curr_idx >= value.len() {
+            return Ok(res);
+        }
+
+        // Step 4: Assign all the remaining to extra_d
+        res.extra_d = value[curr_idx..].into();
+        Ok(res)
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus_cli/cli.rs.html b/book/rust-docs/src/nimbus_cli/cli.rs.html new file mode 100644 index 0000000000..4d719abb60 --- /dev/null +++ b/book/rust-docs/src/nimbus_cli/cli.rs.html @@ -0,0 +1,889 @@ +cli.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+
// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+use std::{ffi::OsString, path::PathBuf};
+
+use chrono::Utc;
+use clap::{Args, Parser, Subcommand};
+
+#[derive(Parser)]
+#[command(
+    author,
+    long_about = r#"Mozilla Nimbus' command line tool for mobile apps"#
+)]
+pub(crate) struct Cli {
+    /// The app name according to Nimbus.
+    #[arg(short, long, value_name = "APP")]
+    pub(crate) app: Option<String>,
+
+    /// The channel according to Nimbus. This determines which app to talk to.
+    #[arg(short, long, value_name = "CHANNEL")]
+    pub(crate) channel: Option<String>,
+
+    /// The device id of the simulator, emulator or device.
+    #[arg(short, long, value_name = "DEVICE_ID")]
+    pub(crate) device_id: Option<String>,
+
+    #[command(subcommand)]
+    pub(crate) command: CliCommand,
+}
+
+#[derive(Subcommand, Clone)]
+pub(crate) enum CliCommand {
+    /// Send a complete JSON file to the Nimbus SDK and apply it immediately.
+    ApplyFile {
+        /// The filename to be loaded into the SDK.
+        file: PathBuf,
+
+        /// Keeps existing enrollments and experiments before enrolling.
+        ///
+        /// This is unlikely what you want to do.
+        #[arg(long, default_value = "false")]
+        preserve_nimbus_db: bool,
+
+        #[command(flatten)]
+        open: OpenArgs,
+    },
+
+    /// Capture the logs into a file.
+    CaptureLogs {
+        /// The file to put the logs.
+        file: PathBuf,
+    },
+
+    /// Print the defaults for the manifest.
+    Defaults {
+        /// An optional feature-id
+        #[arg(short, long = "feature")]
+        feature_id: Option<String>,
+
+        /// An optional file to print the manifest defaults.
+        #[arg(short, long, value_name = "OUTPUT_FILE")]
+        output: Option<PathBuf>,
+
+        #[command(flatten)]
+        manifest: ManifestArgs,
+    },
+
+    /// Enroll into an experiment or a rollout.
+    ///
+    /// The experiment slug is a combination of the actual slug, and the server it came from.
+    ///
+    /// * `release`/`stage` determines the server.
+    ///
+    /// * `preview` selects the preview collection.
+    ///
+    /// These can be further combined: e.g. $slug, preview/$slug, stage/$slug, stage/preview/$slug
+    Enroll {
+        #[command(flatten)]
+        experiment: ExperimentArgs,
+
+        /// The branch slug.
+        #[arg(short, long, value_name = "BRANCH")]
+        branch: String,
+
+        /// Optional rollout slugs, including the server and collection.
+        #[arg(value_name = "ROLLOUTS")]
+        rollouts: Vec<String>,
+
+        /// Preserves the original experiment targeting
+        #[arg(long, default_value = "false")]
+        preserve_targeting: bool,
+
+        /// Preserves the original experiment bucketing
+        #[arg(long, default_value = "false")]
+        preserve_bucketing: bool,
+
+        #[command(flatten)]
+        open: OpenArgs,
+
+        /// Keeps existing enrollments and experiments before enrolling.
+        ///
+        /// This is unlikely what you want to do.
+        #[arg(long, default_value = "false")]
+        preserve_nimbus_db: bool,
+
+        /// Don't validate the feature config files before enrolling
+        #[arg(long, default_value = "false")]
+        no_validate: bool,
+
+        #[command(flatten)]
+        manifest: ManifestArgs,
+    },
+
+    /// Print the feature configuration involved in the branch of an experiment.
+    ///
+    /// This can be optionally merged with the defaults from the feature manifest.
+    Features {
+        #[command(flatten)]
+        manifest: ManifestArgs,
+
+        #[command(flatten)]
+        experiment: ExperimentArgs,
+
+        /// The branch of the experiment
+        #[arg(short, long)]
+        branch: String,
+
+        /// If set, then merge the experimental configuration with the defaults from the manifest
+        #[arg(short, long, default_value = "false")]
+        validate: bool,
+
+        /// An optional feature-id: if it exists in this branch, print this feature
+        /// on its own.
+        #[arg(short, long = "feature")]
+        feature_id: Option<String>,
+
+        /// Print out the features involved in this branch as in a format:
+        /// `{ $feature_id: $value }`.
+        ///
+        /// Automated tools should use this, since the output is predictable.
+        #[arg(short, long = "multi", default_value = "false")]
+        multi: bool,
+
+        /// An optional file to print the output.
+        #[arg(short, long, value_name = "OUTPUT_FILE")]
+        output: Option<PathBuf>,
+    },
+
+    /// Fetch one or more named experiments and rollouts and put them in a file.
+    Fetch {
+        /// The file to download the recipes to.
+        #[arg(short, long, value_name = "OUTPUT_FILE")]
+        output: Option<PathBuf>,
+
+        #[command(flatten)]
+        experiment: ExperimentArgs,
+
+        /// The recipe slugs, including server.
+        ///
+        /// Use once per recipe to download. e.g.
+        /// fetch --output file.json preview/my-experiment my-rollout
+        ///
+        /// Cannot be used with the server option: use `fetch-list` instead.
+        #[arg(value_name = "RECIPE")]
+        recipes: Vec<String>,
+    },
+
+    /// Fetch a list of experiments and put it in a file.
+    FetchList {
+        /// The file to download the recipes to.
+        #[arg(short, long, value_name = "OUTPUT_FILE")]
+        output: Option<PathBuf>,
+
+        #[command(flatten)]
+        list: ExperimentListArgs,
+    },
+
+    /// Execute a nimbus-fml command. See
+    ///
+    /// nimbus-cli fml -- --help
+    ///
+    /// for more.
+    Fml { args: Vec<OsString> },
+
+    /// Displays information about an experiment
+    Info {
+        #[command(flatten)]
+        experiment: ExperimentArgs,
+
+        /// An optional file to print the output.
+        #[arg(short, long, value_name = "OUTPUT_FILE")]
+        output: Option<PathBuf>,
+    },
+
+    /// List the experiments from a server
+    List {
+        #[command(flatten)]
+        list: ExperimentListArgs,
+    },
+
+    /// Print the state of the Nimbus database to logs.
+    ///
+    /// This causes a restart of the app.
+    LogState {
+        #[command(flatten)]
+        open: OpenArgs,
+    },
+
+    /// Open the app without changing the state of experiment enrollments.
+    Open {
+        #[command(flatten)]
+        open: OpenArgs,
+
+        /// By default, the app is terminated before sending the a deeplink.
+        ///
+        /// If this flag is set, then do not terminate the app if it is already runnning.
+        #[arg(long, default_value = "false")]
+        no_clobber: bool,
+    },
+
+    /// Start a server
+    #[cfg(feature = "server")]
+    StartServer,
+
+    /// Reset the app back to its just installed state
+    ResetApp,
+
+    /// Follow the logs for the given app.
+    TailLogs,
+
+    /// Configure an application feature with one or more feature config files.
+    ///
+    /// One file per branch. The branch slugs will correspond to the file names.
+    ///
+    /// By default, the files are validated against the manifest; this can be
+    /// overridden with `--no-validate`.
+    TestFeature {
+        /// The identifier of the feature to configure
+        feature_id: String,
+
+        /// One or more files containing a feature config for the feature.
+        files: Vec<PathBuf>,
+
+        /// An optional patch file, used to patch feature configurations
+        ///
+        /// This is of the format that comes from the
+        /// `features --multi` or `defaults` commands.
+        #[arg(long, value_name = "PATCH_FILE")]
+        patch: Option<PathBuf>,
+
+        #[command(flatten)]
+        open: OpenArgs,
+
+        /// Don't validate the feature config files before enrolling
+        #[arg(long, default_value = "false")]
+        no_validate: bool,
+
+        #[command(flatten)]
+        manifest: ManifestArgs,
+    },
+
+    /// Unenroll from all experiments and rollouts
+    Unenroll {
+        #[command(flatten)]
+        open: OpenArgs,
+    },
+
+    /// Validate an experiment against a feature manifest
+    Validate {
+        #[command(flatten)]
+        experiment: ExperimentArgs,
+
+        #[command(flatten)]
+        manifest: ManifestArgs,
+    },
+}
+
+#[derive(Args, Clone, Debug, Default)]
+pub(crate) struct ManifestArgs {
+    /// An optional manifest file
+    #[arg(long, value_name = "MANIFEST_FILE")]
+    pub(crate) manifest: Option<String>,
+
+    /// An optional version of the app.
+    /// If present, constructs the `ref` from an app specific template.
+    /// Due to inconsistencies in branching names, this isn't always
+    /// reliable.
+    #[arg(long, value_name = "APP_VERSION")]
+    pub(crate) version: Option<String>,
+
+    /// The branch/tag/commit for the version of the manifest
+    /// to get from Github.
+    #[arg(long, value_name = "APP_VERSION", default_value = "main")]
+    pub(crate) ref_: String,
+}
+
+#[derive(Args, Clone, Debug, Default)]
+pub(crate) struct OpenArgs {
+    /// Optional deeplink. If present, launch with this link.
+    #[arg(long, value_name = "DEEPLINK")]
+    pub(crate) deeplink: Option<String>,
+
+    /// Resets the app back to its initial state before launching
+    #[arg(long, default_value = "false")]
+    pub(crate) reset_app: bool,
+
+    /// Instead of opening via adb or xcrun simctl, construct a deeplink
+    /// and put it into the pastebuffer.
+    ///
+    /// If present, then the app is not launched, so this option does not work with
+    /// `--reset-app` or passthrough arguments.
+    #[arg(long, default_value = "false")]
+    pub(crate) pbcopy: bool,
+
+    /// Instead of opening via adb or xcrun simctl, construct a deeplink
+    /// and put it into the pastebuffer.
+    ///
+    /// If present, then the app is not launched, so this option does not work with
+    /// `--reset-app` or passthrough arguments.
+    #[arg(long, default_value = "false")]
+    pub(crate) pbpaste: bool,
+
+    /// Optionally, add platform specific arguments to the adb or xcrun command.
+    ///
+    /// By default, arguments are added to the end of the command, likely to be passed
+    /// directly to the app.
+    ///
+    /// Arguments before a special placeholder `{}` are passed to
+    /// `adb am start` or `xcrun simctl launch` commands directly.
+    #[arg(last = true, value_name = "PASSTHROUGH_ARGS")]
+    pub(crate) passthrough: Vec<String>,
+
+    /// An optional file to dump experiments into.
+    ///
+    /// If present, then the app is not launched, so this option does not work with
+    /// `--reset-app` or passthrough arguments.
+    #[arg(long, value_name = "OUTPUT_FILE")]
+    pub(crate) output: Option<PathBuf>,
+}
+
+#[derive(Args, Clone, Debug, Default)]
+pub(crate) struct ExperimentArgs {
+    /// The experiment slug, including the server and collection.
+    #[arg(value_name = "EXPERIMENT_SLUG")]
+    pub(crate) experiment: String,
+
+    /// An optional file from which to get the experiment.
+    ///
+    /// By default, the file is fetched from the server.
+    #[arg(long, value_name = "EXPERIMENTS_FILE")]
+    pub(crate) file: Option<PathBuf>,
+
+    /// Use remote settings to fetch the experiment recipe.
+    ///
+    /// By default, the file is fetched from the v6 api of experimenter.
+    #[arg(long, default_value = "false")]
+    pub(crate) use_rs: bool,
+
+    /// An optional patch file, used to patch feature configurations
+    ///
+    /// This is of the format that comes from the
+    /// `features --multi` or `defaults` commands.
+    #[arg(long, value_name = "PATCH_FILE")]
+    pub(crate) patch: Option<PathBuf>,
+}
+
+#[derive(Args, Clone, Debug, Default)]
+pub(crate) struct ExperimentListArgs {
+    #[command(flatten)]
+    pub(crate) source: ExperimentListSourceArgs,
+
+    #[command(flatten)]
+    pub(crate) filter: ExperimentListFilterArgs,
+}
+
+#[derive(Args, Clone, Debug, Default)]
+pub(crate) struct ExperimentListSourceArgs {
+    /// A server slug e.g. preview, release, stage, stage/preview
+    #[arg(default_value = "")]
+    pub(crate) server: String,
+
+    /// An optional file
+    #[arg(short, long, value_name = "FILE")]
+    pub(crate) file: Option<PathBuf>,
+
+    /// Use the v6 API to fetch the experiment recipes.
+    ///
+    /// By default, the file is fetched from the Remote Settings.
+    ///
+    /// The API contains *all* launched experiments, past and present,
+    /// so this is considerably slower and longer than Remote Settings.
+    #[arg(long, default_value = "false")]
+    pub(crate) use_api: bool,
+}
+
+#[derive(Args, Clone, Debug, Default)]
+pub(crate) struct ExperimentListFilterArgs {
+    #[arg(short = 'S', long, value_name = "SLUG_PATTERN")]
+    pub(crate) slug: Option<String>,
+
+    #[arg(short = 'F', long, value_name = "FEATURE_PATTERN")]
+    pub(crate) feature: Option<String>,
+
+    #[arg(short = 'A', long, value_name = "DATE", value_parser=validate_date)]
+    pub(crate) active_on: Option<String>,
+
+    #[arg(short = 'E', long, value_name = "DATE", value_parser=validate_date)]
+    pub(crate) enrolling_on: Option<String>,
+
+    #[arg(short = 'C', long, value_name = "CHANNEL")]
+    pub(crate) channel: Option<String>,
+
+    #[arg(short = 'R', long, value_name = "FLAG")]
+    pub(crate) is_rollout: Option<bool>,
+}
+
+fn validate_num(s: &str, l: usize) -> Result<(), &'static str> {
+    if !s.chars().all(char::is_numeric) {
+        Err("String contains non-numeric characters")
+    } else if s.len() != l {
+        Err("String is the wrong length")
+    } else {
+        Ok(())
+    }
+}
+
+fn validate_date_parts(yyyy: &str, mm: &str, dd: &str) -> Result<(), &'static str> {
+    validate_num(yyyy, 4)?;
+    validate_num(mm, 2)?;
+    validate_num(dd, 2)?;
+    Ok(())
+}
+
+fn validate_date(s: &str) -> Result<String, String> {
+    if s == "today" {
+        let now = Utc::now();
+        return Ok(format!("{}", now.format("%Y-%m-%d")));
+    }
+    match s.splitn(3, '-').collect::<Vec<_>>().as_slice() {
+        [yyyy, mm, dd] if validate_date_parts(yyyy, mm, dd).is_ok() => Ok(s.to_string()),
+        _ => Err("Date string must be yyyy-mm-dd".to_string()),
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus_cli/cmd.rs.html b/book/rust-docs/src/nimbus_cli/cmd.rs.html new file mode 100644 index 0000000000..cef55be74e --- /dev/null +++ b/book/rust-docs/src/nimbus_cli/cmd.rs.html @@ -0,0 +1,1311 @@ +cmd.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
+540
+541
+542
+543
+544
+545
+546
+547
+548
+549
+550
+551
+552
+553
+554
+555
+556
+557
+558
+559
+560
+561
+562
+563
+564
+565
+566
+567
+568
+569
+570
+571
+572
+573
+574
+575
+576
+577
+578
+579
+580
+581
+582
+583
+584
+585
+586
+587
+588
+589
+590
+591
+592
+593
+594
+595
+596
+597
+598
+599
+600
+601
+602
+603
+604
+605
+606
+607
+608
+609
+610
+611
+612
+613
+614
+615
+616
+617
+618
+619
+620
+621
+622
+623
+624
+625
+626
+627
+628
+629
+630
+631
+632
+633
+634
+635
+636
+637
+638
+639
+640
+641
+642
+643
+644
+645
+646
+647
+648
+649
+650
+651
+652
+653
+654
+655
+
// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+#[cfg(feature = "server")]
+use crate::output::server;
+use crate::{
+    output::{deeplink, fml_cli},
+    protocol::StartAppProtocol,
+    sources::ManifestSource,
+    value_utils::{
+        self, prepare_experiment, prepare_rollout, try_find_branches_from_experiment,
+        try_find_features_from_branch, CliUtils,
+    },
+    AppCommand, AppOpenArgs, ExperimentListSource, ExperimentSource, LaunchableApp, NimbusApp,
+};
+use anyhow::{bail, Result};
+use console::Term;
+use nimbus_fml::intermediate_representation::FeatureManifest;
+use serde_json::{json, Value};
+use std::{path::PathBuf, process::Command};
+
+pub(crate) fn process_cmd(cmd: &AppCommand) -> Result<bool> {
+    let status = match cmd {
+        AppCommand::ApplyFile {
+            app,
+            open,
+            list,
+            preserve_nimbus_db,
+        } => app.apply_list(open, list, preserve_nimbus_db)?,
+        AppCommand::CaptureLogs { app, file } => app.capture_logs(file)?,
+        AppCommand::Defaults {
+            manifest,
+            feature_id,
+            output,
+        } => manifest.print_defaults(feature_id.as_ref(), output.as_ref())?,
+        AppCommand::Enroll {
+            app,
+            params,
+            experiment,
+            rollouts,
+            branch,
+            preserve_targeting,
+            preserve_bucketing,
+            preserve_nimbus_db,
+            open,
+            ..
+        } => app.enroll(
+            params,
+            experiment,
+            rollouts,
+            branch,
+            preserve_targeting,
+            preserve_bucketing,
+            preserve_nimbus_db,
+            open,
+        )?,
+        AppCommand::ExtractFeatures {
+            experiment,
+            branch,
+            manifest,
+            feature_id,
+            validate,
+            multi,
+            output,
+        } => experiment.print_features(
+            branch,
+            manifest,
+            feature_id.as_ref(),
+            *validate,
+            *multi,
+            output.as_ref(),
+        )?,
+
+        AppCommand::FetchList { list, file } => list.fetch_list(file.as_ref())?,
+        AppCommand::FmlPassthrough { args, cwd } => fml_cli(args, cwd)?,
+        AppCommand::Info { experiment, output } => experiment.print_info(output.as_ref())?,
+        AppCommand::Kill { app } => app.kill_app()?,
+        AppCommand::List { list, .. } => list.print_list()?,
+        AppCommand::LogState { app, open } => app.log_state(open)?,
+        AppCommand::NoOp => true,
+        AppCommand::Open {
+            app, open: args, ..
+        } => app.open(args)?,
+        AppCommand::Reset { app } => app.reset_app()?,
+        #[cfg(feature = "server")]
+        AppCommand::StartServer => server::start_server()?,
+        AppCommand::TailLogs { app } => app.tail_logs()?,
+        AppCommand::Unenroll { app, open } => app.unenroll_all(open)?,
+        AppCommand::ValidateExperiment {
+            params,
+            manifest,
+            experiment,
+        } => params.validate_experiment(manifest, experiment)?,
+    };
+
+    Ok(status)
+}
+
+fn prompt(term: &Term, command: &str) -> Result<()> {
+    let prompt = term.style().cyan();
+    let style = term.style().yellow();
+    term.write_line(&format!(
+        "{} {}",
+        prompt.apply_to("$"),
+        style.apply_to(command)
+    ))?;
+    Ok(())
+}
+
+fn output_ok(term: &Term, title: &str) -> Result<()> {
+    let style = term.style().green();
+    term.write_line(&format!("✅ {}", style.apply_to(title)))?;
+    Ok(())
+}
+
+fn output_err(term: &Term, title: &str, detail: &str) -> Result<()> {
+    let style = term.style().red();
+    term.write_line(&format!("❎ {}: {detail}", style.apply_to(title),))?;
+    Ok(())
+}
+
+impl LaunchableApp {
+    #[cfg(feature = "server")]
+    fn platform(&self) -> &str {
+        match self {
+            Self::Android { .. } => "android",
+            Self::Ios { .. } => "ios",
+        }
+    }
+
+    fn exe(&self) -> Result<Command> {
+        Ok(match self {
+            Self::Android { device_id, .. } => {
+                let adb_name = if std::env::consts::OS != "windows" {
+                    "adb"
+                } else {
+                    "adb.exe"
+                };
+                let adb = std::env::var("ADB_PATH").unwrap_or_else(|_| adb_name.to_string());
+                let mut cmd = Command::new(adb);
+                if let Some(id) = device_id {
+                    cmd.args(["-s", id]);
+                }
+                cmd
+            }
+            Self::Ios { .. } => {
+                if std::env::consts::OS != "macos" {
+                    panic!("Cannot run commands for iOS on anything except macOS");
+                }
+                let xcrun = std::env::var("XCRUN_PATH").unwrap_or_else(|_| "xcrun".to_string());
+                let mut cmd = Command::new(xcrun);
+                cmd.arg("simctl");
+                cmd
+            }
+        })
+    }
+
+    fn kill_app(&self) -> Result<bool> {
+        Ok(match self {
+            Self::Android { package_name, .. } => self
+                .exe()?
+                .arg("shell")
+                .arg(format!("am force-stop {}", package_name))
+                .spawn()?
+                .wait()?
+                .success(),
+            Self::Ios {
+                app_id, device_id, ..
+            } => {
+                let _ = self
+                    .exe()?
+                    .args(["terminate", device_id, app_id])
+                    .output()?;
+                true
+            }
+        })
+    }
+
+    fn unenroll_all(&self, open: &AppOpenArgs) -> Result<bool> {
+        let payload = TryFrom::try_from(&ExperimentListSource::Empty)?;
+        let protocol = StartAppProtocol {
+            log_state: true,
+            experiments: Some(&payload),
+            ..Default::default()
+        };
+        self.start_app(protocol, open)
+    }
+
+    fn reset_app(&self) -> Result<bool> {
+        Ok(match self {
+            Self::Android { package_name, .. } => self
+                .exe()?
+                .arg("shell")
+                .arg(format!("pm clear {}", package_name))
+                .spawn()?
+                .wait()?
+                .success(),
+            Self::Ios {
+                app_id, device_id, ..
+            } => {
+                self.exe()?
+                    .args(["privacy", device_id, "reset", "all", app_id])
+                    .status()?;
+                let data = self.ios_app_container("data")?;
+                let groups = self.ios_app_container("groups")?;
+                self.ios_reset(data, groups)?;
+                true
+            }
+        })
+    }
+
+    fn tail_logs(&self) -> Result<bool> {
+        let term = Term::stdout();
+        let _ = term.clear_screen();
+        Ok(match self {
+            Self::Android { .. } => {
+                let mut args = logcat_args();
+                args.append(&mut vec!["-v", "color"]);
+                prompt(&term, &format!("adb {}", args.join(" ")))?;
+                self.exe()?.args(args).spawn()?.wait()?.success()
+            }
+            Self::Ios { .. } => {
+                prompt(
+                    &term,
+                    &format!("{} | xargs tail -f", self.ios_log_file_command()),
+                )?;
+                let log = self.ios_log_file()?;
+
+                Command::new("tail")
+                    .arg("-f")
+                    .arg(log.as_path().to_str().unwrap())
+                    .spawn()?
+                    .wait()?
+                    .success()
+            }
+        })
+    }
+
+    fn capture_logs(&self, file: &PathBuf) -> Result<bool> {
+        let term = Term::stdout();
+        Ok(match self {
+            Self::Android { .. } => {
+                let mut args = logcat_args();
+                args.append(&mut vec!["-d"]);
+                prompt(
+                    &term,
+                    &format!(
+                        "adb {} > {}",
+                        args.join(" "),
+                        file.as_path().to_str().unwrap()
+                    ),
+                )?;
+                let output = self.exe()?.args(args).output()?;
+                std::fs::write(file, String::from_utf8_lossy(&output.stdout).to_string())?;
+                true
+            }
+
+            Self::Ios { .. } => {
+                let log = self.ios_log_file()?;
+                prompt(
+                    &term,
+                    &format!(
+                        "{} | xargs -J %log_file% cp %log_file% {}",
+                        self.ios_log_file_command(),
+                        file.as_path().to_str().unwrap()
+                    ),
+                )?;
+                std::fs::copy(log, file)?;
+                true
+            }
+        })
+    }
+
+    fn ios_log_file(&self) -> Result<PathBuf> {
+        let data = self.ios_app_container("data")?;
+        let mut files = glob::glob(&format!("{}/**/*.log", data))?;
+        let log = files.next();
+        Ok(log.ok_or_else(|| {
+            anyhow::Error::msg(
+                "Logs are not available before the app is started for the first time",
+            )
+        })??)
+    }
+
+    fn ios_log_file_command(&self) -> String {
+        if let Self::Ios {
+            device_id, app_id, ..
+        } = self
+        {
+            format!(
+                "find $(xcrun simctl get_app_container {0} {1} data) -name \\*.log",
+                device_id, app_id
+            )
+        } else {
+            unreachable!()
+        }
+    }
+
+    fn log_state(&self, open: &AppOpenArgs) -> Result<bool> {
+        let protocol = StartAppProtocol {
+            log_state: true,
+            ..Default::default()
+        };
+        self.start_app(protocol, open)
+    }
+
+    #[allow(clippy::too_many_arguments)]
+    fn enroll(
+        &self,
+        params: &NimbusApp,
+        experiment: &ExperimentSource,
+        rollouts: &Vec<ExperimentSource>,
+        branch: &str,
+        preserve_targeting: &bool,
+        preserve_bucketing: &bool,
+        preserve_nimbus_db: &bool,
+        open: &AppOpenArgs,
+    ) -> Result<bool> {
+        let term = Term::stdout();
+
+        let experiment = Value::try_from(experiment)?;
+        let slug = experiment.get_str("slug")?.to_string();
+
+        let mut recipes = vec![prepare_experiment(
+            &experiment,
+            params,
+            branch,
+            *preserve_targeting,
+            *preserve_bucketing,
+        )?];
+        prompt(
+            &term,
+            &format!("# Enrolling in the '{0}' branch of '{1}'", branch, &slug),
+        )?;
+
+        for r in rollouts {
+            let rollout = Value::try_from(r)?;
+            let slug = rollout.get_str("slug")?.to_string();
+            recipes.push(prepare_rollout(
+                &rollout,
+                params,
+                *preserve_targeting,
+                *preserve_bucketing,
+            )?);
+            prompt(&term, &format!("# Enrolling into the '{0}' rollout", &slug))?;
+        }
+
+        let payload = json! {{ "data": recipes }};
+        let protocol = StartAppProtocol {
+            reset_db: !preserve_nimbus_db,
+            experiments: Some(&payload),
+            log_state: true,
+        };
+        self.start_app(protocol, open)
+    }
+
+    fn apply_list(
+        &self,
+        open: &AppOpenArgs,
+        list: &ExperimentListSource,
+        preserve_nimbus_db: &bool,
+    ) -> Result<bool> {
+        let value: Value = list.try_into()?;
+
+        let protocol = StartAppProtocol {
+            reset_db: !preserve_nimbus_db,
+            experiments: Some(&value),
+            log_state: true,
+        };
+        self.start_app(protocol, open)
+    }
+
+    fn ios_app_container(&self, container: &str) -> Result<String> {
+        if let Self::Ios {
+            app_id, device_id, ..
+        } = self
+        {
+            // We need to get the app container directories, and delete them.
+            let output = self
+                .exe()?
+                .args(["get_app_container", device_id, app_id, container])
+                .output()
+                .expect("Expected an app-container from the simulator");
+            let string = String::from_utf8_lossy(&output.stdout).to_string();
+            Ok(string.trim().to_string())
+        } else {
+            unreachable!()
+        }
+    }
+
+    fn ios_reset(&self, data_dir: String, groups_string: String) -> Result<bool> {
+        let term = Term::stdout();
+        prompt(&term, "# Resetting the app")?;
+        if !data_dir.is_empty() {
+            prompt(&term, &format!("rm -Rf {}/* 2>/dev/null", data_dir))?;
+            let _ = std::fs::remove_dir_all(&data_dir);
+            let _ = std::fs::create_dir_all(&data_dir);
+        }
+        let lines = groups_string.split('\n');
+
+        for line in lines {
+            let words = line.splitn(2, '\t').collect::<Vec<_>>();
+            if let [_, dir] = words.as_slice() {
+                if !dir.is_empty() {
+                    prompt(&term, &format!("rm -Rf {}/* 2>/dev/null", dir))?;
+                    let _ = std::fs::remove_dir_all(dir);
+                    let _ = std::fs::create_dir_all(dir);
+                }
+            }
+        }
+        Ok(true)
+    }
+
+    fn open(&self, open: &AppOpenArgs) -> Result<bool> {
+        self.start_app(Default::default(), open)
+    }
+
+    fn start_app(&self, app_protocol: StartAppProtocol, open: &AppOpenArgs) -> Result<bool> {
+        let term = Term::stdout();
+        if open.pbcopy {
+            let len = self.copy_to_clipboard(&app_protocol, open)?;
+            prompt(
+                &term,
+                &format!("# Copied a deeplink URL ({len} characters) in to the clipboard"),
+            )?;
+        }
+        #[cfg(feature = "server")]
+        if open.pbpaste {
+            let url = self.longform_url(&app_protocol, open)?;
+            let addr = server::get_address()?;
+            match server::post_deeplink(self.platform(), &url, app_protocol.experiments) {
+                Err(_) => output_err(
+                    &term,
+                    "Cannot post to the server",
+                    "Start the server with `nimbus-cli start-server`",
+                )?,
+                _ => output_ok(&term, &format!("Posted to server at http://{addr}"))?,
+            };
+        }
+        if let Some(file) = &open.output {
+            let ex = app_protocol.experiments;
+            if let Some(contents) = ex {
+                value_utils::write_to_file_or_print(Some(file), contents)?;
+                output_ok(
+                    &term,
+                    &format!(
+                        "Written to JSON to file {}",
+                        file.to_str().unwrap_or_default()
+                    ),
+                )?;
+            } else {
+                output_err(
+                    &term,
+                    "No content",
+                    &format!("File {} not written", file.to_str().unwrap_or_default()),
+                )?;
+            }
+        }
+        if open.pbcopy || open.pbpaste || open.output.is_some() {
+            return Ok(true);
+        }
+
+        Ok(match self {
+            Self::Android { .. } => self
+                .android_start(app_protocol, open)?
+                .spawn()?
+                .wait()?
+                .success(),
+            Self::Ios { .. } => self
+                .ios_start(app_protocol, open)?
+                .spawn()?
+                .wait()?
+                .success(),
+        })
+    }
+
+    fn android_start(&self, app_protocol: StartAppProtocol, open: &AppOpenArgs) -> Result<Command> {
+        if let Self::Android {
+            package_name,
+            activity_name,
+            ..
+        } = self
+        {
+            let mut args: Vec<String> = Vec::new();
+
+            let (start_args, ending_args) = open.args();
+            args.extend_from_slice(start_args);
+
+            if let Some(deeplink) = self.deeplink(open)? {
+                args.extend([
+                    "-a android.intent.action.VIEW".to_string(),
+                    "-c android.intent.category.DEFAULT".to_string(),
+                    "-c android.intent.category.BROWSABLE".to_string(),
+                    format!("-d {}", deeplink),
+                ]);
+            } else {
+                args.extend([
+                    format!("-n {}/{}", package_name, activity_name),
+                    "-a android.intent.action.MAIN".to_string(),
+                    "-c android.intent.category.LAUNCHER".to_string(),
+                ]);
+            }
+
+            let StartAppProtocol {
+                reset_db,
+                experiments,
+                log_state,
+            } = app_protocol;
+
+            if log_state || experiments.is_some() || reset_db {
+                args.extend(["--esn nimbus-cli".to_string(), "--ei version 1".to_string()]);
+            }
+
+            if reset_db {
+                args.push("--ez reset-db true".to_string());
+            }
+            if let Some(s) = experiments {
+                let json = s.to_string().replace('\'', "&apos;");
+                args.push(format!("--es experiments '{}'", json))
+            }
+            if log_state {
+                args.push("--ez log-state true".to_string());
+            };
+            args.extend_from_slice(ending_args);
+
+            let sh = format!(r#"am start {}"#, args.join(" \\\n        "),);
+            let term = Term::stdout();
+            prompt(&term, &format!("adb shell \"{}\"", sh))?;
+            let mut cmd = self.exe()?;
+            cmd.arg("shell").arg(&sh);
+            Ok(cmd)
+        } else {
+            unreachable!();
+        }
+    }
+
+    fn ios_start(&self, app_protocol: StartAppProtocol, open: &AppOpenArgs) -> Result<Command> {
+        if let Self::Ios {
+            app_id, device_id, ..
+        } = self
+        {
+            let mut args: Vec<String> = Vec::new();
+
+            let (starting_args, ending_args) = open.args();
+
+            if let Some(deeplink) = self.deeplink(open)? {
+                let deeplink = deeplink::longform_deeplink_url(&deeplink, &app_protocol)?;
+                if deeplink.len() >= 2047 {
+                    anyhow::bail!("Deeplink is too long for xcrun simctl openurl. Use --pbcopy to copy the URL to the clipboard")
+                }
+                args.push("openurl".to_string());
+                args.extend_from_slice(starting_args);
+                args.extend([device_id.to_string(), deeplink]);
+            } else {
+                args.push("launch".to_string());
+                args.extend_from_slice(starting_args);
+                args.extend([device_id.to_string(), app_id.to_string()]);
+
+                let StartAppProtocol {
+                    log_state,
+                    experiments,
+                    reset_db,
+                } = app_protocol;
+
+                if log_state || experiments.is_some() || reset_db {
+                    args.extend([
+                        "--nimbus-cli".to_string(),
+                        "--version".to_string(),
+                        "1".to_string(),
+                    ]);
+                }
+
+                if reset_db {
+                    // We don't check launch here, because reset-db is never used
+                    // without enroll.
+                    args.push("--reset-db".to_string());
+                }
+                if let Some(s) = experiments {
+                    args.extend([
+                        "--experiments".to_string(),
+                        s.to_string().replace('\'', "&apos;"),
+                    ]);
+                }
+                if log_state {
+                    args.push("--log-state".to_string());
+                }
+            }
+            args.extend_from_slice(ending_args);
+
+            let mut cmd = self.exe()?;
+            cmd.args(args.clone());
+
+            let sh = format!(r#"xcrun simctl {}"#, args.join(" \\\n        "),);
+            let term = Term::stdout();
+            prompt(&term, &sh)?;
+            Ok(cmd)
+        } else {
+            unreachable!()
+        }
+    }
+}
+
+fn logcat_args<'a>() -> Vec<&'a str> {
+    vec!["logcat", "-b", "main"]
+}
+
+impl NimbusApp {
+    fn validate_experiment(
+        &self,
+        manifest_source: &ManifestSource,
+        experiment: &ExperimentSource,
+    ) -> Result<bool> {
+        let term = Term::stdout();
+        let value: Value = experiment.try_into()?;
+
+        let manifest = match TryInto::<FeatureManifest>::try_into(manifest_source) {
+            Ok(manifest) => {
+                output_ok(&term, &format!("Loaded manifest from {manifest_source}"))?;
+                manifest
+            }
+            Err(err) => {
+                output_err(
+                    &term,
+                    &format!("Problem with manifest from {manifest_source}"),
+                    &err.to_string(),
+                )?;
+                bail!("Error when loading and validating the manifest");
+            }
+        };
+
+        let mut is_valid = true;
+        for b in try_find_branches_from_experiment(&value)? {
+            let branch = b.get_str("slug")?;
+            for f in try_find_features_from_branch(&b)? {
+                let id = f.get_str("featureId")?;
+                let value = f
+                    .get("value")
+                    .unwrap_or_else(|| panic!("Branch {branch} feature {id} has no value"));
+                let res = manifest.validate_feature_config(id, value.clone());
+                match res {
+                    Ok(_) => output_ok(&term, &format!("{branch: <15} {id}"))?,
+                    Err(err) => {
+                        is_valid = false;
+                        output_err(&term, &format!("{branch: <15} {id}"), &err.to_string())?
+                    }
+                }
+            }
+        }
+        if !is_valid {
+            bail!("At least one error detected");
+        }
+        Ok(true)
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus_cli/config.rs.html b/book/rust-docs/src/nimbus_cli/config.rs.html new file mode 100644 index 0000000000..420ecbf886 --- /dev/null +++ b/book/rust-docs/src/nimbus_cli/config.rs.html @@ -0,0 +1,477 @@ +config.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+
// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+use std::path::PathBuf;
+
+use crate::{cli::Cli, LaunchableApp, NimbusApp};
+use anyhow::{bail, Result};
+
+impl TryFrom<&Cli> for LaunchableApp {
+    type Error = anyhow::Error;
+    fn try_from(value: &Cli) -> Result<Self> {
+        Self::try_from_app_channel_device(
+            value.app.as_deref(),
+            value.channel.as_deref(),
+            value.device_id.as_deref(),
+        )
+    }
+}
+
+impl LaunchableApp {
+    pub(crate) fn try_from_app_channel_device(
+        app: Option<&str>,
+        channel: Option<&str>,
+        device_id: Option<&str>,
+    ) -> Result<Self> {
+        match (&app, &channel) {
+            (None, None) => anyhow::bail!("A value for --app and --channel must be specified. Supported apps are: fenix, focus_android, firefox_ios and focus_ios"),
+            (None, _) => anyhow::bail!("A value for --app must be specified. One of: fenix, focus_android, firefox_ios and focus_ios are currently supported"),
+            (_, None) => anyhow::bail!("A value for --channel must be specified. Supported channels are: developer, nightly, beta and release"),
+            _ => (),
+        }
+
+        let app = app.unwrap();
+        let channel = channel.unwrap();
+
+        let prefix = match app {
+            "fenix" => Some("org.mozilla"),
+            "focus_android" => Some("org.mozilla"),
+            "firefox_ios" => Some("org.mozilla.ios"),
+            "focus_ios" => Some("org.mozilla.ios"),
+            _ => anyhow::bail!("Only --app values of fenix, focus_android, firefox_ios and focus_ios are currently supported"),
+        };
+
+        let suffix = match app {
+            "fenix" => Some(match channel {
+                "developer" => "fenix.debug",
+                "nightly" => "fenix",
+                "beta" => "firefox_beta",
+                "release" => "firefox",
+                _ => bail!(format!("Application {} has no channel '{}'. Try one of developer, nightly, beta or release", app, channel)),
+            }),
+            "focus_android" => Some(match channel {
+                "developer" => "focus.debug",
+                "nightly" => "focus.nightly",
+                "beta" => "focus.beta",
+                "release" => "focus",
+                _ => bail!(format!("Application {} has no channel '{}'. Try one of developer, nightly, beta or release", app, channel)),
+            }),
+            "firefox_ios" => Some(match channel {
+                "developer" => "Fennec",
+                "beta" => "FirefoxBeta",
+                "release" => "Firefox",
+                _ => bail!(format!("Application {} has no channel '{}'. Try one of developer, beta or release", app, channel)),
+            }),
+            "focus_ios" => Some(match channel {
+                "developer" => "Focus",
+                "beta" => "Focus",
+                "release" => "Focus",
+                _ => bail!(format!("Application {} has no channel '{}'. Try one of developer, beta or release", app, channel)),
+            }),
+            _ => None,
+        };
+
+        // Scheme for deeplinks.
+        let scheme = match app {
+            "fenix" => Some(match channel {
+                // Firefox for Android defines per channel deeplink schemes in the app/build.gradle.
+                // e.g. https://github.com/mozilla-mobile/firefox-android/blob/5d18e7ffe2f3e4505ea815d584d20e66ad10f515/fenix/app/build.gradle#L154
+                "developer" => "fenix-dev",
+                "nightly" => "fenix-nightly",
+                "beta" => "fenix-beta",
+                "release" => "fenix",
+                _ => unreachable!(),
+            }),
+            "firefox_ios" => Some(match channel {
+                // Firefox for iOS uses MOZ_PUBLIC_URL_SCHEME, which is always
+                // [`firefox`](https://github.com/mozilla-mobile/firefox-ios/blob/f1acc8a2232a736e65e235b811372ddbf3e802f8/Client/Configuration/Common.xcconfig#L24)
+                // and MOZ_INTERNAL_URL_SCHEME which is different per channel.
+                // e.g. https://github.com/mozilla-mobile/firefox-ios/blob/f1acc8a2232a736e65e235b811372ddbf3e802f8/Client/Configuration/Firefox.xcconfig#L12
+                // From inspection of the code, there are no different uses for the internal vs
+                // public, so very useful for launching the specific app on a phone where you
+                // have multiple versions installed.
+                "developer" => "fennec",
+                "beta" => "firefox-beta",
+                "release" => "firefox-internal",
+                _ => unreachable!(),
+            }),
+            // Focus for iOS has two, firefox-focus and firefox-klar
+            // It's not clear if Focus's channels are configured for this
+            "focus_ios" => Some("firefox-focus"),
+
+            // Focus for Android provides no deeplinks.
+            _ => None,
+        }
+        .map(str::to_string);
+
+        Ok(match (app, prefix, suffix) {
+            ("fenix", Some(prefix), Some(suffix)) => Self::Android {
+                package_name: format!("{}.{}", prefix, suffix),
+                activity_name: ".App".to_string(),
+                device_id: device_id.map(str::to_string),
+                scheme,
+                open_deeplink: Some("open".to_string()),
+            },
+            ("focus_android", Some(prefix), Some(suffix)) => Self::Android {
+                package_name: format!("{}.{}", prefix, suffix),
+                activity_name: "org.mozilla.focus.activity.MainActivity".to_string(),
+                device_id: device_id.map(str::to_string),
+                scheme,
+                open_deeplink: None,
+            },
+            ("firefox_ios", Some(prefix), Some(suffix)) => Self::Ios {
+                app_id: format!("{}.{}", prefix, suffix),
+                device_id: device_id.unwrap_or("booted").to_string(),
+                scheme,
+            },
+            ("focus_ios", Some(prefix), Some(suffix)) => Self::Ios {
+                app_id: format!("{}.{}", prefix, suffix),
+                device_id: device_id.unwrap_or("booted").to_string(),
+                scheme,
+            },
+            _ => unreachable!(),
+        })
+    }
+}
+
+impl NimbusApp {
+    pub(crate) fn ref_from_version(
+        &self,
+        version: &Option<String>,
+        ref_: &String,
+    ) -> Result<String> {
+        if version.is_none() {
+            return Ok(ref_.to_string());
+        }
+        let version = version.as_ref().unwrap();
+        let app_name = self
+            .app_name()
+            .ok_or_else(|| anyhow::anyhow!("Either an --app or a --manifest must be specified"))?;
+        Ok(match app_name.as_str() {
+            // Fenix and Focus are both in the same repo, so should have the
+            // same branching structure.
+            "fenix" | "focus_android" => format!("releases_v{version}"),
+            "firefox_ios" => format!("release/v{version}"),
+            "focus_ios" => format!("releases_v{version}"),
+
+            _ => anyhow::bail!("{} is not defined", app_name),
+        })
+    }
+
+    pub(crate) fn github_repo<'a>(&self) -> Result<&'a str> {
+        let app_name = self
+            .app_name()
+            .ok_or_else(|| anyhow::anyhow!("Either an --app or a --manifest must be specified"))?;
+        Ok(match app_name.as_str() {
+            // Fenix and Focus are both in the same repo
+            "fenix" | "focus_android" => "mozilla-mobile/firefox-android",
+            "firefox_ios" => "mozilla-mobile/firefox-ios",
+            "focus_ios" => "mozilla-mobile/focus-ios",
+            _ => unreachable!("{} is not defined", app_name),
+        })
+    }
+
+    pub(crate) fn manifest_location<'a>(&self) -> Result<&'a str> {
+        let app_name = self
+            .app_name()
+            .ok_or_else(|| anyhow::anyhow!("Either an --app or a --manifest must be specified"))?;
+        Ok(match app_name.as_str() {
+            "fenix" => "fenix/app/nimbus.fml.yaml",
+            "focus_android" => "focus-android/app/nimbus.fml.yaml",
+            "firefox_ios" => "nimbus.fml.yaml",
+            "focus_ios" => "nimbus.fml.yaml",
+            _ => anyhow::bail!("{} is not defined", app_name),
+        })
+    }
+}
+
+pub(crate) fn rs_production_server() -> String {
+    std::env::var("NIMBUS_URL")
+        .unwrap_or_else(|_| "https://firefox.settings.services.mozilla.com".to_string())
+}
+
+pub(crate) fn rs_stage_server() -> String {
+    std::env::var("NIMBUS_URL_STAGE")
+        .unwrap_or_else(|_| "https://firefox.settings.services.allizom.org".to_string())
+}
+
+pub(crate) fn api_v6_production_server() -> String {
+    std::env::var("NIMBUS_API_URL")
+        .unwrap_or_else(|_| "https://experimenter.services.mozilla.com".to_string())
+}
+
+pub(crate) fn api_v6_stage_server() -> String {
+    std::env::var("NIMBUS_API_URL_STAGE")
+        .unwrap_or_else(|_| "https://stage.experimenter.nonprod.dataops.mozgcp.net".to_string())
+}
+
+pub(crate) fn manifest_cache_dir() -> Option<PathBuf> {
+    match std::env::var("NIMBUS_MANIFEST_CACHE") {
+        Ok(s) => {
+            let cwd = std::env::current_dir().expect("Current Working Directory is not set");
+            Some(cwd.join(s))
+        }
+        // We let the Nimbus FML define its own cache.
+        _ => None,
+    }
+}
+
+#[cfg(feature = "server")]
+pub(crate) fn server_port() -> String {
+    match std::env::var("NIMBUS_CLI_SERVER_PORT") {
+        Ok(s) => s,
+        _ => "8080".to_string(),
+    }
+}
+
+#[cfg(feature = "server")]
+pub(crate) fn server_host() -> String {
+    match std::env::var("NIMBUS_CLI_SERVER_HOST") {
+        Ok(s) => s,
+        _ => {
+            use local_ip_address::local_ip;
+            let ip = local_ip().unwrap();
+            ip.to_string()
+        }
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus_cli/feature_utils.rs.html b/book/rust-docs/src/nimbus_cli/feature_utils.rs.html new file mode 100644 index 0000000000..7d857e7027 --- /dev/null +++ b/book/rust-docs/src/nimbus_cli/feature_utils.rs.html @@ -0,0 +1,217 @@ +feature_utils.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+
// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+use std::{
+    path::{Path, PathBuf},
+    time::{SystemTime, UNIX_EPOCH},
+};
+
+use anyhow::Result;
+use heck::ToKebabCase;
+use serde_json::{json, Value};
+
+use crate::{value_utils, NimbusApp};
+
+pub(crate) fn create_experiment(
+    app: &NimbusApp,
+    feature_id: &str,
+    files: &Vec<PathBuf>,
+) -> Result<Value> {
+    let mut branches = Vec::new();
+    for f in files {
+        branches.push(branch(feature_id, f)?);
+    }
+
+    let control = slug(files.first().unwrap())?;
+
+    let start = SystemTime::now();
+    let now = start
+        .duration_since(UNIX_EPOCH)
+        .expect("Time went backawards");
+
+    let user = whoami::username();
+    let slug = format!(
+        "{} test {} {:x}",
+        user,
+        feature_id,
+        now.as_secs() & ((1u64 << 16) - 1)
+    )
+    .to_kebab_case();
+
+    let app_name = app
+        .app_name()
+        .expect("An app name is expected. This is a bug in nimbus-cli");
+    Ok(json!({
+        "appId": &app_name,
+        "appName": &app_name,
+        "application": &app_name,
+        "arguments": {},
+        "branches": branches,
+        "bucketConfig": {
+            "count": 10_000,
+            "namespace": format!("{}-1", &slug),
+            "randomizationUnit": "nimbus_id",
+            "start": 0,
+            "total": 10_000
+        },
+        "channel": app.channel,
+        "endDate": null,
+        "enrollmentEndDate": null,
+        "featureIds": [
+          feature_id,
+        ],
+        "featureValidationOptOut": false,
+        "id": &slug,
+        "isEnrollmentPaused": false,
+        "isRollout": false,
+        "last_modified": now.as_secs() * 1000,
+        "outcomes": [],
+        "probeSets": [],
+        "proposedDuration": 7,
+        "proposedEnrollment": 7,
+        "referenceBranch": control,
+        "schemaVersion": "1.11.0",
+        "slug": &slug,
+        "startDate": null,
+        "targeting": "true",
+        "userFacingDescription": format!("Testing the {} feature from nimbus-cli", feature_id),
+        "userFacingName": format!("[{}] Testing {}", &user, feature_id)
+    }))
+}
+
+pub(crate) fn slug(path: &Path) -> Result<String> {
+    let filename = path
+        .file_stem()
+        .ok_or_else(|| anyhow::Error::msg("File has no filename"))?;
+    Ok(filename.to_string_lossy().to_string().to_kebab_case())
+}
+
+fn branch(feature_id: &str, file: &Path) -> Result<Value> {
+    let value: Value = value_utils::read_from_file(file)?;
+
+    let config = value.as_object().ok_or_else(|| {
+        anyhow::Error::msg(format!(
+            "{} does not contain a JSON object",
+            file.to_str().unwrap()
+        ))
+    })?;
+
+    Ok(json!({
+      "feature": {
+        "enabled": true,
+        "featureId": feature_id,
+        "value": config,
+      },
+      "slug": slug(file)?,
+    }))
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus_cli/main.rs.html b/book/rust-docs/src/nimbus_cli/main.rs.html new file mode 100644 index 0000000000..12c39f84a9 --- /dev/null +++ b/book/rust-docs/src/nimbus_cli/main.rs.html @@ -0,0 +1,3727 @@ +main.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
+540
+541
+542
+543
+544
+545
+546
+547
+548
+549
+550
+551
+552
+553
+554
+555
+556
+557
+558
+559
+560
+561
+562
+563
+564
+565
+566
+567
+568
+569
+570
+571
+572
+573
+574
+575
+576
+577
+578
+579
+580
+581
+582
+583
+584
+585
+586
+587
+588
+589
+590
+591
+592
+593
+594
+595
+596
+597
+598
+599
+600
+601
+602
+603
+604
+605
+606
+607
+608
+609
+610
+611
+612
+613
+614
+615
+616
+617
+618
+619
+620
+621
+622
+623
+624
+625
+626
+627
+628
+629
+630
+631
+632
+633
+634
+635
+636
+637
+638
+639
+640
+641
+642
+643
+644
+645
+646
+647
+648
+649
+650
+651
+652
+653
+654
+655
+656
+657
+658
+659
+660
+661
+662
+663
+664
+665
+666
+667
+668
+669
+670
+671
+672
+673
+674
+675
+676
+677
+678
+679
+680
+681
+682
+683
+684
+685
+686
+687
+688
+689
+690
+691
+692
+693
+694
+695
+696
+697
+698
+699
+700
+701
+702
+703
+704
+705
+706
+707
+708
+709
+710
+711
+712
+713
+714
+715
+716
+717
+718
+719
+720
+721
+722
+723
+724
+725
+726
+727
+728
+729
+730
+731
+732
+733
+734
+735
+736
+737
+738
+739
+740
+741
+742
+743
+744
+745
+746
+747
+748
+749
+750
+751
+752
+753
+754
+755
+756
+757
+758
+759
+760
+761
+762
+763
+764
+765
+766
+767
+768
+769
+770
+771
+772
+773
+774
+775
+776
+777
+778
+779
+780
+781
+782
+783
+784
+785
+786
+787
+788
+789
+790
+791
+792
+793
+794
+795
+796
+797
+798
+799
+800
+801
+802
+803
+804
+805
+806
+807
+808
+809
+810
+811
+812
+813
+814
+815
+816
+817
+818
+819
+820
+821
+822
+823
+824
+825
+826
+827
+828
+829
+830
+831
+832
+833
+834
+835
+836
+837
+838
+839
+840
+841
+842
+843
+844
+845
+846
+847
+848
+849
+850
+851
+852
+853
+854
+855
+856
+857
+858
+859
+860
+861
+862
+863
+864
+865
+866
+867
+868
+869
+870
+871
+872
+873
+874
+875
+876
+877
+878
+879
+880
+881
+882
+883
+884
+885
+886
+887
+888
+889
+890
+891
+892
+893
+894
+895
+896
+897
+898
+899
+900
+901
+902
+903
+904
+905
+906
+907
+908
+909
+910
+911
+912
+913
+914
+915
+916
+917
+918
+919
+920
+921
+922
+923
+924
+925
+926
+927
+928
+929
+930
+931
+932
+933
+934
+935
+936
+937
+938
+939
+940
+941
+942
+943
+944
+945
+946
+947
+948
+949
+950
+951
+952
+953
+954
+955
+956
+957
+958
+959
+960
+961
+962
+963
+964
+965
+966
+967
+968
+969
+970
+971
+972
+973
+974
+975
+976
+977
+978
+979
+980
+981
+982
+983
+984
+985
+986
+987
+988
+989
+990
+991
+992
+993
+994
+995
+996
+997
+998
+999
+1000
+1001
+1002
+1003
+1004
+1005
+1006
+1007
+1008
+1009
+1010
+1011
+1012
+1013
+1014
+1015
+1016
+1017
+1018
+1019
+1020
+1021
+1022
+1023
+1024
+1025
+1026
+1027
+1028
+1029
+1030
+1031
+1032
+1033
+1034
+1035
+1036
+1037
+1038
+1039
+1040
+1041
+1042
+1043
+1044
+1045
+1046
+1047
+1048
+1049
+1050
+1051
+1052
+1053
+1054
+1055
+1056
+1057
+1058
+1059
+1060
+1061
+1062
+1063
+1064
+1065
+1066
+1067
+1068
+1069
+1070
+1071
+1072
+1073
+1074
+1075
+1076
+1077
+1078
+1079
+1080
+1081
+1082
+1083
+1084
+1085
+1086
+1087
+1088
+1089
+1090
+1091
+1092
+1093
+1094
+1095
+1096
+1097
+1098
+1099
+1100
+1101
+1102
+1103
+1104
+1105
+1106
+1107
+1108
+1109
+1110
+1111
+1112
+1113
+1114
+1115
+1116
+1117
+1118
+1119
+1120
+1121
+1122
+1123
+1124
+1125
+1126
+1127
+1128
+1129
+1130
+1131
+1132
+1133
+1134
+1135
+1136
+1137
+1138
+1139
+1140
+1141
+1142
+1143
+1144
+1145
+1146
+1147
+1148
+1149
+1150
+1151
+1152
+1153
+1154
+1155
+1156
+1157
+1158
+1159
+1160
+1161
+1162
+1163
+1164
+1165
+1166
+1167
+1168
+1169
+1170
+1171
+1172
+1173
+1174
+1175
+1176
+1177
+1178
+1179
+1180
+1181
+1182
+1183
+1184
+1185
+1186
+1187
+1188
+1189
+1190
+1191
+1192
+1193
+1194
+1195
+1196
+1197
+1198
+1199
+1200
+1201
+1202
+1203
+1204
+1205
+1206
+1207
+1208
+1209
+1210
+1211
+1212
+1213
+1214
+1215
+1216
+1217
+1218
+1219
+1220
+1221
+1222
+1223
+1224
+1225
+1226
+1227
+1228
+1229
+1230
+1231
+1232
+1233
+1234
+1235
+1236
+1237
+1238
+1239
+1240
+1241
+1242
+1243
+1244
+1245
+1246
+1247
+1248
+1249
+1250
+1251
+1252
+1253
+1254
+1255
+1256
+1257
+1258
+1259
+1260
+1261
+1262
+1263
+1264
+1265
+1266
+1267
+1268
+1269
+1270
+1271
+1272
+1273
+1274
+1275
+1276
+1277
+1278
+1279
+1280
+1281
+1282
+1283
+1284
+1285
+1286
+1287
+1288
+1289
+1290
+1291
+1292
+1293
+1294
+1295
+1296
+1297
+1298
+1299
+1300
+1301
+1302
+1303
+1304
+1305
+1306
+1307
+1308
+1309
+1310
+1311
+1312
+1313
+1314
+1315
+1316
+1317
+1318
+1319
+1320
+1321
+1322
+1323
+1324
+1325
+1326
+1327
+1328
+1329
+1330
+1331
+1332
+1333
+1334
+1335
+1336
+1337
+1338
+1339
+1340
+1341
+1342
+1343
+1344
+1345
+1346
+1347
+1348
+1349
+1350
+1351
+1352
+1353
+1354
+1355
+1356
+1357
+1358
+1359
+1360
+1361
+1362
+1363
+1364
+1365
+1366
+1367
+1368
+1369
+1370
+1371
+1372
+1373
+1374
+1375
+1376
+1377
+1378
+1379
+1380
+1381
+1382
+1383
+1384
+1385
+1386
+1387
+1388
+1389
+1390
+1391
+1392
+1393
+1394
+1395
+1396
+1397
+1398
+1399
+1400
+1401
+1402
+1403
+1404
+1405
+1406
+1407
+1408
+1409
+1410
+1411
+1412
+1413
+1414
+1415
+1416
+1417
+1418
+1419
+1420
+1421
+1422
+1423
+1424
+1425
+1426
+1427
+1428
+1429
+1430
+1431
+1432
+1433
+1434
+1435
+1436
+1437
+1438
+1439
+1440
+1441
+1442
+1443
+1444
+1445
+1446
+1447
+1448
+1449
+1450
+1451
+1452
+1453
+1454
+1455
+1456
+1457
+1458
+1459
+1460
+1461
+1462
+1463
+1464
+1465
+1466
+1467
+1468
+1469
+1470
+1471
+1472
+1473
+1474
+1475
+1476
+1477
+1478
+1479
+1480
+1481
+1482
+1483
+1484
+1485
+1486
+1487
+1488
+1489
+1490
+1491
+1492
+1493
+1494
+1495
+1496
+1497
+1498
+1499
+1500
+1501
+1502
+1503
+1504
+1505
+1506
+1507
+1508
+1509
+1510
+1511
+1512
+1513
+1514
+1515
+1516
+1517
+1518
+1519
+1520
+1521
+1522
+1523
+1524
+1525
+1526
+1527
+1528
+1529
+1530
+1531
+1532
+1533
+1534
+1535
+1536
+1537
+1538
+1539
+1540
+1541
+1542
+1543
+1544
+1545
+1546
+1547
+1548
+1549
+1550
+1551
+1552
+1553
+1554
+1555
+1556
+1557
+1558
+1559
+1560
+1561
+1562
+1563
+1564
+1565
+1566
+1567
+1568
+1569
+1570
+1571
+1572
+1573
+1574
+1575
+1576
+1577
+1578
+1579
+1580
+1581
+1582
+1583
+1584
+1585
+1586
+1587
+1588
+1589
+1590
+1591
+1592
+1593
+1594
+1595
+1596
+1597
+1598
+1599
+1600
+1601
+1602
+1603
+1604
+1605
+1606
+1607
+1608
+1609
+1610
+1611
+1612
+1613
+1614
+1615
+1616
+1617
+1618
+1619
+1620
+1621
+1622
+1623
+1624
+1625
+1626
+1627
+1628
+1629
+1630
+1631
+1632
+1633
+1634
+1635
+1636
+1637
+1638
+1639
+1640
+1641
+1642
+1643
+1644
+1645
+1646
+1647
+1648
+1649
+1650
+1651
+1652
+1653
+1654
+1655
+1656
+1657
+1658
+1659
+1660
+1661
+1662
+1663
+1664
+1665
+1666
+1667
+1668
+1669
+1670
+1671
+1672
+1673
+1674
+1675
+1676
+1677
+1678
+1679
+1680
+1681
+1682
+1683
+1684
+1685
+1686
+1687
+1688
+1689
+1690
+1691
+1692
+1693
+1694
+1695
+1696
+1697
+1698
+1699
+1700
+1701
+1702
+1703
+1704
+1705
+1706
+1707
+1708
+1709
+1710
+1711
+1712
+1713
+1714
+1715
+1716
+1717
+1718
+1719
+1720
+1721
+1722
+1723
+1724
+1725
+1726
+1727
+1728
+1729
+1730
+1731
+1732
+1733
+1734
+1735
+1736
+1737
+1738
+1739
+1740
+1741
+1742
+1743
+1744
+1745
+1746
+1747
+1748
+1749
+1750
+1751
+1752
+1753
+1754
+1755
+1756
+1757
+1758
+1759
+1760
+1761
+1762
+1763
+1764
+1765
+1766
+1767
+1768
+1769
+1770
+1771
+1772
+1773
+1774
+1775
+1776
+1777
+1778
+1779
+1780
+1781
+1782
+1783
+1784
+1785
+1786
+1787
+1788
+1789
+1790
+1791
+1792
+1793
+1794
+1795
+1796
+1797
+1798
+1799
+1800
+1801
+1802
+1803
+1804
+1805
+1806
+1807
+1808
+1809
+1810
+1811
+1812
+1813
+1814
+1815
+1816
+1817
+1818
+1819
+1820
+1821
+1822
+1823
+1824
+1825
+1826
+1827
+1828
+1829
+1830
+1831
+1832
+1833
+1834
+1835
+1836
+1837
+1838
+1839
+1840
+1841
+1842
+1843
+1844
+1845
+1846
+1847
+1848
+1849
+1850
+1851
+1852
+1853
+1854
+1855
+1856
+1857
+1858
+1859
+1860
+1861
+1862
+1863
+
// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+mod cli;
+mod cmd;
+mod config;
+mod feature_utils;
+mod output;
+mod protocol;
+mod sources;
+mod updater;
+mod value_utils;
+
+use anyhow::{bail, Result};
+use clap::Parser;
+use cli::{Cli, CliCommand, ExperimentArgs, OpenArgs};
+use sources::{ExperimentListSource, ExperimentSource, ManifestSource};
+use std::{ffi::OsString, path::PathBuf};
+
+pub(crate) static USER_AGENT: &str =
+    concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),);
+
+fn main() -> Result<()> {
+    let cmds = get_commands_from_cli(std::env::args_os())?;
+    for c in cmds {
+        let success = cmd::process_cmd(&c)?;
+        if !success {
+            bail!("Failed");
+        }
+    }
+    updater::check_for_update();
+    Ok(())
+}
+
+fn get_commands_from_cli<I, T>(args: I) -> Result<Vec<AppCommand>>
+where
+    I: IntoIterator<Item = T>,
+    T: Into<OsString> + Clone,
+{
+    let cli = Cli::try_parse_from(args)?;
+
+    let mut commands: Vec<AppCommand> = Default::default();
+
+    // We do this here to ensure that all the command line is valid
+    // with respect to the main command. We do this here becasue
+    // as the cli has expanded, we've changed when we need `--app`
+    // and `--channel`. We catch those types of errors early by doing this
+    // here.
+    let main_command = AppCommand::try_from(&cli)?;
+
+    // Validating the command line args. Most of this should be done with clap,
+    // but for everything else there's:
+    cli.command.check_valid()?;
+
+    // Validating experiments against manifests
+    commands.push(AppCommand::try_validate(&cli)?);
+
+    if cli.command.should_kill() {
+        let app = LaunchableApp::try_from(&cli)?;
+        commands.push(AppCommand::Kill { app });
+    }
+    if cli.command.should_reset() {
+        let app = LaunchableApp::try_from(&cli)?;
+        commands.push(AppCommand::Reset { app });
+    }
+    commands.push(main_command);
+
+    Ok(commands)
+}
+
+#[derive(Clone, Debug, PartialEq)]
+enum LaunchableApp {
+    Android {
+        package_name: String,
+        activity_name: String,
+        device_id: Option<String>,
+        scheme: Option<String>,
+        open_deeplink: Option<String>,
+    },
+    Ios {
+        device_id: String,
+        app_id: String,
+        scheme: Option<String>,
+    },
+}
+
+#[derive(Clone, Debug, PartialEq)]
+pub(crate) struct NimbusApp {
+    app_name: Option<String>,
+    channel: Option<String>,
+}
+
+impl NimbusApp {
+    #[cfg(test)]
+    fn new(app: &str, channel: &str) -> Self {
+        Self {
+            app_name: Some(app.to_string()),
+            channel: Some(channel.to_string()),
+        }
+    }
+
+    fn channel(&self) -> Option<String> {
+        self.channel.clone()
+    }
+    fn app_name(&self) -> Option<String> {
+        self.app_name.clone()
+    }
+}
+
+impl From<&Cli> for NimbusApp {
+    fn from(value: &Cli) -> Self {
+        Self {
+            channel: value.channel.clone(),
+            app_name: value.app.clone(),
+        }
+    }
+}
+
+#[derive(Debug, PartialEq)]
+enum AppCommand {
+    ApplyFile {
+        app: LaunchableApp,
+        open: AppOpenArgs,
+        list: ExperimentListSource,
+        preserve_nimbus_db: bool,
+    },
+
+    CaptureLogs {
+        app: LaunchableApp,
+        file: PathBuf,
+    },
+
+    Defaults {
+        manifest: ManifestSource,
+        feature_id: Option<String>,
+        output: Option<PathBuf>,
+    },
+
+    Enroll {
+        app: LaunchableApp,
+        params: NimbusApp,
+        experiment: ExperimentSource,
+        rollouts: Vec<ExperimentSource>,
+        branch: String,
+        preserve_targeting: bool,
+        preserve_bucketing: bool,
+        preserve_nimbus_db: bool,
+        open: AppOpenArgs,
+    },
+
+    ExtractFeatures {
+        experiment: ExperimentSource,
+        branch: String,
+        manifest: ManifestSource,
+
+        feature_id: Option<String>,
+        validate: bool,
+        multi: bool,
+
+        output: Option<PathBuf>,
+    },
+
+    FetchList {
+        list: ExperimentListSource,
+        file: Option<PathBuf>,
+    },
+
+    FmlPassthrough {
+        args: Vec<OsString>,
+        cwd: PathBuf,
+    },
+
+    Info {
+        experiment: ExperimentSource,
+        output: Option<PathBuf>,
+    },
+
+    Kill {
+        app: LaunchableApp,
+    },
+
+    List {
+        list: ExperimentListSource,
+    },
+
+    LogState {
+        app: LaunchableApp,
+        open: AppOpenArgs,
+    },
+
+    // No Op, does nothing.
+    NoOp,
+
+    Open {
+        app: LaunchableApp,
+        open: AppOpenArgs,
+    },
+
+    Reset {
+        app: LaunchableApp,
+    },
+
+    #[cfg(feature = "server")]
+    StartServer,
+
+    TailLogs {
+        app: LaunchableApp,
+    },
+
+    Unenroll {
+        app: LaunchableApp,
+        open: AppOpenArgs,
+    },
+
+    ValidateExperiment {
+        params: NimbusApp,
+        manifest: ManifestSource,
+        experiment: ExperimentSource,
+    },
+}
+
+impl AppCommand {
+    fn try_validate(cli: &Cli) -> Result<Self> {
+        let params = cli.into();
+        Ok(match &cli.command {
+            CliCommand::Enroll {
+                no_validate,
+                manifest,
+                ..
+            }
+            | CliCommand::TestFeature {
+                no_validate,
+                manifest,
+                ..
+            } if !no_validate => {
+                let experiment = ExperimentSource::try_from(cli)?;
+                let manifest = ManifestSource::try_from(&params, manifest)?;
+                AppCommand::ValidateExperiment {
+                    params,
+                    experiment,
+                    manifest,
+                }
+            }
+            CliCommand::Validate { manifest, .. } => {
+                let experiment = ExperimentSource::try_from(cli)?;
+                let manifest = ManifestSource::try_from(&params, manifest)?;
+                AppCommand::ValidateExperiment {
+                    params,
+                    experiment,
+                    manifest,
+                }
+            }
+            _ => Self::NoOp,
+        })
+    }
+}
+
+impl TryFrom<&Cli> for AppCommand {
+    type Error = anyhow::Error;
+
+    fn try_from(cli: &Cli) -> Result<Self> {
+        let params = NimbusApp::from(cli);
+        Ok(match cli.command.clone() {
+            CliCommand::ApplyFile {
+                file,
+                preserve_nimbus_db,
+                open,
+            } => {
+                let app = LaunchableApp::try_from(cli)?;
+                let list = ExperimentListSource::try_from(file.as_path())?;
+                AppCommand::ApplyFile {
+                    app,
+                    open: open.into(),
+                    list,
+                    preserve_nimbus_db,
+                }
+            }
+            CliCommand::CaptureLogs { file } => {
+                let app = LaunchableApp::try_from(cli)?;
+                AppCommand::CaptureLogs { app, file }
+            }
+            CliCommand::Defaults {
+                feature_id,
+                output,
+                manifest,
+            } => {
+                let manifest = ManifestSource::try_from(&params, &manifest)?;
+                AppCommand::Defaults {
+                    manifest,
+                    feature_id,
+                    output,
+                }
+            }
+            CliCommand::Enroll {
+                branch,
+                rollouts,
+                preserve_targeting,
+                preserve_bucketing,
+                preserve_nimbus_db,
+                experiment,
+                open,
+                ..
+            } => {
+                let app = LaunchableApp::try_from(cli)?;
+                // Ensure we get the rollouts from the same place we get the experiment from.
+                let mut recipes: Vec<ExperimentSource> = Vec::new();
+                for r in rollouts {
+                    let rollout = ExperimentArgs {
+                        experiment: r,
+                        ..experiment.clone()
+                    };
+                    recipes.push(ExperimentSource::try_from(&rollout)?);
+                }
+
+                let experiment = ExperimentSource::try_from(cli)?;
+
+                Self::Enroll {
+                    app,
+                    params,
+                    experiment,
+                    branch,
+                    rollouts: recipes,
+                    preserve_targeting,
+                    preserve_bucketing,
+                    preserve_nimbus_db,
+                    open: open.into(),
+                }
+            }
+            CliCommand::Features {
+                manifest,
+                branch,
+                feature_id,
+                output,
+                validate,
+                multi,
+                ..
+            } => {
+                let manifest = ManifestSource::try_from(&params, &manifest)?;
+                let experiment = ExperimentSource::try_from(cli)?;
+                AppCommand::ExtractFeatures {
+                    experiment,
+                    branch,
+                    manifest,
+                    feature_id,
+                    validate,
+                    multi,
+                    output,
+                }
+            }
+            CliCommand::Fetch { output, .. } | CliCommand::FetchList { output, .. } => {
+                let list = ExperimentListSource::try_from(cli)?;
+
+                AppCommand::FetchList { list, file: output }
+            }
+            CliCommand::Fml { args } => {
+                let cwd = std::env::current_dir().expect("Current Working Directory is not set");
+                AppCommand::FmlPassthrough { args, cwd }
+            }
+            CliCommand::Info { experiment, output } => AppCommand::Info {
+                experiment: ExperimentSource::try_from(&experiment)?,
+                output,
+            },
+            CliCommand::List { .. } => {
+                let list = ExperimentListSource::try_from(cli)?;
+                AppCommand::List { list }
+            }
+            CliCommand::LogState { open } => {
+                let app = LaunchableApp::try_from(cli)?;
+                AppCommand::LogState {
+                    app,
+                    open: open.into(),
+                }
+            }
+            CliCommand::Open { open, .. } => {
+                let app = LaunchableApp::try_from(cli)?;
+                AppCommand::Open {
+                    app,
+                    open: open.into(),
+                }
+            }
+            #[cfg(feature = "server")]
+            CliCommand::StartServer => AppCommand::StartServer,
+            CliCommand::TailLogs => {
+                let app = LaunchableApp::try_from(cli)?;
+                AppCommand::TailLogs { app }
+            }
+            CliCommand::TestFeature { files, open, .. } => {
+                let app = LaunchableApp::try_from(cli)?;
+                let experiment = ExperimentSource::try_from(cli)?;
+                let first = files
+                    .first()
+                    .ok_or_else(|| anyhow::Error::msg("Need at least one file to make a branch"))?;
+                let branch = feature_utils::slug(first)?;
+
+                Self::Enroll {
+                    app,
+                    params,
+                    experiment,
+                    branch,
+                    rollouts: Default::default(),
+                    open: open.into(),
+                    preserve_targeting: false,
+                    preserve_bucketing: false,
+                    preserve_nimbus_db: false,
+                }
+            }
+            CliCommand::Unenroll { open } => {
+                let app = LaunchableApp::try_from(cli)?;
+                AppCommand::Unenroll {
+                    app,
+                    open: open.into(),
+                }
+            }
+            _ => Self::NoOp,
+        })
+    }
+}
+
+impl CliCommand {
+    fn check_valid(&self) -> Result<()> {
+        // Check validity of the OpenArgs.
+        if let Some(open) = self.open_args() {
+            if open.reset_app || !open.passthrough.is_empty() {
+                const ERR: &str = "does not work with --reset-app or passthrough args";
+                if open.pbcopy {
+                    bail!(format!("{} {}", "--pbcopy", ERR));
+                }
+                if open.pbpaste {
+                    bail!(format!("{} {}", "--pbpaste", ERR));
+                }
+                if open.output.is_some() {
+                    bail!(format!("{} {}", "--output", ERR));
+                }
+            }
+            if open.deeplink.is_some() {
+                const ERR: &str = "does not work with --deeplink";
+                if open.output.is_some() {
+                    bail!(format!("{} {}", "--output", ERR));
+                }
+            }
+        }
+        Ok(())
+    }
+
+    fn open_args(&self) -> Option<&OpenArgs> {
+        if let Self::ApplyFile { open, .. }
+        | Self::Open { open, .. }
+        | Self::Enroll { open, .. }
+        | Self::LogState { open, .. }
+        | Self::TestFeature { open, .. }
+        | Self::Unenroll { open, .. } = self
+        {
+            Some(open)
+        } else {
+            None
+        }
+    }
+
+    fn should_kill(&self) -> bool {
+        if let Some(open) = self.open_args() {
+            let using_links = open.pbcopy || open.pbpaste;
+            let output_to_file = open.output.is_some();
+            let no_clobber = if let Self::Open { no_clobber, .. } = self {
+                *no_clobber
+            } else {
+                false
+            };
+            !using_links && !no_clobber && !output_to_file
+        } else {
+            matches!(self, Self::ResetApp)
+        }
+    }
+
+    fn should_reset(&self) -> bool {
+        if let Some(open) = self.open_args() {
+            open.reset_app
+        } else {
+            matches!(self, Self::ResetApp)
+        }
+    }
+}
+
+#[derive(Debug, Default, PartialEq)]
+pub(crate) struct AppOpenArgs {
+    deeplink: Option<String>,
+    passthrough: Vec<String>,
+    pbcopy: bool,
+    pbpaste: bool,
+
+    output: Option<PathBuf>,
+}
+
+impl From<OpenArgs> for AppOpenArgs {
+    fn from(value: OpenArgs) -> Self {
+        Self {
+            deeplink: value.deeplink,
+            passthrough: value.passthrough,
+            pbcopy: value.pbcopy,
+            pbpaste: value.pbpaste,
+            output: value.output,
+        }
+    }
+}
+
+impl AppOpenArgs {
+    fn args(&self) -> (&[String], &[String]) {
+        let splits = &mut self.passthrough.splitn(2, |item| item == "{}");
+        match (splits.next(), splits.next()) {
+            (Some(first), Some(last)) => (first, last),
+            (None, Some(last)) | (Some(last), None) => (&[], last),
+            _ => (&[], &[]),
+        }
+    }
+}
+
+#[cfg(test)]
+mod unit_tests {
+    use crate::sources::ExperimentListFilter;
+
+    use super::*;
+
+    #[test]
+    fn test_launchable_app() -> Result<()> {
+        fn cli(app: &str, channel: &str) -> Cli {
+            Cli {
+                app: Some(app.to_string()),
+                channel: Some(channel.to_string()),
+                device_id: None,
+                command: CliCommand::ResetApp,
+            }
+        }
+        fn android(
+            package: &str,
+            activity: &str,
+            scheme: Option<&str>,
+            open_deeplink: Option<&str>,
+        ) -> LaunchableApp {
+            LaunchableApp::Android {
+                package_name: package.to_string(),
+                activity_name: activity.to_string(),
+                device_id: None,
+                scheme: scheme.map(str::to_string),
+                open_deeplink: open_deeplink.map(str::to_string),
+            }
+        }
+        fn ios(id: &str, scheme: Option<&str>) -> LaunchableApp {
+            LaunchableApp::Ios {
+                app_id: id.to_string(),
+                device_id: "booted".to_string(),
+                scheme: scheme.map(str::to_string),
+            }
+        }
+
+        // Firefox for Android, a.k.a. fenix
+        assert_eq!(
+            LaunchableApp::try_from(&cli("fenix", "developer"))?,
+            android(
+                "org.mozilla.fenix.debug",
+                ".App",
+                Some("fenix-dev"),
+                Some("open")
+            )
+        );
+        assert_eq!(
+            LaunchableApp::try_from(&cli("fenix", "nightly"))?,
+            android(
+                "org.mozilla.fenix",
+                ".App",
+                Some("fenix-nightly"),
+                Some("open")
+            )
+        );
+        assert_eq!(
+            LaunchableApp::try_from(&cli("fenix", "beta"))?,
+            android(
+                "org.mozilla.firefox_beta",
+                ".App",
+                Some("fenix-beta"),
+                Some("open")
+            )
+        );
+        assert_eq!(
+            LaunchableApp::try_from(&cli("fenix", "release"))?,
+            android("org.mozilla.firefox", ".App", Some("fenix"), Some("open"))
+        );
+
+        // Firefox for iOS
+        assert_eq!(
+            LaunchableApp::try_from(&cli("firefox_ios", "developer"))?,
+            ios("org.mozilla.ios.Fennec", Some("fennec"))
+        );
+        assert_eq!(
+            LaunchableApp::try_from(&cli("firefox_ios", "beta"))?,
+            ios("org.mozilla.ios.FirefoxBeta", Some("firefox-beta"))
+        );
+        assert_eq!(
+            LaunchableApp::try_from(&cli("firefox_ios", "release"))?,
+            ios("org.mozilla.ios.Firefox", Some("firefox-internal"))
+        );
+
+        // Focus for Android
+        assert_eq!(
+            LaunchableApp::try_from(&cli("focus_android", "developer"))?,
+            android(
+                "org.mozilla.focus.debug",
+                "org.mozilla.focus.activity.MainActivity",
+                None,
+                None,
+            )
+        );
+        assert_eq!(
+            LaunchableApp::try_from(&cli("focus_android", "nightly"))?,
+            android(
+                "org.mozilla.focus.nightly",
+                "org.mozilla.focus.activity.MainActivity",
+                None,
+                None,
+            )
+        );
+        assert_eq!(
+            LaunchableApp::try_from(&cli("focus_android", "beta"))?,
+            android(
+                "org.mozilla.focus.beta",
+                "org.mozilla.focus.activity.MainActivity",
+                None,
+                None,
+            )
+        );
+        assert_eq!(
+            LaunchableApp::try_from(&cli("focus_android", "release"))?,
+            android(
+                "org.mozilla.focus",
+                "org.mozilla.focus.activity.MainActivity",
+                None,
+                None,
+            )
+        );
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_split_args() -> Result<()> {
+        let mut open = AppOpenArgs {
+            passthrough: vec![],
+            ..Default::default()
+        };
+        let empty: &[String] = &[];
+        let expected = (empty, empty);
+        let observed = open.args();
+        assert_eq!(observed.0, expected.0);
+        assert_eq!(observed.1, expected.1);
+
+        open.passthrough = vec!["{}".to_string()];
+        let expected = (empty, empty);
+        let observed = open.args();
+        assert_eq!(observed.0, expected.0);
+        assert_eq!(observed.1, expected.1);
+
+        open.passthrough = vec!["foo".to_string(), "bar".to_string()];
+        let expected: (&[String], &[String]) = (empty, &["foo".to_string(), "bar".to_string()]);
+        let observed = open.args();
+        assert_eq!(observed.0, expected.0);
+        assert_eq!(observed.1, expected.1);
+
+        open.passthrough = vec!["foo".to_string(), "bar".to_string(), "{}".to_string()];
+        let expected: (&[String], &[String]) = (&["foo".to_string(), "bar".to_string()], empty);
+        let observed = open.args();
+        assert_eq!(observed.0, expected.0);
+        assert_eq!(observed.1, expected.1);
+
+        open.passthrough = vec!["foo".to_string(), "{}".to_string(), "bar".to_string()];
+        let expected: (&[String], &[String]) = (&["foo".to_string()], &["bar".to_string()]);
+        let observed = open.args();
+        assert_eq!(observed.0, expected.0);
+        assert_eq!(observed.1, expected.1);
+
+        open.passthrough = vec!["{}".to_string(), "foo".to_string(), "bar".to_string()];
+        let expected: (&[String], &[String]) = (empty, &["foo".to_string(), "bar".to_string()]);
+        let observed = open.args();
+        assert_eq!(observed.0, expected.0);
+        assert_eq!(observed.1, expected.1);
+
+        Ok(())
+    }
+
+    fn fenix() -> LaunchableApp {
+        LaunchableApp::Android {
+            package_name: "org.mozilla.fenix.debug".to_string(),
+            activity_name: ".App".to_string(),
+            device_id: None,
+            scheme: Some("fenix-dev".to_string()),
+            open_deeplink: Some("open".to_string()),
+        }
+    }
+
+    fn fenix_params() -> NimbusApp {
+        NimbusApp::new("fenix", "developer")
+    }
+
+    fn fenix_manifest() -> ManifestSource {
+        fenix_manifest_with_ref("main")
+    }
+
+    fn fenix_manifest_with_ref(ref_: &str) -> ManifestSource {
+        ManifestSource::FromGithub {
+            github_repo: "mozilla-mobile/firefox-android".to_string(),
+            ref_: ref_.to_string(),
+            manifest_file: "@mozilla-mobile/firefox-android/fenix/app/nimbus.fml.yaml".to_string(),
+            channel: "developer".to_string(),
+        }
+    }
+
+    fn manifest_from_file(file: &str) -> ManifestSource {
+        ManifestSource::FromFile {
+            channel: "developer".to_string(),
+            manifest_file: file.to_string(),
+        }
+    }
+
+    fn experiment(slug: &str) -> ExperimentSource {
+        let endpoint = config::api_v6_production_server();
+        ExperimentSource::FromApiV6 {
+            slug: slug.to_string(),
+            endpoint,
+        }
+    }
+
+    fn feature_experiment(feature_id: &str, files: &[&str]) -> ExperimentSource {
+        ExperimentSource::FromFeatureFiles {
+            app: fenix_params(),
+            feature_id: feature_id.to_string(),
+            files: files.iter().map(|f| f.into()).collect(),
+        }
+    }
+
+    fn with_deeplink(link: &str) -> AppOpenArgs {
+        AppOpenArgs {
+            deeplink: Some(link.to_string()),
+            ..Default::default()
+        }
+    }
+
+    fn with_pbcopy() -> AppOpenArgs {
+        AppOpenArgs {
+            pbcopy: true,
+            ..Default::default()
+        }
+    }
+
+    fn with_passthrough(params: &[&str]) -> AppOpenArgs {
+        AppOpenArgs {
+            passthrough: params.iter().map(|s| s.to_string()).collect(),
+            ..Default::default()
+        }
+    }
+
+    fn with_output(filename: &str) -> AppOpenArgs {
+        AppOpenArgs {
+            output: Some(PathBuf::from(filename)),
+            ..Default::default()
+        }
+    }
+
+    fn for_app(app: &str, list: ExperimentListSource) -> ExperimentListSource {
+        ExperimentListSource::Filtered {
+            filter: ExperimentListFilter::for_app(app),
+            inner: Box::new(list),
+        }
+    }
+
+    fn for_feature(feature: &str, list: ExperimentListSource) -> ExperimentListSource {
+        ExperimentListSource::Filtered {
+            filter: ExperimentListFilter::for_feature(feature),
+            inner: Box::new(list),
+        }
+    }
+
+    fn for_active_on_date(date: &str, list: ExperimentListSource) -> ExperimentListSource {
+        ExperimentListSource::Filtered {
+            filter: ExperimentListFilter::for_active_on(date),
+            inner: Box::new(list),
+        }
+    }
+
+    fn for_enrolling_on_date(date: &str, list: ExperimentListSource) -> ExperimentListSource {
+        ExperimentListSource::Filtered {
+            filter: ExperimentListFilter::for_enrolling_on(date),
+            inner: Box::new(list),
+        }
+    }
+
+    #[test]
+    fn test_enroll() -> Result<()> {
+        let observed = get_commands_from_cli([
+            "nimbus-cli",
+            "--app",
+            "fenix",
+            "--channel",
+            "developer",
+            "enroll",
+            "my-experiment",
+            "--branch",
+            "my-branch",
+            "--no-validate",
+        ])?;
+
+        let expected = vec![
+            AppCommand::NoOp,
+            AppCommand::Kill { app: fenix() },
+            AppCommand::Enroll {
+                app: fenix(),
+                params: fenix_params(),
+                experiment: experiment("my-experiment"),
+                rollouts: Default::default(),
+                branch: "my-branch".to_string(),
+                preserve_targeting: false,
+                preserve_bucketing: false,
+                preserve_nimbus_db: false,
+                open: Default::default(),
+            },
+        ];
+        assert_eq!(expected, observed);
+        Ok(())
+    }
+
+    #[test]
+    fn test_enroll_with_reset_app() -> Result<()> {
+        let observed = get_commands_from_cli([
+            "nimbus-cli",
+            "--app",
+            "fenix",
+            "--channel",
+            "developer",
+            "enroll",
+            "my-experiment",
+            "--branch",
+            "my-branch",
+            "--reset-app",
+            "--no-validate",
+        ])?;
+
+        let expected = vec![
+            AppCommand::NoOp,
+            AppCommand::Kill { app: fenix() },
+            AppCommand::Reset { app: fenix() },
+            AppCommand::Enroll {
+                app: fenix(),
+                params: fenix_params(),
+                experiment: experiment("my-experiment"),
+                rollouts: Default::default(),
+                branch: "my-branch".to_string(),
+                preserve_targeting: false,
+                preserve_bucketing: false,
+                preserve_nimbus_db: false,
+                open: Default::default(),
+            },
+        ];
+        assert_eq!(expected, observed);
+        Ok(())
+    }
+
+    #[test]
+    fn test_enroll_with_validate() -> Result<()> {
+        let observed = get_commands_from_cli([
+            "nimbus-cli",
+            "--app",
+            "fenix",
+            "--channel",
+            "developer",
+            "enroll",
+            "my-experiment",
+            "--branch",
+            "my-branch",
+            "--reset-app",
+        ])?;
+
+        let expected = vec![
+            AppCommand::ValidateExperiment {
+                params: fenix_params(),
+                manifest: fenix_manifest(),
+                experiment: experiment("my-experiment"),
+            },
+            AppCommand::Kill { app: fenix() },
+            AppCommand::Reset { app: fenix() },
+            AppCommand::Enroll {
+                app: fenix(),
+                params: fenix_params(),
+                experiment: experiment("my-experiment"),
+                rollouts: Default::default(),
+                branch: "my-branch".to_string(),
+                preserve_targeting: false,
+                preserve_bucketing: false,
+                preserve_nimbus_db: false,
+                open: Default::default(),
+            },
+        ];
+        assert_eq!(expected, observed);
+        Ok(())
+    }
+
+    #[test]
+    fn test_enroll_with_deeplink() -> Result<()> {
+        let observed = get_commands_from_cli([
+            "nimbus-cli",
+            "--app",
+            "fenix",
+            "--channel",
+            "developer",
+            "enroll",
+            "my-experiment",
+            "--branch",
+            "my-branch",
+            "--no-validate",
+            "--deeplink",
+            "host/path?key=value",
+        ])?;
+
+        let expected = vec![
+            AppCommand::NoOp,
+            AppCommand::Kill { app: fenix() },
+            AppCommand::Enroll {
+                app: fenix(),
+                params: fenix_params(),
+                experiment: experiment("my-experiment"),
+                rollouts: Default::default(),
+                branch: "my-branch".to_string(),
+                preserve_targeting: false,
+                preserve_bucketing: false,
+                preserve_nimbus_db: false,
+                open: with_deeplink("host/path?key=value"),
+            },
+        ];
+        assert_eq!(expected, observed);
+        Ok(())
+    }
+
+    #[test]
+    fn test_enroll_with_passthrough() -> Result<()> {
+        let observed = get_commands_from_cli([
+            "nimbus-cli",
+            "--app",
+            "fenix",
+            "--channel",
+            "developer",
+            "enroll",
+            "my-experiment",
+            "--branch",
+            "my-branch",
+            "--no-validate",
+            "--",
+            "--start-profiler",
+            "./profile.file",
+            "{}",
+            "--esn",
+            "TEST_FLAG",
+        ])?;
+
+        let expected = vec![
+            AppCommand::NoOp,
+            AppCommand::Kill { app: fenix() },
+            AppCommand::Enroll {
+                app: fenix(),
+                params: fenix_params(),
+                experiment: experiment("my-experiment"),
+                rollouts: Default::default(),
+                branch: "my-branch".to_string(),
+                preserve_targeting: false,
+                preserve_bucketing: false,
+                preserve_nimbus_db: false,
+                open: with_passthrough(&[
+                    "--start-profiler",
+                    "./profile.file",
+                    "{}",
+                    "--esn",
+                    "TEST_FLAG",
+                ]),
+            },
+        ];
+        assert_eq!(expected, observed);
+        Ok(())
+    }
+
+    #[test]
+    fn test_enroll_with_pbcopy() -> Result<()> {
+        let observed = get_commands_from_cli([
+            "nimbus-cli",
+            "--app",
+            "fenix",
+            "--channel",
+            "developer",
+            "enroll",
+            "my-experiment",
+            "--branch",
+            "my-branch",
+            "--no-validate",
+            "--pbcopy",
+        ])?;
+
+        let expected = vec![
+            AppCommand::NoOp,
+            AppCommand::Enroll {
+                app: fenix(),
+                params: fenix_params(),
+                experiment: experiment("my-experiment"),
+                rollouts: Default::default(),
+                branch: "my-branch".to_string(),
+                preserve_targeting: false,
+                preserve_bucketing: false,
+                preserve_nimbus_db: false,
+                open: with_pbcopy(),
+            },
+        ];
+        assert_eq!(expected, observed);
+        Ok(())
+    }
+
+    #[test]
+    fn test_enroll_with_output() -> Result<()> {
+        let observed = get_commands_from_cli([
+            "nimbus-cli",
+            "--app",
+            "fenix",
+            "--channel",
+            "developer",
+            "enroll",
+            "my-experiment",
+            "--branch",
+            "my-branch",
+            "--no-validate",
+            "--output",
+            "./file.json",
+        ])?;
+
+        let expected = vec![
+            AppCommand::NoOp,
+            AppCommand::Enroll {
+                app: fenix(),
+                params: fenix_params(),
+                experiment: experiment("my-experiment"),
+                rollouts: Default::default(),
+                branch: "my-branch".to_string(),
+                preserve_targeting: false,
+                preserve_bucketing: false,
+                preserve_nimbus_db: false,
+                open: with_output("./file.json"),
+            },
+        ];
+        assert_eq!(expected, observed);
+        Ok(())
+    }
+
+    #[test]
+    fn test_validate() -> Result<()> {
+        let observed = get_commands_from_cli([
+            "nimbus-cli",
+            "--app",
+            "fenix",
+            "--channel",
+            "developer",
+            "validate",
+            "my-experiment",
+        ])?;
+
+        let expected = vec![
+            AppCommand::ValidateExperiment {
+                params: fenix_params(),
+                manifest: fenix_manifest(),
+                experiment: experiment("my-experiment"),
+            },
+            AppCommand::NoOp,
+        ];
+        assert_eq!(expected, observed);
+
+        // With a specific version of the manifest.
+        let observed = get_commands_from_cli([
+            "nimbus-cli",
+            "--app",
+            "fenix",
+            "--channel",
+            "developer",
+            "validate",
+            "my-experiment",
+            "--version",
+            "114",
+        ])?;
+
+        let expected = vec![
+            AppCommand::ValidateExperiment {
+                params: fenix_params(),
+                manifest: fenix_manifest_with_ref("releases_v114"),
+                experiment: experiment("my-experiment"),
+            },
+            AppCommand::NoOp,
+        ];
+        assert_eq!(expected, observed);
+
+        // With a specific version of the manifest, via a ref.
+        let observed = get_commands_from_cli([
+            "nimbus-cli",
+            "--app",
+            "fenix",
+            "--channel",
+            "developer",
+            "validate",
+            "my-experiment",
+            "--ref",
+            "my-tag",
+        ])?;
+
+        let expected = vec![
+            AppCommand::ValidateExperiment {
+                params: fenix_params(),
+                manifest: fenix_manifest_with_ref("my-tag"),
+                experiment: experiment("my-experiment"),
+            },
+            AppCommand::NoOp,
+        ];
+        assert_eq!(expected, observed);
+
+        // With a file on disk
+        let observed = get_commands_from_cli([
+            "nimbus-cli",
+            "--channel",
+            "developer",
+            "validate",
+            "my-experiment",
+            "--manifest",
+            "./manifest.fml.yaml",
+        ])?;
+
+        let expected = vec![
+            AppCommand::ValidateExperiment {
+                params: NimbusApp {
+                    channel: Some("developer".to_string()),
+                    app_name: None,
+                },
+                manifest: manifest_from_file("./manifest.fml.yaml"),
+                experiment: experiment("my-experiment"),
+            },
+            AppCommand::NoOp,
+        ];
+        assert_eq!(expected, observed);
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_test_feature() -> Result<()> {
+        let observed = get_commands_from_cli([
+            "nimbus-cli",
+            "--app",
+            "fenix",
+            "--channel",
+            "developer",
+            "test-feature",
+            "my-feature",
+            "./my-branch.json",
+            "./my-treatment.json",
+        ])?;
+
+        let expected = vec![
+            AppCommand::ValidateExperiment {
+                params: fenix_params(),
+                manifest: fenix_manifest(),
+                experiment: feature_experiment(
+                    "my-feature",
+                    &["./my-branch.json", "./my-treatment.json"],
+                ),
+            },
+            AppCommand::Kill { app: fenix() },
+            AppCommand::Enroll {
+                app: fenix(),
+                params: fenix_params(),
+                experiment: feature_experiment(
+                    "my-feature",
+                    &["./my-branch.json", "./my-treatment.json"],
+                ),
+                rollouts: Default::default(),
+                branch: "my-branch".to_string(),
+                preserve_targeting: false,
+                preserve_bucketing: false,
+                preserve_nimbus_db: false,
+                open: Default::default(),
+            },
+        ];
+        assert_eq!(expected, observed);
+
+        // With a specific version of the manifest.
+        let observed = get_commands_from_cli([
+            "nimbus-cli",
+            "--app",
+            "fenix",
+            "--channel",
+            "developer",
+            "test-feature",
+            "my-feature",
+            "./my-branch.json",
+            "./my-treatment.json",
+            "--version",
+            "114",
+        ])?;
+
+        let expected = vec![
+            AppCommand::ValidateExperiment {
+                params: fenix_params(),
+                manifest: fenix_manifest_with_ref("releases_v114"),
+                experiment: feature_experiment(
+                    "my-feature",
+                    &["./my-branch.json", "./my-treatment.json"],
+                ),
+            },
+            AppCommand::Kill { app: fenix() },
+            AppCommand::Enroll {
+                app: fenix(),
+                params: fenix_params(),
+                experiment: feature_experiment(
+                    "my-feature",
+                    &["./my-branch.json", "./my-treatment.json"],
+                ),
+                rollouts: Default::default(),
+                branch: "my-branch".to_string(),
+                preserve_targeting: false,
+                preserve_bucketing: false,
+                preserve_nimbus_db: false,
+                open: Default::default(),
+            },
+        ];
+        assert_eq!(expected, observed);
+
+        // With a specific version of the manifest, via a ref.
+        let observed = get_commands_from_cli([
+            "nimbus-cli",
+            "--app",
+            "fenix",
+            "--channel",
+            "developer",
+            "test-feature",
+            "my-feature",
+            "./my-branch.json",
+            "./my-treatment.json",
+            "--ref",
+            "my-tag",
+        ])?;
+
+        let expected = vec![
+            AppCommand::ValidateExperiment {
+                params: fenix_params(),
+                manifest: fenix_manifest_with_ref("my-tag"),
+                experiment: feature_experiment(
+                    "my-feature",
+                    &["./my-branch.json", "./my-treatment.json"],
+                ),
+            },
+            AppCommand::Kill { app: fenix() },
+            AppCommand::Enroll {
+                app: fenix(),
+                params: fenix_params(),
+                experiment: feature_experiment(
+                    "my-feature",
+                    &["./my-branch.json", "./my-treatment.json"],
+                ),
+                rollouts: Default::default(),
+                branch: "my-branch".to_string(),
+                preserve_targeting: false,
+                preserve_bucketing: false,
+                preserve_nimbus_db: false,
+                open: Default::default(),
+            },
+        ];
+        assert_eq!(expected, observed);
+
+        // With a file on disk
+        let observed = get_commands_from_cli([
+            "nimbus-cli",
+            "--app",
+            "fenix",
+            "--channel",
+            "developer",
+            "test-feature",
+            "my-feature",
+            "./my-branch.json",
+            "./my-treatment.json",
+            "--manifest",
+            "./manifest.fml.yaml",
+        ])?;
+
+        let expected = vec![
+            AppCommand::ValidateExperiment {
+                params: fenix_params(),
+                manifest: manifest_from_file("./manifest.fml.yaml"),
+                experiment: feature_experiment(
+                    "my-feature",
+                    &["./my-branch.json", "./my-treatment.json"],
+                ),
+            },
+            AppCommand::Kill { app: fenix() },
+            AppCommand::Enroll {
+                app: fenix(),
+                params: fenix_params(),
+                experiment: feature_experiment(
+                    "my-feature",
+                    &["./my-branch.json", "./my-treatment.json"],
+                ),
+                rollouts: Default::default(),
+                branch: "my-branch".to_string(),
+                preserve_targeting: false,
+                preserve_bucketing: false,
+                preserve_nimbus_db: false,
+                open: Default::default(),
+            },
+        ];
+        assert_eq!(expected, observed);
+
+        let observed = get_commands_from_cli([
+            "nimbus-cli",
+            "--app",
+            "fenix",
+            "--channel",
+            "developer",
+            "test-feature",
+            "my-feature",
+            "./my-branch.json",
+            "./my-treatment.json",
+            "--no-validate",
+            "--deeplink",
+            "host/path?key=value",
+        ])?;
+
+        let expected = vec![
+            AppCommand::NoOp,
+            AppCommand::Kill { app: fenix() },
+            AppCommand::Enroll {
+                app: fenix(),
+                params: fenix_params(),
+                experiment: feature_experiment(
+                    "my-feature",
+                    &["./my-branch.json", "./my-treatment.json"],
+                ),
+                rollouts: Default::default(),
+                branch: "my-branch".to_string(),
+                preserve_targeting: false,
+                preserve_bucketing: false,
+                preserve_nimbus_db: false,
+                open: with_deeplink("host/path?key=value"),
+            },
+        ];
+        assert_eq!(expected, observed);
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_open() -> Result<()> {
+        let observed = get_commands_from_cli([
+            "nimbus-cli",
+            "--app",
+            "fenix",
+            "--channel",
+            "developer",
+            "open",
+        ])?;
+
+        let expected = vec![
+            AppCommand::NoOp,
+            AppCommand::Kill { app: fenix() },
+            AppCommand::Open {
+                app: fenix(),
+                open: Default::default(),
+            },
+        ];
+        assert_eq!(expected, observed);
+        Ok(())
+    }
+
+    #[test]
+    fn test_open_with_reset() -> Result<()> {
+        let observed = get_commands_from_cli([
+            "nimbus-cli",
+            "--app",
+            "fenix",
+            "--channel",
+            "developer",
+            "open",
+            "--reset-app",
+        ])?;
+
+        let expected = vec![
+            AppCommand::NoOp,
+            AppCommand::Kill { app: fenix() },
+            AppCommand::Reset { app: fenix() },
+            AppCommand::Open {
+                app: fenix(),
+                open: Default::default(),
+            },
+        ];
+        assert_eq!(expected, observed);
+        Ok(())
+    }
+
+    #[test]
+    fn test_open_with_deeplink() -> Result<()> {
+        let observed = get_commands_from_cli([
+            "nimbus-cli",
+            "--app",
+            "fenix",
+            "--channel",
+            "developer",
+            "open",
+            "--deeplink",
+            "host/path",
+        ])?;
+
+        let expected = vec![
+            AppCommand::NoOp,
+            AppCommand::Kill { app: fenix() },
+            AppCommand::Open {
+                app: fenix(),
+                open: with_deeplink("host/path"),
+            },
+        ];
+        assert_eq!(expected, observed);
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_open_with_passthrough_params() -> Result<()> {
+        let observed = get_commands_from_cli([
+            "nimbus-cli",
+            "--app",
+            "fenix",
+            "--channel",
+            "developer",
+            "open",
+            "--",
+            "--start-profiler",
+            "./profile.file",
+            "{}",
+            "--esn",
+            "TEST_FLAG",
+        ])?;
+
+        let expected = vec![
+            AppCommand::NoOp,
+            AppCommand::Kill { app: fenix() },
+            AppCommand::Open {
+                app: fenix(),
+                open: with_passthrough(&[
+                    "--start-profiler",
+                    "./profile.file",
+                    "{}",
+                    "--esn",
+                    "TEST_FLAG",
+                ]),
+            },
+        ];
+        assert_eq!(expected, observed);
+        Ok(())
+    }
+
+    #[test]
+    fn test_open_with_noclobber() -> Result<()> {
+        let observed = get_commands_from_cli([
+            "nimbus-cli",
+            "--app",
+            "fenix",
+            "--channel",
+            "developer",
+            "open",
+            "--no-clobber",
+        ])?;
+
+        let expected = vec![
+            AppCommand::NoOp,
+            AppCommand::Open {
+                app: fenix(),
+                open: Default::default(),
+            },
+        ];
+        assert_eq!(expected, observed);
+        Ok(())
+    }
+
+    #[test]
+    fn test_open_with_pbcopy() -> Result<()> {
+        let observed = get_commands_from_cli([
+            "nimbus-cli",
+            "--app",
+            "fenix",
+            "--channel",
+            "developer",
+            "open",
+            "--pbcopy",
+        ])?;
+
+        let expected = vec![
+            AppCommand::NoOp,
+            AppCommand::Open {
+                app: fenix(),
+                open: with_pbcopy(),
+            },
+        ];
+        assert_eq!(expected, observed);
+        Ok(())
+    }
+
+    #[test]
+    fn test_fetch() -> Result<()> {
+        let file = Some(PathBuf::from("./archived.json"));
+        let observed = get_commands_from_cli([
+            "nimbus-cli",
+            "--app",
+            "fenix",
+            "--channel",
+            "developer",
+            "fetch",
+            "--output",
+            "./archived.json",
+            "my-experiment",
+        ])?;
+
+        let expected = vec![
+            AppCommand::NoOp,
+            AppCommand::FetchList {
+                list: for_app(
+                    "fenix",
+                    ExperimentListSource::FromRecipes {
+                        recipes: vec![experiment("my-experiment")],
+                    },
+                ),
+                file: file.clone(),
+            },
+        ];
+        assert_eq!(expected, observed);
+
+        let observed = get_commands_from_cli([
+            "nimbus-cli",
+            "--app",
+            "fenix",
+            "--channel",
+            "developer",
+            "fetch",
+            "--output",
+            "./archived.json",
+            "my-experiment-1",
+            "my-experiment-2",
+        ])?;
+
+        let expected = vec![
+            AppCommand::NoOp,
+            AppCommand::FetchList {
+                list: for_app(
+                    "fenix",
+                    ExperimentListSource::FromRecipes {
+                        recipes: vec![experiment("my-experiment-1"), experiment("my-experiment-2")],
+                    },
+                ),
+                file,
+            },
+        ];
+        assert_eq!(expected, observed);
+        Ok(())
+    }
+
+    #[test]
+    fn test_fetch_list() -> Result<()> {
+        let file = Some(PathBuf::from("./archived.json"));
+        let observed =
+            get_commands_from_cli(["nimbus-cli", "fetch-list", "--output", "./archived.json"])?;
+
+        let expected = vec![
+            AppCommand::NoOp,
+            AppCommand::FetchList {
+                list: ExperimentListSource::FromRemoteSettings {
+                    endpoint: config::rs_production_server(),
+                    is_preview: false,
+                },
+                file: file.clone(),
+            },
+        ];
+        assert_eq!(expected, observed);
+
+        let observed = get_commands_from_cli([
+            "nimbus-cli",
+            "--app",
+            "fenix",
+            "fetch-list",
+            "--output",
+            "./archived.json",
+        ])?;
+
+        let expected = vec![
+            AppCommand::NoOp,
+            AppCommand::FetchList {
+                list: for_app(
+                    "fenix",
+                    ExperimentListSource::FromRemoteSettings {
+                        endpoint: config::rs_production_server(),
+                        is_preview: false,
+                    },
+                ),
+                file: file.clone(),
+            },
+        ];
+        assert_eq!(expected, observed);
+
+        let observed = get_commands_from_cli([
+            "nimbus-cli",
+            "fetch-list",
+            "--output",
+            "./archived.json",
+            "stage",
+        ])?;
+
+        let expected = vec![
+            AppCommand::NoOp,
+            AppCommand::FetchList {
+                list: ExperimentListSource::FromRemoteSettings {
+                    endpoint: config::rs_stage_server(),
+                    is_preview: false,
+                },
+                file: file.clone(),
+            },
+        ];
+        assert_eq!(expected, observed);
+
+        let observed = get_commands_from_cli([
+            "nimbus-cli",
+            "fetch-list",
+            "--output",
+            "./archived.json",
+            "preview",
+        ])?;
+
+        let expected = vec![
+            AppCommand::NoOp,
+            AppCommand::FetchList {
+                list: ExperimentListSource::FromRemoteSettings {
+                    endpoint: config::rs_production_server(),
+                    is_preview: true,
+                },
+                file: file.clone(),
+            },
+        ];
+        assert_eq!(expected, observed);
+
+        let observed = get_commands_from_cli([
+            "nimbus-cli",
+            "fetch-list",
+            "--output",
+            "./archived.json",
+            "--use-api",
+        ])?;
+
+        let expected = vec![
+            AppCommand::NoOp,
+            AppCommand::FetchList {
+                list: ExperimentListSource::FromApiV6 {
+                    endpoint: config::api_v6_production_server(),
+                },
+                file: file.clone(),
+            },
+        ];
+        assert_eq!(expected, observed);
+
+        let observed = get_commands_from_cli([
+            "nimbus-cli",
+            "fetch-list",
+            "--use-api",
+            "--output",
+            "./archived.json",
+            "stage",
+        ])?;
+
+        let expected = vec![
+            AppCommand::NoOp,
+            AppCommand::FetchList {
+                list: ExperimentListSource::FromApiV6 {
+                    endpoint: config::api_v6_stage_server(),
+                },
+                file,
+            },
+        ];
+        assert_eq!(expected, observed);
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_list() -> Result<()> {
+        let observed = get_commands_from_cli(["nimbus-cli", "list"])?;
+        let expected = vec![
+            AppCommand::NoOp,
+            AppCommand::List {
+                list: ExperimentListSource::FromRemoteSettings {
+                    endpoint: config::rs_production_server(),
+                    is_preview: false,
+                },
+            },
+        ];
+        assert_eq!(expected, observed);
+
+        let observed = get_commands_from_cli(["nimbus-cli", "list", "preview"])?;
+        let expected = vec![
+            AppCommand::NoOp,
+            AppCommand::List {
+                list: ExperimentListSource::FromRemoteSettings {
+                    endpoint: config::rs_production_server(),
+                    is_preview: true,
+                },
+            },
+        ];
+        assert_eq!(expected, observed);
+
+        let observed = get_commands_from_cli(["nimbus-cli", "list", "stage"])?;
+        let expected = vec![
+            AppCommand::NoOp,
+            AppCommand::List {
+                list: ExperimentListSource::FromRemoteSettings {
+                    endpoint: config::rs_stage_server(),
+                    is_preview: false,
+                },
+            },
+        ];
+        assert_eq!(expected, observed);
+
+        let observed = get_commands_from_cli(["nimbus-cli", "list", "--use-api", "stage"])?;
+        let expected = vec![
+            AppCommand::NoOp,
+            AppCommand::List {
+                list: ExperimentListSource::FromApiV6 {
+                    endpoint: config::api_v6_stage_server(),
+                },
+            },
+        ];
+        assert_eq!(expected, observed);
+
+        let observed = get_commands_from_cli(["nimbus-cli", "list", "--use-api"])?;
+        let expected = vec![
+            AppCommand::NoOp,
+            AppCommand::List {
+                list: ExperimentListSource::FromApiV6 {
+                    endpoint: config::api_v6_production_server(),
+                },
+            },
+        ];
+        assert_eq!(expected, observed);
+        Ok(())
+    }
+
+    #[test]
+    fn test_list_filter() -> Result<()> {
+        let observed = get_commands_from_cli(["nimbus-cli", "--app", "my-app", "list"])?;
+        let expected = vec![
+            AppCommand::NoOp,
+            AppCommand::List {
+                list: for_app(
+                    "my-app",
+                    ExperimentListSource::FromRemoteSettings {
+                        endpoint: config::rs_production_server(),
+                        is_preview: false,
+                    },
+                ),
+            },
+        ];
+        assert_eq!(expected, observed);
+
+        let observed = get_commands_from_cli(["nimbus-cli", "list", "--feature", "messaging"])?;
+        let expected = vec![
+            AppCommand::NoOp,
+            AppCommand::List {
+                list: for_feature(
+                    "messaging",
+                    ExperimentListSource::FromRemoteSettings {
+                        endpoint: config::rs_production_server(),
+                        is_preview: false,
+                    },
+                ),
+            },
+        ];
+        assert_eq!(expected, observed);
+
+        let observed = get_commands_from_cli([
+            "nimbus-cli",
+            "--app",
+            "my-app",
+            "list",
+            "--feature",
+            "messaging",
+        ])?;
+        let expected = vec![
+            AppCommand::NoOp,
+            AppCommand::List {
+                list: for_app(
+                    "my-app",
+                    for_feature(
+                        "messaging",
+                        ExperimentListSource::FromRemoteSettings {
+                            endpoint: config::rs_production_server(),
+                            is_preview: false,
+                        },
+                    ),
+                ),
+            },
+        ];
+        assert_eq!(expected, observed);
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_list_filter_by_date_with_error() -> Result<()> {
+        let observed = get_commands_from_cli(["nimbus-cli", "list", "--active-on", "FOO"]);
+        assert!(observed.is_err());
+        let err = observed.unwrap_err();
+        assert!(err.to_string().contains("Date string must be yyyy-mm-dd"));
+
+        let observed = get_commands_from_cli(["nimbus-cli", "list", "--enrolling-on", "FOO"]);
+        assert!(observed.is_err());
+        let err = observed.unwrap_err();
+        assert!(err.to_string().contains("Date string must be yyyy-mm-dd"));
+        Ok(())
+    }
+
+    #[test]
+    fn test_list_filter_by_dates() -> Result<()> {
+        let today = "1970-01-01";
+
+        let observed = get_commands_from_cli(["nimbus-cli", "list", "--active-on", today])?;
+        let expected = vec![
+            AppCommand::NoOp,
+            AppCommand::List {
+                list: for_active_on_date(
+                    today,
+                    ExperimentListSource::FromRemoteSettings {
+                        endpoint: config::rs_production_server(),
+                        is_preview: false,
+                    },
+                ),
+            },
+        ];
+        assert_eq!(expected, observed);
+
+        let observed = get_commands_from_cli(["nimbus-cli", "list", "--enrolling-on", today])?;
+        let expected = vec![
+            AppCommand::NoOp,
+            AppCommand::List {
+                list: for_enrolling_on_date(
+                    today,
+                    ExperimentListSource::FromRemoteSettings {
+                        endpoint: config::rs_production_server(),
+                        is_preview: false,
+                    },
+                ),
+            },
+        ];
+        assert_eq!(expected, observed);
+
+        Ok(())
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus_cli/output/deeplink.rs.html b/book/rust-docs/src/nimbus_cli/output/deeplink.rs.html new file mode 100644 index 0000000000..b70119a6e3 --- /dev/null +++ b/book/rust-docs/src/nimbus_cli/output/deeplink.rs.html @@ -0,0 +1,529 @@ +deeplink.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+
// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+use anyhow::{anyhow, Result};
+use percent_encoding::{AsciiSet, CONTROLS};
+
+use crate::protocol::StartAppProtocol;
+use crate::{AppOpenArgs, LaunchableApp};
+
+impl LaunchableApp {
+    pub(crate) fn copy_to_clipboard(
+        &self,
+        app_protocol: &StartAppProtocol,
+        open: &AppOpenArgs,
+    ) -> Result<usize> {
+        let url = self.longform_url(app_protocol, open)?;
+        let len = url.len();
+        if let Err(e) = set_clipboard(url) {
+            anyhow::bail!("Can't copy URL to clipboard: {}", e)
+        };
+
+        Ok(len)
+    }
+
+    pub(crate) fn longform_url(
+        &self,
+        app_protocol: &StartAppProtocol,
+        open: &AppOpenArgs,
+    ) -> Result<String> {
+        let deeplink = match (&open.deeplink, self.app_opening_deeplink()) {
+            (Some(deeplink), _) => deeplink.to_owned(),
+            (_, Some(deeplink)) => join_query(deeplink, "--nimbus-cli&--is-launcher"),
+            _ => anyhow::bail!("A deeplink must be provided"),
+        };
+
+        let url = longform_deeplink_url(deeplink.as_str(), app_protocol)?;
+
+        self.prepend_scheme(url.as_str())
+    }
+
+    fn app_opening_deeplink(&self) -> Option<&str> {
+        match self {
+            Self::Android { open_deeplink, .. } => open_deeplink.as_deref(),
+            Self::Ios { .. } => Some("noop"),
+        }
+    }
+
+    pub(crate) fn deeplink(&self, open: &AppOpenArgs) -> Result<Option<String>> {
+        let deeplink = &open.deeplink;
+        if deeplink.is_none() {
+            return Ok(None);
+        }
+        let deeplink = self.prepend_scheme(deeplink.as_ref().unwrap())?;
+        Ok(Some(deeplink))
+    }
+
+    fn prepend_scheme(&self, deeplink: &str) -> Result<String> {
+        Ok(if deeplink.contains("://") {
+            deeplink.to_string()
+        } else {
+            let scheme = self.mandatory_scheme()?;
+            format!("{scheme}://{deeplink}")
+        })
+    }
+
+    fn mandatory_scheme(&self) -> Result<&str> {
+        match self {
+            Self::Android { scheme, .. } | Self::Ios { scheme, .. } => scheme
+                .as_deref()
+                .ok_or_else(|| anyhow!("A scheme is not defined for this app")),
+        }
+    }
+}
+
+// The following are the special query percent encode set.
+// https://url.spec.whatwg.org/#query-percent-encode-set
+const QUERY: &AsciiSet = &CONTROLS
+    .add(b' ')
+    .add(b'"')
+    .add(b'<')
+    .add(b'>')
+    .add(b'#')
+    .add(b'\'')
+    // Additionally, we've added '{' and '}' to make  sure iOS simctl works with it.
+    .add(b'{')
+    .add(b'}')
+    // Then some belt and braces: we're quoting a single query attribute value.
+    .add(b':')
+    .add(b'/')
+    .add(b'?')
+    .add(b'&');
+
+/// Construct a URL from the deeplink and the protocol object.
+pub(crate) fn longform_deeplink_url(
+    deeplink: &str,
+    app_protocol: &StartAppProtocol,
+) -> Result<String> {
+    let StartAppProtocol {
+        reset_db,
+        experiments,
+        log_state,
+    } = app_protocol;
+    if !reset_db && experiments.is_none() && !log_state {
+        return Ok(deeplink.to_string());
+    }
+
+    let mut parts: Vec<_> = Default::default();
+    if !deeplink.contains("--nimbus-cli") {
+        parts.push("--nimbus-cli".to_string());
+    }
+    if let Some(v) = experiments {
+        let json = serde_json::to_string(v)?;
+        let string = percent_encoding::utf8_percent_encode(&json, QUERY).to_string();
+        parts.push(format!("--experiments={string}"));
+    }
+
+    if *reset_db {
+        parts.push("--reset-db".to_string());
+    }
+    if *log_state {
+        parts.push("--log-state".to_string());
+    }
+
+    Ok(join_query(deeplink, &parts.join("&")))
+}
+
+fn join_query(url: &str, item: &str) -> String {
+    let suffix = if url.contains('?') { '&' } else { '?' };
+    format!("{url}{suffix}{item}")
+}
+
+fn set_clipboard(contents: String) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
+    use copypasta::{ClipboardContext, ClipboardProvider};
+    let mut ctx = ClipboardContext::new()?;
+    ctx.set_contents(contents)?;
+    Ok(())
+}
+
+#[cfg(test)]
+mod unit_tests {
+
+    use super::*;
+    use serde_json::json;
+
+    #[test]
+    fn test_url_noop() -> Result<()> {
+        let p = StartAppProtocol {
+            reset_db: false,
+            experiments: None,
+            log_state: false,
+        };
+        assert_eq!("host".to_string(), longform_deeplink_url("host", &p)?);
+        assert_eq!(
+            "host?query=1".to_string(),
+            longform_deeplink_url("host?query=1", &p)?
+        );
+        Ok(())
+    }
+
+    #[test]
+    fn test_url_reset_db() -> Result<()> {
+        let p = StartAppProtocol {
+            reset_db: true,
+            experiments: None,
+            log_state: false,
+        };
+        assert_eq!(
+            "host?--nimbus-cli&--reset-db".to_string(),
+            longform_deeplink_url("host", &p)?
+        );
+        assert_eq!(
+            "host?query=1&--nimbus-cli&--reset-db".to_string(),
+            longform_deeplink_url("host?query=1", &p)?
+        );
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_url_log_state() -> Result<()> {
+        let p = StartAppProtocol {
+            reset_db: false,
+            experiments: None,
+            log_state: true,
+        };
+        assert_eq!(
+            "host?--nimbus-cli&--log-state".to_string(),
+            longform_deeplink_url("host", &p)?
+        );
+        assert_eq!(
+            "host?query=1&--nimbus-cli&--log-state".to_string(),
+            longform_deeplink_url("host?query=1", &p)?
+        );
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_url_experiments() -> Result<()> {
+        let v = json!({"data": []});
+        let p = StartAppProtocol {
+            reset_db: false,
+            experiments: Some(&v),
+            log_state: false,
+        };
+        assert_eq!(
+            "host?--nimbus-cli&--experiments=%7B%22data%22%3A[]%7D".to_string(),
+            longform_deeplink_url("host", &p)?
+        );
+        assert_eq!(
+            "host?query=1&--nimbus-cli&--experiments=%7B%22data%22%3A[]%7D".to_string(),
+            longform_deeplink_url("host?query=1", &p)?
+        );
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_deeplink_has_is_launcher_param_if_no_deeplink_is_specified() -> Result<()> {
+        let app =
+            LaunchableApp::try_from_app_channel_device(Some("fenix"), Some("developer"), None)?;
+
+        // No payload, or command line param for deeplink.
+        let payload: StartAppProtocol = Default::default();
+        let open: AppOpenArgs = Default::default();
+        assert_eq!(
+            "fenix-dev://open?--nimbus-cli&--is-launcher".to_string(),
+            app.longform_url(&payload, &open)?
+        );
+
+        // A command line param for deeplink.
+        let open = AppOpenArgs {
+            deeplink: Some("deeplink".to_string()),
+            ..Default::default()
+        };
+        assert_eq!(
+            "fenix-dev://deeplink".to_string(),
+            app.longform_url(&payload, &open)?
+        );
+
+        // A parameter from the payload, but no deeplink.
+        let payload = StartAppProtocol {
+            log_state: true,
+            ..Default::default()
+        };
+        assert_eq!(
+            "fenix-dev://open?--nimbus-cli&--is-launcher&--log-state".to_string(),
+            app.longform_url(&payload, &Default::default())?
+        );
+
+        // A deeplink from the command line, and an extra param from the payload.
+        let open = AppOpenArgs {
+            deeplink: Some("deeplink".to_string()),
+            ..Default::default()
+        };
+        assert_eq!(
+            "fenix-dev://deeplink?--nimbus-cli&--log-state".to_string(),
+            app.longform_url(&payload, &open)?
+        );
+
+        Ok(())
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus_cli/output/features.rs.html b/book/rust-docs/src/nimbus_cli/output/features.rs.html new file mode 100644 index 0000000000..a9af025215 --- /dev/null +++ b/book/rust-docs/src/nimbus_cli/output/features.rs.html @@ -0,0 +1,279 @@ +features.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+
// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+use std::path::Path;
+
+use anyhow::Result;
+use nimbus_fml::intermediate_representation::FeatureManifest;
+use serde_json::Value;
+
+use crate::{
+    sources::{ExperimentSource, ManifestSource},
+    value_utils::{self, CliUtils},
+};
+
+impl ManifestSource {
+    pub(crate) fn print_defaults<P>(
+        &self,
+        feature_id: Option<&String>,
+        output: Option<P>,
+    ) -> Result<bool>
+    where
+        P: AsRef<Path>,
+    {
+        let manifest: FeatureManifest = self.try_into()?;
+        let json = self.get_defaults_json(&manifest, feature_id)?;
+        value_utils::write_to_file_or_print(output, &json)?;
+        Ok(true)
+    }
+
+    fn get_defaults_json(
+        &self,
+        fm: &FeatureManifest,
+        feature_id: Option<&String>,
+    ) -> Result<Value> {
+        Ok(match feature_id {
+            Some(id) => {
+                let (_, feature) = fm.find_feature(id).ok_or_else(|| {
+                    anyhow::Error::msg(format!("Feature '{id}' does not exist in this manifest"))
+                })?;
+                feature.default_json()
+            }
+            _ => fm.default_json(),
+        })
+    }
+}
+
+impl ExperimentSource {
+    #[allow(clippy::too_many_arguments)]
+    pub(crate) fn print_features<P>(
+        &self,
+        branch: &String,
+        manifest_source: &ManifestSource,
+        feature_id: Option<&String>,
+        validate: bool,
+        multi: bool,
+        output: Option<P>,
+    ) -> Result<bool>
+    where
+        P: AsRef<Path>,
+    {
+        let json = self.get_features_json(manifest_source, feature_id, branch, validate, multi)?;
+        value_utils::write_to_file_or_print(output, &json)?;
+        Ok(true)
+    }
+
+    fn get_features_json(
+        &self,
+        manifest_source: &ManifestSource,
+        feature_id: Option<&String>,
+        branch: &String,
+        validate: bool,
+        multi: bool,
+    ) -> Result<Value> {
+        let value = self.try_into()?;
+
+        // Find the named branch.
+        let branches = value_utils::try_find_branches_from_experiment(&value)?;
+        let b = branches
+            .iter()
+            .find(|b| b.get_str("slug").unwrap() == branch)
+            .ok_or_else(|| anyhow::format_err!("Branch '{branch}' does not exist"))?;
+
+        // Find the features for this branch: there may be more than one.
+        let feature_values = value_utils::try_find_features_from_branch(b)?;
+
+        // Now extract the relevant features out of the branches.
+        let mut result = serde_json::value::Map::new();
+        for f in feature_values {
+            let id = f.get_str("featureId")?;
+            let value = f
+                .get("value")
+                .ok_or_else(|| anyhow::format_err!("Branch {branch} feature {id} has no value"))?;
+            match feature_id {
+                None => {
+                    // If the user hasn't specified a feature, then just add it.
+                    result.insert(id.to_string(), value.clone());
+                }
+                Some(feature_id) if feature_id == id => {
+                    // If the user has specified a feature, and this is it, then also add it.
+                    result.insert(id.to_string(), value.clone());
+                }
+                // Otherwise, the user has specified a feature, and this wasn't it.
+                _ => continue,
+            }
+        }
+
+        // By now: we have all the features that we need, and no more.
+
+        // If validating, then we should merge with the defaults from the manifest.
+        // If not, then nothing more is needed to be done: we're delivering the partial feature configuration.
+        if validate {
+            let fm: FeatureManifest = manifest_source.try_into()?;
+            let mut new = serde_json::value::Map::new();
+            for (id, value) in result {
+                let def = fm.validate_feature_config(&id, value)?;
+                new.insert(id.to_owned(), def.default_json());
+            }
+            result = new;
+        }
+
+        Ok(if !multi && result.len() == 1 {
+            // By default, if only a single feature is being displayed,
+            // we can output just the feature config.
+            match (result.values().find(|_| true), feature_id) {
+                (Some(v), _) => v.to_owned(),
+                (_, Some(id)) => anyhow::bail!(
+                    "The '{id}' feature is not involved in '{branch}' branch of '{self}'"
+                ),
+                (_, _) => {
+                    anyhow::bail!("No features available in '{branch}' branch of '{self}'")
+                }
+            }
+        } else {
+            // Otherwise, we can output the `{ featureId: featureValue }` in its entirety.
+            Value::Object(result)
+        })
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus_cli/output/fetch.rs.html b/book/rust-docs/src/nimbus_cli/output/fetch.rs.html new file mode 100644 index 0000000000..bd7c48cfb5 --- /dev/null +++ b/book/rust-docs/src/nimbus_cli/output/fetch.rs.html @@ -0,0 +1,45 @@ +fetch.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+
// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+use std::path::Path;
+
+use anyhow::Result;
+use serde_json::Value;
+
+use crate::{sources::ExperimentListSource, value_utils};
+
+impl ExperimentListSource {
+    pub(crate) fn fetch_list<P>(&self, file: Option<P>) -> Result<bool>
+    where
+        P: AsRef<Path>,
+    {
+        let value: Value = self.try_into()?;
+        value_utils::write_to_file_or_print(file, &value)?;
+
+        Ok(true)
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus_cli/output/fml_cli.rs.html b/book/rust-docs/src/nimbus_cli/output/fml_cli.rs.html new file mode 100644 index 0000000000..5ad8f91dd1 --- /dev/null +++ b/book/rust-docs/src/nimbus_cli/output/fml_cli.rs.html @@ -0,0 +1,69 @@ +fml_cli.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+
// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+use std::{
+    ffi::{OsStr, OsString},
+    path::Path,
+    vec,
+};
+
+use anyhow::Result;
+use nimbus_fml::command_line::do_main;
+
+pub(crate) fn fml_cli(args: &Vec<OsString>, cwd: &Path) -> Result<bool> {
+    // We prepend the string `nimbus-cli fml` to the args to pass to FML
+    // because the clap uses the 0th argument for help messages; so the FML's command line processor
+    // will report an error with a usage message of `nimbus-cli fml generate [FLAGS] INPUT OUTPUT`.
+    let first = OsStr::new("nimbus-cli fml").to_os_string();
+    let mut cli_args = vec![&first];
+
+    let help = OsStr::new("--help").to_os_string();
+    if args.is_empty() {
+        // If the user has just typed `nimbus-cli fml`– with no furher arguments— then the rather unhelpful message
+        // `not implemented: Command  not implemented` is displayed. This will change if and when we upgrade the nimbus-fml
+        // to use cli-derive, but until then, we can do a simple thing to make the experience a bit nicer, by adding
+        // the `--help` flag, so the user gets the nimbus-fml command line help.
+        cli_args.push(&help);
+    }
+
+    // Finally, send all the args after `nimbus-cli fml` verbatim to the FML clap cli.
+    cli_args.extend(args);
+    do_main(cli_args, cwd)?;
+    Ok(true)
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus_cli/output/info.rs.html b/book/rust-docs/src/nimbus_cli/output/info.rs.html new file mode 100644 index 0000000000..54cdf93a01 --- /dev/null +++ b/book/rust-docs/src/nimbus_cli/output/info.rs.html @@ -0,0 +1,739 @@ +info.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+
// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+use std::{fmt::Display, path::Path};
+
+use anyhow::Result;
+use console::Term;
+use serde_json::Value;
+
+use crate::{
+    sources::{ExperimentListSource, ExperimentSource},
+    value_utils::{self, CliUtils},
+};
+
+#[derive(serde::Serialize, Debug, Default)]
+pub(crate) struct ExperimentInfo<'a> {
+    pub(crate) slug: &'a str,
+    pub(crate) app_name: &'a str,
+    pub(crate) channel: &'a str,
+    pub(crate) branches: Vec<&'a str>,
+    pub(crate) features: Vec<&'a str>,
+    pub(crate) targeting: &'a str,
+    pub(crate) bucketing: u64,
+    pub(crate) is_rollout: bool,
+    pub(crate) user_facing_name: &'a str,
+    pub(crate) user_facing_description: &'a str,
+    pub(crate) enrollment: DateRange<'a>,
+    pub(crate) is_enrollment_paused: bool,
+    pub(crate) duration: DateRange<'a>,
+}
+
+impl<'a> ExperimentInfo<'a> {
+    pub(crate) fn enrollment(&self) -> &DateRange<'a> {
+        &self.enrollment
+    }
+
+    pub(crate) fn active(&self) -> &DateRange<'a> {
+        &self.duration
+    }
+
+    fn bucketing_percent(&self) -> String {
+        format!("{: >3.0} %", self.bucketing / 100)
+    }
+}
+
+#[derive(serde::Serialize, Debug, Default)]
+pub(crate) struct DateRange<'a> {
+    start: Option<&'a str>,
+    end: Option<&'a str>,
+    proposed: Option<i64>,
+}
+
+impl<'a> DateRange<'a> {
+    fn new(start: Option<&'a Value>, end: Option<&'a Value>, duration: Option<&'a Value>) -> Self {
+        let start = start.map(Value::as_str).unwrap_or_default();
+        let end = end.map(Value::as_str).unwrap_or_default();
+        let proposed = duration.map(Value::as_i64).unwrap_or_default();
+        Self {
+            start,
+            end,
+            proposed,
+        }
+    }
+
+    pub(crate) fn contains(&self, date: &str) -> bool {
+        let start = self.start.unwrap_or("9999-99-99");
+        let end = self.end.unwrap_or("9999-99-99");
+
+        start <= date && date <= end
+    }
+}
+
+impl Display for DateRange<'_> {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match (self.start, self.end, self.proposed) {
+            (Some(s), Some(e), _) => f.write_str(&format!("{s} ➞ {e}")),
+            (Some(s), _, Some(d)) => f.write_str(&format!("{s}, proposed ending after {d} days")),
+            (Some(s), _, _) => f.write_str(&format!("{s} ➞ ?")),
+            (None, Some(e), Some(d)) => {
+                f.write_str(&format!("ending {e}, started {d} days before"))
+            }
+            (None, Some(e), _) => f.write_str(&format!("ending {e}")),
+            _ => f.write_str("unknown"),
+        }
+    }
+}
+
+impl<'a> TryFrom<&'a Value> for ExperimentInfo<'a> {
+    type Error = anyhow::Error;
+
+    fn try_from(exp: &'a Value) -> Result<Self> {
+        let features: Vec<_> = exp
+            .get_array("featureIds")?
+            .iter()
+            .flat_map(|f| f.as_str())
+            .collect();
+        let branches: Vec<_> = exp
+            .get_array("branches")?
+            .iter()
+            .flat_map(|b| {
+                b.get("slug")
+                    .expect("Expecting a branch with a slug")
+                    .as_str()
+            })
+            .collect();
+
+        let config = exp.get_object("bucketConfig")?;
+
+        Ok(Self {
+            slug: exp.get_str("slug")?,
+            app_name: exp.get_str("appName")?,
+            channel: exp.get_str("channel")?,
+            branches,
+            features,
+            targeting: exp.get_str("targeting")?,
+            bucketing: config.get_u64("count")?,
+            is_rollout: exp.get_bool("isRollout")?,
+            user_facing_name: exp.get_str("userFacingName")?,
+            user_facing_description: exp.get_str("userFacingDescription")?,
+            enrollment: DateRange::new(
+                exp.get("startDate"),
+                exp.get("enrollmentEndDate"),
+                exp.get("proposedEnrollment"),
+            ),
+            is_enrollment_paused: exp.get_bool("isEnrollmentPaused")?,
+            duration: DateRange::new(
+                exp.get("startDate"),
+                exp.get("endDate"),
+                exp.get("proposedDuration"),
+            ),
+        })
+    }
+}
+
+impl ExperimentListSource {
+    pub(crate) fn print_list(&self) -> Result<bool> {
+        let value: Value = self.try_into()?;
+        let array = value_utils::try_extract_data_list(&value)?;
+
+        let term = Term::stdout();
+        let style = term.style().italic().underlined();
+        term.write_line(&format!(
+            "{slug: <66}|{channel: <9}|{bucketing: >7}|{features: <31}|{is_rollout}|{branches: <20}",
+            slug = style.apply_to("Experiment slug"),
+            channel = style.apply_to(" Channel"),
+            bucketing = style.apply_to(" % "),
+            features = style.apply_to(" Features"),
+            is_rollout = style.apply_to("   "),
+            branches = style.apply_to(" Branches"),
+        ))?;
+        for exp in array {
+            let info = match ExperimentInfo::try_from(&exp) {
+                Ok(e) => e,
+                _ => continue,
+            };
+
+            let is_rollout = if info.is_rollout { "R" } else { "" };
+
+            term.write_line(&format!(
+                " {slug: <65}| {channel: <8}| {bucketing: >5} | {features: <30}| {is_rollout: <1} | {branches}",
+                slug = info.slug,
+                channel = info.channel,
+                bucketing = info.bucketing_percent(),
+                features = info.features.join(", "),
+                branches = info.branches.join(", ")
+            ))?;
+        }
+        Ok(true)
+    }
+}
+
+impl ExperimentSource {
+    pub(crate) fn print_info<P>(&self, output: Option<P>) -> Result<bool>
+    where
+        P: AsRef<Path>,
+    {
+        let value = self.try_into()?;
+        let info: ExperimentInfo = ExperimentInfo::try_from(&value)?;
+        if output.is_some() {
+            value_utils::write_to_file_or_print(output, &info)?;
+            return Ok(true);
+        }
+        let url = match self {
+            Self::FromApiV6 { slug, endpoint } => Some(format!("{endpoint}/nimbus/{slug}/summary")),
+            _ => None,
+        };
+        let term = Term::stdout();
+        let t_style = term.style().italic();
+        let d_style = term.style().bold().cyan();
+        let line = |title: &str, detail: &str| {
+            _ = term.write_line(&format!(
+                "{: <11} {}",
+                t_style.apply_to(title),
+                d_style.apply_to(detail)
+            ));
+        };
+
+        let enrollment = format!(
+            "{} ({})",
+            info.enrollment,
+            if info.is_enrollment_paused {
+                "paused"
+            } else {
+                "enrolling"
+            }
+        );
+
+        let is_rollout = if info.is_rollout {
+            "Rollout".to_string()
+        } else {
+            let n = info.branches.len();
+            let b = if n == 1 {
+                "1 branch".to_string()
+            } else {
+                format!("{n} branches")
+            };
+            format!("Experiment with {b}")
+        };
+
+        line("Slug", info.slug);
+        line("Name", info.user_facing_name);
+        line("Description", info.user_facing_description);
+        if let Some(url) = url {
+            line("URL", &url);
+        }
+        line("App", info.app_name);
+        line("Channel", info.channel);
+        line("E/R", &is_rollout);
+        line("Enrollment", &enrollment);
+        line("Observing", &info.duration.to_string());
+        line("Targeting", &format!("\"{}\"", info.targeting));
+        line("Bucketing", &info.bucketing_percent());
+        line("Branches", &info.branches.join(", "));
+        line("Features", &info.features.join(", "));
+
+        Ok(true)
+    }
+}
+
+#[cfg(test)]
+mod unit_tests {
+    use serde_json::json;
+
+    use super::*;
+
+    impl<'a> DateRange<'a> {
+        pub(crate) fn from_str(start: &'a str, end: &'a str, duration: i64) -> Self {
+            Self {
+                start: Some(start),
+                end: Some(end),
+                proposed: Some(duration),
+            }
+        }
+    }
+
+    #[test]
+    fn test_date_range_to_string() -> Result<()> {
+        let from = json!("2023-06-01");
+        let to = json!("2023-06-19");
+        let null = json!(null);
+        let days28 = json!(28);
+
+        let dr = DateRange::new(Some(&null), Some(&null), Some(&null));
+        let expected = "unknown".to_string();
+        let observed = dr.to_string();
+        assert_eq!(expected, observed);
+
+        let dr = DateRange::new(Some(&null), Some(&null), Some(&days28));
+        let expected = "unknown".to_string();
+        let observed = dr.to_string();
+        assert_eq!(expected, observed);
+
+        let dr = DateRange::new(Some(&null), Some(&to), Some(&null));
+        let expected = "ending 2023-06-19".to_string();
+        let observed = dr.to_string();
+        assert_eq!(expected, observed);
+
+        let dr = DateRange::new(Some(&null), Some(&to), Some(&days28));
+        let expected = "ending 2023-06-19, started 28 days before".to_string();
+        let observed = dr.to_string();
+        assert_eq!(expected, observed);
+
+        let dr = DateRange::new(Some(&from), Some(&null), Some(&null));
+        let expected = "2023-06-01 ➞ ?".to_string();
+        let observed = dr.to_string();
+        assert_eq!(expected, observed);
+
+        let dr = DateRange::new(Some(&from), Some(&null), Some(&days28));
+        let expected = "2023-06-01, proposed ending after 28 days".to_string();
+        let observed = dr.to_string();
+        assert_eq!(expected, observed);
+
+        let dr = DateRange::new(Some(&from), Some(&to), Some(&null));
+        let expected = "2023-06-01 ➞ 2023-06-19".to_string();
+        let observed = dr.to_string();
+        assert_eq!(expected, observed);
+
+        let dr = DateRange::new(Some(&from), Some(&to), Some(&days28));
+        let expected = "2023-06-01 ➞ 2023-06-19".to_string();
+        let observed = dr.to_string();
+        assert_eq!(expected, observed);
+        Ok(())
+    }
+
+    #[test]
+    fn test_date_range_contains() -> Result<()> {
+        let from = json!("2023-06-01");
+        let to = json!("2023-06-19");
+        let null = json!(null);
+
+        let before = "2023-05-01";
+        let during = "2023-06-03";
+        let after = "2023-06-20";
+
+        let dr = DateRange::new(Some(&null), Some(&null), Some(&null));
+        assert!(!dr.contains(before));
+        assert!(!dr.contains(during));
+        assert!(!dr.contains(after));
+
+        let dr = DateRange::new(Some(&null), Some(&to), Some(&null));
+        assert!(!dr.contains(before));
+        assert!(!dr.contains(during));
+        assert!(!dr.contains(after));
+
+        let dr = DateRange::new(Some(&from), Some(&null), Some(&null));
+        assert!(!dr.contains(before));
+        assert!(dr.contains(during));
+        assert!(dr.contains(after));
+
+        let dr = DateRange::new(Some(&from), Some(&to), Some(&null));
+        assert!(!dr.contains(before));
+        assert!(dr.contains(during));
+        assert!(!dr.contains(after));
+
+        Ok(())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_experiment_info() -> Result<()> {
+        let exp = ExperimentSource::from_fixture("fenix-nimbus-validation-v3.json");
+        let value: Value = Value::try_from(&exp)?;
+
+        let info = ExperimentInfo::try_from(&value)?;
+
+        assert_eq!("fenix-nimbus-validation-v3", info.slug);
+        assert_eq!("Fenix Nimbus Validation v3", info.user_facing_name);
+        assert_eq!(
+            "Verify we can run A/A experiments and bucket.",
+            info.user_facing_description
+        );
+        assert_eq!("fenix", info.app_name);
+        assert_eq!("nightly", info.channel);
+        assert!(!info.is_rollout);
+        assert!(!info.is_enrollment_paused);
+        assert_eq!("true", info.targeting);
+        assert_eq!(8000, info.bucketing);
+        assert_eq!(" 80 %", info.bucketing_percent());
+        assert_eq!(vec!["a1", "a2"], info.branches);
+        assert_eq!(vec!["no-feature-fenix"], info.features);
+
+        Ok(())
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus_cli/output/mod.rs.html b/book/rust-docs/src/nimbus_cli/output/mod.rs.html new file mode 100644 index 0000000000..e3c273706a --- /dev/null +++ b/book/rust-docs/src/nimbus_cli/output/mod.rs.html @@ -0,0 +1,27 @@ +mod.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+
// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+pub(crate) mod deeplink;
+mod features;
+mod fetch;
+mod fml_cli;
+pub(crate) mod info;
+#[cfg(feature = "server")]
+pub(crate) mod server;
+
+pub(crate) use fml_cli::fml_cli;
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus_cli/output/server.rs.html b/book/rust-docs/src/nimbus_cli/output/server.rs.html new file mode 100644 index 0000000000..bae15cb9fb --- /dev/null +++ b/book/rust-docs/src/nimbus_cli/output/server.rs.html @@ -0,0 +1,829 @@ +server.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+
// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+use anyhow::Result;
+use reqwest::StatusCode;
+use serde::{Deserialize, Serialize};
+use std::sync::RwLock;
+use std::{
+    collections::HashMap,
+    net::{IpAddr, SocketAddr},
+    sync::Arc,
+};
+
+use crate::config;
+
+use anyhow::anyhow;
+use axum::{
+    extract::{Path, State},
+    http,
+    response::{Html, IntoResponse},
+    routing::{get, post, IntoMakeService},
+    Json, Router, Server,
+};
+use hyper::server::conn::AddrIncoming;
+use serde_json::Value;
+use tower::layer::util::Stack;
+use tower_http::set_header::SetResponseHeaderLayer;
+use tower_livereload::{LiveReloadLayer, Reloader};
+
+fn create_server(
+    livereload: LiveReloadLayer,
+    state: Db,
+) -> Result<Server<AddrIncoming, IntoMakeService<Router>>, anyhow::Error> {
+    let app = create_app(livereload, state);
+
+    let addr = get_address()?;
+    eprintln!("Copy the address http://{}/ into your mobile browser", addr);
+
+    let server = Server::try_bind(&addr)?.serve(app.into_make_service());
+
+    Ok(server)
+}
+
+fn create_app(livereload: LiveReloadLayer, state: Db) -> Router {
+    Router::new()
+        .route("/", get(index))
+        .route("/style.css", get(style))
+        .route("/script.js", get(script))
+        .route("/post", post(post_handler))
+        .route("/buckets/:bucket/collections/:collection/records", get(rs))
+        .route(
+            "/v1/buckets/:bucket/collections/:collection/records",
+            get(rs),
+        )
+        .layer(livereload)
+        .layer(no_cache_layer())
+        .with_state(state)
+}
+
+fn create_state(livereload: &LiveReloadLayer) -> Db {
+    let reloader = livereload.reloader();
+    Arc::new(RwLock::new(InMemoryDb::new(reloader)))
+}
+
+#[tokio::main]
+pub(crate) async fn start_server() -> Result<bool> {
+    let livereload = LiveReloadLayer::new();
+    let state = create_state(&livereload);
+    let server = create_server(livereload, state)?;
+    server.await?;
+    Ok(true)
+}
+
+pub(crate) fn post_deeplink(
+    platform: &str,
+    deeplink: &str,
+    experiments: Option<&Value>,
+) -> Result<bool> {
+    let payload = StartAppPostPayload::new(platform, deeplink, experiments);
+    let addr = get_address()?;
+    let _ret = post_payload(&payload, &addr.to_string())?;
+    Ok(true)
+}
+
+type Db = Arc<RwLock<InMemoryDb>>;
+
+pub(crate) fn get_address() -> Result<SocketAddr> {
+    let host = config::server_host();
+    let port = config::server_port();
+
+    let port = port
+        .parse::<u16>()
+        .map_err(|_| anyhow!("NIMBUS_CLI_SERVER_PORT must be numeric"))?;
+    let host = host
+        .parse::<IpAddr>()
+        .map_err(|_| anyhow!("NIMBUS_CLI_SERVER_HOST must be an IP address"))?;
+
+    Ok((host, port).into())
+}
+
+async fn index(State(db): State<Db>) -> Html<String> {
+    let mut html =
+        include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/index.html")).to_string();
+    let li_template = include_str!(concat!(
+        env!("CARGO_MANIFEST_DIR"),
+        "/assets/li-template.html"
+    ));
+
+    let state = db.write().unwrap();
+    for p in ["android", "ios", "web"] {
+        let ppat = format!("{{{p}}}");
+        match state.url(p) {
+            Some(url) => {
+                let li = li_template.replace("{platform}", p).replace("{url}", url);
+                html = html.replace(&ppat, &li);
+            }
+            _ => {
+                html = html.replace(&ppat, "");
+            }
+        }
+    }
+
+    Html(html)
+}
+
+async fn style(State(_): State<Db>) -> &'static str {
+    include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/style.css"))
+}
+
+async fn script(State(_): State<Db>) -> &'static str {
+    include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/script.js"))
+}
+
+async fn rs(
+    State(db): State<Db>,
+    Path((_bucket, _collection)): Path<(String, String)>,
+) -> impl IntoResponse {
+    let state = db.write().unwrap();
+
+    let latest = state.latest();
+    if let Some(latest) = latest {
+        if let Some(e) = &latest.experiments {
+            (StatusCode::OK, Json(e.clone()))
+        } else {
+            // The server's latest content has no experiments; e.g.
+            // nimbus-cli open --pbpaste
+            (StatusCode::NOT_MODIFIED, Json(Value::Null))
+        }
+    } else {
+        // The server is up and running, but the first invocation of a --pbpaste
+        // has not come in yet.
+        (StatusCode::SERVICE_UNAVAILABLE, Json(Value::Null))
+    }
+}
+
+async fn post_handler(
+    State(db): State<Db>,
+    Json(payload): Json<StartAppPostPayload>,
+) -> impl IntoResponse {
+    eprintln!("Updating {platform} URL", platform = payload.platform);
+    let mut state = db.write().unwrap();
+    state.update(payload);
+    // This will be converted into a JSON response
+    // with a status code of `201 Created`
+    (StatusCode::CREATED, Json(()))
+}
+
+#[derive(Deserialize, Serialize)]
+struct StartAppPostPayload {
+    platform: String,
+    url: String,
+    experiments: Option<Value>,
+}
+
+impl StartAppPostPayload {
+    fn new(platform: &str, url: &str, experiments: Option<&Value>) -> Self {
+        Self {
+            platform: platform.to_string(),
+            url: url.to_string(),
+            experiments: experiments.cloned(),
+        }
+    }
+}
+
+fn post_payload<T: Serialize>(payload: &T, addr: &str) -> Result<String> {
+    let url = format!("http://{addr}/post");
+    let body = serde_json::to_string(payload)?;
+    let req = reqwest::blocking::Client::new()
+        .post(url)
+        .header("Content-type", "application/json; charset=UTF-8")
+        .header("accept", "application/json")
+        .body(body);
+    let resp = req.send()?;
+
+    Ok(resp.text()?)
+}
+
+struct InMemoryDb {
+    reloader: Reloader,
+    payloads: HashMap<String, StartAppPostPayload>,
+    latest: Option<String>,
+}
+
+impl InMemoryDb {
+    fn new(reloader: Reloader) -> Self {
+        Self {
+            reloader,
+            payloads: Default::default(),
+            latest: None,
+        }
+    }
+
+    fn url(&self, platform: &str) -> Option<&str> {
+        Some(self.payloads.get(platform)?.url.as_str())
+    }
+
+    fn update(&mut self, payload: StartAppPostPayload) {
+        self.latest = Some(payload.platform.clone());
+        self.payloads.insert(payload.platform.clone(), payload);
+        self.reloader.reload();
+    }
+
+    fn latest(&self) -> Option<&StartAppPostPayload> {
+        let key = self.latest.as_ref()?;
+        self.payloads.get(key)
+    }
+}
+
+type Srhl = SetResponseHeaderLayer<http::HeaderValue>;
+
+fn no_cache_layer() -> Stack<Srhl, Stack<Srhl, Srhl>> {
+    Stack::new(
+        SetResponseHeaderLayer::overriding(
+            http::header::CACHE_CONTROL,
+            http::HeaderValue::from_static("no-cache, no-store, must-revalidate"),
+        ),
+        Stack::new(
+            SetResponseHeaderLayer::overriding(
+                http::header::PRAGMA,
+                http::HeaderValue::from_static("no-cache"),
+            ),
+            SetResponseHeaderLayer::overriding(
+                http::header::EXPIRES,
+                http::HeaderValue::from_static("0"),
+            ),
+        ),
+    )
+}
+
+#[cfg(test)]
+mod tests {
+    use hyper::{Body, Method, Request, Response};
+    use serde_json::json;
+    use std::net::TcpListener;
+    use tokio::sync::oneshot::Sender;
+
+    use super::*;
+
+    fn start_test_server(port: u32) -> Result<(Db, Sender<()>)> {
+        let livereload = LiveReloadLayer::new();
+        let state = create_state(&livereload);
+
+        let app = create_app(livereload, state.clone());
+        let addr = format!("127.0.0.1:{port}");
+        let listener = TcpListener::bind(addr)?;
+        let (tx, rx) = tokio::sync::oneshot::channel::<()>();
+        tokio::spawn(async move {
+            Server::from_tcp(listener)
+                .unwrap()
+                .serve(app.into_make_service())
+                .with_graceful_shutdown(async {
+                    rx.await.ok();
+                })
+                .await
+                .unwrap();
+        });
+
+        Ok((state, tx))
+    }
+
+    async fn get(port: u32, endpoint: &str) -> Result<String> {
+        let url = format!("http://127.0.0.1:{port}{endpoint}");
+
+        let client = hyper::Client::new();
+        let response = client
+            .request(Request::builder().uri(url).body(Body::empty()).unwrap())
+            .await
+            .unwrap();
+
+        let body = hyper::body::to_bytes(response.into_body()).await.unwrap();
+        let s = std::str::from_utf8(&body)?;
+
+        Ok(s.to_string())
+    }
+
+    async fn post_payload<T: Serialize>(payload: &T, addr: &str) -> Result<Response<Body>> {
+        let url = format!("http://{addr}/post");
+        let body = serde_json::to_string(payload)?;
+        let request = Request::builder()
+            .method(Method::POST)
+            .uri(url)
+            .header("accept", "application/json")
+            .header("Content-type", "application/json; charset=UTF-8")
+            .body(Body::from(body))
+            .unwrap();
+        let client = hyper::Client::new();
+        Ok(client.request(request).await?)
+    }
+
+    #[tokio::test]
+    async fn test_smoke_test() -> Result<()> {
+        let port = 1234;
+        let (_db, tx) = start_test_server(port)?;
+
+        let s = get(port, "/").await?;
+        assert!(s.contains("<html>"));
+
+        let _ = tx.send(());
+        Ok(())
+    }
+
+    #[tokio::test]
+    async fn test_posting_platform_url() -> Result<()> {
+        let port = 1235;
+        let (db, tx) = start_test_server(port)?;
+
+        let platform = "android";
+        let deeplink = "fenix-dev-test://open-now";
+
+        let payload = StartAppPostPayload::new(platform, deeplink, None);
+        let _ = post_payload(&payload, &format!("127.0.0.1:{port}")).await?;
+
+        // Check the internal state
+        let state = db.write().unwrap();
+        let url = state.url(platform);
+        assert_eq!(url, Some(deeplink));
+
+        let _ = tx.send(());
+        Ok(())
+    }
+
+    #[tokio::test]
+    async fn test_posting_platform_url_from_index_page() -> Result<()> {
+        let port = 1236;
+        let (_, tx) = start_test_server(port)?;
+
+        let platform = "android";
+        let deeplink = "fenix-dev-test://open-now";
+
+        let payload = StartAppPostPayload::new(platform, deeplink, None);
+        let _ = post_payload(&payload, &format!("127.0.0.1:{port}")).await?;
+
+        // Check the index.html page
+        let s = get(port, "/").await?;
+        assert!(s.contains(deeplink));
+
+        let _ = tx.send(());
+        Ok(())
+    }
+
+    #[tokio::test]
+    async fn test_posting_value_to_fake_remote_settings() -> Result<()> {
+        let port = 1237;
+        let (_, tx) = start_test_server(port)?;
+
+        let platform = "android";
+        let deeplink = "fenix-dev-test://open-now";
+        let value = json!({
+            "int": 1,
+            "boolean": true,
+            "object": {},
+            "array": [],
+            "null": null,
+        });
+        let payload = StartAppPostPayload::new(platform, deeplink, Some(&value));
+        let _ = post_payload(&payload, &format!("127.0.0.1:{port}")).await?;
+
+        // Check the fake Remote Settings page
+        let s = get(port, "/v1/buckets/BUCKET/collections/COLLECTION/records").await?;
+        assert_eq!(s, serde_json::to_string(&value)?);
+
+        let s = get(port, "/buckets/BUCKET/collections/COLLECTION/records").await?;
+        assert_eq!(s, serde_json::to_string(&value)?);
+
+        let _ = tx.send(());
+        Ok(())
+    }
+
+    #[tokio::test]
+    async fn test_getting_null_values_from_fake_remote_settings() -> Result<()> {
+        let port = 1238;
+        let (_, tx) = start_test_server(port)?;
+
+        // Part 1: get from remote settings page before anything has been posted yet.
+        let s = get(port, "/v1/buckets/BUCKET/collections/COLLECTION/records").await?;
+        assert_eq!(s, "null".to_string());
+
+        // Part 2: Post a payload, but not with any experiments.
+        let platform = "android";
+        let deeplink = "fenix-dev-test://open-now";
+
+        let payload = StartAppPostPayload::new(platform, deeplink, None);
+        let _ = post_payload(&payload, &format!("127.0.0.1:{port}")).await?;
+
+        // Check the fake Remote Settings page, should be empty, since an experiments payload
+        // wasn't posted
+        let s = get(port, "/v1/buckets/BUCKET/collections/COLLECTION/records").await?;
+        assert_eq!(s, "".to_string());
+
+        let _ = tx.send(());
+        Ok(())
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus_cli/protocol.rs.html b/book/rust-docs/src/nimbus_cli/protocol.rs.html new file mode 100644 index 0000000000..c59ce29588 --- /dev/null +++ b/book/rust-docs/src/nimbus_cli/protocol.rs.html @@ -0,0 +1,37 @@ +protocol.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+
// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+use serde_json::Value;
+
+/// This is the protocol that each app understands.
+///
+/// It is sent to the apps via the start_app command.
+///
+/// Any change to this protocol requires changing the Kotlin and Swift code,
+/// and perhaps the app code itself.
+#[derive(Clone, Debug, Default)]
+pub(crate) struct StartAppProtocol<'a> {
+    pub(crate) reset_db: bool,
+    pub(crate) experiments: Option<&'a Value>,
+    pub(crate) log_state: bool,
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus_cli/sources/experiment.rs.html b/book/rust-docs/src/nimbus_cli/sources/experiment.rs.html new file mode 100644 index 0000000000..1832515ba1 --- /dev/null +++ b/book/rust-docs/src/nimbus_cli/sources/experiment.rs.html @@ -0,0 +1,851 @@ +experiment.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+
// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+use anyhow::{bail, Result};
+use serde::{Deserialize, Serialize};
+use serde_json::Value;
+use std::collections::BTreeMap;
+use std::{
+    fmt::Display,
+    path::{Path, PathBuf},
+};
+
+use crate::value_utils::{read_from_file, try_find_mut_features_from_branch, CliUtils, Patch};
+use crate::{
+    cli::{Cli, CliCommand, ExperimentArgs},
+    config, feature_utils,
+    sources::ExperimentListSource,
+    value_utils, NimbusApp, USER_AGENT,
+};
+
+use super::experiment_list::decode_list_slug;
+
+#[derive(Clone, Debug, PartialEq)]
+pub(crate) enum ExperimentSource {
+    FromList {
+        slug: String,
+        list: ExperimentListSource,
+    },
+    FromFeatureFiles {
+        app: NimbusApp,
+        feature_id: String,
+        files: Vec<PathBuf>,
+    },
+    FromApiV6 {
+        slug: String,
+        endpoint: String,
+    },
+    WithPatchFile {
+        patch: PathBuf,
+        inner: Box<ExperimentSource>,
+    },
+    #[cfg(test)]
+    FromTestFixture {
+        file: PathBuf,
+    },
+}
+
+// Create ExperimentSources from &str and Cli.
+
+impl ExperimentSource {
+    fn try_from_slug<'a>(
+        value: &'a str,
+        production: &'a str,
+        stage: &'a str,
+    ) -> Result<(&'a str, &'a str, bool)> {
+        let tokens: Vec<&str> = value.splitn(3, '/').collect();
+
+        let (is_production, is_preview) = match tokens.as_slice() {
+            [_] => decode_list_slug("")?,
+            [first, _] => decode_list_slug(first)?,
+            [first, second, _] => decode_list_slug(&format!("{first}/{second}"))?,
+            _ => unreachable!(),
+        };
+
+        let endpoint = if is_production { production } else { stage };
+
+        Ok(match tokens.last() {
+            Some(slug) => (slug, endpoint, is_preview),
+            _ => bail!(format!(
+                "Can't unpack '{value}' into an experiment; try stage/SLUG, or SLUG"
+            )),
+        })
+    }
+
+    fn try_from_rs(value: &str) -> Result<Self> {
+        let p = config::rs_production_server();
+        let s = config::rs_stage_server();
+        let (slug, endpoint, is_preview) = Self::try_from_slug(value, &p, &s)?;
+        Ok(Self::FromList {
+            slug: slug.to_string(),
+            list: ExperimentListSource::FromRemoteSettings {
+                endpoint: endpoint.to_string(),
+                is_preview,
+            },
+        })
+    }
+
+    fn try_from_url(value: &str) -> Result<Self> {
+        if !value.contains("://") {
+            anyhow::bail!("A URL must start with https://, '{value}' does not");
+        }
+        let value = value.replacen("://", "/", 1);
+
+        let parts: Vec<&str> = value.split('/').collect();
+
+        Ok(match parts.as_slice() {
+            [scheme, endpoint, "nimbus", slug]
+            | [scheme, endpoint, "nimbus", slug, _]
+            | [scheme, endpoint, "nimbus", slug, _, ""]
+            | [scheme, endpoint, "api", "v6", "experiments", slug, ""] => Self::FromApiV6 {
+                slug: slug.to_string(),
+                endpoint: format!("{scheme}://{endpoint}"),
+            },
+            _ => anyhow::bail!("Unrecognized URL from which to to get an experiment"),
+        })
+    }
+
+    fn try_from_api(value: &str) -> Result<Self> {
+        let p = config::api_v6_production_server();
+        let s = config::api_v6_stage_server();
+        let (slug, endpoint, _) = Self::try_from_slug(value, &p, &s)?;
+        Ok(Self::FromApiV6 {
+            slug: slug.to_string(),
+            endpoint: endpoint.to_string(),
+        })
+    }
+
+    pub(crate) fn try_from_file(file: &Path, slug: &str) -> Result<Self> {
+        Ok(ExperimentSource::FromList {
+            slug: slug.to_string(),
+            list: file.try_into()?,
+        })
+    }
+
+    #[cfg(test)]
+    pub(crate) fn from_fixture(filename: &str) -> Self {
+        let dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
+        let file = dir.join("test/fixtures").join(filename);
+        Self::FromTestFixture { file }
+    }
+}
+
+impl TryFrom<&ExperimentArgs> for ExperimentSource {
+    type Error = anyhow::Error;
+
+    fn try_from(value: &ExperimentArgs) -> Result<Self> {
+        let experiment = &value.experiment;
+        let is_urlish = experiment.contains("://");
+        let experiment = match &value.file {
+            Some(_) if is_urlish => {
+                anyhow::bail!("Cannot load an experiment from a file and a URL at the same time")
+            }
+            None if is_urlish => Self::try_from_url(experiment.as_str())?,
+            Some(file) => Self::try_from_file(file, experiment)?,
+            _ if value.use_rs => Self::try_from_rs(experiment)?,
+            _ => Self::try_from_api(experiment.as_str())?,
+        };
+        Ok(match &value.patch {
+            Some(file) => Self::WithPatchFile {
+                patch: file.clone(),
+                inner: Box::new(experiment),
+            },
+            _ => experiment,
+        })
+    }
+}
+
+impl TryFrom<&Cli> for ExperimentSource {
+    type Error = anyhow::Error;
+
+    fn try_from(value: &Cli) -> Result<Self> {
+        Ok(match &value.command {
+            CliCommand::Validate { experiment, .. }
+            | CliCommand::Enroll { experiment, .. }
+            | CliCommand::Features { experiment, .. } => experiment.try_into()?,
+            CliCommand::TestFeature {
+                feature_id,
+                files,
+                patch,
+                ..
+            } => {
+                let experiment = Self::FromFeatureFiles {
+                    app: value.into(),
+                    feature_id: feature_id.clone(),
+                    files: files.clone(),
+                };
+                match patch {
+                    Some(f) => Self::WithPatchFile {
+                        patch: f.clone(),
+                        inner: Box::new(experiment),
+                    },
+                    _ => experiment,
+                }
+            }
+            _ => unreachable!("Cli Arg not supporting getting an experiment source"),
+        })
+    }
+}
+
+// Get the experiment itself from the experiment source.
+
+impl Display for ExperimentSource {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            Self::FromList { slug, .. } | Self::FromApiV6 { slug, .. } => f.write_str(slug),
+            Self::FromFeatureFiles { feature_id, .. } => {
+                f.write_str(&format!("{feature_id}-experiment"))
+            }
+            Self::WithPatchFile { inner, .. } => f.write_str(&format!("{inner} (patched)")),
+            #[cfg(test)]
+            Self::FromTestFixture { file } => f.write_str(&format!("{file:?}")),
+        }
+    }
+}
+
+impl TryFrom<&ExperimentSource> for Value {
+    type Error = anyhow::Error;
+
+    fn try_from(value: &ExperimentSource) -> Result<Value> {
+        Ok(match value {
+            ExperimentSource::FromList { slug, list } => {
+                let value = Value::try_from(list)?;
+                value_utils::try_find_experiment(&value, slug)?
+            }
+            ExperimentSource::FromApiV6 { slug, endpoint } => {
+                let url = format!("{endpoint}/api/v6/experiments/{slug}/");
+                let req = reqwest::blocking::Client::builder()
+                    .user_agent(USER_AGENT)
+                    .gzip(true)
+                    .build()?
+                    .get(url);
+
+                req.send()?.json()?
+            }
+            ExperimentSource::FromFeatureFiles {
+                app,
+                feature_id,
+                files,
+            } => feature_utils::create_experiment(app, feature_id, files)?,
+
+            ExperimentSource::WithPatchFile { patch, inner } => patch_experiment(inner, patch)?,
+
+            #[cfg(test)]
+            ExperimentSource::FromTestFixture { file } => value_utils::read_from_file(file)?,
+        })
+    }
+}
+
+fn patch_experiment(experiment: &ExperimentSource, patch: &PathBuf) -> Result<Value> {
+    let mut value: Value = experiment
+        .try_into()
+        .map_err(|e| anyhow::Error::msg(format!("Problem loading experiment: {e}")))?;
+
+    let patch: FeatureDefaults = read_from_file(patch)
+        .map_err(|e| anyhow::Error::msg(format!("Problem loading patch file: {e}")))?;
+
+    for b in value.get_mut_array("branches")? {
+        for (feature_id, value) in try_find_mut_features_from_branch(b)? {
+            match patch.features.get(&feature_id) {
+                Some(v) => value.patch(v),
+                _ => true,
+            };
+        }
+    }
+    Ok(value)
+}
+
+#[derive(Deserialize, Serialize)]
+struct FeatureDefaults {
+    #[serde(flatten)]
+    features: BTreeMap<String, Value>,
+}
+
+#[cfg(test)]
+mod unit_tests {
+    use super::*;
+    #[test]
+    fn test_experiment_source_from_rs() -> Result<()> {
+        let release = ExperimentListSource::try_from_rs("")?;
+        let stage = ExperimentListSource::try_from_rs("stage")?;
+        let release_preview = ExperimentListSource::try_from_rs("preview")?;
+        let stage_preview = ExperimentListSource::try_from_rs("stage/preview")?;
+        let slug = "my-slug".to_string();
+        assert_eq!(
+            ExperimentSource::try_from_rs("my-slug")?,
+            ExperimentSource::FromList {
+                list: release.clone(),
+                slug: slug.clone()
+            }
+        );
+        assert_eq!(
+            ExperimentSource::try_from_rs("release/my-slug")?,
+            ExperimentSource::FromList {
+                list: release,
+                slug: slug.clone()
+            }
+        );
+        assert_eq!(
+            ExperimentSource::try_from_rs("stage/my-slug")?,
+            ExperimentSource::FromList {
+                list: stage,
+                slug: slug.clone()
+            }
+        );
+        assert_eq!(
+            ExperimentSource::try_from_rs("preview/my-slug")?,
+            ExperimentSource::FromList {
+                list: release_preview.clone(),
+                slug: slug.clone()
+            }
+        );
+        assert_eq!(
+            ExperimentSource::try_from_rs("release/preview/my-slug")?,
+            ExperimentSource::FromList {
+                list: release_preview,
+                slug: slug.clone()
+            }
+        );
+        assert_eq!(
+            ExperimentSource::try_from_rs("stage/preview/my-slug")?,
+            ExperimentSource::FromList {
+                list: stage_preview,
+                slug
+            }
+        );
+
+        assert!(ExperimentSource::try_from_rs("not-real/preview/my-slug").is_err());
+        assert!(ExperimentSource::try_from_rs("release/not-real/my-slug").is_err());
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_experiment_source_from_api() -> Result<()> {
+        let release = config::api_v6_production_server();
+        let stage = config::api_v6_stage_server();
+        let slug = "my-slug".to_string();
+        assert_eq!(
+            ExperimentSource::try_from_api("my-slug")?,
+            ExperimentSource::FromApiV6 {
+                slug: slug.to_string(),
+                endpoint: release.clone()
+            }
+        );
+        assert_eq!(
+            ExperimentSource::try_from_api("release/my-slug")?,
+            ExperimentSource::FromApiV6 {
+                slug: slug.to_string(),
+                endpoint: release.clone()
+            }
+        );
+        assert_eq!(
+            ExperimentSource::try_from_api("stage/my-slug")?,
+            ExperimentSource::FromApiV6 {
+                slug: slug.to_string(),
+                endpoint: stage.clone()
+            }
+        );
+        assert_eq!(
+            ExperimentSource::try_from_api("preview/my-slug")?,
+            ExperimentSource::FromApiV6 {
+                slug: slug.to_string(),
+                endpoint: release.clone()
+            }
+        );
+        assert_eq!(
+            ExperimentSource::try_from_api("release/preview/my-slug")?,
+            ExperimentSource::FromApiV6 {
+                slug: slug.to_string(),
+                endpoint: release
+            }
+        );
+        assert_eq!(
+            ExperimentSource::try_from_api("stage/preview/my-slug")?,
+            ExperimentSource::FromApiV6 {
+                slug,
+                endpoint: stage
+            }
+        );
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_experiment_source_from_url() -> Result<()> {
+        let endpoint = "https://example.com";
+        let slug = "my-slug";
+        assert_eq!(
+            ExperimentSource::try_from_url("https://example.com/nimbus/my-slug/summary")?,
+            ExperimentSource::FromApiV6 {
+                slug: slug.to_string(),
+                endpoint: endpoint.to_string(),
+            }
+        );
+        assert_eq!(
+            ExperimentSource::try_from_url("https://example.com/nimbus/my-slug/summary/")?,
+            ExperimentSource::FromApiV6 {
+                slug: slug.to_string(),
+                endpoint: endpoint.to_string(),
+            }
+        );
+        assert_eq!(
+            ExperimentSource::try_from_url("https://example.com/nimbus/my-slug/results#overview")?,
+            ExperimentSource::FromApiV6 {
+                slug: slug.to_string(),
+                endpoint: endpoint.to_string(),
+            }
+        );
+        assert_eq!(
+            ExperimentSource::try_from_url("https://example.com/api/v6/experiments/my-slug/")?,
+            ExperimentSource::FromApiV6 {
+                slug: slug.to_string(),
+                endpoint: endpoint.to_string(),
+            }
+        );
+        let endpoint = "http://localhost:8080";
+        assert_eq!(
+            ExperimentSource::try_from_url("http://localhost:8080/nimbus/my-slug/summary")?,
+            ExperimentSource::FromApiV6 {
+                slug: slug.to_string(),
+                endpoint: endpoint.to_string(),
+            }
+        );
+        assert_eq!(
+            ExperimentSource::try_from_url("http://localhost:8080/api/v6/experiments/my-slug/")?,
+            ExperimentSource::FromApiV6 {
+                slug: slug.to_string(),
+                endpoint: endpoint.to_string(),
+            }
+        );
+
+        Ok(())
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus_cli/sources/experiment_list.rs.html b/book/rust-docs/src/nimbus_cli/sources/experiment_list.rs.html new file mode 100644 index 0000000000..ae12348d95 --- /dev/null +++ b/book/rust-docs/src/nimbus_cli/sources/experiment_list.rs.html @@ -0,0 +1,735 @@ +experiment_list.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+
// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+use crate::{
+    cli::{Cli, CliCommand, ExperimentArgs, ExperimentListArgs, ExperimentListSourceArgs},
+    config,
+    value_utils::{self, CliUtils},
+    USER_AGENT,
+};
+use anyhow::{bail, Result};
+use serde_json::Value;
+use std::path::{Path, PathBuf};
+
+use super::{ExperimentListFilter, ExperimentSource};
+
+#[derive(Clone, Debug, PartialEq)]
+pub(crate) enum ExperimentListSource {
+    Empty,
+    Filtered {
+        filter: ExperimentListFilter,
+        inner: Box<ExperimentListSource>,
+    },
+    FromApiV6 {
+        endpoint: String,
+    },
+    FromFile {
+        file: PathBuf,
+    },
+    FromRemoteSettings {
+        endpoint: String,
+        is_preview: bool,
+    },
+    FromRecipes {
+        recipes: Vec<ExperimentSource>,
+    },
+}
+
+impl ExperimentListSource {
+    fn try_from_slug<'a>(
+        slug: &'a str,
+        production: &'a str,
+        stage: &'a str,
+    ) -> Result<(&'a str, bool)> {
+        let (is_production, is_preview) = decode_list_slug(slug)?;
+
+        let endpoint = if is_production { production } else { stage };
+
+        Ok((endpoint, is_preview))
+    }
+
+    pub(crate) fn try_from_rs(value: &str) -> Result<Self> {
+        let p = config::rs_production_server();
+        let s = config::rs_stage_server();
+        let (endpoint, is_preview) = Self::try_from_slug(value, &p, &s)?;
+        Ok(Self::FromRemoteSettings {
+            endpoint: endpoint.to_string(),
+            is_preview,
+        })
+    }
+
+    pub(crate) fn try_from_api(value: &str) -> Result<Self> {
+        let p = config::api_v6_production_server();
+        let s = config::api_v6_stage_server();
+        let (endpoint, _) = Self::try_from_slug(value, &p, &s)?;
+        Ok(Self::FromApiV6 {
+            endpoint: endpoint.to_string(),
+        })
+    }
+}
+
+// Returns (is_production, is_preview)
+pub(crate) fn decode_list_slug(slug: &str) -> Result<(bool, bool)> {
+    let tokens: Vec<&str> = slug.splitn(3, '/').collect();
+
+    Ok(match tokens.as_slice() {
+        [""] => (true, false),
+        ["preview"] => (true, true),
+        [server] => (is_production_server(server)?, false),
+        [server, preview] => (
+            is_production_server(server)?,
+            is_preview_collection(preview)?,
+        ),
+        _ => bail!(format!(
+            "Can't unpack '{slug}' into an experiment; try stage/SLUG, or SLUG"
+        )),
+    })
+}
+
+fn is_production_server(slug: &str) -> Result<bool> {
+    Ok(match slug {
+        "production" | "release" | "prod" | "" => true,
+        "stage" | "staging" => false,
+        _ => bail!(format!(
+            "Cannot translate '{slug}' into production or stage"
+        )),
+    })
+}
+
+fn is_preview_collection(slug: &str) -> Result<bool> {
+    Ok(match slug {
+        "preview" => true,
+        "" => false,
+        _ => bail!(format!(
+            "Cannot translate '{slug}' into preview or release collection"
+        )),
+    })
+}
+
+impl TryFrom<&Cli> for ExperimentListSource {
+    type Error = anyhow::Error;
+
+    fn try_from(value: &Cli) -> Result<Self> {
+        let list = match &value.command {
+            CliCommand::FetchList { list, .. } | CliCommand::List { list } => {
+                ExperimentListSource::try_from(list)?
+            }
+            CliCommand::Fetch {
+                experiment,
+                recipes: slugs,
+                ..
+            } => {
+                let mut recipes = vec![ExperimentSource::try_from(experiment)?];
+
+                for r in slugs {
+                    let recipe = ExperimentArgs {
+                        experiment: r.clone(),
+                        ..experiment.clone()
+                    };
+                    recipes.push(ExperimentSource::try_from(&recipe)?);
+                }
+                ExperimentListSource::FromRecipes { recipes }
+            }
+            _ => unreachable!(),
+        };
+
+        let app = value.app.clone();
+        Ok(if let Some(app) = app {
+            ExperimentListSource::Filtered {
+                filter: ExperimentListFilter::for_app(app.as_str()),
+                inner: Box::new(list),
+            }
+        } else {
+            list
+        })
+    }
+}
+
+impl TryFrom<&ExperimentListArgs> for ExperimentListSource {
+    type Error = anyhow::Error;
+
+    fn try_from(value: &ExperimentListArgs) -> Result<Self> {
+        let source = match &value.source {
+            ExperimentListSourceArgs {
+                server,
+                file: Some(file),
+                ..
+            } => {
+                if !server.is_empty() {
+                    bail!("Cannot load a list from a file AND a server")
+                } else {
+                    Self::FromFile { file: file.clone() }
+                }
+            }
+            ExperimentListSourceArgs {
+                server: s,
+                file: None,
+                use_api,
+            } => {
+                if *use_api {
+                    Self::try_from_api(s)?
+                } else {
+                    Self::try_from_rs(s)?
+                }
+            }
+        };
+        let filter: ExperimentListFilter = From::from(&value.filter);
+        Ok(if !filter.is_empty() {
+            ExperimentListSource::Filtered {
+                filter,
+                inner: Box::new(source),
+            }
+        } else {
+            source
+        })
+    }
+}
+
+impl TryFrom<&Path> for ExperimentListSource {
+    type Error = anyhow::Error;
+
+    fn try_from(value: &Path) -> Result<Self> {
+        Ok(Self::FromFile {
+            file: value.to_path_buf(),
+        })
+    }
+}
+
+// Get the experiment list
+
+impl TryFrom<&ExperimentListSource> for Value {
+    type Error = anyhow::Error;
+
+    fn try_from(value: &ExperimentListSource) -> Result<Value> {
+        Ok(match value {
+            ExperimentListSource::Empty => serde_json::json!({ "data": [] }),
+            ExperimentListSource::Filtered { filter, inner } => filter_list(filter, inner)?,
+            ExperimentListSource::FromRecipes { recipes } => {
+                let mut data: Vec<Value> = Default::default();
+
+                for r in recipes {
+                    if let Ok(v) = r.try_into() {
+                        data.push(v);
+                    }
+                }
+                serde_json::json!({ "data": data })
+            }
+            ExperimentListSource::FromRemoteSettings {
+                endpoint,
+                is_preview,
+            } => {
+                use remote_settings::{Client, RemoteSettingsConfig};
+                viaduct_reqwest::use_reqwest_backend();
+                let collection_name = if *is_preview {
+                    "nimbus-preview".to_string()
+                } else {
+                    "nimbus-mobile-experiments".to_string()
+                };
+                let config = RemoteSettingsConfig {
+                    server_url: Some(endpoint.clone()),
+                    bucket_name: None,
+                    collection_name,
+                };
+                let client = Client::new(config)?;
+
+                let response = client.get_records_raw()?;
+                response.json::<Value>()?
+            }
+            ExperimentListSource::FromFile { file } => {
+                let v: Value = value_utils::read_from_file(file)?;
+                if v.is_array() {
+                    serde_json::json!({ "data": v })
+                } else if v.get_array("data").is_ok() {
+                    v
+                } else if v.get_array("branches").is_ok() {
+                    serde_json::json!({ "data": [v] })
+                } else {
+                    bail!(
+                        "An unrecognized recipes JSON file: {}",
+                        file.as_path().to_str().unwrap_or_default()
+                    );
+                }
+            }
+            ExperimentListSource::FromApiV6 { endpoint } => {
+                let url = format!("{endpoint}/api/v6/experiments/");
+
+                let req = reqwest::blocking::Client::builder()
+                    .user_agent(USER_AGENT)
+                    .gzip(true)
+                    .build()?
+                    .get(url);
+
+                let resp = req.send()?;
+                let data: Value = resp.json()?;
+
+                fn start_date(v: &Value) -> &str {
+                    let later = "9999-99-99";
+                    match v.get("startDate") {
+                        Some(v) => v.as_str().unwrap_or(later),
+                        _ => later,
+                    }
+                }
+
+                let data = match data {
+                    Value::Array(mut array) => {
+                        array.sort_by(|p, q| {
+                            let p_time = start_date(p);
+                            let q_time = start_date(q);
+                            p_time.cmp(q_time)
+                        });
+                        Value::Array(array)
+                    }
+                    _ => data,
+                };
+                serde_json::json!({ "data": data })
+            }
+        })
+    }
+}
+
+fn filter_list(filter: &ExperimentListFilter, inner: &ExperimentListSource) -> Result<Value> {
+    let v: Value = Value::try_from(inner)?;
+    let data = v.get_array("data")?;
+    let mut array: Vec<Value> = Default::default();
+    for exp in data {
+        if let Ok(true) = filter.matches(exp) {
+            array.push(exp.to_owned());
+        }
+    }
+
+    Ok(serde_json::json!({ "data": array }))
+}
+
+#[cfg(test)]
+mod unit_tests {
+    use super::*;
+
+    #[test]
+    fn test_experiment_list_from_rs() -> Result<()> {
+        let release = config::rs_production_server();
+        let stage = config::rs_stage_server();
+        assert_eq!(
+            ExperimentListSource::try_from_rs("")?,
+            ExperimentListSource::FromRemoteSettings {
+                endpoint: release.clone(),
+                is_preview: false
+            }
+        );
+        assert_eq!(
+            ExperimentListSource::try_from_rs("preview")?,
+            ExperimentListSource::FromRemoteSettings {
+                endpoint: release.clone(),
+                is_preview: true
+            }
+        );
+        assert_eq!(
+            ExperimentListSource::try_from_rs("release")?,
+            ExperimentListSource::FromRemoteSettings {
+                endpoint: release.clone(),
+                is_preview: false
+            }
+        );
+        assert_eq!(
+            ExperimentListSource::try_from_rs("release/preview")?,
+            ExperimentListSource::FromRemoteSettings {
+                endpoint: release.clone(),
+                is_preview: true
+            }
+        );
+        assert_eq!(
+            ExperimentListSource::try_from_rs("stage")?,
+            ExperimentListSource::FromRemoteSettings {
+                endpoint: stage.clone(),
+                is_preview: false
+            }
+        );
+        assert_eq!(
+            ExperimentListSource::try_from_rs("stage/preview")?,
+            ExperimentListSource::FromRemoteSettings {
+                endpoint: stage,
+                is_preview: true
+            }
+        );
+        assert_eq!(
+            ExperimentListSource::try_from_rs("release/preview")?,
+            ExperimentListSource::FromRemoteSettings {
+                endpoint: release,
+                is_preview: true
+            }
+        );
+
+        assert!(ExperimentListSource::try_from_rs("not-real/preview").is_err());
+        assert!(ExperimentListSource::try_from_rs("release/not-real").is_err());
+
+        Ok(())
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus_cli/sources/filter.rs.html b/book/rust-docs/src/nimbus_cli/sources/filter.rs.html new file mode 100644 index 0000000000..65f6140de7 --- /dev/null +++ b/book/rust-docs/src/nimbus_cli/sources/filter.rs.html @@ -0,0 +1,623 @@ +filter.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+
// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+use crate::{cli::ExperimentListFilterArgs, output::info::ExperimentInfo};
+use anyhow::Result;
+use serde_json::Value;
+
+#[derive(Clone, Debug, Default, PartialEq)]
+pub(crate) struct ExperimentListFilter {
+    slug_pattern: Option<String>,
+    app: Option<String>,
+    feature_pattern: Option<String>,
+    active_on: Option<String>,
+    enrolling_on: Option<String>,
+    channel: Option<String>,
+    is_rollout: Option<bool>,
+}
+
+impl From<&ExperimentListFilterArgs> for ExperimentListFilter {
+    fn from(value: &ExperimentListFilterArgs) -> Self {
+        ExperimentListFilter {
+            slug_pattern: value.slug.clone(),
+            feature_pattern: value.feature.clone(),
+            active_on: value.active_on.clone(),
+            enrolling_on: value.enrolling_on.clone(),
+            channel: value.channel.clone(),
+            is_rollout: value.is_rollout,
+            ..Default::default()
+        }
+    }
+}
+
+impl ExperimentListFilter {
+    pub(crate) fn for_app(app: &str) -> Self {
+        ExperimentListFilter {
+            app: Some(app.to_string()),
+            ..Default::default()
+        }
+    }
+}
+
+#[cfg(test)]
+impl ExperimentListFilter {
+    pub(crate) fn for_feature(feature_pattern: &str) -> Self {
+        ExperimentListFilter {
+            feature_pattern: Some(feature_pattern.to_string()),
+            ..Default::default()
+        }
+    }
+
+    pub(crate) fn for_active_on(date: &str) -> Self {
+        ExperimentListFilter {
+            active_on: Some(date.to_string()),
+            ..Default::default()
+        }
+    }
+
+    pub(crate) fn for_enrolling_on(date: &str) -> Self {
+        ExperimentListFilter {
+            enrolling_on: Some(date.to_string()),
+            ..Default::default()
+        }
+    }
+}
+
+impl ExperimentListFilter {
+    pub(crate) fn is_empty(&self) -> bool {
+        self == &Default::default()
+    }
+
+    pub(crate) fn matches(&self, value: &Value) -> Result<bool> {
+        let info: ExperimentInfo = match value.try_into() {
+            Ok(e) => e,
+            _ => return Ok(false),
+        };
+        Ok(self.matches_info(info))
+    }
+
+    fn matches_info(&self, info: ExperimentInfo) -> bool {
+        match self.slug_pattern.as_deref() {
+            Some(s) if !info.slug.contains(s) => return false,
+            _ => (),
+        };
+
+        match self.app.as_deref() {
+            Some(s) if s != info.app_name => return false,
+            _ => (),
+        };
+
+        match self.channel.as_deref() {
+            Some(s) if s != info.channel => return false,
+            _ => (),
+        };
+
+        match self.is_rollout {
+            Some(s) if s != info.is_rollout => return false,
+            _ => (),
+        };
+
+        match self.feature_pattern.as_deref() {
+            Some(f) if !info.features.iter().any(|s| s.contains(f)) => return false,
+            _ => (),
+        };
+
+        match self.active_on.as_deref() {
+            Some(date) if !info.active().contains(date) => return false,
+            _ => (),
+        };
+
+        match self.enrolling_on.as_deref() {
+            Some(date) if !info.enrollment().contains(date) => return false,
+            _ => (),
+        };
+
+        true
+    }
+}
+
+#[cfg(test)]
+mod unit_tests {
+    use crate::output::info::DateRange;
+
+    use super::*;
+
+    #[test]
+    fn test_matches_app() -> Result<()> {
+        let filter = ExperimentListFilter {
+            app: Some("my-app".to_string()),
+            ..Default::default()
+        };
+
+        let positive = ExperimentInfo {
+            app_name: "my-app",
+            ..Default::default()
+        };
+        assert!(filter.matches_info(positive));
+
+        let negative = ExperimentInfo {
+            app_name: "not-my-app",
+            ..Default::default()
+        };
+        assert!(!filter.matches_info(negative));
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_matches_slug() -> Result<()> {
+        let filter = ExperimentListFilter {
+            slug_pattern: Some("my-app".to_string()),
+            ..Default::default()
+        };
+
+        let positive = ExperimentInfo {
+            slug: "my-app",
+            ..Default::default()
+        };
+        assert!(filter.matches_info(positive));
+
+        let negative = ExperimentInfo {
+            slug: "my-other-app",
+            ..Default::default()
+        };
+        assert!(!filter.matches_info(negative));
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_matches_channel() -> Result<()> {
+        let filter = ExperimentListFilter {
+            channel: Some("release".to_string()),
+            ..Default::default()
+        };
+
+        let positive = ExperimentInfo {
+            channel: "release",
+            ..Default::default()
+        };
+        assert!(filter.matches_info(positive));
+
+        let negative = ExperimentInfo {
+            channel: "beta",
+            ..Default::default()
+        };
+        assert!(!filter.matches_info(negative));
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_matches_is_rollout() -> Result<()> {
+        let filter = ExperimentListFilter {
+            is_rollout: Some(false),
+            ..Default::default()
+        };
+
+        let positive = ExperimentInfo {
+            is_rollout: false,
+            ..Default::default()
+        };
+        assert!(filter.matches_info(positive));
+
+        let negative = ExperimentInfo {
+            is_rollout: true,
+            ..Default::default()
+        };
+        assert!(!filter.matches_info(negative));
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_matches_conjunction() -> Result<()> {
+        let filter = ExperimentListFilter {
+            app: Some("my-app".to_string()),
+            channel: Some("release".to_string()),
+            ..Default::default()
+        };
+
+        let positive = ExperimentInfo {
+            app_name: "my-app",
+            channel: "release",
+            ..Default::default()
+        };
+        assert!(filter.matches_info(positive));
+
+        let negative = ExperimentInfo {
+            app_name: "not-my-app",
+            channel: "release",
+            ..Default::default()
+        };
+        assert!(!filter.matches_info(negative));
+
+        let negative = ExperimentInfo {
+            app_name: "my-app",
+            channel: "not-release",
+            ..Default::default()
+        };
+        assert!(!filter.matches_info(negative));
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_matches_features() -> Result<()> {
+        let filter = ExperimentListFilter {
+            feature_pattern: Some("another".to_string()),
+            ..Default::default()
+        };
+
+        let positive = ExperimentInfo {
+            features: vec!["my-feature", "another-feature"],
+            ..Default::default()
+        };
+        assert!(filter.matches_info(positive));
+
+        let negative = ExperimentInfo {
+            features: vec!["my-feature", "not-this-feature"],
+            ..Default::default()
+        };
+        assert!(!filter.matches_info(negative));
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_matches_enrolling_on() -> Result<()> {
+        let filter = ExperimentListFilter {
+            enrolling_on: Some("2023-07-18".to_string()),
+            ..Default::default()
+        };
+
+        let positive = ExperimentInfo {
+            enrollment: DateRange::from_str("2023-07-01", "2023-07-31", 0),
+            ..Default::default()
+        };
+        assert!(filter.matches_info(positive));
+
+        let negative = ExperimentInfo {
+            enrollment: DateRange::from_str("2023-06-01", "2023-06-30", 0),
+            ..Default::default()
+        };
+        assert!(!filter.matches_info(negative));
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_matches_active_on() -> Result<()> {
+        let filter = ExperimentListFilter {
+            active_on: Some("2023-07-18".to_string()),
+            ..Default::default()
+        };
+
+        let positive = ExperimentInfo {
+            duration: DateRange::from_str("2023-07-01", "2023-07-31", 0),
+            ..Default::default()
+        };
+        assert!(filter.matches_info(positive));
+
+        let negative = ExperimentInfo {
+            duration: DateRange::from_str("2023-06-01", "2023-06-30", 0),
+            ..Default::default()
+        };
+        assert!(!filter.matches_info(negative));
+
+        Ok(())
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus_cli/sources/manifest.rs.html b/book/rust-docs/src/nimbus_cli/sources/manifest.rs.html new file mode 100644 index 0000000000..7397eca72d --- /dev/null +++ b/book/rust-docs/src/nimbus_cli/sources/manifest.rs.html @@ -0,0 +1,195 @@ +manifest.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+
// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+use std::fmt::Display;
+
+use anyhow::Result;
+use nimbus_fml::{
+    intermediate_representation::FeatureManifest, parser::Parser, util::loaders::FileLoader,
+};
+
+use crate::{cli::ManifestArgs, config, NimbusApp};
+
+#[derive(Debug, PartialEq)]
+pub(crate) enum ManifestSource {
+    FromGithub {
+        channel: String,
+
+        github_repo: String,
+        ref_: String,
+
+        manifest_file: String,
+    },
+    FromFile {
+        channel: String,
+        manifest_file: String,
+    },
+}
+
+impl ManifestSource {
+    fn manifest_file(&self) -> &str {
+        let (Self::FromFile { manifest_file, .. } | Self::FromGithub { manifest_file, .. }) = self;
+        manifest_file
+    }
+
+    fn channel(&self) -> &str {
+        let (Self::FromFile { channel, .. } | Self::FromGithub { channel, .. }) = self;
+        channel
+    }
+
+    fn manifest_loader(&self) -> Result<FileLoader> {
+        let cwd = std::env::current_dir().expect("Current Working Directory is not set");
+        let mut files = FileLoader::new(cwd, config::manifest_cache_dir(), Default::default())?;
+        if let Self::FromGithub {
+            ref_, github_repo, ..
+        } = self
+        {
+            files.add_repo(github_repo, ref_)?;
+        }
+        Ok(files)
+    }
+
+    pub(crate) fn try_from(params: &NimbusApp, value: &ManifestArgs) -> Result<Self> {
+        Ok(
+            match (value.manifest.clone(), params.channel(), params.app_name()) {
+                (Some(manifest_file), Some(channel), _) => Self::FromFile {
+                    channel,
+                    manifest_file,
+                },
+                (_, Some(channel), Some(_)) => {
+                    let github_repo = params.github_repo()?.to_string();
+                    let ref_ = params.ref_from_version(&value.version, &value.ref_)?;
+                    let manifest_file =
+                        format!("@{}/{}", github_repo, params.manifest_location()?,);
+                    Self::FromGithub {
+                        channel,
+                        manifest_file,
+                        ref_,
+                        github_repo,
+                    }
+                }
+                _ => anyhow::bail!("A channel and either a manifest or an app is expected"),
+            },
+        )
+    }
+}
+
+impl Display for ManifestSource {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        let files = self.manifest_loader().unwrap();
+        let path = files.file_path(self.manifest_file()).unwrap();
+        f.write_str(&path.to_string())
+    }
+}
+
+impl TryFrom<&ManifestSource> for FeatureManifest {
+    type Error = anyhow::Error;
+
+    fn try_from(value: &ManifestSource) -> Result<Self> {
+        let files = value.manifest_loader()?;
+        let path = files.file_path(value.manifest_file())?;
+        let parser: Parser = Parser::new(files, path)?;
+        let manifest = parser.get_intermediate_representation(Some(value.channel()))?;
+        manifest.validate_manifest()?;
+        Ok(manifest)
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus_cli/sources/mod.rs.html b/book/rust-docs/src/nimbus_cli/sources/mod.rs.html new file mode 100644 index 0000000000..aeed350b2a --- /dev/null +++ b/book/rust-docs/src/nimbus_cli/sources/mod.rs.html @@ -0,0 +1,27 @@ +mod.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+
// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+mod experiment;
+mod experiment_list;
+mod filter;
+mod manifest;
+
+pub(crate) use experiment::ExperimentSource;
+pub(crate) use experiment_list::ExperimentListSource;
+pub(crate) use filter::ExperimentListFilter;
+pub(crate) use manifest::ManifestSource;
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus_cli/updater/mod.rs.html b/book/rust-docs/src/nimbus_cli/updater/mod.rs.html new file mode 100644 index 0000000000..2f38187fde --- /dev/null +++ b/book/rust-docs/src/nimbus_cli/updater/mod.rs.html @@ -0,0 +1,69 @@ +mod.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+
// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+mod taskcluster;
+
+use console::Term;
+
+pub(crate) fn check_for_update() {
+    if std::env::var("NIMBUS_CLI_SUPPRESS_UPDATE_CHECK").is_ok() {
+        return;
+    }
+    taskcluster::check_taskcluster_for_update(|curr, next| {
+        let term = Term::stderr();
+        let txt_style = term.style().green();
+        let cmd_style = term.style().yellow();
+
+        _ = term.write_line(&format!(
+            "{}",
+            txt_style.apply_to(format!("An update is available: {} --> {}", curr, next))
+        ));
+
+        _ = if std::env::consts::OS != "windows" {
+            term.write_line(&format!("{}\n{}",
+                txt_style.apply_to("To update, run this command:"),
+                cmd_style.apply_to("  curl https://raw.githubusercontent.com/mozilla/application-services/main/install-nimbus-cli.sh | bash")
+            ))
+        } else {
+            term.write_line(&format!("{}",
+                txt_style.apply_to("To update follow the instructions at https://experimenter.info/nimbus-cli/install")
+            ))
+        };
+    });
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus_cli/updater/taskcluster.rs.html b/book/rust-docs/src/nimbus_cli/updater/taskcluster.rs.html new file mode 100644 index 0000000000..412305e640 --- /dev/null +++ b/book/rust-docs/src/nimbus_cli/updater/taskcluster.rs.html @@ -0,0 +1,153 @@ +taskcluster.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+
// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+use serde::de::DeserializeOwned;
+use std::time::Duration;
+use update_informer::{
+    http_client::{GenericHttpClient, HeaderMap, HttpClient},
+    Check, Package, Registry, Result,
+};
+
+#[derive(serde::Deserialize)]
+struct Response {
+    version: String,
+}
+
+struct TaskClusterRegistry;
+
+impl Registry for TaskClusterRegistry {
+    const NAME: &'static str = "taskcluster";
+
+    fn get_latest_version<T: HttpClient>(
+        http_client: GenericHttpClient<T>,
+        pkg: &Package,
+    ) -> Result<Option<String>> {
+        let name = pkg.to_string();
+        let url = format!("https://firefox-ci-tc.services.mozilla.com/api/index/v1/task/project.application-services.v2.{name}.latest/artifacts/public%2Fbuild%2F{name}.json");
+        let resp = http_client.get::<Response>(&url)?;
+        Ok(Some(resp.version))
+    }
+}
+pub struct ReqwestGunzippingHttpClient;
+
+impl HttpClient for ReqwestGunzippingHttpClient {
+    fn get<T: DeserializeOwned>(url: &str, timeout: Duration, headers: HeaderMap) -> Result<T> {
+        let mut req = reqwest::blocking::Client::builder()
+            .timeout(timeout)
+            // We couldn't use the out-the-box HttpClient
+            // because task-cluster uses gzip.
+            .gzip(true)
+            .build()?
+            .get(url);
+
+        for (key, value) in headers {
+            req = req.header(key, value);
+        }
+
+        let json = req.send()?.json()?;
+
+        Ok(json)
+    }
+}
+
+/// Check the specifically crafted JSON file for this package to see if there has been a change in version.
+/// This is done every hour.
+pub(crate) fn check_taskcluster_for_update<F>(message: F)
+where
+    F: Fn(&str, &str),
+{
+    let name = env!("CARGO_PKG_NAME");
+    let version = env!("CARGO_PKG_VERSION");
+    let interval = Duration::from_secs(60 * 60);
+
+    #[cfg(not(test))]
+    let informer = update_informer::new(TaskClusterRegistry, name, version)
+        .http_client(ReqwestGunzippingHttpClient)
+        .interval(interval);
+
+    #[cfg(test)]
+    let informer =
+        update_informer::fake(TaskClusterRegistry, name, version, "1.0.0").interval(interval);
+
+    if let Ok(Some(new_version)) = informer.check_version() {
+        message(&format!("v{version}"), &new_version.to_string());
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus_cli/value_utils.rs.html b/book/rust-docs/src/nimbus_cli/value_utils.rs.html new file mode 100644 index 0000000000..475fbe2301 --- /dev/null +++ b/book/rust-docs/src/nimbus_cli/value_utils.rs.html @@ -0,0 +1,1105 @@ +value_utils.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
+540
+541
+542
+543
+544
+545
+546
+547
+548
+549
+550
+551
+552
+
// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+use anyhow::Result;
+use serde::{Deserialize, Serialize};
+use serde_json::{Map, Value};
+use std::collections::HashMap;
+use std::path::Path;
+
+use crate::NimbusApp;
+
+pub(crate) trait CliUtils {
+    fn get_str<'a>(&'a self, key: &str) -> Result<&'a str>;
+    fn get_bool(&self, key: &str) -> Result<bool>;
+    fn get_array<'a>(&'a self, key: &str) -> Result<&'a Vec<Value>>;
+    fn get_mut_array<'a>(&'a mut self, key: &str) -> Result<&'a mut Vec<Value>>;
+    fn get_mut_object<'a>(&'a mut self, key: &str) -> Result<&'a mut Value>;
+    fn get_object<'a>(&'a self, key: &str) -> Result<&'a Value>;
+    fn get_u64(&self, key: &str) -> Result<u64>;
+
+    fn has(&self, key: &str) -> bool;
+    fn set<V>(&mut self, key: &str, value: V) -> Result<()>
+    where
+        V: Serialize;
+}
+
+impl CliUtils for Value {
+    fn get_str<'a>(&'a self, key: &str) -> Result<&'a str> {
+        let v = self
+            .get(key)
+            .ok_or_else(|| {
+                anyhow::Error::msg(format!(
+                    "Expected a string with key '{key}' in the JSONObject"
+                ))
+            })?
+            .as_str()
+            .ok_or_else(|| anyhow::Error::msg("value is not a string"))?;
+
+        Ok(v)
+    }
+
+    fn get_bool(&self, key: &str) -> Result<bool> {
+        let v = self
+            .get(key)
+            .ok_or_else(|| {
+                anyhow::Error::msg(format!(
+                    "Expected a string with key '{key}' in the JSONObject"
+                ))
+            })?
+            .as_bool()
+            .ok_or_else(|| anyhow::Error::msg("value is not a string"))?;
+
+        Ok(v)
+    }
+
+    fn get_array<'a>(&'a self, key: &str) -> Result<&'a Vec<Value>> {
+        let v = self
+            .get(key)
+            .ok_or_else(|| {
+                anyhow::Error::msg(format!(
+                    "Expected an array with key '{key}' in the JSONObject"
+                ))
+            })?
+            .as_array()
+            .ok_or_else(|| anyhow::Error::msg("value is not a array"))?;
+        Ok(v)
+    }
+
+    fn get_mut_array<'a>(&'a mut self, key: &str) -> Result<&'a mut Vec<Value>> {
+        let v = self
+            .get_mut(key)
+            .ok_or_else(|| {
+                anyhow::Error::msg(format!(
+                    "Expected an array with key '{key}' in the JSONObject"
+                ))
+            })?
+            .as_array_mut()
+            .ok_or_else(|| anyhow::Error::msg("value is not a array"))?;
+        Ok(v)
+    }
+
+    fn get_object<'a>(&'a self, key: &str) -> Result<&'a Value> {
+        let v = self.get(key).ok_or_else(|| {
+            anyhow::Error::msg(format!(
+                "Expected an object with key '{key}' in the JSONObject"
+            ))
+        })?;
+        Ok(v)
+    }
+
+    fn get_mut_object<'a>(&'a mut self, key: &str) -> Result<&'a mut Value> {
+        let v = self.get_mut(key).ok_or_else(|| {
+            anyhow::Error::msg(format!(
+                "Expected an object with key '{key}' in the JSONObject"
+            ))
+        })?;
+        Ok(v)
+    }
+
+    fn get_u64(&self, key: &str) -> Result<u64> {
+        let v = self
+            .get(key)
+            .ok_or_else(|| {
+                anyhow::Error::msg(format!(
+                    "Expected an array with key '{key}' in the JSONObject"
+                ))
+            })?
+            .as_u64()
+            .ok_or_else(|| anyhow::Error::msg("value is not a array"))?;
+        Ok(v)
+    }
+
+    fn set<V>(&mut self, key: &str, value: V) -> Result<()>
+    where
+        V: Serialize,
+    {
+        let value = serde_json::to_value(value)?;
+        match self.as_object_mut() {
+            Some(m) => m.insert(key.to_string(), value),
+            _ => anyhow::bail!("Can only insert into JSONObjects"),
+        };
+        Ok(())
+    }
+
+    fn has(&self, key: &str) -> bool {
+        self.get(key).is_some()
+    }
+}
+
+pub(crate) fn try_find_experiment(value: &Value, slug: &str) -> Result<Value> {
+    let array = try_extract_data_list(value)?;
+    let exp = array
+        .iter()
+        .find(|exp| {
+            if let Some(Value::String(s)) = exp.get("slug") {
+                slug == s
+            } else {
+                false
+            }
+        })
+        .ok_or_else(|| anyhow::Error::msg(format!("No experiment with slug {}", slug)))?;
+
+    Ok(exp.clone())
+}
+
+pub(crate) fn try_extract_data_list(value: &Value) -> Result<Vec<Value>> {
+    assert!(value.is_object());
+    Ok(value.get_array("data")?.to_vec())
+}
+
+pub(crate) fn try_find_branches_from_experiment(value: &Value) -> Result<Vec<Value>> {
+    Ok(value.get_array("branches")?.to_vec())
+}
+
+pub(crate) fn try_find_features_from_branch(value: &Value) -> Result<Vec<Value>> {
+    let features = value.get_array("features");
+    Ok(if features.is_ok() {
+        features?.to_vec()
+    } else {
+        let feature = value
+            .get("feature")
+            .expect("Expected a feature or features in a branch");
+        vec![feature.clone()]
+    })
+}
+
+pub(crate) fn try_find_mut_features_from_branch<'a>(
+    value: &'a mut Value,
+) -> Result<HashMap<String, &'a mut Value>> {
+    let mut res = HashMap::new();
+    if value.has("features") {
+        let features = value.get_mut_array("features")?;
+        for f in features {
+            res.insert(
+                f.get_str("featureId")?.to_string(),
+                f.get_mut_object("value")?,
+            );
+        }
+    } else {
+        let f: &'a mut Value = value.get_mut_object("feature")?;
+        res.insert(
+            f.get_str("featureId")?.to_string(),
+            f.get_mut_object("value")?,
+        );
+    }
+    Ok(res)
+}
+
+pub(crate) trait Patch {
+    fn patch(&mut self, patch: &Self) -> bool;
+}
+
+impl Patch for Value {
+    fn patch(&mut self, patch: &Self) -> bool {
+        match (self, patch) {
+            (Value::Object(t), Value::Object(p)) => {
+                t.patch(p);
+            }
+            (Value::String(t), Value::String(p)) => *t = p.clone(),
+            (Value::Bool(t), Value::Bool(p)) => *t = *p,
+            (Value::Number(t), Value::Number(p)) => *t = p.clone(),
+            (Value::Array(t), Value::Array(p)) => *t = p.clone(),
+            (Value::Null, Value::Null) => (),
+            _ => return false,
+        };
+        true
+    }
+}
+
+impl Patch for Map<String, Value> {
+    fn patch(&mut self, patch: &Self) -> bool {
+        for (k, v) in patch {
+            match (self.get_mut(k), v) {
+                (Some(_), Value::Null) => {
+                    self.remove(k);
+                }
+                (_, Value::Null) => {
+                    // If the patch is null, then don't add it to this value.
+                }
+                (Some(t), p) => {
+                    if !t.patch(p) {
+                        println!("Warning: the patched key '{k}' has different types: {t} != {p}");
+                        self.insert(k.clone(), v.clone());
+                    }
+                }
+                (None, _) => {
+                    self.insert(k.clone(), v.clone());
+                }
+            }
+        }
+        true
+    }
+}
+
+fn prepare_recipe(
+    recipe: &Value,
+    params: &NimbusApp,
+    preserve_targeting: bool,
+    preserve_bucketing: bool,
+) -> Result<Value> {
+    let mut recipe = recipe.clone();
+    let slug = recipe.get_str("slug")?;
+    let app_name = params
+        .app_name
+        .as_deref()
+        .expect("An app name is expected. This is a bug in nimbus-cli");
+    if app_name != recipe.get_str("appName")? {
+        anyhow::bail!(format!("'{slug}' is not for {app_name} app"));
+    }
+    recipe.set("channel", &params.channel)?;
+    recipe.set("isEnrollmentPaused", false)?;
+    if !preserve_targeting {
+        recipe.set("targeting", "true")?;
+    }
+    if !preserve_bucketing {
+        let bucketing = recipe.get_mut_object("bucketConfig")?;
+        bucketing.set("start", 0)?;
+        bucketing.set("count", 10_000)?;
+    }
+    Ok(recipe)
+}
+
+pub(crate) fn prepare_rollout(
+    recipe: &Value,
+    params: &NimbusApp,
+    preserve_targeting: bool,
+    preserve_bucketing: bool,
+) -> Result<Value> {
+    let rollout = prepare_recipe(recipe, params, preserve_targeting, preserve_bucketing)?;
+    if !rollout.get_bool("isRollout")? {
+        let slug = rollout.get_str("slug")?;
+        anyhow::bail!(format!("Recipe '{}' isn't a rollout", slug));
+    }
+    Ok(rollout)
+}
+
+pub(crate) fn prepare_experiment(
+    recipe: &Value,
+    params: &NimbusApp,
+    branch: &str,
+    preserve_targeting: bool,
+    preserve_bucketing: bool,
+) -> Result<Value> {
+    let mut experiment = prepare_recipe(recipe, params, preserve_targeting, preserve_bucketing)?;
+
+    if !preserve_bucketing {
+        let branches = experiment.get_mut_array("branches")?;
+        let mut found = false;
+        for b in branches {
+            let slug = b.get_str("slug")?;
+            let ratio = if slug == branch {
+                found = true;
+                100
+            } else {
+                0
+            };
+            b.set("ratio", ratio)?;
+        }
+        if !found {
+            let slug = experiment.get_str("slug")?;
+            anyhow::bail!(format!(
+                "No branch called '{}' was found in '{}'",
+                branch, slug
+            ));
+        }
+    }
+    Ok(experiment)
+}
+
+fn is_yaml<P>(file: P) -> bool
+where
+    P: AsRef<Path>,
+{
+    let ext = file.as_ref().extension().unwrap_or_default();
+    ext == "yaml" || ext == "yml"
+}
+
+pub(crate) fn read_from_file<P, T>(file: P) -> Result<T>
+where
+    P: AsRef<Path>,
+    for<'a> T: Deserialize<'a>,
+{
+    let s = std::fs::read_to_string(&file)?;
+    Ok(if is_yaml(&file) {
+        serde_yaml::from_str(&s)?
+    } else {
+        serde_json::from_str(&s)?
+    })
+}
+
+pub(crate) fn write_to_file_or_print<P, T>(file: Option<P>, contents: &T) -> Result<()>
+where
+    P: AsRef<Path>,
+    T: Serialize,
+{
+    match file {
+        Some(file) => {
+            let s = if is_yaml(&file) {
+                serde_yaml::to_string(&contents)?
+            } else {
+                serde_json::to_string_pretty(&contents)?
+            };
+            std::fs::write(file, s)?;
+        }
+        _ => println!("{}", serde_json::to_string_pretty(&contents)?),
+    }
+
+    Ok(())
+}
+
+#[cfg(test)]
+mod tests {
+    use serde_json::json;
+
+    use super::*;
+
+    #[test]
+    fn test_find_experiment() -> Result<()> {
+        let exp = json!({
+            "slug": "a-name",
+        });
+        let source = json!({ "data": [exp] });
+
+        assert_eq!(try_find_experiment(&source, "a-name")?, exp);
+
+        let source = json!({
+            "data": {},
+        });
+        assert!(try_find_experiment(&source, "a-name").is_err());
+
+        let source = json!({
+            "data": [],
+        });
+        assert!(try_find_experiment(&source, "a-name").is_err());
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_prepare_experiment() -> Result<()> {
+        let src = json!({
+            "appName": "an-app",
+            "slug": "a-name",
+            "branches": [
+                {
+                    "slug": "another-branch",
+                },
+                {
+                    "slug": "a-branch",
+                }
+            ],
+            "bucketConfig": {
+            }
+        });
+
+        let params = NimbusApp::new("an-app", "developer");
+
+        assert_eq!(
+            json!({
+                "appName": "an-app",
+                "channel": "developer",
+                "slug": "a-name",
+                "branches": [
+                    {
+                        "slug": "another-branch",
+                        "ratio": 0,
+                    },
+                    {
+                        "slug": "a-branch",
+                        "ratio": 100,
+                    }
+                ],
+                "bucketConfig": {
+                    "start": 0,
+                    "count": 10_000,
+                },
+                "isEnrollmentPaused": false,
+                "targeting": "true"
+            }),
+            prepare_experiment(&src, &params, "a-branch", false, false)?
+        );
+
+        assert_eq!(
+            json!({
+                "appName": "an-app",
+                "channel": "developer",
+                "slug": "a-name",
+                "branches": [
+                    {
+                        "slug": "another-branch",
+                    },
+                    {
+                        "slug": "a-branch",
+                    }
+                ],
+                "bucketConfig": {
+                },
+                "isEnrollmentPaused": false,
+                "targeting": "true"
+            }),
+            prepare_experiment(&src, &params, "a-branch", false, true)?
+        );
+
+        assert_eq!(
+            json!({
+                "appName": "an-app",
+                "channel": "developer",
+                "slug": "a-name",
+                "branches": [
+                    {
+                        "slug": "another-branch",
+                        "ratio": 0,
+                    },
+                    {
+                        "slug": "a-branch",
+                        "ratio": 100,
+                    }
+                ],
+                "bucketConfig": {
+                    "start": 0,
+                    "count": 10_000,
+                },
+                "isEnrollmentPaused": false,
+            }),
+            prepare_experiment(&src, &params, "a-branch", true, false)?
+        );
+
+        assert_eq!(
+            json!({
+                "appName": "an-app",
+                "slug": "a-name",
+                "channel": "developer",
+                "branches": [
+                    {
+                        "slug": "another-branch",
+                    },
+                    {
+                        "slug": "a-branch",
+                    }
+                ],
+                "bucketConfig": {
+                },
+                "isEnrollmentPaused": false,
+            }),
+            prepare_experiment(&src, &params, "a-branch", true, true)?
+        );
+        Ok(())
+    }
+
+    #[test]
+    fn test_patch_value() -> Result<()> {
+        let mut v1 = json!({
+            "string": "string",
+            "obj": {
+                "string": "string",
+                "num": 1,
+                "bool": false,
+            },
+            "num": 1,
+            "bool": false,
+        });
+        let ov1 = json!({
+            "string": "patched",
+            "obj": {
+                "string": "patched",
+            },
+            "num": 2,
+            "bool": true,
+        });
+
+        v1.patch(&ov1);
+
+        let expected = json!({
+            "string": "patched",
+            "obj": {
+                "string": "patched",
+                "num": 1,
+                "bool": false,
+            },
+            "num": 2,
+            "bool": true,
+        });
+
+        assert_eq!(&expected, &v1);
+
+        let mut v1 = json!({
+            "string": "string",
+            "obj": {
+                "string": "string",
+                "num": 1,
+                "bool": false,
+            },
+            "num": 1,
+            "bool": false,
+        });
+        let ov1 = json!({
+            "obj": null,
+            "never": null,
+        });
+        v1.patch(&ov1);
+        let expected = json!({
+            "string": "string",
+            "num": 1,
+            "bool": false,
+        });
+
+        assert_eq!(&expected, &v1);
+
+        Ok(())
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus_fml/backends/experimenter_manifest.rs.html b/book/rust-docs/src/nimbus_fml/backends/experimenter_manifest.rs.html new file mode 100644 index 0000000000..e765a9cda3 --- /dev/null +++ b/book/rust-docs/src/nimbus_fml/backends/experimenter_manifest.rs.html @@ -0,0 +1,333 @@ +experimenter_manifest.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use std::collections::{BTreeMap, BTreeSet};
+use std::fmt::Display;
+
+use serde::{Deserialize, Serialize};
+
+use crate::{
+    command_line::commands::GenerateExperimenterManifestCmd,
+    error::{FMLError, Result},
+    intermediate_representation::{FeatureDef, FeatureManifest, PropDef, TargetLanguage, TypeRef},
+};
+
+pub(crate) type ExperimenterManifest = BTreeMap<String, ExperimenterFeature>;
+
+#[derive(Debug, Default, Clone, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub(crate) struct ExperimenterFeature {
+    description: String,
+    has_exposure: bool,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    exposure_description: Option<String>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    is_early_startup: Option<bool>,
+    variables: BTreeMap<String, ExperimenterFeatureProperty>,
+}
+
+#[derive(Debug, Default, Clone, Serialize, Deserialize)]
+pub(crate) struct ExperimenterFeatureProperty {
+    #[serde(rename = "type")]
+    property_type: String,
+    description: String,
+
+    #[serde(rename = "enum")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    variants: Option<BTreeSet<String>>,
+}
+
+impl TryFrom<FeatureManifest> for ExperimenterManifest {
+    type Error = crate::error::FMLError;
+    fn try_from(fm: FeatureManifest) -> Result<Self> {
+        fm.iter_all_feature_defs()
+            .map(|(fm, f)| Ok((f.name(), fm.create_experimenter_feature(f)?)))
+            .collect()
+    }
+}
+
+impl FeatureManifest {
+    fn create_experimenter_feature(&self, feature: &FeatureDef) -> Result<ExperimenterFeature> {
+        Ok(ExperimenterFeature {
+            description: feature.doc(),
+            has_exposure: true,
+            is_early_startup: None,
+            // TODO: Add exposure description to the IR so
+            // we can use it here if it's needed
+            exposure_description: Some("".into()),
+            variables: self.props_to_variables(&feature.props)?,
+        })
+    }
+
+    fn props_to_variables(
+        &self,
+        props: &[PropDef],
+    ) -> Result<BTreeMap<String, ExperimenterFeatureProperty>> {
+        // Ideally this would be implemented as a `TryFrom<Vec<PropDef>>`
+        // however, we need a reference to the `FeatureManifest` to get the valid
+        // variants of an enum
+        let mut map = BTreeMap::new();
+        props.iter().try_for_each(|prop| -> Result<()> {
+            let typ = ExperimentManifestPropType::from(prop.typ()).to_string();
+
+            let yaml_prop = if let TypeRef::Enum(e) = prop.typ() {
+                let enum_def = self
+                    .find_enum(&e)
+                    .ok_or(FMLError::InternalError("Found enum with no definition"))?;
+
+                let variants = enum_def
+                    .variants
+                    .iter()
+                    .map(|variant| variant.name())
+                    .collect::<BTreeSet<String>>();
+
+                ExperimenterFeatureProperty {
+                    variants: Some(variants),
+                    description: prop.doc(),
+                    property_type: typ,
+                }
+            } else {
+                ExperimenterFeatureProperty {
+                    variants: None,
+                    description: prop.doc(),
+                    property_type: typ,
+                }
+            };
+            map.insert(prop.name(), yaml_prop);
+            Ok(())
+        })?;
+        Ok(map)
+    }
+}
+
+enum ExperimentManifestPropType {
+    Json,
+    Boolean,
+    Int,
+    String,
+}
+
+impl Display for ExperimentManifestPropType {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        let s = match self {
+            ExperimentManifestPropType::Boolean => "boolean",
+            ExperimentManifestPropType::Int => "int",
+            ExperimentManifestPropType::Json => "json",
+            ExperimentManifestPropType::String => "string",
+        };
+        write!(f, "{}", s)
+    }
+}
+
+impl From<TypeRef> for ExperimentManifestPropType {
+    fn from(typ: TypeRef) -> Self {
+        match typ {
+            TypeRef::Object(_)
+            | TypeRef::EnumMap(_, _)
+            | TypeRef::StringMap(_)
+            | TypeRef::List(_) => Self::Json,
+            TypeRef::Boolean => Self::Boolean,
+            TypeRef::Int => Self::Int,
+            TypeRef::String
+            | TypeRef::BundleImage(_)
+            | TypeRef::BundleText(_)
+            | TypeRef::Enum(_) => Self::String,
+            TypeRef::Option(inner) => Self::from(inner),
+        }
+    }
+}
+
+impl From<Box<TypeRef>> for ExperimentManifestPropType {
+    fn from(typ: Box<TypeRef>) -> Self {
+        (*typ).into()
+    }
+}
+
+pub(crate) fn generate_manifest(
+    ir: FeatureManifest,
+    cmd: &GenerateExperimenterManifestCmd,
+) -> Result<()> {
+    let experiment_manifest: ExperimenterManifest = ir.try_into()?;
+    let output_str = match cmd.language {
+        TargetLanguage::ExperimenterJSON => serde_json::to_string_pretty(&experiment_manifest)?,
+        // This is currently just a re-render of the JSON in YAML.
+        // However, the YAML format will diverge in time, so experimenter can support
+        // a richer manifest format (probably involving generating schema that can validate
+        // JSON patches in the FeatureConfig.)
+        TargetLanguage::ExperimenterYAML => serde_yaml::to_string(&experiment_manifest)?,
+
+        // If in doubt, output the previously generated default.
+        _ => serde_json::to_string(&experiment_manifest)?,
+    };
+
+    std::fs::write(&cmd.output, output_str)?;
+    Ok(())
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus_fml/backends/frontend_manifest.rs.html b/book/rust-docs/src/nimbus_fml/backends/frontend_manifest.rs.html new file mode 100644 index 0000000000..9fad8c8541 --- /dev/null +++ b/book/rust-docs/src/nimbus_fml/backends/frontend_manifest.rs.html @@ -0,0 +1,263 @@ +frontend_manifest.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use std::collections::BTreeMap;
+
+use crate::frontend::{
+    EnumBody, EnumVariantBody, FeatureBody, FeatureFieldBody, FieldBody, ManifestFrontEnd,
+    ObjectBody, Types,
+};
+use crate::intermediate_representation::{
+    EnumDef, FeatureDef, FeatureManifest, ObjectDef, PropDef, VariantDef,
+};
+
+impl From<FeatureManifest> for ManifestFrontEnd {
+    fn from(value: FeatureManifest) -> Self {
+        let features = merge(&value, |fm| fm.iter_feature_defs().collect(), |f| &f.name);
+        let objects = merge(&value, |fm| fm.iter_object_defs().collect(), |o| &o.name);
+        let enums = merge(&value, |fm| fm.iter_enum_defs().collect(), |e| &e.name);
+
+        let about = value.about.description_only();
+        let channels = value.channel.into_iter().collect();
+
+        ManifestFrontEnd {
+            about: Some(about),
+            version: "1.0.0".to_string(),
+            channels,
+            includes: Default::default(),
+            imports: Default::default(),
+            features,
+            legacy_types: None,
+            types: Types { enums, objects },
+        }
+    }
+}
+
+fn merge<ListGetter, NameGetter, S, T>(
+    root: &FeatureManifest,
+    list_getter: ListGetter,
+    name_getter: NameGetter,
+) -> BTreeMap<String, T>
+where
+    S: Clone,
+    T: From<S>,
+    ListGetter: Fn(&FeatureManifest) -> Vec<&S>,
+    NameGetter: Fn(&S) -> &str,
+{
+    let mut dest: BTreeMap<String, T> = BTreeMap::new();
+
+    for s in list_getter(root) {
+        dest.insert(name_getter(s).to_string(), s.to_owned().into());
+    }
+
+    for fm in root.all_imports.values() {
+        for s in list_getter(fm) {
+            dest.insert(name_getter(s).to_string(), s.to_owned().into());
+        }
+    }
+
+    dest
+}
+
+impl From<FeatureDef> for FeatureBody {
+    fn from(value: FeatureDef) -> Self {
+        let mut variables = BTreeMap::new();
+        for f in value.props {
+            variables.insert(f.name(), f.into());
+        }
+
+        Self {
+            metadata: value.metadata,
+            variables,
+            default: None,
+            allow_coenrollment: value.allow_coenrollment,
+        }
+    }
+}
+
+impl From<ObjectDef> for ObjectBody {
+    fn from(value: ObjectDef) -> Self {
+        let mut fields = BTreeMap::new();
+        for f in value.props {
+            fields.insert(f.name.clone(), f.into());
+        }
+
+        Self {
+            description: value.doc,
+            fields,
+        }
+    }
+}
+
+impl From<EnumDef> for EnumBody {
+    fn from(value: EnumDef) -> Self {
+        let mut variants = BTreeMap::new();
+        for v in value.variants {
+            variants.insert(v.name.clone(), v.into());
+        }
+        Self {
+            description: value.doc,
+            variants,
+        }
+    }
+}
+
+impl From<VariantDef> for EnumVariantBody {
+    fn from(value: VariantDef) -> Self {
+        Self {
+            description: value.doc,
+        }
+    }
+}
+
+impl From<PropDef> for FieldBody {
+    fn from(value: PropDef) -> Self {
+        Self {
+            description: value.doc,
+            variable_type: value.typ.to_string(),
+            default: Some(value.default),
+        }
+    }
+}
+
+impl From<PropDef> for FeatureFieldBody {
+    fn from(value: PropDef) -> Self {
+        Self {
+            pref_key: value.pref_key.clone(),
+            field: value.into(),
+        }
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus_fml/backends/kotlin/gen_structs/bundled.rs.html b/book/rust-docs/src/nimbus_fml/backends/kotlin/gen_structs/bundled.rs.html new file mode 100644 index 0000000000..6251cfeba3 --- /dev/null +++ b/book/rust-docs/src/nimbus_fml/backends/kotlin/gen_structs/bundled.rs.html @@ -0,0 +1,477 @@ +bundled.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use std::fmt::Display;
+
+use super::common::{code_type, quoted};
+use crate::backends::{CodeOracle, CodeType, LiteralRenderer, TypeIdentifier, VariablesType};
+use crate::intermediate_representation::{Literal, TypeRef};
+use heck::SnakeCase;
+use unicode_segmentation::UnicodeSegmentation;
+
+pub(crate) struct TextCodeType;
+
+impl CodeType for TextCodeType {
+    /// The language specific label used to reference this type. This will be used in
+    /// method signatures and property declarations.
+    fn type_label(&self, _oracle: &dyn CodeOracle) -> String {
+        "String".into()
+    }
+
+    fn property_getter(
+        &self,
+        oracle: &dyn CodeOracle,
+        vars: &dyn Display,
+        prop: &dyn Display,
+        default: &dyn Display,
+    ) -> String {
+        code_type::property_getter(self, oracle, vars, prop, default)
+    }
+
+    fn value_getter(
+        &self,
+        oracle: &dyn CodeOracle,
+        vars: &dyn Display,
+        prop: &dyn Display,
+    ) -> String {
+        code_type::value_getter(self, oracle, vars, prop)
+    }
+
+    fn value_mapper(&self, oracle: &dyn CodeOracle) -> Option<String> {
+        code_type::value_mapper(self, oracle)
+    }
+
+    /// The name of the type as it's represented in the `Variables` object.
+    /// The string return may be used to combine with an indentifier, e.g. a `Variables` method name.
+    fn variables_type(&self, _oracle: &dyn CodeOracle) -> VariablesType {
+        VariablesType::Text
+    }
+
+    fn defaults_type(&self, _oracle: &dyn CodeOracle) -> String {
+        "StringHolder".to_string()
+    }
+
+    fn defaults_mapper(
+        &self,
+        _oracle: &dyn CodeOracle,
+        value: &dyn Display,
+        vars: &dyn Display,
+    ) -> Option<String> {
+        Some(format!(
+            "{value}.toString({vars}.context)",
+            vars = vars,
+            value = value
+        ))
+    }
+
+    /// A representation of the given literal for this type.
+    /// N.B. `Literal` is aliased from `serde_json::Value`.
+    fn literal(
+        &self,
+        _oracle: &dyn CodeOracle,
+        _ctx: &dyn Display,
+        _renderer: &dyn LiteralRenderer,
+        literal: &Literal,
+    ) -> String {
+        match literal {
+            serde_json::Value::String(v) => {
+                if !is_resource_id(v) {
+                    format!("Res.string({literal})", literal = quoted(v))
+                } else {
+                    format!("Res.string(R.string.{id})", id = v.to_snake_case())
+                }
+            }
+            _ => unreachable!("Expecting a string"),
+        }
+    }
+
+    fn preference_getter(
+        &self,
+        oracle: &dyn CodeOracle,
+        prefs: &dyn Display,
+        pref_key: &dyn Display,
+    ) -> Option<String> {
+        let ct = oracle.find(&TypeRef::String);
+        ct.preference_getter(oracle, prefs, pref_key)
+    }
+
+    fn is_resource_id(&self, literal: &Literal) -> bool {
+        match literal {
+            serde_json::Value::String(v) => is_resource_id(v),
+            _ => unreachable!("Expecting a string"),
+        }
+    }
+
+    fn imports(&self, _oracle: &dyn CodeOracle) -> Option<Vec<String>> {
+        Some(vec![
+            "android.content.Context".to_string(),
+            "org.mozilla.experiments.nimbus.Res".to_string(),
+            "org.mozilla.experiments.nimbus.StringHolder".to_string(),
+        ])
+    }
+}
+
+fn is_resource_id(string: &str) -> bool {
+    // In Android apps, resource identifiers are [a-z_][a-z0-9_]*
+    // We don't use the regex crate, so we need some code.
+    let start = "abcdefghijklmnopqrstuvwxyz_";
+    let rest = "abcdefghijklmnopqrstuvwxyz_0123456789";
+    !string.is_empty()
+        && string
+            .grapheme_indices(true)
+            .all(|(i, c)| -> bool { (i > 0 && rest.contains(c)) || start.contains(c) })
+}
+
+pub(crate) struct ImageCodeType;
+
+impl CodeType for ImageCodeType {
+    /// The language specific label used to reference this type. This will be used in
+    /// method signatures and property declarations.
+    fn type_label(&self, _oracle: &dyn CodeOracle) -> String {
+        "Res<Drawable>".into()
+    }
+
+    fn property_getter(
+        &self,
+        oracle: &dyn CodeOracle,
+        vars: &dyn Display,
+        prop: &dyn Display,
+        default: &dyn Display,
+    ) -> String {
+        code_type::property_getter(self, oracle, vars, prop, default)
+    }
+
+    fn value_getter(
+        &self,
+        oracle: &dyn CodeOracle,
+        vars: &dyn Display,
+        prop: &dyn Display,
+    ) -> String {
+        code_type::value_getter(self, oracle, vars, prop)
+    }
+
+    fn value_mapper(&self, oracle: &dyn CodeOracle) -> Option<String> {
+        code_type::value_mapper(self, oracle)
+    }
+
+    /// The name of the type as it's represented in the `Variables` object.
+    /// The string return may be used to combine with an indentifier, e.g. a `Variables` method name.
+    fn variables_type(&self, _oracle: &dyn CodeOracle) -> VariablesType {
+        VariablesType::Image
+    }
+
+    fn defaults_type(&self, oracle: &dyn CodeOracle) -> String {
+        oracle.find(&TypeIdentifier::Int).type_label(oracle)
+    }
+
+    fn defaults_mapper(
+        &self,
+        _oracle: &dyn CodeOracle,
+        value: &dyn Display,
+        vars: &dyn Display,
+    ) -> Option<String> {
+        Some(format!(
+            "Res.drawable({vars}.context, {value})",
+            vars = vars,
+            value = value
+        ))
+    }
+
+    fn as_json_transform(&self, _oracle: &dyn CodeOracle, prop: &dyn Display) -> Option<String> {
+        Some(format!("{}.resourceName", prop))
+    }
+
+    /// A representation of the given literal for this type.
+    /// N.B. `Literal` is aliased from `serde_json::Value`.
+    fn literal(
+        &self,
+        _oracle: &dyn CodeOracle,
+        _ctx: &dyn Display,
+        _renderer: &dyn LiteralRenderer,
+        literal: &Literal,
+    ) -> String {
+        match literal {
+            serde_json::Value::String(v) if is_resource_id(v) => {
+                format!(r#"R.drawable.{id}"#, id = v.to_snake_case())
+            }
+            _ => unreachable!("Expecting a string matching an image/drawable resource"),
+        }
+    }
+
+    fn is_resource_id(&self, literal: &Literal) -> bool {
+        match literal {
+            serde_json::Value::String(v) => is_resource_id(v),
+            _ => unreachable!(
+                "Expecting a string matching an image resource, with pattern [a-z][a-z0-9_]*"
+            ),
+        }
+    }
+
+    fn imports(&self, _oracle: &dyn CodeOracle) -> Option<Vec<String>> {
+        Some(vec![
+            "android.graphics.drawable.Drawable".to_string(),
+            "org.mozilla.experiments.nimbus.Res".to_string(),
+        ])
+    }
+}
+
+#[cfg(test)]
+mod unit_tests {
+
+    use super::*;
+    use crate::error::Result;
+
+    #[test]
+    fn test_is_resource_id() -> Result<()> {
+        assert!(is_resource_id("ok"));
+        assert!(is_resource_id("_ok"));
+        assert!(is_resource_id("ok_then"));
+        assert!(!is_resource_id("https://foo.com"));
+        assert!(!is_resource_id("Ok then"));
+        assert!(!is_resource_id("ok then"));
+        assert!(!is_resource_id("ok!"));
+        assert!(!is_resource_id("1ok"));
+
+        Ok(())
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus_fml/backends/kotlin/gen_structs/common.rs.html b/book/rust-docs/src/nimbus_fml/backends/kotlin/gen_structs/common.rs.html new file mode 100644 index 0000000000..f0a6e8091f --- /dev/null +++ b/book/rust-docs/src/nimbus_fml/backends/kotlin/gen_structs/common.rs.html @@ -0,0 +1,221 @@ +common.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+
// /* This Source Code Form is subject to the terms of the Mozilla Public
+//  * License, v. 2.0. If a copy of the MPL was not distributed with this
+//  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+use heck::{CamelCase, MixedCase, ShoutySnakeCase};
+use std::fmt::Display;
+
+/// Get the idiomatic Kotlin rendering of a class name (for enums, records, errors, etc).
+pub fn class_name(nm: &dyn Display) -> String {
+    nm.to_string().to_camel_case()
+}
+
+/// Get the idiomatic Kotlin rendering of a variable name.
+pub fn var_name(nm: &dyn Display) -> String {
+    nm.to_string().to_mixed_case()
+}
+
+/// Get the idiomatic Kotlin rendering of an individual enum variant.
+pub fn enum_variant_name(nm: &dyn Display) -> String {
+    nm.to_string().to_shouty_snake_case()
+}
+
+/// Surrounds a string with quotes.
+/// In Kotlin, you can """triple quote""" multi-line strings
+/// so you don't have to escape " and \n characters.
+pub fn quoted(string: &dyn Display) -> String {
+    let string = string.to_string();
+    if string.contains('"') || string.contains('\n') {
+        format!(r#""""{}""""#, string)
+    } else {
+        format!(r#""{}""#, string)
+    }
+}
+
+pub(crate) mod code_type {
+    use std::fmt::Display;
+
+    use crate::backends::{CodeOracle, CodeType};
+
+    /// The language specific expression that gets a value of the `prop` from the `vars` object.
+    pub(crate) fn property_getter(
+        ct: &dyn CodeType,
+        oracle: &dyn CodeOracle,
+        vars: &dyn Display,
+        prop: &dyn Display,
+        default: &dyn Display,
+    ) -> String {
+        let getter = ct.value_getter(oracle, vars, prop);
+        let mapper = ct.value_mapper(oracle);
+        let default = ct
+            .defaults_mapper(oracle, &default, vars)
+            .unwrap_or_else(|| default.to_string());
+        let merger = ct.value_merger(oracle, &default);
+
+        // We need to be quite careful about option chaining.
+        // Kotlin takes the `?` as an indicator to that the preceeding expression
+        // is optional to continue processing, but be aware that the
+        // expression returns an optional.
+        // Only the value_getter returns an optional, yet the optionality propogates.
+        // https://kotlinlang.org/docs/null-safety.html#safe-calls
+        let getter = match (mapper, merger) {
+            (Some(mapper), Some(merger)) => format!("{}?.{}?.{}", getter, mapper, merger),
+            (Some(mapper), None) => format!("{}?.{}", getter, mapper),
+            (None, Some(merger)) => format!("{}?.{}", getter, merger),
+            (None, None) => getter,
+        };
+
+        format!(
+            "{getter} ?: {fallback}",
+            getter = getter,
+            fallback = default
+        )
+    }
+
+    pub(crate) fn value_getter(
+        ct: &dyn CodeType,
+        oracle: &dyn CodeOracle,
+        vars: &dyn Display,
+        prop: &dyn Display,
+    ) -> String {
+        let vt = ct.variables_type(oracle);
+        format!(
+            "{vars}.get{vt}(\"{prop}\")",
+            vars = vars,
+            vt = vt,
+            prop = prop
+        )
+    }
+
+    pub(crate) fn value_mapper(ct: &dyn CodeType, oracle: &dyn CodeOracle) -> Option<String> {
+        let transform = ct.create_transform(oracle)?;
+        Some(format!("let({})", transform))
+    }
+}
+
+#[cfg(test)]
+mod unit_tests {
+    use super::*;
+
+    #[test]
+    fn test_quoted() {
+        assert_eq!(
+            quoted(&"no-quotes".to_string()),
+            "\"no-quotes\"".to_string()
+        );
+        assert_eq!(
+            quoted(&"a \"quoted\" string".to_string()),
+            "\"\"\"a \"quoted\" string\"\"\"".to_string()
+        );
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus_fml/backends/kotlin/gen_structs/enum_.rs.html b/book/rust-docs/src/nimbus_fml/backends/kotlin/gen_structs/enum_.rs.html new file mode 100644 index 0000000000..9ee7a9c094 --- /dev/null +++ b/book/rust-docs/src/nimbus_fml/backends/kotlin/gen_structs/enum_.rs.html @@ -0,0 +1,403 @@ +enum_.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use std::fmt::Display;
+
+use askama::Template;
+
+use super::common;
+use super::common::code_type;
+use super::filters;
+use crate::backends::{CodeDeclaration, CodeOracle, CodeType, LiteralRenderer, VariablesType};
+use crate::intermediate_representation::{EnumDef, FeatureManifest, Literal};
+
+pub(crate) struct EnumCodeType {
+    id: String,
+}
+
+impl EnumCodeType {
+    pub(crate) fn new(id: String) -> Self {
+        Self { id }
+    }
+}
+
+impl CodeType for EnumCodeType {
+    /// The language specific label used to reference this type. This will be used in
+    /// method signatures and property declarations.
+    fn type_label(&self, _oracle: &dyn CodeOracle) -> String {
+        common::class_name(&self.id)
+    }
+
+    fn property_getter(
+        &self,
+        oracle: &dyn CodeOracle,
+        vars: &dyn Display,
+        prop: &dyn Display,
+        default: &dyn Display,
+    ) -> String {
+        code_type::property_getter(self, oracle, vars, prop, default)
+    }
+
+    fn value_getter(
+        &self,
+        oracle: &dyn CodeOracle,
+        vars: &dyn Display,
+        prop: &dyn Display,
+    ) -> String {
+        code_type::value_getter(self, oracle, vars, prop)
+    }
+
+    fn value_mapper(&self, oracle: &dyn CodeOracle) -> Option<String> {
+        code_type::value_mapper(self, oracle)
+    }
+
+    /// The name of the type as it's represented in the `Variables` object.
+    /// The string return may be used to combine with an indentifier, e.g. a `Variables` method name.
+    fn variables_type(&self, _oracle: &dyn CodeOracle) -> VariablesType {
+        VariablesType::String
+    }
+
+    /// A function handle that is capable of turning the variables type to the TypeRef type.
+    fn create_transform(&self, oracle: &dyn CodeOracle) -> Option<String> {
+        Some(format!(
+            "{enum_type}::enumValue",
+            enum_type = self.type_label(oracle)
+        ))
+    }
+
+    fn as_json_transform(&self, _oracle: &dyn CodeOracle, prop: &dyn Display) -> Option<String> {
+        Some(format!("{}.toJSONString()", prop))
+    }
+
+    /// A representation of the given literal for this type.
+    /// N.B. `Literal` is aliased from `serde_json::Value`.
+    fn literal(
+        &self,
+        oracle: &dyn CodeOracle,
+        _ctx: &dyn Display,
+        _renderer: &dyn LiteralRenderer,
+        literal: &Literal,
+    ) -> String {
+        let variant = match literal {
+            serde_json::Value::String(v) => v,
+            _ => unreachable!(),
+        };
+
+        format!(
+            "{}.{}",
+            self.type_label(oracle),
+            common::enum_variant_name(variant)
+        )
+    }
+}
+#[derive(Template)]
+#[template(syntax = "kt", escape = "none", path = "EnumTemplate.kt")]
+pub(crate) struct EnumCodeDeclaration {
+    inner: EnumDef,
+}
+
+impl EnumCodeDeclaration {
+    pub fn new(_fm: &FeatureManifest, inner: &EnumDef) -> Self {
+        Self {
+            inner: inner.clone(),
+        }
+    }
+    fn inner(&self) -> EnumDef {
+        self.inner.clone()
+    }
+}
+
+impl CodeDeclaration for EnumCodeDeclaration {
+    fn definition_code(&self, _oracle: &dyn CodeOracle) -> Option<String> {
+        Some(self.render().unwrap())
+    }
+}
+
+#[cfg(test)]
+mod unit_tests {
+
+    use serde_json::json;
+
+    use super::*;
+    use crate::backends::TypeIdentifier;
+
+    struct TestCodeOracle;
+    impl CodeOracle for TestCodeOracle {
+        fn find(&self, _type_: &TypeIdentifier) -> Box<dyn CodeType> {
+            unreachable!()
+        }
+    }
+
+    struct TestRenderer;
+    impl LiteralRenderer for TestRenderer {
+        fn literal(
+            &self,
+            _oracle: &dyn CodeOracle,
+            _typ: &TypeIdentifier,
+            _value: &Literal,
+            _ctx: &dyn Display,
+        ) -> String {
+            unreachable!()
+        }
+    }
+
+    fn oracle() -> Box<dyn CodeOracle> {
+        Box::new(TestCodeOracle) as Box<dyn CodeOracle>
+    }
+
+    fn code_type(name: &str) -> Box<dyn CodeType> {
+        Box::new(EnumCodeType::new(name.to_string())) as Box<dyn CodeType>
+    }
+
+    #[test]
+    fn test_type_label() {
+        let ct = code_type("AEnum");
+        let oracle = &*oracle();
+        assert_eq!("AEnum".to_string(), ct.type_label(oracle))
+    }
+
+    #[test]
+    fn test_literal() {
+        let ct = code_type("AEnum");
+        let oracle = &*oracle();
+        let finder = &TestRenderer;
+        let ctx = String::from("ctx");
+        assert_eq!(
+            "AEnum.FOO".to_string(),
+            ct.literal(oracle, &ctx, finder, &json!("foo"))
+        );
+        assert_eq!(
+            "AEnum.BAR_BAZ".to_string(),
+            ct.literal(oracle, &ctx, finder, &json!("barBaz"))
+        );
+        assert_eq!(
+            "AEnum.A_B_C".to_string(),
+            ct.literal(oracle, &ctx, finder, &json!("a-b-c"))
+        );
+    }
+
+    #[test]
+    fn test_get_value() {
+        let ct = code_type("AEnum");
+        let oracle = &*oracle();
+
+        assert_eq!(
+            r#"v.getString("the-property")"#.to_string(),
+            ct.value_getter(oracle, &"v", &"the-property")
+        );
+    }
+
+    #[test]
+    fn test_getter_with_fallback() {
+        let ct = code_type("AEnum");
+        let oracle = &*oracle();
+
+        assert_eq!(
+            r#"v.getString("the-property")?.let(AEnum::enumValue) ?: def"#.to_string(),
+            ct.property_getter(oracle, &"v", &"the-property", &"def")
+        );
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus_fml/backends/kotlin/gen_structs/feature.rs.html b/book/rust-docs/src/nimbus_fml/backends/kotlin/gen_structs/feature.rs.html new file mode 100644 index 0000000000..f367fb43d2 --- /dev/null +++ b/book/rust-docs/src/nimbus_fml/backends/kotlin/gen_structs/feature.rs.html @@ -0,0 +1,119 @@ +feature.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use std::fmt::Display;
+
+use askama::Template;
+
+use super::filters;
+use super::object::object_literal;
+use crate::{
+    backends::{CodeDeclaration, CodeOracle, LiteralRenderer, TypeIdentifier},
+    intermediate_representation::{FeatureDef, FeatureManifest, Literal},
+};
+
+#[derive(Template)]
+#[template(syntax = "kt", escape = "none", path = "FeatureTemplate.kt")]
+pub(crate) struct FeatureCodeDeclaration {
+    inner: FeatureDef,
+    fm: FeatureManifest,
+}
+
+impl FeatureCodeDeclaration {
+    pub fn new(fm: &FeatureManifest, inner: &FeatureDef) -> Self {
+        Self {
+            inner: inner.clone(),
+            fm: fm.clone(),
+        }
+    }
+    pub fn inner(&self) -> &FeatureDef {
+        &self.inner
+    }
+}
+
+impl CodeDeclaration for FeatureCodeDeclaration {
+    fn definition_code(&self, _oracle: &dyn CodeOracle) -> Option<String> {
+        Some(self.render().unwrap())
+    }
+
+    fn imports(&self, _oracle: &dyn CodeOracle) -> Option<Vec<String>> {
+        Some(vec![
+            "org.mozilla.experiments.nimbus.internal.FeatureHolder".to_string(),
+            "org.mozilla.experiments.nimbus.internal.FMLFeatureInterface".to_string(),
+            "org.mozilla.experiments.nimbus.NullVariables".to_string(),
+        ])
+    }
+}
+
+impl LiteralRenderer for FeatureCodeDeclaration {
+    fn literal(
+        &self,
+        oracle: &dyn CodeOracle,
+        typ: &TypeIdentifier,
+        value: &Literal,
+        ctx: &dyn Display,
+    ) -> String {
+        object_literal(&self.fm, ctx, &self, oracle, typ, value)
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus_fml/backends/kotlin/gen_structs/filters.rs.html b/book/rust-docs/src/nimbus_fml/backends/kotlin/gen_structs/filters.rs.html new file mode 100644 index 0000000000..c725acf1f4 --- /dev/null +++ b/book/rust-docs/src/nimbus_fml/backends/kotlin/gen_structs/filters.rs.html @@ -0,0 +1,209 @@ +filters.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+
// /* This Source Code Form is subject to the terms of the Mozilla Public
+//  * License, v. 2.0. If a copy of the MPL was not distributed with this
+//  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+use super::{common, ConcreteCodeOracle};
+use std::borrow::Borrow;
+use std::fmt::{self, Display};
+
+use crate::backends::{CodeOracle, LiteralRenderer, TypeIdentifier};
+use crate::intermediate_representation::Literal;
+
+pub fn type_label(type_: impl Borrow<TypeIdentifier>) -> Result<String, askama::Error> {
+    let oracle = ConcreteCodeOracle;
+    Ok(oracle.find(type_.borrow()).type_label(&oracle))
+}
+
+pub fn defaults_type_label(type_: impl Borrow<TypeIdentifier>) -> Result<String, askama::Error> {
+    let oracle = ConcreteCodeOracle;
+    Ok(oracle.find(type_.borrow()).defaults_type(&oracle))
+}
+
+pub fn literal(
+    type_: impl Borrow<TypeIdentifier>,
+    renderer: impl LiteralRenderer,
+    literal: impl Borrow<Literal>,
+    ctx: impl Display,
+) -> Result<String, askama::Error> {
+    let oracle = ConcreteCodeOracle;
+    Ok(oracle
+        .find(type_.borrow())
+        .literal(&oracle, &ctx, &renderer, literal.borrow()))
+}
+
+pub fn property(
+    type_: impl Borrow<TypeIdentifier>,
+    prop: impl fmt::Display,
+    vars: impl fmt::Display,
+    default: impl fmt::Display,
+) -> Result<String, askama::Error> {
+    let oracle = &ConcreteCodeOracle;
+    let ct = oracle.find(type_.borrow());
+    Ok(ct.property_getter(oracle, &vars, &prop, &default))
+}
+
+pub fn preference_getter(
+    type_: impl Borrow<TypeIdentifier>,
+    prefs: impl fmt::Display,
+    pref_key: impl fmt::Display,
+) -> Result<String, askama::Error> {
+    let oracle = &ConcreteCodeOracle;
+    let ct = oracle.find(type_.borrow());
+    if let Some(getter) = ct.preference_getter(oracle, &prefs, &pref_key) {
+        Ok(getter)
+    } else {
+        unreachable!("The preference for type {} isn't available. This is a bug in Nimbus FML Kotlin generator", type_.borrow());
+    }
+}
+
+pub fn to_json(
+    prop: impl fmt::Display,
+    type_: impl Borrow<TypeIdentifier>,
+) -> Result<String, askama::Error> {
+    let oracle = &ConcreteCodeOracle;
+    let ct = oracle.find(type_.borrow());
+    Ok(ct.as_json(oracle, &prop))
+}
+
+/// Get the idiomatic Kotlin rendering of a class name (for enums, records, errors, etc).
+pub fn class_name(nm: impl fmt::Display) -> Result<String, askama::Error> {
+    Ok(common::class_name(&nm))
+}
+
+/// Get the idiomatic Kotlin rendering of a variable name.
+pub fn var_name(nm: impl fmt::Display) -> Result<String, askama::Error> {
+    Ok(common::var_name(&nm))
+}
+
+/// Get the idiomatic Kotlin rendering of an individual enum variant.
+pub fn enum_variant_name(nm: impl fmt::Display) -> Result<String, askama::Error> {
+    Ok(common::enum_variant_name(&nm))
+}
+
+pub fn comment(txt: impl fmt::Display, spaces: &str) -> Result<String, askama::Error> {
+    use textwrap::{fill, Options};
+
+    let indent_start = "/** ".to_string();
+    let indent_mid = format!("{} * ", spaces);
+    let indent_end = format!("{} */", spaces);
+
+    let options = Options::new(80)
+        .initial_indent(&indent_mid)
+        .subsequent_indent(&indent_mid);
+
+    let lines = fill(txt.to_string().as_str(), options);
+    Ok(format!(
+        "{start}\n{lines}\n{indent}",
+        start = indent_start,
+        lines = lines,
+        indent = indent_end
+    ))
+}
+
+pub fn quoted(txt: impl fmt::Display) -> Result<String, askama::Error> {
+    Ok(common::quoted(&txt))
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus_fml/backends/kotlin/gen_structs/imports.rs.html b/book/rust-docs/src/nimbus_fml/backends/kotlin/gen_structs/imports.rs.html new file mode 100644 index 0000000000..dad243cd4d --- /dev/null +++ b/book/rust-docs/src/nimbus_fml/backends/kotlin/gen_structs/imports.rs.html @@ -0,0 +1,129 @@ +imports.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use std::fmt::Display;
+
+use super::{filters, object::object_literal};
+use crate::{
+    backends::{CodeDeclaration, CodeOracle, LiteralRenderer, TypeIdentifier},
+    intermediate_representation::{ImportedModule, Literal, TypeFinder},
+};
+use askama::Template;
+
+#[derive(Template)]
+#[template(
+    syntax = "kt",
+    escape = "none",
+    path = "ImportedModuleInitializationTemplate.kt"
+)]
+pub(crate) struct ImportedModuleInitialization<'a> {
+    pub(crate) inner: ImportedModule<'a>,
+}
+
+impl<'a> ImportedModuleInitialization<'a> {
+    pub(crate) fn new(inner: ImportedModule<'a>) -> Self {
+        Self { inner }
+    }
+}
+
+impl CodeDeclaration for ImportedModuleInitialization<'_> {
+    fn imports(&self, oracle: &dyn CodeOracle) -> Option<Vec<String>> {
+        let p = self.inner.about().nimbus_package_name()?;
+        Some(
+            self.inner
+                .fm
+                .all_types()
+                .iter()
+                .filter_map(|t| oracle.find(t).imports(oracle))
+                .flatten()
+                .chain(vec![format!("{}.*", p)])
+                .collect::<Vec<_>>(),
+        )
+    }
+
+    fn initialization_code(&self, _oracle: &dyn CodeOracle) -> Option<String> {
+        Some(self.render().unwrap())
+    }
+
+    fn definition_code(&self, _oracle: &dyn CodeOracle) -> Option<String> {
+        None
+    }
+}
+
+impl LiteralRenderer for ImportedModuleInitialization<'_> {
+    fn literal(
+        &self,
+        oracle: &dyn CodeOracle,
+        typ: &TypeIdentifier,
+        value: &Literal,
+        ctx: &dyn Display,
+    ) -> String {
+        object_literal(self.inner.fm, ctx, &self, oracle, typ, value)
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus_fml/backends/kotlin/gen_structs/mod.rs.html b/book/rust-docs/src/nimbus_fml/backends/kotlin/gen_structs/mod.rs.html new file mode 100644 index 0000000000..627c68ec33 --- /dev/null +++ b/book/rust-docs/src/nimbus_fml/backends/kotlin/gen_structs/mod.rs.html @@ -0,0 +1,351 @@ +mod.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use askama::Template;
+use std::collections::HashSet;
+
+use crate::intermediate_representation::PropDef;
+use crate::{
+    backends::{CodeDeclaration, CodeOracle, CodeType, TypeIdentifier},
+    intermediate_representation::{FeatureDef, FeatureManifest, TypeFinder},
+};
+
+mod bundled;
+mod common;
+mod enum_;
+mod feature;
+mod filters;
+mod imports;
+mod object;
+mod primitives;
+mod structural;
+#[derive(Template)]
+#[template(syntax = "kt", escape = "none", path = "FeatureManifestTemplate.kt")]
+pub struct FeatureManifestDeclaration<'a> {
+    fm: &'a FeatureManifest,
+    oracle: ConcreteCodeOracle,
+}
+impl<'a> FeatureManifestDeclaration<'a> {
+    pub fn new(fm: &'a FeatureManifest) -> Self {
+        Self {
+            fm,
+            oracle: Default::default(),
+        }
+    }
+
+    pub fn members(&self) -> Vec<Box<dyn CodeDeclaration + 'a>> {
+        let fm = self.fm;
+
+        fm.iter_feature_defs()
+            .map(|inner| {
+                Box::new(feature::FeatureCodeDeclaration::new(fm, inner))
+                    as Box<dyn CodeDeclaration>
+            })
+            .chain(fm.iter_enum_defs().map(|inner| {
+                Box::new(enum_::EnumCodeDeclaration::new(fm, inner)) as Box<dyn CodeDeclaration>
+            }))
+            .chain(fm.iter_object_defs().map(|inner| {
+                Box::new(object::ObjectCodeDeclaration::new(fm, inner)) as Box<dyn CodeDeclaration>
+            }))
+            .chain(fm.iter_imported_files().into_iter().map(|inner| {
+                Box::new(imports::ImportedModuleInitialization::new(inner))
+                    as Box<dyn CodeDeclaration>
+            }))
+            .collect()
+    }
+
+    pub fn feature_properties(&self) -> Vec<PropDef> {
+        let fm = self.fm;
+
+        fm.iter_feature_defs()
+            .flat_map(|feature| feature.props())
+            .chain(
+                fm.iter_object_defs()
+                    .flat_map(|object| object.props.clone()),
+            )
+            .chain(fm.iter_imported_files().into_iter().flat_map(|inner| {
+                inner
+                    .fm
+                    .iter_feature_defs()
+                    .flat_map(|feature| feature.props())
+            }))
+            .collect()
+    }
+
+    pub fn iter_feature_defs(&self) -> Vec<&FeatureDef> {
+        self.fm.iter_feature_defs().collect::<_>()
+    }
+
+    pub fn initialization_code(&self) -> Vec<String> {
+        let oracle = &self.oracle;
+        self.members()
+            .into_iter()
+            .filter_map(|member| member.initialization_code(oracle))
+            .collect()
+    }
+
+    pub fn declaration_code(&self) -> Vec<String> {
+        let oracle = &self.oracle;
+        self.members()
+            .into_iter()
+            .filter_map(|member| member.definition_code(oracle))
+            .collect()
+    }
+
+    pub fn imports(&self) -> Vec<String> {
+        let oracle = &self.oracle;
+        // We'll filter out objects from the package we're in.
+        let my_package = format!(
+            "{}.*",
+            self.fm.about.nimbus_package_name().unwrap_or_default()
+        );
+        let mut imports: Vec<String> = self
+            .members()
+            .into_iter()
+            .filter_map(|member| member.imports(oracle))
+            .flatten()
+            .chain(
+                self.fm
+                    .all_types()
+                    .into_iter()
+                    .filter_map(|type_| self.oracle.find(&type_).imports(oracle))
+                    .flatten(),
+            )
+            .chain(vec![
+                "org.mozilla.experiments.nimbus.Variables".to_string(),
+                "org.mozilla.experiments.nimbus.internal.FeatureHolder".to_string(),
+                "org.mozilla.experiments.nimbus.internal.FeatureManifestInterface".to_string(),
+                "org.mozilla.experiments.nimbus.FeaturesInterface".to_string(),
+                "org.json.JSONObject".to_string(),
+                "android.content.SharedPreferences".to_string(),
+            ])
+            .filter(|i| i != &my_package)
+            .collect::<HashSet<String>>()
+            .into_iter()
+            .collect();
+
+        let include_r: bool = self
+            .feature_properties()
+            .into_iter()
+            .map(|prop| self.oracle.find(&prop.typ()).is_resource_id(&prop.default))
+            .any(|v| v);
+        if include_r {
+            imports.push(format!("{}.R", self.fm.about.resource_package_name()))
+        }
+
+        imports.sort();
+        imports
+    }
+}
+
+#[derive(Default, Clone)]
+pub struct ConcreteCodeOracle;
+
+impl ConcreteCodeOracle {
+    fn create_code_type(&self, type_: TypeIdentifier) -> Box<dyn CodeType> {
+        match type_ {
+            TypeIdentifier::Boolean => Box::new(primitives::BooleanCodeType),
+            TypeIdentifier::String => Box::new(primitives::StringCodeType),
+            TypeIdentifier::Int => Box::new(primitives::IntCodeType),
+
+            TypeIdentifier::BundleText(_) => Box::new(bundled::TextCodeType),
+            TypeIdentifier::BundleImage(_) => Box::new(bundled::ImageCodeType),
+
+            TypeIdentifier::Enum(id) => Box::new(enum_::EnumCodeType::new(id)),
+            TypeIdentifier::Object(id) => Box::new(object::ObjectCodeType::new(id)),
+
+            TypeIdentifier::Option(ref inner) => Box::new(structural::OptionalCodeType::new(inner)),
+            TypeIdentifier::List(ref inner) => Box::new(structural::ListCodeType::new(inner)),
+            TypeIdentifier::StringMap(ref v_type) => {
+                let k_type = &TypeIdentifier::String;
+                Box::new(structural::MapCodeType::new(k_type, v_type))
+            }
+            TypeIdentifier::EnumMap(ref k_type, ref v_type) => {
+                Box::new(structural::MapCodeType::new(k_type, v_type))
+            }
+        }
+    }
+}
+
+impl CodeOracle for ConcreteCodeOracle {
+    fn find(&self, type_: &TypeIdentifier) -> Box<dyn CodeType> {
+        self.create_code_type(type_.clone())
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus_fml/backends/kotlin/gen_structs/object.rs.html b/book/rust-docs/src/nimbus_fml/backends/kotlin/gen_structs/object.rs.html new file mode 100644 index 0000000000..54417bc421 --- /dev/null +++ b/book/rust-docs/src/nimbus_fml/backends/kotlin/gen_structs/object.rs.html @@ -0,0 +1,549 @@ +object.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use askama::Template;
+use std::fmt::Display;
+
+use crate::backends::{
+    CodeDeclaration, CodeOracle, CodeType, LiteralRenderer, TypeIdentifier, VariablesType,
+};
+use crate::intermediate_representation::{FeatureManifest, Literal, ObjectDef};
+
+use super::filters;
+
+use super::common::{self, code_type};
+
+pub struct ObjectRuntime;
+
+impl CodeDeclaration for ObjectRuntime {}
+
+pub struct ObjectCodeType {
+    id: String,
+}
+
+impl ObjectCodeType {
+    pub fn new(id: String) -> Self {
+        Self { id }
+    }
+}
+
+impl CodeType for ObjectCodeType {
+    fn type_label(&self, _oracle: &dyn CodeOracle) -> String {
+        common::class_name(&self.id)
+    }
+
+    fn property_getter(
+        &self,
+        oracle: &dyn CodeOracle,
+        vars: &dyn Display,
+        prop: &dyn Display,
+        default: &dyn Display,
+    ) -> String {
+        code_type::property_getter(self, oracle, vars, prop, default)
+    }
+
+    fn value_getter(
+        &self,
+        oracle: &dyn CodeOracle,
+        vars: &dyn Display,
+        prop: &dyn Display,
+    ) -> String {
+        code_type::value_getter(self, oracle, vars, prop)
+    }
+
+    fn value_mapper(&self, oracle: &dyn CodeOracle) -> Option<String> {
+        code_type::value_mapper(self, oracle)
+    }
+
+    /// The language specific expression that gets a value of the `prop` from the `vars` object.
+
+    /// The name of the type as it's represented in the `Variables` object.
+    /// The string return may be used to combine with an indentifier, e.g. a `Variables` method name.
+    fn variables_type(&self, _oracle: &dyn CodeOracle) -> VariablesType {
+        VariablesType::Variables
+    }
+
+    fn create_transform(&self, oracle: &dyn CodeOracle) -> Option<String> {
+        Some(format!("{}::create", self.type_label(oracle)))
+    }
+
+    fn merge_transform(&self, oracle: &dyn CodeOracle) -> Option<String> {
+        Some(format!("{}::mergeWith", self.type_label(oracle)))
+    }
+
+    fn value_merger(&self, _oracle: &dyn CodeOracle, default: &dyn Display) -> Option<String> {
+        Some(format!("_mergeWith({})", default))
+    }
+
+    fn as_json_transform(&self, _oracle: &dyn CodeOracle, prop: &dyn Display) -> Option<String> {
+        Some(format!("{}.toJSONObject()", prop))
+    }
+
+    fn literal(
+        &self,
+        oracle: &dyn CodeOracle,
+        ctx: &dyn Display,
+        renderer: &dyn LiteralRenderer,
+        literal: &Literal,
+    ) -> String {
+        renderer.literal(
+            oracle,
+            &TypeIdentifier::Object(self.id.clone()),
+            literal,
+            ctx,
+        )
+    }
+}
+
+#[derive(Template)]
+#[template(syntax = "kt", escape = "none", path = "ObjectTemplate.kt")]
+pub(crate) struct ObjectCodeDeclaration {
+    inner: ObjectDef,
+    fm: FeatureManifest,
+}
+
+impl ObjectCodeDeclaration {
+    pub fn new(fm: &FeatureManifest, inner: &ObjectDef) -> Self {
+        Self {
+            fm: fm.clone(),
+            inner: inner.clone(),
+        }
+    }
+    pub fn inner(&self) -> ObjectDef {
+        self.inner.clone()
+    }
+}
+
+impl CodeDeclaration for ObjectCodeDeclaration {
+    fn definition_code(&self, _oracle: &dyn CodeOracle) -> Option<String> {
+        Some(self.render().unwrap())
+    }
+
+    fn imports(&self, _oracle: &dyn CodeOracle) -> Option<Vec<String>> {
+        Some(vec![
+            "org.mozilla.experiments.nimbus.internal.FMLObjectInterface".to_string(),
+        ])
+    }
+}
+
+impl LiteralRenderer for ObjectCodeDeclaration {
+    fn literal(
+        &self,
+        oracle: &dyn CodeOracle,
+        typ: &TypeIdentifier,
+        value: &Literal,
+        ctx: &dyn Display,
+    ) -> String {
+        object_literal(&self.fm, ctx, &self, oracle, typ, value)
+    }
+}
+
+pub(crate) fn object_literal(
+    fm: &FeatureManifest,
+    ctx: &dyn Display,
+    renderer: &dyn LiteralRenderer,
+    oracle: &dyn CodeOracle,
+    typ: &TypeIdentifier,
+    value: &Literal,
+) -> String {
+    let id = if let TypeIdentifier::Object(id) = typ {
+        id
+    } else {
+        return oracle.find(typ).literal(oracle, ctx, renderer, value);
+    };
+    let literal_map = if let Literal::Object(map) = value {
+        map
+    } else {
+        unreachable!(
+            "An JSON object is expected for {} object literal",
+            oracle.find(typ).type_label(oracle)
+        )
+    };
+
+    let def = fm.find_object(id).unwrap();
+
+    let args: Vec<String> = literal_map
+        .iter()
+        .map(|(k, v)| {
+            let prop = def.find_prop(k);
+
+            format!(
+                "{var_name} = {var_value}",
+                var_name = common::var_name(k),
+                var_value = oracle.find(&prop.typ).literal(oracle, ctx, renderer, v)
+            )
+        })
+        .collect();
+
+    format!(
+        "{typelabel}({args})",
+        typelabel = oracle.find(typ).type_label(oracle),
+        args = args.join(", ")
+    )
+}
+
+#[cfg(test)]
+mod unit_tests {
+    use serde_json::json;
+
+    use crate::{backends::TypeIdentifier, intermediate_representation::Literal};
+
+    use super::*;
+
+    struct TestCodeOracle;
+    impl CodeOracle for TestCodeOracle {
+        fn find(&self, _type_: &TypeIdentifier) -> Box<dyn CodeType> {
+            unreachable!()
+        }
+    }
+
+    struct TestRenderer;
+    impl LiteralRenderer for TestRenderer {
+        fn literal(
+            &self,
+            _oracle: &dyn CodeOracle,
+            typ: &TypeIdentifier,
+            _value: &Literal,
+            _ctx: &dyn Display,
+        ) -> String {
+            if let TypeIdentifier::Object(nm) = typ {
+                format!("{}()", nm)
+            } else {
+                unreachable!()
+            }
+        }
+    }
+
+    fn oracle() -> Box<dyn CodeOracle> {
+        Box::new(TestCodeOracle) as Box<dyn CodeOracle>
+    }
+
+    fn code_type(name: &str) -> Box<dyn CodeType> {
+        Box::new(ObjectCodeType::new(name.to_string())) as Box<dyn CodeType>
+    }
+
+    fn getter_with_fallback(
+        ct: &dyn CodeType,
+        vars: &dyn Display,
+        prop: &dyn Display,
+        def: &dyn Display,
+    ) -> String {
+        let oracle = &*oracle();
+        ct.property_getter(oracle, vars, prop, def)
+    }
+
+    #[test]
+    fn test_type_label() {
+        let ct = code_type("AnObject");
+        let oracle = &*oracle();
+        assert_eq!("AnObject".to_string(), ct.type_label(oracle))
+    }
+
+    #[test]
+    fn test_literal() {
+        let ct = code_type("AnObject");
+        let oracle = &*oracle();
+        let finder = &TestRenderer;
+        let ctx = "ctx".to_string();
+        assert_eq!(
+            "AnObject()".to_string(),
+            ct.literal(oracle, &ctx, finder, &json!({}))
+        );
+    }
+
+    #[test]
+    fn test_get_value() {
+        let ct = code_type("AnObject");
+        let oracle = &*oracle();
+
+        assert_eq!(
+            r#"v.getVariables("the-property")"#.to_string(),
+            ct.value_getter(oracle, &"v", &"the-property")
+        );
+    }
+
+    #[test]
+    fn test_getter_with_fallback() {
+        let ct = code_type("AnObject");
+        assert_eq!(
+            r#"vars.getVariables("the-property")?.let(AnObject::create)?._mergeWith(default) ?: default"#
+            .to_string(),
+            getter_with_fallback(&*ct, &"vars", &"the-property", &"default"));
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus_fml/backends/kotlin/gen_structs/primitives.rs.html b/book/rust-docs/src/nimbus_fml/backends/kotlin/gen_structs/primitives.rs.html new file mode 100644 index 0000000000..18f0401379 --- /dev/null +++ b/book/rust-docs/src/nimbus_fml/backends/kotlin/gen_structs/primitives.rs.html @@ -0,0 +1,651 @@ +primitives.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use std::fmt::Display;
+
+use super::common::{code_type, quoted};
+use crate::backends::{CodeOracle, CodeType, LiteralRenderer, VariablesType};
+use crate::intermediate_representation::Literal;
+
+pub(crate) struct BooleanCodeType;
+
+impl CodeType for BooleanCodeType {
+    /// The language specific label used to reference this type. This will be used in
+    /// method signatures and property declarations.
+    fn type_label(&self, _oracle: &dyn CodeOracle) -> String {
+        "Boolean".into()
+    }
+
+    fn property_getter(
+        &self,
+        oracle: &dyn CodeOracle,
+        vars: &dyn Display,
+        prop: &dyn Display,
+        default: &dyn Display,
+    ) -> String {
+        code_type::property_getter(self, oracle, vars, prop, default)
+    }
+
+    fn value_getter(
+        &self,
+        oracle: &dyn CodeOracle,
+        vars: &dyn Display,
+        prop: &dyn Display,
+    ) -> String {
+        code_type::value_getter(self, oracle, vars, prop)
+    }
+
+    fn value_mapper(&self, oracle: &dyn CodeOracle) -> Option<String> {
+        code_type::value_mapper(self, oracle)
+    }
+
+    /// The name of the type as it's represented in the `Variables` object.
+    /// The string return may be used to combine with an indentifier, e.g. a `Variables` method name.
+    fn variables_type(&self, _oracle: &dyn CodeOracle) -> VariablesType {
+        VariablesType::Bool
+    }
+
+    /// A representation of the given literal for this type.
+    /// N.B. `Literal` is aliased from `serde_json::Value`.
+    fn literal(
+        &self,
+        _oracle: &dyn CodeOracle,
+        _ctx: &dyn Display,
+        _renderer: &dyn LiteralRenderer,
+        literal: &Literal,
+    ) -> String {
+        match literal {
+            serde_json::Value::Bool(v) => {
+                if *v {
+                    "true".to_string()
+                } else {
+                    "false".to_string()
+                }
+            }
+            _ => unreachable!("Expecting a boolean"),
+        }
+    }
+
+    fn preference_getter(
+        &self,
+        _oracle: &dyn CodeOracle,
+        prefs: &dyn Display,
+        pref_key: &dyn Display,
+    ) -> Option<String> {
+        Some(format!("{prefs}.getBoolean({}, false)", quoted(pref_key)))
+    }
+}
+
+pub(crate) struct IntCodeType;
+
+impl CodeType for IntCodeType {
+    /// The language specific label used to reference this type. This will be used in
+    /// method signatures and property declarations.
+    fn type_label(&self, _oracle: &dyn CodeOracle) -> String {
+        "Int".into()
+    }
+
+    fn property_getter(
+        &self,
+        oracle: &dyn CodeOracle,
+        vars: &dyn Display,
+        prop: &dyn Display,
+        default: &dyn Display,
+    ) -> String {
+        code_type::property_getter(self, oracle, vars, prop, default)
+    }
+
+    fn value_getter(
+        &self,
+        oracle: &dyn CodeOracle,
+        vars: &dyn Display,
+        prop: &dyn Display,
+    ) -> String {
+        code_type::value_getter(self, oracle, vars, prop)
+    }
+
+    fn value_mapper(&self, oracle: &dyn CodeOracle) -> Option<String> {
+        code_type::value_mapper(self, oracle)
+    }
+
+    /// The name of the type as it's represented in the `Variables` object.
+    /// The string return may be used to combine with an indentifier, e.g. a `Variables` method name.
+    fn variables_type(&self, _oracle: &dyn CodeOracle) -> VariablesType {
+        VariablesType::Int
+    }
+
+    /// A representation of the given literal for this type.
+    /// N.B. `Literal` is aliased from `serde_json::Value`.
+    fn literal(
+        &self,
+        _oracle: &dyn CodeOracle,
+        _ctx: &dyn Display,
+        _renderer: &dyn LiteralRenderer,
+        literal: &Literal,
+    ) -> String {
+        match literal {
+            serde_json::Value::Number(v) => {
+                format!("{:.0}", v)
+            }
+            _ => unreachable!("Expecting a number"),
+        }
+    }
+
+    fn preference_getter(
+        &self,
+        _oracle: &dyn CodeOracle,
+        prefs: &dyn Display,
+        pref_key: &dyn Display,
+    ) -> Option<String> {
+        Some(format!("{prefs}.getInt({}, 0)", quoted(pref_key)))
+    }
+}
+
+pub(crate) struct StringCodeType;
+
+impl CodeType for StringCodeType {
+    /// The language specific label used to reference this type. This will be used in
+    /// method signatures and property declarations.
+    fn type_label(&self, _oracle: &dyn CodeOracle) -> String {
+        "String".into()
+    }
+
+    fn property_getter(
+        &self,
+        oracle: &dyn CodeOracle,
+        vars: &dyn Display,
+        prop: &dyn Display,
+        default: &dyn Display,
+    ) -> String {
+        code_type::property_getter(self, oracle, vars, prop, default)
+    }
+
+    fn value_getter(
+        &self,
+        oracle: &dyn CodeOracle,
+        vars: &dyn Display,
+        prop: &dyn Display,
+    ) -> String {
+        code_type::value_getter(self, oracle, vars, prop)
+    }
+
+    fn value_mapper(&self, oracle: &dyn CodeOracle) -> Option<String> {
+        code_type::value_mapper(self, oracle)
+    }
+
+    /// The name of the type as it's represented in the `Variables` object.
+    /// The string return may be used to combine with an indentifier, e.g. a `Variables` method name.
+    fn variables_type(&self, _oracle: &dyn CodeOracle) -> VariablesType {
+        VariablesType::String
+    }
+
+    /// A representation of the given literal for this type.
+    /// N.B. `Literal` is aliased from `serde_json::Value`.
+    fn literal(
+        &self,
+        _oracle: &dyn CodeOracle,
+        _ctx: &dyn Display,
+        _renderer: &dyn LiteralRenderer,
+        literal: &Literal,
+    ) -> String {
+        match literal {
+            serde_json::Value::String(v) => {
+                // Usually, we'd be wanting to escape this, for security reasons. However, this is
+                // will cause a kotlinc compile time error when the app is built if the string is malformed
+                // in the manifest.
+                quoted(v)
+            }
+            _ => unreachable!("Expecting a string"),
+        }
+    }
+
+    fn preference_getter(
+        &self,
+        _oracle: &dyn CodeOracle,
+        prefs: &dyn Display,
+        pref_key: &dyn Display,
+    ) -> Option<String> {
+        Some(format!("{prefs}.getString({}, \"\")", quoted(pref_key)))
+    }
+}
+
+#[cfg(test)]
+mod unit_tests {
+
+    use serde_json::json;
+
+    use crate::backends::TypeIdentifier;
+
+    use super::*;
+
+    struct TestCodeOracle;
+    impl CodeOracle for TestCodeOracle {
+        fn find(&self, _type_: &TypeIdentifier) -> Box<dyn CodeType> {
+            unreachable!()
+        }
+    }
+
+    struct TestRenderer;
+    impl LiteralRenderer for TestRenderer {
+        fn literal(
+            &self,
+            _oracle: &dyn CodeOracle,
+            _typ: &TypeIdentifier,
+            _value: &Literal,
+            _ctx: &dyn Display,
+        ) -> String {
+            unreachable!()
+        }
+    }
+
+    fn oracle() -> Box<dyn CodeOracle> {
+        Box::new(TestCodeOracle) as Box<dyn CodeOracle>
+    }
+
+    fn bool_type() -> Box<dyn CodeType> {
+        Box::new(BooleanCodeType) as Box<dyn CodeType>
+    }
+
+    fn string_type() -> Box<dyn CodeType> {
+        Box::new(StringCodeType) as Box<dyn CodeType>
+    }
+
+    fn int_type() -> Box<dyn CodeType> {
+        Box::new(IntCodeType) as Box<dyn CodeType>
+    }
+
+    #[test]
+    fn test_type_label() {
+        let oracle = &*oracle();
+
+        let ct = bool_type();
+        assert_eq!("Boolean".to_string(), ct.type_label(oracle));
+
+        let ct = string_type();
+        assert_eq!("String".to_string(), ct.type_label(oracle));
+
+        let ct = int_type();
+        assert_eq!("Int".to_string(), ct.type_label(oracle));
+    }
+
+    #[test]
+    fn test_literal() {
+        let oracle = &*oracle();
+        let finder = &TestRenderer;
+
+        let ct = bool_type();
+        let ctx = "context".to_string();
+        assert_eq!(
+            "true".to_string(),
+            ct.literal(oracle, &ctx, finder, &json!(true))
+        );
+        assert_eq!(
+            "false".to_string(),
+            ct.literal(oracle, &ctx, finder, &json!(false))
+        );
+
+        let ct = string_type();
+        assert_eq!(
+            r#""no""#.to_string(),
+            ct.literal(oracle, &ctx, finder, &json!("no"))
+        );
+        assert_eq!(
+            r#""yes""#.to_string(),
+            ct.literal(oracle, &ctx, finder, &json!("yes"))
+        );
+
+        let ct = int_type();
+        assert_eq!("1".to_string(), ct.literal(oracle, &ctx, finder, &json!(1)));
+        assert_eq!("2".to_string(), ct.literal(oracle, &ctx, finder, &json!(2)));
+    }
+
+    #[test]
+    fn test_get_value() {
+        let oracle = &*oracle();
+
+        let ct = bool_type();
+        assert_eq!(
+            r#"v.getBool("the-property")"#.to_string(),
+            ct.value_getter(oracle, &"v", &"the-property")
+        );
+
+        let ct = string_type();
+        assert_eq!(
+            r#"v.getString("the-property")"#.to_string(),
+            ct.value_getter(oracle, &"v", &"the-property")
+        );
+
+        let ct = int_type();
+        assert_eq!(
+            r#"v.getInt("the-property")"#.to_string(),
+            ct.value_getter(oracle, &"v", &"the-property")
+        );
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus_fml/backends/kotlin/gen_structs/structural.rs.html b/book/rust-docs/src/nimbus_fml/backends/kotlin/gen_structs/structural.rs.html new file mode 100644 index 0000000000..8e43ecf969 --- /dev/null +++ b/book/rust-docs/src/nimbus_fml/backends/kotlin/gen_structs/structural.rs.html @@ -0,0 +1,1487 @@ +structural.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
+540
+541
+542
+543
+544
+545
+546
+547
+548
+549
+550
+551
+552
+553
+554
+555
+556
+557
+558
+559
+560
+561
+562
+563
+564
+565
+566
+567
+568
+569
+570
+571
+572
+573
+574
+575
+576
+577
+578
+579
+580
+581
+582
+583
+584
+585
+586
+587
+588
+589
+590
+591
+592
+593
+594
+595
+596
+597
+598
+599
+600
+601
+602
+603
+604
+605
+606
+607
+608
+609
+610
+611
+612
+613
+614
+615
+616
+617
+618
+619
+620
+621
+622
+623
+624
+625
+626
+627
+628
+629
+630
+631
+632
+633
+634
+635
+636
+637
+638
+639
+640
+641
+642
+643
+644
+645
+646
+647
+648
+649
+650
+651
+652
+653
+654
+655
+656
+657
+658
+659
+660
+661
+662
+663
+664
+665
+666
+667
+668
+669
+670
+671
+672
+673
+674
+675
+676
+677
+678
+679
+680
+681
+682
+683
+684
+685
+686
+687
+688
+689
+690
+691
+692
+693
+694
+695
+696
+697
+698
+699
+700
+701
+702
+703
+704
+705
+706
+707
+708
+709
+710
+711
+712
+713
+714
+715
+716
+717
+718
+719
+720
+721
+722
+723
+724
+725
+726
+727
+728
+729
+730
+731
+732
+733
+734
+735
+736
+737
+738
+739
+740
+741
+742
+743
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use std::fmt::Display;
+
+use super::common::{self, code_type};
+use crate::backends::{LiteralRenderer, VariablesType};
+use crate::{
+    backends::{CodeOracle, CodeType, TypeIdentifier},
+    intermediate_representation::Literal,
+};
+
+pub(crate) struct OptionalCodeType {
+    inner: TypeIdentifier,
+}
+
+impl OptionalCodeType {
+    pub(crate) fn new(inner: &TypeIdentifier) -> Self {
+        Self {
+            inner: inner.clone(),
+        }
+    }
+}
+
+impl CodeType for OptionalCodeType {
+    /// The language specific label used to reference this type. This will be used in
+    /// method signatures and property declarations.
+    fn type_label(&self, oracle: &dyn CodeOracle) -> String {
+        format!(
+            "{item}?",
+            item = oracle.find(&self.inner).type_label(oracle),
+        )
+    }
+
+    /// The language specific expression that gets a value of the `prop` from the `vars` object.
+    fn property_getter(
+        &self,
+        oracle: &dyn CodeOracle,
+        vars: &dyn Display,
+        prop: &dyn Display,
+        default: &dyn Display,
+    ) -> String {
+        // all getters are optional.
+        code_type::property_getter(self, oracle, vars, prop, default)
+    }
+
+    fn value_getter(
+        &self,
+        oracle: &dyn CodeOracle,
+        vars: &dyn Display,
+        prop: &dyn Display,
+    ) -> String {
+        code_type::value_getter(self, oracle, vars, prop)
+    }
+
+    /// The name of the type as it's represented in the `Variables` object.
+    /// The string return may be used to combine with an indentifier, e.g. a `Variables` method name.
+    fn create_transform(&self, oracle: &dyn CodeOracle) -> Option<String> {
+        oracle.find(&self.inner).create_transform(oracle)
+    }
+
+    /// The name of the type as it's represented in the `Variables` object.
+    /// The string return may be used to combine with an indentifier, e.g. a `Variables` method name.
+    fn variables_type(&self, oracle: &dyn CodeOracle) -> VariablesType {
+        oracle.find(&self.inner).variables_type(oracle)
+    }
+
+    /// The method call here will use the `create_transform` to transform the value coming out of
+    /// the `Variables` object into the desired type.
+    fn value_mapper(&self, oracle: &dyn CodeOracle) -> Option<String> {
+        oracle.find(&self.inner).value_mapper(oracle)
+    }
+
+    /// The method call to merge the value with the defaults.
+    ///
+    /// This may use the `merge_transform`.
+    ///
+    /// If this returns `None`, no merging happens, and implicit `null` replacement happens.
+    fn value_merger(&self, oracle: &dyn CodeOracle, default: &dyn Display) -> Option<String> {
+        oracle.find(&self.inner).value_merger(oracle, default)
+    }
+
+    fn defaults_type(&self, oracle: &dyn CodeOracle) -> String {
+        let inner = oracle.find(&self.inner).defaults_type(oracle);
+        format!("{}?", inner)
+    }
+
+    fn defaults_mapper(
+        &self,
+        oracle: &dyn CodeOracle,
+        value: &dyn Display,
+        vars: &dyn Display,
+    ) -> Option<String> {
+        let id = "it";
+        let mapper = oracle
+            .find(&self.inner)
+            .defaults_mapper(oracle, &id, vars)?;
+        Some(format!(
+            "{value}?.let {{ {mapper} }}",
+            value = value,
+            mapper = mapper
+        ))
+    }
+
+    /// Implement these in different code types, and call recursively from different code types.
+    fn as_json_transform(&self, oracle: &dyn CodeOracle, prop: &dyn Display) -> Option<String> {
+        // We want to return None if the inner's json trasform is none,
+        // but if it's not, then use `prop?` as the new prop
+        let prop = format!("{}?", prop);
+        oracle.find(&self.inner).as_json_transform(oracle, &prop)
+    }
+
+    /// A representation of the given literal for this type.
+    /// N.B. `Literal` is aliased from `serde_json::Value`.
+    fn literal(
+        &self,
+        oracle: &dyn CodeOracle,
+        ctx: &dyn Display,
+        renderer: &dyn LiteralRenderer,
+        literal: &Literal,
+    ) -> String {
+        match literal {
+            serde_json::Value::Null => "null".to_string(),
+            _ => oracle
+                .find(&self.inner)
+                .literal(oracle, ctx, renderer, literal),
+        }
+    }
+}
+
+// Map type
+
+pub(crate) struct MapCodeType {
+    k_type: TypeIdentifier,
+    v_type: TypeIdentifier,
+}
+
+impl MapCodeType {
+    pub(crate) fn new(k: &TypeIdentifier, v: &TypeIdentifier) -> Self {
+        Self {
+            k_type: k.clone(),
+            v_type: v.clone(),
+        }
+    }
+}
+
+impl CodeType for MapCodeType {
+    fn property_getter(
+        &self,
+        oracle: &dyn CodeOracle,
+        vars: &dyn Display,
+        prop: &dyn Display,
+        default: &dyn Display,
+    ) -> String {
+        code_type::property_getter(self, oracle, vars, prop, default)
+    }
+
+    /// The language specific label used to reference this type. This will be used in
+    /// method signatures and property declarations.
+    fn type_label(&self, oracle: &dyn CodeOracle) -> String {
+        format!(
+            "Map<{k}, {v}>",
+            k = oracle.find(&self.k_type).type_label(oracle),
+            v = oracle.find(&self.v_type).type_label(oracle),
+        )
+    }
+
+    fn value_getter(
+        &self,
+        oracle: &dyn CodeOracle,
+        vars: &dyn Display,
+        prop: &dyn Display,
+    ) -> String {
+        let v_type = oracle.find(&self.v_type);
+        format!(
+            "{vars}.get{vt}Map({prop})",
+            vars = vars,
+            vt = v_type.variables_type(oracle),
+            prop = common::quoted(prop),
+        )
+    }
+
+    fn value_mapper(&self, oracle: &dyn CodeOracle) -> Option<String> {
+        let k_type = oracle.find(&self.k_type);
+        let v_type = oracle.find(&self.v_type);
+        Some(
+            match (
+                k_type.create_transform(oracle),
+                v_type.create_transform(oracle),
+            ) {
+                (Some(k), Some(v)) => {
+                    if v.starts_with('{') {
+                        format!("mapEntriesNotNull({k}) {v}", k = k, v = v)
+                    } else {
+                        format!("mapEntriesNotNull({k}, {v})", k = k, v = v)
+                    }
+                }
+                (None, Some(v)) => {
+                    if v.starts_with('{') {
+                        format!("mapValuesNotNull {v}", v = v)
+                    } else {
+                        format!("mapValuesNotNull({v})", v = v)
+                    }
+                }
+                // We could do something with keys, but it's only every strings and enums.
+                (Some(k), None) => format!("mapKeysNotNull({k})", k = k),
+                _ => return None,
+            },
+        )
+    }
+
+    fn value_merger(&self, oracle: &dyn CodeOracle, default: &dyn Display) -> Option<String> {
+        let v_type = oracle.find(&self.v_type);
+        Some(match v_type.merge_transform(oracle) {
+            Some(transform) if transform.starts_with('{') => format!(
+                "mergeWith({default}) {transform}",
+                default = default,
+                transform = transform
+            ),
+            Some(transform) => format!(
+                "mergeWith({default}, {transform})",
+                default = default,
+                transform = transform
+            ),
+            None => format!("mergeWith({})", default),
+        })
+    }
+
+    fn create_transform(&self, oracle: &dyn CodeOracle) -> Option<String> {
+        let vtype = oracle.find(&self.v_type).variables_type(oracle);
+
+        self.value_mapper(oracle)
+            .map(|mapper| {
+                format!(
+                    r#"{{ _vars -> _vars.as{vtype}Map()?.{mapper} }}"#,
+                    vtype = vtype,
+                    mapper = mapper
+                )
+            })
+            .or_else(|| {
+                Some(format!(
+                    r#"{{ _vars -> _vars.as{vtype}Map()? }}"#,
+                    vtype = vtype
+                ))
+            })
+    }
+
+    fn merge_transform(&self, oracle: &dyn CodeOracle) -> Option<String> {
+        let overrides = "_overrides";
+        let defaults = "_defaults";
+
+        self.value_merger(oracle, &defaults).map(|merger| {
+            format!(
+                r#"{{ {overrides}, {defaults} -> {overrides}.{merger} }}"#,
+                overrides = overrides,
+                defaults = defaults,
+                merger = merger
+            )
+        })
+    }
+
+    /// The name of the type as it's represented in the `Variables` object.
+    /// The string return may be used to combine with an indentifier, e.g. a `Variables` method name.
+    fn variables_type(&self, _oracle: &dyn CodeOracle) -> VariablesType {
+        VariablesType::Variables
+    }
+
+    fn defaults_type(&self, oracle: &dyn CodeOracle) -> String {
+        let k_type = oracle.find(&self.k_type).defaults_type(oracle);
+        let v_type = oracle.find(&self.v_type).defaults_type(oracle);
+        format!("Map<{}, {}>", k_type, v_type)
+    }
+
+    fn defaults_mapper(
+        &self,
+        oracle: &dyn CodeOracle,
+        value: &dyn Display,
+        vars: &dyn Display,
+    ) -> Option<String> {
+        let id = "it.value";
+        let mapper = oracle
+            .find(&self.v_type)
+            .defaults_mapper(oracle, &id, vars)?;
+        Some(format!(
+            "{value}.mapValues {{ {mapper} }}",
+            value = value,
+            mapper = mapper
+        ))
+    }
+
+    fn as_json_transform(&self, oracle: &dyn CodeOracle, prop: &dyn Display) -> Option<String> {
+        let k_type = oracle.find(&self.k_type);
+        let v_type = oracle.find(&self.v_type);
+        Some(
+            match (
+                k_type.as_json_transform(oracle, &"it".to_string()),
+                v_type.as_json_transform(oracle, &"it".to_string()),
+            ) {
+                (Some(k), Some(v)) => {
+                    format!(
+                        "{prop}.mapEntriesNotNull({{ {k} }}, {{ {v} }})",
+                        prop = prop,
+                        k = k,
+                        v = v
+                    )
+                }
+                (None, Some(v)) => {
+                    format!("{prop}.mapValuesNotNull {{ {v} }}", prop = prop, v = v)
+                }
+                // We could do something with keys, but it's only every strings and enums.
+                (Some(k), None) => {
+                    format!("{prop}.mapKeysNotNull {{ {k} }}", prop = prop, k = k)
+                }
+                _ => return None,
+            },
+        )
+    }
+
+    /// A representation of the given literal for this type.
+    /// N.B. `Literal` is aliased from `serde_json::Value`.
+    fn literal(
+        &self,
+        oracle: &dyn CodeOracle,
+        ctx: &dyn Display,
+        renderer: &dyn LiteralRenderer,
+        literal: &Literal,
+    ) -> String {
+        let variant = match literal {
+            serde_json::Value::Object(v) => v,
+            _ => unreachable!(),
+        };
+        let k_type = oracle.find(&self.k_type);
+        let v_type = oracle.find(&self.v_type);
+        let src: Vec<String> = variant
+            .iter()
+            .map(|(k, v)| {
+                format!(
+                    "{k} to {v}",
+                    k = k_type.literal(oracle, ctx, renderer, &Literal::String(k.clone())),
+                    v = v_type.literal(oracle, ctx, renderer, v)
+                )
+            })
+            .collect();
+
+        format!("mapOf({})", src.join(", "))
+    }
+
+    fn imports(&self, oracle: &dyn CodeOracle) -> Option<Vec<String>> {
+        let k_type = oracle.find(&self.k_type);
+        let v_type = oracle.find(&self.v_type);
+        let mapper = map_functions(
+            k_type.create_transform(oracle),
+            v_type.create_transform(oracle),
+        );
+
+        let json_mapper = map_functions(
+            k_type.as_json_transform(oracle, &"k".to_string()),
+            v_type.as_json_transform(oracle, &"v".to_string()),
+        );
+
+        let merger = Some("org.mozilla.experiments.nimbus.internal.mergeWith".to_string());
+        Some(
+            [mapper, json_mapper, merger]
+                .iter()
+                .filter_map(|i| i.to_owned())
+                .collect(),
+        )
+    }
+}
+
+fn map_functions(k: Option<String>, v: Option<String>) -> Option<String> {
+    match (k, v) {
+        (Some(_), Some(_)) => {
+            Some("org.mozilla.experiments.nimbus.internal.mapEntriesNotNull".to_string())
+        }
+        (None, Some(_)) => {
+            Some("org.mozilla.experiments.nimbus.internal.mapValuesNotNull".to_string())
+        }
+        (Some(_), None) => {
+            Some("org.mozilla.experiments.nimbus.internal.mapKeysNotNull".to_string())
+        }
+        _ => None,
+    }
+}
+
+// List type
+
+pub(crate) struct ListCodeType {
+    inner: TypeIdentifier,
+}
+
+impl ListCodeType {
+    pub(crate) fn new(inner: &TypeIdentifier) -> Self {
+        Self {
+            inner: inner.clone(),
+        }
+    }
+}
+
+impl CodeType for ListCodeType {
+    /// The language specific label used to reference this type. This will be used in
+    /// method signatures and property declarations.
+    fn type_label(&self, oracle: &dyn CodeOracle) -> String {
+        format!(
+            "List<{item}>",
+            item = oracle.find(&self.inner).type_label(oracle),
+        )
+    }
+
+    fn property_getter(
+        &self,
+        oracle: &dyn CodeOracle,
+        vars: &dyn Display,
+        prop: &dyn Display,
+        default: &dyn Display,
+    ) -> String {
+        code_type::property_getter(self, oracle, vars, prop, default)
+    }
+
+    fn value_getter(
+        &self,
+        oracle: &dyn CodeOracle,
+        vars: &dyn Display,
+        prop: &dyn Display,
+    ) -> String {
+        let vtype = oracle.find(&self.inner).variables_type(oracle);
+        format!(
+            "{vars}.get{vt}List(\"{prop}\")",
+            vars = vars,
+            vt = vtype,
+            prop = prop
+        )
+    }
+
+    fn value_mapper(&self, oracle: &dyn CodeOracle) -> Option<String> {
+        let transform = oracle.find(&self.inner).create_transform(oracle)?;
+        Some(if transform.starts_with('{') {
+            format!("mapNotNull {}", transform)
+        } else {
+            format!("mapNotNull({})", transform)
+        })
+    }
+
+    fn value_merger(&self, _oracle: &dyn CodeOracle, _default: &dyn Display) -> Option<String> {
+        // We never merge lists.
+        None
+    }
+
+    /// The name of the type as it's represented in the `Variables` object.
+    /// The string return may be used to combine with an indentifier, e.g. a `Variables` method name.
+    fn variables_type(&self, _oracle: &dyn CodeOracle) -> VariablesType {
+        // Our current implementation of Variables doesn't have a getListList() or getListMap().
+        // We do allow getVariablesList and getVariablesMap, but not an vars.asList().
+        unimplemented!("Lists and maps of lists aren't supported. The workaround is to use a list of map of list holder objects")
+    }
+
+    fn defaults_type(&self, oracle: &dyn CodeOracle) -> String {
+        let inner = oracle.find(&self.inner).defaults_type(oracle);
+        format!("List<{}>", inner)
+    }
+
+    fn defaults_mapper(
+        &self,
+        oracle: &dyn CodeOracle,
+        value: &dyn Display,
+        vars: &dyn Display,
+    ) -> Option<String> {
+        let id = "it";
+        let mapper = oracle
+            .find(&self.inner)
+            .defaults_mapper(oracle, &id, vars)?;
+        Some(format!(
+            "{value}.map {{ {mapper} }}",
+            value = value,
+            mapper = mapper
+        ))
+    }
+
+    fn as_json_transform(&self, oracle: &dyn CodeOracle, prop: &dyn Display) -> Option<String> {
+        let mapper = oracle
+            .find(&self.inner)
+            .as_json_transform(oracle, &"it".to_string())?;
+        Some(format!(
+            "{prop}.map {{ {mapper} }}",
+            prop = prop,
+            mapper = mapper
+        ))
+    }
+
+    /// A representation of the given literal for this type.
+    /// N.B. `Literal` is aliased from `serde_json::Value`.
+    fn literal(
+        &self,
+        oracle: &dyn CodeOracle,
+        ctx: &dyn Display,
+        renderer: &dyn LiteralRenderer,
+        literal: &Literal,
+    ) -> String {
+        let variant = match literal {
+            serde_json::Value::Array(v) => v,
+            _ => unreachable!(),
+        };
+
+        let v_type = oracle.find(&self.inner);
+        let src: Vec<String> = variant
+            .iter()
+            .map(|v| v_type.literal(oracle, ctx, renderer, v))
+            .collect();
+
+        format!("listOf({})", src.join(", "))
+    }
+}
+
+#[cfg(test)]
+mod unit_tests {
+
+    use serde_json::json;
+
+    use crate::backends::kotlin::gen_structs::{
+        enum_::EnumCodeType, object::ObjectCodeType, primitives::StringCodeType,
+    };
+    use crate::backends::TypeIdentifier;
+
+    use super::*;
+
+    struct TestCodeOracle;
+    impl CodeOracle for TestCodeOracle {
+        fn find(&self, type_: &TypeIdentifier) -> Box<dyn CodeType> {
+            match type_ {
+                TypeIdentifier::String => Box::new(StringCodeType) as Box<dyn CodeType>,
+                TypeIdentifier::Enum(s) => {
+                    Box::new(EnumCodeType::new(s.clone())) as Box<dyn CodeType>
+                }
+                TypeIdentifier::Object(s) => {
+                    Box::new(ObjectCodeType::new(s.clone())) as Box<dyn CodeType>
+                }
+                TypeIdentifier::List(i) => Box::new(ListCodeType::new(i)),
+                TypeIdentifier::EnumMap(k, v) => Box::new(MapCodeType::new(k, v)),
+                _ => unreachable!(),
+            }
+        }
+    }
+
+    struct TestRenderer;
+    impl LiteralRenderer for TestRenderer {
+        fn literal(
+            &self,
+            _oracle: &dyn CodeOracle,
+            _typ: &TypeIdentifier,
+            _value: &Literal,
+            _ctx: &dyn Display,
+        ) -> String {
+            unreachable!()
+        }
+    }
+
+    fn oracle() -> Box<dyn CodeOracle> {
+        Box::new(TestCodeOracle) as Box<dyn CodeOracle>
+    }
+
+    fn type_(nm: &str) -> TypeIdentifier {
+        match nm {
+            "String" => TypeIdentifier::String,
+            "AnObject" => TypeIdentifier::Object("AnObject".to_string()),
+            nm => TypeIdentifier::Enum(nm.to_string()),
+        }
+    }
+
+    fn list_type(item: &str) -> Box<dyn CodeType> {
+        Box::new(ListCodeType::new(&type_(item)))
+    }
+
+    fn map_type(k: &str, v: &str) -> Box<dyn CodeType> {
+        Box::new(MapCodeType::new(&type_(k), &type_(v)))
+    }
+
+    fn getter_with_fallback(
+        ct: &dyn CodeType,
+        vars: &dyn Display,
+        prop: &dyn Display,
+        def: &dyn Display,
+    ) -> String {
+        let oracle = &*oracle();
+        ct.property_getter(oracle, vars, prop, def)
+    }
+
+    #[test]
+    fn test_list_type_label() {
+        let oracle = &*oracle();
+        let ct = list_type("String");
+        assert_eq!("List<String>".to_string(), ct.type_label(oracle));
+
+        let ct = list_type("AnEnum");
+        assert_eq!("List<AnEnum>".to_string(), ct.type_label(oracle));
+    }
+
+    #[test]
+    fn test_list_literal() {
+        let oracle = &*oracle();
+        let finder = &TestRenderer;
+
+        let ct = list_type("String");
+        let ctx = "_context".to_string();
+        assert_eq!(
+            r#"listOf("x", "y", "z")"#.to_string(),
+            ct.literal(oracle, &ctx, finder, &json!(["x", "y", "z"]))
+        );
+
+        let ct = list_type("AnEnum");
+        assert_eq!(
+            r#"listOf(AnEnum.X, AnEnum.Y, AnEnum.Z)"#.to_string(),
+            ct.literal(oracle, &ctx, finder, &json!(["x", "y", "z"]))
+        );
+    }
+
+    #[test]
+    fn test_list_get_value() {
+        let oracle = &*oracle();
+
+        let ct = list_type("AnEnum");
+        assert_eq!(
+            r#"v.getStringList("the-property")"#.to_string(),
+            ct.value_getter(oracle, &"v", &"the-property")
+        );
+
+        let ct = list_type("AnObject");
+        assert_eq!(
+            r#"v.getVariablesList("the-property")"#.to_string(),
+            ct.value_getter(oracle, &"v", &"the-property")
+        );
+
+        let ct = list_type("String");
+        assert_eq!(
+            r#"v.getStringList("the-property")"#.to_string(),
+            ct.value_getter(oracle, &"v", &"the-property")
+        );
+    }
+
+    #[test]
+    fn test_list_getter_with_fallback() {
+        let ct = list_type("String");
+        assert_eq!(
+            r#"vars.getStringList("the-property") ?: default"#.to_string(),
+            getter_with_fallback(&*ct, &"vars", &"the-property", &"default")
+        );
+
+        let ct = list_type("AnEnum");
+        assert_eq!(
+            r#"vars.getStringList("the-property")?.mapNotNull(AnEnum::enumValue) ?: default"#
+                .to_string(),
+            getter_with_fallback(&*ct, &"vars", &"the-property", &"default")
+        );
+
+        let ct = list_type("AnObject");
+        assert_eq!(
+            r#"vars.getVariablesList("the-property")?.mapNotNull(AnObject::create) ?: default"#
+                .to_string(),
+            getter_with_fallback(&*ct, &"vars", &"the-property", &"default")
+        );
+    }
+
+    #[test]
+    fn test_map_type_label() {
+        let oracle = &*oracle();
+        let ct = map_type("String", "String");
+        assert_eq!("Map<String, String>".to_string(), ct.type_label(oracle));
+
+        let ct = map_type("String", "AnEnum");
+        assert_eq!("Map<String, AnEnum>".to_string(), ct.type_label(oracle));
+    }
+
+    #[test]
+    fn test_map_literal() {
+        let oracle = &*oracle();
+        let finder = &TestRenderer;
+        let ctx = "context".to_string();
+        let ct = map_type("String", "AnEnum");
+        assert_eq!(
+            r#"mapOf("a" to AnEnum.A, "b" to AnEnum.B)"#.to_string(),
+            ct.literal(oracle, &ctx, finder, &json!({"a": "a", "b": "b"}))
+        );
+
+        let ct = map_type("AnEnum", "String");
+        assert_eq!(
+            r#"mapOf(AnEnum.A to "a", AnEnum.B to "b")"#.to_string(),
+            ct.literal(oracle, &ctx, finder, &json!({"a": "a", "b": "b"}))
+        );
+    }
+
+    #[test]
+    fn test_map_get_value() {
+        let oracle = &*oracle();
+
+        let ct = map_type("String", "AnEnum");
+        assert_eq!(
+            r#"v.getStringMap("the-property")"#.to_string(),
+            ct.value_getter(oracle, &"v", &"the-property")
+        );
+
+        let ct = map_type("AnEnum", "String");
+        assert_eq!(
+            r#"v.getStringMap("the-property")"#.to_string(),
+            ct.value_getter(oracle, &"v", &"the-property")
+        );
+
+        let ct = map_type("AnEnum", "Another");
+        assert_eq!(
+            r#"v.getStringMap("the-property")"#.to_string(),
+            ct.value_getter(oracle, &"v", &"the-property")
+        );
+    }
+
+    #[test]
+    fn test_map_getter_with_fallback() {
+        let oracle = &*oracle();
+
+        let ct = map_type("String", "AnEnum");
+        assert_eq!(
+            r#"v.getStringMap("the-property")?.mapValuesNotNull(AnEnum::enumValue)?.mergeWith(def) ?: def"#.to_string(),
+            ct.property_getter(oracle, &"v", &"the-property", &"def")
+        );
+
+        let ct = map_type("AnEnum", "String");
+        assert_eq!(
+            r#"v.getStringMap("the-property")?.mapKeysNotNull(AnEnum::enumValue)?.mergeWith(def) ?: def"#
+                .to_string(),
+            ct.property_getter(oracle, &"v", &"the-property", &"def")
+        );
+
+        let ct = map_type("AnEnum", "Another");
+        assert_eq!(
+            r#"v.getStringMap("the-property")?.mapEntriesNotNull(AnEnum::enumValue, Another::enumValue)?.mergeWith(def) ?: def"#
+                .to_string(),
+            ct.property_getter(oracle, &"v", &"the-property", &"def")
+        );
+
+        let ct = map_type("AnEnum", "AnObject");
+        assert_eq!(
+            r#"v.getVariablesMap("the-property")?.mapEntriesNotNull(AnEnum::enumValue, AnObject::create)?.mergeWith(def, AnObject::mergeWith) ?: def"#.to_string(),
+            ct.property_getter(oracle, &"v", &"the-property", &"def"));
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus_fml/backends/kotlin/mod.rs.html b/book/rust-docs/src/nimbus_fml/backends/kotlin/mod.rs.html new file mode 100644 index 0000000000..ed3881f861 --- /dev/null +++ b/book/rust-docs/src/nimbus_fml/backends/kotlin/mod.rs.html @@ -0,0 +1,413 @@ +mod.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::command_line::commands::GenerateStructCmd;
+use crate::error::{FMLError, Result};
+use crate::frontend::AboutBlock;
+use crate::intermediate_representation::FeatureManifest;
+use askama::Template;
+
+mod gen_structs;
+
+impl AboutBlock {
+    fn nimbus_fully_qualified_name(&self) -> String {
+        let kt_about = self.kotlin_about.as_ref().unwrap();
+
+        let class = &kt_about.class;
+        if class.starts_with('.') {
+            format!("{}{}", kt_about.package, class)
+        } else {
+            class.clone()
+        }
+    }
+
+    fn nimbus_object_name_kt(&self) -> String {
+        let fqe = self.nimbus_fully_qualified_name();
+        let last = fqe.split('.').last().unwrap_or(&fqe);
+        last.to_string()
+    }
+
+    fn nimbus_package_name(&self) -> Option<String> {
+        let fqe = self.nimbus_fully_qualified_name();
+        if !fqe.contains('.') {
+            return None;
+        }
+        let mut it = fqe.split('.');
+        it.next_back()?;
+        Some(it.collect::<Vec<&str>>().join("."))
+    }
+
+    fn resource_package_name(&self) -> String {
+        let kt_about = self.kotlin_about.as_ref().unwrap();
+        kt_about.package.clone()
+    }
+}
+
+pub(crate) fn generate_struct(manifest: &FeatureManifest, cmd: &GenerateStructCmd) -> Result<()> {
+    if manifest.about.kotlin_about.is_none() {
+        return Err(FMLError::ValidationError(
+            "about".to_string(),
+            format!(
+                "The `about` block is missing a valid `android` or `kotlin` entry: {}",
+                &cmd.manifest
+            ),
+        ));
+    }
+
+    let path = &cmd.output;
+    let path = if path.is_dir() {
+        path.join(format!("{}.kt", manifest.about.nimbus_object_name_kt()))
+    } else {
+        path.clone()
+    };
+
+    let kt = gen_structs::FeatureManifestDeclaration::new(manifest);
+
+    let contents = kt.render()?;
+
+    std::fs::write(path, contents)?;
+
+    Ok(())
+}
+
+#[cfg(test)]
+pub mod test {
+    use crate::util::{join, pkg_dir, sdk_dir};
+    use anyhow::{bail, Result};
+    use std::path::Path;
+    use std::process::Command;
+
+    // The root of the Android kotlin package structure
+    fn sdk_android_dir() -> String {
+        join(sdk_dir(), "android/src/main/java")
+    }
+
+    // The directory with the mock implementations of Android
+    // used for testing.
+    fn runtime_dir() -> String {
+        join(pkg_dir(), "fixtures/android/runtime")
+    }
+
+    // We'll put our test scripts in here.
+    fn tests_dir() -> String {
+        join(pkg_dir(), "fixtures/android/tests")
+    }
+
+    // The jar archive we need to do JSON with in Kotlin/Java.
+    // This is the same library as bundled in Android.
+    fn json_jar() -> String {
+        join(runtime_dir(), "json.jar")
+    }
+
+    // The file with the kt implementation of FeatureVariables
+    fn variables_kt() -> String {
+        join(
+            sdk_android_dir(),
+            "org/mozilla/experiments/nimbus/FeatureVariables.kt",
+        )
+    }
+
+    fn nimbus_internals_kt() -> String {
+        join(sdk_android_dir(), "org/mozilla/experiments/nimbus/internal")
+    }
+
+    // The file with the kt implementation of FeatureVariables
+    fn features_kt() -> String {
+        join(
+            sdk_android_dir(),
+            "org/mozilla/experiments/nimbus/FeaturesInterface.kt",
+        )
+    }
+
+    fn hardcoded_features_kt() -> String {
+        join(
+            sdk_android_dir(),
+            "org/mozilla/experiments/nimbus/HardcodedNimbusFeatures.kt",
+        )
+    }
+
+    fn classpath(classes: &Path) -> Result<String> {
+        Ok(format!("{}:{}", json_jar(), classes.to_str().unwrap()))
+    }
+
+    fn detect_kotlinc() -> Result<bool> {
+        let output = Command::new("which").arg("kotlinc").output()?;
+
+        Ok(output.status.success())
+    }
+
+    // Compile a genertaed manifest file against the mocked out Android runtime.
+    pub fn compile_manifest_kt(manifest_paths: &[String]) -> Result<tempfile::TempDir> {
+        let temp = tempfile::tempdir()?;
+        let build_dir = temp.path();
+
+        let status = Command::new("kotlinc")
+            // Our generated bindings should not produce any warnings; fail tests if they do.
+            .arg("-Werror")
+            .arg("-J-ea")
+            // Reflect $CLASSPATH from the environment, to help find `json.jar`.
+            .arg("-classpath")
+            .arg(json_jar())
+            .arg("-d")
+            .arg(build_dir)
+            .arg(&variables_kt())
+            .arg(&features_kt())
+            .arg(&hardcoded_features_kt())
+            .arg(&runtime_dir())
+            .arg(&nimbus_internals_kt())
+            .args(manifest_paths)
+            .spawn()?
+            .wait()?;
+        if status.success() {
+            Ok(temp)
+        } else {
+            bail!("running `kotlinc` failed compiling a generated manifest")
+        }
+    }
+
+    // Given a generated manifest, run a kts script against it.
+    pub fn run_script_with_generated_code(manifests_kt: &[String], script: &str) -> Result<()> {
+        if !detect_kotlinc()? {
+            println!("SDK-446 Install kotlinc or add it the PATH to run tests");
+            return Ok(());
+        }
+        let temp_dir = compile_manifest_kt(manifests_kt)?;
+        let build_dir = temp_dir.path();
+
+        let status = Command::new("kotlinc")
+            // Our generated bindings should not produce any warnings; fail tests if they do.
+            .arg("-Werror")
+            .arg("-J-ea")
+            // Reflect $CLASSPATH from the environment, to help find `json.jar`.
+            .arg("-classpath")
+            .arg(&classpath(build_dir)?)
+            .arg("-script")
+            .arg(script)
+            .spawn()?
+            .wait()?;
+
+        drop(temp_dir);
+        if status.success() {
+            Ok(())
+        } else {
+            bail!("running `kotlinc` failed running a script")
+        }
+    }
+
+    #[test]
+    fn smoke_test_runtime_dir() -> Result<()> {
+        run_script_with_generated_code(
+            &[join(tests_dir(), "SmokeTestFeature.kt")],
+            "fixtures/android/tests/smoke_test.kts",
+        )?;
+        Ok(())
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus_fml/backends/mod.rs.html b/book/rust-docs/src/nimbus_fml/backends/mod.rs.html new file mode 100644 index 0000000000..9beb8fea14 --- /dev/null +++ b/book/rust-docs/src/nimbus_fml/backends/mod.rs.html @@ -0,0 +1,555 @@ +mod.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! # Backend traits
+//!
+//! This module provides a number of traits useful for implementing a backend for FML structs.
+//!
+//! A [CodeType] is needed for each type that is referred to in the feature definition (i.e. every [TypeRef]
+//! instance should have corresponding `CodeType` instance). Helper code for types might include managing how merging/overriding of
+//! defaults occur.
+//!
+//! A [CodeDeclaration] is needed for each type that is declared in the manifest file: i.e. an Object classes, Enum classes and Feature classes.
+//! This has access to intermediate structs of the [crate::intermediate_representation::FeatureManifest] so may want to do some additional lookups to help rendering.
+//!
+//! `CodeDeclaration`s provide the target language's version of the type defined in the feature manifest. For objects and features, this would
+//! be objects that have properties corresponding to the FML variables. For enums, this would mean the Enum class definition. In all cases, this will
+//! likely be attached to an [askama::Template].
+//!
+//! `CodeDeclaration`s can also be used to conditionally include code: e.g. only include the CallbackInterfaceRuntime
+//! if the user has used at least one callback interface.
+//!
+//! Each backend has a wrapper template for each file it needs to generate. This should collect the `CodeDeclaration`s that
+//! the backend and `FeatureManifest` between them specify and use them to stitch together a file in the target language.
+//!
+//! The [CodeOracle] provides methods to map the `TypeRef` values found in the `FeatureManifest` to the `CodeType`s specified
+//! by the backend.
+//!
+//! Each backend will have its own `filter` module, which is used by the askama templates used in all `CodeType`s and `CodeDeclaration`s.
+//! This filter provides methods to generate expressions and identifiers in the target language. These are all forwarded to the oracle.
+
+use std::fmt::Display;
+
+use crate::intermediate_representation::Literal;
+use crate::intermediate_representation::TypeRef;
+
+pub type TypeIdentifier = TypeRef;
+
+/// An object to look up a foreign language code specific renderer for a given type used.
+/// Every [TypeRef] referred to in the [crate::intermediate_representation::FeatureManifest] should map to a corresponding
+/// `CodeType`.
+///
+/// The mapping may be opaque, but the oracle always knows the answer.
+pub trait CodeOracle {
+    fn find(&self, type_: &TypeIdentifier) -> Box<dyn CodeType>;
+}
+
+/// A Trait to emit foreign language code to handle referenced types.
+/// A type which is specified in the FML (i.e. a type that a variable declares itself of)
+/// will have a `CodeDeclaration` as well, but for types used e.g. primitive types, Strings, etc
+/// only a `CodeType` is needed.
+///
+/// This includes generating an literal of the type from the right type of JSON and
+/// expressions to get a property from the JSON backed `Variables` object.
+pub trait CodeType {
+    /// The language specific label used to reference this type. This will be used in
+    /// method signatures and property declarations.
+    fn type_label(&self, oracle: &dyn CodeOracle) -> String;
+
+    /// The language specific expression that gets a value of the `prop` from the `vars` object,
+    /// and fallbacks to the `default` value.
+    ///
+    /// /// All the propertis follow this general pattern:
+    ///
+    /// ```kt
+    /// variables?.{{ value_getter }}
+    ///         ?.{{ value_mapper }}
+    ///         ?.{{ value_merger }}
+    ///         ?: {{ default_fallback}}
+    /// ```
+    ///
+    /// In the case of structural types and objects, `value_mapper` and `value_merger`
+    /// become mutually recursive to generate quite complicated properties.
+    ///
+    fn property_getter(
+        &self,
+        oracle: &dyn CodeOracle,
+        vars: &dyn Display,
+        prop: &dyn Display,
+        default: &dyn Display,
+    ) -> String;
+
+    /// The expression needed to get a value out of a `Variables` objectm with the `prop` key.
+    ///
+    /// This will almost certainly use the `variables_type` method to determine which method to use.
+    /// e.g. `vars?.getString("prop")`
+    ///
+    /// The `value_mapper` will be used to transform this value into the required value.
+    fn value_getter(
+        &self,
+        oracle: &dyn CodeOracle,
+        vars: &dyn Display,
+        prop: &dyn Display,
+    ) -> String;
+
+    /// The method call here will use the `create_transform` to transform the value coming out of
+    /// the `Variables` object into the desired type.
+    ///
+    /// e.g. a string will need to be transformed into an enum, so the value mapper in Kotlin will be
+    /// `let(Enum::enumValue)`.
+    ///
+    /// If the value is `None`, then no mapper is used.
+    fn value_mapper(&self, _oracle: &dyn CodeOracle) -> Option<String> {
+        None
+    }
+
+    /// The method call to merge the value with the defaults.
+    ///
+    /// This may use the `merge_transform`.
+    ///
+    /// If this returns `None`, no merging happens, and implicit `null` replacement happens.
+    fn value_merger(&self, _oracle: &dyn CodeOracle, _default: &dyn Display) -> Option<String> {
+        None
+    }
+
+    /// The name of the type as it's represented in the `Variables` object.
+    /// The string return may be used to combine with an indentifier, e.g. a `Variables` method name.
+    fn variables_type(&self, _oracle: &dyn CodeOracle) -> VariablesType;
+
+    /// A function handle that is capable of turning the variables type to the TypeRef type.
+    fn create_transform(&self, _oracle: &dyn CodeOracle) -> Option<String> {
+        None
+    }
+
+    /// A function handle that is capable of merging two instances of the same class. By default, this is None.
+    fn merge_transform(&self, _oracle: &dyn CodeOracle) -> Option<String> {
+        None
+    }
+
+    // The foreign language type for how default values are stored in the `Defaults` object.
+    // This is usually the same as the type_label itself, but occassionally— e.g. for bundled resources—
+    // this will be different.
+    // If it is different, then a `defaults_mapper` is needed to map between the `defaults_type` and the
+    // `type_label` type.
+    fn defaults_type(&self, oracle: &dyn CodeOracle) -> String {
+        self.type_label(oracle)
+    }
+
+    fn defaults_mapper(
+        &self,
+        _oracle: &dyn CodeOracle,
+        _value: &dyn Display,
+        _vars: &dyn Display,
+    ) -> Option<String> {
+        None
+    }
+
+    fn preference_getter(
+        &self,
+        _oracle: &dyn CodeOracle,
+        _prefs: &dyn Display,
+        _pref_key: &dyn Display,
+    ) -> Option<String> {
+        None
+    }
+
+    /// Call from the template
+    fn as_json(&self, oracle: &dyn CodeOracle, prop: &dyn Display) -> String {
+        self.as_json_transform(oracle, prop)
+            .unwrap_or_else(|| prop.to_string())
+    }
+
+    /// Implement these in different code types, and call recursively from different code types.
+    fn as_json_transform(&self, _oracle: &dyn CodeOracle, _prop: &dyn Display) -> Option<String> {
+        None
+    }
+
+    /// A representation of the given literal for this type.
+    /// N.B. `Literal` is aliased from `serde_json::Value`.
+    fn literal(
+        &self,
+        oracle: &dyn CodeOracle,
+        ctx: &dyn Display,
+        renderer: &dyn LiteralRenderer,
+        literal: &Literal,
+    ) -> String;
+
+    fn is_resource_id(&self, _literal: &Literal) -> bool {
+        false
+    }
+
+    /// Optional helper code to make this type work.
+    /// This might include functions to patch a default value with another.
+    fn helper_code(&self, _oracle: &dyn CodeOracle) -> Option<String> {
+        None
+    }
+
+    /// A list of imports that are needed if this type is in use.
+    /// Classes are imported exactly once.
+    fn imports(&self, _oracle: &dyn CodeOracle) -> Option<Vec<String>> {
+        None
+    }
+}
+
+pub trait LiteralRenderer {
+    fn literal(
+        &self,
+        _oracle: &dyn CodeOracle,
+        _typ: &TypeIdentifier,
+        value: &Literal,
+        ctx: &dyn Display,
+    ) -> String;
+}
+
+impl<T, C> LiteralRenderer for T
+where
+    T: std::ops::Deref<Target = C>,
+    C: LiteralRenderer,
+{
+    fn literal(
+        &self,
+        oracle: &dyn CodeOracle,
+        typ: &TypeIdentifier,
+        value: &Literal,
+        ctx: &dyn Display,
+    ) -> String {
+        self.deref().literal(oracle, typ, value, ctx)
+    }
+}
+
+/// A trait that is able to render a declaration about a particular member declared in
+/// the `FeatureManifest`.
+/// Like `CodeType`, it can render declaration code and imports.
+/// All methods are optional, and there is no requirement that the trait be used for a particular
+/// member. Thus, it can also be useful for conditionally rendering code.
+pub trait CodeDeclaration {
+    /// A list of imports that are needed if this type is in use.
+    /// Classes are imported exactly once.
+    fn imports(&self, _oracle: &dyn CodeOracle) -> Option<Vec<String>> {
+        None
+    }
+
+    /// Code (one or more statements) that is run on start-up of the library,
+    /// but before the client code has access to it.
+    fn initialization_code(&self, _oracle: &dyn CodeOracle) -> Option<String> {
+        None
+    }
+
+    /// Code which represents this member. e.g. the foreign language class definition for
+    /// a given Object type.
+    fn definition_code(&self, _oracle: &dyn CodeOracle) -> Option<String> {
+        None
+    }
+}
+
+/// The generated code is running against hand written code to give type safe, error free access to JSON.
+/// This is the `Variables` object. This enum gives the underlying types that the `Variables` object supports.
+pub enum VariablesType {
+    Bool,
+    Image,
+    Int,
+    String,
+    Text,
+    Variables,
+}
+
+/// The Variables objects use a naming convention to name its methods. e.g. `getBool`, `getBoolList`, `getBoolMap`.
+/// In part this is to make generating code easier.
+/// This is the mapping from type to identifier part that corresponds to its type.
+impl Display for VariablesType {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        let nm = match self {
+            VariablesType::Bool => "Bool",
+            VariablesType::Image => "Image",
+            VariablesType::Int => "Int",
+            VariablesType::String => "String",
+            VariablesType::Text => "Text",
+            VariablesType::Variables => "Variables",
+        };
+        f.write_str(nm)
+    }
+}
+
+pub(crate) mod experimenter_manifest;
+pub(crate) mod frontend_manifest;
+pub(crate) mod kotlin;
+pub(crate) mod swift;
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus_fml/backends/swift/gen_structs/bundled.rs.html b/book/rust-docs/src/nimbus_fml/backends/swift/gen_structs/bundled.rs.html new file mode 100644 index 0000000000..23b169bc45 --- /dev/null +++ b/book/rust-docs/src/nimbus_fml/backends/swift/gen_structs/bundled.rs.html @@ -0,0 +1,325 @@ +bundled.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use std::fmt::Display;
+
+use super::common::{code_type, quoted};
+use crate::backends::{CodeOracle, CodeType, LiteralRenderer, TypeIdentifier, VariablesType};
+use crate::intermediate_representation::Literal;
+
+pub(crate) struct TextCodeType;
+
+impl CodeType for TextCodeType {
+    /// The language specific label used to reference this type. This will be used in
+    /// method signatures and property declarations.
+    fn type_label(&self, _oracle: &dyn CodeOracle) -> String {
+        "String".into()
+    }
+
+    fn property_getter(
+        &self,
+        oracle: &dyn CodeOracle,
+        vars: &dyn Display,
+        prop: &dyn Display,
+        default: &dyn Display,
+    ) -> String {
+        code_type::property_getter(self, oracle, vars, prop, default)
+    }
+
+    fn value_getter(
+        &self,
+        oracle: &dyn CodeOracle,
+        vars: &dyn Display,
+        prop: &dyn Display,
+    ) -> String {
+        code_type::value_getter(self, oracle, vars, prop)
+    }
+
+    fn value_mapper(&self, oracle: &dyn CodeOracle) -> Option<String> {
+        code_type::value_mapper(self, oracle)
+    }
+
+    /// The name of the type as it's represented in the `Variables` object.
+    /// The string return may be used to combine with an indentifier, e.g. a `Variables` method name.
+    fn variables_type(&self, _oracle: &dyn CodeOracle) -> VariablesType {
+        VariablesType::Text
+    }
+
+    /// A representation of the given literal for this type.
+    /// N.B. `Literal` is aliased from `serde_json::Value`.
+    fn literal(
+        &self,
+        _oracle: &dyn CodeOracle,
+        _ctx: &dyn Display,
+        _renderer: &dyn LiteralRenderer,
+        literal: &Literal,
+    ) -> String {
+        match literal {
+            serde_json::Value::String(v) => quoted(v),
+            _ => unreachable!("Expecting a string"),
+        }
+    }
+
+    fn defaults_type(&self, oracle: &dyn CodeOracle) -> String {
+        oracle.find(&TypeIdentifier::String).type_label(oracle)
+    }
+
+    fn defaults_mapper(
+        &self,
+        _oracle: &dyn CodeOracle,
+        value: &dyn Display,
+        vars: &dyn Display,
+    ) -> Option<String> {
+        Some(format!(
+            "{vars}.resourceBundles.getString(named: {value}) ?? {value}",
+            vars = vars,
+            value = value
+        ))
+    }
+}
+
+pub(crate) struct ImageCodeType;
+
+impl CodeType for ImageCodeType {
+    /// The language specific label used to reference this type. This will be used in
+    /// method signatures and property declarations.
+    fn type_label(&self, _oracle: &dyn CodeOracle) -> String {
+        "UIImage".into()
+    }
+
+    fn property_getter(
+        &self,
+        oracle: &dyn CodeOracle,
+        vars: &dyn Display,
+        prop: &dyn Display,
+        default: &dyn Display,
+    ) -> String {
+        code_type::property_getter(self, oracle, vars, prop, default)
+    }
+
+    fn value_getter(
+        &self,
+        oracle: &dyn CodeOracle,
+        vars: &dyn Display,
+        prop: &dyn Display,
+    ) -> String {
+        code_type::value_getter(self, oracle, vars, prop)
+    }
+
+    fn value_mapper(&self, oracle: &dyn CodeOracle) -> Option<String> {
+        code_type::value_mapper(self, oracle)
+    }
+
+    /// The name of the type as it's represented in the `Variables` object.
+    /// The string return may be used to combine with an indentifier, e.g. a `Variables` method name.
+    fn variables_type(&self, _oracle: &dyn CodeOracle) -> VariablesType {
+        VariablesType::Image
+    }
+
+    /// A representation of the given literal for this type.
+    /// N.B. `Literal` is aliased from `serde_json::Value`.
+    fn literal(
+        &self,
+        _oracle: &dyn CodeOracle,
+        _ctx: &dyn Display,
+        _renderer: &dyn LiteralRenderer,
+        literal: &Literal,
+    ) -> String {
+        match literal {
+            serde_json::Value::String(v) => quoted(v),
+            _ => unreachable!("Expecting a string matching an image/drawable resource"),
+        }
+    }
+
+    fn defaults_type(&self, oracle: &dyn CodeOracle) -> String {
+        oracle.find(&TypeIdentifier::String).type_label(oracle)
+    }
+
+    fn defaults_mapper(
+        &self,
+        _oracle: &dyn CodeOracle,
+        value: &dyn Display,
+        vars: &dyn Display,
+    ) -> Option<String> {
+        Some(format!(
+            // UIKit does not provide any compile time safety for bundled images. The string name isn't found to be missing
+            // until runtime.
+            // For these fallback images, if they are missing, we consider it a programmer error,
+            // so `getImageNotNull(image:)` fatalErrors if the image doesn't exist.
+            //
+            // The assumption here is that the developer will discover this
+            // early in the cycle, and provide the image or change the name.
+            "{vars}.resourceBundles.getImageNotNull(named: {value})",
+            vars = vars,
+            value = value
+        ))
+    }
+
+    fn imports(&self, _oracle: &dyn CodeOracle) -> Option<Vec<String>> {
+        Some(vec!["UIKit".to_string()])
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus_fml/backends/swift/gen_structs/common.rs.html b/book/rust-docs/src/nimbus_fml/backends/swift/gen_structs/common.rs.html new file mode 100644 index 0000000000..89ab4bf914 --- /dev/null +++ b/book/rust-docs/src/nimbus_fml/backends/swift/gen_structs/common.rs.html @@ -0,0 +1,171 @@ +common.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+
// /* This Source Code Form is subject to the terms of the Mozilla Public
+//  * License, v. 2.0. If a copy of the MPL was not distributed with this
+//  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+use heck::{CamelCase, MixedCase};
+use std::fmt::Display;
+
+/// Get the idiomatic Swift rendering of a class name (for enums, records, errors, etc).
+pub fn class_name(nm: &dyn Display) -> String {
+    nm.to_string().to_camel_case()
+}
+
+/// Get the idiomatic Swift rendering of a variable name.
+pub fn var_name(nm: &dyn Display) -> String {
+    nm.to_string().to_mixed_case()
+}
+
+/// Get the idiomatic Swift rendering of an individual enum variant.
+pub fn enum_variant_name(nm: &dyn Display) -> String {
+    nm.to_string().to_mixed_case()
+}
+
+/// Surrounds a property name with quotes. It is assumed that property names do not need escaping.
+pub fn quoted(v: &dyn Display) -> String {
+    format!(r#""{}""#, v)
+}
+
+pub(crate) mod code_type {
+    use std::fmt::Display;
+
+    use crate::backends::{CodeOracle, CodeType};
+
+    /// The language specific expression that gets a value of the `prop` from the `vars` object.
+    pub(crate) fn property_getter(
+        ct: &dyn CodeType,
+        oracle: &dyn CodeOracle,
+        vars: &dyn Display,
+        prop: &dyn Display,
+        default: &dyn Display,
+    ) -> String {
+        let getter = ct.value_getter(oracle, vars, prop);
+        let mapper = ct.value_mapper(oracle);
+        let default = ct
+            .defaults_mapper(oracle, &default, vars)
+            .unwrap_or_else(|| default.to_string());
+        let merger = ct.value_merger(oracle, &default);
+
+        // We need to be quite careful about option chaining.
+        // Swift takes the `?` as an indicator to _stop evaulating the chain expression_ if the immediately preceeding
+        // expression returns an optional.
+        // Only the value_getter returns an optional, so that's all we need to `?`.
+        // https://docs.swift.org/swift-book/LanguageGuide/OptionalChaining.html
+        let getter = match (mapper, merger) {
+            (Some(mapper), Some(merger)) => format!("{}?.{}.{}", getter, mapper, merger),
+            (Some(mapper), None) => format!("{}?.{}", getter, mapper),
+            (None, Some(merger)) => format!("{}?.{}", getter, merger),
+            (None, None) => getter,
+        };
+
+        format!(
+            "{getter} ?? {fallback}",
+            getter = getter,
+            fallback = default,
+        )
+    }
+
+    pub(crate) fn value_getter(
+        ct: &dyn CodeType,
+        oracle: &dyn CodeOracle,
+        vars: &dyn Display,
+        prop: &dyn Display,
+    ) -> String {
+        let vt = ct.variables_type(oracle);
+        format!(
+            "{vars}.get{vt}(\"{prop}\")",
+            vars = vars,
+            vt = vt,
+            prop = prop
+        )
+    }
+
+    pub(crate) fn value_mapper(ct: &dyn CodeType, oracle: &dyn CodeOracle) -> Option<String> {
+        let transform = ct.create_transform(oracle)?;
+        Some(format!("map({})", transform))
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus_fml/backends/swift/gen_structs/enum_.rs.html b/book/rust-docs/src/nimbus_fml/backends/swift/gen_structs/enum_.rs.html new file mode 100644 index 0000000000..575b8f36e5 --- /dev/null +++ b/book/rust-docs/src/nimbus_fml/backends/swift/gen_structs/enum_.rs.html @@ -0,0 +1,385 @@ +enum_.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use std::fmt::Display;
+
+use askama::Template;
+
+use super::common;
+use super::common::code_type;
+use super::filters;
+use crate::backends::{CodeDeclaration, CodeOracle, CodeType, LiteralRenderer, VariablesType};
+use crate::intermediate_representation::{EnumDef, FeatureManifest, Literal};
+
+pub(crate) struct EnumCodeType {
+    id: String,
+}
+
+impl EnumCodeType {
+    pub(crate) fn new(id: String) -> Self {
+        Self { id }
+    }
+}
+
+impl CodeType for EnumCodeType {
+    /// The language specific label used to reference this type. This will be used in
+    /// method signatures and property declarations.
+    fn type_label(&self, _oracle: &dyn CodeOracle) -> String {
+        common::class_name(&self.id)
+    }
+
+    fn property_getter(
+        &self,
+        oracle: &dyn CodeOracle,
+        vars: &dyn Display,
+        prop: &dyn Display,
+        default: &dyn Display,
+    ) -> String {
+        code_type::property_getter(self, oracle, vars, prop, default)
+    }
+
+    fn value_getter(
+        &self,
+        oracle: &dyn CodeOracle,
+        vars: &dyn Display,
+        prop: &dyn Display,
+    ) -> String {
+        code_type::value_getter(self, oracle, vars, prop)
+    }
+
+    fn value_mapper(&self, oracle: &dyn CodeOracle) -> Option<String> {
+        code_type::value_mapper(self, oracle)
+    }
+
+    /// The name of the type as it's represented in the `Variables` object.
+    /// The string return may be used to combine with an indentifier, e.g. a `Variables` method name.
+    fn variables_type(&self, _oracle: &dyn CodeOracle) -> VariablesType {
+        VariablesType::String
+    }
+
+    /// A function handle that is capable of turning the variables type to the TypeRef type.
+    fn create_transform(&self, oracle: &dyn CodeOracle) -> Option<String> {
+        Some(format!(
+            "{enum_type}.enumValue",
+            enum_type = self.type_label(oracle)
+        ))
+    }
+
+    /// A representation of the given literal for this type.
+    /// N.B. `Literal` is aliased from `serde_json::Value`.
+    fn literal(
+        &self,
+        _oracle: &dyn CodeOracle,
+        _ctx: &dyn Display,
+        _renderer: &dyn LiteralRenderer,
+        literal: &Literal,
+    ) -> String {
+        let variant = match literal {
+            serde_json::Value::String(v) => v,
+            _ => unreachable!(),
+        };
+
+        format!(".{}", common::enum_variant_name(variant))
+    }
+}
+#[derive(Template)]
+#[template(syntax = "swift", escape = "none", path = "EnumTemplate.swift")]
+pub(crate) struct EnumCodeDeclaration {
+    inner: EnumDef,
+}
+
+impl EnumCodeDeclaration {
+    pub fn new(_fm: &FeatureManifest, inner: &EnumDef) -> Self {
+        Self {
+            inner: inner.clone(),
+        }
+    }
+    fn inner(&self) -> EnumDef {
+        self.inner.clone()
+    }
+}
+
+impl CodeDeclaration for EnumCodeDeclaration {
+    fn definition_code(&self, _oracle: &dyn CodeOracle) -> Option<String> {
+        Some(self.render().unwrap())
+    }
+}
+
+#[cfg(test)]
+mod unit_tests {
+
+    use serde_json::json;
+
+    use super::*;
+    use crate::backends::TypeIdentifier;
+    struct TestCodeOracle;
+    impl CodeOracle for TestCodeOracle {
+        fn find(&self, _type_: &TypeIdentifier) -> Box<dyn CodeType> {
+            unreachable!()
+        }
+    }
+
+    struct TestRenderer;
+    impl LiteralRenderer for TestRenderer {
+        fn literal(
+            &self,
+            _oracle: &dyn CodeOracle,
+            _typ: &TypeIdentifier,
+            _value: &Literal,
+            _ctx: &dyn Display,
+        ) -> String {
+            unreachable!()
+        }
+    }
+
+    fn oracle() -> Box<dyn CodeOracle> {
+        Box::new(TestCodeOracle) as Box<dyn CodeOracle>
+    }
+
+    fn code_type(name: &str) -> Box<dyn CodeType> {
+        Box::new(EnumCodeType::new(name.to_string())) as Box<dyn CodeType>
+    }
+
+    #[test]
+    fn test_type_label() {
+        let ct = code_type("AEnum");
+        let oracle = &*oracle();
+        assert_eq!("AEnum".to_string(), ct.type_label(oracle))
+    }
+
+    #[test]
+    fn test_literal() {
+        let ct = code_type("AEnum");
+        let oracle = &*oracle();
+        let finder = &TestRenderer;
+        let ctx = String::from("ctx");
+        assert_eq!(
+            ".foo".to_string(),
+            ct.literal(oracle, &ctx, finder, &json!("foo"))
+        );
+        assert_eq!(
+            ".barBaz".to_string(),
+            ct.literal(oracle, &ctx, finder, &json!("barBaz"))
+        );
+        assert_eq!(
+            ".aBC".to_string(),
+            ct.literal(oracle, &ctx, finder, &json!("a-b-c"))
+        );
+    }
+
+    #[test]
+    fn test_get_value() {
+        let ct = code_type("AEnum");
+        let oracle = &*oracle();
+
+        assert_eq!(
+            r#"v.getString("the-property")"#.to_string(),
+            ct.value_getter(oracle, &"v", &"the-property")
+        );
+    }
+
+    #[test]
+    fn test_getter_with_fallback() {
+        let ct = code_type("AEnum");
+        let oracle = &*oracle();
+
+        assert_eq!(
+            r#"v.getString("the-property")?.map(AEnum.enumValue) ?? def"#.to_string(),
+            ct.property_getter(oracle, &"v", &"the-property", &"def")
+        );
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus_fml/backends/swift/gen_structs/feature.rs.html b/book/rust-docs/src/nimbus_fml/backends/swift/gen_structs/feature.rs.html new file mode 100644 index 0000000000..65d96f8705 --- /dev/null +++ b/book/rust-docs/src/nimbus_fml/backends/swift/gen_structs/feature.rs.html @@ -0,0 +1,103 @@ +feature.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use std::fmt::Display;
+
+use askama::Template;
+
+use super::filters;
+use super::object::object_literal;
+use crate::{
+    backends::{CodeDeclaration, CodeOracle, LiteralRenderer, TypeIdentifier},
+    intermediate_representation::{FeatureDef, FeatureManifest, Literal},
+};
+
+#[derive(Template)]
+#[template(syntax = "swift", escape = "none", path = "FeatureTemplate.swift")]
+pub(crate) struct FeatureCodeDeclaration {
+    inner: FeatureDef,
+    fm: FeatureManifest,
+}
+
+impl FeatureCodeDeclaration {
+    pub fn new(fm: &FeatureManifest, inner: &FeatureDef) -> Self {
+        Self {
+            inner: inner.clone(),
+            fm: fm.clone(),
+        }
+    }
+    pub fn inner(&self) -> &FeatureDef {
+        &self.inner
+    }
+}
+
+impl CodeDeclaration for FeatureCodeDeclaration {
+    fn definition_code(&self, _oracle: &dyn CodeOracle) -> Option<String> {
+        Some(self.render().unwrap())
+    }
+}
+
+impl LiteralRenderer for FeatureCodeDeclaration {
+    fn literal(
+        &self,
+        oracle: &dyn CodeOracle,
+        typ: &TypeIdentifier,
+        value: &Literal,
+        ctx: &dyn Display,
+    ) -> String {
+        object_literal(&self.fm, &self, oracle, typ, value, ctx)
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus_fml/backends/swift/gen_structs/filters.rs.html b/book/rust-docs/src/nimbus_fml/backends/swift/gen_structs/filters.rs.html new file mode 100644 index 0000000000..05a5aad9d2 --- /dev/null +++ b/book/rust-docs/src/nimbus_fml/backends/swift/gen_structs/filters.rs.html @@ -0,0 +1,151 @@ +filters.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+
// /* This Source Code Form is subject to the terms of the Mozilla Public
+//  * License, v. 2.0. If a copy of the MPL was not distributed with this
+//  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+use super::{common, ConcreteCodeOracle};
+use std::borrow::Borrow;
+use std::fmt::{self, Display};
+
+use crate::backends::{CodeOracle, LiteralRenderer, TypeIdentifier};
+use crate::intermediate_representation::Literal;
+
+pub fn type_label(type_: impl Borrow<TypeIdentifier>) -> Result<String, askama::Error> {
+    let oracle = ConcreteCodeOracle;
+    Ok(oracle.find(type_.borrow()).type_label(&oracle))
+}
+
+pub fn defaults_type_label(type_: impl Borrow<TypeIdentifier>) -> Result<String, askama::Error> {
+    let oracle = ConcreteCodeOracle;
+    Ok(oracle.find(type_.borrow()).defaults_type(&oracle))
+}
+
+pub fn literal(
+    type_: impl Borrow<TypeIdentifier>,
+    renderer: impl LiteralRenderer,
+    literal: impl Borrow<Literal>,
+    ctx: impl Display,
+) -> Result<String, askama::Error> {
+    let oracle = ConcreteCodeOracle;
+    Ok(oracle
+        .find(type_.borrow())
+        .literal(&oracle, &ctx, &renderer, literal.borrow()))
+}
+
+pub fn property(
+    type_: impl Borrow<TypeIdentifier>,
+    prop: impl fmt::Display,
+    vars: impl fmt::Display,
+    default: impl fmt::Display,
+) -> Result<String, askama::Error> {
+    let oracle = &ConcreteCodeOracle;
+    let ct = oracle.find(type_.borrow());
+    Ok(ct.property_getter(oracle, &vars, &prop, &default))
+}
+
+/// Get the idiomatic Swift rendering of a class name (for enums, records, errors, etc).
+pub fn class_name(nm: impl fmt::Display) -> Result<String, askama::Error> {
+    Ok(common::class_name(&nm))
+}
+
+/// Get the idiomatic Swift rendering of a variable name.
+pub fn var_name(nm: impl fmt::Display) -> Result<String, askama::Error> {
+    Ok(common::var_name(&nm))
+}
+
+/// Get the idiomatic Swift rendering of an individual enum variant.
+pub fn enum_variant_name(nm: impl fmt::Display) -> Result<String, askama::Error> {
+    Ok(common::enum_variant_name(&nm))
+}
+
+pub fn comment(txt: impl fmt::Display, spaces: &str) -> Result<String, askama::Error> {
+    use textwrap::{fill, Options};
+
+    let indent1 = "/// ".to_string();
+    let indent2 = format!("{} /// ", spaces);
+
+    let options = Options::new(80)
+        .initial_indent(&indent1)
+        .subsequent_indent(&indent2);
+
+    let lines = fill(txt.to_string().as_str(), options);
+    Ok(lines)
+}
+
+pub fn quoted(txt: impl fmt::Display) -> Result<String, askama::Error> {
+    Ok(common::quoted(&txt))
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus_fml/backends/swift/gen_structs/imports.rs.html b/book/rust-docs/src/nimbus_fml/backends/swift/gen_structs/imports.rs.html new file mode 100644 index 0000000000..a3134814af --- /dev/null +++ b/book/rust-docs/src/nimbus_fml/backends/swift/gen_structs/imports.rs.html @@ -0,0 +1,133 @@ +imports.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use std::fmt::Display;
+
+use super::{filters, object::object_literal};
+use crate::{
+    backends::{CodeDeclaration, CodeOracle, LiteralRenderer, TypeIdentifier},
+    intermediate_representation::{ImportedModule, Literal, TypeFinder},
+};
+use askama::Template;
+
+#[derive(Template)]
+#[template(
+    syntax = "swift",
+    escape = "none",
+    path = "ImportedModuleInitializationTemplate.swift"
+)]
+pub(crate) struct ImportedModuleInitialization<'a> {
+    pub(crate) inner: ImportedModule<'a>,
+}
+
+impl<'a> ImportedModuleInitialization<'a> {
+    pub(crate) fn new(inner: &ImportedModule<'a>) -> Self {
+        Self {
+            inner: inner.clone(),
+        }
+    }
+}
+
+impl CodeDeclaration for ImportedModuleInitialization<'_> {
+    fn imports(&self, oracle: &dyn CodeOracle) -> Option<Vec<String>> {
+        let p = self.inner.about().nimbus_module_name();
+        Some(
+            self.inner
+                .fm
+                .all_types()
+                .iter()
+                .filter_map(|t| oracle.find(t).imports(oracle))
+                .flatten()
+                .chain(vec![p])
+                .collect::<Vec<_>>(),
+        )
+    }
+
+    fn initialization_code(&self, _oracle: &dyn CodeOracle) -> Option<String> {
+        Some(self.render().unwrap())
+    }
+
+    fn definition_code(&self, _oracle: &dyn CodeOracle) -> Option<String> {
+        None
+    }
+}
+
+impl LiteralRenderer for ImportedModuleInitialization<'_> {
+    fn literal(
+        &self,
+        oracle: &dyn CodeOracle,
+        typ: &TypeIdentifier,
+        value: &Literal,
+        ctx: &dyn Display,
+    ) -> String {
+        object_literal(self.inner.fm, &self, oracle, typ, value, ctx)
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus_fml/backends/swift/gen_structs/mod.rs.html b/book/rust-docs/src/nimbus_fml/backends/swift/gen_structs/mod.rs.html new file mode 100644 index 0000000000..a4d587f44a --- /dev/null +++ b/book/rust-docs/src/nimbus_fml/backends/swift/gen_structs/mod.rs.html @@ -0,0 +1,295 @@ +mod.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+use askama::Template;
+use std::collections::HashSet;
+
+use crate::{
+    backends::{CodeDeclaration, CodeOracle, CodeType, TypeIdentifier},
+    intermediate_representation::{FeatureDef, FeatureManifest, TypeFinder},
+};
+mod bundled;
+mod common;
+mod enum_;
+mod feature;
+mod filters;
+mod imports;
+mod object;
+mod primitives;
+mod structural;
+
+#[derive(Template)]
+#[template(
+    syntax = "swift",
+    escape = "none",
+    path = "FeatureManifestTemplate.swift"
+)]
+pub struct FeatureManifestDeclaration<'a> {
+    fm: &'a FeatureManifest,
+    oracle: ConcreteCodeOracle,
+}
+
+impl<'a> FeatureManifestDeclaration<'a> {
+    pub fn new(fm: &'a FeatureManifest) -> Self {
+        Self {
+            fm,
+            oracle: Default::default(),
+        }
+    }
+
+    pub fn members(&self) -> Vec<Box<dyn CodeDeclaration + 'a>> {
+        let fm = self.fm;
+
+        fm.iter_feature_defs()
+            .map(|inner| {
+                Box::new(feature::FeatureCodeDeclaration::new(fm, inner))
+                    as Box<dyn CodeDeclaration>
+            })
+            .chain(fm.iter_enum_defs().map(|inner| {
+                Box::new(enum_::EnumCodeDeclaration::new(fm, inner)) as Box<dyn CodeDeclaration>
+            }))
+            .chain(fm.iter_object_defs().map(|inner| {
+                Box::new(object::ObjectCodeDeclaration::new(fm, inner)) as Box<dyn CodeDeclaration>
+            }))
+            .chain(fm.iter_imported_files().iter().map(|inner| {
+                Box::new(imports::ImportedModuleInitialization::new(inner))
+                    as Box<dyn CodeDeclaration>
+            }))
+            .collect()
+    }
+
+    pub fn iter_feature_defs(&self) -> Vec<&FeatureDef> {
+        self.fm.iter_feature_defs().collect::<_>()
+    }
+
+    pub fn initialization_code(&self) -> Vec<String> {
+        let oracle = &self.oracle;
+        self.members()
+            .into_iter()
+            .filter_map(|member| member.initialization_code(oracle))
+            .collect()
+    }
+
+    pub fn declaration_code(&self) -> Vec<String> {
+        let oracle = &self.oracle;
+        self.members()
+            .into_iter()
+            .filter_map(|member| member.definition_code(oracle))
+            .collect()
+    }
+
+    pub fn imports(&self) -> Vec<String> {
+        let oracle = &self.oracle;
+        // Filter out our own module.
+        let my_module = &self.fm.about.nimbus_module_name();
+        // Get the app-services module from an environment variable.
+        // If it doesn't exist, then we don't add it.
+        let as_module = std::env::var("MOZ_APPSERVICES_MODULE")
+            .map(|s| vec![s])
+            .unwrap_or_else(|_| vec![]);
+        let mut imports: Vec<String> = self
+            .members()
+            .into_iter()
+            .filter_map(|member| member.imports(oracle))
+            .flatten()
+            .chain(
+                self.fm
+                    .all_types()
+                    .into_iter()
+                    .filter_map(|type_| self.oracle.find(&type_).imports(oracle))
+                    .flatten(),
+            )
+            .chain(as_module)
+            .chain(vec!["Foundation".to_string()])
+            .filter(|i| i != my_module)
+            .collect::<HashSet<String>>()
+            .into_iter()
+            .collect();
+
+        imports.sort();
+        imports
+    }
+}
+
+#[derive(Default, Clone)]
+pub struct ConcreteCodeOracle;
+
+impl ConcreteCodeOracle {
+    fn create_code_type(&self, type_: TypeIdentifier) -> Box<dyn CodeType> {
+        match type_ {
+            TypeIdentifier::Boolean => Box::new(primitives::BooleanCodeType),
+            TypeIdentifier::String => Box::new(primitives::StringCodeType),
+            TypeIdentifier::Int => Box::new(primitives::IntCodeType),
+
+            TypeIdentifier::BundleText(_) => Box::new(bundled::TextCodeType),
+            TypeIdentifier::BundleImage(_) => Box::new(bundled::ImageCodeType),
+
+            TypeIdentifier::Enum(id) => Box::new(enum_::EnumCodeType::new(id)),
+            TypeIdentifier::Object(id) => Box::new(object::ObjectCodeType::new(id)),
+
+            TypeIdentifier::Option(ref inner) => Box::new(structural::OptionalCodeType::new(inner)),
+            TypeIdentifier::List(ref inner) => Box::new(structural::ListCodeType::new(inner)),
+            TypeIdentifier::StringMap(ref v_type) => {
+                let k_type = &TypeIdentifier::String;
+                Box::new(structural::MapCodeType::new(k_type, v_type))
+            }
+            TypeIdentifier::EnumMap(ref k_type, ref v_type) => {
+                Box::new(structural::MapCodeType::new(k_type, v_type))
+            }
+        }
+    }
+}
+
+impl CodeOracle for ConcreteCodeOracle {
+    fn find(&self, type_: &TypeIdentifier) -> Box<dyn CodeType> {
+        self.create_code_type(type_.clone())
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus_fml/backends/swift/gen_structs/object.rs.html b/book/rust-docs/src/nimbus_fml/backends/swift/gen_structs/object.rs.html new file mode 100644 index 0000000000..77a7393049 --- /dev/null +++ b/book/rust-docs/src/nimbus_fml/backends/swift/gen_structs/object.rs.html @@ -0,0 +1,531 @@ +object.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use askama::Template;
+use std::fmt::Display;
+
+use crate::backends::{
+    CodeDeclaration, CodeOracle, CodeType, LiteralRenderer, TypeIdentifier, VariablesType,
+};
+use crate::intermediate_representation::{FeatureManifest, Literal, ObjectDef};
+
+use super::filters;
+
+use super::common::{self, code_type};
+
+pub struct ObjectRuntime;
+
+impl CodeDeclaration for ObjectRuntime {}
+
+pub struct ObjectCodeType {
+    id: String,
+}
+
+impl ObjectCodeType {
+    pub fn new(id: String) -> Self {
+        Self { id }
+    }
+}
+
+impl CodeType for ObjectCodeType {
+    fn type_label(&self, _oracle: &dyn CodeOracle) -> String {
+        common::class_name(&self.id)
+    }
+
+    fn property_getter(
+        &self,
+        oracle: &dyn CodeOracle,
+        vars: &dyn Display,
+        prop: &dyn Display,
+        default: &dyn Display,
+    ) -> String {
+        code_type::property_getter(self, oracle, vars, prop, default)
+    }
+
+    fn value_getter(
+        &self,
+        oracle: &dyn CodeOracle,
+        vars: &dyn Display,
+        prop: &dyn Display,
+    ) -> String {
+        code_type::value_getter(self, oracle, vars, prop)
+    }
+
+    fn value_mapper(&self, oracle: &dyn CodeOracle) -> Option<String> {
+        let transform = self.create_transform(oracle)?;
+        Some(format!("map({})", transform))
+    }
+
+    /// The language specific expression that gets a value of the `prop` from the `vars` object.
+
+    /// The name of the type as it's represented in the `Variables` object.
+    /// The string return may be used to combine with an indentifier, e.g. a `Variables` method name.
+    fn variables_type(&self, _oracle: &dyn CodeOracle) -> VariablesType {
+        VariablesType::Variables
+    }
+
+    fn create_transform(&self, oracle: &dyn CodeOracle) -> Option<String> {
+        Some(format!("{}.create", self.type_label(oracle)))
+    }
+
+    fn merge_transform(&self, oracle: &dyn CodeOracle) -> Option<String> {
+        Some(format!("{}.mergeWith", self.type_label(oracle)))
+    }
+
+    fn value_merger(&self, _oracle: &dyn CodeOracle, default: &dyn Display) -> Option<String> {
+        Some(format!("_mergeWith({})", default))
+    }
+
+    fn literal(
+        &self,
+        oracle: &dyn CodeOracle,
+        ctx: &dyn Display,
+        renderer: &dyn LiteralRenderer,
+        literal: &Literal,
+    ) -> String {
+        renderer.literal(
+            oracle,
+            &TypeIdentifier::Object(self.id.clone()),
+            literal,
+            ctx,
+        )
+    }
+}
+
+#[derive(Template)]
+#[template(syntax = "swift", escape = "none", path = "ObjectTemplate.swift")]
+pub(crate) struct ObjectCodeDeclaration {
+    inner: ObjectDef,
+    fm: FeatureManifest,
+}
+
+impl ObjectCodeDeclaration {
+    pub fn new(fm: &FeatureManifest, inner: &ObjectDef) -> Self {
+        Self {
+            fm: fm.clone(),
+            inner: inner.clone(),
+        }
+    }
+    pub fn inner(&self) -> ObjectDef {
+        self.inner.clone()
+    }
+}
+
+impl CodeDeclaration for ObjectCodeDeclaration {
+    fn definition_code(&self, _oracle: &dyn CodeOracle) -> Option<String> {
+        Some(self.render().unwrap())
+    }
+}
+
+impl LiteralRenderer for ObjectCodeDeclaration {
+    fn literal(
+        &self,
+        oracle: &dyn CodeOracle,
+        typ: &TypeIdentifier,
+        value: &Literal,
+        ctx: &dyn Display,
+    ) -> String {
+        object_literal(&self.fm, &self, oracle, typ, value, ctx)
+    }
+}
+
+pub(crate) fn object_literal(
+    fm: &FeatureManifest,
+    renderer: &dyn LiteralRenderer,
+    oracle: &dyn CodeOracle,
+    typ: &TypeIdentifier,
+    value: &Literal,
+    ctx: &dyn Display,
+) -> String {
+    let id = if let TypeIdentifier::Object(id) = typ {
+        id
+    } else {
+        return oracle.find(typ).literal(oracle, ctx, renderer, value);
+    };
+    let literal_map = if let Literal::Object(map) = value {
+        map
+    } else {
+        unreachable!(
+            "An JSON object is expected for {} object literal",
+            oracle.find(typ).type_label(oracle)
+        )
+    };
+
+    let def = fm.find_object(id).unwrap();
+    let args: Vec<String> = def
+        .props()
+        .iter()
+        .filter_map(|prop_def| {
+            literal_map.get(&prop_def.name()).map(|v| {
+                format!(
+                    "{var_name}: {var_value}",
+                    var_name = common::var_name(&prop_def.name()),
+                    var_value = oracle.find(&prop_def.typ).literal(oracle, ctx, renderer, v)
+                )
+            })
+        })
+        .collect();
+
+    format!(
+        "{typelabel}({args})",
+        typelabel = oracle.find(typ).type_label(oracle),
+        args = args.join(", ")
+    )
+}
+
+#[cfg(test)]
+mod unit_tests {
+    use serde_json::json;
+
+    use crate::{backends::TypeIdentifier, intermediate_representation::Literal};
+
+    use super::*;
+
+    struct TestCodeOracle;
+    impl CodeOracle for TestCodeOracle {
+        fn find(&self, _type_: &TypeIdentifier) -> Box<dyn CodeType> {
+            unreachable!()
+        }
+    }
+
+    struct TestRenderer;
+    impl LiteralRenderer for TestRenderer {
+        fn literal(
+            &self,
+            _oracle: &dyn CodeOracle,
+            typ: &TypeIdentifier,
+            _value: &Literal,
+            _ctx: &dyn Display,
+        ) -> String {
+            if let TypeIdentifier::Object(nm) = typ {
+                format!("{}()", nm)
+            } else {
+                unreachable!()
+            }
+        }
+    }
+
+    fn oracle() -> Box<dyn CodeOracle> {
+        Box::new(TestCodeOracle) as Box<dyn CodeOracle>
+    }
+
+    fn code_type(name: &str) -> Box<dyn CodeType> {
+        Box::new(ObjectCodeType::new(name.to_string())) as Box<dyn CodeType>
+    }
+
+    fn getter_with_fallback(
+        ct: &dyn CodeType,
+        vars: &dyn Display,
+        prop: &dyn Display,
+        def: &dyn Display,
+    ) -> String {
+        let oracle = &*oracle();
+        ct.property_getter(oracle, vars, prop, def)
+    }
+
+    #[test]
+    fn test_type_label() {
+        let ct = code_type("AnObject");
+        let oracle = &*oracle();
+        assert_eq!("AnObject".to_string(), ct.type_label(oracle))
+    }
+
+    #[test]
+    fn test_literal() {
+        let ct = code_type("AnObject");
+        let oracle = &*oracle();
+        let finder = &TestRenderer;
+        let ctx = String::from("ctx");
+        assert_eq!(
+            "AnObject()".to_string(),
+            ct.literal(oracle, &ctx, finder, &json!({}))
+        );
+    }
+
+    #[test]
+    fn test_get_value() {
+        let ct = code_type("AnObject");
+        let oracle = &*oracle();
+
+        assert_eq!(
+            r#"v.getVariables("the-property")"#.to_string(),
+            ct.value_getter(oracle, &"v", &"the-property")
+        );
+    }
+
+    #[test]
+    fn test_getter_with_fallback() {
+        let ct = code_type("AnObject");
+        assert_eq!(
+            r#"vars.getVariables("the-property")?.map(AnObject.create)._mergeWith(default) ?? default"#.to_string(),
+            getter_with_fallback(&*ct, &"vars", &"the-property", &"default")
+        );
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus_fml/backends/swift/gen_structs/primitives.rs.html b/book/rust-docs/src/nimbus_fml/backends/swift/gen_structs/primitives.rs.html new file mode 100644 index 0000000000..f1e245288a --- /dev/null +++ b/book/rust-docs/src/nimbus_fml/backends/swift/gen_structs/primitives.rs.html @@ -0,0 +1,585 @@ +primitives.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use std::fmt::Display;
+
+use super::common::{self, code_type};
+use crate::backends::{CodeOracle, CodeType, LiteralRenderer, VariablesType};
+use crate::intermediate_representation::Literal;
+
+pub(crate) struct BooleanCodeType;
+
+impl CodeType for BooleanCodeType {
+    /// The language specific label used to reference this type. This will be used in
+    /// method signatures and property declarations.
+    fn type_label(&self, _oracle: &dyn CodeOracle) -> String {
+        "Bool".into()
+    }
+
+    fn property_getter(
+        &self,
+        oracle: &dyn CodeOracle,
+        vars: &dyn Display,
+        prop: &dyn Display,
+        default: &dyn Display,
+    ) -> String {
+        code_type::property_getter(self, oracle, vars, prop, default)
+    }
+
+    fn value_getter(
+        &self,
+        oracle: &dyn CodeOracle,
+        vars: &dyn Display,
+        prop: &dyn Display,
+    ) -> String {
+        code_type::value_getter(self, oracle, vars, prop)
+    }
+
+    fn value_mapper(&self, oracle: &dyn CodeOracle) -> Option<String> {
+        code_type::value_mapper(self, oracle)
+    }
+
+    /// The name of the type as it's represented in the `Variables` object.
+    /// The string return may be used to combine with an indentifier, e.g. a `Variables` method name.
+    fn variables_type(&self, _oracle: &dyn CodeOracle) -> VariablesType {
+        VariablesType::Bool
+    }
+
+    /// A representation of the given literal for this type.
+    /// N.B. `Literal` is aliased from `serde_json::Value`.
+    fn literal(
+        &self,
+        _oracle: &dyn CodeOracle,
+        _ctx: &dyn Display,
+        _renderer: &dyn LiteralRenderer,
+        literal: &Literal,
+    ) -> String {
+        match literal {
+            serde_json::Value::Bool(v) => {
+                if *v {
+                    "true".to_string()
+                } else {
+                    "false".to_string()
+                }
+            }
+            _ => unreachable!("Expecting a boolean"),
+        }
+    }
+}
+
+pub(crate) struct IntCodeType;
+
+impl CodeType for IntCodeType {
+    /// The language specific label used to reference this type. This will be used in
+    /// method signatures and property declarations.
+    fn type_label(&self, _oracle: &dyn CodeOracle) -> String {
+        "Int".into()
+    }
+
+    fn property_getter(
+        &self,
+        oracle: &dyn CodeOracle,
+        vars: &dyn Display,
+        prop: &dyn Display,
+        default: &dyn Display,
+    ) -> String {
+        code_type::property_getter(self, oracle, vars, prop, default)
+    }
+
+    fn value_getter(
+        &self,
+        oracle: &dyn CodeOracle,
+        vars: &dyn Display,
+        prop: &dyn Display,
+    ) -> String {
+        code_type::value_getter(self, oracle, vars, prop)
+    }
+
+    fn value_mapper(&self, oracle: &dyn CodeOracle) -> Option<String> {
+        code_type::value_mapper(self, oracle)
+    }
+
+    /// The name of the type as it's represented in the `Variables` object.
+    /// The string return may be used to combine with an indentifier, e.g. a `Variables` method name.
+    fn variables_type(&self, _oracle: &dyn CodeOracle) -> VariablesType {
+        VariablesType::Int
+    }
+
+    /// A representation of the given literal for this type.
+    /// N.B. `Literal` is aliased from `serde_json::Value`.
+    fn literal(
+        &self,
+        _oracle: &dyn CodeOracle,
+        _ctx: &dyn Display,
+        _renderer: &dyn LiteralRenderer,
+        literal: &Literal,
+    ) -> String {
+        match literal {
+            serde_json::Value::Number(v) => {
+                format!("{:.0}", v)
+            }
+            _ => unreachable!("Expecting a number"),
+        }
+    }
+}
+
+pub(crate) struct StringCodeType;
+
+impl CodeType for StringCodeType {
+    /// The language specific label used to reference this type. This will be used in
+    /// method signatures and property declarations.
+    fn type_label(&self, _oracle: &dyn CodeOracle) -> String {
+        "String".into()
+    }
+
+    fn property_getter(
+        &self,
+        oracle: &dyn CodeOracle,
+        vars: &dyn Display,
+        prop: &dyn Display,
+        default: &dyn Display,
+    ) -> String {
+        code_type::property_getter(self, oracle, vars, prop, default)
+    }
+
+    fn value_getter(
+        &self,
+        oracle: &dyn CodeOracle,
+        vars: &dyn Display,
+        prop: &dyn Display,
+    ) -> String {
+        code_type::value_getter(self, oracle, vars, prop)
+    }
+
+    fn value_mapper(&self, oracle: &dyn CodeOracle) -> Option<String> {
+        code_type::value_mapper(self, oracle)
+    }
+
+    /// The name of the type as it's represented in the `Variables` object.
+    /// The string return may be used to combine with an indentifier, e.g. a `Variables` method name.
+    fn variables_type(&self, _oracle: &dyn CodeOracle) -> VariablesType {
+        VariablesType::String
+    }
+
+    /// A representation of the given literal for this type.
+    /// N.B. `Literal` is aliased from `serde_json::Value`.
+    fn literal(
+        &self,
+        _oracle: &dyn CodeOracle,
+        _ctx: &dyn Display,
+        _renderer: &dyn LiteralRenderer,
+        literal: &Literal,
+    ) -> String {
+        match literal {
+            serde_json::Value::String(v) => common::quoted(v),
+            _ => unreachable!("Expecting a string"),
+        }
+    }
+}
+
+#[cfg(test)]
+mod unit_tests {
+
+    use serde_json::json;
+
+    use crate::backends::TypeIdentifier;
+
+    use super::*;
+
+    struct TestCodeOracle;
+    impl CodeOracle for TestCodeOracle {
+        fn find(&self, _type_: &TypeIdentifier) -> Box<dyn CodeType> {
+            unreachable!()
+        }
+    }
+
+    struct TestRenderer;
+    impl LiteralRenderer for TestRenderer {
+        fn literal(
+            &self,
+            _oracle: &dyn CodeOracle,
+            _typ: &TypeIdentifier,
+            _value: &Literal,
+            _ctx: &dyn Display,
+        ) -> String {
+            unreachable!()
+        }
+    }
+
+    fn oracle() -> Box<dyn CodeOracle> {
+        Box::new(TestCodeOracle) as Box<dyn CodeOracle>
+    }
+
+    fn bool_type() -> Box<dyn CodeType> {
+        Box::new(BooleanCodeType) as Box<dyn CodeType>
+    }
+
+    fn string_type() -> Box<dyn CodeType> {
+        Box::new(StringCodeType) as Box<dyn CodeType>
+    }
+
+    fn int_type() -> Box<dyn CodeType> {
+        Box::new(IntCodeType) as Box<dyn CodeType>
+    }
+
+    #[test]
+    fn test_type_label() {
+        let oracle = &*oracle();
+
+        let ct = bool_type();
+        assert_eq!("Bool".to_string(), ct.type_label(oracle));
+
+        let ct = string_type();
+        assert_eq!("String".to_string(), ct.type_label(oracle));
+
+        let ct = int_type();
+        assert_eq!("Int".to_string(), ct.type_label(oracle));
+    }
+
+    #[test]
+    fn test_literal() {
+        let oracle = &*oracle();
+        let finder = &TestRenderer;
+        let ctx = String::from("ctx");
+        let ct = bool_type();
+        assert_eq!(
+            "true".to_string(),
+            ct.literal(oracle, &ctx, finder, &json!(true))
+        );
+        assert_eq!(
+            "false".to_string(),
+            ct.literal(oracle, &ctx, finder, &json!(false))
+        );
+
+        let ct = string_type();
+        assert_eq!(
+            r#""no""#.to_string(),
+            ct.literal(oracle, &ctx, finder, &json!("no"))
+        );
+        assert_eq!(
+            r#""yes""#.to_string(),
+            ct.literal(oracle, &ctx, finder, &json!("yes"))
+        );
+
+        let ct = int_type();
+        assert_eq!("1".to_string(), ct.literal(oracle, &ctx, finder, &json!(1)));
+        assert_eq!("2".to_string(), ct.literal(oracle, &ctx, finder, &json!(2)));
+    }
+
+    #[test]
+    fn test_get_value() {
+        let oracle = &*oracle();
+
+        let ct = bool_type();
+        assert_eq!(
+            r#"v.getBool("the-property")"#.to_string(),
+            ct.value_getter(oracle, &"v", &"the-property")
+        );
+
+        let ct = string_type();
+        assert_eq!(
+            r#"v.getString("the-property")"#.to_string(),
+            ct.value_getter(oracle, &"v", &"the-property")
+        );
+
+        let ct = int_type();
+        assert_eq!(
+            r#"v.getInt("the-property")"#.to_string(),
+            ct.value_getter(oracle, &"v", &"the-property")
+        );
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus_fml/backends/swift/gen_structs/structural.rs.html b/book/rust-docs/src/nimbus_fml/backends/swift/gen_structs/structural.rs.html new file mode 100644 index 0000000000..c385c881e5 --- /dev/null +++ b/book/rust-docs/src/nimbus_fml/backends/swift/gen_structs/structural.rs.html @@ -0,0 +1,1289 @@ +structural.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
+540
+541
+542
+543
+544
+545
+546
+547
+548
+549
+550
+551
+552
+553
+554
+555
+556
+557
+558
+559
+560
+561
+562
+563
+564
+565
+566
+567
+568
+569
+570
+571
+572
+573
+574
+575
+576
+577
+578
+579
+580
+581
+582
+583
+584
+585
+586
+587
+588
+589
+590
+591
+592
+593
+594
+595
+596
+597
+598
+599
+600
+601
+602
+603
+604
+605
+606
+607
+608
+609
+610
+611
+612
+613
+614
+615
+616
+617
+618
+619
+620
+621
+622
+623
+624
+625
+626
+627
+628
+629
+630
+631
+632
+633
+634
+635
+636
+637
+638
+639
+640
+641
+642
+643
+644
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use std::fmt::Display;
+
+use super::common::{self, code_type};
+use crate::backends::{LiteralRenderer, VariablesType};
+use crate::{
+    backends::{CodeOracle, CodeType, TypeIdentifier},
+    intermediate_representation::Literal,
+};
+
+pub(crate) struct OptionalCodeType {
+    inner: TypeIdentifier,
+}
+
+impl OptionalCodeType {
+    pub(crate) fn new(inner: &TypeIdentifier) -> Self {
+        Self {
+            inner: inner.clone(),
+        }
+    }
+}
+
+impl CodeType for OptionalCodeType {
+    /// The language specific label used to reference this type. This will be used in
+    /// method signatures and property declarations.
+    fn type_label(&self, oracle: &dyn CodeOracle) -> String {
+        format!(
+            "{item}?",
+            item = oracle.find(&self.inner).type_label(oracle),
+        )
+    }
+
+    /// The language specific expression that gets a value of the `prop` from the `vars` object.
+    fn property_getter(
+        &self,
+        oracle: &dyn CodeOracle,
+        vars: &dyn Display,
+        prop: &dyn Display,
+        default: &dyn Display,
+    ) -> String {
+        // all getters are optional.
+        code_type::property_getter(self, oracle, vars, prop, default)
+    }
+
+    fn value_getter(
+        &self,
+        oracle: &dyn CodeOracle,
+        vars: &dyn Display,
+        prop: &dyn Display,
+    ) -> String {
+        code_type::value_getter(self, oracle, vars, prop)
+    }
+
+    /// The name of the type as it's represented in the `Variables` object.
+    /// The string return may be used to combine with an indentifier, e.g. a `Variables` method name.
+    fn create_transform(&self, oracle: &dyn CodeOracle) -> Option<String> {
+        oracle.find(&self.inner).create_transform(oracle)
+    }
+
+    /// The name of the type as it's represented in the `Variables` object.
+    /// The string return may be used to combine with an indentifier, e.g. a `Variables` method name.
+    fn variables_type(&self, oracle: &dyn CodeOracle) -> VariablesType {
+        oracle.find(&self.inner).variables_type(oracle)
+    }
+
+    /// The method call here will use the `create_transform` to transform the value coming out of
+    /// the `Variables` object into the desired type.
+    fn value_mapper(&self, oracle: &dyn CodeOracle) -> Option<String> {
+        oracle.find(&self.inner).value_mapper(oracle)
+    }
+
+    /// The method call to merge the value with the defaults.
+    ///
+    /// This may use the `merge_transform`.
+    ///
+    /// If this returns `None`, no merging happens, and implicit `null` replacement happens.
+    fn value_merger(&self, oracle: &dyn CodeOracle, default: &dyn Display) -> Option<String> {
+        oracle.find(&self.inner).value_merger(oracle, default)
+    }
+
+    fn defaults_type(&self, oracle: &dyn CodeOracle) -> String {
+        format!("{}?", oracle.find(&self.inner).defaults_type(oracle))
+    }
+
+    fn defaults_mapper(
+        &self,
+        oracle: &dyn CodeOracle,
+        value: &dyn Display,
+        vars: &dyn Display,
+    ) -> Option<String> {
+        let id = "$0";
+        let mapper = oracle
+            .find(&self.inner)
+            .defaults_mapper(oracle, &id, vars)?;
+        Some(format!(
+            "{value}.map {{ {mapper} }}",
+            value = value,
+            mapper = mapper
+        ))
+    }
+
+    /// A representation of the given literal for this type.
+    /// N.B. `Literal` is aliased from `serde_json::Value`.
+    fn literal(
+        &self,
+        oracle: &dyn CodeOracle,
+        ctx: &dyn Display,
+        renderer: &dyn LiteralRenderer,
+        literal: &Literal,
+    ) -> String {
+        match literal {
+            serde_json::Value::Null => "nil".to_string(),
+            _ => oracle
+                .find(&self.inner)
+                .literal(oracle, ctx, renderer, literal),
+        }
+    }
+}
+
+// Map type
+
+pub(crate) struct MapCodeType {
+    k_type: TypeIdentifier,
+    v_type: TypeIdentifier,
+}
+
+impl MapCodeType {
+    pub(crate) fn new(k: &TypeIdentifier, v: &TypeIdentifier) -> Self {
+        Self {
+            k_type: k.clone(),
+            v_type: v.clone(),
+        }
+    }
+}
+
+impl CodeType for MapCodeType {
+    fn property_getter(
+        &self,
+        oracle: &dyn CodeOracle,
+        vars: &dyn Display,
+        prop: &dyn Display,
+        default: &dyn Display,
+    ) -> String {
+        code_type::property_getter(self, oracle, vars, prop, default)
+    }
+
+    /// The language specific label used to reference this type. This will be used in
+    /// method signatures and property declarations.
+    fn type_label(&self, oracle: &dyn CodeOracle) -> String {
+        format!(
+            "[{k}: {v}]",
+            k = oracle.find(&self.k_type).type_label(oracle),
+            v = oracle.find(&self.v_type).type_label(oracle),
+        )
+    }
+
+    fn value_getter(
+        &self,
+        oracle: &dyn CodeOracle,
+        vars: &dyn Display,
+        prop: &dyn Display,
+    ) -> String {
+        let v_type = oracle.find(&self.v_type);
+        format!(
+            "{vars}.get{vt}Map({prop})",
+            vars = vars,
+            vt = v_type.variables_type(oracle),
+            prop = common::quoted(prop),
+        )
+    }
+
+    fn value_mapper(&self, oracle: &dyn CodeOracle) -> Option<String> {
+        let k_type = oracle.find(&self.k_type);
+        let v_type = oracle.find(&self.v_type);
+        Some(
+            match (
+                k_type.create_transform(oracle),
+                v_type.create_transform(oracle),
+            ) {
+                (Some(k), Some(v)) => format!("mapNotNull({k}, {v})", k = k, v = v),
+                (None, Some(v)) => format!("mapValuesNotNull({v})", v = v),
+                // We could do something with keys, but it's only every strings and enums.
+                (Some(k), None) => format!("mapKeysNotNull({k})", k = k),
+                _ => return None,
+            },
+        )
+    }
+
+    fn value_merger(&self, oracle: &dyn CodeOracle, default: &dyn Display) -> Option<String> {
+        let v_type = oracle.find(&self.v_type);
+        Some(match v_type.merge_transform(oracle) {
+            Some(transform) => format!(
+                "mergeWith({default}, {transform})",
+                default = default,
+                transform = transform
+            ),
+            None => format!("mergeWith({})", default),
+        })
+    }
+
+    fn create_transform(&self, oracle: &dyn CodeOracle) -> Option<String> {
+        let vtype = oracle.find(&self.v_type).variables_type(oracle);
+
+        self.value_mapper(oracle)
+            .map(|mapper| {
+                format!(
+                    r#"{{ (_vars) in return _vars.as{vtype}Map()?.{mapper} }}"#,
+                    vtype = vtype,
+                    mapper = mapper
+                )
+            })
+            .or_else(|| {
+                Some(format!(
+                    r#"{{ (_vars) in return _vars.as{vtype}Map()? }}"#,
+                    vtype = vtype
+                ))
+            })
+    }
+
+    fn merge_transform(&self, oracle: &dyn CodeOracle) -> Option<String> {
+        let overrides = "_overrides";
+        let defaults = "_defaults";
+
+        self.value_merger(oracle, &defaults).map(|merger| {
+            format!(
+                r#"{{ ({overrides}, {defaults}) in return {overrides}.{merger} }}"#,
+                overrides = overrides,
+                defaults = defaults,
+                merger = merger
+            )
+        })
+    }
+
+    /// The name of the type as it's represented in the `Variables` object.
+    /// The string return may be used to combine with an indentifier, e.g. a `Variables` method name.
+    fn variables_type(&self, _oracle: &dyn CodeOracle) -> VariablesType {
+        VariablesType::Variables
+    }
+
+    fn defaults_type(&self, oracle: &dyn CodeOracle) -> String {
+        let k = oracle.find(&self.k_type).defaults_type(oracle);
+        let v = oracle.find(&self.v_type).defaults_type(oracle);
+        format!("[{k}: {v}]", k = k, v = v)
+    }
+
+    fn defaults_mapper(
+        &self,
+        oracle: &dyn CodeOracle,
+        value: &dyn Display,
+        vars: &dyn Display,
+    ) -> Option<String> {
+        let id = "$0";
+        let mapper = oracle
+            .find(&self.v_type)
+            .defaults_mapper(oracle, &id, vars)?;
+        Some(format!(
+            "{value}.mapValues {{ {mapper} }}",
+            value = value,
+            mapper = mapper
+        ))
+    }
+
+    /// A representation of the given literal for this type.
+    /// N.B. `Literal` is aliased from `serde_json::Value`.
+    fn literal(
+        &self,
+        oracle: &dyn CodeOracle,
+        ctx: &dyn Display,
+        renderer: &dyn LiteralRenderer,
+        literal: &Literal,
+    ) -> String {
+        let variant = match literal {
+            serde_json::Value::Object(v) => v,
+            _ => unreachable!(),
+        };
+        let k_type = oracle.find(&self.k_type);
+        let v_type = oracle.find(&self.v_type);
+        let src: Vec<String> = variant
+            .iter()
+            .map(|(k, v)| {
+                format!(
+                    "{k}: {v}",
+                    k = k_type.literal(oracle, ctx, renderer, &Literal::String(k.clone())),
+                    v = v_type.literal(oracle, ctx, renderer, v)
+                )
+            })
+            .collect();
+
+        if src.is_empty() {
+            "[:]".to_string()
+        } else {
+            format!("[{}]", src.join(", "))
+        }
+    }
+}
+
+// List type
+
+pub(crate) struct ListCodeType {
+    inner: TypeIdentifier,
+}
+
+impl ListCodeType {
+    pub(crate) fn new(inner: &TypeIdentifier) -> Self {
+        Self {
+            inner: inner.clone(),
+        }
+    }
+}
+
+impl CodeType for ListCodeType {
+    /// The language specific label used to reference this type. This will be used in
+    /// method signatures and property declarations.
+    fn type_label(&self, oracle: &dyn CodeOracle) -> String {
+        format!(
+            "[{item}]",
+            item = oracle.find(&self.inner).type_label(oracle),
+        )
+    }
+
+    fn property_getter(
+        &self,
+        oracle: &dyn CodeOracle,
+        vars: &dyn Display,
+        prop: &dyn Display,
+        default: &dyn Display,
+    ) -> String {
+        code_type::property_getter(self, oracle, vars, prop, default)
+    }
+
+    fn value_getter(
+        &self,
+        oracle: &dyn CodeOracle,
+        vars: &dyn Display,
+        prop: &dyn Display,
+    ) -> String {
+        let vtype = oracle.find(&self.inner).variables_type(oracle);
+        format!(
+            "{vars}.get{vt}List(\"{prop}\")",
+            vars = vars,
+            vt = vtype,
+            prop = prop
+        )
+    }
+
+    fn value_mapper(&self, oracle: &dyn CodeOracle) -> Option<String> {
+        let transform = oracle.find(&self.inner).create_transform(oracle)?;
+        Some(if transform.starts_with('{') {
+            format!("compactMap {}", transform)
+        } else {
+            format!("compactMap({})", transform)
+        })
+    }
+
+    fn value_merger(&self, _oracle: &dyn CodeOracle, _default: &dyn Display) -> Option<String> {
+        // We never merge lists.
+        None
+    }
+
+    /// The name of the type as it's represented in the `Variables` object.
+    /// The string return may be used to combine with an indentifier, e.g. a `Variables` method name.
+    fn variables_type(&self, _oracle: &dyn CodeOracle) -> VariablesType {
+        // Our current implementation of Variables doesn't have a getListList() or getListMap().
+        // We do allow getVariablesList and getVariablesMap, but not an vars.asList().
+        unimplemented!("Lists and maps of lists aren't supported. The workaround is to use a list of map of list holder objects")
+    }
+
+    fn defaults_type(&self, oracle: &dyn CodeOracle) -> String {
+        format!("[{}]", oracle.find(&self.inner).defaults_type(oracle))
+    }
+
+    fn defaults_mapper(
+        &self,
+        oracle: &dyn CodeOracle,
+        value: &dyn Display,
+        vars: &dyn Display,
+    ) -> Option<String> {
+        let id = "$0";
+        let mapper = oracle
+            .find(&self.inner)
+            .defaults_mapper(oracle, &id, vars)?;
+        Some(format!(
+            "{value}.map {{ {mapper} }}",
+            value = value,
+            mapper = mapper
+        ))
+    }
+
+    /// A representation of the given literal for this type.
+    /// N.B. `Literal` is aliased from `serde_json::Value`.
+    fn literal(
+        &self,
+        oracle: &dyn CodeOracle,
+        ctx: &dyn Display,
+        renderer: &dyn LiteralRenderer,
+        literal: &Literal,
+    ) -> String {
+        let variant = match literal {
+            serde_json::Value::Array(v) => v,
+            _ => unreachable!(),
+        };
+
+        let v_type = oracle.find(&self.inner);
+        let src: Vec<String> = variant
+            .iter()
+            .map(|v| v_type.literal(oracle, ctx, renderer, v))
+            .collect();
+
+        format!("[{}]", src.join(", "))
+    }
+}
+
+#[cfg(test)]
+mod unit_tests {
+
+    use serde_json::json;
+
+    use crate::backends::swift::gen_structs::{
+        enum_::EnumCodeType, object::ObjectCodeType, primitives::StringCodeType,
+    };
+    use crate::backends::TypeIdentifier;
+
+    use super::*;
+
+    struct TestCodeOracle;
+    impl CodeOracle for TestCodeOracle {
+        fn find(&self, type_: &TypeIdentifier) -> Box<dyn CodeType> {
+            match type_ {
+                TypeIdentifier::String => Box::new(StringCodeType) as Box<dyn CodeType>,
+                TypeIdentifier::Enum(s) => {
+                    Box::new(EnumCodeType::new(s.clone())) as Box<dyn CodeType>
+                }
+                TypeIdentifier::Object(s) => {
+                    Box::new(ObjectCodeType::new(s.clone())) as Box<dyn CodeType>
+                }
+                TypeIdentifier::List(i) => Box::new(ListCodeType::new(i)),
+                TypeIdentifier::EnumMap(k, v) => Box::new(MapCodeType::new(k, v)),
+                _ => unreachable!(),
+            }
+        }
+    }
+
+    struct TestRenderer;
+    impl LiteralRenderer for TestRenderer {
+        fn literal(
+            &self,
+            _oracle: &dyn CodeOracle,
+            _typ: &TypeIdentifier,
+            _value: &Literal,
+            _ctx: &dyn Display,
+        ) -> String {
+            unreachable!()
+        }
+    }
+
+    fn oracle() -> Box<dyn CodeOracle> {
+        Box::new(TestCodeOracle) as Box<dyn CodeOracle>
+    }
+
+    fn type_(nm: &str) -> TypeIdentifier {
+        match nm {
+            "String" => TypeIdentifier::String,
+            "AnObject" => TypeIdentifier::Object("AnObject".to_string()),
+            nm => TypeIdentifier::Enum(nm.to_string()),
+        }
+    }
+
+    fn list_type(item: &str) -> Box<dyn CodeType> {
+        Box::new(ListCodeType::new(&type_(item)))
+    }
+
+    fn map_type(k: &str, v: &str) -> Box<dyn CodeType> {
+        Box::new(MapCodeType::new(&type_(k), &type_(v)))
+    }
+
+    fn getter_with_fallback(
+        ct: &dyn CodeType,
+        vars: &dyn Display,
+        prop: &dyn Display,
+        def: &dyn Display,
+    ) -> String {
+        let oracle = &*oracle();
+        ct.property_getter(oracle, vars, prop, def)
+    }
+
+    #[test]
+    fn test_list_type_label() {
+        let oracle = &*oracle();
+        let ct = list_type("String");
+        assert_eq!("[String]".to_string(), ct.type_label(oracle));
+
+        let ct = list_type("AnEnum");
+        assert_eq!("[AnEnum]".to_string(), ct.type_label(oracle));
+    }
+
+    #[test]
+    fn test_list_literal() {
+        let oracle = &*oracle();
+        let finder = &TestRenderer;
+        let ctx = String::from("ctx");
+        let ct = list_type("String");
+        assert_eq!(
+            r#"["x", "y", "z"]"#.to_string(),
+            ct.literal(oracle, &ctx, finder, &json!(["x", "y", "z"]))
+        );
+
+        let ct = list_type("AnEnum");
+        assert_eq!(
+            r#"[.x, .y, .z]"#.to_string(),
+            ct.literal(oracle, &ctx, finder, &json!(["x", "y", "z"]))
+        );
+    }
+
+    #[test]
+    fn test_list_get_value() {
+        let oracle = &*oracle();
+
+        let ct = list_type("AnEnum");
+        assert_eq!(
+            r#"v.getStringList("the-property")"#.to_string(),
+            ct.value_getter(oracle, &"v", &"the-property")
+        );
+
+        let ct = list_type("AnObject");
+        assert_eq!(
+            r#"v.getVariablesList("the-property")"#.to_string(),
+            ct.value_getter(oracle, &"v", &"the-property")
+        );
+
+        let ct = list_type("String");
+        assert_eq!(
+            r#"v.getStringList("the-property")"#.to_string(),
+            ct.value_getter(oracle, &"v", &"the-property")
+        );
+    }
+
+    #[test]
+    fn test_list_getter_with_fallback() {
+        let ct = list_type("String");
+        assert_eq!(
+            r#"vars.getStringList("the-property") ?? default"#.to_string(),
+            getter_with_fallback(&*ct, &"vars", &"the-property", &"default")
+        );
+
+        let ct = list_type("AnEnum");
+        assert_eq!(
+            r#"vars.getStringList("the-property")?.compactMap(AnEnum.enumValue) ?? default"#
+                .to_string(),
+            getter_with_fallback(&*ct, &"vars", &"the-property", &"default")
+        );
+
+        let ct = list_type("AnObject");
+        assert_eq!(
+            r#"vars.getVariablesList("the-property")?.compactMap(AnObject.create) ?? default"#
+                .to_string(),
+            getter_with_fallback(&*ct, &"vars", &"the-property", &"default")
+        );
+    }
+
+    #[test]
+    fn test_map_type_label() {
+        let oracle = &*oracle();
+        let ct = map_type("String", "String");
+        assert_eq!("[String: String]".to_string(), ct.type_label(oracle));
+
+        let ct = map_type("String", "AnEnum");
+        assert_eq!("[String: AnEnum]".to_string(), ct.type_label(oracle));
+    }
+
+    #[test]
+    fn test_map_literal() {
+        let oracle = &*oracle();
+        let finder = &TestRenderer;
+        let ctx = String::from("ctx");
+
+        let ct = map_type("String", "AnEnum");
+        assert_eq!(
+            r#"["a": .a, "b": .b]"#.to_string(),
+            ct.literal(oracle, &ctx, finder, &json!({"a": "a", "b": "b"}))
+        );
+
+        let ct = map_type("AnEnum", "String");
+        assert_eq!(
+            r#"[.a: "a", .b: "b"]"#.to_string(),
+            ct.literal(oracle, &ctx, finder, &json!({"a": "a", "b": "b"}))
+        );
+    }
+
+    #[test]
+    fn test_map_get_value() {
+        let oracle = &*oracle();
+
+        let ct = map_type("String", "AnEnum");
+        assert_eq!(
+            r#"v.getStringMap("the-property")"#.to_string(),
+            ct.value_getter(oracle, &"v", &"the-property")
+        );
+
+        let ct = map_type("AnEnum", "String");
+        assert_eq!(
+            r#"v.getStringMap("the-property")"#.to_string(),
+            ct.value_getter(oracle, &"v", &"the-property")
+        );
+
+        let ct = map_type("AnEnum", "Another");
+        assert_eq!(
+            r#"v.getStringMap("the-property")"#.to_string(),
+            ct.value_getter(oracle, &"v", &"the-property")
+        );
+    }
+
+    #[test]
+    fn test_map_getter_with_fallback() {
+        let oracle = &*oracle();
+
+        let ct = map_type("String", "AnEnum");
+        assert_eq!(
+             r#"v.getStringMap("the-property")?.mapValuesNotNull(AnEnum.enumValue).mergeWith(def) ?? def"#.to_string(),
+             ct.property_getter(oracle, &"v", &"the-property", &"def")
+         );
+
+        let ct = map_type("AnEnum", "String");
+        assert_eq!(
+            r#"v.getStringMap("the-property")?.mapKeysNotNull(AnEnum.enumValue).mergeWith(def) ?? def"#
+                .to_string(),
+            ct.property_getter(oracle, &"v", &"the-property", &"def")
+        );
+
+        let ct = map_type("AnEnum", "Another");
+        assert_eq!(
+             r#"v.getStringMap("the-property")?.mapNotNull(AnEnum.enumValue, Another.enumValue).mergeWith(def) ?? def"#
+                 .to_string(),
+             ct.property_getter(oracle, &"v", &"the-property", &"def")
+         );
+
+        let ct = map_type("AnEnum", "AnObject");
+        assert_eq!(
+             r#"v.getVariablesMap("the-property")?.mapNotNull(AnEnum.enumValue, AnObject.create).mergeWith(def, AnObject.mergeWith) ?? def"#.to_string(),
+             ct.property_getter(oracle, &"v", &"the-property", &"def"));
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus_fml/backends/swift/mod.rs.html b/book/rust-docs/src/nimbus_fml/backends/swift/mod.rs.html new file mode 100644 index 0000000000..a624567dea --- /dev/null +++ b/book/rust-docs/src/nimbus_fml/backends/swift/mod.rs.html @@ -0,0 +1,427 @@ +mod.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::error::{FMLError, Result};
+use crate::frontend::AboutBlock;
+use askama::Template;
+
+use crate::command_line::commands::GenerateStructCmd;
+use crate::intermediate_representation::FeatureManifest;
+
+mod gen_structs;
+
+impl AboutBlock {
+    fn nimbus_object_name_swift(&self) -> String {
+        let swift_about = self.swift_about.as_ref().unwrap();
+        swift_about.class.clone()
+    }
+
+    fn nimbus_module_name(&self) -> String {
+        let swift_about = self.swift_about.as_ref().unwrap();
+        swift_about.module.clone()
+    }
+}
+
+pub(crate) fn generate_struct(manifest: &FeatureManifest, cmd: &GenerateStructCmd) -> Result<()> {
+    if manifest.about.swift_about.is_none() {
+        return Err(FMLError::ValidationError(
+            "about".to_string(),
+            format!(
+                "The `about` block is missing a valid `ios` or `swift` entry: {}",
+                &cmd.manifest
+            ),
+        ));
+    }
+
+    let path = &cmd.output;
+    let path = if path.is_dir() {
+        path.join(format!(
+            "{}.swift",
+            manifest.about.nimbus_object_name_swift()
+        ))
+    } else {
+        path.clone()
+    };
+
+    let fm = gen_structs::FeatureManifestDeclaration::new(manifest);
+
+    let contents = fm.render()?;
+
+    std::fs::write(path, contents)?;
+
+    Ok(())
+}
+
+#[cfg(test)]
+pub mod test {
+    use crate::util::{join, pkg_dir, sdk_dir};
+    use anyhow::{bail, Context, Result};
+    use std::{
+        ffi::OsString,
+        path::{Path, PathBuf},
+        process::Command,
+    };
+
+    // The root of the Android kotlin package structure
+    fn sdk_ios_dir() -> String {
+        join(sdk_dir(), "ios/Nimbus")
+    }
+
+    fn mock_nimbus_error_swift() -> String {
+        join(pkg_dir(), "fixtures/ios/runtime/NimbusError.swift")
+    }
+
+    fn mock_uiimage_swift() -> String {
+        join(pkg_dir(), "fixtures/ios/runtime/UIImage.swift")
+    }
+
+    // The file with the swift implementation of FeatureVariables
+    fn variables_swift() -> String {
+        join(sdk_ios_dir(), "FeatureVariables.swift")
+    }
+
+    // The file with the swift implementation of FeatureVariables
+    fn features_swift() -> String {
+        join(sdk_ios_dir(), "FeatureInterface.swift")
+    }
+
+    // The file with the swift implementation of FeatureVariables
+    fn collections_swift() -> String {
+        join(sdk_ios_dir(), "Collections+.swift")
+    }
+
+    // The file with the swift implementation of FeatureVariables
+    fn dictionaries_swift() -> String {
+        join(sdk_ios_dir(), "Dictionary+.swift")
+    }
+
+    // The file with the swift implementation of Bundle extensions
+    fn bundle_swift() -> String {
+        join(sdk_ios_dir(), "Bundle+.swift")
+    }
+
+    // The file with the swift implementation of FeatureHolder
+    fn feature_holder() -> String {
+        join(sdk_ios_dir(), "FeatureHolder.swift")
+    }
+
+    fn hardcoded_nimbus_features() -> String {
+        join(sdk_ios_dir(), "HardcodedNimbusFeatures.swift")
+    }
+
+    // The file with the swift implementation of Feature Manifest protocol file
+    fn generated_feature_manifest() -> String {
+        join(sdk_ios_dir(), "FeatureManifestInterface.swift")
+    }
+
+    fn detect_swiftc() -> Result<bool> {
+        let output = Command::new("which").arg("swiftc").output()?;
+
+        Ok(output.status.success())
+    }
+
+    pub fn compile_manifest_swift(manifest_files: &[String], out_dir: &Path) -> Result<()> {
+        let out_path = PathBuf::from(out_dir);
+        let manifest_files = manifest_files.iter().map(PathBuf::from);
+        let mut dylib_file = out_path.clone();
+        dylib_file.push(format!("lib{}.dylib", "FeatureManifest"));
+
+        // `-emit-library -o <path>` generates a `.dylib`, so that we can use the
+        // Swift module from the REPL. Otherwise, we'll get "Couldn't lookup
+        // symbols" when we try to import the module.
+        // See https://bugs.swift.org/browse/SR-1191.
+
+        let status = Command::new("swiftc")
+            .arg("-module-name")
+            .arg("FeatureManifest")
+            .arg("-emit-library")
+            .arg("-o")
+            .arg(&dylib_file)
+            .arg("-emit-module")
+            .arg("-emit-module-path")
+            .arg(&out_path)
+            .arg("-parse-as-library")
+            .arg("-L")
+            .arg(&out_path)
+            .arg(&collections_swift())
+            .arg(&dictionaries_swift())
+            .arg(&mock_uiimage_swift())
+            .arg(&variables_swift())
+            .arg(&features_swift())
+            .arg(&feature_holder())
+            .arg(&hardcoded_nimbus_features())
+            .arg(&bundle_swift())
+            .arg(&generated_feature_manifest())
+            .arg(&mock_nimbus_error_swift())
+            .args(manifest_files)
+            .spawn()
+            .context("Failed to spawn `swiftc` when compiling bindings")?
+            .wait()
+            .context("Failed to wait for `swiftc` when compiling bindings")?;
+        if !status.success() {
+            bail!("running `swiftc` failed")
+        }
+        Ok(())
+    }
+
+    pub fn run_script(out_dir: &Path, script_file: &Path) -> Result<()> {
+        let mut cmd = Command::new("swift");
+
+        // Find any module maps and/or dylibs in the target directory, and tell swift to use them.
+        // Listing the directory like this is a little bit hacky - it would be nicer if we could tell
+        // Swift to load only the module(s) for the component under test, but the way we're calling
+        // this test function doesn't allow us to pass that name in to the call.
+
+        cmd.arg("-I").arg(out_dir).arg("-L").arg(out_dir);
+        for entry in PathBuf::from(out_dir)
+            .read_dir()
+            .context("Failed to list target directory when running script")?
+        {
+            let entry = entry.context("Failed to list target directory when running script")?;
+            if let Some(ext) = entry.path().extension() {
+                if ext == "dylib" || ext == "so" {
+                    let mut option = OsString::from("-l");
+                    option.push(entry.path());
+                    cmd.arg(option);
+                }
+            }
+        }
+        cmd.arg(script_file);
+
+        let status = cmd
+            .spawn()
+            .context("Failed to spawn `swift` when running script")?
+            .wait()
+            .context("Failed to wait for `swift` when running script")?;
+        if !status.success() {
+            bail!("running `swift` failed")
+        }
+        Ok(())
+    }
+
+    pub fn run_script_with_generated_code(manifest_files: &[String], script: &Path) -> Result<()> {
+        if !detect_swiftc()? {
+            eprintln!("SDK-446 Install swift or add it the PATH to run tests");
+            return Ok(());
+        }
+        let temp = tempfile::tempdir()?;
+        let build_dir = temp.path();
+        compile_manifest_swift(manifest_files, build_dir)?;
+        run_script(build_dir, script)
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus_fml/client/config.rs.html b/book/rust-docs/src/nimbus_fml/client/config.rs.html new file mode 100644 index 0000000000..4a7a4471ed --- /dev/null +++ b/book/rust-docs/src/nimbus_fml/client/config.rs.html @@ -0,0 +1,55 @@ +config.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use std::collections::HashMap;
+
+use crate::util::loaders::LoaderConfig;
+
+#[derive(Debug, Default)]
+pub struct FmlLoaderConfig {
+    pub cache: Option<String>,
+    pub refs: HashMap<String, String>,
+    pub ref_files: Vec<String>,
+}
+
+impl From<FmlLoaderConfig> for LoaderConfig {
+    fn from(value: FmlLoaderConfig) -> Self {
+        let cwd = std::env::current_dir().expect("Current Working Directory is not set");
+        let cache = value.cache.map(|v| cwd.join(v));
+        Self {
+            cwd,
+            refs: value.refs.into_iter().collect(),
+            repo_files: value.ref_files,
+            cache_dir: cache,
+        }
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus_fml/client/descriptor.rs.html b/book/rust-docs/src/nimbus_fml/client/descriptor.rs.html new file mode 100644 index 0000000000..f1074013f0 --- /dev/null +++ b/book/rust-docs/src/nimbus_fml/client/descriptor.rs.html @@ -0,0 +1,213 @@ +descriptor.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use std::collections::BTreeSet;
+
+use email_address::EmailAddress;
+use url::Url;
+
+use crate::{frontend::DocumentationLink, intermediate_representation::FeatureDef, FmlClient};
+
+#[derive(Debug, PartialEq, Default)]
+pub struct FmlFeatureDescriptor {
+    pub(crate) id: String,
+    pub(crate) description: String,
+    pub(crate) is_coenrolling: bool,
+    pub(crate) documentation: Vec<DocumentationLink>,
+    pub(crate) contacts: Vec<EmailAddress>,
+    pub(crate) meta_bug: Option<Url>,
+    pub(crate) events: Vec<Url>,
+    pub(crate) configurator: Option<Url>,
+}
+
+impl From<&FeatureDef> for FmlFeatureDescriptor {
+    fn from(f: &FeatureDef) -> Self {
+        Self {
+            id: f.name(),
+            description: f.doc(),
+            is_coenrolling: f.allow_coenrollment,
+            documentation: f.metadata.documentation.clone(),
+            contacts: f.metadata.contacts.clone(),
+            meta_bug: f.metadata.meta_bug.clone(),
+            events: f.metadata.events.clone(),
+            configurator: f.metadata.configurator.clone(),
+        }
+    }
+}
+
+impl FmlClient {
+    pub fn get_feature_ids(&self) -> Vec<String> {
+        let mut res: BTreeSet<String> = Default::default();
+        for (_, f) in self.manifest.iter_all_feature_defs() {
+            res.insert(f.name());
+        }
+        res.into_iter().collect()
+    }
+
+    pub fn get_feature_descriptor(&self, id: String) -> Option<FmlFeatureDescriptor> {
+        let (_, f) = self.manifest.find_feature(&id)?;
+        Some(f.into())
+    }
+
+    pub fn get_feature_descriptors(&self) -> Vec<FmlFeatureDescriptor> {
+        let mut res: Vec<_> = Default::default();
+        for (_, f) in self.manifest.iter_all_feature_defs() {
+            res.push(f.into());
+        }
+        res
+    }
+}
+
+#[cfg(test)]
+mod unit_tests {
+    use super::*;
+    use crate::{client::test_helper::client, error::Result};
+
+    #[test]
+    fn test_feature_ids() -> Result<()> {
+        let client = client("./bundled_resouces.yaml", "testing")?;
+        let result = client.get_feature_ids();
+
+        assert_eq!(result, vec!["my_images", "my_strings"]);
+        Ok(())
+    }
+
+    #[test]
+    fn test_get_feature() -> Result<()> {
+        let client = client("./bundled_resouces.yaml", "testing")?;
+
+        let result = client.get_feature_descriptor("my_strings".to_string());
+        assert!(result.is_some());
+        assert_eq!(
+            result.unwrap(),
+            FmlFeatureDescriptor {
+                id: "my_strings".to_string(),
+                description: "Testing all the ways bundled text can work".to_string(),
+                is_coenrolling: false,
+                ..Default::default()
+            }
+        );
+
+        let result = client.get_feature_descriptor("my_images".to_string());
+        assert!(result.is_some());
+        assert_eq!(
+            result.unwrap(),
+            FmlFeatureDescriptor {
+                id: "my_images".to_string(),
+                description: "Testing all the ways bundled images can work".to_string(),
+                is_coenrolling: false,
+                ..Default::default()
+            }
+        );
+
+        Ok(())
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus_fml/client/inspector.rs.html b/book/rust-docs/src/nimbus_fml/client/inspector.rs.html new file mode 100644 index 0000000000..9140f28915 --- /dev/null +++ b/book/rust-docs/src/nimbus_fml/client/inspector.rs.html @@ -0,0 +1,985 @@ +inspector.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::{
+    error::{ClientError, FMLError, Result},
+    intermediate_representation::FeatureManifest,
+    FmlClient, JsonObject,
+};
+use serde_json::Value;
+use std::sync::Arc;
+
+impl FmlClient {
+    pub fn get_feature_inspector(&self, id: String) -> Option<Arc<FmlFeatureInspector>> {
+        _ = self.manifest.find_feature(&id)?;
+        Some(Arc::new(FmlFeatureInspector::new(
+            self.manifest.clone(),
+            id,
+        )))
+    }
+}
+
+pub struct FmlFeatureInspector {
+    manifest: Arc<FeatureManifest>,
+    feature_id: String,
+}
+
+impl FmlFeatureInspector {
+    pub(crate) fn new(manifest: Arc<FeatureManifest>, feature_id: String) -> Self {
+        Self {
+            manifest,
+            feature_id,
+        }
+    }
+
+    pub fn get_default_json(&self) -> Result<JsonObject> {
+        match self
+            .manifest
+            .find_feature(&self.feature_id)
+            .map(|(_, f)| f.default_json())
+            // We know it's safe to unwrap here, because find_feature returns something, because we constructed
+            // a inspector with the feature id.
+            .unwrap()
+        {
+            Value::Object(map) => Ok(map),
+            _ => Err(FMLError::ClientError(ClientError::InvalidFeatureValue(
+                "A non-JSON object is returned as default. This is likely a Nimbus FML bug."
+                    .to_string(),
+            ))),
+        }
+    }
+
+    pub fn is_feature_valid(&self, value: JsonObject) -> Result<bool> {
+        self.manifest
+            .validate_feature_config(&self.feature_id, serde_json::Value::Object(value))
+            .map(|_| true)
+    }
+
+    pub fn get_first_error(&self, string: String) -> Option<FmlEditorError> {
+        match self.get_syntax_error(&string) {
+            Ok(json) => self.get_semantic_error(&string, json).err(),
+            Err(err) => Some(err),
+        }
+    }
+
+    pub fn get_errors(&self, string: String) -> Option<Vec<FmlEditorError>> {
+        self.get_first_error(string).map(|e| vec![e])
+    }
+}
+
+impl FmlFeatureInspector {
+    fn get_syntax_error(&self, string: &str) -> Result<Value, FmlEditorError> {
+        let json = serde_json::from_str::<Value>(string);
+        if let Err(e) = json {
+            let col = e.column();
+            return Err(FmlEditorError {
+                message: "Need valid JSON object".to_string(),
+                // serde_json errors are 1 indexed.
+                line: e.line() as u32 - 1,
+                col: if col == 0 { 0 } else { col - 1 } as u32,
+                highlight: None,
+            });
+        }
+        let json = json.ok().unwrap();
+        if json.is_object() {
+            Ok(json)
+        } else {
+            Err(FmlEditorError {
+                message: "Need valid JSON object".to_string(),
+                line: 0,
+                col: 0,
+                highlight: Some(string.to_string()),
+            })
+        }
+    }
+
+    fn get_semantic_error(&self, src: &str, value: Value) -> Result<(), FmlEditorError> {
+        self.manifest
+            .validate_feature_config(&self.feature_id, value)
+            .map_err(|e| match e {
+                FMLError::FeatureValidationError {
+                    literals, message, ..
+                } => {
+                    let highlight = literals.last().cloned();
+                    let (line, col) = find_err(src, literals.into_iter());
+                    FmlEditorError {
+                        message,
+                        line: line as u32,
+                        col: col as u32,
+                        highlight,
+                    }
+                }
+                _ => {
+                    unreachable!("Error {e:?} should be caught as FeatureValidationError");
+                }
+            })
+            .map(|_| ())
+    }
+}
+
+fn find_err(src: &str, path: impl Iterator<Item = String>) -> (usize, usize) {
+    let mut lines = src.lines();
+
+    let mut line_no = 0;
+    let mut col_no = 0;
+
+    let mut first_match = false;
+    let mut cur = lines.next().unwrap_or_default();
+
+    for p in path {
+        loop {
+            // If we haven't had our first match of the line, then start there at the beginning.
+            // Otherwise, start one char on from where we were last time.
+            let start = if !first_match { 0 } else { col_no + 1 };
+
+            // if let Some(i) = cur[start..].find(&p).map(|i| i + start) {
+            if let Some(i) = find_index(cur, &p, start) {
+                col_no = i;
+                first_match = true;
+                break;
+            } else if let Some(next) = lines.next() {
+                // we try the next line!
+                cur = next;
+                line_no += 1;
+                first_match = false;
+                col_no = 0;
+            } else {
+                // we've run out of lines, so we should return
+                return (0, 0);
+            }
+        }
+    }
+
+    (line_no, col_no)
+}
+
+fn find_index(cur: &str, pattern: &str, start: usize) -> Option<usize> {
+    cur.match_indices(pattern)
+        .find(|(i, _)| i >= &start)
+        .map(|(i, _)| i)
+}
+
+#[derive(Debug, PartialEq)]
+pub struct FmlEditorError {
+    pub message: String,
+    pub line: u32,
+    pub col: u32,
+    pub highlight: Option<String>,
+}
+
+#[cfg(test)]
+mod unit_tests {
+    use crate::client::test_helper::client;
+
+    use super::*;
+
+    #[test]
+    fn test_construction() -> Result<()> {
+        let client = client("./nimbus_features.yaml", "release")?;
+        assert_eq!(
+            client.get_feature_ids(),
+            vec!["dialog-appearance".to_string()]
+        );
+        let f = client.get_feature_inspector("dialog-appearance".to_string());
+        assert!(f.is_some());
+
+        let f = client.get_feature_inspector("not-there".to_string());
+        assert!(f.is_none());
+
+        Ok(())
+    }
+
+    fn error(message: &str, line: u32, col: u32, token: Option<&str>) -> FmlEditorError {
+        FmlEditorError {
+            message: message.to_string(),
+            line,
+            col,
+            highlight: token.map(str::to_string),
+        }
+    }
+
+    #[test]
+    fn test_get_first_error_invalid_json() -> Result<()> {
+        let client = client("./nimbus_features.yaml", "release")?;
+        let f = client
+            .get_feature_inspector("dialog-appearance".to_string())
+            .unwrap();
+
+        fn test_syntax_error(f: &FmlFeatureInspector, input: &str, col: u32, highlight: bool) {
+            if let Some(e) = f.get_first_error(input.to_string()) {
+                let highlight = if highlight { Some(input) } else { None };
+                assert_eq!(e, error("Need valid JSON object", 0, col, highlight))
+            } else {
+                unreachable!("No error for \"{input}\"");
+            }
+        }
+
+        test_syntax_error(&f, "", 0, false);
+        test_syntax_error(&f, "x", 0, false);
+        test_syntax_error(&f, "{ \"\" }, ", 5, false);
+        test_syntax_error(&f, "{ \"foo\":", 7, false);
+
+        test_syntax_error(&f, "[]", 0, true);
+        test_syntax_error(&f, "1", 0, true);
+        test_syntax_error(&f, "true", 0, true);
+        test_syntax_error(&f, "\"string\"", 0, true);
+
+        assert!(f.get_first_error("{}".to_string()).is_none());
+        Ok(())
+    }
+
+    #[test]
+    fn test_get_first_error_type_invalid() -> Result<()> {
+        let client = client("./nimbus_features.yaml", "release")?;
+        let f = client
+            .get_feature_inspector("dialog-appearance".to_string())
+            .unwrap();
+
+        let s = r#"{}"#;
+        assert!(f.get_first_error(s.to_string()).is_none());
+        let s = r#"{
+            "positive": {}
+        }"#;
+        assert!(f.get_first_error(s.to_string()).is_none());
+
+        let s = r#"{
+            "positive": 1
+        }"#;
+        if let Some(_err) = f.get_first_error(s.to_string()) {
+        } else {
+            unreachable!("No error for \"{s}\"");
+        }
+
+        let s = r#"{
+            "positive1": {}
+        }"#;
+        if let Some(_err) = f.get_first_error(s.to_string()) {
+        } else {
+            unreachable!("No error for \"{s}\"");
+        }
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_find_err() -> Result<()> {
+        fn do_test(s: &str, path: &[&str], expected: (usize, usize)) {
+            let p = path.last().unwrap();
+            let path = path.iter().map(|p| p.to_string());
+            assert_eq!(
+                find_err(s, path),
+                expected,
+                "Can't find \"{p}\" at {expected:?} in {s}"
+            );
+        }
+
+        fn do_multi(s: &[&str], path: &[&str], expected: (usize, usize)) {
+            let s = s.join("\n");
+            do_test(&s, path, expected);
+        }
+
+        do_test("ab cd", &["ab", "cd"], (0, 3));
+
+        do_test("ab ab", &["ab"], (0, 0));
+        do_test("ab ab", &["ab", "ab"], (0, 3));
+
+        do_multi(
+            &["ab xx cd", "xx ef xx gh", "ij xx"],
+            &["ab", "cd", "gh", "xx"],
+            (2, 3),
+        );
+
+        do_multi(
+            &[
+                "{",                       // 0
+                "  boolean: true,",        // 1
+                "  object: {",             // 2
+                "    integer: \"string\"", // 3
+                "  }",                     // 4
+                "}",                       // 5
+            ],
+            &["object", "integer", "\"string\""],
+            (3, 13),
+        );
+
+        // pathological case
+        do_multi(
+            &[
+                "{",                       // 0
+                "  boolean: true,",        // 1
+                "  object: {",             // 2
+                "    integer: 1,",         // 3
+                "    astring: \"string\"", // 4
+                "  },",                    // 5
+                "  integer: \"string\"",   // 6
+                "}",                       // 7
+            ],
+            &["integer", "\"string\""],
+            (4, 13),
+        );
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_find_index_from() -> Result<()> {
+        assert_eq!(find_index("012345601", "01", 0), Some(0));
+        assert_eq!(find_index("012345601", "01", 1), Some(7));
+        assert_eq!(find_index("012345602", "01", 1), None);
+
+        // TODO unicode indexing does not work.
+        // assert_eq!(find_index("åéîø token", "token", 0), Some(5));
+        Ok(())
+    }
+
+    #[test]
+    fn test_semantic_errors() -> Result<()> {
+        let client = client("./browser.yaml", "release")?;
+        let inspector = client
+            .get_feature_inspector("nimbus-validation".to_string())
+            .unwrap();
+
+        let do_test = |lines: &[&str], token: &str, expected: (u32, u32)| {
+            let input = lines.join("\n");
+            let err = inspector
+                .get_first_error(input.clone())
+                .unwrap_or_else(|| unreachable!("No error for \"{input}\""));
+
+            assert_eq!(
+                err.highlight,
+                Some(token.to_string()),
+                "Token {token} not detected in error in {input}"
+            );
+
+            let observed = (err.line, err.col);
+            assert_eq!(
+                expected, observed,
+                "Error at {token} in the wrong place in {input}"
+            );
+        };
+
+        // invalid property name.
+        do_test(
+            &[
+                // 012345678901234567890
+                r#"{"#,              // 0
+                r#"  "invalid": 1"#, // 1
+                r#"}"#,              // 2
+            ],
+            "\"invalid\"",
+            (1, 2),
+        );
+
+        // simple type mismatch
+        do_test(
+            &[
+                // 012345678901234567890
+                r#"{"#,                // 0
+                r#"  "icon-type": 1"#, // 1
+                r#"}"#,                // 2
+            ],
+            "1",
+            (1, 15),
+        );
+
+        // enum mismatch
+        do_test(
+            &[
+                // 012345678901234567890
+                r#"{"#,                        // 0
+                r#"  "icon-type": "invalid""#, // 1
+                r#"}"#,                        // 2
+            ],
+            "\"invalid\"",
+            (1, 15),
+        );
+
+        // invalid field within object
+        do_test(
+            &[
+                // 012345678901234567890
+                r#"{"#,                   // 0
+                r#"  "nested": {"#,       // 1
+                r#"    "invalid": true"#, // 2
+                r#"  }"#,                 // 3
+                r#"}"#,                   // 4
+            ],
+            "\"invalid\"",
+            (2, 4),
+        );
+
+        // nested in an object type mismatch
+        do_test(
+            &[
+                // 012345678901234567890
+                r#"{"#,                    // 0
+                r#"  "nested": {"#,        // 1
+                r#"    "is-useful": 256"#, // 2
+                r#"  }"#,                  // 3
+                r#"}"#,                    // 4
+            ],
+            "256",
+            (2, 17),
+        );
+
+        // nested in a map type mismatch
+        do_test(
+            &[
+                // 012345678901234567890
+                r#"{"#,                      // 0
+                r#"  "string-int-map": {"#,  // 1
+                r#"    "valid": "invalid""#, // 2
+                r#"  }"#,                    // 3
+                r#"}"#,                      // 4
+            ],
+            "\"invalid\"",
+            (2, 13),
+        );
+
+        // invalid key in enum map
+        do_test(
+            &[
+                // 012345678901234567890
+                r#"{"#,                 // 0
+                r#"  "enum-map": {"#,   // 1
+                r#"    "invalid": 42"#, // 2
+                r#"  }"#,               // 3
+                r#"}"#,                 // 4
+            ],
+            "\"invalid\"",
+            (2, 4),
+        );
+
+        // type mismatch in list
+        do_test(
+            &[
+                // 012345678901234567890
+                r#"{"#,                         // 0
+                r#"  "nested-list": ["#,        // 1
+                r#"     {"#,                    // 2
+                r#"        "is-useful": true"#, // 3
+                r#"     },"#,                   // 4
+                r#"     false"#,                // 5
+                r#"  ]"#,                       // 6
+                r#"}"#,                         // 7
+            ],
+            "false",
+            (5, 5),
+        );
+
+        // Difficult!
+        do_test(
+            &[
+                // 012345678901234567890
+                r#"{"#,                          // 0
+                r#"  "string-int-map": {"#,      // 1
+                r#"    "nested": 1,"#,           // 2
+                r#"    "is-useful": 2,"#,        // 3
+                r#"    "invalid": 3"#,           // 4 error is not here!
+                r#"  },"#,                       // 5
+                r#"  "nested": {"#,              // 6
+                r#"    "is-useful": "invalid""#, // 7 error is here!
+                r#"  }"#,                        // 8
+                r#"}"#,                          // 9
+            ],
+            "\"invalid\"",
+            (7, 17),
+        );
+
+        Ok(())
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus_fml/client/mod.rs.html b/book/rust-docs/src/nimbus_fml/client/mod.rs.html new file mode 100644 index 0000000000..5dbcf9248c --- /dev/null +++ b/book/rust-docs/src/nimbus_fml/client/mod.rs.html @@ -0,0 +1,645 @@ +mod.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+mod config;
+mod descriptor;
+mod inspector;
+#[cfg(test)]
+mod test_helper;
+
+pub use config::FmlLoaderConfig;
+cfg_if::cfg_if! {
+    if #[cfg(feature = "uniffi-bindings")] {
+    use crate::frontend::DocumentationLink;
+    use url::Url;
+    use std::str::FromStr;
+    use email_address::EmailAddress;
+    use descriptor::FmlFeatureDescriptor;
+    }
+}
+pub use inspector::{FmlEditorError, FmlFeatureInspector};
+use serde_json::Value;
+
+use crate::{
+    error::{ClientError::JsonMergeError, FMLError, Result},
+    intermediate_representation::FeatureManifest,
+    parser::Parser,
+    util::loaders::{FileLoader, LoaderConfig},
+};
+use std::collections::HashMap;
+
+use std::sync::Arc;
+
+pub struct MergedJsonWithErrors {
+    pub json: String,
+    pub errors: Vec<FMLError>,
+}
+
+pub struct FmlClient {
+    pub(crate) manifest: Arc<FeatureManifest>,
+    pub(crate) default_json: serde_json::Map<String, serde_json::Value>,
+}
+
+fn get_default_json_for_manifest(manifest: &FeatureManifest) -> Result<JsonObject> {
+    if let Value::Object(json) = manifest.default_json() {
+        Ok(json)
+    } else {
+        Err(FMLError::ClientError(JsonMergeError(
+            "Manifest default json is not an object".to_string(),
+        )))
+    }
+}
+
+impl FmlClient {
+    /// Constructs a new FmlClient object.
+    ///
+    /// Definitions of the parameters are as follows:
+    /// - `manifest_path`: The path (relative to the current working directory) to the fml.yml that should be loaded.
+    /// - `channel`: The channel that should be loaded for the manifest.
+    pub fn new(manifest_path: String, channel: String) -> Result<Self> {
+        Self::new_with_ref(manifest_path, channel, None)
+    }
+
+    pub fn new_with_ref(
+        manifest_path: String,
+        channel: String,
+        ref_: Option<String>,
+    ) -> Result<Self> {
+        let config = Self::create_loader(&manifest_path, ref_.as_deref());
+        Self::new_with_config(manifest_path, channel, config)
+    }
+
+    pub fn new_with_config(
+        manifest_path: String,
+        channel: String,
+        config: FmlLoaderConfig,
+    ) -> Result<Self> {
+        let config: LoaderConfig = config.into();
+        let files = FileLoader::try_from(&config)?;
+        let path = files.file_path(&manifest_path)?;
+        let parser: Parser = Parser::new(files, path)?;
+        let ir = parser.get_intermediate_representation(Some(&channel))?;
+        ir.validate_manifest()?;
+
+        Ok(FmlClient {
+            default_json: get_default_json_for_manifest(&ir)?,
+            manifest: Arc::new(ir),
+        })
+    }
+
+    #[cfg(test)]
+    pub fn new_from_manifest(manifest: FeatureManifest) -> Self {
+        manifest.validate_manifest().ok();
+        Self {
+            default_json: get_default_json_for_manifest(&manifest).ok().unwrap(),
+            manifest: Arc::new(manifest),
+        }
+    }
+
+    fn create_loader(manifest_path: &str, ref_: Option<&str>) -> FmlLoaderConfig {
+        let mut refs: HashMap<_, _> = Default::default();
+        match (LoaderConfig::repo_and_path(manifest_path), ref_) {
+            (Some((repo, _)), Some(ref_)) => refs.insert(repo, ref_.to_string()),
+            _ => None,
+        };
+
+        FmlLoaderConfig {
+            refs,
+            ..Default::default()
+        }
+    }
+
+    /// Validates a supplied feature configuration. Returns true or an FMLError.
+    pub fn is_feature_valid(&self, feature_id: String, value: JsonObject) -> Result<bool> {
+        self.manifest
+            .validate_feature_config(&feature_id, serde_json::Value::Object(value))
+            .map(|_| true)
+    }
+
+    /// Validates a supplied list of feature configurations. The valid configurations will be merged into the manifest's
+    /// default feature JSON, and invalid configurations will be returned as a list of their respective errors.
+    pub fn merge(
+        &self,
+        feature_configs: HashMap<String, JsonObject>,
+    ) -> Result<MergedJsonWithErrors> {
+        let mut json = self.default_json.clone();
+        let mut errors: Vec<FMLError> = Default::default();
+        for (feature_id, value) in feature_configs {
+            match self
+                .manifest
+                .validate_feature_config(&feature_id, serde_json::Value::Object(value))
+            {
+                Ok(fd) => {
+                    json.insert(feature_id, fd.default_json());
+                }
+                Err(e) => errors.push(e),
+            };
+        }
+        Ok(MergedJsonWithErrors {
+            json: serde_json::to_string(&json)?,
+            errors,
+        })
+    }
+
+    /// Returns the default feature JSON for the loaded FML's selected channel.
+    pub fn get_default_json(&self) -> Result<String> {
+        Ok(serde_json::to_string(&self.default_json)?)
+    }
+
+    /// Returns a list of feature ids that support coenrollment.
+    pub fn get_coenrolling_feature_ids(&self) -> Result<Vec<String>> {
+        Ok(self.manifest.get_coenrolling_feature_ids())
+    }
+}
+
+pub(crate) type JsonObject = serde_json::Map<String, serde_json::Value>;
+
+#[cfg(feature = "uniffi-bindings")]
+impl UniffiCustomTypeConverter for JsonObject {
+    type Builtin = String;
+
+    fn into_custom(val: Self::Builtin) -> uniffi::Result<Self> {
+        let json: serde_json::Value = serde_json::from_str(&val)?;
+
+        match json.as_object() {
+            Some(obj) => Ok(obj.to_owned()),
+            _ => Err(uniffi::deps::anyhow::anyhow!(
+                "Unexpected JSON-non-object in the bagging area"
+            )),
+        }
+    }
+
+    fn from_custom(obj: Self) -> Self::Builtin {
+        serde_json::Value::Object(obj).to_string()
+    }
+}
+
+#[cfg(feature = "uniffi-bindings")]
+impl UniffiCustomTypeConverter for Url {
+    type Builtin = String;
+
+    fn into_custom(val: Self::Builtin) -> uniffi::Result<Self> {
+        Ok(Self::from_str(&val)?)
+    }
+
+    fn from_custom(obj: Self) -> Self::Builtin {
+        obj.as_str().to_string()
+    }
+}
+
+#[cfg(feature = "uniffi-bindings")]
+impl UniffiCustomTypeConverter for EmailAddress {
+    type Builtin = String;
+
+    fn into_custom(val: Self::Builtin) -> uniffi::Result<Self> {
+        Ok(Self::from_str(val.as_str())?)
+    }
+
+    fn from_custom(obj: Self) -> Self::Builtin {
+        obj.as_str().to_string()
+    }
+}
+
+#[cfg(feature = "uniffi-bindings")]
+uniffi::include_scaffolding!("fml");
+
+#[cfg(test)]
+mod unit_tests {
+    use super::*;
+    use crate::intermediate_representation::{
+        unit_tests::get_feature_manifest, FeatureDef, ModuleId, PropDef, TypeRef,
+    };
+    use serde_json::{json, Map, Number, Value};
+    use std::collections::HashMap;
+
+    fn create_manifest() -> FeatureManifest {
+        let fm_i = get_feature_manifest(
+            vec![],
+            vec![],
+            vec![FeatureDef {
+                name: "feature_i".into(),
+                props: vec![PropDef::new(
+                    "prop_i_1",
+                    TypeRef::String,
+                    Value::String("prop_i_1_value".into()),
+                )],
+                metadata: Default::default(),
+                ..Default::default()
+            }],
+            HashMap::new(),
+        );
+
+        get_feature_manifest(
+            vec![],
+            vec![],
+            vec![FeatureDef {
+                name: "feature".into(),
+                props: vec![PropDef::new(
+                    "prop_1",
+                    TypeRef::String,
+                    Value::String("prop_1_value".into()),
+                )],
+                metadata: Default::default(),
+                allow_coenrollment: true,
+            }],
+            HashMap::from([(ModuleId::Local("test".into()), fm_i)]),
+        )
+    }
+
+    #[test]
+    fn test_get_default_json() -> Result<()> {
+        let json_result = get_default_json_for_manifest(&create_manifest())?;
+
+        assert_eq!(
+            Value::Object(json_result),
+            json!({
+                "feature": {
+                    "prop_1": "prop_1_value"
+                },
+                "feature_i": {
+                    "prop_i_1": "prop_i_1_value"
+                }
+            })
+        );
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_validate_feature_config() -> Result<()> {
+        let client: FmlClient = create_manifest().into();
+
+        assert!(client.is_feature_valid(
+            "feature".to_string(),
+            Map::from_iter([("prop_1".to_string(), Value::String("new value".into()))])
+        )?);
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_validate_and_merge_feature_configs() -> Result<()> {
+        let client: FmlClient = create_manifest().into();
+
+        let result = client.merge(HashMap::from_iter([
+            (
+                "feature".to_string(),
+                Map::from_iter([("prop_1".to_string(), Value::String("new value".to_string()))]),
+            ),
+            (
+                "feature_i".to_string(),
+                Map::from_iter([("prop_i_1".to_string(), Value::Number(Number::from(1)))]),
+            ),
+        ]))?;
+
+        assert_eq!(
+            serde_json::from_str::<Value>(&result.json)?,
+            json!({
+                "feature": {
+                    "prop_1": "new value"
+                },
+                "feature_i": {
+                    "prop_i_1": "prop_i_1_value"
+                }
+            })
+        );
+        assert_eq!(result.errors.len(), 1);
+        assert_eq!(result.errors[0].to_string(), "Validation Error at features/feature_i.prop_i_1: Mismatch between type String and default 1".to_string());
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_get_coenrolling_feature_ids() -> Result<()> {
+        let client: FmlClient = create_manifest().into();
+        let result = client.get_coenrolling_feature_ids();
+
+        assert_eq!(result.unwrap(), vec!["feature"]);
+
+        Ok(())
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus_fml/command_line/commands.rs.html b/book/rust-docs/src/nimbus_fml/command_line/commands.rs.html new file mode 100644 index 0000000000..853f32b906 --- /dev/null +++ b/book/rust-docs/src/nimbus_fml/command_line/commands.rs.html @@ -0,0 +1,167 @@ +commands.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::intermediate_representation::TargetLanguage;
+use crate::util::loaders::LoaderConfig;
+use anyhow::{bail, Error, Result};
+use std::path::Path;
+use std::path::PathBuf;
+
+pub(crate) enum CliCmd {
+    Generate(GenerateStructCmd),
+    GenerateExperimenter(GenerateExperimenterManifestCmd),
+    GenerateSingleFileManifest(GenerateSingleFileManifestCmd),
+    FetchFile(LoaderConfig, String),
+    Validate(ValidateCmd),
+    PrintChannels(PrintChannelsCmd),
+}
+
+#[derive(Clone)]
+pub(crate) struct GenerateStructCmd {
+    pub(crate) manifest: String,
+    pub(crate) output: PathBuf,
+    pub(crate) language: TargetLanguage,
+    pub(crate) load_from_ir: bool,
+    pub(crate) channel: String,
+    pub(crate) loader: LoaderConfig,
+}
+
+pub(crate) struct GenerateExperimenterManifestCmd {
+    pub(crate) manifest: String,
+    pub(crate) output: PathBuf,
+    pub(crate) language: TargetLanguage,
+    pub(crate) load_from_ir: bool,
+    pub(crate) loader: LoaderConfig,
+}
+
+pub(crate) struct GenerateSingleFileManifestCmd {
+    pub(crate) manifest: String,
+    pub(crate) output: PathBuf,
+    pub(crate) channel: String,
+    pub(crate) loader: LoaderConfig,
+}
+
+pub(crate) struct ValidateCmd {
+    pub(crate) manifest: String,
+    pub(crate) loader: LoaderConfig,
+}
+
+pub(crate) struct PrintChannelsCmd {
+    pub(crate) manifest: String,
+    pub(crate) loader: LoaderConfig,
+    pub(crate) as_json: bool,
+}
+
+impl TryFrom<&std::ffi::OsStr> for TargetLanguage {
+    type Error = Error;
+    fn try_from(value: &std::ffi::OsStr) -> Result<Self> {
+        if let Some(s) = value.to_str() {
+            TryFrom::try_from(s)
+        } else {
+            bail!("Unreadable target language")
+        }
+    }
+}
+
+impl TryFrom<&Path> for TargetLanguage {
+    type Error = Error;
+    fn try_from(value: &Path) -> Result<Self> {
+        TryFrom::try_from(
+            value
+                .extension()
+                .ok_or_else(|| anyhow::anyhow!("No extension available to determine language"))?,
+        )
+    }
+}
+
+impl TryFrom<String> for TargetLanguage {
+    type Error = Error;
+    fn try_from(value: String) -> Result<Self> {
+        TryFrom::try_from(value.as_str())
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus_fml/command_line/mod.rs.html b/book/rust-docs/src/nimbus_fml/command_line/mod.rs.html new file mode 100644 index 0000000000..560ce78da8 --- /dev/null +++ b/book/rust-docs/src/nimbus_fml/command_line/mod.rs.html @@ -0,0 +1,1135 @@ +mod.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
+540
+541
+542
+543
+544
+545
+546
+547
+548
+549
+550
+551
+552
+553
+554
+555
+556
+557
+558
+559
+560
+561
+562
+563
+564
+565
+566
+567
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+pub(crate) mod commands;
+mod workflows;
+
+use crate::intermediate_representation::TargetLanguage;
+use crate::util::loaders::LoaderConfig;
+use anyhow::{bail, Result};
+use clap::{App, ArgMatches};
+use commands::{
+    CliCmd, GenerateExperimenterManifestCmd, GenerateSingleFileManifestCmd, GenerateStructCmd,
+    PrintChannelsCmd, ValidateCmd,
+};
+
+use std::{
+    collections::BTreeMap,
+    ffi::OsString,
+    path::{Path, PathBuf},
+};
+
+const RELEASE_CHANNEL: &str = "release";
+
+pub fn do_main<I, T>(args: I, cwd: &Path) -> Result<()>
+where
+    I: IntoIterator<Item = T>,
+    T: Into<OsString> + Clone,
+{
+    let cmd = get_command_from_cli(args, cwd)?;
+    process_command(&cmd)
+}
+
+fn process_command(cmd: &CliCmd) -> Result<()> {
+    match cmd {
+        CliCmd::Generate(params) => workflows::generate_struct(params)?,
+        CliCmd::GenerateExperimenter(params) => workflows::generate_experimenter_manifest(params)?,
+        CliCmd::GenerateSingleFileManifest(params) => {
+            workflows::generate_single_file_manifest(params)?
+        }
+        CliCmd::FetchFile(files, nm) => workflows::fetch_file(files, nm)?,
+        CliCmd::Validate(params) => workflows::validate(params)?,
+        CliCmd::PrintChannels(params) => workflows::print_channels(params)?,
+    };
+    Ok(())
+}
+
+fn get_command_from_cli<I, T>(args: I, cwd: &Path) -> Result<CliCmd>
+where
+    I: IntoIterator<Item = T>,
+    T: Into<OsString> + Clone,
+{
+    let yaml = clap::load_yaml!("cli.yaml");
+    let matches = App::from_yaml(yaml).get_matches_from(args);
+
+    Ok(match matches.subcommand() {
+        ("generate", Some(matches)) => {
+            CliCmd::Generate(create_generate_command_from_cli(matches, cwd)?)
+        }
+        ("generate-experimenter", Some(matches)) => CliCmd::GenerateExperimenter(
+            create_generate_command_experimenter_from_cli(matches, cwd)?,
+        ),
+        ("fetch", Some(matches)) => {
+            CliCmd::FetchFile(create_loader(matches, cwd)?, input_file(matches)?)
+        }
+        ("single-file", Some(matches)) => {
+            CliCmd::GenerateSingleFileManifest(create_single_file_from_cli(matches, cwd)?)
+        }
+        ("validate", Some(matches)) => {
+            CliCmd::Validate(create_validate_command_from_cli(matches, cwd)?)
+        }
+        ("channels", Some(matches)) => {
+            CliCmd::PrintChannels(create_print_channels_from_cli(matches, cwd)?)
+        }
+        (word, _) => unimplemented!("Command {} not implemented", word),
+    })
+}
+
+fn create_single_file_from_cli(
+    matches: &ArgMatches,
+    cwd: &Path,
+) -> Result<GenerateSingleFileManifestCmd> {
+    let manifest = input_file(matches)?;
+    let output =
+        file_path("output", matches, cwd).or_else(|_| file_path("OUTPUT", matches, cwd))?;
+    let channel = matches
+        .value_of("channel")
+        .map(str::to_string)
+        .unwrap_or_else(|| RELEASE_CHANNEL.into());
+    let loader = create_loader(matches, cwd)?;
+    Ok(GenerateSingleFileManifestCmd {
+        manifest,
+        output,
+        channel,
+        loader,
+    })
+}
+
+fn create_generate_command_experimenter_from_cli(
+    matches: &ArgMatches,
+    cwd: &Path,
+) -> Result<GenerateExperimenterManifestCmd> {
+    let manifest = input_file(matches)?;
+    let load_from_ir =
+        TargetLanguage::ExperimenterJSON == TargetLanguage::from_extension(&manifest)?;
+    let output =
+        file_path("output", matches, cwd).or_else(|_| file_path("OUTPUT", matches, cwd))?;
+    let language = output.as_path().try_into()?;
+    let _channel = matches.value_of("channel").map(str::to_string);
+    let loader = create_loader(matches, cwd)?;
+    let cmd = GenerateExperimenterManifestCmd {
+        manifest,
+        output,
+        language,
+        load_from_ir,
+        loader,
+    };
+    Ok(cmd)
+}
+
+fn create_generate_command_from_cli(matches: &ArgMatches, cwd: &Path) -> Result<GenerateStructCmd> {
+    let manifest = input_file(matches)?;
+    let load_from_ir = matches!(
+        TargetLanguage::from_extension(&manifest),
+        Ok(TargetLanguage::ExperimenterJSON)
+    );
+    let output =
+        file_path("output", matches, cwd).or_else(|_| file_path("OUTPUT", matches, cwd))?;
+    let language = match matches.value_of("language") {
+        Some(s) => TargetLanguage::try_from(s)?, // the language from the cli will always be recognized
+        None => output.as_path().try_into().map_err(|_| anyhow::anyhow!("Can't infer a target language from the file or directory, so specify a --language flag explicitly"))?,
+    };
+    let channel = matches
+        .value_of("channel")
+        .map(str::to_string)
+        .expect("A channel should be specified with --channel");
+    let loader = create_loader(matches, cwd)?;
+    Ok(GenerateStructCmd {
+        language,
+        manifest,
+        output,
+        load_from_ir,
+        channel,
+        loader,
+    })
+}
+
+fn create_loader(matches: &ArgMatches, cwd: &Path) -> Result<LoaderConfig> {
+    let cwd = cwd.to_path_buf();
+    let cache_dir = matches
+        .value_of("cache-dir")
+        .map(|f| Some(cwd.join(f)))
+        .unwrap_or_default();
+
+    let files = matches.values_of("repo-file").unwrap_or_default();
+    let repo_files = files.into_iter().map(|s| s.to_string()).collect();
+
+    let manifest = input_file(matches)?;
+
+    let _ref = matches.value_of("ref").map(String::from);
+
+    let mut refs: BTreeMap<_, _> = Default::default();
+    match (LoaderConfig::repo_and_path(&manifest), _ref) {
+        (Some((repo, _)), Some(ref_)) => refs.insert(repo, ref_),
+        _ => None,
+    };
+
+    Ok(LoaderConfig {
+        cache_dir,
+        repo_files,
+        cwd,
+        refs,
+    })
+}
+
+fn create_validate_command_from_cli(matches: &ArgMatches, cwd: &Path) -> Result<ValidateCmd> {
+    let manifest = input_file(matches)?;
+    let loader = create_loader(matches, cwd)?;
+    Ok(ValidateCmd { manifest, loader })
+}
+
+fn create_print_channels_from_cli(matches: &ArgMatches, cwd: &Path) -> Result<PrintChannelsCmd> {
+    let manifest = input_file(matches)?;
+    let loader = create_loader(matches, cwd)?;
+    let as_json = matches.is_present("json");
+    Ok(PrintChannelsCmd {
+        manifest,
+        loader,
+        as_json,
+    })
+}
+
+fn input_file(args: &ArgMatches) -> Result<String> {
+    args.value_of("INPUT")
+        .map(String::from)
+        .ok_or_else(|| anyhow::anyhow!("INPUT file or directory is needed, but not specified"))
+}
+
+fn file_path(name: &str, args: &ArgMatches, cwd: &Path) -> Result<PathBuf> {
+    let mut abs = cwd.to_path_buf();
+    match args.value_of(name) {
+        Some(suffix) => {
+            abs.push(suffix);
+            Ok(abs)
+        }
+        _ => bail!("A file path is needed for {}", name),
+    }
+}
+
+#[cfg(test)]
+mod cli_tests {
+    use std::env;
+
+    use super::*;
+
+    const FML_BIN: &str = "nimbus-fml";
+    const TEST_FILE: &str = "fixtures/fe/importing/simple/app.yaml";
+    const TEST_DIR: &str = "fixtures/fe/importing/including-imports";
+    const GENERATED_DIR: &str = "build/cli-test";
+    const CACHE_DIR: &str = "./build/cache";
+    const REPO_FILE_1: &str = "./repos.versions.json";
+    const REPO_FILE_2: &str = "./repos.local.json";
+
+    fn package_dir() -> Result<PathBuf> {
+        let string = env::var("CARGO_MANIFEST_DIR")?;
+        Ok(PathBuf::try_from(string)?)
+    }
+
+    // All these tests just exercise the command line parsing.
+    // Each of the tests construct a command struct from the command line, and then
+    // test the command struct against the expected values.
+
+    ///////////////////////////////////////////////////////////////////////////
+    #[test]
+    fn test_cli_generate_android_features_language_implied() -> Result<()> {
+        let cwd = package_dir()?;
+        let cmd = get_command_from_cli(
+            [
+                FML_BIN,
+                "generate",
+                "--channel",
+                "channel-test",
+                TEST_FILE,
+                "./Implied.kt",
+            ],
+            &cwd,
+        )?;
+
+        assert!(matches!(cmd, CliCmd::Generate(_)));
+
+        if let CliCmd::Generate(cmd) = cmd {
+            assert_eq!(cmd.channel, "channel-test");
+            assert_eq!(cmd.language, TargetLanguage::Kotlin);
+            assert!(!cmd.load_from_ir);
+            assert!(cmd.output.ends_with("Implied.kt"));
+            assert!(cmd.manifest.ends_with(TEST_FILE));
+        }
+        Ok(())
+    }
+
+    #[test]
+    fn test_cli_generate_ios_features_language_implied() -> Result<()> {
+        let cwd = package_dir()?;
+        let cmd = get_command_from_cli(
+            [
+                FML_BIN,
+                "generate",
+                "--channel",
+                "channel-test",
+                TEST_FILE,
+                "./Implied.swift",
+            ],
+            &cwd,
+        )?;
+
+        assert!(matches!(cmd, CliCmd::Generate(_)));
+
+        if let CliCmd::Generate(cmd) = cmd {
+            assert_eq!(cmd.channel, "channel-test");
+            assert_eq!(cmd.language, TargetLanguage::Swift);
+            assert!(!cmd.load_from_ir);
+            assert!(cmd.output.ends_with("Implied.swift"));
+            assert!(cmd.manifest.ends_with(TEST_FILE));
+        }
+        Ok(())
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+
+    #[test]
+    fn test_cli_generate_features_with_remote_flags() -> Result<()> {
+        let cwd = package_dir()?;
+        let cmd = get_command_from_cli(
+            [
+                FML_BIN,
+                "generate",
+                "--repo-file",
+                REPO_FILE_1,
+                "--repo-file",
+                REPO_FILE_2,
+                "--cache-dir",
+                CACHE_DIR,
+                TEST_FILE,
+                "./Implied.swift",
+                "--channel",
+                "channel-test",
+            ],
+            &cwd,
+        )?;
+
+        assert!(matches!(cmd, CliCmd::Generate(_)));
+
+        if let CliCmd::Generate(cmd) = cmd {
+            assert_eq!(cmd.channel, "channel-test");
+            assert_eq!(cmd.language, TargetLanguage::Swift);
+            assert!(!cmd.load_from_ir);
+            assert!(cmd.output.ends_with("Implied.swift"));
+            assert!(cmd.manifest.ends_with(TEST_FILE));
+        }
+        Ok(())
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    #[test]
+    fn test_cli_generate_android_features_language_flag() -> Result<()> {
+        let cwd = package_dir()?;
+        let cmd = get_command_from_cli(
+            [
+                FML_BIN,
+                "generate",
+                "--channel",
+                "channel-test",
+                "--language",
+                "kotlin",
+                TEST_FILE,
+                "./build/generated",
+            ],
+            &cwd,
+        )?;
+
+        assert!(matches!(cmd, CliCmd::Generate(_)));
+
+        if let CliCmd::Generate(cmd) = cmd {
+            assert_eq!(cmd.channel, "channel-test");
+            assert_eq!(cmd.language, TargetLanguage::Kotlin);
+            assert!(!cmd.load_from_ir);
+            assert!(cmd.output.ends_with("build/generated"));
+            assert!(cmd.manifest.ends_with(TEST_FILE));
+        }
+        Ok(())
+    }
+
+    #[test]
+    fn test_cli_generate_ios_features_language_flag() -> Result<()> {
+        let cwd = package_dir()?;
+        let cmd = get_command_from_cli(
+            [
+                FML_BIN,
+                "generate",
+                "--channel",
+                "channel-test",
+                "--language",
+                "swift",
+                TEST_FILE,
+                "./build/generated",
+            ],
+            &cwd,
+        )?;
+
+        assert!(matches!(cmd, CliCmd::Generate(_)));
+
+        if let CliCmd::Generate(cmd) = cmd {
+            assert_eq!(cmd.channel, "channel-test");
+            assert_eq!(cmd.language, TargetLanguage::Swift);
+            assert!(!cmd.load_from_ir);
+            assert!(cmd.output.ends_with("build/generated"));
+            assert!(cmd.manifest.ends_with(TEST_FILE));
+        }
+        Ok(())
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    #[test]
+    fn test_cli_generate_experimenter_android() -> Result<()> {
+        let cwd = package_dir()?;
+        let cmd = get_command_from_cli(
+            [
+                FML_BIN,
+                "generate-experimenter",
+                TEST_FILE,
+                ".experimenter.yaml",
+            ],
+            &cwd,
+        )?;
+
+        assert!(matches!(cmd, CliCmd::GenerateExperimenter(_)));
+
+        if let CliCmd::GenerateExperimenter(cmd) = cmd {
+            assert_eq!(cmd.language, TargetLanguage::ExperimenterYAML);
+            assert!(!cmd.load_from_ir);
+            assert!(cmd.output.ends_with(".experimenter.yaml"));
+            assert!(cmd.manifest.ends_with(TEST_FILE));
+        }
+        Ok(())
+    }
+
+    #[test]
+    fn test_cli_generate_experimenter_ios() -> Result<()> {
+        let cwd = package_dir()?;
+        let cmd = get_command_from_cli(
+            [
+                FML_BIN,
+                "generate-experimenter",
+                "--channel",
+                "test-channel",
+                TEST_FILE,
+                ".experimenter.yaml",
+            ],
+            &cwd,
+        )?;
+
+        assert!(matches!(cmd, CliCmd::GenerateExperimenter(_)));
+
+        if let CliCmd::GenerateExperimenter(cmd) = cmd {
+            assert_eq!(cmd.language, TargetLanguage::ExperimenterYAML);
+            assert!(!cmd.load_from_ir);
+            assert!(cmd.output.ends_with(".experimenter.yaml"));
+            assert!(cmd.manifest.ends_with(TEST_FILE));
+        }
+        Ok(())
+    }
+
+    #[test]
+    fn test_cli_generate_experimenter_with_json() -> Result<()> {
+        let cwd = package_dir()?;
+        let cmd = get_command_from_cli(
+            [
+                FML_BIN,
+                "generate-experimenter",
+                TEST_FILE,
+                ".experimenter.json",
+            ],
+            &cwd,
+        )?;
+
+        assert!(matches!(cmd, CliCmd::GenerateExperimenter(_)));
+
+        if let CliCmd::GenerateExperimenter(cmd) = cmd {
+            assert_eq!(cmd.language, TargetLanguage::ExperimenterJSON);
+            assert!(!cmd.load_from_ir);
+            assert!(cmd.output.ends_with(".experimenter.json"));
+            assert!(cmd.manifest.ends_with(TEST_FILE));
+        }
+        Ok(())
+    }
+
+    #[test]
+    fn test_cli_generate_experimenter_with_remote_flags() -> Result<()> {
+        let cwd = package_dir()?;
+        let cmd = get_command_from_cli(
+            [
+                FML_BIN,
+                "generate-experimenter",
+                "--repo-file",
+                REPO_FILE_1,
+                "--repo-file",
+                REPO_FILE_2,
+                "--cache-dir",
+                CACHE_DIR,
+                TEST_FILE,
+                ".experimenter.json",
+            ],
+            &cwd,
+        )?;
+
+        assert!(matches!(cmd, CliCmd::GenerateExperimenter(_)));
+
+        if let CliCmd::GenerateExperimenter(cmd) = cmd {
+            assert_eq!(cmd.language, TargetLanguage::ExperimenterJSON);
+            assert!(!cmd.load_from_ir);
+            assert!(cmd.output.ends_with(".experimenter.json"));
+            assert!(cmd.manifest.ends_with(TEST_FILE));
+        }
+        Ok(())
+    }
+
+    #[test]
+    fn test_cli_generate_features_for_directory_input() -> Result<()> {
+        let cwd = package_dir()?;
+        let cmd = get_command_from_cli(
+            [
+                FML_BIN,
+                "generate",
+                "--language",
+                "swift",
+                "--channel",
+                "release",
+                TEST_DIR,
+                GENERATED_DIR,
+            ],
+            &cwd,
+        )?;
+
+        assert!(matches!(cmd, CliCmd::Generate(_)));
+
+        if let CliCmd::Generate(cmd) = cmd {
+            assert_eq!(cmd.channel, "release");
+            assert_eq!(cmd.language, TargetLanguage::Swift);
+            assert!(!cmd.load_from_ir);
+            assert!(cmd.output.ends_with("build/cli-test"));
+            assert_eq!(&cmd.manifest, TEST_DIR);
+        }
+        Ok(())
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    #[test]
+    fn test_cli_generate_validate() -> Result<()> {
+        let cwd = package_dir()?;
+        let cmd = get_command_from_cli([FML_BIN, "validate", TEST_FILE], &cwd)?;
+
+        assert!(matches!(cmd, CliCmd::Validate(_)));
+        assert!(matches!(cmd, CliCmd::Validate(c) if c.manifest.ends_with(TEST_FILE)));
+        Ok(())
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    #[test]
+    fn test_cli_print_command() -> Result<()> {
+        let cwd = package_dir()?;
+        let cmd = get_command_from_cli([FML_BIN, "channels", TEST_FILE], &cwd)?;
+
+        assert!(matches!(&cmd, CliCmd::PrintChannels(_)));
+        assert!(matches!(&cmd, CliCmd::PrintChannels(c) if c.manifest.ends_with(TEST_FILE)));
+        assert!(matches!(&cmd, CliCmd::PrintChannels(c) if !c.as_json));
+
+        let cmd = get_command_from_cli([FML_BIN, "channels", TEST_FILE, "--json"], &cwd)?;
+
+        assert!(matches!(&cmd, CliCmd::PrintChannels(_)));
+        assert!(matches!(&cmd, CliCmd::PrintChannels(c) if c.manifest.ends_with(TEST_FILE)));
+        assert!(matches!(&cmd, CliCmd::PrintChannels(c) if c.as_json));
+        Ok(())
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    #[test]
+    fn test_cli_add_ref_arg() -> Result<()> {
+        let cwd = package_dir()?;
+        let cmd = get_command_from_cli(
+            [
+                FML_BIN,
+                "generate-experimenter",
+                "--ref",
+                "my-tag",
+                "@foo/bar/baz.fml.yaml",
+                "./baz.yaml",
+            ],
+            &cwd,
+        )?;
+
+        assert!(matches!(cmd, CliCmd::GenerateExperimenter(_)));
+        assert!(
+            matches!(cmd, CliCmd::GenerateExperimenter(c) if c.loader.refs["@foo/bar"] == "my-tag")
+        );
+        Ok(())
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus_fml/command_line/workflows.rs.html b/book/rust-docs/src/nimbus_fml/command_line/workflows.rs.html new file mode 100644 index 0000000000..4cbc99b569 --- /dev/null +++ b/book/rust-docs/src/nimbus_fml/command_line/workflows.rs.html @@ -0,0 +1,2117 @@ +workflows.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
+540
+541
+542
+543
+544
+545
+546
+547
+548
+549
+550
+551
+552
+553
+554
+555
+556
+557
+558
+559
+560
+561
+562
+563
+564
+565
+566
+567
+568
+569
+570
+571
+572
+573
+574
+575
+576
+577
+578
+579
+580
+581
+582
+583
+584
+585
+586
+587
+588
+589
+590
+591
+592
+593
+594
+595
+596
+597
+598
+599
+600
+601
+602
+603
+604
+605
+606
+607
+608
+609
+610
+611
+612
+613
+614
+615
+616
+617
+618
+619
+620
+621
+622
+623
+624
+625
+626
+627
+628
+629
+630
+631
+632
+633
+634
+635
+636
+637
+638
+639
+640
+641
+642
+643
+644
+645
+646
+647
+648
+649
+650
+651
+652
+653
+654
+655
+656
+657
+658
+659
+660
+661
+662
+663
+664
+665
+666
+667
+668
+669
+670
+671
+672
+673
+674
+675
+676
+677
+678
+679
+680
+681
+682
+683
+684
+685
+686
+687
+688
+689
+690
+691
+692
+693
+694
+695
+696
+697
+698
+699
+700
+701
+702
+703
+704
+705
+706
+707
+708
+709
+710
+711
+712
+713
+714
+715
+716
+717
+718
+719
+720
+721
+722
+723
+724
+725
+726
+727
+728
+729
+730
+731
+732
+733
+734
+735
+736
+737
+738
+739
+740
+741
+742
+743
+744
+745
+746
+747
+748
+749
+750
+751
+752
+753
+754
+755
+756
+757
+758
+759
+760
+761
+762
+763
+764
+765
+766
+767
+768
+769
+770
+771
+772
+773
+774
+775
+776
+777
+778
+779
+780
+781
+782
+783
+784
+785
+786
+787
+788
+789
+790
+791
+792
+793
+794
+795
+796
+797
+798
+799
+800
+801
+802
+803
+804
+805
+806
+807
+808
+809
+810
+811
+812
+813
+814
+815
+816
+817
+818
+819
+820
+821
+822
+823
+824
+825
+826
+827
+828
+829
+830
+831
+832
+833
+834
+835
+836
+837
+838
+839
+840
+841
+842
+843
+844
+845
+846
+847
+848
+849
+850
+851
+852
+853
+854
+855
+856
+857
+858
+859
+860
+861
+862
+863
+864
+865
+866
+867
+868
+869
+870
+871
+872
+873
+874
+875
+876
+877
+878
+879
+880
+881
+882
+883
+884
+885
+886
+887
+888
+889
+890
+891
+892
+893
+894
+895
+896
+897
+898
+899
+900
+901
+902
+903
+904
+905
+906
+907
+908
+909
+910
+911
+912
+913
+914
+915
+916
+917
+918
+919
+920
+921
+922
+923
+924
+925
+926
+927
+928
+929
+930
+931
+932
+933
+934
+935
+936
+937
+938
+939
+940
+941
+942
+943
+944
+945
+946
+947
+948
+949
+950
+951
+952
+953
+954
+955
+956
+957
+958
+959
+960
+961
+962
+963
+964
+965
+966
+967
+968
+969
+970
+971
+972
+973
+974
+975
+976
+977
+978
+979
+980
+981
+982
+983
+984
+985
+986
+987
+988
+989
+990
+991
+992
+993
+994
+995
+996
+997
+998
+999
+1000
+1001
+1002
+1003
+1004
+1005
+1006
+1007
+1008
+1009
+1010
+1011
+1012
+1013
+1014
+1015
+1016
+1017
+1018
+1019
+1020
+1021
+1022
+1023
+1024
+1025
+1026
+1027
+1028
+1029
+1030
+1031
+1032
+1033
+1034
+1035
+1036
+1037
+1038
+1039
+1040
+1041
+1042
+1043
+1044
+1045
+1046
+1047
+1048
+1049
+1050
+1051
+1052
+1053
+1054
+1055
+1056
+1057
+1058
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use glob::MatchOptions;
+use std::collections::HashSet;
+
+use super::commands::{
+    GenerateExperimenterManifestCmd, GenerateSingleFileManifestCmd, GenerateStructCmd,
+    PrintChannelsCmd, ValidateCmd,
+};
+use crate::error::FMLError::CliError;
+use crate::frontend::ManifestFrontEnd;
+use crate::{
+    backends,
+    error::{FMLError, Result},
+    intermediate_representation::{FeatureManifest, TargetLanguage},
+    parser::Parser,
+    util::loaders::{FileLoader, FilePath, LoaderConfig},
+};
+use console::Term;
+use std::path::Path;
+
+/// Use this when recursively looking for files.
+const MATCHING_FML_EXTENSION: &str = ".fml.yaml";
+
+pub(crate) fn generate_struct(cmd: &GenerateStructCmd) -> Result<()> {
+    let files: FileLoader = TryFrom::try_from(&cmd.loader)?;
+
+    let filename = &cmd.manifest;
+    let input = files.file_path(filename)?;
+
+    match (&input, &cmd.output.is_dir()) {
+        (FilePath::Remote(_), _) => generate_struct_single(&files, input, cmd),
+        (FilePath::Local(file), _) if file.is_file() => generate_struct_single(&files, input, cmd),
+        (FilePath::Local(dir), true) if dir.is_dir() => generate_struct_from_dir(&files, cmd, dir),
+        (_, true) => generate_struct_from_glob(&files, cmd, filename),
+        _ => Err(FMLError::CliError(
+            "Cannot generate a single output file from an input directory".to_string(),
+        )),
+    }
+}
+
+fn generate_struct_from_dir(files: &FileLoader, cmd: &GenerateStructCmd, cwd: &Path) -> Result<()> {
+    let entries = cwd.read_dir()?;
+    for entry in entries.filter_map(Result::ok) {
+        let pb = entry.path();
+        if pb.is_dir() {
+            generate_struct_from_dir(files, cmd, &pb)?;
+        } else if let Some(nm) = pb.file_name().map(|s| s.to_str().unwrap_or_default()) {
+            if nm.ends_with(MATCHING_FML_EXTENSION) {
+                let path = pb.as_path().into();
+                generate_struct_single(files, path, cmd)?;
+            }
+        }
+    }
+    Ok(())
+}
+
+fn generate_struct_from_glob(
+    files: &FileLoader,
+    cmd: &GenerateStructCmd,
+    pattern: &str,
+) -> Result<()> {
+    use glob::glob_with;
+    let entries = glob_with(pattern, MatchOptions::new()).unwrap();
+    for entry in entries.filter_map(Result::ok) {
+        let path = entry.as_path().into();
+        generate_struct_single(files, path, cmd)?;
+    }
+    Ok(())
+}
+
+fn generate_struct_single(
+    files: &FileLoader,
+    manifest_path: FilePath,
+    cmd: &GenerateStructCmd,
+) -> Result<()> {
+    let ir = load_feature_manifest(
+        files.clone(),
+        manifest_path,
+        cmd.load_from_ir,
+        Some(&cmd.channel),
+    )?;
+    generate_struct_from_ir(&ir, cmd)
+}
+
+fn generate_struct_from_ir(ir: &FeatureManifest, cmd: &GenerateStructCmd) -> Result<()> {
+    let language = &cmd.language;
+    ir.validate_manifest_for_lang(language)?;
+    match language {
+        TargetLanguage::IR => {
+            let contents = serde_json::to_string_pretty(&ir)?;
+            std::fs::write(&cmd.output, contents)?;
+        }
+        TargetLanguage::Kotlin => backends::kotlin::generate_struct(ir, cmd)?,
+        TargetLanguage::Swift => backends::swift::generate_struct(ir, cmd)?,
+        _ => unimplemented!(
+            "Unsupported output language for structs: {}",
+            language.extension()
+        ),
+    };
+    Ok(())
+}
+
+pub(crate) fn generate_experimenter_manifest(cmd: &GenerateExperimenterManifestCmd) -> Result<()> {
+    let files: FileLoader = TryFrom::try_from(&cmd.loader)?;
+    let path = files.file_path(&cmd.manifest)?;
+    let ir = load_feature_manifest(files, path, cmd.load_from_ir, None)?;
+    backends::experimenter_manifest::generate_manifest(ir, cmd)?;
+    Ok(())
+}
+
+pub(crate) fn generate_single_file_manifest(cmd: &GenerateSingleFileManifestCmd) -> Result<()> {
+    let files: FileLoader = TryFrom::try_from(&cmd.loader)?;
+    let path = files.file_path(&cmd.manifest)?;
+    let fm = load_feature_manifest(files, path, false, Some(&cmd.channel))?;
+    let frontend: ManifestFrontEnd = fm.into();
+    std::fs::write(&cmd.output, serde_yaml::to_string(&frontend)?)?;
+    Ok(())
+}
+
+fn load_feature_manifest(
+    files: FileLoader,
+    path: FilePath,
+    load_from_ir: bool,
+    channel: Option<&str>,
+) -> Result<FeatureManifest> {
+    let ir = if !load_from_ir {
+        let parser: Parser = Parser::new(files, path)?;
+        parser.get_intermediate_representation(channel)?
+    } else {
+        let string = files.read_to_string(&path)?;
+        serde_json::from_str::<FeatureManifest>(&string)?
+    };
+    ir.validate_manifest()?;
+    Ok(ir)
+}
+
+pub(crate) fn fetch_file(files: &LoaderConfig, nm: &str) -> Result<()> {
+    let files: FileLoader = files.try_into()?;
+    let file = files.file_path(nm)?;
+
+    let string = files.read_to_string(&file)?;
+
+    println!("{}", string);
+    Ok(())
+}
+
+fn output_ok(term: &Term, title: &str) -> Result<()> {
+    let style = term.style().green();
+    term.write_line(&format!("✅ {}", style.apply_to(title)))?;
+    Ok(())
+}
+
+fn output_note(term: &Term, title: &str) -> Result<()> {
+    let style = term.style().yellow();
+    term.write_line(&format!("ℹ️ {}", style.apply_to(title)))?;
+    Ok(())
+}
+
+fn output_warn(term: &Term, title: &str, detail: &str) -> Result<()> {
+    let style = term.style().yellow();
+    term.write_line(&format!("⚠️ {}: {detail}", style.apply_to(title)))?;
+    Ok(())
+}
+
+fn output_err(term: &Term, title: &str, detail: &str) -> Result<()> {
+    let style = term.style().red();
+    term.write_line(&format!("❎ {}: {detail}", style.apply_to(title),))?;
+    Ok(())
+}
+
+pub(crate) fn validate(cmd: &ValidateCmd) -> Result<()> {
+    let term = Term::stdout();
+
+    let files: FileLoader = TryFrom::try_from(&cmd.loader)?;
+
+    let filename = &cmd.manifest;
+    let file_path = files.file_path(filename)?;
+    let parser: Parser = Parser::new(files, file_path.clone())?;
+    let mut loading = HashSet::new();
+    let manifest_front_end = parser.load_manifest(&file_path, &mut loading)?;
+
+    let iter_includes = loading.iter().map(|id| id.to_string());
+
+    let channels = manifest_front_end.channels();
+    if channels.is_empty() {
+        output_note(
+            &term,
+            &format!(
+                "Loaded modules:\n- {}\n",
+                iter_includes.collect::<Vec<String>>().join("\n- ")
+            ),
+        )?;
+        output_ok(&term, &format!(
+            "{}\n{}\n{}",
+            "The manifest is valid for including in other files. To be imported, or used as an app manifest, it requires the following:",
+            "- A `channels` list",
+            "- An `about` block",
+        ))?;
+        return Ok(());
+    }
+    let intermediate_representation =
+        parser.get_intermediate_representation(None).map_err(|e| {
+            output_err(&term, "Manifest is invalid", &e.to_string()).unwrap();
+            e
+        })?;
+
+    output_note(
+        &term,
+        &format!(
+            "Loaded modules:\n- {}\n",
+            iter_includes
+                .chain(
+                    intermediate_representation
+                        .all_imports
+                        .keys()
+                        .map(|m| m.to_string())
+                )
+                .collect::<Vec<String>>()
+                .join("\n- ")
+        ),
+    )?;
+
+    term.write_line("Validating feature metadata:")?;
+    let mut features_with_warnings = 0;
+    for (_, f) in intermediate_representation.iter_all_feature_defs() {
+        let fm = &f.metadata;
+        let mut missing = vec![];
+        if fm.meta_bug.is_none() {
+            missing.push("'meta-bug'");
+        }
+        if fm.documentation.is_empty() {
+            missing.push("'documentation'");
+        }
+        if fm.contacts.is_empty() {
+            missing.push("'contacts'");
+        }
+        if !missing.is_empty() {
+            output_warn(
+                &term,
+                &format!("'{}' missing metadata", &f.name),
+                &missing.join(", "),
+            )?;
+            features_with_warnings += 1;
+        }
+    }
+
+    if features_with_warnings == 0 {
+        output_ok(&term, "All feature metadata ok\n")?;
+    } else {
+        let features = if features_with_warnings == 1 {
+            "feature"
+        } else {
+            "features"
+        };
+        term.write_line("Each feature should have entries for at least:")?;
+        term.write_line("  - meta-bug: a URL where to file bugs")?;
+        term.write_line("  - documentation: a list of one or more URLs documenting the feature")?;
+        term.write_line("      e.g. QA docs, user docs")?;
+        term.write_line("  - contacts: a list of one or more email addresses")?;
+        term.write_line("      (with Mozilla Jira accounts)")?;
+
+        term.write_line(&format!(
+            "Metadata warnings detected in {features_with_warnings} {features}\n"
+        ))?;
+    }
+
+    term.write_line("Validating manifest for different channels:")?;
+
+    let results = channels
+        .iter()
+        .map(|c| {
+            let intermediate_representation = parser.get_intermediate_representation(Some(c));
+            match intermediate_representation {
+                Ok(ir) => (c, ir.validate_manifest()),
+                Err(e) => (c, Err(e)),
+            }
+        })
+        .collect::<Vec<(&String, Result<_>)>>();
+
+    let mut error_count = 0;
+    for (channel, result) in results {
+        match result {
+            Ok(_) => {
+                output_ok(&term, &format!("{channel:.<20}valid"))?;
+            }
+            Err(e) => {
+                error_count += 1;
+                output_err(&term, &format!("{channel:.<20}invalid"), &e.to_string())?;
+            }
+        };
+    }
+
+    if error_count > 0 {
+        return Err(CliError(format!(
+            "Manifest contains error(s) in {} channel{}",
+            error_count,
+            if error_count > 1 { "s" } else { "" }
+        )));
+    }
+
+    Ok(())
+}
+
+pub(crate) fn print_channels(cmd: &PrintChannelsCmd) -> Result<()> {
+    let files = TryFrom::try_from(&cmd.loader)?;
+    let manifest = Parser::load_frontend(files, &cmd.manifest)?;
+    let channels = manifest.channels();
+    if cmd.as_json {
+        let json = serde_json::Value::from(channels);
+        println!("{}", json);
+    } else {
+        println!("{}", channels.join("\n"));
+    }
+    Ok(())
+}
+
+#[cfg(test)]
+mod test {
+    use std::fs;
+    use std::path::PathBuf;
+
+    use anyhow::anyhow;
+    use jsonschema::JSONSchema;
+
+    use super::*;
+    use crate::backends::experimenter_manifest::ExperimenterManifest;
+    use crate::backends::{kotlin, swift};
+    use crate::frontend::{AboutBlock, KotlinAboutBlock};
+    use crate::util::{generated_src_dir, join, pkg_dir};
+
+    const MANIFEST_PATHS: &[&str] = &[
+        "fixtures/ir/simple_nimbus_validation.json",
+        "fixtures/ir/simple_nimbus_validation.json",
+        "fixtures/ir/with_objects.json",
+        "fixtures/ir/full_homescreen.json",
+        "fixtures/fe/importing/simple/app.yaml",
+        "fixtures/fe/importing/diamond/00-app.yaml",
+    ];
+
+    fn generate_and_assert(
+        test_script: &str,
+        manifest: &str,
+        channel: &str,
+        is_ir: bool,
+    ) -> Result<()> {
+        let cmd = create_command_from_test(test_script, manifest, channel, is_ir)?;
+        generate_struct(&cmd)?;
+        run_script_with_generated_code(
+            &cmd.language,
+            &[cmd.output.as_path().display().to_string()],
+            test_script,
+        )?;
+        Ok(())
+    }
+
+    fn generate_struct_cli_overrides(from_cli: AboutBlock, cmd: &GenerateStructCmd) -> Result<()> {
+        let files: FileLoader = TryFrom::try_from(&cmd.loader)?;
+        let path = files.file_path(&cmd.manifest)?;
+        let mut ir = load_feature_manifest(files, path, cmd.load_from_ir, Some(&cmd.channel))?;
+
+        // We do a dance here to make sure that we can override class names and package names during tests,
+        // and while we still have to support setting those options from the commmand line.
+        // We will deprecate setting classnames, package names etc, then we can simplify.
+        let from_file = ir.about;
+        let from_cli = from_cli;
+        let kotlin_about = from_cli.kotlin_about.or(from_file.kotlin_about);
+        let swift_about = from_cli.swift_about.or(from_file.swift_about);
+        let about = AboutBlock {
+            kotlin_about,
+            swift_about,
+            ..Default::default()
+        };
+        ir.about = about;
+
+        generate_struct_from_ir(&ir, cmd)
+    }
+
+    // Given a manifest.fml and script.kts in the tests directory generate
+    // a manifest.kt and run the script against it.
+    fn generate_and_assert_with_config(
+        test_script: &str,
+        manifest: &str,
+        channel: &str,
+        is_ir: bool,
+        config_about: AboutBlock,
+    ) -> Result<()> {
+        let cmd = create_command_from_test(test_script, manifest, channel, is_ir)?;
+        generate_struct_cli_overrides(config_about, &cmd)?;
+        run_script_with_generated_code(
+            &cmd.language,
+            &[cmd.output.as_path().display().to_string()],
+            test_script,
+        )?;
+        Ok(())
+    }
+
+    fn create_command_from_test(
+        test_script: &str,
+        manifest: &str,
+        channel: &str,
+        is_ir: bool,
+    ) -> Result<GenerateStructCmd, crate::error::FMLError> {
+        let test_script = join(pkg_dir(), test_script);
+        let pbuf = PathBuf::from(&test_script);
+        let ext = pbuf
+            .extension()
+            .ok_or_else(|| anyhow!("Require a test_script with an extension: {}", test_script))?;
+        let language: TargetLanguage = ext.try_into()?;
+        let manifest_fml = join(pkg_dir(), manifest);
+        let file = PathBuf::from(&manifest_fml);
+        let file = file
+            .file_stem()
+            .ok_or_else(|| anyhow!("Manifest file path isn't a file"))?
+            .to_str()
+            .ok_or_else(|| anyhow!("Manifest file path isn't a file with a sensible name"))?;
+        fs::create_dir_all(generated_src_dir())?;
+        let manifest_out = format!(
+            "{}_{}.{}",
+            join(generated_src_dir(), file),
+            channel,
+            language.extension()
+        );
+        let loader = Default::default();
+        Ok(GenerateStructCmd {
+            manifest: manifest_fml,
+            output: manifest_out.into(),
+            load_from_ir: is_ir,
+            language,
+            channel: channel.into(),
+            loader,
+        })
+    }
+
+    fn generate_multiple_and_assert(test_script: &str, manifests: &[(&str, &str)]) -> Result<()> {
+        let cmds = manifests
+            .iter()
+            .map(|(manifest, channel)| {
+                let cmd = create_command_from_test(test_script, manifest, channel, false)?;
+                generate_struct(&cmd)?;
+                Ok(cmd)
+            })
+            .collect::<Result<Vec<_>>>()?;
+
+        let first = cmds
+            .first()
+            .expect("At least one manifests are always used");
+        let language = &first.language;
+
+        let manifests_out = cmds
+            .iter()
+            .map(|cmd| cmd.output.display().to_string())
+            .collect::<Vec<_>>();
+
+        run_script_with_generated_code(language, &manifests_out, test_script)?;
+        Ok(())
+    }
+
+    fn run_script_with_generated_code(
+        language: &TargetLanguage,
+        manifests_out: &[String],
+        test_script: &str,
+    ) -> Result<()> {
+        match language {
+            TargetLanguage::Kotlin => {
+                kotlin::test::run_script_with_generated_code(manifests_out, test_script)?
+            }
+            TargetLanguage::Swift => {
+                swift::test::run_script_with_generated_code(manifests_out, test_script.as_ref())?
+            }
+            _ => unimplemented!(),
+        }
+        Ok(())
+    }
+
+    #[test]
+    fn test_simple_validation_code_from_ir() -> Result<()> {
+        generate_and_assert(
+            "test/simple_nimbus_validation.kts",
+            "fixtures/ir/simple_nimbus_validation.json",
+            "release",
+            true,
+        )?;
+        Ok(())
+    }
+
+    #[test]
+    fn test_with_objects_code_from_ir() -> Result<()> {
+        generate_and_assert(
+            "test/with_objects.kts",
+            "fixtures/ir/with_objects.json",
+            "release",
+            true,
+        )?;
+        Ok(())
+    }
+
+    #[test]
+    fn test_with_full_homescreen_from_ir() -> Result<()> {
+        generate_and_assert(
+            "test/full_homescreen.kts",
+            "fixtures/ir/full_homescreen.json",
+            "release",
+            true,
+        )?;
+        Ok(())
+    }
+
+    #[test]
+    fn test_with_full_fenix_release() -> Result<()> {
+        generate_and_assert_with_config(
+            "test/fenix_release.kts",
+            "fixtures/fe/browser.yaml",
+            "release",
+            false,
+            AboutBlock {
+                kotlin_about: Some(KotlinAboutBlock {
+                    package: "com.example.app".to_string(),
+                    class: "com.example.release.FxNimbus".to_string(),
+                }),
+                swift_about: None,
+                ..Default::default()
+            },
+        )?;
+        Ok(())
+    }
+
+    #[test]
+    fn test_with_full_fenix_nightly() -> Result<()> {
+        generate_and_assert_with_config(
+            "test/fenix_nightly.kts",
+            "fixtures/fe/browser.yaml",
+            "nightly",
+            false,
+            AboutBlock {
+                kotlin_about: Some(KotlinAboutBlock {
+                    package: "com.example.app".to_string(),
+                    class: "com.example.nightly.FxNimbus".to_string(),
+                }),
+                swift_about: None,
+                ..Default::default()
+            },
+        )?;
+        Ok(())
+    }
+
+    #[test]
+    fn test_with_dx_improvements() -> Result<()> {
+        generate_and_assert(
+            "test/dx_improvements_testing.kts",
+            "fixtures/fe/dx_improvements.yaml",
+            "testing",
+            false,
+        )?;
+        Ok(())
+    }
+
+    #[test]
+    fn test_with_app_menu_from_ir() -> Result<()> {
+        generate_and_assert(
+            "test/app_menu.kts",
+            "fixtures/ir/app_menu.json",
+            "release",
+            true,
+        )?;
+        Ok(())
+    }
+
+    #[test]
+    fn test_with_app_menu_swift_from_ir() -> Result<()> {
+        generate_and_assert(
+            "test/app_menu.swift",
+            "fixtures/ir/app_menu.json",
+            "release",
+            true,
+        )?;
+        Ok(())
+    }
+
+    #[test]
+    fn test_with_objects_swift_from_ir() -> Result<()> {
+        generate_and_assert(
+            "test/with_objects.swift",
+            "fixtures/ir/with_objects.json",
+            "release",
+            true,
+        )?;
+        Ok(())
+    }
+
+    #[test]
+    fn test_with_bundled_resources_kotlin() -> Result<()> {
+        generate_and_assert(
+            "test/bundled_resources.kts",
+            "fixtures/fe/bundled_resouces.yaml",
+            "testing",
+            false,
+        )?;
+        Ok(())
+    }
+
+    #[test]
+    fn test_with_bundled_resources_swift() -> Result<()> {
+        generate_and_assert(
+            "test/bundled_resources.swift",
+            "fixtures/fe/bundled_resouces.yaml",
+            "testing",
+            false,
+        )?;
+        Ok(())
+    }
+
+    #[test]
+    fn test_with_full_fenix_release_swift() -> Result<()> {
+        generate_and_assert(
+            "test/fenix_release.swift",
+            "fixtures/fe/browser.yaml",
+            "release",
+            false,
+        )?;
+        Ok(())
+    }
+
+    #[test]
+    fn test_with_full_fenix_nightly_swift() -> Result<()> {
+        generate_and_assert(
+            "test/fenix_nightly.swift",
+            "fixtures/fe/browser.yaml",
+            "nightly",
+            false,
+        )?;
+        Ok(())
+    }
+
+    #[test]
+    fn test_with_full_firefox_ios() -> Result<()> {
+        generate_and_assert(
+            "test/firefox_ios_release.swift",
+            "fixtures/fe/including/ios.yaml",
+            "release",
+            false,
+        )?;
+        Ok(())
+    }
+
+    #[test]
+    fn test_importing_simple_ios() -> Result<()> {
+        generate_multiple_and_assert(
+            "test/importing/simple/app_debug.swift",
+            &[
+                ("fixtures/fe/importing/simple/app.yaml", "debug"),
+                ("fixtures/fe/importing/simple/lib.yaml", "debug"),
+            ],
+        )?;
+        Ok(())
+    }
+
+    #[test]
+    fn test_importing_simple_android() -> Result<()> {
+        generate_multiple_and_assert(
+            "test/importing/simple/app_debug.kts",
+            &[
+                ("fixtures/fe/importing/simple/lib.yaml", "debug"),
+                ("fixtures/fe/importing/simple/app.yaml", "debug"),
+            ],
+        )?;
+        Ok(())
+    }
+
+    #[test]
+    fn test_importing_channel_mismatching_android() -> Result<()> {
+        generate_multiple_and_assert(
+            "test/importing/channels/app_debug.kts",
+            &[
+                ("fixtures/fe/importing/channels/app.fml.yaml", "app-debug"),
+                ("fixtures/fe/importing/channels/lib.fml.yaml", "debug"),
+            ],
+        )?;
+        Ok(())
+    }
+
+    #[test]
+    fn test_importing_override_defaults_android() -> Result<()> {
+        generate_multiple_and_assert(
+            "test/importing/overrides/app_debug.kts",
+            &[
+                ("fixtures/fe/importing/overrides/app.fml.yaml", "debug"),
+                ("fixtures/fe/importing/overrides/lib.fml.yaml", "debug"),
+            ],
+        )?;
+        Ok(())
+    }
+
+    #[test]
+    fn test_importing_override_defaults_ios() -> Result<()> {
+        generate_multiple_and_assert(
+            "test/importing/overrides/app_debug.swift",
+            &[
+                ("fixtures/fe/importing/overrides/app.fml.yaml", "debug"),
+                ("fixtures/fe/importing/overrides/lib.fml.yaml", "debug"),
+            ],
+        )?;
+        Ok(())
+    }
+
+    #[test]
+    fn test_importing_override_defaults_coverall_android() -> Result<()> {
+        generate_multiple_and_assert(
+            "test/importing/overrides-coverall/app_debug.kts",
+            &[
+                (
+                    "fixtures/fe/importing/overrides-coverall/app.fml.yaml",
+                    "debug",
+                ),
+                (
+                    "fixtures/fe/importing/overrides-coverall/lib.fml.yaml",
+                    "debug",
+                ),
+            ],
+        )?;
+        Ok(())
+    }
+
+    #[test]
+    fn test_importing_diamond_overrides_android() -> Result<()> {
+        // In this test, sublib implements a feature.
+        // Both lib and app offer some configuration, and both app and lib
+        // need to import sublib.
+        generate_multiple_and_assert(
+            "test/importing/diamond/00-app.kts",
+            &[
+                ("fixtures/fe/importing/diamond/00-app.yaml", "debug"),
+                ("fixtures/fe/importing/diamond/01-lib.yaml", "debug"),
+                ("fixtures/fe/importing/diamond/02-sublib.yaml", "debug"),
+            ],
+        )?;
+        Ok(())
+    }
+
+    #[test]
+    fn test_importing_diamond_overrides_ios() -> Result<()> {
+        // In this test, sublib implements a feature.
+        // Both lib and app offer some configuration, and both app and lib
+        // need to import sublib.
+        generate_multiple_and_assert(
+            "test/importing/diamond/00-app.swift",
+            &[
+                ("fixtures/fe/importing/diamond/00-app.yaml", "debug"),
+                ("fixtures/fe/importing/diamond/01-lib.yaml", "debug"),
+                ("fixtures/fe/importing/diamond/02-sublib.yaml", "debug"),
+            ],
+        )?;
+        Ok(())
+    }
+
+    #[test]
+    #[ignore]
+    fn test_importing_reexporting_features() -> Result<()> {
+        // In this test, sublib implements a feature.
+        // Both lib and app offer some configuration, but app doesn't need to know
+        // that the feature is provided by sublib– where the feature lives
+        // is an implementation detail, and should be encapsulated by lib.
+        // This is currently not possible, but filed as EXP-2540.
+        generate_multiple_and_assert(
+            "test/importing/reexporting/00-app.kts",
+            &[
+                ("fixtures/fe/importing/reexporting/00-app.yaml", "debug"),
+                ("fixtures/fe/importing/reexporting/01-lib.yaml", "debug"),
+                ("fixtures/fe/importing/reexporting/02-sublib.yaml", "debug"),
+            ],
+        )?;
+        Ok(())
+    }
+
+    #[test]
+    fn test_importing_including_imports_android() -> Result<()> {
+        generate_multiple_and_assert(
+            "test/importing/including-imports/app_release.kts",
+            &[
+                (
+                    "fixtures/fe/importing/including-imports/ui.fml.yaml",
+                    "none",
+                ),
+                (
+                    "fixtures/fe/importing/including-imports/app.fml.yaml",
+                    "release",
+                ),
+            ],
+        )?;
+        Ok(())
+    }
+
+    #[test]
+    fn test_importing_including_imports_ios() -> Result<()> {
+        generate_multiple_and_assert(
+            "test/importing/including-imports/app_release.swift",
+            &[
+                (
+                    "fixtures/fe/importing/including-imports/ui.fml.yaml",
+                    "none",
+                ),
+                (
+                    "fixtures/fe/importing/including-imports/app.fml.yaml",
+                    "release",
+                ),
+            ],
+        )?;
+        Ok(())
+    }
+
+    #[test]
+    fn test_importing_simple_experimenter_manifest() -> Result<()> {
+        // Both the app and lib files declare features, so we should have an experimenter manifest file with two features.
+        let cmd = create_experimenter_manifest_cmd("fixtures/fe/importing/simple/app.yaml")?;
+        let files = FileLoader::default()?;
+        let path = files.file_path(&cmd.manifest)?;
+        let fm = load_feature_manifest(files, path, cmd.load_from_ir, None)?;
+        let m: ExperimenterManifest = fm.try_into()?;
+
+        assert!(m.contains_key("homescreen"));
+        assert!(m.contains_key("search"));
+
+        Ok(())
+    }
+
+    #[test]
+    fn regression_test_concurrent_access_of_feature_holder_swift() -> Result<()> {
+        generate_and_assert(
+            "test/threadsafe_feature_holder.swift",
+            "fixtures/fe/browser.yaml",
+            "release",
+            false,
+        )?;
+        Ok(())
+    }
+
+    #[test]
+    fn regression_test_concurrent_access_of_feature_holder_kts() -> Result<()> {
+        generate_and_assert(
+            "test/threadsafe_feature_holder.kts",
+            "fixtures/fe/browser.yaml",
+            "release",
+            false,
+        )?;
+        Ok(())
+    }
+
+    fn validate_against_experimenter_schema<P: AsRef<Path>>(
+        schema_path: P,
+        generated_yaml: &serde_yaml::Value,
+    ) -> Result<()> {
+        let generated_manifest: ExperimenterManifest =
+            serde_yaml::from_value(generated_yaml.to_owned())?;
+        let generated_json = serde_json::to_value(generated_manifest)?;
+
+        let schema = fs::read_to_string(&schema_path)?;
+        let schema: serde_json::Value = serde_json::from_str(&schema)?;
+        let compiled = JSONSchema::compile(&schema).expect("The schema is invalid");
+        let res = compiled.validate(&generated_json);
+        if let Err(e) = res {
+            panic!(
+                "Validation errors: \n{}",
+                e.map(|e| e.to_string()).collect::<Vec<String>>().join("\n")
+            );
+        }
+        Ok(())
+    }
+
+    #[test]
+    fn test_schema_validation() -> Result<()> {
+        for path in MANIFEST_PATHS {
+            let cmd = create_experimenter_manifest_cmd(path)?;
+            generate_experimenter_manifest(&cmd)?;
+
+            let generated = fs::read_to_string(&cmd.output)?;
+            let generated_yaml = serde_yaml::from_str(&generated)?;
+            validate_against_experimenter_schema(
+                join(pkg_dir(), "ExperimentFeatureManifest.schema.json"),
+                &generated_yaml,
+            )?;
+        }
+        Ok(())
+    }
+
+    #[test]
+    fn test_validate_command() -> Result<()> {
+        let paths = MANIFEST_PATHS
+            .iter()
+            .filter(|p| p.ends_with(".yaml"))
+            .chain([&"fixtures/fe/no_about_no_channels.yaml"])
+            .collect::<Vec<&&str>>();
+        for path in paths {
+            let manifest = join(pkg_dir(), path);
+            let cmd = ValidateCmd {
+                loader: Default::default(),
+                manifest,
+            };
+            validate(&cmd)?;
+        }
+        Ok(())
+    }
+
+    #[test]
+    fn test_validate_command_fails_on_bad_default_value_for_one_channel() -> Result<()> {
+        let path = "fixtures/fe/invalid/invalid_default_value_for_one_channel.fml.yaml";
+        let manifest = join(pkg_dir(), path);
+        let cmd = ValidateCmd {
+            loader: Default::default(),
+            manifest,
+        };
+        let result = validate(&cmd);
+
+        assert!(result.is_err());
+
+        match result.err().unwrap() {
+            CliError(error) => {
+                assert_eq!(error, "Manifest contains error(s) in 1 channel");
+            }
+            _ => panic!("Error is not a ValidationError"),
+        };
+
+        Ok(())
+    }
+
+    fn create_experimenter_manifest_cmd(path: &str) -> Result<GenerateExperimenterManifestCmd> {
+        let manifest = join(pkg_dir(), path);
+        let file = Path::new(&manifest);
+        let filestem = file
+            .file_stem()
+            .ok_or_else(|| anyhow!("Manifest file path isn't a file"))?
+            .to_str()
+            .ok_or_else(|| anyhow!("Manifest file path isn't a file with a sensible name"))?;
+
+        fs::create_dir_all(generated_src_dir())?;
+
+        let output = join(generated_src_dir(), &format!("{filestem}.yaml")).into();
+        let load_from_ir = if let Some(ext) = file.extension() {
+            TargetLanguage::ExperimenterJSON == ext.try_into()?
+        } else {
+            false
+        };
+        let loader = Default::default();
+        Ok(GenerateExperimenterManifestCmd {
+            manifest,
+            output,
+            language: TargetLanguage::ExperimenterYAML,
+            load_from_ir,
+            loader,
+        })
+    }
+
+    fn test_single_merged_manifest_file(path: &str, channel: &str) -> Result<()> {
+        let manifest = join(pkg_dir(), path);
+        let file = Path::new(&manifest);
+        let filestem = file
+            .file_stem()
+            .ok_or_else(|| anyhow!("Manifest file path isn't a file"))?
+            .to_str()
+            .ok_or_else(|| anyhow!("Manifest file path isn't a file with a sensible name"))?;
+
+        fs::create_dir_all(generated_src_dir())?;
+
+        let output: PathBuf =
+            join(generated_src_dir(), &format!("single-file-{filestem}.yaml")).into();
+        let loader = Default::default();
+
+        // Load the source file, and get the default_json()
+        let files: FileLoader = TryFrom::try_from(&loader)?;
+        let src = files.file_path(&manifest)?;
+        let fm = load_feature_manifest(files, src, false, Some(channel))?;
+        let expected = fm.default_json();
+
+        // Generate the merged file
+        let cmd = GenerateSingleFileManifestCmd {
+            loader: Default::default(),
+            manifest,
+            output: output.clone(),
+            channel: channel.to_string(),
+        };
+        generate_single_file_manifest(&cmd)?;
+
+        // Reload the generated file, and get the default_json()
+        let dest = FilePath::Local(output);
+        let files: FileLoader = TryFrom::try_from(&loader)?;
+        let fm = load_feature_manifest(files, dest, false, Some(channel))?;
+        let observed = fm.default_json();
+
+        // They should be the same.
+        assert_eq!(expected, observed);
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_single_file_command() -> Result<()> {
+        test_single_merged_manifest_file("fixtures/fe/browser.yaml", "release")?;
+        test_single_merged_manifest_file(
+            "fixtures/fe/importing/including-imports/ui.fml.yaml",
+            "none",
+        )?;
+        test_single_merged_manifest_file(
+            "fixtures/fe/importing/including-imports/app.fml.yaml",
+            "release",
+        )?;
+        test_single_merged_manifest_file("fixtures/fe/importing/overrides/app.fml.yaml", "debug")?;
+        test_single_merged_manifest_file("fixtures/fe/importing/overrides/lib.fml.yaml", "debug")?;
+        test_single_merged_manifest_file("fixtures/fe/importing/diamond/00-app.yaml", "debug")?;
+        test_single_merged_manifest_file("fixtures/fe/importing/diamond/01-lib.yaml", "debug")?;
+        test_single_merged_manifest_file("fixtures/fe/importing/diamond/02-sublib.yaml", "debug")?;
+
+        test_single_merged_manifest_file("fixtures/fe/misc-features.yaml", "debug")?;
+        Ok(())
+    }
+
+    #[test]
+    fn test_with_coenrolled_features_and_imports_kotlin() -> Result<()> {
+        generate_multiple_and_assert(
+            "test/allow_coenrolling.kts",
+            &[
+                ("fixtures/fe/importing/coenrolling/app.fml.yaml", "release"),
+                ("fixtures/fe/importing/coenrolling/ui.fml.yaml", "release"),
+            ],
+        )?;
+        Ok(())
+    }
+
+    #[test]
+    fn test_with_coenrolled_features_and_imports_swift() -> Result<()> {
+        generate_multiple_and_assert(
+            "test/allow_coenrolling.swift",
+            &[
+                ("fixtures/fe/importing/coenrolling/app.fml.yaml", "release"),
+                ("fixtures/fe/importing/coenrolling/ui.fml.yaml", "release"),
+            ],
+        )?;
+        Ok(())
+    }
+
+    #[test]
+    fn test_with_preference_overrides_kt() -> Result<()> {
+        generate_multiple_and_assert(
+            "test/pref_overrides.kts",
+            &[("fixtures/fe/pref_overrides.fml.yaml", "debug")],
+        )?;
+        Ok(())
+    }
+
+    #[test]
+    fn test_with_preference_overrides_swift() -> Result<()> {
+        generate_multiple_and_assert(
+            "test/pref_overrides.swift",
+            &[("fixtures/fe/pref_overrides.fml.yaml", "debug")],
+        )?;
+        Ok(())
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus_fml/defaults_merger.rs.html b/book/rust-docs/src/nimbus_fml/defaults_merger.rs.html new file mode 100644 index 0000000000..d8c91507c7 --- /dev/null +++ b/book/rust-docs/src/nimbus_fml/defaults_merger.rs.html @@ -0,0 +1,2531 @@ +defaults_merger.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
+540
+541
+542
+543
+544
+545
+546
+547
+548
+549
+550
+551
+552
+553
+554
+555
+556
+557
+558
+559
+560
+561
+562
+563
+564
+565
+566
+567
+568
+569
+570
+571
+572
+573
+574
+575
+576
+577
+578
+579
+580
+581
+582
+583
+584
+585
+586
+587
+588
+589
+590
+591
+592
+593
+594
+595
+596
+597
+598
+599
+600
+601
+602
+603
+604
+605
+606
+607
+608
+609
+610
+611
+612
+613
+614
+615
+616
+617
+618
+619
+620
+621
+622
+623
+624
+625
+626
+627
+628
+629
+630
+631
+632
+633
+634
+635
+636
+637
+638
+639
+640
+641
+642
+643
+644
+645
+646
+647
+648
+649
+650
+651
+652
+653
+654
+655
+656
+657
+658
+659
+660
+661
+662
+663
+664
+665
+666
+667
+668
+669
+670
+671
+672
+673
+674
+675
+676
+677
+678
+679
+680
+681
+682
+683
+684
+685
+686
+687
+688
+689
+690
+691
+692
+693
+694
+695
+696
+697
+698
+699
+700
+701
+702
+703
+704
+705
+706
+707
+708
+709
+710
+711
+712
+713
+714
+715
+716
+717
+718
+719
+720
+721
+722
+723
+724
+725
+726
+727
+728
+729
+730
+731
+732
+733
+734
+735
+736
+737
+738
+739
+740
+741
+742
+743
+744
+745
+746
+747
+748
+749
+750
+751
+752
+753
+754
+755
+756
+757
+758
+759
+760
+761
+762
+763
+764
+765
+766
+767
+768
+769
+770
+771
+772
+773
+774
+775
+776
+777
+778
+779
+780
+781
+782
+783
+784
+785
+786
+787
+788
+789
+790
+791
+792
+793
+794
+795
+796
+797
+798
+799
+800
+801
+802
+803
+804
+805
+806
+807
+808
+809
+810
+811
+812
+813
+814
+815
+816
+817
+818
+819
+820
+821
+822
+823
+824
+825
+826
+827
+828
+829
+830
+831
+832
+833
+834
+835
+836
+837
+838
+839
+840
+841
+842
+843
+844
+845
+846
+847
+848
+849
+850
+851
+852
+853
+854
+855
+856
+857
+858
+859
+860
+861
+862
+863
+864
+865
+866
+867
+868
+869
+870
+871
+872
+873
+874
+875
+876
+877
+878
+879
+880
+881
+882
+883
+884
+885
+886
+887
+888
+889
+890
+891
+892
+893
+894
+895
+896
+897
+898
+899
+900
+901
+902
+903
+904
+905
+906
+907
+908
+909
+910
+911
+912
+913
+914
+915
+916
+917
+918
+919
+920
+921
+922
+923
+924
+925
+926
+927
+928
+929
+930
+931
+932
+933
+934
+935
+936
+937
+938
+939
+940
+941
+942
+943
+944
+945
+946
+947
+948
+949
+950
+951
+952
+953
+954
+955
+956
+957
+958
+959
+960
+961
+962
+963
+964
+965
+966
+967
+968
+969
+970
+971
+972
+973
+974
+975
+976
+977
+978
+979
+980
+981
+982
+983
+984
+985
+986
+987
+988
+989
+990
+991
+992
+993
+994
+995
+996
+997
+998
+999
+1000
+1001
+1002
+1003
+1004
+1005
+1006
+1007
+1008
+1009
+1010
+1011
+1012
+1013
+1014
+1015
+1016
+1017
+1018
+1019
+1020
+1021
+1022
+1023
+1024
+1025
+1026
+1027
+1028
+1029
+1030
+1031
+1032
+1033
+1034
+1035
+1036
+1037
+1038
+1039
+1040
+1041
+1042
+1043
+1044
+1045
+1046
+1047
+1048
+1049
+1050
+1051
+1052
+1053
+1054
+1055
+1056
+1057
+1058
+1059
+1060
+1061
+1062
+1063
+1064
+1065
+1066
+1067
+1068
+1069
+1070
+1071
+1072
+1073
+1074
+1075
+1076
+1077
+1078
+1079
+1080
+1081
+1082
+1083
+1084
+1085
+1086
+1087
+1088
+1089
+1090
+1091
+1092
+1093
+1094
+1095
+1096
+1097
+1098
+1099
+1100
+1101
+1102
+1103
+1104
+1105
+1106
+1107
+1108
+1109
+1110
+1111
+1112
+1113
+1114
+1115
+1116
+1117
+1118
+1119
+1120
+1121
+1122
+1123
+1124
+1125
+1126
+1127
+1128
+1129
+1130
+1131
+1132
+1133
+1134
+1135
+1136
+1137
+1138
+1139
+1140
+1141
+1142
+1143
+1144
+1145
+1146
+1147
+1148
+1149
+1150
+1151
+1152
+1153
+1154
+1155
+1156
+1157
+1158
+1159
+1160
+1161
+1162
+1163
+1164
+1165
+1166
+1167
+1168
+1169
+1170
+1171
+1172
+1173
+1174
+1175
+1176
+1177
+1178
+1179
+1180
+1181
+1182
+1183
+1184
+1185
+1186
+1187
+1188
+1189
+1190
+1191
+1192
+1193
+1194
+1195
+1196
+1197
+1198
+1199
+1200
+1201
+1202
+1203
+1204
+1205
+1206
+1207
+1208
+1209
+1210
+1211
+1212
+1213
+1214
+1215
+1216
+1217
+1218
+1219
+1220
+1221
+1222
+1223
+1224
+1225
+1226
+1227
+1228
+1229
+1230
+1231
+1232
+1233
+1234
+1235
+1236
+1237
+1238
+1239
+1240
+1241
+1242
+1243
+1244
+1245
+1246
+1247
+1248
+1249
+1250
+1251
+1252
+1253
+1254
+1255
+1256
+1257
+1258
+1259
+1260
+1261
+1262
+1263
+1264
+1265
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use std::collections::{BTreeMap, HashMap};
+
+use serde_json::json;
+
+use crate::{
+    error::{did_you_mean, FMLError, Result},
+    frontend::DefaultBlock,
+    intermediate_representation::{FeatureDef, ObjectDef, PropDef, TypeRef},
+};
+
+pub struct DefaultsMerger<'object> {
+    objects: &'object BTreeMap<String, ObjectDef>,
+
+    supported_channels: Vec<String>,
+    channel: Option<String>,
+}
+
+impl<'object> DefaultsMerger<'object> {
+    pub fn new(
+        objects: &'object BTreeMap<String, ObjectDef>,
+        supported_channels: Vec<String>,
+        channel: Option<String>,
+    ) -> Self {
+        Self {
+            objects,
+            supported_channels,
+            channel,
+        }
+    }
+
+    #[cfg(test)]
+    pub fn new_with_channel(
+        objects: &'object BTreeMap<String, ObjectDef>,
+        supported_channels: Vec<String>,
+        channel: String,
+    ) -> Self {
+        Self::new(objects, supported_channels, Some(channel.to_string()))
+    }
+
+    fn collect_feature_defaults(&self, feature: &FeatureDef) -> Result<serde_json::Value> {
+        let mut res = serde_json::value::Map::new();
+
+        for p in feature.props() {
+            let collected = self
+                .collect_prop_defaults(&p.typ, &p.default)?
+                .unwrap_or_else(|| p.default());
+            res.insert(p.name(), collected);
+        }
+
+        Ok(serde_json::to_value(res)?)
+    }
+
+    fn collect_object_defaults(&self, nm: &str) -> Result<serde_json::Value> {
+        if !self.objects.contains_key(nm) {
+            return Err(FMLError::ValidationError(
+                format!("objects/{}", nm),
+                format!("Object named {} is not defined", nm),
+            ));
+        }
+
+        let obj = self.objects.get(nm).unwrap();
+        let mut res = serde_json::value::Map::new();
+
+        for p in obj.props() {
+            if let Some(collected) = self.collect_prop_defaults(&p.typ, &p.default)? {
+                res.insert(p.name(), collected);
+            }
+        }
+
+        Ok(serde_json::to_value(res)?)
+    }
+
+    fn collect_prop_defaults(
+        &self,
+        typ: &TypeRef,
+        v: &serde_json::Value,
+    ) -> Result<Option<serde_json::Value>> {
+        Ok(match typ {
+            TypeRef::Object(nm) => Some(merge_two_defaults(&self.collect_object_defaults(nm)?, v)),
+            TypeRef::EnumMap(_, v_type) => Some(self.collect_map_defaults(v_type, v)?),
+            TypeRef::StringMap(v_type) => Some(self.collect_map_defaults(v_type, v)?),
+            _ => None,
+        })
+    }
+
+    fn collect_map_defaults(
+        &self,
+        v_type: &TypeRef,
+        obj: &serde_json::Value,
+    ) -> Result<serde_json::Value> {
+        let map = obj
+            .as_object()
+            .unwrap_or_else(|| panic!("Expected a JSON object as a default"));
+        let mut res = serde_json::value::Map::new();
+        for (k, v) in map {
+            let collected = self
+                .collect_prop_defaults(v_type, v)?
+                .unwrap_or_else(|| v.clone());
+            res.insert(k.clone(), collected);
+        }
+        Ok(serde_json::to_value(res)?)
+    }
+
+    /// Transforms a feature definition with unmerged defaults into a feature
+    /// definition with its defaults merged.
+    ///
+    /// # How the algorithm works:
+    /// There are two types of defaults:
+    /// 1. Field level defaults
+    /// 1. Feature level defaults, that are listed by channel
+    ///
+    /// The algorithm gathers the field level defaults first, they are the base
+    /// defaults. Then, it gathers the feature level defaults and merges them by
+    /// calling [`collect_channel_defaults`]. Finally, it overwrites any common
+    /// defaults between the merged feature level defaults and the field level defaults
+    ///
+    /// # Example:
+    /// Assume we have the following feature manifest
+    /// ```yaml
+    ///  variables:
+    ///   positive:
+    ///   description: This is a positive button
+    ///   type: Button
+    ///   default:
+    ///     {
+    ///       "label": "Ok then",
+    ///       "color": "blue"
+    ///     }
+    ///  default:
+    ///      - channel: release
+    ///      value: {
+    ///        "positive": {
+    ///          "color": "green"
+    ///        }
+    ///      }
+    ///      - value: {
+    ///      "positive": {
+    ///        "alt-text": "Go Ahead!"
+    ///      }
+    /// }
+    /// ```
+    ///
+    /// The result of the algorithm would be a default that looks like:
+    /// ```yaml
+    /// variables:
+    ///     positive:
+    ///     default:
+    ///     {
+    ///         "label": "Ok then",
+    ///         "color": "green",
+    ///         "alt-text": "Go Ahead!"
+    ///     }
+    ///
+    /// ```
+    ///
+    /// - The `label` comes from the original field level default
+    /// - The `color` comes from the `release` channel feature level default
+    /// - The `alt-text` comes from the feature level default with no channel (that applies to all channels)
+    ///
+    /// # Arguments
+    /// - `feature_def`: a [`FeatureDef`] representing the feature definition to transform
+    /// - `channel`: a [`Option<&String>`] representing the channel to merge back into the field variables
+    /// - `supported_channels`: a [`&[String]`] representing the channels that are supported by the manifest
+    /// If the `channel` is `None` we default to using the `release` channel
+    ///
+    /// # Returns
+    /// Returns a transformed [`FeatureDef`] with its defaults merged
+    pub fn merge_feature_defaults(
+        &self,
+        feature_def: &mut FeatureDef,
+        defaults: &Option<Vec<DefaultBlock>>,
+    ) -> Result<(), FMLError> {
+        let supported_channels = self.supported_channels.as_slice();
+        let channel = &self.channel;
+        if let Some(channel) = channel {
+            if !supported_channels.iter().any(|c| c == channel) {
+                return Err(FMLError::InvalidChannelError(
+                    channel.into(),
+                    supported_channels.into(),
+                ));
+            }
+        }
+        let variable_defaults = self.collect_feature_defaults(feature_def)?;
+        let res = feature_def;
+
+        if let Some(defaults) = defaults {
+            // No channel is represented by an unlikely string.
+            let no_channel = "NO CHANNEL SPECIFIED".to_string();
+            let merged_defaults =
+                collect_channel_defaults(defaults, supported_channels, &no_channel)?;
+            let channel = self.channel.as_ref().unwrap_or(&no_channel);
+            if let Some(default_to_merged) = merged_defaults.get(channel) {
+                let merged = merge_two_defaults(&variable_defaults, default_to_merged);
+                let map = merged.as_object().ok_or(FMLError::InternalError(
+                    "Map was merged into a different type",
+                ))?;
+
+                res.props = map
+                    .iter()
+                    .map(|(k, v)| -> Result<PropDef> {
+                        if let Some(prop) = res.props.iter().find(|p| &p.name == k) {
+                            let mut res = prop.clone();
+                            res.default = v.clone();
+                            Ok(res)
+                        } else {
+                            let valid = res.props.iter().map(|p| p.name()).collect();
+                            Err(FMLError::FeatureValidationError {
+                                literals: vec![format!("\"{k}\"")],
+                                path: format!("features/{}", res.name),
+                                message: format!("Invalid property \"{k}\"{}", did_you_mean(valid)),
+                            })
+                        }
+                    })
+                    .collect::<Result<Vec<_>>>()?;
+            }
+        }
+        Ok(())
+    }
+}
+
+/// Merges two [`serde_json::Value`]s into one
+///
+/// # Arguments:
+/// - `old_default`: a reference to a [`serde_json::Value`], that represents the old default
+/// - `new_default`: a reference to a [`serde_json::Value`], that represents the new default, this takes
+///     precedence over the `old_default` if they have conflicting fields
+///
+/// # Returns
+/// A merged [`serde_json::Value`] that contains all fields from `old_default` and `new_default`, merging
+/// where there is a conflict. If the `old_default` and `new_default` are not both objects, this function
+/// returns the `new_default`
+fn merge_two_defaults(
+    old_default: &serde_json::Value,
+    new_default: &serde_json::Value,
+) -> serde_json::Value {
+    use serde_json::Value::Object;
+    match (old_default.clone(), new_default.clone()) {
+        (Object(old), Object(new)) => {
+            let mut merged = serde_json::Map::new();
+            for (key, val) in old {
+                merged.insert(key, val);
+            }
+            for (key, val) in new {
+                if let Some(old_val) = merged.get(&key).cloned() {
+                    merged.insert(key, merge_two_defaults(&old_val, &val));
+                } else {
+                    merged.insert(key, val);
+                }
+            }
+            Object(merged)
+        }
+        (_, new) => new,
+    }
+}
+
+/// Collects the channel defaults of the feature manifest
+/// and merges them by channel
+///
+/// **NOTE**: defaults with no channel apply to **all** channels
+///
+/// # Arguments
+/// - `defaults`: a [`serde_json::Value`] representing the array of defaults
+///
+/// # Returns
+/// Returns a [`std::collections::HashMap<String, serde_json::Value>`] representing
+/// the merged defaults. The key is the name of the channel and the value is the
+/// merged json.
+///
+/// # Errors
+/// Will return errors in the following cases (not exhaustive):
+/// - The `defaults` argument is not an array
+/// - There is a `channel` in the `defaults` argument that doesn't
+///     exist in the `channels` argument
+fn collect_channel_defaults(
+    defaults: &[DefaultBlock],
+    channels: &[String],
+    no_channel: &str,
+) -> Result<HashMap<String, serde_json::Value>> {
+    // We initialize the map to have an entry for every valid channel
+    let mut channel_map = channels
+        .iter()
+        .map(|channel_name| (channel_name.clone(), json!({})))
+        .collect::<HashMap<_, _>>();
+    channel_map.insert(no_channel.to_string(), json!({}));
+    for default in defaults {
+        if let Some(channels_for_default) = &default.merge_channels() {
+            for channel in channels_for_default {
+                if let Some(old_default) = channel_map.get(channel).cloned() {
+                    if default.targeting.is_none() {
+                        // TODO: we currently ignore any defaults with targeting involved
+                        let merged = merge_two_defaults(&old_default, &default.value);
+                        channel_map.insert(channel.clone(), merged);
+                    }
+                } else {
+                    return Err(FMLError::InvalidChannelError(
+                        channel.into(),
+                        channels.into(),
+                    ));
+                }
+            }
+        // This is a default with no channel, so it applies to all channels
+        } else {
+            channel_map = channel_map
+                .into_iter()
+                .map(|(channel, old_default)| {
+                    (channel, merge_two_defaults(&old_default, &default.value))
+                })
+                .collect();
+        }
+    }
+    Ok(channel_map)
+}
+
+#[cfg(test)]
+mod unit_tests {
+    use super::*;
+    use serde_json::json;
+
+    #[test]
+    fn test_merge_two_defaults_both_objects_no_intersection() -> Result<()> {
+        let old_default = json!({
+            "button-color": "blue",
+            "dialog_option": "greetings",
+            "is_enabled": false,
+            "num_items": 5
+        });
+        let new_default = json!({
+            "new_homepage": true,
+            "item_order": ["first", "second", "third"],
+        });
+        let merged = merge_two_defaults(&old_default, &new_default);
+        assert_eq!(
+            json!({
+                "button-color": "blue",
+                "dialog_option": "greetings",
+                "is_enabled": false,
+                "num_items": 5,
+                "new_homepage": true,
+                "item_order": ["first", "second", "third"],
+            }),
+            merged
+        );
+        Ok(())
+    }
+
+    #[test]
+    fn test_merge_two_defaults_intersecting_different_types() -> Result<()> {
+        // if there is an intersection, but they are different types, we just take the new one
+        let old_default = json!({
+            "button-color": "blue",
+            "dialog_option": "greetings",
+            "is_enabled": {
+                "value": false
+            },
+            "num_items": 5
+        });
+        let new_default = json!({
+            "new_homepage": true,
+            "is_enabled": true,
+            "item_order": ["first", "second", "third"],
+        });
+        let merged = merge_two_defaults(&old_default, &new_default);
+        assert_eq!(
+            json!({
+                "button-color": "blue",
+                "dialog_option": "greetings",
+                "is_enabled": true,
+                "num_items": 5,
+                "new_homepage": true,
+                "item_order": ["first", "second", "third"],
+            }),
+            merged
+        );
+        Ok(())
+    }
+
+    #[test]
+    fn test_merge_two_defaults_non_map_intersection() -> Result<()> {
+        // if they intersect on both key and type, but the type intersected is not an object, we just take the new one
+        let old_default = json!({
+            "button-color": "blue",
+            "dialog_option": "greetings",
+            "is_enabled": false,
+            "num_items": 5
+        });
+        let new_default = json!({
+            "button-color": "green",
+            "new_homepage": true,
+            "is_enabled": true,
+            "num_items": 10,
+            "item_order": ["first", "second", "third"],
+        });
+        let merged = merge_two_defaults(&old_default, &new_default);
+        assert_eq!(
+            json!({
+                "button-color": "green",
+                "dialog_option": "greetings",
+                "is_enabled": true,
+                "num_items": 10,
+                "new_homepage": true,
+                "item_order": ["first", "second", "third"],
+            }),
+            merged
+        );
+        Ok(())
+    }
+
+    #[test]
+    fn test_merge_two_defaults_map_intersection_recursive_merge() -> Result<()> {
+        // if they intersect on both key and type, but the type intersected is not an object, we just take the new one
+        let old_default = json!({
+            "button-color": "blue",
+            "dialog_item": {
+                "title": "hello",
+                "message": "bobo",
+                "priority": 10,
+            },
+            "is_enabled": false,
+            "num_items": 5
+        });
+        let new_default = json!({
+            "button-color": "green",
+            "new_homepage": true,
+            "is_enabled": true,
+            "dialog_item": {
+                "message": "fofo",
+                "priority": 11,
+                "subtitle": "hey there"
+            },
+            "num_items": 10,
+            "item_order": ["first", "second", "third"],
+        });
+        let merged = merge_two_defaults(&old_default, &new_default);
+        assert_eq!(
+            json!({
+                "button-color": "green",
+                "dialog_item": {
+                    "title": "hello",
+                    "message": "fofo",
+                    "priority": 11,
+                    "subtitle": "hey there"
+                },
+                "is_enabled": true,
+                "num_items": 10,
+                "new_homepage": true,
+                "item_order": ["first", "second", "third"],
+            }),
+            merged
+        );
+        Ok(())
+    }
+
+    #[test]
+    fn test_merge_two_defaults_highlevel_non_maps() -> Result<()> {
+        let old_default = json!(["array", "json"]);
+        let new_default = json!(["another", "array"]);
+        let merged = merge_two_defaults(&old_default, &new_default);
+        assert_eq!(json!(["another", "array"]), merged);
+        Ok(())
+    }
+
+    #[test]
+    fn test_channel_defaults_channels_no_merging() -> Result<()> {
+        let input: Vec<DefaultBlock> = serde_json::from_value(json!([
+            {
+                "channel": "release",
+                "value": {
+                    "button-color": "green"
+                }
+            },
+            {
+                "channel": "nightly",
+                "value": {
+                    "button-color": "dark-green"
+                }
+            },
+            {
+                "channel": "beta",
+                "value": {
+                    "button-color": "light-green"
+                }
+            }
+        ]))?;
+        let res = collect_channel_defaults(
+            &input,
+            &[
+                "release".to_string(),
+                "nightly".to_string(),
+                "beta".to_string(),
+            ],
+            "",
+        )?;
+        assert_eq!(
+            vec![
+                (
+                    "release".to_string(),
+                    json!({
+                        "button-color": "green"
+                    })
+                ),
+                (
+                    "nightly".to_string(),
+                    json!({
+                        "button-color": "dark-green"
+                    })
+                ),
+                (
+                    "beta".to_string(),
+                    json!({
+                        "button-color": "light-green"
+                    })
+                ),
+                ("".to_string(), json!({}),),
+            ]
+            .into_iter()
+            .collect::<HashMap<_, _>>(),
+            res
+        );
+        Ok(())
+    }
+
+    #[test]
+    fn test_channel_defaults_channels_merging_same_channel() -> Result<()> {
+        let input: Vec<DefaultBlock> = serde_json::from_value(json!([
+            {
+                "channel": "release",
+                "value": {
+                    "button-color": "green"
+                }
+            },
+            {
+                "channel": "nightly",
+                "value": {
+                    "button-color": "dark-green",
+                    "title": "heya"
+                }
+            },
+            {
+                "channel": "beta",
+                "value": {
+                    "button-color": "light-green"
+                }
+            },
+            {
+                "channel": "nightly",
+                "value": {
+                    "button-color": "dark-red",
+                    "subtitle": "hello",
+                }
+            },
+            {
+                "channel": "beta",
+                "value": {
+                    "title": "hello there"
+                }
+            }
+        ]))?;
+        let res = collect_channel_defaults(
+            &input,
+            &[
+                "release".to_string(),
+                "nightly".to_string(),
+                "beta".to_string(),
+            ],
+            "",
+        )?;
+        assert_eq!(
+            vec![
+                (
+                    "release".to_string(),
+                    json!({
+                        "button-color": "green"
+                    })
+                ),
+                (
+                    "nightly".to_string(),
+                    json!({
+                        "button-color": "dark-red",
+                        "title": "heya",
+                        "subtitle": "hello"
+                    })
+                ),
+                (
+                    "beta".to_string(),
+                    json!({
+                        "button-color": "light-green",
+                        "title": "hello there"
+                    })
+                ),
+                ("".to_string(), json!({}),),
+            ]
+            .into_iter()
+            .collect::<HashMap<_, _>>(),
+            res
+        );
+        Ok(())
+    }
+
+    #[test]
+    fn test_channel_defaults_no_channel_applies_to_all() -> Result<()> {
+        let input: Vec<DefaultBlock> = serde_json::from_value(json!([
+            {
+                "channel": "release",
+                "value": {
+                    "button-color": "green"
+                }
+            },
+            {
+                "channel": "nightly",
+                "value": {
+                    "button-color": "dark-green"
+                }
+            },
+            {
+                "channel": "beta",
+                "value": {
+                    "button-color": "light-green"
+                }
+            },
+            {
+                "value": {
+                    "title": "heya"
+                }
+            }
+        ]))?;
+        let res = collect_channel_defaults(
+            &input,
+            &[
+                "release".to_string(),
+                "nightly".to_string(),
+                "beta".to_string(),
+            ],
+            "",
+        )?;
+        assert_eq!(
+            vec![
+                (
+                    "release".to_string(),
+                    json!({
+                        "button-color": "green",
+                        "title": "heya"
+                    })
+                ),
+                (
+                    "nightly".to_string(),
+                    json!({
+                        "button-color": "dark-green",
+                        "title": "heya"
+                    })
+                ),
+                (
+                    "beta".to_string(),
+                    json!({
+                        "button-color": "light-green",
+                        "title": "heya"
+                    })
+                ),
+                (
+                    "".to_string(),
+                    json!({
+                        "title": "heya",
+                    }),
+                )
+            ]
+            .into_iter()
+            .collect::<HashMap<_, _>>(),
+            res
+        );
+        Ok(())
+    }
+
+    #[test]
+    fn test_channel_defaults_no_channel_overwrites_all() -> Result<()> {
+        let input: Vec<DefaultBlock> = serde_json::from_value(json!([
+            {
+                "channel": "release",
+                "value": {
+                    "button-color": "green"
+                }
+            },
+            {
+                "channel": "nightly",
+                "value": {
+                    "button-color": "dark-green"
+                }
+            },
+            {
+                "channel": "beta",
+                "value": {
+                    "button-color": "light-green"
+                }
+            },
+            {
+                "value": {
+                    "button-color": "red"
+                }
+            }
+        ]))?;
+        let res = collect_channel_defaults(
+            &input,
+            &[
+                "release".to_string(),
+                "nightly".to_string(),
+                "beta".to_string(),
+            ],
+            "",
+        )?;
+        assert_eq!(
+            vec![
+                (
+                    "release".to_string(),
+                    json!({
+                        "button-color": "red"
+                    })
+                ),
+                (
+                    "nightly".to_string(),
+                    json!({
+                        "button-color": "red"
+                    })
+                ),
+                (
+                    "beta".to_string(),
+                    json!({
+                        "button-color": "red"
+                    })
+                ),
+                (
+                    "".to_string(),
+                    json!({
+                        "button-color": "red",
+                    }),
+                )
+            ]
+            .into_iter()
+            .collect::<HashMap<_, _>>(),
+            res
+        );
+        Ok(())
+    }
+
+    #[test]
+    fn test_channel_defaults_no_channel_gets_overwritten_if_followed_by_channel() -> Result<()> {
+        let input: Vec<DefaultBlock> = serde_json::from_value(json!([
+            {
+                "channel": "release",
+                "value": {
+                    "button-color": "green"
+                }
+            },
+            {
+                "channel": "nightly",
+                "value": {
+                    "button-color": "dark-green"
+                }
+            },
+            {
+                "channel": "beta",
+                "value": {
+                    "button-color": "light-green"
+                }
+            },
+            {
+                "value": {
+                    "button-color": "red"
+                }
+            },
+            {
+                "channel": "nightly",
+                "value": {
+                    "button-color": "dark-red"
+                }
+            }
+        ]))?;
+        let res = collect_channel_defaults(
+            &input,
+            &[
+                "release".to_string(),
+                "nightly".to_string(),
+                "beta".to_string(),
+            ],
+            "",
+        )?;
+        assert_eq!(
+            vec![
+                (
+                    "release".to_string(),
+                    json!({
+                        "button-color": "red"
+                    })
+                ),
+                (
+                    "nightly".to_string(),
+                    json!({
+                        "button-color": "dark-red"
+                    })
+                ),
+                (
+                    "beta".to_string(),
+                    json!({
+                        "button-color": "red"
+                    })
+                ),
+                (
+                    "".to_string(),
+                    json!({
+                        "button-color": "red",
+                    }),
+                )
+            ]
+            .into_iter()
+            .collect::<HashMap<_, _>>(),
+            res
+        );
+        Ok(())
+    }
+
+    #[test]
+    fn test_channel_defaults_channels_multiple() -> Result<()> {
+        let input: Vec<DefaultBlock> = serde_json::from_value(json!([
+            {
+                "channels": ["release", "beta"],
+                "value": {
+                    "button-color": "green"
+                }
+            },
+        ]))?;
+        let res =
+            collect_channel_defaults(&input, &["release".to_string(), "beta".to_string()], "")?;
+        assert_eq!(
+            vec![
+                (
+                    "release".to_string(),
+                    json!({
+                        "button-color": "green"
+                    })
+                ),
+                (
+                    "beta".to_string(),
+                    json!({
+                        "button-color": "green"
+                    })
+                ),
+                ("".to_string(), json!({}),)
+            ]
+            .into_iter()
+            .collect::<HashMap<_, _>>(),
+            res
+        );
+        Ok(())
+    }
+
+    #[test]
+    fn test_channel_defaults_channel_multiple_merge_channels_multiple() -> Result<()> {
+        let input: Vec<DefaultBlock> = serde_json::from_value(json!([
+            {
+                "channel": "nightly, debug",
+                "channels": ["release", "beta"],
+                "value": {
+                    "button-color": "green"
+                }
+            },
+        ]))?;
+        let res = collect_channel_defaults(
+            &input,
+            &[
+                "release".to_string(),
+                "beta".to_string(),
+                "nightly".to_string(),
+                "debug".to_string(),
+            ],
+            "",
+        )?;
+        assert_eq!(
+            vec![
+                (
+                    "release".to_string(),
+                    json!({
+                        "button-color": "green"
+                    })
+                ),
+                (
+                    "beta".to_string(),
+                    json!({
+                        "button-color": "green"
+                    })
+                ),
+                (
+                    "nightly".to_string(),
+                    json!({
+                        "button-color": "green"
+                    })
+                ),
+                (
+                    "debug".to_string(),
+                    json!({
+                        "button-color": "green"
+                    })
+                ),
+                ("".to_string(), json!({}),)
+            ]
+            .into_iter()
+            .collect::<HashMap<_, _>>(),
+            res
+        );
+        Ok(())
+    }
+
+    #[test]
+    fn test_channel_defaults_fail_if_invalid_channel_supplied() -> Result<()> {
+        let input: Vec<DefaultBlock> = serde_json::from_value(json!([
+            {
+                "channel": "release",
+                "value": {
+                    "button-color": "green"
+                }
+            },
+            {
+                "channel": "nightly",
+                "value": {
+                    "button-color": "dark-green"
+                }
+            },
+            {
+                "channel": "beta",
+                "value": {
+                    "button-color": "light-green"
+                }
+            },
+            {
+                "channel": "bobo",
+                "value": {
+                    "button-color": "no color"
+                }
+            }
+        ]))?;
+        let res = collect_channel_defaults(
+            &input,
+            &[
+                "release".to_string(),
+                "nightly".to_string(),
+                "beta".to_string(),
+            ],
+            "",
+        )
+        .expect_err("Should return error");
+        if let FMLError::InvalidChannelError(channel, _supported) = res {
+            assert!(channel.contains("bobo"));
+        } else {
+            panic!(
+                "Should have returned a InvalidChannelError, returned {:?}",
+                res
+            )
+        }
+        Ok(())
+    }
+
+    #[test]
+    fn test_channel_defaults_empty_default_created_if_none_supplied_in_feature() -> Result<()> {
+        let input: Vec<DefaultBlock> = serde_json::from_value(json!([
+            {
+                "channel": "release",
+                "value": {
+                    "button-color": "green"
+                }
+            },
+            {
+                "channel": "nightly",
+                "value": {
+                    "button-color": "dark-green"
+                }
+            },
+            // No entry fo beta supplied, we will still get an entry in the result
+            // but it will be empty
+        ]))?;
+        let res = collect_channel_defaults(
+            &input,
+            &[
+                "release".to_string(),
+                "nightly".to_string(),
+                "beta".to_string(),
+            ],
+            "",
+        )?;
+        assert_eq!(
+            vec![
+                (
+                    "release".to_string(),
+                    json!({
+                        "button-color": "green"
+                    })
+                ),
+                (
+                    "nightly".to_string(),
+                    json!({
+                        "button-color": "dark-green"
+                    })
+                ),
+                ("beta".to_string(), json!({})),
+                ("".to_string(), json!({}),)
+            ]
+            .into_iter()
+            .collect::<HashMap<_, _>>(),
+            res
+        );
+        Ok(())
+    }
+
+    #[test]
+    fn test_merge_feature_default_unsupported_channel() -> Result<()> {
+        let mut feature_def: FeatureDef = Default::default();
+        let objects = Default::default();
+        let merger = DefaultsMerger::new_with_channel(
+            &objects,
+            vec!["release".into(), "beta".into()],
+            "nightly".into(),
+        );
+        let err = merger
+            .merge_feature_defaults(&mut feature_def, &None)
+            .expect_err("Should return an error");
+        if let FMLError::InvalidChannelError(channel, _supported) = err {
+            assert!(channel.contains("nightly"));
+        } else {
+            panic!(
+                "Should have returned an InvalidChannelError, returned: {:?}",
+                err
+            );
+        }
+        Ok(())
+    }
+
+    #[test]
+    fn test_merge_feature_default_overwrite_field_default_based_on_channel() -> Result<()> {
+        let mut feature_def = FeatureDef {
+            props: vec![PropDef::new("button-color", TypeRef::String, json!("blue"))],
+            ..Default::default()
+        };
+        let default_blocks = serde_json::from_value(json!([
+            {
+                "channel": "nightly",
+                "value": {
+                    "button-color": "dark-green"
+                }
+            },
+            {
+                "channel": "release",
+                "value": {
+                    "button-color": "green"
+                }
+            },
+            {
+                "channel": "beta",
+                "value": {
+                    "button-color": "light-green"
+                }
+            },
+        ]))?;
+        let objects = Default::default();
+        let merger = DefaultsMerger::new_with_channel(
+            &objects,
+            vec!["release".into(), "beta".into(), "nightly".into()],
+            "nightly".into(),
+        );
+        merger.merge_feature_defaults(&mut feature_def, &default_blocks)?;
+        assert_eq!(
+            feature_def.props,
+            vec![PropDef::new(
+                "button-color",
+                TypeRef::String,
+                json!("dark-green"),
+            )]
+        );
+        Ok(())
+    }
+
+    #[test]
+    fn test_merge_feature_default_field_default_not_overwritten_if_no_feature_default_for_channel(
+    ) -> Result<()> {
+        let mut feature_def = FeatureDef {
+            props: vec![PropDef::new("button-color", TypeRef::String, json!("blue"))],
+            ..Default::default()
+        };
+        let default_blocks = serde_json::from_value(json!([{
+            "channel": "release",
+            "value": {
+                "button-color": "green"
+            }
+        },
+        {
+            "channel": "beta",
+            "value": {
+                "button-color": "light-green"
+            }
+        }]))?;
+        let objects = Default::default();
+        let merger = DefaultsMerger::new_with_channel(
+            &objects,
+            vec!["release".into(), "beta".into(), "nightly".into()],
+            "nightly".into(),
+        );
+        merger.merge_feature_defaults(&mut feature_def, &default_blocks)?;
+        assert_eq!(
+            feature_def.props,
+            vec![PropDef::new("button-color", TypeRef::String, json!("blue"),)]
+        );
+        Ok(())
+    }
+
+    #[test]
+    fn test_merge_feature_default_overwrite_nested_field_default() -> Result<()> {
+        let mut feature_def = FeatureDef {
+            props: vec![PropDef::new(
+                "Dialog",
+                TypeRef::String,
+                json!({
+                    "button-color": "blue",
+                    "title": "hello",
+                    "inner": {
+                        "bobo": "fofo",
+                        "other-field": "other-value"
+                    }
+                }),
+            )],
+
+            ..Default::default()
+        };
+        let default_blocks = serde_json::from_value(json!([
+            {
+                "channel": "nightly",
+                "value": {
+                    "Dialog": {
+                        "button-color": "dark-green",
+                        "inner": {
+                            "bobo": "nightly"
+                        }
+                    }
+                }
+            },
+            {
+                "channel": "release",
+                "value": {
+                    "Dialog": {
+                        "button-color": "green",
+                        "inner": {
+                            "bobo": "release",
+                            "new-field": "new-value"
+                        }
+                    }
+                }
+            },
+            {
+                "channel": "beta",
+                "value": {
+                    "Dialog": {
+                        "button-color": "light-green",
+                        "inner": {
+                            "bobo": "beta"
+                        }
+                    }
+                }
+            },
+        ]))?;
+        let objects = Default::default();
+        let merger = DefaultsMerger::new_with_channel(
+            &objects,
+            vec!["release".into(), "beta".into(), "nightly".into()],
+            "release".into(),
+        );
+        merger.merge_feature_defaults(&mut feature_def, &default_blocks)?;
+        assert_eq!(
+            feature_def.props,
+            vec![PropDef::new(
+                "Dialog",
+                TypeRef::String,
+                json!({
+                        "button-color": "green",
+                        "title": "hello",
+                        "inner": {
+                            "bobo": "release",
+                            "other-field": "other-value",
+                            "new-field": "new-value"
+                        }
+                }),
+            )]
+        );
+        Ok(())
+    }
+
+    #[test]
+    fn test_merge_feature_default_overwrite_field_default_based_on_channel_using_only_no_channel_default(
+    ) -> Result<()> {
+        let mut feature_def = FeatureDef {
+            props: vec![PropDef::new("button-color", TypeRef::String, json!("blue"))],
+            ..Default::default()
+        };
+        let default_blocks = serde_json::from_value(json!([
+            // No channel applies to all channel
+            // so the nightly channel will get this
+            {
+                "value": {
+                    "button-color": "dark-green"
+                }
+            },
+            {
+                "channel": "release",
+                "value": {
+                    "button-color": "green"
+                }
+            },
+            {
+                "channel": "beta",
+                "value": {
+                    "button-color": "light-green"
+                }
+            },
+        ]))?;
+        let objects = Default::default();
+        let merger = DefaultsMerger::new_with_channel(
+            &objects,
+            vec!["release".into(), "beta".into(), "nightly".into()],
+            "nightly".into(),
+        );
+        merger.merge_feature_defaults(&mut feature_def, &default_blocks)?;
+        assert_eq!(
+            feature_def.props,
+            vec![PropDef::new(
+                "button-color",
+                TypeRef::String,
+                json!("dark-green"),
+            )]
+        );
+        Ok(())
+    }
+
+    #[test]
+    fn test_merge_feature_default_throw_error_if_property_not_found_on_feature() -> Result<()> {
+        let mut feature_def = FeatureDef {
+            name: "feature".into(),
+            props: vec![PropDef::new("button-color", TypeRef::String, json!("blue"))],
+            ..Default::default()
+        };
+        let default_blocks = serde_json::from_value(json!([
+            {
+                "value": {
+                    "secondary-button-color": "dark-green"
+                }
+            }
+        ]))?;
+        let objects = Default::default();
+        let merger =
+            DefaultsMerger::new_with_channel(&objects, vec!["nightly".into()], "nightly".into());
+        let result = merger.merge_feature_defaults(&mut feature_def, &default_blocks);
+
+        assert!(result.is_err());
+        assert_eq!(
+            result.err().unwrap().to_string(),
+            "Validation Error at features/feature: Invalid property \"secondary-button-color\"; did you mean \"button-color\"?"
+        );
+        Ok(())
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus_fml/error.rs.html b/book/rust-docs/src/nimbus_fml/error.rs.html new file mode 100644 index 0000000000..4b8e5a46cc --- /dev/null +++ b/book/rust-docs/src/nimbus_fml/error.rs.html @@ -0,0 +1,181 @@ +error.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ * */
+
+use crate::intermediate_representation::ModuleId;
+use std::collections::HashSet;
+
+#[derive(Debug, thiserror::Error)]
+pub enum FMLError {
+    #[error("IO error: {0}")]
+    IOError(#[from] std::io::Error),
+    #[error("JSON Error: {0}")]
+    JSONError(#[from] serde_json::Error),
+    #[error("YAML Error: {0}")]
+    YAMLError(#[from] serde_yaml::Error),
+    #[error("URL Error: {0}")]
+    UrlError(#[from] url::ParseError),
+    #[error("Email Error: {0}")]
+    EmailError(#[from] email_address::Error),
+
+    #[error("Fetch Error: {0}")]
+    FetchError(#[from] reqwest::Error),
+    #[error("Can't find file: {0}")]
+    InvalidPath(String),
+
+    #[error("Unexpected template problem: {0}")]
+    TemplateProblem(#[from] askama::Error),
+
+    #[error("Fatal error: {0}")]
+    Fatal(#[from] anyhow::Error),
+
+    #[error("Internal error: {0}")]
+    InternalError(&'static str),
+    #[error("Validation Error at {0}: {1}")]
+    ValidationError(String, String),
+    #[error("Validation Error at {path}: {message}")]
+    FeatureValidationError {
+        literals: Vec<String>,
+        path: String,
+        message: String,
+    },
+    #[error("Type Parsing Error: {0}")]
+    TypeParsingError(String),
+    #[error("Invalid Channel error: The channel `{0}` is specified, but only {1:?} are supported for the file")]
+    InvalidChannelError(String, Vec<String>),
+
+    #[error("Problem with {0}: {1}")]
+    FMLModuleError(ModuleId, String),
+
+    #[error("{0}")]
+    CliError(String),
+
+    #[cfg(feature = "client-lib")]
+    #[error("{0}")]
+    ClientError(#[from] ClientError),
+
+    #[error("Feature `{0}` not found on manifest")]
+    InvalidFeatureError(String),
+}
+
+#[cfg(feature = "client-lib")]
+#[derive(Debug, thiserror::Error)]
+pub enum ClientError {
+    #[error("{0}")]
+    InvalidFeatureConfig(String),
+    #[error("{0}")]
+    InvalidFeatureId(String),
+    #[error("{0}")]
+    InvalidFeatureValue(String),
+    #[error("{0}")]
+    JsonMergeError(String),
+}
+
+pub type Result<T, E = FMLError> = std::result::Result<T, E>;
+
+pub(crate) fn did_you_mean(words: HashSet<String>) -> String {
+    if words.is_empty() {
+        "".to_string()
+    } else if words.len() == 1 {
+        format!("; did you mean \"{}\"?", words.iter().next().unwrap())
+    } else {
+        let mut words = words.into_iter().collect::<Vec<_>>();
+        let last = words.remove(words.len() - 1);
+        format!(
+            "; did you mean one of \"{}\" or \"{last}\"?",
+            words.join("\", \"")
+        )
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus_fml/frontend.rs.html b/book/rust-docs/src/nimbus_fml/frontend.rs.html new file mode 100644 index 0000000000..4bc8f11eb6 --- /dev/null +++ b/book/rust-docs/src/nimbus_fml/frontend.rs.html @@ -0,0 +1,1339 @@ +frontend.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
+540
+541
+542
+543
+544
+545
+546
+547
+548
+549
+550
+551
+552
+553
+554
+555
+556
+557
+558
+559
+560
+561
+562
+563
+564
+565
+566
+567
+568
+569
+570
+571
+572
+573
+574
+575
+576
+577
+578
+579
+580
+581
+582
+583
+584
+585
+586
+587
+588
+589
+590
+591
+592
+593
+594
+595
+596
+597
+598
+599
+600
+601
+602
+603
+604
+605
+606
+607
+608
+609
+610
+611
+612
+613
+614
+615
+616
+617
+618
+619
+620
+621
+622
+623
+624
+625
+626
+627
+628
+629
+630
+631
+632
+633
+634
+635
+636
+637
+638
+639
+640
+641
+642
+643
+644
+645
+646
+647
+648
+649
+650
+651
+652
+653
+654
+655
+656
+657
+658
+659
+660
+661
+662
+663
+664
+665
+666
+667
+668
+669
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use std::collections::{BTreeMap, HashMap, HashSet};
+
+use email_address::EmailAddress;
+use serde::{Deserialize, Serialize};
+use serde_json::json;
+use url::Url;
+
+use crate::{
+    defaults_merger::DefaultsMerger,
+    error::Result,
+    intermediate_representation::{
+        EnumDef, FeatureDef, FeatureManifest, ModuleId, ObjectDef, PropDef, TargetLanguage,
+        TypeRef, VariantDef,
+    },
+    parser::get_typeref_from_string,
+};
+
+#[derive(Debug, Deserialize, Serialize, Clone)]
+#[serde(deny_unknown_fields)]
+pub(crate) struct EnumVariantBody {
+    pub(crate) description: String,
+}
+
+#[derive(Debug, Deserialize, Serialize, Clone)]
+#[serde(deny_unknown_fields)]
+pub(crate) struct EnumBody {
+    pub(crate) description: String,
+    pub(crate) variants: BTreeMap<String, EnumVariantBody>,
+}
+
+#[derive(Debug, Deserialize, Serialize, Clone)]
+#[serde(deny_unknown_fields)]
+#[serde(rename_all = "kebab-case")]
+pub(crate) struct FeatureFieldBody {
+    #[serde(flatten)]
+    pub(crate) field: FieldBody,
+
+    #[serde(default)]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub(crate) pref_key: Option<String>,
+}
+
+#[derive(Debug, Deserialize, Serialize, Clone)]
+#[serde(deny_unknown_fields)]
+pub(crate) struct FieldBody {
+    pub(crate) description: String,
+    #[serde(rename = "type")]
+    pub(crate) variable_type: String,
+    pub(crate) default: Option<serde_json::Value>,
+}
+
+#[derive(Debug, Deserialize, Serialize, Clone)]
+#[serde(deny_unknown_fields)]
+pub(crate) struct ObjectBody {
+    pub(crate) description: String,
+    // We need these in a deterministic order, so they are stable across multiple
+    // runs of the same manifests.
+    #[serde(skip_serializing_if = "BTreeMap::is_empty")]
+    pub(crate) fields: BTreeMap<String, FieldBody>,
+}
+
+#[derive(Debug, Deserialize, Serialize, Clone, Default)]
+#[serde(deny_unknown_fields)]
+pub(crate) struct Types {
+    #[serde(default)]
+    #[serde(skip_serializing_if = "BTreeMap::is_empty")]
+    pub(crate) enums: BTreeMap<String, EnumBody>,
+    #[serde(default)]
+    #[serde(skip_serializing_if = "BTreeMap::is_empty")]
+    pub(crate) objects: BTreeMap<String, ObjectBody>,
+}
+
+#[derive(Debug, Deserialize, Serialize, Clone, Default, PartialEq, Eq)]
+#[serde(deny_unknown_fields)]
+pub(crate) struct AboutBlock {
+    pub(crate) description: String,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    #[serde(alias = "kotlin", alias = "android")]
+    pub(crate) kotlin_about: Option<KotlinAboutBlock>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    #[serde(alias = "swift", alias = "ios")]
+    pub(crate) swift_about: Option<SwiftAboutBlock>,
+}
+
+impl AboutBlock {
+    pub(crate) fn is_includable(&self) -> bool {
+        self.kotlin_about.is_none() && self.swift_about.is_none()
+    }
+
+    #[allow(unused)]
+    pub(crate) fn supports(&self, lang: &TargetLanguage) -> bool {
+        match lang {
+            TargetLanguage::Kotlin => self.kotlin_about.is_some(),
+            TargetLanguage::Swift => self.swift_about.is_some(),
+            TargetLanguage::IR => true,
+            TargetLanguage::ExperimenterYAML => true,
+            TargetLanguage::ExperimenterJSON => true,
+        }
+    }
+
+    #[allow(unused)]
+    pub fn description_only(&self) -> Self {
+        AboutBlock {
+            description: self.description.clone(),
+            kotlin_about: None,
+            swift_about: None,
+        }
+    }
+}
+
+#[derive(Debug, Deserialize, Serialize, Clone, Default, PartialEq, Eq)]
+pub(crate) struct SwiftAboutBlock {
+    pub(crate) module: String,
+    pub(crate) class: String,
+}
+
+#[derive(Debug, Deserialize, Serialize, Clone, Default, PartialEq, Eq)]
+pub(crate) struct KotlinAboutBlock {
+    pub(crate) package: String,
+    pub(crate) class: String,
+}
+
+#[derive(Debug, Deserialize, Serialize, Clone, Default)]
+pub(crate) struct ImportBlock {
+    pub(crate) path: String,
+    pub(crate) channel: String,
+    #[serde(default)]
+    pub(crate) features: BTreeMap<String, Vec<DefaultBlock>>,
+}
+
+#[derive(Debug, Deserialize, Serialize, Clone)]
+#[serde(deny_unknown_fields)]
+#[serde(rename_all = "kebab-case")]
+pub(crate) struct FeatureBody {
+    #[serde(flatten)]
+    pub(crate) metadata: FeatureMetadata,
+    // We need these in a deterministic order, so they are stable across multiple
+    // runs of the same manifests:
+    // 1. Swift insists on args in the same order they were declared.
+    // 2. imported features are declared and constructed in different runs of the tool.
+    #[serde(skip_serializing_if = "BTreeMap::is_empty")]
+    pub(crate) variables: BTreeMap<String, FeatureFieldBody>,
+    #[serde(alias = "defaults")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub(crate) default: Option<Vec<DefaultBlock>>,
+    #[serde(default)]
+    #[serde(skip_serializing_if = "std::ops::Not::not")]
+    pub(crate) allow_coenrollment: bool,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
+#[serde(deny_unknown_fields)]
+#[serde(rename_all = "kebab-case")]
+pub(crate) struct FeatureMetadata {
+    pub(crate) description: String,
+    /// A list of named URLs to documentation for this feature.
+    #[serde(default)]
+    #[serde(skip_serializing_if = "Vec::is_empty")]
+    pub(crate) documentation: Vec<DocumentationLink>,
+    /// A list of contacts (engineers, product owners) who can be contacted for
+    /// help with this feature. Specifically for QA questions.
+    #[serde(default)]
+    #[serde(alias = "owners", alias = "owner")]
+    #[serde(skip_serializing_if = "Vec::is_empty")]
+    pub(crate) contacts: Vec<EmailAddress>,
+    /// Where should QA file issues for this feature?
+    #[serde(default)]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub(crate) meta_bug: Option<Url>,
+    /// What Glean events can the feature produce?
+    /// These should be links to a Glean dictionary.
+    #[serde(default)]
+    #[serde(skip_serializing_if = "Vec::is_empty")]
+    pub(crate) events: Vec<Url>,
+    /// A link to a Web based configuration UI for this feature.
+    /// This UI should produce the valid JSON instead of typing it
+    /// by hand.
+    #[serde(default)]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub(crate) configurator: Option<Url>,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
+#[serde(deny_unknown_fields)]
+pub(crate) struct DocumentationLink {
+    pub(crate) name: String,
+    pub(crate) url: Url,
+}
+
+#[derive(Debug, Deserialize, Serialize, Clone, Default)]
+#[serde(deny_unknown_fields)]
+pub struct ManifestFrontEnd {
+    #[serde(default)]
+    pub(crate) version: String,
+    #[serde(default)]
+    pub(crate) about: Option<AboutBlock>,
+
+    #[serde(default)]
+    #[serde(skip_serializing_if = "Vec::is_empty")]
+    pub(crate) channels: Vec<String>,
+
+    #[serde(default)]
+    #[serde(alias = "include")]
+    #[serde(skip_serializing_if = "Vec::is_empty")]
+    pub(crate) includes: Vec<String>,
+
+    #[serde(default)]
+    #[serde(alias = "import")]
+    #[serde(skip_serializing_if = "Vec::is_empty")]
+    pub(crate) imports: Vec<ImportBlock>,
+
+    #[serde(default)]
+    #[serde(skip_serializing_if = "BTreeMap::is_empty")]
+    pub(crate) features: BTreeMap<String, FeatureBody>,
+
+    // We'd like to get rid of the `types` property,
+    // but we need to keep supporting it.
+    #[serde(default)]
+    #[serde(rename = "types")]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub(crate) legacy_types: Option<Types>,
+
+    // If a types attribute isn't explicitly expressed,
+    // then we should assume that we use the flattened version.
+    #[serde(default)]
+    #[serde(flatten)]
+    pub(crate) types: Types,
+}
+
+impl ManifestFrontEnd {
+    pub fn channels(&self) -> Vec<String> {
+        self.channels.clone()
+    }
+
+    pub fn includes(&self) -> Vec<String> {
+        self.includes.clone()
+    }
+
+    /// Retrieves all the types represented in the Manifest
+    ///
+    /// # Returns
+    /// Returns a [`std::collections::HashMap<String,TypeRef>`] where
+    /// the key is the name of the type, and the TypeRef represents the type itself
+    fn get_types(&self) -> HashMap<String, TypeRef> {
+        let types = self.legacy_types.as_ref().unwrap_or(&self.types);
+        types
+            .enums
+            .keys()
+            .map(|s| (s.clone(), TypeRef::Enum(s.clone())))
+            .chain(
+                types
+                    .objects
+                    .keys()
+                    .map(|s| (s.clone(), TypeRef::Object(s.clone()))),
+            )
+            .collect()
+    }
+
+    fn get_prop_def_from_feature_field(&self, nm: &str, body: &FeatureFieldBody) -> PropDef {
+        let mut prop = self.get_prop_def_from_field(nm, &body.field);
+        prop.pref_key = body.pref_key.clone();
+        prop
+    }
+
+    /// Transforms a front-end field definition, a tuple of [`String`] and [`FieldBody`],
+    /// into a [`PropDef`]
+    ///
+    /// # Arguments
+    /// - `field`: The [`(&String, &FieldBody)`] tuple to get the propdef from
+    ///
+    /// # Returns
+    /// return the IR [`PropDef`]
+    fn get_prop_def_from_field(&self, nm: &str, body: &FieldBody) -> PropDef {
+        let types = self.get_types();
+        PropDef {
+            name: nm.into(),
+            doc: body.description.clone(),
+            typ: match get_typeref_from_string(body.variable_type.to_owned(), Some(types.clone())) {
+                Ok(type_ref) => type_ref,
+                Err(e) => {
+                    // Try matching against the user defined types
+                    match types.get(&body.variable_type) {
+                        Some(type_ref) => type_ref.to_owned(),
+                        None => panic!(
+                            "{}\n{} is not a valid FML type or user defined type",
+                            e, body.variable_type
+                        ),
+                    }
+                }
+            },
+            default: json!(body.default),
+            pref_key: None,
+        }
+    }
+
+    /// Retrieves all the feature definitions represented in the manifest
+    ///
+    /// # Returns
+    /// Returns a [`std::collections::BTreeMap<String, FeatureDef>`]
+    fn get_feature_defs(&self, merger: &DefaultsMerger) -> Result<BTreeMap<String, FeatureDef>> {
+        let mut features: BTreeMap<_, _> = Default::default();
+        for (nm, body) in &self.features {
+            let mut fields: Vec<_> = Default::default();
+            for (fnm, field) in &body.variables {
+                fields.push(self.get_prop_def_from_feature_field(fnm, field));
+            }
+            let mut def = FeatureDef {
+                name: nm.clone(),
+                metadata: body.metadata.clone(),
+                props: fields,
+                allow_coenrollment: body.allow_coenrollment,
+            };
+            merger.merge_feature_defaults(&mut def, &body.default)?;
+            features.insert(nm.to_owned(), def);
+        }
+        Ok(features)
+    }
+
+    /// Retrieves all the Object type definitions represented in the manifest
+    ///
+    /// # Returns
+    /// Returns a [`std::collections::BTreeMap<String. ObjectDef>`]
+    fn get_objects(&self) -> BTreeMap<String, ObjectDef> {
+        let types = self.legacy_types.as_ref().unwrap_or(&self.types);
+        let mut objs: BTreeMap<_, _> = Default::default();
+        for (nm, body) in &types.objects {
+            let mut fields: Vec<_> = Default::default();
+            for (fnm, field) in &body.fields {
+                fields.push(self.get_prop_def_from_field(fnm, field));
+            }
+            objs.insert(
+                nm.to_owned(),
+                ObjectDef {
+                    name: nm.clone(),
+                    doc: body.description.clone(),
+                    props: fields,
+                },
+            );
+        }
+        objs
+    }
+
+    /// Retrieves all the Enum type definitions represented in the manifest
+    ///
+    /// # Returns
+    /// Returns a [`std::collections::BTreeMap<String, EnumDef>`]
+    fn get_enums(&self) -> BTreeMap<String, EnumDef> {
+        let types = self.legacy_types.as_ref().unwrap_or(&self.types);
+        let mut enums: BTreeMap<_, _> = Default::default();
+        for (name, body) in &types.enums {
+            let mut variants: Vec<_> = Default::default();
+            for (v_name, v_body) in &body.variants {
+                variants.push(VariantDef {
+                    name: v_name.clone(),
+                    doc: v_body.description.clone(),
+                });
+            }
+            enums.insert(
+                name.to_owned(),
+                EnumDef {
+                    name: name.clone(),
+                    doc: body.description.clone(),
+                    variants,
+                },
+            );
+        }
+        enums
+    }
+
+    pub(crate) fn get_intermediate_representation(
+        &self,
+        id: &ModuleId,
+        channel: Option<&str>,
+    ) -> Result<FeatureManifest> {
+        let enums = self.get_enums();
+        let objects = self.get_objects();
+        let merger =
+            DefaultsMerger::new(&objects, self.channels.clone(), channel.map(str::to_string));
+
+        let features = self.get_feature_defs(&merger)?;
+
+        let about = match &self.about {
+            Some(a) => a.clone(),
+            None => Default::default(),
+        };
+
+        Ok(FeatureManifest::new(
+            id.clone(),
+            channel,
+            features,
+            enums,
+            objects,
+            about,
+        ))
+    }
+}
+
+#[derive(Debug, Deserialize, Serialize, Clone)]
+pub struct DefaultBlock {
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub(crate) channel: Option<String>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub(crate) channels: Option<Vec<String>>,
+    pub(crate) value: serde_json::Value,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub(crate) targeting: Option<String>,
+}
+
+impl DefaultBlock {
+    pub fn merge_channels(&self) -> Option<Vec<String>> {
+        let mut res = HashSet::new();
+
+        if let Some(channels) = self.channels.clone() {
+            res.extend(channels)
+        }
+
+        if let Some(channel) = &self.channel {
+            res.extend(
+                channel
+                    .split(',')
+                    .filter(|channel_name| !channel_name.is_empty())
+                    .map(|channel_name| channel_name.trim().to_string())
+                    .collect::<HashSet<String>>(),
+            )
+        }
+
+        let res: Vec<String> = res.into_iter().collect();
+        if res.is_empty() {
+            None
+        } else {
+            Some(res)
+        }
+    }
+}
+
+impl From<serde_json::Value> for DefaultBlock {
+    fn from(value: serde_json::Value) -> Self {
+        Self {
+            value,
+            channels: None,
+            channel: None,
+            targeting: None,
+        }
+    }
+}
+
+#[cfg(test)]
+mod about_block {
+    use super::*;
+
+    #[test]
+    fn test_parsing_about_block() -> Result<()> {
+        let about = AboutBlock {
+            kotlin_about: Some(KotlinAboutBlock {
+                package: "com.example".to_string(),
+                class: "KotlinAbout".to_string(),
+            }),
+            ..Default::default()
+        };
+
+        let yaml = serde_yaml::to_value(&about)?;
+
+        let rehydrated = serde_yaml::from_value(yaml)?;
+
+        assert_eq!(about, rehydrated);
+
+        Ok(())
+    }
+}
+
+#[cfg(test)]
+mod default_block {
+    use super::*;
+
+    #[test]
+    fn test_merge_channels_none_when_empty() {
+        let input: DefaultBlock = serde_json::from_value(json!(
+            {
+                "channel": "",
+                "channels": [],
+                "value": {
+                    "button-color": "green"
+                }
+            }
+        ))
+        .unwrap();
+        assert!(input.merge_channels().is_none())
+    }
+
+    #[test]
+    fn test_merge_channels_merged_when_present() {
+        let input: DefaultBlock = serde_json::from_value(json!(
+            {
+                "channel": "a, b",
+                "channels": ["c"],
+                "value": {
+                    "button-color": "green"
+                }
+            }
+        ))
+        .unwrap();
+        let res = input.merge_channels();
+        assert!(res.is_some());
+        let res = res.unwrap();
+        assert!(res.contains(&"a".to_string()));
+        assert!(res.contains(&"b".to_string()));
+        assert!(res.contains(&"c".to_string()));
+    }
+
+    #[test]
+    fn test_merge_channels_merged_without_duplicates() {
+        let input: DefaultBlock = serde_json::from_value(json!(
+            {
+                "channel": "a, a",
+                "channels": ["a"],
+                "value": {
+                    "button-color": "green"
+                }
+            }
+        ))
+        .unwrap();
+        let res = input.merge_channels();
+        assert!(res.is_some());
+        let res = res.unwrap();
+        assert!(res.contains(&"a".to_string()));
+        assert!(res.len() == 1)
+    }
+}
+
+#[cfg(test)]
+mod feature_metadata {
+    use super::*;
+    use std::str::FromStr;
+
+    impl DocumentationLink {
+        fn new(nm: &str, url: &str) -> Result<Self> {
+            Ok(Self {
+                name: nm.to_string(),
+                url: Url::from_str(url)?,
+            })
+        }
+    }
+
+    #[test]
+    fn test_happy_path() -> Result<()> {
+        let fm = serde_json::from_str::<FeatureMetadata>(
+            r#"{
+            "description": "A description",
+            "meta-bug": "https://example.com/EXP-23",
+            "contacts": [
+                "jdoe@example.com"
+            ],
+            "documentation": [
+                {
+                    "name": "User documentation",
+                    "url": "https://example.info/my-feature"
+                }
+            ],
+            "events": [
+                "https://example.com/glean/dictionary/button-pressed"
+            ],
+            "configurator": "https://auth.example.com/my-feature/configuration-ui"
+        }"#,
+        )?;
+        assert_eq!(
+            fm,
+            FeatureMetadata {
+                description: "A description".to_string(),
+                meta_bug: Some(Url::from_str("https://example.com/EXP-23")?),
+                contacts: vec![EmailAddress::from_str("jdoe@example.com")?],
+                documentation: vec![DocumentationLink::new(
+                    "User documentation",
+                    "https://example.info/my-feature"
+                )?],
+                configurator: Some(Url::from_str(
+                    "https://auth.example.com/my-feature/configuration-ui"
+                )?),
+                events: vec![Url::from_str(
+                    "https://example.com/glean/dictionary/button-pressed"
+                )?,],
+            }
+        );
+
+        let fm = serde_json::from_str::<FeatureMetadata>(
+            r#"{
+            "description": "A description",
+            "meta-bug": "https://example.com/EXP-23",
+            "documentation": [
+                {
+                    "name": "User documentation",
+                    "url": "https://example.info/my-feature"
+                }
+            ]
+        }"#,
+        )?;
+        assert_eq!(
+            fm,
+            FeatureMetadata {
+                description: "A description".to_string(),
+                meta_bug: Some(Url::from_str("https://example.com/EXP-23")?),
+                contacts: Default::default(),
+                documentation: vec![DocumentationLink::new(
+                    "User documentation",
+                    "https://example.info/my-feature"
+                )?],
+                ..Default::default()
+            }
+        );
+
+        let fm = serde_json::from_str::<FeatureMetadata>(
+            r#"{
+            "description": "A description",
+            "contacts": [
+                "jdoe@example.com"
+            ]
+        }"#,
+        )?;
+        assert_eq!(
+            fm,
+            FeatureMetadata {
+                description: "A description".to_string(),
+                contacts: vec![EmailAddress::from_str("jdoe@example.com")?],
+                ..Default::default()
+            }
+        );
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_invalid_email_addresses() -> Result<()> {
+        let fm = serde_json::from_str::<FeatureMetadata>(
+            r#"{
+            "description": "A description",
+            "contacts": [
+                "Not an email address"
+            ],
+        }"#,
+        );
+        assert!(fm.is_err());
+        Ok(())
+    }
+
+    #[test]
+    fn test_invalid_urls() -> Result<()> {
+        let fm = serde_json::from_str::<FeatureMetadata>(
+            r#"{
+            "description": "A description",
+            "documentation": [
+                "Not a url"
+            ],
+        }"#,
+        );
+        assert!(fm.is_err());
+
+        let fm = serde_json::from_str::<FeatureMetadata>(
+            r#"{
+            "description": "A description",
+            "meta-bug": "Not a url"
+        }"#,
+        );
+        assert!(fm.is_err());
+        Ok(())
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus_fml/intermediate_representation.rs.html b/book/rust-docs/src/nimbus_fml/intermediate_representation.rs.html new file mode 100644 index 0000000000..6ab220f476 --- /dev/null +++ b/book/rust-docs/src/nimbus_fml/intermediate_representation.rs.html @@ -0,0 +1,4405 @@ +intermediate_representation.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
+540
+541
+542
+543
+544
+545
+546
+547
+548
+549
+550
+551
+552
+553
+554
+555
+556
+557
+558
+559
+560
+561
+562
+563
+564
+565
+566
+567
+568
+569
+570
+571
+572
+573
+574
+575
+576
+577
+578
+579
+580
+581
+582
+583
+584
+585
+586
+587
+588
+589
+590
+591
+592
+593
+594
+595
+596
+597
+598
+599
+600
+601
+602
+603
+604
+605
+606
+607
+608
+609
+610
+611
+612
+613
+614
+615
+616
+617
+618
+619
+620
+621
+622
+623
+624
+625
+626
+627
+628
+629
+630
+631
+632
+633
+634
+635
+636
+637
+638
+639
+640
+641
+642
+643
+644
+645
+646
+647
+648
+649
+650
+651
+652
+653
+654
+655
+656
+657
+658
+659
+660
+661
+662
+663
+664
+665
+666
+667
+668
+669
+670
+671
+672
+673
+674
+675
+676
+677
+678
+679
+680
+681
+682
+683
+684
+685
+686
+687
+688
+689
+690
+691
+692
+693
+694
+695
+696
+697
+698
+699
+700
+701
+702
+703
+704
+705
+706
+707
+708
+709
+710
+711
+712
+713
+714
+715
+716
+717
+718
+719
+720
+721
+722
+723
+724
+725
+726
+727
+728
+729
+730
+731
+732
+733
+734
+735
+736
+737
+738
+739
+740
+741
+742
+743
+744
+745
+746
+747
+748
+749
+750
+751
+752
+753
+754
+755
+756
+757
+758
+759
+760
+761
+762
+763
+764
+765
+766
+767
+768
+769
+770
+771
+772
+773
+774
+775
+776
+777
+778
+779
+780
+781
+782
+783
+784
+785
+786
+787
+788
+789
+790
+791
+792
+793
+794
+795
+796
+797
+798
+799
+800
+801
+802
+803
+804
+805
+806
+807
+808
+809
+810
+811
+812
+813
+814
+815
+816
+817
+818
+819
+820
+821
+822
+823
+824
+825
+826
+827
+828
+829
+830
+831
+832
+833
+834
+835
+836
+837
+838
+839
+840
+841
+842
+843
+844
+845
+846
+847
+848
+849
+850
+851
+852
+853
+854
+855
+856
+857
+858
+859
+860
+861
+862
+863
+864
+865
+866
+867
+868
+869
+870
+871
+872
+873
+874
+875
+876
+877
+878
+879
+880
+881
+882
+883
+884
+885
+886
+887
+888
+889
+890
+891
+892
+893
+894
+895
+896
+897
+898
+899
+900
+901
+902
+903
+904
+905
+906
+907
+908
+909
+910
+911
+912
+913
+914
+915
+916
+917
+918
+919
+920
+921
+922
+923
+924
+925
+926
+927
+928
+929
+930
+931
+932
+933
+934
+935
+936
+937
+938
+939
+940
+941
+942
+943
+944
+945
+946
+947
+948
+949
+950
+951
+952
+953
+954
+955
+956
+957
+958
+959
+960
+961
+962
+963
+964
+965
+966
+967
+968
+969
+970
+971
+972
+973
+974
+975
+976
+977
+978
+979
+980
+981
+982
+983
+984
+985
+986
+987
+988
+989
+990
+991
+992
+993
+994
+995
+996
+997
+998
+999
+1000
+1001
+1002
+1003
+1004
+1005
+1006
+1007
+1008
+1009
+1010
+1011
+1012
+1013
+1014
+1015
+1016
+1017
+1018
+1019
+1020
+1021
+1022
+1023
+1024
+1025
+1026
+1027
+1028
+1029
+1030
+1031
+1032
+1033
+1034
+1035
+1036
+1037
+1038
+1039
+1040
+1041
+1042
+1043
+1044
+1045
+1046
+1047
+1048
+1049
+1050
+1051
+1052
+1053
+1054
+1055
+1056
+1057
+1058
+1059
+1060
+1061
+1062
+1063
+1064
+1065
+1066
+1067
+1068
+1069
+1070
+1071
+1072
+1073
+1074
+1075
+1076
+1077
+1078
+1079
+1080
+1081
+1082
+1083
+1084
+1085
+1086
+1087
+1088
+1089
+1090
+1091
+1092
+1093
+1094
+1095
+1096
+1097
+1098
+1099
+1100
+1101
+1102
+1103
+1104
+1105
+1106
+1107
+1108
+1109
+1110
+1111
+1112
+1113
+1114
+1115
+1116
+1117
+1118
+1119
+1120
+1121
+1122
+1123
+1124
+1125
+1126
+1127
+1128
+1129
+1130
+1131
+1132
+1133
+1134
+1135
+1136
+1137
+1138
+1139
+1140
+1141
+1142
+1143
+1144
+1145
+1146
+1147
+1148
+1149
+1150
+1151
+1152
+1153
+1154
+1155
+1156
+1157
+1158
+1159
+1160
+1161
+1162
+1163
+1164
+1165
+1166
+1167
+1168
+1169
+1170
+1171
+1172
+1173
+1174
+1175
+1176
+1177
+1178
+1179
+1180
+1181
+1182
+1183
+1184
+1185
+1186
+1187
+1188
+1189
+1190
+1191
+1192
+1193
+1194
+1195
+1196
+1197
+1198
+1199
+1200
+1201
+1202
+1203
+1204
+1205
+1206
+1207
+1208
+1209
+1210
+1211
+1212
+1213
+1214
+1215
+1216
+1217
+1218
+1219
+1220
+1221
+1222
+1223
+1224
+1225
+1226
+1227
+1228
+1229
+1230
+1231
+1232
+1233
+1234
+1235
+1236
+1237
+1238
+1239
+1240
+1241
+1242
+1243
+1244
+1245
+1246
+1247
+1248
+1249
+1250
+1251
+1252
+1253
+1254
+1255
+1256
+1257
+1258
+1259
+1260
+1261
+1262
+1263
+1264
+1265
+1266
+1267
+1268
+1269
+1270
+1271
+1272
+1273
+1274
+1275
+1276
+1277
+1278
+1279
+1280
+1281
+1282
+1283
+1284
+1285
+1286
+1287
+1288
+1289
+1290
+1291
+1292
+1293
+1294
+1295
+1296
+1297
+1298
+1299
+1300
+1301
+1302
+1303
+1304
+1305
+1306
+1307
+1308
+1309
+1310
+1311
+1312
+1313
+1314
+1315
+1316
+1317
+1318
+1319
+1320
+1321
+1322
+1323
+1324
+1325
+1326
+1327
+1328
+1329
+1330
+1331
+1332
+1333
+1334
+1335
+1336
+1337
+1338
+1339
+1340
+1341
+1342
+1343
+1344
+1345
+1346
+1347
+1348
+1349
+1350
+1351
+1352
+1353
+1354
+1355
+1356
+1357
+1358
+1359
+1360
+1361
+1362
+1363
+1364
+1365
+1366
+1367
+1368
+1369
+1370
+1371
+1372
+1373
+1374
+1375
+1376
+1377
+1378
+1379
+1380
+1381
+1382
+1383
+1384
+1385
+1386
+1387
+1388
+1389
+1390
+1391
+1392
+1393
+1394
+1395
+1396
+1397
+1398
+1399
+1400
+1401
+1402
+1403
+1404
+1405
+1406
+1407
+1408
+1409
+1410
+1411
+1412
+1413
+1414
+1415
+1416
+1417
+1418
+1419
+1420
+1421
+1422
+1423
+1424
+1425
+1426
+1427
+1428
+1429
+1430
+1431
+1432
+1433
+1434
+1435
+1436
+1437
+1438
+1439
+1440
+1441
+1442
+1443
+1444
+1445
+1446
+1447
+1448
+1449
+1450
+1451
+1452
+1453
+1454
+1455
+1456
+1457
+1458
+1459
+1460
+1461
+1462
+1463
+1464
+1465
+1466
+1467
+1468
+1469
+1470
+1471
+1472
+1473
+1474
+1475
+1476
+1477
+1478
+1479
+1480
+1481
+1482
+1483
+1484
+1485
+1486
+1487
+1488
+1489
+1490
+1491
+1492
+1493
+1494
+1495
+1496
+1497
+1498
+1499
+1500
+1501
+1502
+1503
+1504
+1505
+1506
+1507
+1508
+1509
+1510
+1511
+1512
+1513
+1514
+1515
+1516
+1517
+1518
+1519
+1520
+1521
+1522
+1523
+1524
+1525
+1526
+1527
+1528
+1529
+1530
+1531
+1532
+1533
+1534
+1535
+1536
+1537
+1538
+1539
+1540
+1541
+1542
+1543
+1544
+1545
+1546
+1547
+1548
+1549
+1550
+1551
+1552
+1553
+1554
+1555
+1556
+1557
+1558
+1559
+1560
+1561
+1562
+1563
+1564
+1565
+1566
+1567
+1568
+1569
+1570
+1571
+1572
+1573
+1574
+1575
+1576
+1577
+1578
+1579
+1580
+1581
+1582
+1583
+1584
+1585
+1586
+1587
+1588
+1589
+1590
+1591
+1592
+1593
+1594
+1595
+1596
+1597
+1598
+1599
+1600
+1601
+1602
+1603
+1604
+1605
+1606
+1607
+1608
+1609
+1610
+1611
+1612
+1613
+1614
+1615
+1616
+1617
+1618
+1619
+1620
+1621
+1622
+1623
+1624
+1625
+1626
+1627
+1628
+1629
+1630
+1631
+1632
+1633
+1634
+1635
+1636
+1637
+1638
+1639
+1640
+1641
+1642
+1643
+1644
+1645
+1646
+1647
+1648
+1649
+1650
+1651
+1652
+1653
+1654
+1655
+1656
+1657
+1658
+1659
+1660
+1661
+1662
+1663
+1664
+1665
+1666
+1667
+1668
+1669
+1670
+1671
+1672
+1673
+1674
+1675
+1676
+1677
+1678
+1679
+1680
+1681
+1682
+1683
+1684
+1685
+1686
+1687
+1688
+1689
+1690
+1691
+1692
+1693
+1694
+1695
+1696
+1697
+1698
+1699
+1700
+1701
+1702
+1703
+1704
+1705
+1706
+1707
+1708
+1709
+1710
+1711
+1712
+1713
+1714
+1715
+1716
+1717
+1718
+1719
+1720
+1721
+1722
+1723
+1724
+1725
+1726
+1727
+1728
+1729
+1730
+1731
+1732
+1733
+1734
+1735
+1736
+1737
+1738
+1739
+1740
+1741
+1742
+1743
+1744
+1745
+1746
+1747
+1748
+1749
+1750
+1751
+1752
+1753
+1754
+1755
+1756
+1757
+1758
+1759
+1760
+1761
+1762
+1763
+1764
+1765
+1766
+1767
+1768
+1769
+1770
+1771
+1772
+1773
+1774
+1775
+1776
+1777
+1778
+1779
+1780
+1781
+1782
+1783
+1784
+1785
+1786
+1787
+1788
+1789
+1790
+1791
+1792
+1793
+1794
+1795
+1796
+1797
+1798
+1799
+1800
+1801
+1802
+1803
+1804
+1805
+1806
+1807
+1808
+1809
+1810
+1811
+1812
+1813
+1814
+1815
+1816
+1817
+1818
+1819
+1820
+1821
+1822
+1823
+1824
+1825
+1826
+1827
+1828
+1829
+1830
+1831
+1832
+1833
+1834
+1835
+1836
+1837
+1838
+1839
+1840
+1841
+1842
+1843
+1844
+1845
+1846
+1847
+1848
+1849
+1850
+1851
+1852
+1853
+1854
+1855
+1856
+1857
+1858
+1859
+1860
+1861
+1862
+1863
+1864
+1865
+1866
+1867
+1868
+1869
+1870
+1871
+1872
+1873
+1874
+1875
+1876
+1877
+1878
+1879
+1880
+1881
+1882
+1883
+1884
+1885
+1886
+1887
+1888
+1889
+1890
+1891
+1892
+1893
+1894
+1895
+1896
+1897
+1898
+1899
+1900
+1901
+1902
+1903
+1904
+1905
+1906
+1907
+1908
+1909
+1910
+1911
+1912
+1913
+1914
+1915
+1916
+1917
+1918
+1919
+1920
+1921
+1922
+1923
+1924
+1925
+1926
+1927
+1928
+1929
+1930
+1931
+1932
+1933
+1934
+1935
+1936
+1937
+1938
+1939
+1940
+1941
+1942
+1943
+1944
+1945
+1946
+1947
+1948
+1949
+1950
+1951
+1952
+1953
+1954
+1955
+1956
+1957
+1958
+1959
+1960
+1961
+1962
+1963
+1964
+1965
+1966
+1967
+1968
+1969
+1970
+1971
+1972
+1973
+1974
+1975
+1976
+1977
+1978
+1979
+1980
+1981
+1982
+1983
+1984
+1985
+1986
+1987
+1988
+1989
+1990
+1991
+1992
+1993
+1994
+1995
+1996
+1997
+1998
+1999
+2000
+2001
+2002
+2003
+2004
+2005
+2006
+2007
+2008
+2009
+2010
+2011
+2012
+2013
+2014
+2015
+2016
+2017
+2018
+2019
+2020
+2021
+2022
+2023
+2024
+2025
+2026
+2027
+2028
+2029
+2030
+2031
+2032
+2033
+2034
+2035
+2036
+2037
+2038
+2039
+2040
+2041
+2042
+2043
+2044
+2045
+2046
+2047
+2048
+2049
+2050
+2051
+2052
+2053
+2054
+2055
+2056
+2057
+2058
+2059
+2060
+2061
+2062
+2063
+2064
+2065
+2066
+2067
+2068
+2069
+2070
+2071
+2072
+2073
+2074
+2075
+2076
+2077
+2078
+2079
+2080
+2081
+2082
+2083
+2084
+2085
+2086
+2087
+2088
+2089
+2090
+2091
+2092
+2093
+2094
+2095
+2096
+2097
+2098
+2099
+2100
+2101
+2102
+2103
+2104
+2105
+2106
+2107
+2108
+2109
+2110
+2111
+2112
+2113
+2114
+2115
+2116
+2117
+2118
+2119
+2120
+2121
+2122
+2123
+2124
+2125
+2126
+2127
+2128
+2129
+2130
+2131
+2132
+2133
+2134
+2135
+2136
+2137
+2138
+2139
+2140
+2141
+2142
+2143
+2144
+2145
+2146
+2147
+2148
+2149
+2150
+2151
+2152
+2153
+2154
+2155
+2156
+2157
+2158
+2159
+2160
+2161
+2162
+2163
+2164
+2165
+2166
+2167
+2168
+2169
+2170
+2171
+2172
+2173
+2174
+2175
+2176
+2177
+2178
+2179
+2180
+2181
+2182
+2183
+2184
+2185
+2186
+2187
+2188
+2189
+2190
+2191
+2192
+2193
+2194
+2195
+2196
+2197
+2198
+2199
+2200
+2201
+2202
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+use crate::defaults_merger::DefaultsMerger;
+use crate::error::FMLError::InvalidFeatureError;
+use crate::error::{did_you_mean, FMLError, Result};
+use crate::frontend::{AboutBlock, FeatureMetadata};
+use crate::util::loaders::FilePath;
+use anyhow::{bail, Error, Result as AnyhowResult};
+use serde::{Deserialize, Serialize};
+use serde_json::{Map, Value};
+use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
+use std::fmt::Display;
+
+#[derive(Eq, PartialEq, Hash, Debug, Clone)]
+pub enum TargetLanguage {
+    Kotlin,
+    Swift,
+    IR,
+    ExperimenterYAML,
+    ExperimenterJSON,
+}
+
+impl TargetLanguage {
+    pub fn extension(&self) -> &str {
+        match self {
+            TargetLanguage::Kotlin => "kt",
+            TargetLanguage::Swift => "swift",
+            TargetLanguage::IR => "fml.json",
+            TargetLanguage::ExperimenterJSON => "json",
+            TargetLanguage::ExperimenterYAML => "yaml",
+        }
+    }
+
+    pub fn from_extension(path: &str) -> AnyhowResult<TargetLanguage> {
+        if let Some((_, extension)) = path.rsplit_once('.') {
+            extension.try_into()
+        } else {
+            bail!("Unknown or unsupported target language: \"{}\"", path)
+        }
+    }
+}
+
+impl TryFrom<&str> for TargetLanguage {
+    type Error = Error;
+    fn try_from(value: &str) -> AnyhowResult<Self> {
+        Ok(match value.to_ascii_lowercase().as_str() {
+            "kotlin" | "kt" | "kts" => TargetLanguage::Kotlin,
+            "swift" => TargetLanguage::Swift,
+            "fml.json" => TargetLanguage::IR,
+            "yaml" => TargetLanguage::ExperimenterYAML,
+            "json" => TargetLanguage::ExperimenterJSON,
+            _ => bail!("Unknown or unsupported target language: \"{}\"", value),
+        })
+    }
+}
+
+/// The `TypeRef` enum defines a reference to a type.
+///
+/// Other types will be defined in terms of these enum values.
+///
+/// They represent the types available via the current `Variables` API—
+/// some primitives and structural types— and can be represented by
+/// Kotlin, Swift and JSON Schema.
+///
+#[non_exhaustive]
+#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Hash, Eq)]
+pub enum TypeRef {
+    // Current primitives.
+    String,
+    Int,
+    Boolean,
+
+    // Strings can be coerced into a few types.
+    // The types here will require the app's bundle or context to look up the final value.
+    // They will likely have
+    BundleText(StringId),
+    BundleImage(StringId),
+
+    Enum(String),
+    // JSON objects can represent a data class.
+    Object(String),
+
+    // JSON objects can also represent a `Map<String, V>` or a `Map` with
+    // keys that can be derived from a string.
+    StringMap(Box<TypeRef>),
+    // We can coerce the String keys into Enums, so this represents that.
+    EnumMap(Box<TypeRef>, Box<TypeRef>),
+
+    List(Box<TypeRef>),
+    Option(Box<TypeRef>),
+}
+
+impl Display for TypeRef {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            Self::String => f.write_str("String"),
+            Self::Int => f.write_str("Int"),
+            Self::Boolean => f.write_str("Boolean"),
+            Self::BundleImage(_) => f.write_str("Image"),
+            Self::BundleText(_) => f.write_str("Text"),
+            Self::Enum(v) => f.write_str(v),
+            Self::Object(v) => f.write_str(v),
+            Self::Option(v) => f.write_fmt(format_args!("Option<{v}>")),
+            Self::List(v) => f.write_fmt(format_args!("List<{v}>")),
+            Self::StringMap(v) => f.write_fmt(format_args!("Map<String, {v}>")),
+            Self::EnumMap(k, v) => f.write_fmt(format_args!("Map<{k}, {v}>")),
+        }
+    }
+}
+
+impl TypeRef {
+    pub(crate) fn supports_prefs(&self) -> bool {
+        match self {
+            Self::Boolean | Self::String | Self::Int | Self::BundleText(_) => true,
+            // There may be a chance that we can get Self::Option to work, but not at this time.
+            // This may be done by adding a branch to this match and adding a `preference_getter` to
+            // the `OptionalCodeType`.
+            _ => false,
+        }
+    }
+}
+
+/**
+ * An identifier derived from a `FilePath` of a top-level or importable FML file.
+ *
+ * An FML module is the conceptual FML file (and included FML files) that a single
+ * Kotlin or Swift file. It can be imported by other FML modules.
+ *
+ * It is somewhat distinct from the `FilePath` enum for three reasons:
+ *
+ * - a file path can specify a non-canonical representation of the path
+ * - a file path is difficult to serialize/deserialize
+ * - a module identifies the cluster of FML files that map to a single generated
+ * Kotlin or Swift file; this difference can be seen as: files can be included,
+ * modules can be imported.
+ */
+#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Hash, Clone, Serialize, Deserialize)]
+pub enum ModuleId {
+    Local(String),
+    Remote(String),
+}
+
+impl Default for ModuleId {
+    fn default() -> Self {
+        Self::Local("none".to_string())
+    }
+}
+
+impl TryFrom<&FilePath> for ModuleId {
+    type Error = FMLError;
+    fn try_from(path: &FilePath) -> Result<Self> {
+        Ok(match path {
+            FilePath::Local(p) => {
+                // We do this map_err here because the IO Error message that comes out of `canonicalize`
+                // doesn't include the problematic file path.
+                let p = p.canonicalize().map_err(|e| {
+                    FMLError::InvalidPath(format!("{}: {}", e, p.as_path().display()))
+                })?;
+                ModuleId::Local(p.display().to_string())
+            }
+            FilePath::Remote(u) => ModuleId::Remote(u.to_string()),
+        })
+    }
+}
+
+impl Display for ModuleId {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.write_str(match self {
+            ModuleId::Local(s) | ModuleId::Remote(s) => s,
+        })
+    }
+}
+
+pub trait TypeFinder {
+    fn all_types(&self) -> HashSet<TypeRef> {
+        let mut types = HashSet::new();
+        self.find_types(&mut types);
+        types
+    }
+
+    fn find_types(&self, types: &mut HashSet<TypeRef>);
+}
+
+impl TypeFinder for TypeRef {
+    fn find_types(&self, types: &mut HashSet<TypeRef>) {
+        if types.insert(self.clone()) {
+            match self {
+                TypeRef::List(v) | TypeRef::Option(v) | TypeRef::StringMap(v) => {
+                    v.find_types(types)
+                }
+                TypeRef::EnumMap(k, v) => {
+                    k.find_types(types);
+                    v.find_types(types);
+                }
+                _ => {}
+            }
+        }
+    }
+}
+
+pub(crate) type StringId = String;
+
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
+pub struct FeatureManifest {
+    #[serde(skip)]
+    pub(crate) id: ModuleId,
+
+    #[serde(skip_serializing_if = "Option::is_none")]
+    #[serde(default)]
+    pub(crate) channel: Option<String>,
+
+    #[serde(rename = "enums")]
+    #[serde(default)]
+    pub(crate) enum_defs: BTreeMap<String, EnumDef>,
+    #[serde(rename = "objects")]
+    #[serde(default)]
+    pub(crate) obj_defs: BTreeMap<String, ObjectDef>,
+    #[serde(rename = "features")]
+    pub(crate) feature_defs: BTreeMap<String, FeatureDef>,
+    #[serde(default)]
+    pub(crate) about: AboutBlock,
+
+    #[serde(default)]
+    pub(crate) imported_features: HashMap<ModuleId, BTreeSet<String>>,
+
+    #[serde(default)]
+    pub(crate) all_imports: HashMap<ModuleId, FeatureManifest>,
+}
+
+impl TypeFinder for FeatureManifest {
+    fn find_types(&self, types: &mut HashSet<TypeRef>) {
+        for e in self.enum_defs.values() {
+            e.find_types(types);
+        }
+        for o in self.iter_object_defs() {
+            o.find_types(types);
+        }
+        for f in self.iter_feature_defs() {
+            f.find_types(types);
+        }
+    }
+}
+
+#[cfg(test)]
+impl FeatureManifest {
+    pub(crate) fn add_feature(&mut self, feature: FeatureDef) {
+        self.feature_defs.insert(feature.name(), feature);
+    }
+}
+
+impl FeatureManifest {
+    pub(crate) fn new(
+        id: ModuleId,
+        channel: Option<&str>,
+        features: BTreeMap<String, FeatureDef>,
+        enums: BTreeMap<String, EnumDef>,
+        objects: BTreeMap<String, ObjectDef>,
+        about: AboutBlock,
+    ) -> Self {
+        Self {
+            id,
+            channel: channel.map(str::to_string),
+            about,
+            enum_defs: enums,
+            obj_defs: objects,
+            feature_defs: features,
+
+            ..Default::default()
+        }
+    }
+
+    #[allow(unused)]
+    pub(crate) fn validate_manifest_for_lang(&self, lang: &TargetLanguage) -> Result<()> {
+        if !&self.about.supports(lang) {
+            return Err(FMLError::ValidationError(
+                "about".to_string(),
+                format!(
+                    "Manifest file {file} is unable to generate {lang} files",
+                    file = &self.id,
+                    lang = &lang.extension(),
+                ),
+            ));
+        }
+        for child in self.all_imports.values() {
+            child.validate_manifest_for_lang(lang)?;
+        }
+        Ok(())
+    }
+
+    pub fn validate_manifest(&self) -> Result<()> {
+        // We first validate that each enum_def has a unique name.
+        // TODO: We repeat this check three times, it should be its
+        // own generic helper
+        let mut enum_names = HashSet::new();
+        self.validate_enum_defs(&mut enum_names)?;
+        // We then validate that each obj_defs also has a unique name.
+        let mut obj_names = HashSet::new();
+        self.validate_obj_defs(&mut obj_names)?;
+
+        // We then validate that each feature_def has a unique name.
+        let mut feature_names = HashSet::new();
+        self.validate_feature_defs(&mut feature_names)?;
+
+        // We then validate that each type_ref is valid
+        for feature_def in self.iter_feature_defs() {
+            for prop in &feature_def.props {
+                let path = format!("features/{}.{}", &feature_def.name, &prop.name);
+                Self::validate_type_ref(&path, &prop.typ, &enum_names, &obj_names)?;
+            }
+        }
+        self.validate_defaults()?;
+
+        // Validating the imported manifests.
+        // This is not only validating the well formed-ness of the imported manifests
+        // but also the defaults that are sent into the child manifests.
+        for child in self.all_imports.values() {
+            child.validate_manifest()?;
+        }
+        Ok(())
+    }
+
+    fn validate_type_ref(
+        path: &str,
+        type_ref: &TypeRef,
+        enum_names: &HashSet<String>,
+        obj_names: &HashSet<String>,
+    ) -> Result<()> {
+        match type_ref {
+            TypeRef::Enum(name) => {
+                if !enum_names.contains(name) {
+                    return Err(FMLError::ValidationError(
+                        path.to_string(),
+                        format!(
+                            "Found enum reference with name: {}, but no definition",
+                            name
+                        ),
+                    ));
+                }
+                Ok(())
+            }
+            TypeRef::Object(name) => {
+                if !obj_names.contains(name) {
+                    return Err(FMLError::ValidationError(
+                        path.to_string(),
+                        format!(
+                            "Found object reference with name: {}, but no definition",
+                            name
+                        ),
+                    ));
+                }
+                Ok(())
+            }
+            TypeRef::EnumMap(key_type, value_type) => {
+                if let TypeRef::Enum(_) = key_type.as_ref() {
+                    Self::validate_type_ref(path, key_type, enum_names, obj_names)?;
+                    Self::validate_type_ref(path, value_type, enum_names, obj_names)
+                } else {
+                    Err(FMLError::ValidationError(
+                        path.to_string(),
+                        format!("EnumMap key has be an enum, found: {:?}", key_type),
+                    ))
+                }
+            }
+            TypeRef::List(list_type) => {
+                Self::validate_type_ref(path, list_type, enum_names, obj_names)
+            }
+            TypeRef::StringMap(value_type) => {
+                Self::validate_type_ref(path, value_type, enum_names, obj_names)
+            }
+            TypeRef::Option(option_type) => {
+                if let TypeRef::Option(_) = option_type.as_ref() {
+                    Err(FMLError::ValidationError(
+                        path.to_string(),
+                        "Found nested optional types".into(),
+                    ))
+                } else {
+                    Self::validate_type_ref(path, option_type, enum_names, obj_names)
+                }
+            }
+            _ => Ok(()),
+        }
+    }
+
+    fn validate_enum_defs(&self, enum_names: &mut HashSet<String>) -> Result<()> {
+        for nm in self.enum_defs.keys() {
+            if !enum_names.insert(nm.clone()) {
+                return Err(FMLError::ValidationError(
+                    format!("enums/{}", nm),
+                    format!(
+                        "EnumDef names must be unique. Found two EnumDefs with the same name: {}",
+                        nm
+                    ),
+                ));
+            }
+        }
+        Ok(())
+    }
+
+    fn validate_obj_defs(&self, obj_names: &mut HashSet<String>) -> Result<()> {
+        for nm in self.obj_defs.keys() {
+            if !obj_names.insert(nm.clone()) {
+                return Err(FMLError::ValidationError(
+                    format!("objects/{}", nm),
+                    format!(
+                    "ObjectDef names must be unique. Found two ObjectDefs with the same name: {}",
+                    nm
+                ),
+                ));
+            }
+        }
+        Ok(())
+    }
+
+    fn validate_feature_defs(&self, feature_names: &mut HashSet<String>) -> Result<()> {
+        for feature_def in self.iter_feature_defs() {
+            if !feature_names.insert(feature_def.name.clone()) {
+                return Err(FMLError::ValidationError(
+                    feature_def.name(),
+                    format!(
+                    "FeatureDef names must be unique. Found two FeatureDefs with the same name: {}",
+                    feature_def.name
+                ),
+                ));
+            }
+            // while checking the feature, we also check that each prop is unique within a feature
+            let mut prop_names = HashSet::new();
+            self.validate_props(feature_def, &mut prop_names)?;
+        }
+        Ok(())
+    }
+
+    fn validate_props(
+        &self,
+        feature_def: &FeatureDef,
+        prop_names: &mut HashSet<String>,
+    ) -> Result<()> {
+        let path = format!("features/{}", &feature_def.name);
+        for prop in &feature_def.props {
+            if !prop_names.insert(prop.name.clone()) {
+                return Err(FMLError::ValidationError(
+                    format!("{}.{}", path, prop.name),
+                    format!(
+                    "PropDef names must be unique. Found two PropDefs with the same name: {} in the same feature_def: {}",
+                    prop.name, feature_def.name
+                )));
+            }
+        }
+        Ok(())
+    }
+
+    fn validate_defaults(&self) -> Result<()> {
+        for object in self.iter_object_defs() {
+            for prop in &object.props {
+                let path = format!("objects/{}.{}", object.name, prop.name);
+                self.validate_prop_defaults(&path, prop)?;
+            }
+        }
+        for feature in self.iter_feature_defs() {
+            self.validate_feature_structure(feature)?;
+            self.validate_feature_def(feature)?;
+        }
+        Ok(())
+    }
+
+    fn validate_feature_structure(&self, feature_def: &FeatureDef) -> Result<()> {
+        for v in &feature_def.props {
+            if v.pref_key.is_some() && !v.typ.supports_prefs() {
+                return Err(FMLError::ValidationError(
+                    format!("features/{}/{}", feature_def.name, v.name),
+                    "Pref keys can only be used with Boolean, String, Int and Text variables"
+                        .to_string(),
+                ));
+            }
+        }
+        Ok(())
+    }
+
+    fn validate_feature_def(&self, feature_def: &FeatureDef) -> Result<()> {
+        for prop in &feature_def.props {
+            let path = format!("features/{}.{}", feature_def.name, prop.name);
+            let literals = vec![prop.name.to_string()];
+            self.validate_default_by_typ(&path, &literals, &prop.typ, &prop.default)?;
+        }
+        Ok(())
+    }
+
+    fn validate_prop_defaults(&self, path: &str, prop: &PropDef) -> Result<()> {
+        let literals = Default::default();
+        self.validate_default_by_typ(path, &literals, &prop.typ, &prop.default)
+    }
+
+    pub fn validate_default_by_typ(
+        &self,
+        path: &str,
+        literals: &Vec<String>,
+        type_ref: &TypeRef,
+        default: &Value,
+    ) -> Result<()> {
+        let add_literals = |new: &[String]| -> Vec<String> {
+            let mut clone = literals.clone();
+            for s in new {
+                clone.push(s.clone());
+            }
+            clone
+        };
+        let add_literal = |new: String| -> Vec<String> {
+            let mut clone = literals.clone();
+            clone.push(new);
+            clone
+        };
+        match (type_ref, default) {
+            (TypeRef::Boolean, Value::Bool(_))
+            | (TypeRef::BundleImage(_), Value::String(_))
+            | (TypeRef::BundleText(_), Value::String(_))
+            | (TypeRef::String, Value::String(_))
+            | (TypeRef::Int, Value::Number(_))
+            | (TypeRef::Option(_), Value::Null) => Ok(()),
+            (TypeRef::Option(inner), v) => {
+                if let TypeRef::Option(_) = inner.as_ref() {
+                    return Err(FMLError::ValidationError(
+                        path.to_string(),
+                        "Nested options".into(),
+                    ));
+                }
+                self.validate_default_by_typ(path, literals, inner, v)
+            }
+            (TypeRef::Enum(enum_name), Value::String(s)) => {
+                let enum_def = self.find_enum(enum_name).ok_or_else(|| {
+                    FMLError::ValidationError(
+                        path.to_string(),
+                        format!("Type `{enum_name}` is not a type. Perhaps you need to declare an enum of that name.")
+                    )
+                })?;
+                let mut valid = HashSet::new();
+                for variant in enum_def.variants() {
+                    let name = variant.name();
+                    if *s == name {
+                        return Ok(());
+                    }
+                    valid.insert(name);
+                }
+                Err(FMLError::FeatureValidationError {
+                    path: path.to_string(),
+                    message: format!("\"{s}\" is not a valid {enum_name}{}", did_you_mean(valid)),
+                    literals: add_literal(format!("\"{s}\"")),
+                })
+            }
+            (TypeRef::EnumMap(enum_type, map_type), Value::Object(map)) => {
+                let enum_name = if let TypeRef::Enum(name) = enum_type.as_ref() {
+                    name.clone()
+                } else {
+                    unreachable!()
+                };
+                // We first validate that the keys of the map cover all all the enum variants, and no more or less
+                let enum_def = self.find_enum(&enum_name).ok_or_else(|| {
+                    FMLError::ValidationError(
+                        path.to_string(),
+                        format!("Type `{}` is not a type. Perhaps you need to declare an enum of that name.", enum_name)
+                    )
+                })?;
+                let mut seen = HashSet::new();
+                let mut unseen = HashSet::new();
+                let mut valid = HashSet::new();
+                for variant in enum_def.variants() {
+                    let nm = variant.name();
+                    valid.insert(nm.clone());
+
+                    let map_value = map.get(&nm);
+                    match (map_type.as_ref(), map_value) {
+                        (TypeRef::Option(_), None) => (),
+                        (_, None) => {
+                            unseen.insert(variant.name());
+                        }
+                        (_, Some(inner)) => {
+                            let path = format!("{path}[{}#{nm}]", enum_def.name);
+                            let literals = add_literals(&["{".to_string(), format!("\"{nm}\"")]);
+                            self.validate_default_by_typ(&path, &literals, map_type, inner)?;
+                            seen.insert(nm);
+                        }
+                    }
+                }
+
+                if !unseen.is_empty() {
+                    return Err(FMLError::FeatureValidationError {
+                        path: path.to_string(),
+                        message: format!("Enum map {enum_name} is missing values for {unseen:?}"),
+                        // Can we be more specific that just the opening brace?
+                        literals: add_literal("{".to_string()),
+                    });
+                }
+                for map_key in map.keys() {
+                    if !seen.contains(map_key) {
+                        return Err(FMLError::FeatureValidationError {
+                            path: path.to_string(),
+                            message: format!("Invalid key \"{map_key}\"{}", did_you_mean(valid)),
+                            literals: add_literals(&["{".to_string(), format!("\"{map_key}\"")]),
+                        });
+                    }
+                }
+                Ok(())
+            }
+            (TypeRef::StringMap(map_type), Value::Object(map)) => {
+                for (key, value) in map {
+                    let path = format!("{path}['{key}']");
+                    let literals = add_literals(&["{".to_string(), format!("\"{key}\"")]);
+                    self.validate_default_by_typ(&path, &literals, map_type, value)?;
+                }
+                Ok(())
+            }
+            (TypeRef::List(list_type), Value::Array(arr)) => {
+                let mut literals = add_literal("[".to_string());
+                for (index, value) in arr.iter().enumerate() {
+                    let path = format!("{path}['{index}']");
+                    self.validate_default_by_typ(&path, &literals, list_type, value)?;
+                    literals.push(",".to_string());
+                }
+                Ok(())
+            }
+            (TypeRef::Object(obj_name), Value::Object(map)) => {
+                let obj_def = self.find_object(obj_name).ok_or_else(|| {
+                    FMLError::ValidationError(
+                        path.to_string(),
+                        format!("Object {obj_name} is not defined in the manifest"),
+                    )
+                })?;
+                let mut valid = HashSet::new();
+                let mut unseen = HashSet::new();
+                let path = format!("{path}#{obj_name}");
+                for prop in &obj_def.props {
+                    // We only check the defaults overriding the property defaults
+                    // from the object's own property defaults.
+                    // We check the object property defaults previously.
+                    let nm = prop.name();
+                    if let Some(map_val) = map.get(&nm) {
+                        let path = format!("{path}.{}", prop.name);
+                        let literals =
+                            add_literals(&["{".to_string(), format!("\"{}\"", &prop.name)]);
+                        self.validate_default_by_typ(&path, &literals, &prop.typ, map_val)?;
+                    } else {
+                        unseen.insert(nm.clone());
+                    }
+
+                    valid.insert(nm);
+                }
+                for map_key in map.keys() {
+                    if !valid.contains(map_key) {
+                        return Err(FMLError::FeatureValidationError {
+                            path,
+                            message: format!(
+                                "Invalid key \"{map_key}\" for object {obj_name}{}",
+                                did_you_mean(valid)
+                            ),
+                            literals: add_literal(format!("\"{map_key}\"")),
+                        });
+                    }
+                }
+
+                Ok(())
+            }
+            _ => Err(FMLError::FeatureValidationError {
+                path: path.to_string(),
+                message: format!("Mismatch between type {type_ref:?} and default {default}"),
+                literals: add_literal(default.to_string()),
+            }),
+        }
+    }
+
+    pub fn iter_enum_defs(&self) -> impl Iterator<Item = &EnumDef> {
+        self.enum_defs.values()
+    }
+
+    pub fn iter_all_enum_defs(&self) -> impl Iterator<Item = (&FeatureManifest, &EnumDef)> {
+        let enums = self.iter_enum_defs().map(move |o| (self, o));
+        let imported: Vec<_> = self
+            .all_imports
+            .values()
+            .flat_map(|fm| fm.iter_all_enum_defs())
+            .collect();
+        enums.chain(imported)
+    }
+
+    pub fn iter_object_defs(&self) -> impl Iterator<Item = &ObjectDef> {
+        self.obj_defs.values()
+    }
+
+    pub fn iter_all_object_defs(&self) -> impl Iterator<Item = (&FeatureManifest, &ObjectDef)> {
+        let objects = self.iter_object_defs().map(move |o| (self, o));
+        let imported: Vec<_> = self
+            .all_imports
+            .values()
+            .flat_map(|fm| fm.iter_all_object_defs())
+            .collect();
+        objects.chain(imported)
+    }
+
+    pub fn iter_feature_defs(&self) -> impl Iterator<Item = &FeatureDef> {
+        self.feature_defs.values()
+    }
+
+    pub fn iter_all_feature_defs(&self) -> impl Iterator<Item = (&FeatureManifest, &FeatureDef)> {
+        let features = self.iter_feature_defs().map(move |f| (self, f));
+        let imported: Vec<_> = self
+            .all_imports
+            .values()
+            .flat_map(|fm| fm.iter_all_feature_defs())
+            .collect();
+        features.chain(imported)
+    }
+
+    #[allow(unused)]
+    pub(crate) fn iter_imported_files(&self) -> Vec<ImportedModule> {
+        let map = &self.all_imports;
+
+        self.imported_features
+            .iter()
+            .filter_map(|(id, features)| {
+                let fm = map.get(id).to_owned()?;
+                Some(ImportedModule::new(fm, features))
+            })
+            .collect()
+    }
+
+    pub fn find_object(&self, nm: &str) -> Option<&ObjectDef> {
+        self.obj_defs.get(nm)
+    }
+
+    pub fn find_enum(&self, nm: &str) -> Option<&EnumDef> {
+        self.enum_defs.get(nm)
+    }
+
+    pub fn get_feature(&self, nm: &str) -> Option<&FeatureDef> {
+        self.feature_defs.get(nm)
+    }
+
+    pub fn get_coenrolling_feature_ids(&self) -> Vec<String> {
+        self.iter_all_feature_defs()
+            .filter(|(_, f)| f.allow_coenrollment())
+            .map(|(_, f)| f.name())
+            .collect()
+    }
+
+    pub fn find_feature(&self, nm: &str) -> Option<(&FeatureManifest, &FeatureDef)> {
+        if let Some(f) = self.get_feature(nm) {
+            Some((self, f))
+        } else {
+            self.all_imports.values().find_map(|fm| fm.find_feature(nm))
+        }
+    }
+
+    pub fn find_import(&self, id: &ModuleId) -> Option<&FeatureManifest> {
+        self.all_imports.get(id)
+    }
+
+    pub fn default_json(&self) -> Value {
+        Value::Object(
+            self.iter_all_feature_defs()
+                .map(|(_, f)| (f.name(), f.default_json()))
+                .collect(),
+        )
+    }
+
+    /// This function is used to validate a new value for a feature. It accepts a feature name and
+    /// a feature value, and returns a Result containing a FeatureDef.
+    ///
+    /// If the value is invalid for the feature, it will return an Err result.
+    ///
+    /// If the value is valid for the feature, it will return an Ok result with a new FeatureDef
+    /// with the supplied feature value applied to the feature's property defaults.
+    pub fn validate_feature_config(
+        &self,
+        feature_name: &str,
+        feature_value: Value,
+    ) -> Result<FeatureDef> {
+        let (manifest, feature_def) = self
+            .find_feature(feature_name)
+            .ok_or_else(|| InvalidFeatureError(feature_name.to_string()))?;
+
+        let merger = DefaultsMerger::new(&manifest.obj_defs, Default::default(), None);
+
+        let mut feature_def = feature_def.clone();
+        merger.merge_feature_defaults(&mut feature_def, &Some(vec![feature_value.into()]))?;
+        manifest.validate_feature_def(&feature_def)?;
+        Ok(feature_def)
+    }
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
+pub struct FeatureDef {
+    pub(crate) name: String,
+    #[serde(flatten)]
+    pub(crate) metadata: FeatureMetadata,
+    pub(crate) props: Vec<PropDef>,
+    pub(crate) allow_coenrollment: bool,
+}
+
+impl FeatureDef {
+    pub fn new(name: &str, doc: &str, props: Vec<PropDef>, allow_coenrollment: bool) -> Self {
+        Self {
+            name: name.into(),
+            metadata: FeatureMetadata {
+                description: doc.into(),
+                ..Default::default()
+            },
+            props,
+            allow_coenrollment,
+        }
+    }
+    pub fn name(&self) -> String {
+        self.name.clone()
+    }
+    pub fn doc(&self) -> String {
+        self.metadata.description.clone()
+    }
+    pub fn props(&self) -> Vec<PropDef> {
+        self.props.clone()
+    }
+    pub fn allow_coenrollment(&self) -> bool {
+        self.allow_coenrollment
+    }
+
+    pub fn default_json(&self) -> Value {
+        let mut props = Map::new();
+
+        for prop in self.props().iter() {
+            props.insert(prop.name(), prop.default());
+        }
+
+        Value::Object(props)
+    }
+
+    pub fn has_prefs(&self) -> bool {
+        self.props.iter().any(|p| p.has_prefs())
+    }
+}
+impl TypeFinder for FeatureDef {
+    fn find_types(&self, types: &mut HashSet<TypeRef>) {
+        for p in self.props() {
+            p.find_types(types);
+        }
+    }
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
+pub struct EnumDef {
+    pub name: String,
+    pub doc: String,
+    pub variants: Vec<VariantDef>,
+}
+
+impl EnumDef {
+    pub fn name(&self) -> String {
+        self.name.clone()
+    }
+    pub fn doc(&self) -> String {
+        self.doc.clone()
+    }
+    pub fn variants(&self) -> Vec<VariantDef> {
+        self.variants.clone()
+    }
+}
+
+impl TypeFinder for EnumDef {
+    fn find_types(&self, types: &mut HashSet<TypeRef>) {
+        types.insert(TypeRef::Enum(self.name()));
+    }
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
+pub struct FromStringDef {
+    pub name: String,
+    pub doc: String,
+    pub variants: Vec<VariantDef>,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
+pub struct VariantDef {
+    pub(crate) name: String,
+    pub(crate) doc: String,
+}
+impl VariantDef {
+    pub fn new(name: &str, doc: &str) -> Self {
+        Self {
+            name: name.into(),
+            doc: doc.into(),
+        }
+    }
+    pub fn name(&self) -> String {
+        self.name.clone()
+    }
+    pub fn doc(&self) -> String {
+        self.doc.clone()
+    }
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
+pub struct ObjectDef {
+    pub(crate) name: String,
+    pub(crate) doc: String,
+    pub(crate) props: Vec<PropDef>,
+}
+
+#[allow(unused)]
+impl ObjectDef {
+    pub fn new(name: &str, doc: &str, props: Vec<PropDef>) -> Self {
+        Self {
+            name: name.into(),
+            doc: doc.into(),
+            props,
+        }
+    }
+    pub(crate) fn name(&self) -> String {
+        self.name.clone()
+    }
+    pub(crate) fn doc(&self) -> String {
+        self.doc.clone()
+    }
+    pub fn props(&self) -> Vec<PropDef> {
+        self.props.clone()
+    }
+
+    pub(crate) fn find_prop(&self, nm: &str) -> PropDef {
+        self.props
+            .iter()
+            .find(|p| p.name == nm)
+            .unwrap_or_else(|| unreachable!("Can't find {}. This is a bug in FML", nm))
+            .clone()
+    }
+}
+impl TypeFinder for ObjectDef {
+    fn find_types(&self, types: &mut HashSet<TypeRef>) {
+        for p in self.props() {
+            p.find_types(types);
+        }
+    }
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
+pub struct PropDef {
+    pub(crate) name: String,
+    pub(crate) doc: String,
+    #[serde(rename = "type")]
+    pub(crate) typ: TypeRef,
+    pub(crate) default: Literal,
+    #[serde(default)]
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub(crate) pref_key: Option<String>,
+}
+
+impl PropDef {
+    pub fn name(&self) -> String {
+        self.name.clone()
+    }
+    pub fn doc(&self) -> String {
+        self.doc.clone()
+    }
+    pub fn typ(&self) -> TypeRef {
+        self.typ.clone()
+    }
+    pub fn default(&self) -> Literal {
+        self.default.clone()
+    }
+    pub fn has_prefs(&self) -> bool {
+        self.pref_key.is_some() && self.typ.supports_prefs()
+    }
+    pub fn pref_key(&self) -> Option<String> {
+        self.pref_key.clone()
+    }
+}
+
+impl TypeFinder for PropDef {
+    fn find_types(&self, types: &mut HashSet<TypeRef>) {
+        types.insert(self.typ());
+    }
+}
+
+pub type Literal = Value;
+
+#[derive(Debug, Clone)]
+pub(crate) struct ImportedModule<'a> {
+    pub(crate) fm: &'a FeatureManifest,
+    features: &'a BTreeSet<String>,
+}
+
+impl<'a> ImportedModule<'a> {
+    pub(crate) fn new(fm: &'a FeatureManifest, features: &'a BTreeSet<String>) -> Self {
+        Self { fm, features }
+    }
+
+    pub(crate) fn about(&self) -> &AboutBlock {
+        &self.fm.about
+    }
+
+    pub(crate) fn features(&self) -> Vec<&'a FeatureDef> {
+        let fm = self.fm;
+        self.features
+            .iter()
+            .filter_map(|f| fm.get_feature(f))
+            .collect()
+    }
+}
+
+#[cfg(test)]
+pub mod unit_tests {
+    use serde_json::{json, Number};
+
+    use super::*;
+    use crate::error::Result;
+    use crate::fixtures::intermediate_representation::get_simple_homescreen_feature;
+
+    impl PropDef {
+        pub(crate) fn new(nm: &str, typ: TypeRef, default: Value) -> Self {
+            PropDef {
+                name: nm.into(),
+                doc: format!("{nm} property of type {typ}"),
+                typ,
+                default,
+                pref_key: None,
+            }
+        }
+
+        pub(crate) fn new_with_doc(nm: &str, doc: &str, typ: TypeRef, default: Value) -> Self {
+            PropDef {
+                name: nm.into(),
+                doc: doc.into(),
+                typ,
+                default,
+                pref_key: None,
+            }
+        }
+    }
+
+    #[test]
+    fn can_ir_represent_smoke_test() -> Result<()> {
+        let reference_manifest = get_simple_homescreen_feature();
+        let json_string = serde_json::to_string(&reference_manifest)?;
+        let manifest_from_json: FeatureManifest = serde_json::from_str(&json_string)?;
+
+        assert_eq!(reference_manifest, manifest_from_json);
+
+        Ok(())
+    }
+
+    #[test]
+    fn validate_good_feature_manifest() -> Result<()> {
+        let fm = get_simple_homescreen_feature();
+        fm.validate_manifest()
+    }
+
+    #[test]
+    fn validate_duplicate_enum_defs_fail() -> Result<()> {
+        let mut fm = get_simple_homescreen_feature();
+        fm.enum_defs.insert(
+            "HomeScreenSection".into(),
+            EnumDef {
+                name: "HomeScreenSection".into(),
+                doc: "The sections of the homescreen".into(),
+                variants: vec![
+                    VariantDef::new("top-sites", "The original frecency sorted sites"),
+                    VariantDef::new("jump-back-in", "Jump back in section"),
+                    VariantDef::new("recently-saved", "Tabs that have been bookmarked recently"),
+                ],
+            },
+        );
+        fm.validate_manifest()
+            .expect_err("Should fail on duplicate enum_defs");
+        Ok(())
+    }
+
+    #[test]
+    fn validate_allow_coenrollment() -> Result<()> {
+        let mut fm = get_simple_homescreen_feature();
+        fm.add_feature(FeatureDef::new(
+            "some_def",
+            "my lovely qtest doc",
+            vec![PropDef::new("some prop", TypeRef::String, json!("default"))],
+            true,
+        ));
+        fm.validate_manifest()?;
+        let coenrolling_ids = fm.get_coenrolling_feature_ids();
+        assert_eq!(coenrolling_ids, vec!["some_def".to_string()]);
+
+        Ok(())
+    }
+
+    #[test]
+    fn validate_duplicate_feature_defs_fails() -> Result<()> {
+        let mut fm = get_simple_homescreen_feature();
+        fm.add_feature(FeatureDef::new(
+            "homescreen",
+            "Represents the homescreen feature",
+            vec![PropDef::new(
+                "sections-enabled",
+                TypeRef::EnumMap(
+                    Box::new(TypeRef::Enum("SectionId".into())),
+                    Box::new(TypeRef::String),
+                ),
+                json!({
+                    "top-sites": true,
+                    "jump-back-in": false,
+                    "recently-saved": false,
+                }),
+            )],
+            false,
+        ));
+        fm.validate_manifest()
+            .expect_err("Should fail on duplicate feature defs");
+        Ok(())
+    }
+
+    #[test]
+    fn validate_duplicate_props_in_same_feature_fails() -> Result<()> {
+        let mut fm = get_simple_homescreen_feature();
+        fm.add_feature(FeatureDef::new(
+            "otherhomescreen",
+            "Represents the homescreen feature",
+            vec![
+                PropDef::new(
+                    "duplicate-prop",
+                    TypeRef::EnumMap(
+                        Box::new(TypeRef::Enum("SectionId".into())),
+                        Box::new(TypeRef::String),
+                    ),
+                    json!({
+                        "top-sites": true,
+                        "jump-back-in": false,
+                        "recently-saved": false,
+                    }),
+                ),
+                PropDef::new(
+                    "duplicate-prop",
+                    TypeRef::EnumMap(
+                        Box::new(TypeRef::Enum("SectionId".into())),
+                        Box::new(TypeRef::String),
+                    ),
+                    json!({
+                        "top-sites": true,
+                        "jump-back-in": false,
+                        "recently-saved": false,
+                    }),
+                ),
+            ],
+            false,
+        ));
+        fm.validate_manifest()
+            .expect_err("Should fail on duplicate props in the same feature");
+        Ok(())
+    }
+
+    #[test]
+    fn validate_enum_type_ref_doesnt_match_def() -> Result<()> {
+        let mut fm = get_simple_homescreen_feature();
+        fm.add_feature(FeatureDef::new(
+            "some_def",
+            "test doc",
+            vec![PropDef::new(
+                "prop name",
+                TypeRef::Enum("EnumDoesntExist".into()),
+                json!(null),
+            )],
+            false,
+        ));
+        fm.validate_manifest().expect_err(
+            "Should fail since EnumDoesntExist isn't a an enum defined in the manifest",
+        );
+        Ok(())
+    }
+
+    #[test]
+    fn validate_obj_type_ref_doesnt_match_def() -> Result<()> {
+        let mut fm = get_simple_homescreen_feature();
+        fm.add_feature(FeatureDef::new(
+            "some_def",
+            "test doc",
+            vec![PropDef::new(
+                "prop name",
+                TypeRef::Object("ObjDoesntExist".into()),
+                json!(null),
+            )],
+            false,
+        ));
+        fm.validate_manifest().expect_err(
+            "Should fail since ObjDoesntExist isn't a an Object defined in the manifest",
+        );
+        Ok(())
+    }
+
+    #[test]
+    fn validate_enum_map_with_non_enum_key() -> Result<()> {
+        let mut fm = get_simple_homescreen_feature();
+        fm.add_feature(FeatureDef::new(
+            "some_def",
+            "test doc",
+            vec![PropDef::new(
+                "prop name",
+                TypeRef::EnumMap(Box::new(TypeRef::String), Box::new(TypeRef::String)),
+                json!(null),
+            )],
+            false,
+        ));
+        fm.validate_manifest()
+            .expect_err("Should fail since the key on an EnumMap must be an Enum");
+        Ok(())
+    }
+
+    #[test]
+    fn validate_list_with_enum_with_no_def() -> Result<()> {
+        let mut fm = get_simple_homescreen_feature();
+        fm.add_feature(FeatureDef::new(
+            "some_def",
+            "test doc",
+            vec![PropDef::new(
+                "prop name",
+                TypeRef::List(Box::new(TypeRef::Enum("EnumDoesntExist".into()))),
+                json!(null),
+            )],
+            false,
+        ));
+        fm.validate_manifest()
+            .expect_err("Should fail EnumDoesntExist isn't a an enum defined in the manifest");
+        Ok(())
+    }
+
+    #[test]
+    fn validate_enum_map_with_enum_with_no_def() -> Result<()> {
+        let mut fm = get_simple_homescreen_feature();
+        fm.add_feature(FeatureDef::new(
+            "some_def",
+            "test doc",
+            vec![PropDef::new(
+                "prop name",
+                TypeRef::EnumMap(
+                    Box::new(TypeRef::Enum("EnumDoesntExist".into())),
+                    Box::new(TypeRef::String),
+                ),
+                json!(null),
+            )],
+            false,
+        ));
+        fm.validate_manifest().expect_err(
+            "Should fail since EnumDoesntExist isn't a an enum defined in the manifest",
+        );
+        Ok(())
+    }
+
+    #[test]
+    fn validate_enum_map_with_obj_value_no_def() -> Result<()> {
+        let mut fm = get_simple_homescreen_feature();
+        fm.add_feature(FeatureDef::new(
+            "some_def",
+            "test doc",
+            vec![PropDef::new(
+                "prop name",
+                TypeRef::EnumMap(
+                    Box::new(TypeRef::Enum("SectionId".into())),
+                    Box::new(TypeRef::Object("ObjDoesntExist".into())),
+                ),
+                json!(null),
+            )],
+            false,
+        ));
+        fm.validate_manifest()
+            .expect_err("Should fail since ObjDoesntExist isn't an Object defined in the manifest");
+        Ok(())
+    }
+
+    #[test]
+    fn validate_string_map_with_enum_value_no_def() -> Result<()> {
+        let mut fm = get_simple_homescreen_feature();
+        fm.add_feature(FeatureDef::new(
+            "some_def",
+            "test doc",
+            vec![PropDef::new(
+                "prop name",
+                TypeRef::StringMap(Box::new(TypeRef::Enum("EnumDoesntExist".into()))),
+                json!(null),
+            )],
+            false,
+        ));
+        fm.validate_manifest()
+            .expect_err("Should fail since ObjDoesntExist isn't an Object defined in the manifest");
+        Ok(())
+    }
+
+    #[test]
+    fn validate_nested_optionals_fail() -> Result<()> {
+        let mut fm = get_simple_homescreen_feature();
+        fm.add_feature(FeatureDef::new(
+            "some_def",
+            "test doc",
+            vec![PropDef::new(
+                "prop name",
+                TypeRef::Option(Box::new(TypeRef::Option(Box::new(TypeRef::String)))),
+                json!(null),
+            )],
+            false,
+        ));
+        fm.validate_manifest()
+            .expect_err("Should fail since we can't have nested optionals");
+        Ok(())
+    }
+
+    pub fn get_feature_manifest(
+        obj_defs: Vec<ObjectDef>,
+        enum_defs: Vec<EnumDef>,
+        feature_defs: Vec<FeatureDef>,
+        all_imports: HashMap<ModuleId, FeatureManifest>,
+    ) -> FeatureManifest {
+        FeatureManifest {
+            enum_defs: map_from(enum_defs, |e| e.name()),
+            obj_defs: map_from(obj_defs, |o| o.name()),
+            feature_defs: map_from(feature_defs, |f| f.name()),
+            all_imports,
+            ..Default::default()
+        }
+    }
+
+    fn get_one_prop_feature_manifest(
+        obj_defs: Vec<ObjectDef>,
+        enum_defs: Vec<EnumDef>,
+        prop: &PropDef,
+    ) -> FeatureManifest {
+        FeatureManifest {
+            enum_defs: map_from(enum_defs, |e| e.name()),
+            obj_defs: map_from(obj_defs, |o| o.name()),
+            feature_defs: BTreeMap::from([(
+                "".to_string(),
+                FeatureDef {
+                    props: vec![prop.clone()],
+                    ..Default::default()
+                },
+            )]),
+            ..Default::default()
+        }
+    }
+
+    fn get_one_prop_feature_manifest_with_imports(
+        obj_defs: Vec<ObjectDef>,
+        enum_defs: Vec<EnumDef>,
+        prop: &PropDef,
+        all_imports: HashMap<ModuleId, FeatureManifest>,
+    ) -> FeatureManifest {
+        let mut fm = FeatureManifest {
+            enum_defs: map_from(enum_defs, |e| e.name()),
+            obj_defs: map_from(obj_defs, |o| o.name()),
+            all_imports,
+            ..Default::default()
+        };
+        fm.add_feature(FeatureDef {
+            props: vec![prop.clone()],
+            ..Default::default()
+        });
+        fm
+    }
+
+    fn map_from<T, F, K>(list: Vec<T>, key: F) -> BTreeMap<K, T>
+    where
+        K: Ord,
+        F: Fn(&T) -> K,
+    {
+        let mut res: BTreeMap<K, T> = Default::default();
+
+        for t in list {
+            let k = key(&t);
+            res.insert(k, t);
+        }
+
+        res
+    }
+
+    #[test]
+    fn test_validate_prop_defaults_string() -> Result<()> {
+        let mut prop = PropDef::new("key", TypeRef::String, json!("default!"));
+        let fm = get_one_prop_feature_manifest(vec![], vec![], &prop);
+        let path = format!("test_validate_prop_defaults_string.{}", prop.name);
+        fm.validate_prop_defaults(&path, &prop)?;
+        prop.default = json!(100);
+        let fm = get_one_prop_feature_manifest(vec![], vec![], &prop);
+        fm.validate_prop_defaults(&path, &prop)
+            .expect_err("Should error out, default is number when it should be string");
+        Ok(())
+    }
+
+    #[test]
+    fn test_validate_prop_defaults_int() -> Result<()> {
+        let mut prop = PropDef::new("key", TypeRef::Int, json!(100));
+        let fm = get_one_prop_feature_manifest(vec![], vec![], &prop);
+        let path = format!("test_validate_prop_defaults_int.{}", prop.name);
+        fm.validate_prop_defaults(&path, &prop)?;
+        prop.default = json!("100");
+        let fm = get_one_prop_feature_manifest(vec![], vec![], &prop);
+        fm.validate_prop_defaults(&path, &prop)
+            .expect_err("Should error out, default is string when it should be number");
+        Ok(())
+    }
+
+    #[test]
+    fn test_validate_prop_defaults_bool() -> Result<()> {
+        let mut prop = PropDef::new("key", TypeRef::Boolean, json!(true));
+        let fm = get_one_prop_feature_manifest(vec![], vec![], &prop);
+        let path = format!("test_validate_prop_defaults_bool.{}", prop.name);
+        fm.validate_prop_defaults(&path, &prop)?;
+        prop.default = json!("100");
+        let fm = get_one_prop_feature_manifest(vec![], vec![], &prop);
+        fm.validate_prop_defaults(&path, &prop)
+            .expect_err("Should error out, default is string when it should be a boolean");
+        Ok(())
+    }
+
+    #[test]
+    fn test_validate_prop_defaults_bundle_image() -> Result<()> {
+        let mut prop = PropDef::new(
+            "key",
+            TypeRef::BundleImage("Icon".into()),
+            json!("IconBlue"),
+        );
+        let fm = get_one_prop_feature_manifest(vec![], vec![], &prop);
+        let path = format!("test_validate_prop_defaults_bundle_image.{}", prop.name);
+        fm.validate_prop_defaults(&path, &prop)?;
+        prop.default = json!(100);
+        let fm = get_one_prop_feature_manifest(vec![], vec![], &prop);
+        fm.validate_prop_defaults(&path, &prop).expect_err(
+            "Should error out, default is number when it should be a string (bundleImage string)",
+        );
+        Ok(())
+    }
+
+    #[test]
+    fn test_validate_prop_defaults_bundle_text() -> Result<()> {
+        let mut prop = PropDef::new(
+            "key",
+            TypeRef::BundleText("Text".into()),
+            json!("BundledText"),
+        );
+        let fm = get_one_prop_feature_manifest(vec![], vec![], &prop);
+        let path = format!("test_validate_prop_defaults_bundle_text.{}", prop.name);
+        fm.validate_prop_defaults(&path, &prop)?;
+        prop.default = json!(100);
+        let fm = get_one_prop_feature_manifest(vec![], vec![], &prop);
+        fm.validate_prop_defaults(&path, &prop).expect_err(
+            "Should error out, default is number when it should be a string (bundleText string)",
+        );
+        Ok(())
+    }
+
+    #[test]
+    fn test_validate_prop_defaults_option_null() -> Result<()> {
+        let mut prop = PropDef::new(
+            "key",
+            TypeRef::Option(Box::new(TypeRef::Boolean)),
+            json!(null),
+        );
+        let fm = get_one_prop_feature_manifest(vec![], vec![], &prop);
+        let path = format!("test_validate_prop_defaults_option_null.{}", prop.name);
+        fm.validate_prop_defaults(&path, &prop)?;
+        prop.default = json!(100);
+        let fm = get_one_prop_feature_manifest(vec![], vec![], &prop);
+        fm.validate_prop_defaults(&path, &prop).expect_err(
+            "Should error out, default is number when it should be a boolean (Optional boolean)",
+        );
+        Ok(())
+    }
+
+    #[test]
+    fn test_validate_prop_defaults_nested_options() -> Result<()> {
+        let prop = PropDef::new(
+            "key",
+            TypeRef::Option(Box::new(TypeRef::Option(Box::new(TypeRef::Boolean)))),
+            json!(true),
+        );
+        let fm = get_one_prop_feature_manifest(vec![], vec![], &prop);
+        let path = format!("test_validate_prop_defaults_nested_options.{}", prop.name);
+        fm.validate_prop_defaults(&path, &prop)
+            .expect_err("Should error out since we have a nested option");
+        Ok(())
+    }
+
+    #[test]
+    fn test_validate_prop_defaults_option_non_null() -> Result<()> {
+        let mut prop = PropDef::new(
+            "key",
+            TypeRef::Option(Box::new(TypeRef::Boolean)),
+            json!(true),
+        );
+        let fm = get_one_prop_feature_manifest(vec![], vec![], &prop);
+        let path = format!("test_validate_prop_defaults_option_non_null.{}", prop.name);
+        fm.validate_prop_defaults(&path, &prop)?;
+        prop.default = json!(100);
+        let fm = get_one_prop_feature_manifest(vec![], vec![], &prop);
+        fm.validate_prop_defaults(&path, &prop).expect_err(
+            "Should error out, default is number when it should be a boolean (Optional boolean)",
+        );
+        Ok(())
+    }
+
+    #[test]
+    fn test_validate_prop_defaults_enum() -> Result<()> {
+        let mut prop = PropDef::new("key", TypeRef::Enum("ButtonColor".into()), json!("blue"));
+        let enum_defs = vec![EnumDef {
+            name: "ButtonColor".into(),
+            variants: vec![
+                VariantDef {
+                    name: "blue".into(),
+                    ..Default::default()
+                },
+                VariantDef {
+                    name: "green".into(),
+                    ..Default::default()
+                },
+            ],
+            ..Default::default()
+        }];
+        let fm = get_one_prop_feature_manifest(vec![], enum_defs.clone(), &prop);
+        let path = format!("test_validate_prop_defaults_enum.{}", prop.name);
+        fm.validate_prop_defaults(&path, &prop)?;
+        prop.default = json!("green");
+        let fm = get_one_prop_feature_manifest(vec![], enum_defs.clone(), &prop);
+        fm.validate_prop_defaults(&path, &prop)?;
+        prop.default = json!("not a valid color");
+        let fm = get_one_prop_feature_manifest(vec![], enum_defs.clone(), &prop);
+        fm.validate_prop_defaults(&path, &prop)
+            .expect_err("Should error out since default is not a valid enum variant");
+        prop.default = json!("blue");
+        prop.typ = TypeRef::Enum("DoesntExist".into());
+        let fm = get_one_prop_feature_manifest(vec![], enum_defs, &prop);
+        fm.validate_prop_defaults(&path, &prop)
+            .expect_err("Should error out since the enum definition doesn't exist for the TypeRef");
+        Ok(())
+    }
+
+    #[test]
+    fn test_validate_prop_defaults_enum_map() -> Result<()> {
+        let mut prop = PropDef::new(
+            "key",
+            TypeRef::EnumMap(
+                Box::new(TypeRef::Enum("ButtonColor".into())),
+                Box::new(TypeRef::Int),
+            ),
+            json!({
+                "blue": 1,
+                "green": 22,
+            }),
+        );
+        let enum_defs = vec![EnumDef {
+            name: "ButtonColor".into(),
+            variants: vec![
+                VariantDef {
+                    name: "blue".into(),
+                    ..Default::default()
+                },
+                VariantDef {
+                    name: "green".into(),
+                    ..Default::default()
+                },
+            ],
+            ..Default::default()
+        }];
+        let fm = get_one_prop_feature_manifest(vec![], enum_defs.clone(), &prop);
+        let path = format!("test_validate_prop_defaults_enum_map.{}", prop.name);
+        fm.validate_prop_defaults(&path, &prop)?;
+        prop.default = json!({
+            "blue": 1,
+        });
+        let fm = get_one_prop_feature_manifest(vec![], enum_defs.clone(), &prop);
+        fm.validate_prop_defaults(&path, &prop)
+            .expect_err("Should error out because the enum map is missing the green key");
+        prop.default = json!({
+            "blue": 1,
+            "green": 22,
+            "red": 3,
+        });
+        let fm = get_one_prop_feature_manifest(vec![], enum_defs, &prop);
+        fm.validate_prop_defaults(&path, &prop).expect_err("Should error out because the default includes an extra key that is not a variant of the enum (red)");
+        Ok(())
+    }
+
+    #[test]
+    fn test_validate_prop_defaults_string_map() -> Result<()> {
+        let mut prop = PropDef::new(
+            "key",
+            TypeRef::StringMap(Box::new(TypeRef::Int)),
+            json!({
+                "blue": 1,
+                "green": 22,
+            }),
+        );
+        let path = format!("test_validate_prop_defaults_string_map.{}", &prop.name);
+        let fm = get_one_prop_feature_manifest(vec![], vec![], &prop);
+        fm.validate_prop_defaults(&path, &prop)?;
+        prop.default = json!({
+            "blue": 1,
+        });
+        let fm = get_one_prop_feature_manifest(vec![], vec![], &prop);
+        fm.validate_prop_defaults(&path, &prop)?;
+        prop.default = json!({
+            "blue": 1,
+            "green": 22,
+            "red": 3,
+            "white": "AHA not a number"
+        });
+        let fm = get_one_prop_feature_manifest(vec![], vec![], &prop);
+        fm.validate_prop_defaults(&path, &prop).expect_err("Should error out because the string map includes a value that is not an int as defined by the TypeRef");
+        Ok(())
+    }
+
+    #[test]
+    fn test_validate_prop_defaults_list() -> Result<()> {
+        let mut prop = PropDef::new(
+            "key",
+            TypeRef::List(Box::new(TypeRef::Int)),
+            json!([1, 3, 100]),
+        );
+        let path = format!("test_validate_prop_defaults_list.{}", &prop.name);
+        let fm = get_one_prop_feature_manifest(vec![], vec![], &prop);
+        fm.validate_prop_defaults(&path, &prop)?;
+        prop.default = json!([1, 2, "oops"]);
+        let fm = get_one_prop_feature_manifest(vec![], vec![], &prop);
+        fm.validate_prop_defaults(&path, &prop)
+            .expect_err("Should error out because one of the values in the array is not an int");
+        Ok(())
+    }
+
+    #[test]
+    fn test_validate_prop_defaults_object() -> Result<()> {
+        let mut prop = PropDef::new(
+            "key",
+            TypeRef::Object("SampleObj".into()),
+            json!({
+                "int": 1,
+                "string": "bobo",
+                "enum": "green",
+                "list": [true, false, true],
+                "nestedObj": {
+                    "enumMap": {
+                        "blue": 1,
+                        "green": 2,
+                    }
+                },
+                "optional": 2,
+            }),
+        );
+        let obj_defs = vec![
+            ObjectDef {
+                name: "SampleObj".into(),
+                props: vec![
+                    PropDef::new("int", TypeRef::Int, json!(1)),
+                    PropDef::new("string", TypeRef::String, json!("a string")),
+                    PropDef::new("enum", TypeRef::Enum("ButtonColor".into()), json!("blue")),
+                    PropDef::new(
+                        "list",
+                        TypeRef::List(Box::new(TypeRef::Boolean)),
+                        json!([true, false]),
+                    ),
+                    PropDef::new(
+                        "optional",
+                        TypeRef::Option(Box::new(TypeRef::Int)),
+                        json!(null),
+                    ),
+                    PropDef::new(
+                        "nestedObj",
+                        TypeRef::Object("NestedObject".into()),
+                        json!({
+                            "enumMap": {
+                                "blue": 1,
+                            },
+                        }),
+                    ),
+                ],
+                ..Default::default()
+            },
+            ObjectDef {
+                name: "NestedObject".into(),
+                props: vec![PropDef::new(
+                    "enumMap",
+                    TypeRef::EnumMap(
+                        Box::new(TypeRef::Enum("ButtonColor".into())),
+                        Box::new(TypeRef::Int),
+                    ),
+                    json!({
+                        "blue": 4,
+                        "green": 2,
+                    }),
+                )],
+                ..Default::default()
+            },
+        ];
+        let enum_defs = vec![EnumDef {
+            name: "ButtonColor".into(),
+            variants: vec![
+                VariantDef {
+                    name: "blue".into(),
+                    ..Default::default()
+                },
+                VariantDef {
+                    name: "green".into(),
+                    ..Default::default()
+                },
+            ],
+            ..Default::default()
+        }];
+        let path = format!("test_validate_prop_defaults_object.{}", &prop.name);
+        let fm = get_one_prop_feature_manifest(obj_defs.clone(), enum_defs.clone(), &prop);
+        fm.validate_prop_defaults(&path, &prop)?;
+        prop.default = json!({
+            "int": 1,
+            "string": "bobo",
+            "enum": "green",
+            "list": [true, false, true],
+            "nestedObj": {
+                "enumMap": {
+                    "blue": 1,
+                    "green": "Wrong type!"
+                }
+            }
+        });
+        let fm = get_one_prop_feature_manifest(obj_defs.clone(), enum_defs.clone(), &prop);
+        fm.validate_prop_defaults(&path, &prop).expect_err(
+            "Should error out because the nested object has an enumMap with the wrong type",
+        );
+        prop.default = json!({
+            "int": 1,
+            "string": "bobo",
+            "enum": "green",
+            "list": [true, false, true],
+            "nestedObj": {
+                "enumMap": {
+                    "blue": 1,
+                    "green": 2,
+                }
+            },
+            "optional": 3,
+            "extra-property": 2
+        });
+        let fm = get_one_prop_feature_manifest(obj_defs.clone(), enum_defs.clone(), &prop);
+        fm.validate_prop_defaults(&path, &prop)
+            .expect_err("Should error out because the object has an extra property");
+
+        // This test is missing a `list` property. But that's ok, because we'll get it from the object definition.
+        prop.default = json!({
+            "int": 1,
+            "string": "bobo",
+            "enum": "green",
+            "nestedObj": {
+                "enumMap": {
+                    "blue": 1,
+                    "green": 2,
+                }
+            },
+            "optional": 2,
+        });
+        let fm = get_one_prop_feature_manifest(obj_defs.clone(), enum_defs.clone(), &prop);
+
+        fm.validate_prop_defaults(&path, &prop)?;
+
+        prop.default = json!({
+            "int": 1,
+            "string": "bobo",
+            "enum": "green",
+            "list": [true, false, true],
+            "nestedObj": {
+                "enumMap": {
+                    "blue": 1,
+                    "green": 2,
+                }
+            },
+        });
+        let fm = get_one_prop_feature_manifest(obj_defs, enum_defs, &prop);
+        // OK, because we are missing `optional` which is optional anyways
+        fm.validate_prop_defaults(&path, &prop)?;
+        Ok(())
+    }
+
+    #[test]
+    fn test_validate_prop_defaults_enum_map_optional() -> Result<()> {
+        let prop = PropDef::new(
+            "key",
+            TypeRef::EnumMap(
+                Box::new(TypeRef::Enum("ButtonColor".into())),
+                Box::new(TypeRef::Option(Box::new(TypeRef::Int))),
+            ),
+            json!({
+                "blue": 1,
+            }),
+        );
+        let enum_defs = vec![EnumDef {
+            name: "ButtonColor".into(),
+            variants: vec![
+                VariantDef {
+                    name: "blue".into(),
+                    ..Default::default()
+                },
+                VariantDef {
+                    name: "green".into(),
+                    ..Default::default()
+                },
+            ],
+            ..Default::default()
+        }];
+        let fm = get_one_prop_feature_manifest(vec![], enum_defs, &prop);
+        // OK because the value is optional, and thus it's okay if it's missing (green is missing from the default)
+        let path = format!("test.{}", &prop.name);
+        fm.validate_prop_defaults(&path, &prop)?;
+        Ok(())
+    }
+
+    #[test]
+    fn test_iter_object_defs_deep_iterates_on_all_imports() -> Result<()> {
+        let prop_i = PropDef::new(
+            "key_i",
+            TypeRef::Object("SampleObjImported".into()),
+            json!({
+                "string": "bobo",
+            }),
+        );
+        let obj_defs_i = vec![ObjectDef {
+            name: "SampleObjImported".into(),
+            props: vec![PropDef::new("string", TypeRef::String, json!("a string"))],
+            ..Default::default()
+        }];
+        let fm_i = get_one_prop_feature_manifest(obj_defs_i, vec![], &prop_i);
+
+        let prop = PropDef::new(
+            "key",
+            TypeRef::Object("SampleObj".into()),
+            json!({
+                "string": "bobo",
+            }),
+        );
+        let obj_defs = vec![ObjectDef {
+            name: "SampleObj".into(),
+            props: vec![PropDef::new("string", TypeRef::String, json!("a string"))],
+            ..Default::default()
+        }];
+        let fm = get_one_prop_feature_manifest_with_imports(
+            obj_defs,
+            vec![],
+            &prop,
+            HashMap::from([(ModuleId::Local("test".into()), fm_i)]),
+        );
+
+        let names: Vec<String> = fm.iter_all_object_defs().map(|(_, o)| o.name()).collect();
+
+        assert_eq!(names[0], "SampleObj".to_string());
+        assert_eq!(names[1], "SampleObjImported".to_string());
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_iter_feature_defs_deep_iterates_on_all_imports() -> Result<()> {
+        let prop_i = PropDef::new("key_i", TypeRef::String, json!("string"));
+        let fm_i = get_one_prop_feature_manifest(vec![], vec![], &prop_i);
+
+        let prop = PropDef::new("key", TypeRef::String, json!("string"));
+        let fm = get_one_prop_feature_manifest_with_imports(
+            vec![],
+            vec![],
+            &prop,
+            HashMap::from([(ModuleId::Local("test".into()), fm_i)]),
+        );
+
+        let names: Vec<String> = fm
+            .iter_all_feature_defs()
+            .map(|(_, f)| f.props[0].name())
+            .collect();
+
+        assert_eq!(names[0], "key".to_string());
+        assert_eq!(names[1], "key_i".to_string());
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_find_feature_deep_finds_across_all_imports() -> Result<()> {
+        let fm_i = get_feature_manifest(
+            vec![],
+            vec![],
+            vec![FeatureDef {
+                name: "feature_i".into(),
+                ..Default::default()
+            }],
+            HashMap::new(),
+        );
+
+        let fm = get_feature_manifest(
+            vec![],
+            vec![],
+            vec![FeatureDef {
+                name: "feature".into(),
+                ..Default::default()
+            }],
+            HashMap::from([(ModuleId::Local("test".into()), fm_i)]),
+        );
+
+        let feature = fm.find_feature("feature_i");
+
+        assert!(feature.is_some());
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_get_coenrolling_feature_finds_across_all_imports() -> Result<()> {
+        let fm_i = get_feature_manifest(
+            vec![],
+            vec![],
+            vec![
+                FeatureDef {
+                    name: "coenrolling_import_1".into(),
+                    allow_coenrollment: true,
+                    ..Default::default()
+                },
+                FeatureDef {
+                    name: "coenrolling_import_2".into(),
+                    allow_coenrollment: true,
+                    ..Default::default()
+                },
+            ],
+            HashMap::new(),
+        );
+
+        let fm = get_feature_manifest(
+            vec![],
+            vec![],
+            vec![
+                FeatureDef {
+                    name: "coenrolling_feature".into(),
+                    allow_coenrollment: true,
+                    ..Default::default()
+                },
+                FeatureDef {
+                    name: "non_coenrolling_feature".into(),
+                    allow_coenrollment: false,
+                    ..Default::default()
+                },
+            ],
+            HashMap::from([(ModuleId::Local("test".into()), fm_i)]),
+        );
+
+        let coenrolling_features = fm.get_coenrolling_feature_ids();
+        let expected = vec![
+            "coenrolling_feature".to_string(),
+            "coenrolling_import_1".to_string(),
+            "coenrolling_import_2".to_string(),
+        ];
+
+        assert_eq!(coenrolling_features, expected);
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_no_coenrolling_feature_finds_across_all_imports() -> Result<()> {
+        let fm_i = get_feature_manifest(
+            vec![],
+            vec![],
+            vec![FeatureDef {
+                name: "not_coenrolling_import".into(),
+                allow_coenrollment: false,
+                ..Default::default()
+            }],
+            HashMap::new(),
+        );
+
+        let fm = get_feature_manifest(
+            vec![],
+            vec![],
+            vec![
+                FeatureDef {
+                    name: "non_coenrolling_feature_1".into(),
+                    allow_coenrollment: false,
+                    ..Default::default()
+                },
+                FeatureDef {
+                    name: "non_coenrolling_feature_2".into(),
+                    allow_coenrollment: false,
+                    ..Default::default()
+                },
+            ],
+            HashMap::from([(ModuleId::Local("test".into()), fm_i)]),
+        );
+
+        let coenrolling_features = fm.get_coenrolling_feature_ids();
+        let expected: Vec<String> = vec![];
+
+        assert_eq!(coenrolling_features, expected);
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_default_json_works_across_all_imports() -> Result<()> {
+        let fm_i = get_feature_manifest(
+            vec![],
+            vec![],
+            vec![FeatureDef {
+                name: "feature_i".into(),
+                props: vec![PropDef::new(
+                    "prop_i_1",
+                    TypeRef::String,
+                    Value::String("prop_i_1_value".into()),
+                )],
+                ..Default::default()
+            }],
+            HashMap::new(),
+        );
+
+        let fm = get_feature_manifest(
+            vec![],
+            vec![],
+            vec![FeatureDef {
+                name: "feature".into(),
+                props: vec![PropDef::new(
+                    "prop_1",
+                    TypeRef::String,
+                    Value::String("prop_1_value".into()),
+                )],
+                ..Default::default()
+            }],
+            HashMap::from([(ModuleId::Local("test".into()), fm_i)]),
+        );
+
+        let json = fm.default_json();
+        assert_eq!(
+            json.get("feature_i").unwrap().get("prop_i_1").unwrap(),
+            &Value::String("prop_i_1_value".into())
+        );
+        assert_eq!(
+            json.get("feature").unwrap().get("prop_1").unwrap(),
+            &Value::String("prop_1_value".into())
+        );
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_validate_feature_config_success() -> Result<()> {
+        let fm = get_feature_manifest(
+            vec![],
+            vec![],
+            vec![FeatureDef {
+                name: "feature".into(),
+                props: vec![PropDef::new(
+                    "prop_1",
+                    TypeRef::String,
+                    Value::String("prop_1_value".into()),
+                )],
+                ..Default::default()
+            }],
+            HashMap::new(),
+        );
+
+        let result = fm.validate_feature_config(
+            "feature",
+            Value::Object(Map::from_iter([(
+                "prop_1".to_string(),
+                Value::String("new value".into()),
+            )])),
+        )?;
+        assert_eq!(result.props[0].default, Value::String("new value".into()));
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_validate_feature_config_invalid_feature_name() -> Result<()> {
+        let fm = get_feature_manifest(
+            vec![],
+            vec![],
+            vec![FeatureDef {
+                name: "feature".into(),
+                props: vec![PropDef::new(
+                    "prop_1",
+                    TypeRef::String,
+                    Value::String("prop_1_value".into()),
+                )],
+                ..Default::default()
+            }],
+            HashMap::new(),
+        );
+
+        let result = fm.validate_feature_config(
+            "feature-1",
+            Value::Object(Map::from_iter([(
+                "prop_1".to_string(),
+                Value::String("new value".into()),
+            )])),
+        );
+        assert!(result.is_err());
+        assert_eq!(
+            result.err().unwrap().to_string(),
+            "Feature `feature-1` not found on manifest".to_string()
+        );
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_validate_feature_config_invalid_feature_prop_name() -> Result<()> {
+        let fm = get_feature_manifest(
+            vec![],
+            vec![],
+            vec![FeatureDef {
+                name: "feature".into(),
+                props: vec![PropDef::new(
+                    "prop_1",
+                    TypeRef::Option(Box::new(TypeRef::String)),
+                    Value::Null,
+                )],
+                ..Default::default()
+            }],
+            HashMap::new(),
+        );
+
+        let result = fm.validate_feature_config(
+            "feature",
+            Value::Object(Map::from_iter([(
+                "prop".to_string(),
+                Value::String("new value".into()),
+            )])),
+        );
+        assert!(result.is_err());
+        assert_eq!(
+            result.err().unwrap().to_string(),
+            "Validation Error at features/feature: Invalid property \"prop\"; did you mean \"prop_1\"?"
+        );
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_validate_feature_config_invalid_feature_prop_value() -> Result<()> {
+        let fm = get_feature_manifest(
+            vec![],
+            vec![],
+            vec![FeatureDef {
+                name: "feature".into(),
+                props: vec![PropDef::new(
+                    "prop_1",
+                    TypeRef::String,
+                    json!("prop_1_value"),
+                )],
+                ..Default::default()
+            }],
+            HashMap::new(),
+        );
+
+        let result = fm.validate_feature_config(
+            "feature",
+            Value::Object(Map::from_iter([(
+                "prop_1".to_string(),
+                Value::Number(Number::from(1)),
+            )])),
+        );
+        assert!(result.is_err());
+        assert_eq!(result.err().unwrap().to_string(), "Validation Error at features/feature.prop_1: Mismatch between type String and default 1".to_string());
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_validate_feature_config_errors_on_invalid_object_prop() -> Result<()> {
+        let obj_defs = vec![ObjectDef {
+            name: "SampleObj".into(),
+            props: vec![PropDef::new("string", TypeRef::String, json!("a string"))],
+            ..Default::default()
+        }];
+        let fm = get_feature_manifest(
+            obj_defs,
+            vec![],
+            vec![FeatureDef {
+                name: "feature".into(),
+                props: vec![PropDef::new(
+                    "prop_1",
+                    TypeRef::Object("SampleObj".into()),
+                    json!({
+                        "string": "a value"
+                    }),
+                )],
+                ..Default::default()
+            }],
+            HashMap::new(),
+        );
+
+        let result = fm.validate_feature_config(
+            "feature",
+            json!({
+                "prop_1": {
+                    "invalid-prop": "invalid-prop value"
+                }
+            }),
+        );
+
+        assert!(result.is_err());
+        assert_eq!(
+            result.err().unwrap().to_string(),
+            "Validation Error at features/feature.prop_1#SampleObj: Invalid key \"invalid-prop\" for object SampleObj; did you mean \"string\"?"
+        );
+
+        Ok(())
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus_fml/lib.rs.html b/book/rust-docs/src/nimbus_fml/lib.rs.html new file mode 100644 index 0000000000..dc055c604c --- /dev/null +++ b/book/rust-docs/src/nimbus_fml/lib.rs.html @@ -0,0 +1,49 @@ +lib.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+mod backends;
+pub mod command_line;
+pub(crate) mod defaults_merger;
+pub mod error;
+pub(crate) mod frontend;
+pub mod intermediate_representation;
+pub mod parser;
+pub mod util;
+
+cfg_if::cfg_if! {
+    if #[cfg(feature = "client-lib")] {
+        pub mod client;
+        pub use crate::client::*;
+    }
+}
+
+#[cfg(test)]
+pub mod fixtures;
+
+const SUPPORT_URL_LOADING: bool = true;
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus_fml/main.rs.html b/book/rust-docs/src/nimbus_fml/main.rs.html new file mode 100644 index 0000000000..47e2aa3883 --- /dev/null +++ b/book/rust-docs/src/nimbus_fml/main.rs.html @@ -0,0 +1,45 @@ +main.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+mod backends;
+mod command_line;
+mod defaults_merger;
+mod error;
+#[cfg(test)]
+mod fixtures;
+mod frontend;
+mod intermediate_representation;
+mod parser;
+mod util;
+
+use anyhow::Result;
+
+const SUPPORT_URL_LOADING: bool = true;
+
+fn main() -> Result<()> {
+    crate::command_line::do_main(std::env::args_os(), &std::env::current_dir()?)
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus_fml/parser.rs.html b/book/rust-docs/src/nimbus_fml/parser.rs.html new file mode 100644 index 0000000000..556d4005b8 --- /dev/null +++ b/book/rust-docs/src/nimbus_fml/parser.rs.html @@ -0,0 +1,2217 @@ +parser.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
+540
+541
+542
+543
+544
+545
+546
+547
+548
+549
+550
+551
+552
+553
+554
+555
+556
+557
+558
+559
+560
+561
+562
+563
+564
+565
+566
+567
+568
+569
+570
+571
+572
+573
+574
+575
+576
+577
+578
+579
+580
+581
+582
+583
+584
+585
+586
+587
+588
+589
+590
+591
+592
+593
+594
+595
+596
+597
+598
+599
+600
+601
+602
+603
+604
+605
+606
+607
+608
+609
+610
+611
+612
+613
+614
+615
+616
+617
+618
+619
+620
+621
+622
+623
+624
+625
+626
+627
+628
+629
+630
+631
+632
+633
+634
+635
+636
+637
+638
+639
+640
+641
+642
+643
+644
+645
+646
+647
+648
+649
+650
+651
+652
+653
+654
+655
+656
+657
+658
+659
+660
+661
+662
+663
+664
+665
+666
+667
+668
+669
+670
+671
+672
+673
+674
+675
+676
+677
+678
+679
+680
+681
+682
+683
+684
+685
+686
+687
+688
+689
+690
+691
+692
+693
+694
+695
+696
+697
+698
+699
+700
+701
+702
+703
+704
+705
+706
+707
+708
+709
+710
+711
+712
+713
+714
+715
+716
+717
+718
+719
+720
+721
+722
+723
+724
+725
+726
+727
+728
+729
+730
+731
+732
+733
+734
+735
+736
+737
+738
+739
+740
+741
+742
+743
+744
+745
+746
+747
+748
+749
+750
+751
+752
+753
+754
+755
+756
+757
+758
+759
+760
+761
+762
+763
+764
+765
+766
+767
+768
+769
+770
+771
+772
+773
+774
+775
+776
+777
+778
+779
+780
+781
+782
+783
+784
+785
+786
+787
+788
+789
+790
+791
+792
+793
+794
+795
+796
+797
+798
+799
+800
+801
+802
+803
+804
+805
+806
+807
+808
+809
+810
+811
+812
+813
+814
+815
+816
+817
+818
+819
+820
+821
+822
+823
+824
+825
+826
+827
+828
+829
+830
+831
+832
+833
+834
+835
+836
+837
+838
+839
+840
+841
+842
+843
+844
+845
+846
+847
+848
+849
+850
+851
+852
+853
+854
+855
+856
+857
+858
+859
+860
+861
+862
+863
+864
+865
+866
+867
+868
+869
+870
+871
+872
+873
+874
+875
+876
+877
+878
+879
+880
+881
+882
+883
+884
+885
+886
+887
+888
+889
+890
+891
+892
+893
+894
+895
+896
+897
+898
+899
+900
+901
+902
+903
+904
+905
+906
+907
+908
+909
+910
+911
+912
+913
+914
+915
+916
+917
+918
+919
+920
+921
+922
+923
+924
+925
+926
+927
+928
+929
+930
+931
+932
+933
+934
+935
+936
+937
+938
+939
+940
+941
+942
+943
+944
+945
+946
+947
+948
+949
+950
+951
+952
+953
+954
+955
+956
+957
+958
+959
+960
+961
+962
+963
+964
+965
+966
+967
+968
+969
+970
+971
+972
+973
+974
+975
+976
+977
+978
+979
+980
+981
+982
+983
+984
+985
+986
+987
+988
+989
+990
+991
+992
+993
+994
+995
+996
+997
+998
+999
+1000
+1001
+1002
+1003
+1004
+1005
+1006
+1007
+1008
+1009
+1010
+1011
+1012
+1013
+1014
+1015
+1016
+1017
+1018
+1019
+1020
+1021
+1022
+1023
+1024
+1025
+1026
+1027
+1028
+1029
+1030
+1031
+1032
+1033
+1034
+1035
+1036
+1037
+1038
+1039
+1040
+1041
+1042
+1043
+1044
+1045
+1046
+1047
+1048
+1049
+1050
+1051
+1052
+1053
+1054
+1055
+1056
+1057
+1058
+1059
+1060
+1061
+1062
+1063
+1064
+1065
+1066
+1067
+1068
+1069
+1070
+1071
+1072
+1073
+1074
+1075
+1076
+1077
+1078
+1079
+1080
+1081
+1082
+1083
+1084
+1085
+1086
+1087
+1088
+1089
+1090
+1091
+1092
+1093
+1094
+1095
+1096
+1097
+1098
+1099
+1100
+1101
+1102
+1103
+1104
+1105
+1106
+1107
+1108
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
+
+use crate::{
+    defaults_merger::DefaultsMerger,
+    error::{FMLError, Result},
+    frontend::{ImportBlock, ManifestFrontEnd, Types},
+    intermediate_representation::{FeatureManifest, ModuleId, TypeRef},
+    util::loaders::{FileLoader, FilePath},
+};
+
+fn parse_typeref_string(input: String) -> Result<(String, Option<String>)> {
+    // Split the string into the TypeRef and the name
+    let mut object_type_iter = input.split(&['<', '>'][..]);
+
+    // This should be the TypeRef type (except for )
+    let type_ref_name = object_type_iter.next().unwrap().trim();
+
+    if ["String", "Int", "Boolean"].contains(&type_ref_name) {
+        return Ok((type_ref_name.to_string(), None));
+    }
+
+    // This should be the name or type of the Object
+    match object_type_iter.next() {
+        Some(object_type_name) => Ok((
+            type_ref_name.to_string(),
+            Some(object_type_name.to_string()),
+        )),
+        None => Ok((type_ref_name.to_string(), None)),
+    }
+}
+
+pub(crate) fn get_typeref_from_string(
+    input: String,
+    types: Option<HashMap<String, TypeRef>>,
+) -> Result<TypeRef, FMLError> {
+    let (type_ref, type_name) = parse_typeref_string(input)?;
+
+    return match type_ref.as_str() {
+        "String" => Ok(TypeRef::String),
+        "Int" => Ok(TypeRef::Int),
+        "Boolean" => Ok(TypeRef::Boolean),
+        "BundleText" | "Text" => Ok(TypeRef::BundleText(
+            type_name.unwrap_or_else(|| "unnamed".to_string()),
+        )),
+        "BundleImage" | "Drawable" | "Image" => Ok(TypeRef::BundleImage(
+            type_name.unwrap_or_else(|| "unnamed".to_string()),
+        )),
+        "Enum" => Ok(TypeRef::Enum(type_name.unwrap())),
+        "Object" => Ok(TypeRef::Object(type_name.unwrap())),
+        "List" => Ok(TypeRef::List(Box::new(get_typeref_from_string(
+            type_name.unwrap(),
+            types,
+        )?))),
+        "Option" => Ok(TypeRef::Option(Box::new(get_typeref_from_string(
+            type_name.unwrap(),
+            types,
+        )?))),
+        "Map" => {
+            // Maps take a little extra massaging to get the key and value types
+            let type_name = type_name.unwrap();
+            let mut map_type_info_iter = type_name.split(',');
+
+            let key_type = map_type_info_iter.next().unwrap().to_string();
+            let value_type = map_type_info_iter.next().unwrap().trim().to_string();
+
+            if key_type.eq("String") {
+                Ok(TypeRef::StringMap(Box::new(get_typeref_from_string(
+                    value_type, types,
+                )?)))
+            } else {
+                Ok(TypeRef::EnumMap(
+                    Box::new(get_typeref_from_string(key_type, types.clone())?),
+                    Box::new(get_typeref_from_string(value_type, types)?),
+                ))
+            }
+        }
+        type_name => {
+            if types.is_none() {
+                return Err(FMLError::TypeParsingError(format!(
+                    "{} is not a recognized FML type",
+                    type_ref
+                )));
+            }
+
+            match types.unwrap().get(type_name) {
+                Some(type_ref) => Ok(type_ref.clone()),
+                None => {
+                    return Err(FMLError::TypeParsingError(format!(
+                        "{} is not a recognized FML type",
+                        type_ref
+                    )));
+                }
+            }
+        }
+    };
+}
+#[derive(Debug)]
+pub struct Parser {
+    files: FileLoader,
+    source: FilePath,
+}
+
+impl Parser {
+    pub fn new(files: FileLoader, source: FilePath) -> Result<Parser> {
+        Ok(Parser { source, files })
+    }
+
+    pub fn load_frontend(files: FileLoader, source: &str) -> Result<ManifestFrontEnd> {
+        let source = files.file_path(source)?;
+        let parser: Parser = Parser::new(files, source)?;
+        let mut loading = HashSet::new();
+        parser.load_manifest(&parser.source, &mut loading)
+    }
+
+    // This method loads a manifest, including resolving the includes and merging the included files
+    // into this top level one.
+    // It recursively calls itself and then calls `merge_manifest`.
+    pub fn load_manifest(
+        &self,
+        path: &FilePath,
+        loading: &mut HashSet<ModuleId>,
+    ) -> Result<ManifestFrontEnd> {
+        let id: ModuleId = path.try_into()?;
+        let files = &self.files;
+        let s = files
+            .read_to_string(path)
+            .map_err(|e| FMLError::FMLModuleError(id.clone(), e.to_string()))?;
+
+        let mut parent = serde_yaml::from_str::<ManifestFrontEnd>(&s)
+            .map_err(|e| FMLError::FMLModuleError(id.clone(), e.to_string()))?;
+
+        // We canonicalize the paths to the import files really soon after the loading so when we merge
+        // other included files, we cam match up the files that _they_ import, the concatenate the default
+        // blocks for their features.
+        self.canonicalize_import_paths(path, &mut parent.imports)
+            .map_err(|e| FMLError::FMLModuleError(id.clone(), e.to_string()))?;
+
+        loading.insert(id.clone());
+        parent
+            .includes()
+            .iter()
+            .try_fold(parent, |parent: ManifestFrontEnd, f| {
+                let src_path = files.join(path, f)?;
+                let child_id = ModuleId::try_from(&src_path)?;
+                Ok(if !loading.contains(&child_id) {
+                    let manifest = self.load_manifest(&src_path, loading)?;
+                    self.merge_manifest(&src_path, parent, &src_path, manifest)
+                        .map_err(|e| FMLError::FMLModuleError(id.clone(), e.to_string()))?
+                } else {
+                    parent
+                })
+            })
+    }
+
+    // Attempts to merge two manifests: a child into a parent.
+    // The `child_path` is needed to report errors.
+    fn merge_manifest(
+        &self,
+        parent_path: &FilePath,
+        parent: ManifestFrontEnd,
+        child_path: &FilePath,
+        child: ManifestFrontEnd,
+    ) -> Result<ManifestFrontEnd> {
+        self.check_can_merge_manifest(parent_path, &parent, child_path, &child)?;
+
+        // Child must not specify any features, objects or enums that the parent has.
+        let features = merge_map(
+            &parent.features,
+            &child.features,
+            "Features",
+            "features",
+            child_path,
+        )?;
+
+        let p_types = &parent.legacy_types.unwrap_or(parent.types);
+        let c_types = &child.legacy_types.unwrap_or(child.types);
+
+        let objects = merge_map(
+            &c_types.objects,
+            &p_types.objects,
+            "Objects",
+            "objects",
+            child_path,
+        )?;
+        let enums = merge_map(&c_types.enums, &p_types.enums, "Enums", "enums", child_path)?;
+
+        let imports = self.merge_import_block_list(&parent.imports, &child.imports)?;
+
+        let merged = ManifestFrontEnd {
+            features,
+            types: Types { enums, objects },
+            legacy_types: None,
+            imports,
+            ..parent
+        };
+
+        Ok(merged)
+    }
+
+    /// Load a manifest and all its imports, recursively if necessary.
+    ///
+    /// We populate a map of `FileId` to `FeatureManifest`s, so to avoid unnecessary clones,
+    /// we return a `FileId` even when the file has already been imported.
+    fn load_imports(
+        &self,
+        current: &FilePath,
+        channel: Option<&str>,
+        imports: &mut HashMap<ModuleId, FeatureManifest>,
+        // includes: &mut HashSet<ModuleId>,
+    ) -> Result<ModuleId> {
+        let id = current.try_into()?;
+        if imports.contains_key(&id) {
+            return Ok(id);
+        }
+        // We put a terminus in here, to make sure we don't try and load more than once.
+        imports.insert(id.clone(), Default::default());
+
+        // This loads the manifest in its frontend format (i.e. direct from YAML via serde), including
+        // all the `includes` for this manifest.
+        let frontend = self.load_manifest(current, &mut HashSet::new())?;
+
+        // Aside: tiny quality of life improvement. In the case where only one channel is supported,
+        // we use it. This helps with globbing directories where the app wants to keep the feature definition
+        // away from the feature configuration.
+        let channel = if frontend.channels.len() == 1 {
+            frontend.channels.first().map(String::as_str)
+        } else {
+            channel
+        };
+
+        let mut manifest = frontend.get_intermediate_representation(&id, channel)?;
+
+        // We're now going to go through all the imports in the manifest YAML.
+        // Each of the import blocks will have a path, and a Map<FeatureId, List<DefaultBlock>>
+        // This loop does the work of merging the default blocks back into the imported manifests.
+        // We'll then attach all the manifests to the root (i.e. the one we're generating code for today), in `imports`.
+        // We associate only the feature ids with the manifest we're loading in this method.
+        let mut imported_feature_id_map = HashMap::new();
+
+        for block in &frontend.imports {
+            // 1. Load the imported manifests in to the hash map.
+            let path = self.files.join(current, &block.path)?;
+            // The channel comes from the importer, rather than the command or the imported file.
+            let child_id = self.load_imports(&path, Some(&block.channel), imports)?;
+            let child_manifest = imports.get_mut(&child_id).expect("just loaded this file");
+
+            // We detect that there are no name collisions after the loading has finished, with `check_can_import_manifest`.
+            // We can't do it greedily, because of transitive imports may cause collisions, but we'll check here for better error
+            // messages.
+            check_can_import_manifest(&manifest, child_manifest)?;
+
+            // We detect that the imported files have language specific files in `validate_manifest_for_lang()`.
+            // We can't do it now because we don't yet know what this run is going to generate.
+
+            // 2. We'll build a set of feature names that this manifest imports from the child manifest.
+            // This will be the only thing we add directly to the manifest we load in this method.
+            let mut feature_ids = BTreeSet::new();
+
+            // 3. For each of the features in each of the imported files, the user can specify new defaults that should
+            //    merge into/overwrite the defaults specified in the imported file. Let's do that now:
+            // a. Prepare a DefaultsMerger, with an object map.
+            let merger = DefaultsMerger::new(
+                &child_manifest.obj_defs,
+                frontend.channels.clone(),
+                channel.map(str::to_string),
+            );
+
+            // b. Prepare a feature map that we'll alter in place.
+            //    EXP- 2540 If we want to support re-exporting/encapsulating features then we will need to change
+            //    this to be a more recursive look up. e.g. change `FeatureManifest.feature_defs` to be a `BTreeMap`.
+            let feature_map = &mut child_manifest.feature_defs;
+
+            // c. Iterate over the features we want to override
+            for (f, default_blocks) in &block.features {
+                let feature_def = feature_map.get_mut(f).ok_or_else(|| {
+                    FMLError::FMLModuleError(
+                        id.clone(),
+                        format!(
+                            "Cannot override defaults for `{}` feature from {}",
+                            f, &child_id
+                        ),
+                    )
+                })?;
+
+                // d. And merge the overrides in place into the FeatureDefs
+                merger
+                    .merge_feature_defaults(feature_def, &Some(default_blocks).cloned())
+                    .map_err(|e| FMLError::FMLModuleError(child_id.clone(), e.to_string()))?;
+
+                feature_ids.insert(f.clone());
+            }
+
+            // 4. Associate the imports as children of this manifest.
+            imported_feature_id_map.insert(child_id.clone(), feature_ids);
+        }
+
+        manifest.imported_features = imported_feature_id_map;
+        imports.insert(id.clone(), manifest);
+
+        Ok(id)
+    }
+
+    pub fn get_intermediate_representation(
+        &self,
+        channel: Option<&str>,
+    ) -> Result<FeatureManifest, FMLError> {
+        let mut manifests = HashMap::new();
+        let id = self.load_imports(&self.source, channel, &mut manifests)?;
+        let mut fm = manifests
+            .remove(&id)
+            .expect("Top level manifest should always be present");
+
+        for child in manifests.values() {
+            check_can_import_manifest(&fm, child)?;
+        }
+
+        fm.all_imports = manifests;
+
+        Ok(fm)
+    }
+}
+
+impl Parser {
+    fn check_can_merge_manifest(
+        &self,
+        parent_path: &FilePath,
+        parent: &ManifestFrontEnd,
+        child_path: &FilePath,
+        child: &ManifestFrontEnd,
+    ) -> Result<()> {
+        if !child.channels.is_empty() {
+            let child = &child.channels;
+            let child = child.iter().collect::<HashSet<&String>>();
+            let parent = &parent.channels;
+            let parent = parent.iter().collect::<HashSet<&String>>();
+            if !child.is_subset(&parent) {
+                return Err(FMLError::ValidationError(
+                    "channels".to_string(),
+                    format!(
+                        "Included manifest should not define its own channels: {}",
+                        child_path
+                    ),
+                ));
+            }
+        }
+
+        if let Some(about) = &child.about {
+            if !about.is_includable() {
+                return Err(FMLError::ValidationError(
+                "about".to_string(),
+                format!("Only files that don't already correspond to generated files may be included: file has a `class` and `package`/`module` name: {}", child_path),
+            ));
+            }
+        }
+
+        let mut map = Default::default();
+        self.check_can_merge_imports(parent_path, &parent.imports, &mut map)?;
+        self.check_can_merge_imports(child_path, &child.imports, &mut map)?;
+
+        Ok(())
+    }
+
+    fn canonicalize_import_paths(
+        &self,
+        path: &FilePath,
+        blocks: &mut Vec<ImportBlock>,
+    ) -> Result<()> {
+        for ib in blocks {
+            let p = &self.files.join(path, &ib.path)?;
+            ib.path = p.canonicalize()?.to_string();
+        }
+        Ok(())
+    }
+
+    fn check_can_merge_imports(
+        &self,
+        path: &FilePath,
+        blocks: &Vec<ImportBlock>,
+        map: &mut HashMap<String, String>,
+    ) -> Result<()> {
+        for b in blocks {
+            let id = &b.path;
+            let channel = &b.channel;
+            let existing = map.insert(id.clone(), channel.clone());
+            if let Some(v) = existing {
+                if &v != channel {
+                    return Err(FMLError::FMLModuleError(
+                        path.try_into()?,
+                        format!(
+                            "File {} is imported with two different channels: {} and {}",
+                            id, v, &channel
+                        ),
+                    ));
+                }
+            }
+        }
+        Ok(())
+    }
+
+    fn merge_import_block_list(
+        &self,
+        parent: &[ImportBlock],
+        child: &[ImportBlock],
+    ) -> Result<Vec<ImportBlock>> {
+        let mut map = parent
+            .iter()
+            .map(|im| (im.path.clone(), im.clone()))
+            .collect::<HashMap<_, _>>();
+
+        for cib in child {
+            let path = &cib.path;
+            if let Some(pib) = map.get(path) {
+                // We'll define an ordering here: the parent will come after the child
+                // so the top-level one will override the lower level ones.
+                // In practice, this shouldn't make a difference.
+                let merged = merge_import_block(cib, pib)?;
+                map.insert(path.clone(), merged);
+            } else {
+                map.insert(path.clone(), cib.clone());
+            }
+        }
+
+        Ok(map.values().map(|b| b.to_owned()).collect::<Vec<_>>())
+    }
+}
+
+fn merge_map<T: Clone>(
+    a: &BTreeMap<String, T>,
+    b: &BTreeMap<String, T>,
+    display_key: &str,
+    key: &str,
+    child_path: &FilePath,
+) -> Result<BTreeMap<String, T>> {
+    let mut set = HashSet::new();
+
+    let (a, b) = if a.len() < b.len() { (a, b) } else { (b, a) };
+
+    let mut map = b.clone();
+
+    for (k, v) in a {
+        if map.contains_key(k) {
+            set.insert(k.clone());
+        } else {
+            map.insert(k.clone(), v.clone());
+        }
+    }
+
+    if set.is_empty() {
+        Ok(map)
+    } else {
+        Err(FMLError::ValidationError(
+            format!("{}/{:?}", key, set),
+            format!(
+                "{} cannot be defined twice, overloaded definition detected at {}",
+                display_key, child_path,
+            ),
+        ))
+    }
+}
+
+fn merge_import_block(a: &ImportBlock, b: &ImportBlock) -> Result<ImportBlock> {
+    let mut block = a.clone();
+
+    for (id, defaults) in &b.features {
+        let mut defaults = defaults.clone();
+        if let Some(existing) = block.features.get_mut(id) {
+            existing.append(&mut defaults);
+        } else {
+            block.features.insert(id.clone(), defaults.clone());
+        }
+    }
+    Ok(block)
+}
+
+/// Check if this parent can import this child.
+fn check_can_import_manifest(parent: &FeatureManifest, child: &FeatureManifest) -> Result<()> {
+    check_can_import_list(parent, child, "enum", |fm: &FeatureManifest| {
+        fm.enum_defs.keys().collect()
+    })?;
+    check_can_import_list(parent, child, "objects", |fm: &FeatureManifest| {
+        fm.obj_defs.keys().collect()
+    })?;
+    check_can_import_list(parent, child, "features", |fm: &FeatureManifest| {
+        fm.feature_defs.keys().collect()
+    })?;
+
+    Ok(())
+}
+
+fn check_can_import_list(
+    parent: &FeatureManifest,
+    child: &FeatureManifest,
+    key: &str,
+    f: fn(&FeatureManifest) -> HashSet<&String>,
+) -> Result<()> {
+    let p = f(parent);
+    let c = f(child);
+    let intersection = p.intersection(&c).collect::<HashSet<_>>();
+    if !intersection.is_empty() {
+        Err(FMLError::ValidationError(
+            key.to_string(),
+            format!(
+                "`{}` types {:?} conflict when {} imports {}",
+                key, &intersection, &parent.id, &child.id
+            ),
+        ))
+    } else {
+        Ok(())
+    }
+}
+
+#[cfg(test)]
+mod unit_tests {
+
+    use std::{
+        path::{Path, PathBuf},
+        vec,
+    };
+
+    use serde_json::json;
+
+    use super::*;
+    use crate::{
+        error::Result,
+        frontend::ImportBlock,
+        intermediate_representation::{PropDef, VariantDef},
+        util::{join, pkg_dir},
+    };
+
+    #[test]
+    fn test_parse_from_front_end_representation() -> Result<()> {
+        let path = join(pkg_dir(), "fixtures/fe/nimbus_features.yaml");
+        let path = Path::new(&path);
+        let files = FileLoader::default()?;
+        let parser = Parser::new(files, path.into())?;
+        let ir = parser.get_intermediate_representation(Some("release"))?;
+
+        // Validate parsed enums
+        assert!(ir.enum_defs.len() == 1);
+        let enum_def = &ir.enum_defs["PlayerProfile"];
+        assert!(enum_def.name == *"PlayerProfile");
+        assert!(enum_def.doc == *"This is an enum type");
+        assert!(enum_def.variants.contains(&VariantDef {
+            name: "adult".to_string(),
+            doc: "This represents an adult player profile".to_string()
+        }));
+        assert!(enum_def.variants.contains(&VariantDef {
+            name: "child".to_string(),
+            doc: "This represents a child player profile".to_string()
+        }));
+
+        // Validate parsed objects
+        assert!(ir.obj_defs.len() == 1);
+        let obj_def = &ir.obj_defs["Button"];
+        assert!(obj_def.name == *"Button");
+        assert!(obj_def.doc == *"This is a button object");
+        assert!(obj_def.props.contains(&PropDef::new_with_doc(
+            "label",
+            "This is the label for the button",
+            TypeRef::String,
+            serde_json::Value::String("REQUIRED FIELD".to_string()),
+        )));
+        assert!(obj_def.props.contains(&PropDef::new_with_doc(
+            "color",
+            "This is the color of the button",
+            TypeRef::Option(Box::new(TypeRef::String)),
+            serde_json::Value::Null,
+        )));
+
+        // Validate parsed features
+        assert!(ir.feature_defs.len() == 1);
+        let feature_def = ir.get_feature("dialog-appearance").unwrap();
+        assert!(feature_def.name == *"dialog-appearance");
+        assert!(feature_def.doc() == *"This is the appearance of the dialog");
+        let positive_button = feature_def
+            .props
+            .iter()
+            .find(|x| x.name == "positive")
+            .unwrap();
+        assert!(positive_button.name == *"positive");
+        assert!(positive_button.doc == *"This is a positive button");
+        assert!(positive_button.typ == TypeRef::Object("Button".to_string()));
+        // We verify that the label, which came from the field default is "Ok then"
+        // and the color default, which came from the feature default is "green"
+        assert!(positive_button.default.get("label").unwrap().as_str() == Some("Ok then"));
+        assert!(positive_button.default.get("color").unwrap().as_str() == Some("green"));
+        let negative_button = feature_def
+            .props
+            .iter()
+            .find(|x| x.name == "negative")
+            .unwrap();
+        assert!(negative_button.name == *"negative");
+        assert!(negative_button.doc == *"This is a negative button");
+        assert!(negative_button.typ == TypeRef::Object("Button".to_string()));
+        assert!(negative_button.default.get("label").unwrap().as_str() == Some("Not this time"));
+        assert!(negative_button.default.get("color").unwrap().as_str() == Some("red"));
+        let background_color = feature_def
+            .props
+            .iter()
+            .find(|x| x.name == "background-color")
+            .unwrap();
+        assert!(background_color.name == *"background-color");
+        assert!(background_color.doc == *"This is the background color");
+        assert!(background_color.typ == TypeRef::String);
+        assert!(background_color.default.as_str() == Some("white"));
+        let player_mapping = feature_def
+            .props
+            .iter()
+            .find(|x| x.name == "player-mapping")
+            .unwrap();
+        assert!(player_mapping.name == *"player-mapping");
+        assert!(player_mapping.doc == *"This is the map of the player type to a button");
+        assert!(
+            player_mapping.typ
+                == TypeRef::EnumMap(
+                    Box::new(TypeRef::Enum("PlayerProfile".to_string())),
+                    Box::new(TypeRef::Object("Button".to_string()))
+                )
+        );
+        assert!(
+            player_mapping.default
+                == json!({
+                    "child": {
+                        "label": "Play game!",
+                        "color": "green"
+                    },
+                    "adult": {
+                        "label": "Play game!",
+                        "color": "blue",
+                    }
+                })
+        );
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_merging_defaults() -> Result<()> {
+        let path = join(pkg_dir(), "fixtures/fe/default_merging.yaml");
+        let path = Path::new(&path);
+        let files = FileLoader::default()?;
+        let parser = Parser::new(files, path.into())?;
+        let ir = parser.get_intermediate_representation(Some("release"))?;
+        let feature_def = ir.get_feature("dialog-appearance").unwrap();
+        let positive_button = feature_def
+            .props
+            .iter()
+            .find(|x| x.name == "positive")
+            .unwrap();
+        // We validate that the no-channel feature level default got merged back
+        assert_eq!(
+            positive_button
+                .default
+                .get("alt-text")
+                .unwrap()
+                .as_str()
+                .unwrap(),
+            "Go Ahead!"
+        );
+        // We validate that the orignal field level default don't get lost if no
+        // feature level default with the same name exists
+        assert_eq!(
+            positive_button
+                .default
+                .get("label")
+                .unwrap()
+                .as_str()
+                .unwrap(),
+            "Ok then"
+        );
+        // We validate that feature level default overwrite field level defaults if one exists
+        // in the field level, it's blue, but on the feature level it's green
+        assert_eq!(
+            positive_button
+                .default
+                .get("color")
+                .unwrap()
+                .as_str()
+                .unwrap(),
+            "green"
+        );
+        // We now re-run this, but merge back the nightly channel instead
+        let files = FileLoader::default()?;
+        let parser = Parser::new(files, path.into())?;
+        let ir = parser.get_intermediate_representation(Some("nightly"))?;
+        let feature_def = ir.get_feature("dialog-appearance").unwrap();
+        let positive_button = feature_def
+            .props
+            .iter()
+            .find(|x| x.name == "positive")
+            .unwrap();
+        // We validate that feature level default overwrite field level defaults if one exists
+        // in the field level, it's blue, but on the feature level it's bright-red
+        // note that it's bright-red because we merged back the `nightly`
+        // channel, instead of the `release` channel that merges back
+        // by default
+        assert_eq!(
+            positive_button
+                .default
+                .get("color")
+                .unwrap()
+                .as_str()
+                .unwrap(),
+            "bright-red"
+        );
+        // We againt validate that regardless
+        // of the channel, the no-channel feature level default got merged back
+        assert_eq!(
+            positive_button
+                .default
+                .get("alt-text")
+                .unwrap()
+                .as_str()
+                .unwrap(),
+            "Go Ahead!"
+        );
+        Ok(())
+    }
+
+    #[test]
+    fn test_convert_to_typeref_string() -> Result<()> {
+        // Testing converting to TypeRef::String
+        assert_eq!(
+            get_typeref_from_string("String".to_string(), None).unwrap(),
+            TypeRef::String
+        );
+        get_typeref_from_string("string".to_string(), None).unwrap_err();
+        get_typeref_from_string("str".to_string(), None).unwrap_err();
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_convert_to_typeref_int() -> Result<()> {
+        // Testing converting to TypeRef::Int
+        assert_eq!(
+            get_typeref_from_string("Int".to_string(), None).unwrap(),
+            TypeRef::Int
+        );
+        get_typeref_from_string("integer".to_string(), None).unwrap_err();
+        get_typeref_from_string("int".to_string(), None).unwrap_err();
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_convert_to_typeref_boolean() -> Result<()> {
+        // Testing converting to TypeRef::Boolean
+        assert_eq!(
+            get_typeref_from_string("Boolean".to_string(), None).unwrap(),
+            TypeRef::Boolean
+        );
+        get_typeref_from_string("boolean".to_string(), None).unwrap_err();
+        get_typeref_from_string("bool".to_string(), None).unwrap_err();
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_convert_to_typeref_bundletext() -> Result<()> {
+        // Testing converting to TypeRef::BundleText
+        assert_eq!(
+            get_typeref_from_string("BundleText<test_name>".to_string(), None).unwrap(),
+            TypeRef::BundleText("test_name".to_string())
+        );
+        get_typeref_from_string("bundletext(something)".to_string(), None).unwrap_err();
+        get_typeref_from_string("BundleText()".to_string(), None).unwrap_err();
+
+        // The commented out lines below represent areas we need better
+        // type checking on, but are ignored for now
+
+        // get_typeref_from_string("BundleText".to_string()).unwrap_err();
+        // get_typeref_from_string("BundleText<>".to_string()).unwrap_err();
+        // get_typeref_from_string("BundleText<21>".to_string()).unwrap_err();
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_convert_to_typeref_bundleimage() -> Result<()> {
+        // Testing converting to TypeRef::BundleImage
+        assert_eq!(
+            get_typeref_from_string("BundleImage<test_name>".to_string(), None).unwrap(),
+            TypeRef::BundleImage("test_name".to_string())
+        );
+        get_typeref_from_string("bundleimage(something)".to_string(), None).unwrap_err();
+        get_typeref_from_string("BundleImage()".to_string(), None).unwrap_err();
+
+        // The commented out lines below represent areas we need better
+        // type checking on, but are ignored for now
+
+        // get_typeref_from_string("BundleImage".to_string()).unwrap_err();
+        // get_typeref_from_string("BundleImage<>".to_string()).unwrap_err();
+        // get_typeref_from_string("BundleImage<21>".to_string()).unwrap_err();
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_convert_to_typeref_enum() -> Result<()> {
+        // Testing converting to TypeRef::Enum
+        assert_eq!(
+            get_typeref_from_string("Enum<test_name>".to_string(), None).unwrap(),
+            TypeRef::Enum("test_name".to_string())
+        );
+        get_typeref_from_string("enum(something)".to_string(), None).unwrap_err();
+        get_typeref_from_string("Enum()".to_string(), None).unwrap_err();
+
+        // The commented out lines below represent areas we need better
+        // type checking on, but are ignored for now
+
+        // get_typeref_from_string("Enum".to_string()).unwrap_err();
+        // get_typeref_from_string("Enum<>".to_string()).unwrap_err();
+        // get_typeref_from_string("Enum<21>".to_string()).unwrap_err();
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_convert_to_typeref_object() -> Result<()> {
+        // Testing converting to TypeRef::Object
+        assert_eq!(
+            get_typeref_from_string("Object<test_name>".to_string(), None).unwrap(),
+            TypeRef::Object("test_name".to_string())
+        );
+        get_typeref_from_string("object(something)".to_string(), None).unwrap_err();
+        get_typeref_from_string("Object()".to_string(), None).unwrap_err();
+
+        // The commented out lines below represent areas we need better
+        // type checking on, but are ignored for now
+
+        // get_typeref_from_string("Object".to_string()).unwrap_err();
+        // get_typeref_from_string("Object<>".to_string()).unwrap_err();
+        // get_typeref_from_string("Object<21>".to_string()).unwrap_err();
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_convert_to_typeref_list() -> Result<()> {
+        // Testing converting to TypeRef::List
+        assert_eq!(
+            get_typeref_from_string("List<String>".to_string(), None).unwrap(),
+            TypeRef::List(Box::new(TypeRef::String))
+        );
+        assert_eq!(
+            get_typeref_from_string("List<Int>".to_string(), None).unwrap(),
+            TypeRef::List(Box::new(TypeRef::Int))
+        );
+        assert_eq!(
+            get_typeref_from_string("List<Boolean>".to_string(), None).unwrap(),
+            TypeRef::List(Box::new(TypeRef::Boolean))
+        );
+
+        // Generate a list of user types to validate use of them in a list
+        let mut types = HashMap::new();
+        types.insert(
+            "TestEnum".to_string(),
+            TypeRef::Enum("TestEnum".to_string()),
+        );
+        types.insert(
+            "TestObject".to_string(),
+            TypeRef::Object("TestObject".to_string()),
+        );
+
+        assert_eq!(
+            get_typeref_from_string("List<TestEnum>".to_string(), Some(types.clone())).unwrap(),
+            TypeRef::List(Box::new(TypeRef::Enum("TestEnum".to_string())))
+        );
+        assert_eq!(
+            get_typeref_from_string("List<TestObject>".to_string(), Some(types)).unwrap(),
+            TypeRef::List(Box::new(TypeRef::Object("TestObject".to_string())))
+        );
+
+        get_typeref_from_string("list(something)".to_string(), None).unwrap_err();
+        get_typeref_from_string("List()".to_string(), None).unwrap_err();
+
+        // The commented out lines below represent areas we need better
+        // type checking on, but are ignored for now
+
+        // get_typeref_from_string("List".to_string()).unwrap_err();
+        // get_typeref_from_string("List<>".to_string()).unwrap_err();
+        // get_typeref_from_string("List<21>".to_string()).unwrap_err();
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_convert_to_typeref_option() -> Result<()> {
+        // Testing converting to TypeRef::Option
+        assert_eq!(
+            get_typeref_from_string("Option<String>".to_string(), None).unwrap(),
+            TypeRef::Option(Box::new(TypeRef::String))
+        );
+        assert_eq!(
+            get_typeref_from_string("Option<Int>".to_string(), None).unwrap(),
+            TypeRef::Option(Box::new(TypeRef::Int))
+        );
+        assert_eq!(
+            get_typeref_from_string("Option<Boolean>".to_string(), None).unwrap(),
+            TypeRef::Option(Box::new(TypeRef::Boolean))
+        );
+
+        // Generate a list of user types to validate use of them as Options
+        let mut types = HashMap::new();
+        types.insert(
+            "TestEnum".to_string(),
+            TypeRef::Enum("TestEnum".to_string()),
+        );
+        types.insert(
+            "TestObject".to_string(),
+            TypeRef::Object("TestObject".to_string()),
+        );
+        assert_eq!(
+            get_typeref_from_string("Option<TestEnum>".to_string(), Some(types.clone())).unwrap(),
+            TypeRef::Option(Box::new(TypeRef::Enum("TestEnum".to_string())))
+        );
+        assert_eq!(
+            get_typeref_from_string("Option<TestObject>".to_string(), Some(types)).unwrap(),
+            TypeRef::Option(Box::new(TypeRef::Object("TestObject".to_string())))
+        );
+
+        get_typeref_from_string("option(something)".to_string(), None).unwrap_err();
+        get_typeref_from_string("Option(Something)".to_string(), None).unwrap_err();
+
+        // The commented out lines below represent areas we need better
+        // type checking on, but are ignored for now
+
+        // get_typeref_from_string("Option".to_string()).unwrap_err();
+        // get_typeref_from_string("Option<>".to_string()).unwrap_err();
+        // get_typeref_from_string("Option<21>".to_string()).unwrap_err();
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_convert_to_typeref_map() -> Result<()> {
+        // Testing converting to TypeRef::Map
+        assert_eq!(
+            get_typeref_from_string("Map<String, String>".to_string(), None).unwrap(),
+            TypeRef::StringMap(Box::new(TypeRef::String))
+        );
+        assert_eq!(
+            get_typeref_from_string("Map<String, Int>".to_string(), None).unwrap(),
+            TypeRef::StringMap(Box::new(TypeRef::Int))
+        );
+        assert_eq!(
+            get_typeref_from_string("Map<String, Boolean>".to_string(), None).unwrap(),
+            TypeRef::StringMap(Box::new(TypeRef::Boolean))
+        );
+
+        // Generate a list of user types to validate use of them in a list
+        let mut types = HashMap::new();
+        types.insert(
+            "TestEnum".to_string(),
+            TypeRef::Enum("TestEnum".to_string()),
+        );
+        types.insert(
+            "TestObject".to_string(),
+            TypeRef::Object("TestObject".to_string()),
+        );
+        assert_eq!(
+            get_typeref_from_string("Map<String, TestEnum>".to_string(), Some(types.clone()))
+                .unwrap(),
+            TypeRef::StringMap(Box::new(TypeRef::Enum("TestEnum".to_string())))
+        );
+        assert_eq!(
+            get_typeref_from_string("Map<String, TestObject>".to_string(), Some(types.clone()))
+                .unwrap(),
+            TypeRef::StringMap(Box::new(TypeRef::Object("TestObject".to_string())))
+        );
+        assert_eq!(
+            get_typeref_from_string("Map<TestEnum, String>".to_string(), Some(types.clone()))
+                .unwrap(),
+            TypeRef::EnumMap(
+                Box::new(TypeRef::Enum("TestEnum".to_string())),
+                Box::new(TypeRef::String)
+            )
+        );
+        assert_eq!(
+            get_typeref_from_string("Map<TestEnum, TestObject>".to_string(), Some(types.clone()))
+                .unwrap(),
+            TypeRef::EnumMap(
+                Box::new(TypeRef::Enum("TestEnum".to_string())),
+                Box::new(TypeRef::Object("TestObject".to_string()))
+            )
+        );
+
+        get_typeref_from_string("map(something)".to_string(), None).unwrap_err();
+        get_typeref_from_string("Map(Something)".to_string(), None).unwrap_err();
+
+        // The commented out lines below represent areas we need better
+        // type checking on, but are ignored for now
+
+        // get_typeref_from_string("Map".to_string()).unwrap_err();
+        // get_typeref_from_string("Map<>".to_string()).unwrap_err();
+        // get_typeref_from_string("Map<21>".to_string()).unwrap_err();
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_include_check_can_merge_manifest() -> Result<()> {
+        let files = FileLoader::default()?;
+        let parser = Parser::new(files, std::env::temp_dir().as_path().into())?;
+        let parent_path: FilePath = std::env::temp_dir().as_path().into();
+        let child_path = parent_path.join("http://not-needed.com")?;
+        let parent = ManifestFrontEnd {
+            channels: vec!["alice".to_string(), "bob".to_string()],
+            ..Default::default()
+        };
+        let child = ManifestFrontEnd {
+            channels: vec!["alice".to_string(), "bob".to_string()],
+            ..Default::default()
+        };
+
+        assert!(parser
+            .check_can_merge_manifest(&parent_path, &parent, &child_path, &child)
+            .is_ok());
+
+        let child = ManifestFrontEnd {
+            channels: vec!["eve".to_string()],
+            ..Default::default()
+        };
+
+        assert!(parser
+            .check_can_merge_manifest(&parent_path, &parent, &child_path, &child)
+            .is_err());
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_include_check_can_merge_manifest_with_imports() -> Result<()> {
+        let files = FileLoader::default()?;
+        let parser = Parser::new(files, std::env::temp_dir().as_path().into())?;
+        let parent_path: FilePath = std::env::temp_dir().as_path().into();
+        let child_path = parent_path.join("http://child")?;
+        let parent = ManifestFrontEnd {
+            channels: vec!["alice".to_string(), "bob".to_string()],
+            imports: vec![ImportBlock {
+                path: "absolute_path".to_string(),
+                channel: "one_channel".to_string(),
+                features: Default::default(),
+            }],
+            ..Default::default()
+        };
+        let child = ManifestFrontEnd {
+            channels: vec!["alice".to_string(), "bob".to_string()],
+            imports: vec![ImportBlock {
+                path: "absolute_path".to_string(),
+                channel: "another_channel".to_string(),
+                features: Default::default(),
+            }],
+            ..Default::default()
+        };
+
+        let mut map = Default::default();
+        let res = parser.check_can_merge_imports(&parent_path, &parent.imports, &mut map);
+        assert!(res.is_ok());
+        assert_eq!(map.get("absolute_path").unwrap(), "one_channel");
+
+        let err_msg = "Problem with http://child/: File absolute_path is imported with two different channels: one_channel and another_channel";
+        let res = parser.check_can_merge_imports(&child_path, &child.imports, &mut map);
+        assert!(res.is_err());
+        assert_eq!(res.unwrap_err().to_string(), err_msg.to_string());
+
+        let res = parser.check_can_merge_manifest(&parent_path, &parent, &child_path, &child);
+        assert!(res.is_err());
+        assert_eq!(res.unwrap_err().to_string(), err_msg.to_string());
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_include_circular_includes() -> Result<()> {
+        use crate::util::pkg_dir;
+        // snake.yaml includes tail.yaml, which includes snake.yaml
+        let path = PathBuf::from(pkg_dir()).join("fixtures/fe/including/circular/snake.yaml");
+
+        let files = FileLoader::default()?;
+        let parser = Parser::new(files, path.as_path().into())?;
+        let ir = parser.get_intermediate_representation(Some("release"));
+        assert!(ir.is_ok());
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_include_deeply_nested_includes() -> Result<()> {
+        use crate::util::pkg_dir;
+        // Deeply nested includes, which start at 00-head.yaml, and then recursively includes all the
+        // way down to 06-toe.yaml
+        let path_buf = PathBuf::from(pkg_dir()).join("fixtures/fe/including/deep/00-head.yaml");
+
+        let files = FileLoader::default()?;
+        let parser = Parser::new(files, path_buf.as_path().into())?;
+
+        let ir = parser.get_intermediate_representation(Some("release"))?;
+        assert_eq!(ir.feature_defs.len(), 1);
+
+        Ok(())
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus_fml/util/loaders.rs.html b/book/rust-docs/src/nimbus_fml/util/loaders.rs.html new file mode 100644 index 0000000000..7d3ae95d0d --- /dev/null +++ b/book/rust-docs/src/nimbus_fml/util/loaders.rs.html @@ -0,0 +1,1457 @@ +loaders.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
+540
+541
+542
+543
+544
+545
+546
+547
+548
+549
+550
+551
+552
+553
+554
+555
+556
+557
+558
+559
+560
+561
+562
+563
+564
+565
+566
+567
+568
+569
+570
+571
+572
+573
+574
+575
+576
+577
+578
+579
+580
+581
+582
+583
+584
+585
+586
+587
+588
+589
+590
+591
+592
+593
+594
+595
+596
+597
+598
+599
+600
+601
+602
+603
+604
+605
+606
+607
+608
+609
+610
+611
+612
+613
+614
+615
+616
+617
+618
+619
+620
+621
+622
+623
+624
+625
+626
+627
+628
+629
+630
+631
+632
+633
+634
+635
+636
+637
+638
+639
+640
+641
+642
+643
+644
+645
+646
+647
+648
+649
+650
+651
+652
+653
+654
+655
+656
+657
+658
+659
+660
+661
+662
+663
+664
+665
+666
+667
+668
+669
+670
+671
+672
+673
+674
+675
+676
+677
+678
+679
+680
+681
+682
+683
+684
+685
+686
+687
+688
+689
+690
+691
+692
+693
+694
+695
+696
+697
+698
+699
+700
+701
+702
+703
+704
+705
+706
+707
+708
+709
+710
+711
+712
+713
+714
+715
+716
+717
+718
+719
+720
+721
+722
+723
+724
+725
+726
+727
+728
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+use crate::{
+    error::{FMLError, Result},
+    SUPPORT_URL_LOADING,
+};
+
+use reqwest::blocking::{Client, ClientBuilder};
+use std::{
+    collections::{hash_map::DefaultHasher, BTreeMap},
+    env,
+    fmt::Display,
+    hash::{Hash, Hasher},
+    path::{Path, PathBuf},
+};
+use url::Url;
+
+pub(crate) const GITHUB_USER_CONTENT_DOTCOM: &str = "https://raw.githubusercontent.com";
+
+#[derive(Clone)]
+pub struct LoaderConfig {
+    pub cwd: PathBuf,
+    pub repo_files: Vec<String>,
+    pub cache_dir: Option<PathBuf>,
+    pub refs: BTreeMap<String, String>,
+}
+
+impl LoaderConfig {
+    pub(crate) fn repo_and_path(f: &str) -> Option<(String, String)> {
+        if f.starts_with('@') {
+            let parts = f.splitn(3, '/').collect::<Vec<&str>>();
+            match parts.as_slice() {
+                [user, repo, path] => Some((format!("{user}/{repo}"), path.to_string())),
+                _ => None,
+            }
+        } else {
+            None
+        }
+    }
+}
+
+impl Default for LoaderConfig {
+    fn default() -> Self {
+        Self {
+            repo_files: Default::default(),
+            cache_dir: None,
+            cwd: env::current_dir().expect("Current Working Directory is not set"),
+            refs: Default::default(),
+        }
+    }
+}
+
+/// A small enum for working with URLs and relative files
+#[derive(Clone, Debug)]
+pub enum FilePath {
+    Local(PathBuf),
+    Remote(Url),
+}
+
+impl FilePath {
+    pub fn new(cwd: &Path, file: &str) -> Result<Self> {
+        Ok(if file.contains("://") {
+            FilePath::Remote(Url::parse(file)?)
+        } else {
+            FilePath::Local(cwd.join(file))
+        })
+    }
+
+    /// Appends a suffix to a path.
+    /// If the `self` is a local file and the suffix is an absolute URL,
+    /// then the return is the URL.
+    pub fn join(&self, file: &str) -> Result<Self> {
+        if file.contains("://") {
+            return Ok(FilePath::Remote(Url::parse(file)?));
+        }
+        Ok(match self {
+            Self::Local(p) => Self::Local(
+                // We implement a join similar to Url::join.
+                // If the root is a directory, we append;
+                // if not we take the parent, then append.
+                if is_dir(p) {
+                    p.join(file)
+                } else {
+                    p.parent()
+                        .expect("a file within a parent directory")
+                        .join(file)
+                },
+            ),
+            Self::Remote(u) => Self::Remote(u.join(file)?),
+        })
+    }
+
+    pub fn canonicalize(&self) -> Result<Self> {
+        Ok(match self {
+            Self::Local(p) => Self::Local(p.canonicalize().map_err(|e| {
+                // We do this map_err here because the IO Error message that comes out of `canonicalize`
+                // doesn't include the problematic file path.
+                FMLError::InvalidPath(format!("{}: {}", e, p.as_path().display()))
+            })?),
+            Self::Remote(u) => Self::Remote(u.clone()),
+        })
+    }
+}
+
+impl Display for FilePath {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(
+            f,
+            "{}",
+            match self {
+                Self::Local(p) => p.display().to_string(),
+                Self::Remote(u) => u.to_string(),
+            }
+        )
+    }
+}
+
+impl From<&Path> for FilePath {
+    fn from(path: &Path) -> Self {
+        Self::Local(path.into())
+    }
+}
+
+#[cfg(not(test))]
+fn is_dir(path_buf: &Path) -> bool {
+    path_buf.is_dir()
+}
+
+#[cfg(test)]
+fn is_dir(path_buf: &Path) -> bool {
+    path_buf.display().to_string().ends_with('/')
+}
+
+static USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),);
+
+/// Utility class to abstract away the differences between loading from file and network.
+///
+/// With a nod to offline developer experience, files which come from the network
+/// are cached on disk.
+///
+/// The cache directory should be in a directory that will get purged on a clean build.
+///
+/// This allows us to import files from another repository (via https) or include files
+/// from a local files.
+///
+/// The loader is able to resolve a shortcut syntax similar to other package managers.
+///
+/// By default a prefix of `@XXXX/YYYY`: resolves to the `main` branch `XXXX/YYYY` Github repo.
+///
+/// The config is a map of repository names to paths, URLs or branches.
+///
+/// Config files can be loaded
+#[derive(Clone, Debug)]
+pub struct FileLoader {
+    cache_dir: Option<PathBuf>,
+    fetch_client: Client,
+
+    config: BTreeMap<String, FilePath>,
+
+    // This is used for resolving relative paths when no other path
+    // information is available.
+    cwd: PathBuf,
+}
+
+impl TryFrom<&LoaderConfig> for FileLoader {
+    type Error = FMLError;
+
+    fn try_from(value: &LoaderConfig) -> Result<Self, Self::Error> {
+        let cache_dir = value.cache_dir.clone();
+        let cwd = value.cwd.clone();
+
+        let mut files = Self::new(cwd, cache_dir, Default::default())?;
+
+        for (k, v) in &value.refs {
+            files.add_repo(k, v)?;
+        }
+
+        for f in &value.repo_files {
+            let path = files.file_path(f)?;
+            files.add_repo_file(&path)?;
+        }
+
+        Ok(files)
+    }
+}
+
+impl FileLoader {
+    pub fn new(
+        cwd: PathBuf,
+        cache_dir: Option<PathBuf>,
+        config: BTreeMap<String, FilePath>,
+    ) -> Result<Self> {
+        let http_client = ClientBuilder::new()
+            .https_only(true)
+            .user_agent(USER_AGENT)
+            .build()?;
+
+        Ok(Self {
+            cache_dir,
+            fetch_client: http_client,
+            cwd,
+
+            config,
+        })
+    }
+
+    #[allow(clippy::should_implement_trait)]
+    #[cfg(test)]
+    pub fn default() -> Result<Self> {
+        let cwd = std::env::current_dir()?;
+        let cache_path = cwd.join("build/app/fml-cache");
+        Self::new(
+            std::env::current_dir().expect("Current Working Directory not set"),
+            Some(cache_path),
+            Default::default(),
+        )
+    }
+
+    /// Load a file containing mapping of repo names to `FilePath`s.
+    /// Repo files can be JSON or YAML in format.
+    /// Files are simple key value pair mappings of `repo_id` to repository locations,
+    /// where:
+    ///
+    /// - a repo id is of the format used on Github: `$ORGANIZATION/$PROJECT`, and
+    /// - location can be
+    ///     - a path to a directory on disk, or
+    ///     - a ref/branch/tag/commit hash in the repo stored on Github.
+    ///
+    /// Relative paths to on disk directories will be taken as relative to this file.
+    pub fn add_repo_file(&mut self, file: &FilePath) -> Result<()> {
+        let string = self.read_to_string(file)?;
+
+        let config: BTreeMap<String, String> = if file.to_string().ends_with(".json") {
+            serde_json::from_str(&string)?
+        } else {
+            serde_yaml::from_str(&string)?
+        };
+
+        for (k, v) in config {
+            self.add_repo_relative(file, &k, &v)?;
+        }
+
+        Ok(())
+    }
+
+    /// Add a repo and version/tag/ref/location.
+    /// `repo_id` is the github `$ORGANIZATION/$PROJECT` string, e.g. `mozilla/application-services`.
+    /// The `loc` string can be a:
+    /// 1. A branch, commit hash or release tag on a remote repository, hosted on Github
+    /// 2. A URL
+    /// 3. A relative path (to the current working directory) to a directory on the local disk.
+    /// 4. An absolute path to a directory on the local disk.
+    pub fn add_repo(&mut self, repo_id: &str, loc: &str) -> Result<()> {
+        self.add_repo_relative(&FilePath::Local(self.cwd.clone()), repo_id, loc)
+    }
+
+    fn add_repo_relative(&mut self, cwd: &FilePath, repo_id: &str, loc: &str) -> Result<()> {
+        // We're building up a mapping of repo_ids to `FilePath`s; recall: `FilePath` is an enum that is an
+        // absolute path or URL.
+
+        // Standardize the form of repo id. We accept `@user/repo` or `user/repo`, but store it as
+        // `user/repo`.
+        let repo_id = repo_id.replacen('@', "", 1);
+
+        // The `loc`, whatever the current working directory, is going to end up as a part of a path.
+        // A trailing slash ensures it gets treated like a directoy, rather than a file.
+        // See Url::join.
+        let loc = if loc.ends_with('/') {
+            loc.to_string()
+        } else {
+            format!("{}/", loc)
+        };
+
+        // We construct the FilePath. We want to be able to tell the difference between a what `FilePath`s
+        // can already reason about (relative file paths, absolute file paths and URLs) and what git knows about (refs, tags, versions).
+        let v = if loc.starts_with('.')
+            || loc.starts_with('/')
+            || loc.contains(":\\")
+            || loc.contains("://")
+        {
+            // URLs, relative file paths, absolute paths.
+            cwd.join(&loc)?
+        } else {
+            // refs, commmit hashes, tags, branches.
+            self.remote_file_path(&repo_id, &loc)?
+        };
+
+        // Finally, add the absolute path that we use every time the user refers to @user/repo.
+        self.config.insert(repo_id, v);
+        Ok(())
+    }
+
+    fn remote_file_path(&self, repo: &str, branch_or_tag: &str) -> Result<FilePath, FMLError> {
+        let base_url = format!("{}/{}/{}", GITHUB_USER_CONTENT_DOTCOM, repo, branch_or_tag);
+        Ok(FilePath::Remote(Url::parse(&base_url)?))
+    }
+
+    fn default_remote_path(&self, key: String) -> FilePath {
+        self.remote_file_path(&key, "main/")
+            .expect("main branch never fails")
+    }
+
+    /// This loads a text file from disk or the network.
+    ///
+    /// If it's coming from the network, then cache the file to disk (based on the URL).
+    ///
+    /// We don't worry about cache invalidation, because a clean build should blow the cache
+    /// away.
+    pub fn read_to_string(&self, file: &FilePath) -> Result<String> {
+        Ok(match file {
+            FilePath::Local(path) => std::fs::read_to_string(path)?,
+            FilePath::Remote(url) => self.fetch_and_cache(url)?,
+        })
+    }
+
+    fn fetch_and_cache(&self, url: &Url) -> Result<String> {
+        if !SUPPORT_URL_LOADING {
+            unimplemented!("Loading manifests from URLs is not yet supported ({})", url);
+        }
+        let path_buf = self.create_cache_path_buf(url);
+        Ok(if path_buf.exists() {
+            std::fs::read_to_string(path_buf)?
+        } else {
+            let res = self.fetch_client.get(url.clone()).send()?;
+            let text = res.text()?;
+
+            let parent = path_buf.parent().expect("Cache directory is specified");
+            if !parent.exists() {
+                std::fs::create_dir_all(parent)?;
+            }
+
+            std::fs::write(path_buf, &text)?;
+            text
+        })
+    }
+
+    fn create_cache_path_buf(&self, url: &Url) -> PathBuf {
+        // Method to look after the cache directory.
+        // We can organize this how we want: in this case we use a flat structure
+        // with a hash of the URL as a prefix of the directory.
+        let mut hasher = DefaultHasher::new();
+        url.hash(&mut hasher);
+        let checksum = hasher.finish();
+        let filename = match url.path_segments() {
+            Some(segments) => segments.last().unwrap_or("unknown.txt"),
+            None => "unknown.txt",
+        };
+        // Take the last 16 bytes of the hash to make sure our prefixes are still random, but
+        // not crazily long.
+        let filename = format!("{:x}_{}", (checksum & 0x000000000000FFFF) as u16, filename,);
+
+        self.cache_dir().join(filename)
+    }
+
+    fn cache_dir(&self) -> &Path {
+        match &self.cache_dir {
+            Some(d) => d,
+            _ => self.tmp_cache_dir(),
+        }
+    }
+
+    fn tmp_cache_dir<'a>(&self) -> &'a Path {
+        use std::time::SystemTime;
+        lazy_static::lazy_static! {
+            static ref CACHE_DIR_NAME: String = format!("nimbus-fml-manifests-{:x}", match SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) {
+                Ok(n) => n.as_micros() & 0x00ffffff,
+                Err(_) => 0,
+            });
+
+            static ref TMP_CACHE_DIR: PathBuf = std::env::temp_dir().join(CACHE_DIR_NAME.as_str());
+        }
+        &TMP_CACHE_DIR
+    }
+
+    /// Joins a path to a string, to make a new path.
+    ///
+    /// We want to be able to support local and remote files.
+    /// We also want to be able to support a configurable short cut format.
+    /// Following a pattern common in other package managers, `@XXXX/YYYY`
+    /// is used as short hand for the main branch in github repos.
+    ///
+    /// If `f` is a relative path, the result is relative to `base`.
+    pub fn join(&self, base: &FilePath, f: &str) -> Result<FilePath> {
+        Ok(if let Some(u) = self.resolve_url_shortcut(f)? {
+            u
+        } else {
+            base.join(f)?
+        })
+    }
+
+    /// Make a new path.
+    ///
+    /// We want to be able to support local and remote files.
+    /// We also want to be able to support a configurable short cut format.
+    /// Following a pattern common in other package managers, `@XXXX/YYYY`
+    /// is used as short hand for the main branch in github repos.
+    ///
+    /// If `f` is a relative path, the result is relative to `self.cwd`.
+    pub fn file_path(&self, f: &str) -> Result<FilePath> {
+        Ok(if let Some(u) = self.resolve_url_shortcut(f)? {
+            u
+        } else {
+            FilePath::new(&self.cwd, f)?
+        })
+    }
+
+    /// Checks that the given string has a @organization/repo/ prefix.
+    /// If it does, then use that as a `repo_id` to look up the `FilePath` prefix
+    /// if it exists in the `config`, and use the `main` branch of the github repo if it
+    /// doesn't exist.
+    fn resolve_url_shortcut(&self, f: &str) -> Result<Option<FilePath>> {
+        if f.starts_with('@') {
+            let f = f.replacen('@', "", 1);
+            let parts = f.splitn(3, '/').collect::<Vec<&str>>();
+            match parts.as_slice() {
+                [user, repo, path] => {
+                    let key = format!("{}/{}", user, repo);
+                    Ok(if let Some(repo) = self.lookup_repo_path(user, repo) {
+                        Some(repo.join(path)?)
+                    } else {
+                        let repo = self.default_remote_path(key);
+                        Some(repo.join(path)?)
+                    })
+                }
+                _ => Err(FMLError::InvalidPath(format!(
+                    "'{}' needs to include a username, a repo and a filepath",
+                    f
+                ))),
+            }
+        } else {
+            Ok(None)
+        }
+    }
+
+    fn lookup_repo_path(&self, user: &str, repo: &str) -> Option<&FilePath> {
+        let key = format!("{}/{}", user, repo);
+        self.config.get(&key)
+    }
+}
+
+impl Drop for FileLoader {
+    fn drop(&mut self) {
+        if self.cache_dir.is_some() {
+            return;
+        }
+        let cache_dir = self.tmp_cache_dir();
+        if cache_dir.exists() {
+            _ = std::fs::remove_dir_all(cache_dir);
+        }
+    }
+}
+
+#[cfg(test)]
+mod unit_tests {
+    use std::fs;
+
+    use crate::util::{build_dir, pkg_dir};
+
+    use super::*;
+
+    #[test]
+    fn test_relative_paths() -> Result<()> {
+        let tmp = std::env::temp_dir();
+
+        let file = tmp.join("foo/bar.txt");
+        let obs = FilePath::from(file.as_path());
+
+        assert!(matches!(obs, FilePath::Local(_)));
+        assert!(obs.to_string().ends_with("foo/bar.txt"));
+
+        let obs = obs.join("baz.txt")?;
+        assert!(obs.to_string().ends_with("foo/baz.txt"));
+
+        let obs = obs.join("./bam.txt")?;
+        // We'd prefer it to be like this:
+        // assert!(obs.to_string().ends_with("foo/bam.txt"));
+        // But there's no easy way to get this (because symlinks).
+        // This is most likely the correct thing for us to do.
+        // We put this test here for documentation purposes, and to
+        // highlight that with URLs, ../ and ./ do what you might
+        // expect.
+        assert!(obs.to_string().ends_with("foo/./bam.txt"));
+
+        let obs = obs.join("https://example.com/foo/bar.txt")?;
+        assert!(matches!(obs, FilePath::Remote(_)));
+        assert_eq!(obs.to_string(), "https://example.com/foo/bar.txt");
+
+        let obs = obs.join("baz.txt")?;
+        assert_eq!(obs.to_string(), "https://example.com/foo/baz.txt");
+
+        let obs = obs.join("./bam.txt")?;
+        assert_eq!(obs.to_string(), "https://example.com/foo/bam.txt");
+
+        let obs = obs.join("../brum/bram.txt")?;
+        assert_eq!(obs.to_string(), "https://example.com/brum/bram.txt");
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_at_shorthand_with_no_at() -> Result<()> {
+        let files = create_loader()?;
+        let cwd = FilePath::Local(files.cwd.clone());
+        let src_file = cwd.join("base/old.txt")?;
+
+        // A source file asks for a destination file relative to it.
+        let obs = files.join(&src_file, "a/file.txt")?;
+        assert!(matches!(obs, FilePath::Local(_)));
+        assert_eq!(
+            obs.to_string(),
+            format!("{}/base/a/file.txt", remove_trailing_slash(&cwd))
+        );
+        Ok(())
+    }
+
+    #[test]
+    fn test_at_shorthand_default_branch() -> Result<()> {
+        let files = create_loader()?;
+        let cwd = FilePath::Local(files.cwd.clone());
+        let src_file = cwd.join("base/old.txt")?;
+
+        // A source file asks for a file in another repo. We haven't any specific configuration
+        // for this repo, so we default to the `main` branch.
+        let obs = files.join(&src_file, "@repo/unspecified/a/file.txt")?;
+        assert!(matches!(obs, FilePath::Remote(_)));
+        assert_eq!(
+            obs.to_string(),
+            "https://raw.githubusercontent.com/repo/unspecified/main/a/file.txt"
+        );
+        Ok(())
+    }
+
+    #[test]
+    fn test_at_shorthand_absolute_url() -> Result<()> {
+        let mut files = create_loader()?;
+        let cwd = FilePath::Local(files.cwd.clone());
+        let src_file = cwd.join("base/old.txt")?;
+
+        // A source file asks for a file in another repo. The loader uses an absolute
+        // URL as the base URL.
+        files.add_repo("@repos/url", "https://example.com/remote/directory/path")?;
+
+        let obs = files.join(&src_file, "@repos/url/a/file.txt")?;
+        assert!(matches!(obs, FilePath::Remote(_)));
+        assert_eq!(
+            obs.to_string(),
+            "https://example.com/remote/directory/path/a/file.txt"
+        );
+
+        let obs = files.file_path("@repos/url/b/file.txt")?;
+        assert!(matches!(obs, FilePath::Remote(_)));
+        assert_eq!(
+            obs.to_string(),
+            "https://example.com/remote/directory/path/b/file.txt"
+        );
+        Ok(())
+    }
+
+    #[test]
+    fn test_at_shorthand_specified_branch() -> Result<()> {
+        let mut files = create_loader()?;
+        let cwd = FilePath::Local(files.cwd.clone());
+        let src_file = cwd.join("base/old.txt")?;
+
+        // A source file asks for a file in another repo. The loader uses the branch/tag/ref
+        // specified.
+        files.add_repo("@repos/branch", "develop")?;
+        let obs = files.join(&src_file, "@repos/branch/a/file.txt")?;
+        assert!(matches!(obs, FilePath::Remote(_)));
+        assert_eq!(
+            obs.to_string(),
+            "https://raw.githubusercontent.com/repos/branch/develop/a/file.txt"
+        );
+
+        let obs = files.file_path("@repos/branch/b/file.txt")?;
+        assert!(matches!(obs, FilePath::Remote(_)));
+        assert_eq!(
+            obs.to_string(),
+            "https://raw.githubusercontent.com/repos/branch/develop/b/file.txt"
+        );
+        Ok(())
+    }
+
+    #[test]
+    fn test_at_shorthand_local_development() -> Result<()> {
+        let mut files = create_loader()?;
+        let cwd = FilePath::Local(files.cwd.clone());
+        let src_file = cwd.join("base/old.txt")?;
+
+        // A source file asks for a file in another repo. The loader is configured to
+        // give a file in a directory on the local filesystem.
+        let rel_dir = "../directory/path";
+        files.add_repo("@repos/local", rel_dir)?;
+
+        let obs = files.join(&src_file, "@repos/local/a/file.txt")?;
+        assert!(matches!(obs, FilePath::Local(_)));
+        assert_eq!(
+            obs.to_string(),
+            format!("{}/{}/a/file.txt", remove_trailing_slash(&cwd), rel_dir)
+        );
+
+        let obs = files.file_path("@repos/local/b/file.txt")?;
+        assert!(matches!(obs, FilePath::Local(_)));
+        assert_eq!(
+            obs.to_string(),
+            format!("{}/{}/b/file.txt", remove_trailing_slash(&cwd), rel_dir)
+        );
+
+        Ok(())
+    }
+
+    fn create_loader() -> Result<FileLoader, FMLError> {
+        let cache_dir = PathBuf::from(format!("{}/cache", build_dir()));
+        let config = Default::default();
+        let cwd = PathBuf::from(format!("{}/fixtures/", pkg_dir()));
+        let loader = FileLoader::new(cwd, Some(cache_dir), config)?;
+        Ok(loader)
+    }
+
+    #[test]
+    fn test_at_shorthand_from_config_file() -> Result<()> {
+        let cwd = PathBuf::from(pkg_dir());
+
+        let config = &LoaderConfig {
+            cwd,
+            cache_dir: None,
+            repo_files: vec![
+                "fixtures/loaders/config_files/remote.json".to_string(),
+                "fixtures/loaders/config_files/local.yaml".to_string(),
+            ],
+            refs: Default::default(),
+        };
+
+        let files: FileLoader = config.try_into()?;
+        let cwd = FilePath::Local(files.cwd.clone());
+
+        // This is a remote repo, specified in remote.json.
+        let tfr = files.file_path("@my/remote/file.txt")?;
+        assert_eq!(
+            tfr.to_string(),
+            "https://example.com/repo/branch/file.txt".to_string()
+        );
+
+        // This is a local file, specified in local.yaml
+        let tf1 = files.file_path("@test/nested1/test-file.txt")?;
+        assert_eq!(
+            tf1.to_string(),
+            format!(
+                "{}/fixtures/loaders/config_files/./nested-1/test-file.txt",
+                &cwd
+            )
+        );
+
+        // This is a remote repo, specified in remote.json, but overridden in local.yaml
+        let tf2 = files.file_path("@test/nested2/test-file.txt")?;
+        assert_eq!(
+            tf2.to_string(),
+            format!(
+                "{}/fixtures/loaders/config_files/./nested-2/test-file.txt",
+                &cwd
+            )
+        );
+
+        let tf1 = files.read_to_string(&tf1)?;
+        let tf2 = files.read_to_string(&tf2)?;
+
+        assert_eq!("test-file/1".to_string(), tf1);
+        assert_eq!("test-file/2".to_string(), tf2);
+
+        Ok(())
+    }
+
+    fn remove_trailing_slash(cwd: &FilePath) -> String {
+        let s = cwd.to_string();
+        let mut chars = s.chars();
+        if s.ends_with('/') {
+            chars.next_back();
+        }
+        chars.as_str().to_string()
+    }
+
+    #[test]
+    fn test_at_shorthand_override_via_cli() -> Result<()> {
+        let cwd = PathBuf::from(pkg_dir());
+
+        let config = &LoaderConfig {
+            cwd,
+            cache_dir: None,
+            repo_files: Default::default(),
+            refs: BTreeMap::from([("@my-remote/repo".to_string(), "cli-branch".to_string())]),
+        };
+
+        let files: FileLoader = config.try_into()?;
+
+        // This is a file from the remote repo
+        let tfr = files.file_path("@my-remote/repo/path/to/file.txt")?;
+        assert_eq!(
+            tfr.to_string(),
+            // We're going to fetch it from the `cli-branch` of the repo.
+            "https://raw.githubusercontent.com/my-remote/repo/cli-branch/path/to/file.txt"
+                .to_string()
+        );
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_dropping_tmp_cache_dir() -> Result<()> {
+        let cwd = PathBuf::from(pkg_dir());
+        let config = &LoaderConfig {
+            cwd,
+            cache_dir: None,
+            repo_files: Default::default(),
+            refs: Default::default(),
+        };
+
+        let files: FileLoader = config.try_into()?;
+        let cache_dir = files.tmp_cache_dir();
+        fs::create_dir_all(cache_dir)?;
+
+        assert!(cache_dir.exists());
+        drop(files);
+
+        assert!(!cache_dir.exists());
+        Ok(())
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nimbus_fml/util/mod.rs.html b/book/rust-docs/src/nimbus_fml/util/mod.rs.html new file mode 100644 index 0000000000..3668dff9cd --- /dev/null +++ b/book/rust-docs/src/nimbus_fml/util/mod.rs.html @@ -0,0 +1,85 @@ +mod.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use std::{env, path::PathBuf};
+
+pub mod loaders;
+
+pub(crate) fn pkg_dir() -> String {
+    env::var("CARGO_MANIFEST_DIR")
+        .expect("Missing $CARGO_MANIFEST_DIR, cannot build tests for generated bindings")
+}
+
+pub(crate) fn join(base: String, suffix: &str) -> String {
+    [base, suffix.to_string()]
+        .iter()
+        .collect::<PathBuf>()
+        .to_string_lossy()
+        .to_string()
+}
+
+// The Application Services directory
+#[allow(dead_code)]
+pub(crate) fn as_dir() -> String {
+    join(pkg_dir(), "../../..")
+}
+
+// The Nimbus SDK directory
+#[allow(dead_code)]
+pub(crate) fn sdk_dir() -> String {
+    join(as_dir(), "components/nimbus")
+}
+
+#[allow(dead_code)]
+pub(crate) fn build_dir() -> String {
+    join(pkg_dir(), "build")
+}
+
+#[allow(dead_code)]
+pub(crate) fn generated_src_dir() -> String {
+    join(build_dir(), "generated")
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nss/aes.rs.html b/book/rust-docs/src/nss/aes.rs.html new file mode 100644 index 0000000000..5617988e89 --- /dev/null +++ b/book/rust-docs/src/nss/aes.rs.html @@ -0,0 +1,233 @@ +aes.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::{
+    error::*,
+    pk11::sym_key::import_sym_key,
+    util::{ensure_nss_initialized, map_nss_secstatus, ScopedPtr},
+};
+use std::{
+    mem,
+    os::raw::{c_uchar, c_uint},
+};
+
+const AES_GCM_TAG_LENGTH: usize = 16;
+
+#[derive(Debug, Copy, Clone)]
+pub enum Operation {
+    Encrypt,
+    Decrypt,
+}
+
+pub fn aes_gcm_crypt(
+    key: &[u8],
+    nonce: &[u8],
+    aad: &[u8],
+    data: &[u8],
+    operation: Operation,
+) -> Result<Vec<u8>> {
+    let mut gcm_params = nss_sys::CK_GCM_PARAMS {
+        pIv: nonce.as_ptr() as nss_sys::CK_BYTE_PTR,
+        ulIvLen: nss_sys::CK_ULONG::try_from(nonce.len())?,
+        ulIvBits: nss_sys::CK_ULONG::try_from(
+            nonce.len().checked_mul(8).ok_or(ErrorKind::InternalError)?,
+        )?,
+        pAAD: aad.as_ptr() as nss_sys::CK_BYTE_PTR,
+        ulAADLen: nss_sys::CK_ULONG::try_from(aad.len())?,
+        ulTagBits: nss_sys::CK_ULONG::try_from(AES_GCM_TAG_LENGTH * 8)?,
+    };
+    let mut params = nss_sys::SECItem {
+        type_: nss_sys::SECItemType::siBuffer as u32,
+        data: &mut gcm_params as *mut _ as *mut c_uchar,
+        len: c_uint::try_from(mem::size_of::<nss_sys::CK_GCM_PARAMS>())?,
+    };
+    common_crypt(
+        nss_sys::CKM_AES_GCM.into(),
+        key,
+        data,
+        AES_GCM_TAG_LENGTH,
+        &mut params,
+        operation,
+    )
+}
+
+pub fn aes_cbc_crypt(
+    key: &[u8],
+    nonce: &[u8],
+    data: &[u8],
+    operation: Operation,
+) -> Result<Vec<u8>> {
+    let mut params = nss_sys::SECItem {
+        type_: nss_sys::SECItemType::siBuffer as u32,
+        data: nonce.as_ptr() as *mut c_uchar,
+        len: c_uint::try_from(nonce.len())?,
+    };
+    common_crypt(
+        nss_sys::CKM_AES_CBC_PAD.into(),
+        key,
+        data,
+        usize::try_from(nss_sys::AES_BLOCK_SIZE)?, // CBC mode might pad the result.
+        &mut params,
+        operation,
+    )
+}
+
+pub fn common_crypt(
+    mech: nss_sys::CK_MECHANISM_TYPE,
+    key: &[u8],
+    data: &[u8],
+    extra_data_len: usize,
+    params: &mut nss_sys::SECItem,
+    operation: Operation,
+) -> Result<Vec<u8>> {
+    ensure_nss_initialized();
+    // Most of the following code is inspired by the Firefox WebCrypto implementation:
+    // https://searchfox.org/mozilla-central/rev/f46e2bf881d522a440b30cbf5cf8d76fc212eaf4/dom/crypto/WebCryptoTask.cpp#566
+    // CKA_ENCRYPT always is fine.
+    let sym_key = import_sym_key(mech, nss_sys::CKA_ENCRYPT.into(), key)?;
+    // Initialize the output buffer (enough space for padding / a full tag).
+    let result_max_len = data
+        .len()
+        .checked_add(extra_data_len)
+        .ok_or(ErrorKind::InternalError)?;
+    let mut out_len: c_uint = 0;
+    let mut out = vec![0u8; result_max_len];
+    let result_max_len_uint = c_uint::try_from(result_max_len)?;
+    let data_len = c_uint::try_from(data.len())?;
+    let f = match operation {
+        Operation::Decrypt => nss_sys::PK11_Decrypt,
+        Operation::Encrypt => nss_sys::PK11_Encrypt,
+    };
+    map_nss_secstatus(|| unsafe {
+        f(
+            sym_key.as_mut_ptr(),
+            mech,
+            params,
+            out.as_mut_ptr(),
+            &mut out_len,
+            result_max_len_uint,
+            data.as_ptr(),
+            data_len,
+        )
+    })?;
+    out.truncate(usize::try_from(out_len)?);
+    Ok(out)
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nss/cert.rs.html b/book/rust-docs/src/nss/cert.rs.html new file mode 100644 index 0000000000..904c98863e --- /dev/null +++ b/book/rust-docs/src/nss/cert.rs.html @@ -0,0 +1,87 @@ +cert.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::error::*;
+use crate::pk11::types::{Certificate, PublicKey};
+use crate::util::{ensure_nss_initialized, sec_item_as_slice, ScopedPtr};
+use nss_sys::{CERT_ExtractPublicKey, CERT_GetDefaultCertDB, CERT_NewTempCertificate};
+
+pub fn extract_ec_public_key(der: &[u8]) -> Result<Vec<u8>> {
+    ensure_nss_initialized();
+
+    let certdb = unsafe { CERT_GetDefaultCertDB() };
+    let mut data = nss_sys::SECItem {
+        len: u32::try_from(der.len())?,
+        data: der.as_ptr() as *mut u8,
+        type_: nss_sys::SECItemType::siBuffer as u32,
+    };
+
+    let cert = unsafe {
+        Certificate::from_ptr(CERT_NewTempCertificate(
+            certdb,
+            &mut data,
+            std::ptr::null_mut(),
+            nss_sys::PR_FALSE,
+            nss_sys::PR_TRUE,
+        ))?
+    };
+
+    let pub_key = unsafe { PublicKey::from_ptr(CERT_ExtractPublicKey(cert.as_mut_ptr()))? };
+    let pub_key_raw = unsafe { &*pub_key.as_ptr() };
+
+    if pub_key_raw.keyType != nss_sys::KeyType::ecKey as u32 {
+        return Err(
+            ErrorKind::InputError("public key is not of type EC (Elliptic Curve).".into()).into(),
+        );
+    }
+
+    let mut pub_key_data = unsafe { pub_key_raw.u.ec.publicValue };
+    let pub_key_data_raw = unsafe { sec_item_as_slice(&mut pub_key_data)? };
+
+    Ok(pub_key_data_raw.to_vec())
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nss/ec.rs.html b/book/rust-docs/src/nss/ec.rs.html new file mode 100644 index 0000000000..db5bd0a7af --- /dev/null +++ b/book/rust-docs/src/nss/ec.rs.html @@ -0,0 +1,845 @@ +ec.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::{
+    error::*,
+    pk11::{
+        self,
+        context::HashAlgorithm,
+        slot,
+        types::{Pkcs11Object, PrivateKey as PK11PrivateKey, PublicKey as PK11PublicKey},
+    },
+    util::{ensure_nss_initialized, map_nss_secstatus, sec_item_as_slice, ScopedPtr},
+};
+use serde_derive::{Deserialize, Serialize};
+use std::{
+    mem,
+    ops::Deref,
+    os::raw::{c_uchar, c_uint, c_void},
+    ptr,
+};
+
+#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq)]
+#[repr(u8)]
+pub enum Curve {
+    P256,
+    P384,
+}
+
+impl Curve {
+    pub fn get_field_len(&self) -> u32 {
+        match &self {
+            Curve::P256 => 32,
+            Curve::P384 => 48,
+        }
+    }
+}
+
+const CRV_P256: &str = "P-256";
+const CRV_P384: &str = "P-384";
+
+#[derive(Serialize, Deserialize, Clone, Debug)]
+pub struct EcKey {
+    curve: String,
+    // The `d` value of the EC Key.
+    private_key: Vec<u8>,
+    // The uncompressed x,y-representation of the public component of the EC Key.
+    public_key: Vec<u8>,
+}
+
+impl EcKey {
+    pub fn new(curve: Curve, private_key: &[u8], public_key: &[u8]) -> Self {
+        let curve = match curve {
+            Curve::P256 => CRV_P256,
+            Curve::P384 => CRV_P384,
+        };
+        Self {
+            curve: curve.to_owned(),
+            private_key: private_key.to_vec(),
+            public_key: public_key.to_vec(),
+        }
+    }
+
+    pub fn from_coordinates(curve: Curve, d: &[u8], x: &[u8], y: &[u8]) -> Result<Self> {
+        let ec_point = create_ec_point_for_coordinates(x, y)?;
+        Ok(EcKey::new(curve, d, &ec_point))
+    }
+
+    pub fn curve(&self) -> Curve {
+        if self.curve == CRV_P256 {
+            return Curve::P256;
+        } else if self.curve == CRV_P384 {
+            return Curve::P384;
+        }
+        unimplemented!("It is impossible to create a curve object with a different CRV.")
+    }
+
+    pub fn public_key(&self) -> &[u8] {
+        &self.public_key
+    }
+
+    pub fn private_key(&self) -> &[u8] {
+        &self.private_key
+    }
+}
+
+fn create_ec_point_for_coordinates(x: &[u8], y: &[u8]) -> Result<Vec<u8>> {
+    if x.len() != y.len() {
+        return Err(ErrorKind::InternalError.into());
+    }
+    let mut buf = vec![0u8; x.len() + y.len() + 1];
+    buf[0] = u8::try_from(nss_sys::EC_POINT_FORM_UNCOMPRESSED)?;
+    let mut offset = 1;
+    buf[offset..offset + x.len()].copy_from_slice(x);
+    offset += x.len();
+    buf[offset..offset + y.len()].copy_from_slice(y);
+    Ok(buf)
+}
+
+pub fn generate_keypair(curve: Curve) -> Result<(PrivateKey, PublicKey)> {
+    ensure_nss_initialized();
+    // 1. Create EC params
+    let params_buf = create_ec_params_for_curve(curve)?;
+    let mut params = nss_sys::SECItem {
+        type_: nss_sys::SECItemType::siBuffer as u32,
+        data: params_buf.as_ptr() as *mut c_uchar,
+        len: c_uint::try_from(params_buf.len())?,
+    };
+
+    // 2. Generate the key pair
+    // The following code is adapted from:
+    // https://searchfox.org/mozilla-central/rev/f46e2bf881d522a440b30cbf5cf8d76fc212eaf4/dom/crypto/WebCryptoTask.cpp#2389
+    let mech = nss_sys::CKM_EC_KEY_PAIR_GEN;
+    let slot = slot::get_internal_slot()?;
+    let mut pub_key: *mut nss_sys::SECKEYPublicKey = ptr::null_mut();
+    let prv_key = PrivateKey::from(curve, unsafe {
+        PK11PrivateKey::from_ptr(nss_sys::PK11_GenerateKeyPair(
+            slot.as_mut_ptr(),
+            mech.into(),
+            &mut params as *mut _ as *mut c_void,
+            &mut pub_key,
+            nss_sys::PR_FALSE,
+            nss_sys::PR_FALSE,
+            ptr::null_mut(),
+        ))?
+    });
+    let pub_key = PublicKey::from(curve, unsafe { PK11PublicKey::from_ptr(pub_key)? });
+    Ok((prv_key, pub_key))
+}
+
+pub struct PrivateKey {
+    curve: Curve,
+    wrapped: PK11PrivateKey,
+}
+
+impl Deref for PrivateKey {
+    type Target = PK11PrivateKey;
+    #[inline]
+    fn deref(&self) -> &PK11PrivateKey {
+        &self.wrapped
+    }
+}
+
+impl PrivateKey {
+    pub fn convert_to_public_key(&self) -> Result<PublicKey> {
+        let mut pub_key = self.wrapped.convert_to_public_key()?;
+
+        // Workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=1562046.
+        let field_len = self.curve.get_field_len();
+        let expected_len = 2 * field_len + 1;
+        let mut pub_value = unsafe { (*pub_key.as_ptr()).u.ec.publicValue };
+        if pub_value.len == expected_len - 2 {
+            let old_pub_value_raw = unsafe { sec_item_as_slice(&mut pub_value)?.to_vec() };
+            let mut new_pub_value_raw = vec![0u8; usize::try_from(expected_len)?];
+            new_pub_value_raw[0] = u8::try_from(nss_sys::EC_POINT_FORM_UNCOMPRESSED)?;
+            new_pub_value_raw[1] = u8::try_from(old_pub_value_raw.len())?;
+            new_pub_value_raw[2..].copy_from_slice(&old_pub_value_raw);
+            pub_key = PublicKey::from_bytes(self.curve, &new_pub_value_raw)?.wrapped;
+        }
+        Ok(PublicKey {
+            wrapped: pub_key,
+            curve: self.curve,
+        })
+    }
+
+    #[inline]
+    pub(crate) fn from(curve: Curve, key: PK11PrivateKey) -> Self {
+        Self {
+            curve,
+            wrapped: key,
+        }
+    }
+
+    pub fn curve(&self) -> Curve {
+        self.curve
+    }
+
+    pub fn private_value(&self) -> Result<Vec<u8>> {
+        let mut private_value = self.read_raw_attribute(nss_sys::CKA_VALUE.into()).unwrap();
+        let private_key = unsafe { sec_item_as_slice(private_value.as_mut_ref())?.to_vec() };
+        Ok(private_key)
+    }
+
+    fn from_nss_params(
+        curve: Curve,
+        ec_params: &[u8],
+        ec_point: &[u8],
+        private_value: &[u8],
+    ) -> Result<Self> {
+        // The following code is adapted from:
+        // https://searchfox.org/mozilla-central/rev/444ee13e14fe30451651c0f62b3979c76766ada4/dom/crypto/CryptoKey.cpp#322
+        // These explicit variable type declarations are *VERY* important, as we pass to NSS a pointer to them
+        // and we need these variables to be of the right size!
+        let mut private_key_value: nss_sys::CK_OBJECT_CLASS = nss_sys::CKO_PRIVATE_KEY.into();
+        let mut false_value: nss_sys::CK_BBOOL = nss_sys::CK_FALSE;
+        let mut ec_value: nss_sys::CK_KEY_TYPE = nss_sys::CKK_EC.into();
+        let bbool_size = mem::size_of::<nss_sys::CK_BBOOL>();
+        let key_template = vec![
+            ck_attribute(
+                nss_sys::CKA_CLASS.into(),
+                &mut private_key_value as *mut _ as *mut c_void,
+                mem::size_of::<nss_sys::CK_OBJECT_CLASS>(),
+            )?,
+            ck_attribute(
+                nss_sys::CKA_KEY_TYPE.into(),
+                &mut ec_value as *mut _ as *mut c_void,
+                mem::size_of::<nss_sys::CK_KEY_TYPE>(),
+            )?,
+            ck_attribute(
+                nss_sys::CKA_TOKEN.into(),
+                &mut false_value as *mut _ as *mut c_void,
+                bbool_size,
+            )?,
+            ck_attribute(
+                nss_sys::CKA_SENSITIVE.into(),
+                &mut false_value as *mut _ as *mut c_void,
+                bbool_size,
+            )?,
+            ck_attribute(
+                nss_sys::CKA_PRIVATE.into(),
+                &mut false_value as *mut _ as *mut c_void,
+                bbool_size,
+            )?,
+            // PrivateKeyFromPrivateKeyTemplate sets the ID.
+            ck_attribute(nss_sys::CKA_ID.into(), ptr::null_mut(), 0)?,
+            ck_attribute(
+                nss_sys::CKA_EC_PARAMS.into(),
+                ec_params.as_ptr() as *mut c_void,
+                ec_params.len(),
+            )?,
+            ck_attribute(
+                nss_sys::CKA_EC_POINT.into(),
+                ec_point.as_ptr() as *mut c_void,
+                ec_point.len(),
+            )?,
+            ck_attribute(
+                nss_sys::CKA_VALUE.into(),
+                private_value.as_ptr() as *mut c_void,
+                private_value.len(),
+            )?,
+        ];
+        Ok(Self::from(
+            curve,
+            PK11PrivateKey::from_private_key_template(key_template)?,
+        ))
+    }
+
+    pub fn import(ec_key: &EcKey) -> Result<Self> {
+        // The following code is adapted from:
+        // https://searchfox.org/mozilla-central/rev/66086345467c69685434dd1c5177b30a7511b1a5/dom/crypto/CryptoKey.cpp#652
+        ensure_nss_initialized();
+        let curve = ec_key.curve();
+        let ec_params = create_ec_params_for_curve(curve)?;
+        Self::from_nss_params(curve, &ec_params, &ec_key.public_key, &ec_key.private_key)
+    }
+
+    pub fn export(&self) -> Result<EcKey> {
+        let public_key = self.convert_to_public_key()?;
+        let public_key_bytes = public_key.to_bytes()?;
+        let private_key_bytes = self.private_value()?;
+        Ok(EcKey::new(
+            self.curve,
+            &private_key_bytes,
+            &public_key_bytes,
+        ))
+    }
+}
+
+#[inline]
+fn ck_attribute(
+    r#type: nss_sys::CK_ATTRIBUTE_TYPE,
+    p_value: nss_sys::CK_VOID_PTR,
+    value_len: usize,
+) -> Result<nss_sys::CK_ATTRIBUTE> {
+    Ok(nss_sys::CK_ATTRIBUTE {
+        type_: r#type,
+        pValue: p_value,
+        ulValueLen: nss_sys::CK_ULONG::try_from(value_len)?,
+    })
+}
+
+pub struct PublicKey {
+    curve: Curve,
+    wrapped: PK11PublicKey,
+}
+
+impl Deref for PublicKey {
+    type Target = PK11PublicKey;
+    #[inline]
+    fn deref(&self) -> &PK11PublicKey {
+        &self.wrapped
+    }
+}
+
+impl PublicKey {
+    #[inline]
+    pub(crate) fn from(curve: Curve, key: PK11PublicKey) -> Self {
+        Self {
+            curve,
+            wrapped: key,
+        }
+    }
+
+    pub fn curve(&self) -> Curve {
+        self.curve
+    }
+
+    /// ECDSA verify operation
+    pub fn verify(
+        &self,
+        message: &[u8],
+        signature: &[u8],
+        hash_algorithm: HashAlgorithm,
+    ) -> Result<()> {
+        // The following code is adapted from:
+        // https://searchfox.org/mozilla-central/rev/b2716c233e9b4398fc5923cbe150e7f83c7c6c5b/dom/crypto/WebCryptoTask.cpp#1144
+        let signature = nss_sys::SECItem {
+            len: u32::try_from(signature.len())?,
+            data: signature.as_ptr() as *mut u8,
+            type_: 0,
+        };
+        let hash = pk11::context::hash_buf(&hash_algorithm, message)?;
+        let hash = nss_sys::SECItem {
+            len: u32::try_from(hash.len())?,
+            data: hash.as_ptr() as *mut u8,
+            type_: 0,
+        };
+        map_nss_secstatus(|| unsafe {
+            nss_sys::PK11_VerifyWithMechanism(
+                self.as_mut_ptr(),
+                nss_sys::PK11_MapSignKeyType((*self.wrapped.as_ptr()).keyType),
+                ptr::null(),
+                &signature,
+                &hash,
+                ptr::null_mut(),
+            )
+        })?;
+        Ok(())
+    }
+
+    pub fn to_bytes(&self) -> Result<Vec<u8>> {
+        // Some public keys we create do not have an associated PCKS#11 slot
+        // therefore we cannot use `read_raw_attribute(CKA_EC_POINT)`
+        // so we read the `publicValue` field directly instead.
+        let mut ec_point = unsafe { (*self.as_ptr()).u.ec.publicValue };
+        let public_key = unsafe { sec_item_as_slice(&mut ec_point)?.to_vec() };
+        check_pub_key_bytes(&public_key, self.curve)?;
+        Ok(public_key)
+    }
+
+    pub fn from_bytes(curve: Curve, bytes: &[u8]) -> Result<PublicKey> {
+        // The following code is adapted from:
+        // https://searchfox.org/mozilla-central/rev/ec489aa170b6486891cf3625717d6fa12bcd11c1/dom/crypto/CryptoKey.cpp#1078
+        check_pub_key_bytes(bytes, curve)?;
+        let key_data = nss_sys::SECItem {
+            type_: nss_sys::SECItemType::siBuffer as u32,
+            data: bytes.as_ptr() as *mut c_uchar,
+            len: c_uint::try_from(bytes.len())?,
+        };
+        let params_buf = create_ec_params_for_curve(curve)?;
+        let params = nss_sys::SECItem {
+            type_: nss_sys::SECItemType::siBuffer as u32,
+            data: params_buf.as_ptr() as *mut c_uchar,
+            len: c_uint::try_from(params_buf.len())?,
+        };
+
+        let pub_key = nss_sys::SECKEYPublicKey {
+            arena: ptr::null_mut(),
+            keyType: nss_sys::KeyType::ecKey as u32,
+            pkcs11Slot: ptr::null_mut(),
+            pkcs11ID: nss_sys::CK_INVALID_HANDLE.into(),
+            u: nss_sys::SECKEYPublicKeyStr_u {
+                ec: nss_sys::SECKEYECPublicKey {
+                    DEREncodedParams: params,
+                    publicValue: key_data,
+                    encoding: nss_sys::ECPointEncoding::ECPoint_Uncompressed as u32,
+                    size: 0,
+                },
+            },
+        };
+        Ok(Self::from(curve, unsafe {
+            PK11PublicKey::from_ptr(nss_sys::SECKEY_CopyPublicKey(&pub_key))?
+        }))
+    }
+}
+
+fn check_pub_key_bytes(bytes: &[u8], curve: Curve) -> Result<()> {
+    let field_len = curve.get_field_len();
+    // Check length of uncompressed point coordinates. There are 2 field elements
+    // and a leading "point form" octet (which must be EC_POINT_FORM_UNCOMPRESSED).
+    if bytes.len() != usize::try_from(2 * field_len + 1)? {
+        return Err(ErrorKind::InternalError.into());
+    }
+    // No support for compressed points.
+    if bytes[0] != u8::try_from(nss_sys::EC_POINT_FORM_UNCOMPRESSED)? {
+        return Err(ErrorKind::InternalError.into());
+    }
+    Ok(())
+}
+
+fn create_ec_params_for_curve(curve: Curve) -> Result<Vec<u8>> {
+    // The following code is adapted from:
+    // https://searchfox.org/mozilla-central/rev/ec489aa170b6486891cf3625717d6fa12bcd11c1/dom/crypto/WebCryptoCommon.h#299
+    let curve_oid_tag = match curve {
+        Curve::P256 => nss_sys::SECOidTag::SEC_OID_SECG_EC_SECP256R1,
+        Curve::P384 => nss_sys::SECOidTag::SEC_OID_SECG_EC_SECP384R1,
+    };
+    // Retrieve curve data by OID tag.
+    let oid_data = unsafe { nss_sys::SECOID_FindOIDByTag(curve_oid_tag as u32) };
+    if oid_data.is_null() {
+        return Err(ErrorKind::InternalError.into());
+    }
+    // Set parameters
+    let oid_data_len = unsafe { (*oid_data).oid.len };
+    let mut buf = vec![0u8; usize::try_from(oid_data_len)? + 2];
+    buf[0] = c_uchar::try_from(nss_sys::SEC_ASN1_OBJECT_ID)?;
+    buf[1] = c_uchar::try_from(oid_data_len)?;
+    let oid_data_data =
+        unsafe { std::slice::from_raw_parts((*oid_data).oid.data, usize::try_from(oid_data_len)?) };
+    buf[2..].copy_from_slice(oid_data_data);
+    Ok(buf)
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nss/ecdh.rs.html b/book/rust-docs/src/nss/ecdh.rs.html new file mode 100644 index 0000000000..7e15fee86a --- /dev/null +++ b/book/rust-docs/src/nss/ecdh.rs.html @@ -0,0 +1,93 @@ +ecdh.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::{
+    ec::{PrivateKey, PublicKey},
+    error::*,
+    pk11::types::SymKey,
+    util::{ensure_nss_initialized, map_nss_secstatus, sec_item_as_slice, ScopedPtr},
+};
+
+pub fn ecdh_agreement(priv_key: &PrivateKey, pub_key: &PublicKey) -> Result<Vec<u8>> {
+    ensure_nss_initialized();
+    if priv_key.curve() != pub_key.curve() {
+        return Err(ErrorKind::InternalError.into());
+    }
+    // The following code is adapted from:
+    // https://searchfox.org/mozilla-central/rev/444ee13e14fe30451651c0f62b3979c76766ada4/dom/crypto/WebCryptoTask.cpp#2835
+
+    // CKM_SHA512_HMAC and CKA_SIGN are key type and usage attributes of the
+    // derived symmetric key and don't matter because we ignore them anyway.
+    let sym_key = unsafe {
+        SymKey::from_ptr(nss_sys::PK11_PubDeriveWithKDF(
+            priv_key.as_mut_ptr(),
+            pub_key.as_mut_ptr(),
+            nss_sys::PR_FALSE,
+            std::ptr::null_mut(),
+            std::ptr::null_mut(),
+            nss_sys::CKM_ECDH1_DERIVE.into(),
+            nss_sys::CKM_SHA512_HMAC.into(),
+            nss_sys::CKA_SIGN.into(),
+            0,
+            nss_sys::CKD_NULL.into(),
+            std::ptr::null_mut(),
+            std::ptr::null_mut(),
+        ))?
+    };
+
+    map_nss_secstatus(|| unsafe { nss_sys::PK11_ExtractKeyValue(sym_key.as_mut_ptr()) })?;
+
+    // This doesn't leak, because the SECItem* returned by PK11_GetKeyData
+    // just refers to a buffer managed by `sym_key` which we copy into `buf`.
+    let mut key_data = unsafe { *nss_sys::PK11_GetKeyData(sym_key.as_mut_ptr()) };
+    let buf = unsafe { sec_item_as_slice(&mut key_data)? };
+    Ok(buf.to_vec())
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nss/error.rs.html b/book/rust-docs/src/nss/error.rs.html new file mode 100644 index 0000000000..73b83f606c --- /dev/null +++ b/book/rust-docs/src/nss/error.rs.html @@ -0,0 +1,73 @@ +error.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#[derive(Debug, thiserror::Error)]
+pub enum ErrorKind {
+    #[error("NSS could not be initialized")]
+    NSSInitFailure,
+    #[error("NSS error: {0} {1}")]
+    NSSError(i32, String),
+    #[error("SSL error: {0} {1}")]
+    SSLError(i32, String),
+    #[error("PKIX error: {0} {1}")]
+    PKIXError(i32, String),
+    #[error("Input or format error: {0}")]
+    InputError(String),
+    #[error("Internal crypto error")]
+    InternalError,
+    #[error("Conversion error: {0}")]
+    ConversionError(#[from] std::num::TryFromIntError),
+    #[error("Base64 decode error: {0}")]
+    Base64Decode(#[from] base64::DecodeError),
+    #[error("Certificate issuer does not match")]
+    CertificateIssuerError,
+    #[error("Certificate subject does not match")]
+    CertificateSubjectError,
+    #[error("Certificate not yet valid or expired")]
+    CertificateValidityError,
+}
+
+error_support::define_error! {
+    ErrorKind {
+        (Base64Decode, base64::DecodeError),
+        (ConversionError, std::num::TryFromIntError),
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nss/lib.rs.html b/book/rust-docs/src/nss/lib.rs.html new file mode 100644 index 0000000000..b740c561a8 --- /dev/null +++ b/book/rust-docs/src/nss/lib.rs.html @@ -0,0 +1,39 @@ +lib.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#![allow(unknown_lints)]
+#![warn(rust_2018_idioms)]
+#[macro_use]
+mod util;
+pub mod aes;
+pub mod cert;
+pub mod ec;
+pub mod ecdh;
+mod error;
+pub mod pbkdf2;
+pub mod pk11;
+pub mod pkixc;
+pub mod secport;
+pub use crate::error::{Error, ErrorKind, Result};
+pub use util::ensure_nss_initialized as ensure_initialized;
+
\ No newline at end of file diff --git a/book/rust-docs/src/nss/pbkdf2.rs.html b/book/rust-docs/src/nss/pbkdf2.rs.html new file mode 100644 index 0000000000..72cbb7e93c --- /dev/null +++ b/book/rust-docs/src/nss/pbkdf2.rs.html @@ -0,0 +1,155 @@ +pbkdf2.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::util::{ensure_nss_initialized, map_nss_secstatus, sec_item_as_slice, ScopedPtr};
+use crate::{
+    error::*,
+    pk11::{
+        slot::get_internal_slot,
+        types::{AlgorithmID, SymKey},
+    },
+};
+
+// Expose for consumers to choose the hashing algorithm
+// Currently only SHA256 supported
+pub use crate::pk11::context::HashAlgorithm;
+use nss_sys::SECOidTag;
+
+// ***** BASED ON THE FOLLOWING IMPLEMENTATION *****
+// https://searchfox.org/mozilla-central/rev/8ccea36c4fb09412609fb738c722830d7098602b/dom/crypto/WebCryptoTask.cpp#2567
+
+pub fn pbkdf2_key_derive(
+    password: &[u8],
+    salt: &[u8],
+    iterations: u32,
+    hash_algorithm: HashAlgorithm,
+    out: &mut [u8],
+) -> Result<()> {
+    ensure_nss_initialized();
+    let oid_tag = match hash_algorithm {
+        HashAlgorithm::SHA256 => SECOidTag::SEC_OID_HMAC_SHA256 as u32,
+        HashAlgorithm::SHA384 => SECOidTag::SEC_OID_HMAC_SHA384 as u32,
+    };
+    let mut sec_salt = nss_sys::SECItem {
+        len: u32::try_from(salt.len())?,
+        data: salt.as_ptr() as *mut u8,
+        type_: 0,
+    };
+    let alg_id = unsafe {
+        AlgorithmID::from_ptr(nss_sys::PK11_CreatePBEV2AlgorithmID(
+            SECOidTag::SEC_OID_PKCS5_PBKDF2 as u32,
+            SECOidTag::SEC_OID_HMAC_SHA1 as u32,
+            oid_tag,
+            i32::try_from(out.len())?,
+            i32::try_from(iterations)?,
+            &mut sec_salt as *mut nss_sys::SECItem,
+        ))?
+    };
+
+    let slot = get_internal_slot()?;
+    let mut sec_pw = nss_sys::SECItem {
+        len: u32::try_from(password.len())?,
+        data: password.as_ptr() as *mut u8,
+        type_: 0,
+    };
+    let sym_key = unsafe {
+        SymKey::from_ptr(nss_sys::PK11_PBEKeyGen(
+            slot.as_mut_ptr(),
+            alg_id.as_mut_ptr(),
+            &mut sec_pw as *mut nss_sys::SECItem,
+            nss_sys::PR_FALSE,
+            std::ptr::null_mut(),
+        ))?
+    };
+    map_nss_secstatus(|| unsafe { nss_sys::PK11_ExtractKeyValue(sym_key.as_mut_ptr()) })?;
+
+    // This doesn't leak, because the SECItem* returned by PK11_GetKeyData
+    // just refers to a buffer managed by `sym_key` which we copy into `buf`
+    let mut key_data = unsafe { *nss_sys::PK11_GetKeyData(sym_key.as_mut_ptr()) };
+    let buf = unsafe { sec_item_as_slice(&mut key_data)? };
+    // Stop panic in swap_with_slice by returning an error if the sizes mismatch
+    if buf.len() != out.len() {
+        return Err(ErrorKind::InternalError.into());
+    }
+    out.swap_with_slice(buf);
+    Ok(())
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nss/pk11/context.rs.html b/book/rust-docs/src/nss/pk11/context.rs.html new file mode 100644 index 0000000000..c742c6b1fc --- /dev/null +++ b/book/rust-docs/src/nss/pk11/context.rs.html @@ -0,0 +1,247 @@ +context.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::{
+    error::*,
+    pk11::{
+        sym_key::import_sym_key,
+        types::{Context, SymKey},
+    },
+    util::{ensure_nss_initialized, map_nss_secstatus, ScopedPtr},
+};
+use std::ptr;
+
+#[derive(Copy, Clone, Debug)]
+#[repr(u8)]
+pub enum HashAlgorithm {
+    SHA256,
+    SHA384,
+}
+
+impl HashAlgorithm {
+    fn result_len(&self) -> u32 {
+        match self {
+            HashAlgorithm::SHA256 => nss_sys::SHA256_LENGTH,
+            HashAlgorithm::SHA384 => nss_sys::SHA384_LENGTH,
+        }
+    }
+
+    fn as_hmac_mechanism(&self) -> u32 {
+        match self {
+            HashAlgorithm::SHA256 => nss_sys::CKM_SHA256_HMAC,
+            HashAlgorithm::SHA384 => nss_sys::CKM_SHA384_HMAC,
+        }
+    }
+
+    pub(crate) fn as_hkdf_mechanism(&self) -> u32 {
+        match self {
+            HashAlgorithm::SHA256 => nss_sys::CKM_NSS_HKDF_SHA256,
+            HashAlgorithm::SHA384 => nss_sys::CKM_NSS_HKDF_SHA384,
+        }
+    }
+}
+
+impl From<&HashAlgorithm> for nss_sys::SECOidTag {
+    fn from(alg: &HashAlgorithm) -> Self {
+        match alg {
+            HashAlgorithm::SHA256 => nss_sys::SECOidTag::SEC_OID_SHA256,
+            HashAlgorithm::SHA384 => nss_sys::SECOidTag::SEC_OID_SHA384,
+        }
+    }
+}
+
+pub fn hash_buf(algorithm: &HashAlgorithm, data: &[u8]) -> Result<Vec<u8>> {
+    ensure_nss_initialized();
+    let result_len = usize::try_from(algorithm.result_len())?;
+    let mut out = vec![0u8; result_len];
+    let data_len = i32::try_from(data.len())?;
+    map_nss_secstatus(|| unsafe {
+        nss_sys::PK11_HashBuf(
+            Into::<nss_sys::SECOidTag>::into(algorithm) as u32,
+            out.as_mut_ptr(),
+            data.as_ptr(),
+            data_len,
+        )
+    })?;
+    Ok(out)
+}
+
+pub fn hmac_sign(digest_alg: &HashAlgorithm, sym_key_bytes: &[u8], data: &[u8]) -> Result<Vec<u8>> {
+    let mech = digest_alg.as_hmac_mechanism();
+    let sym_key = import_sym_key(mech.into(), nss_sys::CKA_SIGN.into(), sym_key_bytes)?;
+    let context = create_context_by_sym_key(mech.into(), nss_sys::CKA_SIGN.into(), &sym_key)?;
+    hash_buf_with_context(&context, data)
+}
+
+/// Similar to hash_buf except the consumer has to provide the digest context.
+fn hash_buf_with_context(context: &Context, data: &[u8]) -> Result<Vec<u8>> {
+    ensure_nss_initialized();
+    map_nss_secstatus(|| unsafe { nss_sys::PK11_DigestBegin(context.as_mut_ptr()) })?;
+    let data_len = u32::try_from(data.len())?;
+    map_nss_secstatus(|| unsafe {
+        nss_sys::PK11_DigestOp(context.as_mut_ptr(), data.as_ptr(), data_len)
+    })?;
+    // We allocate the maximum possible length for the out buffer then we'll
+    // slice it after nss fills `out_len`.
+    let mut out_len: u32 = 0;
+    let mut out = vec![0u8; nss_sys::HASH_LENGTH_MAX as usize];
+    map_nss_secstatus(|| unsafe {
+        nss_sys::PK11_DigestFinal(
+            context.as_mut_ptr(),
+            out.as_mut_ptr(),
+            &mut out_len,
+            nss_sys::HASH_LENGTH_MAX,
+        )
+    })?;
+    out.truncate(usize::try_from(out_len)?);
+    Ok(out)
+}
+
+/// Safe wrapper around PK11_CreateContextBySymKey that
+/// de-allocates memory when the context goes out of
+/// scope.
+pub fn create_context_by_sym_key(
+    mechanism: nss_sys::CK_MECHANISM_TYPE,
+    operation: nss_sys::CK_ATTRIBUTE_TYPE,
+    sym_key: &SymKey,
+) -> Result<Context> {
+    ensure_nss_initialized();
+    let param = nss_sys::SECItem {
+        type_: nss_sys::SECItemType::siBuffer as u32,
+        data: ptr::null_mut(),
+        len: 0,
+    };
+    unsafe {
+        Context::from_ptr(nss_sys::PK11_CreateContextBySymKey(
+            mechanism,
+            operation,
+            sym_key.as_mut_ptr(),
+            &param,
+        ))
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nss/pk11/mod.rs.html b/book/rust-docs/src/nss/pk11/mod.rs.html new file mode 100644 index 0000000000..f38c789ab4 --- /dev/null +++ b/book/rust-docs/src/nss/pk11/mod.rs.html @@ -0,0 +1,17 @@ +mod.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+pub mod context;
+pub mod slot;
+pub mod sym_key;
+pub mod types;
+
\ No newline at end of file diff --git a/book/rust-docs/src/nss/pk11/slot.rs.html b/book/rust-docs/src/nss/pk11/slot.rs.html new file mode 100644 index 0000000000..b09aa020da --- /dev/null +++ b/book/rust-docs/src/nss/pk11/slot.rs.html @@ -0,0 +1,49 @@ +slot.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::{
+    error::*,
+    pk11::types::Slot,
+    util::{ensure_nss_initialized, map_nss_secstatus, ScopedPtr},
+};
+
+pub fn generate_random(data: &mut [u8]) -> Result<()> {
+    // `NSS_Init` will initialize the RNG with data from `/dev/urandom`.
+    ensure_nss_initialized();
+    let len = i32::try_from(data.len())?;
+    map_nss_secstatus(|| unsafe { nss_sys::PK11_GenerateRandom(data.as_mut_ptr(), len) })?;
+    Ok(())
+}
+
+/// Safe wrapper around `PK11_GetInternalSlot` that
+/// de-allocates memory when the slot goes out of
+/// scope.
+pub(crate) fn get_internal_slot() -> Result<Slot> {
+    unsafe { Slot::from_ptr(nss_sys::PK11_GetInternalSlot()) }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nss/pk11/sym_key.rs.html b/book/rust-docs/src/nss/pk11/sym_key.rs.html new file mode 100644 index 0000000000..a762522d94 --- /dev/null +++ b/book/rust-docs/src/nss/pk11/sym_key.rs.html @@ -0,0 +1,185 @@ +sym_key.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::{
+    error::*,
+    pk11::{context::HashAlgorithm, slot, types::SymKey},
+    util::{ensure_nss_initialized, map_nss_secstatus, sec_item_as_slice, ScopedPtr},
+};
+use std::{
+    mem,
+    os::raw::{c_uchar, c_uint, c_ulong},
+    ptr,
+};
+
+pub fn hkdf_expand(
+    digest_alg: &HashAlgorithm,
+    key_bytes: &[u8],
+    info: &[u8],
+    len: usize,
+) -> Result<Vec<u8>> {
+    ensure_nss_initialized();
+    let mech = digest_alg.as_hkdf_mechanism();
+    // Most of the following code is inspired by the Firefox WebCrypto implementation:
+    // https://searchfox.org/mozilla-central/rev/ee3905439acbf81e9c829ece0b46d09d2fa26c5c/dom/crypto/WebCryptoTask.cpp#2530-2597
+    // Except that we only do the expand part, which explains why we use null pointers below.
+    let mut hkdf_params = nss_sys::CK_NSS_HKDFParams {
+        bExtract: nss_sys::CK_FALSE,
+        pSalt: ptr::null_mut(),
+        ulSaltLen: 0,
+        bExpand: nss_sys::CK_TRUE,
+        pInfo: info.as_ptr() as *mut u8,
+        ulInfoLen: c_ulong::try_from(info.len())?,
+    };
+    let mut params = nss_sys::SECItem {
+        type_: nss_sys::SECItemType::siBuffer as u32,
+        data: &mut hkdf_params as *mut _ as *mut c_uchar,
+        len: u32::try_from(mem::size_of::<nss_sys::CK_NSS_HKDFParams>())?,
+    };
+    let base_key = import_sym_key(mech.into(), nss_sys::CKA_WRAP.into(), key_bytes)?;
+    let derived_len = i32::try_from(len)?;
+    let sym_key = unsafe {
+        SymKey::from_ptr(
+            // CKM_SHA512_HMAC and CKA_SIGN are key type and usage attributes of the
+            // derived symmetric key and don't matter because we ignore them anyway.
+            nss_sys::PK11_Derive(
+                base_key.as_mut_ptr(),
+                mech.into(),
+                &mut params,
+                nss_sys::CKM_SHA512_HMAC.into(),
+                nss_sys::CKA_SIGN.into(),
+                derived_len,
+            ),
+        )?
+    };
+    map_nss_secstatus(|| unsafe { nss_sys::PK11_ExtractKeyValue(sym_key.as_mut_ptr()) })?;
+    // This doesn't leak, because the SECItem* returned by PK11_GetKeyData
+    // just refers to a buffer managed by `sym_key` which we copy into `out`.
+    let mut key_data = unsafe { *nss_sys::PK11_GetKeyData(sym_key.as_mut_ptr()) };
+    if u32::try_from(len)? > key_data.len {
+        return Err(ErrorKind::InternalError.into());
+    }
+    let buf = unsafe { sec_item_as_slice(&mut key_data)? };
+    Ok(buf.to_vec())
+}
+
+/// Safe wrapper around PK11_ImportSymKey that
+/// de-allocates memory when the key goes out of
+/// scope.
+pub(crate) fn import_sym_key(
+    mechanism: nss_sys::CK_MECHANISM_TYPE,
+    operation: nss_sys::CK_ATTRIBUTE_TYPE,
+    buf: &[u8],
+) -> Result<SymKey> {
+    ensure_nss_initialized();
+    let mut item = nss_sys::SECItem {
+        type_: nss_sys::SECItemType::siBuffer as u32,
+        data: buf.as_ptr() as *mut c_uchar,
+        len: c_uint::try_from(buf.len())?,
+    };
+    let slot = slot::get_internal_slot()?;
+    unsafe {
+        SymKey::from_ptr(nss_sys::PK11_ImportSymKey(
+            slot.as_mut_ptr(),
+            mechanism,
+            nss_sys::PK11Origin::PK11_OriginUnwrap as u32,
+            operation,
+            &mut item,
+            ptr::null_mut(),
+        ))
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nss/pk11/types.rs.html b/book/rust-docs/src/nss/pk11/types.rs.html new file mode 100644 index 0000000000..137c908e18 --- /dev/null +++ b/book/rust-docs/src/nss/pk11/types.rs.html @@ -0,0 +1,459 @@ +types.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::{
+    error::*,
+    pk11::slot::{generate_random, get_internal_slot},
+    util::{map_nss_secstatus, ScopedPtr},
+};
+use std::{
+    ops::Deref,
+    os::raw::{c_int, c_uchar, c_uint, c_void},
+    ptr,
+};
+
+scoped_ptr!(SymKey, nss_sys::PK11SymKey, nss_sys::PK11_FreeSymKey);
+scoped_ptr!(
+    PrivateKey,
+    nss_sys::SECKEYPrivateKey,
+    nss_sys::SECKEY_DestroyPrivateKey
+);
+scoped_ptr!(
+    PublicKey,
+    nss_sys::SECKEYPublicKey,
+    nss_sys::SECKEY_DestroyPublicKey
+);
+scoped_ptr!(
+    GenericObject,
+    nss_sys::PK11GenericObject,
+    nss_sys::PK11_DestroyGenericObject
+);
+
+scoped_ptr!(
+    Certificate,
+    nss_sys::CERTCertificate,
+    nss_sys::CERT_DestroyCertificate
+);
+
+scoped_ptr!(Context, nss_sys::PK11Context, pk11_destroy_context_true);
+scoped_ptr!(Slot, nss_sys::PK11SlotInfo, nss_sys::PK11_FreeSlot);
+
+scoped_ptr!(
+    AlgorithmID,
+    nss_sys::SECAlgorithmID,
+    secoid_destroy_algorithm_id_true
+);
+
+#[inline]
+unsafe fn secoid_destroy_algorithm_id_true(alg_id: *mut nss_sys::SECAlgorithmID) {
+    nss_sys::SECOID_DestroyAlgorithmID(alg_id, nss_sys::PR_TRUE);
+}
+
+#[inline]
+unsafe fn pk11_destroy_context_true(context: *mut nss_sys::PK11Context) {
+    nss_sys::PK11_DestroyContext(context, nss_sys::PR_TRUE);
+}
+
+// Trait for types that have PCKS#11 attributes that are readable. See
+// https://searchfox.org/mozilla-central/rev/8ed8474757695cdae047150a0eaf94a5f1c96dbe/security/nss/lib/pk11wrap/pk11pub.h#842-864
+/// # Safety
+/// Unsafe since it needs to call [`nss_sys::PK11_ReadRawAttribute`] which is
+/// a C NSS function, and thus inherently unsafe to call
+pub(crate) unsafe trait Pkcs11Object: ScopedPtr {
+    const PK11_OBJECT_TYPE: nss_sys::PK11ObjectType;
+    fn read_raw_attribute(
+        &self,
+        attribute_type: nss_sys::CK_ATTRIBUTE_TYPE,
+    ) -> Result<ScopedSECItem> {
+        let mut out_sec = ScopedSECItem::empty(nss_sys::SECItemType::siBuffer);
+        map_nss_secstatus(|| unsafe {
+            nss_sys::PK11_ReadRawAttribute(
+                Self::PK11_OBJECT_TYPE as u32,
+                self.as_mut_ptr() as *mut c_void,
+                attribute_type,
+                out_sec.as_mut_ref(),
+            )
+        })?;
+        Ok(out_sec)
+    }
+}
+
+unsafe impl Pkcs11Object for GenericObject {
+    const PK11_OBJECT_TYPE: nss_sys::PK11ObjectType = nss_sys::PK11ObjectType::PK11_TypeGeneric;
+}
+unsafe impl Pkcs11Object for PrivateKey {
+    const PK11_OBJECT_TYPE: nss_sys::PK11ObjectType = nss_sys::PK11ObjectType::PK11_TypePrivKey;
+}
+unsafe impl Pkcs11Object for PublicKey {
+    const PK11_OBJECT_TYPE: nss_sys::PK11ObjectType = nss_sys::PK11ObjectType::PK11_TypePubKey;
+}
+unsafe impl Pkcs11Object for SymKey {
+    const PK11_OBJECT_TYPE: nss_sys::PK11ObjectType = nss_sys::PK11ObjectType::PK11_TypeSymKey;
+}
+
+// From https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/NSS_API_Guidelines#Thread_Safety:
+// "Data structures that are read only, like SECKEYPublicKeys or PK11SymKeys, need not be protected."
+unsafe impl Send for PrivateKey {}
+unsafe impl Send for PublicKey {}
+
+impl PrivateKey {
+    pub fn convert_to_public_key(&self) -> Result<PublicKey> {
+        Ok(unsafe { PublicKey::from_ptr(nss_sys::SECKEY_ConvertToPublicKey(self.as_mut_ptr()))? })
+    }
+
+    // To protect against key ID collisions, PrivateKeyFromPrivateKeyTemplate
+    // generates a random ID for each key. The given template must contain an
+    // attribute slot for a key ID, but it must consist of a null pointer and have a
+    // length of 0.
+    pub(crate) fn from_private_key_template(
+        mut template: Vec<nss_sys::CK_ATTRIBUTE>,
+    ) -> Result<Self> {
+        // Generate a random 160-bit object ID. This ID must be unique.
+        let mut obj_id_buf = vec![0u8; 160 / 8];
+        generate_random(&mut obj_id_buf)?;
+        let mut obj_id = nss_sys::SECItem {
+            type_: nss_sys::SECItemType::siBuffer as u32,
+            data: obj_id_buf.as_ptr() as *mut c_uchar,
+            len: c_uint::try_from(obj_id_buf.len())?,
+        };
+        let slot = get_internal_slot()?;
+        let mut pre_existing_key = unsafe {
+            nss_sys::PK11_FindKeyByKeyID(slot.as_mut_ptr(), &mut obj_id, std::ptr::null_mut())
+        };
+        if !pre_existing_key.is_null() {
+            // Note that we can't just call SECKEY_DestroyPrivateKey here because that
+            // will destroy the PKCS#11 object that is backing a preexisting key (that
+            // we still have a handle on somewhere else in memory). If that object were
+            // destroyed, cryptographic operations performed by that other key would
+            // fail.
+            unsafe {
+                destroy_private_key_without_destroying_pkcs11_object(pre_existing_key);
+            }
+            // Try again with a new ID (but only once - collisions are very unlikely).
+            generate_random(&mut obj_id_buf)?;
+            pre_existing_key = unsafe {
+                nss_sys::PK11_FindKeyByKeyID(slot.as_mut_ptr(), &mut obj_id, std::ptr::null_mut())
+            };
+            if !pre_existing_key.is_null() {
+                unsafe {
+                    destroy_private_key_without_destroying_pkcs11_object(pre_existing_key);
+                }
+                return Err(ErrorKind::InternalError.into());
+            }
+        }
+        let template_len = c_int::try_from(template.len())?;
+        let id_attr: &mut nss_sys::CK_ATTRIBUTE = template
+            .iter_mut()
+            .find(|&&mut attr| {
+                attr.type_ == (nss_sys::CKA_ID as nss_sys::CK_ATTRIBUTE_TYPE)
+                    && attr.pValue.is_null()
+                    && attr.ulValueLen == 0
+            })
+            .ok_or(ErrorKind::InternalError)?;
+        id_attr.pValue = obj_id_buf.as_mut_ptr() as *mut c_void;
+        id_attr.ulValueLen = nss_sys::CK_ULONG::try_from(obj_id_buf.len())?;
+        // We use `PK11_CreateGenericObject` instead of `PK11_CreateManagedGenericObject`
+        // to leak the reference on purpose because `PK11_FindKeyByKeyID` will take
+        // ownership of it.
+        let _obj = unsafe {
+            GenericObject::from_ptr(nss_sys::PK11_CreateGenericObject(
+                slot.as_mut_ptr(),
+                template.as_mut_ptr(),
+                template_len,
+                nss_sys::PR_FALSE,
+            ))?
+        };
+        // Have NSS translate the object to a private key.
+        Ok(unsafe {
+            PrivateKey::from_ptr(nss_sys::PK11_FindKeyByKeyID(
+                slot.as_mut_ptr(),
+                &mut obj_id,
+                std::ptr::null_mut(),
+            ))?
+        })
+    }
+}
+
+// This is typically used by functions receiving a pointer to an `out SECItem`,
+// where we allocate the struct, but NSS allocates the elements it points to.
+pub(crate) struct ScopedSECItem {
+    wrapped: nss_sys::SECItem,
+}
+
+impl ScopedSECItem {
+    pub(crate) fn empty(r#type: nss_sys::SECItemType) -> Self {
+        ScopedSECItem {
+            wrapped: nss_sys::SECItem {
+                type_: r#type as u32,
+                data: ptr::null_mut(),
+                len: 0,
+            },
+        }
+    }
+
+    pub(crate) fn as_mut_ref(&mut self) -> &mut nss_sys::SECItem {
+        &mut self.wrapped
+    }
+}
+
+impl Deref for ScopedSECItem {
+    type Target = nss_sys::SECItem;
+    #[inline]
+    fn deref(&self) -> &nss_sys::SECItem {
+        &self.wrapped
+    }
+}
+
+impl Drop for ScopedSECItem {
+    fn drop(&mut self) {
+        unsafe {
+            // PR_FALSE asks the NSS allocator not to free the SECItem
+            // itself, and just the pointee of `self.wrapped.data`.
+            nss_sys::SECITEM_FreeItem(&mut self.wrapped, nss_sys::PR_FALSE);
+        }
+    }
+}
+
+// This helper function will release the memory backing a SECKEYPrivateKey and
+// any resources acquired in its creation. It will leave the backing PKCS#11
+// object untouched, however. This should only be called from
+// PrivateKeyFromPrivateKeyTemplate.
+// From: https://searchfox.org/mozilla-central/rev/444ee13e14fe30451651c0f62b3979c76766ada4/dom/crypto/CryptoKey.cpp#80
+unsafe fn destroy_private_key_without_destroying_pkcs11_object(
+    key: *mut nss_sys::SECKEYPrivateKey,
+) {
+    assert!(!key.is_null());
+    nss_sys::PK11_FreeSlot((*key).pkcs11Slot);
+    nss_sys::PORT_FreeArena((*key).arena, nss_sys::PR_TRUE);
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nss/pkixc.rs.html b/book/rust-docs/src/nss/pkixc.rs.html new file mode 100644 index 0000000000..810d4f2c96 --- /dev/null +++ b/book/rust-docs/src/nss/pkixc.rs.html @@ -0,0 +1,203 @@ +pkixc.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::error::*;
+use crate::util::ensure_nss_initialized;
+
+use nss_sys::PRErrorCode;
+
+// NSS error codes.
+// https://searchfox.org/mozilla-central/rev/352b525/security/nss/lib/util/secerr.h#29
+const SEC_ERROR_BASE: i32 = -0x2000; // -8192
+const SEC_ERROR_EXPIRED_CERTIFICATE: i32 = SEC_ERROR_BASE + 11;
+const SEC_ERROR_UNKNOWN_ISSUER: i32 = SEC_ERROR_BASE + 13;
+const SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE: i32 = SEC_ERROR_BASE + 30;
+
+// SSL error codes.
+// https://searchfox.org/mozilla-central/rev/352b525/security/nss/lib/ssl/sslerr.h#42
+const SSL_ERROR_BASE: i32 = -0x3000; // -12288
+const SSL_ERROR_BAD_CERT_DOMAIN: i32 = SSL_ERROR_BASE + 12;
+
+// PKIX error codes.
+// https://searchfox.org/mozilla-central/rev/352b525/security/nss/lib/mozpkix/include/pkix/pkixnss.h#81
+const PKIX_ERROR_BASE: i32 = -0x4000; // -16384
+const PKIX_ERROR_NOT_YET_VALID_CERTIFICATE: i32 = PKIX_ERROR_BASE + 5;
+const PKIX_ERROR_NOT_YET_VALID_ISSUER_CERTIFICATE: i32 = PKIX_ERROR_BASE + 6;
+
+const ROOT_HASH_LENGTH: usize = 32;
+
+pub fn verify_code_signing_certificate_chain(
+    certificates: Vec<&[u8]>,
+    seconds_since_epoch: u64,
+    root_sha256_hash: &[u8],
+    hostname: &str,
+) -> Result<()> {
+    ensure_nss_initialized();
+
+    let mut cert_lens: Vec<u16> = vec![];
+    for certificate in &certificates {
+        match u16::try_from(certificate.len()) {
+            Ok(v) => cert_lens.push(v),
+            Err(e) => {
+                return Err(ErrorKind::InputError(format!(
+                    "certificate length is more than 65536 bytes: {}",
+                    e
+                ))
+                .into());
+            }
+        }
+    }
+
+    let mut p_certificates: Vec<_> = certificates.iter().map(|c| c.as_ptr()).collect();
+
+    if root_sha256_hash.len() != ROOT_HASH_LENGTH {
+        return Err(ErrorKind::InputError(format!(
+            "root hash contains {} bytes instead of {}",
+            root_sha256_hash.len(),
+            ROOT_HASH_LENGTH
+        ))
+        .into());
+    }
+
+    let mut out: PRErrorCode = 0;
+
+    let result = unsafe {
+        nss_sys::VerifyCodeSigningCertificateChain(
+            p_certificates.as_mut_ptr(), // Ideally the exposed API should not require mutability here.
+            cert_lens.as_ptr(),
+            certificates.len(),
+            seconds_since_epoch,
+            root_sha256_hash.as_ptr(),
+            hostname.as_ptr(),
+            hostname.len(),
+            &mut out,
+        )
+    };
+
+    if !result {
+        let kind = match out {
+            SEC_ERROR_UNKNOWN_ISSUER => ErrorKind::CertificateIssuerError,
+            SEC_ERROR_EXPIRED_CERTIFICATE => ErrorKind::CertificateValidityError,
+            SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE => ErrorKind::CertificateValidityError,
+            PKIX_ERROR_NOT_YET_VALID_CERTIFICATE => ErrorKind::CertificateValidityError,
+            PKIX_ERROR_NOT_YET_VALID_ISSUER_CERTIFICATE => ErrorKind::CertificateValidityError,
+            SSL_ERROR_BAD_CERT_DOMAIN => ErrorKind::CertificateSubjectError,
+            _ => {
+                let msg = "invalid chain of trust".to_string();
+                if SSL_ERROR_BASE < out && out < SSL_ERROR_BASE + 1000 {
+                    ErrorKind::SSLError(out, msg)
+                } else if PKIX_ERROR_BASE < out && out < PKIX_ERROR_BASE + 1000 {
+                    ErrorKind::PKIXError(out, msg)
+                } else {
+                    ErrorKind::NSSError(out, msg)
+                }
+            }
+        };
+        return Err(kind.into());
+    }
+
+    Ok(())
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nss/secport.rs.html b/book/rust-docs/src/nss/secport.rs.html new file mode 100644 index 0000000000..1e76443245 --- /dev/null +++ b/book/rust-docs/src/nss/secport.rs.html @@ -0,0 +1,47 @@ +secport.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::util::ensure_nss_initialized;
+use std::os::raw::c_void;
+
+pub fn secure_memcmp(a: &[u8], b: &[u8]) -> bool {
+    ensure_nss_initialized();
+    // NSS_SecureMemcmp will compare N elements fron our slices,
+    // so make sure they are the same length first.
+    if a.len() != b.len() {
+        return false;
+    }
+    let result = unsafe {
+        nss_sys::NSS_SecureMemcmp(
+            a.as_ptr() as *const c_void,
+            b.as_ptr() as *const c_void,
+            a.len(),
+        )
+    };
+    result == 0
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nss/util.rs.html b/book/rust-docs/src/nss/util.rs.html new file mode 100644 index 0000000000..6f4b13b491 --- /dev/null +++ b/book/rust-docs/src/nss/util.rs.html @@ -0,0 +1,259 @@ +util.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::error::*;
+use nss_sys::*;
+use std::{ffi::CString, os::raw::c_char, sync::Once};
+
+// This is the NSS version that this crate is claiming to be compatible with.
+// We check it at runtime using `NSS_VersionCheck`.
+pub const COMPATIBLE_NSS_VERSION: &str = "3.26";
+
+static NSS_INIT: Once = Once::new();
+
+pub fn ensure_nss_initialized() {
+    NSS_INIT.call_once(|| {
+        let version_ptr = CString::new(COMPATIBLE_NSS_VERSION).unwrap();
+        if unsafe { NSS_VersionCheck(version_ptr.as_ptr()) == PR_FALSE } {
+            panic!("Incompatible NSS version!")
+        }
+        let empty = CString::default();
+        let flags = NSS_INIT_READONLY
+            | NSS_INIT_NOCERTDB
+            | NSS_INIT_NOMODDB
+            | NSS_INIT_FORCEOPEN
+            | NSS_INIT_OPTIMIZESPACE;
+        let context = unsafe {
+            NSS_InitContext(
+                empty.as_ptr(),
+                empty.as_ptr(),
+                empty.as_ptr(),
+                empty.as_ptr(),
+                std::ptr::null_mut(),
+                flags,
+            )
+        };
+        if context.is_null() {
+            let error = get_last_error();
+            panic!("Could not initialize NSS: {}", error);
+        }
+    })
+}
+
+pub fn map_nss_secstatus<F>(callback: F) -> Result<()>
+where
+    F: FnOnce() -> SECStatus,
+{
+    if callback() == SECStatus::SECSuccess {
+        return Ok(());
+    }
+    Err(get_last_error())
+}
+
+/// Retrieve and wrap the last NSS/NSPR error in the current thread.
+#[cold]
+pub fn get_last_error() -> Error {
+    let error_code = unsafe { PR_GetError() };
+    let error_text: String = usize::try_from(unsafe { PR_GetErrorTextLength() })
+        .map(|error_text_len| {
+            let mut out_str = vec![0u8; error_text_len + 1];
+            unsafe { PR_GetErrorText(out_str.as_mut_ptr() as *mut c_char) };
+            CString::new(&out_str[0..error_text_len])
+                .unwrap_or_else(|_| CString::default())
+                .to_str()
+                .unwrap_or("")
+                .to_owned()
+        })
+        .unwrap_or_else(|_| "".to_string());
+    ErrorKind::NSSError(error_code, error_text).into()
+}
+
+pub(crate) trait ScopedPtr
+where
+    Self: std::marker::Sized,
+{
+    type RawType;
+    unsafe fn from_ptr(ptr: *mut Self::RawType) -> Result<Self>;
+    fn as_ptr(&self) -> *const Self::RawType;
+    fn as_mut_ptr(&self) -> *mut Self::RawType;
+}
+
+// The macro defines a wrapper around pointers refering to types allocated by NSS,
+// calling their NSS destructor method when they go out of scope to avoid memory leaks.
+// The `as_ptr`/`as_mut_ptr` are provided to retrieve the raw pointers to pass to
+// NSS functions that consume them.
+#[macro_export]
+macro_rules! scoped_ptr {
+    ($scoped:ident, $target:ty, $dtor:path) => {
+        pub struct $scoped {
+            ptr: *mut $target,
+        }
+
+        impl $crate::util::ScopedPtr for $scoped {
+            type RawType = $target;
+
+            #[allow(dead_code)]
+            unsafe fn from_ptr(ptr: *mut $target) -> $crate::error::Result<$scoped> {
+                if !ptr.is_null() {
+                    Ok($scoped { ptr })
+                } else {
+                    Err($crate::error::ErrorKind::InternalError.into())
+                }
+            }
+
+            #[inline]
+            fn as_ptr(&self) -> *const $target {
+                self.ptr
+            }
+
+            #[inline]
+            fn as_mut_ptr(&self) -> *mut $target {
+                self.ptr
+            }
+        }
+
+        impl Drop for $scoped {
+            fn drop(&mut self) {
+                assert!(!self.ptr.is_null());
+                unsafe { $dtor(self.ptr) };
+            }
+        }
+    };
+}
+
+pub(crate) unsafe fn sec_item_as_slice(sec_item: &mut SECItem) -> Result<&mut [u8]> {
+    let sec_item_buf_len = usize::try_from(sec_item.len)?;
+    let buf = std::slice::from_raw_parts_mut(sec_item.data, sec_item_buf_len);
+    Ok(buf)
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nss_build_common/lib.rs.html b/book/rust-docs/src/nss_build_common/lib.rs.html new file mode 100644 index 0000000000..abdf619aae --- /dev/null +++ b/book/rust-docs/src/nss_build_common/lib.rs.html @@ -0,0 +1,387 @@ +lib.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! This shouldn't exist, but does because if something isn't going to link
+//! against `nss` but has an `nss`-enabled `sqlcipher` turned on (for example,
+//! by a `cargo` feature activated by something else in the workspace).
+//! it might need to issue link commands for NSS.
+
+use std::{
+    env,
+    ffi::OsString,
+    path::{Path, PathBuf},
+};
+
+#[derive(Clone, Copy, PartialEq, Eq, Debug)]
+pub enum LinkingKind {
+    Dynamic { folded_libs: bool },
+    Static,
+}
+
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub struct NoNssDir;
+
+pub fn link_nss() -> Result<(), NoNssDir> {
+    let is_gecko = env::var_os("MOZ_TOPOBJDIR").is_some();
+    if !is_gecko {
+        let (lib_dir, include_dir) = get_nss()?;
+        println!(
+            "cargo:rustc-link-search=native={}",
+            lib_dir.to_string_lossy()
+        );
+        println!("cargo:include={}", include_dir.to_string_lossy());
+        let kind = determine_kind();
+        link_nss_libs(kind);
+    } else {
+        let libs = match env::var("CARGO_CFG_TARGET_OS")
+            .as_ref()
+            .map(std::string::String::as_str)
+        {
+            Ok("android") | Ok("macos") => vec!["nss3"],
+            _ => vec!["nssutil3", "nss3", "plds4", "plc4", "nspr4"],
+        };
+        for lib in &libs {
+            println!("cargo:rustc-link-lib=dylib={}", lib);
+        }
+    }
+    Ok(())
+}
+
+fn get_nss() -> Result<(PathBuf, PathBuf), NoNssDir> {
+    let nss_dir = env("NSS_DIR").ok_or(NoNssDir)?;
+    let nss_dir = Path::new(&nss_dir);
+    if !nss_dir.exists() {
+        println!(
+            "NSS_DIR path (obtained via `env`) does not exist: {}",
+            nss_dir.display()
+        );
+        panic!("It looks like NSS is not built. Please run `libs/verify-[platform]-environment.sh` first!");
+    }
+    let lib_dir = nss_dir.join("lib");
+    let include_dir = nss_dir.join("include");
+    Ok((lib_dir, include_dir))
+}
+
+fn determine_kind() -> LinkingKind {
+    if env_flag("NSS_STATIC") {
+        LinkingKind::Static
+    } else {
+        let folded_libs = env_flag("NSS_USE_FOLDED_LIBS");
+        LinkingKind::Dynamic { folded_libs }
+    }
+}
+
+fn link_nss_libs(kind: LinkingKind) {
+    let libs = get_nss_libs(kind);
+    // Emit -L flags
+    let kind_str = match kind {
+        LinkingKind::Dynamic { .. } => "dylib",
+        LinkingKind::Static => "static",
+    };
+    for lib in libs {
+        println!("cargo:rustc-link-lib={}={}", kind_str, lib);
+    }
+    // Link against C++ stdlib (for mozpkix)
+    let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
+    if target_os == "android" || target_os == "linux" {
+        println!("cargo:rustc-link-lib=stdc++");
+    } else {
+        println!("cargo:rustc-link-lib=c++");
+    }
+    let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap();
+    if target_arch == "x86_64" && target_os == "android" {
+        let android_home = env::var("ANDROID_HOME").expect("ANDROID_HOME not set");
+        const ANDROID_NDK_VERSION: &str = "25.2.9519653";
+        // One of these will exist, depending on the host platform.
+        const DARWIN_X86_64_LIB_DIR: &str =
+            "/toolchains/llvm/prebuilt/darwin-x86_64/lib64/clang/14.0.7/lib/linux/";
+        println!("cargo:rustc-link-search={android_home}/ndk/{ANDROID_NDK_VERSION}/{DARWIN_X86_64_LIB_DIR}");
+        const LINUX_X86_64_LIB_DIR: &str =
+            "/toolchains/llvm/prebuilt/linux-x86_64/lib64/clang/14.0.7/lib/linux/";
+        println!("cargo:rustc-link-search={android_home}/ndk/{ANDROID_NDK_VERSION}/{LINUX_X86_64_LIB_DIR}");
+        println!("cargo:rustc-link-lib=static=clang_rt.builtins-x86_64-android");
+    }
+}
+
+fn get_nss_libs(kind: LinkingKind) -> Vec<&'static str> {
+    match kind {
+        LinkingKind::Static => {
+            let mut static_libs = vec![
+                "certdb",
+                "certhi",
+                "cryptohi",
+                "freebl_static",
+                "mozpkix",
+                "nspr4",
+                "nss_static",
+                "nssb",
+                "nssdev",
+                "nsspki",
+                "nssutil",
+                "pk11wrap_static",
+                "plc4",
+                "plds4",
+                "softokn_static",
+            ];
+            // Hardware specific libs.
+            let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap();
+            let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
+            // https://searchfox.org/nss/rev/0d5696b3edce5124353f03159d2aa15549db8306/lib/freebl/freebl.gyp#508-542
+            if target_arch == "arm" || target_arch == "aarch64" {
+                static_libs.push("armv8_c_lib");
+            }
+            if target_arch == "x86_64" || target_arch == "x86" {
+                static_libs.push("gcm-aes-x86_c_lib");
+                static_libs.push("sha-x86_c_lib");
+            }
+            if target_arch == "arm" {
+                static_libs.push("gcm-aes-arm32-neon_c_lib")
+            }
+            if target_arch == "aarch64" {
+                static_libs.push("gcm-aes-aarch64_c_lib");
+            }
+            if target_arch == "x86_64" {
+                static_libs.push("hw-acc-crypto-avx");
+                static_libs.push("hw-acc-crypto-avx2");
+            }
+            // https://searchfox.org/nss/rev/08c4d05078d00089f8d7540651b0717a9d66f87e/lib/freebl/freebl.gyp#315-324
+            if ((target_os == "android" || target_os == "linux") && target_arch == "x86_64")
+                || target_os == "windows"
+            {
+                static_libs.push("intel-gcm-wrap_c_lib");
+                // https://searchfox.org/nss/rev/08c4d05078d00089f8d7540651b0717a9d66f87e/lib/freebl/freebl.gyp#43-47
+                if (target_os == "android" || target_os == "linux") && target_arch == "x86_64" {
+                    static_libs.push("intel-gcm-s_lib");
+                }
+            }
+            static_libs
+        }
+        LinkingKind::Dynamic { folded_libs } => {
+            let mut dylibs = vec!["freebl3", "nss3", "nssckbi", "softokn3"];
+            if !folded_libs {
+                dylibs.append(&mut vec!["nspr4", "nssutil3", "plc4", "plds4"]);
+            }
+            dylibs
+        }
+    }
+}
+
+pub fn env(name: &str) -> Option<OsString> {
+    println!("cargo:rerun-if-env-changed={}", name);
+    env::var_os(name)
+}
+
+pub fn env_str(name: &str) -> Option<String> {
+    println!("cargo:rerun-if-env-changed={}", name);
+    env::var(name).ok()
+}
+
+pub fn env_flag(name: &str) -> bool {
+    match env_str(name).as_ref().map(String::as_ref) {
+        Some("1") => true,
+        Some("0") => false,
+        Some(s) => {
+            println!(
+                "cargo:warning=unknown value for environment var {:?}: {:?}. Ignoring",
+                name, s
+            );
+            false
+        }
+        None => false,
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nss_sys/bindings/blapit.rs.html b/book/rust-docs/src/nss_sys/bindings/blapit.rs.html new file mode 100644 index 0000000000..f0f78fbad5 --- /dev/null +++ b/book/rust-docs/src/nss_sys/bindings/blapit.rs.html @@ -0,0 +1,19 @@ +blapit.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+pub const EC_POINT_FORM_UNCOMPRESSED: u32 = 4;
+pub const SHA256_LENGTH: u32 = 32;
+pub const SHA384_LENGTH: u32 = 48;
+pub const HASH_LENGTH_MAX: u32 = 64;
+pub const AES_BLOCK_SIZE: u32 = 16;
+
\ No newline at end of file diff --git a/book/rust-docs/src/nss_sys/bindings/certdb.rs.html b/book/rust-docs/src/nss_sys/bindings/certdb.rs.html new file mode 100644 index 0000000000..05a348b79a --- /dev/null +++ b/book/rust-docs/src/nss_sys/bindings/certdb.rs.html @@ -0,0 +1,49 @@ +certdb.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::*;
+use std::os::raw::c_char;
+
+// Opaque types
+pub type CERTCertDBHandle = u8;
+pub type CERTCertificate = u8;
+
+extern "C" {
+    pub fn CERT_GetDefaultCertDB() -> *mut CERTCertDBHandle;
+
+    pub fn CERT_NewTempCertificate(
+        handle: *mut CERTCertDBHandle,
+        derCert: *mut SECItem,
+        nickname: *mut c_char,
+        isperm: PRBool,
+        copyDER: PRBool,
+    ) -> *mut CERTCertificate;
+
+    pub fn CERT_DestroyCertificate(cert: *mut CERTCertificate);
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nss_sys/bindings/keyhi.rs.html b/book/rust-docs/src/nss_sys/bindings/keyhi.rs.html new file mode 100644 index 0000000000..92228dfa96 --- /dev/null +++ b/book/rust-docs/src/nss_sys/bindings/keyhi.rs.html @@ -0,0 +1,25 @@ +keyhi.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::*;
+
+extern "C" {
+    pub fn SECKEY_CopyPublicKey(pubKey: *const SECKEYPublicKey) -> *mut SECKEYPublicKey;
+    pub fn SECKEY_ConvertToPublicKey(privateKey: *mut SECKEYPrivateKey) -> *mut SECKEYPublicKey;
+    pub fn SECKEY_DestroyPrivateKey(key: *mut SECKEYPrivateKey);
+    pub fn SECKEY_DestroyPublicKey(key: *mut SECKEYPublicKey);
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nss_sys/bindings/keythi.rs.html b/book/rust-docs/src/nss_sys/bindings/keythi.rs.html new file mode 100644 index 0000000000..6a47dd5918 --- /dev/null +++ b/book/rust-docs/src/nss_sys/bindings/keythi.rs.html @@ -0,0 +1,281 @@ +keythi.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::*;
+use std::os::raw::{c_int, c_uchar, c_void};
+
+pub type SECKEYPublicKey = SECKEYPublicKeyStr;
+#[repr(C)]
+pub struct SECKEYPublicKeyStr {
+    pub arena: *mut PLArenaPool,
+    pub keyType: u32, /* KeyType */
+    pub pkcs11Slot: *mut PK11SlotInfo,
+    pub pkcs11ID: CK_OBJECT_HANDLE,
+    pub u: SECKEYPublicKeyStr_u,
+}
+
+#[repr(C)]
+pub union SECKEYPublicKeyStr_u {
+    pub rsa: SECKEYRSAPublicKey,
+    pub dsa: SECKEYDSAPublicKey,
+    pub dh: SECKEYDHPublicKey,
+    pub kea: SECKEYKEAPublicKey,
+    pub fortezza: SECKEYFortezzaPublicKey,
+    pub ec: SECKEYECPublicKey,
+}
+
+pub type SECKEYPrivateKey = SECKEYPrivateKeyStr;
+#[repr(C)]
+pub struct SECKEYPrivateKeyStr {
+    pub arena: *mut PLArenaPool,
+    pub keyType: u32, /* KeyType */
+    pub pkcs11Slot: *mut PK11SlotInfo,
+    pub pkcs11ID: CK_OBJECT_HANDLE,
+    pub pkcs11IsTemp: PRBool,
+    pub wincx: *mut c_void,
+    pub staticflags: PRUint32,
+}
+
+#[repr(u32)]
+pub enum KeyType {
+    nullKey = 0,
+    rsaKey = 1,
+    dsaKey = 2,
+    fortezzaKey = 3,
+    dhKey = 4,
+    keaKey = 5,
+    ecKey = 6,
+    rsaPssKey = 7,
+    rsaOaepKey = 8,
+}
+
+pub type SECKEYRSAPublicKey = SECKEYRSAPublicKeyStr;
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub struct SECKEYRSAPublicKeyStr {
+    pub arena: *mut PLArenaPool,
+    pub modulus: SECItem,
+    pub publicExponent: SECItem,
+}
+
+pub type SECKEYDSAPublicKey = SECKEYDSAPublicKeyStr;
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub struct SECKEYDSAPublicKeyStr {
+    pub params: SECKEYPQGParams,
+    pub publicValue: SECItem,
+}
+
+pub type SECKEYPQGParams = SECKEYPQGParamsStr;
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub struct SECKEYPQGParamsStr {
+    pub arena: *mut PLArenaPool,
+    pub prime: SECItem,
+    pub subPrime: SECItem,
+    pub base: SECItem,
+}
+
+pub type SECKEYDHPublicKey = SECKEYDHPublicKeyStr;
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub struct SECKEYDHPublicKeyStr {
+    pub arena: *mut PLArenaPool,
+    pub prime: SECItem,
+    pub base: SECItem,
+    pub publicValue: SECItem,
+}
+
+pub type SECKEYKEAPublicKey = SECKEYKEAPublicKeyStr;
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub struct SECKEYKEAPublicKeyStr {
+    pub params: SECKEYKEAParams,
+    pub publicValue: SECItem,
+}
+
+pub type SECKEYKEAParams = SECKEYKEAParamsStr;
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub struct SECKEYKEAParamsStr {
+    pub arena: *mut PLArenaPool,
+    pub hash: SECItem,
+}
+
+pub type SECKEYFortezzaPublicKey = SECKEYFortezzaPublicKeyStr;
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub struct SECKEYFortezzaPublicKeyStr {
+    pub KEAversion: c_int,
+    pub DSSversion: c_int,
+    pub KMID: [c_uchar; 8usize],
+    pub clearance: SECItem,
+    pub KEApriviledge: SECItem,
+    pub DSSpriviledge: SECItem,
+    pub KEAKey: SECItem,
+    pub DSSKey: SECItem,
+    pub params: SECKEYPQGParams,
+    pub keaParams: SECKEYPQGParams,
+}
+
+pub type SECKEYECPublicKey = SECKEYECPublicKeyStr;
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub struct SECKEYECPublicKeyStr {
+    pub DEREncodedParams: SECKEYECParams,
+    pub size: c_int,
+    pub publicValue: SECItem,
+    pub encoding: u32, /* ECPointEncoding */
+}
+
+pub type SECKEYECParams = SECItem;
+
+#[repr(u32)]
+#[derive(Copy, Clone)]
+pub enum ECPointEncoding {
+    ECPoint_Uncompressed = 0,
+    ECPoint_XOnly = 1,
+    ECPoint_Undefined = 2,
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nss_sys/bindings/mod.rs.html b/book/rust-docs/src/nss_sys/bindings/mod.rs.html new file mode 100644 index 0000000000..f4300e5778 --- /dev/null +++ b/book/rust-docs/src/nss_sys/bindings/mod.rs.html @@ -0,0 +1,89 @@ +mod.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+mod blapit;
+pub use blapit::*;
+mod certdb;
+pub use certdb::*;
+mod keyhi;
+pub use keyhi::*;
+mod keythi;
+pub use keythi::*;
+mod nss;
+pub use nss::*;
+mod pk11pub;
+pub use pk11pub::*;
+mod pkcs11n;
+pub use pkcs11n::*;
+mod pkcs11t;
+pub use pkcs11t::*;
+mod pkixc;
+pub use pkixc::*;
+mod plarena;
+pub use plarena::*;
+mod prerror;
+pub use prerror::*;
+mod prtypes;
+pub use prtypes::*;
+mod secasn1t;
+pub use secasn1t::*;
+mod seccomon;
+pub use seccomon::*;
+mod secitem;
+pub use secitem::*;
+mod seckey;
+pub use seckey::*;
+mod secmodt;
+pub use secmodt::*;
+mod secoid;
+pub use secoid::*;
+mod secoidt;
+pub use secoidt::*;
+mod secport;
+pub use secport::*;
+
\ No newline at end of file diff --git a/book/rust-docs/src/nss_sys/bindings/nss.rs.html b/book/rust-docs/src/nss_sys/bindings/nss.rs.html new file mode 100644 index 0000000000..ebd38607a3 --- /dev/null +++ b/book/rust-docs/src/nss_sys/bindings/nss.rs.html @@ -0,0 +1,57 @@ +nss.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::*;
+use std::os::raw::c_char;
+
+extern "C" {
+    pub fn NSS_VersionCheck(importedVersion: *const c_char) -> PRBool;
+    pub fn NSS_InitContext(
+        configdir: *const c_char,
+        certPrefix: *const c_char,
+        keyPrefix: *const c_char,
+        secmodName: *const c_char,
+        initParams: *mut NSSInitParameters,
+        flags: PRUint32,
+    ) -> *mut NSSInitContext;
+}
+
+pub const NSS_INIT_READONLY: u32 = 1;
+pub const NSS_INIT_NOCERTDB: u32 = 2;
+pub const NSS_INIT_NOMODDB: u32 = 4;
+pub const NSS_INIT_FORCEOPEN: u32 = 8;
+pub const NSS_INIT_OPTIMIZESPACE: u32 = 32;
+
+// Opaque types
+pub type NSSInitContext = u8;
+pub type NSSInitParameters = [u64; 10usize];
+
\ No newline at end of file diff --git a/book/rust-docs/src/nss_sys/bindings/pk11pub.rs.html b/book/rust-docs/src/nss_sys/bindings/pk11pub.rs.html new file mode 100644 index 0000000000..749cba40bb --- /dev/null +++ b/book/rust-docs/src/nss_sys/bindings/pk11pub.rs.html @@ -0,0 +1,277 @@ +pk11pub.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+pub use crate::*;
+use std::os::raw::{c_int, c_uchar, c_uint, c_void};
+
+extern "C" {
+    pub fn PK11_FreeSlot(slot: *mut PK11SlotInfo);
+    pub fn PK11_GetInternalSlot() -> *mut PK11SlotInfo;
+    pub fn PK11_GenerateRandom(data: *mut c_uchar, len: c_int) -> SECStatus;
+    pub fn PK11_FreeSymKey(key: *mut PK11SymKey);
+    pub fn PK11_ImportSymKey(
+        slot: *mut PK11SlotInfo,
+        type_: CK_MECHANISM_TYPE,
+        origin: u32, /* PK11Origin */
+        operation: CK_ATTRIBUTE_TYPE,
+        key: *mut SECItem,
+        wincx: *mut c_void,
+    ) -> *mut PK11SymKey;
+    pub fn PK11_Derive(
+        baseKey: *mut PK11SymKey,
+        mechanism: CK_MECHANISM_TYPE,
+        param: *mut SECItem,
+        target: CK_MECHANISM_TYPE,
+        operation: CK_ATTRIBUTE_TYPE,
+        keySize: c_int,
+    ) -> *mut PK11SymKey;
+    pub fn PK11_PubDeriveWithKDF(
+        privKey: *mut SECKEYPrivateKey,
+        pubKey: *mut SECKEYPublicKey,
+        isSender: PRBool,
+        randomA: *mut SECItem,
+        randomB: *mut SECItem,
+        derive: CK_MECHANISM_TYPE,
+        target: CK_MECHANISM_TYPE,
+        operation: CK_ATTRIBUTE_TYPE,
+        keySize: c_int,
+        kdf: CK_ULONG,
+        sharedData: *mut SECItem,
+        wincx: *mut c_void,
+    ) -> *mut PK11SymKey;
+    pub fn PK11_ExtractKeyValue(symKey: *mut PK11SymKey) -> SECStatus;
+    pub fn PK11_GetKeyData(symKey: *mut PK11SymKey) -> *mut SECItem;
+    pub fn PK11_GenerateKeyPair(
+        slot: *mut PK11SlotInfo,
+        type_: CK_MECHANISM_TYPE,
+        param: *mut c_void,
+        pubk: *mut *mut SECKEYPublicKey,
+        isPerm: PRBool,
+        isSensitive: PRBool,
+        wincx: *mut c_void,
+    ) -> *mut SECKEYPrivateKey;
+    pub fn PK11_FindKeyByKeyID(
+        slot: *mut PK11SlotInfo,
+        keyID: *mut SECItem,
+        wincx: *mut c_void,
+    ) -> *mut SECKEYPrivateKey;
+    pub fn PK11_Decrypt(
+        symkey: *mut PK11SymKey,
+        mechanism: CK_MECHANISM_TYPE,
+        param: *mut SECItem,
+        out: *mut c_uchar,
+        outLen: *mut c_uint,
+        maxLen: c_uint,
+        enc: *const c_uchar,
+        encLen: c_uint,
+    ) -> SECStatus;
+    pub fn PK11_Encrypt(
+        symKey: *mut PK11SymKey,
+        mechanism: CK_MECHANISM_TYPE,
+        param: *mut SECItem,
+        out: *mut c_uchar,
+        outLen: *mut c_uint,
+        maxLen: c_uint,
+        data: *const c_uchar,
+        dataLen: c_uint,
+    ) -> SECStatus;
+    pub fn PK11_VerifyWithMechanism(
+        key: *mut SECKEYPublicKey,
+        mechanism: CK_MECHANISM_TYPE,
+        param: *const SECItem,
+        sig: *const SECItem,
+        hash: *const SECItem,
+        wincx: *mut c_void,
+    ) -> SECStatus;
+    pub fn PK11_MapSignKeyType(keyType: u32 /* KeyType */) -> CK_MECHANISM_TYPE;
+    pub fn PK11_DestroyContext(context: *mut PK11Context, freeit: PRBool);
+    pub fn PK11_CreateContextBySymKey(
+        type_: CK_MECHANISM_TYPE,
+        operation: CK_ATTRIBUTE_TYPE,
+        symKey: *mut PK11SymKey,
+        param: *const SECItem,
+    ) -> *mut PK11Context;
+    pub fn PK11_DigestBegin(cx: *mut PK11Context) -> SECStatus;
+    pub fn PK11_HashBuf(
+        hashAlg: u32, /* SECOidTag */
+        out: *mut c_uchar,
+        in_: *const c_uchar,
+        len: PRInt32,
+    ) -> SECStatus;
+    pub fn PK11_DigestOp(context: *mut PK11Context, in_: *const c_uchar, len: c_uint) -> SECStatus;
+    pub fn PK11_DigestFinal(
+        context: *mut PK11Context,
+        data: *mut c_uchar,
+        outLen: *mut c_uint,
+        length: c_uint,
+    ) -> SECStatus;
+    pub fn PK11_DestroyGenericObject(object: *mut PK11GenericObject) -> SECStatus;
+    pub fn PK11_CreateGenericObject(
+        slot: *mut PK11SlotInfo,
+        pTemplate: *const CK_ATTRIBUTE,
+        count: c_int,
+        token: PRBool,
+    ) -> *mut PK11GenericObject;
+    pub fn PK11_ReadRawAttribute(
+        type_: u32, /* PK11ObjectType */
+        object: *mut c_void,
+        attr: CK_ATTRIBUTE_TYPE,
+        item: *mut SECItem,
+    ) -> SECStatus;
+    pub fn PK11_CreatePBEV2AlgorithmID(
+        pbeAlgTag: u32,    /* SECOidTag */
+        cipherAlgTag: u32, /* SECOidTag */
+        prfAlgTag: u32,    /* SECOidTag */
+        keyLength: c_int,
+        iteration: c_int,
+        salt: *mut SECItem,
+    ) -> *mut SECAlgorithmID;
+
+    pub fn PK11_PBEKeyGen(
+        slot: *mut PK11SlotInfo,
+        algid: *mut SECAlgorithmID,
+        pwitem: *mut SECItem,
+        faulty3DES: PRBool,
+        wincx: *mut c_void,
+    ) -> *mut PK11SymKey;
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nss_sys/bindings/pkcs11n.rs.html b/book/rust-docs/src/nss_sys/bindings/pkcs11n.rs.html new file mode 100644 index 0000000000..b0cd891035 --- /dev/null +++ b/book/rust-docs/src/nss_sys/bindings/pkcs11n.rs.html @@ -0,0 +1,65 @@ +pkcs11n.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+pub use crate::*;
+
+// https://searchfox.org/nss/rev/4d480919bbf204df5e199b9fdedec8f2a6295778/lib/util/pkcs11n.h#27
+pub const NSSCK_VENDOR_NSS: u32 = 0x4E534350;
+
+pub const CKM_NSS: u32 = CKM_VENDOR_DEFINED | NSSCK_VENDOR_NSS;
+pub const CKM_NSS_HKDF_SHA256: u32 = CKM_NSS + 4;
+pub const CKM_NSS_HKDF_SHA384: u32 = CKM_NSS + 5;
+
+pub type CK_GCM_PARAMS = CK_GCM_PARAMS_V3;
+#[repr(C)]
+pub struct CK_GCM_PARAMS_V3 {
+    pub pIv: CK_BYTE_PTR,
+    pub ulIvLen: CK_ULONG,
+    pub ulIvBits: CK_ULONG,
+    pub pAAD: CK_BYTE_PTR,
+    pub ulAADLen: CK_ULONG,
+    pub ulTagBits: CK_ULONG,
+}
+#[repr(C)]
+pub struct CK_NSS_HKDFParams {
+    pub bExtract: CK_BBOOL,
+    pub pSalt: CK_BYTE_PTR,
+    pub ulSaltLen: CK_ULONG,
+    pub bExpand: CK_BBOOL,
+    pub pInfo: CK_BYTE_PTR,
+    pub ulInfoLen: CK_ULONG,
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nss_sys/bindings/pkcs11t.rs.html b/book/rust-docs/src/nss_sys/bindings/pkcs11t.rs.html new file mode 100644 index 0000000000..4fc42f41d8 --- /dev/null +++ b/book/rust-docs/src/nss_sys/bindings/pkcs11t.rs.html @@ -0,0 +1,103 @@ +pkcs11t.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use std::os::raw::{c_uchar, c_ulong, c_void};
+
+pub const CK_TRUE: CK_BBOOL = 1;
+pub const CK_FALSE: CK_BBOOL = 0;
+pub type CK_BYTE = c_uchar;
+pub type CK_BBOOL = CK_BYTE;
+pub type CK_ULONG = c_ulong;
+pub type CK_BYTE_PTR = *mut CK_BYTE;
+pub type CK_VOID_PTR = *mut c_void;
+pub type CK_OBJECT_HANDLE = CK_ULONG;
+pub type CK_OBJECT_CLASS = CK_ULONG;
+pub type CK_KEY_TYPE = CK_ULONG;
+pub type CK_ATTRIBUTE_TYPE = CK_ULONG;
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub struct CK_ATTRIBUTE {
+    pub type_: CK_ATTRIBUTE_TYPE,
+    pub pValue: CK_VOID_PTR,
+    pub ulValueLen: CK_ULONG,
+}
+pub type CK_MECHANISM_TYPE = CK_ULONG;
+
+pub const CK_INVALID_HANDLE: u32 = 0;
+pub const CKO_PRIVATE_KEY: u32 = 3;
+pub const CKK_EC: u32 = 3;
+pub const CKA_CLASS: u32 = 0;
+pub const CKA_TOKEN: u32 = 1;
+pub const CKA_PRIVATE: u32 = 2;
+pub const CKA_VALUE: u32 = 17;
+pub const CKA_KEY_TYPE: u32 = 256;
+pub const CKA_ID: u32 = 258;
+pub const CKA_SENSITIVE: u32 = 259;
+pub const CKA_ENCRYPT: u32 = 260;
+pub const CKA_WRAP: u32 = 262;
+pub const CKA_SIGN: u32 = 264;
+pub const CKA_EC_PARAMS: u32 = 384;
+pub const CKA_EC_POINT: u32 = 385;
+// https://searchfox.org/nss/rev/4d480919bbf204df5e199b9fdedec8f2a6295778/lib/util/pkcs11t.h#1244
+pub const CKM_VENDOR_DEFINED: u32 = 0x80000000;
+pub const CKM_SHA256_HMAC: u32 = 593;
+pub const CKM_SHA384_HMAC: u32 = 609;
+pub const CKM_SHA512_HMAC: u32 = 625;
+pub const CKM_EC_KEY_PAIR_GEN: u32 = 4160;
+pub const CKM_ECDH1_DERIVE: u32 = 4176;
+pub const CKM_AES_CBC_PAD: u32 = 4229;
+pub const CKM_AES_GCM: u32 = 4231;
+pub const CKD_NULL: u32 = 1;
+
\ No newline at end of file diff --git a/book/rust-docs/src/nss_sys/bindings/pkixc.rs.html b/book/rust-docs/src/nss_sys/bindings/pkixc.rs.html new file mode 100644 index 0000000000..c75b66277f --- /dev/null +++ b/book/rust-docs/src/nss_sys/bindings/pkixc.rs.html @@ -0,0 +1,37 @@ +pkixc.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::*;
+
+extern "C" {
+    pub fn VerifyCodeSigningCertificateChain(
+        certificates: *mut *const u8,
+        certificateLengths: *const u16,
+        numCertificates: size_t,
+        secondsSinceEpoch: u64,
+        rootSHA256Hash: *const u8,
+        hostname: *const u8,
+        hostnameLength: size_t,
+        error: *mut PRErrorCode,
+    ) -> bool;
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nss_sys/bindings/plarena.rs.html b/book/rust-docs/src/nss_sys/bindings/plarena.rs.html new file mode 100644 index 0000000000..fc8fc757ac --- /dev/null +++ b/book/rust-docs/src/nss_sys/bindings/plarena.rs.html @@ -0,0 +1,45 @@ +plarena.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+pub use crate::*;
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub struct PLArena {
+    pub next: *mut PLArena,
+    pub base: PRUword,
+    pub limit: PRUword,
+    pub avail: PRUword,
+}
+
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub struct PLArenaPool {
+    pub first: PLArena,
+    pub current: *mut PLArena,
+    pub arenasize: PRUint32,
+    pub mask: PRUword,
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nss_sys/bindings/prerror.rs.html b/book/rust-docs/src/nss_sys/bindings/prerror.rs.html new file mode 100644 index 0000000000..f0f00e4007 --- /dev/null +++ b/book/rust-docs/src/nss_sys/bindings/prerror.rs.html @@ -0,0 +1,29 @@ +prerror.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+pub use crate::*;
+use std::os::raw::c_char;
+
+extern "C" {
+    pub fn PR_GetError() -> PRErrorCode;
+    pub fn PR_GetErrorTextLength() -> PRInt32;
+    pub fn PR_GetErrorText(text: *mut c_char) -> PRInt32;
+}
+
+pub type PRErrorCode = PRInt32;
+
\ No newline at end of file diff --git a/book/rust-docs/src/nss_sys/bindings/prtypes.rs.html b/book/rust-docs/src/nss_sys/bindings/prtypes.rs.html new file mode 100644 index 0000000000..bd90db11d2 --- /dev/null +++ b/book/rust-docs/src/nss_sys/bindings/prtypes.rs.html @@ -0,0 +1,27 @@ +prtypes.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use std::os::raw::{c_int, c_uint};
+
+pub type PRIntn = c_int;
+pub type PRBool = PRIntn;
+pub type PRUword = usize;
+pub type PRInt32 = c_int;
+pub type PRUint32 = c_uint;
+pub const PR_FALSE: PRBool = 0;
+pub const PR_TRUE: PRBool = 1;
+
\ No newline at end of file diff --git a/book/rust-docs/src/nss_sys/bindings/secasn1t.rs.html b/book/rust-docs/src/nss_sys/bindings/secasn1t.rs.html new file mode 100644 index 0000000000..0d33b6b558 --- /dev/null +++ b/book/rust-docs/src/nss_sys/bindings/secasn1t.rs.html @@ -0,0 +1,11 @@ +secasn1t.rs - source
1
+2
+3
+4
+5
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+pub const SEC_ASN1_OBJECT_ID: u32 = 6;
+
\ No newline at end of file diff --git a/book/rust-docs/src/nss_sys/bindings/seccomon.rs.html b/book/rust-docs/src/nss_sys/bindings/seccomon.rs.html new file mode 100644 index 0000000000..24cc9b436d --- /dev/null +++ b/book/rust-docs/src/nss_sys/bindings/seccomon.rs.html @@ -0,0 +1,87 @@ +seccomon.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use std::os::raw::{c_uchar, c_uint};
+
+#[repr(u32)]
+pub enum SECItemType {
+    siBuffer = 0,
+    siClearDataBuffer = 1,
+    siCipherDataBuffer = 2,
+    siDERCertBuffer = 3,
+    siEncodedCertBuffer = 4,
+    siDERNameBuffer = 5,
+    siEncodedNameBuffer = 6,
+    siAsciiNameString = 7,
+    siAsciiString = 8,
+    siDEROID = 9,
+    siUnsignedInteger = 10,
+    siUTCTime = 11,
+    siGeneralizedTime = 12,
+    siVisibleString = 13,
+    siUTF8String = 14,
+    siBMPString = 15,
+}
+
+pub type SECItem = SECItemStr;
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct SECItemStr {
+    pub type_: u32, /* SECItemType */
+    pub data: *mut c_uchar,
+    pub len: c_uint,
+}
+
+#[repr(i32)]
+#[derive(PartialEq, Eq)]
+pub enum _SECStatus {
+    SECWouldBlock = -2,
+    SECFailure = -1,
+    SECSuccess = 0,
+}
+pub use _SECStatus as SECStatus;
+
\ No newline at end of file diff --git a/book/rust-docs/src/nss_sys/bindings/secitem.rs.html b/book/rust-docs/src/nss_sys/bindings/secitem.rs.html new file mode 100644 index 0000000000..6c87cb1d52 --- /dev/null +++ b/book/rust-docs/src/nss_sys/bindings/secitem.rs.html @@ -0,0 +1,19 @@ +secitem.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+pub use crate::*;
+
+extern "C" {
+    pub fn SECITEM_FreeItem(zap: *mut SECItem, freeit: PRBool);
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nss_sys/bindings/seckey.rs.html b/book/rust-docs/src/nss_sys/bindings/seckey.rs.html new file mode 100644 index 0000000000..4deca1aa7a --- /dev/null +++ b/book/rust-docs/src/nss_sys/bindings/seckey.rs.html @@ -0,0 +1,19 @@ +seckey.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+pub use crate::*;
+
+extern "C" {
+    pub fn CERT_ExtractPublicKey(cert: *mut CERTCertificate) -> *mut SECKEYPublicKey;
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nss_sys/bindings/secmodt.rs.html b/book/rust-docs/src/nss_sys/bindings/secmodt.rs.html new file mode 100644 index 0000000000..ce2829faeb --- /dev/null +++ b/book/rust-docs/src/nss_sys/bindings/secmodt.rs.html @@ -0,0 +1,67 @@ +secmodt.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Opaque pointers as these types are giant.
+pub type PK11SlotInfo = u8;
+pub type PK11SymKey = u8;
+pub type PK11Context = u8;
+
+#[repr(u32)]
+pub enum PK11Origin {
+    PK11_OriginNULL = 0,
+    PK11_OriginDerive = 1,
+    PK11_OriginGenerated = 2,
+    PK11_OriginFortezzaHack = 3,
+    PK11_OriginUnwrap = 4,
+}
+
+#[repr(u32)]
+pub enum PK11ObjectType {
+    PK11_TypeGeneric = 0,
+    PK11_TypePrivKey = 1,
+    PK11_TypePubKey = 2,
+    PK11_TypeCert = 3,
+    PK11_TypeSymKey = 4,
+}
+
+// #[repr(C)]
+// #[derive(Copy, Clone)]
+// pub struct PK11GenericObjectStr {
+//     _unused: [u8; 0],
+// }
+pub type PK11GenericObject = u8;
+
\ No newline at end of file diff --git a/book/rust-docs/src/nss_sys/bindings/secoid.rs.html b/book/rust-docs/src/nss_sys/bindings/secoid.rs.html new file mode 100644 index 0000000000..94b973e40e --- /dev/null +++ b/book/rust-docs/src/nss_sys/bindings/secoid.rs.html @@ -0,0 +1,21 @@ +secoid.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+pub use crate::*;
+
+extern "C" {
+    pub fn SECOID_FindOIDByTag(tagnum: u32 /* SECOidTag */) -> *mut SECOidData;
+    pub fn SECOID_DestroyAlgorithmID(aid: *mut SECAlgorithmID, freeit: PRBool);
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nss_sys/bindings/secoidt.rs.html b/book/rust-docs/src/nss_sys/bindings/secoidt.rs.html new file mode 100644 index 0000000000..ee5915d79c --- /dev/null +++ b/book/rust-docs/src/nss_sys/bindings/secoidt.rs.html @@ -0,0 +1,803 @@ +secoidt.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+pub use crate::*;
+use std::os::raw::{c_char, c_ulong};
+
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub struct SECAlgorithmIDStr {
+    pub algorithm: SECItem,
+    pub parameters: SECItem,
+}
+
+pub type SECAlgorithmID = SECAlgorithmIDStr;
+
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub struct SECOidDataStr {
+    pub oid: SECItem,
+    pub offset: u32, /* SECOidTag */
+    pub desc: *const c_char,
+    pub mechanism: c_ulong,
+    pub supportedExtension: u32, /* SECSupportExtenTag */
+}
+pub type SECOidData = SECOidDataStr;
+
+pub enum SECSupportExtenTag {
+    INVALID_CERT_EXTENSION = 0,
+    UNSUPPORTED_CERT_EXTENSION = 1,
+    SUPPORTED_CERT_EXTENSION = 2,
+}
+
+#[repr(u32)]
+pub enum SECOidTag {
+    SEC_OID_UNKNOWN = 0,
+    SEC_OID_MD2 = 1,
+    SEC_OID_MD4 = 2,
+    SEC_OID_MD5 = 3,
+    SEC_OID_SHA1 = 4,
+    SEC_OID_RC2_CBC = 5,
+    SEC_OID_RC4 = 6,
+    SEC_OID_DES_EDE3_CBC = 7,
+    SEC_OID_RC5_CBC_PAD = 8,
+    SEC_OID_DES_ECB = 9,
+    SEC_OID_DES_CBC = 10,
+    SEC_OID_DES_OFB = 11,
+    SEC_OID_DES_CFB = 12,
+    SEC_OID_DES_MAC = 13,
+    SEC_OID_DES_EDE = 14,
+    SEC_OID_ISO_SHA_WITH_RSA_SIGNATURE = 15,
+    SEC_OID_PKCS1_RSA_ENCRYPTION = 16,
+    SEC_OID_PKCS1_MD2_WITH_RSA_ENCRYPTION = 17,
+    SEC_OID_PKCS1_MD4_WITH_RSA_ENCRYPTION = 18,
+    SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION = 19,
+    SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION = 20,
+    SEC_OID_PKCS5_PBE_WITH_MD2_AND_DES_CBC = 21,
+    SEC_OID_PKCS5_PBE_WITH_MD5_AND_DES_CBC = 22,
+    SEC_OID_PKCS5_PBE_WITH_SHA1_AND_DES_CBC = 23,
+    SEC_OID_PKCS7 = 24,
+    SEC_OID_PKCS7_DATA = 25,
+    SEC_OID_PKCS7_SIGNED_DATA = 26,
+    SEC_OID_PKCS7_ENVELOPED_DATA = 27,
+    SEC_OID_PKCS7_SIGNED_ENVELOPED_DATA = 28,
+    SEC_OID_PKCS7_DIGESTED_DATA = 29,
+    SEC_OID_PKCS7_ENCRYPTED_DATA = 30,
+    SEC_OID_PKCS9_EMAIL_ADDRESS = 31,
+    SEC_OID_PKCS9_UNSTRUCTURED_NAME = 32,
+    SEC_OID_PKCS9_CONTENT_TYPE = 33,
+    SEC_OID_PKCS9_MESSAGE_DIGEST = 34,
+    SEC_OID_PKCS9_SIGNING_TIME = 35,
+    SEC_OID_PKCS9_COUNTER_SIGNATURE = 36,
+    SEC_OID_PKCS9_CHALLENGE_PASSWORD = 37,
+    SEC_OID_PKCS9_UNSTRUCTURED_ADDRESS = 38,
+    SEC_OID_PKCS9_EXTENDED_CERTIFICATE_ATTRIBUTES = 39,
+    SEC_OID_PKCS9_SMIME_CAPABILITIES = 40,
+    SEC_OID_AVA_COMMON_NAME = 41,
+    SEC_OID_AVA_COUNTRY_NAME = 42,
+    SEC_OID_AVA_LOCALITY = 43,
+    SEC_OID_AVA_STATE_OR_PROVINCE = 44,
+    SEC_OID_AVA_ORGANIZATION_NAME = 45,
+    SEC_OID_AVA_ORGANIZATIONAL_UNIT_NAME = 46,
+    SEC_OID_AVA_DN_QUALIFIER = 47,
+    SEC_OID_AVA_DC = 48,
+    SEC_OID_NS_TYPE_GIF = 49,
+    SEC_OID_NS_TYPE_JPEG = 50,
+    SEC_OID_NS_TYPE_URL = 51,
+    SEC_OID_NS_TYPE_HTML = 52,
+    SEC_OID_NS_TYPE_CERT_SEQUENCE = 53,
+    SEC_OID_MISSI_KEA_DSS_OLD = 54,
+    SEC_OID_MISSI_DSS_OLD = 55,
+    SEC_OID_MISSI_KEA_DSS = 56,
+    SEC_OID_MISSI_DSS = 57,
+    SEC_OID_MISSI_KEA = 58,
+    SEC_OID_MISSI_ALT_KEA = 59,
+    SEC_OID_NS_CERT_EXT_NETSCAPE_OK = 60,
+    SEC_OID_NS_CERT_EXT_ISSUER_LOGO = 61,
+    SEC_OID_NS_CERT_EXT_SUBJECT_LOGO = 62,
+    SEC_OID_NS_CERT_EXT_CERT_TYPE = 63,
+    SEC_OID_NS_CERT_EXT_BASE_URL = 64,
+    SEC_OID_NS_CERT_EXT_REVOCATION_URL = 65,
+    SEC_OID_NS_CERT_EXT_CA_REVOCATION_URL = 66,
+    SEC_OID_NS_CERT_EXT_CA_CRL_URL = 67,
+    SEC_OID_NS_CERT_EXT_CA_CERT_URL = 68,
+    SEC_OID_NS_CERT_EXT_CERT_RENEWAL_URL = 69,
+    SEC_OID_NS_CERT_EXT_CA_POLICY_URL = 70,
+    SEC_OID_NS_CERT_EXT_HOMEPAGE_URL = 71,
+    SEC_OID_NS_CERT_EXT_ENTITY_LOGO = 72,
+    SEC_OID_NS_CERT_EXT_USER_PICTURE = 73,
+    SEC_OID_NS_CERT_EXT_SSL_SERVER_NAME = 74,
+    SEC_OID_NS_CERT_EXT_COMMENT = 75,
+    SEC_OID_NS_CERT_EXT_LOST_PASSWORD_URL = 76,
+    SEC_OID_NS_CERT_EXT_CERT_RENEWAL_TIME = 77,
+    SEC_OID_NS_KEY_USAGE_GOVT_APPROVED = 78,
+    SEC_OID_X509_SUBJECT_DIRECTORY_ATTR = 79,
+    SEC_OID_X509_SUBJECT_KEY_ID = 80,
+    SEC_OID_X509_KEY_USAGE = 81,
+    SEC_OID_X509_PRIVATE_KEY_USAGE_PERIOD = 82,
+    SEC_OID_X509_SUBJECT_ALT_NAME = 83,
+    SEC_OID_X509_ISSUER_ALT_NAME = 84,
+    SEC_OID_X509_BASIC_CONSTRAINTS = 85,
+    SEC_OID_X509_NAME_CONSTRAINTS = 86,
+    SEC_OID_X509_CRL_DIST_POINTS = 87,
+    SEC_OID_X509_CERTIFICATE_POLICIES = 88,
+    SEC_OID_X509_POLICY_MAPPINGS = 89,
+    SEC_OID_X509_POLICY_CONSTRAINTS = 90,
+    SEC_OID_X509_AUTH_KEY_ID = 91,
+    SEC_OID_X509_EXT_KEY_USAGE = 92,
+    SEC_OID_X509_AUTH_INFO_ACCESS = 93,
+    SEC_OID_X509_CRL_NUMBER = 94,
+    SEC_OID_X509_REASON_CODE = 95,
+    SEC_OID_X509_INVALID_DATE = 96,
+    SEC_OID_X500_RSA_ENCRYPTION = 97,
+    SEC_OID_RFC1274_UID = 98,
+    SEC_OID_RFC1274_MAIL = 99,
+    SEC_OID_PKCS12 = 100,
+    SEC_OID_PKCS12_MODE_IDS = 101,
+    SEC_OID_PKCS12_ESPVK_IDS = 102,
+    SEC_OID_PKCS12_BAG_IDS = 103,
+    SEC_OID_PKCS12_CERT_BAG_IDS = 104,
+    SEC_OID_PKCS12_OIDS = 105,
+    SEC_OID_PKCS12_PBE_IDS = 106,
+    SEC_OID_PKCS12_SIGNATURE_IDS = 107,
+    SEC_OID_PKCS12_ENVELOPING_IDS = 108,
+    SEC_OID_PKCS12_PKCS8_KEY_SHROUDING = 109,
+    SEC_OID_PKCS12_KEY_BAG_ID = 110,
+    SEC_OID_PKCS12_CERT_AND_CRL_BAG_ID = 111,
+    SEC_OID_PKCS12_SECRET_BAG_ID = 112,
+    SEC_OID_PKCS12_X509_CERT_CRL_BAG = 113,
+    SEC_OID_PKCS12_SDSI_CERT_BAG = 114,
+    SEC_OID_PKCS12_PBE_WITH_SHA1_AND_128_BIT_RC4 = 115,
+    SEC_OID_PKCS12_PBE_WITH_SHA1_AND_40_BIT_RC4 = 116,
+    SEC_OID_PKCS12_PBE_WITH_SHA1_AND_TRIPLE_DES_CBC = 117,
+    SEC_OID_PKCS12_PBE_WITH_SHA1_AND_128_BIT_RC2_CBC = 118,
+    SEC_OID_PKCS12_PBE_WITH_SHA1_AND_40_BIT_RC2_CBC = 119,
+    SEC_OID_PKCS12_RSA_ENCRYPTION_WITH_128_BIT_RC4 = 120,
+    SEC_OID_PKCS12_RSA_ENCRYPTION_WITH_40_BIT_RC4 = 121,
+    SEC_OID_PKCS12_RSA_ENCRYPTION_WITH_TRIPLE_DES = 122,
+    SEC_OID_PKCS12_RSA_SIGNATURE_WITH_SHA1_DIGEST = 123,
+    SEC_OID_ANSIX9_DSA_SIGNATURE = 124,
+    SEC_OID_ANSIX9_DSA_SIGNATURE_WITH_SHA1_DIGEST = 125,
+    SEC_OID_BOGUS_DSA_SIGNATURE_WITH_SHA1_DIGEST = 126,
+    SEC_OID_VERISIGN_USER_NOTICES = 127,
+    SEC_OID_PKIX_CPS_POINTER_QUALIFIER = 128,
+    SEC_OID_PKIX_USER_NOTICE_QUALIFIER = 129,
+    SEC_OID_PKIX_OCSP = 130,
+    SEC_OID_PKIX_OCSP_BASIC_RESPONSE = 131,
+    SEC_OID_PKIX_OCSP_NONCE = 132,
+    SEC_OID_PKIX_OCSP_CRL = 133,
+    SEC_OID_PKIX_OCSP_RESPONSE = 134,
+    SEC_OID_PKIX_OCSP_NO_CHECK = 135,
+    SEC_OID_PKIX_OCSP_ARCHIVE_CUTOFF = 136,
+    SEC_OID_PKIX_OCSP_SERVICE_LOCATOR = 137,
+    SEC_OID_PKIX_REGCTRL_REGTOKEN = 138,
+    SEC_OID_PKIX_REGCTRL_AUTHENTICATOR = 139,
+    SEC_OID_PKIX_REGCTRL_PKIPUBINFO = 140,
+    SEC_OID_PKIX_REGCTRL_PKI_ARCH_OPTIONS = 141,
+    SEC_OID_PKIX_REGCTRL_OLD_CERT_ID = 142,
+    SEC_OID_PKIX_REGCTRL_PROTOCOL_ENC_KEY = 143,
+    SEC_OID_PKIX_REGINFO_UTF8_PAIRS = 144,
+    SEC_OID_PKIX_REGINFO_CERT_REQUEST = 145,
+    SEC_OID_EXT_KEY_USAGE_SERVER_AUTH = 146,
+    SEC_OID_EXT_KEY_USAGE_CLIENT_AUTH = 147,
+    SEC_OID_EXT_KEY_USAGE_CODE_SIGN = 148,
+    SEC_OID_EXT_KEY_USAGE_EMAIL_PROTECT = 149,
+    SEC_OID_EXT_KEY_USAGE_TIME_STAMP = 150,
+    SEC_OID_OCSP_RESPONDER = 151,
+    SEC_OID_NETSCAPE_SMIME_KEA = 152,
+    SEC_OID_FORTEZZA_SKIPJACK = 153,
+    SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_128_BIT_RC4 = 154,
+    SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_40_BIT_RC4 = 155,
+    SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_3KEY_TRIPLE_DES_CBC = 156,
+    SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_2KEY_TRIPLE_DES_CBC = 157,
+    SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_128_BIT_RC2_CBC = 158,
+    SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_40_BIT_RC2_CBC = 159,
+    SEC_OID_PKCS12_SAFE_CONTENTS_ID = 160,
+    SEC_OID_PKCS12_PKCS8_SHROUDED_KEY_BAG_ID = 161,
+    SEC_OID_PKCS12_V1_KEY_BAG_ID = 162,
+    SEC_OID_PKCS12_V1_PKCS8_SHROUDED_KEY_BAG_ID = 163,
+    SEC_OID_PKCS12_V1_CERT_BAG_ID = 164,
+    SEC_OID_PKCS12_V1_CRL_BAG_ID = 165,
+    SEC_OID_PKCS12_V1_SECRET_BAG_ID = 166,
+    SEC_OID_PKCS12_V1_SAFE_CONTENTS_BAG_ID = 167,
+    SEC_OID_PKCS9_X509_CERT = 168,
+    SEC_OID_PKCS9_SDSI_CERT = 169,
+    SEC_OID_PKCS9_X509_CRL = 170,
+    SEC_OID_PKCS9_FRIENDLY_NAME = 171,
+    SEC_OID_PKCS9_LOCAL_KEY_ID = 172,
+    SEC_OID_BOGUS_KEY_USAGE = 173,
+    SEC_OID_X942_DIFFIE_HELMAN_KEY = 174,
+    SEC_OID_NETSCAPE_NICKNAME = 175,
+    SEC_OID_NETSCAPE_RECOVERY_REQUEST = 176,
+    SEC_OID_CERT_RENEWAL_LOCATOR = 177,
+    SEC_OID_NS_CERT_EXT_SCOPE_OF_USE = 178,
+    SEC_OID_CMS_EPHEMERAL_STATIC_DIFFIE_HELLMAN = 179,
+    SEC_OID_CMS_3DES_KEY_WRAP = 180,
+    SEC_OID_CMS_RC2_KEY_WRAP = 181,
+    SEC_OID_SMIME_ENCRYPTION_KEY_PREFERENCE = 182,
+    SEC_OID_AES_128_ECB = 183,
+    SEC_OID_AES_128_CBC = 184,
+    SEC_OID_AES_192_ECB = 185,
+    SEC_OID_AES_192_CBC = 186,
+    SEC_OID_AES_256_ECB = 187,
+    SEC_OID_AES_256_CBC = 188,
+    SEC_OID_SDN702_DSA_SIGNATURE = 189,
+    SEC_OID_MS_SMIME_ENCRYPTION_KEY_PREFERENCE = 190,
+    SEC_OID_SHA256 = 191,
+    SEC_OID_SHA384 = 192,
+    SEC_OID_SHA512 = 193,
+    SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION = 194,
+    SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION = 195,
+    SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION = 196,
+    SEC_OID_AES_128_KEY_WRAP = 197,
+    SEC_OID_AES_192_KEY_WRAP = 198,
+    SEC_OID_AES_256_KEY_WRAP = 199,
+    SEC_OID_ANSIX962_EC_PUBLIC_KEY = 200,
+    SEC_OID_ANSIX962_ECDSA_SHA1_SIGNATURE = 201,
+    SEC_OID_ANSIX962_EC_PRIME192V1 = 202,
+    SEC_OID_ANSIX962_EC_PRIME192V2 = 203,
+    SEC_OID_ANSIX962_EC_PRIME192V3 = 204,
+    SEC_OID_ANSIX962_EC_PRIME239V1 = 205,
+    SEC_OID_ANSIX962_EC_PRIME239V2 = 206,
+    SEC_OID_ANSIX962_EC_PRIME239V3 = 207,
+    SEC_OID_SECG_EC_SECP256R1 = 208,
+    SEC_OID_SECG_EC_SECP112R1 = 209,
+    SEC_OID_SECG_EC_SECP112R2 = 210,
+    SEC_OID_SECG_EC_SECP128R1 = 211,
+    SEC_OID_SECG_EC_SECP128R2 = 212,
+    SEC_OID_SECG_EC_SECP160K1 = 213,
+    SEC_OID_SECG_EC_SECP160R1 = 214,
+    SEC_OID_SECG_EC_SECP160R2 = 215,
+    SEC_OID_SECG_EC_SECP192K1 = 216,
+    SEC_OID_SECG_EC_SECP224K1 = 217,
+    SEC_OID_SECG_EC_SECP224R1 = 218,
+    SEC_OID_SECG_EC_SECP256K1 = 219,
+    SEC_OID_SECG_EC_SECP384R1 = 220,
+    SEC_OID_SECG_EC_SECP521R1 = 221,
+    SEC_OID_ANSIX962_EC_C2PNB163V1 = 222,
+    SEC_OID_ANSIX962_EC_C2PNB163V2 = 223,
+    SEC_OID_ANSIX962_EC_C2PNB163V3 = 224,
+    SEC_OID_ANSIX962_EC_C2PNB176V1 = 225,
+    SEC_OID_ANSIX962_EC_C2TNB191V1 = 226,
+    SEC_OID_ANSIX962_EC_C2TNB191V2 = 227,
+    SEC_OID_ANSIX962_EC_C2TNB191V3 = 228,
+    SEC_OID_ANSIX962_EC_C2ONB191V4 = 229,
+    SEC_OID_ANSIX962_EC_C2ONB191V5 = 230,
+    SEC_OID_ANSIX962_EC_C2PNB208W1 = 231,
+    SEC_OID_ANSIX962_EC_C2TNB239V1 = 232,
+    SEC_OID_ANSIX962_EC_C2TNB239V2 = 233,
+    SEC_OID_ANSIX962_EC_C2TNB239V3 = 234,
+    SEC_OID_ANSIX962_EC_C2ONB239V4 = 235,
+    SEC_OID_ANSIX962_EC_C2ONB239V5 = 236,
+    SEC_OID_ANSIX962_EC_C2PNB272W1 = 237,
+    SEC_OID_ANSIX962_EC_C2PNB304W1 = 238,
+    SEC_OID_ANSIX962_EC_C2TNB359V1 = 239,
+    SEC_OID_ANSIX962_EC_C2PNB368W1 = 240,
+    SEC_OID_ANSIX962_EC_C2TNB431R1 = 241,
+    SEC_OID_SECG_EC_SECT113R1 = 242,
+    SEC_OID_SECG_EC_SECT113R2 = 243,
+    SEC_OID_SECG_EC_SECT131R1 = 244,
+    SEC_OID_SECG_EC_SECT131R2 = 245,
+    SEC_OID_SECG_EC_SECT163K1 = 246,
+    SEC_OID_SECG_EC_SECT163R1 = 247,
+    SEC_OID_SECG_EC_SECT163R2 = 248,
+    SEC_OID_SECG_EC_SECT193R1 = 249,
+    SEC_OID_SECG_EC_SECT193R2 = 250,
+    SEC_OID_SECG_EC_SECT233K1 = 251,
+    SEC_OID_SECG_EC_SECT233R1 = 252,
+    SEC_OID_SECG_EC_SECT239K1 = 253,
+    SEC_OID_SECG_EC_SECT283K1 = 254,
+    SEC_OID_SECG_EC_SECT283R1 = 255,
+    SEC_OID_SECG_EC_SECT409K1 = 256,
+    SEC_OID_SECG_EC_SECT409R1 = 257,
+    SEC_OID_SECG_EC_SECT571K1 = 258,
+    SEC_OID_SECG_EC_SECT571R1 = 259,
+    SEC_OID_NETSCAPE_AOLSCREENNAME = 260,
+    SEC_OID_AVA_SURNAME = 261,
+    SEC_OID_AVA_SERIAL_NUMBER = 262,
+    SEC_OID_AVA_STREET_ADDRESS = 263,
+    SEC_OID_AVA_TITLE = 264,
+    SEC_OID_AVA_POSTAL_ADDRESS = 265,
+    SEC_OID_AVA_POSTAL_CODE = 266,
+    SEC_OID_AVA_POST_OFFICE_BOX = 267,
+    SEC_OID_AVA_GIVEN_NAME = 268,
+    SEC_OID_AVA_INITIALS = 269,
+    SEC_OID_AVA_GENERATION_QUALIFIER = 270,
+    SEC_OID_AVA_HOUSE_IDENTIFIER = 271,
+    SEC_OID_AVA_PSEUDONYM = 272,
+    SEC_OID_PKIX_CA_ISSUERS = 273,
+    SEC_OID_PKCS9_EXTENSION_REQUEST = 274,
+    SEC_OID_ANSIX962_ECDSA_SIGNATURE_RECOMMENDED_DIGEST = 275,
+    SEC_OID_ANSIX962_ECDSA_SIGNATURE_SPECIFIED_DIGEST = 276,
+    SEC_OID_ANSIX962_ECDSA_SHA224_SIGNATURE = 277,
+    SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE = 278,
+    SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE = 279,
+    SEC_OID_ANSIX962_ECDSA_SHA512_SIGNATURE = 280,
+    SEC_OID_X509_HOLD_INSTRUCTION_CODE = 281,
+    SEC_OID_X509_DELTA_CRL_INDICATOR = 282,
+    SEC_OID_X509_ISSUING_DISTRIBUTION_POINT = 283,
+    SEC_OID_X509_CERT_ISSUER = 284,
+    SEC_OID_X509_FRESHEST_CRL = 285,
+    SEC_OID_X509_INHIBIT_ANY_POLICY = 286,
+    SEC_OID_X509_SUBJECT_INFO_ACCESS = 287,
+    SEC_OID_CAMELLIA_128_CBC = 288,
+    SEC_OID_CAMELLIA_192_CBC = 289,
+    SEC_OID_CAMELLIA_256_CBC = 290,
+    SEC_OID_PKCS5_PBKDF2 = 291,
+    SEC_OID_PKCS5_PBES2 = 292,
+    SEC_OID_PKCS5_PBMAC1 = 293,
+    SEC_OID_HMAC_SHA1 = 294,
+    SEC_OID_HMAC_SHA224 = 295,
+    SEC_OID_HMAC_SHA256 = 296,
+    SEC_OID_HMAC_SHA384 = 297,
+    SEC_OID_HMAC_SHA512 = 298,
+    SEC_OID_PKIX_TIMESTAMPING = 299,
+    SEC_OID_PKIX_CA_REPOSITORY = 300,
+    SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE = 301,
+    SEC_OID_SEED_CBC = 302,
+    SEC_OID_X509_ANY_POLICY = 303,
+    SEC_OID_PKCS1_RSA_OAEP_ENCRYPTION = 304,
+    SEC_OID_PKCS1_MGF1 = 305,
+    SEC_OID_PKCS1_PSPECIFIED = 306,
+    SEC_OID_PKCS1_RSA_PSS_SIGNATURE = 307,
+    SEC_OID_PKCS1_SHA224_WITH_RSA_ENCRYPTION = 308,
+    SEC_OID_SHA224 = 309,
+    SEC_OID_EV_INCORPORATION_LOCALITY = 310,
+    SEC_OID_EV_INCORPORATION_STATE = 311,
+    SEC_OID_EV_INCORPORATION_COUNTRY = 312,
+    SEC_OID_BUSINESS_CATEGORY = 313,
+    SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA224_DIGEST = 314,
+    SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA256_DIGEST = 315,
+    SEC_OID_MS_EXT_KEY_USAGE_CTL_SIGNING = 316,
+    SEC_OID_AVA_NAME = 317,
+    SEC_OID_AES_128_GCM = 318,
+    SEC_OID_AES_192_GCM = 319,
+    SEC_OID_AES_256_GCM = 320,
+    SEC_OID_IDEA_CBC = 321,
+    SEC_OID_RC2_40_CBC = 322,
+    SEC_OID_DES_40_CBC = 323,
+    SEC_OID_RC4_40 = 324,
+    SEC_OID_RC4_56 = 325,
+    SEC_OID_NULL_CIPHER = 326,
+    SEC_OID_HMAC_MD5 = 327,
+    SEC_OID_TLS_RSA = 328,
+    SEC_OID_TLS_DHE_RSA = 329,
+    SEC_OID_TLS_DHE_DSS = 330,
+    SEC_OID_TLS_DH_RSA = 331,
+    SEC_OID_TLS_DH_DSS = 332,
+    SEC_OID_TLS_DH_ANON = 333,
+    SEC_OID_TLS_ECDHE_ECDSA = 334,
+    SEC_OID_TLS_ECDHE_RSA = 335,
+    SEC_OID_TLS_ECDH_ECDSA = 336,
+    SEC_OID_TLS_ECDH_RSA = 337,
+    SEC_OID_TLS_ECDH_ANON = 338,
+    SEC_OID_TLS_RSA_EXPORT = 339,
+    SEC_OID_TLS_DHE_RSA_EXPORT = 340,
+    SEC_OID_TLS_DHE_DSS_EXPORT = 341,
+    SEC_OID_TLS_DH_RSA_EXPORT = 342,
+    SEC_OID_TLS_DH_DSS_EXPORT = 343,
+    SEC_OID_TLS_DH_ANON_EXPORT = 344,
+    SEC_OID_APPLY_SSL_POLICY = 345,
+    SEC_OID_CHACHA20_POLY1305 = 346,
+    SEC_OID_TLS_ECDHE_PSK = 347,
+    SEC_OID_TLS_DHE_PSK = 348,
+    SEC_OID_TLS_FFDHE_2048 = 349,
+    SEC_OID_TLS_FFDHE_3072 = 350,
+    SEC_OID_TLS_FFDHE_4096 = 351,
+    SEC_OID_TLS_FFDHE_6144 = 352,
+    SEC_OID_TLS_FFDHE_8192 = 353,
+    SEC_OID_TLS_DHE_CUSTOM = 354,
+    SEC_OID_CURVE25519 = 355,
+    SEC_OID_TLS13_KEA_ANY = 356,
+    SEC_OID_X509_ANY_EXT_KEY_USAGE = 357,
+    SEC_OID_EXT_KEY_USAGE_IPSEC_IKE = 358,
+    SEC_OID_IPSEC_IKE_END = 359,
+    SEC_OID_IPSEC_IKE_INTERMEDIATE = 360,
+    SEC_OID_EXT_KEY_USAGE_IPSEC_END = 361,
+    SEC_OID_EXT_KEY_USAGE_IPSEC_TUNNEL = 362,
+    SEC_OID_EXT_KEY_USAGE_IPSEC_USER = 363,
+    SEC_OID_TOTAL = 364,
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nss_sys/bindings/secport.rs.html b/book/rust-docs/src/nss_sys/bindings/secport.rs.html new file mode 100644 index 0000000000..fa9689ce52 --- /dev/null +++ b/book/rust-docs/src/nss_sys/bindings/secport.rs.html @@ -0,0 +1,27 @@ +secport.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::*;
+use std::os::raw::{c_int, c_void};
+
+pub type size_t = usize;
+
+extern "C" {
+    pub fn PORT_FreeArena(arena: *mut PLArenaPool, zero: PRBool);
+    pub fn NSS_SecureMemcmp(a: *const c_void, b: *const c_void, n: size_t) -> c_int;
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/nss_sys/lib.rs.html b/book/rust-docs/src/nss_sys/lib.rs.html new file mode 100644 index 0000000000..f6f2ea9f38 --- /dev/null +++ b/book/rust-docs/src/nss_sys/lib.rs.html @@ -0,0 +1,33 @@ +lib.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#![allow(unknown_lints)]
+#![warn(rust_2018_idioms)]
+#![allow(non_camel_case_types, non_upper_case_globals, non_snake_case)]
+
+mod bindings;
+pub use bindings::*;
+
+// So we link against the SQLite lib imported by parent crates
+// such as places and logins.
+#[allow(unused_extern_crates)]
+#[cfg(any(not(feature = "gecko"), __appsvc_ci_hack))]
+extern crate libsqlite3_sys;
+
\ No newline at end of file diff --git a/book/rust-docs/src/places/api/history.rs.html b/book/rust-docs/src/places/api/history.rs.html new file mode 100644 index 0000000000..3bac43a747 --- /dev/null +++ b/book/rust-docs/src/places/api/history.rs.html @@ -0,0 +1,403 @@ +history.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use super::apply_observation;
+use crate::db::PlacesDb;
+use crate::error::*;
+use crate::observation::VisitObservation;
+use crate::types::*;
+use types::Timestamp;
+use url::Url;
+// This module can become, roughly: PlacesUtils.history()
+
+pub fn can_add_url(_url: &Url) -> Result<bool> {
+    Ok(true)
+}
+
+// eg: PlacesUtils.history.insert({url: "http", title: ..., visits: [{date: ...}]})
+
+// Structs representing place and visit infos for this API.
+// (Not clear this makes sense - it's a copy of what desktop does just to
+// get started)
+// NOTE THAT THESE STRUCTS are only for demo purposes, showing how
+// PlacesUtils.history.insert() could be implemented using the same shaped
+// objects.
+// They should really be moved into an "examples" folder.
+#[derive(Debug)]
+pub struct AddablePlaceInfo {
+    pub url: Url,
+    pub title: Option<String>,
+    pub visits: Vec<AddableVisit>,
+}
+
+#[derive(Debug)]
+pub struct AddableVisit {
+    pub date: Timestamp,
+    pub transition: VisitType,
+    pub referrer: Option<Url>,
+    pub is_local: bool,
+}
+
+// insert a visit a'la PlacesUtils.history.insert()
+pub fn insert(conn: &mut PlacesDb, place: AddablePlaceInfo) -> Result<()> {
+    for v in place.visits {
+        let obs = VisitObservation::new(place.url.clone())
+            .with_visit_type(v.transition)
+            .with_at(v.date)
+            .with_title(place.title.clone())
+            .with_is_remote(!v.is_local);
+        // .with_referrer(...) ????
+
+        //if place.referrer
+        apply_observation(conn, obs)?;
+    }
+    Ok(())
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::api::places_api::test::new_mem_connection;
+
+    #[test]
+    fn test_insert() {
+        let mut c = new_mem_connection();
+        let url = Url::parse("http://example.com").expect("it's a valid url");
+        let date = Timestamp::now();
+        let visits = vec![AddableVisit {
+            date,
+            transition: VisitType::Link,
+            referrer: None,
+            is_local: true,
+        }];
+        let a = AddablePlaceInfo {
+            url,
+            title: None,
+            visits,
+        };
+
+        insert(&mut c, a).expect("should insert");
+
+        // For now, a raw read of the DB.
+        let sql = "SELECT p.id, p.url, p.title,
+                          p.visit_count_local, p.visit_count_remote,
+                          p.hidden, p.typed, p.frecency,
+                          p.last_visit_date_local, p.last_visit_date_remote,
+                          p.guid, p.foreign_count, p.url_hash, p.description,
+                          p.preview_image_url, p.origin_id,
+                          v.is_local, v.from_visit, v.place_id,
+                          v.visit_date, v.visit_type
+                    FROM moz_places p, moz_historyvisits v
+                    WHERE v.place_id = p.id";
+
+        let mut stmt = c.db.prepare(sql).expect("valid sql");
+        let mut rows = stmt.query([]).expect("should execute");
+        let result = rows.next().expect("should get a row");
+        let row = result.expect("expect anything");
+
+        assert_eq!(
+            row.get::<_, String>("url").expect("should work"),
+            "http://example.com/"
+        ); // hrmph - note trailing slash
+        assert_eq!(
+            row.get::<_, Timestamp>("visit_date").expect("should work"),
+            date
+        );
+        assert_ne!(row.get::<_, i32>("frecency").expect("should work"), 0);
+        // XXX - check more.
+    }
+}
+
+/////////////////////////////////////////////
+// Stuff to reimplement nsHistory::VisitUri()
+fn is_recently_visited(_url: &Url) -> bool {
+    // History.cpp keeps an in-memory hashtable of urls visited in the last
+    // 6 minutes to avoid pages which self-refresh from getting many entries.
+    // ie, there's no DB query done here.
+    // TODO: implement this.
+    false
+}
+
+fn add_recently_visited(_url: &Url) {}
+
+// Other "recent" flags:
+// enum RecentEventFlags {
+//    RECENT_TYPED      = 1 << 0,    // User typed in URL recently
+//    RECENT_ACTIVATED  = 1 << 1,    // User tapped URL link recently
+//    RECENT_BOOKMARKED = 1 << 2     // User bookmarked URL recently
+//  };
+// All of which are just a 15-second in-memory cache, and all of which appear
+// to rely on explicit calls to set the flag. eg:
+// nsNavHistory::MarkPageAsTyped(nsIURI *aURI) just adds to the cache.
+
+// Is this URL the *source* is a redirect? Note that this is different than
+// the redirect flags in the TransitionType, as that is the flag for the
+// *target* of the redirect.
+#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd)]
+pub enum RedirectSourceType {
+    Temporary,
+    Permanent,
+}
+
+// nsIHistory::VisitURI - this is the main interface used by the browser
+// itself to record visits.
+// This differs from the desktop implementation in one major way - instead
+// of using various browser-specific heuristics to compute the VisitType
+// we assume the caller has already done this and passed the correct transition
+// flags in.
+pub fn visit_uri(
+    conn: &mut PlacesDb,
+    url: &Url,
+    last_url: Option<Url>,
+    // To be more honest, this would *not* take a VisitType,
+    // but instead other "internal" nsIHistory flags, from which
+    // it would deduce the VisitType.
+    transition: VisitType,
+    redirect_source: Option<RedirectSourceType>,
+    is_error_page: bool,
+) -> Result<()> {
+    // Silently return if URI is something we shouldn't add to DB.
+    if !can_add_url(url)? {
+        return Ok(());
+    };
+    // Do not save a reloaded uri if we have visited the same URI recently.
+    // (Note that desktop implies `reload` based of the "is it the same as last
+    // and is it recent" check below) - but here we are asking for the
+    // VisitType to be passed in, which explicity has a value for reload.
+    // Note clear if we should try and unify these.
+    // (and note that if we can, we can drop the recently_visited cache)
+    if let Some(ref last) = last_url {
+        if url == last && is_recently_visited(url) {
+            // it's a reload we don't want to record, although we do want to
+            // update it as being recent.
+            add_recently_visited(url);
+            return Ok(());
+        };
+    }
+    // So add it.
+
+    // XXX - translate the flags passed to this function, along with the
+    // RECENT_* cache above to create the correct Transition type.
+    // call get_hidden_state to see if .hidden should be set.
+
+    // get_hidden_state...
+
+    // EMBED visits are session-persistent and should not go through the database.
+    // They exist only to keep track of isVisited status during the session.
+    if transition == VisitType::Embed {
+        log::warn!("Embed visit, but in-memory storage of these isn't done yet");
+        return Ok(());
+    }
+
+    let obs = VisitObservation::new(url.clone())
+        .with_is_error(is_error_page)
+        .with_visit_type(transition)
+        .with_is_redirect_source(redirect_source.map(|_r| true))
+        .with_is_permanent_redirect_source(
+            redirect_source.map(|r| r == RedirectSourceType::Permanent),
+        );
+    apply_observation(conn, obs)
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/places/api/matcher.rs.html b/book/rust-docs/src/places/api/matcher.rs.html new file mode 100644 index 0000000000..8df855d147 --- /dev/null +++ b/book/rust-docs/src/places/api/matcher.rs.html @@ -0,0 +1,1549 @@ +matcher.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
+540
+541
+542
+543
+544
+545
+546
+547
+548
+549
+550
+551
+552
+553
+554
+555
+556
+557
+558
+559
+560
+561
+562
+563
+564
+565
+566
+567
+568
+569
+570
+571
+572
+573
+574
+575
+576
+577
+578
+579
+580
+581
+582
+583
+584
+585
+586
+587
+588
+589
+590
+591
+592
+593
+594
+595
+596
+597
+598
+599
+600
+601
+602
+603
+604
+605
+606
+607
+608
+609
+610
+611
+612
+613
+614
+615
+616
+617
+618
+619
+620
+621
+622
+623
+624
+625
+626
+627
+628
+629
+630
+631
+632
+633
+634
+635
+636
+637
+638
+639
+640
+641
+642
+643
+644
+645
+646
+647
+648
+649
+650
+651
+652
+653
+654
+655
+656
+657
+658
+659
+660
+661
+662
+663
+664
+665
+666
+667
+668
+669
+670
+671
+672
+673
+674
+675
+676
+677
+678
+679
+680
+681
+682
+683
+684
+685
+686
+687
+688
+689
+690
+691
+692
+693
+694
+695
+696
+697
+698
+699
+700
+701
+702
+703
+704
+705
+706
+707
+708
+709
+710
+711
+712
+713
+714
+715
+716
+717
+718
+719
+720
+721
+722
+723
+724
+725
+726
+727
+728
+729
+730
+731
+732
+733
+734
+735
+736
+737
+738
+739
+740
+741
+742
+743
+744
+745
+746
+747
+748
+749
+750
+751
+752
+753
+754
+755
+756
+757
+758
+759
+760
+761
+762
+763
+764
+765
+766
+767
+768
+769
+770
+771
+772
+773
+774
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::db::PlacesDb;
+use crate::error::Result;
+use crate::ffi::SearchResult as FfiSearchResult;
+pub use crate::match_impl::{MatchBehavior, SearchBehavior};
+use rusqlite::Row;
+use serde_derive::*;
+use sql_support::ConnExt;
+use url::Url;
+
+// A helper to log, cache and execute a query, returning a vector of flattened rows.
+fn query_flat_rows_and_then<T, F, P>(
+    conn: &PlacesDb,
+    sql: &str,
+    params: P,
+    mapper: F,
+) -> Result<Vec<T>>
+where
+    F: FnMut(&Row<'_>) -> Result<T>,
+    P: rusqlite::Params,
+{
+    let mut stmt = conn.prepare_maybe_cached(sql, true)?;
+    let iter = stmt.query_and_then(params, mapper)?;
+    Ok(iter
+        .inspect(|r| {
+            if let Err(ref e) = *r {
+                log::warn!("Failed to perform a search: {}", e);
+                if cfg!(debug_assertions) {
+                    panic!("Failed to perform a search: {}", e);
+                }
+            }
+        })
+        .flatten()
+        .collect::<Vec<_>>())
+}
+
+#[derive(Debug, Clone)]
+pub struct SearchParams {
+    pub search_string: String,
+    pub limit: u32,
+}
+
+/// Synchronously queries all providers for autocomplete matches, then filters
+/// the matches. This isn't cancelable yet; once a search is started, it can't
+/// be interrupted, even if the user moves on (see
+/// https://github.com/mozilla/application-services/issues/265).
+///
+/// A provider can be anything that returns URL suggestions: Places history
+/// and bookmarks, synced tabs, search engine suggestions, and search keywords.
+pub fn search_frecent(conn: &PlacesDb, params: SearchParams) -> Result<Vec<SearchResult>> {
+    // TODO: Tokenize the query.
+
+    // Try to find the first heuristic result. Desktop tries extensions,
+    // search engine aliases, origins, URLs, search engine domains, and
+    // preloaded sites, before trying to fall back to fixing up the URL,
+    // and a search if all else fails. We only try origins and URLs for
+    // heuristic matches, since that's all we support.
+
+    let mut matches = match_with_limit(
+        conn,
+        &[
+            // Try to match on the origin, or the full URL.
+            &OriginOrUrl::new(&params.search_string),
+            // query adaptive matches and suggestions, matching Anywhere.
+            &Adaptive::with_behavior(
+                &params.search_string,
+                MatchBehavior::Anywhere,
+                SearchBehavior::default(),
+            ),
+            &Suggestions::with_behavior(
+                &params.search_string,
+                MatchBehavior::Anywhere,
+                SearchBehavior::default(),
+            ),
+        ],
+        params.limit,
+    )?;
+
+    matches.sort_unstable_by(|a, b| a.url.cmp(&b.url));
+    matches.dedup_by(|a, b| a.url == b.url);
+
+    Ok(matches)
+}
+
+pub fn match_url(conn: &PlacesDb, query: impl AsRef<str>) -> Result<Option<Url>> {
+    let scope = conn.begin_interrupt_scope()?;
+    let matcher = OriginOrUrl::new(query.as_ref());
+    // Note: The matcher ignores the limit argument (it's a trait method)
+    let results = matcher.search(conn, 1)?;
+    scope.err_if_interrupted()?;
+    // Doing it like this lets us move the result, avoiding a copy (which almost
+    // certainly doesn't matter but whatever)
+    if let Some(res) = results.into_iter().next() {
+        Ok(Some(res.url))
+    } else {
+        Ok(None)
+    }
+}
+
+fn match_with_limit(
+    conn: &PlacesDb,
+    matchers: &[&dyn Matcher],
+    max_results: u32,
+) -> Result<Vec<SearchResult>> {
+    let mut results = Vec::new();
+    let mut rem_results = max_results;
+    let scope = conn.begin_interrupt_scope()?;
+    for m in matchers {
+        if rem_results == 0 {
+            break;
+        }
+        scope.err_if_interrupted()?;
+        let matches = m.search(conn, rem_results)?;
+        results.extend(matches);
+        rem_results = rem_results.saturating_sub(results.len() as u32);
+    }
+    Ok(results)
+}
+
+/// Records an accepted autocomplete match, recording the query string,
+/// and chosen URL for subsequent matches.
+pub fn accept_result(conn: &PlacesDb, search_string: &str, url: &Url) -> Result<()> {
+    // See `nsNavHistory::AutoCompleteFeedback`.
+    conn.execute(
+        "INSERT OR REPLACE INTO moz_inputhistory(place_id, input, use_count)
+         SELECT h.id, IFNULL(i.input, :input_text), IFNULL(i.use_count, 0) * .9 + 1
+         FROM moz_places h
+         LEFT JOIN moz_inputhistory i ON i.place_id = h.id AND i.input = :input_text
+         WHERE url_hash = hash(:page_url) AND url = :page_url",
+        &[
+            (":input_text", &search_string),
+            (":page_url", &url.as_str()),
+        ],
+    )?;
+
+    Ok(())
+}
+
+pub fn split_after_prefix(href: &str) -> (&str, &str) {
+    // Only search up to 64 bytes (matches desktop behavior)
+    let haystack = &href.as_bytes()[..href.len().min(64)];
+    match memchr::memchr(b':', haystack) {
+        None => ("", href),
+        Some(index) => {
+            let hb = href.as_bytes();
+            let mut end = index + 1;
+            if hb.len() >= end + 2 && hb[end] == b'/' && hb[end + 1] == b'/' {
+                end += 2;
+            }
+            href.split_at(end)
+        }
+    }
+}
+
+pub fn split_after_host_and_port(href: &str) -> (&str, &str) {
+    let (_, remainder) = split_after_prefix(href);
+
+    let hp_definite_end =
+        memchr::memchr3(b'/', b'?', b'#', remainder.as_bytes()).unwrap_or(remainder.len());
+
+    let (before_hp, after_hp) = remainder.split_at(hp_definite_end);
+
+    let auth_end = memchr::memchr(b'@', before_hp.as_bytes())
+        .map(|i| i + 1)
+        .unwrap_or(0);
+
+    (&before_hp[auth_end..], after_hp)
+}
+
+fn looks_like_origin(string: &str) -> bool {
+    // Skip nonascii characters, we'll either handle them in autocomplete_match or,
+    // a later part of the origins query.
+    !string.is_empty()
+        && !string.bytes().any(|c| {
+            !c.is_ascii() || c.is_ascii_whitespace() || c == b'/' || c == b'?' || c == b'#'
+        })
+}
+
+#[derive(Debug, Clone, Serialize, Eq, PartialEq)]
+pub struct SearchResult {
+    /// The search string for this match.
+    pub search_string: String,
+
+    /// The URL to open when the user confirms a match. This is
+    /// equivalent to `nsIAutoCompleteResult.getFinalCompleteValueAt`.
+    pub url: Url,
+
+    /// The title of the autocompleted value, to show in the UI. This can be the
+    /// title of the bookmark or page, origin, URL, or URL fragment.
+    pub title: String,
+
+    /// The favicon URL.
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub icon_url: Option<Url>,
+
+    /// A frecency score for this match.
+    pub frecency: i64,
+}
+
+impl SearchResult {
+    /// Default search behaviors from Desktop: HISTORY, BOOKMARK, OPENPAGE, SEARCHES.
+    /// Default match behavior: MATCH_BOUNDARY_ANYWHERE.
+    pub fn from_adaptive_row(row: &rusqlite::Row<'_>) -> Result<Self> {
+        let search_string = row.get::<_, String>("searchString")?;
+        let _place_id = row.get::<_, i64>("id")?;
+        let url = row.get::<_, String>("url")?;
+        let history_title = row.get::<_, Option<String>>("title")?;
+        let bookmark_title = row.get::<_, Option<String>>("btitle")?;
+        let frecency = row.get::<_, i64>("frecency")?;
+        let title = bookmark_title.or(history_title).unwrap_or_default();
+        let url = Url::parse(&url)?;
+
+        Ok(Self {
+            search_string,
+            url,
+            title,
+            icon_url: None,
+            frecency,
+        })
+    }
+
+    pub fn from_suggestion_row(row: &rusqlite::Row<'_>) -> Result<Self> {
+        let search_string = row.get::<_, String>("searchString")?;
+        let url = row.get::<_, String>("url")?;
+
+        let history_title = row.get::<_, Option<String>>("title")?;
+        let bookmark_title = row.get::<_, Option<String>>("btitle")?;
+        let title = bookmark_title.or(history_title).unwrap_or_default();
+
+        let url = Url::parse(&url)?;
+
+        let frecency = row.get::<_, i64>("frecency")?;
+
+        Ok(Self {
+            search_string,
+            url,
+            title,
+            icon_url: None,
+            frecency,
+        })
+    }
+
+    pub fn from_origin_row(row: &rusqlite::Row<'_>) -> Result<Self> {
+        let search_string = row.get::<_, String>("searchString")?;
+        let url = row.get::<_, String>("url")?;
+        let display_url = row.get::<_, String>("displayURL")?;
+        let frecency = row.get::<_, i64>("frecency")?;
+
+        let url = Url::parse(&url)?;
+
+        Ok(Self {
+            search_string,
+            url,
+            title: display_url,
+            icon_url: None,
+            frecency,
+        })
+    }
+
+    pub fn from_url_row(row: &rusqlite::Row<'_>) -> Result<Self> {
+        let search_string = row.get::<_, String>("searchString")?;
+        let href = row.get::<_, String>("url")?;
+        let stripped_url = row.get::<_, String>("strippedURL")?;
+        let frecency = row.get::<_, i64>("frecency")?;
+
+        let (url, display_url) = match href.find(&stripped_url) {
+            Some(stripped_url_index) => {
+                let stripped_prefix = &href[..stripped_url_index];
+                let title = match &href[stripped_url_index + stripped_url.len()..].find('/') {
+                    Some(next_slash_index) => {
+                        &href[stripped_url_index
+                            ..=stripped_url_index + stripped_url.len() + next_slash_index]
+                    }
+                    None => &href[stripped_url_index..],
+                };
+                let url = Url::parse(&[stripped_prefix, title].concat())?;
+                (url, title.into())
+            }
+            None => {
+                let url = Url::parse(&href)?;
+                (url, stripped_url)
+            }
+        };
+
+        Ok(Self {
+            search_string,
+            url,
+            title: display_url,
+            icon_url: None,
+            frecency,
+        })
+    }
+}
+
+impl From<SearchResult> for FfiSearchResult {
+    fn from(res: SearchResult) -> Self {
+        Self {
+            url: res.url,
+            title: res.title,
+            frecency: res.frecency,
+        }
+    }
+}
+
+trait Matcher {
+    fn search(&self, conn: &PlacesDb, max_results: u32) -> Result<Vec<SearchResult>>;
+}
+
+struct OriginOrUrl<'query> {
+    query: &'query str,
+}
+
+impl<'query> OriginOrUrl<'query> {
+    pub fn new(query: &'query str) -> OriginOrUrl<'query> {
+        OriginOrUrl { query }
+    }
+}
+
+const URL_SQL: &str = "
+    SELECT h.url as url,
+            :host || :remainder AS strippedURL,
+            h.frecency as frecency,
+            h.foreign_count > 0 AS bookmarked,
+            h.id as id,
+            :searchString AS searchString
+    FROM moz_places h
+    JOIN moz_origins o ON o.id = h.origin_id
+    WHERE o.rev_host = reverse_host(:host)
+            AND MAX(h.frecency, 0) >= :frecencyThreshold
+            AND h.hidden = 0
+            AND strip_prefix_and_userinfo(h.url) BETWEEN strippedURL AND strippedURL || X'FFFF'
+    UNION ALL
+    SELECT h.url as url,
+            :host || :remainder AS strippedURL,
+            h.frecency as frecency,
+            h.foreign_count > 0 AS bookmarked,
+            h.id as id,
+            :searchString AS searchString
+    FROM moz_places h
+    JOIN moz_origins o ON o.id = h.origin_id
+    WHERE o.rev_host = reverse_host(:host) || 'www.'
+            AND MAX(h.frecency, 0) >= :frecencyThreshold
+            AND h.hidden = 0
+            AND strip_prefix_and_userinfo(h.url) BETWEEN 'www.' || strippedURL AND 'www.' || strippedURL || X'FFFF'
+    ORDER BY h.frecency DESC, h.id DESC
+    LIMIT 1
+";
+const ORIGIN_SQL: &str = "
+    SELECT IFNULL(:prefix, prefix) || moz_origins.host || '/' AS url,
+            moz_origins.host || '/' AS displayURL,
+            frecency,
+            bookmarked,
+            id,
+            :searchString AS searchString
+    FROM (
+        SELECT host,
+                TOTAL(frecency) AS host_frecency,
+                (SELECT TOTAL(foreign_count) > 0 FROM moz_places
+                WHERE moz_places.origin_id = moz_origins.id) AS bookmarked
+        FROM moz_origins
+        WHERE host BETWEEN :searchString AND :searchString || X'FFFF'
+        GROUP BY host
+        HAVING host_frecency >= :frecencyThreshold
+        UNION ALL
+        SELECT host,
+                TOTAL(frecency) AS host_frecency,
+                (SELECT TOTAL(foreign_count) > 0 FROM moz_places
+                WHERE moz_places.origin_id = moz_origins.id) AS bookmarked
+        FROM moz_origins
+        WHERE host BETWEEN 'www.' || :searchString AND 'www.' || :searchString || X'FFFF'
+        GROUP BY host
+        HAVING host_frecency >= :frecencyThreshold
+    ) AS grouped_hosts
+    JOIN moz_origins ON moz_origins.host = grouped_hosts.host
+    ORDER BY frecency DESC, id DESC
+    LIMIT 1
+";
+
+impl<'query> Matcher for OriginOrUrl<'query> {
+    fn search(&self, conn: &PlacesDb, _: u32) -> Result<Vec<SearchResult>> {
+        Ok(if looks_like_origin(self.query) {
+            query_flat_rows_and_then(
+                conn,
+                ORIGIN_SQL,
+                &[
+                    (":prefix", &rusqlite::types::Null as &dyn rusqlite::ToSql),
+                    (":searchString", &self.query),
+                    (":frecencyThreshold", &-1i64),
+                ],
+                SearchResult::from_origin_row,
+            )?
+        } else if self.query.contains(|c| c == '/' || c == ':' || c == '?') {
+            let (host, remainder) = split_after_host_and_port(self.query);
+            // This can fail if the "host" has some characters that are not
+            // currently allowed in URLs (even when punycoded). If that happens,
+            // then the query we'll use here can't return any results (and
+            // indeed, `reverse_host` will get mad at us since it's an invalid
+            // host), so we just return an empty results set.
+            let punycode_host = idna::domain_to_ascii(host);
+            let host_str = if let Ok(host) = &punycode_host {
+                host.as_str()
+            } else {
+                return Ok(vec![]);
+            };
+            query_flat_rows_and_then(
+                conn,
+                URL_SQL,
+                &[
+                    (":searchString", &self.query as &dyn rusqlite::ToSql),
+                    (":host", &host_str),
+                    (":remainder", &remainder),
+                    (":frecencyThreshold", &-1i64),
+                ],
+                SearchResult::from_url_row,
+            )?
+        } else {
+            vec![]
+        })
+    }
+}
+
+struct Adaptive<'query> {
+    query: &'query str,
+    match_behavior: MatchBehavior,
+    search_behavior: SearchBehavior,
+}
+
+impl<'query> Adaptive<'query> {
+    pub fn with_behavior(
+        query: &'query str,
+        match_behavior: MatchBehavior,
+        search_behavior: SearchBehavior,
+    ) -> Adaptive<'query> {
+        Adaptive {
+            query,
+            match_behavior,
+            search_behavior,
+        }
+    }
+}
+
+impl<'query> Matcher for Adaptive<'query> {
+    fn search(&self, conn: &PlacesDb, max_results: u32) -> Result<Vec<SearchResult>> {
+        query_flat_rows_and_then(
+            conn,
+            "
+            SELECT h.url as url,
+                   h.title as title,
+                   EXISTS(SELECT 1 FROM moz_bookmarks
+                          WHERE fk = h.id) AS bookmarked,
+                   (SELECT title FROM moz_bookmarks
+                    WHERE fk = h.id AND
+                          title NOT NULL
+                    ORDER BY lastModified DESC
+                    LIMIT 1) AS btitle,
+                   NULL AS tags,
+                   h.visit_count_local + h.visit_count_remote AS visit_count,
+                   h.typed as typed,
+                   h.id as id,
+                   NULL AS open_count,
+                   h.frecency as frecency,
+                   :searchString AS searchString
+            FROM (
+              SELECT ROUND(MAX(use_count) * (1 + (input = :searchString)), 1) AS rank,
+                     place_id
+              FROM moz_inputhistory
+              WHERE input BETWEEN :searchString AND :searchString || X'FFFF'
+              GROUP BY place_id
+            ) AS i
+            JOIN moz_places h ON h.id = i.place_id
+            WHERE AUTOCOMPLETE_MATCH(:searchString, h.url,
+                                     IFNULL(btitle, h.title), tags,
+                                     visit_count, h.typed, bookmarked,
+                                     NULL, :matchBehavior, :searchBehavior)
+            ORDER BY rank DESC, h.frecency DESC
+            LIMIT :maxResults",
+            &[
+                (":searchString", &self.query as &dyn rusqlite::ToSql),
+                (":matchBehavior", &self.match_behavior),
+                (":searchBehavior", &self.search_behavior),
+                (":maxResults", &max_results),
+            ],
+            SearchResult::from_adaptive_row,
+        )
+    }
+}
+
+struct Suggestions<'query> {
+    query: &'query str,
+    match_behavior: MatchBehavior,
+    search_behavior: SearchBehavior,
+}
+
+impl<'query> Suggestions<'query> {
+    pub fn with_behavior(
+        query: &'query str,
+        match_behavior: MatchBehavior,
+        search_behavior: SearchBehavior,
+    ) -> Suggestions<'query> {
+        Suggestions {
+            query,
+            match_behavior,
+            search_behavior,
+        }
+    }
+}
+
+impl<'query> Matcher for Suggestions<'query> {
+    fn search(&self, conn: &PlacesDb, max_results: u32) -> Result<Vec<SearchResult>> {
+        query_flat_rows_and_then(
+            conn,
+            "
+            SELECT h.url, h.title,
+                   EXISTS(SELECT 1 FROM moz_bookmarks
+                          WHERE fk = h.id) AS bookmarked,
+                   (SELECT title FROM moz_bookmarks
+                    WHERE fk = h.id AND
+                          title NOT NULL
+                    ORDER BY lastModified DESC
+                    LIMIT 1) AS btitle,
+                   NULL AS tags,
+                   h.visit_count_local + h.visit_count_remote AS visit_count,
+                   h.typed as typed,
+                   h.id as id,
+                   NULL AS open_count, h.frecency, :searchString AS searchString
+            FROM moz_places h
+            WHERE h.frecency > 0
+              AND AUTOCOMPLETE_MATCH(:searchString, h.url,
+                                     IFNULL(btitle, h.title), tags,
+                                     visit_count, h.typed,
+                                     bookmarked, NULL,
+                                     :matchBehavior, :searchBehavior)
+              AND (+h.visit_count_local > 0 OR +h.visit_count_remote > 0)
+            ORDER BY h.frecency DESC, h.id DESC
+            LIMIT :maxResults",
+            &[
+                (":searchString", &self.query as &dyn rusqlite::ToSql),
+                (":matchBehavior", &self.match_behavior),
+                (":searchBehavior", &self.search_behavior),
+                (":maxResults", &max_results),
+            ],
+            SearchResult::from_suggestion_row,
+        )
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::api::places_api::test::new_mem_connection;
+    use crate::observation::VisitObservation;
+    use crate::storage::history::apply_observation;
+    use crate::types::VisitType;
+    use types::Timestamp;
+
+    #[test]
+    fn split() {
+        assert_eq!(
+            split_after_prefix("http://example.com"),
+            ("http://", "example.com")
+        );
+        assert_eq!(split_after_prefix("foo:example"), ("foo:", "example"));
+        assert_eq!(split_after_prefix("foo:"), ("foo:", ""));
+        assert_eq!(split_after_prefix("notaspec"), ("", "notaspec"));
+        assert_eq!(split_after_prefix("http:/"), ("http:", "/"));
+        assert_eq!(split_after_prefix("http://"), ("http://", ""));
+
+        assert_eq!(
+            split_after_host_and_port("http://example.com/"),
+            ("example.com", "/")
+        );
+        assert_eq!(
+            split_after_host_and_port("http://example.com:8888/"),
+            ("example.com:8888", "/")
+        );
+        assert_eq!(
+            split_after_host_and_port("http://user:pass@example.com/"),
+            ("example.com", "/")
+        );
+        assert_eq!(split_after_host_and_port("foo:example"), ("example", ""));
+
+        assert_eq!(
+            split_after_host_and_port("http://foo.com/stuff/@21.3132115"),
+            ("foo.com", "/stuff/@21.3132115")
+        );
+        assert_eq!(
+            split_after_host_and_port("http://foo.com/go?email=foo@example.com"),
+            ("foo.com", "/go?email=foo@example.com")
+        );
+
+        assert_eq!(
+            split_after_host_and_port("http://a:b@foo.com/stuff/@21.3132115"),
+            ("foo.com", "/stuff/@21.3132115")
+        );
+        assert_eq!(
+            split_after_host_and_port("http://a:b@foo.com/123#abcdef@title"),
+            ("foo.com", "/123#abcdef@title")
+        );
+    }
+
+    #[test]
+    fn search() {
+        let conn = new_mem_connection();
+
+        let url = Url::parse("http://example.com/123").unwrap();
+        let visit = VisitObservation::new(url.clone())
+            .with_title("Example page 123".to_string())
+            .with_visit_type(VisitType::Typed)
+            .with_at(Timestamp::now());
+
+        apply_observation(&conn, visit).expect("Should apply visit");
+
+        let by_origin = search_frecent(
+            &conn,
+            SearchParams {
+                search_string: "example.com".into(),
+                limit: 10,
+            },
+        )
+        .expect("Should search by origin");
+        assert!(by_origin
+            .iter()
+            .any(|result| result.search_string == "example.com"
+                && result.title == "example.com/"
+                && result.url.as_str() == "http://example.com/"));
+
+        let by_url_without_path = search_frecent(
+            &conn,
+            SearchParams {
+                search_string: "http://example.com".into(),
+                limit: 10,
+            },
+        )
+        .expect("Should search by URL without path");
+        assert!(by_url_without_path
+            .iter()
+            .any(|result| result.title == "example.com/"
+                && result.url.as_str() == "http://example.com/"));
+
+        let by_url_with_path = search_frecent(
+            &conn,
+            SearchParams {
+                search_string: "http://example.com/1".into(),
+                limit: 10,
+            },
+        )
+        .expect("Should search by URL with path");
+        assert!(by_url_with_path
+            .iter()
+            .any(|result| result.title == "example.com/123"
+                && result.url.as_str() == "http://example.com/123"));
+
+        accept_result(&conn, "ample", &url).expect("Should accept input history match");
+
+        let by_adaptive = search_frecent(
+            &conn,
+            SearchParams {
+                search_string: "ample".into(),
+                limit: 10,
+            },
+        )
+        .expect("Should search by adaptive input history");
+        assert!(by_adaptive
+            .iter()
+            .any(|result| result.search_string == "ample" && result.url == url));
+
+        let with_limit = search_frecent(
+            &conn,
+            SearchParams {
+                search_string: "example".into(),
+                limit: 1,
+            },
+        )
+        .expect("Should search until reaching limit");
+        assert_eq!(
+            with_limit,
+            vec![SearchResult {
+                search_string: "example".into(),
+                url: Url::parse("http://example.com/").unwrap(),
+                title: "example.com/".into(),
+                icon_url: None,
+                frecency: 2000,
+            }]
+        );
+    }
+    #[test]
+    fn search_unicode() {
+        let conn = new_mem_connection();
+
+        let url = Url::parse("http://exämple.com/123").unwrap();
+        let visit = VisitObservation::new(url)
+            .with_title("Example page 123".to_string())
+            .with_visit_type(VisitType::Typed)
+            .with_at(Timestamp::now());
+
+        apply_observation(&conn, visit).expect("Should apply visit");
+
+        let by_url_without_path = search_frecent(
+            &conn,
+            SearchParams {
+                search_string: "http://exämple.com".into(),
+                limit: 10,
+            },
+        )
+        .expect("Should search by URL without path");
+        assert!(by_url_without_path
+            .iter()
+            // Should we consider un-punycoding the title? (firefox desktop doesn't...)
+            .any(|result| result.title == "xn--exmple-cua.com/"
+                && result.url.as_str() == "http://xn--exmple-cua.com/"));
+
+        let by_url_with_path = search_frecent(
+            &conn,
+            SearchParams {
+                search_string: "http://exämple.com/1".into(),
+                limit: 10,
+            },
+        )
+        .expect("Should search by URL with path");
+        assert!(
+            by_url_with_path
+                .iter()
+                .any(|result| result.title == "xn--exmple-cua.com/123"
+                    && result.url.as_str() == "http://xn--exmple-cua.com/123"),
+            "{:?}",
+            by_url_with_path
+        );
+
+        // The "ball of yarn" emoji is not currently accepted as valid
+        // in URLs, but we should just return an empty result set.
+        let ball_of_yarn_about_blank = "about:blank🧶";
+        let empty = match_url(&conn, ball_of_yarn_about_blank).unwrap();
+        assert!(empty.is_none());
+        // Just run this to make sure the unwrap doesn't panic us
+        search_frecent(
+            &conn,
+            SearchParams {
+                search_string: ball_of_yarn_about_blank.into(),
+                limit: 10,
+            },
+        )
+        .unwrap();
+    }
+    // This panics in tests but not for "real" consumers. In an effort to ensure
+    // we are panicing where we think we are, note the 'expected' string.
+    // (Not really clear this test offers much value, but seems worth having...)
+    #[test]
+    #[cfg_attr(
+        debug_assertions,
+        should_panic(expected = "Failed to perform a search:")
+    )]
+    fn search_invalid_url() {
+        let conn = new_mem_connection();
+
+        conn.execute(
+            "INSERT INTO moz_places (guid, url, url_hash, frecency)
+             VALUES ('fake_guid___', 'not-a-url', hash('not-a-url'), 10)",
+            [],
+        )
+        .expect("should insert");
+        crate::storage::delete_pending_temp_tables(&conn).expect("should work");
+
+        let _ = search_frecent(
+            &conn,
+            SearchParams {
+                search_string: "not-a-url".into(),
+                limit: 10,
+            },
+        );
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/places/api/mod.rs.html b/book/rust-docs/src/places/api/mod.rs.html new file mode 100644 index 0000000000..6935ec821d --- /dev/null +++ b/book/rust-docs/src/places/api/mod.rs.html @@ -0,0 +1,33 @@ +mod.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+pub mod history;
+pub mod matcher;
+pub mod places_api;
+use crate::db::PlacesDb;
+use crate::error::Result;
+use crate::observation::VisitObservation;
+use crate::storage;
+
+pub fn apply_observation(conn: &mut PlacesDb, visit_obs: VisitObservation) -> Result<()> {
+    storage::history::apply_observation(conn, visit_obs)?;
+    Ok(())
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/places/api/places_api.rs.html b/book/rust-docs/src/places/api/places_api.rs.html new file mode 100644 index 0000000000..55b1dad69c --- /dev/null +++ b/book/rust-docs/src/places/api/places_api.rs.html @@ -0,0 +1,1213 @@ +places_api.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
+540
+541
+542
+543
+544
+545
+546
+547
+548
+549
+550
+551
+552
+553
+554
+555
+556
+557
+558
+559
+560
+561
+562
+563
+564
+565
+566
+567
+568
+569
+570
+571
+572
+573
+574
+575
+576
+577
+578
+579
+580
+581
+582
+583
+584
+585
+586
+587
+588
+589
+590
+591
+592
+593
+594
+595
+596
+597
+598
+599
+600
+601
+602
+603
+604
+605
+606
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::bookmark_sync::BookmarksSyncEngine;
+use crate::db::db::{PlacesDb, SharedPlacesDb};
+use crate::error::*;
+use crate::history_sync::HistorySyncEngine;
+use crate::storage::{
+    self, bookmarks::bookmark_sync, delete_meta, get_meta, history::history_sync, put_meta,
+};
+use crate::util::normalize_path;
+use error_support::handle_error;
+use interrupt_support::register_interrupt;
+use lazy_static::lazy_static;
+use parking_lot::Mutex;
+use rusqlite::OpenFlags;
+use std::cell::Cell;
+use std::collections::HashMap;
+use std::path::{Path, PathBuf};
+use std::sync::{
+    atomic::{AtomicUsize, Ordering},
+    Arc, Weak,
+};
+use sync15::client::{sync_multiple, MemoryCachedState, Sync15StorageClientInit, SyncResult};
+use sync15::engine::{EngineSyncAssociation, SyncEngine, SyncEngineId};
+use sync15::{telemetry, KeyBundle};
+
+// Not clear if this should be here, but this is the "global sync state"
+// which is persisted to disk and reused for all engines.
+// Note that this is only ever round-tripped, and never changed by, or impacted
+// by a store or collection, so it's safe to storage globally rather than
+// per collection.
+pub const GLOBAL_STATE_META_KEY: &str = "global_sync_state_v2";
+
+// Our "sync manager" will use whatever is stashed here.
+lazy_static::lazy_static! {
+    // Mutex: just taken long enough to update the contents - needed to wrap
+    //        the Weak as it isn't `Sync`
+    // [Arc/Weak]: Stores the places api used to create the connection for
+    //             BookmarksSyncEngine/HistorySyncEngine
+    static ref PLACES_API_FOR_SYNC_MANAGER: Mutex<Weak<PlacesApi>> = Mutex::new(Weak::new());
+}
+
+// Called by the sync manager to get a sync engine via the PlacesApi previously
+// registered with the sync manager.
+pub fn get_registered_sync_engine(engine_id: &SyncEngineId) -> Option<Box<dyn SyncEngine>> {
+    match PLACES_API_FOR_SYNC_MANAGER.lock().upgrade() {
+        None => {
+            log::warn!("places: get_registered_sync_engine: no PlacesApi registered");
+            None
+        }
+        Some(places_api) => match create_sync_engine(&places_api, engine_id) {
+            Ok(engine) => Some(engine),
+            Err(e) => {
+                // Report this to Sentry, except if it's an open database error.  That indicates
+                // that there is a registered sync engine, but the connection is busy so we can't
+                // open it.  This is a known issue that we don't need more reports for (see
+                // https://github.com/mozilla/application-services/issues/5237 for discussion).
+                if !matches!(e, Error::OpenDatabaseError(_)) {
+                    error_support::report_error!(
+                        "places-no-registered-sync-engine",
+                        "places: get_registered_sync_engine: {}",
+                        e
+                    );
+                }
+                None
+            }
+        },
+    }
+}
+
+fn create_sync_engine(
+    places_api: &PlacesApi,
+    engine_id: &SyncEngineId,
+) -> Result<Box<dyn SyncEngine>> {
+    let conn = places_api.get_sync_connection()?;
+    match engine_id {
+        SyncEngineId::Bookmarks => Ok(Box::new(BookmarksSyncEngine::new(conn)?)),
+        SyncEngineId::History => Ok(Box::new(HistorySyncEngine::new(conn)?)),
+        _ => unreachable!("can't provide unknown engine: {}", engine_id),
+    }
+}
+
+#[repr(u8)]
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+pub enum ConnectionType {
+    ReadOnly = 1,
+    ReadWrite = 2,
+    Sync = 3,
+}
+
+impl ConnectionType {
+    pub fn from_primitive(p: u8) -> Option<Self> {
+        match p {
+            1 => Some(ConnectionType::ReadOnly),
+            2 => Some(ConnectionType::ReadWrite),
+            3 => Some(ConnectionType::Sync),
+            _ => None,
+        }
+    }
+}
+
+impl ConnectionType {
+    pub fn rusqlite_flags(self) -> OpenFlags {
+        let common_flags = OpenFlags::SQLITE_OPEN_NO_MUTEX | OpenFlags::SQLITE_OPEN_URI;
+        match self {
+            ConnectionType::ReadOnly => common_flags | OpenFlags::SQLITE_OPEN_READ_ONLY,
+            ConnectionType::ReadWrite => {
+                common_flags | OpenFlags::SQLITE_OPEN_CREATE | OpenFlags::SQLITE_OPEN_READ_WRITE
+            }
+            ConnectionType::Sync => common_flags | OpenFlags::SQLITE_OPEN_READ_WRITE,
+        }
+    }
+}
+
+// We only allow a single PlacesApi per filename.
+lazy_static! {
+    static ref APIS: Mutex<HashMap<PathBuf, Weak<PlacesApi>>> = Mutex::new(HashMap::new());
+}
+
+static ID_COUNTER: AtomicUsize = AtomicUsize::new(0);
+
+pub struct SyncState {
+    pub mem_cached_state: Cell<MemoryCachedState>,
+    pub disk_cached_state: Cell<Option<String>>,
+}
+
+/// For uniffi we need to expose our `Arc` returning constructor as a global function :(
+/// https://github.com/mozilla/uniffi-rs/pull/1063 would fix this, but got some pushback
+/// meaning we are forced into this unfortunate workaround.
+#[handle_error(crate::Error)]
+pub fn places_api_new(db_name: impl AsRef<Path>) -> ApiResult<Arc<PlacesApi>> {
+    PlacesApi::new(db_name)
+}
+
+/// The entry-point to the places API. This object gives access to database
+/// connections and other helpers. It enforces that only 1 write connection
+/// can exist to the database at once.
+pub struct PlacesApi {
+    db_name: PathBuf,
+    write_connection: Mutex<Option<PlacesDb>>,
+    sync_state: Mutex<Option<SyncState>>,
+    coop_tx_lock: Arc<Mutex<()>>,
+    // Used for get_sync_connection()
+    // - The inner mutux synchronizes sync operation (for example one of the [SyncEngine] methods).
+    //   This avoids issues like #867
+    // - The weak facilitates connection sharing.  When `get_sync_connection()` returns an Arc, we
+    //   keep a weak reference to it.  If the Arc is still alive when `get_sync_connection()` is
+    //   called again, we reuse it.
+    // - The outer mutex synchronizes the `get_sync_connection()` operation.  If multiple threads
+    //   ran that at the same time there would be issues.
+    sync_connection: Mutex<Weak<SharedPlacesDb>>,
+    id: usize,
+}
+
+impl PlacesApi {
+    /// Create a new, or fetch an already open, PlacesApi backed by a file on disk.
+    pub fn new(db_name: impl AsRef<Path>) -> Result<Arc<Self>> {
+        let db_name = normalize_path(db_name)?;
+        Self::new_or_existing(db_name)
+    }
+
+    /// Create a new, or fetch an already open, memory-based PlacesApi. You must
+    /// provide a name, but you are still able to have a single writer and many
+    ///  reader connections to the same memory DB open.
+    pub fn new_memory(db_name: &str) -> Result<Arc<Self>> {
+        let name = PathBuf::from(format!("file:{}?mode=memory&cache=shared", db_name));
+        Self::new_or_existing(name)
+    }
+    fn new_or_existing_into(
+        target: &mut HashMap<PathBuf, Weak<PlacesApi>>,
+        db_name: PathBuf,
+    ) -> Result<Arc<Self>> {
+        let id = ID_COUNTER.fetch_add(1, Ordering::SeqCst);
+        match target.get(&db_name).and_then(Weak::upgrade) {
+            Some(existing) => Ok(existing),
+            None => {
+                // We always create a new read-write connection for an initial open so
+                // we can create the schema and/or do version upgrades.
+                let coop_tx_lock = Arc::new(Mutex::new(()));
+                let connection = PlacesDb::open(
+                    &db_name,
+                    ConnectionType::ReadWrite,
+                    id,
+                    coop_tx_lock.clone(),
+                )?;
+                let new = PlacesApi {
+                    db_name: db_name.clone(),
+                    write_connection: Mutex::new(Some(connection)),
+                    sync_state: Mutex::new(None),
+                    sync_connection: Mutex::new(Weak::new()),
+                    id,
+                    coop_tx_lock,
+                };
+                let arc = Arc::new(new);
+                target.insert(db_name, Arc::downgrade(&arc));
+                Ok(arc)
+            }
+        }
+    }
+
+    fn new_or_existing(db_name: PathBuf) -> Result<Arc<Self>> {
+        let mut guard = APIS.lock();
+        Self::new_or_existing_into(&mut guard, db_name)
+    }
+
+    /// Open a connection to the database.
+    pub fn open_connection(&self, conn_type: ConnectionType) -> Result<PlacesDb> {
+        match conn_type {
+            ConnectionType::ReadOnly => {
+                // make a new one - we can have as many of these as we want.
+                PlacesDb::open(
+                    self.db_name.clone(),
+                    ConnectionType::ReadOnly,
+                    self.id,
+                    self.coop_tx_lock.clone(),
+                )
+            }
+            ConnectionType::ReadWrite => {
+                // We only allow one of these.
+                let mut guard = self.write_connection.lock();
+                match guard.take() {
+                    None => Err(Error::ConnectionAlreadyOpen),
+                    Some(db) => Ok(db),
+                }
+            }
+            ConnectionType::Sync => {
+                panic!("Use `get_sync_connection` to open a sync connection");
+            }
+        }
+    }
+
+    // Get a database connection to sync with
+    //
+    // This function provides a couple features to facilitate sharing the connection between
+    // different sync engines:
+    //   - Each connection is wrapped in a `Mutex<>` to synchronize access.
+    //   - The mutex is then wrapped in an Arc<>.  If the last Arc<> returned is still alive, then
+    //     get_sync_connection() will reuse it.
+    pub fn get_sync_connection(&self) -> Result<Arc<SharedPlacesDb>> {
+        // First step: lock the outer mutex
+        let mut conn = self.sync_connection.lock();
+        match conn.upgrade() {
+            // If our Weak is still alive, then re-use that
+            Some(db) => Ok(db),
+            // If not, create a new connection
+            None => {
+                let db = Arc::new(SharedPlacesDb::new(PlacesDb::open(
+                    self.db_name.clone(),
+                    ConnectionType::Sync,
+                    self.id,
+                    self.coop_tx_lock.clone(),
+                )?));
+                register_interrupt(Arc::<SharedPlacesDb>::downgrade(&db));
+                // Store a weakref for next time
+                *conn = Arc::downgrade(&db);
+                Ok(db)
+            }
+        }
+    }
+
+    /// Close a connection to the database. If the connection is the write
+    /// connection, you can re-fetch it using open_connection.
+    pub fn close_connection(&self, connection: PlacesDb) -> Result<()> {
+        if connection.api_id() != self.id {
+            return Err(Error::WrongApiForClose);
+        }
+        if connection.conn_type() == ConnectionType::ReadWrite {
+            // We only allow one of these.
+            let mut guard = self.write_connection.lock();
+            assert!((*guard).is_none());
+            *guard = Some(connection);
+        }
+        Ok(())
+    }
+
+    fn get_disk_persisted_state(&self, conn: &PlacesDb) -> Result<Option<String>> {
+        get_meta::<String>(conn, GLOBAL_STATE_META_KEY)
+    }
+
+    fn set_disk_persisted_state(&self, conn: &PlacesDb, state: &Option<String>) -> Result<()> {
+        match state {
+            Some(ref s) => put_meta(conn, GLOBAL_STATE_META_KEY, s),
+            None => delete_meta(conn, GLOBAL_STATE_META_KEY),
+        }
+    }
+
+    // This allows the embedding app to say "make this instance available to
+    // the sync manager". The implementation is more like "offer to sync mgr"
+    // (thereby avoiding us needing to link with the sync manager) but
+    // `register_with_sync_manager()` is logically what's happening so that's
+    // the name it gets.
+    pub fn register_with_sync_manager(self: Arc<Self>) {
+        *PLACES_API_FOR_SYNC_MANAGER.lock() = Arc::downgrade(&self);
+    }
+
+    // NOTE: These should be deprecated as soon as possible - that will be once
+    // all consumers have been updated to use the .sync() method below, and/or
+    // we have implemented the sync manager and migrated consumers to that.
+    pub fn sync_history(
+        &self,
+        client_init: &Sync15StorageClientInit,
+        key_bundle: &KeyBundle,
+    ) -> Result<telemetry::SyncTelemetryPing> {
+        self.do_sync_one(
+            "history",
+            move |conn, mem_cached_state, disk_cached_state| {
+                let engine = HistorySyncEngine::new(conn)?;
+                Ok(sync_multiple(
+                    &[&engine],
+                    disk_cached_state,
+                    mem_cached_state,
+                    client_init,
+                    key_bundle,
+                    &interrupt_support::ShutdownInterruptee,
+                    None,
+                ))
+            },
+        )
+    }
+
+    pub fn sync_bookmarks(
+        &self,
+        client_init: &Sync15StorageClientInit,
+        key_bundle: &KeyBundle,
+    ) -> Result<telemetry::SyncTelemetryPing> {
+        self.do_sync_one(
+            "bookmarks",
+            move |conn, mem_cached_state, disk_cached_state| {
+                let engine = BookmarksSyncEngine::new(conn)?;
+                Ok(sync_multiple(
+                    &[&engine],
+                    disk_cached_state,
+                    mem_cached_state,
+                    client_init,
+                    key_bundle,
+                    &interrupt_support::ShutdownInterruptee,
+                    None,
+                ))
+            },
+        )
+    }
+
+    pub fn do_sync_one<F>(
+        &self,
+        name: &'static str,
+        syncer: F,
+    ) -> Result<telemetry::SyncTelemetryPing>
+    where
+        F: FnOnce(
+            Arc<SharedPlacesDb>,
+            &mut MemoryCachedState,
+            &mut Option<String>,
+        ) -> Result<SyncResult>,
+    {
+        let mut guard = self.sync_state.lock();
+        let conn = self.get_sync_connection()?;
+        if guard.is_none() {
+            *guard = Some(SyncState {
+                mem_cached_state: Cell::default(),
+                disk_cached_state: Cell::new(self.get_disk_persisted_state(&conn.lock())?),
+            });
+        }
+
+        let sync_state = guard.as_ref().unwrap();
+
+        let mut mem_cached_state = sync_state.mem_cached_state.take();
+        let mut disk_cached_state = sync_state.disk_cached_state.take();
+        let mut result = syncer(conn.clone(), &mut mem_cached_state, &mut disk_cached_state)?;
+        // even on failure we set the persisted state - sync itself takes care
+        // to ensure this has been None'd out if necessary.
+        self.set_disk_persisted_state(&conn.lock(), &disk_cached_state)?;
+        sync_state.mem_cached_state.replace(mem_cached_state);
+        sync_state.disk_cached_state.replace(disk_cached_state);
+
+        // for b/w compat reasons, we do some dances with the result.
+        if let Err(e) = result.result {
+            return Err(e.into());
+        }
+        match result.engine_results.remove(name) {
+            None | Some(Ok(())) => Ok(result.telemetry),
+            Some(Err(e)) => Err(e.into()),
+        }
+    }
+
+    // This is the new sync API until the sync manager lands. It's currently
+    // not wired up via the FFI - it's possible we'll do declined engines too
+    // before we do.
+    // Note we've made a policy decision about the return value - even though
+    // it is Result<SyncResult>, we will only return an Err() if there's a
+    // fatal error that prevents us starting a sync, such as failure to open
+    // the DB. Any errors that happen *after* sync must not escape - ie, once
+    // we have a SyncResult, we must return it.
+    pub fn sync(
+        &self,
+        client_init: &Sync15StorageClientInit,
+        key_bundle: &KeyBundle,
+    ) -> Result<SyncResult> {
+        let mut guard = self.sync_state.lock();
+        let conn = self.get_sync_connection()?;
+        if guard.is_none() {
+            *guard = Some(SyncState {
+                mem_cached_state: Cell::default(),
+                disk_cached_state: Cell::new(self.get_disk_persisted_state(&conn.lock())?),
+            });
+        }
+
+        let sync_state = guard.as_ref().unwrap();
+
+        let bm_engine = BookmarksSyncEngine::new(conn.clone())?;
+        let history_engine = HistorySyncEngine::new(conn.clone())?;
+        let mut mem_cached_state = sync_state.mem_cached_state.take();
+        let mut disk_cached_state = sync_state.disk_cached_state.take();
+
+        // NOTE: After here we must never return Err()!
+        let result = sync_multiple(
+            &[&history_engine, &bm_engine],
+            &mut disk_cached_state,
+            &mut mem_cached_state,
+            client_init,
+            key_bundle,
+            &interrupt_support::ShutdownInterruptee,
+            None,
+        );
+        // even on failure we set the persisted state - sync itself takes care
+        // to ensure this has been None'd out if necessary.
+        if let Err(e) = self.set_disk_persisted_state(&conn.lock(), &disk_cached_state) {
+            error_support::report_error!(
+                "places-sync-persist-failure",
+                "Failed to persist the sync state: {:?}",
+                e
+            );
+        }
+        sync_state.mem_cached_state.replace(mem_cached_state);
+        sync_state.disk_cached_state.replace(disk_cached_state);
+
+        Ok(result)
+    }
+
+    pub fn wipe_bookmarks(&self) -> Result<()> {
+        // Take the lock to prevent syncing while we're doing this.
+        let _guard = self.sync_state.lock();
+        let conn = self.get_sync_connection()?;
+
+        storage::bookmarks::delete_everything(&conn.lock())?;
+        Ok(())
+    }
+
+    pub fn reset_bookmarks(&self) -> Result<()> {
+        // Take the lock to prevent syncing while we're doing this.
+        let _guard = self.sync_state.lock();
+        let conn = self.get_sync_connection()?;
+
+        bookmark_sync::reset(&conn.lock(), &EngineSyncAssociation::Disconnected)?;
+        Ok(())
+    }
+
+    #[handle_error(crate::Error)]
+    pub fn reset_history(&self) -> ApiResult<()> {
+        // Take the lock to prevent syncing while we're doing this.
+        let _guard = self.sync_state.lock();
+        let conn = self.get_sync_connection()?;
+
+        history_sync::reset(&conn.lock(), &EngineSyncAssociation::Disconnected)?;
+        Ok(())
+    }
+}
+
+#[cfg(test)]
+pub mod test {
+    use super::*;
+    use std::sync::atomic::{AtomicUsize, Ordering};
+
+    // A helper for our tests to get their own memory Api.
+    static ATOMIC_COUNTER: AtomicUsize = AtomicUsize::new(0);
+
+    pub fn new_mem_api() -> Arc<PlacesApi> {
+        // A bit hacky, but because this is a test-only function that almost all tests use,
+        // it's a convenient place to initialize logging for tests.
+        let _ = env_logger::try_init();
+
+        let counter = ATOMIC_COUNTER.fetch_add(1, Ordering::Relaxed);
+        PlacesApi::new_memory(&format!("test-api-{}", counter)).expect("should get an API")
+    }
+
+    pub fn new_mem_connection() -> PlacesDb {
+        new_mem_api()
+            .open_connection(ConnectionType::ReadWrite)
+            .expect("should get a connection")
+    }
+
+    pub struct MemConnections {
+        pub read: PlacesDb,
+        pub write: PlacesDb,
+        pub api: Arc<PlacesApi>,
+    }
+
+    pub fn new_mem_connections() -> MemConnections {
+        let api = new_mem_api();
+        let read = api
+            .open_connection(ConnectionType::ReadOnly)
+            .expect("should get a read connection");
+        let write = api
+            .open_connection(ConnectionType::ReadWrite)
+            .expect("should get a write connection");
+        MemConnections { read, write, api }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::test::*;
+    use super::*;
+    use sql_support::ConnExt;
+
+    #[test]
+    fn test_multi_writers_fails() {
+        let api = new_mem_api();
+        let writer1 = api
+            .open_connection(ConnectionType::ReadWrite)
+            .expect("should get writer");
+        api.open_connection(ConnectionType::ReadWrite)
+            .expect_err("should fail to get second writer");
+        // But we should be able to re-get it after closing it.
+        api.close_connection(writer1)
+            .expect("should be able to close");
+        api.open_connection(ConnectionType::ReadWrite)
+            .expect("should get a writer after closing the other");
+    }
+
+    #[test]
+    fn test_shared_memory() {
+        let api = new_mem_api();
+        let writer = api
+            .open_connection(ConnectionType::ReadWrite)
+            .expect("should get writer");
+        writer
+            .execute_batch(
+                "CREATE TABLE test_table (test_value INTEGER);
+                              INSERT INTO test_table VALUES (999)",
+            )
+            .expect("should insert");
+        let reader = api
+            .open_connection(ConnectionType::ReadOnly)
+            .expect("should get reader");
+        let val = reader
+            .query_one::<i64>("SELECT test_value FROM test_table")
+            .expect("should get value");
+        assert_eq!(val, 999);
+    }
+
+    #[test]
+    fn test_reader_before_writer() {
+        let api = new_mem_api();
+        let reader = api
+            .open_connection(ConnectionType::ReadOnly)
+            .expect("should get reader");
+        let writer = api
+            .open_connection(ConnectionType::ReadWrite)
+            .expect("should get writer");
+        writer
+            .execute_batch(
+                "CREATE TABLE test_table (test_value INTEGER);
+                              INSERT INTO test_table VALUES (999)",
+            )
+            .expect("should insert");
+        let val = reader
+            .query_one::<i64>("SELECT test_value FROM test_table")
+            .expect("should get value");
+        assert_eq!(val, 999);
+    }
+
+    #[test]
+    fn test_wrong_writer_close() {
+        let api = new_mem_api();
+        // Grab this so `api` doesn't think it still has a writer.
+        let _writer = api
+            .open_connection(ConnectionType::ReadWrite)
+            .expect("should get writer");
+
+        let fake_api = new_mem_api();
+        let fake_writer = fake_api
+            .open_connection(ConnectionType::ReadWrite)
+            .expect("should get writer 2");
+
+        assert!(matches!(
+            api.close_connection(fake_writer).unwrap_err(),
+            Error::WrongApiForClose
+        ));
+    }
+
+    #[test]
+    fn test_valid_writer_close() {
+        let api = new_mem_api();
+        let writer = api
+            .open_connection(ConnectionType::ReadWrite)
+            .expect("should get writer");
+
+        api.close_connection(writer)
+            .expect("Should allow closing own connection");
+
+        // Make sure we can open it again.
+        assert!(api.open_connection(ConnectionType::ReadWrite).is_ok());
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/places/bookmark_sync/engine.rs.html b/book/rust-docs/src/places/bookmark_sync/engine.rs.html new file mode 100644 index 0000000000..d9f27b47b2 --- /dev/null +++ b/book/rust-docs/src/places/bookmark_sync/engine.rs.html @@ -0,0 +1,8591 @@ +engine.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
+540
+541
+542
+543
+544
+545
+546
+547
+548
+549
+550
+551
+552
+553
+554
+555
+556
+557
+558
+559
+560
+561
+562
+563
+564
+565
+566
+567
+568
+569
+570
+571
+572
+573
+574
+575
+576
+577
+578
+579
+580
+581
+582
+583
+584
+585
+586
+587
+588
+589
+590
+591
+592
+593
+594
+595
+596
+597
+598
+599
+600
+601
+602
+603
+604
+605
+606
+607
+608
+609
+610
+611
+612
+613
+614
+615
+616
+617
+618
+619
+620
+621
+622
+623
+624
+625
+626
+627
+628
+629
+630
+631
+632
+633
+634
+635
+636
+637
+638
+639
+640
+641
+642
+643
+644
+645
+646
+647
+648
+649
+650
+651
+652
+653
+654
+655
+656
+657
+658
+659
+660
+661
+662
+663
+664
+665
+666
+667
+668
+669
+670
+671
+672
+673
+674
+675
+676
+677
+678
+679
+680
+681
+682
+683
+684
+685
+686
+687
+688
+689
+690
+691
+692
+693
+694
+695
+696
+697
+698
+699
+700
+701
+702
+703
+704
+705
+706
+707
+708
+709
+710
+711
+712
+713
+714
+715
+716
+717
+718
+719
+720
+721
+722
+723
+724
+725
+726
+727
+728
+729
+730
+731
+732
+733
+734
+735
+736
+737
+738
+739
+740
+741
+742
+743
+744
+745
+746
+747
+748
+749
+750
+751
+752
+753
+754
+755
+756
+757
+758
+759
+760
+761
+762
+763
+764
+765
+766
+767
+768
+769
+770
+771
+772
+773
+774
+775
+776
+777
+778
+779
+780
+781
+782
+783
+784
+785
+786
+787
+788
+789
+790
+791
+792
+793
+794
+795
+796
+797
+798
+799
+800
+801
+802
+803
+804
+805
+806
+807
+808
+809
+810
+811
+812
+813
+814
+815
+816
+817
+818
+819
+820
+821
+822
+823
+824
+825
+826
+827
+828
+829
+830
+831
+832
+833
+834
+835
+836
+837
+838
+839
+840
+841
+842
+843
+844
+845
+846
+847
+848
+849
+850
+851
+852
+853
+854
+855
+856
+857
+858
+859
+860
+861
+862
+863
+864
+865
+866
+867
+868
+869
+870
+871
+872
+873
+874
+875
+876
+877
+878
+879
+880
+881
+882
+883
+884
+885
+886
+887
+888
+889
+890
+891
+892
+893
+894
+895
+896
+897
+898
+899
+900
+901
+902
+903
+904
+905
+906
+907
+908
+909
+910
+911
+912
+913
+914
+915
+916
+917
+918
+919
+920
+921
+922
+923
+924
+925
+926
+927
+928
+929
+930
+931
+932
+933
+934
+935
+936
+937
+938
+939
+940
+941
+942
+943
+944
+945
+946
+947
+948
+949
+950
+951
+952
+953
+954
+955
+956
+957
+958
+959
+960
+961
+962
+963
+964
+965
+966
+967
+968
+969
+970
+971
+972
+973
+974
+975
+976
+977
+978
+979
+980
+981
+982
+983
+984
+985
+986
+987
+988
+989
+990
+991
+992
+993
+994
+995
+996
+997
+998
+999
+1000
+1001
+1002
+1003
+1004
+1005
+1006
+1007
+1008
+1009
+1010
+1011
+1012
+1013
+1014
+1015
+1016
+1017
+1018
+1019
+1020
+1021
+1022
+1023
+1024
+1025
+1026
+1027
+1028
+1029
+1030
+1031
+1032
+1033
+1034
+1035
+1036
+1037
+1038
+1039
+1040
+1041
+1042
+1043
+1044
+1045
+1046
+1047
+1048
+1049
+1050
+1051
+1052
+1053
+1054
+1055
+1056
+1057
+1058
+1059
+1060
+1061
+1062
+1063
+1064
+1065
+1066
+1067
+1068
+1069
+1070
+1071
+1072
+1073
+1074
+1075
+1076
+1077
+1078
+1079
+1080
+1081
+1082
+1083
+1084
+1085
+1086
+1087
+1088
+1089
+1090
+1091
+1092
+1093
+1094
+1095
+1096
+1097
+1098
+1099
+1100
+1101
+1102
+1103
+1104
+1105
+1106
+1107
+1108
+1109
+1110
+1111
+1112
+1113
+1114
+1115
+1116
+1117
+1118
+1119
+1120
+1121
+1122
+1123
+1124
+1125
+1126
+1127
+1128
+1129
+1130
+1131
+1132
+1133
+1134
+1135
+1136
+1137
+1138
+1139
+1140
+1141
+1142
+1143
+1144
+1145
+1146
+1147
+1148
+1149
+1150
+1151
+1152
+1153
+1154
+1155
+1156
+1157
+1158
+1159
+1160
+1161
+1162
+1163
+1164
+1165
+1166
+1167
+1168
+1169
+1170
+1171
+1172
+1173
+1174
+1175
+1176
+1177
+1178
+1179
+1180
+1181
+1182
+1183
+1184
+1185
+1186
+1187
+1188
+1189
+1190
+1191
+1192
+1193
+1194
+1195
+1196
+1197
+1198
+1199
+1200
+1201
+1202
+1203
+1204
+1205
+1206
+1207
+1208
+1209
+1210
+1211
+1212
+1213
+1214
+1215
+1216
+1217
+1218
+1219
+1220
+1221
+1222
+1223
+1224
+1225
+1226
+1227
+1228
+1229
+1230
+1231
+1232
+1233
+1234
+1235
+1236
+1237
+1238
+1239
+1240
+1241
+1242
+1243
+1244
+1245
+1246
+1247
+1248
+1249
+1250
+1251
+1252
+1253
+1254
+1255
+1256
+1257
+1258
+1259
+1260
+1261
+1262
+1263
+1264
+1265
+1266
+1267
+1268
+1269
+1270
+1271
+1272
+1273
+1274
+1275
+1276
+1277
+1278
+1279
+1280
+1281
+1282
+1283
+1284
+1285
+1286
+1287
+1288
+1289
+1290
+1291
+1292
+1293
+1294
+1295
+1296
+1297
+1298
+1299
+1300
+1301
+1302
+1303
+1304
+1305
+1306
+1307
+1308
+1309
+1310
+1311
+1312
+1313
+1314
+1315
+1316
+1317
+1318
+1319
+1320
+1321
+1322
+1323
+1324
+1325
+1326
+1327
+1328
+1329
+1330
+1331
+1332
+1333
+1334
+1335
+1336
+1337
+1338
+1339
+1340
+1341
+1342
+1343
+1344
+1345
+1346
+1347
+1348
+1349
+1350
+1351
+1352
+1353
+1354
+1355
+1356
+1357
+1358
+1359
+1360
+1361
+1362
+1363
+1364
+1365
+1366
+1367
+1368
+1369
+1370
+1371
+1372
+1373
+1374
+1375
+1376
+1377
+1378
+1379
+1380
+1381
+1382
+1383
+1384
+1385
+1386
+1387
+1388
+1389
+1390
+1391
+1392
+1393
+1394
+1395
+1396
+1397
+1398
+1399
+1400
+1401
+1402
+1403
+1404
+1405
+1406
+1407
+1408
+1409
+1410
+1411
+1412
+1413
+1414
+1415
+1416
+1417
+1418
+1419
+1420
+1421
+1422
+1423
+1424
+1425
+1426
+1427
+1428
+1429
+1430
+1431
+1432
+1433
+1434
+1435
+1436
+1437
+1438
+1439
+1440
+1441
+1442
+1443
+1444
+1445
+1446
+1447
+1448
+1449
+1450
+1451
+1452
+1453
+1454
+1455
+1456
+1457
+1458
+1459
+1460
+1461
+1462
+1463
+1464
+1465
+1466
+1467
+1468
+1469
+1470
+1471
+1472
+1473
+1474
+1475
+1476
+1477
+1478
+1479
+1480
+1481
+1482
+1483
+1484
+1485
+1486
+1487
+1488
+1489
+1490
+1491
+1492
+1493
+1494
+1495
+1496
+1497
+1498
+1499
+1500
+1501
+1502
+1503
+1504
+1505
+1506
+1507
+1508
+1509
+1510
+1511
+1512
+1513
+1514
+1515
+1516
+1517
+1518
+1519
+1520
+1521
+1522
+1523
+1524
+1525
+1526
+1527
+1528
+1529
+1530
+1531
+1532
+1533
+1534
+1535
+1536
+1537
+1538
+1539
+1540
+1541
+1542
+1543
+1544
+1545
+1546
+1547
+1548
+1549
+1550
+1551
+1552
+1553
+1554
+1555
+1556
+1557
+1558
+1559
+1560
+1561
+1562
+1563
+1564
+1565
+1566
+1567
+1568
+1569
+1570
+1571
+1572
+1573
+1574
+1575
+1576
+1577
+1578
+1579
+1580
+1581
+1582
+1583
+1584
+1585
+1586
+1587
+1588
+1589
+1590
+1591
+1592
+1593
+1594
+1595
+1596
+1597
+1598
+1599
+1600
+1601
+1602
+1603
+1604
+1605
+1606
+1607
+1608
+1609
+1610
+1611
+1612
+1613
+1614
+1615
+1616
+1617
+1618
+1619
+1620
+1621
+1622
+1623
+1624
+1625
+1626
+1627
+1628
+1629
+1630
+1631
+1632
+1633
+1634
+1635
+1636
+1637
+1638
+1639
+1640
+1641
+1642
+1643
+1644
+1645
+1646
+1647
+1648
+1649
+1650
+1651
+1652
+1653
+1654
+1655
+1656
+1657
+1658
+1659
+1660
+1661
+1662
+1663
+1664
+1665
+1666
+1667
+1668
+1669
+1670
+1671
+1672
+1673
+1674
+1675
+1676
+1677
+1678
+1679
+1680
+1681
+1682
+1683
+1684
+1685
+1686
+1687
+1688
+1689
+1690
+1691
+1692
+1693
+1694
+1695
+1696
+1697
+1698
+1699
+1700
+1701
+1702
+1703
+1704
+1705
+1706
+1707
+1708
+1709
+1710
+1711
+1712
+1713
+1714
+1715
+1716
+1717
+1718
+1719
+1720
+1721
+1722
+1723
+1724
+1725
+1726
+1727
+1728
+1729
+1730
+1731
+1732
+1733
+1734
+1735
+1736
+1737
+1738
+1739
+1740
+1741
+1742
+1743
+1744
+1745
+1746
+1747
+1748
+1749
+1750
+1751
+1752
+1753
+1754
+1755
+1756
+1757
+1758
+1759
+1760
+1761
+1762
+1763
+1764
+1765
+1766
+1767
+1768
+1769
+1770
+1771
+1772
+1773
+1774
+1775
+1776
+1777
+1778
+1779
+1780
+1781
+1782
+1783
+1784
+1785
+1786
+1787
+1788
+1789
+1790
+1791
+1792
+1793
+1794
+1795
+1796
+1797
+1798
+1799
+1800
+1801
+1802
+1803
+1804
+1805
+1806
+1807
+1808
+1809
+1810
+1811
+1812
+1813
+1814
+1815
+1816
+1817
+1818
+1819
+1820
+1821
+1822
+1823
+1824
+1825
+1826
+1827
+1828
+1829
+1830
+1831
+1832
+1833
+1834
+1835
+1836
+1837
+1838
+1839
+1840
+1841
+1842
+1843
+1844
+1845
+1846
+1847
+1848
+1849
+1850
+1851
+1852
+1853
+1854
+1855
+1856
+1857
+1858
+1859
+1860
+1861
+1862
+1863
+1864
+1865
+1866
+1867
+1868
+1869
+1870
+1871
+1872
+1873
+1874
+1875
+1876
+1877
+1878
+1879
+1880
+1881
+1882
+1883
+1884
+1885
+1886
+1887
+1888
+1889
+1890
+1891
+1892
+1893
+1894
+1895
+1896
+1897
+1898
+1899
+1900
+1901
+1902
+1903
+1904
+1905
+1906
+1907
+1908
+1909
+1910
+1911
+1912
+1913
+1914
+1915
+1916
+1917
+1918
+1919
+1920
+1921
+1922
+1923
+1924
+1925
+1926
+1927
+1928
+1929
+1930
+1931
+1932
+1933
+1934
+1935
+1936
+1937
+1938
+1939
+1940
+1941
+1942
+1943
+1944
+1945
+1946
+1947
+1948
+1949
+1950
+1951
+1952
+1953
+1954
+1955
+1956
+1957
+1958
+1959
+1960
+1961
+1962
+1963
+1964
+1965
+1966
+1967
+1968
+1969
+1970
+1971
+1972
+1973
+1974
+1975
+1976
+1977
+1978
+1979
+1980
+1981
+1982
+1983
+1984
+1985
+1986
+1987
+1988
+1989
+1990
+1991
+1992
+1993
+1994
+1995
+1996
+1997
+1998
+1999
+2000
+2001
+2002
+2003
+2004
+2005
+2006
+2007
+2008
+2009
+2010
+2011
+2012
+2013
+2014
+2015
+2016
+2017
+2018
+2019
+2020
+2021
+2022
+2023
+2024
+2025
+2026
+2027
+2028
+2029
+2030
+2031
+2032
+2033
+2034
+2035
+2036
+2037
+2038
+2039
+2040
+2041
+2042
+2043
+2044
+2045
+2046
+2047
+2048
+2049
+2050
+2051
+2052
+2053
+2054
+2055
+2056
+2057
+2058
+2059
+2060
+2061
+2062
+2063
+2064
+2065
+2066
+2067
+2068
+2069
+2070
+2071
+2072
+2073
+2074
+2075
+2076
+2077
+2078
+2079
+2080
+2081
+2082
+2083
+2084
+2085
+2086
+2087
+2088
+2089
+2090
+2091
+2092
+2093
+2094
+2095
+2096
+2097
+2098
+2099
+2100
+2101
+2102
+2103
+2104
+2105
+2106
+2107
+2108
+2109
+2110
+2111
+2112
+2113
+2114
+2115
+2116
+2117
+2118
+2119
+2120
+2121
+2122
+2123
+2124
+2125
+2126
+2127
+2128
+2129
+2130
+2131
+2132
+2133
+2134
+2135
+2136
+2137
+2138
+2139
+2140
+2141
+2142
+2143
+2144
+2145
+2146
+2147
+2148
+2149
+2150
+2151
+2152
+2153
+2154
+2155
+2156
+2157
+2158
+2159
+2160
+2161
+2162
+2163
+2164
+2165
+2166
+2167
+2168
+2169
+2170
+2171
+2172
+2173
+2174
+2175
+2176
+2177
+2178
+2179
+2180
+2181
+2182
+2183
+2184
+2185
+2186
+2187
+2188
+2189
+2190
+2191
+2192
+2193
+2194
+2195
+2196
+2197
+2198
+2199
+2200
+2201
+2202
+2203
+2204
+2205
+2206
+2207
+2208
+2209
+2210
+2211
+2212
+2213
+2214
+2215
+2216
+2217
+2218
+2219
+2220
+2221
+2222
+2223
+2224
+2225
+2226
+2227
+2228
+2229
+2230
+2231
+2232
+2233
+2234
+2235
+2236
+2237
+2238
+2239
+2240
+2241
+2242
+2243
+2244
+2245
+2246
+2247
+2248
+2249
+2250
+2251
+2252
+2253
+2254
+2255
+2256
+2257
+2258
+2259
+2260
+2261
+2262
+2263
+2264
+2265
+2266
+2267
+2268
+2269
+2270
+2271
+2272
+2273
+2274
+2275
+2276
+2277
+2278
+2279
+2280
+2281
+2282
+2283
+2284
+2285
+2286
+2287
+2288
+2289
+2290
+2291
+2292
+2293
+2294
+2295
+2296
+2297
+2298
+2299
+2300
+2301
+2302
+2303
+2304
+2305
+2306
+2307
+2308
+2309
+2310
+2311
+2312
+2313
+2314
+2315
+2316
+2317
+2318
+2319
+2320
+2321
+2322
+2323
+2324
+2325
+2326
+2327
+2328
+2329
+2330
+2331
+2332
+2333
+2334
+2335
+2336
+2337
+2338
+2339
+2340
+2341
+2342
+2343
+2344
+2345
+2346
+2347
+2348
+2349
+2350
+2351
+2352
+2353
+2354
+2355
+2356
+2357
+2358
+2359
+2360
+2361
+2362
+2363
+2364
+2365
+2366
+2367
+2368
+2369
+2370
+2371
+2372
+2373
+2374
+2375
+2376
+2377
+2378
+2379
+2380
+2381
+2382
+2383
+2384
+2385
+2386
+2387
+2388
+2389
+2390
+2391
+2392
+2393
+2394
+2395
+2396
+2397
+2398
+2399
+2400
+2401
+2402
+2403
+2404
+2405
+2406
+2407
+2408
+2409
+2410
+2411
+2412
+2413
+2414
+2415
+2416
+2417
+2418
+2419
+2420
+2421
+2422
+2423
+2424
+2425
+2426
+2427
+2428
+2429
+2430
+2431
+2432
+2433
+2434
+2435
+2436
+2437
+2438
+2439
+2440
+2441
+2442
+2443
+2444
+2445
+2446
+2447
+2448
+2449
+2450
+2451
+2452
+2453
+2454
+2455
+2456
+2457
+2458
+2459
+2460
+2461
+2462
+2463
+2464
+2465
+2466
+2467
+2468
+2469
+2470
+2471
+2472
+2473
+2474
+2475
+2476
+2477
+2478
+2479
+2480
+2481
+2482
+2483
+2484
+2485
+2486
+2487
+2488
+2489
+2490
+2491
+2492
+2493
+2494
+2495
+2496
+2497
+2498
+2499
+2500
+2501
+2502
+2503
+2504
+2505
+2506
+2507
+2508
+2509
+2510
+2511
+2512
+2513
+2514
+2515
+2516
+2517
+2518
+2519
+2520
+2521
+2522
+2523
+2524
+2525
+2526
+2527
+2528
+2529
+2530
+2531
+2532
+2533
+2534
+2535
+2536
+2537
+2538
+2539
+2540
+2541
+2542
+2543
+2544
+2545
+2546
+2547
+2548
+2549
+2550
+2551
+2552
+2553
+2554
+2555
+2556
+2557
+2558
+2559
+2560
+2561
+2562
+2563
+2564
+2565
+2566
+2567
+2568
+2569
+2570
+2571
+2572
+2573
+2574
+2575
+2576
+2577
+2578
+2579
+2580
+2581
+2582
+2583
+2584
+2585
+2586
+2587
+2588
+2589
+2590
+2591
+2592
+2593
+2594
+2595
+2596
+2597
+2598
+2599
+2600
+2601
+2602
+2603
+2604
+2605
+2606
+2607
+2608
+2609
+2610
+2611
+2612
+2613
+2614
+2615
+2616
+2617
+2618
+2619
+2620
+2621
+2622
+2623
+2624
+2625
+2626
+2627
+2628
+2629
+2630
+2631
+2632
+2633
+2634
+2635
+2636
+2637
+2638
+2639
+2640
+2641
+2642
+2643
+2644
+2645
+2646
+2647
+2648
+2649
+2650
+2651
+2652
+2653
+2654
+2655
+2656
+2657
+2658
+2659
+2660
+2661
+2662
+2663
+2664
+2665
+2666
+2667
+2668
+2669
+2670
+2671
+2672
+2673
+2674
+2675
+2676
+2677
+2678
+2679
+2680
+2681
+2682
+2683
+2684
+2685
+2686
+2687
+2688
+2689
+2690
+2691
+2692
+2693
+2694
+2695
+2696
+2697
+2698
+2699
+2700
+2701
+2702
+2703
+2704
+2705
+2706
+2707
+2708
+2709
+2710
+2711
+2712
+2713
+2714
+2715
+2716
+2717
+2718
+2719
+2720
+2721
+2722
+2723
+2724
+2725
+2726
+2727
+2728
+2729
+2730
+2731
+2732
+2733
+2734
+2735
+2736
+2737
+2738
+2739
+2740
+2741
+2742
+2743
+2744
+2745
+2746
+2747
+2748
+2749
+2750
+2751
+2752
+2753
+2754
+2755
+2756
+2757
+2758
+2759
+2760
+2761
+2762
+2763
+2764
+2765
+2766
+2767
+2768
+2769
+2770
+2771
+2772
+2773
+2774
+2775
+2776
+2777
+2778
+2779
+2780
+2781
+2782
+2783
+2784
+2785
+2786
+2787
+2788
+2789
+2790
+2791
+2792
+2793
+2794
+2795
+2796
+2797
+2798
+2799
+2800
+2801
+2802
+2803
+2804
+2805
+2806
+2807
+2808
+2809
+2810
+2811
+2812
+2813
+2814
+2815
+2816
+2817
+2818
+2819
+2820
+2821
+2822
+2823
+2824
+2825
+2826
+2827
+2828
+2829
+2830
+2831
+2832
+2833
+2834
+2835
+2836
+2837
+2838
+2839
+2840
+2841
+2842
+2843
+2844
+2845
+2846
+2847
+2848
+2849
+2850
+2851
+2852
+2853
+2854
+2855
+2856
+2857
+2858
+2859
+2860
+2861
+2862
+2863
+2864
+2865
+2866
+2867
+2868
+2869
+2870
+2871
+2872
+2873
+2874
+2875
+2876
+2877
+2878
+2879
+2880
+2881
+2882
+2883
+2884
+2885
+2886
+2887
+2888
+2889
+2890
+2891
+2892
+2893
+2894
+2895
+2896
+2897
+2898
+2899
+2900
+2901
+2902
+2903
+2904
+2905
+2906
+2907
+2908
+2909
+2910
+2911
+2912
+2913
+2914
+2915
+2916
+2917
+2918
+2919
+2920
+2921
+2922
+2923
+2924
+2925
+2926
+2927
+2928
+2929
+2930
+2931
+2932
+2933
+2934
+2935
+2936
+2937
+2938
+2939
+2940
+2941
+2942
+2943
+2944
+2945
+2946
+2947
+2948
+2949
+2950
+2951
+2952
+2953
+2954
+2955
+2956
+2957
+2958
+2959
+2960
+2961
+2962
+2963
+2964
+2965
+2966
+2967
+2968
+2969
+2970
+2971
+2972
+2973
+2974
+2975
+2976
+2977
+2978
+2979
+2980
+2981
+2982
+2983
+2984
+2985
+2986
+2987
+2988
+2989
+2990
+2991
+2992
+2993
+2994
+2995
+2996
+2997
+2998
+2999
+3000
+3001
+3002
+3003
+3004
+3005
+3006
+3007
+3008
+3009
+3010
+3011
+3012
+3013
+3014
+3015
+3016
+3017
+3018
+3019
+3020
+3021
+3022
+3023
+3024
+3025
+3026
+3027
+3028
+3029
+3030
+3031
+3032
+3033
+3034
+3035
+3036
+3037
+3038
+3039
+3040
+3041
+3042
+3043
+3044
+3045
+3046
+3047
+3048
+3049
+3050
+3051
+3052
+3053
+3054
+3055
+3056
+3057
+3058
+3059
+3060
+3061
+3062
+3063
+3064
+3065
+3066
+3067
+3068
+3069
+3070
+3071
+3072
+3073
+3074
+3075
+3076
+3077
+3078
+3079
+3080
+3081
+3082
+3083
+3084
+3085
+3086
+3087
+3088
+3089
+3090
+3091
+3092
+3093
+3094
+3095
+3096
+3097
+3098
+3099
+3100
+3101
+3102
+3103
+3104
+3105
+3106
+3107
+3108
+3109
+3110
+3111
+3112
+3113
+3114
+3115
+3116
+3117
+3118
+3119
+3120
+3121
+3122
+3123
+3124
+3125
+3126
+3127
+3128
+3129
+3130
+3131
+3132
+3133
+3134
+3135
+3136
+3137
+3138
+3139
+3140
+3141
+3142
+3143
+3144
+3145
+3146
+3147
+3148
+3149
+3150
+3151
+3152
+3153
+3154
+3155
+3156
+3157
+3158
+3159
+3160
+3161
+3162
+3163
+3164
+3165
+3166
+3167
+3168
+3169
+3170
+3171
+3172
+3173
+3174
+3175
+3176
+3177
+3178
+3179
+3180
+3181
+3182
+3183
+3184
+3185
+3186
+3187
+3188
+3189
+3190
+3191
+3192
+3193
+3194
+3195
+3196
+3197
+3198
+3199
+3200
+3201
+3202
+3203
+3204
+3205
+3206
+3207
+3208
+3209
+3210
+3211
+3212
+3213
+3214
+3215
+3216
+3217
+3218
+3219
+3220
+3221
+3222
+3223
+3224
+3225
+3226
+3227
+3228
+3229
+3230
+3231
+3232
+3233
+3234
+3235
+3236
+3237
+3238
+3239
+3240
+3241
+3242
+3243
+3244
+3245
+3246
+3247
+3248
+3249
+3250
+3251
+3252
+3253
+3254
+3255
+3256
+3257
+3258
+3259
+3260
+3261
+3262
+3263
+3264
+3265
+3266
+3267
+3268
+3269
+3270
+3271
+3272
+3273
+3274
+3275
+3276
+3277
+3278
+3279
+3280
+3281
+3282
+3283
+3284
+3285
+3286
+3287
+3288
+3289
+3290
+3291
+3292
+3293
+3294
+3295
+3296
+3297
+3298
+3299
+3300
+3301
+3302
+3303
+3304
+3305
+3306
+3307
+3308
+3309
+3310
+3311
+3312
+3313
+3314
+3315
+3316
+3317
+3318
+3319
+3320
+3321
+3322
+3323
+3324
+3325
+3326
+3327
+3328
+3329
+3330
+3331
+3332
+3333
+3334
+3335
+3336
+3337
+3338
+3339
+3340
+3341
+3342
+3343
+3344
+3345
+3346
+3347
+3348
+3349
+3350
+3351
+3352
+3353
+3354
+3355
+3356
+3357
+3358
+3359
+3360
+3361
+3362
+3363
+3364
+3365
+3366
+3367
+3368
+3369
+3370
+3371
+3372
+3373
+3374
+3375
+3376
+3377
+3378
+3379
+3380
+3381
+3382
+3383
+3384
+3385
+3386
+3387
+3388
+3389
+3390
+3391
+3392
+3393
+3394
+3395
+3396
+3397
+3398
+3399
+3400
+3401
+3402
+3403
+3404
+3405
+3406
+3407
+3408
+3409
+3410
+3411
+3412
+3413
+3414
+3415
+3416
+3417
+3418
+3419
+3420
+3421
+3422
+3423
+3424
+3425
+3426
+3427
+3428
+3429
+3430
+3431
+3432
+3433
+3434
+3435
+3436
+3437
+3438
+3439
+3440
+3441
+3442
+3443
+3444
+3445
+3446
+3447
+3448
+3449
+3450
+3451
+3452
+3453
+3454
+3455
+3456
+3457
+3458
+3459
+3460
+3461
+3462
+3463
+3464
+3465
+3466
+3467
+3468
+3469
+3470
+3471
+3472
+3473
+3474
+3475
+3476
+3477
+3478
+3479
+3480
+3481
+3482
+3483
+3484
+3485
+3486
+3487
+3488
+3489
+3490
+3491
+3492
+3493
+3494
+3495
+3496
+3497
+3498
+3499
+3500
+3501
+3502
+3503
+3504
+3505
+3506
+3507
+3508
+3509
+3510
+3511
+3512
+3513
+3514
+3515
+3516
+3517
+3518
+3519
+3520
+3521
+3522
+3523
+3524
+3525
+3526
+3527
+3528
+3529
+3530
+3531
+3532
+3533
+3534
+3535
+3536
+3537
+3538
+3539
+3540
+3541
+3542
+3543
+3544
+3545
+3546
+3547
+3548
+3549
+3550
+3551
+3552
+3553
+3554
+3555
+3556
+3557
+3558
+3559
+3560
+3561
+3562
+3563
+3564
+3565
+3566
+3567
+3568
+3569
+3570
+3571
+3572
+3573
+3574
+3575
+3576
+3577
+3578
+3579
+3580
+3581
+3582
+3583
+3584
+3585
+3586
+3587
+3588
+3589
+3590
+3591
+3592
+3593
+3594
+3595
+3596
+3597
+3598
+3599
+3600
+3601
+3602
+3603
+3604
+3605
+3606
+3607
+3608
+3609
+3610
+3611
+3612
+3613
+3614
+3615
+3616
+3617
+3618
+3619
+3620
+3621
+3622
+3623
+3624
+3625
+3626
+3627
+3628
+3629
+3630
+3631
+3632
+3633
+3634
+3635
+3636
+3637
+3638
+3639
+3640
+3641
+3642
+3643
+3644
+3645
+3646
+3647
+3648
+3649
+3650
+3651
+3652
+3653
+3654
+3655
+3656
+3657
+3658
+3659
+3660
+3661
+3662
+3663
+3664
+3665
+3666
+3667
+3668
+3669
+3670
+3671
+3672
+3673
+3674
+3675
+3676
+3677
+3678
+3679
+3680
+3681
+3682
+3683
+3684
+3685
+3686
+3687
+3688
+3689
+3690
+3691
+3692
+3693
+3694
+3695
+3696
+3697
+3698
+3699
+3700
+3701
+3702
+3703
+3704
+3705
+3706
+3707
+3708
+3709
+3710
+3711
+3712
+3713
+3714
+3715
+3716
+3717
+3718
+3719
+3720
+3721
+3722
+3723
+3724
+3725
+3726
+3727
+3728
+3729
+3730
+3731
+3732
+3733
+3734
+3735
+3736
+3737
+3738
+3739
+3740
+3741
+3742
+3743
+3744
+3745
+3746
+3747
+3748
+3749
+3750
+3751
+3752
+3753
+3754
+3755
+3756
+3757
+3758
+3759
+3760
+3761
+3762
+3763
+3764
+3765
+3766
+3767
+3768
+3769
+3770
+3771
+3772
+3773
+3774
+3775
+3776
+3777
+3778
+3779
+3780
+3781
+3782
+3783
+3784
+3785
+3786
+3787
+3788
+3789
+3790
+3791
+3792
+3793
+3794
+3795
+3796
+3797
+3798
+3799
+3800
+3801
+3802
+3803
+3804
+3805
+3806
+3807
+3808
+3809
+3810
+3811
+3812
+3813
+3814
+3815
+3816
+3817
+3818
+3819
+3820
+3821
+3822
+3823
+3824
+3825
+3826
+3827
+3828
+3829
+3830
+3831
+3832
+3833
+3834
+3835
+3836
+3837
+3838
+3839
+3840
+3841
+3842
+3843
+3844
+3845
+3846
+3847
+3848
+3849
+3850
+3851
+3852
+3853
+3854
+3855
+3856
+3857
+3858
+3859
+3860
+3861
+3862
+3863
+3864
+3865
+3866
+3867
+3868
+3869
+3870
+3871
+3872
+3873
+3874
+3875
+3876
+3877
+3878
+3879
+3880
+3881
+3882
+3883
+3884
+3885
+3886
+3887
+3888
+3889
+3890
+3891
+3892
+3893
+3894
+3895
+3896
+3897
+3898
+3899
+3900
+3901
+3902
+3903
+3904
+3905
+3906
+3907
+3908
+3909
+3910
+3911
+3912
+3913
+3914
+3915
+3916
+3917
+3918
+3919
+3920
+3921
+3922
+3923
+3924
+3925
+3926
+3927
+3928
+3929
+3930
+3931
+3932
+3933
+3934
+3935
+3936
+3937
+3938
+3939
+3940
+3941
+3942
+3943
+3944
+3945
+3946
+3947
+3948
+3949
+3950
+3951
+3952
+3953
+3954
+3955
+3956
+3957
+3958
+3959
+3960
+3961
+3962
+3963
+3964
+3965
+3966
+3967
+3968
+3969
+3970
+3971
+3972
+3973
+3974
+3975
+3976
+3977
+3978
+3979
+3980
+3981
+3982
+3983
+3984
+3985
+3986
+3987
+3988
+3989
+3990
+3991
+3992
+3993
+3994
+3995
+3996
+3997
+3998
+3999
+4000
+4001
+4002
+4003
+4004
+4005
+4006
+4007
+4008
+4009
+4010
+4011
+4012
+4013
+4014
+4015
+4016
+4017
+4018
+4019
+4020
+4021
+4022
+4023
+4024
+4025
+4026
+4027
+4028
+4029
+4030
+4031
+4032
+4033
+4034
+4035
+4036
+4037
+4038
+4039
+4040
+4041
+4042
+4043
+4044
+4045
+4046
+4047
+4048
+4049
+4050
+4051
+4052
+4053
+4054
+4055
+4056
+4057
+4058
+4059
+4060
+4061
+4062
+4063
+4064
+4065
+4066
+4067
+4068
+4069
+4070
+4071
+4072
+4073
+4074
+4075
+4076
+4077
+4078
+4079
+4080
+4081
+4082
+4083
+4084
+4085
+4086
+4087
+4088
+4089
+4090
+4091
+4092
+4093
+4094
+4095
+4096
+4097
+4098
+4099
+4100
+4101
+4102
+4103
+4104
+4105
+4106
+4107
+4108
+4109
+4110
+4111
+4112
+4113
+4114
+4115
+4116
+4117
+4118
+4119
+4120
+4121
+4122
+4123
+4124
+4125
+4126
+4127
+4128
+4129
+4130
+4131
+4132
+4133
+4134
+4135
+4136
+4137
+4138
+4139
+4140
+4141
+4142
+4143
+4144
+4145
+4146
+4147
+4148
+4149
+4150
+4151
+4152
+4153
+4154
+4155
+4156
+4157
+4158
+4159
+4160
+4161
+4162
+4163
+4164
+4165
+4166
+4167
+4168
+4169
+4170
+4171
+4172
+4173
+4174
+4175
+4176
+4177
+4178
+4179
+4180
+4181
+4182
+4183
+4184
+4185
+4186
+4187
+4188
+4189
+4190
+4191
+4192
+4193
+4194
+4195
+4196
+4197
+4198
+4199
+4200
+4201
+4202
+4203
+4204
+4205
+4206
+4207
+4208
+4209
+4210
+4211
+4212
+4213
+4214
+4215
+4216
+4217
+4218
+4219
+4220
+4221
+4222
+4223
+4224
+4225
+4226
+4227
+4228
+4229
+4230
+4231
+4232
+4233
+4234
+4235
+4236
+4237
+4238
+4239
+4240
+4241
+4242
+4243
+4244
+4245
+4246
+4247
+4248
+4249
+4250
+4251
+4252
+4253
+4254
+4255
+4256
+4257
+4258
+4259
+4260
+4261
+4262
+4263
+4264
+4265
+4266
+4267
+4268
+4269
+4270
+4271
+4272
+4273
+4274
+4275
+4276
+4277
+4278
+4279
+4280
+4281
+4282
+4283
+4284
+4285
+4286
+4287
+4288
+4289
+4290
+4291
+4292
+4293
+4294
+4295
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use super::incoming::IncomingApplicator;
+use super::record::{
+    BookmarkItemRecord, BookmarkRecord, BookmarkRecordId, FolderRecord, QueryRecord,
+    SeparatorRecord,
+};
+use super::{SyncedBookmarkKind, SyncedBookmarkValidity};
+use crate::db::{GlobalChangeCounterTracker, PlacesDb, SharedPlacesDb};
+use crate::error::*;
+use crate::frecency::{calculate_frecency, DEFAULT_FRECENCY_SETTINGS};
+use crate::storage::{
+    bookmarks::{
+        bookmark_sync::{create_synced_bookmark_roots, reset},
+        BookmarkRootGuid,
+    },
+    delete_pending_temp_tables, get_meta, put_meta,
+};
+use crate::types::{BookmarkType, SyncStatus, UnknownFields};
+use dogear::{
+    self, AbortSignal, CompletionOps, Content, Item, MergedRoot, TelemetryEvent, Tree, UploadItem,
+    UploadTombstone,
+};
+use interrupt_support::SqlInterruptScope;
+use rusqlite::Row;
+use sql_support::ConnExt;
+use std::cell::RefCell;
+use std::collections::HashMap;
+use std::fmt;
+use std::sync::Arc;
+use sync15::bso::{IncomingBso, OutgoingBso};
+use sync15::engine::{CollSyncIds, CollectionRequest, EngineSyncAssociation, SyncEngine};
+use sync15::{telemetry, CollectionName, ServerTimestamp};
+use sync_guid::Guid as SyncGuid;
+use types::Timestamp;
+pub const LAST_SYNC_META_KEY: &str = "bookmarks_last_sync_time";
+// Note that all engines in this crate should use a *different* meta key
+// for the global sync ID, because engines are reset individually.
+pub const GLOBAL_SYNCID_META_KEY: &str = "bookmarks_global_sync_id";
+pub const COLLECTION_SYNCID_META_KEY: &str = "bookmarks_sync_id";
+pub const COLLECTION_NAME: &str = "bookmarks";
+
+/// The maximum number of URLs for which to recalculate frecencies at once.
+/// This is a trade-off between write efficiency and transaction time: higher
+/// maximums mean fewer write statements, but longer transactions, possibly
+/// blocking writes from other connections.
+const MAX_FRECENCIES_TO_RECALCULATE_PER_CHUNK: usize = 400;
+
+/// Adapts an interruptee to a Dogear abort signal.
+struct MergeInterruptee<'a>(&'a SqlInterruptScope);
+
+impl<'a> AbortSignal for MergeInterruptee<'a> {
+    #[inline]
+    fn aborted(&self) -> bool {
+        self.0.was_interrupted()
+    }
+}
+
+fn stage_incoming(
+    db: &PlacesDb,
+    scope: &SqlInterruptScope,
+    inbound: Vec<IncomingBso>,
+    incoming_telemetry: &mut telemetry::EngineIncoming,
+) -> Result<()> {
+    let mut tx = db.begin_transaction()?;
+
+    let applicator = IncomingApplicator::new(db);
+
+    for incoming in inbound {
+        applicator.apply_bso(incoming)?;
+        incoming_telemetry.applied(1);
+        if tx.should_commit() {
+            // Trigger frecency updates for all new origins.
+            log::debug!("Updating origins for new synced URLs since last commit");
+            delete_pending_temp_tables(db)?;
+        }
+        tx.maybe_commit()?;
+        scope.err_if_interrupted()?;
+    }
+
+    log::debug!("Updating origins for new synced URLs in last chunk");
+    delete_pending_temp_tables(db)?;
+
+    tx.commit()?;
+    Ok(())
+}
+
+fn db_has_changes(db: &PlacesDb) -> Result<bool> {
+    // In the first subquery, we check incoming items with needsMerge = true
+    // except the tombstones who don't correspond to any local bookmark because
+    // we don't store them yet, hence never "merged" (see bug 1343103).
+    let sql = format!(
+        "SELECT
+            EXISTS (
+                SELECT 1
+                FROM moz_bookmarks_synced v
+                LEFT JOIN moz_bookmarks b ON v.guid = b.guid
+                WHERE v.needsMerge AND
+                (NOT v.isDeleted OR b.guid NOT NULL)
+            ) OR EXISTS (
+                WITH RECURSIVE
+                {}
+                SELECT 1
+                FROM localItems
+                WHERE syncChangeCounter > 0
+            ) OR EXISTS (
+                SELECT 1
+                FROM moz_bookmarks_deleted
+            )
+         AS hasChanges",
+        LocalItemsFragment("localItems")
+    );
+    Ok(db
+        .try_query_row(
+            &sql,
+            [],
+            |row| -> rusqlite::Result<_> { row.get::<_, bool>(0) },
+            false,
+        )?
+        .unwrap_or(false))
+}
+
+/// Builds a temporary table with the merge states of all nodes in the merged
+/// tree, then updates the local tree to match the merged tree.
+///
+/// Conceptually, we examine the merge state of each item, and either leave the
+/// item unchanged, upload the local side, apply the remote side, or apply and
+/// then reupload the remote side with a new structure.
+fn update_local_items_in_places(
+    db: &PlacesDb,
+    scope: &SqlInterruptScope,
+    now: Timestamp,
+    ops: &CompletionOps<'_>,
+) -> Result<()> {
+    // Build a table of new and updated items.
+    log::debug!("Staging apply remote item ops");
+    sql_support::each_sized_chunk(
+        &ops.apply_remote_items,
+        sql_support::default_max_variable_number() / 3,
+        |chunk, _| -> Result<()> {
+            // CTEs in `WITH` clauses aren't indexed, so this query needs a
+            // full table scan on `ops`. But that's okay; a separate temp
+            // table for ops would also need a full scan. Note that we need
+            // both the local _and_ remote GUIDs here, because we haven't
+            // changed the local GUIDs yet.
+            let sql = format!(
+                "WITH ops(mergedGuid, localGuid, remoteGuid, remoteType,
+                          level) AS (
+                     VALUES {ops}
+                 )
+                 INSERT INTO itemsToApply(mergedGuid, localId, remoteId,
+                                          remoteGuid, newLevel, newKind,
+                                          localDateAdded, remoteDateAdded,
+                                          lastModified, oldTitle, newTitle,
+                                          oldPlaceId, newPlaceId,
+                                          newKeyword)
+                 SELECT n.mergedGuid, b.id, v.id,
+                        v.guid, n.level, n.remoteType,
+                        b.dateAdded, v.dateAdded,
+                        MAX(v.dateAdded, {now}), b.title, v.title,
+                        b.fk, v.placeId,
+                        v.keyword
+                 FROM ops n
+                 JOIN moz_bookmarks_synced v ON v.guid = n.remoteGuid
+                 LEFT JOIN moz_bookmarks b ON b.guid = n.localGuid",
+                ops = sql_support::repeat_display(chunk.len(), ",", |index, f| {
+                    let op = &chunk[index];
+                    write!(
+                        f,
+                        "(?, ?, ?, {}, {})",
+                        SyncedBookmarkKind::from(op.remote_node().kind) as u8,
+                        op.level
+                    )
+                }),
+                now = now,
+            );
+
+            // We can't avoid allocating here, since we're binding four
+            // parameters per descendant. Rust's `SliceConcatExt::concat`
+            // is semantically equivalent, but requires a second allocation,
+            // which we _can_ avoid by writing this out.
+            let mut params = Vec::with_capacity(chunk.len() * 3);
+            for op in chunk.iter() {
+                scope.err_if_interrupted()?;
+
+                let merged_guid = op.merged_node.guid.as_str();
+                params.push(Some(merged_guid));
+
+                let local_guid = op
+                    .merged_node
+                    .merge_state
+                    .local_node()
+                    .map(|node| node.guid.as_str());
+                params.push(local_guid);
+
+                let remote_guid = op.remote_node().guid.as_str();
+                params.push(Some(remote_guid));
+            }
+
+            db.execute(&sql, rusqlite::params_from_iter(params))?;
+            Ok(())
+        },
+    )?;
+
+    log::debug!("Staging change GUID ops");
+    sql_support::each_sized_chunk(
+        &ops.change_guids,
+        sql_support::default_max_variable_number() / 2,
+        |chunk, _| -> Result<()> {
+            let sql = format!(
+                "INSERT INTO changeGuidOps(localGuid, mergedGuid,
+                                           syncStatus, level, lastModified)
+                 VALUES {}",
+                sql_support::repeat_display(chunk.len(), ",", |index, f| {
+                    let op = &chunk[index];
+                    // If only the local GUID changed, the item was deduped, so we
+                    // can mark it as syncing. Otherwise, we changed an invalid
+                    // GUID locally or remotely, so we leave its original sync
+                    // status in place until we've uploaded it.
+                    let sync_status = if op.merged_node.remote_guid_changed() {
+                        None
+                    } else {
+                        Some(SyncStatus::Normal as u8)
+                    };
+                    write!(
+                        f,
+                        "(?, ?, {}, {}, {})",
+                        NullableFragment(sync_status),
+                        op.level,
+                        now
+                    )
+                }),
+            );
+
+            let mut params = Vec::with_capacity(chunk.len() * 2);
+            for op in chunk.iter() {
+                scope.err_if_interrupted()?;
+
+                let local_guid = op.local_node().guid.as_str();
+                params.push(local_guid);
+
+                let merged_guid = op.merged_node.guid.as_str();
+                params.push(merged_guid);
+            }
+
+            db.execute(&sql, rusqlite::params_from_iter(params))?;
+            Ok(())
+        },
+    )?;
+
+    log::debug!("Staging apply new local structure ops");
+    sql_support::each_sized_chunk(
+        &ops.apply_new_local_structure,
+        sql_support::default_max_variable_number() / 2,
+        |chunk, _| -> Result<()> {
+            let sql = format!(
+                "INSERT INTO applyNewLocalStructureOps(
+                     mergedGuid, mergedParentGuid, position, level,
+                     lastModified
+                 )
+                 VALUES {}",
+                sql_support::repeat_display(chunk.len(), ",", |index, f| {
+                    let op = &chunk[index];
+                    write!(f, "(?, ?, {}, {}, {})", op.position, op.level, now)
+                }),
+            );
+
+            let mut params = Vec::with_capacity(chunk.len() * 2);
+            for op in chunk.iter() {
+                scope.err_if_interrupted()?;
+
+                let merged_guid = op.merged_node.guid.as_str();
+                params.push(merged_guid);
+
+                let merged_parent_guid = op.merged_parent_node.guid.as_str();
+                params.push(merged_parent_guid);
+            }
+
+            db.execute(&sql, rusqlite::params_from_iter(params))?;
+            Ok(())
+        },
+    )?;
+
+    log::debug!("Removing tombstones for revived items");
+    sql_support::each_chunk_mapped(
+        &ops.delete_local_tombstones,
+        |op| op.guid().as_str(),
+        |chunk, _| -> Result<()> {
+            scope.err_if_interrupted()?;
+            db.execute(
+                &format!(
+                    "DELETE FROM moz_bookmarks_deleted
+                     WHERE guid IN ({})",
+                    sql_support::repeat_sql_vars(chunk.len())
+                ),
+                rusqlite::params_from_iter(chunk),
+            )?;
+            Ok(())
+        },
+    )?;
+
+    log::debug!("Inserting new tombstones for non-syncable and invalid items");
+    sql_support::each_chunk_mapped(
+        &ops.insert_local_tombstones,
+        |op| op.remote_node().guid.as_str().to_owned(),
+        |chunk, _| -> Result<()> {
+            scope.err_if_interrupted()?;
+            db.execute(
+                &format!(
+                    "INSERT INTO moz_bookmarks_deleted(guid, dateRemoved)
+                     VALUES {}",
+                    sql_support::repeat_display(chunk.len(), ",", |_, f| write!(f, "(?, {})", now)),
+                ),
+                rusqlite::params_from_iter(chunk),
+            )?;
+            Ok(())
+        },
+    )?;
+
+    log::debug!("Flag frecencies for removed bookmark URLs as stale");
+    sql_support::each_chunk_mapped(
+        &ops.delete_local_items,
+        |op| op.local_node().guid.as_str().to_owned(),
+        |chunk, _| -> Result<()> {
+            scope.err_if_interrupted()?;
+            db.execute(
+                &format!(
+                    "REPLACE INTO moz_places_stale_frecencies(
+                         place_id, stale_at
+                     )
+                     SELECT b.fk, {now}
+                     FROM moz_bookmarks b
+                     WHERE b.guid IN ({vars})
+                     AND b.fk NOT NULL",
+                    now = now,
+                    vars = sql_support::repeat_sql_vars(chunk.len())
+                ),
+                rusqlite::params_from_iter(chunk),
+            )?;
+            Ok(())
+        },
+    )?;
+
+    log::debug!("Removing deleted items from Places");
+    sql_support::each_chunk_mapped(
+        &ops.delete_local_items,
+        |op| op.local_node().guid.as_str().to_owned(),
+        |chunk, _| -> Result<()> {
+            scope.err_if_interrupted()?;
+            db.execute(
+                &format!(
+                    "DELETE FROM moz_bookmarks
+                     WHERE guid IN ({})",
+                    sql_support::repeat_sql_vars(chunk.len())
+                ),
+                rusqlite::params_from_iter(chunk),
+            )?;
+            Ok(())
+        },
+    )?;
+
+    log::debug!("Changing GUIDs");
+    scope.err_if_interrupted()?;
+    db.execute_batch("DELETE FROM changeGuidOps")?;
+
+    log::debug!("Applying remote items");
+    apply_remote_items(db, scope, now)?;
+
+    // Fires the `applyNewLocalStructure` trigger.
+    log::debug!("Applying new local structure");
+    scope.err_if_interrupted()?;
+    db.execute_batch("DELETE FROM applyNewLocalStructureOps")?;
+
+    log::debug!("Resetting change counters for items that shouldn't be uploaded");
+    sql_support::each_chunk_mapped(
+        &ops.set_local_merged,
+        |op| op.merged_node.guid.as_str(),
+        |chunk, _| -> Result<()> {
+            scope.err_if_interrupted()?;
+            db.execute(
+                &format!(
+                    "UPDATE moz_bookmarks SET
+                         syncChangeCounter = 0
+                     WHERE guid IN ({})",
+                    sql_support::repeat_sql_vars(chunk.len()),
+                ),
+                rusqlite::params_from_iter(chunk),
+            )?;
+            Ok(())
+        },
+    )?;
+
+    log::debug!("Bumping change counters for items that should be uploaded");
+    sql_support::each_chunk_mapped(
+        &ops.set_local_unmerged,
+        |op| op.merged_node.guid.as_str(),
+        |chunk, _| -> Result<()> {
+            scope.err_if_interrupted()?;
+            db.execute(
+                &format!(
+                    "UPDATE moz_bookmarks SET
+                         syncChangeCounter = 1
+                     WHERE guid IN ({})",
+                    sql_support::repeat_sql_vars(chunk.len()),
+                ),
+                rusqlite::params_from_iter(chunk),
+            )?;
+            Ok(())
+        },
+    )?;
+
+    log::debug!("Flagging applied remote items as merged");
+    sql_support::each_chunk_mapped(
+        &ops.set_remote_merged,
+        |op| op.guid().as_str(),
+        |chunk, _| -> Result<()> {
+            scope.err_if_interrupted()?;
+            db.execute(
+                &format!(
+                    "UPDATE moz_bookmarks_synced SET
+                         needsMerge = 0
+                     WHERE guid IN ({})",
+                    sql_support::repeat_sql_vars(chunk.len()),
+                ),
+                rusqlite::params_from_iter(chunk),
+            )?;
+            Ok(())
+        },
+    )?;
+
+    Ok(())
+}
+
+fn apply_remote_items(db: &PlacesDb, scope: &SqlInterruptScope, now: Timestamp) -> Result<()> {
+    // Remove all keywords from old and new URLs, and remove new keywords
+    // from all existing URLs. The `NOT NULL` conditions are important; they
+    // ensure that SQLite uses our partial indexes on `itemsToApply`,
+    // instead of a table scan.
+    log::debug!("Removing old keywords");
+    scope.err_if_interrupted()?;
+    db.execute_batch(
+        "DELETE FROM moz_keywords
+         WHERE place_id IN (SELECT oldPlaceId FROM itemsToApply
+                            WHERE oldPlaceId NOT NULL) OR
+               place_id IN (SELECT newPlaceId FROM itemsToApply
+                            WHERE newPlaceId NOT NULL) OR
+               keyword IN (SELECT newKeyword FROM itemsToApply
+                           WHERE newKeyword NOT NULL)",
+    )?;
+
+    log::debug!("Removing old tags");
+    scope.err_if_interrupted()?;
+    db.execute_batch(
+        "DELETE FROM moz_tags_relation
+         WHERE place_id IN (SELECT oldPlaceId FROM itemsToApply
+                            WHERE oldPlaceId NOT NULL) OR
+               place_id IN (SELECT newPlaceId FROM itemsToApply
+                            WHERE newPlaceId NOT NULL)",
+    )?;
+
+    // Insert and update items, temporarily using the Places root for new
+    // items' parent IDs, and -1 for positions. We'll fix these up later,
+    // when we apply the new local structure. This `INSERT` is a full table
+    // scan on `itemsToApply`. The no-op `WHERE` clause is necessary to
+    // avoid a parsing ambiguity.
+    log::debug!("Upserting new items");
+    scope.err_if_interrupted()?;
+    db.execute_batch(&format!(
+        "INSERT INTO moz_bookmarks(id, guid, parent,
+                                   position, type, fk, title,
+                                   dateAdded,
+                                   lastModified,
+                                   syncStatus, syncChangeCounter)
+         SELECT localId, mergedGuid, (SELECT id FROM moz_bookmarks
+                                      WHERE guid = '{root_guid}'),
+                -1, {type_fragment}, newPlaceId, newTitle,
+                /* Pick the older of the local and remote date added. We'll
+                   weakly reupload any items with an older local date. */
+                MIN(IFNULL(localDateAdded, remoteDateAdded), remoteDateAdded),
+                /* The last modified date should always be newer than the date
+                   added, so we pick the newer of the two here. */
+                MAX(lastModified, remoteDateAdded),
+                {sync_status}, 0
+         FROM itemsToApply
+         WHERE 1
+         ON CONFLICT(id) DO UPDATE SET
+           title = excluded.title,
+           dateAdded = excluded.dateAdded,
+           lastModified = excluded.lastModified,
+           fk = excluded.fk,
+           syncStatus = {sync_status}",
+        root_guid = BookmarkRootGuid::Root.as_guid().as_str(),
+        type_fragment = ItemTypeFragment("newKind"),
+        sync_status = SyncStatus::Normal as u8,
+    ))?;
+
+    log::debug!("Flagging frecencies for recalculation");
+    scope.err_if_interrupted()?;
+    db.execute_batch(&format!(
+        "REPLACE INTO moz_places_stale_frecencies(place_id, stale_at)
+         SELECT oldPlaceId, {now} FROM itemsToApply
+         WHERE newKind = {bookmark_kind} AND (
+                   oldPlaceId IS NULL <> newPlaceId IS NULL OR
+                   oldPlaceId <> newPlaceId
+               )
+         UNION ALL
+         SELECT newPlaceId, {now} FROM itemsToApply
+         WHERE newKind = {bookmark_kind} AND (
+                   newPlaceId IS NULL <> oldPlaceId IS NULL OR
+                   newPlaceId <> oldPlaceId
+               )",
+        now = now,
+        bookmark_kind = SyncedBookmarkKind::Bookmark as u8,
+    ))?;
+
+    log::debug!("Inserting new keywords for new URLs");
+    scope.err_if_interrupted()?;
+    db.execute_batch(
+        "INSERT OR IGNORE INTO moz_keywords(keyword, place_id)
+         SELECT newKeyword, newPlaceId
+         FROM itemsToApply
+         WHERE newKeyword NOT NULL",
+    )?;
+
+    log::debug!("Inserting new tags for new URLs");
+    scope.err_if_interrupted()?;
+    db.execute_batch(
+        "INSERT OR IGNORE INTO moz_tags_relation(tag_id, place_id)
+         SELECT r.tagId, n.newPlaceId
+         FROM itemsToApply n
+         JOIN moz_bookmarks_synced_tag_relation r ON r.itemId = n.remoteId",
+    )?;
+
+    Ok(())
+}
+
+/// Stores a snapshot of all locally changed items in a temporary table for
+/// upload. This is called from within the merge transaction, to ensure that
+/// changes made during the sync don't cause us to upload inconsistent
+/// records.
+///
+/// Conceptually, `itemsToUpload` is a transient "view" of locally changed
+/// items. The local change counter is the persistent record of items that
+/// we need to upload, so, if upload is interrupted or fails, we'll stage
+/// the items again on the next sync.
+fn stage_items_to_upload(
+    db: &PlacesDb,
+    scope: &SqlInterruptScope,
+    upload_items: &[UploadItem<'_>],
+    upload_tombstones: &[UploadTombstone<'_>],
+) -> Result<()> {
+    log::debug!("Cleaning up staged items left from last sync");
+    scope.err_if_interrupted()?;
+    db.execute_batch("DELETE FROM itemsToUpload")?;
+
+    // Stage remotely changed items with older local creation dates. These are
+    // tracked "weakly": if the upload is interrupted or fails, we won't
+    // reupload the record on the next sync.
+    log::debug!("Staging items with older local dates added");
+    scope.err_if_interrupted()?;
+    db.execute_batch(&format!(
+        "INSERT OR IGNORE INTO itemsToUpload(id, guid, syncChangeCounter,
+                                             parentGuid, parentTitle, dateAdded,
+                                             kind, title, placeId, url,
+                                             keyword, position)
+         {}
+         JOIN itemsToApply n ON n.mergedGuid = b.guid
+         WHERE n.localDateAdded < n.remoteDateAdded",
+        UploadItemsFragment("b")
+    ))?;
+
+    log::debug!("Staging remaining locally changed items for upload");
+    sql_support::each_chunk_mapped(
+        upload_items,
+        |op| op.merged_node.guid.as_str(),
+        |chunk, _| -> Result<()> {
+            let sql = format!(
+                "INSERT OR IGNORE INTO itemsToUpload(id, guid, syncChangeCounter,
+                                                  parentGuid, parentTitle,
+                                                  dateAdded, kind, title,
+                                                  placeId, url, keyword,
+                                                  position)
+                 {upload_items_fragment}
+                 WHERE b.guid IN ({vars})",
+                vars = sql_support::repeat_sql_vars(chunk.len()),
+                upload_items_fragment = UploadItemsFragment("b")
+            );
+
+            db.execute(&sql, rusqlite::params_from_iter(chunk))?;
+            Ok(())
+        },
+    )?;
+
+    // Record the child GUIDs of locally changed folders, which we use to
+    // populate the `children` array in the record.
+    log::debug!("Staging structure to upload");
+    scope.err_if_interrupted()?;
+    db.execute_batch(
+        "INSERT INTO structureToUpload(guid, parentId, position)
+         SELECT b.guid, b.parent, b.position
+         FROM moz_bookmarks b
+         JOIN itemsToUpload o ON o.id = b.parent",
+    )?;
+
+    // Stage tags for outgoing bookmarks.
+    log::debug!("Staging tags to upload");
+    scope.err_if_interrupted()?;
+    db.execute_batch(
+        "INSERT INTO tagsToUpload(id, tag)
+         SELECT o.id, t.tag
+         FROM itemsToUpload o
+         JOIN moz_tags_relation r ON r.place_id = o.placeId
+         JOIN moz_tags t ON t.id = r.tag_id",
+    )?;
+
+    // Finally, stage tombstones for deleted items.
+    log::debug!("Staging tombstones to upload");
+    sql_support::each_chunk_mapped(
+        upload_tombstones,
+        |op| op.guid().as_str(),
+        |chunk, _| -> Result<()> {
+            scope.err_if_interrupted()?;
+            db.execute(
+                &format!(
+                    "INSERT OR IGNORE INTO itemsToUpload(
+                     guid, syncChangeCounter, isDeleted
+                 )
+                 VALUES {}",
+                    sql_support::repeat_display(chunk.len(), ",", |_, f| write!(f, "(?, 1, 1)")),
+                ),
+                rusqlite::params_from_iter(chunk),
+            )?;
+            Ok(())
+        },
+    )?;
+
+    Ok(())
+}
+
+/// Inflates Sync records for all staged outgoing items.
+fn fetch_outgoing_records(db: &PlacesDb, scope: &SqlInterruptScope) -> Result<Vec<OutgoingBso>> {
+    let mut changes = Vec::new();
+    let mut child_record_ids_by_local_parent_id: HashMap<i64, Vec<BookmarkRecordId>> =
+        HashMap::new();
+    let mut tags_by_local_id: HashMap<i64, Vec<String>> = HashMap::new();
+
+    let mut stmt = db.prepare(
+        "SELECT parentId, guid FROM structureToUpload
+         ORDER BY parentId, position",
+    )?;
+    let mut results = stmt.query([])?;
+    while let Some(row) = results.next()? {
+        scope.err_if_interrupted()?;
+        let local_parent_id = row.get::<_, i64>("parentId")?;
+        let child_guid = row.get::<_, SyncGuid>("guid")?;
+        let child_record_ids = child_record_ids_by_local_parent_id
+            .entry(local_parent_id)
+            .or_default();
+        child_record_ids.push(child_guid.into());
+    }
+
+    let mut stmt = db.prepare("SELECT id, tag FROM tagsToUpload")?;
+    let mut results = stmt.query([])?;
+    while let Some(row) = results.next()? {
+        scope.err_if_interrupted()?;
+        let local_id = row.get::<_, i64>("id")?;
+        let tag = row.get::<_, String>("tag")?;
+        let tags = tags_by_local_id.entry(local_id).or_default();
+        tags.push(tag);
+    }
+
+    let mut stmt = db.prepare(
+        "SELECT i.id, i.syncChangeCounter, i.guid, i.isDeleted, i.kind, i.keyword,
+                i.url, IFNULL(i.title, '') AS title, i.position, i.parentGuid,
+                IFNULL(i.parentTitle, '') AS parentTitle, i.dateAdded, m.unknownFields
+         FROM itemsToUpload i
+         LEFT JOIN moz_bookmarks_synced m ON i.guid == m.guid
+         ",
+    )?;
+    let mut results = stmt.query([])?;
+    while let Some(row) = results.next()? {
+        scope.err_if_interrupted()?;
+        let guid = row.get::<_, SyncGuid>("guid")?;
+        let is_deleted = row.get::<_, bool>("isDeleted")?;
+        if is_deleted {
+            changes.push(OutgoingBso::new_tombstone(
+                BookmarkRecordId::from(guid).as_guid().clone().into(),
+            ));
+            continue;
+        }
+        let parent_guid = row.get::<_, SyncGuid>("parentGuid")?;
+        let parent_title = row.get::<_, String>("parentTitle")?;
+        let date_added = row.get::<_, i64>("dateAdded")?;
+        let unknown_fields = match row.get::<_, Option<String>>("unknownFields")? {
+            None => UnknownFields::new(),
+            Some(s) => serde_json::from_str(&s)?,
+        };
+        let record: BookmarkItemRecord = match SyncedBookmarkKind::from_u8(row.get("kind")?)? {
+            SyncedBookmarkKind::Bookmark => {
+                let local_id = row.get::<_, i64>("id")?;
+                let title = row.get::<_, String>("title")?;
+                let url = row.get::<_, String>("url")?;
+                BookmarkRecord {
+                    record_id: guid.into(),
+                    parent_record_id: Some(parent_guid.into()),
+                    parent_title: Some(parent_title),
+                    date_added: Some(date_added),
+                    has_dupe: true,
+                    title: Some(title),
+                    url: Some(url),
+                    keyword: row.get::<_, Option<String>>("keyword")?,
+                    tags: tags_by_local_id.remove(&local_id).unwrap_or_default(),
+                    unknown_fields,
+                }
+                .into()
+            }
+            SyncedBookmarkKind::Query => {
+                let title = row.get::<_, String>("title")?;
+                let url = row.get::<_, String>("url")?;
+                QueryRecord {
+                    record_id: guid.into(),
+                    parent_record_id: Some(parent_guid.into()),
+                    parent_title: Some(parent_title),
+                    date_added: Some(date_added),
+                    has_dupe: true,
+                    title: Some(title),
+                    url: Some(url),
+                    tag_folder_name: None,
+                    unknown_fields,
+                }
+                .into()
+            }
+            SyncedBookmarkKind::Folder => {
+                let title = row.get::<_, String>("title")?;
+                let local_id = row.get::<_, i64>("id")?;
+                let children = child_record_ids_by_local_parent_id
+                    .remove(&local_id)
+                    .unwrap_or_default();
+                FolderRecord {
+                    record_id: guid.into(),
+                    parent_record_id: Some(parent_guid.into()),
+                    parent_title: Some(parent_title),
+                    date_added: Some(date_added),
+                    has_dupe: true,
+                    title: Some(title),
+                    children,
+                    unknown_fields,
+                }
+                .into()
+            }
+            SyncedBookmarkKind::Livemark => continue,
+            SyncedBookmarkKind::Separator => {
+                let position = row.get::<_, i64>("position")?;
+                SeparatorRecord {
+                    record_id: guid.into(),
+                    parent_record_id: Some(parent_guid.into()),
+                    parent_title: Some(parent_title),
+                    date_added: Some(date_added),
+                    has_dupe: true,
+                    position: Some(position),
+                    unknown_fields,
+                }
+                .into()
+            }
+        };
+        changes.push(OutgoingBso::from_content_with_id(record)?);
+    }
+
+    Ok(changes)
+}
+
+/// Decrements the change counter, updates the sync status, and cleans up
+/// tombstones for successfully synced items. Sync calls this method at the
+/// end of each bookmark sync.
+fn push_synced_items(
+    db: &PlacesDb,
+    scope: &SqlInterruptScope,
+    uploaded_at: ServerTimestamp,
+    records_synced: Vec<SyncGuid>,
+) -> Result<()> {
+    // Flag all successfully synced records as uploaded. This `UPDATE` fires
+    // the `pushUploadedChanges` trigger, which updates local change
+    // counters and writes the items back to the synced bookmarks table.
+    let mut tx = db.begin_transaction()?;
+
+    let guids = records_synced
+        .into_iter()
+        .map(|id| BookmarkRecordId::from_payload_id(id).into())
+        .collect::<Vec<SyncGuid>>();
+    sql_support::each_chunk(&guids, |chunk, _| -> Result<()> {
+        db.execute(
+            &format!(
+                "UPDATE itemsToUpload SET
+                     uploadedAt = {uploaded_at}
+                     WHERE guid IN ({values})",
+                uploaded_at = uploaded_at.as_millis(),
+                values = sql_support::repeat_sql_values(chunk.len())
+            ),
+            rusqlite::params_from_iter(chunk),
+        )?;
+        tx.maybe_commit()?;
+        scope.err_if_interrupted()?;
+        Ok(())
+    })?;
+
+    // Fast-forward the last sync time, so that we don't download the
+    // records we just uploaded on the next sync.
+    put_meta(db, LAST_SYNC_META_KEY, &uploaded_at.as_millis())?;
+
+    // Clean up.
+    db.execute_batch("DELETE FROM itemsToUpload")?;
+    tx.commit()?;
+
+    Ok(())
+}
+
+pub(crate) fn update_frecencies(db: &PlacesDb, scope: &SqlInterruptScope) -> Result<()> {
+    let mut tx = db.begin_transaction()?;
+
+    let mut frecencies = Vec::with_capacity(MAX_FRECENCIES_TO_RECALCULATE_PER_CHUNK);
+    loop {
+        let sql = format!(
+            "SELECT place_id FROM moz_places_stale_frecencies
+             ORDER BY stale_at DESC
+             LIMIT {}",
+            MAX_FRECENCIES_TO_RECALCULATE_PER_CHUNK
+        );
+        let mut stmt = db.prepare_maybe_cached(&sql, true)?;
+        let mut results = stmt.query([])?;
+        while let Some(row) = results.next()? {
+            let place_id = row.get("place_id")?;
+            // Frecency recalculation runs several statements, so check to
+            // make sure we aren't interrupted before each calculation.
+            scope.err_if_interrupted()?;
+            let frecency =
+                calculate_frecency(db, &DEFAULT_FRECENCY_SETTINGS, place_id, Some(false))?;
+            frecencies.push((place_id, frecency));
+        }
+        if frecencies.is_empty() {
+            break;
+        }
+
+        // Update all frecencies in one fell swoop...
+        db.execute_batch(&format!(
+            "WITH frecencies(id, frecency) AS (
+               VALUES {}
+             )
+             UPDATE moz_places SET
+               frecency = (SELECT frecency FROM frecencies f
+                           WHERE f.id = id)
+             WHERE id IN (SELECT f.id FROM frecencies f)",
+            sql_support::repeat_display(frecencies.len(), ",", |index, f| {
+                let (id, frecency) = frecencies[index];
+                write!(f, "({}, {})", id, frecency)
+            })
+        ))?;
+        tx.maybe_commit()?;
+        scope.err_if_interrupted()?;
+
+        // ...And remove them from the stale table.
+        db.execute_batch(&format!(
+            "DELETE FROM moz_places_stale_frecencies
+             WHERE place_id IN ({})",
+            sql_support::repeat_display(frecencies.len(), ",", |index, f| {
+                let (id, _) = frecencies[index];
+                write!(f, "{}", id)
+            })
+        ))?;
+        tx.maybe_commit()?;
+        scope.err_if_interrupted()?;
+
+        // If the query returned fewer URLs than the maximum, we're done.
+        // Otherwise, we might have more, so clear the ones we just
+        // recalculated and fetch the next chunk.
+        if frecencies.len() < MAX_FRECENCIES_TO_RECALCULATE_PER_CHUNK {
+            break;
+        }
+        frecencies.clear();
+    }
+
+    tx.commit()?;
+
+    Ok(())
+}
+
+// Short-lived struct that's constructed each sync
+pub struct BookmarksSyncEngine {
+    db: Arc<SharedPlacesDb>,
+    // Pub so that it can be used by the PlacesApi methods.  Once all syncing goes through the
+    // `SyncManager` we should be able to make this private.
+    pub(crate) scope: SqlInterruptScope,
+}
+
+impl BookmarksSyncEngine {
+    pub fn new(db: Arc<SharedPlacesDb>) -> Result<Self> {
+        Ok(Self {
+            scope: db.begin_interrupt_scope()?,
+            db,
+        })
+    }
+}
+
+impl SyncEngine for BookmarksSyncEngine {
+    #[inline]
+    fn collection_name(&self) -> CollectionName {
+        COLLECTION_NAME.into()
+    }
+
+    fn stage_incoming(
+        &self,
+        inbound: Vec<IncomingBso>,
+        telem: &mut telemetry::Engine,
+    ) -> anyhow::Result<()> {
+        let conn = self.db.lock();
+        // Stage all incoming items.
+        let mut incoming_telemetry = telemetry::EngineIncoming::new();
+        stage_incoming(&conn, &self.scope, inbound, &mut incoming_telemetry)?;
+        telem.incoming(incoming_telemetry);
+        Ok(())
+    }
+
+    fn apply(
+        &self,
+        timestamp: ServerTimestamp,
+        telem: &mut telemetry::Engine,
+    ) -> anyhow::Result<Vec<OutgoingBso>> {
+        let conn = self.db.lock();
+        // write the timestamp now, so if we are interrupted merging or
+        // creating outgoing changesets we don't need to re-apply the same
+        // records.
+        put_meta(&conn, LAST_SYNC_META_KEY, &timestamp.as_millis())?;
+
+        // Merge.
+        let mut merger = Merger::with_telemetry(&conn, &self.scope, timestamp, telem);
+        merger.merge()?;
+        Ok(fetch_outgoing_records(&conn, &self.scope)?)
+    }
+
+    fn set_uploaded(
+        &self,
+        new_timestamp: ServerTimestamp,
+        ids: Vec<SyncGuid>,
+    ) -> anyhow::Result<()> {
+        let conn = self.db.lock();
+        push_synced_items(&conn, &self.scope, new_timestamp, ids)?;
+        Ok(update_frecencies(&conn, &self.scope)?)
+    }
+
+    fn sync_finished(&self) -> anyhow::Result<()> {
+        let conn = self.db.lock();
+        conn.pragma_update(None, "wal_checkpoint", "PASSIVE")?;
+        Ok(())
+    }
+
+    fn get_collection_request(
+        &self,
+        server_timestamp: ServerTimestamp,
+    ) -> anyhow::Result<Option<CollectionRequest>> {
+        let conn = self.db.lock();
+        let since =
+            ServerTimestamp(get_meta::<i64>(&conn, LAST_SYNC_META_KEY)?.unwrap_or_default());
+        Ok(if since == server_timestamp {
+            None
+        } else {
+            Some(
+                CollectionRequest::new(self.collection_name())
+                    .full()
+                    .newer_than(since),
+            )
+        })
+    }
+
+    fn get_sync_assoc(&self) -> anyhow::Result<EngineSyncAssociation> {
+        let conn = self.db.lock();
+        let global = get_meta(&conn, GLOBAL_SYNCID_META_KEY)?;
+        let coll = get_meta(&conn, COLLECTION_SYNCID_META_KEY)?;
+        Ok(if let (Some(global), Some(coll)) = (global, coll) {
+            EngineSyncAssociation::Connected(CollSyncIds { global, coll })
+        } else {
+            EngineSyncAssociation::Disconnected
+        })
+    }
+
+    fn reset(&self, assoc: &EngineSyncAssociation) -> anyhow::Result<()> {
+        let conn = self.db.lock();
+        reset(&conn, assoc)?;
+        Ok(())
+    }
+
+    /// Erases all local items. Unlike `reset`, this keeps all synced items
+    /// until the next sync, when they will be replaced with tombstones. This
+    /// also preserves the sync ID and last sync time.
+    ///
+    /// Conceptually, the next sync will merge an empty local tree, and a full
+    /// remote tree.
+    fn wipe(&self) -> anyhow::Result<()> {
+        let conn = self.db.lock();
+        let tx = conn.begin_transaction()?;
+        let sql = format!(
+            "INSERT INTO moz_bookmarks_deleted(guid, dateRemoved)
+             SELECT guid, now()
+             FROM moz_bookmarks
+             WHERE guid NOT IN {roots} AND
+                   syncStatus = {sync_status};
+
+             UPDATE moz_bookmarks SET
+               syncChangeCounter = syncChangeCounter + 1
+             WHERE guid IN {roots};
+
+             DELETE FROM moz_bookmarks
+             WHERE guid NOT IN {roots};",
+            roots = RootsFragment(&[
+                BookmarkRootGuid::Root,
+                BookmarkRootGuid::Menu,
+                BookmarkRootGuid::Mobile,
+                BookmarkRootGuid::Toolbar,
+                BookmarkRootGuid::Unfiled
+            ]),
+            sync_status = SyncStatus::Normal as u8
+        );
+        conn.execute_batch(&sql)?;
+        create_synced_bookmark_roots(&conn)?;
+        tx.commit()?;
+        Ok(())
+    }
+}
+
+#[derive(Default)]
+struct Driver {
+    validation: RefCell<telemetry::Validation>,
+}
+
+impl dogear::Driver for Driver {
+    fn generate_new_guid(&self, _invalid_guid: &dogear::Guid) -> dogear::Result<dogear::Guid> {
+        Ok(SyncGuid::random().as_str().into())
+    }
+
+    fn record_telemetry_event(&self, event: TelemetryEvent) {
+        // Record validation telemetry for remote trees.
+        if let TelemetryEvent::FetchRemoteTree(stats) = event {
+            self.validation
+                .borrow_mut()
+                .problem("orphans", stats.problems.orphans)
+                .problem("misparentedRoots", stats.problems.misparented_roots)
+                .problem(
+                    "multipleParents",
+                    stats.problems.multiple_parents_by_children,
+                )
+                .problem("missingParents", stats.problems.missing_parent_guids)
+                .problem("nonFolderParents", stats.problems.non_folder_parent_guids)
+                .problem(
+                    "parentChildDisagreements",
+                    stats.problems.parent_child_disagreements,
+                )
+                .problem("missingChildren", stats.problems.missing_children);
+        }
+    }
+}
+
+// The "merger", which is just a thin wrapper for dogear.
+pub(crate) struct Merger<'a> {
+    db: &'a PlacesDb,
+    scope: &'a SqlInterruptScope,
+    remote_time: ServerTimestamp,
+    local_time: Timestamp,
+    // Used for where the merger is not the one which should be managing the
+    // transaction, e.g. in the case of bookmarks import. The only impact this has
+    // is on the `apply()` function. Always false unless the caller explicitly
+    // turns it on, to avoid accidentally enabling unintentionally.
+    external_transaction: bool,
+    telem: Option<&'a mut telemetry::Engine>,
+    // Allows us to abort applying the result of the merge if the local tree
+    // changed since we fetched it.
+    global_change_tracker: GlobalChangeCounterTracker,
+}
+
+impl<'a> Merger<'a> {
+    #[cfg(test)]
+    pub(crate) fn new(
+        db: &'a PlacesDb,
+        scope: &'a SqlInterruptScope,
+        remote_time: ServerTimestamp,
+    ) -> Self {
+        Self {
+            db,
+            scope,
+            remote_time,
+            local_time: Timestamp::now(),
+            external_transaction: false,
+            telem: None,
+            global_change_tracker: db.global_bookmark_change_tracker(),
+        }
+    }
+
+    pub(crate) fn with_telemetry(
+        db: &'a PlacesDb,
+        scope: &'a SqlInterruptScope,
+        remote_time: ServerTimestamp,
+        telem: &'a mut telemetry::Engine,
+    ) -> Self {
+        Self {
+            db,
+            scope,
+            remote_time,
+            local_time: Timestamp::now(),
+            external_transaction: false,
+            telem: Some(telem),
+            global_change_tracker: db.global_bookmark_change_tracker(),
+        }
+    }
+
+    #[cfg(test)]
+    fn with_localtime(
+        db: &'a PlacesDb,
+        scope: &'a SqlInterruptScope,
+        remote_time: ServerTimestamp,
+        local_time: Timestamp,
+    ) -> Self {
+        Self {
+            db,
+            scope,
+            remote_time,
+            local_time,
+            external_transaction: false,
+            telem: None,
+            global_change_tracker: db.global_bookmark_change_tracker(),
+        }
+    }
+
+    pub(crate) fn merge(&mut self) -> Result<()> {
+        use dogear::Store;
+        if !db_has_changes(self.db)? {
+            return Ok(());
+        }
+        // Merge and stage outgoing items via dogear.
+        let driver = Driver::default();
+        self.prepare()?;
+        let result = self.merge_with_driver(&driver, &MergeInterruptee(self.scope));
+        log::debug!("merge completed: {:?}", result);
+
+        // Record telemetry in all cases, even if the merge fails.
+        if let Some(ref mut telem) = self.telem {
+            telem.validation(driver.validation.into_inner());
+        }
+        result
+    }
+
+    /// Prepares synced bookmarks for merging.
+    fn prepare(&self) -> Result<()> {
+        // Sync and Fennec associate keywords with bookmarks, and don't sync
+        // POST data; Rust Places associates them with URLs, and also doesn't
+        // support POST data; Desktop associates keywords with (URL, POST data)
+        // pairs, and multiple bookmarks may have the same URL.
+        //
+        // When a keyword changes, clients should reupload all bookmarks with
+        // the affected URL (bug 1328737). Just in case, we flag any synced
+        // bookmarks that have different keywords for the same URL, or the same
+        // keyword for different URLs, for reupload.
+        self.scope.err_if_interrupted()?;
+        log::debug!("Flagging bookmarks with mismatched keywords for reupload");
+        let sql = format!(
+            "UPDATE moz_bookmarks_synced SET
+               validity = {reupload}
+             WHERE validity = {valid} AND (
+               placeId IN (
+                 /* Same URL, different keywords. `COUNT` ignores NULLs, so
+                    we need to count them separately. This handles cases where
+                    a keyword was removed from one, but not all bookmarks with
+                    the same URL. */
+                 SELECT placeId FROM moz_bookmarks_synced
+                 GROUP BY placeId
+                 HAVING COUNT(DISTINCT keyword) +
+                        COUNT(DISTINCT CASE WHEN keyword IS NULL
+                                       THEN 1 END) > 1
+               ) OR keyword IN (
+                 /* Different URLs, same keyword. Bookmarks with keywords but
+                    without URLs are already invalid, so we don't need to handle
+                    NULLs here. */
+                 SELECT keyword FROM moz_bookmarks_synced
+                 WHERE keyword NOT NULL
+                 GROUP BY keyword
+                 HAVING COUNT(DISTINCT placeId) > 1
+               )
+             )",
+            reupload = SyncedBookmarkValidity::Reupload as u8,
+            valid = SyncedBookmarkValidity::Valid as u8,
+        );
+        self.db.execute_batch(&sql)?;
+
+        // Like keywords, Sync associates tags with bookmarks, but Places
+        // associates them with URLs. This means multiple bookmarks with the
+        // same URL should have the same tags. In practice, different tags for
+        // bookmarks with the same URL are some of the most common validation
+        // errors we see.
+        //
+        // Unlike keywords, the relationship between URLs and tags in many-many:
+        // multiple URLs can have the same tag, and a URL can have multiple
+        // tags. So, to find mismatches, we need to compare the tags for each
+        // URL with the tags for each item.
+        //
+        // We could fetch both lists of tags, sort them, and then compare them.
+        // But there's a trick here: we're only interested in whether the tags
+        // _match_, not the tags themselves. So we sum the tag IDs!
+        //
+        // This has two advantages: we don't have to sort IDs, since addition is
+        // commutative, and we can compare two integers much more efficiently
+        // than two string lists! If a bookmark has mismatched tags, the sum of
+        // its tag IDs in `tagsByItemId` won't match the sum in `tagsByPlaceId`,
+        // and we'll flag the item for reupload.
+        self.scope.err_if_interrupted()?;
+        log::debug!("Flagging bookmarks with mismatched tags for reupload");
+        let sql = format!(
+            "WITH
+             tagsByPlaceId(placeId, tagIds) AS (
+                 /* For multiple bookmarks with the same URL, each group will
+                    have one tag per bookmark. So, if bookmarks A1, A2, and A3
+                    have the same URL A with tag T, T will be in the group three
+                    times. But we only want to count each tag once per URL, so
+                    we use `SUM(DISTINCT)`. */
+                 SELECT v.placeId, SUM(DISTINCT t.tagId)
+                 FROM moz_bookmarks_synced v
+                 JOIN moz_bookmarks_synced_tag_relation t ON t.itemId = v.id
+                 WHERE v.placeId NOT NULL
+                 GROUP BY v.placeId
+             ),
+             tagsByItemId(itemId, tagIds) AS (
+                 /* But here, we can use a plain `SUM`, since we're grouping by
+                    item ID, and an item can't have duplicate tags thanks to the
+                    primary key on the relation table. */
+                 SELECT t.itemId, SUM(t.tagId)
+                 FROM moz_bookmarks_synced_tag_relation t
+                 GROUP BY t.itemId
+             )
+             UPDATE moz_bookmarks_synced SET
+                 validity = {reupload}
+             WHERE validity = {valid} AND id IN (
+                 SELECT v.id FROM moz_bookmarks_synced v
+                 JOIN tagsByPlaceId u ON v.placeId = u.placeId
+                 /* This left join is important: if A1 has tags and A2 doesn't,
+                    we want to flag A2 for reupload. */
+                 LEFT JOIN tagsByItemId t ON t.itemId = v.id
+                 /* Unlike `<>`, `IS NOT` compares NULLs. */
+                 WHERE t.tagIds IS NOT u.tagIds
+             )",
+            reupload = SyncedBookmarkValidity::Reupload as u8,
+            valid = SyncedBookmarkValidity::Valid as u8,
+        );
+        self.db.execute_batch(&sql)?;
+
+        Ok(())
+    }
+
+    /// Creates a local tree item from a row in the `localItems` CTE.
+    fn local_row_to_item(&self, row: &Row<'_>) -> Result<(Item, Option<Content>)> {
+        let guid = row.get::<_, SyncGuid>("guid")?;
+        let url_href = row.get::<_, Option<String>>("url")?;
+        let kind = match row.get::<_, BookmarkType>("type")? {
+            BookmarkType::Bookmark => match url_href.as_ref() {
+                Some(u) if u.starts_with("place:") => SyncedBookmarkKind::Query,
+                _ => SyncedBookmarkKind::Bookmark,
+            },
+            BookmarkType::Folder => SyncedBookmarkKind::Folder,
+            BookmarkType::Separator => SyncedBookmarkKind::Separator,
+        };
+        let mut item = Item::new(guid.as_str().into(), kind.into());
+        // Note that this doesn't account for local clock skew.
+        let age = self
+            .local_time
+            .duration_since(row.get::<_, Timestamp>("localModified")?)
+            .unwrap_or_default();
+        item.age = age.as_secs() as i64 * 1000 + i64::from(age.subsec_millis());
+        item.needs_merge = row.get::<_, u32>("syncChangeCounter")? > 0;
+
+        let content = if item.guid == dogear::ROOT_GUID {
+            None
+        } else {
+            match row.get::<_, SyncStatus>("syncStatus")? {
+                SyncStatus::Normal => None,
+                _ => match kind {
+                    SyncedBookmarkKind::Bookmark | SyncedBookmarkKind::Query => {
+                        let title = row.get::<_, String>("title")?;
+                        url_href.map(|url_href| Content::Bookmark { title, url_href })
+                    }
+                    SyncedBookmarkKind::Folder | SyncedBookmarkKind::Livemark => {
+                        let title = row.get::<_, String>("title")?;
+                        Some(Content::Folder { title })
+                    }
+                    SyncedBookmarkKind::Separator => Some(Content::Separator),
+                },
+            }
+        };
+
+        Ok((item, content))
+    }
+
+    /// Creates a remote tree item from a row in `moz_bookmarks_synced`.
+    fn remote_row_to_item(&self, row: &Row<'_>) -> Result<(Item, Option<Content>)> {
+        let guid = row.get::<_, SyncGuid>("guid")?;
+        let kind = SyncedBookmarkKind::from_u8(row.get("kind")?)?;
+        let mut item = Item::new(guid.as_str().into(), kind.into());
+        // note that serverModified in this table is an int with ms, which isn't
+        // the format of a ServerTimestamp - so we convert it into a number
+        // of seconds before creating a ServerTimestamp and doing duration_since.
+        let age = self
+            .remote_time
+            .duration_since(ServerTimestamp(row.get::<_, i64>("serverModified")?))
+            .unwrap_or_default();
+        item.age = age.as_secs() as i64 * 1000 + i64::from(age.subsec_millis());
+        item.needs_merge = row.get("needsMerge")?;
+        item.validity = SyncedBookmarkValidity::from_u8(row.get("validity")?)?.into();
+
+        let content = if item.guid == dogear::ROOT_GUID || !item.needs_merge {
+            None
+        } else {
+            match kind {
+                SyncedBookmarkKind::Bookmark | SyncedBookmarkKind::Query => {
+                    let title = row.get::<_, String>("title")?;
+                    let url_href = row.get::<_, Option<String>>("url")?;
+                    url_href.map(|url_href| Content::Bookmark { title, url_href })
+                }
+                SyncedBookmarkKind::Folder | SyncedBookmarkKind::Livemark => {
+                    let title = row.get::<_, String>("title")?;
+                    Some(Content::Folder { title })
+                }
+                SyncedBookmarkKind::Separator => Some(Content::Separator),
+            }
+        };
+
+        Ok((item, content))
+    }
+}
+
+impl<'a> dogear::Store for Merger<'a> {
+    type Ok = ();
+    type Error = Error;
+
+    /// Builds a fully rooted, consistent tree from all local items and
+    /// tombstones.
+    fn fetch_local_tree(&self) -> Result<Tree> {
+        let mut stmt = self.db.prepare(&format!(
+            "SELECT guid, type, syncChangeCounter, syncStatus,
+                    lastModified AS localModified,
+                    NULL AS url
+             FROM moz_bookmarks
+             WHERE guid = '{root_guid}'",
+            root_guid = BookmarkRootGuid::Root.as_guid().as_str(),
+        ))?;
+        let mut results = stmt.query([])?;
+        let mut builder = match results.next()? {
+            Some(row) => {
+                let (item, _) = self.local_row_to_item(row)?;
+                Tree::with_root(item)
+            }
+            None => return Err(Error::Corruption(Corruption::InvalidLocalRoots)),
+        };
+
+        // Add items and contents to the builder, keeping track of their
+        // structure in a separate map. We can't call `p.by_structure(...)`
+        // after adding the item, because this query might return rows for
+        // children before their parents. This approach also lets us scan
+        // `moz_bookmarks` once, using the index on `(b.parent, b.position)`
+        // to avoid a temp B-tree for the `ORDER BY`.
+        let mut child_guids_by_parent_guid: HashMap<SyncGuid, Vec<dogear::Guid>> = HashMap::new();
+        let mut stmt = self.db.prepare(&format!(
+            "SELECT b.guid, p.guid AS parentGuid, b.type, b.syncChangeCounter,
+                    b.syncStatus, b.lastModified AS localModified,
+                    IFNULL(b.title, '') AS title,
+                    {url_fragment} AS url
+             FROM moz_bookmarks b
+             JOIN moz_bookmarks p ON p.id = b.parent
+             WHERE b.guid <> '{root_guid}'
+             ORDER BY b.parent, b.position",
+            url_fragment = UrlOrPlaceIdFragment::PlaceId("b.fk"),
+            root_guid = BookmarkRootGuid::Root.as_guid().as_str(),
+        ))?;
+        let mut results = stmt.query([])?;
+
+        while let Some(row) = results.next()? {
+            self.scope.err_if_interrupted()?;
+
+            let (item, content) = self.local_row_to_item(row)?;
+
+            let parent_guid = row.get::<_, SyncGuid>("parentGuid")?;
+            child_guids_by_parent_guid
+                .entry(parent_guid)
+                .or_default()
+                .push(item.guid.clone());
+
+            let mut p = builder.item(item)?;
+            if let Some(content) = content {
+                p.content(content);
+            }
+        }
+
+        // At this point, we've added entries for all items to the tree, so
+        // we can add their structure info.
+        for (parent_guid, child_guids) in &child_guids_by_parent_guid {
+            for child_guid in child_guids {
+                self.scope.err_if_interrupted()?;
+                builder
+                    .parent_for(child_guid)
+                    .by_structure(&parent_guid.as_str().into())?;
+            }
+        }
+
+        // Note tombstones for locally deleted items.
+        let mut stmt = self.db.prepare("SELECT guid FROM moz_bookmarks_deleted")?;
+        let mut results = stmt.query([])?;
+        while let Some(row) = results.next()? {
+            self.scope.err_if_interrupted()?;
+            let guid = row.get::<_, SyncGuid>("guid")?;
+            builder.deletion(guid.as_str().into());
+        }
+
+        let tree = Tree::try_from(builder)?;
+        Ok(tree)
+    }
+
+    /// Builds a fully rooted tree from all synced items and tombstones.
+    fn fetch_remote_tree(&self) -> Result<Tree> {
+        // Unlike the local tree, items and structure are stored separately, so
+        // we use three separate statements to fetch the root, its descendants,
+        // and their structure.
+        let sql = format!(
+            "SELECT guid, serverModified, kind, needsMerge, validity
+             FROM moz_bookmarks_synced
+             WHERE NOT isDeleted AND
+                   guid = '{root_guid}'",
+            root_guid = BookmarkRootGuid::Root.as_guid().as_str()
+        );
+        let mut builder = self
+            .db
+            .try_query_row(
+                &sql,
+                [],
+                |row| -> Result<_> {
+                    let (root, _) = self.remote_row_to_item(row)?;
+                    Ok(Tree::with_root(root))
+                },
+                false,
+            )?
+            .ok_or(Error::Corruption(Corruption::InvalidSyncedRoots))?;
+        builder.reparent_orphans_to(&dogear::UNFILED_GUID);
+
+        let sql = format!(
+            "SELECT v.guid, v.parentGuid, v.serverModified, v.kind,
+                    IFNULL(v.title, '') AS title, v.needsMerge, v.validity,
+                    v.isDeleted, {url_fragment} AS url
+             FROM moz_bookmarks_synced v
+             WHERE v.guid <> '{root_guid}'
+             ORDER BY v.guid",
+            url_fragment = UrlOrPlaceIdFragment::PlaceId("v.placeId"),
+            root_guid = BookmarkRootGuid::Root.as_guid().as_str()
+        );
+        let mut stmt = self.db.prepare(&sql)?;
+        let mut results = stmt.query([])?;
+        while let Some(row) = results.next()? {
+            self.scope.err_if_interrupted()?;
+
+            let is_deleted = row.get::<_, bool>("isDeleted")?;
+            if is_deleted {
+                let needs_merge = row.get::<_, bool>("needsMerge")?;
+                if !needs_merge {
+                    // Ignore already-merged tombstones. These aren't persisted
+                    // locally, so merging them is a no-op.
+                    continue;
+                }
+                let guid = row.get::<_, SyncGuid>("guid")?;
+                builder.deletion(guid.as_str().into());
+            } else {
+                let (item, content) = self.remote_row_to_item(row)?;
+                let mut p = builder.item(item)?;
+                if let Some(content) = content {
+                    p.content(content);
+                }
+                if let Some(parent_guid) = row.get::<_, Option<SyncGuid>>("parentGuid")? {
+                    p.by_parent_guid(parent_guid.as_str().into())?;
+                }
+            }
+        }
+
+        let sql = format!(
+            "SELECT guid, parentGuid FROM moz_bookmarks_synced_structure
+             WHERE guid <> '{root_guid}'
+             ORDER BY parentGuid, position",
+            root_guid = BookmarkRootGuid::Root.as_guid().as_str()
+        );
+        let mut stmt = self.db.prepare(&sql)?;
+        let mut results = stmt.query([])?;
+        while let Some(row) = results.next()? {
+            self.scope.err_if_interrupted()?;
+            let guid = row.get::<_, SyncGuid>("guid")?;
+            let parent_guid = row.get::<_, SyncGuid>("parentGuid")?;
+            builder
+                .parent_for(&guid.as_str().into())
+                .by_children(&parent_guid.as_str().into())?;
+        }
+
+        let tree = Tree::try_from(builder)?;
+        Ok(tree)
+    }
+
+    fn apply(&mut self, root: MergedRoot<'_>) -> Result<()> {
+        let ops = root.completion_ops_with_signal(&MergeInterruptee(self.scope))?;
+
+        if ops.is_empty() {
+            // If we don't have any items to apply, upload, or delete,
+            // no need to open a transaction at all.
+            return Ok(());
+        }
+
+        let tx = if !self.external_transaction {
+            Some(self.db.begin_transaction()?)
+        } else {
+            None
+        };
+
+        // If the local tree has changed since we started the merge, we abort
+        // in the expectation it will succeed next time.
+        if self.global_change_tracker.changed() {
+            log::info!("Aborting update of local items as local tree changed while merging");
+            if let Some(tx) = tx {
+                tx.rollback()?;
+            }
+            return Ok(());
+        }
+
+        log::debug!("Updating local items in Places");
+        update_local_items_in_places(self.db, self.scope, self.local_time, &ops)?;
+
+        log::debug!("Staging items to upload");
+        stage_items_to_upload(
+            self.db,
+            self.scope,
+            &ops.upload_items,
+            &ops.upload_tombstones,
+        )?;
+
+        self.db.execute_batch("DELETE FROM itemsToApply;")?;
+        if let Some(tx) = tx {
+            tx.commit()?;
+        }
+        Ok(())
+    }
+}
+
+/// A helper that formats an optional value so that it can be included in a SQL
+/// statement. `None` values become SQL `NULL`s.
+struct NullableFragment<T>(Option<T>);
+
+impl<T> fmt::Display for NullableFragment<T>
+where
+    T: fmt::Display,
+{
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match &self.0 {
+            Some(v) => v.fmt(f),
+            None => write!(f, "NULL"),
+        }
+    }
+}
+
+/// A helper that interpolates a SQL `CASE` expression for converting a synced
+/// item kind to a local item type. The expression evaluates to `NULL` if the
+/// kind is unknown.
+struct ItemTypeFragment(&'static str);
+
+impl fmt::Display for ItemTypeFragment {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(
+            f,
+            "(CASE WHEN {col} IN ({bookmark_kind}, {query_kind})
+                        THEN {bookmark_type}
+                   WHEN {col} IN ({folder_kind}, {livemark_kind})
+                        THEN {folder_type}
+                   WHEN {col} = {separator_kind}
+                        THEN {separator_type}
+              END)",
+            col = self.0,
+            bookmark_kind = SyncedBookmarkKind::Bookmark as u8,
+            query_kind = SyncedBookmarkKind::Query as u8,
+            bookmark_type = BookmarkType::Bookmark as u8,
+            folder_kind = SyncedBookmarkKind::Folder as u8,
+            livemark_kind = SyncedBookmarkKind::Livemark as u8,
+            folder_type = BookmarkType::Folder as u8,
+            separator_kind = SyncedBookmarkKind::Separator as u8,
+            separator_type = BookmarkType::Separator as u8,
+        )
+    }
+}
+
+/// Formats a `SELECT` statement for staging local items in the `itemsToUpload`
+/// table.
+struct UploadItemsFragment(&'static str);
+
+impl fmt::Display for UploadItemsFragment {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(
+            f,
+            "SELECT {alias}.id, {alias}.guid, {alias}.syncChangeCounter,
+                    p.guid AS parentGuid, p.title AS parentTitle,
+                    {alias}.dateAdded, {kind_fragment} AS kind,
+                    {alias}.title, h.id AS placeId, h.url,
+                    (SELECT k.keyword FROM moz_keywords k
+                     WHERE k.place_id = h.id) AS keyword,
+                    {alias}.position
+                FROM moz_bookmarks {alias}
+                JOIN moz_bookmarks p ON p.id = {alias}.parent
+                LEFT JOIN moz_places h ON h.id = {alias}.fk",
+            alias = self.0,
+            kind_fragment = item_kind_fragment(self.0, "type", UrlOrPlaceIdFragment::Url("h.url")),
+        )
+    }
+}
+
+/// A helper that interpolates a named SQL common table expression (CTE) for
+/// local items. The CTE may be included in a `WITH RECURSIVE` clause.
+struct LocalItemsFragment<'a>(&'a str);
+
+impl<'a> fmt::Display for LocalItemsFragment<'a> {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(
+            f,
+            "{name}(id, guid, parentId, parentGuid, position, type, title, parentTitle,
+                    placeId, dateAdded, lastModified, syncChangeCounter, level) AS (
+             SELECT b.id, b.guid, 0, NULL, b.position, b.type, b.title, NULL,
+                    b.fk, b.dateAdded, b.lastModified, b.syncChangeCounter, 0
+             FROM moz_bookmarks b
+             WHERE b.guid = '{root_guid}'
+             UNION ALL
+             SELECT b.id, b.guid, s.id, s.guid, b.position, b.type, b.title, s.title,
+                    b.fk, b.dateAdded, b.lastModified, b.syncChangeCounter, s.level + 1
+             FROM moz_bookmarks b
+             JOIN {name} s ON s.id = b.parent)",
+            name = self.0,
+            root_guid = BookmarkRootGuid::Root.as_guid().as_str()
+        )
+    }
+}
+
+fn item_kind_fragment(
+    table_name: &'static str,
+    type_column_name: &'static str,
+    url_or_place_id_fragment: UrlOrPlaceIdFragment,
+) -> ItemKindFragment {
+    ItemKindFragment {
+        table_name,
+        type_column_name,
+        url_or_place_id_fragment,
+    }
+}
+
+/// A helper that interpolates a SQL `CASE` expression for converting a local
+/// item type to a synced item kind. The expression evaluates to `NULL` if the
+/// type is unknown.
+struct ItemKindFragment {
+    /// The name of the Places bookmarks table.
+    table_name: &'static str,
+    /// The name of the column containing the Places item type.
+    type_column_name: &'static str,
+    /// The column containing the item's URL or Place ID.
+    url_or_place_id_fragment: UrlOrPlaceIdFragment,
+}
+
+impl fmt::Display for ItemKindFragment {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(
+            f,
+            "(CASE {table_name}.{type_column_name}
+              WHEN {bookmark_type} THEN (
+                  CASE substr({url}, 1, 6)
+                  /* Queries are bookmarks with a 'place:' URL scheme. */
+                  WHEN 'place:' THEN {query_kind}
+                  ELSE {bookmark_kind}
+                  END
+              )
+              WHEN {folder_type} THEN {folder_kind}
+              WHEN {separator_type} THEN {separator_kind}
+              END)",
+            table_name = self.table_name,
+            type_column_name = self.type_column_name,
+            bookmark_type = BookmarkType::Bookmark as u8,
+            url = self.url_or_place_id_fragment,
+            query_kind = SyncedBookmarkKind::Query as u8,
+            bookmark_kind = SyncedBookmarkKind::Bookmark as u8,
+            folder_type = BookmarkType::Folder as u8,
+            folder_kind = SyncedBookmarkKind::Folder as u8,
+            separator_type = BookmarkType::Separator as u8,
+            separator_kind = SyncedBookmarkKind::Separator as u8,
+        )
+    }
+}
+
+/// A helper that interpolates a SQL expression for querying a local item's
+/// URL. Note that the `&'static str` for each variant specifies the _name of
+/// the column_ containing the URL or ID, not the URL or ID itself.
+enum UrlOrPlaceIdFragment {
+    /// The name of the column containing the URL. This avoids a subquery if
+    /// a column for the URL already exists in the query.
+    Url(&'static str),
+    /// The name of the column containing the Place ID. This writes out a
+    /// subquery to look up the URL.
+    PlaceId(&'static str),
+}
+
+impl fmt::Display for UrlOrPlaceIdFragment {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            UrlOrPlaceIdFragment::Url(s) => write!(f, "{}", s),
+            UrlOrPlaceIdFragment::PlaceId(s) => {
+                write!(f, "(SELECT h.url FROM moz_places h WHERE h.id = {})", s)
+            }
+        }
+    }
+}
+
+/// A helper that interpolates a SQL list containing the given bookmark
+/// root GUIDs.
+struct RootsFragment<'a>(&'a [BookmarkRootGuid]);
+
+impl<'a> fmt::Display for RootsFragment<'a> {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.write_str("(")?;
+        for (i, guid) in self.0.iter().enumerate() {
+            if i != 0 {
+                f.write_str(",")?;
+            }
+            write!(f, "'{}'", guid.as_str())?;
+        }
+        f.write_str(")")
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::api::places_api::{test::new_mem_api, ConnectionType, PlacesApi};
+    use crate::bookmark_sync::tests::SyncedBookmarkItem;
+    use crate::db::PlacesDb;
+    use crate::storage::{
+        bookmarks::{
+            get_raw_bookmark, insert_bookmark, update_bookmark, BookmarkPosition,
+            InsertableBookmark, UpdatableBookmark, USER_CONTENT_ROOTS,
+        },
+        history::frecency_stale_at,
+        tags,
+    };
+    use crate::tests::{
+        assert_json_tree as assert_local_json_tree, insert_json_tree as insert_local_json_tree,
+    };
+    use dogear::{Store as DogearStore, Validity};
+    use pretty_assertions::assert_eq;
+    use rusqlite::{Error as RusqlError, ErrorCode};
+    use serde_json::{json, Value};
+    use std::{
+        borrow::Cow,
+        time::{Duration, SystemTime},
+    };
+    use sync15::bso::{IncomingBso, IncomingKind};
+    use sync15::engine::CollSyncIds;
+    use sync_guid::Guid;
+    use url::Url;
+
+    // A helper type to simplify writing table-driven tests with synced items.
+    struct ExpectedSyncedItem<'a>(SyncGuid, Cow<'a, SyncedBookmarkItem>);
+
+    impl<'a> ExpectedSyncedItem<'a> {
+        fn new(
+            guid: impl Into<SyncGuid>,
+            expected: &'a SyncedBookmarkItem,
+        ) -> ExpectedSyncedItem<'a> {
+            ExpectedSyncedItem(guid.into(), Cow::Borrowed(expected))
+        }
+
+        fn with_properties(
+            guid: impl Into<SyncGuid>,
+            expected: &'a SyncedBookmarkItem,
+            f: impl FnOnce(&mut SyncedBookmarkItem) -> &mut SyncedBookmarkItem + 'static,
+        ) -> ExpectedSyncedItem<'a> {
+            let mut expected = expected.clone();
+            f(&mut expected);
+            ExpectedSyncedItem(guid.into(), Cow::Owned(expected))
+        }
+
+        fn check(&self, conn: &PlacesDb) -> Result<()> {
+            let actual =
+                SyncedBookmarkItem::get(conn, &self.0)?.expect("Expected synced item should exist");
+            assert_eq!(&actual, &*self.1);
+            Ok(())
+        }
+    }
+
+    fn create_sync_engine(api: &PlacesApi) -> BookmarksSyncEngine {
+        BookmarksSyncEngine::new(api.get_sync_connection().unwrap()).unwrap()
+    }
+
+    fn engine_apply_incoming(
+        engine: &BookmarksSyncEngine,
+        incoming: Vec<IncomingBso>,
+    ) -> Vec<OutgoingBso> {
+        let mut telem = telemetry::Engine::new(engine.collection_name());
+        engine
+            .stage_incoming(incoming, &mut telem)
+            .expect("Should stage incoming");
+        engine
+            .apply(ServerTimestamp(0), &mut telem)
+            .expect("Should apply")
+    }
+
+    // Applys the incoming records, and also "finishes" the sync by pretending
+    // we uploaded the outgoing items and marks them as uploaded.
+    // Returns the GUIDs of the outgoing items.
+    fn apply_incoming(
+        api: &PlacesApi,
+        remote_time: ServerTimestamp,
+        records_json: Value,
+    ) -> Vec<Guid> {
+        // suck records into the engine.
+        let engine = create_sync_engine(api);
+
+        let incoming = match records_json {
+            Value::Array(records) => records
+                .into_iter()
+                .map(|record| {
+                    let timestamp = record
+                        .as_object()
+                        .and_then(|r| r.get("modified"))
+                        .map(|v| {
+                            serde_json::from_value(v.clone())
+                                .expect("Should deserialize server modified")
+                        })
+                        .unwrap_or(remote_time);
+                    IncomingBso::from_test_content_ts(record, timestamp)
+                })
+                .collect(),
+            Value::Object(ref r) => {
+                let timestamp = r
+                    .get("modified")
+                    .map(|v| {
+                        serde_json::from_value(v.clone())
+                            .expect("Should deserialize server modified")
+                    })
+                    .unwrap_or(remote_time);
+                vec![IncomingBso::from_test_content_ts(records_json, timestamp)]
+            }
+            _ => panic!("unexpected json value"),
+        };
+
+        engine_apply_incoming(&engine, incoming);
+
+        let sync_db = api.get_sync_connection().unwrap();
+        let syncer = sync_db.lock();
+        let mut stmt = syncer
+            .prepare("SELECT guid FROM itemsToUpload")
+            .expect("Should prepare statement to fetch uploaded GUIDs");
+        let uploaded_guids: Vec<Guid> = stmt
+            .query_and_then([], |row| -> rusqlite::Result<_> { row.get::<_, Guid>(0) })
+            .expect("Should fetch uploaded GUIDs")
+            .map(std::result::Result::unwrap)
+            .collect();
+
+        push_synced_items(&syncer, &engine.scope, remote_time, uploaded_guids.clone())
+            .expect("Should push synced changes back to the engine");
+        uploaded_guids
+    }
+
+    fn assert_incoming_creates_local_tree(
+        api: &PlacesApi,
+        records_json: Value,
+        local_folder: &SyncGuid,
+        local_tree: Value,
+    ) {
+        apply_incoming(api, ServerTimestamp(0), records_json);
+        assert_local_json_tree(
+            &api.get_sync_connection().unwrap().lock(),
+            local_folder,
+            local_tree,
+        );
+    }
+
+    #[test]
+    fn test_fetch_remote_tree() -> Result<()> {
+        let records = vec![
+            json!({
+                "id": "qqVTRWhLBOu3",
+                "type": "bookmark",
+                "parentid": "unfiled",
+                "parentName": "Unfiled Bookmarks",
+                "dateAdded": 1_381_542_355_843u64,
+                "title": "The title",
+                "bmkUri": "https://example.com",
+                "tags": [],
+            }),
+            json!({
+                "id": "unfiled",
+                "type": "folder",
+                "parentid": "places",
+                "parentName": "",
+                "dateAdded": 0,
+                "title": "Unfiled Bookmarks",
+                "children": ["qqVTRWhLBOu3"],
+                "tags": [],
+            }),
+        ];
+
+        let api = new_mem_api();
+        let db = api.get_sync_connection().unwrap();
+        let conn = db.lock();
+
+        // suck records into the database.
+        let interrupt_scope = conn.begin_interrupt_scope()?;
+
+        let incoming = records
+            .into_iter()
+            .map(IncomingBso::from_test_content)
+            .collect();
+
+        stage_incoming(
+            &conn,
+            &interrupt_scope,
+            incoming,
+            &mut telemetry::EngineIncoming::new(),
+        )
+        .expect("Should apply incoming and stage outgoing records");
+
+        let merger = Merger::new(&conn, &interrupt_scope, ServerTimestamp(0));
+
+        let tree = merger.fetch_remote_tree()?;
+
+        // should be each user root, plus the real root, plus the bookmark we added.
+        assert_eq!(tree.guids().count(), USER_CONTENT_ROOTS.len() + 2);
+
+        let node = tree
+            .node_for_guid(&"qqVTRWhLBOu3".into())
+            .expect("should exist");
+        assert!(node.needs_merge);
+        assert_eq!(node.validity, Validity::Valid);
+        assert_eq!(node.level(), 2);
+        assert!(node.is_syncable());
+
+        let node = tree
+            .node_for_guid(&BookmarkRootGuid::Unfiled.as_guid().as_str().into())
+            .expect("should exist");
+        assert!(node.needs_merge);
+        assert_eq!(node.validity, Validity::Valid);
+        assert_eq!(node.level(), 1);
+        assert!(node.is_syncable());
+
+        let node = tree
+            .node_for_guid(&BookmarkRootGuid::Menu.as_guid().as_str().into())
+            .expect("should exist");
+        assert!(!node.needs_merge);
+        assert_eq!(node.validity, Validity::Valid);
+        assert_eq!(node.level(), 1);
+        assert!(node.is_syncable());
+
+        let node = tree
+            .node_for_guid(&BookmarkRootGuid::Root.as_guid().as_str().into())
+            .expect("should exist");
+        assert_eq!(node.validity, Validity::Valid);
+        assert_eq!(node.level(), 0);
+        assert!(!node.is_syncable());
+
+        // We should have changes.
+        assert!(db_has_changes(&conn).unwrap());
+        Ok(())
+    }
+
+    #[test]
+    fn test_fetch_local_tree() -> Result<()> {
+        let now = SystemTime::now();
+        let previously_ts: Timestamp = (now - Duration::new(10, 0)).into();
+        let api = new_mem_api();
+        let writer = api.open_connection(ConnectionType::ReadWrite)?;
+        let sync_db = api.get_sync_connection().unwrap();
+        let syncer = sync_db.lock();
+
+        writer
+            .execute("UPDATE moz_bookmarks SET syncChangeCounter = 0", [])
+            .expect("should work");
+
+        insert_local_json_tree(
+            &writer,
+            json!({
+                "guid": &BookmarkRootGuid::Unfiled.as_guid(),
+                "children": [
+                    {
+                        "guid": "bookmark1___",
+                        "title": "the bookmark",
+                        "url": "https://www.example.com/",
+                        "last_modified": previously_ts,
+                        "date_added": previously_ts,
+                    },
+                ]
+            }),
+        );
+
+        let interrupt_scope = syncer.begin_interrupt_scope()?;
+        let merger =
+            Merger::with_localtime(&syncer, &interrupt_scope, ServerTimestamp(0), now.into());
+
+        let tree = merger.fetch_local_tree()?;
+
+        // should be each user root, plus the real root, plus the bookmark we added.
+        assert_eq!(tree.guids().count(), USER_CONTENT_ROOTS.len() + 2);
+
+        let node = tree
+            .node_for_guid(&"bookmark1___".into())
+            .expect("should exist");
+        assert!(node.needs_merge);
+        assert_eq!(node.level(), 2);
+        assert!(node.is_syncable());
+        assert_eq!(node.age, 10000);
+
+        let node = tree
+            .node_for_guid(&BookmarkRootGuid::Unfiled.as_guid().as_str().into())
+            .expect("should exist");
+        assert!(node.needs_merge);
+        assert_eq!(node.level(), 1);
+        assert!(node.is_syncable());
+
+        let node = tree
+            .node_for_guid(&BookmarkRootGuid::Menu.as_guid().as_str().into())
+            .expect("should exist");
+        assert!(!node.needs_merge);
+        assert_eq!(node.level(), 1);
+        assert!(node.is_syncable());
+
+        let node = tree
+            .node_for_guid(&BookmarkRootGuid::Root.as_guid().as_str().into())
+            .expect("should exist");
+        assert!(!node.needs_merge);
+        assert_eq!(node.level(), 0);
+        assert!(!node.is_syncable());
+        // hard to know the exact age of the root, but we know the max.
+        let max_dur = SystemTime::now().duration_since(now).unwrap();
+        let max_age = max_dur.as_secs() as i64 * 1000 + i64::from(max_dur.subsec_millis());
+        assert!(node.age <= max_age);
+
+        // We should have changes.
+        assert!(db_has_changes(&syncer).unwrap());
+        Ok(())
+    }
+
+    #[test]
+    fn test_apply_bookmark() {
+        let api = new_mem_api();
+        assert_incoming_creates_local_tree(
+            &api,
+            json!([{
+                "id": "bookmark1___",
+                "type": "bookmark",
+                "parentid": "unfiled",
+                "parentName": "Unfiled Bookmarks",
+                "dateAdded": 1_381_542_355_843u64,
+                "title": "Some bookmark",
+                "bmkUri": "http://example.com",
+            },
+            {
+                "id": "unfiled",
+                "type": "folder",
+                "parentid": "places",
+                "dateAdded": 1_381_542_355_843u64,
+                "title": "Unfiled",
+                "children": ["bookmark1___"],
+            }]),
+            &BookmarkRootGuid::Unfiled.as_guid(),
+            json!({"children" : [{"guid": "bookmark1___", "url": "http://example.com"}]}),
+        );
+        let reader = api
+            .open_connection(ConnectionType::ReadOnly)
+            .expect("Should open read-only connection");
+        assert!(
+            frecency_stale_at(&reader, &Url::parse("http://example.com").unwrap())
+                .expect("Should check stale frecency")
+                .is_some(),
+            "Should mark frecency for bookmark URL as stale"
+        );
+
+        let writer = api
+            .open_connection(ConnectionType::ReadWrite)
+            .expect("Should open read-write connection");
+        insert_local_json_tree(
+            &writer,
+            json!({
+                "guid": &BookmarkRootGuid::Menu.as_guid(),
+                "children": [
+                    {
+                        "guid": "bookmark2___",
+                        "title": "2",
+                        "url": "http://example.com/2",
+                    }
+                ],
+            }),
+        );
+        assert_incoming_creates_local_tree(
+            &api,
+            json!([{
+                "id": "menu",
+                "type": "folder",
+                "parentid": "places",
+                "parentName": "",
+                "dateAdded": 0,
+                "title": "menu",
+                "children": ["bookmark2___"],
+            }, {
+                "id": "bookmark2___",
+                "type": "bookmark",
+                "parentid": "menu",
+                "parentName": "menu",
+                "dateAdded": 1_381_542_355_843u64,
+                "title": "2",
+                "bmkUri": "http://example.com/2-remote",
+            }]),
+            &BookmarkRootGuid::Menu.as_guid(),
+            json!({"children" : [{"guid": "bookmark2___", "url": "http://example.com/2-remote"}]}),
+        );
+        assert!(
+            frecency_stale_at(&reader, &Url::parse("http://example.com/2").unwrap())
+                .expect("Should check stale frecency for old URL")
+                .is_some(),
+            "Should mark frecency for old URL as stale"
+        );
+        assert!(
+            frecency_stale_at(&reader, &Url::parse("http://example.com/2-remote").unwrap())
+                .expect("Should check stale frecency for new URL")
+                .is_some(),
+            "Should mark frecency for new URL as stale"
+        );
+
+        let sync_db = api.get_sync_connection().unwrap();
+        let syncer = sync_db.lock();
+        let interrupt_scope = syncer.begin_interrupt_scope().unwrap();
+
+        update_frecencies(&syncer, &interrupt_scope).expect("Should update frecencies");
+
+        assert!(
+            frecency_stale_at(&reader, &Url::parse("http://example.com").unwrap())
+                .expect("Should check stale frecency")
+                .is_none(),
+            "Should recalculate frecency for first bookmark"
+        );
+        assert!(
+            frecency_stale_at(&reader, &Url::parse("http://example.com/2").unwrap())
+                .expect("Should check stale frecency for old URL")
+                .is_none(),
+            "Should recalculate frecency for old URL"
+        );
+        assert!(
+            frecency_stale_at(&reader, &Url::parse("http://example.com/2-remote").unwrap())
+                .expect("Should check stale frecency for new URL")
+                .is_none(),
+            "Should recalculate frecency for new URL"
+        );
+    }
+
+    #[test]
+    fn test_apply_complex_bookmark_tags() -> Result<()> {
+        let api = new_mem_api();
+        let writer = api.open_connection(ConnectionType::ReadWrite)?;
+
+        // Insert two local bookmarks with the same URL A (so they'll have
+        // identical tags) and a third with a different URL B, but one same
+        // tag as A.
+        let local_bookmarks = vec![
+            InsertableBookmark {
+                parent_guid: BookmarkRootGuid::Unfiled.as_guid(),
+                position: BookmarkPosition::Append,
+                date_added: None,
+                last_modified: None,
+                guid: Some("bookmarkAAA1".into()),
+                url: Url::parse("http://example.com/a").unwrap(),
+                title: Some("A1".into()),
+            }
+            .into(),
+            InsertableBookmark {
+                parent_guid: BookmarkRootGuid::Menu.as_guid(),
+                position: BookmarkPosition::Append,
+                date_added: None,
+                last_modified: None,
+                guid: Some("bookmarkAAA2".into()),
+                url: Url::parse("http://example.com/a").unwrap(),
+                title: Some("A2".into()),
+            }
+            .into(),
+            InsertableBookmark {
+                parent_guid: BookmarkRootGuid::Unfiled.as_guid(),
+                position: BookmarkPosition::Append,
+                date_added: None,
+                last_modified: None,
+                guid: Some("bookmarkBBBB".into()),
+                url: Url::parse("http://example.com/b").unwrap(),
+                title: Some("B".into()),
+            }
+            .into(),
+        ];
+        let local_tags = &[
+            ("http://example.com/a", vec!["one", "two"]),
+            (
+                "http://example.com/b",
+                // Local duplicate tags should be ignored.
+                vec!["two", "three", "three", "four"],
+            ),
+        ];
+        for bm in local_bookmarks.into_iter() {
+            insert_bookmark(&writer, bm)?;
+        }
+        for (url, tags) in local_tags {
+            let url = Url::parse(url)?;
+            for t in tags.iter() {
+                tags::tag_url(&writer, &url, t)?;
+            }
+        }
+
+        // Now for some fun server data. Only B, C, and F2 have problems;
+        // D and E are fine, and shouldn't be reuploaded.
+        let remote_records = json!([{
+            // Change B's tags on the server, and duplicate `two` for good
+            // measure. We should reupload B with only one `two` tag.
+            "id": "bookmarkBBBB",
+            "type": "bookmark",
+            "parentid": "unfiled",
+            "parentName": "Unfiled",
+            "dateAdded": 1_381_542_355_843u64,
+            "title": "B",
+            "bmkUri": "http://example.com/b",
+            "tags": ["two", "two", "three", "eight"],
+        }, {
+            // C is an example of bad data on the server: bookmarks with the
+            // same URL should have the same tags, but C1/C2 have different tags
+            // than C3. We should reupload all of them.
+            "id": "bookmarkCCC1",
+            "type": "bookmark",
+            "parentid": "unfiled",
+            "parentName": "Unfiled",
+            "dateAdded": 1_381_542_355_843u64,
+            "title": "C1",
+            "bmkUri": "http://example.com/c",
+            "tags": ["four", "five", "six"],
+        }, {
+            "id": "bookmarkCCC2",
+            "type": "bookmark",
+            "parentid": "menu",
+            "parentName": "Menu",
+            "dateAdded": 1_381_542_355_843u64,
+            "title": "C2",
+            "bmkUri": "http://example.com/c",
+            "tags": ["four", "five", "six"],
+        }, {
+            "id": "bookmarkCCC3",
+            "type": "bookmark",
+            "parentid": "menu",
+            "parentName": "Menu",
+            "dateAdded": 1_381_542_355_843u64,
+            "title": "C3",
+            "bmkUri": "http://example.com/c",
+            "tags": ["six", "six", "seven"],
+        }, {
+            // D has the same tags as C1/2, but a different URL. This is
+            // perfectly fine, since URLs and tags are many-many! D also
+            // isn't duplicated, so it'll be filtered out by the
+            // `HAVING COUNT(*) > 1` clause.
+            "id": "bookmarkDDDD",
+            "type": "bookmark",
+            "parentid": "unfiled",
+            "parentName": "Unfiled",
+            "dateAdded": 1_381_542_355_843u64,
+            "title": "D",
+            "bmkUri": "http://example.com/d",
+            "tags": ["four", "five", "six"],
+        }, {
+            // E1 and E2 have the same URLs and the same tags, so we shouldn't
+            // reupload either.
+            "id": "bookmarkEEE1",
+            "type": "bookmark",
+            "parentid": "toolbar",
+            "parentName": "Toolbar",
+            "dateAdded": 1_381_542_355_843u64,
+            "title": "E1",
+            "bmkUri": "http://example.com/e",
+            "tags": ["nine", "ten", "eleven"],
+        }, {
+            "id": "bookmarkEEE2",
+            "type": "bookmark",
+            "parentid": "mobile",
+            "parentName": "Mobile",
+            "dateAdded": 1_381_542_355_843u64,
+            "title": "E2",
+            "bmkUri": "http://example.com/e",
+            "tags": ["nine", "ten", "eleven"],
+        }, {
+            // F1 and F2 have mismatched tags, but with a twist: F2 doesn't
+            // have _any_ tags! We should only reupload F2.
+            "id": "bookmarkFFF1",
+            "type": "bookmark",
+            "parentid": "toolbar",
+            "parentName": "Toolbar",
+            "dateAdded": 1_381_542_355_843u64,
+            "title": "F1",
+            "bmkUri": "http://example.com/f",
+            "tags": ["twelve"],
+        }, {
+            "id": "bookmarkFFF2",
+            "type": "bookmark",
+            "parentid": "mobile",
+            "parentName": "Mobile",
+            "dateAdded": 1_381_542_355_843u64,
+            "title": "F2",
+            "bmkUri": "http://example.com/f",
+        }, {
+            "id": "unfiled",
+            "type": "folder",
+            "parentid": "places",
+            "dateAdded": 1_381_542_355_843u64,
+            "title": "Unfiled",
+            "children": ["bookmarkBBBB", "bookmarkCCC1", "bookmarkDDDD"],
+        }, {
+            "id": "menu",
+            "type": "folder",
+            "parentid": "places",
+            "dateAdded": 1_381_542_355_843u64,
+            "title": "Menu",
+            "children": ["bookmarkCCC2", "bookmarkCCC3"],
+        }, {
+            "id": "toolbar",
+            "type": "folder",
+            "parentid": "places",
+            "dateAdded": 1_381_542_355_843u64,
+            "title": "Toolbar",
+            "children": ["bookmarkEEE1", "bookmarkFFF1"],
+        }, {
+            "id": "mobile",
+            "type": "folder",
+            "parentid": "places",
+            "dateAdded": 1_381_542_355_843u64,
+            "title": "Mobile",
+            "children": ["bookmarkEEE2", "bookmarkFFF2"],
+        }]);
+
+        // Boilerplate to apply incoming records, since we want to check
+        // outgoing record contents.
+        let engine = create_sync_engine(&api);
+        let incoming = if let Value::Array(records) = remote_records {
+            records
+                .into_iter()
+                .map(IncomingBso::from_test_content)
+                .collect()
+        } else {
+            unreachable!("JSON records must be an array");
+        };
+        let mut outgoing = engine_apply_incoming(&engine, incoming);
+        outgoing.sort_by(|a, b| a.envelope.id.cmp(&b.envelope.id));
+
+        // Verify that we applied all incoming records correctly.
+        assert_local_json_tree(
+            &writer,
+            &BookmarkRootGuid::Root.as_guid(),
+            json!({
+                "guid": &BookmarkRootGuid::Root.as_guid(),
+                "children": [{
+                    "guid": &BookmarkRootGuid::Menu.as_guid(),
+                    "children": [{
+                        "guid": "bookmarkCCC2",
+                        "title": "C2",
+                        "url": "http://example.com/c",
+                    }, {
+                        "guid": "bookmarkCCC3",
+                        "title": "C3",
+                        "url": "http://example.com/c",
+                    }, {
+                        "guid": "bookmarkAAA2",
+                        "title": "A2",
+                        "url": "http://example.com/a",
+                    }],
+                }, {
+                    "guid": &BookmarkRootGuid::Toolbar.as_guid(),
+                    "children": [{
+                        "guid": "bookmarkEEE1",
+                        "title": "E1",
+                        "url": "http://example.com/e",
+                    }, {
+                        "guid": "bookmarkFFF1",
+                        "title": "F1",
+                        "url": "http://example.com/f",
+                    }],
+                }, {
+                    "guid": &BookmarkRootGuid::Unfiled.as_guid(),
+                    "children": [{
+                        "guid": "bookmarkBBBB",
+                        "title": "B",
+                        "url": "http://example.com/b",
+                    }, {
+                        "guid": "bookmarkCCC1",
+                        "title": "C1",
+                        "url": "http://example.com/c",
+                    }, {
+                        "guid": "bookmarkDDDD",
+                        "title": "D",
+                        "url": "http://example.com/d",
+                    }, {
+                        "guid": "bookmarkAAA1",
+                        "title": "A1",
+                        "url": "http://example.com/a",
+                    }],
+                }, {
+                    "guid": &BookmarkRootGuid::Mobile.as_guid(),
+                    "children": [{
+                        "guid": "bookmarkEEE2",
+                        "title": "E2",
+                        "url": "http://example.com/e",
+                    }, {
+                        "guid": "bookmarkFFF2",
+                        "title": "F2",
+                        "url": "http://example.com/f",
+                    }],
+                }],
+            }),
+        );
+        // And verify our local tags are correct, too.
+        let expected_local_tags = &[
+            ("http://example.com/a", vec!["one", "two"]),
+            ("http://example.com/b", vec!["eight", "three", "two"]),
+            ("http://example.com/c", vec!["five", "four", "seven", "six"]),
+            ("http://example.com/d", vec!["five", "four", "six"]),
+            ("http://example.com/e", vec!["eleven", "nine", "ten"]),
+            ("http://example.com/f", vec!["twelve"]),
+        ];
+        for (href, expected) in expected_local_tags {
+            let mut actual = tags::get_tags_for_url(&writer, &Url::parse(href).unwrap())?;
+            actual.sort();
+            assert_eq!(&actual, expected);
+        }
+
+        let expected_outgoing_ids = &[
+            "bookmarkAAA1", // A is new locally.
+            "bookmarkAAA2",
+            "bookmarkBBBB", // B has a duplicate tag.
+            "bookmarkCCC1", // C has mismatched tags.
+            "bookmarkCCC2",
+            "bookmarkCCC3",
+            "bookmarkFFF2", // F2 is missing tags.
+            "menu",         // Roots always get uploaded on the first sync.
+            "mobile",
+            "toolbar",
+            "unfiled",
+        ];
+        assert_eq!(
+            outgoing
+                .iter()
+                .map(|p| p.envelope.id.as_str())
+                .collect::<Vec<_>>(),
+            expected_outgoing_ids,
+            "Should upload new bookmarks and fix up tags",
+        );
+
+        // Now push the records back to the engine, so we can check what we're
+        // uploading.
+        engine
+            .set_uploaded(
+                ServerTimestamp(0),
+                expected_outgoing_ids.iter().map(SyncGuid::from).collect(),
+            )
+            .expect("Should push synced changes back to the engine");
+        engine.sync_finished().expect("should work");
+
+        // A and C should have the same URL and tags, and should be valid now.
+        // Because the builder methods take a `&mut SyncedBookmarkItem`, and we
+        // want to hang on to our base items for cloning later, we can't use
+        // one-liners to create them.
+        let mut synced_item_for_a = SyncedBookmarkItem::new();
+        synced_item_for_a
+            .validity(SyncedBookmarkValidity::Valid)
+            .kind(SyncedBookmarkKind::Bookmark)
+            .url(Some("http://example.com/a"))
+            .tags(["one", "two"].iter().map(|&tag| tag.into()).collect());
+        let mut synced_item_for_b = SyncedBookmarkItem::new();
+        synced_item_for_b
+            .validity(SyncedBookmarkValidity::Valid)
+            .kind(SyncedBookmarkKind::Bookmark)
+            .url(Some("http://example.com/b"))
+            .tags(
+                ["eight", "three", "two"]
+                    .iter()
+                    .map(|&tag| tag.into())
+                    .collect(),
+            )
+            .parent_guid(Some(&BookmarkRootGuid::Unfiled.as_guid()))
+            .title(Some("B"));
+        let mut synced_item_for_c = SyncedBookmarkItem::new();
+        synced_item_for_c
+            .validity(SyncedBookmarkValidity::Valid)
+            .kind(SyncedBookmarkKind::Bookmark)
+            .url(Some("http://example.com/c"))
+            .tags(
+                ["five", "four", "seven", "six"]
+                    .iter()
+                    .map(|&tag| tag.into())
+                    .collect(),
+            );
+        let mut synced_item_for_f = SyncedBookmarkItem::new();
+        synced_item_for_f
+            .validity(SyncedBookmarkValidity::Valid)
+            .kind(SyncedBookmarkKind::Bookmark)
+            .url(Some("http://example.com/f"))
+            .tags(vec!["twelve".into()]);
+        // A table-driven test to clean up some of the boilerplate. We clone
+        // the base item for each test, and pass it to the boxed closure to set
+        // additional properties.
+        let expected_synced_items = &[
+            ExpectedSyncedItem::with_properties("bookmarkAAA1", &synced_item_for_a, |a| {
+                a.parent_guid(Some(&BookmarkRootGuid::Unfiled.as_guid()))
+                    .title(Some("A1"))
+            }),
+            ExpectedSyncedItem::with_properties("bookmarkAAA2", &synced_item_for_a, |a| {
+                a.parent_guid(Some(&BookmarkRootGuid::Menu.as_guid()))
+                    .title(Some("A2"))
+            }),
+            ExpectedSyncedItem::new("bookmarkBBBB", &synced_item_for_b),
+            ExpectedSyncedItem::with_properties("bookmarkCCC1", &synced_item_for_c, |c| {
+                c.parent_guid(Some(&BookmarkRootGuid::Unfiled.as_guid()))
+                    .title(Some("C1"))
+            }),
+            ExpectedSyncedItem::with_properties("bookmarkCCC2", &synced_item_for_c, |c| {
+                c.parent_guid(Some(&BookmarkRootGuid::Menu.as_guid()))
+                    .title(Some("C2"))
+            }),
+            ExpectedSyncedItem::with_properties("bookmarkCCC3", &synced_item_for_c, |c| {
+                c.parent_guid(Some(&BookmarkRootGuid::Menu.as_guid()))
+                    .title(Some("C3"))
+            }),
+            ExpectedSyncedItem::with_properties(
+                // We didn't reupload F1, but let's make sure it's still valid.
+                "bookmarkFFF1",
+                &synced_item_for_f,
+                |f| {
+                    f.parent_guid(Some(&BookmarkRootGuid::Toolbar.as_guid()))
+                        .title(Some("F1"))
+                },
+            ),
+            ExpectedSyncedItem::with_properties("bookmarkFFF2", &synced_item_for_f, |f| {
+                f.parent_guid(Some(&BookmarkRootGuid::Mobile.as_guid()))
+                    .title(Some("F2"))
+            }),
+        ];
+        for item in expected_synced_items {
+            item.check(&writer)?;
+        }
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_apply_bookmark_tags() -> Result<()> {
+        let api = new_mem_api();
+        let writer = api.open_connection(ConnectionType::ReadWrite)?;
+
+        // Insert local item with tagged URL.
+        insert_bookmark(
+            &writer,
+            InsertableBookmark {
+                parent_guid: BookmarkRootGuid::Unfiled.as_guid(),
+                position: BookmarkPosition::Append,
+                date_added: None,
+                last_modified: None,
+                guid: Some("bookmarkAAAA".into()),
+                url: Url::parse("http://example.com/a").unwrap(),
+                title: Some("A".into()),
+            }
+            .into(),
+        )?;
+        tags::tag_url(&writer, &Url::parse("http://example.com/a").unwrap(), "one")?;
+
+        let mut tags_for_a =
+            tags::get_tags_for_url(&writer, &Url::parse("http://example.com/a").unwrap())?;
+        tags_for_a.sort();
+        assert_eq!(tags_for_a, vec!["one".to_owned()]);
+
+        assert_incoming_creates_local_tree(
+            &api,
+            json!([{
+                "id": "bookmarkBBBB",
+                "type": "bookmark",
+                "parentid": "unfiled",
+                "parentName": "Unfiled",
+                "dateAdded": 1_381_542_355_843u64,
+                "title": "B",
+                "bmkUri": "http://example.com/b",
+                "tags": ["one", "two"],
+            }, {
+                "id": "bookmarkCCCC",
+                "type": "bookmark",
+                "parentid": "unfiled",
+                "parentName": "Unfiled",
+                "dateAdded": 1_381_542_355_843u64,
+                "title": "C",
+                "bmkUri": "http://example.com/c",
+                "tags": ["three"],
+            }, {
+                "id": "unfiled",
+                "type": "folder",
+                "parentid": "places",
+                "dateAdded": 1_381_542_355_843u64,
+                "title": "Unfiled",
+                "children": ["bookmarkBBBB", "bookmarkCCCC"],
+            }]),
+            &BookmarkRootGuid::Unfiled.as_guid(),
+            json!({"children" : [
+                  {"guid": "bookmarkBBBB", "url": "http://example.com/b"},
+                  {"guid": "bookmarkCCCC", "url": "http://example.com/c"},
+                  {"guid": "bookmarkAAAA", "url": "http://example.com/a"},
+            ]}),
+        );
+
+        let mut tags_for_a =
+            tags::get_tags_for_url(&writer, &Url::parse("http://example.com/a").unwrap())?;
+        tags_for_a.sort();
+        assert_eq!(tags_for_a, vec!["one".to_owned()]);
+
+        let mut tags_for_b =
+            tags::get_tags_for_url(&writer, &Url::parse("http://example.com/b").unwrap())?;
+        tags_for_b.sort();
+        assert_eq!(tags_for_b, vec!["one".to_owned(), "two".to_owned()]);
+
+        let mut tags_for_c =
+            tags::get_tags_for_url(&writer, &Url::parse("http://example.com/c").unwrap())?;
+        tags_for_c.sort();
+        assert_eq!(tags_for_c, vec!["three".to_owned()]);
+
+        let synced_item_for_a = SyncedBookmarkItem::get(&writer, &"bookmarkAAAA".into())
+            .expect("Should fetch A")
+            .expect("A should exist");
+        assert_eq!(
+            synced_item_for_a,
+            *SyncedBookmarkItem::new()
+                .validity(SyncedBookmarkValidity::Valid)
+                .kind(SyncedBookmarkKind::Bookmark)
+                .parent_guid(Some(&BookmarkRootGuid::Unfiled.as_guid()))
+                .title(Some("A"))
+                .url(Some("http://example.com/a"))
+                .tags(vec!["one".into()])
+        );
+
+        let synced_item_for_b = SyncedBookmarkItem::get(&writer, &"bookmarkBBBB".into())
+            .expect("Should fetch B")
+            .expect("B should exist");
+        assert_eq!(
+            synced_item_for_b,
+            *SyncedBookmarkItem::new()
+                .validity(SyncedBookmarkValidity::Valid)
+                .kind(SyncedBookmarkKind::Bookmark)
+                .parent_guid(Some(&BookmarkRootGuid::Unfiled.as_guid()))
+                .title(Some("B"))
+                .url(Some("http://example.com/b"))
+                .tags(vec!["one".into(), "two".into()])
+        );
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_apply_bookmark_keyword() -> Result<()> {
+        let api = new_mem_api();
+
+        let records = json!([{
+            "id": "bookmarkAAAA",
+            "type": "bookmark",
+            "parentid": "unfiled",
+            "parentName": "Unfiled",
+            "dateAdded": 1_381_542_355_843u64,
+            "title": "A",
+            "bmkUri": "http://example.com/a?b=c&d=%s",
+            "keyword": "ex",
+        },
+        {
+            "id": "unfiled",
+            "type": "folder",
+            "parentid": "places",
+            "dateAdded": 1_381_542_355_843u64,
+            "title": "Unfiled",
+            "children": ["bookmarkAAAA"],
+        }]);
+
+        let db_mutex = api.get_sync_connection().unwrap();
+        let db = db_mutex.lock();
+        let tx = db.begin_transaction()?;
+        let applicator = IncomingApplicator::new(&db);
+
+        if let Value::Array(records) = records {
+            for record in records {
+                applicator.apply_bso(IncomingBso::from_test_content(record))?;
+            }
+        } else {
+            unreachable!("JSON records must be an array");
+        }
+
+        tx.commit()?;
+
+        // Flag the bookmark with the keyword for reupload, so that we can
+        // ensure the keyword is round-tripped correctly.
+        db.execute(
+            "UPDATE moz_bookmarks_synced SET
+                 validity = :validity
+             WHERE guid = :guid",
+            rusqlite::named_params! {
+                ":validity": SyncedBookmarkValidity::Reupload,
+                ":guid": SyncGuid::from("bookmarkAAAA"),
+            },
+        )?;
+
+        let interrupt_scope = db.begin_interrupt_scope()?;
+
+        let mut merger = Merger::new(&db, &interrupt_scope, ServerTimestamp(0));
+        merger.merge()?;
+
+        assert_local_json_tree(
+            &db,
+            &BookmarkRootGuid::Unfiled.as_guid(),
+            json!({"children" : [{"guid": "bookmarkAAAA", "url": "http://example.com/a?b=c&d=%s"}]}),
+        );
+
+        let outgoing = fetch_outgoing_records(&db, &interrupt_scope)?;
+        let record_for_a = outgoing
+            .iter()
+            .find(|payload| payload.envelope.id == "bookmarkAAAA")
+            .expect("Should reupload A");
+        let bk = record_for_a.to_test_incoming_t::<BookmarkRecord>();
+        assert_eq!(bk.url.unwrap(), "http://example.com/a?b=c&d=%s");
+        assert_eq!(bk.keyword.unwrap(), "ex");
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_apply_query() {
+        // should we add some more query variations here?
+        let api = new_mem_api();
+        assert_incoming_creates_local_tree(
+            &api,
+            json!([{
+                "id": "query1______",
+                "type": "query",
+                "parentid": "unfiled",
+                "parentName": "Unfiled Bookmarks",
+                "dateAdded": 1_381_542_355_843u64,
+                "title": "Some query",
+                "bmkUri": "place:tag=foo",
+            },
+            {
+                "id": "unfiled",
+                "type": "folder",
+                "parentid": "places",
+                "dateAdded": 1_381_542_355_843u64,
+                "title": "Unfiled",
+                "children": ["query1______"],
+            }]),
+            &BookmarkRootGuid::Unfiled.as_guid(),
+            json!({"children" : [{"guid": "query1______", "url": "place:tag=foo"}]}),
+        );
+        let reader = api
+            .open_connection(ConnectionType::ReadOnly)
+            .expect("Should open read-only connection");
+        assert!(
+            frecency_stale_at(&reader, &Url::parse("place:tag=foo").unwrap())
+                .expect("Should check stale frecency")
+                .is_none(),
+            "Should not mark frecency for queries as stale"
+        );
+    }
+
+    #[test]
+    fn test_apply() -> Result<()> {
+        let api = new_mem_api();
+        let writer = api.open_connection(ConnectionType::ReadWrite)?;
+        let db = api.get_sync_connection().unwrap();
+        let syncer = db.lock();
+
+        syncer
+            .execute("UPDATE moz_bookmarks SET syncChangeCounter = 0", [])
+            .expect("should work");
+
+        insert_local_json_tree(
+            &writer,
+            json!({
+                "guid": &BookmarkRootGuid::Unfiled.as_guid(),
+                "children": [
+                    {
+                        "guid": "bookmarkAAAA",
+                        "title": "A",
+                        "url": "http://example.com/a",
+                    },
+                    {
+                        "guid": "bookmarkBBBB",
+                        "title": "B",
+                        "url": "http://example.com/b",
+                    },
+                ]
+            }),
+        );
+        tags::tag_url(
+            &writer,
+            &Url::parse("http://example.com/a").expect("Should parse URL for A"),
+            "baz",
+        )
+        .expect("Should tag A");
+
+        let records = vec![
+            json!({
+                "id": "bookmarkCCCC",
+                "type": "bookmark",
+                "parentid": "menu",
+                "parentName": "menu",
+                "dateAdded": 1_552_183_116_885u64,
+                "title": "C",
+                "bmkUri": "http://example.com/c",
+                "tags": ["foo", "bar"],
+            }),
+            json!({
+                "id": "menu",
+                "type": "folder",
+                "parentid": "places",
+                "parentName": "",
+                "dateAdded": 0,
+                "title": "menu",
+                "children": ["bookmarkCCCC"],
+            }),
+        ];
+
+        // Drop the sync connection to avoid a deadlock when the sync engine locks the mutex
+        drop(syncer);
+        let engine = create_sync_engine(&api);
+
+        let incoming = records
+            .into_iter()
+            .map(IncomingBso::from_test_content)
+            .collect();
+
+        let mut outgoing = engine_apply_incoming(&engine, incoming);
+        outgoing.sort_by(|a, b| a.envelope.id.cmp(&b.envelope.id));
+        assert_eq!(
+            outgoing
+                .iter()
+                .map(|p| p.envelope.id.as_str())
+                .collect::<Vec<_>>(),
+            vec!["bookmarkAAAA", "bookmarkBBBB", "unfiled",]
+        );
+        let record_for_a = outgoing
+            .iter()
+            .find(|p| p.envelope.id == "bookmarkAAAA")
+            .expect("Should upload A");
+        let content_for_a = record_for_a.to_test_incoming_t::<BookmarkRecord>();
+        assert_eq!(content_for_a.tags, vec!["baz".to_string()]);
+
+        assert_local_json_tree(
+            &writer,
+            &BookmarkRootGuid::Root.as_guid(),
+            json!({
+                "guid": &BookmarkRootGuid::Root.as_guid(),
+                "children": [
+                    {
+                        "guid": &BookmarkRootGuid::Menu.as_guid(),
+                        "children": [
+                            {
+                                "guid": "bookmarkCCCC",
+                                "title": "C",
+                                "url": "http://example.com/c",
+                                "date_added": Timestamp(1_552_183_116_885),
+                            },
+                        ],
+                    },
+                    {
+                        "guid": &BookmarkRootGuid::Toolbar.as_guid(),
+                        "children": [],
+                    },
+                    {
+                        "guid": &BookmarkRootGuid::Unfiled.as_guid(),
+                        "children": [
+                            {
+                                "guid": "bookmarkAAAA",
+                                "title": "A",
+                                "url": "http://example.com/a",
+                            },
+                            {
+                                "guid": "bookmarkBBBB",
+                                "title": "B",
+                                "url": "http://example.com/b",
+                            },
+                        ],
+                    },
+                    {
+                        "guid": &BookmarkRootGuid::Mobile.as_guid(),
+                        "children": [],
+                    },
+                ],
+            }),
+        );
+
+        // We haven't finished the sync yet, so all local change counts for
+        // items to upload should still be > 0.
+        let guid_for_a: SyncGuid = "bookmarkAAAA".into();
+        let info_for_a = get_raw_bookmark(&writer, &guid_for_a)
+            .expect("Should fetch info for A")
+            .unwrap();
+        assert_eq!(info_for_a._sync_change_counter, 2);
+        let info_for_unfiled = get_raw_bookmark(&writer, &BookmarkRootGuid::Unfiled.as_guid())
+            .expect("Should fetch info for unfiled")
+            .unwrap();
+        assert_eq!(info_for_unfiled._sync_change_counter, 2);
+
+        engine
+            .set_uploaded(
+                ServerTimestamp(0),
+                vec![
+                    "bookmarkAAAA".into(),
+                    "bookmarkBBBB".into(),
+                    "unfiled".into(),
+                ],
+            )
+            .expect("Should push synced changes back to the engine");
+        engine.sync_finished().expect("finish always works");
+
+        let info_for_a = get_raw_bookmark(&writer, &guid_for_a)
+            .expect("Should fetch info for A")
+            .unwrap();
+        assert_eq!(info_for_a._sync_change_counter, 0);
+        let info_for_unfiled = get_raw_bookmark(&writer, &BookmarkRootGuid::Unfiled.as_guid())
+            .expect("Should fetch info for unfiled")
+            .unwrap();
+        assert_eq!(info_for_unfiled._sync_change_counter, 0);
+
+        let mut tags_for_c = tags::get_tags_for_url(
+            &writer,
+            &Url::parse("http://example.com/c").expect("Should parse URL for C"),
+        )
+        .expect("Should return tags for C");
+        tags_for_c.sort();
+        assert_eq!(tags_for_c, &["bar", "foo"]);
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_apply_invalid_url() -> Result<()> {
+        let api = new_mem_api();
+        let db = api.get_sync_connection().unwrap();
+        let syncer = db.lock();
+
+        syncer
+            .execute("UPDATE moz_bookmarks SET syncChangeCounter = 0", [])
+            .expect("should work");
+
+        let records = vec![
+            json!({
+                "id": "bookmarkXXXX",
+                "type": "bookmark",
+                "parentid": "menu",
+                "parentName": "menu",
+                "dateAdded": 1_552_183_116_885u64,
+                "title": "Invalid",
+                "bmkUri": "invalid url",
+            }),
+            json!({
+                "id": "menu",
+                "type": "folder",
+                "parentid": "places",
+                "parentName": "",
+                "dateAdded": 0,
+                "title": "menu",
+                "children": ["bookmarkXXXX"],
+            }),
+        ];
+
+        // Drop the sync connection to avoid a deadlock when the sync engine locks the mutex
+        drop(syncer);
+        let engine = create_sync_engine(&api);
+
+        let incoming = records
+            .into_iter()
+            .map(IncomingBso::from_test_content)
+            .collect();
+
+        let mut outgoing = engine_apply_incoming(&engine, incoming);
+        outgoing.sort_by(|a, b| a.envelope.id.cmp(&b.envelope.id));
+        assert_eq!(
+            outgoing
+                .iter()
+                .map(|p| p.envelope.id.as_str())
+                .collect::<Vec<_>>(),
+            vec!["bookmarkXXXX", "menu",]
+        );
+
+        let record_for_invalid = outgoing
+            .iter()
+            .find(|p| p.envelope.id == "bookmarkXXXX")
+            .expect("Should re-upload the invalid record");
+
+        assert!(
+            matches!(
+                record_for_invalid
+                    .to_test_incoming()
+                    .into_content::<BookmarkRecord>()
+                    .kind,
+                IncomingKind::Tombstone
+            ),
+            "is invalid record"
+        );
+
+        let record_for_menu = outgoing
+            .iter()
+            .find(|p| p.envelope.id == "menu")
+            .expect("Should upload menu");
+        let content_for_menu = record_for_menu.to_test_incoming_t::<FolderRecord>();
+        assert!(
+            content_for_menu.children.is_empty(),
+            "should have been removed from the parent"
+        );
+        Ok(())
+    }
+
+    #[test]
+    fn test_apply_tombstones() -> Result<()> {
+        let local_modified = Timestamp::now();
+        let api = new_mem_api();
+        let writer = api.open_connection(ConnectionType::ReadWrite)?;
+        insert_local_json_tree(
+            &writer,
+            json!({
+                "guid": &BookmarkRootGuid::Unfiled.as_guid(),
+                "children": [{
+                    "guid": "bookmarkAAAA",
+                    "title": "A",
+                    "url": "http://example.com/a",
+                    "date_added": local_modified,
+                    "last_modified": local_modified,
+                }, {
+                    "guid": "separatorAAA",
+                    "type": BookmarkType::Separator as u8,
+                    "date_added": local_modified,
+                    "last_modified": local_modified,
+                }, {
+                    "guid": "folderAAAAAA",
+                    "children": [{
+                        "guid": "bookmarkBBBB",
+                        "title": "b",
+                        "url": "http://example.com/b",
+                        "date_added": local_modified,
+                        "last_modified": local_modified,
+                    }],
+                }],
+            }),
+        );
+        // a first sync, which will populate our mirror.
+        let engine = create_sync_engine(&api);
+        let outgoing = engine_apply_incoming(&engine, vec![]);
+        let outgoing_ids = outgoing
+            .iter()
+            .map(|p| p.envelope.id.clone())
+            .collect::<Vec<_>>();
+        // 4 roots + 4 items
+        assert_eq!(outgoing_ids.len(), 8, "{:?}", outgoing_ids);
+
+        engine
+            .set_uploaded(ServerTimestamp(0), outgoing_ids)
+            .expect("should work");
+        engine.sync_finished().expect("should work");
+
+        // Now the next sync with incoming tombstones.
+        let remote_unfiled = json!({
+            "id": "unfiled",
+            "type": "folder",
+            "parentid": "places",
+            "title": "Unfiled",
+            "children": [],
+        });
+
+        let incoming = vec![
+            IncomingBso::new_test_tombstone(Guid::new("bookmarkAAAA")),
+            IncomingBso::new_test_tombstone(Guid::new("separatorAAA")),
+            IncomingBso::new_test_tombstone(Guid::new("folderAAAAAA")),
+            IncomingBso::new_test_tombstone(Guid::new("bookmarkBBBB")),
+            IncomingBso::from_test_content(remote_unfiled),
+        ];
+
+        let outgoing = engine_apply_incoming(&engine, incoming);
+        let outgoing_ids = outgoing
+            .iter()
+            .map(|p| p.envelope.id.clone())
+            .collect::<Vec<_>>();
+        assert_eq!(outgoing_ids.len(), 0, "{:?}", outgoing_ids);
+
+        engine
+            .set_uploaded(ServerTimestamp(0), outgoing_ids)
+            .expect("should work");
+        engine.sync_finished().expect("should work");
+
+        // We deleted everything from unfiled.
+        assert_local_json_tree(
+            &api.get_sync_connection().unwrap().lock(),
+            &BookmarkRootGuid::Unfiled.as_guid(),
+            json!({"children" : []}),
+        );
+        Ok(())
+    }
+
+    #[test]
+    fn test_keywords() -> Result<()> {
+        use crate::storage::bookmarks::bookmarks_get_url_for_keyword;
+
+        let api = new_mem_api();
+        let writer = api.open_connection(ConnectionType::ReadWrite)?;
+
+        let records = vec![
+            json!({
+                "id": "toolbar",
+                "type": "folder",
+                "parentid": "places",
+                "parentName": "",
+                "dateAdded": 0,
+                "title": "toolbar",
+                "children": ["bookmarkAAAA"],
+            }),
+            json!({
+                "id": "bookmarkAAAA",
+                "type": "bookmark",
+                "parentid": "toolbar",
+                "parentName": "toolbar",
+                "dateAdded": 1_552_183_116_885u64,
+                "title": "A",
+                "bmkUri": "http://example.com/a/%s",
+                "keyword": "a",
+            }),
+        ];
+
+        let engine = create_sync_engine(&api);
+
+        let incoming = records
+            .into_iter()
+            .map(IncomingBso::from_test_content)
+            .collect();
+
+        let outgoing = engine_apply_incoming(&engine, incoming);
+        let mut outgoing_ids = outgoing
+            .iter()
+            .map(|p| p.envelope.id.clone())
+            .collect::<Vec<_>>();
+        outgoing_ids.sort();
+        assert_eq!(outgoing_ids, &["menu", "mobile", "toolbar", "unfiled"],);
+
+        assert_eq!(
+            bookmarks_get_url_for_keyword(&writer, "a")?,
+            Some(Url::parse("http://example.com/a/%s")?)
+        );
+
+        engine
+            .set_uploaded(ServerTimestamp(0), outgoing_ids)
+            .expect("Should push synced changes back to the engine");
+        engine.sync_finished().expect("should work");
+
+        update_bookmark(
+            &writer,
+            &"bookmarkAAAA".into(),
+            &UpdatableBookmark {
+                title: Some("A (local)".into()),
+                ..UpdatableBookmark::default()
+            }
+            .into(),
+        )?;
+
+        let outgoing = engine_apply_incoming(&engine, vec![]);
+        assert_eq!(outgoing.len(), 1);
+        let bk = outgoing[0].to_test_incoming_t::<BookmarkRecord>();
+        assert_eq!(bk.record_id.as_guid(), "bookmarkAAAA");
+        assert_eq!(bk.keyword.unwrap(), "a");
+        assert_eq!(bk.url.unwrap(), "http://example.com/a/%s");
+
+        // URLs with keywords should have a foreign count of 3 (one for the
+        // local bookmark, one for the synced bookmark, and one for the
+        // keyword), and we shouldn't allow deleting them until the keyword
+        // is removed.
+        let foreign_count = writer
+            .try_query_row(
+                "SELECT foreign_count FROM moz_places
+             WHERE url_hash = hash(:url) AND
+                   url = :url",
+                &[(":url", &"http://example.com/a/%s")],
+                |row| -> rusqlite::Result<_> { row.get::<_, i64>(0) },
+                false,
+            )?
+            .expect("Should fetch foreign count for URL A");
+        assert_eq!(foreign_count, 3);
+        let err = writer
+            .execute(
+                "DELETE FROM moz_places
+             WHERE url_hash = hash(:url) AND
+                   url = :url",
+                rusqlite::named_params! {
+                    ":url": "http://example.com/a/%s",
+                },
+            )
+            .expect_err("Should fail to delete URL A with keyword");
+        match err {
+            RusqlError::SqliteFailure(e, _) => assert_eq!(e.code, ErrorCode::ConstraintViolation),
+            _ => panic!("Wanted constraint violation error; got {:?}", err),
+        }
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_apply_complex_bookmark_keywords() -> Result<()> {
+        use crate::storage::bookmarks::bookmarks_get_url_for_keyword;
+
+        // We don't provide an API for setting keywords locally, but we'll
+        // still round-trip and fix up keywords on the server.
+
+        let api = new_mem_api();
+        let writer = api.open_connection(ConnectionType::ReadWrite)?;
+
+        // Let's add some remote bookmarks with keywords.
+        let remote_records = json!([{
+            // A1 and A2 have the same URL and keyword, so we shouldn't
+            // reupload them.
+            "id": "bookmarkAAA1",
+            "type": "bookmark",
+            "parentid": "unfiled",
+            "parentName": "Unfiled",
+            "title": "A1",
+            "bmkUri": "http://example.com/a",
+            "keyword": "one",
+        }, {
+            "id": "bookmarkAAA2",
+            "type": "bookmark",
+            "parentid": "menu",
+            "parentName": "Menu",
+            "title": "A2",
+            "bmkUri": "http://example.com/a",
+            "keyword": "one",
+        }, {
+            // B1 and B2 have mismatched keywords, and we should reupload
+            // both of them. It's not specified which keyword wins, but
+            // reuploading both means we make them consistent.
+            "id": "bookmarkBBB1",
+            "type": "bookmark",
+            "parentid": "unfiled",
+            "parentName": "Unfiled",
+            "title": "B1",
+            "bmkUri": "http://example.com/b",
+            "keyword": "two",
+        }, {
+            "id": "bookmarkBBB2",
+            "type": "bookmark",
+            "parentid": "menu",
+            "parentName": "Menu",
+            "title": "B2",
+            "bmkUri": "http://example.com/b",
+            "keyword": "three",
+        }, {
+            // C1 has a keyword; C2 doesn't. As with B, which one wins
+            // depends on which record we apply last, and how SQLite
+            // processes the rows, but we should reupload both.
+            "id": "bookmarkCCC1",
+            "type": "bookmark",
+            "parentid": "unfiled",
+            "parentName": "Unfiled",
+            "title": "C1",
+            "bmkUri": "http://example.com/c",
+            "keyword": "four",
+        }, {
+            "id": "bookmarkCCC2",
+            "type": "bookmark",
+            "parentid": "menu",
+            "parentName": "Menu",
+            "title": "C2",
+            "bmkUri": "http://example.com/c",
+        }, {
+            // D has a keyword that needs to be cleaned up before
+            // inserting. In this case, we intentionally don't reupload.
+            "id": "bookmarkDDDD",
+            "type": "bookmark",
+            "parentid": "unfiled",
+            "parentName": "Unfiled",
+            "title": "D",
+            "bmkUri": "http://example.com/d",
+            "keyword": " FIVE ",
+        }, {
+            "id": "unfiled",
+            "type": "folder",
+            "parentid": "places",
+            "title": "Unfiled",
+            "children": ["bookmarkAAA1", "bookmarkBBB1", "bookmarkCCC1", "bookmarkDDDD"],
+        }, {
+            "id": "menu",
+            "type": "folder",
+            "parentid": "places",
+            "title": "Menu",
+            "children": ["bookmarkAAA2", "bookmarkBBB2", "bookmarkCCC2"],
+        }]);
+
+        let engine = create_sync_engine(&api);
+        let incoming = if let Value::Array(records) = remote_records {
+            records
+                .into_iter()
+                .map(IncomingBso::from_test_content)
+                .collect()
+        } else {
+            unreachable!("JSON records must be an array");
+        };
+        let mut outgoing = engine_apply_incoming(&engine, incoming);
+        outgoing.sort_by(|a, b| a.envelope.id.cmp(&b.envelope.id));
+
+        assert_local_json_tree(
+            &writer,
+            &BookmarkRootGuid::Root.as_guid(),
+            json!({
+                "guid": &BookmarkRootGuid::Root.as_guid(),
+                "children": [{
+                    "guid": &BookmarkRootGuid::Menu.as_guid(),
+                    "children": [{
+                        "guid": "bookmarkAAA2",
+                        "title": "A2",
+                        "url": "http://example.com/a",
+                    }, {
+                        "guid": "bookmarkBBB2",
+                        "title": "B2",
+                        "url": "http://example.com/b",
+                    }, {
+                        "guid": "bookmarkCCC2",
+                        "title": "C2",
+                        "url": "http://example.com/c",
+                    }],
+                }, {
+                    "guid": &BookmarkRootGuid::Toolbar.as_guid(),
+                    "children": [],
+                }, {
+                    "guid": &BookmarkRootGuid::Unfiled.as_guid(),
+                    "children": [{
+                        "guid": "bookmarkAAA1",
+                        "title": "A1",
+                        "url": "http://example.com/a",
+                    }, {
+                        "guid": "bookmarkBBB1",
+                        "title": "B1",
+                        "url": "http://example.com/b",
+                    }, {
+                        "guid": "bookmarkCCC1",
+                        "title": "C1",
+                        "url": "http://example.com/c",
+                    }, {
+                        "guid": "bookmarkDDDD",
+                        "title": "D",
+                        "url": "http://example.com/d",
+                    }],
+                }, {
+                    "guid": &BookmarkRootGuid::Mobile.as_guid(),
+                    "children": [],
+                }],
+            }),
+        );
+        // And verify our local keywords are correct, too.
+        let url_for_one = bookmarks_get_url_for_keyword(&writer, "one")?
+            .expect("Should have URL for keyword `one`");
+        assert_eq!(url_for_one.as_str(), "http://example.com/a");
+
+        let keyword_for_b = match (
+            bookmarks_get_url_for_keyword(&writer, "two")?,
+            bookmarks_get_url_for_keyword(&writer, "three")?,
+        ) {
+            (Some(url), None) => {
+                assert_eq!(url.as_str(), "http://example.com/b");
+                "two".to_string()
+            }
+            (None, Some(url)) => {
+                assert_eq!(url.as_str(), "http://example.com/b");
+                "three".to_string()
+            }
+            (Some(_), Some(_)) => panic!("Should pick `two` or `three`, not both"),
+            (None, None) => panic!("Should have URL for either `two` or `three`"),
+        };
+
+        let keyword_for_c = match bookmarks_get_url_for_keyword(&writer, "four")? {
+            Some(url) => {
+                assert_eq!(url.as_str(), "http://example.com/c");
+                Some("four".to_string())
+            }
+            None => None,
+        };
+
+        let url_for_five = bookmarks_get_url_for_keyword(&writer, "five")?
+            .expect("Should have URL for keyword `five`");
+        assert_eq!(url_for_five.as_str(), "http://example.com/d");
+
+        let expected_outgoing_keywords = &[
+            ("bookmarkBBB1", Some(keyword_for_b.clone())),
+            ("bookmarkBBB2", Some(keyword_for_b.clone())),
+            ("bookmarkCCC1", keyword_for_c.clone()),
+            ("bookmarkCCC2", keyword_for_c.clone()),
+            ("menu", None), // Roots always get uploaded on the first sync.
+            ("mobile", None),
+            ("toolbar", None),
+            ("unfiled", None),
+        ];
+        assert_eq!(
+            outgoing
+                .iter()
+                .map(|p| (
+                    p.envelope.id.as_str(),
+                    p.to_test_incoming_t::<BookmarkRecord>().keyword
+                ))
+                .collect::<Vec<_>>(),
+            expected_outgoing_keywords,
+            "Should upload new bookmarks and fix up keywords",
+        );
+
+        // Now push the records back to the engine, so we can check what we're
+        // uploading.
+        engine
+            .set_uploaded(
+                ServerTimestamp(0),
+                expected_outgoing_keywords
+                    .iter()
+                    .map(|(id, _)| SyncGuid::from(id))
+                    .collect(),
+            )
+            .expect("Should push synced changes back to the engine");
+        engine.sync_finished().expect("should work");
+
+        let mut synced_item_for_b = SyncedBookmarkItem::new();
+        synced_item_for_b
+            .validity(SyncedBookmarkValidity::Valid)
+            .kind(SyncedBookmarkKind::Bookmark)
+            .url(Some("http://example.com/b"))
+            .keyword(Some(&keyword_for_b));
+        let mut synced_item_for_c = SyncedBookmarkItem::new();
+        synced_item_for_c
+            .validity(SyncedBookmarkValidity::Valid)
+            .kind(SyncedBookmarkKind::Bookmark)
+            .url(Some("http://example.com/c"))
+            .keyword(Some(keyword_for_c.unwrap().as_str()));
+        let expected_synced_items = &[
+            ExpectedSyncedItem::with_properties("bookmarkBBB1", &synced_item_for_b, |a| {
+                a.parent_guid(Some(&BookmarkRootGuid::Unfiled.as_guid()))
+                    .title(Some("B1"))
+            }),
+            ExpectedSyncedItem::with_properties("bookmarkBBB2", &synced_item_for_b, |a| {
+                a.parent_guid(Some(&BookmarkRootGuid::Menu.as_guid()))
+                    .title(Some("B2"))
+            }),
+            ExpectedSyncedItem::with_properties("bookmarkCCC1", &synced_item_for_c, |a| {
+                a.parent_guid(Some(&BookmarkRootGuid::Unfiled.as_guid()))
+                    .title(Some("C1"))
+            }),
+            ExpectedSyncedItem::with_properties("bookmarkCCC2", &synced_item_for_c, |a| {
+                a.parent_guid(Some(&BookmarkRootGuid::Menu.as_guid()))
+                    .title(Some("C2"))
+            }),
+        ];
+        for item in expected_synced_items {
+            item.check(&writer)?;
+        }
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_wipe() -> Result<()> {
+        let api = new_mem_api();
+        let writer = api.open_connection(ConnectionType::ReadWrite)?;
+
+        let records = vec![
+            json!({
+                "id": "toolbar",
+                "type": "folder",
+                "parentid": "places",
+                "parentName": "",
+                "dateAdded": 0,
+                "title": "toolbar",
+                "children": ["folderAAAAAA"],
+            }),
+            json!({
+                "id": "folderAAAAAA",
+                "type": "folder",
+                "parentid": "toolbar",
+                "parentName": "toolbar",
+                "dateAdded": 0,
+                "title": "A",
+                "children": ["bookmarkBBBB"],
+            }),
+            json!({
+                "id": "bookmarkBBBB",
+                "type": "bookmark",
+                "parentid": "folderAAAAAA",
+                "parentName": "A",
+                "dateAdded": 0,
+                "title": "A",
+                "bmkUri": "http://example.com/a",
+            }),
+            json!({
+                "id": "menu",
+                "type": "folder",
+                "parentid": "places",
+                "parentName": "",
+                "dateAdded": 0,
+                "title": "menu",
+                "children": ["folderCCCCCC"],
+            }),
+            json!({
+                "id": "folderCCCCCC",
+                "type": "folder",
+                "parentid": "menu",
+                "parentName": "menu",
+                "dateAdded": 0,
+                "title": "A",
+                "children": ["bookmarkDDDD", "folderEEEEEE"],
+            }),
+            json!({
+                "id": "bookmarkDDDD",
+                "type": "bookmark",
+                "parentid": "folderCCCCCC",
+                "parentName": "C",
+                "dateAdded": 0,
+                "title": "D",
+                "bmkUri": "http://example.com/d",
+            }),
+            json!({
+                "id": "folderEEEEEE",
+                "type": "folder",
+                "parentid": "folderCCCCCC",
+                "parentName": "C",
+                "dateAdded": 0,
+                "title": "E",
+                "children": ["bookmarkFFFF"],
+            }),
+            json!({
+                "id": "bookmarkFFFF",
+                "type": "bookmark",
+                "parentid": "folderEEEEEE",
+                "parentName": "E",
+                "dateAdded": 0,
+                "title": "F",
+                "bmkUri": "http://example.com/f",
+            }),
+        ];
+
+        let engine = create_sync_engine(&api);
+
+        let incoming = records
+            .into_iter()
+            .map(IncomingBso::from_test_content)
+            .collect();
+
+        let outgoing = engine_apply_incoming(&engine, incoming);
+        let mut outgoing_ids = outgoing
+            .iter()
+            .map(|p| p.envelope.id.clone())
+            .collect::<Vec<_>>();
+        outgoing_ids.sort();
+        assert_eq!(outgoing_ids, &["menu", "mobile", "toolbar", "unfiled"],);
+
+        engine
+            .set_uploaded(ServerTimestamp(0), outgoing_ids)
+            .expect("Should push synced changes back to the engine");
+        engine.sync_finished().expect("should work");
+
+        engine.wipe().expect("Should wipe the store");
+
+        // Wiping the store should delete all items except for the roots.
+        assert_local_json_tree(
+            &writer,
+            &BookmarkRootGuid::Root.as_guid(),
+            json!({
+                "guid": &BookmarkRootGuid::Root.as_guid(),
+                "children": [
+                    {
+                        "guid": &BookmarkRootGuid::Menu.as_guid(),
+                        "children": [],
+                    },
+                    {
+                        "guid": &BookmarkRootGuid::Toolbar.as_guid(),
+                        "children": [],
+                    },
+                    {
+                        "guid": &BookmarkRootGuid::Unfiled.as_guid(),
+                        "children": [],
+                    },
+                    {
+                        "guid": &BookmarkRootGuid::Mobile.as_guid(),
+                        "children": [],
+                    },
+                ],
+            }),
+        );
+
+        // Now pretend that F changed remotely between the time we called `wipe`
+        // and the next sync.
+        let record_for_f = json!({
+            "id": "bookmarkFFFF",
+            "type": "bookmark",
+            "parentid": "folderEEEEEE",
+            "parentName": "E",
+            "dateAdded": 0,
+            "title": "F (remote)",
+            "bmkUri": "http://example.com/f-remote",
+        });
+
+        let incoming = vec![IncomingBso::from_test_content_ts(
+            record_for_f,
+            ServerTimestamp(1000),
+        )];
+
+        let outgoing = engine_apply_incoming(&engine, incoming);
+        let (outgoing_tombstones, outgoing_records): (Vec<_>, Vec<_>) =
+            outgoing.iter().partition(|record| {
+                matches!(
+                    record
+                        .to_test_incoming()
+                        .into_content::<BookmarkRecord>()
+                        .kind,
+                    IncomingKind::Tombstone
+                )
+            });
+        let mut outgoing_record_ids = outgoing_records
+            .iter()
+            .map(|p| p.envelope.id.as_str())
+            .collect::<Vec<_>>();
+        outgoing_record_ids.sort_unstable();
+        assert_eq!(
+            outgoing_record_ids,
+            &["bookmarkFFFF", "menu", "mobile", "toolbar", "unfiled"],
+        );
+        let mut outgoing_tombstone_ids = outgoing_tombstones
+            .iter()
+            .map(|p| p.envelope.id.clone())
+            .collect::<Vec<_>>();
+        outgoing_tombstone_ids.sort();
+        assert_eq!(
+            outgoing_tombstone_ids,
+            &[
+                "bookmarkBBBB",
+                "bookmarkDDDD",
+                "folderAAAAAA",
+                "folderCCCCCC",
+                "folderEEEEEE"
+            ]
+        );
+
+        // F should move to the closest surviving ancestor, which, in this case,
+        // is the menu.
+        assert_local_json_tree(
+            &writer,
+            &BookmarkRootGuid::Root.as_guid(),
+            json!({
+                "guid": &BookmarkRootGuid::Root.as_guid(),
+                "children": [
+                    {
+                        "guid": &BookmarkRootGuid::Menu.as_guid(),
+                        "children": [
+                            {
+                                "guid": "bookmarkFFFF",
+                                "title": "F (remote)",
+                                "url": "http://example.com/f-remote",
+                            },
+                        ],
+                    },
+                    {
+                        "guid": &BookmarkRootGuid::Toolbar.as_guid(),
+                        "children": [],
+                    },
+                    {
+                        "guid": &BookmarkRootGuid::Unfiled.as_guid(),
+                        "children": [],
+                    },
+                    {
+                        "guid": &BookmarkRootGuid::Mobile.as_guid(),
+                        "children": [],
+                    },
+                ],
+            }),
+        );
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_reset() -> anyhow::Result<()> {
+        let api = new_mem_api();
+        let writer = api.open_connection(ConnectionType::ReadWrite)?;
+
+        insert_local_json_tree(
+            &writer,
+            json!({
+                "guid": &BookmarkRootGuid::Menu.as_guid(),
+                "children": [
+                    {
+                        "guid": "bookmark2___",
+                        "title": "2",
+                        "url": "http://example.com/2",
+                    }
+                ],
+            }),
+        );
+
+        {
+            // scope to kill our sync connection.
+            let engine = create_sync_engine(&api);
+
+            assert_eq!(
+                engine.get_sync_assoc()?,
+                EngineSyncAssociation::Disconnected
+            );
+
+            let outgoing = engine_apply_incoming(&engine, vec![]);
+            let synced_ids: Vec<Guid> = outgoing.into_iter().map(|c| c.envelope.id).collect();
+            assert_eq!(synced_ids.len(), 5, "should be 4 roots + 1 outgoing item");
+            engine.set_uploaded(ServerTimestamp(2_000), synced_ids)?;
+            engine.sync_finished().expect("should work");
+
+            let db = api.get_sync_connection().unwrap();
+            let syncer = db.lock();
+            assert_eq!(get_meta::<i64>(&syncer, LAST_SYNC_META_KEY)?, Some(2_000));
+
+            let sync_ids = CollSyncIds {
+                global: Guid::random(),
+                coll: Guid::random(),
+            };
+            // Temporarily drop the sync connection to avoid a deadlock when the sync engine locks
+            // the mutex
+            drop(syncer);
+            engine.reset(&EngineSyncAssociation::Connected(sync_ids.clone()))?;
+            let syncer = db.lock();
+            assert_eq!(
+                get_meta::<Guid>(&syncer, GLOBAL_SYNCID_META_KEY)?,
+                Some(sync_ids.global)
+            );
+            assert_eq!(
+                get_meta::<Guid>(&syncer, COLLECTION_SYNCID_META_KEY)?,
+                Some(sync_ids.coll)
+            );
+            assert_eq!(get_meta::<i64>(&syncer, LAST_SYNC_META_KEY)?, Some(0));
+        }
+        // do it all again - after the reset we should get the same results.
+        {
+            let engine = create_sync_engine(&api);
+
+            let outgoing = engine_apply_incoming(&engine, vec![]);
+            let synced_ids: Vec<Guid> = outgoing.into_iter().map(|c| c.envelope.id).collect();
+            assert_eq!(synced_ids.len(), 5, "should be 4 roots + 1 outgoing item");
+            engine.set_uploaded(ServerTimestamp(2_000), synced_ids)?;
+            engine.sync_finished().expect("should work");
+
+            let db = api.get_sync_connection().unwrap();
+            let syncer = db.lock();
+            assert_eq!(get_meta::<i64>(&syncer, LAST_SYNC_META_KEY)?, Some(2_000));
+
+            // Temporarily drop the sync connection to avoid a deadlock when the sync engine locks
+            // the mutex
+            drop(syncer);
+            engine.reset(&EngineSyncAssociation::Disconnected)?;
+            let syncer = db.lock();
+            assert_eq!(
+                get_meta::<Option<String>>(&syncer, GLOBAL_SYNCID_META_KEY)?,
+                None
+            );
+            assert_eq!(
+                get_meta::<Option<String>>(&syncer, COLLECTION_SYNCID_META_KEY)?,
+                None
+            );
+            assert_eq!(get_meta::<i64>(&syncer, LAST_SYNC_META_KEY)?, Some(0));
+        }
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_dedupe_local_newer() -> anyhow::Result<()> {
+        let api = new_mem_api();
+        let writer = api.open_connection(ConnectionType::ReadWrite)?;
+
+        let local_modified = Timestamp::now();
+        let remote_modified = local_modified.as_millis() as f64 / 1000f64 - 5f64;
+
+        // Start with merged items.
+        apply_incoming(
+            &api,
+            ServerTimestamp::from_float_seconds(remote_modified),
+            json!([{
+                "id": "menu",
+                "type": "folder",
+                "parentid": "places",
+                "parentName": "",
+                "title": "menu",
+                "children": ["bookmarkAAA5"],
+                "modified": remote_modified,
+            }, {
+                "id": "bookmarkAAA5",
+                "type": "bookmark",
+                "parentid": "menu",
+                "parentName": "menu",
+                "title": "A",
+                "bmkUri": "http://example.com/a",
+                "modified": remote_modified,
+            }]),
+        );
+
+        // Add newer local dupes.
+        insert_local_json_tree(
+            &writer,
+            json!({
+                "guid": &BookmarkRootGuid::Menu.as_guid(),
+                "children": [{
+                    "guid": "bookmarkAAA1",
+                    "title": "A",
+                    "url": "http://example.com/a",
+                    "date_added": local_modified,
+                    "last_modified": local_modified,
+                }, {
+                    "guid": "bookmarkAAA2",
+                    "title": "A",
+                    "url": "http://example.com/a",
+                    "date_added": local_modified,
+                    "last_modified": local_modified,
+                }, {
+                    "guid": "bookmarkAAA3",
+                    "title": "A",
+                    "url": "http://example.com/a",
+                    "date_added": local_modified,
+                    "last_modified": local_modified,
+                }],
+            }),
+        );
+
+        // Add older remote dupes.
+        apply_incoming(
+            &api,
+            ServerTimestamp(local_modified.as_millis() as i64),
+            json!([{
+                "id": "menu",
+                "type": "folder",
+                "parentid": "places",
+                "parentName": "",
+                "title": "menu",
+                "children": ["bookmarkAAAA", "bookmarkAAA4", "bookmarkAAA5"],
+            }, {
+                "id": "bookmarkAAAA",
+                "type": "bookmark",
+                "parentid": "menu",
+                "parentName": "menu",
+                "title": "A",
+                "bmkUri": "http://example.com/a",
+                "modified": remote_modified,
+            }, {
+                "id": "bookmarkAAA4",
+                "type": "bookmark",
+                "parentid": "menu",
+                "parentName": "menu",
+                "title": "A",
+                "bmkUri": "http://example.com/a",
+                "modified": remote_modified,
+            }]),
+        );
+
+        assert_local_json_tree(
+            &writer,
+            &BookmarkRootGuid::Menu.as_guid(),
+            json!({
+                "guid": &BookmarkRootGuid::Menu.as_guid(),
+                "children": [{
+                    "guid": "bookmarkAAAA",
+                    "title": "A",
+                    "url": "http://example.com/a",
+                }, {
+                    "guid": "bookmarkAAA4",
+                    "title": "A",
+                    "url": "http://example.com/a",
+                }, {
+                    "guid": "bookmarkAAA5",
+                    "title": "A",
+                    "url": "http://example.com/a",
+                }, {
+                    "guid": "bookmarkAAA3",
+                    "title": "A",
+                    "url": "http://example.com/a",
+                }],
+            }),
+        );
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_deduping_remote_newer() -> anyhow::Result<()> {
+        let api = new_mem_api();
+        let writer = api.open_connection(ConnectionType::ReadWrite)?;
+
+        let local_modified = Timestamp::from(Timestamp::now().as_millis() - 5000);
+        let remote_modified = local_modified.as_millis() as f64 / 1000f64;
+
+        // Start with merged items.
+        apply_incoming(
+            &api,
+            ServerTimestamp::from_float_seconds(remote_modified),
+            json!([{
+                "id": "menu",
+                "type": "folder",
+                "parentid": "places",
+                "parentName": "",
+                "title": "menu",
+                "children": ["folderAAAAAA"],
+                "modified": remote_modified,
+            }, {
+                // Shouldn't dedupe to `folderA11111` because it's been applied.
+                "id": "folderAAAAAA",
+                "type": "folder",
+                "parentid": "menu",
+                "parentName": "menu",
+                "title": "A",
+                "children": ["bookmarkGGGG"],
+                "modified": remote_modified,
+            }, {
+                // Shouldn't dedupe to `bookmarkG111`.
+                "id": "bookmarkGGGG",
+                "type": "bookmark",
+                "parentid": "folderAAAAAA",
+                "parentName": "A",
+                "title": "G",
+                "bmkUri": "http://example.com/g",
+                "modified": remote_modified,
+            }]),
+        );
+
+        // Add older local dupes.
+        insert_local_json_tree(
+            &writer,
+            json!({
+                "guid": "folderAAAAAA",
+                "children": [{
+                    // Not a candidate for `bookmarkH111` because we didn't dupe `folderAAAAAA`.
+                    "guid": "bookmarkHHHH",
+                    "title": "H",
+                    "url": "http://example.com/h",
+                    "date_added": local_modified,
+                    "last_modified": local_modified,
+                }]
+            }),
+        );
+        insert_local_json_tree(
+            &writer,
+            json!({
+                "guid": &BookmarkRootGuid::Menu.as_guid(),
+                "children": [{
+                    // Should dupe to `folderB11111`.
+                    "guid": "folderBBBBBB",
+                    "type": BookmarkType::Folder as u8,
+                    "title": "B",
+                    "date_added": local_modified,
+                    "last_modified": local_modified,
+                    "children": [{
+                        // Should dupe to `bookmarkC222`.
+                        "guid": "bookmarkC111",
+                        "title": "C",
+                        "url": "http://example.com/c",
+                        "date_added": local_modified,
+                        "last_modified": local_modified,
+                    }, {
+                        // Should dupe to `separatorF11` because the positions are the same.
+                        "guid": "separatorFFF",
+                        "type": BookmarkType::Separator as u8,
+                        "date_added": local_modified,
+                        "last_modified": local_modified,
+                    }],
+                }, {
+                    // Shouldn't dupe to `separatorE11`, because the positions are different.
+                    "guid": "separatorEEE",
+                    "type": BookmarkType::Separator as u8,
+                    "date_added": local_modified,
+                    "last_modified": local_modified,
+                }, {
+                    // Shouldn't dupe to `bookmarkC222` because the parents are different.
+                    "guid": "bookmarkCCCC",
+                    "title": "C",
+                    "url": "http://example.com/c",
+                    "date_added": local_modified,
+                    "last_modified": local_modified,
+                }, {
+                    // Should dupe to `queryD111111`.
+                    "guid": "queryDDDDDDD",
+                    "title": "Most Visited",
+                    "url": "place:maxResults=10&sort=8",
+                    "date_added": local_modified,
+                    "last_modified": local_modified,
+                }],
+            }),
+        );
+
+        // Add newer remote items.
+        apply_incoming(
+            &api,
+            ServerTimestamp::from_float_seconds(remote_modified),
+            json!([{
+                "id": "menu",
+                "type": "folder",
+                "parentid": "places",
+                "parentName": "",
+                "title": "menu",
+                "children": ["folderAAAAAA", "folderB11111", "folderA11111", "separatorE11", "queryD111111"],
+                "dateAdded": local_modified.as_millis(),
+                "modified": remote_modified + 5f64,
+            }, {
+                "id": "folderB11111",
+                "type": "folder",
+                "parentid": "menu",
+                "parentName": "menu",
+                "title": "B",
+                "children": ["bookmarkC222", "separatorF11"],
+                "dateAdded": local_modified.as_millis(),
+                "modified": remote_modified + 5f64,
+            }, {
+                "id": "bookmarkC222",
+                "type": "bookmark",
+                "parentid": "folderB11111",
+                "parentName": "B",
+                "title": "C",
+                "bmkUri": "http://example.com/c",
+                "dateAdded": local_modified.as_millis(),
+                "modified": remote_modified + 5f64,
+            }, {
+                "id": "separatorF11",
+                "type": "separator",
+                "parentid": "folderB11111",
+                "parentName": "B",
+                "dateAdded": local_modified.as_millis(),
+                "modified": remote_modified + 5f64,
+            }, {
+                "id": "folderA11111",
+                "type": "folder",
+                "parentid": "menu",
+                "parentName": "menu",
+                "title": "A",
+                "children": ["bookmarkG111"],
+                "dateAdded": local_modified.as_millis(),
+                "modified": remote_modified + 5f64,
+            }, {
+                "id": "bookmarkG111",
+                "type": "bookmark",
+                "parentid": "folderA11111",
+                "parentName": "A",
+                "title": "G",
+                "bmkUri": "http://example.com/g",
+                "dateAdded": local_modified.as_millis(),
+                "modified": remote_modified + 5f64,
+            }, {
+                "id": "separatorE11",
+                "type": "separator",
+                "parentid": "folderB11111",
+                "parentName": "B",
+                "dateAdded": local_modified.as_millis(),
+                "modified": remote_modified + 5f64,
+            }, {
+                "id": "queryD111111",
+                "type": "query",
+                "parentid": "menu",
+                "parentName": "menu",
+                "title": "Most Visited",
+                "bmkUri": "place:maxResults=10&sort=8",
+                "dateAdded": local_modified.as_millis(),
+                "modified": remote_modified + 5f64,
+            }]),
+        );
+
+        assert_local_json_tree(
+            &writer,
+            &BookmarkRootGuid::Menu.as_guid(),
+            json!({
+                "guid": &BookmarkRootGuid::Menu.as_guid(),
+                "children": [{
+                    "guid": "folderAAAAAA",
+                    "children": [{
+                        "guid": "bookmarkGGGG",
+                        "title": "G",
+                        "url": "http://example.com/g",
+                    }, {
+                        "guid": "bookmarkHHHH",
+                        "title": "H",
+                        "url": "http://example.com/h",
+                    }]
+                }, {
+                    "guid": "folderB11111",
+                    "children": [{
+                        "guid": "bookmarkC222",
+                        "title": "C",
+                        "url": "http://example.com/c",
+                    }, {
+                        "guid": "separatorF11",
+                        "type": BookmarkType::Separator as u8,
+                    }],
+                }, {
+                    "guid": "folderA11111",
+                    "children": [{
+                        "guid": "bookmarkG111",
+                        "title": "G",
+                        "url": "http://example.com/g",
+                    }]
+                }, {
+                    "guid": "separatorE11",
+                    "type": BookmarkType::Separator as u8,
+                }, {
+                    "guid": "queryD111111",
+                    "title": "Most Visited",
+                    "url": "place:maxResults=10&sort=8",
+                }, {
+                    "guid": "separatorEEE",
+                    "type": BookmarkType::Separator as u8,
+                }, {
+                    "guid": "bookmarkCCCC",
+                    "title": "C",
+                    "url": "http://example.com/c",
+                }],
+            }),
+        );
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_reconcile_sync_metadata() -> anyhow::Result<()> {
+        let api = new_mem_api();
+        let writer = api.open_connection(ConnectionType::ReadWrite)?;
+
+        let local_modified = Timestamp::from(Timestamp::now().as_millis() - 5000);
+        let remote_modified = local_modified.as_millis() as f64 / 1000f64;
+
+        insert_local_json_tree(
+            &writer,
+            json!({
+                "guid": &BookmarkRootGuid::Menu.as_guid(),
+                "children": [{
+                    // this folder is going to reconcile exactly
+                    "guid": "folderAAAAAA",
+                    "type": BookmarkType::Folder as u8,
+                    "title": "A",
+                    "date_added": local_modified,
+                    "last_modified": local_modified,
+                    "children": [{
+                        "guid": "bookmarkBBBB",
+                        "title": "B",
+                        "url": "http://example.com/b",
+                        "date_added": local_modified,
+                        "last_modified": local_modified,
+                    }]
+                }, {
+                    // this folder's existing child isn't on the server (so will be
+                    // outgoing) and also will take a new child from the server.
+                    "guid": "folderCCCCCC",
+                    "type": BookmarkType::Folder as u8,
+                    "title": "C",
+                    "date_added": local_modified,
+                    "last_modified": local_modified,
+                    "children": [{
+                        "guid": "bookmarkEEEE",
+                        "title": "E",
+                        "url": "http://example.com/e",
+                        "date_added": local_modified,
+                        "last_modified": local_modified,
+                    }]
+                }, {
+                    // This bookmark is going to take the remote title.
+                    "guid": "bookmarkFFFF",
+                    "title": "f",
+                    "url": "http://example.com/f",
+                    "date_added": local_modified,
+                    "last_modified": local_modified,
+                }],
+            }),
+        );
+
+        let outgoing = apply_incoming(
+            &api,
+            ServerTimestamp::from_float_seconds(remote_modified),
+            json!([{
+                "id": "menu",
+                "type": "folder",
+                "parentid": "places",
+                "parentName": "",
+                "title": "menu",
+                "children": ["folderAAAAAA", "folderCCCCCC", "bookmarkFFFF"],
+                "dateAdded": local_modified.as_millis(),
+                "modified": remote_modified,
+            }, {
+                "id": "folderAAAAAA",
+                "type": "folder",
+                "parentid": "menu",
+                "parentName": "menu",
+                "title": "A",
+                "children": ["bookmarkBBBB"],
+                "dateAdded": local_modified.as_millis(),
+                "modified": remote_modified,
+            }, {
+                "id": "bookmarkBBBB",
+                "type": "bookmark",
+                "parentid": "folderAAAAAA",
+                "parentName": "A",
+                "title": "B",
+                "bmkUri": "http://example.com/b",
+                "dateAdded": local_modified.as_millis(),
+                "modified": remote_modified,
+            }, {
+                "id": "folderCCCCCC",
+                "type": "folder",
+                "parentid": "menu",
+                "parentName": "menu",
+                "title": "C",
+                "children": ["bookmarkDDDD"],
+                "dateAdded": local_modified.as_millis(),
+                "modified": remote_modified,
+            }, {
+                "id": "bookmarkDDDD",
+                "type": "bookmark",
+                "parentid": "folderCCCCCC",
+                "parentName": "C",
+                "title": "D",
+                "bmkUri": "http://example.com/d",
+                "dateAdded": local_modified.as_millis(),
+                "modified": remote_modified,
+            }, {
+                "id": "bookmarkFFFF",
+                "type": "bookmark",
+                "parentid": "menu",
+                "parentName": "menu",
+                "title": "F",
+                "bmkUri": "http://example.com/f",
+                "dateAdded": local_modified.as_millis(),
+                "modified": remote_modified + 5f64,
+            },]),
+        );
+
+        // Assert the tree is correct even though that's not really the point
+        // of this test.
+        assert_local_json_tree(
+            &writer,
+            &BookmarkRootGuid::Menu.as_guid(),
+            json!({
+                "guid": &BookmarkRootGuid::Menu.as_guid(),
+                "children": [{
+                    // this folder is going to reconcile exactly
+                    "guid": "folderAAAAAA",
+                    "type": BookmarkType::Folder as u8,
+                    "title": "A",
+                    "children": [{
+                        "guid": "bookmarkBBBB",
+                        "title": "B",
+                        "url": "http://example.com/b",
+                    }]
+                }, {
+                    "guid": "folderCCCCCC",
+                    "type": BookmarkType::Folder as u8,
+                    "title": "C",
+                    "children": [{
+                        "guid": "bookmarkDDDD",
+                        "title": "D",
+                        "url": "http://example.com/d",
+                    },{
+                        "guid": "bookmarkEEEE",
+                        "title": "E",
+                        "url": "http://example.com/e",
+                    }]
+                }, {
+                    "guid": "bookmarkFFFF",
+                    "title": "F",
+                    "url": "http://example.com/f",
+                }],
+            }),
+        );
+
+        // After application everything should have SyncStatus::Normal and
+        // a change counter of zero.
+        for guid in &[
+            "folderAAAAAA",
+            "bookmarkBBBB",
+            "folderCCCCCC",
+            "bookmarkDDDD",
+            "bookmarkFFFF",
+        ] {
+            let bm = get_raw_bookmark(&writer, &guid.into())
+                .expect("must work")
+                .expect("must exist");
+            assert_eq!(bm._sync_status, SyncStatus::Normal, "{}", guid);
+            assert_eq!(bm._sync_change_counter, 0, "{}", guid);
+        }
+        // And bookmarkEEEE wasn't on the server, so should be outgoing, and
+        // it's parent too.
+        assert!(outgoing.contains(&"bookmarkEEEE".into()));
+        assert!(outgoing.contains(&"folderCCCCCC".into()));
+        Ok(())
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/places/bookmark_sync/incoming.rs.html b/book/rust-docs/src/places/bookmark_sync/incoming.rs.html new file mode 100644 index 0000000000..b4f7bfc362 --- /dev/null +++ b/book/rust-docs/src/places/bookmark_sync/incoming.rs.html @@ -0,0 +1,1883 @@ +incoming.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
+540
+541
+542
+543
+544
+545
+546
+547
+548
+549
+550
+551
+552
+553
+554
+555
+556
+557
+558
+559
+560
+561
+562
+563
+564
+565
+566
+567
+568
+569
+570
+571
+572
+573
+574
+575
+576
+577
+578
+579
+580
+581
+582
+583
+584
+585
+586
+587
+588
+589
+590
+591
+592
+593
+594
+595
+596
+597
+598
+599
+600
+601
+602
+603
+604
+605
+606
+607
+608
+609
+610
+611
+612
+613
+614
+615
+616
+617
+618
+619
+620
+621
+622
+623
+624
+625
+626
+627
+628
+629
+630
+631
+632
+633
+634
+635
+636
+637
+638
+639
+640
+641
+642
+643
+644
+645
+646
+647
+648
+649
+650
+651
+652
+653
+654
+655
+656
+657
+658
+659
+660
+661
+662
+663
+664
+665
+666
+667
+668
+669
+670
+671
+672
+673
+674
+675
+676
+677
+678
+679
+680
+681
+682
+683
+684
+685
+686
+687
+688
+689
+690
+691
+692
+693
+694
+695
+696
+697
+698
+699
+700
+701
+702
+703
+704
+705
+706
+707
+708
+709
+710
+711
+712
+713
+714
+715
+716
+717
+718
+719
+720
+721
+722
+723
+724
+725
+726
+727
+728
+729
+730
+731
+732
+733
+734
+735
+736
+737
+738
+739
+740
+741
+742
+743
+744
+745
+746
+747
+748
+749
+750
+751
+752
+753
+754
+755
+756
+757
+758
+759
+760
+761
+762
+763
+764
+765
+766
+767
+768
+769
+770
+771
+772
+773
+774
+775
+776
+777
+778
+779
+780
+781
+782
+783
+784
+785
+786
+787
+788
+789
+790
+791
+792
+793
+794
+795
+796
+797
+798
+799
+800
+801
+802
+803
+804
+805
+806
+807
+808
+809
+810
+811
+812
+813
+814
+815
+816
+817
+818
+819
+820
+821
+822
+823
+824
+825
+826
+827
+828
+829
+830
+831
+832
+833
+834
+835
+836
+837
+838
+839
+840
+841
+842
+843
+844
+845
+846
+847
+848
+849
+850
+851
+852
+853
+854
+855
+856
+857
+858
+859
+860
+861
+862
+863
+864
+865
+866
+867
+868
+869
+870
+871
+872
+873
+874
+875
+876
+877
+878
+879
+880
+881
+882
+883
+884
+885
+886
+887
+888
+889
+890
+891
+892
+893
+894
+895
+896
+897
+898
+899
+900
+901
+902
+903
+904
+905
+906
+907
+908
+909
+910
+911
+912
+913
+914
+915
+916
+917
+918
+919
+920
+921
+922
+923
+924
+925
+926
+927
+928
+929
+930
+931
+932
+933
+934
+935
+936
+937
+938
+939
+940
+941
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use super::record::{
+    BookmarkItemRecord, BookmarkRecord, BookmarkRecordId, FolderRecord, LivemarkRecord,
+    QueryRecord, SeparatorRecord,
+};
+use super::{SyncedBookmarkKind, SyncedBookmarkValidity};
+use crate::error::*;
+use crate::storage::{
+    bookmarks::maybe_truncate_title,
+    tags::{validate_tag, ValidatedTag},
+    URL_LENGTH_MAX,
+};
+use crate::types::serialize_unknown_fields;
+use rusqlite::Connection;
+use serde_json::Value as JsonValue;
+use sql_support::{self, ConnExt};
+use std::{collections::HashSet, iter};
+use sync15::bso::{IncomingBso, IncomingKind};
+use sync15::ServerTimestamp;
+use sync_guid::Guid as SyncGuid;
+use url::Url;
+
+// From Desktop's Ci.nsINavHistoryQueryOptions, but we define it as a str
+// as that's how we use it here.
+const RESULTS_AS_TAG_CONTENTS: &str = "7";
+
+/// Manages the application of incoming records into the moz_bookmarks_synced
+/// and related tables.
+pub struct IncomingApplicator<'a> {
+    db: &'a Connection,
+    // For tests to override chunk sizes so they can finish quicker!
+    default_max_variable_number: Option<usize>,
+}
+
+impl<'a> IncomingApplicator<'a> {
+    pub fn new(db: &'a Connection) -> Self {
+        Self {
+            db,
+            default_max_variable_number: None,
+        }
+    }
+
+    pub fn apply_bso(&self, record: IncomingBso) -> Result<()> {
+        let timestamp = record.envelope.modified;
+        let mut validity = SyncedBookmarkValidity::Valid;
+        let json_content = record.into_content_with_fixup::<BookmarkItemRecord>(|json| {
+            validity = fixup_bookmark_json(json)
+        });
+        match json_content.kind {
+            IncomingKind::Tombstone => {
+                self.store_incoming_tombstone(
+                    timestamp,
+                    BookmarkRecordId::from_payload_id(json_content.envelope.id.clone()).as_guid(),
+                )?;
+            }
+            IncomingKind::Content(item) => match item {
+                BookmarkItemRecord::Bookmark(b) => {
+                    self.store_incoming_bookmark(timestamp, &b, validity)?
+                }
+                BookmarkItemRecord::Query(q) => {
+                    self.store_incoming_query(timestamp, &q, validity)?
+                }
+                BookmarkItemRecord::Folder(f) => {
+                    self.store_incoming_folder(timestamp, &f, validity)?
+                }
+                BookmarkItemRecord::Livemark(l) => {
+                    self.store_incoming_livemark(timestamp, &l, validity)?
+                }
+                BookmarkItemRecord::Separator(s) => {
+                    self.store_incoming_sep(timestamp, &s, validity)?
+                }
+            },
+            IncomingKind::Malformed => {
+                log::trace!(
+                    "skipping malformed bookmark record: {}",
+                    json_content.envelope.id
+                );
+                error_support::report_error!(
+                    "malformed-incoming-bookmark",
+                    "Malformed bookmark record"
+                );
+            }
+        }
+        Ok(())
+    }
+
+    fn store_incoming_bookmark(
+        &self,
+        modified: ServerTimestamp,
+        b: &BookmarkRecord,
+        mut validity: SyncedBookmarkValidity,
+    ) -> Result<()> {
+        let url = match self.maybe_store_href(b.url.as_deref()) {
+            Ok(u) => Some(String::from(u)),
+            Err(e) => {
+                log::warn!("Incoming bookmark has an invalid URL: {:?}", e);
+                // The bookmark has an invalid URL, so we can't apply it.
+                set_replace(&mut validity);
+                None
+            }
+        };
+
+        self.db.execute_cached(
+            r#"REPLACE INTO moz_bookmarks_synced(guid, parentGuid, serverModified, needsMerge, kind,
+                                                 dateAdded, title, keyword, validity, unknownFields, placeId)
+               VALUES(:guid, :parentGuid, :serverModified, 1, :kind,
+                      :dateAdded, NULLIF(:title, ""), :keyword, :validity, :unknownFields,
+                      CASE WHEN :url ISNULL
+                      THEN NULL
+                      ELSE (SELECT id FROM moz_places
+                            WHERE url_hash = hash(:url) AND
+                            url = :url)
+                      END
+                      )"#,
+            &[
+                (
+                    ":guid",
+                    &b.record_id.as_guid().as_str() as &dyn rusqlite::ToSql,
+                ),
+                (
+                    ":parentGuid",
+                    &b.parent_record_id.as_ref().map(BookmarkRecordId::as_guid),
+                ),
+                (":serverModified", &modified.as_millis()),
+                (":kind", &SyncedBookmarkKind::Bookmark),
+                (":dateAdded", &b.date_added),
+                (":title", &maybe_truncate_title(&b.title.as_deref())),
+                (":keyword", &b.keyword),
+                (":validity", &validity),
+                (":url", &url),
+                (":unknownFields", &serialize_unknown_fields(&b.unknown_fields)?),
+            ],
+        )?;
+        for t in b.tags.iter() {
+            self.db.execute_cached(
+                "INSERT OR IGNORE INTO moz_tags(tag, lastModified)
+                 VALUES(:tag, now())",
+                &[(":tag", &t)],
+            )?;
+            self.db.execute_cached(
+                "INSERT INTO moz_bookmarks_synced_tag_relation(itemId, tagId)
+                 VALUES((SELECT id FROM moz_bookmarks_synced
+                         WHERE guid = :guid),
+                        (SELECT id FROM moz_tags
+                         WHERE tag = :tag))",
+                &[(":guid", b.record_id.as_guid().as_str()), (":tag", t)],
+            )?;
+        }
+        Ok(())
+    }
+
+    fn store_incoming_folder(
+        &self,
+        modified: ServerTimestamp,
+        f: &FolderRecord,
+        validity: SyncedBookmarkValidity,
+    ) -> Result<()> {
+        self.db.execute_cached(
+            r#"REPLACE INTO moz_bookmarks_synced(guid, parentGuid, serverModified, needsMerge, kind,
+                                                 dateAdded, validity, unknownFields, title)
+               VALUES(:guid, :parentGuid, :serverModified, 1, :kind,
+                      :dateAdded, :validity, :unknownFields, NULLIF(:title, ""))"#,
+            &[
+                (
+                    ":guid",
+                    &f.record_id.as_guid().as_str() as &dyn rusqlite::ToSql,
+                ),
+                (
+                    ":parentGuid",
+                    &f.parent_record_id.as_ref().map(BookmarkRecordId::as_guid),
+                ),
+                (":serverModified", &modified.as_millis()),
+                (":kind", &SyncedBookmarkKind::Folder),
+                (":dateAdded", &f.date_added),
+                (":title", &maybe_truncate_title(&f.title.as_deref())),
+                (":validity", &validity),
+                (
+                    ":unknownFields",
+                    &serialize_unknown_fields(&f.unknown_fields)?,
+                ),
+            ],
+        )?;
+        let default_max_variable_number = self
+            .default_max_variable_number
+            .unwrap_or_else(sql_support::default_max_variable_number);
+        sql_support::each_sized_chunk(
+            &f.children,
+            // -1 because we want to leave an extra binding parameter (`?1`)
+            // for the folder's GUID.
+            default_max_variable_number - 1,
+            |chunk, offset| -> Result<()> {
+                let sql = format!(
+                    "INSERT INTO moz_bookmarks_synced_structure(guid, parentGuid, position)
+                     VALUES {}",
+                    // Builds a fragment like `(?2, ?1, 0), (?3, ?1, 1), ...`,
+                    // where ?1 is the folder's GUID, [?2, ?3] are the first and
+                    // second child GUIDs (SQLite binding parameters index
+                    // from 1), and [0, 1] are the positions. This lets us store
+                    // the folder's children using as few statements as
+                    // possible.
+                    sql_support::repeat_display(chunk.len(), ",", |index, f| {
+                        // Each child's position is its index in `f.children`;
+                        // that is, the `offset` of the current chunk, plus the
+                        // child's `index` within the chunk.
+                        let position = offset + index;
+                        write!(f, "(?{}, ?1, {})", index + 2, position)
+                    })
+                );
+                self.db.execute(
+                    &sql,
+                    rusqlite::params_from_iter(
+                        iter::once(&f.record_id)
+                            .chain(chunk.iter())
+                            .map(|id| id.as_guid().as_str()),
+                    ),
+                )?;
+                Ok(())
+            },
+        )?;
+        Ok(())
+    }
+
+    fn store_incoming_tombstone(&self, modified: ServerTimestamp, guid: &SyncGuid) -> Result<()> {
+        self.db.execute_cached(
+            "REPLACE INTO moz_bookmarks_synced(guid, parentGuid, serverModified, needsMerge,
+                                               dateAdded, isDeleted)
+             VALUES(:guid, NULL, :serverModified, 1, 0, 1)",
+            &[
+                (":guid", guid as &dyn rusqlite::ToSql),
+                (":serverModified", &modified.as_millis()),
+            ],
+        )?;
+        Ok(())
+    }
+
+    fn maybe_rewrite_and_store_query_url(
+        &self,
+        tag_folder_name: Option<&str>,
+        record_id: &BookmarkRecordId,
+        url: Url,
+        validity: &mut SyncedBookmarkValidity,
+    ) -> Result<Option<Url>> {
+        // wow - this  is complex, but markh is struggling to see how to
+        // improve it
+        let maybe_url = {
+            // If the URL has `type={RESULTS_AS_TAG_CONTENTS}` then we
+            // rewrite the URL as `place:tag=...`
+            // Sadly we can't use `url.query_pairs()` here as the format of
+            // the url is, eg, `place:type=7` - ie, the "params" are actually
+            // the path portion of the URL.
+            let parse = url::form_urlencoded::parse(url.path().as_bytes());
+            if parse
+                .clone()
+                .any(|(k, v)| k == "type" && v == RESULTS_AS_TAG_CONTENTS)
+            {
+                if let Some(t) = tag_folder_name {
+                    validate_tag(t)
+                        .ensure_valid()
+                        .and_then(|tag| Ok(Url::parse(&format!("place:tag={}", tag))?))
+                        .map(|url| {
+                            set_reupload(validity);
+                            Some(url)
+                        })
+                        .unwrap_or_else(|_| {
+                            set_replace(validity);
+                            None
+                        })
+                } else {
+                    set_replace(validity);
+                    None
+                }
+            } else {
+                // If we have `folder=...` the folder value is a row_id
+                // from desktop, so useless to us - so we append `&excludeItems=1`
+                // if it isn't already there.
+                if parse.clone().any(|(k, _)| k == "folder") {
+                    if parse.clone().any(|(k, v)| k == "excludeItems" && v == "1") {
+                        Some(url)
+                    } else {
+                        // need to add excludeItems, and I guess we should do
+                        // it properly without resorting to string manipulation...
+                        let tail = url::form_urlencoded::Serializer::new(String::new())
+                            .extend_pairs(parse)
+                            .append_pair("excludeItems", "1")
+                            .finish();
+                        set_reupload(validity);
+                        Some(Url::parse(&format!("place:{}", tail))?)
+                    }
+                } else {
+                    // it appears to be fine!
+                    Some(url)
+                }
+            }
+        };
+        Ok(match self.maybe_store_url(maybe_url) {
+            Ok(url) => Some(url),
+            Err(e) => {
+                log::warn!("query {} has invalid URL: {:?}", record_id.as_guid(), e);
+                set_replace(validity);
+                None
+            }
+        })
+    }
+
+    fn store_incoming_query(
+        &self,
+        modified: ServerTimestamp,
+        q: &QueryRecord,
+        mut validity: SyncedBookmarkValidity,
+    ) -> Result<()> {
+        let url = match q.url.as_ref().and_then(|href| Url::parse(href).ok()) {
+            Some(url) => self.maybe_rewrite_and_store_query_url(
+                q.tag_folder_name.as_deref(),
+                &q.record_id,
+                url,
+                &mut validity,
+            )?,
+            None => {
+                log::warn!("query {} has invalid URL", &q.record_id.as_guid(),);
+                set_replace(&mut validity);
+                None
+            }
+        };
+
+        self.db.execute_cached(
+            r#"REPLACE INTO moz_bookmarks_synced(guid, parentGuid, serverModified, needsMerge, kind,
+                                                 dateAdded, title, validity, unknownFields, placeId)
+               VALUES(:guid, :parentGuid, :serverModified, 1, :kind,
+                      :dateAdded, NULLIF(:title, ""), :validity, :unknownFields,
+                      (SELECT id FROM moz_places
+                            WHERE url_hash = hash(:url) AND
+                            url = :url
+                      )
+                     )"#,
+            &[
+                (
+                    ":guid",
+                    &q.record_id.as_guid().as_str() as &dyn rusqlite::ToSql,
+                ),
+                (
+                    ":parentGuid",
+                    &q.parent_record_id.as_ref().map(BookmarkRecordId::as_guid),
+                ),
+                (":serverModified", &modified.as_millis()),
+                (":kind", &SyncedBookmarkKind::Query),
+                (":dateAdded", &q.date_added),
+                (":title", &maybe_truncate_title(&q.title.as_deref())),
+                (":validity", &validity),
+                (
+                    ":unknownFields",
+                    &serialize_unknown_fields(&q.unknown_fields)?,
+                ),
+                (":url", &url.map(String::from)),
+            ],
+        )?;
+        Ok(())
+    }
+
+    fn store_incoming_livemark(
+        &self,
+        modified: ServerTimestamp,
+        l: &LivemarkRecord,
+        mut validity: SyncedBookmarkValidity,
+    ) -> Result<()> {
+        // livemarks don't store a reference to the place, so we validate it manually.
+        fn validate_href(h: &Option<String>, guid: &SyncGuid, what: &str) -> Option<String> {
+            match h {
+                Some(h) => match Url::parse(h) {
+                    Ok(url) => {
+                        let s = url.to_string();
+                        if s.len() > URL_LENGTH_MAX {
+                            log::warn!("Livemark {} has a {} URL which is too long", &guid, what);
+                            None
+                        } else {
+                            Some(s)
+                        }
+                    }
+                    Err(e) => {
+                        log::warn!("Livemark {} has an invalid {} URL: {:?}", &guid, what, e);
+                        None
+                    }
+                },
+                None => {
+                    log::warn!("Livemark {} has no {} URL", &guid, what);
+                    None
+                }
+            }
+        }
+        let feed_url = validate_href(&l.feed_url, l.record_id.as_guid(), "feed");
+        let site_url = validate_href(&l.site_url, l.record_id.as_guid(), "site");
+
+        if feed_url.is_none() {
+            set_replace(&mut validity);
+        }
+
+        self.db.execute_cached(
+            "REPLACE INTO moz_bookmarks_synced(guid, parentGuid, serverModified, needsMerge, kind,
+                                               dateAdded, title, feedURL, siteURL, validity)
+             VALUES(:guid, :parentGuid, :serverModified, 1, :kind,
+                    :dateAdded, :title, :feedUrl, :siteUrl, :validity)",
+            &[
+                (
+                    ":guid",
+                    &l.record_id.as_guid().as_str() as &dyn rusqlite::ToSql,
+                ),
+                (
+                    ":parentGuid",
+                    &l.parent_record_id.as_ref().map(BookmarkRecordId::as_guid),
+                ),
+                (":serverModified", &modified.as_millis()),
+                (":kind", &SyncedBookmarkKind::Livemark),
+                (":dateAdded", &l.date_added),
+                (":title", &l.title),
+                (":feedUrl", &feed_url),
+                (":siteUrl", &site_url),
+                (":validity", &validity),
+            ],
+        )?;
+        Ok(())
+    }
+
+    fn store_incoming_sep(
+        &self,
+        modified: ServerTimestamp,
+        s: &SeparatorRecord,
+        validity: SyncedBookmarkValidity,
+    ) -> Result<()> {
+        self.db.execute_cached(
+            "REPLACE INTO moz_bookmarks_synced(guid, parentGuid, serverModified, needsMerge, kind,
+                                               dateAdded, validity, unknownFields)
+             VALUES(:guid, :parentGuid, :serverModified, 1, :kind,
+                    :dateAdded, :validity, :unknownFields)",
+            &[
+                (
+                    ":guid",
+                    &s.record_id.as_guid().as_str() as &dyn rusqlite::ToSql,
+                ),
+                (
+                    ":parentGuid",
+                    &s.parent_record_id.as_ref().map(BookmarkRecordId::as_guid),
+                ),
+                (":serverModified", &modified.as_millis()),
+                (":kind", &SyncedBookmarkKind::Separator),
+                (":dateAdded", &s.date_added),
+                (":validity", &validity),
+                (
+                    ":unknownFields",
+                    &serialize_unknown_fields(&s.unknown_fields)?,
+                ),
+            ],
+        )?;
+        Ok(())
+    }
+
+    fn maybe_store_href(&self, href: Option<&str>) -> Result<Url> {
+        if let Some(href) = href {
+            self.maybe_store_url(Some(Url::parse(href)?))
+        } else {
+            self.maybe_store_url(None)
+        }
+    }
+
+    fn maybe_store_url(&self, url: Option<Url>) -> Result<Url> {
+        if let Some(url) = url {
+            if url.as_str().len() > URL_LENGTH_MAX {
+                return Err(Error::InvalidPlaceInfo(InvalidPlaceInfo::UrlTooLong));
+            }
+            self.db.execute_cached(
+                "INSERT OR IGNORE INTO moz_places(guid, url, url_hash, frecency)
+                 VALUES(IFNULL((SELECT guid FROM moz_places
+                                WHERE url_hash = hash(:url) AND
+                                      url = :url),
+                        generate_guid()), :url, hash(:url),
+                        (CASE substr(:url, 1, 6) WHEN 'place:' THEN 0 ELSE -1 END))",
+                &[(":url", &url.as_str())],
+            )?;
+            Ok(url)
+        } else {
+            Err(Error::InvalidPlaceInfo(InvalidPlaceInfo::NoUrl))
+        }
+    }
+}
+
+/// Go through the raw JSON value and try to fixup invalid data -- this mostly means fields with
+/// invalid types.
+///
+/// This is extra important since bookmarks form a tree.  If a parent node is invalid, then we will
+/// have issues trying to merge its children.
+fn fixup_bookmark_json(json: &mut JsonValue) -> SyncedBookmarkValidity {
+    let mut validity = SyncedBookmarkValidity::Valid;
+    // the json value should always be on object, if not don't try to do any fixups.  The result will
+    // be that into_content_with_fixup() returns an IncomingContent with IncomingKind::Malformed.
+    if let JsonValue::Object(ref mut obj) = json {
+        obj.entry("parentid")
+            .and_modify(|v| fixup_optional_str(v, &mut validity));
+        obj.entry("title")
+            .and_modify(|v| fixup_optional_str(v, &mut validity));
+        obj.entry("bmkUri")
+            .and_modify(|v| fixup_optional_str(v, &mut validity));
+        obj.entry("folderName")
+            .and_modify(|v| fixup_optional_str(v, &mut validity));
+        obj.entry("feedUri")
+            .and_modify(|v| fixup_optional_str(v, &mut validity));
+        obj.entry("siteUri")
+            .and_modify(|v| fixup_optional_str(v, &mut validity));
+        obj.entry("dateAdded")
+            .and_modify(|v| fixup_optional_i64(v, &mut validity));
+        obj.entry("keyword")
+            .and_modify(|v| fixup_optional_keyword(v, &mut validity));
+        obj.entry("tags")
+            .and_modify(|v| fixup_optional_tags(v, &mut validity));
+    }
+    validity
+}
+
+fn fixup_optional_str(val: &mut JsonValue, validity: &mut SyncedBookmarkValidity) {
+    if !matches!(val, JsonValue::String(_) | JsonValue::Null) {
+        set_reupload(validity);
+        *val = JsonValue::Null;
+    }
+}
+
+fn fixup_optional_i64(val: &mut JsonValue, validity: &mut SyncedBookmarkValidity) {
+    match val {
+        // There's basically nothing to do for numbers, although we could try to drop any fraction.
+        JsonValue::Number(_) => (),
+        JsonValue::String(s) => {
+            set_reupload(validity);
+            if let Ok(n) = s.parse::<u64>() {
+                *val = JsonValue::Number(n.into());
+            } else {
+                *val = JsonValue::Null;
+            }
+        }
+        JsonValue::Null => (),
+        _ => {
+            set_reupload(validity);
+            *val = JsonValue::Null;
+        }
+    }
+}
+
+// Fixup incoming keywords by lowercasing them and removing surrounding whitespace
+//
+// Like Desktop, we don't reupload if a keyword needs to be fixed-up
+// trailing whitespace, or isn't lowercase.
+fn fixup_optional_keyword(val: &mut JsonValue, validity: &mut SyncedBookmarkValidity) {
+    match val {
+        JsonValue::String(s) => {
+            let fixed = s.trim().to_lowercase();
+            if fixed.is_empty() {
+                *val = JsonValue::Null;
+            } else if fixed != *s {
+                *val = JsonValue::String(fixed);
+            }
+        }
+        JsonValue::Null => (),
+        _ => {
+            set_reupload(validity);
+            *val = JsonValue::Null;
+        }
+    }
+}
+
+fn fixup_optional_tags(val: &mut JsonValue, validity: &mut SyncedBookmarkValidity) {
+    match val {
+        JsonValue::Array(ref tags) => {
+            let mut valid_tags = HashSet::with_capacity(tags.len());
+            for v in tags {
+                if let JsonValue::String(tag) = v {
+                    let tag = match validate_tag(tag) {
+                        ValidatedTag::Invalid(t) => {
+                            log::trace!("Incoming bookmark has invalid tag: {:?}", t);
+                            set_reupload(validity);
+                            continue;
+                        }
+                        ValidatedTag::Normalized(t) => {
+                            set_reupload(validity);
+                            t
+                        }
+                        ValidatedTag::Original(t) => t,
+                    };
+                    if !valid_tags.insert(tag) {
+                        log::trace!("Incoming bookmark has duplicate tag: {:?}", tag);
+                        set_reupload(validity);
+                    }
+                } else {
+                    log::trace!("Incoming bookmark has unexpected tag: {:?}", v);
+                    set_reupload(validity);
+                }
+            }
+            *val = JsonValue::Array(valid_tags.into_iter().map(JsonValue::from).collect());
+        }
+        JsonValue::Null => (),
+        _ => {
+            set_reupload(validity);
+            *val = JsonValue::Null;
+        }
+    }
+}
+
+fn set_replace(validity: &mut SyncedBookmarkValidity) {
+    if *validity < SyncedBookmarkValidity::Replace {
+        *validity = SyncedBookmarkValidity::Replace;
+    }
+}
+
+fn set_reupload(validity: &mut SyncedBookmarkValidity) {
+    if *validity < SyncedBookmarkValidity::Reupload {
+        *validity = SyncedBookmarkValidity::Reupload;
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::api::places_api::{test::new_mem_api, PlacesApi};
+    use crate::bookmark_sync::record::{BookmarkItemRecord, FolderRecord};
+    use crate::bookmark_sync::tests::SyncedBookmarkItem;
+    use crate::storage::bookmarks::BookmarkRootGuid;
+    use crate::types::UnknownFields;
+    use pretty_assertions::assert_eq;
+    use serde_json::{json, Value};
+
+    fn apply_incoming(api: &PlacesApi, records_json: Value) {
+        let db = api.get_sync_connection().expect("should get a db mutex");
+        let conn = db.lock();
+
+        let mut applicator = IncomingApplicator::new(&conn);
+        applicator.default_max_variable_number = Some(5);
+
+        match records_json {
+            Value::Array(records) => {
+                for record in records {
+                    applicator
+                        .apply_bso(IncomingBso::from_test_content(record))
+                        .expect("Should apply incoming and stage outgoing records");
+                }
+            }
+            Value::Object(_) => {
+                applicator
+                    .apply_bso(IncomingBso::from_test_content(records_json))
+                    .expect("Should apply incoming and stage outgoing records");
+            }
+            _ => panic!("unexpected json value"),
+        }
+    }
+
+    fn assert_incoming_creates_mirror_item(record_json: Value, expected: &SyncedBookmarkItem) {
+        let guid = record_json["id"]
+            .as_str()
+            .expect("id must be a string")
+            .to_string();
+        let api = new_mem_api();
+        apply_incoming(&api, record_json);
+        let got = SyncedBookmarkItem::get(&api.get_sync_connection().unwrap().lock(), &guid.into())
+            .expect("should work")
+            .expect("item should exist");
+        assert_eq!(*expected, got);
+    }
+
+    #[test]
+    fn test_apply_bookmark() {
+        assert_incoming_creates_mirror_item(
+            json!({
+                "id": "bookmarkAAAA",
+                "type": "bookmark",
+                "parentid": "unfiled",
+                "parentName": "unfiled",
+                "dateAdded": 1_381_542_355_843u64,
+                "title": "A",
+                "bmkUri": "http://example.com/a",
+                "tags": ["foo", "bar"],
+                "keyword": "baz",
+            }),
+            SyncedBookmarkItem::new()
+                .validity(SyncedBookmarkValidity::Valid)
+                .kind(SyncedBookmarkKind::Bookmark)
+                .parent_guid(Some(&BookmarkRootGuid::Unfiled.as_guid()))
+                .title(Some("A"))
+                .url(Some("http://example.com/a"))
+                .tags(vec!["foo".into(), "bar".into()])
+                .keyword(Some("baz")),
+        );
+    }
+
+    #[test]
+    fn test_apply_folder() {
+        // apply_incoming arranges for the chunk-size to be 5, so to ensure
+        // we exercise the chunking done for folders we only need more than that.
+        let children = (1..6)
+            .map(|i| SyncGuid::from(format!("{:A>12}", i)))
+            .collect::<Vec<_>>();
+        let value = serde_json::to_value(BookmarkItemRecord::from(FolderRecord {
+            record_id: BookmarkRecordId::from_payload_id("folderAAAAAA".into()),
+            parent_record_id: Some(BookmarkRecordId::from_payload_id("unfiled".into())),
+            parent_title: Some("unfiled".into()),
+            date_added: Some(0),
+            has_dupe: true,
+            title: Some("A".into()),
+            children: children
+                .iter()
+                .map(|guid| BookmarkRecordId::from(guid.clone()))
+                .collect(),
+            unknown_fields: UnknownFields::new(),
+        }))
+        .expect("Should serialize folder with children");
+        assert_incoming_creates_mirror_item(
+            value,
+            SyncedBookmarkItem::new()
+                .validity(SyncedBookmarkValidity::Valid)
+                .kind(SyncedBookmarkKind::Folder)
+                .parent_guid(Some(&BookmarkRootGuid::Unfiled.as_guid()))
+                .title(Some("A"))
+                .children(children),
+        );
+    }
+
+    #[test]
+    fn test_apply_tombstone() {
+        assert_incoming_creates_mirror_item(
+            json!({
+                "id": "deadbeef____",
+                "deleted": true
+            }),
+            SyncedBookmarkItem::new()
+                .validity(SyncedBookmarkValidity::Valid)
+                .deleted(true),
+        );
+    }
+
+    #[test]
+    fn test_apply_query() {
+        // First check that various inputs result in the expected records in
+        // the mirror table.
+
+        // A valid query (which actually looks just like a bookmark, but that's ok)
+        assert_incoming_creates_mirror_item(
+            json!({
+                "id": "query1______",
+                "type": "query",
+                "parentid": "unfiled",
+                "parentName": "Unfiled Bookmarks",
+                "dateAdded": 1_381_542_355_843u64,
+                "title": "Some query",
+                "bmkUri": "place:tag=foo",
+            }),
+            SyncedBookmarkItem::new()
+                .validity(SyncedBookmarkValidity::Valid)
+                .kind(SyncedBookmarkKind::Query)
+                .parent_guid(Some(&BookmarkRootGuid::Unfiled.as_guid()))
+                .title(Some("Some query"))
+                .url(Some("place:tag=foo")),
+        );
+
+        // A query with an old "type=" param and a valid folderName. Should
+        // get Reupload due to rewriting the URL.
+        assert_incoming_creates_mirror_item(
+            json!({
+                "id": "query1______",
+                "type": "query",
+                "parentid": "unfiled",
+                "bmkUri": "place:type=7",
+                "folderName": "a-folder-name",
+            }),
+            SyncedBookmarkItem::new()
+                .validity(SyncedBookmarkValidity::Reupload)
+                .kind(SyncedBookmarkKind::Query)
+                .url(Some("place:tag=a-folder-name")),
+        );
+
+        // A query with an old "type=" param and an invalid folderName. Should
+        // get replaced with an empty URL
+        assert_incoming_creates_mirror_item(
+            json!({
+                "id": "query1______",
+                "type": "query",
+                "parentid": "unfiled",
+                "bmkUri": "place:type=7",
+                "folderName": "",
+            }),
+            SyncedBookmarkItem::new()
+                .validity(SyncedBookmarkValidity::Replace)
+                .kind(SyncedBookmarkKind::Query)
+                .url(None),
+        );
+
+        // A query with an old "folder=" but no excludeItems - should be
+        // marked as Reupload due to the URL being rewritten.
+        assert_incoming_creates_mirror_item(
+            json!({
+                "id": "query1______",
+                "type": "query",
+                "parentid": "unfiled",
+                "bmkUri": "place:folder=123",
+            }),
+            SyncedBookmarkItem::new()
+                .validity(SyncedBookmarkValidity::Reupload)
+                .kind(SyncedBookmarkKind::Query)
+                .url(Some("place:folder=123&excludeItems=1")),
+        );
+
+        // A query with an old "folder=" and already with  excludeItems -
+        // should be marked as Valid
+        assert_incoming_creates_mirror_item(
+            json!({
+                "id": "query1______",
+                "type": "query",
+                "parentid": "unfiled",
+                "bmkUri": "place:folder=123&excludeItems=1",
+            }),
+            SyncedBookmarkItem::new()
+                .validity(SyncedBookmarkValidity::Valid)
+                .kind(SyncedBookmarkKind::Query)
+                .url(Some("place:folder=123&excludeItems=1")),
+        );
+
+        // A query with a URL that can't be parsed.
+        assert_incoming_creates_mirror_item(
+            json!({
+                "id": "query1______",
+                "type": "query",
+                "parentid": "unfiled",
+                "bmkUri": "foo",
+            }),
+            SyncedBookmarkItem::new()
+                .validity(SyncedBookmarkValidity::Replace)
+                .kind(SyncedBookmarkKind::Query)
+                .url(None),
+        );
+
+        // With a missing URL
+        assert_incoming_creates_mirror_item(
+            json!({
+                "id": "query1______",
+                "type": "query",
+                "parentid": "unfiled",
+            }),
+            SyncedBookmarkItem::new()
+                .validity(SyncedBookmarkValidity::Replace)
+                .kind(SyncedBookmarkKind::Query)
+                .url(None),
+        );
+    }
+
+    #[test]
+    fn test_apply_sep() {
+        // Separators don't have much variation.
+        assert_incoming_creates_mirror_item(
+            json!({
+                "id": "sep1________",
+                "type": "separator",
+                "parentid": "unfiled",
+                "parentName": "Unfiled Bookmarks",
+            }),
+            SyncedBookmarkItem::new()
+                .validity(SyncedBookmarkValidity::Valid)
+                .kind(SyncedBookmarkKind::Separator)
+                .parent_guid(Some(&BookmarkRootGuid::Unfiled.as_guid()))
+                .needs_merge(true),
+        );
+    }
+
+    #[test]
+    fn test_apply_livemark() {
+        // A livemark with missing URLs
+        assert_incoming_creates_mirror_item(
+            json!({
+                "id": "livemark1___",
+                "type": "livemark",
+                "parentid": "unfiled",
+                "parentName": "Unfiled Bookmarks",
+            }),
+            SyncedBookmarkItem::new()
+                .validity(SyncedBookmarkValidity::Replace)
+                .kind(SyncedBookmarkKind::Livemark)
+                .parent_guid(Some(&BookmarkRootGuid::Unfiled.as_guid()))
+                .needs_merge(true)
+                .feed_url(None)
+                .site_url(None),
+        );
+        // Valid feed_url but invalid site_url is considered "valid", but the
+        // invalid URL is dropped.
+        assert_incoming_creates_mirror_item(
+            json!({
+                "id": "livemark1___",
+                "type": "livemark",
+                "parentid": "unfiled",
+                "parentName": "Unfiled Bookmarks",
+                "feedUri": "http://example.com",
+                "siteUri": "foo"
+            }),
+            SyncedBookmarkItem::new()
+                .validity(SyncedBookmarkValidity::Valid)
+                .kind(SyncedBookmarkKind::Livemark)
+                .parent_guid(Some(&BookmarkRootGuid::Unfiled.as_guid()))
+                .needs_merge(true)
+                .feed_url(Some("http://example.com/"))
+                .site_url(None),
+        );
+        // Everything valid
+        assert_incoming_creates_mirror_item(
+            json!({
+                "id": "livemark1___",
+                "type": "livemark",
+                "parentid": "unfiled",
+                "parentName": "Unfiled Bookmarks",
+                "feedUri": "http://example.com",
+                "siteUri": "http://example.com/something"
+            }),
+            SyncedBookmarkItem::new()
+                .validity(SyncedBookmarkValidity::Valid)
+                .kind(SyncedBookmarkKind::Livemark)
+                .parent_guid(Some(&BookmarkRootGuid::Unfiled.as_guid()))
+                .needs_merge(true)
+                .feed_url(Some("http://example.com/"))
+                .site_url(Some("http://example.com/something")),
+        );
+    }
+
+    #[test]
+    fn test_apply_unknown() {
+        let api = new_mem_api();
+        let db = api.get_sync_connection().expect("should get a db mutex");
+        let conn = db.lock();
+        let applicator = IncomingApplicator::new(&conn);
+
+        let record = json!({
+            "id": "unknownAAAA",
+            "type": "fancy",
+        });
+        let inc = IncomingBso::from_test_content(record);
+        // We report an error for an invalid type but don't fail.
+        applicator
+            .apply_bso(inc)
+            .expect("Should not fail with a record with unknown type");
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/places/bookmark_sync/mod.rs.html b/book/rust-docs/src/places/bookmark_sync/mod.rs.html new file mode 100644 index 0000000000..b04a329f87 --- /dev/null +++ b/book/rust-docs/src/places/bookmark_sync/mod.rs.html @@ -0,0 +1,239 @@ +mod.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+pub mod engine;
+mod incoming;
+pub mod record;
+
+#[cfg(test)]
+mod tests;
+
+use crate::error::*;
+pub use engine::BookmarksSyncEngine;
+use rusqlite::types::{ToSql, ToSqlOutput};
+use rusqlite::Result as RusqliteResult;
+
+/// Synced item kinds. These are stored in `moz_bookmarks_synced.kind` and match
+/// the definitions in `mozISyncedBookmarksMerger`.
+#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
+#[repr(u8)]
+pub enum SyncedBookmarkKind {
+    Bookmark = 1,  // KIND_BOOKMARK
+    Query = 2,     // KIND_QUERY
+    Folder = 3,    // KIND_FOLDER
+    Livemark = 4,  // KIND_LIVEMARK
+    Separator = 5, // KIND_SEPARATOR
+}
+
+impl SyncedBookmarkKind {
+    #[inline]
+    pub fn from_u8(v: u8) -> Result<Self> {
+        match v {
+            1 => Ok(SyncedBookmarkKind::Bookmark),
+            2 => Ok(SyncedBookmarkKind::Query),
+            3 => Ok(SyncedBookmarkKind::Folder),
+            4 => Ok(SyncedBookmarkKind::Livemark),
+            5 => Ok(SyncedBookmarkKind::Separator),
+            _ => Err(Error::UnsupportedSyncedBookmarkKind(v)),
+        }
+    }
+}
+
+impl From<SyncedBookmarkKind> for dogear::Kind {
+    #[inline]
+    fn from(kind: SyncedBookmarkKind) -> dogear::Kind {
+        match kind {
+            SyncedBookmarkKind::Bookmark => dogear::Kind::Bookmark,
+            SyncedBookmarkKind::Query => dogear::Kind::Query,
+            SyncedBookmarkKind::Folder => dogear::Kind::Folder,
+            SyncedBookmarkKind::Livemark => dogear::Kind::Livemark,
+            SyncedBookmarkKind::Separator => dogear::Kind::Separator,
+        }
+    }
+}
+
+impl From<dogear::Kind> for SyncedBookmarkKind {
+    #[inline]
+    fn from(kind: dogear::Kind) -> SyncedBookmarkKind {
+        match kind {
+            dogear::Kind::Bookmark => SyncedBookmarkKind::Bookmark,
+            dogear::Kind::Query => SyncedBookmarkKind::Query,
+            dogear::Kind::Folder => SyncedBookmarkKind::Folder,
+            dogear::Kind::Livemark => SyncedBookmarkKind::Livemark,
+            dogear::Kind::Separator => SyncedBookmarkKind::Separator,
+        }
+    }
+}
+
+impl ToSql for SyncedBookmarkKind {
+    #[inline]
+    fn to_sql(&self) -> RusqliteResult<ToSqlOutput<'_>> {
+        Ok(ToSqlOutput::from(*self as u8))
+    }
+}
+
+/// Synced item validity states. These are stored in
+/// `moz_bookmarks_synced.validity`, and match the definitions in
+/// `mozISyncedBookmarksMerger`. In short:
+/// * `Valid` means the record is valid and should be merged as usual.
+/// * `Reupload` means a remote item can be fixed up and applied,
+///    and should be reuploaded.
+/// * `Replace` means a remote item isn't valid at all, and should either be
+///    replaced with a valid local copy, or deleted if a valid local copy
+///    doesn't exist.
+#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
+#[repr(u8)]
+pub enum SyncedBookmarkValidity {
+    Valid = 1,    // VALIDITY_VALID
+    Reupload = 2, // VALIDITY_REUPLOAD
+    Replace = 3,  // VALIDITY_REPLACE
+}
+
+impl SyncedBookmarkValidity {
+    #[inline]
+    pub fn from_u8(v: u8) -> Result<Self> {
+        match v {
+            1 => Ok(SyncedBookmarkValidity::Valid),
+            2 => Ok(SyncedBookmarkValidity::Reupload),
+            3 => Ok(SyncedBookmarkValidity::Replace),
+            _ => Err(Error::UnsupportedSyncedBookmarkValidity(v)),
+        }
+    }
+}
+
+impl From<SyncedBookmarkValidity> for dogear::Validity {
+    fn from(validity: SyncedBookmarkValidity) -> dogear::Validity {
+        match validity {
+            SyncedBookmarkValidity::Valid => dogear::Validity::Valid,
+            SyncedBookmarkValidity::Reupload => dogear::Validity::Reupload,
+            SyncedBookmarkValidity::Replace => dogear::Validity::Replace,
+        }
+    }
+}
+
+impl ToSql for SyncedBookmarkValidity {
+    fn to_sql(&self) -> RusqliteResult<ToSqlOutput<'_>> {
+        Ok(ToSqlOutput::from(*self as u8))
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/places/bookmark_sync/record.rs.html b/book/rust-docs/src/places/bookmark_sync/record.rs.html new file mode 100644 index 0000000000..57e311b1c7 --- /dev/null +++ b/book/rust-docs/src/places/bookmark_sync/record.rs.html @@ -0,0 +1,1215 @@ +record.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
+540
+541
+542
+543
+544
+545
+546
+547
+548
+549
+550
+551
+552
+553
+554
+555
+556
+557
+558
+559
+560
+561
+562
+563
+564
+565
+566
+567
+568
+569
+570
+571
+572
+573
+574
+575
+576
+577
+578
+579
+580
+581
+582
+583
+584
+585
+586
+587
+588
+589
+590
+591
+592
+593
+594
+595
+596
+597
+598
+599
+600
+601
+602
+603
+604
+605
+606
+607
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use std::fmt;
+
+use crate::storage::bookmarks::BookmarkRootGuid;
+use crate::types::UnknownFields;
+use serde::{
+    de::{Deserialize, Deserializer, Visitor},
+    ser::{Serialize, Serializer},
+};
+use serde_derive::*;
+use sync_guid::Guid as SyncGuid;
+
+/// A bookmark record ID. Bookmark record IDs are the same as Places GUIDs,
+/// except for:
+///
+/// 1. The Places root, which is "places". Note that the Places root is not
+///    synced, but is still referenced in the user content roots' `parentid`s.
+/// 2. The four user content roots, which omit trailing underscores.
+///
+/// This wrapper helps avoid mix-ups like storing a record ID instead of a GUID,
+/// or uploading a GUID instead of a record ID.
+///
+/// Internally, we convert record IDs to GUIDs when applying incoming records,
+/// and only convert back to GUIDs during upload.
+#[derive(Clone, Debug, Hash, PartialEq, Eq)]
+pub struct BookmarkRecordId(SyncGuid);
+
+impl BookmarkRecordId {
+    /// Creates a bookmark record ID from a Sync record payload ID.
+    pub fn from_payload_id(payload_id: SyncGuid) -> BookmarkRecordId {
+        BookmarkRecordId(match payload_id.as_str() {
+            "places" => BookmarkRootGuid::Root.as_guid(),
+            "menu" => BookmarkRootGuid::Menu.as_guid(),
+            "toolbar" => BookmarkRootGuid::Toolbar.as_guid(),
+            "unfiled" => BookmarkRootGuid::Unfiled.as_guid(),
+            "mobile" => BookmarkRootGuid::Mobile.as_guid(),
+            _ => payload_id,
+        })
+    }
+
+    /// Returns a reference to the record payload ID. This is the borrowed
+    /// version of `into_payload_id`, and used for serialization.
+    #[inline]
+    pub fn as_payload_id(&self) -> &str {
+        self.root_payload_id().unwrap_or_else(|| self.0.as_ref())
+    }
+
+    /// Returns the record payload ID. This is the owned version of
+    /// `as_payload_id`, and exists to avoid copying strings when uploading
+    /// tombstones.
+    #[inline]
+    pub fn into_payload_id(self) -> SyncGuid {
+        self.root_payload_id().map(Into::into).unwrap_or(self.0)
+    }
+
+    /// Returns a reference to the GUID for this record ID.
+    #[inline]
+    pub fn as_guid(&self) -> &SyncGuid {
+        &self.0
+    }
+
+    fn root_payload_id(&self) -> Option<&str> {
+        Some(match BookmarkRootGuid::from_guid(self.as_guid()) {
+            Some(BookmarkRootGuid::Root) => "places",
+            Some(BookmarkRootGuid::Menu) => "menu",
+            Some(BookmarkRootGuid::Toolbar) => "toolbar",
+            Some(BookmarkRootGuid::Unfiled) => "unfiled",
+            Some(BookmarkRootGuid::Mobile) => "mobile",
+            None => return None,
+        })
+    }
+}
+
+/// Converts a Places GUID into a bookmark record ID.
+impl From<SyncGuid> for BookmarkRecordId {
+    #[inline]
+    fn from(guid: SyncGuid) -> BookmarkRecordId {
+        BookmarkRecordId(guid)
+    }
+}
+
+/// Converts a bookmark record ID into a Places GUID.
+impl From<BookmarkRecordId> for SyncGuid {
+    #[inline]
+    fn from(record_id: BookmarkRecordId) -> SyncGuid {
+        record_id.0
+    }
+}
+
+impl Serialize for BookmarkRecordId {
+    #[inline]
+    fn serialize<S: Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
+        serializer.serialize_str(self.as_payload_id())
+    }
+}
+
+impl<'de> Deserialize<'de> for BookmarkRecordId {
+    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> std::result::Result<Self, D::Error> {
+        struct V;
+
+        impl<'de> Visitor<'de> for V {
+            type Value = BookmarkRecordId;
+
+            fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+                f.write_str("a bookmark record ID")
+            }
+
+            #[inline]
+            fn visit_string<E: serde::de::Error>(
+                self,
+                payload_id: String,
+            ) -> std::result::Result<BookmarkRecordId, E> {
+                // The JSON deserializer passes owned strings, so we can avoid
+                // cloning the payload ID in the common case...
+                Ok(BookmarkRecordId::from_payload_id(payload_id.into()))
+            }
+
+            #[inline]
+            fn visit_str<E: serde::de::Error>(
+                self,
+                payload_id: &str,
+            ) -> std::result::Result<BookmarkRecordId, E> {
+                // ...However, the Serde docs say we must implement
+                // `visit_str` if we implement `visit_string`, so we also
+                // provide an implementation that clones the ID.
+                Ok(BookmarkRecordId::from_payload_id(payload_id.into()))
+            }
+        }
+
+        deserializer.deserialize_string(V)
+    }
+}
+
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct BookmarkRecord {
+    // Note that `SyncGuid` does not check for validity, which is what we
+    // want. If the bookmark has an invalid GUID, we'll make a new one.
+    #[serde(rename = "id")]
+    pub record_id: BookmarkRecordId,
+
+    #[serde(rename = "parentid")]
+    pub parent_record_id: Option<BookmarkRecordId>,
+
+    #[serde(rename = "parentName", skip_serializing_if = "Option::is_none")]
+    pub parent_title: Option<String>,
+
+    #[serde(skip_serializing_if = "Option::is_none")]
+    #[serde(default, deserialize_with = "de_maybe_stringified_timestamp")]
+    pub date_added: Option<i64>,
+
+    #[serde(default)]
+    pub has_dupe: bool,
+
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub title: Option<String>,
+
+    #[serde(rename = "bmkUri", skip_serializing_if = "Option::is_none")]
+    pub url: Option<String>,
+
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub keyword: Option<String>,
+
+    #[serde(default, skip_serializing_if = "Vec::is_empty")]
+    pub tags: Vec<String>,
+
+    #[serde(flatten)]
+    pub unknown_fields: UnknownFields,
+}
+
+impl From<BookmarkRecord> for BookmarkItemRecord {
+    #[inline]
+    fn from(b: BookmarkRecord) -> BookmarkItemRecord {
+        BookmarkItemRecord::Bookmark(b)
+    }
+}
+
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct QueryRecord {
+    #[serde(rename = "id")]
+    pub record_id: BookmarkRecordId,
+
+    #[serde(rename = "parentid")]
+    pub parent_record_id: Option<BookmarkRecordId>,
+
+    #[serde(rename = "parentName", skip_serializing_if = "Option::is_none")]
+    pub parent_title: Option<String>,
+
+    #[serde(skip_serializing_if = "Option::is_none")]
+    #[serde(default, deserialize_with = "de_maybe_stringified_timestamp")]
+    pub date_added: Option<i64>,
+
+    #[serde(default)]
+    pub has_dupe: bool,
+
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub title: Option<String>,
+
+    #[serde(rename = "bmkUri", skip_serializing_if = "Option::is_none")]
+    pub url: Option<String>,
+
+    #[serde(rename = "folderName", skip_serializing_if = "Option::is_none")]
+    pub tag_folder_name: Option<String>,
+
+    #[serde(flatten)]
+    pub unknown_fields: UnknownFields,
+}
+
+impl From<QueryRecord> for BookmarkItemRecord {
+    #[inline]
+    fn from(q: QueryRecord) -> BookmarkItemRecord {
+        BookmarkItemRecord::Query(q)
+    }
+}
+
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct FolderRecord {
+    #[serde(rename = "id")]
+    pub record_id: BookmarkRecordId,
+
+    #[serde(rename = "parentid")]
+    pub parent_record_id: Option<BookmarkRecordId>,
+
+    #[serde(rename = "parentName", skip_serializing_if = "Option::is_none")]
+    pub parent_title: Option<String>,
+
+    #[serde(skip_serializing_if = "Option::is_none")]
+    #[serde(default, deserialize_with = "de_maybe_stringified_timestamp")]
+    pub date_added: Option<i64>,
+
+    #[serde(default)]
+    pub has_dupe: bool,
+
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub title: Option<String>,
+
+    #[serde(default)]
+    pub children: Vec<BookmarkRecordId>,
+
+    #[serde(flatten)]
+    pub unknown_fields: UnknownFields,
+}
+
+impl From<FolderRecord> for BookmarkItemRecord {
+    #[inline]
+    fn from(f: FolderRecord) -> BookmarkItemRecord {
+        BookmarkItemRecord::Folder(f)
+    }
+}
+
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct LivemarkRecord {
+    #[serde(rename = "id")]
+    pub record_id: BookmarkRecordId,
+
+    #[serde(rename = "parentid")]
+    pub parent_record_id: Option<BookmarkRecordId>,
+
+    #[serde(rename = "parentName", skip_serializing_if = "Option::is_none")]
+    pub parent_title: Option<String>,
+
+    #[serde(skip_serializing_if = "Option::is_none")]
+    #[serde(default, deserialize_with = "de_maybe_stringified_timestamp")]
+    pub date_added: Option<i64>,
+
+    #[serde(default)]
+    pub has_dupe: bool,
+
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub title: Option<String>,
+
+    #[serde(rename = "feedUri", skip_serializing_if = "Option::is_none")]
+    pub feed_url: Option<String>,
+
+    #[serde(rename = "siteUri", skip_serializing_if = "Option::is_none")]
+    pub site_url: Option<String>,
+
+    #[serde(flatten)]
+    pub unknown_fields: UnknownFields,
+}
+
+impl From<LivemarkRecord> for BookmarkItemRecord {
+    #[inline]
+    fn from(l: LivemarkRecord) -> BookmarkItemRecord {
+        BookmarkItemRecord::Livemark(l)
+    }
+}
+
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct SeparatorRecord {
+    #[serde(rename = "id")]
+    pub record_id: BookmarkRecordId,
+
+    #[serde(rename = "parentid")]
+    pub parent_record_id: Option<BookmarkRecordId>,
+
+    #[serde(rename = "parentName", skip_serializing_if = "Option::is_none")]
+    pub parent_title: Option<String>,
+
+    #[serde(skip_serializing_if = "Option::is_none")]
+    #[serde(default, deserialize_with = "de_maybe_stringified_timestamp")]
+    pub date_added: Option<i64>,
+
+    #[serde(default)]
+    pub has_dupe: bool,
+
+    // Not used on newer clients, but can be used to detect parent-child
+    // position disagreements. Older clients use this for deduping.
+    #[serde(rename = "pos", skip_serializing_if = "Option::is_none")]
+    pub position: Option<i64>,
+
+    #[serde(flatten)]
+    pub unknown_fields: UnknownFields,
+}
+
+impl From<SeparatorRecord> for BookmarkItemRecord {
+    #[inline]
+    fn from(s: SeparatorRecord) -> BookmarkItemRecord {
+        BookmarkItemRecord::Separator(s)
+    }
+}
+
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
+#[serde(tag = "type", rename_all = "camelCase")]
+pub enum BookmarkItemRecord {
+    Bookmark(BookmarkRecord),
+    Query(QueryRecord),
+    Folder(FolderRecord),
+    Livemark(LivemarkRecord),
+    Separator(SeparatorRecord),
+}
+
+impl BookmarkItemRecord {
+    pub fn record_id(&self) -> &BookmarkRecordId {
+        match self {
+            Self::Bookmark(b) => &b.record_id,
+            Self::Query(q) => &q.record_id,
+            Self::Folder(f) => &f.record_id,
+            Self::Livemark(l) => &l.record_id,
+            Self::Separator(s) => &s.record_id,
+        }
+    }
+
+    pub fn unknown_fields(&self) -> &UnknownFields {
+        match self {
+            Self::Bookmark(b) => &b.unknown_fields,
+            Self::Folder(f) => &f.unknown_fields,
+            Self::Separator(s) => &s.unknown_fields,
+            Self::Query(q) => &q.unknown_fields,
+            Self::Livemark(l) => &l.unknown_fields,
+        }
+    }
+}
+
+// dateAdded on a bookmark might be a string! See #1148.
+fn de_maybe_stringified_timestamp<'de, D>(
+    deserializer: D,
+) -> std::result::Result<Option<i64>, D::Error>
+where
+    D: serde::de::Deserializer<'de>,
+{
+    use std::marker::PhantomData;
+
+    struct StringOrInt(PhantomData<Option<i64>>);
+
+    impl<'de> Visitor<'de> for StringOrInt {
+        type Value = Option<i64>;
+
+        fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+            formatter.write_str("string or int")
+        }
+
+        fn visit_str<E>(self, value: &str) -> Result<Option<i64>, E>
+        where
+            E: serde::de::Error,
+        {
+            match value.parse::<i64>() {
+                Ok(v) => Ok(Some(v)),
+                Err(_) => Err(E::custom("invalid string literal")),
+            }
+        }
+
+        // all positive int literals
+        fn visit_i64<E: serde::de::Error>(self, value: i64) -> Result<Option<i64>, E> {
+            Ok(Some(value.max(0)))
+        }
+
+        // all negative int literals
+        fn visit_u64<E: serde::de::Error>(self, value: u64) -> Result<Option<i64>, E> {
+            Ok(Some((value as i64).max(0)))
+        }
+    }
+    deserializer.deserialize_any(StringOrInt(PhantomData))
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use serde_json::{json, Error};
+
+    #[test]
+    fn test_invalid_record_type() {
+        let r: std::result::Result<BookmarkItemRecord, Error> =
+            serde_json::from_value(json!({"id": "whatever", "type" : "unknown-type"}));
+        let e = r.unwrap_err();
+        assert!(e.is_data());
+        // I guess is good enough to check we are hitting what we expect.
+        assert!(e.to_string().contains("unknown-type"));
+    }
+
+    #[test]
+    fn test_id_rewriting() {
+        let j = json!({"id": "unfiled", "parentid": "menu", "type": "bookmark"});
+        let r: BookmarkItemRecord = serde_json::from_value(j).expect("should deserialize");
+        match &r {
+            BookmarkItemRecord::Bookmark(b) => {
+                assert_eq!(b.record_id.as_guid(), BookmarkRootGuid::Unfiled);
+                assert_eq!(
+                    b.parent_record_id.as_ref().map(BookmarkRecordId::as_guid),
+                    Some(&BookmarkRootGuid::Menu.as_guid())
+                );
+            }
+            _ => panic!("unexpected record type"),
+        };
+        let v = serde_json::to_value(r).expect("should serialize");
+        assert_eq!(
+            v,
+            json!({
+                "id": "unfiled",
+                "parentid": "menu",
+                "type": "bookmark",
+                "hasDupe": false,
+            })
+        );
+
+        let j = json!({"id": "unfiled", "parentid": "menu", "type": "query"});
+        let r: BookmarkItemRecord = serde_json::from_value(j).expect("should deserialize");
+        match &r {
+            BookmarkItemRecord::Query(q) => {
+                assert_eq!(q.record_id.as_guid(), BookmarkRootGuid::Unfiled);
+                assert_eq!(
+                    q.parent_record_id.as_ref().map(BookmarkRecordId::as_guid),
+                    Some(&BookmarkRootGuid::Menu.as_guid())
+                );
+            }
+            _ => panic!("unexpected record type"),
+        };
+        let v = serde_json::to_value(r).expect("should serialize");
+        assert_eq!(
+            v,
+            json!({
+                "id": "unfiled",
+                "parentid": "menu",
+                "type": "query",
+                "hasDupe": false,
+            })
+        );
+
+        let j = json!({"id": "unfiled", "parentid": "menu", "type": "folder"});
+        let r: BookmarkItemRecord = serde_json::from_value(j).expect("should deserialize");
+        match &r {
+            BookmarkItemRecord::Folder(f) => {
+                assert_eq!(f.record_id.as_guid(), BookmarkRootGuid::Unfiled);
+                assert_eq!(
+                    f.parent_record_id.as_ref().map(BookmarkRecordId::as_guid),
+                    Some(&BookmarkRootGuid::Menu.as_guid())
+                );
+            }
+            _ => panic!("unexpected record type"),
+        };
+        let v = serde_json::to_value(r).expect("should serialize");
+        assert_eq!(
+            v,
+            json!({
+                "id": "unfiled",
+                "parentid": "menu",
+                "type": "folder",
+                "hasDupe": false,
+                "children": [],
+            })
+        );
+
+        let j = json!({"id": "unfiled", "parentid": "menu", "type": "livemark"});
+        let r: BookmarkItemRecord = serde_json::from_value(j).expect("should deserialize");
+        match &r {
+            BookmarkItemRecord::Livemark(l) => {
+                assert_eq!(l.record_id.as_guid(), BookmarkRootGuid::Unfiled);
+                assert_eq!(
+                    l.parent_record_id.as_ref().map(BookmarkRecordId::as_guid),
+                    Some(&BookmarkRootGuid::Menu.as_guid())
+                );
+            }
+            _ => panic!("unexpected record type"),
+        };
+        let v = serde_json::to_value(r).expect("should serialize");
+        assert_eq!(
+            v,
+            json!({
+                "id": "unfiled",
+                "parentid": "menu",
+                "type": "livemark",
+                "hasDupe": false,
+            })
+        );
+
+        let j = json!({"id": "unfiled", "parentid": "menu", "type": "separator"});
+        let r: BookmarkItemRecord = serde_json::from_value(j).expect("should deserialize");
+        match &r {
+            BookmarkItemRecord::Separator(s) => {
+                assert_eq!(s.record_id.as_guid(), BookmarkRootGuid::Unfiled);
+                assert_eq!(
+                    s.parent_record_id.as_ref().map(BookmarkRecordId::as_guid),
+                    Some(&BookmarkRootGuid::Menu.as_guid())
+                );
+            }
+            _ => panic!("unexpected record type"),
+        };
+        let v = serde_json::to_value(r).expect("should serialize");
+        assert_eq!(
+            v,
+            json!({
+                "id": "unfiled",
+                "parentid": "menu",
+                "type": "separator",
+                "hasDupe": false,
+            })
+        );
+    }
+
+    // It's unfortunate that all below 'dateadded' tests only check the
+    // 'BookmarkItemRecord' variant, so it would be a problem if `date_added` on
+    // other variants forgot to do the `deserialize_with` dance. We could
+    // implement a new type to make that less likely, but that's not foolproof
+    // either and causes this hysterical raisin to leak out from this module.
+    fn check_date_added(j: serde_json::Value, expected: Option<i64>) {
+        let r: BookmarkItemRecord = serde_json::from_value(j).expect("should deserialize");
+        match &r {
+            BookmarkItemRecord::Bookmark(b) => assert_eq!(b.date_added, expected),
+            _ => panic!("unexpected record type"),
+        };
+    }
+
+    #[test]
+    fn test_dateadded_missing() {
+        check_date_added(
+            json!({"id": "unfiled", "parentid": "menu", "type": "bookmark"}),
+            None,
+        )
+    }
+
+    #[test]
+    fn test_dateadded_int() {
+        check_date_added(
+            json!({"id": "unfiled", "parentid": "menu", "type": "bookmark", "dateAdded": 123}),
+            Some(123),
+        )
+    }
+
+    #[test]
+    fn test_dateadded_negative() {
+        check_date_added(
+            json!({"id": "unfiled", "parentid": "menu", "type": "bookmark", "dateAdded": -1}),
+            Some(0),
+        )
+    }
+
+    #[test]
+    fn test_dateadded_str() {
+        check_date_added(
+            json!({"id": "unfiled", "parentid": "menu", "type": "bookmark", "dateAdded": "123"}),
+            Some(123),
+        )
+    }
+
+    // A kinda "policy" decision - like serde, 'type errors' fail rather than default.
+    #[test]
+    fn test_dateadded_null() {
+        // a literal `null` is insane (and note we already test it *missing* above)
+        serde_json::from_value::<BookmarkItemRecord>(
+            json!({"id": "unfiled", "parentid": "menu", "type": "bookmark", "dateAdded": null}),
+        )
+        .expect_err("should fail, literal null");
+    }
+
+    #[test]
+    fn test_dateadded_invalid_str() {
+        serde_json::from_value::<BookmarkItemRecord>(
+            json!({"id": "unfiled", "parentid": "menu", "type": "bookmark", "dateAdded": "foo"}),
+        )
+        .expect_err("should fail, bad string value");
+    }
+
+    #[test]
+    fn test_dateadded_invalid_type() {
+        serde_json::from_value::<BookmarkItemRecord>(
+            json!({"id": "unfiled", "parentid": "menu", "type": "bookmark", "dateAdded": []}),
+        )
+        .expect_err("should fail, invalid type");
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/places/db/db.rs.html b/book/rust-docs/src/places/db/db.rs.html new file mode 100644 index 0000000000..24cec24179 --- /dev/null +++ b/book/rust-docs/src/places/db/db.rs.html @@ -0,0 +1,1113 @@ +db.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
+540
+541
+542
+543
+544
+545
+546
+547
+548
+549
+550
+551
+552
+553
+554
+555
+556
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use super::schema;
+use crate::api::places_api::ConnectionType;
+use crate::error::*;
+use interrupt_support::{SqlInterruptHandle, SqlInterruptScope};
+use lazy_static::lazy_static;
+use parking_lot::Mutex;
+use rusqlite::{self, Connection, Transaction};
+use sql_support::{
+    open_database::{self, open_database_with_flags, ConnectionInitializer},
+    ConnExt,
+};
+use std::collections::HashMap;
+use std::ops::Deref;
+use std::path::Path;
+
+use std::sync::{
+    atomic::{AtomicI64, Ordering},
+    Arc, RwLock,
+};
+
+pub const MAX_VARIABLE_NUMBER: usize = 999;
+
+lazy_static! {
+    // Each API has a single bookmark change counter shared across all connections.
+    // This hashmap indexes them by the "api id" of the API.
+    pub static ref GLOBAL_BOOKMARK_CHANGE_COUNTERS: RwLock<HashMap<usize, AtomicI64>> = RwLock::new(HashMap::new());
+}
+
+pub struct PlacesInitializer {
+    api_id: usize,
+    conn_type: ConnectionType,
+}
+
+impl PlacesInitializer {
+    #[cfg(test)]
+    pub fn new_for_test() -> Self {
+        Self {
+            api_id: 0,
+            conn_type: ConnectionType::ReadWrite,
+        }
+    }
+}
+
+impl ConnectionInitializer for PlacesInitializer {
+    const NAME: &'static str = "places";
+    const END_VERSION: u32 = schema::VERSION;
+
+    fn init(&self, tx: &Transaction<'_>) -> open_database::Result<()> {
+        Ok(schema::init(tx)?)
+    }
+
+    fn upgrade_from(&self, tx: &Transaction<'_>, version: u32) -> open_database::Result<()> {
+        Ok(schema::upgrade_from(tx, version)?)
+    }
+
+    fn prepare(&self, conn: &Connection, db_empty: bool) -> open_database::Result<()> {
+        // If this is an empty DB, setup incremental auto-vacuum now rather than wait for the first
+        // run_maintenance_vacuum() call.  It should be much faster now with an empty DB.
+        if db_empty && !matches!(self.conn_type, ConnectionType::ReadOnly) {
+            conn.execute_one("PRAGMA auto_vacuum=incremental")?;
+            conn.execute_one("VACUUM")?;
+        }
+
+        let initial_pragmas = "
+            -- The value we use was taken from Desktop Firefox, and seems necessary to
+            -- help ensure good performance on autocomplete-style queries.
+            -- Modern default value is 4096, but as reported in
+            -- https://bugzilla.mozilla.org/show_bug.cgi?id=1782283, desktop places saw
+            -- a nice improvement with this value.
+            PRAGMA page_size = 32768;
+
+            -- Disable calling mlock/munlock for every malloc/free.
+            -- In practice this results in a massive speedup, especially
+            -- for insert-heavy workloads.
+            PRAGMA cipher_memory_security = false;
+
+            -- `temp_store = 2` is required on Android to force the DB to keep temp
+            -- files in memory, since on Android there's no tmp partition. See
+            -- https://github.com/mozilla/mentat/issues/505. Ideally we'd only
+            -- do this on Android, and/or allow caller to configure it.
+            -- (although see also bug 1313021, where Firefox enabled it for both
+            -- Android and 64bit desktop builds)
+            PRAGMA temp_store = 2;
+
+            -- 6MiB, same as the value used for `promiseLargeCacheDBConnection` in PlacesUtils,
+            -- which is used to improve query performance for autocomplete-style queries (by
+            -- UnifiedComplete). Note that SQLite uses a negative value for this pragma to indicate
+            -- that it's in units of KiB.
+            PRAGMA cache_size = -6144;
+
+            -- We want foreign-key support.
+            PRAGMA foreign_keys = ON;
+
+            -- we unconditionally want write-ahead-logging mode
+            PRAGMA journal_mode=WAL;
+
+            -- How often to autocheckpoint (in units of pages).
+            -- 2048000 (our max desired WAL size) / 32760 (page size).
+            PRAGMA wal_autocheckpoint=62;
+
+            -- How long to wait for a lock before returning SQLITE_BUSY (in ms)
+            -- See `doc/sql_concurrency.md` for details.
+            PRAGMA busy_timeout = 5000;
+        ";
+        conn.execute_batch(initial_pragmas)?;
+        define_functions(conn, self.api_id)?;
+        sql_support::debug_tools::define_debug_functions(conn)?;
+        conn.set_prepared_statement_cache_capacity(128);
+        Ok(())
+    }
+
+    fn finish(&self, conn: &Connection) -> open_database::Result<()> {
+        Ok(schema::finish(conn, self.conn_type)?)
+    }
+}
+
+#[derive(Debug)]
+pub struct PlacesDb {
+    pub db: Connection,
+    conn_type: ConnectionType,
+    interrupt_handle: Arc<SqlInterruptHandle>,
+    api_id: usize,
+    pub(super) coop_tx_lock: Arc<Mutex<()>>,
+}
+
+impl PlacesDb {
+    fn with_connection(
+        db: Connection,
+        conn_type: ConnectionType,
+        api_id: usize,
+        coop_tx_lock: Arc<Mutex<()>>,
+    ) -> Self {
+        Self {
+            interrupt_handle: Arc::new(SqlInterruptHandle::new(&db)),
+            db,
+            conn_type,
+            // The API sets this explicitly.
+            api_id,
+            coop_tx_lock,
+        }
+    }
+
+    pub fn open(
+        path: impl AsRef<Path>,
+        conn_type: ConnectionType,
+        api_id: usize,
+        coop_tx_lock: Arc<Mutex<()>>,
+    ) -> Result<Self> {
+        let initializer = PlacesInitializer { api_id, conn_type };
+        let conn = open_database_with_flags(path, conn_type.rusqlite_flags(), &initializer)?;
+        Ok(Self::with_connection(conn, conn_type, api_id, coop_tx_lock))
+    }
+
+    #[cfg(test)]
+    // Useful for some tests (although most tests should use helper functions
+    // in api::places_api::test)
+    pub fn open_in_memory(conn_type: ConnectionType) -> Result<Self> {
+        let initializer = PlacesInitializer {
+            api_id: 0,
+            conn_type,
+        };
+        let conn = open_database::open_memory_database_with_flags(
+            conn_type.rusqlite_flags(),
+            &initializer,
+        )?;
+        Ok(Self::with_connection(
+            conn,
+            conn_type,
+            0,
+            Arc::new(Mutex::new(())),
+        ))
+    }
+
+    pub fn new_interrupt_handle(&self) -> Arc<SqlInterruptHandle> {
+        Arc::clone(&self.interrupt_handle)
+    }
+
+    #[inline]
+    pub fn begin_interrupt_scope(&self) -> Result<SqlInterruptScope> {
+        Ok(self.interrupt_handle.begin_interrupt_scope()?)
+    }
+
+    #[inline]
+    pub fn conn_type(&self) -> ConnectionType {
+        self.conn_type
+    }
+
+    /// Returns an object that can tell you whether any changes have been made
+    /// to bookmarks since this was called.
+    /// While this conceptually should live on the PlacesApi, the things that
+    /// need this typically only have a PlacesDb, so we expose it here.
+    pub fn global_bookmark_change_tracker(&self) -> GlobalChangeCounterTracker {
+        GlobalChangeCounterTracker::new(self.api_id)
+    }
+
+    #[inline]
+    pub fn api_id(&self) -> usize {
+        self.api_id
+    }
+}
+
+impl Drop for PlacesDb {
+    fn drop(&mut self) {
+        // In line with both the recommendations from SQLite and the behavior of places in
+        // Database.cpp, we run `PRAGMA optimize` before closing the connection.
+        let res = self.db.execute_batch("PRAGMA optimize(0x02);");
+        if let Err(e) = res {
+            log::warn!("Failed to execute pragma optimize (DB locked?): {}", e);
+        }
+    }
+}
+
+impl ConnExt for PlacesDb {
+    #[inline]
+    fn conn(&self) -> &Connection {
+        &self.db
+    }
+}
+
+impl Deref for PlacesDb {
+    type Target = Connection;
+    #[inline]
+    fn deref(&self) -> &Connection {
+        &self.db
+    }
+}
+
+/// PlacesDB that's behind a Mutex so it can be shared between threads
+pub struct SharedPlacesDb {
+    db: Mutex<PlacesDb>,
+    interrupt_handle: Arc<SqlInterruptHandle>,
+}
+
+impl SharedPlacesDb {
+    pub fn new(db: PlacesDb) -> Self {
+        Self {
+            interrupt_handle: db.new_interrupt_handle(),
+            db: Mutex::new(db),
+        }
+    }
+
+    pub fn begin_interrupt_scope(&self) -> Result<SqlInterruptScope> {
+        Ok(self.interrupt_handle.begin_interrupt_scope()?)
+    }
+}
+
+// Deref to a Mutex<PlacesDb>, which is how we will use SharedPlacesDb most of the time
+impl Deref for SharedPlacesDb {
+    type Target = Mutex<PlacesDb>;
+
+    #[inline]
+    fn deref(&self) -> &Mutex<PlacesDb> {
+        &self.db
+    }
+}
+
+// Also implement AsRef<SqlInterruptHandle> so that we can interrupt this at shutdown
+impl AsRef<SqlInterruptHandle> for SharedPlacesDb {
+    fn as_ref(&self) -> &SqlInterruptHandle {
+        &self.interrupt_handle
+    }
+}
+
+/// An object that can tell you whether a bookmark changing operation has
+/// happened since the object was created.
+pub struct GlobalChangeCounterTracker {
+    api_id: usize,
+    start_value: i64,
+}
+
+impl GlobalChangeCounterTracker {
+    pub fn new(api_id: usize) -> Self {
+        GlobalChangeCounterTracker {
+            api_id,
+            start_value: Self::cur_value(api_id),
+        }
+    }
+
+    // The value is an implementation detail, so just expose what we care
+    // about - ie, "has it changed?"
+    pub fn changed(&self) -> bool {
+        Self::cur_value(self.api_id) != self.start_value
+    }
+
+    fn cur_value(api_id: usize) -> i64 {
+        let map = GLOBAL_BOOKMARK_CHANGE_COUNTERS
+            .read()
+            .expect("gbcc poisoned");
+        match map.get(&api_id) {
+            Some(counter) => counter.load(Ordering::Acquire),
+            None => 0,
+        }
+    }
+}
+
+fn define_functions(c: &Connection, api_id: usize) -> rusqlite::Result<()> {
+    use rusqlite::functions::FunctionFlags;
+    c.create_scalar_function(
+        "get_prefix",
+        1,
+        FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
+        sql_fns::get_prefix,
+    )?;
+    c.create_scalar_function(
+        "get_host_and_port",
+        1,
+        FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
+        sql_fns::get_host_and_port,
+    )?;
+    c.create_scalar_function(
+        "strip_prefix_and_userinfo",
+        1,
+        FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
+        sql_fns::strip_prefix_and_userinfo,
+    )?;
+    c.create_scalar_function(
+        "reverse_host",
+        1,
+        FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
+        sql_fns::reverse_host,
+    )?;
+    c.create_scalar_function(
+        "autocomplete_match",
+        10,
+        FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
+        sql_fns::autocomplete_match,
+    )?;
+    c.create_scalar_function(
+        "hash",
+        -1,
+        FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
+        sql_fns::hash,
+    )?;
+    c.create_scalar_function("now", 0, FunctionFlags::SQLITE_UTF8, sql_fns::now)?;
+    c.create_scalar_function(
+        "generate_guid",
+        0,
+        FunctionFlags::SQLITE_UTF8,
+        sql_fns::generate_guid,
+    )?;
+    c.create_scalar_function(
+        "note_bookmarks_sync_change",
+        0,
+        FunctionFlags::SQLITE_UTF8,
+        move |ctx| -> rusqlite::Result<i64> { sql_fns::note_bookmarks_sync_change(ctx, api_id) },
+    )?;
+    Ok(())
+}
+
+pub(crate) mod sql_fns {
+    use super::GLOBAL_BOOKMARK_CHANGE_COUNTERS;
+    use crate::api::matcher::{split_after_host_and_port, split_after_prefix};
+    use crate::hash;
+    use crate::match_impl::{AutocompleteMatch, MatchBehavior, SearchBehavior};
+    use rusqlite::{functions::Context, types::ValueRef, Error, Result};
+    use std::sync::atomic::Ordering;
+    use sync_guid::Guid as SyncGuid;
+    use types::Timestamp;
+
+    // Helpers for define_functions
+    fn get_raw_str<'a>(ctx: &'a Context<'_>, fname: &'static str, idx: usize) -> Result<&'a str> {
+        ctx.get_raw(idx).as_str().map_err(|e| {
+            Error::UserFunctionError(format!("Bad arg {} to '{}': {}", idx, fname, e).into())
+        })
+    }
+
+    fn get_raw_opt_str<'a>(
+        ctx: &'a Context<'_>,
+        fname: &'static str,
+        idx: usize,
+    ) -> Result<Option<&'a str>> {
+        let raw = ctx.get_raw(idx);
+        if raw == ValueRef::Null {
+            return Ok(None);
+        }
+        Ok(Some(raw.as_str().map_err(|e| {
+            Error::UserFunctionError(format!("Bad arg {} to '{}': {}", idx, fname, e).into())
+        })?))
+    }
+
+    // Note: The compiler can't meaningfully inline these, but if we don't put
+    // #[inline(never)] on them they get "inlined" into a temporary Box<FnMut>,
+    // which doesn't have a name (and itself doesn't get inlined). Adding
+    // #[inline(never)] ensures they show up in profiles.
+
+    #[inline(never)]
+    pub fn hash(ctx: &Context<'_>) -> rusqlite::Result<Option<i64>> {
+        Ok(match ctx.len() {
+            1 => {
+                // This is a deterministic function, which means sqlite
+                // does certain optimizations which means hash() may be called
+                // with a null value even though the query prevents the null
+                // value from actually being used. As a special case, we return
+                // null when the input is NULL. We return NULL instead of zero
+                // because the hash columns are NOT NULL, so attempting to
+                // actually use the null should fail.
+                get_raw_opt_str(ctx, "hash", 0)?.map(|value| hash::hash_url(value) as i64)
+            }
+            2 => {
+                let value = get_raw_opt_str(ctx, "hash", 0)?;
+                let mode = get_raw_str(ctx, "hash", 1)?;
+                if let Some(value) = value {
+                    Some(match mode {
+                        "" => hash::hash_url(value),
+                        "prefix_lo" => hash::hash_url_prefix(value, hash::PrefixMode::Lo),
+                        "prefix_hi" => hash::hash_url_prefix(value, hash::PrefixMode::Hi),
+                        arg => {
+                            return Err(rusqlite::Error::UserFunctionError(format!(
+                                "`hash` second argument must be either '', 'prefix_lo', or 'prefix_hi', got {:?}.",
+                                arg).into()));
+                        }
+                    } as i64)
+                } else {
+                    None
+                }
+            }
+            n => {
+                return Err(rusqlite::Error::UserFunctionError(
+                    format!("`hash` expects 1 or 2 arguments, got {}.", n).into(),
+                ));
+            }
+        })
+    }
+
+    #[inline(never)]
+    pub fn autocomplete_match(ctx: &Context<'_>) -> Result<bool> {
+        let search_str = get_raw_str(ctx, "autocomplete_match", 0)?;
+        let url_str = get_raw_str(ctx, "autocomplete_match", 1)?;
+        let title_str = get_raw_opt_str(ctx, "autocomplete_match", 2)?.unwrap_or_default();
+        let tags = get_raw_opt_str(ctx, "autocomplete_match", 3)?.unwrap_or_default();
+        let visit_count = ctx.get::<u32>(4)?;
+        let typed = ctx.get::<bool>(5)?;
+        let bookmarked = ctx.get::<bool>(6)?;
+        let open_page_count = ctx.get::<Option<u32>>(7)?.unwrap_or(0);
+        let match_behavior = ctx.get::<MatchBehavior>(8)?;
+        let search_behavior = ctx.get::<SearchBehavior>(9)?;
+
+        let matcher = AutocompleteMatch {
+            search_str,
+            url_str,
+            title_str,
+            tags,
+            visit_count,
+            typed,
+            bookmarked,
+            open_page_count,
+            match_behavior,
+            search_behavior,
+        };
+        Ok(matcher.invoke())
+    }
+
+    #[inline(never)]
+    pub fn reverse_host(ctx: &Context<'_>) -> Result<String> {
+        // We reuse this memory so no need for get_raw.
+        let mut host = ctx.get::<String>(0)?;
+        debug_assert!(host.is_ascii(), "Hosts must be Punycoded");
+
+        host.make_ascii_lowercase();
+        let mut rev_host_bytes = host.into_bytes();
+        rev_host_bytes.reverse();
+        rev_host_bytes.push(b'.');
+
+        let rev_host = String::from_utf8(rev_host_bytes).map_err(|_err| {
+            rusqlite::Error::UserFunctionError("non-punycode host provided to reverse_host!".into())
+        })?;
+        Ok(rev_host)
+    }
+
+    #[inline(never)]
+    pub fn get_prefix(ctx: &Context<'_>) -> Result<String> {
+        let href = get_raw_str(ctx, "get_prefix", 0)?;
+        let (prefix, _) = split_after_prefix(href);
+        Ok(prefix.to_owned())
+    }
+
+    #[inline(never)]
+    pub fn get_host_and_port(ctx: &Context<'_>) -> Result<String> {
+        let href = get_raw_str(ctx, "get_host_and_port", 0)?;
+        let (host_and_port, _) = split_after_host_and_port(href);
+        Ok(host_and_port.to_owned())
+    }
+
+    #[inline(never)]
+    pub fn strip_prefix_and_userinfo(ctx: &Context<'_>) -> Result<String> {
+        let href = get_raw_str(ctx, "strip_prefix_and_userinfo", 0)?;
+        let (host_and_port, remainder) = split_after_host_and_port(href);
+        let mut res = String::with_capacity(host_and_port.len() + remainder.len() + 1);
+        res += host_and_port;
+        res += remainder;
+        Ok(res)
+    }
+
+    #[inline(never)]
+    pub fn now(_ctx: &Context<'_>) -> Result<Timestamp> {
+        Ok(Timestamp::now())
+    }
+
+    #[inline(never)]
+    pub fn generate_guid(_ctx: &Context<'_>) -> Result<SyncGuid> {
+        Ok(SyncGuid::random())
+    }
+
+    #[inline(never)]
+    pub fn note_bookmarks_sync_change(_ctx: &Context<'_>, api_id: usize) -> Result<i64> {
+        let map = GLOBAL_BOOKMARK_CHANGE_COUNTERS
+            .read()
+            .expect("gbcc poisoned");
+        if let Some(counter) = map.get(&api_id) {
+            // Because we only ever check for equality, we can use Relaxed ordering.
+            return Ok(counter.fetch_add(1, Ordering::Relaxed));
+        }
+        // Need to add the counter to the map - drop the read lock before
+        // taking the write lock.
+        drop(map);
+        let mut map = GLOBAL_BOOKMARK_CHANGE_COUNTERS
+            .write()
+            .expect("gbcc poisoned");
+        let counter = map.entry(api_id).or_default();
+        // Because we only ever check for equality, we can use Relaxed ordering.
+        Ok(counter.fetch_add(1, Ordering::Relaxed))
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    // Sanity check that we can create a database.
+    #[test]
+    fn test_open() {
+        PlacesDb::open_in_memory(ConnectionType::ReadWrite).expect("no memory db");
+    }
+
+    #[test]
+    fn test_reverse_host() {
+        let conn = PlacesDb::open_in_memory(ConnectionType::ReadWrite).expect("no memory db");
+        let rev_host: String = conn
+            .db
+            .query_row("SELECT reverse_host('www.mozilla.org')", [], |row| {
+                row.get(0)
+            })
+            .unwrap();
+        assert_eq!(rev_host, "gro.allizom.www.");
+
+        let rev_host: String = conn
+            .db
+            .query_row("SELECT reverse_host('')", [], |row| row.get(0))
+            .unwrap();
+        assert_eq!(rev_host, ".");
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/places/db/mod.rs.html b/book/rust-docs/src/places/db/mod.rs.html new file mode 100644 index 0000000000..ca8e8a5c21 --- /dev/null +++ b/book/rust-docs/src/places/db/mod.rs.html @@ -0,0 +1,25 @@ +mod.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// We don't want 'db.rs' as a sub-module. We could move the contents here? Or something else?
+#[allow(clippy::module_inception)] // FIXME
+pub mod db;
+mod schema;
+mod tx;
+pub use self::tx::PlacesTransaction;
+
+pub use crate::db::db::{GlobalChangeCounterTracker, PlacesDb, SharedPlacesDb};
+
\ No newline at end of file diff --git a/book/rust-docs/src/places/db/schema.rs.html b/book/rust-docs/src/places/db/schema.rs.html new file mode 100644 index 0000000000..0c4a695710 --- /dev/null +++ b/book/rust-docs/src/places/db/schema.rs.html @@ -0,0 +1,1927 @@ +schema.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
+540
+541
+542
+543
+544
+545
+546
+547
+548
+549
+550
+551
+552
+553
+554
+555
+556
+557
+558
+559
+560
+561
+562
+563
+564
+565
+566
+567
+568
+569
+570
+571
+572
+573
+574
+575
+576
+577
+578
+579
+580
+581
+582
+583
+584
+585
+586
+587
+588
+589
+590
+591
+592
+593
+594
+595
+596
+597
+598
+599
+600
+601
+602
+603
+604
+605
+606
+607
+608
+609
+610
+611
+612
+613
+614
+615
+616
+617
+618
+619
+620
+621
+622
+623
+624
+625
+626
+627
+628
+629
+630
+631
+632
+633
+634
+635
+636
+637
+638
+639
+640
+641
+642
+643
+644
+645
+646
+647
+648
+649
+650
+651
+652
+653
+654
+655
+656
+657
+658
+659
+660
+661
+662
+663
+664
+665
+666
+667
+668
+669
+670
+671
+672
+673
+674
+675
+676
+677
+678
+679
+680
+681
+682
+683
+684
+685
+686
+687
+688
+689
+690
+691
+692
+693
+694
+695
+696
+697
+698
+699
+700
+701
+702
+703
+704
+705
+706
+707
+708
+709
+710
+711
+712
+713
+714
+715
+716
+717
+718
+719
+720
+721
+722
+723
+724
+725
+726
+727
+728
+729
+730
+731
+732
+733
+734
+735
+736
+737
+738
+739
+740
+741
+742
+743
+744
+745
+746
+747
+748
+749
+750
+751
+752
+753
+754
+755
+756
+757
+758
+759
+760
+761
+762
+763
+764
+765
+766
+767
+768
+769
+770
+771
+772
+773
+774
+775
+776
+777
+778
+779
+780
+781
+782
+783
+784
+785
+786
+787
+788
+789
+790
+791
+792
+793
+794
+795
+796
+797
+798
+799
+800
+801
+802
+803
+804
+805
+806
+807
+808
+809
+810
+811
+812
+813
+814
+815
+816
+817
+818
+819
+820
+821
+822
+823
+824
+825
+826
+827
+828
+829
+830
+831
+832
+833
+834
+835
+836
+837
+838
+839
+840
+841
+842
+843
+844
+845
+846
+847
+848
+849
+850
+851
+852
+853
+854
+855
+856
+857
+858
+859
+860
+861
+862
+863
+864
+865
+866
+867
+868
+869
+870
+871
+872
+873
+874
+875
+876
+877
+878
+879
+880
+881
+882
+883
+884
+885
+886
+887
+888
+889
+890
+891
+892
+893
+894
+895
+896
+897
+898
+899
+900
+901
+902
+903
+904
+905
+906
+907
+908
+909
+910
+911
+912
+913
+914
+915
+916
+917
+918
+919
+920
+921
+922
+923
+924
+925
+926
+927
+928
+929
+930
+931
+932
+933
+934
+935
+936
+937
+938
+939
+940
+941
+942
+943
+944
+945
+946
+947
+948
+949
+950
+951
+952
+953
+954
+955
+956
+957
+958
+959
+960
+961
+962
+963
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::api::places_api::ConnectionType;
+use crate::bookmark_sync::engine::LAST_SYNC_META_KEY;
+use crate::storage::bookmarks::{
+    bookmark_sync::create_synced_bookmark_roots, create_bookmark_roots,
+};
+use crate::types::SyncStatus;
+use rusqlite::Connection;
+use sql_support::ConnExt;
+
+pub const VERSION: u32 = 17;
+
+// Shared schema and temp tables for the read-write and Sync connections.
+const CREATE_SHARED_SCHEMA_SQL: &str = include_str!("../../sql/create_shared_schema.sql");
+const CREATE_SHARED_TEMP_TABLES_SQL: &str = include_str!("../../sql/create_shared_temp_tables.sql");
+
+// Sync-specific temp tables and triggers.
+const CREATE_SYNC_TEMP_TABLES_SQL: &str = include_str!("../../sql/create_sync_temp_tables.sql");
+const CREATE_SYNC_TRIGGERS_SQL: &str = include_str!("../../sql/create_sync_triggers.sql");
+
+// Triggers for the main read-write connection only.
+const CREATE_MAIN_TRIGGERS_SQL: &str = include_str!("../../sql/create_main_triggers.sql");
+
+lazy_static::lazy_static! {
+    // Triggers for the read-write and Sync connections.
+    static ref CREATE_SHARED_TRIGGERS_SQL: String = {
+        format!(
+            include_str!("../../sql/create_shared_triggers.sql"),
+            increase_frecency_stats = update_origin_frecency_stats("+"),
+            decrease_frecency_stats = update_origin_frecency_stats("-"),
+        )
+    };
+}
+
+// Keys in the moz_meta table.
+pub(crate) static MOZ_META_KEY_ORIGIN_FRECENCY_COUNT: &str = "origin_frecency_count";
+pub(crate) static MOZ_META_KEY_ORIGIN_FRECENCY_SUM: &str = "origin_frecency_sum";
+pub(crate) static MOZ_META_KEY_ORIGIN_FRECENCY_SUM_OF_SQUARES: &str =
+    "origin_frecency_sum_of_squares";
+
+fn update_origin_frecency_stats(op: &str) -> String {
+    format!(
+        "
+        INSERT OR REPLACE INTO moz_meta(key, value)
+        SELECT
+            '{frecency_count}',
+            IFNULL((SELECT value FROM moz_meta WHERE key = '{frecency_count}'), 0)
+                {op} CAST (frecency > 0 AS INT)
+        FROM moz_origins WHERE prefix = OLD.prefix AND host = OLD.host
+        UNION
+        SELECT
+            '{frecency_sum}',
+            IFNULL((SELECT value FROM moz_meta WHERE key = '{frecency_sum}'), 0)
+                {op} MAX(frecency, 0)
+        FROM moz_origins WHERE prefix = OLD.prefix AND host = OLD.host
+        UNION
+        SELECT
+            '{frecency_sum_of_squares}',
+            IFNULL((SELECT value FROM moz_meta WHERE key = '{frecency_sum_of_squares}'), 0)
+                {op} (MAX(frecency, 0) * MAX(frecency, 0))
+        FROM moz_origins WHERE prefix = OLD.prefix AND host = OLD.host",
+        op = op,
+        frecency_count = MOZ_META_KEY_ORIGIN_FRECENCY_COUNT,
+        frecency_sum = MOZ_META_KEY_ORIGIN_FRECENCY_SUM,
+        frecency_sum_of_squares = MOZ_META_KEY_ORIGIN_FRECENCY_SUM_OF_SQUARES,
+    )
+}
+
+pub fn init(conn: &Connection) -> rusqlite::Result<()> {
+    log::debug!("Initializing schema");
+    conn.execute_batch(CREATE_SHARED_SCHEMA_SQL)?;
+    create_bookmark_roots(conn)?;
+    Ok(())
+}
+
+pub fn finish(db: &Connection, conn_type: ConnectionType) -> rusqlite::Result<()> {
+    match conn_type {
+        // Read-only connections don't need temp tables or triggers, as they
+        // can't write anything.
+        ConnectionType::ReadOnly => {}
+
+        // The main read-write connection needs shared and main-specific
+        // temp tables and triggers (for example, for writing tombstones).
+        ConnectionType::ReadWrite => {
+            // Every writable connection needs these...
+            db.execute_batch(CREATE_SHARED_TEMP_TABLES_SQL)?;
+            db.execute_batch(&CREATE_SHARED_TRIGGERS_SQL)?;
+            db.execute_batch(CREATE_MAIN_TRIGGERS_SQL)?;
+        }
+
+        // The Sync connection needs shared and its own temp tables and
+        // triggers, for merging. It also bypasses some of the main
+        // triggers, so that we don't write tombstones for synced deletions.
+        ConnectionType::Sync => {
+            db.execute_batch(CREATE_SHARED_TEMP_TABLES_SQL)?;
+            db.execute_batch(&CREATE_SHARED_TRIGGERS_SQL)?;
+            db.execute_batch(CREATE_SYNC_TEMP_TABLES_SQL)?;
+            db.execute_batch(CREATE_SYNC_TRIGGERS_SQL)?;
+            create_synced_bookmark_roots(db)?;
+        }
+    }
+    Ok(())
+}
+
+/// Helper for migration - pre-dates MigrationLogic, hence it has slightly strange wiring.
+/// Intended use:
+///
+/// ```rust,ignore
+/// migration(db, cur_ver, 2, &[stuff, to, migrate, version2, to, version3], || Ok(()))?;
+/// migration(db, cur_ver, 3, &[stuff, to, migrate, version3, to, version4], || Ok(()))?;
+/// migration(db, cur_ver, 4, &[stuff, to, migrate, version4, to, version5], || Ok(()))?;
+/// ```
+///
+/// The callback parameter is if any extra logic is needed for the migration
+/// (for example, creating bookmark roots). In an ideal world, this would be an
+/// Option, but sadly, that can't typecheck.
+fn migration<F>(
+    db: &Connection,
+    cur_version: u32,
+    ours: u32,
+    stmts: &[&str],
+    extra_logic: F,
+) -> rusqlite::Result<()>
+where
+    F: FnOnce() -> rusqlite::Result<()>,
+{
+    if cur_version == ours {
+        log::debug!("Upgrading schema from {} to {}", cur_version, ours);
+        for stmt in stmts {
+            db.execute_batch(stmt)?;
+        }
+        extra_logic()?;
+    }
+    Ok(())
+}
+
+pub fn upgrade_from(db: &Connection, from: u32) -> rusqlite::Result<()> {
+    log::debug!("Upgrading schema from {} to {}", from, VERSION);
+
+    // Old-style migrations
+
+    migration(db, from, 2, &[CREATE_SHARED_SCHEMA_SQL], || Ok(()))?;
+    migration(
+        db,
+        from,
+        3,
+        &[
+            // Previous versions had an incomplete version of moz_bookmarks.
+            "DROP TABLE moz_bookmarks",
+            CREATE_SHARED_SCHEMA_SQL,
+        ],
+        || create_bookmark_roots(db.conn()),
+    )?;
+    migration(db, from, 4, &[CREATE_SHARED_SCHEMA_SQL], || Ok(()))?;
+    migration(db, from, 5, &[CREATE_SHARED_SCHEMA_SQL], || Ok(()))?; // new tags tables.
+    migration(db, from, 6, &[CREATE_SHARED_SCHEMA_SQL], || Ok(()))?; // bookmark syncing.
+    migration(
+        db,
+        from,
+        7,
+        &[
+            // Changing `moz_bookmarks_synced_structure` to store multiple
+            // parents, so we need to re-download all synced bookmarks.
+            &format!("DELETE FROM moz_meta WHERE key = '{}'", LAST_SYNC_META_KEY),
+            "DROP TABLE moz_bookmarks_synced",
+            "DROP TABLE moz_bookmarks_synced_structure",
+            CREATE_SHARED_SCHEMA_SQL,
+        ],
+        || Ok(()),
+    )?;
+    migration(
+        db,
+        from,
+        8,
+        &[
+            // Bump change counter of New() items due to bookmarks `reset`
+            // setting the counter to 0 instead of 1 (#1145)
+            &format!(
+                "UPDATE moz_bookmarks
+                 SET syncChangeCounter = syncChangeCounter + 1
+                 WHERE syncStatus = {}",
+                SyncStatus::New as u8
+            ),
+        ],
+        || Ok(()),
+    )?;
+    migration(
+        db,
+        from,
+        9,
+        &[
+            // Add an index for synced bookmark URLs.
+            "CREATE INDEX IF NOT EXISTS moz_bookmarks_synced_urls
+             ON moz_bookmarks_synced(placeId)",
+        ],
+        || Ok(()),
+    )?;
+    migration(
+        db,
+        from,
+        10,
+        &[
+            // Add a new table to hold synced and migrated search keywords.
+            "CREATE TABLE IF NOT EXISTS moz_keywords(
+                 place_id INTEGER PRIMARY KEY REFERENCES moz_places(id)
+                                  ON DELETE RESTRICT,
+                 keyword TEXT NOT NULL UNIQUE
+             )",
+            // Add an index on synced keywords, so that we can search for
+            // mismatched keywords without a table scan.
+            "CREATE INDEX IF NOT EXISTS moz_bookmarks_synced_keywords
+             ON moz_bookmarks_synced(keyword) WHERE keyword NOT NULL",
+            // Migrate synced keywords into their own table, so that they're
+            // available via `bookmarks_get_url_for_keyword` before the next
+            // sync.
+            "INSERT OR IGNORE INTO moz_keywords(keyword, place_id)
+             SELECT keyword, placeId
+             FROM moz_bookmarks_synced
+             WHERE placeId NOT NULL AND
+                   keyword NOT NULL",
+        ],
+        || Ok(()),
+    )?;
+    migration(
+        db,
+        from,
+        11,
+        &[
+            // Greatly helps the multi-join query in frecency.
+            "CREATE INDEX IF NOT EXISTS visits_from_type_idx
+             ON moz_historyvisits(from_visit, visit_type)",
+        ],
+        || Ok(()),
+    )?;
+    migration(
+        db,
+        from,
+        12,
+        &[
+            // Reconciled items didn't end up with the correct syncStatus.
+            // See #3504
+            "UPDATE moz_bookmarks AS b
+             SET syncStatus = 2 -- SyncStatus::Normal
+             WHERE EXISTS (SELECT 1 FROM moz_bookmarks_synced
+                                    WHERE guid = b.guid)",
+        ],
+        || Ok(()),
+    )?;
+    migration(db, from, 13, &[CREATE_SHARED_SCHEMA_SQL], || Ok(()))?; // moz_places_metadata.
+    migration(
+        db,
+        from,
+        14,
+        &[
+            // Changing `moz_places_metadata` structure, drop and recreate it.
+            "DROP TABLE moz_places_metadata",
+            CREATE_SHARED_SCHEMA_SQL,
+        ],
+        || Ok(()),
+    )?;
+
+    // End of old style migrations, starting with the 15 -> 16 migration, we just use match
+    // statements
+
+    match from {
+        // Skip the old style migrations
+        n if n < 15 => (),
+        // New-style migrations start here
+        15 => {
+            // Add the `unknownFields` column
+            //
+            // This migration was rolled out incorrectly and we need to check if it was already
+            // applied (https://github.com/mozilla/application-services/issues/5464)
+            let exists_sql = "SELECT 1 FROM pragma_table_info('moz_bookmarks_synced') WHERE name = 'unknownFields'";
+            let add_column_sql = "ALTER TABLE moz_bookmarks_synced ADD COLUMN unknownFields TEXT";
+            if !db.exists(exists_sql, [])? {
+                db.execute(add_column_sql, [])?;
+            }
+        }
+        16 => {
+            // Add the `unknownFields` column for history
+            db.execute("ALTER TABLE moz_places ADD COLUMN unknown_fields TEXT", ())?;
+            db.execute(
+                "ALTER TABLE moz_historyvisits ADD COLUMN unknown_fields TEXT",
+                (),
+            )?;
+        }
+        // Add more migrations here...
+
+        // Any other from value indicates that something very wrong happened
+        _ => panic!(
+            "Places does not have a v{} -> v{} migration",
+            from,
+            from + 1
+        ),
+    }
+
+    Ok(())
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::db::{db::PlacesInitializer, PlacesDb};
+    use crate::error::Result;
+    use sql_support::open_database::test_utils::MigratedDatabaseFile;
+    use std::collections::BTreeSet;
+    use sync_guid::Guid as SyncGuid;
+    use url::Url;
+
+    #[test]
+    fn test_create_schema_twice() {
+        let conn = PlacesDb::open_in_memory(ConnectionType::ReadWrite).expect("no memory db");
+        conn.execute_batch(CREATE_SHARED_SCHEMA_SQL)
+            .expect("should allow running twice");
+    }
+
+    fn has_tombstone(conn: &PlacesDb, guid: &SyncGuid) -> bool {
+        let count: rusqlite::Result<Option<u32>> = conn.try_query_row(
+            "SELECT COUNT(*) from moz_places_tombstones
+                     WHERE guid = :guid",
+            &[(":guid", guid)],
+            |row| row.get::<_, u32>(0),
+            true,
+        );
+        count.unwrap().unwrap() == 1
+    }
+
+    #[test]
+    fn test_places_no_tombstone() {
+        let conn = PlacesDb::open_in_memory(ConnectionType::ReadWrite).expect("no memory db");
+        let guid = SyncGuid::random();
+
+        conn.execute_cached(
+            "INSERT INTO moz_places (guid, url, url_hash) VALUES (:guid, :url, hash(:url))",
+            &[
+                (":guid", &guid as &dyn rusqlite::ToSql),
+                (
+                    ":url",
+                    &String::from(Url::parse("http://example.com").expect("valid url")),
+                ),
+            ],
+        )
+        .expect("should work");
+
+        let place_id = conn.last_insert_rowid();
+        conn.execute_cached(
+            "DELETE FROM moz_places WHERE id = :id",
+            &[(":id", &place_id)],
+        )
+        .expect("should work");
+
+        // should not have a tombstone.
+        assert!(!has_tombstone(&conn, &guid));
+    }
+
+    #[test]
+    fn test_places_tombstone_removal() {
+        let conn = PlacesDb::open_in_memory(ConnectionType::ReadWrite).expect("no memory db");
+        let guid = SyncGuid::random();
+
+        conn.execute_cached(
+            "INSERT INTO moz_places_tombstones VALUES (:guid)",
+            &[(":guid", &guid)],
+        )
+        .expect("should work");
+
+        // insert into moz_places - the tombstone should be removed.
+        conn.execute_cached(
+            "INSERT INTO moz_places (guid, url, url_hash, sync_status)
+             VALUES (:guid, :url, hash(:url), :sync_status)",
+            &[
+                (":guid", &guid as &dyn rusqlite::ToSql),
+                (
+                    ":url",
+                    &String::from(Url::parse("http://example.com").expect("valid url")),
+                ),
+                (":sync_status", &SyncStatus::Normal),
+            ],
+        )
+        .expect("should work");
+        assert!(!has_tombstone(&conn, &guid));
+    }
+
+    #[test]
+    fn test_bookmark_check_constraints() {
+        let conn = PlacesDb::open_in_memory(ConnectionType::ReadWrite).expect("no memory db");
+
+        // type==BOOKMARK but null fk
+        let e = conn
+            .execute_cached(
+                "INSERT INTO moz_bookmarks
+                    (fk, type, parent, position, dateAdded, lastModified, guid)
+                 VALUES
+                    (NULL, 1, 0, 0, 1, 1, 'fake_guid___')",
+                [],
+            )
+            .expect_err("should fail");
+        assert!(
+            e.to_string().starts_with("CHECK constraint failed"),
+            "Expected `CHECK` failure, got: {:?}",
+            e,
+        );
+
+        // type!=BOOKMARK and non-null fk
+        let e = conn
+            .execute_cached(
+                "INSERT INTO moz_bookmarks
+                    (fk, type, parent, position, dateAdded, lastModified, guid)
+                 VALUES
+                    (1, 2, 0, 0, 1, 1, 'fake_guid___')",
+                [],
+            )
+            .expect_err("should fail");
+        assert!(
+            e.to_string().starts_with("CHECK constraint failed"),
+            "Expected `CHECK` failure, got: {:?}",
+            e,
+        );
+
+        // null parent for item other than the root
+        let e = conn
+            .execute_cached(
+                "INSERT INTO moz_bookmarks
+                    (fk, type, parent, position, dateAdded, lastModified, guid)
+                 VALUES
+                    (NULL, 2, NULL, 0, 1, 1, 'fake_guid___')",
+                [],
+            )
+            .expect_err("should fail");
+        assert!(
+            e.to_string().starts_with("CHECK constraint failed"),
+            "Expected `CHECK` failure, got: {:?}",
+            e,
+        );
+
+        // Invalid length guid
+        let e = conn
+            .execute_cached(
+                "INSERT INTO moz_bookmarks
+                    (fk, type, parent, position, dateAdded, lastModified, guid)
+                 VALUES
+                    (NULL, 2, 0, 0, 1, 1, 'fake_guid')",
+                [],
+            )
+            .expect_err("should fail");
+        assert!(
+            e.to_string().starts_with("CHECK constraint failed"),
+            "Expected `CHECK` failure, got: {:?}",
+            e,
+        );
+    }
+
+    fn select_simple_int(conn: &PlacesDb, stmt: &str) -> u32 {
+        let count: Result<Option<u32>> =
+            conn.try_query_row(stmt, [], |row| Ok(row.get::<_, u32>(0)?), false);
+        count.unwrap().unwrap()
+    }
+
+    fn get_foreign_count(conn: &PlacesDb, guid: &SyncGuid) -> u32 {
+        let count: Result<Option<u32>> = conn.try_query_row(
+            "SELECT foreign_count from moz_places
+                     WHERE guid = :guid",
+            &[(":guid", guid)],
+            |row| Ok(row.get::<_, u32>(0)?),
+            true,
+        );
+        count.unwrap().unwrap()
+    }
+
+    #[test]
+    fn test_bookmark_foreign_count_triggers() {
+        // create the place.
+        let conn = PlacesDb::open_in_memory(ConnectionType::ReadWrite).expect("no memory db");
+        let guid1 = SyncGuid::random();
+        let url1 = Url::parse("http://example.com").expect("valid url");
+        let guid2 = SyncGuid::random();
+        let url2 = Url::parse("http://example2.com").expect("valid url");
+
+        conn.execute_cached(
+            "INSERT INTO moz_places (guid, url, url_hash) VALUES (:guid, :url, hash(:url))",
+            &[
+                (":guid", &guid1 as &dyn rusqlite::ToSql),
+                (":url", &String::from(url1)),
+            ],
+        )
+        .expect("should work");
+        let place_id1 = conn.last_insert_rowid();
+
+        conn.execute_cached(
+            "INSERT INTO moz_places (guid, url, url_hash) VALUES (:guid, :url, hash(:url))",
+            &[
+                (":guid", &guid2 as &dyn rusqlite::ToSql),
+                (":url", &String::from(url2)),
+            ],
+        )
+        .expect("should work");
+        let place_id2 = conn.last_insert_rowid();
+
+        assert_eq!(get_foreign_count(&conn, &guid1), 0);
+        assert_eq!(get_foreign_count(&conn, &guid2), 0);
+
+        // record visits for both URLs, otherwise the place itself will be removed with the bookmark
+        conn.execute_cached(
+            "INSERT INTO moz_historyvisits (place_id, visit_date, visit_type, is_local)
+             VALUES (:place, 10000000, 1, 0);",
+            &[(":place", &place_id1)],
+        )
+        .expect("should work");
+        conn.execute_cached(
+            "INSERT INTO moz_historyvisits (place_id, visit_date, visit_type, is_local)
+             VALUES (:place, 10000000, 1, 1);",
+            &[(":place", &place_id2)],
+        )
+        .expect("should work");
+
+        // create a bookmark pointing at it.
+        conn.execute_cached(
+            "INSERT INTO moz_bookmarks
+                (fk, type, parent, position, dateAdded, lastModified, guid)
+            VALUES
+                (:place_id, 1, 1, 0, 1, 1, 'fake_guid___')",
+            &[(":place_id", &place_id1)],
+        )
+        .expect("should work");
+        assert_eq!(get_foreign_count(&conn, &guid1), 1);
+        assert_eq!(get_foreign_count(&conn, &guid2), 0);
+
+        // change the bookmark to point at a different place.
+        conn.execute_cached(
+            "UPDATE moz_bookmarks SET fk = :new_place WHERE guid = 'fake_guid___';",
+            &[(":new_place", &place_id2)],
+        )
+        .expect("should work");
+        assert_eq!(get_foreign_count(&conn, &guid1), 0);
+        assert_eq!(get_foreign_count(&conn, &guid2), 1);
+
+        conn.execute("DELETE FROM moz_bookmarks WHERE guid = 'fake_guid___';", [])
+            .expect("should work");
+        assert_eq!(get_foreign_count(&conn, &guid1), 0);
+        assert_eq!(get_foreign_count(&conn, &guid2), 0);
+    }
+
+    #[test]
+    fn test_bookmark_synced_foreign_count_triggers() {
+        // create the place.
+        let conn = PlacesDb::open_in_memory(ConnectionType::ReadWrite).expect("no memory db");
+
+        let url = Url::parse("http://example.com").expect("valid url");
+
+        conn.execute_cached(
+            "INSERT INTO moz_places (guid, url, url_hash) VALUES ('fake_guid___', :url, hash(:url))",
+            &[(":url", &String::from(url))],
+        )
+        .expect("should work");
+        let place_id = conn.last_insert_rowid();
+
+        assert_eq!(get_foreign_count(&conn, &"fake_guid___".into()), 0);
+
+        // create a bookmark pointing at it.
+        conn.execute_cached(
+            "INSERT INTO moz_bookmarks_synced
+                (placeId, guid)
+            VALUES
+                (:place_id, 'fake_guid___')",
+            &[(":place_id", &place_id)],
+        )
+        .expect("should work");
+        assert_eq!(get_foreign_count(&conn, &"fake_guid___".into()), 1);
+
+        // delete it.
+        conn.execute_cached(
+            "DELETE FROM moz_bookmarks_synced WHERE guid = 'fake_guid___';",
+            [],
+        )
+        .expect("should work");
+        assert_eq!(get_foreign_count(&conn, &"fake_guid___".into()), 0);
+    }
+
+    #[test]
+    fn test_bookmark_delete_restrict() {
+        let conn = PlacesDb::open_in_memory(ConnectionType::ReadWrite).expect("no memory db");
+        conn.execute_all(&[
+            "INSERT INTO moz_places
+                (guid, url, url_hash)
+             VALUES
+                ('place_guid__', 'http://example.com/', hash('http://example.com/'))",
+            "INSERT INTO moz_bookmarks
+                (type, parent, position, dateAdded, lastModified, guid, fk)
+            VALUES
+                (1, 1, 0, 1, 1, 'fake_guid___', last_insert_rowid())",
+        ])
+        .expect("should be able to do the inserts");
+
+        // Should be impossible to delete the place.
+        conn.execute("DELETE FROM moz_places WHERE guid = 'place_guid__';", [])
+            .expect_err("should fail");
+
+        // delete the bookmark.
+        conn.execute("DELETE FROM moz_bookmarks WHERE guid = 'fake_guid___';", [])
+            .expect("should be able to delete the bookmark");
+
+        // now we should be able to delete the place.
+        conn.execute("DELETE FROM moz_places WHERE guid = 'place_guid__';", [])
+            .expect("should now be able to delete the place");
+    }
+
+    #[test]
+    fn test_bookmark_auto_deletes() {
+        let conn = PlacesDb::open_in_memory(ConnectionType::ReadWrite).expect("no memory db");
+
+        conn.execute_all(&[
+            // A folder to hold a bookmark.
+            "INSERT INTO moz_bookmarks
+                (type, parent, position, dateAdded, lastModified, guid)
+            VALUES
+                (3, 1, 0, 1, 1, 'folder_guid_')",
+            // A place for the bookmark.
+            "INSERT INTO moz_places
+                (guid, url, url_hash)
+            VALUES ('place_guid__', 'http://example.com/', hash('http://example.com/'))",
+            // The bookmark.
+            "INSERT INTO moz_bookmarks
+                (fk, type, parent, position, dateAdded, lastModified, guid)
+            VALUES
+                --fk,                  type
+                (last_insert_rowid(), 1,
+                -- parent
+                 (SELECT id FROM moz_bookmarks WHERE guid = 'folder_guid_'),
+                -- positon, dateAdded, lastModified, guid
+                   0,       1,         1,           'bookmarkguid')",
+        ])
+        .expect("inserts should work");
+
+        // Delete the folder - the bookmark should cascade delete.
+        conn.execute("DELETE FROM moz_bookmarks WHERE guid = 'folder_guid_';", [])
+            .expect("should work");
+
+        // folder should be gone.
+        assert_eq!(
+            select_simple_int(
+                &conn,
+                "SELECT count(*) FROM moz_bookmarks WHERE guid = 'folder_guid_'"
+            ),
+            0
+        );
+        // bookmark should be gone.
+        assert_eq!(
+            select_simple_int(
+                &conn,
+                "SELECT count(*) FROM moz_bookmarks WHERE guid = 'bookmarkguid';"
+            ),
+            0
+        );
+
+        // Place should also be gone as bookmark url had no visits.
+        assert_eq!(
+            select_simple_int(
+                &conn,
+                "SELECT COUNT(*) from moz_places WHERE guid = 'place_guid__';"
+            ),
+            0
+        );
+    }
+
+    #[test]
+    fn test_bookmark_auto_deletes_place_remains() {
+        let conn = PlacesDb::open_in_memory(ConnectionType::ReadWrite).expect("no memory db");
+
+        conn.execute_all(&[
+            // A folder to hold a bookmark.
+            "INSERT INTO moz_bookmarks
+                (type, parent, position, dateAdded, lastModified, guid)
+            VALUES
+                (3, 1, 0, 1, 1, 'folder_guid_')",
+            // A place for the bookmark.
+            "INSERT INTO moz_places
+                (guid, url, url_hash, foreign_count) -- here we pretend it has a foreign count.
+            VALUES ('place_guid__', 'http://example.com/', hash('http://example.com/'), 1)",
+            // The bookmark.
+            "INSERT INTO moz_bookmarks
+                (fk, type, parent, position, dateAdded, lastModified, guid)
+            VALUES
+                --fk,                  type
+                (last_insert_rowid(), 1,
+                -- parent
+                 (SELECT id FROM moz_bookmarks WHERE guid = 'folder_guid_'),
+                -- positon, dateAdded, lastModified, guid
+                   0,       1,         1,           'bookmarkguid')",
+        ])
+        .expect("inserts should work");
+
+        // Delete the folder - the bookmark should cascade delete.
+        conn.execute("DELETE FROM moz_bookmarks WHERE guid = 'folder_guid_';", [])
+            .expect("should work");
+
+        // folder should be gone.
+        assert_eq!(
+            select_simple_int(
+                &conn,
+                "SELECT count(*) FROM moz_bookmarks WHERE guid = 'folder_guid_'"
+            ),
+            0
+        );
+        // bookmark should be gone.
+        assert_eq!(
+            select_simple_int(
+                &conn,
+                "SELECT count(*) FROM moz_bookmarks WHERE guid = 'bookmarkguid';"
+            ),
+            0
+        );
+
+        // Place should remain as we pretended it has a foreign reference.
+        assert_eq!(
+            select_simple_int(
+                &conn,
+                "SELECT COUNT(*) from moz_places WHERE guid = 'place_guid__';"
+            ),
+            1
+        );
+    }
+
+    #[test]
+    fn test_bookmark_tombstone_auto_created() {
+        let conn = PlacesDb::open_in_memory(ConnectionType::ReadWrite).expect("no memory db");
+        conn.execute(
+            &format!(
+                "INSERT INTO moz_bookmarks
+                        (syncStatus, type, parent, position, dateAdded, lastModified, guid)
+                     VALUES
+                        ({}, 3, 1, 0, 1, 1, 'bookmarkguid')",
+                SyncStatus::Normal as u8
+            ),
+            [],
+        )
+        .expect("should insert regular bookmark folder");
+        conn.execute("DELETE FROM moz_bookmarks WHERE guid = 'bookmarkguid'", [])
+            .expect("should delete");
+        // should have a tombstone.
+        assert_eq!(
+            select_simple_int(&conn, "SELECT COUNT(*) from moz_bookmarks_deleted"),
+            1
+        );
+        conn.execute("DELETE from moz_bookmarks_deleted", [])
+            .expect("should delete");
+        conn.execute(
+            &format!(
+                "INSERT INTO moz_bookmarks
+                        (syncStatus, type, parent, position, dateAdded, lastModified, guid)
+                     VALUES
+                        ({}, 3, 1, 0, 1, 1, 'bookmarkguid')",
+                SyncStatus::New as u8
+            ),
+            [],
+        )
+        .expect("should insert regular bookmark folder");
+        conn.execute("DELETE FROM moz_bookmarks WHERE guid = 'bookmarkguid'", [])
+            .expect("should delete");
+        // should not have a tombstone as syncStatus is new.
+        assert_eq!(
+            select_simple_int(&conn, "SELECT COUNT(*) from moz_bookmarks_deleted"),
+            0
+        );
+    }
+
+    #[test]
+    fn test_bookmark_tombstone_auto_deletes() {
+        let conn = PlacesDb::open_in_memory(ConnectionType::ReadWrite).expect("no memory db");
+        conn.execute(
+            "INSERT into moz_bookmarks_deleted VALUES ('bookmarkguid', 1)",
+            [],
+        )
+        .expect("should insert tombstone");
+        assert_eq!(
+            select_simple_int(&conn, "SELECT COUNT(*) from moz_bookmarks_deleted"),
+            1
+        );
+        // create a bookmark with the same guid.
+        conn.execute(
+            "INSERT INTO moz_bookmarks
+                        (type, parent, position, dateAdded, lastModified, guid)
+                     VALUES
+                        (3, 1, 0, 1, 1, 'bookmarkguid')",
+            [],
+        )
+        .expect("should insert regular bookmark folder");
+        // tombstone should have vanished.
+        assert_eq!(
+            select_simple_int(&conn, "SELECT COUNT(*) from moz_bookmarks_deleted"),
+            0
+        );
+    }
+
+    #[test]
+    fn test_bookmark_tombstone_auto_deletes_on_update() {
+        let conn = PlacesDb::open_in_memory(ConnectionType::ReadWrite).expect("no memory db");
+
+        // check updates do the right thing.
+        conn.execute(
+            "INSERT into moz_bookmarks_deleted VALUES ('bookmarkguid', 1)",
+            [],
+        )
+        .expect("should insert tombstone");
+
+        // create a bookmark with a different guid.
+        conn.execute(
+            "INSERT INTO moz_bookmarks
+                        (type, parent, position, dateAdded, lastModified, guid)
+                     VALUES
+                        (3, 1, 0, 1, 1, 'fake_guid___')",
+            [],
+        )
+        .expect("should insert regular bookmark folder");
+        // tombstone should remain.
+        assert_eq!(
+            select_simple_int(&conn, "SELECT COUNT(*) from moz_bookmarks_deleted"),
+            1
+        );
+        // update guid - should fail as we have a trigger with RAISEs.
+        conn.execute(
+            "UPDATE moz_bookmarks SET guid = 'bookmarkguid'
+             WHERE guid = 'fake_guid___'",
+            [],
+        )
+        .expect_err("changing the guid should fail");
+    }
+
+    #[test]
+    fn test_origin_triggers_simple_removal() {
+        let conn = PlacesDb::open_in_memory(ConnectionType::ReadWrite).expect("no memory db");
+        let guid = SyncGuid::random();
+        let url = String::from(Url::parse("http://example.com").expect("valid url"));
+
+        conn.execute(
+            "INSERT INTO moz_places (guid, url, url_hash) VALUES (:guid, :url, hash(:url))",
+            rusqlite::named_params! {
+                ":guid": &guid,
+                ":url": &url,
+            },
+        )
+        .expect("should work");
+        // origins are maintained via triggers, so make sure they are done.
+        crate::storage::delete_pending_temp_tables(&conn).expect("should work");
+
+        // We should have inserted the origin.
+        assert_eq!(
+            select_simple_int(
+                &conn,
+                "SELECT count(*) FROM moz_origins WHERE host = 'example.com'"
+            ),
+            1
+        );
+
+        // delete the place, ensure triggers have run, and check origin has gone.
+        conn.execute("DELETE FROM moz_places", [])
+            .expect("should work");
+        crate::storage::delete_pending_temp_tables(&conn).expect("should work");
+        assert_eq!(
+            select_simple_int(&conn, "SELECT count(*) FROM moz_origins"),
+            0
+        );
+    }
+
+    const CREATE_V15_DB: &str = include_str!("../../sql/tests/create_v15_db.sql");
+
+    #[test]
+    fn test_upgrade_schema_15_16() {
+        let db_file = MigratedDatabaseFile::new(PlacesInitializer::new_for_test(), CREATE_V15_DB);
+
+        db_file.upgrade_to(16);
+        let db = db_file.open();
+
+        // Test the unknownFields column was added
+        assert_eq!(
+            db.query_one::<String>("SELECT type FROM pragma_table_info('moz_bookmarks_synced') WHERE name = 'unknownFields'").unwrap(),
+            "TEXT"
+        );
+    }
+
+    #[test]
+    fn test_gh5464() {
+        // Test the gh-5464 error case: A user with the `v16` schema, but with `user_version` set
+        // to 15
+        let db_file = MigratedDatabaseFile::new(PlacesInitializer::new_for_test(), CREATE_V15_DB);
+        db_file.upgrade_to(16);
+        let db = db_file.open();
+        db.execute("PRAGMA user_version=15", []).unwrap();
+        drop(db);
+        db_file.upgrade_to(16);
+    }
+
+    #[test]
+    fn test_all_upgrades() {
+        // Test the migration process in general: open a fresh DB and a DB that's gone through the migration
+        // process.  Check that the schemas match.
+        let fresh_db = PlacesDb::open_in_memory(ConnectionType::ReadWrite).unwrap();
+
+        let db_file = MigratedDatabaseFile::new(PlacesInitializer::new_for_test(), CREATE_V15_DB);
+        db_file.run_all_upgrades();
+        let upgraded_db = db_file.open();
+
+        assert_eq!(
+            fresh_db.query_one::<u32>("PRAGMA user_version").unwrap(),
+            upgraded_db.query_one::<u32>("PRAGMA user_version").unwrap(),
+        );
+        let all_tables = [
+            "moz_places",
+            "moz_places_tombstones",
+            "moz_places_stale_frecencies",
+            "moz_historyvisits",
+            "moz_historyvisit_tombstones",
+            "moz_inputhistory",
+            "moz_bookmarks",
+            "moz_bookmarks_deleted",
+            "moz_origins",
+            "moz_meta",
+            "moz_tags",
+            "moz_tags_relation",
+            "moz_bookmarks_synced",
+            "moz_bookmarks_synced_structure",
+            "moz_bookmarks_synced_tag_relation",
+            "moz_keywords",
+            "moz_places_metadata",
+            "moz_places_metadata_search_queries",
+        ];
+        #[derive(Debug, Ord, PartialOrd, Eq, PartialEq)]
+        struct ColumnInfo {
+            name: String,
+            type_: String,
+            not_null: bool,
+            default_value: Option<String>,
+            pk: bool,
+        }
+
+        fn get_table_column_info(conn: &Connection, table_name: &str) -> BTreeSet<ColumnInfo> {
+            let mut stmt = conn
+                .prepare("SELECT name, type, `notnull`, dflt_value, pk FROM pragma_table_info(?)")
+                .unwrap();
+            stmt.query_map((table_name,), |row| {
+                Ok(ColumnInfo {
+                    name: row.get(0)?,
+                    type_: row.get(1)?,
+                    not_null: row.get(2)?,
+                    default_value: row.get(3)?,
+                    pk: row.get(4)?,
+                })
+            })
+            .unwrap()
+            .collect::<rusqlite::Result<BTreeSet<_>>>()
+            .unwrap()
+        }
+        for table_name in all_tables {
+            assert_eq!(
+                get_table_column_info(&upgraded_db, table_name),
+                get_table_column_info(&fresh_db, table_name),
+            );
+        }
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/places/db/tx/coop_transaction.rs.html b/book/rust-docs/src/places/db/tx/coop_transaction.rs.html new file mode 100644 index 0000000000..5abf2525f9 --- /dev/null +++ b/book/rust-docs/src/places/db/tx/coop_transaction.rs.html @@ -0,0 +1,461 @@ +coop_transaction.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! This implements "cooperative transactions" for places. It relies on our
+//! decision to have exactly 1 general purpose "writer" connection and exactly
+//! one "sync writer" - ie, exactly 2 write connections.
+//!
+//! We'll describe the implementation and strategy, but note that most callers
+//! should use `PlacesDb::begin_transaction()`, which will do the right thing
+//! for your db type.
+//!
+//! The idea is that anything that uses the sync connection should use
+//! `chunked_coop_trransaction`. Code using this should regularly call
+//! `maybe_commit()`, and every second, will commit the transaction and start
+//! a new one.
+//!
+//! This means that in theory the other writable connection can start
+//! transactions and should block for a max of 1 second - well under the 5
+//! seconds before that other writer will fail with a SQLITE_BUSY or similar error.
+//!
+//! However, in practice we see the writer thread being starved - even though
+//! it's waiting for a lock, the sync thread still manages to re-get the lock.
+//! In other words, the locks used by sqlite aren't "fair".
+//!
+//! So we mitigate this with a simple approach that works fine within our
+//! "exactly 2 writers" constraints:
+//! * Each database connection shares a mutex.
+//! * Before starting a transaction, each connection locks the mutex.
+//! * It then starts an "immediate" transaction - because sqlite now holds a
+//!   lock on our behalf, we release the lock on the mutex.
+//!
+//! In other words, the lock is held only while obtaining the DB lock, then
+//! immediately released.
+//!
+//! The end result here is that if either connection is waiting for the
+//! database lock because the other already holds it, the waiting one is
+//! guaranteed to get the database lock next.
+//!
+//! One additional wrinkle here is that even if there was exactly one writer,
+//! there's still a possibility of SQLITE_BUSY if the database is being
+//! checkpointed. So we handle that case and perform exactly 1 retry.
+
+use crate::api::places_api::ConnectionType;
+use crate::db::PlacesDb;
+use crate::error::*;
+use parking_lot::Mutex;
+use rusqlite::{Connection, TransactionBehavior};
+use sql_support::{ConnExt, UncheckedTransaction};
+use std::ops::Deref;
+use std::time::{Duration, Instant};
+
+impl PlacesDb {
+    /// Begin a ChunkedCoopTransaction. Must be called from the
+    /// sync connection, see module doc for details.
+    pub(super) fn chunked_coop_trransaction(&self) -> Result<ChunkedCoopTransaction<'_>> {
+        // Note: if there's actually a reason for a write conn to take this, we
+        // can consider relaxing this. It's not required for correctness, just happens
+        // to be the right choice for everything we expose and plan on exposing.
+        assert_eq!(
+            self.conn_type(),
+            ConnectionType::Sync,
+            "chunked_coop_trransaction must only be called by the Sync connection"
+        );
+        // Note that we don't allow commit_after as a param because it
+        // is closely related to the timeouts configured on the database
+        // itself.
+        let commit_after = Duration::from_millis(1000);
+        ChunkedCoopTransaction::new(self.conn(), commit_after, &self.coop_tx_lock)
+    }
+
+    /// Begin a "coop" transaction. Must be called from the write connection, see
+    /// module doc for details.
+    pub(super) fn coop_transaction(&self) -> Result<UncheckedTransaction<'_>> {
+        // Only validate tranaction types for ConnectionType::ReadWrite.
+        assert_eq!(
+            self.conn_type(),
+            ConnectionType::ReadWrite,
+            "coop_transaction must only be called on the ReadWrite connection"
+        );
+        let _lock = self.coop_tx_lock.lock();
+        get_tx_with_retry_on_locked(self.conn())
+    }
+}
+
+/// This transaction is suitable for when a transaction is used purely for
+/// performance reasons rather than for data-integrity reasons, or when it's
+/// used for integrity but held longer than strictly necessary for performance
+/// reasons (ie, when it could be multiple transactions and still guarantee
+/// integrity.) Examples of this might be for performance when updating a larger
+/// number of rows, but data integrity concerns could be addressed by using
+/// multiple, smaller transactions.
+///
+/// You should regularly call .maybe_commit() as part of your
+/// processing, and if the current transaction has been open for greater than
+/// some duration the transaction will be committed and another one
+/// started. You should always call .commit() at the end. Note that there is
+/// no .rollback() method as it will be very difficult to work out what was
+/// previously commited and therefore what was rolled back - if you need to
+/// explicitly roll-back, this probably isn't what you should be using. Note
+/// that SQLite might rollback for its own reasons though.
+///
+/// Note that this can still be used for transactions which ensure data
+/// integrity. For example, if you are processing a large group of items, and
+/// each individual item requires multiple updates, you will probably want to
+/// ensure you call .maybe_commit() after every item rather than after
+/// each individual database update.
+pub struct ChunkedCoopTransaction<'conn> {
+    tx: UncheckedTransaction<'conn>,
+    commit_after: Duration,
+    coop: &'conn Mutex<()>,
+}
+
+impl<'conn> ChunkedCoopTransaction<'conn> {
+    /// Begin a new transaction which may be split into multiple transactions
+    /// for performance reasons. Cannot be nested, but this is not
+    /// enforced - however, it is enforced by SQLite; use a rusqlite `savepoint`
+    /// for nested transactions.
+    pub fn new(
+        conn: &'conn Connection,
+        commit_after: Duration,
+        coop: &'conn Mutex<()>,
+    ) -> Result<Self> {
+        let _lock = coop.lock();
+        let tx = get_tx_with_retry_on_locked(conn)?;
+        Ok(Self {
+            tx,
+            commit_after,
+            coop,
+        })
+    }
+
+    /// Returns `true` if the current transaction has been open for longer than
+    /// the requested time, and should be committed; `false` otherwise. In most
+    /// cases, there's no need to use this method, since `maybe_commit()` does
+    /// so internally. It's exposed for consumers that need to run additional
+    /// pre-commit logic, like cleaning up temp tables.
+    ///
+    /// If this method returns `true`, it's guaranteed that `maybe_commit()`
+    /// will commit the transaction.
+    #[inline]
+    pub fn should_commit(&self) -> bool {
+        self.tx.started_at.elapsed() >= self.commit_after
+    }
+
+    /// Checks to see if we have held a transaction for longer than the
+    /// requested time, and if so, commits the current transaction and opens
+    /// another.
+    #[inline]
+    pub fn maybe_commit(&mut self) -> Result<()> {
+        if self.should_commit() {
+            log::debug!("ChunkedCoopTransaction commiting after taking allocated time");
+            self.commit_and_start_new_tx()?;
+        }
+        Ok(())
+    }
+
+    fn commit_and_start_new_tx(&mut self) -> Result<()> {
+        // We can't call self.tx.commit() here as it wants to consume
+        // self.tx, and we can't set up the new self.tx first as then
+        // we'll be trying to start a new transaction while the current
+        // one is in progress. So explicitly set the finished flag on it.
+        self.tx.finished = true;
+        self.tx.execute_batch("COMMIT")?;
+        // acquire a lock on our cooperator - if our only other writer
+        // thread holds a write lock we'll block until it is released.
+        // Note however that sqlite might still return a locked error if the
+        // database is being checkpointed - so we still perform exactly 1 retry,
+        // which we do while we have the lock, because we don't want our other
+        // write connection to win this race either.
+        let _lock = self.coop.lock();
+        self.tx = get_tx_with_retry_on_locked(self.tx.conn)?;
+        Ok(())
+    }
+
+    /// Consumes and commits a ChunkedCoopTransaction transaction.
+    pub fn commit(self) -> Result<()> {
+        self.tx.commit()?;
+        Ok(())
+    }
+
+    /// Consumes and rolls a ChunkedCoopTransaction, but potentially only back
+    /// to the last `maybe_commit`.
+    pub fn rollback(self) -> Result<()> {
+        self.tx.rollback()?;
+        Ok(())
+    }
+}
+
+impl<'conn> Deref for ChunkedCoopTransaction<'conn> {
+    type Target = Connection;
+
+    #[inline]
+    fn deref(&self) -> &Connection {
+        self.tx.conn
+    }
+}
+
+impl<'conn> ConnExt for ChunkedCoopTransaction<'conn> {
+    #[inline]
+    fn conn(&self) -> &Connection {
+        self
+    }
+}
+
+// A helper that attempts to get an Immediate lock on the DB. If it fails with
+// a "busy" or "locked" error, it does exactly 1 retry.
+fn get_tx_with_retry_on_locked(conn: &Connection) -> Result<UncheckedTransaction<'_>> {
+    let behavior = TransactionBehavior::Immediate;
+    match UncheckedTransaction::new(conn, behavior) {
+        Ok(tx) => Ok(tx),
+        Err(rusqlite::Error::SqliteFailure(err, _))
+            if err.code == rusqlite::ErrorCode::DatabaseBusy
+                || err.code == rusqlite::ErrorCode::DatabaseLocked =>
+        {
+            // retry the lock - we assume that this lock request still
+            // blocks for the default period, so we don't need to sleep
+            // etc.
+            let started_at = Instant::now();
+            log::warn!("Attempting to get a read lock failed - doing one retry");
+            let tx = UncheckedTransaction::new(conn, behavior).map_err(|err| {
+                log::warn!("Retrying the lock failed after {:?}", started_at.elapsed());
+                err
+            })?;
+            log::info!("Retrying the lock worked after {:?}", started_at.elapsed());
+            Ok(tx)
+        }
+        Err(e) => Err(e.into()),
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/places/db/tx/mod.rs.html b/book/rust-docs/src/places/db/tx/mod.rs.html new file mode 100644 index 0000000000..b502b8a0d7 --- /dev/null +++ b/book/rust-docs/src/places/db/tx/mod.rs.html @@ -0,0 +1,243 @@ +mod.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+mod coop_transaction;
+
+use crate::api::places_api::ConnectionType;
+use crate::error::*;
+use coop_transaction::ChunkedCoopTransaction;
+use rusqlite::Connection;
+use sql_support::{ConnExt, UncheckedTransaction};
+
+/// High level transaction type which "does the right thing" for you.
+/// Construct one with `PlacesDb::begin_transaction()`.
+pub struct PlacesTransaction<'conn>(PlacesTransactionRepr<'conn>);
+
+/// Only separated from PlacesTransaction so that the internals of the former
+/// are private (so that it can't be `matched` on, for example)
+enum PlacesTransactionRepr<'conn> {
+    ChunkedWrite(ChunkedCoopTransaction<'conn>),
+    UnchunkedWrite(UncheckedTransaction<'conn>),
+    // Note: these might seem pointless, but can allow us to ensure consistency
+    // between separate reads.
+    ReadOnly(UncheckedTransaction<'conn>),
+}
+
+impl<'conn> PlacesTransaction<'conn> {
+    /// Returns `true` if the current transaction should be committed at the
+    /// earliest opportunity.
+    #[inline]
+    pub fn should_commit(&self) -> bool {
+        match &self.0 {
+            PlacesTransactionRepr::ChunkedWrite(tx) => tx.should_commit(),
+            _ => true,
+        }
+    }
+
+    /// - For transactions on sync connnections: Checks to see if we have held a
+    ///   transaction for longer than the requested time, and if so, commits the
+    ///   current transaction and opens another.
+    /// - For transactions on other connections: `debug_assert!`s, or logs a
+    ///   warning and does nothing.
+    #[inline]
+    pub fn maybe_commit(&mut self) -> Result<()> {
+        if let PlacesTransactionRepr::ChunkedWrite(tx) = &mut self.0 {
+            tx.maybe_commit()?;
+        } else {
+            error_support::report_error!(
+                "places-nonchunked-maybe-commit",
+                "maybe_commit called on a non-chunked transaction"
+            );
+            if cfg!(debug_assertions) {
+                panic!("maybe_commit called on a non-chunked transaction");
+            }
+        }
+        Ok(())
+    }
+
+    /// Consumes and commits a PlacesTransaction transaction.
+    pub fn commit(self) -> Result<()> {
+        match self.0 {
+            PlacesTransactionRepr::ChunkedWrite(t) => t.commit()?,
+            PlacesTransactionRepr::UnchunkedWrite(t) => t.commit()?,
+            PlacesTransactionRepr::ReadOnly(t) => t.commit()?,
+        };
+        Ok(())
+    }
+
+    /// Consumes and attempst to roll back a PlacesTransaction. Note that if
+    /// maybe_commit has been called, this may only roll back as far as that
+    /// call.
+    pub fn rollback(self) -> Result<()> {
+        match self.0 {
+            PlacesTransactionRepr::ChunkedWrite(t) => t.rollback()?,
+            PlacesTransactionRepr::UnchunkedWrite(t) => t.rollback()?,
+            PlacesTransactionRepr::ReadOnly(t) => t.rollback()?,
+        };
+        Ok(())
+    }
+}
+
+impl super::PlacesDb {
+    /// Begin the "correct" transaction type for this connection.
+    ///
+    /// - For Sync connections, begins a chunked coop transaction.
+    /// - for ReadWrite connections, begins a normal coop transaction
+    /// - for ReadOnly connections, begins an unchecked transaction.
+    pub fn begin_transaction(&self) -> Result<PlacesTransaction<'_>> {
+        Ok(PlacesTransaction(match self.conn_type() {
+            ConnectionType::Sync => {
+                PlacesTransactionRepr::ChunkedWrite(self.chunked_coop_trransaction()?)
+            }
+            ConnectionType::ReadWrite => {
+                PlacesTransactionRepr::UnchunkedWrite(self.coop_transaction()?)
+            }
+            ConnectionType::ReadOnly => {
+                // Use an unchecked transaction with no locking.
+                PlacesTransactionRepr::ReadOnly(self.unchecked_transaction()?)
+            }
+        }))
+    }
+}
+
+impl<'conn> std::ops::Deref for PlacesTransaction<'conn> {
+    type Target = Connection;
+
+    fn deref(&self) -> &Connection {
+        match &self.0 {
+            PlacesTransactionRepr::ChunkedWrite(t) => t,
+            PlacesTransactionRepr::UnchunkedWrite(t) => t,
+            PlacesTransactionRepr::ReadOnly(t) => t,
+        }
+    }
+}
+
+impl<'conn> ConnExt for PlacesTransaction<'conn> {
+    #[inline]
+    fn conn(&self) -> &Connection {
+        self
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/places/error.rs.html b/book/rust-docs/src/places/error.rs.html new file mode 100644 index 0000000000..ce240c181b --- /dev/null +++ b/book/rust-docs/src/places/error.rs.html @@ -0,0 +1,555 @@ +error.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::storage::bookmarks::BookmarkRootGuid;
+use crate::types::BookmarkType;
+use error_support::{ErrorHandling, GetErrorHandling};
+use interrupt_support::Interrupted;
+
+// Result type used internally
+pub type Result<T> = std::result::Result<T, Error>;
+// Functions which are part of the public API should use this Result.
+pub type ApiResult<T> = std::result::Result<T, PlacesApiError>;
+
+// Errors we return via the public interface.
+#[derive(Debug, thiserror::Error)]
+pub enum PlacesApiError {
+    #[error("Unexpected error: {reason}")]
+    UnexpectedPlacesException { reason: String },
+
+    /// Thrown for invalid URLs
+    ///
+    /// This includes attempting to insert a URL greater than 65536 bytes
+    /// (after punycoding and percent encoding).
+    #[error("UrlParseFailed: {reason}")]
+    UrlParseFailed { reason: String },
+
+    #[error("PlacesConnectionBusy error: {reason}")]
+    PlacesConnectionBusy { reason: String },
+
+    #[error("Operation Interrupted: {reason}")]
+    OperationInterrupted { reason: String },
+
+    /// Thrown when providing a guid to a create or update function
+    /// which does not refer to a known bookmark.
+    #[error("Unknown bookmark: {reason}")]
+    UnknownBookmarkItem { reason: String },
+
+    /// Attempt to create/update/delete a bookmark item in an illegal way.
+    ///
+    /// Some examples:
+    ///  - Attempting to change the URL of a bookmark folder
+    ///  - Referring to a non-folder as the parentGUID parameter to a create or update
+    ///  - Attempting to insert a child under BookmarkRoot.Root,
+    #[error("Invalid bookmark operation: {reason}")]
+    InvalidBookmarkOperation { reason: String },
+}
+
+/// Error enum used internally
+#[derive(Debug, thiserror::Error)]
+pub enum Error {
+    #[error("Invalid place info: {0}")]
+    InvalidPlaceInfo(#[from] InvalidPlaceInfo),
+
+    #[error("The store is corrupt: {0}")]
+    Corruption(#[from] Corruption),
+
+    #[error("Error synchronizing: {0}")]
+    SyncAdapterError(#[from] sync15::Error),
+
+    #[error("Error merging: {0}")]
+    MergeError(#[from] dogear::Error),
+
+    #[error("Error parsing JSON data: {0}")]
+    JsonError(#[from] serde_json::Error),
+
+    #[error("Error executing SQL: {0}")]
+    SqlError(#[from] rusqlite::Error),
+
+    #[error("Error parsing URL: {0}")]
+    UrlParseError(#[from] url::ParseError),
+
+    #[error("A connection of this type is already open")]
+    ConnectionAlreadyOpen,
+
+    #[error("An invalid connection type was specified")]
+    InvalidConnectionType,
+
+    #[error("IO error: {0}")]
+    IoError(#[from] std::io::Error),
+
+    #[error("Operation interrupted")]
+    InterruptedError(#[from] Interrupted),
+
+    #[error("Tried to close connection on wrong PlacesApi instance")]
+    WrongApiForClose,
+
+    #[error("Incoming bookmark missing type")]
+    MissingBookmarkKind,
+
+    #[error("Synced bookmark has unsupported kind {0}")]
+    UnsupportedSyncedBookmarkKind(u8),
+
+    #[error("Synced bookmark has unsupported validity {0}")]
+    UnsupportedSyncedBookmarkValidity(u8),
+
+    // This will happen if you provide something absurd like
+    // "/" or "" as your database path. For more subtley broken paths,
+    // we'll likely return an IoError.
+    #[error("Illegal database path: {0:?}")]
+    IllegalDatabasePath(std::path::PathBuf),
+
+    #[error("UTF8 Error: {0}")]
+    Utf8Error(#[from] std::str::Utf8Error),
+
+    // This error is saying an old Fennec or iOS version isn't supported - it's never used for
+    // our specific version.
+    #[error("Can not import from database version {0}")]
+    UnsupportedDatabaseVersion(i64),
+
+    #[error("Error opening database: {0}")]
+    OpenDatabaseError(#[from] sql_support::open_database::Error),
+
+    #[error("Invalid metadata observation: {0}")]
+    InvalidMetadataObservation(#[from] InvalidMetadataObservation),
+}
+
+#[derive(Debug, thiserror::Error)]
+pub enum InvalidPlaceInfo {
+    #[error("No url specified")]
+    NoUrl,
+    #[error("Invalid guid")]
+    InvalidGuid,
+    #[error("Invalid parent: {0}")]
+    InvalidParent(String),
+    #[error("Invalid child guid")]
+    InvalidChildGuid,
+
+    // NoSuchGuid is used for guids, which aren't considered private information,
+    // so it's fine if this error, including the guid, is in the logs.
+    #[error("No such item: {0}")]
+    NoSuchGuid(String),
+
+    // NoSuchUrl is used for URLs, which are private information, so the URL
+    // itself is not included in the error.
+    #[error("No such url")]
+    NoSuchUrl,
+
+    #[error("Can't update a bookmark of type {0} with one of type {1}")]
+    MismatchedBookmarkType(u8, u8),
+
+    // Only returned when attempting to insert a bookmark --
+    // for history we just ignore it.
+    #[error("URL too long")]
+    UrlTooLong,
+
+    // Like Urls, a tag is considered private info, so the value isn't in the error.
+    #[error("The tag value is invalid")]
+    InvalidTag,
+    #[error("Cannot change the '{0}' property of a bookmark of type {1:?}")]
+    IllegalChange(&'static str, BookmarkType),
+
+    #[error("Cannot update the bookmark root {0:?}")]
+    CannotUpdateRoot(BookmarkRootGuid),
+}
+
+// Error types used when we can't continue due to corruption.
+// Note that this is currently only for "logical" corruption. Should we
+// consider mapping sqlite error codes which mean a lower-level of corruption
+// into an enum value here?
+#[derive(Debug, thiserror::Error)]
+pub enum Corruption {
+    #[error("Bookmark '{0}' has a parent of '{1}' which does not exist")]
+    NoParent(String, String),
+
+    #[error("The local roots are invalid")]
+    InvalidLocalRoots,
+
+    #[error("The synced roots are invalid")]
+    InvalidSyncedRoots,
+
+    #[error("Bookmark '{0}' has no parent but is not the bookmarks root")]
+    NonRootWithoutParent(String),
+}
+
+#[derive(Debug, thiserror::Error)]
+pub enum InvalidMetadataObservation {
+    #[error("Observed view time is invalid (too long)")]
+    ViewTimeTooLong,
+}
+
+// Define how our internal errors are handled and converted to external errors
+// See `support/error/README.md` for how this works, especially the warning about PII.
+impl GetErrorHandling for Error {
+    type ExternalError = PlacesApiError;
+
+    fn get_error_handling(&self) -> ErrorHandling<Self::ExternalError> {
+        match self {
+            Error::InvalidPlaceInfo(info) => {
+                let label = info.to_string();
+                ErrorHandling::convert(match &info {
+                    InvalidPlaceInfo::InvalidParent(..) => {
+                        PlacesApiError::InvalidBookmarkOperation { reason: label }
+                    }
+                    InvalidPlaceInfo::UrlTooLong => {
+                        PlacesApiError::UrlParseFailed { reason: label }
+                    }
+                    InvalidPlaceInfo::NoSuchGuid(..) => {
+                        PlacesApiError::UnknownBookmarkItem { reason: label }
+                    }
+                    InvalidPlaceInfo::IllegalChange(..) => {
+                        PlacesApiError::InvalidBookmarkOperation { reason: label }
+                    }
+                    InvalidPlaceInfo::CannotUpdateRoot(..) => {
+                        PlacesApiError::InvalidBookmarkOperation { reason: label }
+                    }
+                    _ => PlacesApiError::UnexpectedPlacesException { reason: label },
+                })
+                .report_error("places-invalid-place-info")
+            }
+            Error::UrlParseError(e) => {
+                // This is a known issue with invalid URLs coming from Fenix. Let's just log a
+                // warning for this one. See #5235 for more details.
+                ErrorHandling::convert(PlacesApiError::UrlParseFailed {
+                    reason: e.to_string(),
+                })
+                .log_warning()
+            }
+            // Can't pattern match on `err` without adding a dep on the sqlite3-sys crate,
+            // so we just use a `if` guard.
+            Error::SqlError(rusqlite::Error::SqliteFailure(err, _))
+                if err.code == rusqlite::ErrorCode::DatabaseBusy =>
+            {
+                ErrorHandling::convert(PlacesApiError::PlacesConnectionBusy {
+                    reason: self.to_string(),
+                })
+                .log_warning()
+            }
+            Error::SqlError(rusqlite::Error::SqliteFailure(err, _))
+                if err.code == rusqlite::ErrorCode::OperationInterrupted =>
+            {
+                ErrorHandling::convert(PlacesApiError::OperationInterrupted {
+                    reason: self.to_string(),
+                })
+                .log_info()
+            }
+            Error::InterruptedError(err) => {
+                // Can't unify with the above ... :(
+                ErrorHandling::convert(PlacesApiError::OperationInterrupted {
+                    reason: err.to_string(),
+                })
+                .log_info()
+            }
+            Error::Corruption(e) => {
+                ErrorHandling::convert(PlacesApiError::UnexpectedPlacesException {
+                    reason: e.to_string(),
+                })
+                .report_error("places-bookmarks-corruption")
+            }
+            Error::SyncAdapterError(e) => {
+                match e {
+                    sync15::Error::StoreError(store_error) => {
+                        // If it's a type-erased version of one of our errors, try
+                        // and resolve it.
+                        if let Some(places_err) = store_error.downcast_ref::<Error>() {
+                            log::info!("Recursing to resolve places error");
+                            places_err.get_error_handling()
+                        } else {
+                            ErrorHandling::convert(PlacesApiError::UnexpectedPlacesException {
+                                reason: self.to_string(),
+                            })
+                            .report_error("places-unexpected-sync-error")
+                        }
+                    }
+                    _ => ErrorHandling::convert(PlacesApiError::UnexpectedPlacesException {
+                        reason: self.to_string(),
+                    })
+                    .report_error("places-unexpected-sync-error"),
+                }
+            }
+            _ => ErrorHandling::convert(PlacesApiError::UnexpectedPlacesException {
+                reason: self.to_string(),
+            })
+            .report_error("places-unexpected-error"),
+        }
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/places/ffi.rs.html b/book/rust-docs/src/places/ffi.rs.html new file mode 100644 index 0000000000..7b2e11922c --- /dev/null +++ b/book/rust-docs/src/places/ffi.rs.html @@ -0,0 +1,1293 @@ +ffi.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
+540
+541
+542
+543
+544
+545
+546
+547
+548
+549
+550
+551
+552
+553
+554
+555
+556
+557
+558
+559
+560
+561
+562
+563
+564
+565
+566
+567
+568
+569
+570
+571
+572
+573
+574
+575
+576
+577
+578
+579
+580
+581
+582
+583
+584
+585
+586
+587
+588
+589
+590
+591
+592
+593
+594
+595
+596
+597
+598
+599
+600
+601
+602
+603
+604
+605
+606
+607
+608
+609
+610
+611
+612
+613
+614
+615
+616
+617
+618
+619
+620
+621
+622
+623
+624
+625
+626
+627
+628
+629
+630
+631
+632
+633
+634
+635
+636
+637
+638
+639
+640
+641
+642
+643
+644
+645
+646
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This module implement the traits that make the FFI code easier to manage.
+
+use crate::api::matcher::{self, search_frecent, SearchParams};
+pub use crate::api::places_api::places_api_new;
+pub use crate::error::Result;
+pub use crate::error::{ApiResult, PlacesApiError};
+pub use crate::import::common::HistoryMigrationResult;
+use crate::import::import_ios_history;
+use crate::storage;
+use crate::storage::bookmarks;
+pub use crate::storage::bookmarks::BookmarkPosition;
+pub use crate::storage::history_metadata::{
+    DocumentType, HistoryHighlight, HistoryHighlightWeights, HistoryMetadata,
+    HistoryMetadataObservation,
+};
+pub use crate::storage::RunMaintenanceMetrics;
+use crate::storage::{history, history_metadata};
+use crate::types::VisitTransitionSet;
+use crate::ConnectionType;
+use crate::UniffiCustomTypeConverter;
+use crate::VisitObservation;
+use crate::VisitType;
+use crate::{PlacesApi, PlacesDb};
+use error_support::handle_error;
+use interrupt_support::register_interrupt;
+pub use interrupt_support::SqlInterruptHandle;
+use parking_lot::Mutex;
+use std::sync::{Arc, Weak};
+use sync15::client::Sync15StorageClientInit;
+pub use sync_guid::Guid;
+pub use types::Timestamp as PlacesTimestamp;
+pub use url::Url;
+
+// From https://searchfox.org/mozilla-central/rev/1674b86019a96f076e0f98f1d0f5f3ab9d4e9020/browser/components/newtab/lib/TopSitesFeed.jsm#87
+const SKIP_ONE_PAGE_FRECENCY_THRESHOLD: i64 = 101 + 1;
+
+// `bookmarks::InsertableItem` is clear for Rust code, but just `InsertableItem` is less
+// clear in the UDL - so change some of the type names.
+pub type InsertableBookmarkItem = crate::storage::bookmarks::InsertableItem;
+pub type InsertableBookmarkFolder = crate::storage::bookmarks::InsertableFolder;
+pub type InsertableBookmarkSeparator = crate::storage::bookmarks::InsertableSeparator;
+pub use crate::storage::bookmarks::InsertableBookmark;
+
+pub use crate::storage::bookmarks::BookmarkUpdateInfo;
+
+// And types used when fetching items.
+pub type BookmarkItem = crate::storage::bookmarks::fetch::Item;
+pub type BookmarkFolder = crate::storage::bookmarks::fetch::Folder;
+pub type BookmarkSeparator = crate::storage::bookmarks::fetch::Separator;
+pub use crate::storage::bookmarks::fetch::BookmarkData;
+
+impl UniffiCustomTypeConverter for Url {
+    type Builtin = String;
+
+    fn into_custom(val: Self::Builtin) -> uniffi::Result<url::Url> {
+        match Url::parse(val.as_str()) {
+            Ok(url) => Ok(url),
+            Err(e) => Err(PlacesApiError::UrlParseFailed {
+                reason: e.to_string(),
+            }
+            .into()),
+        }
+    }
+
+    fn from_custom(obj: Self) -> Self::Builtin {
+        obj.into()
+    }
+}
+
+impl UniffiCustomTypeConverter for PlacesTimestamp {
+    type Builtin = i64;
+
+    fn into_custom(val: Self::Builtin) -> uniffi::Result<Self> {
+        Ok(PlacesTimestamp(val as u64))
+    }
+
+    fn from_custom(obj: Self) -> Self::Builtin {
+        obj.as_millis() as i64
+    }
+}
+
+impl UniffiCustomTypeConverter for VisitTransitionSet {
+    type Builtin = i32;
+
+    fn into_custom(val: Self::Builtin) -> uniffi::Result<Self> {
+        Ok(VisitTransitionSet::from_u16(val as u16).expect("Bug: Invalid VisitTransitionSet"))
+    }
+
+    fn from_custom(obj: Self) -> Self::Builtin {
+        VisitTransitionSet::into_u16(obj) as i32
+    }
+}
+
+impl UniffiCustomTypeConverter for Guid {
+    type Builtin = String;
+
+    fn into_custom(val: Self::Builtin) -> uniffi::Result<Guid> {
+        Ok(Guid::new(val.as_str()))
+    }
+
+    fn from_custom(obj: Self) -> Self::Builtin {
+        obj.into()
+    }
+}
+
+// Check for multiple write connections open at the same time
+//
+// One potential cause of #5040 is that Fenix is somehow openening multiiple write connections to
+// the places DB.  This code tests if that's happening and reports an error if so.
+lazy_static::lazy_static! {
+    static ref READ_WRITE_CONNECTIONS: Mutex<Vec<Weak<PlacesConnection>>> = Mutex::new(Vec::new());
+    static ref SYNC_CONNECTIONS: Mutex<Vec<Weak<PlacesConnection>>> = Mutex::new(Vec::new());
+}
+
+impl PlacesApi {
+    #[handle_error(crate::Error)]
+    pub fn new_connection(&self, conn_type: ConnectionType) -> ApiResult<Arc<PlacesConnection>> {
+        let db = self.open_connection(conn_type)?;
+        let connection = Arc::new(PlacesConnection::new(db));
+        register_interrupt(Arc::<PlacesConnection>::downgrade(&connection));
+        Ok(connection)
+    }
+
+    // NOTE: These methods are unused on Android but will remain needed for
+    // iOS until we can move them to the sync manager and replace their existing
+    // sync engines with ours
+    #[handle_error(crate::Error)]
+    pub fn history_sync(
+        &self,
+        key_id: String,
+        access_token: String,
+        sync_key: String,
+        tokenserver_url: Url,
+    ) -> ApiResult<String> {
+        let root_sync_key = sync15::KeyBundle::from_ksync_base64(sync_key.as_str())?;
+        let ping = self.sync_history(
+            &Sync15StorageClientInit {
+                key_id,
+                access_token,
+                tokenserver_url,
+            },
+            &root_sync_key,
+        )?;
+        Ok(serde_json::to_string(&ping).unwrap())
+    }
+
+    #[handle_error(crate::Error)]
+    pub fn bookmarks_sync(
+        &self,
+        key_id: String,
+        access_token: String,
+        sync_key: String,
+        tokenserver_url: Url,
+    ) -> ApiResult<String> {
+        let root_sync_key = sync15::KeyBundle::from_ksync_base64(sync_key.as_str())?;
+        let ping = self.sync_bookmarks(
+            &Sync15StorageClientInit {
+                key_id,
+                access_token,
+                tokenserver_url,
+            },
+            &root_sync_key,
+        )?;
+        Ok(serde_json::to_string(&ping).unwrap())
+    }
+
+    #[handle_error(crate::Error)]
+    pub fn bookmarks_reset(&self) -> ApiResult<()> {
+        self.reset_bookmarks()?;
+        Ok(())
+    }
+}
+
+pub struct PlacesConnection {
+    db: Mutex<PlacesDb>,
+    interrupt_handle: Arc<SqlInterruptHandle>,
+}
+
+impl PlacesConnection {
+    pub fn new(db: PlacesDb) -> Self {
+        Self {
+            interrupt_handle: db.new_interrupt_handle(),
+            db: Mutex::new(db),
+        }
+    }
+
+    // A helper that gets the connection from the mutex and converts errors.
+    fn with_conn<F, T>(&self, f: F) -> Result<T>
+    where
+        F: FnOnce(&PlacesDb) -> crate::error::Result<T>,
+    {
+        let conn = self.db.lock();
+        f(&conn)
+    }
+
+    // pass the SqlInterruptHandle as an object through Uniffi
+    pub fn new_interrupt_handle(&self) -> Arc<SqlInterruptHandle> {
+        Arc::clone(&self.interrupt_handle)
+    }
+
+    #[handle_error(crate::Error)]
+    pub fn get_latest_history_metadata_for_url(
+        &self,
+        url: Url,
+    ) -> ApiResult<Option<HistoryMetadata>> {
+        self.with_conn(|conn| history_metadata::get_latest_for_url(conn, &url))
+    }
+
+    #[handle_error(crate::Error)]
+    pub fn get_history_metadata_between(
+        &self,
+        start: PlacesTimestamp,
+        end: PlacesTimestamp,
+    ) -> ApiResult<Vec<HistoryMetadata>> {
+        self.with_conn(|conn| {
+            history_metadata::get_between(conn, start.as_millis_i64(), end.as_millis_i64())
+        })
+    }
+
+    #[handle_error(crate::Error)]
+    pub fn get_history_metadata_since(
+        &self,
+        start: PlacesTimestamp,
+    ) -> ApiResult<Vec<HistoryMetadata>> {
+        self.with_conn(|conn| history_metadata::get_since(conn, start.as_millis_i64()))
+    }
+
+    #[handle_error(crate::Error)]
+    pub fn query_history_metadata(
+        &self,
+        query: String,
+        limit: i32,
+    ) -> ApiResult<Vec<HistoryMetadata>> {
+        self.with_conn(|conn| history_metadata::query(conn, query.as_str(), limit))
+    }
+
+    #[handle_error(crate::Error)]
+    pub fn get_history_highlights(
+        &self,
+        weights: HistoryHighlightWeights,
+        limit: i32,
+    ) -> ApiResult<Vec<HistoryHighlight>> {
+        self.with_conn(|conn| history_metadata::get_highlights(conn, weights, limit))
+    }
+
+    #[handle_error(crate::Error)]
+    pub fn note_history_metadata_observation(
+        &self,
+        data: HistoryMetadataObservation,
+    ) -> ApiResult<()> {
+        // odd historical naming discrepency - public function is "note_*", impl is "apply_*"
+        self.with_conn(|conn| history_metadata::apply_metadata_observation(conn, data))
+    }
+
+    #[handle_error(crate::Error)]
+    pub fn metadata_delete_older_than(&self, older_than: PlacesTimestamp) -> ApiResult<()> {
+        self.with_conn(|conn| history_metadata::delete_older_than(conn, older_than.as_millis_i64()))
+    }
+
+    #[handle_error(crate::Error)]
+    pub fn metadata_delete(
+        &self,
+        url: Url,
+        referrer_url: Option<Url>,
+        search_term: Option<String>,
+    ) -> ApiResult<()> {
+        self.with_conn(|conn| {
+            history_metadata::delete_metadata(
+                conn,
+                &url,
+                referrer_url.as_ref(),
+                search_term.as_deref(),
+            )
+        })
+    }
+
+    /// Add an observation to the database.
+    #[handle_error(crate::Error)]
+    pub fn apply_observation(&self, visit: VisitObservation) -> ApiResult<()> {
+        self.with_conn(|conn| history::apply_observation(conn, visit))?;
+        Ok(())
+    }
+
+    #[handle_error(crate::Error)]
+    pub fn get_visited_urls_in_range(
+        &self,
+        start: PlacesTimestamp,
+        end: PlacesTimestamp,
+        include_remote: bool,
+    ) -> ApiResult<Vec<Url>> {
+        self.with_conn(|conn| {
+            let urls = history::get_visited_urls(conn, start, end, include_remote)?
+                .iter()
+                // Turn the list of strings into valid Urls
+                .filter_map(|s| Url::parse(s).ok())
+                .collect::<Vec<_>>();
+            Ok(urls)
+        })
+    }
+
+    #[handle_error(crate::Error)]
+    pub fn get_visit_infos(
+        &self,
+        start_date: PlacesTimestamp,
+        end_date: PlacesTimestamp,
+        exclude_types: VisitTransitionSet,
+    ) -> ApiResult<Vec<HistoryVisitInfo>> {
+        self.with_conn(|conn| history::get_visit_infos(conn, start_date, end_date, exclude_types))
+    }
+
+    #[handle_error(crate::Error)]
+    pub fn get_visit_count(&self, exclude_types: VisitTransitionSet) -> ApiResult<i64> {
+        self.with_conn(|conn| history::get_visit_count(conn, exclude_types))
+    }
+
+    #[handle_error(crate::Error)]
+    pub fn get_visit_page(
+        &self,
+        offset: i64,
+        count: i64,
+        exclude_types: VisitTransitionSet,
+    ) -> ApiResult<Vec<HistoryVisitInfo>> {
+        self.with_conn(|conn| history::get_visit_page(conn, offset, count, exclude_types))
+    }
+
+    #[handle_error(crate::Error)]
+    pub fn get_visit_page_with_bound(
+        &self,
+        bound: i64,
+        offset: i64,
+        count: i64,
+        exclude_types: VisitTransitionSet,
+    ) -> ApiResult<HistoryVisitInfosWithBound> {
+        self.with_conn(|conn| {
+            history::get_visit_page_with_bound(conn, bound, offset, count, exclude_types)
+        })
+    }
+
+    // This is identical to get_visited in history.rs but takes a list of strings instead of urls
+    // This is necessary b/c we still need to return 'false' for bad URLs which prevents us from
+    // parsing/filtering them before reaching the history layer
+    #[handle_error(crate::Error)]
+    pub fn get_visited(&self, urls: Vec<String>) -> ApiResult<Vec<bool>> {
+        let iter = urls.into_iter();
+        let mut result = vec![false; iter.len()];
+        let url_idxs = iter
+            .enumerate()
+            .filter_map(|(idx, s)| Url::parse(&s).ok().map(|url| (idx, url)))
+            .collect::<Vec<_>>();
+        self.with_conn(|conn| history::get_visited_into(conn, &url_idxs, &mut result))?;
+        Ok(result)
+    }
+
+    #[handle_error(crate::Error)]
+    pub fn delete_visits_for(&self, url: String) -> ApiResult<()> {
+        self.with_conn(|conn| {
+            let guid = match Url::parse(&url) {
+                Ok(url) => history::url_to_guid(conn, &url)?,
+                Err(e) => {
+                    log::warn!("Invalid URL passed to places_delete_visits_for, {}", e);
+                    history::href_to_guid(conn, url.clone().as_str())?
+                }
+            };
+            if let Some(guid) = guid {
+                history::delete_visits_for(conn, &guid)?;
+            }
+            Ok(())
+        })
+    }
+
+    #[handle_error(crate::Error)]
+    pub fn delete_visits_between(
+        &self,
+        start: PlacesTimestamp,
+        end: PlacesTimestamp,
+    ) -> ApiResult<()> {
+        self.with_conn(|conn| history::delete_visits_between(conn, start, end))
+    }
+
+    #[handle_error(crate::Error)]
+    pub fn delete_visit(&self, url: String, timestamp: PlacesTimestamp) -> ApiResult<()> {
+        self.with_conn(|conn| {
+            match Url::parse(&url) {
+                Ok(url) => {
+                    history::delete_place_visit_at_time(conn, &url, timestamp)?;
+                }
+                Err(e) => {
+                    log::warn!("Invalid URL passed to places_delete_visit, {}", e);
+                    history::delete_place_visit_at_time_by_href(conn, url.as_str(), timestamp)?;
+                }
+            };
+            Ok(())
+        })
+    }
+
+    #[handle_error(crate::Error)]
+    pub fn get_top_frecent_site_infos(
+        &self,
+        num_items: i32,
+        threshold_option: FrecencyThresholdOption,
+    ) -> ApiResult<Vec<TopFrecentSiteInfo>> {
+        self.with_conn(|conn| {
+            crate::storage::history::get_top_frecent_site_infos(
+                conn,
+                num_items,
+                threshold_option.value(),
+            )
+        })
+    }
+    // deletes all history and updates the sync metadata to only sync after
+    // most recent visit to prevent further syncing of older data
+    #[handle_error(crate::Error)]
+    pub fn delete_everything_history(&self) -> ApiResult<()> {
+        history::delete_everything(&self.db.lock())
+    }
+
+    #[handle_error(crate::Error)]
+    pub fn run_maintenance_prune(&self, db_size_limit: u32) -> ApiResult<RunMaintenanceMetrics> {
+        self.with_conn(|conn| storage::run_maintenance_prune(conn, db_size_limit))
+    }
+
+    #[handle_error(crate::Error)]
+    pub fn run_maintenance_vacuum(&self) -> ApiResult<()> {
+        self.with_conn(storage::run_maintenance_vacuum)
+    }
+
+    #[handle_error(crate::Error)]
+    pub fn run_maintenance_optimize(&self) -> ApiResult<()> {
+        self.with_conn(storage::run_maintenance_optimize)
+    }
+
+    #[handle_error(crate::Error)]
+    pub fn run_maintenance_checkpoint(&self) -> ApiResult<()> {
+        self.with_conn(storage::run_maintenance_checkpoint)
+    }
+
+    #[handle_error(crate::Error)]
+    pub fn query_autocomplete(&self, search: String, limit: i32) -> ApiResult<Vec<SearchResult>> {
+        self.with_conn(|conn| {
+            search_frecent(
+                conn,
+                SearchParams {
+                    search_string: search,
+                    limit: limit as u32,
+                },
+            )
+            .map(|search_results| search_results.into_iter().map(Into::into).collect())
+        })
+    }
+
+    #[handle_error(crate::Error)]
+    pub fn accept_result(&self, search_string: String, url: String) -> ApiResult<()> {
+        self.with_conn(|conn| {
+            match Url::parse(&url) {
+                Ok(url) => {
+                    matcher::accept_result(conn, &search_string, &url)?;
+                }
+                Err(_) => {
+                    log::warn!("Ignoring invalid URL in places_accept_result");
+                    return Ok(());
+                }
+            };
+            Ok(())
+        })
+    }
+
+    #[handle_error(crate::Error)]
+    pub fn match_url(&self, query: String) -> ApiResult<Option<Url>> {
+        self.with_conn(|conn| matcher::match_url(conn, query))
+    }
+
+    #[handle_error(crate::Error)]
+    pub fn bookmarks_get_tree(&self, item_guid: &Guid) -> ApiResult<Option<BookmarkItem>> {
+        self.with_conn(|conn| bookmarks::fetch::fetch_tree(conn, item_guid))
+    }
+
+    #[handle_error(crate::Error)]
+    pub fn bookmarks_get_by_guid(
+        &self,
+        guid: &Guid,
+        get_direct_children: bool,
+    ) -> ApiResult<Option<BookmarkItem>> {
+        self.with_conn(|conn| {
+            let bookmark = bookmarks::fetch::fetch_bookmark(conn, guid, get_direct_children)?;
+            Ok(bookmark.map(BookmarkItem::from))
+        })
+    }
+
+    #[handle_error(crate::Error)]
+    pub fn bookmarks_get_all_with_url(&self, url: String) -> ApiResult<Vec<BookmarkItem>> {
+        self.with_conn(|conn| {
+            // XXX - We should return the exact type - ie, BookmarkData rather than BookmarkItem.
+            match Url::parse(&url) {
+                Ok(url) => Ok(bookmarks::fetch::fetch_bookmarks_by_url(conn, &url)?
+                    .into_iter()
+                    .map(|b| BookmarkItem::Bookmark { b })
+                    .collect::<Vec<BookmarkItem>>()),
+                Err(e) => {
+                    // There are no bookmarks with the URL if it's invalid.
+                    log::warn!("Invalid URL passed to bookmarks_get_all_with_url, {}", e);
+                    Ok(Vec::<BookmarkItem>::new())
+                }
+            }
+        })
+    }
+
+    #[handle_error(crate::Error)]
+    pub fn bookmarks_search(&self, query: String, limit: i32) -> ApiResult<Vec<BookmarkItem>> {
+        self.with_conn(|conn| {
+            // XXX - We should return the exact type - ie, BookmarkData rather than BookmarkItem.
+            Ok(
+                bookmarks::fetch::search_bookmarks(conn, query.as_str(), limit as u32)?
+                    .into_iter()
+                    .map(|b| BookmarkItem::Bookmark { b })
+                    .collect(),
+            )
+        })
+    }
+
+    #[handle_error(crate::Error)]
+    pub fn bookmarks_get_recent(&self, limit: i32) -> ApiResult<Vec<BookmarkItem>> {
+        self.with_conn(|conn| {
+            // XXX - We should return the exact type - ie, BookmarkData rather than BookmarkItem.
+            Ok(bookmarks::fetch::recent_bookmarks(conn, limit as u32)?
+                .into_iter()
+                .map(|b| BookmarkItem::Bookmark { b })
+                .collect())
+        })
+    }
+
+    #[handle_error(crate::Error)]
+    pub fn bookmarks_delete(&self, id: Guid) -> ApiResult<bool> {
+        self.with_conn(|conn| bookmarks::delete_bookmark(conn, &id))
+    }
+
+    #[handle_error(crate::Error)]
+    pub fn bookmarks_delete_everything(&self) -> ApiResult<()> {
+        self.with_conn(bookmarks::delete_everything)
+    }
+
+    #[handle_error(crate::Error)]
+    pub fn bookmarks_get_url_for_keyword(&self, keyword: String) -> ApiResult<Option<Url>> {
+        self.with_conn(|conn| bookmarks::bookmarks_get_url_for_keyword(conn, keyword.as_str()))
+    }
+
+    #[handle_error(crate::Error)]
+    pub fn bookmarks_insert(&self, data: InsertableBookmarkItem) -> ApiResult<Guid> {
+        self.with_conn(|conn| bookmarks::insert_bookmark(conn, data))
+    }
+
+    #[handle_error(crate::Error)]
+    pub fn bookmarks_update(&self, item: BookmarkUpdateInfo) -> ApiResult<()> {
+        self.with_conn(|conn| bookmarks::update_bookmark_from_info(conn, item))
+    }
+
+    #[handle_error(crate::Error)]
+    pub fn bookmarks_count_bookmarks_in_trees(&self, guids: &[Guid]) -> ApiResult<u32> {
+        self.with_conn(|conn| bookmarks::count_bookmarks_in_trees(conn, guids))
+    }
+
+    #[handle_error(crate::Error)]
+    pub fn places_history_import_from_ios(
+        &self,
+        db_path: String,
+        last_sync_timestamp: i64,
+    ) -> ApiResult<HistoryMigrationResult> {
+        self.with_conn(|conn| import_ios_history(conn, &db_path, last_sync_timestamp))
+    }
+}
+
+impl AsRef<SqlInterruptHandle> for PlacesConnection {
+    fn as_ref(&self) -> &SqlInterruptHandle {
+        &self.interrupt_handle
+    }
+}
+
+#[derive(Clone, PartialEq, Eq)]
+pub struct HistoryVisitInfo {
+    pub url: Url,
+    pub title: Option<String>,
+    pub timestamp: PlacesTimestamp,
+    pub visit_type: VisitType,
+    pub is_hidden: bool,
+    pub preview_image_url: Option<Url>,
+    pub is_remote: bool,
+}
+#[derive(Clone, PartialEq, Eq)]
+pub struct HistoryVisitInfosWithBound {
+    pub infos: Vec<HistoryVisitInfo>,
+    pub bound: i64,
+    pub offset: i64,
+}
+
+pub struct TopFrecentSiteInfo {
+    pub url: Url,
+    pub title: Option<String>,
+}
+
+pub enum FrecencyThresholdOption {
+    None,
+    SkipOneTimePages,
+}
+
+impl FrecencyThresholdOption {
+    fn value(&self) -> i64 {
+        match self {
+            FrecencyThresholdOption::None => 0,
+            FrecencyThresholdOption::SkipOneTimePages => SKIP_ONE_PAGE_FRECENCY_THRESHOLD,
+        }
+    }
+}
+
+pub struct SearchResult {
+    pub url: Url,
+    pub title: String,
+    pub frecency: i64,
+}
+
+// Exists just to convince uniffi to generate `liftSequence*` helpers!
+pub struct Dummy {
+    pub md: Option<Vec<HistoryMetadata>>,
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::test::new_mem_connection;
+
+    #[test]
+    fn test_accept_result_with_invalid_url() {
+        let conn = PlacesConnection::new(new_mem_connection());
+        let invalid_url = "http://1234.56.78.90".to_string();
+        assert!(PlacesConnection::accept_result(&conn, "ample".to_string(), invalid_url).is_ok());
+    }
+
+    #[test]
+    fn test_bookmarks_get_all_with_url_with_invalid_url() {
+        let conn = PlacesConnection::new(new_mem_connection());
+        let invalid_url = "http://1234.56.78.90".to_string();
+        assert!(PlacesConnection::bookmarks_get_all_with_url(&conn, invalid_url).is_ok());
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/places/frecency.rs.html b/book/rust-docs/src/places/frecency.rs.html new file mode 100644 index 0000000000..0a0d008847 --- /dev/null +++ b/book/rust-docs/src/places/frecency.rs.html @@ -0,0 +1,661 @@ +frecency.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::error::*;
+use crate::types::VisitType;
+use error_support::trace_error;
+use rusqlite::Connection;
+use types::Timestamp;
+
+#[derive(Debug, Clone, Copy, PartialEq)]
+enum RedirectBonus {
+    Unknown,
+    Redirect,
+    Normal,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct FrecencySettings {
+    // TODO: These probably should not all be i32s...
+    pub num_visits: i32,                     // from "places.frecency.numVisits"
+    pub first_bucket_cutoff_days: i32,       // from "places.frecency.firstBucketCutoff"
+    pub second_bucket_cutoff_days: i32,      // from "places.frecency.secondBucketCutoff"
+    pub third_bucket_cutoff_days: i32,       // from "places.frecency.thirdBucketCutoff"
+    pub fourth_bucket_cutoff_days: i32,      // from "places.frecency.fourthBucketCutoff"
+    pub first_bucket_weight: i32,            // from "places.frecency.firstBucketWeight"
+    pub second_bucket_weight: i32,           // from "places.frecency.secondBucketWeight"
+    pub third_bucket_weight: i32,            // from "places.frecency.thirdBucketWeight"
+    pub fourth_bucket_weight: i32,           // from "places.frecency.fourthBucketWeight"
+    pub default_bucket_weight: i32,          // from "places.frecency.defaultBucketWeight"
+    pub embed_visit_bonus: i32,              // from "places.frecency.embedVisitBonus"
+    pub framed_link_visit_bonus: i32,        // from "places.frecency.framedLinkVisitBonus"
+    pub link_visit_bonus: i32,               // from "places.frecency.linkVisitBonus"
+    pub typed_visit_bonus: i32,              // from "places.frecency.typedVisitBonus"
+    pub bookmark_visit_bonus: i32,           // from "places.frecency.bookmarkVisitBonus"
+    pub download_visit_bonus: i32,           // from "places.frecency.downloadVisitBonus"
+    pub permanent_redirect_visit_bonus: i32, // from "places.frecency.permRedirectVisitBonus"
+    pub temporary_redirect_visit_bonus: i32, // from "places.frecency.tempRedirectVisitBonus"
+    pub redirect_source_visit_bonus: i32,    // from "places.frecency.redirectSourceVisitBonus"
+    pub default_visit_bonus: i32,            // from "places.frecency.defaultVisitBonus"
+    pub unvisited_bookmark_bonus: i32,       // from "places.frecency.unvisitedBookmarkBonus"
+    pub unvisited_typed_bonus: i32,          // from "places.frecency.unvisitedTypedBonus"
+    pub reload_visit_bonus: i32,             // from "places.frecency.reloadVisitBonus"
+}
+
+pub const DEFAULT_FRECENCY_SETTINGS: FrecencySettings = FrecencySettings {
+    // These are the default values of the preferences.
+    num_visits: 10,
+    first_bucket_cutoff_days: 4,
+    second_bucket_cutoff_days: 14,
+    third_bucket_cutoff_days: 31,
+    fourth_bucket_cutoff_days: 90,
+    first_bucket_weight: 100,
+    second_bucket_weight: 70,
+    third_bucket_weight: 50,
+    fourth_bucket_weight: 30,
+    default_bucket_weight: 10,
+    embed_visit_bonus: 0,
+    framed_link_visit_bonus: 0,
+    link_visit_bonus: 100,
+    typed_visit_bonus: 2000,
+    bookmark_visit_bonus: 75,
+    download_visit_bonus: 0,
+    permanent_redirect_visit_bonus: 0,
+    temporary_redirect_visit_bonus: 0,
+    redirect_source_visit_bonus: 25,
+    default_visit_bonus: 0,
+    unvisited_bookmark_bonus: 140,
+    unvisited_typed_bonus: 200,
+    reload_visit_bonus: 0,
+};
+
+impl Default for FrecencySettings {
+    #[inline]
+    fn default() -> Self {
+        DEFAULT_FRECENCY_SETTINGS
+    }
+}
+
+impl FrecencySettings {
+    // Note: in Places, `redirect` defaults to false.
+    pub fn get_transition_bonus(
+        &self,
+        visit_type: Option<VisitType>,
+        visited: bool,
+        redirect: bool,
+    ) -> i32 {
+        if redirect {
+            return self.redirect_source_visit_bonus;
+        }
+        match (visit_type, visited) {
+            (Some(VisitType::Link), _) => self.link_visit_bonus,
+            (Some(VisitType::Embed), _) => self.embed_visit_bonus,
+            (Some(VisitType::FramedLink), _) => self.framed_link_visit_bonus,
+            (Some(VisitType::RedirectPermanent), _) => self.temporary_redirect_visit_bonus,
+            (Some(VisitType::RedirectTemporary), _) => self.permanent_redirect_visit_bonus,
+            (Some(VisitType::Download), _) => self.download_visit_bonus,
+            (Some(VisitType::Reload), _) => self.reload_visit_bonus,
+            (Some(VisitType::Typed), true) => self.typed_visit_bonus,
+            (Some(VisitType::Typed), false) => self.unvisited_typed_bonus,
+            (Some(VisitType::Bookmark), true) => self.bookmark_visit_bonus,
+            (Some(VisitType::Bookmark), false) => self.unvisited_bookmark_bonus,
+            (Some(VisitType::UpdatePlace), _) => self.default_visit_bonus,
+            // 0 == undefined (see bug 375777 in bugzilla for details)
+            (None, _) => self.default_visit_bonus,
+        }
+    }
+
+    fn get_frecency_aged_weight(&self, age_in_days: i32) -> i32 {
+        if age_in_days <= self.first_bucket_cutoff_days {
+            self.first_bucket_weight
+        } else if age_in_days <= self.second_bucket_cutoff_days {
+            self.second_bucket_weight
+        } else if age_in_days <= self.third_bucket_cutoff_days {
+            self.third_bucket_weight
+        } else if age_in_days <= self.fourth_bucket_cutoff_days {
+            self.fourth_bucket_weight
+        } else {
+            self.default_bucket_weight
+        }
+    }
+}
+
+struct FrecencyComputation<'db, 's> {
+    conn: &'db Connection,
+    settings: &'s FrecencySettings,
+    page_id: i64,
+    most_recent_redirect_bonus: RedirectBonus,
+
+    typed: i32,
+    visit_count: i32,
+    foreign_count: i32,
+    is_query: bool,
+}
+
+impl<'db, 's> FrecencyComputation<'db, 's> {
+    fn new(
+        conn: &'db Connection,
+        settings: &'s FrecencySettings,
+        page_id: i64,
+        most_recent_redirect_bonus: RedirectBonus,
+    ) -> Result<Self> {
+        let mut stmt = conn.prepare_cached(
+            "
+            SELECT
+                typed,
+                (visit_count_local + visit_count_remote) as visit_count,
+                foreign_count,
+                (substr(url, 0, 7) = 'place:') as is_query
+            FROM moz_places
+            WHERE id = :page_id
+        ",
+        )?;
+        let mut rows = stmt.query(&[(":page_id", &page_id)])?;
+        // trace_error to track down #4856
+        let row = trace_error!(rows.next()?.ok_or(rusqlite::Error::QueryReturnedNoRows))?;
+        let typed: i32 = row.get("typed")?;
+        let visit_count: i32 = row.get("visit_count")?;
+        let foreign_count: i32 = row.get("foreign_count")?;
+        let is_query: bool = row.get("is_query")?;
+
+        Ok(Self {
+            conn,
+            settings,
+            page_id,
+            most_recent_redirect_bonus,
+            typed,
+            visit_count,
+            foreign_count,
+            is_query,
+        })
+    }
+
+    fn has_bookmark(&self) -> bool {
+        self.foreign_count > 0
+    }
+
+    fn score_recent_visits(&self) -> Result<(usize, f32)> {
+        // Get a sample of the last visits to the page, to calculate its weight.
+        // In case the visit is a redirect target, calculate the frecency
+        // as if the original page was visited.
+        // If it's a redirect source, we may want to use a lower bonus.
+        let get_recent_visits = format!(
+            "SELECT
+                 IFNULL(origin.visit_type, v.visit_type) AS visit_type,
+                 target.visit_type AS target_visit_type,
+                 v.visit_date
+             FROM moz_historyvisits v
+             LEFT JOIN moz_historyvisits origin ON origin.id = v.from_visit
+                 AND v.visit_type IN ({redirect_permanent}, {redirect_temporary})
+             LEFT JOIN moz_historyvisits target ON v.id = target.from_visit
+                 AND target.visit_type IN ({redirect_permanent}, {redirect_temporary})
+             WHERE v.place_id = :page_id
+             ORDER BY v.visit_date DESC
+             LIMIT {max_visits}",
+            redirect_permanent = VisitType::RedirectPermanent as u8,
+            redirect_temporary = VisitType::RedirectTemporary as u8,
+            // in practice this is constant, so caching the query is fine.
+            // (rusqlite has a max cache size too should things change)
+            max_visits = self.settings.num_visits,
+        );
+
+        let mut stmt = self.conn.prepare_cached(&get_recent_visits)?;
+
+        let now = Timestamp::now();
+
+        let row_iter = stmt.query_and_then(
+            &[(":page_id", &self.page_id)],
+            |row| -> rusqlite::Result<_> {
+                let visit_type = row.get::<_, Option<u8>>("visit_type")?.unwrap_or(0);
+                let target_visit_type = row.get::<_, Option<u8>>("target_visit_type")?.unwrap_or(0);
+                let visit_date: Timestamp = row.get("visit_date")?;
+                let age_in_days =
+                    (now.as_millis() as f64 - visit_date.as_millis() as f64) / 86_400_000.0;
+                Ok((
+                    VisitType::from_primitive(visit_type),
+                    VisitType::from_primitive(target_visit_type),
+                    age_in_days.round() as i32,
+                ))
+            },
+        )?;
+
+        let mut num_sampled_visits = 0;
+        let mut points_for_sampled_visits = 0.0f32;
+
+        for row_result in row_iter {
+            let (visit_type, target_visit_type, age_in_days) = row_result?;
+            // When adding a new visit, we should haved passed-in whether we should
+            // use the redirect bonus. We can't fetch this information from the
+            // database, because we only store redirect targets.
+            // For older visits we extract the value from the database.
+            let use_redirect_bonus = if self.most_recent_redirect_bonus == RedirectBonus::Unknown
+                || num_sampled_visits > 0
+            {
+                target_visit_type == Some(VisitType::RedirectPermanent)
+                    || (target_visit_type == Some(VisitType::RedirectTemporary)
+                        && visit_type != Some(VisitType::Typed))
+            } else {
+                self.most_recent_redirect_bonus == RedirectBonus::Redirect
+            };
+
+            let mut bonus =
+                self.settings
+                    .get_transition_bonus(visit_type, true, use_redirect_bonus);
+
+            if self.has_bookmark() {
+                bonus += self
+                    .settings
+                    .get_transition_bonus(Some(VisitType::Bookmark), true, false);
+            }
+            if bonus != 0 {
+                let weight = self.settings.get_frecency_aged_weight(age_in_days) as f32;
+                points_for_sampled_visits += weight * (bonus as f32 / 100.0)
+            }
+            num_sampled_visits += 1;
+        }
+
+        Ok((num_sampled_visits, points_for_sampled_visits))
+    }
+
+    fn get_frecency_for_sample(&self, num_sampled: usize, score: f32) -> i32 {
+        if score == 0.0f32 {
+            // We were unable to calculate points, maybe cause all the visits in the
+            // sample had a zero bonus. Though, we know the page has some past valid
+            // visit, or visit_count would be zero. Thus we set the frecency to
+            // -1, so they are still shown in autocomplete.
+            -1
+        } else {
+            // Estimate frecency using the sampled visits.
+            // Use ceil() so that we don't round down to 0, which
+            // would cause us to completely ignore the place during autocomplete.
+            ((self.visit_count as f32) * score.ceil() / (num_sampled as f32)).ceil() as i32
+        }
+    }
+
+    fn compute_unvisited_bookmark_frecency(&self) -> i32 {
+        // Make it so something bookmarked and typed will have a higher frecency
+        // than something just typed or just bookmarked.
+        let mut bonus = self
+            .settings
+            .get_transition_bonus(Some(VisitType::Bookmark), false, false);
+        if self.typed != 0 {
+            bonus += self
+                .settings
+                .get_transition_bonus(Some(VisitType::Typed), false, false);
+        }
+
+        // Assume "now" as our age_in_days, so use the first bucket.
+        let score = (self.settings.first_bucket_weight as f32) * (bonus as f32 / 100.0f32);
+
+        // use ceil() so that we don't round down to 0, which
+        // would cause us to completely ignore the place during autocomplete
+        score.ceil() as i32
+    }
+}
+
+pub fn calculate_frecency(
+    db: &Connection,
+    settings: &FrecencySettings,
+    page_id: i64,
+    is_redirect: Option<bool>,
+) -> Result<i32> {
+    assert!(page_id > 0, "calculate_frecency given invalid page_id");
+
+    let most_recent_redirect_bonus = match is_redirect {
+        None => RedirectBonus::Unknown,
+        Some(true) => RedirectBonus::Redirect,
+        Some(false) => RedirectBonus::Normal,
+    };
+
+    let fc = FrecencyComputation::new(db, settings, page_id, most_recent_redirect_bonus)?;
+
+    let (num_sampled_visits, sample_score) = if fc.visit_count > 0 {
+        fc.score_recent_visits()?
+    } else {
+        (0, 0.0f32)
+    };
+
+    Ok(if num_sampled_visits > 0 {
+        // If we sampled some visits for this page, use the calculated weight.
+        fc.get_frecency_for_sample(num_sampled_visits, sample_score)
+    } else if !fc.has_bookmark() || fc.is_query {
+        // Otherwise, this page has no visits, it may be bookmarked.
+        0
+    } else {
+        // For unvisited bookmarks, produce a non-zero frecency, so that they show
+        // up in URL bar autocomplete.
+        fc.compute_unvisited_bookmark_frecency()
+    })
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/places/hash.rs.html b/book/rust-docs/src/places/hash.rs.html new file mode 100644 index 0000000000..12d309961d --- /dev/null +++ b/book/rust-docs/src/places/hash.rs.html @@ -0,0 +1,275 @@ +hash.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This is MAX_CHARS_TO_HASH in places, but I've renamed it because it's in bytes.
+// Note that the indices for slicing a Rust `str` are in bytes, so this is what
+// we want anyway.
+const MAX_BYTES_TO_HASH: usize = 1500;
+
+/// This should be identical to the "real" `mozilla::places::HashURL` with no prefix arg
+/// (see also `hash_url_prefix` for the version with one).
+///
+/// This returns a u64, but only the lower 48 bits should ever be set, so casting to
+/// an i64 is totally safe and lossless. If the string has no ':' in it, then the
+/// returned hash will be a 32 bit hash.
+pub fn hash_url(spec: &str) -> u64 {
+    let max_len_to_hash = spec.len().min(MAX_BYTES_TO_HASH);
+    let str_hash = u64::from(hash_string(&spec[..max_len_to_hash]));
+    let str_head = &spec[..spec.len().min(50)];
+    // We should be using memchr -- there's almost no chance we aren't
+    // already pulling it in transitively and it's supposedly *way* faster.
+    if let Some(pos) = str_head.as_bytes().iter().position(|&b| b == b':') {
+        let prefix_hash = u64::from(hash_string(&spec[..pos]) & 0x0000_ffff);
+        (prefix_hash << 32).wrapping_add(str_hash)
+    } else {
+        str_hash
+    }
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum PrefixMode {
+    /// Equivalent to `"prefix_lo"` in mozilla::places::HashURL
+    Lo,
+    /// Equivalent to `"prefix_hi"` in mozilla::places::HashURL
+    Hi,
+}
+
+/// This should be identical to the "real" `mozilla::places::HashURL` when given
+/// a prefix arg. Specifically:
+///
+/// - `hash_url_prefix(spec, PrefixMode::Lo)` is identical to
+/// - `hash_url_prefix(spec, PrefixMode::Hi)` is identical to
+///
+/// As with `hash_url`, it returns a u64, but only the lower 48 bits should ever be set, so
+/// casting to e.g. an i64 is lossless.
+pub fn hash_url_prefix(spec_prefix: &str, mode: PrefixMode) -> u64 {
+    let to_hash = &spec_prefix[..spec_prefix.len().min(MAX_BYTES_TO_HASH)];
+
+    // Keep 16 bits
+    let unshifted_hash = hash_string(to_hash) & 0x0000_ffff;
+    let hash = u64::from(unshifted_hash) << 32;
+    if mode == PrefixMode::Hi {
+        hash.wrapping_add(0xffff_ffffu64)
+    } else {
+        hash
+    }
+}
+
+// mozilla::kGoldenRatioU32
+const GOLDEN_RATIO: u32 = 0x9E37_79B9;
+
+// mozilla::AddU32ToHash
+#[inline]
+fn add_u32_to_hash(hash: u32, new_value: u32) -> u32 {
+    (hash.rotate_left(5) ^ new_value).wrapping_mul(GOLDEN_RATIO)
+}
+
+/// This should return identical results to `mozilla::HashString`!
+#[inline]
+pub fn hash_string(string: &str) -> u32 {
+    string
+        .as_bytes()
+        .iter()
+        .fold(0u32, |hash, &cur| add_u32_to_hash(hash, u32::from(cur)))
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_prefixes() {
+        // These are the unique 16 bits of the prefix. You can generate these with:
+        // `PlacesUtils.history.hashURL(val, "prefix_lo").toString(16).slice(0, 4)`.
+        let test_values = &[
+            ("http", 0x7226u16),
+            ("https", 0x2b12),
+            ("blob", 0x2612),
+            ("data", 0x9736),
+            ("chrome", 0x75fc),
+            ("resource", 0x37f8),
+            ("file", 0xc7c9),
+            ("place", 0xf434),
+        ];
+        for &(prefix, top16bits) in test_values {
+            let expected_lo = u64::from(top16bits) << 32;
+            let expected_hi = expected_lo | 0xffff_ffffu64;
+            assert_eq!(
+                hash_url_prefix(prefix, PrefixMode::Lo),
+                expected_lo,
+                "wrong value for hash_url_prefix({:?}, PrefixMode::Lo)",
+                prefix
+            );
+            assert_eq!(
+                hash_url_prefix(prefix, PrefixMode::Hi),
+                expected_hi,
+                "wrong value for hash_url_prefix({:?}, PrefixMode::Hi)",
+                prefix
+            );
+        }
+    }
+
+    #[test]
+    fn test_hash_url() {
+        // not actually a valid png, but whatever.
+        let data_url = "data:image/png;base64,".to_owned() + &"iVBORw0KGgoAAA".repeat(500);
+        let test_values = &[
+            ("http://www.example.com", 0x7226_2c1a_3496u64),
+            ("http://user:pass@foo:21/bar;par?b#c", 0x7226_61d2_18a7u64),
+            (
+                "https://github.com/mozilla/application-services/",
+                0x2b12_e7bd_7fcdu64,
+            ),
+            ("place:transition=7&sort=4", 0xf434_ac2b_2dafu64),
+            (
+                "blob:36c6ded1-6190-45f4-8fcd-355d1b6c9f48",
+                0x2612_0a43_1050u64,
+            ),
+            ("www.example.com", 0x8b14_9337u64), // URLs without a prefix are hashed to 32 bits
+            (&data_url[..], 0x9736_d65d_86d9u64),
+        ];
+
+        for &(url_str, hash) in test_values {
+            assert_eq!(hash_url(url_str), hash, "Wrong value for url {:?}", url_str);
+        }
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/places/history_sync/engine.rs.html b/book/rust-docs/src/places/history_sync/engine.rs.html new file mode 100644 index 0000000000..5219885a38 --- /dev/null +++ b/book/rust-docs/src/places/history_sync/engine.rs.html @@ -0,0 +1,311 @@ +engine.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::db::{PlacesDb, SharedPlacesDb};
+use crate::error::*;
+use crate::storage::history::{delete_everything, history_sync::reset};
+use crate::storage::{get_meta, put_meta};
+use interrupt_support::SqlInterruptScope;
+use std::sync::Arc;
+use sync15::bso::{IncomingBso, OutgoingBso};
+use sync15::engine::{
+    CollSyncIds, CollectionRequest, EngineSyncAssociation, RequestOrder, SyncEngine,
+};
+use sync15::{telemetry, Guid, ServerTimestamp};
+
+use super::plan::{apply_plan, finish_plan, get_planned_outgoing};
+use super::MAX_INCOMING_PLACES;
+
+pub const LAST_SYNC_META_KEY: &str = "history_last_sync_time";
+// Note that all engines in this crate should use a *different* meta key
+// for the global sync ID, because engines are reset individually.
+pub const GLOBAL_SYNCID_META_KEY: &str = "history_global_sync_id";
+pub const COLLECTION_SYNCID_META_KEY: &str = "history_sync_id";
+
+fn do_apply_incoming(
+    db: &PlacesDb,
+    scope: &SqlInterruptScope,
+    inbound: Vec<IncomingBso>,
+    telem: &mut telemetry::Engine,
+) -> Result<()> {
+    let mut incoming_telemetry = telemetry::EngineIncoming::new();
+    apply_plan(db, inbound, &mut incoming_telemetry, scope)?;
+    telem.incoming(incoming_telemetry);
+    Ok(())
+}
+
+fn do_sync_finished(
+    db: &PlacesDb,
+    new_timestamp: ServerTimestamp,
+    records_synced: Vec<Guid>,
+) -> Result<()> {
+    log::info!(
+        "sync completed after uploading {} records",
+        records_synced.len()
+    );
+    finish_plan(db)?;
+
+    // write timestamp to reflect what we just wrote.
+    // XXX - should clean up transactions, but we *are not* in a transaction
+    // here, so this value applies immediately.
+    put_meta(db, LAST_SYNC_META_KEY, &new_timestamp.as_millis())?;
+
+    db.pragma_update(None, "wal_checkpoint", "PASSIVE")?;
+
+    Ok(())
+}
+
+// Short-lived struct that's constructed each sync
+pub struct HistorySyncEngine {
+    pub db: Arc<SharedPlacesDb>,
+    // We should stage these in a temp table! For now though we just hold them
+    // in memory.
+    // Public because we use it in the [PlacesApi] sync methods.  We can probably make this private
+    // once all syncing goes through the sync manager.
+    pub(crate) scope: SqlInterruptScope,
+}
+
+impl HistorySyncEngine {
+    pub fn new(db: Arc<SharedPlacesDb>) -> Result<Self> {
+        Ok(Self {
+            scope: db.begin_interrupt_scope()?,
+            db,
+        })
+    }
+}
+
+impl SyncEngine for HistorySyncEngine {
+    fn collection_name(&self) -> std::borrow::Cow<'static, str> {
+        "history".into()
+    }
+
+    fn stage_incoming(
+        &self,
+        inbound: Vec<IncomingBso>,
+        telem: &mut telemetry::Engine,
+    ) -> anyhow::Result<()> {
+        // This is minor abuse of the engine concept, but for each "stage_incoming" call we
+        // just apply it directly. We can't advance our timestamp, which means if we are
+        // interrupted we'll re-download and re-apply them, but that will be fine in practice.
+        let conn = self.db.lock();
+        do_apply_incoming(&conn, &self.scope, inbound, telem)?;
+        Ok(())
+    }
+
+    fn apply(
+        &self,
+        timestamp: ServerTimestamp,
+        _telem: &mut telemetry::Engine,
+    ) -> anyhow::Result<Vec<OutgoingBso>> {
+        let conn = self.db.lock();
+        // We know we've seen everything incoming, so it's safe to write the timestamp now.
+        // If we are interrupted creating outgoing BSOs we won't re-apply what we just did.
+        put_meta(&conn, LAST_SYNC_META_KEY, &timestamp.as_millis())?;
+        Ok(get_planned_outgoing(&conn)?)
+    }
+
+    fn set_uploaded(&self, new_timestamp: ServerTimestamp, ids: Vec<Guid>) -> anyhow::Result<()> {
+        Ok(do_sync_finished(&self.db.lock(), new_timestamp, ids)?)
+    }
+
+    fn sync_finished(&self) -> anyhow::Result<()> {
+        Ok(())
+    }
+
+    fn get_collection_request(
+        &self,
+        server_timestamp: ServerTimestamp,
+    ) -> anyhow::Result<Option<CollectionRequest>> {
+        let conn = self.db.lock();
+        let since =
+            ServerTimestamp(get_meta::<i64>(&conn, LAST_SYNC_META_KEY)?.unwrap_or_default());
+        Ok(if since == server_timestamp {
+            None
+        } else {
+            Some(
+                CollectionRequest::new("history".into())
+                    .full()
+                    .newer_than(since)
+                    .limit(MAX_INCOMING_PLACES, RequestOrder::Newest),
+            )
+        })
+    }
+
+    fn get_sync_assoc(&self) -> anyhow::Result<EngineSyncAssociation> {
+        let conn = self.db.lock();
+        let global = get_meta(&conn, GLOBAL_SYNCID_META_KEY)?;
+        let coll = get_meta(&conn, COLLECTION_SYNCID_META_KEY)?;
+        Ok(if let (Some(global), Some(coll)) = (global, coll) {
+            EngineSyncAssociation::Connected(CollSyncIds { global, coll })
+        } else {
+            EngineSyncAssociation::Disconnected
+        })
+    }
+
+    fn reset(&self, assoc: &EngineSyncAssociation) -> anyhow::Result<()> {
+        reset(&self.db.lock(), assoc)?;
+        Ok(())
+    }
+
+    fn wipe(&self) -> anyhow::Result<()> {
+        delete_everything(&self.db.lock())?;
+        Ok(())
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/places/history_sync/mod.rs.html b/book/rust-docs/src/places/history_sync/mod.rs.html new file mode 100644 index 0000000000..e814c69da9 --- /dev/null +++ b/book/rust-docs/src/places/history_sync/mod.rs.html @@ -0,0 +1,113 @@ +mod.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use serde_derive::*;
+use std::fmt;
+use std::time::{SystemTime, UNIX_EPOCH};
+use types::Timestamp;
+
+pub mod engine;
+#[cfg(test)]
+mod payload_evolution_tests;
+mod plan;
+pub mod record;
+
+pub use engine::HistorySyncEngine;
+
+const MAX_INCOMING_PLACES: usize = 5000;
+const MAX_OUTGOING_PLACES: usize = 5000;
+const MAX_VISITS: usize = 20;
+pub const HISTORY_TTL: u32 = 5_184_000; // 60 days in milliseconds
+
+/// Visit timestamps on the server are *microseconds* since the epoch.
+#[derive(
+    Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize, Default,
+)]
+pub struct ServerVisitTimestamp(pub u64);
+
+impl From<ServerVisitTimestamp> for Timestamp {
+    #[inline]
+    fn from(ts: ServerVisitTimestamp) -> Timestamp {
+        Timestamp(ts.0 / 1000)
+    }
+}
+
+impl From<Timestamp> for ServerVisitTimestamp {
+    #[inline]
+    fn from(ts: Timestamp) -> ServerVisitTimestamp {
+        ServerVisitTimestamp(ts.0 * 1000)
+    }
+}
+
+impl From<SystemTime> for ServerVisitTimestamp {
+    #[inline]
+    fn from(st: SystemTime) -> Self {
+        let d = st.duration_since(UNIX_EPOCH).unwrap_or_default();
+        ServerVisitTimestamp(d.as_secs() * 1_000_000 + (u64::from(d.subsec_nanos()) / 1_000))
+    }
+}
+
+impl fmt::Display for ServerVisitTimestamp {
+    #[inline]
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "{}", self.0)
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/places/history_sync/plan.rs.html b/book/rust-docs/src/places/history_sync/plan.rs.html new file mode 100644 index 0000000000..8175ec842e --- /dev/null +++ b/book/rust-docs/src/places/history_sync/plan.rs.html @@ -0,0 +1,1893 @@ +plan.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
+540
+541
+542
+543
+544
+545
+546
+547
+548
+549
+550
+551
+552
+553
+554
+555
+556
+557
+558
+559
+560
+561
+562
+563
+564
+565
+566
+567
+568
+569
+570
+571
+572
+573
+574
+575
+576
+577
+578
+579
+580
+581
+582
+583
+584
+585
+586
+587
+588
+589
+590
+591
+592
+593
+594
+595
+596
+597
+598
+599
+600
+601
+602
+603
+604
+605
+606
+607
+608
+609
+610
+611
+612
+613
+614
+615
+616
+617
+618
+619
+620
+621
+622
+623
+624
+625
+626
+627
+628
+629
+630
+631
+632
+633
+634
+635
+636
+637
+638
+639
+640
+641
+642
+643
+644
+645
+646
+647
+648
+649
+650
+651
+652
+653
+654
+655
+656
+657
+658
+659
+660
+661
+662
+663
+664
+665
+666
+667
+668
+669
+670
+671
+672
+673
+674
+675
+676
+677
+678
+679
+680
+681
+682
+683
+684
+685
+686
+687
+688
+689
+690
+691
+692
+693
+694
+695
+696
+697
+698
+699
+700
+701
+702
+703
+704
+705
+706
+707
+708
+709
+710
+711
+712
+713
+714
+715
+716
+717
+718
+719
+720
+721
+722
+723
+724
+725
+726
+727
+728
+729
+730
+731
+732
+733
+734
+735
+736
+737
+738
+739
+740
+741
+742
+743
+744
+745
+746
+747
+748
+749
+750
+751
+752
+753
+754
+755
+756
+757
+758
+759
+760
+761
+762
+763
+764
+765
+766
+767
+768
+769
+770
+771
+772
+773
+774
+775
+776
+777
+778
+779
+780
+781
+782
+783
+784
+785
+786
+787
+788
+789
+790
+791
+792
+793
+794
+795
+796
+797
+798
+799
+800
+801
+802
+803
+804
+805
+806
+807
+808
+809
+810
+811
+812
+813
+814
+815
+816
+817
+818
+819
+820
+821
+822
+823
+824
+825
+826
+827
+828
+829
+830
+831
+832
+833
+834
+835
+836
+837
+838
+839
+840
+841
+842
+843
+844
+845
+846
+847
+848
+849
+850
+851
+852
+853
+854
+855
+856
+857
+858
+859
+860
+861
+862
+863
+864
+865
+866
+867
+868
+869
+870
+871
+872
+873
+874
+875
+876
+877
+878
+879
+880
+881
+882
+883
+884
+885
+886
+887
+888
+889
+890
+891
+892
+893
+894
+895
+896
+897
+898
+899
+900
+901
+902
+903
+904
+905
+906
+907
+908
+909
+910
+911
+912
+913
+914
+915
+916
+917
+918
+919
+920
+921
+922
+923
+924
+925
+926
+927
+928
+929
+930
+931
+932
+933
+934
+935
+936
+937
+938
+939
+940
+941
+942
+943
+944
+945
+946
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use super::record::{HistoryRecord, HistoryRecordVisit};
+use super::{MAX_OUTGOING_PLACES, MAX_VISITS};
+use crate::api::history::can_add_url;
+use crate::db::PlacesDb;
+use crate::error::*;
+use crate::storage::{
+    delete_pending_temp_tables,
+    history::history_sync::{
+        apply_synced_deletion, apply_synced_reconciliation, apply_synced_visits, fetch_outgoing,
+        fetch_visits, finish_outgoing, FetchedVisit, FetchedVisitPage,
+    },
+};
+use crate::types::{UnknownFields, VisitType};
+use interrupt_support::Interruptee;
+use std::collections::HashSet;
+use std::time::{SystemTime, UNIX_EPOCH};
+use sync15::bso::{IncomingBso, IncomingKind, OutgoingBso};
+use sync15::telemetry;
+use sync_guid::Guid as SyncGuid;
+use types::Timestamp;
+use url::Url;
+
+/// Clamps a history visit date between the current date and the earliest
+/// sensible date.
+fn clamp_visit_date(visit_date: Timestamp) -> std::result::Result<Timestamp, ()> {
+    let now = Timestamp::now();
+    if visit_date > now {
+        return Ok(now);
+    }
+    if visit_date < Timestamp::EARLIEST {
+        return Err(());
+    }
+    Ok(visit_date)
+}
+
+/// This is the action we will take *locally* for each incoming record.
+/// For example, IncomingPlan::Delete means we will be deleting a local record
+/// and not that we will be uploading a tombstone or deleting the record itself.
+#[derive(Debug)]
+pub enum IncomingPlan {
+    /// An entry we just want to ignore - either due to the URL etc, or because no changes.
+    Skip,
+    /// Something's wrong with this entry.
+    Invalid(Error),
+    /// The entry appears sane, but there was some error.
+    Failed(Error),
+    /// We should locally delete this.
+    Delete,
+    /// We should apply this.
+    Apply {
+        url: Url,
+        new_title: Option<String>,
+        visits: Vec<HistoryRecordVisit>,
+        unknown_fields: UnknownFields,
+    },
+    /// Entry exists locally and it's the same as the incoming record. This is
+    /// subtly different from Skip as we may still need to write metadata to
+    /// the local DB for reconciled items.
+    Reconciled,
+}
+
+fn plan_incoming_record(conn: &PlacesDb, record: HistoryRecord, max_visits: usize) -> IncomingPlan {
+    let url = match Url::parse(&record.hist_uri) {
+        Ok(u) => u,
+        Err(e) => return IncomingPlan::Invalid(e.into()),
+    };
+
+    if !record.id.is_valid_for_places() {
+        return IncomingPlan::Invalid(InvalidPlaceInfo::InvalidGuid.into());
+    }
+
+    match can_add_url(&url) {
+        Ok(can) => {
+            if !can {
+                return IncomingPlan::Skip;
+            }
+        }
+        Err(e) => return IncomingPlan::Failed(e),
+    }
+    // Let's get what we know about it, if anything - last 20, like desktop?
+    let visit_tuple = match fetch_visits(conn, &url, max_visits) {
+        Ok(v) => v,
+        Err(e) => return IncomingPlan::Failed(e),
+    };
+
+    // This all seems more messy than it should be - struggling to find the
+    // correct signature for fetch_visits.
+    // An improvement might be to do this via a temp table so we can dedupe
+    // and apply in one operation rather than the fetch, rust-merge and update
+    // we are doing here.
+    let (existing_page, existing_visits): (Option<FetchedVisitPage>, Vec<FetchedVisit>) =
+        match visit_tuple {
+            None => (None, Vec::new()),
+            Some((p, v)) => (Some(p), v),
+        };
+
+    let guid_changed = match existing_page {
+        Some(p) => p.guid != record.id,
+        None => false,
+    };
+
+    let mut cur_visit_map: HashSet<(VisitType, Timestamp)> =
+        HashSet::with_capacity(existing_visits.len());
+    for visit in &existing_visits {
+        // it should be impossible for us to have invalid visits locally, but...
+        let transition = match visit.visit_type {
+            Some(t) => t,
+            None => continue,
+        };
+        match clamp_visit_date(visit.visit_date) {
+            Ok(date_use) => {
+                cur_visit_map.insert((transition, date_use));
+            }
+            Err(_) => {
+                log::warn!("Ignored visit before 1993-01-23");
+            }
+        }
+    }
+    // If we already have MAX_RECORDS visits, then we will ignore incoming
+    // visits older than that, to avoid adding dupes of earlier visits.
+    // (Not really clear why 20 is magic, but what's good enough for desktop
+    // is good enough for us at this stage.)
+    // We should also consider pushing this deduping down into storage, where
+    // it can possibly do a better job directly in SQL or similar.
+    let earliest_allowed: SystemTime = if existing_visits.len() == max_visits {
+        existing_visits[existing_visits.len() - 1].visit_date.into()
+    } else {
+        UNIX_EPOCH
+    };
+
+    // work out which of the incoming visits we should apply.
+    let mut to_apply = Vec::with_capacity(record.visits.len());
+    for incoming_visit in record.visits {
+        let transition = match VisitType::from_primitive(incoming_visit.transition) {
+            Some(v) => v,
+            None => continue,
+        };
+        match clamp_visit_date(incoming_visit.date.into()) {
+            Ok(timestamp) => {
+                if earliest_allowed > timestamp.into() {
+                    continue;
+                }
+                // If the entry isn't in our map we should add it.
+                let key = (transition, timestamp);
+                if !cur_visit_map.contains(&key) {
+                    to_apply.push(HistoryRecordVisit {
+                        date: timestamp.into(),
+                        transition: transition as u8,
+                        unknown_fields: incoming_visit.unknown_fields,
+                    });
+                    cur_visit_map.insert(key);
+                }
+            }
+            Err(()) => {
+                log::warn!("Ignored visit before 1993-01-23");
+            }
+        }
+    }
+    // Now we need to check the other attributes.
+    // Check if we should update title? For now, assume yes. It appears
+    // as though desktop always updates it.
+    if guid_changed || !to_apply.is_empty() {
+        let new_title = Some(record.title);
+        IncomingPlan::Apply {
+            url,
+            new_title,
+            visits: to_apply,
+            unknown_fields: record.unknown_fields,
+        }
+    } else {
+        IncomingPlan::Reconciled
+    }
+}
+
+pub fn apply_plan(
+    db: &PlacesDb,
+    inbound: Vec<IncomingBso>,
+    telem: &mut telemetry::EngineIncoming,
+    interruptee: &impl Interruptee,
+) -> Result<()> {
+    // for a first-cut, let's do this in the most naive way possible...
+    let mut plans: Vec<(SyncGuid, IncomingPlan)> = Vec::with_capacity(inbound.len());
+    for incoming in inbound {
+        interruptee.err_if_interrupted()?;
+        let content = incoming.into_content::<HistoryRecord>();
+        let plan = match content.kind {
+            IncomingKind::Tombstone => IncomingPlan::Delete,
+            IncomingKind::Content(record) => plan_incoming_record(db, record, MAX_VISITS),
+            IncomingKind::Malformed => {
+                // We could push IncomingPlan::Invalid here, but the code before the IncomingKind
+                // refactor didn't know what `id` to use, so skipped it - so we do too.
+                log::warn!(
+                    "Error deserializing incoming record: {}",
+                    content.envelope.id
+                );
+                telem.failed(1);
+                continue;
+            }
+        };
+        plans.push((content.envelope.id.clone(), plan));
+    }
+
+    let mut tx = db.begin_transaction()?;
+
+    for (guid, plan) in plans {
+        interruptee.err_if_interrupted()?;
+        match &plan {
+            IncomingPlan::Skip => {
+                log::trace!("incoming: skipping item {:?}", guid);
+                // XXX - should we `telem.reconciled(1);` here?
+            }
+            IncomingPlan::Invalid(err) => {
+                log::warn!(
+                    "incoming: record {:?} skipped because it is invalid: {}",
+                    guid,
+                    err
+                );
+                telem.failed(1);
+            }
+            IncomingPlan::Failed(err) => {
+                error_support::report_error!(
+                    "places-failed-to-apply",
+                    "incoming: record {:?} failed to apply: {}",
+                    guid,
+                    err
+                );
+                telem.failed(1);
+            }
+            IncomingPlan::Delete => {
+                log::trace!("incoming: deleting {:?}", guid);
+                apply_synced_deletion(db, &guid)?;
+                telem.applied(1);
+            }
+            IncomingPlan::Apply {
+                url,
+                new_title,
+                visits,
+                unknown_fields,
+            } => {
+                log::trace!(
+                    "incoming: will apply {guid:?}: url={url:?}, title={new_title:?}, to_add={visits:?}, unknown_fields={unknown_fields:?}"
+                );
+                apply_synced_visits(db, &guid, url, new_title, visits, unknown_fields)?;
+                telem.applied(1);
+            }
+            IncomingPlan::Reconciled => {
+                telem.reconciled(1);
+                log::trace!("incoming: reconciled {:?}", guid);
+                apply_synced_reconciliation(db, &guid)?;
+            }
+        };
+        if tx.should_commit() {
+            // Trigger frecency and origin updates before committing the
+            // transaction, so that our origins table is consistent even
+            // if we're interrupted.
+            delete_pending_temp_tables(db)?;
+        }
+        tx.maybe_commit()?;
+    }
+    // ...And commit the final chunk of plans, making sure we trigger
+    // frecency and origin updates.
+    delete_pending_temp_tables(db)?;
+    tx.commit()?;
+    log::info!("incoming: {}", serde_json::to_string(&telem).unwrap());
+    Ok(())
+}
+
+pub fn get_planned_outgoing(db: &PlacesDb) -> Result<Vec<OutgoingBso>> {
+    // It might make sense for fetch_outgoing to manage its own
+    // begin_transaction - even though doesn't seem a large bottleneck
+    // at this time, the fact we hold a single transaction for the entire call
+    // really is used only for performance, so it's certainly a candidate.
+    let tx = db.begin_transaction()?;
+    let outgoing = fetch_outgoing(db, MAX_OUTGOING_PLACES, MAX_VISITS)?;
+    tx.commit()?;
+    Ok(outgoing)
+}
+
+pub fn finish_plan(db: &PlacesDb) -> Result<()> {
+    let tx = db.begin_transaction()?;
+    finish_outgoing(db)?;
+    log::trace!("Committing final sync plan");
+    tx.commit()?;
+    Ok(())
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::api::matcher::{search_frecent, SearchParams};
+    use crate::api::places_api::ConnectionType;
+    use crate::db::PlacesDb;
+    use crate::history_sync::ServerVisitTimestamp;
+    use crate::observation::VisitObservation;
+    use crate::storage::history::history_sync::fetch_visits;
+    use crate::storage::history::{apply_observation, delete_visits_for, url_to_guid};
+    use crate::types::SyncStatus;
+    use interrupt_support::NeverInterrupts;
+    use serde_json::json;
+    use sql_support::ConnExt;
+    use std::time::Duration;
+    use sync15::bso::IncomingBso;
+    use types::Timestamp;
+    use url::Url;
+
+    fn get_existing_guid(conn: &PlacesDb, url: &Url) -> SyncGuid {
+        url_to_guid(conn, url)
+            .expect("should have worked")
+            .expect("should have got a value")
+    }
+
+    fn get_tombstone_count(conn: &PlacesDb) -> u32 {
+        let result: Result<Option<u32>> = conn.try_query_row(
+            "SELECT COUNT(*) from moz_places_tombstones;",
+            [],
+            |row| Ok(row.get::<_, u32>(0)?),
+            true,
+        );
+        result
+            .expect("should have worked")
+            .expect("should have got a value")
+    }
+
+    fn get_sync(conn: &PlacesDb, url: &Url) -> (SyncStatus, u32) {
+        let guid_result: Result<Option<(SyncStatus, u32)>> = conn.try_query_row(
+            "SELECT sync_status, sync_change_counter
+                     FROM moz_places
+                     WHERE url = :url;",
+            &[(":url", &String::from(url.clone()))],
+            |row| {
+                Ok((
+                    SyncStatus::from_u8(row.get::<_, u8>(0)?),
+                    row.get::<_, u32>(1)?,
+                ))
+            },
+            true,
+        );
+        guid_result
+            .expect("should have worked")
+            .expect("should have got values")
+    }
+
+    fn apply_and_get_outgoing(db: &PlacesDb, incoming: Vec<IncomingBso>) -> Vec<OutgoingBso> {
+        apply_plan(
+            db,
+            incoming,
+            &mut telemetry::EngineIncoming::new(),
+            &NeverInterrupts,
+        )
+        .expect("should apply");
+        get_planned_outgoing(db).expect("should get outgoing")
+    }
+
+    #[test]
+    fn test_invalid_guid() -> Result<()> {
+        let _ = env_logger::try_init();
+        let conn = PlacesDb::open_in_memory(ConnectionType::Sync)?;
+        let record = HistoryRecord {
+            id: "foo".into(),
+            title: "title".into(),
+            hist_uri: "http://example.com".into(),
+            visits: vec![],
+            unknown_fields: UnknownFields::new(),
+        };
+
+        assert!(matches!(
+            plan_incoming_record(&conn, record, 10),
+            IncomingPlan::Invalid(_)
+        ));
+        Ok(())
+    }
+
+    #[test]
+    fn test_invalid_url() -> Result<()> {
+        let _ = env_logger::try_init();
+        let conn = PlacesDb::open_in_memory(ConnectionType::Sync)?;
+        let record = HistoryRecord {
+            id: "aaaaaaaaaaaa".into(),
+            title: "title".into(),
+            hist_uri: "invalid".into(),
+            visits: vec![],
+            unknown_fields: UnknownFields::new(),
+        };
+
+        assert!(matches!(
+            plan_incoming_record(&conn, record, 10),
+            IncomingPlan::Invalid(_)
+        ));
+        Ok(())
+    }
+
+    #[test]
+    fn test_new() -> Result<()> {
+        let _ = env_logger::try_init();
+        let conn = PlacesDb::open_in_memory(ConnectionType::Sync)?;
+        let visits = vec![HistoryRecordVisit {
+            date: SystemTime::now().into(),
+            transition: 1,
+            unknown_fields: UnknownFields::new(),
+        }];
+        let record = HistoryRecord {
+            id: "aaaaaaaaaaaa".into(),
+            title: "title".into(),
+            hist_uri: "https://example.com".into(),
+            visits,
+            unknown_fields: UnknownFields::new(),
+        };
+
+        assert!(matches!(
+            plan_incoming_record(&conn, record, 10),
+            IncomingPlan::Apply { .. }
+        ));
+        Ok(())
+    }
+
+    #[test]
+    fn test_plan_dupe_visit_same_guid() {
+        let _ = env_logger::try_init();
+        let conn = PlacesDb::open_in_memory(ConnectionType::Sync).expect("no memory db");
+        let now = SystemTime::now();
+        let url = Url::parse("https://example.com").expect("is valid");
+        // add it locally
+        let obs = VisitObservation::new(url.clone())
+            .with_visit_type(VisitType::Link)
+            .with_at(Some(now.into()));
+        apply_observation(&conn, obs).expect("should apply");
+        // should be New with a change counter.
+        assert_eq!(get_sync(&conn, &url), (SyncStatus::New, 1));
+
+        let guid = get_existing_guid(&conn, &url);
+
+        // try and add it remotely.
+        let visits = vec![HistoryRecordVisit {
+            date: now.into(),
+            transition: 1,
+            unknown_fields: UnknownFields::new(),
+        }];
+        let record = HistoryRecord {
+            id: guid,
+            title: "title".into(),
+            hist_uri: "https://example.com".into(),
+            visits,
+            unknown_fields: UnknownFields::new(),
+        };
+        // We should have reconciled it.
+        assert!(matches!(
+            plan_incoming_record(&conn, record, 10),
+            IncomingPlan::Reconciled
+        ));
+    }
+
+    #[test]
+    fn test_plan_dupe_visit_different_guid_no_visits() {
+        let _ = env_logger::try_init();
+        let conn = PlacesDb::open_in_memory(ConnectionType::Sync).expect("no memory db");
+        let now = SystemTime::now();
+        let url = Url::parse("https://example.com").expect("is valid");
+        // add it locally
+        let obs = VisitObservation::new(url.clone())
+            .with_visit_type(VisitType::Link)
+            .with_at(Some(now.into()));
+        apply_observation(&conn, obs).expect("should apply");
+
+        assert_eq!(get_sync(&conn, &url), (SyncStatus::New, 1));
+
+        // try and add an incoming record with the same URL but different guid.
+        let record = HistoryRecord {
+            id: SyncGuid::random(),
+            title: "title".into(),
+            hist_uri: "https://example.com".into(),
+            visits: vec![],
+            unknown_fields: UnknownFields::new(),
+        };
+        // Even though there are no visits we should record that it will be
+        // applied with the guid change.
+        assert!(matches!(
+            plan_incoming_record(&conn, record, 10),
+            IncomingPlan::Apply { .. }
+        ));
+    }
+
+    // These "dupe" tests all do the full application of the plan and checks
+    // the end state of the db.
+    #[test]
+    fn test_apply_dupe_no_local_visits() -> Result<()> {
+        // There's a chance the server ends up with different records but
+        // which reference the same URL.
+        // This is testing the case when there are no local visits to that URL.
+        let _ = env_logger::try_init();
+        let db = PlacesDb::open_in_memory(ConnectionType::Sync)?;
+        let guid1 = SyncGuid::random();
+        let ts1: Timestamp = (SystemTime::now() - Duration::new(5, 0)).into();
+
+        let guid2 = SyncGuid::random();
+        let ts2: Timestamp = SystemTime::now().into();
+        let url = Url::parse("https://example.com")?;
+
+        // 2 incoming records with the same URL.
+        let incoming = vec![
+            IncomingBso::from_test_content(json!({
+                "id": guid1,
+                "title": "title",
+                "histUri": url.as_str(),
+                "visits": [ {"date": ServerVisitTimestamp::from(ts1), "type": 1}]
+            })),
+            IncomingBso::from_test_content(json!({
+                "id": guid2,
+                "title": "title",
+                "histUri": url.as_str(),
+                "visits": [ {"date": ServerVisitTimestamp::from(ts2), "type": 1}]
+            })),
+            IncomingBso::from_test_content(json!({
+                "id": guid2,
+                "title": "title2",
+                "histUri": url.as_str(),
+                "visits": [ {"date": ServerVisitTimestamp::from(ts2), "type": 1}]
+            })),
+        ];
+
+        let outgoing = apply_and_get_outgoing(&db, incoming);
+        assert_eq!(
+            outgoing.len(),
+            1,
+            "should have guid1 as outgoing with both visits."
+        );
+        assert_eq!(outgoing[0].envelope.id, guid1);
+
+        // should have 1 URL with both visits locally.
+        let (page, visits) = fetch_visits(&db, &url, 3)?.expect("page exists");
+        assert_eq!(
+            page.guid, guid1,
+            "page should have the guid from the first record"
+        );
+        assert_eq!(
+            page.title, "title2",
+            "page should have the title from the second record"
+        );
+        assert_eq!(visits.len(), 2, "page should have 2 visits");
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_apply_dupe_local_unsynced_visits() -> Result<()> {
+        // There's a chance the server ends up with different records but
+        // which reference the same URL.
+        // This is testing the case when there are a local visits to that URL,
+        // but they are yet to be synced - the local guid should change and
+        // all visits should be applied.
+        let _ = env_logger::try_init();
+        let db = PlacesDb::open_in_memory(ConnectionType::Sync)?;
+
+        let guid1 = SyncGuid::random();
+        let ts1: Timestamp = (SystemTime::now() - Duration::new(5, 0)).into();
+
+        let guid2 = SyncGuid::random();
+        let ts2: Timestamp = SystemTime::now().into();
+        let url = Url::parse("https://example.com")?;
+
+        let ts_local: Timestamp = (SystemTime::now() - Duration::new(10, 0)).into();
+        let obs = VisitObservation::new(url.clone())
+            .with_visit_type(VisitType::Link)
+            .with_at(Some(ts_local));
+        apply_observation(&db, obs)?;
+
+        // 2 incoming records with the same URL.
+        let incoming = vec![
+            IncomingBso::from_test_content(json!({
+                "id": guid1,
+                "title": "title",
+                "histUri": url.as_str(),
+                "visits": [ {"date": ServerVisitTimestamp::from(ts1), "type": 1}]
+            })),
+            IncomingBso::from_test_content(json!({
+                "id": guid2,
+                "title": "title",
+                "histUri": url.as_str(),
+                "visits": [ {"date": ServerVisitTimestamp::from(ts2), "type": 1}]
+            })),
+        ];
+
+        let outgoing = apply_and_get_outgoing(&db, incoming);
+        assert_eq!(outgoing.len(), 1, "should have guid1 as outgoing");
+        assert_eq!(outgoing[0].envelope.id, guid1);
+
+        // should have 1 URL with all visits locally, but with the first incoming guid.
+        let (page, visits) = fetch_visits(&db, &url, 3)?.expect("page exists");
+        assert_eq!(page.guid, guid1, "should have the expected guid");
+        assert_eq!(visits.len(), 3, "should have all visits");
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_apply_dupe_local_synced_visits() -> Result<()> {
+        // There's a chance the server ends up with different records but
+        // which reference the same URL.
+        // This is testing the case when there are a local visits to that URL,
+        // and they have been synced - the existing guid should not change,
+        // although all visits should still be applied.
+        let _ = env_logger::try_init();
+        let db = PlacesDb::open_in_memory(ConnectionType::Sync)?;
+
+        let guid1 = SyncGuid::random();
+        let ts1: Timestamp = (SystemTime::now() - Duration::new(5, 0)).into();
+
+        let guid2 = SyncGuid::random();
+        let ts2: Timestamp = SystemTime::now().into();
+        let url = Url::parse("https://example.com")?;
+
+        let ts_local: Timestamp = (SystemTime::now() - Duration::new(10, 0)).into();
+        let obs = VisitObservation::new(url.clone())
+            .with_visit_type(VisitType::Link)
+            .with_at(Some(ts_local));
+        apply_observation(&db, obs)?;
+
+        // 2 incoming records with the same URL.
+        let incoming = vec![
+            IncomingBso::from_test_content(json!({
+                "id": guid1,
+                "title": "title",
+                "histUri": url.as_str(),
+                "visits": [ {"date": ServerVisitTimestamp::from(ts1), "type": 1}]
+            })),
+            IncomingBso::from_test_content(json!({
+                "id": guid2,
+                "title": "title",
+                "histUri": url.as_str(),
+                "sortindex": 0,
+                "ttl": 100,
+                "visits": [ {"date": ServerVisitTimestamp::from(ts2), "type": 1}]
+            })),
+        ];
+
+        let outgoing = apply_and_get_outgoing(&db, incoming);
+        assert_eq!(
+            outgoing.len(),
+            1,
+            "should have guid1 as outgoing with both visits."
+        );
+
+        // should have 1 URL with all visits locally, but with the first incoming guid.
+        let (page, visits) = fetch_visits(&db, &url, 3)?.expect("page exists");
+        assert_eq!(page.guid, guid1, "should have the expected guid");
+        assert_eq!(visits.len(), 3, "should have all visits");
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_apply_plan_incoming_invalid_timestamp() -> Result<()> {
+        let _ = env_logger::try_init();
+        let json = json!({
+            "id": "aaaaaaaaaaaa",
+            "title": "title",
+            "histUri": "http://example.com",
+            "visits": [ {"date": 15_423_493_234_840_000_000u64, "type": 1}]
+        });
+        let db = PlacesDb::open_in_memory(ConnectionType::Sync)?;
+        let outgoing = apply_and_get_outgoing(&db, vec![IncomingBso::from_test_content(json)]);
+        assert_eq!(outgoing.len(), 0, "nothing outgoing");
+
+        let now: Timestamp = SystemTime::now().into();
+        let (_page, visits) =
+            fetch_visits(&db, &Url::parse("http://example.com").unwrap(), 2)?.expect("page exists");
+        assert_eq!(visits.len(), 1);
+        assert!(
+            visits[0].visit_date <= now,
+            "should have clamped the timestamp"
+        );
+        Ok(())
+    }
+
+    #[test]
+    fn test_apply_plan_incoming_invalid_negative_timestamp() -> Result<()> {
+        let _ = env_logger::try_init();
+        let json = json!({
+            "id": "aaaaaaaaaaaa",
+            "title": "title",
+            "histUri": "http://example.com",
+            "visits": [ {"date": -123, "type": 1}]
+        });
+        let db = PlacesDb::open_in_memory(ConnectionType::Sync)?;
+        let outgoing = apply_and_get_outgoing(&db, vec![IncomingBso::from_test_content(json)]);
+        assert_eq!(outgoing.len(), 0, "should skip the invalid entry");
+        Ok(())
+    }
+
+    #[test]
+    fn test_apply_plan_incoming_invalid_visit_type() -> Result<()> {
+        let db = PlacesDb::open_in_memory(ConnectionType::Sync)?;
+        let visits = vec![HistoryRecordVisit {
+            date: SystemTime::now().into(),
+            transition: 99,
+            unknown_fields: UnknownFields::new(),
+        }];
+        let record = HistoryRecord {
+            id: "aaaaaaaaaaaa".into(),
+            title: "title".into(),
+            hist_uri: "http://example.com".into(),
+            visits,
+            unknown_fields: UnknownFields::new(),
+        };
+        let plan = plan_incoming_record(&db, record, 10);
+        // We expect "Reconciled" because after skipping the invalid visit
+        // we found nothing to apply.
+        assert!(matches!(plan, IncomingPlan::Reconciled));
+        Ok(())
+    }
+
+    #[test]
+    fn test_apply_plan_incoming_new() -> Result<()> {
+        let _ = env_logger::try_init();
+        let now: Timestamp = SystemTime::now().into();
+        let json = json!({
+            "id": "aaaaaaaaaaaa",
+            "title": "title",
+            "histUri": "http://example.com",
+            "visits": [ {"date": ServerVisitTimestamp::from(now), "type": 1}]
+        });
+        let db = PlacesDb::open_in_memory(ConnectionType::Sync)?;
+        let outgoing = apply_and_get_outgoing(&db, vec![IncomingBso::from_test_content(json)]);
+
+        // should have applied it locally.
+        let (page, visits) =
+            fetch_visits(&db, &Url::parse("http://example.com").unwrap(), 2)?.expect("page exists");
+        assert_eq!(page.title, "title");
+        assert_eq!(visits.len(), 1);
+        let visit = visits.into_iter().next().unwrap();
+        assert_eq!(visit.visit_date, now);
+
+        // page should have frecency (going through a public api to get this is a pain)
+        // XXX - FIXME - searching for "title" here fails to find a result?
+        // But above, we've checked title is in the record.
+        let found = search_frecent(
+            &db,
+            SearchParams {
+                search_string: "http://example.com".into(),
+                limit: 2,
+            },
+        )?;
+        assert_eq!(found.len(), 1);
+        let result = found.into_iter().next().unwrap();
+        assert!(result.frecency > 0, "should have frecency");
+
+        // and nothing outgoing.
+        assert_eq!(outgoing.len(), 0);
+        Ok(())
+    }
+
+    #[test]
+    fn test_apply_plan_outgoing_new() -> Result<()> {
+        let _ = env_logger::try_init();
+        let db = PlacesDb::open_in_memory(ConnectionType::Sync)?;
+        let url = Url::parse("https://example.com")?;
+        let now = SystemTime::now();
+        let obs = VisitObservation::new(url)
+            .with_visit_type(VisitType::Link)
+            .with_at(Some(now.into()));
+        apply_observation(&db, obs)?;
+
+        let outgoing = apply_and_get_outgoing(&db, vec![]);
+
+        assert_eq!(outgoing.len(), 1);
+        Ok(())
+    }
+
+    #[test]
+    fn test_simple_visit_reconciliation() -> Result<()> {
+        let _ = env_logger::try_init();
+        let db = PlacesDb::open_in_memory(ConnectionType::Sync)?;
+        let ts: Timestamp = (SystemTime::now() - Duration::new(5, 0)).into();
+        let url = Url::parse("https://example.com")?;
+
+        // First add a local visit with the timestamp.
+        let obs = VisitObservation::new(url.clone())
+            .with_visit_type(VisitType::Link)
+            .with_at(Some(ts));
+        apply_observation(&db, obs)?;
+        // Sync status should be "new" and have a change recorded.
+        assert_eq!(get_sync(&db, &url), (SyncStatus::New, 1));
+
+        let guid = get_existing_guid(&db, &url);
+
+        // and an incoming record with the same timestamp
+        let json = json!({
+            "id": guid,
+            "title": "title",
+            "histUri": url.as_str(),
+            "visits": [ {"date": ServerVisitTimestamp::from(ts), "type": 1}]
+        });
+
+        apply_and_get_outgoing(&db, vec![IncomingBso::from_test_content(json)]);
+
+        // should still have only 1 visit and it should still be local.
+        let (_page, visits) = fetch_visits(&db, &url, 2)?.expect("page exists");
+        assert_eq!(visits.len(), 1);
+        assert!(visits[0].is_local);
+        // The item should have changed to Normal and have no change counter.
+        assert_eq!(get_sync(&db, &url), (SyncStatus::Normal, 0));
+        Ok(())
+    }
+
+    #[test]
+    fn test_simple_visit_incoming_and_outgoing() -> Result<()> {
+        let _ = env_logger::try_init();
+        let db = PlacesDb::open_in_memory(ConnectionType::Sync)?;
+        let ts1: Timestamp = (SystemTime::now() - Duration::new(5, 0)).into();
+        let ts2: Timestamp = SystemTime::now().into();
+        let url = Url::parse("https://example.com")?;
+
+        // First add a local visit with ts1.
+        let obs = VisitObservation::new(url.clone())
+            .with_visit_type(VisitType::Link)
+            .with_at(Some(ts1));
+        apply_observation(&db, obs)?;
+
+        let guid = get_existing_guid(&db, &url);
+
+        // and an incoming record with ts2
+        let json = json!({
+            "id": guid,
+            "title": "title",
+            "histUri": url.as_str(),
+            "visits": [ {"date": ServerVisitTimestamp::from(ts2), "type": 1}]
+        });
+
+        let outgoing = apply_and_get_outgoing(&db, vec![IncomingBso::from_test_content(json)]);
+
+        // should now have both visits locally.
+        let (_page, visits) = fetch_visits(&db, &url, 3)?.expect("page exists");
+        assert_eq!(visits.len(), 2);
+
+        // and the record should still be in outgoing due to our local change.
+        assert_eq!(outgoing.len(), 1);
+        let record = outgoing[0].to_test_incoming_t::<HistoryRecord>();
+        assert_eq!(record.id, guid);
+        assert_eq!(record.visits.len(), 2, "should have both visits outgoing");
+        assert_eq!(
+            record.visits[0].date,
+            ts2.into(),
+            "most recent timestamp should be first"
+        );
+        assert_eq!(
+            record.visits[1].date,
+            ts1.into(),
+            "both timestamps should appear"
+        );
+        Ok(())
+    }
+
+    #[test]
+    fn test_incoming_tombstone_local_new() -> Result<()> {
+        let _ = env_logger::try_init();
+        let db = PlacesDb::open_in_memory(ConnectionType::Sync)?;
+        let url = Url::parse("https://example.com")?;
+        let obs = VisitObservation::new(url.clone())
+            .with_visit_type(VisitType::Link)
+            .with_at(Some(SystemTime::now().into()));
+        apply_observation(&db, obs)?;
+        assert_eq!(get_sync(&db, &url), (SyncStatus::New, 1));
+
+        let guid = get_existing_guid(&db, &url);
+
+        // and an incoming tombstone for that guid
+        let json = json!({
+            "id": guid,
+            "deleted": true,
+        });
+        let outgoing = apply_and_get_outgoing(&db, vec![IncomingBso::from_test_content(json)]);
+        assert_eq!(outgoing.len(), 0, "should be nothing outgoing");
+        assert_eq!(get_tombstone_count(&db), 0, "should be no tombstones");
+        Ok(())
+    }
+
+    #[test]
+    fn test_incoming_tombstone_local_normal() -> Result<()> {
+        let _ = env_logger::try_init();
+        let db = PlacesDb::open_in_memory(ConnectionType::Sync)?;
+        let url = Url::parse("https://example.com")?;
+        let obs = VisitObservation::new(url.clone())
+            .with_visit_type(VisitType::Link)
+            .with_at(Some(SystemTime::now().into()));
+        apply_observation(&db, obs)?;
+        let guid = get_existing_guid(&db, &url);
+
+        // Set the status to normal
+        apply_and_get_outgoing(&db, vec![]);
+        // It should have changed to normal but still have the initial counter.
+        assert_eq!(get_sync(&db, &url), (SyncStatus::Normal, 1));
+
+        // and an incoming tombstone for that guid
+        let json = json!({
+            "id": guid,
+            "deleted": true,
+        });
+
+        let outgoing = apply_and_get_outgoing(&db, vec![IncomingBso::from_test_content(json)]);
+        assert_eq!(outgoing.len(), 0, "should be nothing outgoing");
+        Ok(())
+    }
+
+    #[test]
+    fn test_outgoing_tombstone() -> Result<()> {
+        let _ = env_logger::try_init();
+        let db = PlacesDb::open_in_memory(ConnectionType::Sync)?;
+        let url = Url::parse("https://example.com")?;
+        let obs = VisitObservation::new(url.clone())
+            .with_visit_type(VisitType::Link)
+            .with_at(Some(SystemTime::now().into()));
+        apply_observation(&db, obs)?;
+        let guid = get_existing_guid(&db, &url);
+
+        // Set the status to normal
+        apply_and_get_outgoing(&db, vec![]);
+        // It should have changed to normal but still have the initial counter.
+        assert_eq!(get_sync(&db, &url), (SyncStatus::Normal, 1));
+
+        // Delete it.
+        delete_visits_for(&db, &guid)?;
+
+        // should be a local tombstone.
+        assert_eq!(get_tombstone_count(&db), 1);
+
+        let outgoing = apply_and_get_outgoing(&db, vec![]);
+        assert_eq!(outgoing.len(), 1, "tombstone should be uploaded");
+        finish_plan(&db)?;
+        // tombstone should be removed.
+        assert_eq!(get_tombstone_count(&db), 0);
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_clamp_visit_date() {
+        let ts = Timestamp::from(727_747_199_999);
+        assert!(clamp_visit_date(ts).is_err());
+
+        let ts = Timestamp::now();
+        assert_eq!(clamp_visit_date(ts), Ok(ts));
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/places/history_sync/record.rs.html b/book/rust-docs/src/places/history_sync/record.rs.html new file mode 100644 index 0000000000..50a6249160 --- /dev/null +++ b/book/rust-docs/src/places/history_sync/record.rs.html @@ -0,0 +1,163 @@ +record.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::{history_sync::ServerVisitTimestamp, types::UnknownFields};
+use serde::Deserialize;
+use serde_derive::*;
+use sync_guid::Guid as SyncGuid;
+
+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
+#[serde(rename_all = "camelCase")]
+pub struct HistoryRecordVisit {
+    pub date: ServerVisitTimestamp,
+    #[serde(rename = "type")]
+    pub transition: u8,
+
+    #[serde(flatten)]
+    pub unknown_fields: UnknownFields,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct HistoryRecord {
+    // TODO: consider `#[serde(rename = "id")] pub guid: String` to avoid confusion
+    pub id: SyncGuid,
+
+    #[serde(default)]
+    #[serde(deserialize_with = "deserialize_nonull_string")]
+    #[serde(skip_serializing_if = "String::is_empty")]
+    pub title: String,
+
+    pub hist_uri: String,
+
+    pub visits: Vec<HistoryRecordVisit>,
+
+    #[serde(flatten)]
+    pub unknown_fields: UnknownFields,
+}
+
+fn deserialize_nonull_string<'de, D>(deserializer: D) -> Result<String, D::Error>
+where
+    D: serde::Deserializer<'de>,
+{
+    Ok(match <Option<String>>::deserialize(deserializer)? {
+        Some(s) => s,
+        None => "".to_string(),
+    })
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_null_title() {
+        // #5544 tells us we are seeing an explicit null for an incoming tab title.
+        // Really not clear where these are coming from - possibly very old versions of
+        // apps, but seems easy to handle, so here we are!
+        let json = serde_json::json!({
+            "id": "foo",
+            "title": null,
+            "histUri": "https://example.com",
+            "visits": [],
+        });
+
+        let rec = serde_json::from_value::<HistoryRecord>(json).expect("should deser");
+        assert!(rec.title.is_empty());
+    }
+
+    #[test]
+    fn test_missing_title() {
+        let json = serde_json::json!({
+            "id": "foo",
+            "histUri": "https://example.com",
+            "visits": [],
+        });
+
+        let rec = serde_json::from_value::<HistoryRecord>(json).expect("should deser");
+        assert!(rec.title.is_empty());
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/places/import/common.rs.html b/book/rust-docs/src/places/import/common.rs.html new file mode 100644 index 0000000000..455214d942 --- /dev/null +++ b/book/rust-docs/src/places/import/common.rs.html @@ -0,0 +1,427 @@ +common.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::db::PlacesDb;
+use crate::error::*;
+use rusqlite::{named_params, Connection};
+use serde::Serialize;
+use sql_support::ConnExt;
+use types::Timestamp;
+use url::Url;
+
+// sanitize_timestamp can't use `Timestamp::now();` directly because it needs
+// to sanitize both created and modified, plus ensure modified isn't before
+// created - which isn't possible with the non-monotonic timestamp.
+// So we have a static `NOW`, which will be initialized the first time it is
+// referenced, and that value subsequently used for every imported bookmark (and
+// note that it's only used in cases where the existing timestamps are invalid.)
+// This is fine for our use-case, where we do exactly one import as soon as the
+// process starts.
+lazy_static::lazy_static! {
+    pub static ref NOW: Timestamp = Timestamp::now();
+}
+
+pub mod sql_fns {
+    use crate::import::common::NOW;
+    use crate::storage::URL_LENGTH_MAX;
+    use rusqlite::{functions::Context, types::ValueRef, Result};
+    use types::Timestamp;
+    use url::Url;
+
+    fn sanitize_timestamp(ts: i64) -> Result<Timestamp> {
+        let now = *NOW;
+        let is_sane = |ts: Timestamp| -> bool { Timestamp::EARLIEST <= ts && ts <= now };
+        let ts = Timestamp(u64::try_from(ts).unwrap_or(0));
+        if is_sane(ts) {
+            return Ok(ts);
+        }
+        // Maybe the timestamp was actually in μs?
+        let ts = Timestamp(ts.as_millis() / 1000);
+        if is_sane(ts) {
+            return Ok(ts);
+        }
+        Ok(now)
+    }
+
+    // Unfortunately dates for history visits in old iOS databases
+    // have a type of `REAL` in their schema. This means they are represented
+    // as a float value and have to be read as f64s.
+    // This is unconventional, and you probably don't need to use
+    // this function otherwise.
+    #[inline(never)]
+    pub fn sanitize_float_timestamp(ctx: &Context<'_>) -> Result<Timestamp> {
+        let ts = ctx
+            .get::<f64>(0)
+            .map(|num| {
+                if num.is_normal() && num > 0.0 {
+                    num.round() as i64
+                } else {
+                    0
+                }
+            })
+            .unwrap_or(0);
+        sanitize_timestamp(ts)
+    }
+
+    #[inline(never)]
+    pub fn sanitize_integer_timestamp(ctx: &Context<'_>) -> Result<Timestamp> {
+        sanitize_timestamp(ctx.get::<i64>(0).unwrap_or(0))
+    }
+
+    // Possibly better named as "normalize URL" - even in non-error cases, the
+    // result string may not be the same href used passed as input.
+    #[inline(never)]
+    pub fn validate_url(ctx: &Context<'_>) -> Result<Option<String>> {
+        let val = ctx.get_raw(0);
+        let href = if let ValueRef::Text(s) = val {
+            String::from_utf8_lossy(s).to_string()
+        } else {
+            return Ok(None);
+        };
+        if href.len() > URL_LENGTH_MAX {
+            return Ok(None);
+        }
+        if let Ok(url) = Url::parse(&href) {
+            Ok(Some(url.into()))
+        } else {
+            Ok(None)
+        }
+    }
+
+    // Sanitize a text column into valid utf-8. Leave NULLs alone, but all other
+    // types are converted to an empty string.
+    #[inline(never)]
+    pub fn sanitize_utf8(ctx: &Context<'_>) -> Result<Option<String>> {
+        let val = ctx.get_raw(0);
+        Ok(match val {
+            ValueRef::Text(s) => Some(String::from_utf8_lossy(s).to_string()),
+            ValueRef::Null => None,
+            _ => Some("".to_owned()),
+        })
+    }
+}
+
+pub fn attached_database<'a>(
+    conn: &'a PlacesDb,
+    path: &Url,
+    db_alias: &'static str,
+) -> Result<ExecuteOnDrop<'a>> {
+    conn.execute(
+        "ATTACH DATABASE :path AS :db_alias",
+        named_params! {
+            ":path": path.as_str(),
+            ":db_alias": db_alias,
+        },
+    )?;
+    Ok(ExecuteOnDrop {
+        conn,
+        sql: format!("DETACH DATABASE {};", db_alias),
+    })
+}
+
+/// We use/abuse the mirror to perform our import, but need to clean it up
+/// afterwards. This is an RAII helper to do so.
+///
+/// Ideally, you should call `execute_now` rather than letting this drop
+/// automatically, as we can't report errors beyond logging when running
+/// Drop.
+pub struct ExecuteOnDrop<'a> {
+    conn: &'a PlacesDb,
+    sql: String,
+}
+
+impl<'a> ExecuteOnDrop<'a> {
+    pub fn new(conn: &'a PlacesDb, sql: String) -> Self {
+        Self { conn, sql }
+    }
+
+    pub fn execute_now(self) -> Result<()> {
+        self.conn.execute_batch(&self.sql)?;
+        // Don't run our `drop` function.
+        std::mem::forget(self);
+        Ok(())
+    }
+}
+
+impl Drop for ExecuteOnDrop<'_> {
+    fn drop(&mut self) {
+        if let Err(e) = self.conn.execute_batch(&self.sql) {
+            error_support::report_error!(
+                "places-cleanup-failure",
+                "Failed to clean up after import! {}",
+                e
+            );
+            log::debug!("  Failed query: {}", &self.sql);
+        }
+    }
+}
+
+pub fn select_count(conn: &PlacesDb, stmt: &str) -> Result<u32> {
+    let count: Result<Option<u32>> =
+        conn.try_query_row(stmt, [], |row| Ok(row.get::<_, u32>(0)?), false);
+    count.map(|op| op.unwrap_or(0))
+}
+
+#[derive(Serialize, PartialEq, Eq, Debug, Clone, Default)]
+pub struct HistoryMigrationResult {
+    pub num_total: u32,
+    pub num_succeeded: u32,
+    pub num_failed: u32,
+    pub total_duration: u64,
+}
+
+pub fn define_history_migration_functions(c: &Connection) -> Result<()> {
+    use rusqlite::functions::FunctionFlags;
+    c.create_scalar_function(
+        "validate_url",
+        1,
+        FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
+        crate::import::common::sql_fns::validate_url,
+    )?;
+    c.create_scalar_function(
+        "sanitize_timestamp",
+        1,
+        FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
+        crate::import::common::sql_fns::sanitize_integer_timestamp,
+    )?;
+    c.create_scalar_function(
+        "hash",
+        -1,
+        FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
+        crate::db::db::sql_fns::hash,
+    )?;
+    c.create_scalar_function(
+        "generate_guid",
+        0,
+        FunctionFlags::SQLITE_UTF8,
+        crate::db::db::sql_fns::generate_guid,
+    )?;
+    c.create_scalar_function(
+        "sanitize_utf8",
+        1,
+        FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
+        crate::import::common::sql_fns::sanitize_utf8,
+    )?;
+    c.create_scalar_function(
+        "sanitize_float_timestamp",
+        1,
+        FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
+        crate::import::common::sql_fns::sanitize_float_timestamp,
+    )?;
+    Ok(())
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/places/import/ios.rs.html b/book/rust-docs/src/places/import/ios.rs.html new file mode 100644 index 0000000000..d839695c2f --- /dev/null +++ b/book/rust-docs/src/places/import/ios.rs.html @@ -0,0 +1,13 @@ +ios.rs - source
1
+2
+3
+4
+5
+6
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+pub mod history;
+pub use history::import as import_history;
+
\ No newline at end of file diff --git a/book/rust-docs/src/places/import/ios/history.rs.html b/book/rust-docs/src/places/import/ios/history.rs.html new file mode 100644 index 0000000000..153969403c --- /dev/null +++ b/book/rust-docs/src/places/import/ios/history.rs.html @@ -0,0 +1,461 @@ +history.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use std::time::Instant;
+
+use crate::error::Result;
+use crate::history_sync::engine::LAST_SYNC_META_KEY;
+use crate::import::common::{
+    attached_database, define_history_migration_functions, select_count, HistoryMigrationResult,
+};
+use crate::storage::{put_meta, update_all_frecencies_at_once};
+use crate::PlacesDb;
+use types::Timestamp;
+use url::Url;
+
+/// This import is used for iOS users migrating from `browser.db`-based
+/// history storage to the new rust-places store.
+///
+/// The goal of this import is to persist all local browser.db items into places database
+///
+///
+/// ### Basic process
+///
+/// - Attach the iOS database.
+/// - Slurp records into a temp table "iOSHistoryStaging" from iOS database.
+///   - This is mostly done for convenience, to punycode the URLs and some performance benefits over
+///     using a view or reading things into Rust
+/// - Add any entries to moz_places that are needed (in practice, most are
+///   needed, users in practice don't have nearly as many bookmarks as history entries)
+/// - Use iosHistoryStaging and the browser.db to migrate visits to the places visits table.
+/// - Update frecency for new items.
+/// - Cleanup (detach iOS database, etc).
+pub fn import(
+    conn: &PlacesDb,
+    path: impl AsRef<std::path::Path>,
+    last_sync_timestamp: i64,
+) -> Result<HistoryMigrationResult> {
+    let url = crate::util::ensure_url_path(path)?;
+    do_import(conn, url, last_sync_timestamp)
+}
+
+fn do_import(
+    conn: &PlacesDb,
+    ios_db_file_url: Url,
+    last_sync_timestamp: i64,
+) -> Result<HistoryMigrationResult> {
+    let scope = conn.begin_interrupt_scope()?;
+    define_history_migration_functions(conn)?;
+    // TODO: for some reason opening the db as read-only in **iOS** causes
+    // the migration to fail with an "attempting to write to a read-only database"
+    // when the migration is **not** writing to the BrowserDB database.
+    // this only happens in the simulator with artifacts built for iOS and not
+    // in unit tests.
+
+    // ios_db_file_url.query_pairs_mut().append_pair("mode", "ro");
+    let import_start = Instant::now();
+    log::info!("Attaching database {}", ios_db_file_url);
+    let auto_detach = attached_database(conn, &ios_db_file_url, "ios")?;
+    let tx = conn.begin_transaction()?;
+    let num_total = select_count(conn, &COUNT_IOS_HISTORY_VISITS)?;
+    log::info!("The number of visits is: {:?}", num_total);
+
+    log::info!("Creating and populating staging table");
+
+    tx.execute_batch(&CREATE_TEMP_VISIT_TABLE)?;
+    tx.execute_batch(&FILL_VISIT_TABLE)?;
+    tx.execute_batch(&CREATE_STAGING_TABLE)?;
+    tx.execute_batch(&FILL_STAGING)?;
+    scope.err_if_interrupted()?;
+
+    log::info!("Updating old titles that may be missing, but now are available");
+    tx.execute_batch(&UPDATE_PLACES_TITLES)?;
+    scope.err_if_interrupted()?;
+
+    log::info!("Populating missing entries in moz_places");
+    tx.execute_batch(&FILL_MOZ_PLACES)?;
+    scope.err_if_interrupted()?;
+
+    log::info!("Inserting the history visits");
+    tx.execute_batch(&INSERT_HISTORY_VISITS)?;
+    scope.err_if_interrupted()?;
+
+    log::info!("Insert all new entries into stale frecencies");
+    let now = Timestamp::now().as_millis();
+    tx.execute(&ADD_TO_STALE_FRECENCIES, &[(":now", &now)])?;
+    scope.err_if_interrupted()?;
+
+    // Once the migration is done, we also migrate the sync timestamp if we have one
+    // this prevents us from having to do a **full** sync
+    put_meta(conn, LAST_SYNC_META_KEY, &last_sync_timestamp)?;
+
+    tx.commit()?;
+    log::info!("Successfully imported history visits!");
+
+    log::info!("Counting Places history visits");
+
+    let num_succeeded = select_count(conn, &COUNT_PLACES_HISTORY_VISITS)?;
+    let num_failed = num_total.saturating_sub(num_succeeded);
+
+    // We now update the frecencies as its own transaction
+    // this is desired because we want reader connections to
+    // read the migrated data and not have to wait for the
+    // frecencies to be up to date
+    log::info!("Updating all frecencies");
+    update_all_frecencies_at_once(conn, &scope)?;
+    log::info!("Frecencies updated!");
+    auto_detach.execute_now()?;
+
+    Ok(HistoryMigrationResult {
+        num_total,
+        num_succeeded,
+        num_failed,
+        total_duration: import_start.elapsed().as_millis() as u64,
+    })
+}
+
+lazy_static::lazy_static! {
+   // Count IOS history visits
+   static ref COUNT_IOS_HISTORY_VISITS: &'static str =
+       "SELECT COUNT(*) FROM ios.visits v
+        LEFT JOIN ios.history h on v.siteID = h.id
+        WHERE h.is_deleted = 0"
+   ;
+
+   // Create a temporrary table for visists
+   static ref CREATE_TEMP_VISIT_TABLE: &'static str = "
+    CREATE TEMP TABLE IF NOT EXISTS temp.latestVisits(
+        id INTEGER PRIMARY KEY,
+        siteID INTEGER NOT NULL,
+        date REAL NOT NULL,
+        type INTEGER NOT NULL,
+        is_local TINYINT NOT NULL
+    ) WITHOUT ROWID;
+   ";
+
+   // Insert into temp visit table
+   static ref FILL_VISIT_TABLE: &'static str = "
+    INSERT OR IGNORE INTO temp.latestVisits(id, siteID, date, type, is_local)
+        SELECT
+            id,
+            siteID,
+            date,
+            type,
+            is_local
+        FROM ios.visits
+        ORDER BY date DESC
+        LIMIT 10000
+   ";
+
+   // We use a staging table purely so that we can normalize URLs (and
+   // specifically, punycode them)
+   static ref CREATE_STAGING_TABLE: &'static str = "
+        CREATE TEMP TABLE IF NOT EXISTS temp.iOSHistoryStaging(
+            id INTEGER PRIMARY KEY,
+            url TEXT,
+            url_hash INTEGER NOT NULL,
+            title TEXT
+        ) WITHOUT ROWID;";
+
+   static ref FILL_STAGING: &'static str = "
+    INSERT OR IGNORE INTO temp.iOSHistoryStaging(id, url, url_hash, title)
+        SELECT
+            h.id,
+            validate_url(h.url),
+            hash(validate_url(h.url)),
+            sanitize_utf8(h.title)
+        FROM temp.latestVisits v
+        JOIN ios.history h on v.siteID = h.id
+        WHERE h.url IS NOT NULL
+        AND h.is_deleted = 0
+        "
+   ;
+
+    // Unfortunately UPDATE FROM is not available until sqlite 3.33
+   // however, iOS does not ship with 3.33 yet as of the time of writing.
+   static ref UPDATE_PLACES_TITLES: &'static str =
+   "UPDATE main.moz_places
+        SET title = IFNULL((SELECT t.title
+                            FROM temp.iOSHistoryStaging t
+                            WHERE t.url_hash = main.moz_places.url_hash AND t.url = main.moz_places.url), title)"
+    ;
+
+   // Insert any missing entries into moz_places that we'll need for this.
+   static ref FILL_MOZ_PLACES: &'static str =
+   "INSERT OR IGNORE INTO main.moz_places(guid, url, url_hash, title, frecency, sync_change_counter)
+        SELECT
+            IFNULL(
+                (SELECT p.guid FROM main.moz_places p WHERE p.url_hash = t.url_hash AND p.url = t.url),
+                generate_guid()
+            ),
+            t.url,
+            t.url_hash,
+            t.title,
+            -1,
+            1
+        FROM temp.iOSHistoryStaging t
+   "
+   ;
+
+   // Insert history visits
+   static ref INSERT_HISTORY_VISITS: &'static str =
+   "INSERT OR IGNORE INTO main.moz_historyvisits(from_visit, place_id, visit_date, visit_type, is_local)
+        SELECT
+            NULL, -- iOS does not store enough information to rebuild redirect chains.
+            (SELECT p.id FROM main.moz_places p WHERE p.url_hash = t.url_hash AND p.url = t.url),
+            sanitize_float_timestamp(v.date),
+            v.type, -- iOS stores visit types that map 1:1 to ours.
+            v.is_local
+        FROM temp.latestVisits v
+        JOIN temp.iOSHistoryStaging t on v.siteID = t.id
+    "
+   ;
+
+
+   // Count places history visits
+   static ref COUNT_PLACES_HISTORY_VISITS: &'static str =
+       "SELECT COUNT(*) FROM main.moz_historyvisits"
+   ;
+
+   // Adds newly modified places entries into the stale frecencies table
+   static ref ADD_TO_STALE_FRECENCIES: &'static str =
+   "INSERT OR IGNORE INTO main.moz_places_stale_frecencies(place_id, stale_at)
+    SELECT
+        p.id,
+        :now
+    FROM main.moz_places p
+    WHERE p.frecency = -1"
+    ;
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/places/import/mod.rs.html b/book/rust-docs/src/places/import/mod.rs.html new file mode 100644 index 0000000000..aca8464b7f --- /dev/null +++ b/book/rust-docs/src/places/import/mod.rs.html @@ -0,0 +1,15 @@ +mod.rs - source
1
+2
+3
+4
+5
+6
+7
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+pub mod common;
+pub mod ios;
+pub use ios::import_history as import_ios_history;
+
\ No newline at end of file diff --git a/book/rust-docs/src/places/lib.rs.html b/book/rust-docs/src/places/lib.rs.html new file mode 100644 index 0000000000..e2284ece76 --- /dev/null +++ b/book/rust-docs/src/places/lib.rs.html @@ -0,0 +1,83 @@ +lib.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#![allow(unknown_lints)]
+#![warn(rust_2018_idioms)]
+
+pub mod api;
+pub mod error;
+pub mod types;
+// Making these all pub for now while we flesh out the API.
+pub mod bookmark_sync;
+pub mod db;
+pub mod ffi;
+pub mod frecency;
+pub mod hash;
+pub mod history_sync;
+// match_impl is pub mostly for benchmarks (which have to run as a separate pseudo-crate).
+pub mod import;
+pub mod match_impl;
+pub mod observation;
+pub mod storage;
+#[cfg(test)]
+mod tests;
+mod util;
+
+pub use crate::api::apply_observation;
+#[cfg(test)]
+pub use crate::api::places_api::test;
+pub use crate::api::places_api::{get_registered_sync_engine, ConnectionType, PlacesApi};
+
+pub use crate::db::PlacesDb;
+pub use crate::error::*;
+pub use crate::observation::*;
+pub use crate::storage::PageInfo;
+pub use crate::storage::RowId;
+pub use crate::types::*;
+
+pub use ffi::*;
+
+uniffi::include_scaffolding!("places");
+
\ No newline at end of file diff --git a/book/rust-docs/src/places/match_impl.rs.html b/book/rust-docs/src/places/match_impl.rs.html new file mode 100644 index 0000000000..53b47c4a29 --- /dev/null +++ b/book/rust-docs/src/places/match_impl.rs.html @@ -0,0 +1,1043 @@ +match_impl.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::util;
+use bitflags::bitflags;
+use caseless::Caseless;
+use rusqlite::{
+    self,
+    types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef},
+};
+use std::borrow::Cow;
+
+const MAX_CHARS_TO_SEARCH_THROUGH: usize = 255;
+
+#[derive(Clone, Copy, PartialEq, Eq, Debug)]
+#[repr(u32)]
+pub enum MatchBehavior {
+    // Match anywhere in each searchable tearm
+    Anywhere = 0,
+    /// Match first on word boundaries, and if we do not get enough results, then
+    /// match anywhere in each searchable term.
+    BoundaryAnywhere = 1,
+    /// Match on word boundaries in each searchable term.
+    Boundary = 2,
+    /// Match only the beginning of each search term.
+    Beginning = 3,
+    /// Match anywhere in each searchable term without doing any transformation
+    /// or stripping on the underlying data.
+    AnywhereUnmodified = 4,
+    /// Match only the beginning of each search term using a case sensitive
+    /// comparator
+    BeginningCaseSensitive = 5,
+}
+
+impl FromSql for MatchBehavior {
+    #[inline]
+    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
+        Ok(match value.as_i64()? {
+            0 => MatchBehavior::Anywhere,
+            1 => MatchBehavior::BoundaryAnywhere,
+            2 => MatchBehavior::Boundary,
+            3 => MatchBehavior::Beginning,
+            4 => MatchBehavior::AnywhereUnmodified,
+            5 => MatchBehavior::BeginningCaseSensitive,
+            _ => return Err(FromSqlError::InvalidType),
+        })
+    }
+}
+
+impl ToSql for MatchBehavior {
+    #[inline]
+    fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
+        Ok(ToSqlOutput::from(*self as u32))
+    }
+}
+
+bitflags! {
+    pub struct SearchBehavior: u32 {
+        /// Search through history.
+        const HISTORY = 1;
+
+        /// Search through bookmarks.
+        const BOOKMARK = 1 << 1;
+
+        /// Search through tags.
+        const TAG = 1 << 2;
+
+        /// Search through the title of pages.
+        const TITLE = 1 << 3;
+
+        /// Search the URL of pages.
+        const URL = 1 << 4;
+
+        /// Search for typed pages
+        const TYPED = 1 << 5;
+
+        /// Search for javascript: urls
+        const JAVASCRIPT = 1 << 6;
+
+        /// Search for open pages (currently not meaningfully implemented)
+        const OPENPAGE = 1 << 7;
+
+        /// Use intersection between history, typed, bookmark, tag and openpage
+        /// instead of union, when the restrict bit is set.
+        const RESTRICT = 1 << 8;
+
+        /// Include search suggestions from the currently selected search provider
+        /// (currently not implemented)
+        const SEARCHES = 1 << 9;
+    }
+}
+
+impl Default for SearchBehavior {
+    // See `defaultBehavior` in Desktop's `UrlbarPrefs.jsm`.
+    fn default() -> SearchBehavior {
+        SearchBehavior::HISTORY
+            | SearchBehavior::BOOKMARK
+            | SearchBehavior::OPENPAGE
+            | SearchBehavior::SEARCHES
+    }
+}
+
+impl SearchBehavior {
+    #[inline]
+    pub fn any() -> Self {
+        SearchBehavior::all() & !SearchBehavior::RESTRICT
+    }
+}
+
+impl FromSql for SearchBehavior {
+    #[inline]
+    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
+        SearchBehavior::from_bits(u32::column_result(value)?).ok_or(FromSqlError::InvalidType)
+    }
+}
+
+impl ToSql for SearchBehavior {
+    #[inline]
+    fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
+        Ok(ToSqlOutput::from(self.bits()))
+    }
+}
+
+/// Convert `c` to lower case if it's an alphabetic ascii character, or completely mangle it if it's
+/// not. Just returns `c | 0x20`. I don't know if I actually believe this is faster in a way that
+/// matters than the saner version.
+#[inline(always)]
+fn dubious_to_ascii_lower(c: u8) -> u8 {
+    c | 0x20
+}
+
+/// A port of nextSearchCandidate in the desktop places's SQLFunctions.cpp:
+///
+/// > Scan forward through UTF-8 text until the next potential character that
+/// > could match a given codepoint when lower-cased (false positives are okay).
+/// > This avoids having to actually parse the UTF-8 text, which is slow.
+///
+/// It returns the byte index of the first character that could possibly match.
+#[inline(always)]
+fn next_search_candidate(to_search: &str, search_for: char) -> Option<usize> {
+    // If the character we search for is ASCII, then we can scan until we find
+    // it or its ASCII uppercase character, modulo the special cases
+    // U+0130 LATIN CAPITAL LETTER I WITH DOT ABOVE and U+212A KELVIN SIGN
+    // (which are the only non-ASCII characters that lower-case to ASCII ones).
+    // Since false positives are okay, we approximate ASCII lower-casing by
+    // bit-ORing with 0x20, for increased performance.
+    //
+    // If the character we search for is *not* ASCII, we can ignore everything
+    // that is, since all ASCII characters lower-case to ASCII.
+    //
+    // Because of how UTF-8 uses high-order bits, this will never land us
+    // in the middle of a codepoint.
+    //
+    // The assumptions about Unicode made here are verified in test_casing.
+    let search_bytes = to_search.as_bytes();
+    if (search_for as u32) < 128 {
+        // When searching for I or K, we pick out the first byte of the UTF-8
+        // encoding of the corresponding special case character, and look for it
+        // in the loop below.  For other characters we fall back to 0xff, which
+        // is not a valid UTF-8 byte.
+        let target = dubious_to_ascii_lower(search_for as u8);
+        let special = if target == b'i' {
+            0xc4u8
+        } else if target == b'k' {
+            0xe2u8
+        } else {
+            0xffu8
+        };
+        // Note: rustc doesn't do great at all on the more idiomatic
+        // implementation of this (or below), but it does okay for this.
+        let mut ci = 0;
+        while ci < search_bytes.len() {
+            let cur = search_bytes[ci];
+            if dubious_to_ascii_lower(cur) == target || cur == special {
+                return Some(ci);
+            }
+            ci += 1;
+        }
+    } else {
+        let mut ci = 0;
+        while ci < search_bytes.len() {
+            let cur = search_bytes[ci];
+            if (cur & 0x80) != 0 {
+                return Some(ci);
+            }
+            ci += 1;
+        }
+    }
+    None
+}
+
+#[inline(always)]
+fn is_ascii_lower_alpha(c: u8) -> bool {
+    // Equivalent to (but fewer operations than) `b'a' <= c && c <= b'z'`
+    c.wrapping_sub(b'a') <= (b'z' - b'a')
+}
+
+/// port of isOnBoundary from gecko places.
+///
+/// > Check whether a character position is on a word boundary of a UTF-8 string
+/// > (rather than within a word).  We define "within word" to be any position
+/// > between [a-zA-Z] and [a-z] -- this lets us match CamelCase words.
+/// > TODO: support non-latin alphabets.
+#[inline(always)]
+fn is_on_boundary(text: &str, index: usize) -> bool {
+    if index == 0 {
+        return true;
+    }
+    let bytes = text.as_bytes();
+    if is_ascii_lower_alpha(bytes[index]) {
+        let prev_lower = dubious_to_ascii_lower(bytes[index - 1]);
+        !is_ascii_lower_alpha(prev_lower)
+    } else {
+        true
+    }
+}
+
+/// Returns true if `source` starts with `token` ignoring case.
+///
+/// Loose port of stringMatch from places, which we've modified to perform more correct case
+/// folding (if this turns out to be a perf issue we can always address it then).
+#[inline]
+fn string_match(token: &str, source: &str) -> bool {
+    if source.len() < token.len() {
+        return false;
+    }
+    let mut ti = token.chars().default_case_fold();
+    let mut si = source.chars().default_case_fold();
+    loop {
+        match (ti.next(), si.next()) {
+            (None, _) => return true,
+            (Some(_), None) => return false,
+            (Some(x), Some(y)) => {
+                if x != y {
+                    return false;
+                }
+            }
+        }
+    }
+}
+
+/// This performs single-codepoint case folding. It will do the wrong thing
+/// for characters which have lowercase equivalents with multiple characters.
+#[inline]
+fn char_to_lower_single(c: char) -> char {
+    c.to_lowercase().next().unwrap()
+}
+
+/// Read the next codepoint out of `s` and return it's lowercase variant, and the index of the
+/// codepoint after it.
+#[inline]
+fn next_codepoint_lower(s: &str) -> (char, usize) {
+    // This is super convoluted, and I wish a more direct way to do it was exposed. (In theory
+    // this should be more efficient than this implementation is)
+    let mut indices = s.char_indices();
+    let (_, next_char) = indices.next().unwrap();
+    let next_index = indices
+        .next()
+        .map(|(index, _)| index)
+        .unwrap_or_else(|| s.len());
+    (char_to_lower_single(next_char), next_index)
+}
+
+// Port of places `findInString`.
+pub fn find_in_string(token: &str, src: &str, only_boundary: bool) -> bool {
+    // Place's version has this restriction too
+    assert!(!token.is_empty(), "Don't search for an empty string");
+    if src.len() < token.len() {
+        return false;
+    }
+
+    let token_first_char = next_codepoint_lower(token).0;
+    // The C++ code is a big ol pointer party, and even indexes with negative numbers
+    // in some places. We aren't quite this depraved, so we just use indices into slices.
+    //
+    // There's probably a higher cost to this than usual, and if we had more robust testing
+    // (fuzzing, even) it might be worth measuring a version of this that avoids more of the
+    // bounds checks.
+    let mut cur_offset = 0;
+    // Scan forward to the next viable candidate (if any).
+    while let Some(src_idx) = next_search_candidate(&src[cur_offset..], token_first_char) {
+        if cur_offset + src_idx >= src.len() {
+            break;
+        }
+        cur_offset += src_idx;
+        let src_cur = &src[cur_offset..];
+
+        // Check whether the first character in the token matches the character
+        // at src_cur. At the same time, get the index of the next character
+        // in the source.
+        let (src_next_char, next_offset_in_cur) = next_codepoint_lower(src_cur);
+
+        // If it is the first character, and we either don't care about boundaries or
+        // we're on one, do the more expensive string matching and return true if it hits.
+        if src_next_char == token_first_char
+            && (!only_boundary || is_on_boundary(src, cur_offset))
+            && string_match(token, src_cur)
+        {
+            return true;
+        }
+        cur_offset += next_offset_in_cur;
+    }
+    false
+}
+
+// Search functions used as function pointers by AutocompleteMatch::Invoke
+
+fn find_anywhere(token: &str, source: &str) -> bool {
+    assert!(!token.is_empty(), "Don't search for an empty token");
+    find_in_string(token, source, false)
+}
+
+fn find_on_boundary(token: &str, source: &str) -> bool {
+    assert!(!token.is_empty(), "Don't search for an empty token");
+    find_in_string(token, source, true)
+}
+
+fn find_beginning(token: &str, source: &str) -> bool {
+    assert!(!token.is_empty(), "Don't search for an empty token");
+    string_match(token, source)
+}
+
+fn find_beginning_case_sensitive(token: &str, source: &str) -> bool {
+    assert!(!token.is_empty(), "Don't search for an empty token");
+    source.starts_with(token)
+}
+
+// I can't wait for Rust 2018 when lifetime annotations are automatic.
+pub struct AutocompleteMatch<'search, 'url, 'title, 'tags> {
+    pub search_str: &'search str,
+    pub url_str: &'url str,
+    pub title_str: &'title str,
+    pub tags: &'tags str,
+    pub visit_count: u32,
+    pub typed: bool,
+    pub bookmarked: bool,
+    pub open_page_count: u32,
+    pub match_behavior: MatchBehavior,
+    pub search_behavior: SearchBehavior,
+}
+
+impl<'search, 'url, 'title, 'tags> AutocompleteMatch<'search, 'url, 'title, 'tags> {
+    fn get_search_fn(&self) -> fn(&str, &str) -> bool {
+        match self.match_behavior {
+            MatchBehavior::Anywhere | MatchBehavior::AnywhereUnmodified => find_anywhere,
+            MatchBehavior::Beginning => find_beginning,
+            MatchBehavior::BeginningCaseSensitive => find_beginning_case_sensitive,
+            _ => find_on_boundary,
+        }
+    }
+
+    fn fixup_url_str<'a>(&self, mut s: &'a str) -> Cow<'a, str> {
+        if self.match_behavior != MatchBehavior::AnywhereUnmodified {
+            if s.starts_with("http://") {
+                s = &s[7..];
+            } else if s.starts_with("https://") {
+                s = &s[8..];
+            } else if s.starts_with("ftp://") {
+                s = &s[6..];
+            }
+        }
+        // Bail out early if we don't need to percent decode. It's a
+        // little weird that it's measurably faster to check this
+        // separately, but whatever.
+        if memchr::memchr(b'%', s.as_bytes()).is_none() {
+            return Cow::Borrowed(s);
+        }
+        // TODO: would be nice to decode punycode here too, but for now
+        // this is probably fine.
+        match percent_encoding::percent_decode(s.as_bytes()).decode_utf8() {
+            Err(_) => Cow::Borrowed(s),
+            Ok(decoded) => decoded,
+        }
+    }
+
+    #[inline]
+    fn has_behavior(&self, behavior: SearchBehavior) -> bool {
+        self.search_behavior.intersects(behavior)
+    }
+
+    pub fn invoke(&self) -> bool {
+        // We only want to filter javascript: URLs if we are not supposed to search
+        // for them, and the search does not start with "javascript:".
+        if self.match_behavior == MatchBehavior::AnywhereUnmodified
+            && self.url_str.starts_with("javascript:")
+            && !self.has_behavior(SearchBehavior::JAVASCRIPT)
+            && !self.search_str.starts_with("javascript:")
+        {
+            return false;
+        }
+        let matches = if self.has_behavior(SearchBehavior::RESTRICT) {
+            (!self.has_behavior(SearchBehavior::HISTORY) || self.visit_count > 0)
+                && (!self.has_behavior(SearchBehavior::TYPED) || self.typed)
+                && (!self.has_behavior(SearchBehavior::BOOKMARK) || self.bookmarked)
+                && (!self.has_behavior(SearchBehavior::TAG) || !self.tags.is_empty())
+                && (!self.has_behavior(SearchBehavior::OPENPAGE) || self.open_page_count > 0)
+        } else {
+            (self.has_behavior(SearchBehavior::HISTORY) && self.visit_count > 0)
+                || (self.has_behavior(SearchBehavior::TYPED) && self.typed)
+                || (self.has_behavior(SearchBehavior::BOOKMARK) && self.bookmarked)
+                || (self.has_behavior(SearchBehavior::TAG) && !self.tags.is_empty())
+                || (self.has_behavior(SearchBehavior::OPENPAGE) && self.open_page_count > 0)
+        };
+        if !matches {
+            return false;
+        }
+        let fixed_url = self.fixup_url_str(self.url_str);
+        let search_fn = self.get_search_fn();
+
+        let trimmed_url = util::slice_up_to(fixed_url.as_ref(), MAX_CHARS_TO_SEARCH_THROUGH);
+        let trimmed_title = util::slice_up_to(self.title_str, MAX_CHARS_TO_SEARCH_THROUGH);
+        for token in self.search_str.split_ascii_whitespace() {
+            let matches = match (
+                self.has_behavior(SearchBehavior::TITLE),
+                self.has_behavior(SearchBehavior::URL),
+            ) {
+                (true, true) => {
+                    (search_fn(token, trimmed_title) || search_fn(token, self.tags))
+                        && search_fn(token, trimmed_url)
+                }
+                (true, false) => search_fn(token, trimmed_title) || search_fn(token, self.tags),
+                (false, true) => search_fn(token, trimmed_url),
+                (false, false) => {
+                    search_fn(token, trimmed_url)
+                        || search_fn(token, trimmed_title)
+                        || search_fn(token, self.tags)
+                }
+            };
+            if !matches {
+                return false;
+            }
+        }
+        true
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn test_is_ascii_lower_alpha() {
+        // just check exhaustively
+        for c in 0u8..=255u8 {
+            assert_eq!(
+                is_ascii_lower_alpha(c),
+                c.is_ascii_lowercase(),
+                "is_lower_ascii_alpha is wrong for {}",
+                c
+            );
+        }
+    }
+
+    // Test the various dubious things this code assumes about unicode / ascii text
+    // in the name of performance. This is mostly a port of the test_casing gtests in places
+    #[test]
+    fn test_casing_assumptions() {
+        use std::char;
+        // Verify the assertion in next_search_candidate that the
+        // only non-ASCII characters that lower-case to ASCII ones are:
+        //  * U+0130 LATIN CAPITAL LETTER I WITH DOT ABOVE
+        //  * U+212A KELVIN SIGN
+        //
+        // It also checks that U+0130 is the only single codepoint that lower cases
+        // to multiple characters.
+        for c in 128..0x11_0000 {
+            if let Some(ch) = char::from_u32(c) {
+                // Not quite the same (because codepoints aren't characters), but
+                // should serve the same purpose.
+                let mut li = ch.to_lowercase();
+                let lc = li.next().unwrap();
+                if c != 304 && c != 8490 {
+                    assert!(
+                        (lc as u32) >= 128,
+                        "Lower case of non-ascii '{}' ({}) was unexpectedly ascii",
+                        ch,
+                        c
+                    );
+                    // This one we added (it's an implicit assumption in the utilities the
+                    // places code uses).
+                    assert!(
+                        li.next().is_none(),
+                        "Lower case of '{}' ({}) produced multiple codepoints unexpectedly",
+                        ch,
+                        c
+                    );
+                } else {
+                    assert!(
+                        (lc as u32) < 128,
+                        "Lower case of non-ascii '{}' ({}) was unexpectedly not ascii",
+                        ch,
+                        c
+                    );
+                }
+            }
+        }
+
+        // Verify the assertion that all ASCII characters lower-case to ASCII.
+        for c in 0..128 {
+            let ch = char::from_u32(c).unwrap();
+            let mut li = ch.to_lowercase();
+            let lc = li.next().unwrap();
+            assert!(
+                li.next().is_none() && (lc as u32) < 128,
+                "Lower case of ascii '{}' ({}) wasn't ascii :(",
+                ch,
+                c
+            );
+        }
+
+        for c in (b'a'..=b'z').chain(b'A'..=b'Z') {
+            assert_eq!(
+                dubious_to_ascii_lower(c),
+                c.to_ascii_lowercase(),
+                "c: '{}'",
+                c as char
+            );
+        }
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/places/observation.rs.html b/book/rust-docs/src/places/observation.rs.html new file mode 100644 index 0000000000..31c7d7a4f3 --- /dev/null +++ b/book/rust-docs/src/places/observation.rs.html @@ -0,0 +1,241 @@ +observation.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::types::*;
+use types::Timestamp;
+use url::Url;
+
+/// An "observation" based model for updating history.
+/// You create a VisitObservation, call functions on it which correspond
+/// with what you observed. The page will then be updated using this info.
+///
+/// It's implemented such that the making of an "observation" is itself
+/// significant - it records what specific changes should be made to storage.
+/// For example, instead of simple bools with defaults (where you can't
+/// differentiate explicitly setting a value from the default value), we use
+/// Option<bool>, with the idea being it's better for a store shaped like Mentat.
+///
+/// It exposes a "builder api", but for convenience, that API allows Options too.
+/// So, eg, `.with_title(None)` or `with_is_error(None)` is allowed but records
+/// no observation.
+#[derive(Clone, Debug)]
+pub struct VisitObservation {
+    pub url: Url,
+    pub title: Option<String>,
+    pub visit_type: Option<VisitType>,
+    pub is_error: Option<bool>,
+    pub is_redirect_source: Option<bool>,
+    pub is_permanent_redirect_source: Option<bool>,
+    pub at: Option<Timestamp>,
+    pub referrer: Option<Url>,
+    pub is_remote: Option<bool>,
+    pub preview_image_url: Option<Url>,
+}
+
+impl VisitObservation {
+    pub fn new(url: Url) -> Self {
+        VisitObservation {
+            url,
+            title: None,
+            visit_type: None,
+            is_error: None,
+            is_redirect_source: None,
+            is_permanent_redirect_source: None,
+            at: None,
+            referrer: None,
+            is_remote: None,
+            preview_image_url: None,
+        }
+    }
+
+    // A "builder" API to sanely build an observation. Note that this can be
+    // called with Option<String> (and if None will effectively be a no-op)
+    // or directly with a string.
+    pub fn with_title(mut self, t: impl Into<Option<String>>) -> Self {
+        self.title = t.into();
+        self
+    }
+
+    pub fn with_visit_type(mut self, t: impl Into<Option<VisitType>>) -> Self {
+        self.visit_type = t.into();
+        self
+    }
+
+    pub fn with_is_error(mut self, v: impl Into<Option<bool>>) -> Self {
+        self.is_error = v.into();
+        self
+    }
+
+    pub fn with_is_redirect_source(mut self, v: impl Into<Option<bool>>) -> Self {
+        self.is_redirect_source = v.into();
+        self
+    }
+
+    pub fn with_is_permanent_redirect_source(mut self, v: impl Into<Option<bool>>) -> Self {
+        self.is_permanent_redirect_source = v.into();
+        self
+    }
+
+    pub fn with_at(mut self, v: impl Into<Option<Timestamp>>) -> Self {
+        self.at = v.into();
+        self
+    }
+
+    pub fn with_is_remote(mut self, v: impl Into<Option<bool>>) -> Self {
+        self.is_remote = v.into();
+        self
+    }
+
+    pub fn with_referrer(mut self, v: impl Into<Option<Url>>) -> Self {
+        self.referrer = v.into().map(Url::into);
+        self
+    }
+
+    pub fn with_preview_image_url(mut self, v: impl Into<Option<Url>>) -> Self {
+        self.preview_image_url = v.into();
+        self
+    }
+
+    // Other helpers which can be derived.
+    pub fn get_redirect_frecency_boost(&self) -> bool {
+        self.is_redirect_source.is_some()
+            && match self.visit_type {
+                Some(t) => t != VisitType::Typed,
+                _ => true,
+            }
+    }
+
+    // nsHistory::GetHiddenState()
+    pub fn get_is_hidden(&self) -> bool {
+        match self.visit_type {
+            Some(visit_type) => {
+                self.is_redirect_source.is_some()
+                    || visit_type == VisitType::FramedLink
+                    || visit_type == VisitType::Embed
+            }
+            None => false,
+        }
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/places/storage/bookmarks.rs.html b/book/rust-docs/src/places/storage/bookmarks.rs.html new file mode 100644 index 0000000000..f33d94d2f4 --- /dev/null +++ b/book/rust-docs/src/places/storage/bookmarks.rs.html @@ -0,0 +1,4083 @@ +bookmarks.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
+540
+541
+542
+543
+544
+545
+546
+547
+548
+549
+550
+551
+552
+553
+554
+555
+556
+557
+558
+559
+560
+561
+562
+563
+564
+565
+566
+567
+568
+569
+570
+571
+572
+573
+574
+575
+576
+577
+578
+579
+580
+581
+582
+583
+584
+585
+586
+587
+588
+589
+590
+591
+592
+593
+594
+595
+596
+597
+598
+599
+600
+601
+602
+603
+604
+605
+606
+607
+608
+609
+610
+611
+612
+613
+614
+615
+616
+617
+618
+619
+620
+621
+622
+623
+624
+625
+626
+627
+628
+629
+630
+631
+632
+633
+634
+635
+636
+637
+638
+639
+640
+641
+642
+643
+644
+645
+646
+647
+648
+649
+650
+651
+652
+653
+654
+655
+656
+657
+658
+659
+660
+661
+662
+663
+664
+665
+666
+667
+668
+669
+670
+671
+672
+673
+674
+675
+676
+677
+678
+679
+680
+681
+682
+683
+684
+685
+686
+687
+688
+689
+690
+691
+692
+693
+694
+695
+696
+697
+698
+699
+700
+701
+702
+703
+704
+705
+706
+707
+708
+709
+710
+711
+712
+713
+714
+715
+716
+717
+718
+719
+720
+721
+722
+723
+724
+725
+726
+727
+728
+729
+730
+731
+732
+733
+734
+735
+736
+737
+738
+739
+740
+741
+742
+743
+744
+745
+746
+747
+748
+749
+750
+751
+752
+753
+754
+755
+756
+757
+758
+759
+760
+761
+762
+763
+764
+765
+766
+767
+768
+769
+770
+771
+772
+773
+774
+775
+776
+777
+778
+779
+780
+781
+782
+783
+784
+785
+786
+787
+788
+789
+790
+791
+792
+793
+794
+795
+796
+797
+798
+799
+800
+801
+802
+803
+804
+805
+806
+807
+808
+809
+810
+811
+812
+813
+814
+815
+816
+817
+818
+819
+820
+821
+822
+823
+824
+825
+826
+827
+828
+829
+830
+831
+832
+833
+834
+835
+836
+837
+838
+839
+840
+841
+842
+843
+844
+845
+846
+847
+848
+849
+850
+851
+852
+853
+854
+855
+856
+857
+858
+859
+860
+861
+862
+863
+864
+865
+866
+867
+868
+869
+870
+871
+872
+873
+874
+875
+876
+877
+878
+879
+880
+881
+882
+883
+884
+885
+886
+887
+888
+889
+890
+891
+892
+893
+894
+895
+896
+897
+898
+899
+900
+901
+902
+903
+904
+905
+906
+907
+908
+909
+910
+911
+912
+913
+914
+915
+916
+917
+918
+919
+920
+921
+922
+923
+924
+925
+926
+927
+928
+929
+930
+931
+932
+933
+934
+935
+936
+937
+938
+939
+940
+941
+942
+943
+944
+945
+946
+947
+948
+949
+950
+951
+952
+953
+954
+955
+956
+957
+958
+959
+960
+961
+962
+963
+964
+965
+966
+967
+968
+969
+970
+971
+972
+973
+974
+975
+976
+977
+978
+979
+980
+981
+982
+983
+984
+985
+986
+987
+988
+989
+990
+991
+992
+993
+994
+995
+996
+997
+998
+999
+1000
+1001
+1002
+1003
+1004
+1005
+1006
+1007
+1008
+1009
+1010
+1011
+1012
+1013
+1014
+1015
+1016
+1017
+1018
+1019
+1020
+1021
+1022
+1023
+1024
+1025
+1026
+1027
+1028
+1029
+1030
+1031
+1032
+1033
+1034
+1035
+1036
+1037
+1038
+1039
+1040
+1041
+1042
+1043
+1044
+1045
+1046
+1047
+1048
+1049
+1050
+1051
+1052
+1053
+1054
+1055
+1056
+1057
+1058
+1059
+1060
+1061
+1062
+1063
+1064
+1065
+1066
+1067
+1068
+1069
+1070
+1071
+1072
+1073
+1074
+1075
+1076
+1077
+1078
+1079
+1080
+1081
+1082
+1083
+1084
+1085
+1086
+1087
+1088
+1089
+1090
+1091
+1092
+1093
+1094
+1095
+1096
+1097
+1098
+1099
+1100
+1101
+1102
+1103
+1104
+1105
+1106
+1107
+1108
+1109
+1110
+1111
+1112
+1113
+1114
+1115
+1116
+1117
+1118
+1119
+1120
+1121
+1122
+1123
+1124
+1125
+1126
+1127
+1128
+1129
+1130
+1131
+1132
+1133
+1134
+1135
+1136
+1137
+1138
+1139
+1140
+1141
+1142
+1143
+1144
+1145
+1146
+1147
+1148
+1149
+1150
+1151
+1152
+1153
+1154
+1155
+1156
+1157
+1158
+1159
+1160
+1161
+1162
+1163
+1164
+1165
+1166
+1167
+1168
+1169
+1170
+1171
+1172
+1173
+1174
+1175
+1176
+1177
+1178
+1179
+1180
+1181
+1182
+1183
+1184
+1185
+1186
+1187
+1188
+1189
+1190
+1191
+1192
+1193
+1194
+1195
+1196
+1197
+1198
+1199
+1200
+1201
+1202
+1203
+1204
+1205
+1206
+1207
+1208
+1209
+1210
+1211
+1212
+1213
+1214
+1215
+1216
+1217
+1218
+1219
+1220
+1221
+1222
+1223
+1224
+1225
+1226
+1227
+1228
+1229
+1230
+1231
+1232
+1233
+1234
+1235
+1236
+1237
+1238
+1239
+1240
+1241
+1242
+1243
+1244
+1245
+1246
+1247
+1248
+1249
+1250
+1251
+1252
+1253
+1254
+1255
+1256
+1257
+1258
+1259
+1260
+1261
+1262
+1263
+1264
+1265
+1266
+1267
+1268
+1269
+1270
+1271
+1272
+1273
+1274
+1275
+1276
+1277
+1278
+1279
+1280
+1281
+1282
+1283
+1284
+1285
+1286
+1287
+1288
+1289
+1290
+1291
+1292
+1293
+1294
+1295
+1296
+1297
+1298
+1299
+1300
+1301
+1302
+1303
+1304
+1305
+1306
+1307
+1308
+1309
+1310
+1311
+1312
+1313
+1314
+1315
+1316
+1317
+1318
+1319
+1320
+1321
+1322
+1323
+1324
+1325
+1326
+1327
+1328
+1329
+1330
+1331
+1332
+1333
+1334
+1335
+1336
+1337
+1338
+1339
+1340
+1341
+1342
+1343
+1344
+1345
+1346
+1347
+1348
+1349
+1350
+1351
+1352
+1353
+1354
+1355
+1356
+1357
+1358
+1359
+1360
+1361
+1362
+1363
+1364
+1365
+1366
+1367
+1368
+1369
+1370
+1371
+1372
+1373
+1374
+1375
+1376
+1377
+1378
+1379
+1380
+1381
+1382
+1383
+1384
+1385
+1386
+1387
+1388
+1389
+1390
+1391
+1392
+1393
+1394
+1395
+1396
+1397
+1398
+1399
+1400
+1401
+1402
+1403
+1404
+1405
+1406
+1407
+1408
+1409
+1410
+1411
+1412
+1413
+1414
+1415
+1416
+1417
+1418
+1419
+1420
+1421
+1422
+1423
+1424
+1425
+1426
+1427
+1428
+1429
+1430
+1431
+1432
+1433
+1434
+1435
+1436
+1437
+1438
+1439
+1440
+1441
+1442
+1443
+1444
+1445
+1446
+1447
+1448
+1449
+1450
+1451
+1452
+1453
+1454
+1455
+1456
+1457
+1458
+1459
+1460
+1461
+1462
+1463
+1464
+1465
+1466
+1467
+1468
+1469
+1470
+1471
+1472
+1473
+1474
+1475
+1476
+1477
+1478
+1479
+1480
+1481
+1482
+1483
+1484
+1485
+1486
+1487
+1488
+1489
+1490
+1491
+1492
+1493
+1494
+1495
+1496
+1497
+1498
+1499
+1500
+1501
+1502
+1503
+1504
+1505
+1506
+1507
+1508
+1509
+1510
+1511
+1512
+1513
+1514
+1515
+1516
+1517
+1518
+1519
+1520
+1521
+1522
+1523
+1524
+1525
+1526
+1527
+1528
+1529
+1530
+1531
+1532
+1533
+1534
+1535
+1536
+1537
+1538
+1539
+1540
+1541
+1542
+1543
+1544
+1545
+1546
+1547
+1548
+1549
+1550
+1551
+1552
+1553
+1554
+1555
+1556
+1557
+1558
+1559
+1560
+1561
+1562
+1563
+1564
+1565
+1566
+1567
+1568
+1569
+1570
+1571
+1572
+1573
+1574
+1575
+1576
+1577
+1578
+1579
+1580
+1581
+1582
+1583
+1584
+1585
+1586
+1587
+1588
+1589
+1590
+1591
+1592
+1593
+1594
+1595
+1596
+1597
+1598
+1599
+1600
+1601
+1602
+1603
+1604
+1605
+1606
+1607
+1608
+1609
+1610
+1611
+1612
+1613
+1614
+1615
+1616
+1617
+1618
+1619
+1620
+1621
+1622
+1623
+1624
+1625
+1626
+1627
+1628
+1629
+1630
+1631
+1632
+1633
+1634
+1635
+1636
+1637
+1638
+1639
+1640
+1641
+1642
+1643
+1644
+1645
+1646
+1647
+1648
+1649
+1650
+1651
+1652
+1653
+1654
+1655
+1656
+1657
+1658
+1659
+1660
+1661
+1662
+1663
+1664
+1665
+1666
+1667
+1668
+1669
+1670
+1671
+1672
+1673
+1674
+1675
+1676
+1677
+1678
+1679
+1680
+1681
+1682
+1683
+1684
+1685
+1686
+1687
+1688
+1689
+1690
+1691
+1692
+1693
+1694
+1695
+1696
+1697
+1698
+1699
+1700
+1701
+1702
+1703
+1704
+1705
+1706
+1707
+1708
+1709
+1710
+1711
+1712
+1713
+1714
+1715
+1716
+1717
+1718
+1719
+1720
+1721
+1722
+1723
+1724
+1725
+1726
+1727
+1728
+1729
+1730
+1731
+1732
+1733
+1734
+1735
+1736
+1737
+1738
+1739
+1740
+1741
+1742
+1743
+1744
+1745
+1746
+1747
+1748
+1749
+1750
+1751
+1752
+1753
+1754
+1755
+1756
+1757
+1758
+1759
+1760
+1761
+1762
+1763
+1764
+1765
+1766
+1767
+1768
+1769
+1770
+1771
+1772
+1773
+1774
+1775
+1776
+1777
+1778
+1779
+1780
+1781
+1782
+1783
+1784
+1785
+1786
+1787
+1788
+1789
+1790
+1791
+1792
+1793
+1794
+1795
+1796
+1797
+1798
+1799
+1800
+1801
+1802
+1803
+1804
+1805
+1806
+1807
+1808
+1809
+1810
+1811
+1812
+1813
+1814
+1815
+1816
+1817
+1818
+1819
+1820
+1821
+1822
+1823
+1824
+1825
+1826
+1827
+1828
+1829
+1830
+1831
+1832
+1833
+1834
+1835
+1836
+1837
+1838
+1839
+1840
+1841
+1842
+1843
+1844
+1845
+1846
+1847
+1848
+1849
+1850
+1851
+1852
+1853
+1854
+1855
+1856
+1857
+1858
+1859
+1860
+1861
+1862
+1863
+1864
+1865
+1866
+1867
+1868
+1869
+1870
+1871
+1872
+1873
+1874
+1875
+1876
+1877
+1878
+1879
+1880
+1881
+1882
+1883
+1884
+1885
+1886
+1887
+1888
+1889
+1890
+1891
+1892
+1893
+1894
+1895
+1896
+1897
+1898
+1899
+1900
+1901
+1902
+1903
+1904
+1905
+1906
+1907
+1908
+1909
+1910
+1911
+1912
+1913
+1914
+1915
+1916
+1917
+1918
+1919
+1920
+1921
+1922
+1923
+1924
+1925
+1926
+1927
+1928
+1929
+1930
+1931
+1932
+1933
+1934
+1935
+1936
+1937
+1938
+1939
+1940
+1941
+1942
+1943
+1944
+1945
+1946
+1947
+1948
+1949
+1950
+1951
+1952
+1953
+1954
+1955
+1956
+1957
+1958
+1959
+1960
+1961
+1962
+1963
+1964
+1965
+1966
+1967
+1968
+1969
+1970
+1971
+1972
+1973
+1974
+1975
+1976
+1977
+1978
+1979
+1980
+1981
+1982
+1983
+1984
+1985
+1986
+1987
+1988
+1989
+1990
+1991
+1992
+1993
+1994
+1995
+1996
+1997
+1998
+1999
+2000
+2001
+2002
+2003
+2004
+2005
+2006
+2007
+2008
+2009
+2010
+2011
+2012
+2013
+2014
+2015
+2016
+2017
+2018
+2019
+2020
+2021
+2022
+2023
+2024
+2025
+2026
+2027
+2028
+2029
+2030
+2031
+2032
+2033
+2034
+2035
+2036
+2037
+2038
+2039
+2040
+2041
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use super::RowId;
+use super::{delete_meta, put_meta};
+use super::{fetch_page_info, new_page_info};
+use crate::bookmark_sync::engine::{
+    COLLECTION_SYNCID_META_KEY, GLOBAL_SYNCID_META_KEY, LAST_SYNC_META_KEY,
+};
+use crate::db::PlacesDb;
+use crate::error::*;
+use crate::types::{BookmarkType, SyncStatus};
+use rusqlite::{self, Connection, Row};
+#[cfg(test)]
+use serde_json::{self, json};
+use sql_support::{self, repeat_sql_vars, ConnExt};
+use std::cmp::{max, min};
+use sync15::engine::EngineSyncAssociation;
+use sync_guid::Guid as SyncGuid;
+use types::Timestamp;
+use url::Url;
+
+pub use root_guid::{BookmarkRootGuid, USER_CONTENT_ROOTS};
+
+mod conversions;
+pub mod fetch;
+pub mod json_tree;
+mod root_guid;
+
+fn create_root(
+    db: &Connection,
+    title: &str,
+    guid: &SyncGuid,
+    position: u32,
+    when: Timestamp,
+) -> rusqlite::Result<()> {
+    let sql = format!(
+        "
+        INSERT INTO moz_bookmarks
+            (type, position, title, dateAdded, lastModified, guid, parent,
+             syncChangeCounter, syncStatus)
+        VALUES
+            (:item_type, :item_position, :item_title, :date_added, :last_modified, :guid,
+             (SELECT id FROM moz_bookmarks WHERE guid = {:?}),
+             1, :sync_status)
+        ",
+        BookmarkRootGuid::Root.as_guid().as_str()
+    );
+    let params = rusqlite::named_params! {
+        ":item_type": &BookmarkType::Folder,
+        ":item_position": &position,
+        ":item_title": &title,
+        ":date_added": &when,
+        ":last_modified": &when,
+        ":guid": guid,
+        ":sync_status": &SyncStatus::New,
+    };
+    db.execute_cached(&sql, params)?;
+    Ok(())
+}
+
+pub fn create_bookmark_roots(db: &Connection) -> rusqlite::Result<()> {
+    let now = Timestamp::now();
+    create_root(db, "root", &BookmarkRootGuid::Root.into(), 0, now)?;
+    create_root(db, "menu", &BookmarkRootGuid::Menu.into(), 0, now)?;
+    create_root(db, "toolbar", &BookmarkRootGuid::Toolbar.into(), 1, now)?;
+    create_root(db, "unfiled", &BookmarkRootGuid::Unfiled.into(), 2, now)?;
+    create_root(db, "mobile", &BookmarkRootGuid::Mobile.into(), 3, now)?;
+    Ok(())
+}
+
+#[derive(Debug, Copy, Clone)]
+pub enum BookmarkPosition {
+    Specific { pos: u32 },
+    Append,
+}
+
+/// Helpers to deal with managing the position correctly.
+
+/// Updates the position of existing items so that the insertion of a child in
+/// the position specified leaves all siblings with the correct position.
+/// Returns the index the item should be inserted at.
+fn resolve_pos_for_insert(
+    db: &PlacesDb,
+    pos: BookmarkPosition,
+    parent: &RawBookmark,
+) -> Result<u32> {
+    Ok(match pos {
+        BookmarkPosition::Specific { pos } => {
+            let actual = min(pos, parent.child_count);
+            // must reorder existing children.
+            db.execute_cached(
+                "UPDATE moz_bookmarks SET position = position + 1
+                 WHERE parent = :parent_id
+                 AND position >= :position",
+                &[
+                    (":parent_id", &parent.row_id as &dyn rusqlite::ToSql),
+                    (":position", &actual),
+                ],
+            )?;
+            actual
+        }
+        BookmarkPosition::Append => parent.child_count,
+    })
+}
+
+/// Updates the position of existing items so that the deletion of a child
+/// from the position specified leaves all siblings with the correct position.
+fn update_pos_for_deletion(db: &PlacesDb, pos: u32, parent_id: RowId) -> Result<()> {
+    db.execute_cached(
+        "UPDATE moz_bookmarks SET position = position - 1
+         WHERE parent = :parent
+         AND position >= :position",
+        &[
+            (":parent", &parent_id as &dyn rusqlite::ToSql),
+            (":position", &pos),
+        ],
+    )?;
+    Ok(())
+}
+
+/// Updates the position of existing items when an item is being moved in the
+/// same folder.
+/// Returns what the position should be updated to.
+fn update_pos_for_move(
+    db: &PlacesDb,
+    pos: BookmarkPosition,
+    bm: &RawBookmark,
+    parent: &RawBookmark,
+) -> Result<u32> {
+    assert_eq!(bm.parent_id.unwrap(), parent.row_id);
+    // Note the additional -1's below are to account for the item already being
+    // in the folder.
+    let new_index = match pos {
+        BookmarkPosition::Specific { pos } => min(pos, parent.child_count - 1),
+        BookmarkPosition::Append => parent.child_count - 1,
+    };
+    db.execute_cached(
+        "UPDATE moz_bookmarks
+         SET position = CASE WHEN :new_index < :cur_index
+            THEN position + 1
+            ELSE position - 1
+         END
+         WHERE parent = :parent_id
+         AND position BETWEEN :low_index AND :high_index",
+        &[
+            (":new_index", &new_index as &dyn rusqlite::ToSql),
+            (":cur_index", &bm.position),
+            (":parent_id", &parent.row_id),
+            (":low_index", &min(bm.position, new_index)),
+            (":high_index", &max(bm.position, new_index)),
+        ],
+    )?;
+    Ok(new_index)
+}
+
+/// Structures which can be used to insert a bookmark, folder or separator.
+#[derive(Debug, Clone)]
+pub struct InsertableBookmark {
+    pub parent_guid: SyncGuid,
+    pub position: BookmarkPosition,
+    pub date_added: Option<Timestamp>,
+    pub last_modified: Option<Timestamp>,
+    pub guid: Option<SyncGuid>,
+    pub url: Url,
+    pub title: Option<String>,
+}
+
+impl From<InsertableBookmark> for InsertableItem {
+    fn from(b: InsertableBookmark) -> Self {
+        InsertableItem::Bookmark { b }
+    }
+}
+
+#[derive(Debug, Clone)]
+pub struct InsertableSeparator {
+    pub parent_guid: SyncGuid,
+    pub position: BookmarkPosition,
+    pub date_added: Option<Timestamp>,
+    pub last_modified: Option<Timestamp>,
+    pub guid: Option<SyncGuid>,
+}
+
+impl From<InsertableSeparator> for InsertableItem {
+    fn from(s: InsertableSeparator) -> Self {
+        InsertableItem::Separator { s }
+    }
+}
+
+#[derive(Debug, Clone)]
+pub struct InsertableFolder {
+    pub parent_guid: SyncGuid,
+    pub position: BookmarkPosition,
+    pub date_added: Option<Timestamp>,
+    pub last_modified: Option<Timestamp>,
+    pub guid: Option<SyncGuid>,
+    pub title: Option<String>,
+    pub children: Vec<InsertableItem>,
+}
+
+impl From<InsertableFolder> for InsertableItem {
+    fn from(f: InsertableFolder) -> Self {
+        InsertableItem::Folder { f }
+    }
+}
+
+// The type used to insert the actual item.
+#[derive(Debug, Clone)]
+pub enum InsertableItem {
+    Bookmark { b: InsertableBookmark },
+    Separator { s: InsertableSeparator },
+    Folder { f: InsertableFolder },
+}
+
+// We allow all "common" fields from the sub-types to be getters on the
+// InsertableItem type.
+macro_rules! impl_common_bookmark_getter {
+    ($getter_name:ident, $T:ty) => {
+        fn $getter_name(&self) -> &$T {
+            match self {
+                InsertableItem::Bookmark { b } => &b.$getter_name,
+                InsertableItem::Separator { s } => &s.$getter_name,
+                InsertableItem::Folder { f } => &f.$getter_name,
+            }
+        }
+    };
+}
+
+impl InsertableItem {
+    fn bookmark_type(&self) -> BookmarkType {
+        match self {
+            InsertableItem::Bookmark { .. } => BookmarkType::Bookmark,
+            InsertableItem::Separator { .. } => BookmarkType::Separator,
+            InsertableItem::Folder { .. } => BookmarkType::Folder,
+        }
+    }
+    impl_common_bookmark_getter!(parent_guid, SyncGuid);
+    impl_common_bookmark_getter!(position, BookmarkPosition);
+    impl_common_bookmark_getter!(date_added, Option<Timestamp>);
+    impl_common_bookmark_getter!(last_modified, Option<Timestamp>);
+    impl_common_bookmark_getter!(guid, Option<SyncGuid>);
+
+    // We allow a setter for parent_guid and timestamps to help when inserting a tree.
+    fn set_parent_guid(&mut self, guid: SyncGuid) {
+        match self {
+            InsertableItem::Bookmark { b } => b.parent_guid = guid,
+            InsertableItem::Separator { s } => s.parent_guid = guid,
+            InsertableItem::Folder { f } => f.parent_guid = guid,
+        }
+    }
+
+    fn set_last_modified(&mut self, ts: Timestamp) {
+        match self {
+            InsertableItem::Bookmark { b } => b.last_modified = Some(ts),
+            InsertableItem::Separator { s } => s.last_modified = Some(ts),
+            InsertableItem::Folder { f } => f.last_modified = Some(ts),
+        }
+    }
+
+    fn set_date_added(&mut self, ts: Timestamp) {
+        match self {
+            InsertableItem::Bookmark { b } => b.date_added = Some(ts),
+            InsertableItem::Separator { s } => s.date_added = Some(ts),
+            InsertableItem::Folder { f } => f.date_added = Some(ts),
+        }
+    }
+}
+
+pub fn insert_bookmark(db: &PlacesDb, bm: InsertableItem) -> Result<SyncGuid> {
+    let tx = db.begin_transaction()?;
+    let result = insert_bookmark_in_tx(db, bm);
+    super::delete_pending_temp_tables(db)?;
+    match result {
+        Ok(_) => tx.commit()?,
+        Err(_) => tx.rollback()?,
+    }
+    result
+}
+
+pub fn maybe_truncate_title<'a>(t: &Option<&'a str>) -> Option<&'a str> {
+    use super::TITLE_LENGTH_MAX;
+    use crate::util::slice_up_to;
+    t.map(|title| slice_up_to(title, TITLE_LENGTH_MAX))
+}
+
+fn insert_bookmark_in_tx(db: &PlacesDb, bm: InsertableItem) -> Result<SyncGuid> {
+    // find the row ID of the parent.
+    if bm.parent_guid() == BookmarkRootGuid::Root {
+        return Err(InvalidPlaceInfo::CannotUpdateRoot(BookmarkRootGuid::Root).into());
+    }
+    let parent_guid = bm.parent_guid();
+    let parent = get_raw_bookmark(db, parent_guid)?
+        .ok_or_else(|| InvalidPlaceInfo::NoSuchGuid(parent_guid.to_string()))?;
+    if parent.bookmark_type != BookmarkType::Folder {
+        return Err(InvalidPlaceInfo::InvalidParent(parent_guid.to_string()).into());
+    }
+    // Do the "position" dance.
+    let position = resolve_pos_for_insert(db, *bm.position(), &parent)?;
+
+    // Note that we could probably do this 'fk' work as a sub-query (although
+    // markh isn't clear how we could perform the insert) - it probably doesn't
+    // matter in practice though...
+    let fk = match bm {
+        InsertableItem::Bookmark { ref b } => {
+            let page_info = match fetch_page_info(db, &b.url)? {
+                Some(info) => info.page,
+                None => new_page_info(db, &b.url, None)?,
+            };
+            Some(page_info.row_id)
+        }
+        _ => None,
+    };
+    let sql = "INSERT INTO moz_bookmarks
+              (fk, type, parent, position, title, dateAdded, lastModified,
+               guid, syncStatus, syncChangeCounter) VALUES
+              (:fk, :type, :parent, :position, :title, :dateAdded, :lastModified,
+               :guid, :syncStatus, :syncChangeCounter)";
+
+    let guid = bm.guid().clone().unwrap_or_else(SyncGuid::random);
+    if !guid.is_valid_for_places() || !guid.is_valid_for_sync_server() {
+        return Err(InvalidPlaceInfo::InvalidGuid.into());
+    }
+    let date_added = bm.date_added().unwrap_or_else(Timestamp::now);
+    // last_modified can't be before date_added
+    let last_modified = max(
+        bm.last_modified().unwrap_or_else(Timestamp::now),
+        date_added,
+    );
+
+    let bookmark_type = bm.bookmark_type();
+    match bm {
+        InsertableItem::Bookmark { ref b } => {
+            let title = maybe_truncate_title(&b.title.as_deref());
+            db.execute_cached(
+                sql,
+                &[
+                    (":fk", &fk as &dyn rusqlite::ToSql),
+                    (":type", &bookmark_type),
+                    (":parent", &parent.row_id),
+                    (":position", &position),
+                    (":title", &title),
+                    (":dateAdded", &date_added),
+                    (":lastModified", &last_modified),
+                    (":guid", &guid),
+                    (":syncStatus", &SyncStatus::New),
+                    (":syncChangeCounter", &1),
+                ],
+            )?;
+        }
+        InsertableItem::Separator { .. } => {
+            db.execute_cached(
+                sql,
+                &[
+                    (":type", &bookmark_type as &dyn rusqlite::ToSql),
+                    (":parent", &parent.row_id),
+                    (":position", &position),
+                    (":dateAdded", &date_added),
+                    (":lastModified", &last_modified),
+                    (":guid", &guid),
+                    (":syncStatus", &SyncStatus::New),
+                    (":syncChangeCounter", &1),
+                ],
+            )?;
+        }
+        InsertableItem::Folder { f } => {
+            let title = maybe_truncate_title(&f.title.as_deref());
+            db.execute_cached(
+                sql,
+                &[
+                    (":type", &bookmark_type as &dyn rusqlite::ToSql),
+                    (":parent", &parent.row_id),
+                    (":title", &title),
+                    (":position", &position),
+                    (":dateAdded", &date_added),
+                    (":lastModified", &last_modified),
+                    (":guid", &guid),
+                    (":syncStatus", &SyncStatus::New),
+                    (":syncChangeCounter", &1),
+                ],
+            )?;
+            // now recurse for children
+            for mut child in f.children.into_iter() {
+                // As a special case for trees, each child in a folder can specify
+                // Guid::Empty as the parent.
+                let specified_parent_guid = child.parent_guid();
+                if specified_parent_guid.is_empty() {
+                    child.set_parent_guid(guid.clone());
+                } else if *specified_parent_guid != guid {
+                    return Err(
+                        InvalidPlaceInfo::InvalidParent(specified_parent_guid.to_string()).into(),
+                    );
+                }
+                // If children have defaults for last_modified and date_added we use the parent
+                if child.last_modified().is_none() {
+                    child.set_last_modified(last_modified);
+                }
+                if child.date_added().is_none() {
+                    child.set_date_added(date_added);
+                }
+                insert_bookmark_in_tx(db, child)?;
+            }
+        }
+    };
+
+    // Bump the parent's change counter.
+    let sql_counter = "
+        UPDATE moz_bookmarks SET syncChangeCounter = syncChangeCounter + 1
+        WHERE id = :parent_id";
+    db.execute_cached(sql_counter, &[(":parent_id", &parent.row_id)])?;
+
+    Ok(guid)
+}
+
+/// Delete the specified bookmark. Returns true if a bookmark with the guid
+/// existed and was deleted, false otherwise.
+pub fn delete_bookmark(db: &PlacesDb, guid: &SyncGuid) -> Result<bool> {
+    let tx = db.begin_transaction()?;
+    let result = delete_bookmark_in_tx(db, guid);
+    match result {
+        Ok(_) => tx.commit()?,
+        Err(_) => tx.rollback()?,
+    }
+    result
+}
+
+fn delete_bookmark_in_tx(db: &PlacesDb, guid: &SyncGuid) -> Result<bool> {
+    // Can't delete a root.
+    if let Some(root) = BookmarkRootGuid::well_known(guid.as_str()) {
+        return Err(InvalidPlaceInfo::CannotUpdateRoot(root).into());
+    }
+    let record = match get_raw_bookmark(db, guid)? {
+        Some(r) => r,
+        None => {
+            log::debug!("Can't delete bookmark '{:?}' as it doesn't exist", guid);
+            return Ok(false);
+        }
+    };
+    // There's an argument to be made here that we should still honor the
+    // deletion in the case of this corruption, since it would be fixed by
+    // performing the deletion, and the user wants it gone...
+    let record_parent_id = record
+        .parent_id
+        .ok_or_else(|| Corruption::NonRootWithoutParent(guid.to_string()))?;
+    // must reorder existing children.
+    update_pos_for_deletion(db, record.position, record_parent_id)?;
+    // and delete - children are recursively deleted.
+    db.execute_cached(
+        "DELETE from moz_bookmarks WHERE id = :id",
+        &[(":id", &record.row_id)],
+    )?;
+    super::delete_pending_temp_tables(db)?;
+    Ok(true)
+}
+
+/// Support for modifying bookmarks, including changing the location in
+/// the tree.
+
+// Used to specify how the location of the item in the tree should be updated.
+#[derive(Debug, Default, Clone)]
+pub enum UpdateTreeLocation {
+    #[default]
+    None, // no change
+    Position {
+        pos: BookmarkPosition,
+    }, // new position in the same folder.
+    Parent {
+        guid: SyncGuid,
+        pos: BookmarkPosition,
+    }, // new parent
+}
+
+/// Structures which can be used to update a bookmark, folder or separator.
+/// Almost all fields are Option<>-like, with None meaning "do not change".
+/// Many fields which can't be changed by our public API are omitted (eg,
+/// guid, date_added, last_modified, etc)
+#[derive(Debug, Clone, Default)]
+pub struct UpdatableBookmark {
+    pub location: UpdateTreeLocation,
+    pub url: Option<Url>,
+    pub title: Option<String>,
+}
+
+impl From<UpdatableBookmark> for UpdatableItem {
+    fn from(b: UpdatableBookmark) -> Self {
+        UpdatableItem::Bookmark { b }
+    }
+}
+
+#[derive(Debug, Clone)]
+pub struct UpdatableSeparator {
+    pub location: UpdateTreeLocation,
+}
+
+impl From<UpdatableSeparator> for UpdatableItem {
+    fn from(s: UpdatableSeparator) -> Self {
+        UpdatableItem::Separator { s }
+    }
+}
+
+#[derive(Debug, Clone, Default)]
+pub struct UpdatableFolder {
+    pub location: UpdateTreeLocation,
+    pub title: Option<String>,
+}
+
+impl From<UpdatableFolder> for UpdatableItem {
+    fn from(f: UpdatableFolder) -> Self {
+        UpdatableItem::Folder { f }
+    }
+}
+
+// The type used to update the actual item.
+#[derive(Debug, Clone)]
+pub enum UpdatableItem {
+    Bookmark { b: UpdatableBookmark },
+    Separator { s: UpdatableSeparator },
+    Folder { f: UpdatableFolder },
+}
+
+impl UpdatableItem {
+    fn bookmark_type(&self) -> BookmarkType {
+        match self {
+            UpdatableItem::Bookmark { .. } => BookmarkType::Bookmark,
+            UpdatableItem::Separator { .. } => BookmarkType::Separator,
+            UpdatableItem::Folder { .. } => BookmarkType::Folder,
+        }
+    }
+
+    pub fn location(&self) -> &UpdateTreeLocation {
+        match self {
+            UpdatableItem::Bookmark { b } => &b.location,
+            UpdatableItem::Separator { s } => &s.location,
+            UpdatableItem::Folder { f } => &f.location,
+        }
+    }
+}
+
+/// We don't require bookmark type for updates on the other side of the FFI,
+/// since the type is immutable, and iOS wants to be able to move bookmarks by
+/// GUID. We also don't/can't enforce as much in the Kotlin/Swift type system
+/// as we can/do in Rust.
+///
+/// This is a type that represents the data we get from the FFI, which we then
+/// turn into a `UpdatableItem` that we can actually use (we do this by
+/// reading the type out of the DB, but we can do that transactionally, so it's
+/// not a problem).
+///
+/// It's basically an intermediate between the protobuf message format and
+/// `UpdatableItem`, used to avoid needing to pass in the `type` to update, and
+/// to give us a place to check things that we can't enforce in Swift/Kotlin's
+/// type system, but that we do in Rust's.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct BookmarkUpdateInfo {
+    pub guid: SyncGuid,
+    pub title: Option<String>,
+    pub url: Option<String>,
+    pub parent_guid: Option<SyncGuid>,
+    pub position: Option<u32>,
+}
+
+pub fn update_bookmark_from_info(db: &PlacesDb, info: BookmarkUpdateInfo) -> Result<()> {
+    let tx = db.begin_transaction()?;
+    let existing = get_raw_bookmark(db, &info.guid)?
+        .ok_or_else(|| InvalidPlaceInfo::NoSuchGuid(info.guid.to_string()))?;
+    let (guid, updatable) = info.into_updatable(existing.bookmark_type)?;
+
+    update_bookmark_in_tx(db, &guid, &updatable, existing)?;
+    tx.commit()?;
+    Ok(())
+}
+
+pub fn update_bookmark(db: &PlacesDb, guid: &SyncGuid, item: &UpdatableItem) -> Result<()> {
+    let tx = db.begin_transaction()?;
+    let existing = get_raw_bookmark(db, guid)?
+        .ok_or_else(|| InvalidPlaceInfo::NoSuchGuid(guid.to_string()))?;
+    let result = update_bookmark_in_tx(db, guid, item, existing);
+    super::delete_pending_temp_tables(db)?;
+    // Note: `tx` automatically rolls back on drop if we don't commit
+    tx.commit()?;
+    result
+}
+
+fn update_bookmark_in_tx(
+    db: &PlacesDb,
+    guid: &SyncGuid,
+    item: &UpdatableItem,
+    raw: RawBookmark,
+) -> Result<()> {
+    // if guid is root
+    if BookmarkRootGuid::well_known(guid.as_str()).is_some() {
+        return Err(InvalidPlaceInfo::CannotUpdateRoot(BookmarkRootGuid::Root).into());
+    }
+    let existing_parent_guid = raw
+        .parent_guid
+        .as_ref()
+        .ok_or_else(|| Corruption::NonRootWithoutParent(guid.to_string()))?;
+
+    let existing_parent_id = raw
+        .parent_id
+        .ok_or_else(|| Corruption::NoParent(guid.to_string(), existing_parent_guid.to_string()))?;
+
+    if raw.bookmark_type != item.bookmark_type() {
+        return Err(InvalidPlaceInfo::MismatchedBookmarkType(
+            raw.bookmark_type as u8,
+            item.bookmark_type() as u8,
+        )
+        .into());
+    }
+
+    let update_old_parent_status;
+    let update_new_parent_status;
+    // to make our life easier we update every field, using existing when
+    // no value is specified.
+    let parent_id;
+    let position;
+    match item.location() {
+        UpdateTreeLocation::None => {
+            parent_id = existing_parent_id;
+            position = raw.position;
+            update_old_parent_status = false;
+            update_new_parent_status = false;
+        }
+        UpdateTreeLocation::Position { pos } => {
+            parent_id = existing_parent_id;
+            update_old_parent_status = true;
+            update_new_parent_status = false;
+            let parent = get_raw_bookmark(db, existing_parent_guid)?.ok_or_else(|| {
+                Corruption::NoParent(guid.to_string(), existing_parent_guid.to_string())
+            })?;
+            position = update_pos_for_move(db, *pos, &raw, &parent)?;
+        }
+        UpdateTreeLocation::Parent {
+            guid: new_parent_guid,
+            pos,
+        } => {
+            if new_parent_guid == BookmarkRootGuid::Root {
+                return Err(InvalidPlaceInfo::CannotUpdateRoot(BookmarkRootGuid::Root).into());
+            }
+            let new_parent = get_raw_bookmark(db, new_parent_guid)?
+                .ok_or_else(|| InvalidPlaceInfo::NoSuchGuid(new_parent_guid.to_string()))?;
+            if new_parent.bookmark_type != BookmarkType::Folder {
+                return Err(InvalidPlaceInfo::InvalidParent(new_parent_guid.to_string()).into());
+            }
+            parent_id = new_parent.row_id;
+            update_old_parent_status = true;
+            update_new_parent_status = true;
+            let existing_parent = get_raw_bookmark(db, existing_parent_guid)?.ok_or_else(|| {
+                Corruption::NoParent(guid.to_string(), existing_parent_guid.to_string())
+            })?;
+            update_pos_for_deletion(db, raw.position, existing_parent.row_id)?;
+            position = resolve_pos_for_insert(db, *pos, &new_parent)?;
+        }
+    };
+    let place_id = match item {
+        UpdatableItem::Bookmark { b } => match &b.url {
+            None => raw.place_id,
+            Some(url) => {
+                let page_info = match fetch_page_info(db, url)? {
+                    Some(info) => info.page,
+                    None => new_page_info(db, url, None)?,
+                };
+                Some(page_info.row_id)
+            }
+        },
+        _ => {
+            // Updating a non-bookmark item, so the existing item must not
+            // have a place_id
+            assert_eq!(raw.place_id, None);
+            None
+        }
+    };
+    // While we could let the SQL take care of being clever about the update
+    // via, say `title = NULLIF(IFNULL(:title, title), '')`, this code needs
+    // to know if it changed so the sync counter can be managed correctly.
+    let update_title = match item {
+        UpdatableItem::Bookmark { b } => &b.title,
+        UpdatableItem::Folder { f } => &f.title,
+        UpdatableItem::Separator { .. } => &None,
+    };
+
+    let title: Option<String> = match update_title {
+        None => raw.title.clone(),
+        // We don't differentiate between null and the empty string for title,
+        // just like desktop doesn't post bug 1360872, hence an empty string
+        // means "set to null".
+        Some(val) => {
+            if val.is_empty() {
+                None
+            } else {
+                Some(val.clone())
+            }
+        }
+    };
+
+    let change_incr = title != raw.title || place_id != raw.place_id;
+
+    let now = Timestamp::now();
+
+    let sql = "
+        UPDATE moz_bookmarks SET
+            fk = :fk,
+            parent = :parent,
+            position = :position,
+            title = :title,
+            lastModified = :now,
+            syncChangeCounter = syncChangeCounter + :change_incr
+        WHERE id = :id";
+
+    db.execute_cached(
+        sql,
+        &[
+            (":fk", &place_id as &dyn rusqlite::ToSql),
+            (":parent", &parent_id),
+            (":position", &position),
+            (":title", &maybe_truncate_title(&title.as_deref())),
+            (":now", &now),
+            (":change_incr", &(change_incr as u32)),
+            (":id", &raw.row_id),
+        ],
+    )?;
+
+    let sql_counter = "
+        UPDATE moz_bookmarks SET syncChangeCounter = syncChangeCounter + 1
+        WHERE id = :parent_id";
+
+    // The lastModified of the existing parent ancestors (which may still be
+    // the current parent) is always updated, even if the change counter for it
+    // isn't.
+    set_ancestors_last_modified(db, existing_parent_id, now)?;
+    if update_old_parent_status {
+        db.execute_cached(sql_counter, &[(":parent_id", &existing_parent_id)])?;
+    }
+    if update_new_parent_status {
+        set_ancestors_last_modified(db, parent_id, now)?;
+        db.execute_cached(sql_counter, &[(":parent_id", &parent_id)])?;
+    }
+    Ok(())
+}
+
+fn set_ancestors_last_modified(db: &PlacesDb, parent_id: RowId, time: Timestamp) -> Result<()> {
+    let sql = "
+        WITH RECURSIVE
+        ancestors(aid) AS (
+            SELECT :parent_id
+            UNION ALL
+            SELECT parent FROM moz_bookmarks
+            JOIN ancestors ON id = aid
+            WHERE type = :type
+        )
+        UPDATE moz_bookmarks SET lastModified = :time
+        WHERE id IN ancestors
+    ";
+    db.execute_cached(
+        sql,
+        &[
+            (":parent_id", &parent_id as &dyn rusqlite::ToSql),
+            (":type", &(BookmarkType::Folder as u8)),
+            (":time", &time),
+        ],
+    )?;
+    Ok(())
+}
+
+/// Get the URL of the bookmark matching a keyword
+pub fn bookmarks_get_url_for_keyword(db: &PlacesDb, keyword: &str) -> Result<Option<Url>> {
+    let bookmark_url = db.try_query_row(
+        "SELECT h.url FROM moz_keywords k
+         JOIN moz_places h ON h.id = k.place_id
+         WHERE k.keyword = :keyword",
+        &[(":keyword", &keyword)],
+        |row| row.get::<_, String>("url"),
+        true,
+    )?;
+
+    match bookmark_url {
+        Some(b) => match Url::parse(&b) {
+            Ok(u) => Ok(Some(u)),
+            Err(e) => {
+                // We don't have the guid to log and the keyword is PII...
+                log::warn!("ignoring invalid url: {:?}", e);
+                Ok(None)
+            }
+        },
+        None => Ok(None),
+    }
+}
+
+// Counts the number of bookmark items in the bookmark trees under the specified GUIDs.
+// Does not count folder items, separators. A set of empty folders will return zero, as will
+// a set of non-existing GUIDs or guids of a non-folder item.
+// The result is implementation dependant if the trees overlap.
+pub fn count_bookmarks_in_trees(db: &PlacesDb, item_guids: &[SyncGuid]) -> Result<u32> {
+    if item_guids.is_empty() {
+        return Ok(0);
+    }
+    let vars = repeat_sql_vars(item_guids.len());
+    let sql = format!(
+        r#"
+        WITH RECURSIVE bookmark_tree(id, parent, type)
+        AS (
+            SELECT id, parent, type FROM moz_bookmarks
+            WHERE parent IN (SELECT id from moz_bookmarks WHERE guid IN ({vars}))
+            UNION ALL
+            SELECT bm.id, bm.parent, bm.type FROM moz_bookmarks bm, bookmark_tree bt WHERE bm.parent = bt.id
+        )
+    SELECT COUNT(*) from bookmark_tree WHERE type = {};
+    "#,
+        BookmarkType::Bookmark as u8
+    );
+    let params = rusqlite::params_from_iter(item_guids);
+    Ok(db.try_query_one(&sql, params, true)?.unwrap_or_default())
+}
+
+/// Erases all bookmarks and resets all Sync metadata.
+pub fn delete_everything(db: &PlacesDb) -> Result<()> {
+    let tx = db.begin_transaction()?;
+    db.execute_batch(&format!(
+        "DELETE FROM moz_bookmarks
+         WHERE guid NOT IN ('{}', '{}', '{}', '{}', '{}');",
+        BookmarkRootGuid::Root.as_str(),
+        BookmarkRootGuid::Menu.as_str(),
+        BookmarkRootGuid::Mobile.as_str(),
+        BookmarkRootGuid::Toolbar.as_str(),
+        BookmarkRootGuid::Unfiled.as_str(),
+    ))?;
+    reset_in_tx(db, &EngineSyncAssociation::Disconnected)?;
+    tx.commit()?;
+    Ok(())
+}
+
+/// A "raw" bookmark - a representation of the row and some summary fields.
+#[derive(Debug)]
+pub(crate) struct RawBookmark {
+    pub place_id: Option<RowId>,
+    pub row_id: RowId,
+    pub bookmark_type: BookmarkType,
+    pub parent_id: Option<RowId>,
+    pub parent_guid: Option<SyncGuid>,
+    pub position: u32,
+    pub title: Option<String>,
+    pub url: Option<Url>,
+    pub date_added: Timestamp,
+    pub date_modified: Timestamp,
+    pub guid: SyncGuid,
+    pub _sync_status: SyncStatus,
+    pub _sync_change_counter: u32,
+    pub child_count: u32,
+    pub _grandparent_id: Option<RowId>,
+}
+
+impl RawBookmark {
+    pub fn from_row(row: &Row<'_>) -> Result<Self> {
+        let place_id = row.get::<_, Option<RowId>>("fk")?;
+        Ok(Self {
+            row_id: row.get("_id")?,
+            place_id,
+            bookmark_type: BookmarkType::from_u8_with_valid_url(row.get::<_, u8>("type")?, || {
+                place_id.is_some()
+            }),
+            parent_id: row.get("_parentId")?,
+            parent_guid: row.get("parentGuid")?,
+            position: row.get("position")?,
+            title: row.get::<_, Option<String>>("title")?,
+            url: match row.get::<_, Option<String>>("url")? {
+                Some(s) => Some(Url::parse(&s)?),
+                None => None,
+            },
+            date_added: row.get("dateAdded")?,
+            date_modified: row.get("lastModified")?,
+            guid: row.get::<_, String>("guid")?.into(),
+            _sync_status: SyncStatus::from_u8(row.get::<_, u8>("_syncStatus")?),
+            _sync_change_counter: row
+                .get::<_, Option<u32>>("syncChangeCounter")?
+                .unwrap_or_default(),
+            child_count: row.get("_childCount")?,
+            _grandparent_id: row.get("_grandparentId")?,
+        })
+    }
+}
+
+/// sql is based on fetchBookmark() in Desktop's Bookmarks.jsm, with 'fk' added
+/// and title's NULLIF handling.
+const RAW_BOOKMARK_SQL: &str = "
+    SELECT
+        b.guid,
+        p.guid AS parentGuid,
+        b.position,
+        b.dateAdded,
+        b.lastModified,
+        b.type,
+        -- Note we return null for titles with an empty string.
+        NULLIF(b.title, '') AS title,
+        h.url AS url,
+        b.id AS _id,
+        b.parent AS _parentId,
+        (SELECT count(*) FROM moz_bookmarks WHERE parent = b.id) AS _childCount,
+        p.parent AS _grandParentId,
+        b.syncStatus AS _syncStatus,
+        -- the columns below don't appear in the desktop query
+        b.fk,
+        b.syncChangeCounter
+    FROM moz_bookmarks b
+    LEFT JOIN moz_bookmarks p ON p.id = b.parent
+    LEFT JOIN moz_places h ON h.id = b.fk
+";
+
+pub(crate) fn get_raw_bookmark(db: &PlacesDb, guid: &SyncGuid) -> Result<Option<RawBookmark>> {
+    // sql is based on fetchBookmark() in Desktop's Bookmarks.jsm, with 'fk' added
+    // and title's NULLIF handling.
+    db.try_query_row(
+        &format!("{} WHERE b.guid = :guid", RAW_BOOKMARK_SQL),
+        &[(":guid", guid)],
+        RawBookmark::from_row,
+        true,
+    )
+}
+
+fn get_raw_bookmarks_for_url(db: &PlacesDb, url: &Url) -> Result<Vec<RawBookmark>> {
+    db.query_rows_into_cached(
+        &format!(
+            "{} WHERE h.url_hash = hash(:url) AND h.url = :url",
+            RAW_BOOKMARK_SQL
+        ),
+        &[(":url", &url.as_str())],
+        RawBookmark::from_row,
+    )
+}
+
+fn reset_in_tx(db: &PlacesDb, assoc: &EngineSyncAssociation) -> Result<()> {
+    // Remove all synced bookmarks and pending tombstones, and mark all
+    // local bookmarks as new.
+    db.execute_batch(&format!(
+        "DELETE FROM moz_bookmarks_synced;
+
+        DELETE FROM moz_bookmarks_deleted;
+
+        UPDATE moz_bookmarks
+        SET syncChangeCounter = 1,
+            syncStatus = {}",
+        (SyncStatus::New as u8)
+    ))?;
+
+    // Recreate the set of synced roots, since we just removed all synced
+    // bookmarks.
+    bookmark_sync::create_synced_bookmark_roots(db)?;
+
+    // Reset the last sync time, so that the next sync fetches fresh records
+    // from the server.
+    put_meta(db, LAST_SYNC_META_KEY, &0)?;
+
+    // Clear the sync ID if we're signing out, or set it to whatever the
+    // server gave us if we're signing in.
+    match assoc {
+        EngineSyncAssociation::Disconnected => {
+            delete_meta(db, GLOBAL_SYNCID_META_KEY)?;
+            delete_meta(db, COLLECTION_SYNCID_META_KEY)?;
+        }
+        EngineSyncAssociation::Connected(ids) => {
+            put_meta(db, GLOBAL_SYNCID_META_KEY, &ids.global)?;
+            put_meta(db, COLLECTION_SYNCID_META_KEY, &ids.coll)?;
+        }
+    }
+
+    Ok(())
+}
+
+pub mod bookmark_sync {
+    use super::*;
+    use crate::bookmark_sync::SyncedBookmarkKind;
+
+    /// Removes all sync metadata, including synced bookmarks, pending tombstones,
+    /// change counters, sync statuses, the last sync time, and sync ID. This
+    /// should be called when the user signs out of Sync.
+    pub(crate) fn reset(db: &PlacesDb, assoc: &EngineSyncAssociation) -> Result<()> {
+        let tx = db.begin_transaction()?;
+        reset_in_tx(db, assoc)?;
+        tx.commit()?;
+        Ok(())
+    }
+
+    /// Sets up the syncable roots. All items in `moz_bookmarks_synced` descend
+    /// from these roots.
+    pub fn create_synced_bookmark_roots(db: &Connection) -> rusqlite::Result<()> {
+        // NOTE: This is called in a transaction.
+        fn maybe_insert(
+            db: &Connection,
+            guid: &SyncGuid,
+            parent_guid: &SyncGuid,
+            pos: u32,
+        ) -> rusqlite::Result<()> {
+            db.execute_batch(&format!(
+                "INSERT OR IGNORE INTO moz_bookmarks_synced(guid, parentGuid, kind)
+                VALUES('{guid}', '{parent_guid}', {kind});
+
+                INSERT OR IGNORE INTO moz_bookmarks_synced_structure(
+                    guid, parentGuid, position)
+                VALUES('{guid}', '{parent_guid}', {pos});",
+                guid = guid.as_str(),
+                parent_guid = parent_guid.as_str(),
+                kind = SyncedBookmarkKind::Folder as u8,
+                pos = pos
+            ))?;
+            Ok(())
+        }
+
+        // The Places root is its own parent, to ensure it's always in
+        // `moz_bookmarks_synced_structure`.
+        maybe_insert(
+            db,
+            &BookmarkRootGuid::Root.as_guid(),
+            &BookmarkRootGuid::Root.as_guid(),
+            0,
+        )?;
+        for (pos, user_root) in USER_CONTENT_ROOTS.iter().enumerate() {
+            maybe_insert(
+                db,
+                &user_root.as_guid(),
+                &BookmarkRootGuid::Root.as_guid(),
+                pos as u32,
+            )?;
+        }
+        Ok(())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::api::places_api::test::new_mem_connection;
+    use crate::db::PlacesDb;
+    use crate::storage::get_meta;
+    use crate::tests::{append_invalid_bookmark, assert_json_tree, insert_json_tree};
+    use json_tree::*;
+    use pretty_assertions::assert_eq;
+    use serde_json::Value;
+    use std::collections::HashSet;
+
+    fn get_pos(conn: &PlacesDb, guid: &SyncGuid) -> u32 {
+        get_raw_bookmark(conn, guid)
+            .expect("should work")
+            .unwrap()
+            .position
+    }
+
+    #[test]
+    fn test_bookmark_url_for_keyword() -> Result<()> {
+        let conn = new_mem_connection();
+
+        let url = Url::parse("http://example.com").expect("valid url");
+
+        conn.execute_cached(
+            "INSERT INTO moz_places (guid, url, url_hash) VALUES ('fake_guid___', :url, hash(:url))",
+            &[(":url", &String::from(url))],
+        )
+        .expect("should work");
+        let place_id = conn.last_insert_rowid();
+
+        // create a bookmark with keyword 'donut' pointing at it.
+        conn.execute_cached(
+            "INSERT INTO moz_keywords
+                (keyword, place_id)
+            VALUES
+                ('donut', :place_id)",
+            &[(":place_id", &place_id)],
+        )
+        .expect("should work");
+
+        assert_eq!(
+            bookmarks_get_url_for_keyword(&conn, "donut")?,
+            Some(Url::parse("http://example.com")?)
+        );
+        assert_eq!(bookmarks_get_url_for_keyword(&conn, "juice")?, None);
+
+        // now change the keyword to 'ice cream'
+        conn.execute_cached(
+            "REPLACE INTO moz_keywords
+                (keyword, place_id)
+            VALUES
+                ('ice cream', :place_id)",
+            &[(":place_id", &place_id)],
+        )
+        .expect("should work");
+
+        assert_eq!(
+            bookmarks_get_url_for_keyword(&conn, "ice cream")?,
+            Some(Url::parse("http://example.com")?)
+        );
+        assert_eq!(bookmarks_get_url_for_keyword(&conn, "donut")?, None);
+        assert_eq!(bookmarks_get_url_for_keyword(&conn, "ice")?, None);
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_bookmark_invalid_url_for_keyword() -> Result<()> {
+        let conn = new_mem_connection();
+
+        let place_id =
+            append_invalid_bookmark(&conn, BookmarkRootGuid::Unfiled.guid(), "invalid", "badurl")
+                .place_id;
+
+        // create a bookmark with keyword 'donut' pointing at it.
+        conn.execute_cached(
+            "INSERT INTO moz_keywords
+                (keyword, place_id)
+            VALUES
+                ('donut', :place_id)",
+            &[(":place_id", &place_id)],
+        )
+        .expect("should work");
+
+        assert_eq!(bookmarks_get_url_for_keyword(&conn, "donut")?, None);
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_insert() -> Result<()> {
+        let conn = new_mem_connection();
+        let url = Url::parse("https://www.example.com")?;
+
+        conn.execute("UPDATE moz_bookmarks SET syncChangeCounter = 0", [])
+            .expect("should work");
+
+        let global_change_tracker = conn.global_bookmark_change_tracker();
+        assert!(!global_change_tracker.changed(), "can't start as changed!");
+        let bm = InsertableItem::Bookmark {
+            b: InsertableBookmark {
+                parent_guid: BookmarkRootGuid::Unfiled.into(),
+                position: BookmarkPosition::Append,
+                date_added: None,
+                last_modified: None,
+                guid: None,
+                url: url.clone(),
+                title: Some("the title".into()),
+            },
+        };
+        let guid = insert_bookmark(&conn, bm)?;
+
+        // re-fetch it.
+        let rb = get_raw_bookmark(&conn, &guid)?.expect("should get the bookmark");
+
+        assert!(rb.place_id.is_some());
+        assert_eq!(rb.bookmark_type, BookmarkType::Bookmark);
+        assert_eq!(rb.parent_guid.unwrap(), BookmarkRootGuid::Unfiled);
+        assert_eq!(rb.position, 0);
+        assert_eq!(rb.title, Some("the title".into()));
+        assert_eq!(rb.url, Some(url));
+        assert_eq!(rb._sync_status, SyncStatus::New);
+        assert_eq!(rb._sync_change_counter, 1);
+        assert!(global_change_tracker.changed());
+        assert_eq!(rb.child_count, 0);
+
+        let unfiled = get_raw_bookmark(&conn, &BookmarkRootGuid::Unfiled.as_guid())?
+            .expect("should get unfiled");
+        assert_eq!(unfiled._sync_change_counter, 1);
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_insert_titles() -> Result<()> {
+        let conn = new_mem_connection();
+        let url = Url::parse("https://www.example.com")?;
+
+        let bm = InsertableItem::Bookmark {
+            b: InsertableBookmark {
+                parent_guid: BookmarkRootGuid::Unfiled.into(),
+                position: BookmarkPosition::Append,
+                date_added: None,
+                last_modified: None,
+                guid: None,
+                url: url.clone(),
+                title: Some("".into()),
+            },
+        };
+        let guid = insert_bookmark(&conn, bm)?;
+        let rb = get_raw_bookmark(&conn, &guid)?.expect("should get the bookmark");
+        assert_eq!(rb.title, None);
+
+        let bm2 = InsertableItem::Bookmark {
+            b: InsertableBookmark {
+                parent_guid: BookmarkRootGuid::Unfiled.into(),
+                position: BookmarkPosition::Append,
+                date_added: None,
+                last_modified: None,
+                guid: None,
+                url,
+                title: None,
+            },
+        };
+        let guid2 = insert_bookmark(&conn, bm2)?;
+        let rb2 = get_raw_bookmark(&conn, &guid2)?.expect("should get the bookmark");
+        assert_eq!(rb2.title, None);
+        Ok(())
+    }
+
+    #[test]
+    fn test_delete() -> Result<()> {
+        let conn = new_mem_connection();
+
+        let guid1 = SyncGuid::random();
+        let guid2 = SyncGuid::random();
+        let guid2_1 = SyncGuid::random();
+        let guid3 = SyncGuid::random();
+
+        let jtree = json!({
+            "guid": &BookmarkRootGuid::Unfiled.as_guid(),
+            "children": [
+                {
+                    "guid": &guid1,
+                    "title": "the bookmark",
+                    "url": "https://www.example.com/"
+                },
+                {
+                    "guid": &guid2,
+                    "title": "A folder",
+                    "children": [
+                        {
+                            "guid": &guid2_1,
+                            "title": "bookmark in A folder",
+                            "url": "https://www.example2.com/"
+                        }
+                    ]
+                },
+                {
+                    "guid": &guid3,
+                    "title": "the last bookmark",
+                    "url": "https://www.example3.com/"
+                },
+            ]
+        });
+
+        insert_json_tree(&conn, jtree);
+
+        conn.execute(
+            &format!(
+                "UPDATE moz_bookmarks SET syncChangeCounter = 1, syncStatus = {}",
+                SyncStatus::Normal as u8
+            ),
+            [],
+        )
+        .expect("should work");
+
+        // Make sure the positions are correct now.
+        assert_eq!(get_pos(&conn, &guid1), 0);
+        assert_eq!(get_pos(&conn, &guid2), 1);
+        assert_eq!(get_pos(&conn, &guid3), 2);
+
+        let global_change_tracker = conn.global_bookmark_change_tracker();
+
+        // Delete the middle folder.
+        delete_bookmark(&conn, &guid2)?;
+        // Should no longer exist.
+        assert!(get_raw_bookmark(&conn, &guid2)?.is_none());
+        // Neither should the child.
+        assert!(get_raw_bookmark(&conn, &guid2_1)?.is_none());
+        // Positions of the remaining should be correct.
+        assert_eq!(get_pos(&conn, &guid1), 0);
+        assert_eq!(get_pos(&conn, &guid3), 1);
+        assert!(global_change_tracker.changed());
+
+        assert_eq!(
+            conn.query_one::<i64>(
+                "SELECT COUNT(*) FROM moz_origins WHERE host='www.example2.com';"
+            )?,
+            0
+        );
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_delete_roots() {
+        let conn = new_mem_connection();
+
+        delete_bookmark(&conn, &BookmarkRootGuid::Root.into()).expect_err("can't delete root");
+        delete_bookmark(&conn, &BookmarkRootGuid::Unfiled.into())
+            .expect_err("can't delete any root");
+    }
+
+    #[test]
+    fn test_insert_pos_too_large() -> Result<()> {
+        let conn = new_mem_connection();
+        let url = Url::parse("https://www.example.com")?;
+
+        let bm = InsertableItem::Bookmark {
+            b: InsertableBookmark {
+                parent_guid: BookmarkRootGuid::Unfiled.into(),
+                position: BookmarkPosition::Specific { pos: 100 },
+                date_added: None,
+                last_modified: None,
+                guid: None,
+                url,
+                title: Some("the title".into()),
+            },
+        };
+        let guid = insert_bookmark(&conn, bm)?;
+
+        // re-fetch it.
+        let rb = get_raw_bookmark(&conn, &guid)?.expect("should get the bookmark");
+
+        assert_eq!(rb.position, 0, "large value should have been ignored");
+        Ok(())
+    }
+
+    #[test]
+    fn test_update_move_same_parent() {
+        let conn = new_mem_connection();
+        let unfiled = &BookmarkRootGuid::Unfiled.as_guid();
+
+        // A helper to make the moves below more concise.
+        let do_move = |guid: &str, pos: BookmarkPosition| {
+            let global_change_tracker = conn.global_bookmark_change_tracker();
+            update_bookmark(
+                &conn,
+                &guid.into(),
+                &UpdatableBookmark {
+                    location: UpdateTreeLocation::Position { pos },
+                    ..Default::default()
+                }
+                .into(),
+            )
+            .expect("update should work");
+            assert!(global_change_tracker.changed(), "should be tracked");
+        };
+
+        // A helper to make the checks below more concise.
+        let check_tree = |children: Value| {
+            assert_json_tree(
+                &conn,
+                unfiled,
+                json!({
+                    "guid": unfiled,
+                    "children": children
+                }),
+            );
+        };
+
+        insert_json_tree(
+            &conn,
+            json!({
+                "guid": unfiled,
+                "children": [
+                    {
+                        "guid": "bookmark1___",
+                        "url": "https://www.example1.com/"
+                    },
+                    {
+                        "guid": "bookmark2___",
+                        "url": "https://www.example2.com/"
+                    },
+                    {
+                        "guid": "bookmark3___",
+                        "url": "https://www.example3.com/"
+                    },
+
+                ]
+            }),
+        );
+
+        // Move a bookmark to the end.
+        do_move("bookmark2___", BookmarkPosition::Append);
+        check_tree(json!([
+            {"url": "https://www.example1.com/"},
+            {"url": "https://www.example3.com/"},
+            {"url": "https://www.example2.com/"},
+        ]));
+
+        // Move a bookmark to its existing position
+        do_move("bookmark3___", BookmarkPosition::Specific { pos: 1 });
+        check_tree(json!([
+            {"url": "https://www.example1.com/"},
+            {"url": "https://www.example3.com/"},
+            {"url": "https://www.example2.com/"},
+        ]));
+
+        // Move a bookmark back 1 position.
+        do_move("bookmark2___", BookmarkPosition::Specific { pos: 1 });
+        check_tree(json!([
+            {"url": "https://www.example1.com/"},
+            {"url": "https://www.example2.com/"},
+            {"url": "https://www.example3.com/"},
+        ]));
+
+        // Move a bookmark forward 1 position.
+        do_move("bookmark2___", BookmarkPosition::Specific { pos: 2 });
+        check_tree(json!([
+            {"url": "https://www.example1.com/"},
+            {"url": "https://www.example3.com/"},
+            {"url": "https://www.example2.com/"},
+        ]));
+
+        // Move a bookmark beyond the end.
+        do_move("bookmark1___", BookmarkPosition::Specific { pos: 10 });
+        check_tree(json!([
+            {"url": "https://www.example3.com/"},
+            {"url": "https://www.example2.com/"},
+            {"url": "https://www.example1.com/"},
+        ]));
+    }
+
+    #[test]
+    fn test_update() -> Result<()> {
+        let conn = new_mem_connection();
+        let unfiled = &BookmarkRootGuid::Unfiled.as_guid();
+
+        insert_json_tree(
+            &conn,
+            json!({
+                "guid": unfiled,
+                "children": [
+                    {
+                        "guid": "bookmark1___",
+                        "title": "the bookmark",
+                        "url": "https://www.example.com/"
+                    },
+                    {
+                        "guid": "bookmark2___",
+                        "title": "another bookmark",
+                        "url": "https://www.example2.com/"
+                    },
+                    {
+                        "guid": "folder1_____",
+                        "title": "A folder",
+                        "children": [
+                            {
+                                "guid": "bookmark3___",
+                                "title": "bookmark in A folder",
+                                "url": "https://www.example3.com/"
+                            },
+                            {
+                                "guid": "bookmark4___",
+                                "title": "next bookmark in A folder",
+                                "url": "https://www.example4.com/"
+                            },
+                            {
+                                "guid": "bookmark5___",
+                                "title": "next next bookmark in A folder",
+                                "url": "https://www.example5.com/"
+                            }
+                        ]
+                    },
+                    {
+                        "guid": "bookmark6___",
+                        "title": "yet another bookmark",
+                        "url": "https://www.example6.com/"
+                    },
+
+                ]
+            }),
+        );
+
+        update_bookmark(
+            &conn,
+            &"folder1_____".into(),
+            &UpdatableFolder {
+                title: Some("new name".to_string()),
+                ..Default::default()
+            }
+            .into(),
+        )?;
+        update_bookmark(
+            &conn,
+            &"bookmark1___".into(),
+            &UpdatableBookmark {
+                url: Some(Url::parse("https://www.example3.com/")?),
+                title: None,
+                ..Default::default()
+            }
+            .into(),
+        )?;
+
+        // A move in the same folder.
+        update_bookmark(
+            &conn,
+            &"bookmark6___".into(),
+            &UpdatableBookmark {
+                location: UpdateTreeLocation::Position {
+                    pos: BookmarkPosition::Specific { pos: 2 },
+                },
+                ..Default::default()
+            }
+            .into(),
+        )?;
+
+        // A move across folders.
+        update_bookmark(
+            &conn,
+            &"bookmark2___".into(),
+            &UpdatableBookmark {
+                location: UpdateTreeLocation::Parent {
+                    guid: "folder1_____".into(),
+                    pos: BookmarkPosition::Specific { pos: 1 },
+                },
+                ..Default::default()
+            }
+            .into(),
+        )?;
+
+        assert_json_tree(
+            &conn,
+            unfiled,
+            json!({
+                "guid": unfiled,
+                "children": [
+                    {
+                        // We updated the url and title of this.
+                        "guid": "bookmark1___",
+                        "title": null,
+                        "url": "https://www.example3.com/"
+                    },
+                        // We moved bookmark6 to position=2 (ie, 3rd) of the same
+                        // parent, but then moved the existing 2nd item to the
+                        // folder, so this ends up second.
+                    {
+                        "guid": "bookmark6___",
+                        "url": "https://www.example6.com/"
+                    },
+                    {
+                        // We changed the name of the folder.
+                        "guid": "folder1_____",
+                        "title": "new name",
+                        "children": [
+                            {
+                                "guid": "bookmark3___",
+                                "url": "https://www.example3.com/"
+                            },
+                            {
+                                // This was moved from the parent to position 1
+                                "guid": "bookmark2___",
+                                "url": "https://www.example2.com/"
+                            },
+                            {
+                                "guid": "bookmark4___",
+                                "url": "https://www.example4.com/"
+                            },
+                            {
+                                "guid": "bookmark5___",
+                                "url": "https://www.example5.com/"
+                            }
+                        ]
+                    },
+
+                ]
+            }),
+        );
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_update_titles() -> Result<()> {
+        let conn = new_mem_connection();
+        let guid: SyncGuid = "bookmark1___".into();
+
+        insert_json_tree(
+            &conn,
+            json!({
+                "guid": &BookmarkRootGuid::Unfiled.as_guid(),
+                "children": [
+                    {
+                        "guid": "bookmark1___",
+                        "title": "the bookmark",
+                        "url": "https://www.example.com/"
+                    },
+                ],
+            }),
+        );
+
+        conn.execute("UPDATE moz_bookmarks SET syncChangeCounter = 0", [])
+            .expect("should work");
+
+        // Update of None means no change.
+        update_bookmark(
+            &conn,
+            &guid,
+            &UpdatableBookmark {
+                title: None,
+                ..Default::default()
+            }
+            .into(),
+        )?;
+        let bm = get_raw_bookmark(&conn, &guid)?.expect("should exist");
+        assert_eq!(bm.title, Some("the bookmark".to_string()));
+        assert_eq!(bm._sync_change_counter, 0);
+
+        // Update to the same value is still not a change.
+        update_bookmark(
+            &conn,
+            &guid,
+            &UpdatableBookmark {
+                title: Some("the bookmark".to_string()),
+                ..Default::default()
+            }
+            .into(),
+        )?;
+        let bm = get_raw_bookmark(&conn, &guid)?.expect("should exist");
+        assert_eq!(bm.title, Some("the bookmark".to_string()));
+        assert_eq!(bm._sync_change_counter, 0);
+
+        // Update to an empty string sets it to null
+        update_bookmark(
+            &conn,
+            &guid,
+            &UpdatableBookmark {
+                title: Some("".to_string()),
+                ..Default::default()
+            }
+            .into(),
+        )?;
+        let bm = get_raw_bookmark(&conn, &guid)?.expect("should exist");
+        assert_eq!(bm.title, None);
+        assert_eq!(bm._sync_change_counter, 1);
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_update_statuses() -> Result<()> {
+        let conn = new_mem_connection();
+        let unfiled = &BookmarkRootGuid::Unfiled.as_guid();
+
+        let check_change_counters = |guids: Vec<&str>| {
+            let sql = "SELECT guid FROM moz_bookmarks WHERE syncChangeCounter != 0";
+            let mut stmt = conn.prepare(sql).expect("sql is ok");
+            let got_guids: HashSet<String> = stmt
+                .query_and_then([], |row| -> rusqlite::Result<_> { row.get::<_, String>(0) })
+                .expect("should work")
+                .map(std::result::Result::unwrap)
+                .collect();
+
+            assert_eq!(
+                got_guids,
+                guids.into_iter().map(ToString::to_string).collect()
+            );
+            // reset them all back
+            conn.execute("UPDATE moz_bookmarks SET syncChangeCounter = 0", [])
+                .expect("should work");
+        };
+
+        let check_last_modified = |guids: Vec<&str>| {
+            let sql = "SELECT guid FROM moz_bookmarks
+                       WHERE lastModified >= 1000 AND guid != 'root________'";
+
+            let mut stmt = conn.prepare(sql).expect("sql is ok");
+            let got_guids: HashSet<String> = stmt
+                .query_and_then([], |row| -> rusqlite::Result<_> { row.get::<_, String>(0) })
+                .expect("should work")
+                .map(std::result::Result::unwrap)
+                .collect();
+
+            assert_eq!(
+                got_guids,
+                guids.into_iter().map(ToString::to_string).collect()
+            );
+            // reset them all back
+            conn.execute("UPDATE moz_bookmarks SET lastModified = 123", [])
+                .expect("should work");
+        };
+
+        insert_json_tree(
+            &conn,
+            json!({
+                "guid": unfiled,
+                "children": [
+                    {
+                        "guid": "folder1_____",
+                        "title": "A folder",
+                        "children": [
+                            {
+                                "guid": "bookmark1___",
+                                "title": "bookmark in A folder",
+                                "url": "https://www.example2.com/"
+                            },
+                            {
+                                "guid": "bookmark2___",
+                                "title": "next bookmark in A folder",
+                                "url": "https://www.example3.com/"
+                            },
+                        ]
+                    },
+                    {
+                        "guid": "folder2_____",
+                        "title": "folder 2",
+                    },
+                ]
+            }),
+        );
+
+        // reset all statuses and timestamps.
+        conn.execute(
+            "UPDATE moz_bookmarks SET syncChangeCounter = 0, lastModified = 123",
+            [],
+        )?;
+
+        // update a title - should get a change counter.
+        update_bookmark(
+            &conn,
+            &"bookmark1___".into(),
+            &UpdatableBookmark {
+                title: Some("new name".to_string()),
+                ..Default::default()
+            }
+            .into(),
+        )?;
+        check_change_counters(vec!["bookmark1___"]);
+        // last modified should be all the way up the tree.
+        check_last_modified(vec!["unfiled_____", "folder1_____", "bookmark1___"]);
+
+        // update the position in the same folder.
+        update_bookmark(
+            &conn,
+            &"bookmark1___".into(),
+            &UpdatableBookmark {
+                location: UpdateTreeLocation::Position {
+                    pos: BookmarkPosition::Append,
+                },
+                ..Default::default()
+            }
+            .into(),
+        )?;
+        // parent should be the only thing with a change counter.
+        check_change_counters(vec!["folder1_____"]);
+        // last modified should be all the way up the tree.
+        check_last_modified(vec!["unfiled_____", "folder1_____", "bookmark1___"]);
+
+        // update the position to a different folder.
+        update_bookmark(
+            &conn,
+            &"bookmark1___".into(),
+            &UpdatableBookmark {
+                location: UpdateTreeLocation::Parent {
+                    guid: "folder2_____".into(),
+                    pos: BookmarkPosition::Append,
+                },
+                ..Default::default()
+            }
+            .into(),
+        )?;
+        // Both parents should have a change counter.
+        check_change_counters(vec!["folder1_____", "folder2_____"]);
+        // last modified should be all the way up the tree and include both parents.
+        check_last_modified(vec![
+            "unfiled_____",
+            "folder1_____",
+            "folder2_____",
+            "bookmark1___",
+        ]);
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_update_errors() {
+        let conn = new_mem_connection();
+
+        insert_json_tree(
+            &conn,
+            json!({
+                "guid": &BookmarkRootGuid::Unfiled.as_guid(),
+                "children": [
+                    {
+                        "guid": "bookmark1___",
+                        "title": "the bookmark",
+                        "url": "https://www.example.com/"
+                    },
+                    {
+                        "guid": "folder1_____",
+                        "title": "A folder",
+                        "children": [
+                            {
+                                "guid": "bookmark2___",
+                                "title": "bookmark in A folder",
+                                "url": "https://www.example2.com/"
+                            },
+                        ]
+                    },
+                ]
+            }),
+        );
+        // Update an item that doesn't exist.
+        update_bookmark(
+            &conn,
+            &"bookmark9___".into(),
+            &UpdatableBookmark {
+                ..Default::default()
+            }
+            .into(),
+        )
+        .expect_err("should fail to update an item that doesn't exist");
+
+        // A move across to a non-folder
+        update_bookmark(
+            &conn,
+            &"bookmark1___".into(),
+            &UpdatableBookmark {
+                location: UpdateTreeLocation::Parent {
+                    guid: "bookmark2___".into(),
+                    pos: BookmarkPosition::Specific { pos: 1 },
+                },
+                ..Default::default()
+            }
+            .into(),
+        )
+        .expect_err("can't move to a bookmark");
+
+        // A move to the root
+        update_bookmark(
+            &conn,
+            &"bookmark1___".into(),
+            &UpdatableBookmark {
+                location: UpdateTreeLocation::Parent {
+                    guid: BookmarkRootGuid::Root.as_guid(),
+                    pos: BookmarkPosition::Specific { pos: 1 },
+                },
+                ..Default::default()
+            }
+            .into(),
+        )
+        .expect_err("can't move to the root");
+    }
+
+    #[test]
+    fn test_delete_everything() -> Result<()> {
+        let conn = new_mem_connection();
+
+        insert_bookmark(
+            &conn,
+            InsertableFolder {
+                parent_guid: BookmarkRootGuid::Unfiled.into(),
+                position: BookmarkPosition::Append,
+                date_added: None,
+                last_modified: None,
+                guid: Some("folderAAAAAA".into()),
+                title: Some("A".into()),
+                children: vec![],
+            }
+            .into(),
+        )?;
+        insert_bookmark(
+            &conn,
+            InsertableBookmark {
+                parent_guid: BookmarkRootGuid::Unfiled.into(),
+                position: BookmarkPosition::Append,
+                date_added: None,
+                last_modified: None,
+                guid: Some("bookmarkBBBB".into()),
+                url: Url::parse("http://example.com/b")?,
+                title: Some("B".into()),
+            }
+            .into(),
+        )?;
+        insert_bookmark(
+            &conn,
+            InsertableBookmark {
+                parent_guid: "folderAAAAAA".into(),
+                position: BookmarkPosition::Append,
+                date_added: None,
+                last_modified: None,
+                guid: Some("bookmarkCCCC".into()),
+                url: Url::parse("http://example.com/c")?,
+                title: Some("C".into()),
+            }
+            .into(),
+        )?;
+
+        delete_everything(&conn)?;
+
+        let (tree, _, _) =
+            fetch_tree(&conn, &BookmarkRootGuid::Root.into(), &FetchDepth::Deepest)?.unwrap();
+        if let BookmarkTreeNode::Folder { f: root } = tree {
+            assert_eq!(root.children.len(), 4);
+            let unfiled = root
+                .children
+                .iter()
+                .find(|c| c.guid() == BookmarkRootGuid::Unfiled.guid())
+                .expect("Should return unfiled root");
+            if let BookmarkTreeNode::Folder { f: unfiled } = unfiled {
+                assert!(unfiled.children.is_empty());
+            } else {
+                panic!("The unfiled root should be a folder");
+            }
+        } else {
+            panic!("`fetch_tree` should return the Places root folder");
+        }
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_sync_reset() -> Result<()> {
+        let conn = new_mem_connection();
+
+        // Add Sync metadata keys, to ensure they're reset.
+        put_meta(&conn, GLOBAL_SYNCID_META_KEY, &"syncAAAAAAAA")?;
+        put_meta(&conn, COLLECTION_SYNCID_META_KEY, &"syncBBBBBBBB")?;
+        put_meta(&conn, LAST_SYNC_META_KEY, &12345)?;
+
+        insert_bookmark(
+            &conn,
+            InsertableBookmark {
+                parent_guid: BookmarkRootGuid::Unfiled.into(),
+                position: BookmarkPosition::Append,
+                date_added: None,
+                last_modified: None,
+                guid: Some("bookmarkAAAA".into()),
+                url: Url::parse("http://example.com/a")?,
+                title: Some("A".into()),
+            }
+            .into(),
+        )?;
+
+        // Mark all items as synced.
+        conn.execute(
+            &format!(
+                "UPDATE moz_bookmarks SET
+                     syncChangeCounter = 0,
+                     syncStatus = {}",
+                (SyncStatus::Normal as u8)
+            ),
+            [],
+        )?;
+
+        let bmk = get_raw_bookmark(&conn, &"bookmarkAAAA".into())?
+            .expect("Should fetch A before resetting");
+        assert_eq!(bmk._sync_change_counter, 0);
+        assert_eq!(bmk._sync_status, SyncStatus::Normal);
+
+        bookmark_sync::reset(&conn, &EngineSyncAssociation::Disconnected)?;
+
+        let bmk = get_raw_bookmark(&conn, &"bookmarkAAAA".into())?
+            .expect("Should fetch A after resetting");
+        assert_eq!(bmk._sync_change_counter, 1);
+        assert_eq!(bmk._sync_status, SyncStatus::New);
+
+        // Ensure we reset Sync metadata, too.
+        let global = get_meta::<SyncGuid>(&conn, GLOBAL_SYNCID_META_KEY)?;
+        assert!(global.is_none());
+        let coll = get_meta::<SyncGuid>(&conn, COLLECTION_SYNCID_META_KEY)?;
+        assert!(coll.is_none());
+        let since = get_meta::<i64>(&conn, LAST_SYNC_META_KEY)?;
+        assert_eq!(since, Some(0));
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_count_tree() -> Result<()> {
+        let conn = new_mem_connection();
+        let unfiled = BookmarkRootGuid::Unfiled.as_guid();
+
+        insert_json_tree(
+            &conn,
+            json!({
+                "guid": &unfiled,
+                "children": [
+                    {
+                        "guid": "folder1_____",
+                        "title": "A folder",
+                        "children": [
+                            {
+                                "guid": "bookmark1___",
+                                "title": "bookmark in A folder",
+                                "url": "https://www.example2.com/"
+                            },
+                            {
+                                "guid": "separator1__",
+                                "type": BookmarkType::Separator,
+                            },
+                            {
+                                "guid": "bookmark2___",
+                                "title": "next bookmark in A folder",
+                                "url": "https://www.example3.com/"
+                            },
+                        ]
+                    },
+                    {
+                        "guid": "folder2_____",
+                        "title": "folder 2",
+                    },
+                    {
+                        "guid": "folder3_____",
+                        "title": "Another folder",
+                        "children": [
+                            {
+                                "guid": "bookmark3___",
+                                "title": "bookmark in folder 3",
+                                "url": "https://www.example2.com/"
+                            },
+                            {
+                                "guid": "separator2__",
+                                "type": BookmarkType::Separator,
+                            },
+                            {
+                                "guid": "bookmark4___",
+                                "title": "next bookmark in folder 3",
+                                "url": "https://www.example3.com/"
+                            },
+                        ]
+                    },
+                ]
+            }),
+        );
+        assert_eq!(count_bookmarks_in_trees(&conn, &[])?, 0);
+        // A folder with sub-folders
+        assert_eq!(count_bookmarks_in_trees(&conn, &[unfiled])?, 4);
+        // A folder with items but no folders.
+        assert_eq!(
+            count_bookmarks_in_trees(&conn, &[SyncGuid::from("folder1_____")])?,
+            2
+        );
+        // Asking for a bookmark (ie, not a folder) or an invalid guid gives zero.
+        assert_eq!(
+            count_bookmarks_in_trees(&conn, &[SyncGuid::from("bookmark1___")])?,
+            0
+        );
+        assert_eq!(
+            count_bookmarks_in_trees(&conn, &[SyncGuid::from("no_such_guid")])?,
+            0
+        );
+        // empty folder also zero.
+        assert_eq!(
+            count_bookmarks_in_trees(&conn, &[SyncGuid::from("folder2_____")])?,
+            0
+        );
+        // multiple folders
+        assert_eq!(
+            count_bookmarks_in_trees(
+                &conn,
+                &[
+                    SyncGuid::from("folder1_____"),
+                    SyncGuid::from("folder3_____")
+                ]
+            )?,
+            4
+        );
+        Ok(())
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/places/storage/bookmarks/conversions.rs.html b/book/rust-docs/src/places/storage/bookmarks/conversions.rs.html new file mode 100644 index 0000000000..1d1dc5f53f --- /dev/null +++ b/book/rust-docs/src/places/storage/bookmarks/conversions.rs.html @@ -0,0 +1,127 @@ +conversions.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use super::{
+    BookmarkPosition, BookmarkUpdateInfo, InvalidPlaceInfo, UpdatableBookmark, UpdatableFolder,
+    UpdatableItem, UpdatableSeparator, UpdateTreeLocation,
+};
+
+use crate::error::Result;
+use crate::types::BookmarkType;
+use sync_guid::Guid as SyncGuid;
+use url::Url;
+
+impl BookmarkUpdateInfo {
+    /// The functions exposed over the FFI use the same type for all inserts.
+    /// This function converts that into the type our update API uses.
+    pub fn into_updatable(self, ty: BookmarkType) -> Result<(SyncGuid, UpdatableItem)> {
+        // Check the things that otherwise would be enforced by the type system.
+
+        if self.title.is_some() && ty == BookmarkType::Separator {
+            return Err(InvalidPlaceInfo::IllegalChange("title", ty).into());
+        }
+
+        if self.url.is_some() && ty != BookmarkType::Bookmark {
+            return Err(InvalidPlaceInfo::IllegalChange("url", ty).into());
+        }
+
+        let location = match (self.parent_guid, self.position) {
+            (None, None) => UpdateTreeLocation::None,
+            (None, Some(pos)) => UpdateTreeLocation::Position {
+                pos: BookmarkPosition::Specific { pos },
+            },
+            (Some(parent_guid), pos) => UpdateTreeLocation::Parent {
+                guid: parent_guid,
+                pos: pos.map_or(BookmarkPosition::Append, |p| BookmarkPosition::Specific {
+                    pos: p,
+                }),
+            },
+        };
+
+        let updatable = match ty {
+            BookmarkType::Bookmark => UpdatableItem::Bookmark {
+                b: UpdatableBookmark {
+                    location,
+                    title: self.title,
+                    url: self.url.map(|u| Url::parse(&u)).transpose()?,
+                },
+            },
+            BookmarkType::Separator => UpdatableItem::Separator {
+                s: UpdatableSeparator { location },
+            },
+            BookmarkType::Folder => UpdatableItem::Folder {
+                f: UpdatableFolder {
+                    location,
+                    title: self.title,
+                },
+            },
+        };
+
+        Ok((self.guid, updatable))
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/places/storage/bookmarks/fetch.rs.html b/book/rust-docs/src/places/storage/bookmarks/fetch.rs.html new file mode 100644 index 0000000000..2d344e5dcc --- /dev/null +++ b/book/rust-docs/src/places/storage/bookmarks/fetch.rs.html @@ -0,0 +1,1779 @@ +fetch.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
+540
+541
+542
+543
+544
+545
+546
+547
+548
+549
+550
+551
+552
+553
+554
+555
+556
+557
+558
+559
+560
+561
+562
+563
+564
+565
+566
+567
+568
+569
+570
+571
+572
+573
+574
+575
+576
+577
+578
+579
+580
+581
+582
+583
+584
+585
+586
+587
+588
+589
+590
+591
+592
+593
+594
+595
+596
+597
+598
+599
+600
+601
+602
+603
+604
+605
+606
+607
+608
+609
+610
+611
+612
+613
+614
+615
+616
+617
+618
+619
+620
+621
+622
+623
+624
+625
+626
+627
+628
+629
+630
+631
+632
+633
+634
+635
+636
+637
+638
+639
+640
+641
+642
+643
+644
+645
+646
+647
+648
+649
+650
+651
+652
+653
+654
+655
+656
+657
+658
+659
+660
+661
+662
+663
+664
+665
+666
+667
+668
+669
+670
+671
+672
+673
+674
+675
+676
+677
+678
+679
+680
+681
+682
+683
+684
+685
+686
+687
+688
+689
+690
+691
+692
+693
+694
+695
+696
+697
+698
+699
+700
+701
+702
+703
+704
+705
+706
+707
+708
+709
+710
+711
+712
+713
+714
+715
+716
+717
+718
+719
+720
+721
+722
+723
+724
+725
+726
+727
+728
+729
+730
+731
+732
+733
+734
+735
+736
+737
+738
+739
+740
+741
+742
+743
+744
+745
+746
+747
+748
+749
+750
+751
+752
+753
+754
+755
+756
+757
+758
+759
+760
+761
+762
+763
+764
+765
+766
+767
+768
+769
+770
+771
+772
+773
+774
+775
+776
+777
+778
+779
+780
+781
+782
+783
+784
+785
+786
+787
+788
+789
+790
+791
+792
+793
+794
+795
+796
+797
+798
+799
+800
+801
+802
+803
+804
+805
+806
+807
+808
+809
+810
+811
+812
+813
+814
+815
+816
+817
+818
+819
+820
+821
+822
+823
+824
+825
+826
+827
+828
+829
+830
+831
+832
+833
+834
+835
+836
+837
+838
+839
+840
+841
+842
+843
+844
+845
+846
+847
+848
+849
+850
+851
+852
+853
+854
+855
+856
+857
+858
+859
+860
+861
+862
+863
+864
+865
+866
+867
+868
+869
+870
+871
+872
+873
+874
+875
+876
+877
+878
+879
+880
+881
+882
+883
+884
+885
+886
+887
+888
+889
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use super::super::bookmarks::json_tree::{self, FetchDepth};
+use super::*;
+use rusqlite::Row;
+
+// A helper that will ensure tests fail, but in production will make log noise instead.
+fn noisy_debug_assert_eq<T: std::cmp::PartialEq + std::fmt::Debug>(a: &T, b: &T, msg: &str) {
+    debug_assert_eq!(a, b);
+    if a != b {
+        error_support::report_error!(
+            "places-bookmarks-corruption",
+            "check failed: {}: {:?} != {:?}",
+            msg,
+            a,
+            b
+        );
+    }
+}
+
+fn noisy_debug_assert(v: bool, msg: &str) {
+    debug_assert!(v);
+    if !v {
+        error_support::report_error!(
+            "places-bookmark-corruption",
+            "check failed: {}: expected true, got false",
+            msg
+        );
+    }
+}
+
+/// Structs we return when reading bookmarks
+#[derive(Debug, Clone)]
+pub struct BookmarkData {
+    pub guid: SyncGuid,
+    pub parent_guid: SyncGuid,
+    pub position: u32,
+    pub date_added: Timestamp,
+    pub last_modified: Timestamp,
+    pub url: Url,
+    pub title: Option<String>,
+}
+
+impl From<BookmarkData> for Item {
+    fn from(b: BookmarkData) -> Self {
+        Item::Bookmark { b }
+    }
+}
+
+// Only for tests because we ignore timestamps
+#[cfg(test)]
+impl PartialEq for BookmarkData {
+    fn eq(&self, other: &Self) -> bool {
+        self.guid == other.guid
+            && self.parent_guid == other.parent_guid
+            && self.position == other.position
+            && self.url == other.url
+            && self.title == other.title
+    }
+}
+
+#[derive(Debug, Clone)]
+pub struct Separator {
+    pub guid: SyncGuid,
+    pub date_added: Timestamp,
+    pub last_modified: Timestamp,
+    pub parent_guid: SyncGuid,
+    pub position: u32,
+}
+
+impl From<Separator> for Item {
+    fn from(s: Separator) -> Self {
+        Item::Separator { s }
+    }
+}
+
+#[derive(Debug, Clone, Default)]
+pub struct Folder {
+    pub guid: SyncGuid,
+    pub date_added: Timestamp,
+    pub last_modified: Timestamp,
+    pub parent_guid: Option<SyncGuid>, // Option because the root is a folder but has no parent.
+    // Always 0 if parent_guid is None
+    pub position: u32,
+    pub title: Option<String>,
+    // Depending on the specific API request, either, both, or none of these `child_*` vecs
+    // will be populated.
+    pub child_guids: Option<Vec<SyncGuid>>,
+    pub child_nodes: Option<Vec<Item>>,
+}
+
+impl From<Folder> for Item {
+    fn from(f: Folder) -> Self {
+        Item::Folder { f }
+    }
+}
+
+// The type used to update the actual item.
+#[derive(Debug, Clone)]
+pub enum Item {
+    Bookmark { b: BookmarkData },
+    Separator { s: Separator },
+    Folder { f: Folder },
+}
+
+// We allow all "common" fields from the sub-types to be getters on the
+// InsertableItem type.
+macro_rules! impl_common_bookmark_getter {
+    ($getter_name:ident, $T:ty) => {
+        pub fn $getter_name(&self) -> &$T {
+            match self {
+                Item::Bookmark { b } => &b.$getter_name,
+                Item::Separator { s } => &s.$getter_name,
+                Item::Folder { f } => &f.$getter_name,
+            }
+        }
+    };
+}
+
+impl Item {
+    impl_common_bookmark_getter!(guid, SyncGuid);
+    impl_common_bookmark_getter!(position, u32);
+    impl_common_bookmark_getter!(date_added, Timestamp);
+    impl_common_bookmark_getter!(last_modified, Timestamp);
+    pub fn parent_guid(&self) -> Option<&SyncGuid> {
+        match self {
+            Item::Bookmark { b } => Some(&b.parent_guid),
+            Item::Folder { f } => f.parent_guid.as_ref(),
+            Item::Separator { s } => Some(&s.parent_guid),
+        }
+    }
+}
+
+/// No simple `From` here, because json_tree doesn't give us the parent or position - it
+/// expects us to walk a tree, so we do.
+///
+/// Extra complication for the fact the root has a None parent_guid :)
+fn folder_from_node_with_parent_info(
+    f: json_tree::FolderNode,
+    parent_guid: Option<SyncGuid>,
+    position: u32,
+    depth_left: usize,
+) -> Folder {
+    let guid = f.guid.expect("all items have guids");
+    // We always provide child_guids, and only provide child_nodes if we are
+    // going to keep recursing.
+    let child_guids = Some(
+        f.children
+            .iter()
+            .map(|child| child.guid().clone())
+            .collect(),
+    );
+    let child_nodes = if depth_left != 0 {
+        Some(
+            f.children
+                .into_iter()
+                .enumerate()
+                .map(|(child_pos, child)| {
+                    item_from_node_with_parent_info(
+                        child,
+                        guid.clone(),
+                        child_pos as u32,
+                        depth_left - 1,
+                    )
+                })
+                .collect(),
+        )
+    } else {
+        None
+    };
+    Folder {
+        guid,
+        parent_guid,
+        position,
+        child_nodes,
+        child_guids,
+        title: f.title,
+        date_added: f.date_added.expect("always get dates"),
+        last_modified: f.last_modified.expect("always get dates"),
+    }
+}
+
+fn item_from_node_with_parent_info(
+    n: json_tree::BookmarkTreeNode,
+    parent_guid: SyncGuid,
+    position: u32,
+    depth_left: usize,
+) -> Item {
+    match n {
+        json_tree::BookmarkTreeNode::Bookmark { b } => BookmarkData {
+            guid: b.guid.expect("all items have guids"),
+            parent_guid,
+            position,
+            url: b.url,
+            title: b.title,
+            date_added: b.date_added.expect("always get dates"),
+            last_modified: b.last_modified.expect("always get dates"),
+        }
+        .into(),
+        json_tree::BookmarkTreeNode::Separator { s } => Separator {
+            guid: s.guid.expect("all items have guids"),
+            parent_guid,
+            position,
+            date_added: s.date_added.expect("always get dates"),
+            last_modified: s.last_modified.expect("always get dates"),
+        }
+        .into(),
+        json_tree::BookmarkTreeNode::Folder { f } => {
+            folder_from_node_with_parent_info(f, Some(parent_guid), position, depth_left).into()
+        }
+    }
+}
+
+/// Call fetch_tree_with_depth with FetchDepth::Deepest.
+/// This is the function called by the FFI when requesting the tree.
+pub fn fetch_tree(db: &PlacesDb, item_guid: &SyncGuid) -> Result<Option<Item>> {
+    fetch_tree_with_depth(db, item_guid, &FetchDepth::Deepest)
+}
+
+/// Call fetch_tree with a depth parameter and convert the result
+/// to an Item.
+pub fn fetch_tree_with_depth(
+    db: &PlacesDb,
+    item_guid: &SyncGuid,
+    target_depth: &FetchDepth,
+) -> Result<Option<Item>> {
+    let (tree, parent_guid, position) = if let Some((tree, parent_guid, position)) =
+        json_tree::fetch_tree(db, item_guid, target_depth)?
+    {
+        (tree, parent_guid, position)
+    } else {
+        return Ok(None);
+    };
+    // parent_guid being an Option<> is a bit if a pain :(
+    Ok(Some(match tree {
+        json_tree::BookmarkTreeNode::Folder { f } => {
+            noisy_debug_assert(
+                parent_guid.is_none() ^ (f.guid.as_ref() != Some(BookmarkRootGuid::Root.guid())),
+                "only root has no parent",
+            );
+            let depth_left = match target_depth {
+                FetchDepth::Specific(v) => *v,
+                FetchDepth::Deepest => usize::MAX,
+            };
+            folder_from_node_with_parent_info(f, parent_guid, position, depth_left).into()
+        }
+        _ => item_from_node_with_parent_info(
+            tree,
+            parent_guid.expect("must have parent"),
+            position,
+            0,
+        ),
+    }))
+}
+
+pub fn fetch_bookmarks_by_url(db: &PlacesDb, url: &Url) -> Result<Vec<BookmarkData>> {
+    let nodes = crate::storage::bookmarks::get_raw_bookmarks_for_url(db, url)?
+        .into_iter()
+        .map(|rb| {
+            // Cause tests to fail, but we'd rather not panic here
+            // for real.
+            noisy_debug_assert_eq(&rb.child_count, &0, "child count should be zero");
+            noisy_debug_assert_eq(
+                &rb.bookmark_type,
+                &BookmarkType::Bookmark,
+                "not a bookmark!",
+            );
+            // We don't log URLs so we do the comparison here.
+            noisy_debug_assert(rb.url.as_ref() == Some(url), "urls don't match");
+            noisy_debug_assert(rb.parent_guid.is_some(), "no parent guid");
+            BookmarkData {
+                guid: rb.guid,
+                parent_guid: rb
+                    .parent_guid
+                    .unwrap_or_else(|| BookmarkRootGuid::Unfiled.into()),
+                position: rb.position,
+                date_added: rb.date_added,
+                last_modified: rb.date_modified,
+                url: url.clone(),
+                title: rb.title,
+            }
+        })
+        .collect::<Vec<_>>();
+    Ok(nodes)
+}
+
+/// This is similar to fetch_tree, but does not recursively fetch children of
+/// folders.
+///
+/// If `get_direct_children` is true, it will return 1 level of folder children,
+/// otherwise it returns just their guids.
+pub fn fetch_bookmark(
+    db: &PlacesDb,
+    item_guid: &SyncGuid,
+    get_direct_children: bool,
+) -> Result<Option<Item>> {
+    let depth = if get_direct_children {
+        FetchDepth::Specific(1)
+    } else {
+        FetchDepth::Specific(0)
+    };
+    fetch_tree_with_depth(db, item_guid, &depth)
+}
+
+fn bookmark_from_row(row: &Row<'_>) -> Result<Option<BookmarkData>> {
+    Ok(
+        match row
+            .get::<_, Option<String>>("url")?
+            .and_then(|href| url::Url::parse(&href).ok())
+        {
+            Some(url) => Some(BookmarkData {
+                guid: row.get("guid")?,
+                parent_guid: row.get("parentGuid")?,
+                position: row.get("position")?,
+                date_added: row.get("dateAdded")?,
+                last_modified: row.get("lastModified")?,
+                title: row.get("title")?,
+                url,
+            }),
+            None => None,
+        },
+    )
+}
+
+pub fn search_bookmarks(db: &PlacesDb, search: &str, limit: u32) -> Result<Vec<BookmarkData>> {
+    let scope = db.begin_interrupt_scope()?;
+    Ok(db
+        .query_rows_into_cached::<Vec<Option<BookmarkData>>, _, _, _, _>(
+            &SEARCH_QUERY,
+            &[
+                (":search", &search as &dyn rusqlite::ToSql),
+                (":limit", &limit),
+            ],
+            |row| -> Result<_> {
+                scope.err_if_interrupted()?;
+                bookmark_from_row(row)
+            },
+        )?
+        .into_iter()
+        .flatten()
+        .collect())
+}
+
+pub fn recent_bookmarks(db: &PlacesDb, limit: u32) -> Result<Vec<BookmarkData>> {
+    let scope = db.begin_interrupt_scope()?;
+    Ok(db
+        .query_rows_into_cached::<Vec<Option<BookmarkData>>, _, _, _, _>(
+            &RECENT_BOOKMARKS_QUERY,
+            &[(":limit", &limit as &dyn rusqlite::ToSql)],
+            |row| -> Result<_> {
+                scope.err_if_interrupted()?;
+                bookmark_from_row(row)
+            },
+        )?
+        .into_iter()
+        .flatten()
+        .collect())
+}
+
+lazy_static::lazy_static! {
+    pub static ref SEARCH_QUERY: String = format!(
+        "SELECT
+            b.guid,
+            p.guid AS parentGuid,
+            b.position,
+            b.dateAdded,
+            b.lastModified,
+            -- Note we return null for titles with an empty string.
+            NULLIF(b.title, '') AS title,
+            h.url AS url
+        FROM moz_bookmarks b
+        JOIN moz_bookmarks p ON p.id = b.parent
+        JOIN moz_places h ON h.id = b.fk
+        WHERE b.type = {bookmark_type}
+            AND AUTOCOMPLETE_MATCH(
+                :search, h.url, IFNULL(b.title, h.title),
+                NULL, -- tags
+                -- We could pass the versions of these from history in,
+                -- but they're just used to figure out whether or not
+                -- the query fits the given behavior, and we know
+                -- we're only passing in and looking for bookmarks,
+                -- so using the args from history would be pointless
+                -- and would make things slower.
+                0, -- visit_count
+                0, -- typed
+                1, -- bookmarked
+                NULL, -- open page count
+                {match_bhvr},
+                {search_bhvr}
+            )
+        LIMIT :limit",
+        bookmark_type = BookmarkType::Bookmark as u8,
+        match_bhvr = crate::match_impl::MatchBehavior::Anywhere as u32,
+        search_bhvr = crate::match_impl::SearchBehavior::BOOKMARK.bits(),
+    );
+
+    pub static ref RECENT_BOOKMARKS_QUERY: String = format!(
+        "SELECT
+            b.guid,
+            p.guid AS parentGuid,
+            b.position,
+            b.dateAdded,
+            b.lastModified,
+            NULLIF(b.title, '') AS title,
+            h.url AS url
+        FROM moz_bookmarks b
+        JOIN moz_bookmarks p ON p.id = b.parent
+        JOIN moz_places h ON h.id = b.fk
+        WHERE b.type = {bookmark_type}
+        ORDER BY b.dateAdded DESC
+        LIMIT :limit",
+        bookmark_type = BookmarkType::Bookmark as u8
+    );
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+    use crate::api::places_api::test::new_mem_connections;
+    use crate::tests::{append_invalid_bookmark, insert_json_tree};
+    use serde_json::json;
+    #[test]
+    fn test_get_by_url() -> Result<()> {
+        let conns = new_mem_connections();
+        insert_json_tree(
+            &conns.write,
+            json!({
+                "guid": String::from(BookmarkRootGuid::Unfiled.as_str()),
+                "children": [
+                    {
+                        "guid": "bookmark1___",
+                        "url": "https://www.example1.com/",
+                        "title": "no 1",
+                    },
+                    {
+                        "guid": "bookmark2___",
+                        "url": "https://www.example2.com/a/b/c/d?q=v#abcde",
+                        "title": "yes 1",
+                    },
+                    {
+                        "guid": "bookmark3___",
+                        "url": "https://www.example2.com/a/b/c/d",
+                        "title": "no 2",
+                    },
+                    {
+                        "guid": "bookmark4___",
+                        "url": "https://www.example2.com/a/b/c/d?q=v#abcde",
+                        "title": "yes 2",
+                    },
+                ]
+            }),
+        );
+        let url = url::Url::parse("https://www.example2.com/a/b/c/d?q=v#abcde")?;
+        let mut bmks = fetch_bookmarks_by_url(&conns.read, &url)?;
+        bmks.sort_by_key(|b| b.guid.as_str().to_string());
+        assert_eq!(bmks.len(), 2);
+        assert_eq!(
+            bmks[0],
+            BookmarkData {
+                guid: "bookmark2___".into(),
+                title: Some("yes 1".into()),
+                url: url.clone(),
+                parent_guid: BookmarkRootGuid::Unfiled.into(),
+                position: 1,
+                // Ignored by our PartialEq
+                date_added: Timestamp(0),
+                last_modified: Timestamp(0),
+            }
+        );
+        assert_eq!(
+            bmks[1],
+            BookmarkData {
+                guid: "bookmark4___".into(),
+                title: Some("yes 2".into()),
+                url,
+                parent_guid: BookmarkRootGuid::Unfiled.into(),
+                position: 3,
+                // Ignored by our PartialEq
+                date_added: Timestamp(0),
+                last_modified: Timestamp(0),
+            }
+        );
+
+        let no_url = url::Url::parse("https://no.bookmark.com")?;
+        assert!(fetch_bookmarks_by_url(&conns.read, &no_url)?.is_empty());
+
+        Ok(())
+    }
+    #[test]
+    fn test_search() -> Result<()> {
+        let conns = new_mem_connections();
+        insert_json_tree(
+            &conns.write,
+            json!({
+                "guid": String::from(BookmarkRootGuid::Unfiled.as_str()),
+                "children": [
+                    {
+                        "guid": "bookmark1___",
+                        "url": "https://www.example1.com/",
+                        "title": "",
+                    },
+                    {
+                        "guid": "bookmark2___",
+                        "url": "https://www.example2.com/a/b/c/d?q=v#example",
+                        "title": "",
+                    },
+                    {
+                        "guid": "bookmark3___",
+                        "url": "https://www.example2.com/a/b/c/d",
+                        "title": "",
+                    },
+                    {
+                        "guid": "bookmark4___",
+                        "url": "https://www.doesnt_match.com/a/b/c/d",
+                        "title": "",
+                    },
+                    {
+                        "guid": "bookmark5___",
+                        "url": "https://www.example2.com/a/b/",
+                        "title": "a b c d",
+                    },
+                    {
+                        "guid": "bookmark6___",
+                        "url": "https://www.example2.com/a/b/c/d",
+                        "title": "foo bar baz",
+                    },
+                    {
+                        "guid": "bookmark7___",
+                        "url": "https://www.1234.com/a/b/c/d",
+                        "title": "my example bookmark",
+                    },
+                ]
+            }),
+        );
+        append_invalid_bookmark(
+            &conns.write,
+            BookmarkRootGuid::Unfiled.guid(),
+            "invalid",
+            "badurl",
+        );
+        let mut bmks = search_bookmarks(&conns.read, "ample", 10)?;
+        bmks.sort_by_key(|b| b.guid.as_str().to_string());
+        assert_eq!(bmks.len(), 6);
+        let expect = [
+            ("bookmark1___", "https://www.example1.com/", "", 0),
+            (
+                "bookmark2___",
+                "https://www.example2.com/a/b/c/d?q=v#example",
+                "",
+                1,
+            ),
+            ("bookmark3___", "https://www.example2.com/a/b/c/d", "", 2),
+            (
+                "bookmark5___",
+                "https://www.example2.com/a/b/",
+                "a b c d",
+                4,
+            ),
+            (
+                "bookmark6___",
+                "https://www.example2.com/a/b/c/d",
+                "foo bar baz",
+                5,
+            ),
+            (
+                "bookmark7___",
+                "https://www.1234.com/a/b/c/d",
+                "my example bookmark",
+                6,
+            ),
+        ];
+        for (got, want) in bmks.iter().zip(expect.iter()) {
+            assert_eq!(got.guid.as_str(), want.0);
+            assert_eq!(got.url, url::Url::parse(want.1).unwrap());
+            assert_eq!(got.title.as_ref().unwrap_or(&String::new()), want.2);
+            assert_eq!(got.position, want.3);
+            assert_eq!(got.parent_guid, BookmarkRootGuid::Unfiled);
+        }
+        Ok(())
+    }
+    #[test]
+    fn test_fetch_bookmark() -> Result<()> {
+        let conns = new_mem_connections();
+
+        insert_json_tree(
+            &conns.write,
+            json!({
+                "guid": BookmarkRootGuid::Mobile.as_guid(),
+                "children": [
+                    {
+                        "guid": "bookmark1___",
+                        "url": "https://www.example1.com/"
+                    },
+                    {
+                        "guid": "bookmark2___",
+                        "url": "https://www.example2.com/"
+                    },
+                ]
+            }),
+        );
+
+        // Put a couple of invalid items in the tree - not only should fetching
+        // them directly "work" (as in, not crash!), fetching their parent's
+        // tree should also do a sane thing (ie, not crash *and* return the
+        // valid items)
+        let guid_bad = append_invalid_bookmark(
+            &conns.write,
+            BookmarkRootGuid::Mobile.guid(),
+            "invalid url",
+            "badurl",
+        )
+        .guid;
+        assert!(fetch_bookmark(&conns.read, &guid_bad, false)?.is_none());
+
+        // Now fetch the entire tree.
+        let root = match fetch_bookmark(&conns.read, BookmarkRootGuid::Root.guid(), false)?.unwrap()
+        {
+            Item::Folder { f } => f,
+            _ => panic!("root not a folder?"),
+        };
+        assert!(root.child_guids.is_some());
+        assert!(root.child_nodes.is_none());
+        assert_eq!(root.child_guids.unwrap().len(), 4);
+
+        let root = match fetch_bookmark(&conns.read, BookmarkRootGuid::Root.guid(), true)?.unwrap()
+        {
+            Item::Folder { f } => f,
+            _ => panic!("not a folder?"),
+        };
+
+        assert!(root.child_nodes.is_some());
+        assert!(root.child_guids.is_some());
+        assert_eq!(
+            root.child_guids.unwrap(),
+            root.child_nodes
+                .as_ref()
+                .unwrap()
+                .iter()
+                .map(|c| c.guid().clone())
+                .collect::<Vec<SyncGuid>>()
+        );
+        let root_children = root.child_nodes.unwrap();
+        assert_eq!(root_children.len(), 4);
+        for child in root_children {
+            match child {
+                Item::Folder { f: child } => {
+                    assert!(child.child_guids.is_some());
+                    assert!(child.child_nodes.is_none());
+                    if child.guid == BookmarkRootGuid::Mobile {
+                        assert_eq!(
+                            child.child_guids.unwrap(),
+                            &[
+                                SyncGuid::from("bookmark1___"),
+                                SyncGuid::from("bookmark2___")
+                            ]
+                        );
+                    }
+                }
+                _ => panic!("all root children should be folders"),
+            }
+        }
+
+        let unfiled =
+            match fetch_bookmark(&conns.read, BookmarkRootGuid::Unfiled.guid(), false)?.unwrap() {
+                Item::Folder { f } => f,
+                _ => panic!("not a folder?"),
+            };
+
+        assert!(unfiled.child_guids.is_some());
+        assert!(unfiled.child_nodes.is_none());
+        assert_eq!(unfiled.child_guids.unwrap().len(), 0);
+
+        let unfiled =
+            match fetch_bookmark(&conns.read, BookmarkRootGuid::Unfiled.guid(), true)?.unwrap() {
+                Item::Folder { f } => f,
+                _ => panic!("not a folder?"),
+            };
+        assert!(unfiled.child_guids.is_some());
+        assert!(unfiled.child_nodes.is_some());
+
+        assert_eq!(unfiled.child_nodes.unwrap().len(), 0);
+        assert_eq!(unfiled.child_guids.unwrap().len(), 0);
+
+        assert!(fetch_bookmark(&conns.read, &"not_exist___".into(), true)?.is_none());
+        Ok(())
+    }
+    #[test]
+    fn test_fetch_tree() -> Result<()> {
+        let conns = new_mem_connections();
+
+        insert_json_tree(
+            &conns.write,
+            json!({
+                "guid": BookmarkRootGuid::Mobile.as_guid(),
+                "children": [
+                    {
+                        "guid": "bookmark1___",
+                        "url": "https://www.example1.com/"
+                    },
+                    {
+                        "guid": "bookmark2___",
+                        "url": "https://www.example2.com/"
+                    },
+                ]
+            }),
+        );
+
+        append_invalid_bookmark(
+            &conns.write,
+            BookmarkRootGuid::Mobile.guid(),
+            "invalid url",
+            "badurl",
+        );
+
+        let root = match fetch_tree(&conns.read, BookmarkRootGuid::Root.guid())?.unwrap() {
+            Item::Folder { f } => f,
+            _ => panic!("root not a folder?"),
+        };
+        assert!(root.parent_guid.is_none());
+        assert_eq!(root.position, 0);
+
+        assert!(root.child_guids.is_some());
+        let children = root.child_nodes.as_ref().unwrap();
+        assert_eq!(
+            root.child_guids.unwrap(),
+            children
+                .iter()
+                .map(|c| c.guid().clone())
+                .collect::<Vec<SyncGuid>>()
+        );
+        let mut mobile_pos = None;
+        for (i, c) in children.iter().enumerate() {
+            assert_eq!(i as u32, *c.position());
+            assert_eq!(c.parent_guid().unwrap(), &root.guid);
+            match c {
+                Item::Folder { f } => {
+                    // all out roots are here, so check it is mobile.
+                    if f.guid == BookmarkRootGuid::Mobile {
+                        assert!(f.child_guids.is_some());
+                        assert!(f.child_nodes.is_some());
+                        let child_nodes = f.child_nodes.as_ref().unwrap();
+                        assert_eq!(
+                            f.child_guids.as_ref().unwrap(),
+                            &child_nodes
+                                .iter()
+                                .map(|c| c.guid().clone())
+                                .collect::<Vec<SyncGuid>>()
+                        );
+                        mobile_pos = Some(i as u32);
+                        let b = match &child_nodes[0] {
+                            Item::Bookmark { b } => b,
+                            _ => panic!("expect a bookmark"),
+                        };
+                        assert_eq!(b.position, 0);
+                        assert_eq!(b.guid, SyncGuid::from("bookmark1___"));
+                        assert_eq!(b.url, Url::parse("https://www.example1.com/").unwrap());
+
+                        let b = match &child_nodes[1] {
+                            Item::Bookmark { b } => b,
+                            _ => panic!("expect a bookmark"),
+                        };
+                        assert_eq!(b.position, 1);
+                        assert_eq!(b.guid, SyncGuid::from("bookmark2___"));
+                        assert_eq!(b.url, Url::parse("https://www.example2.com/").unwrap());
+                    }
+                }
+                _ => panic!("unexpected type"),
+            }
+        }
+        // parent_guid/position for the directly returned node is filled in separately,
+        // so make sure it works for non-root nodes too.
+        let mobile = match fetch_tree(&conns.read, BookmarkRootGuid::Mobile.guid())?.unwrap() {
+            Item::Folder { f } => f,
+            _ => panic!("not a folder?"),
+        };
+        assert_eq!(mobile.parent_guid.unwrap(), BookmarkRootGuid::Root);
+        assert_eq!(mobile.position, mobile_pos.unwrap());
+
+        let bm1 = match fetch_tree(&conns.read, &SyncGuid::from("bookmark1___"))?.unwrap() {
+            Item::Bookmark { b } => b,
+            _ => panic!("not a bookmark?"),
+        };
+        assert_eq!(bm1.parent_guid, BookmarkRootGuid::Mobile);
+        assert_eq!(bm1.position, 0);
+
+        Ok(())
+    }
+    #[test]
+    fn test_recent() -> Result<()> {
+        let conns = new_mem_connections();
+        let kids = [
+            json!({
+                "guid": "bookmark1___",
+                "url": "https://www.example1.com/",
+                "title": "b1",
+            }),
+            json!({
+                "guid": "bookmark2___",
+                "url": "https://www.example2.com/",
+                "title": "b2",
+            }),
+            json!({
+                "guid": "bookmark3___",
+                "url": "https://www.example3.com/",
+                "title": "b3",
+            }),
+            json!({
+                "guid": "bookmark4___",
+                "url": "https://www.example4.com/",
+                "title": "b4",
+            }),
+            // should be ignored.
+            json!({
+                "guid": "folder1_____",
+                "title": "A folder",
+                "children": []
+            }),
+            json!({
+                "guid": "bookmark5___",
+                "url": "https://www.example5.com/",
+                "title": "b5",
+            }),
+        ];
+        for k in &kids {
+            insert_json_tree(
+                &conns.write,
+                json!({
+                    "guid": String::from(BookmarkRootGuid::Unfiled.as_str()),
+                    "children": [k.clone()],
+                }),
+            );
+            std::thread::sleep(std::time::Duration::from_millis(10));
+        }
+
+        append_invalid_bookmark(
+            &conns.write,
+            BookmarkRootGuid::Unfiled.guid(),
+            "invalid url",
+            "badurl",
+        );
+
+        // The limit applies before we filter the invalid bookmark, so ask for 4.
+        let bmks = recent_bookmarks(&conns.read, 4)?;
+        assert_eq!(bmks.len(), 3);
+
+        assert_eq!(
+            bmks[0],
+            BookmarkData {
+                guid: "bookmark5___".into(),
+                title: Some("b5".into()),
+                url: Url::parse("https://www.example5.com/").unwrap(),
+                parent_guid: BookmarkRootGuid::Unfiled.into(),
+                position: 5,
+                // Ignored by our PartialEq
+                date_added: Timestamp(0),
+                last_modified: Timestamp(0),
+            }
+        );
+        assert_eq!(
+            bmks[1],
+            BookmarkData {
+                guid: "bookmark4___".into(),
+                title: Some("b4".into()),
+                url: Url::parse("https://www.example4.com/").unwrap(),
+                parent_guid: BookmarkRootGuid::Unfiled.into(),
+                position: 3,
+                // Ignored by our PartialEq
+                date_added: Timestamp(0),
+                last_modified: Timestamp(0),
+            }
+        );
+        assert_eq!(
+            bmks[2],
+            BookmarkData {
+                guid: "bookmark3___".into(),
+                title: Some("b3".into()),
+                url: Url::parse("https://www.example3.com/").unwrap(),
+                parent_guid: BookmarkRootGuid::Unfiled.into(),
+                position: 2,
+                // Ignored by our PartialEq
+                date_added: Timestamp(0),
+                last_modified: Timestamp(0),
+            }
+        );
+        Ok(())
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/places/storage/bookmarks/json_tree.rs.html b/book/rust-docs/src/places/storage/bookmarks/json_tree.rs.html new file mode 100644 index 0000000000..560ba59899 --- /dev/null +++ b/book/rust-docs/src/places/storage/bookmarks/json_tree.rs.html @@ -0,0 +1,1625 @@ +json_tree.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
+540
+541
+542
+543
+544
+545
+546
+547
+548
+549
+550
+551
+552
+553
+554
+555
+556
+557
+558
+559
+560
+561
+562
+563
+564
+565
+566
+567
+568
+569
+570
+571
+572
+573
+574
+575
+576
+577
+578
+579
+580
+581
+582
+583
+584
+585
+586
+587
+588
+589
+590
+591
+592
+593
+594
+595
+596
+597
+598
+599
+600
+601
+602
+603
+604
+605
+606
+607
+608
+609
+610
+611
+612
+613
+614
+615
+616
+617
+618
+619
+620
+621
+622
+623
+624
+625
+626
+627
+628
+629
+630
+631
+632
+633
+634
+635
+636
+637
+638
+639
+640
+641
+642
+643
+644
+645
+646
+647
+648
+649
+650
+651
+652
+653
+654
+655
+656
+657
+658
+659
+660
+661
+662
+663
+664
+665
+666
+667
+668
+669
+670
+671
+672
+673
+674
+675
+676
+677
+678
+679
+680
+681
+682
+683
+684
+685
+686
+687
+688
+689
+690
+691
+692
+693
+694
+695
+696
+697
+698
+699
+700
+701
+702
+703
+704
+705
+706
+707
+708
+709
+710
+711
+712
+713
+714
+715
+716
+717
+718
+719
+720
+721
+722
+723
+724
+725
+726
+727
+728
+729
+730
+731
+732
+733
+734
+735
+736
+737
+738
+739
+740
+741
+742
+743
+744
+745
+746
+747
+748
+749
+750
+751
+752
+753
+754
+755
+756
+757
+758
+759
+760
+761
+762
+763
+764
+765
+766
+767
+768
+769
+770
+771
+772
+773
+774
+775
+776
+777
+778
+779
+780
+781
+782
+783
+784
+785
+786
+787
+788
+789
+790
+791
+792
+793
+794
+795
+796
+797
+798
+799
+800
+801
+802
+803
+804
+805
+806
+807
+808
+809
+810
+811
+812
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This supports inserting and fetching an entire bookmark tree via JSON
+// compatible data structures.
+// It's currently used only by tests, examples and our utilities for importing
+// from a desktop JSON exports.
+//
+// None of our "real" consumers currently require JSON compatibility, so try
+// and avoid using this if you can!
+// (We could possibly put this behind a feature flag?)
+
+use crate::error::Result;
+use crate::types::BookmarkType;
+//#[cfg(test)]
+use crate::db::PlacesDb;
+use rusqlite::Row;
+use sql_support::ConnExt;
+use std::collections::HashMap;
+use sync_guid::Guid as SyncGuid;
+use types::Timestamp;
+use url::Url;
+
+use super::{
+    BookmarkPosition, InsertableBookmark, InsertableFolder, InsertableItem, InsertableSeparator,
+    RowId,
+};
+
+use serde::{
+    de::{Deserialize, Deserializer},
+    ser::{Serialize, SerializeStruct, Serializer},
+};
+use serde_derive::*;
+
+/// Support for inserting and fetching a tree. Same limitations as desktop.
+/// Note that the guids are optional when inserting a tree. They will always
+/// have values when fetching it.
+
+// For testing purposes we implement PartialEq, such that optional fields are
+// ignored in the comparison. This allows tests to construct a tree with
+// missing fields and be able to compare against a tree with all fields (such
+// as one exported from the DB)
+#[cfg(test)]
+fn cmp_options<T: PartialEq>(s: &Option<T>, o: &Option<T>) -> bool {
+    match (s, o) {
+        (None, None) => true,
+        (None, Some(_)) => true,
+        (Some(_), None) => true,
+        (s, o) => s == o,
+    }
+}
+
+#[derive(Debug)]
+pub struct BookmarkNode {
+    pub guid: Option<SyncGuid>,
+    pub date_added: Option<Timestamp>,
+    pub last_modified: Option<Timestamp>,
+    pub title: Option<String>,
+    pub url: Url,
+}
+
+impl From<BookmarkNode> for BookmarkTreeNode {
+    fn from(b: BookmarkNode) -> Self {
+        BookmarkTreeNode::Bookmark { b }
+    }
+}
+
+#[cfg(test)]
+impl PartialEq for BookmarkNode {
+    fn eq(&self, other: &BookmarkNode) -> bool {
+        cmp_options(&self.guid, &other.guid)
+            && cmp_options(&self.date_added, &other.date_added)
+            && cmp_options(&self.last_modified, &other.last_modified)
+            && cmp_options(&self.title, &other.title)
+            && self.url == other.url
+    }
+}
+
+#[derive(Debug, Default)]
+pub struct SeparatorNode {
+    pub guid: Option<SyncGuid>,
+    pub date_added: Option<Timestamp>,
+    pub last_modified: Option<Timestamp>,
+}
+
+impl From<SeparatorNode> for BookmarkTreeNode {
+    fn from(s: SeparatorNode) -> Self {
+        BookmarkTreeNode::Separator { s }
+    }
+}
+
+#[cfg(test)]
+impl PartialEq for SeparatorNode {
+    fn eq(&self, other: &SeparatorNode) -> bool {
+        cmp_options(&self.guid, &other.guid)
+            && cmp_options(&self.date_added, &other.date_added)
+            && cmp_options(&self.last_modified, &other.last_modified)
+    }
+}
+
+#[derive(Debug, Default)]
+pub struct FolderNode {
+    pub guid: Option<SyncGuid>,
+    pub date_added: Option<Timestamp>,
+    pub last_modified: Option<Timestamp>,
+    pub title: Option<String>,
+    pub children: Vec<BookmarkTreeNode>,
+}
+
+impl From<FolderNode> for BookmarkTreeNode {
+    fn from(f: FolderNode) -> Self {
+        BookmarkTreeNode::Folder { f }
+    }
+}
+
+#[cfg(test)]
+impl PartialEq for FolderNode {
+    fn eq(&self, other: &FolderNode) -> bool {
+        cmp_options(&self.guid, &other.guid)
+            && cmp_options(&self.date_added, &other.date_added)
+            && cmp_options(&self.last_modified, &other.last_modified)
+            && cmp_options(&self.title, &other.title)
+            && self.children == other.children
+    }
+}
+
+#[derive(Debug)]
+#[cfg_attr(test, derive(PartialEq))]
+pub enum BookmarkTreeNode {
+    Bookmark { b: BookmarkNode },
+    Separator { s: SeparatorNode },
+    Folder { f: FolderNode },
+}
+
+impl BookmarkTreeNode {
+    pub fn node_type(&self) -> BookmarkType {
+        match self {
+            BookmarkTreeNode::Bookmark { .. } => BookmarkType::Bookmark,
+            BookmarkTreeNode::Folder { .. } => BookmarkType::Folder,
+            BookmarkTreeNode::Separator { .. } => BookmarkType::Separator,
+        }
+    }
+
+    pub fn guid(&self) -> &SyncGuid {
+        let guid = match self {
+            BookmarkTreeNode::Bookmark { b } => b.guid.as_ref(),
+            BookmarkTreeNode::Folder { f } => f.guid.as_ref(),
+            BookmarkTreeNode::Separator { s } => s.guid.as_ref(),
+        };
+        // Can this happen? Why is this an Option?
+        guid.expect("Missing guid?")
+    }
+
+    pub fn created_modified(&self) -> (Timestamp, Timestamp) {
+        let (created, modified) = match self {
+            BookmarkTreeNode::Bookmark { b } => (b.date_added, b.last_modified),
+            BookmarkTreeNode::Folder { f } => (f.date_added, f.last_modified),
+            BookmarkTreeNode::Separator { s } => (s.date_added, s.last_modified),
+        };
+        (
+            created.unwrap_or_else(Timestamp::now),
+            modified.unwrap_or_else(Timestamp::now),
+        )
+    }
+}
+
+// Serde makes it tricky to serialize what we need here - a 'type' from the
+// enum and then a flattened variant struct. So we gotta do it manually.
+impl Serialize for BookmarkTreeNode {
+    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        let mut state = serializer.serialize_struct("BookmarkTreeNode", 2)?;
+        match self {
+            BookmarkTreeNode::Bookmark { b } => {
+                state.serialize_field("type", &BookmarkType::Bookmark)?;
+                state.serialize_field("guid", &b.guid)?;
+                state.serialize_field("date_added", &b.date_added)?;
+                state.serialize_field("last_modified", &b.last_modified)?;
+                state.serialize_field("title", &b.title)?;
+                state.serialize_field("url", &b.url.to_string())?;
+            }
+            BookmarkTreeNode::Separator { s } => {
+                state.serialize_field("type", &BookmarkType::Separator)?;
+                state.serialize_field("guid", &s.guid)?;
+                state.serialize_field("date_added", &s.date_added)?;
+                state.serialize_field("last_modified", &s.last_modified)?;
+            }
+            BookmarkTreeNode::Folder { f } => {
+                state.serialize_field("type", &BookmarkType::Folder)?;
+                state.serialize_field("guid", &f.guid)?;
+                state.serialize_field("date_added", &f.date_added)?;
+                state.serialize_field("last_modified", &f.last_modified)?;
+                state.serialize_field("title", &f.title)?;
+                state.serialize_field("children", &f.children)?;
+            }
+        };
+        state.end()
+    }
+}
+
+impl<'de> Deserialize<'de> for BookmarkTreeNode {
+    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        // *sob* - a union of fields we post-process.
+        #[derive(Debug, Default, Deserialize)]
+        #[serde(default)]
+        struct Mapping {
+            #[serde(rename = "type")]
+            bookmark_type: u8,
+            guid: Option<SyncGuid>,
+            date_added: Option<Timestamp>,
+            last_modified: Option<Timestamp>,
+            title: Option<String>,
+            url: Option<String>,
+            children: Vec<BookmarkTreeNode>,
+        }
+        let m = Mapping::deserialize(deserializer)?;
+
+        let url = m.url.as_ref().and_then(|u| match Url::parse(u) {
+            Err(e) => {
+                log::warn!(
+                    "ignoring invalid url for {}: {:?}",
+                    m.guid.as_ref().map(AsRef::as_ref).unwrap_or("<no guid>"),
+                    e
+                );
+                None
+            }
+            Ok(parsed) => Some(parsed),
+        });
+
+        let bookmark_type = BookmarkType::from_u8_with_valid_url(m.bookmark_type, || url.is_some());
+        Ok(match bookmark_type {
+            BookmarkType::Bookmark => BookmarkNode {
+                guid: m.guid,
+                date_added: m.date_added,
+                last_modified: m.last_modified,
+                title: m.title,
+                url: url.unwrap(),
+            }
+            .into(),
+            BookmarkType::Separator => SeparatorNode {
+                guid: m.guid,
+                date_added: m.date_added,
+                last_modified: m.last_modified,
+            }
+            .into(),
+            BookmarkType::Folder => FolderNode {
+                guid: m.guid,
+                date_added: m.date_added,
+                last_modified: m.last_modified,
+                title: m.title,
+                children: m.children,
+            }
+            .into(),
+        })
+    }
+}
+
+impl From<BookmarkTreeNode> for InsertableItem {
+    fn from(node: BookmarkTreeNode) -> Self {
+        match node {
+            BookmarkTreeNode::Bookmark { b } => InsertableBookmark {
+                parent_guid: SyncGuid::empty(),
+                position: BookmarkPosition::Append,
+                date_added: b.date_added,
+                last_modified: b.last_modified,
+                guid: b.guid,
+                url: b.url,
+                title: b.title,
+            }
+            .into(),
+            BookmarkTreeNode::Separator { s } => InsertableSeparator {
+                parent_guid: SyncGuid::empty(),
+                position: BookmarkPosition::Append,
+                date_added: s.date_added,
+                last_modified: s.last_modified,
+                guid: s.guid,
+            }
+            .into(),
+            BookmarkTreeNode::Folder { f } => InsertableFolder {
+                parent_guid: SyncGuid::empty(),
+                position: BookmarkPosition::Append,
+                date_added: f.date_added,
+                last_modified: f.last_modified,
+                guid: f.guid,
+                title: f.title,
+                children: f.children.into_iter().map(Into::into).collect(),
+            }
+            .into(),
+        }
+    }
+}
+
+#[cfg(test)]
+mod test_serialize {
+    use super::*;
+    use serde_json::json;
+
+    #[test]
+    fn test_tree_serialize() -> Result<()> {
+        let guid = SyncGuid::random();
+        let tree = BookmarkTreeNode::Folder {
+            f: FolderNode {
+                guid: Some(guid.clone()),
+                date_added: None,
+                last_modified: None,
+                title: None,
+                children: vec![BookmarkTreeNode::Bookmark {
+                    b: BookmarkNode {
+                        guid: None,
+                        date_added: None,
+                        last_modified: None,
+                        title: Some("the bookmark".into()),
+                        url: Url::parse("https://www.example.com")?,
+                    },
+                }],
+            },
+        };
+        // round-trip the tree via serde.
+        let json = serde_json::to_string_pretty(&tree)?;
+        let deser: BookmarkTreeNode = serde_json::from_str(&json)?;
+        assert_eq!(tree, deser);
+        // and check against the simplest json repr of the tree, which checks
+        // our PartialEq implementation.
+        let jtree = json!({
+            "type": 2,
+            "guid": &guid,
+            "children" : [
+                {
+                    "type": 1,
+                    "title": "the bookmark",
+                    "url": "https://www.example.com/"
+                }
+            ]
+        });
+        let deser_tree: BookmarkTreeNode = serde_json::from_value(jtree).expect("should deser");
+        assert_eq!(tree, deser_tree);
+        Ok(())
+    }
+
+    #[test]
+    fn test_tree_invalid() {
+        let jtree = json!({
+            "type": 2,
+            "children" : [
+                {
+                    "type": 1,
+                    "title": "bookmark with invalid URL",
+                    "url": "invalid_url"
+                },
+                {
+                    "type": 1,
+                    "title": "bookmark with missing URL",
+                },
+                {
+                    "title": "bookmark with missing type, no URL",
+                },
+                {
+                    "title": "bookmark with missing type, valid URL",
+                    "url": "http://example.com"
+                },
+
+            ]
+        });
+        let deser_tree: BookmarkTreeNode = serde_json::from_value(jtree).expect("should deser");
+        let folder = match deser_tree {
+            BookmarkTreeNode::Folder { f } => f,
+            _ => panic!("must be a folder"),
+        };
+
+        let children = folder.children;
+        assert_eq!(children.len(), 4);
+
+        assert!(match &children[0] {
+            BookmarkTreeNode::Folder { f } =>
+                f.title == Some("bookmark with invalid URL".to_string()),
+            _ => false,
+        });
+        assert!(match &children[1] {
+            BookmarkTreeNode::Folder { f } =>
+                f.title == Some("bookmark with missing URL".to_string()),
+            _ => false,
+        });
+        assert!(match &children[2] {
+            BookmarkTreeNode::Folder { f } => {
+                f.title == Some("bookmark with missing type, no URL".to_string())
+            }
+            _ => false,
+        });
+        assert!(match &children[3] {
+            BookmarkTreeNode::Bookmark { b } => {
+                b.title == Some("bookmark with missing type, valid URL".to_string())
+            }
+            _ => false,
+        });
+    }
+}
+
+pub fn insert_tree(db: &PlacesDb, tree: FolderNode) -> Result<()> {
+    // This API is strange - we don't add `tree`, but just use it for the parent.
+    // It's only used for json importing, so we can live with a strange API :)
+    let parent = tree.guid.expect("inserting a tree without the root guid");
+    let tx = db.begin_transaction()?;
+    for child in tree.children {
+        let mut insertable: InsertableItem = child.into();
+        assert!(
+            insertable.parent_guid().is_empty(),
+            "can't specify a parent inserting a tree"
+        );
+        insertable.set_parent_guid(parent.clone());
+        crate::storage::bookmarks::insert_bookmark_in_tx(db, insertable)?;
+    }
+    crate::storage::delete_pending_temp_tables(db)?;
+    tx.commit()?;
+    Ok(())
+}
+
+fn inflate(
+    parent: &mut BookmarkTreeNode,
+    pseudo_tree: &mut HashMap<SyncGuid, Vec<BookmarkTreeNode>>,
+) {
+    if let BookmarkTreeNode::Folder { f: parent } = parent {
+        if let Some(children) = parent
+            .guid
+            .as_ref()
+            .and_then(|guid| pseudo_tree.remove(guid))
+        {
+            parent.children = children;
+            for child in &mut parent.children {
+                inflate(child, pseudo_tree);
+            }
+        }
+    }
+}
+
+#[derive(Debug)]
+struct FetchedTreeRow {
+    level: u32,
+    _id: RowId,
+    guid: SyncGuid,
+    // parent and parent_guid are Option<> only to handle the root - we would
+    // assert but they aren't currently used.
+    _parent: Option<RowId>,
+    parent_guid: Option<SyncGuid>,
+    node_type: BookmarkType,
+    position: u32,
+    title: Option<String>,
+    date_added: Timestamp,
+    last_modified: Timestamp,
+    url: Option<String>,
+}
+
+impl FetchedTreeRow {
+    pub fn from_row(row: &Row<'_>) -> Result<Self> {
+        let url = row.get::<_, Option<String>>("url")?;
+        Ok(Self {
+            level: row.get("level")?,
+            _id: row.get::<_, RowId>("id")?,
+            guid: row.get::<_, String>("guid")?.into(),
+            _parent: row.get::<_, Option<RowId>>("parent")?,
+            parent_guid: row
+                .get::<_, Option<String>>("parentGuid")?
+                .map(SyncGuid::from),
+            node_type: BookmarkType::from_u8_with_valid_url(row.get::<_, u8>("type")?, || {
+                url.is_some()
+            }),
+            position: row.get("position")?,
+            title: row.get::<_, Option<String>>("title")?,
+            date_added: row.get("dateAdded")?,
+            last_modified: row.get("lastModified")?,
+            url,
+        })
+    }
+}
+
+/// Fetch the tree starting at the specified guid.
+/// Returns a `BookmarkTreeNode`, its parent's guid (if any), and
+/// position inside its parent.
+pub enum FetchDepth {
+    Specific(usize),
+    Deepest,
+}
+
+pub fn fetch_tree(
+    db: &PlacesDb,
+    item_guid: &SyncGuid,
+    target_depth: &FetchDepth,
+) -> Result<Option<(BookmarkTreeNode, Option<SyncGuid>, u32)>> {
+    // XXX - this needs additional work for tags - unlike desktop, there's no
+    // "tags" folder, but instead a couple of tables to join on.
+    let sql = r#"
+        WITH RECURSIVE
+        descendants(fk, level, type, id, guid, parent, parentGuid, position,
+                    title, dateAdded, lastModified) AS (
+        SELECT b1.fk, 0, b1.type, b1.id, b1.guid, b1.parent,
+                (SELECT guid FROM moz_bookmarks WHERE id = b1.parent),
+                b1.position, b1.title, b1.dateAdded, b1.lastModified
+        FROM moz_bookmarks b1 WHERE b1.guid=:item_guid
+        UNION ALL
+        SELECT b2.fk, level + 1, b2.type, b2.id, b2.guid, b2.parent,
+                descendants.guid, b2.position, b2.title, b2.dateAdded,
+                b2.lastModified
+        FROM moz_bookmarks b2
+        JOIN descendants ON b2.parent = descendants.id) -- AND b2.id <> :tags_folder)
+        SELECT d.level, d.id, d.guid, d.parent, d.parentGuid, d.type,
+            d.position, NULLIF(d.title, '') AS title, d.dateAdded,
+            d.lastModified, h.url
+--               (SELECT icon_url FROM moz_icons i
+--                      JOIN moz_icons_to_pages ON icon_id = i.id
+--                      JOIN moz_pages_w_icons pi ON page_id = pi.id
+--                      WHERE pi.page_url_hash = hash(h.url) AND pi.page_url = h.url
+--                      ORDER BY width DESC LIMIT 1) AS iconuri,
+--               (SELECT GROUP_CONCAT(t.title, ',')
+--                FROM moz_bookmarks b2
+--                JOIN moz_bookmarks t ON t.id = +b2.parent AND t.parent = :tags_folder
+--                WHERE b2.fk = h.id
+--               ) AS tags,
+--               EXISTS (SELECT 1 FROM moz_items_annos
+--                       WHERE item_id = d.id LIMIT 1) AS has_annos,
+--               (SELECT a.content FROM moz_annos a
+--                JOIN moz_anno_attributes n ON a.anno_attribute_id = n.id
+--                WHERE place_id = h.id AND n.name = :charset_anno
+--               ) AS charset
+        FROM descendants d
+        LEFT JOIN moz_bookmarks b3 ON b3.id = d.parent
+        LEFT JOIN moz_places h ON h.id = d.fk
+        ORDER BY d.level, d.parent, d.position"#;
+
+    let scope = db.begin_interrupt_scope()?;
+
+    let mut stmt = db.conn().prepare(sql)?;
+
+    let mut results =
+        stmt.query_and_then(&[(":item_guid", item_guid)], FetchedTreeRow::from_row)?;
+
+    let parent_guid: Option<SyncGuid>;
+    let position: u32;
+
+    // The first row in the result set is always the root of our tree.
+    let mut root = match results.next() {
+        Some(result) => {
+            let row = result?;
+            parent_guid = row.parent_guid.clone();
+            position = row.position;
+            match row.node_type {
+                BookmarkType::Folder => FolderNode {
+                    guid: Some(row.guid.clone()),
+                    date_added: Some(row.date_added),
+                    last_modified: Some(row.last_modified),
+                    title: row.title,
+                    children: Vec::new(),
+                }
+                .into(),
+                BookmarkType::Bookmark => {
+                    // pretend invalid or missing URLs don't exist.
+                    match row.url {
+                        Some(str_val) => match Url::parse(str_val.as_str()) {
+                            // an invalid URL presumably means a logic error
+                            // somewhere far away from here...
+                            Err(_) => return Ok(None),
+                            Ok(url) => BookmarkNode {
+                                guid: Some(row.guid.clone()),
+                                date_added: Some(row.date_added),
+                                last_modified: Some(row.last_modified),
+                                title: row.title,
+                                url,
+                            }
+                            .into(),
+                        },
+                        // This is double-extra-invalid because various
+                        // constaints in the schema should prevent it (but we
+                        // know from desktop's experience that on-disk
+                        // corruption can cause it, so it's possible) - but
+                        // we treat it as an `error` rather than just a `warn`
+                        None => {
+                            error_support::report_error!(
+                                "places-bookmark-corruption",
+                                "bookmark {:#} has missing url",
+                                row.guid
+                            );
+                            return Ok(None);
+                        }
+                    }
+                }
+                BookmarkType::Separator => SeparatorNode {
+                    guid: Some(row.guid.clone()),
+                    date_added: Some(row.date_added),
+                    last_modified: Some(row.last_modified),
+                }
+                .into(),
+            }
+        }
+        None => return Ok(None),
+    };
+
+    // Skip the rest and return if root is not a folder
+    if let BookmarkTreeNode::Bookmark { .. } | BookmarkTreeNode::Separator { .. } = root {
+        return Ok(Some((root, parent_guid, position)));
+    }
+
+    scope.err_if_interrupted()?;
+    // For all remaining rows, build a pseudo-tree that maps parent GUIDs to
+    // ordered children. We need this intermediate step because SQLite returns
+    // results in level order, so we'll see a node's siblings and cousins (same
+    // level, but different parents) before any of their descendants.
+    let mut pseudo_tree: HashMap<SyncGuid, Vec<BookmarkTreeNode>> = HashMap::new();
+    for result in results {
+        let row = result?;
+        scope.err_if_interrupted()?;
+        // Check if we have done fetching the asked depth
+        if let FetchDepth::Specific(d) = *target_depth {
+            if row.level as usize > d + 1 {
+                break;
+            }
+        }
+        let node = match row.node_type {
+            BookmarkType::Bookmark => match &row.url {
+                Some(url_str) => match Url::parse(url_str) {
+                    Ok(url) => BookmarkNode {
+                        guid: Some(row.guid.clone()),
+                        date_added: Some(row.date_added),
+                        last_modified: Some(row.last_modified),
+                        title: row.title.clone(),
+                        url,
+                    }
+                    .into(),
+                    Err(e) => {
+                        log::warn!(
+                            "ignoring malformed bookmark {} - invalid URL: {:?}",
+                            row.guid,
+                            e
+                        );
+                        continue;
+                    }
+                },
+                None => {
+                    log::warn!("ignoring malformed bookmark {} - no URL", row.guid);
+                    continue;
+                }
+            },
+            BookmarkType::Separator => SeparatorNode {
+                guid: Some(row.guid.clone()),
+                date_added: Some(row.date_added),
+                last_modified: Some(row.last_modified),
+            }
+            .into(),
+            BookmarkType::Folder => FolderNode {
+                guid: Some(row.guid.clone()),
+                date_added: Some(row.date_added),
+                last_modified: Some(row.last_modified),
+                title: row.title.clone(),
+                children: Vec::new(),
+            }
+            .into(),
+        };
+        if let Some(parent_guid) = row.parent_guid.as_ref().cloned() {
+            let children = pseudo_tree.entry(parent_guid).or_default();
+            children.push(node);
+        }
+    }
+
+    // Finally, inflate our tree.
+    inflate(&mut root, &mut pseudo_tree);
+    Ok(Some((root, parent_guid, position)))
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::api::places_api::test::new_mem_connection;
+    use crate::storage::bookmarks::BookmarkRootGuid;
+    use crate::tests::{assert_json_tree, assert_json_tree_with_depth};
+    use serde_json::json;
+
+    // These tests check the SQL that this JSON module does "behind the back" of the
+    // main storage API.
+    #[test]
+    fn test_fetch_root() -> Result<()> {
+        let conn = new_mem_connection();
+
+        // Fetch the root
+        let (t, _, _) =
+            fetch_tree(&conn, &BookmarkRootGuid::Root.into(), &FetchDepth::Deepest)?.unwrap();
+        let f = match t {
+            BookmarkTreeNode::Folder { ref f } => f,
+            _ => panic!("tree root must be a folder"),
+        };
+        assert_eq!(f.guid, Some(BookmarkRootGuid::Root.into()));
+        assert_eq!(f.children.len(), 4);
+        Ok(())
+    }
+
+    #[test]
+    fn test_insert_tree_and_fetch_level() -> Result<()> {
+        let conn = new_mem_connection();
+
+        let tree = FolderNode {
+            guid: Some(BookmarkRootGuid::Unfiled.into()),
+            children: vec![
+                BookmarkNode {
+                    guid: None,
+                    date_added: None,
+                    last_modified: None,
+                    title: Some("the bookmark".into()),
+                    url: Url::parse("https://www.example.com")?,
+                }
+                .into(),
+                FolderNode {
+                    title: Some("A folder".into()),
+                    children: vec![
+                        BookmarkNode {
+                            guid: None,
+                            date_added: None,
+                            last_modified: None,
+                            title: Some("bookmark 1 in A folder".into()),
+                            url: Url::parse("https://www.example2.com")?,
+                        }
+                        .into(),
+                        BookmarkNode {
+                            guid: None,
+                            date_added: None,
+                            last_modified: None,
+                            title: Some("bookmark 2 in A folder".into()),
+                            url: Url::parse("https://www.example3.com")?,
+                        }
+                        .into(),
+                    ],
+                    ..Default::default()
+                }
+                .into(),
+                BookmarkNode {
+                    guid: None,
+                    date_added: None,
+                    last_modified: None,
+                    title: Some("another bookmark".into()),
+                    url: Url::parse("https://www.example4.com")?,
+                }
+                .into(),
+            ],
+            ..Default::default()
+        };
+        insert_tree(&conn, tree)?;
+
+        let expected = json!({
+            "guid": &BookmarkRootGuid::Unfiled.as_guid(),
+            "children": [
+                {
+                    "title": "the bookmark",
+                    "url": "https://www.example.com/"
+                },
+                {
+                    "title": "A folder",
+                    "children": [
+                        {
+                            "title": "bookmark 1 in A folder",
+                            "url": "https://www.example2.com/"
+                        },
+                        {
+                            "title": "bookmark 2 in A folder",
+                            "url": "https://www.example3.com/"
+                        }
+                    ],
+                },
+                {
+                    "title": "another bookmark",
+                    "url": "https://www.example4.com/",
+                }
+            ]
+        });
+        // check it with deepest fetching level.
+        assert_json_tree(&conn, &BookmarkRootGuid::Unfiled.into(), expected.clone());
+
+        // check it with one level deep, which should be the same as the previous
+        assert_json_tree_with_depth(
+            &conn,
+            &BookmarkRootGuid::Unfiled.into(),
+            expected,
+            &FetchDepth::Specific(1),
+        );
+
+        // check it with zero level deep, which should return root and its children only
+        assert_json_tree_with_depth(
+            &conn,
+            &BookmarkRootGuid::Unfiled.into(),
+            json!({
+                "guid": &BookmarkRootGuid::Unfiled.as_guid(),
+                "children": [
+                    {
+                        "title": "the bookmark",
+                        "url": "https://www.example.com/"
+                    },
+                    {
+                        "title": "A folder",
+                        "children": [],
+                    },
+                    {
+                        "title": "another bookmark",
+                        "url": "https://www.example4.com/",
+                    }
+                ]
+            }),
+            &FetchDepth::Specific(0),
+        );
+
+        Ok(())
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/places/storage/bookmarks/root_guid.rs.html b/book/rust-docs/src/places/storage/bookmarks/root_guid.rs.html new file mode 100644 index 0000000000..d9c0b1b43d --- /dev/null +++ b/book/rust-docs/src/places/storage/bookmarks/root_guid.rs.html @@ -0,0 +1,253 @@ +root_guid.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use lazy_static::lazy_static;
+use sync_guid::Guid as SyncGuid;
+
+pub const USER_CONTENT_ROOTS: &[BookmarkRootGuid] = &[
+    BookmarkRootGuid::Menu,
+    BookmarkRootGuid::Toolbar,
+    BookmarkRootGuid::Unfiled,
+    BookmarkRootGuid::Mobile,
+];
+
+/// Special GUIDs associated with bookmark roots.
+/// It's guaranteed that the roots will always have these guids.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Hash)]
+#[repr(u8)]
+pub enum BookmarkRootGuid {
+    Root,
+    Menu,
+    Toolbar,
+    Unfiled,
+    Mobile,
+}
+
+lazy_static! {
+    static ref GUIDS: [(BookmarkRootGuid, SyncGuid); 5] = [
+        (
+            BookmarkRootGuid::Root,
+            SyncGuid::from(BookmarkRootGuid::Root.as_str())
+        ),
+        (
+            BookmarkRootGuid::Menu,
+            SyncGuid::from(BookmarkRootGuid::Menu.as_str())
+        ),
+        (
+            BookmarkRootGuid::Toolbar,
+            SyncGuid::from(BookmarkRootGuid::Toolbar.as_str())
+        ),
+        (
+            BookmarkRootGuid::Unfiled,
+            SyncGuid::from(BookmarkRootGuid::Unfiled.as_str())
+        ),
+        (
+            BookmarkRootGuid::Mobile,
+            SyncGuid::from(BookmarkRootGuid::Mobile.as_str())
+        ),
+    ];
+}
+
+impl BookmarkRootGuid {
+    pub fn as_str(self) -> &'static str {
+        match self {
+            BookmarkRootGuid::Root => "root________",
+            BookmarkRootGuid::Menu => "menu________",
+            BookmarkRootGuid::Toolbar => "toolbar_____",
+            BookmarkRootGuid::Unfiled => "unfiled_____",
+            BookmarkRootGuid::Mobile => "mobile______",
+        }
+    }
+
+    pub fn guid(self) -> &'static SyncGuid {
+        &GUIDS[self as usize].1
+    }
+
+    pub fn as_guid(self) -> SyncGuid {
+        self.guid().clone()
+    }
+
+    pub fn well_known(guid: &str) -> Option<Self> {
+        GUIDS
+            .iter()
+            .find(|(_, sync_guid)| sync_guid.as_str() == guid)
+            .map(|(root, _)| *root)
+    }
+
+    pub fn from_guid(guid: &SyncGuid) -> Option<Self> {
+        Self::well_known(guid.as_ref())
+    }
+}
+
+impl From<BookmarkRootGuid> for SyncGuid {
+    fn from(item: BookmarkRootGuid) -> SyncGuid {
+        item.as_guid()
+    }
+}
+
+// Allow comparisons between BookmarkRootGuid and SyncGuids
+impl PartialEq<BookmarkRootGuid> for SyncGuid {
+    fn eq(&self, other: &BookmarkRootGuid) -> bool {
+        self.as_str().as_bytes() == other.as_str().as_bytes()
+    }
+}
+
+impl PartialEq<SyncGuid> for BookmarkRootGuid {
+    fn eq(&self, other: &SyncGuid) -> bool {
+        other.as_str().as_bytes() == self.as_str().as_bytes()
+    }
+}
+
+// Even if we have a reference to &SyncGuid
+impl<'a> PartialEq<BookmarkRootGuid> for &'a SyncGuid {
+    fn eq(&self, other: &BookmarkRootGuid) -> bool {
+        self.as_str().as_bytes() == other.as_str().as_bytes()
+    }
+}
+
+impl<'a> PartialEq<&'a SyncGuid> for BookmarkRootGuid {
+    fn eq(&self, other: &&'a SyncGuid) -> bool {
+        other.as_str().as_bytes() == self.as_str().as_bytes()
+    }
+}
+
+// And between BookmarkRootGuid and &str
+impl<'a> PartialEq<BookmarkRootGuid> for &'a str {
+    fn eq(&self, other: &BookmarkRootGuid) -> bool {
+        *self == other.as_str()
+    }
+}
+
+impl<'a> PartialEq<&'a str> for BookmarkRootGuid {
+    fn eq(&self, other: &&'a str) -> bool {
+        self.as_str() == *other
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/places/storage/history.rs.html b/book/rust-docs/src/places/storage/history.rs.html new file mode 100644 index 0000000000..5e6d43c05f --- /dev/null +++ b/book/rust-docs/src/places/storage/history.rs.html @@ -0,0 +1,6753 @@ +history.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
+540
+541
+542
+543
+544
+545
+546
+547
+548
+549
+550
+551
+552
+553
+554
+555
+556
+557
+558
+559
+560
+561
+562
+563
+564
+565
+566
+567
+568
+569
+570
+571
+572
+573
+574
+575
+576
+577
+578
+579
+580
+581
+582
+583
+584
+585
+586
+587
+588
+589
+590
+591
+592
+593
+594
+595
+596
+597
+598
+599
+600
+601
+602
+603
+604
+605
+606
+607
+608
+609
+610
+611
+612
+613
+614
+615
+616
+617
+618
+619
+620
+621
+622
+623
+624
+625
+626
+627
+628
+629
+630
+631
+632
+633
+634
+635
+636
+637
+638
+639
+640
+641
+642
+643
+644
+645
+646
+647
+648
+649
+650
+651
+652
+653
+654
+655
+656
+657
+658
+659
+660
+661
+662
+663
+664
+665
+666
+667
+668
+669
+670
+671
+672
+673
+674
+675
+676
+677
+678
+679
+680
+681
+682
+683
+684
+685
+686
+687
+688
+689
+690
+691
+692
+693
+694
+695
+696
+697
+698
+699
+700
+701
+702
+703
+704
+705
+706
+707
+708
+709
+710
+711
+712
+713
+714
+715
+716
+717
+718
+719
+720
+721
+722
+723
+724
+725
+726
+727
+728
+729
+730
+731
+732
+733
+734
+735
+736
+737
+738
+739
+740
+741
+742
+743
+744
+745
+746
+747
+748
+749
+750
+751
+752
+753
+754
+755
+756
+757
+758
+759
+760
+761
+762
+763
+764
+765
+766
+767
+768
+769
+770
+771
+772
+773
+774
+775
+776
+777
+778
+779
+780
+781
+782
+783
+784
+785
+786
+787
+788
+789
+790
+791
+792
+793
+794
+795
+796
+797
+798
+799
+800
+801
+802
+803
+804
+805
+806
+807
+808
+809
+810
+811
+812
+813
+814
+815
+816
+817
+818
+819
+820
+821
+822
+823
+824
+825
+826
+827
+828
+829
+830
+831
+832
+833
+834
+835
+836
+837
+838
+839
+840
+841
+842
+843
+844
+845
+846
+847
+848
+849
+850
+851
+852
+853
+854
+855
+856
+857
+858
+859
+860
+861
+862
+863
+864
+865
+866
+867
+868
+869
+870
+871
+872
+873
+874
+875
+876
+877
+878
+879
+880
+881
+882
+883
+884
+885
+886
+887
+888
+889
+890
+891
+892
+893
+894
+895
+896
+897
+898
+899
+900
+901
+902
+903
+904
+905
+906
+907
+908
+909
+910
+911
+912
+913
+914
+915
+916
+917
+918
+919
+920
+921
+922
+923
+924
+925
+926
+927
+928
+929
+930
+931
+932
+933
+934
+935
+936
+937
+938
+939
+940
+941
+942
+943
+944
+945
+946
+947
+948
+949
+950
+951
+952
+953
+954
+955
+956
+957
+958
+959
+960
+961
+962
+963
+964
+965
+966
+967
+968
+969
+970
+971
+972
+973
+974
+975
+976
+977
+978
+979
+980
+981
+982
+983
+984
+985
+986
+987
+988
+989
+990
+991
+992
+993
+994
+995
+996
+997
+998
+999
+1000
+1001
+1002
+1003
+1004
+1005
+1006
+1007
+1008
+1009
+1010
+1011
+1012
+1013
+1014
+1015
+1016
+1017
+1018
+1019
+1020
+1021
+1022
+1023
+1024
+1025
+1026
+1027
+1028
+1029
+1030
+1031
+1032
+1033
+1034
+1035
+1036
+1037
+1038
+1039
+1040
+1041
+1042
+1043
+1044
+1045
+1046
+1047
+1048
+1049
+1050
+1051
+1052
+1053
+1054
+1055
+1056
+1057
+1058
+1059
+1060
+1061
+1062
+1063
+1064
+1065
+1066
+1067
+1068
+1069
+1070
+1071
+1072
+1073
+1074
+1075
+1076
+1077
+1078
+1079
+1080
+1081
+1082
+1083
+1084
+1085
+1086
+1087
+1088
+1089
+1090
+1091
+1092
+1093
+1094
+1095
+1096
+1097
+1098
+1099
+1100
+1101
+1102
+1103
+1104
+1105
+1106
+1107
+1108
+1109
+1110
+1111
+1112
+1113
+1114
+1115
+1116
+1117
+1118
+1119
+1120
+1121
+1122
+1123
+1124
+1125
+1126
+1127
+1128
+1129
+1130
+1131
+1132
+1133
+1134
+1135
+1136
+1137
+1138
+1139
+1140
+1141
+1142
+1143
+1144
+1145
+1146
+1147
+1148
+1149
+1150
+1151
+1152
+1153
+1154
+1155
+1156
+1157
+1158
+1159
+1160
+1161
+1162
+1163
+1164
+1165
+1166
+1167
+1168
+1169
+1170
+1171
+1172
+1173
+1174
+1175
+1176
+1177
+1178
+1179
+1180
+1181
+1182
+1183
+1184
+1185
+1186
+1187
+1188
+1189
+1190
+1191
+1192
+1193
+1194
+1195
+1196
+1197
+1198
+1199
+1200
+1201
+1202
+1203
+1204
+1205
+1206
+1207
+1208
+1209
+1210
+1211
+1212
+1213
+1214
+1215
+1216
+1217
+1218
+1219
+1220
+1221
+1222
+1223
+1224
+1225
+1226
+1227
+1228
+1229
+1230
+1231
+1232
+1233
+1234
+1235
+1236
+1237
+1238
+1239
+1240
+1241
+1242
+1243
+1244
+1245
+1246
+1247
+1248
+1249
+1250
+1251
+1252
+1253
+1254
+1255
+1256
+1257
+1258
+1259
+1260
+1261
+1262
+1263
+1264
+1265
+1266
+1267
+1268
+1269
+1270
+1271
+1272
+1273
+1274
+1275
+1276
+1277
+1278
+1279
+1280
+1281
+1282
+1283
+1284
+1285
+1286
+1287
+1288
+1289
+1290
+1291
+1292
+1293
+1294
+1295
+1296
+1297
+1298
+1299
+1300
+1301
+1302
+1303
+1304
+1305
+1306
+1307
+1308
+1309
+1310
+1311
+1312
+1313
+1314
+1315
+1316
+1317
+1318
+1319
+1320
+1321
+1322
+1323
+1324
+1325
+1326
+1327
+1328
+1329
+1330
+1331
+1332
+1333
+1334
+1335
+1336
+1337
+1338
+1339
+1340
+1341
+1342
+1343
+1344
+1345
+1346
+1347
+1348
+1349
+1350
+1351
+1352
+1353
+1354
+1355
+1356
+1357
+1358
+1359
+1360
+1361
+1362
+1363
+1364
+1365
+1366
+1367
+1368
+1369
+1370
+1371
+1372
+1373
+1374
+1375
+1376
+1377
+1378
+1379
+1380
+1381
+1382
+1383
+1384
+1385
+1386
+1387
+1388
+1389
+1390
+1391
+1392
+1393
+1394
+1395
+1396
+1397
+1398
+1399
+1400
+1401
+1402
+1403
+1404
+1405
+1406
+1407
+1408
+1409
+1410
+1411
+1412
+1413
+1414
+1415
+1416
+1417
+1418
+1419
+1420
+1421
+1422
+1423
+1424
+1425
+1426
+1427
+1428
+1429
+1430
+1431
+1432
+1433
+1434
+1435
+1436
+1437
+1438
+1439
+1440
+1441
+1442
+1443
+1444
+1445
+1446
+1447
+1448
+1449
+1450
+1451
+1452
+1453
+1454
+1455
+1456
+1457
+1458
+1459
+1460
+1461
+1462
+1463
+1464
+1465
+1466
+1467
+1468
+1469
+1470
+1471
+1472
+1473
+1474
+1475
+1476
+1477
+1478
+1479
+1480
+1481
+1482
+1483
+1484
+1485
+1486
+1487
+1488
+1489
+1490
+1491
+1492
+1493
+1494
+1495
+1496
+1497
+1498
+1499
+1500
+1501
+1502
+1503
+1504
+1505
+1506
+1507
+1508
+1509
+1510
+1511
+1512
+1513
+1514
+1515
+1516
+1517
+1518
+1519
+1520
+1521
+1522
+1523
+1524
+1525
+1526
+1527
+1528
+1529
+1530
+1531
+1532
+1533
+1534
+1535
+1536
+1537
+1538
+1539
+1540
+1541
+1542
+1543
+1544
+1545
+1546
+1547
+1548
+1549
+1550
+1551
+1552
+1553
+1554
+1555
+1556
+1557
+1558
+1559
+1560
+1561
+1562
+1563
+1564
+1565
+1566
+1567
+1568
+1569
+1570
+1571
+1572
+1573
+1574
+1575
+1576
+1577
+1578
+1579
+1580
+1581
+1582
+1583
+1584
+1585
+1586
+1587
+1588
+1589
+1590
+1591
+1592
+1593
+1594
+1595
+1596
+1597
+1598
+1599
+1600
+1601
+1602
+1603
+1604
+1605
+1606
+1607
+1608
+1609
+1610
+1611
+1612
+1613
+1614
+1615
+1616
+1617
+1618
+1619
+1620
+1621
+1622
+1623
+1624
+1625
+1626
+1627
+1628
+1629
+1630
+1631
+1632
+1633
+1634
+1635
+1636
+1637
+1638
+1639
+1640
+1641
+1642
+1643
+1644
+1645
+1646
+1647
+1648
+1649
+1650
+1651
+1652
+1653
+1654
+1655
+1656
+1657
+1658
+1659
+1660
+1661
+1662
+1663
+1664
+1665
+1666
+1667
+1668
+1669
+1670
+1671
+1672
+1673
+1674
+1675
+1676
+1677
+1678
+1679
+1680
+1681
+1682
+1683
+1684
+1685
+1686
+1687
+1688
+1689
+1690
+1691
+1692
+1693
+1694
+1695
+1696
+1697
+1698
+1699
+1700
+1701
+1702
+1703
+1704
+1705
+1706
+1707
+1708
+1709
+1710
+1711
+1712
+1713
+1714
+1715
+1716
+1717
+1718
+1719
+1720
+1721
+1722
+1723
+1724
+1725
+1726
+1727
+1728
+1729
+1730
+1731
+1732
+1733
+1734
+1735
+1736
+1737
+1738
+1739
+1740
+1741
+1742
+1743
+1744
+1745
+1746
+1747
+1748
+1749
+1750
+1751
+1752
+1753
+1754
+1755
+1756
+1757
+1758
+1759
+1760
+1761
+1762
+1763
+1764
+1765
+1766
+1767
+1768
+1769
+1770
+1771
+1772
+1773
+1774
+1775
+1776
+1777
+1778
+1779
+1780
+1781
+1782
+1783
+1784
+1785
+1786
+1787
+1788
+1789
+1790
+1791
+1792
+1793
+1794
+1795
+1796
+1797
+1798
+1799
+1800
+1801
+1802
+1803
+1804
+1805
+1806
+1807
+1808
+1809
+1810
+1811
+1812
+1813
+1814
+1815
+1816
+1817
+1818
+1819
+1820
+1821
+1822
+1823
+1824
+1825
+1826
+1827
+1828
+1829
+1830
+1831
+1832
+1833
+1834
+1835
+1836
+1837
+1838
+1839
+1840
+1841
+1842
+1843
+1844
+1845
+1846
+1847
+1848
+1849
+1850
+1851
+1852
+1853
+1854
+1855
+1856
+1857
+1858
+1859
+1860
+1861
+1862
+1863
+1864
+1865
+1866
+1867
+1868
+1869
+1870
+1871
+1872
+1873
+1874
+1875
+1876
+1877
+1878
+1879
+1880
+1881
+1882
+1883
+1884
+1885
+1886
+1887
+1888
+1889
+1890
+1891
+1892
+1893
+1894
+1895
+1896
+1897
+1898
+1899
+1900
+1901
+1902
+1903
+1904
+1905
+1906
+1907
+1908
+1909
+1910
+1911
+1912
+1913
+1914
+1915
+1916
+1917
+1918
+1919
+1920
+1921
+1922
+1923
+1924
+1925
+1926
+1927
+1928
+1929
+1930
+1931
+1932
+1933
+1934
+1935
+1936
+1937
+1938
+1939
+1940
+1941
+1942
+1943
+1944
+1945
+1946
+1947
+1948
+1949
+1950
+1951
+1952
+1953
+1954
+1955
+1956
+1957
+1958
+1959
+1960
+1961
+1962
+1963
+1964
+1965
+1966
+1967
+1968
+1969
+1970
+1971
+1972
+1973
+1974
+1975
+1976
+1977
+1978
+1979
+1980
+1981
+1982
+1983
+1984
+1985
+1986
+1987
+1988
+1989
+1990
+1991
+1992
+1993
+1994
+1995
+1996
+1997
+1998
+1999
+2000
+2001
+2002
+2003
+2004
+2005
+2006
+2007
+2008
+2009
+2010
+2011
+2012
+2013
+2014
+2015
+2016
+2017
+2018
+2019
+2020
+2021
+2022
+2023
+2024
+2025
+2026
+2027
+2028
+2029
+2030
+2031
+2032
+2033
+2034
+2035
+2036
+2037
+2038
+2039
+2040
+2041
+2042
+2043
+2044
+2045
+2046
+2047
+2048
+2049
+2050
+2051
+2052
+2053
+2054
+2055
+2056
+2057
+2058
+2059
+2060
+2061
+2062
+2063
+2064
+2065
+2066
+2067
+2068
+2069
+2070
+2071
+2072
+2073
+2074
+2075
+2076
+2077
+2078
+2079
+2080
+2081
+2082
+2083
+2084
+2085
+2086
+2087
+2088
+2089
+2090
+2091
+2092
+2093
+2094
+2095
+2096
+2097
+2098
+2099
+2100
+2101
+2102
+2103
+2104
+2105
+2106
+2107
+2108
+2109
+2110
+2111
+2112
+2113
+2114
+2115
+2116
+2117
+2118
+2119
+2120
+2121
+2122
+2123
+2124
+2125
+2126
+2127
+2128
+2129
+2130
+2131
+2132
+2133
+2134
+2135
+2136
+2137
+2138
+2139
+2140
+2141
+2142
+2143
+2144
+2145
+2146
+2147
+2148
+2149
+2150
+2151
+2152
+2153
+2154
+2155
+2156
+2157
+2158
+2159
+2160
+2161
+2162
+2163
+2164
+2165
+2166
+2167
+2168
+2169
+2170
+2171
+2172
+2173
+2174
+2175
+2176
+2177
+2178
+2179
+2180
+2181
+2182
+2183
+2184
+2185
+2186
+2187
+2188
+2189
+2190
+2191
+2192
+2193
+2194
+2195
+2196
+2197
+2198
+2199
+2200
+2201
+2202
+2203
+2204
+2205
+2206
+2207
+2208
+2209
+2210
+2211
+2212
+2213
+2214
+2215
+2216
+2217
+2218
+2219
+2220
+2221
+2222
+2223
+2224
+2225
+2226
+2227
+2228
+2229
+2230
+2231
+2232
+2233
+2234
+2235
+2236
+2237
+2238
+2239
+2240
+2241
+2242
+2243
+2244
+2245
+2246
+2247
+2248
+2249
+2250
+2251
+2252
+2253
+2254
+2255
+2256
+2257
+2258
+2259
+2260
+2261
+2262
+2263
+2264
+2265
+2266
+2267
+2268
+2269
+2270
+2271
+2272
+2273
+2274
+2275
+2276
+2277
+2278
+2279
+2280
+2281
+2282
+2283
+2284
+2285
+2286
+2287
+2288
+2289
+2290
+2291
+2292
+2293
+2294
+2295
+2296
+2297
+2298
+2299
+2300
+2301
+2302
+2303
+2304
+2305
+2306
+2307
+2308
+2309
+2310
+2311
+2312
+2313
+2314
+2315
+2316
+2317
+2318
+2319
+2320
+2321
+2322
+2323
+2324
+2325
+2326
+2327
+2328
+2329
+2330
+2331
+2332
+2333
+2334
+2335
+2336
+2337
+2338
+2339
+2340
+2341
+2342
+2343
+2344
+2345
+2346
+2347
+2348
+2349
+2350
+2351
+2352
+2353
+2354
+2355
+2356
+2357
+2358
+2359
+2360
+2361
+2362
+2363
+2364
+2365
+2366
+2367
+2368
+2369
+2370
+2371
+2372
+2373
+2374
+2375
+2376
+2377
+2378
+2379
+2380
+2381
+2382
+2383
+2384
+2385
+2386
+2387
+2388
+2389
+2390
+2391
+2392
+2393
+2394
+2395
+2396
+2397
+2398
+2399
+2400
+2401
+2402
+2403
+2404
+2405
+2406
+2407
+2408
+2409
+2410
+2411
+2412
+2413
+2414
+2415
+2416
+2417
+2418
+2419
+2420
+2421
+2422
+2423
+2424
+2425
+2426
+2427
+2428
+2429
+2430
+2431
+2432
+2433
+2434
+2435
+2436
+2437
+2438
+2439
+2440
+2441
+2442
+2443
+2444
+2445
+2446
+2447
+2448
+2449
+2450
+2451
+2452
+2453
+2454
+2455
+2456
+2457
+2458
+2459
+2460
+2461
+2462
+2463
+2464
+2465
+2466
+2467
+2468
+2469
+2470
+2471
+2472
+2473
+2474
+2475
+2476
+2477
+2478
+2479
+2480
+2481
+2482
+2483
+2484
+2485
+2486
+2487
+2488
+2489
+2490
+2491
+2492
+2493
+2494
+2495
+2496
+2497
+2498
+2499
+2500
+2501
+2502
+2503
+2504
+2505
+2506
+2507
+2508
+2509
+2510
+2511
+2512
+2513
+2514
+2515
+2516
+2517
+2518
+2519
+2520
+2521
+2522
+2523
+2524
+2525
+2526
+2527
+2528
+2529
+2530
+2531
+2532
+2533
+2534
+2535
+2536
+2537
+2538
+2539
+2540
+2541
+2542
+2543
+2544
+2545
+2546
+2547
+2548
+2549
+2550
+2551
+2552
+2553
+2554
+2555
+2556
+2557
+2558
+2559
+2560
+2561
+2562
+2563
+2564
+2565
+2566
+2567
+2568
+2569
+2570
+2571
+2572
+2573
+2574
+2575
+2576
+2577
+2578
+2579
+2580
+2581
+2582
+2583
+2584
+2585
+2586
+2587
+2588
+2589
+2590
+2591
+2592
+2593
+2594
+2595
+2596
+2597
+2598
+2599
+2600
+2601
+2602
+2603
+2604
+2605
+2606
+2607
+2608
+2609
+2610
+2611
+2612
+2613
+2614
+2615
+2616
+2617
+2618
+2619
+2620
+2621
+2622
+2623
+2624
+2625
+2626
+2627
+2628
+2629
+2630
+2631
+2632
+2633
+2634
+2635
+2636
+2637
+2638
+2639
+2640
+2641
+2642
+2643
+2644
+2645
+2646
+2647
+2648
+2649
+2650
+2651
+2652
+2653
+2654
+2655
+2656
+2657
+2658
+2659
+2660
+2661
+2662
+2663
+2664
+2665
+2666
+2667
+2668
+2669
+2670
+2671
+2672
+2673
+2674
+2675
+2676
+2677
+2678
+2679
+2680
+2681
+2682
+2683
+2684
+2685
+2686
+2687
+2688
+2689
+2690
+2691
+2692
+2693
+2694
+2695
+2696
+2697
+2698
+2699
+2700
+2701
+2702
+2703
+2704
+2705
+2706
+2707
+2708
+2709
+2710
+2711
+2712
+2713
+2714
+2715
+2716
+2717
+2718
+2719
+2720
+2721
+2722
+2723
+2724
+2725
+2726
+2727
+2728
+2729
+2730
+2731
+2732
+2733
+2734
+2735
+2736
+2737
+2738
+2739
+2740
+2741
+2742
+2743
+2744
+2745
+2746
+2747
+2748
+2749
+2750
+2751
+2752
+2753
+2754
+2755
+2756
+2757
+2758
+2759
+2760
+2761
+2762
+2763
+2764
+2765
+2766
+2767
+2768
+2769
+2770
+2771
+2772
+2773
+2774
+2775
+2776
+2777
+2778
+2779
+2780
+2781
+2782
+2783
+2784
+2785
+2786
+2787
+2788
+2789
+2790
+2791
+2792
+2793
+2794
+2795
+2796
+2797
+2798
+2799
+2800
+2801
+2802
+2803
+2804
+2805
+2806
+2807
+2808
+2809
+2810
+2811
+2812
+2813
+2814
+2815
+2816
+2817
+2818
+2819
+2820
+2821
+2822
+2823
+2824
+2825
+2826
+2827
+2828
+2829
+2830
+2831
+2832
+2833
+2834
+2835
+2836
+2837
+2838
+2839
+2840
+2841
+2842
+2843
+2844
+2845
+2846
+2847
+2848
+2849
+2850
+2851
+2852
+2853
+2854
+2855
+2856
+2857
+2858
+2859
+2860
+2861
+2862
+2863
+2864
+2865
+2866
+2867
+2868
+2869
+2870
+2871
+2872
+2873
+2874
+2875
+2876
+2877
+2878
+2879
+2880
+2881
+2882
+2883
+2884
+2885
+2886
+2887
+2888
+2889
+2890
+2891
+2892
+2893
+2894
+2895
+2896
+2897
+2898
+2899
+2900
+2901
+2902
+2903
+2904
+2905
+2906
+2907
+2908
+2909
+2910
+2911
+2912
+2913
+2914
+2915
+2916
+2917
+2918
+2919
+2920
+2921
+2922
+2923
+2924
+2925
+2926
+2927
+2928
+2929
+2930
+2931
+2932
+2933
+2934
+2935
+2936
+2937
+2938
+2939
+2940
+2941
+2942
+2943
+2944
+2945
+2946
+2947
+2948
+2949
+2950
+2951
+2952
+2953
+2954
+2955
+2956
+2957
+2958
+2959
+2960
+2961
+2962
+2963
+2964
+2965
+2966
+2967
+2968
+2969
+2970
+2971
+2972
+2973
+2974
+2975
+2976
+2977
+2978
+2979
+2980
+2981
+2982
+2983
+2984
+2985
+2986
+2987
+2988
+2989
+2990
+2991
+2992
+2993
+2994
+2995
+2996
+2997
+2998
+2999
+3000
+3001
+3002
+3003
+3004
+3005
+3006
+3007
+3008
+3009
+3010
+3011
+3012
+3013
+3014
+3015
+3016
+3017
+3018
+3019
+3020
+3021
+3022
+3023
+3024
+3025
+3026
+3027
+3028
+3029
+3030
+3031
+3032
+3033
+3034
+3035
+3036
+3037
+3038
+3039
+3040
+3041
+3042
+3043
+3044
+3045
+3046
+3047
+3048
+3049
+3050
+3051
+3052
+3053
+3054
+3055
+3056
+3057
+3058
+3059
+3060
+3061
+3062
+3063
+3064
+3065
+3066
+3067
+3068
+3069
+3070
+3071
+3072
+3073
+3074
+3075
+3076
+3077
+3078
+3079
+3080
+3081
+3082
+3083
+3084
+3085
+3086
+3087
+3088
+3089
+3090
+3091
+3092
+3093
+3094
+3095
+3096
+3097
+3098
+3099
+3100
+3101
+3102
+3103
+3104
+3105
+3106
+3107
+3108
+3109
+3110
+3111
+3112
+3113
+3114
+3115
+3116
+3117
+3118
+3119
+3120
+3121
+3122
+3123
+3124
+3125
+3126
+3127
+3128
+3129
+3130
+3131
+3132
+3133
+3134
+3135
+3136
+3137
+3138
+3139
+3140
+3141
+3142
+3143
+3144
+3145
+3146
+3147
+3148
+3149
+3150
+3151
+3152
+3153
+3154
+3155
+3156
+3157
+3158
+3159
+3160
+3161
+3162
+3163
+3164
+3165
+3166
+3167
+3168
+3169
+3170
+3171
+3172
+3173
+3174
+3175
+3176
+3177
+3178
+3179
+3180
+3181
+3182
+3183
+3184
+3185
+3186
+3187
+3188
+3189
+3190
+3191
+3192
+3193
+3194
+3195
+3196
+3197
+3198
+3199
+3200
+3201
+3202
+3203
+3204
+3205
+3206
+3207
+3208
+3209
+3210
+3211
+3212
+3213
+3214
+3215
+3216
+3217
+3218
+3219
+3220
+3221
+3222
+3223
+3224
+3225
+3226
+3227
+3228
+3229
+3230
+3231
+3232
+3233
+3234
+3235
+3236
+3237
+3238
+3239
+3240
+3241
+3242
+3243
+3244
+3245
+3246
+3247
+3248
+3249
+3250
+3251
+3252
+3253
+3254
+3255
+3256
+3257
+3258
+3259
+3260
+3261
+3262
+3263
+3264
+3265
+3266
+3267
+3268
+3269
+3270
+3271
+3272
+3273
+3274
+3275
+3276
+3277
+3278
+3279
+3280
+3281
+3282
+3283
+3284
+3285
+3286
+3287
+3288
+3289
+3290
+3291
+3292
+3293
+3294
+3295
+3296
+3297
+3298
+3299
+3300
+3301
+3302
+3303
+3304
+3305
+3306
+3307
+3308
+3309
+3310
+3311
+3312
+3313
+3314
+3315
+3316
+3317
+3318
+3319
+3320
+3321
+3322
+3323
+3324
+3325
+3326
+3327
+3328
+3329
+3330
+3331
+3332
+3333
+3334
+3335
+3336
+3337
+3338
+3339
+3340
+3341
+3342
+3343
+3344
+3345
+3346
+3347
+3348
+3349
+3350
+3351
+3352
+3353
+3354
+3355
+3356
+3357
+3358
+3359
+3360
+3361
+3362
+3363
+3364
+3365
+3366
+3367
+3368
+3369
+3370
+3371
+3372
+3373
+3374
+3375
+3376
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+mod actions;
+
+use super::{fetch_page_info, new_page_info, PageInfo, RowId};
+use crate::db::PlacesDb;
+use crate::error::Result;
+use crate::ffi::{HistoryVisitInfo, HistoryVisitInfosWithBound, TopFrecentSiteInfo};
+use crate::frecency;
+use crate::hash;
+use crate::history_sync::engine::{
+    COLLECTION_SYNCID_META_KEY, GLOBAL_SYNCID_META_KEY, LAST_SYNC_META_KEY,
+};
+use crate::observation::VisitObservation;
+use crate::storage::{
+    delete_meta, delete_pending_temp_tables, get_meta, history_metadata, put_meta,
+};
+use crate::types::{
+    serialize_unknown_fields, SyncStatus, UnknownFields, VisitTransitionSet, VisitType,
+};
+use actions::*;
+use rusqlite::types::ToSql;
+use rusqlite::Result as RusqliteResult;
+use rusqlite::Row;
+use sql_support::{self, ConnExt};
+use std::collections::HashSet;
+use std::time::Duration;
+use sync15::bso::OutgoingBso;
+use sync15::engine::EngineSyncAssociation;
+use sync_guid::Guid as SyncGuid;
+use types::Timestamp;
+use url::Url;
+
+/// When `delete_everything` is called (to perform a permanent local deletion), in
+/// addition to performing the deletion as requested, we make a note of the time
+/// when it occurred, and refuse to sync incoming visits from before this time.
+///
+/// This allows us to avoid these visits trickling back in as other devices
+/// add visits to them remotely.
+static DELETION_HIGH_WATER_MARK_META_KEY: &str = "history_deleted_hwm";
+
+/// Returns the RowId of a new visit in moz_historyvisits, or None if no new visit was added.
+pub fn apply_observation(db: &PlacesDb, visit_ob: VisitObservation) -> Result<Option<RowId>> {
+    let tx = db.begin_transaction()?;
+    let result = apply_observation_direct(db, visit_ob)?;
+    delete_pending_temp_tables(db)?;
+    tx.commit()?;
+    Ok(result)
+}
+
+/// Returns the RowId of a new visit in moz_historyvisits, or None if no new visit was added.
+pub fn apply_observation_direct(
+    db: &PlacesDb,
+    visit_ob: VisitObservation,
+) -> Result<Option<RowId>> {
+    // Don't insert urls larger than our length max.
+    if visit_ob.url.as_str().len() > super::URL_LENGTH_MAX {
+        return Ok(None);
+    }
+    // Make sure we have a valid preview URL - it should parse, and not exceed max size.
+    // In case the URL is too long, ignore it and proceed with the rest of the observation.
+    // In case the URL is entirely invalid, let the caller know by failing.
+    let preview_image_url = if let Some(ref piu) = visit_ob.preview_image_url {
+        if piu.as_str().len() > super::URL_LENGTH_MAX {
+            None
+        } else {
+            Some(piu.clone())
+        }
+    } else {
+        None
+    };
+    let mut page_info = match fetch_page_info(db, &visit_ob.url)? {
+        Some(info) => info.page,
+        None => new_page_info(db, &visit_ob.url, None)?,
+    };
+    let mut update_change_counter = false;
+    let mut update_frec = false;
+    let mut updates: Vec<(&str, &str, &dyn ToSql)> = Vec::new();
+
+    if let Some(ref title) = visit_ob.title {
+        page_info.title = crate::util::slice_up_to(title, super::TITLE_LENGTH_MAX).into();
+        updates.push(("title", ":title", &page_info.title));
+        update_change_counter = true;
+    }
+    let preview_image_url_str;
+    if let Some(ref preview_image_url) = preview_image_url {
+        preview_image_url_str = preview_image_url.as_str();
+        updates.push((
+            "preview_image_url",
+            ":preview_image_url",
+            &preview_image_url_str,
+        ));
+    }
+    // There's a new visit, so update everything that implies. To help with
+    // testing we return the rowid of the visit we added.
+    let visit_row_id = match visit_ob.visit_type {
+        Some(visit_type) => {
+            // A single non-hidden visit makes the place non-hidden.
+            if !visit_ob.get_is_hidden() {
+                updates.push(("hidden", ":hidden", &false));
+            }
+            if visit_type == VisitType::Typed {
+                page_info.typed += 1;
+                updates.push(("typed", ":typed", &page_info.typed));
+            }
+
+            let at = visit_ob.at.unwrap_or_else(Timestamp::now);
+            let is_remote = visit_ob.is_remote.unwrap_or(false);
+            let row_id = add_visit(db, page_info.row_id, None, at, visit_type, !is_remote, None)?;
+            // a new visit implies new frecency except in error cases.
+            if !visit_ob.is_error.unwrap_or(false) {
+                update_frec = true;
+            }
+            update_change_counter = true;
+            Some(row_id)
+        }
+        None => None,
+    };
+
+    if update_change_counter {
+        page_info.sync_change_counter += 1;
+        updates.push((
+            "sync_change_counter",
+            ":sync_change_counter",
+            &page_info.sync_change_counter,
+        ));
+    }
+
+    if !updates.is_empty() {
+        let mut params: Vec<(&str, &dyn ToSql)> = Vec::with_capacity(updates.len() + 1);
+        let mut sets: Vec<String> = Vec::with_capacity(updates.len());
+        for (col, name, val) in updates {
+            sets.push(format!("{} = {}", col, name));
+            params.push((name, val))
+        }
+        params.push((":row_id", &page_info.row_id.0));
+        let sql = format!(
+            "UPDATE moz_places
+                          SET {}
+                          WHERE id == :row_id",
+            sets.join(",")
+        );
+        db.execute(&sql, &params[..])?;
+    }
+    // This needs to happen after the other updates.
+    if update_frec {
+        update_frecency(
+            db,
+            page_info.row_id,
+            Some(visit_ob.get_redirect_frecency_boost()),
+        )?;
+    }
+    Ok(visit_row_id)
+}
+
+pub fn update_frecency(db: &PlacesDb, id: RowId, redirect_boost: Option<bool>) -> Result<()> {
+    let score = frecency::calculate_frecency(
+        db.conn(),
+        &frecency::DEFAULT_FRECENCY_SETTINGS,
+        id.0, // TODO: calculate_frecency should take a RowId here.
+        redirect_boost,
+    )?;
+
+    db.execute(
+        "
+        UPDATE moz_places
+            SET frecency = :frecency
+        WHERE id = :page_id",
+        &[
+            (":frecency", &score as &dyn rusqlite::ToSql),
+            (":page_id", &id.0),
+        ],
+    )?;
+
+    Ok(())
+}
+
+/// Indicates if and when a URL's frecency was marked as stale.
+pub fn frecency_stale_at(db: &PlacesDb, url: &Url) -> Result<Option<Timestamp>> {
+    let result = db.try_query_row(
+        "SELECT stale_at FROM moz_places_stale_frecencies s
+         JOIN moz_places h ON h.id = s.place_id
+         WHERE h.url_hash = hash(:url) AND
+               h.url = :url",
+        &[(":url", &url.as_str())],
+        |row| -> rusqlite::Result<_> { row.get::<_, Timestamp>(0) },
+        true,
+    )?;
+    Ok(result)
+}
+
+// Add a single visit - you must know the page rowid. Does not update the
+// page info - if you are calling this, you will also need to update the
+// parent page with an updated change counter etc.
+fn add_visit(
+    db: &PlacesDb,
+    page_id: RowId,
+    from_visit: Option<RowId>,
+    visit_date: Timestamp,
+    visit_type: VisitType,
+    is_local: bool,
+    unknown_fields: Option<String>,
+) -> Result<RowId> {
+    let sql = "INSERT INTO moz_historyvisits
+            (from_visit, place_id, visit_date, visit_type, is_local, unknown_fields)
+        VALUES (:from_visit, :page_id, :visit_date, :visit_type, :is_local, :unknown_fields)";
+    db.execute_cached(
+        sql,
+        &[
+            (":from_visit", &from_visit as &dyn rusqlite::ToSql),
+            (":page_id", &page_id),
+            (":visit_date", &visit_date),
+            (":visit_type", &visit_type),
+            (":is_local", &is_local),
+            (":unknown_fields", &unknown_fields),
+        ],
+    )?;
+    let rid = db.conn().last_insert_rowid();
+    // Delete any tombstone that exists.
+    db.execute_cached(
+        "DELETE FROM moz_historyvisit_tombstones
+         WHERE place_id = :place_id
+           AND visit_date = :visit_date",
+        &[
+            (":place_id", &page_id as &dyn rusqlite::ToSql),
+            (":visit_date", &visit_date),
+        ],
+    )?;
+    Ok(RowId(rid))
+}
+
+/// Returns the GUID for the specified Url, or None if it doesn't exist.
+pub fn url_to_guid(db: &PlacesDb, url: &Url) -> Result<Option<SyncGuid>> {
+    href_to_guid(db, url.clone().as_str())
+}
+
+/// Returns the GUID for the specified Url String, or None if it doesn't exist.
+pub fn href_to_guid(db: &PlacesDb, url: &str) -> Result<Option<SyncGuid>> {
+    let sql = "SELECT guid FROM moz_places WHERE url_hash = hash(:url) AND url = :url";
+    let result: Option<SyncGuid> = db.try_query_row(
+        sql,
+        &[(":url", &url.to_owned())],
+        // subtle: we explicitly need to specify rusqlite::Result or the compiler
+        // struggles to work out what error type to return from try_query_row.
+        |row| -> rusqlite::Result<_> { row.get::<_, SyncGuid>(0) },
+        true,
+    )?;
+    Ok(result)
+}
+
+/// Internal function for deleting a page, creating a tombstone if necessary.
+/// Assumes a transaction is already set up by the caller.
+fn delete_visits_for_in_tx(db: &PlacesDb, guid: &SyncGuid) -> Result<()> {
+    // We only create tombstones for history which exists and with sync_status
+    // == SyncStatus::Normal
+    let to_clean = db.conn().try_query_row(
+        "SELECT id,
+                (foreign_count != 0) AS has_foreign,
+                1 as has_visits,
+                sync_status
+        FROM moz_places
+        WHERE guid = :guid",
+        &[(":guid", guid)],
+        PageToClean::from_row,
+        true,
+    )?;
+    // Note that history metadata has an `ON DELETE CASCADE` for the place ID - so if we
+    // call `delete_page` here, we assume history metadata dies too. Otherwise we
+    // explicitly delete the metadata after we delete the visits themselves.
+    match to_clean {
+        Some(PageToClean {
+            id,
+            has_foreign: true,
+            sync_status: SyncStatus::Normal,
+            ..
+        }) => {
+            // If our page is syncing, and has foreign key references (like
+            // local or synced bookmarks, keywords, and tags), we can't delete
+            // its row from `moz_places` directly; that would cause a constraint
+            // violation. Instead, we must insert tombstones for all visits, and
+            // then delete just the visits, keeping the page in place (pun most
+            // definitely intended).
+            insert_tombstones_for_all_page_visits(db, id)?;
+            delete_all_visits_for_page(db, id)?;
+            history_metadata::delete_all_metadata_for_page(db, id)?;
+        }
+        Some(PageToClean {
+            id,
+            has_foreign: false,
+            sync_status: SyncStatus::Normal,
+            ..
+        }) => {
+            // However, if our page is syncing and _doesn't_ have any foreign
+            // key references, we can delete it from `moz_places` outright, and
+            // write a tombstone for the page instead of all the visits.
+            insert_tombstone_for_page(db, guid)?;
+            delete_page(db, id)?;
+        }
+        Some(PageToClean {
+            id,
+            has_foreign: true,
+            ..
+        }) => {
+            // If our page has foreign key references but _isn't_ syncing,
+            // we still can't delete it; we must delete its visits. But we
+            // don't need to write any tombstones for those deleted visits.
+            delete_all_visits_for_page(db, id)?;
+            // and we need to delete all history metadata.
+            history_metadata::delete_all_metadata_for_page(db, id)?;
+        }
+        Some(PageToClean {
+            id,
+            has_foreign: false,
+            ..
+        }) => {
+            // And, finally, the easiest case: not syncing, and no foreign
+            // key references, so just delete the page.
+            delete_page(db, id)?;
+        }
+        None => {}
+    }
+    delete_pending_temp_tables(db)?;
+    Ok(())
+}
+
+/// Inserts Sync tombstones for all of a page's visits.
+fn insert_tombstones_for_all_page_visits(db: &PlacesDb, page_id: RowId) -> Result<()> {
+    db.execute_cached(
+        "INSERT OR IGNORE INTO moz_historyvisit_tombstones(place_id, visit_date)
+         SELECT place_id, visit_date
+         FROM moz_historyvisits
+         WHERE place_id = :page_id",
+        &[(":page_id", &page_id)],
+    )?;
+    Ok(())
+}
+
+/// Removes all visits from a page. DOES NOT remove history_metadata - use
+/// `history_metadata::delete_all_metadata_for_page` for that.
+fn delete_all_visits_for_page(db: &PlacesDb, page_id: RowId) -> Result<()> {
+    db.execute_cached(
+        "DELETE FROM moz_historyvisits
+         WHERE place_id = :page_id",
+        &[(":page_id", &page_id)],
+    )?;
+    Ok(())
+}
+
+/// Inserts a Sync tombstone for a page.
+fn insert_tombstone_for_page(db: &PlacesDb, guid: &SyncGuid) -> Result<()> {
+    db.execute_cached(
+        "INSERT OR IGNORE INTO moz_places_tombstones (guid)
+         VALUES(:guid)",
+        &[(":guid", guid)],
+    )?;
+    Ok(())
+}
+
+/// Deletes a page. Note that this throws a constraint violation if the page is
+/// bookmarked, or has a keyword or tags.
+fn delete_page(db: &PlacesDb, page_id: RowId) -> Result<()> {
+    db.execute_cached(
+        "DELETE FROM moz_places
+         WHERE id = :page_id",
+        &[(":page_id", &page_id)],
+    )?;
+    Ok(())
+}
+
+/// Deletes all visits for a page given its GUID, creating tombstones if
+/// necessary.
+pub fn delete_visits_for(db: &PlacesDb, guid: &SyncGuid) -> Result<()> {
+    let tx = db.begin_transaction()?;
+    let result = delete_visits_for_in_tx(db, guid);
+    tx.commit()?;
+    result
+}
+
+/// Delete all visits in a date range.
+pub fn delete_visits_between(db: &PlacesDb, start: Timestamp, end: Timestamp) -> Result<()> {
+    let tx = db.begin_transaction()?;
+    delete_visits_between_in_tx(db, start, end)?;
+    tx.commit()?;
+    Ok(())
+}
+
+pub fn delete_place_visit_at_time(db: &PlacesDb, place: &Url, visit: Timestamp) -> Result<()> {
+    delete_place_visit_at_time_by_href(db, place.as_str(), visit)
+}
+
+pub fn delete_place_visit_at_time_by_href(
+    db: &PlacesDb,
+    place: &str,
+    visit: Timestamp,
+) -> Result<()> {
+    let tx = db.begin_transaction()?;
+    delete_place_visit_at_time_in_tx(db, place, visit)?;
+    tx.commit()?;
+    Ok(())
+}
+
+pub fn prune_older_visits(db: &PlacesDb) -> Result<()> {
+    let tx = db.begin_transaction()?;
+    // Prune 6 items at a time, which matches desktops "small limit" value
+    let limit: usize = 6;
+
+    let result = DbAction::apply_all(
+        db,
+        db_actions_from_visits_to_delete(find_visits_to_prune(db, limit, Timestamp::now())?),
+    );
+    tx.commit()?;
+    result
+}
+
+fn find_visits_to_prune(db: &PlacesDb, limit: usize, now: Timestamp) -> Result<Vec<VisitToDelete>> {
+    // Start with the exotic visits
+    let mut to_delete: HashSet<_> = find_exotic_visits_to_prune(db, limit, now)?
+        .into_iter()
+        .collect();
+    // If we still have more visits to prune, then add them from find_normal_visits_to_prune,
+    // leveraging the HashSet to ensure we don't add a duplicate item.
+    if to_delete.len() < limit {
+        for delete_visit in find_normal_visits_to_prune(db, limit, now)? {
+            to_delete.insert(delete_visit);
+            if to_delete.len() >= limit {
+                break;
+            }
+        }
+    }
+    Ok(Vec::from_iter(to_delete))
+}
+
+fn find_normal_visits_to_prune(
+    db: &PlacesDb,
+    limit: usize,
+    now: Timestamp,
+) -> Result<Vec<VisitToDelete>> {
+    // 7 days ago
+    let visit_date_cutoff = now.checked_sub(Duration::from_secs(60 * 60 * 24 * 7));
+    db.query_rows_and_then(
+        "
+        SELECT v.id, v.place_id
+        FROM moz_places p
+        JOIN moz_historyvisits v ON v.place_id = p.id
+        WHERE v.visit_date < :visit_date_cuttoff
+        ORDER BY v.visit_date
+        LIMIT :limit
+        ",
+        rusqlite::named_params! {
+            ":visit_date_cuttoff": visit_date_cutoff,
+            ":limit": limit,
+        },
+        VisitToDelete::from_row,
+    )
+}
+
+/// Find "exotic" visits to prune.  These are visits visits that should be pruned first because
+/// they are less useful to the user because:
+///   - They're very old
+///   - They're not useful in the awesome bar because they're either a long URL or a download
+///
+/// This is based on the desktop pruning logic:
+/// https://searchfox.org/mozilla-central/search?q=QUERY_FIND_EXOTIC_VISITS_TO_EXPIRE
+fn find_exotic_visits_to_prune(
+    db: &PlacesDb,
+    limit: usize,
+    now: Timestamp,
+) -> Result<Vec<VisitToDelete>> {
+    // 60 days ago
+    let visit_date_cutoff = now.checked_sub(Duration::from_secs(60 * 60 * 24 * 60));
+    db.query_rows_and_then(
+        "
+        SELECT v.id, v.place_id
+        FROM moz_places p
+        JOIN moz_historyvisits v ON v.place_id = p.id
+        WHERE v.visit_date < :visit_date_cuttoff
+        AND (LENGTH(p.url) > 255 OR v.visit_type = :download_visit_type)
+        ORDER BY v.visit_date
+        LIMIT :limit
+        ",
+        rusqlite::named_params! {
+            ":visit_date_cuttoff": visit_date_cutoff,
+            ":download_visit_type": VisitType::Download,
+            ":limit": limit,
+        },
+        VisitToDelete::from_row,
+    )
+}
+
+fn wipe_local_in_tx(db: &PlacesDb) -> Result<()> {
+    use crate::frecency::DEFAULT_FRECENCY_SETTINGS;
+    db.execute_all(&[
+        "DELETE FROM moz_places WHERE foreign_count == 0",
+        "DELETE FROM moz_places_metadata",
+        "DELETE FROM moz_places_metadata_search_queries",
+        "DELETE FROM moz_historyvisits",
+        "DELETE FROM moz_places_tombstones",
+        "DELETE FROM moz_inputhistory AS i WHERE NOT EXISTS(
+             SELECT 1 FROM moz_places h
+             WHERE h.id = i.place_id)",
+        "DELETE FROM moz_historyvisit_tombstones",
+        "DELETE FROM moz_origins
+         WHERE id NOT IN (SELECT origin_id FROM moz_places)",
+        &format!(
+            r#"UPDATE moz_places SET
+                frecency = (CASE WHEN url_hash BETWEEN hash("place", "prefix_lo") AND
+                                                       hash("place", "prefix_hi")
+                                 THEN 0
+                                 ELSE {unvisited_bookmark_frec}
+                            END),
+                sync_change_counter = 0"#,
+            unvisited_bookmark_frec = DEFAULT_FRECENCY_SETTINGS.unvisited_bookmark_bonus
+        ),
+    ])?;
+
+    let need_frecency_update =
+        db.query_rows_and_then("SELECT id FROM moz_places", [], |r| r.get::<_, RowId>(0))?;
+    // Update the frecency for any remaining items, which basically means just
+    // for the bookmarks.
+    for row_id in need_frecency_update {
+        update_frecency(db, row_id, None)?;
+    }
+    delete_pending_temp_tables(db)?;
+    Ok(())
+}
+
+pub fn delete_everything(db: &PlacesDb) -> Result<()> {
+    let tx = db.begin_transaction()?;
+
+    // Remote visits could have a higher date than `now` if our clock is weird.
+    let most_recent_known_visit_time = db
+        .try_query_one::<Timestamp, _>("SELECT MAX(visit_date) FROM moz_historyvisits", [], false)?
+        .unwrap_or_default();
+
+    // Check the old value (if any) for the same reason
+    let previous_mark =
+        get_meta::<Timestamp>(db, DELETION_HIGH_WATER_MARK_META_KEY)?.unwrap_or_default();
+
+    let new_mark = Timestamp::now()
+        .max(previous_mark)
+        .max(most_recent_known_visit_time);
+
+    put_meta(db, DELETION_HIGH_WATER_MARK_META_KEY, &new_mark)?;
+
+    wipe_local_in_tx(db)?;
+
+    // Remove Sync metadata, too.
+    reset_in_tx(db, &EngineSyncAssociation::Disconnected)?;
+
+    tx.commit()?;
+
+    // Note: SQLite cannot VACUUM within a transaction.
+    db.execute_batch("VACUUM")?;
+    Ok(())
+}
+
+fn delete_place_visit_at_time_in_tx(db: &PlacesDb, url: &str, visit_date: Timestamp) -> Result<()> {
+    DbAction::apply_all(
+        db,
+        db_actions_from_visits_to_delete(db.query_rows_and_then(
+            "SELECT v.id, v.place_id
+                 FROM moz_places h
+                 JOIN moz_historyvisits v
+                   ON v.place_id = h.id
+                 WHERE v.visit_date = :visit_date
+                   AND h.url_hash = hash(:url)
+                   AND h.url = :url",
+            &[
+                (":url", &url as &dyn rusqlite::ToSql),
+                (":visit_date", &visit_date),
+            ],
+            VisitToDelete::from_row,
+        )?),
+    )
+}
+
+pub fn delete_visits_between_in_tx(db: &PlacesDb, start: Timestamp, end: Timestamp) -> Result<()> {
+    // Like desktop's removeVisitsByFilter, we query the visit and place ids
+    // affected, then delete all visits, then delete all place ids in the set
+    // which are orphans after the delete.
+    let sql = "
+        SELECT id, place_id, visit_date
+        FROM moz_historyvisits
+        WHERE visit_date
+            BETWEEN :start AND :end
+    ";
+    let visits = db.query_rows_and_then(
+        sql,
+        &[(":start", &start), (":end", &end)],
+        |row| -> rusqlite::Result<_> {
+            Ok((
+                row.get::<_, RowId>(0)?,
+                row.get::<_, RowId>(1)?,
+                row.get::<_, Timestamp>(2)?,
+            ))
+        },
+    )?;
+
+    sql_support::each_chunk_mapped(
+        &visits,
+        |(visit_id, _, _)| visit_id,
+        |chunk, _| -> Result<()> {
+            db.conn().execute(
+                &format!(
+                    "DELETE from moz_historyvisits WHERE id IN ({})",
+                    sql_support::repeat_sql_vars(chunk.len()),
+                ),
+                rusqlite::params_from_iter(chunk),
+            )?;
+            Ok(())
+        },
+    )?;
+
+    // Insert tombstones for the deleted visits.
+    if !visits.is_empty() {
+        let sql = format!(
+            "INSERT OR IGNORE INTO moz_historyvisit_tombstones(place_id, visit_date) VALUES {}",
+            sql_support::repeat_display(visits.len(), ",", |i, f| {
+                let (_, place_id, visit_date) = visits[i];
+                write!(f, "({},{})", place_id.0, visit_date.0)
+            })
+        );
+        db.conn().execute(&sql, [])?;
+    }
+
+    // Find out which pages have been possibly orphaned and clean them up.
+    sql_support::each_chunk_mapped(
+        &visits,
+        |(_, place_id, _)| place_id.0,
+        |chunk, _| -> Result<()> {
+            let query = format!(
+                "SELECT id,
+                    (foreign_count != 0) AS has_foreign,
+                    ((last_visit_date_local + last_visit_date_remote) != 0) as has_visits,
+                    sync_status
+                FROM moz_places
+                WHERE id IN ({})",
+                sql_support::repeat_sql_vars(chunk.len()),
+            );
+
+            let mut stmt = db.conn().prepare(&query)?;
+            let page_results =
+                stmt.query_and_then(rusqlite::params_from_iter(chunk), PageToClean::from_row)?;
+            let pages: Vec<PageToClean> = page_results.collect::<Result<_>>()?;
+            cleanup_pages(db, &pages)
+        },
+    )?;
+
+    // Clean up history metadata between start and end
+    history_metadata::delete_between(db, start.as_millis_i64(), end.as_millis_i64())?;
+    delete_pending_temp_tables(db)?;
+    Ok(())
+}
+
+#[derive(Debug)]
+struct PageToClean {
+    id: RowId,
+    has_foreign: bool,
+    has_visits: bool,
+    sync_status: SyncStatus,
+}
+
+impl PageToClean {
+    pub fn from_row(row: &Row<'_>) -> Result<Self> {
+        Ok(Self {
+            id: row.get("id")?,
+            has_foreign: row.get("has_foreign")?,
+            has_visits: row.get("has_visits")?,
+            sync_status: row.get("sync_status")?,
+        })
+    }
+}
+
+/// Clean up pages whose history has been modified, by either
+/// removing them entirely (if they are marked for removal,
+/// typically because all visits have been removed and there
+/// are no more foreign keys such as bookmarks) or updating
+/// their frecency.
+fn cleanup_pages(db: &PlacesDb, pages: &[PageToClean]) -> Result<()> {
+    // desktop does this frecency work using a function in a single sql
+    // statement - we should see if we can do that too.
+    let frec_ids = pages
+        .iter()
+        .filter(|&p| p.has_foreign || p.has_visits)
+        .map(|p| p.id);
+
+    for id in frec_ids {
+        update_frecency(db, id, None)?;
+    }
+
+    // Like desktop, we do "AND foreign_count = 0 AND last_visit_date ISNULL"
+    // to creating orphans in case of async race conditions - in Desktop's
+    // case, it reads the pages before starting a write transaction, so that
+    // probably is possible. We don't currently do that, but might later, so
+    // we do it anyway.
+    let remove_ids: Vec<RowId> = pages
+        .iter()
+        .filter(|p| !p.has_foreign && !p.has_visits)
+        .map(|p| p.id)
+        .collect();
+    sql_support::each_chunk(&remove_ids, |chunk, _| -> Result<()> {
+        // tombstones first.
+        db.conn().execute(
+            &format!(
+                "
+                INSERT OR IGNORE INTO moz_places_tombstones (guid)
+                SELECT guid FROM moz_places
+                WHERE id in ({ids}) AND sync_status = {status}
+                    AND foreign_count = 0
+                    AND last_visit_date_local = 0
+                    AND last_visit_date_remote = 0",
+                ids = sql_support::repeat_sql_vars(chunk.len()),
+                status = SyncStatus::Normal as u8,
+            ),
+            rusqlite::params_from_iter(chunk),
+        )?;
+        db.conn().execute(
+            &format!(
+                "
+                DELETE FROM moz_places
+                WHERE id IN ({ids})
+                    AND foreign_count = 0
+                    AND last_visit_date_local = 0
+                    AND last_visit_date_remote = 0",
+                ids = sql_support::repeat_sql_vars(chunk.len())
+            ),
+            rusqlite::params_from_iter(chunk),
+        )?;
+        Ok(())
+    })?;
+
+    Ok(())
+}
+
+fn reset_in_tx(db: &PlacesDb, assoc: &EngineSyncAssociation) -> Result<()> {
+    // Reset change counters and sync statuses for all URLs.
+    db.execute_cached(
+        &format!(
+            "
+            UPDATE moz_places
+                SET sync_change_counter = 0,
+                sync_status = {}",
+            (SyncStatus::New as u8)
+        ),
+        [],
+    )?;
+
+    // Reset the last sync time, so that the next sync fetches fresh records
+    // from the server.
+    put_meta(db, LAST_SYNC_META_KEY, &0)?;
+
+    // Clear the sync ID if we're signing out, or set it to whatever the
+    // server gave us if we're signing in.
+    match assoc {
+        EngineSyncAssociation::Disconnected => {
+            delete_meta(db, GLOBAL_SYNCID_META_KEY)?;
+            delete_meta(db, COLLECTION_SYNCID_META_KEY)?;
+        }
+        EngineSyncAssociation::Connected(ids) => {
+            put_meta(db, GLOBAL_SYNCID_META_KEY, &ids.global)?;
+            put_meta(db, COLLECTION_SYNCID_META_KEY, &ids.coll)?;
+        }
+    }
+
+    Ok(())
+}
+
+// Support for Sync - in its own module to try and keep a delineation
+pub mod history_sync {
+    use sync15::bso::OutgoingEnvelope;
+
+    use super::*;
+    use crate::history_sync::record::{HistoryRecord, HistoryRecordVisit};
+    use crate::history_sync::HISTORY_TTL;
+    use std::collections::HashSet;
+
+    #[derive(Debug, Clone, PartialEq, Eq)]
+    pub struct FetchedVisit {
+        pub is_local: bool,
+        pub visit_date: Timestamp,
+        pub visit_type: Option<VisitType>,
+    }
+
+    impl FetchedVisit {
+        pub fn from_row(row: &Row<'_>) -> Result<Self> {
+            Ok(Self {
+                is_local: row.get("is_local")?,
+                visit_date: row
+                    .get::<_, Option<Timestamp>>("visit_date")?
+                    .unwrap_or_default(),
+                visit_type: VisitType::from_primitive(
+                    row.get::<_, Option<u8>>("visit_type")?.unwrap_or(0),
+                ),
+            })
+        }
+    }
+
+    #[derive(Debug)]
+    pub struct FetchedVisitPage {
+        pub url: Url,
+        pub guid: SyncGuid,
+        pub row_id: RowId,
+        pub title: String,
+        pub unknown_fields: UnknownFields,
+    }
+
+    impl FetchedVisitPage {
+        pub fn from_row(row: &Row<'_>) -> Result<Self> {
+            Ok(Self {
+                url: Url::parse(&row.get::<_, String>("url")?)?,
+                guid: row.get::<_, String>("guid")?.into(),
+                row_id: row.get("id")?,
+                title: row.get::<_, Option<String>>("title")?.unwrap_or_default(),
+                unknown_fields: match row.get::<_, Option<String>>("unknown_fields")? {
+                    None => UnknownFields::new(),
+                    Some(v) => serde_json::from_str(&v)?,
+                },
+            })
+        }
+    }
+
+    pub fn fetch_visits(
+        db: &PlacesDb,
+        url: &Url,
+        limit: usize,
+    ) -> Result<Option<(FetchedVisitPage, Vec<FetchedVisit>)>> {
+        // We do this in 2 steps - "do we have a page" then "get visits"
+        let page_sql = "
+          SELECT guid, url, id, title, unknown_fields
+          FROM moz_places h
+          WHERE url_hash = hash(:url) AND url = :url";
+
+        let page_info = match db.try_query_row(
+            page_sql,
+            &[(":url", &url.to_string())],
+            FetchedVisitPage::from_row,
+            true,
+        )? {
+            None => return Ok(None),
+            Some(pi) => pi,
+        };
+
+        let visits = db.query_rows_and_then(
+            "SELECT is_local, visit_type, visit_date
+            FROM moz_historyvisits
+            WHERE place_id = :place_id
+            LIMIT :limit",
+            &[
+                (":place_id", &page_info.row_id as &dyn rusqlite::ToSql),
+                (":limit", &(limit as u32)),
+            ],
+            FetchedVisit::from_row,
+        )?;
+        Ok(Some((page_info, visits)))
+    }
+
+    /// Apply history visit from sync. This assumes they have all been
+    /// validated, deduped, etc - it's just the storage we do here.
+    pub fn apply_synced_visits(
+        db: &PlacesDb,
+        incoming_guid: &SyncGuid,
+        url: &Url,
+        title: &Option<String>,
+        visits: &[HistoryRecordVisit],
+        unknown_fields: &UnknownFields,
+    ) -> Result<()> {
+        // At some point we may have done a local wipe of all visits. We skip applying
+        // incoming visits that could have been part of that deletion, to avoid them
+        // trickling back in.
+        let visit_ignored_mark =
+            get_meta::<Timestamp>(db, DELETION_HIGH_WATER_MARK_META_KEY)?.unwrap_or_default();
+
+        let visits = visits
+            .iter()
+            .filter(|v| Timestamp::from(v.date) > visit_ignored_mark)
+            .collect::<Vec<_>>();
+
+        let mut counter_incr = 0;
+        let page_info = match fetch_page_info(db, url)? {
+            Some(mut info) => {
+                // If the existing record has not yet been synced, then we will
+                // change the GUID to the incoming one. If it has been synced
+                // we keep the existing guid, but still apply the visits.
+                // See doc/history_duping.rst for more details.
+                if &info.page.guid != incoming_guid {
+                    if info.page.sync_status == SyncStatus::New {
+                        db.execute_cached(
+                            "UPDATE moz_places SET guid = :new_guid WHERE id = :row_id",
+                            &[
+                                (":new_guid", incoming_guid as &dyn rusqlite::ToSql),
+                                (":row_id", &info.page.row_id),
+                            ],
+                        )?;
+                        info.page.guid = incoming_guid.clone();
+                    }
+                    // Even if we didn't take the new guid, we are going to
+                    // take the new visits - so we want the change counter to
+                    // reflect there are changes.
+                    counter_incr = 1;
+                }
+                info.page
+            }
+            None => {
+                // Before we insert a new page_info, make sure we actually will
+                // have any visits to add.
+                if visits.is_empty() {
+                    return Ok(());
+                }
+                new_page_info(db, url, Some(incoming_guid.clone()))?
+            }
+        };
+
+        if !visits.is_empty() {
+            // Skip visits that are in tombstones, or that happen at the same time
+            // as visit that's already present. The 2nd lets us avoid inserting
+            // visits that we sent up to the server in the first place.
+            //
+            // It does cause us to ignore visits that legitimately happen
+            // at the same time, but that's probably fine and not worth
+            // worrying about.
+            let mut visits_to_skip: HashSet<Timestamp> = db.query_rows_into(
+                &format!(
+                    "SELECT t.visit_date AS visit_date
+                     FROM moz_historyvisit_tombstones t
+                     WHERE t.place_id = {place}
+                       AND t.visit_date IN ({dates})
+                     UNION ALL
+                     SELECT v.visit_date AS visit_date
+                     FROM moz_historyvisits v
+                     WHERE v.place_id = {place}
+                       AND v.visit_date IN ({dates})",
+                    place = page_info.row_id,
+                    dates = sql_support::repeat_display(visits.len(), ",", |i, f| write!(
+                        f,
+                        "{}",
+                        Timestamp::from(visits[i].date).0
+                    )),
+                ),
+                [],
+                |row| row.get::<_, Timestamp>(0),
+            )?;
+
+            visits_to_skip.reserve(visits.len());
+
+            for visit in visits {
+                let timestamp = Timestamp::from(visit.date);
+                // Don't insert visits that have been locally deleted.
+                if visits_to_skip.contains(&timestamp) {
+                    continue;
+                }
+                let transition = VisitType::from_primitive(visit.transition)
+                    .expect("these should already be validated");
+                add_visit(
+                    db,
+                    page_info.row_id,
+                    None,
+                    timestamp,
+                    transition,
+                    false,
+                    serialize_unknown_fields(&visit.unknown_fields)?,
+                )?;
+                // Make sure that even if a history entry weirdly has the same visit
+                // twice, we don't insert it twice. (This avoids us needing to
+                // recompute visits_to_skip in each step of the iteration)
+                visits_to_skip.insert(timestamp);
+            }
+        }
+        // XXX - we really need a better story for frecency-boost than
+        // Option<bool> - None vs Some(false) is confusing. We should use an enum.
+        update_frecency(db, page_info.row_id, None)?;
+
+        // and the place itself if necessary.
+        let new_title = title.as_ref().unwrap_or(&page_info.title);
+        // We set the Status to Normal, otherwise we will re-upload it as
+        // outgoing even if nothing has changed. Note that we *do not* reset
+        // the change counter - if it is non-zero now, we want it to remain
+        // as non-zero, so we do re-upload it if there were actual changes)
+        db.execute_cached(
+            "UPDATE moz_places
+             SET title = :title,
+                 unknown_fields = :unknown_fields,
+                 sync_status = :status,
+                 sync_change_counter = :sync_change_counter
+             WHERE id == :row_id",
+            &[
+                (":title", new_title as &dyn rusqlite::ToSql),
+                (":row_id", &page_info.row_id),
+                (":status", &SyncStatus::Normal),
+                (
+                    ":unknown_fields",
+                    &serialize_unknown_fields(unknown_fields)?,
+                ),
+                (
+                    ":sync_change_counter",
+                    &(page_info.sync_change_counter + counter_incr),
+                ),
+            ],
+        )?;
+
+        Ok(())
+    }
+
+    pub fn apply_synced_reconciliation(db: &PlacesDb, guid: &SyncGuid) -> Result<()> {
+        db.execute_cached(
+            "UPDATE moz_places
+                SET sync_status = :status,
+                    sync_change_counter = 0
+             WHERE guid == :guid",
+            &[
+                (":guid", guid as &dyn rusqlite::ToSql),
+                (":status", &SyncStatus::Normal),
+            ],
+        )?;
+        Ok(())
+    }
+
+    pub fn apply_synced_deletion(db: &PlacesDb, guid: &SyncGuid) -> Result<()> {
+        db.execute_cached(
+            "DELETE FROM moz_places WHERE guid = :guid",
+            &[(":guid", guid)],
+        )?;
+        Ok(())
+    }
+
+    pub fn fetch_outgoing(
+        db: &PlacesDb,
+        max_places: usize,
+        max_visits: usize,
+    ) -> Result<Vec<OutgoingBso>> {
+        // Note that we want *all* "new" regardless of change counter,
+        // so that we do the right thing after a "reset". We also
+        // exclude hidden URLs from syncing, to match Desktop
+        // (bug 1173359).
+        let places_sql = format!(
+            "
+            SELECT guid, url, id, title, hidden, typed, frecency,
+                visit_count_local, visit_count_remote,
+                last_visit_date_local, last_visit_date_remote,
+                sync_status, sync_change_counter, preview_image_url,
+                unknown_fields
+            FROM moz_places
+            WHERE (sync_change_counter > 0 OR sync_status != {}) AND
+                  NOT hidden
+            ORDER BY frecency DESC
+            LIMIT :max_places",
+            (SyncStatus::Normal as u8)
+        );
+        let visits_sql = "
+            SELECT visit_date as date, visit_type as transition, unknown_fields
+            FROM moz_historyvisits
+            WHERE place_id = :place_id
+            ORDER BY visit_date DESC
+            LIMIT :max_visits";
+        // tombstones
+        let tombstones_sql = "SELECT guid FROM moz_places_tombstones LIMIT :max_places";
+
+        let mut tombstone_ids = HashSet::new();
+        let mut result = Vec::new();
+
+        // We want to limit to 5000 places - tombstones are arguably the
+        // most important, so we fetch these first.
+        let ts_rows = db.query_rows_and_then(
+            tombstones_sql,
+            &[(":max_places", &(max_places as u32))],
+            |row| -> rusqlite::Result<SyncGuid> { Ok(row.get::<_, String>("guid")?.into()) },
+        )?;
+        // It's unfortunatee that query_rows_and_then returns a Vec instead of an iterator
+        // (which would be very hard to do), but as long as we have it, we might as well make use
+        // of it...
+        result.reserve(ts_rows.len());
+        tombstone_ids.reserve(ts_rows.len());
+        for guid in ts_rows {
+            log::trace!("outgoing tombstone {:?}", &guid);
+            let envelope = OutgoingEnvelope {
+                id: guid.clone(),
+                ttl: Some(HISTORY_TTL),
+                ..Default::default()
+            };
+            result.push(OutgoingBso::new_tombstone(envelope));
+            tombstone_ids.insert(guid);
+        }
+
+        // Max records is now limited by how many tombstones we found.
+        let max_places_left = max_places - result.len();
+
+        // We write info about the records we are updating to a temp table.
+        // While we could carry this around in memory, we'll need a temp table
+        // in `finish_outgoing` anyway, because we execute a `NOT IN` query
+        // there - which, in a worst-case scenario, is a very large `NOT IN`
+        // set.
+        db.execute(
+            "CREATE TEMP TABLE IF NOT EXISTS temp_sync_updated_meta
+                    (id INTEGER PRIMARY KEY,
+                     change_delta INTEGER NOT NULL)",
+            [],
+        )?;
+
+        let insert_meta_sql = "
+            INSERT INTO temp_sync_updated_meta VALUES (:row_id, :change_delta)";
+
+        let rows = db.query_rows_and_then(
+            &places_sql,
+            &[(":max_places", &(max_places_left as u32))],
+            PageInfo::from_row,
+        )?;
+        result.reserve(rows.len());
+        let mut ids_to_update = Vec::with_capacity(rows.len());
+        for page in rows {
+            let visits = db.query_rows_and_then_cached(
+                visits_sql,
+                &[
+                    (":max_visits", &(max_visits as u32) as &dyn rusqlite::ToSql),
+                    (":place_id", &page.row_id),
+                ],
+                |row| -> Result<_> {
+                    Ok(HistoryRecordVisit {
+                        date: row.get::<_, Timestamp>("date")?.into(),
+                        transition: row.get::<_, u8>("transition")?,
+                        unknown_fields: match row.get::<_, Option<String>>("unknown_fields")? {
+                            None => UnknownFields::new(),
+                            Some(v) => serde_json::from_str(&v)?,
+                        },
+                    })
+                },
+            )?;
+            if tombstone_ids.contains(&page.guid) {
+                // should be impossible!
+                log::warn!("Found {:?} in both tombstones and live records", &page.guid);
+                continue;
+            }
+            if visits.is_empty() {
+                // This will be true for things like bookmarks which haven't
+                // had visits locally applied, and if we later prune old visits
+                // we'll also hit it, so don't make much log noise.
+                log::trace!(
+                    "Page {:?} is flagged to be uploaded, but has no visits - skipping",
+                    &page.guid
+                );
+                continue;
+            }
+            log::trace!("outgoing record {:?}", &page.guid);
+            ids_to_update.push(page.row_id);
+            db.execute_cached(
+                insert_meta_sql,
+                &[
+                    (":row_id", &page.row_id as &dyn rusqlite::ToSql),
+                    (":change_delta", &page.sync_change_counter),
+                ],
+            )?;
+
+            let content = HistoryRecord {
+                id: page.guid.clone(),
+                title: page.title,
+                hist_uri: page.url.to_string(),
+                visits,
+                unknown_fields: page.unknown_fields,
+            };
+
+            let envelope = OutgoingEnvelope {
+                id: page.guid,
+                sortindex: Some(page.frecency),
+                ttl: Some(HISTORY_TTL),
+            };
+            let bso = OutgoingBso::from_content(envelope, content)?;
+            result.push(bso);
+        }
+
+        // We need to update the sync status of these items now rather than after
+        // the upload, because if we are interrupted between upload and writing
+        // we could end up with local items with state New even though we
+        // uploaded them.
+        sql_support::each_chunk(&ids_to_update, |chunk, _| -> Result<()> {
+            db.conn().execute(
+                &format!(
+                    "UPDATE moz_places SET sync_status={status}
+                                 WHERE id IN ({vars})",
+                    vars = sql_support::repeat_sql_vars(chunk.len()),
+                    status = SyncStatus::Normal as u8
+                ),
+                rusqlite::params_from_iter(chunk),
+            )?;
+            Ok(())
+        })?;
+
+        Ok(result)
+    }
+
+    pub fn finish_outgoing(db: &PlacesDb) -> Result<()> {
+        // So all items *other* than those above must be set to "not dirty"
+        // (ie, status=SyncStatus::Normal, change_counter=0). Otherwise every
+        // subsequent sync will continue to add more and more local pages
+        // until every page we have is uploaded. And we only want to do it
+        // at the end of the sync because if we are interrupted, we'll end up
+        // thinking we have nothing to upload.
+        // BUT - this is potentially alot of rows! Because we want "NOT IN (...)"
+        // we can't do chunking and building a literal string with the ids seems
+        // wrong and likely to hit max sql length limits.
+        // So we use a temp table.
+        log::debug!("Updating all synced rows");
+        // XXX - is there a better way to express this SQL? Multi-selects
+        // doesn't seem ideal...
+        db.conn().execute_cached(
+            "
+            UPDATE moz_places
+                SET sync_change_counter = sync_change_counter -
+                (SELECT change_delta FROM temp_sync_updated_meta m WHERE moz_places.id = m.id)
+            WHERE id IN (SELECT id FROM temp_sync_updated_meta)
+            ",
+            [],
+        )?;
+
+        log::debug!("Updating all non-synced rows");
+        db.execute_all(&[
+            &format!(
+                "UPDATE moz_places
+                    SET sync_change_counter = 0, sync_status = {}
+                WHERE id NOT IN (SELECT id from temp_sync_updated_meta)",
+                (SyncStatus::Normal as u8)
+            ),
+            "DELETE FROM temp_sync_updated_meta",
+        ])?;
+
+        log::debug!("Removing local tombstones");
+        db.conn()
+            .execute_cached("DELETE from moz_places_tombstones", [])?;
+
+        Ok(())
+    }
+
+    /// Resets all sync metadata, including change counters, sync statuses,
+    /// the last sync time, and sync ID. This should be called when the user
+    /// signs out of Sync.
+    pub(crate) fn reset(db: &PlacesDb, assoc: &EngineSyncAssociation) -> Result<()> {
+        let tx = db.begin_transaction()?;
+        reset_in_tx(db, assoc)?;
+        tx.commit()?;
+        Ok(())
+    }
+} // end of sync module.
+
+pub fn get_visited<I>(db: &PlacesDb, urls: I) -> Result<Vec<bool>>
+where
+    I: IntoIterator<Item = Url>,
+    I::IntoIter: ExactSizeIterator,
+{
+    let iter = urls.into_iter();
+    let mut result = vec![false; iter.len()];
+    let url_idxs = iter.enumerate().collect::<Vec<_>>();
+    get_visited_into(db, &url_idxs, &mut result)?;
+    Ok(result)
+}
+
+/// Low level api used to implement both get_visited and the FFI get_visited call.
+/// Takes a slice where we should output the results, as well as a slice of
+/// index/url pairs.
+///
+/// This is done so that the FFI can more easily support returning
+/// false when asked if it's visited an invalid URL.
+pub fn get_visited_into(
+    db: &PlacesDb,
+    urls_idxs: &[(usize, Url)],
+    result: &mut [bool],
+) -> Result<()> {
+    sql_support::each_chunk_mapped(
+        urls_idxs,
+        |(_, url)| url.as_str(),
+        |chunk, offset| -> Result<()> {
+            let values_with_idx = sql_support::repeat_display(chunk.len(), ",", |i, f| {
+                let (idx, url) = &urls_idxs[i + offset];
+                write!(f, "({},{},?)", *idx, hash::hash_url(url.as_str()))
+            });
+            let sql = format!(
+                "WITH to_fetch(fetch_url_index, url_hash, url) AS (VALUES {})
+                 SELECT fetch_url_index
+                 FROM moz_places h
+                 JOIN to_fetch f ON h.url_hash = f.url_hash
+                   AND h.url = f.url",
+                values_with_idx
+            );
+            let mut stmt = db.prepare(&sql)?;
+            for idx_r in stmt.query_and_then(
+                rusqlite::params_from_iter(chunk),
+                |row| -> rusqlite::Result<_> { Ok(row.get::<_, i64>(0)? as usize) },
+            )? {
+                let idx = idx_r?;
+                result[idx] = true;
+            }
+            Ok(())
+        },
+    )?;
+    Ok(())
+}
+
+/// Get the set of urls that were visited between `start` and `end`. Only considers local visits
+/// unless you pass in `include_remote`.
+pub fn get_visited_urls(
+    db: &PlacesDb,
+    start: Timestamp,
+    end: Timestamp,
+    include_remote: bool,
+) -> Result<Vec<String>> {
+    // TODO: if `end` is >= now then we can probably just look at last_visit_date_{local,remote},
+    // and avoid touching `moz_historyvisits` at all. That said, this query is taken more or less
+    // from what places does so it's probably fine.
+    let sql = format!(
+        "SELECT h.url
+        FROM moz_places h
+        WHERE EXISTS (
+            SELECT 1 FROM moz_historyvisits v
+            WHERE place_id = h.id
+                AND visit_date BETWEEN :start AND :end
+                {and_is_local}
+            LIMIT 1
+        )",
+        and_is_local = if include_remote { "" } else { "AND is_local" }
+    );
+    Ok(db.query_rows_and_then_cached(
+        &sql,
+        &[(":start", &start), (":end", &end)],
+        |row| -> RusqliteResult<_> { row.get::<_, String>(0) },
+    )?)
+}
+
+pub fn get_top_frecent_site_infos(
+    db: &PlacesDb,
+    num_items: i32,
+    frecency_threshold: i64,
+) -> Result<Vec<TopFrecentSiteInfo>> {
+    // Get the complement of the visit types that should be excluded.
+    let allowed_types = VisitTransitionSet::for_specific(&[
+        VisitType::Download,
+        VisitType::Embed,
+        VisitType::RedirectPermanent,
+        VisitType::RedirectTemporary,
+        VisitType::FramedLink,
+        VisitType::Reload,
+    ])
+    .complement();
+
+    let infos = db.query_rows_and_then_cached(
+        "SELECT h.frecency, h.title, h.url
+        FROM moz_places h
+        WHERE EXISTS (
+            SELECT v.visit_type
+            FROM moz_historyvisits v
+            WHERE h.id = v.place_id
+              AND (SUBSTR(h.url, 1, 6) == 'https:' OR SUBSTR(h.url, 1, 5) == 'http:')
+              AND (h.last_visit_date_local + h.last_visit_date_remote) != 0
+              AND ((1 << v.visit_type) & :allowed_types) != 0
+              AND h.frecency >= :frecency_threshold AND
+              NOT h.hidden
+        )
+        ORDER BY h.frecency DESC
+        LIMIT :limit",
+        rusqlite::named_params! {
+            ":limit": num_items,
+            ":allowed_types": allowed_types,
+            ":frecency_threshold": frecency_threshold,
+        },
+        TopFrecentSiteInfo::from_row,
+    )?;
+    Ok(infos)
+}
+
+pub fn get_visit_infos(
+    db: &PlacesDb,
+    start: Timestamp,
+    end: Timestamp,
+    exclude_types: VisitTransitionSet,
+) -> Result<Vec<HistoryVisitInfo>> {
+    let allowed_types = exclude_types.complement();
+    let infos = db.query_rows_and_then_cached(
+        "SELECT h.url, h.title, v.visit_date, v.visit_type, h.hidden, h.preview_image_url,
+                v.is_local
+         FROM moz_places h
+         JOIN moz_historyvisits v
+           ON h.id = v.place_id
+         WHERE v.visit_date BETWEEN :start AND :end
+           AND ((1 << visit_type) & :allowed_types) != 0 AND
+           NOT h.hidden
+         ORDER BY v.visit_date",
+        rusqlite::named_params! {
+            ":start": start,
+            ":end": end,
+            ":allowed_types": allowed_types,
+        },
+        HistoryVisitInfo::from_row,
+    )?;
+    Ok(infos)
+}
+
+pub fn get_visit_count(db: &PlacesDb, exclude_types: VisitTransitionSet) -> Result<i64> {
+    let count = if exclude_types.is_empty() {
+        db.query_one::<i64>("SELECT COUNT(*) FROM moz_historyvisits")?
+    } else {
+        let allowed_types = exclude_types.complement();
+        db.query_row_and_then_cachable(
+            "SELECT COUNT(*)
+             FROM moz_historyvisits
+             WHERE ((1 << visit_type) & :allowed_types) != 0",
+            rusqlite::named_params! {
+                ":allowed_types": allowed_types,
+            },
+            |r| r.get(0),
+            true,
+        )?
+    };
+    Ok(count)
+}
+
+pub fn get_visit_page(
+    db: &PlacesDb,
+    offset: i64,
+    count: i64,
+    exclude_types: VisitTransitionSet,
+) -> Result<Vec<HistoryVisitInfo>> {
+    let allowed_types = exclude_types.complement();
+    let infos = db.query_rows_and_then_cached(
+        "SELECT h.url, h.title, v.visit_date, v.visit_type, h.hidden, h.preview_image_url,
+                v.is_local
+         FROM moz_places h
+         JOIN moz_historyvisits v
+           ON h.id = v.place_id
+         WHERE ((1 << v.visit_type) & :allowed_types) != 0 AND
+               NOT h.hidden
+         ORDER BY v.visit_date DESC, v.id
+         LIMIT :count
+         OFFSET :offset",
+        rusqlite::named_params! {
+            ":count": count,
+            ":offset": offset,
+            ":allowed_types": allowed_types,
+        },
+        HistoryVisitInfo::from_row,
+    )?;
+    Ok(infos)
+}
+
+pub fn get_visit_page_with_bound(
+    db: &PlacesDb,
+    bound: i64,
+    offset: i64,
+    count: i64,
+    exclude_types: VisitTransitionSet,
+) -> Result<HistoryVisitInfosWithBound> {
+    let allowed_types = exclude_types.complement();
+    let infos = db.query_rows_and_then_cached(
+        "SELECT h.url, h.title, v.visit_date, v.visit_type, h.hidden, h.preview_image_url,
+                v.is_local
+         FROM moz_places h
+         JOIN moz_historyvisits v
+           ON h.id = v.place_id
+         WHERE ((1 << v.visit_type) & :allowed_types) != 0 AND
+               NOT h.hidden
+               AND v.visit_date <= :bound
+         ORDER BY v.visit_date DESC, v.id
+         LIMIT :count
+         OFFSET :offset",
+        rusqlite::named_params! {
+            ":allowed_types": allowed_types,
+            ":bound": bound,
+            ":count": count,
+            ":offset": offset,
+        },
+        HistoryVisitInfo::from_row,
+    )?;
+
+    if let Some(l) = infos.last() {
+        if l.timestamp.as_millis_i64() == bound {
+            // all items' timestamp are equal to the previous bound
+            let offset = offset + infos.len() as i64;
+            Ok(HistoryVisitInfosWithBound {
+                infos,
+                bound,
+                offset,
+            })
+        } else {
+            let bound = l.timestamp;
+            let offset = infos
+                .iter()
+                .rev()
+                .take_while(|i| i.timestamp == bound)
+                .count() as i64;
+            Ok(HistoryVisitInfosWithBound {
+                infos,
+                bound: bound.as_millis_i64(),
+                offset,
+            })
+        }
+    } else {
+        // infos is Empty
+        Ok(HistoryVisitInfosWithBound {
+            infos,
+            bound: 0,
+            offset: 0,
+        })
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::history_sync::*;
+    use super::*;
+    use crate::api::places_api::ConnectionType;
+    use crate::history_sync::record::HistoryRecordVisit;
+    use crate::types::VisitTransitionSet;
+    use pretty_assertions::assert_eq;
+    use std::time::{Duration, SystemTime};
+    use sync15::engine::CollSyncIds;
+    use types::Timestamp;
+
+    #[test]
+    fn test_get_visited_urls() {
+        use std::collections::HashSet;
+        use std::time::SystemTime;
+        let conn = PlacesDb::open_in_memory(ConnectionType::ReadWrite).expect("no memory db");
+        let now: Timestamp = SystemTime::now().into();
+        let now_u64 = now.0;
+        // (url, when, is_remote, (expected_always, expected_only_local)
+        let to_add = [
+            (
+                "https://www.example.com/1",
+                now_u64 - 200_100,
+                false,
+                (false, false),
+            ),
+            (
+                "https://www.example.com/12",
+                now_u64 - 200_000,
+                false,
+                (true, true),
+            ),
+            (
+                "https://www.example.com/123",
+                now_u64 - 10_000,
+                true,
+                (true, false),
+            ),
+            (
+                "https://www.example.com/1234",
+                now_u64 - 1000,
+                false,
+                (true, true),
+            ),
+            (
+                "https://www.mozilla.com",
+                now_u64 - 500,
+                false,
+                (false, false),
+            ),
+        ];
+
+        for &(url, when, remote, _) in &to_add {
+            apply_observation(
+                &conn,
+                VisitObservation::new(Url::parse(url).unwrap())
+                    .with_at(Timestamp(when))
+                    .with_is_remote(remote)
+                    .with_visit_type(VisitType::Link),
+            )
+            .expect("Should apply visit");
+        }
+
+        let visited_all = get_visited_urls(
+            &conn,
+            Timestamp(now_u64 - 200_000),
+            Timestamp(now_u64 - 1000),
+            true,
+        )
+        .unwrap()
+        .into_iter()
+        .collect::<HashSet<_>>();
+
+        let visited_local = get_visited_urls(
+            &conn,
+            Timestamp(now_u64 - 200_000),
+            Timestamp(now_u64 - 1000),
+            false,
+        )
+        .unwrap()
+        .into_iter()
+        .collect::<HashSet<_>>();
+
+        for &(url, ts, is_remote, (expected_in_all, expected_in_local)) in &to_add {
+            // Make sure we format stuff the same way (in practice, just trailing slashes)
+            let url = Url::parse(url).unwrap().to_string();
+            assert_eq!(
+                expected_in_local,
+                visited_local.contains(&url),
+                "Failed in local for {:?}",
+                (url, ts, is_remote)
+            );
+            assert_eq!(
+                expected_in_all,
+                visited_all.contains(&url),
+                "Failed in all for {:?}",
+                (url, ts, is_remote)
+            );
+        }
+    }
+
+    fn get_custom_observed_page<F>(conn: &mut PlacesDb, url: &str, custom: F) -> Result<PageInfo>
+    where
+        F: Fn(VisitObservation) -> VisitObservation,
+    {
+        let u = Url::parse(url)?;
+        let obs = VisitObservation::new(u.clone()).with_visit_type(VisitType::Link);
+        apply_observation(conn, custom(obs))?;
+        Ok(fetch_page_info(conn, &u)?
+            .expect("should have the page")
+            .page)
+    }
+
+    fn get_observed_page(conn: &mut PlacesDb, url: &str) -> Result<PageInfo> {
+        get_custom_observed_page(conn, url, |o| o)
+    }
+
+    fn get_tombstone_count(conn: &PlacesDb) -> u32 {
+        let result: Result<Option<u32>> = conn.try_query_row(
+            "SELECT COUNT(*) from moz_places_tombstones;",
+            [],
+            |row| Ok(row.get::<_, u32>(0)?),
+            true,
+        );
+        result
+            .expect("should have worked")
+            .expect("should have got a value")
+    }
+
+    #[test]
+    fn test_visit_counts() -> Result<()> {
+        let _ = env_logger::try_init();
+        let conn = PlacesDb::open_in_memory(ConnectionType::ReadWrite)?;
+        let url = Url::parse("https://www.example.com").expect("it's a valid url");
+        let early_time = SystemTime::now() - Duration::new(60, 0);
+        let late_time = SystemTime::now();
+
+        // add 2 local visits - add latest first
+        let rid1 = apply_observation(
+            &conn,
+            VisitObservation::new(url.clone())
+                .with_visit_type(VisitType::Link)
+                .with_at(Some(late_time.into())),
+        )?
+        .expect("should get a rowid");
+
+        let rid2 = apply_observation(
+            &conn,
+            VisitObservation::new(url.clone())
+                .with_visit_type(VisitType::Link)
+                .with_at(Some(early_time.into())),
+        )?
+        .expect("should get a rowid");
+
+        let mut pi = fetch_page_info(&conn, &url)?.expect("should have the page");
+        assert_eq!(pi.page.visit_count_local, 2);
+        assert_eq!(pi.page.last_visit_date_local, late_time.into());
+        assert_eq!(pi.page.visit_count_remote, 0);
+        assert_eq!(pi.page.last_visit_date_remote.0, 0);
+
+        // 2 remote visits, earliest first.
+        let rid3 = apply_observation(
+            &conn,
+            VisitObservation::new(url.clone())
+                .with_visit_type(VisitType::Link)
+                .with_at(Some(early_time.into()))
+                .with_is_remote(true),
+        )?
+        .expect("should get a rowid");
+
+        let rid4 = apply_observation(
+            &conn,
+            VisitObservation::new(url.clone())
+                .with_visit_type(VisitType::Link)
+                .with_at(Some(late_time.into()))
+                .with_is_remote(true),
+        )?
+        .expect("should get a rowid");
+
+        pi = fetch_page_info(&conn, &url)?.expect("should have the page");
+        assert_eq!(pi.page.visit_count_local, 2);
+        assert_eq!(pi.page.last_visit_date_local, late_time.into());
+        assert_eq!(pi.page.visit_count_remote, 2);
+        assert_eq!(pi.page.last_visit_date_remote, late_time.into());
+
+        // Delete some and make sure things update.
+        // XXX - we should add a trigger to update frecency on delete, but at
+        // this stage we don't "officially" support deletes, so this is TODO.
+        let sql = "DELETE FROM moz_historyvisits WHERE id = :row_id";
+        // Delete the latest local visit.
+        conn.execute_cached(sql, &[(":row_id", &rid1)])?;
+        pi = fetch_page_info(&conn, &url)?.expect("should have the page");
+        assert_eq!(pi.page.visit_count_local, 1);
+        assert_eq!(pi.page.last_visit_date_local, early_time.into());
+        assert_eq!(pi.page.visit_count_remote, 2);
+        assert_eq!(pi.page.last_visit_date_remote, late_time.into());
+
+        // Delete the earliest remote  visit.
+        conn.execute_cached(sql, &[(":row_id", &rid3)])?;
+        pi = fetch_page_info(&conn, &url)?.expect("should have the page");
+        assert_eq!(pi.page.visit_count_local, 1);
+        assert_eq!(pi.page.last_visit_date_local, early_time.into());
+        assert_eq!(pi.page.visit_count_remote, 1);
+        assert_eq!(pi.page.last_visit_date_remote, late_time.into());
+
+        // Delete all visits.
+        conn.execute_cached(sql, &[(":row_id", &rid2)])?;
+        conn.execute_cached(sql, &[(":row_id", &rid4)])?;
+        // It may turn out that we also delete the place after deleting all
+        // visits, but for now we don't - check the values are sane though.
+        pi = fetch_page_info(&conn, &url)?.expect("should have the page");
+        assert_eq!(pi.page.visit_count_local, 0);
+        assert_eq!(pi.page.last_visit_date_local, Timestamp(0));
+        assert_eq!(pi.page.visit_count_remote, 0);
+        assert_eq!(pi.page.last_visit_date_remote, Timestamp(0));
+        Ok(())
+    }
+
+    #[test]
+    fn test_get_visited() -> Result<()> {
+        let _ = env_logger::try_init();
+        let conn = PlacesDb::open_in_memory(ConnectionType::ReadWrite)?;
+
+        let unicode_in_path = "http://www.example.com/tëst😀abc";
+        let escaped_unicode_in_path = "http://www.example.com/t%C3%ABst%F0%9F%98%80abc";
+
+        let unicode_in_domain = "http://www.exämple😀123.com";
+        let escaped_unicode_in_domain = "http://www.xn--exmple123-w2a24222l.com";
+
+        let to_add = [
+            "https://www.example.com/1".to_string(),
+            "https://www.example.com/12".to_string(),
+            "https://www.example.com/123".to_string(),
+            "https://www.example.com/1234".to_string(),
+            "https://www.mozilla.com".to_string(),
+            "https://www.firefox.com".to_string(),
+            unicode_in_path.to_string() + "/1",
+            escaped_unicode_in_path.to_string() + "/2",
+            unicode_in_domain.to_string() + "/1",
+            escaped_unicode_in_domain.to_string() + "/2",
+        ];
+
+        for item in &to_add {
+            apply_observation(
+                &conn,
+                VisitObservation::new(Url::parse(item).unwrap()).with_visit_type(VisitType::Link),
+            )?;
+        }
+
+        let to_search = [
+            ("https://www.example.com".to_string(), false),
+            ("https://www.example.com/1".to_string(), true),
+            ("https://www.example.com/12".to_string(), true),
+            ("https://www.example.com/123".to_string(), true),
+            ("https://www.example.com/1234".to_string(), true),
+            ("https://www.example.com/12345".to_string(), false),
+            ("https://www.mozilla.com".to_string(), true),
+            ("https://www.firefox.com".to_string(), true),
+            ("https://www.mozilla.org".to_string(), false),
+            // dupes should still work!
+            ("https://www.example.com/1234".to_string(), true),
+            ("https://www.example.com/12345".to_string(), false),
+            // The unicode URLs should work when escaped the way we
+            // encountered them
+            (unicode_in_path.to_string() + "/1", true),
+            (escaped_unicode_in_path.to_string() + "/2", true),
+            (unicode_in_domain.to_string() + "/1", true),
+            (escaped_unicode_in_domain.to_string() + "/2", true),
+            // But also the other way.
+            (unicode_in_path.to_string() + "/2", true),
+            (escaped_unicode_in_path.to_string() + "/1", true),
+            (unicode_in_domain.to_string() + "/2", true),
+            (escaped_unicode_in_domain.to_string() + "/1", true),
+        ];
+
+        let urls = to_search
+            .iter()
+            .map(|(url, _expect)| Url::parse(url).unwrap())
+            .collect::<Vec<_>>();
+
+        let visited = get_visited(&conn, urls).unwrap();
+
+        assert_eq!(visited.len(), to_search.len());
+
+        for (i, &did_see) in visited.iter().enumerate() {
+            assert_eq!(
+                did_see,
+                to_search[i].1,
+                "Wrong value in get_visited for '{}' (idx {}), want {}, have {}",
+                to_search[i].0,
+                i, // idx is logged because some things are repeated
+                to_search[i].1,
+                did_see
+            );
+        }
+        Ok(())
+    }
+
+    #[test]
+    fn test_get_visited_into() {
+        let _ = env_logger::try_init();
+        let conn = PlacesDb::open_in_memory(ConnectionType::ReadWrite).unwrap();
+
+        let u0 = Url::parse("https://www.example.com/1").unwrap();
+        let u1 = Url::parse("https://www.example.com/12").unwrap();
+        let u2 = Url::parse("https://www.example.com/123").unwrap();
+
+        let to_add = [&u0, &u1, &u2];
+        for &item in &to_add {
+            apply_observation(
+                &conn,
+                VisitObservation::new(item.clone()).with_visit_type(VisitType::Link),
+            )
+            .unwrap();
+        }
+
+        let mut results = [false; 10];
+
+        let get_visited_request = [
+            // 0 blank
+            (2, u1.clone()),
+            (1, u0),
+            // 3 blank
+            (4, u2),
+            // 5 blank
+            // Note: url for 6 is not visited.
+            (6, Url::parse("https://www.example.com/1234").unwrap()),
+            // 7 blank
+            // Note: dupe is allowed
+            (8, u1),
+            // 9 is blank
+        ];
+
+        get_visited_into(&conn, &get_visited_request, &mut results).unwrap();
+        let expect = [
+            false, // 0
+            true,  // 1
+            true,  // 2
+            false, // 3
+            true,  // 4
+            false, // 5
+            false, // 6
+            false, // 7
+            true,  // 8
+            false, // 9
+        ];
+
+        assert_eq!(expect, results);
+    }
+
+    #[test]
+    fn test_delete_visited() {
+        let conn = PlacesDb::open_in_memory(ConnectionType::ReadWrite).expect("no memory db");
+        let late: Timestamp = SystemTime::now().into();
+        let early: Timestamp = (SystemTime::now() - Duration::from_secs(30)).into();
+        let url1 = Url::parse("https://www.example.com/1").unwrap();
+        let url2 = Url::parse("https://www.example.com/2").unwrap();
+        let url3 = Url::parse("https://www.example.com/3").unwrap();
+        let url4 = Url::parse("https://www.example.com/4").unwrap();
+        // (url, when)
+        let to_add = [
+            // 2 visits to "https://www.example.com/1", one early, one late.
+            (&url1, early),
+            (&url1, late),
+            // One to url2, only late.
+            (&url2, late),
+            // One to url2, only early.
+            (&url3, early),
+            // One to url4, only late - this will have SyncStatus::Normal
+            (&url4, late),
+        ];
+
+        for &(url, when) in &to_add {
+            apply_observation(
+                &conn,
+                VisitObservation::new(url.clone())
+                    .with_at(when)
+                    .with_visit_type(VisitType::Link),
+            )
+            .expect("Should apply visit");
+        }
+        // Check we added what we think we did.
+        let pi = fetch_page_info(&conn, &url1)
+            .expect("should work")
+            .expect("should get the page");
+        assert_eq!(pi.page.visit_count_local, 2);
+
+        let pi2 = fetch_page_info(&conn, &url2)
+            .expect("should work")
+            .expect("should get the page");
+        assert_eq!(pi2.page.visit_count_local, 1);
+
+        let pi3 = fetch_page_info(&conn, &url3)
+            .expect("should work")
+            .expect("should get the page");
+        assert_eq!(pi3.page.visit_count_local, 1);
+
+        let pi4 = fetch_page_info(&conn, &url4)
+            .expect("should work")
+            .expect("should get the page");
+        assert_eq!(pi4.page.visit_count_local, 1);
+
+        conn.execute_cached(
+            &format!(
+                "UPDATE moz_places set sync_status = {}
+                 WHERE url = 'https://www.example.com/4'",
+                (SyncStatus::Normal as u8)
+            ),
+            [],
+        )
+        .expect("should work");
+
+        // Delete some.
+        delete_visits_between(&conn, late, Timestamp::now()).expect("should work");
+        // should have removed one of the visits to /1
+        let pi = fetch_page_info(&conn, &url1)
+            .expect("should work")
+            .expect("should get the page");
+        assert_eq!(pi.page.visit_count_local, 1);
+
+        // should have removed all the visits to /2
+        assert!(fetch_page_info(&conn, &url2)
+            .expect("should work")
+            .is_none());
+
+        // Should still have the 1 visit to /3
+        let pi3 = fetch_page_info(&conn, &url3)
+            .expect("should work")
+            .expect("should get the page");
+        assert_eq!(pi3.page.visit_count_local, 1);
+
+        // should have removed all the visits to /4
+        assert!(fetch_page_info(&conn, &url4)
+            .expect("should work")
+            .is_none());
+        // should be a tombstone for url4 and no others.
+        assert_eq!(get_tombstone_count(&conn), 1);
+        // XXX - test frecency?
+        // XXX - origins?
+    }
+
+    #[test]
+    fn test_change_counter() -> Result<()> {
+        let _ = env_logger::try_init();
+        let mut conn = PlacesDb::open_in_memory(ConnectionType::ReadWrite).expect("no memory db");
+        let mut pi = get_observed_page(&mut conn, "http://example.com")?;
+        // A new observation with just a title (ie, no visit) should update it.
+        apply_observation(
+            &conn,
+            VisitObservation::new(pi.url.clone()).with_title(Some("new title".into())),
+        )?;
+        pi = fetch_page_info(&conn, &pi.url)?
+            .expect("page should exist")
+            .page;
+        assert_eq!(pi.title, "new title");
+        assert_eq!(pi.preview_image_url, None);
+        assert_eq!(pi.sync_change_counter, 2);
+        // An observation with just a preview_image_url should not update it.
+        apply_observation(
+            &conn,
+            VisitObservation::new(pi.url.clone()).with_preview_image_url(Some(
+                Url::parse("https://www.example.com/preview.png").unwrap(),
+            )),
+        )?;
+        pi = fetch_page_info(&conn, &pi.url)?
+            .expect("page should exist")
+            .page;
+        assert_eq!(pi.title, "new title");
+        assert_eq!(
+            pi.preview_image_url,
+            Some(Url::parse("https://www.example.com/preview.png").expect("parsed"))
+        );
+        assert_eq!(pi.sync_change_counter, 2);
+        Ok(())
+    }
+
+    #[test]
+    fn test_status_columns() -> Result<()> {
+        let _ = env_logger::try_init();
+        let mut conn = PlacesDb::open_in_memory(ConnectionType::ReadWrite)?;
+        // A page with "normal" and a change counter.
+        let mut pi = get_observed_page(&mut conn, "http://example.com/1")?;
+        assert_eq!(pi.sync_change_counter, 1);
+        conn.execute_cached(
+            "UPDATE moz_places
+                                   SET frecency = 100
+                                   WHERE id = :id",
+            &[(":id", &pi.row_id)],
+        )?;
+        // A page with "new" and no change counter.
+        let mut pi2 = get_observed_page(&mut conn, "http://example.com/2")?;
+        conn.execute_cached(
+            "UPDATE moz_places
+                SET sync_status = :status,
+                sync_change_counter = 0,
+                frecency = 50
+            WHERE id = :id",
+            &[
+                (":status", &(SyncStatus::New as u8) as &dyn rusqlite::ToSql),
+                (":id", &pi2.row_id),
+            ],
+        )?;
+
+        // A second page with "new", a change counter (which will be ignored
+        // as we will limit such that this isn't sent) and a low frecency.
+        let mut pi3 = get_observed_page(&mut conn, "http://example.com/3")?;
+        conn.execute_cached(
+            "UPDATE moz_places
+                SET sync_status = :status,
+                sync_change_counter = 1,
+                frecency = 10
+            WHERE id = :id",
+            &[
+                (":status", &(SyncStatus::New as u8) as &dyn ToSql),
+                (":id", &pi3.row_id),
+            ],
+        )?;
+
+        let outgoing = fetch_outgoing(&conn, 2, 3)?;
+        assert_eq!(outgoing.len(), 2, "should have restricted to the limit");
+        // want pi or pi2 (but order is indeterminate) and this seems simpler than sorting.
+        assert!(outgoing[0].envelope.id != outgoing[1].envelope.id);
+        assert!(outgoing[0].envelope.id == pi.guid || outgoing[0].envelope.id == pi2.guid);
+        assert!(outgoing[1].envelope.id == pi.guid || outgoing[1].envelope.id == pi2.guid);
+        finish_outgoing(&conn)?;
+
+        pi = fetch_page_info(&conn, &pi.url)?
+            .expect("page should exist")
+            .page;
+        assert_eq!(pi.sync_change_counter, 0);
+        pi2 = fetch_page_info(&conn, &pi2.url)?
+            .expect("page should exist")
+            .page;
+        assert_eq!(pi2.sync_change_counter, 0);
+        assert_eq!(pi2.sync_status, SyncStatus::Normal);
+
+        // pi3 wasn't uploaded, but it should still have been changed to
+        // Normal and had the change counter reset.
+        pi3 = fetch_page_info(&conn, &pi3.url)?
+            .expect("page should exist")
+            .page;
+        assert_eq!(pi3.sync_change_counter, 0);
+        assert_eq!(pi3.sync_status, SyncStatus::Normal);
+        Ok(())
+    }
+
+    #[test]
+    fn test_delete_visits_for() -> Result<()> {
+        use crate::storage::bookmarks::{
+            self, BookmarkPosition, BookmarkRootGuid, InsertableBookmark,
+        };
+
+        let db = PlacesDb::open_in_memory(ConnectionType::ReadWrite)?;
+
+        struct TestPage {
+            href: &'static str,
+            synced: bool,
+            bookmark_title: Option<&'static str>,
+            keyword: Option<&'static str>,
+        }
+
+        fn page_has_tombstone(conn: &PlacesDb, guid: &SyncGuid) -> Result<bool> {
+            let exists = conn
+                .try_query_one::<bool, _>(
+                    "SELECT EXISTS(SELECT 1 FROM moz_places_tombstones
+                                   WHERE guid = :guid)",
+                    rusqlite::named_params! { ":guid" : guid },
+                    false,
+                )?
+                .unwrap_or_default();
+            Ok(exists)
+        }
+
+        fn page_has_visit_tombstones(conn: &PlacesDb, page_id: RowId) -> Result<bool> {
+            let exists = conn
+                .try_query_one::<bool, _>(
+                    "SELECT EXISTS(SELECT 1 FROM moz_historyvisit_tombstones
+                                   WHERE place_id = :page_id)",
+                    rusqlite::named_params! { ":page_id": page_id },
+                    false,
+                )?
+                .unwrap_or_default();
+            Ok(exists)
+        }
+
+        let pages = &[
+            // A is synced and has a bookmark, so we should insert tombstones
+            // for all its visits.
+            TestPage {
+                href: "http://example.com/a",
+                synced: true,
+                bookmark_title: Some("A"),
+                keyword: None,
+            },
+            // B is synced but only has visits, so we should insert a tombstone
+            // for the page.
+            TestPage {
+                href: "http://example.com/b",
+                synced: true,
+                bookmark_title: None,
+                keyword: None,
+            },
+            // C isn't synced but has a keyword, so we should delete all its
+            // visits, but not the page.
+            TestPage {
+                href: "http://example.com/c",
+                synced: false,
+                bookmark_title: None,
+                keyword: Some("one"),
+            },
+            // D isn't synced and only has visits, so we should delete it
+            // entirely.
+            TestPage {
+                href: "http://example.com/d",
+                synced: false,
+                bookmark_title: None,
+                keyword: None,
+            },
+        ];
+        for page in pages {
+            let url = Url::parse(page.href)?;
+            let obs = VisitObservation::new(url.clone())
+                .with_visit_type(VisitType::Link)
+                .with_at(Some(SystemTime::now().into()));
+            apply_observation(&db, obs)?;
+
+            if page.synced {
+                db.execute_cached(
+                    &format!(
+                        "UPDATE moz_places
+                             SET sync_status = {}
+                         WHERE url_hash = hash(:url) AND
+                               url = :url",
+                        (SyncStatus::Normal as u8)
+                    ),
+                    &[(":url", &url.as_str())],
+                )?;
+            }
+
+            if let Some(title) = page.bookmark_title {
+                bookmarks::insert_bookmark(
+                    &db,
+                    InsertableBookmark {
+                        parent_guid: BookmarkRootGuid::Unfiled.into(),
+                        position: BookmarkPosition::Append,
+                        date_added: None,
+                        last_modified: None,
+                        guid: None,
+                        url: url.clone(),
+                        title: Some(title.to_owned()),
+                    }
+                    .into(),
+                )?;
+            }
+
+            if let Some(keyword) = page.keyword {
+                // We don't have a public API for inserting keywords, so just
+                // write to the database directly.
+                db.execute_cached(
+                    "INSERT INTO moz_keywords(place_id, keyword)
+                     SELECT id, :keyword
+                     FROM moz_places
+                     WHERE url_hash = hash(:url) AND
+                           url = :url",
+                    &[(":url", &url.as_str()), (":keyword", &keyword)],
+                )?;
+            }
+
+            // Now delete all visits.
+            let (info, _) =
+                fetch_visits(&db, &url, 0)?.expect("Should return visits for test page");
+            delete_visits_for(&db, &info.guid)?;
+
+            match (
+                page.synced,
+                page.bookmark_title.is_some() || page.keyword.is_some(),
+            ) {
+                (true, true) => {
+                    let (_, visits) = fetch_visits(&db, &url, 0)?
+                        .expect("Shouldn't delete synced page with foreign count");
+                    assert!(
+                        visits.is_empty(),
+                        "Should delete all visits from synced page with foreign count"
+                    );
+                    assert!(
+                        !page_has_tombstone(&db, &info.guid)?,
+                        "Shouldn't insert tombstone for synced page with foreign count"
+                    );
+                    assert!(
+                        page_has_visit_tombstones(&db, info.row_id)?,
+                        "Should insert visit tombstones for synced page with foreign count"
+                    );
+                }
+                (true, false) => {
+                    assert!(
+                        fetch_visits(&db, &url, 0)?.is_none(),
+                        "Should delete synced page"
+                    );
+                    assert!(
+                        page_has_tombstone(&db, &info.guid)?,
+                        "Should insert tombstone for synced page"
+                    );
+                    assert!(
+                        !page_has_visit_tombstones(&db, info.row_id)?,
+                        "Shouldn't insert visit tombstones for synced page"
+                    );
+                }
+                (false, true) => {
+                    let (_, visits) = fetch_visits(&db, &url, 0)?
+                        .expect("Shouldn't delete page with foreign count");
+                    assert!(
+                        visits.is_empty(),
+                        "Should delete all visits from page with foreign count"
+                    );
+                    assert!(
+                        !page_has_tombstone(&db, &info.guid)?,
+                        "Shouldn't insert tombstone for page with foreign count"
+                    );
+                    assert!(
+                        !page_has_visit_tombstones(&db, info.row_id)?,
+                        "Shouldn't insert visit tombstones for page with foreign count"
+                    );
+                }
+                (false, false) => {
+                    assert!(fetch_visits(&db, &url, 0)?.is_none(), "Should delete page");
+                    assert!(
+                        !page_has_tombstone(&db, &info.guid)?,
+                        "Shouldn't insert tombstone for page"
+                    );
+                    assert!(
+                        !page_has_visit_tombstones(&db, info.row_id)?,
+                        "Shouldn't insert visit tombstones for page"
+                    );
+                }
+            }
+        }
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_tombstones() -> Result<()> {
+        let _ = env_logger::try_init();
+        let db = PlacesDb::open_in_memory(ConnectionType::ReadWrite)?;
+        let url = Url::parse("https://example.com")?;
+        let obs = VisitObservation::new(url.clone())
+            .with_visit_type(VisitType::Link)
+            .with_at(Some(SystemTime::now().into()));
+        apply_observation(&db, obs)?;
+        let guid = url_to_guid(&db, &url)?.expect("should exist");
+
+        delete_visits_for(&db, &guid)?;
+
+        // status was "New", so expect no tombstone.
+        assert_eq!(get_tombstone_count(&db), 0);
+
+        let obs = VisitObservation::new(url.clone())
+            .with_visit_type(VisitType::Link)
+            .with_at(Some(SystemTime::now().into()));
+        apply_observation(&db, obs)?;
+        let new_guid = url_to_guid(&db, &url)?.expect("should exist");
+
+        // Set the status to normal
+        db.execute_cached(
+            &format!(
+                "UPDATE moz_places
+                    SET sync_status = {}
+                 WHERE guid = :guid",
+                (SyncStatus::Normal as u8)
+            ),
+            &[(":guid", &new_guid)],
+        )?;
+        delete_visits_for(&db, &new_guid)?;
+        assert_eq!(get_tombstone_count(&db), 1);
+        Ok(())
+    }
+
+    #[test]
+    fn test_reset() -> Result<()> {
+        fn mark_all_as_synced(db: &PlacesDb) -> Result<()> {
+            db.execute_cached(
+                &format!(
+                    "UPDATE moz_places set sync_status = {}",
+                    (SyncStatus::Normal as u8)
+                ),
+                [],
+            )?;
+            Ok(())
+        }
+
+        let _ = env_logger::try_init();
+        let mut conn = PlacesDb::open_in_memory(ConnectionType::ReadWrite)?;
+
+        // Add Sync metadata keys, to ensure they're reset.
+        put_meta(&conn, GLOBAL_SYNCID_META_KEY, &"syncAAAAAAAA")?;
+        put_meta(&conn, COLLECTION_SYNCID_META_KEY, &"syncBBBBBBBB")?;
+        put_meta(&conn, LAST_SYNC_META_KEY, &12345)?;
+
+        // Delete everything first, to ensure we keep the high-water mark
+        // (see #2445 for a discussion about that).
+        delete_everything(&conn)?;
+
+        let mut pi = get_observed_page(&mut conn, "http://example.com")?;
+        mark_all_as_synced(&conn)?;
+        pi = fetch_page_info(&conn, &pi.url)?
+            .expect("page should exist")
+            .page;
+        assert_eq!(pi.sync_change_counter, 1);
+        assert_eq!(pi.sync_status, SyncStatus::Normal);
+
+        let sync_ids = CollSyncIds {
+            global: SyncGuid::random(),
+            coll: SyncGuid::random(),
+        };
+        history_sync::reset(&conn, &EngineSyncAssociation::Connected(sync_ids.clone()))?;
+
+        assert_eq!(
+            get_meta::<SyncGuid>(&conn, GLOBAL_SYNCID_META_KEY)?,
+            Some(sync_ids.global)
+        );
+        assert_eq!(
+            get_meta::<SyncGuid>(&conn, COLLECTION_SYNCID_META_KEY)?,
+            Some(sync_ids.coll)
+        );
+        assert_eq!(get_meta::<i64>(&conn, LAST_SYNC_META_KEY)?, Some(0));
+        assert!(get_meta::<Timestamp>(&conn, DELETION_HIGH_WATER_MARK_META_KEY)?.is_some());
+
+        pi = fetch_page_info(&conn, &pi.url)?
+            .expect("page should exist")
+            .page;
+        assert_eq!(pi.sync_change_counter, 0);
+        assert_eq!(pi.sync_status, SyncStatus::New);
+        // Ensure we are going to do a full re-upload after a reset.
+        let outgoing = fetch_outgoing(&conn, 100, 100)?;
+        assert_eq!(outgoing.len(), 1);
+
+        mark_all_as_synced(&conn)?;
+        assert!(fetch_outgoing(&conn, 100, 100)?.is_empty());
+        // ...
+
+        // Now simulate a reset on disconnect, and verify we've removed all Sync
+        // metadata again.
+        history_sync::reset(&conn, &EngineSyncAssociation::Disconnected)?;
+
+        assert_eq!(get_meta::<SyncGuid>(&conn, GLOBAL_SYNCID_META_KEY)?, None);
+        assert_eq!(
+            get_meta::<SyncGuid>(&conn, COLLECTION_SYNCID_META_KEY)?,
+            None
+        );
+        assert_eq!(get_meta::<i64>(&conn, LAST_SYNC_META_KEY)?, Some(0));
+        assert!(get_meta::<Timestamp>(&conn, DELETION_HIGH_WATER_MARK_META_KEY)?.is_some());
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_fetch_visits() -> Result<()> {
+        let _ = env_logger::try_init();
+        let mut conn = PlacesDb::open_in_memory(ConnectionType::ReadWrite).expect("no memory db");
+        let pi = get_observed_page(&mut conn, "http://example.com/1")?;
+        assert_eq!(fetch_visits(&conn, &pi.url, 0).unwrap().unwrap().1.len(), 0);
+        assert_eq!(fetch_visits(&conn, &pi.url, 1).unwrap().unwrap().1.len(), 1);
+        Ok(())
+    }
+
+    #[test]
+    fn test_apply_synced_reconciliation() -> Result<()> {
+        let _ = env_logger::try_init();
+        let mut conn = PlacesDb::open_in_memory(ConnectionType::ReadWrite)?;
+        let mut pi = get_observed_page(&mut conn, "http://example.com/1")?;
+        assert_eq!(pi.sync_status, SyncStatus::New);
+        assert_eq!(pi.sync_change_counter, 1);
+        apply_synced_reconciliation(&conn, &pi.guid)?;
+        pi = fetch_page_info(&conn, &pi.url)?
+            .expect("page should exist")
+            .page;
+        assert_eq!(pi.sync_status, SyncStatus::Normal);
+        assert_eq!(pi.sync_change_counter, 0);
+        Ok(())
+    }
+
+    #[test]
+    fn test_apply_synced_deletion_new() -> Result<()> {
+        let _ = env_logger::try_init();
+        let mut conn = PlacesDb::open_in_memory(ConnectionType::ReadWrite)?;
+        let pi = get_observed_page(&mut conn, "http://example.com/1")?;
+        assert_eq!(pi.sync_status, SyncStatus::New);
+        apply_synced_deletion(&conn, &pi.guid)?;
+        assert!(
+            fetch_page_info(&conn, &pi.url)?.is_none(),
+            "should have been deleted"
+        );
+        assert_eq!(get_tombstone_count(&conn), 0, "should be no tombstones");
+        Ok(())
+    }
+
+    #[test]
+    fn test_apply_synced_deletion_normal() -> Result<()> {
+        let _ = env_logger::try_init();
+        let mut conn = PlacesDb::open_in_memory(ConnectionType::ReadWrite)?;
+        let pi = get_observed_page(&mut conn, "http://example.com/1")?;
+        assert_eq!(pi.sync_status, SyncStatus::New);
+        conn.execute_cached(
+            &format!(
+                "UPDATE moz_places set sync_status = {}",
+                (SyncStatus::Normal as u8)
+            ),
+            [],
+        )?;
+
+        apply_synced_deletion(&conn, &pi.guid)?;
+        assert!(
+            fetch_page_info(&conn, &pi.url)?.is_none(),
+            "should have been deleted"
+        );
+        assert_eq!(get_tombstone_count(&conn), 0, "should be no tombstones");
+        Ok(())
+    }
+
+    fn assert_tombstones(c: &PlacesDb, expected: &[(RowId, Timestamp)]) {
+        let mut expected: Vec<(RowId, Timestamp)> = expected.into();
+        expected.sort();
+        let mut tombstones = c
+            .query_rows_and_then(
+                "SELECT place_id, visit_date FROM moz_historyvisit_tombstones",
+                [],
+                |row| -> Result<_> { Ok((row.get::<_, RowId>(0)?, row.get::<_, Timestamp>(1)?)) },
+            )
+            .unwrap();
+        tombstones.sort();
+        assert_eq!(expected, tombstones);
+    }
+
+    #[test]
+    fn test_visit_tombstones() {
+        use url::Url;
+        let _ = env_logger::try_init();
+        let mut conn = PlacesDb::open_in_memory(ConnectionType::ReadWrite).unwrap();
+        let now = Timestamp::now();
+
+        let urls = &[
+            Url::parse("http://example.com/1").unwrap(),
+            Url::parse("http://example.com/2").unwrap(),
+        ];
+
+        let dates = &[
+            Timestamp(now.0 - 10000),
+            Timestamp(now.0 - 5000),
+            Timestamp(now.0),
+        ];
+        for url in urls {
+            for &date in dates {
+                get_custom_observed_page(&mut conn, url.as_str(), |o| o.with_at(date)).unwrap();
+            }
+        }
+        delete_place_visit_at_time(&conn, &urls[0], dates[1]).unwrap();
+        // Delete the most recent visit.
+        delete_visits_between(&conn, Timestamp(now.0 - 4000), Timestamp::now()).unwrap();
+
+        let (info0, visits0) = fetch_visits(&conn, &urls[0], 100).unwrap().unwrap();
+        assert_eq!(
+            visits0,
+            &[FetchedVisit {
+                is_local: true,
+                visit_date: dates[0],
+                visit_type: Some(VisitType::Link)
+            },]
+        );
+
+        assert!(
+            !visits0.iter().any(|v| v.visit_date == dates[1]),
+            "Shouldn't have deleted visit"
+        );
+
+        let (info1, mut visits1) = fetch_visits(&conn, &urls[1], 100).unwrap().unwrap();
+        visits1.sort_by_key(|v| v.visit_date);
+        // Shouldn't have most recent visit, but should still have the dates[1]
+        // visit, which should be uneffected.
+        assert_eq!(
+            visits1,
+            &[
+                FetchedVisit {
+                    is_local: true,
+                    visit_date: dates[0],
+                    visit_type: Some(VisitType::Link)
+                },
+                FetchedVisit {
+                    is_local: true,
+                    visit_date: dates[1],
+                    visit_type: Some(VisitType::Link)
+                },
+            ]
+        );
+
+        // Make sure syncing doesn't resurrect them.
+        apply_synced_visits(
+            &conn,
+            &info0.guid,
+            &info0.url,
+            &Some(info0.title.clone()),
+            // Ignore dates[0] since we know it's present.
+            &dates
+                .iter()
+                .map(|&d| HistoryRecordVisit {
+                    date: d.into(),
+                    transition: VisitType::Link as u8,
+                    unknown_fields: UnknownFields::new(),
+                })
+                .collect::<Vec<_>>(),
+            &UnknownFields::new(),
+        )
+        .unwrap();
+
+        let (info0, visits0) = fetch_visits(&conn, &urls[0], 100).unwrap().unwrap();
+        assert_eq!(
+            visits0,
+            &[FetchedVisit {
+                is_local: true,
+                visit_date: dates[0],
+                visit_type: Some(VisitType::Link)
+            }]
+        );
+
+        assert_tombstones(
+            &conn,
+            &[
+                (info0.row_id, dates[1]),
+                (info0.row_id, dates[2]),
+                (info1.row_id, dates[2]),
+            ],
+        );
+
+        // Delete the last visit from info0. This should delete the page entirely,
+        // as well as it's tomebstones.
+        delete_place_visit_at_time(&conn, &urls[0], dates[0]).unwrap();
+
+        assert!(fetch_visits(&conn, &urls[0], 100).unwrap().is_none());
+
+        assert_tombstones(&conn, &[(info1.row_id, dates[2])]);
+    }
+
+    #[test]
+    fn test_delete_local() {
+        use crate::frecency::DEFAULT_FRECENCY_SETTINGS;
+        use crate::storage::bookmarks::{
+            self, BookmarkPosition, BookmarkRootGuid, InsertableBookmark, InsertableItem,
+        };
+        use url::Url;
+        let _ = env_logger::try_init();
+        let mut conn = PlacesDb::open_in_memory(ConnectionType::ReadWrite).unwrap();
+        let ts = Timestamp::now().0 - 5_000_000;
+        // Add a number of visits across a handful of origins.
+        for o in 0..10 {
+            for i in 0..11 {
+                for t in 0..3 {
+                    get_custom_observed_page(
+                        &mut conn,
+                        &format!("http://www.example{}.com/{}", o, i),
+                        |obs| obs.with_at(Timestamp(ts + t * 1000 + i * 10_000 + o * 100_000)),
+                    )
+                    .unwrap();
+                }
+            }
+        }
+        // Add some bookmarks.
+        let b0 = (
+            SyncGuid::from("aaaaaaaaaaaa"),
+            Url::parse("http://www.example3.com/5").unwrap(),
+        );
+        let b1 = (
+            SyncGuid::from("bbbbbbbbbbbb"),
+            Url::parse("http://www.example6.com/10").unwrap(),
+        );
+        let b2 = (
+            SyncGuid::from("cccccccccccc"),
+            Url::parse("http://www.example9.com/4").unwrap(),
+        );
+        for (guid, url) in &[&b0, &b1, &b2] {
+            bookmarks::insert_bookmark(
+                &conn,
+                InsertableItem::Bookmark {
+                    b: InsertableBookmark {
+                        parent_guid: BookmarkRootGuid::Unfiled.into(),
+                        position: BookmarkPosition::Append,
+                        date_added: None,
+                        last_modified: None,
+                        guid: Some(guid.clone()),
+                        url: url.clone(),
+                        title: None,
+                    },
+                },
+            )
+            .unwrap();
+        }
+
+        // Make sure tombstone insertions stick.
+        conn.execute_all(&[
+            &format!(
+                "UPDATE moz_places set sync_status = {}",
+                (SyncStatus::Normal as u8)
+            ),
+            &format!(
+                "UPDATE moz_bookmarks set syncStatus = {}",
+                (SyncStatus::Normal as u8)
+            ),
+        ])
+        .unwrap();
+
+        // Ensure some various tombstones exist
+        delete_visits_for(
+            &conn,
+            &url_to_guid(&conn, &Url::parse("http://www.example8.com/5").unwrap())
+                .unwrap()
+                .unwrap(),
+        )
+        .unwrap();
+
+        delete_place_visit_at_time(
+            &conn,
+            &Url::parse("http://www.example10.com/5").unwrap(),
+            Timestamp(ts + 5 * 10_000 + 10 * 100_000),
+        )
+        .unwrap();
+
+        assert!(bookmarks::delete_bookmark(&conn, &b0.0).unwrap());
+
+        delete_everything(&conn).unwrap();
+
+        let places = conn
+            .query_rows_and_then(
+                "SELECT * FROM moz_places ORDER BY url ASC",
+                [],
+                PageInfo::from_row,
+            )
+            .unwrap();
+        assert_eq!(places.len(), 2);
+        assert_eq!(places[0].url, b1.1);
+        assert_eq!(places[1].url, b2.1);
+        for p in &places {
+            assert_eq!(
+                p.frecency,
+                DEFAULT_FRECENCY_SETTINGS.unvisited_bookmark_bonus
+            );
+            assert_eq!(p.visit_count_local, 0);
+            assert_eq!(p.visit_count_remote, 0);
+            assert_eq!(p.last_visit_date_local, Timestamp(0));
+            assert_eq!(p.last_visit_date_remote, Timestamp(0));
+        }
+
+        let counts_sql = [
+            (0i64, "SELECT COUNT(*) FROM moz_historyvisits"),
+            (2, "SELECT COUNT(*) FROM moz_origins"),
+            (7, "SELECT COUNT(*) FROM moz_bookmarks"), // the two we added + 5 roots
+            (1, "SELECT COUNT(*) FROM moz_bookmarks_deleted"),
+            (0, "SELECT COUNT(*) FROM moz_historyvisit_tombstones"),
+            (0, "SELECT COUNT(*) FROM moz_places_tombstones"),
+        ];
+        for (want, query) in &counts_sql {
+            assert_eq!(
+                *want,
+                conn.query_one::<i64>(query).unwrap(),
+                "Unexpected value for {}",
+                query
+            );
+        }
+    }
+
+    #[test]
+    fn test_delete_everything() {
+        use crate::storage::bookmarks::{
+            self, BookmarkPosition, BookmarkRootGuid, InsertableBookmark,
+        };
+        use url::Url;
+        let _ = env_logger::try_init();
+        let mut conn = PlacesDb::open_in_memory(ConnectionType::ReadWrite).unwrap();
+        let start = Timestamp::now();
+
+        let urls = &[
+            Url::parse("http://example.com/1").unwrap(),
+            Url::parse("http://example.com/2").unwrap(),
+            Url::parse("http://example.com/3").unwrap(),
+        ];
+
+        let dates = &[
+            Timestamp(start.0 - 10000),
+            Timestamp(start.0 - 5000),
+            Timestamp(start.0),
+        ];
+
+        for url in urls {
+            for &date in dates {
+                get_custom_observed_page(&mut conn, url.as_str(), |o| o.with_at(date)).unwrap();
+            }
+        }
+
+        bookmarks::insert_bookmark(
+            &conn,
+            InsertableBookmark {
+                parent_guid: BookmarkRootGuid::Unfiled.into(),
+                position: BookmarkPosition::Append,
+                date_added: None,
+                last_modified: None,
+                guid: Some("bookmarkAAAA".into()),
+                url: urls[2].clone(),
+                title: Some("A".into()),
+            }
+            .into(),
+        )
+        .expect("Should insert bookmark with URL 3");
+
+        conn.execute(
+            "WITH entries(url, input) AS (
+               VALUES(:url1, 'hi'), (:url3, 'bye')
+             )
+             INSERT INTO moz_inputhistory(place_id, input, use_count)
+             SELECT h.id, e.input, 1
+             FROM entries e
+             JOIN moz_places h ON h.url_hash = hash(e.url) AND
+                                  h.url = e.url",
+            &[(":url1", &urls[1].as_str()), (":url3", &urls[2].as_str())],
+        )
+        .expect("Should insert autocomplete history entries");
+
+        delete_everything(&conn).expect("Should delete everything except URL 3");
+
+        std::thread::sleep(std::time::Duration::from_millis(50));
+
+        // Should leave bookmarked URLs alone, and keep autocomplete history for
+        // those URLs.
+        let mut places_stmt = conn.prepare("SELECT url FROM moz_places").unwrap();
+        let remaining_urls: Vec<String> = places_stmt
+            .query_and_then([], |row| -> rusqlite::Result<_> { row.get::<_, String>(0) })
+            .expect("Should fetch remaining URLs")
+            .map(std::result::Result::unwrap)
+            .collect();
+        assert_eq!(remaining_urls, &["http://example.com/3"]);
+
+        let mut input_stmt = conn.prepare("SELECT input FROM moz_inputhistory").unwrap();
+        let remaining_inputs: Vec<String> = input_stmt
+            .query_and_then([], |row| -> rusqlite::Result<_> { row.get::<_, String>(0) })
+            .expect("Should fetch remaining autocomplete history entries")
+            .map(std::result::Result::unwrap)
+            .collect();
+        assert_eq!(remaining_inputs, &["bye"]);
+
+        bookmarks::delete_bookmark(&conn, &"bookmarkAAAA".into())
+            .expect("Should delete bookmark with URL 3");
+
+        delete_everything(&conn).expect("Should delete all URLs");
+
+        assert_eq!(
+            0,
+            conn.query_one::<i64>("SELECT COUNT(*) FROM moz_historyvisits")
+                .unwrap(),
+        );
+
+        apply_synced_visits(
+            &conn,
+            &SyncGuid::random(),
+            &url::Url::parse("http://www.example.com/123").unwrap(),
+            &None,
+            &[
+                HistoryRecordVisit {
+                    // This should make it in
+                    date: Timestamp::now().into(),
+                    transition: VisitType::Link as u8,
+                    unknown_fields: UnknownFields::new(),
+                },
+                HistoryRecordVisit {
+                    // This should not.
+                    date: start.into(),
+                    transition: VisitType::Link as u8,
+                    unknown_fields: UnknownFields::new(),
+                },
+            ],
+            &UnknownFields::new(),
+        )
+        .unwrap();
+        assert_eq!(
+            1,
+            conn.query_one::<i64>("SELECT COUNT(*) FROM moz_places")
+                .unwrap(),
+        );
+        // Only one visit should be applied.
+        assert_eq!(
+            1,
+            conn.query_one::<i64>("SELECT COUNT(*) FROM moz_historyvisits")
+                .unwrap(),
+        );
+
+        // Check that we don't insert a place if all visits are too old.
+        apply_synced_visits(
+            &conn,
+            &SyncGuid::random(),
+            &url::Url::parse("http://www.example.com/1234").unwrap(),
+            &None,
+            &[HistoryRecordVisit {
+                date: start.into(),
+                transition: VisitType::Link as u8,
+                unknown_fields: UnknownFields::new(),
+            }],
+            &UnknownFields::new(),
+        )
+        .unwrap();
+        // unchanged.
+        assert_eq!(
+            1,
+            conn.query_one::<i64>("SELECT COUNT(*) FROM moz_places")
+                .unwrap(),
+        );
+        assert_eq!(
+            1,
+            conn.query_one::<i64>("SELECT COUNT(*) FROM moz_historyvisits")
+                .unwrap(),
+        );
+    }
+
+    // See https://github.com/mozilla-mobile/fenix/issues/8531#issuecomment-590498878.
+    #[test]
+    fn test_delete_everything_deletes_origins() {
+        let conn = PlacesDb::open_in_memory(ConnectionType::ReadWrite).unwrap();
+
+        let u = Url::parse("https://www.reddit.com/r/climbing").expect("Should parse URL");
+        let ts = Timestamp::now().0 - 5_000_000;
+        let obs = VisitObservation::new(u)
+            .with_visit_type(VisitType::Link)
+            .with_at(Timestamp(ts));
+        apply_observation(&conn, obs).expect("Should apply observation");
+
+        delete_everything(&conn).expect("Should delete everything");
+
+        // We should clear all origins after deleting everythig.
+        let origin_count = conn
+            .query_one::<i64>("SELECT COUNT(*) FROM moz_origins")
+            .expect("Should fetch origin count");
+        assert_eq!(0, origin_count);
+    }
+
+    #[test]
+    fn test_apply_observation_updates_origins() {
+        let conn = PlacesDb::open_in_memory(ConnectionType::ReadWrite).unwrap();
+
+        let obs_for_a = VisitObservation::new(
+            Url::parse("https://example1.com/a").expect("Should parse URL A"),
+        )
+        .with_visit_type(VisitType::Link)
+        .with_at(Timestamp(Timestamp::now().0 - 5_000_000));
+        apply_observation(&conn, obs_for_a).expect("Should apply observation for A");
+
+        let obs_for_b = VisitObservation::new(
+            Url::parse("https://example2.com/b").expect("Should parse URL B"),
+        )
+        .with_visit_type(VisitType::Link)
+        .with_at(Timestamp(Timestamp::now().0 - 2_500_000));
+        apply_observation(&conn, obs_for_b).expect("Should apply observation for B");
+
+        let mut origins = conn
+            .prepare("SELECT host FROM moz_origins")
+            .expect("Should prepare origins statement")
+            .query_and_then([], |row| -> rusqlite::Result<_> { row.get::<_, String>(0) })
+            .expect("Should fetch all origins")
+            .map(|r| r.expect("Should get origin from row"))
+            .collect::<Vec<_>>();
+        origins.sort();
+        assert_eq!(origins, &["example1.com", "example2.com",]);
+    }
+
+    #[test]
+    fn test_preview_url() {
+        let conn = PlacesDb::open_in_memory(ConnectionType::ReadWrite).unwrap();
+
+        let url1 = Url::parse("https://www.example.com/").unwrap();
+        // Can observe preview url without an associated visit.
+        assert!(apply_observation(
+            &conn,
+            VisitObservation::new(url1.clone()).with_preview_image_url(Some(
+                Url::parse("https://www.example.com/image.png").unwrap()
+            ))
+        )
+        .unwrap()
+        .is_none());
+
+        // We don't get a visit id back above, so just assume an id of the corresponding moz_places entry.
+        let mut db_preview_url = conn
+            .query_row_and_then_cachable(
+                "SELECT preview_image_url FROM moz_places WHERE id = 1",
+                [],
+                |row| row.get(0),
+                false,
+            )
+            .unwrap();
+        assert_eq!(
+            Some("https://www.example.com/image.png".to_string()),
+            db_preview_url
+        );
+
+        // Observing a visit afterwards doesn't erase a preview url.
+        let visit_id = apply_observation(
+            &conn,
+            VisitObservation::new(url1).with_visit_type(VisitType::Link),
+        )
+        .unwrap();
+        assert!(visit_id.is_some());
+
+        db_preview_url = conn
+            .query_row_and_then_cachable(
+                "SELECT h.preview_image_url FROM moz_places AS h JOIN moz_historyvisits AS v ON h.id = v.place_id WHERE v.id = :id",
+                &[(":id", &visit_id.unwrap() as &dyn ToSql)],
+                |row| row.get(0),
+                false,
+            )
+            .unwrap();
+        assert_eq!(
+            Some("https://www.example.com/image.png".to_string()),
+            db_preview_url
+        );
+
+        // Can observe a preview image url as part of a visit observation.
+        let another_visit_id = apply_observation(
+            &conn,
+            VisitObservation::new(Url::parse("https://www.example.com/another/").unwrap())
+                .with_preview_image_url(Some(
+                    Url::parse("https://www.example.com/funky/image.png").unwrap(),
+                ))
+                .with_visit_type(VisitType::Link),
+        )
+        .unwrap();
+        assert!(another_visit_id.is_some());
+
+        db_preview_url = conn
+            .query_row_and_then_cachable(
+                "SELECT h.preview_image_url FROM moz_places AS h JOIN moz_historyvisits AS v ON h.id = v.place_id WHERE v.id = :id",
+                &[(":id", &another_visit_id.unwrap())],
+                |row| row.get(0),
+                false,
+            )
+            .unwrap();
+        assert_eq!(
+            Some("https://www.example.com/funky/image.png".to_string()),
+            db_preview_url
+        );
+    }
+
+    #[test]
+    fn test_long_strings() {
+        let _ = env_logger::try_init();
+        let conn = PlacesDb::open_in_memory(ConnectionType::ReadWrite).unwrap();
+        let mut url = "http://www.example.com".to_string();
+        while url.len() < crate::storage::URL_LENGTH_MAX {
+            url += "/garbage";
+        }
+        let maybe_row = apply_observation(
+            &conn,
+            VisitObservation::new(Url::parse(&url).unwrap())
+                .with_visit_type(VisitType::Link)
+                .with_at(Timestamp::now()),
+        )
+        .unwrap();
+        assert!(maybe_row.is_none(), "Shouldn't insert overlong URL");
+
+        let maybe_row_preview = apply_observation(
+            &conn,
+            VisitObservation::new(Url::parse("https://www.example.com/").unwrap())
+                .with_visit_type(VisitType::Link)
+                .with_preview_image_url(Url::parse(&url).unwrap()),
+        )
+        .unwrap();
+        assert!(
+            maybe_row_preview.is_some(),
+            "Shouldn't avoid a visit observation due to an overly long preview url"
+        );
+
+        let mut title = "example 1 2 3".to_string();
+        // Make sure whatever we use here surpasses the length.
+        while title.len() < crate::storage::TITLE_LENGTH_MAX + 10 {
+            title += " test test";
+        }
+        let maybe_visit_row = apply_observation(
+            &conn,
+            VisitObservation::new(Url::parse("http://www.example.com/123").unwrap())
+                .with_title(title.clone())
+                .with_visit_type(VisitType::Link)
+                .with_at(Timestamp::now()),
+        )
+        .unwrap();
+
+        assert!(maybe_visit_row.is_some());
+        let db_title: String = conn
+            .query_row_and_then_cachable(
+                "SELECT h.title FROM moz_places AS h JOIN moz_historyvisits AS v ON h.id = v.place_id WHERE v.id = :id",
+                &[(":id", &maybe_visit_row.unwrap())],
+                |row| row.get(0),
+                false,
+            )
+            .unwrap();
+        // Ensure what we get back the trimmed title.
+        assert_eq!(db_title.len(), crate::storage::TITLE_LENGTH_MAX);
+        assert!(title.starts_with(&db_title));
+    }
+
+    #[test]
+    fn test_get_visit_page_with_bound() {
+        use std::time::SystemTime;
+        let conn = PlacesDb::open_in_memory(ConnectionType::ReadWrite).expect("no memory db");
+        let now: Timestamp = SystemTime::now().into();
+        let now_u64 = now.0;
+        let now_i64 = now.0 as i64;
+        // (url, title, when, is_remote, (expected_always, expected_only_local)
+        let to_add = [
+            (
+                "https://www.example.com/0",
+                "older 2",
+                now_u64 - 200_200,
+                false,
+                (true, false),
+            ),
+            (
+                "https://www.example.com/1",
+                "older 1",
+                now_u64 - 200_100,
+                true,
+                (true, false),
+            ),
+            (
+                "https://www.example.com/2",
+                "same time",
+                now_u64 - 200_000,
+                false,
+                (true, false),
+            ),
+            (
+                "https://www.example.com/3",
+                "same time",
+                now_u64 - 200_000,
+                false,
+                (true, false),
+            ),
+            (
+                "https://www.example.com/4",
+                "same time",
+                now_u64 - 200_000,
+                false,
+                (true, false),
+            ),
+            (
+                "https://www.example.com/5",
+                "same time",
+                now_u64 - 200_000,
+                false,
+                (true, false),
+            ),
+            (
+                "https://www.example.com/6",
+                "same time",
+                now_u64 - 200_000,
+                false,
+                (true, false),
+            ),
+            (
+                "https://www.example.com/7",
+                "same time",
+                now_u64 - 200_000,
+                false,
+                (true, false),
+            ),
+            (
+                "https://www.example.com/8",
+                "same time",
+                now_u64 - 200_000,
+                false,
+                (true, false),
+            ),
+            (
+                "https://www.example.com/9",
+                "same time",
+                now_u64 - 200_000,
+                false,
+                (true, false),
+            ),
+            (
+                "https://www.example.com/10",
+                "more recent 2",
+                now_u64 - 199_000,
+                false,
+                (true, false),
+            ),
+            (
+                "https://www.example.com/11",
+                "more recent 1",
+                now_u64 - 198_000,
+                false,
+                (true, false),
+            ),
+        ];
+
+        for &(url, title, when, remote, _) in &to_add {
+            apply_observation(
+                &conn,
+                VisitObservation::new(Url::parse(url).unwrap())
+                    .with_title(title.to_owned())
+                    .with_at(Timestamp(when))
+                    .with_is_remote(remote)
+                    .with_visit_type(VisitType::Link),
+            )
+            .expect("Should apply visit");
+        }
+
+        // test when offset fall on a point where visited_date changes
+        let infos_with_bound =
+            get_visit_page_with_bound(&conn, now_i64 - 200_000, 8, 2, VisitTransitionSet::empty())
+                .unwrap();
+        let infos = infos_with_bound.infos;
+        assert_eq!(infos[0].title.as_ref().unwrap().as_str(), "older 1",);
+        assert!(infos[0].is_remote); // "older 1" is remote
+        assert_eq!(infos[1].title.as_ref().unwrap().as_str(), "older 2",);
+        assert!(!infos[1].is_remote); // "older 2" is local
+        assert_eq!(infos_with_bound.bound, now_i64 - 200_200,);
+        assert_eq!(infos_with_bound.offset, 1,);
+
+        // test when offset fall on one item before visited_date changes
+        let infos_with_bound =
+            get_visit_page_with_bound(&conn, now_i64 - 200_000, 7, 1, VisitTransitionSet::empty())
+                .unwrap();
+        assert_eq!(
+            infos_with_bound.infos[0].url,
+            Url::parse("https://www.example.com/9").unwrap(),
+        );
+
+        // test when offset fall on one item after visited_date changes
+        let infos_with_bound =
+            get_visit_page_with_bound(&conn, now_i64 - 200_000, 9, 1, VisitTransitionSet::empty())
+                .unwrap();
+        assert_eq!(
+            infos_with_bound.infos[0].title.as_ref().unwrap().as_str(),
+            "older 2",
+        );
+
+        // with a small page length, loop through items that have the same visited date
+        let count = 2;
+        let mut bound = now_i64 - 199_000;
+        let mut offset = 1;
+        for _i in 0..4 {
+            let infos_with_bound =
+                get_visit_page_with_bound(&conn, bound, offset, count, VisitTransitionSet::empty())
+                    .unwrap();
+            assert_eq!(
+                infos_with_bound.infos[0].title.as_ref().unwrap().as_str(),
+                "same time",
+            );
+            assert_eq!(
+                infos_with_bound.infos[1].title.as_ref().unwrap().as_str(),
+                "same time",
+            );
+            bound = infos_with_bound.bound;
+            offset = infos_with_bound.offset;
+        }
+        // bound and offset should have skipped the 8 items that have the same visited date
+        assert_eq!(bound, now_i64 - 200_000,);
+        assert_eq!(offset, 8,);
+
+        // when bound is now and offset is zero
+        let infos_with_bound =
+            get_visit_page_with_bound(&conn, now_i64, 0, 2, VisitTransitionSet::empty()).unwrap();
+        assert_eq!(
+            infos_with_bound.infos[0].title.as_ref().unwrap().as_str(),
+            "more recent 1",
+        );
+        assert_eq!(
+            infos_with_bound.infos[1].title.as_ref().unwrap().as_str(),
+            "more recent 2",
+        );
+        assert_eq!(infos_with_bound.bound, now_i64 - 199_000);
+        assert_eq!(infos_with_bound.offset, 1);
+    }
+
+    /// Test find_normal_visits_to_prune
+    #[test]
+    fn test_normal_visit_pruning() {
+        use std::time::{Duration, SystemTime};
+        let conn = PlacesDb::open_in_memory(ConnectionType::ReadWrite).expect("no memory db");
+        let one_day = Duration::from_secs(60 * 60 * 24);
+        let now: Timestamp = SystemTime::now().into();
+        let url = Url::parse("https://mozilla.com/").unwrap();
+
+        // Create 1 visit per day for the last 30 days
+        let mut visits: Vec<_> = (0..30)
+            .map(|i| {
+                apply_observation(
+                    &conn,
+                    VisitObservation::new(url.clone())
+                        .with_at(now.checked_sub(one_day * i))
+                        .with_visit_type(VisitType::Link),
+                )
+                .unwrap()
+                .unwrap()
+            })
+            .collect();
+        // Reverse visits so that they're oldest first
+        visits.reverse();
+
+        check_visits_to_prune(
+            &conn,
+            find_normal_visits_to_prune(&conn, 4, now).unwrap(),
+            &visits[..4],
+        );
+
+        // Only visits older than 7 days should be pruned
+        check_visits_to_prune(
+            &conn,
+            find_normal_visits_to_prune(&conn, 30, now).unwrap(),
+            &visits[..22],
+        );
+    }
+
+    /// Test find_exotic_visits_to_prune
+    #[test]
+    fn test_exotic_visit_pruning() {
+        use std::time::{Duration, SystemTime};
+        let conn = PlacesDb::open_in_memory(ConnectionType::ReadWrite).expect("no memory db");
+        let one_month = Duration::from_secs(60 * 60 * 24 * 31);
+        let now: Timestamp = SystemTime::now().into();
+        let short_url = Url::parse("https://mozilla.com/").unwrap();
+        let long_url = Url::parse(&format!(
+            "https://mozilla.com/{}",
+            (0..255).map(|_| "x").collect::<String>()
+        ))
+        .unwrap();
+
+        let visit_with_long_url = apply_observation(
+            &conn,
+            VisitObservation::new(long_url.clone())
+                .with_at(now.checked_sub(one_month * 2))
+                .with_visit_type(VisitType::Link),
+        )
+        .unwrap()
+        .unwrap();
+
+        let visit_for_download = apply_observation(
+            &conn,
+            VisitObservation::new(short_url)
+                .with_at(now.checked_sub(one_month * 3))
+                .with_visit_type(VisitType::Download),
+        )
+        .unwrap()
+        .unwrap();
+
+        // This visit should not be pruned, since it's too recent
+        apply_observation(
+            &conn,
+            VisitObservation::new(long_url)
+                .with_at(now.checked_sub(one_month))
+                .with_visit_type(VisitType::Download),
+        )
+        .unwrap()
+        .unwrap();
+
+        check_visits_to_prune(
+            &conn,
+            find_exotic_visits_to_prune(&conn, 2, now).unwrap(),
+            &[visit_for_download, visit_with_long_url],
+        );
+
+        // With limit = 1, it should pick the oldest visit
+        check_visits_to_prune(
+            &conn,
+            find_exotic_visits_to_prune(&conn, 1, now).unwrap(),
+            &[visit_for_download],
+        );
+
+        // If the limit exceeds the number of candidates, it should return as many as it can find
+        check_visits_to_prune(
+            &conn,
+            find_exotic_visits_to_prune(&conn, 3, now).unwrap(),
+            &[visit_for_download, visit_with_long_url],
+        );
+    }
+    /// Test that find_visits_to_prune correctly combines find_exotic_visits_to_prune and
+    /// find_normal_visits_to_prune
+    #[test]
+    fn test_visit_pruning() {
+        use std::time::{Duration, SystemTime};
+        let conn = PlacesDb::open_in_memory(ConnectionType::ReadWrite).expect("no memory db");
+        let one_month = Duration::from_secs(60 * 60 * 24 * 31);
+        let now: Timestamp = SystemTime::now().into();
+        let short_url = Url::parse("https://mozilla.com/").unwrap();
+        let long_url = Url::parse(&format!(
+            "https://mozilla.com/{}",
+            (0..255).map(|_| "x").collect::<String>()
+        ))
+        .unwrap();
+
+        // An exotic visit that should be pruned first, even if it's not the oldest
+        let excotic_visit = apply_observation(
+            &conn,
+            VisitObservation::new(long_url)
+                .with_at(now.checked_sub(one_month * 3))
+                .with_visit_type(VisitType::Link),
+        )
+        .unwrap()
+        .unwrap();
+
+        // Normal visits that should be pruned after excotic visits
+        let old_visit = apply_observation(
+            &conn,
+            VisitObservation::new(short_url.clone())
+                .with_at(now.checked_sub(one_month * 4))
+                .with_visit_type(VisitType::Link),
+        )
+        .unwrap()
+        .unwrap();
+        let really_old_visit = apply_observation(
+            &conn,
+            VisitObservation::new(short_url.clone())
+                .with_at(now.checked_sub(one_month * 12))
+                .with_visit_type(VisitType::Link),
+        )
+        .unwrap()
+        .unwrap();
+
+        // Newer visit that's too new to be pruned
+        apply_observation(
+            &conn,
+            VisitObservation::new(short_url)
+                .with_at(now.checked_sub(Duration::from_secs(100)))
+                .with_visit_type(VisitType::Link),
+        )
+        .unwrap()
+        .unwrap();
+
+        check_visits_to_prune(
+            &conn,
+            find_visits_to_prune(&conn, 2, now).unwrap(),
+            &[excotic_visit, really_old_visit],
+        );
+
+        check_visits_to_prune(
+            &conn,
+            find_visits_to_prune(&conn, 10, now).unwrap(),
+            &[excotic_visit, really_old_visit, old_visit],
+        );
+    }
+
+    fn check_visits_to_prune(
+        db: &PlacesDb,
+        visits_to_delete: Vec<VisitToDelete>,
+        correct_visits: &[RowId],
+    ) {
+        assert_eq!(
+            correct_visits.iter().collect::<HashSet<_>>(),
+            visits_to_delete
+                .iter()
+                .map(|v| &v.visit_id)
+                .collect::<HashSet<_>>()
+        );
+
+        let correct_place_ids: HashSet<RowId> = correct_visits
+            .iter()
+            .map(|vid| {
+                db.query_one(&format!(
+                    "SELECT v.place_id FROM moz_historyvisits v WHERE v.id = {}",
+                    vid
+                ))
+                .unwrap()
+            })
+            .collect();
+        assert_eq!(
+            correct_place_ids,
+            visits_to_delete
+                .iter()
+                .map(|v| v.page_id)
+                .collect::<HashSet<_>>()
+        );
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/places/storage/history/actions.rs.html b/book/rust-docs/src/places/storage/history/actions.rs.html new file mode 100644 index 0000000000..83045ec6c2 --- /dev/null +++ b/book/rust-docs/src/places/storage/history/actions.rs.html @@ -0,0 +1,985 @@ +actions.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+
/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+//! Structs to handle actions that mutate the History DB
+//!
+//! Places DB operations are complex and often involve several layers of changes which makes them
+//! hard to test.  For example, a function that deletes visits in a date range needs to:
+//!   - Calculate which visits to delete
+//!   - Delete the visits
+//!   - Insert visit tombstones
+//!   - Update the frecency for non-orphaned affected pages
+//!   - Delete orphaned pages
+//!   - Insert page tombstones for deleted synced pages
+//!
+//! Test all of this functionality at once leads to ugly tests that are hard to reason about and
+//! hard to change.  This is especially true since many steps have multiple branches which
+//! multiplies the complexity.
+//!
+//! This module is intended to split up operations to make testing simpler.  It defines an enum
+//! whose variants encapsulate particular actions.  We can use that enum to split operations into
+//! multiple parts, each which can be tested separately: code that calculates which actions to run
+//! and the code to run each action.
+//!
+//! Right now, only a couple function use this system, but hopefully we can use it more in the
+//! future.
+
+use super::{cleanup_pages, PageToClean};
+use crate::error::Result;
+use crate::{PlacesDb, RowId};
+use rusqlite::Row;
+use sql_support::ConnExt;
+use std::collections::HashSet;
+
+/// Enum whose variants describe a particular action on the DB
+#[derive(Debug, PartialEq, Eq)]
+pub(super) enum DbAction {
+    /// Delete visit rows from the DB.
+    DeleteVisitRows { visit_ids: HashSet<RowId> },
+    /// Recalculate the moz_places data, including frecency, after changes to their visits.  This
+    /// also deletes orphaned pages (pages whose visits have all been deleted).
+    RecalcPages { page_ids: HashSet<RowId> },
+    /// Delete rows in pending temp tables.  This should be done after any changes to the
+    /// moz_places table.
+    ///
+    /// Deleting from these tables triggers changes to the `moz_origins` table. See
+    /// `sql/create_shared_temp_tables.sql` and `sql/create_shared_triggers.sql` for details.
+    DeleteFromPendingTempTables,
+}
+
+impl DbAction {
+    pub(super) fn apply(self, db: &PlacesDb) -> Result<()> {
+        match self {
+            Self::DeleteVisitRows { visit_ids } => Self::delete_visit_rows(db, visit_ids),
+            Self::RecalcPages { page_ids } => Self::recalc_pages(db, page_ids),
+            Self::DeleteFromPendingTempTables => Self::delete_from_pending_temp_tables(db),
+        }
+    }
+
+    pub(super) fn apply_all(db: &PlacesDb, actions: Vec<Self>) -> Result<()> {
+        for action in actions {
+            action.apply(db)?;
+        }
+        Ok(())
+    }
+
+    fn delete_visit_rows(db: &PlacesDb, visit_ids: HashSet<RowId>) -> Result<()> {
+        sql_support::each_chunk(&Vec::from_iter(visit_ids), |chunk, _| -> Result<()> {
+            let var_repeat = sql_support::repeat_sql_vars(chunk.len());
+            let params = rusqlite::params_from_iter(chunk);
+            db.execute_cached(
+                &format!(
+                    "
+                    INSERT OR IGNORE INTO moz_historyvisit_tombstones(place_id, visit_date)
+                    SELECT place_id, visit_date
+                    FROM moz_historyvisits
+                    WHERE id IN ({})
+                    ",
+                    var_repeat,
+                ),
+                params.clone(),
+            )?;
+
+            db.execute_cached(
+                &format!("DELETE FROM moz_historyvisits WHERE id IN ({})", var_repeat),
+                params,
+            )?;
+            Ok(())
+        })?;
+        Ok(())
+    }
+
+    fn recalc_pages(db: &PlacesDb, page_ids: HashSet<RowId>) -> Result<()> {
+        let mut pages_to_clean: Vec<PageToClean> = vec![];
+        sql_support::each_chunk(&Vec::from_iter(page_ids), |chunk, _| -> Result<()> {
+            pages_to_clean.append(&mut db.query_rows_and_then_cached(
+                &format!(
+                    "SELECT
+                    id,
+                    (foreign_count != 0) AS has_foreign,
+                    ((last_visit_date_local + last_visit_date_remote) != 0) as has_visits,
+                    sync_status
+                FROM moz_places
+                WHERE id IN ({})",
+                    sql_support::repeat_sql_vars(chunk.len())
+                ),
+                rusqlite::params_from_iter(chunk),
+                PageToClean::from_row,
+            )?);
+            Ok(())
+        })?;
+        cleanup_pages(db, &pages_to_clean)?;
+        Ok(())
+    }
+
+    fn delete_from_pending_temp_tables(db: &PlacesDb) -> Result<()> {
+        crate::storage::delete_pending_temp_tables(db)
+    }
+}
+
+/// Stores a visit that we want to delete
+///
+/// We build a Vec of these from queries against the `moz_historyvisits` table, then transform that
+/// into a `Vec<DbAction>`.
+#[derive(Debug, PartialEq, Eq, Hash)]
+pub(super) struct VisitToDelete {
+    pub(super) visit_id: RowId,
+    pub(super) page_id: RowId,
+}
+
+impl VisitToDelete {
+    /// Create a VisitToDelete from a query row
+    ///
+    /// The query must that includes the `id` and `place_id` columns from `moz_historyvisits`.
+    pub(super) fn from_row(row: &Row<'_>) -> Result<Self> {
+        Ok(Self {
+            visit_id: row.get("id")?,
+            page_id: row.get("place_id")?,
+        })
+    }
+}
+
+/// Create a Vec<DbAction> from a Vec<VisitToDelete>
+pub(super) fn db_actions_from_visits_to_delete(
+    visits_to_delete: Vec<VisitToDelete>,
+) -> Vec<DbAction> {
+    let mut visit_ids = HashSet::<RowId>::new();
+    let mut page_ids = HashSet::<RowId>::new();
+    for visit_to_delete in visits_to_delete.into_iter() {
+        visit_ids.insert(visit_to_delete.visit_id);
+        page_ids.insert(visit_to_delete.page_id);
+    }
+    vec![
+        DbAction::DeleteVisitRows { visit_ids },
+        DbAction::RecalcPages { page_ids },
+        DbAction::DeleteFromPendingTempTables,
+    ]
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::observation::VisitObservation;
+    use crate::storage::bookmarks::*;
+    use crate::storage::history::apply_observation;
+    use crate::types::VisitType;
+    use crate::{frecency, ConnectionType, SyncStatus};
+    use rusqlite::params;
+    use rusqlite::types::{FromSql, ToSql};
+    use std::time::Duration;
+    use sync_guid::Guid;
+    use types::Timestamp;
+    use url::Url;
+
+    fn query_vec<T: FromSql>(conn: &PlacesDb, sql: &str, params: &[&dyn ToSql]) -> Vec<T> {
+        conn.prepare(sql)
+            .unwrap()
+            .query_map(params, |row| row.get(0))
+            .unwrap()
+            .collect::<rusqlite::Result<Vec<T>>>()
+            .unwrap()
+    }
+
+    fn query_vec_pairs<T: FromSql, V: FromSql>(
+        conn: &PlacesDb,
+        sql: &str,
+        params: &[&dyn ToSql],
+    ) -> Vec<(T, V)> {
+        conn.prepare(sql)
+            .unwrap()
+            .query_map(params, |row| Ok((row.get(0)?, row.get(1)?)))
+            .unwrap()
+            .collect::<rusqlite::Result<Vec<(T, V)>>>()
+            .unwrap()
+    }
+
+    fn query_visit_ids(conn: &PlacesDb) -> Vec<RowId> {
+        query_vec(conn, "SELECT id FROM moz_historyvisits ORDER BY id", &[])
+    }
+
+    fn query_visit_tombstones(conn: &PlacesDb) -> Vec<(RowId, Timestamp)> {
+        query_vec_pairs(
+            conn,
+            "
+            SELECT place_id, visit_date
+            FROM moz_historyvisit_tombstones
+            ORDER BY place_id, visit_date
+            ",
+            &[],
+        )
+    }
+
+    fn query_page_ids(conn: &PlacesDb) -> Vec<RowId> {
+        query_vec(conn, "SELECT id FROM moz_places ORDER BY id", &[])
+    }
+
+    fn query_page_tombstones(conn: &PlacesDb) -> Vec<Guid> {
+        query_vec(
+            conn,
+            "SELECT guid FROM moz_places_tombstones ORDER BY guid",
+            &[],
+        )
+    }
+
+    struct TestPage {
+        id: RowId,
+        guid: Guid,
+        url: Url,
+        visit_ids: Vec<RowId>,
+        visit_dates: Vec<Timestamp>,
+    }
+
+    impl TestPage {
+        fn new(conn: &mut PlacesDb, url: &str, visit_dates: &[Timestamp]) -> Self {
+            let url = Url::parse(url).unwrap();
+            let mut visit_ids = vec![];
+
+            for date in visit_dates {
+                visit_ids.push(
+                    apply_observation(
+                        conn,
+                        VisitObservation::new(url.clone())
+                            .with_visit_type(VisitType::Link)
+                            .with_at(*date),
+                    )
+                    .unwrap()
+                    .unwrap(),
+                );
+            }
+
+            let (id, guid) = conn
+                .query_row(
+                    "
+                SELECT p.id, p.guid
+                FROM moz_places p
+                JOIN moz_historyvisits v ON p.id = v.place_id
+                WHERE v.id = ?",
+                    [visit_ids[0]],
+                    |row| Ok((row.get(0)?, row.get(1)?)),
+                )
+                .unwrap();
+
+            Self {
+                id,
+                guid,
+                visit_ids,
+                url,
+                visit_dates: Vec::from_iter(visit_dates.iter().cloned()),
+            }
+        }
+
+        fn set_sync_status(&self, conn: &PlacesDb, sync_status: SyncStatus) {
+            conn.execute(
+                "UPDATE moz_places SET sync_status = ? WHERE id = ?",
+                params! {sync_status, self.id },
+            )
+            .unwrap();
+        }
+
+        fn query_frecency(&self, conn: &PlacesDb) -> i32 {
+            conn.query_row(
+                "SELECT frecency FROM moz_places WHERE id = ?",
+                [self.id],
+                |row| row.get::<usize, i32>(0),
+            )
+            .unwrap()
+        }
+
+        fn calculate_frecency(&self, conn: &PlacesDb) -> i32 {
+            frecency::calculate_frecency(
+                conn,
+                &frecency::DEFAULT_FRECENCY_SETTINGS,
+                self.id.0,
+                None,
+            )
+            .unwrap()
+        }
+
+        fn bookmark(&self, conn: &PlacesDb, title: &str) {
+            insert_bookmark(
+                conn,
+                InsertableBookmark {
+                    parent_guid: BookmarkRootGuid::Unfiled.into(),
+                    position: BookmarkPosition::Append,
+                    date_added: None,
+                    last_modified: None,
+                    guid: None,
+                    url: self.url.clone(),
+                    title: Some(title.to_owned()),
+                }
+                .into(),
+            )
+            .unwrap();
+        }
+    }
+
+    #[test]
+    fn test_delete_visit_rows() {
+        let mut conn = PlacesDb::open_in_memory(ConnectionType::ReadWrite).unwrap();
+        let yesterday = Timestamp::now()
+            .checked_sub(Duration::from_secs(60 * 60 * 24))
+            .unwrap();
+        let page = TestPage::new(
+            &mut conn,
+            "http://example.com/",
+            &[
+                Timestamp(yesterday.0 + 100),
+                Timestamp(yesterday.0 + 200),
+                Timestamp(yesterday.0 + 300),
+            ],
+        );
+
+        DbAction::DeleteVisitRows {
+            visit_ids: HashSet::from_iter([page.visit_ids[0], page.visit_ids[1]]),
+        }
+        .apply(&conn)
+        .unwrap();
+
+        assert_eq!(query_visit_ids(&conn), vec![page.visit_ids[2]]);
+        assert_eq!(
+            query_visit_tombstones(&conn),
+            vec![
+                (page.id, page.visit_dates[0]),
+                (page.id, page.visit_dates[1]),
+            ]
+        );
+    }
+
+    #[test]
+    fn test_recalc_pages() {
+        let mut conn = PlacesDb::open_in_memory(ConnectionType::ReadWrite).unwrap();
+        let yesterday = Timestamp::now()
+            .checked_sub(Duration::from_secs(60 * 60 * 24))
+            .unwrap();
+        let page_with_visits_left = TestPage::new(
+            &mut conn,
+            "http://example.com/1",
+            &[Timestamp(yesterday.0 + 100), Timestamp(yesterday.0 + 200)],
+        );
+        let page_with_no_visits_unsynced = TestPage::new(
+            &mut conn,
+            "http://example.com/2",
+            &[Timestamp(yesterday.0 + 300)],
+        );
+        let page_with_no_visits_synced = TestPage::new(
+            &mut conn,
+            "http://example.com/2",
+            &[Timestamp(yesterday.0 + 400)],
+        );
+        let page_with_no_visits_bookmarked = TestPage::new(
+            &mut conn,
+            "http://example.com/3",
+            &[Timestamp(yesterday.0 + 500)],
+        );
+
+        page_with_no_visits_synced.set_sync_status(&conn, SyncStatus::Normal);
+        page_with_no_visits_bookmarked.bookmark(&conn, "My Bookmark");
+
+        DbAction::DeleteVisitRows {
+            visit_ids: HashSet::from_iter([
+                page_with_visits_left.visit_ids[0],
+                page_with_no_visits_unsynced.visit_ids[0],
+                page_with_no_visits_synced.visit_ids[0],
+                page_with_no_visits_bookmarked.visit_ids[0],
+            ]),
+        }
+        .apply(&conn)
+        .unwrap();
+
+        DbAction::RecalcPages {
+            page_ids: HashSet::from_iter([
+                page_with_visits_left.id,
+                page_with_no_visits_unsynced.id,
+                page_with_no_visits_synced.id,
+                page_with_no_visits_bookmarked.id,
+            ]),
+        }
+        .apply(&conn)
+        .unwrap();
+
+        assert_eq!(
+            query_page_ids(&conn),
+            [page_with_visits_left.id, page_with_no_visits_bookmarked.id]
+        );
+        assert_eq!(
+            query_page_tombstones(&conn),
+            [page_with_no_visits_synced.guid]
+        );
+        assert_eq!(
+            page_with_visits_left.query_frecency(&conn),
+            page_with_visits_left.calculate_frecency(&conn)
+        );
+        assert_eq!(
+            page_with_no_visits_bookmarked.query_frecency(&conn),
+            page_with_no_visits_bookmarked.calculate_frecency(&conn)
+        );
+    }
+
+    #[test]
+    fn test_delete_from_pending_temp_tables() {
+        let mut conn = PlacesDb::open_in_memory(ConnectionType::ReadWrite).unwrap();
+        let yesterday = Timestamp::now()
+            .checked_sub(Duration::from_secs(60 * 60 * 24))
+            .unwrap();
+        let test_page = TestPage::new(
+            &mut conn,
+            "http://example.com/",
+            &[
+                Timestamp(yesterday.0 + 100),
+                Timestamp(yesterday.0 + 200),
+                Timestamp(yesterday.0 + 300),
+            ],
+        );
+        DbAction::DeleteVisitRows {
+            visit_ids: HashSet::from_iter([test_page.visit_ids[0]]),
+        }
+        .apply(&conn)
+        .unwrap();
+        DbAction::RecalcPages {
+            page_ids: HashSet::from_iter([test_page.id]),
+        }
+        .apply(&conn)
+        .unwrap();
+        DbAction::DeleteFromPendingTempTables.apply(&conn).unwrap();
+        assert_eq!(
+            conn.query_one::<u32>("SELECT COUNT(*) FROM moz_updateoriginsinsert_temp")
+                .unwrap(),
+            0
+        );
+        assert_eq!(
+            conn.query_one::<u32>("SELECT COUNT(*) FROM moz_updateoriginsupdate_temp")
+                .unwrap(),
+            0
+        );
+        assert_eq!(
+            conn.query_one::<u32>("SELECT COUNT(*) FROM moz_updateoriginsdelete_temp")
+                .unwrap(),
+            0
+        );
+    }
+
+    #[test]
+    fn test_db_actions_from_visits_to_delete() {
+        assert_eq!(
+            db_actions_from_visits_to_delete(vec![
+                VisitToDelete {
+                    visit_id: RowId(1),
+                    page_id: RowId(1),
+                },
+                VisitToDelete {
+                    visit_id: RowId(2),
+                    page_id: RowId(2),
+                },
+                VisitToDelete {
+                    visit_id: RowId(3),
+                    page_id: RowId(2),
+                },
+            ]),
+            vec![
+                DbAction::DeleteVisitRows {
+                    visit_ids: HashSet::from_iter([RowId(1), RowId(2), RowId(3)])
+                },
+                DbAction::RecalcPages {
+                    page_ids: HashSet::from_iter([RowId(1), RowId(2)])
+                },
+                DbAction::DeleteFromPendingTempTables,
+            ],
+        )
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/places/storage/history_metadata.rs.html b/book/rust-docs/src/places/storage/history_metadata.rs.html new file mode 100644 index 0000000000..9df62a5d10 --- /dev/null +++ b/book/rust-docs/src/places/storage/history_metadata.rs.html @@ -0,0 +1,4311 @@ +history_metadata.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
+540
+541
+542
+543
+544
+545
+546
+547
+548
+549
+550
+551
+552
+553
+554
+555
+556
+557
+558
+559
+560
+561
+562
+563
+564
+565
+566
+567
+568
+569
+570
+571
+572
+573
+574
+575
+576
+577
+578
+579
+580
+581
+582
+583
+584
+585
+586
+587
+588
+589
+590
+591
+592
+593
+594
+595
+596
+597
+598
+599
+600
+601
+602
+603
+604
+605
+606
+607
+608
+609
+610
+611
+612
+613
+614
+615
+616
+617
+618
+619
+620
+621
+622
+623
+624
+625
+626
+627
+628
+629
+630
+631
+632
+633
+634
+635
+636
+637
+638
+639
+640
+641
+642
+643
+644
+645
+646
+647
+648
+649
+650
+651
+652
+653
+654
+655
+656
+657
+658
+659
+660
+661
+662
+663
+664
+665
+666
+667
+668
+669
+670
+671
+672
+673
+674
+675
+676
+677
+678
+679
+680
+681
+682
+683
+684
+685
+686
+687
+688
+689
+690
+691
+692
+693
+694
+695
+696
+697
+698
+699
+700
+701
+702
+703
+704
+705
+706
+707
+708
+709
+710
+711
+712
+713
+714
+715
+716
+717
+718
+719
+720
+721
+722
+723
+724
+725
+726
+727
+728
+729
+730
+731
+732
+733
+734
+735
+736
+737
+738
+739
+740
+741
+742
+743
+744
+745
+746
+747
+748
+749
+750
+751
+752
+753
+754
+755
+756
+757
+758
+759
+760
+761
+762
+763
+764
+765
+766
+767
+768
+769
+770
+771
+772
+773
+774
+775
+776
+777
+778
+779
+780
+781
+782
+783
+784
+785
+786
+787
+788
+789
+790
+791
+792
+793
+794
+795
+796
+797
+798
+799
+800
+801
+802
+803
+804
+805
+806
+807
+808
+809
+810
+811
+812
+813
+814
+815
+816
+817
+818
+819
+820
+821
+822
+823
+824
+825
+826
+827
+828
+829
+830
+831
+832
+833
+834
+835
+836
+837
+838
+839
+840
+841
+842
+843
+844
+845
+846
+847
+848
+849
+850
+851
+852
+853
+854
+855
+856
+857
+858
+859
+860
+861
+862
+863
+864
+865
+866
+867
+868
+869
+870
+871
+872
+873
+874
+875
+876
+877
+878
+879
+880
+881
+882
+883
+884
+885
+886
+887
+888
+889
+890
+891
+892
+893
+894
+895
+896
+897
+898
+899
+900
+901
+902
+903
+904
+905
+906
+907
+908
+909
+910
+911
+912
+913
+914
+915
+916
+917
+918
+919
+920
+921
+922
+923
+924
+925
+926
+927
+928
+929
+930
+931
+932
+933
+934
+935
+936
+937
+938
+939
+940
+941
+942
+943
+944
+945
+946
+947
+948
+949
+950
+951
+952
+953
+954
+955
+956
+957
+958
+959
+960
+961
+962
+963
+964
+965
+966
+967
+968
+969
+970
+971
+972
+973
+974
+975
+976
+977
+978
+979
+980
+981
+982
+983
+984
+985
+986
+987
+988
+989
+990
+991
+992
+993
+994
+995
+996
+997
+998
+999
+1000
+1001
+1002
+1003
+1004
+1005
+1006
+1007
+1008
+1009
+1010
+1011
+1012
+1013
+1014
+1015
+1016
+1017
+1018
+1019
+1020
+1021
+1022
+1023
+1024
+1025
+1026
+1027
+1028
+1029
+1030
+1031
+1032
+1033
+1034
+1035
+1036
+1037
+1038
+1039
+1040
+1041
+1042
+1043
+1044
+1045
+1046
+1047
+1048
+1049
+1050
+1051
+1052
+1053
+1054
+1055
+1056
+1057
+1058
+1059
+1060
+1061
+1062
+1063
+1064
+1065
+1066
+1067
+1068
+1069
+1070
+1071
+1072
+1073
+1074
+1075
+1076
+1077
+1078
+1079
+1080
+1081
+1082
+1083
+1084
+1085
+1086
+1087
+1088
+1089
+1090
+1091
+1092
+1093
+1094
+1095
+1096
+1097
+1098
+1099
+1100
+1101
+1102
+1103
+1104
+1105
+1106
+1107
+1108
+1109
+1110
+1111
+1112
+1113
+1114
+1115
+1116
+1117
+1118
+1119
+1120
+1121
+1122
+1123
+1124
+1125
+1126
+1127
+1128
+1129
+1130
+1131
+1132
+1133
+1134
+1135
+1136
+1137
+1138
+1139
+1140
+1141
+1142
+1143
+1144
+1145
+1146
+1147
+1148
+1149
+1150
+1151
+1152
+1153
+1154
+1155
+1156
+1157
+1158
+1159
+1160
+1161
+1162
+1163
+1164
+1165
+1166
+1167
+1168
+1169
+1170
+1171
+1172
+1173
+1174
+1175
+1176
+1177
+1178
+1179
+1180
+1181
+1182
+1183
+1184
+1185
+1186
+1187
+1188
+1189
+1190
+1191
+1192
+1193
+1194
+1195
+1196
+1197
+1198
+1199
+1200
+1201
+1202
+1203
+1204
+1205
+1206
+1207
+1208
+1209
+1210
+1211
+1212
+1213
+1214
+1215
+1216
+1217
+1218
+1219
+1220
+1221
+1222
+1223
+1224
+1225
+1226
+1227
+1228
+1229
+1230
+1231
+1232
+1233
+1234
+1235
+1236
+1237
+1238
+1239
+1240
+1241
+1242
+1243
+1244
+1245
+1246
+1247
+1248
+1249
+1250
+1251
+1252
+1253
+1254
+1255
+1256
+1257
+1258
+1259
+1260
+1261
+1262
+1263
+1264
+1265
+1266
+1267
+1268
+1269
+1270
+1271
+1272
+1273
+1274
+1275
+1276
+1277
+1278
+1279
+1280
+1281
+1282
+1283
+1284
+1285
+1286
+1287
+1288
+1289
+1290
+1291
+1292
+1293
+1294
+1295
+1296
+1297
+1298
+1299
+1300
+1301
+1302
+1303
+1304
+1305
+1306
+1307
+1308
+1309
+1310
+1311
+1312
+1313
+1314
+1315
+1316
+1317
+1318
+1319
+1320
+1321
+1322
+1323
+1324
+1325
+1326
+1327
+1328
+1329
+1330
+1331
+1332
+1333
+1334
+1335
+1336
+1337
+1338
+1339
+1340
+1341
+1342
+1343
+1344
+1345
+1346
+1347
+1348
+1349
+1350
+1351
+1352
+1353
+1354
+1355
+1356
+1357
+1358
+1359
+1360
+1361
+1362
+1363
+1364
+1365
+1366
+1367
+1368
+1369
+1370
+1371
+1372
+1373
+1374
+1375
+1376
+1377
+1378
+1379
+1380
+1381
+1382
+1383
+1384
+1385
+1386
+1387
+1388
+1389
+1390
+1391
+1392
+1393
+1394
+1395
+1396
+1397
+1398
+1399
+1400
+1401
+1402
+1403
+1404
+1405
+1406
+1407
+1408
+1409
+1410
+1411
+1412
+1413
+1414
+1415
+1416
+1417
+1418
+1419
+1420
+1421
+1422
+1423
+1424
+1425
+1426
+1427
+1428
+1429
+1430
+1431
+1432
+1433
+1434
+1435
+1436
+1437
+1438
+1439
+1440
+1441
+1442
+1443
+1444
+1445
+1446
+1447
+1448
+1449
+1450
+1451
+1452
+1453
+1454
+1455
+1456
+1457
+1458
+1459
+1460
+1461
+1462
+1463
+1464
+1465
+1466
+1467
+1468
+1469
+1470
+1471
+1472
+1473
+1474
+1475
+1476
+1477
+1478
+1479
+1480
+1481
+1482
+1483
+1484
+1485
+1486
+1487
+1488
+1489
+1490
+1491
+1492
+1493
+1494
+1495
+1496
+1497
+1498
+1499
+1500
+1501
+1502
+1503
+1504
+1505
+1506
+1507
+1508
+1509
+1510
+1511
+1512
+1513
+1514
+1515
+1516
+1517
+1518
+1519
+1520
+1521
+1522
+1523
+1524
+1525
+1526
+1527
+1528
+1529
+1530
+1531
+1532
+1533
+1534
+1535
+1536
+1537
+1538
+1539
+1540
+1541
+1542
+1543
+1544
+1545
+1546
+1547
+1548
+1549
+1550
+1551
+1552
+1553
+1554
+1555
+1556
+1557
+1558
+1559
+1560
+1561
+1562
+1563
+1564
+1565
+1566
+1567
+1568
+1569
+1570
+1571
+1572
+1573
+1574
+1575
+1576
+1577
+1578
+1579
+1580
+1581
+1582
+1583
+1584
+1585
+1586
+1587
+1588
+1589
+1590
+1591
+1592
+1593
+1594
+1595
+1596
+1597
+1598
+1599
+1600
+1601
+1602
+1603
+1604
+1605
+1606
+1607
+1608
+1609
+1610
+1611
+1612
+1613
+1614
+1615
+1616
+1617
+1618
+1619
+1620
+1621
+1622
+1623
+1624
+1625
+1626
+1627
+1628
+1629
+1630
+1631
+1632
+1633
+1634
+1635
+1636
+1637
+1638
+1639
+1640
+1641
+1642
+1643
+1644
+1645
+1646
+1647
+1648
+1649
+1650
+1651
+1652
+1653
+1654
+1655
+1656
+1657
+1658
+1659
+1660
+1661
+1662
+1663
+1664
+1665
+1666
+1667
+1668
+1669
+1670
+1671
+1672
+1673
+1674
+1675
+1676
+1677
+1678
+1679
+1680
+1681
+1682
+1683
+1684
+1685
+1686
+1687
+1688
+1689
+1690
+1691
+1692
+1693
+1694
+1695
+1696
+1697
+1698
+1699
+1700
+1701
+1702
+1703
+1704
+1705
+1706
+1707
+1708
+1709
+1710
+1711
+1712
+1713
+1714
+1715
+1716
+1717
+1718
+1719
+1720
+1721
+1722
+1723
+1724
+1725
+1726
+1727
+1728
+1729
+1730
+1731
+1732
+1733
+1734
+1735
+1736
+1737
+1738
+1739
+1740
+1741
+1742
+1743
+1744
+1745
+1746
+1747
+1748
+1749
+1750
+1751
+1752
+1753
+1754
+1755
+1756
+1757
+1758
+1759
+1760
+1761
+1762
+1763
+1764
+1765
+1766
+1767
+1768
+1769
+1770
+1771
+1772
+1773
+1774
+1775
+1776
+1777
+1778
+1779
+1780
+1781
+1782
+1783
+1784
+1785
+1786
+1787
+1788
+1789
+1790
+1791
+1792
+1793
+1794
+1795
+1796
+1797
+1798
+1799
+1800
+1801
+1802
+1803
+1804
+1805
+1806
+1807
+1808
+1809
+1810
+1811
+1812
+1813
+1814
+1815
+1816
+1817
+1818
+1819
+1820
+1821
+1822
+1823
+1824
+1825
+1826
+1827
+1828
+1829
+1830
+1831
+1832
+1833
+1834
+1835
+1836
+1837
+1838
+1839
+1840
+1841
+1842
+1843
+1844
+1845
+1846
+1847
+1848
+1849
+1850
+1851
+1852
+1853
+1854
+1855
+1856
+1857
+1858
+1859
+1860
+1861
+1862
+1863
+1864
+1865
+1866
+1867
+1868
+1869
+1870
+1871
+1872
+1873
+1874
+1875
+1876
+1877
+1878
+1879
+1880
+1881
+1882
+1883
+1884
+1885
+1886
+1887
+1888
+1889
+1890
+1891
+1892
+1893
+1894
+1895
+1896
+1897
+1898
+1899
+1900
+1901
+1902
+1903
+1904
+1905
+1906
+1907
+1908
+1909
+1910
+1911
+1912
+1913
+1914
+1915
+1916
+1917
+1918
+1919
+1920
+1921
+1922
+1923
+1924
+1925
+1926
+1927
+1928
+1929
+1930
+1931
+1932
+1933
+1934
+1935
+1936
+1937
+1938
+1939
+1940
+1941
+1942
+1943
+1944
+1945
+1946
+1947
+1948
+1949
+1950
+1951
+1952
+1953
+1954
+1955
+1956
+1957
+1958
+1959
+1960
+1961
+1962
+1963
+1964
+1965
+1966
+1967
+1968
+1969
+1970
+1971
+1972
+1973
+1974
+1975
+1976
+1977
+1978
+1979
+1980
+1981
+1982
+1983
+1984
+1985
+1986
+1987
+1988
+1989
+1990
+1991
+1992
+1993
+1994
+1995
+1996
+1997
+1998
+1999
+2000
+2001
+2002
+2003
+2004
+2005
+2006
+2007
+2008
+2009
+2010
+2011
+2012
+2013
+2014
+2015
+2016
+2017
+2018
+2019
+2020
+2021
+2022
+2023
+2024
+2025
+2026
+2027
+2028
+2029
+2030
+2031
+2032
+2033
+2034
+2035
+2036
+2037
+2038
+2039
+2040
+2041
+2042
+2043
+2044
+2045
+2046
+2047
+2048
+2049
+2050
+2051
+2052
+2053
+2054
+2055
+2056
+2057
+2058
+2059
+2060
+2061
+2062
+2063
+2064
+2065
+2066
+2067
+2068
+2069
+2070
+2071
+2072
+2073
+2074
+2075
+2076
+2077
+2078
+2079
+2080
+2081
+2082
+2083
+2084
+2085
+2086
+2087
+2088
+2089
+2090
+2091
+2092
+2093
+2094
+2095
+2096
+2097
+2098
+2099
+2100
+2101
+2102
+2103
+2104
+2105
+2106
+2107
+2108
+2109
+2110
+2111
+2112
+2113
+2114
+2115
+2116
+2117
+2118
+2119
+2120
+2121
+2122
+2123
+2124
+2125
+2126
+2127
+2128
+2129
+2130
+2131
+2132
+2133
+2134
+2135
+2136
+2137
+2138
+2139
+2140
+2141
+2142
+2143
+2144
+2145
+2146
+2147
+2148
+2149
+2150
+2151
+2152
+2153
+2154
+2155
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::db::{PlacesDb, PlacesTransaction};
+use crate::error::*;
+use crate::RowId;
+use error_support::{breadcrumb, redact_url};
+use rusqlite::types::{FromSql, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
+use sql_support::ConnExt;
+use std::vec::Vec;
+use sync_guid::Guid as SyncGuid;
+use types::Timestamp;
+use url::Url;
+
+use lazy_static::lazy_static;
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub enum DocumentType {
+    Regular = 0,
+    Media = 1,
+}
+
+impl FromSql for DocumentType {
+    #[inline]
+    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
+        Ok(match value.as_i64()? {
+            0 => DocumentType::Regular,
+            1 => DocumentType::Media,
+            other => {
+                // seems safe to ignore?
+                log::warn!("invalid DocumentType {}", other);
+                DocumentType::Regular
+            }
+        })
+    }
+}
+
+impl ToSql for DocumentType {
+    #[inline]
+    fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
+        Ok(ToSqlOutput::from(*self as u32))
+    }
+}
+
+#[derive(Clone)]
+pub struct HistoryHighlightWeights {
+    pub view_time: f64,
+    pub frequency: f64,
+}
+
+#[derive(Clone)]
+pub struct HistoryHighlight {
+    pub score: f64,
+    pub place_id: i32,
+    pub url: String,
+    pub title: Option<String>,
+    pub preview_image_url: Option<String>,
+}
+
+impl HistoryHighlight {
+    pub(crate) fn from_row(row: &rusqlite::Row<'_>) -> Result<Self> {
+        Ok(Self {
+            score: row.get("score")?,
+            place_id: row.get("place_id")?,
+            url: row.get("url")?,
+            title: row.get("title")?,
+            preview_image_url: row.get("preview_image_url")?,
+        })
+    }
+}
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct HistoryMetadataObservation {
+    pub url: String,
+    pub view_time: Option<i32>,
+    pub search_term: Option<String>,
+    pub document_type: Option<DocumentType>,
+    pub referrer_url: Option<String>,
+    pub title: Option<String>,
+}
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct HistoryMetadata {
+    pub url: String,
+    pub title: Option<String>,
+    pub preview_image_url: Option<String>,
+    pub created_at: i64,
+    pub updated_at: i64,
+    pub total_view_time: i32,
+    pub search_term: Option<String>,
+    pub document_type: DocumentType,
+    pub referrer_url: Option<String>,
+}
+
+impl HistoryMetadata {
+    pub(crate) fn from_row(row: &rusqlite::Row<'_>) -> Result<Self> {
+        let created_at: Timestamp = row.get("created_at")?;
+        let updated_at: Timestamp = row.get("updated_at")?;
+
+        // Guard against invalid data in the db.
+        // Certain client bugs allowed accumulating values that are too large to fit into i32,
+        // leading to overflow failures. While this data will expire and will be deleted
+        // by clients via `delete_older_than`, we still want to ensure we won't crash in case of
+        // encountering it.
+        // See `apply_metadata_observation` for where we guard against observing invalid view times.
+        let total_view_time: i64 = row.get("total_view_time")?;
+        let total_view_time = match i32::try_from(total_view_time) {
+            Ok(tvt) => tvt,
+            Err(_) => i32::MAX,
+        };
+
+        Ok(Self {
+            url: row.get("url")?,
+            title: row.get("title")?,
+            preview_image_url: row.get("preview_image_url")?,
+            created_at: created_at.0 as i64,
+            updated_at: updated_at.0 as i64,
+            total_view_time,
+            search_term: row.get("search_term")?,
+            document_type: row.get("document_type")?,
+            referrer_url: row.get("referrer_url")?,
+        })
+    }
+}
+
+enum PlaceEntry {
+    Existing(i64),
+    CreateFor(Url, Option<String>),
+}
+
+trait WhereArg {
+    fn to_where_arg(&self, db_field: &str) -> String;
+}
+
+impl PlaceEntry {
+    fn fetch(url: &str, tx: &PlacesTransaction<'_>, title: Option<String>) -> Result<Self> {
+        let url = Url::parse(url).map_err(|e| {
+            breadcrumb!(
+                "PlaceEntry::fetch -- Error parsing url: {}",
+                redact_url(url)
+            );
+            e
+        })?;
+        let place_id = tx.try_query_one(
+            "SELECT id FROM moz_places WHERE url_hash = hash(:url) AND url = :url",
+            &[(":url", &url.as_str())],
+            true,
+        )?;
+
+        Ok(match place_id {
+            Some(id) => PlaceEntry::Existing(id),
+            None => PlaceEntry::CreateFor(url, title),
+        })
+    }
+}
+
+impl WhereArg for PlaceEntry {
+    fn to_where_arg(&self, db_field: &str) -> String {
+        match self {
+            PlaceEntry::Existing(id) => format!("{} = {}", db_field, id),
+            PlaceEntry::CreateFor(_, _) => panic!("WhereArg: place entry must exist"),
+        }
+    }
+}
+
+impl WhereArg for Option<PlaceEntry> {
+    fn to_where_arg(&self, db_field: &str) -> String {
+        match self {
+            Some(entry) => entry.to_where_arg(db_field),
+            None => format!("{} IS NULL", db_field),
+        }
+    }
+}
+
+trait DatabaseId {
+    fn get_or_insert(&self, tx: &PlacesTransaction<'_>) -> Result<i64>;
+}
+
+impl DatabaseId for PlaceEntry {
+    fn get_or_insert(&self, tx: &PlacesTransaction<'_>) -> Result<i64> {
+        Ok(match self {
+            PlaceEntry::Existing(id) => *id,
+            PlaceEntry::CreateFor(url, title) => {
+                let sql = "INSERT INTO moz_places (guid, url, title, url_hash)
+                VALUES (:guid, :url, :title, hash(:url))";
+
+                let guid = SyncGuid::random();
+
+                tx.execute_cached(
+                    sql,
+                    &[
+                        (":guid", &guid as &dyn rusqlite::ToSql),
+                        (":title", &title),
+                        (":url", &url.as_str()),
+                    ],
+                )?;
+                tx.conn().last_insert_rowid()
+            }
+        })
+    }
+}
+
+enum SearchQueryEntry {
+    Existing(i64),
+    CreateFor(String),
+}
+
+impl DatabaseId for SearchQueryEntry {
+    fn get_or_insert(&self, tx: &PlacesTransaction<'_>) -> Result<i64> {
+        Ok(match self {
+            SearchQueryEntry::Existing(id) => *id,
+            SearchQueryEntry::CreateFor(term) => {
+                tx.execute_cached(
+                    "INSERT INTO moz_places_metadata_search_queries(term) VALUES (:term)",
+                    &[(":term", &term)],
+                )?;
+                tx.conn().last_insert_rowid()
+            }
+        })
+    }
+}
+
+impl SearchQueryEntry {
+    fn from(search_term: &str, tx: &PlacesTransaction<'_>) -> Result<Self> {
+        let lowercase_term = search_term.to_lowercase();
+        Ok(
+            match tx.try_query_one(
+                "SELECT id FROM moz_places_metadata_search_queries WHERE term = :term",
+                &[(":term", &lowercase_term)],
+                true,
+            )? {
+                Some(id) => SearchQueryEntry::Existing(id),
+                None => SearchQueryEntry::CreateFor(lowercase_term),
+            },
+        )
+    }
+}
+
+impl WhereArg for SearchQueryEntry {
+    fn to_where_arg(&self, db_field: &str) -> String {
+        match self {
+            SearchQueryEntry::Existing(id) => format!("{} = {}", db_field, id),
+            SearchQueryEntry::CreateFor(_) => panic!("WhereArg: search query entry must exist"),
+        }
+    }
+}
+
+impl WhereArg for Option<SearchQueryEntry> {
+    fn to_where_arg(&self, db_field: &str) -> String {
+        match self {
+            Some(entry) => entry.to_where_arg(db_field),
+            None => format!("{} IS NULL", db_field),
+        }
+    }
+}
+
+struct HistoryMetadataCompoundKey {
+    place_entry: PlaceEntry,
+    referrer_entry: Option<PlaceEntry>,
+    search_query_entry: Option<SearchQueryEntry>,
+}
+
+struct MetadataObservation {
+    document_type: Option<DocumentType>,
+    view_time: Option<i32>,
+}
+
+impl HistoryMetadataCompoundKey {
+    fn can_debounce(&self) -> Option<i64> {
+        match self.place_entry {
+            PlaceEntry::Existing(id) => {
+                if (match self.search_query_entry {
+                    None | Some(SearchQueryEntry::Existing(_)) => true,
+                    Some(SearchQueryEntry::CreateFor(_)) => false,
+                } && match self.referrer_entry {
+                    None | Some(PlaceEntry::Existing(_)) => true,
+                    Some(PlaceEntry::CreateFor(_, _)) => false,
+                }) {
+                    Some(id)
+                } else {
+                    None
+                }
+            }
+            _ => None,
+        }
+    }
+
+    // Looks up matching metadata records, by the compound key and time window.
+    fn lookup(&self, tx: &PlacesTransaction<'_>, newer_than: i64) -> Result<Option<i64>> {
+        Ok(match self.can_debounce() {
+            Some(id) => {
+                let search_query_id = match self.search_query_entry {
+                    None | Some(SearchQueryEntry::CreateFor(_)) => None,
+                    Some(SearchQueryEntry::Existing(id)) => Some(id),
+                };
+
+                let referrer_place_id = match self.referrer_entry {
+                    None | Some(PlaceEntry::CreateFor(_, _)) => None,
+                    Some(PlaceEntry::Existing(id)) => Some(id),
+                };
+
+                tx.try_query_one::<i64, _>(
+                    "SELECT id FROM moz_places_metadata
+                        WHERE
+                            place_id IS :place_id AND
+                            referrer_place_id IS :referrer_place_id AND
+                            search_query_id IS :search_query_id AND
+                            updated_at >= :newer_than
+                        ORDER BY updated_at DESC LIMIT 1",
+                    rusqlite::named_params! {
+                        ":place_id": id,
+                        ":search_query_id": search_query_id,
+                        ":referrer_place_id": referrer_place_id,
+                        ":newer_than": newer_than
+                    },
+                    true,
+                )?
+            }
+            None => None,
+        })
+    }
+}
+
+const DEBOUNCE_WINDOW_MS: i64 = 2 * 60 * 1000; // 2 minutes
+const MAX_QUERY_RESULTS: i32 = 1000;
+
+const COMMON_METADATA_SELECT: &str = "
+SELECT
+    m.id as metadata_id, p.url as url, p.title as title, p.preview_image_url as preview_image_url,
+    m.created_at as created_at, m.updated_at as updated_at, m.total_view_time as total_view_time,
+    m.document_type as document_type, o.url as referrer_url, s.term as search_term
+FROM moz_places_metadata m
+LEFT JOIN moz_places p ON m.place_id = p.id
+LEFT JOIN moz_places_metadata_search_queries s ON m.search_query_id = s.id
+LEFT JOIN moz_places o ON o.id = m.referrer_place_id";
+
+// Highlight query returns moz_places entries ranked by a "highlight score".
+// This score takes into account two factors:
+// 1) frequency of visits to a page,
+// 2) cummulative view time of a page.
+//
+// Eventually, we could consider combining this with `moz_places.frecency` as a basis for (1), that assumes we have a populated moz_historyvisits table.
+// Currently, iOS doesn't use 'places' library to track visits, so iOS clients won't have meaningful frecency scores.
+//
+// Instead, we use moz_places_metadata entries to compute both (1) and (2).
+// This has several nice properties:
+// - it works on clients that only use 'metadata' APIs, not 'places'
+// - since metadata is capped by clients to a certain time window (via `delete_older_than`), the scores will be computed for the same time window
+// - we debounce metadata observations to the same "key" if they're close in time.
+// -- this is an equivalent of saying that if a page was visited multiple times in quick succession, treat that as a single visit while accumulating the view time
+// -- the assumption we're making is that this better matches user perception of their browsing activity
+//
+// The score is computed as a weighted sum of two probabilities:
+// - at any given moment in my browsing sessions for the past X days, how likely am I to be looking at a page?
+// - for any given visit during my browsing sessions for the past X days, how likely am I to visit a page?
+//
+// This kind of scoring is fairly intuitive and simple to reason about at the product level.
+//
+// An alternative way to arrive at the same ranking would be to normalize the values to compare data of different dimensions, time vs frequency.
+// We can normalize view time and frequency into a 0-1 scale before computing weighted scores.
+// (select place_id, (normal_frequency * 1.0 + normal_view_time * 1.0) as score from
+//     (select place_id, cast(count(*) - min_f as REAL) / cast(range_f as REAL) as normal_frequency, cast(sum(total_view_time) - min_v as REAL) / cast(max_v as REAL) as normal_view_time from moz_places_metadata,
+//     (select min(frequency) as min_f, max(frequency) as max_f, max(frequency) - min(frequency) as range_f
+//         from (select count(*) as frequency from moz_places_metadata group by place_id)
+//     ),
+//     (select min(view_time) as min_v, max(view_time) as max_v, max(view_time) - min(view_time) as range_v
+//         from (select sum(total_view_time) as view_time from moz_places_metadata where total_view_time > 0 group by place_id)
+//     ) where total_view_time > 0 group by place_id)) ranked
+//
+// Note that while it's tempting to use built-in window functions such percent_rank, they're not sufficient.
+// The built-in functions concern themselves with absolute ranking, not taking into account magnitudes of differences between values.
+// For example, given two entries we'll know that one is larger than another, but not by how much.
+const HIGHLIGHTS_QUERY: &str = "
+SELECT
+    IFNULL(ranked.score, 0.0) AS score, p.id AS place_id, p.url AS url, p.title AS title, p.preview_image_url AS preview_image_url
+FROM moz_places p
+INNER JOIN
+    (
+        SELECT place_id, :view_time_weight * view_time_prob + :frequency_weight * frequency_prob AS score FROM (
+            SELECT
+                place_id,
+                CAST(count(*) AS REAL) / total_count AS frequency_prob,
+                CAST(sum(total_view_time) AS REAL) / all_view_time AS view_time_prob
+                FROM (
+                    SELECT place_id, count(*) OVER () AS total_count, total_view_time, sum(total_view_time) OVER () AS all_view_time FROM moz_places_metadata
+                )
+            GROUP BY place_id
+        )
+    ) ranked
+ON p.id = ranked.place_id
+ORDER BY ranked.score DESC
+LIMIT :limit";
+
+lazy_static! {
+    static ref GET_LATEST_SQL: String = format!(
+        "{common_select_sql}
+        WHERE p.url_hash = hash(:url) AND p.url = :url
+        ORDER BY updated_at DESC, metadata_id DESC
+        LIMIT 1",
+        common_select_sql = COMMON_METADATA_SELECT
+    );
+    static ref GET_BETWEEN_SQL: String = format!(
+        "{common_select_sql}
+        WHERE updated_at BETWEEN :start AND :end
+        ORDER BY updated_at DESC
+        LIMIT {max_limit}",
+        common_select_sql = COMMON_METADATA_SELECT,
+        max_limit = MAX_QUERY_RESULTS
+    );
+    static ref GET_SINCE_SQL: String = format!(
+        "{common_select_sql}
+        WHERE updated_at >= :start
+        ORDER BY updated_at DESC
+        LIMIT {max_limit}",
+        common_select_sql = COMMON_METADATA_SELECT,
+        max_limit = MAX_QUERY_RESULTS
+    );
+    static ref QUERY_SQL: String = format!(
+        "{common_select_sql}
+        WHERE
+            p.url LIKE :query OR
+            p.title LIKE :query OR
+            search_term LIKE :query
+        ORDER BY total_view_time DESC
+        LIMIT :limit",
+        common_select_sql = COMMON_METADATA_SELECT
+    );
+}
+
+pub fn get_latest_for_url(db: &PlacesDb, url: &Url) -> Result<Option<HistoryMetadata>> {
+    let metadata = db.try_query_row(
+        GET_LATEST_SQL.as_str(),
+        &[(":url", &url.as_str())],
+        HistoryMetadata::from_row,
+        true,
+    )?;
+    Ok(metadata)
+}
+
+pub fn get_between(db: &PlacesDb, start: i64, end: i64) -> Result<Vec<HistoryMetadata>> {
+    db.query_rows_and_then_cached(
+        GET_BETWEEN_SQL.as_str(),
+        rusqlite::named_params! {
+            ":start": start,
+            ":end": end,
+        },
+        HistoryMetadata::from_row,
+    )
+}
+
+pub fn get_since(db: &PlacesDb, start: i64) -> Result<Vec<HistoryMetadata>> {
+    db.query_rows_and_then_cached(
+        GET_SINCE_SQL.as_str(),
+        rusqlite::named_params! {
+            ":start": start
+        },
+        HistoryMetadata::from_row,
+    )
+}
+
+pub fn get_highlights(
+    db: &PlacesDb,
+    weights: HistoryHighlightWeights,
+    limit: i32,
+) -> Result<Vec<HistoryHighlight>> {
+    db.query_rows_and_then_cached(
+        HIGHLIGHTS_QUERY,
+        rusqlite::named_params! {
+            ":view_time_weight": weights.view_time,
+            ":frequency_weight": weights.frequency,
+            ":limit": limit
+        },
+        HistoryHighlight::from_row,
+    )
+}
+
+pub fn query(db: &PlacesDb, query: &str, limit: i32) -> Result<Vec<HistoryMetadata>> {
+    db.query_rows_and_then_cached(
+        QUERY_SQL.as_str(),
+        rusqlite::named_params! {
+            ":query": format!("%{}%", query),
+            ":limit": limit
+        },
+        HistoryMetadata::from_row,
+    )
+}
+
+pub fn delete_older_than(db: &PlacesDb, older_than: i64) -> Result<()> {
+    db.execute_cached(
+        "DELETE FROM moz_places_metadata
+         WHERE updated_at < :older_than",
+        &[(":older_than", &older_than)],
+    )?;
+    Ok(())
+}
+
+pub fn delete_between(db: &PlacesDb, start: i64, end: i64) -> Result<()> {
+    db.execute_cached(
+        "DELETE FROM moz_places_metadata
+        WHERE updated_at > :start and updated_at < :end",
+        &[(":start", &start), (":end", &end)],
+    )?;
+    Ok(())
+}
+
+/// Delete all metadata for the specified place id.
+pub fn delete_all_metadata_for_page(db: &PlacesDb, place_id: RowId) -> Result<()> {
+    db.execute_cached(
+        "DELETE FROM moz_places_metadata
+         WHERE place_id = :place_id",
+        &[(":place_id", &place_id)],
+    )?;
+    Ok(())
+}
+
+pub fn delete_metadata(
+    db: &PlacesDb,
+    url: &Url,
+    referrer_url: Option<&Url>,
+    search_term: Option<&str>,
+) -> Result<()> {
+    let tx = db.begin_transaction()?;
+
+    // Only delete entries that exactly match the key (url+referrer+search_term) we were passed-in.
+    // Do nothing if we were asked to delete a key which doesn't match what's in the database.
+    // e.g. referrer_url.is_some(), but a correspodning moz_places entry doesn't exist.
+    // In practice this shouldn't happen, or it may imply API misuse, but in either case we shouldn't
+    // delete things we were not asked to delete.
+    let place_entry = PlaceEntry::fetch(url.as_str(), &tx, None)?;
+    let place_entry = match place_entry {
+        PlaceEntry::Existing(_) => place_entry,
+        PlaceEntry::CreateFor(_, _) => {
+            tx.rollback()?;
+            return Ok(());
+        }
+    };
+    let referrer_entry = match referrer_url {
+        Some(referrer_url) if !referrer_url.as_str().is_empty() => {
+            Some(PlaceEntry::fetch(referrer_url.as_str(), &tx, None)?)
+        }
+        _ => None,
+    };
+    let referrer_entry = match referrer_entry {
+        Some(PlaceEntry::Existing(_)) | None => referrer_entry,
+        Some(PlaceEntry::CreateFor(_, _)) => {
+            tx.rollback()?;
+            return Ok(());
+        }
+    };
+    let search_query_entry = match search_term {
+        Some(search_term) if !search_term.is_empty() => {
+            Some(SearchQueryEntry::from(search_term, &tx)?)
+        }
+        _ => None,
+    };
+    let search_query_entry = match search_query_entry {
+        Some(SearchQueryEntry::Existing(_)) | None => search_query_entry,
+        Some(SearchQueryEntry::CreateFor(_)) => {
+            tx.rollback()?;
+            return Ok(());
+        }
+    };
+
+    let sql = format!(
+        "DELETE FROM moz_places_metadata WHERE {} AND {} AND {}",
+        place_entry.to_where_arg("place_id"),
+        referrer_entry.to_where_arg("referrer_place_id"),
+        search_query_entry.to_where_arg("search_query_id")
+    );
+
+    tx.execute_cached(&sql, [])?;
+    tx.commit()?;
+
+    Ok(())
+}
+
+pub fn apply_metadata_observation(
+    db: &PlacesDb,
+    observation: HistoryMetadataObservation,
+) -> Result<()> {
+    if let Some(view_time) = observation.view_time {
+        // Consider any view_time observations that are higher than 24hrs to be invalid.
+        // This guards against clients passing us wildly inaccurate view_time observations,
+        // likely resulting from some measurement bug. If we detect such cases, we fail so
+        // that the client has a chance to discover its mistake.
+        // When recording a view time, we increment the stored value directly in SQL, which
+        // doesn't allow for error detection unless we run an additional SELECT statement to
+        // query current cumulative view time and see if incrementing it will result in an
+        // overflow. This check is a simpler way to achieve the same goal (detect invalid inputs).
+        if view_time > 1000 * 60 * 60 * 24 {
+            return Err(InvalidMetadataObservation::ViewTimeTooLong.into());
+        }
+    }
+
+    // Begin a write transaction. We do this before any other work (e.g. SELECTs) to avoid racing against
+    // other writers. Even though we expect to only have a single application writer, a sync writer
+    // can come in at any time and change data we depend on, such as moz_places
+    // and moz_origins, leaving us in a potentially inconsistent state.
+    let tx = db.begin_transaction()?;
+
+    let place_entry = PlaceEntry::fetch(&observation.url, &tx, observation.title.clone())?;
+    let result = apply_metadata_observation_impl(&tx, place_entry, observation);
+
+    // Inserting into moz_places has side-effects (temp tables are populated via triggers and need to be flushed).
+    // This call "finalizes" these side-effects.
+    super::delete_pending_temp_tables(db)?;
+    match result {
+        Ok(_) => tx.commit()?,
+        Err(_) => tx.rollback()?,
+    };
+
+    result
+}
+
+fn apply_metadata_observation_impl(
+    tx: &PlacesTransaction<'_>,
+    place_entry: PlaceEntry,
+    observation: HistoryMetadataObservation,
+) -> Result<()> {
+    let referrer_entry = match observation.referrer_url {
+        Some(referrer_url) if !referrer_url.is_empty() => {
+            Some(PlaceEntry::fetch(&referrer_url, tx, None)?)
+        }
+        Some(_) | None => None,
+    };
+    let search_query_entry = match observation.search_term {
+        Some(search_term) if !search_term.is_empty() => {
+            Some(SearchQueryEntry::from(&search_term, tx)?)
+        }
+        Some(_) | None => None,
+    };
+
+    let compound_key = HistoryMetadataCompoundKey {
+        place_entry,
+        referrer_entry,
+        search_query_entry,
+    };
+
+    let observation = MetadataObservation {
+        document_type: observation.document_type,
+        view_time: observation.view_time,
+    };
+
+    let now = Timestamp::now().as_millis() as i64;
+    let newer_than = now - DEBOUNCE_WINDOW_MS;
+    let matching_metadata = compound_key.lookup(tx, newer_than)?;
+
+    // If a matching record exists, update it; otherwise, insert a new one.
+    match matching_metadata {
+        Some(metadata_id) => {
+            // If document_type isn't part of the observation, make sure we don't accidentally erase what's currently set.
+            match observation {
+                MetadataObservation {
+                    document_type: Some(dt),
+                    view_time,
+                } => {
+                    tx.execute_cached(
+                        "UPDATE
+                            moz_places_metadata
+                        SET
+                            document_type = :document_type,
+                            total_view_time = total_view_time + :view_time_delta,
+                            updated_at = :updated_at
+                        WHERE id = :id",
+                        rusqlite::named_params! {
+                            ":id": metadata_id,
+                            ":document_type": dt,
+                            ":view_time_delta": view_time.unwrap_or(0),
+                            ":updated_at": now
+                        },
+                    )?;
+                }
+                MetadataObservation {
+                    document_type: None,
+                    view_time,
+                } => {
+                    tx.execute_cached(
+                        "UPDATE
+                            moz_places_metadata
+                        SET
+                            total_view_time = total_view_time + :view_time_delta,
+                            updated_at = :updated_at
+                        WHERE id = :id",
+                        rusqlite::named_params! {
+                            ":id": metadata_id,
+                            ":view_time_delta": view_time.unwrap_or(0),
+                            ":updated_at": now
+                        },
+                    )?;
+                }
+            }
+            Ok(())
+        }
+        None => insert_metadata_in_tx(tx, compound_key, observation),
+    }
+}
+
+fn insert_metadata_in_tx(
+    tx: &PlacesTransaction<'_>,
+    key: HistoryMetadataCompoundKey,
+    observation: MetadataObservation,
+) -> Result<()> {
+    let now = Timestamp::now();
+
+    let referrer_place_id = match key.referrer_entry {
+        None => None,
+        Some(entry) => Some(entry.get_or_insert(tx)?),
+    };
+
+    let search_query_id = match key.search_query_entry {
+        None => None,
+        Some(entry) => Some(entry.get_or_insert(tx)?),
+    };
+
+    // Heavy lifting around moz_places inserting (e.g. updating moz_origins, frecency, etc) is performed via triggers.
+    // This lets us simply INSERT here without worrying about the rest.
+    let place_id = key.place_entry.get_or_insert(tx)?;
+
+    let sql = "INSERT INTO moz_places_metadata
+        (place_id, created_at, updated_at, total_view_time, search_query_id, document_type, referrer_place_id)
+    VALUES
+        (:place_id, :created_at, :updated_at, :total_view_time, :search_query_id, :document_type, :referrer_place_id)";
+
+    tx.execute_cached(
+        sql,
+        &[
+            (":place_id", &place_id as &dyn rusqlite::ToSql),
+            (":created_at", &now),
+            (":updated_at", &now),
+            (":search_query_id", &search_query_id),
+            (":referrer_place_id", &referrer_place_id),
+            (
+                ":document_type",
+                &observation.document_type.unwrap_or(DocumentType::Regular),
+            ),
+            (":total_view_time", &observation.view_time.unwrap_or(0)),
+        ],
+    )?;
+
+    Ok(())
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::api::places_api::ConnectionType;
+    use crate::observation::VisitObservation;
+    use crate::storage::bookmarks::{
+        get_raw_bookmark, insert_bookmark, BookmarkPosition, BookmarkRootGuid, InsertableBookmark,
+        InsertableItem,
+    };
+    use crate::storage::fetch_page_info;
+    use crate::storage::history::{
+        apply_observation, delete_everything, delete_visits_between, delete_visits_for,
+        get_visit_count, url_to_guid,
+    };
+    use crate::types::VisitType;
+    use crate::VisitTransitionSet;
+    use pretty_assertions::assert_eq;
+    use std::{thread, time};
+
+    macro_rules! assert_table_size {
+        ($conn:expr, $table:expr, $count:expr) => {
+            assert_eq!(
+                $count,
+                $conn
+                    .try_query_one::<i64, _>(
+                        format!("SELECT count(*) FROM {table}", table = $table).as_str(),
+                        [],
+                        true
+                    )
+                    .expect("select works")
+                    .expect("got count")
+            );
+        };
+    }
+
+    macro_rules! assert_history_metadata_record {
+        ($record:expr, url $url:expr, total_time $tvt:expr, search_term $search_term:expr, document_type $document_type:expr, referrer_url $referrer_url:expr, title $title:expr, preview_image_url $preview_image_url:expr) => {
+            assert_eq!(String::from($url), $record.url, "url must match");
+            assert_eq!($tvt, $record.total_view_time, "total_view_time must match");
+            assert_eq!($document_type, $record.document_type, "is_media must match");
+
+            let meta = $record.clone(); // ugh... not sure why this `clone` is necessary.
+
+            match $search_term as Option<&str> {
+                Some(t) => assert_eq!(
+                    String::from(t),
+                    meta.search_term.expect("search_term must be Some"),
+                    "search_term must match"
+                ),
+                None => assert_eq!(
+                    true,
+                    meta.search_term.is_none(),
+                    "search_term expected to be None"
+                ),
+            };
+            match $referrer_url as Option<&str> {
+                Some(t) => assert_eq!(
+                    String::from(t),
+                    meta.referrer_url.expect("referrer_url must be Some"),
+                    "referrer_url must match"
+                ),
+                None => assert_eq!(
+                    true,
+                    meta.referrer_url.is_none(),
+                    "referrer_url expected to be None"
+                ),
+            };
+            match $title as Option<&str> {
+                Some(t) => assert_eq!(
+                    String::from(t),
+                    meta.title.expect("title must be Some"),
+                    "title must match"
+                ),
+                None => assert_eq!(true, meta.title.is_none(), "title expected to be None"),
+            };
+            match $preview_image_url as Option<&str> {
+                Some(t) => assert_eq!(
+                    String::from(t),
+                    meta.preview_image_url
+                        .expect("preview_image_url must be Some"),
+                    "preview_image_url must match"
+                ),
+                None => assert_eq!(
+                    true,
+                    meta.preview_image_url.is_none(),
+                    "preview_image_url expected to be None"
+                ),
+            };
+        };
+    }
+
+    macro_rules! assert_total_after_observation {
+        ($conn:expr, total_records_after $total_records:expr, total_view_time_after $total_view_time:expr, url $url:expr, view_time $view_time:expr, search_term $search_term:expr, document_type $document_type:expr, referrer_url $referrer_url:expr, title $title:expr) => {
+            note_observation!($conn,
+                url $url,
+                view_time $view_time,
+                search_term $search_term,
+                document_type $document_type,
+                referrer_url $referrer_url,
+                title $title
+            );
+
+            assert_table_size!($conn, "moz_places_metadata", $total_records);
+            let updated = get_latest_for_url($conn, &Url::parse($url).unwrap()).unwrap().unwrap();
+            assert_eq!($total_view_time, updated.total_view_time, "total view time must match");
+        }
+    }
+
+    macro_rules! note_observation {
+        ($conn:expr, url $url:expr, view_time $view_time:expr, search_term $search_term:expr, document_type $document_type:expr, referrer_url $referrer_url:expr, title $title:expr) => {
+            apply_metadata_observation(
+                $conn,
+                HistoryMetadataObservation {
+                    url: String::from($url),
+                    view_time: $view_time,
+                    search_term: $search_term.map(|s: &str| s.to_string()),
+                    document_type: $document_type,
+                    referrer_url: $referrer_url.map(|s: &str| s.to_string()),
+                    title: $title.map(|s: &str| s.to_string()),
+                },
+            )
+            .unwrap();
+        };
+    }
+
+    macro_rules! assert_after_observation {
+        ($conn:expr, total_records_after $total_records:expr, total_view_time_after $total_view_time:expr, url $url:expr, view_time $view_time:expr, search_term $search_term:expr, document_type $document_type:expr, referrer_url $referrer_url:expr, title $title:expr, assertion $assertion:expr) => {
+            // can set title on creating a new record
+            assert_total_after_observation!($conn,
+                total_records_after $total_records,
+                total_view_time_after $total_view_time,
+                url $url,
+                view_time $view_time,
+                search_term $search_term,
+                document_type $document_type,
+                referrer_url $referrer_url,
+                title $title
+            );
+
+            let m = get_latest_for_url(
+                $conn,
+                &Url::parse(&String::from($url)).unwrap(),
+            )
+            .unwrap()
+            .unwrap();
+            #[allow(clippy::redundant_closure_call)]
+            $assertion(m);
+        }
+    }
+
+    #[test]
+    fn test_note_observation() {
+        let conn = PlacesDb::open_in_memory(ConnectionType::ReadWrite).unwrap();
+
+        assert_table_size!(&conn, "moz_places_metadata", 0);
+
+        assert_total_after_observation!(&conn,
+            total_records_after 1,
+            total_view_time_after 1500,
+            url "http://mozilla.com/",
+            view_time Some(1500),
+            search_term None,
+            document_type Some(DocumentType::Regular),
+            referrer_url None,
+            title None
+        );
+
+        // debounced! total time was updated
+        assert_total_after_observation!(&conn,
+            total_records_after 1,
+            total_view_time_after 2500,
+            url "http://mozilla.com/",
+            view_time Some(1000),
+            search_term None,
+            document_type Some(DocumentType::Regular),
+            referrer_url None,
+            title None
+        );
+
+        // different document type, record updated
+        assert_total_after_observation!(&conn,
+            total_records_after 1,
+            total_view_time_after 3500,
+            url "http://mozilla.com/",
+            view_time Some(1000),
+            search_term None,
+            document_type Some(DocumentType::Media),
+            referrer_url None,
+            title None
+        );
+
+        // referrer set
+        assert_total_after_observation!(&conn,
+            total_records_after 2,
+            total_view_time_after 2000,
+            url "http://mozilla.com/",
+            view_time Some(2000),
+            search_term None,
+            document_type Some(DocumentType::Media),
+            referrer_url Some("https://news.website"),
+            title None
+        );
+
+        // search term and referrer are set
+        assert_total_after_observation!(&conn,
+            total_records_after 3,
+            total_view_time_after 1100,
+            url "http://mozilla.com/",
+            view_time Some(1100),
+            search_term Some("firefox"),
+            document_type Some(DocumentType::Media),
+            referrer_url Some("https://www.google.com/search?client=firefox-b-d&q=firefox"),
+            title None
+        );
+
+        // debounce!
+        assert_total_after_observation!(&conn,
+            total_records_after 3,
+            total_view_time_after 6100,
+            url "http://mozilla.com/",
+            view_time Some(5000),
+            search_term Some("firefox"),
+            document_type Some(DocumentType::Media),
+            referrer_url Some("https://www.google.com/search?client=firefox-b-d&q=firefox"),
+            title None
+        );
+
+        // different url now
+        assert_total_after_observation!(&conn,
+            total_records_after 4,
+            total_view_time_after 3000,
+            url "http://mozilla.com/another",
+            view_time Some(3000),
+            search_term None,
+            document_type Some(DocumentType::Regular),
+            referrer_url Some("https://news.website/tech"),
+            title None
+        );
+
+        // shared origin for both url and referrer
+        assert_total_after_observation!(&conn,
+            total_records_after 5,
+            total_view_time_after 100000,
+            url "https://www.youtube.com/watch?v=tpiyEe_CqB4",
+            view_time Some(100000),
+            search_term Some("cute cat"),
+            document_type Some(DocumentType::Media),
+            referrer_url Some("https://www.youtube.com/results?search_query=cute+cat"),
+            title None
+        );
+
+        // empty search term/referrer url are treated the same as None
+        assert_total_after_observation!(&conn,
+            total_records_after 6,
+            total_view_time_after 80000,
+            url "https://www.youtube.com/watch?v=daff43jif3",
+            view_time Some(80000),
+            search_term Some(""),
+            document_type Some(DocumentType::Media),
+            referrer_url Some(""),
+            title None
+        );
+
+        assert_total_after_observation!(&conn,
+            total_records_after 6,
+            total_view_time_after 90000,
+            url "https://www.youtube.com/watch?v=daff43jif3",
+            view_time Some(10000),
+            search_term None,
+            document_type Some(DocumentType::Media),
+            referrer_url None,
+            title None
+        );
+
+        // document type recording
+        assert_total_after_observation!(&conn,
+            total_records_after 7,
+            total_view_time_after 0,
+            url "https://www.youtube.com/watch?v=fds32fds",
+            view_time None,
+            search_term None,
+            document_type Some(DocumentType::Media),
+            referrer_url None,
+            title None
+        );
+
+        // now, update the view time as a separate call
+        assert_total_after_observation!(&conn,
+            total_records_after 7,
+            total_view_time_after 1338,
+            url "https://www.youtube.com/watch?v=fds32fds",
+            view_time Some(1338),
+            search_term None,
+            document_type None,
+            referrer_url None,
+            title None
+        );
+
+        // and again, bump the view time
+        assert_total_after_observation!(&conn,
+            total_records_after 7,
+            total_view_time_after 2000,
+            url "https://www.youtube.com/watch?v=fds32fds",
+            view_time Some(662),
+            search_term None,
+            document_type None,
+            referrer_url None,
+            title None
+        );
+
+        // now try the other way - record view time first, document type after.
+        // and again, bump the view time
+        assert_after_observation!(&conn,
+            total_records_after 8,
+            total_view_time_after 662,
+            url "https://www.youtube.com/watch?v=dasdg34d",
+            view_time Some(662),
+            search_term None,
+            document_type None,
+            referrer_url None,
+            title None,
+            assertion |m: HistoryMetadata| { assert_eq!(DocumentType::Regular, m.document_type) }
+        );
+
+        assert_after_observation!(&conn,
+            total_records_after 8,
+            total_view_time_after 662,
+            url "https://www.youtube.com/watch?v=dasdg34d",
+            view_time None,
+            search_term None,
+            document_type Some(DocumentType::Media),
+            referrer_url None,
+            title None,
+            assertion |m: HistoryMetadata| { assert_eq!(DocumentType::Media, m.document_type) }
+        );
+
+        // document type not overwritten (e.g. remains 1, not default 0).
+        assert_after_observation!(&conn,
+            total_records_after 8,
+            total_view_time_after 675,
+            url "https://www.youtube.com/watch?v=dasdg34d",
+            view_time Some(13),
+            search_term None,
+            document_type None,
+            referrer_url None,
+            title None,
+            assertion |m: HistoryMetadata| { assert_eq!(DocumentType::Media, m.document_type) }
+        );
+
+        // can set title on creating a new record
+        assert_after_observation!(&conn,
+            total_records_after 9,
+            total_view_time_after 13,
+            url "https://www.youtube.com/watch?v=dasdsada",
+            view_time Some(13),
+            search_term None,
+            document_type None,
+            referrer_url None,
+            title Some("hello!"),
+            assertion |m: HistoryMetadata| { assert_eq!(Some(String::from("hello!")), m.title) }
+        );
+
+        // can not update title after
+        assert_after_observation!(&conn,
+            total_records_after 9,
+            total_view_time_after 26,
+            url "https://www.youtube.com/watch?v=dasdsada",
+            view_time Some(13),
+            search_term None,
+            document_type None,
+            referrer_url None,
+            title Some("world!"),
+            assertion |m: HistoryMetadata| { assert_eq!(Some(String::from("hello!")), m.title) }
+        );
+    }
+
+    #[test]
+    fn test_note_observation_invalid_view_time() {
+        let conn = PlacesDb::open_in_memory(ConnectionType::ReadWrite).expect("memory db");
+
+        note_observation!(&conn,
+            url "https://www.mozilla.org/",
+            view_time None,
+            search_term None,
+            document_type Some(DocumentType::Regular),
+            referrer_url None,
+            title None
+        );
+
+        // 48 hrs is clearly a bad view to observe.
+        assert!(apply_metadata_observation(
+            &conn,
+            HistoryMetadataObservation {
+                url: String::from("https://www.mozilla.org"),
+                view_time: Some(1000 * 60 * 60 * 24 * 2),
+                search_term: None,
+                document_type: None,
+                referrer_url: None,
+                title: None
+            }
+        )
+        .is_err());
+
+        // 12 hrs is assumed to be "plausible".
+        assert!(apply_metadata_observation(
+            &conn,
+            HistoryMetadataObservation {
+                url: String::from("https://www.mozilla.org"),
+                view_time: Some(1000 * 60 * 60 * 12),
+                search_term: None,
+                document_type: None,
+                referrer_url: None,
+                title: None
+            }
+        )
+        .is_ok());
+    }
+
+    #[test]
+    fn test_get_between() {
+        let conn = PlacesDb::open_in_memory(ConnectionType::ReadWrite).expect("memory db");
+
+        assert_eq!(0, get_between(&conn, 0, 0).unwrap().len());
+
+        let beginning = Timestamp::now().as_millis() as i64;
+        note_observation!(&conn,
+            url "http://mozilla.com/another",
+            view_time Some(3000),
+            search_term None,
+            document_type Some(DocumentType::Regular),
+            referrer_url Some("https://news.website/tech"),
+            title None
+        );
+        let after_meta1 = Timestamp::now().as_millis() as i64;
+
+        assert_eq!(0, get_between(&conn, 0, beginning - 1).unwrap().len());
+        assert_eq!(1, get_between(&conn, 0, after_meta1).unwrap().len());
+
+        thread::sleep(time::Duration::from_millis(10));
+
+        note_observation!(&conn,
+            url "http://mozilla.com/video/",
+            view_time Some(1000),
+            search_term None,
+            document_type Some(DocumentType::Media),
+            referrer_url None,
+            title None
+        );
+        let after_meta2 = Timestamp::now().as_millis() as i64;
+
+        assert_eq!(1, get_between(&conn, beginning, after_meta1).unwrap().len());
+        assert_eq!(2, get_between(&conn, beginning, after_meta2).unwrap().len());
+        assert_eq!(
+            1,
+            get_between(&conn, after_meta1, after_meta2).unwrap().len()
+        );
+        assert_eq!(
+            0,
+            get_between(&conn, after_meta2, after_meta2 + 1)
+                .unwrap()
+                .len()
+        );
+    }
+
+    #[test]
+    fn test_get_since() {
+        let conn = PlacesDb::open_in_memory(ConnectionType::ReadWrite).expect("memory db");
+
+        assert_eq!(0, get_since(&conn, 0).unwrap().len());
+
+        let beginning = Timestamp::now().as_millis() as i64;
+        note_observation!(&conn,
+            url "http://mozilla.com/another",
+            view_time Some(3000),
+            search_term None,
+            document_type Some(DocumentType::Regular),
+            referrer_url Some("https://news.website/tech"),
+            title None
+        );
+        let after_meta1 = Timestamp::now().as_millis() as i64;
+
+        assert_eq!(1, get_since(&conn, 0).unwrap().len());
+        assert_eq!(1, get_since(&conn, beginning).unwrap().len());
+        assert_eq!(0, get_since(&conn, after_meta1).unwrap().len());
+
+        // thread::sleep(time::Duration::from_millis(50));
+
+        note_observation!(&conn,
+            url "http://mozilla.com/video/",
+            view_time Some(1000),
+            search_term None,
+            document_type Some(DocumentType::Media),
+            referrer_url None,
+            title None
+        );
+        let after_meta2 = Timestamp::now().as_millis() as i64;
+        assert_eq!(2, get_since(&conn, beginning).unwrap().len());
+        assert_eq!(1, get_since(&conn, after_meta1).unwrap().len());
+        assert_eq!(0, get_since(&conn, after_meta2).unwrap().len());
+    }
+
+    #[test]
+    fn test_get_highlights() {
+        let conn = PlacesDb::open_in_memory(ConnectionType::ReadWrite).expect("memory db");
+
+        // Empty database is fine.
+        assert_eq!(
+            0,
+            get_highlights(
+                &conn,
+                HistoryHighlightWeights {
+                    view_time: 1.0,
+                    frequency: 1.0
+                },
+                10
+            )
+            .unwrap()
+            .len()
+        );
+
+        // Database with "normal" history but no metadata observations is fine.
+        apply_observation(
+            &conn,
+            VisitObservation::new(
+                Url::parse("https://www.reddit.com/r/climbing").expect("Should parse URL"),
+            )
+            .with_visit_type(VisitType::Link)
+            .with_at(Timestamp::now()),
+        )
+        .expect("Should apply observation");
+        assert_eq!(
+            0,
+            get_highlights(
+                &conn,
+                HistoryHighlightWeights {
+                    view_time: 1.0,
+                    frequency: 1.0
+                },
+                10
+            )
+            .unwrap()
+            .len()
+        );
+
+        // three observation to url1, each recording a second of view time.
+        note_observation!(&conn,
+            url "http://mozilla.com/1",
+            view_time Some(1000),
+            search_term None,
+            document_type Some(DocumentType::Regular),
+            referrer_url Some("https://news.website/tech"),
+            title None
+        );
+
+        note_observation!(&conn,
+            url "http://mozilla.com/1",
+            view_time Some(1000),
+            search_term None,
+            document_type Some(DocumentType::Regular),
+            referrer_url Some("https://news.website/tech"),
+            title None
+        );
+
+        note_observation!(&conn,
+            url "http://mozilla.com/1",
+            view_time Some(1000),
+            search_term None,
+            document_type Some(DocumentType::Regular),
+            referrer_url Some("https://news.website/tech"),
+            title None
+        );
+
+        // one observation to url2 for 3.5s of view time.
+        note_observation!(&conn,
+            url "http://mozilla.com/2",
+            view_time Some(3500),
+            search_term None,
+            document_type Some(DocumentType::Regular),
+            referrer_url Some("https://news.website/tech"),
+            title None
+        );
+
+        // The three visits to /2 got "debounced" into a single metadata entry (since they were made in quick succession).
+        // We'll calculate the scoring as follows:
+        // - for /1: 1.0 * 1/2 + 1.0 * 3000/6500 = 0.9615...
+        // - for /2: 1.0 * 1/2 + 1.0 * 3500/6500 = 1.0384...
+        // (above, 1/2 means 1 entry out of 2 entries total).
+
+        let even_weights = HistoryHighlightWeights {
+            view_time: 1.0,
+            frequency: 1.0,
+        };
+        let highlights1 = get_highlights(&conn, even_weights.clone(), 10).unwrap();
+        assert_eq!(2, highlights1.len());
+        assert_eq!("http://mozilla.com/2", highlights1[0].url);
+
+        // Since we have an equal amount of metadata entries, providing a very high view_time weight won't change the ranking.
+        let frequency_heavy_weights = HistoryHighlightWeights {
+            view_time: 1.0,
+            frequency: 100.0,
+        };
+        let highlights2 = get_highlights(&conn, frequency_heavy_weights, 10).unwrap();
+        assert_eq!(2, highlights2.len());
+        assert_eq!("http://mozilla.com/2", highlights2[0].url);
+
+        // Now, make an observation for url /1, but with a different metadata key.
+        // It won't debounce, producing an additional entry for /1.
+        // Total view time for /1 is now 3100 (vs 3500 for /2).
+        note_observation!(&conn,
+            url "http://mozilla.com/1",
+            view_time Some(100),
+            search_term Some("test search"),
+            document_type Some(DocumentType::Regular),
+            referrer_url Some("https://news.website/tech"),
+            title None
+        );
+
+        // Since we now have 2 metadata entries for /1, it ranks higher with even weights.
+        let highlights3 = get_highlights(&conn, even_weights, 10).unwrap();
+        assert_eq!(2, highlights3.len());
+        assert_eq!("http://mozilla.com/1", highlights3[0].url);
+
+        // With a high-enough weight for view_time, we can flip this order.
+        // Even though we had 2x entries for /1, it now ranks second due to its lower total view time (3100 vs 3500).
+        let view_time_heavy_weights = HistoryHighlightWeights {
+            view_time: 6.0,
+            frequency: 1.0,
+        };
+        let highlights4 = get_highlights(&conn, view_time_heavy_weights, 10).unwrap();
+        assert_eq!(2, highlights4.len());
+        assert_eq!("http://mozilla.com/2", highlights4[0].url);
+    }
+
+    #[test]
+    fn test_get_highlights_no_viewtime() {
+        let conn = PlacesDb::open_in_memory(ConnectionType::ReadWrite).expect("memory db");
+
+        // Make sure we work if the only observations for a URL have a view time of zero.
+        note_observation!(&conn,
+            url "http://mozilla.com/1",
+            view_time Some(0),
+            search_term None,
+            document_type Some(DocumentType::Regular),
+            referrer_url Some("https://news.website/tech"),
+            title None
+        );
+        let highlights = get_highlights(
+            &conn,
+            HistoryHighlightWeights {
+                view_time: 1.0,
+                frequency: 1.0,
+            },
+            2,
+        )
+        .unwrap();
+        assert_eq!(highlights.len(), 1);
+        assert_eq!(highlights[0].score, 0.0);
+    }
+
+    #[test]
+    fn test_query() {
+        let conn = PlacesDb::open_in_memory(ConnectionType::ReadWrite).expect("memory db");
+        let now = Timestamp::now();
+
+        // need a history observation to get a title query working.
+        let observation1 = VisitObservation::new(Url::parse("https://www.cbc.ca/news/politics/federal-budget-2021-freeland-zimonjic-1.5991021").unwrap())
+                .with_at(now)
+                .with_title(Some(String::from("Budget vows to build &#x27;for the long term&#x27; as it promises child care cash, projects massive deficits | CBC News")))
+                .with_preview_image_url(Some(Url::parse("https://i.cbc.ca/1.5993583.1618861792!/cpImage/httpImage/image.jpg_gen/derivatives/16x9_620/fedbudget-20210419.jpg").unwrap()))
+                .with_is_remote(false)
+                .with_visit_type(VisitType::Link);
+        apply_observation(&conn, observation1).unwrap();
+
+        note_observation!(
+            &conn,
+            url "https://www.cbc.ca/news/politics/federal-budget-2021-freeland-zimonjic-1.5991021",
+            view_time Some(20000),
+            search_term Some("cbc federal budget 2021"),
+            document_type Some(DocumentType::Regular),
+            referrer_url Some("https://yandex.ru/search/?text=cbc%20federal%20budget%202021&lr=21512"),
+            title None
+        );
+
+        note_observation!(
+            &conn,
+            url "https://stackoverflow.com/questions/37777675/how-to-create-a-formatted-string-out-of-a-literal-in-rust",
+            view_time Some(20000),
+            search_term Some("rust string format"),
+            document_type Some(DocumentType::Regular),
+            referrer_url Some("https://yandex.ru/search/?lr=21512&text=rust%20string%20format"),
+            title None
+        );
+
+        note_observation!(
+            &conn,
+            url "https://www.sqlite.org/lang_corefunc.html#instr",
+            view_time Some(20000),
+            search_term Some("sqlite like"),
+            document_type Some(DocumentType::Regular),
+            referrer_url Some("https://www.google.com/search?client=firefox-b-d&q=sqlite+like"),
+            title None
+        );
+
+        note_observation!(
+            &conn,
+            url "https://www.youtube.com/watch?v=tpiyEe_CqB4",
+            view_time Some(100000),
+            search_term Some("cute cat"),
+            document_type Some(DocumentType::Media),
+            referrer_url Some("https://www.youtube.com/results?search_query=cute+cat"),
+            title None
+        );
+
+        // query by title
+        let meta = query(&conn, "child care", 10).expect("query should work");
+        assert_eq!(1, meta.len(), "expected exactly one result");
+        assert_history_metadata_record!(meta[0],
+            url "https://www.cbc.ca/news/politics/federal-budget-2021-freeland-zimonjic-1.5991021",
+            total_time 20000,
+            search_term Some("cbc federal budget 2021"),
+            document_type DocumentType::Regular,
+            referrer_url Some("https://yandex.ru/search/?text=cbc%20federal%20budget%202021&lr=21512"),
+            title Some("Budget vows to build &#x27;for the long term&#x27; as it promises child care cash, projects massive deficits | CBC News"),
+            preview_image_url Some("https://i.cbc.ca/1.5993583.1618861792!/cpImage/httpImage/image.jpg_gen/derivatives/16x9_620/fedbudget-20210419.jpg")
+        );
+
+        // query by search term
+        let meta = query(&conn, "string format", 10).expect("query should work");
+        assert_eq!(1, meta.len(), "expected exactly one result");
+        assert_history_metadata_record!(meta[0],
+            url "https://stackoverflow.com/questions/37777675/how-to-create-a-formatted-string-out-of-a-literal-in-rust",
+            total_time 20000,
+            search_term Some("rust string format"),
+            document_type DocumentType::Regular,
+            referrer_url Some("https://yandex.ru/search/?lr=21512&text=rust%20string%20format"),
+            title None,
+            preview_image_url None
+        );
+
+        // query by url
+        let meta = query(&conn, "instr", 10).expect("query should work");
+        assert_history_metadata_record!(meta[0],
+            url "https://www.sqlite.org/lang_corefunc.html#instr",
+            total_time 20000,
+            search_term Some("sqlite like"),
+            document_type DocumentType::Regular,
+            referrer_url Some("https://www.google.com/search?client=firefox-b-d&q=sqlite+like"),
+            title None,
+            preview_image_url None
+        );
+
+        // by url, referrer domain is different
+        let meta = query(&conn, "youtube", 10).expect("query should work");
+        assert_history_metadata_record!(meta[0],
+            url "https://www.youtube.com/watch?v=tpiyEe_CqB4",
+            total_time 100000,
+            search_term Some("cute cat"),
+            document_type DocumentType::Media,
+            referrer_url Some("https://www.youtube.com/results?search_query=cute+cat"),
+            title None,
+            preview_image_url None
+        );
+    }
+
+    #[test]
+    fn test_delete_metadata() {
+        let conn = PlacesDb::open_in_memory(ConnectionType::ReadWrite).expect("memory db");
+
+        // url  |   search_term |   referrer
+        // 1    |    1          |   1
+        // 1    |    1          |   0
+        // 1    |    0          |   1
+        // 1    |    0          |   0
+
+        note_observation!(&conn,
+            url "http://mozilla.com/1",
+            view_time Some(20000),
+            search_term Some("1 with search"),
+            document_type Some(DocumentType::Regular),
+            referrer_url Some("http://mozilla.com/"),
+            title None
+        );
+
+        note_observation!(&conn,
+            url "http://mozilla.com/1",
+            view_time Some(20000),
+            search_term Some("1 with search"),
+            document_type Some(DocumentType::Regular),
+            referrer_url None,
+            title None
+        );
+
+        note_observation!(&conn,
+            url "http://mozilla.com/1",
+            view_time Some(20000),
+            search_term None,
+            document_type Some(DocumentType::Regular),
+            referrer_url Some("http://mozilla.com/"),
+            title None
+        );
+
+        note_observation!(&conn,
+            url "http://mozilla.com/1",
+            view_time Some(20000),
+            search_term None,
+            document_type Some(DocumentType::Regular),
+            referrer_url None,
+            title None
+        );
+
+        note_observation!(&conn,
+            url "http://mozilla.com/2",
+            view_time Some(20000),
+            search_term None,
+            document_type Some(DocumentType::Regular),
+            referrer_url None,
+            title None
+        );
+
+        note_observation!(&conn,
+            url "http://mozilla.com/2",
+            view_time Some(20000),
+            search_term None,
+            document_type Some(DocumentType::Regular),
+            referrer_url Some("http://mozilla.com/"),
+            title None
+        );
+
+        thread::sleep(time::Duration::from_millis(10));
+        // same observation a bit later:
+        note_observation!(&conn,
+            url "http://mozilla.com/2",
+            view_time Some(20000),
+            search_term None,
+            document_type Some(DocumentType::Regular),
+            referrer_url Some("http://mozilla.com/"),
+            title None
+        );
+
+        assert_eq!(6, get_since(&conn, 0).expect("get worked").len());
+        delete_metadata(
+            &conn,
+            &Url::parse("http://mozilla.com/1").unwrap(),
+            None,
+            None,
+        )
+        .expect("delete metadata");
+        assert_eq!(5, get_since(&conn, 0).expect("get worked").len());
+
+        delete_metadata(
+            &conn,
+            &Url::parse("http://mozilla.com/1").unwrap(),
+            Some(&Url::parse("http://mozilla.com/").unwrap()),
+            None,
+        )
+        .expect("delete metadata");
+        assert_eq!(4, get_since(&conn, 0).expect("get worked").len());
+
+        delete_metadata(
+            &conn,
+            &Url::parse("http://mozilla.com/1").unwrap(),
+            Some(&Url::parse("http://mozilla.com/").unwrap()),
+            Some("1 with search"),
+        )
+        .expect("delete metadata");
+        assert_eq!(3, get_since(&conn, 0).expect("get worked").len());
+
+        delete_metadata(
+            &conn,
+            &Url::parse("http://mozilla.com/1").unwrap(),
+            None,
+            Some("1 with search"),
+        )
+        .expect("delete metadata");
+        assert_eq!(2, get_since(&conn, 0).expect("get worked").len());
+
+        // key doesn't match, do nothing
+        delete_metadata(
+            &conn,
+            &Url::parse("http://mozilla.com/2").unwrap(),
+            Some(&Url::parse("http://wrong-referrer.com").unwrap()),
+            Some("2 with search"),
+        )
+        .expect("delete metadata");
+        assert_eq!(2, get_since(&conn, 0).expect("get worked").len());
+    }
+
+    #[test]
+    fn test_delete_older_than() {
+        let conn = PlacesDb::open_in_memory(ConnectionType::ReadWrite).expect("memory db");
+
+        let beginning = Timestamp::now().as_millis() as i64;
+
+        note_observation!(&conn,
+            url "http://mozilla.com/1",
+            view_time Some(20000),
+            search_term None,
+            document_type Some(DocumentType::Regular),
+            referrer_url None,
+            title None
+        );
+        let after_meta1 = Timestamp::now().as_millis() as i64;
+
+        thread::sleep(time::Duration::from_millis(10));
+
+        note_observation!(&conn,
+            url "http://mozilla.com/2",
+            view_time Some(20000),
+            search_term None,
+            document_type Some(DocumentType::Regular),
+            referrer_url None,
+            title None
+        );
+
+        thread::sleep(time::Duration::from_millis(10));
+
+        note_observation!(&conn,
+            url "http://mozilla.com/3",
+            view_time Some(20000),
+            search_term None,
+            document_type Some(DocumentType::Regular),
+            referrer_url None,
+            title None
+        );
+        let after_meta3 = Timestamp::now().as_millis() as i64;
+
+        // deleting nothing.
+        delete_older_than(&conn, beginning).expect("delete worked");
+        assert_eq!(3, get_since(&conn, beginning).expect("get worked").len());
+
+        // boundary condition, should only delete the first one.
+        delete_older_than(&conn, after_meta1).expect("delete worked");
+        assert_eq!(2, get_since(&conn, beginning).expect("get worked").len());
+        assert_eq!(
+            None,
+            get_latest_for_url(&conn, &Url::parse("http://mozilla.com/1").expect("url"))
+                .expect("get")
+        );
+
+        // delete everything now.
+        delete_older_than(&conn, after_meta3).expect("delete worked");
+        assert_eq!(0, get_since(&conn, beginning).expect("get worked").len());
+    }
+
+    #[test]
+    fn test_delete_between() {
+        let conn = PlacesDb::open_in_memory(ConnectionType::ReadWrite).expect("memory db");
+
+        let beginning = Timestamp::now().as_millis() as i64;
+        thread::sleep(time::Duration::from_millis(10));
+
+        note_observation!(&conn,
+            url "http://mozilla.com/1",
+            view_time Some(20000),
+            search_term None,
+            document_type Some(DocumentType::Regular),
+            referrer_url None,
+            title None
+        );
+
+        thread::sleep(time::Duration::from_millis(10));
+
+        note_observation!(&conn,
+            url "http://mozilla.com/2",
+            view_time Some(20000),
+            search_term None,
+            document_type Some(DocumentType::Regular),
+            referrer_url None,
+            title None
+        );
+        let after_meta2 = Timestamp::now().as_millis() as i64;
+
+        thread::sleep(time::Duration::from_millis(10));
+
+        note_observation!(&conn,
+            url "http://mozilla.com/3",
+            view_time Some(20000),
+            search_term None,
+            document_type Some(DocumentType::Regular),
+            referrer_url None,
+            title None
+        );
+        let after_meta3 = Timestamp::now().as_millis() as i64;
+
+        // deleting meta 3
+        delete_between(&conn, after_meta2, after_meta3).expect("delete worked");
+        assert_eq!(2, get_since(&conn, beginning).expect("get worked").len());
+        assert_eq!(
+            None,
+            get_latest_for_url(&conn, &Url::parse("http://mozilla.com/3").expect("url"))
+                .expect("get")
+        );
+    }
+
+    #[test]
+    fn test_metadata_deletes_do_not_affect_places() {
+        let conn = PlacesDb::open_in_memory(ConnectionType::ReadWrite).expect("memory db");
+
+        note_observation!(
+            &conn,
+            url "https://www.mozilla.org/first/",
+            view_time Some(20000),
+            search_term None,
+            document_type Some(DocumentType::Regular),
+            referrer_url Some("https://www.google.com/search?client=firefox-b-d&q=mozilla+firefox"),
+            title None
+        );
+
+        note_observation!(
+            &conn,
+            url "https://www.mozilla.org/",
+            view_time Some(20000),
+            search_term None,
+            document_type Some(DocumentType::Regular),
+            referrer_url Some("https://www.google.com/search?client=firefox-b-d&q=mozilla+firefox"),
+            title None
+        );
+        let after_meta_added = Timestamp::now().as_millis() as i64;
+
+        // Delete all metadata.
+        delete_older_than(&conn, after_meta_added).expect("delete older than worked");
+
+        // Query places. Records there should not have been affected by the delete above.
+        // 2 for metadata entries + 1 for referrer url.
+        assert_table_size!(&conn, "moz_places", 3);
+    }
+
+    #[test]
+    fn test_delete_history_also_deletes_metadata_bookmarked() {
+        let conn = PlacesDb::open_in_memory(ConnectionType::ReadWrite).expect("memory db");
+        // Item 1 - bookmarked with regular visits and history metadata
+        let url = Url::parse("https://www.mozilla.org/bookmarked").unwrap();
+        let bm_guid: SyncGuid = "bookmarkAAAA".into();
+        let bm = InsertableBookmark {
+            parent_guid: BookmarkRootGuid::Unfiled.into(),
+            position: BookmarkPosition::Append,
+            date_added: None,
+            last_modified: None,
+            guid: Some(bm_guid.clone()),
+            url: url.clone(),
+            title: Some("bookmarked page".to_string()),
+        };
+        insert_bookmark(&conn, InsertableItem::Bookmark { b: bm }).expect("bookmark should insert");
+        let obs = VisitObservation::new(url.clone()).with_visit_type(VisitType::Link);
+        apply_observation(&conn, obs).expect("Should apply visit");
+        note_observation!(
+            &conn,
+            url url.to_string(),
+            view_time Some(20000),
+            search_term None,
+            document_type Some(DocumentType::Regular),
+            referrer_url Some("https://www.google.com/search?client=firefox-b-d&q=mozilla+firefox"),
+            title None
+        );
+
+        // Check the DB is what we expect before deleting.
+        assert_eq!(
+            get_visit_count(&conn, VisitTransitionSet::empty()).unwrap(),
+            1
+        );
+        let place_guid = url_to_guid(&conn, &url)
+            .expect("is valid")
+            .expect("should exist");
+
+        delete_visits_for(&conn, &place_guid).expect("should work");
+        // bookmark must still exist.
+        assert!(get_raw_bookmark(&conn, &bm_guid).unwrap().is_some());
+        // place exists but has no visits.
+        let pi = fetch_page_info(&conn, &url)
+            .expect("should work")
+            .expect("should exist");
+        assert!(pi.last_visit_id.is_none());
+        // and no metadata observations.
+        assert!(get_latest_for_url(&conn, &url)
+            .expect("should work")
+            .is_none());
+    }
+
+    #[test]
+    fn test_delete_history_also_deletes_metadata_not_bookmarked() {
+        let conn = PlacesDb::open_in_memory(ConnectionType::ReadWrite).expect("memory db");
+        // Item is not bookmarked, but has regular visit and a metadata observation.
+        let url = Url::parse("https://www.mozilla.org/not-bookmarked").unwrap();
+        let obs = VisitObservation::new(url.clone()).with_visit_type(VisitType::Link);
+        apply_observation(&conn, obs).expect("Should apply visit");
+        note_observation!(
+            &conn,
+            url url.to_string(),
+            view_time Some(20000),
+            search_term None,
+            document_type Some(DocumentType::Regular),
+            referrer_url Some("https://www.google.com/search?client=firefox-b-d&q=mozilla+firefox"),
+            title None
+        );
+
+        // Check the DB is what we expect before deleting.
+        assert_eq!(
+            get_visit_count(&conn, VisitTransitionSet::empty()).unwrap(),
+            1
+        );
+        let place_guid = url_to_guid(&conn, &url)
+            .expect("is valid")
+            .expect("should exist");
+
+        delete_visits_for(&conn, &place_guid).expect("should work");
+        // place no longer exists.
+        assert!(fetch_page_info(&conn, &url).expect("should work").is_none());
+        assert!(get_latest_for_url(&conn, &url)
+            .expect("should work")
+            .is_none());
+    }
+
+    #[test]
+    fn test_delete_history_also_deletes_metadata_no_visits() {
+        let conn = PlacesDb::open_in_memory(ConnectionType::ReadWrite).expect("memory db");
+        // Item is not bookmarked, no regular visits but a metadata observation.
+        let url = Url::parse("https://www.mozilla.org/no-visits").unwrap();
+        note_observation!(
+            &conn,
+            url url.to_string(),
+            view_time Some(20000),
+            search_term None,
+            document_type Some(DocumentType::Regular),
+            referrer_url Some("https://www.google.com/search?client=firefox-b-d&q=mozilla+firefox"),
+            title None
+        );
+
+        // Check the DB is what we expect before deleting.
+        assert_eq!(
+            get_visit_count(&conn, VisitTransitionSet::empty()).unwrap(),
+            0
+        );
+        let place_guid = url_to_guid(&conn, &url)
+            .expect("is valid")
+            .expect("should exist");
+
+        delete_visits_for(&conn, &place_guid).expect("should work");
+        // place no longer exists.
+        assert!(fetch_page_info(&conn, &url).expect("should work").is_none());
+        assert!(get_latest_for_url(&conn, &url)
+            .expect("should work")
+            .is_none());
+    }
+
+    #[test]
+    fn test_delete_between_also_deletes_metadata() -> Result<()> {
+        let conn = PlacesDb::open_in_memory(ConnectionType::ReadWrite).expect("memory db");
+
+        let now = Timestamp::now();
+        let url = Url::parse("https://www.mozilla.org/").unwrap();
+        let other_url =
+            Url::parse("https://www.google.com/search?client=firefox-b-d&q=mozilla+firefox")
+                .unwrap();
+        let start_timestamp = Timestamp(now.as_millis() - 1000_u64);
+        let end_timestamp = Timestamp(now.as_millis() + 1000_u64);
+        let observation1 = VisitObservation::new(url.clone())
+            .with_at(start_timestamp)
+            .with_title(Some(String::from("Test page 0")))
+            .with_is_remote(false)
+            .with_visit_type(VisitType::Link);
+
+        let observation2 = VisitObservation::new(other_url)
+            .with_at(end_timestamp)
+            .with_title(Some(String::from("Test page 1")))
+            .with_is_remote(false)
+            .with_visit_type(VisitType::Link);
+
+        apply_observation(&conn, observation1).expect("Should apply visit");
+        apply_observation(&conn, observation2).expect("Should apply visit");
+
+        note_observation!(
+            &conn,
+            url "https://www.mozilla.org/",
+            view_time Some(20000),
+            search_term Some("mozilla firefox"),
+            document_type Some(DocumentType::Regular),
+            referrer_url Some("https://www.google.com/search?client=firefox-b-d&q=mozilla+firefox"),
+            title None
+        );
+        assert_eq!(
+            "https://www.mozilla.org/",
+            get_latest_for_url(&conn, &url)?.unwrap().url
+        );
+        delete_visits_between(&conn, start_timestamp, end_timestamp)?;
+        assert_eq!(None, get_latest_for_url(&conn, &url)?);
+        Ok(())
+    }
+
+    #[test]
+    fn test_places_delete_triggers_with_bookmarks() {
+        // The cleanup functionality lives as a TRIGGER in `create_shared_triggers`.
+        let conn = PlacesDb::open_in_memory(ConnectionType::ReadWrite).expect("memory db");
+
+        let now = Timestamp::now();
+        let url = Url::parse("https://www.mozilla.org/").unwrap();
+        let parent_url =
+            Url::parse("https://www.google.com/search?client=firefox-b-d&q=mozilla+firefox")
+                .unwrap();
+
+        let observation1 = VisitObservation::new(url.clone())
+            .with_at(now)
+            .with_title(Some(String::from("Test page 0")))
+            .with_is_remote(false)
+            .with_visit_type(VisitType::Link);
+
+        let observation2 = VisitObservation::new(parent_url.clone())
+            .with_at(now)
+            .with_title(Some(String::from("Test page 1")))
+            .with_is_remote(false)
+            .with_visit_type(VisitType::Link);
+
+        apply_observation(&conn, observation1).expect("Should apply visit");
+        apply_observation(&conn, observation2).expect("Should apply visit");
+
+        assert_table_size!(&conn, "moz_bookmarks", 5);
+
+        // add bookmark for the page we have a metadata entry
+        insert_bookmark(
+            &conn,
+            InsertableItem::Bookmark {
+                b: InsertableBookmark {
+                    parent_guid: BookmarkRootGuid::Unfiled.into(),
+                    position: BookmarkPosition::Append,
+                    date_added: None,
+                    last_modified: None,
+                    guid: Some(SyncGuid::from("cccccccccccc")),
+                    url,
+                    title: None,
+                },
+            },
+        )
+        .expect("bookmark insert worked");
+
+        // add another bookmark to the "parent" of our metadata entry
+        insert_bookmark(
+            &conn,
+            InsertableItem::Bookmark {
+                b: InsertableBookmark {
+                    parent_guid: BookmarkRootGuid::Unfiled.into(),
+                    position: BookmarkPosition::Append,
+                    date_added: None,
+                    last_modified: None,
+                    guid: Some(SyncGuid::from("ccccccccccca")),
+                    url: parent_url,
+                    title: None,
+                },
+            },
+        )
+        .expect("bookmark insert worked");
+
+        assert_table_size!(&conn, "moz_bookmarks", 7);
+        assert_table_size!(&conn, "moz_origins", 2);
+
+        note_observation!(
+            &conn,
+            url "https://www.mozilla.org/",
+            view_time Some(20000),
+            search_term Some("mozilla firefox"),
+            document_type Some(DocumentType::Regular),
+            referrer_url Some("https://www.google.com/search?client=firefox-b-d&q=mozilla+firefox"),
+            title None
+        );
+
+        assert_table_size!(&conn, "moz_origins", 2);
+
+        // this somehow deletes 1 origin record, and our metadata
+        delete_everything(&conn).expect("places wipe succeeds");
+
+        assert_table_size!(&conn, "moz_places_metadata", 0);
+        assert_table_size!(&conn, "moz_places_metadata_search_queries", 0);
+    }
+
+    #[test]
+    fn test_places_delete_triggers() {
+        // The cleanup functionality lives as a TRIGGER in `create_shared_triggers`.
+        let conn = PlacesDb::open_in_memory(ConnectionType::ReadWrite).expect("memory db");
+
+        let now = Timestamp::now();
+        let observation1 = VisitObservation::new(Url::parse("https://www.mozilla.org/").unwrap())
+            .with_at(now)
+            .with_title(Some(String::from("Test page 1")))
+            .with_is_remote(false)
+            .with_visit_type(VisitType::Link);
+        let observation2 =
+            VisitObservation::new(Url::parse("https://www.mozilla.org/another/").unwrap())
+                .with_at(Timestamp(now.as_millis() + 10000))
+                .with_title(Some(String::from("Test page 3")))
+                .with_is_remote(false)
+                .with_visit_type(VisitType::Link);
+        let observation3 =
+            VisitObservation::new(Url::parse("https://www.mozilla.org/first/").unwrap())
+                .with_at(Timestamp(now.as_millis() - 10000))
+                .with_title(Some(String::from("Test page 0")))
+                .with_is_remote(true)
+                .with_visit_type(VisitType::Link);
+        apply_observation(&conn, observation1).expect("Should apply visit");
+        apply_observation(&conn, observation2).expect("Should apply visit");
+        apply_observation(&conn, observation3).expect("Should apply visit");
+
+        note_observation!(
+            &conn,
+            url "https://www.mozilla.org/first/",
+            view_time Some(20000),
+            search_term None,
+            document_type Some(DocumentType::Regular),
+            referrer_url Some("https://www.google.com/search?client=firefox-b-d&q=mozilla+firefox"),
+            title None
+        );
+
+        note_observation!(
+            &conn,
+            url "https://www.mozilla.org/",
+            view_time Some(20000),
+            search_term None,
+            document_type Some(DocumentType::Regular),
+            referrer_url Some("https://www.google.com/search?client=firefox-b-d&q=mozilla+firefox"),
+            title None
+        );
+
+        note_observation!(
+            &conn,
+            url "https://www.mozilla.org/",
+            view_time Some(20000),
+            search_term Some("mozilla"),
+            document_type Some(DocumentType::Regular),
+            referrer_url Some("https://www.google.com/search?client=firefox-b-d&q=mozilla+firefox"),
+            title None
+        );
+
+        note_observation!(
+            &conn,
+            url "https://www.mozilla.org/",
+            view_time Some(25000),
+            search_term Some("firefox"),
+            document_type Some(DocumentType::Media),
+            referrer_url Some("https://www.google.com/search?client=firefox-b-d&q=mozilla+firefox"),
+            title None
+        );
+
+        note_observation!(
+            &conn,
+            url "https://www.mozilla.org/another/",
+            view_time Some(20000),
+            search_term Some("mozilla"),
+            document_type Some(DocumentType::Regular),
+            referrer_url Some("https://www.google.com/search?client=firefox-b-d&q=mozilla+firefox"),
+            title None
+        );
+
+        // double-check that we have the 'firefox' search query entry.
+        assert!(conn
+            .try_query_one::<i64, _>(
+                "SELECT id FROM moz_places_metadata_search_queries WHERE term = :term",
+                rusqlite::named_params! { ":term": "firefox" },
+                true
+            )
+            .expect("select works")
+            .is_some());
+
+        // Delete our first page & its visits. Note that /another/ page will remain in place.
+        delete_visits_between(
+            &conn,
+            Timestamp(now.as_millis() - 1000),
+            Timestamp(now.as_millis() + 1000),
+        )
+        .expect("delete worked");
+
+        let meta1 =
+            get_latest_for_url(&conn, &Url::parse("https://www.mozilla.org/").expect("url"))
+                .expect("get worked");
+        let meta2 = get_latest_for_url(
+            &conn,
+            &Url::parse("https://www.mozilla.org/another/").expect("url"),
+        )
+        .expect("get worked");
+
+        assert!(meta1.is_none(), "expected metadata to have been deleted");
+        // Verify that if a history metadata entry was entered **after** the visit
+        // then we delete the range of the metadata, and not the visit. The metadata
+        // is still deleted
+        assert!(meta2.is_none(), "expected metadata to been deleted");
+
+        // The 'mozilla' search query entry is deleted since the delete cascades.
+        assert!(
+            conn.try_query_one::<i64, _>(
+                "SELECT id FROM moz_places_metadata_search_queries WHERE term = :term",
+                rusqlite::named_params! { ":term": "mozilla" },
+                true
+            )
+            .expect("select works")
+            .is_none(),
+            "search_query records with related metadata should have been deleted"
+        );
+
+        // don't have the 'firefox' search query entry either, nothing points to it.
+        assert!(
+            conn.try_query_one::<i64, _>(
+                "SELECT id FROM moz_places_metadata_search_queries WHERE term = :term",
+                rusqlite::named_params! { ":term": "firefox" },
+                true
+            )
+            .expect("select works")
+            .is_none(),
+            "search_query records without related metadata should have been deleted"
+        );
+
+        // now, let's wipe places, and make sure none of the metadata stuff remains.
+        delete_everything(&conn).expect("places wipe succeeds");
+
+        assert_table_size!(&conn, "moz_places_metadata", 0);
+        assert_table_size!(&conn, "moz_places_metadata_search_queries", 0);
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/places/storage/mod.rs.html b/book/rust-docs/src/places/storage/mod.rs.html new file mode 100644 index 0000000000..0b6e806951 --- /dev/null +++ b/book/rust-docs/src/places/storage/mod.rs.html @@ -0,0 +1,1211 @@ +mod.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
+540
+541
+542
+543
+544
+545
+546
+547
+548
+549
+550
+551
+552
+553
+554
+555
+556
+557
+558
+559
+560
+561
+562
+563
+564
+565
+566
+567
+568
+569
+570
+571
+572
+573
+574
+575
+576
+577
+578
+579
+580
+581
+582
+583
+584
+585
+586
+587
+588
+589
+590
+591
+592
+593
+594
+595
+596
+597
+598
+599
+600
+601
+602
+603
+604
+605
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// A "storage" module - this module is intended to be the layer between the
+// API and the database.
+
+pub mod bookmarks;
+pub mod history;
+pub mod history_metadata;
+pub mod tags;
+
+use crate::db::PlacesDb;
+use crate::error::{Error, InvalidPlaceInfo, Result};
+use crate::ffi::HistoryVisitInfo;
+use crate::ffi::TopFrecentSiteInfo;
+use crate::frecency::{calculate_frecency, DEFAULT_FRECENCY_SETTINGS};
+use crate::types::{SyncStatus, UnknownFields, VisitType};
+use interrupt_support::SqlInterruptScope;
+use rusqlite::types::{FromSql, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
+use rusqlite::Result as RusqliteResult;
+use rusqlite::{Connection, Row};
+use serde_derive::*;
+use sql_support::{self, ConnExt};
+use std::fmt;
+use sync_guid::Guid as SyncGuid;
+use types::Timestamp;
+use url::Url;
+
+/// From https://searchfox.org/mozilla-central/rev/93905b660f/toolkit/components/places/PlacesUtils.jsm#189
+pub const URL_LENGTH_MAX: usize = 65536;
+pub const TITLE_LENGTH_MAX: usize = 4096;
+pub const TAG_LENGTH_MAX: usize = 100;
+// pub const DESCRIPTION_LENGTH_MAX: usize = 256;
+
+// Typesafe way to manage RowIds. Does it make sense? A better way?
+#[derive(
+    Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Deserialize, Serialize, Default, Hash,
+)]
+pub struct RowId(pub i64);
+
+impl From<RowId> for i64 {
+    // XXX - ToSql!
+    #[inline]
+    fn from(id: RowId) -> Self {
+        id.0
+    }
+}
+
+impl fmt::Display for RowId {
+    #[inline]
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "{}", self.0)
+    }
+}
+
+impl ToSql for RowId {
+    fn to_sql(&self) -> RusqliteResult<ToSqlOutput<'_>> {
+        Ok(ToSqlOutput::from(self.0))
+    }
+}
+
+impl FromSql for RowId {
+    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
+        value.as_i64().map(RowId)
+    }
+}
+
+#[derive(Debug)]
+pub struct PageInfo {
+    pub url: Url,
+    pub guid: SyncGuid,
+    pub row_id: RowId,
+    pub title: String,
+    pub hidden: bool,
+    pub preview_image_url: Option<Url>,
+    pub typed: u32,
+    pub frecency: i32,
+    pub visit_count_local: i32,
+    pub visit_count_remote: i32,
+    pub last_visit_date_local: Timestamp,
+    pub last_visit_date_remote: Timestamp,
+    pub sync_status: SyncStatus,
+    pub sync_change_counter: u32,
+    pub unknown_fields: UnknownFields,
+}
+
+impl PageInfo {
+    pub fn from_row(row: &Row<'_>) -> Result<Self> {
+        Ok(Self {
+            url: Url::parse(&row.get::<_, String>("url")?)?,
+            guid: row.get::<_, String>("guid")?.into(),
+            row_id: row.get("id")?,
+            title: row.get::<_, Option<String>>("title")?.unwrap_or_default(),
+            hidden: row.get("hidden")?,
+            preview_image_url: match row.get::<_, Option<String>>("preview_image_url")? {
+                Some(ref preview_image_url) => Some(Url::parse(preview_image_url)?),
+                None => None,
+            },
+            typed: row.get("typed")?,
+
+            frecency: row.get("frecency")?,
+            visit_count_local: row.get("visit_count_local")?,
+            visit_count_remote: row.get("visit_count_remote")?,
+
+            last_visit_date_local: row
+                .get::<_, Option<Timestamp>>("last_visit_date_local")?
+                .unwrap_or_default(),
+            last_visit_date_remote: row
+                .get::<_, Option<Timestamp>>("last_visit_date_remote")?
+                .unwrap_or_default(),
+
+            sync_status: SyncStatus::from_u8(row.get::<_, u8>("sync_status")?),
+            sync_change_counter: row
+                .get::<_, Option<u32>>("sync_change_counter")?
+                .unwrap_or_default(),
+            unknown_fields: match row.get::<_, Option<String>>("unknown_fields")? {
+                Some(v) => serde_json::from_str(&v)?,
+                None => UnknownFields::new(),
+            },
+        })
+    }
+}
+
+// fetch_page_info gives you one of these.
+#[derive(Debug)]
+pub struct FetchedPageInfo {
+    pub page: PageInfo,
+    // XXX - not clear what this is used for yet, and whether it should be local, remote or either?
+    // The sql below isn't quite sure either :)
+    pub last_visit_id: Option<RowId>,
+}
+
+impl FetchedPageInfo {
+    pub fn from_row(row: &Row<'_>) -> Result<Self> {
+        Ok(Self {
+            page: PageInfo::from_row(row)?,
+            last_visit_id: row.get::<_, Option<RowId>>("last_visit_id")?,
+        })
+    }
+}
+
+// History::FetchPageInfo
+pub fn fetch_page_info(db: &PlacesDb, url: &Url) -> Result<Option<FetchedPageInfo>> {
+    let sql = "
+      SELECT guid, url, id, title, hidden, typed, frecency,
+             visit_count_local, visit_count_remote,
+             last_visit_date_local, last_visit_date_remote,
+             sync_status, sync_change_counter, preview_image_url,
+             unknown_fields,
+             (SELECT id FROM moz_historyvisits
+              WHERE place_id = h.id
+                AND (visit_date = h.last_visit_date_local OR
+                     visit_date = h.last_visit_date_remote)) AS last_visit_id
+      FROM moz_places h
+      WHERE url_hash = hash(:page_url) AND url = :page_url";
+    db.try_query_row(
+        sql,
+        &[(":page_url", &String::from(url.clone()))],
+        FetchedPageInfo::from_row,
+        true,
+    )
+}
+
+fn new_page_info(db: &PlacesDb, url: &Url, new_guid: Option<SyncGuid>) -> Result<PageInfo> {
+    let guid = match new_guid {
+        Some(guid) => guid,
+        None => SyncGuid::random(),
+    };
+    let url_str = url.as_str();
+    if url_str.len() > URL_LENGTH_MAX {
+        // Generally callers check this first (bookmarks don't, history does).
+        return Err(Error::InvalidPlaceInfo(InvalidPlaceInfo::UrlTooLong));
+    }
+    let sql = "INSERT INTO moz_places (guid, url, url_hash)
+               VALUES (:guid, :url, hash(:url))";
+    db.execute_cached(sql, &[(":guid", &guid as &dyn ToSql), (":url", &url_str)])?;
+    Ok(PageInfo {
+        url: url.clone(),
+        guid,
+        row_id: RowId(db.conn().last_insert_rowid()),
+        title: "".into(),
+        hidden: true, // will be set to false as soon as a non-hidden visit appears.
+        preview_image_url: None,
+        typed: 0,
+        frecency: -1,
+        visit_count_local: 0,
+        visit_count_remote: 0,
+        last_visit_date_local: Timestamp(0),
+        last_visit_date_remote: Timestamp(0),
+        sync_status: SyncStatus::New,
+        sync_change_counter: 0,
+        unknown_fields: UnknownFields::new(),
+    })
+}
+
+impl HistoryVisitInfo {
+    fn from_row(row: &rusqlite::Row<'_>) -> Result<Self> {
+        let visit_type = VisitType::from_primitive(row.get::<_, u8>("visit_type")?)
+            // Do we have an existing error we use for this? For now they
+            // probably don't care too much about VisitType, so this
+            // is fine.
+            .unwrap_or(VisitType::Link);
+        let visit_date: Timestamp = row.get("visit_date")?;
+        let url: String = row.get("url")?;
+        let preview_image_url: Option<String> = row.get("preview_image_url")?;
+        Ok(Self {
+            url: Url::parse(&url)?,
+            title: row.get("title")?,
+            timestamp: visit_date,
+            visit_type,
+            is_hidden: row.get("hidden")?,
+            preview_image_url: match preview_image_url {
+                Some(s) => Some(Url::parse(&s)?),
+                None => None,
+            },
+            is_remote: !row.get("is_local")?,
+        })
+    }
+}
+
+impl TopFrecentSiteInfo {
+    pub(crate) fn from_row(row: &rusqlite::Row<'_>) -> Result<Self> {
+        let url: String = row.get("url")?;
+        Ok(Self {
+            url: Url::parse(&url)?,
+            title: row.get("title")?,
+        })
+    }
+}
+
+#[derive(Debug)]
+pub struct RunMaintenanceMetrics {
+    pub pruned_visits: bool,
+    pub db_size_before: u32,
+    pub db_size_after: u32,
+}
+
+/// Run maintenance on the places DB (prune step)
+///
+/// The `run_maintenance_*()` functions are intended to be run during idle time and will take steps
+/// to clean up / shrink the database.  They're split up so that we can time each one in the
+/// Kotlin wrapper code (This is needed because we only have access to the Glean API in Kotlin and
+/// it supports a stop-watch style API, not recording specific values).
+///
+/// db_size_limit is the approximate storage limit in bytes.  If the database is using more space
+/// than this, some older visits will be deleted to free up space.  Pass in a 0 to skip this.
+pub fn run_maintenance_prune(conn: &PlacesDb, db_size_limit: u32) -> Result<RunMaintenanceMetrics> {
+    let db_size_before = conn.get_db_size()?;
+    let should_prune = db_size_limit > 0 && db_size_before > db_size_limit;
+    if should_prune {
+        history::prune_older_visits(conn)?;
+    }
+    let db_size_after = conn.get_db_size()?;
+    Ok(RunMaintenanceMetrics {
+        pruned_visits: should_prune,
+        db_size_before,
+        db_size_after,
+    })
+}
+
+/// Run maintenance on the places DB (vacuum step)
+///
+/// The `run_maintenance_*()` functions are intended to be run during idle time and will take steps
+/// to clean up / shrink the database.  They're split up so that we can time each one in the
+/// Kotlin wrapper code (This is needed because we only have access to the Glean API in Kotlin and
+/// it supports a stop-watch style API, not recording specific values).
+pub fn run_maintenance_vacuum(conn: &PlacesDb) -> Result<()> {
+    let auto_vacuum_setting: u32 = conn.query_one("PRAGMA auto_vacuum")?;
+    if auto_vacuum_setting == 2 {
+        // Ideally, we run an incremental vacuum to delete 2 pages
+        conn.execute_one("PRAGMA incremental_vacuum(2)")?;
+    } else {
+        // If auto_vacuum=incremental isn't set, configure it and run a full vacuum.
+        log::warn!(
+            "run_maintenance_vacuum: Need to run a full vacuum to set auto_vacuum=incremental"
+        );
+        conn.execute_one("PRAGMA auto_vacuum=incremental")?;
+        conn.execute_one("VACUUM")?;
+    }
+    Ok(())
+}
+
+/// Run maintenance on the places DB (optimize step)
+///
+/// The `run_maintenance_*()` functions are intended to be run during idle time and will take steps
+/// to clean up / shrink the database.  They're split up so that we can time each one in the
+/// Kotlin wrapper code (This is needed because we only have access to the Glean API in Kotlin and
+/// it supports a stop-watch style API, not recording specific values).
+pub fn run_maintenance_optimize(conn: &PlacesDb) -> Result<()> {
+    conn.execute_one("PRAGMA optimize")?;
+    Ok(())
+}
+
+/// Run maintenance on the places DB (checkpoint step)
+///
+/// The `run_maintenance_*()` functions are intended to be run during idle time and will take steps
+/// to clean up / shrink the database.  They're split up so that we can time each one in the
+/// Kotlin wrapper code (This is needed because we only have access to the Glean API in Kotlin and
+/// it supports a stop-watch style API, not recording specific values).
+pub fn run_maintenance_checkpoint(conn: &PlacesDb) -> Result<()> {
+    conn.execute_one("PRAGMA wal_checkpoint(PASSIVE)")?;
+    Ok(())
+}
+
+pub fn update_all_frecencies_at_once(db: &PlacesDb, scope: &SqlInterruptScope) -> Result<()> {
+    let tx = db.begin_transaction()?;
+
+    let need_frecency_update = tx.query_rows_and_then(
+        "SELECT place_id FROM moz_places_stale_frecencies",
+        [],
+        |r| r.get::<_, i64>(0),
+    )?;
+    scope.err_if_interrupted()?;
+    let frecencies = need_frecency_update
+        .iter()
+        .map(|places_id| {
+            scope.err_if_interrupted()?;
+            Ok((
+                *places_id,
+                calculate_frecency(db, &DEFAULT_FRECENCY_SETTINGS, *places_id, Some(false))?,
+            ))
+        })
+        .collect::<Result<Vec<(i64, i32)>>>()?;
+
+    if frecencies.is_empty() {
+        return Ok(());
+    }
+    // Update all frecencies in one fell swoop
+    tx.execute_batch(&format!(
+        "WITH frecencies(id, frecency) AS (
+            VALUES {}
+            )
+            UPDATE moz_places SET
+            frecency = (SELECT frecency FROM frecencies f
+                        WHERE f.id = id)
+            WHERE id IN (SELECT f.id FROM frecencies f)",
+        sql_support::repeat_display(frecencies.len(), ",", |index, f| {
+            let (id, frecency) = frecencies[index];
+            write!(f, "({}, {})", id, frecency)
+        })
+    ))?;
+
+    scope.err_if_interrupted()?;
+
+    // ...And remove them from the stale table.
+    tx.execute_batch(&format!(
+        "DELETE FROM moz_places_stale_frecencies
+         WHERE place_id IN ({})",
+        sql_support::repeat_display(frecencies.len(), ",", |index, f| {
+            let (id, _) = frecencies[index];
+            write!(f, "{}", id)
+        })
+    ))?;
+    tx.commit()?;
+
+    Ok(())
+}
+
+pub(crate) fn put_meta(conn: &Connection, key: &str, value: &dyn ToSql) -> Result<()> {
+    conn.execute_cached(
+        "REPLACE INTO moz_meta (key, value) VALUES (:key, :value)",
+        &[(":key", &key as &dyn ToSql), (":value", value)],
+    )?;
+    Ok(())
+}
+
+pub(crate) fn get_meta<T: FromSql>(db: &PlacesDb, key: &str) -> Result<Option<T>> {
+    let res = db.try_query_one(
+        "SELECT value FROM moz_meta WHERE key = :key",
+        &[(":key", &key)],
+        true,
+    )?;
+    Ok(res)
+}
+
+pub(crate) fn delete_meta(db: &PlacesDb, key: &str) -> Result<()> {
+    db.execute_cached("DELETE FROM moz_meta WHERE key = :key", &[(":key", &key)])?;
+    Ok(())
+}
+
+/// Delete all items in the temp tables we use for staging changes.
+pub fn delete_pending_temp_tables(conn: &PlacesDb) -> Result<()> {
+    conn.execute_batch(
+        "DELETE FROM moz_updateoriginsinsert_temp;
+         DELETE FROM moz_updateoriginsupdate_temp;
+         DELETE FROM moz_updateoriginsdelete_temp;",
+    )?;
+    Ok(())
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::api::places_api::test::new_mem_connection;
+    use crate::observation::VisitObservation;
+    use bookmarks::{
+        delete_bookmark, insert_bookmark, BookmarkPosition, BookmarkRootGuid, InsertableBookmark,
+        InsertableItem,
+    };
+    use history::apply_observation;
+
+    #[test]
+    fn test_meta() {
+        let conn = new_mem_connection();
+        let value1 = "value 1".to_string();
+        let value2 = "value 2".to_string();
+        assert!(get_meta::<String>(&conn, "foo")
+            .expect("should get")
+            .is_none());
+        put_meta(&conn, "foo", &value1).expect("should put");
+        assert_eq!(
+            get_meta(&conn, "foo").expect("should get new val"),
+            Some(value1)
+        );
+        put_meta(&conn, "foo", &value2).expect("should put an existing value");
+        assert_eq!(get_meta(&conn, "foo").expect("should get"), Some(value2));
+        delete_meta(&conn, "foo").expect("should delete");
+        assert!(get_meta::<String>(&conn, "foo")
+            .expect("should get non-existing")
+            .is_none());
+        delete_meta(&conn, "foo").expect("delete non-existing should work");
+    }
+
+    // Here we try and test that we replicate desktop behaviour, which isn't that obvious.
+    // * create a bookmark
+    // * remove the bookmark - this doesn't remove the place or origin - probably because in
+    //   real browsers there will be visits for the URL existing, but this still smells like
+    //   a bug - see https://bugzilla.mozilla.org/show_bug.cgi?id=1650511#c34
+    // * Arrange for history for that item to be removed, via various means
+    // At this point the origin and place should be removed. The only code (in desktop and here) which
+    // removes places with a foreign_count of zero is that history removal!
+
+    #[test]
+    fn test_removal_delete_visits_between() {
+        do_test_removal_places_and_origins(|conn: &PlacesDb, _guid: &SyncGuid| {
+            history::delete_visits_between(conn, Timestamp::EARLIEST, Timestamp::now())
+        })
+    }
+
+    #[test]
+    fn test_removal_delete_visits_for() {
+        do_test_removal_places_and_origins(|conn: &PlacesDb, guid: &SyncGuid| {
+            history::delete_visits_for(conn, guid)
+        })
+    }
+
+    #[test]
+    fn test_removal_prune() {
+        do_test_removal_places_and_origins(|conn: &PlacesDb, _guid: &SyncGuid| {
+            history::prune_older_visits(conn)
+        })
+    }
+
+    #[test]
+    fn test_removal_visit_at_time() {
+        do_test_removal_places_and_origins(|conn: &PlacesDb, _guid: &SyncGuid| {
+            let url = Url::parse("http://example.com/foo").unwrap();
+            let visit = Timestamp::from(727_747_200_001);
+            history::delete_place_visit_at_time(conn, &url, visit)
+        })
+    }
+
+    #[test]
+    fn test_removal_everything() {
+        do_test_removal_places_and_origins(|conn: &PlacesDb, _guid: &SyncGuid| {
+            history::delete_everything(conn)
+        })
+    }
+
+    // The core test - takes a function which deletes history.
+    fn do_test_removal_places_and_origins<F>(removal_fn: F)
+    where
+        F: FnOnce(&PlacesDb, &SyncGuid) -> Result<()>,
+    {
+        let conn = new_mem_connection();
+        let url = Url::parse("http://example.com/foo").unwrap();
+        let bm = InsertableItem::Bookmark {
+            b: InsertableBookmark {
+                parent_guid: BookmarkRootGuid::Unfiled.into(),
+                position: BookmarkPosition::Append,
+                date_added: None,
+                last_modified: None,
+                guid: None,
+                url: url.clone(),
+                title: Some("the title".into()),
+            },
+        };
+        assert_eq!(
+            conn.query_one::<i64>("SELECT COUNT(*) FROM moz_bookmarks;")
+                .unwrap(),
+            5
+        ); // our 5 roots.
+        let bookmark_guid = insert_bookmark(&conn, bm).unwrap();
+        let place_guid = fetch_page_info(&conn, &url)
+            .expect("should work")
+            .expect("must exist")
+            .page
+            .guid;
+        // the place should exist with a foreign_count of 1.
+        assert_eq!(
+            conn.query_one::<i64>("SELECT COUNT(*) FROM moz_bookmarks;")
+                .unwrap(),
+            6
+        ); // our 5 roots + new bookmark
+        assert_eq!(
+            conn.query_one::<i64>(
+                "SELECT foreign_count FROM moz_places WHERE url = \"http://example.com/foo\";"
+            )
+            .unwrap(),
+            1
+        );
+        // visit the bookmark.
+        assert!(apply_observation(
+            &conn,
+            VisitObservation::new(url)
+                .with_at(Timestamp::from(727_747_200_001))
+                .with_visit_type(VisitType::Link)
+        )
+        .unwrap()
+        .is_some());
+
+        delete_bookmark(&conn, &bookmark_guid).unwrap();
+        assert_eq!(
+            conn.query_one::<i64>("SELECT COUNT(*) FROM moz_bookmarks;")
+                .unwrap(),
+            5
+        ); // our 5 roots
+           // the place should have no foreign references, but still exists.
+        assert_eq!(
+            conn.query_one::<i64>(
+                "SELECT foreign_count FROM moz_places WHERE url = \"http://example.com/foo\";"
+            )
+            .unwrap(),
+            0
+        );
+        removal_fn(&conn, &place_guid).expect("removal function should work");
+        assert_eq!(
+            conn.query_one::<i64>("SELECT COUNT(*) FROM moz_places;")
+                .unwrap(),
+            0
+        );
+        assert_eq!(
+            conn.query_one::<i64>("SELECT COUNT(*) FROM moz_origins;")
+                .unwrap(),
+            0
+        );
+    }
+
+    // Similar to the above, but if the bookmark has no visits the place/origin should die
+    // without requiring history removal
+    #[test]
+    fn test_visitless_removal_places_and_origins() {
+        let conn = new_mem_connection();
+        let url = Url::parse("http://example.com/foo").unwrap();
+        let bm = InsertableItem::Bookmark {
+            b: InsertableBookmark {
+                parent_guid: BookmarkRootGuid::Unfiled.into(),
+                position: BookmarkPosition::Append,
+                date_added: None,
+                last_modified: None,
+                guid: None,
+                url,
+                title: Some("the title".into()),
+            },
+        };
+        assert_eq!(
+            conn.query_one::<i64>("SELECT COUNT(*) FROM moz_bookmarks;")
+                .unwrap(),
+            5
+        ); // our 5 roots.
+        let bookmark_guid = insert_bookmark(&conn, bm).unwrap();
+        // the place should exist with a foreign_count of 1.
+        assert_eq!(
+            conn.query_one::<i64>("SELECT COUNT(*) FROM moz_bookmarks;")
+                .unwrap(),
+            6
+        ); // our 5 roots + new bookmark
+        assert_eq!(
+            conn.query_one::<i64>(
+                "SELECT foreign_count FROM moz_places WHERE url = \"http://example.com/foo\";"
+            )
+            .unwrap(),
+            1
+        );
+        // Delete it.
+        delete_bookmark(&conn, &bookmark_guid).unwrap();
+        assert_eq!(
+            conn.query_one::<i64>("SELECT COUNT(*) FROM moz_bookmarks;")
+                .unwrap(),
+            5
+        ); // our 5 roots
+           // should be gone from places and origins.
+        assert_eq!(
+            conn.query_one::<i64>("SELECT COUNT(*) FROM moz_places;")
+                .unwrap(),
+            0
+        );
+        assert_eq!(
+            conn.query_one::<i64>("SELECT COUNT(*) FROM moz_origins;")
+                .unwrap(),
+            0
+        );
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/places/storage/tags.rs.html b/book/rust-docs/src/places/storage/tags.rs.html new file mode 100644 index 0000000000..c20de4dc51 --- /dev/null +++ b/book/rust-docs/src/places/storage/tags.rs.html @@ -0,0 +1,735 @@ +tags.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use super::{fetch_page_info, TAG_LENGTH_MAX};
+use crate::db::PlacesDb;
+use crate::error::{InvalidPlaceInfo, Result};
+use sql_support::ConnExt;
+use url::Url;
+
+/// The validity of a tag.
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub enum ValidatedTag<'a> {
+    /// The tag is invalid.
+    Invalid(&'a str),
+
+    /// The tag is valid, but normalized to remove leading and trailing
+    /// whitespace.
+    Normalized(&'a str),
+
+    /// The original tag is valid.
+    Original(&'a str),
+}
+
+impl<'a> ValidatedTag<'a> {
+    /// Returns `true` if the original tag is valid; `false` if it's invalid or
+    /// normalized.
+    pub fn is_original(&self) -> bool {
+        matches!(&self, ValidatedTag::Original(_))
+    }
+
+    /// Returns the tag string if the tag is valid or normalized, or an error
+    /// if the tag is invalid.
+    pub fn ensure_valid(&self) -> Result<&'a str> {
+        match self {
+            ValidatedTag::Invalid(_) => Err(InvalidPlaceInfo::InvalidTag.into()),
+            ValidatedTag::Normalized(t) | ValidatedTag::Original(t) => Ok(t),
+        }
+    }
+}
+
+/// Checks the validity of the specified tag.
+pub fn validate_tag(tag: &str) -> ValidatedTag<'_> {
+    // Drop empty and oversized tags.
+    let t = tag.trim();
+    if t.is_empty() || t.len() > TAG_LENGTH_MAX {
+        ValidatedTag::Invalid(tag)
+    } else if t.len() != tag.len() {
+        ValidatedTag::Normalized(t)
+    } else {
+        ValidatedTag::Original(t)
+    }
+}
+
+/// Tags the specified URL.
+///
+/// # Arguments
+///
+/// * `conn` - A database connection on which to operate.
+///
+/// * `url` - The URL to tag.
+///
+/// * `tag` - The tag to add for the URL.
+///
+/// # Returns
+///
+/// There is no success return value.
+pub fn tag_url(db: &PlacesDb, url: &Url, tag: &str) -> Result<()> {
+    let tag = validate_tag(tag).ensure_valid()?;
+    let tx = db.begin_transaction()?;
+
+    // This function will not create a new place.
+    // Fetch the place id, so we (a) avoid creating a new tag when we aren't
+    // going to reference it and (b) to avoid a sub-query.
+    let place_id = match fetch_page_info(db, url)? {
+        Some(info) => info.page.row_id,
+        None => return Err(InvalidPlaceInfo::NoSuchUrl.into()),
+    };
+
+    db.execute_cached(
+        "INSERT OR IGNORE INTO moz_tags(tag, lastModified)
+         VALUES(:tag, now())",
+        &[(":tag", &tag)],
+    )?;
+
+    db.execute_cached(
+        "INSERT OR IGNORE INTO moz_tags_relation(tag_id, place_id)
+         VALUES((SELECT id FROM moz_tags WHERE tag = :tag), :place_id)",
+        &[
+            (":tag", &tag as &dyn rusqlite::ToSql),
+            (":place_id", &place_id),
+        ],
+    )?;
+    tx.commit()?;
+    Ok(())
+}
+
+/// Remove the specified tag from the specified URL.
+///
+/// # Arguments
+///
+/// * `conn` - A database connection on which to operate.
+///
+/// * `url` - The URL from which the tag should be removed.
+///
+/// * `tag` - The tag to remove from the URL.
+///
+/// # Returns
+///
+/// There is no success return value - the operation is ignored if the URL
+/// does not have the tag.
+pub fn untag_url(db: &PlacesDb, url: &Url, tag: &str) -> Result<()> {
+    let tag = validate_tag(tag).ensure_valid()?;
+    db.execute_cached(
+        "DELETE FROM moz_tags_relation
+         WHERE tag_id = (SELECT id FROM moz_tags
+                         WHERE tag = :tag)
+         AND place_id = (SELECT id FROM moz_places
+                         WHERE url_hash = hash(:url)
+                         AND url = :url)",
+        &[(":tag", &tag), (":url", &url.as_str())],
+    )?;
+    Ok(())
+}
+
+/// Remove all tags from the specified URL.
+///
+/// # Arguments
+///
+/// * `conn` - A database connection on which to operate.
+///
+/// * `url` - The URL for which all tags should be removed.
+///
+/// # Returns
+///
+/// There is no success return value.
+pub fn remove_all_tags_from_url(db: &PlacesDb, url: &Url) -> Result<()> {
+    db.execute_cached(
+        "DELETE FROM moz_tags_relation
+         WHERE
+         place_id = (SELECT id FROM moz_places
+                     WHERE url_hash = hash(:url)
+                     AND url = :url)",
+        &[(":url", &url.as_str())],
+    )?;
+    Ok(())
+}
+
+/// Remove the specified tag from all URLs.
+///
+/// # Arguments
+///
+/// * `conn` - A database connection on which to operate.
+///
+/// * `tag` - The tag to remove.
+///
+/// # Returns
+///
+/// There is no success return value.
+pub fn remove_tag(db: &PlacesDb, tag: &str) -> Result<()> {
+    db.execute_cached(
+        "DELETE FROM moz_tags
+         WHERE tag = :tag",
+        &[(":tag", &tag)],
+    )?;
+    Ok(())
+}
+
+/// Retrieves a list of URLs which have the specified tag.
+///
+/// # Arguments
+///
+/// * `conn` - A database connection on which to operate.
+///
+/// * `tag` - The tag to query.
+///
+/// # Returns
+///
+/// * A Vec<Url> with all URLs which have the tag, ordered by the frecency of
+/// the URLs.
+pub fn get_urls_with_tag(db: &PlacesDb, tag: &str) -> Result<Vec<Url>> {
+    let tag = validate_tag(tag).ensure_valid()?;
+
+    let mut stmt = db.prepare(
+        "SELECT p.url FROM moz_places p
+         JOIN moz_tags_relation r ON r.place_id = p.id
+         JOIN moz_tags t ON t.id = r.tag_id
+         WHERE t.tag = :tag
+         ORDER BY p.frecency",
+    )?;
+
+    let rows = stmt.query_and_then(&[(":tag", &tag)], |row| row.get::<_, String>("url"))?;
+    let mut urls = Vec::new();
+    for row in rows {
+        urls.push(Url::parse(&row?)?);
+    }
+    Ok(urls)
+}
+
+/// Retrieves a list of tags for the specified URL.
+///
+/// # Arguments
+///
+/// * `conn` - A database connection on which to operate.
+///
+/// * `url` - The URL to query.
+///
+/// # Returns
+///
+/// * A Vec<String> with all tags for the URL, sorted by the last modified
+///   date of the tag (latest to oldest)
+pub fn get_tags_for_url(db: &PlacesDb, url: &Url) -> Result<Vec<String>> {
+    let mut stmt = db.prepare(
+        "SELECT t.tag
+         FROM moz_tags t
+         JOIN moz_tags_relation r ON r.tag_id = t.id
+         JOIN moz_places h ON h.id = r.place_id
+         WHERE url_hash = hash(:url) AND url = :url
+         ORDER BY t.lastModified DESC",
+    )?;
+    let rows = stmt.query_and_then(&[(":url", &url.as_str())], |row| {
+        row.get::<_, String>("tag")
+    })?;
+    let mut tags = Vec::new();
+    for row in rows {
+        tags.push(row?);
+    }
+    Ok(tags)
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::api::places_api::test::new_mem_connection;
+    use crate::storage::new_page_info;
+
+    fn check_tags_for_url(db: &PlacesDb, url: &Url, mut expected: Vec<String>) {
+        let mut tags = get_tags_for_url(db, url).expect("should work");
+        tags.sort();
+        expected.sort();
+        assert_eq!(tags, expected);
+    }
+
+    fn check_urls_with_tag(db: &PlacesDb, tag: &str, mut expected: Vec<Url>) {
+        let mut with_tag = get_urls_with_tag(db, tag).expect("should work");
+        with_tag.sort();
+        expected.sort();
+        assert_eq!(with_tag, expected);
+    }
+
+    fn get_foreign_count(db: &PlacesDb, url: &Url) -> i32 {
+        let count: Result<Option<i32>> = db.try_query_row(
+            "SELECT foreign_count
+             FROM moz_places
+             WHERE url = :url",
+            &[(":url", &url.as_str())],
+            |row| Ok(row.get::<_, i32>(0)?),
+            false,
+        );
+        count.expect("should work").expect("should get a value")
+    }
+
+    #[test]
+    fn test_validate_tag() {
+        let v = validate_tag("foo");
+        assert_eq!(v, ValidatedTag::Original("foo"));
+        assert!(v.is_original());
+        assert_eq!(v.ensure_valid().expect("should work"), "foo");
+
+        let v = validate_tag(" foo ");
+        assert_eq!(v, ValidatedTag::Normalized("foo"));
+        assert!(!v.is_original());
+        assert_eq!(v.ensure_valid().expect("should work"), "foo");
+
+        let v = validate_tag("");
+        assert_eq!(v, ValidatedTag::Invalid(""));
+        assert!(!v.is_original());
+        assert!(v.ensure_valid().is_err());
+
+        assert_eq!(validate_tag("foo bar"), ValidatedTag::Original("foo bar"));
+        assert_eq!(
+            validate_tag(" foo bar "),
+            ValidatedTag::Normalized("foo bar")
+        );
+
+        assert!(validate_tag(&"f".repeat(101)).ensure_valid().is_err());
+    }
+
+    #[test]
+    fn test_tags() {
+        let conn = new_mem_connection();
+        let url1 = Url::parse("http://example.com").expect("valid url");
+        let url2 = Url::parse("http://example2.com").expect("valid url");
+
+        new_page_info(&conn, &url1, None).expect("should create the page");
+        new_page_info(&conn, &url2, None).expect("should create the page");
+        check_tags_for_url(&conn, &url1, vec![]);
+        check_tags_for_url(&conn, &url2, vec![]);
+        assert_eq!(get_foreign_count(&conn, &url1), 0);
+        assert_eq!(get_foreign_count(&conn, &url2), 0);
+
+        tag_url(&conn, &url1, "common").expect("should work");
+        assert_eq!(get_foreign_count(&conn, &url1), 1);
+        tag_url(&conn, &url1, "tag-1").expect("should work");
+        assert_eq!(get_foreign_count(&conn, &url1), 2);
+        tag_url(&conn, &url2, "common").expect("should work");
+        assert_eq!(get_foreign_count(&conn, &url2), 1);
+        tag_url(&conn, &url2, "tag-2").expect("should work");
+        assert_eq!(get_foreign_count(&conn, &url2), 2);
+
+        check_tags_for_url(
+            &conn,
+            &url1,
+            vec!["common".to_string(), "tag-1".to_string()],
+        );
+        check_tags_for_url(
+            &conn,
+            &url2,
+            vec!["common".to_string(), "tag-2".to_string()],
+        );
+
+        check_urls_with_tag(&conn, "common", vec![url1.clone(), url2.clone()]);
+        check_urls_with_tag(&conn, "tag-1", vec![url1.clone()]);
+        check_urls_with_tag(&conn, "tag-2", vec![url2.clone()]);
+
+        untag_url(&conn, &url1, "common").expect("should work");
+        assert_eq!(get_foreign_count(&conn, &url1), 1);
+
+        check_urls_with_tag(&conn, "common", vec![url2.clone()]);
+
+        remove_tag(&conn, "common").expect("should work");
+        check_urls_with_tag(&conn, "common", vec![]);
+        assert_eq!(get_foreign_count(&conn, &url2), 1);
+
+        remove_tag(&conn, "tag-1").expect("should work");
+        check_urls_with_tag(&conn, "tag-1", vec![]);
+        assert_eq!(get_foreign_count(&conn, &url1), 0);
+
+        remove_tag(&conn, "tag-2").expect("should work");
+        check_urls_with_tag(&conn, "tag-2", vec![]);
+        assert_eq!(get_foreign_count(&conn, &url2), 0);
+
+        // should be no tags rows left.
+        let count: Result<Option<u32>> = conn.try_query_row(
+            "SELECT COUNT(*) from moz_tags",
+            [],
+            |row| Ok(row.get::<_, u32>(0)?),
+            true,
+        );
+        assert_eq!(count.unwrap().unwrap(), 0);
+        let count: Result<Option<u32>> = conn.try_query_row(
+            "SELECT COUNT(*) from moz_tags_relation",
+            [],
+            |row| Ok(row.get::<_, u32>(0)?),
+            true,
+        );
+        assert_eq!(count.unwrap().unwrap(), 0);
+
+        // places should still exist.
+        fetch_page_info(&conn, &url1)
+            .expect("should work")
+            .expect("should exist");
+        fetch_page_info(&conn, &url2)
+            .expect("should work")
+            .expect("should exist");
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/places/types.rs.html b/book/rust-docs/src/places/types.rs.html new file mode 100644 index 0000000000..d1c5a5a9ff --- /dev/null +++ b/book/rust-docs/src/places/types.rs.html @@ -0,0 +1,517 @@ +types.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
+use rusqlite::Result as RusqliteResult;
+use serde::ser::{Serialize, Serializer};
+use std::fmt;
+
+mod visit_transition_set;
+pub use visit_transition_set::VisitTransitionSet;
+
+#[derive(thiserror::Error, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+#[error("Invalid visit type")]
+pub struct InvalidVisitType;
+
+// NOTE: These discriminator values are the same as those used by Desktop
+// Firefox and are what is written to the database. We also duplicate them
+// as a set of flags in visit_transition_set.rs
+#[repr(u8)]
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
+pub enum VisitType {
+    // This transition type means the user followed a link.
+    Link = 1,
+
+    // The user typed the page's URL in the
+    // URL bar or selected it from UI (URL bar autocomplete results, etc)
+    Typed = 2,
+
+    // The user followed a bookmark to get to the page.
+    Bookmark = 3,
+    /*
+     * This transition type is set when some inner content is loaded. This is
+     * true of all images on a page, and the contents of the iframe. It is also
+     * true of any content in a frame if the user did not explicitly follow
+     * a link to get there.
+     */
+    Embed = 4,
+
+    // Transition was a permanent redirect.
+    RedirectPermanent = 5,
+
+    // Transition was a temporary redirect.
+    RedirectTemporary = 6,
+
+    // Transition is a download.
+    Download = 7,
+
+    // The user followed a link and got a visit in a frame.
+    FramedLink = 8,
+
+    // The page has been reloaded.
+    Reload = 9,
+
+    // Internal visit type used for meta data updates. Doesn't represent an actual page visit
+    UpdatePlace = 10,
+}
+
+impl ToSql for VisitType {
+    fn to_sql(&self) -> RusqliteResult<ToSqlOutput<'_>> {
+        Ok(ToSqlOutput::from(*self as u8))
+    }
+}
+
+impl VisitType {
+    pub fn from_primitive(p: u8) -> Option<Self> {
+        match p {
+            1 => Some(VisitType::Link),
+            2 => Some(VisitType::Typed),
+            3 => Some(VisitType::Bookmark),
+            4 => Some(VisitType::Embed),
+            5 => Some(VisitType::RedirectPermanent),
+            6 => Some(VisitType::RedirectTemporary),
+            7 => Some(VisitType::Download),
+            8 => Some(VisitType::FramedLink),
+            9 => Some(VisitType::Reload),
+            10 => Some(VisitType::UpdatePlace),
+            _ => None,
+        }
+    }
+}
+
+impl TryFrom<u8> for VisitType {
+    type Error = InvalidVisitType;
+    fn try_from(p: u8) -> Result<Self, Self::Error> {
+        VisitType::from_primitive(p).ok_or(InvalidVisitType)
+    }
+}
+
+struct VisitTransitionSerdeVisitor;
+
+impl<'de> serde::de::Visitor<'de> for VisitTransitionSerdeVisitor {
+    type Value = VisitType;
+
+    fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+        formatter.write_str("positive integer representing VisitType")
+    }
+
+    fn visit_u64<E: serde::de::Error>(self, value: u64) -> Result<VisitType, E> {
+        use std::u8::MAX as U8_MAX;
+        if value > u64::from(U8_MAX) {
+            // In practice this is *way* out of the valid range of VisitType, but
+            // serde requires us to implement this as visit_u64 so...
+            return Err(E::custom(format!("value out of u8 range: {}", value)));
+        }
+        VisitType::from_primitive(value as u8)
+            .ok_or_else(|| E::custom(format!("unknown VisitType value: {}", value)))
+    }
+}
+
+impl serde::Serialize for VisitType {
+    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+        serializer.serialize_u64(*self as u64)
+    }
+}
+
+impl<'de> serde::Deserialize<'de> for VisitType {
+    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
+        deserializer.deserialize_u64(VisitTransitionSerdeVisitor)
+    }
+}
+
+/// Bookmark types.
+#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
+#[repr(u8)]
+pub enum BookmarkType {
+    Bookmark = 1, // TYPE_BOOKMARK
+    Folder = 2,   // TYPE_FOLDER
+    Separator = 3, // TYPE_SEPARATOR;
+                  // On desktop, TYPE_DYNAMIC_CONTAINER = 4 but is deprecated - so please
+                  // avoid using this value in the future.
+}
+
+impl FromSql for BookmarkType {
+    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
+        let v = value.as_i64()?;
+        if v < 0 || v > i64::from(u8::max_value()) {
+            return Err(FromSqlError::OutOfRange(v));
+        }
+        BookmarkType::from_u8(v as u8).ok_or(FromSqlError::OutOfRange(v))
+    }
+}
+
+impl BookmarkType {
+    #[inline]
+    pub fn from_u8(v: u8) -> Option<Self> {
+        match v {
+            1 => Some(BookmarkType::Bookmark),
+            2 => Some(BookmarkType::Folder),
+            3 => Some(BookmarkType::Separator),
+            _ => None,
+        }
+    }
+
+    pub fn from_u8_with_valid_url<F: Fn() -> bool>(v: u8, has_valid_url: F) -> Self {
+        match BookmarkType::from_u8(v) {
+            Some(BookmarkType::Bookmark) | None => {
+                if has_valid_url() {
+                    // Even if the node says it is a bookmark it still must have a
+                    // valid url.
+                    BookmarkType::Bookmark
+                } else {
+                    BookmarkType::Folder
+                }
+            }
+            Some(t) => t,
+        }
+    }
+}
+
+impl ToSql for BookmarkType {
+    fn to_sql(&self) -> RusqliteResult<ToSqlOutput<'_>> {
+        Ok(ToSqlOutput::from(*self as u8))
+    }
+}
+
+impl Serialize for BookmarkType {
+    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        serializer.serialize_u8(*self as u8)
+    }
+}
+
+/// Re SyncStatus - note that:
+/// * logins has synced=0, changed=1, new=2
+/// * desktop bookmarks has unknown=0, new=1, normal=2
+/// This is "places", so eventually bookmarks will have a status - should history
+/// and bookmarks share this enum?
+/// Note that history specifically needs neither (a) login's "changed" (the
+/// changeCounter works there), nor (b) bookmark's "unknown" (as that's only
+/// used after a restore).
+/// History only needs a distinction between "synced" and "new" so it doesn't
+/// accumulate never-to-be-synced tombstones - so we basically copy bookmarks
+/// and treat unknown as new.
+/// Which means we get the "bonus side-effect" ;) of ::Unknown replacing Option<>!
+///
+/// Note that some of these values are in schema.rs
+#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
+#[repr(u8)]
+pub enum SyncStatus {
+    Unknown = 0,
+    New = 1,
+    Normal = 2,
+}
+
+impl SyncStatus {
+    #[inline]
+    pub fn from_u8(v: u8) -> Self {
+        match v {
+            1 => SyncStatus::New,
+            2 => SyncStatus::Normal,
+            _ => SyncStatus::Unknown,
+        }
+    }
+}
+
+impl FromSql for SyncStatus {
+    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
+        let v = value.as_i64()?;
+        if v < 0 || v > i64::from(u8::max_value()) {
+            return Err(FromSqlError::OutOfRange(v));
+        }
+        Ok(SyncStatus::from_u8(v as u8))
+    }
+}
+
+impl ToSql for SyncStatus {
+    fn to_sql(&self) -> RusqliteResult<ToSqlOutput<'_>> {
+        Ok(ToSqlOutput::from(*self as u8))
+    }
+}
+
+// This type is used as a snazzy way to capture all unknown fields from the payload
+// upon deserialization without having to work with a concrete type
+pub type UnknownFields = serde_json::Map<String, serde_json::Value>;
+
+pub(crate) fn serialize_unknown_fields(
+    unknown_fields: &UnknownFields,
+) -> crate::Result<Option<String>> {
+    if unknown_fields.is_empty() {
+        Ok(None)
+    } else {
+        Ok(Some(serde_json::to_string(unknown_fields)?))
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_primitive() {
+        assert_eq!(Some(VisitType::Link), VisitType::from_primitive(1));
+        assert_eq!(None, VisitType::from_primitive(99));
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/places/types/visit_transition_set.rs.html b/book/rust-docs/src/places/types/visit_transition_set.rs.html new file mode 100644 index 0000000000..14fda5b453 --- /dev/null +++ b/book/rust-docs/src/places/types/visit_transition_set.rs.html @@ -0,0 +1,527 @@ +visit_transition_set.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use super::{InvalidVisitType, VisitType};
+use rusqlite::types::ToSqlOutput;
+
+#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
+pub struct VisitTransitionSet {
+    pub(crate) bits: u16,
+}
+
+const ALL_BITS_SET: u16 = (1u16 << (VisitType::Link as u8))
+    | (1u16 << (VisitType::Typed as u8))
+    | (1u16 << (VisitType::Bookmark as u8))
+    | (1u16 << (VisitType::Embed as u8))
+    | (1u16 << (VisitType::RedirectPermanent as u8))
+    | (1u16 << (VisitType::RedirectTemporary as u8))
+    | (1u16 << (VisitType::Download as u8))
+    | (1u16 << (VisitType::FramedLink as u8))
+    | (1u16 << (VisitType::Reload as u8));
+
+impl VisitTransitionSet {
+    pub const fn new() -> Self {
+        Self { bits: 0 }
+    }
+
+    pub const fn empty() -> Self {
+        Self::new()
+    }
+
+    pub const fn all() -> Self {
+        Self { bits: ALL_BITS_SET }
+    }
+
+    pub const fn single(ty: VisitType) -> Self {
+        Self {
+            bits: (1u16 << (ty as u8)),
+        }
+    }
+
+    pub fn for_specific(tys: &[VisitType]) -> Self {
+        tys.iter().cloned().collect()
+    }
+
+    pub fn into_u16(self) -> u16 {
+        self.bits
+    }
+
+    pub fn from_u16(v: u16) -> Result<VisitTransitionSet, InvalidVisitType> {
+        v.try_into()
+    }
+
+    pub fn contains(self, t: VisitType) -> bool {
+        (self.bits & (1 << (t as u32))) != 0
+    }
+
+    pub fn insert(&mut self, t: VisitType) {
+        self.bits |= 1 << (t as u8);
+    }
+
+    pub fn remove(&mut self, t: VisitType) {
+        self.bits &= !(1 << (t as u8));
+    }
+
+    pub fn complement(self) -> VisitTransitionSet {
+        Self {
+            bits: (!self.bits) & ALL_BITS_SET,
+        }
+    }
+
+    pub fn len(self) -> usize {
+        self.bits.count_ones() as usize
+    }
+
+    pub fn is_empty(self) -> bool {
+        self.bits == 0
+    }
+}
+
+impl TryFrom<u16> for VisitTransitionSet {
+    type Error = InvalidVisitType;
+    fn try_from(bits: u16) -> Result<Self, InvalidVisitType> {
+        if bits != (bits & ALL_BITS_SET) {
+            Err(InvalidVisitType)
+        } else {
+            Ok(Self { bits })
+        }
+    }
+}
+
+impl IntoIterator for VisitTransitionSet {
+    type Item = VisitType;
+    type IntoIter = VisitTransitionSetIter;
+    fn into_iter(self) -> VisitTransitionSetIter {
+        VisitTransitionSetIter {
+            bits: self.bits,
+            pos: 0,
+        }
+    }
+}
+
+pub struct VisitTransitionSetIter {
+    bits: u16,
+    pos: u8,
+}
+
+impl Iterator for VisitTransitionSetIter {
+    type Item = VisitType;
+    fn next(&mut self) -> Option<Self::Item> {
+        if self.bits == 0 {
+            return None;
+        }
+        while (self.bits & 1) == 0 {
+            self.pos += 1;
+            self.bits >>= 1;
+        }
+        // Should always be fine unless VisitTransitionSet has a bug.
+        let result: VisitType = self.pos.try_into().expect("Bug in VisitTransitionSet");
+        self.pos += 1;
+        self.bits >>= 1;
+        Some(result)
+    }
+
+    fn size_hint(&self) -> (usize, Option<usize>) {
+        let value = self.bits.count_ones() as usize;
+        (value, Some(value))
+    }
+}
+
+impl From<VisitTransitionSet> for u16 {
+    fn from(vts: VisitTransitionSet) -> Self {
+        vts.bits
+    }
+}
+
+impl Extend<VisitType> for VisitTransitionSet {
+    fn extend<I>(&mut self, iter: I)
+    where
+        I: IntoIterator<Item = VisitType>,
+    {
+        for element in iter {
+            self.insert(element);
+        }
+    }
+}
+
+impl std::iter::FromIterator<VisitType> for VisitTransitionSet {
+    fn from_iter<I>(iterator: I) -> Self
+    where
+        I: IntoIterator<Item = VisitType>,
+    {
+        let mut ret = Self::new();
+        ret.extend(iterator);
+        ret
+    }
+}
+
+impl rusqlite::ToSql for VisitTransitionSet {
+    fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
+        Ok(ToSqlOutput::from(u16::from(*self)))
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    const ALL_TRANSITIONS: &[VisitType] = &[
+        VisitType::Link,
+        VisitType::Typed,
+        VisitType::Bookmark,
+        VisitType::Embed,
+        VisitType::RedirectPermanent,
+        VisitType::RedirectTemporary,
+        VisitType::Download,
+        VisitType::FramedLink,
+        VisitType::Reload,
+    ];
+    #[test]
+    fn test_vtset() {
+        let mut vts = VisitTransitionSet::empty();
+        let vts_all = VisitTransitionSet::all();
+        assert_eq!(vts_all.len(), ALL_TRANSITIONS.len());
+        assert_eq!(vts.len(), 0);
+        for &ty in ALL_TRANSITIONS {
+            assert!(vts_all.contains(ty));
+            vts.insert(ty);
+            assert_eq!(vts.into_u16().try_into(), Ok(vts));
+        }
+        assert_eq!(vts_all, vts);
+
+        let to_remove = &[
+            VisitType::Typed,
+            VisitType::RedirectPermanent,
+            VisitType::RedirectTemporary,
+        ];
+        for &r in to_remove {
+            assert!(vts.contains(r));
+            vts.remove(r);
+            assert!(!vts.contains(r));
+        }
+        for &ty in ALL_TRANSITIONS {
+            if to_remove.contains(&ty) {
+                assert!(!vts.contains(ty));
+                assert!(vts.complement().contains(ty));
+            } else {
+                assert!(vts.contains(ty));
+                assert!(!vts.complement().contains(ty));
+            }
+        }
+    }
+
+    #[test]
+    fn test_vtset_iter() {
+        let mut vts = VisitTransitionSet::all();
+        assert_eq!(&vts.into_iter().collect::<Vec<_>>()[..], ALL_TRANSITIONS);
+
+        let to_remove = &[
+            VisitType::Typed,
+            VisitType::RedirectPermanent,
+            VisitType::RedirectTemporary,
+        ];
+
+        for &r in to_remove {
+            vts.remove(r);
+        }
+
+        let want = &[
+            VisitType::Link,
+            VisitType::Bookmark,
+            VisitType::Embed,
+            VisitType::Download,
+            VisitType::FramedLink,
+            VisitType::Reload,
+        ];
+        assert_eq!(&vts.into_iter().collect::<Vec<_>>()[..], want);
+
+        assert_eq!(
+            &vts.complement().into_iter().collect::<Vec<_>>()[..],
+            to_remove
+        );
+    }
+
+    #[test]
+    fn test_vtset_try_from() {
+        assert!(VisitTransitionSet::try_from(1).is_err());
+
+        assert_eq!(
+            VisitTransitionSet::try_from(2),
+            Ok(VisitTransitionSet::single(VisitType::Link)),
+        );
+
+        assert!(VisitTransitionSet::try_from(ALL_BITS_SET + 1).is_err(),);
+
+        assert!(VisitTransitionSet::try_from(ALL_BITS_SET + 2).is_err(),);
+
+        assert_eq!(
+            VisitTransitionSet::try_from(ALL_BITS_SET),
+            Ok(VisitTransitionSet::all()),
+        );
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/places/util.rs.html b/book/rust-docs/src/places/util.rs.html new file mode 100644 index 0000000000..6a1182f378 --- /dev/null +++ b/book/rust-docs/src/places/util.rs.html @@ -0,0 +1,267 @@ +util.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::error::{Error, Result};
+use std::path::{Path, PathBuf};
+use url::Url;
+
+/// Equivalent to `&s[..max_len.min(s.len())]`, but handles the case where
+/// `s.is_char_boundary(max_len)` is false (which would otherwise panic).
+pub fn slice_up_to(s: &str, max_len: usize) -> &str {
+    if max_len >= s.len() {
+        return s;
+    }
+    let mut idx = max_len;
+    while !s.is_char_boundary(idx) {
+        idx -= 1;
+    }
+    &s[..idx]
+}
+
+/// `Path` is basically just a `str` with no validation, and so in practice it
+/// could contain a file URL. Rusqlite takes advantage of this a bit, and says
+/// `AsRef<Path>` but really means "anything sqlite can take as an argument".
+///
+/// Swift loves using file urls (the only support it has for file manipulation
+/// is through file urls), so it's handy to support them if possible.
+fn unurl_path(p: impl AsRef<Path>) -> PathBuf {
+    p.as_ref()
+        .to_str()
+        .and_then(|s| Url::parse(s).ok())
+        .and_then(|u| {
+            if u.scheme() == "file" {
+                u.to_file_path().ok()
+            } else {
+                None
+            }
+        })
+        .unwrap_or_else(|| p.as_ref().to_owned())
+}
+
+/// If `p` is a file URL, return it, otherwise try and make it one.
+///
+/// Errors if `p` is a relative non-url path, or if it's a URL path
+/// that's isn't a `file:` URL.
+pub fn ensure_url_path(p: impl AsRef<Path>) -> Result<Url> {
+    if let Some(u) = p.as_ref().to_str().and_then(|s| Url::parse(s).ok()) {
+        if u.scheme() == "file" {
+            Ok(u)
+        } else {
+            Err(Error::IllegalDatabasePath(p.as_ref().to_owned()))
+        }
+    } else {
+        let p = p.as_ref();
+        let u = Url::from_file_path(p).map_err(|_| Error::IllegalDatabasePath(p.to_owned()))?;
+        Ok(u)
+    }
+}
+
+/// As best as possible, convert `p` into an absolute path, resolving
+/// all symlinks along the way.
+///
+/// If `p` is a file url, it's converted to a path before this.
+pub fn normalize_path(p: impl AsRef<Path>) -> Result<PathBuf> {
+    let path = unurl_path(p);
+    if let Ok(canonical) = path.canonicalize() {
+        return Ok(canonical);
+    }
+    // It probably doesn't exist yet. This is an error, although it seems to
+    // work on some systems.
+    //
+    // We resolve this by trying to canonicalize the parent directory, and
+    // appending the requested file name onto that. If we can't canonicalize
+    // the parent, we return an error.
+    //
+    // Also, we return errors if the path ends in "..", if there is no
+    // parent directory, etc.
+    let file_name = path
+        .file_name()
+        .ok_or_else(|| Error::IllegalDatabasePath(path.clone()))?;
+
+    let parent = path
+        .parent()
+        .ok_or_else(|| Error::IllegalDatabasePath(path.clone()))?;
+
+    let mut canonical = parent.canonicalize()?;
+    canonical.push(file_name);
+    Ok(canonical)
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+    #[test]
+    fn test_slice_up_to() {
+        assert_eq!(slice_up_to("abcde", 4), "abcd");
+        assert_eq!(slice_up_to("abcde", 5), "abcde");
+        assert_eq!(slice_up_to("abcde", 6), "abcde");
+        let s = "abcd😀";
+        assert_eq!(s.len(), 8);
+        assert_eq!(slice_up_to(s, 4), "abcd");
+        assert_eq!(slice_up_to(s, 5), "abcd");
+        assert_eq!(slice_up_to(s, 6), "abcd");
+        assert_eq!(slice_up_to(s, 7), "abcd");
+        assert_eq!(slice_up_to(s, 8), s);
+    }
+    #[test]
+    fn test_unurl_path() {
+        assert_eq!(
+            unurl_path("file:///foo%20bar/baz").to_string_lossy(),
+            "/foo bar/baz"
+        );
+        assert_eq!(unurl_path("/foo bar/baz").to_string_lossy(), "/foo bar/baz");
+        assert_eq!(unurl_path("../baz").to_string_lossy(), "../baz");
+    }
+
+    #[test]
+    fn test_ensure_url() {
+        assert_eq!(
+            ensure_url_path("file:///foo%20bar/baz").unwrap().as_str(),
+            "file:///foo%20bar/baz"
+        );
+
+        assert_eq!(
+            ensure_url_path("/foo bar/baz").unwrap().as_str(),
+            "file:///foo%20bar/baz"
+        );
+
+        assert!(ensure_url_path("bar").is_err());
+
+        assert!(ensure_url_path("http://www.not-a-file.com").is_err());
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/protobuf_gen/main.rs.html b/book/rust-docs/src/protobuf_gen/main.rs.html new file mode 100644 index 0000000000..dfd091a637 --- /dev/null +++ b/book/rust-docs/src/protobuf_gen/main.rs.html @@ -0,0 +1,99 @@ +main.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use clap::{App, Arg};
+use serde_derive::Deserialize;
+use std::{collections::HashMap, fs, path::PathBuf};
+
+#[derive(Deserialize, Debug)]
+struct ProtobufOpts {
+    dir: String,
+    out_dir: Option<String>,
+}
+
+fn main() {
+    let matches = App::new("Protobuf files generator")
+        .about("Generates Rust structs from protobuf files in the repository.")
+        .arg(
+            Arg::with_name("PROTOBUF_CONFIG")
+                .help("Absolute path to the protobuf configuration file.")
+                .required(true),
+        )
+        .get_matches();
+    let config_path = matches.value_of("PROTOBUF_CONFIG").unwrap();
+    let config_path = PathBuf::from(config_path);
+    let files_config = fs::read_to_string(&config_path).expect("unable to read protobuf_files");
+    let files: HashMap<String, ProtobufOpts> = toml::from_str(&files_config).unwrap();
+    let config_dir = config_path.parent().unwrap();
+
+    for (proto_file, opts) in files {
+        // Can't re-use Config because the out_dir is always different.
+        let mut config = prost_build::Config::new();
+        let out_dir = opts.out_dir.clone().unwrap_or_else(|| opts.dir.clone());
+        let out_dir_absolute = config_dir.join(out_dir).canonicalize().unwrap();
+        let out_dir_absolute = out_dir_absolute.to_str().unwrap();
+        let proto_path_absolute = config_dir
+            .join(&opts.dir)
+            .join(proto_file)
+            .canonicalize()
+            .unwrap();
+        let proto_path_absolute = proto_path_absolute.to_str().unwrap();
+        let include_dir_absolute = config_dir.join(&opts.dir).canonicalize().unwrap();
+        let include_dir_absolute = include_dir_absolute.to_str().unwrap();
+        config.out_dir(out_dir_absolute);
+        config
+            .compile_protos(&[proto_path_absolute], &[&include_dir_absolute])
+            .unwrap();
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/push/error.rs.html b/book/rust-docs/src/push/error.rs.html new file mode 100644 index 0000000000..c4c3d2a642 --- /dev/null +++ b/book/rust-docs/src/push/error.rs.html @@ -0,0 +1,233 @@ +error.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use error_support::{ErrorHandling, GetErrorHandling};
+
+pub type Result<T, E = PushError> = std::result::Result<T, E>;
+
+pub type ApiResult<T, E = PushApiError> = std::result::Result<T, E>;
+
+#[derive(Debug, thiserror::Error)]
+pub enum PushApiError {
+    /// The UAID was not recognized by the server
+    #[error("Unrecognized UAID: {0}")]
+    UAIDNotRecognizedError(String),
+
+    /// Record not found for the given chid
+    #[error("No record for chid {0}")]
+    RecordNotFoundError(String),
+
+    /// Internal Error
+    #[error("Internal Error: {0}")]
+    InternalError(String),
+}
+
+#[derive(Debug, thiserror::Error)]
+pub enum PushError {
+    /// An unspecified general error has occured
+    #[error("General Error: {0:?}")]
+    GeneralError(String),
+
+    #[error("Crypto error: {0}")]
+    CryptoError(String),
+
+    /// A Client communication error
+    #[error("Communication Error: {0:?}")]
+    CommunicationError(String),
+
+    /// An error returned from the registration Server
+    #[error("Communication Server Error: {0}")]
+    CommunicationServerError(String),
+
+    /// Channel is already registered, generate new channelID
+    #[error("Channel already registered.")]
+    AlreadyRegisteredError,
+
+    /// An error with Storage
+    #[error("Storage Error: {0:?}")]
+    StorageError(String),
+
+    #[error("No record for chid {0:?}")]
+    RecordNotFoundError(String),
+
+    /// A failure to encode data to/from storage.
+    #[error("Error executing SQL: {0}")]
+    StorageSqlError(#[from] rusqlite::Error),
+
+    #[error("Transcoding Error: {0}")]
+    TranscodingError(String),
+
+    /// A failure to parse a URL.
+    #[error("URL parse error: {0:?}")]
+    UrlParseError(#[from] url::ParseError),
+
+    /// A failure deserializing json.
+    #[error("Failed to parse json: {0}")]
+    JSONDeserializeError(#[from] serde_json::Error),
+
+    /// The UAID was not recognized by the server
+    #[error("Unrecognized UAID: {0}")]
+    UAIDNotRecognizedError(String),
+
+    /// Was unable to send request to server
+    #[error("Unable to send request to server: {0}")]
+    RequestError(#[from] viaduct::Error),
+
+    /// Was unable to open the database
+    #[error("Error opening database: {0}")]
+    OpenDatabaseError(#[from] sql_support::open_database::Error),
+}
+
+impl From<bincode::Error> for PushError {
+    fn from(value: bincode::Error) -> Self {
+        PushError::TranscodingError(format!("bincode error: {value}"))
+    }
+}
+
+impl From<base64::DecodeError> for PushError {
+    fn from(value: base64::DecodeError) -> Self {
+        PushError::TranscodingError(format!("base64 error: {value}"))
+    }
+}
+
+impl From<rc_crypto::ece::Error> for PushError {
+    fn from(value: rc_crypto::ece::Error) -> Self {
+        PushError::CryptoError(value.to_string())
+    }
+}
+
+impl GetErrorHandling for PushError {
+    type ExternalError = PushApiError;
+
+    fn get_error_handling(&self) -> error_support::ErrorHandling<Self::ExternalError> {
+        match self {
+            Self::UAIDNotRecognizedError(s) => {
+                ErrorHandling::convert(PushApiError::UAIDNotRecognizedError(s.clone()))
+                    .report_error("uaid-not-recognized")
+            }
+            Self::RecordNotFoundError(s) => {
+                ErrorHandling::convert(PushApiError::RecordNotFoundError(s.clone()))
+            }
+
+            _ => ErrorHandling::convert(PushApiError::InternalError(self.to_string())),
+        }
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/push/internal/communications.rs.html b/book/rust-docs/src/push/internal/communications.rs.html new file mode 100644 index 0000000000..52d5b01726 --- /dev/null +++ b/book/rust-docs/src/push/internal/communications.rs.html @@ -0,0 +1,1153 @@ +communications.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
+540
+541
+542
+543
+544
+545
+546
+547
+548
+549
+550
+551
+552
+553
+554
+555
+556
+557
+558
+559
+560
+561
+562
+563
+564
+565
+566
+567
+568
+569
+570
+571
+572
+573
+574
+575
+576
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Server Communications.
+//!
+//! Handles however communication to and from the remote Push Server should be done. For Desktop
+//! this will be over Websocket. For mobile, it calls into the local operating
+//! system and HTTPS to the web push server.
+//!
+//! Mainly exposes a trait [`Connection`] and a concrete type that implements it [`ConnectHttp`]
+//!
+//! The trait is a lightweight interface that talks to autopush servers and provides the following functionality
+//! - Subscription: Through [`Connection::subscribe_new`] on first subscription, and [`Connection::subscribe_with_uaid`] on subsequent subscriptiosn
+//! - Unsubscription: Through [`Connection::unsubscribe`] for a single channel, and [`Connection::unsubscribe_all`] for all channels
+//! - Updating tokens: Through [`Connection::update`] to update a native token
+//! - Getting all subscription channels: Through [`Connection::channel_list`]
+
+use serde::{Deserialize, Serialize};
+use url::Url;
+use viaduct::{header_names, status_codes, Headers, Request};
+
+use crate::error::{
+    self,
+    PushError::{
+        AlreadyRegisteredError, CommunicationError, CommunicationServerError,
+        UAIDNotRecognizedError,
+    },
+};
+use crate::internal::config::PushConfiguration;
+use crate::internal::storage::Store;
+
+mod rate_limiter;
+pub use rate_limiter::PersistedRateLimiter;
+
+const UAID_NOT_FOUND_ERRNO: u32 = 103;
+#[derive(Deserialize, Debug)]
+/// The response from the `/registration` endpoint
+pub struct RegisterResponse {
+    /// The UAID assigned by autopush
+    pub uaid: String,
+
+    /// The Channel ID associated with the request
+    /// The server might assign a new one if "" is sent
+    /// with the request. Consumers should treat this channel_id
+    /// as the tru channel id.
+    #[serde(rename = "channelID")]
+    pub channel_id: String,
+
+    /// Auth token for subsequent calls (note, only generated on new UAIDs)
+    pub secret: String,
+
+    /// Push endpoint for 3rd parties
+    pub endpoint: String,
+
+    /// The sender id
+    #[serde(rename = "senderid")]
+    pub sender_id: Option<String>,
+}
+
+#[derive(Deserialize, Debug)]
+/// The response from the `/subscribe` endpoint
+pub struct SubscribeResponse {
+    /// The Channel ID associated with the request
+    /// The server sends it back with the response.
+    /// The server might assign a new one if "" is sent
+    /// with the request. Consumers should treat this channel_id
+    /// as the tru channel id
+    #[serde(rename = "channelID")]
+    pub channel_id: String,
+
+    /// Push endpoint for 3rd parties
+    pub endpoint: String,
+
+    /// The sender id
+    #[serde(rename = "senderid")]
+    pub sender_id: Option<String>,
+}
+
+#[derive(Serialize)]
+/// The request body for the `/registration` endpoint
+struct RegisterRequest<'a> {
+    /// The native registration id, a token provided by the app
+    token: &'a str,
+
+    /// An optional app server key
+    key: Option<&'a str>,
+}
+
+#[derive(Serialize)]
+struct UpdateRequest<'a> {
+    token: &'a str,
+}
+
+/// A new communication link to the Autopush server
+#[cfg_attr(test, mockall::automock)]
+pub trait Connection: Sized {
+    /// Create a new instance of a [`Connection`]
+    fn connect(options: PushConfiguration) -> Self;
+
+    /// Sends this client's very first subscription request. Note that the `uaid` is not available at this stage
+    /// the server will assign and return a uaid. Subsequent subscriptions will call [`Connection::subscribe_with_uaid`]
+    ///
+    /// # Arguments
+    /// - `registration_id`: A string representing a native token. In practice, this is a Firebase token for Android and a APNS token for iOS
+    /// - `app_server_key`: Optional VAPID public key to "lock" subscriptions
+    ///
+    /// # Returns
+    /// - Returns a [`RegisterResponse`] which is the autopush server's registration response deserialized
+    fn register(
+        &self,
+        registration_id: &str,
+        app_server_key: &Option<String>,
+    ) -> error::Result<RegisterResponse>;
+
+    /// Sends subsequent subscriptions for this client. This will be called when the client has already been assigned a `uaid`
+    /// by the server when it first called [`Connection::subscribe_new`]
+    /// # Arguments
+    /// - `uaid`: A string representing the users `uaid` that was assigned when the user first registered for a subscription
+    /// - `auth`: A string representing an authorization token that will be sent as a header to autopush. The auth was returned on the user's first subscription.
+    /// - `registration_id`: A string representing a native token. In practice, this is a Firebase token for Android and a APNS token for iOS
+    /// - `app_server_key`: Optional VAPID public key to "lock" subscriptions
+    ///
+    /// # Returns
+    /// - Returns a [`RegisterResponse`] which is the autopush server's registration response deserialized
+    fn subscribe(
+        &self,
+        uaid: &str,
+        auth: &str,
+        registration_id: &str,
+        app_server_key: &Option<String>,
+    ) -> error::Result<SubscribeResponse>;
+
+    /// Drop a subscription previously registered with autopush
+    /// # Arguments
+    /// - `channel_id`: A string defined by client. The client is expected to provide this id when requesting the subscription record
+    /// - `uaid`: A string representing the users `uaid` that was assigned when the user first registered for a subscription
+    /// - `auth`: A string representing an authorization token that will be sent as a header to autopush. The auth was returned on the user's first subscription.
+    fn unsubscribe(&self, channel_id: &str, uaid: &str, auth: &str) -> error::Result<()>;
+
+    /// Drop all subscriptions previously registered with autopush
+    /// # Arguments
+    /// - `channel_id`: A string defined by client. The client is expected to provide this id when requesting the subscription record
+    /// - `uaid`: A string representing the users `uaid` that was assigned when the user first registered for a subscription
+    /// - `auth`: A string representing an authorization token that will be sent as a header to autopush. The auth was returned on the user's first subscription.
+    fn unsubscribe_all(&self, uaid: &str, auth: &str) -> error::Result<()>;
+
+    /// Update the autopush server with the new native OS Messaging authorization token
+    /// # Arguments
+    /// - `new_token`: A string representing a new natvie token for the user. This would be an FCM token for Android, and an APNS token for iOS
+    /// - `uaid`: A string representing the users `uaid` that was assigned when the user first registered for a subscription
+    /// - `auth`: A string representing an authorization token that will be sent as a header to autopush. The auth was returned on the user's first subscription.
+    fn update(&self, new_token: &str, uaid: &str, auth: &str) -> error::Result<()>;
+
+    /// Get a list of server known channels.
+    /// # Arguments
+    /// - `uaid`: A string representing the users `uaid` that was assigned when the user first registered for a subscription
+    /// - `auth`: A string representing an authorization token that will be sent as a header to autopush. The auth was returned on the user's first subscription.
+    ///
+    /// # Returns
+    /// A list of channel ids representing all the channels the user is subscribed to
+    fn channel_list(&self, uaid: &str, auth: &str) -> error::Result<Vec<String>>;
+}
+
+/// Connect to the Autopush server via the HTTP interface
+pub struct ConnectHttp {
+    options: PushConfiguration,
+}
+
+impl ConnectHttp {
+    fn auth_headers(&self, auth: &str) -> error::Result<Headers> {
+        let mut headers = Headers::new();
+        headers
+            .insert(header_names::AUTHORIZATION, &*format!("webpush {}", auth))
+            .map_err(|e| error::PushError::CommunicationError(format!("Header error: {:?}", e)))?;
+
+        Ok(headers)
+    }
+
+    fn check_response_error(&self, response: &viaduct::Response) -> error::Result<()> {
+        // An error response, the extended object structure is retrieved from
+        // https://autopush.readthedocs.io/en/latest/http.html#response
+        #[derive(Deserialize)]
+        struct ResponseError {
+            pub errno: Option<u32>,
+            pub message: String,
+        }
+        if response.is_server_error() {
+            let response_error = response.json::<ResponseError>()?;
+            return Err(CommunicationServerError(format!(
+                "General Server Error: {}",
+                response_error.message
+            )));
+        }
+        if response.is_client_error() {
+            let response_error = response.json::<ResponseError>()?;
+            if response.status == status_codes::CONFLICT {
+                return Err(AlreadyRegisteredError);
+            }
+            if response.status == status_codes::GONE
+                && matches!(response_error.errno, Some(UAID_NOT_FOUND_ERRNO))
+            {
+                return Err(UAIDNotRecognizedError(response_error.message));
+            }
+            return Err(CommunicationError(format!(
+                "Unhandled client error {:?}",
+                response
+            )));
+        }
+        Ok(())
+    }
+
+    fn format_unsubscribe_url(&self, uaid: &str) -> error::Result<String> {
+        Ok(format!(
+            "{}://{}/v1/{}/{}/registration/{}",
+            &self.options.http_protocol,
+            &self.options.server_host,
+            &self.options.bridge_type,
+            &self.options.sender_id,
+            &uaid,
+        ))
+    }
+
+    fn send_subscription_request<T>(
+        &self,
+        url: Url,
+        headers: Headers,
+        registration_id: &str,
+        app_server_key: &Option<String>,
+    ) -> error::Result<T>
+    where
+        T: for<'a> Deserialize<'a>,
+    {
+        let body = RegisterRequest {
+            token: registration_id,
+            key: app_server_key.as_ref().map(|s| s.as_str()),
+        };
+
+        let response = Request::post(url).headers(headers).json(&body).send()?;
+        self.check_response_error(&response)?;
+        Ok(response.json()?)
+    }
+}
+
+impl Connection for ConnectHttp {
+    fn connect(options: PushConfiguration) -> ConnectHttp {
+        ConnectHttp { options }
+    }
+
+    fn register(
+        &self,
+        registration_id: &str,
+        app_server_key: &Option<String>,
+    ) -> error::Result<RegisterResponse> {
+        let url = format!(
+            "{}://{}/v1/{}/{}/registration",
+            &self.options.http_protocol,
+            &self.options.server_host,
+            &self.options.bridge_type,
+            &self.options.sender_id
+        );
+
+        let headers = Headers::new();
+
+        self.send_subscription_request(Url::parse(&url)?, headers, registration_id, app_server_key)
+    }
+
+    fn subscribe(
+        &self,
+        uaid: &str,
+        auth: &str,
+        registration_id: &str,
+        app_server_key: &Option<String>,
+    ) -> error::Result<SubscribeResponse> {
+        let url = format!(
+            "{}://{}/v1/{}/{}/registration/{}/subscription",
+            &self.options.http_protocol,
+            &self.options.server_host,
+            &self.options.bridge_type,
+            &self.options.sender_id,
+            uaid,
+        );
+
+        let headers = self.auth_headers(auth)?;
+
+        self.send_subscription_request(Url::parse(&url)?, headers, registration_id, app_server_key)
+    }
+
+    fn unsubscribe(&self, channel_id: &str, uaid: &str, auth: &str) -> error::Result<()> {
+        let url = format!(
+            "{}/subscription/{}",
+            self.format_unsubscribe_url(uaid)?,
+            channel_id
+        );
+        let response = Request::delete(Url::parse(&url)?)
+            .headers(self.auth_headers(auth)?)
+            .send()?;
+        log::info!("unsubscribed from {}: {}", url, response.status);
+        self.check_response_error(&response)?;
+        Ok(())
+    }
+
+    fn unsubscribe_all(&self, uaid: &str, auth: &str) -> error::Result<()> {
+        let url = self.format_unsubscribe_url(uaid)?;
+        let response = Request::delete(Url::parse(&url)?)
+            .headers(self.auth_headers(auth)?)
+            .send()?;
+        log::info!("unsubscribed from all via {}: {}", url, response.status);
+        self.check_response_error(&response)?;
+        Ok(())
+    }
+
+    fn update(&self, new_token: &str, uaid: &str, auth: &str) -> error::Result<()> {
+        let options = self.options.clone();
+        let url = format!(
+            "{}://{}/v1/{}/{}/registration/{}",
+            &options.http_protocol,
+            &options.server_host,
+            &options.bridge_type,
+            &options.sender_id,
+            uaid
+        );
+        let body = UpdateRequest { token: new_token };
+        let response = Request::put(Url::parse(&url)?)
+            .json(&body)
+            .headers(self.auth_headers(auth)?)
+            .send()?;
+        log::info!("update via {}: {}", url, response.status);
+        self.check_response_error(&response)?;
+        Ok(())
+    }
+
+    fn channel_list(&self, uaid: &str, auth: &str) -> error::Result<Vec<String>> {
+        #[derive(Deserialize, Debug)]
+        struct Payload {
+            uaid: String,
+            #[serde(rename = "channelIDs")]
+            channel_ids: Vec<String>,
+        }
+
+        let options = self.options.clone();
+
+        let url = format!(
+            "{}://{}/v1/{}/{}/registration/{}",
+            &options.http_protocol,
+            &options.server_host,
+            &options.bridge_type,
+            &options.sender_id,
+            &uaid,
+        );
+        let response = match Request::get(Url::parse(&url)?)
+            .headers(self.auth_headers(auth)?)
+            .send()
+        {
+            Ok(v) => v,
+            Err(e) => {
+                return Err(CommunicationServerError(format!(
+                    "Could not fetch channel list: {}",
+                    e
+                )));
+            }
+        };
+        self.check_response_error(&response)?;
+        let payload: Payload = response.json()?;
+        if payload.uaid != uaid {
+            return Err(CommunicationServerError(
+                "Invalid Response from server".to_string(),
+            ));
+        }
+        Ok(payload
+            .channel_ids
+            .iter()
+            .map(|s| Store::normalize_uuid(s))
+            .collect())
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use crate::internal::config::Protocol;
+
+    use super::*;
+
+    use super::Connection;
+
+    use mockito::{mock, server_address};
+    use serde_json::json;
+
+    const DUMMY_CHID: &str = "deadbeef00000000decafbad00000000";
+    const DUMMY_CHID2: &str = "decafbad00000000deadbeef00000000";
+
+    const DUMMY_UAID: &str = "abad1dea00000000aabbccdd00000000";
+
+    // Local test SENDER_ID ("test*" reserved for Kotlin testing.)
+    const SENDER_ID: &str = "FakeSenderID";
+    const SECRET: &str = "SuP3rS1kRet";
+
+    #[test]
+    fn test_communications() {
+        viaduct_reqwest::use_reqwest_backend();
+        // mockito forces task serialization, so for now, we test everything in one go.
+        let config = PushConfiguration {
+            http_protocol: Protocol::Http,
+            server_host: server_address().to_string(),
+            sender_id: SENDER_ID.to_owned(),
+            ..Default::default()
+        };
+        // SUBSCRIPTION with secret
+        {
+            let body = json!({
+                "uaid": DUMMY_UAID,
+                "channelID": DUMMY_CHID,
+                "endpoint": "https://example.com/update",
+                "senderid": SENDER_ID,
+                "secret": SECRET,
+            })
+            .to_string();
+            let ap_mock = mock("POST", &*format!("/v1/fcm/{}/registration", SENDER_ID))
+                .with_status(200)
+                .with_header("content-type", "application/json")
+                .with_body(body)
+                .create();
+            let conn = ConnectHttp::connect(config.clone());
+            let response = conn.register(SENDER_ID, &None).unwrap();
+            ap_mock.assert();
+            assert_eq!(response.uaid, DUMMY_UAID);
+        }
+        // Second subscription, after first is send with uaid
+        {
+            let body = json!({
+                "uaid": DUMMY_UAID,
+                "channelID": DUMMY_CHID,
+                "endpoint": "https://example.com/update",
+                "senderid": SENDER_ID,
+                "secret": SECRET,
+            })
+            .to_string();
+            let ap_mock = mock("POST", &*format!("/v1/fcm/{}/registration", SENDER_ID))
+                .with_status(200)
+                .with_header("content-type", "application/json")
+                .with_body(body)
+                .create();
+            let conn = ConnectHttp::connect(config.clone());
+            let response = conn.register(SENDER_ID, &None).unwrap();
+            ap_mock.assert();
+            assert_eq!(response.uaid, DUMMY_UAID);
+            assert_eq!(response.channel_id, DUMMY_CHID);
+            assert_eq!(response.endpoint, "https://example.com/update");
+
+            let body_2 = json!({
+                "uaid": DUMMY_UAID,
+                "channelID": DUMMY_CHID2,
+                "endpoint": "https://example.com/otherendpoint",
+                "senderid": SENDER_ID,
+                "secret": SECRET,
+            })
+            .to_string();
+            let ap_mock_2 = mock(
+                "POST",
+                &*format!(
+                    "/v1/fcm/{}/registration/{}/subscription",
+                    SENDER_ID, DUMMY_UAID
+                ),
+            )
+            .with_status(200)
+            .with_header("content-type", "application/json")
+            .with_body(body_2)
+            .create();
+
+            let response = conn
+                .subscribe(DUMMY_UAID, SECRET, SENDER_ID, &None)
+                .unwrap();
+            ap_mock_2.assert();
+            assert_eq!(response.endpoint, "https://example.com/otherendpoint");
+        }
+        // UNSUBSCRIBE - Single channel
+        {
+            let ap_mock = mock(
+                "DELETE",
+                &*format!(
+                    "/v1/fcm/{}/registration/{}/subscription/{}",
+                    SENDER_ID, DUMMY_UAID, DUMMY_CHID
+                ),
+            )
+            .match_header("authorization", format!("webpush {}", SECRET).as_str())
+            .with_status(200)
+            .with_header("content-type", "application/json")
+            .with_body("{}")
+            .create();
+            let conn = ConnectHttp::connect(config.clone());
+            conn.unsubscribe(DUMMY_CHID, DUMMY_UAID, SECRET).unwrap();
+            ap_mock.assert();
+        }
+        // UNSUBSCRIBE - All for UAID
+        {
+            let ap_mock = mock(
+                "DELETE",
+                &*format!("/v1/fcm/{}/registration/{}", SENDER_ID, DUMMY_UAID),
+            )
+            .match_header("authorization", format!("webpush {}", SECRET).as_str())
+            .with_status(200)
+            .with_header("content-type", "application/json")
+            .with_body("{}")
+            .create();
+            let conn = ConnectHttp::connect(config.clone());
+            conn.unsubscribe_all(DUMMY_UAID, SECRET).unwrap();
+            ap_mock.assert();
+        }
+        // UPDATE
+        {
+            let ap_mock = mock(
+                "PUT",
+                &*format!("/v1/fcm/{}/registration/{}", SENDER_ID, DUMMY_UAID),
+            )
+            .match_header("authorization", format!("webpush {}", SECRET).as_str())
+            .with_status(200)
+            .with_header("content-type", "application/json")
+            .with_body("{}")
+            .create();
+            let conn = ConnectHttp::connect(config.clone());
+
+            conn.update("NewTokenValue", DUMMY_UAID, SECRET).unwrap();
+            ap_mock.assert();
+        }
+        // CHANNEL LIST
+        {
+            let body_cl_success = json!({
+                "uaid": DUMMY_UAID,
+                "channelIDs": [DUMMY_CHID],
+            })
+            .to_string();
+            let ap_mock = mock(
+                "GET",
+                &*format!("/v1/fcm/{}/registration/{}", SENDER_ID, DUMMY_UAID),
+            )
+            .match_header("authorization", format!("webpush {}", SECRET).as_str())
+            .with_status(200)
+            .with_header("content-type", "application/json")
+            .with_body(body_cl_success)
+            .create();
+            let conn = ConnectHttp::connect(config);
+            let response = conn.channel_list(DUMMY_UAID, SECRET).unwrap();
+            ap_mock.assert();
+            assert!(response == [DUMMY_CHID.to_owned()]);
+        }
+        // we test that we properly return a `AlreadyRegisteredError` when a client
+        // gets a `CONFLICT` status code
+        {
+            let config = PushConfiguration {
+                http_protocol: Protocol::Http,
+                server_host: server_address().to_string(),
+                sender_id: SENDER_ID.to_owned(),
+                ..Default::default()
+            };
+            // We mock that the server thinks
+            // we already registered!
+            let body = json!({
+                "code": status_codes::CONFLICT,
+                "errno": 999u32,
+                "error": "",
+                "message": "Already registered"
+
+            })
+            .to_string();
+            let ap_mock = mock("POST", &*format!("/v1/fcm/{}/registration", SENDER_ID))
+                .with_status(status_codes::CONFLICT as usize)
+                .with_header("content-type", "application/json")
+                .with_body(body)
+                .create();
+            let conn = ConnectHttp::connect(config);
+            let err = conn.register(SENDER_ID, &None).unwrap_err();
+            ap_mock.assert();
+            assert!(matches!(err, error::PushError::AlreadyRegisteredError));
+        }
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/push/internal/communications/rate_limiter.rs.html b/book/rust-docs/src/push/internal/communications/rate_limiter.rs.html new file mode 100644 index 0000000000..d3e96bd6c5 --- /dev/null +++ b/book/rust-docs/src/push/internal/communications/rate_limiter.rs.html @@ -0,0 +1,367 @@ +rate_limiter.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::internal::storage::Storage;
+use std::{
+    str::FromStr,
+    time::{SystemTime, UNIX_EPOCH},
+};
+
+// DB persisted rate limiter.
+// Implementation notes: This saves the timestamp of our latest call and the number of times we have
+// called `Self::check` within the `Self::periodic_interval` interval of time.
+pub struct PersistedRateLimiter {
+    op_name: String,
+    periodic_interval: u64, // In seconds.
+    max_requests_in_interval: u16,
+}
+
+impl PersistedRateLimiter {
+    pub fn new(op_name: &str, periodic_interval: u64, max_requests_in_interval: u16) -> Self {
+        Self {
+            op_name: op_name.to_owned(),
+            periodic_interval,
+            max_requests_in_interval,
+        }
+    }
+
+    pub fn check<S: Storage>(&self, store: &S) -> bool {
+        let (mut timestamp, mut count) = self.impl_get_counters(store);
+
+        let now = now_secs();
+        if (now - timestamp) >= self.periodic_interval {
+            log::info!(
+                "Resetting. now({}) - {} < {} for {}.",
+                now,
+                timestamp,
+                self.periodic_interval,
+                &self.op_name
+            );
+            count = 0;
+            timestamp = now;
+        } else {
+            log::info!(
+                "No need to reset inner timestamp and count for {}.",
+                &self.op_name
+            )
+        }
+
+        count += 1;
+        self.impl_persist_counters(store, timestamp, count);
+
+        // within interval counter
+        if count > self.max_requests_in_interval {
+            log::info!(
+                "Not allowed: count({}) > {} for {}.",
+                count,
+                self.max_requests_in_interval,
+                &self.op_name
+            );
+            return false;
+        }
+
+        log::info!("Allowed to pass through for {}!", &self.op_name);
+
+        true
+    }
+
+    pub fn reset<S: Storage>(&self, store: &S) {
+        self.impl_persist_counters(store, now_secs(), 0)
+    }
+
+    fn db_meta_keys(&self) -> (String, String) {
+        (
+            format!("ratelimit_{}_timestamp", &self.op_name),
+            format!("ratelimit_{}_count", &self.op_name),
+        )
+    }
+
+    fn impl_get_counters<S: Storage>(&self, store: &S) -> (u64, u16) {
+        let (timestamp_key, count_key) = self.db_meta_keys();
+        (
+            Self::get_meta_integer(store, &timestamp_key),
+            Self::get_meta_integer(store, &count_key),
+        )
+    }
+
+    #[cfg(test)]
+    pub(crate) fn get_counters<S: Storage>(&self, store: &S) -> (u64, u16) {
+        self.impl_get_counters(store)
+    }
+
+    fn get_meta_integer<S: Storage, T: FromStr + Default>(store: &S, key: &str) -> T {
+        store
+            .get_meta(key)
+            .ok()
+            .flatten()
+            .map(|s| s.parse())
+            .transpose()
+            .ok()
+            .flatten()
+            .unwrap_or_default()
+    }
+
+    fn impl_persist_counters<S: Storage>(&self, store: &S, timestamp: u64, count: u16) {
+        let (timestamp_key, count_key) = self.db_meta_keys();
+        let r1 = store.set_meta(&timestamp_key, &timestamp.to_string());
+        let r2 = store.set_meta(&count_key, &count.to_string());
+        if r1.is_err() || r2.is_err() {
+            log::warn!("Error updating persisted counters for {}.", &self.op_name);
+        }
+    }
+
+    #[cfg(test)]
+    pub(crate) fn persist_counters<S: Storage>(&self, store: &S, timestamp: u64, count: u16) {
+        self.impl_persist_counters(store, timestamp, count)
+    }
+}
+
+fn now_secs() -> u64 {
+    SystemTime::now()
+        .duration_since(UNIX_EPOCH)
+        .expect("Current date before unix epoch.")
+        .as_secs()
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+    use crate::error::Result;
+    use crate::Store;
+
+    static PERIODIC_INTERVAL: u64 = 24 * 3600;
+    static VERIFY_NOW_INTERVAL: u64 = PERIODIC_INTERVAL + 3600;
+    static MAX_REQUESTS: u16 = 500;
+
+    #[test]
+    fn test_persisted_rate_limiter_store_counters_roundtrip() -> Result<()> {
+        let limiter = PersistedRateLimiter::new("op1", PERIODIC_INTERVAL, MAX_REQUESTS);
+        let store = Store::open_in_memory()?;
+        limiter.impl_persist_counters(&store, 123, 321);
+        assert_eq!((123, 321), limiter.impl_get_counters(&store));
+        Ok(())
+    }
+
+    #[test]
+    fn test_persisted_rate_limiter_after_interval_counter_resets() -> Result<()> {
+        let limiter = PersistedRateLimiter::new("op1", PERIODIC_INTERVAL, MAX_REQUESTS);
+        let store = Store::open_in_memory()?;
+        limiter.impl_persist_counters(&store, now_secs() - VERIFY_NOW_INTERVAL, 50);
+        assert!(limiter.check(&store));
+        assert_eq!(1, limiter.impl_get_counters(&store).1);
+        Ok(())
+    }
+
+    #[test]
+    fn test_persisted_rate_limiter_false_above_rate_limit() -> Result<()> {
+        let limiter = PersistedRateLimiter::new("op1", PERIODIC_INTERVAL, MAX_REQUESTS);
+        let store = Store::open_in_memory()?;
+        limiter.impl_persist_counters(&store, now_secs(), MAX_REQUESTS + 1);
+        assert!(!limiter.check(&store));
+        assert_eq!(MAX_REQUESTS + 2, limiter.impl_get_counters(&store).1);
+        Ok(())
+    }
+
+    #[test]
+    fn test_persisted_rate_limiter_reset_above_rate_limit_and_interval() -> Result<()> {
+        let limiter = PersistedRateLimiter::new("op1", PERIODIC_INTERVAL, MAX_REQUESTS);
+        let store = Store::open_in_memory()?;
+        limiter.impl_persist_counters(&store, now_secs() - VERIFY_NOW_INTERVAL, 501);
+        assert!(limiter.check(&store));
+        assert_eq!(1, limiter.impl_get_counters(&store).1);
+        Ok(())
+    }
+
+    #[test]
+    fn test_persisted_rate_limiter_no_reset_with_rate_limits() -> Result<()> {
+        let limiter = PersistedRateLimiter::new("op1", PERIODIC_INTERVAL, MAX_REQUESTS);
+        let store = Store::open_in_memory()?;
+        assert!(limiter.check(&store));
+        Ok(())
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/push/internal/config.rs.html b/book/rust-docs/src/push/internal/config.rs.html new file mode 100644 index 0000000000..5b5a799cd2 --- /dev/null +++ b/book/rust-docs/src/push/internal/config.rs.html @@ -0,0 +1,233 @@ +config.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Provides configuration for the [PushManager](`crate::PushManager`)
+//!
+
+use std::{fmt::Display, str::FromStr};
+
+pub const DEFAULT_VERIFY_CONNECTION_LIMITER_INTERVAL: u64 = 24 * 60 * 60; // 24 hours.
+
+use crate::PushError;
+/// The types of supported native bridges.
+///
+/// FCM = Google Android Firebase Cloud Messaging
+/// ADM = Amazon Device Messaging for FireTV
+/// APNS = Apple Push Notification System for iOS
+///
+/// Please contact services back-end for any additional bridge protocols.
+///
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+pub enum BridgeType {
+    Fcm,
+    Adm,
+    Apns,
+}
+
+#[cfg(test)]
+// To avoid a future footgun, the default implementation is only for tests
+impl Default for BridgeType {
+    fn default() -> Self {
+        Self::Fcm
+    }
+}
+
+impl Display for BridgeType {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(
+            f,
+            "{}",
+            match self {
+                BridgeType::Adm => "adm",
+                BridgeType::Apns => "apns",
+                BridgeType::Fcm => "fcm",
+            }
+        )
+    }
+}
+#[derive(Clone, Debug)]
+pub struct PushConfiguration {
+    /// host name:port
+    pub server_host: String,
+
+    /// http protocol (for mobile, bridged connections "https")
+    pub http_protocol: Protocol,
+
+    /// bridge protocol ("fcm")
+    pub bridge_type: BridgeType,
+
+    /// Sender/Application ID value
+    pub sender_id: String,
+
+    /// OS Path to the database
+    pub database_path: String,
+
+    /// Number of seconds between to rate limit
+    /// the verify connection call
+    /// defaults to 24 hours
+    pub verify_connection_rate_limiter: Option<u64>,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
+pub enum Protocol {
+    #[default]
+    Https,
+    Http,
+}
+
+impl Display for Protocol {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(
+            f,
+            "{}",
+            match self {
+                Protocol::Http => "http",
+                Protocol::Https => "https",
+            }
+        )
+    }
+}
+
+impl FromStr for Protocol {
+    type Err = PushError;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        Ok(match s {
+            "http" => Protocol::Http,
+            "https" => Protocol::Https,
+            _ => return Err(PushError::GeneralError("Invalid protocol".to_string())),
+        })
+    }
+}
+
+#[cfg(test)]
+impl Default for PushConfiguration {
+    fn default() -> PushConfiguration {
+        PushConfiguration {
+            server_host: String::from("push.services.mozilla.com"),
+            http_protocol: Protocol::Https,
+            bridge_type: Default::default(),
+            sender_id: String::from(""),
+            database_path: String::from(""),
+            verify_connection_rate_limiter: Some(DEFAULT_VERIFY_CONNECTION_LIMITER_INTERVAL),
+        }
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/push/internal/crypto.rs.html b/book/rust-docs/src/push/internal/crypto.rs.html new file mode 100644 index 0000000000..7ec43d6f04 --- /dev/null +++ b/book/rust-docs/src/push/internal/crypto.rs.html @@ -0,0 +1,677 @@ +crypto.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Module providing all the cryptography needed by the push component
+//!
+//! Mainly exports a trait [`Cryptography`] and a concrete type that implements that trait
+//! [`Crypto`]
+//!
+//! The push component encrypts its push notifications. When a subscription is created,
+//! [`Cryptography::generate_key`] is called to generate a public/private key pair.
+//!
+//! The public key is then given to the subscriber (for example, Firefox Accounts) and the private key
+//! is persisted in the client. Subscribers are required to encrypt their payloads using the public key and
+//! when delivered to the client, the client would load the private key from storage and decrypt the payload.
+//!
+
+use std::borrow::Cow;
+use std::collections::HashMap;
+use std::fmt::Display;
+use std::str::FromStr;
+
+use crate::{error, PushError};
+use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
+use rc_crypto::ece::{self, EcKeyComponents, LocalKeyPair};
+use rc_crypto::ece_crypto::RcCryptoLocalKeyPair;
+use rc_crypto::rand;
+use serde::{Deserialize, Serialize};
+
+pub const SER_AUTH_LENGTH: usize = 16;
+pub type Decrypted = Vec<u8>;
+
+#[derive(Serialize, Deserialize, Clone)]
+pub(crate) enum VersionnedKey<'a> {
+    V1(Cow<'a, KeyV1>),
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+enum CryptoEncoding {
+    Aesgcm,
+    Aes128gcm,
+}
+
+impl FromStr for CryptoEncoding {
+    type Err = PushError;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        Ok(match s.to_lowercase().as_str() {
+            "aesgcm" => Self::Aesgcm,
+            "aes128gcm" => Self::Aes128gcm,
+            _ => {
+                return Err(PushError::CryptoError(format!(
+                    "Invalid crypto encoding {}",
+                    s
+                )))
+            }
+        })
+    }
+}
+
+impl Display for CryptoEncoding {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(
+            f,
+            "{}",
+            match self {
+                Self::Aesgcm => "aesgcm",
+                Self::Aes128gcm => "aes128gcm",
+            }
+        )
+    }
+}
+
+#[derive(Clone, PartialEq, Serialize, Deserialize)]
+pub struct KeyV1 {
+    pub(crate) p256key: EcKeyComponents,
+    pub(crate) auth: Vec<u8>,
+}
+pub type Key = KeyV1;
+
+impl std::fmt::Debug for KeyV1 {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.debug_struct("KeyV1").finish()
+    }
+}
+
+impl Key {
+    // We define this method so the type-checker prevents us from
+    // trying to serialize `Key` directly since `bincode::serialize`
+    // would compile because both types derive `Serialize`.
+    pub(crate) fn serialize(&self) -> error::Result<Vec<u8>> {
+        Ok(bincode::serialize(&VersionnedKey::V1(Cow::Borrowed(self)))?)
+    }
+
+    pub(crate) fn deserialize(bytes: &[u8]) -> error::Result<Self> {
+        let versionned = bincode::deserialize(bytes)?;
+        match versionned {
+            VersionnedKey::V1(prv_key) => Ok(prv_key.into_owned()),
+        }
+    }
+
+    pub fn key_pair(&self) -> &EcKeyComponents {
+        &self.p256key
+    }
+
+    pub fn auth_secret(&self) -> &[u8] {
+        &self.auth
+    }
+
+    pub fn private_key(&self) -> &[u8] {
+        self.p256key.private_key()
+    }
+
+    pub fn public_key(&self) -> &[u8] {
+        self.p256key.public_key()
+    }
+}
+
+#[cfg_attr(test, mockall::automock)]
+pub trait Cryptography: Default {
+    /// generate a new local EC p256 key
+    fn generate_key() -> error::Result<Key>;
+
+    /// General decrypt function. Calls to decrypt_aesgcm or decrypt_aes128gcm as needed.
+    #[allow(clippy::needless_lifetimes)]
+    // Clippy complains here although the lifetime is needed, seems like a bug with automock
+    fn decrypt<'a>(key: &Key, push_payload: PushPayload<'a>) -> error::Result<Decrypted>;
+
+    /// Decrypt the obsolete "aesgcm" format (which is still used by a number of providers)
+    fn decrypt_aesgcm(
+        key: &Key,
+        content: &[u8],
+        salt: Option<Vec<u8>>,
+        crypto_key: Option<Vec<u8>>,
+    ) -> error::Result<Decrypted>;
+
+    /// Decrypt the RFC 8188 format.
+    fn decrypt_aes128gcm(key: &Key, content: &[u8]) -> error::Result<Decrypted>;
+}
+
+#[derive(Default)]
+pub struct Crypto;
+
+pub fn get_random_bytes(size: usize) -> error::Result<Vec<u8>> {
+    let mut bytes = vec![0u8; size];
+    rand::fill(&mut bytes).map_err(|e| {
+        error::PushError::CryptoError(format!("Could not generate random bytes: {:?}", e))
+    })?;
+    Ok(bytes)
+}
+
+/// Extract the sub-value from the header.
+/// Sub values have the form of `label=value`. Due to a bug in some push providers, treat ',' and ';' as
+/// equivalent.
+fn extract_value(val: &str, target: &str) -> Option<Vec<u8>> {
+    if !val.contains(&format!("{}=", target)) {
+        log::debug!("No sub-value found for {}", target);
+        return None;
+    }
+    let items = val.split(|c| c == ',' || c == ';');
+    for item in items {
+        let mut kv = item.split('=');
+        if kv.next() == Some(target) {
+            if let Some(val) = kv.next() {
+                return match URL_SAFE_NO_PAD.decode(val) {
+                    Ok(v) => Some(v),
+                    Err(e) => {
+                        error_support::report_error!(
+                            "push-base64-decode",
+                            "base64 failed for target:{}; {:?}",
+                            target,
+                            e
+                        );
+                        None
+                    }
+                };
+            }
+        }
+    }
+    None
+}
+
+impl Cryptography for Crypto {
+    fn generate_key() -> error::Result<Key> {
+        rc_crypto::ensure_initialized();
+
+        let key = RcCryptoLocalKeyPair::generate_random()?;
+        let components = key.raw_components()?;
+        let auth = get_random_bytes(SER_AUTH_LENGTH)?;
+        Ok(Key {
+            p256key: components,
+            auth,
+        })
+    }
+
+    fn decrypt(key: &Key, push_payload: PushPayload<'_>) -> error::Result<Decrypted> {
+        rc_crypto::ensure_initialized();
+        // convert the private key into something useful.
+        let d_salt = extract_value(push_payload.salt, "salt");
+        let d_dh = extract_value(push_payload.dh, "dh");
+        let d_body = URL_SAFE_NO_PAD.decode(push_payload.body)?;
+
+        match CryptoEncoding::from_str(push_payload.encoding)? {
+            CryptoEncoding::Aesgcm => Self::decrypt_aesgcm(key, &d_body, d_salt, d_dh),
+            CryptoEncoding::Aes128gcm => Self::decrypt_aes128gcm(key, &d_body),
+        }
+    }
+
+    fn decrypt_aesgcm(
+        key: &Key,
+        content: &[u8],
+        salt: Option<Vec<u8>>,
+        crypto_key: Option<Vec<u8>>,
+    ) -> error::Result<Decrypted> {
+        let dh = crypto_key
+            .ok_or_else(|| error::PushError::CryptoError("Missing public key".to_string()))?;
+        let salt = salt.ok_or_else(|| error::PushError::CryptoError("Missing salt".to_string()))?;
+        let block = ece::legacy::AesGcmEncryptedBlock::new(&dh, &salt, 4096, content.to_vec())?;
+        Ok(ece::legacy::decrypt_aesgcm(
+            key.key_pair(),
+            key.auth_secret(),
+            &block,
+        )?)
+    }
+
+    fn decrypt_aes128gcm(key: &Key, content: &[u8]) -> error::Result<Vec<u8>> {
+        Ok(ece::decrypt(key.key_pair(), key.auth_secret(), content)?)
+    }
+}
+
+#[derive(Debug, Deserialize)]
+pub struct PushPayload<'a> {
+    pub(crate) channel_id: &'a str,
+    pub(crate) body: &'a str,
+    pub(crate) encoding: &'a str,
+    pub(crate) salt: &'a str,
+    pub(crate) dh: &'a str,
+}
+
+impl<'a> TryFrom<&'a HashMap<String, String>> for PushPayload<'a> {
+    type Error = PushError;
+
+    fn try_from(value: &'a HashMap<String, String>) -> Result<Self, Self::Error> {
+        let channel_id = value
+            .get("chid")
+            .ok_or_else(|| PushError::CryptoError("Invalid Push payload".to_string()))?;
+        let body = value
+            .get("body")
+            .ok_or_else(|| PushError::CryptoError("Invalid Push payload".to_string()))?;
+        let encoding = value.get("con").map(|s| s.as_str()).unwrap_or("aes128gcm");
+        let salt = value.get("enc").map(|s| s.as_str()).unwrap_or("");
+        let dh = value.get("cryptokey").map(|s| s.as_str()).unwrap_or("");
+        Ok(Self {
+            channel_id,
+            body,
+            encoding,
+            salt,
+            dh,
+        })
+    }
+}
+
+#[cfg(test)]
+mod crypto_tests {
+    use super::*;
+
+    // generate unit test key
+    fn test_key(priv_key: &str, pub_key: &str, auth: &str) -> Key {
+        let components = EcKeyComponents::new(
+            URL_SAFE_NO_PAD.decode(priv_key).unwrap(),
+            URL_SAFE_NO_PAD.decode(pub_key).unwrap(),
+        );
+        let auth = URL_SAFE_NO_PAD.decode(auth).unwrap();
+        Key {
+            p256key: components,
+            auth,
+        }
+    }
+
+    const PLAINTEXT:&str = "Amidst the mists and coldest frosts I thrust my fists against the\nposts and still demand to see the ghosts.\n\n";
+
+    fn decrypter(ciphertext: &str, encoding: &str, salt: &str, dh: &str) -> error::Result<Vec<u8>> {
+        let priv_key_d = "qJkxxWGVVxy7BKvraNY3hg8Gs-Y8qi0lRaXWJ3R3aJ8";
+        // The auth token
+        let auth_raw = "LsuUOBKVQRY6-l7_Ajo-Ag";
+        // This would be the public key sent to the subscription service.
+        let pub_key_raw = "BBcJdfs1GtMyymFTtty6lIGWRFXrEtJP40Df0gOvRDR4D8CKVgqE6vlYR7tCYksIRdKD1MxDPhQVmKLnzuife50";
+
+        let key = test_key(priv_key_d, pub_key_raw, auth_raw);
+        Crypto::decrypt(
+            &key,
+            PushPayload {
+                channel_id: "channel_id",
+                body: ciphertext,
+                encoding,
+                salt,
+                dh,
+            },
+        )
+    }
+
+    #[test]
+    fn test_decrypt_aesgcm() {
+        // The following comes from the delivered message body
+        let ciphertext = "BNKu5uTFhjyS-06eECU9-6O61int3Rr7ARbm-xPhFuyDO5sfxVs-HywGaVonvzkarvfvXE9IRT_YNA81Og2uSqDasdMuw\
+                          qm1zd0O3f7049IkQep3RJ2pEZTy5DqvI7kwMLDLzea9nroq3EMH5hYhvQtQgtKXeWieEL_3yVDQVg";
+        // and now from the header values
+        let dh = "keyid=foo;dh=BMOebOMWSRisAhWpRK9ZPszJC8BL9MiWvLZBoBU6pG6Kh6vUFSW4BHFMh0b83xCg3_7IgfQZXwmVuyu27vwiv5c,otherval=abcde";
+        let salt = "salt=tSf2qu43C9BD0zkvRW5eUg";
+
+        // and this is what it should be.
+
+        let decrypted = decrypter(ciphertext, "aesgcm", salt, dh).unwrap();
+
+        assert_eq!(String::from_utf8(decrypted).unwrap(), PLAINTEXT.to_string());
+    }
+
+    #[test]
+    fn test_fail_decrypt_aesgcm() {
+        let ciphertext = "BNKu5uTFhjyS-06eECU9-6O61int3Rr7ARbm-xPhFuyDO5sfxVs-HywGaVonvzkarvfvXE9IRT_\
+                          YNA81Og2uSqDasdMuwqm1zd0O3f7049IkQep3RJ2pEZTy5DqvI7kwMLDLzea9nroq3EMH5hYhvQtQgtKXeWieEL_3yVDQVg";
+        let dh = "dh=BMOebOMWSRisAhWpRK9ZPszJC8BL9MiWvLZBoBU6pG6Kh6vUFSW4BHFMh0b83xCg3_7IgfQZXwmVuyu27vwiv5c";
+        let salt = "salt=SomeInvalidSaltValue";
+
+        decrypter(ciphertext, "aesgcm", salt, dh).expect_err("Failed to abort, bad salt");
+    }
+
+    #[test]
+    fn test_decrypt_aes128gcm() {
+        let ciphertext = "Ek7iQgliMqS9kjFoiVOqRgAAEABBBFirfBtF6XTeHVPABFDveb1iu7uO1XVA_MYJeAo-\
+             4ih8WYUsXSTIYmkKMv5_UB3tZuQI7BQ2EVpYYQfvOCrWZVMRL8fJCuB5wVXcoRoTaFJw\
+             TlJ5hnw6IMSiaMqGVlc8drX7Hzy-ugzzAKRhGPV2x-gdsp58DZh9Ww5vHpHyT1xwVkXz\
+             x3KTyeBZu4gl_zR0Q00li17g0xGsE6Dg3xlkKEmaalgyUyObl6_a8RA6Ko1Rc6RhAy2jdyY1LQbBUnA";
+
+        let decrypted = decrypter(ciphertext, "aes128gcm", "", "").unwrap();
+        assert_eq!(String::from_utf8(decrypted).unwrap(), PLAINTEXT.to_string());
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/push/internal/mod.rs.html b/book/rust-docs/src/push/internal/mod.rs.html new file mode 100644 index 0000000000..63e830374a --- /dev/null +++ b/book/rust-docs/src/push/internal/mod.rs.html @@ -0,0 +1,23 @@ +mod.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+pub mod communications;
+pub mod config;
+pub mod crypto;
+pub mod push_manager;
+pub mod storage;
+
+pub(crate) use push_manager::PushManager;
+
\ No newline at end of file diff --git a/book/rust-docs/src/push/internal/push_manager.rs.html b/book/rust-docs/src/push/internal/push_manager.rs.html new file mode 100644 index 0000000000..d3514a2658 --- /dev/null +++ b/book/rust-docs/src/push/internal/push_manager.rs.html @@ -0,0 +1,1979 @@ +push_manager.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
+540
+541
+542
+543
+544
+545
+546
+547
+548
+549
+550
+551
+552
+553
+554
+555
+556
+557
+558
+559
+560
+561
+562
+563
+564
+565
+566
+567
+568
+569
+570
+571
+572
+573
+574
+575
+576
+577
+578
+579
+580
+581
+582
+583
+584
+585
+586
+587
+588
+589
+590
+591
+592
+593
+594
+595
+596
+597
+598
+599
+600
+601
+602
+603
+604
+605
+606
+607
+608
+609
+610
+611
+612
+613
+614
+615
+616
+617
+618
+619
+620
+621
+622
+623
+624
+625
+626
+627
+628
+629
+630
+631
+632
+633
+634
+635
+636
+637
+638
+639
+640
+641
+642
+643
+644
+645
+646
+647
+648
+649
+650
+651
+652
+653
+654
+655
+656
+657
+658
+659
+660
+661
+662
+663
+664
+665
+666
+667
+668
+669
+670
+671
+672
+673
+674
+675
+676
+677
+678
+679
+680
+681
+682
+683
+684
+685
+686
+687
+688
+689
+690
+691
+692
+693
+694
+695
+696
+697
+698
+699
+700
+701
+702
+703
+704
+705
+706
+707
+708
+709
+710
+711
+712
+713
+714
+715
+716
+717
+718
+719
+720
+721
+722
+723
+724
+725
+726
+727
+728
+729
+730
+731
+732
+733
+734
+735
+736
+737
+738
+739
+740
+741
+742
+743
+744
+745
+746
+747
+748
+749
+750
+751
+752
+753
+754
+755
+756
+757
+758
+759
+760
+761
+762
+763
+764
+765
+766
+767
+768
+769
+770
+771
+772
+773
+774
+775
+776
+777
+778
+779
+780
+781
+782
+783
+784
+785
+786
+787
+788
+789
+790
+791
+792
+793
+794
+795
+796
+797
+798
+799
+800
+801
+802
+803
+804
+805
+806
+807
+808
+809
+810
+811
+812
+813
+814
+815
+816
+817
+818
+819
+820
+821
+822
+823
+824
+825
+826
+827
+828
+829
+830
+831
+832
+833
+834
+835
+836
+837
+838
+839
+840
+841
+842
+843
+844
+845
+846
+847
+848
+849
+850
+851
+852
+853
+854
+855
+856
+857
+858
+859
+860
+861
+862
+863
+864
+865
+866
+867
+868
+869
+870
+871
+872
+873
+874
+875
+876
+877
+878
+879
+880
+881
+882
+883
+884
+885
+886
+887
+888
+889
+890
+891
+892
+893
+894
+895
+896
+897
+898
+899
+900
+901
+902
+903
+904
+905
+906
+907
+908
+909
+910
+911
+912
+913
+914
+915
+916
+917
+918
+919
+920
+921
+922
+923
+924
+925
+926
+927
+928
+929
+930
+931
+932
+933
+934
+935
+936
+937
+938
+939
+940
+941
+942
+943
+944
+945
+946
+947
+948
+949
+950
+951
+952
+953
+954
+955
+956
+957
+958
+959
+960
+961
+962
+963
+964
+965
+966
+967
+968
+969
+970
+971
+972
+973
+974
+975
+976
+977
+978
+979
+980
+981
+982
+983
+984
+985
+986
+987
+988
+989
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Main entrypoint for the push component, handles push subscriptions
+//!
+//! Exposes a struct [`PushManager`] that manages push subscriptions for a client
+//!
+//! The [`PushManager`] allows users to:
+//! - Create new subscriptions persist their private keys and return a URL for sender to send encrypted payloads using a returned public key
+//! - Delete existing subscriptions
+//! - Update native tokens with autopush server
+//! - routinely check subscriptions to make sure they are in a good state.
+
+use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
+use std::collections::{HashMap, HashSet};
+
+use crate::error::{self, PushError, Result};
+use crate::internal::communications::{Connection, PersistedRateLimiter};
+use crate::internal::config::PushConfiguration;
+use crate::internal::crypto::KeyV1 as Key;
+use crate::internal::storage::{PushRecord, Storage};
+use crate::{KeyInfo, PushSubscriptionChanged, SubscriptionInfo, SubscriptionResponse};
+
+use super::crypto::{Cryptography, PushPayload};
+const UPDATE_RATE_LIMITER_INTERVAL: u64 = 24 * 60 * 60; // 24 hours.
+const UPDATE_RATE_LIMITER_MAX_CALLS: u16 = 500; // 500
+
+impl From<Key> for KeyInfo {
+    fn from(key: Key) -> Self {
+        KeyInfo {
+            auth: URL_SAFE_NO_PAD.encode(key.auth_secret()),
+            p256dh: URL_SAFE_NO_PAD.encode(key.public_key()),
+        }
+    }
+}
+
+impl From<PushRecord> for PushSubscriptionChanged {
+    fn from(record: PushRecord) -> Self {
+        PushSubscriptionChanged {
+            channel_id: record.channel_id,
+            scope: record.scope,
+        }
+    }
+}
+
+impl TryFrom<PushRecord> for SubscriptionResponse {
+    type Error = PushError;
+    fn try_from(value: PushRecord) -> Result<Self, Self::Error> {
+        Ok(SubscriptionResponse {
+            channel_id: value.channel_id,
+            subscription_info: SubscriptionInfo {
+                endpoint: value.endpoint,
+                keys: Key::deserialize(&value.key)?.into(),
+            },
+        })
+    }
+}
+
+#[derive(Debug)]
+pub struct DecryptResponse {
+    pub result: Vec<i8>,
+    pub scope: String,
+}
+
+pub struct PushManager<Co, Cr, S> {
+    _crypo: Cr,
+    connection: Co,
+    uaid: Option<String>,
+    auth: Option<String>,
+    registration_id: Option<String>,
+    store: S,
+    update_rate_limiter: PersistedRateLimiter,
+    verify_connection_rate_limiter: PersistedRateLimiter,
+}
+
+impl<Co: Connection, Cr: Cryptography, S: Storage> PushManager<Co, Cr, S> {
+    pub fn new(config: PushConfiguration) -> Result<Self> {
+        let store = S::open(&config.database_path)?;
+        let uaid = store.get_uaid()?;
+        let auth = store.get_auth()?;
+        let registration_id = store.get_registration_id()?;
+        let verify_connection_rate_limiter = PersistedRateLimiter::new(
+            "verify_connection",
+            config
+                .verify_connection_rate_limiter
+                .unwrap_or(super::config::DEFAULT_VERIFY_CONNECTION_LIMITER_INTERVAL),
+            1,
+        );
+
+        let update_rate_limiter = PersistedRateLimiter::new(
+            "update_token",
+            UPDATE_RATE_LIMITER_INTERVAL,
+            UPDATE_RATE_LIMITER_MAX_CALLS,
+        );
+
+        Ok(Self {
+            connection: Co::connect(config),
+            _crypo: Default::default(),
+            uaid,
+            auth,
+            registration_id,
+            store,
+            update_rate_limiter,
+            verify_connection_rate_limiter,
+        })
+    }
+
+    fn ensure_auth_pair(&self) -> Result<(&str, &str)> {
+        if let (Some(uaid), Some(auth)) = (&self.uaid, &self.auth) {
+            Ok((uaid, auth))
+        } else {
+            Err(PushError::GeneralError(
+                "No subscriptions created yet.".into(),
+            ))
+        }
+    }
+
+    pub fn subscribe(
+        &mut self,
+        scope: &str,
+        server_key: Option<&str>,
+    ) -> Result<SubscriptionResponse> {
+        // While potentially an error, a misconfigured system may use "" as
+        // an application key. In that case, we drop the application key.
+        let server_key = if let Some("") = server_key {
+            None
+        } else {
+            server_key
+        };
+        // Don't fetch the subscription from the server if we've already got one.
+        if let Some(record) = self.store.get_record_by_scope(scope)? {
+            if self.uaid.is_none() {
+                // should be impossible - we should delete all records when we lose our uiad.
+                return Err(PushError::StorageError(
+                    "DB has a subscription but no UAID".to_string(),
+                ));
+            }
+            log::debug!("returning existing subscription for '{}'", scope);
+            return record.try_into();
+        }
+
+        let registration_id = self
+            .registration_id
+            .as_ref()
+            .ok_or_else(|| PushError::CommunicationError("No native id".to_string()))?
+            .clone();
+
+        self.impl_subscribe(scope, &registration_id, server_key)
+    }
+
+    pub fn get_subscription(&self, scope: &str) -> Result<Option<SubscriptionResponse>> {
+        self.store
+            .get_record_by_scope(scope)?
+            .map(TryInto::try_into)
+            .transpose()
+    }
+
+    pub fn unsubscribe(&mut self, scope: &str) -> Result<bool> {
+        let (uaid, auth) = self.ensure_auth_pair()?;
+        let record = self.store.get_record_by_scope(scope)?;
+        if let Some(record) = record {
+            self.connection
+                .unsubscribe(&record.channel_id, uaid, auth)?;
+            self.store.delete_record(&record.channel_id)?;
+            Ok(true)
+        } else {
+            Ok(false)
+        }
+    }
+
+    pub fn unsubscribe_all(&mut self) -> Result<()> {
+        let (uaid, auth) = self.ensure_auth_pair()?;
+
+        self.connection.unsubscribe_all(uaid, auth)?;
+        self.wipe_local_registrations()?;
+        Ok(())
+    }
+
+    pub fn update(&mut self, new_token: &str) -> error::Result<()> {
+        if self.registration_id.as_deref() == Some(new_token) {
+            // Already up to date!
+            // if we haven't send it to the server yet, we will on the next subscribe!
+            // if we have sent it to the server, no need to do so again. We will catch any issues
+            // through the [`PushManager::verify_connection`] check
+            return Ok(());
+        }
+
+        // It's OK if we don't have a uaid yet - that means we don't have any subscriptions,
+        // let save our registration_id, so will use it on our first subscription.
+        if self.uaid.is_none() {
+            self.store.set_registration_id(new_token)?;
+            self.registration_id = Some(new_token.to_string());
+            log::info!(
+                "saved the registration ID but not telling the server as we have no subs yet"
+            );
+            return Ok(());
+        }
+
+        if !self.update_rate_limiter.check(&self.store) {
+            return Ok(());
+        }
+
+        let (uaid, auth) = self.ensure_auth_pair()?;
+
+        if let Err(e) = self.connection.update(new_token, uaid, auth) {
+            match e {
+                PushError::UAIDNotRecognizedError(_) => {
+                    // Our subscriptions are dead, but for now, just let the existing mechanisms
+                    // deal with that (eg, next `subscribe()` or `verify_connection()`)
+                    log::info!("updating our token indicated our subscriptions are gone");
+                }
+                _ => return Err(e),
+            }
+        }
+
+        self.store.set_registration_id(new_token)?;
+        self.registration_id = Some(new_token.to_string());
+        Ok(())
+    }
+
+    pub fn verify_connection(
+        &mut self,
+        force_verify: bool,
+    ) -> Result<Vec<PushSubscriptionChanged>> {
+        if force_verify {
+            self.verify_connection_rate_limiter.reset(&self.store);
+        }
+        if !self.verify_connection_rate_limiter.check(&self.store) {
+            return Ok(vec![]);
+        }
+        let channels = self.store.get_channel_list()?;
+        let (uaid, auth) = self.ensure_auth_pair()?;
+
+        let local_channels: HashSet<String> = channels.into_iter().collect();
+        let remote_channels = match self.connection.channel_list(uaid, auth) {
+            Ok(v) => Some(HashSet::from_iter(v)),
+            Err(e) => match e {
+                PushError::UAIDNotRecognizedError(_) => {
+                    // We do not unsubscribe, because the server already lost our UAID
+                    None
+                }
+                _ => return Err(e),
+            },
+        };
+
+        // verify both lists match. Either side could have lost its mind.
+        match remote_channels {
+            // Everything is OK! Lets return early
+            Some(channels) if channels == local_channels => return Ok(Vec::new()),
+            Some(_) => {
+                log::info!("verify_connection found a mismatch - unsubscribing");
+                // Unsubscribe all the channels (just to be sure and avoid a loop).
+                self.connection.unsubscribe_all(uaid, auth)?;
+            }
+            // Means the server lost our UAID, lets not unsubscribe,
+            // as that operation will fail
+            None => (),
+        };
+
+        let mut subscriptions: Vec<PushSubscriptionChanged> = Vec::new();
+        for channel in local_channels {
+            if let Some(record) = self.store.get_record(&channel)? {
+                subscriptions.push(record.into());
+            }
+        }
+        // we wipe all existing subscriptions and the UAID if there is a mismatch; the next
+        // `subscribe()` call will get a new UAID.
+        self.wipe_local_registrations()?;
+        Ok(subscriptions)
+    }
+
+    pub fn decrypt(&self, payload: HashMap<String, String>) -> Result<DecryptResponse> {
+        let payload = PushPayload::try_from(&payload)?;
+        let val = self
+            .store
+            .get_record(payload.channel_id)?
+            .ok_or_else(|| PushError::RecordNotFoundError(payload.channel_id.to_string()))?;
+        let key = Key::deserialize(&val.key)?;
+        let decrypted = Cr::decrypt(&key, payload)?;
+        // NOTE: this returns a `Vec<i8>` since the kotlin consumer is expecting
+        // signed bytes.
+        Ok(DecryptResponse {
+            result: decrypted.into_iter().map(|ub| ub as i8).collect(),
+            scope: val.scope,
+        })
+    }
+
+    fn wipe_local_registrations(&mut self) -> error::Result<()> {
+        self.store.delete_all_records()?;
+        self.auth = None;
+        self.uaid = None;
+        Ok(())
+    }
+
+    fn impl_subscribe(
+        &mut self,
+        scope: &str,
+        registration_id: &str,
+        server_key: Option<&str>,
+    ) -> error::Result<SubscriptionResponse> {
+        if let (Some(uaid), Some(auth)) = (&self.uaid, &self.auth) {
+            self.subscribe_with_uaid(scope, uaid, auth, registration_id, server_key)
+        } else {
+            self.register(scope, registration_id, server_key)
+        }
+    }
+
+    fn subscribe_with_uaid(
+        &self,
+        scope: &str,
+        uaid: &str,
+        auth: &str,
+        registration_id: &str,
+        app_server_key: Option<&str>,
+    ) -> error::Result<SubscriptionResponse> {
+        let app_server_key = app_server_key.map(|v| v.to_owned());
+
+        let subscription_response =
+            self.connection
+                .subscribe(uaid, auth, registration_id, &app_server_key)?;
+        let subscription_key = Cr::generate_key()?;
+        let mut record = crate::internal::storage::PushRecord::new(
+            &subscription_response.channel_id,
+            &subscription_response.endpoint,
+            scope,
+            subscription_key.clone(),
+        )?;
+        record.app_server_key = app_server_key;
+        self.store.put_record(&record)?;
+        log::debug!("subscribed OK");
+        Ok(SubscriptionResponse {
+            channel_id: subscription_response.channel_id,
+            subscription_info: SubscriptionInfo {
+                endpoint: subscription_response.endpoint,
+                keys: subscription_key.into(),
+            },
+        })
+    }
+
+    fn register(
+        &mut self,
+        scope: &str,
+        registration_id: &str,
+        app_server_key: Option<&str>,
+    ) -> error::Result<SubscriptionResponse> {
+        let app_server_key = app_server_key.map(|v| v.to_owned());
+        let register_response = self.connection.register(registration_id, &app_server_key)?;
+        // Registration successful! Before we return our registration, lets save our uaid and auth
+        self.store.set_uaid(&register_response.uaid)?;
+        self.store.set_auth(&register_response.secret)?;
+        self.uaid = Some(register_response.uaid.clone());
+        self.auth = Some(register_response.secret.clone());
+
+        let subscription_key = Cr::generate_key()?;
+        let mut record = crate::internal::storage::PushRecord::new(
+            &register_response.channel_id,
+            &register_response.endpoint,
+            scope,
+            subscription_key.clone(),
+        )?;
+        record.app_server_key = app_server_key;
+        self.store.put_record(&record)?;
+        log::debug!("subscribed OK");
+        Ok(SubscriptionResponse {
+            channel_id: register_response.channel_id,
+            subscription_info: SubscriptionInfo {
+                endpoint: register_response.endpoint,
+                keys: subscription_key.into(),
+            },
+        })
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use mockall::predicate::eq;
+    use rc_crypto::ece::{self, EcKeyComponents};
+
+    use crate::internal::{
+        communications::{MockConnection, RegisterResponse, SubscribeResponse},
+        crypto::MockCryptography,
+    };
+
+    use super::*;
+    use lazy_static::lazy_static;
+    use std::sync::{Mutex, MutexGuard};
+
+    use crate::Store;
+
+    lazy_static! {
+        static ref MTX: Mutex<()> = Mutex::new(());
+    }
+
+    // we need to run our tests in sequence. The tests mock static
+    // methods. Mocked static methods are global are susceptible to data races
+    // see: https://docs.rs/mockall/latest/mockall/#static-methods
+    fn get_lock(m: &'static Mutex<()>) -> MutexGuard<'static, ()> {
+        match m.lock() {
+            Ok(guard) => guard,
+            Err(poisoned) => poisoned.into_inner(),
+        }
+    }
+
+    const TEST_UAID: &str = "abad1d3a00000000aabbccdd00000000";
+    const DATA: &[u8] = b"Mary had a little lamb, with some nice mint jelly";
+    const TEST_CHANNEL_ID: &str = "deadbeef00000000decafbad00000000";
+    const TEST_CHANNEL_ID2: &str = "decafbad00000000deadbeef00000000";
+
+    const PRIV_KEY_D: &str = "qJkxxWGVVxy7BKvraNY3hg8Gs-Y8qi0lRaXWJ3R3aJ8";
+    // The auth token
+    const TEST_AUTH: &str = "LsuUOBKVQRY6-l7_Ajo-Ag";
+    // This would be the public key sent to the subscription service.
+    const PUB_KEY_RAW: &str =
+        "BBcJdfs1GtMyymFTtty6lIGWRFXrEtJP40Df0gOvRDR4D8CKVgqE6vlYR7tCYksIRdKD1MxDPhQVmKLnzuife50";
+
+    const ONE_DAY_AND_ONE_SECOND: u64 = (24 * 60 * 60) + 1;
+
+    fn get_test_manager() -> Result<PushManager<MockConnection, MockCryptography, Store>> {
+        let test_config = PushConfiguration {
+            sender_id: "test".to_owned(),
+            ..Default::default()
+        };
+
+        let mut pm: PushManager<MockConnection, MockCryptography, Store> =
+            PushManager::new(test_config)?;
+        pm.store.set_registration_id("native-id")?;
+        pm.registration_id = Some("native-id".to_string());
+        Ok(pm)
+    }
+    #[test]
+    fn basic() -> Result<()> {
+        let _m = get_lock(&MTX);
+        let ctx = MockConnection::connect_context();
+        ctx.expect().returning(|_| Default::default());
+
+        let mut pm = get_test_manager()?;
+        pm.connection
+            .expect_register()
+            .with(eq("native-id"), eq(None))
+            .times(1)
+            .returning(|_, _| {
+                Ok(RegisterResponse {
+                    uaid: TEST_UAID.to_string(),
+                    channel_id: TEST_CHANNEL_ID.to_string(),
+                    secret: TEST_AUTH.to_string(),
+                    endpoint: "https://example.com/dummy-endpoint".to_string(),
+                    sender_id: Some("test".to_string()),
+                })
+            });
+        let crypto_ctx = MockCryptography::generate_key_context();
+        crypto_ctx.expect().returning(|| {
+            let components = EcKeyComponents::new(
+                URL_SAFE_NO_PAD.decode(PRIV_KEY_D).unwrap(),
+                URL_SAFE_NO_PAD.decode(PUB_KEY_RAW).unwrap(),
+            );
+            let auth = URL_SAFE_NO_PAD.decode(TEST_AUTH).unwrap();
+            Ok(Key {
+                p256key: components,
+                auth,
+            })
+        });
+        let resp = pm.subscribe("test-scope", None)?;
+        // verify that a subsequent request for the same channel ID returns the same subscription
+        let resp2 = pm.subscribe("test-scope", None)?;
+        assert_eq!(Some(TEST_AUTH.to_owned()), pm.store.get_auth()?);
+        assert_eq!(
+            resp.subscription_info.endpoint,
+            resp2.subscription_info.endpoint
+        );
+        assert_eq!(resp.subscription_info.keys, resp2.subscription_info.keys);
+
+        pm.connection
+            .expect_unsubscribe()
+            .with(eq(TEST_CHANNEL_ID), eq(TEST_UAID), eq(TEST_AUTH))
+            .times(1)
+            .returning(|_, _, _| Ok(()));
+        pm.connection
+            .expect_unsubscribe_all()
+            .with(eq(TEST_UAID), eq(TEST_AUTH))
+            .times(1)
+            .returning(|_, _| Ok(()));
+
+        pm.unsubscribe("test-scope")?;
+        // It's already deleted, we still return an OK, but it won't trigger a network request
+        pm.unsubscribe("test-scope")?;
+        pm.unsubscribe_all()?;
+        Ok(())
+    }
+
+    #[test]
+    fn full() -> Result<()> {
+        let _m = get_lock(&MTX);
+        rc_crypto::ensure_initialized();
+        let ctx = MockConnection::connect_context();
+        ctx.expect().returning(|_| Default::default());
+        let data_string = b"Mary had a little lamb, with some nice mint jelly";
+        let mut pm = get_test_manager()?;
+        pm.connection
+            .expect_register()
+            .with(eq("native-id"), eq(None))
+            .times(1)
+            .returning(|_, _| {
+                Ok(RegisterResponse {
+                    uaid: TEST_UAID.to_string(),
+                    channel_id: TEST_CHANNEL_ID.to_string(),
+                    secret: TEST_AUTH.to_string(),
+                    endpoint: "https://example.com/dummy-endpoint".to_string(),
+                    sender_id: Some("test".to_string()),
+                })
+            });
+        let crypto_ctx = MockCryptography::generate_key_context();
+        crypto_ctx.expect().returning(|| {
+            let components = EcKeyComponents::new(
+                URL_SAFE_NO_PAD.decode(PRIV_KEY_D).unwrap(),
+                URL_SAFE_NO_PAD.decode(PUB_KEY_RAW).unwrap(),
+            );
+            let auth = URL_SAFE_NO_PAD.decode(TEST_AUTH).unwrap();
+            Ok(Key {
+                p256key: components,
+                auth,
+            })
+        });
+
+        let resp = pm.subscribe("test-scope", None)?;
+        let key_info = resp.subscription_info.keys;
+        let remote_pub = URL_SAFE_NO_PAD.decode(&key_info.p256dh).unwrap();
+        let auth = URL_SAFE_NO_PAD.decode(&key_info.auth).unwrap();
+        // Act like a subscription provider, so create a "local" key to encrypt the data
+        let ciphertext = ece::encrypt(&remote_pub, &auth, data_string).unwrap();
+        let body = URL_SAFE_NO_PAD.encode(ciphertext);
+
+        let decryp_ctx = MockCryptography::decrypt_context();
+        let body_clone = body.clone();
+        decryp_ctx
+            .expect()
+            .withf(move |key, push_payload| {
+                *key == Key {
+                    p256key: EcKeyComponents::new(
+                        URL_SAFE_NO_PAD.decode(PRIV_KEY_D).unwrap(),
+                        URL_SAFE_NO_PAD.decode(PUB_KEY_RAW).unwrap(),
+                    ),
+                    auth: URL_SAFE_NO_PAD.decode(TEST_AUTH).unwrap(),
+                } && push_payload.body == body_clone
+                    && push_payload.encoding == "aes128gcm"
+                    && push_payload.dh.is_empty()
+                    && push_payload.salt.is_empty()
+            })
+            .returning(|_, _| Ok(data_string.to_vec()));
+
+        let payload = HashMap::from_iter(vec![
+            ("chid".to_string(), resp.channel_id),
+            ("body".to_string(), body),
+            ("con".to_string(), "aes128gcm".to_string()),
+            ("enc".to_string(), "".to_string()),
+            ("cryptokey".to_string(), "".to_string()),
+        ]);
+        pm.decrypt(payload).unwrap();
+        Ok(())
+    }
+
+    #[test]
+    fn test_aesgcm_decryption() -> Result<()> {
+        let _m = get_lock(&MTX);
+        rc_crypto::ensure_initialized();
+
+        let ctx = MockConnection::connect_context();
+        ctx.expect().returning(|_| Default::default());
+
+        let mut pm = get_test_manager()?;
+
+        pm.connection
+            .expect_register()
+            .with(eq("native-id"), eq(None))
+            .times(1)
+            .returning(|_, _| {
+                Ok(RegisterResponse {
+                    uaid: TEST_UAID.to_string(),
+                    channel_id: TEST_CHANNEL_ID.to_string(),
+                    secret: TEST_AUTH.to_string(),
+                    endpoint: "https://example.com/dummy-endpoint".to_string(),
+                    sender_id: Some("test".to_string()),
+                })
+            });
+        let crypto_ctx = MockCryptography::generate_key_context();
+        crypto_ctx.expect().returning(|| {
+            let components = EcKeyComponents::new(
+                URL_SAFE_NO_PAD.decode(PRIV_KEY_D).unwrap(),
+                URL_SAFE_NO_PAD.decode(PUB_KEY_RAW).unwrap(),
+            );
+            let auth = URL_SAFE_NO_PAD.decode(TEST_AUTH).unwrap();
+            Ok(Key {
+                p256key: components,
+                auth,
+            })
+        });
+        let resp = pm.subscribe("test-scope", None)?;
+        let key_info = resp.subscription_info.keys;
+        let remote_pub = URL_SAFE_NO_PAD.decode(&key_info.p256dh).unwrap();
+        let auth = URL_SAFE_NO_PAD.decode(&key_info.auth).unwrap();
+        // Act like a subscription provider, so create a "local" key to encrypt the data
+        let ciphertext = ece::encrypt(&remote_pub, &auth, DATA).unwrap();
+        let body = URL_SAFE_NO_PAD.encode(ciphertext);
+
+        let decryp_ctx = MockCryptography::decrypt_context();
+        let body_clone = body.clone();
+        decryp_ctx
+            .expect()
+            .withf(move |key, push_payload| {
+                *key == Key {
+                    p256key: EcKeyComponents::new(
+                        URL_SAFE_NO_PAD.decode(PRIV_KEY_D).unwrap(),
+                        URL_SAFE_NO_PAD.decode(PUB_KEY_RAW).unwrap(),
+                    ),
+                    auth: URL_SAFE_NO_PAD.decode(TEST_AUTH).unwrap(),
+                } && push_payload.body == body_clone
+                    && push_payload.encoding == "aesgcm"
+                    && push_payload.dh.is_empty()
+                    && push_payload.salt.is_empty()
+            })
+            .returning(|_, _| Ok(DATA.to_vec()));
+
+        let payload = HashMap::from_iter(vec![
+            ("chid".to_string(), resp.channel_id),
+            ("body".to_string(), body),
+            ("con".to_string(), "aesgcm".to_string()),
+            ("enc".to_string(), "".to_string()),
+            ("cryptokey".to_string(), "".to_string()),
+        ]);
+        pm.decrypt(payload).unwrap();
+        Ok(())
+    }
+
+    #[test]
+    fn test_duplicate_subscription_requests() -> Result<()> {
+        let _m = get_lock(&MTX);
+        rc_crypto::ensure_initialized();
+
+        let ctx = MockConnection::connect_context();
+        ctx.expect().returning(|_| Default::default());
+
+        let mut pm = get_test_manager()?;
+
+        pm.connection
+            .expect_register()
+            .with(eq("native-id"), eq(None))
+            .times(1) // only once, second time we'll hit cache!
+            .returning(|_, _| {
+                Ok(RegisterResponse {
+                    uaid: TEST_UAID.to_string(),
+                    channel_id: TEST_CHANNEL_ID.to_string(),
+                    secret: TEST_AUTH.to_string(),
+                    endpoint: "https://example.com/dummy-endpoint".to_string(),
+                    sender_id: Some("test".to_string()),
+                })
+            });
+        let crypto_ctx = MockCryptography::generate_key_context();
+        crypto_ctx.expect().returning(|| {
+            let components = EcKeyComponents::new(
+                URL_SAFE_NO_PAD.decode(PRIV_KEY_D).unwrap(),
+                URL_SAFE_NO_PAD.decode(PUB_KEY_RAW).unwrap(),
+            );
+            let auth = URL_SAFE_NO_PAD.decode(TEST_AUTH).unwrap();
+            Ok(Key {
+                p256key: components,
+                auth,
+            })
+        });
+        let sub_1 = pm.subscribe("test-scope", None)?;
+        let sub_2 = pm.subscribe("test-scope", None)?;
+        assert_eq!(sub_1, sub_2);
+        Ok(())
+    }
+    #[test]
+    fn test_verify_wipe_uaid_if_mismatch() -> Result<()> {
+        let _m = get_lock(&MTX);
+        let ctx = MockConnection::connect_context();
+        ctx.expect().returning(|_| Default::default());
+
+        let mut pm = get_test_manager()?;
+        pm.connection
+            .expect_register()
+            .with(eq("native-id"), eq(None))
+            .times(2)
+            .returning(|_, _| {
+                Ok(RegisterResponse {
+                    uaid: TEST_UAID.to_string(),
+                    channel_id: TEST_CHANNEL_ID.to_string(),
+                    secret: TEST_AUTH.to_string(),
+                    endpoint: "https://example.com/dummy-endpoint".to_string(),
+                    sender_id: Some("test".to_string()),
+                })
+            });
+
+        let crypto_ctx = MockCryptography::generate_key_context();
+        crypto_ctx.expect().returning(|| {
+            let components = EcKeyComponents::new(
+                URL_SAFE_NO_PAD.decode(PRIV_KEY_D).unwrap(),
+                URL_SAFE_NO_PAD.decode(PUB_KEY_RAW).unwrap(),
+            );
+            let auth = URL_SAFE_NO_PAD.decode(TEST_AUTH).unwrap();
+            Ok(Key {
+                p256key: components,
+                auth,
+            })
+        });
+        pm.connection
+            .expect_channel_list()
+            .with(eq(TEST_UAID), eq(TEST_AUTH))
+            .times(1)
+            .returning(|_, _| Ok(vec![TEST_CHANNEL_ID2.to_string()]));
+
+        pm.connection
+            .expect_unsubscribe_all()
+            .with(eq(TEST_UAID), eq(TEST_AUTH))
+            .times(1)
+            .returning(|_, _| Ok(()));
+        let _ = pm.subscribe("test-scope", None)?;
+        // verify that a uaid got added to our store and
+        // that there is a record associated with the channel ID provided
+        assert_eq!(pm.store.get_uaid()?.unwrap(), TEST_UAID);
+        assert_eq!(
+            pm.store.get_record(TEST_CHANNEL_ID)?.unwrap().channel_id,
+            TEST_CHANNEL_ID
+        );
+        let unsubscribed_channels = pm.verify_connection(false)?;
+        assert_eq!(unsubscribed_channels.len(), 1);
+        assert_eq!(unsubscribed_channels[0].channel_id, TEST_CHANNEL_ID);
+        // since verify_connection failed,
+        // we wipe the uaid and all associated records from our store
+        assert!(pm.store.get_uaid()?.is_none());
+        assert!(pm.store.get_record(TEST_CHANNEL_ID)?.is_none());
+
+        // we now check that a new subscription will cause us to
+        // re-generate a uaid and store it in our store
+        let _ = pm.subscribe("test-scope", None)?;
+        // verify that the uaid got added to our store and
+        // that there is a record associated with the channel ID provided
+        assert_eq!(pm.store.get_uaid()?.unwrap(), TEST_UAID);
+        assert_eq!(
+            pm.store.get_record(TEST_CHANNEL_ID)?.unwrap().channel_id,
+            TEST_CHANNEL_ID
+        );
+        Ok(())
+    }
+
+    #[test]
+    fn test_verify_server_lost_uaid_not_error() -> Result<()> {
+        let _m = get_lock(&MTX);
+        let ctx = MockConnection::connect_context();
+        ctx.expect().returning(|_| Default::default());
+
+        let mut pm = get_test_manager()?;
+        pm.connection
+            .expect_register()
+            .with(eq("native-id"), eq(None))
+            .times(1)
+            .returning(|_, _| {
+                Ok(RegisterResponse {
+                    uaid: TEST_UAID.to_string(),
+                    channel_id: TEST_CHANNEL_ID.to_string(),
+                    secret: TEST_AUTH.to_string(),
+                    endpoint: "https://example.com/dummy-endpoint".to_string(),
+                    sender_id: Some("test".to_string()),
+                })
+            });
+
+        let crypto_ctx = MockCryptography::generate_key_context();
+        crypto_ctx.expect().returning(|| {
+            let components = EcKeyComponents::new(
+                URL_SAFE_NO_PAD.decode(PRIV_KEY_D).unwrap(),
+                URL_SAFE_NO_PAD.decode(PUB_KEY_RAW).unwrap(),
+            );
+            let auth = URL_SAFE_NO_PAD.decode(TEST_AUTH).unwrap();
+            Ok(Key {
+                p256key: components,
+                auth,
+            })
+        });
+        pm.connection
+            .expect_channel_list()
+            .with(eq(TEST_UAID), eq(TEST_AUTH))
+            .times(1)
+            .returning(|_, _| {
+                Err(PushError::UAIDNotRecognizedError(
+                    "Couldn't find uaid".to_string(),
+                ))
+            });
+
+        let _ = pm.subscribe("test-scope", None)?;
+        // verify that a uaid got added to our store and
+        // that there is a record associated with the channel ID provided
+        assert_eq!(pm.store.get_uaid()?.unwrap(), TEST_UAID);
+        assert_eq!(
+            pm.store.get_record(TEST_CHANNEL_ID)?.unwrap().channel_id,
+            TEST_CHANNEL_ID
+        );
+        let unsubscribed_channels = pm.verify_connection(false)?;
+        assert_eq!(unsubscribed_channels.len(), 1);
+        assert_eq!(unsubscribed_channels[0].channel_id, TEST_CHANNEL_ID);
+        // since verify_connection failed,
+        // we wipe the uaid and all associated records from our store
+        assert!(pm.store.get_uaid()?.is_none());
+        assert!(pm.store.get_record(TEST_CHANNEL_ID)?.is_none());
+        Ok(())
+    }
+
+    #[test]
+    fn test_verify_server_hard_error() -> Result<()> {
+        let _m = get_lock(&MTX);
+        let ctx = MockConnection::connect_context();
+        ctx.expect().returning(|_| Default::default());
+
+        let mut pm = get_test_manager()?;
+        pm.connection
+            .expect_register()
+            .with(eq("native-id"), eq(None))
+            .times(1)
+            .returning(|_, _| {
+                Ok(RegisterResponse {
+                    uaid: TEST_UAID.to_string(),
+                    channel_id: TEST_CHANNEL_ID.to_string(),
+                    secret: TEST_AUTH.to_string(),
+                    endpoint: "https://example.com/dummy-endpoint".to_string(),
+                    sender_id: Some("test".to_string()),
+                })
+            });
+
+        let crypto_ctx = MockCryptography::generate_key_context();
+        crypto_ctx.expect().returning(|| {
+            let components = EcKeyComponents::new(
+                URL_SAFE_NO_PAD.decode(PRIV_KEY_D).unwrap(),
+                URL_SAFE_NO_PAD.decode(PUB_KEY_RAW).unwrap(),
+            );
+            let auth = URL_SAFE_NO_PAD.decode(TEST_AUTH).unwrap();
+            Ok(Key {
+                p256key: components,
+                auth,
+            })
+        });
+        pm.connection
+            .expect_channel_list()
+            .with(eq(TEST_UAID), eq(TEST_AUTH))
+            .times(1)
+            .returning(|_, _| {
+                Err(PushError::CommunicationError(
+                    "Unrecoverable error".to_string(),
+                ))
+            });
+
+        let _ = pm.subscribe("test-scope", None)?;
+        // verify that a uaid got added to our store and
+        // that there is a record associated with the channel ID provided
+        assert_eq!(pm.store.get_uaid()?.unwrap(), TEST_UAID);
+        assert_eq!(
+            pm.store.get_record(TEST_CHANNEL_ID)?.unwrap().channel_id,
+            TEST_CHANNEL_ID
+        );
+        let err = pm.verify_connection(false).unwrap_err();
+
+        // the same error got propagated
+        assert!(matches!(err, PushError::CommunicationError(_)));
+        Ok(())
+    }
+
+    #[test]
+    fn test_second_subscribe_hits_subscribe_endpoint() -> Result<()> {
+        let _m = get_lock(&MTX);
+        let ctx = MockConnection::connect_context();
+        ctx.expect().returning(|_| Default::default());
+
+        let mut pm = get_test_manager()?;
+        pm.connection
+            .expect_register()
+            .with(eq("native-id"), eq(None))
+            .times(1)
+            .returning(|_, _| {
+                Ok(RegisterResponse {
+                    uaid: TEST_UAID.to_string(),
+                    channel_id: TEST_CHANNEL_ID.to_string(),
+                    secret: TEST_AUTH.to_string(),
+                    endpoint: "https://example.com/dummy-endpoint".to_string(),
+                    sender_id: Some("test".to_string()),
+                })
+            });
+
+        pm.connection
+            .expect_subscribe()
+            .with(eq(TEST_UAID), eq(TEST_AUTH), eq("native-id"), eq(None))
+            .times(1)
+            .returning(|_, _, _, _| {
+                Ok(SubscribeResponse {
+                    channel_id: TEST_CHANNEL_ID2.to_string(),
+                    endpoint: "https://example.com/different-dummy-endpoint".to_string(),
+                    sender_id: Some("test".to_string()),
+                })
+            });
+
+        let crypto_ctx = MockCryptography::generate_key_context();
+        crypto_ctx.expect().returning(|| {
+            let components = EcKeyComponents::new(
+                URL_SAFE_NO_PAD.decode(PRIV_KEY_D).unwrap(),
+                URL_SAFE_NO_PAD.decode(PUB_KEY_RAW).unwrap(),
+            );
+            let auth = URL_SAFE_NO_PAD.decode(TEST_AUTH).unwrap();
+            Ok(Key {
+                p256key: components,
+                auth,
+            })
+        });
+
+        let resp_1 = pm.subscribe("test-scope", None)?;
+        let resp_2 = pm.subscribe("another-scope", None)?;
+        assert_eq!(
+            resp_1.subscription_info.endpoint,
+            "https://example.com/dummy-endpoint"
+        );
+        assert_eq!(
+            resp_2.subscription_info.endpoint,
+            "https://example.com/different-dummy-endpoint"
+        );
+        Ok(())
+    }
+
+    #[test]
+    fn test_verify_connection_rate_limiter() -> Result<()> {
+        let _m = get_lock(&MTX);
+        let ctx = MockConnection::connect_context();
+        ctx.expect().returning(|_| Default::default());
+
+        let mut pm = get_test_manager()?;
+        pm.connection
+            .expect_register()
+            .with(eq("native-id"), eq(None))
+            .times(1)
+            .returning(|_, _| {
+                Ok(RegisterResponse {
+                    uaid: TEST_UAID.to_string(),
+                    channel_id: TEST_CHANNEL_ID.to_string(),
+                    secret: TEST_AUTH.to_string(),
+                    endpoint: "https://example.com/dummy-endpoint".to_string(),
+                    sender_id: Some("test".to_string()),
+                })
+            });
+        let crypto_ctx = MockCryptography::generate_key_context();
+        crypto_ctx.expect().returning(|| {
+            let components = EcKeyComponents::new(
+                URL_SAFE_NO_PAD.decode(PRIV_KEY_D).unwrap(),
+                URL_SAFE_NO_PAD.decode(PUB_KEY_RAW).unwrap(),
+            );
+            let auth = URL_SAFE_NO_PAD.decode(TEST_AUTH).unwrap();
+            Ok(Key {
+                p256key: components,
+                auth,
+            })
+        });
+        let _ = pm.subscribe("test-scope", None)?;
+        pm.connection
+            .expect_channel_list()
+            .with(eq(TEST_UAID), eq(TEST_AUTH))
+            .times(3)
+            .returning(|_, _| Ok(vec![TEST_CHANNEL_ID.to_string()]));
+        let _ = pm.verify_connection(false)?;
+        let (_, count) = pm.verify_connection_rate_limiter.get_counters(&pm.store);
+        assert_eq!(count, 1);
+        let _ = pm.verify_connection(false)?;
+        let (timestamp, count) = pm.verify_connection_rate_limiter.get_counters(&pm.store);
+
+        assert_eq!(count, 2);
+
+        pm.verify_connection_rate_limiter.persist_counters(
+            &pm.store,
+            timestamp - ONE_DAY_AND_ONE_SECOND,
+            count,
+        );
+
+        let _ = pm.verify_connection(false)?;
+        let (_, count) = pm.verify_connection_rate_limiter.get_counters(&pm.store);
+        assert_eq!(count, 1);
+
+        // Even though a day hasn't passed, we passed `true` to force verify
+        // so the counter is now reset
+        let _ = pm.verify_connection(true)?;
+        let (_, count) = pm.verify_connection_rate_limiter.get_counters(&pm.store);
+        assert_eq!(count, 1);
+
+        Ok(())
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/push/internal/storage/db.rs.html b/book/rust-docs/src/push/internal/storage/db.rs.html new file mode 100644 index 0000000000..9310afd6a4 --- /dev/null +++ b/book/rust-docs/src/push/internal/storage/db.rs.html @@ -0,0 +1,753 @@ +db.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+use std::{ops::Deref, path::Path};
+
+use rusqlite::Connection;
+use sql_support::{open_database, ConnExt};
+
+use crate::error::{PushError, Result};
+
+use super::{record::PushRecord, schema};
+
+pub trait Storage: Sized {
+    fn open<P: AsRef<Path>>(path: P) -> Result<Self>;
+
+    fn get_record(&self, chid: &str) -> Result<Option<PushRecord>>;
+
+    fn get_record_by_scope(&self, scope: &str) -> Result<Option<PushRecord>>;
+
+    fn put_record(&self, record: &PushRecord) -> Result<bool>;
+
+    fn delete_record(&self, chid: &str) -> Result<bool>;
+
+    fn delete_all_records(&self) -> Result<()>;
+
+    fn get_channel_list(&self) -> Result<Vec<String>>;
+
+    fn update_endpoint(&self, channel_id: &str, endpoint: &str) -> Result<bool>;
+
+    // Some of our "meta" keys are more important than others, so they get special helpers.
+    fn get_uaid(&self) -> Result<Option<String>>;
+    fn set_uaid(&self, uaid: &str) -> Result<()>;
+
+    fn get_auth(&self) -> Result<Option<String>>;
+    fn set_auth(&self, auth: &str) -> Result<()>;
+
+    fn get_registration_id(&self) -> Result<Option<String>>;
+    fn set_registration_id(&self, native_id: &str) -> Result<()>;
+
+    // And general purpose meta with hard-coded key names spread everywhere.
+    fn get_meta(&self, key: &str) -> Result<Option<String>>;
+    fn set_meta(&self, key: &str, value: &str) -> Result<()>;
+}
+
+pub struct PushDb {
+    pub db: Connection,
+}
+
+impl PushDb {
+    pub fn open(path: impl AsRef<Path>) -> Result<Self> {
+        let path = path.as_ref();
+        // By default, file open errors are StorageSqlErrors and aren't super helpful.
+        // Instead, remap to StorageError and provide the path to the file that couldn't be opened.
+        let initializer = schema::PushConnectionInitializer {};
+        let db = open_database::open_database(path, &initializer).map_err(|orig| {
+            PushError::StorageError(format!(
+                "Could not open database file {:?} - {}",
+                &path.as_os_str(),
+                orig,
+            ))
+        })?;
+        Ok(Self { db })
+    }
+
+    #[cfg(test)]
+    pub fn open_in_memory() -> Result<Self> {
+        // A nod to our tests which use this.
+        env_logger::try_init().ok();
+
+        let initializer = schema::PushConnectionInitializer {};
+        let db = open_database::open_memory_database(&initializer)?;
+        Ok(Self { db })
+    }
+
+    /// Normalize UUID values to undashed, lowercase.
+    // The server mangles ChannelID UUIDs to undashed lowercase values. We should force those
+    // so that key lookups continue to work.
+    pub fn normalize_uuid(uuid: &str) -> String {
+        uuid.replace('-', "").to_lowercase()
+    }
+}
+
+impl Deref for PushDb {
+    type Target = Connection;
+    fn deref(&self) -> &Connection {
+        &self.db
+    }
+}
+
+impl ConnExt for PushDb {
+    fn conn(&self) -> &Connection {
+        &self.db
+    }
+}
+
+impl Storage for PushDb {
+    fn get_record(&self, chid: &str) -> Result<Option<PushRecord>> {
+        let query = format!(
+            "SELECT {common_cols}
+             FROM push_record WHERE channel_id = :chid",
+            common_cols = schema::COMMON_COLS,
+        );
+        self.try_query_row(
+            &query,
+            &[(":chid", &Self::normalize_uuid(chid))],
+            PushRecord::from_row,
+            false,
+        )
+    }
+
+    fn get_record_by_scope(&self, scope: &str) -> Result<Option<PushRecord>> {
+        let query = format!(
+            "SELECT {common_cols}
+             FROM push_record WHERE scope = :scope",
+            common_cols = schema::COMMON_COLS,
+        );
+        self.try_query_row(&query, &[(":scope", scope)], PushRecord::from_row, false)
+    }
+
+    fn put_record(&self, record: &PushRecord) -> Result<bool> {
+        log::debug!(
+            "adding push subscription for scope '{}', channel '{}', endpoint '{}'",
+            record.scope,
+            record.channel_id,
+            record.endpoint
+        );
+        let query = format!(
+            "INSERT OR REPLACE INTO push_record
+                 ({common_cols})
+             VALUES
+                 (:channel_id, :endpoint, :scope, :key, :ctime, :app_server_key)",
+            common_cols = schema::COMMON_COLS,
+        );
+        let affected_rows = self.execute(
+            &query,
+            &[
+                (
+                    ":channel_id",
+                    &Self::normalize_uuid(&record.channel_id) as &dyn rusqlite::ToSql,
+                ),
+                (":endpoint", &record.endpoint),
+                (":scope", &record.scope),
+                (":key", &record.key),
+                (":ctime", &record.ctime),
+                (":app_server_key", &record.app_server_key),
+            ],
+        )?;
+        Ok(affected_rows == 1)
+    }
+
+    fn delete_record(&self, chid: &str) -> Result<bool> {
+        log::debug!("deleting push subscription: {}", chid);
+        let affected_rows = self.execute(
+            "DELETE FROM push_record
+             WHERE channel_id = :chid",
+            &[(":chid", &Self::normalize_uuid(chid))],
+        )?;
+        Ok(affected_rows == 1)
+    }
+
+    fn delete_all_records(&self) -> Result<()> {
+        log::debug!("deleting all push subscriptions and some metadata");
+        self.execute("DELETE FROM push_record", [])?;
+        // Clean up the meta data records as well, since we probably want to reset the
+        // UAID and get a new secret.
+        // Note we *do not* delete the registration_id - it's possible we are deleting all
+        // subscriptions because we just provided a different registration_id.
+        self.execute_batch(
+            "DELETE FROM meta_data WHERE key='uaid';
+             DELETE FROM meta_data WHERE key='auth';
+             ",
+        )?;
+        Ok(())
+    }
+
+    fn get_channel_list(&self) -> Result<Vec<String>> {
+        self.query_rows_and_then(
+            "SELECT channel_id FROM push_record",
+            [],
+            |row| -> Result<String> { Ok(row.get(0)?) },
+        )
+    }
+
+    fn update_endpoint(&self, channel_id: &str, endpoint: &str) -> Result<bool> {
+        log::debug!("updating endpoint for '{}' to '{}'", channel_id, endpoint);
+        let affected_rows = self.execute(
+            "UPDATE push_record set endpoint = :endpoint
+             WHERE channel_id = :channel_id",
+            &[
+                (":endpoint", &endpoint as &dyn rusqlite::ToSql),
+                (":channel_id", &Self::normalize_uuid(channel_id)),
+            ],
+        )?;
+        Ok(affected_rows == 1)
+    }
+
+    // A couple of helpers to get/set "well known" meta keys.
+    fn get_uaid(&self) -> Result<Option<String>> {
+        self.get_meta("uaid")
+    }
+
+    fn set_uaid(&self, uaid: &str) -> Result<()> {
+        self.set_meta("uaid", uaid)
+    }
+
+    fn get_auth(&self) -> Result<Option<String>> {
+        self.get_meta("auth")
+    }
+
+    fn set_auth(&self, auth: &str) -> Result<()> {
+        self.set_meta("auth", auth)
+    }
+
+    fn get_registration_id(&self) -> Result<Option<String>> {
+        self.get_meta("registration_id")
+    }
+
+    fn set_registration_id(&self, registration_id: &str) -> Result<()> {
+        self.set_meta("registration_id", registration_id)
+    }
+
+    fn get_meta(&self, key: &str) -> Result<Option<String>> {
+        // Get the most recent UAID (which should be the same value across all records,
+        // but paranoia)
+        self.try_query_one(
+            "SELECT value FROM meta_data where key = :key limit 1",
+            &[(":key", &key)],
+            true,
+        )
+        .map_err(PushError::StorageSqlError)
+    }
+
+    fn set_meta(&self, key: &str, value: &str) -> Result<()> {
+        let query = "INSERT or REPLACE into meta_data (key, value) values (:k, :v)";
+        self.execute_cached(query, &[(":k", &key), (":v", &value)])?;
+        Ok(())
+    }
+
+    #[cfg(not(test))]
+    fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
+        PushDb::open(path)
+    }
+
+    #[cfg(test)]
+    fn open<P: AsRef<Path>>(_path: P) -> Result<Self> {
+        PushDb::open_in_memory()
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use crate::error::Result;
+    use crate::internal::crypto::{Crypto, Cryptography};
+
+    use super::PushDb;
+    use crate::internal::crypto::get_random_bytes;
+    use crate::internal::storage::{db::Storage, record::PushRecord};
+
+    const DUMMY_UAID: &str = "abad1dea00000000aabbccdd00000000";
+
+    fn get_db() -> Result<PushDb> {
+        env_logger::try_init().ok();
+        // NOTE: In Memory tests can sometimes produce false positives. Use the following
+        // for debugging
+        // PushDb::open("/tmp/push.sqlite");
+        PushDb::open_in_memory()
+    }
+
+    fn get_uuid() -> Result<String> {
+        Ok(get_random_bytes(16)?
+            .iter()
+            .map(|b| format!("{:02x}", b))
+            .collect::<Vec<String>>()
+            .join(""))
+    }
+
+    fn prec(chid: &str) -> PushRecord {
+        PushRecord::new(
+            chid,
+            &format!("https://example.com/update/{}", chid),
+            "https://example.com/",
+            Crypto::generate_key().expect("Couldn't generate_key"),
+        )
+        .unwrap()
+    }
+
+    #[test]
+    fn basic() -> Result<()> {
+        let db = get_db()?;
+        let chid = &get_uuid()?;
+        let rec = prec(chid);
+
+        assert!(db.get_record(chid)?.is_none());
+        db.put_record(&rec)?;
+        assert!(db.get_record(chid)?.is_some());
+        // don't fail if you've already added this record.
+        db.put_record(&rec)?;
+        // make sure that fetching the same uaid & chid returns the same record.
+        assert_eq!(db.get_record(chid)?, Some(rec.clone()));
+
+        let mut rec2 = rec.clone();
+        rec2.endpoint = format!("https://example.com/update2/{}", chid);
+        db.put_record(&rec2)?;
+        let result = db.get_record(chid)?.unwrap();
+        assert_ne!(result, rec);
+        assert_eq!(result, rec2);
+
+        let result = db.get_record_by_scope("https://example.com/")?.unwrap();
+        assert_eq!(result, rec2);
+
+        Ok(())
+    }
+
+    #[test]
+    fn delete() -> Result<()> {
+        let db = get_db()?;
+        let chid = &get_uuid()?;
+        let rec = prec(chid);
+
+        assert!(db.put_record(&rec)?);
+        assert!(db.get_record(chid)?.is_some());
+        assert!(db.delete_record(chid)?);
+        assert!(db.get_record(chid)?.is_none());
+        Ok(())
+    }
+
+    #[test]
+    fn delete_all_records() -> Result<()> {
+        let db = get_db()?;
+        let chid = &get_uuid()?;
+        let rec = prec(chid);
+        let mut rec2 = rec.clone();
+        rec2.channel_id = get_uuid()?;
+        rec2.endpoint = format!("https://example.com/update/{}", &rec2.channel_id);
+
+        assert!(db.put_record(&rec)?);
+        // save a record with different channel and endpoint, but same scope - it should overwrite
+        // the first because scopes are unique.
+        assert!(db.put_record(&rec2)?);
+        assert!(db.get_record(&rec.channel_id)?.is_none());
+        assert!(db.get_record(&rec2.channel_id)?.is_some());
+        db.delete_all_records()?;
+        assert!(db.get_record(&rec.channel_id)?.is_none());
+        assert!(db.get_record(&rec.channel_id)?.is_none());
+        assert!(db.get_uaid()?.is_none());
+        assert!(db.get_auth()?.is_none());
+        Ok(())
+    }
+
+    #[test]
+    fn meta() -> Result<()> {
+        use super::Storage;
+        let db = get_db()?;
+        let no_rec = db.get_uaid()?;
+        assert_eq!(no_rec, None);
+        db.set_uaid(DUMMY_UAID)?;
+        db.set_meta("fruit", "apple")?;
+        db.set_meta("fruit", "banana")?;
+        assert_eq!(db.get_uaid()?, Some(DUMMY_UAID.to_owned()));
+        assert_eq!(db.get_meta("fruit")?, Some("banana".to_owned()));
+        Ok(())
+    }
+
+    #[test]
+    fn dash() -> Result<()> {
+        let db = get_db()?;
+        let chid = "deadbeef-0000-0000-0000-decafbad12345678";
+
+        let rec = prec(chid);
+
+        assert!(db.put_record(&rec)?);
+        assert!(db.get_record(chid)?.is_some());
+        assert!(db.delete_record(chid)?);
+        Ok(())
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/push/internal/storage/mod.rs.html b/book/rust-docs/src/push/internal/storage/mod.rs.html new file mode 100644 index 0000000000..55f04dc5c3 --- /dev/null +++ b/book/rust-docs/src/push/internal/storage/mod.rs.html @@ -0,0 +1,47 @@ +mod.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Handles Push component storage
+//!
+//! Mainly exposes a trait, [`Storage`] and a concrete type that implements the trait, [`Store`]
+//!
+//! [`Storage`] is a trait representing the storage of records. Each record is a subscription record associated with a `channel_id`
+//!
+//! Records mainly include the autopush endpoint senders need to send their payloads to and the private key associated with the subscription
+//! The records act as both:
+//! - A cache for subscription records that are returned when senders re-subscribe to an already subscribed channel
+//! - Storage for the private keys used to decrypt push payloads
+
+mod db;
+mod record;
+mod schema;
+
+pub use self::{
+    db::{PushDb as Store, Storage},
+    record::PushRecord,
+};
+
\ No newline at end of file diff --git a/book/rust-docs/src/push/internal/storage/record.rs.html b/book/rust-docs/src/push/internal/storage/record.rs.html new file mode 100644 index 0000000000..173fd0c01a --- /dev/null +++ b/book/rust-docs/src/push/internal/storage/record.rs.html @@ -0,0 +1,121 @@ +record.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use rusqlite::Row;
+
+use crate::error::Result;
+use crate::internal::crypto::KeyV1 as Key;
+
+use types::Timestamp;
+
+pub type ChannelID = String;
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct PushRecord {
+    /// Designation label provided by the subscribing service
+    pub channel_id: ChannelID,
+
+    /// Endpoint provided from the push server
+    pub endpoint: String,
+
+    /// The receipient (service worker)'s scope
+    pub scope: String,
+
+    /// Private EC Prime256v1 key info.
+    pub key: Vec<u8>,
+
+    /// Time this subscription was created.
+    pub ctime: Timestamp,
+
+    /// VAPID public key to restrict subscription updates for only those that sign
+    /// using the private VAPID key.
+    pub app_server_key: Option<String>,
+}
+
+impl PushRecord {
+    /// Create a Push Record from the Subscription info: endpoint, encryption
+    /// keys, etc.
+    pub fn new(chid: &str, endpoint: &str, scope: &str, key: Key) -> Result<Self> {
+        Ok(Self {
+            channel_id: chid.to_owned(),
+            endpoint: endpoint.to_owned(),
+            scope: scope.to_owned(),
+            key: key.serialize()?,
+            ctime: Timestamp::now(),
+            app_server_key: None,
+        })
+    }
+
+    pub(crate) fn from_row(row: &Row<'_>) -> Result<Self> {
+        Ok(PushRecord {
+            channel_id: row.get("channel_id")?,
+            endpoint: row.get("endpoint")?,
+            scope: row.get("scope")?,
+            key: row.get("key")?,
+            ctime: row.get("ctime")?,
+            app_server_key: row.get("app_server_key")?,
+        })
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/push/internal/storage/schema.rs.html b/book/rust-docs/src/push/internal/storage/schema.rs.html new file mode 100644 index 0000000000..baadb8aeb7 --- /dev/null +++ b/book/rust-docs/src/push/internal/storage/schema.rs.html @@ -0,0 +1,287 @@ +schema.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+use rusqlite::Transaction;
+use sql_support::open_database;
+
+const CREATE_TABLE_PUSH_SQL: &str = include_str!("schema.sql");
+
+pub struct PushConnectionInitializer;
+
+impl open_database::ConnectionInitializer for PushConnectionInitializer {
+    const NAME: &'static str = "push db";
+    const END_VERSION: u32 = 3;
+
+    // This is such a simple database that we do almost nothing!
+    // * We have no foreign keys, so `PRAGMA foreign_keys = ON;` is pointless.
+    // * We have no temp tables, so `PRAGMA temp_store = 2;` is pointless.
+    // * We don't even use transactions, so `PRAGMA journal_mode=WAL;` is pointless.
+    // * We have a tiny number of different SQL statements, so
+    //   set_prepared_statement_cache_capacity is pointless.
+    // * We have no SQL functions.
+    // Kinda makes you wonder why we use a sql db at all :)
+    // So - no "prepare" and no "finish" methods.
+    fn init(&self, db: &Transaction<'_>) -> open_database::Result<()> {
+        db.execute_batch(CREATE_TABLE_PUSH_SQL)?;
+        Ok(())
+    }
+
+    fn upgrade_from(&self, db: &Transaction<'_>, version: u32) -> open_database::Result<()> {
+        match version {
+            0 => db.execute_batch(CREATE_TABLE_PUSH_SQL)?,
+            1 => db.execute_batch(CREATE_TABLE_PUSH_SQL)?,
+            2 => {
+                // We dropped the `uaid` and `native_id` columns and added a constraint that scope
+                // must not be an empty string and must be unique.
+                let sql = format!(
+                    "
+                    -- rename the old table.
+                    ALTER TABLE push_record RENAME TO push_record_old;
+                    -- create the new table with the new schema.
+                    {CREATE_TABLE_PUSH_SQL};
+                    -- move the data across.
+                    INSERT OR IGNORE INTO push_record ({COMMON_COLS})
+                    SELECT {COMMON_COLS} FROM push_record_old WHERE length(scope) > 0;
+                    -- drop the old table
+                    DROP TABLE push_record_old;",
+                    CREATE_TABLE_PUSH_SQL = CREATE_TABLE_PUSH_SQL,
+                    COMMON_COLS = COMMON_COLS,
+                );
+                db.execute_batch(&sql)?;
+            }
+            other => {
+                log::warn!(
+                    "Loaded future schema version {} (we only understand version {}). \
+                    Optimistically ",
+                    other,
+                    Self::END_VERSION
+                )
+            }
+        };
+        Ok(())
+    }
+}
+
+pub const COMMON_COLS: &str = "
+    channel_id,
+    endpoint,
+    scope,
+    key,
+    ctime,
+    app_server_key
+";
+
+#[cfg(test)]
+mod test {
+    use crate::internal::storage::db::{PushDb, Storage};
+    use rusqlite::{Connection, OpenFlags};
+    use sql_support::ConnExt;
+
+    const CREATE_V2_SCHEMA: &str = include_str!("test/schema_v2.sql");
+
+    #[test]
+    fn test_migrate_v2_v3() {
+        env_logger::try_init().ok();
+        let tempdir = tempfile::tempdir().unwrap();
+        let path = tempdir.path().join("push_v2.sql");
+
+        let conn = Connection::open_with_flags(path.clone(), OpenFlags::default()).unwrap();
+        conn.execute_batch(CREATE_V2_SCHEMA).unwrap();
+
+        // insert some stuff
+        conn.execute_batch(
+            r#"
+            INSERT INTO push_record (
+                uaid,    channel_id, endpoint, scope,  key,     ctime, app_server_key, native_id
+            ) VALUES
+                ("id-1", "cid1",     "ep-1",   "sc-1", x'1234', 1,    "ask-1",         "nid-1"),
+                -- duplicate scope, which isn't allowed in the new schema
+                ("id-2", "cid2",     "ep-2",   "sc-1", x'5678', 2,    "ask-2",         "nid-2"),
+                -- empty scope, which isn't allowed in the new schema
+                ("id-3", "cid3",     "ep-3",   "",     x'0000', 3,    "ask-3",         "nid-3")
+            ;
+            INSERT into meta_data (
+                key, value
+            ) VALUES
+                ("key-1", "value-1"),
+                ("key-2", "value-2")
+            "#,
+        )
+        .unwrap();
+
+        // reopen the database.
+        drop(conn);
+        let db = PushDb::open(path).expect("should open");
+
+        // Should only have 1 row in push_record
+        assert_eq!(
+            db.query_one::<u32>("SELECT COUNT(*) FROM push_record")
+                .unwrap(),
+            1
+        );
+        let record = db
+            .get_record("cid1")
+            .expect("should work")
+            .expect("should get a record");
+        assert_eq!(record.channel_id, "cid1");
+        assert_eq!(record.endpoint, "ep-1");
+        assert_eq!(record.scope, "sc-1");
+        assert_eq!(record.key, [0x12, 0x34]);
+        assert_eq!(record.ctime.0, 1);
+        assert_eq!(record.app_server_key.unwrap(), "ask-1");
+
+        // But both metadata ones.
+        assert_eq!(
+            db.db
+                .query_one::<u32>("SELECT COUNT(*) FROM meta_data")
+                .unwrap(),
+            2
+        );
+        assert_eq!(db.get_meta("key-1").unwrap().unwrap(), "value-1");
+        assert_eq!(db.get_meta("key-2").unwrap().unwrap(), "value-2");
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/push/lib.rs.html b/book/rust-docs/src/push/lib.rs.html new file mode 100644 index 0000000000..c2eba3ac5b --- /dev/null +++ b/book/rust-docs/src/push/lib.rs.html @@ -0,0 +1,829 @@ +lib.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#![allow(unknown_lints)]
+#![warn(rust_2018_idioms)]
+//! # Rust Push Component
+//!
+//! This component helps an application to manage [WebPush](https://developer.mozilla.org/en-US/docs/Web/API/Push_API) subscriptions,
+//! acting as an intermediary between Mozilla's [autopush service](https://autopush.readthedocs.io/en/latest/)
+//! and platform native push infrastructure such as [Firebase Cloud Messaging](https://firebase.google.com/docs/cloud-messaging) or [Amazon Device Messaging](https://developer.amazon.com/docs/adm/overview.html).
+//!
+//! ## Background Concepts
+//!
+//! ### WebPush Subscriptions
+//!
+//! A WebPush client manages a number of *subscriptions*, each of which is used to deliver push
+//! notifications to a different part of the app. For example, a web browser might manage a separate
+//! subscription for each website that has registered a [service worker](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API), and an application that includes Firefox Accounts would manage
+//! a dedicated subscription on which to receive account state updates.
+//!
+//! Each subscription is identified by a unique *channel id*, which is a randomly-generated identifier.
+//! It's the responsibility of the application to know how to map a channel id to an appropriate function
+//! in the app to receive push notifications. Subscriptions also have an associated *scope* which is something
+//! to do which service workers that your humble author doesn't really understand :-/.
+//!
+//! When a subscription is created for a channel id, we allocate *subscription info* consisting of:
+//!
+//! * An HTTP endpoint URL at which push messages can be submitted.
+//! * A cryptographic key and authentication secret with which push messages can be encrypted.
+//!
+//! This subscription info is distributed to other services that want to send push messages to
+//! the application.
+//!
+//! The HTTP endpoint is provided by Mozilla's [autopush service](https://autopush.readthedocs.io/en/latest/),
+//! and we use the [rust-ece](https://github.com/mozilla/rust-ece) to manage encryption with the cryptographic keys.
+//!
+//! Here's a helpful diagram of how the *subscription* flow works at a high level across the moving parts:
+//! ![A Sequence diagram showing how the different parts of push interact](https://mozilla.github.io/application-services/book/diagrams/Push-Component-Subscription-flow.png "Sequence diagram")
+//!
+//! ### AutoPush Bridging
+//!
+//! Our target consumer platforms each have their own proprietary push-notification infrastructure,
+//! such as [Firebase Cloud Messaging](https://firebase.google.com/docs/cloud-messaging) for Android
+//! and the [Apple Push Notification Service](https://developer.apple.com/notifications/) for iOS.
+//! Mozilla's [autopush service](https://autopush.readthedocs.io/en/latest/) provides a bridge between
+//! these different mechanisms and the WebPush standard so that they can be used with a consistent
+//! interface.
+//!
+//! This component acts a client of the [Push Service Bridge HTTP Interface](https://autopush.readthedocs.io/en/latest/http.html#push-service-bridge-http-interface).
+//!
+//! We assume two things about the consuming application:
+//! * It has registered with the autopush service and received a unique `app_id` identifying this registration.
+//! * It has registred with whatever platform-specific notification infrastructure is appropriate, and is
+//!   able to obtain a `token` corresponding to its native push notification state.
+//!
+//! On first use, this component will register itself as an *application instance* with the autopush service, providing the `app_id` and `token` and receiving a unique `uaid` ("user-agent id") to identify its
+//! connection to the server.
+//!
+//! As the application adds or removes subscriptions using the API of this component, it will:
+//! * Manage a local database of subscriptions and the corresponding cryptographic material.
+//! * Make corresponding HTTP API calls to update the state associated with its `uaid` on the autopush server.
+//!
+//! Periodically, the application should call a special `verify_connection` method to check whether
+//! the state on the autopush server matches the local state and take any corrective action if it
+//! differs.
+//!
+//! For local development and debugging, it is possible to run a local instance of the autopush
+//! bridge service; see [this google doc](https://docs.google.com/document/d/18L_g2hIj_1mncF978A_SHXN4udDQLut5P_ZHYZEwGP8) for details.
+//!
+//! ## API
+//!
+//! ## Initialization
+//!
+//! Calls are handled by the `PushManager`, which provides a handle for future calls.
+//!
+//! example:
+//! ```kotlin
+//!
+//! import mozilla.appservices.push.(PushManager, BridgeTypes)
+//!
+//! // The following are mock calls for fetching application level configuration options.
+//! // "SenderID" is the native OS push message application identifier. See Native
+//! // messaging documentation for details.
+//! val sender_id = SystemConfigurationOptions.get("SenderID")
+//!
+//! // The "bridge type" is the identifier for the native OS push message system.
+//! // (e.g. FCM for Google Firebase Cloud Messaging, ADM for Amazon Direct Messaging,
+//! // etc.)
+//! val bridge_type = BridgeTypes.FCM
+//!
+//! // The "registration_id" is the native OS push message user registration number.
+//! // Native push message registration usually happens at application start, and returns
+//! // an opaque user identifier string. See Native messaging documentation for details.
+//! val registration_id = NativeMessagingSystem.register(sender_id)
+//!
+//! val push_manager = PushManager(
+//!     sender_id,
+//!     bridge_type,
+//!     registration_id
+//! )
+//!
+//! // It is strongly encouraged that the connection is verified at least once a day.
+//! // This will ensure that the server and UA have matching information regarding
+//! // subscriptions. This call usually returns quickly, but may take longer if the
+//! // UA has a large number of subscriptions and things have fallen out of sync.
+//!
+//! for change in push_manager.verify_connection() {
+//!     // fetch the subscriber from storage using the change[0] and
+//!     // notify them with a `pushsubscriptionchange` message containing the new
+//!     // endpoint change[1]
+//! }
+//!
+//! ```
+//!
+//! ## New subscription
+//!
+//! Before messages can be delivered, a new subscription must be requested. The subscription info block contains all the information a remote subscription provider service will need to encrypt and transmit a message to this user agent.
+//!
+//! example:
+//! ```kotlin
+//!
+//! // Each new request must have a unique "channel" identifier. This channel helps
+//! // later identify recipients and aid in routing. A ChannelID is a UUID4 value.
+//! // the "scope" is the ServiceWorkerRegistration scope. This will be used
+//! // later for push notification management.
+//! val channelID = GUID.randomUUID()
+//!
+//! val subscription_info = push_manager.subscribe(channelID, endpoint_scope)
+//!
+//! // the published subscription info has the following JSON format:
+//! // {"endpoint": subscription_info.endpoint,
+//! //  "keys": {
+//! //      "auth": subscription_info.keys.auth,
+//! //      "p256dh": subscription_info.keys.p256dh
+//! //  }}
+//! ```
+//!
+//! ## End a subscription
+//!
+//! A user may decide to no longer receive a given subscription. To remove a given subscription, pass the associated channelID
+//!
+//! ```kotlin
+//! push_manager.unsubscribe(channelID)  // Terminate a single subscription
+//! ```
+//!
+//! If the user wishes to terminate all subscriptions, send and empty string for channelID
+//!
+//! ```kotlin
+//! push_manager.unsubscribe("")        // Terminate all subscriptions for a user
+//! ```
+//!
+//! If this function returns `false` the subsequent `verify_connection` may result in new channel endpoints.
+//!
+//! ## Decrypt an incoming subscription message
+//!
+//! An incoming subscription body will contain a number of metadata elements along with the body of the message. Due to platform differences, how that metadata is provided may //! vary, however the most common form is that the messages "payload" looks like.
+//!
+//! ```javascript
+//! {"chid": "...",         // ChannelID
+//!  "con": "...",          // Encoding form
+//!  "enc": "...",          // Optional encryption header
+//!  "crypto-key": "...",   // Optional crypto key header
+//!  "body": "...",         // Encrypted message body
+//! }
+//! ```
+//! These fields may be included as a sub-hash, or may be intermingled with other data fields. If you have doubts or concerns, please contact the Application Services team guidance
+//!
+//! Based on the above payload, an example call might look like:
+//!
+//! ```kotlin
+//!     val result = manager.decrypt(
+//!         channelID = payload["chid"].toString(),
+//!         body = payload["body"].toString(),
+//!         encoding = payload["con"].toString(),
+//!         salt = payload.getOrElse("enc", "").toString(),
+//!         dh = payload.getOrElse("dh", "").toString()
+//!     )
+//!     // result returns a byte array. You may need to convert to a string
+//!     return result.toString(Charset.forName("UTF-8"))
+//!```
+
+uniffi::include_scaffolding!("push");
+// All implementation detail lives in the `internal` module
+mod internal;
+use std::{collections::HashMap, sync::Mutex};
+mod error;
+
+use error_support::handle_error;
+pub use internal::config::{BridgeType, Protocol as PushHttpProtocol, PushConfiguration};
+use internal::crypto::Crypto;
+use internal::{communications::ConnectHttp, push_manager::DecryptResponse};
+
+pub use error::{ApiResult, PushApiError, PushError};
+use internal::storage::Store;
+
+/// Object representing the PushManager used to manage subscriptions
+///
+/// The `PushManager` object is the main interface provided by this crate
+/// it allow consumers to manage push subscriptions. It exposes methods that
+/// interact with the [`autopush server`](https://autopush.readthedocs.io/en/latest/)
+/// and persists state representing subscriptions.
+pub struct PushManager {
+    // We serialize all access on a mutex for thread safety
+    // TODO: this can improved by making the locking more granular
+    // and moving the mutex down to ensure `internal::PushManager`
+    // is Sync + Send
+    internal: Mutex<internal::PushManager<ConnectHttp, Crypto, Store>>,
+}
+
+impl PushManager {
+    /// Creates a new [`PushManager`] object, not subscribed to any
+    /// channels
+    ///
+    /// # Arguments
+    ///   - `config`: [`PushConfiguration`] the configuration for this instance of PushManager
+    ///
+    /// # Errors
+    /// Returns an error in the following cases:
+    ///   - PushManager is unable to open the `database_path` given
+    ///   - PushManager is unable to establish a connection to the autopush server
+    #[handle_error(PushError)]
+    pub fn new(config: PushConfiguration) -> ApiResult<Self> {
+        log::debug!(
+            "PushManager server_host: {}, http_protocol: {}",
+            config.server_host,
+            config.http_protocol
+        );
+        Ok(Self {
+            internal: Mutex::new(internal::PushManager::new(config)?),
+        })
+    }
+
+    /// Subscribes to a new channel and gets the Subscription Info block
+    ///
+    /// # Arguments
+    ///   - `channel_id` - Channel ID (UUID4) for new subscription, either pre-generated or "" and one will be created.
+    ///   - `scope` - Site scope string (defaults to "" for no site scope string).
+    ///   - `server_key` - optional VAPID public key to "lock" subscriptions (defaults to "" for no key)
+    ///
+    /// # Returns
+    /// A Subscription response that includes the following:
+    ///   - A URL that can be used to deliver push messages
+    ///   - A cryptographic key that can be used to encrypt messages
+    ///     that would then be decrypted using the [`PushManager::decrypt`] function
+    ///
+    /// # Errors
+    /// Returns an error in the following cases:
+    ///   - PushManager was unable to access its persisted storage
+    ///   - An error occurred sending a subscription request to the autopush server
+    ///   - An error occurred generating or deserializing the cryptographic keys
+    #[handle_error(PushError)]
+    pub fn subscribe(
+        &self,
+        scope: &str,
+        server_key: &Option<String>,
+    ) -> ApiResult<SubscriptionResponse> {
+        self.internal
+            .lock()
+            .unwrap()
+            .subscribe(scope, server_key.as_deref())
+    }
+
+    /// Retrieves an existing push subscription
+    ///
+    /// # Arguments
+    ///   - `scope` - Site scope string
+    ///
+    /// # Returns
+    /// A Subscription response that includes the following:
+    ///   - A URL that can be used to deliver push messages
+    ///   - A cryptographic key that can be used to encrypt messages
+    ///     that would then be decrypted using the [`PushManager::decrypt`] function
+    ///
+    /// # Errors
+    /// Returns an error in the following cases:
+    ///   - PushManager was unable to access its persisted storage
+    ///   - An error occurred generating or deserializing the cryptographic keys
+    #[handle_error(PushError)]
+    pub fn get_subscription(&self, scope: &str) -> ApiResult<Option<SubscriptionResponse>> {
+        self.internal.lock().unwrap().get_subscription(scope)
+    }
+
+    /// Unsubscribe from given channelID, ending that subscription for the user.
+    ///
+    /// # Arguments
+    ///   - `channel_id` - Channel ID (UUID) for subscription to remove
+    ///
+    /// # Returns
+    /// Returns a boolean. Boolean is False if the subscription was already
+    /// terminated in the past.
+    ///
+    /// # Errors
+    /// Returns an error in the following cases:
+    ///   - The PushManager does not contain a valid UAID
+    ///   - An error occurred sending an unsubscribe request to the autopush server
+    ///   - An error occurred accessing the PushManager's persisted storage
+    #[handle_error(PushError)]
+    pub fn unsubscribe(&self, channel_id: &str) -> ApiResult<bool> {
+        self.internal.lock().unwrap().unsubscribe(channel_id)
+    }
+
+    /// Unsubscribe all channels for the user
+    ///
+    /// # Errors
+    /// Returns an error in the following cases:
+    ///   - The PushManager does not contain a valid UAID
+    ///   - An error occurred sending an unsubscribe request to the autopush server
+    ///   - An error occurred accessing the PushManager's persisted storage
+    #[handle_error(PushError)]
+    pub fn unsubscribe_all(&self) -> ApiResult<()> {
+        self.internal.lock().unwrap().unsubscribe_all()
+    }
+
+    /// Updates the Native OS push registration ID.
+    ///
+    /// # Arguments:
+    ///   - `new_token` - the new Native OS push registration ID
+    /// # Errors
+    /// Return an error in the following cases:
+    ///   - The PushManager does not contain a valid UAID
+    ///   - An error occurred sending an update request to the autopush server
+    ///   - An error occurred accessing the PushManager's persisted storage
+    #[handle_error(PushError)]
+    pub fn update(&self, new_token: &str) -> ApiResult<()> {
+        self.internal.lock().unwrap().update(new_token)
+    }
+
+    /// Verifies the connection state
+    ///
+    /// **NOTE**: This does not resubscribe to any channels
+    /// it only returns the list of channels that the client should
+    /// re-subscribe to.
+    ///
+    /// # Arguments
+    ///   - `force_verify`: Force verification and ignore the rate limiter
+    ///
+    /// # Returns
+    /// Returns a list of [`PushSubscriptionChanged`]
+    /// indicating the channels the consumer the client should re-subscribe
+    /// to. If the list is empty, the client's connection was verified
+    /// successfully, and the client does not need to resubscribe
+    ///
+    /// # Errors
+    /// Return an error in the following cases:
+    ///   - The PushManager does not contain a valid UAID
+    ///   - An error occurred sending an channel list retrieval request to the autopush server
+    ///   - An error occurred accessing the PushManager's persisted storage
+    #[handle_error(PushError)]
+    pub fn verify_connection(&self, force_verify: bool) -> ApiResult<Vec<PushSubscriptionChanged>> {
+        self.internal
+            .lock()
+            .unwrap()
+            .verify_connection(force_verify)
+    }
+
+    /// Decrypts a raw push message.
+    ///
+    /// This accepts the content of a Push Message (from websocket or via Native Push systems).
+    /// # Arguments:
+    ///   - `channel_id` - the ChannelID (included in the envelope of the message)
+    ///   - `body` - The encrypted body of the message
+    ///   - `encoding` - The Content Encoding "enc" field of the message (defaults to "aes128gcm")
+    ///   - `salt` - The "salt" field (if present in the raw message, defaults to "")
+    ///   - `dh` - The "dh" field (if present in the raw message, defaults to "")
+    ///
+    /// # Returns
+    /// Decrypted message body as a signed byte array
+    /// they byte array is signed to allow consumers (Kotlin only at the time of this documentation)
+    /// to work easily with the message. (They can directly call `.toByteArray` on it)
+    ///
+    /// # Errors
+    /// Returns an error in the following cases:
+    ///   - The PushManager does not contain a valid UAID
+    ///   - There are no records associated with the UAID the [`PushManager`] contains
+    ///   - An error occurred while decrypting the message
+    ///   - An error occurred accessing the PushManager's persisted storage
+    #[handle_error(PushError)]
+    pub fn decrypt(&self, payload: HashMap<String, String>) -> ApiResult<DecryptResponse> {
+        self.internal.lock().unwrap().decrypt(payload)
+    }
+}
+
+/// Key Information that can be used to encrypt payloads. These are encoded as base64
+/// so will need to be decoded before they can actually be used as keys.
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
+pub struct KeyInfo {
+    pub auth: String,
+    pub p256dh: String,
+}
+/// Subscription Information, the endpoint to send push messages to and
+/// the key information that can be used to encrypt payloads
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
+pub struct SubscriptionInfo {
+    pub endpoint: String,
+    pub keys: KeyInfo,
+}
+
+/// The subscription response object returned from [`PushManager::subscribe`]
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
+pub struct SubscriptionResponse {
+    pub channel_id: String,
+    pub subscription_info: SubscriptionInfo,
+}
+
+/// An dictionary describing the push subscription that changed, the caller
+/// will receive a list of [`PushSubscriptionChanged`] when calling
+/// [`PushManager::verify_connection`], one entry for each channel that the
+/// caller should resubscribe to
+#[derive(Debug, Clone)]
+pub struct PushSubscriptionChanged {
+    pub channel_id: String,
+    pub scope: String,
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/rc_crypto/aead.rs.html b/book/rust-docs/src/rc_crypto/aead.rs.html new file mode 100644 index 0000000000..1cc131e2ce --- /dev/null +++ b/book/rust-docs/src/rc_crypto/aead.rs.html @@ -0,0 +1,635 @@ +aead.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This file contains code that was copied from the ring crate which is under
+// the ISC license, reproduced below:
+
+// Copyright 2015-2017 Brian Smith.
+
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
+// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+mod aes_cbc;
+mod aes_gcm;
+
+use crate::error::*;
+pub use aes_cbc::LEGACY_SYNC_AES_256_CBC_HMAC_SHA256;
+pub use aes_gcm::{AES_128_GCM, AES_256_GCM};
+use nss::aes;
+
+pub fn open(
+    key: &OpeningKey,
+    nonce: Nonce,
+    aad: Aad<'_>,
+    ciphertext_and_tag: &[u8],
+) -> Result<Vec<u8>> {
+    (key.algorithm().open)(&key.key, nonce, &aad, ciphertext_and_tag)
+}
+
+pub fn seal(key: &SealingKey, nonce: Nonce, aad: Aad<'_>, plaintext: &[u8]) -> Result<Vec<u8>> {
+    (key.algorithm().seal)(&key.key, nonce, &aad, plaintext)
+}
+
+/// The additional authenticated data (AAD) for an opening or sealing
+/// operation. This data is authenticated but is **not** encrypted.
+/// This is a type-safe wrapper around the raw bytes designed to encourage
+/// correct use of the API.
+#[repr(transparent)]
+pub struct Aad<'a>(&'a [u8]);
+
+impl<'a> Aad<'a> {
+    /// Construct the `Aad` by borrowing a contiguous sequence of bytes.
+    #[inline]
+    pub fn from(aad: &'a [u8]) -> Self {
+        Aad(aad)
+    }
+}
+
+impl Aad<'static> {
+    /// Construct an empty `Aad`.
+    pub fn empty() -> Self {
+        Self::from(&[])
+    }
+}
+
+/// The nonce for an opening or sealing operation.
+/// This is a type-safe wrapper around the raw bytes designed to encourage
+/// correct use of the API.
+pub struct Nonce(Vec<u8>);
+
+impl Nonce {
+    #[inline]
+    pub fn try_assume_unique_for_key(algorithm: &'static Algorithm, value: &[u8]) -> Result<Self> {
+        if value.len() != algorithm.nonce_len() {
+            return Err(ErrorKind::InternalError.into());
+        }
+        Ok(Self(value.to_vec()))
+    }
+}
+
+pub struct OpeningKey {
+    key: Key,
+}
+
+impl OpeningKey {
+    /// Create a new opening key.
+    ///
+    /// `key_bytes` must be exactly `algorithm.key_len` bytes long.
+    #[inline]
+    pub fn new(algorithm: &'static Algorithm, key_bytes: &[u8]) -> Result<Self> {
+        Ok(Self {
+            key: Key::new(algorithm, key_bytes)?,
+        })
+    }
+
+    /// The key's AEAD algorithm.
+    #[inline]
+    pub fn algorithm(&self) -> &'static Algorithm {
+        self.key.algorithm()
+    }
+}
+
+pub struct SealingKey {
+    key: Key,
+}
+
+impl SealingKey {
+    /// Create a new sealing key.
+    ///
+    /// `key_bytes` must be exactly `algorithm.key_len` bytes long.
+    #[inline]
+    pub fn new(algorithm: &'static Algorithm, key_bytes: &[u8]) -> Result<Self> {
+        Ok(Self {
+            key: Key::new(algorithm, key_bytes)?,
+        })
+    }
+
+    /// The key's AEAD algorithm.
+    #[inline]
+    pub fn algorithm(&self) -> &'static Algorithm {
+        self.key.algorithm()
+    }
+}
+
+/// `OpeningKey` and `SealingKey` are type-safety wrappers around `Key`.
+pub(crate) struct Key {
+    key_value: Vec<u8>,
+    algorithm: &'static Algorithm,
+}
+
+impl Key {
+    fn new(algorithm: &'static Algorithm, key_bytes: &[u8]) -> Result<Self> {
+        if key_bytes.len() != algorithm.key_len() {
+            return Err(ErrorKind::InternalError.into());
+        }
+        Ok(Key {
+            key_value: key_bytes.to_vec(),
+            algorithm,
+        })
+    }
+
+    #[inline]
+    pub fn algorithm(&self) -> &'static Algorithm {
+        self.algorithm
+    }
+}
+
+// An AEAD algorithm.
+#[allow(clippy::type_complexity)]
+pub struct Algorithm {
+    tag_len: usize,
+    key_len: usize,
+    nonce_len: usize,
+    open: fn(key: &Key, nonce: Nonce, aad: &Aad<'_>, ciphertext_and_tag: &[u8]) -> Result<Vec<u8>>,
+    seal: fn(key: &Key, nonce: Nonce, aad: &Aad<'_>, plaintext: &[u8]) -> Result<Vec<u8>>,
+}
+
+impl Algorithm {
+    /// The length of the key.
+    #[inline]
+    pub const fn key_len(&self) -> usize {
+        self.key_len
+    }
+
+    /// The length of a tag.
+    #[inline]
+    pub const fn tag_len(&self) -> usize {
+        self.tag_len
+    }
+
+    /// The length of the nonces.
+    #[inline]
+    pub const fn nonce_len(&self) -> usize {
+        self.nonce_len
+    }
+}
+
+pub(crate) enum Direction {
+    Opening,
+    Sealing,
+}
+
+impl Direction {
+    fn to_nss_operation(&self) -> aes::Operation {
+        match self {
+            Direction::Opening => aes::Operation::Decrypt,
+            Direction::Sealing => aes::Operation::Encrypt,
+        }
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    static ALL_ALGORITHMS: &[&Algorithm] = &[
+        &LEGACY_SYNC_AES_256_CBC_HMAC_SHA256,
+        &AES_128_GCM,
+        &AES_256_GCM,
+    ];
+    static ALL_ALGORITHMS_THAT_SUPPORT_AAD: &[&Algorithm] = &[&AES_128_GCM, &AES_256_GCM];
+
+    #[test]
+    fn test_roundtrip() {
+        for algorithm in ALL_ALGORITHMS {
+            let mut cleartext_bytes = vec![0u8; 127];
+            crate::rand::fill(&mut cleartext_bytes).unwrap();
+
+            let mut key_bytes = vec![0u8; algorithm.key_len()];
+            crate::rand::fill(&mut key_bytes).unwrap();
+
+            let nonce_bytes = vec![0u8; algorithm.nonce_len()];
+
+            let key = SealingKey::new(algorithm, &key_bytes).unwrap();
+            let nonce = Nonce::try_assume_unique_for_key(algorithm, &nonce_bytes).unwrap();
+            let ciphertext_bytes = seal(&key, nonce, Aad::empty(), &cleartext_bytes).unwrap();
+
+            let key = OpeningKey::new(algorithm, &key_bytes).unwrap();
+            let nonce = Nonce::try_assume_unique_for_key(algorithm, &nonce_bytes).unwrap();
+            let roundtriped_cleartext_bytes =
+                open(&key, nonce, Aad::empty(), &ciphertext_bytes).unwrap();
+            assert_eq!(roundtriped_cleartext_bytes, cleartext_bytes);
+        }
+    }
+
+    #[test]
+    fn test_cant_open_with_mismatched_key() {
+        let mut key_bytes_1 = vec![0u8; AES_256_GCM.key_len()];
+        crate::rand::fill(&mut key_bytes_1).unwrap();
+
+        let mut key_bytes_2 = vec![0u8; AES_128_GCM.key_len()];
+        crate::rand::fill(&mut key_bytes_2).unwrap();
+
+        let nonce_bytes = vec![0u8; AES_256_GCM.nonce_len()];
+
+        let key = SealingKey::new(&AES_256_GCM, &key_bytes_1).unwrap();
+        let nonce = Nonce::try_assume_unique_for_key(&AES_256_GCM, &nonce_bytes).unwrap();
+        let ciphertext_bytes = seal(&key, nonce, Aad::empty(), &[0u8; 0]).unwrap();
+
+        let key = OpeningKey::new(&AES_128_GCM, &key_bytes_2).unwrap();
+        let nonce = Nonce::try_assume_unique_for_key(&AES_128_GCM, &nonce_bytes).unwrap();
+        let result = open(&key, nonce, Aad::empty(), &ciphertext_bytes);
+        assert!(result.is_err());
+    }
+
+    #[test]
+    fn test_cant_open_modified_ciphertext() {
+        for algorithm in ALL_ALGORITHMS {
+            let mut key_bytes = vec![0u8; algorithm.key_len()];
+            crate::rand::fill(&mut key_bytes).unwrap();
+
+            let nonce_bytes = vec![0u8; algorithm.nonce_len()];
+
+            let key = SealingKey::new(algorithm, &key_bytes).unwrap();
+            let nonce = Nonce::try_assume_unique_for_key(algorithm, &nonce_bytes).unwrap();
+            let ciphertext_bytes = seal(&key, nonce, Aad::empty(), &[0u8; 0]).unwrap();
+
+            for i in 0..ciphertext_bytes.len() {
+                let mut modified_ciphertext = ciphertext_bytes.clone();
+                modified_ciphertext[i] = modified_ciphertext[i].wrapping_add(1);
+
+                let key = OpeningKey::new(algorithm, &key_bytes).unwrap();
+                let nonce = Nonce::try_assume_unique_for_key(algorithm, &nonce_bytes).unwrap();
+                let result = open(&key, nonce, Aad::empty(), &modified_ciphertext);
+                assert!(result.is_err());
+            }
+        }
+    }
+
+    #[test]
+    fn test_cant_open_with_incorrect_associated_data() {
+        for algorithm in ALL_ALGORITHMS_THAT_SUPPORT_AAD {
+            let mut key_bytes = vec![0u8; algorithm.key_len()];
+            crate::rand::fill(&mut key_bytes).unwrap();
+
+            let nonce_bytes = vec![0u8; algorithm.nonce_len()];
+
+            let key = SealingKey::new(algorithm, &key_bytes).unwrap();
+            let nonce = Nonce::try_assume_unique_for_key(algorithm, &nonce_bytes).unwrap();
+            let ciphertext_bytes = seal(&key, nonce, Aad::from(&[1, 2, 3]), &[0u8; 0]).unwrap();
+
+            let key = OpeningKey::new(algorithm, &key_bytes).unwrap();
+            let nonce = Nonce::try_assume_unique_for_key(algorithm, &nonce_bytes).unwrap();
+            let result = open(&key, nonce, Aad::empty(), &ciphertext_bytes);
+            assert!(result.is_err());
+
+            let nonce = Nonce::try_assume_unique_for_key(&AES_256_GCM, &nonce_bytes).unwrap();
+            let result = open(&key, nonce, Aad::from(&[2, 3, 4]), &ciphertext_bytes);
+            assert!(result.is_err());
+        }
+    }
+
+    #[test]
+    fn test_cant_use_incorrectly_sized_key() {
+        for algorithm in ALL_ALGORITHMS {
+            let key_bytes = vec![0u8; algorithm.key_len() - 1];
+            let result = Key::new(algorithm, &key_bytes);
+            assert!(result.is_err());
+
+            let key_bytes = vec![0u8; algorithm.key_len() + 1];
+            let result = Key::new(algorithm, &key_bytes);
+            assert!(result.is_err());
+        }
+    }
+
+    #[test]
+    fn test_cant_use_incorrectly_sized_nonce() {
+        for algorithm in ALL_ALGORITHMS {
+            let nonce_bytes = vec![0u8; algorithm.nonce_len() - 1];
+            let result = Nonce::try_assume_unique_for_key(algorithm, &nonce_bytes);
+            assert!(result.is_err());
+
+            let nonce_bytes = vec![0u8; algorithm.nonce_len() + 1];
+            let result = Nonce::try_assume_unique_for_key(algorithm, &nonce_bytes);
+            assert!(result.is_err());
+        }
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/rc_crypto/aead/aes_cbc.rs.html b/book/rust-docs/src/rc_crypto/aead/aes_cbc.rs.html new file mode 100644 index 0000000000..c971787935 --- /dev/null +++ b/book/rust-docs/src/rc_crypto/aead/aes_cbc.rs.html @@ -0,0 +1,521 @@ +aes_cbc.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This file contains code that was copied from the ring crate which is under
+// the ISC license, reproduced below:
+
+// Copyright 2015-2017 Brian Smith.
+
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
+// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+use crate::{aead, digest, error::*, hmac};
+use base64::{engine::general_purpose::STANDARD, Engine};
+use nss::aes;
+
+/// AES-256 in CBC mode with HMAC-SHA256 tags and 128 bit nonces.
+/// This is a Sync 1.5 specific encryption scheme, do not use for new
+/// applications, there are better options out there nowadays.
+/// Important note: The HMAC tag verification is done against the
+/// base64 representation of the ciphertext.
+/// More details here: https://mozilla-services.readthedocs.io/en/latest/sync/storageformat5.html#record-encryption
+pub static LEGACY_SYNC_AES_256_CBC_HMAC_SHA256: aead::Algorithm = aead::Algorithm {
+    key_len: 64, // 32 bytes for the AES key, 32 bytes for the HMAC key.
+    tag_len: 32,
+    nonce_len: 128 / 8,
+    open,
+    seal,
+};
+
+// Warning: This does not run in constant time (which is fine for our usage).
+pub(crate) fn open(
+    key: &aead::Key,
+    nonce: aead::Nonce,
+    aad: &aead::Aad<'_>,
+    ciphertext_and_tag: &[u8],
+) -> Result<Vec<u8>> {
+    let ciphertext_len = ciphertext_and_tag
+        .len()
+        .checked_sub(key.algorithm().tag_len())
+        .ok_or(ErrorKind::InternalError)?;
+    let (ciphertext, hmac_signature) = ciphertext_and_tag.split_at(ciphertext_len);
+    let (aes_key, hmac_key_bytes) = extract_keys(key);
+    // 1. Tag (HMAC signature) check.
+    let hmac_key = hmac::VerificationKey::new(&digest::SHA256, hmac_key_bytes);
+    hmac::verify(
+        &hmac_key,
+        STANDARD.encode(ciphertext).as_bytes(),
+        hmac_signature,
+    )?;
+    // 2. Decryption.
+    aes_cbc(aes_key, nonce, aad, ciphertext, aead::Direction::Opening)
+}
+
+pub(crate) fn seal(
+    key: &aead::Key,
+    nonce: aead::Nonce,
+    aad: &aead::Aad<'_>,
+    plaintext: &[u8],
+) -> Result<Vec<u8>> {
+    let (aes_key, hmac_key_bytes) = extract_keys(key);
+    // 1. Encryption.
+    let mut ciphertext = aes_cbc(aes_key, nonce, aad, plaintext, aead::Direction::Sealing)?;
+    // 2. Tag (HMAC signature) generation.
+    let hmac_key = hmac::SigningKey::new(&digest::SHA256, hmac_key_bytes);
+    let signature = hmac::sign(&hmac_key, STANDARD.encode(&ciphertext).as_bytes())?;
+    ciphertext.extend(&signature.0.value);
+    Ok(ciphertext)
+}
+
+fn extract_keys(key: &aead::Key) -> (&[u8], &[u8]) {
+    // Always split at 32 since we only do AES 256 w/ HMAC 256 tag.
+    let (aes_key, hmac_key_bytes) = key.key_value.split_at(32);
+    (aes_key, hmac_key_bytes)
+}
+
+fn aes_cbc(
+    aes_key: &[u8],
+    nonce: aead::Nonce,
+    aad: &aead::Aad<'_>,
+    data: &[u8],
+    direction: aead::Direction,
+) -> Result<Vec<u8>> {
+    if !aad.0.is_empty() {
+        // CBC mode does not support AAD.
+        return Err(ErrorKind::InternalError.into());
+    }
+    Ok(aes::aes_cbc_crypt(
+        aes_key,
+        &nonce.0,
+        data,
+        direction.to_nss_operation(),
+    )?)
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    // These are the test vectors used by the sync15 crate, but concatenated
+    // together rather than split into individual pieces.
+    const IV_B64: &str = "GX8L37AAb2FZJMzIoXlX8w==";
+
+    const KEY_B64: &str = "9K/wLdXdw+nrTtXo4ZpECyHFNr4d7aYHqeg3KW9+m6Qwye0R+62At\
+                           NzwWVMtAWazz/Ew+YKV2o+Wr9BBcSPHvQ==";
+
+    const CIPHERTEXT_AND_TAG_B64: &str =
+        "NMsdnRulLwQsVcwxKW9XwaUe7ouJk5Wn80QhbD80l0HEcZGCynh45qIbeYBik0lgcHbKm\
+         lIxTJNwU+OeqipN+/j7MqhjKOGIlvbpiPQQLC6/ffF2vbzL0nzMUuSyvaQzyGGkSYM2xU\
+         Ft06aNivoQTvU2GgGmUK6MvadoY38hhW2LCMkoZcNfgCqJ26lO1O0sEO6zHsk3IVz6vsK\
+         iJ2Hq6VCo7hu123wNegmujHWQSGyf8JeudZjKzfi0OFRRvvm4QAKyBWf0MgrW1F8SFDnV\
+         fkq8amCB7NhdwhgLWbN+21NitNwWYknoEWe1m6hmGZDgDT32uxzWxCV8QqqrpH/ZggViE\
+         r9uMgoy4lYaWqP7G5WKvvechc62aqnsNEYhH26A5QgzmlNyvB+KPFvPsYzxDnSCjOoRSL\
+         x7GG86wT59QZyx5sGKww3rcCNrwNZaRvek3OO4sOAs+SGCuRTjr6XuvA==";
+
+    const CLEARTEXT_B64: &str =
+        "eyJpZCI6IjVxUnNnWFdSSlpYciIsImhpc3RVcmkiOiJmaWxlOi8vL1VzZXJzL2phc29u\
+         L0xpYnJhcnkvQXBwbGljYXRpb24lMjBTdXBwb3J0L0ZpcmVmb3gvUHJvZmlsZXMva3Nn\
+         ZDd3cGsuTG9jYWxTeW5jU2VydmVyL3dlYXZlL2xvZ3MvIiwidGl0bGUiOiJJbmRleCBv\
+         ZiBmaWxlOi8vL1VzZXJzL2phc29uL0xpYnJhcnkvQXBwbGljYXRpb24gU3VwcG9ydC9G\
+         aXJlZm94L1Byb2ZpbGVzL2tzZ2Q3d3BrLkxvY2FsU3luY1NlcnZlci93ZWF2ZS9sb2dz\
+         LyIsInZpc2l0cyI6W3siZGF0ZSI6MTMxOTE0OTAxMjM3MjQyNSwidHlwZSI6MX1dfQ==";
+
+    #[test]
+    fn test_decrypt() {
+        let key_bytes = STANDARD.decode(KEY_B64).unwrap();
+        let key = aead::Key::new(&LEGACY_SYNC_AES_256_CBC_HMAC_SHA256, &key_bytes).unwrap();
+        let ciphertext_and_tag = STANDARD.decode(CIPHERTEXT_AND_TAG_B64).unwrap();
+
+        let iv = STANDARD.decode(IV_B64).unwrap();
+        let nonce =
+            aead::Nonce::try_assume_unique_for_key(&LEGACY_SYNC_AES_256_CBC_HMAC_SHA256, &iv)
+                .unwrap();
+        let cleartext_bytes = open(&key, nonce, &aead::Aad::empty(), &ciphertext_and_tag).unwrap();
+
+        let expected_cleartext_bytes = STANDARD.decode(CLEARTEXT_B64).unwrap();
+        assert_eq!(&expected_cleartext_bytes, &cleartext_bytes);
+    }
+
+    #[test]
+    fn test_encrypt() {
+        let key_bytes = STANDARD.decode(KEY_B64).unwrap();
+        let key = aead::Key::new(&LEGACY_SYNC_AES_256_CBC_HMAC_SHA256, &key_bytes).unwrap();
+        let cleartext = STANDARD.decode(CLEARTEXT_B64).unwrap();
+
+        let iv = STANDARD.decode(IV_B64).unwrap();
+        let nonce =
+            aead::Nonce::try_assume_unique_for_key(&LEGACY_SYNC_AES_256_CBC_HMAC_SHA256, &iv)
+                .unwrap();
+        let ciphertext_bytes = seal(&key, nonce, &aead::Aad::empty(), &cleartext).unwrap();
+
+        let expected_ciphertext_bytes = STANDARD.decode(CIPHERTEXT_AND_TAG_B64).unwrap();
+        assert_eq!(&expected_ciphertext_bytes, &ciphertext_bytes);
+    }
+
+    #[test]
+    fn test_roundtrip() {
+        let key_bytes = STANDARD.decode(KEY_B64).unwrap();
+        let key = aead::Key::new(&LEGACY_SYNC_AES_256_CBC_HMAC_SHA256, &key_bytes).unwrap();
+        let cleartext = STANDARD.decode(CLEARTEXT_B64).unwrap();
+
+        let iv = STANDARD.decode(IV_B64).unwrap();
+        let nonce =
+            aead::Nonce::try_assume_unique_for_key(&LEGACY_SYNC_AES_256_CBC_HMAC_SHA256, &iv)
+                .unwrap();
+        let ciphertext_bytes = seal(&key, nonce, &aead::Aad::empty(), &cleartext).unwrap();
+        let nonce =
+            aead::Nonce::try_assume_unique_for_key(&LEGACY_SYNC_AES_256_CBC_HMAC_SHA256, &iv)
+                .unwrap();
+        let roundtriped_cleartext_bytes =
+            open(&key, nonce, &aead::Aad::empty(), &ciphertext_bytes).unwrap();
+        assert_eq!(roundtriped_cleartext_bytes, cleartext);
+    }
+
+    #[test]
+    fn test_decrypt_fails_with_wrong_aes_key() {
+        let mut key_bytes = STANDARD.decode(KEY_B64).unwrap();
+        key_bytes[1] = b'X';
+
+        let key = aead::Key::new(&LEGACY_SYNC_AES_256_CBC_HMAC_SHA256, &key_bytes).unwrap();
+        let ciphertext_and_tag = STANDARD.decode(CIPHERTEXT_AND_TAG_B64).unwrap();
+        let iv = STANDARD.decode(IV_B64).unwrap();
+        let nonce =
+            aead::Nonce::try_assume_unique_for_key(&LEGACY_SYNC_AES_256_CBC_HMAC_SHA256, &iv)
+                .unwrap();
+
+        let err = open(&key, nonce, &aead::Aad::empty(), &ciphertext_and_tag).unwrap_err();
+        match err.kind() {
+            ErrorKind::NSSError(_) | ErrorKind::InternalError => {}
+            _ => panic!("unexpected error kind"),
+        }
+    }
+
+    #[test]
+    fn test_decrypt_fails_with_wrong_hmac_key() {
+        let mut key_bytes = STANDARD.decode(KEY_B64).unwrap();
+        key_bytes[60] = b'X';
+
+        let key = aead::Key::new(&LEGACY_SYNC_AES_256_CBC_HMAC_SHA256, &key_bytes).unwrap();
+        let ciphertext_and_tag = STANDARD.decode(CIPHERTEXT_AND_TAG_B64).unwrap();
+        let iv = STANDARD.decode(IV_B64).unwrap();
+        let nonce =
+            aead::Nonce::try_assume_unique_for_key(&LEGACY_SYNC_AES_256_CBC_HMAC_SHA256, &iv)
+                .unwrap();
+
+        let err = open(&key, nonce, &aead::Aad::empty(), &ciphertext_and_tag).unwrap_err();
+        match err.kind() {
+            ErrorKind::InternalError => {}
+            _ => panic!("unexpected error kind"),
+        }
+    }
+
+    #[test]
+    fn test_decrypt_fails_with_modified_ciphertext() {
+        let key_bytes = STANDARD.decode(KEY_B64).unwrap();
+        let key = aead::Key::new(&LEGACY_SYNC_AES_256_CBC_HMAC_SHA256, &key_bytes).unwrap();
+        let iv = STANDARD.decode(IV_B64).unwrap();
+        let nonce =
+            aead::Nonce::try_assume_unique_for_key(&LEGACY_SYNC_AES_256_CBC_HMAC_SHA256, &iv)
+                .unwrap();
+
+        let mut ciphertext_and_tag = STANDARD.decode(CIPHERTEXT_AND_TAG_B64).unwrap();
+        ciphertext_and_tag[4] = b'Z';
+
+        let err = open(&key, nonce, &aead::Aad::empty(), &ciphertext_and_tag).unwrap_err();
+        match err.kind() {
+            ErrorKind::InternalError => {}
+            _ => panic!("unexpected error kind"),
+        }
+    }
+
+    #[test]
+    fn test_decrypt_fails_with_modified_tag() {
+        let key_bytes = STANDARD.decode(KEY_B64).unwrap();
+        let key = aead::Key::new(&LEGACY_SYNC_AES_256_CBC_HMAC_SHA256, &key_bytes).unwrap();
+        let iv = STANDARD.decode(IV_B64).unwrap();
+        let nonce =
+            aead::Nonce::try_assume_unique_for_key(&LEGACY_SYNC_AES_256_CBC_HMAC_SHA256, &iv)
+                .unwrap();
+
+        let mut ciphertext_and_tag = STANDARD.decode(CIPHERTEXT_AND_TAG_B64).unwrap();
+        let end = ciphertext_and_tag.len();
+        ciphertext_and_tag[end - 4] = b'Z';
+
+        let err = open(&key, nonce, &aead::Aad::empty(), &ciphertext_and_tag).unwrap_err();
+        match err.kind() {
+            ErrorKind::InternalError => {}
+            _ => panic!("unexpected error kind"),
+        }
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/rc_crypto/aead/aes_gcm.rs.html b/book/rust-docs/src/rc_crypto/aead/aes_gcm.rs.html new file mode 100644 index 0000000000..353ee94020 --- /dev/null +++ b/book/rust-docs/src/rc_crypto/aead/aes_gcm.rs.html @@ -0,0 +1,293 @@ +aes_gcm.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This file contains code that was copied from the ring crate which is under
+// the ISC license, reproduced below:
+
+// Copyright 2015-2017 Brian Smith.
+
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
+// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+use crate::{aead, error::*};
+use nss::aes;
+
+/// AES-128 in GCM mode with 128-bit tags and 96 bit nonces.
+pub static AES_128_GCM: aead::Algorithm = aead::Algorithm {
+    key_len: 16,
+    tag_len: 16,
+    nonce_len: 96 / 8,
+    open,
+    seal,
+};
+
+/// AES-256 in GCM mode with 128-bit tags and 96 bit nonces.
+pub static AES_256_GCM: aead::Algorithm = aead::Algorithm {
+    key_len: 32,
+    tag_len: 16,
+    nonce_len: 96 / 8,
+    open,
+    seal,
+};
+
+pub(crate) fn open(
+    key: &aead::Key,
+    nonce: aead::Nonce,
+    aad: &aead::Aad<'_>,
+    ciphertext_and_tag: &[u8],
+) -> Result<Vec<u8>> {
+    aes_gcm(
+        key,
+        nonce,
+        aad,
+        ciphertext_and_tag,
+        aead::Direction::Opening,
+    )
+}
+
+pub(crate) fn seal(
+    key: &aead::Key,
+    nonce: aead::Nonce,
+    aad: &aead::Aad<'_>,
+    plaintext: &[u8],
+) -> Result<Vec<u8>> {
+    aes_gcm(key, nonce, aad, plaintext, aead::Direction::Sealing)
+}
+
+fn aes_gcm(
+    key: &aead::Key,
+    nonce: aead::Nonce,
+    aad: &aead::Aad<'_>,
+    data: &[u8],
+    direction: aead::Direction,
+) -> Result<Vec<u8>> {
+    Ok(aes::aes_gcm_crypt(
+        &key.key_value,
+        &nonce.0,
+        aad.0,
+        data,
+        direction.to_nss_operation(),
+    )?)
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    // Test vector from the AES-GCM spec.
+    const NONCE_HEX: &str = "cafebabefacedbaddecaf888";
+    const KEY_HEX: &str = "feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308";
+    const AAD_HEX: &str = "feedfacedeadbeeffeedfacedeadbeefabaddad2";
+    const TAG_HEX: &str = "76fc6ece0f4e1768cddf8853bb2d551b";
+    const CIPHERTEXT_HEX: &str =
+        "522dc1f099567d07f47f37a32a84427d643a8cdcbfe5c0c97598a2bd2555d1aa8cb08e48590dbb3da7b08b1056828838c5f61e6393ba7a0abcc9f662";
+    const CLEARTEXT_HEX: &str =
+        "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39";
+
+    #[test]
+    fn test_decrypt() {
+        let key_bytes = hex::decode(KEY_HEX).unwrap();
+        let key = aead::Key::new(&AES_256_GCM, &key_bytes).unwrap();
+        let mut ciphertext_and_tag = hex::decode(CIPHERTEXT_HEX).unwrap();
+        let tag = hex::decode(TAG_HEX).unwrap();
+        ciphertext_and_tag.extend(&tag);
+
+        let iv = hex::decode(NONCE_HEX).unwrap();
+        let nonce = aead::Nonce::try_assume_unique_for_key(&AES_256_GCM, &iv).unwrap();
+        let aad_bytes = hex::decode(AAD_HEX).unwrap();
+        let aad = aead::Aad::from(&aad_bytes);
+        let cleartext_bytes = open(&key, nonce, &aad, &ciphertext_and_tag).unwrap();
+        let encoded_cleartext = hex::encode(cleartext_bytes);
+        assert_eq!(&CLEARTEXT_HEX, &encoded_cleartext);
+    }
+
+    #[test]
+    fn test_encrypt() {
+        let key_bytes = hex::decode(KEY_HEX).unwrap();
+        let key = aead::Key::new(&AES_256_GCM, &key_bytes).unwrap();
+        let cleartext = hex::decode(CLEARTEXT_HEX).unwrap();
+
+        let iv = hex::decode(NONCE_HEX).unwrap();
+        let nonce = aead::Nonce::try_assume_unique_for_key(&AES_256_GCM, &iv).unwrap();
+        let aad_bytes = hex::decode(AAD_HEX).unwrap();
+        let aad = aead::Aad::from(&aad_bytes);
+        let ciphertext_bytes = seal(&key, nonce, &aad, &cleartext).unwrap();
+
+        let expected_tag = hex::decode(TAG_HEX).unwrap();
+        let mut expected_ciphertext = hex::decode(CIPHERTEXT_HEX).unwrap();
+        expected_ciphertext.extend(&expected_tag);
+        assert_eq!(&expected_ciphertext, &ciphertext_bytes);
+    }
+
+    #[test]
+    fn test_roundtrip() {
+        let key_bytes = hex::decode(KEY_HEX).unwrap();
+        let key = aead::Key::new(&AES_256_GCM, &key_bytes).unwrap();
+        let cleartext = hex::decode(CLEARTEXT_HEX).unwrap();
+
+        let iv = hex::decode(NONCE_HEX).unwrap();
+        let nonce = aead::Nonce::try_assume_unique_for_key(&AES_256_GCM, &iv).unwrap();
+        let ciphertext_bytes = seal(&key, nonce, &aead::Aad::empty(), &cleartext).unwrap();
+        let nonce = aead::Nonce::try_assume_unique_for_key(&AES_256_GCM, &iv).unwrap();
+        let roundtriped_cleartext_bytes =
+            open(&key, nonce, &aead::Aad::empty(), &ciphertext_bytes).unwrap();
+        assert_eq!(roundtriped_cleartext_bytes, cleartext);
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/rc_crypto/agreement.rs.html b/book/rust-docs/src/rc_crypto/agreement.rs.html new file mode 100644 index 0000000000..ccd624c191 --- /dev/null +++ b/book/rust-docs/src/rc_crypto/agreement.rs.html @@ -0,0 +1,821 @@ +agreement.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This file contains code that was copied from the ring crate which is under
+// the ISC license, reproduced below:
+
+// Copyright 2015-2017 Brian Smith.
+
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
+// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+use crate::error::*;
+use core::marker::PhantomData;
+
+pub use ec::{Curve, EcKey};
+use nss::{ec, ecdh};
+
+pub type EphemeralKeyPair = KeyPair<Ephemeral>;
+
+/// A key agreement algorithm.
+#[derive(PartialEq, Eq)]
+pub struct Algorithm {
+    pub(crate) curve_id: ec::Curve,
+}
+
+pub static ECDH_P256: Algorithm = Algorithm {
+    curve_id: ec::Curve::P256,
+};
+
+pub static ECDH_P384: Algorithm = Algorithm {
+    curve_id: ec::Curve::P384,
+};
+
+/// How many times the key may be used.
+pub trait Lifetime {}
+
+/// The key may be used at most once.
+pub struct Ephemeral {}
+impl Lifetime for Ephemeral {}
+
+/// The key may be used more than once.
+pub struct Static {}
+impl Lifetime for Static {}
+
+/// A key pair for key agreement.
+pub struct KeyPair<U: Lifetime> {
+    private_key: PrivateKey<U>,
+    public_key: PublicKey,
+}
+
+impl<U: Lifetime> KeyPair<U> {
+    /// Generate a new key pair for the given algorithm.
+    pub fn generate(alg: &'static Algorithm) -> Result<Self> {
+        let (prv_key, pub_key) = ec::generate_keypair(alg.curve_id)?;
+        Ok(Self {
+            private_key: PrivateKey {
+                alg,
+                wrapped: prv_key,
+                usage: PhantomData,
+            },
+            public_key: PublicKey {
+                alg,
+                wrapped: pub_key,
+            },
+        })
+    }
+
+    pub fn from_private_key(private_key: PrivateKey<U>) -> Result<Self> {
+        let public_key = private_key
+            .compute_public_key()
+            .map_err(|_| ErrorKind::InternalError)?;
+        Ok(Self {
+            private_key,
+            public_key,
+        })
+    }
+
+    /// The private key.
+    pub fn private_key(&self) -> &PrivateKey<U> {
+        &self.private_key
+    }
+
+    /// The public key.
+    pub fn public_key(&self) -> &PublicKey {
+        &self.public_key
+    }
+
+    /// Split the key pair apart.
+    pub fn split(self) -> (PrivateKey<U>, PublicKey) {
+        (self.private_key, self.public_key)
+    }
+}
+
+impl KeyPair<Static> {
+    pub fn from(private_key: PrivateKey<Static>) -> Result<Self> {
+        Self::from_private_key(private_key)
+    }
+}
+
+/// A public key for key agreement.
+pub struct PublicKey {
+    wrapped: ec::PublicKey,
+    alg: &'static Algorithm,
+}
+
+impl PublicKey {
+    #[inline]
+    pub fn to_bytes(&self) -> Result<Vec<u8>> {
+        Ok(self.wrapped.to_bytes()?)
+    }
+
+    #[inline]
+    pub fn algorithm(&self) -> &'static Algorithm {
+        self.alg
+    }
+}
+
+/// An unparsed public key for key agreement.
+pub struct UnparsedPublicKey<'a> {
+    alg: &'static Algorithm,
+    bytes: &'a [u8],
+}
+
+impl<'a> UnparsedPublicKey<'a> {
+    pub fn new(algorithm: &'static Algorithm, bytes: &'a [u8]) -> Self {
+        Self {
+            alg: algorithm,
+            bytes,
+        }
+    }
+
+    pub fn algorithm(&self) -> &'static Algorithm {
+        self.alg
+    }
+
+    pub fn bytes(&self) -> &'a [u8] {
+        self.bytes
+    }
+}
+
+/// A private key for key agreement.
+pub struct PrivateKey<U: Lifetime> {
+    wrapped: ec::PrivateKey,
+    alg: &'static Algorithm,
+    usage: PhantomData<U>,
+}
+
+impl<U: Lifetime> PrivateKey<U> {
+    #[inline]
+    pub fn algorithm(&self) -> &'static Algorithm {
+        self.alg
+    }
+
+    pub fn compute_public_key(&self) -> Result<PublicKey> {
+        let pub_key = self.wrapped.convert_to_public_key()?;
+        Ok(PublicKey {
+            wrapped: pub_key,
+            alg: self.alg,
+        })
+    }
+
+    /// Ephemeral agreement.
+    /// This consumes `self`, ensuring that the private key can
+    /// only be used for a single agreement operation.
+    pub fn agree(self, peer_public_key: &UnparsedPublicKey<'_>) -> Result<InputKeyMaterial> {
+        agree_(&self.wrapped, self.alg, peer_public_key)
+    }
+}
+
+impl PrivateKey<Static> {
+    /// Static agreement.
+    /// This borrows `self`, allowing the private key to
+    /// be used for a multiple agreement operations.
+    pub fn agree_static(
+        &self,
+        peer_public_key: &UnparsedPublicKey<'_>,
+    ) -> Result<InputKeyMaterial> {
+        agree_(&self.wrapped, self.alg, peer_public_key)
+    }
+
+    pub fn import(ec_key: &EcKey) -> Result<Self> {
+        // XXX: we should just let ec::PrivateKey own alg.
+        let alg = match ec_key.curve() {
+            Curve::P256 => &ECDH_P256,
+            Curve::P384 => &ECDH_P384,
+        };
+        let private_key = ec::PrivateKey::import(ec_key)?;
+        Ok(Self {
+            wrapped: private_key,
+            alg,
+            usage: PhantomData,
+        })
+    }
+
+    pub fn export(&self) -> Result<EcKey> {
+        Ok(self.wrapped.export()?)
+    }
+
+    /// The whole point of having `Ephemeral` and `Static` lifetimes is to use the type
+    /// system to avoid re-using the same ephemeral key. However for tests we might need
+    /// to create a "static" ephemeral key.
+    pub fn _tests_only_dangerously_convert_to_ephemeral(self) -> PrivateKey<Ephemeral> {
+        PrivateKey::<Ephemeral> {
+            wrapped: self.wrapped,
+            alg: self.alg,
+            usage: PhantomData,
+        }
+    }
+}
+
+fn agree_(
+    my_private_key: &ec::PrivateKey,
+    my_alg: &Algorithm,
+    peer_public_key: &UnparsedPublicKey<'_>,
+) -> Result<InputKeyMaterial> {
+    let alg = &my_alg;
+    if peer_public_key.algorithm() != *alg {
+        return Err(ErrorKind::InternalError.into());
+    }
+    let pub_key = ec::PublicKey::from_bytes(my_private_key.curve(), peer_public_key.bytes())?;
+    let value = ecdh::ecdh_agreement(my_private_key, &pub_key)?;
+    Ok(InputKeyMaterial { value })
+}
+
+/// The result of a key agreement operation, to be fed into a KDF.
+#[must_use]
+pub struct InputKeyMaterial {
+    value: Vec<u8>,
+}
+
+impl InputKeyMaterial {
+    /// Calls `kdf` with the raw key material and then returns what `kdf`
+    /// returns, consuming `Self` so that the key material can only be used
+    /// once.
+    pub fn derive<F, R>(self, kdf: F) -> R
+    where
+        F: FnOnce(&[u8]) -> R,
+    {
+        kdf(&self.value)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
+
+    // Test vectors copied from:
+    // https://chromium.googlesource.com/chromium/src/+/56f1232/components/test/data/webcrypto/ecdh.json#5
+
+    const PUB_KEY_1_B64: &str =
+        "BLunVoWkR67xRdAohVblFBWn1Oosb3kH_baxw1yfIYFfthSm4LIY35vDD-5LE454eB7TShn919DVVGZ_7tWdjTE";
+    const PRIV_KEY_1_JWK_D: &str = "CQ8uF_-zB1NftLO6ytwKM3Cnuol64PQw5qOuCzQJeFU";
+    const PRIV_KEY_1_JWK_X: &str = "u6dWhaRHrvFF0CiFVuUUFafU6ixveQf9trHDXJ8hgV8";
+    const PRIV_KEY_1_JWK_Y: &str = "thSm4LIY35vDD-5LE454eB7TShn919DVVGZ_7tWdjTE";
+
+    const PRIV_KEY_2_JWK_D: &str = "uN2YSQvxuxhQQ9Y1XXjYi1vr2ZTdzuoDX18PYu4LU-0";
+    const PRIV_KEY_2_JWK_X: &str = "S2S3tjygMB0DkM-N9jYUgGLt_9_H6km5P9V6V_KS4_4";
+    const PRIV_KEY_2_JWK_Y: &str = "03j8Tyqgrc4R4FAUV2C7-im96yMmfmO_5Om6Kr8YP3o";
+
+    const SHARED_SECRET_HEX: &str =
+        "163FAA3FC4815D47345C8E959F707B2F1D3537E7B2EA1DAEC23CA8D0A242CFF3";
+
+    fn load_priv_key_1() -> PrivateKey<Static> {
+        let private_key = URL_SAFE_NO_PAD.decode(PRIV_KEY_1_JWK_D).unwrap();
+        let x = URL_SAFE_NO_PAD.decode(PRIV_KEY_1_JWK_X).unwrap();
+        let y = URL_SAFE_NO_PAD.decode(PRIV_KEY_1_JWK_Y).unwrap();
+        PrivateKey::<Static>::import(
+            &EcKey::from_coordinates(Curve::P256, &private_key, &x, &y).unwrap(),
+        )
+        .unwrap()
+    }
+
+    fn load_priv_key_2() -> PrivateKey<Static> {
+        let private_key = URL_SAFE_NO_PAD.decode(PRIV_KEY_2_JWK_D).unwrap();
+        let x = URL_SAFE_NO_PAD.decode(PRIV_KEY_2_JWK_X).unwrap();
+        let y = URL_SAFE_NO_PAD.decode(PRIV_KEY_2_JWK_Y).unwrap();
+        PrivateKey::<Static>::import(
+            &EcKey::from_coordinates(Curve::P256, &private_key, &x, &y).unwrap(),
+        )
+        .unwrap()
+    }
+
+    #[test]
+    fn test_static_agreement() {
+        let pub_key_raw = URL_SAFE_NO_PAD.decode(PUB_KEY_1_B64).unwrap();
+        let peer_pub_key = UnparsedPublicKey::new(&ECDH_P256, &pub_key_raw);
+        let prv_key = load_priv_key_2();
+        let ikm = prv_key.agree_static(&peer_pub_key).unwrap();
+        let secret = ikm
+            .derive(|z| -> Result<Vec<u8>> { Ok(z.to_vec()) })
+            .unwrap();
+        let secret_b64 = hex::encode_upper(secret);
+        assert_eq!(secret_b64, *SHARED_SECRET_HEX);
+    }
+
+    #[test]
+    fn test_ephemeral_agreement_roundtrip() {
+        let (our_prv_key, our_pub_key) =
+            KeyPair::<Ephemeral>::generate(&ECDH_P256).unwrap().split();
+        let (their_prv_key, their_pub_key) =
+            KeyPair::<Ephemeral>::generate(&ECDH_P256).unwrap().split();
+        let their_pub_key_raw = their_pub_key.to_bytes().unwrap();
+        let peer_public_key_1 = UnparsedPublicKey::new(&ECDH_P256, &their_pub_key_raw);
+        let ikm_1 = our_prv_key.agree(&peer_public_key_1).unwrap();
+        let secret_1 = ikm_1
+            .derive(|z| -> Result<Vec<u8>> { Ok(z.to_vec()) })
+            .unwrap();
+        let our_pub_key_raw = our_pub_key.to_bytes().unwrap();
+        let peer_public_key_2 = UnparsedPublicKey::new(&ECDH_P256, &our_pub_key_raw);
+        let ikm_2 = their_prv_key.agree(&peer_public_key_2).unwrap();
+        let secret_2 = ikm_2
+            .derive(|z| -> Result<Vec<u8>> { Ok(z.to_vec()) })
+            .unwrap();
+        assert_eq!(secret_1, secret_2);
+    }
+
+    #[test]
+    fn test_compute_public_key() {
+        let (prv_key, pub_key) = KeyPair::<Static>::generate(&ECDH_P256).unwrap().split();
+        let computed_pub_key = prv_key.compute_public_key().unwrap();
+        assert_eq!(
+            computed_pub_key.to_bytes().unwrap(),
+            pub_key.to_bytes().unwrap()
+        );
+    }
+
+    #[test]
+    fn test_compute_public_key_known_values() {
+        let prv_key = load_priv_key_1();
+        let pub_key = URL_SAFE_NO_PAD.decode(PUB_KEY_1_B64).unwrap();
+        let computed_pub_key = prv_key.compute_public_key().unwrap();
+        assert_eq!(computed_pub_key.to_bytes().unwrap(), pub_key.as_slice());
+
+        let prv_key = load_priv_key_2();
+        let computed_pub_key = prv_key.compute_public_key().unwrap();
+        assert_ne!(computed_pub_key.to_bytes().unwrap(), pub_key.as_slice());
+    }
+
+    #[test]
+    fn test_keys_byte_representations_roundtrip() {
+        let key_pair = KeyPair::<Static>::generate(&ECDH_P256).unwrap();
+        let prv_key = key_pair.private_key;
+        let extracted_pub_key = prv_key.compute_public_key().unwrap();
+        let ec_key = prv_key.export().unwrap();
+        let prv_key_reconstructed = PrivateKey::<Static>::import(&ec_key).unwrap();
+        let extracted_pub_key_reconstructed = prv_key.compute_public_key().unwrap();
+        let ec_key_reconstructed = prv_key_reconstructed.export().unwrap();
+        assert_eq!(ec_key.curve(), ec_key_reconstructed.curve());
+        assert_eq!(ec_key.public_key(), ec_key_reconstructed.public_key());
+        assert_eq!(ec_key.private_key(), ec_key_reconstructed.private_key());
+        assert_eq!(
+            extracted_pub_key.to_bytes().unwrap(),
+            extracted_pub_key_reconstructed.to_bytes().unwrap()
+        );
+    }
+
+    #[test]
+    fn test_agreement_rejects_invalid_pubkeys() {
+        let prv_key = load_priv_key_2();
+
+        let mut invalid_pub_key = URL_SAFE_NO_PAD.decode(PUB_KEY_1_B64).unwrap();
+        invalid_pub_key[0] = invalid_pub_key[0].wrapping_add(1);
+        assert!(prv_key
+            .agree_static(&UnparsedPublicKey::new(&ECDH_P256, &invalid_pub_key))
+            .is_err());
+
+        let mut invalid_pub_key = URL_SAFE_NO_PAD.decode(PUB_KEY_1_B64).unwrap();
+        invalid_pub_key[0] = 0x02;
+        assert!(prv_key
+            .agree_static(&UnparsedPublicKey::new(&ECDH_P256, &invalid_pub_key))
+            .is_err());
+
+        let mut invalid_pub_key = URL_SAFE_NO_PAD.decode(PUB_KEY_1_B64).unwrap();
+        invalid_pub_key[64] = invalid_pub_key[0].wrapping_add(1);
+        assert!(prv_key
+            .agree_static(&UnparsedPublicKey::new(&ECDH_P256, &invalid_pub_key))
+            .is_err());
+
+        let mut invalid_pub_key = [0u8; 65];
+        assert!(prv_key
+            .agree_static(&UnparsedPublicKey::new(&ECDH_P256, &invalid_pub_key))
+            .is_err());
+        invalid_pub_key[0] = 0x04;
+
+        let mut invalid_pub_key = URL_SAFE_NO_PAD.decode(PUB_KEY_1_B64).unwrap().to_vec();
+        invalid_pub_key = invalid_pub_key[0..64].to_vec();
+        assert!(prv_key
+            .agree_static(&UnparsedPublicKey::new(&ECDH_P256, &invalid_pub_key))
+            .is_err());
+
+        // From FxA tests at https://github.com/mozilla/fxa-crypto-relier/blob/04f61dc/test/deriver/DeriverUtils.js#L78
+        // We trust that NSS will do the right thing here, but it seems worthwhile to confirm for completeness.
+        let invalid_pub_key_b64 = "BEogZ-rnm44oJkKsOE6Tc7NwFMgmntf7Btm_Rc4atxcqq99Xq1RWNTFpk99pdQOSjUvwELss51PkmAGCXhLfMV0";
+        let invalid_pub_key = URL_SAFE_NO_PAD.decode(invalid_pub_key_b64).unwrap();
+        assert!(prv_key
+            .agree_static(&UnparsedPublicKey::new(&ECDH_P256, &invalid_pub_key))
+            .is_err());
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/rc_crypto/constant_time.rs.html b/book/rust-docs/src/rc_crypto/constant_time.rs.html new file mode 100644 index 0000000000..4b7bb58596 --- /dev/null +++ b/book/rust-docs/src/rc_crypto/constant_time.rs.html @@ -0,0 +1,91 @@ +constant_time.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This file contains code that was copied from the ring crate which is under
+// the ISC license, reproduced below:
+
+// Copyright 2015-2017 Brian Smith.
+
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
+// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+use crate::error::*;
+
+/// Returns `Ok(())` if `a == b` and `Error` otherwise.
+/// The comparison of `a` and `b` is done in constant time with respect to the
+/// contents of each, but NOT in constant time with respect to the lengths of
+/// `a` and `b`.
+pub fn verify_slices_are_equal(a: &[u8], b: &[u8]) -> Result<()> {
+    if nss::secport::secure_memcmp(a, b) {
+        Ok(())
+    } else {
+        Err(ErrorKind::InternalError.into())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    #[test]
+    fn does_compare() {
+        assert!(verify_slices_are_equal(b"bobo", b"bobo").is_ok());
+        assert!(verify_slices_are_equal(b"bobo", b"obob").is_err());
+        assert!(verify_slices_are_equal(b"bobo", b"notbobo").is_err());
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/rc_crypto/contentsignature.rs.html b/book/rust-docs/src/rc_crypto/contentsignature.rs.html new file mode 100644 index 0000000000..9215be49dc --- /dev/null +++ b/book/rust-docs/src/rc_crypto/contentsignature.rs.html @@ -0,0 +1,845 @@ +contentsignature.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use std::str;
+
+use base64::{
+    engine::general_purpose::{STANDARD, URL_SAFE},
+    Engine,
+};
+
+use crate::error::*;
+use crate::signature;
+
+/// Verify content signatures, with the ECDSA P384 curve and SHA-384 hashing (NIST384p / secp384r1).
+///
+/// These signatures are typically used to garantee integrity of data between our servers and clients.
+/// This is a critical part of systems like Remote Settings or the experiment platform.
+///
+/// The equivalent implementation for Gecko is ``security/manager/ssl/nsIContentSignatureVerifier.idl``.
+
+/// Decode a string with colon separated hexadecimal pairs into an array of bytes
+/// (eg. "3C:01:44" -> [60, 1, 68]).
+fn decode_root_hash(input: &str) -> Result<Vec<u8>> {
+    let bytes_hex = input.split(':');
+
+    let mut result: Vec<u8> = vec![];
+    for byte_hex in bytes_hex {
+        let byte = match hex::decode(byte_hex) {
+            Ok(v) => v,
+            Err(_) => return Err(ErrorKind::RootHashFormatError(input.to_string()).into()),
+        };
+        result.extend(byte);
+    }
+
+    Ok(result)
+}
+
+/// Split a certificate chain in PEM format into a list of certificates bytes,
+/// decoded from base64.
+fn split_pem(pem_content: &[u8]) -> Result<Vec<Vec<u8>>> {
+    let pem_str = match str::from_utf8(pem_content) {
+        Ok(v) => v,
+        Err(e) => {
+            return Err(ErrorKind::PEMFormatError(e.to_string()).into());
+        }
+    };
+
+    let pem_lines = pem_str.split('\n');
+
+    let mut blocks: Vec<Vec<u8>> = vec![];
+    let mut block: Vec<u8> = vec![];
+    let mut read = false;
+    for line in pem_lines {
+        if line.contains("-----BEGIN CERTIFICATE") {
+            read = true;
+        } else if line.contains("-----END CERTIFICATE") {
+            read = false;
+            let decoded = match STANDARD.decode(&block) {
+                Ok(v) => v,
+                Err(e) => return Err(ErrorKind::PEMFormatError(e.to_string()).into()),
+            };
+            blocks.push(decoded);
+            block.clear();
+        } else if read {
+            block.extend_from_slice(line.as_bytes());
+        }
+    }
+    if read {
+        return Err(ErrorKind::PEMFormatError("Missing end header".into()).into());
+    }
+    if blocks.is_empty() {
+        return Err(ErrorKind::PEMFormatError("Missing PEM data".into()).into());
+    }
+
+    Ok(blocks)
+}
+
+/// Verify that the signature matches the input data.
+///
+/// The data must be prefixed with ``Content-Signature:\u{0}``.
+/// The signature must be provided as base 64 url-safe encoded.
+/// The certificate chain, provided as PEM, must be valid at the provided current time.
+/// The root certificate content must match the provided root hash, and the leaf
+/// subject name must match the provided hostname.
+pub fn verify(
+    input: &[u8],
+    signature: &[u8],
+    pem_bytes: &[u8],
+    seconds_since_epoch: u64,
+    root_sha256_hash: &str,
+    hostname: &str,
+) -> Result<()> {
+    let certificates = split_pem(pem_bytes)?;
+
+    let mut certificates_slices: Vec<&[u8]> = vec![];
+    for certificate in &certificates {
+        certificates_slices.push(certificate);
+    }
+
+    let root_hash_bytes = decode_root_hash(root_sha256_hash)?;
+
+    nss::pkixc::verify_code_signing_certificate_chain(
+        certificates_slices,
+        seconds_since_epoch,
+        &root_hash_bytes,
+        hostname,
+    )
+    .map_err(|err| match err.kind() {
+        nss::ErrorKind::CertificateIssuerError => ErrorKind::CertificateIssuerError,
+        nss::ErrorKind::CertificateValidityError => ErrorKind::CertificateValidityError,
+        nss::ErrorKind::CertificateSubjectError => ErrorKind::CertificateSubjectError,
+        _ => ErrorKind::CertificateChainError(err.to_string()),
+    })?;
+
+    let leaf_cert = certificates.first().unwrap(); // PEM parse fails if len == 0.
+
+    let public_key_bytes = match nss::cert::extract_ec_public_key(leaf_cert) {
+        Ok(bytes) => bytes,
+        Err(err) => return Err(ErrorKind::CertificateContentError(err.to_string()).into()),
+    };
+
+    let signature_bytes = match URL_SAFE.decode(signature) {
+        Ok(b) => b,
+        Err(err) => return Err(ErrorKind::SignatureContentError(err.to_string()).into()),
+    };
+
+    // Since signature is NIST384p / secp384r1, we can perform a few safety checks.
+    if signature_bytes.len() != 96 {
+        return Err(ErrorKind::SignatureContentError(format!(
+            "signature contains {} bytes instead of {}",
+            signature_bytes.len(),
+            96
+        ))
+        .into());
+    }
+    if public_key_bytes.len() != 96 + 1 {
+        // coordinates with x04 prefix.
+        return Err(ErrorKind::CertificateContentError(format!(
+            "public key contains {} bytes instead of {}",
+            public_key_bytes.len(),
+            97
+        ))
+        .into());
+    }
+
+    let signature_alg = &signature::ECDSA_P384_SHA384;
+    let public_key = signature::UnparsedPublicKey::new(signature_alg, &public_key_bytes);
+    // Note that if the provided key type or curve is incorrect here, the signature will
+    // be considered as invalid.
+    match public_key.verify(input, &signature_bytes) {
+        Ok(_) => Ok(()),
+        Err(err) => Err(ErrorKind::SignatureMismatchError(err.to_string()).into()),
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    const ROOT_HASH: &str = "3C:01:44:6A:BE:90:36:CE:A9:A0:9A:CA:A3:A5:20:AC:62:8F:20:A7:AE:32:CE:86:1C:B2:EF:B7:0F:A0:C7:45";
+    const VALID_CERT_CHAIN: &[u8] = b"\
+-----BEGIN CERTIFICATE-----
+MIIDBjCCAougAwIBAgIIFml6g0ldRGowCgYIKoZIzj0EAwMwgaMxCzAJBgNVBAYT
+AlVTMRwwGgYDVQQKExNNb3ppbGxhIENvcnBvcmF0aW9uMS8wLQYDVQQLEyZNb3pp
+bGxhIEFNTyBQcm9kdWN0aW9uIFNpZ25pbmcgU2VydmljZTFFMEMGA1UEAww8Q29u
+dGVudCBTaWduaW5nIEludGVybWVkaWF0ZS9lbWFpbEFkZHJlc3M9Zm94c2VjQG1v
+emlsbGEuY29tMB4XDTIxMDIwMzE1MDQwNVoXDTIxMDQyNDE1MDQwNVowgakxCzAJ
+BgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFp
+biBWaWV3MRwwGgYDVQQKExNNb3ppbGxhIENvcnBvcmF0aW9uMRcwFQYDVQQLEw5D
+bG91ZCBTZXJ2aWNlczE2MDQGA1UEAxMtcmVtb3RlLXNldHRpbmdzLmNvbnRlbnQt
+c2lnbmF0dXJlLm1vemlsbGEub3JnMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE8pKb
+HX4IiD0SCy+NO7gwKqRRZ8IhGd8PTaIHIBgM6RDLRyDeswXgV+2kGUoHyzkbNKZt
+zlrS3AhqeUCtl1g6ECqSmZBbRTjCpn/UCpCnMLL0T0goxtAB8Rmi3CdM0cBUo4GD
+MIGAMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAfBgNVHSME
+GDAWgBQlZawrqt0eUz/t6OdN45oKfmzy6DA4BgNVHREEMTAvgi1yZW1vdGUtc2V0
+dGluZ3MuY29udGVudC1zaWduYXR1cmUubW96aWxsYS5vcmcwCgYIKoZIzj0EAwMD
+aQAwZgIxAPh43Bxl4MxPT6Ra1XvboN5O2OvIn2r8rHvZPWR/jJ9vcTwH9X3F0aLJ
+9FiresnsLAIxAOoAcREYB24gFBeWxbiiXaG7TR/yM1/MXw4qxbN965FFUaoB+5Bc
+fS8//SQGTlCqKQ==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIF2jCCA8KgAwIBAgIEAQAAADANBgkqhkiG9w0BAQsFADCBqTELMAkGA1UEBhMC
+VVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRwwGgYDVQQK
+ExNBZGRvbnMgVGVzdCBTaWduaW5nMSQwIgYDVQQDExt0ZXN0LmFkZG9ucy5zaWdu
+aW5nLnJvb3QuY2ExMTAvBgkqhkiG9w0BCQEWInNlY29wcytzdGFnZXJvb3RhZGRv
+bnNAbW96aWxsYS5jb20wHhcNMjEwMTExMDAwMDAwWhcNMjQxMTE0MjA0ODU5WjCB
+ozELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE01vemlsbGEgQ29ycG9yYXRpb24xLzAt
+BgNVBAsTJk1vemlsbGEgQU1PIFByb2R1Y3Rpb24gU2lnbmluZyBTZXJ2aWNlMUUw
+QwYDVQQDDDxDb250ZW50IFNpZ25pbmcgSW50ZXJtZWRpYXRlL2VtYWlsQWRkcmVz
+cz1mb3hzZWNAbW96aWxsYS5jb20wdjAQBgcqhkjOPQIBBgUrgQQAIgNiAARw1dyE
+xV5aNiHJPa/fVHO6kxJn3oZLVotJ0DzFZA9r1sQf8i0+v78Pg0/c3nTAyZWfkULz
+vOpKYK/GEGBtisxCkDJ+F3NuLPpSIg3fX25pH0LE15fvASBVcr8tKLVHeOmjggG6
+MIIBtjAMBgNVHRMEBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAWBgNVHSUBAf8EDDAK
+BggrBgEFBQcDAzAdBgNVHQ4EFgQUJWWsK6rdHlM/7ejnTeOaCn5s8ugwgdkGA1Ud
+IwSB0TCBzoAUhtg0HE5Y0RNcmV/YQpjtFA8Z8l2hga+kgawwgakxCzAJBgNVBAYT
+AlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEcMBoGA1UE
+ChMTQWRkb25zIFRlc3QgU2lnbmluZzEkMCIGA1UEAxMbdGVzdC5hZGRvbnMuc2ln
+bmluZy5yb290LmNhMTEwLwYJKoZIhvcNAQkBFiJzZWNvcHMrc3RhZ2Vyb290YWRk
+b25zQG1vemlsbGEuY29tggRgJZg7MDMGCWCGSAGG+EIBBAQmFiRodHRwOi8vYWRk
+b25zLmFsbGl6b20ub3JnL2NhL2NybC5wZW0wTgYDVR0eBEcwRaBDMCCCHi5jb250
+ZW50LXNpZ25hdHVyZS5tb3ppbGxhLm9yZzAfgh1jb250ZW50LXNpZ25hdHVyZS5t
+b3ppbGxhLm9yZzANBgkqhkiG9w0BAQsFAAOCAgEAtGTTzcPzpcdf07kIeRs9vPMx
+qiF8ylW5L/IQ2NzT3sFFAvPW1vW1wZC0xAHMsuVyo+BTGrv+4mlD0AUR9acRfiTZ
+9qyZ3sJbyhQwJAXLKU4YpnzuFOf58T/yOnOdwpH2ky/0FuHskMyfXaAz2Az4JXJH
+TCgggqfdZNvsZ5eOnQlKoC5NadMa8oTI5sd4SyR5ANUPAtYok931MvVSz3IMbwTr
+v4PPWXdl9SGXuOknSqdY6/bS1LGvC2KprsT+PBlvVtS6YgZOH0uCgTTLpnrco87O
+ErzC2PJBA1Ftn3Mbaou6xy7O+YX+reJ6soNUV+0JHOuKj0aTXv0c+lXEAh4Y8nea
+UGhW6+MRGYMOP2NuKv8s2+CtNH7asPq3KuTQpM5RerjdouHMIedX7wpNlNk0CYbg
+VMJLxZfAdwcingLWda/H3j7PxMoAm0N+eA24TGDQPC652ZakYk4MQL/45lm0A5f0
+xLGKEe6JMZcTBQyO7ANWcrpVjKMiwot6bY6S2xU17mf/h7J32JXZJ23OPOKpMS8d
+mljj4nkdoYDT35zFuS1z+5q6R5flLca35vRHzC3XA0H/XJvgOKUNLEW/IiJIqLNi
+ab3Ao0RubuX+CAdFML5HaJmkyuJvL3YtwIOwe93RGcGRZSKZsnMS+uY5QN8+qKQz
+LC4GzWQGSCGDyD+JCVw=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIHbDCCBVSgAwIBAgIEYCWYOzANBgkqhkiG9w0BAQwFADCBqTELMAkGA1UEBhMC
+VVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRwwGgYDVQQK
+ExNBZGRvbnMgVGVzdCBTaWduaW5nMSQwIgYDVQQDExt0ZXN0LmFkZG9ucy5zaWdu
+aW5nLnJvb3QuY2ExMTAvBgkqhkiG9w0BCQEWInNlY29wcytzdGFnZXJvb3RhZGRv
+bnNAbW96aWxsYS5jb20wHhcNMjEwMjExMjA0ODU5WhcNMjQxMTE0MjA0ODU5WjCB
+qTELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBW
+aWV3MRwwGgYDVQQKExNBZGRvbnMgVGVzdCBTaWduaW5nMSQwIgYDVQQDExt0ZXN0
+LmFkZG9ucy5zaWduaW5nLnJvb3QuY2ExMTAvBgkqhkiG9w0BCQEWInNlY29wcytz
+dGFnZXJvb3RhZGRvbnNAbW96aWxsYS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4IC
+DwAwggIKAoICAQDKRVty/FRsO4Ech6EYleyaKgAueaLYfMSsAIyPC/N8n/P8QcH8
+rjoiMJrKHRlqiJmMBSmjUZVzZAP0XJku0orLKWPKq7cATt+xhGY/RJtOzenMMsr5
+eN02V3GzUd1jOShUpERjzXdaO3pnfZqhdqNYqP9ocqQpyno7bZ3FZQ2vei+bF52k
+51uPioTZo+1zduoR/rT01twGtZm3QpcwU4mO74ysyxxgqEy3kpojq8Nt6haDwzrj
+khV9M6DGPLHZD71QaUiz5lOhD9CS8x0uqXhBhwMUBBkHsUDSxbN4ZhjDDWpCmwaD
+OtbJMUJxDGPCr9qj49QESccb367OeXLrfZ2Ntu/US2Bw9EDfhyNsXr9dg9NHj5yf
+4sDUqBHG0W8zaUvJx5T2Ivwtno1YZLyJwQW5pWeWn8bEmpQKD2KS/3y2UjlDg+YM
+NdNASjFe0fh6I5NCFYmFWA73DpDGlUx0BtQQU/eZQJ+oLOTLzp8d3dvenTBVnKF+
+uwEmoNfZwc4TTWJOhLgwxA4uK+Paaqo4Ap2RGS2ZmVkPxmroB3gL5n3k3QEXvULh
+7v8Psk4+MuNWnxudrPkN38MGJo7ju7gDOO8h1jLD4tdfuAqbtQLduLXzT4DJPA4y
+JBTFIRMIpMqP9CovaS8VPtMFLTrYlFh9UnEGpCeLPanJr+VEj7ae5sc8YwIDAQAB
+o4IBmDCCAZQwDAYDVR0TBAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwFgYDVR0lAQH/
+BAwwCgYIKwYBBQUHAwMwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVk
+IENlcnRpZmljYXRlMDMGCWCGSAGG+EIBBAQmFiRodHRwOi8vYWRkb25zLm1vemls
+bGEub3JnL2NhL2NybC5wZW0wHQYDVR0OBBYEFIbYNBxOWNETXJlf2EKY7RQPGfJd
+MIHZBgNVHSMEgdEwgc6AFIbYNBxOWNETXJlf2EKY7RQPGfJdoYGvpIGsMIGpMQsw
+CQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcx
+HDAaBgNVBAoTE0FkZG9ucyBUZXN0IFNpZ25pbmcxJDAiBgNVBAMTG3Rlc3QuYWRk
+b25zLnNpZ25pbmcucm9vdC5jYTExMC8GCSqGSIb3DQEJARYic2Vjb3BzK3N0YWdl
+cm9vdGFkZG9uc0Btb3ppbGxhLmNvbYIEYCWYOzANBgkqhkiG9w0BAQwFAAOCAgEA
+nowyJv8UaIV7NA0B3wkWratq6FgA1s/PzetG/ZKZDIW5YtfUvvyy72HDAwgKbtap
+Eog6zGI4L86K0UGUAC32fBjE5lWYEgsxNM5VWlQjbgTG0dc3dYiufxfDFeMbAPmD
+DzpIgN3jHW2uRqa/MJ+egHhv7kGFL68uVLboqk/qHr+SOCc1LNeSMCuQqvHwwM0+
+AU1GxhzBWDkealTS34FpVxF4sT5sKLODdIS5HXJr2COHHfYkw2SW/Sfpt6fsOwaF
+2iiDaK4LPWHWhhIYa6yaynJ+6O6KPlpvKYCChaTOVdc+ikyeiSO6AakJykr5Gy7d
+PkkK7MDCxuY6psHj7iJQ59YK7ujQB8QYdzuXBuLLo5hc5gBcq3PJs0fLT2YFcQHA
+dj+olGaDn38T0WI8ycWaFhQfKwATeLWfiQepr8JfoNlC2vvSDzGUGfdAfZfsJJZ8
+5xZxahHoTFGS0mDRfXqzKH5uD578GgjOZp0fULmzkcjWsgzdpDhadGjExRZFKlAy
+iKv8cXTONrGY0fyBDKennuX0uAca3V0Qm6v2VRp+7wG/pywWwc5n+04qgxTQPxgO
+6pPB9UUsNbaLMDR5QPYAWrNhqJ7B07XqIYJZSwGP5xB9NqUZLF4z+AOMYgWtDpmg
+IKdcFKAt3fFrpyMhlfIKkLfmm0iDjmfmIXbDGBJw9SE=
+-----END CERTIFICATE-----";
+    const VALID_INPUT: &[u8] =
+        b"Content-Signature:\x00{\"data\":[],\"last_modified\":\"1603992731957\"}";
+    const VALID_SIGNATURE: &[u8] = b"fJJcOpwdnkjEWFeHXfdOJN6GaGLuDTPGzQOxA2jn6ldIleIk6KqMhZcy2GZv2uYiGwl6DERWwpaoUfQFLyCAOcVjck1qlaaEFZGY1BQba9p99xEc9FNQ3YPPfvSSZqsw";
+    const VALID_HOSTNAME: &str = "remote-settings.content-signature.mozilla.org";
+
+    const INVALID_CERTIFICATE: &[u8] = b"\
+    -----BEGIN CERTIFICATE-----
+    invalidCertificategIFiJLFfdxFlYwCgYIKoZIzj0EAwMwgaMxCzAJBgNVBAYT
+    AlVTMRwwGgYDVQQKExNNb3ppbGxhIENvcnBvcmF0aW9uMS8wLQYDVQQLEyZNb3pp
+    bGxhIEFNTyBQcm9kdWN0aW9uIFNpZ25pbmcgU2VydmljZTFFMEMGA1UEAww8Q29u
+    dGVudCBTaWduaW5nIEludGVybWVkaWF0ZS9lbWFpbEFkZHJlc3M9Zm94c2VjQG1v
+    emlsbGEuY29tMB4XDTIwMDYxNjE3MTYxNVoXDTIwMDkwNDE3MTYxNVowgakxCzAJ
+    BgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFp
+    biBWaWV3MRwwGgYDVQQKExNNb3ppbGxhIENvcnBvcmF0aW9uMRcwFQYDVQQLEw5D
+    bG91ZCBTZXJ2aWNlczE2MDQGA1UEAxMtcmVtb3RlLXNldHRpbmdzLmNvbnRlbnQt
+    c2lnbmF0dXJlLm1vemlsbGEub3JnMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEDmOX
+    N5IGlUqCvu6xkOKr020Eo3kY2uPdJO0ZihVUoglk1ktQPss184OajFOMKm/BJX4W
+    IsZUzQoRL8NgGfZDwBjT95Q87lhOWEWs5AU/nMXIYwDp7rpUPaUqw0QLMikdo4GD
+    MIGAMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAfBgNVHSME
+    GDAWgBSgHUoXT4zCKzVF8WPx2nBwp8744TA4BgNVHREEMTAvgi1yZW1vdGUtc2V0
+    dGluZ3MuY29udGVudC1zaWduYXR1cmUubW96aWxsYS5vcmcwCgYIKoZIzj0EAwMD
+    aQAwZgIxAJvyynyPqRmRMqf95FPH5xfcoT3jb/2LOkUifGDtjtZ338ScpT2glUK8
+    HszKVANqXQIxAIygMaeTiD9figEusmHMthBdFoIoHk31x4MHukAy+TWZ863X6/V2
+    6/ZrZMpinvalid==
+    -----END CERTIFICATE-----";
+
+    #[test]
+    fn test_decode_root_hash() {
+        assert!(decode_root_hash("meh!").is_err());
+        assert!(decode_root_hash("3C:rr:44").is_err());
+
+        let result = decode_root_hash(ROOT_HASH).unwrap();
+        assert_eq!(
+            result,
+            vec![
+                60, 1, 68, 106, 190, 144, 54, 206, 169, 160, 154, 202, 163, 165, 32, 172, 98, 143,
+                32, 167, 174, 50, 206, 134, 28, 178, 239, 183, 15, 160, 199, 69
+            ]
+        );
+    }
+
+    #[test]
+    fn test_split_pem() {
+        assert!(split_pem(b"meh!").is_err());
+
+        assert!(split_pem(
+            b"-----BEGIN CERTIFICATE-----
+invalidCertificate
+-----END CERTIFICATE-----"
+        )
+        .is_err());
+
+        assert!(split_pem(
+            b"-----BEGIN CERTIFICATE-----
+bGxhIEFNTyBQcm9kdWN0aW9uIFNp
+-----BEGIN CERTIFICATE-----"
+        )
+        .is_err());
+
+        let result = split_pem(
+            b"-----BEGIN CERTIFICATE-----
+AQID
+BAUG
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+/f7/
+-----END CERTIFICATE-----",
+        )
+        .unwrap();
+        assert_eq!(result, vec![vec![1, 2, 3, 4, 5, 6], vec![253, 254, 255]]);
+    }
+
+    #[test]
+    fn test_verify_fails_if_invalid() {
+        assert!(verify(
+            b"msg",
+            b"sig",
+            b"-----BEGIN CERTIFICATE-----
+fdfeff
+-----END CERTIFICATE-----",
+            42,
+            ROOT_HASH,
+            "remotesettings.firefox.com",
+        )
+        .is_err());
+    }
+
+    #[test]
+    fn test_verify_fails_if_cert_has_expired() {
+        assert!(verify(
+            VALID_INPUT,
+            VALID_SIGNATURE,
+            VALID_CERT_CHAIN,
+            1215559719, // July 9, 2008
+            ROOT_HASH,
+            VALID_HOSTNAME,
+        )
+        .is_err());
+    }
+
+    #[test]
+    fn test_verify_fails_if_bad_certificate_chain() {
+        assert!(verify(
+            VALID_INPUT,
+            VALID_SIGNATURE,
+            INVALID_CERTIFICATE,
+            1615559719, // March 12, 2021
+            ROOT_HASH,
+            VALID_HOSTNAME,
+        )
+        .is_err());
+    }
+
+    #[test]
+    fn test_verify_fails_if_mismatch() {
+        assert!(verify(
+            b"msg",
+            VALID_SIGNATURE,
+            VALID_CERT_CHAIN,
+            1615559719, // March 12, 2021
+            ROOT_HASH,
+            VALID_HOSTNAME,
+        )
+        .is_err());
+    }
+
+    #[test]
+    fn test_verify_fails_if_bad_hostname() {
+        assert!(verify(
+            VALID_INPUT,
+            VALID_SIGNATURE,
+            VALID_CERT_CHAIN,
+            1615559719, // March 12, 2021
+            ROOT_HASH,
+            "some.hostname.org",
+        )
+        .is_err());
+    }
+
+    #[test]
+    fn test_verify_fails_if_bad_root_hash() {
+        assert!(verify(
+            VALID_INPUT,
+            VALID_SIGNATURE,
+            VALID_CERT_CHAIN,
+            1615559719, // March 12, 2021
+            "00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00",
+            VALID_HOSTNAME,
+        )
+        .is_err());
+    }
+
+    #[test]
+    fn test_verify_succeeds_if_valid() {
+        verify(
+            VALID_INPUT,
+            VALID_SIGNATURE,
+            VALID_CERT_CHAIN,
+            1615559719, // March 12, 2021
+            ROOT_HASH,
+            VALID_HOSTNAME,
+        )
+        .unwrap();
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/rc_crypto/digest.rs.html b/book/rust-docs/src/rc_crypto/digest.rs.html new file mode 100644 index 0000000000..171a2fd3b0 --- /dev/null +++ b/book/rust-docs/src/rc_crypto/digest.rs.html @@ -0,0 +1,151 @@ +digest.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This file contains code that was copied from the ring crate which is under
+// the ISC license, reproduced below:
+
+// Copyright 2015-2017 Brian Smith.
+
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
+// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+use crate::error::*;
+
+pub use nss::pk11::context::HashAlgorithm::{self as Algorithm, *};
+
+/// A calculated digest value.
+#[derive(Clone)]
+pub struct Digest {
+    pub(crate) value: Vec<u8>,
+    pub(crate) algorithm: Algorithm,
+}
+
+impl Digest {
+    pub fn algorithm(&self) -> &Algorithm {
+        &self.algorithm
+    }
+}
+
+impl AsRef<[u8]> for Digest {
+    fn as_ref(&self) -> &[u8] {
+        self.value.as_ref()
+    }
+}
+
+/// Returns the digest of data using the given digest algorithm.
+pub fn digest(algorithm: &Algorithm, data: &[u8]) -> Result<Digest> {
+    let value = nss::pk11::context::hash_buf(algorithm, data)?;
+    Ok(Digest {
+        value,
+        algorithm: *algorithm,
+    })
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    const MESSAGE: &[u8] = b"bobo";
+    const DIGEST_HEX: &str = "bf0c97708b849de696e7373508b13c5ea92bafa972fc941d694443e494a4b84d";
+
+    #[test]
+    fn sha256_digest() {
+        assert_eq!(hex::encode(digest(&SHA256, MESSAGE).unwrap()), DIGEST_HEX);
+        assert_ne!(
+            hex::encode(digest(&SHA256, b"notbobo").unwrap()),
+            DIGEST_HEX
+        );
+    }
+
+    #[test]
+    fn digest_cleanly_rejects_gigantic_messages() {
+        let message = vec![0; (std::i32::MAX as usize) + 1];
+        assert!(digest(&SHA256, &message).is_err());
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/rc_crypto/ece_crypto.rs.html b/book/rust-docs/src/rc_crypto/ece_crypto.rs.html new file mode 100644 index 0000000000..0081e90a13 --- /dev/null +++ b/book/rust-docs/src/rc_crypto/ece_crypto.rs.html @@ -0,0 +1,375 @@ +ece_crypto.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::{
+    aead,
+    agreement::{self, Curve, EcKey, UnparsedPublicKey},
+    digest, hkdf, hmac, rand,
+};
+use ece::crypto::{Cryptographer, EcKeyComponents, LocalKeyPair, RemotePublicKey};
+
+impl From<crate::Error> for ece::Error {
+    fn from(_: crate::Error) -> Self {
+        ece::Error::CryptoError
+    }
+}
+
+pub struct RcCryptoLocalKeyPair {
+    wrapped: agreement::KeyPair<agreement::Static>,
+}
+// SECKEYPrivateKeyStr and SECKEYPublicKeyStr are Sync.
+unsafe impl Sync for RcCryptoLocalKeyPair {}
+
+impl RcCryptoLocalKeyPair {
+    pub fn from_raw_components(components: &EcKeyComponents) -> Result<Self, ece::Error> {
+        let ec_key = EcKey::new(
+            Curve::P256,
+            components.private_key(),
+            components.public_key(),
+        );
+        let priv_key = agreement::PrivateKey::<agreement::Static>::import(&ec_key)?;
+        let wrapped = agreement::KeyPair::<agreement::Static>::from_private_key(priv_key)?;
+        Ok(RcCryptoLocalKeyPair { wrapped })
+    }
+
+    pub fn generate_random() -> Result<Self, ece::Error> {
+        let wrapped = agreement::KeyPair::<agreement::Static>::generate(&agreement::ECDH_P256)?;
+        Ok(RcCryptoLocalKeyPair { wrapped })
+    }
+
+    fn agree(&self, peer: &RcCryptoRemotePublicKey) -> Result<Vec<u8>, ece::Error> {
+        let peer_public_key_raw_bytes = &peer.as_raw()?;
+        let peer_public_key =
+            UnparsedPublicKey::new(&agreement::ECDH_P256, peer_public_key_raw_bytes);
+        self.wrapped
+            .private_key()
+            .agree_static(&peer_public_key)?
+            .derive(|z| Ok(z.to_vec()))
+    }
+}
+
+impl LocalKeyPair for RcCryptoLocalKeyPair {
+    fn raw_components(&self) -> Result<EcKeyComponents, ece::Error> {
+        let ec_key = self.wrapped.private_key().export()?;
+        Ok(EcKeyComponents::new(
+            ec_key.private_key(),
+            ec_key.public_key(),
+        ))
+    }
+
+    fn pub_as_raw(&self) -> Result<Vec<u8>, ece::Error> {
+        let bytes = self.wrapped.public_key().to_bytes()?;
+        Ok(bytes.to_vec())
+    }
+
+    fn as_any(&self) -> &dyn std::any::Any {
+        self
+    }
+}
+pub struct RcCryptoRemotePublicKey {
+    raw: Vec<u8>,
+}
+
+impl RcCryptoRemotePublicKey {
+    pub fn from_raw(bytes: &[u8]) -> Result<RcCryptoRemotePublicKey, ece::Error> {
+        Ok(RcCryptoRemotePublicKey {
+            raw: bytes.to_owned(),
+        })
+    }
+}
+
+impl RemotePublicKey for RcCryptoRemotePublicKey {
+    fn as_raw(&self) -> Result<Vec<u8>, ece::Error> {
+        Ok(self.raw.to_vec())
+    }
+
+    fn as_any(&self) -> &dyn std::any::Any {
+        self
+    }
+}
+
+pub(crate) struct RcCryptoCryptographer;
+
+impl Cryptographer for RcCryptoCryptographer {
+    fn generate_ephemeral_keypair(&self) -> Result<Box<dyn LocalKeyPair>, ece::Error> {
+        Ok(Box::new(RcCryptoLocalKeyPair::generate_random()?))
+    }
+
+    fn import_key_pair(
+        &self,
+        components: &EcKeyComponents,
+    ) -> Result<Box<dyn LocalKeyPair>, ece::Error> {
+        Ok(Box::new(RcCryptoLocalKeyPair::from_raw_components(
+            components,
+        )?))
+    }
+
+    fn import_public_key(&self, raw: &[u8]) -> Result<Box<dyn RemotePublicKey>, ece::Error> {
+        Ok(Box::new(RcCryptoRemotePublicKey::from_raw(raw)?))
+    }
+
+    fn compute_ecdh_secret(
+        &self,
+        remote: &dyn RemotePublicKey,
+        local: &dyn LocalKeyPair,
+    ) -> Result<Vec<u8>, ece::Error> {
+        let local_any = local.as_any();
+        let local = local_any.downcast_ref::<RcCryptoLocalKeyPair>().unwrap();
+        let remote_any = remote.as_any();
+        let remote = remote_any
+            .downcast_ref::<RcCryptoRemotePublicKey>()
+            .unwrap();
+        local.agree(remote)
+    }
+
+    fn hkdf_sha256(
+        &self,
+        salt: &[u8],
+        secret: &[u8],
+        info: &[u8],
+        len: usize,
+    ) -> Result<Vec<u8>, ece::Error> {
+        let salt = hmac::SigningKey::new(&digest::SHA256, salt);
+        let mut out = vec![0u8; len];
+        hkdf::extract_and_expand(&salt, secret, info, &mut out)?;
+        Ok(out)
+    }
+
+    fn aes_gcm_128_encrypt(
+        &self,
+        key: &[u8],
+        iv: &[u8],
+        data: &[u8],
+    ) -> Result<Vec<u8>, ece::Error> {
+        let key = aead::SealingKey::new(&aead::AES_128_GCM, key)?;
+        let nonce = aead::Nonce::try_assume_unique_for_key(&aead::AES_128_GCM, iv)?;
+        Ok(aead::seal(&key, nonce, aead::Aad::empty(), data)?)
+    }
+
+    fn aes_gcm_128_decrypt(
+        &self,
+        key: &[u8],
+        iv: &[u8],
+        ciphertext_and_tag: &[u8],
+    ) -> Result<Vec<u8>, ece::Error> {
+        let key = aead::OpeningKey::new(&aead::AES_128_GCM, key)?;
+        let nonce = aead::Nonce::try_assume_unique_for_key(&aead::AES_128_GCM, iv)?;
+        Ok(aead::open(
+            &key,
+            nonce,
+            aead::Aad::empty(),
+            ciphertext_and_tag,
+        )?)
+    }
+
+    fn random_bytes(&self, dest: &mut [u8]) -> Result<(), ece::Error> {
+        Ok(rand::fill(dest)?)
+    }
+}
+
+// Please call `rc_crypto::ensure_initialized()` instead of calling
+// this function directly.
+pub(crate) fn init() {
+    ece::crypto::set_cryptographer(&crate::ece_crypto::RcCryptoCryptographer)
+        .expect("Failed to initialize `ece` cryptographer!")
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_cryptographer_backend() {
+        crate::ensure_initialized();
+        ece::crypto::test_cryptographer(RcCryptoCryptographer);
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/rc_crypto/error.rs.html b/book/rust-docs/src/rc_crypto/error.rs.html new file mode 100644 index 0000000000..e4817ed716 --- /dev/null +++ b/book/rust-docs/src/rc_crypto/error.rs.html @@ -0,0 +1,77 @@ +error.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#[derive(Debug, thiserror::Error)]
+pub enum ErrorKind {
+    #[error("NSS error: {0}")]
+    NSSError(#[from] nss::Error),
+    #[error("Internal crypto error")]
+    InternalError,
+    #[error("Conversion error: {0}")]
+    ConversionError(#[from] std::num::TryFromIntError),
+    #[error("Root hash format error: {0}")]
+    RootHashFormatError(String),
+    #[error("PEM content format error: {0}")]
+    PEMFormatError(String),
+    #[error("Certificate content error: {0}")]
+    CertificateContentError(String),
+    #[error("Certificate not yet valid or expired")]
+    CertificateValidityError,
+    #[error("Certificate subject mismatch")]
+    CertificateSubjectError,
+    #[error("Certificate issuer mismatch")]
+    CertificateIssuerError,
+    #[error("Certificate chain of trust error: {0}")]
+    CertificateChainError(String),
+    #[error("Signature content error: {0}")]
+    SignatureContentError(String),
+    #[error("Content signature mismatch error: {0}")]
+    SignatureMismatchError(String),
+}
+
+error_support::define_error! {
+    ErrorKind {
+        (ConversionError, std::num::TryFromIntError),
+        (NSSError, nss::Error),
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/rc_crypto/hawk_crypto.rs.html b/book/rust-docs/src/rc_crypto/hawk_crypto.rs.html new file mode 100644 index 0000000000..c6ede95f2c --- /dev/null +++ b/book/rust-docs/src/rc_crypto/hawk_crypto.rs.html @@ -0,0 +1,315 @@ +hawk_crypto.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::{digest, hmac, rand};
+use hawk::crypto as hc;
+
+impl From<crate::Error> for hc::CryptoError {
+    // Our errors impl `Fail`, so we can do this.
+    fn from(e: crate::Error) -> Self {
+        hc::CryptoError::Other(e.into())
+    }
+}
+
+pub(crate) struct RcCryptoCryptographer;
+
+impl hc::HmacKey for crate::hmac::SigningKey {
+    fn sign(&self, data: &[u8]) -> Result<Vec<u8>, hc::CryptoError> {
+        let digest = hmac::sign(self, data)?;
+        Ok(digest.as_ref().into())
+    }
+}
+
+// I don't really see a reason to bother doing incremental hashing here. A
+// one-shot is going to be faster in many cases anyway, and the higher memory
+// usage probably doesn't matter given our usage.
+struct NssHasher {
+    buffer: Vec<u8>,
+    algorithm: &'static digest::Algorithm,
+}
+
+impl hc::Hasher for NssHasher {
+    fn update(&mut self, data: &[u8]) -> Result<(), hc::CryptoError> {
+        self.buffer.extend_from_slice(data);
+        Ok(())
+    }
+
+    fn finish(&mut self) -> Result<Vec<u8>, hc::CryptoError> {
+        let digest = digest::digest(self.algorithm, &self.buffer)?;
+        let bytes: &[u8] = digest.as_ref();
+        Ok(bytes.to_owned())
+    }
+}
+
+impl hc::Cryptographer for RcCryptoCryptographer {
+    fn rand_bytes(&self, output: &mut [u8]) -> Result<(), hc::CryptoError> {
+        rand::fill(output)?;
+        Ok(())
+    }
+
+    fn new_key(
+        &self,
+        algorithm: hawk::DigestAlgorithm,
+        key: &[u8],
+    ) -> Result<Box<dyn hc::HmacKey>, hc::CryptoError> {
+        let k = hmac::SigningKey::new(to_rc_crypto_algorithm(algorithm)?, key);
+        Ok(Box::new(k))
+    }
+
+    fn constant_time_compare(&self, a: &[u8], b: &[u8]) -> bool {
+        crate::constant_time::verify_slices_are_equal(a, b).is_ok()
+    }
+
+    fn new_hasher(
+        &self,
+        algorithm: hawk::DigestAlgorithm,
+    ) -> Result<Box<dyn hc::Hasher>, hc::CryptoError> {
+        Ok(Box::new(NssHasher {
+            algorithm: to_rc_crypto_algorithm(algorithm)?,
+            buffer: vec![],
+        }))
+    }
+}
+
+fn to_rc_crypto_algorithm(
+    algorithm: hawk::DigestAlgorithm,
+) -> Result<&'static digest::Algorithm, hc::CryptoError> {
+    match algorithm {
+        hawk::DigestAlgorithm::Sha256 => Ok(&digest::SHA256),
+        algo => Err(hc::CryptoError::UnsupportedDigest(algo)),
+    }
+}
+
+// Note: this doesn't initialize NSS!
+pub(crate) fn init() {
+    hawk::crypto::set_cryptographer(&crate::hawk_crypto::RcCryptoCryptographer)
+        .expect("Failed to initialize `hawk` cryptographer!")
+}
+
+#[cfg(test)]
+mod test {
+
+    // Based on rust-hawk's hash_consistency. This fails if we've messed up the hashing.
+    #[test]
+    fn test_hawk_hashing() {
+        crate::ensure_initialized();
+        let mut hasher1 = hawk::PayloadHasher::new("text/plain", hawk::SHA256).unwrap();
+        hasher1.update("pày").unwrap();
+        hasher1.update("load").unwrap();
+        let hash1 = hasher1.finish().unwrap();
+
+        let mut hasher2 = hawk::PayloadHasher::new("text/plain", hawk::SHA256).unwrap();
+        hasher2.update("pàyload").unwrap();
+        let hash2 = hasher2.finish().unwrap();
+
+        let hash3 = hawk::PayloadHasher::hash("text/plain", hawk::SHA256, "pàyload").unwrap();
+
+        let hash4 = // "pàyload" as utf-8 bytes
+            hawk::PayloadHasher::hash("text/plain", hawk::SHA256, [112, 195, 160, 121, 108, 111, 97, 100]).unwrap();
+
+        assert_eq!(
+            hash1,
+            &[
+                228, 238, 241, 224, 235, 114, 158, 112, 211, 254, 118, 89, 25, 236, 87, 176, 181,
+                54, 61, 135, 42, 223, 188, 103, 194, 59, 83, 36, 136, 31, 198, 50
+            ]
+        );
+        assert_eq!(hash2, hash1);
+        assert_eq!(hash3, hash1);
+        assert_eq!(hash4, hash1);
+    }
+
+    // Based on rust-hawk's test_make_mac. This fails if we've messed up the signing.
+    #[test]
+    fn test_hawk_signing() {
+        crate::ensure_initialized();
+        let key = hawk::Key::new(
+            [
+                11u8, 19, 228, 209, 79, 189, 200, 59, 166, 47, 86, 254, 235, 184, 120, 197, 75,
+                152, 201, 79, 115, 61, 111, 242, 219, 187, 173, 14, 227, 108, 60, 232,
+            ],
+            hawk::SHA256,
+        )
+        .unwrap();
+
+        let mac = hawk::mac::Mac::new(
+            hawk::mac::MacType::Header,
+            &key,
+            std::time::UNIX_EPOCH + std::time::Duration::new(1000, 100),
+            "nonny",
+            "POST",
+            "mysite.com",
+            443,
+            "/v1/api",
+            None,
+            None,
+        )
+        .unwrap();
+        assert_eq!(
+            mac.as_ref(),
+            &[
+                192, 227, 235, 121, 157, 185, 197, 79, 189, 214, 235, 139, 9, 232, 99, 55, 67, 30,
+                68, 0, 150, 187, 192, 238, 21, 200, 209, 107, 245, 159, 243, 178
+            ]
+        );
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/rc_crypto/hkdf.rs.html b/book/rust-docs/src/rc_crypto/hkdf.rs.html new file mode 100644 index 0000000000..1af6246f5b --- /dev/null +++ b/book/rust-docs/src/rc_crypto/hkdf.rs.html @@ -0,0 +1,255 @@ +hkdf.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This file contains code that was copied from the ring crate which is under
+// the ISC license, reproduced below:
+
+// Copyright 2015-2017 Brian Smith.
+
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
+// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+use crate::{error::*, hmac};
+
+pub fn extract_and_expand(
+    salt: &hmac::SigningKey,
+    secret: &[u8],
+    info: &[u8],
+    out: &mut [u8],
+) -> Result<()> {
+    let prk = extract(salt, secret)?;
+    expand(&prk, info, out)?;
+    Ok(())
+}
+
+pub fn extract(salt: &hmac::SigningKey, secret: &[u8]) -> Result<hmac::SigningKey> {
+    let prk = hmac::sign(salt, secret)?;
+    Ok(hmac::SigningKey::new(salt.digest_algorithm(), prk.as_ref()))
+}
+
+pub fn expand(prk: &hmac::SigningKey, info: &[u8], out: &mut [u8]) -> Result<()> {
+    let mut derived =
+        nss::pk11::sym_key::hkdf_expand(prk.digest_alg, &prk.key_value, info, out.len())?;
+    out.swap_with_slice(&mut derived[0..out.len()]);
+    Ok(())
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::digest;
+
+    #[test]
+    fn hkdf_produces_correct_result() {
+        let secret = hex::decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").unwrap();
+        let salt = hex::decode("000102030405060708090a0b0c").unwrap();
+        let info = hex::decode("f0f1f2f3f4f5f6f7f8f9").unwrap();
+        let expected_out = hex::decode(
+            "3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865",
+        )
+        .unwrap();
+        let salt = hmac::SigningKey::new(&digest::SHA256, &salt);
+        let mut out = vec![0u8; expected_out.len()];
+        extract_and_expand(&salt, &secret, &info, &mut out).unwrap();
+        assert_eq!(out, expected_out);
+    }
+
+    #[test]
+    fn hkdf_rejects_gigantic_salt() {
+        if (std::u32::MAX as usize) < std::usize::MAX {
+            let salt_bytes = vec![0; (std::u32::MAX as usize) + 1];
+            let salt = hmac::SigningKey {
+                digest_alg: &digest::SHA256,
+                key_value: salt_bytes,
+            };
+            let mut out = vec![0u8; 8];
+            assert!(extract_and_expand(&salt, b"secret", b"info", &mut out).is_err());
+        }
+    }
+
+    #[test]
+    fn hkdf_rejects_gigantic_secret() {
+        if (std::u32::MAX as usize) < std::usize::MAX {
+            let salt = hmac::SigningKey::new(&digest::SHA256, b"salt");
+            let secret = vec![0; (std::u32::MAX as usize) + 1];
+            let mut out = vec![0u8; 8];
+            assert!(extract_and_expand(&salt, secret.as_slice(), b"info", &mut out).is_err());
+        }
+    }
+
+    // N.B. the `info `parameter is a `c_ulong`, and I can't figure out how to check whether
+    // `c_ulong` is smaller than `usize` in order to write a `hkdf_rejects_gigantic_info` test.
+
+    #[test]
+    fn hkdf_rejects_gigantic_output_buffers() {
+        let salt = hmac::SigningKey::new(&digest::SHA256, b"salt");
+        let mut out = vec![0u8; 8160 + 1]; // RFC maximum (hashlen * 255) + 1
+        assert!(extract_and_expand(&salt, b"secret", b"info", &mut out).is_err());
+    }
+
+    #[test]
+    fn hkdf_rejects_zero_length_output_buffer() {
+        let salt = hmac::SigningKey::new(&digest::SHA256, b"salt");
+        let mut out = vec![0u8; 0];
+        assert!(extract_and_expand(&salt, b"secret", b"info", &mut out).is_err());
+    }
+
+    #[test]
+    fn hkdf_can_produce_small_output() {
+        let salt = hmac::SigningKey::new(&digest::SHA256, b"salt");
+        let mut out = vec![0u8; 1];
+        assert!(extract_and_expand(&salt, b"secret", b"info", &mut out).is_ok());
+    }
+
+    #[test]
+    fn hkdf_accepts_zero_length_info() {
+        let salt = hmac::SigningKey::new(&digest::SHA256, b"salt");
+        let mut out = vec![0u8; 32];
+        assert!(extract_and_expand(&salt, b"secret", b"", &mut out).is_ok());
+    }
+
+    #[test]
+    fn hkdf_expand_rejects_short_prk() {
+        let prk = hmac::SigningKey::new(&digest::SHA256, b"too short"); // must be >= HashLen
+        let mut out = vec![0u8; 8];
+        assert!(expand(&prk, b"info", &mut out).is_ok());
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/rc_crypto/hmac.rs.html b/book/rust-docs/src/rc_crypto/hmac.rs.html new file mode 100644 index 0000000000..ad9965c484 --- /dev/null +++ b/book/rust-docs/src/rc_crypto/hmac.rs.html @@ -0,0 +1,399 @@ +hmac.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This file contains code that was copied from the ring crate which is under
+// the ISC license, reproduced below:
+
+// Copyright 2015-2017 Brian Smith.
+
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
+// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+use crate::{constant_time, digest, error::*};
+
+/// A calculated signature value.
+/// This is a type-safe wrappper that discourages attempts at comparing signatures
+/// for equality, which might naively be done using a non-constant-time comparison.
+#[derive(Clone)]
+pub struct Signature(pub(crate) digest::Digest);
+
+impl AsRef<[u8]> for Signature {
+    #[inline]
+    fn as_ref(&self) -> &[u8] {
+        self.0.as_ref()
+    }
+}
+
+/// A key to use for HMAC signing.
+pub struct SigningKey {
+    pub(crate) digest_alg: &'static digest::Algorithm,
+    pub(crate) key_value: Vec<u8>,
+}
+
+impl SigningKey {
+    pub fn new(digest_alg: &'static digest::Algorithm, key_value: &[u8]) -> Self {
+        SigningKey {
+            digest_alg,
+            key_value: key_value.to_vec(),
+        }
+    }
+
+    #[inline]
+    pub fn digest_algorithm(&self) -> &'static digest::Algorithm {
+        self.digest_alg
+    }
+}
+
+/// A key to use for HMAC authentication.
+pub struct VerificationKey {
+    wrapped: SigningKey,
+}
+
+impl VerificationKey {
+    pub fn new(digest_alg: &'static digest::Algorithm, key_value: &[u8]) -> Self {
+        VerificationKey {
+            wrapped: SigningKey::new(digest_alg, key_value),
+        }
+    }
+
+    #[inline]
+    pub fn digest_algorithm(&self) -> &'static digest::Algorithm {
+        self.wrapped.digest_algorithm()
+    }
+}
+
+/// Calculate the HMAC of `data` using `key` and verify it corresponds to the provided signature.
+pub fn verify(key: &VerificationKey, data: &[u8], signature: &[u8]) -> Result<()> {
+    verify_with_own_key(&key.wrapped, data, signature)
+}
+
+/// Equivalent to `verify` but allows the consumer to pass a `SigningKey`.
+pub fn verify_with_own_key(key: &SigningKey, data: &[u8], signature: &[u8]) -> Result<()> {
+    constant_time::verify_slices_are_equal(sign(key, data)?.as_ref(), signature)
+}
+
+/// Calculate the HMAC of `data` using `key`.
+pub fn sign(key: &SigningKey, data: &[u8]) -> Result<Signature> {
+    let value = nss::pk11::context::hmac_sign(key.digest_alg, &key.key_value, data)?;
+    Ok(Signature(digest::Digest {
+        value,
+        algorithm: *key.digest_alg,
+    }))
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    const KEY: &[u8] = b"key";
+    const MESSAGE: &[u8] = b"The quick brown fox jumps over the lazy dog";
+    const SIGNATURE_HEX: &str = "f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8";
+
+    #[test]
+    fn hmac_sign() {
+        let key = SigningKey::new(&digest::SHA256, KEY);
+        let signature = sign(&key, MESSAGE).unwrap();
+        let expected_signature = hex::decode(SIGNATURE_HEX).unwrap();
+        assert_eq!(signature.as_ref(), expected_signature.as_slice());
+        assert!(verify_with_own_key(&key, MESSAGE, &expected_signature).is_ok());
+    }
+
+    #[test]
+    fn hmac_sign_gives_different_signatures_for_different_keys() {
+        let key = SigningKey::new(&digest::SHA256, b"another key");
+        let signature = sign(&key, MESSAGE).unwrap();
+        let expected_signature = hex::decode(SIGNATURE_HEX).unwrap();
+        assert_ne!(signature.as_ref(), expected_signature.as_slice());
+    }
+
+    #[test]
+    fn hmac_sign_gives_different_signatures_for_different_messages() {
+        let key = SigningKey::new(&digest::SHA256, KEY);
+        let signature = sign(&key, b"a different message").unwrap();
+        let expected_signature = hex::decode(SIGNATURE_HEX).unwrap();
+        assert_ne!(signature.as_ref(), expected_signature.as_slice());
+    }
+
+    #[test]
+    fn hmac_verify() {
+        let key = VerificationKey::new(&digest::SHA256, KEY);
+        let expected_signature = hex::decode(SIGNATURE_HEX).unwrap();
+        assert!(verify(&key, MESSAGE, &expected_signature).is_ok());
+    }
+
+    #[test]
+    fn hmac_verify_fails_with_incorrect_signature() {
+        let key = VerificationKey::new(&digest::SHA256, KEY);
+        let signature = hex::decode(SIGNATURE_HEX).unwrap();
+        for i in 0..signature.len() {
+            let mut wrong_signature = signature.clone();
+            wrong_signature[i] = wrong_signature[i].wrapping_add(1);
+            assert!(verify(&key, MESSAGE, &wrong_signature).is_err());
+        }
+    }
+
+    #[test]
+    fn hmac_verify_fails_with_incorrect_key() {
+        let key = VerificationKey::new(&digest::SHA256, b"wrong key");
+        let signature = hex::decode(SIGNATURE_HEX).unwrap();
+        assert!(verify(&key, MESSAGE, &signature).is_err());
+    }
+
+    #[test]
+    fn hmac_sign_cleanly_rejects_gigantic_keys() {
+        if (std::u32::MAX as usize) < std::usize::MAX {
+            let key_bytes = vec![0; (std::u32::MAX as usize) + 1];
+            // Direct construction of SigningKey to avoid instantiating the array.
+            let key = SigningKey {
+                digest_alg: &digest::SHA256,
+                key_value: key_bytes,
+            };
+            assert!(sign(&key, MESSAGE).is_err());
+        }
+    }
+
+    #[test]
+    fn hmac_verify_cleanly_rejects_gigantic_keys() {
+        if (std::u32::MAX as usize) < std::usize::MAX {
+            let key_bytes = vec![0; (std::u32::MAX as usize) + 1];
+            // Direct construction of VerificationKey to avoid instantiating the array.
+            let key = VerificationKey {
+                wrapped: SigningKey {
+                    digest_alg: &digest::SHA256,
+                    key_value: key_bytes,
+                },
+            };
+            let signature = hex::decode(SIGNATURE_HEX).unwrap();
+            assert!(verify(&key, MESSAGE, &signature).is_err());
+        }
+    }
+
+    #[test]
+    fn hmac_sign_cleanly_rejects_gigantic_messages() {
+        if (std::u32::MAX as usize) < std::usize::MAX {
+            let key = SigningKey::new(&digest::SHA256, KEY);
+            let message = vec![0; (std::u32::MAX as usize) + 1];
+            assert!(sign(&key, &message).is_err());
+        }
+    }
+
+    #[test]
+    fn hmac_verify_cleanly_rejects_gigantic_messages() {
+        if (std::u32::MAX as usize) < std::usize::MAX {
+            let key = VerificationKey::new(&digest::SHA256, KEY);
+            let signature = hex::decode(SIGNATURE_HEX).unwrap();
+            let message = vec![0; (std::u32::MAX as usize) + 1];
+            assert!(verify(&key, &message, &signature).is_err());
+        }
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/rc_crypto/lib.rs.html b/book/rust-docs/src/rc_crypto/lib.rs.html new file mode 100644 index 0000000000..72c71b2fee --- /dev/null +++ b/book/rust-docs/src/rc_crypto/lib.rs.html @@ -0,0 +1,139 @@ +lib.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This file contains code that was copied from the ring crate which is under
+// the ISC license, reproduced below:
+
+// Copyright 2015-2017 Brian Smith.
+
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
+// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+#![allow(unknown_lints)]
+#![warn(rust_2018_idioms)]
+/// This crate provides all the cryptographic primitives required by
+/// this workspace, backed by the NSS library.
+/// The exposed API is pretty much the same as the `ring` crate.
+pub mod aead;
+pub mod agreement;
+pub mod constant_time;
+pub mod contentsignature;
+pub mod digest;
+#[cfg(feature = "ece")]
+pub mod ece_crypto;
+mod error;
+#[cfg(feature = "hawk")]
+mod hawk_crypto;
+pub mod hkdf;
+pub mod hmac;
+pub mod pbkdf2;
+pub mod rand;
+pub mod signature;
+
+// Expose `hawk` if the hawk feature is on. This avoids consumers needing to
+// configure this separately, which is more or less trivial to do incorrectly.
+#[cfg(feature = "hawk")]
+pub use hawk;
+
+// Expose `ece` if the ece feature is on. This avoids consumers needing to
+// configure this separately, which is more or less trivial to do incorrectly.
+#[cfg(feature = "ece")]
+pub use ece;
+
+pub use crate::error::{Error, ErrorKind, Result};
+
+/// Only required to be called if you intend to use this library in conjunction
+/// with the `hawk` or the `ece` crate.
+pub fn ensure_initialized() {
+    nss::ensure_initialized();
+    #[cfg(any(feature = "hawk", feature = "ece"))]
+    {
+        static INIT_ONCE: std::sync::Once = std::sync::Once::new();
+        INIT_ONCE.call_once(|| {
+            #[cfg(feature = "hawk")]
+            crate::hawk_crypto::init();
+            #[cfg(feature = "ece")]
+            crate::ece_crypto::init();
+        });
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/rc_crypto/pbkdf2.rs.html b/book/rust-docs/src/rc_crypto/pbkdf2.rs.html new file mode 100644 index 0000000000..a7e20d6714 --- /dev/null +++ b/book/rust-docs/src/rc_crypto/pbkdf2.rs.html @@ -0,0 +1,393 @@ +pbkdf2.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::error::*;
+use nss::pbkdf2::pbkdf2_key_derive;
+pub use nss::pbkdf2::HashAlgorithm;
+/// Extend passwords using pbkdf2, based on the following [rfc](https://www.ietf.org/rfc/rfc2898.txt) it runs the NSS implementation
+/// # Arguments
+///
+///  * `passphrase` - The password to stretch
+///  * `salt` - A salt to use in the generation process
+///  * `iterations` - The number of iterations the hashing algorithm will run on each section of the key
+///  * `hash_algorithm` - The hash algorithm to use
+///  * `out` - The slice the algorithm will populate
+///
+/// # Examples
+///
+/// ```
+/// use rc_crypto::pbkdf2;
+/// let password = b"password";
+/// let salt = b"salt";
+/// let mut out = vec![0u8; 32];
+/// let iterations = 2; // Real code should have a MUCH higher number of iterations (Think 1000+)
+/// pbkdf2::derive(password, salt, iterations, pbkdf2::HashAlgorithm::SHA256, &mut out).unwrap(); // Oh oh should handle the error!
+/// assert_eq!(hex::encode(out), "ae4d0c95af6b46d32d0adff928f06dd02a303f8ef3c251dfd6e2d85a95474c43");
+//
+///```
+///
+/// # Errors
+///
+/// Could possibly return an error if the HMAC algorithm fails, or if the NSS algorithm returns an error
+pub fn derive(
+    passphrase: &[u8],
+    salt: &[u8],
+    iterations: u32,
+    hash_algorithm: HashAlgorithm,
+    out: &mut [u8],
+) -> Result<()> {
+    pbkdf2_key_derive(passphrase, salt, iterations, hash_algorithm, out)?;
+    Ok(())
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    #[test]
+    fn test_generate_correct_out() {
+        let expected = "120fb6cffcf8b32c43e7225256c4f837a86548c92ccc35480805987cb70be17b";
+        let mut out = vec![0u8; 32];
+        let password = b"password";
+        let salt = b"salt";
+        derive(password, salt, 1, HashAlgorithm::SHA256, &mut out).unwrap();
+        assert_eq!(expected, hex::encode(out));
+    }
+
+    #[test]
+    fn test_longer_key() {
+        let expected = "120fb6cffcf8b32c43e7225256c4f837a86548c92ccc35480805987cb70be17b4dbf3a2f3dad3377264bb7b8e8330d4efc7451418617dabef683735361cdc18c";
+        let password = b"password";
+        let salt = b"salt";
+        let mut out = vec![0u8; 64];
+        derive(password, salt, 1, HashAlgorithm::SHA256, &mut out).unwrap();
+        assert_eq!(expected, hex::encode(out));
+    }
+
+    #[test]
+    fn test_more_iterations() {
+        let expected = "ae4d0c95af6b46d32d0adff928f06dd02a303f8ef3c251dfd6e2d85a95474c43";
+        let password = b"password";
+        let salt = b"salt";
+        let mut out = vec![0u8; 32];
+        derive(password, salt, 2, HashAlgorithm::SHA256, &mut out).unwrap();
+        assert_eq!(expected, hex::encode(out));
+    }
+
+    #[test]
+    fn test_odd_length() {
+        let expected = "ad35240ac683febfaf3cd49d845473fbbbaa2437f5f82d5a415ae00ac76c6bfccf";
+        let password = b"password";
+        let salt = b"salt";
+        let mut out = vec![0u8; 33];
+        derive(password, salt, 3, HashAlgorithm::SHA256, &mut out).unwrap();
+        assert_eq!(expected, hex::encode(out));
+    }
+
+    #[test]
+    fn test_nulls() {
+        let expected = "e25d526987819f966e324faa4a";
+        let password = b"passw\x00rd";
+        let salt = b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
+        let mut out = vec![0u8; 13];
+        derive(password, salt, 5, HashAlgorithm::SHA256, &mut out).unwrap();
+        assert_eq!(expected, hex::encode(out));
+    }
+
+    #[test]
+    fn test_password_null() {
+        let expected = "62384466264daadc4144018c6bd864648272b34da8980d31521ffcce92ae003b";
+        let password = b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
+        let salt = b"salt";
+        let mut out = vec![0u8; 32];
+        derive(password, salt, 2, HashAlgorithm::SHA256, &mut out).unwrap();
+        assert_eq!(expected, hex::encode(out));
+    }
+
+    #[test]
+    fn test_empty_password() {
+        let expected = "f135c27993baf98773c5cdb40a5706ce6a345cde61b000a67858650cd6a324d7";
+        let mut out = vec![0u8; 32];
+        let password = b"";
+        let salt = b"salt";
+        derive(password, salt, 1, HashAlgorithm::SHA256, &mut out).unwrap();
+        assert_eq!(expected, hex::encode(out));
+    }
+
+    #[test]
+    fn test_empty_salt() {
+        let expected = "c1232f10f62715fda06ae7c0a2037ca19b33cf103b727ba56d870c11f290a2ab";
+        let mut out = vec![0u8; 32];
+        let password = b"password";
+        let salt = b"";
+        derive(password, salt, 1, HashAlgorithm::SHA256, &mut out).unwrap();
+        assert_eq!(expected, hex::encode(out));
+    }
+
+    #[test]
+    fn test_tiny_out() {
+        let expected = "12";
+        let mut out = vec![0u8; 1];
+        let password = b"password";
+        let salt = b"salt";
+        derive(password, salt, 1, HashAlgorithm::SHA256, &mut out).unwrap();
+        assert_eq!(expected, hex::encode(out));
+    }
+
+    #[test]
+    fn test_rejects_zero_iterations() {
+        let mut out = vec![0u8; 32];
+        let password = b"password";
+        let salt = b"salt";
+        assert!(derive(password, salt, 0, HashAlgorithm::SHA256, &mut out).is_err());
+    }
+
+    #[test]
+    fn test_rejects_empty_out() {
+        let mut out = vec![0u8; 0];
+        let password = b"password";
+        let salt = b"salt";
+        assert!(derive(password, salt, 1, HashAlgorithm::SHA256, &mut out).is_err());
+    }
+
+    #[test]
+    fn test_rejects_gaigantic_salt() {
+        if (std::u32::MAX as usize) < std::usize::MAX {
+            let salt = vec![0; (std::u32::MAX as usize) + 1];
+            let mut out = vec![0u8; 1];
+            let password = b"password";
+            assert!(derive(password, &salt, 1, HashAlgorithm::SHA256, &mut out).is_err());
+        }
+    }
+    #[test]
+    fn test_rejects_gaigantic_password() {
+        if (std::u32::MAX as usize) < std::usize::MAX {
+            let password = vec![0; (std::u32::MAX as usize) + 1];
+            let mut out = vec![0u8; 1];
+            let salt = b"salt";
+            assert!(derive(&password, salt, 1, HashAlgorithm::SHA256, &mut out).is_err());
+        }
+    }
+
+    #[test]
+    fn test_rejects_gaigantic_out() {
+        if (std::u32::MAX as usize) < std::usize::MAX {
+            let password = b"password";
+            let mut out = vec![0; (std::u32::MAX as usize) + 1];
+            let salt = b"salt";
+            assert!(derive(password, salt, 1, HashAlgorithm::SHA256, &mut out).is_err());
+        }
+    }
+
+    #[test]
+    fn test_rejects_gaigantic_iterations() {
+        let password = b"password";
+        let mut out = vec![0; 32];
+        let salt = b"salt";
+        assert!(derive(
+            password,
+            salt,
+            std::u32::MAX,
+            HashAlgorithm::SHA256,
+            &mut out
+        )
+        .is_err());
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/rc_crypto/rand.rs.html b/book/rust-docs/src/rc_crypto/rand.rs.html new file mode 100644 index 0000000000..705e40d075 --- /dev/null +++ b/book/rust-docs/src/rc_crypto/rand.rs.html @@ -0,0 +1,141 @@ +rand.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This file contains code that was copied from the ring crate which is under
+// the ISC license, reproduced below:
+
+// Copyright 2015-2017 Brian Smith.
+
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
+// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+use crate::error::*;
+
+/// Fill a buffer with cryptographically secure pseudo-random data.
+pub fn fill(dest: &mut [u8]) -> Result<()> {
+    Ok(nss::pk11::slot::generate_random(dest)?)
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn random_fill() {
+        let mut out = vec![0u8; 64];
+        assert!(fill(&mut out).is_ok());
+        // This check could *in theory* fail if we randomly generate all zeroes
+        // but we're treating that probability as negligible in practice.
+        assert_ne!(out, vec![0u8; 64]);
+
+        let mut out2 = vec![0u8; 64];
+        assert!(fill(&mut out2).is_ok());
+        assert_ne!(out, vec![0u8; 64]);
+        assert_ne!(out2, out);
+    }
+
+    #[test]
+    fn random_fill_empty() {
+        let mut out = vec![0u8; 0];
+        assert!(fill(&mut out).is_ok());
+        assert_eq!(out, vec![0u8; 0]);
+    }
+
+    #[test]
+    fn random_fill_oddly_sized_arrays() {
+        let sizes: [usize; 4] = [61, 63, 65, 67];
+        for size in &sizes {
+            let mut out = vec![0u8; *size];
+            assert!(fill(&mut out).is_ok());
+            assert_ne!(out, vec![0u8; *size]);
+        }
+    }
+
+    #[test]
+    fn random_fill_rejects_attempts_to_fill_gigantic_arrays() {
+        let max_size: usize = std::i32::MAX as usize;
+        let mut out = vec![0u8; max_size + 1];
+        assert!(fill(&mut out).is_err());
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/rc_crypto/signature.rs.html b/book/rust-docs/src/rc_crypto/signature.rs.html new file mode 100644 index 0000000000..d301b081ed --- /dev/null +++ b/book/rust-docs/src/rc_crypto/signature.rs.html @@ -0,0 +1,219 @@ +signature.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This file contains code that was copied from the ring crate which is under
+// the ISC license, reproduced below:
+
+// Copyright 2015-2017 Brian Smith.
+
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
+// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+use crate::Result;
+use nss::{ec::Curve, ec::PublicKey, pbkdf2::HashAlgorithm};
+
+/// A signature verification algorithm.
+pub struct VerificationAlgorithm {
+    curve: Curve,
+    digest_alg: HashAlgorithm,
+}
+
+pub static ECDSA_P256_SHA256: VerificationAlgorithm = VerificationAlgorithm {
+    curve: Curve::P256,
+    digest_alg: HashAlgorithm::SHA256,
+};
+
+pub static ECDSA_P384_SHA384: VerificationAlgorithm = VerificationAlgorithm {
+    curve: Curve::P384,
+    digest_alg: HashAlgorithm::SHA384,
+};
+
+/// An unparsed public key for signature operations.
+pub struct UnparsedPublicKey<'a> {
+    alg: &'static VerificationAlgorithm,
+    bytes: &'a [u8],
+}
+
+impl<'a> UnparsedPublicKey<'a> {
+    pub fn new(algorithm: &'static VerificationAlgorithm, bytes: &'a [u8]) -> Self {
+        Self {
+            alg: algorithm,
+            bytes,
+        }
+    }
+
+    pub fn verify(&self, message: &[u8], signature: &[u8]) -> Result<()> {
+        let pub_key = PublicKey::from_bytes(self.alg.curve, self.bytes)?;
+        Ok(pub_key.verify(message, signature, self.alg.digest_alg)?)
+    }
+
+    pub fn algorithm(&self) -> &'static VerificationAlgorithm {
+        self.alg
+    }
+
+    pub fn bytes(&self) -> &'a [u8] {
+        self.bytes
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
+
+    #[test]
+    fn test_ecdsa_p384_sha384_verify() {
+        // Test generated with JS DOM's WebCrypto.
+        let pub_key_bytes = URL_SAFE_NO_PAD.decode(
+            "BMZj_xHOfLQn5DIEQcYUkyASDWo8O30gWdkWXHHHWN5owKhGWplYHEb4PLf3DkFTg_smprr-ApdULy3NV10x8IZ0EfVaUZdXvTquH1kiw2PxD7fhqiozMXUaSuZI5KBE6w",
+        ).unwrap();
+        let message = URL_SAFE_NO_PAD.decode(
+            "F9MQDmEEdvOfm-NkCRrXqG-aVA9kq0xqtjvtWLndmmt6bO2gfLE2CVDDLzJYds0n88uz27c5JkzdsLpm5HP3aLFgD8bgnGm-EgdBpm99CRiIm7mAMbb0-NRAyUxeoGmdgJPVQLWFNoHRwzKV2wZ0Bk-Bq7jkeDHmDfnx-CJKVMQ",
+        )
+        .unwrap();
+        let signature = URL_SAFE_NO_PAD.decode(
+            "XLZmtJweW4qx0u0l6EpfmB5z-S-CNj4mrl9d7U0MuftdNPhmlNacV4AKR-i4uNn0TUIycU7GsfIjIqxuiL9WdAnfq_KH_SJ95mduqXgWNKlyt8JgMLd4h-jKOllh4erh",
+        )
+        .unwrap();
+        let public_key =
+            crate::signature::UnparsedPublicKey::new(&ECDSA_P384_SHA384, &pub_key_bytes);
+
+        // Failure case: Wrong key algorithm.
+        let public_key_wrong_alg =
+            crate::signature::UnparsedPublicKey::new(&ECDSA_P256_SHA256, &pub_key_bytes);
+        assert!(public_key_wrong_alg.verify(&message, &signature).is_err());
+
+        // Failure case: Add garbage to signature.
+        let mut garbage_signature = signature.clone();
+        garbage_signature.push(42);
+        assert!(public_key.verify(&message, &garbage_signature).is_err());
+
+        // Failure case: Flip a bit in message.
+        let mut garbage_message = message.clone();
+        garbage_message[42] = 42;
+        assert!(public_key.verify(&garbage_message, &signature).is_err());
+
+        // Happy case.
+        assert!(public_key.verify(&message, &signature).is_ok());
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/rc_log_ffi/android.rs.html b/book/rust-docs/src/rc_log_ffi/android.rs.html new file mode 100644 index 0000000000..8387c97645 --- /dev/null +++ b/book/rust-docs/src/rc_log_ffi/android.rs.html @@ -0,0 +1,437 @@ +android.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! This is the android backend for rc_log. It has a decent amount of
+//! complexity, as Rust logs can be emitted by any thread, regardless of whether
+//! or not they have an associated JVM thread. JNA's Callback class helps us
+//! here, by providing a way for mapping native threads to JVM threads.
+//! Unfortunately, naive usage of this class in a multithreaded context will be
+//! very suboptimal in terms of memory and thread usage.
+//!
+//! To avoid this, we only call into the JVM from a single thread, which we
+//! launch when initializing the logger. This thread just polls a channel
+//! listening for log messages, where a log message is an enum (`LogMessage`)
+//! that either tells it to log an item, or to stop logging all together.
+//!
+//! 1. We cannot guarantee that the callback from android lives past when the
+//!    android code tells us to stop logging, so in order to be memory safe, we
+//!    need to stop logging immediately when this happens. We do this using an
+//!    `Arc<AtomicBool>`, used to indicate that we should stop logging.
+//!
+//! 2. There's no safe way to terminate a thread in Rust (for good reason), so
+//!    the background thread must close willingly. To make sure this happens
+//!    promptly (e.g. to avoid a case where we're blocked until some thread
+//!    somewhere else happens to log something), we need to add something onto
+//!    the log channel, hence the existence of `LogMessage::Stop`.
+//!
+//!    It's important to note that because of point 1, the polling thread may
+//!    have to stop prior to getting `LogMessage::Stop`. We do not want to wait
+//!    for it to process whatever log messages were sent prior to being told to
+//!    stop.
+
+use std::{
+    ffi::CString,
+    os::raw::c_char,
+    sync::{
+        atomic::{AtomicBool, Ordering},
+        mpsc::{sync_channel, SyncSender},
+        Arc, Mutex,
+    },
+    thread,
+};
+
+use crate::LogLevel;
+
+/// Type of the log callback provided to us by java/swift. Takes the following
+/// arguments:
+///
+/// - Log level (an i32).
+///
+/// - Tag: a (nullable) nul terminated c string. The callback must not free this
+///   string, which is only valid until the the callback returns. If you need
+///   it past that, you must copy it into an internal buffer!
+///
+/// - Message: a (non-nullable) nul terminated c string. The callback must not free this
+///   string, which is only valid until the the callback returns. If you need
+///   it past that, you must copy it into an internal buffer!
+///
+/// and returns 0 if we should close the thread, and 1 otherwise. This is done
+/// because attempting to call `disable` from within the log callback will
+/// deadlock.
+pub type LogCallback = unsafe extern "C" fn(i32, *const c_char, *const c_char) -> u8;
+
+// TODO: use serde to send this to the other thread as bincode or something,
+// rather than allocating all these strings for every message.
+struct LogRecord {
+    level: LogLevel,
+    tag: Option<CString>,
+    message: CString,
+}
+
+impl<'a, 'b> From<&'b log::Record<'a>> for LogRecord {
+    // XXX important! Don't log in this function!
+    fn from(r: &'b log::Record<'a>) -> Self {
+        let thread_id = format!("{:?}", std::thread::current().id());
+        let thread_id = if thread_id.starts_with("ThreadId(") && thread_id.ends_with(')') {
+            format!("t{}", &thread_id[9..(thread_id.len() - 1)])
+        } else {
+            thread_id
+        };
+        let message = format!("{} {}", thread_id, r.args());
+        let level = LogLevel::from_level_and_message(r.level(), &message);
+        Self {
+            level,
+            tag: r
+                .module_path()
+                .and_then(|mp| CString::new(mp.to_owned()).ok()),
+            message: crate::string_to_cstring_lossy(message),
+        }
+    }
+}
+
+enum LogMessage {
+    Stop,
+    Record(LogRecord),
+}
+
+pub struct LogAdapterState {
+    // Thread handle for the BG thread.
+    handle: Option<std::thread::JoinHandle<()>>,
+    stopped: Arc<Mutex<bool>>,
+    sender: SyncSender<LogMessage>,
+}
+
+pub struct LogSink {
+    sender: SyncSender<LogMessage>,
+    // Used locally for preventing unnecessary work after the `sender`
+    // is closed. Not shared. Not required for correctness.
+    disabled: AtomicBool,
+}
+
+impl log::Log for LogSink {
+    fn enabled(&self, _metadata: &log::Metadata<'_>) -> bool {
+        // Really this could just be Acquire but whatever
+        !self.disabled.load(Ordering::SeqCst)
+    }
+
+    fn flush(&self) {}
+    fn log(&self, record: &log::Record<'_>) {
+        // Important: we check stopped before writing, which means
+        // it must be set before
+        if self.disabled.load(Ordering::SeqCst) {
+            // Note: `enabled` is not automatically called.
+            return;
+        }
+        // Either the queue is full, or the receiver is closed.
+        // In either case, we want to stop all logging immediately.
+        if self
+            .sender
+            .try_send(LogMessage::Record(record.into()))
+            .is_err()
+        {
+            self.disabled.store(true, Ordering::SeqCst);
+        }
+    }
+}
+
+impl LogAdapterState {
+    #[allow(clippy::mutex_atomic)]
+    pub fn init(callback: LogCallback) -> Self {
+        // This uses a mutex (instead of an atomic bool) to avoid a race condition
+        // where `stopped` gets set by another thread between when we read it and
+        // when we call the callback. This way, they'll block.
+        let stopped = Arc::new(Mutex::new(false));
+        let (message_sender, message_recv) = sync_channel(4096);
+        let handle = {
+            let stopped = stopped.clone();
+            thread::spawn(move || {
+                // We stop if we see `Err` (which means the channel got closed,
+                // which probably can't happen since the sender owned by the
+                // logger will never get dropped), or if we get `LogMessage::Stop`,
+                // which means we should stop processing.
+                while let Ok(LogMessage::Record(record)) = message_recv.recv() {
+                    let LogRecord {
+                        tag,
+                        level,
+                        message,
+                    } = record;
+                    let tag_ptr = tag
+                        .as_ref()
+                        .map(|s| s.as_ptr())
+                        .unwrap_or_else(std::ptr::null);
+                    let msg_ptr = message.as_ptr();
+
+                    let mut stop_guard = stopped.lock().unwrap();
+                    if *stop_guard {
+                        return;
+                    }
+                    let keep_going = unsafe { callback(level as i32, tag_ptr, msg_ptr) };
+                    if keep_going == 0 {
+                        *stop_guard = true;
+                        return;
+                    }
+                }
+            })
+        };
+
+        let sink = LogSink {
+            sender: message_sender.clone(),
+            disabled: AtomicBool::new(false),
+        };
+
+        crate::settable_log::set_logger(Box::new(sink));
+        log::set_max_level(log::LevelFilter::Debug);
+        log::info!("rc_log adapter initialized!");
+        Self {
+            handle: Some(handle),
+            stopped,
+            sender: message_sender,
+        }
+    }
+}
+
+impl Drop for LogAdapterState {
+    fn drop(&mut self) {
+        {
+            // It would be nice to write a log that says something like
+            // "if we deadlock here it's because you tried to close the
+            // log adapter from within the log callback", but, well, we
+            // can't exactly log anything from here (and even if we could,
+            // they'd never see it if they hit that situation)
+            let mut stop_guard = self.stopped.lock().unwrap();
+            *stop_guard = true;
+            // We can ignore a failure here because it means either
+            // - The recv is dropped, in which case we don't need to send anything
+            // - The recv is completely full, in which case it will see the flag we
+            //   wrote into `stop_guard` soon enough anyway.
+            let _ = self.sender.try_send(LogMessage::Stop);
+        }
+        // Wait for the calling thread to stop. This should be relatively
+        // quickly unless something terrible has happened.
+        if let Some(h) = self.handle.take() {
+            h.join().unwrap();
+        }
+    }
+}
+
+ffi_support::implement_into_ffi_by_pointer!(LogAdapterState);
+
\ No newline at end of file diff --git a/book/rust-docs/src/rc_log_ffi/lib.rs.html b/book/rust-docs/src/rc_log_ffi/lib.rs.html new file mode 100644 index 0000000000..1a541bc6bc --- /dev/null +++ b/book/rust-docs/src/rc_log_ffi/lib.rs.html @@ -0,0 +1,341 @@ +lib.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! This crate allows users from the other side of the FFI to hook into Rust's
+//! `log` crate, which is used by us and several of our dependencies. The
+//! primary use case is providing logs to Android and iOS in a way that is more
+//! flexible than writing to liblog (which goes to logcat, which cannot be
+//! accessed by programs on the device, short of rooting it), or stdout/stderr.
+//!
+//! See the header comment in android.rs and fallback.rs for details.
+//!
+//! It's worth noting that the log crate is rather inflexable, in that
+//! it does not allow users to change loggers after the first initialization. We
+//! work around this using our `settable_log` module.
+
+#![allow(unknown_lints)]
+#![allow(clippy::upper_case_acronyms)]
+#![warn(rust_2018_idioms)]
+// We always include both modules when doing test builds, so for test builds,
+// allow dead code.
+#![cfg_attr(test, allow(dead_code))]
+
+use std::ffi::CString;
+
+// Import this in tests (even on non-android builds / cases where the
+// force_android feature is not enabled) so we can check that it compiles
+// easily.
+#[cfg(any(test, os = "android", feature = "force_android"))]
+pub mod android;
+// Import this in tests (even if we're building for android or force_android is
+// turned on) so we can check that it compiles easily
+#[cfg(any(test, not(any(os = "android", feature = "force_android"))))]
+pub mod ios;
+
+mod settable_log;
+
+cfg_if::cfg_if! {
+    if #[cfg(any(os = "android", feature = "force_android"))] {
+        use crate::android as imp;
+    } else {
+        use crate::ios as imp;
+    }
+}
+
+pub(crate) fn string_to_cstring_lossy(s: String) -> CString {
+    let mut bytes = s.into_bytes();
+    for byte in bytes.iter_mut() {
+        if *byte == 0 {
+            *byte = b'?';
+        }
+    }
+    CString::new(bytes).expect("Bug in string_to_cstring_lossy!")
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+#[repr(i32)]
+pub enum LogLevel {
+    // Android logger levels
+    VERBOSE = 2,
+    DEBUG = 3,
+    INFO = 4,
+    WARN = 5,
+    ERROR = 6,
+}
+
+impl LogLevel {
+    /// Equivalent to the `into()` conversion but avoids reporting network
+    /// errors as errors, and downgrades them into warnings.
+    pub(crate) fn from_level_and_message(mut level: log::Level, msg: &str) -> Self {
+        if level == log::Level::Error && msg.contains("[no-sentry]") {
+            level = log::Level::Warn;
+        }
+        level.into()
+    }
+}
+
+impl From<log::Level> for LogLevel {
+    fn from(l: log::Level) -> Self {
+        match l {
+            log::Level::Trace => LogLevel::VERBOSE,
+            log::Level::Debug => LogLevel::DEBUG,
+            log::Level::Info => LogLevel::INFO,
+            log::Level::Warn => LogLevel::WARN,
+            log::Level::Error => LogLevel::ERROR,
+        }
+    }
+}
+
+#[no_mangle]
+pub extern "C" fn rc_log_adapter_create(
+    callback: imp::LogCallback,
+    out_err: &mut ffi_support::ExternError,
+) -> *mut imp::LogAdapterState {
+    ffi_support::call_with_output(out_err, || imp::LogAdapterState::init(callback))
+}
+
+// Note: keep in sync with LogLevelFilter in kotlin.
+fn level_filter_from_i32(level_arg: i32) -> log::LevelFilter {
+    match level_arg {
+        4 => log::LevelFilter::Debug,
+        3 => log::LevelFilter::Info,
+        2 => log::LevelFilter::Warn,
+        1 => log::LevelFilter::Error,
+        // We clamp out of bounds level values.
+        n if n <= 0 => log::LevelFilter::Off,
+        n if n >= 5 => log::LevelFilter::Trace,
+        _ => unreachable!("This is actually exhaustive"),
+    }
+}
+
+#[no_mangle]
+pub extern "C" fn rc_log_adapter_set_max_level(level: i32, out_err: &mut ffi_support::ExternError) {
+    ffi_support::call_with_output(out_err, || log::set_max_level(level_filter_from_i32(level)))
+}
+
+// Can't use define_box_destructor because this can panic. TODO: Maybe we should
+// keep this around globally (as lazy_static or something) and basically just
+// turn it on/off in create/destroy... Might be more reliable?
+/// # Safety
+/// Unsafe because it frees it's argument.
+#[no_mangle]
+pub unsafe extern "C" fn rc_log_adapter_destroy(to_destroy: *mut imp::LogAdapterState) {
+    ffi_support::abort_on_panic::call_with_output(move || {
+        log::set_max_level(log::LevelFilter::Off);
+        drop(Box::from_raw(to_destroy));
+        settable_log::unset_logger();
+    })
+}
+
+// Used just to allow tests to produce logs.
+#[no_mangle]
+pub extern "C" fn rc_log_adapter_test__log_msg(msg: ffi_support::FfiStr<'_>) {
+    ffi_support::abort_on_panic::call_with_output(|| {
+        log::info!("testing: {}", msg.as_str());
+    });
+}
+
+ffi_support::define_string_destructor!(rc_log_adapter_destroy_string);
+
+#[cfg(test)]
+mod test {
+    use super::*;
+    #[test]
+    fn test_level_msg() {
+        assert_eq!(
+            LogLevel::ERROR,
+            LogLevel::from_level_and_message(
+                log::Level::Error,
+                "Rusqlite Error: The database is wrong and bad.",
+            ),
+            "Normal errors should come through as errors",
+        );
+
+        assert_eq!(
+            LogLevel::WARN,
+            LogLevel::from_level_and_message(
+                log::Level::Error,
+                "Network Error: [no-sentry] Network error: the server is furious at you.",
+            ),
+            "[no-sentry] errors should come through as warnings",
+        );
+
+        assert_eq!(
+            LogLevel::INFO,
+            LogLevel::from_level_and_message(log::Level::Info, "[no-sentry] 🙀"),
+            "Everything else should be unchanged, even if it has a [no-sentry] tag",
+        );
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/rc_log_ffi/settable_log.rs.html b/book/rust-docs/src/rc_log_ffi/settable_log.rs.html new file mode 100644 index 0000000000..4d38dc0cc2 --- /dev/null +++ b/book/rust-docs/src/rc_log_ffi/settable_log.rs.html @@ -0,0 +1,149 @@ +settable_log.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use lazy_static::lazy_static;
+use std::sync::{Once, RwLock};
+
+use log::Log;
+
+struct SettableLog {
+    inner: RwLock<Option<Box<dyn Log>>>,
+}
+
+lazy_static! {
+    static ref SETTABLE_LOG: SettableLog = SettableLog {
+        inner: RwLock::new(None)
+    };
+}
+
+impl SettableLog {
+    fn set(&self, logger: Box<dyn Log>) {
+        let mut write_lock = self.inner.write().unwrap();
+        *write_lock = Some(logger);
+    }
+
+    fn unset(&self) {
+        let mut write_lock = self.inner.write().unwrap();
+        drop(write_lock.take());
+    }
+}
+
+impl Log for SettableLog {
+    fn enabled(&self, metadata: &log::Metadata<'_>) -> bool {
+        let inner = self.inner.read().unwrap();
+        if let Some(log) = &*inner {
+            log.enabled(metadata)
+        } else {
+            false
+        }
+    }
+
+    fn flush(&self) {
+        let inner = self.inner.read().unwrap();
+        if let Some(log) = &*inner {
+            log.flush();
+        }
+    }
+
+    fn log(&self, record: &log::Record<'_>) {
+        let inner = self.inner.read().unwrap();
+        if let Some(log) = &*inner {
+            log.log(record);
+        }
+    }
+}
+
+pub fn init_once() {
+    static INITIALIZER: Once = Once::new();
+    INITIALIZER.call_once(|| {
+        log::set_logger(&*SETTABLE_LOG).expect(
+            "Failed to initialize SettableLog, other log implementation already initialized?",
+        );
+    });
+}
+
+pub fn set_logger(logger: Box<dyn Log>) {
+    init_once();
+    SETTABLE_LOG.set(logger);
+}
+
+pub fn unset_logger() {
+    init_once();
+    SETTABLE_LOG.unset();
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/remote_settings/client.rs.html b/book/rust-docs/src/remote_settings/client.rs.html new file mode 100644 index 0000000000..77e3c99505 --- /dev/null +++ b/book/rust-docs/src/remote_settings/client.rs.html @@ -0,0 +1,2189 @@ +client.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
+540
+541
+542
+543
+544
+545
+546
+547
+548
+549
+550
+551
+552
+553
+554
+555
+556
+557
+558
+559
+560
+561
+562
+563
+564
+565
+566
+567
+568
+569
+570
+571
+572
+573
+574
+575
+576
+577
+578
+579
+580
+581
+582
+583
+584
+585
+586
+587
+588
+589
+590
+591
+592
+593
+594
+595
+596
+597
+598
+599
+600
+601
+602
+603
+604
+605
+606
+607
+608
+609
+610
+611
+612
+613
+614
+615
+616
+617
+618
+619
+620
+621
+622
+623
+624
+625
+626
+627
+628
+629
+630
+631
+632
+633
+634
+635
+636
+637
+638
+639
+640
+641
+642
+643
+644
+645
+646
+647
+648
+649
+650
+651
+652
+653
+654
+655
+656
+657
+658
+659
+660
+661
+662
+663
+664
+665
+666
+667
+668
+669
+670
+671
+672
+673
+674
+675
+676
+677
+678
+679
+680
+681
+682
+683
+684
+685
+686
+687
+688
+689
+690
+691
+692
+693
+694
+695
+696
+697
+698
+699
+700
+701
+702
+703
+704
+705
+706
+707
+708
+709
+710
+711
+712
+713
+714
+715
+716
+717
+718
+719
+720
+721
+722
+723
+724
+725
+726
+727
+728
+729
+730
+731
+732
+733
+734
+735
+736
+737
+738
+739
+740
+741
+742
+743
+744
+745
+746
+747
+748
+749
+750
+751
+752
+753
+754
+755
+756
+757
+758
+759
+760
+761
+762
+763
+764
+765
+766
+767
+768
+769
+770
+771
+772
+773
+774
+775
+776
+777
+778
+779
+780
+781
+782
+783
+784
+785
+786
+787
+788
+789
+790
+791
+792
+793
+794
+795
+796
+797
+798
+799
+800
+801
+802
+803
+804
+805
+806
+807
+808
+809
+810
+811
+812
+813
+814
+815
+816
+817
+818
+819
+820
+821
+822
+823
+824
+825
+826
+827
+828
+829
+830
+831
+832
+833
+834
+835
+836
+837
+838
+839
+840
+841
+842
+843
+844
+845
+846
+847
+848
+849
+850
+851
+852
+853
+854
+855
+856
+857
+858
+859
+860
+861
+862
+863
+864
+865
+866
+867
+868
+869
+870
+871
+872
+873
+874
+875
+876
+877
+878
+879
+880
+881
+882
+883
+884
+885
+886
+887
+888
+889
+890
+891
+892
+893
+894
+895
+896
+897
+898
+899
+900
+901
+902
+903
+904
+905
+906
+907
+908
+909
+910
+911
+912
+913
+914
+915
+916
+917
+918
+919
+920
+921
+922
+923
+924
+925
+926
+927
+928
+929
+930
+931
+932
+933
+934
+935
+936
+937
+938
+939
+940
+941
+942
+943
+944
+945
+946
+947
+948
+949
+950
+951
+952
+953
+954
+955
+956
+957
+958
+959
+960
+961
+962
+963
+964
+965
+966
+967
+968
+969
+970
+971
+972
+973
+974
+975
+976
+977
+978
+979
+980
+981
+982
+983
+984
+985
+986
+987
+988
+989
+990
+991
+992
+993
+994
+995
+996
+997
+998
+999
+1000
+1001
+1002
+1003
+1004
+1005
+1006
+1007
+1008
+1009
+1010
+1011
+1012
+1013
+1014
+1015
+1016
+1017
+1018
+1019
+1020
+1021
+1022
+1023
+1024
+1025
+1026
+1027
+1028
+1029
+1030
+1031
+1032
+1033
+1034
+1035
+1036
+1037
+1038
+1039
+1040
+1041
+1042
+1043
+1044
+1045
+1046
+1047
+1048
+1049
+1050
+1051
+1052
+1053
+1054
+1055
+1056
+1057
+1058
+1059
+1060
+1061
+1062
+1063
+1064
+1065
+1066
+1067
+1068
+1069
+1070
+1071
+1072
+1073
+1074
+1075
+1076
+1077
+1078
+1079
+1080
+1081
+1082
+1083
+1084
+1085
+1086
+1087
+1088
+1089
+1090
+1091
+1092
+1093
+1094
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::config::RemoteSettingsConfig;
+use crate::error::{RemoteSettingsError, Result};
+use crate::UniffiCustomTypeConverter;
+use parking_lot::Mutex;
+use serde::Deserialize;
+use std::{
+    borrow::Cow,
+    time::{Duration, Instant},
+};
+use url::Url;
+use viaduct::{Request, Response};
+
+const HEADER_BACKOFF: &str = "Backoff";
+const HEADER_ETAG: &str = "ETag";
+const HEADER_RETRY_AFTER: &str = "Retry-After";
+
+/// A simple HTTP client that can retrieve Remote Settings data using the properties by [ClientConfig].
+/// Methods defined on this will fetch data from
+/// <base_url>/v1/buckets/<bucket_name>/collections/<collection_name>/
+pub struct Client {
+    pub(crate) base_url: Url,
+    pub(crate) bucket_name: String,
+    pub(crate) collection_name: String,
+    pub(crate) remote_state: Mutex<RemoteState>,
+}
+
+impl Client {
+    /// Create a new [Client] with properties matching config.
+    pub fn new(config: RemoteSettingsConfig) -> Result<Self> {
+        let server_url = config
+            .server_url
+            .unwrap_or_else(|| String::from("https://firefox.settings.services.mozilla.com"));
+        let bucket_name = config.bucket_name.unwrap_or_else(|| String::from("main"));
+        let base_url = Url::parse(&server_url)?;
+
+        Ok(Self {
+            base_url,
+            bucket_name,
+            collection_name: config.collection_name,
+            remote_state: Default::default(),
+        })
+    }
+
+    /// Fetches all records for a collection that can be found in the server,
+    /// bucket, and collection defined by the [ClientConfig] used to generate
+    /// this [Client].
+    pub fn get_records(&self) -> Result<RemoteSettingsResponse> {
+        self.get_records_with_options(&GetItemsOptions::new())
+    }
+
+    /// Fetches all records for a collection that can be found in the server,
+    /// bucket, and collection defined by the [ClientConfig] used to generate
+    /// this [Client]. This function will return the raw network [Response].
+    pub fn get_records_raw(&self) -> Result<Response> {
+        self.get_records_raw_with_options(&GetItemsOptions::new())
+    }
+
+    /// Fetches all records that have been published since provided timestamp
+    /// for a collection that can be found in the server, bucket, and
+    /// collection defined by the [ClientConfig] used to generate this [Client].
+    pub fn get_records_since(&self, timestamp: u64) -> Result<RemoteSettingsResponse> {
+        self.get_records_with_options(
+            GetItemsOptions::new().gt("last_modified", timestamp.to_string()),
+        )
+    }
+
+    /// Fetches records from this client's collection with the given options.
+    pub fn get_records_with_options(
+        &self,
+        options: &GetItemsOptions,
+    ) -> Result<RemoteSettingsResponse> {
+        let resp = self.get_records_raw_with_options(options)?;
+        let records = resp.json::<RecordsResponse>()?.data;
+        let etag = resp
+            .headers
+            .get(HEADER_ETAG)
+            .ok_or_else(|| RemoteSettingsError::ResponseError("no etag header".into()))?;
+        // Per https://docs.kinto-storage.org/en/stable/api/1.x/timestamps.html,
+        // the `ETag` header value is a quoted integer. Trim the quotes before
+        // parsing.
+        let last_modified = etag.trim_matches('"').parse().map_err(|_| {
+            RemoteSettingsError::ResponseError(format!(
+                "expected quoted integer in etag header; got `{}`",
+                etag
+            ))
+        })?;
+        Ok(RemoteSettingsResponse {
+            records,
+            last_modified,
+        })
+    }
+
+    /// Fetches a raw network [Response] for records from this client's
+    /// collection with the given options.
+    pub fn get_records_raw_with_options(&self, options: &GetItemsOptions) -> Result<Response> {
+        let path = format!(
+            "v1/buckets/{}/collections/{}/records",
+            &self.bucket_name, &self.collection_name
+        );
+        let mut url = self.base_url.join(&path)?;
+        for (name, value) in options.iter_query_pairs() {
+            url.query_pairs_mut().append_pair(&name, &value);
+        }
+        self.make_request(url)
+    }
+
+    /// Downloads an attachment from [attachment_location]. NOTE: there are no
+    /// guarantees about a maximum size, so use care when fetching potentially
+    /// large attachments.
+    pub fn get_attachment(&self, attachment_location: &str) -> Result<Vec<u8>> {
+        Ok(self.get_attachment_raw(attachment_location)?.body)
+    }
+
+    /// Fetches a raw network [Response] for an attachment.
+    pub fn get_attachment_raw(&self, attachment_location: &str) -> Result<Response> {
+        // Important: We use a `let` binding here to ensure that the mutex is
+        // unlocked immediately after cloning the URL. If we matched directly on
+        // the `.lock()` expression, the mutex would stay locked until the end
+        // of the `match`, causing a deadlock.
+        let maybe_attachments_base_url = self.remote_state.lock().attachments_base_url.clone();
+
+        let attachments_base_url = match maybe_attachments_base_url {
+            Some(attachments_base_url) => attachments_base_url,
+            None => {
+                let server_info = self
+                    .make_request(self.base_url.clone())?
+                    .json::<ServerInfo>()?;
+                let attachments_base_url = match server_info.capabilities.attachments {
+                    Some(capability) => Url::parse(&capability.base_url)?,
+                    None => Err(RemoteSettingsError::AttachmentsUnsupportedError)?,
+                };
+                self.remote_state.lock().attachments_base_url = Some(attachments_base_url.clone());
+                attachments_base_url
+            }
+        };
+
+        self.make_request(attachments_base_url.join(attachment_location)?)
+    }
+
+    fn make_request(&self, url: Url) -> Result<Response> {
+        let mut current_remote_state = self.remote_state.lock();
+        self.ensure_no_backoff(&mut current_remote_state.backoff)?;
+        drop(current_remote_state);
+
+        let req = Request::get(url);
+        let resp = req.send()?;
+
+        let mut current_remote_state = self.remote_state.lock();
+        self.handle_backoff_hint(&resp, &mut current_remote_state.backoff)?;
+
+        if resp.is_success() {
+            Ok(resp)
+        } else {
+            Err(RemoteSettingsError::ResponseError(resp.text().to_string()))
+        }
+    }
+
+    fn ensure_no_backoff(&self, current_state: &mut BackoffState) -> Result<()> {
+        if let BackoffState::Backoff {
+            observed_at,
+            duration,
+        } = *current_state
+        {
+            let elapsed_time = observed_at.elapsed();
+            if elapsed_time >= duration {
+                *current_state = BackoffState::Ok;
+            } else {
+                let remaining = duration - elapsed_time;
+                return Err(RemoteSettingsError::BackoffError(remaining.as_secs()));
+            }
+        }
+        Ok(())
+    }
+
+    fn handle_backoff_hint(
+        &self,
+        response: &Response,
+        current_state: &mut BackoffState,
+    ) -> Result<()> {
+        let extract_backoff_header = |header| -> Result<u64> {
+            Ok(response
+                .headers
+                .get_as::<u64, _>(header)
+                .transpose()
+                .unwrap_or_default() // Ignore number parsing errors.
+                .unwrap_or(0))
+        };
+        // In practice these two headers are mutually exclusive.
+        let backoff = extract_backoff_header(HEADER_BACKOFF)?;
+        let retry_after = extract_backoff_header(HEADER_RETRY_AFTER)?;
+        let max_backoff = backoff.max(retry_after);
+
+        if max_backoff > 0 {
+            *current_state = BackoffState::Backoff {
+                observed_at: Instant::now(),
+                duration: Duration::from_secs(max_backoff),
+            };
+        }
+        Ok(())
+    }
+}
+
+/// Data structure representing the top-level response from the Remote Settings.
+/// [last_modified] will be extracted from the etag header of the response.
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub struct RemoteSettingsResponse {
+    pub records: Vec<RemoteSettingsRecord>,
+    pub last_modified: u64,
+}
+
+#[derive(Deserialize)]
+struct RecordsResponse {
+    data: Vec<RemoteSettingsRecord>,
+}
+
+/// A parsed Remote Settings record. Records can contain arbitrary fields, so clients
+/// are required to further extract expected values from the [fields] member.
+#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
+pub struct RemoteSettingsRecord {
+    pub id: String,
+    pub last_modified: u64,
+    #[serde(default)]
+    pub deleted: bool,
+    pub attachment: Option<Attachment>,
+    #[serde(flatten)]
+    pub fields: RsJsonObject,
+}
+
+/// Attachment metadata that can be optionally attached to a [Record]. The [location] should
+/// included in calls to [Client::get_attachment].
+#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
+pub struct Attachment {
+    pub filename: String,
+    pub mimetype: String,
+    pub location: String,
+    pub hash: String,
+    pub size: u64,
+}
+
+// At time of writing, UniFFI cannot rename iOS bindings and JsonObject conflicted with the declaration in Nimbus.
+// This shouldn't really impact Android, since the type is converted into the platform
+// JsonObject thanks to the UniFFI binding.
+pub type RsJsonObject = serde_json::Map<String, serde_json::Value>;
+impl UniffiCustomTypeConverter for RsJsonObject {
+    type Builtin = String;
+    fn into_custom(val: Self::Builtin) -> uniffi::Result<Self> {
+        let json: serde_json::Value = serde_json::from_str(&val)?;
+
+        match json {
+            serde_json::Value::Object(obj) => Ok(obj),
+            _ => Err(uniffi::deps::anyhow::anyhow!(
+                "Unexpected JSON-non-object in the bagging area"
+            )),
+        }
+    }
+
+    fn from_custom(obj: Self) -> Self::Builtin {
+        serde_json::Value::Object(obj).to_string()
+    }
+}
+
+#[derive(Clone, Debug)]
+pub(crate) struct RemoteState {
+    attachments_base_url: Option<Url>,
+    backoff: BackoffState,
+}
+
+impl Default for RemoteState {
+    fn default() -> Self {
+        Self {
+            attachments_base_url: None,
+            backoff: BackoffState::Ok,
+        }
+    }
+}
+
+/// Used in handling backoff responses from the Remote Settings server.
+#[derive(Clone, Copy, Debug)]
+pub(crate) enum BackoffState {
+    Ok,
+    Backoff {
+        observed_at: Instant,
+        duration: Duration,
+    },
+}
+
+#[derive(Deserialize)]
+struct ServerInfo {
+    capabilities: Capabilities,
+}
+
+#[derive(Deserialize)]
+struct Capabilities {
+    attachments: Option<AttachmentsCapability>,
+}
+
+#[derive(Deserialize)]
+struct AttachmentsCapability {
+    base_url: String,
+}
+
+/// Options for requests to endpoints that return multiple items.
+#[derive(Clone, Debug, Default)]
+pub struct GetItemsOptions {
+    filters: Vec<Filter>,
+    sort: Vec<Sort>,
+    fields: Vec<String>,
+    limit: Option<u64>,
+}
+
+impl GetItemsOptions {
+    /// Creates an empty option set.
+    pub fn new() -> Self {
+        Self::default()
+    }
+
+    /// Sets an option to only return items whose `field` is equal to the given
+    /// `value`.
+    ///
+    /// `field` can be a simple or dotted field name, like `author` or
+    /// `author.name`. `value` can be a bare number or string (like
+    /// `2` or `Ben`), or a stringified JSON value (`"2.0"`, `[1, 2]`,
+    /// `{"checked": true}`).
+    pub fn eq(&mut self, field: impl Into<String>, value: impl Into<String>) -> &mut Self {
+        self.filters.push(Filter::Eq(field.into(), value.into()));
+        self
+    }
+
+    /// Sets an option to only return items whose `field` is not equal to the
+    /// given `value`.
+    pub fn not(&mut self, field: impl Into<String>, value: impl Into<String>) -> &mut Self {
+        self.filters.push(Filter::Not(field.into(), value.into()));
+        self
+    }
+
+    /// Sets an option to only return items whose `field` is an array that
+    /// contains the given `value`. If `value` is a stringified JSON array, the
+    /// field must contain all its elements.
+    pub fn contains(&mut self, field: impl Into<String>, value: impl Into<String>) -> &mut Self {
+        self.filters
+            .push(Filter::Contains(field.into(), value.into()));
+        self
+    }
+
+    /// Sets an option to only return items whose `field` is strictly less
+    /// than the given `value`.
+    pub fn lt(&mut self, field: impl Into<String>, value: impl Into<String>) -> &mut Self {
+        self.filters.push(Filter::Lt(field.into(), value.into()));
+        self
+    }
+
+    /// Sets an option to only return items whose `field` is strictly greater
+    /// than the given `value`.
+    pub fn gt(&mut self, field: impl Into<String>, value: impl Into<String>) -> &mut Self {
+        self.filters.push(Filter::Gt(field.into(), value.into()));
+        self
+    }
+
+    /// Sets an option to only return items whose `field` is less than or equal
+    /// to the given `value`.
+    pub fn max(&mut self, field: impl Into<String>, value: impl Into<String>) -> &mut Self {
+        self.filters.push(Filter::Max(field.into(), value.into()));
+        self
+    }
+
+    /// Sets an option to only return items whose `field` is greater than or
+    /// equal to the given `value`.
+    pub fn min(&mut self, field: impl Into<String>, value: impl Into<String>) -> &mut Self {
+        self.filters.push(Filter::Min(field.into(), value.into()));
+        self
+    }
+
+    /// Sets an option to only return items whose `field` is a string that
+    /// contains the substring `value`. `value` can contain `*` wildcards.
+    pub fn like(&mut self, field: impl Into<String>, value: impl Into<String>) -> &mut Self {
+        self.filters.push(Filter::Like(field.into(), value.into()));
+        self
+    }
+
+    /// Sets an option to only return items that have the given `field`.
+    pub fn has(&mut self, field: impl Into<String>) -> &mut Self {
+        self.filters.push(Filter::Has(field.into()));
+        self
+    }
+
+    /// Sets an option to only return items that do not have the given `field`.
+    pub fn has_not(&mut self, field: impl Into<String>) -> &mut Self {
+        self.filters.push(Filter::HasNot(field.into()));
+        self
+    }
+
+    /// Sets an option to return items in `order` for the given `field`.
+    pub fn sort(&mut self, field: impl Into<String>, order: SortOrder) -> &mut Self {
+        self.sort.push(Sort(field.into(), order));
+        self
+    }
+
+    /// Sets an option to only return the given `field` of each item.
+    ///
+    /// The special `id` and `last_modified` fields are always returned.
+    pub fn field(&mut self, field: impl Into<String>) -> &mut Self {
+        self.fields.push(field.into());
+        self
+    }
+
+    /// Sets the option to return at most `count` items.
+    pub fn limit(&mut self, count: u64) -> &mut Self {
+        self.limit = Some(count);
+        self
+    }
+
+    /// Returns an iterator of (name, value) query pairs for these options.
+    pub fn iter_query_pairs(&self) -> impl Iterator<Item = (Cow<str>, Cow<str>)> {
+        self.filters
+            .iter()
+            .map(Filter::as_query_pair)
+            .chain({
+                // For sorting (https://docs.kinto-storage.org/en/latest/api/1.x/sorting.html),
+                // the query pair syntax is `_sort=field1,-field2`, where the
+                // fields to sort by are specified in a comma-separated ordered
+                // list, and `-` indicates descending order.
+                (!self.sort.is_empty()).then(|| {
+                    (
+                        "_sort".into(),
+                        (self
+                            .sort
+                            .iter()
+                            .map(Sort::as_query_value)
+                            .collect::<Vec<_>>()
+                            .join(","))
+                        .into(),
+                    )
+                })
+            })
+            .chain({
+                // For selecting fields (https://docs.kinto-storage.org/en/latest/api/1.x/selecting_fields.html),
+                // the query pair syntax is `_fields=field1,field2`.
+                (!self.fields.is_empty()).then(|| ("_fields".into(), self.fields.join(",").into()))
+            })
+            .chain({
+                // For pagination (https://docs.kinto-storage.org/en/latest/api/1.x/pagination.html),
+                // the query pair syntax is `_limit={count}`.
+                self.limit
+                    .map(|count| ("_limit".into(), count.to_string().into()))
+            })
+    }
+}
+
+/// The order in which to return items.
+#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
+pub enum SortOrder {
+    /// Smaller values first.
+    Ascending,
+    /// Larger values first.
+    Descending,
+}
+
+#[derive(Clone, Debug)]
+enum Filter {
+    Eq(String, String),
+    Not(String, String),
+    Contains(String, String),
+    Lt(String, String),
+    Gt(String, String),
+    Max(String, String),
+    Min(String, String),
+    Like(String, String),
+    Has(String),
+    HasNot(String),
+}
+
+impl Filter {
+    fn as_query_pair(&self) -> (Cow<str>, Cow<str>) {
+        // For filters (https://docs.kinto-storage.org/en/latest/api/1.x/filtering.html),
+        // the query pair syntax is `[operator_]field=value` for each field.
+        match self {
+            Filter::Eq(field, value) => (field.into(), value.into()),
+            Filter::Not(field, value) => (format!("not_{field}").into(), value.into()),
+            Filter::Contains(field, value) => (format!("contains_{field}").into(), value.into()),
+            Filter::Lt(field, value) => (format!("lt_{field}").into(), value.into()),
+            Filter::Gt(field, value) => (format!("gt_{field}").into(), value.into()),
+            Filter::Max(field, value) => (format!("max_{field}").into(), value.into()),
+            Filter::Min(field, value) => (format!("min_{field}").into(), value.into()),
+            Filter::Like(field, value) => (format!("like_{field}").into(), value.into()),
+            Filter::Has(field) => (format!("has_{field}").into(), "true".into()),
+            Filter::HasNot(field) => (format!("has_{field}").into(), "false".into()),
+        }
+    }
+}
+
+#[derive(Clone, Debug)]
+struct Sort(String, SortOrder);
+
+impl Sort {
+    fn as_query_value(&self) -> Cow<str> {
+        match self.1 {
+            SortOrder::Ascending => self.0.as_str().into(),
+            SortOrder::Descending => format!("-{}", self.0).into(),
+        }
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+    use expect_test::expect;
+    use mockito::{mock, Matcher};
+    #[test]
+    fn test_defaults() {
+        let config = RemoteSettingsConfig {
+            server_url: None,
+            bucket_name: None,
+            collection_name: String::from("the-collection"),
+        };
+        let client = Client::new(config).unwrap();
+        assert_eq!(
+            Url::parse("https://firefox.settings.services.mozilla.com").unwrap(),
+            client.base_url
+        );
+        assert_eq!(String::from("main"), client.bucket_name);
+    }
+
+    #[test]
+    fn test_attachment_can_be_downloaded() {
+        viaduct_reqwest::use_reqwest_backend();
+        let server_info_m = mock("GET", "/")
+            .with_body(attachment_metadata(mockito::server_url()))
+            .with_status(200)
+            .with_header("content-type", "application/json")
+            .create();
+
+        let attachment_location = "123.jpg";
+        let attachment_bytes: Vec<u8> = "I'm a JPG, I swear".into();
+        let attachment_m = mock(
+            "GET",
+            format!("/attachments/{}", attachment_location).as_str(),
+        )
+        .with_body(attachment_bytes.clone())
+        .with_status(200)
+        .with_header("content-type", "application/json")
+        .create();
+
+        let config = RemoteSettingsConfig {
+            server_url: Some(mockito::server_url()),
+            collection_name: String::from("the-collection"),
+            bucket_name: None,
+        };
+
+        let client = Client::new(config).unwrap();
+        let first_resp = client.get_attachment(attachment_location).unwrap();
+        let second_resp = client.get_attachment(attachment_location).unwrap();
+
+        server_info_m.expect(1).assert();
+        attachment_m.expect(2).assert();
+        assert_eq!(first_resp, attachment_bytes);
+        assert_eq!(second_resp, attachment_bytes);
+    }
+
+    #[test]
+    fn test_attachment_errors_if_server_not_configured_for_attachments() {
+        viaduct_reqwest::use_reqwest_backend();
+        let server_info_m = mock("GET", "/")
+            .with_body(NO_ATTACHMENTS_METADATA)
+            .with_status(200)
+            .with_header("content-type", "application/json")
+            .create();
+
+        let attachment_location = "123.jpg";
+        let attachment_bytes: Vec<u8> = "I'm a JPG, I swear".into();
+        let attachment_m = mock(
+            "GET",
+            format!("/attachments/{}", attachment_location).as_str(),
+        )
+        .with_body(attachment_bytes)
+        .with_status(200)
+        .with_header("content-type", "application/json")
+        .create();
+
+        let config = RemoteSettingsConfig {
+            server_url: Some(mockito::server_url()),
+            collection_name: String::from("the-collection"),
+            bucket_name: None,
+        };
+
+        let client = Client::new(config).unwrap();
+        let resp = client.get_attachment(attachment_location);
+        server_info_m.expect(1).assert();
+        attachment_m.expect(0).assert();
+        assert!(matches!(
+            resp,
+            Err(RemoteSettingsError::AttachmentsUnsupportedError)
+        ))
+    }
+
+    #[test]
+    fn test_backoff() {
+        viaduct_reqwest::use_reqwest_backend();
+        let m = mock(
+            "GET",
+            "/v1/buckets/the-bucket/collections/the-collection/records",
+        )
+        .with_body(response_body())
+        .with_status(200)
+        .with_header("content-type", "application/json")
+        .with_header("Backoff", "60")
+        .with_header("etag", "\"1000\"")
+        .create();
+        let config = RemoteSettingsConfig {
+            server_url: Some(mockito::server_url()),
+            collection_name: String::from("the-collection"),
+            bucket_name: Some(String::from("the-bucket")),
+        };
+        let http_client = Client::new(config).unwrap();
+
+        assert!(http_client.get_records().is_ok());
+        let second_resp = http_client.get_records();
+        assert!(matches!(
+            second_resp,
+            Err(RemoteSettingsError::BackoffError(_))
+        ));
+        m.expect(1).assert();
+    }
+
+    #[test]
+    fn test_500_retry_after() {
+        viaduct_reqwest::use_reqwest_backend();
+        let m = mock(
+            "GET",
+            "/v1/buckets/the-bucket/collections/the-collection/records",
+        )
+        .with_body("Boom!")
+        .with_status(500)
+        .with_header("Retry-After", "60")
+        .create();
+        let config = RemoteSettingsConfig {
+            server_url: Some(mockito::server_url()),
+            collection_name: String::from("the-collection"),
+            bucket_name: Some(String::from("the-bucket")),
+        };
+        let http_client = Client::new(config).unwrap();
+        assert!(http_client.get_records().is_err());
+        let second_request = http_client.get_records();
+        assert!(matches!(
+            second_request,
+            Err(RemoteSettingsError::BackoffError(_))
+        ));
+        m.expect(1).assert();
+    }
+
+    #[test]
+    fn test_options() {
+        viaduct_reqwest::use_reqwest_backend();
+        let m = mock(
+            "GET",
+            "/v1/buckets/the-bucket/collections/the-collection/records",
+        )
+        .match_query(Matcher::AllOf(vec![
+            Matcher::UrlEncoded("a".into(), "b".into()),
+            Matcher::UrlEncoded("lt_c.d".into(), "5".into()),
+            Matcher::UrlEncoded("gt_e".into(), "15".into()),
+            Matcher::UrlEncoded("max_f".into(), "20".into()),
+            Matcher::UrlEncoded("min_g".into(), "10".into()),
+            Matcher::UrlEncoded("not_h".into(), "i".into()),
+            Matcher::UrlEncoded("like_j".into(), "*k*".into()),
+            Matcher::UrlEncoded("has_l".into(), "true".into()),
+            Matcher::UrlEncoded("has_m".into(), "false".into()),
+            Matcher::UrlEncoded("contains_n".into(), "o".into()),
+            Matcher::UrlEncoded("_sort".into(), "-b,a".into()),
+            Matcher::UrlEncoded("_fields".into(), "a,c,b".into()),
+            Matcher::UrlEncoded("_limit".into(), "3".into()),
+        ]))
+        .with_body(response_body())
+        .with_status(200)
+        .with_header("content-type", "application/json")
+        .with_header("etag", "\"1000\"")
+        .create();
+        let config = RemoteSettingsConfig {
+            server_url: Some(mockito::server_url()),
+            collection_name: String::from("the-collection"),
+            bucket_name: Some(String::from("the-bucket")),
+        };
+        let http_client = Client::new(config).unwrap();
+        let mut options = GetItemsOptions::new();
+        options
+            .field("a")
+            .field("c")
+            .field("b")
+            .eq("a", "b")
+            .lt("c.d", "5")
+            .gt("e", "15")
+            .max("f", "20")
+            .min("g", "10")
+            .not("h", "i")
+            .like("j", "*k*")
+            .has("l")
+            .has_not("m")
+            .contains("n", "o")
+            .sort("b", SortOrder::Descending)
+            .sort("a", SortOrder::Ascending)
+            .limit(3);
+
+        assert!(http_client.get_records_raw_with_options(&options).is_ok());
+        expect![[r#"
+            RemoteSettingsResponse {
+                records: [
+                    RemoteSettingsRecord {
+                        id: "c5dcd1da-7126-4abb-846b-ec85b0d4d0d7",
+                        last_modified: 1677694949407,
+                        deleted: false,
+                        attachment: Some(
+                            Attachment {
+                                filename: "jgp-attachment.jpg",
+                                mimetype: "image/jpeg",
+                                location: "the-bucket/the-collection/d3a5eccc-f0ca-42c3-b0bb-c0d4408c21c9.jpg",
+                                hash: "2cbd593f3fd5f1585f92265433a6696a863bc98726f03e7222135ff0d8e83543",
+                                size: 1374325,
+                            },
+                        ),
+                        fields: {
+                            "content": String(
+                                "content",
+                            ),
+                            "schema": Number(
+                                1677694447771,
+                            ),
+                            "title": String(
+                                "jpg-attachment",
+                            ),
+                        },
+                    },
+                    RemoteSettingsRecord {
+                        id: "ff301910-6bf5-4cfe-bc4c-5c80308661a5",
+                        last_modified: 1677694470354,
+                        deleted: false,
+                        attachment: Some(
+                            Attachment {
+                                filename: "pdf-attachment.pdf",
+                                mimetype: "application/pdf",
+                                location: "the-bucket/the-collection/5f7347c2-af92-411d-a65b-f794f9b5084c.pdf",
+                                hash: "de1cde3571ef3faa77ea0493276de9231acaa6f6651602e93aa1036f51181e9b",
+                                size: 157,
+                            },
+                        ),
+                        fields: {
+                            "content": String(
+                                "content",
+                            ),
+                            "schema": Number(
+                                1677694447771,
+                            ),
+                            "title": String(
+                                "with-attachment",
+                            ),
+                        },
+                    },
+                    RemoteSettingsRecord {
+                        id: "7403c6f9-79be-4e0c-a37a-8f2b5bd7ad58",
+                        last_modified: 1677694455368,
+                        deleted: false,
+                        attachment: None,
+                        fields: {
+                            "content": String(
+                                "content",
+                            ),
+                            "schema": Number(
+                                1677694447771,
+                            ),
+                            "title": String(
+                                "no-attachment",
+                            ),
+                        },
+                    },
+                    RemoteSettingsRecord {
+                        id: "9320f53c-0a39-4997-9120-62ff597ffb26",
+                        last_modified: 1690921847416,
+                        deleted: true,
+                        attachment: None,
+                        fields: {},
+                    },
+                ],
+                last_modified: 1000,
+            }
+        "#]].assert_debug_eq(&http_client
+            .get_records_with_options(&options)
+            .unwrap());
+        m.expect(2).assert();
+    }
+
+    #[test]
+    fn test_backoff_recovery() {
+        viaduct_reqwest::use_reqwest_backend();
+        let m = mock(
+            "GET",
+            "/v1/buckets/the-bucket/collections/the-collection/records",
+        )
+        .with_body(response_body())
+        .with_status(200)
+        .with_header("content-type", "application/json")
+        .with_header("etag", "\"1000\"")
+        .create();
+        let config = RemoteSettingsConfig {
+            server_url: Some(mockito::server_url()),
+            collection_name: String::from("the-collection"),
+            bucket_name: Some(String::from("the-bucket")),
+        };
+        let http_client = Client::new(config).unwrap();
+        // First, sanity check that manipulating the remote state does something.
+        let mut current_remote_state = http_client.remote_state.lock();
+        current_remote_state.backoff = BackoffState::Backoff {
+            observed_at: Instant::now(),
+            duration: Duration::from_secs(30),
+        };
+        drop(current_remote_state);
+        assert!(matches!(
+            http_client.get_records(),
+            Err(RemoteSettingsError::BackoffError(_))
+        ));
+        // Then do the actual test.
+        let mut current_remote_state = http_client.remote_state.lock();
+        current_remote_state.backoff = BackoffState::Backoff {
+            observed_at: Instant::now() - Duration::from_secs(31),
+            duration: Duration::from_secs(30),
+        };
+        drop(current_remote_state);
+        assert!(http_client.get_records().is_ok());
+        m.expect(1).assert();
+    }
+
+    #[test]
+    fn test_record_fields() {
+        viaduct_reqwest::use_reqwest_backend();
+        let m = mock(
+            "GET",
+            "/v1/buckets/the-bucket/collections/the-collection/records",
+        )
+        .with_body(response_body())
+        .with_status(200)
+        .with_header("content-type", "application/json")
+        .with_header("etag", "\"1000\"")
+        .create();
+        let config = RemoteSettingsConfig {
+            server_url: Some(mockito::server_url()),
+            collection_name: String::from("the-collection"),
+            bucket_name: Some(String::from("the-bucket")),
+        };
+        let http_client = Client::new(config).unwrap();
+        let response = http_client.get_records().unwrap();
+        expect![[r#"
+            RemoteSettingsResponse {
+                records: [
+                    RemoteSettingsRecord {
+                        id: "c5dcd1da-7126-4abb-846b-ec85b0d4d0d7",
+                        last_modified: 1677694949407,
+                        deleted: false,
+                        attachment: Some(
+                            Attachment {
+                                filename: "jgp-attachment.jpg",
+                                mimetype: "image/jpeg",
+                                location: "the-bucket/the-collection/d3a5eccc-f0ca-42c3-b0bb-c0d4408c21c9.jpg",
+                                hash: "2cbd593f3fd5f1585f92265433a6696a863bc98726f03e7222135ff0d8e83543",
+                                size: 1374325,
+                            },
+                        ),
+                        fields: {
+                            "content": String(
+                                "content",
+                            ),
+                            "schema": Number(
+                                1677694447771,
+                            ),
+                            "title": String(
+                                "jpg-attachment",
+                            ),
+                        },
+                    },
+                    RemoteSettingsRecord {
+                        id: "ff301910-6bf5-4cfe-bc4c-5c80308661a5",
+                        last_modified: 1677694470354,
+                        deleted: false,
+                        attachment: Some(
+                            Attachment {
+                                filename: "pdf-attachment.pdf",
+                                mimetype: "application/pdf",
+                                location: "the-bucket/the-collection/5f7347c2-af92-411d-a65b-f794f9b5084c.pdf",
+                                hash: "de1cde3571ef3faa77ea0493276de9231acaa6f6651602e93aa1036f51181e9b",
+                                size: 157,
+                            },
+                        ),
+                        fields: {
+                            "content": String(
+                                "content",
+                            ),
+                            "schema": Number(
+                                1677694447771,
+                            ),
+                            "title": String(
+                                "with-attachment",
+                            ),
+                        },
+                    },
+                    RemoteSettingsRecord {
+                        id: "7403c6f9-79be-4e0c-a37a-8f2b5bd7ad58",
+                        last_modified: 1677694455368,
+                        deleted: false,
+                        attachment: None,
+                        fields: {
+                            "content": String(
+                                "content",
+                            ),
+                            "schema": Number(
+                                1677694447771,
+                            ),
+                            "title": String(
+                                "no-attachment",
+                            ),
+                        },
+                    },
+                    RemoteSettingsRecord {
+                        id: "9320f53c-0a39-4997-9120-62ff597ffb26",
+                        last_modified: 1690921847416,
+                        deleted: true,
+                        attachment: None,
+                        fields: {},
+                    },
+                ],
+                last_modified: 1000,
+            }
+        "#]].assert_debug_eq(&response);
+        m.expect(1).assert();
+    }
+
+    #[test]
+    fn test_missing_etag() {
+        viaduct_reqwest::use_reqwest_backend();
+        let m = mock(
+            "GET",
+            "/v1/buckets/the-bucket/collections/the-collection/records",
+        )
+        .with_body(response_body())
+        .with_status(200)
+        .with_header("content-type", "application/json")
+        .create();
+
+        let config = RemoteSettingsConfig {
+            server_url: Some(mockito::server_url()),
+            bucket_name: Some(String::from("the-bucket")),
+            collection_name: String::from("the-collection"),
+        };
+        let client = Client::new(config).unwrap();
+
+        let err = client.get_records().unwrap_err();
+        assert!(
+            matches!(err, RemoteSettingsError::ResponseError(_)),
+            "Want response error for missing `ETag`; got {}",
+            err
+        );
+        m.expect(1).assert();
+    }
+
+    #[test]
+    fn test_invalid_etag() {
+        viaduct_reqwest::use_reqwest_backend();
+        let m = mock(
+            "GET",
+            "/v1/buckets/the-bucket/collections/the-collection/records",
+        )
+        .with_body(response_body())
+        .with_status(200)
+        .with_header("content-type", "application/json")
+        .with_header("etag", "bad!")
+        .create();
+
+        let config = RemoteSettingsConfig {
+            server_url: Some(mockito::server_url()),
+            bucket_name: Some(String::from("the-bucket")),
+            collection_name: String::from("the-collection"),
+        };
+        let client = Client::new(config).unwrap();
+
+        let err = client.get_records().unwrap_err();
+        assert!(
+            matches!(err, RemoteSettingsError::ResponseError(_)),
+            "Want response error for invalid `ETag`; got {}",
+            err
+        );
+        m.expect(1).assert();
+    }
+
+    fn attachment_metadata(base_url: String) -> String {
+        format!(
+            r#"
+            {{
+                "capabilities": {{
+                    "admin": {{
+                        "description": "Serves the admin console.",
+                        "url": "https://github.com/Kinto/kinto-admin/",
+                        "version": "2.0.0"
+                    }},
+                    "attachments": {{
+                        "description": "Add file attachments to records",
+                        "url": "https://github.com/Kinto/kinto-attachment/",
+                        "version": "6.3.1",
+                        "base_url": "{}/attachments/"
+                    }}
+                }}
+            }}
+    "#,
+            base_url
+        )
+    }
+
+    const NO_ATTACHMENTS_METADATA: &str = r#"
+    {
+      "capabilities": {
+          "admin": {
+            "description": "Serves the admin console.",
+            "url": "https://github.com/Kinto/kinto-admin/",
+            "version": "2.0.0"
+          }
+      }
+    }
+  "#;
+
+    fn response_body() -> String {
+        format!(
+            r#"
+        {{
+            "data": [
+                {},
+                {},
+                {},
+                {}
+            ]
+          }}"#,
+            JPG_ATTACHMENT, PDF_ATTACHMENT, NO_ATTACHMENT, TOMBSTONE
+        )
+    }
+
+    const JPG_ATTACHMENT: &str = r#"
+    {
+      "title": "jpg-attachment",
+      "content": "content",
+      "attachment": {
+      "filename": "jgp-attachment.jpg",
+      "location": "the-bucket/the-collection/d3a5eccc-f0ca-42c3-b0bb-c0d4408c21c9.jpg",
+      "hash": "2cbd593f3fd5f1585f92265433a6696a863bc98726f03e7222135ff0d8e83543",
+      "mimetype": "image/jpeg",
+      "size": 1374325
+      },
+      "id": "c5dcd1da-7126-4abb-846b-ec85b0d4d0d7",
+      "schema": 1677694447771,
+      "last_modified": 1677694949407
+    }
+  "#;
+
+    const PDF_ATTACHMENT: &str = r#"
+    {
+      "title": "with-attachment",
+      "content": "content",
+      "attachment": {
+          "filename": "pdf-attachment.pdf",
+          "location": "the-bucket/the-collection/5f7347c2-af92-411d-a65b-f794f9b5084c.pdf",
+          "hash": "de1cde3571ef3faa77ea0493276de9231acaa6f6651602e93aa1036f51181e9b",
+          "mimetype": "application/pdf",
+          "size": 157
+      },
+      "id": "ff301910-6bf5-4cfe-bc4c-5c80308661a5",
+      "schema": 1677694447771,
+      "last_modified": 1677694470354
+    }
+  "#;
+
+    const NO_ATTACHMENT: &str = r#"
+      {
+        "title": "no-attachment",
+        "content": "content",
+        "schema": 1677694447771,
+        "id": "7403c6f9-79be-4e0c-a37a-8f2b5bd7ad58",
+        "last_modified": 1677694455368
+      }
+    "#;
+
+    const TOMBSTONE: &str = r#"
+    {
+      "id": "9320f53c-0a39-4997-9120-62ff597ffb26",
+      "last_modified": 1690921847416,
+      "deleted": true
+    }
+  "#;
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/remote_settings/config.rs.html b/book/rust-docs/src/remote_settings/config.rs.html new file mode 100644 index 0000000000..4396d760b2 --- /dev/null +++ b/book/rust-docs/src/remote_settings/config.rs.html @@ -0,0 +1,43 @@ +config.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! This module defines the custom configurations that consumers can set.
+//! Those configurations override default values and can be used to set a custom server url,
+//! collection name, and bucket name.
+//! The purpose of the configuration parameters are to allow consumers an easy debugging option,
+//! and the ability to be explicit about the server.
+
+/// Custom configuration for the client.
+/// Currently includes the following:
+/// - `server_url`: The optional url for the settings server. If not specified, the standard server will be used.
+/// - `bucket_name`: The optional name of the bucket containing the collection on the server. If not specified, the standard bucket will be used.
+/// - `collection_name`: The name of the collection for the settings server.
+#[derive(Debug, Clone)]
+pub struct RemoteSettingsConfig {
+    pub server_url: Option<String>,
+    pub bucket_name: Option<String>,
+    pub collection_name: String,
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/remote_settings/error.rs.html b/book/rust-docs/src/remote_settings/error.rs.html new file mode 100644 index 0000000000..cb144dba8f --- /dev/null +++ b/book/rust-docs/src/remote_settings/error.rs.html @@ -0,0 +1,55 @@ +error.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#[derive(Debug, thiserror::Error)]
+pub enum RemoteSettingsError {
+    #[error("JSON Error: {0}")]
+    JSONError(#[from] serde_json::Error),
+    #[error("Error writing downloaded attachment: {0}")]
+    FileError(#[from] std::io::Error),
+    /// An error has occured while sending a request.
+    #[error("Error sending request: {0}")]
+    RequestError(#[from] viaduct::Error),
+    /// An error has occured while parsing an URL.
+    #[error("Error parsing URL: {0}")]
+    UrlParsingError(#[from] url::ParseError),
+    /// The server has asked the client to backoff.
+    #[error("Server asked the client to back off ({0} seconds remaining)")]
+    BackoffError(u64),
+    /// The server returned an error code or the response was unexpected.
+    #[error("Error in network response: {0}")]
+    ResponseError(String),
+    #[error("This server doesn't support attachments")]
+    AttachmentsUnsupportedError,
+}
+
+pub type Result<T, E = RemoteSettingsError> = std::result::Result<T, E>;
+
\ No newline at end of file diff --git a/book/rust-docs/src/remote_settings/lib.rs.html b/book/rust-docs/src/remote_settings/lib.rs.html new file mode 100644 index 0000000000..63715a2f1c --- /dev/null +++ b/book/rust-docs/src/remote_settings/lib.rs.html @@ -0,0 +1,397 @@ +lib.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+pub mod error;
+pub use error::{RemoteSettingsError, Result};
+use std::{fs::File, io::prelude::Write};
+pub mod client;
+pub use client::{
+    Attachment, Client, GetItemsOptions, RemoteSettingsRecord, RemoteSettingsResponse,
+    RsJsonObject, SortOrder,
+};
+pub mod config;
+pub use config::RemoteSettingsConfig;
+
+uniffi::include_scaffolding!("remote_settings");
+
+pub struct RemoteSettings {
+    pub config: RemoteSettingsConfig,
+    client: Client,
+}
+
+impl RemoteSettings {
+    pub fn new(config: RemoteSettingsConfig) -> Result<Self> {
+        Ok(RemoteSettings {
+            config: config.clone(),
+            client: Client::new(config)?,
+        })
+    }
+
+    pub fn get_records(&self) -> Result<RemoteSettingsResponse> {
+        let resp = self.client.get_records()?;
+        Ok(resp)
+    }
+
+    pub fn get_records_since(&self, timestamp: u64) -> Result<RemoteSettingsResponse> {
+        let resp = self.client.get_records_since(timestamp)?;
+        Ok(resp)
+    }
+
+    pub fn download_attachment_to_path(
+        &self,
+        attachment_location: String,
+        path: String,
+    ) -> Result<()> {
+        let resp = self.client.get_attachment(&attachment_location)?;
+        let mut file = File::create(path)?;
+        file.write_all(&resp)?;
+        Ok(())
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+    use crate::RemoteSettingsRecord;
+    use mockito::{mock, Matcher};
+
+    #[test]
+    fn test_get_records() {
+        viaduct_reqwest::use_reqwest_backend();
+        let m = mock(
+            "GET",
+            "/v1/buckets/the-bucket/collections/the-collection/records",
+        )
+        .with_body(response_body())
+        .with_status(200)
+        .with_header("content-type", "application/json")
+        .with_header("etag", "\"1000\"")
+        .create();
+
+        let config = RemoteSettingsConfig {
+            server_url: Some(mockito::server_url()),
+            bucket_name: Some(String::from("the-bucket")),
+            collection_name: String::from("the-collection"),
+        };
+        let remote_settings = RemoteSettings::new(config).unwrap();
+
+        let resp = remote_settings.get_records().unwrap();
+
+        assert!(are_equal_json(JPG_ATTACHMENT, &resp.records[0]));
+        assert_eq!(1000, resp.last_modified);
+        m.expect(1).assert();
+    }
+
+    #[test]
+    fn test_get_records_since() {
+        viaduct_reqwest::use_reqwest_backend();
+        let m = mock(
+            "GET",
+            "/v1/buckets/the-bucket/collections/the-collection/records",
+        )
+        .match_query(Matcher::UrlEncoded("gt_last_modified".into(), "500".into()))
+        .with_body(response_body())
+        .with_status(200)
+        .with_header("content-type", "application/json")
+        .with_header("etag", "\"1000\"")
+        .create();
+
+        let config = RemoteSettingsConfig {
+            server_url: Some(mockito::server_url()),
+            bucket_name: Some(String::from("the-bucket")),
+            collection_name: String::from("the-collection"),
+        };
+        let remote_settings = RemoteSettings::new(config).unwrap();
+
+        let resp = remote_settings.get_records_since(500).unwrap();
+        assert!(are_equal_json(JPG_ATTACHMENT, &resp.records[0]));
+        assert_eq!(1000, resp.last_modified);
+        m.expect(1).assert();
+    }
+
+    // This test was designed as a proof-of-concept and requires a locally-run Remote Settings server.
+    // If this were to be included in CI, it would require pulling the RS docker image and scripting
+    // its configuration, as well as dynamically finding the attachment id, which would more closely
+    // mimic a real world usecase.
+    // #[test]
+    #[allow(dead_code)]
+    fn test_download() {
+        viaduct_reqwest::use_reqwest_backend();
+        let config = RemoteSettingsConfig {
+            server_url: Some("http://localhost:8888".to_string()),
+            bucket_name: Some(String::from("the-bucket")),
+            collection_name: String::from("the-collection"),
+        };
+        let remote_settings = RemoteSettings::new(config).unwrap();
+
+        remote_settings
+            .download_attachment_to_path(
+                "d3a5eccc-f0ca-42c3-b0bb-c0d4408c21c9.jpg".to_string(),
+                "test.jpg".to_string(),
+            )
+            .unwrap();
+    }
+
+    fn are_equal_json(str: &str, rec: &RemoteSettingsRecord) -> bool {
+        let r1: RemoteSettingsRecord = serde_json::from_str(str).unwrap();
+        &r1 == rec
+    }
+
+    fn response_body() -> String {
+        format!(
+            r#"
+        {{
+            "data": [
+                {},
+                {},
+                {}
+            ]
+          }}"#,
+            JPG_ATTACHMENT, PDF_ATTACHMENT, NO_ATTACHMENT
+        )
+    }
+
+    const JPG_ATTACHMENT: &str = r#"
+          {
+            "title": "jpg-attachment",
+            "content": "content",
+            "attachment": {
+            "filename": "jgp-attachment.jpg",
+            "location": "the-bucket/the-collection/d3a5eccc-f0ca-42c3-b0bb-c0d4408c21c9.jpg",
+            "hash": "2cbd593f3fd5f1585f92265433a6696a863bc98726f03e7222135ff0d8e83543",
+            "mimetype": "image/jpeg",
+            "size": 1374325
+            },
+            "id": "c5dcd1da-7126-4abb-846b-ec85b0d4d0d7",
+            "schema": 1677694447771,
+            "last_modified": 1677694949407
+          }
+        "#;
+
+    const PDF_ATTACHMENT: &str = r#"
+          {
+            "title": "with-attachment",
+            "content": "content",
+            "attachment": {
+                "filename": "pdf-attachment.pdf",
+                "location": "the-bucket/the-collection/5f7347c2-af92-411d-a65b-f794f9b5084c.pdf",
+                "hash": "de1cde3571ef3faa77ea0493276de9231acaa6f6651602e93aa1036f51181e9b",
+                "mimetype": "application/pdf",
+                "size": 157
+            },
+            "id": "ff301910-6bf5-4cfe-bc4c-5c80308661a5",
+            "schema": 1677694447771,
+            "last_modified": 1677694470354
+          }
+        "#;
+
+    const NO_ATTACHMENT: &str = r#"
+          {
+            "title": "no-attachment",
+            "content": "content",
+            "schema": 1677694447771,
+            "id": "7403c6f9-79be-4e0c-a37a-8f2b5bd7ad58",
+            "last_modified": 1677694455368
+          }
+        "#;
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/restmail_client/error.rs.html b/book/rust-docs/src/restmail_client/error.rs.html new file mode 100644 index 0000000000..67821eac7c --- /dev/null +++ b/book/rust-docs/src/restmail_client/error.rs.html @@ -0,0 +1,39 @@ +error.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use thiserror::Error;
+
+#[derive(Error, Debug)]
+pub enum RestmailClientError {
+    #[error("Not a restmail account (doesn't end with @restmail.net)")]
+    NotARestmailEmail,
+    #[error("Max tries reached and the email couldn't be found.")]
+    HitRetryMax,
+    #[error("JSON error: {0}")]
+    JsonError(#[from] serde_json::Error),
+    #[error("Error parsing URL: {0}")]
+    Disconnect(#[from] url::ParseError),
+    #[error("Network error: {0}")]
+    RequestError(#[from] viaduct::Error),
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/restmail_client/lib.rs.html b/book/rust-docs/src/restmail_client/lib.rs.html new file mode 100644 index 0000000000..149a9ed712 --- /dev/null +++ b/book/rust-docs/src/restmail_client/lib.rs.html @@ -0,0 +1,163 @@ +lib.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use error::RestmailClientError;
+use serde_json::Value as EmailJson;
+use url::Url;
+use viaduct::Request;
+
+mod error;
+
+type Result<T> = std::result::Result<T, RestmailClientError>;
+
+/// For a given restmail email, find the first email that satisfies the given predicate.
+/// If no email is found, this function sleeps for a few seconds then tries again, up
+/// to `max_tries` times.
+pub fn find_email<F>(email: &str, predicate: F, max_tries: u8) -> Result<EmailJson>
+where
+    F: Fn(&EmailJson) -> bool,
+{
+    let mail_url = url_for_email(email)?;
+    log::info!("Checking {} up to {} times.", email, max_tries);
+    for i in 0..max_tries {
+        let resp: Vec<serde_json::Value> = Request::get(mail_url.clone()).send()?.json()?;
+        let mut matching_emails: Vec<serde_json::Value> =
+            resp.into_iter().filter(|email| predicate(email)).collect();
+
+        if matching_emails.is_empty() {
+            log::info!(
+                "Failed to find matching email. Waiting {} seconds and retrying.",
+                i + 1
+            );
+            std::thread::sleep(std::time::Duration::from_secs((i + 1).into()));
+            continue;
+        }
+
+        if matching_emails.len() > 1 {
+            log::info!(
+                "Found {} emails that applies (taking latest)",
+                matching_emails.len()
+            );
+            matching_emails.sort_by(|a, b| {
+                let a_time = a["receivedAt"].as_u64();
+                let b_time = b["receivedAt"].as_u64();
+                match (a_time, b_time) {
+                    (Some(a_time), Some(b_time)) => b_time.cmp(&a_time),
+                    _ => {
+                        log::warn!(
+                            "Could not de-serialize receivedAt for at least one of the emails."
+                        );
+                        std::cmp::Ordering::Equal
+                    }
+                }
+            })
+        }
+        return Ok(matching_emails[0].clone());
+    }
+    log::info!("Error: Failed to find email after {} tries!", max_tries);
+    Err(RestmailClientError::HitRetryMax)
+}
+
+pub fn clear_mailbox(email: &str) -> Result<()> {
+    let mail_url = url_for_email(email)?;
+    log::info!("Clearing restmail for {}.", email);
+    Request::delete(mail_url).send()?;
+    Ok(())
+}
+
+fn username_from_email(email: &str) -> Result<String> {
+    let user = email.replace("@restmail.net", "");
+    if user.len() == email.len() {
+        return Err(RestmailClientError::NotARestmailEmail);
+    }
+    Ok(user)
+}
+
+fn url_for_email(email: &str) -> Result<Url> {
+    let restmail_user = username_from_email(email)?;
+    let path = format!("/mail/{}", restmail_user);
+    Ok(Url::parse("https://restmail.net")?.join(&path)?)
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/sql_support/conn_ext.rs.html b/book/rust-docs/src/sql_support/conn_ext.rs.html new file mode 100644 index 0000000000..8da556a1ab --- /dev/null +++ b/book/rust-docs/src/sql_support/conn_ext.rs.html @@ -0,0 +1,821 @@ +conn_ext.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use rusqlite::{
+    self,
+    types::{FromSql, ToSql},
+    Connection, Params, Result as SqlResult, Row, Savepoint, Transaction, TransactionBehavior,
+};
+use std::iter::FromIterator;
+use std::ops::Deref;
+use std::time::Instant;
+
+use crate::maybe_cached::MaybeCached;
+
+pub struct Conn(rusqlite::Connection);
+
+/// This trait exists so that we can use these helpers on `rusqlite::{Transaction, Connection}`.
+/// Note that you must import ConnExt in order to call these methods on anything.
+pub trait ConnExt {
+    /// The method you need to implement to opt in to all of this.
+    fn conn(&self) -> &Connection;
+
+    /// Set the value of the pragma on the main database. Returns the same object, for chaining.
+    fn set_pragma<T>(&self, pragma_name: &str, pragma_value: T) -> SqlResult<&Self>
+    where
+        T: ToSql,
+        Self: Sized,
+    {
+        // None == Schema name, e.g. `PRAGMA some_attached_db.something = blah`
+        self.conn()
+            .pragma_update(None, pragma_name, &pragma_value)?;
+        Ok(self)
+    }
+
+    /// Get a cached or uncached statement based on a flag.
+    fn prepare_maybe_cached<'conn>(
+        &'conn self,
+        sql: &str,
+        cache: bool,
+    ) -> SqlResult<MaybeCached<'conn>> {
+        MaybeCached::prepare(self.conn(), sql, cache)
+    }
+
+    /// Execute all the provided statements.
+    fn execute_all(&self, stmts: &[&str]) -> SqlResult<()> {
+        let conn = self.conn();
+        for sql in stmts {
+            let r = conn.execute(sql, []);
+            match r {
+                Ok(_) => {}
+                // Ignore ExecuteReturnedResults error because they're pointless
+                // and annoying.
+                Err(rusqlite::Error::ExecuteReturnedResults) => {}
+                Err(e) => return Err(e),
+            }
+        }
+        Ok(())
+    }
+
+    /// Execute a single statement.
+    fn execute_one(&self, stmt: &str) -> SqlResult<()> {
+        self.execute_all(&[stmt])
+    }
+
+    /// Equivalent to `Connection::execute` but caches the statement so that subsequent
+    /// calls to `execute_cached` will have improved performance.
+    fn execute_cached<P: Params>(&self, sql: &str, params: P) -> SqlResult<usize> {
+        let mut stmt = self.conn().prepare_cached(sql)?;
+        stmt.execute(params)
+    }
+
+    /// Execute a query that returns a single result column, and return that result.
+    fn query_one<T: FromSql>(&self, sql: &str) -> SqlResult<T> {
+        let res: T = self.conn().query_row_and_then(sql, [], |row| row.get(0))?;
+        Ok(res)
+    }
+
+    /// Return true if a query returns any rows
+    fn exists<P: Params>(&self, sql: &str, params: P) -> SqlResult<bool> {
+        let conn = self.conn();
+        let mut stmt = conn.prepare(sql)?;
+        let exists = stmt.query(params)?.next()?.is_some();
+        Ok(exists)
+    }
+
+    /// Execute a query that returns 0 or 1 result columns, returning None
+    /// if there were no rows, or if the only result was NULL.
+    fn try_query_one<T: FromSql, P: Params>(
+        &self,
+        sql: &str,
+        params: P,
+        cache: bool,
+    ) -> SqlResult<Option<T>>
+    where
+        Self: Sized,
+    {
+        use rusqlite::OptionalExtension;
+        // The outer option is if we got rows, the inner option is
+        // if the first row was null.
+        let res: Option<Option<T>> = self
+            .conn()
+            .query_row_and_then_cachable(sql, params, |row| row.get(0), cache)
+            .optional()?;
+        // go from Option<Option<T>> to Option<T>
+        Ok(res.unwrap_or_default())
+    }
+
+    /// Equivalent to `rusqlite::Connection::query_row_and_then` but allows
+    /// passing a flag to indicate that it's cached.
+    fn query_row_and_then_cachable<T, E, P, F>(
+        &self,
+        sql: &str,
+        params: P,
+        mapper: F,
+        cache: bool,
+    ) -> Result<T, E>
+    where
+        Self: Sized,
+        P: Params,
+        E: From<rusqlite::Error>,
+        F: FnOnce(&Row<'_>) -> Result<T, E>,
+    {
+        Ok(self
+            .try_query_row(sql, params, mapper, cache)?
+            .ok_or(rusqlite::Error::QueryReturnedNoRows)?)
+    }
+
+    /// Helper for when you'd like to get a `Vec<T>` of all the rows returned by a
+    /// query that takes named arguments. See also
+    /// `query_rows_and_then_cached`.
+    fn query_rows_and_then<T, E, P, F>(&self, sql: &str, params: P, mapper: F) -> Result<Vec<T>, E>
+    where
+        Self: Sized,
+        P: Params,
+        E: From<rusqlite::Error>,
+        F: FnMut(&Row<'_>) -> Result<T, E>,
+    {
+        query_rows_and_then_cachable(self.conn(), sql, params, mapper, false)
+    }
+
+    /// Helper for when you'd like to get a `Vec<T>` of all the rows returned by a
+    /// query that takes named arguments.
+    fn query_rows_and_then_cached<T, E, P, F>(
+        &self,
+        sql: &str,
+        params: P,
+        mapper: F,
+    ) -> Result<Vec<T>, E>
+    where
+        Self: Sized,
+        P: Params,
+        E: From<rusqlite::Error>,
+        F: FnMut(&Row<'_>) -> Result<T, E>,
+    {
+        query_rows_and_then_cachable(self.conn(), sql, params, mapper, true)
+    }
+
+    /// Like `query_rows_and_then_cachable`, but works if you want a non-Vec as a result.
+    /// # Example:
+    /// ```rust,no_run
+    /// # use std::collections::HashSet;
+    /// # use sql_support::ConnExt;
+    /// # use rusqlite::Connection;
+    /// fn get_visit_tombstones(conn: &Connection, id: i64) -> rusqlite::Result<HashSet<i64>> {
+    ///     Ok(conn.query_rows_into(
+    ///         "SELECT visit_date FROM moz_historyvisit_tombstones
+    ///          WHERE place_id = :place_id",
+    ///         &[(":place_id", &id)],
+    ///         |row| row.get::<_, i64>(0))?)
+    /// }
+    /// ```
+    /// Note if the type isn't inferred, you'll have to do something gross like
+    /// `conn.query_rows_into::<HashSet<_>, _, _, _>(...)`.
+    fn query_rows_into<Coll, T, E, P, F>(&self, sql: &str, params: P, mapper: F) -> Result<Coll, E>
+    where
+        Self: Sized,
+        E: From<rusqlite::Error>,
+        F: FnMut(&Row<'_>) -> Result<T, E>,
+        Coll: FromIterator<T>,
+        P: Params,
+    {
+        query_rows_and_then_cachable(self.conn(), sql, params, mapper, false)
+    }
+
+    /// Same as `query_rows_into`, but caches the stmt if possible.
+    fn query_rows_into_cached<Coll, T, E, P, F>(
+        &self,
+        sql: &str,
+        params: P,
+        mapper: F,
+    ) -> Result<Coll, E>
+    where
+        Self: Sized,
+        P: Params,
+        E: From<rusqlite::Error>,
+        F: FnMut(&Row<'_>) -> Result<T, E>,
+        Coll: FromIterator<T>,
+    {
+        query_rows_and_then_cachable(self.conn(), sql, params, mapper, true)
+    }
+
+    // This should probably have a longer name...
+    /// Like `query_row_and_then_cachable` but returns None instead of erroring
+    /// if no such row exists.
+    fn try_query_row<T, E, P, F>(
+        &self,
+        sql: &str,
+        params: P,
+        mapper: F,
+        cache: bool,
+    ) -> Result<Option<T>, E>
+    where
+        Self: Sized,
+        P: Params,
+        E: From<rusqlite::Error>,
+        F: FnOnce(&Row<'_>) -> Result<T, E>,
+    {
+        let conn = self.conn();
+        let mut stmt = MaybeCached::prepare(conn, sql, cache)?;
+        let mut rows = stmt.query(params)?;
+        rows.next()?.map(mapper).transpose()
+    }
+
+    /// Caveat: This won't actually get used most of the time, and calls will
+    /// usually invoke rusqlite's method with the same name. See comment on
+    /// `UncheckedTransaction` for details (generally you probably don't need to
+    /// care)
+    fn unchecked_transaction(&self) -> SqlResult<UncheckedTransaction<'_>> {
+        UncheckedTransaction::new(self.conn(), TransactionBehavior::Deferred)
+    }
+
+    /// Begin `unchecked_transaction` with `TransactionBehavior::Immediate`. Use
+    /// when the first operation will be a read operation, that further writes
+    /// depend on for correctness.
+    fn unchecked_transaction_imm(&self) -> SqlResult<UncheckedTransaction<'_>> {
+        UncheckedTransaction::new(self.conn(), TransactionBehavior::Immediate)
+    }
+
+    /// Get the DB size in bytes
+    fn get_db_size(&self) -> Result<u32, rusqlite::Error> {
+        let page_count: u32 = self.query_one("SELECT * from pragma_page_count()")?;
+        let page_size: u32 = self.query_one("SELECT * from pragma_page_size()")?;
+        let freelist_count: u32 = self.query_one("SELECT * from pragma_freelist_count()")?;
+
+        Ok((page_count - freelist_count) * page_size)
+    }
+}
+
+impl ConnExt for Connection {
+    #[inline]
+    fn conn(&self) -> &Connection {
+        self
+    }
+}
+
+impl<'conn> ConnExt for Transaction<'conn> {
+    #[inline]
+    fn conn(&self) -> &Connection {
+        self
+    }
+}
+
+impl<'conn> ConnExt for Savepoint<'conn> {
+    #[inline]
+    fn conn(&self) -> &Connection {
+        self
+    }
+}
+
+/// rusqlite, in an attempt to save us from ourselves, needs a mutable ref to a
+/// connection to start a transaction. That is a bit of a PITA in some cases, so
+/// we offer this as an alternative - but the responsibility of ensuring there
+/// are no concurrent transactions is on our head.
+///
+/// This is very similar to the rusqlite `Transaction` - it doesn't prevent
+/// against nested transactions but does allow you to use an immutable
+/// `Connection`.
+///
+/// FIXME: This currently won't actually be used most of the time, because
+/// `rusqlite` added [`Connection::unchecked_transaction`] (and
+/// `Transaction::new_unchecked`, which can be used to reimplement
+/// `unchecked_transaction_imm`), which will be preferred in a call to
+/// `c.unchecked_transaction()`, because inherent methods have precedence over
+/// methods on extension traits. The exception here is that this will still be
+/// used by code which takes `&impl ConnExt` (I believe it would also be used if
+/// you attempted to call `unchecked_transaction()` on a non-Connection that
+/// implements ConnExt, such as a `Safepoint`, `UncheckedTransaction`, or
+/// `Transaction` itself, but such code is clearly broken, so is not worth
+/// considering).
+///
+/// The difference is that `rusqlite`'s version returns a normal
+/// `rusqlite::Transaction`, rather than the `UncheckedTransaction` from this
+/// crate. Aside from type's name and location (and the fact that `rusqlite`'s
+/// detects slightly more misuse at compile time, and has more features), the
+/// main difference is: `rusqlite`'s does not track when a transaction began,
+/// which unfortunatly seems to be used by the coop-transaction management in
+/// places in some fashion.
+///
+/// There are at least two options for how to fix this:
+/// 1. Decide we don't need this version, and delete it, and moving the
+///    transaction timing into the coop-transaction code directly (or something
+///    like this).
+/// 2. Decide this difference *is* important, and rename
+///    `ConnExt::unchecked_transaction` to something like
+///    `ConnExt::transaction_unchecked`.
+pub struct UncheckedTransaction<'conn> {
+    pub conn: &'conn Connection,
+    pub started_at: Instant,
+    pub finished: bool,
+    // we could add drop_behavior etc too, but we don't need it yet - we
+    // always rollback.
+}
+
+impl<'conn> UncheckedTransaction<'conn> {
+    /// Begin a new unchecked transaction. Cannot be nested, but this is not
+    /// enforced by Rust (hence 'unchecked') - however, it is enforced by
+    /// SQLite; use a rusqlite `savepoint` for nested transactions.
+    pub fn new(conn: &'conn Connection, behavior: TransactionBehavior) -> SqlResult<Self> {
+        let query = match behavior {
+            TransactionBehavior::Deferred => "BEGIN DEFERRED",
+            TransactionBehavior::Immediate => "BEGIN IMMEDIATE",
+            TransactionBehavior::Exclusive => "BEGIN EXCLUSIVE",
+            _ => unreachable!(),
+        };
+        conn.execute_batch(query)
+            .map(move |_| UncheckedTransaction {
+                conn,
+                started_at: Instant::now(),
+                finished: false,
+            })
+    }
+
+    /// Consumes and commits an unchecked transaction.
+    pub fn commit(mut self) -> SqlResult<()> {
+        if self.finished {
+            log::warn!("ignoring request to commit an already finished transaction");
+            return Ok(());
+        }
+        self.finished = true;
+        self.conn.execute_batch("COMMIT")?;
+        log::debug!("Transaction commited after {:?}", self.started_at.elapsed());
+        Ok(())
+    }
+
+    /// Consumes and rolls back an unchecked transaction.
+    pub fn rollback(mut self) -> SqlResult<()> {
+        if self.finished {
+            log::warn!("ignoring request to rollback an already finished transaction");
+            return Ok(());
+        }
+        self.rollback_()
+    }
+
+    fn rollback_(&mut self) -> SqlResult<()> {
+        self.finished = true;
+        self.conn.execute_batch("ROLLBACK")?;
+        Ok(())
+    }
+
+    fn finish_(&mut self) -> SqlResult<()> {
+        if self.finished || self.conn.is_autocommit() {
+            return Ok(());
+        }
+        self.rollback_()?;
+        Ok(())
+    }
+}
+
+impl<'conn> Deref for UncheckedTransaction<'conn> {
+    type Target = Connection;
+
+    #[inline]
+    fn deref(&self) -> &Connection {
+        self.conn
+    }
+}
+
+impl<'conn> Drop for UncheckedTransaction<'conn> {
+    fn drop(&mut self) {
+        if let Err(e) = self.finish_() {
+            log::warn!("Error dropping an unchecked transaction: {}", e);
+        }
+    }
+}
+
+impl<'conn> ConnExt for UncheckedTransaction<'conn> {
+    #[inline]
+    fn conn(&self) -> &Connection {
+        self
+    }
+}
+
+fn query_rows_and_then_cachable<Coll, T, E, P, F>(
+    conn: &Connection,
+    sql: &str,
+    params: P,
+    mapper: F,
+    cache: bool,
+) -> Result<Coll, E>
+where
+    E: From<rusqlite::Error>,
+    F: FnMut(&Row<'_>) -> Result<T, E>,
+    Coll: FromIterator<T>,
+    P: Params,
+{
+    let mut stmt = conn.prepare_maybe_cached(sql, cache)?;
+    let iter = stmt.query_and_then(params, mapper)?;
+    iter.collect::<Result<Coll, E>>()
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/sql_support/debug_tools.rs.html b/book/rust-docs/src/sql_support/debug_tools.rs.html new file mode 100644 index 0000000000..5a2c72dafd --- /dev/null +++ b/book/rust-docs/src/sql_support/debug_tools.rs.html @@ -0,0 +1,247 @@ +debug_tools.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/// This module supplies utilities to help with debugging SQL in these components.
+///
+/// To take advantage of this module, you must enable the `debug-tools` feature for
+/// this crate.
+use rusqlite::{functions::Context, types::Value, Connection};
+
+/// Print the entire contents of an arbitrary query. A common usage would be to pass
+/// `SELECT * FROM table`
+#[cfg(feature = "debug-tools")]
+pub fn print_query(conn: &Connection, query: &str) -> rusqlite::Result<()> {
+    use prettytable::{Cell, Row};
+
+    let mut stmt = conn.prepare(query)?;
+    let mut rows = stmt.query([])?;
+    let mut table = prettytable::Table::new();
+    let mut titles = Row::empty();
+    for col in rows.as_ref().expect("must have statement").columns() {
+        titles.add_cell(Cell::new(col.name()));
+    }
+    table.set_titles(titles);
+    while let Some(sql_row) = rows.next()? {
+        let mut table_row = Row::empty();
+        for i in 0..sql_row.as_ref().column_count() {
+            let val = match sql_row.get::<_, Value>(i)? {
+                Value::Null => "null".to_string(),
+                Value::Integer(i) => i.to_string(),
+                Value::Real(f) => f.to_string(),
+                Value::Text(s) => s,
+                Value::Blob(b) => format!("<blob with {} bytes>", b.len()),
+            };
+            table_row.add_cell(Cell::new(&val));
+        }
+        table.add_row(table_row);
+    }
+    // printstd ends up on stdout - if we just println!() extra buffering seems to happen?
+    use std::io::Write;
+    std::io::stdout()
+        .write_all(format!("query: {query}\n").as_bytes())
+        .unwrap();
+    table.printstd();
+    Ok(())
+}
+
+#[cfg(feature = "debug-tools")]
+#[inline(never)]
+fn dbg(ctx: &Context<'_>) -> rusqlite::Result<Value> {
+    let mut s = Value::Null;
+    for i in 0..ctx.len() {
+        let raw = ctx.get_raw(i);
+        let str_repr = match raw {
+            rusqlite::types::ValueRef::Text(_) => raw.as_str().unwrap().to_owned(),
+            _ => format!("{:?}", raw),
+        };
+        eprint!("{} ", str_repr);
+        s = raw.into();
+    }
+    eprintln!();
+    Ok(s)
+}
+
+#[cfg(not(feature = "debug-tools"))]
+// It would be very bad if the `dbg()` function only existed when the `debug-tools` feature was
+// enabled - you could imagine some crate defining it as a `dev-dependency`, but then shipping
+// sql with an embedded `dbg()` call - tests and CI would pass, but it would fail in the real lib.
+fn dbg(ctx: &Context<'_>) -> rusqlite::Result<Value> {
+    Ok(if ctx.is_empty() {
+        Value::Null
+    } else {
+        ctx.get_raw(ctx.len() - 1).into()
+    })
+}
+
+/// You can call this function to add all sql functions provided by this module
+/// to your connection. The list of supported functions is described below.
+///
+/// *Note:* you must enable the `debug-tools` feature for these functions to perform
+/// as described. If this feature is not enabled, functions of the same name will still
+/// exist, but will be no-ops.
+///
+/// # dbg
+/// `dbg()` is a simple sql function that prints all args to stderr and returns the last argument.
+/// It's useful for instrumenting existing WHERE statements - eg, changing a statement
+/// ```sql
+/// WHERE a.bar = foo
+/// ```
+/// to
+/// ```sql
+/// WHERE dbg("a bar", a.bar) = foo
+/// ```
+/// will print the *literal* `a bar` followed by the *value* of `a.bar`, then return `a.bar`,
+/// so the semantics of the `WHERE` statement do not change.
+/// If you have a series of statements being executed (ie, statements separated by a `;`),
+/// adding a new statement like `SELECT dbg("hello");` is also possible.
+pub fn define_debug_functions(c: &Connection) -> rusqlite::Result<()> {
+    use rusqlite::functions::FunctionFlags;
+    c.create_scalar_function("dbg", -1, FunctionFlags::SQLITE_UTF8, dbg)?;
+    Ok(())
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+    use crate::conn_ext::ConnExt;
+
+    #[test]
+    fn test_dbg() {
+        let conn = Connection::open_with_flags(":memory:", rusqlite::OpenFlags::default()).unwrap();
+        define_debug_functions(&conn).unwrap();
+        assert_eq!(conn.query_one::<i64>("SELECT dbg('foo', 0);").unwrap(), 0);
+        assert_eq!(
+            conn.query_one::<String>("SELECT dbg('foo');").unwrap(),
+            "foo"
+        );
+        assert_eq!(
+            conn.query_one::<Option<String>>("SELECT dbg();").unwrap(),
+            None
+        );
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/sql_support/each_chunk.rs.html b/book/rust-docs/src/sql_support/each_chunk.rs.html new file mode 100644 index 0000000000..4be582ec3d --- /dev/null +++ b/book/rust-docs/src/sql_support/each_chunk.rs.html @@ -0,0 +1,623 @@ +each_chunk.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use lazy_static::lazy_static;
+use rusqlite::{self, limits::Limit, types::ToSql};
+use std::iter::Map;
+use std::slice::Iter;
+
+/// Returns SQLITE_LIMIT_VARIABLE_NUMBER as read from an in-memory connection and cached.
+/// connection and cached. That means this will return the wrong value if it's set to a lower
+/// value for a connection using this will return the wrong thing, but doing so is rare enough
+/// that we explicitly don't support it (why would you want to lower this at runtime?).
+///
+/// If you call this and the actual value was set to a negative number or zero (nothing prevents
+/// this beyond a warning in the SQLite documentation), we panic. However, it's unlikely you can
+/// run useful queries if this happened anyway.
+pub fn default_max_variable_number() -> usize {
+    lazy_static! {
+        static ref MAX_VARIABLE_NUMBER: usize = {
+            let conn = rusqlite::Connection::open_in_memory()
+                .expect("Failed to initialize in-memory connection (out of memory?)");
+
+            let limit = conn.limit(Limit::SQLITE_LIMIT_VARIABLE_NUMBER);
+            assert!(
+                limit > 0,
+                "Illegal value for SQLITE_LIMIT_VARIABLE_NUMBER (must be > 0) {}",
+                limit
+            );
+            limit as usize
+        };
+    }
+    *MAX_VARIABLE_NUMBER
+}
+
+/// Helper for the case where you have a `&[impl ToSql]` of arbitrary length, but need one
+/// of no more than the connection's `MAX_VARIABLE_NUMBER` (rather,
+/// `default_max_variable_number()`). This is useful when performing batched updates.
+///
+/// The `do_chunk` callback is called with a slice of no more than `default_max_variable_number()`
+/// items as it's first argument, and the offset from the start as it's second.
+///
+/// See `each_chunk_mapped` for the case where `T` doesn't implement `ToSql`, but can be
+/// converted to something that does.
+pub fn each_chunk<'a, T, E, F>(items: &'a [T], do_chunk: F) -> Result<(), E>
+where
+    T: 'a,
+    F: FnMut(&'a [T], usize) -> Result<(), E>,
+{
+    each_sized_chunk(items, default_max_variable_number(), do_chunk)
+}
+
+/// A version of `each_chunk` for the case when the conversion to `to_sql` requires an custom
+/// intermediate step. For example, you might want to grab a property off of an arrray of records
+pub fn each_chunk_mapped<'a, T, U, E, Mapper, DoChunk>(
+    items: &'a [T],
+    to_sql: Mapper,
+    do_chunk: DoChunk,
+) -> Result<(), E>
+where
+    T: 'a,
+    U: ToSql + 'a,
+    Mapper: Fn(&'a T) -> U,
+    DoChunk: FnMut(Map<Iter<'a, T>, &'_ Mapper>, usize) -> Result<(), E>,
+{
+    each_sized_chunk_mapped(items, default_max_variable_number(), to_sql, do_chunk)
+}
+
+// Split out for testing. Separate so that we can pass an actual slice
+// to the callback if they don't need mapping. We could probably unify
+// this with each_sized_chunk_mapped with a lot of type system trickery,
+// but one of the benefits to each_chunk over the mapped versions is
+// that the declaration is simpler.
+pub fn each_sized_chunk<'a, T, E, F>(
+    items: &'a [T],
+    chunk_size: usize,
+    mut do_chunk: F,
+) -> Result<(), E>
+where
+    T: 'a,
+    F: FnMut(&'a [T], usize) -> Result<(), E>,
+{
+    if items.is_empty() {
+        return Ok(());
+    }
+    let mut offset = 0;
+    for chunk in items.chunks(chunk_size) {
+        do_chunk(chunk, offset)?;
+        offset += chunk.len();
+    }
+    Ok(())
+}
+
+/// Utility to help perform batched updates, inserts, queries, etc. This is the low-level version
+/// of this utility which is wrapped by `each_chunk` and `each_chunk_mapped`, and it allows you to
+/// provide both the mapping function, and the chunk size.
+///
+/// Note: `mapped` basically just refers to the translating of `T` to some `U` where `U: ToSql`
+/// using the `to_sql` function. This is useful for e.g. inserting the IDs of a large list
+/// of records.
+pub fn each_sized_chunk_mapped<'a, T, U, E, Mapper, DoChunk>(
+    items: &'a [T],
+    chunk_size: usize,
+    to_sql: Mapper,
+    mut do_chunk: DoChunk,
+) -> Result<(), E>
+where
+    T: 'a,
+    U: ToSql + 'a,
+    Mapper: Fn(&'a T) -> U,
+    DoChunk: FnMut(Map<Iter<'a, T>, &'_ Mapper>, usize) -> Result<(), E>,
+{
+    if items.is_empty() {
+        return Ok(());
+    }
+    let mut offset = 0;
+    for chunk in items.chunks(chunk_size) {
+        let mapped = chunk.iter().map(&to_sql);
+        do_chunk(mapped, offset)?;
+        offset += chunk.len();
+    }
+    Ok(())
+}
+
+#[cfg(test)]
+fn check_chunk<T, C>(items: C, expect: &[T], desc: &str)
+where
+    C: IntoIterator,
+    <C as IntoIterator>::Item: ToSql,
+    T: ToSql,
+{
+    let items = items.into_iter().collect::<Vec<_>>();
+    assert_eq!(items.len(), expect.len());
+    // Can't quite make the borrowing work out here w/o a loop, oh well.
+    for (idx, (got, want)) in items.iter().zip(expect.iter()).enumerate() {
+        assert_eq!(
+            got.to_sql().unwrap(),
+            want.to_sql().unwrap(),
+            // ToSqlOutput::Owned(Value::Integer(*num)),
+            "{}: Bad value at index {}",
+            desc,
+            idx
+        );
+    }
+}
+
+#[cfg(test)]
+mod test_mapped {
+    use super::*;
+
+    #[test]
+    fn test_separate() {
+        let mut iteration = 0;
+        each_sized_chunk_mapped(
+            &[1, 2, 3, 4, 5],
+            3,
+            |item| item as &dyn ToSql,
+            |chunk, offset| {
+                match offset {
+                    0 => {
+                        assert_eq!(iteration, 0);
+                        check_chunk(chunk, &[1, 2, 3], "first chunk");
+                    }
+                    3 => {
+                        assert_eq!(iteration, 1);
+                        check_chunk(chunk, &[4, 5], "second chunk");
+                    }
+                    n => {
+                        panic!("Unexpected offset {}", n);
+                    }
+                }
+                iteration += 1;
+                Ok::<(), ()>(())
+            },
+        )
+        .unwrap();
+    }
+
+    #[test]
+    fn test_leq_chunk_size() {
+        for &check_size in &[5, 6] {
+            let mut iteration = 0;
+            each_sized_chunk_mapped(
+                &[1, 2, 3, 4, 5],
+                check_size,
+                |item| item as &dyn ToSql,
+                |chunk, offset| {
+                    assert_eq!(iteration, 0);
+                    iteration += 1;
+                    assert_eq!(offset, 0);
+                    check_chunk(chunk, &[1, 2, 3, 4, 5], "only iteration");
+                    Ok::<(), ()>(())
+                },
+            )
+            .unwrap();
+        }
+    }
+
+    #[test]
+    fn test_empty_chunk() {
+        let items: &[i64] = &[];
+        each_sized_chunk_mapped::<_, _, (), _, _>(
+            items,
+            100,
+            |item| item as &dyn ToSql,
+            |_, _| {
+                panic!("Should never be called");
+            },
+        )
+        .unwrap();
+    }
+
+    #[test]
+    fn test_error() {
+        let mut iteration = 0;
+        let e = each_sized_chunk_mapped(
+            &[1, 2, 3, 4, 5, 6, 7],
+            3,
+            |item| item as &dyn ToSql,
+            |_, offset| {
+                if offset == 0 {
+                    assert_eq!(iteration, 0);
+                    iteration += 1;
+                    Ok(())
+                } else if offset == 3 {
+                    assert_eq!(iteration, 1);
+                    iteration += 1;
+                    Err("testing".to_string())
+                } else {
+                    // Make sure we stopped after the error.
+                    panic!("Shouldn't get called with offset of {}", offset);
+                }
+            },
+        )
+        .expect_err("Should be an error");
+        assert_eq!(e, "testing");
+    }
+}
+
+#[cfg(test)]
+mod test_unmapped {
+    use super::*;
+
+    #[test]
+    fn test_separate() {
+        let mut iteration = 0;
+        each_sized_chunk(&[1, 2, 3, 4, 5], 3, |chunk, offset| {
+            match offset {
+                0 => {
+                    assert_eq!(iteration, 0);
+                    check_chunk(chunk, &[1, 2, 3], "first chunk");
+                }
+                3 => {
+                    assert_eq!(iteration, 1);
+                    check_chunk(chunk, &[4, 5], "second chunk");
+                }
+                n => {
+                    panic!("Unexpected offset {}", n);
+                }
+            }
+            iteration += 1;
+            Ok::<(), ()>(())
+        })
+        .unwrap();
+    }
+
+    #[test]
+    fn test_leq_chunk_size() {
+        for &check_size in &[5, 6] {
+            let mut iteration = 0;
+            each_sized_chunk(&[1, 2, 3, 4, 5], check_size, |chunk, offset| {
+                assert_eq!(iteration, 0);
+                iteration += 1;
+                assert_eq!(offset, 0);
+                check_chunk(chunk, &[1, 2, 3, 4, 5], "only iteration");
+                Ok::<(), ()>(())
+            })
+            .unwrap();
+        }
+    }
+
+    #[test]
+    fn test_empty_chunk() {
+        let items: &[i64] = &[];
+        each_sized_chunk::<_, (), _>(items, 100, |_, _| {
+            panic!("Should never be called");
+        })
+        .unwrap();
+    }
+
+    #[test]
+    fn test_error() {
+        let mut iteration = 0;
+        let e = each_sized_chunk(&[1, 2, 3, 4, 5, 6, 7], 3, |_, offset| {
+            if offset == 0 {
+                assert_eq!(iteration, 0);
+                iteration += 1;
+                Ok(())
+            } else if offset == 3 {
+                assert_eq!(iteration, 1);
+                iteration += 1;
+                Err("testing".to_string())
+            } else {
+                // Make sure we stopped after the error.
+                panic!("Shouldn't get called with offset of {}", offset);
+            }
+        })
+        .expect_err("Should be an error");
+        assert_eq!(e, "testing");
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/sql_support/lib.rs.html b/book/rust-docs/src/sql_support/lib.rs.html new file mode 100644 index 0000000000..a6057e4ef7 --- /dev/null +++ b/book/rust-docs/src/sql_support/lib.rs.html @@ -0,0 +1,79 @@ +lib.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#![allow(unknown_lints)]
+#![warn(rust_2018_idioms)]
+
+//! A crate with various sql/sqlcipher helpers.
+
+mod conn_ext;
+pub mod debug_tools;
+mod each_chunk;
+mod maybe_cached;
+pub mod open_database;
+mod repeat;
+
+pub use crate::conn_ext::*;
+pub use crate::each_chunk::*;
+pub use crate::maybe_cached::*;
+pub use crate::repeat::*;
+
+/// In PRAGMA foo='bar', `'bar'` must be a constant string (it cannot be a
+/// bound parameter), so we need to escape manually. According to
+/// <https://www.sqlite.org/faq.html>, the only character that must be escaped is
+/// the single quote, which is escaped by placing two single quotes in a row.
+pub fn escape_string_for_pragma(s: &str) -> String {
+    s.replace('\'', "''")
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+    #[test]
+    fn test_escape_string_for_pragma() {
+        assert_eq!(escape_string_for_pragma("foobar"), "foobar");
+        assert_eq!(escape_string_for_pragma("'foo'bar'"), "''foo''bar''");
+        assert_eq!(escape_string_for_pragma("''"), "''''");
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/sql_support/maybe_cached.rs.html b/book/rust-docs/src/sql_support/maybe_cached.rs.html new file mode 100644 index 0000000000..9905a618d2 --- /dev/null +++ b/book/rust-docs/src/sql_support/maybe_cached.rs.html @@ -0,0 +1,129 @@ +maybe_cached.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use rusqlite::{self, CachedStatement, Connection, Statement};
+
+use std::ops::{Deref, DerefMut};
+
+/// MaybeCached is a type that can be used to help abstract
+/// over cached and uncached rusqlite statements in a transparent manner.
+pub enum MaybeCached<'conn> {
+    Uncached(Statement<'conn>),
+    Cached(CachedStatement<'conn>),
+}
+
+impl<'conn> Deref for MaybeCached<'conn> {
+    type Target = Statement<'conn>;
+    #[inline]
+    fn deref(&self) -> &Statement<'conn> {
+        match self {
+            MaybeCached::Cached(cached) => Deref::deref(cached),
+            MaybeCached::Uncached(uncached) => uncached,
+        }
+    }
+}
+
+impl<'conn> DerefMut for MaybeCached<'conn> {
+    #[inline]
+    fn deref_mut(&mut self) -> &mut Statement<'conn> {
+        match self {
+            MaybeCached::Cached(cached) => DerefMut::deref_mut(cached),
+            MaybeCached::Uncached(uncached) => uncached,
+        }
+    }
+}
+
+impl<'conn> From<Statement<'conn>> for MaybeCached<'conn> {
+    #[inline]
+    fn from(stmt: Statement<'conn>) -> Self {
+        MaybeCached::Uncached(stmt)
+    }
+}
+
+impl<'conn> From<CachedStatement<'conn>> for MaybeCached<'conn> {
+    #[inline]
+    fn from(stmt: CachedStatement<'conn>) -> Self {
+        MaybeCached::Cached(stmt)
+    }
+}
+
+impl<'conn> MaybeCached<'conn> {
+    #[inline]
+    pub fn prepare(
+        conn: &'conn Connection,
+        sql: &str,
+        cached: bool,
+    ) -> rusqlite::Result<MaybeCached<'conn>> {
+        if cached {
+            Ok(MaybeCached::Cached(conn.prepare_cached(sql)?))
+        } else {
+            Ok(MaybeCached::Uncached(conn.prepare(sql)?))
+        }
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/sql_support/open_database.rs.html b/book/rust-docs/src/sql_support/open_database.rs.html new file mode 100644 index 0000000000..86a2571ba3 --- /dev/null +++ b/book/rust-docs/src/sql_support/open_database.rs.html @@ -0,0 +1,1149 @@ +open_database.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
+540
+541
+542
+543
+544
+545
+546
+547
+548
+549
+550
+551
+552
+553
+554
+555
+556
+557
+558
+559
+560
+561
+562
+563
+564
+565
+566
+567
+568
+569
+570
+571
+572
+573
+574
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/// Use this module to open a new SQLite database connection.
+///
+/// Usage:
+///    - Define a struct that implements ConnectionInitializer.  This handles:
+///      - Initializing the schema for a new database
+///      - Upgrading the schema for an existing database
+///      - Extra preparation/finishing steps, for example setting up SQLite functions
+///
+///    - Call open_database() in your database constructor:
+///      - The first method called is `prepare()`.  This is executed outside of a transaction
+///        and is suitable for executing pragmas (eg, `PRAGMA journal_mode=wal`), defining
+///        functions, etc.
+///      - If the database file is not present and the connection is writable, open_database()
+///        will create a new DB and call init(), then finish(). If the connection is not
+///        writable it will panic, meaning that if you support ReadOnly connections, they must
+///        be created after a writable connection is open.
+///      - If the database file exists and the connection is writable, open_database() will open
+///        it and call prepare(), upgrade_from() for each upgrade that needs to be applied, then
+///        finish(). As above, a read-only connection will panic if upgrades are necessary, so
+///        you should ensure the first connection opened is writable.
+///      - If the database file is corrupt, or upgrade_from() returns [`Error::Corrupt`], the
+///        database file will be removed and replaced with a new DB.
+///      - If the connection is not writable, `finish()` will be called (ie, `finish()`, like
+///        `prepare()`, is called for all connections)
+///
+///  See the autofill DB code for an example.
+///
+use crate::ConnExt;
+use rusqlite::{
+    Connection, Error as RusqliteError, ErrorCode, OpenFlags, Transaction, TransactionBehavior,
+};
+use std::path::Path;
+use thiserror::Error;
+
+#[derive(Error, Debug)]
+pub enum Error {
+    #[error("Incompatible database version: {0}")]
+    IncompatibleVersion(u32),
+    #[error("Database is corrupt")]
+    Corrupt,
+    #[error("Error executing SQL: {0}")]
+    SqlError(rusqlite::Error),
+    #[error("Failed to recover a corrupt database due to an error deleting the file: {0}")]
+    RecoveryError(std::io::Error),
+}
+
+impl From<rusqlite::Error> for Error {
+    fn from(value: rusqlite::Error) -> Self {
+        match value {
+            RusqliteError::SqliteFailure(e, _)
+                if matches!(e.code, ErrorCode::DatabaseCorrupt | ErrorCode::NotADatabase) =>
+            {
+                Self::Corrupt
+            }
+            _ => Self::SqlError(value),
+        }
+    }
+}
+
+pub type Result<T> = std::result::Result<T, Error>;
+
+pub trait ConnectionInitializer {
+    // Name to display in the logs
+    const NAME: &'static str;
+
+    // The version that the last upgrade function upgrades to.
+    const END_VERSION: u32;
+
+    // Functions called only for writable connections all take a Transaction
+    // Initialize a newly created database to END_VERSION
+    fn init(&self, tx: &Transaction<'_>) -> Result<()>;
+
+    // Upgrade schema from version -> version + 1
+    fn upgrade_from(&self, conn: &Transaction<'_>, version: u32) -> Result<()>;
+
+    // Runs immediately after creation for all types of connections. If writable,
+    // will *not* be in the transaction created for the "only writable" functions above.
+    fn prepare(&self, _conn: &Connection, _db_empty: bool) -> Result<()> {
+        Ok(())
+    }
+
+    // Runs for all types of connections. If a writable connection is being
+    // initialized, this will be called after all initialization functions,
+    // but inside their transaction.
+    fn finish(&self, _conn: &Connection) -> Result<()> {
+        Ok(())
+    }
+}
+
+pub fn open_database<CI: ConnectionInitializer, P: AsRef<Path>>(
+    path: P,
+    connection_initializer: &CI,
+) -> Result<Connection> {
+    open_database_with_flags(path, OpenFlags::default(), connection_initializer)
+}
+
+pub fn open_memory_database<CI: ConnectionInitializer>(
+    conn_initializer: &CI,
+) -> Result<Connection> {
+    open_memory_database_with_flags(OpenFlags::default(), conn_initializer)
+}
+
+pub fn open_database_with_flags<CI: ConnectionInitializer, P: AsRef<Path>>(
+    path: P,
+    open_flags: OpenFlags,
+    connection_initializer: &CI,
+) -> Result<Connection> {
+    do_open_database_with_flags(&path, open_flags, connection_initializer).or_else(|e| {
+        // See if we can recover from the error and try a second time
+        try_handle_db_failure(&path, open_flags, connection_initializer, e)?;
+        do_open_database_with_flags(&path, open_flags, connection_initializer)
+    })
+}
+
+fn do_open_database_with_flags<CI: ConnectionInitializer, P: AsRef<Path>>(
+    path: P,
+    open_flags: OpenFlags,
+    connection_initializer: &CI,
+) -> Result<Connection> {
+    // Try running the migration logic with an existing file
+    log::debug!("{}: opening database", CI::NAME);
+    let mut conn = Connection::open_with_flags(path, open_flags)?;
+    log::debug!("{}: checking if initialization is necessary", CI::NAME);
+    let db_empty = is_db_empty(&conn)?;
+
+    log::debug!("{}: preparing", CI::NAME);
+    connection_initializer.prepare(&conn, db_empty)?;
+
+    if open_flags.contains(OpenFlags::SQLITE_OPEN_READ_WRITE) {
+        let tx = conn.transaction_with_behavior(TransactionBehavior::Immediate)?;
+        if db_empty {
+            log::debug!("{}: initializing new database", CI::NAME);
+            connection_initializer.init(&tx)?;
+        } else {
+            let mut current_version = get_schema_version(&tx)?;
+            if current_version > CI::END_VERSION {
+                return Err(Error::IncompatibleVersion(current_version));
+            }
+            while current_version < CI::END_VERSION {
+                log::debug!(
+                    "{}: upgrading database to {}",
+                    CI::NAME,
+                    current_version + 1
+                );
+                connection_initializer.upgrade_from(&tx, current_version)?;
+                current_version += 1;
+            }
+        }
+        log::debug!("{}: finishing writable database open", CI::NAME);
+        connection_initializer.finish(&tx)?;
+        set_schema_version(&tx, CI::END_VERSION)?;
+        tx.commit()?;
+    } else {
+        // There's an implied requirement that the first connection to a DB is
+        // writable, so read-only connections do much less, but panic if stuff is wrong
+        assert!(!db_empty, "existing writer must have initialized");
+        assert!(
+            get_schema_version(&conn)? == CI::END_VERSION,
+            "existing writer must have migrated"
+        );
+        log::debug!("{}: finishing readonly database open", CI::NAME);
+        connection_initializer.finish(&conn)?;
+    }
+    log::debug!("{}: database open successful", CI::NAME);
+    Ok(conn)
+}
+
+pub fn open_memory_database_with_flags<CI: ConnectionInitializer>(
+    flags: OpenFlags,
+    conn_initializer: &CI,
+) -> Result<Connection> {
+    open_database_with_flags(":memory:", flags, conn_initializer)
+}
+
+// Attempt to handle failure when opening the database.
+//
+// Returns:
+//   - Ok(()) the failure is potentially handled and we should make a second open attempt
+//   - Err(e) the failure couldn't be handled and we should return this error
+fn try_handle_db_failure<CI: ConnectionInitializer, P: AsRef<Path>>(
+    path: P,
+    open_flags: OpenFlags,
+    _connection_initializer: &CI,
+    err: Error,
+) -> Result<()> {
+    if !open_flags.contains(OpenFlags::SQLITE_OPEN_CREATE)
+        && matches!(err, Error::SqlError(rusqlite::Error::SqliteFailure(code, _)) if code.code == rusqlite::ErrorCode::CannotOpen)
+    {
+        log::info!(
+            "{}: database doesn't exist, but we weren't requested to create it",
+            CI::NAME
+        );
+        return Err(err);
+    }
+    log::warn!("{}: database operation failed: {}", CI::NAME, err);
+    if !open_flags.contains(OpenFlags::SQLITE_OPEN_READ_WRITE) {
+        log::warn!(
+            "{}: not attempting recovery as this is a read-only connection request",
+            CI::NAME
+        );
+        return Err(err);
+    }
+
+    let delete = matches!(err, Error::Corrupt);
+    if delete {
+        log::info!(
+            "{}: the database is fatally damaged; deleting and starting fresh",
+            CI::NAME
+        );
+        // Note we explicitly decline to move the path to, say ".corrupt", as it's difficult to
+        // identify any value there - actually getting our hands on the file from a mobile device
+        // is tricky and it would just take up disk space forever.
+        if let Err(io_err) = std::fs::remove_file(path) {
+            return Err(Error::RecoveryError(io_err));
+        }
+        Ok(())
+    } else {
+        Err(err)
+    }
+}
+
+fn is_db_empty(conn: &Connection) -> Result<bool> {
+    Ok(conn.query_one::<u32>("SELECT COUNT(*) FROM sqlite_master")? == 0)
+}
+
+fn get_schema_version(conn: &Connection) -> Result<u32> {
+    let version = conn.query_row_and_then("PRAGMA user_version", [], |row| row.get(0))?;
+    Ok(version)
+}
+
+fn set_schema_version(conn: &Connection, version: u32) -> Result<()> {
+    conn.set_pragma("user_version", version)?;
+    Ok(())
+}
+
+// It would be nice for this to be #[cfg(test)], but that doesn't allow it to be used in tests for
+// our other crates.
+pub mod test_utils {
+    use super::*;
+    use std::path::PathBuf;
+    use tempfile::TempDir;
+
+    // Database file that we can programatically run upgrades on
+    //
+    // We purposefully don't keep a connection to the database around to force upgrades to always
+    // run against a newly opened DB, like they would in the real world.  See #4106 for
+    // details.
+    pub struct MigratedDatabaseFile<CI: ConnectionInitializer> {
+        // Keep around a TempDir to ensure the database file stays around until this struct is
+        // dropped
+        _tempdir: TempDir,
+        pub connection_initializer: CI,
+        pub path: PathBuf,
+    }
+
+    impl<CI: ConnectionInitializer> MigratedDatabaseFile<CI> {
+        pub fn new(connection_initializer: CI, init_sql: &str) -> Self {
+            Self::new_with_flags(connection_initializer, init_sql, OpenFlags::default())
+        }
+
+        pub fn new_with_flags(
+            connection_initializer: CI,
+            init_sql: &str,
+            open_flags: OpenFlags,
+        ) -> Self {
+            let tempdir = tempfile::tempdir().unwrap();
+            let path = tempdir.path().join(Path::new("db.sql"));
+            let conn = Connection::open_with_flags(&path, open_flags).unwrap();
+            conn.execute_batch(init_sql).unwrap();
+            Self {
+                _tempdir: tempdir,
+                connection_initializer,
+                path,
+            }
+        }
+
+        pub fn upgrade_to(&self, version: u32) {
+            let mut conn = self.open();
+            let tx = conn.transaction().unwrap();
+            let mut current_version = get_schema_version(&tx).unwrap();
+            while current_version < version {
+                self.connection_initializer
+                    .upgrade_from(&tx, current_version)
+                    .unwrap();
+                current_version += 1;
+            }
+            set_schema_version(&tx, current_version).unwrap();
+            self.connection_initializer.finish(&tx).unwrap();
+            tx.commit().unwrap();
+        }
+
+        pub fn run_all_upgrades(&self) {
+            let current_version = get_schema_version(&self.open()).unwrap();
+            for version in current_version..CI::END_VERSION {
+                self.upgrade_to(version + 1);
+            }
+        }
+
+        pub fn open(&self) -> Connection {
+            Connection::open(&self.path).unwrap()
+        }
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::test_utils::MigratedDatabaseFile;
+    use super::*;
+    use std::cell::RefCell;
+    use std::io::Write;
+
+    struct TestConnectionInitializer {
+        pub calls: RefCell<Vec<&'static str>>,
+        pub buggy_v3_upgrade: bool,
+    }
+
+    impl TestConnectionInitializer {
+        pub fn new() -> Self {
+            let _ = env_logger::try_init();
+            Self {
+                calls: RefCell::new(Vec::new()),
+                buggy_v3_upgrade: false,
+            }
+        }
+        pub fn new_with_buggy_logic() -> Self {
+            let _ = env_logger::try_init();
+            Self {
+                calls: RefCell::new(Vec::new()),
+                buggy_v3_upgrade: true,
+            }
+        }
+
+        pub fn clear_calls(&self) {
+            self.calls.borrow_mut().clear();
+        }
+
+        pub fn push_call(&self, call: &'static str) {
+            self.calls.borrow_mut().push(call);
+        }
+
+        pub fn check_calls(&self, expected: Vec<&'static str>) {
+            assert_eq!(*self.calls.borrow(), expected);
+        }
+    }
+
+    impl ConnectionInitializer for TestConnectionInitializer {
+        const NAME: &'static str = "test db";
+        const END_VERSION: u32 = 4;
+
+        fn prepare(&self, conn: &Connection, _: bool) -> Result<()> {
+            self.push_call("prep");
+            conn.execute_batch(
+                "
+                PRAGMA journal_mode = wal;
+                ",
+            )?;
+            Ok(())
+        }
+
+        fn init(&self, conn: &Transaction<'_>) -> Result<()> {
+            self.push_call("init");
+            conn.execute_batch(
+                "
+                CREATE TABLE prep_table(col);
+                INSERT INTO prep_table(col) VALUES ('correct-value');
+                CREATE TABLE my_table(col);
+                ",
+            )
+            .map_err(|e| e.into())
+        }
+
+        fn upgrade_from(&self, conn: &Transaction<'_>, version: u32) -> Result<()> {
+            match version {
+                // This upgrade forces the database to be replaced by returning
+                // `Error::Corrupt`.
+                1 => {
+                    self.push_call("upgrade_from_v1");
+                    Err(Error::Corrupt)
+                }
+                2 => {
+                    self.push_call("upgrade_from_v2");
+                    conn.execute_batch(
+                        "
+                        ALTER TABLE my_old_table_name RENAME TO my_table;
+                        ",
+                    )?;
+                    Ok(())
+                }
+                3 => {
+                    self.push_call("upgrade_from_v3");
+
+                    if self.buggy_v3_upgrade {
+                        conn.execute_batch("ILLEGAL_SQL_CODE")?;
+                    }
+
+                    conn.execute_batch(
+                        "
+                        ALTER TABLE my_table RENAME COLUMN old_col to col;
+                        ",
+                    )?;
+                    Ok(())
+                }
+                _ => {
+                    panic!("Unexpected version: {}", version);
+                }
+            }
+        }
+
+        fn finish(&self, conn: &Connection) -> Result<()> {
+            self.push_call("finish");
+            conn.execute_batch(
+                "
+                INSERT INTO my_table(col) SELECT col FROM prep_table;
+                ",
+            )?;
+            Ok(())
+        }
+    }
+
+    // A special schema used to test the upgrade that forces the database to be
+    // replaced.
+    static INIT_V1: &str = "
+        CREATE TABLE prep_table(col);
+        PRAGMA user_version=1;
+    ";
+
+    // Initialize the database to v2 to test upgrading from there
+    static INIT_V2: &str = "
+        CREATE TABLE prep_table(col);
+        INSERT INTO prep_table(col) VALUES ('correct-value');
+        CREATE TABLE my_old_table_name(old_col);
+        PRAGMA user_version=2;
+    ";
+
+    fn check_final_data(conn: &Connection) {
+        let value: String = conn
+            .query_row("SELECT col FROM my_table", [], |r| r.get(0))
+            .unwrap();
+        assert_eq!(value, "correct-value");
+        assert_eq!(get_schema_version(conn).unwrap(), 4);
+    }
+
+    #[test]
+    fn test_init() {
+        let connection_initializer = TestConnectionInitializer::new();
+        let conn = open_memory_database(&connection_initializer).unwrap();
+        check_final_data(&conn);
+        connection_initializer.check_calls(vec!["prep", "init", "finish"]);
+    }
+
+    #[test]
+    fn test_upgrades() {
+        let db_file = MigratedDatabaseFile::new(TestConnectionInitializer::new(), INIT_V2);
+        let conn = open_database(db_file.path.clone(), &db_file.connection_initializer).unwrap();
+        check_final_data(&conn);
+        db_file.connection_initializer.check_calls(vec![
+            "prep",
+            "upgrade_from_v2",
+            "upgrade_from_v3",
+            "finish",
+        ]);
+    }
+
+    #[test]
+    fn test_open_current_version() {
+        let db_file = MigratedDatabaseFile::new(TestConnectionInitializer::new(), INIT_V2);
+        db_file.upgrade_to(4);
+        db_file.connection_initializer.clear_calls();
+        let conn = open_database(db_file.path.clone(), &db_file.connection_initializer).unwrap();
+        check_final_data(&conn);
+        db_file
+            .connection_initializer
+            .check_calls(vec!["prep", "finish"]);
+    }
+
+    #[test]
+    fn test_pragmas() {
+        let db_file = MigratedDatabaseFile::new(TestConnectionInitializer::new(), INIT_V2);
+        let conn = open_database(db_file.path.clone(), &db_file.connection_initializer).unwrap();
+        assert_eq!(
+            conn.query_one::<String>("PRAGMA journal_mode").unwrap(),
+            "wal"
+        );
+    }
+
+    #[test]
+    fn test_migration_error() {
+        let db_file =
+            MigratedDatabaseFile::new(TestConnectionInitializer::new_with_buggy_logic(), INIT_V2);
+        db_file
+            .open()
+            .execute(
+                "INSERT INTO my_old_table_name(old_col) VALUES ('I should not be deleted')",
+                [],
+            )
+            .unwrap();
+
+        open_database(db_file.path.clone(), &db_file.connection_initializer).unwrap_err();
+        // Even though the upgrades failed, the data should still be there.  The changes that
+        // upgrade_to_v3 made should have been rolled back.
+        assert_eq!(
+            db_file
+                .open()
+                .query_one::<i32>("SELECT COUNT(*) FROM my_old_table_name")
+                .unwrap(),
+            1
+        );
+    }
+
+    #[test]
+    fn test_version_too_new() {
+        let db_file = MigratedDatabaseFile::new(TestConnectionInitializer::new(), INIT_V2);
+        set_schema_version(&db_file.open(), 5).unwrap();
+
+        db_file
+            .open()
+            .execute(
+                "INSERT INTO my_old_table_name(old_col) VALUES ('I should not be deleted')",
+                [],
+            )
+            .unwrap();
+
+        assert!(matches!(
+            open_database(db_file.path.clone(), &db_file.connection_initializer,),
+            Err(Error::IncompatibleVersion(5))
+        ));
+        // Make sure that even when DeleteAndRecreate is specified, we don't delete the database
+        // file when the schema is newer
+        assert_eq!(
+            db_file
+                .open()
+                .query_one::<i32>("SELECT COUNT(*) FROM my_old_table_name")
+                .unwrap(),
+            1
+        );
+    }
+
+    #[test]
+    fn test_corrupt_db() {
+        let tempdir = tempfile::tempdir().unwrap();
+        let path = tempdir.path().join(Path::new("corrupt-db.sql"));
+        let mut file = std::fs::File::create(path.clone()).unwrap();
+        // interestingly, sqlite seems to treat a 0-byte file as a missing one.
+        // Note that this will exercise the `ErrorCode::NotADatabase` error code. It's not clear
+        // how we could hit `ErrorCode::DatabaseCorrupt`, but even if we could, there's not much
+        // value as this test can't really observe which one it was.
+        file.write_all(b"not sql").unwrap();
+        let metadata = std::fs::metadata(path.clone()).unwrap();
+        assert_eq!(metadata.len(), 7);
+        drop(file);
+        open_database(path.clone(), &TestConnectionInitializer::new()).unwrap();
+        let metadata = std::fs::metadata(path).unwrap();
+        // just check the file is no longer what it was before.
+        assert_ne!(metadata.len(), 7);
+    }
+
+    #[test]
+    fn test_force_replace() {
+        let db_file = MigratedDatabaseFile::new(TestConnectionInitializer::new(), INIT_V1);
+        let conn = open_database(db_file.path.clone(), &db_file.connection_initializer).unwrap();
+        check_final_data(&conn);
+        db_file.connection_initializer.check_calls(vec![
+            "prep",
+            "upgrade_from_v1",
+            "prep",
+            "init",
+            "finish",
+        ]);
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/sql_support/repeat.rs.html b/book/rust-docs/src/sql_support/repeat.rs.html new file mode 100644 index 0000000000..a8b4084008 --- /dev/null +++ b/book/rust-docs/src/sql_support/repeat.rs.html @@ -0,0 +1,227 @@ +repeat.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use std::fmt;
+
+/// Helper type for printing repeated strings more efficiently. You should use
+/// [`repeat_display`] or one of the `repeat_sql_*` helpers to
+/// construct it.
+#[derive(Debug, Clone)]
+pub struct RepeatDisplay<'a, F> {
+    count: usize,
+    sep: &'a str,
+    fmt_one: F,
+}
+
+impl<'a, F> fmt::Display for RepeatDisplay<'a, F>
+where
+    F: Fn(usize, &mut fmt::Formatter<'_>) -> fmt::Result,
+{
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        for i in 0..self.count {
+            if i != 0 {
+                f.write_str(self.sep)?;
+            }
+            (self.fmt_one)(i, f)?;
+        }
+        Ok(())
+    }
+}
+
+/// Construct a RepeatDisplay that will repeatedly call `fmt_one` with a formatter `count` times,
+/// separated by `sep`.
+///
+/// # Example
+///
+/// ```rust
+/// # use sql_support::repeat_display;
+/// assert_eq!(format!("{}", repeat_display(1, ",", |i, f| write!(f, "({},?)", i))),
+///            "(0,?)");
+/// assert_eq!(format!("{}", repeat_display(2, ",", |i, f| write!(f, "({},?)", i))),
+///            "(0,?),(1,?)");
+/// assert_eq!(format!("{}", repeat_display(3, ",", |i, f| write!(f, "({},?)", i))),
+///            "(0,?),(1,?),(2,?)");
+/// ```
+#[inline]
+pub fn repeat_display<F>(count: usize, sep: &str, fmt_one: F) -> RepeatDisplay<'_, F>
+where
+    F: Fn(usize, &mut fmt::Formatter<'_>) -> fmt::Result,
+{
+    RepeatDisplay {
+        count,
+        sep,
+        fmt_one,
+    }
+}
+
+/// Returns a value that formats as `count` instances of `?` separated by commas.
+///
+/// # Example
+///
+/// ```rust
+/// # use sql_support::repeat_sql_vars;
+/// assert_eq!(format!("{}", repeat_sql_vars(0)), "");
+/// assert_eq!(format!("{}", repeat_sql_vars(1)), "?");
+/// assert_eq!(format!("{}", repeat_sql_vars(2)), "?,?");
+/// assert_eq!(format!("{}", repeat_sql_vars(3)), "?,?,?");
+/// ```
+pub fn repeat_sql_vars(count: usize) -> impl fmt::Display {
+    repeat_display(count, ",", |_, f| write!(f, "?"))
+}
+
+/// Returns a value that formats as `count` instances of `(?)` separated by commas.
+///
+/// # Example
+///
+/// ```rust
+/// # use sql_support::repeat_sql_values;
+/// assert_eq!(format!("{}", repeat_sql_values(0)), "");
+/// assert_eq!(format!("{}", repeat_sql_values(1)), "(?)");
+/// assert_eq!(format!("{}", repeat_sql_values(2)), "(?),(?)");
+/// assert_eq!(format!("{}", repeat_sql_values(3)), "(?),(?),(?)");
+/// ```
+///
+pub fn repeat_sql_values(count: usize) -> impl fmt::Display {
+    // We could also implement this as `repeat_sql_multi_values(count, 1)`,
+    // but this is faster and no less clear IMO.
+    repeat_display(count, ",", |_, f| write!(f, "(?)"))
+}
+
+/// Returns a value that formats as `num_values` instances of `(?,?,?,...)` (where there are
+/// `vars_per_value` question marks separated by commas in between the `?`s).
+///
+/// Panics if `vars_per_value` is zero (however, `num_values` is allowed to be zero).
+///
+/// # Example
+///
+/// ```rust
+/// # use sql_support::repeat_multi_values;
+/// assert_eq!(format!("{}", repeat_multi_values(0, 2)), "");
+/// assert_eq!(format!("{}", repeat_multi_values(1, 5)), "(?,?,?,?,?)");
+/// assert_eq!(format!("{}", repeat_multi_values(2, 3)), "(?,?,?),(?,?,?)");
+/// assert_eq!(format!("{}", repeat_multi_values(3, 1)), "(?),(?),(?)");
+/// ```
+pub fn repeat_multi_values(num_values: usize, vars_per_value: usize) -> impl fmt::Display {
+    assert_ne!(
+        vars_per_value, 0,
+        "Illegal value for `vars_per_value`, must not be zero"
+    );
+    repeat_display(num_values, ",", move |_, f| {
+        write!(f, "({})", repeat_sql_vars(vars_per_value))
+    })
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/sync15/bso/content.rs.html b/book/rust-docs/src/sync15/bso/content.rs.html new file mode 100644 index 0000000000..1009d8dde4 --- /dev/null +++ b/book/rust-docs/src/sync15/bso/content.rs.html @@ -0,0 +1,811 @@ +content.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+//! This module enhances the IncomingBso and OutgoingBso records to deal with
+//! arbitrary <T> types, which we call "content"
+//! It can:
+//! * Parse JSON into some <T> while handling tombstones and invalid json.
+//! * Turn arbitrary <T> objects with an `id` field into an OutgoingBso.
+
+use super::{IncomingBso, IncomingContent, IncomingKind, OutgoingBso, OutgoingEnvelope};
+use crate::Guid;
+use error_support::report_error;
+use serde::Serialize;
+
+// The only errors we return here are serde errors.
+type Result<T> = std::result::Result<T, serde_json::Error>;
+
+impl<T> IncomingContent<T> {
+    /// Returns Some(content) if [self.kind] is [IncomingKind::Content], None otherwise.
+    pub fn content(self) -> Option<T> {
+        match self.kind {
+            IncomingKind::Content(t) => Some(t),
+            _ => None,
+        }
+    }
+}
+
+// We don't want to force our T to be Debug, but we can be Debug if T is.
+impl<T: std::fmt::Debug> std::fmt::Debug for IncomingKind<T> {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            IncomingKind::Content(r) => {
+                write!(f, "IncomingKind::Content<{:?}>", r)
+            }
+            IncomingKind::Tombstone => write!(f, "IncomingKind::Tombstone"),
+            IncomingKind::Malformed => write!(f, "IncomingKind::Malformed"),
+        }
+    }
+}
+
+impl IncomingBso {
+    /// Convert an [IncomingBso] to an [IncomingContent] possibly holding a T.
+    pub fn into_content<T: for<'de> serde::Deserialize<'de>>(self) -> IncomingContent<T> {
+        self.into_content_with_fixup(|_| {})
+    }
+
+    /// Like into_content, but adds an additional fixup step where the caller can adjust the
+    /// `serde_json::Value'
+    pub fn into_content_with_fixup<T: for<'de> serde::Deserialize<'de>>(
+        self,
+        fixup: impl FnOnce(&mut serde_json::Value),
+    ) -> IncomingContent<T> {
+        match serde_json::from_str(&self.payload) {
+            Ok(mut json) => {
+                // We got a good serde_json::Value, run the fixup method
+                fixup(&mut json);
+                // ...now see if it's a <T>.
+                let kind = json_to_kind(json, &self.envelope.id);
+                IncomingContent {
+                    envelope: self.envelope,
+                    kind,
+                }
+            }
+            Err(e) => {
+                // payload isn't valid json.
+                log::warn!("Invalid incoming cleartext {}: {}", self.envelope.id, e);
+                IncomingContent {
+                    envelope: self.envelope,
+                    kind: IncomingKind::Malformed,
+                }
+            }
+        }
+    }
+}
+
+impl OutgoingBso {
+    /// Creates a new tombstone record.
+    /// Not all collections expect tombstones.
+    pub fn new_tombstone(envelope: OutgoingEnvelope) -> Self {
+        Self {
+            envelope,
+            payload: serde_json::json!({"deleted": true}).to_string(),
+        }
+    }
+
+    /// Creates a outgoing record from some <T>, which can be made into a JSON object
+    /// with a valid `id`. This is the most convenient way to create an outgoing
+    /// item from a <T> when the default envelope is suitable.
+    /// Will panic if there's no good `id` in the json.
+    pub fn from_content_with_id<T>(record: T) -> Result<Self>
+    where
+        T: Serialize,
+    {
+        let (json, id) = content_with_id_to_json(record)?;
+        Ok(Self {
+            envelope: id.into(),
+            payload: serde_json::to_string(&json)?,
+        })
+    }
+
+    /// Create an Outgoing record with an explicit envelope. Will panic if the
+    /// payload has an ID but it doesn't match the envelope.
+    pub fn from_content<T>(envelope: OutgoingEnvelope, record: T) -> Result<Self>
+    where
+        T: Serialize,
+    {
+        let json = content_to_json(record, &envelope.id)?;
+        Ok(Self {
+            envelope,
+            payload: serde_json::to_string(&json)?,
+        })
+    }
+}
+
+// Helpers for packing and unpacking serde objects to and from a <T>. In particular:
+// * Helping deal complications around raw json payload not having 'id' (the envelope is
+//   canonical) but needing it to exist when dealing with serde locally.
+//   For example, a record on the server after being decrypted looks like:
+//   `{"id": "a-guid", payload: {"field": "value"}}`
+//   But the `T` for this typically looks like `struct T { id: Guid, field: String}`
+//   So before we try and deserialize this record into a T, we copy the `id` field
+//   from the envelope into the payload, and when serializing from a T we do the
+//   reverse (ie, ensure the `id` in the payload is removed and placed in the envelope)
+// * Tombstones.
+
+// Deserializing json into a T
+fn json_to_kind<T>(mut json: serde_json::Value, id: &Guid) -> IncomingKind<T>
+where
+    T: for<'de> serde::Deserialize<'de>,
+{
+    // It's possible that the payload does not carry 'id', but <T> always does - so grab it from the
+    // envelope and put it into the json before deserializing the record.
+    if let serde_json::Value::Object(ref mut map) = json {
+        if map.contains_key("deleted") {
+            return IncomingKind::Tombstone;
+        }
+        match map.get("id") {
+            Some(serde_json::Value::String(content_id)) => {
+                // It exists in the payload! We treat a mismatch as malformed.
+                if content_id != id {
+                    log::trace!(
+                        "malformed incoming record: envelope id: {} payload id: {}",
+                        content_id,
+                        id
+                    );
+                    report_error!(
+                        "incoming-invalid-mismatched-ids",
+                        "Envelope and payload don't agree on the ID"
+                    );
+                    return IncomingKind::Malformed;
+                }
+                if !id.is_valid_for_sync_server() {
+                    log::trace!("malformed incoming record: id is not valid: {}", id);
+                    report_error!(
+                        "incoming-invalid-bad-payload-id",
+                        "ID in the payload is invalid"
+                    );
+                    return IncomingKind::Malformed;
+                }
+            }
+            Some(v) => {
+                // It exists in the payload but is not a string - they can't possibly be
+                // the same as the envelope uses a String, so must be malformed.
+                log::trace!("malformed incoming record: id is not a string: {}", v);
+                report_error!("incoming-invalid-wrong_type", "ID is not a string");
+                return IncomingKind::Malformed;
+            }
+            None => {
+                // Doesn't exist in the payload - add it before trying to deser a T.
+                if !id.is_valid_for_sync_server() {
+                    log::trace!("malformed incoming record: id is not valid: {}", id);
+                    report_error!(
+                        "incoming-invalid-bad-envelope-id",
+                        "ID in envelope is not valid"
+                    );
+                    return IncomingKind::Malformed;
+                }
+                map.insert("id".to_string(), id.to_string().into());
+            }
+        }
+    };
+    match serde_path_to_error::deserialize(json) {
+        Ok(v) => IncomingKind::Content(v),
+        Err(e) => {
+            report_error!(
+                "invalid-incoming-content",
+                "{}.{}: {}",
+                std::any::type_name::<T>(),
+                e.path(),
+                e.inner()
+            );
+            IncomingKind::Malformed
+        }
+    }
+}
+
+// Serializing <T> into json with special handling of `id` (the `id` from the payload
+// is used as the envelope ID)
+fn content_with_id_to_json<T>(record: T) -> Result<(serde_json::Value, Guid)>
+where
+    T: Serialize,
+{
+    let mut json = serde_json::to_value(record)?;
+    let id = match json.as_object_mut() {
+        Some(ref mut map) => {
+            match map.get("id").as_ref().and_then(|v| v.as_str()) {
+                Some(id) => {
+                    let id: Guid = id.into();
+                    assert!(id.is_valid_for_sync_server(), "record's ID is invalid");
+                    id
+                }
+                // In practice, this is a "static" error and not influenced by runtime behavior
+                None => panic!("record does not have an ID in the payload"),
+            }
+        }
+        None => panic!("record is not a json object"),
+    };
+    Ok((json, id))
+}
+
+// Serializing <T> into json with special handling of `id` (if `id` in serialized
+// JSON already exists, we panic if it doesn't match the envelope. If the serialized
+// content does not have an `id`, it is added from the envelope)
+// is used as the envelope ID)
+fn content_to_json<T>(record: T, id: &Guid) -> Result<serde_json::Value>
+where
+    T: Serialize,
+{
+    let mut payload = serde_json::to_value(record)?;
+    if let Some(ref mut map) = payload.as_object_mut() {
+        if let Some(content_id) = map.get("id").as_ref().and_then(|v| v.as_str()) {
+            assert_eq!(content_id, id);
+            assert!(id.is_valid_for_sync_server(), "record's ID is invalid");
+        } else {
+            map.insert("id".to_string(), serde_json::Value::String(id.to_string()));
+        }
+    };
+    Ok(payload)
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::bso::IncomingBso;
+    use serde::{Deserialize, Serialize};
+    use serde_json::json;
+
+    #[derive(Default, Debug, PartialEq, Serialize, Deserialize)]
+    struct TestStruct {
+        id: Guid,
+        data: u32,
+    }
+    #[test]
+    fn test_content_deser() {
+        env_logger::try_init().ok();
+        let json = json!({
+            "id": "test",
+            "payload": json!({"data": 1}).to_string(),
+        });
+        let incoming: IncomingBso = serde_json::from_value(json).unwrap();
+        assert_eq!(incoming.envelope.id, "test");
+        let record = incoming.into_content::<TestStruct>().content().unwrap();
+        let expected = TestStruct {
+            id: Guid::new("test"),
+            data: 1,
+        };
+        assert_eq!(record, expected);
+    }
+
+    #[test]
+    fn test_content_deser_empty_id() {
+        env_logger::try_init().ok();
+        let json = json!({
+            "id": "",
+            "payload": json!({"data": 1}).to_string(),
+        });
+        let incoming: IncomingBso = serde_json::from_value(json).unwrap();
+        // The envelope has an invalid ID, but it's not handled until we try and deserialize
+        // it into a T
+        assert_eq!(incoming.envelope.id, "");
+        let content = incoming.into_content::<TestStruct>();
+        assert!(matches!(content.kind, IncomingKind::Malformed));
+    }
+
+    #[test]
+    fn test_content_deser_invalid() {
+        env_logger::try_init().ok();
+        // And a non-empty but still invalid guid.
+        let json = json!({
+            "id": "X".repeat(65),
+            "payload": json!({"data": 1}).to_string(),
+        });
+        let incoming: IncomingBso = serde_json::from_value(json).unwrap();
+        let content = incoming.into_content::<TestStruct>();
+        assert!(matches!(content.kind, IncomingKind::Malformed));
+    }
+
+    #[test]
+    fn test_content_deser_not_string() {
+        env_logger::try_init().ok();
+        // A non-string id.
+        let json = json!({
+            "id": "0",
+            "payload": json!({"id": 0, "data": 1}).to_string(),
+        });
+        let incoming: IncomingBso = serde_json::from_value(json).unwrap();
+        let content = incoming.into_content::<serde_json::Value>();
+        assert!(matches!(content.kind, IncomingKind::Malformed));
+    }
+
+    #[test]
+    fn test_content_ser_with_id() {
+        env_logger::try_init().ok();
+        // When serializing, expect the ID to be in the top-level payload (ie,
+        // in the envelope) but should not appear in the cleartext `payload` part of
+        // the payload.
+        let val = TestStruct {
+            id: Guid::new("test"),
+            data: 1,
+        };
+        let outgoing = OutgoingBso::from_content_with_id(val).unwrap();
+
+        // The envelope should have our ID.
+        assert_eq!(outgoing.envelope.id, Guid::new("test"));
+
+        // and make sure `cleartext` part of the payload the data and the id.
+        let ct_value = serde_json::from_str::<serde_json::Value>(&outgoing.payload).unwrap();
+        assert_eq!(ct_value, json!({"data": 1, "id": "test"}));
+    }
+
+    #[test]
+    fn test_content_ser_with_envelope() {
+        env_logger::try_init().ok();
+        // When serializing, expect the ID to be in the top-level payload (ie,
+        // in the envelope) but should not appear in the cleartext `payload`
+        let val = TestStruct {
+            id: Guid::new("test"),
+            data: 1,
+        };
+        let envelope: OutgoingEnvelope = Guid::new("test").into();
+        let outgoing = OutgoingBso::from_content(envelope, val).unwrap();
+
+        // The envelope should have our ID.
+        assert_eq!(outgoing.envelope.id, Guid::new("test"));
+
+        // and make sure `cleartext` part of the payload has data and the id.
+        let ct_value = serde_json::from_str::<serde_json::Value>(&outgoing.payload).unwrap();
+        assert_eq!(ct_value, json!({"data": 1, "id": "test"}));
+    }
+
+    #[test]
+    #[should_panic]
+    fn test_content_ser_no_ids() {
+        env_logger::try_init().ok();
+        #[derive(Serialize)]
+        struct StructWithNoId {
+            data: u32,
+        }
+        let val = StructWithNoId { data: 1 };
+        let _ = OutgoingBso::from_content_with_id(val);
+    }
+
+    #[test]
+    #[should_panic]
+    fn test_content_ser_not_object() {
+        env_logger::try_init().ok();
+        let _ = OutgoingBso::from_content_with_id(json!("string"));
+    }
+
+    #[test]
+    #[should_panic]
+    fn test_content_ser_mismatched_ids() {
+        env_logger::try_init().ok();
+        let val = TestStruct {
+            id: Guid::new("test"),
+            data: 1,
+        };
+        let envelope: OutgoingEnvelope = Guid::new("different").into();
+        let _ = OutgoingBso::from_content(envelope, val);
+    }
+
+    #[test]
+    #[should_panic]
+    fn test_content_empty_id() {
+        env_logger::try_init().ok();
+        let val = TestStruct {
+            id: Guid::new(""),
+            data: 1,
+        };
+        let _ = OutgoingBso::from_content_with_id(val);
+    }
+
+    #[test]
+    #[should_panic]
+    fn test_content_invalid_id() {
+        env_logger::try_init().ok();
+        let val = TestStruct {
+            id: Guid::new(&"X".repeat(65)),
+            data: 1,
+        };
+        let _ = OutgoingBso::from_content_with_id(val);
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/sync15/bso/crypto.rs.html b/book/rust-docs/src/sync15/bso/crypto.rs.html new file mode 100644 index 0000000000..ae8ed38ebc --- /dev/null +++ b/book/rust-docs/src/sync15/bso/crypto.rs.html @@ -0,0 +1,395 @@ +crypto.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Support for "encrypted bso"s, as received by the storage servers.
+//! This module decrypts them into IncomingBso's suitable for use by the
+//! engines.
+use super::{IncomingBso, IncomingEnvelope, OutgoingBso, OutgoingEnvelope};
+use crate::error;
+use crate::key_bundle::KeyBundle;
+use crate::EncryptedPayload;
+use serde::{de::DeserializeOwned, Deserialize, Serialize};
+
+// The BSO implementation we use for encrypted payloads.
+// Note that this is almost identical to the IncomingBso implementations, except
+// instead of a String payload we use an EncryptedPayload. Obviously we *could*
+// just use a String payload and transform it into an EncryptedPayload - any maybe we
+// should - but this is marginally optimal in terms of deserialization.
+#[derive(Deserialize, Debug)]
+pub struct IncomingEncryptedBso {
+    #[serde(flatten)]
+    pub envelope: IncomingEnvelope,
+    #[serde(
+        with = "as_json",
+        bound(deserialize = "EncryptedPayload: DeserializeOwned")
+    )]
+    pub(crate) payload: EncryptedPayload,
+}
+
+impl IncomingEncryptedBso {
+    pub fn new(envelope: IncomingEnvelope, payload: EncryptedPayload) -> Self {
+        Self { envelope, payload }
+    }
+    /// Decrypt a BSO, consuming it into a clear-text version.
+    pub fn into_decrypted(self, key: &KeyBundle) -> error::Result<IncomingBso> {
+        Ok(IncomingBso::new(self.envelope, self.payload.decrypt(key)?))
+    }
+}
+
+#[derive(Serialize, Debug)]
+pub struct OutgoingEncryptedBso {
+    #[serde(flatten)]
+    pub envelope: OutgoingEnvelope,
+    #[serde(with = "as_json", bound(serialize = "EncryptedPayload: Serialize"))]
+    payload: EncryptedPayload,
+}
+
+impl OutgoingEncryptedBso {
+    pub fn new(envelope: OutgoingEnvelope, payload: EncryptedPayload) -> Self {
+        Self { envelope, payload }
+    }
+
+    #[inline]
+    pub fn serialized_payload_len(&self) -> usize {
+        self.payload.serialized_len()
+    }
+}
+
+impl OutgoingBso {
+    pub fn into_encrypted(self, key: &KeyBundle) -> error::Result<OutgoingEncryptedBso> {
+        Ok(OutgoingEncryptedBso {
+            envelope: self.envelope,
+            payload: EncryptedPayload::from_cleartext(key, self.payload)?,
+        })
+    }
+}
+
+// The BSOs we write to the servers expect a "payload" attribute which is a JSON serialized
+// string, rather than the JSON representation of the object.
+// ie, the serialized object is expected to look like:
+// `{"id": "some-guid", "payload": "{\"IV\": ... }"}` <-- payload is a string.
+// However, if we just serialize it directly, we end up with:
+// `{"id": "some-guid", "payload": {"IV":  ... }}` <-- payload is an object.
+// The magic here means we can serialize and deserialize directly into/from the object, correctly
+// working with the payload as a string, instead of needing to explicitly stringify/parse the
+// payload as an extra step.
+//
+// This would work for any <T>, but we only use it for EncryptedPayload - the way our cleartext
+// BSOs work mean it's not necessary there as they define the payload as a String - ie, they do
+// explicitly end up doing 2 JSON operations as an ergonomic design choice.
+mod as_json {
+    use serde::de::{self, Deserialize, DeserializeOwned, Deserializer};
+    use serde::ser::{self, Serialize, Serializer};
+
+    pub fn serialize<T, S>(t: &T, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        T: Serialize,
+        S: Serializer,
+    {
+        let j = serde_json::to_string(t).map_err(ser::Error::custom)?;
+        serializer.serialize_str(&j)
+    }
+
+    pub fn deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error>
+    where
+        T: DeserializeOwned,
+        D: Deserializer<'de>,
+    {
+        let j = String::deserialize(deserializer)?;
+        serde_json::from_str(&j).map_err(de::Error::custom)
+    }
+}
+
+// Lots of stuff for testing the sizes of encrypted records, because the servers have
+// certain limits in terms of max-POST sizes, forcing us to chunk uploads, but
+// we need to calculate based on encrypted record size rather than the raw <T> size.
+//
+// This is a little cludgey but I couldn't think of another way to have easy deserialization
+// without a bunch of wrapper types, while still only serializing a single time in the
+// postqueue.
+#[cfg(test)]
+impl OutgoingEncryptedBso {
+    /// Return the length of the serialized payload.
+    pub fn payload_serialized_len(&self) -> usize {
+        self.payload.serialized_len()
+    }
+
+    // self.payload is private, but tests want to create funky things.
+    // XXX - test only, but test in another crate :(
+    //#[cfg(test)]
+    pub fn make_test_bso(ciphertext: String) -> Self {
+        Self {
+            envelope: OutgoingEnvelope {
+                id: "".into(),
+                sortindex: None,
+                ttl: None,
+            },
+            payload: EncryptedPayload {
+                iv: "".into(),
+                hmac: "".into(),
+                ciphertext,
+            },
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::bso::OutgoingEnvelope;
+
+    #[test]
+    fn test_deserialize_enc() {
+        let serialized = r#"{
+            "id": "1234",
+            "collection": "passwords",
+            "modified": 12344321.0,
+            "payload": "{\"IV\": \"aaaaa\", \"hmac\": \"bbbbb\", \"ciphertext\": \"ccccc\"}"
+        }"#;
+        let record: IncomingEncryptedBso = serde_json::from_str(serialized).unwrap();
+        assert_eq!(&record.envelope.id, "1234");
+        assert_eq!((record.envelope.modified.0 - 12_344_321_000).abs(), 0);
+        assert_eq!(record.envelope.sortindex, None);
+        assert_eq!(&record.payload.iv, "aaaaa");
+        assert_eq!(&record.payload.hmac, "bbbbb");
+        assert_eq!(&record.payload.ciphertext, "ccccc");
+    }
+
+    #[test]
+    fn test_deserialize_autofields() {
+        let serialized = r#"{
+            "id": "1234",
+            "collection": "passwords",
+            "modified": 12344321.0,
+            "sortindex": 100,
+            "ttl": 99,
+            "payload": "{\"IV\": \"aaaaa\", \"hmac\": \"bbbbb\", \"ciphertext\": \"ccccc\"}"
+        }"#;
+        let record: IncomingEncryptedBso = serde_json::from_str(serialized).unwrap();
+        assert_eq!(record.envelope.sortindex, Some(100));
+        assert_eq!(record.envelope.ttl, Some(99));
+    }
+
+    #[test]
+    fn test_serialize_enc() {
+        let goal = r#"{"id":"1234","payload":"{\"IV\":\"aaaaa\",\"hmac\":\"bbbbb\",\"ciphertext\":\"ccccc\"}"}"#;
+        let record = OutgoingEncryptedBso {
+            envelope: OutgoingEnvelope {
+                id: "1234".into(),
+                ..Default::default()
+            },
+            payload: EncryptedPayload {
+                iv: "aaaaa".into(),
+                hmac: "bbbbb".into(),
+                ciphertext: "ccccc".into(),
+            },
+        };
+        let actual = serde_json::to_string(&record).unwrap();
+        assert_eq!(actual, goal);
+
+        let val_str_payload: serde_json::Value = serde_json::from_str(goal).unwrap();
+        assert_eq!(
+            val_str_payload["payload"].as_str().unwrap().len(),
+            record.payload.serialized_len()
+        )
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/sync15/bso/mod.rs.html b/book/rust-docs/src/sync15/bso/mod.rs.html new file mode 100644 index 0000000000..0ebaa02e8d --- /dev/null +++ b/book/rust-docs/src/sync15/bso/mod.rs.html @@ -0,0 +1,409 @@ +mod.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/// This module defines our core "bso" abstractions.
+/// In the terminology of this crate:
+/// * "bso" is an acronym for "basic storage object" and used extensively in the sync server docs.
+///    the record always has a well-defined "envelope" with metadata (eg, the ID of the record,
+///    the server timestamp of the resource,  etc) and a field called `payload`.
+///    A bso is serialized to and from JSON.
+/// * There's a "cleartext" bso:
+/// ** The payload is a String, which itself is JSON encoded (ie, this string `payload` is
+///    always double JSON encoded in a server record)
+/// ** This supplies helper methods for working with the "content" (some arbitrary <T>) in the
+///    payload.
+/// * There's an "encrypted" bso
+/// ** The payload is an [crate::enc_payload::EncryptedPayload]
+/// ** Only clients use this; as soon as practical we decrypt and as late as practical we encrypt
+///    to and from encrypted bsos.
+/// ** The encrypted bsos etc are all in the [crypto] module and require the `crypto` feature.
+///
+/// Let's look at some real-world examples:
+/// # meta/global
+/// A "bso" (ie, record with an "envelope" and a "payload" with a JSON string) - but the payload
+/// is cleartext.
+/// ```json
+/// {
+///   "id":"global",
+///   "modified":1661564513.50,
+///   "payload": "{\"syncID\":\"p1z5_oDdOfLF\",\"storageVersion\":5,\"engines\":{\"passwords\":{\"version\":1,\"syncID\":\"6Y6JJkB074cF\"} /* snip */},\"declined\":[]}"
+/// }```
+///
+/// # encrypted bsos:
+/// Encrypted BSOs are still a "bso" (ie, a record with a field names `payload` which is a string)
+/// but the payload is in the form of an EncryptedPayload.
+/// For example, crypto/keys:
+/// ```json
+/// {
+///   "id":"keys",
+///   "modified":1661564513.74,
+///   "payload":"{\"IV\":\"snip-base-64==\",\"hmac\":\"snip-hex\",\"ciphertext\":\"snip-base64==\"}"
+/// }```
+/// (Note that as described above, most code working with bsos *do not* use that `payload`
+/// directly, but instead a decrypted cleartext bso.
+///
+/// Note all collection responses are the same shape as `crypto/keys` - a `payload` field with a
+/// JSON serialized EncryptedPayload, it's just that the final <T> content differs for each
+/// collection (eg, tabs and bookmarks have quite different <T>s JSON-encoded in the
+/// String payload.)
+///
+/// For completeness, some other "non-BSO" records - no "id", "modified" or "payload" fields in
+/// the response, just plain-old clear-text JSON.
+/// # Example
+/// ## `info/collections`
+/// ```json
+/// {
+///   "bookmarks":1661564648.65,
+///   "meta":1661564513.50,
+///   "addons":1661564649.09,
+///   "clients":1661564643.57,
+///   ...
+/// }```
+/// ## `info/configuration`
+/// ```json
+/// {
+///   "max_post_bytes":2097152,
+///   "max_post_records":100,
+///   "max_record_payload_bytes":2097152,
+///   ...
+/// }```
+///
+/// Given our definitions above, these are not any kind of "bso", so are
+/// not relevant to this module
+use crate::{Guid, ServerTimestamp};
+use serde::{Deserialize, Serialize};
+
+#[cfg(feature = "crypto")]
+mod crypto;
+#[cfg(feature = "crypto")]
+pub use crypto::{IncomingEncryptedBso, OutgoingEncryptedBso};
+
+mod content;
+
+// A feature for this would be ideal, but (a) the module is small and (b) it
+// doesn't really fit the "features" model for sync15 to have a dev-dependency
+// against itself but with a different feature set.
+pub mod test_utils;
+
+/// An envelope for an incoming item. Envelopes carry all the metadata for
+/// a Sync BSO record (`id`, `modified`, `sortindex`), *but not* the payload
+/// itself.
+#[derive(Debug, Clone, Deserialize)]
+pub struct IncomingEnvelope {
+    /// The ID of the record.
+    pub id: Guid,
+    // If we don't give it a default, a small handful of tests fail.
+    // XXX - we should probably fix the tests and kill this?
+    #[serde(default = "ServerTimestamp::default")]
+    pub modified: ServerTimestamp,
+    pub sortindex: Option<i32>,
+    pub ttl: Option<u32>,
+}
+
+/// An envelope for an outgoing item. This is conceptually identical to
+/// [IncomingEnvelope], but omits fields that are only set by the server,
+/// like `modified`.
+#[derive(Debug, Default, Clone, Serialize)]
+pub struct OutgoingEnvelope {
+    /// The ID of the record.
+    pub id: Guid,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub sortindex: Option<i32>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub ttl: Option<u32>,
+}
+
+/// Allow an outgoing envelope to be constructed with just a guid when default
+/// values for the other fields are OK.
+impl From<Guid> for OutgoingEnvelope {
+    fn from(id: Guid) -> Self {
+        OutgoingEnvelope {
+            id,
+            ..Default::default()
+        }
+    }
+}
+
+/// IncomingBso's can come from:
+/// * Directly from the server (ie, some records aren't encrypted, such as meta/global)
+/// * From environments where the encryption is done externally (eg, Rust syncing in Desktop
+///   Firefox has the encryption/decryption done by Firefox and the cleartext BSOs are passed in.
+/// * Read from the server as an EncryptedBso; see EncryptedBso description above.
+#[derive(Deserialize, Debug)]
+pub struct IncomingBso {
+    #[serde(flatten)]
+    pub envelope: IncomingEnvelope,
+    // payload is public for some edge-cases in some components, but in general,
+    // you should use into_content<> to get a record out of it.
+    pub payload: String,
+}
+
+impl IncomingBso {
+    pub fn new(envelope: IncomingEnvelope, payload: String) -> Self {
+        Self { envelope, payload }
+    }
+}
+
+#[derive(Serialize, Debug)]
+pub struct OutgoingBso {
+    #[serde(flatten)]
+    pub envelope: OutgoingEnvelope,
+    // payload is public for some edge-cases in some components, but in general,
+    // you should use into_content<> to get a record out of it.
+    pub payload: String,
+}
+
+impl OutgoingBso {
+    /// Most consumers will use `self.from_content` and `self.from_content_with_id`
+    /// but this exists for the few consumers for whom that doesn't make sense.
+    pub fn new<T: Serialize>(
+        envelope: OutgoingEnvelope,
+        val: &T,
+    ) -> Result<Self, serde_json::Error> {
+        Ok(Self {
+            envelope,
+            payload: serde_json::to_string(&val)?,
+        })
+    }
+}
+
+/// We also have the concept of "content", which helps work with a `T` which
+/// is represented inside the payload. Real-world examples of a `T` include
+/// Bookmarks or Tabs.
+/// See the content module for the implementations.
+///
+/// So this all flows together in the following way:
+/// * Incoming encrypted data:
+///   EncryptedIncomingBso -> IncomingBso -> [specific engine] -> IncomingContent<T>
+/// * Incoming cleartext data:
+///   IncomingBso -> IncomingContent<T>
+///   (Note that incoming cleartext only happens for a few collections managed by
+///   the sync client and never by specific engines - engine BSOs are always encryted)
+/// * Outgoing encrypted data:
+///   OutgoingBso (created in the engine) -> [this crate] -> EncryptedOutgoingBso
+///  * Outgoing cleartext data: just an OutgoingBso with no conversions needed.
+
+/// [IncomingContent] is the result of converting an [IncomingBso] into
+/// some <T> - it consumes the Bso, so you get the envelope, and the [IncomingKind]
+/// which reflects the state of parsing the json.
+#[derive(Debug)]
+pub struct IncomingContent<T> {
+    pub envelope: IncomingEnvelope,
+    pub kind: IncomingKind<T>,
+}
+
+/// The "kind" of incoming content after deserializing it.
+pub enum IncomingKind<T> {
+    /// A good, live T.
+    Content(T),
+    /// A record that used to be a T but has been replaced with a tombstone.
+    Tombstone,
+    /// Either not JSON, or can't be made into a T.
+    Malformed,
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/sync15/bso/test_utils.rs.html b/book/rust-docs/src/sync15/bso/test_utils.rs.html new file mode 100644 index 0000000000..124f10b0db --- /dev/null +++ b/book/rust-docs/src/sync15/bso/test_utils.rs.html @@ -0,0 +1,123 @@ +test_utils.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Utilities for tests to make IncomingBsos and Content from test data.
+use super::{IncomingBso, IncomingEnvelope, OutgoingBso};
+use crate::{Guid, ServerTimestamp};
+
+/// Tests often want an IncomingBso to test, and the easiest way is often to
+/// create an OutgoingBso convert it back to an incoming.
+impl OutgoingBso {
+    // These functions would ideally consume `self` and avoid the clones, but
+    // this is more convenient for some tests and the extra overhead doesn't
+    // really matter for tests.
+    /// When a test has an [OutgoingBso] and wants it as an [IncomingBso]
+    pub fn to_test_incoming(&self) -> IncomingBso {
+        self.to_test_incoming_ts(ServerTimestamp::default())
+    }
+
+    /// When a test has an [OutgoingBso] and wants it as an [IncomingBso] with a specific timestamp.
+    pub fn to_test_incoming_ts(&self, ts: ServerTimestamp) -> IncomingBso {
+        IncomingBso {
+            envelope: IncomingEnvelope {
+                id: self.envelope.id.clone(),
+                modified: ts,
+                sortindex: self.envelope.sortindex,
+                ttl: self.envelope.ttl,
+            },
+            payload: self.payload.clone(),
+        }
+    }
+
+    /// When a test has an [OutgoingBso] and wants it as an [IncomingBso] with a specific T.
+    pub fn to_test_incoming_t<T: for<'de> serde::Deserialize<'de>>(&self) -> T {
+        self.to_test_incoming().into_content().content().unwrap()
+    }
+}
+
+/// Helpers to create an IncomingBso from some T
+impl IncomingBso {
+    /// When a test has an T and wants it as an [IncomingBso]
+    pub fn from_test_content<T: serde::Serialize>(json: T) -> Self {
+        // Go via an OutgoingBso
+        OutgoingBso::from_content_with_id(json)
+            .unwrap()
+            .to_test_incoming()
+    }
+
+    /// When a test has an T and wants it as an [IncomingBso] with a specific timestamp.
+    pub fn from_test_content_ts<T: serde::Serialize>(json: T, ts: ServerTimestamp) -> Self {
+        // Go via an OutgoingBso
+        OutgoingBso::from_content_with_id(json)
+            .unwrap()
+            .to_test_incoming_ts(ts)
+    }
+
+    /// When a test wants a new incoming tombstone.
+    pub fn new_test_tombstone(guid: Guid) -> Self {
+        OutgoingBso::new_tombstone(guid.into()).to_test_incoming()
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/sync15/client/coll_state.rs.html b/book/rust-docs/src/sync15/client/coll_state.rs.html new file mode 100644 index 0000000000..c5cf74732c --- /dev/null +++ b/book/rust-docs/src/sync15/client/coll_state.rs.html @@ -0,0 +1,723 @@ +coll_state.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use super::request::InfoConfiguration;
+use super::{CollectionKeys, GlobalState};
+use crate::engine::{CollSyncIds, EngineSyncAssociation, SyncEngine};
+use crate::error;
+use crate::KeyBundle;
+use crate::ServerTimestamp;
+
+/// Holds state for a collection necessary to perform a sync of it. Lives for the lifetime
+/// of a single sync.
+#[derive(Debug, Clone)]
+pub struct CollState {
+    // Info about the server configuration/capabilities
+    pub config: InfoConfiguration,
+    // from meta/global, used for XIUS when we POST outgoing record based on this state.
+    pub last_modified: ServerTimestamp,
+    pub key: KeyBundle,
+}
+
+/// This mini state-machine helps build a CollState
+#[derive(Debug)]
+pub enum LocalCollState {
+    /// The state is unknown, with the EngineSyncAssociation the collection
+    /// reports.
+    Unknown { assoc: EngineSyncAssociation },
+
+    /// The engine has been declined. This is a "terminal" state.
+    Declined,
+
+    /// There's no such collection in meta/global. We could possibly update
+    /// meta/global, but currently all known collections are there by default,
+    /// so this is, basically, an error condition.
+    NoSuchCollection,
+
+    /// Either the global or collection sync ID has changed - we will reset the engine.
+    SyncIdChanged { ids: CollSyncIds },
+
+    /// The collection is ready to sync.
+    Ready { coll_state: CollState },
+}
+
+pub struct LocalCollStateMachine<'state> {
+    global_state: &'state GlobalState,
+    root_key: &'state KeyBundle,
+}
+
+impl<'state> LocalCollStateMachine<'state> {
+    fn advance(
+        &self,
+        from: LocalCollState,
+        engine: &dyn SyncEngine,
+    ) -> error::Result<LocalCollState> {
+        let name = &engine.collection_name().to_string();
+        let meta_global = &self.global_state.global;
+        match from {
+            LocalCollState::Unknown { assoc } => {
+                if meta_global.declined.contains(name) {
+                    return Ok(LocalCollState::Declined);
+                }
+                match meta_global.engines.get(name) {
+                    Some(engine_meta) => match assoc {
+                        EngineSyncAssociation::Disconnected => Ok(LocalCollState::SyncIdChanged {
+                            ids: CollSyncIds {
+                                global: meta_global.sync_id.clone(),
+                                coll: engine_meta.sync_id.clone(),
+                            },
+                        }),
+                        EngineSyncAssociation::Connected(ref ids)
+                            if ids.global == meta_global.sync_id
+                                && ids.coll == engine_meta.sync_id =>
+                        {
+                            // We are done - build the CollState
+                            let coll_keys = CollectionKeys::from_encrypted_payload(
+                                self.global_state.keys.clone(),
+                                self.global_state.keys_timestamp,
+                                self.root_key,
+                            )?;
+                            let key = coll_keys.key_for_collection(name).clone();
+                            let name = engine.collection_name();
+                            let config = self.global_state.config.clone();
+                            let last_modified = self
+                                .global_state
+                                .collections
+                                .get(name.as_ref())
+                                .cloned()
+                                .unwrap_or_default();
+                            let coll_state = CollState {
+                                config,
+                                last_modified,
+                                key,
+                            };
+                            Ok(LocalCollState::Ready { coll_state })
+                        }
+                        _ => Ok(LocalCollState::SyncIdChanged {
+                            ids: CollSyncIds {
+                                global: meta_global.sync_id.clone(),
+                                coll: engine_meta.sync_id.clone(),
+                            },
+                        }),
+                    },
+                    None => Ok(LocalCollState::NoSuchCollection),
+                }
+            }
+
+            LocalCollState::Declined => unreachable!("can't advance from declined"),
+
+            LocalCollState::NoSuchCollection => unreachable!("the collection is unknown"),
+
+            LocalCollState::SyncIdChanged { ids } => {
+                let assoc = EngineSyncAssociation::Connected(ids);
+                log::info!("Resetting {} engine", engine.collection_name());
+                engine.reset(&assoc)?;
+                Ok(LocalCollState::Unknown { assoc })
+            }
+
+            LocalCollState::Ready { .. } => unreachable!("can't advance from ready"),
+        }
+    }
+
+    // A little whimsy - a portmanteau of far and fast
+    fn run_and_run_as_farst_as_you_can(
+        &mut self,
+        engine: &dyn SyncEngine,
+    ) -> error::Result<Option<CollState>> {
+        let mut s = LocalCollState::Unknown {
+            assoc: engine.get_sync_assoc()?,
+        };
+        // This is a simple state machine and should never take more than
+        // 10 goes around.
+        let mut count = 0;
+        loop {
+            log::trace!("LocalCollState in {:?}", s);
+            match s {
+                LocalCollState::Ready { coll_state } => return Ok(Some(coll_state)),
+                LocalCollState::Declined | LocalCollState::NoSuchCollection => return Ok(None),
+                _ => {
+                    count += 1;
+                    if count > 10 {
+                        log::warn!("LocalCollStateMachine appears to be looping");
+                        return Ok(None);
+                    }
+                    // should we have better loop detection? Our limit of 10
+                    // goes is probably OK for now, but not really ideal.
+                    s = self.advance(s, engine)?;
+                }
+            };
+        }
+    }
+
+    pub fn get_state(
+        engine: &dyn SyncEngine,
+        global_state: &'state GlobalState,
+        root_key: &'state KeyBundle,
+    ) -> error::Result<Option<CollState>> {
+        let mut gingerbread_man = Self {
+            global_state,
+            root_key,
+        };
+        gingerbread_man.run_and_run_as_farst_as_you_can(engine)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::super::request::{InfoCollections, InfoConfiguration};
+    use super::super::CollectionKeys;
+    use super::*;
+    use crate::bso::{IncomingBso, OutgoingBso};
+    use crate::engine::CollectionRequest;
+    use crate::record_types::{MetaGlobalEngine, MetaGlobalRecord};
+    use crate::{telemetry, CollectionName};
+    use anyhow::Result;
+    use std::cell::{Cell, RefCell};
+    use std::collections::HashMap;
+    use sync_guid::Guid;
+
+    fn get_global_state(root_key: &KeyBundle) -> GlobalState {
+        let keys = CollectionKeys::new_random()
+            .unwrap()
+            .to_encrypted_payload(root_key)
+            .unwrap();
+        GlobalState {
+            config: InfoConfiguration::default(),
+            collections: InfoCollections::new(HashMap::new()),
+            global: MetaGlobalRecord {
+                sync_id: "syncIDAAAAAA".into(),
+                storage_version: 5usize,
+                engines: vec![(
+                    "bookmarks",
+                    MetaGlobalEngine {
+                        version: 1usize,
+                        sync_id: "syncIDBBBBBB".into(),
+                    },
+                )]
+                .into_iter()
+                .map(|(key, value)| (key.to_owned(), value))
+                .collect(),
+                declined: vec![],
+            },
+            global_timestamp: ServerTimestamp::default(),
+            keys,
+            keys_timestamp: ServerTimestamp::default(),
+        }
+    }
+
+    struct TestSyncEngine {
+        collection_name: &'static str,
+        assoc: Cell<EngineSyncAssociation>,
+        num_resets: RefCell<usize>,
+    }
+
+    impl TestSyncEngine {
+        fn new(collection_name: &'static str, assoc: EngineSyncAssociation) -> Self {
+            Self {
+                collection_name,
+                assoc: Cell::new(assoc),
+                num_resets: RefCell::new(0),
+            }
+        }
+        fn get_num_resets(&self) -> usize {
+            *self.num_resets.borrow()
+        }
+    }
+
+    impl SyncEngine for TestSyncEngine {
+        fn collection_name(&self) -> CollectionName {
+            self.collection_name.into()
+        }
+
+        fn stage_incoming(
+            &self,
+            _inbound: Vec<IncomingBso>,
+            _telem: &mut telemetry::Engine,
+        ) -> Result<()> {
+            unreachable!("these tests shouldn't call these");
+        }
+
+        fn apply(
+            &self,
+            _timestamp: ServerTimestamp,
+            _telem: &mut telemetry::Engine,
+        ) -> Result<Vec<OutgoingBso>> {
+            unreachable!("these tests shouldn't call these");
+        }
+
+        fn set_uploaded(&self, _new_timestamp: ServerTimestamp, _ids: Vec<Guid>) -> Result<()> {
+            unreachable!("these tests shouldn't call these");
+        }
+
+        fn sync_finished(&self) -> Result<()> {
+            unreachable!("these tests shouldn't call these");
+        }
+
+        fn get_collection_request(
+            &self,
+            _server_timestamp: ServerTimestamp,
+        ) -> Result<Option<CollectionRequest>> {
+            unreachable!("these tests shouldn't call these");
+        }
+
+        fn get_sync_assoc(&self) -> Result<EngineSyncAssociation> {
+            Ok(self.assoc.replace(EngineSyncAssociation::Disconnected))
+        }
+
+        fn reset(&self, new_assoc: &EngineSyncAssociation) -> Result<()> {
+            self.assoc.replace(new_assoc.clone());
+            *self.num_resets.borrow_mut() += 1;
+            Ok(())
+        }
+
+        fn wipe(&self) -> Result<()> {
+            unreachable!("these tests shouldn't call these");
+        }
+    }
+
+    #[test]
+    fn test_unknown() {
+        let root_key = KeyBundle::new_random().expect("should work");
+        let gs = get_global_state(&root_key);
+        let engine = TestSyncEngine::new("unknown", EngineSyncAssociation::Disconnected);
+        let cs = LocalCollStateMachine::get_state(&engine, &gs, &root_key).expect("should work");
+        assert!(cs.is_none(), "unknown collection name can't sync");
+        assert_eq!(engine.get_num_resets(), 0);
+    }
+
+    #[test]
+    fn test_known_no_state() {
+        let root_key = KeyBundle::new_random().expect("should work");
+        let gs = get_global_state(&root_key);
+        let engine = TestSyncEngine::new("bookmarks", EngineSyncAssociation::Disconnected);
+        let cs = LocalCollStateMachine::get_state(&engine, &gs, &root_key).expect("should work");
+        assert!(cs.is_some(), "collection can sync");
+        assert_eq!(
+            engine.assoc.replace(EngineSyncAssociation::Disconnected),
+            EngineSyncAssociation::Connected(CollSyncIds {
+                global: "syncIDAAAAAA".into(),
+                coll: "syncIDBBBBBB".into(),
+            })
+        );
+        assert_eq!(engine.get_num_resets(), 1);
+    }
+
+    #[test]
+    fn test_known_wrong_state() {
+        let root_key = KeyBundle::new_random().expect("should work");
+        let gs = get_global_state(&root_key);
+        let engine = TestSyncEngine::new(
+            "bookmarks",
+            EngineSyncAssociation::Connected(CollSyncIds {
+                global: "syncIDXXXXXX".into(),
+                coll: "syncIDYYYYYY".into(),
+            }),
+        );
+        let cs = LocalCollStateMachine::get_state(&engine, &gs, &root_key).expect("should work");
+        assert!(cs.is_some(), "collection can sync");
+        assert_eq!(
+            engine.assoc.replace(EngineSyncAssociation::Disconnected),
+            EngineSyncAssociation::Connected(CollSyncIds {
+                global: "syncIDAAAAAA".into(),
+                coll: "syncIDBBBBBB".into(),
+            })
+        );
+        assert_eq!(engine.get_num_resets(), 1);
+    }
+
+    #[test]
+    fn test_known_good_state() {
+        let root_key = KeyBundle::new_random().expect("should work");
+        let gs = get_global_state(&root_key);
+        let engine = TestSyncEngine::new(
+            "bookmarks",
+            EngineSyncAssociation::Connected(CollSyncIds {
+                global: "syncIDAAAAAA".into(),
+                coll: "syncIDBBBBBB".into(),
+            }),
+        );
+        let cs = LocalCollStateMachine::get_state(&engine, &gs, &root_key).expect("should work");
+        assert!(cs.is_some(), "collection can sync");
+        assert_eq!(engine.get_num_resets(), 0);
+    }
+
+    #[test]
+    fn test_declined() {
+        let root_key = KeyBundle::new_random().expect("should work");
+        let mut gs = get_global_state(&root_key);
+        gs.global.declined.push("bookmarks".to_string());
+        let engine = TestSyncEngine::new(
+            "bookmarks",
+            EngineSyncAssociation::Connected(CollSyncIds {
+                global: "syncIDAAAAAA".into(),
+                coll: "syncIDBBBBBB".into(),
+            }),
+        );
+        let cs = LocalCollStateMachine::get_state(&engine, &gs, &root_key).expect("should work");
+        assert!(cs.is_none(), "declined collection can sync");
+        assert_eq!(engine.get_num_resets(), 0);
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/sync15/client/coll_update.rs.html b/book/rust-docs/src/sync15/client/coll_update.rs.html new file mode 100644 index 0000000000..8a76f0d22b --- /dev/null +++ b/book/rust-docs/src/sync15/client/coll_update.rs.html @@ -0,0 +1,243 @@ +coll_update.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use super::{
+    request::{NormalResponseHandler, UploadInfo},
+    CollState, Sync15ClientResponse, Sync15StorageClient,
+};
+use crate::bso::{IncomingBso, OutgoingBso, OutgoingEncryptedBso};
+use crate::engine::CollectionRequest;
+use crate::error::{self, Error, Result};
+use crate::{CollectionName, KeyBundle, ServerTimestamp};
+
+fn encrypt_outgoing(o: Vec<OutgoingBso>, key: &KeyBundle) -> Result<Vec<OutgoingEncryptedBso>> {
+    o.into_iter()
+        .map(|change| change.into_encrypted(key))
+        .collect()
+}
+
+pub fn fetch_incoming(
+    client: &Sync15StorageClient,
+    state: &CollState,
+    collection_request: CollectionRequest,
+) -> Result<Vec<IncomingBso>> {
+    let (records, _timestamp) = match client.get_encrypted_records(collection_request)? {
+        Sync15ClientResponse::Success {
+            record,
+            last_modified,
+            ..
+        } => (record, last_modified),
+        other => return Err(other.create_storage_error()),
+    };
+    let mut result = Vec::with_capacity(records.len());
+    for record in records {
+        // if we see a HMAC error, we've made an explicit decision to
+        // NOT handle it here, but restart the global state machine.
+        // That should cause us to re-read crypto/keys and things should
+        // work (although if for some reason crypto/keys was updated but
+        // not all storage was wiped we are probably screwed.)
+        result.push(record.into_decrypted(&state.key)?);
+    }
+    Ok(result)
+}
+
+pub struct CollectionUpdate<'a> {
+    client: &'a Sync15StorageClient,
+    state: &'a CollState,
+    collection: CollectionName,
+    xius: ServerTimestamp,
+    to_update: Vec<OutgoingEncryptedBso>,
+    fully_atomic: bool,
+}
+
+impl<'a> CollectionUpdate<'a> {
+    pub fn new(
+        client: &'a Sync15StorageClient,
+        state: &'a CollState,
+        collection: CollectionName,
+        xius: ServerTimestamp,
+        records: Vec<OutgoingEncryptedBso>,
+        fully_atomic: bool,
+    ) -> CollectionUpdate<'a> {
+        CollectionUpdate {
+            client,
+            state,
+            collection,
+            xius,
+            to_update: records,
+            fully_atomic,
+        }
+    }
+
+    pub fn new_from_changeset(
+        client: &'a Sync15StorageClient,
+        state: &'a CollState,
+        collection: CollectionName,
+        changeset: Vec<OutgoingBso>,
+        fully_atomic: bool,
+    ) -> Result<CollectionUpdate<'a>> {
+        let to_update = encrypt_outgoing(changeset, &state.key)?;
+        Ok(CollectionUpdate::new(
+            client,
+            state,
+            collection,
+            state.last_modified,
+            to_update,
+            fully_atomic,
+        ))
+    }
+
+    /// Returns a list of the IDs that failed if allowed_dropped_records is true, otherwise
+    /// returns an empty vec.
+    pub fn upload(self) -> error::Result<UploadInfo> {
+        let mut failed = vec![];
+        let mut q = self.client.new_post_queue(
+            &self.collection,
+            &self.state.config,
+            self.xius,
+            NormalResponseHandler::new(!self.fully_atomic),
+        )?;
+
+        for record in self.to_update.into_iter() {
+            let enqueued = q.enqueue(&record)?;
+            if !enqueued && self.fully_atomic {
+                return Err(Error::RecordTooLargeError);
+            }
+        }
+
+        q.flush(true)?;
+        let mut info = q.completed_upload_info();
+        info.failed_ids.append(&mut failed);
+        if self.fully_atomic {
+            assert_eq!(
+                info.failed_ids.len(),
+                0,
+                "Bug: Should have failed by now if we aren't allowing dropped records"
+            );
+        }
+        Ok(info)
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/sync15/client/collection_keys.rs.html b/book/rust-docs/src/sync15/client/collection_keys.rs.html new file mode 100644 index 0000000000..0c4de44aeb --- /dev/null +++ b/book/rust-docs/src/sync15/client/collection_keys.rs.html @@ -0,0 +1,123 @@ +collection_keys.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::error::Result;
+use crate::record_types::CryptoKeysRecord;
+use crate::{EncryptedPayload, KeyBundle, ServerTimestamp};
+use std::collections::HashMap;
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct CollectionKeys {
+    pub timestamp: ServerTimestamp,
+    pub default: KeyBundle,
+    pub collections: HashMap<String, KeyBundle>,
+}
+
+impl CollectionKeys {
+    pub fn new_random() -> Result<CollectionKeys> {
+        let default = KeyBundle::new_random()?;
+        Ok(CollectionKeys {
+            timestamp: ServerTimestamp(0),
+            default,
+            collections: HashMap::new(),
+        })
+    }
+
+    pub fn from_encrypted_payload(
+        record: EncryptedPayload,
+        timestamp: ServerTimestamp,
+        root_key: &KeyBundle,
+    ) -> Result<CollectionKeys> {
+        let keys: CryptoKeysRecord = record.decrypt_into(root_key)?;
+        Ok(CollectionKeys {
+            timestamp,
+            default: KeyBundle::from_base64(&keys.default[0], &keys.default[1])?,
+            collections: keys
+                .collections
+                .into_iter()
+                .map(|kv| Ok((kv.0, KeyBundle::from_base64(&kv.1[0], &kv.1[1])?)))
+                .collect::<Result<HashMap<String, KeyBundle>>>()?,
+        })
+    }
+
+    pub fn to_encrypted_payload(&self, root_key: &KeyBundle) -> Result<EncryptedPayload> {
+        let record = CryptoKeysRecord {
+            id: "keys".into(),
+            collection: "crypto".into(),
+            default: self.default.to_b64_array(),
+            collections: self
+                .collections
+                .iter()
+                .map(|kv| (kv.0.clone(), kv.1.to_b64_array()))
+                .collect(),
+        };
+        EncryptedPayload::from_cleartext_payload(root_key, &record)
+    }
+
+    pub fn key_for_collection<'a>(&'a self, collection: &str) -> &'a KeyBundle {
+        self.collections.get(collection).unwrap_or(&self.default)
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/sync15/client/mod.rs.html b/book/rust-docs/src/sync15/client/mod.rs.html new file mode 100644 index 0000000000..00b6eaeeef --- /dev/null +++ b/book/rust-docs/src/sync15/client/mod.rs.html @@ -0,0 +1,79 @@ +mod.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! A module for everything needed to be a "sync client" - ie, a device which
+//! can perform a full sync of any number of collections, including managing
+//! the server state.
+//!
+//! In general, the client is responsible for all communication with the sync server,
+//! including ensuring the state is correct, and encrypting/decrypting all records
+//! to and from the server. However, the actual syncing of the collections is
+//! delegated to an external [crate::engine](Sync Engine).
+//!
+//! One exception is that the "sync client" owns one sync engine - the
+//! [crate::clients_engine], which is managed internally.
+mod coll_state;
+mod coll_update;
+mod collection_keys;
+mod request;
+mod state;
+mod status;
+mod storage_client;
+mod sync;
+mod sync_multiple;
+mod token;
+mod util;
+
+pub(crate) use coll_state::{CollState, LocalCollStateMachine};
+pub(crate) use coll_update::{fetch_incoming, CollectionUpdate};
+pub(crate) use collection_keys::CollectionKeys;
+pub(crate) use request::InfoConfiguration;
+pub(crate) use state::GlobalState;
+pub use status::{ServiceStatus, SyncResult};
+pub use storage_client::{
+    SetupStorageClient, Sync15ClientResponse, Sync15StorageClient, Sync15StorageClientInit,
+};
+pub use sync_multiple::{
+    sync_multiple, sync_multiple_with_command_processor, MemoryCachedState, SyncRequestInfo,
+};
+
\ No newline at end of file diff --git a/book/rust-docs/src/sync15/client/request.rs.html b/book/rust-docs/src/sync15/client/request.rs.html new file mode 100644 index 0000000000..dd11130855 --- /dev/null +++ b/book/rust-docs/src/sync15/client/request.rs.html @@ -0,0 +1,2399 @@ +request.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
+540
+541
+542
+543
+544
+545
+546
+547
+548
+549
+550
+551
+552
+553
+554
+555
+556
+557
+558
+559
+560
+561
+562
+563
+564
+565
+566
+567
+568
+569
+570
+571
+572
+573
+574
+575
+576
+577
+578
+579
+580
+581
+582
+583
+584
+585
+586
+587
+588
+589
+590
+591
+592
+593
+594
+595
+596
+597
+598
+599
+600
+601
+602
+603
+604
+605
+606
+607
+608
+609
+610
+611
+612
+613
+614
+615
+616
+617
+618
+619
+620
+621
+622
+623
+624
+625
+626
+627
+628
+629
+630
+631
+632
+633
+634
+635
+636
+637
+638
+639
+640
+641
+642
+643
+644
+645
+646
+647
+648
+649
+650
+651
+652
+653
+654
+655
+656
+657
+658
+659
+660
+661
+662
+663
+664
+665
+666
+667
+668
+669
+670
+671
+672
+673
+674
+675
+676
+677
+678
+679
+680
+681
+682
+683
+684
+685
+686
+687
+688
+689
+690
+691
+692
+693
+694
+695
+696
+697
+698
+699
+700
+701
+702
+703
+704
+705
+706
+707
+708
+709
+710
+711
+712
+713
+714
+715
+716
+717
+718
+719
+720
+721
+722
+723
+724
+725
+726
+727
+728
+729
+730
+731
+732
+733
+734
+735
+736
+737
+738
+739
+740
+741
+742
+743
+744
+745
+746
+747
+748
+749
+750
+751
+752
+753
+754
+755
+756
+757
+758
+759
+760
+761
+762
+763
+764
+765
+766
+767
+768
+769
+770
+771
+772
+773
+774
+775
+776
+777
+778
+779
+780
+781
+782
+783
+784
+785
+786
+787
+788
+789
+790
+791
+792
+793
+794
+795
+796
+797
+798
+799
+800
+801
+802
+803
+804
+805
+806
+807
+808
+809
+810
+811
+812
+813
+814
+815
+816
+817
+818
+819
+820
+821
+822
+823
+824
+825
+826
+827
+828
+829
+830
+831
+832
+833
+834
+835
+836
+837
+838
+839
+840
+841
+842
+843
+844
+845
+846
+847
+848
+849
+850
+851
+852
+853
+854
+855
+856
+857
+858
+859
+860
+861
+862
+863
+864
+865
+866
+867
+868
+869
+870
+871
+872
+873
+874
+875
+876
+877
+878
+879
+880
+881
+882
+883
+884
+885
+886
+887
+888
+889
+890
+891
+892
+893
+894
+895
+896
+897
+898
+899
+900
+901
+902
+903
+904
+905
+906
+907
+908
+909
+910
+911
+912
+913
+914
+915
+916
+917
+918
+919
+920
+921
+922
+923
+924
+925
+926
+927
+928
+929
+930
+931
+932
+933
+934
+935
+936
+937
+938
+939
+940
+941
+942
+943
+944
+945
+946
+947
+948
+949
+950
+951
+952
+953
+954
+955
+956
+957
+958
+959
+960
+961
+962
+963
+964
+965
+966
+967
+968
+969
+970
+971
+972
+973
+974
+975
+976
+977
+978
+979
+980
+981
+982
+983
+984
+985
+986
+987
+988
+989
+990
+991
+992
+993
+994
+995
+996
+997
+998
+999
+1000
+1001
+1002
+1003
+1004
+1005
+1006
+1007
+1008
+1009
+1010
+1011
+1012
+1013
+1014
+1015
+1016
+1017
+1018
+1019
+1020
+1021
+1022
+1023
+1024
+1025
+1026
+1027
+1028
+1029
+1030
+1031
+1032
+1033
+1034
+1035
+1036
+1037
+1038
+1039
+1040
+1041
+1042
+1043
+1044
+1045
+1046
+1047
+1048
+1049
+1050
+1051
+1052
+1053
+1054
+1055
+1056
+1057
+1058
+1059
+1060
+1061
+1062
+1063
+1064
+1065
+1066
+1067
+1068
+1069
+1070
+1071
+1072
+1073
+1074
+1075
+1076
+1077
+1078
+1079
+1080
+1081
+1082
+1083
+1084
+1085
+1086
+1087
+1088
+1089
+1090
+1091
+1092
+1093
+1094
+1095
+1096
+1097
+1098
+1099
+1100
+1101
+1102
+1103
+1104
+1105
+1106
+1107
+1108
+1109
+1110
+1111
+1112
+1113
+1114
+1115
+1116
+1117
+1118
+1119
+1120
+1121
+1122
+1123
+1124
+1125
+1126
+1127
+1128
+1129
+1130
+1131
+1132
+1133
+1134
+1135
+1136
+1137
+1138
+1139
+1140
+1141
+1142
+1143
+1144
+1145
+1146
+1147
+1148
+1149
+1150
+1151
+1152
+1153
+1154
+1155
+1156
+1157
+1158
+1159
+1160
+1161
+1162
+1163
+1164
+1165
+1166
+1167
+1168
+1169
+1170
+1171
+1172
+1173
+1174
+1175
+1176
+1177
+1178
+1179
+1180
+1181
+1182
+1183
+1184
+1185
+1186
+1187
+1188
+1189
+1190
+1191
+1192
+1193
+1194
+1195
+1196
+1197
+1198
+1199
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use super::storage_client::Sync15ClientResponse;
+use crate::bso::OutgoingEncryptedBso;
+use crate::error::{self, Error as ErrorKind, Result};
+use crate::ServerTimestamp;
+use serde_derive::*;
+use std::collections::HashMap;
+use std::default::Default;
+use std::ops::Deref;
+use sync_guid::Guid;
+use viaduct::status_codes;
+
+/// Manages a pair of (byte, count) limits for a PostQueue, such as
+/// (max_post_bytes, max_post_records) or (max_total_bytes, max_total_records).
+#[derive(Debug, Clone)]
+struct LimitTracker {
+    max_bytes: usize,
+    max_records: usize,
+    cur_bytes: usize,
+    cur_records: usize,
+}
+
+impl LimitTracker {
+    pub fn new(max_bytes: usize, max_records: usize) -> LimitTracker {
+        LimitTracker {
+            max_bytes,
+            max_records,
+            cur_bytes: 0,
+            cur_records: 0,
+        }
+    }
+
+    pub fn clear(&mut self) {
+        self.cur_records = 0;
+        self.cur_bytes = 0;
+    }
+
+    pub fn can_add_record(&self, payload_size: usize) -> bool {
+        // Desktop does the cur_bytes check as exclusive, but we shouldn't see any servers that
+        // don't have https://github.com/mozilla-services/server-syncstorage/issues/73
+        self.cur_records < self.max_records && self.cur_bytes + payload_size <= self.max_bytes
+    }
+
+    pub fn can_never_add(&self, record_size: usize) -> bool {
+        record_size >= self.max_bytes
+    }
+
+    pub fn record_added(&mut self, record_size: usize) {
+        assert!(
+            self.can_add_record(record_size),
+            "LimitTracker::record_added caller must check can_add_record"
+        );
+        self.cur_records += 1;
+        self.cur_bytes += record_size;
+    }
+}
+
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub struct InfoConfiguration {
+    /// The maximum size in bytes of the overall HTTP request body that will be accepted by the
+    /// server.
+    #[serde(default = "default_max_request_bytes")]
+    pub max_request_bytes: usize,
+
+    /// The maximum number of records that can be uploaded to a collection in a single POST request.
+    #[serde(default = "usize::max_value")]
+    pub max_post_records: usize,
+
+    /// The maximum combined size in bytes of the record payloads that can be uploaded to a
+    /// collection in a single POST request.
+    #[serde(default = "usize::max_value")]
+    pub max_post_bytes: usize,
+
+    /// The maximum total number of records that can be uploaded to a collection as part of a
+    /// batched upload.
+    #[serde(default = "usize::max_value")]
+    pub max_total_records: usize,
+
+    /// The maximum total combined size in bytes of the record payloads that can be uploaded to a
+    /// collection as part of a batched upload.
+    #[serde(default = "usize::max_value")]
+    pub max_total_bytes: usize,
+
+    /// The maximum size of an individual BSO payload, in bytes.
+    #[serde(default = "default_max_record_payload_bytes")]
+    pub max_record_payload_bytes: usize,
+}
+
+// This is annoying but seems to be the only way to do it...
+fn default_max_request_bytes() -> usize {
+    260 * 1024
+}
+fn default_max_record_payload_bytes() -> usize {
+    256 * 1024
+}
+
+impl Default for InfoConfiguration {
+    #[inline]
+    fn default() -> InfoConfiguration {
+        InfoConfiguration {
+            max_request_bytes: default_max_request_bytes(),
+            max_record_payload_bytes: default_max_record_payload_bytes(),
+            max_post_records: usize::max_value(),
+            max_post_bytes: usize::max_value(),
+            max_total_records: usize::max_value(),
+            max_total_bytes: usize::max_value(),
+        }
+    }
+}
+
+#[derive(Clone, Debug, Default, Deserialize, Serialize)]
+pub struct InfoCollections(pub(crate) HashMap<String, ServerTimestamp>);
+
+impl InfoCollections {
+    pub fn new(collections: HashMap<String, ServerTimestamp>) -> InfoCollections {
+        InfoCollections(collections)
+    }
+}
+
+impl Deref for InfoCollections {
+    type Target = HashMap<String, ServerTimestamp>;
+
+    fn deref(&self) -> &HashMap<String, ServerTimestamp> {
+        &self.0
+    }
+}
+
+#[derive(Debug, Clone, Deserialize)]
+pub struct UploadResult {
+    batch: Option<String>,
+    /// Maps record id => why failed
+    #[serde(default = "HashMap::new")]
+    pub failed: HashMap<Guid, String>,
+    /// Vec of ids
+    #[serde(default = "Vec::new")]
+    pub success: Vec<Guid>,
+}
+
+pub type PostResponse = Sync15ClientResponse<UploadResult>;
+
+#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
+pub enum BatchState {
+    Unsupported,
+    NoBatch,
+    InBatch(String),
+}
+
+#[derive(Debug)]
+pub struct PostQueue<Post, OnResponse> {
+    poster: Post,
+    on_response: OnResponse,
+    post_limits: LimitTracker,
+    batch_limits: LimitTracker,
+    max_payload_bytes: usize,
+    max_request_bytes: usize,
+    queued: Vec<u8>,
+    batch: BatchState,
+    last_modified: ServerTimestamp,
+}
+
+pub trait BatchPoster {
+    /// Note: Last argument (reference to the batch poster) is provided for the purposes of testing
+    /// Important: Poster should not report non-success HTTP statuses as errors!!
+    fn post<P, O>(
+        &self,
+        body: Vec<u8>,
+        xius: ServerTimestamp,
+        batch: Option<String>,
+        commit: bool,
+        queue: &PostQueue<P, O>,
+    ) -> Result<PostResponse>;
+}
+
+// We don't just use a FnMut here since we want to override it in mocking for RefCell<TestType>,
+// which we can't do for FnMut since neither FnMut nor RefCell are defined here. Also, this
+// is somewhat better for documentation.
+pub trait PostResponseHandler {
+    fn handle_response(&mut self, r: PostResponse, mid_batch: bool) -> Result<()>;
+}
+
+#[derive(Debug, Clone)]
+pub(crate) struct NormalResponseHandler {
+    pub failed_ids: Vec<Guid>,
+    pub successful_ids: Vec<Guid>,
+    pub allow_failed: bool,
+    pub pending_failed: Vec<Guid>,
+    pub pending_success: Vec<Guid>,
+}
+
+impl NormalResponseHandler {
+    pub fn new(allow_failed: bool) -> NormalResponseHandler {
+        NormalResponseHandler {
+            failed_ids: vec![],
+            successful_ids: vec![],
+            pending_failed: vec![],
+            pending_success: vec![],
+            allow_failed,
+        }
+    }
+}
+
+impl PostResponseHandler for NormalResponseHandler {
+    fn handle_response(&mut self, r: PostResponse, mid_batch: bool) -> error::Result<()> {
+        match r {
+            Sync15ClientResponse::Success { record, .. } => {
+                if !record.failed.is_empty() && !self.allow_failed {
+                    return Err(ErrorKind::RecordUploadFailed);
+                }
+                for id in record.success.iter() {
+                    self.pending_success.push(id.clone());
+                }
+                for kv in record.failed.iter() {
+                    self.pending_failed.push(kv.0.clone());
+                }
+                if !mid_batch {
+                    self.successful_ids.append(&mut self.pending_success);
+                    self.failed_ids.append(&mut self.pending_failed);
+                }
+                Ok(())
+            }
+            _ => Err(r.create_storage_error()),
+        }
+    }
+}
+
+impl<Poster, OnResponse> PostQueue<Poster, OnResponse>
+where
+    Poster: BatchPoster,
+    OnResponse: PostResponseHandler,
+{
+    pub fn new(
+        config: &InfoConfiguration,
+        ts: ServerTimestamp,
+        poster: Poster,
+        on_response: OnResponse,
+    ) -> PostQueue<Poster, OnResponse> {
+        PostQueue {
+            poster,
+            on_response,
+            last_modified: ts,
+            post_limits: LimitTracker::new(config.max_post_bytes, config.max_post_records),
+            batch_limits: LimitTracker::new(config.max_total_bytes, config.max_total_records),
+            batch: BatchState::NoBatch,
+            max_payload_bytes: config.max_record_payload_bytes,
+            max_request_bytes: config.max_request_bytes,
+            queued: Vec::new(),
+        }
+    }
+
+    #[inline]
+    fn in_batch(&self) -> bool {
+        !matches!(&self.batch, BatchState::Unsupported | BatchState::NoBatch)
+    }
+
+    pub fn enqueue(&mut self, record: &OutgoingEncryptedBso) -> Result<bool> {
+        let payload_length = record.serialized_payload_len();
+
+        if self.post_limits.can_never_add(payload_length)
+            || self.batch_limits.can_never_add(payload_length)
+            || payload_length >= self.max_payload_bytes
+        {
+            log::warn!(
+                "Single record too large to submit to server ({} b)",
+                payload_length
+            );
+            return Ok(false);
+        }
+
+        // Write directly into `queued` but undo if necessary (the vast majority of the time
+        // it won't be necessary). If we hit a problem we need to undo that, but the only error
+        // case we have to worry about right now is in flush()
+        let item_start = self.queued.len();
+
+        // This is conservative but can't hurt.
+        self.queued.reserve(payload_length + 2);
+
+        // Either the first character in an array, or a comma separating
+        // it from the previous item.
+        let c = if self.queued.is_empty() { b'[' } else { b',' };
+        self.queued.push(c);
+
+        // This unwrap is fine, since serde_json's failure case is HashMaps that have non-object
+        // keys, which is impossible. If you decide to change this part, you *need* to call
+        // `self.queued.truncate(item_start)` here in the failure case!
+        serde_json::to_writer(&mut self.queued, &record).unwrap();
+
+        let item_end = self.queued.len();
+
+        debug_assert!(
+            item_end >= payload_length,
+            "EncryptedPayload::serialized_len is bugged"
+        );
+
+        // The + 1 is only relevant for the final record, which will have a trailing ']'.
+        let item_len = item_end - item_start + 1;
+
+        if item_len >= self.max_request_bytes {
+            self.queued.truncate(item_start);
+            log::warn!(
+                "Single record too large to submit to server ({} b)",
+                item_len
+            );
+            return Ok(false);
+        }
+
+        let can_post_record = self.post_limits.can_add_record(payload_length);
+        let can_batch_record = self.batch_limits.can_add_record(payload_length);
+        let can_send_record = self.queued.len() < self.max_request_bytes;
+
+        if !can_post_record || !can_send_record || !can_batch_record {
+            log::debug!(
+                "PostQueue flushing! (can_post = {}, can_send = {}, can_batch = {})",
+                can_post_record,
+                can_send_record,
+                can_batch_record
+            );
+            // "unwrite" the record.
+            self.queued.truncate(item_start);
+            // Flush whatever we have queued.
+            self.flush(!can_batch_record)?;
+            // And write it again.
+            let c = if self.queued.is_empty() { b'[' } else { b',' };
+            self.queued.push(c);
+            serde_json::to_writer(&mut self.queued, &record).unwrap();
+        }
+
+        self.post_limits.record_added(payload_length);
+        self.batch_limits.record_added(payload_length);
+
+        Ok(true)
+    }
+
+    pub fn flush(&mut self, want_commit: bool) -> Result<()> {
+        if self.queued.is_empty() {
+            assert!(
+                !self.in_batch(),
+                "Bug: Somehow we're in a batch but have no queued records"
+            );
+            // Nothing to do!
+            return Ok(());
+        }
+
+        self.queued.push(b']');
+        let batch_id = match &self.batch {
+            // Not the first post and we know we have no batch semantics.
+            BatchState::Unsupported => None,
+            // First commit in possible batch
+            BatchState::NoBatch => Some("true".into()),
+            // In a batch and we have a batch id.
+            BatchState::InBatch(ref s) => Some(s.clone()),
+        };
+
+        log::info!(
+            "Posting {} records of {} bytes",
+            self.post_limits.cur_records,
+            self.queued.len()
+        );
+
+        let is_commit = want_commit && batch_id.is_some();
+        // Weird syntax for calling a function object that is a property.
+        let resp_or_error = self.poster.post(
+            self.queued.clone(),
+            self.last_modified,
+            batch_id,
+            is_commit,
+            self,
+        );
+
+        self.queued.truncate(0);
+
+        if want_commit || self.batch == BatchState::Unsupported {
+            self.batch_limits.clear();
+        }
+        self.post_limits.clear();
+
+        let resp = resp_or_error?;
+
+        let (status, last_modified, record) = match resp {
+            Sync15ClientResponse::Success {
+                status,
+                last_modified,
+                ref record,
+                ..
+            } => (status, last_modified, record),
+            _ => {
+                self.on_response.handle_response(resp, !want_commit)?;
+                // on_response() should always fail!
+                unreachable!();
+            }
+        };
+
+        if want_commit || self.batch == BatchState::Unsupported {
+            self.last_modified = last_modified;
+        }
+
+        if want_commit {
+            log::debug!("Committed batch {:?}", self.batch);
+            self.batch = BatchState::NoBatch;
+            self.on_response.handle_response(resp, false)?;
+            return Ok(());
+        }
+
+        if status != status_codes::ACCEPTED {
+            if self.in_batch() {
+                return Err(ErrorKind::ServerBatchProblem(
+                    "Server responded non-202 success code while a batch was in progress",
+                ));
+            }
+            self.last_modified = last_modified;
+            self.batch = BatchState::Unsupported;
+            self.batch_limits.clear();
+            self.on_response.handle_response(resp, false)?;
+            return Ok(());
+        }
+
+        let batch_id = record
+            .batch
+            .as_ref()
+            .ok_or({
+                ErrorKind::ServerBatchProblem("Invalid server response: 202 without a batch ID")
+            })?
+            .clone();
+
+        match &self.batch {
+            BatchState::Unsupported => {
+                log::warn!("Server changed its mind about supporting batching mid-batch...");
+            }
+
+            BatchState::InBatch(ref cur_id) => {
+                if cur_id != &batch_id {
+                    return Err(ErrorKind::ServerBatchProblem(
+                        "Invalid server response: 202 without a batch ID",
+                    ));
+                }
+            }
+            _ => {}
+        }
+
+        // Can't change this in match arms without NLL
+        self.batch = BatchState::InBatch(batch_id);
+        self.last_modified = last_modified;
+
+        self.on_response.handle_response(resp, true)?;
+
+        Ok(())
+    }
+}
+
+#[derive(Clone)]
+pub struct UploadInfo {
+    pub successful_ids: Vec<Guid>,
+    pub failed_ids: Vec<Guid>,
+    pub modified_timestamp: ServerTimestamp,
+}
+
+impl<Poster> PostQueue<Poster, NormalResponseHandler> {
+    // TODO: should take by move
+    pub fn completed_upload_info(&mut self) -> UploadInfo {
+        let mut result = UploadInfo {
+            successful_ids: Vec::with_capacity(self.on_response.successful_ids.len()),
+            failed_ids: Vec::with_capacity(
+                self.on_response.failed_ids.len()
+                    + self.on_response.pending_failed.len()
+                    + self.on_response.pending_success.len(),
+            ),
+            modified_timestamp: self.last_modified,
+        };
+
+        result
+            .successful_ids
+            .append(&mut self.on_response.successful_ids);
+
+        result.failed_ids.append(&mut self.on_response.failed_ids);
+        result
+            .failed_ids
+            .append(&mut self.on_response.pending_failed);
+        result
+            .failed_ids
+            .append(&mut self.on_response.pending_success);
+
+        result
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+    use crate::bso::{IncomingEncryptedBso, OutgoingEncryptedBso, OutgoingEnvelope};
+    use crate::EncryptedPayload;
+    use lazy_static::lazy_static;
+    use std::cell::RefCell;
+    use std::collections::VecDeque;
+    use std::rc::Rc;
+
+    #[derive(Debug, Clone)]
+    struct PostedData {
+        body: String,
+        _xius: ServerTimestamp,
+        batch: Option<String>,
+        commit: bool,
+        payload_bytes: usize,
+        records: usize,
+    }
+
+    impl PostedData {
+        fn records_as_json(&self) -> Vec<serde_json::Value> {
+            let values =
+                serde_json::from_str::<serde_json::Value>(&self.body).expect("Posted invalid json");
+            // Check that they actually deserialize as what we want
+            let records_or_err =
+                serde_json::from_value::<Vec<IncomingEncryptedBso>>(values.clone());
+            records_or_err.expect("Failed to deserialize data");
+            serde_json::from_value(values).unwrap()
+        }
+    }
+
+    #[derive(Debug, Clone)]
+    struct BatchInfo {
+        id: Option<String>,
+        posts: Vec<PostedData>,
+        bytes: usize,
+        records: usize,
+    }
+
+    #[derive(Debug, Clone)]
+    struct TestPoster {
+        all_posts: Vec<PostedData>,
+        responses: VecDeque<PostResponse>,
+        batches: Vec<BatchInfo>,
+        cur_batch: Option<BatchInfo>,
+        cfg: InfoConfiguration,
+    }
+
+    type TestPosterRef = Rc<RefCell<TestPoster>>;
+    impl TestPoster {
+        pub fn new<T>(cfg: &InfoConfiguration, responses: T) -> TestPosterRef
+        where
+            T: Into<VecDeque<PostResponse>>,
+        {
+            Rc::new(RefCell::new(TestPoster {
+                all_posts: vec![],
+                responses: responses.into(),
+                batches: vec![],
+                cur_batch: None,
+                cfg: cfg.clone(),
+            }))
+        }
+        // Adds &mut
+        fn do_post<T, O>(
+            &mut self,
+            body: &[u8],
+            xius: ServerTimestamp,
+            batch: Option<String>,
+            commit: bool,
+            queue: &PostQueue<T, O>,
+        ) -> Sync15ClientResponse<UploadResult> {
+            let mut post = PostedData {
+                body: String::from_utf8(body.into()).expect("Posted invalid utf8..."),
+                batch: batch.clone(),
+                _xius: xius,
+                commit,
+                payload_bytes: 0,
+                records: 0,
+            };
+
+            assert!(body.len() <= self.cfg.max_request_bytes);
+
+            let (num_records, record_payload_bytes) = {
+                let recs = post.records_as_json();
+                assert!(recs.len() <= self.cfg.max_post_records);
+                assert!(recs.len() <= self.cfg.max_total_records);
+                let payload_bytes: usize = recs
+                    .iter()
+                    .map(|r| {
+                        let len = r["payload"]
+                            .as_str()
+                            .expect("Non string payload property")
+                            .len();
+                        assert!(len <= self.cfg.max_record_payload_bytes);
+                        len
+                    })
+                    .sum();
+                assert!(payload_bytes <= self.cfg.max_post_bytes);
+                assert!(payload_bytes <= self.cfg.max_total_bytes);
+
+                assert_eq!(queue.post_limits.cur_bytes, payload_bytes);
+                assert_eq!(queue.post_limits.cur_records, recs.len());
+                (recs.len(), payload_bytes)
+            };
+            post.payload_bytes = record_payload_bytes;
+            post.records = num_records;
+
+            self.all_posts.push(post.clone());
+            let response = self.responses.pop_front().unwrap();
+
+            let record = match response {
+                Sync15ClientResponse::Success { ref record, .. } => record,
+                _ => {
+                    panic!("only success codes are used in this test");
+                }
+            };
+
+            if self.cur_batch.is_none() {
+                assert!(
+                    batch.is_none() || batch == Some("true".into()),
+                    "We shouldn't be in a batch now"
+                );
+                self.cur_batch = Some(BatchInfo {
+                    id: record.batch.clone(),
+                    posts: vec![],
+                    records: 0,
+                    bytes: 0,
+                });
+            } else {
+                assert_eq!(
+                    batch,
+                    self.cur_batch.as_ref().unwrap().id,
+                    "We're in a batch but got the wrong batch id"
+                );
+            }
+
+            {
+                let batch = self.cur_batch.as_mut().unwrap();
+                batch.posts.push(post);
+                batch.records += num_records;
+                batch.bytes += record_payload_bytes;
+
+                assert!(batch.bytes <= self.cfg.max_total_bytes);
+                assert!(batch.records <= self.cfg.max_total_records);
+
+                assert_eq!(batch.records, queue.batch_limits.cur_records);
+                assert_eq!(batch.bytes, queue.batch_limits.cur_bytes);
+            }
+
+            if commit || record.batch.is_none() {
+                let batch = self.cur_batch.take().unwrap();
+                self.batches.push(batch);
+            }
+
+            response
+        }
+
+        fn do_handle_response(&mut self, _: PostResponse, mid_batch: bool) {
+            assert_eq!(mid_batch, self.cur_batch.is_some());
+        }
+    }
+    impl BatchPoster for TestPosterRef {
+        fn post<T, O>(
+            &self,
+            body: Vec<u8>,
+            xius: ServerTimestamp,
+            batch: Option<String>,
+            commit: bool,
+            queue: &PostQueue<T, O>,
+        ) -> Result<PostResponse> {
+            Ok(self.borrow_mut().do_post(&body, xius, batch, commit, queue))
+        }
+    }
+
+    impl PostResponseHandler for TestPosterRef {
+        fn handle_response(&mut self, r: PostResponse, mid_batch: bool) -> Result<()> {
+            self.borrow_mut().do_handle_response(r, mid_batch);
+            Ok(())
+        }
+    }
+
+    type MockedPostQueue = PostQueue<TestPosterRef, TestPosterRef>;
+
+    fn pq_test_setup(
+        cfg: InfoConfiguration,
+        lm: i64,
+        resps: Vec<PostResponse>,
+    ) -> (MockedPostQueue, TestPosterRef) {
+        let tester = TestPoster::new(&cfg, resps);
+        let pq = PostQueue::new(&cfg, ServerTimestamp(lm), tester.clone(), tester.clone());
+        (pq, tester)
+    }
+
+    fn fake_response<'a, T: Into<Option<&'a str>>>(status: u16, lm: i64, batch: T) -> PostResponse {
+        assert!(status_codes::is_success_code(status));
+        Sync15ClientResponse::Success {
+            status,
+            last_modified: ServerTimestamp(lm),
+            record: UploadResult {
+                batch: batch.into().map(Into::into),
+                failed: HashMap::new(),
+                success: vec![],
+            },
+            route: "test/path".into(),
+        }
+    }
+
+    lazy_static! {
+        // ~40b
+        static ref PAYLOAD_OVERHEAD: usize = {
+            let payload = EncryptedPayload {
+                iv: "".into(),
+                hmac: "".into(),
+                ciphertext: "".into()
+            };
+            serde_json::to_string(&payload).unwrap().len()
+        };
+        // ~80b
+        static ref TOTAL_RECORD_OVERHEAD: usize = {
+            let val = serde_json::to_value(OutgoingEncryptedBso::new(OutgoingEnvelope {
+                    id: "".into(),
+                    sortindex: None,
+                    ttl: None,
+                },
+                EncryptedPayload {
+                    iv: "".into(),
+                    hmac: "".into(),
+                    ciphertext: "".into()
+                },
+            )).unwrap();
+            serde_json::to_string(&val).unwrap().len()
+        };
+        // There's some subtlety in how we calulate this having to do with the fact that
+        // the quotes in the payload are escaped but the escape chars count to the request len
+        // and *not* to the payload len (the payload len check happens after json parsing the
+        // top level object).
+        static ref NON_PAYLOAD_OVERHEAD: usize = {
+            *TOTAL_RECORD_OVERHEAD - *PAYLOAD_OVERHEAD
+        };
+    }
+
+    // Actual record size (for max_request_len) will be larger by some amount
+    fn make_record(payload_size: usize) -> OutgoingEncryptedBso {
+        assert!(payload_size > *PAYLOAD_OVERHEAD);
+        let ciphertext_len = payload_size - *PAYLOAD_OVERHEAD;
+        OutgoingEncryptedBso::new(
+            OutgoingEnvelope {
+                id: "".into(),
+                sortindex: None,
+                ttl: None,
+            },
+            EncryptedPayload {
+                iv: "".into(),
+                hmac: "".into(),
+                ciphertext: "x".repeat(ciphertext_len),
+            },
+        )
+    }
+
+    fn request_bytes_for_payloads(payloads: &[usize]) -> usize {
+        1 + payloads
+            .iter()
+            .map(|&size| size + 1 + *NON_PAYLOAD_OVERHEAD)
+            .sum::<usize>()
+    }
+
+    #[test]
+    fn test_pq_basic() {
+        let cfg = InfoConfiguration {
+            max_request_bytes: 1000,
+            max_record_payload_bytes: 1000,
+            ..InfoConfiguration::default()
+        };
+        let time = 11_111_111_000;
+        let (mut pq, tester) = pq_test_setup(
+            cfg,
+            time,
+            vec![fake_response(status_codes::OK, time + 100_000, None)],
+        );
+
+        pq.enqueue(&make_record(100)).unwrap();
+        pq.flush(true).unwrap();
+
+        let t = tester.borrow();
+        assert!(t.cur_batch.is_none());
+        assert_eq!(t.all_posts.len(), 1);
+        assert_eq!(t.batches.len(), 1);
+        assert_eq!(t.batches[0].posts.len(), 1);
+        assert_eq!(t.batches[0].records, 1);
+        assert_eq!(t.batches[0].bytes, 100);
+        assert_eq!(
+            t.batches[0].posts[0].body.len(),
+            request_bytes_for_payloads(&[100])
+        );
+    }
+
+    #[test]
+    fn test_pq_max_request_bytes_no_batch() {
+        let cfg = InfoConfiguration {
+            max_request_bytes: 250,
+            ..InfoConfiguration::default()
+        };
+        let time = 11_111_111_000;
+        let (mut pq, tester) = pq_test_setup(
+            cfg,
+            time,
+            vec![
+                fake_response(status_codes::OK, time + 100_000, None),
+                fake_response(status_codes::OK, time + 200_000, None),
+            ],
+        );
+
+        // Note that the total record overhead is around 85 bytes
+        let payload_size = 100 - *NON_PAYLOAD_OVERHEAD;
+        pq.enqueue(&make_record(payload_size)).unwrap(); // total size == 102; [r]
+        pq.enqueue(&make_record(payload_size)).unwrap(); // total size == 203; [r,r]
+        pq.enqueue(&make_record(payload_size)).unwrap(); // too big, 2nd post.
+        pq.flush(true).unwrap();
+
+        let t = tester.borrow();
+        assert!(t.cur_batch.is_none());
+        assert_eq!(t.all_posts.len(), 2);
+        assert_eq!(t.batches.len(), 2);
+        assert_eq!(t.batches[0].posts.len(), 1);
+        assert_eq!(t.batches[0].records, 2);
+        assert_eq!(t.batches[0].bytes, payload_size * 2);
+        assert_eq!(t.batches[0].posts[0].batch, Some("true".into()));
+        assert_eq!(
+            t.batches[0].posts[0].body.len(),
+            request_bytes_for_payloads(&[payload_size, payload_size])
+        );
+
+        assert_eq!(t.batches[1].posts.len(), 1);
+        assert_eq!(t.batches[1].records, 1);
+        assert_eq!(t.batches[1].bytes, payload_size);
+        // We know at this point that the server does not support batching.
+        assert_eq!(t.batches[1].posts[0].batch, None);
+        assert!(!t.batches[1].posts[0].commit);
+        assert_eq!(
+            t.batches[1].posts[0].body.len(),
+            request_bytes_for_payloads(&[payload_size])
+        );
+    }
+
+    #[test]
+    fn test_pq_max_record_payload_bytes_no_batch() {
+        let cfg = InfoConfiguration {
+            max_record_payload_bytes: 150,
+            max_request_bytes: 350,
+            ..InfoConfiguration::default()
+        };
+        let time = 11_111_111_000;
+        let (mut pq, tester) = pq_test_setup(
+            cfg,
+            time,
+            vec![
+                fake_response(status_codes::OK, time + 100_000, None),
+                fake_response(status_codes::OK, time + 200_000, None),
+            ],
+        );
+
+        // Note that the total record overhead is around 85 bytes
+        let payload_size = 100 - *NON_PAYLOAD_OVERHEAD;
+        pq.enqueue(&make_record(payload_size)).unwrap(); // total size == 102; [r]
+        let enqueued = pq.enqueue(&make_record(151)).unwrap(); // still 102
+        assert!(!enqueued, "Should not have fit");
+        pq.enqueue(&make_record(payload_size)).unwrap();
+        pq.flush(true).unwrap();
+
+        let t = tester.borrow();
+        assert!(t.cur_batch.is_none());
+        assert_eq!(t.all_posts.len(), 1);
+        assert_eq!(t.batches.len(), 1);
+        assert_eq!(t.batches[0].posts.len(), 1);
+        assert_eq!(t.batches[0].records, 2);
+        assert_eq!(t.batches[0].bytes, payload_size * 2);
+        assert_eq!(
+            t.batches[0].posts[0].body.len(),
+            request_bytes_for_payloads(&[payload_size, payload_size])
+        );
+    }
+
+    #[test]
+    fn test_pq_single_batch() {
+        let cfg = InfoConfiguration::default();
+        let time = 11_111_111_000;
+        let (mut pq, tester) = pq_test_setup(
+            cfg,
+            time,
+            vec![fake_response(
+                status_codes::ACCEPTED,
+                time + 100_000,
+                Some("1234"),
+            )],
+        );
+
+        let payload_size = 100 - *NON_PAYLOAD_OVERHEAD;
+        pq.enqueue(&make_record(payload_size)).unwrap();
+        pq.enqueue(&make_record(payload_size)).unwrap();
+        pq.enqueue(&make_record(payload_size)).unwrap();
+        pq.flush(true).unwrap();
+
+        let t = tester.borrow();
+        assert!(t.cur_batch.is_none());
+        assert_eq!(t.all_posts.len(), 1);
+        assert_eq!(t.batches.len(), 1);
+        assert_eq!(t.batches[0].id.as_ref().unwrap(), "1234");
+        assert_eq!(t.batches[0].posts.len(), 1);
+        assert_eq!(t.batches[0].records, 3);
+        assert_eq!(t.batches[0].bytes, payload_size * 3);
+        assert!(t.batches[0].posts[0].commit);
+        assert_eq!(
+            t.batches[0].posts[0].body.len(),
+            request_bytes_for_payloads(&[payload_size, payload_size, payload_size])
+        );
+    }
+
+    #[test]
+    fn test_pq_multi_post_batch_bytes() {
+        let cfg = InfoConfiguration {
+            max_post_bytes: 200,
+            ..InfoConfiguration::default()
+        };
+        let time = 11_111_111_000;
+        let (mut pq, tester) = pq_test_setup(
+            cfg,
+            time,
+            vec![
+                fake_response(status_codes::ACCEPTED, time, Some("1234")),
+                fake_response(status_codes::ACCEPTED, time + 100_000, Some("1234")),
+            ],
+        );
+
+        pq.enqueue(&make_record(100)).unwrap();
+        pq.enqueue(&make_record(100)).unwrap();
+        // POST
+        pq.enqueue(&make_record(100)).unwrap();
+        pq.flush(true).unwrap(); // COMMIT
+
+        let t = tester.borrow();
+        assert!(t.cur_batch.is_none());
+        assert_eq!(t.all_posts.len(), 2);
+        assert_eq!(t.batches.len(), 1);
+        assert_eq!(t.batches[0].posts.len(), 2);
+        assert_eq!(t.batches[0].records, 3);
+        assert_eq!(t.batches[0].bytes, 300);
+
+        assert_eq!(t.batches[0].posts[0].batch.as_ref().unwrap(), "true");
+        assert_eq!(t.batches[0].posts[0].records, 2);
+        assert_eq!(t.batches[0].posts[0].payload_bytes, 200);
+        assert!(!t.batches[0].posts[0].commit);
+        assert_eq!(
+            t.batches[0].posts[0].body.len(),
+            request_bytes_for_payloads(&[100, 100])
+        );
+
+        assert_eq!(t.batches[0].posts[1].batch.as_ref().unwrap(), "1234");
+        assert_eq!(t.batches[0].posts[1].records, 1);
+        assert_eq!(t.batches[0].posts[1].payload_bytes, 100);
+        assert!(t.batches[0].posts[1].commit);
+        assert_eq!(
+            t.batches[0].posts[1].body.len(),
+            request_bytes_for_payloads(&[100])
+        );
+    }
+
+    #[test]
+    fn test_pq_multi_post_batch_records() {
+        let cfg = InfoConfiguration {
+            max_post_records: 3,
+            ..InfoConfiguration::default()
+        };
+        let time = 11_111_111_000;
+        let (mut pq, tester) = pq_test_setup(
+            cfg,
+            time,
+            vec![
+                fake_response(status_codes::ACCEPTED, time, Some("1234")),
+                fake_response(status_codes::ACCEPTED, time, Some("1234")),
+                fake_response(status_codes::ACCEPTED, time + 100_000, Some("1234")),
+            ],
+        );
+
+        pq.enqueue(&make_record(100)).unwrap();
+        pq.enqueue(&make_record(100)).unwrap();
+        pq.enqueue(&make_record(100)).unwrap();
+        // POST
+        pq.enqueue(&make_record(100)).unwrap();
+        pq.enqueue(&make_record(100)).unwrap();
+        pq.enqueue(&make_record(100)).unwrap();
+        // POST
+        pq.enqueue(&make_record(100)).unwrap();
+        pq.flush(true).unwrap(); // COMMIT
+
+        let t = tester.borrow();
+        assert!(t.cur_batch.is_none());
+        assert_eq!(t.all_posts.len(), 3);
+        assert_eq!(t.batches.len(), 1);
+        assert_eq!(t.batches[0].posts.len(), 3);
+        assert_eq!(t.batches[0].records, 7);
+        assert_eq!(t.batches[0].bytes, 700);
+
+        assert_eq!(t.batches[0].posts[0].batch.as_ref().unwrap(), "true");
+        assert_eq!(t.batches[0].posts[0].records, 3);
+        assert_eq!(t.batches[0].posts[0].payload_bytes, 300);
+        assert!(!t.batches[0].posts[0].commit);
+        assert_eq!(
+            t.batches[0].posts[0].body.len(),
+            request_bytes_for_payloads(&[100, 100, 100])
+        );
+
+        assert_eq!(t.batches[0].posts[1].batch.as_ref().unwrap(), "1234");
+        assert_eq!(t.batches[0].posts[1].records, 3);
+        assert_eq!(t.batches[0].posts[1].payload_bytes, 300);
+        assert!(!t.batches[0].posts[1].commit);
+        assert_eq!(
+            t.batches[0].posts[1].body.len(),
+            request_bytes_for_payloads(&[100, 100, 100])
+        );
+
+        assert_eq!(t.batches[0].posts[2].batch.as_ref().unwrap(), "1234");
+        assert_eq!(t.batches[0].posts[2].records, 1);
+        assert_eq!(t.batches[0].posts[2].payload_bytes, 100);
+        assert!(t.batches[0].posts[2].commit);
+        assert_eq!(
+            t.batches[0].posts[2].body.len(),
+            request_bytes_for_payloads(&[100])
+        );
+    }
+
+    #[test]
+    #[allow(clippy::cognitive_complexity)]
+    fn test_pq_multi_post_multi_batch_records() {
+        let cfg = InfoConfiguration {
+            max_post_records: 3,
+            max_total_records: 5,
+            ..InfoConfiguration::default()
+        };
+        let time = 11_111_111_000;
+        let (mut pq, tester) = pq_test_setup(
+            cfg,
+            time,
+            vec![
+                fake_response(status_codes::ACCEPTED, time, Some("1234")),
+                fake_response(status_codes::ACCEPTED, time + 100_000, Some("1234")),
+                fake_response(status_codes::ACCEPTED, time + 100_000, Some("abcd")),
+                fake_response(status_codes::ACCEPTED, time + 200_000, Some("abcd")),
+            ],
+        );
+
+        pq.enqueue(&make_record(100)).unwrap();
+        pq.enqueue(&make_record(100)).unwrap();
+        pq.enqueue(&make_record(100)).unwrap();
+        // POST
+        pq.enqueue(&make_record(100)).unwrap();
+        pq.enqueue(&make_record(100)).unwrap();
+        // POST + COMMIT
+        pq.enqueue(&make_record(100)).unwrap();
+        pq.enqueue(&make_record(100)).unwrap();
+        pq.enqueue(&make_record(100)).unwrap();
+        // POST
+        pq.enqueue(&make_record(100)).unwrap();
+        pq.flush(true).unwrap(); // COMMIT
+
+        let t = tester.borrow();
+        assert!(t.cur_batch.is_none());
+        assert_eq!(t.all_posts.len(), 4);
+        assert_eq!(t.batches.len(), 2);
+        assert_eq!(t.batches[0].posts.len(), 2);
+        assert_eq!(t.batches[1].posts.len(), 2);
+
+        assert_eq!(t.batches[0].records, 5);
+        assert_eq!(t.batches[1].records, 4);
+
+        assert_eq!(t.batches[0].bytes, 500);
+        assert_eq!(t.batches[1].bytes, 400);
+
+        assert_eq!(t.batches[0].posts[0].batch.as_ref().unwrap(), "true");
+        assert_eq!(t.batches[0].posts[0].records, 3);
+        assert_eq!(t.batches[0].posts[0].payload_bytes, 300);
+        assert!(!t.batches[0].posts[0].commit);
+        assert_eq!(
+            t.batches[0].posts[0].body.len(),
+            request_bytes_for_payloads(&[100, 100, 100])
+        );
+
+        assert_eq!(t.batches[0].posts[1].batch.as_ref().unwrap(), "1234");
+        assert_eq!(t.batches[0].posts[1].records, 2);
+        assert_eq!(t.batches[0].posts[1].payload_bytes, 200);
+        assert!(t.batches[0].posts[1].commit);
+        assert_eq!(
+            t.batches[0].posts[1].body.len(),
+            request_bytes_for_payloads(&[100, 100])
+        );
+
+        assert_eq!(t.batches[1].posts[0].batch.as_ref().unwrap(), "true");
+        assert_eq!(t.batches[1].posts[0].records, 3);
+        assert_eq!(t.batches[1].posts[0].payload_bytes, 300);
+        assert!(!t.batches[1].posts[0].commit);
+        assert_eq!(
+            t.batches[1].posts[0].body.len(),
+            request_bytes_for_payloads(&[100, 100, 100])
+        );
+
+        assert_eq!(t.batches[1].posts[1].batch.as_ref().unwrap(), "abcd");
+        assert_eq!(t.batches[1].posts[1].records, 1);
+        assert_eq!(t.batches[1].posts[1].payload_bytes, 100);
+        assert!(t.batches[1].posts[1].commit);
+        assert_eq!(
+            t.batches[1].posts[1].body.len(),
+            request_bytes_for_payloads(&[100])
+        );
+    }
+
+    #[test]
+    #[allow(clippy::cognitive_complexity)]
+    fn test_pq_multi_post_multi_batch_bytes() {
+        let cfg = InfoConfiguration {
+            max_post_bytes: 300,
+            max_total_bytes: 500,
+            ..InfoConfiguration::default()
+        };
+        let time = 11_111_111_000;
+        let (mut pq, tester) = pq_test_setup(
+            cfg,
+            time,
+            vec![
+                fake_response(status_codes::ACCEPTED, time, Some("1234")),
+                fake_response(status_codes::ACCEPTED, time + 100_000, Some("1234")), // should commit
+                fake_response(status_codes::ACCEPTED, time + 100_000, Some("abcd")),
+                fake_response(status_codes::ACCEPTED, time + 200_000, Some("abcd")), // should commit
+            ],
+        );
+
+        pq.enqueue(&make_record(100)).unwrap();
+        pq.enqueue(&make_record(100)).unwrap();
+        pq.enqueue(&make_record(100)).unwrap();
+        assert_eq!(pq.last_modified.0, time);
+        // POST
+        pq.enqueue(&make_record(100)).unwrap();
+        pq.enqueue(&make_record(100)).unwrap();
+        // POST + COMMIT
+        pq.enqueue(&make_record(100)).unwrap();
+        assert_eq!(pq.last_modified.0, time + 100_000);
+        pq.enqueue(&make_record(100)).unwrap();
+        pq.enqueue(&make_record(100)).unwrap();
+
+        // POST
+        pq.enqueue(&make_record(100)).unwrap();
+        assert_eq!(pq.last_modified.0, time + 100_000);
+        pq.flush(true).unwrap(); // COMMIT
+
+        assert_eq!(pq.last_modified.0, time + 200_000);
+
+        let t = tester.borrow();
+        assert!(t.cur_batch.is_none());
+        assert_eq!(t.all_posts.len(), 4);
+        assert_eq!(t.batches.len(), 2);
+        assert_eq!(t.batches[0].posts.len(), 2);
+        assert_eq!(t.batches[1].posts.len(), 2);
+
+        assert_eq!(t.batches[0].records, 5);
+        assert_eq!(t.batches[1].records, 4);
+
+        assert_eq!(t.batches[0].bytes, 500);
+        assert_eq!(t.batches[1].bytes, 400);
+
+        assert_eq!(t.batches[0].posts[0].batch.as_ref().unwrap(), "true");
+        assert_eq!(t.batches[0].posts[0].records, 3);
+        assert_eq!(t.batches[0].posts[0].payload_bytes, 300);
+        assert!(!t.batches[0].posts[0].commit);
+        assert_eq!(
+            t.batches[0].posts[0].body.len(),
+            request_bytes_for_payloads(&[100, 100, 100])
+        );
+
+        assert_eq!(t.batches[0].posts[1].batch.as_ref().unwrap(), "1234");
+        assert_eq!(t.batches[0].posts[1].records, 2);
+        assert_eq!(t.batches[0].posts[1].payload_bytes, 200);
+        assert!(t.batches[0].posts[1].commit);
+        assert_eq!(
+            t.batches[0].posts[1].body.len(),
+            request_bytes_for_payloads(&[100, 100])
+        );
+
+        assert_eq!(t.batches[1].posts[0].batch.as_ref().unwrap(), "true");
+        assert_eq!(t.batches[1].posts[0].records, 3);
+        assert_eq!(t.batches[1].posts[0].payload_bytes, 300);
+        assert!(!t.batches[1].posts[0].commit);
+        assert_eq!(
+            t.batches[1].posts[0].body.len(),
+            request_bytes_for_payloads(&[100, 100, 100])
+        );
+
+        assert_eq!(t.batches[1].posts[1].batch.as_ref().unwrap(), "abcd");
+        assert_eq!(t.batches[1].posts[1].records, 1);
+        assert_eq!(t.batches[1].posts[1].payload_bytes, 100);
+        assert!(t.batches[1].posts[1].commit);
+        assert_eq!(
+            t.batches[1].posts[1].body.len(),
+            request_bytes_for_payloads(&[100])
+        );
+    }
+
+    // TODO: Test
+    //
+    // - error cases!!! We don't test our handling of server errors at all!
+    // - mixed bytes/record limits
+    //
+    // A lot of these have good examples in test_postqueue.js on deskftop sync
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/sync15/client/state.rs.html b/book/rust-docs/src/sync15/client/state.rs.html new file mode 100644 index 0000000000..52faeab8a0 --- /dev/null +++ b/book/rust-docs/src/sync15/client/state.rs.html @@ -0,0 +1,2179 @@ +state.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
+540
+541
+542
+543
+544
+545
+546
+547
+548
+549
+550
+551
+552
+553
+554
+555
+556
+557
+558
+559
+560
+561
+562
+563
+564
+565
+566
+567
+568
+569
+570
+571
+572
+573
+574
+575
+576
+577
+578
+579
+580
+581
+582
+583
+584
+585
+586
+587
+588
+589
+590
+591
+592
+593
+594
+595
+596
+597
+598
+599
+600
+601
+602
+603
+604
+605
+606
+607
+608
+609
+610
+611
+612
+613
+614
+615
+616
+617
+618
+619
+620
+621
+622
+623
+624
+625
+626
+627
+628
+629
+630
+631
+632
+633
+634
+635
+636
+637
+638
+639
+640
+641
+642
+643
+644
+645
+646
+647
+648
+649
+650
+651
+652
+653
+654
+655
+656
+657
+658
+659
+660
+661
+662
+663
+664
+665
+666
+667
+668
+669
+670
+671
+672
+673
+674
+675
+676
+677
+678
+679
+680
+681
+682
+683
+684
+685
+686
+687
+688
+689
+690
+691
+692
+693
+694
+695
+696
+697
+698
+699
+700
+701
+702
+703
+704
+705
+706
+707
+708
+709
+710
+711
+712
+713
+714
+715
+716
+717
+718
+719
+720
+721
+722
+723
+724
+725
+726
+727
+728
+729
+730
+731
+732
+733
+734
+735
+736
+737
+738
+739
+740
+741
+742
+743
+744
+745
+746
+747
+748
+749
+750
+751
+752
+753
+754
+755
+756
+757
+758
+759
+760
+761
+762
+763
+764
+765
+766
+767
+768
+769
+770
+771
+772
+773
+774
+775
+776
+777
+778
+779
+780
+781
+782
+783
+784
+785
+786
+787
+788
+789
+790
+791
+792
+793
+794
+795
+796
+797
+798
+799
+800
+801
+802
+803
+804
+805
+806
+807
+808
+809
+810
+811
+812
+813
+814
+815
+816
+817
+818
+819
+820
+821
+822
+823
+824
+825
+826
+827
+828
+829
+830
+831
+832
+833
+834
+835
+836
+837
+838
+839
+840
+841
+842
+843
+844
+845
+846
+847
+848
+849
+850
+851
+852
+853
+854
+855
+856
+857
+858
+859
+860
+861
+862
+863
+864
+865
+866
+867
+868
+869
+870
+871
+872
+873
+874
+875
+876
+877
+878
+879
+880
+881
+882
+883
+884
+885
+886
+887
+888
+889
+890
+891
+892
+893
+894
+895
+896
+897
+898
+899
+900
+901
+902
+903
+904
+905
+906
+907
+908
+909
+910
+911
+912
+913
+914
+915
+916
+917
+918
+919
+920
+921
+922
+923
+924
+925
+926
+927
+928
+929
+930
+931
+932
+933
+934
+935
+936
+937
+938
+939
+940
+941
+942
+943
+944
+945
+946
+947
+948
+949
+950
+951
+952
+953
+954
+955
+956
+957
+958
+959
+960
+961
+962
+963
+964
+965
+966
+967
+968
+969
+970
+971
+972
+973
+974
+975
+976
+977
+978
+979
+980
+981
+982
+983
+984
+985
+986
+987
+988
+989
+990
+991
+992
+993
+994
+995
+996
+997
+998
+999
+1000
+1001
+1002
+1003
+1004
+1005
+1006
+1007
+1008
+1009
+1010
+1011
+1012
+1013
+1014
+1015
+1016
+1017
+1018
+1019
+1020
+1021
+1022
+1023
+1024
+1025
+1026
+1027
+1028
+1029
+1030
+1031
+1032
+1033
+1034
+1035
+1036
+1037
+1038
+1039
+1040
+1041
+1042
+1043
+1044
+1045
+1046
+1047
+1048
+1049
+1050
+1051
+1052
+1053
+1054
+1055
+1056
+1057
+1058
+1059
+1060
+1061
+1062
+1063
+1064
+1065
+1066
+1067
+1068
+1069
+1070
+1071
+1072
+1073
+1074
+1075
+1076
+1077
+1078
+1079
+1080
+1081
+1082
+1083
+1084
+1085
+1086
+1087
+1088
+1089
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use std::collections::{HashMap, HashSet};
+
+use super::request::{InfoCollections, InfoConfiguration};
+use super::storage_client::{SetupStorageClient, Sync15ClientResponse};
+use super::CollectionKeys;
+use crate::bso::OutgoingEncryptedBso;
+use crate::error::{self, Error as ErrorKind, ErrorResponse};
+use crate::record_types::{MetaGlobalEngine, MetaGlobalRecord};
+use crate::EncryptedPayload;
+use crate::{Guid, KeyBundle, ServerTimestamp};
+use interrupt_support::Interruptee;
+use serde_derive::*;
+
+use self::SetupState::*;
+
+const STORAGE_VERSION: usize = 5;
+
+/// Maps names to storage versions for engines to include in a fresh
+/// `meta/global` record. We include engines that we don't implement
+/// because they'll be disabled on other clients if we omit them
+/// (bug 1479929).
+const DEFAULT_ENGINES: &[(&str, usize)] = &[
+    ("passwords", 1),
+    ("clients", 1),
+    ("addons", 1),
+    ("addresses", 1),
+    ("bookmarks", 2),
+    ("creditcards", 1),
+    ("forms", 1),
+    ("history", 1),
+    ("prefs", 2),
+    ("tabs", 1),
+];
+
+// Declined engines to include in a fresh `meta/global` record.
+const DEFAULT_DECLINED: &[&str] = &[];
+
+/// State that we require the app to persist to storage for us.
+/// It's a little unfortunate we need this, because it's only tracking
+/// "declined engines", and even then, only needed in practice when there's
+/// no meta/global so we need to create one. It's extra unfortunate because we
+/// want to move away from "globally declined" engines anyway, moving towards
+/// allowing engines to be enabled or disabled per client rather than globally.
+///
+/// Apps are expected to treat this as opaque, so we support serializing it.
+/// Note that this structure is *not* used to *change* the declined engines
+/// list - that will be done in the future, but the API exposed for that
+/// purpose will also take a mutable PersistedGlobalState.
+#[derive(Debug, Serialize, Deserialize)]
+#[serde(tag = "schema_version")]
+pub enum PersistedGlobalState {
+    /// V1 was when we persisted the entire GlobalState, keys and all!
+
+    /// V2 is just tracking the globally declined list.
+    /// None means "I've no idea" and theoretically should only happen on the
+    /// very first sync for an app.
+    V2 { declined: Option<Vec<String>> },
+}
+
+impl Default for PersistedGlobalState {
+    #[inline]
+    fn default() -> PersistedGlobalState {
+        PersistedGlobalState::V2 { declined: None }
+    }
+}
+
+#[derive(Debug, Default, Clone, PartialEq)]
+pub(crate) struct EngineChangesNeeded {
+    pub local_resets: HashSet<String>,
+    pub remote_wipes: HashSet<String>,
+}
+
+#[derive(Debug, Default, Clone, PartialEq)]
+struct RemoteEngineState {
+    info_collections: HashSet<String>,
+    declined: HashSet<String>,
+}
+
+#[derive(Debug, Default, Clone, PartialEq)]
+struct EngineStateInput {
+    local_declined: HashSet<String>,
+    remote: Option<RemoteEngineState>,
+    user_changes: HashMap<String, bool>,
+}
+
+#[derive(Debug, Default, Clone, PartialEq)]
+struct EngineStateOutput {
+    // The new declined.
+    declined: HashSet<String>,
+    // Which engines need resets or wipes.
+    changes_needed: EngineChangesNeeded,
+}
+
+fn compute_engine_states(input: EngineStateInput) -> EngineStateOutput {
+    use super::util::*;
+    log::debug!("compute_engine_states: input {:?}", input);
+    let (must_enable, must_disable) = partition_by_value(&input.user_changes);
+    let have_remote = input.remote.is_some();
+    let RemoteEngineState {
+        info_collections,
+        declined: remote_declined,
+    } = input.remote.clone().unwrap_or_default();
+
+    let both_declined_and_remote = set_intersection(&info_collections, &remote_declined);
+    if !both_declined_and_remote.is_empty() {
+        // Should we wipe these too?
+        log::warn!(
+            "Remote state contains engines which are in both info/collections and meta/global's declined: {:?}",
+            both_declined_and_remote,
+        );
+    }
+
+    let most_recent_declined_list = if have_remote {
+        &remote_declined
+    } else {
+        &input.local_declined
+    };
+
+    let result_declined = set_difference(
+        &set_union(most_recent_declined_list, &must_disable),
+        &must_enable,
+    );
+
+    let output = EngineStateOutput {
+        changes_needed: EngineChangesNeeded {
+            // Anything now declined which wasn't in our declined list before gets a reset.
+            local_resets: set_difference(&result_declined, &input.local_declined),
+            // Anything remote that we just declined gets a wipe. In the future
+            // we might want to consider wiping things in both remote declined
+            // and info/collections, but we'll let other clients pick up their
+            // own mess for now.
+            remote_wipes: set_intersection(&info_collections, &must_disable),
+        },
+        declined: result_declined,
+    };
+    // No PII here and this helps debug problems.
+    log::debug!("compute_engine_states: output {:?}", output);
+    output
+}
+
+impl PersistedGlobalState {
+    fn set_declined(&mut self, new_declined: Vec<String>) {
+        match self {
+            Self::V2 { ref mut declined } => *declined = Some(new_declined),
+        }
+    }
+    pub(crate) fn get_declined(&self) -> &[String] {
+        match self {
+            Self::V2 { declined: Some(d) } => d,
+            Self::V2 { declined: None } => &[],
+        }
+    }
+}
+
+/// Holds global Sync state, including server upload limits, the
+/// last-fetched collection modified times, `meta/global` record, and
+/// an encrypted copy of the crypto/keys resource (avoids keeping them
+/// in memory longer than necessary; avoids key mismatches by ensuring the same KeyBundle
+/// is used for both the keys and encrypted payloads.)
+#[derive(Debug, Clone)]
+pub struct GlobalState {
+    pub config: InfoConfiguration,
+    pub collections: InfoCollections,
+    pub global: MetaGlobalRecord,
+    pub global_timestamp: ServerTimestamp,
+    pub keys: EncryptedPayload,
+    pub keys_timestamp: ServerTimestamp,
+}
+
+/// Creates a fresh `meta/global` record, using the default engine selections,
+/// and declined engines from our PersistedGlobalState.
+fn new_global(pgs: &PersistedGlobalState) -> MetaGlobalRecord {
+    let sync_id = Guid::random();
+    let mut engines: HashMap<String, _> = HashMap::new();
+    for (name, version) in DEFAULT_ENGINES.iter() {
+        let sync_id = Guid::random();
+        engines.insert(
+            (*name).to_string(),
+            MetaGlobalEngine {
+                version: *version,
+                sync_id,
+            },
+        );
+    }
+    // We only need our PersistedGlobalState to fill out a new meta/global - if
+    // we previously saw a meta/global then we would have updated it with what
+    // it was at the time.
+    let declined = match pgs {
+        PersistedGlobalState::V2 { declined: Some(d) } => d.clone(),
+        _ => DEFAULT_DECLINED.iter().map(ToString::to_string).collect(),
+    };
+
+    MetaGlobalRecord {
+        sync_id,
+        storage_version: STORAGE_VERSION,
+        engines,
+        declined,
+    }
+}
+
+fn fixup_meta_global(global: &mut MetaGlobalRecord) -> bool {
+    let mut changed_any = false;
+    for &(name, version) in DEFAULT_ENGINES.iter() {
+        let had_engine = global.engines.contains_key(name);
+        let should_have_engine = !global.declined.iter().any(|c| c == name);
+        if had_engine != should_have_engine {
+            if should_have_engine {
+                log::debug!("SyncID for engine {:?} was missing", name);
+                global.engines.insert(
+                    name.to_string(),
+                    MetaGlobalEngine {
+                        version,
+                        sync_id: Guid::random(),
+                    },
+                );
+            } else {
+                log::debug!("SyncID for engine {:?} was present, but shouldn't be", name);
+                global.engines.remove(name);
+            }
+            changed_any = true;
+        }
+    }
+    changed_any
+}
+
+pub struct SetupStateMachine<'a> {
+    client: &'a dyn SetupStorageClient,
+    root_key: &'a KeyBundle,
+    pgs: &'a mut PersistedGlobalState,
+    // `allowed_states` is designed so that we can arrange for the concept of
+    // a "fast" sync - so we decline to advance if we need to setup from scratch.
+    // The idea is that if we need to sync before going to sleep we should do
+    // it as fast as possible. However, in practice this isn't going to do
+    // what we expect - a "fast sync" that finds lots to do is almost certainly
+    // going to take longer than a "full sync" that finds nothing to do.
+    // We should almost certainly remove this and instead allow for a "time
+    // budget", after which we get interrupted. Later...
+    allowed_states: Vec<&'static str>,
+    sequence: Vec<&'static str>,
+    engine_updates: Option<&'a HashMap<String, bool>>,
+    interruptee: &'a dyn Interruptee,
+    pub(crate) changes_needed: Option<EngineChangesNeeded>,
+}
+
+impl<'a> SetupStateMachine<'a> {
+    /// Creates a state machine for a "classic" Sync 1.5 client that supports
+    /// all states, including uploading a fresh `meta/global` and `crypto/keys`
+    /// after a node reassignment.
+    pub fn for_full_sync(
+        client: &'a dyn SetupStorageClient,
+        root_key: &'a KeyBundle,
+        pgs: &'a mut PersistedGlobalState,
+        engine_updates: Option<&'a HashMap<String, bool>>,
+        interruptee: &'a dyn Interruptee,
+    ) -> SetupStateMachine<'a> {
+        SetupStateMachine::with_allowed_states(
+            client,
+            root_key,
+            pgs,
+            interruptee,
+            engine_updates,
+            vec![
+                "Initial",
+                "InitialWithConfig",
+                "InitialWithInfo",
+                "InitialWithMetaGlobal",
+                "Ready",
+                "FreshStartRequired",
+                "WithPreviousState",
+            ],
+        )
+    }
+
+    fn with_allowed_states(
+        client: &'a dyn SetupStorageClient,
+        root_key: &'a KeyBundle,
+        pgs: &'a mut PersistedGlobalState,
+        interruptee: &'a dyn Interruptee,
+        engine_updates: Option<&'a HashMap<String, bool>>,
+        allowed_states: Vec<&'static str>,
+    ) -> SetupStateMachine<'a> {
+        SetupStateMachine {
+            client,
+            root_key,
+            pgs,
+            sequence: Vec::new(),
+            allowed_states,
+            engine_updates,
+            interruptee,
+            changes_needed: None,
+        }
+    }
+
+    fn advance(&mut self, from: SetupState) -> error::Result<SetupState> {
+        match from {
+            // Fetch `info/configuration` with current server limits, and
+            // `info/collections` with collection last modified times.
+            Initial => {
+                let config = match self.client.fetch_info_configuration()? {
+                    Sync15ClientResponse::Success { record, .. } => record,
+                    Sync15ClientResponse::Error(ErrorResponse::NotFound { .. }) => {
+                        InfoConfiguration::default()
+                    }
+                    other => return Err(other.create_storage_error()),
+                };
+                Ok(InitialWithConfig { config })
+            }
+
+            // XXX - we could consider combining these Initial* states, because we don't
+            // attempt to support filling in "missing" global state - *any* 404 in them
+            // means `FreshStart`.
+            // IOW, in all cases, they either `Err()`, move to `FreshStartRequired`, or
+            // advance to a specific next state.
+            InitialWithConfig { config } => {
+                match self.client.fetch_info_collections()? {
+                    Sync15ClientResponse::Success {
+                        record: collections,
+                        ..
+                    } => Ok(InitialWithInfo {
+                        config,
+                        collections,
+                    }),
+                    // If the server doesn't have a `crypto/keys`, start over
+                    // and reupload our `meta/global` and `crypto/keys`.
+                    Sync15ClientResponse::Error(ErrorResponse::NotFound { .. }) => {
+                        Ok(FreshStartRequired { config })
+                    }
+                    other => Err(other.create_storage_error()),
+                }
+            }
+
+            InitialWithInfo {
+                config,
+                collections,
+            } => {
+                match self.client.fetch_meta_global()? {
+                    Sync15ClientResponse::Success {
+                        record: mut global,
+                        last_modified: mut global_timestamp,
+                        ..
+                    } => {
+                        // If the server has a newer storage version, we can't
+                        // sync until our client is updated.
+                        if global.storage_version > STORAGE_VERSION {
+                            return Err(ErrorKind::ClientUpgradeRequired);
+                        }
+
+                        // If the server has an older storage version, wipe and
+                        // reupload.
+                        if global.storage_version < STORAGE_VERSION {
+                            Ok(FreshStartRequired { config })
+                        } else {
+                            log::info!("Have info/collections and meta/global. Computing new engine states");
+                            let initial_global_declined: HashSet<String> =
+                                global.declined.iter().cloned().collect();
+                            let result = compute_engine_states(EngineStateInput {
+                                local_declined: self.pgs.get_declined().iter().cloned().collect(),
+                                user_changes: self.engine_updates.cloned().unwrap_or_default(),
+                                remote: Some(RemoteEngineState {
+                                    declined: initial_global_declined.clone(),
+                                    info_collections: collections.keys().cloned().collect(),
+                                }),
+                            });
+                            // Persist the new declined.
+                            self.pgs
+                                .set_declined(result.declined.iter().cloned().collect());
+                            // If the declined engines differ from remote, fix that.
+                            let fixed_declined = if result.declined != initial_global_declined {
+                                global.declined = result.declined.iter().cloned().collect();
+                                log::info!(
+                                    "Uploading new declined {:?} to meta/global with timestamp {:?}",
+                                    global.declined,
+                                    global_timestamp,
+                                );
+                                true
+                            } else {
+                                false
+                            };
+                            // If there are missing syncIds, we need to fix those as well
+                            let fixed_ids = if fixup_meta_global(&mut global) {
+                                log::info!(
+                                    "Uploading corrected meta/global with timestamp {:?}",
+                                    global_timestamp,
+                                );
+                                true
+                            } else {
+                                false
+                            };
+
+                            if fixed_declined || fixed_ids {
+                                global_timestamp =
+                                    self.client.put_meta_global(global_timestamp, &global)?;
+                                log::debug!("new global_timestamp: {:?}", global_timestamp);
+                            }
+                            // Update the set of changes needed.
+                            if self.changes_needed.is_some() {
+                                // Should never happen (we prevent state machine
+                                // loops elsewhere) but if it did, the info is stale
+                                // anyway.
+                                log::warn!("Already have a set of changes needed, Overwriting...");
+                            }
+                            self.changes_needed = Some(result.changes_needed);
+                            Ok(InitialWithMetaGlobal {
+                                config,
+                                collections,
+                                global,
+                                global_timestamp,
+                            })
+                        }
+                    }
+                    Sync15ClientResponse::Error(ErrorResponse::NotFound { .. }) => {
+                        Ok(FreshStartRequired { config })
+                    }
+                    other => Err(other.create_storage_error()),
+                }
+            }
+
+            InitialWithMetaGlobal {
+                config,
+                collections,
+                global,
+                global_timestamp,
+            } => {
+                // Now try and get keys etc - if we fresh-start we'll re-use declined.
+                match self.client.fetch_crypto_keys()? {
+                    Sync15ClientResponse::Success {
+                        record,
+                        last_modified,
+                        ..
+                    } => {
+                        // Note that collection/keys is itself a bso, so the
+                        // json body also carries the timestamp. If they aren't
+                        // identical something has screwed up and we should die.
+                        assert_eq!(last_modified, record.envelope.modified);
+                        let state = GlobalState {
+                            config,
+                            collections,
+                            global,
+                            global_timestamp,
+                            keys: record.payload,
+                            keys_timestamp: last_modified,
+                        };
+                        Ok(Ready { state })
+                    }
+                    // If the server doesn't have a `crypto/keys`, start over
+                    // and reupload our `meta/global` and `crypto/keys`.
+                    Sync15ClientResponse::Error(ErrorResponse::NotFound { .. }) => {
+                        Ok(FreshStartRequired { config })
+                    }
+                    other => Err(other.create_storage_error()),
+                }
+            }
+
+            // We've got old state that's likely to be OK.
+            // We keep things simple here - if there's evidence of a new/missing
+            // meta/global or new/missing keys we just restart from scratch.
+            WithPreviousState { old_state } => match self.client.fetch_info_collections()? {
+                Sync15ClientResponse::Success {
+                    record: collections,
+                    ..
+                } => Ok(
+                    if self.engine_updates.is_none()
+                        && is_same_timestamp(old_state.global_timestamp, &collections, "meta")
+                        && is_same_timestamp(old_state.keys_timestamp, &collections, "crypto")
+                    {
+                        Ready {
+                            state: GlobalState {
+                                collections,
+                                ..old_state
+                            },
+                        }
+                    } else {
+                        InitialWithConfig {
+                            config: old_state.config,
+                        }
+                    },
+                ),
+                _ => Ok(InitialWithConfig {
+                    config: old_state.config,
+                }),
+            },
+
+            Ready { state } => Ok(Ready { state }),
+
+            FreshStartRequired { config } => {
+                // Wipe the server.
+                log::info!("Fresh start: wiping remote");
+                self.client.wipe_all_remote()?;
+
+                // Upload a fresh `meta/global`...
+                log::info!("Uploading meta/global");
+                let computed = compute_engine_states(EngineStateInput {
+                    local_declined: self.pgs.get_declined().iter().cloned().collect(),
+                    user_changes: self.engine_updates.cloned().unwrap_or_default(),
+                    remote: None,
+                });
+                self.pgs
+                    .set_declined(computed.declined.iter().cloned().collect());
+
+                self.changes_needed = Some(computed.changes_needed);
+
+                let new_global = new_global(self.pgs);
+
+                self.client
+                    .put_meta_global(ServerTimestamp::default(), &new_global)?;
+
+                // ...And a fresh `crypto/keys`.
+                let new_keys = CollectionKeys::new_random()?.to_encrypted_payload(self.root_key)?;
+                let bso = OutgoingEncryptedBso::new(Guid::new("keys").into(), new_keys);
+                self.client
+                    .put_crypto_keys(ServerTimestamp::default(), &bso)?;
+
+                // TODO(lina): Can we pass along server timestamps from the PUTs
+                // above, and avoid re-fetching the `m/g` and `c/k` we just
+                // uploaded?
+                // OTOH(mark): restarting the state machine keeps life simple and rare.
+                Ok(InitialWithConfig { config })
+            }
+        }
+    }
+
+    /// Runs through the state machine to the ready state.
+    pub fn run_to_ready(&mut self, state: Option<GlobalState>) -> error::Result<GlobalState> {
+        let mut s = match state {
+            Some(old_state) => WithPreviousState { old_state },
+            None => Initial,
+        };
+        loop {
+            self.interruptee.err_if_interrupted()?;
+            let label = &s.label();
+            log::trace!("global state: {:?}", label);
+            match s {
+                Ready { state } => {
+                    self.sequence.push(label);
+                    return Ok(state);
+                }
+                // If we already started over once before, we're likely in a
+                // cycle, and should try again later. Intermediate states
+                // aren't a problem, just the initial ones.
+                FreshStartRequired { .. } | WithPreviousState { .. } | Initial => {
+                    if self.sequence.contains(label) {
+                        // Is this really the correct error?
+                        return Err(ErrorKind::SetupRace);
+                    }
+                }
+                _ => {
+                    if !self.allowed_states.contains(label) {
+                        return Err(ErrorKind::SetupRequired);
+                    }
+                }
+            };
+            self.sequence.push(label);
+            s = self.advance(s)?;
+        }
+    }
+}
+
+/// States in the remote setup process.
+/// TODO(lina): Add link once #56 is merged.
+#[derive(Debug)]
+#[allow(clippy::large_enum_variant)]
+enum SetupState {
+    // These "Initial" states are only ever used when starting from scratch.
+    Initial,
+    InitialWithConfig {
+        config: InfoConfiguration,
+    },
+    InitialWithInfo {
+        config: InfoConfiguration,
+        collections: InfoCollections,
+    },
+    InitialWithMetaGlobal {
+        config: InfoConfiguration,
+        collections: InfoCollections,
+        global: MetaGlobalRecord,
+        global_timestamp: ServerTimestamp,
+    },
+    WithPreviousState {
+        old_state: GlobalState,
+    },
+    Ready {
+        state: GlobalState,
+    },
+    FreshStartRequired {
+        config: InfoConfiguration,
+    },
+}
+
+impl SetupState {
+    fn label(&self) -> &'static str {
+        match self {
+            Initial { .. } => "Initial",
+            InitialWithConfig { .. } => "InitialWithConfig",
+            InitialWithInfo { .. } => "InitialWithInfo",
+            InitialWithMetaGlobal { .. } => "InitialWithMetaGlobal",
+            Ready { .. } => "Ready",
+            WithPreviousState { .. } => "WithPreviousState",
+            FreshStartRequired { .. } => "FreshStartRequired",
+        }
+    }
+}
+
+/// Whether we should skip fetching an item. Used when we already have timestamps
+/// and want to check if we should reuse our existing state. The state's fairly
+/// cheap to recreate and very bad to use if it is wrong, so we insist on the
+/// *exact* timestamp matching and not a simple "later than" check.
+fn is_same_timestamp(local: ServerTimestamp, collections: &InfoCollections, key: &str) -> bool {
+    collections.get(key).map_or(false, |ts| local == *ts)
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    use crate::bso::{IncomingEncryptedBso, IncomingEnvelope};
+    use interrupt_support::NeverInterrupts;
+
+    struct InMemoryClient {
+        info_configuration: error::Result<Sync15ClientResponse<InfoConfiguration>>,
+        info_collections: error::Result<Sync15ClientResponse<InfoCollections>>,
+        meta_global: error::Result<Sync15ClientResponse<MetaGlobalRecord>>,
+        crypto_keys: error::Result<Sync15ClientResponse<IncomingEncryptedBso>>,
+    }
+
+    impl SetupStorageClient for InMemoryClient {
+        fn fetch_info_configuration(
+            &self,
+        ) -> error::Result<Sync15ClientResponse<InfoConfiguration>> {
+            match &self.info_configuration {
+                Ok(client_response) => Ok(client_response.clone()),
+                Err(_) => Ok(Sync15ClientResponse::Error(ErrorResponse::ServerError {
+                    status: 500,
+                    route: "test/path".into(),
+                })),
+            }
+        }
+
+        fn fetch_info_collections(&self) -> error::Result<Sync15ClientResponse<InfoCollections>> {
+            match &self.info_collections {
+                Ok(collections) => Ok(collections.clone()),
+                Err(_) => Ok(Sync15ClientResponse::Error(ErrorResponse::ServerError {
+                    status: 500,
+                    route: "test/path".into(),
+                })),
+            }
+        }
+
+        fn fetch_meta_global(&self) -> error::Result<Sync15ClientResponse<MetaGlobalRecord>> {
+            match &self.meta_global {
+                Ok(global) => Ok(global.clone()),
+                // TODO(lina): Special handling for 404s, we want to ensure we
+                // handle missing keys and other server errors correctly.
+                Err(_) => Ok(Sync15ClientResponse::Error(ErrorResponse::ServerError {
+                    status: 500,
+                    route: "test/path".into(),
+                })),
+            }
+        }
+
+        fn put_meta_global(
+            &self,
+            xius: ServerTimestamp,
+            global: &MetaGlobalRecord,
+        ) -> error::Result<ServerTimestamp> {
+            // Ensure that the meta/global record we uploaded is "fixed up"
+            assert!(DEFAULT_ENGINES
+                .iter()
+                .filter(|e| e.0 != "logins")
+                .all(|&(k, _v)| global.engines.contains_key(k)));
+            assert!(!global.engines.contains_key("logins"));
+            assert_eq!(global.declined, vec!["logins".to_string()]);
+            // return a different timestamp.
+            Ok(ServerTimestamp(xius.0 + 1))
+        }
+
+        fn fetch_crypto_keys(&self) -> error::Result<Sync15ClientResponse<IncomingEncryptedBso>> {
+            match &self.crypto_keys {
+                Ok(Sync15ClientResponse::Success {
+                    status,
+                    record,
+                    last_modified,
+                    route,
+                }) => Ok(Sync15ClientResponse::Success {
+                    status: *status,
+                    record: IncomingEncryptedBso::new(
+                        record.envelope.clone(),
+                        record.payload.clone(),
+                    ),
+                    last_modified: *last_modified,
+                    route: route.clone(),
+                }),
+                // TODO(lina): Same as above, for 404s.
+                _ => Ok(Sync15ClientResponse::Error(ErrorResponse::ServerError {
+                    status: 500,
+                    route: "test/path".into(),
+                })),
+            }
+        }
+
+        fn put_crypto_keys(
+            &self,
+            xius: ServerTimestamp,
+            _keys: &OutgoingEncryptedBso,
+        ) -> error::Result<()> {
+            assert_eq!(xius, ServerTimestamp(888_800));
+            Err(ErrorKind::StorageHttpError(ErrorResponse::ServerError {
+                status: 500,
+                route: "crypto/keys".to_string(),
+            }))
+        }
+
+        fn wipe_all_remote(&self) -> error::Result<()> {
+            Ok(())
+        }
+    }
+
+    #[allow(clippy::unnecessary_wraps)]
+    fn mocked_success_ts<T>(t: T, ts: i64) -> error::Result<Sync15ClientResponse<T>> {
+        Ok(Sync15ClientResponse::Success {
+            status: 200,
+            record: t,
+            last_modified: ServerTimestamp(ts),
+            route: "test/path".into(),
+        })
+    }
+
+    fn mocked_success<T>(t: T) -> error::Result<Sync15ClientResponse<T>> {
+        mocked_success_ts(t, 0)
+    }
+
+    fn mocked_success_keys(
+        keys: CollectionKeys,
+        root_key: &KeyBundle,
+    ) -> error::Result<Sync15ClientResponse<IncomingEncryptedBso>> {
+        let timestamp = keys.timestamp;
+        let payload = keys.to_encrypted_payload(root_key).unwrap();
+        let bso = IncomingEncryptedBso::new(
+            IncomingEnvelope {
+                id: Guid::new("keys"),
+                modified: timestamp,
+                sortindex: None,
+                ttl: None,
+            },
+            payload,
+        );
+        Ok(Sync15ClientResponse::Success {
+            status: 200,
+            record: bso,
+            last_modified: timestamp,
+            route: "test/path".into(),
+        })
+    }
+
+    #[test]
+    fn test_state_machine_ready_from_empty() {
+        let _ = env_logger::try_init();
+        let root_key = KeyBundle::new_random().unwrap();
+        let keys = CollectionKeys {
+            timestamp: ServerTimestamp(123_400),
+            default: KeyBundle::new_random().unwrap(),
+            collections: HashMap::new(),
+        };
+        let mg = MetaGlobalRecord {
+            sync_id: "syncIDAAAAAA".into(),
+            storage_version: 5usize,
+            engines: vec![(
+                "bookmarks",
+                MetaGlobalEngine {
+                    version: 1usize,
+                    sync_id: "syncIDBBBBBB".into(),
+                },
+            )]
+            .into_iter()
+            .map(|(key, value)| (key.to_owned(), value))
+            .collect(),
+            // We ensure that the record we upload doesn't have a logins record.
+            declined: vec!["logins".to_string()],
+        };
+        let client = InMemoryClient {
+            info_configuration: mocked_success(InfoConfiguration::default()),
+            info_collections: mocked_success(InfoCollections::new(
+                vec![("meta", 123_456), ("crypto", 145_000)]
+                    .into_iter()
+                    .map(|(key, value)| (key.to_owned(), ServerTimestamp(value)))
+                    .collect(),
+            )),
+            meta_global: mocked_success_ts(mg, 999_000),
+            crypto_keys: mocked_success_keys(keys, &root_key),
+        };
+        let mut pgs = PersistedGlobalState::V2 { declined: None };
+
+        let mut state_machine =
+            SetupStateMachine::for_full_sync(&client, &root_key, &mut pgs, None, &NeverInterrupts);
+        assert!(
+            state_machine.run_to_ready(None).is_ok(),
+            "Should drive state machine to ready"
+        );
+        assert_eq!(
+            state_machine.sequence,
+            vec![
+                "Initial",
+                "InitialWithConfig",
+                "InitialWithInfo",
+                "InitialWithMetaGlobal",
+                "Ready",
+            ],
+            "Should cycle through all states"
+        );
+    }
+
+    #[test]
+    fn test_from_previous_state_declined() {
+        let _ = env_logger::try_init();
+        // The state-machine sequence where we didn't use the previous state
+        // (ie, where the state machine restarted)
+        let sm_seq_restarted = vec![
+            "WithPreviousState",
+            "InitialWithConfig",
+            "InitialWithInfo",
+            "InitialWithMetaGlobal",
+            "Ready",
+        ];
+        // The state-machine sequence where we used the previous state.
+        let sm_seq_used_previous = vec!["WithPreviousState", "Ready"];
+
+        // do the actual test.
+        fn do_test(
+            client: &dyn SetupStorageClient,
+            root_key: &KeyBundle,
+            pgs: &mut PersistedGlobalState,
+            engine_updates: Option<&HashMap<String, bool>>,
+            old_state: GlobalState,
+            expected_states: &[&str],
+        ) {
+            let mut state_machine = SetupStateMachine::for_full_sync(
+                client,
+                root_key,
+                pgs,
+                engine_updates,
+                &NeverInterrupts,
+            );
+            assert!(
+                state_machine.run_to_ready(Some(old_state)).is_ok(),
+                "Should drive state machine to ready"
+            );
+            assert_eq!(state_machine.sequence, expected_states);
+        }
+
+        // and all the complicated setup...
+        let ts_metaglobal = 123_456;
+        let ts_keys = 145_000;
+        let root_key = KeyBundle::new_random().unwrap();
+        let keys = CollectionKeys {
+            timestamp: ServerTimestamp(ts_keys + 1),
+            default: KeyBundle::new_random().unwrap(),
+            collections: HashMap::new(),
+        };
+        let mg = MetaGlobalRecord {
+            sync_id: "syncIDAAAAAA".into(),
+            storage_version: 5usize,
+            engines: vec![(
+                "bookmarks",
+                MetaGlobalEngine {
+                    version: 1usize,
+                    sync_id: "syncIDBBBBBB".into(),
+                },
+            )]
+            .into_iter()
+            .map(|(key, value)| (key.to_owned(), value))
+            .collect(),
+            // We ensure that the record we upload doesn't have a logins record.
+            declined: vec!["logins".to_string()],
+        };
+        let collections = InfoCollections::new(
+            vec![("meta", ts_metaglobal), ("crypto", ts_keys)]
+                .into_iter()
+                .map(|(key, value)| (key.to_owned(), ServerTimestamp(value)))
+                .collect(),
+        );
+        let client = InMemoryClient {
+            info_configuration: mocked_success(InfoConfiguration::default()),
+            info_collections: mocked_success(collections.clone()),
+            meta_global: mocked_success_ts(mg.clone(), ts_metaglobal),
+            crypto_keys: mocked_success_keys(keys.clone(), &root_key),
+        };
+
+        // First a test where the "previous" global state is OK to reuse.
+        {
+            let mut pgs = PersistedGlobalState::V2 { declined: None };
+            // A "previous" global state.
+            let old_state = GlobalState {
+                config: InfoConfiguration::default(),
+                collections: collections.clone(),
+                global: mg.clone(),
+                global_timestamp: ServerTimestamp(ts_metaglobal),
+                keys: keys
+                    .to_encrypted_payload(&root_key)
+                    .expect("should always work in this test"),
+                keys_timestamp: ServerTimestamp(ts_keys),
+            };
+            do_test(
+                &client,
+                &root_key,
+                &mut pgs,
+                None,
+                old_state,
+                &sm_seq_used_previous,
+            );
+        }
+
+        // Now where the meta/global record on the server is later.
+        {
+            let mut pgs = PersistedGlobalState::V2 { declined: None };
+            // A "previous" global state.
+            let old_state = GlobalState {
+                config: InfoConfiguration::default(),
+                collections: collections.clone(),
+                global: mg.clone(),
+                global_timestamp: ServerTimestamp(999_999),
+                keys: keys
+                    .to_encrypted_payload(&root_key)
+                    .expect("should always work in this test"),
+                keys_timestamp: ServerTimestamp(ts_keys),
+            };
+            do_test(
+                &client,
+                &root_key,
+                &mut pgs,
+                None,
+                old_state,
+                &sm_seq_restarted,
+            );
+        }
+
+        // Where keys on the server is later.
+        {
+            let mut pgs = PersistedGlobalState::V2 { declined: None };
+            // A "previous" global state.
+            let old_state = GlobalState {
+                config: InfoConfiguration::default(),
+                collections: collections.clone(),
+                global: mg.clone(),
+                global_timestamp: ServerTimestamp(ts_metaglobal),
+                keys: keys
+                    .to_encrypted_payload(&root_key)
+                    .expect("should always work in this test"),
+                keys_timestamp: ServerTimestamp(999_999),
+            };
+            do_test(
+                &client,
+                &root_key,
+                &mut pgs,
+                None,
+                old_state,
+                &sm_seq_restarted,
+            );
+        }
+
+        // Where there are engine-state changes.
+        {
+            let mut pgs = PersistedGlobalState::V2 { declined: None };
+            // A "previous" global state.
+            let old_state = GlobalState {
+                config: InfoConfiguration::default(),
+                collections,
+                global: mg,
+                global_timestamp: ServerTimestamp(ts_metaglobal),
+                keys: keys
+                    .to_encrypted_payload(&root_key)
+                    .expect("should always work in this test"),
+                keys_timestamp: ServerTimestamp(ts_keys),
+            };
+            let mut engine_updates = HashMap::<String, bool>::new();
+            engine_updates.insert("logins".to_string(), false);
+            do_test(
+                &client,
+                &root_key,
+                &mut pgs,
+                Some(&engine_updates),
+                old_state,
+                &sm_seq_restarted,
+            );
+            let declined = match pgs {
+                PersistedGlobalState::V2 { declined: d } => d,
+            };
+            // and check we now consider logins as declined.
+            assert_eq!(declined, Some(vec!["logins".to_string()]));
+        }
+    }
+
+    fn string_set(s: &[&str]) -> HashSet<String> {
+        s.iter().map(ToString::to_string).collect()
+    }
+    fn string_map<T: Clone>(s: &[(&str, T)]) -> HashMap<String, T> {
+        s.iter().map(|v| (v.0.to_string(), v.1.clone())).collect()
+    }
+    #[test]
+    fn test_engine_states() {
+        assert_eq!(
+            compute_engine_states(EngineStateInput {
+                local_declined: string_set(&["foo", "bar"]),
+                remote: None,
+                user_changes: Default::default(),
+            }),
+            EngineStateOutput {
+                declined: string_set(&["foo", "bar"]),
+                // No wipes, no resets
+                changes_needed: Default::default(),
+            }
+        );
+        assert_eq!(
+            compute_engine_states(EngineStateInput {
+                local_declined: string_set(&["foo", "bar"]),
+                remote: Some(RemoteEngineState {
+                    declined: string_set(&["foo"]),
+                    info_collections: string_set(&["bar"])
+                }),
+                user_changes: Default::default(),
+            }),
+            EngineStateOutput {
+                // Now we have `foo`.
+                declined: string_set(&["foo"]),
+                // No wipes, no resets, should just be a local update.
+                changes_needed: Default::default(),
+            }
+        );
+        assert_eq!(
+            compute_engine_states(EngineStateInput {
+                local_declined: string_set(&["foo", "bar"]),
+                remote: Some(RemoteEngineState {
+                    declined: string_set(&["foo", "bar", "quux"]),
+                    info_collections: string_set(&[])
+                }),
+                user_changes: Default::default(),
+            }),
+            EngineStateOutput {
+                // Now we have `foo`.
+                declined: string_set(&["foo", "bar", "quux"]),
+                changes_needed: EngineChangesNeeded {
+                    // Should reset `quux`.
+                    local_resets: string_set(&["quux"]),
+                    // No wipes, though.
+                    remote_wipes: string_set(&[]),
+                }
+            }
+        );
+        assert_eq!(
+            compute_engine_states(EngineStateInput {
+                local_declined: string_set(&["bar", "baz"]),
+                remote: Some(RemoteEngineState {
+                    declined: string_set(&["bar", "baz",]),
+                    info_collections: string_set(&["quux"])
+                }),
+                // Change a declined engine to undeclined.
+                user_changes: string_map(&[("bar", true)]),
+            }),
+            EngineStateOutput {
+                declined: string_set(&["baz"]),
+                // No wipes, just undecline it.
+                changes_needed: Default::default()
+            }
+        );
+        assert_eq!(
+            compute_engine_states(EngineStateInput {
+                local_declined: string_set(&["bar", "baz"]),
+                remote: Some(RemoteEngineState {
+                    declined: string_set(&["bar", "baz"]),
+                    info_collections: string_set(&["foo"])
+                }),
+                // Change an engine which exists remotely to declined.
+                user_changes: string_map(&[("foo", false)]),
+            }),
+            EngineStateOutput {
+                declined: string_set(&["baz", "bar", "foo"]),
+                // No wipes, just undecline it.
+                changes_needed: EngineChangesNeeded {
+                    // Should reset our local foo
+                    local_resets: string_set(&["foo"]),
+                    // And wipe the server.
+                    remote_wipes: string_set(&["foo"]),
+                }
+            }
+        );
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/sync15/client/status.rs.html b/book/rust-docs/src/sync15/client/status.rs.html new file mode 100644 index 0000000000..0e8b40604e --- /dev/null +++ b/book/rust-docs/src/sync15/client/status.rs.html @@ -0,0 +1,213 @@ +status.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::error::{Error, ErrorResponse};
+use crate::telemetry::SyncTelemetryPing;
+use std::collections::HashMap;
+use std::time::{Duration, SystemTime};
+
+/// The general status of sync - should probably be moved to the "sync manager"
+/// once we have one!
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum ServiceStatus {
+    /// Everything is fine.
+    Ok,
+    /// Some general network issue.
+    NetworkError,
+    /// Some apparent issue with the servers.
+    ServiceError,
+    /// Some external FxA action needs to be taken.
+    AuthenticationError,
+    /// We declined to do anything for backoff or rate-limiting reasons.
+    BackedOff,
+    /// We were interrupted.
+    Interrupted,
+    /// Something else - you need to check the logs for more details. May
+    /// or may not be transient, we really don't know.
+    OtherError,
+}
+
+impl ServiceStatus {
+    // This is a bit naive and probably will not survive in this form in the
+    // SyncManager - eg, we'll want to handle backoff etc.
+    pub fn from_err(err: &Error) -> ServiceStatus {
+        match err {
+            // HTTP based errors.
+            Error::TokenserverHttpError(status) => {
+                // bit of a shame the tokenserver is different to storage...
+                if *status == 401 {
+                    ServiceStatus::AuthenticationError
+                } else {
+                    ServiceStatus::ServiceError
+                }
+            }
+            // BackoffError is also from the tokenserver.
+            Error::BackoffError(_) => ServiceStatus::ServiceError,
+            Error::StorageHttpError(ref e) => match e {
+                ErrorResponse::Unauthorized { .. } => ServiceStatus::AuthenticationError,
+                _ => ServiceStatus::ServiceError,
+            },
+
+            // Network errors.
+            Error::RequestError(_) | Error::UnexpectedStatus(_) | Error::HawkError(_) => {
+                ServiceStatus::NetworkError
+            }
+
+            Error::Interrupted(_) => ServiceStatus::Interrupted,
+            _ => ServiceStatus::OtherError,
+        }
+    }
+}
+
+/// The result of a sync request. This too is from the "sync manager", but only
+/// has a fraction of the things it will have when we actually build that.
+#[derive(Debug)]
+pub struct SyncResult {
+    /// The general health.
+    pub service_status: ServiceStatus,
+
+    /// The set of declined engines, if we know them.
+    pub declined: Option<Vec<String>>,
+
+    /// The result of the sync.
+    pub result: Result<(), Error>,
+
+    /// The result for each engine.
+    /// Note that we expect the `String` to be replaced with an enum later.
+    pub engine_results: HashMap<String, Result<(), Error>>,
+
+    pub telemetry: SyncTelemetryPing,
+
+    pub next_sync_after: Option<std::time::SystemTime>,
+}
+
+// If `r` has a BackoffError, then returns the later backoff value.
+fn advance_backoff(cur_best: SystemTime, r: &Result<(), Error>) -> SystemTime {
+    if let Err(e) = r {
+        if let Some(time) = e.get_backoff() {
+            return std::cmp::max(time, cur_best);
+        }
+    }
+    cur_best
+}
+
+impl SyncResult {
+    pub(crate) fn set_sync_after(&mut self, backoff_duration: Duration) {
+        let now = SystemTime::now();
+        let toplevel = advance_backoff(now + backoff_duration, &self.result);
+        let sync_after = self.engine_results.values().fold(toplevel, advance_backoff);
+        if sync_after <= now {
+            self.next_sync_after = None;
+        } else {
+            self.next_sync_after = Some(sync_after);
+        }
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/sync15/client/storage_client.rs.html b/book/rust-docs/src/sync15/client/storage_client.rs.html new file mode 100644 index 0000000000..a89179f9d1 --- /dev/null +++ b/book/rust-docs/src/sync15/client/storage_client.rs.html @@ -0,0 +1,1211 @@ +storage_client.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
+540
+541
+542
+543
+544
+545
+546
+547
+548
+549
+550
+551
+552
+553
+554
+555
+556
+557
+558
+559
+560
+561
+562
+563
+564
+565
+566
+567
+568
+569
+570
+571
+572
+573
+574
+575
+576
+577
+578
+579
+580
+581
+582
+583
+584
+585
+586
+587
+588
+589
+590
+591
+592
+593
+594
+595
+596
+597
+598
+599
+600
+601
+602
+603
+604
+605
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use super::request::{
+    BatchPoster, InfoCollections, InfoConfiguration, PostQueue, PostResponse, PostResponseHandler,
+};
+use super::token;
+use crate::bso::{IncomingBso, IncomingEncryptedBso, OutgoingBso, OutgoingEncryptedBso};
+use crate::engine::{CollectionPost, CollectionRequest};
+use crate::error::{self, Error, ErrorResponse};
+use crate::record_types::MetaGlobalRecord;
+use crate::{CollectionName, Guid, ServerTimestamp};
+use serde_json::Value;
+use std::str::FromStr;
+use std::sync::atomic::{AtomicU32, Ordering};
+use url::Url;
+use viaduct::{
+    header_names::{self, AUTHORIZATION},
+    Method, Request, Response,
+};
+
+/// A response from a GET request on a Sync15StorageClient, encapsulating all
+/// the variants users of this client needs to care about.
+#[derive(Debug, Clone)]
+pub enum Sync15ClientResponse<T> {
+    Success {
+        status: u16,
+        record: T,
+        last_modified: ServerTimestamp,
+        route: String,
+    },
+    Error(ErrorResponse),
+}
+
+fn parse_seconds(seconds_str: &str) -> Option<u32> {
+    let secs = seconds_str.parse::<f64>().ok()?.ceil();
+    // Note: u32 doesn't impl TryFrom<f64> :(
+    if !secs.is_finite() || secs < 0.0 || secs > f64::from(u32::max_value()) {
+        log::warn!("invalid backoff value: {}", secs);
+        None
+    } else {
+        Some(secs as u32)
+    }
+}
+
+impl<T> Sync15ClientResponse<T> {
+    pub fn from_response(resp: Response, backoff_listener: &BackoffListener) -> error::Result<Self>
+    where
+        for<'a> T: serde::de::Deserialize<'a>,
+    {
+        let route: String = resp.url.path().into();
+        // Android seems to respect retry_after even on success requests, so we
+        // will too if it's present. This also lets us handle both backoff-like
+        // properties in the same place.
+        let retry_after = resp
+            .headers
+            .get(header_names::RETRY_AFTER)
+            .and_then(parse_seconds);
+
+        let backoff = resp
+            .headers
+            .get(header_names::X_WEAVE_BACKOFF)
+            .and_then(parse_seconds);
+
+        if let Some(b) = backoff {
+            backoff_listener.note_backoff(b);
+        }
+        if let Some(ra) = retry_after {
+            backoff_listener.note_retry_after(ra);
+        }
+
+        Ok(if resp.is_success() {
+            let record: T = resp.json()?;
+            let last_modified = resp
+                .headers
+                .get(header_names::X_LAST_MODIFIED)
+                .and_then(|s| ServerTimestamp::from_str(s).ok())
+                .ok_or(Error::MissingServerTimestamp)?;
+            log::info!(
+                "Successful request to \"{}\", incoming x-last-modified={:?}",
+                route,
+                last_modified
+            );
+
+            Sync15ClientResponse::Success {
+                status: resp.status,
+                record,
+                last_modified,
+                route,
+            }
+        } else {
+            let status = resp.status;
+            log::info!("Request \"{}\" was an error (status={})", route, status);
+            match status {
+                404 => Sync15ClientResponse::Error(ErrorResponse::NotFound { route }),
+                401 => Sync15ClientResponse::Error(ErrorResponse::Unauthorized { route }),
+                412 => Sync15ClientResponse::Error(ErrorResponse::PreconditionFailed { route }),
+                500..=600 => {
+                    Sync15ClientResponse::Error(ErrorResponse::ServerError { route, status })
+                }
+                _ => Sync15ClientResponse::Error(ErrorResponse::RequestFailed { route, status }),
+            }
+        })
+    }
+
+    pub fn create_storage_error(self) -> Error {
+        let inner = match self {
+            Sync15ClientResponse::Success { status, route, .. } => {
+                // This should never happen as callers are expected to have
+                // already special-cased this response, so warn if it does.
+                // (or maybe we could panic?)
+                log::warn!("Converting success response into an error");
+                ErrorResponse::RequestFailed { status, route }
+            }
+            Sync15ClientResponse::Error(e) => e,
+        };
+        Error::StorageHttpError(inner)
+    }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct Sync15StorageClientInit {
+    pub key_id: String,
+    pub access_token: String,
+    pub tokenserver_url: Url,
+}
+
+/// A trait containing the methods required to run through the setup state
+/// machine. This is factored out into a separate trait to make mocking
+/// easier.
+pub trait SetupStorageClient {
+    fn fetch_info_configuration(&self) -> error::Result<Sync15ClientResponse<InfoConfiguration>>;
+    fn fetch_info_collections(&self) -> error::Result<Sync15ClientResponse<InfoCollections>>;
+    fn fetch_meta_global(&self) -> error::Result<Sync15ClientResponse<MetaGlobalRecord>>;
+    fn fetch_crypto_keys(&self) -> error::Result<Sync15ClientResponse<IncomingEncryptedBso>>;
+
+    fn put_meta_global(
+        &self,
+        xius: ServerTimestamp,
+        global: &MetaGlobalRecord,
+    ) -> error::Result<ServerTimestamp>;
+    fn put_crypto_keys(
+        &self,
+        xius: ServerTimestamp,
+        keys: &OutgoingEncryptedBso,
+    ) -> error::Result<()>;
+    fn wipe_all_remote(&self) -> error::Result<()>;
+}
+
+#[derive(Debug, Default)]
+pub struct BackoffState {
+    pub backoff_secs: AtomicU32,
+    pub retry_after_secs: AtomicU32,
+}
+
+pub(crate) type BackoffListener = std::sync::Arc<BackoffState>;
+
+pub(crate) fn new_backoff_listener() -> BackoffListener {
+    std::sync::Arc::new(BackoffState::default())
+}
+
+impl BackoffState {
+    pub fn note_backoff(&self, noted: u32) {
+        super::util::atomic_update_max(&self.backoff_secs, noted)
+    }
+
+    pub fn note_retry_after(&self, noted: u32) {
+        super::util::atomic_update_max(&self.retry_after_secs, noted)
+    }
+
+    pub fn get_backoff_secs(&self) -> u32 {
+        self.backoff_secs.load(Ordering::SeqCst)
+    }
+
+    pub fn get_retry_after_secs(&self) -> u32 {
+        self.retry_after_secs.load(Ordering::SeqCst)
+    }
+
+    pub fn get_required_wait(&self, ignore_soft_backoff: bool) -> Option<std::time::Duration> {
+        let bo = self.get_backoff_secs();
+        let ra = self.get_retry_after_secs();
+        let secs = u64::from(if ignore_soft_backoff { ra } else { bo.max(ra) });
+        if secs > 0 {
+            Some(std::time::Duration::from_secs(secs))
+        } else {
+            None
+        }
+    }
+
+    pub fn reset(&self) {
+        self.backoff_secs.store(0, Ordering::SeqCst);
+        self.retry_after_secs.store(0, Ordering::SeqCst);
+    }
+}
+
+// meta/global is a clear-text Bso (ie, there's a String `payload` which has a MetaGlobalRecord)
+// We don't use the 'content' helpers here because we want json errors to be fatal here
+// (ie, we don't need tombstones and can't just skip a malformed record)
+type IncMetaGlobalBso = IncomingBso;
+type OutMetaGlobalBso = OutgoingBso;
+
+#[derive(Debug)]
+pub struct Sync15StorageClient {
+    tsc: token::TokenProvider,
+    pub(crate) backoff: BackoffListener,
+}
+
+impl SetupStorageClient for Sync15StorageClient {
+    fn fetch_info_configuration(&self) -> error::Result<Sync15ClientResponse<InfoConfiguration>> {
+        self.relative_storage_request(Method::Get, "info/configuration")
+    }
+
+    fn fetch_info_collections(&self) -> error::Result<Sync15ClientResponse<InfoCollections>> {
+        self.relative_storage_request(Method::Get, "info/collections")
+    }
+
+    fn fetch_meta_global(&self) -> error::Result<Sync15ClientResponse<MetaGlobalRecord>> {
+        let got: Sync15ClientResponse<IncMetaGlobalBso> =
+            self.relative_storage_request(Method::Get, "storage/meta/global")?;
+        Ok(match got {
+            Sync15ClientResponse::Success {
+                record,
+                last_modified,
+                route,
+                status,
+            } => {
+                log::debug!(
+                    "Got meta global with modified = {}; last-modified = {}",
+                    record.envelope.modified,
+                    last_modified
+                );
+                Sync15ClientResponse::Success {
+                    record: serde_json::from_str(&record.payload)?,
+                    last_modified,
+                    route,
+                    status,
+                }
+            }
+            Sync15ClientResponse::Error(e) => Sync15ClientResponse::Error(e),
+        })
+    }
+
+    fn fetch_crypto_keys(&self) -> error::Result<Sync15ClientResponse<IncomingEncryptedBso>> {
+        self.relative_storage_request(Method::Get, "storage/crypto/keys")
+    }
+
+    fn put_meta_global(
+        &self,
+        xius: ServerTimestamp,
+        global: &MetaGlobalRecord,
+    ) -> error::Result<ServerTimestamp> {
+        let bso = OutMetaGlobalBso::new(Guid::new("global").into(), global)?;
+        self.put("storage/meta/global", xius, &bso)
+    }
+
+    fn put_crypto_keys(
+        &self,
+        xius: ServerTimestamp,
+        keys: &OutgoingEncryptedBso,
+    ) -> error::Result<()> {
+        self.put("storage/crypto/keys", xius, keys)?;
+        Ok(())
+    }
+
+    fn wipe_all_remote(&self) -> error::Result<()> {
+        let s = self.tsc.api_endpoint()?;
+        let url = Url::parse(&s)?;
+
+        let req = self.build_request(Method::Delete, url)?;
+        match self.exec_request::<Value>(req, false) {
+            Ok(Sync15ClientResponse::Error(ErrorResponse::NotFound { .. }))
+            | Ok(Sync15ClientResponse::Success { .. }) => Ok(()),
+            Ok(resp) => Err(resp.create_storage_error()),
+            Err(e) => Err(e),
+        }
+    }
+}
+
+impl Sync15StorageClient {
+    pub fn new(init_params: Sync15StorageClientInit) -> error::Result<Sync15StorageClient> {
+        rc_crypto::ensure_initialized();
+        let tsc = token::TokenProvider::new(
+            init_params.tokenserver_url,
+            init_params.access_token,
+            init_params.key_id,
+        )?;
+        Ok(Sync15StorageClient {
+            tsc,
+            backoff: new_backoff_listener(),
+        })
+    }
+
+    pub fn get_encrypted_records(
+        &self,
+        collection_request: CollectionRequest,
+    ) -> error::Result<Sync15ClientResponse<Vec<IncomingEncryptedBso>>> {
+        self.collection_request(Method::Get, collection_request)
+    }
+
+    #[inline]
+    fn authorized(&self, req: Request) -> error::Result<Request> {
+        let hawk_header_value = self.tsc.authorization(&req)?;
+        Ok(req.header(AUTHORIZATION, hawk_header_value)?)
+    }
+
+    // TODO: probably want a builder-like API to do collection requests (e.g. something
+    // that occupies roughly the same conceptual role as the Collection class in desktop)
+    fn build_request(&self, method: Method, url: Url) -> error::Result<Request> {
+        self.authorized(Request::new(method, url).header(header_names::ACCEPT, "application/json")?)
+    }
+
+    fn relative_storage_request<P, T>(
+        &self,
+        method: Method,
+        relative_path: P,
+    ) -> error::Result<Sync15ClientResponse<T>>
+    where
+        P: AsRef<str>,
+        for<'a> T: serde::de::Deserialize<'a>,
+    {
+        let s = self.tsc.api_endpoint()? + "/";
+        let url = Url::parse(&s)?.join(relative_path.as_ref())?;
+        self.exec_request(self.build_request(method, url)?, false)
+    }
+
+    fn exec_request<T>(
+        &self,
+        req: Request,
+        require_success: bool,
+    ) -> error::Result<Sync15ClientResponse<T>>
+    where
+        for<'a> T: serde::de::Deserialize<'a>,
+    {
+        log::trace!(
+            "request: {} {} ({:?})",
+            req.method,
+            req.url.path(),
+            req.url.query()
+        );
+        let resp = req.send()?;
+
+        let result = Sync15ClientResponse::from_response(resp, &self.backoff)?;
+        match result {
+            Sync15ClientResponse::Success { .. } => Ok(result),
+            _ => {
+                if require_success {
+                    Err(result.create_storage_error())
+                } else {
+                    Ok(result)
+                }
+            }
+        }
+    }
+
+    fn collection_request<T>(
+        &self,
+        method: Method,
+        r: CollectionRequest,
+    ) -> error::Result<Sync15ClientResponse<T>>
+    where
+        for<'a> T: serde::de::Deserialize<'a>,
+    {
+        let url = build_collection_request_url(Url::parse(&self.tsc.api_endpoint()?)?, r)?;
+        self.exec_request(self.build_request(method, url)?, false)
+    }
+
+    pub fn new_post_queue<'a, F: PostResponseHandler>(
+        &'a self,
+        coll: &'a CollectionName,
+        config: &InfoConfiguration,
+        ts: ServerTimestamp,
+        on_response: F,
+    ) -> error::Result<PostQueue<PostWrapper<'a>, F>> {
+        let pw = PostWrapper { client: self, coll };
+        Ok(PostQueue::new(config, ts, pw, on_response))
+    }
+
+    fn put<P, B>(
+        &self,
+        relative_path: P,
+        xius: ServerTimestamp,
+        body: &B,
+    ) -> error::Result<ServerTimestamp>
+    where
+        P: AsRef<str>,
+        B: serde::ser::Serialize,
+    {
+        let s = self.tsc.api_endpoint()? + "/";
+        let url = Url::parse(&s)?.join(relative_path.as_ref())?;
+
+        let req = self
+            .build_request(Method::Put, url)?
+            .json(body)
+            .header(header_names::X_IF_UNMODIFIED_SINCE, format!("{}", xius))?;
+
+        let resp = self.exec_request::<Value>(req, true)?;
+        // Note: we pass `true` for require_success, so this panic never happens.
+        if let Sync15ClientResponse::Success { last_modified, .. } = resp {
+            Ok(last_modified)
+        } else {
+            unreachable!("Error returned exec_request when `require_success` was true");
+        }
+    }
+
+    pub fn hashed_uid(&self) -> error::Result<String> {
+        self.tsc.hashed_uid()
+    }
+
+    pub(crate) fn wipe_remote_engine(&self, engine: &str) -> error::Result<()> {
+        let s = self.tsc.api_endpoint()? + "/";
+        let url = Url::parse(&s)?.join(&format!("storage/{}", engine))?;
+        log::debug!("Wiping: {:?}", url);
+        let req = self.build_request(Method::Delete, url)?;
+        match self.exec_request::<Value>(req, false) {
+            Ok(Sync15ClientResponse::Error(ErrorResponse::NotFound { .. }))
+            | Ok(Sync15ClientResponse::Success { .. }) => Ok(()),
+            Ok(resp) => Err(resp.create_storage_error()),
+            Err(e) => Err(e),
+        }
+    }
+}
+
+pub struct PostWrapper<'a> {
+    client: &'a Sync15StorageClient,
+    coll: &'a CollectionName,
+}
+
+impl<'a> BatchPoster for PostWrapper<'a> {
+    fn post<T, O>(
+        &self,
+        bytes: Vec<u8>,
+        xius: ServerTimestamp,
+        batch: Option<String>,
+        commit: bool,
+        _: &PostQueue<T, O>,
+    ) -> error::Result<PostResponse> {
+        let r = CollectionPost::new(self.coll.clone())
+            .batch(batch)
+            .commit(commit);
+        let url = build_collection_post_url(Url::parse(&self.client.tsc.api_endpoint()?)?, r)?;
+
+        let req = self
+            .client
+            .build_request(Method::Post, url)?
+            .header(header_names::CONTENT_TYPE, "application/json")?
+            .header(header_names::X_IF_UNMODIFIED_SINCE, format!("{}", xius))?
+            .body(bytes);
+        self.client.exec_request(req, false)
+    }
+}
+
+fn build_collection_url(mut base_url: Url, collection: CollectionName) -> error::Result<Url> {
+    base_url
+        .path_segments_mut()
+        .map_err(|_| Error::UnacceptableUrl("Storage server URL is not a base".to_string()))?
+        .extend(&["storage", &collection]);
+
+    // This is strange but just accessing query_pairs_mut makes you have
+    // a trailing question mark on your url. I don't think anything bad
+    // would happen here, but I don't know, and also, it looks dumb so
+    // I'd rather not have it.
+    if base_url.query() == Some("") {
+        base_url.set_query(None);
+    }
+    Ok(base_url)
+}
+
+fn build_collection_request_url(mut base_url: Url, r: CollectionRequest) -> error::Result<Url> {
+    let mut pairs = base_url.query_pairs_mut();
+    if r.full {
+        pairs.append_pair("full", "1");
+    }
+    if let Some(ids) = &r.ids {
+        // Most ids are 12 characters, and we comma separate them, so 13.
+        let mut buf = String::with_capacity(ids.len() * 13);
+        for (i, id) in ids.iter().enumerate() {
+            if i > 0 {
+                buf.push(',');
+            }
+            buf.push_str(id.as_str());
+        }
+        pairs.append_pair("ids", &buf);
+    }
+    if let Some(ts) = r.older {
+        pairs.append_pair("older", &ts.to_string());
+    }
+    if let Some(ts) = r.newer {
+        pairs.append_pair("newer", &ts.to_string());
+    }
+    if let Some(l) = r.limit {
+        pairs.append_pair("sort", l.order.as_str());
+        pairs.append_pair("limit", &l.num.to_string());
+    }
+    pairs.finish();
+    drop(pairs);
+    build_collection_url(base_url, r.collection)
+}
+
+#[cfg(feature = "sync-client")]
+fn build_collection_post_url(mut base_url: Url, r: CollectionPost) -> error::Result<Url> {
+    let mut pairs = base_url.query_pairs_mut();
+    if let Some(batch) = &r.batch {
+        pairs.append_pair("batch", batch);
+    }
+    if r.commit {
+        pairs.append_pair("commit", "true");
+    }
+    pairs.finish();
+    drop(pairs);
+    build_collection_url(base_url, r.collection)
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+    #[test]
+    fn test_send() {
+        fn ensure_send<T: Send>() {}
+        // Compile will fail if not send.
+        ensure_send::<Sync15StorageClient>();
+    }
+
+    #[test]
+    fn test_parse_seconds() {
+        assert_eq!(parse_seconds("1"), Some(1));
+        assert_eq!(parse_seconds("1.4"), Some(2));
+        assert_eq!(parse_seconds("1.5"), Some(2));
+        assert_eq!(parse_seconds("3600.0"), Some(3600));
+        assert_eq!(parse_seconds("3600"), Some(3600));
+        assert_eq!(parse_seconds("-1"), None);
+        assert_eq!(parse_seconds("inf"), None);
+        assert_eq!(parse_seconds("-inf"), None);
+        assert_eq!(parse_seconds("one-thousand"), None);
+        assert_eq!(parse_seconds("4294967295"), Some(4294967295));
+        assert_eq!(parse_seconds("4294967296"), None);
+    }
+
+    #[test]
+    fn test_query_building() {
+        use crate::engine::RequestOrder;
+        let base = Url::parse("https://example.com/sync").unwrap();
+
+        let empty =
+            build_collection_request_url(base.clone(), CollectionRequest::new("foo".into()))
+                .unwrap();
+        assert_eq!(empty.as_str(), "https://example.com/sync/storage/foo");
+
+        let idreq = build_collection_request_url(
+            base.clone(),
+            CollectionRequest::new("wutang".into())
+                .full()
+                .ids(&["rza", "gza"]),
+        )
+        .unwrap();
+        assert_eq!(
+            idreq.as_str(),
+            "https://example.com/sync/storage/wutang?full=1&ids=rza%2Cgza"
+        );
+
+        let complex = build_collection_request_url(
+            base,
+            CollectionRequest::new("specific".into())
+                .full()
+                .limit(10, RequestOrder::Oldest)
+                .older_than(ServerTimestamp(9_876_540))
+                .newer_than(ServerTimestamp(1_234_560)),
+        )
+        .unwrap();
+        assert_eq!(complex.as_str(),
+            "https://example.com/sync/storage/specific?full=1&older=9876.54&newer=1234.56&sort=oldest&limit=10");
+    }
+
+    #[cfg(feature = "sync-client")]
+    #[test]
+    fn test_post_query_building() {
+        let base = Url::parse("https://example.com/sync").unwrap();
+
+        let empty =
+            build_collection_post_url(base.clone(), CollectionPost::new("foo".into())).unwrap();
+        assert_eq!(empty.as_str(), "https://example.com/sync/storage/foo");
+        let batch_start = build_collection_post_url(
+            base.clone(),
+            CollectionPost::new("bar".into())
+                .batch(Some("true".into()))
+                .commit(false),
+        )
+        .unwrap();
+        assert_eq!(
+            batch_start.as_str(),
+            "https://example.com/sync/storage/bar?batch=true"
+        );
+        let batch_commit = build_collection_post_url(
+            base,
+            CollectionPost::new("asdf".into())
+                .batch(Some("1234abc".into()))
+                .commit(true),
+        )
+        .unwrap();
+        assert_eq!(
+            batch_commit.as_str(),
+            "https://example.com/sync/storage/asdf?batch=1234abc&commit=true"
+        );
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/sync15/client/sync.rs.html b/book/rust-docs/src/sync15/client/sync.rs.html new file mode 100644 index 0000000000..5a8e39988b --- /dev/null +++ b/book/rust-docs/src/sync15/client/sync.rs.html @@ -0,0 +1,229 @@ +sync.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use super::{CollectionUpdate, GlobalState, LocalCollStateMachine, Sync15StorageClient};
+use crate::clients_engine;
+use crate::engine::SyncEngine;
+use crate::error::Error;
+use crate::telemetry;
+use crate::KeyBundle;
+use interrupt_support::Interruptee;
+
+#[allow(clippy::too_many_arguments)]
+pub fn synchronize_with_clients_engine(
+    client: &Sync15StorageClient,
+    global_state: &GlobalState,
+    root_sync_key: &KeyBundle,
+    clients: Option<&clients_engine::Engine<'_>>,
+    engine: &dyn SyncEngine,
+    fully_atomic: bool,
+    telem_engine: &mut telemetry::Engine,
+    interruptee: &dyn Interruptee,
+) -> Result<(), Error> {
+    let collection = engine.collection_name();
+    log::info!("Syncing collection {}", collection);
+
+    // our global state machine is ready - get the collection machine going.
+    let coll_state = match LocalCollStateMachine::get_state(engine, global_state, root_sync_key)? {
+        Some(coll_state) => coll_state,
+        None => {
+            // XXX - this is either "error" or "declined".
+            log::warn!(
+                "can't setup for the {} collection - hopefully it works later",
+                collection
+            );
+            return Ok(());
+        }
+    };
+
+    if let Some(clients) = clients {
+        engine.prepare_for_sync(&|| clients.get_client_data())?;
+    }
+    interruptee.err_if_interrupted()?;
+    // We assume an "engine" manages exactly one "collection" with the engine's name.
+    match engine.get_collection_request(coll_state.last_modified)? {
+        None => {
+            log::info!("skipping incoming for {} - not needed.", collection);
+        }
+        Some(collection_request) => {
+            // Ideally we would "batch" incoming records (eg, fetch just 1000 at a time)
+            // and ask the engine to "stage" them as they come in - but currently we just read
+            // them all in one request.
+
+            // Doing this batching will involve specifying a "limit=" param and
+            // "x-if-unmodified-since" for each request, looking for an
+            // "X-Weave-Next-Offset header in the response and using that in subsequent
+            // requests.
+            // See https://mozilla-services.readthedocs.io/en/latest/storage/apis-1.5.html#syncstorage-paging
+            //
+            // But even if we had that, we need to deal with a 412 response on a subsequent batch,
+            // so we can't know if we've staged *every* record for that timestamp; the next
+            // sync must use an earlier one.
+            //
+            // For this reason, an engine can't really trust a server timestamp until the
+            // very end when we know we've staged them all.
+            let incoming = super::fetch_incoming(client, &coll_state, collection_request)?;
+            log::info!("Downloaded {} remote changes", incoming.len());
+            engine.stage_incoming(incoming, telem_engine)?;
+            interruptee.err_if_interrupted()?;
+        }
+    };
+
+    // Should consider adding a new `fetch_outgoing()` and having `apply()` only apply.
+    // It *might* even make sense to only call `apply()` when something was staged,
+    // but that's not clear - see the discussion at
+    // https://github.com/mozilla/application-services/pull/5441/files/f36274f455a6299f10e7ce56b167882c369aa806#r1189267540
+    log::info!("Applying changes");
+    let outgoing = engine.apply(coll_state.last_modified, telem_engine)?;
+    interruptee.err_if_interrupted()?;
+
+    // XXX - this upload strategy is buggy due to batching. With enough records, we will commit
+    // 2 batches on the server. If the second fails, we get an Err<> here, so can't tell the
+    // engine about the successful server batch commit.
+    // Most stuff below should be called per-batch rather than at the successful end of all
+    // batches, but that's not trivial.
+    log::info!("Uploading {} outgoing changes", outgoing.len());
+    let upload_info = CollectionUpdate::new_from_changeset(
+        client,
+        &coll_state,
+        collection,
+        outgoing,
+        fully_atomic,
+    )?
+    .upload()?;
+    log::info!(
+        "Upload success ({} records success, {} records failed)",
+        upload_info.successful_ids.len(),
+        upload_info.failed_ids.len()
+    );
+
+    let mut telem_outgoing = telemetry::EngineOutgoing::new();
+    telem_outgoing.sent(upload_info.successful_ids.len() + upload_info.failed_ids.len());
+    telem_outgoing.failed(upload_info.failed_ids.len());
+    telem_engine.outgoing(telem_outgoing);
+
+    engine.set_uploaded(upload_info.modified_timestamp, upload_info.successful_ids)?;
+
+    // The above should all be per-batch :(
+
+    engine.sync_finished()?;
+
+    log::info!("Sync finished!");
+    Ok(())
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/sync15/client/sync_multiple.rs.html b/book/rust-docs/src/sync15/client/sync_multiple.rs.html new file mode 100644 index 0000000000..955122840b --- /dev/null +++ b/book/rust-docs/src/sync15/client/sync_multiple.rs.html @@ -0,0 +1,983 @@ +sync_multiple.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This helps you perform a sync of multiple engines and helps you manage
+// global and local state between syncs.
+
+use super::state::{EngineChangesNeeded, GlobalState, PersistedGlobalState, SetupStateMachine};
+use super::status::{ServiceStatus, SyncResult};
+use super::storage_client::{BackoffListener, Sync15StorageClient, Sync15StorageClientInit};
+use crate::clients_engine::{self, CommandProcessor, CLIENTS_TTL_REFRESH};
+use crate::engine::{EngineSyncAssociation, SyncEngine};
+use crate::error::Error;
+use crate::telemetry;
+use crate::KeyBundle;
+use interrupt_support::Interruptee;
+use std::collections::HashMap;
+use std::result;
+use std::time::{Duration, SystemTime};
+
+/// Info about the client to use. We reuse the client unless
+/// we discover the client_init has changed, in which case we re-create one.
+#[derive(Debug)]
+struct ClientInfo {
+    // the client_init used to create `client`.
+    client_init: Sync15StorageClientInit,
+    // the client (our tokenserver state machine state, and our http library's state)
+    client: Sync15StorageClient,
+}
+
+impl ClientInfo {
+    fn new(ci: &Sync15StorageClientInit) -> Result<Self, Error> {
+        Ok(Self {
+            client_init: ci.clone(),
+            client: Sync15StorageClient::new(ci.clone())?,
+        })
+    }
+}
+
+/// Info we want callers to engine *in memory* for us so that subsequent
+/// syncs are faster. This should never be persisted to storage as it holds
+/// sensitive information, such as the sync decryption keys.
+#[derive(Debug, Default)]
+pub struct MemoryCachedState {
+    last_client_info: Option<ClientInfo>,
+    last_global_state: Option<GlobalState>,
+    // These are just engined in memory, as persisting an invalid value far in the
+    // future has the potential to break sync for good.
+    next_sync_after: Option<SystemTime>,
+    next_client_refresh_after: Option<SystemTime>,
+}
+
+impl MemoryCachedState {
+    // Called we notice the cached state is stale.
+    pub fn clear_sensitive_info(&mut self) {
+        self.last_client_info = None;
+        self.last_global_state = None;
+        // Leave the backoff time, as there's no reason to think it's not still
+        // true.
+    }
+    pub fn get_next_sync_after(&self) -> Option<SystemTime> {
+        self.next_sync_after
+    }
+    pub fn should_refresh_client(&self) -> bool {
+        match self.next_client_refresh_after {
+            Some(t) => SystemTime::now() > t,
+            None => true,
+        }
+    }
+    pub fn note_client_refresh(&mut self) {
+        self.next_client_refresh_after =
+            Some(SystemTime::now() + Duration::from_secs(CLIENTS_TTL_REFRESH));
+    }
+}
+
+/// Sync multiple engines
+/// * `engines` - The engines to sync
+/// * `persisted_global_state` - The global state to use, or None if never
+///   before provided. At the end of the sync, and even when the sync fails,
+///   the value in this cell should be persisted to permanent storage and
+///   provided next time the sync is called.
+/// * `last_client_info` - The client state to use, or None if never before
+///   provided. At the end of the sync, the value should be persisted
+///   *in memory only* - it should not be persisted to disk.
+/// * `storage_init` - Information about how the sync http client should be
+///   configured.
+/// * `root_sync_key` - The KeyBundle used for encryption.
+///
+/// Returns a map, keyed by name and holding an error value - if any engine
+/// fails, the sync will continue on to other engines, but the error will be
+/// places in this map. The absence of a name in the map implies the engine
+/// succeeded.
+pub fn sync_multiple(
+    engines: &[&dyn SyncEngine],
+    persisted_global_state: &mut Option<String>,
+    mem_cached_state: &mut MemoryCachedState,
+    storage_init: &Sync15StorageClientInit,
+    root_sync_key: &KeyBundle,
+    interruptee: &dyn Interruptee,
+    req_info: Option<SyncRequestInfo<'_>>,
+) -> SyncResult {
+    sync_multiple_with_command_processor(
+        None,
+        engines,
+        persisted_global_state,
+        mem_cached_state,
+        storage_init,
+        root_sync_key,
+        interruptee,
+        req_info,
+    )
+}
+
+/// Like `sync_multiple`, but specifies an optional command processor to handle
+/// commands from the clients collection. This function is called by the sync
+/// manager, which provides its own processor.
+#[allow(clippy::too_many_arguments)]
+pub fn sync_multiple_with_command_processor(
+    command_processor: Option<&dyn CommandProcessor>,
+    engines: &[&dyn SyncEngine],
+    persisted_global_state: &mut Option<String>,
+    mem_cached_state: &mut MemoryCachedState,
+    storage_init: &Sync15StorageClientInit,
+    root_sync_key: &KeyBundle,
+    interruptee: &dyn Interruptee,
+    req_info: Option<SyncRequestInfo<'_>>,
+) -> SyncResult {
+    log::info!("Syncing {} engines", engines.len());
+    let mut sync_result = SyncResult {
+        service_status: ServiceStatus::OtherError,
+        result: Ok(()),
+        declined: None,
+        next_sync_after: None,
+        engine_results: HashMap::with_capacity(engines.len()),
+        telemetry: telemetry::SyncTelemetryPing::new(),
+    };
+    let backoff = super::storage_client::new_backoff_listener();
+    let req_info = req_info.unwrap_or_default();
+    let driver = SyncMultipleDriver {
+        command_processor,
+        engines,
+        storage_init,
+        interruptee,
+        engines_to_state_change: req_info.engines_to_state_change,
+        backoff: backoff.clone(),
+        root_sync_key,
+        result: &mut sync_result,
+        persisted_global_state,
+        mem_cached_state,
+        saw_auth_error: false,
+        ignore_soft_backoff: req_info.is_user_action,
+    };
+    match driver.sync() {
+        Ok(()) => {
+            log::debug!(
+                "sync was successful, final status={:?}",
+                sync_result.service_status
+            );
+        }
+        Err(e) => {
+            log::warn!(
+                "sync failed: {}, final status={:?}",
+                e,
+                sync_result.service_status,
+            );
+            sync_result.result = Err(e);
+        }
+    }
+    // Respect `backoff` value when computing the next sync time even if we were
+    // ignoring it during the sync
+    sync_result.set_sync_after(backoff.get_required_wait(false).unwrap_or_default());
+    mem_cached_state.next_sync_after = sync_result.next_sync_after;
+    log::trace!("Sync result: {:?}", sync_result);
+    sync_result
+}
+
+/// This is essentially a bag of information that the sync manager knows, but
+/// otherwise we won't. It should probably be rethought if it gains many more
+/// fields.
+#[derive(Debug, Default)]
+pub struct SyncRequestInfo<'a> {
+    pub engines_to_state_change: Option<&'a HashMap<String, bool>>,
+    pub is_user_action: bool,
+}
+
+// The sync multiple driver
+struct SyncMultipleDriver<'info, 'res, 'pgs, 'mcs> {
+    command_processor: Option<&'info dyn CommandProcessor>,
+    engines: &'info [&'info dyn SyncEngine],
+    storage_init: &'info Sync15StorageClientInit,
+    root_sync_key: &'info KeyBundle,
+    interruptee: &'info dyn Interruptee,
+    backoff: BackoffListener,
+    engines_to_state_change: Option<&'info HashMap<String, bool>>,
+    result: &'res mut SyncResult,
+    persisted_global_state: &'pgs mut Option<String>,
+    mem_cached_state: &'mcs mut MemoryCachedState,
+    ignore_soft_backoff: bool,
+    saw_auth_error: bool,
+}
+
+impl<'info, 'res, 'pgs, 'mcs> SyncMultipleDriver<'info, 'res, 'pgs, 'mcs> {
+    /// The actual worker for sync_multiple.
+    fn sync(mut self) -> result::Result<(), Error> {
+        log::info!("Loading/initializing persisted state");
+        let mut pgs = self.prepare_persisted_state();
+
+        log::info!("Preparing client info");
+        let client_info = self.prepare_client_info()?;
+
+        if self.was_interrupted() {
+            return Ok(());
+        }
+
+        log::info!("Entering sync state machine");
+        // Advance the state machine to the point where it can perform a full
+        // sync. This may involve uploading meta/global, crypto/keys etc.
+        let mut global_state = self.run_state_machine(&client_info, &mut pgs)?;
+
+        if self.was_interrupted() {
+            return Ok(());
+        }
+
+        // Set the service status to OK here - we may adjust it based on an individual
+        // engine failing.
+        self.result.service_status = ServiceStatus::Ok;
+
+        let clients_engine = if let Some(command_processor) = self.command_processor {
+            log::info!("Synchronizing clients engine");
+            let should_refresh = self.mem_cached_state.should_refresh_client();
+            let mut engine = clients_engine::Engine::new(command_processor, self.interruptee);
+            if let Err(e) = engine.sync(
+                &client_info.client,
+                &global_state,
+                self.root_sync_key,
+                should_refresh,
+            ) {
+                // Record telemetry with the error just in case...
+                let mut telem_sync = telemetry::SyncTelemetry::new();
+                let mut telem_engine = telemetry::Engine::new("clients");
+                telem_engine.failure(&e);
+                telem_sync.engine(telem_engine);
+                self.result.service_status = ServiceStatus::from_err(&e);
+
+                // ...And bail, because a clients engine sync failure is fatal.
+                return Err(e);
+            }
+            // We don't record telemetry for successful clients engine
+            // syncs, since we only keep client records in memory, we
+            // expect the counts to be the same most times, and a
+            // failure aborts the entire sync.
+            if self.was_interrupted() {
+                return Ok(());
+            }
+            self.mem_cached_state.note_client_refresh();
+            Some(engine)
+        } else {
+            None
+        };
+
+        log::info!("Synchronizing engines");
+
+        let telem_sync =
+            self.sync_engines(&client_info, &mut global_state, clients_engine.as_ref());
+        self.result.telemetry.sync(telem_sync);
+
+        log::info!("Finished syncing engines.");
+
+        if !self.saw_auth_error {
+            log::trace!("Updating persisted global state");
+            self.mem_cached_state.last_client_info = Some(client_info);
+            self.mem_cached_state.last_global_state = Some(global_state);
+        }
+
+        Ok(())
+    }
+
+    fn was_interrupted(&mut self) -> bool {
+        if self.interruptee.was_interrupted() {
+            log::info!("Interrupted, bailing out");
+            self.result.service_status = ServiceStatus::Interrupted;
+            true
+        } else {
+            false
+        }
+    }
+
+    fn sync_engines(
+        &mut self,
+        client_info: &ClientInfo,
+        global_state: &mut GlobalState,
+        clients: Option<&clients_engine::Engine<'_>>,
+    ) -> telemetry::SyncTelemetry {
+        let mut telem_sync = telemetry::SyncTelemetry::new();
+        for engine in self.engines {
+            let name = engine.collection_name();
+            if self
+                .backoff
+                .get_required_wait(self.ignore_soft_backoff)
+                .is_some()
+            {
+                log::warn!("Got backoff, bailing out of sync early");
+                break;
+            }
+            if global_state.global.declined.iter().any(|e| e == &*name) {
+                log::info!("The {} engine is declined. Skipping", name);
+                continue;
+            }
+            log::info!("Syncing {} engine!", name);
+
+            let mut telem_engine = telemetry::Engine::new(&*name);
+            let result = super::sync::synchronize_with_clients_engine(
+                &client_info.client,
+                global_state,
+                self.root_sync_key,
+                clients,
+                *engine,
+                true,
+                &mut telem_engine,
+                self.interruptee,
+            );
+
+            match result {
+                Ok(()) => log::info!("Sync of {} was successful!", name),
+                Err(ref e) => {
+                    log::warn!("Sync of {} failed! {:?}", name, e);
+                    let this_status = ServiceStatus::from_err(e);
+                    // The only error which forces us to discard our state is an
+                    // auth error.
+                    self.saw_auth_error =
+                        self.saw_auth_error || this_status == ServiceStatus::AuthenticationError;
+                    telem_engine.failure(e);
+                    // If the failure from the engine looks like anything other than
+                    // a "engine error" we don't bother trying the others.
+                    if this_status != ServiceStatus::OtherError {
+                        telem_sync.engine(telem_engine);
+                        self.result.engine_results.insert(name.into(), result);
+                        self.result.service_status = this_status;
+                        break;
+                    }
+                }
+            }
+            telem_sync.engine(telem_engine);
+            self.result.engine_results.insert(name.into(), result);
+            if self.was_interrupted() {
+                break;
+            }
+        }
+        telem_sync
+    }
+
+    fn run_state_machine(
+        &mut self,
+        client_info: &ClientInfo,
+        pgs: &mut PersistedGlobalState,
+    ) -> result::Result<GlobalState, Error> {
+        let last_state = self.mem_cached_state.last_global_state.take();
+
+        let mut state_machine = SetupStateMachine::for_full_sync(
+            &client_info.client,
+            self.root_sync_key,
+            pgs,
+            self.engines_to_state_change,
+            self.interruptee,
+        );
+
+        log::info!("Advancing state machine to ready (full)");
+        let res = state_machine.run_to_ready(last_state);
+        // Grab this now even though we don't need it until later to avoid a
+        // lifetime issue
+        let changes = state_machine.changes_needed.take();
+        // The state machine might have updated our persisted_global_state, so
+        // update the caller's repr of it.
+        *self.persisted_global_state = Some(serde_json::to_string(&pgs)?);
+
+        // Now that we've gone through the state machine, engine the declined list in
+        // the sync_result
+        self.result.declined = Some(pgs.get_declined().to_vec());
+        log::debug!(
+            "Declined engines list after state machine set to: {:?}",
+            self.result.declined,
+        );
+
+        if let Some(c) = changes {
+            self.wipe_or_reset_engines(c, &client_info.client)?;
+        }
+        let state = match res {
+            Err(e) => {
+                self.result.service_status = ServiceStatus::from_err(&e);
+                return Err(e);
+            }
+            Ok(state) => state,
+        };
+        self.result.telemetry.uid(client_info.client.hashed_uid()?);
+        // As for client_info, put None back now so we start from scratch on error.
+        self.mem_cached_state.last_global_state = None;
+        Ok(state)
+    }
+
+    fn wipe_or_reset_engines(
+        &mut self,
+        changes: EngineChangesNeeded,
+        client: &Sync15StorageClient,
+    ) -> result::Result<(), Error> {
+        if changes.local_resets.is_empty() && changes.remote_wipes.is_empty() {
+            return Ok(());
+        }
+        for e in &changes.remote_wipes {
+            log::info!("Engine {:?} just got disabled locally, wiping server", e);
+            client.wipe_remote_engine(e)?;
+        }
+
+        for s in self.engines {
+            let name = s.collection_name();
+            if changes.local_resets.contains(&*name) {
+                log::info!("Resetting engine {}, as it was declined remotely", name);
+                s.reset(&EngineSyncAssociation::Disconnected)?;
+            }
+        }
+
+        Ok(())
+    }
+
+    fn prepare_client_info(&mut self) -> result::Result<ClientInfo, Error> {
+        let mut client_info = match self.mem_cached_state.last_client_info.take() {
+            Some(client_info) => {
+                // if our storage_init has changed it probably means the user has
+                // changed, courtesy of the 'kid' in the structure. Thus, we can't
+                // reuse the client or the memory cached state. We do keep the disk
+                // state as currently that's only the declined list.
+                if client_info.client_init != *self.storage_init {
+                    log::info!("Discarding all state as the account might have changed");
+                    *self.mem_cached_state = MemoryCachedState::default();
+                    ClientInfo::new(self.storage_init)?
+                } else {
+                    log::debug!("Reusing memory-cached client_info");
+                    // we can reuse it (which should be the common path)
+                    client_info
+                }
+            }
+            None => {
+                log::debug!("mem_cached_state was stale or missing, need setup");
+                // We almost certainly have no other state here, but to be safe, we
+                // throw away any memory state we do have.
+                self.mem_cached_state.clear_sensitive_info();
+                ClientInfo::new(self.storage_init)?
+            }
+        };
+        // Ensure we use the correct listener here rather than on all the branches
+        // above, since it seems less error prone.
+        client_info.client.backoff = self.backoff.clone();
+        Ok(client_info)
+    }
+
+    fn prepare_persisted_state(&mut self) -> PersistedGlobalState {
+        // Note that any failure to use a persisted state means we also decline
+        // to use our memory cached state, so that we fully rebuild that
+        // persisted state for next time.
+        match self.persisted_global_state {
+            Some(persisted_string) if !persisted_string.is_empty() => {
+                match serde_json::from_str::<PersistedGlobalState>(persisted_string) {
+                    Ok(state) => {
+                        log::trace!("Read persisted state: {:?}", state);
+                        // Note that we don't set `result.declined` from the
+                        // data in state - it remains None, which explicitly
+                        // indicates "we don't have updated info".
+                        state
+                    }
+                    _ => {
+                        // Don't log the error since it might contain sensitive
+                        // info (although currently it only contains the declined engines list)
+                        error_support::report_error!(
+                            "sync15-prepare-persisted-state",
+                            "Failed to parse PersistedGlobalState from JSON! Falling back to default"
+                        );
+                        *self.mem_cached_state = MemoryCachedState::default();
+                        PersistedGlobalState::default()
+                    }
+                }
+            }
+            _ => {
+                log::info!(
+                    "The application didn't give us persisted state - \
+                     this is only expected on the very first run for a given user."
+                );
+                *self.mem_cached_state = MemoryCachedState::default();
+                PersistedGlobalState::default()
+            }
+        }
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/sync15/client/token.rs.html b/book/rust-docs/src/sync15/client/token.rs.html new file mode 100644 index 0000000000..d99ef51de2 --- /dev/null +++ b/book/rust-docs/src/sync15/client/token.rs.html @@ -0,0 +1,1205 @@ +token.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
+540
+541
+542
+543
+544
+545
+546
+547
+548
+549
+550
+551
+552
+553
+554
+555
+556
+557
+558
+559
+560
+561
+562
+563
+564
+565
+566
+567
+568
+569
+570
+571
+572
+573
+574
+575
+576
+577
+578
+579
+580
+581
+582
+583
+584
+585
+586
+587
+588
+589
+590
+591
+592
+593
+594
+595
+596
+597
+598
+599
+600
+601
+602
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::error::{self, Error as ErrorKind, Result};
+use crate::ServerTimestamp;
+use rc_crypto::hawk;
+use serde_derive::*;
+use std::borrow::{Borrow, Cow};
+use std::cell::RefCell;
+use std::fmt;
+use std::time::{Duration, SystemTime};
+use url::Url;
+use viaduct::{header_names, Request};
+
+const RETRY_AFTER_DEFAULT_MS: u64 = 10000;
+
+// The TokenserverToken is the token as received directly from the token server
+// and deserialized from JSON.
+#[derive(Deserialize, Clone, PartialEq, Eq)]
+struct TokenserverToken {
+    id: String,
+    key: String,
+    api_endpoint: String,
+    uid: u64,
+    duration: u64,
+    hashed_fxa_uid: String,
+}
+
+impl std::fmt::Debug for TokenserverToken {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.debug_struct("TokenserverToken")
+            .field("api_endpoint", &self.api_endpoint)
+            .field("uid", &self.uid)
+            .field("duration", &self.duration)
+            .field("hashed_fxa_uid", &self.hashed_fxa_uid)
+            .finish()
+    }
+}
+
+// The struct returned by the TokenFetcher - the token itself and the
+// server timestamp.
+struct TokenFetchResult {
+    token: TokenserverToken,
+    server_timestamp: ServerTimestamp,
+}
+
+// The trait for fetching tokens - we'll provide a "real" implementation but
+// tests will re-implement it.
+trait TokenFetcher {
+    fn fetch_token(&self) -> crate::Result<TokenFetchResult>;
+    // We allow the trait to tell us what the time is so tests can get funky.
+    fn now(&self) -> SystemTime;
+}
+
+// Our "real" token fetcher, implementing the TokenFetcher trait, which hits
+// the token server
+#[derive(Debug)]
+struct TokenServerFetcher {
+    // The stuff needed to fetch a token.
+    server_url: Url,
+    access_token: String,
+    key_id: String,
+}
+
+fn fixup_server_url(mut url: Url) -> url::Url {
+    // The given `url` is the end-point as returned by .well-known/fxa-client-configuration,
+    // or as directly specified by self-hosters. As a result, it may or may not have
+    // the sync 1.5 suffix of "/1.0/sync/1.5", so add it on here if it does not.
+    if url.as_str().ends_with("1.0/sync/1.5") {
+        // ok!
+    } else if url.as_str().ends_with("1.0/sync/1.5/") {
+        // Shouldn't ever be Err() here, but the result is `Result<PathSegmentsMut, ()>`
+        // and I don't want to unwrap or add a new error type just for PathSegmentsMut failing.
+        if let Ok(mut path) = url.path_segments_mut() {
+            path.pop();
+        }
+    } else {
+        // We deliberately don't use `.join()` here in order to preserve all path components.
+        // For example, "http://example.com/token" should produce "http://example.com/token/1.0/sync/1.5"
+        // but using `.join()` would produce "http://example.com/1.0/sync/1.5".
+        if let Ok(mut path) = url.path_segments_mut() {
+            path.pop_if_empty();
+            path.extend(&["1.0", "sync", "1.5"]);
+        }
+    };
+    url
+}
+
+impl TokenServerFetcher {
+    fn new(base_url: Url, access_token: String, key_id: String) -> TokenServerFetcher {
+        TokenServerFetcher {
+            server_url: fixup_server_url(base_url),
+            access_token,
+            key_id,
+        }
+    }
+}
+
+impl TokenFetcher for TokenServerFetcher {
+    fn fetch_token(&self) -> Result<TokenFetchResult> {
+        log::debug!("Fetching token from {}", self.server_url);
+        let resp = Request::get(self.server_url.clone())
+            .header(
+                header_names::AUTHORIZATION,
+                format!("Bearer {}", self.access_token),
+            )?
+            .header(header_names::X_KEYID, self.key_id.clone())?
+            .send()?;
+
+        if !resp.is_success() {
+            log::warn!("Non-success status when fetching token: {}", resp.status);
+            // TODO: the body should be JSON and contain a status parameter we might need?
+            log::trace!("  Response body {}", resp.text());
+            // XXX - shouldn't we "chain" these errors - ie, a BackoffError could
+            // have a TokenserverHttpError as its cause?
+            if let Some(res) = resp.headers.get_as::<f64, _>(header_names::RETRY_AFTER) {
+                let ms = res
+                    .ok()
+                    .map_or(RETRY_AFTER_DEFAULT_MS, |f| (f * 1000f64) as u64);
+                let when = self.now() + Duration::from_millis(ms);
+                return Err(ErrorKind::BackoffError(when));
+            }
+            let status = resp.status;
+            return Err(ErrorKind::TokenserverHttpError(status));
+        }
+
+        let token: TokenserverToken = resp.json()?;
+        let server_timestamp = resp
+            .headers
+            .try_get::<ServerTimestamp, _>(header_names::X_TIMESTAMP)
+            .ok_or(ErrorKind::MissingServerTimestamp)?;
+        Ok(TokenFetchResult {
+            token,
+            server_timestamp,
+        })
+    }
+
+    fn now(&self) -> SystemTime {
+        SystemTime::now()
+    }
+}
+
+// The context stored by our TokenProvider when it has a TokenState::Token
+// state.
+struct TokenContext {
+    token: TokenserverToken,
+    credentials: hawk::Credentials,
+    server_timestamp: ServerTimestamp,
+    valid_until: SystemTime,
+}
+
+// hawk::Credentials doesn't implement debug -_-
+impl fmt::Debug for TokenContext {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> ::std::result::Result<(), fmt::Error> {
+        f.debug_struct("TokenContext")
+            .field("token", &self.token)
+            .field("credentials", &"(omitted)")
+            .field("server_timestamp", &self.server_timestamp)
+            .field("valid_until", &self.valid_until)
+            .finish()
+    }
+}
+
+impl TokenContext {
+    fn new(
+        token: TokenserverToken,
+        credentials: hawk::Credentials,
+        server_timestamp: ServerTimestamp,
+        valid_until: SystemTime,
+    ) -> Self {
+        Self {
+            token,
+            credentials,
+            server_timestamp,
+            valid_until,
+        }
+    }
+
+    fn is_valid(&self, now: SystemTime) -> bool {
+        // We could consider making the duration a little shorter - if it
+        // only has 1 second validity there seems a reasonable chance it will
+        // have expired by the time it gets presented to the remote that wants
+        // it.
+        // Either way though, we will eventually need to handle a token being
+        // rejected as a non-fatal error and recover, so maybe we don't care?
+        now < self.valid_until
+    }
+
+    fn authorization(&self, req: &Request) -> Result<String> {
+        let url = &req.url;
+
+        let path_and_query = match url.query() {
+            None => Cow::from(url.path()),
+            Some(qs) => Cow::from(format!("{}?{}", url.path(), qs)),
+        };
+
+        let host = url
+            .host_str()
+            .ok_or_else(|| ErrorKind::UnacceptableUrl("Storage URL has no host".into()))?;
+
+        // Known defaults exist for https? (among others), so this should be impossible
+        let port = url.port_or_known_default().ok_or_else(|| {
+            ErrorKind::UnacceptableUrl(
+                "Storage URL has no port and no default port is known for the protocol".into(),
+            )
+        })?;
+
+        let header =
+            hawk::RequestBuilder::new(req.method.as_str(), host, port, path_and_query.borrow())
+                .request()
+                .make_header(&self.credentials)?;
+
+        Ok(format!("Hawk {}", header))
+    }
+}
+
+// The state our TokenProvider holds to reflect the state of the token.
+#[derive(Debug)]
+enum TokenState {
+    // We've never fetched a token.
+    NoToken,
+    // Have a token and last we checked it remained valid.
+    Token(TokenContext),
+    // We failed to fetch a token. First elt is the error, second elt is
+    // the api_endpoint we had before we failed to fetch a new token (or
+    // None if the very first attempt at fetching a token failed)
+    Failed(Option<error::Error>, Option<String>),
+    // Previously failed and told to back-off for SystemTime duration. Second
+    // elt is the api_endpoint we had before we hit the backoff error.
+    // XXX - should we roll Backoff and Failed together?
+    Backoff(SystemTime, Option<String>),
+    // api_endpoint changed - we are never going to get a token nor move out
+    // of this state.
+    NodeReassigned,
+}
+
+/// The generic TokenProvider implementation - long lived and fetches tokens
+/// on demand (eg, when first needed, or when an existing one expires.)
+#[derive(Debug)]
+struct TokenProviderImpl<TF: TokenFetcher> {
+    fetcher: TF,
+    // Our token state (ie, whether we have a token, and if not, why not)
+    current_state: RefCell<TokenState>,
+}
+
+impl<TF: TokenFetcher> TokenProviderImpl<TF> {
+    fn new(fetcher: TF) -> Self {
+        // We check this at the real entrypoint of the application, but tests
+        // can/do bypass that, so check this here too.
+        rc_crypto::ensure_initialized();
+        TokenProviderImpl {
+            fetcher,
+            current_state: RefCell::new(TokenState::NoToken),
+        }
+    }
+
+    // Uses our fetcher to grab a new token and if successfull, derives other
+    // info from that token into a usable TokenContext.
+    fn fetch_context(&self) -> Result<TokenContext> {
+        let result = self.fetcher.fetch_token()?;
+        let token = result.token;
+        let valid_until = SystemTime::now() + Duration::from_secs(token.duration);
+
+        let credentials = hawk::Credentials {
+            id: token.id.clone(),
+            key: hawk::Key::new(token.key.as_bytes(), hawk::SHA256)?,
+        };
+
+        Ok(TokenContext::new(
+            token,
+            credentials,
+            result.server_timestamp,
+            valid_until,
+        ))
+    }
+
+    // Attempt to fetch a new token and return a new state reflecting that
+    // operation. If it worked a TokenState will be returned, but errors may
+    // cause other states.
+    fn fetch_token(&self, previous_endpoint: Option<&str>) -> TokenState {
+        match self.fetch_context() {
+            Ok(tc) => {
+                // We got a new token - check that the endpoint is the same
+                // as a previous endpoint we saw (if any)
+                match previous_endpoint {
+                    Some(prev) => {
+                        if prev == tc.token.api_endpoint {
+                            TokenState::Token(tc)
+                        } else {
+                            log::warn!(
+                                "api_endpoint changed from {} to {}",
+                                prev,
+                                tc.token.api_endpoint
+                            );
+                            TokenState::NodeReassigned
+                        }
+                    }
+                    None => {
+                        // Never had an api_endpoint in the past, so this is OK.
+                        TokenState::Token(tc)
+                    }
+                }
+            }
+            Err(e) => {
+                // Early to avoid nll issues...
+                if let ErrorKind::BackoffError(be) = e {
+                    return TokenState::Backoff(be, previous_endpoint.map(ToString::to_string));
+                }
+                TokenState::Failed(Some(e), previous_endpoint.map(ToString::to_string))
+            }
+        }
+    }
+
+    // Given the state we are currently in, return a new current state.
+    // Returns None if the current state should be used (eg, if we are
+    // holding a token that remains valid) or Some() if the state has changed
+    // (which may have changed to a state with a token or an error state)
+    fn advance_state(&self, state: &TokenState) -> Option<TokenState> {
+        match state {
+            TokenState::NoToken => Some(self.fetch_token(None)),
+            TokenState::Failed(_, existing_endpoint) => {
+                Some(self.fetch_token(existing_endpoint.as_ref().map(String::as_str)))
+            }
+            TokenState::Token(existing_context) => {
+                if existing_context.is_valid(self.fetcher.now()) {
+                    None
+                } else {
+                    Some(self.fetch_token(Some(existing_context.token.api_endpoint.as_str())))
+                }
+            }
+            TokenState::Backoff(ref until, ref existing_endpoint) => {
+                if let Ok(remaining) = until.duration_since(self.fetcher.now()) {
+                    log::debug!("enforcing existing backoff - {:?} remains", remaining);
+                    None
+                } else {
+                    // backoff period is over
+                    Some(self.fetch_token(existing_endpoint.as_ref().map(String::as_str)))
+                }
+            }
+            TokenState::NodeReassigned => {
+                // We never leave this state.
+                None
+            }
+        }
+    }
+
+    fn with_token<T, F>(&self, func: F) -> Result<T>
+    where
+        F: FnOnce(&TokenContext) -> Result<T>,
+    {
+        // first get a mutable ref to our existing state, advance to the
+        // state we will use, then re-stash that state for next time.
+        let state: &mut TokenState = &mut self.current_state.borrow_mut();
+        if let Some(new_state) = self.advance_state(state) {
+            *state = new_state;
+        }
+
+        // Now re-fetch the state we should use for this call - if it's
+        // anything other than TokenState::Token we will fail.
+        match state {
+            TokenState::NoToken => {
+                // it should be impossible to get here.
+                panic!("Can't be in NoToken state after advancing");
+            }
+            TokenState::Token(ref token_context) => {
+                // make the call.
+                func(token_context)
+            }
+            TokenState::Failed(e, _) => {
+                // We swap the error out of the state enum and return it.
+                Err(e.take().unwrap())
+            }
+            TokenState::NodeReassigned => {
+                // this is unrecoverable.
+                Err(ErrorKind::StorageResetError)
+            }
+            TokenState::Backoff(ref remaining, _) => Err(ErrorKind::BackoffError(*remaining)),
+        }
+    }
+
+    fn hashed_uid(&self) -> Result<String> {
+        self.with_token(|ctx| Ok(ctx.token.hashed_fxa_uid.clone()))
+    }
+
+    fn authorization(&self, req: &Request) -> Result<String> {
+        self.with_token(|ctx| ctx.authorization(req))
+    }
+
+    fn api_endpoint(&self) -> Result<String> {
+        self.with_token(|ctx| Ok(ctx.token.api_endpoint.clone()))
+    }
+    // TODO: we probably want a "drop_token/context" type method so that when
+    // using a token with some validity fails the caller can force a new one
+    // (in which case the new token request will probably fail with a 401)
+}
+
+// The public concrete object exposed by this module
+#[derive(Debug)]
+pub struct TokenProvider {
+    imp: TokenProviderImpl<TokenServerFetcher>,
+}
+
+impl TokenProvider {
+    pub fn new(url: Url, access_token: String, key_id: String) -> Result<Self> {
+        let fetcher = TokenServerFetcher::new(url, access_token, key_id);
+        Ok(Self {
+            imp: TokenProviderImpl::new(fetcher),
+        })
+    }
+
+    pub fn hashed_uid(&self) -> Result<String> {
+        self.imp.hashed_uid()
+    }
+
+    pub fn authorization(&self, req: &Request) -> Result<String> {
+        self.imp.authorization(req)
+    }
+
+    pub fn api_endpoint(&self) -> Result<String> {
+        self.imp.api_endpoint()
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use std::cell::Cell;
+
+    struct TestFetcher<FF, FN>
+    where
+        FF: Fn() -> Result<TokenFetchResult>,
+        FN: Fn() -> SystemTime,
+    {
+        fetch: FF,
+        now: FN,
+    }
+    impl<FF, FN> TokenFetcher for TestFetcher<FF, FN>
+    where
+        FF: Fn() -> Result<TokenFetchResult>,
+        FN: Fn() -> SystemTime,
+    {
+        fn fetch_token(&self) -> Result<TokenFetchResult> {
+            (self.fetch)()
+        }
+        fn now(&self) -> SystemTime {
+            (self.now)()
+        }
+    }
+
+    fn make_tsc<FF, FN>(fetch: FF, now: FN) -> TokenProviderImpl<TestFetcher<FF, FN>>
+    where
+        FF: Fn() -> Result<TokenFetchResult>,
+        FN: Fn() -> SystemTime,
+    {
+        let fetcher: TestFetcher<FF, FN> = TestFetcher { fetch, now };
+        TokenProviderImpl::new(fetcher)
+    }
+
+    #[test]
+    fn test_endpoint() {
+        // Use a cell to avoid the closure having a mutable ref to this scope.
+        let counter: Cell<u32> = Cell::new(0);
+        let fetch = || {
+            counter.set(counter.get() + 1);
+            Ok(TokenFetchResult {
+                token: TokenserverToken {
+                    id: "id".to_string(),
+                    key: "key".to_string(),
+                    api_endpoint: "api_endpoint".to_string(),
+                    uid: 1,
+                    duration: 1000,
+                    hashed_fxa_uid: "hash".to_string(),
+                },
+                server_timestamp: ServerTimestamp(0i64),
+            })
+        };
+
+        let tsc = make_tsc(fetch, SystemTime::now);
+
+        let e = tsc.api_endpoint().expect("should work");
+        assert_eq!(e, "api_endpoint".to_string());
+        assert_eq!(counter.get(), 1);
+
+        let e2 = tsc.api_endpoint().expect("should work");
+        assert_eq!(e2, "api_endpoint".to_string());
+        // should not have re-fetched.
+        assert_eq!(counter.get(), 1);
+    }
+
+    #[test]
+    fn test_backoff() {
+        let counter: Cell<u32> = Cell::new(0);
+        let fetch = || {
+            counter.set(counter.get() + 1);
+            let when = SystemTime::now() + Duration::from_millis(10000);
+            Err(ErrorKind::BackoffError(when))
+        };
+        let now: Cell<SystemTime> = Cell::new(SystemTime::now());
+        let tsc = make_tsc(fetch, || now.get());
+
+        tsc.api_endpoint().expect_err("should bail");
+        // XXX - check error type.
+        assert_eq!(counter.get(), 1);
+        // try and get another token - should not re-fetch as backoff is still
+        // in progress.
+        tsc.api_endpoint().expect_err("should bail");
+        assert_eq!(counter.get(), 1);
+
+        // Advance the clock.
+        now.set(now.get() + Duration::new(20, 0));
+
+        // Our token fetch mock is still returning a backoff error, so we
+        // still fail, but should have re-hit the fetch function.
+        tsc.api_endpoint().expect_err("should bail");
+        assert_eq!(counter.get(), 2);
+    }
+
+    #[test]
+    fn test_validity() {
+        let counter: Cell<u32> = Cell::new(0);
+        let fetch = || {
+            counter.set(counter.get() + 1);
+            Ok(TokenFetchResult {
+                token: TokenserverToken {
+                    id: "id".to_string(),
+                    key: "key".to_string(),
+                    api_endpoint: "api_endpoint".to_string(),
+                    uid: 1,
+                    duration: 10,
+                    hashed_fxa_uid: "hash".to_string(),
+                },
+                server_timestamp: ServerTimestamp(0i64),
+            })
+        };
+        let now: Cell<SystemTime> = Cell::new(SystemTime::now());
+        let tsc = make_tsc(fetch, || now.get());
+
+        tsc.api_endpoint().expect("should get a valid token");
+        assert_eq!(counter.get(), 1);
+
+        // try and get another token - should not re-fetch as the old one
+        // remains valid.
+        tsc.api_endpoint().expect("should reuse existing token");
+        assert_eq!(counter.get(), 1);
+
+        // Advance the clock.
+        now.set(now.get() + Duration::new(20, 0));
+
+        // We should discard our token and fetch a new one.
+        tsc.api_endpoint().expect("should re-fetch");
+        assert_eq!(counter.get(), 2);
+    }
+
+    #[test]
+    fn test_server_url() {
+        assert_eq!(
+            fixup_server_url(
+                Url::parse("https://token.services.mozilla.com/1.0/sync/1.5").unwrap()
+            )
+            .as_str(),
+            "https://token.services.mozilla.com/1.0/sync/1.5"
+        );
+        assert_eq!(
+            fixup_server_url(
+                Url::parse("https://token.services.mozilla.com/1.0/sync/1.5/").unwrap()
+            )
+            .as_str(),
+            "https://token.services.mozilla.com/1.0/sync/1.5"
+        );
+        assert_eq!(
+            fixup_server_url(Url::parse("https://token.services.mozilla.com").unwrap()).as_str(),
+            "https://token.services.mozilla.com/1.0/sync/1.5"
+        );
+        assert_eq!(
+            fixup_server_url(Url::parse("https://token.services.mozilla.com/").unwrap()).as_str(),
+            "https://token.services.mozilla.com/1.0/sync/1.5"
+        );
+        assert_eq!(
+            fixup_server_url(
+                Url::parse("https://selfhosted.example.com/token/1.0/sync/1.5").unwrap()
+            )
+            .as_str(),
+            "https://selfhosted.example.com/token/1.0/sync/1.5"
+        );
+        assert_eq!(
+            fixup_server_url(
+                Url::parse("https://selfhosted.example.com/token/1.0/sync/1.5/").unwrap()
+            )
+            .as_str(),
+            "https://selfhosted.example.com/token/1.0/sync/1.5"
+        );
+        assert_eq!(
+            fixup_server_url(Url::parse("https://selfhosted.example.com/token/").unwrap()).as_str(),
+            "https://selfhosted.example.com/token/1.0/sync/1.5"
+        );
+        assert_eq!(
+            fixup_server_url(Url::parse("https://selfhosted.example.com/token").unwrap()).as_str(),
+            "https://selfhosted.example.com/token/1.0/sync/1.5"
+        );
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/sync15/client/util.rs.html b/book/rust-docs/src/sync15/client/util.rs.html new file mode 100644 index 0000000000..b3d40f0c52 --- /dev/null +++ b/book/rust-docs/src/sync15/client/util.rs.html @@ -0,0 +1,205 @@ +util.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use std::collections::{HashMap, HashSet};
+use std::sync::atomic::{AtomicU32, Ordering};
+
+/// Finds the maximum of the current value and the argument `val`, and sets the
+/// new value to the result.
+///
+/// Note: `AtomicFoo::fetch_max` is unstable, and can't really be implemented as
+/// a single atomic operation from outside the stdlib ;-;
+pub(crate) fn atomic_update_max(v: &AtomicU32, new: u32) {
+    // For loads (and the compare_exchange_weak second ordering argument) this
+    // is too strong, we could probably get away with Acquire (or maybe Relaxed
+    // because we don't need the result?). In either case, this fn isn't called
+    // from a hot spot so whatever.
+    let mut cur = v.load(Ordering::SeqCst);
+    while cur < new {
+        // we're already handling the failure case so there's no reason not to
+        // use _weak here.
+        match v.compare_exchange_weak(cur, new, Ordering::SeqCst, Ordering::SeqCst) {
+            Ok(_) => {
+                // Success.
+                break;
+            }
+            Err(new_cur) => {
+                // Interrupted, keep trying.
+                cur = new_cur
+            }
+        }
+    }
+}
+
+// Slight wrappers around the builtin methods for doing this.
+pub(crate) fn set_union(a: &HashSet<String>, b: &HashSet<String>) -> HashSet<String> {
+    a.union(b).cloned().collect()
+}
+
+pub(crate) fn set_difference(a: &HashSet<String>, b: &HashSet<String>) -> HashSet<String> {
+    a.difference(b).cloned().collect()
+}
+
+pub(crate) fn set_intersection(a: &HashSet<String>, b: &HashSet<String>) -> HashSet<String> {
+    a.intersection(b).cloned().collect()
+}
+
+pub(crate) fn partition_by_value(v: &HashMap<String, bool>) -> (HashSet<String>, HashSet<String>) {
+    let mut true_: HashSet<String> = HashSet::new();
+    let mut false_: HashSet<String> = HashSet::new();
+    for (s, val) in v {
+        if *val {
+            true_.insert(s.clone());
+        } else {
+            false_.insert(s.clone());
+        }
+    }
+    (true_, false_)
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn test_set_ops() {
+        fn hash_set(s: &[&str]) -> HashSet<String> {
+            s.iter()
+                .copied()
+                .map(ToOwned::to_owned)
+                .collect::<HashSet<_>>()
+        }
+
+        assert_eq!(
+            set_union(&hash_set(&["a", "b", "c"]), &hash_set(&["b", "d"])),
+            hash_set(&["a", "b", "c", "d"]),
+        );
+
+        assert_eq!(
+            set_difference(&hash_set(&["a", "b", "c"]), &hash_set(&["b", "d"])),
+            hash_set(&["a", "c"]),
+        );
+        assert_eq!(
+            set_intersection(&hash_set(&["a", "b", "c"]), &hash_set(&["b", "d"])),
+            hash_set(&["b"]),
+        );
+        let m: HashMap<String, bool> = [
+            ("foo", true),
+            ("bar", true),
+            ("baz", false),
+            ("quux", false),
+        ]
+        .iter()
+        .copied()
+        .map(|(a, b)| (a.to_owned(), b))
+        .collect();
+        assert_eq!(
+            partition_by_value(&m),
+            (hash_set(&["foo", "bar"]), hash_set(&["baz", "quux"])),
+        );
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/sync15/client_types.rs.html b/book/rust-docs/src/sync15/client_types.rs.html new file mode 100644 index 0000000000..e53c3f670c --- /dev/null +++ b/book/rust-docs/src/sync15/client_types.rs.html @@ -0,0 +1,259 @@ +client_types.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! This module has to be here because of some hard-to-avoid hacks done for the
+//! tabs engine... See issue #2590
+
+use crate::DeviceType;
+use serde::{Deserialize, Serialize};
+use std::collections::HashMap;
+
+/// Argument to Store::prepare_for_sync. See comment there for more info. Only
+/// really intended to be used by tabs engine.
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
+pub struct ClientData {
+    pub local_client_id: String,
+    /// A hashmap of records in the `clients` collection. Key is the id of the record in
+    /// that collection, which may or may not be the device's fxa_device_id.
+    pub recent_clients: HashMap<String, RemoteClient>,
+}
+
+/// Information about a remote client in the clients collection.
+#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
+pub struct RemoteClient {
+    pub fxa_device_id: Option<String>,
+    pub device_name: String,
+    #[serde(default)]
+    pub device_type: DeviceType,
+}
+
+#[cfg(test)]
+mod client_types_tests {
+    use super::*;
+
+    #[test]
+    fn test_remote_client() {
+        // Missing `device_type` gets DeviceType::Unknown.
+        let dt = serde_json::from_str::<RemoteClient>("{\"device_name\": \"foo\"}").unwrap();
+        assert_eq!(dt.device_type, DeviceType::Unknown);
+        // But reserializes as null.
+        assert_eq!(
+            serde_json::to_string(&dt).unwrap(),
+            "{\"fxa_device_id\":null,\"device_name\":\"foo\",\"device_type\":null}"
+        );
+
+        // explicit null is also unknown.
+        assert_eq!(
+            serde_json::from_str::<RemoteClient>(
+                "{\"device_name\": \"foo\", \"device_type\": null}",
+            )
+            .unwrap()
+            .device_type,
+            DeviceType::Unknown
+        );
+
+        // Unknown device_type string deserializes as DeviceType::Unknown.
+        let dt = serde_json::from_str::<RemoteClient>(
+            "{\"device_name\": \"foo\", \"device_type\": \"foo\"}",
+        )
+        .unwrap();
+        assert_eq!(dt.device_type, DeviceType::Unknown);
+        // The None gets re-serialized as null.
+        assert_eq!(
+            serde_json::to_string(&dt).unwrap(),
+            "{\"fxa_device_id\":null,\"device_name\":\"foo\",\"device_type\":null}"
+        );
+
+        // DeviceType::Unknown gets serialized as null.
+        let dt = RemoteClient {
+            device_name: "bar".to_string(),
+            fxa_device_id: None,
+            device_type: DeviceType::Unknown,
+        };
+        assert_eq!(
+            serde_json::to_string(&dt).unwrap(),
+            "{\"fxa_device_id\":null,\"device_name\":\"bar\",\"device_type\":null}"
+        );
+
+        // DeviceType::Desktop gets serialized as "desktop".
+        let dt = RemoteClient {
+            device_name: "bar".to_string(),
+            fxa_device_id: Some("fxa".to_string()),
+            device_type: DeviceType::Desktop,
+        };
+        assert_eq!(
+            serde_json::to_string(&dt).unwrap(),
+            "{\"fxa_device_id\":\"fxa\",\"device_name\":\"bar\",\"device_type\":\"desktop\"}"
+        );
+    }
+
+    #[test]
+    fn test_client_data() {
+        let client_data = ClientData {
+            local_client_id: "my-device".to_string(),
+            recent_clients: HashMap::from([
+                (
+                    "my-device".to_string(),
+                    RemoteClient {
+                        fxa_device_id: None,
+                        device_name: "my device".to_string(),
+                        device_type: DeviceType::Unknown,
+                    },
+                ),
+                (
+                    "device-no-tabs".to_string(),
+                    RemoteClient {
+                        fxa_device_id: None,
+                        device_name: "device with no tabs".to_string(),
+                        device_type: DeviceType::Unknown,
+                    },
+                ),
+                (
+                    "device-with-a-tab".to_string(),
+                    RemoteClient {
+                        fxa_device_id: None,
+                        device_name: "device with a tab".to_string(),
+                        device_type: DeviceType::Desktop,
+                    },
+                ),
+            ]),
+        };
+        //serialize
+        let client_data_ser = serde_json::to_string(&client_data).unwrap();
+        println!("SER: {}", client_data_ser);
+        // deserialize
+        let client_data_des: ClientData = serde_json::from_str(&client_data_ser).unwrap();
+        assert_eq!(client_data_des, client_data);
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/sync15/clients_engine/engine.rs.html b/book/rust-docs/src/sync15/clients_engine/engine.rs.html new file mode 100644 index 0000000000..213948592f --- /dev/null +++ b/book/rust-docs/src/sync15/clients_engine/engine.rs.html @@ -0,0 +1,1569 @@ +engine.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
+540
+541
+542
+543
+544
+545
+546
+547
+548
+549
+550
+551
+552
+553
+554
+555
+556
+557
+558
+559
+560
+561
+562
+563
+564
+565
+566
+567
+568
+569
+570
+571
+572
+573
+574
+575
+576
+577
+578
+579
+580
+581
+582
+583
+584
+585
+586
+587
+588
+589
+590
+591
+592
+593
+594
+595
+596
+597
+598
+599
+600
+601
+602
+603
+604
+605
+606
+607
+608
+609
+610
+611
+612
+613
+614
+615
+616
+617
+618
+619
+620
+621
+622
+623
+624
+625
+626
+627
+628
+629
+630
+631
+632
+633
+634
+635
+636
+637
+638
+639
+640
+641
+642
+643
+644
+645
+646
+647
+648
+649
+650
+651
+652
+653
+654
+655
+656
+657
+658
+659
+660
+661
+662
+663
+664
+665
+666
+667
+668
+669
+670
+671
+672
+673
+674
+675
+676
+677
+678
+679
+680
+681
+682
+683
+684
+685
+686
+687
+688
+689
+690
+691
+692
+693
+694
+695
+696
+697
+698
+699
+700
+701
+702
+703
+704
+705
+706
+707
+708
+709
+710
+711
+712
+713
+714
+715
+716
+717
+718
+719
+720
+721
+722
+723
+724
+725
+726
+727
+728
+729
+730
+731
+732
+733
+734
+735
+736
+737
+738
+739
+740
+741
+742
+743
+744
+745
+746
+747
+748
+749
+750
+751
+752
+753
+754
+755
+756
+757
+758
+759
+760
+761
+762
+763
+764
+765
+766
+767
+768
+769
+770
+771
+772
+773
+774
+775
+776
+777
+778
+779
+780
+781
+782
+783
+784
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use std::collections::{HashMap, HashSet};
+
+use crate::bso::{IncomingBso, IncomingKind, OutgoingBso, OutgoingEnvelope};
+use crate::client::{
+    CollState, CollectionKeys, CollectionUpdate, GlobalState, InfoConfiguration,
+    Sync15StorageClient,
+};
+use crate::client_types::{ClientData, RemoteClient};
+use crate::engine::CollectionRequest;
+use crate::{error::Result, Guid, KeyBundle};
+use interrupt_support::Interruptee;
+
+use super::{
+    record::{ClientRecord, CommandRecord},
+    ser::shrink_to_fit,
+    Command, CommandProcessor, CommandStatus, CLIENTS_TTL,
+};
+
+const COLLECTION_NAME: &str = "clients";
+
+/// The driver for the clients engine. Internal; split out from the `Engine`
+/// struct to make testing easier.
+struct Driver<'a> {
+    command_processor: &'a dyn CommandProcessor,
+    interruptee: &'a dyn Interruptee,
+    config: &'a InfoConfiguration,
+    recent_clients: HashMap<String, RemoteClient>,
+}
+
+impl<'a> Driver<'a> {
+    fn new(
+        command_processor: &'a dyn CommandProcessor,
+        interruptee: &'a dyn Interruptee,
+        config: &'a InfoConfiguration,
+    ) -> Driver<'a> {
+        Driver {
+            command_processor,
+            interruptee,
+            config,
+            recent_clients: HashMap::new(),
+        }
+    }
+
+    fn note_recent_client(&mut self, client: &ClientRecord) {
+        self.recent_clients.insert(client.id.clone(), client.into());
+    }
+
+    fn sync(
+        &mut self,
+        inbound: Vec<IncomingBso>,
+        should_refresh_client: bool,
+    ) -> Result<Vec<OutgoingBso>> {
+        self.interruptee.err_if_interrupted()?;
+        let outgoing_commands = self.command_processor.fetch_outgoing_commands()?;
+
+        let mut has_own_client_record = false;
+        let mut changes = Vec::new();
+
+        for bso in inbound {
+            self.interruptee.err_if_interrupted()?;
+
+            let content = bso.into_content();
+
+            let client: ClientRecord = match content.kind {
+                IncomingKind::Malformed => {
+                    log::debug!("Error unpacking record");
+                    continue;
+                }
+                IncomingKind::Tombstone => {
+                    log::debug!("Record has been deleted; skipping...");
+                    continue;
+                }
+                IncomingKind::Content(client) => client,
+            };
+
+            if client.id == self.command_processor.settings().fxa_device_id {
+                log::debug!("Found my record on the server");
+                // If we see our own client record, apply any incoming commands,
+                // remove them from the list, and reupload the record. Any
+                // commands that we don't understand also go back in the list.
+                // https://github.com/mozilla/application-services/issues/1800
+                // tracks if that's the right thing to do.
+                has_own_client_record = true;
+                let mut current_client_record = self.current_client_record();
+                for c in &client.commands {
+                    let status = match c.as_command() {
+                        Some(command) => self.command_processor.apply_incoming_command(command)?,
+                        None => CommandStatus::Unsupported,
+                    };
+                    match status {
+                        CommandStatus::Applied => {}
+                        CommandStatus::Ignored => {
+                            log::debug!("Ignored command {:?}", c);
+                        }
+                        CommandStatus::Unsupported => {
+                            log::warn!("Don't know how to apply command {:?}", c);
+                            current_client_record.commands.push(c.clone());
+                        }
+                    }
+                }
+
+                // The clients collection has a hard limit on the payload size,
+                // after which the server starts rejecting our records. Large
+                // command lists can cause us to exceed this, so we truncate
+                // the list.
+                shrink_to_fit(
+                    &mut current_client_record.commands,
+                    self.memcache_max_record_payload_size(),
+                )?;
+
+                // Add the new client record to our map of recently synced
+                // clients, so that downstream consumers like synced tabs can
+                // access them.
+                self.note_recent_client(&current_client_record);
+
+                // We periodically upload our own client record, even if it
+                // doesn't change, to keep it fresh.
+                if should_refresh_client || client != current_client_record {
+                    log::debug!("Will update our client record on the server");
+                    let envelope = OutgoingEnvelope {
+                        id: content.envelope.id,
+                        ttl: Some(CLIENTS_TTL),
+                        ..Default::default()
+                    };
+                    changes.push(OutgoingBso::from_content(envelope, current_client_record)?);
+                }
+            } else {
+                // Add the other client to our map of recently synced clients.
+                self.note_recent_client(&client);
+
+                // Bail if we don't have any outgoing commands to write into
+                // the other client's record.
+                if outgoing_commands.is_empty() {
+                    continue;
+                }
+
+                // Determine if we have new commands, that aren't already in the
+                // client's command list.
+                let current_commands: HashSet<Command> = client
+                    .commands
+                    .iter()
+                    .filter_map(|c| c.as_command())
+                    .collect();
+                let mut new_outgoing_commands = outgoing_commands
+                    .difference(&current_commands)
+                    .cloned()
+                    .collect::<Vec<_>>();
+                // Sort, to ensure deterministic ordering for tests.
+                new_outgoing_commands.sort();
+                let mut new_client = client.clone();
+                new_client
+                    .commands
+                    .extend(new_outgoing_commands.into_iter().map(CommandRecord::from));
+                if new_client.commands.len() == client.commands.len() {
+                    continue;
+                }
+
+                // Hooray, we added new commands! Make sure the record still
+                // fits in the maximum record size, or the server will reject
+                // our upload.
+                shrink_to_fit(
+                    &mut new_client.commands,
+                    self.memcache_max_record_payload_size(),
+                )?;
+
+                let envelope = OutgoingEnvelope {
+                    id: content.envelope.id,
+                    ttl: Some(CLIENTS_TTL),
+                    ..Default::default()
+                };
+                changes.push(OutgoingBso::from_content(envelope, new_client)?);
+            }
+        }
+
+        // Upload a record for our own client, if we didn't replace it already.
+        if !has_own_client_record {
+            let current_client_record = self.current_client_record();
+            self.note_recent_client(&current_client_record);
+            let envelope = OutgoingEnvelope {
+                id: Guid::new(&current_client_record.id),
+                ttl: Some(CLIENTS_TTL),
+                ..Default::default()
+            };
+            changes.push(OutgoingBso::from_content(envelope, current_client_record)?);
+        }
+
+        Ok(changes)
+    }
+
+    /// Builds a fresh client record for this device.
+    fn current_client_record(&self) -> ClientRecord {
+        let settings = self.command_processor.settings();
+        ClientRecord {
+            id: settings.fxa_device_id.clone(),
+            name: settings.device_name.clone(),
+            typ: settings.device_type,
+            commands: Vec::new(),
+            fxa_device_id: Some(settings.fxa_device_id.clone()),
+            version: None,
+            protocols: vec!["1.5".into()],
+            form_factor: None,
+            os: None,
+            app_package: None,
+            application: None,
+            device: None,
+        }
+    }
+
+    fn max_record_payload_size(&self) -> usize {
+        let payload_max = self.config.max_record_payload_bytes;
+        if payload_max <= self.config.max_post_bytes {
+            self.config.max_post_bytes.saturating_sub(4096)
+        } else {
+            payload_max
+        }
+    }
+
+    /// Collections stored in memcached ("tabs", "clients" or "meta") have a
+    /// different max size than ones stored in the normal storage server db.
+    /// In practice, the real limit here is 1M (bug 1300451 comment 40), but
+    /// there's overhead involved that is hard to calculate on the client, so we
+    /// use 512k to be safe (at the recommendation of the server team). Note
+    /// that if the server reports a lower limit (via info/configuration), we
+    /// respect that limit instead. See also bug 1403052.
+    /// XXX - the above comment is stale and refers to the world before the
+    /// move to spanner and the rust sync server.
+    fn memcache_max_record_payload_size(&self) -> usize {
+        self.max_record_payload_size().min(512 * 1024)
+    }
+}
+
+pub struct Engine<'a> {
+    pub command_processor: &'a dyn CommandProcessor,
+    pub interruptee: &'a dyn Interruptee,
+    pub recent_clients: HashMap<String, RemoteClient>,
+}
+
+impl<'a> Engine<'a> {
+    /// Creates a new clients engine that delegates to the given command
+    /// processor to apply incoming commands.
+    pub fn new<'b>(
+        command_processor: &'b dyn CommandProcessor,
+        interruptee: &'b dyn Interruptee,
+    ) -> Engine<'b> {
+        Engine {
+            command_processor,
+            interruptee,
+            recent_clients: HashMap::new(),
+        }
+    }
+
+    /// Syncs the clients collection. This works a little differently than
+    /// other collections:
+    ///
+    ///   1. It can't be disabled or declined.
+    ///   2. The sync ID and last sync time aren't meaningful, since we always
+    ///      fetch all client records on every sync. As such, the
+    ///      `LocalCollStateMachine` that we use for other engines doesn't
+    ///      apply to it.
+    ///   3. It doesn't persist state directly, but relies on the sync manager
+    ///      to persist device settings, and process commands.
+    ///   4. Failing to sync the clients collection is fatal, and aborts the
+    ///      sync.
+    ///
+    /// For these reasons, we implement this engine directly in the `sync15`
+    /// crate, and provide a specialized `sync` method instead of implementing
+    /// `sync15::Store`.
+    pub fn sync(
+        &mut self,
+        storage_client: &Sync15StorageClient,
+        global_state: &GlobalState,
+        root_sync_key: &KeyBundle,
+        should_refresh_client: bool,
+    ) -> Result<()> {
+        log::info!("Syncing collection clients");
+
+        let coll_keys = CollectionKeys::from_encrypted_payload(
+            global_state.keys.clone(),
+            global_state.keys_timestamp,
+            root_sync_key,
+        )?;
+        let coll_state = CollState {
+            config: global_state.config.clone(),
+            last_modified: global_state
+                .collections
+                .get(COLLECTION_NAME)
+                .cloned()
+                .unwrap_or_default(),
+            key: coll_keys.key_for_collection(COLLECTION_NAME).clone(),
+        };
+
+        let inbound = self.fetch_incoming(storage_client, &coll_state)?;
+
+        let mut driver = Driver::new(
+            self.command_processor,
+            self.interruptee,
+            &global_state.config,
+        );
+
+        let outgoing = driver.sync(inbound, should_refresh_client)?;
+        self.recent_clients = driver.recent_clients;
+
+        self.interruptee.err_if_interrupted()?;
+        let upload_info = CollectionUpdate::new_from_changeset(
+            storage_client,
+            &coll_state,
+            COLLECTION_NAME.into(),
+            outgoing,
+            true,
+        )?
+        .upload()?;
+
+        log::info!(
+            "Upload success ({} records success, {} records failed)",
+            upload_info.successful_ids.len(),
+            upload_info.failed_ids.len()
+        );
+
+        log::info!("Finished syncing clients");
+        Ok(())
+    }
+
+    fn fetch_incoming(
+        &self,
+        storage_client: &Sync15StorageClient,
+        coll_state: &CollState,
+    ) -> Result<Vec<IncomingBso>> {
+        // Note that, unlike other stores, we always fetch the full collection
+        // on every sync, so `inbound` will return all clients, not just the
+        // ones that changed since the last sync.
+        let coll_request = CollectionRequest::new(COLLECTION_NAME.into()).full();
+
+        self.interruptee.err_if_interrupted()?;
+        let inbound = crate::client::fetch_incoming(storage_client, coll_state, coll_request)?;
+
+        Ok(inbound)
+    }
+
+    pub fn local_client_id(&self) -> String {
+        // Bit dirty but it's the easiest way to reach to our own
+        // device ID without refactoring the whole sync manager crate.
+        self.command_processor.settings().fxa_device_id.clone()
+    }
+
+    pub fn get_client_data(&self) -> ClientData {
+        ClientData {
+            local_client_id: self.local_client_id(),
+            recent_clients: self.recent_clients.clone(),
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::super::{CommandStatus, DeviceType, Settings};
+    use super::*;
+    use crate::bso::IncomingBso;
+    use anyhow::Result;
+    use interrupt_support::NeverInterrupts;
+    use serde_json::{json, Value};
+    use std::iter::zip;
+
+    struct TestProcessor {
+        settings: Settings,
+        outgoing_commands: HashSet<Command>,
+    }
+
+    impl CommandProcessor for TestProcessor {
+        fn settings(&self) -> &Settings {
+            &self.settings
+        }
+
+        fn apply_incoming_command(&self, command: Command) -> Result<CommandStatus> {
+            Ok(if let Command::Reset(name) = command {
+                if name == "forms" {
+                    CommandStatus::Unsupported
+                } else {
+                    CommandStatus::Applied
+                }
+            } else {
+                CommandStatus::Ignored
+            })
+        }
+
+        fn fetch_outgoing_commands(&self) -> Result<HashSet<Command>> {
+            Ok(self.outgoing_commands.clone())
+        }
+    }
+
+    fn inbound_from_clients(clients: Value) -> Vec<IncomingBso> {
+        if let Value::Array(clients) = clients {
+            clients
+                .into_iter()
+                .map(IncomingBso::from_test_content)
+                .collect()
+        } else {
+            unreachable!("`clients` must be an array of client records")
+        }
+    }
+
+    #[test]
+    fn test_clients_sync() {
+        let processor = TestProcessor {
+            settings: Settings {
+                fxa_device_id: "deviceAAAAAA".into(),
+                device_name: "Laptop".into(),
+                device_type: DeviceType::Desktop,
+            },
+            outgoing_commands: [
+                Command::Wipe("bookmarks".into()),
+                Command::Reset("history".into()),
+            ]
+            .iter()
+            .cloned()
+            .collect(),
+        };
+
+        let config = InfoConfiguration::default();
+
+        let mut driver = Driver::new(&processor, &NeverInterrupts, &config);
+
+        let inbound = inbound_from_clients(json!([{
+            "id": "deviceBBBBBB",
+            "name": "iPhone",
+            "type": "mobile",
+            "commands": [{
+                "command": "resetEngine",
+                "args": ["history"],
+            }],
+            "fxaDeviceId": "iPhooooooone",
+            "protocols": ["1.5"],
+            "device": "iPhone",
+        }, {
+            "id": "deviceCCCCCC",
+            "name": "Fenix",
+            "type": "mobile",
+            "commands": [],
+            "fxaDeviceId": "deviceCCCCCC",
+        }, {
+            "id": "deviceAAAAAA",
+            "name": "Laptop with a different name",
+            "type": "desktop",
+            "commands": [{
+                "command": "wipeEngine",
+                "args": ["logins"]
+            }, {
+                "command": "displayURI",
+                "args": ["http://example.com", "Fennec", "Example page"],
+                "flowID": "flooooooooow",
+            }, {
+                "command": "resetEngine",
+                "args": ["forms"],
+            }, {
+                "command": "logout",
+                "args": [],
+            }],
+            "fxaDeviceId": "deviceAAAAAA",
+        }]));
+
+        // Passing false for `should_refresh_client` - it should be ignored
+        // because we've changed the commands.
+        let mut outgoing = driver.sync(inbound, false).expect("Should sync clients");
+        outgoing.sort_by(|a, b| a.envelope.id.cmp(&b.envelope.id));
+
+        // Make sure the list of recently synced remote clients is correct.
+        let expected_ids = &["deviceAAAAAA", "deviceBBBBBB", "deviceCCCCCC"];
+        let mut actual_ids = driver.recent_clients.keys().collect::<Vec<&String>>();
+        actual_ids.sort();
+        assert_eq!(actual_ids, expected_ids);
+
+        let expected_remote_clients = &[
+            RemoteClient {
+                fxa_device_id: Some("deviceAAAAAA".to_string()),
+                device_name: "Laptop".into(),
+                device_type: DeviceType::Desktop,
+            },
+            RemoteClient {
+                fxa_device_id: Some("iPhooooooone".to_string()),
+                device_name: "iPhone".into(),
+                device_type: DeviceType::Mobile,
+            },
+            RemoteClient {
+                fxa_device_id: Some("deviceCCCCCC".to_string()),
+                device_name: "Fenix".into(),
+                device_type: DeviceType::Mobile,
+            },
+        ];
+        let actual_remote_clients = expected_ids
+            .iter()
+            .filter_map(|&id| driver.recent_clients.get(id))
+            .cloned()
+            .collect::<Vec<RemoteClient>>();
+        assert_eq!(actual_remote_clients, expected_remote_clients);
+
+        let expected = json!([{
+            "id": "deviceAAAAAA",
+            "name": "Laptop",
+            "type": "desktop",
+            "commands": [{
+                "command": "displayURI",
+                "args": ["http://example.com", "Fennec", "Example page"],
+                "flowID": "flooooooooow",
+            }, {
+                "command": "resetEngine",
+                "args": ["forms"],
+            }, {
+                "command": "logout",
+                "args": [],
+            }],
+            "fxaDeviceId": "deviceAAAAAA",
+            "protocols": ["1.5"],
+        }, {
+            "id": "deviceBBBBBB",
+            "name": "iPhone",
+            "type": "mobile",
+            "commands": [{
+                "command": "resetEngine",
+                "args": ["history"],
+            }, {
+                "command": "wipeEngine",
+                "args": ["bookmarks"],
+            }],
+            "fxaDeviceId": "iPhooooooone",
+            "protocols": ["1.5"],
+            "device": "iPhone",
+        }, {
+            "id": "deviceCCCCCC",
+            "name": "Fenix",
+            "type": "mobile",
+            "commands": [{
+                "command": "wipeEngine",
+                "args": ["bookmarks"],
+            }, {
+                "command": "resetEngine",
+                "args": ["history"],
+            }],
+            "fxaDeviceId": "deviceCCCCCC",
+        }]);
+        // turn outgoing into an incoming payload.
+        let incoming = outgoing
+            .into_iter()
+            .map(|c| OutgoingBso::to_test_incoming(&c))
+            .collect::<Vec<IncomingBso>>();
+        if let Value::Array(expected) = expected {
+            for (incoming_cleartext, exp_client) in zip(incoming, expected) {
+                let incoming_client: ClientRecord =
+                    incoming_cleartext.into_content().content().unwrap();
+                assert_eq!(incoming_client, serde_json::from_value(exp_client).unwrap());
+            }
+        } else {
+            unreachable!("`expected_clients` must be an array of client records")
+        }
+    }
+
+    #[test]
+    fn test_clients_sync_bad_incoming_record_skipped() {
+        let processor = TestProcessor {
+            settings: Settings {
+                fxa_device_id: "deviceAAAAAA".into(),
+                device_name: "Laptop".into(),
+                device_type: DeviceType::Desktop,
+            },
+            outgoing_commands: [].iter().cloned().collect(),
+        };
+
+        let config = InfoConfiguration::default();
+
+        let mut driver = Driver::new(&processor, &NeverInterrupts, &config);
+
+        let inbound = inbound_from_clients(json!([{
+            "id": "deviceBBBBBB",
+            "name": "iPhone",
+            "type": "mobile",
+            "commands": [{
+                "command": "resetEngine",
+                "args": ["history"],
+            }],
+            "fxaDeviceId": "iPhooooooone",
+            "protocols": ["1.5"],
+            "device": "iPhone",
+        }, {
+            "id": "garbage",
+            "garbage": "value",
+        }, {
+            "id": "deviceCCCCCC",
+            "deleted": true,
+            "name": "Fenix",
+            "type": "mobile",
+            "commands": [],
+            "fxaDeviceId": "deviceCCCCCC",
+        }]));
+
+        driver.sync(inbound, false).expect("Should sync clients");
+
+        // Make sure the list of recently synced remote clients is correct.
+        let expected_ids = &["deviceAAAAAA", "deviceBBBBBB"];
+        let mut actual_ids = driver.recent_clients.keys().collect::<Vec<&String>>();
+        actual_ids.sort();
+        assert_eq!(actual_ids, expected_ids);
+
+        let expected_remote_clients = &[
+            RemoteClient {
+                fxa_device_id: Some("deviceAAAAAA".to_string()),
+                device_name: "Laptop".into(),
+                device_type: DeviceType::Desktop,
+            },
+            RemoteClient {
+                fxa_device_id: Some("iPhooooooone".to_string()),
+                device_name: "iPhone".into(),
+                device_type: DeviceType::Mobile,
+            },
+        ];
+        let actual_remote_clients = expected_ids
+            .iter()
+            .filter_map(|&id| driver.recent_clients.get(id))
+            .cloned()
+            .collect::<Vec<RemoteClient>>();
+        assert_eq!(actual_remote_clients, expected_remote_clients);
+    }
+
+    #[test]
+    fn test_clients_sync_explicit_refresh() {
+        let processor = TestProcessor {
+            settings: Settings {
+                fxa_device_id: "deviceAAAAAA".into(),
+                device_name: "Laptop".into(),
+                device_type: DeviceType::Desktop,
+            },
+            outgoing_commands: [].iter().cloned().collect(),
+        };
+
+        let config = InfoConfiguration::default();
+
+        let mut driver = Driver::new(&processor, &NeverInterrupts, &config);
+
+        let test_clients = json!([{
+            "id": "deviceBBBBBB",
+            "name": "iPhone",
+            "type": "mobile",
+            "commands": [{
+                "command": "resetEngine",
+                "args": ["history"],
+            }],
+            "fxaDeviceId": "iPhooooooone",
+            "protocols": ["1.5"],
+            "device": "iPhone",
+        }, {
+            "id": "deviceAAAAAA",
+            "name": "Laptop",
+            "type": "desktop",
+            "commands": [],
+            "fxaDeviceId": "deviceAAAAAA",
+            "protocols": ["1.5"],
+        }]);
+
+        let outgoing = driver
+            .sync(inbound_from_clients(test_clients.clone()), false)
+            .expect("Should sync clients");
+        // should be no outgoing changes.
+        assert_eq!(outgoing.len(), 0);
+
+        // Make sure the list of recently synced remote clients is correct and
+        // still includes our record we didn't update.
+        let expected_ids = &["deviceAAAAAA", "deviceBBBBBB"];
+        let mut actual_ids = driver.recent_clients.keys().collect::<Vec<&String>>();
+        actual_ids.sort();
+        assert_eq!(actual_ids, expected_ids);
+
+        // Do it again - still no changes, but force a refresh.
+        let outgoing = driver
+            .sync(inbound_from_clients(test_clients), true)
+            .expect("Should sync clients");
+        assert_eq!(outgoing.len(), 1);
+
+        // Do it again - but this time with our own client record needing
+        // some change.
+        let inbound = inbound_from_clients(json!([{
+            "id": "deviceAAAAAA",
+            "name": "Laptop with New Name",
+            "type": "desktop",
+            "commands": [],
+            "fxaDeviceId": "deviceAAAAAA",
+            "protocols": ["1.5"],
+        }]));
+        let outgoing = driver.sync(inbound, false).expect("Should sync clients");
+        // should still be outgoing because the name changed.
+        assert_eq!(outgoing.len(), 1);
+    }
+
+    #[test]
+    fn test_fresh_client_record() {
+        let processor = TestProcessor {
+            settings: Settings {
+                fxa_device_id: "deviceAAAAAA".into(),
+                device_name: "Laptop".into(),
+                device_type: DeviceType::Desktop,
+            },
+            outgoing_commands: HashSet::new(),
+        };
+
+        let config = InfoConfiguration::default();
+
+        let mut driver = Driver::new(&processor, &NeverInterrupts, &config);
+
+        let clients = json!([{
+            "id": "deviceBBBBBB",
+            "name": "iPhone",
+            "type": "mobile",
+            "commands": [{
+                "command": "resetEngine",
+                "args": ["history"],
+            }],
+            "fxaDeviceId": "iPhooooooone",
+            "protocols": ["1.5"],
+            "device": "iPhone",
+        }]);
+
+        let inbound = if let Value::Array(clients) = clients {
+            clients
+                .into_iter()
+                .map(IncomingBso::from_test_content)
+                .collect()
+        } else {
+            unreachable!("`clients` must be an array of client records")
+        };
+
+        // Passing false here for should_refresh_client, but it should be
+        // ignored as we don't have an existing record yet.
+        let mut outgoing = driver.sync(inbound, false).expect("Should sync clients");
+        outgoing.sort_by(|a, b| a.envelope.id.cmp(&b.envelope.id));
+
+        // Make sure the list of recently synced remote clients is correct.
+        let expected_ids = &["deviceAAAAAA", "deviceBBBBBB"];
+        let mut actual_ids = driver.recent_clients.keys().collect::<Vec<&String>>();
+        actual_ids.sort();
+        assert_eq!(actual_ids, expected_ids);
+
+        let expected_remote_clients = &[
+            RemoteClient {
+                fxa_device_id: Some("deviceAAAAAA".to_string()),
+                device_name: "Laptop".into(),
+                device_type: DeviceType::Desktop,
+            },
+            RemoteClient {
+                fxa_device_id: Some("iPhooooooone".to_string()),
+                device_name: "iPhone".into(),
+                device_type: DeviceType::Mobile,
+            },
+        ];
+        let actual_remote_clients = expected_ids
+            .iter()
+            .filter_map(|&id| driver.recent_clients.get(id))
+            .cloned()
+            .collect::<Vec<RemoteClient>>();
+        assert_eq!(actual_remote_clients, expected_remote_clients);
+
+        let expected = json!([{
+            "id": "deviceAAAAAA",
+            "name": "Laptop",
+            "type": "desktop",
+            "fxaDeviceId": "deviceAAAAAA",
+            "protocols": ["1.5"],
+            "ttl": CLIENTS_TTL,
+        }]);
+        if let Value::Array(expected) = expected {
+            // turn outgoing into an incoming payload.
+            let incoming = outgoing
+                .into_iter()
+                .map(|c| OutgoingBso::to_test_incoming(&c))
+                .collect::<Vec<IncomingBso>>();
+            for (incoming_cleartext, record) in zip(incoming, expected) {
+                let incoming_client: ClientRecord =
+                    incoming_cleartext.into_content().content().unwrap();
+                assert_eq!(incoming_client, serde_json::from_value(record).unwrap());
+            }
+        } else {
+            unreachable!("`expected_clients` must be an array of client records")
+        }
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/sync15/clients_engine/mod.rs.html b/book/rust-docs/src/sync15/clients_engine/mod.rs.html new file mode 100644 index 0000000000..1942e6a623 --- /dev/null +++ b/book/rust-docs/src/sync15/clients_engine/mod.rs.html @@ -0,0 +1,187 @@ +mod.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! The client engine is a [crate::engine](Sync Engine) used to manage the
+//! "clients" collection. The clients engine manages the client record for
+//! "this device, and also manages "commands".
+//! In short, commands target one or more engines and instruct them to
+//! perform various operations - such as wiping all local data.
+//! These commands are used very rarely - currently the only command used
+//! in practice is for bookmarks to wipe all their data, which is sent when
+//! a desktop device restores all bookmarks from a backup. In this scenario,
+//! desktop will delete all local bookmarks then replace them with the backed
+//! up set, which without a "wipe" command would almost certainly cause other
+//! connected devices to "resurrect" the deleted bookmarks.
+use std::collections::HashSet;
+
+mod engine;
+mod record;
+mod ser;
+
+use crate::DeviceType;
+use anyhow::Result;
+pub use engine::Engine;
+
+// These are what desktop uses.
+const CLIENTS_TTL: u32 = 15_552_000; // 180 days
+pub(crate) const CLIENTS_TTL_REFRESH: u64 = 604_800; // 7 days
+
+/// A command processor applies incoming commands like wipes and resets for all
+/// stores, and returns commands to send to other clients. It also manages
+/// settings like the device name and type, which is stored in the special
+/// `clients` collection.
+///
+/// In practice, this trait only has one implementation, in the sync manager.
+/// It's split this way because the clients engine depends on internal `sync15`
+/// structures, and can't be implemented as a syncable store...but `sync15`
+/// doesn't know anything about multiple engines. This lets the sync manager
+/// provide its own implementation for handling wipe and reset commands for all
+/// the engines that it manages.
+pub trait CommandProcessor {
+    fn settings(&self) -> &Settings;
+
+    /// Fetches commands to send to other clients. An error return value means
+    /// commands couldn't be fetched, and halts the sync.
+    fn fetch_outgoing_commands(&self) -> Result<HashSet<Command>>;
+
+    /// Applies a command sent to this client from another client. This method
+    /// should return a `CommandStatus` indicating whether the command was
+    /// processed.
+    ///
+    /// An error return value means the sync manager encountered an error
+    /// applying the command, and halts the sync to prevent unexpected behavior
+    /// (for example, merging local and remote bookmarks, when we were told to
+    /// wipe our local bookmarks).
+    fn apply_incoming_command(&self, command: Command) -> Result<CommandStatus>;
+}
+
+/// Indicates if a command was applied successfully, ignored, or not supported.
+/// Applied and ignored commands are removed from our client record, and never
+/// retried. Unsupported commands are put back into our record, and retried on
+/// subsequent syncs. This is to handle clients adding support for new data
+/// types.
+#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
+pub enum CommandStatus {
+    Applied,
+    Ignored,
+    Unsupported,
+}
+
+/// Information about this device to include in its client record. This should
+/// be persisted across syncs, as part of the sync manager state.
+#[derive(Clone, Debug, Eq, Hash, PartialEq)]
+pub struct Settings {
+    /// The FxA device ID of this client, also used as this client's record ID
+    /// in the clients collection.
+    pub fxa_device_id: String,
+    /// The name of this client. This should match the client's name in the
+    /// FxA device manager.
+    pub device_name: String,
+    /// The type of this client: mobile, tablet, desktop, or other.
+    pub device_type: DeviceType,
+}
+
+#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
+pub enum Command {
+    /// Erases all local data for a specific engine.
+    Wipe(String),
+    /// Resets local sync state for all engines.
+    ResetAll,
+    /// Resets local sync state for a specific engine.
+    Reset(String),
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/sync15/clients_engine/record.rs.html b/book/rust-docs/src/sync15/clients_engine/record.rs.html new file mode 100644 index 0000000000..42279ad544 --- /dev/null +++ b/book/rust-docs/src/sync15/clients_engine/record.rs.html @@ -0,0 +1,409 @@ +record.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use serde_derive::*;
+
+use super::Command;
+
+/// The serialized form of a client record.
+#[derive(Clone, Debug, Eq, Deserialize, Hash, PartialEq, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct ClientRecord {
+    #[serde(rename = "id")]
+    pub id: String,
+
+    pub name: String,
+
+    #[serde(rename = "type")]
+    pub typ: crate::DeviceType,
+
+    #[serde(default, skip_serializing_if = "Vec::is_empty")]
+    pub commands: Vec<CommandRecord>,
+
+    #[serde(default, skip_serializing_if = "Option::is_none")]
+    pub fxa_device_id: Option<String>,
+
+    /// `version`, `protocols`, `formfactor`, `os`, `appPackage`, `application`,
+    /// and `device` are unused and optional in all implementations (Desktop,
+    /// iOS, and Fennec), but we round-trip them.
+
+    #[serde(default, skip_serializing_if = "Option::is_none")]
+    pub version: Option<String>,
+
+    #[serde(default, skip_serializing_if = "Vec::is_empty")]
+    pub protocols: Vec<String>,
+
+    #[serde(
+        default,
+        rename = "formfactor",
+        skip_serializing_if = "Option::is_none"
+    )]
+    pub form_factor: Option<String>,
+
+    #[serde(default, skip_serializing_if = "Option::is_none")]
+    pub os: Option<String>,
+
+    #[serde(default, skip_serializing_if = "Option::is_none")]
+    pub app_package: Option<String>,
+
+    #[serde(default, skip_serializing_if = "Option::is_none")]
+    pub application: Option<String>,
+
+    /// The model of the device, like "iPhone" or "iPod touch" on iOS. Note
+    /// that this is _not_ the client ID (`id`) or the FxA device ID
+    /// (`fxa_device_id`).
+    #[serde(default, skip_serializing_if = "Option::is_none")]
+    pub device: Option<String>,
+}
+
+impl From<&ClientRecord> for crate::RemoteClient {
+    fn from(record: &ClientRecord) -> crate::RemoteClient {
+        crate::RemoteClient {
+            fxa_device_id: record.fxa_device_id.clone(),
+            device_name: record.name.clone(),
+            device_type: record.typ,
+        }
+    }
+}
+
+/// The serialized form of a client command.
+#[derive(Clone, Debug, Eq, Deserialize, Hash, PartialEq, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct CommandRecord {
+    /// The command name. This is a string, not an enum, because we want to
+    /// round-trip commands that we don't support yet.
+    #[serde(rename = "command")]
+    pub name: String,
+
+    /// Extra, command-specific arguments. Note that we must send an empty
+    /// array if the command expects no arguments.
+    #[serde(default)]
+    pub args: Vec<Option<String>>,
+
+    /// Some commands, like repair, send a "flow ID" that other cliennts can
+    /// record in their telemetry. We don't currently send commands with
+    /// flow IDs, but we round-trip them.
+    #[serde(default, rename = "flowID", skip_serializing_if = "Option::is_none")]
+    pub flow_id: Option<String>,
+}
+
+impl CommandRecord {
+    // In an ideal future we'd treat non-string args as "soft errors" rather than a hard
+    // serde failure, but there's no evidence we actually see these. There *is* evidence of
+    // seeing nulls instead of strings though (presumably due to old sendTab commands), so we
+    // do handle that.
+    fn get_single_string_arg(&self) -> Option<String> {
+        let cmd_name = &self.name;
+        if self.args.len() == 1 {
+            match &self.args[0] {
+                Some(name) => Some(name.into()),
+                None => {
+                    log::error!("Incoming '{cmd_name}' command has null argument");
+                    None
+                }
+            }
+        } else {
+            log::error!(
+                "Incoming '{cmd_name}' command has wrong number of arguments ({})",
+                self.args.len()
+            );
+            None
+        }
+    }
+
+    /// Converts a serialized command into one that we can apply. Returns `None`
+    /// if we don't support the command.
+    pub fn as_command(&self) -> Option<Command> {
+        match self.name.as_str() {
+            "wipeEngine" => self.get_single_string_arg().map(Command::Wipe),
+            "resetEngine" => self.get_single_string_arg().map(Command::Reset),
+            "resetAll" => {
+                if self.args.is_empty() {
+                    Some(Command::ResetAll)
+                } else {
+                    log::error!("Invalid arguments for 'resetAll' command");
+                    None
+                }
+            }
+            // Note callers are expected to log on an unknown command.
+            _ => None,
+        }
+    }
+}
+
+impl From<Command> for CommandRecord {
+    fn from(command: Command) -> CommandRecord {
+        match command {
+            Command::Wipe(engine) => CommandRecord {
+                name: "wipeEngine".into(),
+                args: vec![Some(engine)],
+                flow_id: None,
+            },
+            Command::Reset(engine) => CommandRecord {
+                name: "resetEngine".into(),
+                args: vec![Some(engine)],
+                flow_id: None,
+            },
+            Command::ResetAll => CommandRecord {
+                name: "resetAll".into(),
+                args: Vec::new(),
+                flow_id: None,
+            },
+        }
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn test_valid_commands() {
+        let ser = serde_json::json!({"command": "wipeEngine", "args": ["foo"]});
+        let record: CommandRecord = serde_json::from_value(ser).unwrap();
+        assert_eq!(record.as_command(), Some(Command::Wipe("foo".to_string())));
+
+        let ser = serde_json::json!({"command": "resetEngine", "args": ["foo"]});
+        let record: CommandRecord = serde_json::from_value(ser).unwrap();
+        assert_eq!(record.as_command(), Some(Command::Reset("foo".to_string())));
+
+        let ser = serde_json::json!({"command": "resetAll"});
+        let record: CommandRecord = serde_json::from_value(ser).unwrap();
+        assert_eq!(record.as_command(), Some(Command::ResetAll));
+    }
+
+    #[test]
+    fn test_unknown_command() {
+        let ser = serde_json::json!({"command": "unknown", "args": ["foo", "bar"]});
+        let record: CommandRecord = serde_json::from_value(ser).unwrap();
+        assert_eq!(record.as_command(), None);
+    }
+
+    #[test]
+    fn test_bad_args() {
+        let ser = serde_json::json!({"command": "wipeEngine", "args": ["foo", "bar"]});
+        let record: CommandRecord = serde_json::from_value(ser).unwrap();
+        assert_eq!(record.as_command(), None);
+
+        let ser = serde_json::json!({"command": "wipeEngine"});
+        let record: CommandRecord = serde_json::from_value(ser).unwrap();
+        assert_eq!(record.as_command(), None);
+
+        let ser = serde_json::json!({"command": "resetAll", "args": ["foo"]});
+        let record: CommandRecord = serde_json::from_value(ser).unwrap();
+        assert_eq!(record.as_command(), None);
+    }
+
+    #[test]
+    fn test_null_args() {
+        let ser = serde_json::json!({"command": "unknown", "args": ["foo", null]});
+        let record: CommandRecord = serde_json::from_value(ser).unwrap();
+        assert_eq!(record.as_command(), None);
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/sync15/clients_engine/ser.rs.html b/book/rust-docs/src/sync15/clients_engine/ser.rs.html new file mode 100644 index 0000000000..414765e503 --- /dev/null +++ b/book/rust-docs/src/sync15/clients_engine/ser.rs.html @@ -0,0 +1,251 @@ +ser.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::error::Result;
+use serde::Serialize;
+use std::io::{self, Write};
+
+/// A writer that counts the number of bytes it's asked to write, and discards
+/// the data. Used to calculate the serialized size of the commands list.
+#[derive(Clone, Copy, Default)]
+pub struct WriteCount(usize);
+
+impl WriteCount {
+    #[inline]
+    pub fn len(self) -> usize {
+        self.0
+    }
+}
+
+impl Write for WriteCount {
+    #[inline]
+    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+        self.0 += buf.len();
+        Ok(buf.len())
+    }
+
+    #[inline]
+    fn flush(&mut self) -> io::Result<()> {
+        Ok(())
+    }
+}
+
+/// Returns the size of the given value, in bytes, when serialized to JSON.
+fn compute_serialized_size<T: Serialize>(value: &T) -> Result<usize> {
+    let mut w = WriteCount::default();
+    serde_json::to_writer(&mut w, value)?;
+    Ok(w.len())
+}
+
+/// Truncates `list` to fit within `payload_size_max_bytes` when serialized to
+/// JSON.
+pub fn shrink_to_fit<T: Serialize>(list: &mut Vec<T>, payload_size_max_bytes: usize) -> Result<()> {
+    let size = compute_serialized_size(&list)?;
+    // See bug 535326 comment 8 for an explanation of the estimation
+    match ((payload_size_max_bytes / 4) * 3).checked_sub(1500) {
+        Some(max_serialized_size) => {
+            if size > max_serialized_size {
+                // Estimate a little more than the direct fraction to maximize packing
+                let cutoff = (list.len() * max_serialized_size - 1) / size + 1;
+                list.truncate(cutoff + 1);
+                // Keep dropping off the last entry until the data fits.
+                while compute_serialized_size(&list)? > max_serialized_size {
+                    if list.pop().is_none() {
+                        break;
+                    }
+                }
+            }
+            Ok(())
+        }
+        None => {
+            list.clear();
+            Ok(())
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::super::record::CommandRecord;
+    use super::*;
+
+    #[test]
+    fn test_compute_serialized_size() {
+        assert_eq!(compute_serialized_size(&1).unwrap(), 1);
+        assert_eq!(compute_serialized_size(&"hi").unwrap(), 4);
+        assert_eq!(
+            compute_serialized_size(&["hi", "hello", "bye"]).unwrap(),
+            20
+        );
+    }
+
+    #[test]
+    fn test_shrink_to_fit() {
+        let mut commands = vec![
+            CommandRecord {
+                name: "wipeEngine".into(),
+                args: vec![Some("bookmarks".into())],
+                flow_id: Some("flow".into()),
+            },
+            CommandRecord {
+                name: "resetEngine".into(),
+                args: vec![Some("history".into())],
+                flow_id: Some("flow".into()),
+            },
+            CommandRecord {
+                name: "logout".into(),
+                args: Vec::new(),
+                flow_id: None,
+            },
+        ];
+
+        // 4096 bytes is enough to fit all three commands.
+        shrink_to_fit(&mut commands, 4096).unwrap();
+        assert_eq!(commands.len(), 3);
+
+        let sizes = commands
+            .iter()
+            .map(|c| compute_serialized_size(c).unwrap())
+            .collect::<Vec<_>>();
+        assert_eq!(sizes, &[61, 60, 30]);
+
+        // `logout` won't fit within 2168 bytes.
+        shrink_to_fit(&mut commands, 2168).unwrap();
+        assert_eq!(commands.len(), 2);
+
+        // `resetEngine` won't fit within 2084 bytes.
+        shrink_to_fit(&mut commands, 2084).unwrap();
+        assert_eq!(commands.len(), 1);
+
+        // `wipeEngine` won't fit at all.
+        shrink_to_fit(&mut commands, 1024).unwrap();
+        assert!(commands.is_empty());
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/sync15/device_type.rs.html b/book/rust-docs/src/sync15/device_type.rs.html new file mode 100644 index 0000000000..18cb74a2da --- /dev/null +++ b/book/rust-docs/src/sync15/device_type.rs.html @@ -0,0 +1,295 @@ +device_type.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! This type is strictly owned by FxA, but is defined in this crate because of
+//! some hard-to-avoid hacks done for the tabs engine... See issue #2590.
+//!
+//! Thus, fxa-client ends up taking a dep on this crate, which is roughly
+//! the opposite of reality.
+
+use serde::{Deserialize, Deserializer, Serialize, Serializer};
+
+/// Enumeration for the different types of device.
+///
+/// Firefox Accounts and the broader Sync universe separates devices into broad categories for
+/// various purposes, such as distinguishing a desktop PC from a mobile phone.
+///
+/// A special variant in this enum, `DeviceType::Unknown` is used to capture
+/// the string values we don't recognise. It also has a custom serde serializer and deserializer
+/// which implements the following semantics:
+/// * deserializing a `DeviceType` which uses a string value we don't recognise or null will return
+///   `DeviceType::Unknown` rather than returning an error.
+/// * serializing `DeviceType::Unknown` will serialize `null`.
+///
+/// This has a few important implications:
+/// * In general, `Option<DeviceType>` should be avoided, and a plain `DeviceType` used instead,
+///   because in that case, `None` would be semantically identical to `DeviceType::Unknown` and
+///   as mentioned above, `null` already deserializes as `DeviceType::Unknown`.
+/// * Any unknown device types can not be round-tripped via this enum - eg, if you deserialize
+///   a struct holding a `DeviceType` string value we don't recognize, then re-serialize it, the
+///   original string value is lost. We don't consider this a problem because in practice, we only
+///   upload records with *this* device's type, not the type of other devices, and it's reasonable
+///   to assume that this module knows about all valid device types for the device type it is
+///   deployed on.
+#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
+pub enum DeviceType {
+    Desktop,
+    Mobile,
+    Tablet,
+    VR,
+    TV,
+    // See docstrings above re how Unknown is serialized and deserialized.
+    #[default]
+    Unknown,
+}
+
+impl<'de> Deserialize<'de> for DeviceType {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        Ok(match String::deserialize(deserializer) {
+            Ok(s) => match s.as_str() {
+                "desktop" => DeviceType::Desktop,
+                "mobile" => DeviceType::Mobile,
+                "tablet" => DeviceType::Tablet,
+                "vr" => DeviceType::VR,
+                "tv" => DeviceType::TV,
+                // There's a vague possibility that desktop might serialize "phone" for mobile
+                // devices - https://searchfox.org/mozilla-central/rev/a156a65ced2dae5913ae35a68e9445b8ee7ca457/services/sync/modules/engines/clients.js#292
+                "phone" => DeviceType::Mobile,
+                // Everything else is Unknown.
+                _ => DeviceType::Unknown,
+            },
+            // Anything other than a string is "unknown" - this isn't ideal - we really only want
+            // to handle null and, eg, a number probably should be an error, but meh.
+            Err(_) => DeviceType::Unknown,
+        })
+    }
+}
+impl Serialize for DeviceType {
+    fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        match self {
+            // It's unfortunate we need to duplicate the strings here...
+            DeviceType::Desktop => s.serialize_unit_variant("DeviceType", 0, "desktop"),
+            DeviceType::Mobile => s.serialize_unit_variant("DeviceType", 1, "mobile"),
+            DeviceType::Tablet => s.serialize_unit_variant("DeviceType", 2, "tablet"),
+            DeviceType::VR => s.serialize_unit_variant("DeviceType", 3, "vr"),
+            DeviceType::TV => s.serialize_unit_variant("DeviceType", 4, "tv"),
+            // This is the important bit - Unknown -> None
+            DeviceType::Unknown => s.serialize_none(),
+        }
+    }
+}
+
+#[cfg(test)]
+mod device_type_tests {
+    use super::*;
+
+    #[test]
+    fn test_serde_ser() {
+        assert_eq!(
+            serde_json::to_string(&DeviceType::Desktop).unwrap(),
+            "\"desktop\""
+        );
+        assert_eq!(
+            serde_json::to_string(&DeviceType::Mobile).unwrap(),
+            "\"mobile\""
+        );
+        assert_eq!(
+            serde_json::to_string(&DeviceType::Tablet).unwrap(),
+            "\"tablet\""
+        );
+        assert_eq!(serde_json::to_string(&DeviceType::VR).unwrap(), "\"vr\"");
+        assert_eq!(serde_json::to_string(&DeviceType::TV).unwrap(), "\"tv\"");
+        assert_eq!(serde_json::to_string(&DeviceType::Unknown).unwrap(), "null");
+    }
+
+    #[test]
+    fn test_serde_de() {
+        assert!(matches!(
+            serde_json::from_str::<DeviceType>("\"desktop\"").unwrap(),
+            DeviceType::Desktop
+        ));
+        assert!(matches!(
+            serde_json::from_str::<DeviceType>("\"mobile\"").unwrap(),
+            DeviceType::Mobile
+        ));
+        assert!(matches!(
+            serde_json::from_str::<DeviceType>("\"tablet\"").unwrap(),
+            DeviceType::Tablet
+        ));
+        assert!(matches!(
+            serde_json::from_str::<DeviceType>("\"vr\"").unwrap(),
+            DeviceType::VR
+        ));
+        assert!(matches!(
+            serde_json::from_str::<DeviceType>("\"tv\"").unwrap(),
+            DeviceType::TV
+        ));
+        assert!(matches!(
+            serde_json::from_str::<DeviceType>("\"something-else\"").unwrap(),
+            DeviceType::Unknown,
+        ));
+        assert!(matches!(
+            serde_json::from_str::<DeviceType>("null").unwrap(),
+            DeviceType::Unknown,
+        ));
+        assert!(matches!(
+            serde_json::from_str::<DeviceType>("99").unwrap(),
+            DeviceType::Unknown,
+        ));
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/sync15/enc_payload.rs.html b/book/rust-docs/src/sync15/enc_payload.rs.html new file mode 100644 index 0000000000..f5af4b0a5d --- /dev/null +++ b/book/rust-docs/src/sync15/enc_payload.rs.html @@ -0,0 +1,221 @@ +enc_payload.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::error;
+use crate::key_bundle::KeyBundle;
+use lazy_static::lazy_static;
+use serde::{Deserialize, Serialize};
+
+/// A representation of an encrypted payload. Used as the payload in EncryptedBso and
+/// also anywhere else the sync keys might be used to encrypt/decrypt, such as send-tab payloads.
+#[derive(Deserialize, Serialize, Clone, Debug)]
+pub struct EncryptedPayload {
+    #[serde(rename = "IV")]
+    pub iv: String,
+    pub hmac: String,
+    pub ciphertext: String,
+}
+
+impl EncryptedPayload {
+    #[inline]
+    pub fn serialized_len(&self) -> usize {
+        (*EMPTY_ENCRYPTED_PAYLOAD_SIZE) + self.ciphertext.len() + self.hmac.len() + self.iv.len()
+    }
+
+    pub fn decrypt(&self, key: &KeyBundle) -> error::Result<String> {
+        key.decrypt(&self.ciphertext, &self.iv, &self.hmac)
+    }
+
+    pub fn decrypt_into<T>(&self, key: &KeyBundle) -> error::Result<T>
+    where
+        for<'a> T: Deserialize<'a>,
+    {
+        Ok(serde_json::from_str(&self.decrypt(key)?)?)
+    }
+
+    pub fn from_cleartext(key: &KeyBundle, cleartext: String) -> error::Result<Self> {
+        let (enc_base64, iv_base64, hmac_base16) =
+            key.encrypt_bytes_rand_iv(cleartext.as_bytes())?;
+        Ok(EncryptedPayload {
+            iv: iv_base64,
+            hmac: hmac_base16,
+            ciphertext: enc_base64,
+        })
+    }
+
+    pub fn from_cleartext_payload<T: Serialize>(
+        key: &KeyBundle,
+        cleartext_payload: &T,
+    ) -> error::Result<Self> {
+        Self::from_cleartext(key, serde_json::to_string(cleartext_payload)?)
+    }
+}
+
+// Our "postqueue", which chunks records for upload, needs to know this value.
+// It's tricky to determine at compile time, so do it once at at runtime.
+lazy_static! {
+    // The number of bytes taken up by padding in a EncryptedPayload.
+    static ref EMPTY_ENCRYPTED_PAYLOAD_SIZE: usize = serde_json::to_string(
+        &EncryptedPayload { iv: "".into(), hmac: "".into(), ciphertext: "".into() }
+    ).unwrap().len();
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use serde_json::json;
+
+    #[derive(Serialize, Deserialize, Debug)]
+    struct TestStruct {
+        id: String,
+        age: u32,
+        meta: String,
+    }
+
+    #[test]
+    fn test_roundtrip_crypt_record() {
+        let key = KeyBundle::new_random().unwrap();
+        let payload_json = json!({ "id": "aaaaaaaaaaaa", "age": 105, "meta": "data" });
+        let payload =
+            EncryptedPayload::from_cleartext(&key, serde_json::to_string(&payload_json).unwrap())
+                .unwrap();
+
+        let record = payload.decrypt_into::<TestStruct>(&key).unwrap();
+        assert_eq!(record.id, "aaaaaaaaaaaa");
+        assert_eq!(record.age, 105);
+        assert_eq!(record.meta, "data");
+
+        // While we're here, check on EncryptedPayload::serialized_len
+        let val_rec = serde_json::to_string(&serde_json::to_value(&payload).unwrap()).unwrap();
+        assert_eq!(payload.serialized_len(), val_rec.len());
+    }
+
+    #[test]
+    fn test_record_bad_hmac() {
+        let key1 = KeyBundle::new_random().unwrap();
+        let json = json!({ "id": "aaaaaaaaaaaa", "deleted": true, });
+
+        let payload =
+            EncryptedPayload::from_cleartext(&key1, serde_json::to_string(&json).unwrap()).unwrap();
+
+        let key2 = KeyBundle::new_random().unwrap();
+        let e = payload
+            .decrypt(&key2)
+            .expect_err("Should fail because wrong keybundle");
+
+        // Note: ErrorKind isn't PartialEq, so.
+        assert!(matches!(e, error::Error::CryptoError(_)));
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/sync15/engine/bridged_engine.rs.html b/book/rust-docs/src/sync15/engine/bridged_engine.rs.html new file mode 100644 index 0000000000..f10fa00cbb --- /dev/null +++ b/book/rust-docs/src/sync15/engine/bridged_engine.rs.html @@ -0,0 +1,485 @@ +bridged_engine.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::{telemetry, ServerTimestamp};
+use anyhow::Result;
+
+use crate::bso::{IncomingBso, OutgoingBso};
+use crate::Guid;
+
+use super::{CollSyncIds, EngineSyncAssociation, SyncEngine};
+
+/// A BridgedEngine acts as a bridge between application-services, rust
+/// implemented sync engines and sync engines as defined by Desktop Firefox.
+///
+/// [Desktop Firefox has an abstract implementation of a Sync
+/// Engine](https://searchfox.org/mozilla-central/source/services/sync/modules/engines.js)
+/// with a number of functions each engine is expected to override. Engines
+/// implemented in Rust use a different shape (specifically, the
+/// [SyncEngine](crate::SyncEngine) trait), so this BridgedEngine trait adapts
+/// between the 2.
+pub trait BridgedEngine: Send + Sync {
+    /// Returns the last sync time, in milliseconds, for this engine's
+    /// collection. This is called before each sync, to determine the lower
+    /// bound for new records to fetch from the server.
+    fn last_sync(&self) -> Result<i64>;
+
+    /// Sets the last sync time, in milliseconds. This is called throughout
+    /// the sync, to fast-forward the stored last sync time to match the
+    /// timestamp on the uploaded records.
+    fn set_last_sync(&self, last_sync_millis: i64) -> Result<()>;
+
+    /// Returns the sync ID for this engine's collection. This is only used in
+    /// tests.
+    fn sync_id(&self) -> Result<Option<String>>;
+
+    /// Resets the sync ID for this engine's collection, returning the new ID.
+    /// As a side effect, implementations should reset all local Sync state,
+    /// as in `reset`.
+    /// (Note that bridged engines never maintain the "global" guid - that's all managed
+    /// by the bridged_engine consumer (ie, desktop). bridged_engines only care about
+    /// the per-collection one.)
+    fn reset_sync_id(&self) -> Result<String>;
+
+    /// Ensures that the locally stored sync ID for this engine's collection
+    /// matches the `new_sync_id` from the server. If the two don't match,
+    /// implementations should reset all local Sync state, as in `reset`.
+    /// This method returns the assigned sync ID, which can be either the
+    /// `new_sync_id`, or a different one if the engine wants to force other
+    /// devices to reset their Sync state for this collection the next time they
+    /// sync.
+    fn ensure_current_sync_id(&self, new_sync_id: &str) -> Result<String>;
+
+    /// Tells the tabs engine about recent FxA devices. A bit of a leaky abstration as it only
+    /// makes sense for tabs.
+    /// The arg is a json serialized `ClientData` struct.
+    fn prepare_for_sync(&self, _client_data: &str) -> Result<()> {
+        Ok(())
+    }
+
+    /// Indicates that the engine is about to start syncing. This is called
+    /// once per sync, and always before `store_incoming`.
+    fn sync_started(&self) -> Result<()>;
+
+    /// Stages a batch of incoming Sync records. This is called multiple
+    /// times per sync, once for each batch. Implementations can use the
+    /// signal to check if the operation was aborted, and cancel any
+    /// pending work.
+    fn store_incoming(&self, incoming_records: Vec<IncomingBso>) -> Result<()>;
+
+    /// Applies all staged records, reconciling changes on both sides and
+    /// resolving conflicts. Returns a list of records to upload.
+    fn apply(&self) -> Result<ApplyResults>;
+
+    /// Indicates that the given record IDs were uploaded successfully to the
+    /// server. This is called multiple times per sync, once for each batch
+    /// upload.
+    fn set_uploaded(&self, server_modified_millis: i64, ids: &[Guid]) -> Result<()>;
+
+    /// Indicates that all records have been uploaded. At this point, any record
+    /// IDs marked for upload that haven't been passed to `set_uploaded`, can be
+    /// assumed to have failed: for example, because the server rejected a record
+    /// with an invalid TTL or sort index.
+    fn sync_finished(&self) -> Result<()>;
+
+    /// Resets all local Sync state, including any change flags, mirrors, and
+    /// the last sync time, such that the next sync is treated as a first sync
+    /// with all new local data. Does not erase any local user data.
+    fn reset(&self) -> Result<()>;
+
+    /// Erases all local user data for this collection, and any Sync metadata.
+    /// This method is destructive, and unused for most collections.
+    fn wipe(&self) -> Result<()>;
+}
+
+// This is an adaptor trait - the idea is that engines can implement this
+// trait along with SyncEngine and get a BridgedEngine for free. It's temporary
+// so we can land this trait without needing to update desktop.
+// Longer term, we should remove both this trait and BridgedEngine entirely, sucking up
+// the breaking change for desktop. The main blocker to this is moving desktop away
+// from the explicit timestamp handling and moving closer to the `get_collection_request`
+// model.
+pub trait BridgedEngineAdaptor: Send + Sync {
+    // These are the main mismatches between the 2 engines
+    fn last_sync(&self) -> Result<i64>;
+    fn set_last_sync(&self, last_sync_millis: i64) -> Result<()>;
+    fn sync_started(&self) -> Result<()> {
+        Ok(())
+    }
+
+    fn engine(&self) -> &dyn SyncEngine;
+}
+
+impl<A: BridgedEngineAdaptor> BridgedEngine for A {
+    fn last_sync(&self) -> Result<i64> {
+        self.last_sync()
+    }
+
+    fn set_last_sync(&self, last_sync_millis: i64) -> Result<()> {
+        self.set_last_sync(last_sync_millis)
+    }
+
+    fn sync_id(&self) -> Result<Option<String>> {
+        Ok(match self.engine().get_sync_assoc()? {
+            EngineSyncAssociation::Disconnected => None,
+            EngineSyncAssociation::Connected(c) => Some(c.coll.into()),
+        })
+    }
+
+    fn reset_sync_id(&self) -> Result<String> {
+        // Note that bridged engines never maintain the "global" guid - that's all managed
+        // by desktop. bridged_engines only care about the per-collection one.
+        let global = Guid::empty();
+        let coll = Guid::random();
+        self.engine()
+            .reset(&EngineSyncAssociation::Connected(CollSyncIds {
+                global,
+                coll: coll.clone(),
+            }))?;
+        Ok(coll.to_string())
+    }
+
+    fn ensure_current_sync_id(&self, sync_id: &str) -> Result<String> {
+        let engine = self.engine();
+        let assoc = engine.get_sync_assoc()?;
+        if matches!(assoc, EngineSyncAssociation::Connected(c) if c.coll == sync_id) {
+            log::debug!("ensure_current_sync_id is current");
+        } else {
+            let new_coll_ids = CollSyncIds {
+                global: Guid::empty(),
+                coll: sync_id.into(),
+            };
+            engine.reset(&EngineSyncAssociation::Connected(new_coll_ids))?;
+        }
+        Ok(sync_id.to_string())
+    }
+
+    fn prepare_for_sync(&self, client_data: &str) -> Result<()> {
+        // unwrap here is unfortunate, but can hopefully go away if we can
+        // start using the ClientData type instead of the string.
+        self.engine()
+            .prepare_for_sync(&|| serde_json::from_str::<crate::ClientData>(client_data).unwrap())
+    }
+
+    fn sync_started(&self) -> Result<()> {
+        A::sync_started(self)
+    }
+
+    fn store_incoming(&self, incoming_records: Vec<IncomingBso>) -> Result<()> {
+        let engine = self.engine();
+        let mut telem = telemetry::Engine::new(engine.collection_name());
+        engine.stage_incoming(incoming_records, &mut telem)
+    }
+
+    fn apply(&self) -> Result<ApplyResults> {
+        let engine = self.engine();
+        let mut telem = telemetry::Engine::new(engine.collection_name());
+        // Desktop tells a bridged engine to apply the records without telling it
+        // the server timestamp, and once applied, explicitly calls `set_last_sync()`
+        // with that timestamp. So this adaptor needs to call apply with an invalid
+        // timestamp, and hope that later call with the correct timestamp does come.
+        // This isn't ideal as it means the timestamp is updated in a different transaction,
+        // but nothing too bad should happen if it doesn't - we'll just end up applying
+        // the same records again next sync.
+        let records = engine.apply(ServerTimestamp::from_millis(0), &mut telem)?;
+        Ok(ApplyResults {
+            records,
+            num_reconciled: telem
+                .get_incoming()
+                .as_ref()
+                .map(|i| i.get_reconciled() as usize),
+        })
+    }
+
+    fn set_uploaded(&self, millis: i64, ids: &[Guid]) -> Result<()> {
+        self.engine()
+            .set_uploaded(ServerTimestamp::from_millis(millis), ids.to_vec())
+    }
+
+    fn sync_finished(&self) -> Result<()> {
+        self.engine().sync_finished()
+    }
+
+    fn reset(&self) -> Result<()> {
+        self.engine().reset(&EngineSyncAssociation::Disconnected)
+    }
+
+    fn wipe(&self) -> Result<()> {
+        self.engine().wipe()
+    }
+}
+
+// TODO: We should see if we can remove this to reduce the number of types engines need to deal
+// with. num_reconciled is only used for telemetry on desktop.
+#[derive(Debug, Default)]
+pub struct ApplyResults {
+    /// List of records
+    pub records: Vec<OutgoingBso>,
+    /// The number of incoming records whose contents were merged because they
+    /// changed on both sides. None indicates we aren't reporting this
+    /// information.
+    pub num_reconciled: Option<usize>,
+}
+
+impl ApplyResults {
+    pub fn new(records: Vec<OutgoingBso>, num_reconciled: impl Into<Option<usize>>) -> Self {
+        Self {
+            records,
+            num_reconciled: num_reconciled.into(),
+        }
+    }
+}
+
+// Shorthand for engines that don't care.
+impl From<Vec<OutgoingBso>> for ApplyResults {
+    fn from(records: Vec<OutgoingBso>) -> Self {
+        Self {
+            records,
+            num_reconciled: None,
+        }
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/sync15/engine/mod.rs.html b/book/rust-docs/src/sync15/engine/mod.rs.html new file mode 100644 index 0000000000..17b8c367c1 --- /dev/null +++ b/book/rust-docs/src/sync15/engine/mod.rs.html @@ -0,0 +1,75 @@ +mod.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! This module is used by crates which need to implement a "sync engine".
+//! At a high-level, a "sync engine" is code which knows how to take records
+//! from a sync server, apply and reconcile them with the local data, then
+//! provide records which should be uploaded to the server.
+//!
+//! Note that the "sync engine" does not itself talk to the server, nor does
+//! it manage the state of the remote server, nor does it do any of the
+//! encryption/decryption - that is the responsbility of the "sync client", as
+//! implemented in the [client] module (or in some cases, implemented externally)
+//!
+//! There are currently 2 types of engine:
+//! * Code which implements the [crate::engine::sync_engine::SyncEngine]
+//!   trait. These are the "original" Rust engines, designed to be used with
+//!   the [crate::client](sync client)
+//! * Code which implements the [crate::engine::bridged_engine::BridgedEngine]
+//!   trait. These engines are a "bridge" between the Desktop JS Sync world and
+//!   this rust code.
+//! While these engines end up doing the same thing, the difference is due to
+//! implementation differences between the Desktop Sync client and the Rust
+//! client.
+//! We intend merging these engines - the first step will be to merge the
+//! types and payload management used by these traits, then to combine the
+//! requirements into a single trait that captures both use-cases.
+mod bridged_engine;
+mod request;
+mod sync_engine;
+
+pub use bridged_engine::{ApplyResults, BridgedEngine, BridgedEngineAdaptor};
+#[cfg(feature = "sync-client")]
+pub(crate) use request::CollectionPost;
+
+pub use request::{CollectionRequest, RequestOrder};
+pub use sync_engine::{CollSyncIds, EngineSyncAssociation, SyncEngine, SyncEngineId};
+
\ No newline at end of file diff --git a/book/rust-docs/src/sync15/engine/request.rs.html b/book/rust-docs/src/sync15/engine/request.rs.html new file mode 100644 index 0000000000..3cc920f962 --- /dev/null +++ b/book/rust-docs/src/sync15/engine/request.rs.html @@ -0,0 +1,251 @@ +request.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+use crate::{CollectionName, Guid, ServerTimestamp};
+#[derive(Debug, Default, Clone, PartialEq, Eq)]
+pub struct CollectionRequest {
+    pub collection: CollectionName,
+    pub full: bool,
+    pub ids: Option<Vec<Guid>>,
+
+    pub limit: Option<RequestLimit>,
+    pub older: Option<ServerTimestamp>,
+    pub newer: Option<ServerTimestamp>,
+}
+
+impl CollectionRequest {
+    #[inline]
+    pub fn new(collection: CollectionName) -> CollectionRequest {
+        CollectionRequest {
+            collection,
+            ..Default::default()
+        }
+    }
+
+    #[inline]
+    pub fn ids<V>(mut self, v: V) -> CollectionRequest
+    where
+        V: IntoIterator,
+        V::Item: Into<Guid>,
+    {
+        self.ids = Some(v.into_iter().map(|id| id.into()).collect());
+        self
+    }
+
+    #[inline]
+    pub fn full(mut self) -> CollectionRequest {
+        self.full = true;
+        self
+    }
+
+    #[inline]
+    pub fn older_than(mut self, ts: ServerTimestamp) -> CollectionRequest {
+        self.older = Some(ts);
+        self
+    }
+
+    #[inline]
+    pub fn newer_than(mut self, ts: ServerTimestamp) -> CollectionRequest {
+        self.newer = Some(ts);
+        self
+    }
+
+    #[inline]
+    pub fn limit(mut self, num: usize, order: RequestOrder) -> CollectionRequest {
+        self.limit = Some(RequestLimit { num, order });
+        self
+    }
+}
+
+// This is just used interally - consumers just provide the content, not request params.
+#[cfg(feature = "sync-client")]
+#[derive(Debug, Default, Clone, PartialEq, Eq)]
+pub(crate) struct CollectionPost {
+    pub collection: CollectionName,
+    pub commit: bool,
+    pub batch: Option<String>,
+}
+
+#[cfg(feature = "sync-client")]
+impl CollectionPost {
+    #[inline]
+    pub fn new(collection: CollectionName) -> Self {
+        Self {
+            collection,
+            ..Default::default()
+        }
+    }
+
+    #[inline]
+    pub fn batch(mut self, batch: Option<String>) -> Self {
+        self.batch = batch;
+        self
+    }
+
+    #[inline]
+    pub fn commit(mut self, v: bool) -> Self {
+        self.commit = v;
+        self
+    }
+}
+
+// Asking for the order of records only makes sense if you are limiting them
+// in some way - consumers don't care about the order otherwise as everything
+// is processed as a set.
+#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
+pub enum RequestOrder {
+    Oldest,
+    Newest,
+    Index,
+}
+
+impl RequestOrder {
+    #[inline]
+    pub fn as_str(self) -> &'static str {
+        match self {
+            RequestOrder::Oldest => "oldest",
+            RequestOrder::Newest => "newest",
+            RequestOrder::Index => "index",
+        }
+    }
+}
+
+impl std::fmt::Display for RequestOrder {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.write_str(self.as_str())
+    }
+}
+
+// If you specify a numerical limit you must provide the order so backfilling
+// is possible (ie, so you know which ones you got!)
+#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
+pub struct RequestLimit {
+    pub(crate) num: usize,
+    pub(crate) order: RequestOrder,
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/sync15/engine/sync_engine.rs.html b/book/rust-docs/src/sync15/engine/sync_engine.rs.html new file mode 100644 index 0000000000..2a901bd88d --- /dev/null +++ b/book/rust-docs/src/sync15/engine/sync_engine.rs.html @@ -0,0 +1,519 @@ +sync_engine.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use super::CollectionRequest;
+use crate::bso::{IncomingBso, OutgoingBso};
+use crate::client_types::ClientData;
+use crate::{telemetry, CollectionName, Guid, ServerTimestamp};
+use anyhow::Result;
+use std::fmt;
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct CollSyncIds {
+    pub global: Guid,
+    pub coll: Guid,
+}
+
+/// Defines how an engine is associated with a particular set of records
+/// on a sync storage server. It's either disconnected, or believes it is
+/// connected with a specific set of GUIDs. If the server and the engine don't
+/// agree on the exact GUIDs, the engine will assume something radical happened
+/// so it can't believe anything it thinks it knows about the state of the
+/// server (ie, it will "reset" then do a full reconcile)
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum EngineSyncAssociation {
+    /// This store is disconnected (although it may be connected in the future).
+    Disconnected,
+    /// Sync is connected, and has the following sync IDs.
+    Connected(CollSyncIds),
+}
+
+/// The concrete `SyncEngine` implementations
+#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
+pub enum SyncEngineId {
+    // Note that we've derived PartialOrd etc, which uses lexicographic ordering
+    // of the variants. We leverage that such that the higher priority engines
+    // are listed first.
+    // This order matches desktop.
+    Passwords,
+    Tabs,
+    Bookmarks,
+    Addresses,
+    CreditCards,
+    History,
+}
+
+impl SyncEngineId {
+    // Iterate over all possible engines. Note that we've made a policy decision
+    // that this should enumerate in "order" as defined by PartialCmp, and tests
+    // enforce this.
+    pub fn iter() -> impl Iterator<Item = SyncEngineId> {
+        [
+            Self::Passwords,
+            Self::Tabs,
+            Self::Bookmarks,
+            Self::Addresses,
+            Self::CreditCards,
+            Self::History,
+        ]
+        .into_iter()
+    }
+
+    // Get the string identifier for this engine.  This must match the strings in SyncEngineSelection.
+    pub fn name(&self) -> &'static str {
+        match self {
+            Self::Passwords => "passwords",
+            Self::History => "history",
+            Self::Bookmarks => "bookmarks",
+            Self::Tabs => "tabs",
+            Self::Addresses => "addresses",
+            Self::CreditCards => "creditcards",
+        }
+    }
+}
+
+impl fmt::Display for SyncEngineId {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "{}", self.name())
+    }
+}
+
+impl TryFrom<&str> for SyncEngineId {
+    type Error = String;
+
+    fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {
+        match value {
+            "passwords" => Ok(Self::Passwords),
+            "history" => Ok(Self::History),
+            "bookmarks" => Ok(Self::Bookmarks),
+            "tabs" => Ok(Self::Tabs),
+            "addresses" => Ok(Self::Addresses),
+            "creditcards" => Ok(Self::CreditCards),
+            _ => Err(value.into()),
+        }
+    }
+}
+
+/// A "sync engine" is a thing that knows how to sync. It's often implemented
+/// by a "store" (which is the generic term responsible for all storage
+/// associated with a component, including storage required for sync.)
+///
+/// The model described by this trait is that engines first "stage" sets of incoming records,
+/// then apply them returning outgoing records, then handle the success (or otherwise) of each
+/// batch as it's uploaded.
+///
+/// Staging incoming records is (or should be ;) done in batches - eg, 1000 record chunks.
+/// Some engines will "stage" these into a database temp table, while ones expecting less records
+/// might just store them in memory.
+///
+/// For outgoing records, a single vec is supplied by the engine. The sync client will use the
+/// batch facilities of the server to make multiple POST requests and commit them.
+/// Sadly it's not truly atomic (there's a batch size limit) - so the model reflects that in that
+/// the engine gets told each time a batch is committed, which might happen more than once for the
+/// supplied vec. We should upgrade this model so the engine can avoid reading every outgoing
+/// record into memory at once (ie, we should try and better reflect the upload batch model at
+/// this level)
+///
+/// Sync Engines should not assume they live for exactly one sync, so `prepare_for_sync()` should
+/// clean up any state, including staged records, from previous syncs.
+///
+/// Different engines will produce errors of different types.  To accommodate
+/// this, we force them all to return anyhow::Error.
+pub trait SyncEngine {
+    fn collection_name(&self) -> CollectionName;
+
+    /// Prepares the engine for syncing. The tabs engine currently uses this to
+    /// store the current list of clients, which it uses to look up device names
+    /// and types.
+    ///
+    /// Note that this method is only called by `sync_multiple`, and only if a
+    /// command processor is registered. In particular, `prepare_for_sync` will
+    /// not be called if the store is synced using `sync::synchronize` or
+    /// `sync_multiple::sync_multiple`. It _will_ be called if the store is
+    /// synced via the Sync Manager.
+    ///
+    /// TODO(issue #2590): This is pretty cludgey and will be hard to extend for
+    /// any case other than the tabs case. We should find another way to support
+    /// tabs...
+    fn prepare_for_sync(&self, _get_client_data: &dyn Fn() -> ClientData) -> Result<()> {
+        Ok(())
+    }
+
+    /// Tells the engine what the local encryption key is for the data managed
+    /// by the engine. This is only used by collections that store data
+    /// encrypted locally and is unrelated to the encryption used by Sync.
+    /// The intent is that for such collections, this key can be used to
+    /// decrypt local data before it is re-encrypted by Sync and sent to the
+    /// storage servers, and similarly, data from the storage servers will be
+    /// decrypted by Sync, then encrypted by the local encryption key before
+    /// being added to the local database.
+    ///
+    /// The expectation is that the key value is being maintained by the
+    /// embedding application in some secure way suitable for the environment
+    /// in which the app is running - eg, the OS "keychain". The value of the
+    /// key is implementation dependent - it is expected that the engine and
+    /// embedding application already have some external agreement about how
+    /// to generate keys and in what form they are exchanged. Finally, there's
+    /// an assumption that sync engines are short-lived and only live for a
+    /// single sync - this means that sync doesn't hold on to the key for an
+    /// extended period. In practice, all sync engines which aren't a "bridged
+    /// engine" are short lived - we might need to rethink this later if we need
+    /// engines with local encryption keys to be used on desktop.
+    ///
+    /// This will panic if called by an engine that doesn't have explicit
+    /// support for local encryption keys as that implies a degree of confusion
+    /// which shouldn't be possible to ignore.
+    fn set_local_encryption_key(&mut self, _key: &str) -> Result<()> {
+        unimplemented!("This engine does not support local encryption");
+    }
+
+    /// Stage some incoming records. This might be called multiple times in the same sync
+    /// if we fetch the incoming records in batches.
+    ///
+    /// Note there is no timestamp provided here, because the procedure for fetching in batches
+    /// means that the timestamp advancing during a batch means we must abort and start again.
+    /// The final collection timestamp after staging all records is supplied to `apply()`
+    fn stage_incoming(
+        &self,
+        inbound: Vec<IncomingBso>,
+        telem: &mut telemetry::Engine,
+    ) -> Result<()>;
+
+    /// Apply the staged records, returning outgoing records.
+    /// Ideally we would adjust this model to better support batching of outgoing records
+    /// without needing to keep them all in memory (ie, an iterator or similar?)
+    fn apply(
+        &self,
+        timestamp: ServerTimestamp,
+        telem: &mut telemetry::Engine,
+    ) -> Result<Vec<OutgoingBso>>;
+
+    /// Indicates that the given record IDs were uploaded successfully to the server.
+    /// This may be called multiple times per sync, once for each batch. Batching is determined
+    /// dynamically based on payload sizes and counts via the server's advertised limits.
+    fn set_uploaded(&self, new_timestamp: ServerTimestamp, ids: Vec<Guid>) -> Result<()>;
+
+    /// Called once the sync is finished. Not currently called if uploads fail (which
+    /// seems sad, but the other batching confusion there needs sorting out first).
+    /// Many engines will have nothing to do here, as most "post upload" work should be
+    /// done in `set_uploaded()`
+    fn sync_finished(&self) -> Result<()> {
+        Ok(())
+    }
+
+    /// The engine is responsible for building a single collection request. Engines
+    /// typically will store a lastModified timestamp and use that to build a
+    /// request saying "give me full records since that date" - however, other
+    /// engines might do something fancier. It can return None if the server timestamp
+    /// has not advanced since the last sync.
+    /// This could even later be extended to handle "backfills", and we might end up
+    /// wanting one engine to use multiple collections (eg, as a "foreign key" via guid), etc.
+    fn get_collection_request(
+        &self,
+        server_timestamp: ServerTimestamp,
+    ) -> Result<Option<CollectionRequest>>;
+
+    /// Get persisted sync IDs. If they don't match the global state we'll be
+    /// `reset()` with the new IDs.
+    fn get_sync_assoc(&self) -> Result<EngineSyncAssociation>;
+
+    /// Reset the engine (and associated store) without wiping local data,
+    /// ready for a "first sync".
+    /// `assoc` defines how this store is to be associated with sync.
+    fn reset(&self, assoc: &EngineSyncAssociation) -> Result<()>;
+
+    fn wipe(&self) -> Result<()>;
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+    use std::iter::zip;
+
+    #[test]
+    fn test_engine_priority() {
+        fn sorted(mut engines: Vec<SyncEngineId>) -> Vec<SyncEngineId> {
+            engines.sort();
+            engines
+        }
+        assert_eq!(
+            vec![SyncEngineId::Passwords, SyncEngineId::Tabs],
+            sorted(vec![SyncEngineId::Passwords, SyncEngineId::Tabs])
+        );
+        assert_eq!(
+            vec![SyncEngineId::Passwords, SyncEngineId::Tabs],
+            sorted(vec![SyncEngineId::Tabs, SyncEngineId::Passwords])
+        );
+    }
+
+    #[test]
+    fn test_engine_enum_order() {
+        let unsorted = SyncEngineId::iter().collect::<Vec<SyncEngineId>>();
+        let mut sorted = SyncEngineId::iter().collect::<Vec<SyncEngineId>>();
+        sorted.sort();
+
+        // iterating should supply identical elements in each.
+        assert!(zip(unsorted, sorted).fold(true, |acc, (a, b)| acc && (a == b)))
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/sync15/error.rs.html b/book/rust-docs/src/sync15/error.rs.html new file mode 100644 index 0000000000..8ff2ba4d00 --- /dev/null +++ b/book/rust-docs/src/sync15/error.rs.html @@ -0,0 +1,277 @@ +error.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use interrupt_support::Interrupted;
+
+/// This enum is to discriminate `StorageHttpError`, and not used as an error.
+#[cfg(feature = "sync-client")]
+#[derive(Debug, Clone)]
+pub enum ErrorResponse {
+    NotFound { route: String },
+    // 401
+    Unauthorized { route: String },
+    // 412
+    PreconditionFailed { route: String },
+    // 5XX
+    ServerError { route: String, status: u16 }, // TODO: info for "retry-after" and backoff handling etc here.
+    // Other HTTP responses.
+    RequestFailed { route: String, status: u16 },
+}
+
+pub type Result<T> = std::result::Result<T, Error>;
+
+#[derive(Debug, thiserror::Error)]
+pub enum Error {
+    #[cfg(feature = "crypto")]
+    #[error("Key {0} had wrong length, got {1}, expected {2}")]
+    BadKeyLength(&'static str, usize, usize),
+
+    #[cfg(feature = "crypto")]
+    #[error("SHA256 HMAC Mismatch error")]
+    HmacMismatch,
+
+    #[cfg(feature = "crypto")]
+    #[error("Crypto/NSS error: {0}")]
+    CryptoError(#[from] rc_crypto::Error),
+
+    #[cfg(feature = "crypto")]
+    #[error("Base64 decode error: {0}")]
+    Base64Decode(#[from] base64::DecodeError),
+
+    #[error("JSON error: {0}")]
+    JsonError(#[from] serde_json::Error),
+
+    #[error("Bad cleartext UTF8: {0}")]
+    BadCleartextUtf8(#[from] std::string::FromUtf8Error),
+
+    #[cfg(feature = "crypto")]
+    #[error("HAWK error: {0}")]
+    HawkError(#[from] rc_crypto::hawk::Error),
+
+    //
+    // Errors specific to this module.
+    //
+    #[cfg(feature = "sync-client")]
+    #[error("HTTP status {0} when requesting a token from the tokenserver")]
+    TokenserverHttpError(u16),
+
+    #[cfg(feature = "sync-client")]
+    #[error("HTTP storage error: {0:?}")]
+    StorageHttpError(ErrorResponse),
+
+    #[cfg(feature = "sync-client")]
+    #[error("Server requested backoff. Retry after {0:?}")]
+    BackoffError(std::time::SystemTime),
+
+    #[cfg(feature = "sync-client")]
+    #[error("Outgoing record is too large to upload")]
+    RecordTooLargeError,
+
+    // Do we want to record the concrete problems?
+    #[cfg(feature = "sync-client")]
+    #[error("Not all records were successfully uploaded")]
+    RecordUploadFailed,
+
+    /// Used for things like a node reassignment or an unexpected syncId
+    /// implying the app needs to "reset" its understanding of remote storage.
+    #[cfg(feature = "sync-client")]
+    #[error("The server has reset the storage for this account")]
+    StorageResetError,
+
+    #[cfg(feature = "sync-client")]
+    #[error("Unacceptable URL: {0}")]
+    UnacceptableUrl(String),
+
+    #[cfg(feature = "sync-client")]
+    #[error("Missing server timestamp header in request")]
+    MissingServerTimestamp,
+
+    #[cfg(feature = "sync-client")]
+    #[error("Unexpected server behavior during batch upload: {0}")]
+    ServerBatchProblem(&'static str),
+
+    #[cfg(feature = "sync-client")]
+    #[error("It appears some other client is also trying to setup storage; try again later")]
+    SetupRace,
+
+    #[cfg(feature = "sync-client")]
+    #[error("Client upgrade required; server storage version too new")]
+    ClientUpgradeRequired,
+
+    // This means that our global state machine needs to enter a state (such as
+    // "FreshStartNeeded", but the allowed_states don't include that state.)
+    // It typically means we are trying to do a "fast" or "read-only" sync.
+    #[cfg(feature = "sync-client")]
+    #[error("Our storage needs setting up and we can't currently do it")]
+    SetupRequired,
+
+    #[cfg(feature = "sync-client")]
+    #[error("Store error: {0}")]
+    StoreError(#[from] anyhow::Error),
+
+    #[cfg(feature = "sync-client")]
+    #[error("Network error: {0}")]
+    RequestError(#[from] viaduct::Error),
+
+    #[cfg(feature = "sync-client")]
+    #[error("Unexpected HTTP status: {0}")]
+    UnexpectedStatus(#[from] viaduct::UnexpectedStatus),
+
+    #[cfg(feature = "sync-client")]
+    #[error("URL parse error: {0}")]
+    MalformedUrl(#[from] url::ParseError),
+
+    #[error("The operation was interrupted.")]
+    Interrupted(#[from] Interrupted),
+}
+
+#[cfg(feature = "sync-client")]
+impl Error {
+    pub(crate) fn get_backoff(&self) -> Option<std::time::SystemTime> {
+        if let Error::BackoffError(time) = self {
+            Some(*time)
+        } else {
+            None
+        }
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/sync15/key_bundle.rs.html b/book/rust-docs/src/sync15/key_bundle.rs.html new file mode 100644 index 0000000000..f058e53a52 --- /dev/null +++ b/book/rust-docs/src/sync15/key_bundle.rs.html @@ -0,0 +1,463 @@ +key_bundle.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::error::{Error, Result};
+use base64::{
+    engine::general_purpose::{STANDARD, URL_SAFE_NO_PAD},
+    Engine,
+};
+use rc_crypto::{
+    aead::{self, OpeningKey, SealingKey},
+    rand,
+};
+
+#[derive(Clone, PartialEq, Eq, Hash)]
+pub struct KeyBundle {
+    enc_key: Vec<u8>,
+    mac_key: Vec<u8>,
+}
+
+impl std::fmt::Debug for KeyBundle {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.debug_struct("KeyBundle").finish()
+    }
+}
+
+impl KeyBundle {
+    /// Construct a key bundle from the already-decoded encrypt and hmac keys.
+    /// Panics (asserts) if they aren't both 32 bytes.
+    pub fn new(enc: Vec<u8>, mac: Vec<u8>) -> Result<KeyBundle> {
+        if enc.len() != 32 {
+            error_support::report_error!(
+                "sync15-key-bundle",
+                "Bad key length (enc_key): {} != 32",
+                enc.len()
+            );
+            return Err(Error::BadKeyLength("enc_key", enc.len(), 32));
+        }
+        if mac.len() != 32 {
+            error_support::report_error!(
+                "sync15-key-bundle",
+                "Bad key length (mac_key): {} != 32",
+                mac.len()
+            );
+            return Err(Error::BadKeyLength("mac_key", mac.len(), 32));
+        }
+        Ok(KeyBundle {
+            enc_key: enc,
+            mac_key: mac,
+        })
+    }
+
+    pub fn new_random() -> Result<KeyBundle> {
+        let mut buffer = [0u8; 64];
+        rand::fill(&mut buffer)?;
+        KeyBundle::from_ksync_bytes(&buffer)
+    }
+
+    pub fn from_ksync_bytes(ksync: &[u8]) -> Result<KeyBundle> {
+        if ksync.len() != 64 {
+            error_support::report_error!(
+                "sync15-key-bundle",
+                "Bad key length (kSync): {} != 64",
+                ksync.len()
+            );
+            return Err(Error::BadKeyLength("kSync", ksync.len(), 64));
+        }
+        Ok(KeyBundle {
+            enc_key: ksync[0..32].into(),
+            mac_key: ksync[32..64].into(),
+        })
+    }
+
+    pub fn from_ksync_base64(ksync: &str) -> Result<KeyBundle> {
+        let bytes = URL_SAFE_NO_PAD.decode(ksync)?;
+        KeyBundle::from_ksync_bytes(&bytes)
+    }
+
+    pub fn from_base64(enc: &str, mac: &str) -> Result<KeyBundle> {
+        let enc_bytes = STANDARD.decode(enc)?;
+        let mac_bytes = STANDARD.decode(mac)?;
+        KeyBundle::new(enc_bytes, mac_bytes)
+    }
+
+    #[inline]
+    pub fn encryption_key(&self) -> &[u8] {
+        &self.enc_key
+    }
+
+    #[inline]
+    pub fn hmac_key(&self) -> &[u8] {
+        &self.mac_key
+    }
+
+    #[inline]
+    pub fn to_b64_array(&self) -> [String; 2] {
+        [
+            STANDARD.encode(&self.enc_key),
+            STANDARD.encode(&self.mac_key),
+        ]
+    }
+
+    /// Decrypt the provided ciphertext with the given iv, and decodes the
+    /// result as a utf8 string.
+    pub fn decrypt(&self, enc_base64: &str, iv_base64: &str, hmac_base16: &str) -> Result<String> {
+        // Decode the expected_hmac into bytes to avoid issues if a client happens to encode
+        // this as uppercase. This shouldn't happen in practice, but doing it this way is more
+        // robust and avoids an allocation.
+        let mut decoded_hmac = vec![0u8; 32];
+        if base16::decode_slice(hmac_base16, &mut decoded_hmac).is_err() {
+            log::warn!("Garbage HMAC verification string: contained non base16 characters");
+            return Err(Error::HmacMismatch);
+        }
+        let iv = STANDARD.decode(iv_base64)?;
+        let ciphertext_bytes = STANDARD.decode(enc_base64)?;
+        let key_bytes = [self.encryption_key(), self.hmac_key()].concat();
+        let key = OpeningKey::new(&aead::LEGACY_SYNC_AES_256_CBC_HMAC_SHA256, &key_bytes)?;
+        let nonce = aead::Nonce::try_assume_unique_for_key(
+            &aead::LEGACY_SYNC_AES_256_CBC_HMAC_SHA256,
+            &iv,
+        )?;
+        let ciphertext_and_hmac = [ciphertext_bytes, decoded_hmac].concat();
+        let cleartext_bytes = aead::open(&key, nonce, aead::Aad::empty(), &ciphertext_and_hmac)?;
+        let cleartext = String::from_utf8(cleartext_bytes)?;
+        Ok(cleartext)
+    }
+
+    /// Encrypt using the provided IV.
+    pub fn encrypt_bytes_with_iv(
+        &self,
+        cleartext_bytes: &[u8],
+        iv: &[u8],
+    ) -> Result<(String, String)> {
+        let key_bytes = [self.encryption_key(), self.hmac_key()].concat();
+        let key = SealingKey::new(&aead::LEGACY_SYNC_AES_256_CBC_HMAC_SHA256, &key_bytes)?;
+        let nonce =
+            aead::Nonce::try_assume_unique_for_key(&aead::LEGACY_SYNC_AES_256_CBC_HMAC_SHA256, iv)?;
+        let ciphertext_and_hmac = aead::seal(&key, nonce, aead::Aad::empty(), cleartext_bytes)?;
+        let ciphertext_len = ciphertext_and_hmac.len() - key.algorithm().tag_len();
+        // Do the string conversions here so we don't have to split and copy to 2 vectors.
+        let (ciphertext, hmac_signature) = ciphertext_and_hmac.split_at(ciphertext_len);
+        let enc_base64 = STANDARD.encode(ciphertext);
+        let hmac_base16 = base16::encode_lower(&hmac_signature);
+        Ok((enc_base64, hmac_base16))
+    }
+
+    /// Generate a random iv and encrypt with it. Return both the encrypted bytes
+    /// and the generated iv.
+    pub fn encrypt_bytes_rand_iv(
+        &self,
+        cleartext_bytes: &[u8],
+    ) -> Result<(String, String, String)> {
+        let mut iv = [0u8; 16];
+        rand::fill(&mut iv)?;
+        let (enc_base64, hmac_base16) = self.encrypt_bytes_with_iv(cleartext_bytes, &iv)?;
+        let iv_base64 = STANDARD.encode(iv);
+        Ok((enc_base64, iv_base64, hmac_base16))
+    }
+
+    pub fn encrypt_with_iv(&self, cleartext: &str, iv: &[u8]) -> Result<(String, String)> {
+        self.encrypt_bytes_with_iv(cleartext.as_bytes(), iv)
+    }
+
+    pub fn encrypt_rand_iv(&self, cleartext: &str) -> Result<(String, String, String)> {
+        self.encrypt_bytes_rand_iv(cleartext.as_bytes())
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    const HMAC_B16: &str = "b1e6c18ac30deb70236bc0d65a46f7a4dce3b8b0e02cf92182b914e3afa5eebc";
+    const IV_B64: &str = "GX8L37AAb2FZJMzIoXlX8w==";
+    const HMAC_KEY_B64: &str = "MMntEfutgLTc8FlTLQFms8/xMPmCldqPlq/QQXEjx70=";
+    const ENC_KEY_B64: &str = "9K/wLdXdw+nrTtXo4ZpECyHFNr4d7aYHqeg3KW9+m6Q=";
+
+    const CIPHERTEXT_B64_PIECES: &[&str] = &[
+        "NMsdnRulLwQsVcwxKW9XwaUe7ouJk5Wn80QhbD80l0HEcZGCynh45qIbeYBik0lgcHbK",
+        "mlIxTJNwU+OeqipN+/j7MqhjKOGIlvbpiPQQLC6/ffF2vbzL0nzMUuSyvaQzyGGkSYM2",
+        "xUFt06aNivoQTvU2GgGmUK6MvadoY38hhW2LCMkoZcNfgCqJ26lO1O0sEO6zHsk3IVz6",
+        "vsKiJ2Hq6VCo7hu123wNegmujHWQSGyf8JeudZjKzfi0OFRRvvm4QAKyBWf0MgrW1F8S",
+        "FDnVfkq8amCB7NhdwhgLWbN+21NitNwWYknoEWe1m6hmGZDgDT32uxzWxCV8QqqrpH/Z",
+        "ggViEr9uMgoy4lYaWqP7G5WKvvechc62aqnsNEYhH26A5QgzmlNyvB+KPFvPsYzxDnSC",
+        "jOoRSLx7GG86wT59QZw=",
+    ];
+
+    const CLEARTEXT_B64_PIECES: &[&str] = &[
+        "eyJpZCI6IjVxUnNnWFdSSlpYciIsImhpc3RVcmkiOiJmaWxlOi8vL1VzZXJzL2phc29u",
+        "L0xpYnJhcnkvQXBwbGljYXRpb24lMjBTdXBwb3J0L0ZpcmVmb3gvUHJvZmlsZXMva3Nn",
+        "ZDd3cGsuTG9jYWxTeW5jU2VydmVyL3dlYXZlL2xvZ3MvIiwidGl0bGUiOiJJbmRleCBv",
+        "ZiBmaWxlOi8vL1VzZXJzL2phc29uL0xpYnJhcnkvQXBwbGljYXRpb24gU3VwcG9ydC9G",
+        "aXJlZm94L1Byb2ZpbGVzL2tzZ2Q3d3BrLkxvY2FsU3luY1NlcnZlci93ZWF2ZS9sb2dz",
+        "LyIsInZpc2l0cyI6W3siZGF0ZSI6MTMxOTE0OTAxMjM3MjQyNSwidHlwZSI6MX1dfQ==",
+    ];
+
+    #[test]
+    fn test_decrypt() {
+        let key_bundle = KeyBundle::from_base64(ENC_KEY_B64, HMAC_KEY_B64).unwrap();
+        let ciphertext = CIPHERTEXT_B64_PIECES.join("");
+        let s = key_bundle.decrypt(&ciphertext, IV_B64, HMAC_B16).unwrap();
+
+        let cleartext =
+            String::from_utf8(STANDARD.decode(CLEARTEXT_B64_PIECES.join("")).unwrap()).unwrap();
+        assert_eq!(&cleartext, &s);
+    }
+
+    #[test]
+    fn test_encrypt() {
+        let key_bundle = KeyBundle::from_base64(ENC_KEY_B64, HMAC_KEY_B64).unwrap();
+        let iv = STANDARD.decode(IV_B64).unwrap();
+
+        let cleartext_bytes = STANDARD.decode(CLEARTEXT_B64_PIECES.join("")).unwrap();
+        let (enc_base64, _hmac_base16) = key_bundle
+            .encrypt_bytes_with_iv(&cleartext_bytes, &iv)
+            .unwrap();
+
+        let expect_ciphertext = CIPHERTEXT_B64_PIECES.join("");
+
+        assert_eq!(&enc_base64, &expect_ciphertext);
+
+        let (enc_base64_2, iv_base64_2, hmac_base16_2) =
+            key_bundle.encrypt_bytes_rand_iv(&cleartext_bytes).unwrap();
+        assert_ne!(&enc_base64_2, &expect_ciphertext);
+
+        let s = key_bundle
+            .decrypt(&enc_base64_2, &iv_base64_2, &hmac_base16_2)
+            .unwrap();
+        assert_eq!(&cleartext_bytes, &s.as_bytes());
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/sync15/lib.rs.html b/book/rust-docs/src/sync15/lib.rs.html new file mode 100644 index 0000000000..aa5c4f4828 --- /dev/null +++ b/book/rust-docs/src/sync15/lib.rs.html @@ -0,0 +1,101 @@ +lib.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#![allow(unknown_lints, clippy::implicit_hasher)]
+#![warn(rust_2018_idioms)]
+
+pub mod bso;
+#[cfg(feature = "sync-client")]
+pub mod client;
+// Type to describe device types
+mod device_type;
+// Types to describe client records
+mod client_types;
+// Note that `clients_engine` should probably be in `sync_client`, but let's not make
+// things too nested at this stage...
+#[cfg(feature = "sync-client")]
+pub mod clients_engine;
+#[cfg(feature = "crypto")]
+mod enc_payload;
+#[cfg(feature = "sync-engine")]
+pub mod engine;
+mod error;
+#[cfg(feature = "crypto")]
+mod key_bundle;
+mod record_types;
+mod server_timestamp;
+pub mod telemetry;
+
+pub use crate::client_types::{ClientData, RemoteClient};
+pub use crate::device_type::DeviceType;
+pub use crate::error::{Error, Result};
+#[cfg(feature = "crypto")]
+pub use enc_payload::EncryptedPayload;
+#[cfg(feature = "crypto")]
+pub use key_bundle::KeyBundle;
+pub use server_timestamp::ServerTimestamp;
+pub use sync_guid::Guid;
+
+// Collection names are almost always `static, so we use a `Cow`:
+// * Either a `String` or a `'static &str` can be `.into()`'d into one of these.
+// * Cloning one with a `'static &str` is extremely cheap and doesn't allocate memory.
+pub type CollectionName = std::borrow::Cow<'static, str>;
+
+// For skip_serializing_if
+fn skip_if_default<T: PartialEq + Default>(v: &T) -> bool {
+    *v == T::default()
+}
+
+uniffi::include_scaffolding!("sync15");
+
\ No newline at end of file diff --git a/book/rust-docs/src/sync15/record_types.rs.html b/book/rust-docs/src/sync15/record_types.rs.html new file mode 100644 index 0000000000..2cb7a97a64 --- /dev/null +++ b/book/rust-docs/src/sync15/record_types.rs.html @@ -0,0 +1,103 @@ +record_types.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use serde_derive::*;
+use std::collections::HashMap;
+use sync_guid::Guid;
+
+// Known record formats.
+
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub struct MetaGlobalEngine {
+    pub version: usize,
+    #[serde(rename = "syncID")]
+    pub sync_id: Guid,
+}
+
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub struct MetaGlobalRecord {
+    #[serde(rename = "syncID")]
+    pub sync_id: Guid,
+    #[serde(rename = "storageVersion")]
+    pub storage_version: usize,
+    #[serde(default)]
+    pub engines: HashMap<String, MetaGlobalEngine>,
+    #[serde(default)]
+    pub declined: Vec<String>,
+}
+
+#[derive(Deserialize, Serialize, Clone, Debug, Eq, PartialEq)]
+pub struct CryptoKeysRecord {
+    pub id: Guid,
+    pub collection: String,
+    pub default: [String; 2],
+    pub collections: HashMap<String, [String; 2]>,
+}
+
+#[cfg(test)]
+#[test]
+fn test_deserialize_meta_global() {
+    let record = serde_json::json!({
+        "syncID": "abcd1234abcd",
+        "storageVersion": 1,
+    })
+    .to_string();
+    let r = serde_json::from_str::<MetaGlobalRecord>(&record).unwrap();
+    assert_eq!(r.sync_id, "abcd1234abcd");
+    assert_eq!(r.storage_version, 1);
+    assert_eq!(r.engines.len(), 0);
+    assert_eq!(r.declined.len(), 0);
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/sync15/server_timestamp.rs.html b/book/rust-docs/src/sync15/server_timestamp.rs.html new file mode 100644 index 0000000000..a4cd22d6e6 --- /dev/null +++ b/book/rust-docs/src/sync15/server_timestamp.rs.html @@ -0,0 +1,245 @@ +server_timestamp.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+use std::time::Duration;
+
+/// Typesafe way to manage server timestamps without accidentally mixing them up with
+/// local ones.
+#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Default)]
+pub struct ServerTimestamp(pub i64);
+
+impl ServerTimestamp {
+    pub fn from_float_seconds(ts: f64) -> Self {
+        let rf = (ts * 1000.0).round();
+        if !rf.is_finite() || rf < 0.0 || rf >= i64::max_value() as f64 {
+            error_support::report_error!("sync15-illegal-timestamp", "Illegal timestamp: {}", ts);
+            ServerTimestamp(0)
+        } else {
+            ServerTimestamp(rf as i64)
+        }
+    }
+
+    pub fn from_millis(ts: i64) -> Self {
+        // Catch it in tests, but just complain and replace with 0 otherwise.
+        debug_assert!(ts >= 0, "Bad timestamp: {}", ts);
+        if ts >= 0 {
+            Self(ts)
+        } else {
+            error_support::report_error!(
+                "sync15-illegal-timestamp",
+                "Illegal timestamp, substituting 0: {}",
+                ts
+            );
+            Self(0)
+        }
+    }
+}
+
+// This lets us use these in hyper header! blocks.
+impl std::str::FromStr for ServerTimestamp {
+    type Err = std::num::ParseFloatError;
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        let val = f64::from_str(s)?;
+        Ok(Self::from_float_seconds(val))
+    }
+}
+
+impl std::fmt::Display for ServerTimestamp {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "{}", self.0 as f64 / 1000.0)
+    }
+}
+
+impl ServerTimestamp {
+    pub const EPOCH: ServerTimestamp = ServerTimestamp(0);
+
+    /// Returns None if `other` is later than `self` (Duration may not represent
+    /// negative timespans in rust).
+    #[inline]
+    pub fn duration_since(self, other: ServerTimestamp) -> Option<Duration> {
+        let delta = self.0 - other.0;
+        if delta < 0 {
+            None
+        } else {
+            Some(Duration::from_millis(delta as u64))
+        }
+    }
+
+    /// Get the milliseconds for the timestamp.
+    #[inline]
+    pub fn as_millis(self) -> i64 {
+        self.0
+    }
+}
+
+impl serde::ser::Serialize for ServerTimestamp {
+    fn serialize<S: serde::ser::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+        serializer.serialize_f64(self.0 as f64 / 1000.0)
+    }
+}
+
+impl<'de> serde::de::Deserialize<'de> for ServerTimestamp {
+    fn deserialize<D: serde::de::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
+        f64::deserialize(d).map(Self::from_float_seconds)
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn test_server_timestamp() {
+        let t0 = ServerTimestamp(10_300_150);
+        let t1 = ServerTimestamp(10_100_050);
+        assert!(t1.duration_since(t0).is_none());
+        assert!(t0.duration_since(t1).is_some());
+        let dur = t0.duration_since(t1).unwrap();
+        assert_eq!(dur.as_secs(), 200);
+        assert_eq!(dur.subsec_nanos(), 100_000_000);
+    }
+
+    #[test]
+    fn test_serde() {
+        let ts = ServerTimestamp(123_456);
+
+        // test serialize
+        let ser = serde_json::to_string(&ts).unwrap();
+        assert_eq!("123.456".to_string(), ser);
+
+        // test deserialize of float
+        let ts: ServerTimestamp = serde_json::from_str(&ser).unwrap();
+        assert_eq!(ServerTimestamp(123_456), ts);
+
+        // test deserialize of whole number
+        let ts: ServerTimestamp = serde_json::from_str("123").unwrap();
+        assert_eq!(ServerTimestamp(123_000), ts);
+
+        // test deserialize of negative number
+        let ts: ServerTimestamp = serde_json::from_str("-123").unwrap();
+        assert_eq!(ServerTimestamp(0), ts);
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/sync15/telemetry.rs.html b/book/rust-docs/src/sync15/telemetry.rs.html new file mode 100644 index 0000000000..082b6e3133 --- /dev/null +++ b/book/rust-docs/src/sync15/telemetry.rs.html @@ -0,0 +1,1719 @@ +telemetry.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
+540
+541
+542
+543
+544
+545
+546
+547
+548
+549
+550
+551
+552
+553
+554
+555
+556
+557
+558
+559
+560
+561
+562
+563
+564
+565
+566
+567
+568
+569
+570
+571
+572
+573
+574
+575
+576
+577
+578
+579
+580
+581
+582
+583
+584
+585
+586
+587
+588
+589
+590
+591
+592
+593
+594
+595
+596
+597
+598
+599
+600
+601
+602
+603
+604
+605
+606
+607
+608
+609
+610
+611
+612
+613
+614
+615
+616
+617
+618
+619
+620
+621
+622
+623
+624
+625
+626
+627
+628
+629
+630
+631
+632
+633
+634
+635
+636
+637
+638
+639
+640
+641
+642
+643
+644
+645
+646
+647
+648
+649
+650
+651
+652
+653
+654
+655
+656
+657
+658
+659
+660
+661
+662
+663
+664
+665
+666
+667
+668
+669
+670
+671
+672
+673
+674
+675
+676
+677
+678
+679
+680
+681
+682
+683
+684
+685
+686
+687
+688
+689
+690
+691
+692
+693
+694
+695
+696
+697
+698
+699
+700
+701
+702
+703
+704
+705
+706
+707
+708
+709
+710
+711
+712
+713
+714
+715
+716
+717
+718
+719
+720
+721
+722
+723
+724
+725
+726
+727
+728
+729
+730
+731
+732
+733
+734
+735
+736
+737
+738
+739
+740
+741
+742
+743
+744
+745
+746
+747
+748
+749
+750
+751
+752
+753
+754
+755
+756
+757
+758
+759
+760
+761
+762
+763
+764
+765
+766
+767
+768
+769
+770
+771
+772
+773
+774
+775
+776
+777
+778
+779
+780
+781
+782
+783
+784
+785
+786
+787
+788
+789
+790
+791
+792
+793
+794
+795
+796
+797
+798
+799
+800
+801
+802
+803
+804
+805
+806
+807
+808
+809
+810
+811
+812
+813
+814
+815
+816
+817
+818
+819
+820
+821
+822
+823
+824
+825
+826
+827
+828
+829
+830
+831
+832
+833
+834
+835
+836
+837
+838
+839
+840
+841
+842
+843
+844
+845
+846
+847
+848
+849
+850
+851
+852
+853
+854
+855
+856
+857
+858
+859
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! Manage recording sync telemetry. Assumes some external telemetry
+//! library/code which manages submitting.
+
+use crate::error::Error;
+#[cfg(feature = "sync-client")]
+use crate::error::ErrorResponse;
+
+use std::collections::HashMap;
+use std::time;
+
+use serde::{ser, Serialize, Serializer};
+
+// A test helper, used by the many test modules below.
+#[cfg(test)]
+fn assert_json<T: ?Sized>(v: &T, expected: serde_json::Value)
+where
+    T: serde::Serialize,
+{
+    assert_eq!(
+        serde_json::to_value(v).expect("should get a value"),
+        expected
+    );
+}
+
+/// What we record for 'when' and 'took' in a telemetry record.
+#[derive(Debug, Serialize)]
+struct WhenTook {
+    when: f64,
+    #[serde(skip_serializing_if = "crate::skip_if_default")]
+    took: u64,
+}
+
+/// What we track while recording 'when' and 'took. It serializes as a WhenTook,
+/// except when .finished() hasn't been called, in which case it panics.
+#[derive(Debug)]
+enum Stopwatch {
+    Started(time::SystemTime, time::Instant),
+    Finished(WhenTook),
+}
+
+impl Default for Stopwatch {
+    fn default() -> Self {
+        Stopwatch::new()
+    }
+}
+
+impl Stopwatch {
+    fn new() -> Self {
+        Stopwatch::Started(time::SystemTime::now(), time::Instant::now())
+    }
+
+    // For tests we don't want real timestamps because we test against literals.
+    #[cfg(test)]
+    fn finished(&self) -> Self {
+        Stopwatch::Finished(WhenTook { when: 0.0, took: 0 })
+    }
+
+    #[cfg(not(test))]
+    fn finished(&self) -> Self {
+        match self {
+            Stopwatch::Started(st, si) => {
+                let std = st.duration_since(time::UNIX_EPOCH).unwrap_or_default();
+                let when = std.as_secs() as f64; // we don't want sub-sec accuracy. Do we need to write a float?
+
+                let sid = si.elapsed();
+                let took = sid.as_secs() * 1000 + (u64::from(sid.subsec_nanos()) / 1_000_000);
+                Stopwatch::Finished(WhenTook { when, took })
+            }
+            _ => {
+                unreachable!("can't finish twice");
+            }
+        }
+    }
+}
+
+impl Serialize for Stopwatch {
+    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        match self {
+            Stopwatch::Started(_, _) => Err(ser::Error::custom("StopWatch has not been finished")),
+            Stopwatch::Finished(c) => c.serialize(serializer),
+        }
+    }
+}
+
+#[cfg(test)]
+mod stopwatch_tests {
+    use super::*;
+
+    // A wrapper struct because we flatten - this struct should serialize with
+    // 'when' and 'took' keys (but with no 'sw'.)
+    #[derive(Debug, Serialize)]
+    struct WT {
+        #[serde(flatten)]
+        sw: Stopwatch,
+    }
+
+    #[test]
+    fn test_not_finished() {
+        let wt = WT {
+            sw: Stopwatch::new(),
+        };
+        serde_json::to_string(&wt).expect_err("unfinished stopwatch should fail");
+    }
+
+    #[test]
+    fn test() {
+        assert_json(
+            &WT {
+                sw: Stopwatch::Finished(WhenTook { when: 1.0, took: 1 }),
+            },
+            serde_json::json!({"when": 1.0, "took": 1}),
+        );
+        assert_json(
+            &WT {
+                sw: Stopwatch::Finished(WhenTook { when: 1.0, took: 0 }),
+            },
+            serde_json::json!({"when": 1.0}),
+        );
+    }
+}
+
+/// A generic "Event" - suitable for all kinds of pings (although this module
+/// only cares about the sync ping)
+#[derive(Debug, Serialize)]
+pub struct Event {
+    // We use static str references as we expect values to be literals.
+    object: &'static str,
+
+    method: &'static str,
+
+    // Maybe "value" should be a string?
+    #[serde(skip_serializing_if = "Option::is_none")]
+    value: Option<&'static str>,
+
+    // we expect the keys to be literals but values are real strings.
+    #[serde(skip_serializing_if = "Option::is_none")]
+    extra: Option<HashMap<&'static str, String>>,
+}
+
+impl Event {
+    pub fn new(object: &'static str, method: &'static str) -> Self {
+        assert!(object.len() <= 20);
+        assert!(method.len() <= 20);
+        Self {
+            object,
+            method,
+            value: None,
+            extra: None,
+        }
+    }
+
+    pub fn value(mut self, v: &'static str) -> Self {
+        assert!(v.len() <= 80);
+        self.value = Some(v);
+        self
+    }
+
+    pub fn extra(mut self, key: &'static str, val: String) -> Self {
+        assert!(key.len() <= 15);
+        assert!(val.len() <= 85);
+        match self.extra {
+            None => self.extra = Some(HashMap::new()),
+            Some(ref e) => assert!(e.len() < 10),
+        }
+        self.extra.as_mut().unwrap().insert(key, val);
+        self
+    }
+}
+
+#[cfg(test)]
+mod test_events {
+    use super::*;
+
+    #[test]
+    #[should_panic]
+    fn test_invalid_length_ctor() {
+        Event::new("A very long object value", "Method");
+    }
+
+    #[test]
+    #[should_panic]
+    fn test_invalid_length_extra_key() {
+        Event::new("O", "M").extra("A very long key value", "v".to_string());
+    }
+
+    #[test]
+    #[should_panic]
+    fn test_invalid_length_extra_val() {
+        let l = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
+                abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
+        Event::new("O", "M").extra("k", l.to_string());
+    }
+
+    #[test]
+    #[should_panic]
+    fn test_too_many_extras() {
+        let l = "abcdefghijk";
+        let mut e = Event::new("Object", "Method");
+        for i in 0..l.len() {
+            e = e.extra(&l[i..=i], "v".to_string());
+        }
+    }
+
+    #[test]
+    fn test_json() {
+        assert_json(
+            &Event::new("Object", "Method").value("Value"),
+            serde_json::json!({"object": "Object", "method": "Method", "value": "Value"}),
+        );
+
+        assert_json(
+            &Event::new("Object", "Method").extra("one", "one".to_string()),
+            serde_json::json!({"object": "Object",
+             "method": "Method",
+             "extra": {"one": "one"}
+            }),
+        )
+    }
+}
+
+/// A Sync failure.
+#[derive(Debug, Serialize)]
+#[serde(tag = "name")]
+pub enum SyncFailure {
+    #[serde(rename = "shutdownerror")]
+    Shutdown,
+
+    #[serde(rename = "othererror")]
+    Other { error: String },
+
+    #[serde(rename = "unexpectederror")]
+    Unexpected { error: String },
+
+    #[serde(rename = "autherror")]
+    Auth { from: &'static str },
+
+    #[serde(rename = "httperror")]
+    Http { code: u16 },
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn reprs() {
+        assert_json(
+            &SyncFailure::Shutdown,
+            serde_json::json!({"name": "shutdownerror"}),
+        );
+
+        assert_json(
+            &SyncFailure::Other {
+                error: "dunno".to_string(),
+            },
+            serde_json::json!({"name": "othererror", "error": "dunno"}),
+        );
+
+        assert_json(
+            &SyncFailure::Unexpected {
+                error: "dunno".to_string(),
+            },
+            serde_json::json!({"name": "unexpectederror", "error": "dunno"}),
+        );
+
+        assert_json(
+            &SyncFailure::Auth { from: "FxA" },
+            serde_json::json!({"name": "autherror", "from": "FxA"}),
+        );
+
+        assert_json(
+            &SyncFailure::Http { code: 500 },
+            serde_json::json!({"name": "httperror", "code": 500}),
+        );
+    }
+}
+
+/// Incoming record for an engine's sync
+#[derive(Debug, Default, Serialize)]
+pub struct EngineIncoming {
+    #[serde(skip_serializing_if = "crate::skip_if_default")]
+    applied: u32,
+
+    #[serde(skip_serializing_if = "crate::skip_if_default")]
+    failed: u32,
+
+    #[serde(rename = "newFailed")]
+    #[serde(skip_serializing_if = "crate::skip_if_default")]
+    new_failed: u32,
+
+    #[serde(skip_serializing_if = "crate::skip_if_default")]
+    reconciled: u32,
+}
+
+impl EngineIncoming {
+    pub fn new() -> Self {
+        Self {
+            ..Default::default()
+        }
+    }
+
+    // A helper used via skip_serializing_if
+    fn is_empty(inc: &Option<Self>) -> bool {
+        match inc {
+            Some(a) => a.applied == 0 && a.failed == 0 && a.new_failed == 0 && a.reconciled == 0,
+            None => true,
+        }
+    }
+
+    /// Increment the value of `applied` by `n`.
+    #[inline]
+    pub fn applied(&mut self, n: u32) {
+        self.applied += n;
+    }
+
+    /// Increment the value of `failed` by `n`.
+    #[inline]
+    pub fn failed(&mut self, n: u32) {
+        self.failed += n;
+    }
+
+    /// Increment the value of `new_failed` by `n`.
+    #[inline]
+    pub fn new_failed(&mut self, n: u32) {
+        self.new_failed += n;
+    }
+
+    /// Increment the value of `reconciled` by `n`.
+    #[inline]
+    pub fn reconciled(&mut self, n: u32) {
+        self.reconciled += n;
+    }
+
+    /// Accumulate values from another EngineIncoming - useful when dealing with
+    /// incoming batches.
+    fn accum(&mut self, other: &EngineIncoming) {
+        self.applied += other.applied;
+        self.failed += other.failed;
+        self.new_failed += other.new_failed;
+        self.reconciled += other.reconciled;
+    }
+
+    /// Get the value of `applied`. Mostly useful for testing.
+    #[inline]
+    pub fn get_applied(&self) -> u32 {
+        self.applied
+    }
+
+    /// Get the value of `failed`. Mostly useful for testing.
+    #[inline]
+    pub fn get_failed(&self) -> u32 {
+        self.failed
+    }
+
+    /// Get the value of `new_failed`. Mostly useful for testing.
+    #[inline]
+    pub fn get_new_failed(&self) -> u32 {
+        self.new_failed
+    }
+
+    /// Get the value of `reconciled`. Mostly useful for testing.
+    #[inline]
+    pub fn get_reconciled(&self) -> u32 {
+        self.reconciled
+    }
+}
+
+/// Outgoing record for an engine's sync.
+#[derive(Debug, Default, Serialize)]
+pub struct EngineOutgoing {
+    #[serde(skip_serializing_if = "crate::skip_if_default")]
+    sent: usize,
+
+    #[serde(skip_serializing_if = "crate::skip_if_default")]
+    failed: usize,
+}
+
+impl EngineOutgoing {
+    pub fn new() -> Self {
+        EngineOutgoing {
+            ..Default::default()
+        }
+    }
+
+    #[inline]
+    pub fn sent(&mut self, n: usize) {
+        self.sent += n;
+    }
+
+    #[inline]
+    pub fn failed(&mut self, n: usize) {
+        self.failed += n;
+    }
+}
+
+/// One engine's sync.
+#[derive(Debug, Serialize)]
+pub struct Engine {
+    name: String,
+
+    #[serde(flatten)]
+    when_took: Stopwatch,
+
+    #[serde(skip_serializing_if = "EngineIncoming::is_empty")]
+    incoming: Option<EngineIncoming>,
+
+    #[serde(skip_serializing_if = "Vec::is_empty")]
+    outgoing: Vec<EngineOutgoing>, // one for each batch posted.
+
+    #[serde(skip_serializing_if = "Option::is_none")]
+    #[serde(rename = "failureReason")]
+    failure: Option<SyncFailure>,
+
+    #[serde(skip_serializing_if = "Option::is_none")]
+    validation: Option<Validation>,
+}
+
+impl Engine {
+    pub fn new(name: impl Into<String>) -> Self {
+        Self {
+            name: name.into(),
+            when_took: Stopwatch::new(),
+            incoming: None,
+            outgoing: Vec::new(),
+            failure: None,
+            validation: None,
+        }
+    }
+
+    pub fn incoming(&mut self, inc: EngineIncoming) {
+        match &mut self.incoming {
+            None => self.incoming = Some(inc),
+            Some(ref mut existing) => existing.accum(&inc),
+        };
+    }
+
+    // A bit hacky as we need this to report telemetry for desktop via the bridged engine.
+    pub fn get_incoming(&self) -> &Option<EngineIncoming> {
+        &self.incoming
+    }
+
+    pub fn outgoing(&mut self, out: EngineOutgoing) {
+        self.outgoing.push(out);
+    }
+
+    pub fn failure(&mut self, err: impl Into<SyncFailure>) {
+        // Currently we take the first error, under the assumption that the
+        // first is the most important and all others stem from that.
+        let failure = err.into();
+        if self.failure.is_none() {
+            self.failure = Some(failure);
+        } else {
+            log::warn!(
+                "engine already has recorded a failure of {:?} - ignoring {:?}",
+                &self.failure,
+                &failure
+            );
+        }
+    }
+
+    pub fn validation(&mut self, v: Validation) {
+        assert!(self.validation.is_none());
+        self.validation = Some(v);
+    }
+
+    fn finished(&mut self) {
+        self.when_took = self.when_took.finished();
+    }
+}
+
+#[derive(Debug, Default, Serialize)]
+pub struct Validation {
+    version: u32,
+
+    #[serde(skip_serializing_if = "Vec::is_empty")]
+    problems: Vec<Problem>,
+
+    #[serde(skip_serializing_if = "Option::is_none")]
+    #[serde(rename = "failureReason")]
+    failure: Option<SyncFailure>,
+}
+
+impl Validation {
+    pub fn with_version(version: u32) -> Validation {
+        Validation {
+            version,
+            ..Validation::default()
+        }
+    }
+
+    pub fn problem(&mut self, name: &'static str, count: usize) -> &mut Self {
+        if count > 0 {
+            self.problems.push(Problem { name, count });
+        }
+        self
+    }
+}
+
+#[derive(Debug, Default, Serialize)]
+pub struct Problem {
+    name: &'static str,
+    #[serde(skip_serializing_if = "crate::skip_if_default")]
+    count: usize,
+}
+
+#[cfg(test)]
+mod engine_tests {
+    use super::*;
+
+    #[test]
+    fn test_engine() {
+        let mut e = Engine::new("test_engine");
+        e.finished();
+        assert_json(&e, serde_json::json!({"name": "test_engine", "when": 0.0}));
+    }
+
+    #[test]
+    fn test_engine_not_finished() {
+        let e = Engine::new("test_engine");
+        serde_json::to_value(e).expect_err("unfinished stopwatch should fail");
+    }
+
+    #[test]
+    fn test_incoming() {
+        let mut i = EngineIncoming::new();
+        i.applied(1);
+        i.failed(2);
+        let mut e = Engine::new("TestEngine");
+        e.incoming(i);
+        e.finished();
+        assert_json(
+            &e,
+            serde_json::json!({"name": "TestEngine", "when": 0.0, "incoming": {"applied": 1, "failed": 2}}),
+        );
+    }
+
+    #[test]
+    fn test_incoming_accum() {
+        let mut e = Engine::new("TestEngine");
+        let mut i1 = EngineIncoming::new();
+        i1.applied(1);
+        i1.failed(2);
+        e.incoming(i1);
+        let mut i2 = EngineIncoming::new();
+        i2.applied(1);
+        i2.failed(1);
+        i2.reconciled(4);
+        e.incoming(i2);
+        e.finished();
+        assert_json(
+            &e,
+            serde_json::json!({"name": "TestEngine", "when": 0.0, "incoming": {"applied": 2, "failed": 3, "reconciled": 4}}),
+        );
+    }
+
+    #[test]
+    fn test_outgoing() {
+        let mut o = EngineOutgoing::new();
+        o.sent(2);
+        o.failed(1);
+        let mut e = Engine::new("TestEngine");
+        e.outgoing(o);
+        e.finished();
+        assert_json(
+            &e,
+            serde_json::json!({"name": "TestEngine", "when": 0.0, "outgoing": [{"sent": 2, "failed": 1}]}),
+        );
+    }
+
+    #[test]
+    fn test_failure() {
+        let mut e = Engine::new("TestEngine");
+        e.failure(SyncFailure::Http { code: 500 });
+        e.finished();
+        assert_json(
+            &e,
+            serde_json::json!({"name": "TestEngine",
+             "when": 0.0,
+             "failureReason": {"name": "httperror", "code": 500}
+            }),
+        );
+    }
+
+    #[test]
+    fn test_raw() {
+        let mut e = Engine::new("TestEngine");
+        let mut inc = EngineIncoming::new();
+        inc.applied(10);
+        e.incoming(inc);
+        let mut out = EngineOutgoing::new();
+        out.sent(1);
+        e.outgoing(out);
+        e.failure(SyncFailure::Http { code: 500 });
+        e.finished();
+
+        assert_eq!(e.outgoing.len(), 1);
+        assert_eq!(e.incoming.as_ref().unwrap().applied, 10);
+        assert_eq!(e.outgoing[0].sent, 1);
+        assert!(e.failure.is_some());
+        serde_json::to_string(&e).expect("should get json");
+    }
+}
+
+/// A single sync. May have many engines, may have its own failure.
+#[derive(Debug, Serialize, Default)]
+pub struct SyncTelemetry {
+    #[serde(flatten)]
+    when_took: Stopwatch,
+
+    #[serde(skip_serializing_if = "Vec::is_empty")]
+    engines: Vec<Engine>,
+
+    #[serde(skip_serializing_if = "Option::is_none")]
+    #[serde(rename = "failureReason")]
+    failure: Option<SyncFailure>,
+}
+
+impl SyncTelemetry {
+    pub fn new() -> Self {
+        Default::default()
+    }
+
+    pub fn engine(&mut self, mut e: Engine) {
+        e.finished();
+        self.engines.push(e);
+    }
+
+    pub fn failure(&mut self, failure: SyncFailure) {
+        assert!(self.failure.is_none());
+        self.failure = Some(failure);
+    }
+
+    // Note that unlike other 'finished' methods, this isn't private - someone
+    // needs to explicitly call this before handling the json payload to
+    // whatever ends up submitting it.
+    pub fn finished(&mut self) {
+        self.when_took = self.when_took.finished();
+    }
+}
+
+#[cfg(test)]
+mod sync_tests {
+    use super::*;
+
+    #[test]
+    fn test_accum() {
+        let mut s = SyncTelemetry::new();
+        let mut inc = EngineIncoming::new();
+        inc.applied(10);
+        let mut e = Engine::new("test_engine");
+        e.incoming(inc);
+        e.failure(SyncFailure::Http { code: 500 });
+        e.finished();
+        s.engine(e);
+        s.finished();
+
+        assert_json(
+            &s,
+            serde_json::json!({
+                "when": 0.0,
+                "engines": [{
+                    "name":"test_engine",
+                    "when":0.0,
+                    "incoming": {
+                        "applied": 10
+                    },
+                    "failureReason": {
+                        "name": "httperror",
+                        "code": 500
+                    }
+                }]
+            }),
+        );
+    }
+
+    #[test]
+    fn test_multi_engine() {
+        let mut inc_e1 = EngineIncoming::new();
+        inc_e1.applied(1);
+        let mut e1 = Engine::new("test_engine");
+        e1.incoming(inc_e1);
+
+        let mut inc_e2 = EngineIncoming::new();
+        inc_e2.failed(1);
+        let mut e2 = Engine::new("test_engine_2");
+        e2.incoming(inc_e2);
+        let mut out_e2 = EngineOutgoing::new();
+        out_e2.sent(1);
+        e2.outgoing(out_e2);
+
+        let mut s = SyncTelemetry::new();
+        s.engine(e1);
+        s.engine(e2);
+        s.failure(SyncFailure::Http { code: 500 });
+        s.finished();
+        assert_json(
+            &s,
+            serde_json::json!({
+                "when": 0.0,
+                "engines": [{
+                    "name": "test_engine",
+                    "when": 0.0,
+                    "incoming": {
+                        "applied": 1
+                    }
+                },{
+                    "name": "test_engine_2",
+                    "when": 0.0,
+                    "incoming": {
+                        "failed": 1
+                    },
+                    "outgoing": [{
+                        "sent": 1
+                    }]
+                }],
+                "failureReason": {
+                    "name": "httperror",
+                    "code": 500
+                }
+            }),
+        );
+    }
+}
+
+/// The Sync ping payload, as documented at
+/// https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/telemetry/data/sync-ping.html.
+/// May have many syncs, may have many events. However, due to the architecture
+/// of apps which use these components, this payload is almost certainly not
+/// suitable for submitting directly. For example, we will always return a
+/// payload with exactly 1 sync, and it will not know certain other fields
+/// in the payload, such as the *hashed* FxA device ID (see
+/// https://searchfox.org/mozilla-central/rev/c3ebaf6de2d481c262c04bb9657eaf76bf47e2ac/services/sync/modules/browserid_identity.js#185
+/// for an example of how the device ID is constructed). The intention is that
+/// consumers of this will use this to create a "real" payload - eg, accumulating
+/// until some threshold number of syncs is reached, and contributing
+/// additional data which only the consumer knows.
+#[derive(Debug, Serialize, Default)]
+pub struct SyncTelemetryPing {
+    version: u32,
+
+    uid: Option<String>,
+
+    #[serde(skip_serializing_if = "Vec::is_empty")]
+    events: Vec<Event>,
+
+    #[serde(skip_serializing_if = "Vec::is_empty")]
+    syncs: Vec<SyncTelemetry>,
+}
+
+impl SyncTelemetryPing {
+    pub fn new() -> Self {
+        Self {
+            version: 1,
+            ..Default::default()
+        }
+    }
+
+    pub fn uid(&mut self, uid: String) {
+        if let Some(ref existing) = self.uid {
+            if *existing != uid {
+                log::warn!("existing uid ${} being replaced by {}", existing, uid);
+            }
+        }
+        self.uid = Some(uid);
+    }
+
+    pub fn sync(&mut self, mut s: SyncTelemetry) {
+        s.finished();
+        self.syncs.push(s);
+    }
+
+    pub fn event(&mut self, e: Event) {
+        self.events.push(e);
+    }
+}
+
+ffi_support::implement_into_ffi_by_json!(SyncTelemetryPing);
+
+#[cfg(test)]
+mod ping_tests {
+    use super::*;
+    #[test]
+    fn test_ping() {
+        let engine = Engine::new("test");
+        let mut s = SyncTelemetry::new();
+        s.engine(engine);
+        let mut p = SyncTelemetryPing::new();
+        p.uid("user-id".into());
+        p.sync(s);
+        let event = Event::new("foo", "bar");
+        p.event(event);
+        assert_json(
+            &p,
+            serde_json::json!({
+                "events": [{
+                    "method": "bar", "object": "foo"
+                }],
+                "syncs": [{
+                    "engines": [{
+                        "name": "test", "when": 0.0
+                    }],
+                    "when": 0.0
+                }],
+                "uid": "user-id",
+                "version": 1
+            }),
+        );
+    }
+}
+
+impl<'a> From<&'a Error> for SyncFailure {
+    fn from(e: &Error) -> SyncFailure {
+        match e {
+            #[cfg(feature = "sync-client")]
+            Error::TokenserverHttpError(status) => {
+                if *status == 401 {
+                    SyncFailure::Auth {
+                        from: "tokenserver",
+                    }
+                } else {
+                    SyncFailure::Http { code: *status }
+                }
+            }
+            #[cfg(feature = "sync-client")]
+            Error::BackoffError(_) => SyncFailure::Http { code: 503 },
+            #[cfg(feature = "sync-client")]
+            Error::StorageHttpError(ref e) => match e {
+                ErrorResponse::NotFound { .. } => SyncFailure::Http { code: 404 },
+                ErrorResponse::Unauthorized { .. } => SyncFailure::Auth { from: "storage" },
+                ErrorResponse::PreconditionFailed { .. } => SyncFailure::Http { code: 412 },
+                ErrorResponse::ServerError { status, .. } => SyncFailure::Http { code: *status },
+                ErrorResponse::RequestFailed { status, .. } => SyncFailure::Http { code: *status },
+            },
+            #[cfg(feature = "crypto")]
+            Error::CryptoError(ref e) => SyncFailure::Unexpected {
+                error: e.to_string(),
+            },
+            #[cfg(feature = "sync-client")]
+            Error::RequestError(ref e) => SyncFailure::Unexpected {
+                error: e.to_string(),
+            },
+            #[cfg(feature = "sync-client")]
+            Error::UnexpectedStatus(ref e) => SyncFailure::Http { code: e.status },
+            Error::Interrupted(ref e) => SyncFailure::Unexpected {
+                error: e.to_string(),
+            },
+            e => SyncFailure::Other {
+                error: e.to_string(),
+            },
+        }
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/sync_guid/lib.rs.html b/book/rust-docs/src/sync_guid/lib.rs.html new file mode 100644 index 0000000000..d48c2e452b --- /dev/null +++ b/book/rust-docs/src/sync_guid/lib.rs.html @@ -0,0 +1,973 @@ +lib.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#![allow(unknown_lints)]
+#![warn(rust_2018_idioms)]
+// (It's tempting to avoid the utf8 checks, but they're easy to get wrong, so)
+#![deny(unsafe_code)]
+#[cfg(feature = "serde_support")]
+mod serde_support;
+
+#[cfg(feature = "rusqlite_support")]
+mod rusqlite_support;
+
+use std::{
+    cmp::Ordering,
+    fmt,
+    hash::{Hash, Hasher},
+    ops, str,
+};
+
+#[cfg(feature = "random")]
+use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
+
+/// This is a type intended to be used to represent the guids used by sync. It
+/// has several benefits over using a `String`:
+///
+/// 1. It's more explicit about what is being stored, and could prevent bugs
+///    where a Guid is passed to a function expecting text.
+///
+/// 2. Guids are guaranteed to be immutable.
+///
+/// 3. It's optimized for the guids commonly used by sync. In particular, short guids
+///    (including the guids which would meet `PlacesUtils.isValidGuid`) do not incur
+///    any heap allocation, and are stored inline.
+#[derive(Clone)]
+pub struct Guid(Repr);
+
+// The internal representation of a GUID. Most Sync GUIDs are 12 bytes,
+// and contain only base64url characters; we can store them on the stack
+// without a heap allocation. However, arbitrary ascii guids of up to length 64
+// are possible, in which case we fall back to a heap-allocated string.
+//
+// This is separate only because making `Guid` an enum would expose the
+// internals.
+#[derive(Clone)]
+enum Repr {
+    // see FastGuid for invariants
+    Fast(FastGuid),
+
+    // invariants:
+    // - _0.len() > MAX_FAST_GUID_LEN
+    Slow(String),
+}
+
+/// Invariants:
+///
+/// - `len <= MAX_FAST_GUID_LEN`.
+/// - `data[0..len]` encodes valid utf8.
+/// - `data[len..].iter().all(|&b| b == b'\0')`
+///
+/// Note: None of these are required for memory safety, just correctness.
+#[derive(Clone)]
+struct FastGuid {
+    len: u8,
+    data: [u8; MAX_FAST_GUID_LEN],
+}
+
+// This is the maximum length (experimentally determined) we can make it before
+// `Repr::Fast` is larger than `Guid::Slow` on 32 bit systems. The important
+// thing is really that it's not too big, and is above 12 bytes.
+const MAX_FAST_GUID_LEN: usize = 14;
+
+impl FastGuid {
+    #[inline]
+    fn from_slice(bytes: &[u8]) -> Self {
+        // Checked by the caller, so debug_assert is fine.
+        debug_assert!(
+            can_use_fast(bytes),
+            "Bug: Caller failed to check can_use_fast: {:?}",
+            bytes
+        );
+        let mut data = [0u8; MAX_FAST_GUID_LEN];
+        data[0..bytes.len()].copy_from_slice(bytes);
+        FastGuid {
+            len: bytes.len() as u8,
+            data,
+        }
+    }
+
+    #[inline]
+    fn as_str(&self) -> &str {
+        // Note: we only use debug_assert! to enusre valid utf8-ness, so this need
+        str::from_utf8(self.bytes()).expect("Invalid fast guid bytes!")
+    }
+
+    #[inline]
+    fn len(&self) -> usize {
+        self.len as usize
+    }
+
+    #[inline]
+    fn bytes(&self) -> &[u8] {
+        &self.data[0..self.len()]
+    }
+}
+
+// Returns:
+// - true to use Repr::Fast
+// - false to use Repr::Slow
+#[inline]
+fn can_use_fast<T: ?Sized + AsRef<[u8]>>(bytes: &T) -> bool {
+    let bytes = bytes.as_ref();
+    // This is fine as a debug_assert since we'll still panic if it's ever used
+    // in such a way where it would matter.
+    debug_assert!(str::from_utf8(bytes).is_ok());
+    bytes.len() <= MAX_FAST_GUID_LEN
+}
+
+impl Guid {
+    /// Create a guid from a `str`.
+    #[inline]
+    pub fn new(s: &str) -> Self {
+        Guid::from_slice(s.as_ref())
+    }
+
+    /// Create an empty guid. Usable as a constant.
+    #[inline]
+    pub const fn empty() -> Self {
+        Guid(Repr::Fast(FastGuid {
+            len: 0,
+            data: [0u8; MAX_FAST_GUID_LEN],
+        }))
+    }
+
+    /// Create a random guid (of 12 base64url characters). Requires the `random`
+    /// feature.
+    #[cfg(feature = "random")]
+    pub fn random() -> Self {
+        let bytes: [u8; 9] = rand::random();
+
+        // Note: only first 12 bytes are used, but remaining are required to
+        // build the FastGuid
+        let mut output = [0u8; MAX_FAST_GUID_LEN];
+
+        let bytes_written = URL_SAFE_NO_PAD
+            .encode_slice(bytes, &mut output[..12])
+            .expect("Output buffer too small");
+
+        debug_assert!(bytes_written == 12);
+
+        Guid(Repr::Fast(FastGuid {
+            len: 12,
+            data: output,
+        }))
+    }
+
+    /// Convert `b` into a `Guid`.
+    #[inline]
+    pub fn from_string(s: String) -> Self {
+        Guid::from_vec(s.into_bytes())
+    }
+
+    /// Convert `b` into a `Guid`.
+    #[inline]
+    pub fn from_slice(b: &[u8]) -> Self {
+        if can_use_fast(b) {
+            Guid(Repr::Fast(FastGuid::from_slice(b)))
+        } else {
+            Guid::new_slow(b.into())
+        }
+    }
+
+    /// Convert `v` to a `Guid`, consuming it.
+    #[inline]
+    pub fn from_vec(v: Vec<u8>) -> Self {
+        if can_use_fast(&v) {
+            Guid(Repr::Fast(FastGuid::from_slice(&v)))
+        } else {
+            Guid::new_slow(v)
+        }
+    }
+
+    /// Get the data backing this `Guid` as a `&[u8]`.
+    #[inline]
+    pub fn as_bytes(&self) -> &[u8] {
+        match &self.0 {
+            Repr::Fast(rep) => rep.bytes(),
+            Repr::Slow(rep) => rep.as_ref(),
+        }
+    }
+
+    /// Get the data backing this `Guid` as a `&str`.
+    #[inline]
+    pub fn as_str(&self) -> &str {
+        match &self.0 {
+            Repr::Fast(rep) => rep.as_str(),
+            Repr::Slow(rep) => rep.as_ref(),
+        }
+    }
+
+    /// Convert this `Guid` into a `String`, consuming it in the process.
+    #[inline]
+    pub fn into_string(self) -> String {
+        match self.0 {
+            Repr::Fast(rep) => rep.as_str().into(),
+            Repr::Slow(rep) => rep,
+        }
+    }
+
+    /// Returns true for Guids that are deemed valid by the sync server.
+    /// See https://github.com/mozilla-services/server-syncstorage/blob/d92ef07877aebd05b92f87f6ade341d6a55bffc8/syncstorage/bso.py#L24
+    pub fn is_valid_for_sync_server(&self) -> bool {
+        !self.is_empty()
+            && self.len() <= 64
+            && self
+                .bytes()
+                .all(|b| (b' '..=b'~').contains(&b) && b != b',')
+    }
+
+    /// Returns true for Guids that are valid places guids, and false for all others.
+    pub fn is_valid_for_places(&self) -> bool {
+        self.len() == 12 && self.bytes().all(Guid::is_valid_places_byte)
+    }
+
+    /// Returns true if the byte `b` is a valid base64url byte.
+    #[inline]
+    pub fn is_valid_places_byte(b: u8) -> bool {
+        BASE64URL_BYTES[b as usize] == 1
+    }
+
+    #[cold]
+    fn new_slow(v: Vec<u8>) -> Self {
+        assert!(
+            !can_use_fast(&v),
+            "Could use fast for guid (len = {})",
+            v.len()
+        );
+        Guid(Repr::Slow(
+            String::from_utf8(v).expect("Invalid slow guid bytes!"),
+        ))
+    }
+}
+
+// This is used to implement the places tests.
+const BASE64URL_BYTES: [u8; 256] = [
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
+    0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1,
+    0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+];
+
+impl Ord for Guid {
+    fn cmp(&self, other: &Self) -> Ordering {
+        self.as_bytes().cmp(other.as_bytes())
+    }
+}
+
+impl PartialOrd for Guid {
+    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+        Some(self.cmp(other))
+    }
+}
+
+impl PartialEq for Guid {
+    fn eq(&self, other: &Self) -> bool {
+        self.as_bytes() == other.as_bytes()
+    }
+}
+
+impl Eq for Guid {}
+
+impl Hash for Guid {
+    fn hash<H: Hasher>(&self, state: &mut H) {
+        self.as_bytes().hash(state);
+    }
+}
+
+impl<'a> From<&'a str> for Guid {
+    #[inline]
+    fn from(s: &'a str) -> Guid {
+        Guid::from_slice(s.as_ref())
+    }
+}
+impl<'a> From<&'a &str> for Guid {
+    #[inline]
+    fn from(s: &'a &str) -> Guid {
+        Guid::from_slice(s.as_ref())
+    }
+}
+
+impl<'a> From<&'a [u8]> for Guid {
+    #[inline]
+    fn from(s: &'a [u8]) -> Guid {
+        Guid::from_slice(s)
+    }
+}
+
+impl From<String> for Guid {
+    #[inline]
+    fn from(s: String) -> Guid {
+        Guid::from_string(s)
+    }
+}
+
+impl From<Vec<u8>> for Guid {
+    #[inline]
+    fn from(v: Vec<u8>) -> Guid {
+        Guid::from_vec(v)
+    }
+}
+
+impl From<Guid> for String {
+    #[inline]
+    fn from(guid: Guid) -> String {
+        guid.into_string()
+    }
+}
+
+impl From<Guid> for Vec<u8> {
+    #[inline]
+    fn from(guid: Guid) -> Vec<u8> {
+        guid.into_string().into_bytes()
+    }
+}
+
+impl AsRef<str> for Guid {
+    #[inline]
+    fn as_ref(&self) -> &str {
+        self.as_str()
+    }
+}
+
+impl AsRef<[u8]> for Guid {
+    #[inline]
+    fn as_ref(&self) -> &[u8] {
+        self.as_bytes()
+    }
+}
+
+impl ops::Deref for Guid {
+    type Target = str;
+    #[inline]
+    fn deref(&self) -> &str {
+        self.as_str()
+    }
+}
+
+// The default Debug impl is pretty unhelpful here.
+impl fmt::Debug for Guid {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "Guid({:?})", self.as_str())
+    }
+}
+
+impl fmt::Display for Guid {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        fmt::Display::fmt(self.as_str(), f)
+    }
+}
+
+impl std::default::Default for Guid {
+    /// Create a default guid by calling `Guid::empty()`
+    #[inline]
+    fn default() -> Self {
+        Guid::empty()
+    }
+}
+
+macro_rules! impl_guid_eq {
+    ($($other: ty),+) => {$(
+        // This macro is used for items with and without lifetimes.
+        #[allow(clippy::extra_unused_lifetimes)]
+        impl<'a> PartialEq<$other> for Guid {
+            #[inline]
+            fn eq(&self, other: &$other) -> bool {
+                PartialEq::eq(AsRef::<[u8]>::as_ref(self), AsRef::<[u8]>::as_ref(other))
+            }
+        }
+
+        #[allow(clippy::extra_unused_lifetimes)]
+        impl<'a> PartialEq<Guid> for $other {
+            #[inline]
+            fn eq(&self, other: &Guid) -> bool {
+                PartialEq::eq(AsRef::<[u8]>::as_ref(self), AsRef::<[u8]>::as_ref(other))
+            }
+        }
+    )+}
+}
+
+// Implement direct comparison with some common types from the stdlib.
+impl_guid_eq![str, &'a str, String, [u8], &'a [u8], Vec<u8>];
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn test_base64url_bytes() {
+        let mut expect = [0u8; 256];
+        for b in b'0'..=b'9' {
+            expect[b as usize] = 1;
+        }
+        for b in b'a'..=b'z' {
+            expect[b as usize] = 1;
+        }
+        for b in b'A'..=b'Z' {
+            expect[b as usize] = 1;
+        }
+        expect[b'_' as usize] = 1;
+        expect[b'-' as usize] = 1;
+        assert_eq!(&BASE64URL_BYTES[..], &expect[..]);
+    }
+
+    #[test]
+    fn test_valid_for_places() {
+        assert!(Guid::from("aaaabbbbcccc").is_valid_for_places());
+        assert!(Guid::from_slice(b"09_az-AZ_09-").is_valid_for_places());
+        assert!(!Guid::from("aaaabbbbccccd").is_valid_for_places()); // too long
+        assert!(!Guid::from("aaaabbbbccc").is_valid_for_places()); // too short
+        assert!(!Guid::from("aaaabbbbccc=").is_valid_for_places()); // right length, bad character
+        assert!(!Guid::empty().is_valid_for_places()); // empty isn't valid to insert.
+    }
+
+    #[test]
+    fn test_valid_for_sync_server() {
+        assert!(!Guid::empty().is_valid_for_sync_server()); // empty isn't valid remotely.
+    }
+
+    #[allow(clippy::cmp_owned)] // See clippy note below.
+    #[test]
+    fn test_comparison() {
+        assert_eq!(Guid::from("abcdabcdabcd"), "abcdabcdabcd");
+        assert_ne!(Guid::from("abcdabcdabcd".to_string()), "ABCDabcdabcd");
+
+        assert_eq!(Guid::from("abcdabcdabcd"), &b"abcdabcdabcd"[..]); // b"abcdabcdabcd" has type &[u8; 12]...
+        assert_ne!(Guid::from(&b"abcdabcdabcd"[..]), &b"ABCDabcdabcd"[..]);
+
+        assert_eq!(
+            Guid::from(b"abcdabcdabcd"[..].to_owned()),
+            "abcdabcdabcd".to_string()
+        );
+        assert_ne!(Guid::from("abcdabcdabcd"), "ABCDabcdabcd".to_string());
+
+        assert_eq!(
+            Guid::from("abcdabcdabcd1234"),
+            Vec::from(b"abcdabcdabcd1234".as_ref())
+        );
+        assert_ne!(
+            Guid::from("abcdabcdabcd4321"),
+            Vec::from(b"ABCDabcdabcd4321".as_ref())
+        );
+
+        // order by data instead of length
+        // hrmph - clippy in 1.54-nightly complains about the below:
+        // 'error: this creates an owned instance just for comparison'
+        // '... help: try: `*"aaaaaa"`'
+        // and suggests a change that's wrong - so we've ignored the lint above.
+        assert!(Guid::from("zzz") > Guid::from("aaaaaa"));
+        assert!(Guid::from("ThisIsASolowGuid") < Guid::from("zzz"));
+        assert!(Guid::from("ThisIsASolowGuid") > Guid::from("AnotherSlowGuid"));
+    }
+
+    #[cfg(feature = "random")]
+    #[test]
+    fn test_random() {
+        use std::collections::HashSet;
+        // Used to verify uniqueness within our sample of 1000. Could cause
+        // random failures, but desktop has the same test, and it's never caused
+        // a problem AFAIK.
+        let mut seen: HashSet<String> = HashSet::new();
+        for _ in 0..1000 {
+            let g = Guid::random();
+            assert_eq!(g.len(), 12);
+            assert!(g.is_valid_for_places());
+            let decoded = URL_SAFE_NO_PAD.decode(&g).unwrap();
+            assert_eq!(decoded.len(), 9);
+            let no_collision = seen.insert(g.clone().into_string());
+            assert!(no_collision, "{}", g);
+        }
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/sync_guid/rusqlite_support.rs.html b/book/rust-docs/src/sync_guid/rusqlite_support.rs.html new file mode 100644 index 0000000000..d9e6cb409b --- /dev/null +++ b/book/rust-docs/src/sync_guid/rusqlite_support.rs.html @@ -0,0 +1,47 @@ +rusqlite_support.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#![cfg(feature = "rusqlite_support")]
+
+use crate::Guid;
+use rusqlite::{
+    self,
+    types::{FromSql, FromSqlResult, ToSql, ToSqlOutput, ValueRef},
+};
+
+impl ToSql for Guid {
+    fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
+        Ok(ToSqlOutput::from(self.as_str()))
+    }
+}
+
+impl FromSql for Guid {
+    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
+        value.as_str().map(Guid::from)
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/sync_guid/serde_support.rs.html b/book/rust-docs/src/sync_guid/serde_support.rs.html new file mode 100644 index 0000000000..eef79f400b --- /dev/null +++ b/book/rust-docs/src/sync_guid/serde_support.rs.html @@ -0,0 +1,123 @@ +serde_support.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#![cfg(feature = "serde_support")]
+
+use std::fmt;
+
+use serde::{
+    de::{self, Deserialize, Deserializer, Visitor},
+    ser::{Serialize, Serializer},
+};
+
+use crate::Guid;
+
+struct GuidVisitor;
+impl<'de> Visitor<'de> for GuidVisitor {
+    type Value = Guid;
+    #[inline]
+    fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+        formatter.write_str("a sync guid")
+    }
+    #[inline]
+    fn visit_str<E: de::Error>(self, s: &str) -> Result<Self::Value, E> {
+        Ok(Guid::from_slice(s.as_ref()))
+    }
+}
+
+impl<'de> Deserialize<'de> for Guid {
+    #[inline]
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        deserializer.deserialize_str(GuidVisitor)
+    }
+}
+
+impl Serialize for Guid {
+    #[inline]
+    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+        serializer.serialize_str(self.as_str())
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+    use serde_test::{assert_tokens, Token};
+    #[test]
+    fn test_ser_de() {
+        let guid = Guid::from("asdffdsa12344321");
+        assert_tokens(&guid, &[Token::Str("asdffdsa12344321")]);
+
+        let guid = Guid::from("");
+        assert_tokens(&guid, &[Token::Str("")]);
+
+        let guid = Guid::from(&b"abcd43211234"[..]);
+        assert_tokens(&guid, &[Token::Str("abcd43211234")]);
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/sync_manager/error.rs.html b/book/rust-docs/src/sync_manager/error.rs.html new file mode 100644 index 0000000000..3106ff0589 --- /dev/null +++ b/book/rust-docs/src/sync_manager/error.rs.html @@ -0,0 +1,67 @@ +error.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use interrupt_support::Interrupted;
+
+#[derive(Debug, thiserror::Error)]
+pub enum SyncManagerError {
+    #[error("Unknown engine: {0}")]
+    UnknownEngine(String),
+    #[error("Manager was compiled without support for {0:?}")]
+    UnsupportedFeature(String),
+    // Used for things like 'failed to decode the provided sync key because it's
+    // completely the wrong format', etc.
+    #[error("Sync error: {0}")]
+    Sync15Error(#[from] sync15::Error),
+    #[error("URL parse error: {0}")]
+    UrlParseError(#[from] url::ParseError),
+    #[error("Operation interrupted")]
+    InterruptedError(#[from] Interrupted),
+    #[error("Error parsing JSON data: {0}")]
+    JsonError(#[from] serde_json::Error),
+    #[error("Logins error: {0}")]
+    LoginsError(#[from] logins::Error),
+    #[error("Places error: {0}")]
+    PlacesError(#[from] places::Error),
+    // We should probably upgrade this crate to anyhow, which would mean this
+    // gets replaced with AutofillError or similar.
+    #[error("External error: {0}")]
+    AnyhowError(#[from] anyhow::Error),
+}
+
+pub type Result<T> = std::result::Result<T, SyncManagerError>;
+
\ No newline at end of file diff --git a/book/rust-docs/src/sync_manager/lib.rs.html b/book/rust-docs/src/sync_manager/lib.rs.html new file mode 100644 index 0000000000..b170b3414b --- /dev/null +++ b/book/rust-docs/src/sync_manager/lib.rs.html @@ -0,0 +1,99 @@ +lib.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#![allow(unknown_lints)]
+#![warn(rust_2018_idioms)]
+
+pub mod error;
+pub mod manager;
+mod types;
+
+pub use sync15::DeviceType;
+
+pub use error::{Result, SyncManagerError};
+pub use types::*;
+
+use manager::SyncManager;
+use parking_lot::Mutex;
+
+lazy_static::lazy_static! {
+    static ref MANAGER: Mutex<SyncManager> = Mutex::new(SyncManager::new());
+}
+
+pub fn disconnect() {
+    let manager = MANAGER.lock();
+    manager.disconnect();
+}
+
+pub fn wipe(engine: &str) -> Result<()> {
+    let manager = MANAGER.lock();
+    manager.wipe(engine)
+}
+
+pub fn reset(engine: &str) -> Result<()> {
+    let manager = MANAGER.lock();
+    manager.reset(engine)
+}
+
+pub fn reset_all() -> Result<()> {
+    let manager = MANAGER.lock();
+    manager.reset_all()
+}
+
+pub fn sync(params: SyncParams) -> Result<SyncResult> {
+    let manager = MANAGER.lock();
+    manager.sync(params)
+}
+
+uniffi::include_scaffolding!("syncmanager");
+
\ No newline at end of file diff --git a/book/rust-docs/src/sync_manager/manager.rs.html b/book/rust-docs/src/sync_manager/manager.rs.html new file mode 100644 index 0000000000..9bddcb431a --- /dev/null +++ b/book/rust-docs/src/sync_manager/manager.rs.html @@ -0,0 +1,653 @@ +manager.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::error::*;
+use crate::types::{ServiceStatus, SyncEngineSelection, SyncParams, SyncReason, SyncResult};
+use crate::{reset, reset_all, wipe};
+use error_support::breadcrumb;
+use parking_lot::Mutex;
+use std::collections::{BTreeMap, HashMap, HashSet};
+use std::convert::TryFrom;
+use std::time::SystemTime;
+use sync15::client::{
+    sync_multiple_with_command_processor, MemoryCachedState, Sync15StorageClientInit,
+    SyncRequestInfo,
+};
+use sync15::clients_engine::{Command, CommandProcessor, CommandStatus, Settings};
+use sync15::engine::{EngineSyncAssociation, SyncEngine, SyncEngineId};
+
+#[derive(Default)]
+pub struct SyncManager {
+    mem_cached_state: Mutex<Option<MemoryCachedState>>,
+}
+
+impl SyncManager {
+    pub fn new() -> Self {
+        Self::default()
+    }
+
+    fn get_engine_id(engine_name: &str) -> Result<SyncEngineId> {
+        SyncEngineId::try_from(engine_name).map_err(SyncManagerError::UnknownEngine)
+    }
+
+    fn get_engine(engine_id: &SyncEngineId) -> Option<Box<dyn SyncEngine>> {
+        match engine_id {
+            SyncEngineId::History => places::get_registered_sync_engine(engine_id),
+            SyncEngineId::Bookmarks => places::get_registered_sync_engine(engine_id),
+            SyncEngineId::Addresses => autofill::get_registered_sync_engine(engine_id),
+            SyncEngineId::CreditCards => autofill::get_registered_sync_engine(engine_id),
+            SyncEngineId::Passwords => logins::get_registered_sync_engine(engine_id),
+            SyncEngineId::Tabs => tabs::get_registered_sync_engine(engine_id),
+        }
+    }
+
+    pub fn wipe(&self, engine_name: &str) -> Result<()> {
+        if let Some(engine) = Self::get_engine(&Self::get_engine_id(engine_name)?) {
+            engine.wipe()?;
+        }
+        Ok(())
+    }
+
+    pub fn reset(&self, engine_name: &str) -> Result<()> {
+        if let Some(engine) = Self::get_engine(&Self::get_engine_id(engine_name)?) {
+            engine.reset(&EngineSyncAssociation::Disconnected)?;
+        }
+        Ok(())
+    }
+
+    pub fn reset_all(&self) -> Result<()> {
+        for (_, engine) in self.iter_registered_engines() {
+            engine.reset(&EngineSyncAssociation::Disconnected)?;
+        }
+        Ok(())
+    }
+
+    /// Disconnect engines from sync, deleting/resetting the sync-related data
+    pub fn disconnect(&self) {
+        breadcrumb!("SyncManager disconnect()");
+        for engine_id in SyncEngineId::iter() {
+            if let Some(engine) = Self::get_engine(&engine_id) {
+                if let Err(e) = engine.reset(&EngineSyncAssociation::Disconnected) {
+                    error_support::report_error!(
+                        "sync-manager-reset",
+                        "Failed to reset {}: {}",
+                        engine_id,
+                        e
+                    );
+                }
+            } else {
+                log::warn!("Unable to reset {}, be sure to call register_with_sync_manager before disconnect if this is surprising", engine_id);
+            }
+        }
+    }
+
+    /// Perform a sync.  See [SyncParams] and [SyncResult] for details on how this works
+    pub fn sync(&self, params: SyncParams) -> Result<SyncResult> {
+        breadcrumb!("SyncManager::sync started");
+        let mut state = self.mem_cached_state.lock();
+        let engines = self.calc_engines_to_sync(&params.engines)?;
+        let next_sync_after = state.as_ref().and_then(|mcs| mcs.get_next_sync_after());
+        let result = if !backoff_in_effect(next_sync_after, &params) {
+            log::info!("No backoff in effect (or we decided to ignore it), starting sync");
+            self.do_sync(params, &mut state, engines)
+        } else {
+            breadcrumb!(
+                "Backoff still in effect (until {:?}), bailing out early",
+                next_sync_after
+            );
+            Ok(SyncResult {
+                status: ServiceStatus::BackedOff,
+                successful: Default::default(),
+                failures: Default::default(),
+                declined: None,
+                next_sync_allowed_at: next_sync_after,
+                persisted_state: params.persisted_state.unwrap_or_default(),
+                // It would be nice to record telemetry here.
+                telemetry_json: None,
+            })
+        };
+        breadcrumb!("SyncManager sync ended");
+        result
+    }
+
+    fn do_sync(
+        &self,
+        mut params: SyncParams,
+        state: &mut Option<MemoryCachedState>,
+        mut engines: Vec<Box<dyn SyncEngine>>,
+    ) -> Result<SyncResult> {
+        let key_bundle = sync15::KeyBundle::from_ksync_base64(&params.auth_info.sync_key)?;
+        let tokenserver_url = url::Url::parse(&params.auth_info.tokenserver_url)?;
+        let interruptee = interrupt_support::ShutdownInterruptee;
+        let mut mem_cached_state = state.take().unwrap_or_default();
+        let mut disk_cached_state = params.persisted_state.take();
+
+        // tell engines about the local encryption key.
+        for engine in engines.iter_mut() {
+            if let Some(key) = params.local_encryption_keys.get(&*engine.collection_name()) {
+                engine.set_local_encryption_key(key)?
+            }
+        }
+
+        let engine_refs: Vec<&dyn SyncEngine> = engines.iter().map(|s| &**s).collect();
+
+        let client_init = Sync15StorageClientInit {
+            key_id: params.auth_info.kid.clone(),
+            access_token: params.auth_info.fxa_access_token.clone(),
+            tokenserver_url,
+        };
+        let engines_to_change = if params.enabled_changes.is_empty() {
+            None
+        } else {
+            Some(&params.enabled_changes)
+        };
+
+        let settings = Settings {
+            fxa_device_id: params.device_settings.fxa_device_id,
+            device_name: params.device_settings.name,
+            device_type: params.device_settings.kind,
+        };
+        let c = SyncClient::new(settings);
+        let result = sync_multiple_with_command_processor(
+            Some(&c),
+            &engine_refs,
+            &mut disk_cached_state,
+            &mut mem_cached_state,
+            &client_init,
+            &key_bundle,
+            &interruptee,
+            Some(SyncRequestInfo {
+                engines_to_state_change: engines_to_change,
+                is_user_action: matches!(params.reason, SyncReason::User),
+            }),
+        );
+        *state = Some(mem_cached_state);
+
+        log::info!("Sync finished with status {:?}", result.service_status);
+        let status = ServiceStatus::from(result.service_status);
+        for (engine, result) in result.engine_results.iter() {
+            log::info!("engine {:?} status: {:?}", engine, result);
+        }
+        let mut successful: Vec<String> = Vec::new();
+        let mut failures: HashMap<String, String> = HashMap::new();
+        for (engine, result) in result.engine_results.into_iter() {
+            match result {
+                Ok(_) => {
+                    successful.push(engine);
+                }
+                Err(err) => {
+                    failures.insert(engine, err.to_string());
+                }
+            }
+        }
+        let telemetry_json = serde_json::to_string(&result.telemetry).unwrap();
+
+        Ok(SyncResult {
+            status,
+            successful,
+            failures,
+            declined: result.declined,
+            next_sync_allowed_at: result.next_sync_after,
+            persisted_state: disk_cached_state.unwrap_or_default(),
+            telemetry_json: Some(telemetry_json),
+        })
+    }
+
+    fn iter_registered_engines(&self) -> impl Iterator<Item = (SyncEngineId, Box<dyn SyncEngine>)> {
+        SyncEngineId::iter().filter_map(|id| Self::get_engine(&id).map(|engine| (id, engine)))
+    }
+
+    pub fn get_available_engines(&self) -> Vec<String> {
+        self.iter_registered_engines()
+            .map(|(name, _)| name.to_string())
+            .collect()
+    }
+
+    fn calc_engines_to_sync(
+        &self,
+        selection: &SyncEngineSelection,
+    ) -> Result<Vec<Box<dyn SyncEngine>>> {
+        // BTreeMap to ensure we sync the engines in priority order.
+        let mut engine_map: BTreeMap<_, _> = self.iter_registered_engines().collect();
+        breadcrumb!(
+            "Checking engines requested ({:?}) vs local engines ({:?})",
+            selection,
+            engine_map
+                .keys()
+                .map(|engine_id| engine_id.name())
+                .collect::<Vec<_>>(),
+        );
+        if let SyncEngineSelection::Some {
+            engines: engine_names,
+        } = selection
+        {
+            // Validate selection and convert to SyncEngineId
+            let mut selected_engine_ids: HashSet<SyncEngineId> = HashSet::new();
+            for name in engine_names {
+                let engine_id = Self::get_engine_id(name)?;
+                if !engine_map.contains_key(&engine_id) {
+                    return Err(SyncManagerError::UnsupportedFeature(name.to_string()));
+                }
+                selected_engine_ids.insert(engine_id);
+            }
+            // Filter engines based on the selection
+            engine_map.retain(|engine_id, _| selected_engine_ids.contains(engine_id))
+        }
+        Ok(engine_map.into_values().collect())
+    }
+}
+
+fn backoff_in_effect(next_sync_after: Option<SystemTime>, p: &SyncParams) -> bool {
+    let now = SystemTime::now();
+    if let Some(nsa) = next_sync_after {
+        if nsa > now {
+            return if matches!(p.reason, SyncReason::User | SyncReason::EnabledChange) {
+                log::info!(
+                    "Still under backoff, but syncing anyway because reason is {:?}",
+                    p.reason
+                );
+                false
+            } else if !p.enabled_changes.is_empty() {
+                log::info!(
+                    "Still under backoff, but syncing because we have enabled state changes."
+                );
+                false
+            } else {
+                log::info!(
+                    "Still under backoff, and there's no compelling reason for us to ignore it"
+                );
+                true
+            };
+        }
+    }
+    log::debug!("Not under backoff");
+    false
+}
+
+impl From<sync15::client::ServiceStatus> for ServiceStatus {
+    fn from(s15s: sync15::client::ServiceStatus) -> Self {
+        use sync15::client::ServiceStatus::*;
+        match s15s {
+            Ok => ServiceStatus::Ok,
+            NetworkError => ServiceStatus::NetworkError,
+            ServiceError => ServiceStatus::ServiceError,
+            AuthenticationError => ServiceStatus::AuthError,
+            BackedOff => ServiceStatus::BackedOff,
+            Interrupted => ServiceStatus::OtherError, // Eh...
+            OtherError => ServiceStatus::OtherError,
+        }
+    }
+}
+
+struct SyncClient(Settings);
+
+impl SyncClient {
+    pub fn new(settings: Settings) -> SyncClient {
+        SyncClient(settings)
+    }
+}
+
+impl CommandProcessor for SyncClient {
+    fn settings(&self) -> &Settings {
+        &self.0
+    }
+
+    fn apply_incoming_command(&self, command: Command) -> anyhow::Result<CommandStatus> {
+        let result = match command {
+            Command::Wipe(engine) => wipe(&engine),
+            Command::Reset(engine) => reset(&engine),
+            Command::ResetAll => reset_all(),
+        };
+        match result {
+            Ok(()) => Ok(CommandStatus::Applied),
+            Err(err) => match err {
+                SyncManagerError::UnknownEngine(_) => Ok(CommandStatus::Unsupported),
+                _ => Err(err.into()),
+            },
+        }
+    }
+
+    fn fetch_outgoing_commands(&self) -> anyhow::Result<HashSet<Command>> {
+        Ok(HashSet::new())
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn test_engine_id_sanity() {
+        for engine_id in SyncEngineId::iter() {
+            assert_eq!(engine_id, SyncEngineId::try_from(engine_id.name()).unwrap());
+        }
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/sync_manager/types.rs.html b/book/rust-docs/src/sync_manager/types.rs.html new file mode 100644 index 0000000000..04bb7159e1 --- /dev/null +++ b/book/rust-docs/src/sync_manager/types.rs.html @@ -0,0 +1,201 @@ +types.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+
use std::collections::HashMap;
+use std::time::SystemTime;
+use sync15::DeviceType;
+
+#[derive(Debug)]
+pub struct SyncParams {
+    // Why are we performing this sync?
+    pub reason: SyncReason,
+    // Which engines should we sync?
+    pub engines: SyncEngineSelection,
+    // Which engines should be enabled in the "account global" list (for
+    // example, if the UI was used to change an engine's state since the last
+    // sync).
+    pub enabled_changes: HashMap<String, bool>,
+    // Keys to encrypt/decrypt data from local database files.  These are
+    // separate from the key we use to encrypt the sync payload as a whole.
+    pub local_encryption_keys: HashMap<String, String>,
+    // Authorization for the sync server
+    pub auth_info: SyncAuthInfo,
+    // An opaque string, as returned in the previous sync's SyncResult and
+    // persisted to disk, or null if no such state is available. This includes
+    // information such as the list of engines previously enabled, certain
+    // server timestamps and GUIDs etc. If this value isn't correctly persisted
+    // and round-tripped, each sync may look like a "first sync".
+    pub persisted_state: Option<String>,
+    // Information about the current device, such as its name, formfactor and
+    // FxA device ID.
+    pub device_settings: DeviceSettings,
+}
+
+#[derive(Debug)]
+pub enum SyncReason {
+    Scheduled,
+    User,
+    PreSleep,
+    Startup,
+    EnabledChange,
+    Backgrounded,
+}
+
+#[derive(Debug)]
+pub enum SyncEngineSelection {
+    All,
+    Some { engines: Vec<String> },
+}
+
+#[derive(Debug)]
+pub struct SyncAuthInfo {
+    pub kid: String,
+    pub fxa_access_token: String,
+    pub sync_key: String,
+    pub tokenserver_url: String,
+}
+
+#[derive(Debug)]
+pub struct DeviceSettings {
+    pub fxa_device_id: String,
+    pub name: String,
+    pub kind: DeviceType,
+}
+
+#[derive(Debug)]
+pub struct SyncResult {
+    // Result from the sync server
+    pub status: ServiceStatus,
+    // Engines that synced successfully
+    pub successful: Vec<String>,
+    // Maps the names of engines that failed to sync to the reason why
+    pub failures: HashMap<String, String>,
+    // State that should be persisted to disk and supplied to the sync method
+    // on the next sync (See SyncParams.persisted_state).
+    pub persisted_state: String,
+    // The list of engines which are marked as "declined" (ie, disabled) on the
+    // sync server. The list of declined engines is global to the account
+    // rather than to the device. Apps should use this after every sync to
+    // update the local state (ie, to ensure that their Sync UI correctly
+    // reflects what engines are enabled and disabled), because these could
+    // change after every sync.
+    pub declined: Option<Vec<String>>,
+    // Earliest time that the next sync should happen at
+    pub next_sync_allowed_at: Option<SystemTime>,
+    // JSON string encoding a `SyncTelemetryPing` object
+    pub telemetry_json: Option<String>,
+}
+
+#[derive(Debug)]
+pub enum ServiceStatus {
+    Ok,
+    NetworkError,
+    ServiceError,
+    AuthError,
+    BackedOff,
+    OtherError,
+}
+
+impl ServiceStatus {
+    pub fn is_ok(&self) -> bool {
+        matches!(self, ServiceStatus::Ok)
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/tabs/error.rs.html b/book/rust-docs/src/tabs/error.rs.html new file mode 100644 index 0000000000..1acd2156a9 --- /dev/null +++ b/book/rust-docs/src/tabs/error.rs.html @@ -0,0 +1,197 @@ +error.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use error_support::{ErrorHandling, GetErrorHandling};
+
+/// Result enum for the public interface
+pub type ApiResult<T> = std::result::Result<T, TabsApiError>;
+/// Result enum for internal functions
+pub type Result<T> = std::result::Result<T, Error>;
+
+// Errors we return via the public interface.
+#[derive(Debug, thiserror::Error)]
+pub enum TabsApiError {
+    #[error("SyncError: {reason}")]
+    SyncError { reason: String },
+
+    #[error("SqlError: {reason}")]
+    SqlError { reason: String },
+
+    #[error("Unexpected tabs error: {reason}")]
+    UnexpectedTabsError { reason: String },
+}
+
+// Error we use internally
+#[derive(Debug, thiserror::Error)]
+pub enum Error {
+    #[cfg(feature = "full-sync")]
+    #[error("Error synchronizing: {0}")]
+    SyncAdapterError(#[from] sync15::Error),
+
+    // Note we are abusing this as a kind of "mis-matched feature" error.
+    // This works because when `full-sync` isn't enabled we don't actually
+    // handle any sync15 errors as the bridged-engine never returns them.
+    #[cfg(not(feature = "full-sync"))]
+    #[error("Sync feature is disabled: {0}")]
+    SyncAdapterError(String),
+
+    #[error("Error parsing JSON data: {0}")]
+    JsonError(#[from] serde_json::Error),
+
+    #[error("Missing SyncUnlockInfo Local ID")]
+    MissingLocalIdError,
+
+    #[error("Error parsing URL: {0}")]
+    UrlParseError(#[from] url::ParseError),
+
+    #[error("Error executing SQL: {0}")]
+    SqlError(#[from] rusqlite::Error),
+
+    #[error("Error opening database: {0}")]
+    OpenDatabaseError(#[from] sql_support::open_database::Error),
+}
+
+// Define how our internal errors are handled and converted to external errors
+// See `support/error/README.md` for how this works, especially the warning about PII.
+impl GetErrorHandling for Error {
+    type ExternalError = TabsApiError;
+
+    fn get_error_handling(&self) -> ErrorHandling<Self::ExternalError> {
+        match self {
+            Self::SyncAdapterError(e) => ErrorHandling::convert(TabsApiError::SyncError {
+                reason: e.to_string(),
+            })
+            .report_error("tabs-sync-error"),
+            Self::JsonError(e) => ErrorHandling::convert(TabsApiError::UnexpectedTabsError {
+                reason: e.to_string(),
+            })
+            .report_error("tabs-json-error"),
+            Self::MissingLocalIdError => {
+                ErrorHandling::convert(TabsApiError::UnexpectedTabsError {
+                    reason: "MissingLocalId".to_string(),
+                })
+                .report_error("tabs-missing-local-id-error")
+            }
+            Self::UrlParseError(e) => ErrorHandling::convert(TabsApiError::UnexpectedTabsError {
+                reason: e.to_string(),
+            })
+            .report_error("tabs-url-parse-error"),
+            Self::SqlError(e) => ErrorHandling::convert(TabsApiError::SqlError {
+                reason: e.to_string(),
+            })
+            .report_error("tabs-sql-error"),
+            Self::OpenDatabaseError(e) => ErrorHandling::convert(TabsApiError::SqlError {
+                reason: e.to_string(),
+            })
+            .report_error("tabs-open-database-error"),
+        }
+    }
+}
+
+impl From<anyhow::Error> for TabsApiError {
+    fn from(value: anyhow::Error) -> Self {
+        TabsApiError::UnexpectedTabsError {
+            reason: value.to_string(),
+        }
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/tabs/lib.rs.html b/book/rust-docs/src/tabs/lib.rs.html new file mode 100644 index 0000000000..34b29bc829 --- /dev/null +++ b/book/rust-docs/src/tabs/lib.rs.html @@ -0,0 +1,79 @@ +lib.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#![allow(unknown_lints)]
+#![warn(rust_2018_idioms)]
+
+#[macro_use]
+pub mod error;
+mod schema;
+mod storage;
+mod store;
+mod sync;
+
+uniffi::include_scaffolding!("tabs");
+
+// Our UDL uses a `Guid` type.
+use sync_guid::Guid as TabsGuid;
+impl UniffiCustomTypeConverter for TabsGuid {
+    type Builtin = String;
+
+    fn into_custom(val: Self::Builtin) -> uniffi::Result<TabsGuid> {
+        Ok(TabsGuid::new(val.as_str()))
+    }
+
+    fn from_custom(obj: Self) -> Self::Builtin {
+        obj.into()
+    }
+}
+
+pub use crate::storage::{ClientRemoteTabs, RemoteTabRecord, TabsDeviceType};
+pub use crate::store::TabsStore;
+pub use error::{ApiResult, Error, Result, TabsApiError};
+use sync15::DeviceType;
+
+pub use crate::sync::engine::get_registered_sync_engine;
+
+pub use crate::sync::bridge::TabsBridgedEngine;
+pub use crate::sync::engine::TabsEngine;
+
\ No newline at end of file diff --git a/book/rust-docs/src/tabs/schema.rs.html b/book/rust-docs/src/tabs/schema.rs.html new file mode 100644 index 0000000000..9b6c91987f --- /dev/null +++ b/book/rust-docs/src/tabs/schema.rs.html @@ -0,0 +1,301 @@ +schema.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Tabs is a bit special - it's a trivial SQL schema and is only used as a persistent
+// cache, and the semantics of the "tabs" collection means there's no need for
+// syncChangeCounter/syncStatus nor a mirror etc.
+
+use rusqlite::{Connection, Transaction};
+use sql_support::{
+    open_database::{
+        ConnectionInitializer as MigrationLogic, Error as MigrationError, Result as MigrationResult,
+    },
+    ConnExt,
+};
+
+// The record is the TabsRecord struct in json and this module doesn't need to deserialize, so we just
+// store each client as its own row.
+const CREATE_SCHEMA_SQL: &str = "
+    CREATE TABLE IF NOT EXISTS tabs (
+        guid            TEXT NOT NULL PRIMARY KEY,
+        record          TEXT NOT NULL,
+        last_modified   INTEGER NOT NULL
+    );
+";
+
+const CREATE_META_TABLE_SQL: &str = "
+    CREATE TABLE IF NOT EXISTS moz_meta (
+        key    TEXT PRIMARY KEY,
+        value  NOT NULL
+    )
+";
+
+pub(crate) static LAST_SYNC_META_KEY: &str = "last_sync_time";
+pub(crate) static GLOBAL_SYNCID_META_KEY: &str = "global_sync_id";
+pub(crate) static COLLECTION_SYNCID_META_KEY: &str = "tabs_sync_id";
+// Tabs stores this in the meta table due to a unique requirement that we only know the list
+// of connected clients when syncing, however getting the list of tabs could be called at anytime
+// so we store it so we can translate from the tabs sync record ID to the FxA device id for the client
+pub(crate) static REMOTE_CLIENTS_KEY: &str = "remote_clients";
+
+pub struct TabsMigrationLogic;
+
+impl MigrationLogic for TabsMigrationLogic {
+    const NAME: &'static str = "tabs storage db";
+    const END_VERSION: u32 = 2;
+
+    fn prepare(&self, conn: &Connection, _db_empty: bool) -> MigrationResult<()> {
+        let initial_pragmas = "
+            -- We don't care about temp tables being persisted to disk.
+            PRAGMA temp_store = 2;
+            -- we unconditionally want write-ahead-logging mode.
+            PRAGMA journal_mode=WAL;
+            -- foreign keys seem worth enforcing (and again, we don't care in practice)
+            PRAGMA foreign_keys = ON;
+        ";
+        conn.execute_batch(initial_pragmas)?;
+        // This is where we'd define our sql functions if we had any!
+        conn.set_prepared_statement_cache_capacity(128);
+        Ok(())
+    }
+
+    fn init(&self, db: &Transaction<'_>) -> MigrationResult<()> {
+        log::debug!("Creating schemas");
+        db.execute_all(&[CREATE_SCHEMA_SQL, CREATE_META_TABLE_SQL])?;
+        Ok(())
+    }
+
+    fn upgrade_from(&self, db: &Transaction<'_>, version: u32) -> MigrationResult<()> {
+        match version {
+            1 => upgrade_from_v1(db),
+            _ => Err(MigrationError::IncompatibleVersion(version)),
+        }
+    }
+}
+
+fn upgrade_from_v1(db: &Connection) -> MigrationResult<()> {
+    // The previous version stored the entire payload in one row
+    // and cleared on each sync -- it's fine to just drop it
+    db.execute_batch("DROP TABLE tabs;")?;
+    // Recreate the world
+    db.execute_all(&[CREATE_SCHEMA_SQL, CREATE_META_TABLE_SQL])?;
+    Ok(())
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::storage::TabsStorage;
+    use rusqlite::OptionalExtension;
+    use serde_json::json;
+    use sql_support::open_database::test_utils::MigratedDatabaseFile;
+
+    const CREATE_V1_SCHEMA_SQL: &str = "
+        CREATE TABLE IF NOT EXISTS tabs (
+            payload TEXT NOT NULL
+        );
+        PRAGMA user_version=1;
+    ";
+
+    #[test]
+    fn test_create_schema_twice() {
+        let mut db = TabsStorage::new_with_mem_path("test");
+        let conn = db.open_or_create().unwrap();
+        conn.execute_batch(CREATE_SCHEMA_SQL)
+            .expect("should allow running twice");
+    }
+
+    #[test]
+    fn test_tabs_db_upgrade_from_v1() {
+        let db_file = MigratedDatabaseFile::new(TabsMigrationLogic, CREATE_V1_SCHEMA_SQL);
+        db_file.run_all_upgrades();
+        // Verify we can open the DB just fine, since migration is essentially a drop
+        // we don't need to check any data integrity
+        let mut storage = TabsStorage::new(db_file.path);
+        storage.open_or_create().unwrap();
+        assert!(storage.open_if_exists().unwrap().is_some());
+
+        let test_payload = json!({
+            "id": "device-with-a-tab",
+            "clientName": "device with a tab",
+            "tabs": [{
+                "title": "the title",
+                "urlHistory": [
+                    "https://mozilla.org/"
+                ],
+                "icon": "https://mozilla.org/icon",
+                "lastUsed": 1643764207,
+            }]
+        });
+        let db = storage.open_if_exists().unwrap().unwrap();
+        // We should be able to insert without a SQL error after upgrade
+        db.execute(
+            "INSERT INTO tabs (guid, record, last_modified) VALUES (:guid, :record, :last_modified);",
+            rusqlite::named_params! {
+                ":guid": "my-device",
+                ":record": serde_json::to_string(&test_payload).unwrap(),
+                ":last_modified": "1643764207"
+            },
+        )
+        .unwrap();
+
+        let row: Option<String> = db
+            .query_row("SELECT guid FROM tabs;", [], |row| row.get(0))
+            .optional()
+            .unwrap();
+        // Verify we can query for a valid guid now
+        assert_eq!(row.unwrap(), "my-device");
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/tabs/storage.rs.html b/book/rust-docs/src/tabs/storage.rs.html new file mode 100644 index 0000000000..9d98c8e38f --- /dev/null +++ b/book/rust-docs/src/tabs/storage.rs.html @@ -0,0 +1,1455 @@ +storage.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
+540
+541
+542
+543
+544
+545
+546
+547
+548
+549
+550
+551
+552
+553
+554
+555
+556
+557
+558
+559
+560
+561
+562
+563
+564
+565
+566
+567
+568
+569
+570
+571
+572
+573
+574
+575
+576
+577
+578
+579
+580
+581
+582
+583
+584
+585
+586
+587
+588
+589
+590
+591
+592
+593
+594
+595
+596
+597
+598
+599
+600
+601
+602
+603
+604
+605
+606
+607
+608
+609
+610
+611
+612
+613
+614
+615
+616
+617
+618
+619
+620
+621
+622
+623
+624
+625
+626
+627
+628
+629
+630
+631
+632
+633
+634
+635
+636
+637
+638
+639
+640
+641
+642
+643
+644
+645
+646
+647
+648
+649
+650
+651
+652
+653
+654
+655
+656
+657
+658
+659
+660
+661
+662
+663
+664
+665
+666
+667
+668
+669
+670
+671
+672
+673
+674
+675
+676
+677
+678
+679
+680
+681
+682
+683
+684
+685
+686
+687
+688
+689
+690
+691
+692
+693
+694
+695
+696
+697
+698
+699
+700
+701
+702
+703
+704
+705
+706
+707
+708
+709
+710
+711
+712
+713
+714
+715
+716
+717
+718
+719
+720
+721
+722
+723
+724
+725
+726
+727
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// From https://searchfox.org/mozilla-central/rev/ea63a0888d406fae720cf24f4727d87569a8cab5/services/sync/modules/constants.js#75
+const URI_LENGTH_MAX: usize = 65536;
+// https://searchfox.org/mozilla-central/rev/ea63a0888d406fae720cf24f4727d87569a8cab5/services/sync/modules/engines/tabs.js#8
+const TAB_ENTRIES_LIMIT: usize = 5;
+
+use crate::error::*;
+use crate::schema;
+use crate::sync::record::TabsRecord;
+use crate::DeviceType;
+use rusqlite::{
+    types::{FromSql, ToSql},
+    Connection, OpenFlags,
+};
+use serde_derive::{Deserialize, Serialize};
+use sql_support::open_database::{self, open_database_with_flags};
+use sql_support::ConnExt;
+use std::cell::RefCell;
+use std::collections::HashMap;
+use std::path::{Path, PathBuf};
+use sync15::{RemoteClient, ServerTimestamp};
+pub type TabsDeviceType = crate::DeviceType;
+pub type RemoteTabRecord = RemoteTab;
+
+pub(crate) const TABS_CLIENT_TTL: u32 = 15_552_000; // 180 days, same as CLIENTS_TTL
+const FAR_FUTURE: i64 = 4_102_405_200_000; // 2100/01/01
+const MAX_PAYLOAD_SIZE: usize = 512 * 1024; // Twice as big as desktop, still smaller than server max (2MB)
+const MAX_TITLE_CHAR_LENGTH: usize = 512; // We put an upper limit on title sizes for tabs to reduce memory
+
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub struct RemoteTab {
+    pub title: String,
+    pub url_history: Vec<String>,
+    pub icon: Option<String>,
+    pub last_used: i64, // In ms.
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize)]
+pub struct ClientRemoteTabs {
+    // The fxa_device_id of the client. *Should not* come from the id in the `clients` collection,
+    // because that may or may not be the fxa_device_id (currently, it will not be for desktop
+    // records.)
+    pub client_id: String,
+    pub client_name: String,
+    #[serde(
+        default = "devicetype_default_deser",
+        skip_serializing_if = "devicetype_is_unknown"
+    )]
+    pub device_type: DeviceType,
+    // serde default so we can read old rows that didn't persist this.
+    #[serde(default)]
+    pub last_modified: i64,
+    pub remote_tabs: Vec<RemoteTab>,
+}
+
+fn devicetype_default_deser() -> DeviceType {
+    // replace with `DeviceType::default_deser` once #4861 lands.
+    DeviceType::Unknown
+}
+
+// Unlike most other uses-cases, here we do allow serializing ::Unknown, but skip it.
+fn devicetype_is_unknown(val: &DeviceType) -> bool {
+    matches!(val, DeviceType::Unknown)
+}
+
+// Tabs has unique requirements for storage:
+// * The "local_tabs" exist only so we can sync them out. There's no facility to
+//   query "local tabs", so there's no need to store these persistently - ie, they
+//   are write-only.
+// * The "remote_tabs" exist purely for incoming items via sync - there's no facility
+//   to set them locally - they are read-only.
+// Note that this means a database is only actually needed after Sync fetches remote tabs,
+// and because sync users are in the minority, the use of a database here is purely
+// optional and created on demand. The implication here is that asking for the "remote tabs"
+// when no database exists is considered a normal situation and just implies no remote tabs exist.
+// (Note however we don't attempt to remove the database when no remote tabs exist, so having
+// no remote tabs in an existing DB is also a normal situation)
+pub struct TabsStorage {
+    local_tabs: RefCell<Option<Vec<RemoteTab>>>,
+    db_path: PathBuf,
+    db_connection: Option<Connection>,
+}
+
+impl TabsStorage {
+    pub fn new(db_path: impl AsRef<Path>) -> Self {
+        Self {
+            local_tabs: RefCell::default(),
+            db_path: db_path.as_ref().to_path_buf(),
+            db_connection: None,
+        }
+    }
+
+    /// Arrange for a new memory-based TabsStorage. As per other DB semantics, creating
+    /// this isn't enough to actually create the db!
+    pub fn new_with_mem_path(db_path: &str) -> Self {
+        let name = PathBuf::from(format!("file:{}?mode=memory&cache=shared", db_path));
+        Self::new(name)
+    }
+
+    /// If a DB file exists, open and return it.
+    pub fn open_if_exists(&mut self) -> Result<Option<&Connection>> {
+        if let Some(ref existing) = self.db_connection {
+            return Ok(Some(existing));
+        }
+        let flags = OpenFlags::SQLITE_OPEN_NO_MUTEX
+            | OpenFlags::SQLITE_OPEN_URI
+            | OpenFlags::SQLITE_OPEN_READ_WRITE;
+        match open_database_with_flags(
+            self.db_path.clone(),
+            flags,
+            &crate::schema::TabsMigrationLogic,
+        ) {
+            Ok(conn) => {
+                self.db_connection = Some(conn);
+                Ok(self.db_connection.as_ref())
+            }
+            Err(open_database::Error::SqlError(rusqlite::Error::SqliteFailure(code, _)))
+                if code.code == rusqlite::ErrorCode::CannotOpen =>
+            {
+                Ok(None)
+            }
+            Err(e) => Err(e.into()),
+        }
+    }
+
+    /// Open and return the DB, creating it if necessary.
+    pub fn open_or_create(&mut self) -> Result<&Connection> {
+        if let Some(ref existing) = self.db_connection {
+            return Ok(existing);
+        }
+        let flags = OpenFlags::SQLITE_OPEN_NO_MUTEX
+            | OpenFlags::SQLITE_OPEN_URI
+            | OpenFlags::SQLITE_OPEN_READ_WRITE
+            | OpenFlags::SQLITE_OPEN_CREATE;
+        let conn = open_database_with_flags(
+            self.db_path.clone(),
+            flags,
+            &crate::schema::TabsMigrationLogic,
+        )?;
+        self.db_connection = Some(conn);
+        Ok(self.db_connection.as_ref().unwrap())
+    }
+
+    pub fn update_local_state(&mut self, local_state: Vec<RemoteTab>) {
+        self.local_tabs.borrow_mut().replace(local_state);
+    }
+
+    // We try our best to fit as many tabs in a payload as possible, this includes
+    // limiting the url history entries, title character count and finally drop enough tabs
+    // until we have small enough payload that the server will accept
+    pub fn prepare_local_tabs_for_upload(&self) -> Option<Vec<RemoteTab>> {
+        if let Some(local_tabs) = self.local_tabs.borrow().as_ref() {
+            let mut sanitized_tabs: Vec<RemoteTab> = local_tabs
+                .iter()
+                .cloned()
+                .filter_map(|mut tab| {
+                    if tab.url_history.is_empty() || !is_url_syncable(&tab.url_history[0]) {
+                        return None;
+                    }
+                    let mut sanitized_history = Vec::with_capacity(TAB_ENTRIES_LIMIT);
+                    for url in tab.url_history {
+                        if sanitized_history.len() == TAB_ENTRIES_LIMIT {
+                            break;
+                        }
+                        if is_url_syncable(&url) {
+                            sanitized_history.push(url);
+                        }
+                    }
+
+                    tab.url_history = sanitized_history;
+                    // Potentially truncate the title to some limit
+                    tab.title = slice_up_to(tab.title, MAX_TITLE_CHAR_LENGTH);
+                    Some(tab)
+                })
+                .collect();
+            // Sort the tabs so when we trim tabs it's the oldest tabs
+            sanitized_tabs.sort_by(|a, b| b.last_used.cmp(&a.last_used));
+            // If trimming the tab length failed for some reason, just return the untrimmed tabs
+            trim_tabs_length(&mut sanitized_tabs, MAX_PAYLOAD_SIZE);
+            return Some(sanitized_tabs);
+        }
+        None
+    }
+
+    pub fn get_remote_tabs(&mut self) -> Option<Vec<ClientRemoteTabs>> {
+        let conn = match self.open_if_exists() {
+            Err(e) => {
+                error_support::report_error!(
+                    "tabs-read-remote",
+                    "Failed to read remote tabs: {}",
+                    e
+                );
+                return None;
+            }
+            Ok(None) => return None,
+            Ok(Some(conn)) => conn,
+        };
+
+        let records: Vec<(TabsRecord, ServerTimestamp)> = match conn.query_rows_and_then_cached(
+            "SELECT record, last_modified FROM tabs",
+            [],
+            |row| -> Result<_> {
+                Ok((
+                    serde_json::from_str(&row.get::<_, String>(0)?)?,
+                    ServerTimestamp(row.get::<_, i64>(1)?),
+                ))
+            },
+        ) {
+            Ok(records) => records,
+            Err(e) => {
+                error_support::report_error!("tabs-read-remote", "Failed to read database: {}", e);
+                return None;
+            }
+        };
+        let mut crts: Vec<ClientRemoteTabs> = Vec::new();
+        let remote_clients: HashMap<String, RemoteClient> =
+            match self.get_meta::<String>(schema::REMOTE_CLIENTS_KEY) {
+                Err(e) => {
+                    error_support::report_error!(
+                        "tabs-read-remote",
+                        "Failed to get remote clients: {}",
+                        e
+                    );
+                    return None;
+                }
+                // We don't return early here since we still store tabs even if we don't
+                // "know" about the client it's associated with (incase it becomes available later)
+                Ok(None) => HashMap::default(),
+                Ok(Some(json)) => serde_json::from_str(&json).unwrap(),
+            };
+        for (record, last_modified) in records {
+            let id = record.id.clone();
+            let crt = if let Some(remote_client) = remote_clients.get(&id) {
+                ClientRemoteTabs::from_record_with_remote_client(
+                    remote_client
+                        .fxa_device_id
+                        .as_ref()
+                        .unwrap_or(&id)
+                        .to_owned(),
+                    last_modified,
+                    remote_client,
+                    record,
+                )
+            } else {
+                // A record with a device that's not in our remote clients seems unlikely, but
+                // could happen - in most cases though, it will be due to a disconnected client -
+                // so we really should consider just dropping it? (Sadly though, it does seem
+                // possible it's actually a very recently connected client, so we keep it)
+                // We should get rid of this eventually - https://github.com/mozilla/application-services/issues/5199
+                log::info!(
+                    "Storing tabs from a client that doesn't appear in the devices list: {}",
+                    id,
+                );
+                ClientRemoteTabs::from_record(id, last_modified, record)
+            };
+            crts.push(crt);
+        }
+        Some(crts)
+    }
+
+    // Keep DB from growing infinitely since we only ask for records since our last sync
+    // and may or may not know about the client it's associated with -- but we could at some point
+    // and should start returning those tabs immediately. If that client hasn't been seen in 3 weeks,
+    // we remove it until it reconnects
+    pub fn remove_stale_clients(&mut self) -> Result<()> {
+        let last_sync = self.get_meta::<i64>(schema::LAST_SYNC_META_KEY)?;
+        if let Some(conn) = self.open_if_exists()? {
+            if let Some(last_sync) = last_sync {
+                let client_ttl_ms = (TABS_CLIENT_TTL as i64) * 1000;
+                // On desktop, a quick write temporarily sets the last_sync to FAR_FUTURE
+                // but if it doesn't set it back to the original (crash, etc) it
+                // means we'll most likely trash all our records (as it's more than any TTL we'd ever do)
+                // so we need to detect this for now until we have native quick write support
+                if last_sync - client_ttl_ms >= 0 && last_sync != (FAR_FUTURE * 1000) {
+                    let tx = conn.unchecked_transaction()?;
+                    let num_removed = tx.execute_cached(
+                        "DELETE FROM tabs WHERE last_modified <= :last_sync - :ttl",
+                        rusqlite::named_params! {
+                            ":last_sync": last_sync,
+                            ":ttl": client_ttl_ms,
+                        },
+                    )?;
+                    log::info!(
+                        "removed {} stale clients (threshold was {})",
+                        num_removed,
+                        last_sync - client_ttl_ms
+                    );
+                    tx.commit()?;
+                }
+            }
+        }
+        Ok(())
+    }
+}
+
+impl TabsStorage {
+    pub(crate) fn replace_remote_tabs(
+        &mut self,
+        // This is a tuple because we need to know what the server reports
+        // as the last time a record was modified
+        new_remote_tabs: Vec<(TabsRecord, ServerTimestamp)>,
+    ) -> Result<()> {
+        let connection = self.open_or_create()?;
+        let tx = connection.unchecked_transaction()?;
+
+        // For tabs it's fine if we override the existing tabs for a remote
+        // there can only ever be one record for each client
+        for remote_tab in new_remote_tabs {
+            let record = remote_tab.0;
+            let last_modified = remote_tab.1;
+            log::info!(
+                "inserting tab for device {}, last modified at {}",
+                record.id,
+                last_modified.as_millis()
+            );
+            tx.execute_cached(
+                "INSERT OR REPLACE INTO tabs (guid, record, last_modified) VALUES (:guid, :record, :last_modified);",
+                rusqlite::named_params! {
+                    ":guid": &record.id,
+                    ":record": serde_json::to_string(&record).expect("tabs don't fail to serialize"),
+                    ":last_modified": last_modified.as_millis()
+                },
+            )?;
+        }
+        tx.commit()?;
+        Ok(())
+    }
+
+    pub(crate) fn wipe_remote_tabs(&mut self) -> Result<()> {
+        if let Some(db) = self.open_if_exists()? {
+            db.execute_batch("DELETE FROM tabs")?;
+        }
+        Ok(())
+    }
+
+    pub(crate) fn wipe_local_tabs(&self) {
+        self.local_tabs.replace(None);
+    }
+
+    pub(crate) fn put_meta(&mut self, key: &str, value: &dyn ToSql) -> Result<()> {
+        let db = self.open_or_create()?;
+        db.execute_cached(
+            "REPLACE INTO moz_meta (key, value) VALUES (:key, :value)",
+            &[(":key", &key as &dyn ToSql), (":value", value)],
+        )?;
+        Ok(())
+    }
+
+    pub(crate) fn get_meta<T: FromSql>(&mut self, key: &str) -> Result<Option<T>> {
+        match self.open_if_exists() {
+            Ok(Some(db)) => {
+                let res = db.try_query_one(
+                    "SELECT value FROM moz_meta WHERE key = :key",
+                    &[(":key", &key)],
+                    true,
+                )?;
+                Ok(res)
+            }
+            Err(e) => Err(e),
+            Ok(None) => Ok(None),
+        }
+    }
+
+    pub(crate) fn delete_meta(&mut self, key: &str) -> Result<()> {
+        if let Some(db) = self.open_if_exists()? {
+            db.execute_cached("DELETE FROM moz_meta WHERE key = :key", &[(":key", &key)])?;
+        }
+        Ok(())
+    }
+}
+
+// Trim the amount of tabs in a list to fit the specified memory size
+fn trim_tabs_length(tabs: &mut Vec<RemoteTab>, payload_size_max_bytes: usize) {
+    // Ported from https://searchfox.org/mozilla-central/rev/84fb1c4511312a0b9187f647d90059e3a6dd27f8/services/sync/modules/util.sys.mjs#422
+    // See bug 535326 comment 8 for an explanation of the estimation
+    let max_serialized_size = (payload_size_max_bytes / 4) * 3 - 1500;
+    let size = compute_serialized_size(tabs);
+    if size > max_serialized_size {
+        // Estimate a little more than the direct fraction to maximize packing
+        let cutoff = (tabs.len() * max_serialized_size) / size;
+        tabs.truncate(cutoff);
+
+        // Keep dropping off the last entry until the data fits.
+        while compute_serialized_size(tabs) > max_serialized_size {
+            tabs.pop();
+        }
+    }
+}
+
+fn compute_serialized_size(v: &Vec<RemoteTab>) -> usize {
+    serde_json::to_string(v).unwrap_or_default().len()
+}
+
+// Similar to places/utils.js
+// This method ensures we safely truncate a string up to a certain max_len while
+// respecting char bounds to prevent rust panics. If we do end up truncating, we
+// append an ellipsis to the string
+pub fn slice_up_to(s: String, max_len: usize) -> String {
+    if max_len >= s.len() {
+        return s;
+    }
+
+    let ellipsis = '\u{2026}';
+    // Ensure we leave space for the ellipsis while still being under the max
+    let mut idx = max_len - ellipsis.len_utf8();
+    while !s.is_char_boundary(idx) {
+        idx -= 1;
+    }
+    let mut new_str = s[..idx].to_string();
+    new_str.push(ellipsis);
+    new_str
+}
+
+// Try to keep in sync with https://searchfox.org/mozilla-central/rev/2ad13433da20a0749e1e9a10ec0ab49b987c2c8e/modules/libpref/init/all.js#3927
+fn is_url_syncable(url: &str) -> bool {
+    url.len() <= URI_LENGTH_MAX
+        && !(url.starts_with("about:")
+            || url.starts_with("resource:")
+            || url.starts_with("chrome:")
+            || url.starts_with("wyciwyg:")
+            || url.starts_with("blob:")
+            || url.starts_with("file:")
+            || url.starts_with("moz-extension:")
+            || url.starts_with("data:"))
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::sync::record::TabsRecordTab;
+
+    #[test]
+    fn test_is_url_syncable() {
+        assert!(is_url_syncable("https://bobo.com"));
+        assert!(is_url_syncable("ftp://bobo.com"));
+        assert!(!is_url_syncable("about:blank"));
+        // XXX - this smells wrong - we should insist on a valid complete URL?
+        assert!(is_url_syncable("aboutbobo.com"));
+        assert!(!is_url_syncable("file:///Users/eoger/bobo"));
+    }
+
+    #[test]
+    fn test_open_if_exists_no_file() {
+        let dir = tempfile::tempdir().unwrap();
+        let db_name = dir.path().join("test_open_for_read_no_file.db");
+        let mut storage = TabsStorage::new(db_name.clone());
+        assert!(storage.open_if_exists().unwrap().is_none());
+        storage.open_or_create().unwrap(); // will have created it.
+                                           // make a new storage, but leave the file alone.
+        let mut storage = TabsStorage::new(db_name);
+        // db file exists, so opening for read should open it.
+        assert!(storage.open_if_exists().unwrap().is_some());
+    }
+
+    #[test]
+    fn test_tabs_meta() {
+        let dir = tempfile::tempdir().unwrap();
+        let db_name = dir.path().join("test_tabs_meta.db");
+        let mut db = TabsStorage::new(db_name);
+        let test_key = "TEST KEY A";
+        let test_value = "TEST VALUE A";
+        let test_key2 = "TEST KEY B";
+        let test_value2 = "TEST VALUE B";
+
+        // should automatically make the DB if one doesn't exist
+        db.put_meta(test_key, &test_value).unwrap();
+        db.put_meta(test_key2, &test_value2).unwrap();
+
+        let retrieved_value: String = db.get_meta(test_key).unwrap().expect("test value");
+        let retrieved_value2: String = db.get_meta(test_key2).unwrap().expect("test value 2");
+
+        assert_eq!(retrieved_value, test_value);
+        assert_eq!(retrieved_value2, test_value2);
+
+        // check that the value of an existing key can be updated
+        let test_value3 = "TEST VALUE C";
+        db.put_meta(test_key, &test_value3).unwrap();
+
+        let retrieved_value3: String = db.get_meta(test_key).unwrap().expect("test value 3");
+
+        assert_eq!(retrieved_value3, test_value3);
+
+        // check that a deleted key is not retrieved
+        db.delete_meta(test_key).unwrap();
+        let retrieved_value4: Option<String> = db.get_meta(test_key).unwrap();
+        assert!(retrieved_value4.is_none());
+    }
+
+    #[test]
+    fn test_prepare_local_tabs_for_upload() {
+        let mut storage = TabsStorage::new_with_mem_path("test_prepare_local_tabs_for_upload");
+        assert_eq!(storage.prepare_local_tabs_for_upload(), None);
+        storage.update_local_state(vec![
+            RemoteTab {
+                title: "".to_owned(),
+                url_history: vec!["about:blank".to_owned(), "https://foo.bar".to_owned()],
+                icon: None,
+                last_used: 0,
+            },
+            RemoteTab {
+                title: "".to_owned(),
+                url_history: vec![
+                    "https://foo.bar".to_owned(),
+                    "about:blank".to_owned(),
+                    "about:blank".to_owned(),
+                    "about:blank".to_owned(),
+                    "about:blank".to_owned(),
+                    "about:blank".to_owned(),
+                    "about:blank".to_owned(),
+                    "about:blank".to_owned(),
+                ],
+                icon: None,
+                last_used: 0,
+            },
+            RemoteTab {
+                title: "".to_owned(),
+                url_history: vec![
+                    "https://foo.bar".to_owned(),
+                    "about:blank".to_owned(),
+                    "https://foo2.bar".to_owned(),
+                    "https://foo3.bar".to_owned(),
+                    "https://foo4.bar".to_owned(),
+                    "https://foo5.bar".to_owned(),
+                    "https://foo6.bar".to_owned(),
+                ],
+                icon: None,
+                last_used: 0,
+            },
+            RemoteTab {
+                title: "".to_owned(),
+                url_history: vec![],
+                icon: None,
+                last_used: 0,
+            },
+        ]);
+        assert_eq!(
+            storage.prepare_local_tabs_for_upload(),
+            Some(vec![
+                RemoteTab {
+                    title: "".to_owned(),
+                    url_history: vec!["https://foo.bar".to_owned()],
+                    icon: None,
+                    last_used: 0,
+                },
+                RemoteTab {
+                    title: "".to_owned(),
+                    url_history: vec![
+                        "https://foo.bar".to_owned(),
+                        "https://foo2.bar".to_owned(),
+                        "https://foo3.bar".to_owned(),
+                        "https://foo4.bar".to_owned(),
+                        "https://foo5.bar".to_owned()
+                    ],
+                    icon: None,
+                    last_used: 0,
+                },
+            ])
+        );
+    }
+    #[test]
+    fn test_trimming_tab_title() {
+        let mut storage = TabsStorage::new_with_mem_path("test_prepare_local_tabs_for_upload");
+        assert_eq!(storage.prepare_local_tabs_for_upload(), None);
+        storage.update_local_state(vec![RemoteTab {
+            title: "a".repeat(MAX_TITLE_CHAR_LENGTH + 10), // Fill a string more than max
+            url_history: vec!["https://foo.bar".to_owned()],
+            icon: None,
+            last_used: 0,
+        }]);
+        let ellipsis_char = '\u{2026}';
+        let mut truncated_title = "a".repeat(MAX_TITLE_CHAR_LENGTH - ellipsis_char.len_utf8());
+        truncated_title.push(ellipsis_char);
+        assert_eq!(
+            storage.prepare_local_tabs_for_upload(),
+            Some(vec![
+                // title trimmed to 50 characters
+                RemoteTab {
+                    title: truncated_title, // title was trimmed to only max char length
+                    url_history: vec!["https://foo.bar".to_owned()],
+                    icon: None,
+                    last_used: 0,
+                },
+            ])
+        );
+    }
+    #[test]
+    fn test_utf8_safe_title_trim() {
+        let mut storage = TabsStorage::new_with_mem_path("test_prepare_local_tabs_for_upload");
+        assert_eq!(storage.prepare_local_tabs_for_upload(), None);
+        storage.update_local_state(vec![
+            RemoteTab {
+                title: "😍".repeat(MAX_TITLE_CHAR_LENGTH + 10), // Fill a string more than max
+                url_history: vec!["https://foo.bar".to_owned()],
+                icon: None,
+                last_used: 0,
+            },
+            RemoteTab {
+                title: "を".repeat(MAX_TITLE_CHAR_LENGTH + 5), // Fill a string more than max
+                url_history: vec!["https://foo_jp.bar".to_owned()],
+                icon: None,
+                last_used: 0,
+            },
+        ]);
+        let ellipsis_char = '\u{2026}';
+        // (MAX_TITLE_CHAR_LENGTH - ellipsis / "😍" bytes)
+        let mut truncated_title = "😍".repeat(127);
+        // (MAX_TITLE_CHAR_LENGTH - ellipsis / "を" bytes)
+        let mut truncated_jp_title = "を".repeat(169);
+        truncated_title.push(ellipsis_char);
+        truncated_jp_title.push(ellipsis_char);
+        let remote_tabs = storage.prepare_local_tabs_for_upload().unwrap();
+        assert_eq!(
+            remote_tabs,
+            vec![
+                RemoteTab {
+                    title: truncated_title, // title was trimmed to only max char length
+                    url_history: vec!["https://foo.bar".to_owned()],
+                    icon: None,
+                    last_used: 0,
+                },
+                RemoteTab {
+                    title: truncated_jp_title, // title was trimmed to only max char length
+                    url_history: vec!["https://foo_jp.bar".to_owned()],
+                    icon: None,
+                    last_used: 0,
+                },
+            ]
+        );
+        // We should be less than max
+        assert!(remote_tabs[0].title.chars().count() <= MAX_TITLE_CHAR_LENGTH);
+        assert!(remote_tabs[1].title.chars().count() <= MAX_TITLE_CHAR_LENGTH);
+    }
+    #[test]
+    fn test_trim_tabs_length() {
+        let mut storage = TabsStorage::new_with_mem_path("test_prepare_local_tabs_for_upload");
+        assert_eq!(storage.prepare_local_tabs_for_upload(), None);
+        let mut too_many_tabs: Vec<RemoteTab> = Vec::new();
+        for n in 1..5000 {
+            too_many_tabs.push(RemoteTab {
+                title: "aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa aaaa" //50 characters
+                    .to_owned(),
+                url_history: vec![format!("https://foo{}.bar", n)],
+                icon: None,
+                last_used: 0,
+            });
+        }
+        let tabs_mem_size = compute_serialized_size(&too_many_tabs);
+        // ensure we are definitely over the payload limit
+        assert!(tabs_mem_size > MAX_PAYLOAD_SIZE);
+        // Add our over-the-limit tabs to the local state
+        storage.update_local_state(too_many_tabs.clone());
+        // prepare_local_tabs_for_upload did the trimming we needed to get under payload size
+        let tabs_to_upload = &storage.prepare_local_tabs_for_upload().unwrap();
+        assert!(compute_serialized_size(tabs_to_upload) <= MAX_PAYLOAD_SIZE);
+    }
+    // Helper struct to model what's stored in the DB
+    struct TabsSQLRecord {
+        guid: String,
+        record: TabsRecord,
+        last_modified: i64,
+    }
+    #[test]
+    fn test_remove_stale_clients() {
+        let dir = tempfile::tempdir().unwrap();
+        let db_name = dir.path().join("test_remove_stale_clients.db");
+        let mut storage = TabsStorage::new(db_name);
+        storage.open_or_create().unwrap();
+        assert!(storage.open_if_exists().unwrap().is_some());
+
+        let records = vec![
+            TabsSQLRecord {
+                guid: "device-1".to_string(),
+                record: TabsRecord {
+                    id: "device-1".to_string(),
+                    client_name: "Device #1".to_string(),
+                    tabs: vec![TabsRecordTab {
+                        title: "the title".to_string(),
+                        url_history: vec!["https://mozilla.org/".to_string()],
+                        icon: Some("https://mozilla.org/icon".to_string()),
+                        last_used: 1643764207000,
+                    }],
+                },
+                last_modified: 1643764207000,
+            },
+            TabsSQLRecord {
+                guid: "device-outdated".to_string(),
+                record: TabsRecord {
+                    id: "device-outdated".to_string(),
+                    client_name: "Device outdated".to_string(),
+                    tabs: vec![TabsRecordTab {
+                        title: "the title".to_string(),
+                        url_history: vec!["https://mozilla.org/".to_string()],
+                        icon: Some("https://mozilla.org/icon".to_string()),
+                        last_used: 1643764207000,
+                    }],
+                },
+                last_modified: 1443764207000, // old
+            },
+        ];
+        let db = storage.open_if_exists().unwrap().unwrap();
+        for record in records {
+            db.execute(
+                "INSERT INTO tabs (guid, record, last_modified) VALUES (:guid, :record, :last_modified);",
+                rusqlite::named_params! {
+                    ":guid": &record.guid,
+                    ":record": serde_json::to_string(&record.record).unwrap(),
+                    ":last_modified": &record.last_modified,
+                },
+            ).unwrap();
+        }
+        // pretend we just synced
+        let last_synced = 1643764207000_i64;
+        storage
+            .put_meta(schema::LAST_SYNC_META_KEY, &last_synced)
+            .unwrap();
+        storage.remove_stale_clients().unwrap();
+
+        let remote_tabs = storage.get_remote_tabs().unwrap();
+        // We should've removed the outdated device
+        assert_eq!(remote_tabs.len(), 1);
+        // Assert the correct record is still being returned
+        assert_eq!(remote_tabs[0].client_id, "device-1");
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/tabs/store.rs.html b/book/rust-docs/src/tabs/store.rs.html new file mode 100644 index 0000000000..9390b6a3cb --- /dev/null +++ b/book/rust-docs/src/tabs/store.rs.html @@ -0,0 +1,83 @@ +store.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::storage::{ClientRemoteTabs, RemoteTab, TabsStorage};
+use std::path::Path;
+use std::sync::Mutex;
+
+pub struct TabsStore {
+    pub storage: Mutex<TabsStorage>,
+}
+
+impl TabsStore {
+    pub fn new(db_path: impl AsRef<Path>) -> Self {
+        Self {
+            storage: Mutex::new(TabsStorage::new(db_path)),
+        }
+    }
+
+    pub fn new_with_mem_path(db_path: &str) -> Self {
+        Self {
+            storage: Mutex::new(TabsStorage::new_with_mem_path(db_path)),
+        }
+    }
+
+    pub fn set_local_tabs(&self, local_state: Vec<RemoteTab>) {
+        self.storage.lock().unwrap().update_local_state(local_state);
+    }
+
+    // like remote_tabs, but serves the uniffi layer
+    pub fn get_all(&self) -> Vec<ClientRemoteTabs> {
+        match self.remote_tabs() {
+            Some(list) => list,
+            None => vec![],
+        }
+    }
+
+    pub fn remote_tabs(&self) -> Option<Vec<ClientRemoteTabs>> {
+        self.storage.lock().unwrap().get_remote_tabs()
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/tabs/sync/bridge.rs.html b/book/rust-docs/src/tabs/sync/bridge.rs.html new file mode 100644 index 0000000000..29540799c3 --- /dev/null +++ b/book/rust-docs/src/tabs/sync/bridge.rs.html @@ -0,0 +1,631 @@ +bridge.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::sync::engine::TabsEngine;
+use crate::TabsStore;
+use anyhow::Result;
+use std::sync::Arc;
+use sync15::bso::{IncomingBso, OutgoingBso};
+use sync15::engine::{BridgedEngine, BridgedEngineAdaptor};
+use sync15::ServerTimestamp;
+use sync_guid::Guid as SyncGuid;
+
+impl TabsStore {
+    // Returns a bridged sync engine for Desktop for this store.
+    pub fn bridged_engine(self: Arc<Self>) -> Arc<TabsBridgedEngine> {
+        let engine = TabsEngine::new(self);
+        let bridged_engine = TabsBridgedEngineAdaptor { engine };
+        Arc::new(TabsBridgedEngine::new(Box::new(bridged_engine)))
+    }
+}
+
+/// A bridged engine implements all the methods needed to make the
+/// `storage.sync` store work with Desktop's Sync implementation.
+/// Conceptually it's very similar to our SyncEngine and there's a BridgedEngineAdaptor
+/// trait we can implement to get a `BridgedEngine` from a `SyncEngine`, so that's
+/// what we do. See also #2841, which will finally unify them completely.
+struct TabsBridgedEngineAdaptor {
+    engine: TabsEngine,
+}
+
+impl BridgedEngineAdaptor for TabsBridgedEngineAdaptor {
+    fn last_sync(&self) -> Result<i64> {
+        Ok(self.engine.get_last_sync()?.unwrap_or_default().as_millis())
+    }
+
+    fn set_last_sync(&self, last_sync_millis: i64) -> Result<()> {
+        self.engine
+            .set_last_sync(ServerTimestamp::from_millis(last_sync_millis))
+    }
+
+    fn engine(&self) -> &dyn sync15::engine::SyncEngine {
+        &self.engine
+    }
+}
+
+// This is for uniffi to expose, and does nothing than delegate back to the trait.
+pub struct TabsBridgedEngine {
+    bridge_impl: Box<dyn BridgedEngine>,
+}
+
+impl TabsBridgedEngine {
+    pub fn new(bridge_impl: Box<dyn BridgedEngine>) -> Self {
+        Self { bridge_impl }
+    }
+
+    pub fn last_sync(&self) -> Result<i64> {
+        self.bridge_impl.last_sync()
+    }
+
+    pub fn set_last_sync(&self, last_sync: i64) -> Result<()> {
+        self.bridge_impl.set_last_sync(last_sync)
+    }
+
+    pub fn sync_id(&self) -> Result<Option<String>> {
+        self.bridge_impl.sync_id()
+    }
+
+    pub fn reset_sync_id(&self) -> Result<String> {
+        self.bridge_impl.reset_sync_id()
+    }
+
+    pub fn ensure_current_sync_id(&self, sync_id: &str) -> Result<String> {
+        self.bridge_impl.ensure_current_sync_id(sync_id)
+    }
+
+    pub fn prepare_for_sync(&self, client_data: &str) -> Result<()> {
+        self.bridge_impl.prepare_for_sync(client_data)
+    }
+
+    pub fn sync_started(&self) -> Result<()> {
+        self.bridge_impl.sync_started()
+    }
+
+    // Decode the JSON-encoded IncomingBso's that UniFFI passes to us
+    fn convert_incoming_bsos(&self, incoming: Vec<String>) -> Result<Vec<IncomingBso>> {
+        let mut bsos = Vec::with_capacity(incoming.len());
+        for inc in incoming {
+            bsos.push(serde_json::from_str::<IncomingBso>(&inc)?);
+        }
+        Ok(bsos)
+    }
+
+    // Encode OutgoingBso's into JSON for UniFFI
+    fn convert_outgoing_bsos(&self, outgoing: Vec<OutgoingBso>) -> Result<Vec<String>> {
+        let mut bsos = Vec::with_capacity(outgoing.len());
+        for e in outgoing {
+            bsos.push(serde_json::to_string(&e)?);
+        }
+        Ok(bsos)
+    }
+
+    pub fn store_incoming(&self, incoming: Vec<String>) -> Result<()> {
+        self.bridge_impl
+            .store_incoming(self.convert_incoming_bsos(incoming)?)
+    }
+
+    pub fn apply(&self) -> Result<Vec<String>> {
+        let apply_results = self.bridge_impl.apply()?;
+        self.convert_outgoing_bsos(apply_results.records)
+    }
+
+    pub fn set_uploaded(&self, server_modified_millis: i64, guids: Vec<SyncGuid>) -> Result<()> {
+        self.bridge_impl
+            .set_uploaded(server_modified_millis, &guids)
+    }
+
+    pub fn sync_finished(&self) -> Result<()> {
+        self.bridge_impl.sync_finished()
+    }
+
+    pub fn reset(&self) -> Result<()> {
+        self.bridge_impl.reset()
+    }
+
+    pub fn wipe(&self) -> Result<()> {
+        self.bridge_impl.wipe()
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::storage::{RemoteTab, TABS_CLIENT_TTL};
+    use crate::sync::record::TabsRecordTab;
+    use serde_json::json;
+    use std::collections::HashMap;
+    use sync15::{ClientData, DeviceType, RemoteClient};
+
+    // A copy of the normal "engine" tests but which go via the bridge
+    #[test]
+    fn test_sync_via_bridge() {
+        env_logger::try_init().ok();
+
+        let store = Arc::new(TabsStore::new_with_mem_path("test-bridge_incoming"));
+
+        // Set some local tabs for our device.
+        let my_tabs = vec![
+            RemoteTab {
+                title: "my first tab".to_string(),
+                url_history: vec!["http://1.com".to_string()],
+                icon: None,
+                last_used: 2,
+            },
+            RemoteTab {
+                title: "my second tab".to_string(),
+                url_history: vec!["http://2.com".to_string()],
+                icon: None,
+                last_used: 1,
+            },
+        ];
+        store.set_local_tabs(my_tabs.clone());
+
+        let bridge = store.bridged_engine();
+
+        let client_data = ClientData {
+            local_client_id: "my-device".to_string(),
+            recent_clients: HashMap::from([
+                (
+                    "my-device".to_string(),
+                    RemoteClient {
+                        fxa_device_id: None,
+                        device_name: "my device".to_string(),
+                        device_type: sync15::DeviceType::Unknown,
+                    },
+                ),
+                (
+                    "device-no-tabs".to_string(),
+                    RemoteClient {
+                        fxa_device_id: None,
+                        device_name: "device with no tabs".to_string(),
+                        device_type: DeviceType::Unknown,
+                    },
+                ),
+                (
+                    "device-with-a-tab".to_string(),
+                    RemoteClient {
+                        fxa_device_id: None,
+                        device_name: "device with a tab".to_string(),
+                        device_type: DeviceType::Unknown,
+                    },
+                ),
+            ]),
+        };
+        bridge
+            .prepare_for_sync(&serde_json::to_string(&client_data).unwrap())
+            .expect("should work");
+
+        let records = vec![
+            // my-device should be ignored by sync - here it is acting as what our engine last
+            // wrote, but the actual tabs in our store we set above are what should be used.
+            json!({
+                "id": "my-device",
+                "clientName": "my device",
+                "tabs": [{
+                    "title": "the title",
+                    "urlHistory": [
+                        "https://mozilla.org/"
+                    ],
+                    "icon": "https://mozilla.org/icon",
+                    "lastUsed": 1643764207
+                }]
+            }),
+            json!({
+                "id": "device-no-tabs",
+                "clientName": "device with no tabs",
+                "tabs": [],
+            }),
+            json!({
+                "id": "device-with-a-tab",
+                "clientName": "device with a tab",
+                "tabs": [{
+                    "title": "the title",
+                    "urlHistory": [
+                        "https://mozilla.org/"
+                    ],
+                    "icon": "https://mozilla.org/icon",
+                    "lastUsed": 1643764207
+                }]
+            }),
+            // This has the main payload as OK but the tabs part invalid.
+            json!({
+                "id": "device-with-invalid-tab",
+                "clientName": "device with a tab",
+                "tabs": [{
+                    "foo": "bar",
+                }]
+            }),
+            // We want this to be a valid payload but an invalid tab - so it needs an ID.
+            json!({
+                "id": "invalid-tab",
+                "foo": "bar"
+            }),
+        ];
+
+        let mut incoming = Vec::new();
+        for record in records {
+            // Annoyingly we can't use `IncomingEnvelope` directly as it intentionally doesn't
+            // support Serialize - so need to use explicit json.
+            let envelope = json!({
+                "id": record.get("id"),
+                "modified": 0,
+                "payload": serde_json::to_string(&record).unwrap(),
+            });
+            incoming.push(serde_json::to_string(&envelope).unwrap());
+        }
+
+        bridge.store_incoming(incoming).expect("should store");
+
+        let out = bridge.apply().expect("should apply");
+
+        assert_eq!(out.len(), 1);
+        let ours = serde_json::from_str::<serde_json::Value>(&out[0]).unwrap();
+        // As above, can't use `OutgoingEnvelope` as it doesn't Deserialize.
+        // First, convert my_tabs from the local `RemoteTab` to the Sync specific `TabsRecord`
+        let expected_tabs: Vec<TabsRecordTab> =
+            my_tabs.into_iter().map(|t| t.to_record_tab()).collect();
+        let expected = json!({
+            "id": "my-device".to_string(),
+            "payload": json!({
+                "id": "my-device".to_string(),
+                "clientName": "my device",
+                "tabs": serde_json::to_value(expected_tabs).unwrap(),
+            }).to_string(),
+            "ttl": TABS_CLIENT_TTL,
+        });
+
+        assert_eq!(ours, expected);
+        bridge.set_uploaded(1234, vec![]).unwrap();
+        assert_eq!(bridge.last_sync().unwrap(), 1234);
+    }
+
+    #[test]
+    fn test_sync_meta() {
+        env_logger::try_init().ok();
+
+        let store = Arc::new(TabsStore::new_with_mem_path("test-meta"));
+        let bridge = store.bridged_engine();
+
+        // Should not error or panic
+        assert_eq!(bridge.last_sync().unwrap(), 0);
+        bridge.set_last_sync(3).unwrap();
+        assert_eq!(bridge.last_sync().unwrap(), 3);
+
+        assert!(bridge.sync_id().unwrap().is_none());
+
+        bridge.ensure_current_sync_id("some_guid").unwrap();
+        assert_eq!(bridge.sync_id().unwrap(), Some("some_guid".to_string()));
+        // changing the sync ID should reset the timestamp
+        assert_eq!(bridge.last_sync().unwrap(), 0);
+        bridge.set_last_sync(3).unwrap();
+
+        bridge.reset_sync_id().unwrap();
+        // should now be a random guid.
+        assert_ne!(bridge.sync_id().unwrap(), Some("some_guid".to_string()));
+        // should have reset the last sync timestamp.
+        assert_eq!(bridge.last_sync().unwrap(), 0);
+        bridge.set_last_sync(3).unwrap();
+
+        // `reset` clears the guid and the timestamp
+        bridge.reset().unwrap();
+        assert_eq!(bridge.last_sync().unwrap(), 0);
+        assert!(bridge.sync_id().unwrap().is_none());
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/tabs/sync/engine.rs.html b/book/rust-docs/src/tabs/sync/engine.rs.html new file mode 100644 index 0000000000..a868192426 --- /dev/null +++ b/book/rust-docs/src/tabs/sync/engine.rs.html @@ -0,0 +1,1047 @@ +engine.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::schema;
+use crate::storage::{ClientRemoteTabs, RemoteTab, TABS_CLIENT_TTL};
+use crate::store::TabsStore;
+use crate::sync::record::{TabsRecord, TabsRecordTab};
+use anyhow::Result;
+use std::collections::HashMap;
+use std::sync::{Arc, Mutex, RwLock, Weak};
+use sync15::bso::{IncomingBso, OutgoingBso, OutgoingEnvelope};
+use sync15::engine::{
+    CollSyncIds, CollectionRequest, EngineSyncAssociation, SyncEngine, SyncEngineId,
+};
+use sync15::{telemetry, ClientData, CollectionName, DeviceType, RemoteClient, ServerTimestamp};
+use sync_guid::Guid;
+
+// Our "sync manager" will use whatever is stashed here.
+lazy_static::lazy_static! {
+    // Mutex: just taken long enough to update the inner stuff
+    static ref STORE_FOR_MANAGER: Mutex<Weak<TabsStore>> = Mutex::new(Weak::new());
+}
+
+/// Called by the sync manager to get a sync engine via the store previously
+/// registered with the sync manager.
+pub fn get_registered_sync_engine(
+    engine_id: &SyncEngineId,
+) -> Option<Box<dyn sync15::engine::SyncEngine>> {
+    let weak = STORE_FOR_MANAGER.lock().unwrap();
+    match weak.upgrade() {
+        None => None,
+        Some(store) => match engine_id {
+            SyncEngineId::Tabs => Some(Box::new(TabsEngine::new(Arc::clone(&store)))),
+            // panicing here seems reasonable - it's a static error if this
+            // it hit, not something that runtime conditions can influence.
+            _ => unreachable!("can't provide unknown engine: {}", engine_id),
+        },
+    }
+}
+
+impl ClientRemoteTabs {
+    pub(crate) fn from_record_with_remote_client(
+        client_id: String,
+        last_modified: ServerTimestamp,
+        remote_client: &RemoteClient,
+        record: TabsRecord,
+    ) -> Self {
+        Self {
+            client_id,
+            client_name: remote_client.device_name.clone(),
+            device_type: remote_client.device_type,
+            last_modified: last_modified.as_millis(),
+            remote_tabs: record.tabs.iter().map(RemoteTab::from_record_tab).collect(),
+        }
+    }
+
+    // Note that this should die as part of https://github.com/mozilla/application-services/issues/5199
+    // If we don't have a `RemoteClient` record, then we don't know whether the ID passed here is
+    // the fxa_device_id (which is must be) or the client_id (which it will be if this ends up being
+    // called for desktop records, where client_id != fxa_device_id)
+    pub(crate) fn from_record(
+        client_id: String,
+        last_modified: ServerTimestamp,
+        record: TabsRecord,
+    ) -> Self {
+        Self {
+            client_id,
+            client_name: record.client_name,
+            device_type: DeviceType::Unknown,
+            last_modified: last_modified.as_millis(),
+            remote_tabs: record.tabs.iter().map(RemoteTab::from_record_tab).collect(),
+        }
+    }
+    fn to_record(&self) -> TabsRecord {
+        TabsRecord {
+            id: self.client_id.clone(),
+            client_name: self.client_name.clone(),
+            tabs: self
+                .remote_tabs
+                .iter()
+                .map(RemoteTab::to_record_tab)
+                .collect(),
+        }
+    }
+}
+
+impl RemoteTab {
+    pub(crate) fn from_record_tab(tab: &TabsRecordTab) -> Self {
+        Self {
+            title: tab.title.clone(),
+            url_history: tab.url_history.clone(),
+            icon: tab.icon.clone(),
+            last_used: tab.last_used.checked_mul(1000).unwrap_or_default(),
+        }
+    }
+    pub(super) fn to_record_tab(&self) -> TabsRecordTab {
+        TabsRecordTab {
+            title: self.title.clone(),
+            url_history: self.url_history.clone(),
+            icon: self.icon.clone(),
+            last_used: self.last_used.checked_div(1000).unwrap_or_default(),
+        }
+    }
+}
+
+// This is the implementation of syncing, which is used by the 2 different "sync engines"
+// (We hope to get these 2 engines even closer in the future, but for now, we suck this up)
+pub struct TabsEngine {
+    pub(super) store: Arc<TabsStore>,
+    // local_id is made public for use in examples/tabs-sync
+    pub local_id: RwLock<String>,
+}
+
+impl TabsEngine {
+    pub fn new(store: Arc<TabsStore>) -> Self {
+        Self {
+            store,
+            local_id: Default::default(),
+        }
+    }
+
+    pub fn set_last_sync(&self, last_sync: ServerTimestamp) -> Result<()> {
+        let mut storage = self.store.storage.lock().unwrap();
+        log::debug!("Updating last sync to {}", last_sync);
+        let last_sync_millis = last_sync.as_millis();
+        Ok(storage.put_meta(schema::LAST_SYNC_META_KEY, &last_sync_millis)?)
+    }
+
+    pub fn get_last_sync(&self) -> Result<Option<ServerTimestamp>> {
+        let mut storage = self.store.storage.lock().unwrap();
+        let millis = storage.get_meta::<i64>(schema::LAST_SYNC_META_KEY)?;
+        Ok(millis.map(ServerTimestamp))
+    }
+}
+
+impl SyncEngine for TabsEngine {
+    fn collection_name(&self) -> CollectionName {
+        "tabs".into()
+    }
+
+    fn prepare_for_sync(&self, get_client_data: &dyn Fn() -> ClientData) -> Result<()> {
+        let mut storage = self.store.storage.lock().unwrap();
+        // We only know the client list at sync time, but need to return tabs potentially
+        // at any time -- so we store the clients in the meta table to be able to properly
+        // return a ClientRemoteTab struct
+        let client_data = get_client_data();
+        storage.put_meta(
+            schema::REMOTE_CLIENTS_KEY,
+            &serde_json::to_string(&client_data.recent_clients)?,
+        )?;
+        *self.local_id.write().unwrap() = client_data.local_client_id;
+        Ok(())
+    }
+
+    fn stage_incoming(
+        &self,
+        inbound: Vec<IncomingBso>,
+        telem: &mut telemetry::Engine,
+    ) -> Result<()> {
+        // We don't really "stage" records, we just apply them.
+        let local_id = &*self.local_id.read().unwrap();
+        let mut remote_tabs = Vec::with_capacity(inbound.len());
+
+        let mut incoming_telemetry = telemetry::EngineIncoming::new();
+        for incoming in inbound {
+            if incoming.envelope.id == *local_id {
+                // That's our own record, ignore it.
+                continue;
+            }
+            let modified = incoming.envelope.modified;
+            let record = match incoming.into_content::<TabsRecord>().content() {
+                Some(record) => record,
+                None => {
+                    // Invalid record or a "tombstone" which tabs don't have.
+                    log::warn!("Ignoring incoming invalid tab");
+                    incoming_telemetry.failed(1);
+                    continue;
+                }
+            };
+            incoming_telemetry.applied(1);
+            remote_tabs.push((record, modified));
+        }
+        telem.incoming(incoming_telemetry);
+        let mut storage = self.store.storage.lock().unwrap();
+        // In desktop we might end up here with zero records when doing a quick-write, in
+        // which case we don't want to wipe the DB.
+        if !remote_tabs.is_empty() {
+            storage.replace_remote_tabs(remote_tabs)?;
+        }
+        storage.remove_stale_clients()?;
+        Ok(())
+    }
+
+    fn apply(
+        &self,
+        timestamp: ServerTimestamp,
+        _telem: &mut telemetry::Engine,
+    ) -> Result<Vec<OutgoingBso>> {
+        // We've already applied them - really we just need to fetch outgoing.
+        let (local_tabs, remote_clients) = {
+            let mut storage = self.store.storage.lock().unwrap();
+            let local_tabs = storage.prepare_local_tabs_for_upload();
+            let remote_clients: HashMap<String, RemoteClient> = {
+                match storage.get_meta::<String>(schema::REMOTE_CLIENTS_KEY)? {
+                    None => HashMap::default(),
+                    Some(json) => serde_json::from_str(&json).unwrap(),
+                }
+            };
+            (local_tabs, remote_clients)
+        };
+
+        let local_id = &*self.local_id.read().unwrap();
+        // Timestamp will be zero when used as a "bridged" engine.
+        if timestamp.0 != 0 {
+            self.set_last_sync(timestamp)?;
+        }
+        // XXX - outgoing telem?
+        let outgoing = if let Some(local_tabs) = local_tabs {
+            let (client_name, device_type) = remote_clients
+                .get(local_id)
+                .map(|client| (client.device_name.clone(), client.device_type))
+                .unwrap_or_else(|| (String::new(), DeviceType::Unknown));
+            let local_record = ClientRemoteTabs {
+                client_id: local_id.clone(),
+                client_name,
+                device_type,
+                last_modified: 0, // ignored for outgoing records.
+                remote_tabs: local_tabs.to_vec(),
+            };
+            log::trace!("outgoing {:?}", local_record);
+            let envelope = OutgoingEnvelope {
+                id: local_id.as_str().into(),
+                ttl: Some(TABS_CLIENT_TTL),
+                ..Default::default()
+            };
+            vec![OutgoingBso::from_content(
+                envelope,
+                local_record.to_record(),
+            )?]
+        } else {
+            vec![]
+        };
+        Ok(outgoing)
+    }
+
+    fn set_uploaded(&self, new_timestamp: ServerTimestamp, ids: Vec<Guid>) -> Result<()> {
+        log::info!("sync uploaded {} records", ids.len());
+        self.set_last_sync(new_timestamp)?;
+        Ok(())
+    }
+
+    fn get_collection_request(
+        &self,
+        server_timestamp: ServerTimestamp,
+    ) -> Result<Option<CollectionRequest>> {
+        let since = self.get_last_sync()?.unwrap_or_default();
+        Ok(if since == server_timestamp {
+            None
+        } else {
+            Some(
+                CollectionRequest::new("tabs".into())
+                    .full()
+                    .newer_than(since),
+            )
+        })
+    }
+
+    fn reset(&self, assoc: &EngineSyncAssociation) -> Result<()> {
+        self.set_last_sync(ServerTimestamp(0))?;
+        let mut storage = self.store.storage.lock().unwrap();
+        storage.delete_meta(schema::REMOTE_CLIENTS_KEY)?;
+        storage.wipe_remote_tabs()?;
+        match assoc {
+            EngineSyncAssociation::Disconnected => {
+                storage.delete_meta(schema::GLOBAL_SYNCID_META_KEY)?;
+                storage.delete_meta(schema::COLLECTION_SYNCID_META_KEY)?;
+            }
+            EngineSyncAssociation::Connected(ids) => {
+                storage.put_meta(schema::GLOBAL_SYNCID_META_KEY, &ids.global.to_string())?;
+                storage.put_meta(schema::COLLECTION_SYNCID_META_KEY, &ids.coll.to_string())?;
+            }
+        };
+        Ok(())
+    }
+
+    fn wipe(&self) -> Result<()> {
+        self.reset(&EngineSyncAssociation::Disconnected)?;
+        // not clear why we need to wipe the local tabs - the app is just going
+        // to re-add them?
+        self.store.storage.lock().unwrap().wipe_local_tabs();
+        Ok(())
+    }
+
+    fn get_sync_assoc(&self) -> Result<EngineSyncAssociation> {
+        let mut storage = self.store.storage.lock().unwrap();
+        let global = storage.get_meta::<String>(schema::GLOBAL_SYNCID_META_KEY)?;
+        let coll = storage.get_meta::<String>(schema::COLLECTION_SYNCID_META_KEY)?;
+        Ok(if let (Some(global), Some(coll)) = (global, coll) {
+            EngineSyncAssociation::Connected(CollSyncIds {
+                global: Guid::from_string(global),
+                coll: Guid::from_string(coll),
+            })
+        } else {
+            EngineSyncAssociation::Disconnected
+        })
+    }
+}
+
+impl crate::TabsStore {
+    // This allows the embedding app to say "make this instance available to
+    // the sync manager". The implementation is more like "offer to sync mgr"
+    // (thereby avoiding us needing to link with the sync manager) but
+    // `register_with_sync_manager()` is logically what's happening so that's
+    // the name it gets.
+    pub fn register_with_sync_manager(self: Arc<Self>) {
+        let mut state = STORE_FOR_MANAGER.lock().unwrap();
+        *state = Arc::downgrade(&self);
+    }
+}
+
+#[cfg(test)]
+pub mod test {
+    use super::*;
+    use serde_json::json;
+    use sync15::bso::IncomingBso;
+
+    #[test]
+    fn test_incoming_tabs() {
+        env_logger::try_init().ok();
+
+        let engine = TabsEngine::new(Arc::new(TabsStore::new_with_mem_path("test-incoming")));
+
+        let records = vec![
+            json!({
+                "id": "device-no-tabs",
+                "clientName": "device with no tabs",
+                "tabs": [],
+            }),
+            json!({
+                "id": "device-with-a-tab",
+                "clientName": "device with a tab",
+                "tabs": [{
+                    "title": "the title",
+                    "urlHistory": [
+                        "https://mozilla.org/"
+                    ],
+                    "icon": "https://mozilla.org/icon",
+                    "lastUsed": 1643764207
+                }]
+            }),
+            // test an updated payload will replace the previous record
+            json!({
+                "id": "device-with-a-tab",
+                "clientName": "device with an updated tab",
+                "tabs": [{
+                    "title": "the new title",
+                    "urlHistory": [
+                        "https://mozilla.org/"
+                    ],
+                    "icon": "https://mozilla.org/icon",
+                    "lastUsed": 1643764208
+                }]
+            }),
+            // This has the main payload as OK but the tabs part invalid.
+            json!({
+                "id": "device-with-invalid-tab",
+                "clientName": "device with a tab",
+                "tabs": [{
+                    "foo": "bar",
+                }]
+            }),
+            // We want this to be a valid payload but an invalid tab - so it needs an ID.
+            json!({
+                "id": "invalid-tab",
+                "foo": "bar"
+            }),
+        ];
+
+        let mut telem = telemetry::Engine::new("tabs");
+        let incoming = records
+            .into_iter()
+            .map(IncomingBso::from_test_content)
+            .collect();
+        engine
+            .stage_incoming(incoming, &mut telem)
+            .expect("Should apply incoming and stage outgoing records");
+        let outgoing = engine
+            .apply(ServerTimestamp(0), &mut telem)
+            .expect("should apply");
+
+        assert!(outgoing.is_empty());
+
+        // now check the store has what we think it has.
+        let mut storage = engine.store.storage.lock().unwrap();
+        let mut crts = storage.get_remote_tabs().expect("should work");
+        crts.sort_by(|a, b| a.client_name.partial_cmp(&b.client_name).unwrap());
+        assert_eq!(crts.len(), 2, "we currently include devices with no tabs");
+        let crt = &crts[0];
+        assert_eq!(crt.client_name, "device with an updated tab");
+        assert_eq!(crt.device_type, DeviceType::Unknown);
+        assert_eq!(crt.remote_tabs.len(), 1);
+        assert_eq!(crt.remote_tabs[0].title, "the new title");
+
+        let crt = &crts[1];
+        assert_eq!(crt.client_name, "device with no tabs");
+        assert_eq!(crt.device_type, DeviceType::Unknown);
+        assert_eq!(crt.remote_tabs.len(), 0);
+    }
+
+    #[test]
+    fn test_no_incoming_doesnt_write() {
+        env_logger::try_init().ok();
+
+        let engine = TabsEngine::new(Arc::new(TabsStore::new_with_mem_path(
+            "test_no_incoming_doesnt_write",
+        )));
+
+        let records = vec![json!({
+            "id": "device-with-a-tab",
+            "clientName": "device with a tab",
+            "tabs": [{
+                "title": "the title",
+                "urlHistory": [
+                    "https://mozilla.org/"
+                ],
+                "icon": "https://mozilla.org/icon",
+                "lastUsed": 1643764207
+            }]
+        })];
+
+        let mut telem = telemetry::Engine::new("tabs");
+        let incoming = records
+            .into_iter()
+            .map(IncomingBso::from_test_content)
+            .collect();
+        engine
+            .stage_incoming(incoming, &mut telem)
+            .expect("Should apply incoming and stage outgoing records");
+        engine
+            .apply(ServerTimestamp(0), &mut telem)
+            .expect("should apply");
+
+        // now check the store has what we think it has.
+        {
+            let mut storage = engine.store.storage.lock().unwrap();
+            assert_eq!(storage.get_remote_tabs().expect("should work").len(), 1);
+        }
+
+        // Now another sync with zero incoming records, should still be able to get back
+        // our one client.
+        engine
+            .stage_incoming(vec![], &mut telemetry::Engine::new("tabs"))
+            .expect("Should succeed applying zero records");
+
+        {
+            let mut storage = engine.store.storage.lock().unwrap();
+            assert_eq!(storage.get_remote_tabs().expect("should work").len(), 1);
+        }
+    }
+
+    #[test]
+    fn test_sync_manager_registration() {
+        let store = Arc::new(TabsStore::new_with_mem_path("test-registration"));
+        assert_eq!(Arc::strong_count(&store), 1);
+        assert_eq!(Arc::weak_count(&store), 0);
+        Arc::clone(&store).register_with_sync_manager();
+        assert_eq!(Arc::strong_count(&store), 1);
+        assert_eq!(Arc::weak_count(&store), 1);
+        let registered = STORE_FOR_MANAGER
+            .lock()
+            .unwrap()
+            .upgrade()
+            .expect("should upgrade");
+        assert!(Arc::ptr_eq(&store, &registered));
+        drop(registered);
+        // should be no new references
+        assert_eq!(Arc::strong_count(&store), 1);
+        assert_eq!(Arc::weak_count(&store), 1);
+        // dropping the registered object should drop the registration.
+        drop(store);
+        assert!(STORE_FOR_MANAGER.lock().unwrap().upgrade().is_none());
+    }
+
+    #[test]
+    fn test_apply_timestamp() {
+        env_logger::try_init().ok();
+
+        let engine = TabsEngine::new(Arc::new(TabsStore::new_with_mem_path(
+            "test-apply-timestamp",
+        )));
+
+        let records = vec![json!({
+            "id": "device-no-tabs",
+            "clientName": "device with no tabs",
+            "tabs": [],
+        })];
+
+        let mut telem = telemetry::Engine::new("tabs");
+        engine
+            .set_last_sync(ServerTimestamp::from_millis(123))
+            .unwrap();
+        let incoming = records
+            .into_iter()
+            .map(IncomingBso::from_test_content)
+            .collect();
+        engine
+            .stage_incoming(incoming, &mut telem)
+            .expect("Should apply incoming and stage outgoing records");
+        engine
+            .apply(ServerTimestamp(0), &mut telem)
+            .expect("should apply");
+
+        assert_eq!(
+            engine
+                .get_last_sync()
+                .expect("should work")
+                .expect("should have a value"),
+            ServerTimestamp::from_millis(123),
+            "didn't set a zero timestamp"
+        )
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/tabs/sync/mod.rs.html b/book/rust-docs/src/tabs/sync/mod.rs.html new file mode 100644 index 0000000000..53fbe5209a --- /dev/null +++ b/book/rust-docs/src/tabs/sync/mod.rs.html @@ -0,0 +1,15 @@ +mod.rs - source
1
+2
+3
+4
+5
+6
+7
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+pub(crate) mod bridge;
+pub(crate) mod engine;
+pub(crate) mod record;
+
\ No newline at end of file diff --git a/book/rust-docs/src/tabs/sync/record.rs.html b/book/rust-docs/src/tabs/sync/record.rs.html new file mode 100644 index 0000000000..f8cfa9cc63 --- /dev/null +++ b/book/rust-docs/src/tabs/sync/record.rs.html @@ -0,0 +1,195 @@ +record.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use serde_derive::{Deserialize, Serialize};
+
+#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize, Default)]
+#[serde(rename_all = "camelCase")]
+pub struct TabsRecordTab {
+    pub title: String,
+    pub url_history: Vec<String>,
+    pub icon: Option<String>,
+    pub last_used: i64, // Seconds since epoch!
+}
+
+#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+// This struct mirrors what is stored on the server
+pub struct TabsRecord {
+    // `String` instead of `SyncGuid` because some IDs are FxA device ID (XXX - that doesn't
+    // matter though - this could easily be a Guid!)
+    pub id: String,
+    pub client_name: String,
+    pub tabs: Vec<TabsRecordTab>,
+}
+
+#[cfg(test)]
+pub mod test {
+    use super::*;
+    use serde_json::json;
+
+    #[test]
+    fn test_payload() {
+        let payload = json!({
+            "id": "JkeBPC50ZI0m",
+            "clientName": "client name",
+            "tabs": [{
+                "title": "the title",
+                "urlHistory": [
+                    "https://mozilla.org/"
+                ],
+                "icon": "https://mozilla.org/icon",
+                "lastUsed": 1643764207
+            }]
+        });
+        let record: TabsRecord = serde_json::from_value(payload).expect("should work");
+        assert_eq!(record.id, "JkeBPC50ZI0m");
+        assert_eq!(record.client_name, "client name");
+        assert_eq!(record.tabs.len(), 1);
+        let tab = &record.tabs[0];
+        assert_eq!(tab.title, "the title");
+        assert_eq!(tab.icon, Some("https://mozilla.org/icon".to_string()));
+        assert_eq!(tab.last_used, 1643764207);
+    }
+
+    #[test]
+    fn test_roundtrip() {
+        let tab = TabsRecord {
+            id: "JkeBPC50ZI0m".into(),
+            client_name: "client name".into(),
+            tabs: vec![TabsRecordTab {
+                title: "the title".into(),
+                url_history: vec!["https://mozilla.org/".into()],
+                icon: Some("https://mozilla.org/icon".into()),
+                last_used: 1643764207,
+            }],
+        };
+        let round_tripped =
+            serde_json::from_value(serde_json::to_value(tab.clone()).unwrap()).unwrap();
+        assert_eq!(tab, round_tripped);
+    }
+
+    #[test]
+    fn test_extra_fields() {
+        let payload = json!({
+            "id": "JkeBPC50ZI0m",
+            // Let's say we agree on new tabs to record, we want old versions to
+            // ignore them!
+            "ignoredField": "??",
+            "clientName": "client name",
+            "tabs": [{
+                "title": "the title",
+                "urlHistory": [
+                    "https://mozilla.org/"
+                ],
+                "icon": "https://mozilla.org/icon",
+                "lastUsed": 1643764207,
+                // Ditto - make sure we ignore unexpected fields in each tab.
+                "ignoredField": "??",
+            }]
+        });
+        let record: TabsRecord = serde_json::from_value(payload).unwrap();
+        // The point of this test is really just to ensure the deser worked, so
+        // just check the ID.
+        assert_eq!(record.id, "JkeBPC50ZI0m");
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/types/lib.rs.html b/book/rust-docs/src/types/lib.rs.html new file mode 100644 index 0000000000..af8488902f --- /dev/null +++ b/book/rust-docs/src/types/lib.rs.html @@ -0,0 +1,209 @@ +lib.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use rusqlite::types::{FromSql, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
+use rusqlite::Result as RusqliteResult;
+use serde_derive::*;
+use std::fmt;
+use std::time::{Duration, SystemTime, UNIX_EPOCH};
+
+// Typesafe way to manage timestamps.
+// We should probably work out how to share this too?
+#[derive(
+    Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize, Default,
+)]
+pub struct Timestamp(pub u64);
+
+impl Timestamp {
+    pub fn now() -> Self {
+        SystemTime::now().into()
+    }
+
+    /// Returns None if `other` is later than `self` (Duration may not represent
+    /// negative timespans in rust).
+    #[inline]
+    pub fn duration_since(self, other: Timestamp) -> Option<Duration> {
+        // just do this via SystemTime.
+        SystemTime::from(self).duration_since(other.into()).ok()
+    }
+
+    #[inline]
+    pub fn checked_sub(self, d: Duration) -> Option<Timestamp> {
+        SystemTime::from(self).checked_sub(d).map(Timestamp::from)
+    }
+
+    #[inline]
+    pub fn checked_add(self, d: Duration) -> Option<Timestamp> {
+        SystemTime::from(self).checked_add(d).map(Timestamp::from)
+    }
+
+    pub fn as_millis(self) -> u64 {
+        self.0
+    }
+
+    pub fn as_millis_i64(self) -> i64 {
+        self.0 as i64
+    }
+    /// In desktop sync, bookmarks are clamped to Jan 23, 1993 (which is 727747200000)
+    /// There's no good reason history records could be older than that, so we do
+    /// the same here (even though desktop's history currently doesn't)
+    /// XXX - there's probably a case to be made for this being, say, 5 years ago -
+    /// then all requests earlier than that are collapsed into a single visit at
+    /// this timestamp.
+    pub const EARLIEST: Timestamp = Timestamp(727_747_200_000);
+}
+
+impl From<Timestamp> for u64 {
+    #[inline]
+    fn from(ts: Timestamp) -> Self {
+        ts.0
+    }
+}
+
+impl From<SystemTime> for Timestamp {
+    #[inline]
+    fn from(st: SystemTime) -> Self {
+        let d = st.duration_since(UNIX_EPOCH).unwrap(); // hrmph - unwrap doesn't seem ideal
+        Timestamp((d.as_secs()) * 1000 + (u64::from(d.subsec_nanos()) / 1_000_000))
+    }
+}
+
+impl From<Timestamp> for SystemTime {
+    #[inline]
+    fn from(ts: Timestamp) -> Self {
+        UNIX_EPOCH + Duration::from_millis(ts.into())
+    }
+}
+
+impl From<u64> for Timestamp {
+    #[inline]
+    fn from(ts: u64) -> Self {
+        assert!(ts != 0);
+        Timestamp(ts)
+    }
+}
+
+impl fmt::Display for Timestamp {
+    #[inline]
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "{}", self.0)
+    }
+}
+
+impl ToSql for Timestamp {
+    fn to_sql(&self) -> RusqliteResult<ToSqlOutput<'_>> {
+        Ok(ToSqlOutput::from(self.0 as i64)) // hrm - no u64 in rusqlite
+    }
+}
+
+impl FromSql for Timestamp {
+    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
+        value.as_i64().map(|v| Timestamp(v as u64)) // hrm - no u64
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/viaduct/backend.rs.html b/book/rust-docs/src/viaduct/backend.rs.html new file mode 100644 index 0000000000..eaf74da376 --- /dev/null +++ b/book/rust-docs/src/viaduct/backend.rs.html @@ -0,0 +1,309 @@ +backend.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::GLOBAL_SETTINGS;
+use ffi::FfiBackend;
+use once_cell::sync::OnceCell;
+mod ffi;
+
+pub fn note_backend(which: &str) {
+    // If trace logs are enabled: log on every request. Otherwise, just log on
+    // the first request at `info` level. We remember if the Once was triggered
+    // to avoid logging twice in the first case.
+    static NOTE_BACKEND_ONCE: std::sync::Once = std::sync::Once::new();
+    let mut called = false;
+    NOTE_BACKEND_ONCE.call_once(|| {
+        log::info!("Using HTTP backend {}", which);
+        called = true;
+    });
+    if !called {
+        log::trace!("Using HTTP backend {}", which);
+    }
+}
+
+pub trait Backend: Send + Sync + 'static {
+    fn send(&self, request: crate::Request) -> Result<crate::Response, crate::Error>;
+}
+
+static BACKEND: OnceCell<&'static dyn Backend> = OnceCell::new();
+
+pub fn set_backend(b: &'static dyn Backend) -> Result<(), crate::Error> {
+    BACKEND
+        .set(b)
+        .map_err(|_| crate::error::Error::SetBackendError)
+}
+
+pub(crate) fn get_backend() -> &'static dyn Backend {
+    *BACKEND.get_or_init(|| Box::leak(Box::new(FfiBackend)))
+}
+
+pub fn send(request: crate::Request) -> Result<crate::Response, crate::Error> {
+    validate_request(&request)?;
+    get_backend().send(request)
+}
+
+pub fn validate_request(request: &crate::Request) -> Result<(), crate::Error> {
+    if request.url.scheme() != "https"
+        && match request.url.host() {
+            Some(url::Host::Domain(d)) => d != "localhost",
+            Some(url::Host::Ipv4(addr)) => !addr.is_loopback(),
+            Some(url::Host::Ipv6(addr)) => !addr.is_loopback(),
+            None => true,
+        }
+        && {
+            let settings = GLOBAL_SETTINGS.read();
+            settings
+                .addn_allowed_insecure_url
+                .as_ref()
+                .map(|url| url.host() != request.url.host() || url.scheme() != request.url.scheme())
+                .unwrap_or(true)
+        }
+    {
+        return Err(crate::Error::NonTlsUrl);
+    }
+    Ok(())
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_validate_request() {
+        let _https_request = crate::Request::new(
+            crate::Method::Get,
+            url::Url::parse("https://www.example.com").unwrap(),
+        );
+        assert!(validate_request(&_https_request).is_ok());
+
+        let _http_request = crate::Request::new(
+            crate::Method::Get,
+            url::Url::parse("http://www.example.com").unwrap(),
+        );
+        assert!(validate_request(&_http_request).is_err());
+
+        let _localhost_https_request = crate::Request::new(
+            crate::Method::Get,
+            url::Url::parse("https://127.0.0.1/index.html").unwrap(),
+        );
+        assert!(validate_request(&_localhost_https_request).is_ok());
+
+        let _localhost_https_request_2 = crate::Request::new(
+            crate::Method::Get,
+            url::Url::parse("https://localhost:4242/").unwrap(),
+        );
+        assert!(validate_request(&_localhost_https_request_2).is_ok());
+
+        let _localhost_http_request = crate::Request::new(
+            crate::Method::Get,
+            url::Url::parse("http://localhost:4242/").unwrap(),
+        );
+        assert!(validate_request(&_localhost_http_request).is_ok());
+
+        let localhost_request = crate::Request::new(
+            crate::Method::Get,
+            url::Url::parse("localhost:4242/").unwrap(),
+        );
+        assert!(validate_request(&localhost_request).is_err());
+
+        let localhost_request_shorthand_ipv6 =
+            crate::Request::new(crate::Method::Get, url::Url::parse("http://[::1]").unwrap());
+        assert!(validate_request(&localhost_request_shorthand_ipv6).is_ok());
+
+        let localhost_request_ipv6 = crate::Request::new(
+            crate::Method::Get,
+            url::Url::parse("http://[0:0:0:0:0:0:0:1]").unwrap(),
+        );
+        assert!(validate_request(&localhost_request_ipv6).is_ok());
+    }
+
+    #[test]
+    fn test_validate_request_addn_allowed_insecure_url() {
+        let request_root = crate::Request::new(
+            crate::Method::Get,
+            url::Url::parse("http://anything").unwrap(),
+        );
+        let request = crate::Request::new(
+            crate::Method::Get,
+            url::Url::parse("http://anything/path").unwrap(),
+        );
+        // This should never be accepted.
+        let request_ftp = crate::Request::new(
+            crate::Method::Get,
+            url::Url::parse("ftp://anything/path").unwrap(),
+        );
+        assert!(validate_request(&request_root).is_err());
+        assert!(validate_request(&request).is_err());
+        {
+            let mut settings = GLOBAL_SETTINGS.write();
+            settings.addn_allowed_insecure_url =
+                Some(url::Url::parse("http://something-else").unwrap());
+        }
+        assert!(validate_request(&request_root).is_err());
+        assert!(validate_request(&request).is_err());
+
+        {
+            let mut settings = GLOBAL_SETTINGS.write();
+            settings.addn_allowed_insecure_url = Some(url::Url::parse("http://anything").unwrap());
+        }
+        assert!(validate_request(&request_root).is_ok());
+        assert!(validate_request(&request).is_ok());
+        assert!(validate_request(&request_ftp).is_err());
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/viaduct/backend/ffi.rs.html b/book/rust-docs/src/viaduct/backend/ffi.rs.html new file mode 100644 index 0000000000..f7da5489bd --- /dev/null +++ b/book/rust-docs/src/viaduct/backend/ffi.rs.html @@ -0,0 +1,419 @@ +ffi.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::{backend::Backend, settings::GLOBAL_SETTINGS};
+use crate::{msg_types, Error};
+use ffi_support::{ByteBuffer, FfiStr};
+
+ffi_support::implement_into_ffi_by_protobuf!(msg_types::Request);
+
+impl From<crate::Request> for msg_types::Request {
+    fn from(request: crate::Request) -> Self {
+        let settings = GLOBAL_SETTINGS.read();
+        msg_types::Request {
+            url: request.url.to_string(),
+            body: request.body,
+            // Real weird that this needs to be specified as an i32, but
+            // it certainly makes it convenient for us...
+            method: request.method as i32,
+            headers: request.headers.into(),
+            follow_redirects: settings.follow_redirects,
+            use_caches: settings.use_caches,
+            connect_timeout_secs: settings.connect_timeout.map_or(0, |d| d.as_secs() as i32),
+            read_timeout_secs: settings.read_timeout.map_or(0, |d| d.as_secs() as i32),
+        }
+    }
+}
+
+macro_rules! backend_error {
+    ($($args:tt)*) => {{
+        let msg = format!($($args)*);
+        log::error!("{}", msg);
+        Error::BackendError(msg)
+    }};
+}
+
+pub struct FfiBackend;
+impl Backend for FfiBackend {
+    fn send(&self, request: crate::Request) -> Result<crate::Response, Error> {
+        use ffi_support::IntoFfi;
+        use prost::Message;
+        super::note_backend("FFI (trusted)");
+
+        let method = request.method;
+        let fetch = callback_holder::get_callback().ok_or(Error::BackendNotInitialized)?;
+        let proto_req: msg_types::Request = request.into();
+        let buf = proto_req.into_ffi_value();
+        let response = unsafe { fetch(buf) };
+        // This way we'll Drop it if we panic, unlike if we just got a slice into
+        // it. Besides, we already own it.
+        let response_bytes = response.destroy_into_vec();
+
+        let response: msg_types::Response = match Message::decode(response_bytes.as_slice()) {
+            Ok(v) => v,
+            Err(e) => {
+                panic!(
+                    "Failed to parse protobuf returned from fetch callback! {}",
+                    e
+                );
+            }
+        };
+
+        if let Some(exn) = response.exception_message {
+            return Err(Error::NetworkError(format!("Java error: {:?}", exn)));
+        }
+        let status = response
+            .status
+            .ok_or_else(|| backend_error!("Missing HTTP status"))?;
+
+        if status < 0 || status > i32::from(u16::max_value()) {
+            return Err(backend_error!("Illegal HTTP status: {}", status));
+        }
+
+        let mut headers = crate::Headers::with_capacity(response.headers.len());
+        for (name, val) in response.headers {
+            let hname = match crate::HeaderName::new(name) {
+                Ok(name) => name,
+                Err(e) => {
+                    // Ignore headers with invalid names, since nobody can look for them anyway.
+                    log::warn!("Server sent back invalid header name: '{}'", e);
+                    continue;
+                }
+            };
+            // Not using Header::new since the error it returns is for request headers.
+            headers.insert_header(crate::Header::new_unchecked(hname, val));
+        }
+
+        let url = url::Url::parse(
+            &response
+                .url
+                .ok_or_else(|| backend_error!("Response has no URL"))?,
+        )
+        .map_err(|e| backend_error!("Response has illegal URL: {}", e))?;
+
+        Ok(crate::Response {
+            url,
+            request_method: method,
+            body: response.body.unwrap_or_default(),
+            status: status as u16,
+            headers,
+        })
+    }
+}
+
+/// Type of the callback we need callers on the other side of the FFI to
+/// provide.
+///
+/// Takes and returns a ffi_support::ByteBuffer. (TODO: it would be nice if we could
+/// make this take/return pointers, so that we could use JNA direct mapping. Maybe
+/// we need some kind of ThinBuffer?)
+///
+/// This is a bit weird, since it requires us to allow code on the other side of
+/// the FFI to allocate a ByteBuffer from us, but it works.
+///
+/// The code on the other side of the FFI is responsible for freeing the ByteBuffer
+/// it's passed using `viaduct_destroy_bytebuffer`.
+type FetchCallback = unsafe extern "C" fn(ByteBuffer) -> ByteBuffer;
+
+/// Module that manages get/set of the global fetch callback pointer.
+mod callback_holder {
+    use super::FetchCallback;
+    use std::sync::atomic::{AtomicUsize, Ordering};
+
+    /// Note: We only assign to this once.
+    static CALLBACK_PTR: AtomicUsize = AtomicUsize::new(0);
+
+    // Overly-paranoid sanity checking to ensure that these types are
+    // convertible between each-other. `transmute` actually should check this for
+    // us too, but this helps document the invariants we rely on in this code.
+    //
+    // Note that these are guaranteed by
+    // https://rust-lang.github.io/unsafe-code-guidelines/layout/function-pointers.html
+    // and thus this is a little paranoid.
+    ffi_support::static_assert!(
+        STATIC_ASSERT_USIZE_EQ_FUNC_SIZE,
+        std::mem::size_of::<usize>() == std::mem::size_of::<FetchCallback>()
+    );
+
+    ffi_support::static_assert!(
+        STATIC_ASSERT_USIZE_EQ_OPT_FUNC_SIZE,
+        std::mem::size_of::<usize>() == std::mem::size_of::<Option<FetchCallback>>()
+    );
+
+    /// Get the function pointer to the FetchCallback. Panics if the callback
+    /// has not yet been initialized.
+    pub(super) fn get_callback() -> Option<FetchCallback> {
+        let ptr_value = CALLBACK_PTR.load(Ordering::SeqCst);
+        unsafe { std::mem::transmute::<usize, Option<FetchCallback>>(ptr_value) }
+    }
+
+    /// Set the function pointer to the FetchCallback. Returns false if we did nothing because the callback had already been initialized
+    pub(super) fn set_callback(h: FetchCallback) -> bool {
+        let as_usize = h as usize;
+        match CALLBACK_PTR.compare_exchange(0, as_usize, Ordering::SeqCst, Ordering::SeqCst) {
+            Ok(_) => true,
+            Err(_) => {
+                // This is an internal bug, the other side of the FFI should ensure
+                // it sets this only once. Note that this is actually going to be
+                // before logging is initialized in practice, so there's not a lot
+                // we can actually do here.
+                log::error!("Bug: Initialized CALLBACK_PTR multiple times");
+                false
+            }
+        }
+    }
+}
+
+/// Return a ByteBuffer of the requested size. This is used to store the
+/// response from the callback.
+#[no_mangle]
+pub extern "C" fn viaduct_alloc_bytebuffer(sz: i32) -> ByteBuffer {
+    let mut error = ffi_support::ExternError::default();
+    let buffer =
+        ffi_support::call_with_output(&mut error, || ByteBuffer::new_with_size(sz.max(0) as usize));
+    error.consume_and_log_if_error();
+    buffer
+}
+
+#[no_mangle]
+pub extern "C" fn viaduct_log_error(s: FfiStr<'_>) {
+    let mut error = ffi_support::ExternError::default();
+    ffi_support::call_with_output(&mut error, || {
+        log::error!("Viaduct Ffi Error: {}", s.as_str())
+    });
+    error.consume_and_log_if_error();
+}
+
+#[no_mangle]
+pub extern "C" fn viaduct_initialize(callback: FetchCallback) -> u8 {
+    ffi_support::abort_on_panic::call_with_output(|| callback_holder::set_callback(callback))
+}
+
+/// Allows connections to the hard-coded address the Android Emulator uses for
+/// localhost. It would be easy to support allowing the address to be passed in,
+/// but we've made a decision to avoid that possible footgun. The expectation is
+/// that this will only be called in debug builds or if the app can determine it
+/// is in the emulator, but the Rust code doesn't know that, so we can't check.
+#[no_mangle]
+pub extern "C" fn viaduct_allow_android_emulator_loopback() {
+    let mut error = ffi_support::ExternError::default();
+    ffi_support::call_with_output(&mut error, || {
+        let url = url::Url::parse("http://10.0.2.2").unwrap();
+        let mut settings = GLOBAL_SETTINGS.write();
+        settings.addn_allowed_insecure_url = Some(url);
+    });
+    error.consume_and_log_if_error();
+}
+
+ffi_support::define_bytebuffer_destructor!(viaduct_destroy_bytebuffer);
+
\ No newline at end of file diff --git a/book/rust-docs/src/viaduct/error.rs.html b/book/rust-docs/src/viaduct/error.rs.html new file mode 100644 index 0000000000..4e4622ebff --- /dev/null +++ b/book/rust-docs/src/viaduct/error.rs.html @@ -0,0 +1,97 @@ +error.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#[derive(Debug, thiserror::Error)]
+pub enum Error {
+    #[error("[no-sentry] Illegal characters in request header '{0}'")]
+    RequestHeaderError(crate::HeaderName),
+
+    #[error("[no-sentry] Backend error: {0}")]
+    BackendError(String),
+
+    #[error("[no-sentry] Network error: {0}")]
+    NetworkError(String),
+
+    #[error("The rust-components network backend must be initialized before use!")]
+    BackendNotInitialized,
+
+    #[error("Backend already initialized.")]
+    SetBackendError,
+
+    /// Note: we return this if the server returns a bad URL with
+    /// its response. This *probably* should never happen, but who knows.
+    #[error("[no-sentry] URL Parse Error: {0}")]
+    UrlError(#[source] url::ParseError),
+
+    #[error("[no-sentry] Validation error: URL does not use TLS protocol.")]
+    NonTlsUrl,
+}
+
+impl From<url::ParseError> for Error {
+    fn from(u: url::ParseError) -> Self {
+        Error::UrlError(u)
+    }
+}
+
+/// This error is returned as the `Err` result from
+/// [`Response::require_success`].
+///
+/// Note that it's not a variant on `Error` to distinguish between errors
+/// caused by the network, and errors returned from the server.
+#[derive(thiserror::Error, Debug, Clone)]
+#[error("Error: {method} {url} returned {status}")]
+pub struct UnexpectedStatus {
+    pub status: u16,
+    pub method: crate::Method,
+    pub url: url::Url,
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/viaduct/headers.rs.html b/book/rust-docs/src/viaduct/headers.rs.html new file mode 100644 index 0000000000..fd8dda9c36 --- /dev/null +++ b/book/rust-docs/src/viaduct/headers.rs.html @@ -0,0 +1,829 @@ +headers.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+pub use name::{HeaderName, InvalidHeaderName};
+use std::collections::HashMap;
+use std::iter::FromIterator;
+use std::str::FromStr;
+mod name;
+
+/// A single header. Headers have a name (case insensitive) and a value. The
+/// character set for header and values are both restrictive.
+/// - Names must only contain a-zA-Z0-9 and and ('!' | '#' | '$' | '%' | '&' |
+///   '\'' | '*' | '+' | '-' | '.' | '^' | '_' | '`' | '|' | '~') characters
+///   (the field-name token production defined at
+///   https://tools.ietf.org/html/rfc7230#section-3.2).
+///   For request headers, we expect these to all be specified statically,
+///   and so we panic if you provide an invalid one. (For response headers, we
+///   ignore headers with invalid names, but emit a warning).
+///
+///   Header names are case insensitive, and we have several pre-defined ones in
+///   the [`header_names`] module.
+///
+/// - Values may only contain printable ascii characters, and may not contain
+///   \r or \n. Strictly speaking, HTTP is more flexible for header values,
+///   however we don't need to support binary header values, and so we do not.
+///
+/// Note that typically you should not interact with this directly, and instead
+/// use the methods on [`Request`] or [`Headers`] to manipulate these.
+#[derive(Clone, Debug, PartialEq, PartialOrd, Hash, Eq, Ord)]
+pub struct Header {
+    pub(crate) name: HeaderName,
+    pub(crate) value: String,
+}
+
+// Trim `s` without copying if it can be avoided.
+fn trim_string<S: AsRef<str> + Into<String>>(s: S) -> String {
+    let sr = s.as_ref();
+    let trimmed = sr.trim();
+    if sr.len() != trimmed.len() {
+        trimmed.into()
+    } else {
+        s.into()
+    }
+}
+
+fn is_valid_header_value(value: &str) -> bool {
+    value.bytes().all(|b| (32..127).contains(&b) || b == b'\t')
+}
+
+impl Header {
+    pub fn new<Name, Value>(name: Name, value: Value) -> Result<Self, crate::Error>
+    where
+        Name: Into<HeaderName>,
+        Value: AsRef<str> + Into<String>,
+    {
+        let name = name.into();
+        let value = trim_string(value);
+        if !is_valid_header_value(&value) {
+            return Err(crate::Error::RequestHeaderError(name));
+        }
+        Ok(Self { name, value })
+    }
+
+    pub fn new_unchecked<Value>(name: HeaderName, value: Value) -> Self
+    where
+        Value: AsRef<str> + Into<String>,
+    {
+        Self {
+            name,
+            value: value.into(),
+        }
+    }
+
+    #[inline]
+    pub fn name(&self) -> &HeaderName {
+        &self.name
+    }
+
+    #[inline]
+    pub fn value(&self) -> &str {
+        &self.value
+    }
+
+    #[inline]
+    fn set_value<V: AsRef<str>>(&mut self, s: V) -> Result<(), crate::Error> {
+        let value = s.as_ref();
+        if !is_valid_header_value(value) {
+            Err(crate::Error::RequestHeaderError(self.name.clone()))
+        } else {
+            self.value.clear();
+            self.value.push_str(s.as_ref().trim());
+            Ok(())
+        }
+    }
+}
+
+impl std::fmt::Display for Header {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "{}: {}", self.name, self.value)
+    }
+}
+
+/// A list of headers.
+#[derive(Clone, Debug, PartialEq, Eq, Default)]
+pub struct Headers {
+    headers: Vec<Header>,
+}
+
+impl Headers {
+    /// Initialize an empty list of headers.
+    #[inline]
+    pub fn new() -> Self {
+        Default::default()
+    }
+
+    /// Initialize an empty list of headers backed by a vector with the provided
+    /// capacity.
+    pub fn with_capacity(c: usize) -> Self {
+        Self {
+            headers: Vec::with_capacity(c),
+        }
+    }
+
+    /// Convert this list of headers to a Vec<Header>
+    #[inline]
+    pub fn into_vec(self) -> Vec<Header> {
+        self.headers
+    }
+
+    /// Returns the number of headers.
+    #[inline]
+    pub fn len(&self) -> usize {
+        self.headers.len()
+    }
+
+    /// Returns true if `len()` is zero.
+    #[inline]
+    pub fn is_empty(&self) -> bool {
+        self.headers.is_empty()
+    }
+    /// Clear this set of headers.
+    #[inline]
+    pub fn clear(&mut self) {
+        self.headers.clear();
+    }
+
+    /// Insert or update a new header.
+    ///
+    /// This returns an error if you attempt to specify a header with an
+    /// invalid value (values must be printable ASCII and may not contain
+    /// \r or \n)
+    ///
+    /// ## Example
+    /// ```
+    /// # use viaduct::Headers;
+    /// # fn main() -> Result<(), viaduct::Error> {
+    /// let mut h = Headers::new();
+    /// h.insert("My-Cool-Header", "example")?;
+    /// assert_eq!(h.get("My-Cool-Header"), Some("example"));
+    ///
+    /// // Note: names are sensitive
+    /// assert_eq!(h.get("my-cool-header"), Some("example"));
+    ///
+    /// // Also note, constants for headers are in `viaduct::header_names`, and
+    /// // you can chain the result of this function.
+    /// h.insert(viaduct::header_names::CONTENT_TYPE, "something...")?
+    ///  .insert("Something-Else", "etc")?;
+    /// # Ok(())
+    /// # }
+    /// ```
+    pub fn insert<N, V>(&mut self, name: N, value: V) -> Result<&mut Self, crate::Error>
+    where
+        N: Into<HeaderName> + PartialEq<HeaderName>,
+        V: Into<String> + AsRef<str>,
+    {
+        if let Some(entry) = self.headers.iter_mut().find(|h| name == h.name) {
+            entry.set_value(value)?;
+        } else {
+            self.headers.push(Header::new(name, value)?);
+        }
+        Ok(self)
+    }
+
+    /// Insert the provided header unless a header is already specified.
+    /// Mostly used internally, e.g. to set "Content-Type: application/json"
+    /// in `Request::json()` unless it has been set specifically.
+    pub fn insert_if_missing<N, V>(&mut self, name: N, value: V) -> Result<&mut Self, crate::Error>
+    where
+        N: Into<HeaderName> + PartialEq<HeaderName>,
+        V: Into<String> + AsRef<str>,
+    {
+        if !self.headers.iter_mut().any(|h| name == h.name) {
+            self.headers.push(Header::new(name, value)?);
+        }
+        Ok(self)
+    }
+
+    /// Insert or update a header directly. Typically you will want to use
+    /// `insert` over this, as it performs less work if the header needs
+    /// updating instead of insertion.
+    pub fn insert_header(&mut self, new: Header) -> &mut Self {
+        if let Some(entry) = self.headers.iter_mut().find(|h| h.name == new.name) {
+            entry.value = new.value;
+        } else {
+            self.headers.push(new);
+        }
+        self
+    }
+
+    /// Add all the headers in the provided iterator to this list of headers.
+    pub fn extend<I>(&mut self, iter: I) -> &mut Self
+    where
+        I: IntoIterator<Item = Header>,
+    {
+        let it = iter.into_iter();
+        self.headers.reserve(it.size_hint().0);
+        for h in it {
+            self.insert_header(h);
+        }
+        self
+    }
+
+    /// Add all the headers in the provided iterator, unless any of them are Err.
+    pub fn try_extend<I, E>(&mut self, iter: I) -> Result<&mut Self, E>
+    where
+        I: IntoIterator<Item = Result<Header, E>>,
+    {
+        // Not the most efficient but avoids leaving us in an unspecified state
+        // if one returns Err.
+        self.extend(iter.into_iter().collect::<Result<Vec<_>, E>>()?);
+        Ok(self)
+    }
+
+    /// Get the header object with the requested name. Usually, you will
+    /// want to use `get()` or `get_as::<T>()` instead.
+    pub fn get_header<S>(&self, name: S) -> Option<&Header>
+    where
+        S: PartialEq<HeaderName>,
+    {
+        self.headers.iter().find(|h| name == h.name)
+    }
+
+    /// Get the value of the header with the provided name.
+    ///
+    /// See also `get_as`.
+    ///
+    /// ## Example
+    /// ```
+    /// # use viaduct::{Headers, header_names::CONTENT_TYPE};
+    /// # fn main() -> Result<(), viaduct::Error> {
+    /// let mut h = Headers::new();
+    /// h.insert(CONTENT_TYPE, "application/json")?;
+    /// assert_eq!(h.get(CONTENT_TYPE), Some("application/json"));
+    /// assert_eq!(h.get("Something-Else"), None);
+    /// # Ok(())
+    /// # }
+    /// ```
+    pub fn get<S>(&self, name: S) -> Option<&str>
+    where
+        S: PartialEq<HeaderName>,
+    {
+        self.get_header(name).map(|h| h.value.as_str())
+    }
+
+    /// Get the value of the header with the provided name, and
+    /// attempt to parse it using [`std::str::FromStr`].
+    ///
+    /// - If the header is missing, it returns None.
+    /// - If the header is present but parsing failed, returns
+    ///   `Some(Err(<error returned by parsing>))`.
+    /// - Otherwise, returns `Some(Ok(result))`.
+    ///
+    /// Note that if `Option<Result<T, E>>` is inconvenient for you,
+    /// and you wish this returned `Result<Option<T>, E>`, you may use
+    /// the built-in `transpose()` method to convert between them.
+    ///
+    /// ```
+    /// # use viaduct::Headers;
+    /// # fn main() -> Result<(), viaduct::Error> {
+    /// let mut h = Headers::new();
+    /// h.insert("Example", "1234")?.insert("Illegal", "abcd")?;
+    /// let v: Option<Result<i64, _>> = h.get_as("Example");
+    /// assert_eq!(v, Some(Ok(1234)));
+    /// assert_eq!(h.get_as::<i64, _>("Example"), Some(Ok(1234)));
+    /// assert_eq!(h.get_as::<i64, _>("Illegal"), Some("abcd".parse::<i64>()));
+    /// assert_eq!(h.get_as::<i64, _>("Something-Else"), None);
+    /// # Ok(())
+    /// # }
+    /// ```
+    pub fn get_as<T, S>(&self, name: S) -> Option<Result<T, <T as FromStr>::Err>>
+    where
+        T: FromStr,
+        S: PartialEq<HeaderName>,
+    {
+        self.get(name).map(str::parse)
+    }
+    /// Get the value of the header with the provided name, and
+    /// attempt to parse it using [`std::str::FromStr`].
+    ///
+    /// This is a variant of `get_as` that returns None on error,
+    /// intended to be used for cases where missing and invalid
+    /// headers should be treated the same. (With `get_as` this
+    /// requires `h.get_as(...).and_then(|r| r.ok())`, which is
+    /// somewhat opaque.
+    pub fn try_get<T, S>(&self, name: S) -> Option<T>
+    where
+        T: FromStr,
+        S: PartialEq<HeaderName>,
+    {
+        self.get(name).and_then(|val| val.parse::<T>().ok())
+    }
+
+    /// Get an iterator over the headers in no particular order.
+    ///
+    /// Note that we also implement IntoIterator.
+    pub fn iter(&self) -> <&Headers as IntoIterator>::IntoIter {
+        self.into_iter()
+    }
+}
+
+impl std::iter::IntoIterator for Headers {
+    type IntoIter = <Vec<Header> as IntoIterator>::IntoIter;
+    type Item = Header;
+    fn into_iter(self) -> Self::IntoIter {
+        self.headers.into_iter()
+    }
+}
+
+impl<'a> std::iter::IntoIterator for &'a Headers {
+    type IntoIter = <&'a [Header] as IntoIterator>::IntoIter;
+    type Item = &'a Header;
+    fn into_iter(self) -> Self::IntoIter {
+        self.headers[..].iter()
+    }
+}
+
+impl FromIterator<Header> for Headers {
+    fn from_iter<T>(iter: T) -> Self
+    where
+        T: IntoIterator<Item = Header>,
+    {
+        let mut v = iter.into_iter().collect::<Vec<Header>>();
+        v.sort_by(|a, b| a.name.cmp(&b.name));
+        v.reverse();
+        v.dedup_by(|a, b| a.name == b.name);
+        Headers { headers: v }
+    }
+}
+
+#[allow(clippy::implicit_hasher)] // https://github.com/rust-lang/rust-clippy/issues/3899
+impl From<Headers> for HashMap<String, String> {
+    fn from(headers: Headers) -> HashMap<String, String> {
+        headers
+            .into_iter()
+            .map(|h| (String::from(h.name), h.value))
+            .collect()
+    }
+}
+
+pub mod consts {
+    use super::name::HeaderName;
+    macro_rules! def_header_consts {
+        ($(($NAME:ident, $string:literal)),* $(,)?) => {
+            $(pub const $NAME: HeaderName = HeaderName(std::borrow::Cow::Borrowed($string));)*
+        };
+    }
+
+    macro_rules! headers {
+        ($(($NAME:ident, $string:literal)),* $(,)?) => {
+            def_header_consts!($(($NAME, $string)),*);
+            // Unused except for tests.
+            const _ALL: &[&str] = &[$($string),*];
+        };
+    }
+
+    // Predefined header names, for convenience.
+    // Feel free to add to these.
+    headers!(
+        (ACCEPT_ENCODING, "accept-encoding"),
+        (ACCEPT, "accept"),
+        (AUTHORIZATION, "authorization"),
+        (CONTENT_TYPE, "content-type"),
+        (ETAG, "etag"),
+        (IF_NONE_MATCH, "if-none-match"),
+        (USER_AGENT, "user-agent"),
+        // non-standard, but it's convenient to have these.
+        (RETRY_AFTER, "retry-after"),
+        (X_IF_UNMODIFIED_SINCE, "x-if-unmodified-since"),
+        (X_KEYID, "x-keyid"),
+        (X_LAST_MODIFIED, "x-last-modified"),
+        (X_TIMESTAMP, "x-timestamp"),
+        (X_WEAVE_NEXT_OFFSET, "x-weave-next-offset"),
+        (X_WEAVE_RECORDS, "x-weave-records"),
+        (X_WEAVE_TIMESTAMP, "x-weave-timestamp"),
+        (X_WEAVE_BACKOFF, "x-weave-backoff"),
+    );
+
+    #[test]
+    fn test_predefined() {
+        for &name in _ALL {
+            assert!(
+                HeaderName::new(name).is_ok(),
+                "Invalid header name in predefined header constants: {}",
+                name
+            );
+            assert_eq!(
+                name.to_ascii_lowercase(),
+                name,
+                "Non-lowercase name in predefined header constants: {}",
+                name
+            );
+        }
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/viaduct/headers/name.rs.html b/book/rust-docs/src/viaduct/headers/name.rs.html new file mode 100644 index 0000000000..19dbe3df3b --- /dev/null +++ b/book/rust-docs/src/viaduct/headers/name.rs.html @@ -0,0 +1,465 @@ +name.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use std::borrow::Cow;
+
+/// Represents a header name that we know to be both valid and lowercase.
+/// Internally, this avoids allocating for headers that are constant strings,
+/// like the predefined ones in this crate, however even without that
+/// optimization, we would still likely have an equivalent of this for use
+/// as a case-insensitive string guaranteed to only have valid characters.
+#[derive(Debug, Clone, PartialEq, PartialOrd, Hash, Eq, Ord)]
+pub struct HeaderName(pub(super) Cow<'static, str>);
+
+/// Indicates an invalid header name. Note that we only emit
+/// this for response headers, for request headers, we panic
+/// instead. This is because it would likely come through as
+/// a network error if we emitted it for local headers, when
+/// it's actually a bug that we'd need to fix.
+#[derive(thiserror::Error, Debug, Clone, PartialEq, Eq)]
+#[error("Invalid header name: {0:?}")]
+pub struct InvalidHeaderName(Cow<'static, str>);
+
+impl From<&'static str> for HeaderName {
+    fn from(s: &'static str) -> HeaderName {
+        match HeaderName::new(s) {
+            Ok(v) => v,
+            Err(e) => {
+                panic!("Illegal locally specified header {}", e);
+            }
+        }
+    }
+}
+
+impl From<String> for HeaderName {
+    fn from(s: String) -> HeaderName {
+        match HeaderName::new(s) {
+            Ok(v) => v,
+            Err(e) => {
+                panic!("Illegal locally specified header {}", e);
+            }
+        }
+    }
+}
+
+impl From<Cow<'static, str>> for HeaderName {
+    fn from(s: Cow<'static, str>) -> HeaderName {
+        match HeaderName::new(s) {
+            Ok(v) => v,
+            Err(e) => {
+                panic!("Illegal locally specified header {}", e);
+            }
+        }
+    }
+}
+
+impl InvalidHeaderName {
+    pub fn name(&self) -> &str {
+        &self.0[..]
+    }
+}
+
+fn validate_header(mut name: Cow<'static, str>) -> Result<HeaderName, InvalidHeaderName> {
+    if name.len() == 0 {
+        return Err(invalid_header_name(name));
+    }
+    let mut need_lower_case = false;
+    for b in name.bytes() {
+        let validity = VALID_HEADER_LUT[b as usize];
+        if validity == 0 {
+            return Err(invalid_header_name(name));
+        }
+        if validity == 2 {
+            need_lower_case = true;
+        }
+    }
+    if need_lower_case {
+        // Only do this if needed, since it causes us to own the header.
+        name.to_mut().make_ascii_lowercase();
+    }
+    Ok(HeaderName(name))
+}
+
+impl HeaderName {
+    /// Create a new header. In general you likely want to use `HeaderName::from(s)`
+    /// instead for headers being specified locally (This will panic instead of
+    /// returning a Result, since we have control over headers we specify locally,
+    /// and want to know if we specify an illegal one).
+    #[inline]
+    pub fn new<S: Into<Cow<'static, str>>>(s: S) -> Result<Self, InvalidHeaderName> {
+        validate_header(s.into())
+    }
+
+    #[inline]
+    pub fn as_str(&self) -> &str {
+        &self.0[..]
+    }
+}
+
+impl std::fmt::Display for HeaderName {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.write_str(self.as_str())
+    }
+}
+
+// Separate for dumb micro-optimization reasons.
+#[cold]
+#[inline(never)]
+fn invalid_header_name(s: Cow<'static, str>) -> InvalidHeaderName {
+    log::warn!("Invalid header name: {}", s);
+    InvalidHeaderName(s)
+}
+// Note: 0 = invalid, 1 = valid, 2 = valid but needs lowercasing. I'd use an
+// enum for this, but it would make this LUT *way* harder to look at. This
+// includes 0-9, a-z, A-Z (as 2), and ('!' | '#' | '$' | '%' | '&' | '\'' | '*'
+// | '+' | '-' | '.' | '^' | '_' | '`' | '|' | '~'), matching the field-name
+// token production defined at https://tools.ietf.org/html/rfc7230#section-3.2.
+static VALID_HEADER_LUT: [u8; 256] = [
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
+    0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 1, 1,
+    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+];
+
+impl std::ops::Deref for HeaderName {
+    type Target = str;
+    #[inline]
+    fn deref(&self) -> &str {
+        self.as_str()
+    }
+}
+
+impl AsRef<str> for HeaderName {
+    #[inline]
+    fn as_ref(&self) -> &str {
+        self.as_str()
+    }
+}
+
+impl AsRef<[u8]> for HeaderName {
+    #[inline]
+    fn as_ref(&self) -> &[u8] {
+        self.as_str().as_bytes()
+    }
+}
+
+impl From<HeaderName> for String {
+    #[inline]
+    fn from(h: HeaderName) -> Self {
+        h.0.into()
+    }
+}
+
+impl From<HeaderName> for Cow<'static, str> {
+    #[inline]
+    fn from(h: HeaderName) -> Self {
+        h.0
+    }
+}
+
+impl From<HeaderName> for Vec<u8> {
+    #[inline]
+    fn from(h: HeaderName) -> Self {
+        String::from(h.0).into()
+    }
+}
+
+macro_rules! partialeq_boilerplate {
+    ($T0:ty, $T1:ty) => {
+        // This macro is used for items with and without lifetimes.
+        #[allow(clippy::extra_unused_lifetimes)]
+        impl<'a> PartialEq<$T0> for $T1 {
+            fn eq(&self, other: &$T0) -> bool {
+                // The &* should invoke Deref::deref if it exists, no-op otherwise.
+                (&*self).eq_ignore_ascii_case(&*other)
+            }
+        }
+        #[allow(clippy::extra_unused_lifetimes)]
+        impl<'a> PartialEq<$T1> for $T0 {
+            fn eq(&self, other: &$T1) -> bool {
+                PartialEq::eq(other, self)
+            }
+        }
+    };
+}
+
+partialeq_boilerplate!(HeaderName, str);
+partialeq_boilerplate!(HeaderName, &'a str);
+partialeq_boilerplate!(HeaderName, String);
+partialeq_boilerplate!(HeaderName, &'a String);
+partialeq_boilerplate!(HeaderName, Cow<'a, str>);
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn test_lut() {
+        let mut expect = [0u8; 256];
+        for b in b'0'..=b'9' {
+            expect[b as usize] = 1;
+        }
+        for b in b'a'..=b'z' {
+            expect[b as usize] = 1;
+        }
+        for b in b'A'..=b'Z' {
+            expect[b as usize] = 2;
+        }
+        for b in b"!#$%&'*+-.^_`|~" {
+            expect[*b as usize] = 1;
+        }
+        assert_eq!(&VALID_HEADER_LUT[..], &expect[..]);
+    }
+    #[test]
+    fn test_validate() {
+        assert!(validate_header("".into()).is_err());
+        assert!(validate_header(" foo ".into()).is_err());
+        assert!(validate_header("a=b".into()).is_err());
+        assert_eq!(
+            validate_header("content-type".into()),
+            Ok(HeaderName("content-type".into()))
+        );
+        assert_eq!(
+            validate_header("Content-Type".into()),
+            Ok(HeaderName("content-type".into()))
+        );
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/viaduct/lib.rs.html b/book/rust-docs/src/viaduct/lib.rs.html new file mode 100644 index 0000000000..8902b27207 --- /dev/null +++ b/book/rust-docs/src/viaduct/lib.rs.html @@ -0,0 +1,739 @@ +lib.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#![allow(unknown_lints)]
+#![warn(rust_2018_idioms)]
+
+use url::Url;
+#[macro_use]
+mod headers;
+
+mod backend;
+pub mod error;
+pub mod settings;
+pub use error::*;
+
+pub use backend::{note_backend, set_backend, Backend};
+pub use headers::{consts as header_names, Header, HeaderName, Headers, InvalidHeaderName};
+pub use settings::GLOBAL_SETTINGS;
+
+#[allow(clippy::derive_partial_eq_without_eq)]
+pub(crate) mod msg_types {
+    include!("mozilla.appservices.httpconfig.protobuf.rs");
+}
+
+/// HTTP Methods.
+///
+/// The supported methods are the limited to what's supported by android-components.
+#[derive(Clone, Debug, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[repr(u8)]
+pub enum Method {
+    Get,
+    Head,
+    Post,
+    Put,
+    Delete,
+    Connect,
+    Options,
+    Trace,
+    Patch,
+}
+
+impl Method {
+    pub fn as_str(self) -> &'static str {
+        match self {
+            Method::Get => "GET",
+            Method::Head => "HEAD",
+            Method::Post => "POST",
+            Method::Put => "PUT",
+            Method::Delete => "DELETE",
+            Method::Connect => "CONNECT",
+            Method::Options => "OPTIONS",
+            Method::Trace => "TRACE",
+            Method::Patch => "PATCH",
+        }
+    }
+}
+
+impl std::fmt::Display for Method {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.write_str(self.as_str())
+    }
+}
+
+#[must_use = "`Request`'s \"builder\" functions take by move, not by `&mut self`"]
+#[derive(Clone, Debug)]
+pub struct Request {
+    pub method: Method,
+    pub url: Url,
+    pub headers: Headers,
+    pub body: Option<Vec<u8>>,
+}
+
+impl Request {
+    /// Construct a new request to the given `url` using the given `method`.
+    /// Note that the request is not made until `send()` is called.
+    pub fn new(method: Method, url: Url) -> Self {
+        Self {
+            method,
+            url,
+            headers: Headers::new(),
+            body: None,
+        }
+    }
+
+    pub fn send(self) -> Result<Response, Error> {
+        crate::backend::send(self)
+    }
+
+    /// Alias for `Request::new(Method::Get, url)`, for convenience.
+    pub fn get(url: Url) -> Self {
+        Self::new(Method::Get, url)
+    }
+
+    /// Alias for `Request::new(Method::Patch, url)`, for convenience.
+    pub fn patch(url: Url) -> Self {
+        Self::new(Method::Patch, url)
+    }
+
+    /// Alias for `Request::new(Method::Post, url)`, for convenience.
+    pub fn post(url: Url) -> Self {
+        Self::new(Method::Post, url)
+    }
+
+    /// Alias for `Request::new(Method::Put, url)`, for convenience.
+    pub fn put(url: Url) -> Self {
+        Self::new(Method::Put, url)
+    }
+
+    /// Alias for `Request::new(Method::Delete, url)`, for convenience.
+    pub fn delete(url: Url) -> Self {
+        Self::new(Method::Delete, url)
+    }
+
+    /// Append the provided query parameters to the URL
+    ///
+    /// ## Example
+    /// ```
+    /// # use viaduct::{Request, header_names};
+    /// # use url::Url;
+    /// let some_url = url::Url::parse("https://www.example.com/xyz").unwrap();
+    ///
+    /// let req = Request::post(some_url).query(&[("a", "1234"), ("b", "qwerty")]);
+    /// assert_eq!(req.url.as_str(), "https://www.example.com/xyz?a=1234&b=qwerty");
+    ///
+    /// // This appends to the query query instead of replacing `a`.
+    /// let req = req.query(&[("a", "5678")]);
+    /// assert_eq!(req.url.as_str(), "https://www.example.com/xyz?a=1234&b=qwerty&a=5678");
+    /// ```
+    pub fn query(mut self, pairs: &[(&str, &str)]) -> Self {
+        let mut append_to = self.url.query_pairs_mut();
+        for (k, v) in pairs {
+            append_to.append_pair(k, v);
+        }
+        drop(append_to);
+        self
+    }
+
+    /// Set the query string of the URL. Note that `req.set_query(None)` will
+    /// clear the query.
+    ///
+    /// See also `Request::query` which appends a slice of query pairs, which is
+    /// typically more ergonomic when usable.
+    ///
+    /// ## Example
+    /// ```
+    /// # use viaduct::{Request, header_names};
+    /// # use url::Url;
+    /// let some_url = url::Url::parse("https://www.example.com/xyz").unwrap();
+    ///
+    /// let req = Request::post(some_url).set_query("a=b&c=d");
+    /// assert_eq!(req.url.as_str(), "https://www.example.com/xyz?a=b&c=d");
+    ///
+    /// let req = req.set_query(None);
+    /// assert_eq!(req.url.as_str(), "https://www.example.com/xyz");
+    /// ```
+    pub fn set_query<'a, Q: Into<Option<&'a str>>>(mut self, query: Q) -> Self {
+        self.url.set_query(query.into());
+        self
+    }
+
+    /// Add all the provided headers to the list of headers to send with this
+    /// request.
+    pub fn headers<I>(mut self, to_add: I) -> Self
+    where
+        I: IntoIterator<Item = Header>,
+    {
+        self.headers.extend(to_add);
+        self
+    }
+
+    /// Add the provided header to the list of headers to send with this request.
+    ///
+    /// This returns `Err` if `val` contains characters that may not appear in
+    /// the body of a header.
+    ///
+    /// ## Example
+    /// ```
+    /// # use viaduct::{Request, header_names};
+    /// # use url::Url;
+    /// # fn main() -> Result<(), viaduct::Error> {
+    /// # let some_url = url::Url::parse("https://www.example.com").unwrap();
+    /// Request::post(some_url)
+    ///     .header(header_names::CONTENT_TYPE, "application/json")?
+    ///     .header("My-Header", "Some special value")?;
+    /// // ...
+    /// # Ok(())
+    /// # }
+    /// ```
+    pub fn header<Name, Val>(mut self, name: Name, val: Val) -> Result<Self, crate::Error>
+    where
+        Name: Into<HeaderName> + PartialEq<HeaderName>,
+        Val: Into<String> + AsRef<str>,
+    {
+        self.headers.insert(name, val)?;
+        Ok(self)
+    }
+
+    /// Set this request's body.
+    pub fn body(mut self, body: impl Into<Vec<u8>>) -> Self {
+        self.body = Some(body.into());
+        self
+    }
+
+    /// Set body to the result of serializing `val`, and, unless it has already
+    /// been set, set the Content-Type header to "application/json".
+    ///
+    /// Note: This panics if serde_json::to_vec fails. This can only happen
+    /// in a couple cases:
+    ///
+    /// 1. Trying to serialize a map with non-string keys.
+    /// 2. We wrote a custom serializer that fails.
+    ///
+    /// Neither of these are things we do. If they happen, it seems better for
+    /// this to fail hard with an easy to track down panic, than for e.g. `sync`
+    /// to fail with a JSON parse error (which we'd probably attribute to
+    /// corrupt data on the server, or something).
+    pub fn json<T: ?Sized + serde::Serialize>(mut self, val: &T) -> Self {
+        self.body =
+            Some(serde_json::to_vec(val).expect("Rust component bug: serde_json::to_vec failure"));
+        self.headers
+            .insert_if_missing(header_names::CONTENT_TYPE, "application/json")
+            .unwrap(); // We know this has to be valid.
+        self
+    }
+}
+
+/// A response from the server.
+#[derive(Clone, Debug)]
+pub struct Response {
+    /// The method used to request this response.
+    pub request_method: Method,
+    /// The URL of this response.
+    pub url: Url,
+    /// The HTTP Status code of this response.
+    pub status: u16,
+    /// The headers returned with this response.
+    pub headers: Headers,
+    /// The body of the response.
+    pub body: Vec<u8>,
+}
+
+impl Response {
+    /// Parse the body as JSON.
+    pub fn json<'a, T>(&'a self) -> Result<T, serde_json::Error>
+    where
+        T: serde::Deserialize<'a>,
+    {
+        serde_json::from_slice(&self.body)
+    }
+
+    /// Get the body as a string. Assumes UTF-8 encoding. Any non-utf8 bytes
+    /// are replaced with the replacement character.
+    pub fn text(&self) -> std::borrow::Cow<'_, str> {
+        String::from_utf8_lossy(&self.body)
+    }
+
+    /// Returns true if the status code is in the interval `[200, 300)`.
+    #[inline]
+    pub fn is_success(&self) -> bool {
+        status_codes::is_success_code(self.status)
+    }
+
+    /// Returns true if the status code is in the interval `[500, 600)`.
+    #[inline]
+    pub fn is_server_error(&self) -> bool {
+        status_codes::is_server_error_code(self.status)
+    }
+
+    /// Returns true if the status code is in the interval `[400, 500)`.
+    #[inline]
+    pub fn is_client_error(&self) -> bool {
+        status_codes::is_client_error_code(self.status)
+    }
+
+    /// Returns an [`UnexpectedStatus`] error if `self.is_success()` is false,
+    /// otherwise returns `Ok(self)`.
+    #[inline]
+    pub fn require_success(self) -> Result<Self, UnexpectedStatus> {
+        if self.is_success() {
+            Ok(self)
+        } else {
+            Err(UnexpectedStatus {
+                method: self.request_method,
+                // XXX We probably should try and sanitize this. Replace the user id
+                // if it's a sync token server URL, for example.
+                url: self.url,
+                status: self.status,
+            })
+        }
+    }
+}
+
+/// A module containing constants for all HTTP status codes.
+pub mod status_codes {
+
+    /// Is it a 2xx status?
+    #[inline]
+    pub fn is_success_code(c: u16) -> bool {
+        (200..300).contains(&c)
+    }
+
+    /// Is it a 4xx error?
+    #[inline]
+    pub fn is_client_error_code(c: u16) -> bool {
+        (400..500).contains(&c)
+    }
+
+    /// Is it a 5xx error?
+    #[inline]
+    pub fn is_server_error_code(c: u16) -> bool {
+        (500..600).contains(&c)
+    }
+
+    macro_rules! define_status_codes {
+        ($(($val:expr, $NAME:ident)),* $(,)?) => {
+            $(pub const $NAME: u16 = $val;)*
+        };
+    }
+    // From https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
+    define_status_codes![
+        (100, CONTINUE),
+        (101, SWITCHING_PROTOCOLS),
+        // 2xx
+        (200, OK),
+        (201, CREATED),
+        (202, ACCEPTED),
+        (203, NONAUTHORITATIVE_INFORMATION),
+        (204, NO_CONTENT),
+        (205, RESET_CONTENT),
+        (206, PARTIAL_CONTENT),
+        // 3xx
+        (300, MULTIPLE_CHOICES),
+        (301, MOVED_PERMANENTLY),
+        (302, FOUND),
+        (303, SEE_OTHER),
+        (304, NOT_MODIFIED),
+        (305, USE_PROXY),
+        // no 306
+        (307, TEMPORARY_REDIRECT),
+        // 4xx
+        (400, BAD_REQUEST),
+        (401, UNAUTHORIZED),
+        (402, PAYMENT_REQUIRED),
+        (403, FORBIDDEN),
+        (404, NOT_FOUND),
+        (405, METHOD_NOT_ALLOWED),
+        (406, NOT_ACCEPTABLE),
+        (407, PROXY_AUTHENTICATION_REQUIRED),
+        (408, REQUEST_TIMEOUT),
+        (409, CONFLICT),
+        (410, GONE),
+        (411, LENGTH_REQUIRED),
+        (412, PRECONDITION_FAILED),
+        (413, REQUEST_ENTITY_TOO_LARGE),
+        (414, REQUEST_URI_TOO_LONG),
+        (415, UNSUPPORTED_MEDIA_TYPE),
+        (416, REQUESTED_RANGE_NOT_SATISFIABLE),
+        (417, EXPECTATION_FAILED),
+        (429, TOO_MANY_REQUESTS),
+        // 5xx
+        (500, INTERNAL_SERVER_ERROR),
+        (501, NOT_IMPLEMENTED),
+        (502, BAD_GATEWAY),
+        (503, SERVICE_UNAVAILABLE),
+        (504, GATEWAY_TIMEOUT),
+        (505, HTTP_VERSION_NOT_SUPPORTED),
+    ];
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/viaduct/mozilla.appservices.httpconfig.protobuf.rs.html b/book/rust-docs/src/viaduct/mozilla.appservices.httpconfig.protobuf.rs.html new file mode 100644 index 0000000000..971843b20a --- /dev/null +++ b/book/rust-docs/src/viaduct/mozilla.appservices.httpconfig.protobuf.rs.html @@ -0,0 +1,203 @@ +mozilla.appservices.httpconfig.protobuf.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+
#[allow(clippy::derive_partial_eq_without_eq)]
+#[derive(Clone, PartialEq, ::prost::Message)]
+pub struct Request {
+    #[prost(enumeration = "request::Method", required, tag = "1")]
+    pub method: i32,
+    #[prost(string, required, tag = "2")]
+    pub url: ::prost::alloc::string::String,
+    #[prost(bytes = "vec", optional, tag = "3")]
+    pub body: ::core::option::Option<::prost::alloc::vec::Vec<u8>>,
+    #[prost(map = "string, string", tag = "4")]
+    pub headers: ::std::collections::HashMap<
+        ::prost::alloc::string::String,
+        ::prost::alloc::string::String,
+    >,
+    #[prost(bool, required, tag = "5")]
+    pub follow_redirects: bool,
+    #[prost(bool, required, tag = "6")]
+    pub use_caches: bool,
+    #[prost(int32, required, tag = "7")]
+    pub connect_timeout_secs: i32,
+    #[prost(int32, required, tag = "8")]
+    pub read_timeout_secs: i32,
+}
+/// Nested message and enum types in `Request`.
+pub mod request {
+    #[derive(
+        Clone,
+        Copy,
+        Debug,
+        PartialEq,
+        Eq,
+        Hash,
+        PartialOrd,
+        Ord,
+        ::prost::Enumeration
+    )]
+    #[repr(i32)]
+    pub enum Method {
+        Get = 0,
+        Head = 1,
+        Post = 2,
+        Put = 3,
+        Delete = 4,
+        Connect = 5,
+        Options = 6,
+        Trace = 7,
+        Patch = 8,
+    }
+    impl Method {
+        /// String value of the enum field names used in the ProtoBuf definition.
+        ///
+        /// The values are not transformed in any way and thus are considered stable
+        /// (if the ProtoBuf definition does not change) and safe for programmatic use.
+        pub fn as_str_name(&self) -> &'static str {
+            match self {
+                Method::Get => "GET",
+                Method::Head => "HEAD",
+                Method::Post => "POST",
+                Method::Put => "PUT",
+                Method::Delete => "DELETE",
+                Method::Connect => "CONNECT",
+                Method::Options => "OPTIONS",
+                Method::Trace => "TRACE",
+                Method::Patch => "PATCH",
+            }
+        }
+        /// Creates an enum from field names used in the ProtoBuf definition.
+        pub fn from_str_name(value: &str) -> ::core::option::Option<Self> {
+            match value {
+                "GET" => Some(Self::Get),
+                "HEAD" => Some(Self::Head),
+                "POST" => Some(Self::Post),
+                "PUT" => Some(Self::Put),
+                "DELETE" => Some(Self::Delete),
+                "CONNECT" => Some(Self::Connect),
+                "OPTIONS" => Some(Self::Options),
+                "TRACE" => Some(Self::Trace),
+                "PATCH" => Some(Self::Patch),
+                _ => None,
+            }
+        }
+    }
+}
+#[allow(clippy::derive_partial_eq_without_eq)]
+#[derive(Clone, PartialEq, ::prost::Message)]
+pub struct Response {
+    /// If this is present, nothing else is.
+    #[prost(string, optional, tag = "1")]
+    pub exception_message: ::core::option::Option<::prost::alloc::string::String>,
+    #[prost(string, optional, tag = "2")]
+    pub url: ::core::option::Option<::prost::alloc::string::String>,
+    #[prost(int32, optional, tag = "3")]
+    pub status: ::core::option::Option<i32>,
+    #[prost(bytes = "vec", optional, tag = "4")]
+    pub body: ::core::option::Option<::prost::alloc::vec::Vec<u8>>,
+    #[prost(map = "string, string", tag = "5")]
+    pub headers: ::std::collections::HashMap<
+        ::prost::alloc::string::String,
+        ::prost::alloc::string::String,
+    >,
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/viaduct/settings.rs.html b/book/rust-docs/src/viaduct/settings.rs.html new file mode 100644 index 0000000000..afb84de450 --- /dev/null +++ b/book/rust-docs/src/viaduct/settings.rs.html @@ -0,0 +1,93 @@ +settings.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use once_cell::sync::Lazy;
+use parking_lot::RwLock;
+use std::time::Duration;
+use url::Url;
+
+/// Note: reqwest allows these only to be specified per-Client. concept-fetch
+/// allows these to be specified on each call to fetch. I think it's worth
+/// keeping a single global reqwest::Client in the reqwest backend, to simplify
+/// the way we abstract away from these.
+///
+/// In the future, should we need it, we might be able to add a CustomClient type
+/// with custom settings. In the reqwest backend this would store a Client, and
+/// in the concept-fetch backend it would only store the settings, and populate
+/// things on the fly.
+#[derive(Debug)]
+#[non_exhaustive]
+pub struct Settings {
+    pub read_timeout: Option<Duration>,
+    pub connect_timeout: Option<Duration>,
+    pub follow_redirects: bool,
+    pub use_caches: bool,
+    // For testing purposes, we allow exactly one additional Url which is
+    // allowed to not be https.
+    pub addn_allowed_insecure_url: Option<Url>,
+}
+
+#[cfg(target_os = "ios")]
+const TIMEOUT_DURATION: Duration = Duration::from_secs(7);
+
+#[cfg(not(target_os = "ios"))]
+const TIMEOUT_DURATION: Duration = Duration::from_secs(10);
+
+// The singleton instance of our settings.
+pub static GLOBAL_SETTINGS: Lazy<RwLock<Settings>> = Lazy::new(|| {
+    RwLock::new(Settings {
+        read_timeout: Some(TIMEOUT_DURATION),
+        connect_timeout: Some(TIMEOUT_DURATION),
+        follow_redirects: true,
+        use_caches: false,
+        addn_allowed_insecure_url: None,
+    })
+});
+
\ No newline at end of file diff --git a/book/rust-docs/src/viaduct_reqwest/lib.rs.html b/book/rust-docs/src/viaduct_reqwest/lib.rs.html new file mode 100644 index 0000000000..912b3d7363 --- /dev/null +++ b/book/rust-docs/src/viaduct_reqwest/lib.rs.html @@ -0,0 +1,251 @@ +lib.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use once_cell::sync::Lazy;
+use std::{io::Read, sync::Once};
+use viaduct::{settings::GLOBAL_SETTINGS, Backend};
+
+// Note: we don't `use` things from reqwest or the viaduct crate because
+// it would be rather confusing given that we have the same name for
+// most things as them.
+
+static CLIENT: Lazy<reqwest::blocking::Client> = Lazy::new(|| {
+    let settings = GLOBAL_SETTINGS.read();
+    let mut builder = reqwest::blocking::ClientBuilder::new()
+        .timeout(settings.read_timeout)
+        .connect_timeout(settings.connect_timeout)
+        .redirect(if settings.follow_redirects {
+            reqwest::redirect::Policy::default()
+        } else {
+            reqwest::redirect::Policy::none()
+        });
+    if cfg!(target_os = "ios") {
+        // The FxA servers rely on the UA agent to filter
+        // some push messages directed to iOS devices.
+        // This is obviously a terrible hack and we should
+        // probably do https://github.com/mozilla/application-services/issues/1326
+        // instead, but this will unblock us for now.
+        builder = builder.user_agent("Firefox-iOS-FxA/24");
+    }
+    // Note: no cookie or cache support.
+    builder
+        .build()
+        .expect("Failed to initialize global reqwest::Client")
+});
+
+#[allow(clippy::unnecessary_wraps)] // not worth the time to untangle
+fn into_reqwest(request: viaduct::Request) -> Result<reqwest::blocking::Request, viaduct::Error> {
+    let method = match request.method {
+        viaduct::Method::Get => reqwest::Method::GET,
+        viaduct::Method::Head => reqwest::Method::HEAD,
+        viaduct::Method::Post => reqwest::Method::POST,
+        viaduct::Method::Put => reqwest::Method::PUT,
+        viaduct::Method::Delete => reqwest::Method::DELETE,
+        viaduct::Method::Connect => reqwest::Method::CONNECT,
+        viaduct::Method::Options => reqwest::Method::OPTIONS,
+        viaduct::Method::Trace => reqwest::Method::TRACE,
+        viaduct::Method::Patch => reqwest::Method::PATCH,
+    };
+    let mut result = reqwest::blocking::Request::new(method, request.url);
+    for h in request.headers {
+        use reqwest::header::{HeaderName, HeaderValue};
+        // Unwraps should be fine, we verify these in `Header`
+        let value = HeaderValue::from_str(h.value()).unwrap();
+        result
+            .headers_mut()
+            .insert(HeaderName::from_bytes(h.name().as_bytes()).unwrap(), value);
+    }
+    *result.body_mut() = request.body.map(reqwest::blocking::Body::from);
+    Ok(result)
+}
+
+pub struct ReqwestBackend;
+impl Backend for ReqwestBackend {
+    fn send(&self, request: viaduct::Request) -> Result<viaduct::Response, viaduct::Error> {
+        viaduct::note_backend("reqwest (untrusted)");
+        let request_method = request.method;
+        let req = into_reqwest(request)?;
+        let mut resp = CLIENT
+            .execute(req)
+            .map_err(|e| viaduct::Error::NetworkError(e.to_string()))?;
+        let status = resp.status().as_u16();
+        let url = resp.url().clone();
+        let mut body = Vec::with_capacity(resp.content_length().unwrap_or_default() as usize);
+        resp.read_to_end(&mut body).map_err(|e| {
+            log::error!("Failed to get body from response: {:?}", e);
+            viaduct::Error::NetworkError(e.to_string())
+        })?;
+        let mut headers = viaduct::Headers::with_capacity(resp.headers().len());
+        for (k, v) in resp.headers() {
+            let val = String::from_utf8_lossy(v.as_bytes()).to_string();
+            let hname = match viaduct::HeaderName::new(k.as_str().to_owned()) {
+                Ok(name) => name,
+                Err(e) => {
+                    // Ignore headers with invalid names, since nobody can look for them anyway.
+                    log::warn!("Server sent back invalid header name: '{}'", e);
+                    continue;
+                }
+            };
+            // Not using Header::new since the error it returns is for request headers.
+            headers.insert_header(viaduct::Header::new_unchecked(hname, val));
+        }
+        Ok(viaduct::Response {
+            request_method,
+            url,
+            status,
+            headers,
+            body,
+        })
+    }
+}
+
+static INIT_REQWEST_BACKEND: Once = Once::new();
+
+pub fn use_reqwest_backend() {
+    INIT_REQWEST_BACKEND.call_once(|| {
+        viaduct::set_backend(Box::leak(Box::new(ReqwestBackend)))
+            .expect("Backend already set (FFI)");
+    })
+}
+
+#[no_mangle]
+#[cfg(target_os = "ios")]
+pub extern "C" fn viaduct_use_reqwest_backend() {
+    use_reqwest_backend();
+}
+
+/// A dummy symbol we include so that we can detect whether or not the reqwest
+/// backend got compiled in.
+#[no_mangle]
+pub extern "C" fn viaduct_detect_reqwest_backend() {
+    ffi_support::abort_on_panic::call_with_output(|| {
+        println!("Nothing to see here (reqwest backend available).");
+    });
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/webext_storage/api.rs.html b/book/rust-docs/src/webext_storage/api.rs.html new file mode 100644 index 0000000000..51286bb633 --- /dev/null +++ b/book/rust-docs/src/webext_storage/api.rs.html @@ -0,0 +1,1491 @@ +api.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
+540
+541
+542
+543
+544
+545
+546
+547
+548
+549
+550
+551
+552
+553
+554
+555
+556
+557
+558
+559
+560
+561
+562
+563
+564
+565
+566
+567
+568
+569
+570
+571
+572
+573
+574
+575
+576
+577
+578
+579
+580
+581
+582
+583
+584
+585
+586
+587
+588
+589
+590
+591
+592
+593
+594
+595
+596
+597
+598
+599
+600
+601
+602
+603
+604
+605
+606
+607
+608
+609
+610
+611
+612
+613
+614
+615
+616
+617
+618
+619
+620
+621
+622
+623
+624
+625
+626
+627
+628
+629
+630
+631
+632
+633
+634
+635
+636
+637
+638
+639
+640
+641
+642
+643
+644
+645
+646
+647
+648
+649
+650
+651
+652
+653
+654
+655
+656
+657
+658
+659
+660
+661
+662
+663
+664
+665
+666
+667
+668
+669
+670
+671
+672
+673
+674
+675
+676
+677
+678
+679
+680
+681
+682
+683
+684
+685
+686
+687
+688
+689
+690
+691
+692
+693
+694
+695
+696
+697
+698
+699
+700
+701
+702
+703
+704
+705
+706
+707
+708
+709
+710
+711
+712
+713
+714
+715
+716
+717
+718
+719
+720
+721
+722
+723
+724
+725
+726
+727
+728
+729
+730
+731
+732
+733
+734
+735
+736
+737
+738
+739
+740
+741
+742
+743
+744
+745
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::error::*;
+use rusqlite::{Connection, Transaction};
+use serde::{ser::SerializeMap, Serialize, Serializer};
+
+use serde_json::{Map, Value as JsonValue};
+use sql_support::{self, ConnExt};
+
+// These constants are defined by the chrome.storage.sync spec. We export them
+// publicly from this module, then from the crate, so they wind up in the
+// clients.
+// Note the limits for `chrome.storage.sync` and `chrome.storage.local` are
+// different, and these are from `.sync` - we'll have work to do if we end up
+// wanting this to be used for `.local` too!
+pub const SYNC_QUOTA_BYTES: usize = 102_400;
+pub const SYNC_QUOTA_BYTES_PER_ITEM: usize = 8_192;
+pub const SYNC_MAX_ITEMS: usize = 512;
+// Note there are also constants for "operations per minute" etc, which aren't
+// enforced here.
+
+type JsonMap = Map<String, JsonValue>;
+
+enum StorageChangeOp {
+    Clear,
+    Set(JsonValue),
+    SetWithoutQuota(JsonValue),
+}
+
+fn get_from_db(conn: &Connection, ext_id: &str) -> Result<Option<JsonMap>> {
+    Ok(
+        match conn.try_query_one::<String, _>(
+            "SELECT data FROM storage_sync_data
+             WHERE ext_id = :ext_id",
+            &[(":ext_id", &ext_id)],
+            true,
+        )? {
+            Some(s) => match serde_json::from_str(&s)? {
+                JsonValue::Object(m) => Some(m),
+                // we could panic here as it's theoretically impossible, but we
+                // might as well treat it as not existing...
+                _ => None,
+            },
+            None => None,
+        },
+    )
+}
+
+fn save_to_db(tx: &Transaction<'_>, ext_id: &str, val: &StorageChangeOp) -> Result<()> {
+    // This function also handles removals. Either an empty map or explicit null
+    // is a removal. If there's a mirror record for this extension ID, then we
+    // must leave a tombstone behind for syncing.
+    let is_delete = match val {
+        StorageChangeOp::Clear => true,
+        StorageChangeOp::Set(JsonValue::Object(v)) => v.is_empty(),
+        StorageChangeOp::SetWithoutQuota(JsonValue::Object(v)) => v.is_empty(),
+        _ => false,
+    };
+    if is_delete {
+        let in_mirror = tx
+            .try_query_one(
+                "SELECT EXISTS(SELECT 1 FROM storage_sync_mirror WHERE ext_id = :ext_id);",
+                rusqlite::named_params! {
+                    ":ext_id": ext_id,
+                },
+                true,
+            )?
+            .unwrap_or_default();
+        if in_mirror {
+            log::trace!("saving data for '{}': leaving a tombstone", ext_id);
+            tx.execute_cached(
+                "
+                INSERT INTO storage_sync_data(ext_id, data, sync_change_counter)
+                VALUES (:ext_id, NULL, 1)
+                ON CONFLICT (ext_id) DO UPDATE
+                SET data = NULL, sync_change_counter = sync_change_counter + 1",
+                rusqlite::named_params! {
+                    ":ext_id": ext_id,
+                },
+            )?;
+        } else {
+            log::trace!("saving data for '{}': removing the row", ext_id);
+            tx.execute_cached(
+                "
+                DELETE FROM storage_sync_data WHERE ext_id = :ext_id",
+                rusqlite::named_params! {
+                    ":ext_id": ext_id,
+                },
+            )?;
+        }
+    } else {
+        // Convert to bytes so we can enforce the quota if necessary.
+        let sval = match val {
+            StorageChangeOp::Set(v) => {
+                let sv = v.to_string();
+                if sv.len() > SYNC_QUOTA_BYTES {
+                    return Err(ErrorKind::QuotaError(QuotaReason::TotalBytes).into());
+                }
+                sv
+            }
+            StorageChangeOp::SetWithoutQuota(v) => v.to_string(),
+            StorageChangeOp::Clear => unreachable!(),
+        };
+
+        log::trace!("saving data for '{}': writing", ext_id);
+        tx.execute_cached(
+            "INSERT INTO storage_sync_data(ext_id, data, sync_change_counter)
+                VALUES (:ext_id, :data, 1)
+                ON CONFLICT (ext_id) DO UPDATE
+                set data=:data, sync_change_counter = sync_change_counter + 1",
+            rusqlite::named_params! {
+                ":ext_id": ext_id,
+                ":data": &sval,
+            },
+        )?;
+    }
+    Ok(())
+}
+
+fn remove_from_db(tx: &Transaction<'_>, ext_id: &str) -> Result<()> {
+    save_to_db(tx, ext_id, &StorageChangeOp::Clear)
+}
+
+// This is a "helper struct" for the callback part of the chrome.storage spec,
+// but shaped in a way to make it more convenient from the rust side of the
+// world.
+#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct StorageValueChange {
+    #[serde(skip_serializing)]
+    pub key: String,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub old_value: Option<JsonValue>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub new_value: Option<JsonValue>,
+}
+
+// This is, largely, a helper so that this serializes correctly as per the
+// chrome.storage.sync spec. If not for custom serialization it should just
+// be a plain vec
+#[derive(Debug, Default, Clone, PartialEq, Eq)]
+pub struct StorageChanges {
+    changes: Vec<StorageValueChange>,
+}
+
+impl StorageChanges {
+    pub fn new() -> Self {
+        Self::default()
+    }
+
+    pub fn with_capacity(n: usize) -> Self {
+        Self {
+            changes: Vec::with_capacity(n),
+        }
+    }
+
+    pub fn is_empty(&self) -> bool {
+        self.changes.is_empty()
+    }
+
+    pub fn push(&mut self, change: StorageValueChange) {
+        self.changes.push(change)
+    }
+}
+
+// and it serializes as a map.
+impl Serialize for StorageChanges {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        let mut map = serializer.serialize_map(Some(self.changes.len()))?;
+        for change in &self.changes {
+            map.serialize_entry(&change.key, change)?;
+        }
+        map.end()
+    }
+}
+
+// A helper to determine the size of a key/value combination from the
+// perspective of quota and getBytesInUse().
+pub fn get_quota_size_of(key: &str, v: &JsonValue) -> usize {
+    // Reading the chrome docs literally re the quota, the length of the key
+    // is just the string len, but the value is the json val, as bytes.
+    key.len() + v.to_string().len()
+}
+
+/// The implementation of `storage[.sync].set()`. On success this returns the
+/// StorageChanges defined by the chrome API - it's assumed the caller will
+/// arrange to deliver this to observers as defined in that API.
+pub fn set(tx: &Transaction<'_>, ext_id: &str, val: JsonValue) -> Result<StorageChanges> {
+    let val_map = match val {
+        JsonValue::Object(m) => m,
+        // Not clear what the error semantics should be yet. For now, pretend an empty map.
+        _ => Map::new(),
+    };
+
+    let mut current = get_from_db(tx, ext_id)?.unwrap_or_default();
+
+    let mut changes = StorageChanges::with_capacity(val_map.len());
+
+    // iterate over the value we are adding/updating.
+    for (k, v) in val_map.into_iter() {
+        let old_value = current.remove(&k);
+        if current.len() >= SYNC_MAX_ITEMS {
+            return Err(ErrorKind::QuotaError(QuotaReason::MaxItems).into());
+        }
+        // Reading the chrome docs literally re the quota, the length of the key
+        // is just the string len, but the value is the json val, as bytes
+        if get_quota_size_of(&k, &v) > SYNC_QUOTA_BYTES_PER_ITEM {
+            return Err(ErrorKind::QuotaError(QuotaReason::ItemBytes).into());
+        }
+        let change = StorageValueChange {
+            key: k.clone(),
+            old_value,
+            new_value: Some(v.clone()),
+        };
+        changes.push(change);
+        current.insert(k, v);
+    }
+
+    save_to_db(
+        tx,
+        ext_id,
+        &StorageChangeOp::Set(JsonValue::Object(current)),
+    )?;
+    Ok(changes)
+}
+
+// A helper which takes a param indicating what keys should be returned and
+// converts that to a vec of real strings. Also returns "default" values to
+// be used if no item exists for that key.
+fn get_keys(keys: JsonValue) -> Vec<(String, Option<JsonValue>)> {
+    match keys {
+        JsonValue::String(s) => vec![(s, None)],
+        JsonValue::Array(keys) => {
+            // because nothing with json is ever simple, each key may not be
+            // a string. We ignore any which aren't.
+            keys.iter()
+                .filter_map(|v| v.as_str().map(|s| (s.to_string(), None)))
+                .collect()
+        }
+        JsonValue::Object(m) => m.into_iter().map(|(k, d)| (k, Some(d))).collect(),
+        _ => vec![],
+    }
+}
+
+/// The implementation of `storage[.sync].get()` - on success this always
+/// returns a Json object.
+pub fn get(conn: &Connection, ext_id: &str, keys: JsonValue) -> Result<JsonValue> {
+    // key is optional, or string or array of string or object keys
+    let maybe_existing = get_from_db(conn, ext_id)?;
+    let mut existing = match (maybe_existing, keys.is_object()) {
+        (None, true) => return Ok(keys),
+        (None, false) => return Ok(JsonValue::Object(Map::new())),
+        (Some(v), _) => v,
+    };
+    // take the quick path for null, where we just return the entire object.
+    if keys.is_null() {
+        return Ok(JsonValue::Object(existing));
+    }
+    // OK, so we need to build a list of keys to get.
+    let keys_and_defaults = get_keys(keys);
+    let mut result = Map::with_capacity(keys_and_defaults.len());
+    for (key, maybe_default) in keys_and_defaults {
+        if let Some(v) = existing.remove(&key) {
+            result.insert(key, v);
+        } else if let Some(def) = maybe_default {
+            result.insert(key, def);
+        }
+        // else |keys| is a string/array instead of an object with defaults.
+        // Don't include keys without default values.
+    }
+    Ok(JsonValue::Object(result))
+}
+
+/// The implementation of `storage[.sync].remove()`. On success this returns the
+/// StorageChanges defined by the chrome API - it's assumed the caller will
+/// arrange to deliver this to observers as defined in that API.
+pub fn remove(tx: &Transaction<'_>, ext_id: &str, keys: JsonValue) -> Result<StorageChanges> {
+    let mut existing = match get_from_db(tx, ext_id)? {
+        None => return Ok(StorageChanges::new()),
+        Some(v) => v,
+    };
+
+    // Note: get_keys parses strings, arrays and objects, but remove()
+    // is expected to only be passed a string or array of strings.
+    let keys_and_defs = get_keys(keys);
+
+    let mut result = StorageChanges::with_capacity(keys_and_defs.len());
+    for (key, _) in keys_and_defs {
+        if let Some(v) = existing.remove(&key) {
+            result.push(StorageValueChange {
+                key,
+                old_value: Some(v),
+                new_value: None,
+            });
+        }
+    }
+    if !result.is_empty() {
+        save_to_db(
+            tx,
+            ext_id,
+            &StorageChangeOp::SetWithoutQuota(JsonValue::Object(existing)),
+        )?;
+    }
+    Ok(result)
+}
+
+/// The implementation of `storage[.sync].clear()`. On success this returns the
+/// StorageChanges defined by the chrome API - it's assumed the caller will
+/// arrange to deliver this to observers as defined in that API.
+pub fn clear(tx: &Transaction<'_>, ext_id: &str) -> Result<StorageChanges> {
+    let existing = match get_from_db(tx, ext_id)? {
+        None => return Ok(StorageChanges::new()),
+        Some(v) => v,
+    };
+    let mut result = StorageChanges::with_capacity(existing.len());
+    for (key, val) in existing.into_iter() {
+        result.push(StorageValueChange {
+            key: key.to_string(),
+            new_value: None,
+            old_value: Some(val),
+        });
+    }
+    remove_from_db(tx, ext_id)?;
+    Ok(result)
+}
+
+/// The implementation of `storage[.sync].getBytesInUse()`.
+pub fn get_bytes_in_use(conn: &Connection, ext_id: &str, keys: JsonValue) -> Result<usize> {
+    let maybe_existing = get_from_db(conn, ext_id)?;
+    let existing = match maybe_existing {
+        None => return Ok(0),
+        Some(v) => v,
+    };
+    // Make an array of all the keys we we are going to count.
+    let keys: Vec<&str> = match &keys {
+        JsonValue::Null => existing.keys().map(|v| v.as_str()).collect(),
+        JsonValue::String(name) => vec![name.as_str()],
+        JsonValue::Array(names) => names.iter().filter_map(|v| v.as_str()).collect(),
+        // in the spirit of json-based APIs, silently ignore strange things.
+        _ => return Ok(0),
+    };
+    // We must use the same way of counting as our quota enforcement.
+    let mut size = 0;
+    for key in keys.into_iter() {
+        if let Some(v) = existing.get(key) {
+            size += get_quota_size_of(key, v);
+        }
+    }
+    Ok(size)
+}
+
+/// Information about the usage of a single extension.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct UsageInfo {
+    /// The extension id.
+    pub ext_id: String,
+    /// The number of keys the extension uses.
+    pub num_keys: usize,
+    /// The number of bytes used by the extension. This result is somewhat rough
+    /// -- it doesn't bother counting the size of the extension ID, or data in
+    /// the mirror, and favors returning the exact number of bytes used by the
+    /// column (that is, the size of the JSON object) rather than replicating
+    /// the `get_bytes_in_use` return value for all keys.
+    pub num_bytes: usize,
+}
+
+/// Exposes information about per-collection usage for the purpose of telemetry.
+/// (Doesn't map to an actual `chrome.storage.sync` API).
+pub fn usage(db: &Connection) -> Result<Vec<UsageInfo>> {
+    type JsonObject = Map<String, JsonValue>;
+    let sql = "
+        SELECT ext_id, data
+        FROM storage_sync_data
+        WHERE data IS NOT NULL
+        -- for tests and determinism
+        ORDER BY ext_id
+    ";
+    db.query_rows_into(sql, [], |row| {
+        let ext_id: String = row.get("ext_id")?;
+        let data: String = row.get("data")?;
+        let num_bytes = data.len();
+        let num_keys = serde_json::from_str::<JsonObject>(&data)?.len();
+        Ok(UsageInfo {
+            ext_id,
+            num_keys,
+            num_bytes,
+        })
+    })
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::db::test::new_mem_db;
+    use serde_json::json;
+
+    #[test]
+    fn test_serialize_storage_changes() -> Result<()> {
+        let c = StorageChanges {
+            changes: vec![StorageValueChange {
+                key: "key".to_string(),
+                old_value: Some(json!("old")),
+                new_value: None,
+            }],
+        };
+        assert_eq!(serde_json::to_string(&c)?, r#"{"key":{"oldValue":"old"}}"#);
+        let c = StorageChanges {
+            changes: vec![StorageValueChange {
+                key: "key".to_string(),
+                old_value: None,
+                new_value: Some(json!({"foo": "bar"})),
+            }],
+        };
+        assert_eq!(
+            serde_json::to_string(&c)?,
+            r#"{"key":{"newValue":{"foo":"bar"}}}"#
+        );
+        Ok(())
+    }
+
+    fn make_changes(changes: &[(&str, Option<JsonValue>, Option<JsonValue>)]) -> StorageChanges {
+        let mut r = StorageChanges::with_capacity(changes.len());
+        for (name, old_value, new_value) in changes {
+            r.push(StorageValueChange {
+                key: (*name).to_string(),
+                old_value: old_value.clone(),
+                new_value: new_value.clone(),
+            });
+        }
+        r
+    }
+
+    #[test]
+    fn test_simple() -> Result<()> {
+        let ext_id = "x";
+        let mut db = new_mem_db();
+        let tx = db.transaction()?;
+
+        // an empty store.
+        for q in vec![JsonValue::Null, json!("foo"), json!(["foo"])].into_iter() {
+            assert_eq!(get(&tx, ext_id, q)?, json!({}));
+        }
+
+        // Default values in an empty store.
+        for q in vec![json!({ "foo": null }), json!({"foo": "default"})].into_iter() {
+            assert_eq!(get(&tx, ext_id, q.clone())?, q.clone());
+        }
+
+        // Single item in the store.
+        set(&tx, ext_id, json!({"foo": "bar" }))?;
+        for q in vec![
+            JsonValue::Null,
+            json!("foo"),
+            json!(["foo"]),
+            json!({ "foo": null }),
+            json!({"foo": "default"}),
+        ]
+        .into_iter()
+        {
+            assert_eq!(get(&tx, ext_id, q)?, json!({"foo": "bar" }));
+        }
+
+        // Default values in a non-empty store.
+        for q in vec![
+            json!({ "non_existing_key": null }),
+            json!({"non_existing_key": 0}),
+            json!({"non_existing_key": false}),
+            json!({"non_existing_key": "default"}),
+            json!({"non_existing_key": ["array"]}),
+            json!({"non_existing_key": {"objectkey": "value"}}),
+        ]
+        .into_iter()
+        {
+            assert_eq!(get(&tx, ext_id, q.clone())?, q.clone());
+        }
+
+        // more complex stuff, including changes checking.
+        assert_eq!(
+            set(&tx, ext_id, json!({"foo": "new", "other": "also new" }))?,
+            make_changes(&[
+                ("foo", Some(json!("bar")), Some(json!("new"))),
+                ("other", None, Some(json!("also new")))
+            ])
+        );
+        assert_eq!(
+            get(&tx, ext_id, JsonValue::Null)?,
+            json!({"foo": "new", "other": "also new"})
+        );
+        assert_eq!(get(&tx, ext_id, json!("foo"))?, json!({"foo": "new"}));
+        assert_eq!(
+            get(&tx, ext_id, json!(["foo", "other"]))?,
+            json!({"foo": "new", "other": "also new"})
+        );
+        assert_eq!(
+            get(&tx, ext_id, json!({"foo": null, "default": "yo"}))?,
+            json!({"foo": "new", "default": "yo"})
+        );
+
+        assert_eq!(
+            remove(&tx, ext_id, json!("foo"))?,
+            make_changes(&[("foo", Some(json!("new")), None)]),
+        );
+
+        assert_eq!(
+            set(&tx, ext_id, json!({"foo": {"sub-object": "sub-value"}}))?,
+            make_changes(&[("foo", None, Some(json!({"sub-object": "sub-value"}))),])
+        );
+
+        // XXX - other variants.
+
+        assert_eq!(
+            clear(&tx, ext_id)?,
+            make_changes(&[
+                ("foo", Some(json!({"sub-object": "sub-value"})), None),
+                ("other", Some(json!("also new")), None),
+            ]),
+        );
+        assert_eq!(get(&tx, ext_id, JsonValue::Null)?, json!({}));
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_check_get_impl() -> Result<()> {
+        // This is a port of checkGetImpl in test_ext_storage.js in Desktop.
+        let ext_id = "x";
+        let mut db = new_mem_db();
+        let tx = db.transaction()?;
+
+        let prop = "test-prop";
+        let value = "test-value";
+
+        set(&tx, ext_id, json!({ prop: value }))?;
+
+        // this is the checkGetImpl part!
+        let mut data = get(&tx, ext_id, json!(null))?;
+        assert_eq!(value, json!(data[prop]), "null getter worked for {}", prop);
+
+        data = get(&tx, ext_id, json!(prop))?;
+        assert_eq!(
+            value,
+            json!(data[prop]),
+            "string getter worked for {}",
+            prop
+        );
+        assert_eq!(
+            data.as_object().unwrap().len(),
+            1,
+            "string getter should return an object with a single property"
+        );
+
+        data = get(&tx, ext_id, json!([prop]))?;
+        assert_eq!(value, json!(data[prop]), "array getter worked for {}", prop);
+        assert_eq!(
+            data.as_object().unwrap().len(),
+            1,
+            "array getter with a single key should return an object with a single property"
+        );
+
+        // checkGetImpl() uses `{ [prop]: undefined }` - but json!() can't do that :(
+        // Hopefully it's just testing a simple object, so we use `{ prop: null }`
+        data = get(&tx, ext_id, json!({ prop: null }))?;
+        assert_eq!(
+            value,
+            json!(data[prop]),
+            "object getter worked for {}",
+            prop
+        );
+        assert_eq!(
+            data.as_object().unwrap().len(),
+            1,
+            "object getter with a single key should return an object with a single property"
+        );
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_bug_1621162() -> Result<()> {
+        // apparently Firefox, unlike Chrome, will not optimize the changes.
+        // See bug 1621162 for more!
+        let mut db = new_mem_db();
+        let tx = db.transaction()?;
+        let ext_id = "xyz";
+
+        set(&tx, ext_id, json!({"foo": "bar" }))?;
+
+        assert_eq!(
+            set(&tx, ext_id, json!({"foo": "bar" }))?,
+            make_changes(&[("foo", Some(json!("bar")), Some(json!("bar")))]),
+        );
+        Ok(())
+    }
+
+    #[test]
+    fn test_quota_maxitems() -> Result<()> {
+        let mut db = new_mem_db();
+        let tx = db.transaction()?;
+        let ext_id = "xyz";
+        for i in 1..SYNC_MAX_ITEMS + 1 {
+            set(
+                &tx,
+                ext_id,
+                json!({ format!("key-{}", i): format!("value-{}", i) }),
+            )?;
+        }
+        let e = set(&tx, ext_id, json!({"another": "another"})).unwrap_err();
+        match e.kind() {
+            ErrorKind::QuotaError(QuotaReason::MaxItems) => {}
+            _ => panic!("unexpected error type"),
+        };
+        Ok(())
+    }
+
+    #[test]
+    fn test_quota_bytesperitem() -> Result<()> {
+        let mut db = new_mem_db();
+        let tx = db.transaction()?;
+        let ext_id = "xyz";
+        // A string 5 bytes less than the max. This should be counted as being
+        // 3 bytes less than the max as the quotes are counted. Plus the length
+        // of the key (no quotes) means we should come in 2 bytes under.
+        let val = "x".repeat(SYNC_QUOTA_BYTES_PER_ITEM - 5);
+
+        // Key length doesn't push it over.
+        set(&tx, ext_id, json!({ "x": val }))?;
+        assert_eq!(
+            get_bytes_in_use(&tx, ext_id, json!("x"))?,
+            SYNC_QUOTA_BYTES_PER_ITEM - 2
+        );
+
+        // Key length does push it over.
+        let e = set(&tx, ext_id, json!({ "xxxx": val })).unwrap_err();
+        match e.kind() {
+            ErrorKind::QuotaError(QuotaReason::ItemBytes) => {}
+            _ => panic!("unexpected error type"),
+        };
+        Ok(())
+    }
+
+    #[test]
+    fn test_quota_bytes() -> Result<()> {
+        let mut db = new_mem_db();
+        let tx = db.transaction()?;
+        let ext_id = "xyz";
+        let val = "x".repeat(SYNC_QUOTA_BYTES + 1);
+
+        // Init an over quota db with a single key.
+        save_to_db(
+            &tx,
+            ext_id,
+            &StorageChangeOp::SetWithoutQuota(json!({ "x": val })),
+        )?;
+
+        // Adding more data fails.
+        let e = set(&tx, ext_id, json!({ "y": "newvalue" })).unwrap_err();
+        match e.kind() {
+            ErrorKind::QuotaError(QuotaReason::TotalBytes) => {}
+            _ => panic!("unexpected error type"),
+        };
+
+        // Remove data does not fails.
+        remove(&tx, ext_id, json!["x"])?;
+
+        // Restore the over quota data.
+        save_to_db(
+            &tx,
+            ext_id,
+            &StorageChangeOp::SetWithoutQuota(json!({ "y": val })),
+        )?;
+
+        // Overwrite with less data does not fail.
+        set(&tx, ext_id, json!({ "y": "lessdata" }))?;
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_get_bytes_in_use() -> Result<()> {
+        let mut db = new_mem_db();
+        let tx = db.transaction()?;
+        let ext_id = "xyz";
+
+        assert_eq!(get_bytes_in_use(&tx, ext_id, json!(null))?, 0);
+
+        set(&tx, ext_id, json!({ "a": "a" }))?; // should be 4
+        set(&tx, ext_id, json!({ "b": "bb" }))?; // should be 5
+        set(&tx, ext_id, json!({ "c": "ccc" }))?; // should be 6
+        set(&tx, ext_id, json!({ "n": 999_999 }))?; // should be 7
+
+        assert_eq!(get_bytes_in_use(&tx, ext_id, json!("x"))?, 0);
+        assert_eq!(get_bytes_in_use(&tx, ext_id, json!("a"))?, 4);
+        assert_eq!(get_bytes_in_use(&tx, ext_id, json!("b"))?, 5);
+        assert_eq!(get_bytes_in_use(&tx, ext_id, json!("c"))?, 6);
+        assert_eq!(get_bytes_in_use(&tx, ext_id, json!("n"))?, 7);
+
+        assert_eq!(get_bytes_in_use(&tx, ext_id, json!(["a"]))?, 4);
+        assert_eq!(get_bytes_in_use(&tx, ext_id, json!(["a", "x"]))?, 4);
+        assert_eq!(get_bytes_in_use(&tx, ext_id, json!(["a", "b"]))?, 9);
+        assert_eq!(get_bytes_in_use(&tx, ext_id, json!(["a", "c"]))?, 10);
+
+        assert_eq!(
+            get_bytes_in_use(&tx, ext_id, json!(["a", "b", "c", "n"]))?,
+            22
+        );
+        assert_eq!(get_bytes_in_use(&tx, ext_id, json!(null))?, 22);
+        Ok(())
+    }
+
+    #[test]
+    fn test_usage() {
+        let mut db = new_mem_db();
+        let tx = db.transaction().unwrap();
+        // '{"a":"a","b":"bb","c":"ccc","n":999999}': 39 bytes
+        set(&tx, "xyz", json!({ "a": "a" })).unwrap();
+        set(&tx, "xyz", json!({ "b": "bb" })).unwrap();
+        set(&tx, "xyz", json!({ "c": "ccc" })).unwrap();
+        set(&tx, "xyz", json!({ "n": 999_999 })).unwrap();
+
+        // '{"a":"a"}': 9 bytes
+        set(&tx, "abc", json!({ "a": "a" })).unwrap();
+
+        tx.commit().unwrap();
+
+        let usage = usage(&db).unwrap();
+        let expect = [
+            UsageInfo {
+                ext_id: "abc".to_string(),
+                num_keys: 1,
+                num_bytes: 9,
+            },
+            UsageInfo {
+                ext_id: "xyz".to_string(),
+                num_keys: 4,
+                num_bytes: 39,
+            },
+        ];
+        assert_eq!(&usage, &expect);
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/webext_storage/db.rs.html b/book/rust-docs/src/webext_storage/db.rs.html new file mode 100644 index 0000000000..ef330b0157 --- /dev/null +++ b/book/rust-docs/src/webext_storage/db.rs.html @@ -0,0 +1,603 @@ +db.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::error::*;
+use crate::schema;
+use interrupt_support::{SqlInterruptHandle, SqlInterruptScope};
+use parking_lot::Mutex;
+use rusqlite::types::{FromSql, ToSql};
+use rusqlite::Connection;
+use rusqlite::OpenFlags;
+use sql_support::open_database::open_database_with_flags;
+use sql_support::ConnExt;
+use std::ops::{Deref, DerefMut};
+use std::path::{Path, PathBuf};
+use std::sync::Arc;
+use url::Url;
+
+/// A `StorageDb` wraps a read-write SQLite connection, and handles schema
+/// migrations and recovering from database file corruption. It can be used
+/// anywhere a `rusqlite::Connection` is expected, thanks to its `Deref{Mut}`
+/// implementations.
+///
+/// We only support a single writer connection - so that's the only thing we
+/// store. It's still a bit overkill, but there's only so many yaks in a day.
+pub struct StorageDb {
+    writer: Connection,
+    interrupt_handle: Arc<SqlInterruptHandle>,
+}
+impl StorageDb {
+    /// Create a new, or fetch an already open, StorageDb backed by a file on disk.
+    pub fn new(db_path: impl AsRef<Path>) -> Result<Self> {
+        let db_path = normalize_path(db_path)?;
+        Self::new_named(db_path)
+    }
+
+    /// Create a new, or fetch an already open, memory-based StorageDb. You must
+    /// provide a name, but you are still able to have a single writer and many
+    /// reader connections to the same memory DB open.
+    #[cfg(test)]
+    pub fn new_memory(db_path: &str) -> Result<Self> {
+        let name = PathBuf::from(format!("file:{}?mode=memory&cache=shared", db_path));
+        Self::new_named(name)
+    }
+
+    fn new_named(db_path: PathBuf) -> Result<Self> {
+        // We always create the read-write connection for an initial open so
+        // we can create the schema and/or do version upgrades.
+        let flags = OpenFlags::SQLITE_OPEN_NO_MUTEX
+            | OpenFlags::SQLITE_OPEN_URI
+            | OpenFlags::SQLITE_OPEN_CREATE
+            | OpenFlags::SQLITE_OPEN_READ_WRITE;
+
+        let conn = open_database_with_flags(db_path, flags, &schema::WebExtMigrationLogin)?;
+        Ok(Self {
+            interrupt_handle: Arc::new(SqlInterruptHandle::new(&conn)),
+            writer: conn,
+        })
+    }
+
+    pub fn interrupt_handle(&self) -> Arc<SqlInterruptHandle> {
+        Arc::clone(&self.interrupt_handle)
+    }
+
+    #[allow(dead_code)]
+    pub fn begin_interrupt_scope(&self) -> Result<SqlInterruptScope> {
+        Ok(self.interrupt_handle.begin_interrupt_scope()?)
+    }
+
+    /// Closes the database connection. If there are any unfinalized prepared
+    /// statements on the connection, `close` will fail and the `StorageDb` will
+    /// remain open and the connection will be leaked - we used to return the
+    /// underlying connection so the caller can retry but (a) that's very tricky
+    /// in an Arc<Mutex<>> world and (b) we never actually took advantage of
+    /// that retry capability.
+    pub fn close(self) -> Result<()> {
+        self.writer.close().map_err(|(writer, err)| {
+            // In rusqlite 0.28.0 and earlier, if we just let `writer` drop,
+            // the close would panic on failure.
+            // Later rusqlite versions will not panic, but this behavior doesn't
+            // hurt there.
+            std::mem::forget(writer);
+            err.into()
+        })
+    }
+}
+
+impl Deref for StorageDb {
+    type Target = Connection;
+
+    fn deref(&self) -> &Self::Target {
+        &self.writer
+    }
+}
+
+impl DerefMut for StorageDb {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.writer
+    }
+}
+
+// We almost exclusively use this ThreadSafeStorageDb
+pub struct ThreadSafeStorageDb {
+    db: Mutex<StorageDb>,
+    // This "outer" interrupt_handle not protected by the mutex means
+    // consumers can interrupt us when the mutex is held - which it always will
+    // be if we are doing anything interruptable!
+    interrupt_handle: Arc<SqlInterruptHandle>,
+}
+
+impl ThreadSafeStorageDb {
+    pub fn new(db: StorageDb) -> Self {
+        Self {
+            interrupt_handle: db.interrupt_handle(),
+            db: Mutex::new(db),
+        }
+    }
+
+    pub fn interrupt_handle(&self) -> Arc<SqlInterruptHandle> {
+        Arc::clone(&self.interrupt_handle)
+    }
+
+    pub fn begin_interrupt_scope(&self) -> Result<SqlInterruptScope> {
+        Ok(self.interrupt_handle.begin_interrupt_scope()?)
+    }
+
+    pub fn into_inner(self) -> StorageDb {
+        self.db.into_inner()
+    }
+}
+
+// Deref to a Mutex<StorageDb>, which is how we will use ThreadSafeStorageDb most of the time
+impl Deref for ThreadSafeStorageDb {
+    type Target = Mutex<StorageDb>;
+
+    #[inline]
+    fn deref(&self) -> &Mutex<StorageDb> {
+        &self.db
+    }
+}
+
+// Also implement AsRef<SqlInterruptHandle> so that we can interrupt this at shutdown
+impl AsRef<SqlInterruptHandle> for ThreadSafeStorageDb {
+    fn as_ref(&self) -> &SqlInterruptHandle {
+        &self.interrupt_handle
+    }
+}
+
+pub(crate) mod sql_fns {
+    use rusqlite::{functions::Context, Result};
+    use sync_guid::Guid as SyncGuid;
+
+    #[inline(never)]
+    pub fn generate_guid(_ctx: &Context<'_>) -> Result<SyncGuid> {
+        Ok(SyncGuid::random())
+    }
+}
+
+// These should be somewhere else...
+pub fn put_meta(db: &Connection, key: &str, value: &dyn ToSql) -> Result<()> {
+    db.conn().execute_cached(
+        "REPLACE INTO meta (key, value) VALUES (:key, :value)",
+        rusqlite::named_params! { ":key": key, ":value": value },
+    )?;
+    Ok(())
+}
+
+pub fn get_meta<T: FromSql>(db: &Connection, key: &str) -> Result<Option<T>> {
+    let res = db.conn().try_query_one(
+        "SELECT value FROM meta WHERE key = :key",
+        &[(":key", &key)],
+        true,
+    )?;
+    Ok(res)
+}
+
+pub fn delete_meta(db: &Connection, key: &str) -> Result<()> {
+    db.conn()
+        .execute_cached("DELETE FROM meta WHERE key = :key", &[(":key", &key)])?;
+    Ok(())
+}
+
+// Utilities for working with paths.
+// (From places_utils - ideally these would be shared, but the use of
+// ErrorKind values makes that non-trivial.
+
+/// `Path` is basically just a `str` with no validation, and so in practice it
+/// could contain a file URL. Rusqlite takes advantage of this a bit, and says
+/// `AsRef<Path>` but really means "anything sqlite can take as an argument".
+///
+/// Swift loves using file urls (the only support it has for file manipulation
+/// is through file urls), so it's handy to support them if possible.
+fn unurl_path(p: impl AsRef<Path>) -> PathBuf {
+    p.as_ref()
+        .to_str()
+        .and_then(|s| Url::parse(s).ok())
+        .and_then(|u| {
+            if u.scheme() == "file" {
+                u.to_file_path().ok()
+            } else {
+                None
+            }
+        })
+        .unwrap_or_else(|| p.as_ref().to_owned())
+}
+
+/// If `p` is a file URL, return it, otherwise try and make it one.
+///
+/// Errors if `p` is a relative non-url path, or if it's a URL path
+/// that's isn't a `file:` URL.
+#[allow(dead_code)]
+pub fn ensure_url_path(p: impl AsRef<Path>) -> Result<Url> {
+    if let Some(u) = p.as_ref().to_str().and_then(|s| Url::parse(s).ok()) {
+        if u.scheme() == "file" {
+            Ok(u)
+        } else {
+            Err(ErrorKind::IllegalDatabasePath(p.as_ref().to_owned()).into())
+        }
+    } else {
+        let p = p.as_ref();
+        let u = Url::from_file_path(p).map_err(|_| ErrorKind::IllegalDatabasePath(p.to_owned()))?;
+        Ok(u)
+    }
+}
+
+/// As best as possible, convert `p` into an absolute path, resolving
+/// all symlinks along the way.
+///
+/// If `p` is a file url, it's converted to a path before this.
+fn normalize_path(p: impl AsRef<Path>) -> Result<PathBuf> {
+    let path = unurl_path(p);
+    if let Ok(canonical) = path.canonicalize() {
+        return Ok(canonical);
+    }
+    // It probably doesn't exist yet. This is an error, although it seems to
+    // work on some systems.
+    //
+    // We resolve this by trying to canonicalize the parent directory, and
+    // appending the requested file name onto that. If we can't canonicalize
+    // the parent, we return an error.
+    //
+    // Also, we return errors if the path ends in "..", if there is no
+    // parent directory, etc.
+    let file_name = path
+        .file_name()
+        .ok_or_else(|| ErrorKind::IllegalDatabasePath(path.clone()))?;
+
+    let parent = path
+        .parent()
+        .ok_or_else(|| ErrorKind::IllegalDatabasePath(path.clone()))?;
+
+    let mut canonical = parent.canonicalize()?;
+    canonical.push(file_name);
+    Ok(canonical)
+}
+
+// Helpers for tests
+#[cfg(test)]
+pub mod test {
+    use super::*;
+    use std::sync::atomic::{AtomicUsize, Ordering};
+
+    // A helper for our tests to get their own memory Api.
+    static ATOMIC_COUNTER: AtomicUsize = AtomicUsize::new(0);
+
+    pub fn new_mem_db() -> StorageDb {
+        let _ = env_logger::try_init();
+        let counter = ATOMIC_COUNTER.fetch_add(1, Ordering::Relaxed);
+        StorageDb::new_memory(&format!("test-api-{}", counter)).expect("should get an API")
+    }
+
+    pub fn new_mem_thread_safe_storage_db() -> Arc<ThreadSafeStorageDb> {
+        Arc::new(ThreadSafeStorageDb::new(new_mem_db()))
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::test::*;
+    use super::*;
+
+    // Sanity check that we can create a database.
+    #[test]
+    fn test_open() {
+        new_mem_db();
+        // XXX - should we check anything else? Seems a bit pointless, but if
+        // we move the meta functions away from here then it's better than
+        // nothing.
+    }
+
+    #[test]
+    fn test_meta() -> Result<()> {
+        let writer = new_mem_db();
+        assert_eq!(get_meta::<String>(&writer, "foo")?, None);
+        put_meta(&writer, "foo", &"bar".to_string())?;
+        assert_eq!(get_meta(&writer, "foo")?, Some("bar".to_string()));
+        delete_meta(&writer, "foo")?;
+        assert_eq!(get_meta::<String>(&writer, "foo")?, None);
+        Ok(())
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/webext_storage/error.rs.html b/book/rust-docs/src/webext_storage/error.rs.html new file mode 100644 index 0000000000..9fa14a388e --- /dev/null +++ b/book/rust-docs/src/webext_storage/error.rs.html @@ -0,0 +1,145 @@ +error.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use interrupt_support::Interrupted;
+
+#[derive(Debug)]
+pub enum QuotaReason {
+    TotalBytes,
+    ItemBytes,
+    MaxItems,
+}
+
+#[derive(Debug, thiserror::Error)]
+pub enum ErrorKind {
+    #[error("Quota exceeded: {0:?}")]
+    QuotaError(QuotaReason),
+
+    #[error("Error parsing JSON data: {0}")]
+    JsonError(#[from] serde_json::Error),
+
+    #[error("Error executing SQL: {0}")]
+    SqlError(#[from] rusqlite::Error),
+
+    #[error("A connection of this type is already open")]
+    ConnectionAlreadyOpen,
+
+    #[error("An invalid connection type was specified")]
+    InvalidConnectionType,
+
+    #[error("IO error: {0}")]
+    IoError(#[from] std::io::Error),
+
+    #[error("Operation interrupted")]
+    InterruptedError(#[from] Interrupted),
+
+    #[error("Tried to close connection on wrong StorageApi instance")]
+    WrongApiForClose,
+
+    // This will happen if you provide something absurd like
+    // "/" or "" as your database path. For more subtley broken paths,
+    // we'll likely return an IoError.
+    #[error("Illegal database path: {0:?}")]
+    IllegalDatabasePath(std::path::PathBuf),
+
+    #[error("UTF8 Error: {0}")]
+    Utf8Error(#[from] std::str::Utf8Error),
+
+    #[error("Error opening database: {0}")]
+    OpenDatabaseError(#[from] sql_support::open_database::Error),
+
+    // When trying to close a connection but we aren't the exclusive owner of the containing Arc<>
+    #[error("Other shared references to this connection are alive")]
+    OtherConnectionReferencesExist,
+
+    #[error("The storage database has been closed")]
+    DatabaseConnectionClosed,
+
+    #[error("Sync Error: {0}")]
+    SyncError(String),
+}
+
+error_support::define_error! {
+    ErrorKind {
+        (JsonError, serde_json::Error),
+        (SqlError, rusqlite::Error),
+        (IoError, std::io::Error),
+        (InterruptedError, Interrupted),
+        (Utf8Error, std::str::Utf8Error),
+        (OpenDatabaseError, sql_support::open_database::Error),
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/webext_storage/ffi.rs.html b/book/rust-docs/src/webext_storage/ffi.rs.html new file mode 100644 index 0000000000..da5b904388 --- /dev/null +++ b/book/rust-docs/src/webext_storage/ffi.rs.html @@ -0,0 +1,97 @@ +ffi.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/// This module contains glue for the FFI, including error codes and a
+/// `From<Error>` implementation for `ffi_support::ExternError`. These must be
+/// implemented in this crate, and not `webext_storage_ffi`, because of Rust's
+/// orphan rule for implementing traits (`webext_storage_ffi` can't implement
+/// a trait in `ffi_support` for a type in `webext_storage`).
+use ffi_support::{ErrorCode, ExternError};
+
+use crate::error::{Error, ErrorKind, QuotaReason};
+
+mod error_codes {
+    /// An unexpected error occurred which likely cannot be meaningfully handled
+    /// by the application.
+    pub const UNEXPECTED: i32 = 1;
+
+    /// The application passed an invalid JSON string for a storage key or value.
+    pub const INVALID_JSON: i32 = 2;
+
+    /// The total number of bytes stored in the database for this extension,
+    /// counting all key-value pairs serialized to JSON, exceeds the allowed limit.
+    pub const QUOTA_TOTAL_BYTES_EXCEEDED: i32 = 32;
+
+    /// A single key-value pair exceeds the allowed byte limit when serialized
+    /// to JSON.
+    pub const QUOTA_ITEM_BYTES_EXCEEDED: i32 = 32 + 1;
+
+    /// The total number of key-value pairs stored for this extension exceeded the
+    /// allowed limit.
+    pub const QUOTA_MAX_ITEMS_EXCEEDED: i32 = 32 + 2;
+}
+
+impl From<Error> for ExternError {
+    fn from(err: Error) -> ExternError {
+        let code = ErrorCode::new(match err.kind() {
+            ErrorKind::JsonError(_) => error_codes::INVALID_JSON,
+            ErrorKind::QuotaError(QuotaReason::TotalBytes) => {
+                error_codes::QUOTA_TOTAL_BYTES_EXCEEDED
+            }
+            ErrorKind::QuotaError(QuotaReason::ItemBytes) => error_codes::QUOTA_ITEM_BYTES_EXCEEDED,
+            ErrorKind::QuotaError(QuotaReason::MaxItems) => error_codes::QUOTA_MAX_ITEMS_EXCEEDED,
+            _ => error_codes::UNEXPECTED,
+        });
+        ExternError::new_error(code, err.to_string())
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/webext_storage/lib.rs.html b/book/rust-docs/src/webext_storage/lib.rs.html new file mode 100644 index 0000000000..68cc815d20 --- /dev/null +++ b/book/rust-docs/src/webext_storage/lib.rs.html @@ -0,0 +1,53 @@ +lib.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#![allow(unknown_lints)]
+#![warn(rust_2018_idioms)]
+
+mod api;
+mod db;
+pub mod error;
+mod ffi;
+mod migration;
+mod schema;
+pub mod store;
+mod sync;
+
+pub use migration::MigrationInfo;
+
+// We publish some constants from non-public modules.
+pub use sync::STORAGE_VERSION;
+
+pub use api::SYNC_MAX_ITEMS;
+pub use api::SYNC_QUOTA_BYTES;
+pub use api::SYNC_QUOTA_BYTES_PER_ITEM;
+
+pub use api::UsageInfo;
+
\ No newline at end of file diff --git a/book/rust-docs/src/webext_storage/migration.rs.html b/book/rust-docs/src/webext_storage/migration.rs.html new file mode 100644 index 0000000000..2dc7c5e313 --- /dev/null +++ b/book/rust-docs/src/webext_storage/migration.rs.html @@ -0,0 +1,909 @@ +migration.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::error::*;
+use rusqlite::{named_params, Connection, OpenFlags, Transaction};
+use serde_json::{Map, Value};
+use sql_support::ConnExt;
+use std::collections::HashSet;
+use std::path::Path;
+
+// Simple migration from the "old" kinto-with-sqlite-backing implementation
+// to ours.
+// Could almost be trivially done in JS using the regular public API if not
+// for:
+// * We don't want to enforce the same quotas when migrating.
+// * We'd rather do the entire migration in a single transaction for perf
+//   reasons.
+
+// The sqlite database we migrate from has a very simple structure:
+// * table collection_data with columns collection_name, record_id and record
+// * `collection_name` is a string of form "default/{extension_id}"
+// * `record_id` is `key-{key}`
+// * `record` is a string with json, of form: {
+//     id: {the record id repeated},
+//     key: {the key},
+//     data: {the actual data},
+//     _status: {sync status},
+//     last_modified: {timestamp},
+//  }
+// So the info we need is stored somewhat redundantly.
+// Further:
+// * There's a special collection_name "default/storage-sync-crypto" that
+//   we don't want to migrate. Its record_id is 'keys' and its json has no
+//   `data`
+
+// Note we don't enforce a quota - we migrate everything - even if this means
+// it's too big for the server to store. This is a policy decision - it's better
+// to not lose data than to try and work out what data can be disposed of, as
+// the addon has the ability to determine this.
+
+// Our error strategy is "ignore read errors, propagate write errors" under the
+// assumption that the former tends to mean a damaged DB or file-system and is
+// unlikely to work if we try later (eg, replacing the disk isn't likely to
+// uncorrupt the DB), where the latter is likely to be disk-space or file-system
+// error, but retry might work (eg, replacing the disk then trying again might
+// make the writes work)
+
+// The struct we read from the DB.
+struct LegacyRow {
+    col_name: String, // collection_name column
+    record: String,   // record column
+}
+
+impl LegacyRow {
+    // Parse the 2 columns from the DB into the data we need to insert into
+    // our target database.
+    fn parse(&self) -> Option<Parsed<'_>> {
+        if self.col_name.len() < 8 {
+            log::trace!("collection_name of '{}' is too short", self.col_name);
+            return None;
+        }
+        if &self.col_name[..8] != "default/" {
+            log::trace!("collection_name of '{}' isn't ours", self.col_name);
+            return None;
+        }
+        let ext_id = &self.col_name[8..];
+        let mut record_map = match serde_json::from_str(&self.record) {
+            Ok(Value::Object(m)) => m,
+            Ok(o) => {
+                log::info!("skipping non-json-object 'record' column");
+                log::trace!("record value is json, but not an object: {}", o);
+                return None;
+            }
+            Err(e) => {
+                log::info!("skipping non-json 'record' column");
+                log::trace!("record value isn't json: {}", e);
+                return None;
+            }
+        };
+
+        let key = match record_map.remove("key") {
+            Some(Value::String(s)) if !s.is_empty() => s,
+            Some(o) => {
+                log::trace!("key is json but not a string: {}", o);
+                return None;
+            }
+            _ => {
+                log::trace!("key doesn't exist in the map");
+                return None;
+            }
+        };
+        let data = match record_map.remove("data") {
+            Some(d) => d,
+            _ => {
+                log::trace!("data doesn't exist in the map");
+                return None;
+            }
+        };
+        Some(Parsed { ext_id, key, data })
+    }
+}
+
+// The info we parse from the raw DB strings.
+struct Parsed<'a> {
+    ext_id: &'a str,
+    key: String,
+    data: serde_json::Value,
+}
+
+pub fn migrate(tx: &Transaction<'_>, filename: &Path) -> Result<MigrationInfo> {
+    // We do the grouping manually, collecting string values as we go.
+    let mut last_ext_id = "".to_string();
+    let mut curr_values: Vec<(String, serde_json::Value)> = Vec::new();
+    let (rows, mut mi) = read_rows(filename);
+    for row in rows {
+        log::trace!("processing '{}' - '{}'", row.col_name, row.record);
+        let parsed = match row.parse() {
+            Some(p) => p,
+            None => continue,
+        };
+        // Do our "grouping"
+        if parsed.ext_id != last_ext_id {
+            if !last_ext_id.is_empty() && !curr_values.is_empty() {
+                // a different extension id - write what we have to the DB.
+                let entries = do_insert(tx, &last_ext_id, curr_values)?;
+                mi.extensions_successful += 1;
+                mi.entries_successful += entries;
+            }
+            last_ext_id = parsed.ext_id.to_string();
+            curr_values = Vec::new();
+        }
+        // no 'else' here - must also enter this block on ext_id change.
+        if parsed.ext_id == last_ext_id {
+            curr_values.push((parsed.key.to_string(), parsed.data));
+            log::trace!(
+                "extension {} now has {} keys",
+                parsed.ext_id,
+                curr_values.len()
+            );
+        }
+    }
+    // and the last one
+    if !last_ext_id.is_empty() && !curr_values.is_empty() {
+        // a different extension id - write what we have to the DB.
+        let entries = do_insert(tx, &last_ext_id, curr_values)?;
+        mi.extensions_successful += 1;
+        mi.entries_successful += entries;
+    }
+    log::info!("migrated {} extensions: {:?}", mi.extensions_successful, mi);
+    Ok(mi)
+}
+
+fn read_rows(filename: &Path) -> (Vec<LegacyRow>, MigrationInfo) {
+    let flags = OpenFlags::SQLITE_OPEN_NO_MUTEX | OpenFlags::SQLITE_OPEN_READ_ONLY;
+    let src_conn = match Connection::open_with_flags(filename, flags) {
+        Ok(conn) => conn,
+        Err(e) => {
+            log::warn!("Failed to open the source DB: {}", e);
+            return (Vec::new(), MigrationInfo::open_failure());
+        }
+    };
+    // Failure to prepare the statement probably just means the source DB is
+    // damaged.
+    let mut stmt = match src_conn.prepare(
+        "SELECT collection_name, record FROM collection_data
+         WHERE collection_name != 'default/storage-sync-crypto'
+         ORDER BY collection_name",
+    ) {
+        Ok(stmt) => stmt,
+        Err(e) => {
+            log::warn!("Failed to prepare the statement: {}", e);
+            return (Vec::new(), MigrationInfo::open_failure());
+        }
+    };
+    let rows = match stmt.query_and_then([], |row| -> Result<LegacyRow> {
+        Ok(LegacyRow {
+            col_name: row.get(0)?,
+            record: row.get(1)?,
+        })
+    }) {
+        Ok(r) => r,
+        Err(e) => {
+            log::warn!("Failed to read any rows from the source DB: {}", e);
+            return (Vec::new(), MigrationInfo::open_failure());
+        }
+    };
+    let all_rows: Vec<Result<LegacyRow>> = rows.collect();
+    let entries = all_rows.len();
+    let successful_rows: Vec<LegacyRow> = all_rows.into_iter().filter_map(Result::ok).collect();
+    let distinct_extensions: HashSet<_> = successful_rows.iter().map(|c| &c.col_name).collect();
+
+    let mi = MigrationInfo {
+        entries,
+        extensions: distinct_extensions.len(),
+        // Populated later.
+        extensions_successful: 0,
+        entries_successful: 0,
+        open_failure: false,
+    };
+
+    (successful_rows, mi)
+}
+
+/// Insert the extension and values. If there are multiple values with the same
+/// key (which shouldn't be possible but who knows, database corruption causes
+/// strange things), chooses an arbitrary one. Returns the number of entries
+/// inserted, which could be different from `vals.len()` if multiple entries in
+/// `vals` have the same key.
+fn do_insert(tx: &Transaction<'_>, ext_id: &str, vals: Vec<(String, Value)>) -> Result<usize> {
+    let mut map = Map::with_capacity(vals.len());
+    for (key, val) in vals {
+        map.insert(key, val);
+    }
+    let num_entries = map.len();
+    tx.execute_cached(
+        "INSERT OR REPLACE INTO storage_sync_data(ext_id, data, sync_change_counter)
+         VALUES (:ext_id, :data, 1)",
+        rusqlite::named_params! {
+            ":ext_id": &ext_id,
+            ":data": &Value::Object(map),
+        },
+    )?;
+    Ok(num_entries)
+}
+
+#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
+pub struct MigrationInfo {
+    /// The number of entries (rows in the original table) we attempted to
+    /// migrate. Zero if there was some error in computing this number.
+    ///
+    /// Note that for the original table, a single row stores a single
+    /// preference for one extension. That is, if you view the set of
+    /// preferences for a given extension as a HashMap (as we do), it would be a
+    /// single entry/key-value-pair in the map.
+    pub entries: usize,
+    /// The number of records we successfully migrated (equal to `entries` for
+    /// entirely successful migrations).
+    pub entries_successful: usize,
+    /// The number of extensions (distinct extension ids) in the original
+    /// table.
+    pub extensions: usize,
+    /// The number of extensions we successfully migrated
+    pub extensions_successful: usize,
+    /// True iff we failed to open the source DB at all.
+    pub open_failure: bool,
+}
+
+impl MigrationInfo {
+    /// Returns a MigrationInfo indicating that we failed to read any rows due
+    /// to some error case (e.g. the database open failed, or some other very
+    /// early read error).
+    fn open_failure() -> Self {
+        Self {
+            open_failure: true,
+            ..Self::default()
+        }
+    }
+
+    const META_KEY: &'static str = "migration_info";
+
+    /// Store `self` in the provided database under `Self::META_KEY`.
+    pub(crate) fn store(&self, conn: &Connection) -> Result<()> {
+        let json = serde_json::to_string(self)?;
+        conn.execute(
+            "INSERT OR REPLACE INTO meta(key, value) VALUES (:k, :v)",
+            named_params! {
+                ":k": Self::META_KEY,
+                ":v": &json
+            },
+        )?;
+        Ok(())
+    }
+
+    /// Get the MigrationInfo stored under `Self::META_KEY` (if any) out of the
+    /// DB, and delete it.
+    pub(crate) fn take(tx: &Transaction<'_>) -> Result<Option<Self>> {
+        let s = tx.try_query_one::<String, _>(
+            "SELECT value FROM meta WHERE key = :k",
+            named_params! {
+                ":k": Self::META_KEY,
+            },
+            false,
+        )?;
+        tx.execute(
+            "DELETE FROM meta WHERE key = :k",
+            named_params! {
+                ":k": Self::META_KEY,
+            },
+        )?;
+        if let Some(s) = s {
+            match serde_json::from_str(&s) {
+                Ok(v) => Ok(Some(v)),
+                Err(e) => {
+                    // Force test failure, but just log an error otherwise so that
+                    // we commit the transaction that wil.
+                    debug_assert!(false, "Failed to read migration JSON: {:?}", e);
+                    error_support::report_error!(
+                        "webext-storage-migration-json",
+                        "Failed to read migration JSON: {}",
+                        e
+                    );
+                    Ok(None)
+                }
+            }
+        } else {
+            Ok(None)
+        }
+    }
+}
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::api;
+    use crate::db::{test::new_mem_db, StorageDb};
+    use serde_json::json;
+    use tempfile::tempdir;
+
+    fn init_source_db(path: impl AsRef<Path>, f: impl FnOnce(&Connection)) {
+        let flags = OpenFlags::SQLITE_OPEN_NO_MUTEX
+            | OpenFlags::SQLITE_OPEN_CREATE
+            | OpenFlags::SQLITE_OPEN_READ_WRITE;
+        let mut conn = Connection::open_with_flags(path, flags).expect("open should work");
+        let tx = conn.transaction().expect("should be able to get a tx");
+        tx.execute_batch(
+            "CREATE TABLE collection_data (
+                collection_name TEXT,
+                record_id TEXT,
+                record TEXT
+            );",
+        )
+        .expect("create should work");
+        f(&tx);
+        tx.commit().expect("should commit");
+        conn.close().expect("close should work");
+    }
+
+    // Create a test database, populate it via the callback, migrate it, and
+    // return a connection to the new, migrated DB for further checking.
+    fn do_migrate<F>(expect_mi: MigrationInfo, f: F) -> StorageDb
+    where
+        F: FnOnce(&Connection),
+    {
+        let tmpdir = tempdir().unwrap();
+        let path = tmpdir.path().join("source.db");
+        init_source_db(path, f);
+
+        // now migrate
+        let mut db = new_mem_db();
+        let tx = db.transaction().expect("tx should work");
+
+        let mi = migrate(&tx, &tmpdir.path().join("source.db")).expect("migrate should work");
+        tx.commit().expect("should work");
+        assert_eq!(mi, expect_mi);
+        db
+    }
+
+    fn assert_has(c: &Connection, ext_id: &str, expect: Value) {
+        assert_eq!(
+            api::get(c, ext_id, json!(null)).expect("should get"),
+            expect
+        );
+    }
+
+    const HAPPY_PATH_SQL: &str = r#"
+        INSERT INTO collection_data(collection_name, record)
+        VALUES
+            ('default/{e7fefcf3-b39c-4f17-5215-ebfe120a7031}', '{"id":"key-userWelcomed","key":"userWelcomed","data":1570659224457,"_status":"synced","last_modified":1579755940527}'),
+            ('default/{e7fefcf3-b39c-4f17-5215-ebfe120a7031}', '{"id":"key-isWho","key":"isWho","data":"4ec8109f","_status":"synced","last_modified":1579755940497}'),
+            ('default/storage-sync-crypto', '{"id":"keys","keys":{"default":["rQ=","lR="],"collections":{"extension@redux.devtools":["Bd=","ju="]}}}'),
+            ('default/https-everywhere@eff.org', '{"id":"key-userRules","key":"userRules","data":[],"_status":"synced","last_modified":1570079920045}'),
+            ('default/https-everywhere@eff.org', '{"id":"key-ruleActiveStates","key":"ruleActiveStates","data":{},"_status":"synced","last_modified":1570079919993}'),
+            ('default/https-everywhere@eff.org', '{"id":"key-migration_5F_version","key":"migration_version","data":2,"_status":"synced","last_modified":1570079919966}')
+    "#;
+    const HAPPY_PATH_MIGRATION_INFO: MigrationInfo = MigrationInfo {
+        entries: 5,
+        entries_successful: 5,
+        extensions: 2,
+        extensions_successful: 2,
+        open_failure: false,
+    };
+
+    #[allow(clippy::unreadable_literal)]
+    #[test]
+    fn test_happy_paths() {
+        // some real data.
+        let conn = do_migrate(HAPPY_PATH_MIGRATION_INFO, |c| {
+            c.execute_batch(HAPPY_PATH_SQL).expect("should populate")
+        });
+
+        assert_has(
+            &conn,
+            "{e7fefcf3-b39c-4f17-5215-ebfe120a7031}",
+            json!({"userWelcomed": 1570659224457u64, "isWho": "4ec8109f"}),
+        );
+        assert_has(
+            &conn,
+            "https-everywhere@eff.org",
+            json!({"userRules": [], "ruleActiveStates": {}, "migration_version": 2}),
+        );
+    }
+
+    #[test]
+    fn test_sad_paths() {
+        do_migrate(
+            MigrationInfo {
+                entries: 10,
+                entries_successful: 0,
+                extensions: 6,
+                extensions_successful: 0,
+                open_failure: false,
+            },
+            |c| {
+                c.execute_batch(
+                    r#"INSERT INTO collection_data(collection_name, record)
+                    VALUES
+                    ('default/test', '{"key":2,"data":1}'), -- key not a string
+                    ('default/test', '{"key":"","data":1}'), -- key empty string
+                    ('default/test', '{"xey":"k","data":1}'), -- key missing
+                    ('default/test', '{"key":"k","xata":1}'), -- data missing
+                    ('default/test', '{"key":"k","data":1'), -- invalid json
+                    ('xx/test', '{"key":"k","data":1}'), -- bad key format
+                    ('default', '{"key":"k","data":1}'), -- bad key format 2
+                    ('default/', '{"key":"k","data":1}'), -- bad key format 3
+                    ('defaultx/test', '{"key":"k","data":1}'), -- bad key format 4
+                    ('', '') -- empty strings
+                    "#,
+                )
+                .expect("should populate");
+            },
+        );
+    }
+
+    #[test]
+    fn test_migration_info_storage() {
+        let tmpdir = tempdir().unwrap();
+        let path = tmpdir.path().join("source.db");
+        init_source_db(&path, |c| {
+            c.execute_batch(HAPPY_PATH_SQL).expect("should populate")
+        });
+
+        // now migrate
+        let db = crate::store::test::new_mem_store();
+        db.migrate(&path).expect("migration should work");
+        let mi = db
+            .take_migration_info()
+            .expect("take failed with info present");
+        assert_eq!(mi, Some(HAPPY_PATH_MIGRATION_INFO));
+        let mi2 = db
+            .take_migration_info()
+            .expect("take failed with info missing");
+        assert_eq!(mi2, None);
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/webext_storage/schema.rs.html b/book/rust-docs/src/webext_storage/schema.rs.html new file mode 100644 index 0000000000..7e0e1cff6d --- /dev/null +++ b/book/rust-docs/src/webext_storage/schema.rs.html @@ -0,0 +1,355 @@ +schema.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::db::sql_fns;
+use crate::error::Result;
+use rusqlite::{Connection, Transaction};
+use sql_support::open_database::{
+    ConnectionInitializer as MigrationLogic, Error as MigrationError, Result as MigrationResult,
+};
+
+const CREATE_SCHEMA_SQL: &str = include_str!("../sql/create_schema.sql");
+const CREATE_SYNC_TEMP_TABLES_SQL: &str = include_str!("../sql/create_sync_temp_tables.sql");
+
+pub struct WebExtMigrationLogin;
+
+impl MigrationLogic for WebExtMigrationLogin {
+    const NAME: &'static str = "webext storage db";
+    const END_VERSION: u32 = 2;
+
+    fn prepare(&self, conn: &Connection, _db_empty: bool) -> MigrationResult<()> {
+        let initial_pragmas = "
+            -- We don't care about temp tables being persisted to disk.
+            PRAGMA temp_store = 2;
+            -- we unconditionally want write-ahead-logging mode
+            PRAGMA journal_mode=WAL;
+            -- foreign keys seem worth enforcing!
+            PRAGMA foreign_keys = ON;
+        ";
+        conn.execute_batch(initial_pragmas)?;
+        define_functions(conn)?;
+        conn.set_prepared_statement_cache_capacity(128);
+        Ok(())
+    }
+
+    fn init(&self, db: &Transaction<'_>) -> MigrationResult<()> {
+        log::debug!("Creating schema");
+        db.execute_batch(CREATE_SCHEMA_SQL)?;
+        Ok(())
+    }
+
+    fn upgrade_from(&self, db: &Transaction<'_>, version: u32) -> MigrationResult<()> {
+        match version {
+            1 => upgrade_from_1(db),
+            _ => Err(MigrationError::IncompatibleVersion(version)),
+        }
+    }
+}
+
+fn define_functions(c: &Connection) -> MigrationResult<()> {
+    use rusqlite::functions::FunctionFlags;
+    c.create_scalar_function(
+        "generate_guid",
+        0,
+        FunctionFlags::SQLITE_UTF8,
+        sql_fns::generate_guid,
+    )?;
+    Ok(())
+}
+
+fn upgrade_from_1(db: &Connection) -> MigrationResult<()> {
+    // We changed a not null constraint
+    db.execute_batch("ALTER TABLE storage_sync_mirror RENAME TO old_mirror;")?;
+    // just re-run the full schema commands to recreate the able.
+    db.execute_batch(CREATE_SCHEMA_SQL)?;
+    db.execute_batch(
+        "INSERT OR IGNORE INTO storage_sync_mirror(guid, ext_id, data)
+         SELECT guid, ext_id, data FROM old_mirror;",
+    )?;
+    db.execute_batch("DROP TABLE old_mirror;")?;
+    db.execute_batch("PRAGMA user_version = 2;")?;
+    Ok(())
+}
+
+// Note that we expect this to be called before and after a sync - before to
+// ensure we are syncing with a clean state, after to be good memory citizens
+// given the temp tables are in memory.
+pub fn create_empty_sync_temp_tables(db: &Connection) -> Result<()> {
+    log::debug!("Initializing sync temp tables");
+    db.execute_batch(CREATE_SYNC_TEMP_TABLES_SQL)?;
+    Ok(())
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::db::test::new_mem_db;
+    use rusqlite::Error;
+    use sql_support::open_database::test_utils::MigratedDatabaseFile;
+    use sql_support::ConnExt;
+
+    const CREATE_SCHEMA_V1_SQL: &str = include_str!("../sql/tests/create_schema_v1.sql");
+
+    #[test]
+    fn test_create_schema_twice() {
+        let db = new_mem_db();
+        db.execute_batch(CREATE_SCHEMA_SQL)
+            .expect("should allow running twice");
+    }
+
+    #[test]
+    fn test_create_empty_sync_temp_tables_twice() {
+        let db = new_mem_db();
+        create_empty_sync_temp_tables(&db).expect("should work first time");
+        // insert something into our new temp table and check it's there.
+        db.execute_batch(
+            "INSERT INTO temp.storage_sync_staging
+                            (guid, ext_id) VALUES
+                            ('guid', 'ext_id');",
+        )
+        .expect("should work once");
+        let count = db
+            .query_row_and_then(
+                "SELECT COUNT(*) FROM temp.storage_sync_staging;",
+                [],
+                |row| row.get::<_, u32>(0),
+            )
+            .expect("query should work");
+        assert_eq!(count, 1, "should be one row");
+
+        // re-execute
+        create_empty_sync_temp_tables(&db).expect("should second first time");
+        // and it should have deleted existing data.
+        let count = db
+            .query_row_and_then(
+                "SELECT COUNT(*) FROM temp.storage_sync_staging;",
+                [],
+                |row| row.get::<_, u32>(0),
+            )
+            .expect("query should work");
+        assert_eq!(count, 0, "should be no rows");
+    }
+
+    #[test]
+    fn test_all_upgrades() -> Result<()> {
+        let db_file = MigratedDatabaseFile::new(WebExtMigrationLogin, CREATE_SCHEMA_V1_SQL);
+        db_file.run_all_upgrades();
+        let db = db_file.open();
+
+        let get_id_data = |guid: &str| -> Result<(Option<String>, Option<String>)> {
+            let (ext_id, data) = db
+                .try_query_row::<_, Error, _, _>(
+                    "SELECT ext_id, data FROM storage_sync_mirror WHERE guid = :guid",
+                    &[(":guid", &guid.to_string())],
+                    |row| Ok((row.get(0)?, row.get(1)?)),
+                    true,
+                )?
+                .expect("row should exist.");
+            Ok((ext_id, data))
+        };
+        assert_eq!(
+            get_id_data("guid-1")?,
+            (Some("ext-id-1".to_string()), Some("data-1".to_string()))
+        );
+        assert_eq!(
+            get_id_data("guid-2")?,
+            (Some("ext-id-2".to_string()), Some("data-2".to_string()))
+        );
+        Ok(())
+    }
+
+    #[test]
+    fn test_upgrade_2() -> Result<()> {
+        let _ = env_logger::try_init();
+
+        let db_file = MigratedDatabaseFile::new(WebExtMigrationLogin, CREATE_SCHEMA_V1_SQL);
+        db_file.upgrade_to(2);
+        let db = db_file.open();
+
+        // Should be able to insert a new with a NULL ext_id
+        db.execute_batch(
+            "INSERT INTO storage_sync_mirror(guid, ext_id, data)
+             VALUES ('guid-3', NULL, NULL);",
+        )?;
+        Ok(())
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/webext_storage/store.rs.html b/book/rust-docs/src/webext_storage/store.rs.html new file mode 100644 index 0000000000..149cbdbdee --- /dev/null +++ b/book/rust-docs/src/webext_storage/store.rs.html @@ -0,0 +1,437 @@ +store.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::api::{self, StorageChanges};
+use crate::db::{StorageDb, ThreadSafeStorageDb};
+use crate::error::*;
+use crate::migration::{migrate, MigrationInfo};
+use crate::sync;
+use std::path::Path;
+use std::sync::Arc;
+
+use interrupt_support::SqlInterruptHandle;
+use serde_json::Value as JsonValue;
+
+/// A store is used to access `storage.sync` data. It manages an underlying
+/// database connection, and exposes methods for reading and writing storage
+/// items scoped to an extension ID. Each item is a JSON object, with one or
+/// more string keys, and values of any type that can serialize to JSON.
+///
+/// An application should create only one store, and manage the instance as a
+/// singleton. While this isn't enforced, if you make multiple stores pointing
+/// to the same database file, you are going to have a bad time: each store will
+/// create its own database connection, using up extra memory and CPU cycles,
+/// and causing write contention. For this reason, you should only call
+/// `Store::new()` (or `webext_store_new()`, from the FFI) once.
+///
+/// Note that our Db implementation is behind an Arc<> because we share that
+/// connection with our sync engines - ie, these engines also hold an Arc<>
+/// around the same object.
+pub struct Store {
+    db: Arc<ThreadSafeStorageDb>,
+}
+
+impl Store {
+    /// Creates a store backed by a database at `db_path`. The path can be a
+    /// file path or `file:` URI.
+    pub fn new(db_path: impl AsRef<Path>) -> Result<Self> {
+        let db = StorageDb::new(db_path)?;
+        Ok(Self {
+            db: Arc::new(ThreadSafeStorageDb::new(db)),
+        })
+    }
+
+    /// Creates a store backed by an in-memory database.
+    #[cfg(test)]
+    pub fn new_memory(db_path: &str) -> Result<Self> {
+        let db = StorageDb::new_memory(db_path)?;
+        Ok(Self {
+            db: Arc::new(ThreadSafeStorageDb::new(db)),
+        })
+    }
+
+    /// Returns an interrupt handle for this store.
+    pub fn interrupt_handle(&self) -> Arc<SqlInterruptHandle> {
+        self.db.interrupt_handle()
+    }
+
+    /// Sets one or more JSON key-value pairs for an extension ID. Returns a
+    /// list of changes, with existing and new values for each key in `val`.
+    pub fn set(&self, ext_id: &str, val: JsonValue) -> Result<StorageChanges> {
+        let db = self.db.lock();
+        let tx = db.unchecked_transaction()?;
+        let result = api::set(&tx, ext_id, val)?;
+        tx.commit()?;
+        Ok(result)
+    }
+
+    /// Returns information about per-extension usage
+    pub fn usage(&self) -> Result<Vec<crate::UsageInfo>> {
+        let db = self.db.lock();
+        api::usage(&db)
+    }
+
+    /// Returns the values for one or more keys `keys` can be:
+    ///
+    /// - `null`, in which case all key-value pairs for the extension are
+    ///   returned, or an empty object if the extension doesn't have any
+    ///   stored data.
+    /// - A single string key, in which case an object with only that key
+    ///   and its value is returned, or an empty object if the key doesn't
+    //    exist.
+    /// - An array of string keys, in which case an object with only those
+    ///   keys and their values is returned. Any keys that don't exist will be
+    ///   omitted.
+    /// - An object where the property names are keys, and each value is the
+    ///   default value to return if the key doesn't exist.
+    ///
+    /// This method always returns an object (that is, a
+    /// `serde_json::Value::Object`).
+    pub fn get(&self, ext_id: &str, keys: JsonValue) -> Result<JsonValue> {
+        // Don't care about transactions here.
+        let db = self.db.lock();
+        api::get(&db, ext_id, keys)
+    }
+
+    /// Deletes the values for one or more keys. As with `get`, `keys` can be
+    /// either a single string key, or an array of string keys. Returns a list
+    /// of changes, where each change contains the old value for each deleted
+    /// key.
+    pub fn remove(&self, ext_id: &str, keys: JsonValue) -> Result<StorageChanges> {
+        let db = self.db.lock();
+        let tx = db.unchecked_transaction()?;
+        let result = api::remove(&tx, ext_id, keys)?;
+        tx.commit()?;
+        Ok(result)
+    }
+
+    /// Deletes all key-value pairs for the extension. As with `remove`, returns
+    /// a list of changes, where each change contains the old value for each
+    /// deleted key.
+    pub fn clear(&self, ext_id: &str) -> Result<StorageChanges> {
+        let db = self.db.lock();
+        let tx = db.unchecked_transaction()?;
+        let result = api::clear(&tx, ext_id)?;
+        tx.commit()?;
+        Ok(result)
+    }
+
+    /// Returns the bytes in use for the specified items (which can be null,
+    /// a string, or an array)
+    pub fn get_bytes_in_use(&self, ext_id: &str, keys: JsonValue) -> Result<usize> {
+        let db = self.db.lock();
+        api::get_bytes_in_use(&db, ext_id, keys)
+    }
+
+    /// Returns a bridged sync engine for Desktop for this store.
+    pub fn bridged_engine(&self) -> sync::BridgedEngine {
+        sync::BridgedEngine::new(&self.db)
+    }
+
+    /// Closes the store and its database connection. See the docs for
+    /// `StorageDb::close` for more details on when this can fail.
+    pub fn close(self) -> Result<()> {
+        // Even though this consumes `self`, the fact we use an Arc<> means
+        // we can't guarantee we can actually consume the inner DB - so do
+        // the best we can.
+        let shared: ThreadSafeStorageDb = match Arc::try_unwrap(self.db) {
+            Ok(shared) => shared,
+            _ => {
+                // The only way this is possible is if the sync engine has an operation
+                // running - but that shouldn't be possible in practice because desktop
+                // uses a single "task queue" such that the close operation can't possibly
+                // be running concurrently with any sync or storage tasks.
+
+                // If this *could* get hit, rusqlite will attempt to close the DB connection
+                // as it is dropped, and if that close fails, then rusqlite 0.28.0 and earlier
+                // would panic - but even that only happens if prepared statements are
+                // not finalized, which ruqlite also does.
+
+                // tl;dr - this should be impossible. If it was possible, rusqlite might panic,
+                // but we've never seen it panic in practice other places we don't close
+                // connections, and the next rusqlite version will not panic anyway.
+                // So this-is-fine.jpg
+                log::warn!("Attempting to close a store while other DB references exist.");
+                return Err(ErrorKind::OtherConnectionReferencesExist.into());
+            }
+        };
+        // consume the mutex and get back the inner.
+        let db = shared.into_inner();
+        db.close()
+    }
+
+    /// Gets the changes which the current sync applied. Should be used
+    /// immediately after the bridged engine is told to apply incoming changes,
+    /// and can be used to notify observers of the StorageArea of the changes
+    /// that were applied.
+    /// The result is a Vec of already JSON stringified changes.
+    pub fn get_synced_changes(&self) -> Result<Vec<sync::SyncedExtensionChange>> {
+        let db = self.db.lock();
+        sync::get_synced_changes(&db)
+    }
+
+    /// Migrates data from a database in the format of the "old" kinto
+    /// implementation. Information about how the migration went is stored in
+    /// the database, and can be read using `Self::take_migration_info`.
+    ///
+    /// Note that `filename` isn't normalized or canonicalized.
+    pub fn migrate(&self, filename: impl AsRef<Path>) -> Result<()> {
+        let db = self.db.lock();
+        let tx = db.unchecked_transaction()?;
+        let result = migrate(&tx, filename.as_ref())?;
+        tx.commit()?;
+        // Failing to store this information should not cause migration failure.
+        if let Err(e) = result.store(&db) {
+            debug_assert!(false, "Migration error: {:?}", e);
+            log::warn!("Failed to record migration telmetry: {}", e);
+        }
+        Ok(())
+    }
+
+    /// Read-and-delete (e.g. `take` in rust parlance, see Option::take)
+    /// operation for any MigrationInfo stored in this database.
+    pub fn take_migration_info(&self) -> Result<Option<MigrationInfo>> {
+        let db = self.db.lock();
+        let tx = db.unchecked_transaction()?;
+        let result = MigrationInfo::take(&tx)?;
+        tx.commit()?;
+        Ok(result)
+    }
+}
+
+#[cfg(test)]
+pub mod test {
+    use super::*;
+    #[test]
+    fn test_send() {
+        fn ensure_send<T: Send>() {}
+        // Compile will fail if not send.
+        ensure_send::<Store>();
+    }
+
+    pub fn new_mem_store() -> Store {
+        Store {
+            db: Arc::new(ThreadSafeStorageDb::new(crate::db::test::new_mem_db())),
+        }
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/webext_storage/sync/bridge.rs.html b/book/rust-docs/src/webext_storage/sync/bridge.rs.html new file mode 100644 index 0000000000..a5de36cba0 --- /dev/null +++ b/book/rust-docs/src/webext_storage/sync/bridge.rs.html @@ -0,0 +1,777 @@ +bridge.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use anyhow::Result;
+use rusqlite::Transaction;
+use std::sync::{Arc, Weak};
+use sync15::bso::IncomingBso;
+use sync15::engine::ApplyResults;
+use sync_guid::Guid as SyncGuid;
+
+use crate::db::{delete_meta, get_meta, put_meta, ThreadSafeStorageDb};
+use crate::schema;
+use crate::sync::incoming::{apply_actions, get_incoming, plan_incoming, stage_incoming};
+use crate::sync::outgoing::{get_outgoing, record_uploaded, stage_outgoing};
+
+const LAST_SYNC_META_KEY: &str = "last_sync_time";
+const SYNC_ID_META_KEY: &str = "sync_id";
+
+/// A bridged engine implements all the methods needed to make the
+/// `storage.sync` store work with Desktop's Sync implementation.
+/// Conceptually, it's similar to `sync15::Store`, which we
+/// should eventually rename and unify with this trait (#2841).
+///
+/// Unlike most of our other implementation which hold a strong reference
+/// to the store, this engine keeps a weak reference in an attempt to keep
+/// the desktop semantics as close as possible to what they were when the
+/// engines all took lifetime params to ensure they don't outlive the store.
+pub struct BridgedEngine {
+    db: Weak<ThreadSafeStorageDb>,
+}
+
+impl BridgedEngine {
+    /// Creates a bridged engine for syncing.
+    pub fn new(db: &Arc<ThreadSafeStorageDb>) -> Self {
+        BridgedEngine {
+            db: Arc::downgrade(db),
+        }
+    }
+
+    fn do_reset(&self, tx: &Transaction<'_>) -> Result<()> {
+        tx.execute_batch(
+            "DELETE FROM storage_sync_mirror;
+             UPDATE storage_sync_data SET sync_change_counter = 1;",
+        )?;
+        delete_meta(tx, LAST_SYNC_META_KEY)?;
+        Ok(())
+    }
+
+    fn thread_safe_storage_db(&self) -> Result<Arc<ThreadSafeStorageDb>> {
+        self.db
+            .upgrade()
+            .ok_or_else(|| crate::error::ErrorKind::DatabaseConnectionClosed.into())
+    }
+}
+
+impl sync15::engine::BridgedEngine for BridgedEngine {
+    fn last_sync(&self) -> Result<i64> {
+        let shared_db = self.thread_safe_storage_db()?;
+        let db = shared_db.lock();
+        Ok(get_meta(&db, LAST_SYNC_META_KEY)?.unwrap_or(0))
+    }
+
+    fn set_last_sync(&self, last_sync_millis: i64) -> Result<()> {
+        let shared_db = self.thread_safe_storage_db()?;
+        let db = shared_db.lock();
+        put_meta(&db, LAST_SYNC_META_KEY, &last_sync_millis)?;
+        Ok(())
+    }
+
+    fn sync_id(&self) -> Result<Option<String>> {
+        let shared_db = self.thread_safe_storage_db()?;
+        let db = shared_db.lock();
+        Ok(get_meta(&db, SYNC_ID_META_KEY)?)
+    }
+
+    fn reset_sync_id(&self) -> Result<String> {
+        let shared_db = self.thread_safe_storage_db()?;
+        let db = shared_db.lock();
+        let tx = db.unchecked_transaction()?;
+        let new_id = SyncGuid::random().to_string();
+        self.do_reset(&tx)?;
+        put_meta(&tx, SYNC_ID_META_KEY, &new_id)?;
+        tx.commit()?;
+        Ok(new_id)
+    }
+
+    fn ensure_current_sync_id(&self, sync_id: &str) -> Result<String> {
+        let shared_db = self.thread_safe_storage_db()?;
+        let db = shared_db.lock();
+        let current: Option<String> = get_meta(&db, SYNC_ID_META_KEY)?;
+        Ok(match current {
+            Some(current) if current == sync_id => current,
+            _ => {
+                let tx = db.unchecked_transaction()?;
+                self.do_reset(&tx)?;
+                let result = sync_id.to_string();
+                put_meta(&tx, SYNC_ID_META_KEY, &result)?;
+                tx.commit()?;
+                result
+            }
+        })
+    }
+
+    fn sync_started(&self) -> Result<()> {
+        let shared_db = self.thread_safe_storage_db()?;
+        let db = shared_db.lock();
+        schema::create_empty_sync_temp_tables(&db)?;
+        Ok(())
+    }
+
+    fn store_incoming(&self, incoming_bsos: Vec<IncomingBso>) -> Result<()> {
+        let shared_db = self.thread_safe_storage_db()?;
+        let db = shared_db.lock();
+        let signal = db.begin_interrupt_scope()?;
+        let tx = db.unchecked_transaction()?;
+        let incoming_content: Vec<_> = incoming_bsos
+            .into_iter()
+            .map(IncomingBso::into_content::<super::WebextRecord>)
+            .collect();
+        stage_incoming(&tx, &incoming_content, &signal)?;
+        tx.commit()?;
+        Ok(())
+    }
+
+    fn apply(&self) -> Result<ApplyResults> {
+        let shared_db = self.thread_safe_storage_db()?;
+        let db = shared_db.lock();
+        let signal = db.begin_interrupt_scope()?;
+
+        let tx = db.unchecked_transaction()?;
+        let incoming = get_incoming(&tx)?;
+        let actions = incoming
+            .into_iter()
+            .map(|(item, state)| (item, plan_incoming(state)))
+            .collect();
+        apply_actions(&tx, actions, &signal)?;
+        stage_outgoing(&tx)?;
+        tx.commit()?;
+
+        Ok(get_outgoing(&db, &signal)?.into())
+    }
+
+    fn set_uploaded(&self, _server_modified_millis: i64, ids: &[SyncGuid]) -> Result<()> {
+        let shared_db = self.thread_safe_storage_db()?;
+        let db = shared_db.lock();
+        let signal = db.begin_interrupt_scope()?;
+        let tx = db.unchecked_transaction()?;
+        record_uploaded(&tx, ids, &signal)?;
+        tx.commit()?;
+
+        Ok(())
+    }
+
+    fn sync_finished(&self) -> Result<()> {
+        let shared_db = self.thread_safe_storage_db()?;
+        let db = shared_db.lock();
+        schema::create_empty_sync_temp_tables(&db)?;
+        Ok(())
+    }
+
+    fn reset(&self) -> Result<()> {
+        let shared_db = self.thread_safe_storage_db()?;
+        let db = shared_db.lock();
+        let tx = db.unchecked_transaction()?;
+        self.do_reset(&tx)?;
+        delete_meta(&tx, SYNC_ID_META_KEY)?;
+        tx.commit()?;
+        Ok(())
+    }
+
+    fn wipe(&self) -> Result<()> {
+        let shared_db = self.thread_safe_storage_db()?;
+        let db = shared_db.lock();
+        let tx = db.unchecked_transaction()?;
+        // We assume the meta table is only used by sync.
+        tx.execute_batch(
+            "DELETE FROM storage_sync_data; DELETE FROM storage_sync_mirror; DELETE FROM meta;",
+        )?;
+        tx.commit()?;
+        Ok(())
+    }
+}
+
+impl From<anyhow::Error> for crate::error::Error {
+    fn from(value: anyhow::Error) -> Self {
+        crate::error::ErrorKind::SyncError(value.to_string()).into()
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::db::test::new_mem_thread_safe_storage_db;
+    use crate::db::StorageDb;
+    use sync15::engine::BridgedEngine;
+
+    fn query_count(conn: &StorageDb, table: &str) -> u32 {
+        conn.query_row_and_then(&format!("SELECT COUNT(*) FROM {};", table), [], |row| {
+            row.get::<_, u32>(0)
+        })
+        .expect("should work")
+    }
+
+    // Sets up mock data for the tests here.
+    fn setup_mock_data(engine: &super::BridgedEngine) -> Result<()> {
+        {
+            let shared = engine.thread_safe_storage_db()?;
+            let db = shared.lock();
+            db.execute(
+                "INSERT INTO storage_sync_data (ext_id, data, sync_change_counter)
+                    VALUES ('ext-a', 'invalid-json', 2)",
+                [],
+            )?;
+            db.execute(
+                "INSERT INTO storage_sync_mirror (guid, ext_id, data)
+                    VALUES ('guid', 'ext-a', '3')",
+                [],
+            )?;
+        }
+        engine.set_last_sync(1)?;
+
+        let shared = engine.thread_safe_storage_db()?;
+        let db = shared.lock();
+        // and assert we wrote what we think we did.
+        assert_eq!(query_count(&db, "storage_sync_data"), 1);
+        assert_eq!(query_count(&db, "storage_sync_mirror"), 1);
+        assert_eq!(query_count(&db, "meta"), 1);
+        Ok(())
+    }
+
+    // Assuming a DB setup with setup_mock_data, assert it was correctly reset.
+    fn assert_reset(engine: &super::BridgedEngine) -> Result<()> {
+        // A reset never wipes data...
+        let shared = engine.thread_safe_storage_db()?;
+        let db = shared.lock();
+        assert_eq!(query_count(&db, "storage_sync_data"), 1);
+
+        // But did reset the change counter.
+        let cc = db.query_row_and_then(
+            "SELECT sync_change_counter FROM storage_sync_data WHERE ext_id = 'ext-a';",
+            [],
+            |row| row.get::<_, u32>(0),
+        )?;
+        assert_eq!(cc, 1);
+        // But did wipe the mirror...
+        assert_eq!(query_count(&db, "storage_sync_mirror"), 0);
+        // And the last_sync should have been wiped.
+        assert!(get_meta::<i64>(&db, LAST_SYNC_META_KEY)?.is_none());
+        Ok(())
+    }
+
+    // Assuming a DB setup with setup_mock_data, assert it has not been reset.
+    fn assert_not_reset(engine: &super::BridgedEngine) -> Result<()> {
+        let shared = engine.thread_safe_storage_db()?;
+        let db = shared.lock();
+        assert_eq!(query_count(&db, "storage_sync_data"), 1);
+        let cc = db.query_row_and_then(
+            "SELECT sync_change_counter FROM storage_sync_data WHERE ext_id = 'ext-a';",
+            [],
+            |row| row.get::<_, u32>(0),
+        )?;
+        assert_eq!(cc, 2);
+        assert_eq!(query_count(&db, "storage_sync_mirror"), 1);
+        // And the last_sync should remain.
+        assert!(get_meta::<i64>(&db, LAST_SYNC_META_KEY)?.is_some());
+        Ok(())
+    }
+
+    #[test]
+    fn test_wipe() -> Result<()> {
+        let strong = new_mem_thread_safe_storage_db();
+        let engine = super::BridgedEngine::new(&strong);
+
+        setup_mock_data(&engine)?;
+
+        engine.wipe()?;
+
+        let shared = engine.thread_safe_storage_db()?;
+        let db = shared.lock();
+
+        assert_eq!(query_count(&db, "storage_sync_data"), 0);
+        assert_eq!(query_count(&db, "storage_sync_mirror"), 0);
+        assert_eq!(query_count(&db, "meta"), 0);
+        Ok(())
+    }
+
+    #[test]
+    fn test_reset() -> Result<()> {
+        let strong = new_mem_thread_safe_storage_db();
+        let engine = super::BridgedEngine::new(&strong);
+
+        setup_mock_data(&engine)?;
+        put_meta(
+            &engine.thread_safe_storage_db()?.lock(),
+            SYNC_ID_META_KEY,
+            &"sync-id".to_string(),
+        )?;
+
+        engine.reset()?;
+        assert_reset(&engine)?;
+        // Only an explicit reset kills the sync-id, so check that here.
+        assert_eq!(
+            get_meta::<String>(&engine.thread_safe_storage_db()?.lock(), SYNC_ID_META_KEY)?,
+            None
+        );
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_ensure_missing_sync_id() -> Result<()> {
+        let strong = new_mem_thread_safe_storage_db();
+        let engine = super::BridgedEngine::new(&strong);
+
+        setup_mock_data(&engine)?;
+
+        assert_eq!(engine.sync_id()?, None);
+        // We don't have a sync ID - so setting one should reset.
+        engine.ensure_current_sync_id("new-id")?;
+        // should have cause a reset.
+        assert_reset(&engine)?;
+        Ok(())
+    }
+
+    #[test]
+    fn test_ensure_new_sync_id() -> Result<()> {
+        let strong = new_mem_thread_safe_storage_db();
+        let engine = super::BridgedEngine::new(&strong);
+
+        setup_mock_data(&engine)?;
+
+        put_meta(
+            &engine.thread_safe_storage_db()?.lock(),
+            SYNC_ID_META_KEY,
+            &"old-id".to_string(),
+        )?;
+        assert_not_reset(&engine)?;
+        assert_eq!(engine.sync_id()?, Some("old-id".to_string()));
+
+        engine.ensure_current_sync_id("new-id")?;
+        // should have cause a reset.
+        assert_reset(&engine)?;
+        // should have the new id.
+        assert_eq!(engine.sync_id()?, Some("new-id".to_string()));
+        Ok(())
+    }
+
+    #[test]
+    fn test_ensure_same_sync_id() -> Result<()> {
+        let strong = new_mem_thread_safe_storage_db();
+        let engine = super::BridgedEngine::new(&strong);
+
+        setup_mock_data(&engine)?;
+        assert_not_reset(&engine)?;
+
+        put_meta(
+            &engine.thread_safe_storage_db()?.lock(),
+            SYNC_ID_META_KEY,
+            &"sync-id".to_string(),
+        )?;
+
+        engine.ensure_current_sync_id("sync-id")?;
+        // should not have reset.
+        assert_not_reset(&engine)?;
+        Ok(())
+    }
+
+    #[test]
+    fn test_reset_sync_id() -> Result<()> {
+        let strong = new_mem_thread_safe_storage_db();
+        let engine = super::BridgedEngine::new(&strong);
+
+        setup_mock_data(&engine)?;
+        put_meta(
+            &engine.thread_safe_storage_db()?.lock(),
+            SYNC_ID_META_KEY,
+            &"sync-id".to_string(),
+        )?;
+
+        assert_eq!(engine.sync_id()?, Some("sync-id".to_string()));
+        let new_id = engine.reset_sync_id()?;
+        // should have cause a reset.
+        assert_reset(&engine)?;
+        assert_eq!(engine.sync_id()?, Some(new_id));
+        Ok(())
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/webext_storage/sync/incoming.rs.html b/book/rust-docs/src/webext_storage/sync/incoming.rs.html new file mode 100644 index 0000000000..18bb0c5120 --- /dev/null +++ b/book/rust-docs/src/webext_storage/sync/incoming.rs.html @@ -0,0 +1,1727 @@ +incoming.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+500
+501
+502
+503
+504
+505
+506
+507
+508
+509
+510
+511
+512
+513
+514
+515
+516
+517
+518
+519
+520
+521
+522
+523
+524
+525
+526
+527
+528
+529
+530
+531
+532
+533
+534
+535
+536
+537
+538
+539
+540
+541
+542
+543
+544
+545
+546
+547
+548
+549
+550
+551
+552
+553
+554
+555
+556
+557
+558
+559
+560
+561
+562
+563
+564
+565
+566
+567
+568
+569
+570
+571
+572
+573
+574
+575
+576
+577
+578
+579
+580
+581
+582
+583
+584
+585
+586
+587
+588
+589
+590
+591
+592
+593
+594
+595
+596
+597
+598
+599
+600
+601
+602
+603
+604
+605
+606
+607
+608
+609
+610
+611
+612
+613
+614
+615
+616
+617
+618
+619
+620
+621
+622
+623
+624
+625
+626
+627
+628
+629
+630
+631
+632
+633
+634
+635
+636
+637
+638
+639
+640
+641
+642
+643
+644
+645
+646
+647
+648
+649
+650
+651
+652
+653
+654
+655
+656
+657
+658
+659
+660
+661
+662
+663
+664
+665
+666
+667
+668
+669
+670
+671
+672
+673
+674
+675
+676
+677
+678
+679
+680
+681
+682
+683
+684
+685
+686
+687
+688
+689
+690
+691
+692
+693
+694
+695
+696
+697
+698
+699
+700
+701
+702
+703
+704
+705
+706
+707
+708
+709
+710
+711
+712
+713
+714
+715
+716
+717
+718
+719
+720
+721
+722
+723
+724
+725
+726
+727
+728
+729
+730
+731
+732
+733
+734
+735
+736
+737
+738
+739
+740
+741
+742
+743
+744
+745
+746
+747
+748
+749
+750
+751
+752
+753
+754
+755
+756
+757
+758
+759
+760
+761
+762
+763
+764
+765
+766
+767
+768
+769
+770
+771
+772
+773
+774
+775
+776
+777
+778
+779
+780
+781
+782
+783
+784
+785
+786
+787
+788
+789
+790
+791
+792
+793
+794
+795
+796
+797
+798
+799
+800
+801
+802
+803
+804
+805
+806
+807
+808
+809
+810
+811
+812
+813
+814
+815
+816
+817
+818
+819
+820
+821
+822
+823
+824
+825
+826
+827
+828
+829
+830
+831
+832
+833
+834
+835
+836
+837
+838
+839
+840
+841
+842
+843
+844
+845
+846
+847
+848
+849
+850
+851
+852
+853
+854
+855
+856
+857
+858
+859
+860
+861
+862
+863
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// The "incoming" part of syncing - handling the incoming rows, staging them,
+// working out a plan for them, updating the local data and mirror, etc.
+
+use interrupt_support::Interruptee;
+use rusqlite::{Connection, Row, Transaction};
+use sql_support::ConnExt;
+use sync15::bso::{IncomingContent, IncomingKind};
+use sync_guid::Guid as SyncGuid;
+
+use crate::api::{StorageChanges, StorageValueChange};
+use crate::error::*;
+
+use super::{merge, remove_matching_keys, JsonMap, WebextRecord};
+
+/// The state data can be in. Could be represented as Option<JsonMap>, but this
+/// is clearer and independent of how the data is stored.
+#[derive(Debug, PartialEq, Eq)]
+pub enum DataState {
+    /// The data was deleted.
+    Deleted,
+    /// Data exists, as stored in the map.
+    Exists(JsonMap),
+}
+
+// A little helper to create a StorageChanges object when we are creating
+// a new value with multiple keys that doesn't exist locally.
+fn changes_for_new_incoming(new: &JsonMap) -> StorageChanges {
+    let mut result = StorageChanges::with_capacity(new.len());
+    for (key, val) in new.iter() {
+        result.push(StorageValueChange {
+            key: key.clone(),
+            old_value: None,
+            new_value: Some(val.clone()),
+        });
+    }
+    result
+}
+
+// This module deals exclusively with the Map inside a JsonValue::Object().
+// This helper reads such a Map from a SQL row, ignoring anything which is
+// either invalid JSON or a different JSON type.
+fn json_map_from_row(row: &Row<'_>, col: &str) -> Result<DataState> {
+    let s = row.get::<_, Option<String>>(col)?;
+    Ok(match s {
+        None => DataState::Deleted,
+        Some(s) => match serde_json::from_str(&s) {
+            Ok(serde_json::Value::Object(m)) => DataState::Exists(m),
+            _ => {
+                // We don't want invalid json or wrong types to kill syncing.
+                // It should be impossible as we never write anything which
+                // could cause it, but we can't really log the bad data as there
+                // might be PII. Logging just a message without any additional
+                // clues is going to be unhelpfully noisy, so, silently None.
+                // XXX - Maybe record telemetry?
+                DataState::Deleted
+            }
+        },
+    })
+}
+
+/// The first thing we do with incoming items is to "stage" them in a temp table.
+/// The actual processing is done via this table.
+pub fn stage_incoming(
+    tx: &Transaction<'_>,
+    incoming_records: &[IncomingContent<WebextRecord>],
+    signal: &dyn Interruptee,
+) -> Result<()> {
+    sql_support::each_sized_chunk(
+        incoming_records,
+        // We bind 3 params per chunk.
+        sql_support::default_max_variable_number() / 3,
+        |chunk, _| -> Result<()> {
+            let mut params = Vec::with_capacity(chunk.len() * 3);
+            for record in chunk {
+                signal.err_if_interrupted()?;
+                match &record.kind {
+                    IncomingKind::Content(r) => {
+                        params.push(Some(record.envelope.id.to_string()));
+                        params.push(Some(r.ext_id.to_string()));
+                        params.push(Some(r.data.clone()));
+                    }
+                    IncomingKind::Tombstone => {
+                        params.push(Some(record.envelope.id.to_string()));
+                        params.push(None);
+                        params.push(None);
+                    }
+                    IncomingKind::Malformed => {
+                        log::error!("Ignoring incoming malformed record: {}", record.envelope.id);
+                    }
+                }
+            }
+            // we might have skipped records
+            let actual_len = params.len() / 3;
+            if actual_len != 0 {
+                let sql = format!(
+                    "INSERT OR REPLACE INTO temp.storage_sync_staging
+                    (guid, ext_id, data)
+                    VALUES {}",
+                    sql_support::repeat_multi_values(actual_len, 3)
+                );
+                tx.execute(&sql, rusqlite::params_from_iter(params))?;
+            }
+            Ok(())
+        },
+    )?;
+    Ok(())
+}
+
+/// The "state" we find ourselves in when considering an incoming/staging
+/// record. This "state" is the input to calculating the IncomingAction and
+/// carries all the data we need to make the required local changes.
+#[derive(Debug, PartialEq, Eq)]
+pub enum IncomingState {
+    /// There's an incoming item, but data for that extension doesn't exist
+    /// either in our local data store or in the local mirror. IOW, this is the
+    /// very first time we've seen this extension.
+    IncomingOnlyData { ext_id: String, data: JsonMap },
+
+    /// An incoming tombstone that doesn't exist locally. Because tombstones
+    /// don't carry the ext-id, it means it's not in our mirror. We are just
+    /// going to ignore it, but we track the state for consistency.
+    IncomingOnlyTombstone,
+
+    /// There's an incoming item and we have data for the same extension in
+    /// our local store - but not in our mirror. This should be relatively
+    /// uncommon as it means:
+    /// * Some other profile has recently installed an extension and synced.
+    /// * This profile has recently installed the same extension.
+    /// * This is the first sync for this profile since both those events
+    ///   happened.
+    HasLocal {
+        ext_id: String,
+        incoming: DataState,
+        local: DataState,
+    },
+    /// There's an incoming item and there's an item for the same extension in
+    /// the mirror. The addon probably doesn't exist locally, or if it does,
+    /// the last time we synced we synced the deletion of all data.
+    NotLocal {
+        ext_id: String,
+        incoming: DataState,
+        mirror: DataState,
+    },
+    /// This will be the most common "incoming" case - there's data incoming,
+    /// in the mirror and in the local store for an addon.
+    Everywhere {
+        ext_id: String,
+        incoming: DataState,
+        mirror: DataState,
+        local: DataState,
+    },
+}
+
+/// Get the items we need to process from the staging table. Return details about
+/// the item and the state of that item, ready for processing.
+pub fn get_incoming(conn: &Connection) -> Result<Vec<(SyncGuid, IncomingState)>> {
+    let sql = "
+        SELECT
+            s.guid as guid,
+            l.ext_id as l_ext_id,
+            m.ext_id as m_ext_id,
+            s.ext_id as s_ext_id,
+            s.data as s_data, m.data as m_data, l.data as l_data,
+            l.sync_change_counter
+        FROM temp.storage_sync_staging s
+        LEFT JOIN storage_sync_mirror m ON m.guid = s.guid
+        LEFT JOIN storage_sync_data l on l.ext_id IN (m.ext_id, s.ext_id);";
+
+    fn from_row(row: &Row<'_>) -> Result<(SyncGuid, IncomingState)> {
+        let guid = row.get("guid")?;
+        // This is complicated because the staging row doesn't hold the ext_id.
+        // However, both the local table and the mirror do.
+        let mirror_ext_id: Option<String> = row.get("m_ext_id")?;
+        let local_ext_id: Option<String> = row.get("l_ext_id")?;
+        let staged_ext_id: Option<String> = row.get("s_ext_id")?;
+        let incoming = json_map_from_row(row, "s_data")?;
+
+        // We find the state by examining which tables the ext-id exists in,
+        // using whether that column is null as a proxy for that.
+        let state = match (local_ext_id, mirror_ext_id) {
+            (None, None) => {
+                match staged_ext_id {
+                    Some(ext_id) => {
+                        let data = match incoming {
+                            // incoming record with missing data that's not a
+                            // tombstone shouldn't happen, but we can cope by
+                            // pretending it was an empty json map.
+                            DataState::Deleted => JsonMap::new(),
+                            DataState::Exists(data) => data,
+                        };
+                        IncomingState::IncomingOnlyData { ext_id, data }
+                    }
+                    None => IncomingState::IncomingOnlyTombstone,
+                }
+            }
+            (Some(ext_id), None) => IncomingState::HasLocal {
+                ext_id,
+                incoming,
+                local: json_map_from_row(row, "l_data")?,
+            },
+            (None, Some(ext_id)) => IncomingState::NotLocal {
+                ext_id,
+                incoming,
+                mirror: json_map_from_row(row, "m_data")?,
+            },
+            (Some(ext_id), Some(_)) => IncomingState::Everywhere {
+                ext_id,
+                incoming,
+                mirror: json_map_from_row(row, "m_data")?,
+                local: json_map_from_row(row, "l_data")?,
+            },
+        };
+        Ok((guid, state))
+    }
+
+    conn.conn().query_rows_and_then(sql, [], from_row)
+}
+
+/// This is the set of actions we know how to take *locally* for incoming
+/// records. Which one depends on the IncomingState.
+/// Every state which updates also records the set of changes we should notify
+#[derive(Debug, PartialEq, Eq)]
+pub enum IncomingAction {
+    /// We should locally delete the data for this record
+    DeleteLocally {
+        ext_id: String,
+        changes: StorageChanges,
+    },
+    /// We will take the remote.
+    TakeRemote {
+        ext_id: String,
+        data: JsonMap,
+        changes: StorageChanges,
+    },
+    /// We merged this data - this is what we came up with.
+    Merge {
+        ext_id: String,
+        data: JsonMap,
+        changes: StorageChanges,
+    },
+    /// Entry exists locally and it's the same as the incoming record.
+    Same { ext_id: String },
+    /// Incoming tombstone for an item we've never seen.
+    Nothing,
+}
+
+/// Takes the state of an item and returns the action we should take for it.
+pub fn plan_incoming(s: IncomingState) -> IncomingAction {
+    match s {
+        IncomingState::Everywhere {
+            ext_id,
+            incoming,
+            local,
+            mirror,
+        } => {
+            // All records exist - but do they all have data?
+            match (incoming, local, mirror) {
+                (
+                    DataState::Exists(incoming_data),
+                    DataState::Exists(local_data),
+                    DataState::Exists(mirror_data),
+                ) => {
+                    // all records have data - 3-way merge.
+                    merge(ext_id, incoming_data, local_data, Some(mirror_data))
+                }
+                (
+                    DataState::Exists(incoming_data),
+                    DataState::Exists(local_data),
+                    DataState::Deleted,
+                ) => {
+                    // No parent, so first time seeing this remotely - 2-way merge
+                    merge(ext_id, incoming_data, local_data, None)
+                }
+                (DataState::Exists(incoming_data), DataState::Deleted, _) => {
+                    // Incoming data, removed locally. Server wins.
+                    IncomingAction::TakeRemote {
+                        ext_id,
+                        changes: changes_for_new_incoming(&incoming_data),
+                        data: incoming_data,
+                    }
+                }
+                (DataState::Deleted, DataState::Exists(local_data), DataState::Exists(mirror)) => {
+                    // Deleted remotely.
+                    // Treat this as a delete of every key that we
+                    // know was present at the time.
+                    let (result, changes) = remove_matching_keys(local_data, &mirror);
+                    if result.is_empty() {
+                        // If there were no more keys left, we can
+                        // delete our version too.
+                        IncomingAction::DeleteLocally { ext_id, changes }
+                    } else {
+                        IncomingAction::Merge {
+                            ext_id,
+                            data: result,
+                            changes,
+                        }
+                    }
+                }
+                (DataState::Deleted, DataState::Exists(local_data), DataState::Deleted) => {
+                    // Perhaps another client created and then deleted
+                    // the whole object for this extension since the
+                    // last time we synced.
+                    // Treat this as a delete of every key that we
+                    // knew was present. Unfortunately, we don't know
+                    // any keys that were present, so we delete no keys.
+                    IncomingAction::Merge {
+                        ext_id,
+                        data: local_data,
+                        changes: StorageChanges::new(),
+                    }
+                }
+                (DataState::Deleted, DataState::Deleted, _) => {
+                    // We agree with the remote (regardless of what we
+                    // have mirrored).
+                    IncomingAction::Same { ext_id }
+                }
+            }
+        }
+        IncomingState::HasLocal {
+            ext_id,
+            incoming,
+            local,
+        } => {
+            // So we have a local record and an incoming/staging record, but *not* a
+            // mirror record. This means some other device has synced this for
+            // the first time and we are yet to do the same.
+            match (incoming, local) {
+                (DataState::Exists(incoming_data), DataState::Exists(local_data)) => {
+                    // This means the extension exists locally and remotely
+                    // but this is the first time we've synced it. That's no problem, it's
+                    // just a 2-way merge...
+                    merge(ext_id, incoming_data, local_data, None)
+                }
+                (DataState::Deleted, DataState::Exists(local_data)) => {
+                    // We've data locally, but there's an incoming deletion.
+                    // We would normally remove keys that we knew were
+                    // present on the server, but we don't know what
+                    // was on the server, so we don't remove anything.
+                    IncomingAction::Merge {
+                        ext_id,
+                        data: local_data,
+                        changes: StorageChanges::new(),
+                    }
+                }
+                (DataState::Exists(incoming_data), DataState::Deleted) => {
+                    // No data locally, but some is incoming - take it.
+                    IncomingAction::TakeRemote {
+                        ext_id,
+                        changes: changes_for_new_incoming(&incoming_data),
+                        data: incoming_data,
+                    }
+                }
+                (DataState::Deleted, DataState::Deleted) => {
+                    // Nothing anywhere - odd, but OK.
+                    IncomingAction::Same { ext_id }
+                }
+            }
+        }
+        IncomingState::NotLocal {
+            ext_id, incoming, ..
+        } => {
+            // No local data but there's mirror and an incoming record.
+            // This means a local deletion is being replaced by, or just re-doing
+            // the incoming record.
+            match incoming {
+                DataState::Exists(data) => IncomingAction::TakeRemote {
+                    ext_id,
+                    changes: changes_for_new_incoming(&data),
+                    data,
+                },
+                DataState::Deleted => IncomingAction::Same { ext_id },
+            }
+        }
+        IncomingState::IncomingOnlyData { ext_id, data } => {
+            // Only the staging record exists and it's not a tombstone.
+            // This means it's the first time we've ever seen it. No
+            // conflict possible, just take the remote.
+            IncomingAction::TakeRemote {
+                ext_id,
+                changes: changes_for_new_incoming(&data),
+                data,
+            }
+        }
+        IncomingState::IncomingOnlyTombstone => {
+            // Only the staging record exists and it is a tombstone - nothing to do.
+            IncomingAction::Nothing
+        }
+    }
+}
+
+fn insert_changes(tx: &Transaction<'_>, ext_id: &str, changes: &StorageChanges) -> Result<()> {
+    tx.execute_cached(
+        "INSERT INTO temp.storage_sync_applied (ext_id, changes)
+            VALUES (:ext_id, :changes)",
+        rusqlite::named_params! {
+            ":ext_id": ext_id,
+            ":changes": &serde_json::to_string(&changes)?,
+        },
+    )?;
+    Ok(())
+}
+
+// Apply the actions necessary to fully process the incoming items.
+pub fn apply_actions(
+    tx: &Transaction<'_>,
+    actions: Vec<(SyncGuid, IncomingAction)>,
+    signal: &dyn Interruptee,
+) -> Result<()> {
+    for (item, action) in actions {
+        signal.err_if_interrupted()?;
+
+        log::trace!("action for '{:?}': {:?}", item, action);
+        match action {
+            IncomingAction::DeleteLocally { ext_id, changes } => {
+                // Can just nuke it entirely.
+                tx.execute_cached(
+                    "DELETE FROM storage_sync_data WHERE ext_id = :ext_id",
+                    &[(":ext_id", &ext_id)],
+                )?;
+                insert_changes(tx, &ext_id, &changes)?;
+            }
+            // We want to update the local record with 'data' and after this update the item no longer is considered dirty.
+            IncomingAction::TakeRemote {
+                ext_id,
+                data,
+                changes,
+            } => {
+                tx.execute_cached(
+                    "INSERT OR REPLACE INTO storage_sync_data(ext_id, data, sync_change_counter)
+                        VALUES (:ext_id, :data, 0)",
+                    rusqlite::named_params! {
+                        ":ext_id": ext_id,
+                        ":data": serde_json::Value::Object(data),
+                    },
+                )?;
+                insert_changes(tx, &ext_id, &changes)?;
+            }
+
+            // We merged this data, so need to update locally but still consider
+            // it dirty because the merged data must be uploaded.
+            IncomingAction::Merge {
+                ext_id,
+                data,
+                changes,
+            } => {
+                tx.execute_cached(
+                    "UPDATE storage_sync_data SET data = :data, sync_change_counter = sync_change_counter + 1 WHERE ext_id = :ext_id",
+                    rusqlite::named_params! {
+                        ":ext_id": ext_id,
+                        ":data": serde_json::Value::Object(data),
+                    },
+                )?;
+                insert_changes(tx, &ext_id, &changes)?;
+            }
+
+            // Both local and remote ended up the same - only need to nuke the
+            // change counter.
+            IncomingAction::Same { ext_id } => {
+                tx.execute_cached(
+                    "UPDATE storage_sync_data SET sync_change_counter = 0 WHERE ext_id = :ext_id",
+                    &[(":ext_id", &ext_id)],
+                )?;
+                // no changes to write
+            }
+            // Literally nothing to do!
+            IncomingAction::Nothing => {}
+        }
+    }
+    Ok(())
+}
+
+#[cfg(test)]
+mod tests {
+    use super::super::test::new_syncable_mem_db;
+    use super::*;
+    use crate::api;
+    use interrupt_support::NeverInterrupts;
+    use serde_json::{json, Value};
+    use sync15::bso::IncomingBso;
+
+    // select simple int
+    fn ssi(conn: &Connection, stmt: &str) -> u32 {
+        conn.try_query_one(stmt, [], true)
+            .expect("must work")
+            .unwrap_or_default()
+    }
+
+    fn array_to_incoming(mut array: Value) -> Vec<IncomingContent<WebextRecord>> {
+        let jv = array.as_array_mut().expect("you must pass a json array");
+        let mut result = Vec::with_capacity(jv.len());
+        for elt in jv {
+            result.push(IncomingBso::from_test_content(elt.take()).into_content());
+        }
+        result
+    }
+
+    // Can't find a way to import these from crate::sync::tests...
+    macro_rules! map {
+        ($($map:tt)+) => {
+            json!($($map)+).as_object().unwrap().clone()
+        };
+    }
+    macro_rules! change {
+        ($key:literal, None, None) => {
+            StorageValueChange {
+                key: $key.to_string(),
+                old_value: None,
+                new_value: None,
+            };
+        };
+        ($key:literal, $old:tt, None) => {
+            StorageValueChange {
+                key: $key.to_string(),
+                old_value: Some(json!($old)),
+                new_value: None,
+            }
+        };
+        ($key:literal, None, $new:tt) => {
+            StorageValueChange {
+                key: $key.to_string(),
+                old_value: None,
+                new_value: Some(json!($new)),
+            };
+        };
+        ($key:literal, $old:tt, $new:tt) => {
+            StorageValueChange {
+                key: $key.to_string(),
+                old_value: Some(json!($old)),
+                new_value: Some(json!($new)),
+            }
+        };
+    }
+    macro_rules! changes {
+        ( $( $change:expr ),* ) => {
+            {
+                let mut changes = StorageChanges::new();
+                $(
+                    changes.push($change);
+                )*
+                changes
+            }
+        };
+    }
+
+    #[test]
+    fn test_incoming_populates_staging() -> Result<()> {
+        let mut db = new_syncable_mem_db();
+        let tx = db.transaction()?;
+
+        let incoming = json! {[
+            {
+                "id": "guidAAAAAAAA",
+                "extId": "ext1@example.com",
+                "data": json!({"foo": "bar"}).to_string(),
+            }
+        ]};
+
+        stage_incoming(&tx, &array_to_incoming(incoming), &NeverInterrupts)?;
+        // check staging table
+        assert_eq!(
+            ssi(&tx, "SELECT count(*) FROM temp.storage_sync_staging"),
+            1
+        );
+        Ok(())
+    }
+
+    #[test]
+    fn test_fetch_incoming_state() -> Result<()> {
+        let mut db = new_syncable_mem_db();
+        let tx = db.transaction()?;
+
+        // Start with an item just in staging.
+        tx.execute(
+            r#"
+            INSERT INTO temp.storage_sync_staging (guid, ext_id, data)
+            VALUES ('guid', 'ext_id', '{"foo":"bar"}')
+        "#,
+            [],
+        )?;
+
+        let incoming = get_incoming(&tx)?;
+        assert_eq!(incoming.len(), 1);
+        assert_eq!(incoming[0].0, SyncGuid::new("guid"),);
+        assert_eq!(
+            incoming[0].1,
+            IncomingState::IncomingOnlyData {
+                ext_id: "ext_id".to_string(),
+                data: map!({"foo": "bar"}),
+            }
+        );
+
+        // Add the same item to the mirror.
+        tx.execute(
+            r#"
+            INSERT INTO storage_sync_mirror (guid, ext_id, data)
+            VALUES ('guid', 'ext_id', '{"foo":"new"}')
+        "#,
+            [],
+        )?;
+        let incoming = get_incoming(&tx)?;
+        assert_eq!(incoming.len(), 1);
+        assert_eq!(
+            incoming[0].1,
+            IncomingState::NotLocal {
+                ext_id: "ext_id".to_string(),
+                incoming: DataState::Exists(map!({"foo": "bar"})),
+                mirror: DataState::Exists(map!({"foo": "new"})),
+            }
+        );
+
+        // and finally the data itself - might as use the API here!
+        api::set(&tx, "ext_id", json!({"foo": "local"}))?;
+        let incoming = get_incoming(&tx)?;
+        assert_eq!(incoming.len(), 1);
+        assert_eq!(
+            incoming[0].1,
+            IncomingState::Everywhere {
+                ext_id: "ext_id".to_string(),
+                incoming: DataState::Exists(map!({"foo": "bar"})),
+                local: DataState::Exists(map!({"foo": "local"})),
+                mirror: DataState::Exists(map!({"foo": "new"})),
+            }
+        );
+        Ok(())
+    }
+
+    // Like test_fetch_incoming_state, but check NULLs are handled correctly.
+    #[test]
+    fn test_fetch_incoming_state_nulls() -> Result<()> {
+        let mut db = new_syncable_mem_db();
+        let tx = db.transaction()?;
+
+        // Start with a tombstone just in staging.
+        tx.execute(
+            r#"
+            INSERT INTO temp.storage_sync_staging (guid, ext_id, data)
+            VALUES ('guid', NULL, NULL)
+        "#,
+            [],
+        )?;
+
+        let incoming = get_incoming(&tx)?;
+        assert_eq!(incoming.len(), 1);
+        assert_eq!(incoming[0].1, IncomingState::IncomingOnlyTombstone,);
+
+        // Add the same item to the mirror (can't store an ext_id for a
+        // tombstone in the mirror as incoming tombstones never have it)
+        tx.execute(
+            r#"
+            INSERT INTO storage_sync_mirror (guid, ext_id, data)
+            VALUES ('guid', NULL, NULL)
+        "#,
+            [],
+        )?;
+        let incoming = get_incoming(&tx)?;
+        assert_eq!(incoming.len(), 1);
+        assert_eq!(incoming[0].1, IncomingState::IncomingOnlyTombstone);
+
+        tx.execute(
+            r#"
+            INSERT INTO storage_sync_data (ext_id, data)
+            VALUES ('ext_id', NULL)
+        "#,
+            [],
+        )?;
+        let incoming = get_incoming(&tx)?;
+        assert_eq!(incoming.len(), 1);
+        assert_eq!(
+            incoming[0].1,
+            // IncomingOnly* seems a little odd, but it is because we can't
+            // tie the tombstones together due to the lack of any ext-id/guid
+            // mapping in this case.
+            IncomingState::IncomingOnlyTombstone
+        );
+        Ok(())
+    }
+
+    // apply_action tests.
+    #[derive(Debug, PartialEq)]
+    struct LocalItem {
+        data: DataState,
+        sync_change_counter: i32,
+    }
+
+    fn get_local_item(conn: &Connection) -> Option<LocalItem> {
+        conn.try_query_row::<_, Error, _, _>(
+            "SELECT data, sync_change_counter FROM storage_sync_data WHERE ext_id = 'ext_id'",
+            [],
+            |row| {
+                let data = json_map_from_row(row, "data")?;
+                let sync_change_counter = row.get::<_, i32>(1)?;
+                Ok(LocalItem {
+                    data,
+                    sync_change_counter,
+                })
+            },
+            true,
+        )
+        .expect("query should work")
+    }
+
+    fn get_applied_item_changes(conn: &Connection) -> Option<StorageChanges> {
+        // no custom deserialize for storagechanges and we only need it for
+        // tests, so do it manually.
+        conn.try_query_row::<_, Error, _, _>(
+            "SELECT changes FROM temp.storage_sync_applied WHERE ext_id = 'ext_id'",
+            [],
+            |row| Ok(serde_json::from_str(&row.get::<_, String>("changes")?)?),
+            true,
+        )
+        .expect("query should work")
+        .map(|val: serde_json::Value| {
+            let ob = val.as_object().expect("should be an object of items");
+            let mut result = StorageChanges::with_capacity(ob.len());
+            for (key, val) in ob.into_iter() {
+                let details = val.as_object().expect("elts should be objects");
+                result.push(StorageValueChange {
+                    key: key.to_string(),
+                    old_value: details.get("oldValue").cloned(),
+                    new_value: details.get("newValue").cloned(),
+                });
+            }
+            result
+        })
+    }
+
+    fn do_apply_action(tx: &Transaction<'_>, action: IncomingAction) {
+        let guid = SyncGuid::new("guid");
+        apply_actions(tx, vec![(guid, action)], &NeverInterrupts).expect("should apply");
+    }
+
+    #[test]
+    fn test_apply_actions() -> Result<()> {
+        let mut db = new_syncable_mem_db();
+
+        // DeleteLocally - row should be entirely removed.
+        let tx = db.transaction().expect("transaction should work");
+        api::set(&tx, "ext_id", json!({"foo": "local"}))?;
+        assert_eq!(
+            api::get(&tx, "ext_id", json!(null))?,
+            json!({"foo": "local"})
+        );
+        let changes = changes![change!("foo", "local", None)];
+        do_apply_action(
+            &tx,
+            IncomingAction::DeleteLocally {
+                ext_id: "ext_id".to_string(),
+                changes: changes.clone(),
+            },
+        );
+        assert_eq!(api::get(&tx, "ext_id", json!(null))?, json!({}));
+        // and there should not be a local record at all.
+        assert!(get_local_item(&tx).is_none());
+        assert_eq!(get_applied_item_changes(&tx), Some(changes));
+        tx.rollback()?;
+
+        // TakeRemote - replace local data with remote and marked as not dirty.
+        let tx = db.transaction().expect("transaction should work");
+        api::set(&tx, "ext_id", json!({"foo": "local"}))?;
+        assert_eq!(
+            api::get(&tx, "ext_id", json!(null))?,
+            json!({"foo": "local"})
+        );
+        // data should exist locally with a change recorded.
+        assert_eq!(
+            get_local_item(&tx),
+            Some(LocalItem {
+                data: DataState::Exists(map!({"foo": "local"})),
+                sync_change_counter: 1
+            })
+        );
+        let changes = changes![change!("foo", "local", "remote")];
+        do_apply_action(
+            &tx,
+            IncomingAction::TakeRemote {
+                ext_id: "ext_id".to_string(),
+                data: map!({"foo": "remote"}),
+                changes: changes.clone(),
+            },
+        );
+        // data should exist locally with the remote data and not be dirty.
+        assert_eq!(
+            get_local_item(&tx),
+            Some(LocalItem {
+                data: DataState::Exists(map!({"foo": "remote"})),
+                sync_change_counter: 0
+            })
+        );
+        assert_eq!(get_applied_item_changes(&tx), Some(changes));
+        tx.rollback()?;
+
+        // Merge - like ::TakeRemote, but data remains dirty.
+        let tx = db.transaction().expect("transaction should work");
+        api::set(&tx, "ext_id", json!({"foo": "local"}))?;
+        assert_eq!(
+            api::get(&tx, "ext_id", json!(null))?,
+            json!({"foo": "local"})
+        );
+        // data should exist locally with a change recorded.
+        assert_eq!(
+            get_local_item(&tx),
+            Some(LocalItem {
+                data: DataState::Exists(map!({"foo": "local"})),
+                sync_change_counter: 1
+            })
+        );
+        let changes = changes![change!("foo", "local", "remote")];
+        do_apply_action(
+            &tx,
+            IncomingAction::Merge {
+                ext_id: "ext_id".to_string(),
+                data: map!({"foo": "remote"}),
+                changes: changes.clone(),
+            },
+        );
+        assert_eq!(
+            get_local_item(&tx),
+            Some(LocalItem {
+                data: DataState::Exists(map!({"foo": "remote"})),
+                sync_change_counter: 2
+            })
+        );
+        assert_eq!(get_applied_item_changes(&tx), Some(changes));
+        tx.rollback()?;
+
+        // Same - data stays the same but is marked not dirty.
+        let tx = db.transaction().expect("transaction should work");
+        api::set(&tx, "ext_id", json!({"foo": "local"}))?;
+        assert_eq!(
+            api::get(&tx, "ext_id", json!(null))?,
+            json!({"foo": "local"})
+        );
+        // data should exist locally with a change recorded.
+        assert_eq!(
+            get_local_item(&tx),
+            Some(LocalItem {
+                data: DataState::Exists(map!({"foo": "local"})),
+                sync_change_counter: 1
+            })
+        );
+        do_apply_action(
+            &tx,
+            IncomingAction::Same {
+                ext_id: "ext_id".to_string(),
+            },
+        );
+        assert_eq!(
+            get_local_item(&tx),
+            Some(LocalItem {
+                data: DataState::Exists(map!({"foo": "local"})),
+                sync_change_counter: 0
+            })
+        );
+        assert_eq!(get_applied_item_changes(&tx), None);
+        tx.rollback()?;
+
+        Ok(())
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/webext_storage/sync/mod.rs.html b/book/rust-docs/src/webext_storage/sync/mod.rs.html new file mode 100644 index 0000000000..3d94c48f3e --- /dev/null +++ b/book/rust-docs/src/webext_storage/sync/mod.rs.html @@ -0,0 +1,859 @@ +mod.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+mod bridge;
+mod incoming;
+mod outgoing;
+
+#[cfg(test)]
+mod sync_tests;
+
+use crate::api::{StorageChanges, StorageValueChange};
+use crate::db::StorageDb;
+use crate::error::*;
+use serde::Deserialize;
+use serde_derive::*;
+use sql_support::ConnExt;
+use sync_guid::Guid as SyncGuid;
+
+pub use bridge::BridgedEngine;
+use incoming::IncomingAction;
+
+type JsonMap = serde_json::Map<String, serde_json::Value>;
+
+pub const STORAGE_VERSION: usize = 1;
+
+#[derive(Debug, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct WebextRecord {
+    #[serde(rename = "id")]
+    guid: SyncGuid,
+    #[serde(rename = "extId")]
+    ext_id: String,
+    data: String,
+}
+
+// Perform a 2-way or 3-way merge, where the incoming value wins on confict.
+fn merge(
+    ext_id: String,
+    mut other: JsonMap,
+    mut ours: JsonMap,
+    parent: Option<JsonMap>,
+) -> IncomingAction {
+    if other == ours {
+        return IncomingAction::Same { ext_id };
+    }
+    let old_incoming = other.clone();
+    // worst case is keys in each are unique.
+    let mut changes = StorageChanges::with_capacity(other.len() + ours.len());
+    if let Some(parent) = parent {
+        // Perform 3-way merge. First, for every key in parent,
+        // compare the parent value with the incoming value to compute
+        // an implicit "diff".
+        for (key, parent_value) in parent.into_iter() {
+            if let Some(incoming_value) = other.remove(&key) {
+                if incoming_value != parent_value {
+                    log::trace!(
+                        "merge: key {} was updated in incoming - copying value locally",
+                        key
+                    );
+                    let old_value = ours.remove(&key);
+                    let new_value = Some(incoming_value.clone());
+                    if old_value != new_value {
+                        changes.push(StorageValueChange {
+                            key: key.clone(),
+                            old_value,
+                            new_value,
+                        });
+                    }
+                    ours.insert(key, incoming_value);
+                }
+            } else {
+                // Key was not present in incoming value.
+                // Another client must have deleted it.
+                log::trace!(
+                    "merge: key {} no longer present in incoming - removing it locally",
+                    key
+                );
+                if let Some(old_value) = ours.remove(&key) {
+                    changes.push(StorageValueChange {
+                        key,
+                        old_value: Some(old_value),
+                        new_value: None,
+                    });
+                }
+            }
+        }
+
+        // Then, go through every remaining key in incoming. These are
+        // the ones where a corresponding key does not exist in
+        // parent, so it is a new key, and we need to add it.
+        for (key, incoming_value) in other.into_iter() {
+            log::trace!(
+                "merge: key {} doesn't occur in parent - copying from incoming",
+                key
+            );
+            changes.push(StorageValueChange {
+                key: key.clone(),
+                old_value: None,
+                new_value: Some(incoming_value.clone()),
+            });
+            ours.insert(key, incoming_value);
+        }
+    } else {
+        // No parent. Server wins. Overwrite every key in ours with
+        // the corresponding value in other.
+        log::trace!("merge: no parent - copying all keys from incoming");
+        for (key, incoming_value) in other.into_iter() {
+            let old_value = ours.remove(&key);
+            let new_value = Some(incoming_value.clone());
+            if old_value != new_value {
+                changes.push(StorageValueChange {
+                    key: key.clone(),
+                    old_value,
+                    new_value,
+                });
+            }
+            ours.insert(key, incoming_value);
+        }
+    }
+
+    if ours == old_incoming {
+        IncomingAction::TakeRemote {
+            ext_id,
+            data: old_incoming,
+            changes,
+        }
+    } else {
+        IncomingAction::Merge {
+            ext_id,
+            data: ours,
+            changes,
+        }
+    }
+}
+
+fn remove_matching_keys(mut ours: JsonMap, keys_to_remove: &JsonMap) -> (JsonMap, StorageChanges) {
+    let mut changes = StorageChanges::with_capacity(keys_to_remove.len());
+    for key in keys_to_remove.keys() {
+        if let Some(old_value) = ours.remove(key) {
+            changes.push(StorageValueChange {
+                key: key.clone(),
+                old_value: Some(old_value),
+                new_value: None,
+            });
+        }
+    }
+    (ours, changes)
+}
+
+/// Holds a JSON-serialized map of all synced changes for an extension.
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub struct SyncedExtensionChange {
+    /// The extension ID.
+    pub ext_id: String,
+    /// The contents of a `StorageChanges` struct, in JSON format. We don't
+    /// deserialize these because they need to be passed back to the browser
+    /// as strings anyway.
+    pub changes: String,
+}
+
+// Fetches the applied changes we stashed in the storage_sync_applied table.
+pub fn get_synced_changes(db: &StorageDb) -> Result<Vec<SyncedExtensionChange>> {
+    let signal = db.begin_interrupt_scope()?;
+    let sql = "SELECT ext_id, changes FROM temp.storage_sync_applied";
+    db.conn().query_rows_and_then(sql, [], |row| -> Result<_> {
+        signal.err_if_interrupted()?;
+        Ok(SyncedExtensionChange {
+            ext_id: row.get("ext_id")?,
+            changes: row.get("changes")?,
+        })
+    })
+}
+
+// Helpers for tests
+#[cfg(test)]
+pub mod test {
+    use crate::db::{test::new_mem_db, StorageDb};
+    use crate::schema::create_empty_sync_temp_tables;
+
+    pub fn new_syncable_mem_db() -> StorageDb {
+        let _ = env_logger::try_init();
+        let db = new_mem_db();
+        create_empty_sync_temp_tables(&db).expect("should work");
+        db
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::test::new_syncable_mem_db;
+    use super::*;
+    use serde_json::json;
+
+    #[test]
+    fn test_serde_record_ser() {
+        assert_eq!(
+            serde_json::to_string(&WebextRecord {
+                guid: "guid".into(),
+                ext_id: "ext_id".to_string(),
+                data: "data".to_string()
+            })
+            .unwrap(),
+            r#"{"id":"guid","extId":"ext_id","data":"data"}"#
+        );
+    }
+
+    // a macro for these tests - constructs a serde_json::Value::Object
+    macro_rules! map {
+        ($($map:tt)+) => {
+            json!($($map)+).as_object().unwrap().clone()
+        };
+    }
+
+    macro_rules! change {
+        ($key:literal, None, None) => {
+            StorageValueChange {
+                key: $key.to_string(),
+                old_value: None,
+                new_value: None,
+            };
+        };
+        ($key:literal, $old:tt, None) => {
+            StorageValueChange {
+                key: $key.to_string(),
+                old_value: Some(json!($old)),
+                new_value: None,
+            }
+        };
+        ($key:literal, None, $new:tt) => {
+            StorageValueChange {
+                key: $key.to_string(),
+                old_value: None,
+                new_value: Some(json!($new)),
+            }
+        };
+        ($key:literal, $old:tt, $new:tt) => {
+            StorageValueChange {
+                key: $key.to_string(),
+                old_value: Some(json!($old)),
+                new_value: Some(json!($new)),
+            }
+        };
+    }
+    macro_rules! changes {
+        ( ) => {
+            StorageChanges::new()
+        };
+        ( $( $change:expr ),* ) => {
+            {
+                let mut changes = StorageChanges::new();
+                $(
+                    changes.push($change);
+                )*
+                changes
+            }
+        };
+    }
+
+    #[test]
+    fn test_3way_merging() {
+        // No conflict - identical local and remote.
+        assert_eq!(
+            merge(
+                "ext-id".to_string(),
+                map!({"one": "one", "two": "two"}),
+                map!({"two": "two", "one": "one"}),
+                Some(map!({"parent_only": "parent"})),
+            ),
+            IncomingAction::Same {
+                ext_id: "ext-id".to_string()
+            }
+        );
+        assert_eq!(
+            merge(
+                "ext-id".to_string(),
+                map!({"other_only": "other", "common": "common"}),
+                map!({"ours_only": "ours", "common": "common"}),
+                Some(map!({"parent_only": "parent", "common": "old_common"})),
+            ),
+            IncomingAction::Merge {
+                ext_id: "ext-id".to_string(),
+                data: map!({"other_only": "other", "ours_only": "ours", "common": "common"}),
+                changes: changes![change!("other_only", None, "other")],
+            }
+        );
+        // Simple conflict - parent value is neither local nor incoming. incoming wins.
+        assert_eq!(
+            merge(
+                "ext-id".to_string(),
+                map!({"other_only": "other", "common": "incoming"}),
+                map!({"ours_only": "ours", "common": "local"}),
+                Some(map!({"parent_only": "parent", "common": "parent"})),
+            ),
+            IncomingAction::Merge {
+                ext_id: "ext-id".to_string(),
+                data: map!({"other_only": "other", "ours_only": "ours", "common": "incoming"}),
+                changes: changes![
+                    change!("common", "local", "incoming"),
+                    change!("other_only", None, "other")
+                ],
+            }
+        );
+        // Local change, no conflict.
+        assert_eq!(
+            merge(
+                "ext-id".to_string(),
+                map!({"other_only": "other", "common": "old_value"}),
+                map!({"ours_only": "ours", "common": "new_value"}),
+                Some(map!({"parent_only": "parent", "common": "old_value"})),
+            ),
+            IncomingAction::Merge {
+                ext_id: "ext-id".to_string(),
+                data: map!({"other_only": "other", "ours_only": "ours", "common": "new_value"}),
+                changes: changes![change!("other_only", None, "other")],
+            }
+        );
+        // Field was removed remotely.
+        assert_eq!(
+            merge(
+                "ext-id".to_string(),
+                map!({"other_only": "other"}),
+                map!({"common": "old_value"}),
+                Some(map!({"common": "old_value"})),
+            ),
+            IncomingAction::TakeRemote {
+                ext_id: "ext-id".to_string(),
+                data: map!({"other_only": "other"}),
+                changes: changes![
+                    change!("common", "old_value", None),
+                    change!("other_only", None, "other")
+                ],
+            }
+        );
+        // Field was removed remotely but we added another one.
+        assert_eq!(
+            merge(
+                "ext-id".to_string(),
+                map!({"other_only": "other"}),
+                map!({"common": "old_value", "new_key": "new_value"}),
+                Some(map!({"common": "old_value"})),
+            ),
+            IncomingAction::Merge {
+                ext_id: "ext-id".to_string(),
+                data: map!({"other_only": "other", "new_key": "new_value"}),
+                changes: changes![
+                    change!("common", "old_value", None),
+                    change!("other_only", None, "other")
+                ],
+            }
+        );
+        // Field was removed both remotely and locally.
+        assert_eq!(
+            merge(
+                "ext-id".to_string(),
+                map!({}),
+                map!({"new_key": "new_value"}),
+                Some(map!({"common": "old_value"})),
+            ),
+            IncomingAction::Merge {
+                ext_id: "ext-id".to_string(),
+                data: map!({"new_key": "new_value"}),
+                changes: changes![],
+            }
+        );
+    }
+
+    #[test]
+    fn test_remove_matching_keys() {
+        assert_eq!(
+            remove_matching_keys(
+                map!({"key1": "value1", "key2": "value2"}),
+                &map!({"key1": "ignored", "key3": "ignored"})
+            ),
+            (
+                map!({"key2": "value2"}),
+                changes![change!("key1", "value1", None)]
+            )
+        );
+    }
+
+    #[test]
+    fn test_get_synced_changes() -> Result<()> {
+        let db = new_syncable_mem_db();
+        db.execute_batch(&format!(
+            r#"INSERT INTO temp.storage_sync_applied (ext_id, changes)
+                VALUES
+                ('an-extension', '{change1}'),
+                ('ext"id', '{change2}')
+            "#,
+            change1 = serde_json::to_string(&changes![change!("key1", "old-val", None)])?,
+            change2 = serde_json::to_string(&changes![change!("key-for-second", None, "new-val")])?
+        ))?;
+        let changes = get_synced_changes(&db)?;
+        assert_eq!(changes[0].ext_id, "an-extension");
+        // sanity check it's valid!
+        let c1: JsonMap =
+            serde_json::from_str(&changes[0].changes).expect("changes must be an object");
+        assert_eq!(
+            c1.get("key1")
+                .expect("must exist")
+                .as_object()
+                .expect("must be an object")
+                .get("oldValue"),
+            Some(&json!("old-val"))
+        );
+
+        // phew - do it again to check the string got escaped.
+        assert_eq!(
+            changes[1],
+            SyncedExtensionChange {
+                ext_id: "ext\"id".into(),
+                changes: r#"{"key-for-second":{"newValue":"new-val"}}"#.into(),
+            }
+        );
+        assert_eq!(changes[1].ext_id, "ext\"id");
+        let c2: JsonMap =
+            serde_json::from_str(&changes[1].changes).expect("changes must be an object");
+        assert_eq!(
+            c2.get("key-for-second")
+                .expect("must exist")
+                .as_object()
+                .expect("must be an object")
+                .get("newValue"),
+            Some(&json!("new-val"))
+        );
+        Ok(())
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/src/webext_storage/sync/outgoing.rs.html b/book/rust-docs/src/webext_storage/sync/outgoing.rs.html new file mode 100644 index 0000000000..ff31474cf0 --- /dev/null +++ b/book/rust-docs/src/webext_storage/sync/outgoing.rs.html @@ -0,0 +1,373 @@ +outgoing.rs - source
1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+
/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// The "outgoing" part of syncing - building the payloads to upload and
+// managing the sync state of the local DB.
+
+use interrupt_support::Interruptee;
+use rusqlite::{Connection, Row, Transaction};
+use sql_support::ConnExt;
+use sync15::bso::OutgoingBso;
+use sync_guid::Guid as SyncGuid;
+
+use crate::error::*;
+
+use super::WebextRecord;
+
+fn outgoing_from_row(row: &Row<'_>) -> Result<OutgoingBso> {
+    let guid: SyncGuid = row.get("guid")?;
+    let ext_id: String = row.get("ext_id")?;
+    let raw_data: Option<String> = row.get("data")?;
+    Ok(match raw_data {
+        Some(raw_data) => {
+            let record = WebextRecord {
+                guid,
+                ext_id,
+                data: raw_data,
+            };
+            OutgoingBso::from_content_with_id(record)?
+        }
+        None => OutgoingBso::new_tombstone(guid.into()),
+    })
+}
+
+/// Stages info about what should be uploaded in a temp table. This should be
+/// called in the same transaction as `apply_actions`. record_uploaded() can be
+/// called after the upload is complete and the data in the temp table will be
+/// used to update the local store.
+pub fn stage_outgoing(tx: &Transaction<'_>) -> Result<()> {
+    let sql = "
+        -- Stage outgoing items. The item may not yet have a GUID (ie, it might
+        -- not already be in either the mirror nor the incoming staging table),
+        -- so we generate one if it doesn't exist.
+        INSERT INTO storage_sync_outgoing_staging
+        (guid, ext_id, data, sync_change_counter)
+        SELECT coalesce(m.guid, s.guid, generate_guid()),
+        l.ext_id, l.data, l.sync_change_counter
+        FROM storage_sync_data l
+        -- left joins as one or both may not exist.
+        LEFT JOIN storage_sync_mirror m ON m.ext_id = l.ext_id
+        LEFT JOIN storage_sync_staging s ON s.ext_id = l.ext_id
+        WHERE sync_change_counter > 0;
+
+        -- At this point, we've merged in all new records, so copy incoming
+        -- staging into the mirror so that it matches what's on the server.
+        INSERT OR REPLACE INTO storage_sync_mirror (guid, ext_id, data)
+        SELECT guid, ext_id, data FROM temp.storage_sync_staging;
+
+        -- And copy any incoming records that we aren't reuploading into the
+        -- local table. We'll copy the outgoing ones into the mirror and local
+        -- after we upload them.
+        INSERT OR REPLACE INTO storage_sync_data (ext_id, data, sync_change_counter)
+        SELECT ext_id, data, 0
+        FROM storage_sync_staging s
+        WHERE ext_id IS NOT NULL
+        AND NOT EXISTS(SELECT 1 FROM storage_sync_outgoing_staging o
+                       WHERE o.guid = s.guid);";
+    tx.execute_batch(sql)?;
+    Ok(())
+}
+
+/// Returns a vec of the outgoing records which should be uploaded.
+pub fn get_outgoing(conn: &Connection, signal: &dyn Interruptee) -> Result<Vec<OutgoingBso>> {
+    let sql = "SELECT guid, ext_id, data FROM storage_sync_outgoing_staging";
+    let elts = conn
+        .conn()
+        .query_rows_and_then(sql, [], |row| -> Result<_> {
+            signal.err_if_interrupted()?;
+            outgoing_from_row(row)
+        })?;
+
+    log::debug!("get_outgoing found {} items", elts.len());
+    Ok(elts.into_iter().collect())
+}
+
+/// Record the fact that items were uploaded. This updates the state of the
+/// local DB to reflect the state of the server we just updated.
+/// Note that this call is almost certainly going to be made in a *different*
+/// transaction than the transaction used in `stage_outgoing()`, and it will
+/// be called once per batch upload.
+pub fn record_uploaded(
+    tx: &Transaction<'_>,
+    items: &[SyncGuid],
+    signal: &dyn Interruptee,
+) -> Result<()> {
+    log::debug!(
+        "record_uploaded recording that {} items were uploaded",
+        items.len()
+    );
+
+    // Updating the `was_uploaded` column fires the `record_uploaded` trigger,
+    // which updates the local change counter and writes the uploaded record
+    // data back to the mirror.
+    sql_support::each_chunk(items, |chunk, _| -> Result<()> {
+        signal.err_if_interrupted()?;
+        let sql = format!(
+            "UPDATE storage_sync_outgoing_staging SET
+                 was_uploaded = 1
+             WHERE guid IN ({})",
+            sql_support::repeat_sql_vars(chunk.len()),
+        );
+        tx.execute(&sql, rusqlite::params_from_iter(chunk))?;
+        Ok(())
+    })?;
+
+    Ok(())
+}
+
+#[cfg(test)]
+mod tests {
+    use super::super::test::new_syncable_mem_db;
+    use super::*;
+    use interrupt_support::NeverInterrupts;
+
+    #[test]
+    fn test_simple() -> Result<()> {
+        let mut db = new_syncable_mem_db();
+        let tx = db.transaction()?;
+
+        tx.execute_batch(
+            r#"
+            INSERT INTO storage_sync_data (ext_id, data, sync_change_counter)
+            VALUES
+                ('ext_no_changes', '{"foo":"bar"}', 0),
+                ('ext_with_changes', '{"foo":"bar"}', 1);
+        "#,
+        )?;
+
+        stage_outgoing(&tx)?;
+        let changes = get_outgoing(&tx, &NeverInterrupts)?;
+        assert_eq!(changes.len(), 1);
+        let record: serde_json::Value = serde_json::from_str(&changes[0].payload).unwrap();
+        let ext_id = record.get("extId").unwrap().as_str().unwrap();
+
+        assert_eq!(ext_id, "ext_with_changes");
+
+        record_uploaded(
+            &tx,
+            changes
+                .into_iter()
+                .map(|p| p.envelope.id)
+                .collect::<Vec<SyncGuid>>()
+                .as_slice(),
+            &NeverInterrupts,
+        )?;
+
+        let counter: i32 = tx.conn().query_one(
+            "SELECT sync_change_counter FROM storage_sync_data WHERE ext_id = 'ext_with_changes'",
+        )?;
+        assert_eq!(counter, 0);
+        Ok(())
+    }
+
+    #[test]
+    fn test_payload_serialization() {
+        let record = WebextRecord {
+            guid: SyncGuid::new("guid"),
+            ext_id: "ext-id".to_string(),
+            data: "{}".to_string(),
+        };
+
+        let outgoing = OutgoingBso::from_content_with_id(record).unwrap();
+
+        // The envelope should have our ID.
+        assert_eq!(outgoing.envelope.id, "guid");
+
+        let outgoing_payload =
+            serde_json::from_str::<serde_json::Value>(&outgoing.payload).unwrap();
+        let outgoing_map = outgoing_payload.as_object().unwrap();
+
+        assert!(outgoing_map.contains_key("id"));
+        assert!(outgoing_map.contains_key("data"));
+        assert!(outgoing_map.contains_key("extId"));
+        assert_eq!(outgoing_map.len(), 3);
+    }
+}
+
\ No newline at end of file diff --git a/book/rust-docs/static.files/COPYRIGHT-23e9bde6c69aea69.txt b/book/rust-docs/static.files/COPYRIGHT-23e9bde6c69aea69.txt new file mode 100644 index 0000000000..1447df792f --- /dev/null +++ b/book/rust-docs/static.files/COPYRIGHT-23e9bde6c69aea69.txt @@ -0,0 +1,50 @@ +# REUSE-IgnoreStart + +These documentation pages include resources by third parties. This copyright +file applies only to those resources. The following third party resources are +included, and carry their own copyright notices and license terms: + +* Fira Sans (FiraSans-Regular.woff2, FiraSans-Medium.woff2): + + Copyright (c) 2014, Mozilla Foundation https://mozilla.org/ + with Reserved Font Name Fira Sans. + + Copyright (c) 2014, Telefonica S.A. + + Licensed under the SIL Open Font License, Version 1.1. + See FiraSans-LICENSE.txt. + +* rustdoc.css, main.js, and playpen.js: + + Copyright 2015 The Rust Developers. + Licensed under the Apache License, Version 2.0 (see LICENSE-APACHE.txt) or + the MIT license (LICENSE-MIT.txt) at your option. + +* normalize.css: + + Copyright (c) Nicolas Gallagher and Jonathan Neal. + Licensed under the MIT license (see LICENSE-MIT.txt). + +* Source Code Pro (SourceCodePro-Regular.ttf.woff2, + SourceCodePro-Semibold.ttf.woff2, SourceCodePro-It.ttf.woff2): + + Copyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), + with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark + of Adobe Systems Incorporated in the United States and/or other countries. + + Licensed under the SIL Open Font License, Version 1.1. + See SourceCodePro-LICENSE.txt. + +* Source Serif 4 (SourceSerif4-Regular.ttf.woff2, SourceSerif4-Bold.ttf.woff2, + SourceSerif4-It.ttf.woff2): + + Copyright 2014-2021 Adobe (http://www.adobe.com/), with Reserved Font Name + 'Source'. All Rights Reserved. Source is a trademark of Adobe in the United + States and/or other countries. + + Licensed under the SIL Open Font License, Version 1.1. + See SourceSerif4-LICENSE.md. + +This copyright file is intended to be distributed with rustdoc output. + +# REUSE-IgnoreEnd diff --git a/book/rust-docs/static.files/FiraSans-LICENSE-db4b642586e02d97.txt b/book/rust-docs/static.files/FiraSans-LICENSE-db4b642586e02d97.txt new file mode 100644 index 0000000000..d7e9c149b7 --- /dev/null +++ b/book/rust-docs/static.files/FiraSans-LICENSE-db4b642586e02d97.txt @@ -0,0 +1,98 @@ +// REUSE-IgnoreStart + +Digitized data copyright (c) 2012-2015, The Mozilla Foundation and Telefonica S.A. +with Reserved Font Name < Fira >, + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. + +// REUSE-IgnoreEnd diff --git a/book/rust-docs/static.files/FiraSans-Medium-8f9a781e4970d388.woff2 b/book/rust-docs/static.files/FiraSans-Medium-8f9a781e4970d388.woff2 new file mode 100644 index 0000000000..7a1e5fc548 Binary files /dev/null and b/book/rust-docs/static.files/FiraSans-Medium-8f9a781e4970d388.woff2 differ diff --git a/book/rust-docs/static.files/FiraSans-Regular-018c141bf0843ffd.woff2 b/book/rust-docs/static.files/FiraSans-Regular-018c141bf0843ffd.woff2 new file mode 100644 index 0000000000..e766e06ccb Binary files /dev/null and b/book/rust-docs/static.files/FiraSans-Regular-018c141bf0843ffd.woff2 differ diff --git a/book/rust-docs/static.files/LICENSE-APACHE-b91fa81cba47b86a.txt b/book/rust-docs/static.files/LICENSE-APACHE-b91fa81cba47b86a.txt new file mode 100644 index 0000000000..16fe87b06e --- /dev/null +++ b/book/rust-docs/static.files/LICENSE-APACHE-b91fa81cba47b86a.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/book/rust-docs/static.files/LICENSE-MIT-65090b722b3f6c56.txt b/book/rust-docs/static.files/LICENSE-MIT-65090b722b3f6c56.txt new file mode 100644 index 0000000000..31aa79387f --- /dev/null +++ b/book/rust-docs/static.files/LICENSE-MIT-65090b722b3f6c56.txt @@ -0,0 +1,23 @@ +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/book/rust-docs/static.files/NanumBarunGothic-0f09457c7a19b7c6.ttf.woff2 b/book/rust-docs/static.files/NanumBarunGothic-0f09457c7a19b7c6.ttf.woff2 new file mode 100644 index 0000000000..1866ad4bce Binary files /dev/null and b/book/rust-docs/static.files/NanumBarunGothic-0f09457c7a19b7c6.ttf.woff2 differ diff --git a/book/rust-docs/static.files/NanumBarunGothic-LICENSE-18c5adf4b52b4041.txt b/book/rust-docs/static.files/NanumBarunGothic-LICENSE-18c5adf4b52b4041.txt new file mode 100644 index 0000000000..4b3edc29eb --- /dev/null +++ b/book/rust-docs/static.files/NanumBarunGothic-LICENSE-18c5adf4b52b4041.txt @@ -0,0 +1,103 @@ +// REUSE-IgnoreStart + +Copyright (c) 2010, NAVER Corporation (https://www.navercorp.com/), + +with Reserved Font Name Nanum, Naver Nanum, NanumGothic, Naver NanumGothic, +NanumMyeongjo, Naver NanumMyeongjo, NanumBrush, Naver NanumBrush, NanumPen, +Naver NanumPen, Naver NanumGothicEco, NanumGothicEco, Naver NanumMyeongjoEco, +NanumMyeongjoEco, Naver NanumGothicLight, NanumGothicLight, NanumBarunGothic, +Naver NanumBarunGothic, NanumSquareRound, NanumBarunPen, MaruBuri + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. + +// REUSE-IgnoreEnd diff --git a/book/rust-docs/static.files/SourceCodePro-It-1cc31594bf4f1f79.ttf.woff2 b/book/rust-docs/static.files/SourceCodePro-It-1cc31594bf4f1f79.ttf.woff2 new file mode 100644 index 0000000000..462c34efcd Binary files /dev/null and b/book/rust-docs/static.files/SourceCodePro-It-1cc31594bf4f1f79.ttf.woff2 differ diff --git a/book/rust-docs/static.files/SourceCodePro-LICENSE-d180d465a756484a.txt b/book/rust-docs/static.files/SourceCodePro-LICENSE-d180d465a756484a.txt new file mode 100644 index 0000000000..0d2941e148 --- /dev/null +++ b/book/rust-docs/static.files/SourceCodePro-LICENSE-d180d465a756484a.txt @@ -0,0 +1,97 @@ +// REUSE-IgnoreStart + +Copyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries. + +This Font Software is licensed under the SIL Open Font License, Version 1.1. + +This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. + +// REUSE-IgnoreEnd diff --git a/book/rust-docs/static.files/SourceCodePro-Regular-562dcc5011b6de7d.ttf.woff2 b/book/rust-docs/static.files/SourceCodePro-Regular-562dcc5011b6de7d.ttf.woff2 new file mode 100644 index 0000000000..10b558e0b6 Binary files /dev/null and b/book/rust-docs/static.files/SourceCodePro-Regular-562dcc5011b6de7d.ttf.woff2 differ diff --git a/book/rust-docs/static.files/SourceCodePro-Semibold-d899c5a5c4aeb14a.ttf.woff2 b/book/rust-docs/static.files/SourceCodePro-Semibold-d899c5a5c4aeb14a.ttf.woff2 new file mode 100644 index 0000000000..5ec64eef0e Binary files /dev/null and b/book/rust-docs/static.files/SourceCodePro-Semibold-d899c5a5c4aeb14a.ttf.woff2 differ diff --git a/book/rust-docs/static.files/SourceSerif4-Bold-a2c9cd1067f8b328.ttf.woff2 b/book/rust-docs/static.files/SourceSerif4-Bold-a2c9cd1067f8b328.ttf.woff2 new file mode 100644 index 0000000000..181a07f63b Binary files /dev/null and b/book/rust-docs/static.files/SourceSerif4-Bold-a2c9cd1067f8b328.ttf.woff2 differ diff --git a/book/rust-docs/static.files/SourceSerif4-It-acdfaf1a8af734b1.ttf.woff2 b/book/rust-docs/static.files/SourceSerif4-It-acdfaf1a8af734b1.ttf.woff2 new file mode 100644 index 0000000000..2ae08a7bed Binary files /dev/null and b/book/rust-docs/static.files/SourceSerif4-It-acdfaf1a8af734b1.ttf.woff2 differ diff --git a/book/rust-docs/static.files/SourceSerif4-Regular-46f98efaafac5295.ttf.woff2 b/book/rust-docs/static.files/SourceSerif4-Regular-46f98efaafac5295.ttf.woff2 new file mode 100644 index 0000000000..0263fc3042 Binary files /dev/null and b/book/rust-docs/static.files/SourceSerif4-Regular-46f98efaafac5295.ttf.woff2 differ diff --git a/book/rust-docs/static.files/ayu-614652228113ac93.css b/book/rust-docs/static.files/ayu-614652228113ac93.css new file mode 100644 index 0000000000..8fd09c9e44 --- /dev/null +++ b/book/rust-docs/static.files/ayu-614652228113ac93.css @@ -0,0 +1 @@ + :root{--main-background-color:#0f1419;--main-color:#c5c5c5;--settings-input-color:#ffb454;--settings-input-border-color:#999;--settings-button-color:#fff;--settings-button-border-focus:#e0e0e0;--sidebar-background-color:#14191f;--sidebar-background-color-hover:rgba(70,70,70,0.33);--code-block-background-color:#191f26;--scrollbar-track-background-color:transparent;--scrollbar-thumb-background-color:#5c6773;--scrollbar-color:#5c6773 #24292f;--headings-border-bottom-color:#5c6773;--border-color:#5c6773;--button-background-color:#141920;--right-side-color:grey;--code-attribute-color:#999;--toggles-color:#999;--toggle-filter:invert(100%);--search-input-focused-border-color:#5c6773;--copy-path-button-color:#fff;--copy-path-img-filter:invert(70%);--copy-path-img-hover-filter:invert(100%);--codeblock-error-hover-color:rgb(255,0,0);--codeblock-error-color:rgba(255,0,0,.5);--codeblock-ignore-hover-color:rgb(255,142,0);--codeblock-ignore-color:rgba(255,142,0,.6);--type-link-color:#ffa0a5;--trait-link-color:#39afd7;--assoc-item-link-color:#39afd7;--function-link-color:#fdd687;--macro-link-color:#a37acc;--keyword-link-color:#39afd7;--mod-link-color:#39afd7;--link-color:#39afd7;--sidebar-link-color:#53b1db;--sidebar-current-link-background-color:transparent;--search-result-link-focus-background-color:#3c3c3c;--search-result-border-color:#aaa3;--search-color:#fff;--search-error-code-background-color:#4f4c4c;--search-results-alias-color:#c5c5c5;--search-results-grey-color:#999;--search-tab-title-count-color:#888;--search-tab-button-not-selected-border-top-color:none;--search-tab-button-not-selected-background:transparent !important;--search-tab-button-selected-border-top-color:none;--search-tab-button-selected-background:#141920 !important;--stab-background-color:#314559;--stab-code-color:#e6e1cf;--code-highlight-kw-color:#ff7733;--code-highlight-kw-2-color:#ff7733;--code-highlight-lifetime-color:#ff7733;--code-highlight-prelude-color:#69f2df;--code-highlight-prelude-val-color:#ff7733;--code-highlight-number-color:#b8cc52;--code-highlight-string-color:#b8cc52;--code-highlight-literal-color:#ff7733;--code-highlight-attribute-color:#e6e1cf;--code-highlight-self-color:#36a3d9;--code-highlight-macro-color:#a37acc;--code-highlight-question-mark-color:#ff9011;--code-highlight-comment-color:#788797;--code-highlight-doc-comment-color:#a1ac88;--src-line-numbers-span-color:#5c6773;--src-line-number-highlighted-background-color:rgba(255,236,164,0.06);--test-arrow-color:#788797;--test-arrow-background-color:rgba(57,175,215,0.09);--test-arrow-hover-color:#c5c5c5;--test-arrow-hover-background-color:rgba(57,175,215,0.368);--target-background-color:rgba(255,236,164,0.06);--target-border-color:rgba(255,180,76,0.85);--kbd-color:#c5c5c5;--kbd-background:#314559;--kbd-box-shadow-color:#5c6773;--rust-logo-filter:drop-shadow(1px 0 0px #fff) drop-shadow(0 1px 0 #fff) drop-shadow(-1px 0 0 #fff) drop-shadow(0 -1px 0 #fff);--crate-search-div-filter:invert(41%) sepia(12%) saturate(487%) hue-rotate(171deg) brightness(94%) contrast(94%);--crate-search-div-hover-filter:invert(98%) sepia(12%) saturate(81%) hue-rotate(343deg) brightness(113%) contrast(76%);--crate-search-hover-border:#e0e0e0;--source-sidebar-background-selected:#14191f;--source-sidebar-background-hover:#14191f;--table-alt-row-background-color:#191f26;--codeblock-link-background:#333;--scrape-example-toggle-line-background:#999;--scrape-example-toggle-line-hover-background:#c5c5c5;--scrape-example-code-line-highlight:rgb(91,59,1);--scrape-example-code-line-highlight-focus:rgb(124,75,15);--scrape-example-help-border-color:#aaa;--scrape-example-help-color:#eee;--scrape-example-help-hover-border-color:#fff;--scrape-example-help-hover-color:#fff;--scrape-example-code-wrapper-background-start:rgba(15,20,25,1);--scrape-example-code-wrapper-background-end:rgba(15,20,25,0);}h1,h2,h3,h4,h1 a,.sidebar h2 a,.sidebar h3 a,#source-sidebar>.title{color:#fff;}h4{border:none;}.docblock code{color:#ffb454;}.docblock a>code{color:#39AFD7 !important;}.code-header,.docblock pre>code,pre,pre>code,.item-info code,.rustdoc.source .example-wrap{color:#e6e1cf;}.sidebar .current,.sidebar a:hover,#source-sidebar div.files>a:hover,details.dir-entry summary:hover,#source-sidebar div.files>a:focus,details.dir-entry summary:focus,#source-sidebar div.files>a.selected{color:#ffb44c;}.sidebar-elems .location{color:#ff7733;}.src-line-numbers .line-highlighted{color:#708090;padding-right:7px;border-right:1px solid #ffb44c;}.search-results a:hover,.search-results a:focus{color:#fff !important;background-color:#3c3c3c;}.search-results a{color:#0096cf;}.search-results a div.desc{color:#c5c5c5;}.result-name .primitive>i,.result-name .keyword>i{color:#788797;}#search-tabs>button.selected{border-bottom:1px solid #ffb44c !important;border-top:none;}#search-tabs>button:not(.selected){border:none;background-color:transparent !important;}#search-tabs>button:hover{border-bottom:1px solid rgba(242,151,24,0.3);}#settings-menu>a img{filter:invert(100);} \ No newline at end of file diff --git a/book/rust-docs/static.files/clipboard-7571035ce49a181d.svg b/book/rust-docs/static.files/clipboard-7571035ce49a181d.svg new file mode 100644 index 0000000000..8adbd99630 --- /dev/null +++ b/book/rust-docs/static.files/clipboard-7571035ce49a181d.svg @@ -0,0 +1 @@ + diff --git a/book/rust-docs/static.files/dark-1097f8e92a01e3cf.css b/book/rust-docs/static.files/dark-1097f8e92a01e3cf.css new file mode 100644 index 0000000000..1e5e7d1944 --- /dev/null +++ b/book/rust-docs/static.files/dark-1097f8e92a01e3cf.css @@ -0,0 +1 @@ +:root{--main-background-color:#353535;--main-color:#ddd;--settings-input-color:#2196f3;--settings-input-border-color:#999;--settings-button-color:#000;--settings-button-border-focus:#ffb900;--sidebar-background-color:#505050;--sidebar-background-color-hover:#676767;--code-block-background-color:#2A2A2A;--scrollbar-track-background-color:#717171;--scrollbar-thumb-background-color:rgba(32,34,37,.6);--scrollbar-color:rgba(32,34,37,.6) #5a5a5a;--headings-border-bottom-color:#d2d2d2;--border-color:#e0e0e0;--button-background-color:#f0f0f0;--right-side-color:grey;--code-attribute-color:#999;--toggles-color:#999;--toggle-filter:invert(100%);--search-input-focused-border-color:#008dfd;--copy-path-button-color:#999;--copy-path-img-filter:invert(50%);--copy-path-img-hover-filter:invert(65%);--codeblock-error-hover-color:rgb(255,0,0);--codeblock-error-color:rgba(255,0,0,.5);--codeblock-ignore-hover-color:rgb(255,142,0);--codeblock-ignore-color:rgba(255,142,0,.6);--type-link-color:#2dbfb8;--trait-link-color:#b78cf2;--assoc-item-link-color:#d2991d;--function-link-color:#2bab63;--macro-link-color:#09bd00;--keyword-link-color:#d2991d;--mod-link-color:#d2991d;--link-color:#d2991d;--sidebar-link-color:#fdbf35;--sidebar-current-link-background-color:#444;--search-result-link-focus-background-color:#616161;--search-result-border-color:#aaa3;--search-color:#111;--search-error-code-background-color:#484848;--search-results-alias-color:#fff;--search-results-grey-color:#ccc;--search-tab-title-count-color:#888;--search-tab-button-not-selected-border-top-color:#252525;--search-tab-button-not-selected-background:#252525;--search-tab-button-selected-border-top-color:#0089ff;--search-tab-button-selected-background:#353535;--stab-background-color:#314559;--stab-code-color:#e6e1cf;--code-highlight-kw-color:#ab8ac1;--code-highlight-kw-2-color:#769acb;--code-highlight-lifetime-color:#d97f26;--code-highlight-prelude-color:#769acb;--code-highlight-prelude-val-color:#ee6868;--code-highlight-number-color:#83a300;--code-highlight-string-color:#83a300;--code-highlight-literal-color:#ee6868;--code-highlight-attribute-color:#ee6868;--code-highlight-self-color:#ee6868;--code-highlight-macro-color:#3e999f;--code-highlight-question-mark-color:#ff9011;--code-highlight-comment-color:#8d8d8b;--code-highlight-doc-comment-color:#8ca375;--src-line-numbers-span-color:#3b91e2;--src-line-number-highlighted-background-color:#0a042f;--test-arrow-color:#dedede;--test-arrow-background-color:rgba(78,139,202,0.2);--test-arrow-hover-color:#dedede;--test-arrow-hover-background-color:#4e8bca;--target-background-color:#494a3d;--target-border-color:#bb7410;--kbd-color:#000;--kbd-background:#fafbfc;--kbd-box-shadow-color:#c6cbd1;--rust-logo-filter:drop-shadow(1px 0 0px #fff) drop-shadow(0 1px 0 #fff) drop-shadow(-1px 0 0 #fff) drop-shadow(0 -1px 0 #fff);--crate-search-div-filter:invert(94%) sepia(0%) saturate(721%) hue-rotate(255deg) brightness(90%) contrast(90%);--crate-search-div-hover-filter:invert(69%) sepia(60%) saturate(6613%) hue-rotate(184deg) brightness(100%) contrast(91%);--crate-search-hover-border:#2196f3;--source-sidebar-background-selected:#333;--source-sidebar-background-hover:#444;--table-alt-row-background-color:#2A2A2A;--codeblock-link-background:#333;--scrape-example-toggle-line-background:#999;--scrape-example-toggle-line-hover-background:#c5c5c5;--scrape-example-code-line-highlight:rgb(91,59,1);--scrape-example-code-line-highlight-focus:rgb(124,75,15);--scrape-example-help-border-color:#aaa;--scrape-example-help-color:#eee;--scrape-example-help-hover-border-color:#fff;--scrape-example-help-hover-color:#fff;--scrape-example-code-wrapper-background-start:rgba(53,53,53,1);--scrape-example-code-wrapper-background-end:rgba(53,53,53,0);} \ No newline at end of file diff --git a/book/rust-docs/static.files/favicon-16x16-8b506e7a72182f1c.png b/book/rust-docs/static.files/favicon-16x16-8b506e7a72182f1c.png new file mode 100644 index 0000000000..ea4b45cae1 Binary files /dev/null and b/book/rust-docs/static.files/favicon-16x16-8b506e7a72182f1c.png differ diff --git a/book/rust-docs/static.files/favicon-2c020d218678b618.svg b/book/rust-docs/static.files/favicon-2c020d218678b618.svg new file mode 100644 index 0000000000..8b34b51198 --- /dev/null +++ b/book/rust-docs/static.files/favicon-2c020d218678b618.svg @@ -0,0 +1,24 @@ + + + + + diff --git a/book/rust-docs/static.files/favicon-32x32-422f7d1d52889060.png b/book/rust-docs/static.files/favicon-32x32-422f7d1d52889060.png new file mode 100644 index 0000000000..69b8613ce1 Binary files /dev/null and b/book/rust-docs/static.files/favicon-32x32-422f7d1d52889060.png differ diff --git a/book/rust-docs/static.files/light-0f8c037637f9eb3e.css b/book/rust-docs/static.files/light-0f8c037637f9eb3e.css new file mode 100644 index 0000000000..21c3a859b8 --- /dev/null +++ b/book/rust-docs/static.files/light-0f8c037637f9eb3e.css @@ -0,0 +1 @@ +:root{--main-background-color:white;--main-color:black;--settings-input-color:#2196f3;--settings-input-border-color:#717171;--settings-button-color:#000;--settings-button-border-focus:#717171;--sidebar-background-color:#F5F5F5;--sidebar-background-color-hover:#E0E0E0;--code-block-background-color:#F5F5F5;--scrollbar-track-background-color:#dcdcdc;--scrollbar-thumb-background-color:rgba(36,37,39,0.6);--scrollbar-color:rgba(36,37,39,0.6) #d9d9d9;--headings-border-bottom-color:#ddd;--border-color:#e0e0e0;--button-background-color:#fff;--right-side-color:grey;--code-attribute-color:#999;--toggles-color:#999;--toggle-filter:none;--search-input-focused-border-color:#66afe9;--copy-path-button-color:#999;--copy-path-img-filter:invert(50%);--copy-path-img-hover-filter:invert(35%);--codeblock-error-hover-color:rgb(255,0,0);--codeblock-error-color:rgba(255,0,0,.5);--codeblock-ignore-hover-color:rgb(255,142,0);--codeblock-ignore-color:rgba(255,142,0,.6);--type-link-color:#ad378a;--trait-link-color:#6e4fc9;--assoc-item-link-color:#3873ad;--function-link-color:#ad7c37;--macro-link-color:#068000;--keyword-link-color:#3873ad;--mod-link-color:#3873ad;--link-color:#3873ad;--sidebar-link-color:#356da4;--sidebar-current-link-background-color:#fff;--search-result-link-focus-background-color:#ccc;--search-result-border-color:#aaa3;--search-color:#000;--search-error-code-background-color:#d0cccc;--search-results-alias-color:#000;--search-results-grey-color:#999;--search-tab-title-count-color:#888;--search-tab-button-not-selected-border-top-color:#e6e6e6;--search-tab-button-not-selected-background:#e6e6e6;--search-tab-button-selected-border-top-color:#0089ff;--search-tab-button-selected-background:#ffffff;--stab-background-color:#fff5d6;--stab-code-color:#000;--code-highlight-kw-color:#8959a8;--code-highlight-kw-2-color:#4271ae;--code-highlight-lifetime-color:#b76514;--code-highlight-prelude-color:#4271ae;--code-highlight-prelude-val-color:#c82829;--code-highlight-number-color:#718c00;--code-highlight-string-color:#718c00;--code-highlight-literal-color:#c82829;--code-highlight-attribute-color:#c82829;--code-highlight-self-color:#c82829;--code-highlight-macro-color:#3e999f;--code-highlight-question-mark-color:#ff9011;--code-highlight-comment-color:#8e908c;--code-highlight-doc-comment-color:#4d4d4c;--src-line-numbers-span-color:#c67e2d;--src-line-number-highlighted-background-color:#fdffd3;--test-arrow-color:#f5f5f5;--test-arrow-background-color:rgba(78,139,202,0.2);--test-arrow-hover-color:#f5f5f5;--test-arrow-hover-background-color:#4e8bca;--target-background-color:#fdffd3;--target-border-color:#ad7c37;--kbd-color:#000;--kbd-background:#fafbfc;--kbd-box-shadow-color:#c6cbd1;--rust-logo-filter:initial;--crate-search-div-filter:invert(100%) sepia(0%) saturate(4223%) hue-rotate(289deg) brightness(114%) contrast(76%);--crate-search-div-hover-filter:invert(44%) sepia(18%) saturate(23%) hue-rotate(317deg) brightness(96%) contrast(93%);--crate-search-hover-border:#717171;--source-sidebar-background-selected:#fff;--source-sidebar-background-hover:#e0e0e0;--table-alt-row-background-color:#F5F5F5;--codeblock-link-background:#eee;--scrape-example-toggle-line-background:#ccc;--scrape-example-toggle-line-hover-background:#999;--scrape-example-code-line-highlight:#fcffd6;--scrape-example-code-line-highlight-focus:#f6fdb0;--scrape-example-help-border-color:#555;--scrape-example-help-color:#333;--scrape-example-help-hover-border-color:#000;--scrape-example-help-hover-color:#000;--scrape-example-code-wrapper-background-start:rgba(255,255,255,1);--scrape-example-code-wrapper-background-end:rgba(255,255,255,0);} \ No newline at end of file diff --git a/book/rust-docs/static.files/main-0795b7d26be81095.js b/book/rust-docs/static.files/main-0795b7d26be81095.js new file mode 100644 index 0000000000..87b4338982 --- /dev/null +++ b/book/rust-docs/static.files/main-0795b7d26be81095.js @@ -0,0 +1,12 @@ +"use strict";window.RUSTDOC_TOOLTIP_HOVER_MS=300;window.RUSTDOC_TOOLTIP_HOVER_EXIT_MS=450;function resourcePath(basename,extension){return getVar("root-path")+basename+getVar("resource-suffix")+extension}function hideMain(){addClass(document.getElementById(MAIN_ID),"hidden")}function showMain(){removeClass(document.getElementById(MAIN_ID),"hidden")}function elemIsInParent(elem,parent){while(elem&&elem!==document.body){if(elem===parent){return true}elem=elem.parentElement}return false}function blurHandler(event,parentElem,hideCallback){if(!elemIsInParent(document.activeElement,parentElem)&&!elemIsInParent(event.relatedTarget,parentElem)){hideCallback()}}window.rootPath=getVar("root-path");window.currentCrate=getVar("current-crate");function setMobileTopbar(){const mobileLocationTitle=document.querySelector(".mobile-topbar h2");const locationTitle=document.querySelector(".sidebar h2.location");if(mobileLocationTitle&&locationTitle){mobileLocationTitle.innerHTML=locationTitle.innerHTML}}function getVirtualKey(ev){if("key"in ev&&typeof ev.key!=="undefined"){return ev.key}const c=ev.charCode||ev.keyCode;if(c===27){return"Escape"}return String.fromCharCode(c)}const MAIN_ID="main-content";const SETTINGS_BUTTON_ID="settings-menu";const ALTERNATIVE_DISPLAY_ID="alternative-display";const NOT_DISPLAYED_ID="not-displayed";const HELP_BUTTON_ID="help-button";function getSettingsButton(){return document.getElementById(SETTINGS_BUTTON_ID)}function getHelpButton(){return document.getElementById(HELP_BUTTON_ID)}function getNakedUrl(){return window.location.href.split("?")[0].split("#")[0]}function insertAfter(newNode,referenceNode){referenceNode.parentNode.insertBefore(newNode,referenceNode.nextSibling)}function getOrCreateSection(id,classes){let el=document.getElementById(id);if(!el){el=document.createElement("section");el.id=id;el.className=classes;insertAfter(el,document.getElementById(MAIN_ID))}return el}function getAlternativeDisplayElem(){return getOrCreateSection(ALTERNATIVE_DISPLAY_ID,"content hidden")}function getNotDisplayedElem(){return getOrCreateSection(NOT_DISPLAYED_ID,"hidden")}function switchDisplayedElement(elemToDisplay){const el=getAlternativeDisplayElem();if(el.children.length>0){getNotDisplayedElem().appendChild(el.firstElementChild)}if(elemToDisplay===null){addClass(el,"hidden");showMain();return}el.appendChild(elemToDisplay);hideMain();removeClass(el,"hidden")}function browserSupportsHistoryApi(){return window.history&&typeof window.history.pushState==="function"}function loadCss(cssUrl){const link=document.createElement("link");link.href=cssUrl;link.rel="stylesheet";document.getElementsByTagName("head")[0].appendChild(link)}function preLoadCss(cssUrl){const link=document.createElement("link");link.href=cssUrl;link.rel="preload";link.as="style";document.getElementsByTagName("head")[0].appendChild(link)}(function(){const isHelpPage=window.location.pathname.endsWith("/help.html");function loadScript(url){const script=document.createElement("script");script.src=url;document.head.append(script)}getSettingsButton().onclick=event=>{if(event.ctrlKey||event.altKey||event.metaKey){return}window.hideAllModals(false);addClass(getSettingsButton(),"rotate");event.preventDefault();loadCss(getVar("static-root-path")+getVar("settings-css"));loadScript(getVar("static-root-path")+getVar("settings-js"));preLoadCss(getVar("static-root-path")+getVar("theme-light-css"));preLoadCss(getVar("static-root-path")+getVar("theme-dark-css"));preLoadCss(getVar("static-root-path")+getVar("theme-ayu-css"));setTimeout(()=>{const themes=getVar("themes").split(",");for(const theme of themes){if(theme!==""){preLoadCss(getVar("root-path")+theme+".css")}}},0)};window.searchState={loadingText:"Loading search results...",input:document.getElementsByClassName("search-input")[0],outputElement:()=>{let el=document.getElementById("search");if(!el){el=document.createElement("section");el.id="search";getNotDisplayedElem().appendChild(el)}return el},title:document.title,titleBeforeSearch:document.title,timeout:null,currentTab:0,focusedByTab:[null,null,null],clearInputTimeout:()=>{if(searchState.timeout!==null){clearTimeout(searchState.timeout);searchState.timeout=null}},isDisplayed:()=>searchState.outputElement().parentElement.id===ALTERNATIVE_DISPLAY_ID,focus:()=>{searchState.input.focus()},defocus:()=>{searchState.input.blur()},showResults:search=>{if(search===null||typeof search==="undefined"){search=searchState.outputElement()}switchDisplayedElement(search);searchState.mouseMovedAfterSearch=false;document.title=searchState.title},removeQueryParameters:()=>{document.title=searchState.titleBeforeSearch;if(browserSupportsHistoryApi()){history.replaceState(null,"",getNakedUrl()+window.location.hash)}},hideResults:()=>{switchDisplayedElement(null);searchState.removeQueryParameters()},getQueryStringParams:()=>{const params={};window.location.search.substring(1).split("&").map(s=>{const pair=s.split("=");params[decodeURIComponent(pair[0])]=typeof pair[1]==="undefined"?null:decodeURIComponent(pair[1])});return params},setup:()=>{const search_input=searchState.input;if(!searchState.input){return}let searchLoaded=false;function loadSearch(){if(!searchLoaded){searchLoaded=true;loadScript(getVar("static-root-path")+getVar("search-js"));loadScript(resourcePath("search-index",".js"))}}search_input.addEventListener("focus",()=>{search_input.origPlaceholder=search_input.placeholder;search_input.placeholder="Type your search here.";loadSearch()});if(search_input.value!==""){loadSearch()}const params=searchState.getQueryStringParams();if(params.search!==undefined){searchState.setLoadingSearch();loadSearch()}},setLoadingSearch:()=>{const search=searchState.outputElement();search.innerHTML="

"+searchState.loadingText+"

";searchState.showResults(search)},};const toggleAllDocsId="toggle-all-docs";let savedHash="";function handleHashes(ev){if(ev!==null&&searchState.isDisplayed()&&ev.newURL){switchDisplayedElement(null);const hash=ev.newURL.slice(ev.newURL.indexOf("#")+1);if(browserSupportsHistoryApi()){history.replaceState(null,"",getNakedUrl()+window.location.search+"#"+hash)}const elem=document.getElementById(hash);if(elem){elem.scrollIntoView()}}const pageId=window.location.hash.replace(/^#/,"");if(savedHash!==pageId){savedHash=pageId;if(pageId!==""){expandSection(pageId)}}}function onHashChange(ev){hideSidebar();handleHashes(ev)}function openParentDetails(elem){while(elem){if(elem.tagName==="DETAILS"){elem.open=true}elem=elem.parentNode}}function expandSection(id){openParentDetails(document.getElementById(id))}function handleEscape(ev){searchState.clearInputTimeout();searchState.hideResults();ev.preventDefault();searchState.defocus();window.hideAllModals(true)}function handleShortcut(ev){const disableShortcuts=getSettingValue("disable-shortcuts")==="true";if(ev.ctrlKey||ev.altKey||ev.metaKey||disableShortcuts){return}if(document.activeElement.tagName==="INPUT"&&document.activeElement.type!=="checkbox"&&document.activeElement.type!=="radio"){switch(getVirtualKey(ev)){case"Escape":handleEscape(ev);break}}else{switch(getVirtualKey(ev)){case"Escape":handleEscape(ev);break;case"s":case"S":ev.preventDefault();searchState.focus();break;case"+":ev.preventDefault();expandAllDocs();break;case"-":ev.preventDefault();collapseAllDocs();break;case"?":showHelp();break;default:break}}}document.addEventListener("keypress",handleShortcut);document.addEventListener("keydown",handleShortcut);function addSidebarItems(){if(!window.SIDEBAR_ITEMS){return}const sidebar=document.getElementsByClassName("sidebar-elems")[0];function block(shortty,id,longty){const filtered=window.SIDEBAR_ITEMS[shortty];if(!filtered){return}const h3=document.createElement("h3");h3.innerHTML=`${longty}`;const ul=document.createElement("ul");ul.className="block "+shortty;for(const name of filtered){let path;if(shortty==="mod"){path=name+"/index.html"}else{path=shortty+"."+name+".html"}const current_page=document.location.href.split("/").pop();const link=document.createElement("a");link.href=path;if(path===current_page){link.className="current"}link.textContent=name;const li=document.createElement("li");li.appendChild(link);ul.appendChild(li)}sidebar.appendChild(h3);sidebar.appendChild(ul)}if(sidebar){block("primitive","primitives","Primitive Types");block("mod","modules","Modules");block("macro","macros","Macros");block("struct","structs","Structs");block("enum","enums","Enums");block("union","unions","Unions");block("constant","constants","Constants");block("static","static","Statics");block("trait","traits","Traits");block("fn","functions","Functions");block("type","types","Type Definitions");block("foreigntype","foreign-types","Foreign Types");block("keyword","keywords","Keywords");block("traitalias","trait-aliases","Trait Aliases")}}window.register_implementors=imp=>{const implementors=document.getElementById("implementors-list");const synthetic_implementors=document.getElementById("synthetic-implementors-list");const inlined_types=new Set();const TEXT_IDX=0;const SYNTHETIC_IDX=1;const TYPES_IDX=2;if(synthetic_implementors){onEachLazy(synthetic_implementors.getElementsByClassName("impl"),el=>{const aliases=el.getAttribute("data-aliases");if(!aliases){return}aliases.split(",").forEach(alias=>{inlined_types.add(alias)})})}let currentNbImpls=implementors.getElementsByClassName("impl").length;const traitName=document.querySelector(".main-heading h1 > .trait").textContent;const baseIdName="impl-"+traitName+"-";const libs=Object.getOwnPropertyNames(imp);const script=document.querySelector("script[data-ignore-extern-crates]");const ignoreExternCrates=new Set((script?script.getAttribute("data-ignore-extern-crates"):"").split(","));for(const lib of libs){if(lib===window.currentCrate||ignoreExternCrates.has(lib)){continue}const structs=imp[lib];struct_loop:for(const struct of structs){const list=struct[SYNTHETIC_IDX]?synthetic_implementors:implementors;if(struct[SYNTHETIC_IDX]){for(const struct_type of struct[TYPES_IDX]){if(inlined_types.has(struct_type)){continue struct_loop}inlined_types.add(struct_type)}}const code=document.createElement("h3");code.innerHTML=struct[TEXT_IDX];addClass(code,"code-header");onEachLazy(code.getElementsByTagName("a"),elem=>{const href=elem.getAttribute("href");if(href&&!/^(?:[a-z+]+:)?\/\//.test(href)){elem.setAttribute("href",window.rootPath+href)}});const currentId=baseIdName+currentNbImpls;const anchor=document.createElement("a");anchor.href="#"+currentId;addClass(anchor,"anchor");const display=document.createElement("div");display.id=currentId;addClass(display,"impl");display.appendChild(anchor);display.appendChild(code);list.appendChild(display);currentNbImpls+=1}}};if(window.pending_implementors){window.register_implementors(window.pending_implementors)}function addSidebarCrates(){if(!window.ALL_CRATES){return}const sidebarElems=document.getElementsByClassName("sidebar-elems")[0];if(!sidebarElems){return}const h3=document.createElement("h3");h3.innerHTML="Crates";const ul=document.createElement("ul");ul.className="block crate";for(const crate of window.ALL_CRATES){const link=document.createElement("a");link.href=window.rootPath+crate+"/index.html";if(window.rootPath!=="./"&&crate===window.currentCrate){link.className="current"}link.textContent=crate;const li=document.createElement("li");li.appendChild(link);ul.appendChild(li)}sidebarElems.appendChild(h3);sidebarElems.appendChild(ul)}function expandAllDocs(){const innerToggle=document.getElementById(toggleAllDocsId);removeClass(innerToggle,"will-expand");onEachLazy(document.getElementsByClassName("toggle"),e=>{if(!hasClass(e,"type-contents-toggle")&&!hasClass(e,"more-examples-toggle")){e.open=true}});innerToggle.title="collapse all docs";innerToggle.children[0].innerText="\u2212"}function collapseAllDocs(){const innerToggle=document.getElementById(toggleAllDocsId);addClass(innerToggle,"will-expand");onEachLazy(document.getElementsByClassName("toggle"),e=>{if(e.parentNode.id!=="implementations-list"||(!hasClass(e,"implementors-toggle")&&!hasClass(e,"type-contents-toggle"))){e.open=false}});innerToggle.title="expand all docs";innerToggle.children[0].innerText="+"}function toggleAllDocs(){const innerToggle=document.getElementById(toggleAllDocsId);if(!innerToggle){return}if(hasClass(innerToggle,"will-expand")){expandAllDocs()}else{collapseAllDocs()}}(function(){const toggles=document.getElementById(toggleAllDocsId);if(toggles){toggles.onclick=toggleAllDocs}const hideMethodDocs=getSettingValue("auto-hide-method-docs")==="true";const hideImplementations=getSettingValue("auto-hide-trait-implementations")==="true";const hideLargeItemContents=getSettingValue("auto-hide-large-items")!=="false";function setImplementorsTogglesOpen(id,open){const list=document.getElementById(id);if(list!==null){onEachLazy(list.getElementsByClassName("implementors-toggle"),e=>{e.open=open})}}if(hideImplementations){setImplementorsTogglesOpen("trait-implementations-list",false);setImplementorsTogglesOpen("blanket-implementations-list",false)}onEachLazy(document.getElementsByClassName("toggle"),e=>{if(!hideLargeItemContents&&hasClass(e,"type-contents-toggle")){e.open=true}if(hideMethodDocs&&hasClass(e,"method-toggle")){e.open=false}})}());window.rustdoc_add_line_numbers_to_examples=()=>{onEachLazy(document.getElementsByClassName("rust-example-rendered"),x=>{const parent=x.parentNode;const line_numbers=parent.querySelectorAll(".example-line-numbers");if(line_numbers.length>0){return}const count=x.textContent.split("\n").length;const elems=[];for(let i=0;i{onEachLazy(document.getElementsByClassName("rust-example-rendered"),x=>{const parent=x.parentNode;const line_numbers=parent.querySelectorAll(".example-line-numbers");for(const node of line_numbers){parent.removeChild(node)}})};if(getSettingValue("line-numbers")==="true"){window.rustdoc_add_line_numbers_to_examples()}function showSidebar(){window.hideAllModals(false);const sidebar=document.getElementsByClassName("sidebar")[0];addClass(sidebar,"shown")}function hideSidebar(){const sidebar=document.getElementsByClassName("sidebar")[0];removeClass(sidebar,"shown")}window.addEventListener("resize",()=>{if(window.CURRENT_TOOLTIP_ELEMENT){const base=window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE;const force_visible=base.TOOLTIP_FORCE_VISIBLE;hideTooltip(false);if(force_visible){showTooltip(base);base.TOOLTIP_FORCE_VISIBLE=true}}});const mainElem=document.getElementById(MAIN_ID);if(mainElem){mainElem.addEventListener("click",hideSidebar)}onEachLazy(document.querySelectorAll("a[href^='#']"),el=>{el.addEventListener("click",()=>{expandSection(el.hash.slice(1));hideSidebar()})});onEachLazy(document.querySelectorAll(".toggle > summary:not(.hideme)"),el=>{el.addEventListener("click",e=>{if(e.target.tagName!=="SUMMARY"&&e.target.tagName!=="A"){e.preventDefault()}})});function showTooltip(e){const notable_ty=e.getAttribute("data-notable-ty");if(!window.NOTABLE_TRAITS&¬able_ty){const data=document.getElementById("notable-traits-data");if(data){window.NOTABLE_TRAITS=JSON.parse(data.innerText)}else{throw new Error("showTooltip() called with notable without any notable traits!")}}if(window.CURRENT_TOOLTIP_ELEMENT&&window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE===e){clearTooltipHoverTimeout(window.CURRENT_TOOLTIP_ELEMENT);return}window.hideAllModals(false);const wrapper=document.createElement("div");if(notable_ty){wrapper.innerHTML="
"+window.NOTABLE_TRAITS[notable_ty]+"
"}else{if(e.getAttribute("title")!==null){e.setAttribute("data-title",e.getAttribute("title"));e.removeAttribute("title")}if(e.getAttribute("data-title")!==null){const titleContent=document.createElement("div");titleContent.className="content";titleContent.appendChild(document.createTextNode(e.getAttribute("data-title")));wrapper.appendChild(titleContent)}}wrapper.className="tooltip popover";const focusCatcher=document.createElement("div");focusCatcher.setAttribute("tabindex","0");focusCatcher.onfocus=hideTooltip;wrapper.appendChild(focusCatcher);const pos=e.getBoundingClientRect();wrapper.style.top=(pos.top+window.scrollY+pos.height)+"px";wrapper.style.left=0;wrapper.style.right="auto";wrapper.style.visibility="hidden";const body=document.getElementsByTagName("body")[0];body.appendChild(wrapper);const wrapperPos=wrapper.getBoundingClientRect();const finalPos=pos.left+window.scrollX-wrapperPos.width+24;if(finalPos>0){wrapper.style.left=finalPos+"px"}else{wrapper.style.setProperty("--popover-arrow-offset",(wrapperPos.right-pos.right+4)+"px")}wrapper.style.visibility="";window.CURRENT_TOOLTIP_ELEMENT=wrapper;window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE=e;clearTooltipHoverTimeout(window.CURRENT_TOOLTIP_ELEMENT);wrapper.onpointerenter=function(ev){if(ev.pointerType!=="mouse"){return}clearTooltipHoverTimeout(e)};wrapper.onpointerleave=function(ev){if(ev.pointerType!=="mouse"){return}if(!e.TOOLTIP_FORCE_VISIBLE&&!elemIsInParent(ev.relatedTarget,e)){setTooltipHoverTimeout(e,false);addClass(wrapper,"fade-out")}}}function setTooltipHoverTimeout(element,show){clearTooltipHoverTimeout(element);if(!show&&!window.CURRENT_TOOLTIP_ELEMENT){return}if(show&&window.CURRENT_TOOLTIP_ELEMENT){return}if(window.CURRENT_TOOLTIP_ELEMENT&&window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE!==element){return}element.TOOLTIP_HOVER_TIMEOUT=setTimeout(()=>{if(show){showTooltip(element)}else if(!element.TOOLTIP_FORCE_VISIBLE){hideTooltip(false)}},show?window.RUSTDOC_TOOLTIP_HOVER_MS:window.RUSTDOC_TOOLTIP_HOVER_EXIT_MS)}function clearTooltipHoverTimeout(element){if(element.TOOLTIP_HOVER_TIMEOUT!==undefined){removeClass(window.CURRENT_TOOLTIP_ELEMENT,"fade-out");clearTimeout(element.TOOLTIP_HOVER_TIMEOUT);delete element.TOOLTIP_HOVER_TIMEOUT}}function tooltipBlurHandler(event){if(window.CURRENT_TOOLTIP_ELEMENT&&!elemIsInParent(document.activeElement,window.CURRENT_TOOLTIP_ELEMENT)&&!elemIsInParent(event.relatedTarget,window.CURRENT_TOOLTIP_ELEMENT)&&!elemIsInParent(document.activeElement,window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE)&&!elemIsInParent(event.relatedTarget,window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE)){setTimeout(()=>hideTooltip(false),0)}}function hideTooltip(focus){if(window.CURRENT_TOOLTIP_ELEMENT){if(window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE.TOOLTIP_FORCE_VISIBLE){if(focus){window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE.focus()}window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE.TOOLTIP_FORCE_VISIBLE=false}const body=document.getElementsByTagName("body")[0];body.removeChild(window.CURRENT_TOOLTIP_ELEMENT);clearTooltipHoverTimeout(window.CURRENT_TOOLTIP_ELEMENT);window.CURRENT_TOOLTIP_ELEMENT=null}}onEachLazy(document.getElementsByClassName("tooltip"),e=>{e.onclick=function(){this.TOOLTIP_FORCE_VISIBLE=this.TOOLTIP_FORCE_VISIBLE?false:true;if(window.CURRENT_TOOLTIP_ELEMENT&&!this.TOOLTIP_FORCE_VISIBLE){hideTooltip(true)}else{showTooltip(this);window.CURRENT_TOOLTIP_ELEMENT.setAttribute("tabindex","0");window.CURRENT_TOOLTIP_ELEMENT.focus();window.CURRENT_TOOLTIP_ELEMENT.onblur=tooltipBlurHandler}return false};e.onpointerenter=function(ev){if(ev.pointerType!=="mouse"){return}setTooltipHoverTimeout(this,true)};e.onpointermove=function(ev){if(ev.pointerType!=="mouse"){return}setTooltipHoverTimeout(this,true)};e.onpointerleave=function(ev){if(ev.pointerType!=="mouse"){return}if(!this.TOOLTIP_FORCE_VISIBLE&&!elemIsInParent(ev.relatedTarget,window.CURRENT_TOOLTIP_ELEMENT)){setTooltipHoverTimeout(e,false);addClass(window.CURRENT_TOOLTIP_ELEMENT,"fade-out")}}});const sidebar_menu_toggle=document.getElementsByClassName("sidebar-menu-toggle")[0];if(sidebar_menu_toggle){sidebar_menu_toggle.addEventListener("click",()=>{const sidebar=document.getElementsByClassName("sidebar")[0];if(!hasClass(sidebar,"shown")){showSidebar()}else{hideSidebar()}})}function helpBlurHandler(event){blurHandler(event,getHelpButton(),window.hidePopoverMenus)}function buildHelpMenu(){const book_info=document.createElement("span");const channel=getVar("channel");book_info.className="top";book_info.innerHTML=`You can find more information in \ +the rustdoc book.`;const shortcuts=[["?","Show this help dialog"],["S","Focus the search field"],["↑","Move up in search results"],["↓","Move down in search results"],["← / →","Switch result tab (when results focused)"],["⏎","Go to active search result"],["+","Expand all sections"],["-","Collapse all sections"],].map(x=>"
"+x[0].split(" ").map((y,index)=>((index&1)===0?""+y+"":" "+y+" ")).join("")+"
"+x[1]+"
").join("");const div_shortcuts=document.createElement("div");addClass(div_shortcuts,"shortcuts");div_shortcuts.innerHTML="

Keyboard Shortcuts

"+shortcuts+"
";const infos=[`For a full list of all search features, take a look here.`,"Prefix searches with a type followed by a colon (e.g., fn:) to \ + restrict the search to a given item kind.","Accepted kinds are: fn, mod, struct, \ + enum, trait, type, macro, \ + and const.","Search functions by type signature (e.g., vec -> usize or \ + -> vec or String, enum:Cow -> bool)","You can look for items with an exact name by putting double quotes around \ + your request: \"string\"","Look for functions that accept or return \ + slices and \ + arrays by writing \ + square brackets (e.g., -> [u8] or [] -> Option)","Look for items inside another one by searching for a path: vec::Vec",].map(x=>"

"+x+"

").join("");const div_infos=document.createElement("div");addClass(div_infos,"infos");div_infos.innerHTML="

Search Tricks

"+infos;const rustdoc_version=document.createElement("span");rustdoc_version.className="bottom";const rustdoc_version_code=document.createElement("code");rustdoc_version_code.innerText="rustdoc "+getVar("rustdoc-version");rustdoc_version.appendChild(rustdoc_version_code);const container=document.createElement("div");if(!isHelpPage){container.className="popover"}container.id="help";container.style.display="none";const side_by_side=document.createElement("div");side_by_side.className="side-by-side";side_by_side.appendChild(div_shortcuts);side_by_side.appendChild(div_infos);container.appendChild(book_info);container.appendChild(side_by_side);container.appendChild(rustdoc_version);if(isHelpPage){const help_section=document.createElement("section");help_section.appendChild(container);document.getElementById("main-content").appendChild(help_section);container.style.display="block"}else{const help_button=getHelpButton();help_button.appendChild(container);container.onblur=helpBlurHandler;help_button.onblur=helpBlurHandler;help_button.children[0].onblur=helpBlurHandler}return container}window.hideAllModals=function(switchFocus){hideSidebar();window.hidePopoverMenus();hideTooltip(switchFocus)};window.hidePopoverMenus=function(){onEachLazy(document.querySelectorAll(".search-form .popover"),elem=>{elem.style.display="none"})};function getHelpMenu(buildNeeded){let menu=getHelpButton().querySelector(".popover");if(!menu&&buildNeeded){menu=buildHelpMenu()}return menu}function showHelp(){getHelpButton().querySelector("a").focus();const menu=getHelpMenu(true);if(menu.style.display==="none"){window.hideAllModals();menu.style.display=""}}if(isHelpPage){showHelp();document.querySelector(`#${HELP_BUTTON_ID} > a`).addEventListener("click",event=>{const target=event.target;if(target.tagName!=="A"||target.parentElement.id!==HELP_BUTTON_ID||event.ctrlKey||event.altKey||event.metaKey){return}event.preventDefault()})}else{document.querySelector(`#${HELP_BUTTON_ID} > a`).addEventListener("click",event=>{const target=event.target;if(target.tagName!=="A"||target.parentElement.id!==HELP_BUTTON_ID||event.ctrlKey||event.altKey||event.metaKey){return}event.preventDefault();const menu=getHelpMenu(true);const shouldShowHelp=menu.style.display==="none";if(shouldShowHelp){showHelp()}else{window.hidePopoverMenus()}})}setMobileTopbar();addSidebarItems();addSidebarCrates();onHashChange(null);window.addEventListener("hashchange",onHashChange);searchState.setup()}());(function(){let reset_button_timeout=null;const but=document.getElementById("copy-path");if(!but){return}but.onclick=()=>{const parent=but.parentElement;const path=[];onEach(parent.childNodes,child=>{if(child.tagName==="A"){path.push(child.textContent)}});const el=document.createElement("textarea");el.value=path.join("::");el.setAttribute("readonly","");el.style.position="absolute";el.style.left="-9999px";document.body.appendChild(el);el.select();document.execCommand("copy");document.body.removeChild(el);but.children[0].style.display="none";let tmp;if(but.childNodes.length<2){tmp=document.createTextNode("✓");but.appendChild(tmp)}else{onEachLazy(but.childNodes,e=>{if(e.nodeType===Node.TEXT_NODE){tmp=e;return true}});tmp.textContent="✓"}if(reset_button_timeout!==null){window.clearTimeout(reset_button_timeout)}function reset_button(){tmp.textContent="";reset_button_timeout=null;but.children[0].style.display=""}reset_button_timeout=window.setTimeout(reset_button,1000)}}()) \ No newline at end of file diff --git a/book/rust-docs/static.files/normalize-76eba96aa4d2e634.css b/book/rust-docs/static.files/normalize-76eba96aa4d2e634.css new file mode 100644 index 0000000000..469959f137 --- /dev/null +++ b/book/rust-docs/static.files/normalize-76eba96aa4d2e634.css @@ -0,0 +1,2 @@ + /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ +html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:0.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-0.25em}sup{top:-0.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type="button"],[type="reset"],[type="submit"],button{-webkit-appearance:button}[type="button"]::-moz-focus-inner,[type="reset"]::-moz-focus-inner,[type="submit"]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type="button"]:-moz-focusring,[type="reset"]:-moz-focusring,[type="submit"]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:0.35em 0.75em 0.625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type="checkbox"],[type="radio"]{box-sizing:border-box;padding:0}[type="number"]::-webkit-inner-spin-button,[type="number"]::-webkit-outer-spin-button{height:auto}[type="search"]{-webkit-appearance:textfield;outline-offset:-2px}[type="search"]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}template{display:none}[hidden]{display:none} \ No newline at end of file diff --git a/book/rust-docs/static.files/noscript-13285aec31fa243e.css b/book/rust-docs/static.files/noscript-13285aec31fa243e.css new file mode 100644 index 0000000000..c32e0cb135 --- /dev/null +++ b/book/rust-docs/static.files/noscript-13285aec31fa243e.css @@ -0,0 +1 @@ + #main-content .attributes{margin-left:0 !important;}#copy-path{display:none;}nav.sub{display:none;}.source .sidebar{display:none;}.notable-traits{display:none;} \ No newline at end of file diff --git a/book/rust-docs/static.files/rust-logo-151179464ae7ed46.svg b/book/rust-docs/static.files/rust-logo-151179464ae7ed46.svg new file mode 100644 index 0000000000..62424d8ffd --- /dev/null +++ b/book/rust-docs/static.files/rust-logo-151179464ae7ed46.svg @@ -0,0 +1,61 @@ + + + diff --git a/book/rust-docs/static.files/rustdoc-9bb858ba049f1f21.css b/book/rust-docs/static.files/rustdoc-9bb858ba049f1f21.css new file mode 100644 index 0000000000..73eacd8030 --- /dev/null +++ b/book/rust-docs/static.files/rustdoc-9bb858ba049f1f21.css @@ -0,0 +1,8 @@ + :root{--nav-sub-mobile-padding:8px;--search-typename-width:6.75rem;}@font-face {font-family:'Fira Sans';font-style:normal;font-weight:400;src:local('Fira Sans'),url("FiraSans-Regular-018c141bf0843ffd.woff2") format("woff2");font-display:swap;}@font-face {font-family:'Fira Sans';font-style:normal;font-weight:500;src:local('Fira Sans Medium'),url("FiraSans-Medium-8f9a781e4970d388.woff2") format("woff2");font-display:swap;}@font-face {font-family:'Source Serif 4';font-style:normal;font-weight:400;src:local('Source Serif 4'),url("SourceSerif4-Regular-46f98efaafac5295.ttf.woff2") format("woff2");font-display:swap;}@font-face {font-family:'Source Serif 4';font-style:italic;font-weight:400;src:local('Source Serif 4 Italic'),url("SourceSerif4-It-acdfaf1a8af734b1.ttf.woff2") format("woff2");font-display:swap;}@font-face {font-family:'Source Serif 4';font-style:normal;font-weight:700;src:local('Source Serif 4 Bold'),url("SourceSerif4-Bold-a2c9cd1067f8b328.ttf.woff2") format("woff2");font-display:swap;}@font-face {font-family:'Source Code Pro';font-style:normal;font-weight:400;src:url("SourceCodePro-Regular-562dcc5011b6de7d.ttf.woff2") format("woff2");font-display:swap;}@font-face {font-family:'Source Code Pro';font-style:italic;font-weight:400;src:url("SourceCodePro-It-1cc31594bf4f1f79.ttf.woff2") format("woff2");font-display:swap;}@font-face {font-family:'Source Code Pro';font-style:normal;font-weight:600;src:url("SourceCodePro-Semibold-d899c5a5c4aeb14a.ttf.woff2") format("woff2");font-display:swap;}@font-face {font-family:'NanumBarunGothic';src:url("NanumBarunGothic-0f09457c7a19b7c6.ttf.woff2") format("woff2");font-display:swap;unicode-range:U+AC00-D7AF,U+1100-11FF,U+3130-318F,U+A960-A97F,U+D7B0-D7FF;}*{box-sizing:border-box;}body{font:1rem/1.5 "Source Serif 4",NanumBarunGothic,serif;margin:0;position:relative;overflow-wrap:break-word;overflow-wrap:anywhere;font-feature-settings:"kern","liga";background-color:var(--main-background-color);color:var(--main-color);}h1{font-size:1.5rem;}h2{font-size:1.375rem;}h3{font-size:1.25rem;}h1,h2,h3,h4,h5,h6{font-weight:500;}h1,h2,h3,h4{margin:25px 0 15px 0;padding-bottom:6px;}.docblock h3,.docblock h4,h5,h6{margin:15px 0 5px 0;}.docblock>h2:first-child,.docblock>h3:first-child,.docblock>h4:first-child,.docblock>h5:first-child,.docblock>h6:first-child{margin-top:0;}.main-heading h1{margin:0;padding:0;flex-grow:1;overflow-wrap:break-word;overflow-wrap:anywhere;}.main-heading{display:flex;flex-wrap:wrap;padding-bottom:6px;margin-bottom:15px;}.content h2,.top-doc .docblock>h3,.top-doc .docblock>h4{border-bottom:1px solid var(--headings-border-bottom-color);}h1,h2{line-height:1.25;padding-top:3px;padding-bottom:9px;}h3.code-header{font-size:1.125rem;}h4.code-header{font-size:1rem;}.code-header{font-weight:600;margin:0;padding:0;white-space:pre-wrap;}#crate-search,h1,h2,h3,h4,h5,h6,.sidebar,.mobile-topbar,.search-input,.search-results .result-name,.item-name>a,.out-of-band,span.since,a.srclink,#help-button>a,summary.hideme,.scraped-example-list,ul.all-items{font-family:"Fira Sans",Arial,NanumBarunGothic,sans-serif;}#toggle-all-docs,a.anchor,.small-section-header a,#source-sidebar a,.rust a,.sidebar h2 a,.sidebar h3 a,.mobile-topbar h2 a,h1 a,.search-results a,.stab,.result-name i{color:var(--main-color);}span.enum,a.enum,span.struct,a.struct,span.union,a.union,span.primitive,a.primitive,span.type,a.type,span.foreigntype,a.foreigntype{color:var(--type-link-color);}span.trait,a.trait,span.traitalias,a.traitalias{color:var(--trait-link-color);}span.associatedtype,a.associatedtype,span.constant,a.constant,span.static,a.static{color:var(--assoc-item-link-color);}span.fn,a.fn,span.method,a.method,span.tymethod,a.tymethod{color:var(--function-link-color);}span.attr,a.attr,span.derive,a.derive,span.macro,a.macro{color:var(--macro-link-color);}span.mod,a.mod{color:var(--mod-link-color);}span.keyword,a.keyword{color:var(--keyword-link-color);}a{color:var(--link-color);text-decoration:none;}ol,ul{padding-left:24px;}ul ul,ol ul,ul ol,ol ol{margin-bottom:.625em;}p{margin:0 0 .75em 0;}p:last-child{margin:0;}button{padding:1px 6px;cursor:pointer;}button#toggle-all-docs{padding:0;background:none;border:none;-webkit-appearance:none;opacity:1;}.rustdoc{display:flex;flex-direction:row;flex-wrap:nowrap;}main{position:relative;flex-grow:1;padding:10px 15px 40px 45px;min-width:0;}.source main{padding:15px;}.width-limiter{max-width:960px;margin-right:auto;}details:not(.toggle) summary{margin-bottom:.6em;}code,pre,a.test-arrow,.code-header{font-family:"Source Code Pro",monospace;}.docblock code,.docblock-short code{border-radius:3px;padding:0 0.125em;}.docblock pre code,.docblock-short pre code{padding:0;}pre{padding:14px;line-height:1.5;}pre.item-decl{overflow-x:auto;}.item-decl .type-contents-toggle{contain:initial;}.source .content pre{padding:20px;}.rustdoc.source .example-wrap pre.src-line-numbers{padding:20px 0 20px 4px;}img{max-width:100%;}.sub-logo-container,.logo-container{line-height:0;display:block;}.sub-logo-container{margin-right:32px;}.sub-logo-container>img{height:60px;width:60px;object-fit:contain;}.rust-logo{filter:var(--rust-logo-filter);}.sidebar{font-size:0.875rem;flex:0 0 200px;overflow-y:scroll;overscroll-behavior:contain;position:sticky;height:100vh;top:0;left:0;}.rustdoc.source .sidebar{flex-basis:50px;border-right:1px solid;overflow-x:hidden;overflow-y:hidden;z-index:1;}.sidebar,.mobile-topbar,.sidebar-menu-toggle,#src-sidebar-toggle,#source-sidebar{background-color:var(--sidebar-background-color);}#src-sidebar-toggle>button:hover,#src-sidebar-toggle>button:focus{background-color:var(--sidebar-background-color-hover);}.source .sidebar>*:not(#src-sidebar-toggle){visibility:hidden;}.source-sidebar-expanded .source .sidebar{overflow-y:auto;flex-basis:300px;}.source-sidebar-expanded .source .sidebar>*:not(#src-sidebar-toggle){visibility:visible;}#all-types{margin-top:1em;}*{scrollbar-width:initial;scrollbar-color:var(--scrollbar-color);}.sidebar{scrollbar-width:thin;scrollbar-color:var(--scrollbar-color);}::-webkit-scrollbar{width:12px;}.sidebar::-webkit-scrollbar{width:8px;}::-webkit-scrollbar-track{-webkit-box-shadow:inset 0;background-color:var(--scrollbar-track-background-color);}.sidebar::-webkit-scrollbar-track{background-color:var(--scrollbar-track-background-color);}::-webkit-scrollbar-thumb,.sidebar::-webkit-scrollbar-thumb{background-color:var(--scrollbar-thumb-background-color);}.hidden{display:none !important;}.sidebar .logo-container{margin-top:10px;margin-bottom:10px;text-align:center;}.version{overflow-wrap:break-word;}.logo-container>img{height:100px;width:100px;}ul.block,.block li{padding:0;margin:0;list-style:none;}.sidebar-elems a,.sidebar>h2 a{display:block;padding:0.25rem;margin-left:-0.25rem;}.sidebar h2{overflow-wrap:anywhere;padding:0;margin:0.7rem 0;}.sidebar h3{font-size:1.125rem;padding:0;margin:0;}.sidebar-elems,.sidebar>h2{padding-left:24px;}.sidebar a{color:var(--sidebar-link-color);}.sidebar .current,.sidebar a:hover:not(.logo-container){background-color:var(--sidebar-current-link-background-color);}.sidebar-elems .block{margin-bottom:2em;}.sidebar-elems .block li a{white-space:nowrap;text-overflow:ellipsis;overflow:hidden;}.mobile-topbar{display:none;}.rustdoc .example-wrap{display:flex;position:relative;margin-bottom:10px;}.rustdoc .example-wrap:last-child{margin-bottom:0px;}.rustdoc .example-wrap pre{margin:0;flex-grow:1;}.rustdoc:not(.source) .example-wrap pre{overflow:auto hidden;}.rustdoc .example-wrap pre.example-line-numbers,.rustdoc .example-wrap pre.src-line-numbers{flex-grow:0;min-width:fit-content;overflow:initial;text-align:right;-webkit-user-select:none;user-select:none;padding:14px 8px;color:var(--src-line-numbers-span-color);}.rustdoc .example-wrap pre.src-line-numbers{padding:14px 0;}.src-line-numbers a,.src-line-numbers span{color:var(--src-line-numbers-span-color);padding:0 8px;}.src-line-numbers :target{background-color:transparent;border-right:none;padding:0 8px;}.src-line-numbers .line-highlighted{background-color:var(--src-line-number-highlighted-background-color);}.search-loading{text-align:center;}.docblock-short{overflow-wrap:break-word;overflow-wrap:anywhere;}.docblock :not(pre)>code,.docblock-short code{white-space:pre-wrap;}.top-doc .docblock h2{font-size:1.375rem;}.top-doc .docblock h3{font-size:1.25rem;}.top-doc .docblock h4,.top-doc .docblock h5{font-size:1.125rem;}.top-doc .docblock h6{font-size:1rem;}.docblock h5{font-size:1rem;}.docblock h6{font-size:0.875rem;}.docblock{margin-left:24px;position:relative;}.docblock>:not(.more-examples-toggle):not(.example-wrap){max-width:100%;overflow-x:auto;}.out-of-band{flex-grow:0;font-size:1.125rem;}.docblock code,.docblock-short code,pre,.rustdoc.source .example-wrap{background-color:var(--code-block-background-color);}#main-content{position:relative;}.docblock table{margin:.5em 0;border-collapse:collapse;}.docblock table td,.docblock table th{padding:.5em;border:1px solid var(--border-color);}.docblock table tbody tr:nth-child(2n){background:var(--table-alt-row-background-color);}.method .where,.fn .where,.where.fmt-newline{display:block;white-space:pre-wrap;font-size:0.875rem;}.item-info{display:block;margin-left:24px;}.item-info code{font-size:0.875rem;}#main-content>.item-info{margin-left:0;}nav.sub{flex-grow:1;flex-flow:row nowrap;margin:4px 0 25px 0;display:flex;align-items:center;}.search-form{position:relative;display:flex;height:34px;flex-grow:1;}.source nav.sub{margin:0 0 15px 0;}.small-section-header{display:block;position:relative;}.small-section-header:hover>.anchor,.impl:hover>.anchor,.trait-impl:hover>.anchor,.variant:hover>.anchor{display:initial;}.anchor{display:none;position:absolute;left:-0.5em;background:none !important;}.anchor.field{left:-5px;}.small-section-header>.anchor{left:-15px;padding-right:8px;}h2.small-section-header>.anchor{padding-right:6px;}.main-heading a:hover,.example-wrap .rust a:hover,.all-items a:hover,.docblock a:not(.test-arrow):not(.scrape-help):not(.tooltip):hover,.docblock-short a:not(.test-arrow):not(.scrape-help):not(.tooltip):hover,.item-info a{text-decoration:underline;}.crate.block a.current{font-weight:500;}table,.item-table{overflow-wrap:break-word;}.item-table{display:table;padding:0;margin:0;}.item-table>li{display:table-row;}.item-table>li>div{display:table-cell;}.item-table>li>.item-name{padding-right:1.25rem;}.search-results-title{margin-top:0;white-space:nowrap;display:flex;align-items:baseline;}#crate-search-div{position:relative;min-width:5em;}#crate-search{min-width:115px;padding:0 23px 0 4px;max-width:100%;text-overflow:ellipsis;border:1px solid var(--border-color);border-radius:4px;outline:none;cursor:pointer;-moz-appearance:none;-webkit-appearance:none;text-indent:0.01px;background-color:var(--main-background-color);color:inherit;line-height:1.5;font-weight:500;}#crate-search:hover,#crate-search:focus{border-color:var(--crate-search-hover-border);}@-moz-document url-prefix(){#crate-search{padding-left:0px;padding-right:19px;}}#crate-search-div::after{pointer-events:none;width:100%;height:100%;position:absolute;top:0;left:0;content:"";background-repeat:no-repeat;background-size:20px;background-position:calc(100% - 2px) 56%;background-image:url('data:image/svg+xml, \ + ');filter:var(--crate-search-div-filter);}#crate-search-div:hover::after,#crate-search-div:focus-within::after{filter:var(--crate-search-div-hover-filter);}#crate-search>option{font-size:1rem;}.search-input{-webkit-appearance:none;outline:none;border:1px solid var(--border-color);border-radius:2px;padding:8px;font-size:1rem;flex-grow:1;background-color:var(--button-background-color);color:var(--search-color);}.search-input:focus{border-color:var(--search-input-focused-border-color);}.search-results{display:none;}.search-results.active{display:block;}.search-results>a{display:flex;margin-left:2px;margin-right:2px;border-bottom:1px solid var(--search-result-border-color);gap:1em;}.search-results>a>div.desc{white-space:nowrap;text-overflow:ellipsis;overflow:hidden;flex:2;}.search-results a:hover,.search-results a:focus{background-color:var(--search-result-link-focus-background-color);}.search-results .result-name{display:flex;align-items:center;justify-content:start;flex:3;}.search-results .result-name span.alias{color:var(--search-results-alias-color);}.search-results .result-name .grey{color:var(--search-results-grey-color);}.search-results .result-name .typename{color:var(--search-results-grey-color);font-size:0.875rem;width:var(--search-typename-width);}.search-results .result-name .path{word-break:break-all;max-width:calc(100% - var(--search-typename-width));display:inline-block;}.popover{position:absolute;top:100%;right:0;z-index:2;margin-top:7px;border-radius:3px;border:1px solid var(--border-color);background-color:var(--main-background-color);color:var(--main-color);--popover-arrow-offset:11px;}.popover::before{content:'';position:absolute;right:var(--popover-arrow-offset);border:solid var(--border-color);border-width:1px 1px 0 0;background-color:var(--main-background-color);padding:4px;transform:rotate(-45deg);top:-5px;}#help.popover{max-width:600px;--popover-arrow-offset:48px;}#help dt{float:left;clear:left;margin-right:0.5rem;}#help span.top,#help span.bottom{text-align:center;display:block;font-size:1.125rem;}#help span.top{margin:10px 0;border-bottom:1px solid var(--border-color);padding-bottom:4px;margin-bottom:6px;}#help span.bottom{clear:both;border-top:1px solid var(--border-color);}.side-by-side>div{width:50%;float:left;padding:0 20px 20px 17px;}.item-info .stab{min-height:36px;display:flex;padding:3px;margin-bottom:5px;align-items:center;vertical-align:text-bottom;}.item-name .stab{margin-left:0.3125em;}.stab{padding:0 2px;font-size:0.875rem;font-weight:normal;color:var(--main-color);background-color:var(--stab-background-color);width:fit-content;white-space:pre-wrap;border-radius:3px;display:inline;}.stab.portability>code{background:none;color:var(--stab-code-color);}.stab .emoji{font-size:1.25rem;margin-right:0.3rem;}.emoji{text-shadow:1px 0 0 black,-1px 0 0 black,0 1px 0 black,0 -1px 0 black;}.since{font-weight:normal;font-size:initial;}.rightside{padding-left:12px;float:right;}.rightside:not(a),.out-of-band{color:var(--right-side-color);}pre.rust{tab-size:4;-moz-tab-size:4;}pre.rust .kw{color:var(--code-highlight-kw-color);}pre.rust .kw-2{color:var(--code-highlight-kw-2-color);}pre.rust .lifetime{color:var(--code-highlight-lifetime-color);}pre.rust .prelude-ty{color:var(--code-highlight-prelude-color);}pre.rust .prelude-val{color:var(--code-highlight-prelude-val-color);}pre.rust .string{color:var(--code-highlight-string-color);}pre.rust .number{color:var(--code-highlight-number-color);}pre.rust .bool-val{color:var(--code-highlight-literal-color);}pre.rust .self{color:var(--code-highlight-self-color);}pre.rust .attr{color:var(--code-highlight-attribute-color);}pre.rust .macro,pre.rust .macro-nonterminal{color:var(--code-highlight-macro-color);}pre.rust .question-mark{font-weight:bold;color:var(--code-highlight-question-mark-color);}pre.rust .comment{color:var(--code-highlight-comment-color);}pre.rust .doccomment{color:var(--code-highlight-doc-comment-color);}.rustdoc.source .example-wrap pre.rust a{background:var(--codeblock-link-background);}.example-wrap.compile_fail,.example-wrap.should_panic{border-left:2px solid var(--codeblock-error-color);}.ignore.example-wrap{border-left:2px solid var(--codeblock-ignore-color);}.example-wrap.compile_fail:hover,.example-wrap.should_panic:hover{border-left:2px solid var(--codeblock-error-hover-color);}.example-wrap.ignore:hover{border-left:2px solid var(--codeblock-ignore-hover-color);}.example-wrap.compile_fail .tooltip,.example-wrap.should_panic .tooltip{color:var(--codeblock-error-color);}.example-wrap.ignore .tooltip{color:var(--codeblock-ignore-color);}.example-wrap.compile_fail:hover .tooltip,.example-wrap.should_panic:hover .tooltip{color:var(--codeblock-error-hover-color);}.example-wrap.ignore:hover .tooltip{color:var(--codeblock-ignore-hover-color);}.example-wrap .tooltip{position:absolute;display:block;left:-25px;top:5px;margin:0;line-height:1;}.example-wrap.compile_fail .tooltip,.example-wrap.should_panic .tooltip,.example-wrap.ignore .tooltip{font-weight:bold;font-size:1.25rem;}a.test-arrow{visibility:hidden;position:absolute;padding:5px 10px 5px 10px;border-radius:5px;font-size:1.375rem;top:5px;right:5px;z-index:1;color:var(--test-arrow-color);background-color:var(--test-arrow-background-color);}a.test-arrow:hover{color:var(--test-arrow-hover-color);background-color:var(--test-arrow-hover-background-color);}.example-wrap:hover .test-arrow{visibility:visible;}.code-attribute{font-weight:300;color:var(--code-attribute-color);}.item-spacer{width:100%;height:12px;display:block;}.out-of-band>span.since{font-size:1.25rem;}.sub-variant h4{font-size:1rem;font-weight:400;margin-top:0;margin-bottom:0;}.sub-variant{margin-left:24px;margin-bottom:40px;}.sub-variant>.sub-variant-field{margin-left:24px;}:target{padding-right:3px;background-color:var(--target-background-color);border-right:3px solid var(--target-border-color);}.code-header a.tooltip{color:inherit;margin-right:15px;position:relative;}.code-header a.tooltip:hover{color:var(--link-color);}a.tooltip:hover::after{position:absolute;top:calc(100% - 10px);left:-15px;right:-15px;height:20px;content:"\00a0";}.fade-out{opacity:0;transition:opacity 0.45s cubic-bezier(0,0,0.1,1.0);}.popover.tooltip .content{margin:0.25em 0.5em;}.popover.tooltip .content pre,.popover.tooltip .content code{background:transparent;margin:0;padding:0;font-size:1.25rem;white-space:pre-wrap;}.popover.tooltip .content>h3:first-child{margin:0 0 5px 0;}.search-failed{text-align:center;margin-top:20px;display:none;}.search-failed.active{display:block;}.search-failed>ul{text-align:left;max-width:570px;margin-left:auto;margin-right:auto;}#search-tabs{display:flex;flex-direction:row;gap:1px;margin-bottom:4px;}#search-tabs button{text-align:center;font-size:1.125rem;border:0;border-top:2px solid;flex:1;line-height:1.5;color:inherit;}#search-tabs button:not(.selected){background-color:var(--search-tab-button-not-selected-background);border-top-color:var(--search-tab-button-not-selected-border-top-color);}#search-tabs button:hover,#search-tabs button.selected{background-color:var(--search-tab-button-selected-background);border-top-color:var(--search-tab-button-selected-border-top-color);}#search-tabs .count{font-size:1rem;color:var(--search-tab-title-count-color);}#search .error code{border-radius:3px;background-color:var(--search-error-code-background-color);}.search-corrections{font-weight:normal;}#src-sidebar-toggle{position:sticky;top:0;left:0;font-size:1.25rem;border-bottom:1px solid;display:flex;height:40px;justify-content:stretch;align-items:stretch;z-index:10;}#source-sidebar{width:100%;overflow:auto;}#source-sidebar>.title{font-size:1.5rem;text-align:center;border-bottom:1px solid var(--border-color);margin-bottom:6px;}#source-sidebar div.files>a:hover,details.dir-entry summary:hover,#source-sidebar div.files>a:focus,details.dir-entry summary:focus{background-color:var(--source-sidebar-background-hover);}#source-sidebar div.files>a.selected{background-color:var(--source-sidebar-background-selected);}#src-sidebar-toggle>button{font-size:inherit;font-weight:bold;background:none;color:inherit;text-align:center;border:none;outline:none;flex:1 1;-webkit-appearance:none;opacity:1;}#settings-menu,#help-button{margin-left:4px;display:flex;}#settings-menu>a,#help-button>a{display:flex;align-items:center;justify-content:center;background-color:var(--button-background-color);border:1px solid var(--border-color);border-radius:2px;color:var(--settings-button-color);font-size:20px;width:33px;}#settings-menu>a:hover,#settings-menu>a:focus,#help-button>a:hover,#help-button>a:focus{border-color:var(--settings-button-border-focus);}#copy-path{color:var(--copy-path-button-color);background:var(--main-background-color);height:34px;margin-left:10px;padding:0;padding-left:2px;border:0;width:33px;}#copy-path>img{filter:var(--copy-path-img-filter);}#copy-path:hover>img{filter:var(--copy-path-img-hover-filter);}@keyframes rotating{from{transform:rotate(0deg);}to{transform:rotate(360deg);}}#settings-menu.rotate>a img{animation:rotating 2s linear infinite;}kbd{display:inline-block;padding:3px 5px;font:15px monospace;line-height:10px;vertical-align:middle;border:solid 1px var(--border-color);border-radius:3px;color:var(--kbd-color);background-color:var(--kbd-background);box-shadow:inset 0 -1px 0 var(--kbd-box-shadow-color);}ul.all-items>li{list-style:none;}details.dir-entry{padding-left:4px;}details.dir-entry>summary{margin:0 0 0 -4px;padding:0 0 0 4px;cursor:pointer;}details.dir-entry div.folders,details.dir-entry div.files{padding-left:23px;}details.dir-entry a{display:block;}details.toggle{contain:layout;position:relative;}details.toggle>summary.hideme{cursor:pointer;font-size:1rem;}details.toggle>summary{list-style:none;outline:none;}details.toggle>summary::-webkit-details-marker,details.toggle>summary::marker{display:none;}details.toggle>summary.hideme>span{margin-left:9px;}details.toggle>summary::before{background:url('data:image/svg+xml,') no-repeat top left;content:"";cursor:pointer;width:16px;height:16px;display:inline-block;vertical-align:middle;opacity:.5;filter:var(--toggle-filter);}details.toggle>summary.hideme>span,.more-examples-toggle summary,.more-examples-toggle .hide-more{color:var(--toggles-color);}details.toggle>summary::after{content:"Expand";overflow:hidden;width:0;height:0;position:absolute;}details.toggle>summary.hideme::after{content:"";}details.toggle>summary:focus::before,details.toggle>summary:hover::before{opacity:1;}details.toggle>summary:focus-visible::before{outline:1px dotted #000;outline-offset:1px;}details.non-exhaustive{margin-bottom:8px;}details.toggle>summary.hideme::before{position:relative;}details.toggle>summary:not(.hideme)::before{position:absolute;left:-24px;top:4px;}.impl-items>details.toggle>summary:not(.hideme)::before{position:absolute;left:-24px;}details.toggle[open] >summary.hideme{position:absolute;}details.toggle[open] >summary.hideme>span{display:none;}details.toggle[open] >summary::before{background:url('data:image/svg+xml,') no-repeat top left;}details.toggle[open] >summary::after{content:"Collapse";}.docblock summary>*{display:inline-block;}.docblock>.example-wrap:first-child .tooltip{margin-top:16px;}@media (max-width:700px){*[id]{scroll-margin-top:45px;}.rustdoc{display:block;}main{padding-left:15px;padding-top:0px;}.main-heading{flex-direction:column;}.out-of-band{text-align:left;margin-left:initial;padding:initial;}.out-of-band .since::before{content:"Since ";}.sidebar .logo-container,.sidebar .location{display:none;}.sidebar{position:fixed;top:45px;left:-1000px;z-index:11;height:calc(100vh - 45px);width:200px;}.source main,.rustdoc.source .sidebar{top:0;padding:0;height:100vh;border:0;}.sidebar.shown,.source-sidebar-expanded .source .sidebar,.rustdoc:not(.source) .sidebar:focus-within{left:0;}.mobile-topbar h2{padding-bottom:0;margin:auto 0.5em auto auto;overflow:hidden;font-size:24px;}.mobile-topbar h2 a{display:block;text-overflow:ellipsis;overflow:hidden;white-space:nowrap;}.mobile-topbar .logo-container>img{max-width:35px;max-height:35px;margin:5px 0 5px 20px;}.mobile-topbar{display:flex;flex-direction:row;position:sticky;z-index:10;font-size:2rem;height:45px;width:100%;left:0;top:0;}.sidebar-menu-toggle{width:45px;font-size:32px;border:none;color:var(--main-color);}.sidebar-elems{margin-top:1em;}.anchor{display:none !important;}#search-tabs .count{display:block;}#main-content>details.toggle>summary::before,#main-content>div>details.toggle>summary::before{left:-11px;}#src-sidebar-toggle{position:fixed;left:1px;top:100px;width:30px;font-size:1.5rem;padding:0;z-index:10;border-top-right-radius:3px;border-bottom-right-radius:3px;border:1px solid;border-left:0;}.source-sidebar-expanded #src-sidebar-toggle{left:unset;top:unset;width:unset;border-top-right-radius:unset;border-bottom-right-radius:unset;position:sticky;border:0;border-bottom:1px solid;}#copy-path,#help-button{display:none;}.item-table,.item-row,.item-table>li,.item-table>li>div,.search-results>a,.search-results>a>div{display:block;}.search-results>a{padding:5px 0px;}.search-results>a>div.desc,.item-table>li>div.desc{padding-left:2em;}.search-results .result-name{display:block;}.search-results .result-name .typename{width:initial;margin-right:0;}.search-results .result-name .typename,.search-results .result-name .path{display:inline;}.source-sidebar-expanded .source .sidebar{max-width:100vw;width:100vw;}details.toggle:not(.top-doc)>summary{margin-left:10px;}.impl-items>details.toggle>summary:not(.hideme)::before,#main-content>details.toggle:not(.top-doc)>summary::before,#main-content>div>details.toggle>summary::before{left:-11px;}.impl-items>.item-info{margin-left:34px;}.source nav.sub{margin:0;padding:var(--nav-sub-mobile-padding);}}@media (min-width:701px){.scraped-example-title{position:absolute;z-index:10;background:var(--main-background-color);bottom:8px;right:5px;padding:2px 4px;box-shadow:0 0 4px var(--main-background-color);}}@media print{nav.sidebar,nav.sub,.out-of-band,a.srclink,#copy-path,details.toggle[open] >summary::before,details.toggle>summary::before,details.toggle.top-doc>summary{display:none;}.docblock{margin-left:0;}main{padding:10px;}}@media (max-width:464px){.docblock{margin-left:12px;}.docblock code{overflow-wrap:break-word;overflow-wrap:anywhere;}nav.sub{flex-direction:column;}.search-form{align-self:stretch;}.sub-logo-container>img{height:35px;width:35px;margin-bottom:var(--nav-sub-mobile-padding);}}.variant,.implementors-toggle>summary,.impl,#implementors-list>.docblock,.impl-items>section,.impl-items>.toggle>summary,.methods>section,.methods>.toggle>summary{margin-bottom:0.75em;}.variants>.docblock,.implementors-toggle>.docblock,.impl-items>.toggle[open]:not(:last-child),.methods>.toggle[open]:not(:last-child),.implementors-toggle[open]:not(:last-child){margin-bottom:2em;}#trait-implementations-list .impl-items>.toggle:not(:last-child),#synthetic-implementations-list .impl-items>.toggle:not(:last-child),#blanket-implementations-list .impl-items>.toggle:not(:last-child){margin-bottom:1em;}.scraped-example-list .scrape-help{margin-left:10px;padding:0 4px;font-weight:normal;font-size:12px;position:relative;bottom:1px;border:1px solid var(--scrape-example-help-border-color);border-radius:50px;color:var(--scrape-example-help-color);}.scraped-example-list .scrape-help:hover{border-color:var(--scrape-example-help-hover-border-color);color:var(--scrape-example-help-hover-color);}.scraped-example{position:relative;}.scraped-example .code-wrapper{position:relative;display:flex;flex-direction:row;flex-wrap:wrap;width:100%;}.scraped-example:not(.expanded) .code-wrapper{max-height:calc(1.5em * 5 + 10px);}.scraped-example:not(.expanded) .code-wrapper pre{overflow-y:hidden;padding-bottom:0;max-height:calc(1.5em * 5 + 10px);}.more-scraped-examples .scraped-example:not(.expanded) .code-wrapper,.more-scraped-examples .scraped-example:not(.expanded) .code-wrapper pre{max-height:calc(1.5em * 10 + 10px);}.scraped-example .code-wrapper .next,.scraped-example .code-wrapper .prev,.scraped-example .code-wrapper .expand{color:var(--main-color);position:absolute;top:0.25em;z-index:1;padding:0;background:none;border:none;-webkit-appearance:none;opacity:1;}.scraped-example .code-wrapper .prev{right:2.25em;}.scraped-example .code-wrapper .next{right:1.25em;}.scraped-example .code-wrapper .expand{right:0.25em;}.scraped-example:not(.expanded) .code-wrapper::before,.scraped-example:not(.expanded) .code-wrapper::after{content:" ";width:100%;height:5px;position:absolute;z-index:1;}.scraped-example:not(.expanded) .code-wrapper::before{top:0;background:linear-gradient(to bottom,var(--scrape-example-code-wrapper-background-start),var(--scrape-example-code-wrapper-background-end));}.scraped-example:not(.expanded) .code-wrapper::after{bottom:0;background:linear-gradient(to top,var(--scrape-example-code-wrapper-background-start),var(--scrape-example-code-wrapper-background-end));}.scraped-example .code-wrapper .example-wrap{width:100%;overflow-y:hidden;margin-bottom:0;}.scraped-example:not(.expanded) .code-wrapper .example-wrap{overflow-x:hidden;}.scraped-example .example-wrap .rust span.highlight{background:var(--scrape-example-code-line-highlight);}.scraped-example .example-wrap .rust span.highlight.focus{background:var(--scrape-example-code-line-highlight-focus);}.more-examples-toggle{max-width:calc(100% + 25px);margin-top:10px;margin-left:-25px;}.more-examples-toggle .hide-more{margin-left:25px;cursor:pointer;}.more-scraped-examples{margin-left:25px;position:relative;}.toggle-line{position:absolute;top:5px;bottom:0;right:calc(100% + 10px);padding:0 4px;cursor:pointer;}.toggle-line-inner{min-width:2px;height:100%;background:var(--scrape-example-toggle-line-background);}.toggle-line:hover .toggle-line-inner{background:var(--scrape-example-toggle-line-hover-background);}.more-scraped-examples .scraped-example,.example-links{margin-top:20px;}.more-scraped-examples .scraped-example:first-child{margin-top:5px;}.example-links ul{margin-bottom:0;} \ No newline at end of file diff --git a/book/rust-docs/static.files/scrape-examples-ef1e698c1d417c0c.js b/book/rust-docs/static.files/scrape-examples-ef1e698c1d417c0c.js new file mode 100644 index 0000000000..ba830e3744 --- /dev/null +++ b/book/rust-docs/static.files/scrape-examples-ef1e698c1d417c0c.js @@ -0,0 +1 @@ +"use strict";(function(){const DEFAULT_MAX_LINES=5;const HIDDEN_MAX_LINES=10;function scrollToLoc(elt,loc,isHidden){const lines=elt.querySelector(".src-line-numbers");let scrollOffset;const maxLines=isHidden?HIDDEN_MAX_LINES:DEFAULT_MAX_LINES;if(loc[1]-loc[0]>maxLines){const line=Math.max(0,loc[0]-1);scrollOffset=lines.children[line].offsetTop}else{const wrapper=elt.querySelector(".code-wrapper");const halfHeight=wrapper.offsetHeight/2;const offsetTop=lines.children[loc[0]].offsetTop;const lastLine=lines.children[loc[1]];const offsetBot=lastLine.offsetTop+lastLine.offsetHeight;const offsetMid=(offsetTop+offsetBot)/2;scrollOffset=offsetMid-halfHeight}lines.scrollTo(0,scrollOffset);elt.querySelector(".rust").scrollTo(0,scrollOffset)}function updateScrapedExample(example,isHidden){const locs=JSON.parse(example.attributes.getNamedItem("data-locs").textContent);let locIndex=0;const highlights=Array.prototype.slice.call(example.querySelectorAll(".highlight"));const link=example.querySelector(".scraped-example-title a");if(locs.length>1){const onChangeLoc=changeIndex=>{removeClass(highlights[locIndex],"focus");changeIndex();scrollToLoc(example,locs[locIndex][0],isHidden);addClass(highlights[locIndex],"focus");const url=locs[locIndex][1];const title=locs[locIndex][2];link.href=url;link.innerHTML=title};example.querySelector(".prev").addEventListener("click",()=>{onChangeLoc(()=>{locIndex=(locIndex-1+locs.length)%locs.length})});example.querySelector(".next").addEventListener("click",()=>{onChangeLoc(()=>{locIndex=(locIndex+1)%locs.length})})}const expandButton=example.querySelector(".expand");if(expandButton){expandButton.addEventListener("click",()=>{if(hasClass(example,"expanded")){removeClass(example,"expanded");scrollToLoc(example,locs[0][0],isHidden)}else{addClass(example,"expanded")}})}scrollToLoc(example,locs[0][0],isHidden)}const firstExamples=document.querySelectorAll(".scraped-example-list > .scraped-example");onEachLazy(firstExamples,el=>updateScrapedExample(el,false));onEachLazy(document.querySelectorAll(".more-examples-toggle"),toggle=>{onEachLazy(toggle.querySelectorAll(".toggle-line, .hide-more"),button=>{button.addEventListener("click",()=>{toggle.open=false})});const moreExamples=toggle.querySelectorAll(".scraped-example");toggle.querySelector("summary").addEventListener("click",()=>{setTimeout(()=>{onEachLazy(moreExamples,el=>updateScrapedExample(el,true))})},{once:true})})})() \ No newline at end of file diff --git a/book/rust-docs/static.files/search-f6292fe389d70017.js b/book/rust-docs/static.files/search-f6292fe389d70017.js new file mode 100644 index 0000000000..c91355a613 --- /dev/null +++ b/book/rust-docs/static.files/search-f6292fe389d70017.js @@ -0,0 +1,5 @@ +"use strict";(function(){const itemTypes=["mod","externcrate","import","struct","enum","fn","type","static","trait","impl","tymethod","method","structfield","variant","macro","primitive","associatedtype","constant","associatedconstant","union","foreigntype","keyword","existential","attr","derive","traitalias",];const longItemTypes=["module","extern crate","re-export","struct","enum","function","type alias","static","trait","","trait method","method","struct field","enum variant","macro","primitive type","assoc type","constant","assoc const","union","foreign type","keyword","existential type","attribute macro","derive macro","trait alias",];const TY_PRIMITIVE=itemTypes.indexOf("primitive");const TY_KEYWORD=itemTypes.indexOf("keyword");const ROOT_PATH=typeof window!=="undefined"?window.rootPath:"../";function hasOwnPropertyRustdoc(obj,property){return Object.prototype.hasOwnProperty.call(obj,property)}function printTab(nb){let iter=0;let foundCurrentTab=false;let foundCurrentResultSet=false;onEachLazy(document.getElementById("search-tabs").childNodes,elem=>{if(nb===iter){addClass(elem,"selected");foundCurrentTab=true}else{removeClass(elem,"selected")}iter+=1});const isTypeSearch=(nb>0||iter===1);iter=0;onEachLazy(document.getElementById("results").childNodes,elem=>{if(nb===iter){addClass(elem,"active");foundCurrentResultSet=true}else{removeClass(elem,"active")}iter+=1});if(foundCurrentTab&&foundCurrentResultSet){searchState.currentTab=nb;const correctionsElem=document.getElementsByClassName("search-corrections");if(isTypeSearch){removeClass(correctionsElem[0],"hidden")}else{addClass(correctionsElem[0],"hidden")}}else if(nb!==0){printTab(0)}}const editDistanceState={current:[],prev:[],prevPrev:[],calculate:function calculate(a,b,limit){if(a.lengthlimit){return limit+1}while(b.length>0&&b[0]===a[0]){a=a.substring(1);b=b.substring(1)}while(b.length>0&&b[b.length-1]===a[a.length-1]){a=a.substring(0,a.length-1);b=b.substring(0,b.length-1)}if(b.length===0){return minDist}const aLength=a.length;const bLength=b.length;for(let i=0;i<=bLength;++i){this.current[i]=0;this.prev[i]=i;this.prevPrev[i]=Number.MAX_VALUE}for(let i=1;i<=aLength;++i){this.current[0]=i;const aIdx=i-1;for(let j=1;j<=bLength;++j){const bIdx=j-1;const substitutionCost=a[aIdx]===b[bIdx]?0:1;this.current[j]=Math.min(this.prev[j]+1,this.current[j-1]+1,this.prev[j-1]+substitutionCost);if((i>1)&&(j>1)&&(a[aIdx]===b[bIdx-1])&&(a[aIdx-1]===b[bIdx])){this.current[j]=Math.min(this.current[j],this.prevPrev[j-2]+1)}}const prevPrevTmp=this.prevPrev;this.prevPrev=this.prev;this.prev=this.current;this.current=prevPrevTmp}const distance=this.prev[bLength];return distance<=limit?distance:(limit+1)},};function editDistance(a,b,limit){return editDistanceState.calculate(a,b,limit)}function initSearch(rawSearchIndex){const MAX_RESULTS=200;const NO_TYPE_FILTER=-1;let searchIndex;let currentResults;let typeNameIdMap;const ALIASES=new Map();let typeNameIdOfArray;let typeNameIdOfSlice;let typeNameIdOfArrayOrSlice;function buildTypeMapIndex(name){if(name===""||name===null){return-1}if(typeNameIdMap.has(name)){return typeNameIdMap.get(name)}else{const id=typeNameIdMap.size;typeNameIdMap.set(name,id);return id}}function isWhitespace(c){return" \t\n\r".indexOf(c)!==-1}function isSpecialStartCharacter(c){return"<\"".indexOf(c)!==-1}function isEndCharacter(c){return",>-]".indexOf(c)!==-1}function isStopCharacter(c){return isEndCharacter(c)}function isErrorCharacter(c){return"()".indexOf(c)!==-1}function itemTypeFromName(typename){const index=itemTypes.findIndex(i=>i===typename);if(index<0){throw["Unknown type filter ",typename]}return index}function getStringElem(query,parserState,isInGenerics){if(isInGenerics){throw["Unexpected ","\""," in generics"]}else if(query.literalSearch){throw["Cannot have more than one literal search element"]}else if(parserState.totalElems-parserState.genericsElems>0){throw["Cannot use literal search when there is more than one element"]}parserState.pos+=1;const start=parserState.pos;const end=getIdentEndPosition(parserState);if(parserState.pos>=parserState.length){throw["Unclosed ","\""]}else if(parserState.userQuery[end]!=="\""){throw["Unexpected ",parserState.userQuery[end]," in a string element"]}else if(start===end){throw["Cannot have empty string element"]}parserState.pos+=1;query.literalSearch=true}function isPathStart(parserState){return parserState.userQuery.slice(parserState.pos,parserState.pos+2)==="::"}function isReturnArrow(parserState){return parserState.userQuery.slice(parserState.pos,parserState.pos+2)==="->"}function isIdentCharacter(c){return(c==="_"||(c>="0"&&c<="9")||(c>="a"&&c<="z")||(c>="A"&&c<="Z"))}function isSeparatorCharacter(c){return c===","}function isPathSeparator(c){return c===":"||isWhitespace(c)}function prevIs(parserState,lookingFor){let pos=parserState.pos;while(pos>0){const c=parserState.userQuery[pos-1];if(c===lookingFor){return true}else if(!isWhitespace(c)){break}pos-=1}return false}function isLastElemGeneric(elems,parserState){return(elems.length>0&&elems[elems.length-1].generics.length>0)||prevIs(parserState,">")}function skipWhitespace(parserState){while(parserState.pos0){throw["Cannot have more than one element if you use quotes"]}const typeFilter=parserState.typeFilter;parserState.typeFilter=null;if(name==="!"){if(typeFilter!==null&&typeFilter!=="primitive"){throw["Invalid search type: primitive never type ","!"," and ",typeFilter," both specified",]}if(generics.length!==0){throw["Never type ","!"," does not accept generic parameters",]}return{name:"never",id:-1,fullPath:["never"],pathWithoutLast:[],pathLast:"never",generics:[],typeFilter:"primitive",}}if(path.startsWith("::")){throw["Paths cannot start with ","::"]}else if(path.endsWith("::")){throw["Paths cannot end with ","::"]}else if(path.includes("::::")){throw["Unexpected ","::::"]}else if(path.includes(" ::")){throw["Unexpected "," ::"]}else if(path.includes(":: ")){throw["Unexpected ",":: "]}const pathSegments=path.split(/::|\s+/);if(pathSegments.length===0||(pathSegments.length===1&&pathSegments[0]==="")){if(generics.length>0||prevIs(parserState,">")){throw["Found generics without a path"]}else{throw["Unexpected ",parserState.userQuery[parserState.pos]]}}for(const[i,pathSegment]of pathSegments.entries()){if(pathSegment==="!"){if(i!==0){throw["Never type ","!"," is not associated item"]}pathSegments[i]="never"}}parserState.totalElems+=1;if(isInGenerics){parserState.genericsElems+=1}return{name:name.trim(),id:-1,fullPath:pathSegments,pathWithoutLast:pathSegments.slice(0,pathSegments.length-1),pathLast:pathSegments[pathSegments.length-1],generics:generics,typeFilter,}}function getIdentEndPosition(parserState){const start=parserState.pos;let end=parserState.pos;let foundExclamation=-1;while(parserState.pos=end){throw["Found generics without a path"]}parserState.pos+=1;getItemsBefore(query,parserState,generics,">")}if(isStringElem){skipWhitespace(parserState)}if(start>=end&&generics.length===0){return}elems.push(createQueryElement(query,parserState,parserState.userQuery.slice(start,end),generics,isInGenerics))}}function getItemsBefore(query,parserState,elems,endChar){let foundStopChar=true;let start=parserState.pos;const oldTypeFilter=parserState.typeFilter;parserState.typeFilter=null;let extra="";if(endChar===">"){extra="<"}else if(endChar==="]"){extra="["}else if(endChar===""){extra="->"}else{extra=endChar}while(parserState.pos"]}else if(prevIs(parserState,"\"")){throw["Cannot have more than one element if you use quotes"]}if(endChar!==""){throw["Expected ",","," or ",endChar,...extra,", found ",c,]}throw["Expected ",",",...extra,", found ",c,]}const posBefore=parserState.pos;start=parserState.pos;getNextElem(query,parserState,elems,endChar!=="");if(endChar!==""&&parserState.pos>=parserState.length){throw["Unclosed ",extra]}if(posBefore===parserState.pos){parserState.pos+=1}foundStopChar=false}if(parserState.pos>=parserState.length&&endChar!==""){throw["Unclosed ",extra]}parserState.pos+=1;parserState.typeFilter=oldTypeFilter}function checkExtraTypeFilterCharacters(start,parserState){const query=parserState.userQuery.slice(start,parserState.pos).trim();for(const c in query){if(!isIdentCharacter(query[c])){throw["Unexpected ",query[c]," in type filter (before ",":",")",]}}}function parseInput(query,parserState){let foundStopChar=true;let start=parserState.pos;while(parserState.pos"){if(isReturnArrow(parserState)){break}throw["Unexpected ",c," (did you mean ","->","?)"]}throw["Unexpected ",c]}else if(c===":"&&!isPathStart(parserState)){if(parserState.typeFilter!==null){throw["Unexpected ",":"," (expected path after type filter ",parserState.typeFilter+":",")",]}else if(query.elems.length===0){throw["Expected type filter before ",":"]}else if(query.literalSearch){throw["Cannot use quotes on type filter"]}const typeFilterElem=query.elems.pop();checkExtraTypeFilterCharacters(start,parserState);parserState.typeFilter=typeFilterElem.name;parserState.pos+=1;parserState.totalElems-=1;query.literalSearch=false;foundStopChar=true;continue}else if(isWhitespace(c)){skipWhitespace(parserState);continue}if(!foundStopChar){let extra="";if(isLastElemGeneric(query.elems,parserState)){extra=[" after ",">"]}else if(prevIs(parserState,"\"")){throw["Cannot have more than one element if you use quotes"]}if(parserState.typeFilter!==null){throw["Expected ",","," or ","->",...extra,", found ",c,]}throw["Expected ",",",", ",":"," or ","->",...extra,", found ",c,]}const before=query.elems.length;start=parserState.pos;getNextElem(query,parserState,query.elems,false);if(query.elems.length===before){parserState.pos+=1}foundStopChar=false}if(parserState.typeFilter!==null){throw["Unexpected ",":"," (expected path after type filter ",parserState.typeFilter+":",")",]}while(parserState.pos"]}break}else{parserState.pos+=1}}}function newParsedQuery(userQuery){return{original:userQuery,userQuery:userQuery.toLowerCase(),elems:[],returned:[],foundElems:0,literalSearch:false,error:null,correction:null,}}function buildUrl(search,filterCrates){let extra="?search="+encodeURIComponent(search);if(filterCrates!==null){extra+="&filter-crate="+encodeURIComponent(filterCrates)}return getNakedUrl()+extra+window.location.hash}function getFilterCrates(){const elem=document.getElementById("crate-search");if(elem&&elem.value!=="all crates"&&hasOwnPropertyRustdoc(rawSearchIndex,elem.value)){return elem.value}return null}function parseQuery(userQuery){function convertTypeFilterOnElem(elem){if(elem.typeFilter!==null){let typeFilter=elem.typeFilter;if(typeFilter==="const"){typeFilter="constant"}elem.typeFilter=itemTypeFromName(typeFilter)}else{elem.typeFilter=NO_TYPE_FILTER}for(const elem2 of elem.generics){convertTypeFilterOnElem(elem2)}}userQuery=userQuery.trim();const parserState={length:userQuery.length,pos:0,totalElems:0,genericsElems:0,typeFilter:null,userQuery:userQuery.toLowerCase(),};let query=newParsedQuery(userQuery);try{parseInput(query,parserState);for(const elem of query.elems){convertTypeFilterOnElem(elem)}for(const elem of query.returned){convertTypeFilterOnElem(elem)}}catch(err){query=newParsedQuery(userQuery);query.error=err;return query}if(!query.literalSearch){query.literalSearch=parserState.totalElems>1}query.foundElems=query.elems.length+query.returned.length;return query}function createQueryResults(results_in_args,results_returned,results_others,parsedQuery){return{"in_args":results_in_args,"returned":results_returned,"others":results_others,"query":parsedQuery,}}function execQuery(parsedQuery,searchWords,filterCrates,currentCrate){const results_others=new Map(),results_in_args=new Map(),results_returned=new Map();function transformResults(results){const duplicates=new Set();const out=[];for(const result of results){if(result.id>-1){const obj=searchIndex[result.id];obj.dist=result.dist;const res=buildHrefAndPath(obj);obj.displayPath=pathSplitter(res[0]);obj.fullPath=obj.displayPath+obj.name;obj.fullPath+="|"+obj.ty;if(duplicates.has(obj.fullPath)){continue}duplicates.add(obj.fullPath);obj.href=res[1];out.push(obj);if(out.length>=MAX_RESULTS){break}}}return out}function sortResults(results,isType,preferredCrate){if(results.size===0){return[]}const userQuery=parsedQuery.userQuery;const result_list=[];for(const result of results.values()){result.word=searchWords[result.id];result.item=searchIndex[result.id]||{};result_list.push(result)}result_list.sort((aaa,bbb)=>{let a,b;a=(aaa.word!==userQuery);b=(bbb.word!==userQuery);if(a!==b){return a-b}a=(aaa.index<0);b=(bbb.index<0);if(a!==b){return a-b}a=aaa.path_dist;b=bbb.path_dist;if(a!==b){return a-b}a=aaa.index;b=bbb.index;if(a!==b){return a-b}a=(aaa.dist);b=(bbb.dist);if(a!==b){return a-b}a=aaa.item.deprecated;b=bbb.item.deprecated;if(a!==b){return a-b}a=(aaa.item.crate!==preferredCrate);b=(bbb.item.crate!==preferredCrate);if(a!==b){return a-b}a=aaa.word.length;b=bbb.word.length;if(a!==b){return a-b}a=aaa.word;b=bbb.word;if(a!==b){return(a>b?+1:-1)}if((aaa.item.ty===TY_PRIMITIVE&&bbb.item.ty!==TY_KEYWORD)||(aaa.item.ty===TY_KEYWORD&&bbb.item.ty!==TY_PRIMITIVE)){return-1}if((bbb.item.ty===TY_PRIMITIVE&&aaa.item.ty!==TY_PRIMITIVE)||(bbb.item.ty===TY_KEYWORD&&aaa.item.ty!==TY_KEYWORD)){return 1}a=(aaa.item.desc==="");b=(bbb.item.desc==="");if(a!==b){return a-b}a=aaa.item.ty;b=bbb.item.ty;if(a!==b){return a-b}a=aaa.item.path;b=bbb.item.path;if(a!==b){return(a>b?+1:-1)}return 0});let nameSplit=null;if(parsedQuery.elems.length===1){const hasPath=typeof parsedQuery.elems[0].path==="undefined";nameSplit=hasPath?null:parsedQuery.elems[0].path}for(const result of result_list){if(result.dontValidate){continue}const name=result.item.name.toLowerCase(),path=result.item.path.toLowerCase(),parent=result.item.parent;if(!isType&&!validateResult(name,path,nameSplit,parent)){result.id=-1}}return transformResults(result_list)}function checkGenerics(fnType,queryElem){return unifyFunctionTypes(fnType.generics,queryElem.generics)}function unifyFunctionTypes(fnTypes,queryElems){if(queryElems.length===0){return true}if(!fnTypes||fnTypes.length===0){return false}const queryElemSet=new Map();const addQueryElemToQueryElemSet=function addQueryElemToQueryElemSet(queryElem){let currentQueryElemList;if(queryElemSet.has(queryElem.id)){currentQueryElemList=queryElemSet.get(queryElem.id)}else{currentQueryElemList=[];queryElemSet.set(queryElem.id,currentQueryElemList)}currentQueryElemList.push(queryElem)};for(const queryElem of queryElems){addQueryElemToQueryElemSet(queryElem)}const fnTypeSet=new Map();const addFnTypeToFnTypeSet=function addFnTypeToFnTypeSet(fnType){const queryContainsArrayOrSliceElem=queryElemSet.has(typeNameIdOfArrayOrSlice);if(fnType.id===-1||!(queryElemSet.has(fnType.id)||(fnType.id===typeNameIdOfSlice&&queryContainsArrayOrSliceElem)||(fnType.id===typeNameIdOfArray&&queryContainsArrayOrSliceElem))){for(const innerFnType of fnType.generics){addFnTypeToFnTypeSet(innerFnType)}return}let currentQueryElemList=queryElemSet.get(fnType.id)||[];let matchIdx=currentQueryElemList.findIndex(queryElem=>{return typePassesFilter(queryElem.typeFilter,fnType.ty)&&checkGenerics(fnType,queryElem)});if(matchIdx===-1&&(fnType.id===typeNameIdOfSlice||fnType.id===typeNameIdOfArray)&&queryContainsArrayOrSliceElem){currentQueryElemList=queryElemSet.get(typeNameIdOfArrayOrSlice)||[];matchIdx=currentQueryElemList.findIndex(queryElem=>{return typePassesFilter(queryElem.typeFilter,fnType.ty)&&checkGenerics(fnType,queryElem)})}if(matchIdx===-1){for(const innerFnType of fnType.generics){addFnTypeToFnTypeSet(innerFnType)}return}let currentFnTypeList;if(fnTypeSet.has(fnType.id)){currentFnTypeList=fnTypeSet.get(fnType.id)}else{currentFnTypeList=[];fnTypeSet.set(fnType.id,currentFnTypeList)}currentFnTypeList.push(fnType)};for(const fnType of fnTypes){addFnTypeToFnTypeSet(fnType)}const doHandleQueryElemList=(currentFnTypeList,queryElemList)=>{if(queryElemList.length===0){return true}const queryElem=queryElemList.pop();const l=currentFnTypeList.length;for(let i=0;i{if(!fnTypeSet.has(id)){if(id===typeNameIdOfArrayOrSlice){return handleQueryElemList(typeNameIdOfSlice,queryElemList)||handleQueryElemList(typeNameIdOfArray,queryElemList)}return false}const currentFnTypeList=fnTypeSet.get(id);if(currentFnTypeList.length0?checkIfInList(row.generics,elem):false}const matchesExact=row.id===elem.id;const matchesArrayOrSlice=elem.id===typeNameIdOfArrayOrSlice&&(row.id===typeNameIdOfSlice||row.id===typeNameIdOfArray);if((matchesExact||matchesArrayOrSlice)&&typePassesFilter(elem.typeFilter,row.ty)){if(elem.generics.length>0){return checkGenerics(row,elem)}return true}return checkIfInList(row.generics,elem)}function checkPath(contains,ty,maxEditDistance){if(contains.length===0){return 0}let ret_dist=maxEditDistance+1;const path=ty.path.split("::");if(ty.parent&&ty.parent.name){path.push(ty.parent.name.toLowerCase())}const length=path.length;const clength=contains.length;if(clength>length){return maxEditDistance+1}for(let i=0;ilength){break}let dist_total=0;let aborted=false;for(let x=0;xmaxEditDistance){aborted=true;break}dist_total+=dist}if(!aborted){ret_dist=Math.min(ret_dist,Math.round(dist_total/clength))}}return ret_dist}function typePassesFilter(filter,type){if(filter<=NO_TYPE_FILTER||filter===type)return true;const name=itemTypes[type];switch(itemTypes[filter]){case"constant":return name==="associatedconstant";case"fn":return name==="method"||name==="tymethod";case"type":return name==="primitive"||name==="associatedtype";case"trait":return name==="traitalias"}return false}function createAliasFromItem(item){return{crate:item.crate,name:item.name,path:item.path,desc:item.desc,ty:item.ty,parent:item.parent,type:item.type,is_alias:true,deprecated:item.deprecated,}}function handleAliases(ret,query,filterCrates,currentCrate){const lowerQuery=query.toLowerCase();const aliases=[];const crateAliases=[];if(filterCrates!==null){if(ALIASES.has(filterCrates)&&ALIASES.get(filterCrates).has(lowerQuery)){const query_aliases=ALIASES.get(filterCrates).get(lowerQuery);for(const alias of query_aliases){aliases.push(createAliasFromItem(searchIndex[alias]))}}}else{for(const[crate,crateAliasesIndex]of ALIASES){if(crateAliasesIndex.has(lowerQuery)){const pushTo=crate===currentCrate?crateAliases:aliases;const query_aliases=crateAliasesIndex.get(lowerQuery);for(const alias of query_aliases){pushTo.push(createAliasFromItem(searchIndex[alias]))}}}}const sortFunc=(aaa,bbb)=>{if(aaa.path{alias.alias=query;const res=buildHrefAndPath(alias);alias.displayPath=pathSplitter(res[0]);alias.fullPath=alias.displayPath+alias.name;alias.href=res[1];ret.others.unshift(alias);if(ret.others.length>MAX_RESULTS){ret.others.pop()}};aliases.forEach(pushFunc);crateAliases.forEach(pushFunc)}function addIntoResults(results,fullId,id,index,dist,path_dist,maxEditDistance){const inBounds=dist<=maxEditDistance||index!==-1;if(dist===0||(!parsedQuery.literalSearch&&inBounds)){if(results.has(fullId)){const result=results.get(fullId);if(result.dontValidate||result.dist<=dist){return}}results.set(fullId,{id:id,index:index,dontValidate:parsedQuery.literalSearch,dist:dist,path_dist:path_dist,})}}function handleSingleArg(row,pos,elem,results_others,results_in_args,results_returned,maxEditDistance){if(!row||(filterCrates!==null&&row.crate!==filterCrates)){return}let index=-1,path_dist=0;const fullId=row.id;const searchWord=searchWords[pos];const in_args=row.type&&row.type.inputs&&checkIfInList(row.type.inputs,elem);if(in_args){addIntoResults(results_in_args,fullId,pos,-1,0,0,maxEditDistance)}const returned=row.type&&row.type.output&&checkIfInList(row.type.output,elem);if(returned){addIntoResults(results_returned,fullId,pos,-1,0,0,maxEditDistance)}if(!typePassesFilter(elem.typeFilter,row.ty)){return}const row_index=row.normalizedName.indexOf(elem.pathLast);const word_index=searchWord.indexOf(elem.pathLast);if(row_index===-1){index=word_index}else if(word_index===-1){index=row_index}else if(word_index1){path_dist=checkPath(elem.pathWithoutLast,row,maxEditDistance);if(path_dist>maxEditDistance){return}}if(parsedQuery.literalSearch){if(searchWord===elem.name){addIntoResults(results_others,fullId,pos,index,0,path_dist)}return}const dist=editDistance(searchWord,elem.pathLast,maxEditDistance);if(index===-1&&dist+path_dist>maxEditDistance){return}addIntoResults(results_others,fullId,pos,index,dist,path_dist,maxEditDistance)}function handleArgs(row,pos,results){if(!row||(filterCrates!==null&&row.crate!==filterCrates)||!row.type){return}if(!unifyFunctionTypes(row.type.inputs,parsedQuery.elems)){return}if(!unifyFunctionTypes(row.type.output,parsedQuery.returned)){return}addIntoResults(results,row.id,pos,0,0,0,Number.MAX_VALUE)}function innerRunQuery(){let elem,i,nSearchWords,in_returned,row;let queryLen=0;for(const elem of parsedQuery.elems){queryLen+=elem.name.length}for(const elem of parsedQuery.returned){queryLen+=elem.name.length}const maxEditDistance=Math.floor(queryLen/3);function convertNameToId(elem){if(typeNameIdMap.has(elem.name)){elem.id=typeNameIdMap.get(elem.name)}else if(!parsedQuery.literalSearch){let match=-1;let matchDist=maxEditDistance+1;let matchName="";for(const[name,id]of typeNameIdMap){const dist=editDistance(name,elem.name,maxEditDistance);if(dist<=matchDist&&dist<=maxEditDistance){if(dist===matchDist&&matchName>name){continue}match=id;matchDist=dist;matchName=name}}if(match!==-1){parsedQuery.correction=matchName}elem.id=match}for(const elem2 of elem.generics){convertNameToId(elem2)}}for(const elem of parsedQuery.elems){convertNameToId(elem)}for(const elem of parsedQuery.returned){convertNameToId(elem)}if(parsedQuery.foundElems===1){if(parsedQuery.elems.length===1){elem=parsedQuery.elems[0];for(i=0,nSearchWords=searchWords.length;i0){for(i=0,nSearchWords=searchWords.length;i-1||path.indexOf(key)>-1||(parent!==undefined&&parent.name!==undefined&&parent.name.toLowerCase().indexOf(key)>-1)||editDistance(name,key,maxEditDistance)<=maxEditDistance)){return false}}return true}function nextTab(direction){const next=(searchState.currentTab+direction+3)%searchState.focusedByTab.length;searchState.focusedByTab[searchState.currentTab]=document.activeElement;printTab(next);focusSearchResult()}function focusSearchResult(){const target=searchState.focusedByTab[searchState.currentTab]||document.querySelectorAll(".search-results.active a").item(0)||document.querySelectorAll("#search-tabs button").item(searchState.currentTab);searchState.focusedByTab[searchState.currentTab]=null;if(target){target.focus()}}function buildHrefAndPath(item){let displayPath;let href;const type=itemTypes[item.ty];const name=item.name;let path=item.path;if(type==="mod"){displayPath=path+"::";href=ROOT_PATH+path.replace(/::/g,"/")+"/"+name+"/index.html"}else if(type==="import"){displayPath=item.path+"::";href=ROOT_PATH+item.path.replace(/::/g,"/")+"/index.html#reexport."+name}else if(type==="primitive"||type==="keyword"){displayPath="";href=ROOT_PATH+path.replace(/::/g,"/")+"/"+type+"."+name+".html"}else if(type==="externcrate"){displayPath="";href=ROOT_PATH+name+"/index.html"}else if(item.parent!==undefined){const myparent=item.parent;let anchor="#"+type+"."+name;const parentType=itemTypes[myparent.ty];let pageType=parentType;let pageName=myparent.name;if(parentType==="primitive"){displayPath=myparent.name+"::"}else if(type==="structfield"&&parentType==="variant"){const enumNameIdx=item.path.lastIndexOf("::");const enumName=item.path.substr(enumNameIdx+2);path=item.path.substr(0,enumNameIdx);displayPath=path+"::"+enumName+"::"+myparent.name+"::";anchor="#variant."+myparent.name+".field."+name;pageType="enum";pageName=enumName}else{displayPath=path+"::"+myparent.name+"::"}href=ROOT_PATH+path.replace(/::/g,"/")+"/"+pageType+"."+pageName+".html"+anchor}else{displayPath=item.path+"::";href=ROOT_PATH+item.path.replace(/::/g,"/")+"/"+type+"."+name+".html"}return[displayPath,href]}function pathSplitter(path){const tmp=""+path.replace(/::/g,"::");if(tmp.endsWith("")){return tmp.slice(0,tmp.length-6)}return tmp}function addTab(array,query,display){let extraClass="";if(display===true){extraClass=" active"}const output=document.createElement("div");let length=0;if(array.length>0){output.className="search-results "+extraClass;array.forEach(item=>{const name=item.name;const type=itemTypes[item.ty];const longType=longItemTypes[item.ty];const typeName=longType.length!==0?`${longType}`:"?";length+=1;const link=document.createElement("a");link.className="result-"+type;link.href=item.href;const resultName=document.createElement("div");resultName.className="result-name";if(item.is_alias){const alias=document.createElement("span");alias.className="alias";const bold=document.createElement("b");bold.innerText=item.alias;alias.appendChild(bold);alias.insertAdjacentHTML("beforeend"," - see ");resultName.appendChild(alias)}resultName.insertAdjacentHTML("beforeend",`\ +${typeName}\ +
\ + ${item.displayPath}${name}\ +
`);link.appendChild(resultName);const description=document.createElement("div");description.className="desc";description.insertAdjacentHTML("beforeend",item.desc);link.appendChild(description);output.appendChild(link)})}else if(query.error===null){output.className="search-failed"+extraClass;output.innerHTML="No results :(
"+"Try on DuckDuckGo?

"+"Or try looking in one of these:"}return[output,length]}function makeTabHeader(tabNb,text,nbElems){if(searchState.currentTab===tabNb){return""}return""}function showResults(results,go_to_first,filterCrates){const search=searchState.outputElement();if(go_to_first||(results.others.length===1&&getSettingValue("go-to-only-result")==="true")){window.onunload=()=>{};searchState.removeQueryParameters();const elem=document.createElement("a");elem.href=results.others[0].href;removeClass(elem,"active");document.body.appendChild(elem);elem.click();return}if(results.query===undefined){results.query=parseQuery(searchState.input.value)}currentResults=results.query.userQuery;const ret_others=addTab(results.others,results.query,true);const ret_in_args=addTab(results.in_args,results.query,false);const ret_returned=addTab(results.returned,results.query,false);let currentTab=searchState.currentTab;if((currentTab===0&&ret_others[1]===0)||(currentTab===1&&ret_in_args[1]===0)||(currentTab===2&&ret_returned[1]===0)){if(ret_others[1]!==0){currentTab=0}else if(ret_in_args[1]!==0){currentTab=1}else if(ret_returned[1]!==0){currentTab=2}}let crates="";const crates_list=Object.keys(rawSearchIndex);if(crates_list.length>1){crates=" in 
"}let output=`

Results${crates}

`;if(results.query.error!==null){const error=results.query.error;error.forEach((value,index)=>{value=value.split("<").join("<").split(">").join(">");if(index%2!==0){error[index]=`${value.replaceAll(" ", " ")}`}else{error[index]=value}});output+=`

Query parser error: "${error.join("")}".

`;output+="
"+makeTabHeader(0,"In Names",ret_others[1])+"
";currentTab=0}else if(results.query.foundElems<=1&&results.query.returned.length===0){output+="
"+makeTabHeader(0,"In Names",ret_others[1])+makeTabHeader(1,"In Parameters",ret_in_args[1])+makeTabHeader(2,"In Return Types",ret_returned[1])+"
"}else{const signatureTabTitle=results.query.elems.length===0?"In Function Return Types":results.query.returned.length===0?"In Function Parameters":"In Function Signatures";output+="
"+makeTabHeader(0,signatureTabTitle,ret_others[1])+"
";currentTab=0}if(results.query.correction!==null){const orig=results.query.returned.length>0?results.query.returned[0].name:results.query.elems[0].name;output+="

"+`Type "${orig}" not found. `+"Showing results for closest type name "+`"${results.query.correction}" instead.

`}const resultsElem=document.createElement("div");resultsElem.id="results";resultsElem.appendChild(ret_others[0]);resultsElem.appendChild(ret_in_args[0]);resultsElem.appendChild(ret_returned[0]);search.innerHTML=output;const crateSearch=document.getElementById("crate-search");if(crateSearch){crateSearch.addEventListener("input",updateCrate)}search.appendChild(resultsElem);searchState.showResults(search);const elems=document.getElementById("search-tabs").childNodes;searchState.focusedByTab=[];let i=0;for(const elem of elems){const j=i;elem.onclick=()=>printTab(j);searchState.focusedByTab.push(null);i+=1}printTab(currentTab)}function updateSearchHistory(url){if(!browserSupportsHistoryApi()){return}const params=searchState.getQueryStringParams();if(!history.state&&!params.search){history.pushState(null,"",url)}else{history.replaceState(null,"",url)}}function search(e,forced){if(e){e.preventDefault()}const query=parseQuery(searchState.input.value.trim());let filterCrates=getFilterCrates();if(!forced&&query.userQuery===currentResults){if(query.userQuery.length>0){putBackSearch()}return}searchState.setLoadingSearch();const params=searchState.getQueryStringParams();if(filterCrates===null&¶ms["filter-crate"]!==undefined){filterCrates=params["filter-crate"]}searchState.title="Results for "+query.original+" - Rust";updateSearchHistory(buildUrl(query.original,filterCrates));showResults(execQuery(query,searchWords,filterCrates,window.currentCrate),params.go_to_first,filterCrates)}function buildItemSearchTypeAll(types,lowercasePaths){const PATH_INDEX_DATA=0;const GENERICS_DATA=1;return types.map(type=>{let pathIndex,generics;if(typeof type==="number"){pathIndex=type;generics=[]}else{pathIndex=type[PATH_INDEX_DATA];generics=buildItemSearchTypeAll(type[GENERICS_DATA],lowercasePaths)}return{id:pathIndex===0?-1:buildTypeMapIndex(lowercasePaths[pathIndex-1].name),ty:pathIndex===0?null:lowercasePaths[pathIndex-1].ty,generics:generics,}})}function buildFunctionSearchType(functionSearchType,lowercasePaths){const INPUTS_DATA=0;const OUTPUT_DATA=1;if(functionSearchType===0){return null}let inputs,output;if(typeof functionSearchType[INPUTS_DATA]==="number"){const pathIndex=functionSearchType[INPUTS_DATA];inputs=[{id:pathIndex===0?-1:buildTypeMapIndex(lowercasePaths[pathIndex-1].name),ty:pathIndex===0?null:lowercasePaths[pathIndex-1].ty,generics:[],}]}else{inputs=buildItemSearchTypeAll(functionSearchType[INPUTS_DATA],lowercasePaths)}if(functionSearchType.length>1){if(typeof functionSearchType[OUTPUT_DATA]==="number"){const pathIndex=functionSearchType[OUTPUT_DATA];output=[{id:pathIndex===0?-1:buildTypeMapIndex(lowercasePaths[pathIndex-1].name),ty:pathIndex===0?null:lowercasePaths[pathIndex-1].ty,generics:[],}]}else{output=buildItemSearchTypeAll(functionSearchType[OUTPUT_DATA],lowercasePaths)}}else{output=[]}return{inputs,output,}}function buildIndex(rawSearchIndex){searchIndex=[];const searchWords=[];typeNameIdMap=new Map();const charA="A".charCodeAt(0);let currentIndex=0;let id=0;typeNameIdOfArray=buildTypeMapIndex("array");typeNameIdOfSlice=buildTypeMapIndex("slice");typeNameIdOfArrayOrSlice=buildTypeMapIndex("[]");for(const crate in rawSearchIndex){if(!hasOwnPropertyRustdoc(rawSearchIndex,crate)){continue}let crateSize=0;const crateCorpus=rawSearchIndex[crate];searchWords.push(crate);const crateRow={crate:crate,ty:1,name:crate,path:"",desc:crateCorpus.doc,parent:undefined,type:null,id:id,normalizedName:crate.indexOf("_")===-1?crate:crate.replace(/_/g,""),deprecated:null,};id+=1;searchIndex.push(crateRow);currentIndex+=1;const itemTypes=crateCorpus.t;const itemNames=crateCorpus.n;const itemPaths=new Map(crateCorpus.q);const itemDescs=crateCorpus.d;const itemParentIdxs=crateCorpus.i;const itemFunctionSearchTypes=crateCorpus.f;const deprecatedItems=new Set(crateCorpus.c);const paths=crateCorpus.p;const aliases=crateCorpus.a;const lowercasePaths=[];let len=paths.length;for(let i=0;i0?paths[itemParentIdxs[i]-1]:undefined,type:buildFunctionSearchType(itemFunctionSearchTypes[i],lowercasePaths),id:id,normalizedName:word.indexOf("_")===-1?word:word.replace(/_/g,""),deprecated:deprecatedItems.has(i),};id+=1;searchIndex.push(row);lastPath=row.path;crateSize+=1}if(aliases){const currentCrateAliases=new Map();ALIASES.set(crate,currentCrateAliases);for(const alias_name in aliases){if(!hasOwnPropertyRustdoc(aliases,alias_name)){continue}let currentNameAliases;if(currentCrateAliases.has(alias_name)){currentNameAliases=currentCrateAliases.get(alias_name)}else{currentNameAliases=[];currentCrateAliases.set(alias_name,currentNameAliases)}for(const local_alias of aliases[alias_name]){currentNameAliases.push(local_alias+currentIndex)}}}currentIndex+=crateSize}return searchWords}function onSearchSubmit(e){e.preventDefault();searchState.clearInputTimeout();search()}function putBackSearch(){const search_input=searchState.input;if(!searchState.input){return}if(search_input.value!==""&&!searchState.isDisplayed()){searchState.showResults();if(browserSupportsHistoryApi()){history.replaceState(null,"",buildUrl(search_input.value,getFilterCrates()))}document.title=searchState.title}}function registerSearchEvents(){const params=searchState.getQueryStringParams();if(searchState.input.value===""){searchState.input.value=params.search||""}const searchAfter500ms=()=>{searchState.clearInputTimeout();if(searchState.input.value.length===0){searchState.hideResults()}else{searchState.timeout=setTimeout(search,500)}};searchState.input.onkeyup=searchAfter500ms;searchState.input.oninput=searchAfter500ms;document.getElementsByClassName("search-form")[0].onsubmit=onSearchSubmit;searchState.input.onchange=e=>{if(e.target!==document.activeElement){return}searchState.clearInputTimeout();setTimeout(search,0)};searchState.input.onpaste=searchState.input.onchange;searchState.outputElement().addEventListener("keydown",e=>{if(e.altKey||e.ctrlKey||e.shiftKey||e.metaKey){return}if(e.which===38){const previous=document.activeElement.previousElementSibling;if(previous){previous.focus()}else{searchState.focus()}e.preventDefault()}else if(e.which===40){const next=document.activeElement.nextElementSibling;if(next){next.focus()}const rect=document.activeElement.getBoundingClientRect();if(window.innerHeight-rect.bottom{if(e.which===40){focusSearchResult();e.preventDefault()}});searchState.input.addEventListener("focus",()=>{putBackSearch()});searchState.input.addEventListener("blur",()=>{searchState.input.placeholder=searchState.input.origPlaceholder});if(browserSupportsHistoryApi()){const previousTitle=document.title;window.addEventListener("popstate",e=>{const params=searchState.getQueryStringParams();document.title=previousTitle;currentResults=null;if(params.search&¶ms.search.length>0){searchState.input.value=params.search;search(e)}else{searchState.input.value="";searchState.hideResults()}})}window.onpageshow=()=>{const qSearch=searchState.getQueryStringParams().search;if(searchState.input.value===""&&qSearch){searchState.input.value=qSearch}search()}}function updateCrate(ev){if(ev.target.value==="all crates"){const query=searchState.input.value.trim();updateSearchHistory(buildUrl(query,null))}currentResults=null;search(undefined,true)}const searchWords=buildIndex(rawSearchIndex);if(typeof window!=="undefined"){registerSearchEvents();if(window.searchState.getQueryStringParams().search){search()}}if(typeof exports!=="undefined"){exports.initSearch=initSearch;exports.execQuery=execQuery;exports.parseQuery=parseQuery}return searchWords}if(typeof window!=="undefined"){window.initSearch=initSearch;if(window.searchIndex!==undefined){initSearch(window.searchIndex)}}else{initSearch({})}})() \ No newline at end of file diff --git a/book/rust-docs/static.files/settings-8c76f75bfb6bd192.css b/book/rust-docs/static.files/settings-8c76f75bfb6bd192.css new file mode 100644 index 0000000000..5241bb861b --- /dev/null +++ b/book/rust-docs/static.files/settings-8c76f75bfb6bd192.css @@ -0,0 +1,3 @@ +.setting-line{margin:1.2em 0.6em;}.setting-radio input,.setting-check input{margin-right:0.3em;height:1.2rem;width:1.2rem;border:2px solid var(--settings-input-border-color);outline:none;-webkit-appearance:none;cursor:pointer;}.setting-radio input{border-radius:50%;}.setting-radio span,.setting-check span{padding-bottom:1px;}.setting-radio{margin-top:0.1em;margin-bottom:0.1em;min-width:3.8em;padding:0.3em;display:inline-flex;align-items:center;cursor:pointer;}.setting-radio+.setting-radio{margin-left:0.5em;}.setting-check{margin-right:20px;display:flex;align-items:center;cursor:pointer;}.setting-radio input:checked{box-shadow:inset 0 0 0 3px var(--main-background-color);background-color:var(--settings-input-color);}.setting-check input:checked{background-color:var(--settings-input-color);border-width:1px;content:url('data:image/svg+xml,\ + \ + ');}.setting-radio input:focus,.setting-check input:focus{box-shadow:0 0 1px 1px var(--settings-input-color);}.setting-radio input:checked:focus{box-shadow:inset 0 0 0 3px var(--main-background-color),0 0 2px 2px var(--settings-input-color);}.setting-radio input:hover,.setting-check input:hover{border-color:var(--settings-input-color) !important;} \ No newline at end of file diff --git a/book/rust-docs/static.files/settings-de11bff964e9d4e5.js b/book/rust-docs/static.files/settings-de11bff964e9d4e5.js new file mode 100644 index 0000000000..cc508a861c --- /dev/null +++ b/book/rust-docs/static.files/settings-de11bff964e9d4e5.js @@ -0,0 +1,17 @@ +"use strict";(function(){const isSettingsPage=window.location.pathname.endsWith("/settings.html");function changeSetting(settingName,value){if(settingName==="theme"){const useSystem=value==="system preference"?"true":"false";updateLocalStorage("use-system-theme",useSystem)}updateLocalStorage(settingName,value);switch(settingName){case"theme":case"preferred-dark-theme":case"preferred-light-theme":updateTheme();updateLightAndDark();break;case"line-numbers":if(value===true){window.rustdoc_add_line_numbers_to_examples()}else{window.rustdoc_remove_line_numbers_from_examples()}break}}function showLightAndDark(){removeClass(document.getElementById("preferred-light-theme"),"hidden");removeClass(document.getElementById("preferred-dark-theme"),"hidden")}function hideLightAndDark(){addClass(document.getElementById("preferred-light-theme"),"hidden");addClass(document.getElementById("preferred-dark-theme"),"hidden")}function updateLightAndDark(){const useSystem=getSettingValue("use-system-theme");if(useSystem==="true"||(useSystem===null&&getSettingValue("theme")===null)){showLightAndDark()}else{hideLightAndDark()}}function setEvents(settingsElement){updateLightAndDark();onEachLazy(settingsElement.querySelectorAll("input[type=\"checkbox\"]"),toggle=>{const settingId=toggle.id;const settingValue=getSettingValue(settingId);if(settingValue!==null){toggle.checked=settingValue==="true"}toggle.onchange=function(){changeSetting(this.id,this.checked)}});onEachLazy(settingsElement.querySelectorAll("input[type=\"radio\"]"),elem=>{const settingId=elem.name;let settingValue=getSettingValue(settingId);if(settingId==="theme"){const useSystem=getSettingValue("use-system-theme");if(useSystem==="true"||settingValue===null){settingValue=useSystem==="false"?"light":"system preference"}}if(settingValue!==null&&settingValue!=="null"){elem.checked=settingValue===elem.value}elem.addEventListener("change",ev=>{changeSetting(ev.target.name,ev.target.value)})})}function buildSettingsPageSections(settings){let output="";for(const setting of settings){const js_data_name=setting["js_name"];const setting_name=setting["name"];if(setting["options"]!==undefined){output+=`\ +
+
${setting_name}
+
`;onEach(setting["options"],option=>{const checked=option===setting["default"]?" checked":"";const full=`${js_data_name}-${option.replace(/ /g,"-")}`;output+=`\ + `});output+=`\ +
+
`}else{const checked=setting["default"]===true?" checked":"";output+=`\ +
\ + \ +
`}}return output}function buildSettingsPage(){const theme_names=getVar("themes").split(",").filter(t=>t);theme_names.push("light","dark","ayu");const settings=[{"name":"Theme","js_name":"theme","default":"system preference","options":theme_names.concat("system preference"),},{"name":"Preferred light theme","js_name":"preferred-light-theme","default":"light","options":theme_names,},{"name":"Preferred dark theme","js_name":"preferred-dark-theme","default":"dark","options":theme_names,},{"name":"Auto-hide item contents for large items","js_name":"auto-hide-large-items","default":true,},{"name":"Auto-hide item methods' documentation","js_name":"auto-hide-method-docs","default":false,},{"name":"Auto-hide trait implementation documentation","js_name":"auto-hide-trait-implementations","default":false,},{"name":"Directly go to item in search if there is only one result","js_name":"go-to-only-result","default":false,},{"name":"Show line numbers on code examples","js_name":"line-numbers","default":false,},{"name":"Disable keyboard shortcuts","js_name":"disable-shortcuts","default":false,},];const elementKind=isSettingsPage?"section":"div";const innerHTML=`
${buildSettingsPageSections(settings)}
`;const el=document.createElement(elementKind);el.id="settings";if(!isSettingsPage){el.className="popover"}el.innerHTML=innerHTML;if(isSettingsPage){document.getElementById(MAIN_ID).appendChild(el)}else{el.setAttribute("tabindex","-1");getSettingsButton().appendChild(el)}return el}const settingsMenu=buildSettingsPage();function displaySettings(){settingsMenu.style.display=""}function settingsBlurHandler(event){blurHandler(event,getSettingsButton(),window.hidePopoverMenus)}if(isSettingsPage){getSettingsButton().onclick=function(event){event.preventDefault()}}else{const settingsButton=getSettingsButton();const settingsMenu=document.getElementById("settings");settingsButton.onclick=function(event){if(elemIsInParent(event.target,settingsMenu)){return}event.preventDefault();const shouldDisplaySettings=settingsMenu.style.display==="none";window.hideAllModals();if(shouldDisplaySettings){displaySettings()}};settingsButton.onblur=settingsBlurHandler;settingsButton.querySelector("a").onblur=settingsBlurHandler;onEachLazy(settingsMenu.querySelectorAll("input"),el=>{el.onblur=settingsBlurHandler});settingsMenu.onblur=settingsBlurHandler}setTimeout(()=>{setEvents(settingsMenu);if(!isSettingsPage){displaySettings()}removeClass(getSettingsButton(),"rotate")},0)})() \ No newline at end of file diff --git a/book/rust-docs/static.files/source-script-106908c7a7964ba4.js b/book/rust-docs/static.files/source-script-106908c7a7964ba4.js new file mode 100644 index 0000000000..191bf6d999 --- /dev/null +++ b/book/rust-docs/static.files/source-script-106908c7a7964ba4.js @@ -0,0 +1 @@ +"use strict";(function(){const rootPath=getVar("root-path");const NAME_OFFSET=0;const DIRS_OFFSET=1;const FILES_OFFSET=2;const RUSTDOC_MOBILE_BREAKPOINT=700;function closeSidebarIfMobile(){if(window.innerWidth"){addClass(document.documentElement,"source-sidebar-expanded");child.innerText="<";updateLocalStorage("source-sidebar-show","true")}else{removeClass(document.documentElement,"source-sidebar-expanded");child.innerText=">";updateLocalStorage("source-sidebar-show","false")}}function createSidebarToggle(){const sidebarToggle=document.createElement("div");sidebarToggle.id="src-sidebar-toggle";const inner=document.createElement("button");if(getCurrentValue("source-sidebar-show")==="true"){inner.innerText="<"}else{inner.innerText=">"}inner.onclick=toggleSidebar;sidebarToggle.appendChild(inner);return sidebarToggle}function createSourceSidebar(){const container=document.querySelector("nav.sidebar");const sidebarToggle=createSidebarToggle();container.insertBefore(sidebarToggle,container.firstChild);const sidebar=document.createElement("div");sidebar.id="source-sidebar";let hasFoundFile=false;const title=document.createElement("div");title.className="title";title.innerText="Files";sidebar.appendChild(title);Object.keys(sourcesIndex).forEach(key=>{sourcesIndex[key][NAME_OFFSET]=key;hasFoundFile=createDirEntry(sourcesIndex[key],sidebar,"",hasFoundFile)});container.appendChild(sidebar);const selected_elem=sidebar.getElementsByClassName("selected")[0];if(typeof selected_elem!=="undefined"){selected_elem.focus()}}const lineNumbersRegex=/^#?(\d+)(?:-(\d+))?$/;function highlightSourceLines(match){if(typeof match==="undefined"){match=window.location.hash.match(lineNumbersRegex)}if(!match){return}let from=parseInt(match[1],10);let to=from;if(typeof match[2]!=="undefined"){to=parseInt(match[2],10)}if(to{onEachLazy(e.getElementsByTagName("a"),i_e=>{removeClass(i_e,"line-highlighted")})});for(let i=from;i<=to;++i){elem=document.getElementById(i);if(!elem){break}addClass(elem,"line-highlighted")}}const handleSourceHighlight=(function(){let prev_line_id=0;const set_fragment=name=>{const x=window.scrollX,y=window.scrollY;if(browserSupportsHistoryApi()){history.replaceState(null,null,"#"+name);highlightSourceLines()}else{location.replace("#"+name)}window.scrollTo(x,y)};return ev=>{let cur_line_id=parseInt(ev.target.id,10);if(isNaN(cur_line_id)||ev.ctrlKey||ev.altKey||ev.metaKey){return}ev.preventDefault();if(ev.shiftKey&&prev_line_id){if(prev_line_id>cur_line_id){const tmp=prev_line_id;prev_line_id=cur_line_id;cur_line_id=tmp}set_fragment(prev_line_id+"-"+cur_line_id)}else{prev_line_id=cur_line_id;set_fragment(cur_line_id)}}}());window.addEventListener("hashchange",()=>{const match=window.location.hash.match(lineNumbersRegex);if(match){return highlightSourceLines(match)}});onEachLazy(document.getElementsByClassName("src-line-numbers"),el=>{el.addEventListener("click",handleSourceHighlight)});highlightSourceLines();window.createSourceSidebar=createSourceSidebar})() \ No newline at end of file diff --git a/book/rust-docs/static.files/storage-59fd9b8ccb335783.js b/book/rust-docs/static.files/storage-59fd9b8ccb335783.js new file mode 100644 index 0000000000..ca5481acb2 --- /dev/null +++ b/book/rust-docs/static.files/storage-59fd9b8ccb335783.js @@ -0,0 +1 @@ +"use strict";const darkThemes=["dark","ayu"];window.currentTheme=document.getElementById("themeStyle");const settingsDataset=(function(){const settingsElement=document.getElementById("default-settings");return settingsElement&&settingsElement.dataset?settingsElement.dataset:null})();function getSettingValue(settingName){const current=getCurrentValue(settingName);if(current===null&&settingsDataset!==null){const def=settingsDataset[settingName.replace(/-/g,"_")];if(def!==undefined){return def}}return current}const localStoredTheme=getSettingValue("theme");function hasClass(elem,className){return elem&&elem.classList&&elem.classList.contains(className)}function addClass(elem,className){if(elem&&elem.classList){elem.classList.add(className)}}function removeClass(elem,className){if(elem&&elem.classList){elem.classList.remove(className)}}function onEach(arr,func,reversed){if(arr&&arr.length>0){if(reversed){for(let i=arr.length-1;i>=0;--i){if(func(arr[i])){return true}}}else{for(const elem of arr){if(func(elem)){return true}}}}return false}function onEachLazy(lazyArray,func,reversed){return onEach(Array.prototype.slice.call(lazyArray),func,reversed)}function updateLocalStorage(name,value){try{window.localStorage.setItem("rustdoc-"+name,value)}catch(e){}}function getCurrentValue(name){try{return window.localStorage.getItem("rustdoc-"+name)}catch(e){return null}}const getVar=(function getVar(name){const el=document.querySelector("head > meta[name='rustdoc-vars']");return el?el.attributes["data-"+name].value:null});function switchTheme(newThemeName,saveTheme){if(saveTheme){updateLocalStorage("theme",newThemeName)}let newHref;if(newThemeName==="light"||newThemeName==="dark"||newThemeName==="ayu"){newHref=getVar("static-root-path")+getVar("theme-"+newThemeName+"-css")}else{newHref=getVar("root-path")+newThemeName+getVar("resource-suffix")+".css"}if(!window.currentTheme){document.write(``);window.currentTheme=document.getElementById("themeStyle")}else if(newHref!==window.currentTheme.href){window.currentTheme.href=newHref}}const updateTheme=(function(){const mql=window.matchMedia("(prefers-color-scheme: dark)");function updateTheme(){if(getSettingValue("use-system-theme")!=="false"){const lightTheme=getSettingValue("preferred-light-theme")||"light";const darkTheme=getSettingValue("preferred-dark-theme")||"dark";updateLocalStorage("use-system-theme","true");switchTheme(mql.matches?darkTheme:lightTheme,true)}else{switchTheme(getSettingValue("theme"),false)}}mql.addEventListener("change",updateTheme);return updateTheme})();if(getSettingValue("use-system-theme")!=="false"&&window.matchMedia){if(getSettingValue("use-system-theme")===null&&getSettingValue("preferred-dark-theme")===null&&darkThemes.indexOf(localStoredTheme)>=0){updateLocalStorage("preferred-dark-theme",localStoredTheme)}}updateTheme();if(getSettingValue("source-sidebar-show")==="true"){addClass(document.documentElement,"source-sidebar-expanded")}window.addEventListener("pageshow",ev=>{if(ev.persisted){setTimeout(updateTheme,0)}}) \ No newline at end of file diff --git a/book/rust-docs/static.files/wheel-7b819b6101059cd0.svg b/book/rust-docs/static.files/wheel-7b819b6101059cd0.svg new file mode 100644 index 0000000000..83c07f63d1 --- /dev/null +++ b/book/rust-docs/static.files/wheel-7b819b6101059cd0.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/book/rust-docs/sync15/all.html b/book/rust-docs/sync15/all.html new file mode 100644 index 0000000000..6cb58609de --- /dev/null +++ b/book/rust-docs/sync15/all.html @@ -0,0 +1 @@ +List of all items in this crate
\ No newline at end of file diff --git a/book/rust-docs/sync15/bso/crypto/struct.IncomingEncryptedBso.html b/book/rust-docs/sync15/bso/crypto/struct.IncomingEncryptedBso.html new file mode 100644 index 0000000000..476fe1ab78 --- /dev/null +++ b/book/rust-docs/sync15/bso/crypto/struct.IncomingEncryptedBso.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../sync15/bso/struct.IncomingEncryptedBso.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/sync15/bso/crypto/struct.OutgoingEncryptedBso.html b/book/rust-docs/sync15/bso/crypto/struct.OutgoingEncryptedBso.html new file mode 100644 index 0000000000..d768a3197c --- /dev/null +++ b/book/rust-docs/sync15/bso/crypto/struct.OutgoingEncryptedBso.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../sync15/bso/struct.OutgoingEncryptedBso.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/sync15/bso/enum.IncomingKind.html b/book/rust-docs/sync15/bso/enum.IncomingKind.html new file mode 100644 index 0000000000..b1385e0e2c --- /dev/null +++ b/book/rust-docs/sync15/bso/enum.IncomingKind.html @@ -0,0 +1,25 @@ +IncomingKind in sync15::bso - Rust

Enum sync15::bso::IncomingKind

source ·
pub enum IncomingKind<T> {
+    Content(T),
+    Tombstone,
+    Malformed,
+}
Expand description

The “kind” of incoming content after deserializing it.

+

Variants§

§

Content(T)

A good, live T.

+
§

Tombstone

A record that used to be a T but has been replaced with a tombstone.

+
§

Malformed

Either not JSON, or can’t be made into a T.

+

Trait Implementations§

source§

impl<T: Debug> Debug for IncomingKind<T>

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl<T> RefUnwindSafe for IncomingKind<T>where + T: RefUnwindSafe,

§

impl<T> Send for IncomingKind<T>where + T: Send,

§

impl<T> Sync for IncomingKind<T>where + T: Sync,

§

impl<T> Unpin for IncomingKind<T>where + T: Unpin,

§

impl<T> UnwindSafe for IncomingKind<T>where + T: UnwindSafe,

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/sync15/bso/index.html b/book/rust-docs/sync15/bso/index.html new file mode 100644 index 0000000000..5dca8659a6 --- /dev/null +++ b/book/rust-docs/sync15/bso/index.html @@ -0,0 +1,8 @@ +sync15::bso - Rust

Module sync15::bso

source ·

Modules

  • Utilities for tests to make IncomingBsos and Content from test data.

Structs

  • IncomingBso’s can come from:
  • We also have the concept of “content”, which helps work with a T which +is represented inside the payload. Real-world examples of a T include +Bookmarks or Tabs. +See the content module for the implementations.
  • An envelope for an incoming item. Envelopes carry all the metadata for +a Sync BSO record (id, modified, sortindex), but not the payload +itself.
  • An envelope for an outgoing item. This is conceptually identical to +IncomingEnvelope, but omits fields that are only set by the server, +like modified.

Enums

  • The “kind” of incoming content after deserializing it.
\ No newline at end of file diff --git a/book/rust-docs/sync15/bso/sidebar-items.js b/book/rust-docs/sync15/bso/sidebar-items.js new file mode 100644 index 0000000000..b9c806f4ba --- /dev/null +++ b/book/rust-docs/sync15/bso/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["IncomingKind"],"mod":["test_utils"],"struct":["IncomingBso","IncomingContent","IncomingEncryptedBso","IncomingEnvelope","OutgoingBso","OutgoingEncryptedBso","OutgoingEnvelope"]}; \ No newline at end of file diff --git a/book/rust-docs/sync15/bso/struct.IncomingBso.html b/book/rust-docs/sync15/bso/struct.IncomingBso.html new file mode 100644 index 0000000000..4438aebbb2 --- /dev/null +++ b/book/rust-docs/sync15/bso/struct.IncomingBso.html @@ -0,0 +1,34 @@ +IncomingBso in sync15::bso - Rust

Struct sync15::bso::IncomingBso

source ·
pub struct IncomingBso {
+    pub envelope: IncomingEnvelope,
+    pub payload: String,
+}
Expand description

IncomingBso’s can come from:

+
    +
  • Directly from the server (ie, some records aren’t encrypted, such as meta/global)
  • +
  • From environments where the encryption is done externally (eg, Rust syncing in Desktop +Firefox has the encryption/decryption done by Firefox and the cleartext BSOs are passed in.
  • +
  • Read from the server as an EncryptedBso; see EncryptedBso description above.
  • +
+

Fields§

§envelope: IncomingEnvelope§payload: String

Implementations§

source§

impl IncomingBso

source

pub fn into_content<T: for<'de> Deserialize<'de>>(self) -> IncomingContent<T>

Convert an IncomingBso to an IncomingContent possibly holding a T.

+
source

pub fn into_content_with_fixup<T: for<'de> Deserialize<'de>>( + self, + fixup: impl FnOnce(&mut Value) +) -> IncomingContent<T>

Like into_content, but adds an additional fixup step where the caller can adjust the +`serde_json::Value’

+
source§

impl IncomingBso

Helpers to create an IncomingBso from some T

+
source

pub fn from_test_content<T: Serialize>(json: T) -> Self

When a test has an T and wants it as an IncomingBso

+
source

pub fn from_test_content_ts<T: Serialize>(json: T, ts: ServerTimestamp) -> Self

When a test has an T and wants it as an IncomingBso with a specific timestamp.

+
source

pub fn new_test_tombstone(guid: Guid) -> Self

When a test wants a new incoming tombstone.

+
source§

impl IncomingBso

source

pub fn new(envelope: IncomingEnvelope, payload: String) -> Self

Trait Implementations§

source§

impl Debug for IncomingBso

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl<'de> Deserialize<'de> for IncomingBso

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/sync15/bso/struct.IncomingContent.html b/book/rust-docs/sync15/bso/struct.IncomingContent.html new file mode 100644 index 0000000000..29004c0517 --- /dev/null +++ b/book/rust-docs/sync15/bso/struct.IncomingContent.html @@ -0,0 +1,40 @@ +IncomingContent in sync15::bso - Rust

Struct sync15::bso::IncomingContent

source ·
pub struct IncomingContent<T> {
+    pub envelope: IncomingEnvelope,
+    pub kind: IncomingKind<T>,
+}
Expand description

We also have the concept of “content”, which helps work with a T which +is represented inside the payload. Real-world examples of a T include +Bookmarks or Tabs. +See the content module for the implementations.

+

So this all flows together in the following way:

+
    +
  • Incoming encrypted data: +EncryptedIncomingBso -> IncomingBso -> [specific engine] -> IncomingContent
  • +
  • Incoming cleartext data: +IncomingBso -> IncomingContent +(Note that incoming cleartext only happens for a few collections managed by +the sync client and never by specific engines - engine BSOs are always encryted)
  • +
  • Outgoing encrypted data: +OutgoingBso (created in the engine) -> [this crate] -> EncryptedOutgoingBso
  • +
  • Outgoing cleartext data: just an OutgoingBso with no conversions needed. +IncomingContent is the result of converting an IncomingBso into +some - it consumes the Bso, so you get the envelope, and the IncomingKind +which reflects the state of parsing the json.
  • +
+

Fields§

§envelope: IncomingEnvelope§kind: IncomingKind<T>

Implementations§

source§

impl<T> IncomingContent<T>

source

pub fn content(self) -> Option<T>

Returns Some(content) if [self.kind] is IncomingKind::Content, None otherwise.

+

Trait Implementations§

source§

impl<T: Debug> Debug for IncomingContent<T>

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl<T> RefUnwindSafe for IncomingContent<T>where + T: RefUnwindSafe,

§

impl<T> Send for IncomingContent<T>where + T: Send,

§

impl<T> Sync for IncomingContent<T>where + T: Sync,

§

impl<T> Unpin for IncomingContent<T>where + T: Unpin,

§

impl<T> UnwindSafe for IncomingContent<T>where + T: UnwindSafe,

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/sync15/bso/struct.IncomingEncryptedBso.html b/book/rust-docs/sync15/bso/struct.IncomingEncryptedBso.html new file mode 100644 index 0000000000..3f5f6ba8d9 --- /dev/null +++ b/book/rust-docs/sync15/bso/struct.IncomingEncryptedBso.html @@ -0,0 +1,19 @@ +IncomingEncryptedBso in sync15::bso - Rust
pub struct IncomingEncryptedBso {
+    pub envelope: IncomingEnvelope,
+    /* private fields */
+}

Fields§

§envelope: IncomingEnvelope

Implementations§

source§

impl IncomingEncryptedBso

source

pub fn new(envelope: IncomingEnvelope, payload: EncryptedPayload) -> Self

source

pub fn into_decrypted(self, key: &KeyBundle) -> Result<IncomingBso>

Decrypt a BSO, consuming it into a clear-text version.

+

Trait Implementations§

source§

impl Debug for IncomingEncryptedBso

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl<'de> Deserialize<'de> for IncomingEncryptedBsowhere + EncryptedPayload: DeserializeOwned,

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/sync15/bso/struct.IncomingEnvelope.html b/book/rust-docs/sync15/bso/struct.IncomingEnvelope.html new file mode 100644 index 0000000000..8a6f26002c --- /dev/null +++ b/book/rust-docs/sync15/bso/struct.IncomingEnvelope.html @@ -0,0 +1,24 @@ +IncomingEnvelope in sync15::bso - Rust
pub struct IncomingEnvelope {
+    pub id: Guid,
+    pub modified: ServerTimestamp,
+    pub sortindex: Option<i32>,
+    pub ttl: Option<u32>,
+}
Expand description

An envelope for an incoming item. Envelopes carry all the metadata for +a Sync BSO record (id, modified, sortindex), but not the payload +itself.

+

Fields§

§id: Guid

The ID of the record.

+
§modified: ServerTimestamp§sortindex: Option<i32>§ttl: Option<u32>

Trait Implementations§

source§

impl Clone for IncomingEnvelope

source§

fn clone(&self) -> IncomingEnvelope

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for IncomingEnvelope

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl<'de> Deserialize<'de> for IncomingEnvelope

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/sync15/bso/struct.OutgoingBso.html b/book/rust-docs/sync15/bso/struct.OutgoingBso.html new file mode 100644 index 0000000000..5117e5b2c8 --- /dev/null +++ b/book/rust-docs/sync15/bso/struct.OutgoingBso.html @@ -0,0 +1,39 @@ +OutgoingBso in sync15::bso - Rust

Struct sync15::bso::OutgoingBso

source ·
pub struct OutgoingBso {
+    pub envelope: OutgoingEnvelope,
+    pub payload: String,
+}

Fields§

§envelope: OutgoingEnvelope§payload: String

Implementations§

source§

impl OutgoingBso

source§

impl OutgoingBso

source

pub fn new_tombstone(envelope: OutgoingEnvelope) -> Self

Creates a new tombstone record. +Not all collections expect tombstones.

+
source

pub fn from_content_with_id<T>(record: T) -> Result<Self, Error>where + T: Serialize,

Creates a outgoing record from some , which can be made into a JSON object +with a valid id. This is the most convenient way to create an outgoing +item from a when the default envelope is suitable. +Will panic if there’s no good id in the json.

+
source

pub fn from_content<T>( + envelope: OutgoingEnvelope, + record: T +) -> Result<Self, Error>where + T: Serialize,

Create an Outgoing record with an explicit envelope. Will panic if the +payload has an ID but it doesn’t match the envelope.

+
source§

impl OutgoingBso

Tests often want an IncomingBso to test, and the easiest way is often to +create an OutgoingBso convert it back to an incoming.

+
source

pub fn to_test_incoming(&self) -> IncomingBso

When a test has an OutgoingBso and wants it as an IncomingBso

+
source

pub fn to_test_incoming_ts(&self, ts: ServerTimestamp) -> IncomingBso

When a test has an OutgoingBso and wants it as an IncomingBso with a specific timestamp.

+
source

pub fn to_test_incoming_t<T: for<'de> Deserialize<'de>>(&self) -> T

When a test has an OutgoingBso and wants it as an IncomingBso with a specific T.

+
source§

impl OutgoingBso

source

pub fn new<T: Serialize>( + envelope: OutgoingEnvelope, + val: &T +) -> Result<Self, Error>

Most consumers will use self.from_content and self.from_content_with_id +but this exists for the few consumers for whom that doesn’t make sense.

+

Trait Implementations§

source§

impl Debug for OutgoingBso

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Serialize for OutgoingBso

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/sync15/bso/struct.OutgoingEncryptedBso.html b/book/rust-docs/sync15/bso/struct.OutgoingEncryptedBso.html new file mode 100644 index 0000000000..1c70b11769 --- /dev/null +++ b/book/rust-docs/sync15/bso/struct.OutgoingEncryptedBso.html @@ -0,0 +1,17 @@ +OutgoingEncryptedBso in sync15::bso - Rust
pub struct OutgoingEncryptedBso {
+    pub envelope: OutgoingEnvelope,
+    /* private fields */
+}

Fields§

§envelope: OutgoingEnvelope

Implementations§

Trait Implementations§

source§

impl Debug for OutgoingEncryptedBso

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Serialize for OutgoingEncryptedBsowhere + EncryptedPayload: Serialize,

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/sync15/bso/struct.OutgoingEnvelope.html b/book/rust-docs/sync15/bso/struct.OutgoingEnvelope.html new file mode 100644 index 0000000000..9f39e1911c --- /dev/null +++ b/book/rust-docs/sync15/bso/struct.OutgoingEnvelope.html @@ -0,0 +1,24 @@ +OutgoingEnvelope in sync15::bso - Rust
pub struct OutgoingEnvelope {
+    pub id: Guid,
+    pub sortindex: Option<i32>,
+    pub ttl: Option<u32>,
+}
Expand description

An envelope for an outgoing item. This is conceptually identical to +IncomingEnvelope, but omits fields that are only set by the server, +like modified.

+

Fields§

§id: Guid

The ID of the record.

+
§sortindex: Option<i32>§ttl: Option<u32>

Trait Implementations§

source§

impl Clone for OutgoingEnvelope

source§

fn clone(&self) -> OutgoingEnvelope

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for OutgoingEnvelope

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for OutgoingEnvelope

source§

fn default() -> OutgoingEnvelope

Returns the “default value” for a type. Read more
source§

impl From<Guid> for OutgoingEnvelope

Allow an outgoing envelope to be constructed with just a guid when default +values for the other fields are OK.

+
source§

fn from(id: Guid) -> Self

Converts to this type from the input type.
source§

impl Serialize for OutgoingEnvelope

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/sync15/bso/test_utils/index.html b/book/rust-docs/sync15/bso/test_utils/index.html new file mode 100644 index 0000000000..57c45c48af --- /dev/null +++ b/book/rust-docs/sync15/bso/test_utils/index.html @@ -0,0 +1,2 @@ +sync15::bso::test_utils - Rust

Module sync15::bso::test_utils

source ·
Expand description

Utilities for tests to make IncomingBsos and Content from test data.

+
\ No newline at end of file diff --git a/book/rust-docs/sync15/bso/test_utils/sidebar-items.js b/book/rust-docs/sync15/bso/test_utils/sidebar-items.js new file mode 100644 index 0000000000..5244ce01cc --- /dev/null +++ b/book/rust-docs/sync15/bso/test_utils/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {}; \ No newline at end of file diff --git a/book/rust-docs/sync15/client/enum.ServiceStatus.html b/book/rust-docs/sync15/client/enum.ServiceStatus.html new file mode 100644 index 0000000000..201791dc8f --- /dev/null +++ b/book/rust-docs/sync15/client/enum.ServiceStatus.html @@ -0,0 +1,33 @@ +ServiceStatus in sync15::client - Rust
pub enum ServiceStatus {
+    Ok,
+    NetworkError,
+    ServiceError,
+    AuthenticationError,
+    BackedOff,
+    Interrupted,
+    OtherError,
+}
Expand description

The general status of sync - should probably be moved to the “sync manager” +once we have one!

+

Variants§

§

Ok

Everything is fine.

+
§

NetworkError

Some general network issue.

+
§

ServiceError

Some apparent issue with the servers.

+
§

AuthenticationError

Some external FxA action needs to be taken.

+
§

BackedOff

We declined to do anything for backoff or rate-limiting reasons.

+
§

Interrupted

We were interrupted.

+
§

OtherError

Something else - you need to check the logs for more details. May +or may not be transient, we really don’t know.

+

Implementations§

Trait Implementations§

source§

impl Clone for ServiceStatus

source§

fn clone(&self) -> ServiceStatus

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for ServiceStatus

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl PartialEq<ServiceStatus> for ServiceStatus

source§

fn eq(&self, other: &ServiceStatus) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Eq for ServiceStatus

source§

impl StructuralEq for ServiceStatus

source§

impl StructuralPartialEq for ServiceStatus

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/sync15/client/enum.Sync15ClientResponse.html b/book/rust-docs/sync15/client/enum.Sync15ClientResponse.html new file mode 100644 index 0000000000..672d95082d --- /dev/null +++ b/book/rust-docs/sync15/client/enum.Sync15ClientResponse.html @@ -0,0 +1,32 @@ +Sync15ClientResponse in sync15::client - Rust
pub enum Sync15ClientResponse<T> {
+    Success {
+        status: u16,
+        record: T,
+        last_modified: ServerTimestamp,
+        route: String,
+    },
+    Error(ErrorResponse),
+}
Expand description

A response from a GET request on a Sync15StorageClient, encapsulating all +the variants users of this client needs to care about.

+

Variants§

§

Success

Fields

§status: u16
§record: T
§last_modified: ServerTimestamp
§route: String
§

Error(ErrorResponse)

Implementations§

source§

impl<T> Sync15ClientResponse<T>

source

pub fn from_response( + resp: Response, + backoff_listener: &Arc<BackoffState> +) -> Result<Self>where + for<'a> T: Deserialize<'a>,

source

pub fn create_storage_error(self) -> Error

Trait Implementations§

source§

impl<T: Clone> Clone for Sync15ClientResponse<T>

source§

fn clone(&self) -> Sync15ClientResponse<T>

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl<T: Debug> Debug for Sync15ClientResponse<T>

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl<T> RefUnwindSafe for Sync15ClientResponse<T>where + T: RefUnwindSafe,

§

impl<T> Send for Sync15ClientResponse<T>where + T: Send,

§

impl<T> Sync for Sync15ClientResponse<T>where + T: Sync,

§

impl<T> Unpin for Sync15ClientResponse<T>where + T: Unpin,

§

impl<T> UnwindSafe for Sync15ClientResponse<T>where + T: UnwindSafe,

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/sync15/client/fn.sync_multiple.html b/book/rust-docs/sync15/client/fn.sync_multiple.html new file mode 100644 index 0000000000..a66ee81945 --- /dev/null +++ b/book/rust-docs/sync15/client/fn.sync_multiple.html @@ -0,0 +1,27 @@ +sync_multiple in sync15::client - Rust

Function sync15::client::sync_multiple

source ·
pub fn sync_multiple(
+    engines: &[&dyn SyncEngine],
+    persisted_global_state: &mut Option<String>,
+    mem_cached_state: &mut MemoryCachedState,
+    storage_init: &Sync15StorageClientInit,
+    root_sync_key: &KeyBundle,
+    interruptee: &dyn Interruptee,
+    req_info: Option<SyncRequestInfo<'_>>
+) -> SyncResult
Expand description

Sync multiple engines

+
    +
  • engines - The engines to sync
  • +
  • persisted_global_state - The global state to use, or None if never +before provided. At the end of the sync, and even when the sync fails, +the value in this cell should be persisted to permanent storage and +provided next time the sync is called.
  • +
  • last_client_info - The client state to use, or None if never before +provided. At the end of the sync, the value should be persisted +in memory only - it should not be persisted to disk.
  • +
  • storage_init - Information about how the sync http client should be +configured.
  • +
  • root_sync_key - The KeyBundle used for encryption.
  • +
+

Returns a map, keyed by name and holding an error value - if any engine +fails, the sync will continue on to other engines, but the error will be +places in this map. The absence of a name in the map implies the engine +succeeded.

+
\ No newline at end of file diff --git a/book/rust-docs/sync15/client/fn.sync_multiple_with_command_processor.html b/book/rust-docs/sync15/client/fn.sync_multiple_with_command_processor.html new file mode 100644 index 0000000000..96675a5aee --- /dev/null +++ b/book/rust-docs/sync15/client/fn.sync_multiple_with_command_processor.html @@ -0,0 +1,13 @@ +sync_multiple_with_command_processor in sync15::client - Rust
pub fn sync_multiple_with_command_processor(
+    command_processor: Option<&dyn CommandProcessor>,
+    engines: &[&dyn SyncEngine],
+    persisted_global_state: &mut Option<String>,
+    mem_cached_state: &mut MemoryCachedState,
+    storage_init: &Sync15StorageClientInit,
+    root_sync_key: &KeyBundle,
+    interruptee: &dyn Interruptee,
+    req_info: Option<SyncRequestInfo<'_>>
+) -> SyncResult
Expand description

Like sync_multiple, but specifies an optional command processor to handle +commands from the clients collection. This function is called by the sync +manager, which provides its own processor.

+
\ No newline at end of file diff --git a/book/rust-docs/sync15/client/index.html b/book/rust-docs/sync15/client/index.html new file mode 100644 index 0000000000..b1204c1c16 --- /dev/null +++ b/book/rust-docs/sync15/client/index.html @@ -0,0 +1,21 @@ +sync15::client - Rust

Module sync15::client

source ·
Expand description

A module for everything needed to be a “sync client” - ie, a device which +can perform a full sync of any number of collections, including managing +the server state.

+

In general, the client is responsible for all communication with the sync server, +including ensuring the state is correct, and encrypting/decrypting all records +to and from the server. However, the actual syncing of the collections is +delegated to an external crate::engine(Sync Engine).

+

One exception is that the “sync client” owns one sync engine - the +crate::clients_engine, which is managed internally.

+

Structs

  • Info we want callers to engine in memory for us so that subsequent +syncs are faster. This should never be persisted to storage as it holds +sensitive information, such as the sync decryption keys.
  • This is essentially a bag of information that the sync manager knows, but +otherwise we won’t. It should probably be rethought if it gains many more +fields.
  • The result of a sync request. This too is from the “sync manager”, but only +has a fraction of the things it will have when we actually build that.

Enums

  • The general status of sync - should probably be moved to the “sync manager” +once we have one!
  • A response from a GET request on a Sync15StorageClient, encapsulating all +the variants users of this client needs to care about.

Traits

  • A trait containing the methods required to run through the setup state +machine. This is factored out into a separate trait to make mocking +easier.

Functions

  • Sync multiple engines
  • Like sync_multiple, but specifies an optional command processor to handle +commands from the clients collection. This function is called by the sync +manager, which provides its own processor.
\ No newline at end of file diff --git a/book/rust-docs/sync15/client/sidebar-items.js b/book/rust-docs/sync15/client/sidebar-items.js new file mode 100644 index 0000000000..f015f6e39c --- /dev/null +++ b/book/rust-docs/sync15/client/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["ServiceStatus","Sync15ClientResponse"],"fn":["sync_multiple","sync_multiple_with_command_processor"],"struct":["MemoryCachedState","Sync15StorageClient","Sync15StorageClientInit","SyncRequestInfo","SyncResult"],"trait":["SetupStorageClient"]}; \ No newline at end of file diff --git a/book/rust-docs/sync15/client/status/enum.ServiceStatus.html b/book/rust-docs/sync15/client/status/enum.ServiceStatus.html new file mode 100644 index 0000000000..95d9f2b302 --- /dev/null +++ b/book/rust-docs/sync15/client/status/enum.ServiceStatus.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../sync15/client/enum.ServiceStatus.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/sync15/client/status/struct.SyncResult.html b/book/rust-docs/sync15/client/status/struct.SyncResult.html new file mode 100644 index 0000000000..ffc40d7fd4 --- /dev/null +++ b/book/rust-docs/sync15/client/status/struct.SyncResult.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../sync15/client/struct.SyncResult.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/sync15/client/storage_client/enum.Sync15ClientResponse.html b/book/rust-docs/sync15/client/storage_client/enum.Sync15ClientResponse.html new file mode 100644 index 0000000000..a522699ea4 --- /dev/null +++ b/book/rust-docs/sync15/client/storage_client/enum.Sync15ClientResponse.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../sync15/client/enum.Sync15ClientResponse.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/sync15/client/storage_client/struct.Sync15StorageClient.html b/book/rust-docs/sync15/client/storage_client/struct.Sync15StorageClient.html new file mode 100644 index 0000000000..779397fcd5 --- /dev/null +++ b/book/rust-docs/sync15/client/storage_client/struct.Sync15StorageClient.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../sync15/client/struct.Sync15StorageClient.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/sync15/client/storage_client/struct.Sync15StorageClientInit.html b/book/rust-docs/sync15/client/storage_client/struct.Sync15StorageClientInit.html new file mode 100644 index 0000000000..e3870bac2c --- /dev/null +++ b/book/rust-docs/sync15/client/storage_client/struct.Sync15StorageClientInit.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../sync15/client/struct.Sync15StorageClientInit.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/sync15/client/storage_client/trait.SetupStorageClient.html b/book/rust-docs/sync15/client/storage_client/trait.SetupStorageClient.html new file mode 100644 index 0000000000..e57f075365 --- /dev/null +++ b/book/rust-docs/sync15/client/storage_client/trait.SetupStorageClient.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../sync15/client/trait.SetupStorageClient.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/sync15/client/struct.MemoryCachedState.html b/book/rust-docs/sync15/client/struct.MemoryCachedState.html new file mode 100644 index 0000000000..bf7ff5f3e9 --- /dev/null +++ b/book/rust-docs/sync15/client/struct.MemoryCachedState.html @@ -0,0 +1,15 @@ +MemoryCachedState in sync15::client - Rust
pub struct MemoryCachedState { /* private fields */ }
Expand description

Info we want callers to engine in memory for us so that subsequent +syncs are faster. This should never be persisted to storage as it holds +sensitive information, such as the sync decryption keys.

+

Implementations§

Trait Implementations§

source§

impl Debug for MemoryCachedState

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for MemoryCachedState

source§

fn default() -> MemoryCachedState

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/sync15/client/struct.Sync15StorageClient.html b/book/rust-docs/sync15/client/struct.Sync15StorageClient.html new file mode 100644 index 0000000000..7d5dde2ffc --- /dev/null +++ b/book/rust-docs/sync15/client/struct.Sync15StorageClient.html @@ -0,0 +1,35 @@ +Sync15StorageClient in sync15::client - Rust
pub struct Sync15StorageClient { /* private fields */ }

Implementations§

source§

impl Sync15StorageClient

source

pub fn new(init_params: Sync15StorageClientInit) -> Result<Sync15StorageClient>

source

pub fn get_encrypted_records( + &self, + collection_request: CollectionRequest +) -> Result<Sync15ClientResponse<Vec<IncomingEncryptedBso>>>

source

pub fn new_post_queue<'a, F: PostResponseHandler>( + &'a self, + coll: &'a CollectionName, + config: &InfoConfiguration, + ts: ServerTimestamp, + on_response: F +) -> Result<PostQueue<PostWrapper<'a>, F>>

source

pub fn hashed_uid(&self) -> Result<String>

Trait Implementations§

source§

impl Debug for Sync15StorageClient

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl SetupStorageClient for Sync15StorageClient

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/sync15/client/struct.Sync15StorageClientInit.html b/book/rust-docs/sync15/client/struct.Sync15StorageClientInit.html new file mode 100644 index 0000000000..c334e900e7 --- /dev/null +++ b/book/rust-docs/sync15/client/struct.Sync15StorageClientInit.html @@ -0,0 +1,29 @@ +Sync15StorageClientInit in sync15::client - Rust
pub struct Sync15StorageClientInit {
+    pub key_id: String,
+    pub access_token: String,
+    pub tokenserver_url: Url,
+}

Fields§

§key_id: String§access_token: String§tokenserver_url: Url

Trait Implementations§

source§

impl Clone for Sync15StorageClientInit

source§

fn clone(&self) -> Sync15StorageClientInit

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for Sync15StorageClientInit

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Hash for Sync15StorageClientInit

source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · source§

fn hash_slice<H>(data: &[Self], state: &mut H)where + H: Hasher, + Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
source§

impl Ord for Sync15StorageClientInit

source§

fn cmp(&self, other: &Sync15StorageClientInit) -> Ordering

This method returns an Ordering between self and other. Read more
1.21.0 · source§

fn max(self, other: Self) -> Selfwhere + Self: Sized,

Compares and returns the maximum of two values. Read more
1.21.0 · source§

fn min(self, other: Self) -> Selfwhere + Self: Sized,

Compares and returns the minimum of two values. Read more
1.50.0 · source§

fn clamp(self, min: Self, max: Self) -> Selfwhere + Self: Sized + PartialOrd<Self>,

Restrict a value to a certain interval. Read more
source§

impl PartialEq<Sync15StorageClientInit> for Sync15StorageClientInit

source§

fn eq(&self, other: &Sync15StorageClientInit) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl PartialOrd<Sync15StorageClientInit> for Sync15StorageClientInit

source§

fn partial_cmp(&self, other: &Sync15StorageClientInit) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · source§

fn lt(&self, other: &Rhs) -> bool

This method tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · source§

fn le(&self, other: &Rhs) -> bool

This method tests less than or equal to (for self and other) and is used by the <= +operator. Read more
1.0.0 · source§

fn gt(&self, other: &Rhs) -> bool

This method tests greater than (for self and other) and is used by the > operator. Read more
1.0.0 · source§

fn ge(&self, other: &Rhs) -> bool

This method tests greater than or equal to (for self and other) and is used by the >= +operator. Read more
source§

impl Eq for Sync15StorageClientInit

source§

impl StructuralEq for Sync15StorageClientInit

source§

impl StructuralPartialEq for Sync15StorageClientInit

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> CallHasher for Twhere + T: Hash + ?Sized,

§

fn get_hash<H, B>(value: &H, build_hasher: &B) -> u64where + H: Hash + ?Sized, + B: BuildHasher,

source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/sync15/client/struct.SyncRequestInfo.html b/book/rust-docs/sync15/client/struct.SyncRequestInfo.html new file mode 100644 index 0000000000..75835396d2 --- /dev/null +++ b/book/rust-docs/sync15/client/struct.SyncRequestInfo.html @@ -0,0 +1,18 @@ +SyncRequestInfo in sync15::client - Rust
pub struct SyncRequestInfo<'a> {
+    pub engines_to_state_change: Option<&'a HashMap<String, bool>>,
+    pub is_user_action: bool,
+}
Expand description

This is essentially a bag of information that the sync manager knows, but +otherwise we won’t. It should probably be rethought if it gains many more +fields.

+

Fields§

§engines_to_state_change: Option<&'a HashMap<String, bool>>§is_user_action: bool

Trait Implementations§

source§

impl<'a> Debug for SyncRequestInfo<'a>

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl<'a> Default for SyncRequestInfo<'a>

source§

fn default() -> SyncRequestInfo<'a>

Returns the “default value” for a type. Read more

Auto Trait Implementations§

§

impl<'a> RefUnwindSafe for SyncRequestInfo<'a>

§

impl<'a> Send for SyncRequestInfo<'a>

§

impl<'a> Sync for SyncRequestInfo<'a>

§

impl<'a> Unpin for SyncRequestInfo<'a>

§

impl<'a> UnwindSafe for SyncRequestInfo<'a>

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/sync15/client/struct.SyncResult.html b/book/rust-docs/sync15/client/struct.SyncResult.html new file mode 100644 index 0000000000..2f1bdb3764 --- /dev/null +++ b/book/rust-docs/sync15/client/struct.SyncResult.html @@ -0,0 +1,26 @@ +SyncResult in sync15::client - Rust

Struct sync15::client::SyncResult

source ·
pub struct SyncResult {
+    pub service_status: ServiceStatus,
+    pub declined: Option<Vec<String>>,
+    pub result: Result<(), Error>,
+    pub engine_results: HashMap<String, Result<(), Error>>,
+    pub telemetry: SyncTelemetryPing,
+    pub next_sync_after: Option<SystemTime>,
+}
Expand description

The result of a sync request. This too is from the “sync manager”, but only +has a fraction of the things it will have when we actually build that.

+

Fields§

§service_status: ServiceStatus

The general health.

+
§declined: Option<Vec<String>>

The set of declined engines, if we know them.

+
§result: Result<(), Error>

The result of the sync.

+
§engine_results: HashMap<String, Result<(), Error>>

The result for each engine. +Note that we expect the String to be replaced with an enum later.

+
§telemetry: SyncTelemetryPing§next_sync_after: Option<SystemTime>

Trait Implementations§

source§

impl Debug for SyncResult

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/sync15/client/sync_multiple/fn.sync_multiple.html b/book/rust-docs/sync15/client/sync_multiple/fn.sync_multiple.html new file mode 100644 index 0000000000..1345168920 --- /dev/null +++ b/book/rust-docs/sync15/client/sync_multiple/fn.sync_multiple.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../sync15/client/fn.sync_multiple.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/sync15/client/sync_multiple/fn.sync_multiple_with_command_processor.html b/book/rust-docs/sync15/client/sync_multiple/fn.sync_multiple_with_command_processor.html new file mode 100644 index 0000000000..eadbfe1a9c --- /dev/null +++ b/book/rust-docs/sync15/client/sync_multiple/fn.sync_multiple_with_command_processor.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../sync15/client/fn.sync_multiple_with_command_processor.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/sync15/client/sync_multiple/struct.MemoryCachedState.html b/book/rust-docs/sync15/client/sync_multiple/struct.MemoryCachedState.html new file mode 100644 index 0000000000..0e61bdb249 --- /dev/null +++ b/book/rust-docs/sync15/client/sync_multiple/struct.MemoryCachedState.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../sync15/client/struct.MemoryCachedState.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/sync15/client/sync_multiple/struct.SyncRequestInfo.html b/book/rust-docs/sync15/client/sync_multiple/struct.SyncRequestInfo.html new file mode 100644 index 0000000000..6a08e329a0 --- /dev/null +++ b/book/rust-docs/sync15/client/sync_multiple/struct.SyncRequestInfo.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../sync15/client/struct.SyncRequestInfo.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/sync15/client/trait.SetupStorageClient.html b/book/rust-docs/sync15/client/trait.SetupStorageClient.html new file mode 100644 index 0000000000..c0c39c3a6f --- /dev/null +++ b/book/rust-docs/sync15/client/trait.SetupStorageClient.html @@ -0,0 +1,43 @@ +SetupStorageClient in sync15::client - Rust
pub trait SetupStorageClient {
+    // Required methods
+    fn fetch_info_configuration(
+        &self
+    ) -> Result<Sync15ClientResponse<InfoConfiguration>>;
+    fn fetch_info_collections(
+        &self
+    ) -> Result<Sync15ClientResponse<InfoCollections>>;
+    fn fetch_meta_global(
+        &self
+    ) -> Result<Sync15ClientResponse<MetaGlobalRecord>>;
+    fn fetch_crypto_keys(
+        &self
+    ) -> Result<Sync15ClientResponse<IncomingEncryptedBso>>;
+    fn put_meta_global(
+        &self,
+        xius: ServerTimestamp,
+        global: &MetaGlobalRecord
+    ) -> Result<ServerTimestamp>;
+    fn put_crypto_keys(
+        &self,
+        xius: ServerTimestamp,
+        keys: &OutgoingEncryptedBso
+    ) -> Result<()>;
+    fn wipe_all_remote(&self) -> Result<()>;
+}
Expand description

A trait containing the methods required to run through the setup state +machine. This is factored out into a separate trait to make mocking +easier.

+

Required Methods§

source

fn fetch_info_configuration( + &self +) -> Result<Sync15ClientResponse<InfoConfiguration>>

source

fn fetch_info_collections( + &self +) -> Result<Sync15ClientResponse<InfoCollections>>

source

fn fetch_meta_global(&self) -> Result<Sync15ClientResponse<MetaGlobalRecord>>

source

fn fetch_crypto_keys( + &self +) -> Result<Sync15ClientResponse<IncomingEncryptedBso>>

source

fn put_meta_global( + &self, + xius: ServerTimestamp, + global: &MetaGlobalRecord +) -> Result<ServerTimestamp>

source

fn put_crypto_keys( + &self, + xius: ServerTimestamp, + keys: &OutgoingEncryptedBso +) -> Result<()>

source

fn wipe_all_remote(&self) -> Result<()>

Implementors§

\ No newline at end of file diff --git a/book/rust-docs/sync15/client_types/struct.ClientData.html b/book/rust-docs/sync15/client_types/struct.ClientData.html new file mode 100644 index 0000000000..8db2d0f56e --- /dev/null +++ b/book/rust-docs/sync15/client_types/struct.ClientData.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../sync15/struct.ClientData.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/sync15/client_types/struct.RemoteClient.html b/book/rust-docs/sync15/client_types/struct.RemoteClient.html new file mode 100644 index 0000000000..7ea1fcca29 --- /dev/null +++ b/book/rust-docs/sync15/client_types/struct.RemoteClient.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../sync15/struct.RemoteClient.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/sync15/clients_engine/engine/struct.Engine.html b/book/rust-docs/sync15/clients_engine/engine/struct.Engine.html new file mode 100644 index 0000000000..32b2d2d861 --- /dev/null +++ b/book/rust-docs/sync15/clients_engine/engine/struct.Engine.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../sync15/clients_engine/struct.Engine.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/sync15/clients_engine/enum.Command.html b/book/rust-docs/sync15/clients_engine/enum.Command.html new file mode 100644 index 0000000000..e141e4a9f4 --- /dev/null +++ b/book/rust-docs/sync15/clients_engine/enum.Command.html @@ -0,0 +1,32 @@ +Command in sync15::clients_engine - Rust
pub enum Command {
+    Wipe(String),
+    ResetAll,
+    Reset(String),
+}

Variants§

§

Wipe(String)

Erases all local data for a specific engine.

+
§

ResetAll

Resets local sync state for all engines.

+
§

Reset(String)

Resets local sync state for a specific engine.

+

Trait Implementations§

source§

impl Clone for Command

source§

fn clone(&self) -> Command

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for Command

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Hash for Command

source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · source§

fn hash_slice<H>(data: &[Self], state: &mut H)where + H: Hasher, + Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
source§

impl Ord for Command

source§

fn cmp(&self, other: &Command) -> Ordering

This method returns an Ordering between self and other. Read more
1.21.0 · source§

fn max(self, other: Self) -> Selfwhere + Self: Sized,

Compares and returns the maximum of two values. Read more
1.21.0 · source§

fn min(self, other: Self) -> Selfwhere + Self: Sized,

Compares and returns the minimum of two values. Read more
1.50.0 · source§

fn clamp(self, min: Self, max: Self) -> Selfwhere + Self: Sized + PartialOrd<Self>,

Restrict a value to a certain interval. Read more
source§

impl PartialEq<Command> for Command

source§

fn eq(&self, other: &Command) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl PartialOrd<Command> for Command

source§

fn partial_cmp(&self, other: &Command) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · source§

fn lt(&self, other: &Rhs) -> bool

This method tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · source§

fn le(&self, other: &Rhs) -> bool

This method tests less than or equal to (for self and other) and is used by the <= +operator. Read more
1.0.0 · source§

fn gt(&self, other: &Rhs) -> bool

This method tests greater than (for self and other) and is used by the > operator. Read more
1.0.0 · source§

fn ge(&self, other: &Rhs) -> bool

This method tests greater than or equal to (for self and other) and is used by the >= +operator. Read more
source§

impl Eq for Command

source§

impl StructuralEq for Command

source§

impl StructuralPartialEq for Command

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> CallHasher for Twhere + T: Hash + ?Sized,

§

fn get_hash<H, B>(value: &H, build_hasher: &B) -> u64where + H: Hash + ?Sized, + B: BuildHasher,

source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/sync15/clients_engine/enum.CommandStatus.html b/book/rust-docs/sync15/clients_engine/enum.CommandStatus.html new file mode 100644 index 0000000000..db38394d3f --- /dev/null +++ b/book/rust-docs/sync15/clients_engine/enum.CommandStatus.html @@ -0,0 +1,29 @@ +CommandStatus in sync15::clients_engine - Rust
pub enum CommandStatus {
+    Applied,
+    Ignored,
+    Unsupported,
+}
Expand description

Indicates if a command was applied successfully, ignored, or not supported. +Applied and ignored commands are removed from our client record, and never +retried. Unsupported commands are put back into our record, and retried on +subsequent syncs. This is to handle clients adding support for new data +types.

+

Variants§

§

Applied

§

Ignored

§

Unsupported

Trait Implementations§

source§

impl Clone for CommandStatus

source§

fn clone(&self) -> CommandStatus

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for CommandStatus

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Hash for CommandStatus

source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · source§

fn hash_slice<H>(data: &[Self], state: &mut H)where + H: Hasher, + Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
source§

impl PartialEq<CommandStatus> for CommandStatus

source§

fn eq(&self, other: &CommandStatus) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Copy for CommandStatus

source§

impl Eq for CommandStatus

source§

impl StructuralEq for CommandStatus

source§

impl StructuralPartialEq for CommandStatus

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> CallHasher for Twhere + T: Hash + ?Sized,

§

fn get_hash<H, B>(value: &H, build_hasher: &B) -> u64where + H: Hash + ?Sized, + B: BuildHasher,

source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/sync15/clients_engine/index.html b/book/rust-docs/sync15/clients_engine/index.html new file mode 100644 index 0000000000..e06d1026b5 --- /dev/null +++ b/book/rust-docs/sync15/clients_engine/index.html @@ -0,0 +1,20 @@ +sync15::clients_engine - Rust

Module sync15::clients_engine

source ·
Expand description

The client engine is a crate::engine(Sync Engine) used to manage the +“clients” collection. The clients engine manages the client record for +“this device, and also manages “commands”. +In short, commands target one or more engines and instruct them to +perform various operations - such as wiping all local data. +These commands are used very rarely - currently the only command used +in practice is for bookmarks to wipe all their data, which is sent when +a desktop device restores all bookmarks from a backup. In this scenario, +desktop will delete all local bookmarks then replace them with the backed +up set, which without a “wipe” command would almost certainly cause other +connected devices to “resurrect” the deleted bookmarks.

+

Structs

  • Information about this device to include in its client record. This should +be persisted across syncs, as part of the sync manager state.

Enums

  • Indicates if a command was applied successfully, ignored, or not supported. +Applied and ignored commands are removed from our client record, and never +retried. Unsupported commands are put back into our record, and retried on +subsequent syncs. This is to handle clients adding support for new data +types.

Traits

  • A command processor applies incoming commands like wipes and resets for all +stores, and returns commands to send to other clients. It also manages +settings like the device name and type, which is stored in the special +clients collection.
\ No newline at end of file diff --git a/book/rust-docs/sync15/clients_engine/sidebar-items.js b/book/rust-docs/sync15/clients_engine/sidebar-items.js new file mode 100644 index 0000000000..924f9aaee7 --- /dev/null +++ b/book/rust-docs/sync15/clients_engine/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["Command","CommandStatus"],"struct":["Engine","Settings"],"trait":["CommandProcessor"]}; \ No newline at end of file diff --git a/book/rust-docs/sync15/clients_engine/struct.Engine.html b/book/rust-docs/sync15/clients_engine/struct.Engine.html new file mode 100644 index 0000000000..e13ee44469 --- /dev/null +++ b/book/rust-docs/sync15/clients_engine/struct.Engine.html @@ -0,0 +1,43 @@ +Engine in sync15::clients_engine - Rust
pub struct Engine<'a> {
+    pub command_processor: &'a dyn CommandProcessor,
+    pub interruptee: &'a dyn Interruptee,
+    pub recent_clients: HashMap<String, RemoteClient>,
+}

Fields§

§command_processor: &'a dyn CommandProcessor§interruptee: &'a dyn Interruptee§recent_clients: HashMap<String, RemoteClient>

Implementations§

source§

impl<'a> Engine<'a>

source

pub fn new<'b>( + command_processor: &'b dyn CommandProcessor, + interruptee: &'b dyn Interruptee +) -> Engine<'b>

Creates a new clients engine that delegates to the given command +processor to apply incoming commands.

+
source

pub fn sync( + &mut self, + storage_client: &Sync15StorageClient, + global_state: &GlobalState, + root_sync_key: &KeyBundle, + should_refresh_client: bool +) -> Result<()>

Syncs the clients collection. This works a little differently than +other collections:

+
    +
  1. It can’t be disabled or declined.
  2. +
  3. The sync ID and last sync time aren’t meaningful, since we always +fetch all client records on every sync. As such, the +LocalCollStateMachine that we use for other engines doesn’t +apply to it.
  4. +
  5. It doesn’t persist state directly, but relies on the sync manager +to persist device settings, and process commands.
  6. +
  7. Failing to sync the clients collection is fatal, and aborts the +sync.
  8. +
+

For these reasons, we implement this engine directly in the sync15 +crate, and provide a specialized sync method instead of implementing +sync15::Store.

+
source

pub fn local_client_id(&self) -> String

source

pub fn get_client_data(&self) -> ClientData

Auto Trait Implementations§

§

impl<'a> !RefUnwindSafe for Engine<'a>

§

impl<'a> !Send for Engine<'a>

§

impl<'a> !Sync for Engine<'a>

§

impl<'a> Unpin for Engine<'a>

§

impl<'a> !UnwindSafe for Engine<'a>

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/sync15/clients_engine/struct.Settings.html b/book/rust-docs/sync15/clients_engine/struct.Settings.html new file mode 100644 index 0000000000..7734fc0d76 --- /dev/null +++ b/book/rust-docs/sync15/clients_engine/struct.Settings.html @@ -0,0 +1,31 @@ +Settings in sync15::clients_engine - Rust
pub struct Settings {
+    pub fxa_device_id: String,
+    pub device_name: String,
+    pub device_type: DeviceType,
+}
Expand description

Information about this device to include in its client record. This should +be persisted across syncs, as part of the sync manager state.

+

Fields§

§fxa_device_id: String

The FxA device ID of this client, also used as this client’s record ID +in the clients collection.

+
§device_name: String

The name of this client. This should match the client’s name in the +FxA device manager.

+
§device_type: DeviceType

The type of this client: mobile, tablet, desktop, or other.

+

Trait Implementations§

source§

impl Clone for Settings

source§

fn clone(&self) -> Settings

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for Settings

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Hash for Settings

source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · source§

fn hash_slice<H>(data: &[Self], state: &mut H)where + H: Hasher, + Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
source§

impl PartialEq<Settings> for Settings

source§

fn eq(&self, other: &Settings) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Eq for Settings

source§

impl StructuralEq for Settings

source§

impl StructuralPartialEq for Settings

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> CallHasher for Twhere + T: Hash + ?Sized,

§

fn get_hash<H, B>(value: &H, build_hasher: &B) -> u64where + H: Hash + ?Sized, + B: BuildHasher,

source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/sync15/clients_engine/trait.CommandProcessor.html b/book/rust-docs/sync15/clients_engine/trait.CommandProcessor.html new file mode 100644 index 0000000000..fd4e20709b --- /dev/null +++ b/book/rust-docs/sync15/clients_engine/trait.CommandProcessor.html @@ -0,0 +1,25 @@ +CommandProcessor in sync15::clients_engine - Rust
pub trait CommandProcessor {
+    // Required methods
+    fn settings(&self) -> &Settings;
+    fn fetch_outgoing_commands(&self) -> Result<HashSet<Command>>;
+    fn apply_incoming_command(&self, command: Command) -> Result<CommandStatus>;
+}
Expand description

A command processor applies incoming commands like wipes and resets for all +stores, and returns commands to send to other clients. It also manages +settings like the device name and type, which is stored in the special +clients collection.

+

In practice, this trait only has one implementation, in the sync manager. +It’s split this way because the clients engine depends on internal sync15 +structures, and can’t be implemented as a syncable store…but sync15 +doesn’t know anything about multiple engines. This lets the sync manager +provide its own implementation for handling wipe and reset commands for all +the engines that it manages.

+

Required Methods§

source

fn settings(&self) -> &Settings

source

fn fetch_outgoing_commands(&self) -> Result<HashSet<Command>>

Fetches commands to send to other clients. An error return value means +commands couldn’t be fetched, and halts the sync.

+
source

fn apply_incoming_command(&self, command: Command) -> Result<CommandStatus>

Applies a command sent to this client from another client. This method +should return a CommandStatus indicating whether the command was +processed.

+

An error return value means the sync manager encountered an error +applying the command, and halts the sync to prevent unexpected behavior +(for example, merging local and remote bookmarks, when we were told to +wipe our local bookmarks).

+

Implementors§

\ No newline at end of file diff --git a/book/rust-docs/sync15/device_type/enum.DeviceType.html b/book/rust-docs/sync15/device_type/enum.DeviceType.html new file mode 100644 index 0000000000..628f1a91a8 --- /dev/null +++ b/book/rust-docs/sync15/device_type/enum.DeviceType.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../sync15/enum.DeviceType.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/sync15/enc_payload/struct.EncryptedPayload.html b/book/rust-docs/sync15/enc_payload/struct.EncryptedPayload.html new file mode 100644 index 0000000000..2ff37e3f2f --- /dev/null +++ b/book/rust-docs/sync15/enc_payload/struct.EncryptedPayload.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../sync15/struct.EncryptedPayload.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/sync15/engine/bridged_engine/struct.ApplyResults.html b/book/rust-docs/sync15/engine/bridged_engine/struct.ApplyResults.html new file mode 100644 index 0000000000..905bfabac8 --- /dev/null +++ b/book/rust-docs/sync15/engine/bridged_engine/struct.ApplyResults.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../sync15/engine/struct.ApplyResults.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/sync15/engine/bridged_engine/trait.BridgedEngine.html b/book/rust-docs/sync15/engine/bridged_engine/trait.BridgedEngine.html new file mode 100644 index 0000000000..421552e86a --- /dev/null +++ b/book/rust-docs/sync15/engine/bridged_engine/trait.BridgedEngine.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../sync15/engine/trait.BridgedEngine.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/sync15/engine/bridged_engine/trait.BridgedEngineAdaptor.html b/book/rust-docs/sync15/engine/bridged_engine/trait.BridgedEngineAdaptor.html new file mode 100644 index 0000000000..12916fc0ea --- /dev/null +++ b/book/rust-docs/sync15/engine/bridged_engine/trait.BridgedEngineAdaptor.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../sync15/engine/trait.BridgedEngineAdaptor.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/sync15/engine/enum.EngineSyncAssociation.html b/book/rust-docs/sync15/engine/enum.EngineSyncAssociation.html new file mode 100644 index 0000000000..077cef02ce --- /dev/null +++ b/book/rust-docs/sync15/engine/enum.EngineSyncAssociation.html @@ -0,0 +1,26 @@ +EngineSyncAssociation in sync15::engine - Rust
pub enum EngineSyncAssociation {
+    Disconnected,
+    Connected(CollSyncIds),
+}
Expand description

Defines how an engine is associated with a particular set of records +on a sync storage server. It’s either disconnected, or believes it is +connected with a specific set of GUIDs. If the server and the engine don’t +agree on the exact GUIDs, the engine will assume something radical happened +so it can’t believe anything it thinks it knows about the state of the +server (ie, it will “reset” then do a full reconcile)

+

Variants§

§

Disconnected

This store is disconnected (although it may be connected in the future).

+
§

Connected(CollSyncIds)

Sync is connected, and has the following sync IDs.

+

Trait Implementations§

source§

impl Clone for EngineSyncAssociation

source§

fn clone(&self) -> EngineSyncAssociation

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for EngineSyncAssociation

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl PartialEq<EngineSyncAssociation> for EngineSyncAssociation

source§

fn eq(&self, other: &EngineSyncAssociation) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Eq for EngineSyncAssociation

source§

impl StructuralEq for EngineSyncAssociation

source§

impl StructuralPartialEq for EngineSyncAssociation

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/sync15/engine/enum.RequestOrder.html b/book/rust-docs/sync15/engine/enum.RequestOrder.html new file mode 100644 index 0000000000..455def5847 --- /dev/null +++ b/book/rust-docs/sync15/engine/enum.RequestOrder.html @@ -0,0 +1,30 @@ +RequestOrder in sync15::engine - Rust
pub enum RequestOrder {
+    Oldest,
+    Newest,
+    Index,
+}

Variants§

§

Oldest

§

Newest

§

Index

Implementations§

source§

impl RequestOrder

source

pub fn as_str(self) -> &'static str

Trait Implementations§

source§

impl Clone for RequestOrder

source§

fn clone(&self) -> RequestOrder

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for RequestOrder

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Display for RequestOrder

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Hash for RequestOrder

source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · source§

fn hash_slice<H>(data: &[Self], state: &mut H)where + H: Hasher, + Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
source§

impl Ord for RequestOrder

source§

fn cmp(&self, other: &RequestOrder) -> Ordering

This method returns an Ordering between self and other. Read more
1.21.0 · source§

fn max(self, other: Self) -> Selfwhere + Self: Sized,

Compares and returns the maximum of two values. Read more
1.21.0 · source§

fn min(self, other: Self) -> Selfwhere + Self: Sized,

Compares and returns the minimum of two values. Read more
1.50.0 · source§

fn clamp(self, min: Self, max: Self) -> Selfwhere + Self: Sized + PartialOrd<Self>,

Restrict a value to a certain interval. Read more
source§

impl PartialEq<RequestOrder> for RequestOrder

source§

fn eq(&self, other: &RequestOrder) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl PartialOrd<RequestOrder> for RequestOrder

source§

fn partial_cmp(&self, other: &RequestOrder) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · source§

fn lt(&self, other: &Rhs) -> bool

This method tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · source§

fn le(&self, other: &Rhs) -> bool

This method tests less than or equal to (for self and other) and is used by the <= +operator. Read more
1.0.0 · source§

fn gt(&self, other: &Rhs) -> bool

This method tests greater than (for self and other) and is used by the > operator. Read more
1.0.0 · source§

fn ge(&self, other: &Rhs) -> bool

This method tests greater than or equal to (for self and other) and is used by the >= +operator. Read more
source§

impl Copy for RequestOrder

source§

impl Eq for RequestOrder

source§

impl StructuralEq for RequestOrder

source§

impl StructuralPartialEq for RequestOrder

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> CallHasher for Twhere + T: Hash + ?Sized,

§

fn get_hash<H, B>(value: &H, build_hasher: &B) -> u64where + H: Hash + ?Sized, + B: BuildHasher,

source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/sync15/engine/enum.SyncEngineId.html b/book/rust-docs/sync15/engine/enum.SyncEngineId.html new file mode 100644 index 0000000000..13e3d327e2 --- /dev/null +++ b/book/rust-docs/sync15/engine/enum.SyncEngineId.html @@ -0,0 +1,34 @@ +SyncEngineId in sync15::engine - Rust
pub enum SyncEngineId {
+    Passwords,
+    Tabs,
+    Bookmarks,
+    Addresses,
+    CreditCards,
+    History,
+}
Expand description

The concrete SyncEngine implementations

+

Variants§

§

Passwords

§

Tabs

§

Bookmarks

§

Addresses

§

CreditCards

§

History

Implementations§

source§

impl SyncEngineId

source

pub fn iter() -> impl Iterator<Item = SyncEngineId>

source

pub fn name(&self) -> &'static str

Trait Implementations§

source§

impl Clone for SyncEngineId

source§

fn clone(&self) -> SyncEngineId

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for SyncEngineId

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Display for SyncEngineId

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Hash for SyncEngineId

source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · source§

fn hash_slice<H>(data: &[Self], state: &mut H)where + H: Hasher, + Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
source§

impl Ord for SyncEngineId

source§

fn cmp(&self, other: &SyncEngineId) -> Ordering

This method returns an Ordering between self and other. Read more
1.21.0 · source§

fn max(self, other: Self) -> Selfwhere + Self: Sized,

Compares and returns the maximum of two values. Read more
1.21.0 · source§

fn min(self, other: Self) -> Selfwhere + Self: Sized,

Compares and returns the minimum of two values. Read more
1.50.0 · source§

fn clamp(self, min: Self, max: Self) -> Selfwhere + Self: Sized + PartialOrd<Self>,

Restrict a value to a certain interval. Read more
source§

impl PartialEq<SyncEngineId> for SyncEngineId

source§

fn eq(&self, other: &SyncEngineId) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl PartialOrd<SyncEngineId> for SyncEngineId

source§

fn partial_cmp(&self, other: &SyncEngineId) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · source§

fn lt(&self, other: &Rhs) -> bool

This method tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · source§

fn le(&self, other: &Rhs) -> bool

This method tests less than or equal to (for self and other) and is used by the <= +operator. Read more
1.0.0 · source§

fn gt(&self, other: &Rhs) -> bool

This method tests greater than (for self and other) and is used by the > operator. Read more
1.0.0 · source§

fn ge(&self, other: &Rhs) -> bool

This method tests greater than or equal to (for self and other) and is used by the >= +operator. Read more
source§

impl TryFrom<&str> for SyncEngineId

§

type Error = String

The type returned in the event of a conversion error.
source§

fn try_from(value: &str) -> Result<Self, Self::Error>

Performs the conversion.
source§

impl Eq for SyncEngineId

source§

impl StructuralEq for SyncEngineId

source§

impl StructuralPartialEq for SyncEngineId

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> CallHasher for Twhere + T: Hash + ?Sized,

§

fn get_hash<H, B>(value: &H, build_hasher: &B) -> u64where + H: Hash + ?Sized, + B: BuildHasher,

source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/sync15/engine/index.html b/book/rust-docs/sync15/engine/index.html new file mode 100644 index 0000000000..2858de3170 --- /dev/null +++ b/book/rust-docs/sync15/engine/index.html @@ -0,0 +1,32 @@ +sync15::engine - Rust

Module sync15::engine

source ·
Expand description

This module is used by crates which need to implement a “sync engine”. +At a high-level, a “sync engine” is code which knows how to take records +from a sync server, apply and reconcile them with the local data, then +provide records which should be uploaded to the server.

+

Note that the “sync engine” does not itself talk to the server, nor does +it manage the state of the remote server, nor does it do any of the +encryption/decryption - that is the responsbility of the “sync client”, as +implemented in the [client] module (or in some cases, implemented externally)

+

There are currently 2 types of engine:

+
    +
  • Code which implements the crate::engine::sync_engine::SyncEngine +trait. These are the “original” Rust engines, designed to be used with +the crate::client(sync client)
  • +
  • Code which implements the crate::engine::bridged_engine::BridgedEngine +trait. These engines are a “bridge” between the Desktop JS Sync world and +this rust code. +While these engines end up doing the same thing, the difference is due to +implementation differences between the Desktop Sync client and the Rust +client. +We intend merging these engines - the first step will be to merge the +types and payload management used by these traits, then to combine the +requirements into a single trait that captures both use-cases.
  • +
+

Structs

Enums

  • Defines how an engine is associated with a particular set of records +on a sync storage server. It’s either disconnected, or believes it is +connected with a specific set of GUIDs. If the server and the engine don’t +agree on the exact GUIDs, the engine will assume something radical happened +so it can’t believe anything it thinks it knows about the state of the +server (ie, it will “reset” then do a full reconcile)
  • The concrete SyncEngine implementations

Traits

  • A BridgedEngine acts as a bridge between application-services, rust +implemented sync engines and sync engines as defined by Desktop Firefox.
  • A “sync engine” is a thing that knows how to sync. It’s often implemented +by a “store” (which is the generic term responsible for all storage +associated with a component, including storage required for sync.)
\ No newline at end of file diff --git a/book/rust-docs/sync15/engine/request/enum.RequestOrder.html b/book/rust-docs/sync15/engine/request/enum.RequestOrder.html new file mode 100644 index 0000000000..66f41f5ffe --- /dev/null +++ b/book/rust-docs/sync15/engine/request/enum.RequestOrder.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../sync15/engine/enum.RequestOrder.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/sync15/engine/request/struct.CollectionRequest.html b/book/rust-docs/sync15/engine/request/struct.CollectionRequest.html new file mode 100644 index 0000000000..23f5493dc2 --- /dev/null +++ b/book/rust-docs/sync15/engine/request/struct.CollectionRequest.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../sync15/engine/struct.CollectionRequest.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/sync15/engine/sidebar-items.js b/book/rust-docs/sync15/engine/sidebar-items.js new file mode 100644 index 0000000000..82e62fd552 --- /dev/null +++ b/book/rust-docs/sync15/engine/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["EngineSyncAssociation","RequestOrder","SyncEngineId"],"struct":["ApplyResults","CollSyncIds","CollectionRequest"],"trait":["BridgedEngine","BridgedEngineAdaptor","SyncEngine"]}; \ No newline at end of file diff --git a/book/rust-docs/sync15/engine/struct.ApplyResults.html b/book/rust-docs/sync15/engine/struct.ApplyResults.html new file mode 100644 index 0000000000..3f07a3a1d6 --- /dev/null +++ b/book/rust-docs/sync15/engine/struct.ApplyResults.html @@ -0,0 +1,22 @@ +ApplyResults in sync15::engine - Rust

Struct sync15::engine::ApplyResults

source ·
pub struct ApplyResults {
+    pub records: Vec<OutgoingBso>,
+    pub num_reconciled: Option<usize>,
+}

Fields§

§records: Vec<OutgoingBso>

List of records

+
§num_reconciled: Option<usize>

The number of incoming records whose contents were merged because they +changed on both sides. None indicates we aren’t reporting this +information.

+

Implementations§

source§

impl ApplyResults

source

pub fn new( + records: Vec<OutgoingBso>, + num_reconciled: impl Into<Option<usize>> +) -> Self

Trait Implementations§

source§

impl Debug for ApplyResults

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for ApplyResults

source§

fn default() -> ApplyResults

Returns the “default value” for a type. Read more
source§

impl From<Vec<OutgoingBso, Global>> for ApplyResults

source§

fn from(records: Vec<OutgoingBso>) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/sync15/engine/struct.CollSyncIds.html b/book/rust-docs/sync15/engine/struct.CollSyncIds.html new file mode 100644 index 0000000000..e9dac8ff62 --- /dev/null +++ b/book/rust-docs/sync15/engine/struct.CollSyncIds.html @@ -0,0 +1,18 @@ +CollSyncIds in sync15::engine - Rust

Struct sync15::engine::CollSyncIds

source ·
pub struct CollSyncIds {
+    pub global: Guid,
+    pub coll: Guid,
+}

Fields§

§global: Guid§coll: Guid

Trait Implementations§

source§

impl Clone for CollSyncIds

source§

fn clone(&self) -> CollSyncIds

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for CollSyncIds

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl PartialEq<CollSyncIds> for CollSyncIds

source§

fn eq(&self, other: &CollSyncIds) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Eq for CollSyncIds

source§

impl StructuralEq for CollSyncIds

source§

impl StructuralPartialEq for CollSyncIds

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/sync15/engine/struct.CollectionRequest.html b/book/rust-docs/sync15/engine/struct.CollectionRequest.html new file mode 100644 index 0000000000..2ffdd8032b --- /dev/null +++ b/book/rust-docs/sync15/engine/struct.CollectionRequest.html @@ -0,0 +1,24 @@ +CollectionRequest in sync15::engine - Rust
pub struct CollectionRequest {
+    pub collection: CollectionName,
+    pub full: bool,
+    pub ids: Option<Vec<Guid>>,
+    pub limit: Option<RequestLimit>,
+    pub older: Option<ServerTimestamp>,
+    pub newer: Option<ServerTimestamp>,
+}

Fields§

§collection: CollectionName§full: bool§ids: Option<Vec<Guid>>§limit: Option<RequestLimit>§older: Option<ServerTimestamp>§newer: Option<ServerTimestamp>

Implementations§

Trait Implementations§

source§

impl Clone for CollectionRequest

source§

fn clone(&self) -> CollectionRequest

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for CollectionRequest

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for CollectionRequest

source§

fn default() -> CollectionRequest

Returns the “default value” for a type. Read more
source§

impl PartialEq<CollectionRequest> for CollectionRequest

source§

fn eq(&self, other: &CollectionRequest) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Eq for CollectionRequest

source§

impl StructuralEq for CollectionRequest

source§

impl StructuralPartialEq for CollectionRequest

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/sync15/engine/sync_engine/enum.EngineSyncAssociation.html b/book/rust-docs/sync15/engine/sync_engine/enum.EngineSyncAssociation.html new file mode 100644 index 0000000000..152d44046e --- /dev/null +++ b/book/rust-docs/sync15/engine/sync_engine/enum.EngineSyncAssociation.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../sync15/engine/enum.EngineSyncAssociation.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/sync15/engine/sync_engine/enum.SyncEngineId.html b/book/rust-docs/sync15/engine/sync_engine/enum.SyncEngineId.html new file mode 100644 index 0000000000..52ca24800f --- /dev/null +++ b/book/rust-docs/sync15/engine/sync_engine/enum.SyncEngineId.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../sync15/engine/enum.SyncEngineId.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/sync15/engine/sync_engine/struct.CollSyncIds.html b/book/rust-docs/sync15/engine/sync_engine/struct.CollSyncIds.html new file mode 100644 index 0000000000..a743089a0d --- /dev/null +++ b/book/rust-docs/sync15/engine/sync_engine/struct.CollSyncIds.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../sync15/engine/struct.CollSyncIds.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/sync15/engine/sync_engine/trait.SyncEngine.html b/book/rust-docs/sync15/engine/sync_engine/trait.SyncEngine.html new file mode 100644 index 0000000000..61fdbfdfb6 --- /dev/null +++ b/book/rust-docs/sync15/engine/sync_engine/trait.SyncEngine.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../sync15/engine/trait.SyncEngine.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/sync15/engine/trait.BridgedEngine.html b/book/rust-docs/sync15/engine/trait.BridgedEngine.html new file mode 100644 index 0000000000..2d43c23ff7 --- /dev/null +++ b/book/rust-docs/sync15/engine/trait.BridgedEngine.html @@ -0,0 +1,74 @@ +BridgedEngine in sync15::engine - Rust
pub trait BridgedEngine: Send + Sync {
+
Show 13 methods // Required methods + fn last_sync(&self) -> Result<i64>; + fn set_last_sync(&self, last_sync_millis: i64) -> Result<()>; + fn sync_id(&self) -> Result<Option<String>>; + fn reset_sync_id(&self) -> Result<String>; + fn ensure_current_sync_id(&self, new_sync_id: &str) -> Result<String>; + fn sync_started(&self) -> Result<()>; + fn store_incoming(&self, incoming_records: Vec<IncomingBso>) -> Result<()>; + fn apply(&self) -> Result<ApplyResults>; + fn set_uploaded( + &self, + server_modified_millis: i64, + ids: &[Guid] + ) -> Result<()>; + fn sync_finished(&self) -> Result<()>; + fn reset(&self) -> Result<()>; + fn wipe(&self) -> Result<()>; + + // Provided method + fn prepare_for_sync(&self, _client_data: &str) -> Result<()> { ... } +
}
Expand description

A BridgedEngine acts as a bridge between application-services, rust +implemented sync engines and sync engines as defined by Desktop Firefox.

+

Desktop Firefox has an abstract implementation of a Sync +Engine +with a number of functions each engine is expected to override. Engines +implemented in Rust use a different shape (specifically, the +SyncEngine trait), so this BridgedEngine trait adapts +between the 2.

+

Required Methods§

source

fn last_sync(&self) -> Result<i64>

Returns the last sync time, in milliseconds, for this engine’s +collection. This is called before each sync, to determine the lower +bound for new records to fetch from the server.

+
source

fn set_last_sync(&self, last_sync_millis: i64) -> Result<()>

Sets the last sync time, in milliseconds. This is called throughout +the sync, to fast-forward the stored last sync time to match the +timestamp on the uploaded records.

+
source

fn sync_id(&self) -> Result<Option<String>>

Returns the sync ID for this engine’s collection. This is only used in +tests.

+
source

fn reset_sync_id(&self) -> Result<String>

Resets the sync ID for this engine’s collection, returning the new ID. +As a side effect, implementations should reset all local Sync state, +as in reset. +(Note that bridged engines never maintain the “global” guid - that’s all managed +by the bridged_engine consumer (ie, desktop). bridged_engines only care about +the per-collection one.)

+
source

fn ensure_current_sync_id(&self, new_sync_id: &str) -> Result<String>

Ensures that the locally stored sync ID for this engine’s collection +matches the new_sync_id from the server. If the two don’t match, +implementations should reset all local Sync state, as in reset. +This method returns the assigned sync ID, which can be either the +new_sync_id, or a different one if the engine wants to force other +devices to reset their Sync state for this collection the next time they +sync.

+
source

fn sync_started(&self) -> Result<()>

Indicates that the engine is about to start syncing. This is called +once per sync, and always before store_incoming.

+
source

fn store_incoming(&self, incoming_records: Vec<IncomingBso>) -> Result<()>

Stages a batch of incoming Sync records. This is called multiple +times per sync, once for each batch. Implementations can use the +signal to check if the operation was aborted, and cancel any +pending work.

+
source

fn apply(&self) -> Result<ApplyResults>

Applies all staged records, reconciling changes on both sides and +resolving conflicts. Returns a list of records to upload.

+
source

fn set_uploaded(&self, server_modified_millis: i64, ids: &[Guid]) -> Result<()>

Indicates that the given record IDs were uploaded successfully to the +server. This is called multiple times per sync, once for each batch +upload.

+
source

fn sync_finished(&self) -> Result<()>

Indicates that all records have been uploaded. At this point, any record +IDs marked for upload that haven’t been passed to set_uploaded, can be +assumed to have failed: for example, because the server rejected a record +with an invalid TTL or sort index.

+
source

fn reset(&self) -> Result<()>

Resets all local Sync state, including any change flags, mirrors, and +the last sync time, such that the next sync is treated as a first sync +with all new local data. Does not erase any local user data.

+
source

fn wipe(&self) -> Result<()>

Erases all local user data for this collection, and any Sync metadata. +This method is destructive, and unused for most collections.

+

Provided Methods§

source

fn prepare_for_sync(&self, _client_data: &str) -> Result<()>

Tells the tabs engine about recent FxA devices. A bit of a leaky abstration as it only +makes sense for tabs. +The arg is a json serialized ClientData struct.

+

Implementors§

\ No newline at end of file diff --git a/book/rust-docs/sync15/engine/trait.BridgedEngineAdaptor.html b/book/rust-docs/sync15/engine/trait.BridgedEngineAdaptor.html new file mode 100644 index 0000000000..de0025a374 --- /dev/null +++ b/book/rust-docs/sync15/engine/trait.BridgedEngineAdaptor.html @@ -0,0 +1,9 @@ +BridgedEngineAdaptor in sync15::engine - Rust
pub trait BridgedEngineAdaptor: Send + Sync {
+    // Required methods
+    fn last_sync(&self) -> Result<i64>;
+    fn set_last_sync(&self, last_sync_millis: i64) -> Result<()>;
+    fn engine(&self) -> &dyn SyncEngine;
+
+    // Provided method
+    fn sync_started(&self) -> Result<()> { ... }
+}

Required Methods§

source

fn last_sync(&self) -> Result<i64>

source

fn set_last_sync(&self, last_sync_millis: i64) -> Result<()>

source

fn engine(&self) -> &dyn SyncEngine

Provided Methods§

Implementors§

\ No newline at end of file diff --git a/book/rust-docs/sync15/engine/trait.SyncEngine.html b/book/rust-docs/sync15/engine/trait.SyncEngine.html new file mode 100644 index 0000000000..b08121dc87 --- /dev/null +++ b/book/rust-docs/sync15/engine/trait.SyncEngine.html @@ -0,0 +1,132 @@ +SyncEngine in sync15::engine - Rust

Trait sync15::engine::SyncEngine

source ·
pub trait SyncEngine {
+    // Required methods
+    fn collection_name(&self) -> CollectionName;
+    fn stage_incoming(
+        &self,
+        inbound: Vec<IncomingBso>,
+        telem: &mut Engine
+    ) -> Result<()>;
+    fn apply(
+        &self,
+        timestamp: ServerTimestamp,
+        telem: &mut Engine
+    ) -> Result<Vec<OutgoingBso>>;
+    fn set_uploaded(
+        &self,
+        new_timestamp: ServerTimestamp,
+        ids: Vec<Guid>
+    ) -> Result<()>;
+    fn get_collection_request(
+        &self,
+        server_timestamp: ServerTimestamp
+    ) -> Result<Option<CollectionRequest>>;
+    fn get_sync_assoc(&self) -> Result<EngineSyncAssociation>;
+    fn reset(&self, assoc: &EngineSyncAssociation) -> Result<()>;
+    fn wipe(&self) -> Result<()>;
+
+    // Provided methods
+    fn prepare_for_sync(
+        &self,
+        _get_client_data: &dyn Fn() -> ClientData
+    ) -> Result<()> { ... }
+    fn set_local_encryption_key(&mut self, _key: &str) -> Result<()> { ... }
+    fn sync_finished(&self) -> Result<()> { ... }
+}
Expand description

A “sync engine” is a thing that knows how to sync. It’s often implemented +by a “store” (which is the generic term responsible for all storage +associated with a component, including storage required for sync.)

+

The model described by this trait is that engines first “stage” sets of incoming records, +then apply them returning outgoing records, then handle the success (or otherwise) of each +batch as it’s uploaded.

+

Staging incoming records is (or should be ;) done in batches - eg, 1000 record chunks. +Some engines will “stage” these into a database temp table, while ones expecting less records +might just store them in memory.

+

For outgoing records, a single vec is supplied by the engine. The sync client will use the +batch facilities of the server to make multiple POST requests and commit them. +Sadly it’s not truly atomic (there’s a batch size limit) - so the model reflects that in that +the engine gets told each time a batch is committed, which might happen more than once for the +supplied vec. We should upgrade this model so the engine can avoid reading every outgoing +record into memory at once (ie, we should try and better reflect the upload batch model at +this level)

+

Sync Engines should not assume they live for exactly one sync, so prepare_for_sync() should +clean up any state, including staged records, from previous syncs.

+

Different engines will produce errors of different types. To accommodate +this, we force them all to return anyhow::Error.

+

Required Methods§

source

fn collection_name(&self) -> CollectionName

source

fn stage_incoming( + &self, + inbound: Vec<IncomingBso>, + telem: &mut Engine +) -> Result<()>

Stage some incoming records. This might be called multiple times in the same sync +if we fetch the incoming records in batches.

+

Note there is no timestamp provided here, because the procedure for fetching in batches +means that the timestamp advancing during a batch means we must abort and start again. +The final collection timestamp after staging all records is supplied to apply()

+
source

fn apply( + &self, + timestamp: ServerTimestamp, + telem: &mut Engine +) -> Result<Vec<OutgoingBso>>

Apply the staged records, returning outgoing records. +Ideally we would adjust this model to better support batching of outgoing records +without needing to keep them all in memory (ie, an iterator or similar?)

+
source

fn set_uploaded( + &self, + new_timestamp: ServerTimestamp, + ids: Vec<Guid> +) -> Result<()>

Indicates that the given record IDs were uploaded successfully to the server. +This may be called multiple times per sync, once for each batch. Batching is determined +dynamically based on payload sizes and counts via the server’s advertised limits.

+
source

fn get_collection_request( + &self, + server_timestamp: ServerTimestamp +) -> Result<Option<CollectionRequest>>

The engine is responsible for building a single collection request. Engines +typically will store a lastModified timestamp and use that to build a +request saying “give me full records since that date” - however, other +engines might do something fancier. It can return None if the server timestamp +has not advanced since the last sync. +This could even later be extended to handle “backfills”, and we might end up +wanting one engine to use multiple collections (eg, as a “foreign key” via guid), etc.

+
source

fn get_sync_assoc(&self) -> Result<EngineSyncAssociation>

Get persisted sync IDs. If they don’t match the global state we’ll be +reset() with the new IDs.

+
source

fn reset(&self, assoc: &EngineSyncAssociation) -> Result<()>

Reset the engine (and associated store) without wiping local data, +ready for a “first sync”. +assoc defines how this store is to be associated with sync.

+
source

fn wipe(&self) -> Result<()>

Provided Methods§

source

fn prepare_for_sync( + &self, + _get_client_data: &dyn Fn() -> ClientData +) -> Result<()>

Prepares the engine for syncing. The tabs engine currently uses this to +store the current list of clients, which it uses to look up device names +and types.

+

Note that this method is only called by sync_multiple, and only if a +command processor is registered. In particular, prepare_for_sync will +not be called if the store is synced using sync::synchronize or +sync_multiple::sync_multiple. It will be called if the store is +synced via the Sync Manager.

+

TODO(issue #2590): This is pretty cludgey and will be hard to extend for +any case other than the tabs case. We should find another way to support +tabs…

+
source

fn set_local_encryption_key(&mut self, _key: &str) -> Result<()>

Tells the engine what the local encryption key is for the data managed +by the engine. This is only used by collections that store data +encrypted locally and is unrelated to the encryption used by Sync. +The intent is that for such collections, this key can be used to +decrypt local data before it is re-encrypted by Sync and sent to the +storage servers, and similarly, data from the storage servers will be +decrypted by Sync, then encrypted by the local encryption key before +being added to the local database.

+

The expectation is that the key value is being maintained by the +embedding application in some secure way suitable for the environment +in which the app is running - eg, the OS “keychain”. The value of the +key is implementation dependent - it is expected that the engine and +embedding application already have some external agreement about how +to generate keys and in what form they are exchanged. Finally, there’s +an assumption that sync engines are short-lived and only live for a +single sync - this means that sync doesn’t hold on to the key for an +extended period. In practice, all sync engines which aren’t a “bridged +engine” are short lived - we might need to rethink this later if we need +engines with local encryption keys to be used on desktop.

+

This will panic if called by an engine that doesn’t have explicit +support for local encryption keys as that implies a degree of confusion +which shouldn’t be possible to ignore.

+
source

fn sync_finished(&self) -> Result<()>

Called once the sync is finished. Not currently called if uploads fail (which +seems sad, but the other batching confusion there needs sorting out first). +Many engines will have nothing to do here, as most “post upload” work should be +done in set_uploaded()

+

Implementors§

\ No newline at end of file diff --git a/book/rust-docs/sync15/enum.DeviceType.html b/book/rust-docs/sync15/enum.DeviceType.html new file mode 100644 index 0000000000..5c9af1b048 --- /dev/null +++ b/book/rust-docs/sync15/enum.DeviceType.html @@ -0,0 +1,53 @@ +DeviceType in sync15 - Rust

Enum sync15::DeviceType

source ·
pub enum DeviceType {
+    Desktop,
+    Mobile,
+    Tablet,
+    VR,
+    TV,
+    Unknown,
+}
Expand description

Enumeration for the different types of device.

+

Firefox Accounts and the broader Sync universe separates devices into broad categories for +various purposes, such as distinguishing a desktop PC from a mobile phone.

+

A special variant in this enum, DeviceType::Unknown is used to capture +the string values we don’t recognise. It also has a custom serde serializer and deserializer +which implements the following semantics:

+
    +
  • deserializing a DeviceType which uses a string value we don’t recognise or null will return +DeviceType::Unknown rather than returning an error.
  • +
  • serializing DeviceType::Unknown will serialize null.
  • +
+

This has a few important implications:

+
    +
  • In general, Option<DeviceType> should be avoided, and a plain DeviceType used instead, +because in that case, None would be semantically identical to DeviceType::Unknown and +as mentioned above, null already deserializes as DeviceType::Unknown.
  • +
  • Any unknown device types can not be round-tripped via this enum - eg, if you deserialize +a struct holding a DeviceType string value we don’t recognize, then re-serialize it, the +original string value is lost. We don’t consider this a problem because in practice, we only +upload records with this device’s type, not the type of other devices, and it’s reasonable +to assume that this module knows about all valid device types for the device type it is +deployed on.
  • +
+

Variants§

§

Desktop

§

Mobile

§

Tablet

§

VR

§

TV

§

Unknown

Trait Implementations§

source§

impl Clone for DeviceType

source§

fn clone(&self) -> DeviceType

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for DeviceType

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for DeviceType

source§

fn default() -> DeviceType

Returns the “default value” for a type. Read more
source§

impl<'de> Deserialize<'de> for DeviceType

source§

fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>where + D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl Hash for DeviceType

source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · source§

fn hash_slice<H>(data: &[Self], state: &mut H)where + H: Hasher, + Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
source§

impl PartialEq<DeviceType> for DeviceType

source§

fn eq(&self, other: &DeviceType) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Serialize for DeviceType

source§

fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>where + S: Serializer,

Serialize this value into the given Serde serializer. Read more
source§

impl Copy for DeviceType

source§

impl Eq for DeviceType

source§

impl StructuralEq for DeviceType

source§

impl StructuralPartialEq for DeviceType

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> CallHasher for Twhere + T: Hash + ?Sized,

§

fn get_hash<H, B>(value: &H, build_hasher: &B) -> u64where + H: Hash + ?Sized, + B: BuildHasher,

source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/sync15/enum.Error.html b/book/rust-docs/sync15/enum.Error.html new file mode 100644 index 0000000000..8b70d7eb5a --- /dev/null +++ b/book/rust-docs/sync15/enum.Error.html @@ -0,0 +1,42 @@ +Error in sync15 - Rust

Enum sync15::Error

source ·
pub enum Error {
+
Show 24 variants BadKeyLength(&'static str, usize, usize), + HmacMismatch, + CryptoError(Error), + Base64Decode(DecodeError), + JsonError(Error), + BadCleartextUtf8(FromUtf8Error), + HawkError(Error), + TokenserverHttpError(u16), + StorageHttpError(ErrorResponse), + BackoffError(SystemTime), + RecordTooLargeError, + RecordUploadFailed, + StorageResetError, + UnacceptableUrl(String), + MissingServerTimestamp, + ServerBatchProblem(&'static str), + SetupRace, + ClientUpgradeRequired, + SetupRequired, + StoreError(Error), + RequestError(Error), + UnexpectedStatus(UnexpectedStatus), + MalformedUrl(ParseError), + Interrupted(Interrupted), +
}

Variants§

§

BadKeyLength(&'static str, usize, usize)

§

HmacMismatch

§

CryptoError(Error)

§

Base64Decode(DecodeError)

§

JsonError(Error)

§

BadCleartextUtf8(FromUtf8Error)

§

HawkError(Error)

§

TokenserverHttpError(u16)

§

StorageHttpError(ErrorResponse)

§

BackoffError(SystemTime)

§

RecordTooLargeError

§

RecordUploadFailed

§

StorageResetError

Used for things like a node reassignment or an unexpected syncId +implying the app needs to “reset” its understanding of remote storage.

+
§

UnacceptableUrl(String)

§

MissingServerTimestamp

§

ServerBatchProblem(&'static str)

§

SetupRace

§

ClientUpgradeRequired

§

SetupRequired

§

StoreError(Error)

§

RequestError(Error)

§

UnexpectedStatus(UnexpectedStatus)

§

MalformedUrl(ParseError)

§

Interrupted(Interrupted)

Trait Implementations§

source§

impl Debug for Error

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Display for Error

source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Error for Error

source§

fn source(&self) -> Option<&(dyn Error + 'static)>

The lower-level source of this error, if any. Read more
1.0.0 · source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type based access to context intended for error reports. Read more
source§

impl<'a> From<&'a Error> for SyncFailure

source§

fn from(e: &Error) -> SyncFailure

Converts to this type from the input type.
source§

impl From<DecodeError> for Error

source§

fn from(source: DecodeError) -> Self

Converts to this type from the input type.
source§

impl From<Error> for Error

source§

fn from(source: Error) -> Self

Converts to this type from the input type.
source§

impl From<Error> for Error

source§

fn from(source: Error) -> Self

Converts to this type from the input type.
source§

impl From<Error> for Error

source§

fn from(source: Error) -> Self

Converts to this type from the input type.
source§

impl From<Error> for Error

source§

fn from(source: Error) -> Self

Converts to this type from the input type.
source§

impl From<Error> for Error

source§

fn from(source: Error) -> Self

Converts to this type from the input type.
source§

impl From<FromUtf8Error> for Error

source§

fn from(source: FromUtf8Error) -> Self

Converts to this type from the input type.
source§

impl From<Interrupted> for Error

source§

fn from(source: Interrupted) -> Self

Converts to this type from the input type.
source§

impl From<ParseError> for Error

source§

fn from(source: ParseError) -> Self

Converts to this type from the input type.
source§

impl From<UnexpectedStatus> for Error

source§

fn from(source: UnexpectedStatus) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

§

impl !RefUnwindSafe for Error

§

impl Send for Error

§

impl Sync for Error

§

impl Unpin for Error

§

impl !UnwindSafe for Error

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<E> Provider for Ewhere + E: Error + ?Sized,

source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (provide_any)
Data providers should implement this method to provide all values they are able to +provide by using demand. Read more
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/sync15/error/enum.Error.html b/book/rust-docs/sync15/error/enum.Error.html new file mode 100644 index 0000000000..0ae72cbe8d --- /dev/null +++ b/book/rust-docs/sync15/error/enum.Error.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../sync15/enum.Error.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/sync15/error/type.Result.html b/book/rust-docs/sync15/error/type.Result.html new file mode 100644 index 0000000000..6b86aba5fd --- /dev/null +++ b/book/rust-docs/sync15/error/type.Result.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../sync15/type.Result.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/sync15/index.html b/book/rust-docs/sync15/index.html new file mode 100644 index 0000000000..ae95c7d664 --- /dev/null +++ b/book/rust-docs/sync15/index.html @@ -0,0 +1,21 @@ +sync15 - Rust

Crate sync15

source ·

Modules

  • A module for everything needed to be a “sync client” - ie, a device which +can perform a full sync of any number of collections, including managing +the server state.
  • The client engine is a crate::engine(Sync Engine) used to manage the +“clients” collection. The clients engine manages the client record for +“this device, and also manages “commands”. +In short, commands target one or more engines and instruct them to +perform various operations - such as wiping all local data. +These commands are used very rarely - currently the only command used +in practice is for bookmarks to wipe all their data, which is sent when +a desktop device restores all bookmarks from a backup. In this scenario, +desktop will delete all local bookmarks then replace them with the backed +up set, which without a “wipe” command would almost certainly cause other +connected devices to “resurrect” the deleted bookmarks.
  • This module is used by crates which need to implement a “sync engine”. +At a high-level, a “sync engine” is code which knows how to take records +from a sync server, apply and reconcile them with the local data, then +provide records which should be uploaded to the server.
  • Manage recording sync telemetry. Assumes some external telemetry +library/code which manages submitting.

Structs

  • Argument to Store::prepare_for_sync. See comment there for more info. Only +really intended to be used by tabs engine.
  • A representation of an encrypted payload. Used as the payload in EncryptedBso and +also anywhere else the sync keys might be used to encrypt/decrypt, such as send-tab payloads.
  • This is a type intended to be used to represent the guids used by sync. It +has several benefits over using a String:
  • Information about a remote client in the clients collection.
  • Typesafe way to manage server timestamps without accidentally mixing them up with +local ones.

Enums

Type Definitions

\ No newline at end of file diff --git a/book/rust-docs/sync15/key_bundle/struct.KeyBundle.html b/book/rust-docs/sync15/key_bundle/struct.KeyBundle.html new file mode 100644 index 0000000000..94e6b3d7f6 --- /dev/null +++ b/book/rust-docs/sync15/key_bundle/struct.KeyBundle.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../sync15/struct.KeyBundle.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/sync15/server_timestamp/struct.ServerTimestamp.html b/book/rust-docs/sync15/server_timestamp/struct.ServerTimestamp.html new file mode 100644 index 0000000000..aaa7422ffe --- /dev/null +++ b/book/rust-docs/sync15/server_timestamp/struct.ServerTimestamp.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../sync15/struct.ServerTimestamp.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/sync15/sidebar-items.js b/book/rust-docs/sync15/sidebar-items.js new file mode 100644 index 0000000000..3fb6f98918 --- /dev/null +++ b/book/rust-docs/sync15/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["DeviceType","Error"],"mod":["bso","client","clients_engine","engine","telemetry"],"struct":["ClientData","EncryptedPayload","Guid","KeyBundle","RemoteClient","ServerTimestamp"],"type":["CollectionName","Result"]}; \ No newline at end of file diff --git a/book/rust-docs/sync15/struct.ClientData.html b/book/rust-docs/sync15/struct.ClientData.html new file mode 100644 index 0000000000..49786f3964 --- /dev/null +++ b/book/rust-docs/sync15/struct.ClientData.html @@ -0,0 +1,25 @@ +ClientData in sync15 - Rust

Struct sync15::ClientData

source ·
pub struct ClientData {
+    pub local_client_id: String,
+    pub recent_clients: HashMap<String, RemoteClient>,
+}
Expand description

Argument to Store::prepare_for_sync. See comment there for more info. Only +really intended to be used by tabs engine.

+

Fields§

§local_client_id: String§recent_clients: HashMap<String, RemoteClient>

A hashmap of records in the clients collection. Key is the id of the record in +that collection, which may or may not be the device’s fxa_device_id.

+

Trait Implementations§

source§

impl Clone for ClientData

source§

fn clone(&self) -> ClientData

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for ClientData

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl<'de> Deserialize<'de> for ClientData

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl PartialEq<ClientData> for ClientData

source§

fn eq(&self, other: &ClientData) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Serialize for ClientData

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more
source§

impl Eq for ClientData

source§

impl StructuralEq for ClientData

source§

impl StructuralPartialEq for ClientData

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/sync15/struct.EncryptedPayload.html b/book/rust-docs/sync15/struct.EncryptedPayload.html new file mode 100644 index 0000000000..73b47df376 --- /dev/null +++ b/book/rust-docs/sync15/struct.EncryptedPayload.html @@ -0,0 +1,26 @@ +EncryptedPayload in sync15 - Rust
pub struct EncryptedPayload {
+    pub iv: String,
+    pub hmac: String,
+    pub ciphertext: String,
+}
Expand description

A representation of an encrypted payload. Used as the payload in EncryptedBso and +also anywhere else the sync keys might be used to encrypt/decrypt, such as send-tab payloads.

+

Fields§

§iv: String§hmac: String§ciphertext: String

Implementations§

source§

impl EncryptedPayload

source

pub fn serialized_len(&self) -> usize

source

pub fn decrypt(&self, key: &KeyBundle) -> Result<String>

source

pub fn decrypt_into<T>(&self, key: &KeyBundle) -> Result<T>where + for<'a> T: Deserialize<'a>,

source

pub fn from_cleartext(key: &KeyBundle, cleartext: String) -> Result<Self>

source

pub fn from_cleartext_payload<T: Serialize>( + key: &KeyBundle, + cleartext_payload: &T +) -> Result<Self>

Trait Implementations§

source§

impl Clone for EncryptedPayload

source§

fn clone(&self) -> EncryptedPayload

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for EncryptedPayload

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl<'de> Deserialize<'de> for EncryptedPayload

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl Serialize for EncryptedPayload

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/sync15/struct.Guid.html b/book/rust-docs/sync15/struct.Guid.html new file mode 100644 index 0000000000..2f345a0d60 --- /dev/null +++ b/book/rust-docs/sync15/struct.Guid.html @@ -0,0 +1,1264 @@ +Guid in sync15 - Rust

Struct sync15::Guid

pub struct Guid(_);
Expand description

This is a type intended to be used to represent the guids used by sync. It +has several benefits over using a String:

+
    +
  1. +

    It’s more explicit about what is being stored, and could prevent bugs +where a Guid is passed to a function expecting text.

    +
  2. +
  3. +

    Guids are guaranteed to be immutable.

    +
  4. +
  5. +

    It’s optimized for the guids commonly used by sync. In particular, short guids +(including the guids which would meet PlacesUtils.isValidGuid) do not incur +any heap allocation, and are stored inline.

    +
  6. +
+

Implementations§

§

impl Guid

pub fn new(s: &str) -> Guid

Create a guid from a str.

+

pub const fn empty() -> Guid

Create an empty guid. Usable as a constant.

+

pub fn random() -> Guid

Create a random guid (of 12 base64url characters). Requires the random +feature.

+

pub fn from_string(s: String) -> Guid

Convert b into a Guid.

+

pub fn from_slice(b: &[u8]) -> Guid

Convert b into a Guid.

+

pub fn from_vec(v: Vec<u8, Global>) -> Guid

Convert v to a Guid, consuming it.

+

pub fn as_bytes(&self) -> &[u8]

Get the data backing this Guid as a &[u8].

+

pub fn as_str(&self) -> &str

Get the data backing this Guid as a &str.

+

pub fn into_string(self) -> String

Convert this Guid into a String, consuming it in the process.

+

pub fn is_valid_for_sync_server(&self) -> bool

Returns true for Guids that are deemed valid by the sync server. +See https://github.com/mozilla-services/server-syncstorage/blob/d92ef07877aebd05b92f87f6ade341d6a55bffc8/syncstorage/bso.py#L24

+

pub fn is_valid_for_places(&self) -> bool

Returns true for Guids that are valid places guids, and false for all others.

+

pub fn is_valid_places_byte(b: u8) -> bool

Returns true if the byte b is a valid base64url byte.

+

Methods from Deref<Target = str>§

1.0.0 · source

pub fn len(&self) -> usize

Returns the length of self.

+

This length is in bytes, not chars or graphemes. In other words, +it might not be what a human considers the length of the string.

+
Examples
+
let len = "foo".len();
+assert_eq!(3, len);
+
+assert_eq!("ƒoo".len(), 4); // fancy f!
+assert_eq!("ƒoo".chars().count(), 3);
+
1.0.0 · source

pub fn is_empty(&self) -> bool

Returns true if self has a length of zero bytes.

+
Examples
+
let s = "";
+assert!(s.is_empty());
+
+let s = "not empty";
+assert!(!s.is_empty());
+
1.9.0 · source

pub fn is_char_boundary(&self, index: usize) -> bool

Checks that index-th byte is the first byte in a UTF-8 code point +sequence or the end of the string.

+

The start and end of the string (when index == self.len()) are +considered to be boundaries.

+

Returns false if index is greater than self.len().

+
Examples
+
let s = "Löwe 老虎 Léopard";
+assert!(s.is_char_boundary(0));
+// start of `老`
+assert!(s.is_char_boundary(6));
+assert!(s.is_char_boundary(s.len()));
+
+// second byte of `ö`
+assert!(!s.is_char_boundary(2));
+
+// third byte of `老`
+assert!(!s.is_char_boundary(8));
+
source

pub fn floor_char_boundary(&self, index: usize) -> usize

🔬This is a nightly-only experimental API. (round_char_boundary)

Finds the closest x not exceeding index where is_char_boundary(x) is true.

+

This method can help you truncate a string so that it’s still valid UTF-8, but doesn’t +exceed a given number of bytes. Note that this is done purely at the character level +and can still visually split graphemes, even though the underlying characters aren’t +split. For example, the emoji 🧑‍🔬 (scientist) could be split so that the string only +includes 🧑 (person) instead.

+
Examples
+
#![feature(round_char_boundary)]
+let s = "❤️🧡💛💚💙💜";
+assert_eq!(s.len(), 26);
+assert!(!s.is_char_boundary(13));
+
+let closest = s.floor_char_boundary(13);
+assert_eq!(closest, 10);
+assert_eq!(&s[..closest], "❤️🧡");
+
source

pub fn ceil_char_boundary(&self, index: usize) -> usize

🔬This is a nightly-only experimental API. (round_char_boundary)

Finds the closest x not below index where is_char_boundary(x) is true.

+

This method is the natural complement to floor_char_boundary. See that method +for more details.

+
Panics
+

Panics if index > self.len().

+
Examples
+
#![feature(round_char_boundary)]
+let s = "❤️🧡💛💚💙💜";
+assert_eq!(s.len(), 26);
+assert!(!s.is_char_boundary(13));
+
+let closest = s.ceil_char_boundary(13);
+assert_eq!(closest, 14);
+assert_eq!(&s[..closest], "❤️🧡💛");
+
1.0.0 · source

pub fn as_bytes(&self) -> &[u8]

Converts a string slice to a byte slice. To convert the byte slice back +into a string slice, use the from_utf8 function.

+
Examples
+
let bytes = "bors".as_bytes();
+assert_eq!(b"bors", bytes);
+
1.0.0 · source

pub fn as_ptr(&self) -> *const u8

Converts a string slice to a raw pointer.

+

As string slices are a slice of bytes, the raw pointer points to a +u8. This pointer will be pointing to the first byte of the string +slice.

+

The caller must ensure that the returned pointer is never written to. +If you need to mutate the contents of the string slice, use as_mut_ptr.

+
Examples
+
let s = "Hello";
+let ptr = s.as_ptr();
+
1.20.0 · source

pub fn get<I>(&self, i: I) -> Option<&<I as SliceIndex<str>>::Output>where + I: SliceIndex<str>,

Returns a subslice of str.

+

This is the non-panicking alternative to indexing the str. Returns +None whenever equivalent indexing operation would panic.

+
Examples
+
let v = String::from("🗻∈🌏");
+
+assert_eq!(Some("🗻"), v.get(0..4));
+
+// indices not on UTF-8 sequence boundaries
+assert!(v.get(1..).is_none());
+assert!(v.get(..8).is_none());
+
+// out of bounds
+assert!(v.get(..42).is_none());
+
1.20.0 · source

pub unsafe fn get_unchecked<I>(&self, i: I) -> &<I as SliceIndex<str>>::Outputwhere + I: SliceIndex<str>,

Returns an unchecked subslice of str.

+

This is the unchecked alternative to indexing the str.

+
Safety
+

Callers of this function are responsible that these preconditions are +satisfied:

+
    +
  • The starting index must not exceed the ending index;
  • +
  • Indexes must be within bounds of the original slice;
  • +
  • Indexes must lie on UTF-8 sequence boundaries.
  • +
+

Failing that, the returned string slice may reference invalid memory or +violate the invariants communicated by the str type.

+
Examples
+
let v = "🗻∈🌏";
+unsafe {
+    assert_eq!("🗻", v.get_unchecked(0..4));
+    assert_eq!("∈", v.get_unchecked(4..7));
+    assert_eq!("🌏", v.get_unchecked(7..11));
+}
+
1.0.0 · source

pub unsafe fn slice_unchecked(&self, begin: usize, end: usize) -> &str

👎Deprecated since 1.29.0: use get_unchecked(begin..end) instead

Creates a string slice from another string slice, bypassing safety +checks.

+

This is generally not recommended, use with caution! For a safe +alternative see str and Index.

+

This new slice goes from begin to end, including begin but +excluding end.

+

To get a mutable string slice instead, see the +slice_mut_unchecked method.

+
Safety
+

Callers of this function are responsible that three preconditions are +satisfied:

+
    +
  • begin must not exceed end.
  • +
  • begin and end must be byte positions within the string slice.
  • +
  • begin and end must lie on UTF-8 sequence boundaries.
  • +
+
Examples
+
let s = "Löwe 老虎 Léopard";
+
+unsafe {
+    assert_eq!("Löwe 老虎 Léopard", s.slice_unchecked(0, 21));
+}
+
+let s = "Hello, world!";
+
+unsafe {
+    assert_eq!("world", s.slice_unchecked(7, 12));
+}
+
1.4.0 · source

pub fn split_at(&self, mid: usize) -> (&str, &str)

Divide one string slice into two at an index.

+

The argument, mid, should be a byte offset from the start of the +string. It must also be on the boundary of a UTF-8 code point.

+

The two slices returned go from the start of the string slice to mid, +and from mid to the end of the string slice.

+

To get mutable string slices instead, see the split_at_mut +method.

+
Panics
+

Panics if mid is not on a UTF-8 code point boundary, or if it is +past the end of the last code point of the string slice.

+
Examples
+
let s = "Per Martin-Löf";
+
+let (first, last) = s.split_at(3);
+
+assert_eq!("Per", first);
+assert_eq!(" Martin-Löf", last);
+
1.0.0 · source

pub fn chars(&self) -> Chars<'_>

Returns an iterator over the chars of a string slice.

+

As a string slice consists of valid UTF-8, we can iterate through a +string slice by char. This method returns such an iterator.

+

It’s important to remember that char represents a Unicode Scalar +Value, and might not match your idea of what a ‘character’ is. Iteration +over grapheme clusters may be what you actually want. This functionality +is not provided by Rust’s standard library, check crates.io instead.

+
Examples
+

Basic usage:

+ +
let word = "goodbye";
+
+let count = word.chars().count();
+assert_eq!(7, count);
+
+let mut chars = word.chars();
+
+assert_eq!(Some('g'), chars.next());
+assert_eq!(Some('o'), chars.next());
+assert_eq!(Some('o'), chars.next());
+assert_eq!(Some('d'), chars.next());
+assert_eq!(Some('b'), chars.next());
+assert_eq!(Some('y'), chars.next());
+assert_eq!(Some('e'), chars.next());
+
+assert_eq!(None, chars.next());
+

Remember, chars might not match your intuition about characters:

+ +
let y = "y̆";
+
+let mut chars = y.chars();
+
+assert_eq!(Some('y'), chars.next()); // not 'y̆'
+assert_eq!(Some('\u{0306}'), chars.next());
+
+assert_eq!(None, chars.next());
+
1.0.0 · source

pub fn char_indices(&self) -> CharIndices<'_>

Returns an iterator over the chars of a string slice, and their +positions.

+

As a string slice consists of valid UTF-8, we can iterate through a +string slice by char. This method returns an iterator of both +these chars, as well as their byte positions.

+

The iterator yields tuples. The position is first, the char is +second.

+
Examples
+

Basic usage:

+ +
let word = "goodbye";
+
+let count = word.char_indices().count();
+assert_eq!(7, count);
+
+let mut char_indices = word.char_indices();
+
+assert_eq!(Some((0, 'g')), char_indices.next());
+assert_eq!(Some((1, 'o')), char_indices.next());
+assert_eq!(Some((2, 'o')), char_indices.next());
+assert_eq!(Some((3, 'd')), char_indices.next());
+assert_eq!(Some((4, 'b')), char_indices.next());
+assert_eq!(Some((5, 'y')), char_indices.next());
+assert_eq!(Some((6, 'e')), char_indices.next());
+
+assert_eq!(None, char_indices.next());
+

Remember, chars might not match your intuition about characters:

+ +
let yes = "y̆es";
+
+let mut char_indices = yes.char_indices();
+
+assert_eq!(Some((0, 'y')), char_indices.next()); // not (0, 'y̆')
+assert_eq!(Some((1, '\u{0306}')), char_indices.next());
+
+// note the 3 here - the last character took up two bytes
+assert_eq!(Some((3, 'e')), char_indices.next());
+assert_eq!(Some((4, 's')), char_indices.next());
+
+assert_eq!(None, char_indices.next());
+
1.0.0 · source

pub fn bytes(&self) -> Bytes<'_>

An iterator over the bytes of a string slice.

+

As a string slice consists of a sequence of bytes, we can iterate +through a string slice by byte. This method returns such an iterator.

+
Examples
+
let mut bytes = "bors".bytes();
+
+assert_eq!(Some(b'b'), bytes.next());
+assert_eq!(Some(b'o'), bytes.next());
+assert_eq!(Some(b'r'), bytes.next());
+assert_eq!(Some(b's'), bytes.next());
+
+assert_eq!(None, bytes.next());
+
1.1.0 · source

pub fn split_whitespace(&self) -> SplitWhitespace<'_>

Splits a string slice by whitespace.

+

The iterator returned will return string slices that are sub-slices of +the original string slice, separated by any amount of whitespace.

+

‘Whitespace’ is defined according to the terms of the Unicode Derived +Core Property White_Space. If you only want to split on ASCII whitespace +instead, use split_ascii_whitespace.

+
Examples
+

Basic usage:

+ +
let mut iter = "A few words".split_whitespace();
+
+assert_eq!(Some("A"), iter.next());
+assert_eq!(Some("few"), iter.next());
+assert_eq!(Some("words"), iter.next());
+
+assert_eq!(None, iter.next());
+

All kinds of whitespace are considered:

+ +
let mut iter = " Mary   had\ta\u{2009}little  \n\t lamb".split_whitespace();
+assert_eq!(Some("Mary"), iter.next());
+assert_eq!(Some("had"), iter.next());
+assert_eq!(Some("a"), iter.next());
+assert_eq!(Some("little"), iter.next());
+assert_eq!(Some("lamb"), iter.next());
+
+assert_eq!(None, iter.next());
+

If the string is empty or all whitespace, the iterator yields no string slices:

+ +
assert_eq!("".split_whitespace().next(), None);
+assert_eq!("   ".split_whitespace().next(), None);
+
1.34.0 · source

pub fn split_ascii_whitespace(&self) -> SplitAsciiWhitespace<'_>

Splits a string slice by ASCII whitespace.

+

The iterator returned will return string slices that are sub-slices of +the original string slice, separated by any amount of ASCII whitespace.

+

To split by Unicode Whitespace instead, use split_whitespace.

+
Examples
+

Basic usage:

+ +
let mut iter = "A few words".split_ascii_whitespace();
+
+assert_eq!(Some("A"), iter.next());
+assert_eq!(Some("few"), iter.next());
+assert_eq!(Some("words"), iter.next());
+
+assert_eq!(None, iter.next());
+

All kinds of ASCII whitespace are considered:

+ +
let mut iter = " Mary   had\ta little  \n\t lamb".split_ascii_whitespace();
+assert_eq!(Some("Mary"), iter.next());
+assert_eq!(Some("had"), iter.next());
+assert_eq!(Some("a"), iter.next());
+assert_eq!(Some("little"), iter.next());
+assert_eq!(Some("lamb"), iter.next());
+
+assert_eq!(None, iter.next());
+

If the string is empty or all ASCII whitespace, the iterator yields no string slices:

+ +
assert_eq!("".split_ascii_whitespace().next(), None);
+assert_eq!("   ".split_ascii_whitespace().next(), None);
+
1.0.0 · source

pub fn lines(&self) -> Lines<'_>

An iterator over the lines of a string, as string slices.

+

Lines are split at line endings that are either newlines (\n) or +sequences of a carriage return followed by a line feed (\r\n).

+

Line terminators are not included in the lines returned by the iterator.

+

The final line ending is optional. A string that ends with a final line +ending will return the same lines as an otherwise identical string +without a final line ending.

+
Examples
+

Basic usage:

+ +
let text = "foo\r\nbar\n\nbaz\n";
+let mut lines = text.lines();
+
+assert_eq!(Some("foo"), lines.next());
+assert_eq!(Some("bar"), lines.next());
+assert_eq!(Some(""), lines.next());
+assert_eq!(Some("baz"), lines.next());
+
+assert_eq!(None, lines.next());
+

The final line ending isn’t required:

+ +
let text = "foo\nbar\n\r\nbaz";
+let mut lines = text.lines();
+
+assert_eq!(Some("foo"), lines.next());
+assert_eq!(Some("bar"), lines.next());
+assert_eq!(Some(""), lines.next());
+assert_eq!(Some("baz"), lines.next());
+
+assert_eq!(None, lines.next());
+
1.0.0 · source

pub fn lines_any(&self) -> LinesAny<'_>

👎Deprecated since 1.4.0: use lines() instead now

An iterator over the lines of a string.

+
1.8.0 · source

pub fn encode_utf16(&self) -> EncodeUtf16<'_>

Returns an iterator of u16 over the string encoded as UTF-16.

+
Examples
+
let text = "Zażółć gęślą jaźń";
+
+let utf8_len = text.len();
+let utf16_len = text.encode_utf16().count();
+
+assert!(utf16_len <= utf8_len);
+
1.0.0 · source

pub fn contains<'a, P>(&'a self, pat: P) -> boolwhere + P: Pattern<'a>,

Returns true if the given pattern matches a sub-slice of +this string slice.

+

Returns false if it does not.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Examples
+
let bananas = "bananas";
+
+assert!(bananas.contains("nana"));
+assert!(!bananas.contains("apples"));
+
1.0.0 · source

pub fn starts_with<'a, P>(&'a self, pat: P) -> boolwhere + P: Pattern<'a>,

Returns true if the given pattern matches a prefix of this +string slice.

+

Returns false if it does not.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Examples
+
let bananas = "bananas";
+
+assert!(bananas.starts_with("bana"));
+assert!(!bananas.starts_with("nana"));
+
1.0.0 · source

pub fn ends_with<'a, P>(&'a self, pat: P) -> boolwhere + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,

Returns true if the given pattern matches a suffix of this +string slice.

+

Returns false if it does not.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Examples
+
let bananas = "bananas";
+
+assert!(bananas.ends_with("anas"));
+assert!(!bananas.ends_with("nana"));
+
1.0.0 · source

pub fn find<'a, P>(&'a self, pat: P) -> Option<usize>where + P: Pattern<'a>,

Returns the byte index of the first character of this string slice that +matches the pattern.

+

Returns None if the pattern doesn’t match.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Examples
+

Simple patterns:

+ +
let s = "Löwe 老虎 Léopard Gepardi";
+
+assert_eq!(s.find('L'), Some(0));
+assert_eq!(s.find('é'), Some(14));
+assert_eq!(s.find("pard"), Some(17));
+

More complex patterns using point-free style and closures:

+ +
let s = "Löwe 老虎 Léopard";
+
+assert_eq!(s.find(char::is_whitespace), Some(5));
+assert_eq!(s.find(char::is_lowercase), Some(1));
+assert_eq!(s.find(|c: char| c.is_whitespace() || c.is_lowercase()), Some(1));
+assert_eq!(s.find(|c: char| (c < 'o') && (c > 'a')), Some(4));
+

Not finding the pattern:

+ +
let s = "Löwe 老虎 Léopard";
+let x: &[_] = &['1', '2'];
+
+assert_eq!(s.find(x), None);
+
1.0.0 · source

pub fn rfind<'a, P>(&'a self, pat: P) -> Option<usize>where + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,

Returns the byte index for the first character of the last match of the pattern in +this string slice.

+

Returns None if the pattern doesn’t match.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Examples
+

Simple patterns:

+ +
let s = "Löwe 老虎 Léopard Gepardi";
+
+assert_eq!(s.rfind('L'), Some(13));
+assert_eq!(s.rfind('é'), Some(14));
+assert_eq!(s.rfind("pard"), Some(24));
+

More complex patterns with closures:

+ +
let s = "Löwe 老虎 Léopard";
+
+assert_eq!(s.rfind(char::is_whitespace), Some(12));
+assert_eq!(s.rfind(char::is_lowercase), Some(20));
+

Not finding the pattern:

+ +
let s = "Löwe 老虎 Léopard";
+let x: &[_] = &['1', '2'];
+
+assert_eq!(s.rfind(x), None);
+
1.0.0 · source

pub fn split<'a, P>(&'a self, pat: P) -> Split<'a, P>where + P: Pattern<'a>,

An iterator over substrings of this string slice, separated by +characters matched by a pattern.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Iterator behavior
+

The returned iterator will be a DoubleEndedIterator if the pattern +allows a reverse search and forward/reverse search yields the same +elements. This is true for, e.g., char, but not for &str.

+

If the pattern allows a reverse search but its results might differ +from a forward search, the rsplit method can be used.

+
Examples
+

Simple patterns:

+ +
let v: Vec<&str> = "Mary had a little lamb".split(' ').collect();
+assert_eq!(v, ["Mary", "had", "a", "little", "lamb"]);
+
+let v: Vec<&str> = "".split('X').collect();
+assert_eq!(v, [""]);
+
+let v: Vec<&str> = "lionXXtigerXleopard".split('X').collect();
+assert_eq!(v, ["lion", "", "tiger", "leopard"]);
+
+let v: Vec<&str> = "lion::tiger::leopard".split("::").collect();
+assert_eq!(v, ["lion", "tiger", "leopard"]);
+
+let v: Vec<&str> = "abc1def2ghi".split(char::is_numeric).collect();
+assert_eq!(v, ["abc", "def", "ghi"]);
+
+let v: Vec<&str> = "lionXtigerXleopard".split(char::is_uppercase).collect();
+assert_eq!(v, ["lion", "tiger", "leopard"]);
+

If the pattern is a slice of chars, split on each occurrence of any of the characters:

+ +
let v: Vec<&str> = "2020-11-03 23:59".split(&['-', ' ', ':', '@'][..]).collect();
+assert_eq!(v, ["2020", "11", "03", "23", "59"]);
+

A more complex pattern, using a closure:

+ +
let v: Vec<&str> = "abc1defXghi".split(|c| c == '1' || c == 'X').collect();
+assert_eq!(v, ["abc", "def", "ghi"]);
+

If a string contains multiple contiguous separators, you will end up +with empty strings in the output:

+ +
let x = "||||a||b|c".to_string();
+let d: Vec<_> = x.split('|').collect();
+
+assert_eq!(d, &["", "", "", "", "a", "", "b", "c"]);
+

Contiguous separators are separated by the empty string.

+ +
let x = "(///)".to_string();
+let d: Vec<_> = x.split('/').collect();
+
+assert_eq!(d, &["(", "", "", ")"]);
+

Separators at the start or end of a string are neighbored +by empty strings.

+ +
let d: Vec<_> = "010".split("0").collect();
+assert_eq!(d, &["", "1", ""]);
+

When the empty string is used as a separator, it separates +every character in the string, along with the beginning +and end of the string.

+ +
let f: Vec<_> = "rust".split("").collect();
+assert_eq!(f, &["", "r", "u", "s", "t", ""]);
+

Contiguous separators can lead to possibly surprising behavior +when whitespace is used as the separator. This code is correct:

+ +
let x = "    a  b c".to_string();
+let d: Vec<_> = x.split(' ').collect();
+
+assert_eq!(d, &["", "", "", "", "a", "", "b", "c"]);
+

It does not give you:

+ +
assert_eq!(d, &["a", "b", "c"]);
+

Use split_whitespace for this behavior.

+
1.51.0 · source

pub fn split_inclusive<'a, P>(&'a self, pat: P) -> SplitInclusive<'a, P>where + P: Pattern<'a>,

An iterator over substrings of this string slice, separated by +characters matched by a pattern. Differs from the iterator produced by +split in that split_inclusive leaves the matched part as the +terminator of the substring.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Examples
+
let v: Vec<&str> = "Mary had a little lamb\nlittle lamb\nlittle lamb."
+    .split_inclusive('\n').collect();
+assert_eq!(v, ["Mary had a little lamb\n", "little lamb\n", "little lamb."]);
+

If the last element of the string is matched, +that element will be considered the terminator of the preceding substring. +That substring will be the last item returned by the iterator.

+ +
let v: Vec<&str> = "Mary had a little lamb\nlittle lamb\nlittle lamb.\n"
+    .split_inclusive('\n').collect();
+assert_eq!(v, ["Mary had a little lamb\n", "little lamb\n", "little lamb.\n"]);
+
1.0.0 · source

pub fn rsplit<'a, P>(&'a self, pat: P) -> RSplit<'a, P>where + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,

An iterator over substrings of the given string slice, separated by +characters matched by a pattern and yielded in reverse order.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Iterator behavior
+

The returned iterator requires that the pattern supports a reverse +search, and it will be a DoubleEndedIterator if a forward/reverse +search yields the same elements.

+

For iterating from the front, the split method can be used.

+
Examples
+

Simple patterns:

+ +
let v: Vec<&str> = "Mary had a little lamb".rsplit(' ').collect();
+assert_eq!(v, ["lamb", "little", "a", "had", "Mary"]);
+
+let v: Vec<&str> = "".rsplit('X').collect();
+assert_eq!(v, [""]);
+
+let v: Vec<&str> = "lionXXtigerXleopard".rsplit('X').collect();
+assert_eq!(v, ["leopard", "tiger", "", "lion"]);
+
+let v: Vec<&str> = "lion::tiger::leopard".rsplit("::").collect();
+assert_eq!(v, ["leopard", "tiger", "lion"]);
+

A more complex pattern, using a closure:

+ +
let v: Vec<&str> = "abc1defXghi".rsplit(|c| c == '1' || c == 'X').collect();
+assert_eq!(v, ["ghi", "def", "abc"]);
+
1.0.0 · source

pub fn split_terminator<'a, P>(&'a self, pat: P) -> SplitTerminator<'a, P>where + P: Pattern<'a>,

An iterator over substrings of the given string slice, separated by +characters matched by a pattern.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+

Equivalent to split, except that the trailing substring +is skipped if empty.

+

This method can be used for string data that is terminated, +rather than separated by a pattern.

+
Iterator behavior
+

The returned iterator will be a DoubleEndedIterator if the pattern +allows a reverse search and forward/reverse search yields the same +elements. This is true for, e.g., char, but not for &str.

+

If the pattern allows a reverse search but its results might differ +from a forward search, the rsplit_terminator method can be used.

+
Examples
+
let v: Vec<&str> = "A.B.".split_terminator('.').collect();
+assert_eq!(v, ["A", "B"]);
+
+let v: Vec<&str> = "A..B..".split_terminator(".").collect();
+assert_eq!(v, ["A", "", "B", ""]);
+
+let v: Vec<&str> = "A.B:C.D".split_terminator(&['.', ':'][..]).collect();
+assert_eq!(v, ["A", "B", "C", "D"]);
+
1.0.0 · source

pub fn rsplit_terminator<'a, P>(&'a self, pat: P) -> RSplitTerminator<'a, P>where + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,

An iterator over substrings of self, separated by characters +matched by a pattern and yielded in reverse order.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+

Equivalent to split, except that the trailing substring is +skipped if empty.

+

This method can be used for string data that is terminated, +rather than separated by a pattern.

+
Iterator behavior
+

The returned iterator requires that the pattern supports a +reverse search, and it will be double ended if a forward/reverse +search yields the same elements.

+

For iterating from the front, the split_terminator method can be +used.

+
Examples
+
let v: Vec<&str> = "A.B.".rsplit_terminator('.').collect();
+assert_eq!(v, ["B", "A"]);
+
+let v: Vec<&str> = "A..B..".rsplit_terminator(".").collect();
+assert_eq!(v, ["", "B", "", "A"]);
+
+let v: Vec<&str> = "A.B:C.D".rsplit_terminator(&['.', ':'][..]).collect();
+assert_eq!(v, ["D", "C", "B", "A"]);
+
1.0.0 · source

pub fn splitn<'a, P>(&'a self, n: usize, pat: P) -> SplitN<'a, P>where + P: Pattern<'a>,

An iterator over substrings of the given string slice, separated by a +pattern, restricted to returning at most n items.

+

If n substrings are returned, the last substring (the nth substring) +will contain the remainder of the string.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Iterator behavior
+

The returned iterator will not be double ended, because it is +not efficient to support.

+

If the pattern allows a reverse search, the rsplitn method can be +used.

+
Examples
+

Simple patterns:

+ +
let v: Vec<&str> = "Mary had a little lambda".splitn(3, ' ').collect();
+assert_eq!(v, ["Mary", "had", "a little lambda"]);
+
+let v: Vec<&str> = "lionXXtigerXleopard".splitn(3, "X").collect();
+assert_eq!(v, ["lion", "", "tigerXleopard"]);
+
+let v: Vec<&str> = "abcXdef".splitn(1, 'X').collect();
+assert_eq!(v, ["abcXdef"]);
+
+let v: Vec<&str> = "".splitn(1, 'X').collect();
+assert_eq!(v, [""]);
+

A more complex pattern, using a closure:

+ +
let v: Vec<&str> = "abc1defXghi".splitn(2, |c| c == '1' || c == 'X').collect();
+assert_eq!(v, ["abc", "defXghi"]);
+
1.0.0 · source

pub fn rsplitn<'a, P>(&'a self, n: usize, pat: P) -> RSplitN<'a, P>where + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,

An iterator over substrings of this string slice, separated by a +pattern, starting from the end of the string, restricted to returning +at most n items.

+

If n substrings are returned, the last substring (the nth substring) +will contain the remainder of the string.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Iterator behavior
+

The returned iterator will not be double ended, because it is not +efficient to support.

+

For splitting from the front, the splitn method can be used.

+
Examples
+

Simple patterns:

+ +
let v: Vec<&str> = "Mary had a little lamb".rsplitn(3, ' ').collect();
+assert_eq!(v, ["lamb", "little", "Mary had a"]);
+
+let v: Vec<&str> = "lionXXtigerXleopard".rsplitn(3, 'X').collect();
+assert_eq!(v, ["leopard", "tiger", "lionX"]);
+
+let v: Vec<&str> = "lion::tiger::leopard".rsplitn(2, "::").collect();
+assert_eq!(v, ["leopard", "lion::tiger"]);
+

A more complex pattern, using a closure:

+ +
let v: Vec<&str> = "abc1defXghi".rsplitn(2, |c| c == '1' || c == 'X').collect();
+assert_eq!(v, ["ghi", "abc1def"]);
+
1.52.0 · source

pub fn split_once<'a, P>(&'a self, delimiter: P) -> Option<(&'a str, &'a str)>where + P: Pattern<'a>,

Splits the string on the first occurrence of the specified delimiter and +returns prefix before delimiter and suffix after delimiter.

+
Examples
+
assert_eq!("cfg".split_once('='), None);
+assert_eq!("cfg=".split_once('='), Some(("cfg", "")));
+assert_eq!("cfg=foo".split_once('='), Some(("cfg", "foo")));
+assert_eq!("cfg=foo=bar".split_once('='), Some(("cfg", "foo=bar")));
+
1.52.0 · source

pub fn rsplit_once<'a, P>(&'a self, delimiter: P) -> Option<(&'a str, &'a str)>where + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,

Splits the string on the last occurrence of the specified delimiter and +returns prefix before delimiter and suffix after delimiter.

+
Examples
+
assert_eq!("cfg".rsplit_once('='), None);
+assert_eq!("cfg=foo".rsplit_once('='), Some(("cfg", "foo")));
+assert_eq!("cfg=foo=bar".rsplit_once('='), Some(("cfg=foo", "bar")));
+
1.2.0 · source

pub fn matches<'a, P>(&'a self, pat: P) -> Matches<'a, P>where + P: Pattern<'a>,

An iterator over the disjoint matches of a pattern within the given string +slice.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Iterator behavior
+

The returned iterator will be a DoubleEndedIterator if the pattern +allows a reverse search and forward/reverse search yields the same +elements. This is true for, e.g., char, but not for &str.

+

If the pattern allows a reverse search but its results might differ +from a forward search, the rmatches method can be used.

+
Examples
+
let v: Vec<&str> = "abcXXXabcYYYabc".matches("abc").collect();
+assert_eq!(v, ["abc", "abc", "abc"]);
+
+let v: Vec<&str> = "1abc2abc3".matches(char::is_numeric).collect();
+assert_eq!(v, ["1", "2", "3"]);
+
1.2.0 · source

pub fn rmatches<'a, P>(&'a self, pat: P) -> RMatches<'a, P>where + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,

An iterator over the disjoint matches of a pattern within this string slice, +yielded in reverse order.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Iterator behavior
+

The returned iterator requires that the pattern supports a reverse +search, and it will be a DoubleEndedIterator if a forward/reverse +search yields the same elements.

+

For iterating from the front, the matches method can be used.

+
Examples
+
let v: Vec<&str> = "abcXXXabcYYYabc".rmatches("abc").collect();
+assert_eq!(v, ["abc", "abc", "abc"]);
+
+let v: Vec<&str> = "1abc2abc3".rmatches(char::is_numeric).collect();
+assert_eq!(v, ["3", "2", "1"]);
+
1.5.0 · source

pub fn match_indices<'a, P>(&'a self, pat: P) -> MatchIndices<'a, P>where + P: Pattern<'a>,

An iterator over the disjoint matches of a pattern within this string +slice as well as the index that the match starts at.

+

For matches of pat within self that overlap, only the indices +corresponding to the first match are returned.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Iterator behavior
+

The returned iterator will be a DoubleEndedIterator if the pattern +allows a reverse search and forward/reverse search yields the same +elements. This is true for, e.g., char, but not for &str.

+

If the pattern allows a reverse search but its results might differ +from a forward search, the rmatch_indices method can be used.

+
Examples
+
let v: Vec<_> = "abcXXXabcYYYabc".match_indices("abc").collect();
+assert_eq!(v, [(0, "abc"), (6, "abc"), (12, "abc")]);
+
+let v: Vec<_> = "1abcabc2".match_indices("abc").collect();
+assert_eq!(v, [(1, "abc"), (4, "abc")]);
+
+let v: Vec<_> = "ababa".match_indices("aba").collect();
+assert_eq!(v, [(0, "aba")]); // only the first `aba`
+
1.5.0 · source

pub fn rmatch_indices<'a, P>(&'a self, pat: P) -> RMatchIndices<'a, P>where + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,

An iterator over the disjoint matches of a pattern within self, +yielded in reverse order along with the index of the match.

+

For matches of pat within self that overlap, only the indices +corresponding to the last match are returned.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Iterator behavior
+

The returned iterator requires that the pattern supports a reverse +search, and it will be a DoubleEndedIterator if a forward/reverse +search yields the same elements.

+

For iterating from the front, the match_indices method can be used.

+
Examples
+
let v: Vec<_> = "abcXXXabcYYYabc".rmatch_indices("abc").collect();
+assert_eq!(v, [(12, "abc"), (6, "abc"), (0, "abc")]);
+
+let v: Vec<_> = "1abcabc2".rmatch_indices("abc").collect();
+assert_eq!(v, [(4, "abc"), (1, "abc")]);
+
+let v: Vec<_> = "ababa".rmatch_indices("aba").collect();
+assert_eq!(v, [(2, "aba")]); // only the last `aba`
+
1.0.0 · source

pub fn trim(&self) -> &str

Returns a string slice with leading and trailing whitespace removed.

+

‘Whitespace’ is defined according to the terms of the Unicode Derived +Core Property White_Space, which includes newlines.

+
Examples
+
let s = "\n Hello\tworld\t\n";
+
+assert_eq!("Hello\tworld", s.trim());
+
1.30.0 · source

pub fn trim_start(&self) -> &str

Returns a string slice with leading whitespace removed.

+

‘Whitespace’ is defined according to the terms of the Unicode Derived +Core Property White_Space, which includes newlines.

+
Text directionality
+

A string is a sequence of bytes. start in this context means the first +position of that byte string; for a left-to-right language like English or +Russian, this will be left side, and for right-to-left languages like +Arabic or Hebrew, this will be the right side.

+
Examples
+

Basic usage:

+ +
let s = "\n Hello\tworld\t\n";
+assert_eq!("Hello\tworld\t\n", s.trim_start());
+

Directionality:

+ +
let s = "  English  ";
+assert!(Some('E') == s.trim_start().chars().next());
+
+let s = "  עברית  ";
+assert!(Some('ע') == s.trim_start().chars().next());
+
1.30.0 · source

pub fn trim_end(&self) -> &str

Returns a string slice with trailing whitespace removed.

+

‘Whitespace’ is defined according to the terms of the Unicode Derived +Core Property White_Space, which includes newlines.

+
Text directionality
+

A string is a sequence of bytes. end in this context means the last +position of that byte string; for a left-to-right language like English or +Russian, this will be right side, and for right-to-left languages like +Arabic or Hebrew, this will be the left side.

+
Examples
+

Basic usage:

+ +
let s = "\n Hello\tworld\t\n";
+assert_eq!("\n Hello\tworld", s.trim_end());
+

Directionality:

+ +
let s = "  English  ";
+assert!(Some('h') == s.trim_end().chars().rev().next());
+
+let s = "  עברית  ";
+assert!(Some('ת') == s.trim_end().chars().rev().next());
+
1.0.0 · source

pub fn trim_left(&self) -> &str

👎Deprecated since 1.33.0: superseded by trim_start

Returns a string slice with leading whitespace removed.

+

‘Whitespace’ is defined according to the terms of the Unicode Derived +Core Property White_Space.

+
Text directionality
+

A string is a sequence of bytes. ‘Left’ in this context means the first +position of that byte string; for a language like Arabic or Hebrew +which are ‘right to left’ rather than ‘left to right’, this will be +the right side, not the left.

+
Examples
+

Basic usage:

+ +
let s = " Hello\tworld\t";
+
+assert_eq!("Hello\tworld\t", s.trim_left());
+

Directionality:

+ +
let s = "  English";
+assert!(Some('E') == s.trim_left().chars().next());
+
+let s = "  עברית";
+assert!(Some('ע') == s.trim_left().chars().next());
+
1.0.0 · source

pub fn trim_right(&self) -> &str

👎Deprecated since 1.33.0: superseded by trim_end

Returns a string slice with trailing whitespace removed.

+

‘Whitespace’ is defined according to the terms of the Unicode Derived +Core Property White_Space.

+
Text directionality
+

A string is a sequence of bytes. ‘Right’ in this context means the last +position of that byte string; for a language like Arabic or Hebrew +which are ‘right to left’ rather than ‘left to right’, this will be +the left side, not the right.

+
Examples
+

Basic usage:

+ +
let s = " Hello\tworld\t";
+
+assert_eq!(" Hello\tworld", s.trim_right());
+

Directionality:

+ +
let s = "English  ";
+assert!(Some('h') == s.trim_right().chars().rev().next());
+
+let s = "עברית  ";
+assert!(Some('ת') == s.trim_right().chars().rev().next());
+
1.0.0 · source

pub fn trim_matches<'a, P>(&'a self, pat: P) -> &'a strwhere + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: DoubleEndedSearcher<'a>,

Returns a string slice with all prefixes and suffixes that match a +pattern repeatedly removed.

+

The pattern can be a char, a slice of chars, or a function +or closure that determines if a character matches.

+
Examples
+

Simple patterns:

+ +
assert_eq!("11foo1bar11".trim_matches('1'), "foo1bar");
+assert_eq!("123foo1bar123".trim_matches(char::is_numeric), "foo1bar");
+
+let x: &[_] = &['1', '2'];
+assert_eq!("12foo1bar12".trim_matches(x), "foo1bar");
+

A more complex pattern, using a closure:

+ +
assert_eq!("1foo1barXX".trim_matches(|c| c == '1' || c == 'X'), "foo1bar");
+
1.30.0 · source

pub fn trim_start_matches<'a, P>(&'a self, pat: P) -> &'a strwhere + P: Pattern<'a>,

Returns a string slice with all prefixes that match a pattern +repeatedly removed.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Text directionality
+

A string is a sequence of bytes. start in this context means the first +position of that byte string; for a left-to-right language like English or +Russian, this will be left side, and for right-to-left languages like +Arabic or Hebrew, this will be the right side.

+
Examples
+
assert_eq!("11foo1bar11".trim_start_matches('1'), "foo1bar11");
+assert_eq!("123foo1bar123".trim_start_matches(char::is_numeric), "foo1bar123");
+
+let x: &[_] = &['1', '2'];
+assert_eq!("12foo1bar12".trim_start_matches(x), "foo1bar12");
+
1.45.0 · source

pub fn strip_prefix<'a, P>(&'a self, prefix: P) -> Option<&'a str>where + P: Pattern<'a>,

Returns a string slice with the prefix removed.

+

If the string starts with the pattern prefix, returns substring after the prefix, wrapped +in Some. Unlike trim_start_matches, this method removes the prefix exactly once.

+

If the string does not start with prefix, returns None.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Examples
+
assert_eq!("foo:bar".strip_prefix("foo:"), Some("bar"));
+assert_eq!("foo:bar".strip_prefix("bar"), None);
+assert_eq!("foofoo".strip_prefix("foo"), Some("foo"));
+
1.45.0 · source

pub fn strip_suffix<'a, P>(&'a self, suffix: P) -> Option<&'a str>where + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,

Returns a string slice with the suffix removed.

+

If the string ends with the pattern suffix, returns the substring before the suffix, +wrapped in Some. Unlike trim_end_matches, this method removes the suffix exactly once.

+

If the string does not end with suffix, returns None.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Examples
+
assert_eq!("bar:foo".strip_suffix(":foo"), Some("bar"));
+assert_eq!("bar:foo".strip_suffix("bar"), None);
+assert_eq!("foofoo".strip_suffix("foo"), Some("foo"));
+
1.30.0 · source

pub fn trim_end_matches<'a, P>(&'a self, pat: P) -> &'a strwhere + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,

Returns a string slice with all suffixes that match a pattern +repeatedly removed.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Text directionality
+

A string is a sequence of bytes. end in this context means the last +position of that byte string; for a left-to-right language like English or +Russian, this will be right side, and for right-to-left languages like +Arabic or Hebrew, this will be the left side.

+
Examples
+

Simple patterns:

+ +
assert_eq!("11foo1bar11".trim_end_matches('1'), "11foo1bar");
+assert_eq!("123foo1bar123".trim_end_matches(char::is_numeric), "123foo1bar");
+
+let x: &[_] = &['1', '2'];
+assert_eq!("12foo1bar12".trim_end_matches(x), "12foo1bar");
+

A more complex pattern, using a closure:

+ +
assert_eq!("1fooX".trim_end_matches(|c| c == '1' || c == 'X'), "1foo");
+
1.0.0 · source

pub fn trim_left_matches<'a, P>(&'a self, pat: P) -> &'a strwhere + P: Pattern<'a>,

👎Deprecated since 1.33.0: superseded by trim_start_matches

Returns a string slice with all prefixes that match a pattern +repeatedly removed.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Text directionality
+

A string is a sequence of bytes. ‘Left’ in this context means the first +position of that byte string; for a language like Arabic or Hebrew +which are ‘right to left’ rather than ‘left to right’, this will be +the right side, not the left.

+
Examples
+
assert_eq!("11foo1bar11".trim_left_matches('1'), "foo1bar11");
+assert_eq!("123foo1bar123".trim_left_matches(char::is_numeric), "foo1bar123");
+
+let x: &[_] = &['1', '2'];
+assert_eq!("12foo1bar12".trim_left_matches(x), "foo1bar12");
+
1.0.0 · source

pub fn trim_right_matches<'a, P>(&'a self, pat: P) -> &'a strwhere + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,

👎Deprecated since 1.33.0: superseded by trim_end_matches

Returns a string slice with all suffixes that match a pattern +repeatedly removed.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Text directionality
+

A string is a sequence of bytes. ‘Right’ in this context means the last +position of that byte string; for a language like Arabic or Hebrew +which are ‘right to left’ rather than ‘left to right’, this will be +the left side, not the right.

+
Examples
+

Simple patterns:

+ +
assert_eq!("11foo1bar11".trim_right_matches('1'), "11foo1bar");
+assert_eq!("123foo1bar123".trim_right_matches(char::is_numeric), "123foo1bar");
+
+let x: &[_] = &['1', '2'];
+assert_eq!("12foo1bar12".trim_right_matches(x), "12foo1bar");
+

A more complex pattern, using a closure:

+ +
assert_eq!("1fooX".trim_right_matches(|c| c == '1' || c == 'X'), "1foo");
+
1.0.0 · source

pub fn parse<F>(&self) -> Result<F, <F as FromStr>::Err>where + F: FromStr,

Parses this string slice into another type.

+

Because parse is so general, it can cause problems with type +inference. As such, parse is one of the few times you’ll see +the syntax affectionately known as the ‘turbofish’: ::<>. This +helps the inference algorithm understand specifically which type +you’re trying to parse into.

+

parse can parse into any type that implements the FromStr trait.

+
Errors
+

Will return Err if it’s not possible to parse this string slice into +the desired type.

+
Examples
+

Basic usage

+ +
let four: u32 = "4".parse().unwrap();
+
+assert_eq!(4, four);
+

Using the ‘turbofish’ instead of annotating four:

+ +
let four = "4".parse::<u32>();
+
+assert_eq!(Ok(4), four);
+

Failing to parse:

+ +
let nope = "j".parse::<u32>();
+
+assert!(nope.is_err());
+
1.23.0 · source

pub fn is_ascii(&self) -> bool

Checks if all characters in this string are within the ASCII range.

+
Examples
+
let ascii = "hello!\n";
+let non_ascii = "Grüße, Jürgen ❤";
+
+assert!(ascii.is_ascii());
+assert!(!non_ascii.is_ascii());
+
source

pub fn as_ascii(&self) -> Option<&[AsciiChar]>

🔬This is a nightly-only experimental API. (ascii_char)

If this string slice is_ascii, returns it as a slice +of ASCII characters, otherwise returns None.

+
1.23.0 · source

pub fn eq_ignore_ascii_case(&self, other: &str) -> bool

Checks that two strings are an ASCII case-insensitive match.

+

Same as to_ascii_lowercase(a) == to_ascii_lowercase(b), +but without allocating and copying temporaries.

+
Examples
+
assert!("Ferris".eq_ignore_ascii_case("FERRIS"));
+assert!("Ferrös".eq_ignore_ascii_case("FERRöS"));
+assert!(!"Ferrös".eq_ignore_ascii_case("FERRÖS"));
+
1.34.0 · source

pub fn escape_debug(&self) -> EscapeDebug<'_>

Return an iterator that escapes each char in self with char::escape_debug.

+

Note: only extended grapheme codepoints that begin the string will be +escaped.

+
Examples
+

As an iterator:

+ +
for c in "❤\n!".escape_debug() {
+    print!("{c}");
+}
+println!();
+

Using println! directly:

+ +
println!("{}", "❤\n!".escape_debug());
+

Both are equivalent to:

+ +
println!("❤\\n!");
+

Using to_string:

+ +
assert_eq!("❤\n!".escape_debug().to_string(), "❤\\n!");
+
1.34.0 · source

pub fn escape_default(&self) -> EscapeDefault<'_>

Return an iterator that escapes each char in self with char::escape_default.

+
Examples
+

As an iterator:

+ +
for c in "❤\n!".escape_default() {
+    print!("{c}");
+}
+println!();
+

Using println! directly:

+ +
println!("{}", "❤\n!".escape_default());
+

Both are equivalent to:

+ +
println!("\\u{{2764}}\\n!");
+

Using to_string:

+ +
assert_eq!("❤\n!".escape_default().to_string(), "\\u{2764}\\n!");
+
1.34.0 · source

pub fn escape_unicode(&self) -> EscapeUnicode<'_>

Return an iterator that escapes each char in self with char::escape_unicode.

+
Examples
+

As an iterator:

+ +
for c in "❤\n!".escape_unicode() {
+    print!("{c}");
+}
+println!();
+

Using println! directly:

+ +
println!("{}", "❤\n!".escape_unicode());
+

Both are equivalent to:

+ +
println!("\\u{{2764}}\\u{{a}}\\u{{21}}");
+

Using to_string:

+ +
assert_eq!("❤\n!".escape_unicode().to_string(), "\\u{2764}\\u{a}\\u{21}");
+
1.0.0 · source

pub fn replace<'a, P>(&'a self, from: P, to: &str) -> Stringwhere + P: Pattern<'a>,

Replaces all matches of a pattern with another string.

+

replace creates a new String, and copies the data from this string slice into it. +While doing so, it attempts to find matches of a pattern. If it finds any, it +replaces them with the replacement string slice.

+
Examples
+

Basic usage:

+ +
let s = "this is old";
+
+assert_eq!("this is new", s.replace("old", "new"));
+assert_eq!("than an old", s.replace("is", "an"));
+

When the pattern doesn’t match, it returns this string slice as String:

+ +
let s = "this is old";
+assert_eq!(s, s.replace("cookie monster", "little lamb"));
+
1.16.0 · source

pub fn replacen<'a, P>(&'a self, pat: P, to: &str, count: usize) -> Stringwhere + P: Pattern<'a>,

Replaces first N matches of a pattern with another string.

+

replacen creates a new String, and copies the data from this string slice into it. +While doing so, it attempts to find matches of a pattern. If it finds any, it +replaces them with the replacement string slice at most count times.

+
Examples
+

Basic usage:

+ +
let s = "foo foo 123 foo";
+assert_eq!("new new 123 foo", s.replacen("foo", "new", 2));
+assert_eq!("faa fao 123 foo", s.replacen('o', "a", 3));
+assert_eq!("foo foo new23 foo", s.replacen(char::is_numeric, "new", 1));
+

When the pattern doesn’t match, it returns this string slice as String:

+ +
let s = "this is old";
+assert_eq!(s, s.replacen("cookie monster", "little lamb", 10));
+
1.2.0 · source

pub fn to_lowercase(&self) -> String

Returns the lowercase equivalent of this string slice, as a new String.

+

‘Lowercase’ is defined according to the terms of the Unicode Derived Core Property +Lowercase.

+

Since some characters can expand into multiple characters when changing +the case, this function returns a String instead of modifying the +parameter in-place.

+
Examples
+

Basic usage:

+ +
let s = "HELLO";
+
+assert_eq!("hello", s.to_lowercase());
+

A tricky example, with sigma:

+ +
let sigma = "Σ";
+
+assert_eq!("σ", sigma.to_lowercase());
+
+// but at the end of a word, it's ς, not σ:
+let odysseus = "ὈΔΥΣΣΕΎΣ";
+
+assert_eq!("ὀδυσσεύς", odysseus.to_lowercase());
+

Languages without case are not changed:

+ +
let new_year = "农历新年";
+
+assert_eq!(new_year, new_year.to_lowercase());
+
1.2.0 · source

pub fn to_uppercase(&self) -> String

Returns the uppercase equivalent of this string slice, as a new String.

+

‘Uppercase’ is defined according to the terms of the Unicode Derived Core Property +Uppercase.

+

Since some characters can expand into multiple characters when changing +the case, this function returns a String instead of modifying the +parameter in-place.

+
Examples
+

Basic usage:

+ +
let s = "hello";
+
+assert_eq!("HELLO", s.to_uppercase());
+

Scripts without case are not changed:

+ +
let new_year = "农历新年";
+
+assert_eq!(new_year, new_year.to_uppercase());
+

One character can become multiple:

+ +
let s = "tschüß";
+
+assert_eq!("TSCHÜSS", s.to_uppercase());
+
1.16.0 · source

pub fn repeat(&self, n: usize) -> String

Creates a new String by repeating a string n times.

+
Panics
+

This function will panic if the capacity would overflow.

+
Examples
+

Basic usage:

+ +
assert_eq!("abc".repeat(4), String::from("abcabcabcabc"));
+

A panic upon overflow:

+ +
// this will panic at runtime
+let huge = "0123456789abcdef".repeat(usize::MAX);
+
1.23.0 · source

pub fn to_ascii_uppercase(&self) -> String

Returns a copy of this string where each character is mapped to its +ASCII upper case equivalent.

+

ASCII letters ‘a’ to ‘z’ are mapped to ‘A’ to ‘Z’, +but non-ASCII letters are unchanged.

+

To uppercase the value in-place, use make_ascii_uppercase.

+

To uppercase ASCII characters in addition to non-ASCII characters, use +to_uppercase.

+
Examples
+
let s = "Grüße, Jürgen ❤";
+
+assert_eq!("GRüßE, JüRGEN ❤", s.to_ascii_uppercase());
+
1.23.0 · source

pub fn to_ascii_lowercase(&self) -> String

Returns a copy of this string where each character is mapped to its +ASCII lower case equivalent.

+

ASCII letters ‘A’ to ‘Z’ are mapped to ‘a’ to ‘z’, +but non-ASCII letters are unchanged.

+

To lowercase the value in-place, use make_ascii_lowercase.

+

To lowercase ASCII characters in addition to non-ASCII characters, use +to_lowercase.

+
Examples
+
let s = "Grüße, Jürgen ❤";
+
+assert_eq!("grüße, jürgen ❤", s.to_ascii_lowercase());
+

Trait Implementations§

§

impl AsRef<[u8]> for Guid

§

fn as_ref(&self) -> &[u8]

Converts this type into a shared reference of the (usually inferred) input type.
§

impl AsRef<str> for Guid

§

fn as_ref(&self) -> &str

Converts this type into a shared reference of the (usually inferred) input type.
§

impl Clone for Guid

§

fn clone(&self) -> Guid

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
§

impl Debug for Guid

§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
§

impl Default for Guid

§

fn default() -> Guid

Create a default guid by calling Guid::empty()

+
§

impl Deref for Guid

§

type Target = str

The resulting type after dereferencing.
§

fn deref(&self) -> &str

Dereferences the value.
§

impl<'de> Deserialize<'de> for Guid

§

fn deserialize<D>( + deserializer: D +) -> Result<Guid, <D as Deserializer<'de>>::Error>where + D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
§

impl Display for Guid

§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
§

impl<'a> From<&'a &str> for Guid

§

fn from(s: &'a &str) -> Guid

Converts to this type from the input type.
§

impl<'a> From<&'a [u8]> for Guid

§

fn from(s: &'a [u8]) -> Guid

Converts to this type from the input type.
§

impl<'a> From<&'a str> for Guid

§

fn from(s: &'a str) -> Guid

Converts to this type from the input type.
source§

impl From<Guid> for OutgoingEnvelope

Allow an outgoing envelope to be constructed with just a guid when default +values for the other fields are OK.

+
source§

fn from(id: Guid) -> Self

Converts to this type from the input type.
§

impl From<Guid> for Vec<u8, Global>

§

fn from(guid: Guid) -> Vec<u8, Global>

Converts to this type from the input type.
§

impl From<String> for Guid

§

fn from(s: String) -> Guid

Converts to this type from the input type.
§

impl From<Vec<u8, Global>> for Guid

§

fn from(v: Vec<u8, Global>) -> Guid

Converts to this type from the input type.
§

impl FromSql for Guid

§

fn column_result(value: ValueRef<'_>) -> Result<Guid, FromSqlError>

Converts SQLite value into Rust value.
§

impl Hash for Guid

§

fn hash<H>(&self, state: &mut H)where + H: Hasher,

Feeds this value into the given Hasher. Read more
1.3.0 · source§

fn hash_slice<H>(data: &[Self], state: &mut H)where + H: Hasher, + Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
§

impl Ord for Guid

§

fn cmp(&self, other: &Guid) -> Ordering

This method returns an Ordering between self and other. Read more
1.21.0 · source§

fn max(self, other: Self) -> Selfwhere + Self: Sized,

Compares and returns the maximum of two values. Read more
1.21.0 · source§

fn min(self, other: Self) -> Selfwhere + Self: Sized,

Compares and returns the minimum of two values. Read more
1.50.0 · source§

fn clamp(self, min: Self, max: Self) -> Selfwhere + Self: Sized + PartialOrd<Self>,

Restrict a value to a certain interval. Read more
§

impl<'a> PartialEq<&'a [u8]> for Guid

§

fn eq(&self, other: &&'a [u8]) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
§

impl<'a> PartialEq<&'a str> for Guid

§

fn eq(&self, other: &&'a str) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
§

impl<'a> PartialEq<[u8]> for Guid

§

fn eq(&self, other: &[u8]) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
§

impl<'a> PartialEq<Guid> for &'a [u8]

§

fn eq(&self, other: &Guid) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
§

impl<'a> PartialEq<Guid> for &'a str

§

fn eq(&self, other: &Guid) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
§

impl<'a> PartialEq<Guid> for [u8]

§

fn eq(&self, other: &Guid) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
§

impl PartialEq<Guid> for Guid

§

fn eq(&self, other: &Guid) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
§

impl<'a> PartialEq<Guid> for Vec<u8, Global>

§

fn eq(&self, other: &Guid) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
§

impl<'a> PartialEq<Guid> for str

§

fn eq(&self, other: &Guid) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
§

impl<'a> PartialEq<String> for Guid

§

fn eq(&self, other: &String) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
§

impl<'a> PartialEq<Vec<u8, Global>> for Guid

§

fn eq(&self, other: &Vec<u8, Global>) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
§

impl<'a> PartialEq<str> for Guid

§

fn eq(&self, other: &str) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
§

impl PartialOrd<Guid> for Guid

§

fn partial_cmp(&self, other: &Guid) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · source§

fn lt(&self, other: &Rhs) -> bool

This method tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · source§

fn le(&self, other: &Rhs) -> bool

This method tests less than or equal to (for self and other) and is used by the <= +operator. Read more
1.0.0 · source§

fn gt(&self, other: &Rhs) -> bool

This method tests greater than (for self and other) and is used by the > operator. Read more
1.0.0 · source§

fn ge(&self, other: &Rhs) -> bool

This method tests greater than or equal to (for self and other) and is used by the >= +operator. Read more
§

impl Serialize for Guid

§

fn serialize<S>( + &self, + serializer: S +) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>where + S: Serializer,

Serialize this value into the given Serde serializer. Read more
§

impl ToSql for Guid

§

fn to_sql(&self) -> Result<ToSqlOutput<'_>, Error>

Converts Rust value to SQLite value
§

impl Eq for Guid

Auto Trait Implementations§

§

impl RefUnwindSafe for Guid

§

impl Send for Guid

§

impl Sync for Guid

§

impl Unpin for Guid

§

impl UnwindSafe for Guid

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> CallHasher for Twhere + T: Hash + ?Sized,

§

fn get_hash<H, B>(value: &H, build_hasher: &B) -> u64where + H: Hash + ?Sized, + B: BuildHasher,

source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
§

impl<I> IntoResettable<String> for Iwhere + I: Into<String>,

§

fn into_resettable(self) -> Resettable<String>

Convert to the intended resettable type
§

impl<Ctx, T> MeasureWith<Ctx> for Twhere + T: AsRef<[u8]>,

§

fn measure_with(&self, _ctx: &Ctx) -> usize

How large is Self, given the ctx?
source§

impl<T> ToHex for Twhere + T: AsRef<[u8]>,

source§

fn encode_hex<U>(&self) -> Uwhere + U: FromIterator<char>,

Encode the hex strict representing self into the result. Lower case +letters are used (e.g. f9b4ca)
source§

fn encode_hex_upper<U>(&self) -> Uwhere + U: FromIterator<char>,

Encode the hex strict representing self into the result. Upper case +letters are used (e.g. F9B4CA)
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/sync15/struct.KeyBundle.html b/book/rust-docs/sync15/struct.KeyBundle.html new file mode 100644 index 0000000000..22b5c8f753 --- /dev/null +++ b/book/rust-docs/sync15/struct.KeyBundle.html @@ -0,0 +1,46 @@ +KeyBundle in sync15 - Rust

Struct sync15::KeyBundle

source ·
pub struct KeyBundle { /* private fields */ }

Implementations§

source§

impl KeyBundle

source

pub fn new(enc: Vec<u8>, mac: Vec<u8>) -> Result<KeyBundle>

Construct a key bundle from the already-decoded encrypt and hmac keys. +Panics (asserts) if they aren’t both 32 bytes.

+
source

pub fn new_random() -> Result<KeyBundle>

source

pub fn from_ksync_bytes(ksync: &[u8]) -> Result<KeyBundle>

source

pub fn from_ksync_base64(ksync: &str) -> Result<KeyBundle>

source

pub fn from_base64(enc: &str, mac: &str) -> Result<KeyBundle>

source

pub fn encryption_key(&self) -> &[u8]

source

pub fn hmac_key(&self) -> &[u8]

source

pub fn to_b64_array(&self) -> [String; 2]

source

pub fn decrypt( + &self, + enc_base64: &str, + iv_base64: &str, + hmac_base16: &str +) -> Result<String>

Decrypt the provided ciphertext with the given iv, and decodes the +result as a utf8 string.

+
source

pub fn encrypt_bytes_with_iv( + &self, + cleartext_bytes: &[u8], + iv: &[u8] +) -> Result<(String, String)>

Encrypt using the provided IV.

+
source

pub fn encrypt_bytes_rand_iv( + &self, + cleartext_bytes: &[u8] +) -> Result<(String, String, String)>

Generate a random iv and encrypt with it. Return both the encrypted bytes +and the generated iv.

+
source

pub fn encrypt_with_iv( + &self, + cleartext: &str, + iv: &[u8] +) -> Result<(String, String)>

source

pub fn encrypt_rand_iv( + &self, + cleartext: &str +) -> Result<(String, String, String)>

Trait Implementations§

source§

impl Clone for KeyBundle

source§

fn clone(&self) -> KeyBundle

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for KeyBundle

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Hash for KeyBundle

source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · source§

fn hash_slice<H>(data: &[Self], state: &mut H)where + H: Hasher, + Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
source§

impl PartialEq<KeyBundle> for KeyBundle

source§

fn eq(&self, other: &KeyBundle) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Eq for KeyBundle

source§

impl StructuralEq for KeyBundle

source§

impl StructuralPartialEq for KeyBundle

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> CallHasher for Twhere + T: Hash + ?Sized,

§

fn get_hash<H, B>(value: &H, build_hasher: &B) -> u64where + H: Hash + ?Sized, + B: BuildHasher,

source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/sync15/struct.RemoteClient.html b/book/rust-docs/sync15/struct.RemoteClient.html new file mode 100644 index 0000000000..cb51dbe7a1 --- /dev/null +++ b/book/rust-docs/sync15/struct.RemoteClient.html @@ -0,0 +1,28 @@ +RemoteClient in sync15 - Rust

Struct sync15::RemoteClient

source ·
pub struct RemoteClient {
+    pub fxa_device_id: Option<String>,
+    pub device_name: String,
+    pub device_type: DeviceType,
+}
Expand description

Information about a remote client in the clients collection.

+

Fields§

§fxa_device_id: Option<String>§device_name: String§device_type: DeviceType

Trait Implementations§

source§

impl Clone for RemoteClient

source§

fn clone(&self) -> RemoteClient

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for RemoteClient

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl<'de> Deserialize<'de> for RemoteClient

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl Hash for RemoteClient

source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · source§

fn hash_slice<H>(data: &[Self], state: &mut H)where + H: Hasher, + Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
source§

impl PartialEq<RemoteClient> for RemoteClient

source§

fn eq(&self, other: &RemoteClient) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Serialize for RemoteClient

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more
source§

impl Eq for RemoteClient

source§

impl StructuralEq for RemoteClient

source§

impl StructuralPartialEq for RemoteClient

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> CallHasher for Twhere + T: Hash + ?Sized,

§

fn get_hash<H, B>(value: &H, build_hasher: &B) -> u64where + H: Hash + ?Sized, + B: BuildHasher,

source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/sync15/struct.ServerTimestamp.html b/book/rust-docs/sync15/struct.ServerTimestamp.html new file mode 100644 index 0000000000..d4ea585ac6 --- /dev/null +++ b/book/rust-docs/sync15/struct.ServerTimestamp.html @@ -0,0 +1,24 @@ +ServerTimestamp in sync15 - Rust

Struct sync15::ServerTimestamp

source ·
pub struct ServerTimestamp(pub i64);
Expand description

Typesafe way to manage server timestamps without accidentally mixing them up with +local ones.

+

Tuple Fields§

§0: i64

Implementations§

source§

impl ServerTimestamp

source

pub fn from_float_seconds(ts: f64) -> Self

source

pub fn from_millis(ts: i64) -> Self

source§

impl ServerTimestamp

source

pub const EPOCH: ServerTimestamp = _

source

pub fn duration_since(self, other: ServerTimestamp) -> Option<Duration>

Returns None if other is later than self (Duration may not represent +negative timespans in rust).

+
source

pub fn as_millis(self) -> i64

Get the milliseconds for the timestamp.

+

Trait Implementations§

source§

impl Clone for ServerTimestamp

source§

fn clone(&self) -> ServerTimestamp

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for ServerTimestamp

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for ServerTimestamp

source§

fn default() -> ServerTimestamp

Returns the “default value” for a type. Read more
source§

impl<'de> Deserialize<'de> for ServerTimestamp

source§

fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error>

Deserialize this value from the given Serde deserializer. Read more
source§

impl Display for ServerTimestamp

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl FromStr for ServerTimestamp

§

type Err = ParseFloatError

The associated error which can be returned from parsing.
source§

fn from_str(s: &str) -> Result<Self, Self::Err>

Parses a string s to return a value of this type. Read more
source§

impl PartialEq<ServerTimestamp> for ServerTimestamp

source§

fn eq(&self, other: &ServerTimestamp) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl PartialOrd<ServerTimestamp> for ServerTimestamp

source§

fn partial_cmp(&self, other: &ServerTimestamp) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · source§

fn lt(&self, other: &Rhs) -> bool

This method tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · source§

fn le(&self, other: &Rhs) -> bool

This method tests less than or equal to (for self and other) and is used by the <= +operator. Read more
1.0.0 · source§

fn gt(&self, other: &Rhs) -> bool

This method tests greater than (for self and other) and is used by the > operator. Read more
1.0.0 · source§

fn ge(&self, other: &Rhs) -> bool

This method tests greater than or equal to (for self and other) and is used by the >= +operator. Read more
source§

impl Serialize for ServerTimestamp

source§

fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error>

Serialize this value into the given Serde serializer. Read more
source§

impl Copy for ServerTimestamp

source§

impl Eq for ServerTimestamp

source§

impl StructuralEq for ServerTimestamp

source§

impl StructuralPartialEq for ServerTimestamp

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/sync15/telemetry/enum.SyncFailure.html b/book/rust-docs/sync15/telemetry/enum.SyncFailure.html new file mode 100644 index 0000000000..79373d6168 --- /dev/null +++ b/book/rust-docs/sync15/telemetry/enum.SyncFailure.html @@ -0,0 +1,28 @@ +SyncFailure in sync15::telemetry - Rust
pub enum SyncFailure {
+    Shutdown,
+    Other {
+        error: String,
+    },
+    Unexpected {
+        error: String,
+    },
+    Auth {
+        from: &'static str,
+    },
+    Http {
+        code: u16,
+    },
+}
Expand description

A Sync failure.

+

Variants§

§

Shutdown

§

Other

Fields

§error: String
§

Unexpected

Fields

§error: String
§

Auth

Fields

§from: &'static str
§

Http

Fields

§code: u16

Trait Implementations§

source§

impl Debug for SyncFailure

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl<'a> From<&'a Error> for SyncFailure

source§

fn from(e: &Error) -> SyncFailure

Converts to this type from the input type.
source§

impl Serialize for SyncFailure

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/sync15/telemetry/index.html b/book/rust-docs/sync15/telemetry/index.html new file mode 100644 index 0000000000..b9b77cd75f --- /dev/null +++ b/book/rust-docs/sync15/telemetry/index.html @@ -0,0 +1,15 @@ +sync15::telemetry - Rust

Module sync15::telemetry

source ·
Expand description

Manage recording sync telemetry. Assumes some external telemetry +library/code which manages submitting.

+

Structs

  • One engine’s sync.
  • Incoming record for an engine’s sync
  • Outgoing record for an engine’s sync.
  • A generic “Event” - suitable for all kinds of pings (although this module +only cares about the sync ping)
  • A single sync. May have many engines, may have its own failure.
  • The Sync ping payload, as documented at +https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/telemetry/data/sync-ping.html. +May have many syncs, may have many events. However, due to the architecture +of apps which use these components, this payload is almost certainly not +suitable for submitting directly. For example, we will always return a +payload with exactly 1 sync, and it will not know certain other fields +in the payload, such as the hashed FxA device ID (see +https://searchfox.org/mozilla-central/rev/c3ebaf6de2d481c262c04bb9657eaf76bf47e2ac/services/sync/modules/browserid_identity.js#185 +for an example of how the device ID is constructed). The intention is that +consumers of this will use this to create a “real” payload - eg, accumulating +until some threshold number of syncs is reached, and contributing +additional data which only the consumer knows.

Enums

\ No newline at end of file diff --git a/book/rust-docs/sync15/telemetry/sidebar-items.js b/book/rust-docs/sync15/telemetry/sidebar-items.js new file mode 100644 index 0000000000..3df066a6dd --- /dev/null +++ b/book/rust-docs/sync15/telemetry/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["SyncFailure"],"struct":["Engine","EngineIncoming","EngineOutgoing","Event","Problem","SyncTelemetry","SyncTelemetryPing","Validation"]}; \ No newline at end of file diff --git a/book/rust-docs/sync15/telemetry/struct.Engine.html b/book/rust-docs/sync15/telemetry/struct.Engine.html new file mode 100644 index 0000000000..47889f6ce3 --- /dev/null +++ b/book/rust-docs/sync15/telemetry/struct.Engine.html @@ -0,0 +1,14 @@ +Engine in sync15::telemetry - Rust

Struct sync15::telemetry::Engine

source ·
pub struct Engine { /* private fields */ }
Expand description

One engine’s sync.

+

Implementations§

source§

impl Engine

source

pub fn new(name: impl Into<String>) -> Self

source

pub fn incoming(&mut self, inc: EngineIncoming)

source

pub fn get_incoming(&self) -> &Option<EngineIncoming>

source

pub fn outgoing(&mut self, out: EngineOutgoing)

source

pub fn failure(&mut self, err: impl Into<SyncFailure>)

source

pub fn validation(&mut self, v: Validation)

Trait Implementations§

source§

impl Debug for Engine

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Serialize for Engine

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/sync15/telemetry/struct.EngineIncoming.html b/book/rust-docs/sync15/telemetry/struct.EngineIncoming.html new file mode 100644 index 0000000000..72cad30180 --- /dev/null +++ b/book/rust-docs/sync15/telemetry/struct.EngineIncoming.html @@ -0,0 +1,22 @@ +EngineIncoming in sync15::telemetry - Rust
pub struct EngineIncoming { /* private fields */ }
Expand description

Incoming record for an engine’s sync

+

Implementations§

source§

impl EngineIncoming

source

pub fn new() -> Self

source

pub fn applied(&mut self, n: u32)

Increment the value of applied by n.

+
source

pub fn failed(&mut self, n: u32)

Increment the value of failed by n.

+
source

pub fn new_failed(&mut self, n: u32)

Increment the value of new_failed by n.

+
source

pub fn reconciled(&mut self, n: u32)

Increment the value of reconciled by n.

+
source

pub fn get_applied(&self) -> u32

Get the value of applied. Mostly useful for testing.

+
source

pub fn get_failed(&self) -> u32

Get the value of failed. Mostly useful for testing.

+
source

pub fn get_new_failed(&self) -> u32

Get the value of new_failed. Mostly useful for testing.

+
source

pub fn get_reconciled(&self) -> u32

Get the value of reconciled. Mostly useful for testing.

+

Trait Implementations§

source§

impl Debug for EngineIncoming

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for EngineIncoming

source§

fn default() -> EngineIncoming

Returns the “default value” for a type. Read more
source§

impl Serialize for EngineIncoming

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/sync15/telemetry/struct.EngineOutgoing.html b/book/rust-docs/sync15/telemetry/struct.EngineOutgoing.html new file mode 100644 index 0000000000..20fbe67ac8 --- /dev/null +++ b/book/rust-docs/sync15/telemetry/struct.EngineOutgoing.html @@ -0,0 +1,14 @@ +EngineOutgoing in sync15::telemetry - Rust
pub struct EngineOutgoing { /* private fields */ }
Expand description

Outgoing record for an engine’s sync.

+

Implementations§

source§

impl EngineOutgoing

source

pub fn new() -> Self

source

pub fn sent(&mut self, n: usize)

source

pub fn failed(&mut self, n: usize)

Trait Implementations§

source§

impl Debug for EngineOutgoing

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for EngineOutgoing

source§

fn default() -> EngineOutgoing

Returns the “default value” for a type. Read more
source§

impl Serialize for EngineOutgoing

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/sync15/telemetry/struct.Event.html b/book/rust-docs/sync15/telemetry/struct.Event.html new file mode 100644 index 0000000000..23f0a8d34f --- /dev/null +++ b/book/rust-docs/sync15/telemetry/struct.Event.html @@ -0,0 +1,15 @@ +Event in sync15::telemetry - Rust

Struct sync15::telemetry::Event

source ·
pub struct Event { /* private fields */ }
Expand description

A generic “Event” - suitable for all kinds of pings (although this module +only cares about the sync ping)

+

Implementations§

source§

impl Event

source

pub fn new(object: &'static str, method: &'static str) -> Self

source

pub fn value(self, v: &'static str) -> Self

source

pub fn extra(self, key: &'static str, val: String) -> Self

Trait Implementations§

source§

impl Debug for Event

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Serialize for Event

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more

Auto Trait Implementations§

§

impl RefUnwindSafe for Event

§

impl Send for Event

§

impl Sync for Event

§

impl Unpin for Event

§

impl UnwindSafe for Event

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/sync15/telemetry/struct.Problem.html b/book/rust-docs/sync15/telemetry/struct.Problem.html new file mode 100644 index 0000000000..ff60239496 --- /dev/null +++ b/book/rust-docs/sync15/telemetry/struct.Problem.html @@ -0,0 +1,13 @@ +Problem in sync15::telemetry - Rust

Struct sync15::telemetry::Problem

source ·
pub struct Problem { /* private fields */ }

Trait Implementations§

source§

impl Debug for Problem

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for Problem

source§

fn default() -> Problem

Returns the “default value” for a type. Read more
source§

impl Serialize for Problem

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/sync15/telemetry/struct.SyncTelemetry.html b/book/rust-docs/sync15/telemetry/struct.SyncTelemetry.html new file mode 100644 index 0000000000..8224a5d2c6 --- /dev/null +++ b/book/rust-docs/sync15/telemetry/struct.SyncTelemetry.html @@ -0,0 +1,14 @@ +SyncTelemetry in sync15::telemetry - Rust
pub struct SyncTelemetry { /* private fields */ }
Expand description

A single sync. May have many engines, may have its own failure.

+

Implementations§

source§

impl SyncTelemetry

source

pub fn new() -> Self

source

pub fn engine(&mut self, e: Engine)

source

pub fn failure(&mut self, failure: SyncFailure)

source

pub fn finished(&mut self)

Trait Implementations§

source§

impl Debug for SyncTelemetry

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for SyncTelemetry

source§

fn default() -> SyncTelemetry

Returns the “default value” for a type. Read more
source§

impl Serialize for SyncTelemetry

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/sync15/telemetry/struct.SyncTelemetryPing.html b/book/rust-docs/sync15/telemetry/struct.SyncTelemetryPing.html new file mode 100644 index 0000000000..586ddb7448 --- /dev/null +++ b/book/rust-docs/sync15/telemetry/struct.SyncTelemetryPing.html @@ -0,0 +1,28 @@ +SyncTelemetryPing in sync15::telemetry - Rust
pub struct SyncTelemetryPing { /* private fields */ }
Expand description

The Sync ping payload, as documented at +https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/telemetry/data/sync-ping.html. +May have many syncs, may have many events. However, due to the architecture +of apps which use these components, this payload is almost certainly not +suitable for submitting directly. For example, we will always return a +payload with exactly 1 sync, and it will not know certain other fields +in the payload, such as the hashed FxA device ID (see +https://searchfox.org/mozilla-central/rev/c3ebaf6de2d481c262c04bb9657eaf76bf47e2ac/services/sync/modules/browserid_identity.js#185 +for an example of how the device ID is constructed). The intention is that +consumers of this will use this to create a “real” payload - eg, accumulating +until some threshold number of syncs is reached, and contributing +additional data which only the consumer knows.

+

Implementations§

source§

impl SyncTelemetryPing

source

pub fn new() -> Self

source

pub fn uid(&mut self, uid: String)

source

pub fn sync(&mut self, s: SyncTelemetry)

source

pub fn event(&mut self, e: Event)

Trait Implementations§

source§

impl Debug for SyncTelemetryPing

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for SyncTelemetryPing

source§

fn default() -> SyncTelemetryPing

Returns the “default value” for a type. Read more
source§

impl IntoFfi for SyncTelemetryPingwhere + SyncTelemetryPing: Serialize,

§

type Value = *mut i8

This type must be: Read more
source§

fn ffi_default() -> *mut c_char

Return an ‘empty’ value. This is what’s passed back to C in the case of an error, +so it doesn’t actually need to be “empty”, so much as “ignorable”. Note that this +is also used when an empty Option<T> is returned.
source§

fn into_ffi_value(self) -> *mut c_char

Convert ourselves into a value we can pass back to C with confidence.
source§

impl Serialize for SyncTelemetryPing

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/sync15/telemetry/struct.Validation.html b/book/rust-docs/sync15/telemetry/struct.Validation.html new file mode 100644 index 0000000000..8a45beab9a --- /dev/null +++ b/book/rust-docs/sync15/telemetry/struct.Validation.html @@ -0,0 +1,13 @@ +Validation in sync15::telemetry - Rust
pub struct Validation { /* private fields */ }

Implementations§

source§

impl Validation

source

pub fn with_version(version: u32) -> Validation

source

pub fn problem(&mut self, name: &'static str, count: usize) -> &mut Self

Trait Implementations§

source§

impl Debug for Validation

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for Validation

source§

fn default() -> Validation

Returns the “default value” for a type. Read more
source§

impl Serialize for Validation

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/sync15/type.CollectionName.html b/book/rust-docs/sync15/type.CollectionName.html new file mode 100644 index 0000000000..80840f4487 --- /dev/null +++ b/book/rust-docs/sync15/type.CollectionName.html @@ -0,0 +1 @@ +CollectionName in sync15 - Rust

Type Definition sync15::CollectionName

source ·
pub type CollectionName = Cow<'static, str>;
\ No newline at end of file diff --git a/book/rust-docs/sync15/type.Result.html b/book/rust-docs/sync15/type.Result.html new file mode 100644 index 0000000000..b4689d45f5 --- /dev/null +++ b/book/rust-docs/sync15/type.Result.html @@ -0,0 +1 @@ +Result in sync15 - Rust

Type Definition sync15::Result

source ·
pub type Result<T> = Result<T, Error>;
\ No newline at end of file diff --git a/book/rust-docs/sync_guid/all.html b/book/rust-docs/sync_guid/all.html new file mode 100644 index 0000000000..58c5c5a9cf --- /dev/null +++ b/book/rust-docs/sync_guid/all.html @@ -0,0 +1 @@ +List of all items in this crate

List of all items

Structs

\ No newline at end of file diff --git a/book/rust-docs/sync_guid/index.html b/book/rust-docs/sync_guid/index.html new file mode 100644 index 0000000000..7c53aca9a1 --- /dev/null +++ b/book/rust-docs/sync_guid/index.html @@ -0,0 +1,2 @@ +sync_guid - Rust

Crate sync_guid

source ·

Structs

  • This is a type intended to be used to represent the guids used by sync. It +has several benefits over using a String:
\ No newline at end of file diff --git a/book/rust-docs/sync_guid/sidebar-items.js b/book/rust-docs/sync_guid/sidebar-items.js new file mode 100644 index 0000000000..5387707773 --- /dev/null +++ b/book/rust-docs/sync_guid/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"struct":["Guid"]}; \ No newline at end of file diff --git a/book/rust-docs/sync_guid/struct.Guid.html b/book/rust-docs/sync_guid/struct.Guid.html new file mode 100644 index 0000000000..432aef0cc0 --- /dev/null +++ b/book/rust-docs/sync_guid/struct.Guid.html @@ -0,0 +1,1250 @@ +Guid in sync_guid - Rust

Struct sync_guid::Guid

source ·
pub struct Guid(_);
Expand description

This is a type intended to be used to represent the guids used by sync. It +has several benefits over using a String:

+
    +
  1. +

    It’s more explicit about what is being stored, and could prevent bugs +where a Guid is passed to a function expecting text.

    +
  2. +
  3. +

    Guids are guaranteed to be immutable.

    +
  4. +
  5. +

    It’s optimized for the guids commonly used by sync. In particular, short guids +(including the guids which would meet PlacesUtils.isValidGuid) do not incur +any heap allocation, and are stored inline.

    +
  6. +
+

Implementations§

source§

impl Guid

source

pub fn new(s: &str) -> Self

Create a guid from a str.

+
source

pub const fn empty() -> Self

Create an empty guid. Usable as a constant.

+
source

pub fn random() -> Self

Create a random guid (of 12 base64url characters). Requires the random +feature.

+
source

pub fn from_string(s: String) -> Self

Convert b into a Guid.

+
source

pub fn from_slice(b: &[u8]) -> Self

Convert b into a Guid.

+
source

pub fn from_vec(v: Vec<u8>) -> Self

Convert v to a Guid, consuming it.

+
source

pub fn as_bytes(&self) -> &[u8]

Get the data backing this Guid as a &[u8].

+
source

pub fn as_str(&self) -> &str

Get the data backing this Guid as a &str.

+
source

pub fn into_string(self) -> String

Convert this Guid into a String, consuming it in the process.

+
source

pub fn is_valid_for_sync_server(&self) -> bool

Returns true for Guids that are deemed valid by the sync server. +See https://github.com/mozilla-services/server-syncstorage/blob/d92ef07877aebd05b92f87f6ade341d6a55bffc8/syncstorage/bso.py#L24

+
source

pub fn is_valid_for_places(&self) -> bool

Returns true for Guids that are valid places guids, and false for all others.

+
source

pub fn is_valid_places_byte(b: u8) -> bool

Returns true if the byte b is a valid base64url byte.

+

Methods from Deref<Target = str>§

1.0.0 · source

pub fn len(&self) -> usize

Returns the length of self.

+

This length is in bytes, not chars or graphemes. In other words, +it might not be what a human considers the length of the string.

+
Examples
+
let len = "foo".len();
+assert_eq!(3, len);
+
+assert_eq!("ƒoo".len(), 4); // fancy f!
+assert_eq!("ƒoo".chars().count(), 3);
+
1.0.0 · source

pub fn is_empty(&self) -> bool

Returns true if self has a length of zero bytes.

+
Examples
+
let s = "";
+assert!(s.is_empty());
+
+let s = "not empty";
+assert!(!s.is_empty());
+
1.9.0 · source

pub fn is_char_boundary(&self, index: usize) -> bool

Checks that index-th byte is the first byte in a UTF-8 code point +sequence or the end of the string.

+

The start and end of the string (when index == self.len()) are +considered to be boundaries.

+

Returns false if index is greater than self.len().

+
Examples
+
let s = "Löwe 老虎 Léopard";
+assert!(s.is_char_boundary(0));
+// start of `老`
+assert!(s.is_char_boundary(6));
+assert!(s.is_char_boundary(s.len()));
+
+// second byte of `ö`
+assert!(!s.is_char_boundary(2));
+
+// third byte of `老`
+assert!(!s.is_char_boundary(8));
+
source

pub fn floor_char_boundary(&self, index: usize) -> usize

🔬This is a nightly-only experimental API. (round_char_boundary)

Finds the closest x not exceeding index where is_char_boundary(x) is true.

+

This method can help you truncate a string so that it’s still valid UTF-8, but doesn’t +exceed a given number of bytes. Note that this is done purely at the character level +and can still visually split graphemes, even though the underlying characters aren’t +split. For example, the emoji 🧑‍🔬 (scientist) could be split so that the string only +includes 🧑 (person) instead.

+
Examples
+
#![feature(round_char_boundary)]
+let s = "❤️🧡💛💚💙💜";
+assert_eq!(s.len(), 26);
+assert!(!s.is_char_boundary(13));
+
+let closest = s.floor_char_boundary(13);
+assert_eq!(closest, 10);
+assert_eq!(&s[..closest], "❤️🧡");
+
source

pub fn ceil_char_boundary(&self, index: usize) -> usize

🔬This is a nightly-only experimental API. (round_char_boundary)

Finds the closest x not below index where is_char_boundary(x) is true.

+

This method is the natural complement to floor_char_boundary. See that method +for more details.

+
Panics
+

Panics if index > self.len().

+
Examples
+
#![feature(round_char_boundary)]
+let s = "❤️🧡💛💚💙💜";
+assert_eq!(s.len(), 26);
+assert!(!s.is_char_boundary(13));
+
+let closest = s.ceil_char_boundary(13);
+assert_eq!(closest, 14);
+assert_eq!(&s[..closest], "❤️🧡💛");
+
1.0.0 · source

pub fn as_bytes(&self) -> &[u8]

Converts a string slice to a byte slice. To convert the byte slice back +into a string slice, use the from_utf8 function.

+
Examples
+
let bytes = "bors".as_bytes();
+assert_eq!(b"bors", bytes);
+
1.0.0 · source

pub fn as_ptr(&self) -> *const u8

Converts a string slice to a raw pointer.

+

As string slices are a slice of bytes, the raw pointer points to a +u8. This pointer will be pointing to the first byte of the string +slice.

+

The caller must ensure that the returned pointer is never written to. +If you need to mutate the contents of the string slice, use as_mut_ptr.

+
Examples
+
let s = "Hello";
+let ptr = s.as_ptr();
+
1.20.0 · source

pub fn get<I>(&self, i: I) -> Option<&<I as SliceIndex<str>>::Output>where + I: SliceIndex<str>,

Returns a subslice of str.

+

This is the non-panicking alternative to indexing the str. Returns +None whenever equivalent indexing operation would panic.

+
Examples
+
let v = String::from("🗻∈🌏");
+
+assert_eq!(Some("🗻"), v.get(0..4));
+
+// indices not on UTF-8 sequence boundaries
+assert!(v.get(1..).is_none());
+assert!(v.get(..8).is_none());
+
+// out of bounds
+assert!(v.get(..42).is_none());
+
1.20.0 · source

pub unsafe fn get_unchecked<I>(&self, i: I) -> &<I as SliceIndex<str>>::Outputwhere + I: SliceIndex<str>,

Returns an unchecked subslice of str.

+

This is the unchecked alternative to indexing the str.

+
Safety
+

Callers of this function are responsible that these preconditions are +satisfied:

+
    +
  • The starting index must not exceed the ending index;
  • +
  • Indexes must be within bounds of the original slice;
  • +
  • Indexes must lie on UTF-8 sequence boundaries.
  • +
+

Failing that, the returned string slice may reference invalid memory or +violate the invariants communicated by the str type.

+
Examples
+
let v = "🗻∈🌏";
+unsafe {
+    assert_eq!("🗻", v.get_unchecked(0..4));
+    assert_eq!("∈", v.get_unchecked(4..7));
+    assert_eq!("🌏", v.get_unchecked(7..11));
+}
+
1.0.0 · source

pub unsafe fn slice_unchecked(&self, begin: usize, end: usize) -> &str

👎Deprecated since 1.29.0: use get_unchecked(begin..end) instead

Creates a string slice from another string slice, bypassing safety +checks.

+

This is generally not recommended, use with caution! For a safe +alternative see str and Index.

+

This new slice goes from begin to end, including begin but +excluding end.

+

To get a mutable string slice instead, see the +slice_mut_unchecked method.

+
Safety
+

Callers of this function are responsible that three preconditions are +satisfied:

+
    +
  • begin must not exceed end.
  • +
  • begin and end must be byte positions within the string slice.
  • +
  • begin and end must lie on UTF-8 sequence boundaries.
  • +
+
Examples
+
let s = "Löwe 老虎 Léopard";
+
+unsafe {
+    assert_eq!("Löwe 老虎 Léopard", s.slice_unchecked(0, 21));
+}
+
+let s = "Hello, world!";
+
+unsafe {
+    assert_eq!("world", s.slice_unchecked(7, 12));
+}
+
1.4.0 · source

pub fn split_at(&self, mid: usize) -> (&str, &str)

Divide one string slice into two at an index.

+

The argument, mid, should be a byte offset from the start of the +string. It must also be on the boundary of a UTF-8 code point.

+

The two slices returned go from the start of the string slice to mid, +and from mid to the end of the string slice.

+

To get mutable string slices instead, see the split_at_mut +method.

+
Panics
+

Panics if mid is not on a UTF-8 code point boundary, or if it is +past the end of the last code point of the string slice.

+
Examples
+
let s = "Per Martin-Löf";
+
+let (first, last) = s.split_at(3);
+
+assert_eq!("Per", first);
+assert_eq!(" Martin-Löf", last);
+
1.0.0 · source

pub fn chars(&self) -> Chars<'_>

Returns an iterator over the chars of a string slice.

+

As a string slice consists of valid UTF-8, we can iterate through a +string slice by char. This method returns such an iterator.

+

It’s important to remember that char represents a Unicode Scalar +Value, and might not match your idea of what a ‘character’ is. Iteration +over grapheme clusters may be what you actually want. This functionality +is not provided by Rust’s standard library, check crates.io instead.

+
Examples
+

Basic usage:

+ +
let word = "goodbye";
+
+let count = word.chars().count();
+assert_eq!(7, count);
+
+let mut chars = word.chars();
+
+assert_eq!(Some('g'), chars.next());
+assert_eq!(Some('o'), chars.next());
+assert_eq!(Some('o'), chars.next());
+assert_eq!(Some('d'), chars.next());
+assert_eq!(Some('b'), chars.next());
+assert_eq!(Some('y'), chars.next());
+assert_eq!(Some('e'), chars.next());
+
+assert_eq!(None, chars.next());
+

Remember, chars might not match your intuition about characters:

+ +
let y = "y̆";
+
+let mut chars = y.chars();
+
+assert_eq!(Some('y'), chars.next()); // not 'y̆'
+assert_eq!(Some('\u{0306}'), chars.next());
+
+assert_eq!(None, chars.next());
+
1.0.0 · source

pub fn char_indices(&self) -> CharIndices<'_>

Returns an iterator over the chars of a string slice, and their +positions.

+

As a string slice consists of valid UTF-8, we can iterate through a +string slice by char. This method returns an iterator of both +these chars, as well as their byte positions.

+

The iterator yields tuples. The position is first, the char is +second.

+
Examples
+

Basic usage:

+ +
let word = "goodbye";
+
+let count = word.char_indices().count();
+assert_eq!(7, count);
+
+let mut char_indices = word.char_indices();
+
+assert_eq!(Some((0, 'g')), char_indices.next());
+assert_eq!(Some((1, 'o')), char_indices.next());
+assert_eq!(Some((2, 'o')), char_indices.next());
+assert_eq!(Some((3, 'd')), char_indices.next());
+assert_eq!(Some((4, 'b')), char_indices.next());
+assert_eq!(Some((5, 'y')), char_indices.next());
+assert_eq!(Some((6, 'e')), char_indices.next());
+
+assert_eq!(None, char_indices.next());
+

Remember, chars might not match your intuition about characters:

+ +
let yes = "y̆es";
+
+let mut char_indices = yes.char_indices();
+
+assert_eq!(Some((0, 'y')), char_indices.next()); // not (0, 'y̆')
+assert_eq!(Some((1, '\u{0306}')), char_indices.next());
+
+// note the 3 here - the last character took up two bytes
+assert_eq!(Some((3, 'e')), char_indices.next());
+assert_eq!(Some((4, 's')), char_indices.next());
+
+assert_eq!(None, char_indices.next());
+
1.0.0 · source

pub fn bytes(&self) -> Bytes<'_>

An iterator over the bytes of a string slice.

+

As a string slice consists of a sequence of bytes, we can iterate +through a string slice by byte. This method returns such an iterator.

+
Examples
+
let mut bytes = "bors".bytes();
+
+assert_eq!(Some(b'b'), bytes.next());
+assert_eq!(Some(b'o'), bytes.next());
+assert_eq!(Some(b'r'), bytes.next());
+assert_eq!(Some(b's'), bytes.next());
+
+assert_eq!(None, bytes.next());
+
1.1.0 · source

pub fn split_whitespace(&self) -> SplitWhitespace<'_>

Splits a string slice by whitespace.

+

The iterator returned will return string slices that are sub-slices of +the original string slice, separated by any amount of whitespace.

+

‘Whitespace’ is defined according to the terms of the Unicode Derived +Core Property White_Space. If you only want to split on ASCII whitespace +instead, use split_ascii_whitespace.

+
Examples
+

Basic usage:

+ +
let mut iter = "A few words".split_whitespace();
+
+assert_eq!(Some("A"), iter.next());
+assert_eq!(Some("few"), iter.next());
+assert_eq!(Some("words"), iter.next());
+
+assert_eq!(None, iter.next());
+

All kinds of whitespace are considered:

+ +
let mut iter = " Mary   had\ta\u{2009}little  \n\t lamb".split_whitespace();
+assert_eq!(Some("Mary"), iter.next());
+assert_eq!(Some("had"), iter.next());
+assert_eq!(Some("a"), iter.next());
+assert_eq!(Some("little"), iter.next());
+assert_eq!(Some("lamb"), iter.next());
+
+assert_eq!(None, iter.next());
+

If the string is empty or all whitespace, the iterator yields no string slices:

+ +
assert_eq!("".split_whitespace().next(), None);
+assert_eq!("   ".split_whitespace().next(), None);
+
1.34.0 · source

pub fn split_ascii_whitespace(&self) -> SplitAsciiWhitespace<'_>

Splits a string slice by ASCII whitespace.

+

The iterator returned will return string slices that are sub-slices of +the original string slice, separated by any amount of ASCII whitespace.

+

To split by Unicode Whitespace instead, use split_whitespace.

+
Examples
+

Basic usage:

+ +
let mut iter = "A few words".split_ascii_whitespace();
+
+assert_eq!(Some("A"), iter.next());
+assert_eq!(Some("few"), iter.next());
+assert_eq!(Some("words"), iter.next());
+
+assert_eq!(None, iter.next());
+

All kinds of ASCII whitespace are considered:

+ +
let mut iter = " Mary   had\ta little  \n\t lamb".split_ascii_whitespace();
+assert_eq!(Some("Mary"), iter.next());
+assert_eq!(Some("had"), iter.next());
+assert_eq!(Some("a"), iter.next());
+assert_eq!(Some("little"), iter.next());
+assert_eq!(Some("lamb"), iter.next());
+
+assert_eq!(None, iter.next());
+

If the string is empty or all ASCII whitespace, the iterator yields no string slices:

+ +
assert_eq!("".split_ascii_whitespace().next(), None);
+assert_eq!("   ".split_ascii_whitespace().next(), None);
+
1.0.0 · source

pub fn lines(&self) -> Lines<'_>

An iterator over the lines of a string, as string slices.

+

Lines are split at line endings that are either newlines (\n) or +sequences of a carriage return followed by a line feed (\r\n).

+

Line terminators are not included in the lines returned by the iterator.

+

The final line ending is optional. A string that ends with a final line +ending will return the same lines as an otherwise identical string +without a final line ending.

+
Examples
+

Basic usage:

+ +
let text = "foo\r\nbar\n\nbaz\n";
+let mut lines = text.lines();
+
+assert_eq!(Some("foo"), lines.next());
+assert_eq!(Some("bar"), lines.next());
+assert_eq!(Some(""), lines.next());
+assert_eq!(Some("baz"), lines.next());
+
+assert_eq!(None, lines.next());
+

The final line ending isn’t required:

+ +
let text = "foo\nbar\n\r\nbaz";
+let mut lines = text.lines();
+
+assert_eq!(Some("foo"), lines.next());
+assert_eq!(Some("bar"), lines.next());
+assert_eq!(Some(""), lines.next());
+assert_eq!(Some("baz"), lines.next());
+
+assert_eq!(None, lines.next());
+
1.0.0 · source

pub fn lines_any(&self) -> LinesAny<'_>

👎Deprecated since 1.4.0: use lines() instead now

An iterator over the lines of a string.

+
1.8.0 · source

pub fn encode_utf16(&self) -> EncodeUtf16<'_>

Returns an iterator of u16 over the string encoded as UTF-16.

+
Examples
+
let text = "Zażółć gęślą jaźń";
+
+let utf8_len = text.len();
+let utf16_len = text.encode_utf16().count();
+
+assert!(utf16_len <= utf8_len);
+
1.0.0 · source

pub fn contains<'a, P>(&'a self, pat: P) -> boolwhere + P: Pattern<'a>,

Returns true if the given pattern matches a sub-slice of +this string slice.

+

Returns false if it does not.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Examples
+
let bananas = "bananas";
+
+assert!(bananas.contains("nana"));
+assert!(!bananas.contains("apples"));
+
1.0.0 · source

pub fn starts_with<'a, P>(&'a self, pat: P) -> boolwhere + P: Pattern<'a>,

Returns true if the given pattern matches a prefix of this +string slice.

+

Returns false if it does not.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Examples
+
let bananas = "bananas";
+
+assert!(bananas.starts_with("bana"));
+assert!(!bananas.starts_with("nana"));
+
1.0.0 · source

pub fn ends_with<'a, P>(&'a self, pat: P) -> boolwhere + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,

Returns true if the given pattern matches a suffix of this +string slice.

+

Returns false if it does not.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Examples
+
let bananas = "bananas";
+
+assert!(bananas.ends_with("anas"));
+assert!(!bananas.ends_with("nana"));
+
1.0.0 · source

pub fn find<'a, P>(&'a self, pat: P) -> Option<usize>where + P: Pattern<'a>,

Returns the byte index of the first character of this string slice that +matches the pattern.

+

Returns None if the pattern doesn’t match.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Examples
+

Simple patterns:

+ +
let s = "Löwe 老虎 Léopard Gepardi";
+
+assert_eq!(s.find('L'), Some(0));
+assert_eq!(s.find('é'), Some(14));
+assert_eq!(s.find("pard"), Some(17));
+

More complex patterns using point-free style and closures:

+ +
let s = "Löwe 老虎 Léopard";
+
+assert_eq!(s.find(char::is_whitespace), Some(5));
+assert_eq!(s.find(char::is_lowercase), Some(1));
+assert_eq!(s.find(|c: char| c.is_whitespace() || c.is_lowercase()), Some(1));
+assert_eq!(s.find(|c: char| (c < 'o') && (c > 'a')), Some(4));
+

Not finding the pattern:

+ +
let s = "Löwe 老虎 Léopard";
+let x: &[_] = &['1', '2'];
+
+assert_eq!(s.find(x), None);
+
1.0.0 · source

pub fn rfind<'a, P>(&'a self, pat: P) -> Option<usize>where + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,

Returns the byte index for the first character of the last match of the pattern in +this string slice.

+

Returns None if the pattern doesn’t match.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Examples
+

Simple patterns:

+ +
let s = "Löwe 老虎 Léopard Gepardi";
+
+assert_eq!(s.rfind('L'), Some(13));
+assert_eq!(s.rfind('é'), Some(14));
+assert_eq!(s.rfind("pard"), Some(24));
+

More complex patterns with closures:

+ +
let s = "Löwe 老虎 Léopard";
+
+assert_eq!(s.rfind(char::is_whitespace), Some(12));
+assert_eq!(s.rfind(char::is_lowercase), Some(20));
+

Not finding the pattern:

+ +
let s = "Löwe 老虎 Léopard";
+let x: &[_] = &['1', '2'];
+
+assert_eq!(s.rfind(x), None);
+
1.0.0 · source

pub fn split<'a, P>(&'a self, pat: P) -> Split<'a, P>where + P: Pattern<'a>,

An iterator over substrings of this string slice, separated by +characters matched by a pattern.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Iterator behavior
+

The returned iterator will be a DoubleEndedIterator if the pattern +allows a reverse search and forward/reverse search yields the same +elements. This is true for, e.g., char, but not for &str.

+

If the pattern allows a reverse search but its results might differ +from a forward search, the rsplit method can be used.

+
Examples
+

Simple patterns:

+ +
let v: Vec<&str> = "Mary had a little lamb".split(' ').collect();
+assert_eq!(v, ["Mary", "had", "a", "little", "lamb"]);
+
+let v: Vec<&str> = "".split('X').collect();
+assert_eq!(v, [""]);
+
+let v: Vec<&str> = "lionXXtigerXleopard".split('X').collect();
+assert_eq!(v, ["lion", "", "tiger", "leopard"]);
+
+let v: Vec<&str> = "lion::tiger::leopard".split("::").collect();
+assert_eq!(v, ["lion", "tiger", "leopard"]);
+
+let v: Vec<&str> = "abc1def2ghi".split(char::is_numeric).collect();
+assert_eq!(v, ["abc", "def", "ghi"]);
+
+let v: Vec<&str> = "lionXtigerXleopard".split(char::is_uppercase).collect();
+assert_eq!(v, ["lion", "tiger", "leopard"]);
+

If the pattern is a slice of chars, split on each occurrence of any of the characters:

+ +
let v: Vec<&str> = "2020-11-03 23:59".split(&['-', ' ', ':', '@'][..]).collect();
+assert_eq!(v, ["2020", "11", "03", "23", "59"]);
+

A more complex pattern, using a closure:

+ +
let v: Vec<&str> = "abc1defXghi".split(|c| c == '1' || c == 'X').collect();
+assert_eq!(v, ["abc", "def", "ghi"]);
+

If a string contains multiple contiguous separators, you will end up +with empty strings in the output:

+ +
let x = "||||a||b|c".to_string();
+let d: Vec<_> = x.split('|').collect();
+
+assert_eq!(d, &["", "", "", "", "a", "", "b", "c"]);
+

Contiguous separators are separated by the empty string.

+ +
let x = "(///)".to_string();
+let d: Vec<_> = x.split('/').collect();
+
+assert_eq!(d, &["(", "", "", ")"]);
+

Separators at the start or end of a string are neighbored +by empty strings.

+ +
let d: Vec<_> = "010".split("0").collect();
+assert_eq!(d, &["", "1", ""]);
+

When the empty string is used as a separator, it separates +every character in the string, along with the beginning +and end of the string.

+ +
let f: Vec<_> = "rust".split("").collect();
+assert_eq!(f, &["", "r", "u", "s", "t", ""]);
+

Contiguous separators can lead to possibly surprising behavior +when whitespace is used as the separator. This code is correct:

+ +
let x = "    a  b c".to_string();
+let d: Vec<_> = x.split(' ').collect();
+
+assert_eq!(d, &["", "", "", "", "a", "", "b", "c"]);
+

It does not give you:

+ +
assert_eq!(d, &["a", "b", "c"]);
+

Use split_whitespace for this behavior.

+
1.51.0 · source

pub fn split_inclusive<'a, P>(&'a self, pat: P) -> SplitInclusive<'a, P>where + P: Pattern<'a>,

An iterator over substrings of this string slice, separated by +characters matched by a pattern. Differs from the iterator produced by +split in that split_inclusive leaves the matched part as the +terminator of the substring.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Examples
+
let v: Vec<&str> = "Mary had a little lamb\nlittle lamb\nlittle lamb."
+    .split_inclusive('\n').collect();
+assert_eq!(v, ["Mary had a little lamb\n", "little lamb\n", "little lamb."]);
+

If the last element of the string is matched, +that element will be considered the terminator of the preceding substring. +That substring will be the last item returned by the iterator.

+ +
let v: Vec<&str> = "Mary had a little lamb\nlittle lamb\nlittle lamb.\n"
+    .split_inclusive('\n').collect();
+assert_eq!(v, ["Mary had a little lamb\n", "little lamb\n", "little lamb.\n"]);
+
1.0.0 · source

pub fn rsplit<'a, P>(&'a self, pat: P) -> RSplit<'a, P>where + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,

An iterator over substrings of the given string slice, separated by +characters matched by a pattern and yielded in reverse order.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Iterator behavior
+

The returned iterator requires that the pattern supports a reverse +search, and it will be a DoubleEndedIterator if a forward/reverse +search yields the same elements.

+

For iterating from the front, the split method can be used.

+
Examples
+

Simple patterns:

+ +
let v: Vec<&str> = "Mary had a little lamb".rsplit(' ').collect();
+assert_eq!(v, ["lamb", "little", "a", "had", "Mary"]);
+
+let v: Vec<&str> = "".rsplit('X').collect();
+assert_eq!(v, [""]);
+
+let v: Vec<&str> = "lionXXtigerXleopard".rsplit('X').collect();
+assert_eq!(v, ["leopard", "tiger", "", "lion"]);
+
+let v: Vec<&str> = "lion::tiger::leopard".rsplit("::").collect();
+assert_eq!(v, ["leopard", "tiger", "lion"]);
+

A more complex pattern, using a closure:

+ +
let v: Vec<&str> = "abc1defXghi".rsplit(|c| c == '1' || c == 'X').collect();
+assert_eq!(v, ["ghi", "def", "abc"]);
+
1.0.0 · source

pub fn split_terminator<'a, P>(&'a self, pat: P) -> SplitTerminator<'a, P>where + P: Pattern<'a>,

An iterator over substrings of the given string slice, separated by +characters matched by a pattern.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+

Equivalent to split, except that the trailing substring +is skipped if empty.

+

This method can be used for string data that is terminated, +rather than separated by a pattern.

+
Iterator behavior
+

The returned iterator will be a DoubleEndedIterator if the pattern +allows a reverse search and forward/reverse search yields the same +elements. This is true for, e.g., char, but not for &str.

+

If the pattern allows a reverse search but its results might differ +from a forward search, the rsplit_terminator method can be used.

+
Examples
+
let v: Vec<&str> = "A.B.".split_terminator('.').collect();
+assert_eq!(v, ["A", "B"]);
+
+let v: Vec<&str> = "A..B..".split_terminator(".").collect();
+assert_eq!(v, ["A", "", "B", ""]);
+
+let v: Vec<&str> = "A.B:C.D".split_terminator(&['.', ':'][..]).collect();
+assert_eq!(v, ["A", "B", "C", "D"]);
+
1.0.0 · source

pub fn rsplit_terminator<'a, P>(&'a self, pat: P) -> RSplitTerminator<'a, P>where + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,

An iterator over substrings of self, separated by characters +matched by a pattern and yielded in reverse order.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+

Equivalent to split, except that the trailing substring is +skipped if empty.

+

This method can be used for string data that is terminated, +rather than separated by a pattern.

+
Iterator behavior
+

The returned iterator requires that the pattern supports a +reverse search, and it will be double ended if a forward/reverse +search yields the same elements.

+

For iterating from the front, the split_terminator method can be +used.

+
Examples
+
let v: Vec<&str> = "A.B.".rsplit_terminator('.').collect();
+assert_eq!(v, ["B", "A"]);
+
+let v: Vec<&str> = "A..B..".rsplit_terminator(".").collect();
+assert_eq!(v, ["", "B", "", "A"]);
+
+let v: Vec<&str> = "A.B:C.D".rsplit_terminator(&['.', ':'][..]).collect();
+assert_eq!(v, ["D", "C", "B", "A"]);
+
1.0.0 · source

pub fn splitn<'a, P>(&'a self, n: usize, pat: P) -> SplitN<'a, P>where + P: Pattern<'a>,

An iterator over substrings of the given string slice, separated by a +pattern, restricted to returning at most n items.

+

If n substrings are returned, the last substring (the nth substring) +will contain the remainder of the string.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Iterator behavior
+

The returned iterator will not be double ended, because it is +not efficient to support.

+

If the pattern allows a reverse search, the rsplitn method can be +used.

+
Examples
+

Simple patterns:

+ +
let v: Vec<&str> = "Mary had a little lambda".splitn(3, ' ').collect();
+assert_eq!(v, ["Mary", "had", "a little lambda"]);
+
+let v: Vec<&str> = "lionXXtigerXleopard".splitn(3, "X").collect();
+assert_eq!(v, ["lion", "", "tigerXleopard"]);
+
+let v: Vec<&str> = "abcXdef".splitn(1, 'X').collect();
+assert_eq!(v, ["abcXdef"]);
+
+let v: Vec<&str> = "".splitn(1, 'X').collect();
+assert_eq!(v, [""]);
+

A more complex pattern, using a closure:

+ +
let v: Vec<&str> = "abc1defXghi".splitn(2, |c| c == '1' || c == 'X').collect();
+assert_eq!(v, ["abc", "defXghi"]);
+
1.0.0 · source

pub fn rsplitn<'a, P>(&'a self, n: usize, pat: P) -> RSplitN<'a, P>where + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,

An iterator over substrings of this string slice, separated by a +pattern, starting from the end of the string, restricted to returning +at most n items.

+

If n substrings are returned, the last substring (the nth substring) +will contain the remainder of the string.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Iterator behavior
+

The returned iterator will not be double ended, because it is not +efficient to support.

+

For splitting from the front, the splitn method can be used.

+
Examples
+

Simple patterns:

+ +
let v: Vec<&str> = "Mary had a little lamb".rsplitn(3, ' ').collect();
+assert_eq!(v, ["lamb", "little", "Mary had a"]);
+
+let v: Vec<&str> = "lionXXtigerXleopard".rsplitn(3, 'X').collect();
+assert_eq!(v, ["leopard", "tiger", "lionX"]);
+
+let v: Vec<&str> = "lion::tiger::leopard".rsplitn(2, "::").collect();
+assert_eq!(v, ["leopard", "lion::tiger"]);
+

A more complex pattern, using a closure:

+ +
let v: Vec<&str> = "abc1defXghi".rsplitn(2, |c| c == '1' || c == 'X').collect();
+assert_eq!(v, ["ghi", "abc1def"]);
+
1.52.0 · source

pub fn split_once<'a, P>(&'a self, delimiter: P) -> Option<(&'a str, &'a str)>where + P: Pattern<'a>,

Splits the string on the first occurrence of the specified delimiter and +returns prefix before delimiter and suffix after delimiter.

+
Examples
+
assert_eq!("cfg".split_once('='), None);
+assert_eq!("cfg=".split_once('='), Some(("cfg", "")));
+assert_eq!("cfg=foo".split_once('='), Some(("cfg", "foo")));
+assert_eq!("cfg=foo=bar".split_once('='), Some(("cfg", "foo=bar")));
+
1.52.0 · source

pub fn rsplit_once<'a, P>(&'a self, delimiter: P) -> Option<(&'a str, &'a str)>where + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,

Splits the string on the last occurrence of the specified delimiter and +returns prefix before delimiter and suffix after delimiter.

+
Examples
+
assert_eq!("cfg".rsplit_once('='), None);
+assert_eq!("cfg=foo".rsplit_once('='), Some(("cfg", "foo")));
+assert_eq!("cfg=foo=bar".rsplit_once('='), Some(("cfg=foo", "bar")));
+
1.2.0 · source

pub fn matches<'a, P>(&'a self, pat: P) -> Matches<'a, P>where + P: Pattern<'a>,

An iterator over the disjoint matches of a pattern within the given string +slice.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Iterator behavior
+

The returned iterator will be a DoubleEndedIterator if the pattern +allows a reverse search and forward/reverse search yields the same +elements. This is true for, e.g., char, but not for &str.

+

If the pattern allows a reverse search but its results might differ +from a forward search, the rmatches method can be used.

+
Examples
+
let v: Vec<&str> = "abcXXXabcYYYabc".matches("abc").collect();
+assert_eq!(v, ["abc", "abc", "abc"]);
+
+let v: Vec<&str> = "1abc2abc3".matches(char::is_numeric).collect();
+assert_eq!(v, ["1", "2", "3"]);
+
1.2.0 · source

pub fn rmatches<'a, P>(&'a self, pat: P) -> RMatches<'a, P>where + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,

An iterator over the disjoint matches of a pattern within this string slice, +yielded in reverse order.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Iterator behavior
+

The returned iterator requires that the pattern supports a reverse +search, and it will be a DoubleEndedIterator if a forward/reverse +search yields the same elements.

+

For iterating from the front, the matches method can be used.

+
Examples
+
let v: Vec<&str> = "abcXXXabcYYYabc".rmatches("abc").collect();
+assert_eq!(v, ["abc", "abc", "abc"]);
+
+let v: Vec<&str> = "1abc2abc3".rmatches(char::is_numeric).collect();
+assert_eq!(v, ["3", "2", "1"]);
+
1.5.0 · source

pub fn match_indices<'a, P>(&'a self, pat: P) -> MatchIndices<'a, P>where + P: Pattern<'a>,

An iterator over the disjoint matches of a pattern within this string +slice as well as the index that the match starts at.

+

For matches of pat within self that overlap, only the indices +corresponding to the first match are returned.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Iterator behavior
+

The returned iterator will be a DoubleEndedIterator if the pattern +allows a reverse search and forward/reverse search yields the same +elements. This is true for, e.g., char, but not for &str.

+

If the pattern allows a reverse search but its results might differ +from a forward search, the rmatch_indices method can be used.

+
Examples
+
let v: Vec<_> = "abcXXXabcYYYabc".match_indices("abc").collect();
+assert_eq!(v, [(0, "abc"), (6, "abc"), (12, "abc")]);
+
+let v: Vec<_> = "1abcabc2".match_indices("abc").collect();
+assert_eq!(v, [(1, "abc"), (4, "abc")]);
+
+let v: Vec<_> = "ababa".match_indices("aba").collect();
+assert_eq!(v, [(0, "aba")]); // only the first `aba`
+
1.5.0 · source

pub fn rmatch_indices<'a, P>(&'a self, pat: P) -> RMatchIndices<'a, P>where + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,

An iterator over the disjoint matches of a pattern within self, +yielded in reverse order along with the index of the match.

+

For matches of pat within self that overlap, only the indices +corresponding to the last match are returned.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Iterator behavior
+

The returned iterator requires that the pattern supports a reverse +search, and it will be a DoubleEndedIterator if a forward/reverse +search yields the same elements.

+

For iterating from the front, the match_indices method can be used.

+
Examples
+
let v: Vec<_> = "abcXXXabcYYYabc".rmatch_indices("abc").collect();
+assert_eq!(v, [(12, "abc"), (6, "abc"), (0, "abc")]);
+
+let v: Vec<_> = "1abcabc2".rmatch_indices("abc").collect();
+assert_eq!(v, [(4, "abc"), (1, "abc")]);
+
+let v: Vec<_> = "ababa".rmatch_indices("aba").collect();
+assert_eq!(v, [(2, "aba")]); // only the last `aba`
+
1.0.0 · source

pub fn trim(&self) -> &str

Returns a string slice with leading and trailing whitespace removed.

+

‘Whitespace’ is defined according to the terms of the Unicode Derived +Core Property White_Space, which includes newlines.

+
Examples
+
let s = "\n Hello\tworld\t\n";
+
+assert_eq!("Hello\tworld", s.trim());
+
1.30.0 · source

pub fn trim_start(&self) -> &str

Returns a string slice with leading whitespace removed.

+

‘Whitespace’ is defined according to the terms of the Unicode Derived +Core Property White_Space, which includes newlines.

+
Text directionality
+

A string is a sequence of bytes. start in this context means the first +position of that byte string; for a left-to-right language like English or +Russian, this will be left side, and for right-to-left languages like +Arabic or Hebrew, this will be the right side.

+
Examples
+

Basic usage:

+ +
let s = "\n Hello\tworld\t\n";
+assert_eq!("Hello\tworld\t\n", s.trim_start());
+

Directionality:

+ +
let s = "  English  ";
+assert!(Some('E') == s.trim_start().chars().next());
+
+let s = "  עברית  ";
+assert!(Some('ע') == s.trim_start().chars().next());
+
1.30.0 · source

pub fn trim_end(&self) -> &str

Returns a string slice with trailing whitespace removed.

+

‘Whitespace’ is defined according to the terms of the Unicode Derived +Core Property White_Space, which includes newlines.

+
Text directionality
+

A string is a sequence of bytes. end in this context means the last +position of that byte string; for a left-to-right language like English or +Russian, this will be right side, and for right-to-left languages like +Arabic or Hebrew, this will be the left side.

+
Examples
+

Basic usage:

+ +
let s = "\n Hello\tworld\t\n";
+assert_eq!("\n Hello\tworld", s.trim_end());
+

Directionality:

+ +
let s = "  English  ";
+assert!(Some('h') == s.trim_end().chars().rev().next());
+
+let s = "  עברית  ";
+assert!(Some('ת') == s.trim_end().chars().rev().next());
+
1.0.0 · source

pub fn trim_left(&self) -> &str

👎Deprecated since 1.33.0: superseded by trim_start

Returns a string slice with leading whitespace removed.

+

‘Whitespace’ is defined according to the terms of the Unicode Derived +Core Property White_Space.

+
Text directionality
+

A string is a sequence of bytes. ‘Left’ in this context means the first +position of that byte string; for a language like Arabic or Hebrew +which are ‘right to left’ rather than ‘left to right’, this will be +the right side, not the left.

+
Examples
+

Basic usage:

+ +
let s = " Hello\tworld\t";
+
+assert_eq!("Hello\tworld\t", s.trim_left());
+

Directionality:

+ +
let s = "  English";
+assert!(Some('E') == s.trim_left().chars().next());
+
+let s = "  עברית";
+assert!(Some('ע') == s.trim_left().chars().next());
+
1.0.0 · source

pub fn trim_right(&self) -> &str

👎Deprecated since 1.33.0: superseded by trim_end

Returns a string slice with trailing whitespace removed.

+

‘Whitespace’ is defined according to the terms of the Unicode Derived +Core Property White_Space.

+
Text directionality
+

A string is a sequence of bytes. ‘Right’ in this context means the last +position of that byte string; for a language like Arabic or Hebrew +which are ‘right to left’ rather than ‘left to right’, this will be +the left side, not the right.

+
Examples
+

Basic usage:

+ +
let s = " Hello\tworld\t";
+
+assert_eq!(" Hello\tworld", s.trim_right());
+

Directionality:

+ +
let s = "English  ";
+assert!(Some('h') == s.trim_right().chars().rev().next());
+
+let s = "עברית  ";
+assert!(Some('ת') == s.trim_right().chars().rev().next());
+
1.0.0 · source

pub fn trim_matches<'a, P>(&'a self, pat: P) -> &'a strwhere + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: DoubleEndedSearcher<'a>,

Returns a string slice with all prefixes and suffixes that match a +pattern repeatedly removed.

+

The pattern can be a char, a slice of chars, or a function +or closure that determines if a character matches.

+
Examples
+

Simple patterns:

+ +
assert_eq!("11foo1bar11".trim_matches('1'), "foo1bar");
+assert_eq!("123foo1bar123".trim_matches(char::is_numeric), "foo1bar");
+
+let x: &[_] = &['1', '2'];
+assert_eq!("12foo1bar12".trim_matches(x), "foo1bar");
+

A more complex pattern, using a closure:

+ +
assert_eq!("1foo1barXX".trim_matches(|c| c == '1' || c == 'X'), "foo1bar");
+
1.30.0 · source

pub fn trim_start_matches<'a, P>(&'a self, pat: P) -> &'a strwhere + P: Pattern<'a>,

Returns a string slice with all prefixes that match a pattern +repeatedly removed.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Text directionality
+

A string is a sequence of bytes. start in this context means the first +position of that byte string; for a left-to-right language like English or +Russian, this will be left side, and for right-to-left languages like +Arabic or Hebrew, this will be the right side.

+
Examples
+
assert_eq!("11foo1bar11".trim_start_matches('1'), "foo1bar11");
+assert_eq!("123foo1bar123".trim_start_matches(char::is_numeric), "foo1bar123");
+
+let x: &[_] = &['1', '2'];
+assert_eq!("12foo1bar12".trim_start_matches(x), "foo1bar12");
+
1.45.0 · source

pub fn strip_prefix<'a, P>(&'a self, prefix: P) -> Option<&'a str>where + P: Pattern<'a>,

Returns a string slice with the prefix removed.

+

If the string starts with the pattern prefix, returns substring after the prefix, wrapped +in Some. Unlike trim_start_matches, this method removes the prefix exactly once.

+

If the string does not start with prefix, returns None.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Examples
+
assert_eq!("foo:bar".strip_prefix("foo:"), Some("bar"));
+assert_eq!("foo:bar".strip_prefix("bar"), None);
+assert_eq!("foofoo".strip_prefix("foo"), Some("foo"));
+
1.45.0 · source

pub fn strip_suffix<'a, P>(&'a self, suffix: P) -> Option<&'a str>where + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,

Returns a string slice with the suffix removed.

+

If the string ends with the pattern suffix, returns the substring before the suffix, +wrapped in Some. Unlike trim_end_matches, this method removes the suffix exactly once.

+

If the string does not end with suffix, returns None.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Examples
+
assert_eq!("bar:foo".strip_suffix(":foo"), Some("bar"));
+assert_eq!("bar:foo".strip_suffix("bar"), None);
+assert_eq!("foofoo".strip_suffix("foo"), Some("foo"));
+
1.30.0 · source

pub fn trim_end_matches<'a, P>(&'a self, pat: P) -> &'a strwhere + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,

Returns a string slice with all suffixes that match a pattern +repeatedly removed.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Text directionality
+

A string is a sequence of bytes. end in this context means the last +position of that byte string; for a left-to-right language like English or +Russian, this will be right side, and for right-to-left languages like +Arabic or Hebrew, this will be the left side.

+
Examples
+

Simple patterns:

+ +
assert_eq!("11foo1bar11".trim_end_matches('1'), "11foo1bar");
+assert_eq!("123foo1bar123".trim_end_matches(char::is_numeric), "123foo1bar");
+
+let x: &[_] = &['1', '2'];
+assert_eq!("12foo1bar12".trim_end_matches(x), "12foo1bar");
+

A more complex pattern, using a closure:

+ +
assert_eq!("1fooX".trim_end_matches(|c| c == '1' || c == 'X'), "1foo");
+
1.0.0 · source

pub fn trim_left_matches<'a, P>(&'a self, pat: P) -> &'a strwhere + P: Pattern<'a>,

👎Deprecated since 1.33.0: superseded by trim_start_matches

Returns a string slice with all prefixes that match a pattern +repeatedly removed.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Text directionality
+

A string is a sequence of bytes. ‘Left’ in this context means the first +position of that byte string; for a language like Arabic or Hebrew +which are ‘right to left’ rather than ‘left to right’, this will be +the right side, not the left.

+
Examples
+
assert_eq!("11foo1bar11".trim_left_matches('1'), "foo1bar11");
+assert_eq!("123foo1bar123".trim_left_matches(char::is_numeric), "foo1bar123");
+
+let x: &[_] = &['1', '2'];
+assert_eq!("12foo1bar12".trim_left_matches(x), "foo1bar12");
+
1.0.0 · source

pub fn trim_right_matches<'a, P>(&'a self, pat: P) -> &'a strwhere + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,

👎Deprecated since 1.33.0: superseded by trim_end_matches

Returns a string slice with all suffixes that match a pattern +repeatedly removed.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Text directionality
+

A string is a sequence of bytes. ‘Right’ in this context means the last +position of that byte string; for a language like Arabic or Hebrew +which are ‘right to left’ rather than ‘left to right’, this will be +the left side, not the right.

+
Examples
+

Simple patterns:

+ +
assert_eq!("11foo1bar11".trim_right_matches('1'), "11foo1bar");
+assert_eq!("123foo1bar123".trim_right_matches(char::is_numeric), "123foo1bar");
+
+let x: &[_] = &['1', '2'];
+assert_eq!("12foo1bar12".trim_right_matches(x), "12foo1bar");
+

A more complex pattern, using a closure:

+ +
assert_eq!("1fooX".trim_right_matches(|c| c == '1' || c == 'X'), "1foo");
+
1.0.0 · source

pub fn parse<F>(&self) -> Result<F, <F as FromStr>::Err>where + F: FromStr,

Parses this string slice into another type.

+

Because parse is so general, it can cause problems with type +inference. As such, parse is one of the few times you’ll see +the syntax affectionately known as the ‘turbofish’: ::<>. This +helps the inference algorithm understand specifically which type +you’re trying to parse into.

+

parse can parse into any type that implements the FromStr trait.

+
Errors
+

Will return Err if it’s not possible to parse this string slice into +the desired type.

+
Examples
+

Basic usage

+ +
let four: u32 = "4".parse().unwrap();
+
+assert_eq!(4, four);
+

Using the ‘turbofish’ instead of annotating four:

+ +
let four = "4".parse::<u32>();
+
+assert_eq!(Ok(4), four);
+

Failing to parse:

+ +
let nope = "j".parse::<u32>();
+
+assert!(nope.is_err());
+
1.23.0 · source

pub fn is_ascii(&self) -> bool

Checks if all characters in this string are within the ASCII range.

+
Examples
+
let ascii = "hello!\n";
+let non_ascii = "Grüße, Jürgen ❤";
+
+assert!(ascii.is_ascii());
+assert!(!non_ascii.is_ascii());
+
source

pub fn as_ascii(&self) -> Option<&[AsciiChar]>

🔬This is a nightly-only experimental API. (ascii_char)

If this string slice is_ascii, returns it as a slice +of ASCII characters, otherwise returns None.

+
1.23.0 · source

pub fn eq_ignore_ascii_case(&self, other: &str) -> bool

Checks that two strings are an ASCII case-insensitive match.

+

Same as to_ascii_lowercase(a) == to_ascii_lowercase(b), +but without allocating and copying temporaries.

+
Examples
+
assert!("Ferris".eq_ignore_ascii_case("FERRIS"));
+assert!("Ferrös".eq_ignore_ascii_case("FERRöS"));
+assert!(!"Ferrös".eq_ignore_ascii_case("FERRÖS"));
+
1.34.0 · source

pub fn escape_debug(&self) -> EscapeDebug<'_>

Return an iterator that escapes each char in self with char::escape_debug.

+

Note: only extended grapheme codepoints that begin the string will be +escaped.

+
Examples
+

As an iterator:

+ +
for c in "❤\n!".escape_debug() {
+    print!("{c}");
+}
+println!();
+

Using println! directly:

+ +
println!("{}", "❤\n!".escape_debug());
+

Both are equivalent to:

+ +
println!("❤\\n!");
+

Using to_string:

+ +
assert_eq!("❤\n!".escape_debug().to_string(), "❤\\n!");
+
1.34.0 · source

pub fn escape_default(&self) -> EscapeDefault<'_>

Return an iterator that escapes each char in self with char::escape_default.

+
Examples
+

As an iterator:

+ +
for c in "❤\n!".escape_default() {
+    print!("{c}");
+}
+println!();
+

Using println! directly:

+ +
println!("{}", "❤\n!".escape_default());
+

Both are equivalent to:

+ +
println!("\\u{{2764}}\\n!");
+

Using to_string:

+ +
assert_eq!("❤\n!".escape_default().to_string(), "\\u{2764}\\n!");
+
1.34.0 · source

pub fn escape_unicode(&self) -> EscapeUnicode<'_>

Return an iterator that escapes each char in self with char::escape_unicode.

+
Examples
+

As an iterator:

+ +
for c in "❤\n!".escape_unicode() {
+    print!("{c}");
+}
+println!();
+

Using println! directly:

+ +
println!("{}", "❤\n!".escape_unicode());
+

Both are equivalent to:

+ +
println!("\\u{{2764}}\\u{{a}}\\u{{21}}");
+

Using to_string:

+ +
assert_eq!("❤\n!".escape_unicode().to_string(), "\\u{2764}\\u{a}\\u{21}");
+
1.0.0 · source

pub fn replace<'a, P>(&'a self, from: P, to: &str) -> Stringwhere + P: Pattern<'a>,

Replaces all matches of a pattern with another string.

+

replace creates a new String, and copies the data from this string slice into it. +While doing so, it attempts to find matches of a pattern. If it finds any, it +replaces them with the replacement string slice.

+
Examples
+

Basic usage:

+ +
let s = "this is old";
+
+assert_eq!("this is new", s.replace("old", "new"));
+assert_eq!("than an old", s.replace("is", "an"));
+

When the pattern doesn’t match, it returns this string slice as String:

+ +
let s = "this is old";
+assert_eq!(s, s.replace("cookie monster", "little lamb"));
+
1.16.0 · source

pub fn replacen<'a, P>(&'a self, pat: P, to: &str, count: usize) -> Stringwhere + P: Pattern<'a>,

Replaces first N matches of a pattern with another string.

+

replacen creates a new String, and copies the data from this string slice into it. +While doing so, it attempts to find matches of a pattern. If it finds any, it +replaces them with the replacement string slice at most count times.

+
Examples
+

Basic usage:

+ +
let s = "foo foo 123 foo";
+assert_eq!("new new 123 foo", s.replacen("foo", "new", 2));
+assert_eq!("faa fao 123 foo", s.replacen('o', "a", 3));
+assert_eq!("foo foo new23 foo", s.replacen(char::is_numeric, "new", 1));
+

When the pattern doesn’t match, it returns this string slice as String:

+ +
let s = "this is old";
+assert_eq!(s, s.replacen("cookie monster", "little lamb", 10));
+
1.2.0 · source

pub fn to_lowercase(&self) -> String

Returns the lowercase equivalent of this string slice, as a new String.

+

‘Lowercase’ is defined according to the terms of the Unicode Derived Core Property +Lowercase.

+

Since some characters can expand into multiple characters when changing +the case, this function returns a String instead of modifying the +parameter in-place.

+
Examples
+

Basic usage:

+ +
let s = "HELLO";
+
+assert_eq!("hello", s.to_lowercase());
+

A tricky example, with sigma:

+ +
let sigma = "Σ";
+
+assert_eq!("σ", sigma.to_lowercase());
+
+// but at the end of a word, it's ς, not σ:
+let odysseus = "ὈΔΥΣΣΕΎΣ";
+
+assert_eq!("ὀδυσσεύς", odysseus.to_lowercase());
+

Languages without case are not changed:

+ +
let new_year = "农历新年";
+
+assert_eq!(new_year, new_year.to_lowercase());
+
1.2.0 · source

pub fn to_uppercase(&self) -> String

Returns the uppercase equivalent of this string slice, as a new String.

+

‘Uppercase’ is defined according to the terms of the Unicode Derived Core Property +Uppercase.

+

Since some characters can expand into multiple characters when changing +the case, this function returns a String instead of modifying the +parameter in-place.

+
Examples
+

Basic usage:

+ +
let s = "hello";
+
+assert_eq!("HELLO", s.to_uppercase());
+

Scripts without case are not changed:

+ +
let new_year = "农历新年";
+
+assert_eq!(new_year, new_year.to_uppercase());
+

One character can become multiple:

+ +
let s = "tschüß";
+
+assert_eq!("TSCHÜSS", s.to_uppercase());
+
1.16.0 · source

pub fn repeat(&self, n: usize) -> String

Creates a new String by repeating a string n times.

+
Panics
+

This function will panic if the capacity would overflow.

+
Examples
+

Basic usage:

+ +
assert_eq!("abc".repeat(4), String::from("abcabcabcabc"));
+

A panic upon overflow:

+ +
// this will panic at runtime
+let huge = "0123456789abcdef".repeat(usize::MAX);
+
1.23.0 · source

pub fn to_ascii_uppercase(&self) -> String

Returns a copy of this string where each character is mapped to its +ASCII upper case equivalent.

+

ASCII letters ‘a’ to ‘z’ are mapped to ‘A’ to ‘Z’, +but non-ASCII letters are unchanged.

+

To uppercase the value in-place, use make_ascii_uppercase.

+

To uppercase ASCII characters in addition to non-ASCII characters, use +to_uppercase.

+
Examples
+
let s = "Grüße, Jürgen ❤";
+
+assert_eq!("GRüßE, JüRGEN ❤", s.to_ascii_uppercase());
+
1.23.0 · source

pub fn to_ascii_lowercase(&self) -> String

Returns a copy of this string where each character is mapped to its +ASCII lower case equivalent.

+

ASCII letters ‘A’ to ‘Z’ are mapped to ‘a’ to ‘z’, +but non-ASCII letters are unchanged.

+

To lowercase the value in-place, use make_ascii_lowercase.

+

To lowercase ASCII characters in addition to non-ASCII characters, use +to_lowercase.

+
Examples
+
let s = "Grüße, Jürgen ❤";
+
+assert_eq!("grüße, jürgen ❤", s.to_ascii_lowercase());
+

Trait Implementations§

source§

impl AsRef<[u8]> for Guid

source§

fn as_ref(&self) -> &[u8]

Converts this type into a shared reference of the (usually inferred) input type.
source§

impl AsRef<str> for Guid

source§

fn as_ref(&self) -> &str

Converts this type into a shared reference of the (usually inferred) input type.
source§

impl Clone for Guid

source§

fn clone(&self) -> Guid

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for Guid

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for Guid

source§

fn default() -> Self

Create a default guid by calling Guid::empty()

+
source§

impl Deref for Guid

§

type Target = str

The resulting type after dereferencing.
source§

fn deref(&self) -> &str

Dereferences the value.
source§

impl<'de> Deserialize<'de> for Guid

source§

fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>where + D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl Display for Guid

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl<'a> From<&'a &str> for Guid

source§

fn from(s: &'a &str) -> Guid

Converts to this type from the input type.
source§

impl<'a> From<&'a [u8]> for Guid

source§

fn from(s: &'a [u8]) -> Guid

Converts to this type from the input type.
source§

impl<'a> From<&'a str> for Guid

source§

fn from(s: &'a str) -> Guid

Converts to this type from the input type.
source§

impl From<Guid> for String

source§

fn from(guid: Guid) -> String

Converts to this type from the input type.
source§

impl From<Guid> for Vec<u8>

source§

fn from(guid: Guid) -> Vec<u8>

Converts to this type from the input type.
source§

impl From<String> for Guid

source§

fn from(s: String) -> Guid

Converts to this type from the input type.
source§

impl From<Vec<u8, Global>> for Guid

source§

fn from(v: Vec<u8>) -> Guid

Converts to this type from the input type.
source§

impl FromSql for Guid

source§

fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self>

Converts SQLite value into Rust value.
source§

impl Hash for Guid

source§

fn hash<H: Hasher>(&self, state: &mut H)

Feeds this value into the given Hasher. Read more
1.3.0 · source§

fn hash_slice<H>(data: &[Self], state: &mut H)where + H: Hasher, + Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
source§

impl Ord for Guid

source§

fn cmp(&self, other: &Self) -> Ordering

This method returns an Ordering between self and other. Read more
1.21.0 · source§

fn max(self, other: Self) -> Selfwhere + Self: Sized,

Compares and returns the maximum of two values. Read more
1.21.0 · source§

fn min(self, other: Self) -> Selfwhere + Self: Sized,

Compares and returns the minimum of two values. Read more
1.50.0 · source§

fn clamp(self, min: Self, max: Self) -> Selfwhere + Self: Sized + PartialOrd<Self>,

Restrict a value to a certain interval. Read more
source§

impl<'a> PartialEq<&'a [u8]> for Guid

source§

fn eq(&self, other: &&'a [u8]) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl<'a> PartialEq<&'a str> for Guid

source§

fn eq(&self, other: &&'a str) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl<'a> PartialEq<[u8]> for Guid

source§

fn eq(&self, other: &[u8]) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl<'a> PartialEq<Guid> for &'a [u8]

source§

fn eq(&self, other: &Guid) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl<'a> PartialEq<Guid> for &'a str

source§

fn eq(&self, other: &Guid) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl<'a> PartialEq<Guid> for [u8]

source§

fn eq(&self, other: &Guid) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl PartialEq<Guid> for Guid

source§

fn eq(&self, other: &Self) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl<'a> PartialEq<Guid> for String

source§

fn eq(&self, other: &Guid) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl<'a> PartialEq<Guid> for Vec<u8>

source§

fn eq(&self, other: &Guid) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl<'a> PartialEq<Guid> for str

source§

fn eq(&self, other: &Guid) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl<'a> PartialEq<String> for Guid

source§

fn eq(&self, other: &String) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl<'a> PartialEq<Vec<u8, Global>> for Guid

source§

fn eq(&self, other: &Vec<u8>) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl<'a> PartialEq<str> for Guid

source§

fn eq(&self, other: &str) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl PartialOrd<Guid> for Guid

source§

fn partial_cmp(&self, other: &Self) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · source§

fn lt(&self, other: &Rhs) -> bool

This method tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · source§

fn le(&self, other: &Rhs) -> bool

This method tests less than or equal to (for self and other) and is used by the <= +operator. Read more
1.0.0 · source§

fn gt(&self, other: &Rhs) -> bool

This method tests greater than (for self and other) and is used by the > operator. Read more
1.0.0 · source§

fn ge(&self, other: &Rhs) -> bool

This method tests greater than or equal to (for self and other) and is used by the >= +operator. Read more
source§

impl Serialize for Guid

source§

fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error>

Serialize this value into the given Serde serializer. Read more
source§

impl ToSql for Guid

source§

fn to_sql(&self) -> Result<ToSqlOutput<'_>>

Converts Rust value to SQLite value
source§

impl Eq for Guid

Auto Trait Implementations§

§

impl RefUnwindSafe for Guid

§

impl Send for Guid

§

impl Sync for Guid

§

impl Unpin for Guid

§

impl UnwindSafe for Guid

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> CallHasher for Twhere + T: Hash + ?Sized,

§

fn get_hash<H, B>(value: &H, build_hasher: &B) -> u64where + H: Hash + ?Sized, + B: BuildHasher,

source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/sync_manager/all.html b/book/rust-docs/sync_manager/all.html new file mode 100644 index 0000000000..b35a81c2f1 --- /dev/null +++ b/book/rust-docs/sync_manager/all.html @@ -0,0 +1 @@ +List of all items in this crate
\ No newline at end of file diff --git a/book/rust-docs/sync_manager/enum.DeviceType.html b/book/rust-docs/sync_manager/enum.DeviceType.html new file mode 100644 index 0000000000..e22e0daccf --- /dev/null +++ b/book/rust-docs/sync_manager/enum.DeviceType.html @@ -0,0 +1,59 @@ +DeviceType in sync_manager - Rust
pub enum DeviceType {
+    Desktop,
+    Mobile,
+    Tablet,
+    VR,
+    TV,
+    Unknown,
+}
Expand description

Enumeration for the different types of device.

+

Firefox Accounts and the broader Sync universe separates devices into broad categories for +various purposes, such as distinguishing a desktop PC from a mobile phone.

+

A special variant in this enum, DeviceType::Unknown is used to capture +the string values we don’t recognise. It also has a custom serde serializer and deserializer +which implements the following semantics:

+
    +
  • deserializing a DeviceType which uses a string value we don’t recognise or null will return +DeviceType::Unknown rather than returning an error.
  • +
  • serializing DeviceType::Unknown will serialize null.
  • +
+

This has a few important implications:

+
    +
  • In general, Option<DeviceType> should be avoided, and a plain DeviceType used instead, +because in that case, None would be semantically identical to DeviceType::Unknown and +as mentioned above, null already deserializes as DeviceType::Unknown.
  • +
  • Any unknown device types can not be round-tripped via this enum - eg, if you deserialize +a struct holding a DeviceType string value we don’t recognize, then re-serialize it, the +original string value is lost. We don’t consider this a problem because in practice, we only +upload records with this device’s type, not the type of other devices, and it’s reasonable +to assume that this module knows about all valid device types for the device type it is +deployed on.
  • +
+

Variants§

§

Desktop

§

Mobile

§

Tablet

§

VR

§

TV

§

Unknown

Trait Implementations§

§

impl Clone for DeviceType

§

fn clone(&self) -> DeviceType

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
§

impl Debug for DeviceType

§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
§

impl Default for DeviceType

§

fn default() -> DeviceType

Returns the “default value” for a type. Read more
§

impl<'de> Deserialize<'de> for DeviceType

§

fn deserialize<D>( + deserializer: D +) -> Result<DeviceType, <D as Deserializer<'de>>::Error>where + D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
§

impl Hash for DeviceType

§

fn hash<__H>(&self, state: &mut __H)where + __H: Hasher,

Feeds this value into the given Hasher. Read more
1.3.0 · source§

fn hash_slice<H>(data: &[Self], state: &mut H)where + H: Hasher, + Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
§

impl PartialEq<DeviceType> for DeviceType

§

fn eq(&self, other: &DeviceType) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
§

impl Serialize for DeviceType

§

fn serialize<S>( + &self, + s: S +) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>where + S: Serializer,

Serialize this value into the given Serde serializer. Read more
§

impl Copy for DeviceType

§

impl Eq for DeviceType

§

impl StructuralEq for DeviceType

§

impl StructuralPartialEq for DeviceType

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> CallHasher for Twhere + T: Hash + ?Sized,

§

fn get_hash<H, B>(value: &H, build_hasher: &B) -> u64where + H: Hash + ?Sized, + B: BuildHasher,

source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/sync_manager/enum.ServiceStatus.html b/book/rust-docs/sync_manager/enum.ServiceStatus.html new file mode 100644 index 0000000000..1fcaae24a5 --- /dev/null +++ b/book/rust-docs/sync_manager/enum.ServiceStatus.html @@ -0,0 +1,19 @@ +ServiceStatus in sync_manager - Rust
pub enum ServiceStatus {
+    Ok,
+    NetworkError,
+    ServiceError,
+    AuthError,
+    BackedOff,
+    OtherError,
+}

Variants§

§

Ok

§

NetworkError

§

ServiceError

§

AuthError

§

BackedOff

§

OtherError

Implementations§

Trait Implementations§

source§

impl Debug for ServiceStatus

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl From<ServiceStatus> for ServiceStatus

source§

fn from(s15s: ServiceStatus) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/sync_manager/enum.SyncEngineSelection.html b/book/rust-docs/sync_manager/enum.SyncEngineSelection.html new file mode 100644 index 0000000000..2554ef40de --- /dev/null +++ b/book/rust-docs/sync_manager/enum.SyncEngineSelection.html @@ -0,0 +1,17 @@ +SyncEngineSelection in sync_manager - Rust
pub enum SyncEngineSelection {
+    All,
+    Some {
+        engines: Vec<String>,
+    },
+}

Variants§

§

All

§

Some

Fields

§engines: Vec<String>

Trait Implementations§

source§

impl Debug for SyncEngineSelection

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/sync_manager/enum.SyncReason.html b/book/rust-docs/sync_manager/enum.SyncReason.html new file mode 100644 index 0000000000..9789082e4a --- /dev/null +++ b/book/rust-docs/sync_manager/enum.SyncReason.html @@ -0,0 +1,19 @@ +SyncReason in sync_manager - Rust
pub enum SyncReason {
+    Scheduled,
+    User,
+    PreSleep,
+    Startup,
+    EnabledChange,
+    Backgrounded,
+}

Variants§

§

Scheduled

§

User

§

PreSleep

§

Startup

§

EnabledChange

§

Backgrounded

Trait Implementations§

source§

impl Debug for SyncReason

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/sync_manager/error/enum.SyncManagerError.html b/book/rust-docs/sync_manager/error/enum.SyncManagerError.html new file mode 100644 index 0000000000..22b0e07462 --- /dev/null +++ b/book/rust-docs/sync_manager/error/enum.SyncManagerError.html @@ -0,0 +1,25 @@ +SyncManagerError in sync_manager::error - Rust
pub enum SyncManagerError {
+    UnknownEngine(String),
+    UnsupportedFeature(String),
+    Sync15Error(Error),
+    UrlParseError(ParseError),
+    InterruptedError(Interrupted),
+    JsonError(Error),
+    LoginsError(Error),
+    PlacesError(Error),
+    AnyhowError(Error),
+}

Variants§

§

UnknownEngine(String)

§

UnsupportedFeature(String)

§

Sync15Error(Error)

§

UrlParseError(ParseError)

§

InterruptedError(Interrupted)

§

JsonError(Error)

§

LoginsError(Error)

§

PlacesError(Error)

§

AnyhowError(Error)

Trait Implementations§

source§

impl Debug for SyncManagerError

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Display for SyncManagerError

source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Error for SyncManagerError

source§

fn source(&self) -> Option<&(dyn Error + 'static)>

The lower-level source of this error, if any. Read more
1.0.0 · source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type based access to context intended for error reports. Read more
source§

impl From<Error> for SyncManagerError

source§

fn from(source: Error) -> Self

Converts to this type from the input type.
source§

impl From<Error> for SyncManagerError

source§

fn from(source: Error) -> Self

Converts to this type from the input type.
source§

impl From<Error> for SyncManagerError

source§

fn from(source: Error) -> Self

Converts to this type from the input type.
source§

impl From<Error> for SyncManagerError

source§

fn from(source: Error) -> Self

Converts to this type from the input type.
source§

impl From<Error> for SyncManagerError

source§

fn from(source: Error) -> Self

Converts to this type from the input type.
source§

impl From<Interrupted> for SyncManagerError

source§

fn from(source: Interrupted) -> Self

Converts to this type from the input type.
source§

impl From<ParseError> for SyncManagerError

source§

fn from(source: ParseError) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<E> Provider for Ewhere + E: Error + ?Sized,

source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (provide_any)
Data providers should implement this method to provide all values they are able to +provide by using demand. Read more
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/sync_manager/error/index.html b/book/rust-docs/sync_manager/error/index.html new file mode 100644 index 0000000000..a5b7134e09 --- /dev/null +++ b/book/rust-docs/sync_manager/error/index.html @@ -0,0 +1 @@ +sync_manager::error - Rust
\ No newline at end of file diff --git a/book/rust-docs/sync_manager/error/sidebar-items.js b/book/rust-docs/sync_manager/error/sidebar-items.js new file mode 100644 index 0000000000..7e7b17e22f --- /dev/null +++ b/book/rust-docs/sync_manager/error/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["SyncManagerError"],"type":["Result"]}; \ No newline at end of file diff --git a/book/rust-docs/sync_manager/error/type.Result.html b/book/rust-docs/sync_manager/error/type.Result.html new file mode 100644 index 0000000000..b123dad446 --- /dev/null +++ b/book/rust-docs/sync_manager/error/type.Result.html @@ -0,0 +1 @@ +Result in sync_manager::error - Rust

Type Definition sync_manager::error::Result

source ·
pub type Result<T> = Result<T, SyncManagerError>;
\ No newline at end of file diff --git a/book/rust-docs/sync_manager/fn.disconnect.html b/book/rust-docs/sync_manager/fn.disconnect.html new file mode 100644 index 0000000000..9ed4ae6877 --- /dev/null +++ b/book/rust-docs/sync_manager/fn.disconnect.html @@ -0,0 +1 @@ +disconnect in sync_manager - Rust

Function sync_manager::disconnect

source ·
pub fn disconnect()
\ No newline at end of file diff --git a/book/rust-docs/sync_manager/fn.reset.html b/book/rust-docs/sync_manager/fn.reset.html new file mode 100644 index 0000000000..267f46d5aa --- /dev/null +++ b/book/rust-docs/sync_manager/fn.reset.html @@ -0,0 +1 @@ +reset in sync_manager - Rust

Function sync_manager::reset

source ·
pub fn reset(engine: &str) -> Result<()>
\ No newline at end of file diff --git a/book/rust-docs/sync_manager/fn.reset_all.html b/book/rust-docs/sync_manager/fn.reset_all.html new file mode 100644 index 0000000000..84ed70473b --- /dev/null +++ b/book/rust-docs/sync_manager/fn.reset_all.html @@ -0,0 +1 @@ +reset_all in sync_manager - Rust

Function sync_manager::reset_all

source ·
pub fn reset_all() -> Result<()>
\ No newline at end of file diff --git a/book/rust-docs/sync_manager/fn.sync.html b/book/rust-docs/sync_manager/fn.sync.html new file mode 100644 index 0000000000..d8af47044f --- /dev/null +++ b/book/rust-docs/sync_manager/fn.sync.html @@ -0,0 +1 @@ +sync in sync_manager - Rust

Function sync_manager::sync

source ·
pub fn sync(params: SyncParams) -> Result<SyncResult>
\ No newline at end of file diff --git a/book/rust-docs/sync_manager/fn.wipe.html b/book/rust-docs/sync_manager/fn.wipe.html new file mode 100644 index 0000000000..97609b66b8 --- /dev/null +++ b/book/rust-docs/sync_manager/fn.wipe.html @@ -0,0 +1 @@ +wipe in sync_manager - Rust

Function sync_manager::wipe

source ·
pub fn wipe(engine: &str) -> Result<()>
\ No newline at end of file diff --git a/book/rust-docs/sync_manager/index.html b/book/rust-docs/sync_manager/index.html new file mode 100644 index 0000000000..a76f266f84 --- /dev/null +++ b/book/rust-docs/sync_manager/index.html @@ -0,0 +1 @@ +sync_manager - Rust
\ No newline at end of file diff --git a/book/rust-docs/sync_manager/manager/index.html b/book/rust-docs/sync_manager/manager/index.html new file mode 100644 index 0000000000..a3b6fcc85c --- /dev/null +++ b/book/rust-docs/sync_manager/manager/index.html @@ -0,0 +1 @@ +sync_manager::manager - Rust
\ No newline at end of file diff --git a/book/rust-docs/sync_manager/manager/sidebar-items.js b/book/rust-docs/sync_manager/manager/sidebar-items.js new file mode 100644 index 0000000000..152a73da19 --- /dev/null +++ b/book/rust-docs/sync_manager/manager/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"struct":["SyncManager"]}; \ No newline at end of file diff --git a/book/rust-docs/sync_manager/manager/struct.SyncManager.html b/book/rust-docs/sync_manager/manager/struct.SyncManager.html new file mode 100644 index 0000000000..8c0b05454b --- /dev/null +++ b/book/rust-docs/sync_manager/manager/struct.SyncManager.html @@ -0,0 +1,14 @@ +SyncManager in sync_manager::manager - Rust
pub struct SyncManager { /* private fields */ }

Implementations§

source§

impl SyncManager

source

pub fn new() -> Self

source

pub fn wipe(&self, engine_name: &str) -> Result<()>

source

pub fn reset(&self, engine_name: &str) -> Result<()>

source

pub fn reset_all(&self) -> Result<()>

source

pub fn disconnect(&self)

Disconnect engines from sync, deleting/resetting the sync-related data

+
source

pub fn sync(&self, params: SyncParams) -> Result<SyncResult>

Perform a sync. See SyncParams and SyncResult for details on how this works

+
source

pub fn get_available_engines(&self) -> Vec<String>

Trait Implementations§

source§

impl Default for SyncManager

source§

fn default() -> SyncManager

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/sync_manager/sidebar-items.js b/book/rust-docs/sync_manager/sidebar-items.js new file mode 100644 index 0000000000..4c4ac98c86 --- /dev/null +++ b/book/rust-docs/sync_manager/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["DeviceType","ServiceStatus","SyncEngineSelection","SyncReason"],"fn":["disconnect","reset","reset_all","sync","wipe"],"mod":["error","manager"],"struct":["DeviceSettings","SyncAuthInfo","SyncParams","SyncResult"]}; \ No newline at end of file diff --git a/book/rust-docs/sync_manager/struct.DeviceSettings.html b/book/rust-docs/sync_manager/struct.DeviceSettings.html new file mode 100644 index 0000000000..c0d8a6e244 --- /dev/null +++ b/book/rust-docs/sync_manager/struct.DeviceSettings.html @@ -0,0 +1,16 @@ +DeviceSettings in sync_manager - Rust
pub struct DeviceSettings {
+    pub fxa_device_id: String,
+    pub name: String,
+    pub kind: DeviceType,
+}

Fields§

§fxa_device_id: String§name: String§kind: DeviceType

Trait Implementations§

source§

impl Debug for DeviceSettings

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/sync_manager/struct.SyncAuthInfo.html b/book/rust-docs/sync_manager/struct.SyncAuthInfo.html new file mode 100644 index 0000000000..a2a4277c91 --- /dev/null +++ b/book/rust-docs/sync_manager/struct.SyncAuthInfo.html @@ -0,0 +1,17 @@ +SyncAuthInfo in sync_manager - Rust
pub struct SyncAuthInfo {
+    pub kid: String,
+    pub fxa_access_token: String,
+    pub sync_key: String,
+    pub tokenserver_url: String,
+}

Fields§

§kid: String§fxa_access_token: String§sync_key: String§tokenserver_url: String

Trait Implementations§

source§

impl Debug for SyncAuthInfo

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/sync_manager/struct.SyncParams.html b/book/rust-docs/sync_manager/struct.SyncParams.html new file mode 100644 index 0000000000..d001ce085b --- /dev/null +++ b/book/rust-docs/sync_manager/struct.SyncParams.html @@ -0,0 +1,20 @@ +SyncParams in sync_manager - Rust
pub struct SyncParams {
+    pub reason: SyncReason,
+    pub engines: SyncEngineSelection,
+    pub enabled_changes: HashMap<String, bool>,
+    pub local_encryption_keys: HashMap<String, String>,
+    pub auth_info: SyncAuthInfo,
+    pub persisted_state: Option<String>,
+    pub device_settings: DeviceSettings,
+}

Fields§

§reason: SyncReason§engines: SyncEngineSelection§enabled_changes: HashMap<String, bool>§local_encryption_keys: HashMap<String, String>§auth_info: SyncAuthInfo§persisted_state: Option<String>§device_settings: DeviceSettings

Trait Implementations§

source§

impl Debug for SyncParams

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/sync_manager/struct.SyncResult.html b/book/rust-docs/sync_manager/struct.SyncResult.html new file mode 100644 index 0000000000..57a49c9c33 --- /dev/null +++ b/book/rust-docs/sync_manager/struct.SyncResult.html @@ -0,0 +1,20 @@ +SyncResult in sync_manager - Rust
pub struct SyncResult {
+    pub status: ServiceStatus,
+    pub successful: Vec<String>,
+    pub failures: HashMap<String, String>,
+    pub persisted_state: String,
+    pub declined: Option<Vec<String>>,
+    pub next_sync_allowed_at: Option<SystemTime>,
+    pub telemetry_json: Option<String>,
+}

Fields§

§status: ServiceStatus§successful: Vec<String>§failures: HashMap<String, String>§persisted_state: String§declined: Option<Vec<String>>§next_sync_allowed_at: Option<SystemTime>§telemetry_json: Option<String>

Trait Implementations§

source§

impl Debug for SyncResult

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/sync_manager/types/enum.ServiceStatus.html b/book/rust-docs/sync_manager/types/enum.ServiceStatus.html new file mode 100644 index 0000000000..7b2239cbbf --- /dev/null +++ b/book/rust-docs/sync_manager/types/enum.ServiceStatus.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../sync_manager/enum.ServiceStatus.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/sync_manager/types/enum.SyncEngineSelection.html b/book/rust-docs/sync_manager/types/enum.SyncEngineSelection.html new file mode 100644 index 0000000000..b04865f4f4 --- /dev/null +++ b/book/rust-docs/sync_manager/types/enum.SyncEngineSelection.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../sync_manager/enum.SyncEngineSelection.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/sync_manager/types/enum.SyncReason.html b/book/rust-docs/sync_manager/types/enum.SyncReason.html new file mode 100644 index 0000000000..34b375dbf8 --- /dev/null +++ b/book/rust-docs/sync_manager/types/enum.SyncReason.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../sync_manager/enum.SyncReason.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/sync_manager/types/struct.DeviceSettings.html b/book/rust-docs/sync_manager/types/struct.DeviceSettings.html new file mode 100644 index 0000000000..5b25e830cf --- /dev/null +++ b/book/rust-docs/sync_manager/types/struct.DeviceSettings.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../sync_manager/struct.DeviceSettings.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/sync_manager/types/struct.SyncAuthInfo.html b/book/rust-docs/sync_manager/types/struct.SyncAuthInfo.html new file mode 100644 index 0000000000..244d79f217 --- /dev/null +++ b/book/rust-docs/sync_manager/types/struct.SyncAuthInfo.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../sync_manager/struct.SyncAuthInfo.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/sync_manager/types/struct.SyncParams.html b/book/rust-docs/sync_manager/types/struct.SyncParams.html new file mode 100644 index 0000000000..0b8f12a95f --- /dev/null +++ b/book/rust-docs/sync_manager/types/struct.SyncParams.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../sync_manager/struct.SyncParams.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/sync_manager/types/struct.SyncResult.html b/book/rust-docs/sync_manager/types/struct.SyncResult.html new file mode 100644 index 0000000000..8e90f05c97 --- /dev/null +++ b/book/rust-docs/sync_manager/types/struct.SyncResult.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../sync_manager/struct.SyncResult.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/tabs/all.html b/book/rust-docs/tabs/all.html new file mode 100644 index 0000000000..4c9cd14a22 --- /dev/null +++ b/book/rust-docs/tabs/all.html @@ -0,0 +1 @@ +List of all items in this crate
\ No newline at end of file diff --git a/book/rust-docs/tabs/error/enum.Error.html b/book/rust-docs/tabs/error/enum.Error.html new file mode 100644 index 0000000000..344c051d68 --- /dev/null +++ b/book/rust-docs/tabs/error/enum.Error.html @@ -0,0 +1,22 @@ +Error in tabs::error - Rust

Enum tabs::error::Error

source ·
pub enum Error {
+    SyncAdapterError(String),
+    JsonError(Error),
+    MissingLocalIdError,
+    UrlParseError(ParseError),
+    SqlError(Error),
+    OpenDatabaseError(Error),
+}

Variants§

§

SyncAdapterError(String)

§

JsonError(Error)

§

MissingLocalIdError

§

UrlParseError(ParseError)

§

SqlError(Error)

§

OpenDatabaseError(Error)

Trait Implementations§

source§

impl Debug for Error

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Display for Error

source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Error for Error

source§

fn source(&self) -> Option<&(dyn Error + 'static)>

The lower-level source of this error, if any. Read more
1.0.0 · source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type based access to context intended for error reports. Read more
source§

impl From<Error> for Error

source§

fn from(source: Error) -> Self

Converts to this type from the input type.
source§

impl From<Error> for Error

source§

fn from(source: Error) -> Self

Converts to this type from the input type.
source§

impl From<Error> for Error

source§

fn from(source: Error) -> Self

Converts to this type from the input type.
source§

impl From<ParseError> for Error

source§

fn from(source: ParseError) -> Self

Converts to this type from the input type.
source§

impl GetErrorHandling for Error

§

type ExternalError = TabsApiError

source§

fn get_error_handling(&self) -> ErrorHandling<Self::ExternalError>

Return how to handle our internal errors

Auto Trait Implementations§

§

impl !RefUnwindSafe for Error

§

impl Send for Error

§

impl Sync for Error

§

impl Unpin for Error

§

impl !UnwindSafe for Error

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<E> Provider for Ewhere + E: Error + ?Sized,

source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (provide_any)
Data providers should implement this method to provide all values they are able to +provide by using demand. Read more
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/tabs/error/enum.TabsApiError.html b/book/rust-docs/tabs/error/enum.TabsApiError.html new file mode 100644 index 0000000000..82e55c10f1 --- /dev/null +++ b/book/rust-docs/tabs/error/enum.TabsApiError.html @@ -0,0 +1,25 @@ +TabsApiError in tabs::error - Rust

Enum tabs::error::TabsApiError

source ·
pub enum TabsApiError {
+    SyncError {
+        reason: String,
+    },
+    SqlError {
+        reason: String,
+    },
+    UnexpectedTabsError {
+        reason: String,
+    },
+}

Variants§

§

SyncError

Fields

§reason: String
§

SqlError

Fields

§reason: String
§

UnexpectedTabsError

Fields

§reason: String

Trait Implementations§

source§

impl Debug for TabsApiError

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Display for TabsApiError

source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Error for TabsApiError

1.30.0 · source§

fn source(&self) -> Option<&(dyn Error + 'static)>

The lower-level source of this error, if any. Read more
1.0.0 · source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type based access to context intended for error reports. Read more
source§

impl From<Error> for TabsApiError

source§

fn from(value: Error) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<E> Provider for Ewhere + E: Error + ?Sized,

source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (provide_any)
Data providers should implement this method to provide all values they are able to +provide by using demand. Read more
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/tabs/error/index.html b/book/rust-docs/tabs/error/index.html new file mode 100644 index 0000000000..1fe1e07491 --- /dev/null +++ b/book/rust-docs/tabs/error/index.html @@ -0,0 +1 @@ +tabs::error - Rust

Module tabs::error

source ·

Enums

Type Definitions

  • Result enum for the public interface
  • Result enum for internal functions
\ No newline at end of file diff --git a/book/rust-docs/tabs/error/sidebar-items.js b/book/rust-docs/tabs/error/sidebar-items.js new file mode 100644 index 0000000000..947cd05bcc --- /dev/null +++ b/book/rust-docs/tabs/error/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["Error","TabsApiError"],"type":["ApiResult","Result"]}; \ No newline at end of file diff --git a/book/rust-docs/tabs/error/type.ApiResult.html b/book/rust-docs/tabs/error/type.ApiResult.html new file mode 100644 index 0000000000..aedc99009c --- /dev/null +++ b/book/rust-docs/tabs/error/type.ApiResult.html @@ -0,0 +1,2 @@ +ApiResult in tabs::error - Rust

Type Definition tabs::error::ApiResult

source ·
pub type ApiResult<T> = Result<T, TabsApiError>;
Expand description

Result enum for the public interface

+
\ No newline at end of file diff --git a/book/rust-docs/tabs/error/type.Result.html b/book/rust-docs/tabs/error/type.Result.html new file mode 100644 index 0000000000..bacbd0dc43 --- /dev/null +++ b/book/rust-docs/tabs/error/type.Result.html @@ -0,0 +1,2 @@ +Result in tabs::error - Rust

Type Definition tabs::error::Result

source ·
pub type Result<T> = Result<T, Error>;
Expand description

Result enum for internal functions

+
\ No newline at end of file diff --git a/book/rust-docs/tabs/fn.get_registered_sync_engine.html b/book/rust-docs/tabs/fn.get_registered_sync_engine.html new file mode 100644 index 0000000000..bade15506d --- /dev/null +++ b/book/rust-docs/tabs/fn.get_registered_sync_engine.html @@ -0,0 +1,5 @@ +get_registered_sync_engine in tabs - Rust
pub fn get_registered_sync_engine(
+    engine_id: &SyncEngineId
+) -> Option<Box<dyn SyncEngine>>
Expand description

Called by the sync manager to get a sync engine via the store previously +registered with the sync manager.

+
\ No newline at end of file diff --git a/book/rust-docs/tabs/index.html b/book/rust-docs/tabs/index.html new file mode 100644 index 0000000000..566f2f72c3 --- /dev/null +++ b/book/rust-docs/tabs/index.html @@ -0,0 +1,2 @@ +tabs - Rust

Crate tabs

source ·

Re-exports

Modules

Structs

Functions

  • Called by the sync manager to get a sync engine via the store previously +registered with the sync manager.

Type Definitions

\ No newline at end of file diff --git a/book/rust-docs/tabs/sidebar-items.js b/book/rust-docs/tabs/sidebar-items.js new file mode 100644 index 0000000000..cf7bb0b73a --- /dev/null +++ b/book/rust-docs/tabs/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["get_registered_sync_engine"],"mod":["error"],"struct":["ClientRemoteTabs","TabsBridgedEngine","TabsEngine","TabsStore"],"type":["RemoteTabRecord","TabsDeviceType"]}; \ No newline at end of file diff --git a/book/rust-docs/tabs/storage/struct.ClientRemoteTabs.html b/book/rust-docs/tabs/storage/struct.ClientRemoteTabs.html new file mode 100644 index 0000000000..a0e3b4c3e6 --- /dev/null +++ b/book/rust-docs/tabs/storage/struct.ClientRemoteTabs.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../tabs/struct.ClientRemoteTabs.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/tabs/storage/type.RemoteTabRecord.html b/book/rust-docs/tabs/storage/type.RemoteTabRecord.html new file mode 100644 index 0000000000..e8244841e7 --- /dev/null +++ b/book/rust-docs/tabs/storage/type.RemoteTabRecord.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../tabs/type.RemoteTabRecord.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/tabs/storage/type.TabsDeviceType.html b/book/rust-docs/tabs/storage/type.TabsDeviceType.html new file mode 100644 index 0000000000..4533cdf484 --- /dev/null +++ b/book/rust-docs/tabs/storage/type.TabsDeviceType.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../tabs/type.TabsDeviceType.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/tabs/store/struct.TabsStore.html b/book/rust-docs/tabs/store/struct.TabsStore.html new file mode 100644 index 0000000000..eb012deb3c --- /dev/null +++ b/book/rust-docs/tabs/store/struct.TabsStore.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../tabs/struct.TabsStore.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/tabs/struct.ClientRemoteTabs.html b/book/rust-docs/tabs/struct.ClientRemoteTabs.html new file mode 100644 index 0000000000..510e7b7390 --- /dev/null +++ b/book/rust-docs/tabs/struct.ClientRemoteTabs.html @@ -0,0 +1,22 @@ +ClientRemoteTabs in tabs - Rust

Struct tabs::ClientRemoteTabs

source ·
pub struct ClientRemoteTabs {
+    pub client_id: String,
+    pub client_name: String,
+    pub device_type: DeviceType,
+    pub last_modified: i64,
+    pub remote_tabs: Vec<RemoteTab>,
+}

Fields§

§client_id: String§client_name: String§device_type: DeviceType§last_modified: i64§remote_tabs: Vec<RemoteTab>

Trait Implementations§

source§

impl Clone for ClientRemoteTabs

source§

fn clone(&self) -> ClientRemoteTabs

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for ClientRemoteTabs

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl<'de> Deserialize<'de> for ClientRemoteTabs

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl Serialize for ClientRemoteTabs

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/tabs/struct.TabsBridgedEngine.html b/book/rust-docs/tabs/struct.TabsBridgedEngine.html new file mode 100644 index 0000000000..801273f2f8 --- /dev/null +++ b/book/rust-docs/tabs/struct.TabsBridgedEngine.html @@ -0,0 +1,16 @@ +TabsBridgedEngine in tabs - Rust

Struct tabs::TabsBridgedEngine

source ·
pub struct TabsBridgedEngine { /* private fields */ }

Implementations§

source§

impl TabsBridgedEngine

source

pub fn new(bridge_impl: Box<dyn BridgedEngine>) -> Self

source

pub fn last_sync(&self) -> Result<i64>

source

pub fn set_last_sync(&self, last_sync: i64) -> Result<()>

source

pub fn sync_id(&self) -> Result<Option<String>>

source

pub fn reset_sync_id(&self) -> Result<String>

source

pub fn ensure_current_sync_id(&self, sync_id: &str) -> Result<String>

source

pub fn prepare_for_sync(&self, client_data: &str) -> Result<()>

source

pub fn sync_started(&self) -> Result<()>

source

pub fn store_incoming(&self, incoming: Vec<String>) -> Result<()>

source

pub fn apply(&self) -> Result<Vec<String>>

source

pub fn set_uploaded( + &self, + server_modified_millis: i64, + guids: Vec<SyncGuid> +) -> Result<()>

source

pub fn sync_finished(&self) -> Result<()>

source

pub fn reset(&self) -> Result<()>

source

pub fn wipe(&self) -> Result<()>

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/tabs/struct.TabsEngine.html b/book/rust-docs/tabs/struct.TabsEngine.html new file mode 100644 index 0000000000..784f8ad833 --- /dev/null +++ b/book/rust-docs/tabs/struct.TabsEngine.html @@ -0,0 +1,59 @@ +TabsEngine in tabs - Rust

Struct tabs::TabsEngine

source ·
pub struct TabsEngine {
+    pub local_id: RwLock<String>,
+    /* private fields */
+}

Fields§

§local_id: RwLock<String>

Implementations§

source§

impl TabsEngine

source

pub fn new(store: Arc<TabsStore>) -> Self

source

pub fn set_last_sync(&self, last_sync: ServerTimestamp) -> Result<()>

source

pub fn get_last_sync(&self) -> Result<Option<ServerTimestamp>>

Trait Implementations§

source§

impl SyncEngine for TabsEngine

source§

fn collection_name(&self) -> CollectionName

source§

fn prepare_for_sync( + &self, + get_client_data: &dyn Fn() -> ClientData +) -> Result<()>

Prepares the engine for syncing. The tabs engine currently uses this to +store the current list of clients, which it uses to look up device names +and types. Read more
source§

fn stage_incoming( + &self, + inbound: Vec<IncomingBso>, + telem: &mut Engine +) -> Result<()>

Stage some incoming records. This might be called multiple times in the same sync +if we fetch the incoming records in batches. Read more
source§

fn apply( + &self, + timestamp: ServerTimestamp, + _telem: &mut Engine +) -> Result<Vec<OutgoingBso>>

Apply the staged records, returning outgoing records. +Ideally we would adjust this model to better support batching of outgoing records +without needing to keep them all in memory (ie, an iterator or similar?)
source§

fn set_uploaded( + &self, + new_timestamp: ServerTimestamp, + ids: Vec<Guid> +) -> Result<()>

Indicates that the given record IDs were uploaded successfully to the server. +This may be called multiple times per sync, once for each batch. Batching is determined +dynamically based on payload sizes and counts via the server’s advertised limits.
source§

fn get_collection_request( + &self, + server_timestamp: ServerTimestamp +) -> Result<Option<CollectionRequest>>

The engine is responsible for building a single collection request. Engines +typically will store a lastModified timestamp and use that to build a +request saying “give me full records since that date” - however, other +engines might do something fancier. It can return None if the server timestamp +has not advanced since the last sync. +This could even later be extended to handle “backfills”, and we might end up +wanting one engine to use multiple collections (eg, as a “foreign key” via guid), etc.
source§

fn reset(&self, assoc: &EngineSyncAssociation) -> Result<()>

Reset the engine (and associated store) without wiping local data, +ready for a “first sync”. +assoc defines how this store is to be associated with sync.
source§

fn wipe(&self) -> Result<()>

source§

fn get_sync_assoc(&self) -> Result<EngineSyncAssociation>

Get persisted sync IDs. If they don’t match the global state we’ll be +reset() with the new IDs.
§

fn set_local_encryption_key(&mut self, _key: &str) -> Result<(), Error>

Tells the engine what the local encryption key is for the data managed +by the engine. This is only used by collections that store data +encrypted locally and is unrelated to the encryption used by Sync. +The intent is that for such collections, this key can be used to +decrypt local data before it is re-encrypted by Sync and sent to the +storage servers, and similarly, data from the storage servers will be +decrypted by Sync, then encrypted by the local encryption key before +being added to the local database. Read more
§

fn sync_finished(&self) -> Result<(), Error>

Called once the sync is finished. Not currently called if uploads fail (which +seems sad, but the other batching confusion there needs sorting out first). +Many engines will have nothing to do here, as most “post upload” work should be +done in set_uploaded()

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/tabs/struct.TabsStore.html b/book/rust-docs/tabs/struct.TabsStore.html new file mode 100644 index 0000000000..2215dec3d9 --- /dev/null +++ b/book/rust-docs/tabs/struct.TabsStore.html @@ -0,0 +1,14 @@ +TabsStore in tabs - Rust

Struct tabs::TabsStore

source ·
pub struct TabsStore {
+    pub storage: Mutex<TabsStorage>,
+}

Fields§

§storage: Mutex<TabsStorage>

Implementations§

source§

impl TabsStore

source

pub fn new(db_path: impl AsRef<Path>) -> Self

source

pub fn new_with_mem_path(db_path: &str) -> Self

source

pub fn set_local_tabs(&self, local_state: Vec<RemoteTab>)

source

pub fn get_all(&self) -> Vec<ClientRemoteTabs>

source

pub fn remote_tabs(&self) -> Option<Vec<ClientRemoteTabs>>

source§

impl TabsStore

source§

impl TabsStore

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/tabs/sync/bridge/struct.TabsBridgedEngine.html b/book/rust-docs/tabs/sync/bridge/struct.TabsBridgedEngine.html new file mode 100644 index 0000000000..944a1ac6b5 --- /dev/null +++ b/book/rust-docs/tabs/sync/bridge/struct.TabsBridgedEngine.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../tabs/struct.TabsBridgedEngine.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/tabs/sync/engine/fn.get_registered_sync_engine.html b/book/rust-docs/tabs/sync/engine/fn.get_registered_sync_engine.html new file mode 100644 index 0000000000..5f90b16f71 --- /dev/null +++ b/book/rust-docs/tabs/sync/engine/fn.get_registered_sync_engine.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../tabs/fn.get_registered_sync_engine.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/tabs/sync/engine/struct.TabsEngine.html b/book/rust-docs/tabs/sync/engine/struct.TabsEngine.html new file mode 100644 index 0000000000..ef2cdceaa8 --- /dev/null +++ b/book/rust-docs/tabs/sync/engine/struct.TabsEngine.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../tabs/struct.TabsEngine.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/tabs/type.RemoteTabRecord.html b/book/rust-docs/tabs/type.RemoteTabRecord.html new file mode 100644 index 0000000000..3e178a48ea --- /dev/null +++ b/book/rust-docs/tabs/type.RemoteTabRecord.html @@ -0,0 +1 @@ +RemoteTabRecord in tabs - Rust

Type Definition tabs::RemoteTabRecord

source ·
pub type RemoteTabRecord = RemoteTab;
\ No newline at end of file diff --git a/book/rust-docs/tabs/type.TabsDeviceType.html b/book/rust-docs/tabs/type.TabsDeviceType.html new file mode 100644 index 0000000000..e5daa2495d --- /dev/null +++ b/book/rust-docs/tabs/type.TabsDeviceType.html @@ -0,0 +1 @@ +TabsDeviceType in tabs - Rust

Type Definition tabs::TabsDeviceType

source ·
pub type TabsDeviceType = DeviceType;
\ No newline at end of file diff --git a/book/rust-docs/types/all.html b/book/rust-docs/types/all.html new file mode 100644 index 0000000000..83382cb184 --- /dev/null +++ b/book/rust-docs/types/all.html @@ -0,0 +1 @@ +List of all items in this crate

List of all items

Structs

\ No newline at end of file diff --git a/book/rust-docs/types/index.html b/book/rust-docs/types/index.html new file mode 100644 index 0000000000..a8de50f151 --- /dev/null +++ b/book/rust-docs/types/index.html @@ -0,0 +1 @@ +types - Rust
\ No newline at end of file diff --git a/book/rust-docs/types/sidebar-items.js b/book/rust-docs/types/sidebar-items.js new file mode 100644 index 0000000000..689712a8d6 --- /dev/null +++ b/book/rust-docs/types/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"struct":["Timestamp"]}; \ No newline at end of file diff --git a/book/rust-docs/types/struct.Timestamp.html b/book/rust-docs/types/struct.Timestamp.html new file mode 100644 index 0000000000..f90f231b46 --- /dev/null +++ b/book/rust-docs/types/struct.Timestamp.html @@ -0,0 +1,36 @@ +Timestamp in types - Rust

Struct types::Timestamp

source ·
pub struct Timestamp(pub u64);

Tuple Fields§

§0: u64

Implementations§

source§

impl Timestamp

source

pub fn now() -> Self

source

pub fn duration_since(self, other: Timestamp) -> Option<Duration>

Returns None if other is later than self (Duration may not represent +negative timespans in rust).

+
source

pub fn checked_sub(self, d: Duration) -> Option<Timestamp>

source

pub fn checked_add(self, d: Duration) -> Option<Timestamp>

source

pub fn as_millis(self) -> u64

source

pub fn as_millis_i64(self) -> i64

source

pub const EARLIEST: Timestamp = _

In desktop sync, bookmarks are clamped to Jan 23, 1993 (which is 727747200000) +There’s no good reason history records could be older than that, so we do +the same here (even though desktop’s history currently doesn’t) +XXX - there’s probably a case to be made for this being, say, 5 years ago - +then all requests earlier than that are collapsed into a single visit at +this timestamp.

+

Trait Implementations§

source§

impl Clone for Timestamp

source§

fn clone(&self) -> Timestamp

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for Timestamp

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for Timestamp

source§

fn default() -> Timestamp

Returns the “default value” for a type. Read more
source§

impl<'de> Deserialize<'de> for Timestamp

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl Display for Timestamp

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl From<SystemTime> for Timestamp

source§

fn from(st: SystemTime) -> Self

Converts to this type from the input type.
source§

impl From<Timestamp> for SystemTime

source§

fn from(ts: Timestamp) -> Self

Converts to this type from the input type.
source§

impl From<Timestamp> for u64

source§

fn from(ts: Timestamp) -> Self

Converts to this type from the input type.
source§

impl From<u64> for Timestamp

source§

fn from(ts: u64) -> Self

Converts to this type from the input type.
source§

impl FromSql for Timestamp

source§

fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self>

Converts SQLite value into Rust value.
source§

impl Hash for Timestamp

source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · source§

fn hash_slice<H>(data: &[Self], state: &mut H)where + H: Hasher, + Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
source§

impl Ord for Timestamp

source§

fn cmp(&self, other: &Timestamp) -> Ordering

This method returns an Ordering between self and other. Read more
1.21.0 · source§

fn max(self, other: Self) -> Selfwhere + Self: Sized,

Compares and returns the maximum of two values. Read more
1.21.0 · source§

fn min(self, other: Self) -> Selfwhere + Self: Sized,

Compares and returns the minimum of two values. Read more
1.50.0 · source§

fn clamp(self, min: Self, max: Self) -> Selfwhere + Self: Sized + PartialOrd<Self>,

Restrict a value to a certain interval. Read more
source§

impl PartialEq<Timestamp> for Timestamp

source§

fn eq(&self, other: &Timestamp) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl PartialOrd<Timestamp> for Timestamp

source§

fn partial_cmp(&self, other: &Timestamp) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · source§

fn lt(&self, other: &Rhs) -> bool

This method tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · source§

fn le(&self, other: &Rhs) -> bool

This method tests less than or equal to (for self and other) and is used by the <= +operator. Read more
1.0.0 · source§

fn gt(&self, other: &Rhs) -> bool

This method tests greater than (for self and other) and is used by the > operator. Read more
1.0.0 · source§

fn ge(&self, other: &Rhs) -> bool

This method tests greater than or equal to (for self and other) and is used by the >= +operator. Read more
source§

impl Serialize for Timestamp

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more
source§

impl ToSql for Timestamp

source§

fn to_sql(&self) -> RusqliteResult<ToSqlOutput<'_>>

Converts Rust value to SQLite value
source§

impl Copy for Timestamp

source§

impl Eq for Timestamp

source§

impl StructuralEq for Timestamp

source§

impl StructuralPartialEq for Timestamp

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> CallHasher for Twhere + T: Hash + ?Sized,

§

fn get_hash<H, B>(value: &H, build_hasher: &B) -> u64where + H: Hash + ?Sized, + B: BuildHasher,

source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/viaduct/all.html b/book/rust-docs/viaduct/all.html new file mode 100644 index 0000000000..fd6f51cff2 --- /dev/null +++ b/book/rust-docs/viaduct/all.html @@ -0,0 +1 @@ +List of all items in this crate

List of all items

Structs

Enums

Traits

Functions

Statics

Constants

\ No newline at end of file diff --git a/book/rust-docs/viaduct/backend/fn.note_backend.html b/book/rust-docs/viaduct/backend/fn.note_backend.html new file mode 100644 index 0000000000..0594ad5a92 --- /dev/null +++ b/book/rust-docs/viaduct/backend/fn.note_backend.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../viaduct/fn.note_backend.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/viaduct/backend/fn.set_backend.html b/book/rust-docs/viaduct/backend/fn.set_backend.html new file mode 100644 index 0000000000..35ac16b682 --- /dev/null +++ b/book/rust-docs/viaduct/backend/fn.set_backend.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../viaduct/fn.set_backend.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/viaduct/backend/trait.Backend.html b/book/rust-docs/viaduct/backend/trait.Backend.html new file mode 100644 index 0000000000..138149bf67 --- /dev/null +++ b/book/rust-docs/viaduct/backend/trait.Backend.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../viaduct/trait.Backend.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/viaduct/enum.Method.html b/book/rust-docs/viaduct/enum.Method.html new file mode 100644 index 0000000000..93cccfa869 --- /dev/null +++ b/book/rust-docs/viaduct/enum.Method.html @@ -0,0 +1,34 @@ +Method in viaduct - Rust

Enum viaduct::Method

source ·
#[repr(u8)]
pub enum Method { + Get, + Head, + Post, + Put, + Delete, + Connect, + Options, + Trace, + Patch, +}
Expand description

HTTP Methods.

+

The supported methods are the limited to what’s supported by android-components.

+

Variants§

§

Get

§

Head

§

Post

§

Put

§

Delete

§

Connect

§

Options

§

Trace

§

Patch

Implementations§

source§

impl Method

source

pub fn as_str(self) -> &'static str

Trait Implementations§

source§

impl Clone for Method

source§

fn clone(&self) -> Method

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for Method

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Display for Method

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Hash for Method

source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · source§

fn hash_slice<H>(data: &[Self], state: &mut H)where + H: Hasher, + Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
source§

impl Ord for Method

source§

fn cmp(&self, other: &Method) -> Ordering

This method returns an Ordering between self and other. Read more
1.21.0 · source§

fn max(self, other: Self) -> Selfwhere + Self: Sized,

Compares and returns the maximum of two values. Read more
1.21.0 · source§

fn min(self, other: Self) -> Selfwhere + Self: Sized,

Compares and returns the minimum of two values. Read more
1.50.0 · source§

fn clamp(self, min: Self, max: Self) -> Selfwhere + Self: Sized + PartialOrd<Self>,

Restrict a value to a certain interval. Read more
source§

impl PartialEq<Method> for Method

source§

fn eq(&self, other: &Method) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl PartialOrd<Method> for Method

source§

fn partial_cmp(&self, other: &Method) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · source§

fn lt(&self, other: &Rhs) -> bool

This method tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · source§

fn le(&self, other: &Rhs) -> bool

This method tests less than or equal to (for self and other) and is used by the <= +operator. Read more
1.0.0 · source§

fn gt(&self, other: &Rhs) -> bool

This method tests greater than (for self and other) and is used by the > operator. Read more
1.0.0 · source§

fn ge(&self, other: &Rhs) -> bool

This method tests greater than or equal to (for self and other) and is used by the >= +operator. Read more
source§

impl Copy for Method

source§

impl Eq for Method

source§

impl StructuralEq for Method

source§

impl StructuralPartialEq for Method

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/viaduct/error/enum.Error.html b/book/rust-docs/viaduct/error/enum.Error.html new file mode 100644 index 0000000000..947a65083d --- /dev/null +++ b/book/rust-docs/viaduct/error/enum.Error.html @@ -0,0 +1,24 @@ +Error in viaduct::error - Rust

Enum viaduct::error::Error

source ·
pub enum Error {
+    RequestHeaderError(HeaderName),
+    BackendError(String),
+    NetworkError(String),
+    BackendNotInitialized,
+    SetBackendError,
+    UrlError(ParseError),
+    NonTlsUrl,
+}

Variants§

§

RequestHeaderError(HeaderName)

§

BackendError(String)

§

NetworkError(String)

§

BackendNotInitialized

§

SetBackendError

§

UrlError(ParseError)

Note: we return this if the server returns a bad URL with +its response. This probably should never happen, but who knows.

+
§

NonTlsUrl

Trait Implementations§

source§

impl Debug for Error

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Display for Error

source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Error for Error

source§

fn source(&self) -> Option<&(dyn Error + 'static)>

The lower-level source of this error, if any. Read more
1.0.0 · source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type based access to context intended for error reports. Read more
source§

impl From<ParseError> for Error

source§

fn from(u: ParseError) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

§

impl RefUnwindSafe for Error

§

impl Send for Error

§

impl Sync for Error

§

impl Unpin for Error

§

impl UnwindSafe for Error

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<E> Provider for Ewhere + E: Error + ?Sized,

source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (provide_any)
Data providers should implement this method to provide all values they are able to +provide by using demand. Read more
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/viaduct/error/index.html b/book/rust-docs/viaduct/error/index.html new file mode 100644 index 0000000000..54cb76e6f0 --- /dev/null +++ b/book/rust-docs/viaduct/error/index.html @@ -0,0 +1,2 @@ +viaduct::error - Rust

Module viaduct::error

source ·

Structs

  • This error is returned as the Err result from +[Response::require_success].

Enums

\ No newline at end of file diff --git a/book/rust-docs/viaduct/error/sidebar-items.js b/book/rust-docs/viaduct/error/sidebar-items.js new file mode 100644 index 0000000000..1ada31588c --- /dev/null +++ b/book/rust-docs/viaduct/error/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["Error"],"struct":["UnexpectedStatus"]}; \ No newline at end of file diff --git a/book/rust-docs/viaduct/error/struct.UnexpectedStatus.html b/book/rust-docs/viaduct/error/struct.UnexpectedStatus.html new file mode 100644 index 0000000000..401b34c425 --- /dev/null +++ b/book/rust-docs/viaduct/error/struct.UnexpectedStatus.html @@ -0,0 +1,23 @@ +UnexpectedStatus in viaduct::error - Rust
pub struct UnexpectedStatus {
+    pub status: u16,
+    pub method: Method,
+    pub url: Url,
+}
Expand description

This error is returned as the Err result from +[Response::require_success].

+

Note that it’s not a variant on Error to distinguish between errors +caused by the network, and errors returned from the server.

+

Fields§

§status: u16§method: Method§url: Url

Trait Implementations§

source§

impl Clone for UnexpectedStatus

source§

fn clone(&self) -> UnexpectedStatus

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for UnexpectedStatus

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Display for UnexpectedStatus

source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Error for UnexpectedStatus

1.30.0 · source§

fn source(&self) -> Option<&(dyn Error + 'static)>

The lower-level source of this error, if any. Read more
1.0.0 · source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type based access to context intended for error reports. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<E> Provider for Ewhere + E: Error + ?Sized,

source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (provide_any)
Data providers should implement this method to provide all values they are able to +provide by using demand. Read more
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/viaduct/fn.note_backend.html b/book/rust-docs/viaduct/fn.note_backend.html new file mode 100644 index 0000000000..0aab10545e --- /dev/null +++ b/book/rust-docs/viaduct/fn.note_backend.html @@ -0,0 +1 @@ +note_backend in viaduct - Rust

Function viaduct::note_backend

source ·
pub fn note_backend(which: &str)
\ No newline at end of file diff --git a/book/rust-docs/viaduct/fn.set_backend.html b/book/rust-docs/viaduct/fn.set_backend.html new file mode 100644 index 0000000000..c51158d00f --- /dev/null +++ b/book/rust-docs/viaduct/fn.set_backend.html @@ -0,0 +1 @@ +set_backend in viaduct - Rust

Function viaduct::set_backend

source ·
pub fn set_backend(b: &'static dyn Backend) -> Result<(), Error>
\ No newline at end of file diff --git a/book/rust-docs/viaduct/header_names/constant.ACCEPT.html b/book/rust-docs/viaduct/header_names/constant.ACCEPT.html new file mode 100644 index 0000000000..a7e204a02f --- /dev/null +++ b/book/rust-docs/viaduct/header_names/constant.ACCEPT.html @@ -0,0 +1 @@ +ACCEPT in viaduct::header_names - Rust

Constant viaduct::header_names::ACCEPT

source ·
pub const ACCEPT: HeaderName;
\ No newline at end of file diff --git a/book/rust-docs/viaduct/header_names/constant.ACCEPT_ENCODING.html b/book/rust-docs/viaduct/header_names/constant.ACCEPT_ENCODING.html new file mode 100644 index 0000000000..12bbb2165f --- /dev/null +++ b/book/rust-docs/viaduct/header_names/constant.ACCEPT_ENCODING.html @@ -0,0 +1 @@ +ACCEPT_ENCODING in viaduct::header_names - Rust
pub const ACCEPT_ENCODING: HeaderName;
\ No newline at end of file diff --git a/book/rust-docs/viaduct/header_names/constant.AUTHORIZATION.html b/book/rust-docs/viaduct/header_names/constant.AUTHORIZATION.html new file mode 100644 index 0000000000..18faa5b2a1 --- /dev/null +++ b/book/rust-docs/viaduct/header_names/constant.AUTHORIZATION.html @@ -0,0 +1 @@ +AUTHORIZATION in viaduct::header_names - Rust
pub const AUTHORIZATION: HeaderName;
\ No newline at end of file diff --git a/book/rust-docs/viaduct/header_names/constant.CONTENT_TYPE.html b/book/rust-docs/viaduct/header_names/constant.CONTENT_TYPE.html new file mode 100644 index 0000000000..104ea814f3 --- /dev/null +++ b/book/rust-docs/viaduct/header_names/constant.CONTENT_TYPE.html @@ -0,0 +1 @@ +CONTENT_TYPE in viaduct::header_names - Rust
pub const CONTENT_TYPE: HeaderName;
\ No newline at end of file diff --git a/book/rust-docs/viaduct/header_names/constant.ETAG.html b/book/rust-docs/viaduct/header_names/constant.ETAG.html new file mode 100644 index 0000000000..4a5ff2af2d --- /dev/null +++ b/book/rust-docs/viaduct/header_names/constant.ETAG.html @@ -0,0 +1 @@ +ETAG in viaduct::header_names - Rust

Constant viaduct::header_names::ETAG

source ·
pub const ETAG: HeaderName;
\ No newline at end of file diff --git a/book/rust-docs/viaduct/header_names/constant.IF_NONE_MATCH.html b/book/rust-docs/viaduct/header_names/constant.IF_NONE_MATCH.html new file mode 100644 index 0000000000..279a392bc7 --- /dev/null +++ b/book/rust-docs/viaduct/header_names/constant.IF_NONE_MATCH.html @@ -0,0 +1 @@ +IF_NONE_MATCH in viaduct::header_names - Rust
pub const IF_NONE_MATCH: HeaderName;
\ No newline at end of file diff --git a/book/rust-docs/viaduct/header_names/constant.RETRY_AFTER.html b/book/rust-docs/viaduct/header_names/constant.RETRY_AFTER.html new file mode 100644 index 0000000000..2b327a15b4 --- /dev/null +++ b/book/rust-docs/viaduct/header_names/constant.RETRY_AFTER.html @@ -0,0 +1 @@ +RETRY_AFTER in viaduct::header_names - Rust
pub const RETRY_AFTER: HeaderName;
\ No newline at end of file diff --git a/book/rust-docs/viaduct/header_names/constant.USER_AGENT.html b/book/rust-docs/viaduct/header_names/constant.USER_AGENT.html new file mode 100644 index 0000000000..d423f850d9 --- /dev/null +++ b/book/rust-docs/viaduct/header_names/constant.USER_AGENT.html @@ -0,0 +1 @@ +USER_AGENT in viaduct::header_names - Rust
pub const USER_AGENT: HeaderName;
\ No newline at end of file diff --git a/book/rust-docs/viaduct/header_names/constant.X_IF_UNMODIFIED_SINCE.html b/book/rust-docs/viaduct/header_names/constant.X_IF_UNMODIFIED_SINCE.html new file mode 100644 index 0000000000..1fabcd99bc --- /dev/null +++ b/book/rust-docs/viaduct/header_names/constant.X_IF_UNMODIFIED_SINCE.html @@ -0,0 +1 @@ +X_IF_UNMODIFIED_SINCE in viaduct::header_names - Rust
pub const X_IF_UNMODIFIED_SINCE: HeaderName;
\ No newline at end of file diff --git a/book/rust-docs/viaduct/header_names/constant.X_KEYID.html b/book/rust-docs/viaduct/header_names/constant.X_KEYID.html new file mode 100644 index 0000000000..a30ddea018 --- /dev/null +++ b/book/rust-docs/viaduct/header_names/constant.X_KEYID.html @@ -0,0 +1 @@ +X_KEYID in viaduct::header_names - Rust

Constant viaduct::header_names::X_KEYID

source ·
pub const X_KEYID: HeaderName;
\ No newline at end of file diff --git a/book/rust-docs/viaduct/header_names/constant.X_LAST_MODIFIED.html b/book/rust-docs/viaduct/header_names/constant.X_LAST_MODIFIED.html new file mode 100644 index 0000000000..311438d0ee --- /dev/null +++ b/book/rust-docs/viaduct/header_names/constant.X_LAST_MODIFIED.html @@ -0,0 +1 @@ +X_LAST_MODIFIED in viaduct::header_names - Rust
pub const X_LAST_MODIFIED: HeaderName;
\ No newline at end of file diff --git a/book/rust-docs/viaduct/header_names/constant.X_TIMESTAMP.html b/book/rust-docs/viaduct/header_names/constant.X_TIMESTAMP.html new file mode 100644 index 0000000000..8a21b62dd3 --- /dev/null +++ b/book/rust-docs/viaduct/header_names/constant.X_TIMESTAMP.html @@ -0,0 +1 @@ +X_TIMESTAMP in viaduct::header_names - Rust
pub const X_TIMESTAMP: HeaderName;
\ No newline at end of file diff --git a/book/rust-docs/viaduct/header_names/constant.X_WEAVE_BACKOFF.html b/book/rust-docs/viaduct/header_names/constant.X_WEAVE_BACKOFF.html new file mode 100644 index 0000000000..161f52d380 --- /dev/null +++ b/book/rust-docs/viaduct/header_names/constant.X_WEAVE_BACKOFF.html @@ -0,0 +1 @@ +X_WEAVE_BACKOFF in viaduct::header_names - Rust
pub const X_WEAVE_BACKOFF: HeaderName;
\ No newline at end of file diff --git a/book/rust-docs/viaduct/header_names/constant.X_WEAVE_NEXT_OFFSET.html b/book/rust-docs/viaduct/header_names/constant.X_WEAVE_NEXT_OFFSET.html new file mode 100644 index 0000000000..4d2ae9f3e2 --- /dev/null +++ b/book/rust-docs/viaduct/header_names/constant.X_WEAVE_NEXT_OFFSET.html @@ -0,0 +1 @@ +X_WEAVE_NEXT_OFFSET in viaduct::header_names - Rust
pub const X_WEAVE_NEXT_OFFSET: HeaderName;
\ No newline at end of file diff --git a/book/rust-docs/viaduct/header_names/constant.X_WEAVE_RECORDS.html b/book/rust-docs/viaduct/header_names/constant.X_WEAVE_RECORDS.html new file mode 100644 index 0000000000..f65b4128fa --- /dev/null +++ b/book/rust-docs/viaduct/header_names/constant.X_WEAVE_RECORDS.html @@ -0,0 +1 @@ +X_WEAVE_RECORDS in viaduct::header_names - Rust
pub const X_WEAVE_RECORDS: HeaderName;
\ No newline at end of file diff --git a/book/rust-docs/viaduct/header_names/constant.X_WEAVE_TIMESTAMP.html b/book/rust-docs/viaduct/header_names/constant.X_WEAVE_TIMESTAMP.html new file mode 100644 index 0000000000..f9e6d43d9e --- /dev/null +++ b/book/rust-docs/viaduct/header_names/constant.X_WEAVE_TIMESTAMP.html @@ -0,0 +1 @@ +X_WEAVE_TIMESTAMP in viaduct::header_names - Rust
pub const X_WEAVE_TIMESTAMP: HeaderName;
\ No newline at end of file diff --git a/book/rust-docs/viaduct/header_names/index.html b/book/rust-docs/viaduct/header_names/index.html new file mode 100644 index 0000000000..4b730794e3 --- /dev/null +++ b/book/rust-docs/viaduct/header_names/index.html @@ -0,0 +1 @@ +viaduct::header_names - Rust
\ No newline at end of file diff --git a/book/rust-docs/viaduct/header_names/sidebar-items.js b/book/rust-docs/viaduct/header_names/sidebar-items.js new file mode 100644 index 0000000000..52ec407494 --- /dev/null +++ b/book/rust-docs/viaduct/header_names/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"constant":["ACCEPT","ACCEPT_ENCODING","AUTHORIZATION","CONTENT_TYPE","ETAG","IF_NONE_MATCH","RETRY_AFTER","USER_AGENT","X_IF_UNMODIFIED_SINCE","X_KEYID","X_LAST_MODIFIED","X_TIMESTAMP","X_WEAVE_BACKOFF","X_WEAVE_NEXT_OFFSET","X_WEAVE_RECORDS","X_WEAVE_TIMESTAMP"]}; \ No newline at end of file diff --git a/book/rust-docs/viaduct/headers/consts/constant.ACCEPT.html b/book/rust-docs/viaduct/headers/consts/constant.ACCEPT.html new file mode 100644 index 0000000000..eee62ac9c4 --- /dev/null +++ b/book/rust-docs/viaduct/headers/consts/constant.ACCEPT.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../viaduct/header_names/constant.ACCEPT.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/viaduct/headers/consts/constant.ACCEPT_ENCODING.html b/book/rust-docs/viaduct/headers/consts/constant.ACCEPT_ENCODING.html new file mode 100644 index 0000000000..e3ae35db26 --- /dev/null +++ b/book/rust-docs/viaduct/headers/consts/constant.ACCEPT_ENCODING.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../viaduct/header_names/constant.ACCEPT_ENCODING.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/viaduct/headers/consts/constant.AUTHORIZATION.html b/book/rust-docs/viaduct/headers/consts/constant.AUTHORIZATION.html new file mode 100644 index 0000000000..542eba1703 --- /dev/null +++ b/book/rust-docs/viaduct/headers/consts/constant.AUTHORIZATION.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../viaduct/header_names/constant.AUTHORIZATION.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/viaduct/headers/consts/constant.CONTENT_TYPE.html b/book/rust-docs/viaduct/headers/consts/constant.CONTENT_TYPE.html new file mode 100644 index 0000000000..2675dc6fbd --- /dev/null +++ b/book/rust-docs/viaduct/headers/consts/constant.CONTENT_TYPE.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../viaduct/header_names/constant.CONTENT_TYPE.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/viaduct/headers/consts/constant.ETAG.html b/book/rust-docs/viaduct/headers/consts/constant.ETAG.html new file mode 100644 index 0000000000..3b59393178 --- /dev/null +++ b/book/rust-docs/viaduct/headers/consts/constant.ETAG.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../viaduct/header_names/constant.ETAG.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/viaduct/headers/consts/constant.IF_NONE_MATCH.html b/book/rust-docs/viaduct/headers/consts/constant.IF_NONE_MATCH.html new file mode 100644 index 0000000000..2b0b201bdb --- /dev/null +++ b/book/rust-docs/viaduct/headers/consts/constant.IF_NONE_MATCH.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../viaduct/header_names/constant.IF_NONE_MATCH.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/viaduct/headers/consts/constant.RETRY_AFTER.html b/book/rust-docs/viaduct/headers/consts/constant.RETRY_AFTER.html new file mode 100644 index 0000000000..b046734022 --- /dev/null +++ b/book/rust-docs/viaduct/headers/consts/constant.RETRY_AFTER.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../viaduct/header_names/constant.RETRY_AFTER.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/viaduct/headers/consts/constant.USER_AGENT.html b/book/rust-docs/viaduct/headers/consts/constant.USER_AGENT.html new file mode 100644 index 0000000000..40d3486868 --- /dev/null +++ b/book/rust-docs/viaduct/headers/consts/constant.USER_AGENT.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../viaduct/header_names/constant.USER_AGENT.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/viaduct/headers/consts/constant.X_IF_UNMODIFIED_SINCE.html b/book/rust-docs/viaduct/headers/consts/constant.X_IF_UNMODIFIED_SINCE.html new file mode 100644 index 0000000000..364fd04e62 --- /dev/null +++ b/book/rust-docs/viaduct/headers/consts/constant.X_IF_UNMODIFIED_SINCE.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../viaduct/header_names/constant.X_IF_UNMODIFIED_SINCE.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/viaduct/headers/consts/constant.X_KEYID.html b/book/rust-docs/viaduct/headers/consts/constant.X_KEYID.html new file mode 100644 index 0000000000..3618e6c5de --- /dev/null +++ b/book/rust-docs/viaduct/headers/consts/constant.X_KEYID.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../viaduct/header_names/constant.X_KEYID.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/viaduct/headers/consts/constant.X_LAST_MODIFIED.html b/book/rust-docs/viaduct/headers/consts/constant.X_LAST_MODIFIED.html new file mode 100644 index 0000000000..1c944b2b71 --- /dev/null +++ b/book/rust-docs/viaduct/headers/consts/constant.X_LAST_MODIFIED.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../viaduct/header_names/constant.X_LAST_MODIFIED.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/viaduct/headers/consts/constant.X_TIMESTAMP.html b/book/rust-docs/viaduct/headers/consts/constant.X_TIMESTAMP.html new file mode 100644 index 0000000000..c31d28342f --- /dev/null +++ b/book/rust-docs/viaduct/headers/consts/constant.X_TIMESTAMP.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../viaduct/header_names/constant.X_TIMESTAMP.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/viaduct/headers/consts/constant.X_WEAVE_BACKOFF.html b/book/rust-docs/viaduct/headers/consts/constant.X_WEAVE_BACKOFF.html new file mode 100644 index 0000000000..e6a9791a07 --- /dev/null +++ b/book/rust-docs/viaduct/headers/consts/constant.X_WEAVE_BACKOFF.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../viaduct/header_names/constant.X_WEAVE_BACKOFF.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/viaduct/headers/consts/constant.X_WEAVE_NEXT_OFFSET.html b/book/rust-docs/viaduct/headers/consts/constant.X_WEAVE_NEXT_OFFSET.html new file mode 100644 index 0000000000..a878cd09b9 --- /dev/null +++ b/book/rust-docs/viaduct/headers/consts/constant.X_WEAVE_NEXT_OFFSET.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../viaduct/header_names/constant.X_WEAVE_NEXT_OFFSET.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/viaduct/headers/consts/constant.X_WEAVE_RECORDS.html b/book/rust-docs/viaduct/headers/consts/constant.X_WEAVE_RECORDS.html new file mode 100644 index 0000000000..e35b0a246b --- /dev/null +++ b/book/rust-docs/viaduct/headers/consts/constant.X_WEAVE_RECORDS.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../viaduct/header_names/constant.X_WEAVE_RECORDS.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/viaduct/headers/consts/constant.X_WEAVE_TIMESTAMP.html b/book/rust-docs/viaduct/headers/consts/constant.X_WEAVE_TIMESTAMP.html new file mode 100644 index 0000000000..47ecb942c2 --- /dev/null +++ b/book/rust-docs/viaduct/headers/consts/constant.X_WEAVE_TIMESTAMP.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../viaduct/header_names/constant.X_WEAVE_TIMESTAMP.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/viaduct/headers/consts/index.html b/book/rust-docs/viaduct/headers/consts/index.html new file mode 100644 index 0000000000..f649c52690 --- /dev/null +++ b/book/rust-docs/viaduct/headers/consts/index.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../viaduct/header_names/index.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/viaduct/headers/name/struct.HeaderName.html b/book/rust-docs/viaduct/headers/name/struct.HeaderName.html new file mode 100644 index 0000000000..0950f9bbd5 --- /dev/null +++ b/book/rust-docs/viaduct/headers/name/struct.HeaderName.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../viaduct/struct.HeaderName.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/viaduct/headers/name/struct.InvalidHeaderName.html b/book/rust-docs/viaduct/headers/name/struct.InvalidHeaderName.html new file mode 100644 index 0000000000..6a7a4ee258 --- /dev/null +++ b/book/rust-docs/viaduct/headers/name/struct.InvalidHeaderName.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../../viaduct/struct.InvalidHeaderName.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/viaduct/headers/struct.Header.html b/book/rust-docs/viaduct/headers/struct.Header.html new file mode 100644 index 0000000000..5b1d67e1c2 --- /dev/null +++ b/book/rust-docs/viaduct/headers/struct.Header.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../viaduct/struct.Header.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/viaduct/headers/struct.Headers.html b/book/rust-docs/viaduct/headers/struct.Headers.html new file mode 100644 index 0000000000..2353c3a9ae --- /dev/null +++ b/book/rust-docs/viaduct/headers/struct.Headers.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../viaduct/struct.Headers.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/viaduct/index.html b/book/rust-docs/viaduct/index.html new file mode 100644 index 0000000000..685a414f54 --- /dev/null +++ b/book/rust-docs/viaduct/index.html @@ -0,0 +1,10 @@ +viaduct - Rust

Crate viaduct

source ·

Re-exports

Modules

Structs

  • A single header. Headers have a name (case insensitive) and a value. The +character set for header and values are both restrictive.
  • Represents a header name that we know to be both valid and lowercase. +Internally, this avoids allocating for headers that are constant strings, +like the predefined ones in this crate, however even without that +optimization, we would still likely have an equivalent of this for use +as a case-insensitive string guaranteed to only have valid characters.
  • A list of headers.
  • Indicates an invalid header name. Note that we only emit +this for response headers, for request headers, we panic +instead. This is because it would likely come through as +a network error if we emitted it for local headers, when +it’s actually a bug that we’d need to fix.
  • A response from the server.

Enums

Traits

Functions

\ No newline at end of file diff --git a/book/rust-docs/viaduct/settings/index.html b/book/rust-docs/viaduct/settings/index.html new file mode 100644 index 0000000000..2347654691 --- /dev/null +++ b/book/rust-docs/viaduct/settings/index.html @@ -0,0 +1,4 @@ +viaduct::settings - Rust

Module viaduct::settings

source ·

Structs

  • Note: reqwest allows these only to be specified per-Client. concept-fetch +allows these to be specified on each call to fetch. I think it’s worth +keeping a single global reqwest::Client in the reqwest backend, to simplify +the way we abstract away from these.

Statics

\ No newline at end of file diff --git a/book/rust-docs/viaduct/settings/sidebar-items.js b/book/rust-docs/viaduct/settings/sidebar-items.js new file mode 100644 index 0000000000..9e276f9fe6 --- /dev/null +++ b/book/rust-docs/viaduct/settings/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"static":["GLOBAL_SETTINGS"],"struct":["Settings"]}; \ No newline at end of file diff --git a/book/rust-docs/viaduct/settings/static.GLOBAL_SETTINGS.html b/book/rust-docs/viaduct/settings/static.GLOBAL_SETTINGS.html new file mode 100644 index 0000000000..01d0b1060d --- /dev/null +++ b/book/rust-docs/viaduct/settings/static.GLOBAL_SETTINGS.html @@ -0,0 +1 @@ +GLOBAL_SETTINGS in viaduct::settings - Rust
pub static GLOBAL_SETTINGS: Lazy<RwLock<Settings>>
\ No newline at end of file diff --git a/book/rust-docs/viaduct/settings/struct.Settings.html b/book/rust-docs/viaduct/settings/struct.Settings.html new file mode 100644 index 0000000000..fdb949ef8d --- /dev/null +++ b/book/rust-docs/viaduct/settings/struct.Settings.html @@ -0,0 +1,25 @@ +Settings in viaduct::settings - Rust

Struct viaduct::settings::Settings

source ·
#[non_exhaustive]
pub struct Settings { + pub read_timeout: Option<Duration>, + pub connect_timeout: Option<Duration>, + pub follow_redirects: bool, + pub use_caches: bool, + pub addn_allowed_insecure_url: Option<Url>, +}
Expand description

Note: reqwest allows these only to be specified per-Client. concept-fetch +allows these to be specified on each call to fetch. I think it’s worth +keeping a single global reqwest::Client in the reqwest backend, to simplify +the way we abstract away from these.

+

In the future, should we need it, we might be able to add a CustomClient type +with custom settings. In the reqwest backend this would store a Client, and +in the concept-fetch backend it would only store the settings, and populate +things on the fly.

+

Fields (Non-exhaustive)§

This struct is marked as non-exhaustive
Non-exhaustive structs could have additional fields added in future. Therefore, non-exhaustive structs cannot be constructed in external crates using the traditional Struct { .. } syntax; cannot be matched against without a wildcard ..; and struct update syntax will not work.
§read_timeout: Option<Duration>§connect_timeout: Option<Duration>§follow_redirects: bool§use_caches: bool§addn_allowed_insecure_url: Option<Url>

Trait Implementations§

source§

impl Debug for Settings

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/viaduct/sidebar-items.js b/book/rust-docs/viaduct/sidebar-items.js new file mode 100644 index 0000000000..e45924ac35 --- /dev/null +++ b/book/rust-docs/viaduct/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["Method"],"fn":["note_backend","set_backend"],"mod":["error","header_names","settings","status_codes"],"struct":["Header","HeaderName","Headers","InvalidHeaderName","Request","Response"],"trait":["Backend"]}; \ No newline at end of file diff --git a/book/rust-docs/viaduct/status_codes/constant.ACCEPTED.html b/book/rust-docs/viaduct/status_codes/constant.ACCEPTED.html new file mode 100644 index 0000000000..c022f8fc87 --- /dev/null +++ b/book/rust-docs/viaduct/status_codes/constant.ACCEPTED.html @@ -0,0 +1 @@ +ACCEPTED in viaduct::status_codes - Rust

Constant viaduct::status_codes::ACCEPTED

source ·
pub const ACCEPTED: u16 = 202;
\ No newline at end of file diff --git a/book/rust-docs/viaduct/status_codes/constant.BAD_GATEWAY.html b/book/rust-docs/viaduct/status_codes/constant.BAD_GATEWAY.html new file mode 100644 index 0000000000..55310cd3ad --- /dev/null +++ b/book/rust-docs/viaduct/status_codes/constant.BAD_GATEWAY.html @@ -0,0 +1 @@ +BAD_GATEWAY in viaduct::status_codes - Rust
pub const BAD_GATEWAY: u16 = 502;
\ No newline at end of file diff --git a/book/rust-docs/viaduct/status_codes/constant.BAD_REQUEST.html b/book/rust-docs/viaduct/status_codes/constant.BAD_REQUEST.html new file mode 100644 index 0000000000..b43422b566 --- /dev/null +++ b/book/rust-docs/viaduct/status_codes/constant.BAD_REQUEST.html @@ -0,0 +1 @@ +BAD_REQUEST in viaduct::status_codes - Rust
pub const BAD_REQUEST: u16 = 400;
\ No newline at end of file diff --git a/book/rust-docs/viaduct/status_codes/constant.CONFLICT.html b/book/rust-docs/viaduct/status_codes/constant.CONFLICT.html new file mode 100644 index 0000000000..fbef89b6d4 --- /dev/null +++ b/book/rust-docs/viaduct/status_codes/constant.CONFLICT.html @@ -0,0 +1 @@ +CONFLICT in viaduct::status_codes - Rust

Constant viaduct::status_codes::CONFLICT

source ·
pub const CONFLICT: u16 = 409;
\ No newline at end of file diff --git a/book/rust-docs/viaduct/status_codes/constant.CONTINUE.html b/book/rust-docs/viaduct/status_codes/constant.CONTINUE.html new file mode 100644 index 0000000000..deb286dd0b --- /dev/null +++ b/book/rust-docs/viaduct/status_codes/constant.CONTINUE.html @@ -0,0 +1 @@ +CONTINUE in viaduct::status_codes - Rust

Constant viaduct::status_codes::CONTINUE

source ·
pub const CONTINUE: u16 = 100;
\ No newline at end of file diff --git a/book/rust-docs/viaduct/status_codes/constant.CREATED.html b/book/rust-docs/viaduct/status_codes/constant.CREATED.html new file mode 100644 index 0000000000..07d5d32c4f --- /dev/null +++ b/book/rust-docs/viaduct/status_codes/constant.CREATED.html @@ -0,0 +1 @@ +CREATED in viaduct::status_codes - Rust

Constant viaduct::status_codes::CREATED

source ·
pub const CREATED: u16 = 201;
\ No newline at end of file diff --git a/book/rust-docs/viaduct/status_codes/constant.EXPECTATION_FAILED.html b/book/rust-docs/viaduct/status_codes/constant.EXPECTATION_FAILED.html new file mode 100644 index 0000000000..97cb8bf966 --- /dev/null +++ b/book/rust-docs/viaduct/status_codes/constant.EXPECTATION_FAILED.html @@ -0,0 +1 @@ +EXPECTATION_FAILED in viaduct::status_codes - Rust
pub const EXPECTATION_FAILED: u16 = 417;
\ No newline at end of file diff --git a/book/rust-docs/viaduct/status_codes/constant.FORBIDDEN.html b/book/rust-docs/viaduct/status_codes/constant.FORBIDDEN.html new file mode 100644 index 0000000000..8f1d3464f5 --- /dev/null +++ b/book/rust-docs/viaduct/status_codes/constant.FORBIDDEN.html @@ -0,0 +1 @@ +FORBIDDEN in viaduct::status_codes - Rust
pub const FORBIDDEN: u16 = 403;
\ No newline at end of file diff --git a/book/rust-docs/viaduct/status_codes/constant.FOUND.html b/book/rust-docs/viaduct/status_codes/constant.FOUND.html new file mode 100644 index 0000000000..e76b926545 --- /dev/null +++ b/book/rust-docs/viaduct/status_codes/constant.FOUND.html @@ -0,0 +1 @@ +FOUND in viaduct::status_codes - Rust

Constant viaduct::status_codes::FOUND

source ·
pub const FOUND: u16 = 302;
\ No newline at end of file diff --git a/book/rust-docs/viaduct/status_codes/constant.GATEWAY_TIMEOUT.html b/book/rust-docs/viaduct/status_codes/constant.GATEWAY_TIMEOUT.html new file mode 100644 index 0000000000..c8303a410f --- /dev/null +++ b/book/rust-docs/viaduct/status_codes/constant.GATEWAY_TIMEOUT.html @@ -0,0 +1 @@ +GATEWAY_TIMEOUT in viaduct::status_codes - Rust
pub const GATEWAY_TIMEOUT: u16 = 504;
\ No newline at end of file diff --git a/book/rust-docs/viaduct/status_codes/constant.GONE.html b/book/rust-docs/viaduct/status_codes/constant.GONE.html new file mode 100644 index 0000000000..eaa21c09ee --- /dev/null +++ b/book/rust-docs/viaduct/status_codes/constant.GONE.html @@ -0,0 +1 @@ +GONE in viaduct::status_codes - Rust

Constant viaduct::status_codes::GONE

source ·
pub const GONE: u16 = 410;
\ No newline at end of file diff --git a/book/rust-docs/viaduct/status_codes/constant.HTTP_VERSION_NOT_SUPPORTED.html b/book/rust-docs/viaduct/status_codes/constant.HTTP_VERSION_NOT_SUPPORTED.html new file mode 100644 index 0000000000..870d8eec8b --- /dev/null +++ b/book/rust-docs/viaduct/status_codes/constant.HTTP_VERSION_NOT_SUPPORTED.html @@ -0,0 +1 @@ +HTTP_VERSION_NOT_SUPPORTED in viaduct::status_codes - Rust
pub const HTTP_VERSION_NOT_SUPPORTED: u16 = 505;
\ No newline at end of file diff --git a/book/rust-docs/viaduct/status_codes/constant.INTERNAL_SERVER_ERROR.html b/book/rust-docs/viaduct/status_codes/constant.INTERNAL_SERVER_ERROR.html new file mode 100644 index 0000000000..49a8d5b87f --- /dev/null +++ b/book/rust-docs/viaduct/status_codes/constant.INTERNAL_SERVER_ERROR.html @@ -0,0 +1 @@ +INTERNAL_SERVER_ERROR in viaduct::status_codes - Rust
pub const INTERNAL_SERVER_ERROR: u16 = 500;
\ No newline at end of file diff --git a/book/rust-docs/viaduct/status_codes/constant.LENGTH_REQUIRED.html b/book/rust-docs/viaduct/status_codes/constant.LENGTH_REQUIRED.html new file mode 100644 index 0000000000..9a8d04ff30 --- /dev/null +++ b/book/rust-docs/viaduct/status_codes/constant.LENGTH_REQUIRED.html @@ -0,0 +1 @@ +LENGTH_REQUIRED in viaduct::status_codes - Rust
pub const LENGTH_REQUIRED: u16 = 411;
\ No newline at end of file diff --git a/book/rust-docs/viaduct/status_codes/constant.METHOD_NOT_ALLOWED.html b/book/rust-docs/viaduct/status_codes/constant.METHOD_NOT_ALLOWED.html new file mode 100644 index 0000000000..41706f60ab --- /dev/null +++ b/book/rust-docs/viaduct/status_codes/constant.METHOD_NOT_ALLOWED.html @@ -0,0 +1 @@ +METHOD_NOT_ALLOWED in viaduct::status_codes - Rust
pub const METHOD_NOT_ALLOWED: u16 = 405;
\ No newline at end of file diff --git a/book/rust-docs/viaduct/status_codes/constant.MOVED_PERMANENTLY.html b/book/rust-docs/viaduct/status_codes/constant.MOVED_PERMANENTLY.html new file mode 100644 index 0000000000..f000ebc97e --- /dev/null +++ b/book/rust-docs/viaduct/status_codes/constant.MOVED_PERMANENTLY.html @@ -0,0 +1 @@ +MOVED_PERMANENTLY in viaduct::status_codes - Rust
pub const MOVED_PERMANENTLY: u16 = 301;
\ No newline at end of file diff --git a/book/rust-docs/viaduct/status_codes/constant.MULTIPLE_CHOICES.html b/book/rust-docs/viaduct/status_codes/constant.MULTIPLE_CHOICES.html new file mode 100644 index 0000000000..0a6aa34b19 --- /dev/null +++ b/book/rust-docs/viaduct/status_codes/constant.MULTIPLE_CHOICES.html @@ -0,0 +1 @@ +MULTIPLE_CHOICES in viaduct::status_codes - Rust
pub const MULTIPLE_CHOICES: u16 = 300;
\ No newline at end of file diff --git a/book/rust-docs/viaduct/status_codes/constant.NONAUTHORITATIVE_INFORMATION.html b/book/rust-docs/viaduct/status_codes/constant.NONAUTHORITATIVE_INFORMATION.html new file mode 100644 index 0000000000..e72463f4da --- /dev/null +++ b/book/rust-docs/viaduct/status_codes/constant.NONAUTHORITATIVE_INFORMATION.html @@ -0,0 +1 @@ +NONAUTHORITATIVE_INFORMATION in viaduct::status_codes - Rust
pub const NONAUTHORITATIVE_INFORMATION: u16 = 203;
\ No newline at end of file diff --git a/book/rust-docs/viaduct/status_codes/constant.NOT_ACCEPTABLE.html b/book/rust-docs/viaduct/status_codes/constant.NOT_ACCEPTABLE.html new file mode 100644 index 0000000000..e9bda8df73 --- /dev/null +++ b/book/rust-docs/viaduct/status_codes/constant.NOT_ACCEPTABLE.html @@ -0,0 +1 @@ +NOT_ACCEPTABLE in viaduct::status_codes - Rust
pub const NOT_ACCEPTABLE: u16 = 406;
\ No newline at end of file diff --git a/book/rust-docs/viaduct/status_codes/constant.NOT_FOUND.html b/book/rust-docs/viaduct/status_codes/constant.NOT_FOUND.html new file mode 100644 index 0000000000..7a3ce914fc --- /dev/null +++ b/book/rust-docs/viaduct/status_codes/constant.NOT_FOUND.html @@ -0,0 +1 @@ +NOT_FOUND in viaduct::status_codes - Rust
pub const NOT_FOUND: u16 = 404;
\ No newline at end of file diff --git a/book/rust-docs/viaduct/status_codes/constant.NOT_IMPLEMENTED.html b/book/rust-docs/viaduct/status_codes/constant.NOT_IMPLEMENTED.html new file mode 100644 index 0000000000..c5be038bf7 --- /dev/null +++ b/book/rust-docs/viaduct/status_codes/constant.NOT_IMPLEMENTED.html @@ -0,0 +1 @@ +NOT_IMPLEMENTED in viaduct::status_codes - Rust
pub const NOT_IMPLEMENTED: u16 = 501;
\ No newline at end of file diff --git a/book/rust-docs/viaduct/status_codes/constant.NOT_MODIFIED.html b/book/rust-docs/viaduct/status_codes/constant.NOT_MODIFIED.html new file mode 100644 index 0000000000..b81d19ee1e --- /dev/null +++ b/book/rust-docs/viaduct/status_codes/constant.NOT_MODIFIED.html @@ -0,0 +1 @@ +NOT_MODIFIED in viaduct::status_codes - Rust
pub const NOT_MODIFIED: u16 = 304;
\ No newline at end of file diff --git a/book/rust-docs/viaduct/status_codes/constant.NO_CONTENT.html b/book/rust-docs/viaduct/status_codes/constant.NO_CONTENT.html new file mode 100644 index 0000000000..3f8fa0d450 --- /dev/null +++ b/book/rust-docs/viaduct/status_codes/constant.NO_CONTENT.html @@ -0,0 +1 @@ +NO_CONTENT in viaduct::status_codes - Rust
pub const NO_CONTENT: u16 = 204;
\ No newline at end of file diff --git a/book/rust-docs/viaduct/status_codes/constant.OK.html b/book/rust-docs/viaduct/status_codes/constant.OK.html new file mode 100644 index 0000000000..3197e23974 --- /dev/null +++ b/book/rust-docs/viaduct/status_codes/constant.OK.html @@ -0,0 +1 @@ +OK in viaduct::status_codes - Rust

Constant viaduct::status_codes::OK

source ·
pub const OK: u16 = 200;
\ No newline at end of file diff --git a/book/rust-docs/viaduct/status_codes/constant.PARTIAL_CONTENT.html b/book/rust-docs/viaduct/status_codes/constant.PARTIAL_CONTENT.html new file mode 100644 index 0000000000..79109412c9 --- /dev/null +++ b/book/rust-docs/viaduct/status_codes/constant.PARTIAL_CONTENT.html @@ -0,0 +1 @@ +PARTIAL_CONTENT in viaduct::status_codes - Rust
pub const PARTIAL_CONTENT: u16 = 206;
\ No newline at end of file diff --git a/book/rust-docs/viaduct/status_codes/constant.PAYMENT_REQUIRED.html b/book/rust-docs/viaduct/status_codes/constant.PAYMENT_REQUIRED.html new file mode 100644 index 0000000000..77eb090b97 --- /dev/null +++ b/book/rust-docs/viaduct/status_codes/constant.PAYMENT_REQUIRED.html @@ -0,0 +1 @@ +PAYMENT_REQUIRED in viaduct::status_codes - Rust
pub const PAYMENT_REQUIRED: u16 = 402;
\ No newline at end of file diff --git a/book/rust-docs/viaduct/status_codes/constant.PRECONDITION_FAILED.html b/book/rust-docs/viaduct/status_codes/constant.PRECONDITION_FAILED.html new file mode 100644 index 0000000000..bc9e3f16eb --- /dev/null +++ b/book/rust-docs/viaduct/status_codes/constant.PRECONDITION_FAILED.html @@ -0,0 +1 @@ +PRECONDITION_FAILED in viaduct::status_codes - Rust
pub const PRECONDITION_FAILED: u16 = 412;
\ No newline at end of file diff --git a/book/rust-docs/viaduct/status_codes/constant.PROXY_AUTHENTICATION_REQUIRED.html b/book/rust-docs/viaduct/status_codes/constant.PROXY_AUTHENTICATION_REQUIRED.html new file mode 100644 index 0000000000..588f240500 --- /dev/null +++ b/book/rust-docs/viaduct/status_codes/constant.PROXY_AUTHENTICATION_REQUIRED.html @@ -0,0 +1 @@ +PROXY_AUTHENTICATION_REQUIRED in viaduct::status_codes - Rust
pub const PROXY_AUTHENTICATION_REQUIRED: u16 = 407;
\ No newline at end of file diff --git a/book/rust-docs/viaduct/status_codes/constant.REQUESTED_RANGE_NOT_SATISFIABLE.html b/book/rust-docs/viaduct/status_codes/constant.REQUESTED_RANGE_NOT_SATISFIABLE.html new file mode 100644 index 0000000000..209b4cdc55 --- /dev/null +++ b/book/rust-docs/viaduct/status_codes/constant.REQUESTED_RANGE_NOT_SATISFIABLE.html @@ -0,0 +1 @@ +REQUESTED_RANGE_NOT_SATISFIABLE in viaduct::status_codes - Rust
pub const REQUESTED_RANGE_NOT_SATISFIABLE: u16 = 416;
\ No newline at end of file diff --git a/book/rust-docs/viaduct/status_codes/constant.REQUEST_ENTITY_TOO_LARGE.html b/book/rust-docs/viaduct/status_codes/constant.REQUEST_ENTITY_TOO_LARGE.html new file mode 100644 index 0000000000..6ff61cad74 --- /dev/null +++ b/book/rust-docs/viaduct/status_codes/constant.REQUEST_ENTITY_TOO_LARGE.html @@ -0,0 +1 @@ +REQUEST_ENTITY_TOO_LARGE in viaduct::status_codes - Rust
pub const REQUEST_ENTITY_TOO_LARGE: u16 = 413;
\ No newline at end of file diff --git a/book/rust-docs/viaduct/status_codes/constant.REQUEST_TIMEOUT.html b/book/rust-docs/viaduct/status_codes/constant.REQUEST_TIMEOUT.html new file mode 100644 index 0000000000..daaf37731b --- /dev/null +++ b/book/rust-docs/viaduct/status_codes/constant.REQUEST_TIMEOUT.html @@ -0,0 +1 @@ +REQUEST_TIMEOUT in viaduct::status_codes - Rust
pub const REQUEST_TIMEOUT: u16 = 408;
\ No newline at end of file diff --git a/book/rust-docs/viaduct/status_codes/constant.REQUEST_URI_TOO_LONG.html b/book/rust-docs/viaduct/status_codes/constant.REQUEST_URI_TOO_LONG.html new file mode 100644 index 0000000000..57d597af8e --- /dev/null +++ b/book/rust-docs/viaduct/status_codes/constant.REQUEST_URI_TOO_LONG.html @@ -0,0 +1 @@ +REQUEST_URI_TOO_LONG in viaduct::status_codes - Rust
pub const REQUEST_URI_TOO_LONG: u16 = 414;
\ No newline at end of file diff --git a/book/rust-docs/viaduct/status_codes/constant.RESET_CONTENT.html b/book/rust-docs/viaduct/status_codes/constant.RESET_CONTENT.html new file mode 100644 index 0000000000..0dfa4f3b0c --- /dev/null +++ b/book/rust-docs/viaduct/status_codes/constant.RESET_CONTENT.html @@ -0,0 +1 @@ +RESET_CONTENT in viaduct::status_codes - Rust
pub const RESET_CONTENT: u16 = 205;
\ No newline at end of file diff --git a/book/rust-docs/viaduct/status_codes/constant.SEE_OTHER.html b/book/rust-docs/viaduct/status_codes/constant.SEE_OTHER.html new file mode 100644 index 0000000000..380f3e1d87 --- /dev/null +++ b/book/rust-docs/viaduct/status_codes/constant.SEE_OTHER.html @@ -0,0 +1 @@ +SEE_OTHER in viaduct::status_codes - Rust
pub const SEE_OTHER: u16 = 303;
\ No newline at end of file diff --git a/book/rust-docs/viaduct/status_codes/constant.SERVICE_UNAVAILABLE.html b/book/rust-docs/viaduct/status_codes/constant.SERVICE_UNAVAILABLE.html new file mode 100644 index 0000000000..38ca8122ab --- /dev/null +++ b/book/rust-docs/viaduct/status_codes/constant.SERVICE_UNAVAILABLE.html @@ -0,0 +1 @@ +SERVICE_UNAVAILABLE in viaduct::status_codes - Rust
pub const SERVICE_UNAVAILABLE: u16 = 503;
\ No newline at end of file diff --git a/book/rust-docs/viaduct/status_codes/constant.SWITCHING_PROTOCOLS.html b/book/rust-docs/viaduct/status_codes/constant.SWITCHING_PROTOCOLS.html new file mode 100644 index 0000000000..ead3f14d23 --- /dev/null +++ b/book/rust-docs/viaduct/status_codes/constant.SWITCHING_PROTOCOLS.html @@ -0,0 +1 @@ +SWITCHING_PROTOCOLS in viaduct::status_codes - Rust
pub const SWITCHING_PROTOCOLS: u16 = 101;
\ No newline at end of file diff --git a/book/rust-docs/viaduct/status_codes/constant.TEMPORARY_REDIRECT.html b/book/rust-docs/viaduct/status_codes/constant.TEMPORARY_REDIRECT.html new file mode 100644 index 0000000000..7b796255c6 --- /dev/null +++ b/book/rust-docs/viaduct/status_codes/constant.TEMPORARY_REDIRECT.html @@ -0,0 +1 @@ +TEMPORARY_REDIRECT in viaduct::status_codes - Rust
pub const TEMPORARY_REDIRECT: u16 = 307;
\ No newline at end of file diff --git a/book/rust-docs/viaduct/status_codes/constant.TOO_MANY_REQUESTS.html b/book/rust-docs/viaduct/status_codes/constant.TOO_MANY_REQUESTS.html new file mode 100644 index 0000000000..e30c40d544 --- /dev/null +++ b/book/rust-docs/viaduct/status_codes/constant.TOO_MANY_REQUESTS.html @@ -0,0 +1 @@ +TOO_MANY_REQUESTS in viaduct::status_codes - Rust
pub const TOO_MANY_REQUESTS: u16 = 429;
\ No newline at end of file diff --git a/book/rust-docs/viaduct/status_codes/constant.UNAUTHORIZED.html b/book/rust-docs/viaduct/status_codes/constant.UNAUTHORIZED.html new file mode 100644 index 0000000000..0d309f4b2b --- /dev/null +++ b/book/rust-docs/viaduct/status_codes/constant.UNAUTHORIZED.html @@ -0,0 +1 @@ +UNAUTHORIZED in viaduct::status_codes - Rust
pub const UNAUTHORIZED: u16 = 401;
\ No newline at end of file diff --git a/book/rust-docs/viaduct/status_codes/constant.UNSUPPORTED_MEDIA_TYPE.html b/book/rust-docs/viaduct/status_codes/constant.UNSUPPORTED_MEDIA_TYPE.html new file mode 100644 index 0000000000..d924d755d5 --- /dev/null +++ b/book/rust-docs/viaduct/status_codes/constant.UNSUPPORTED_MEDIA_TYPE.html @@ -0,0 +1 @@ +UNSUPPORTED_MEDIA_TYPE in viaduct::status_codes - Rust
pub const UNSUPPORTED_MEDIA_TYPE: u16 = 415;
\ No newline at end of file diff --git a/book/rust-docs/viaduct/status_codes/constant.USE_PROXY.html b/book/rust-docs/viaduct/status_codes/constant.USE_PROXY.html new file mode 100644 index 0000000000..20e78dffb9 --- /dev/null +++ b/book/rust-docs/viaduct/status_codes/constant.USE_PROXY.html @@ -0,0 +1 @@ +USE_PROXY in viaduct::status_codes - Rust
pub const USE_PROXY: u16 = 305;
\ No newline at end of file diff --git a/book/rust-docs/viaduct/status_codes/fn.is_client_error_code.html b/book/rust-docs/viaduct/status_codes/fn.is_client_error_code.html new file mode 100644 index 0000000000..3ca742c634 --- /dev/null +++ b/book/rust-docs/viaduct/status_codes/fn.is_client_error_code.html @@ -0,0 +1,2 @@ +is_client_error_code in viaduct::status_codes - Rust
pub fn is_client_error_code(c: u16) -> bool
Expand description

Is it a 4xx error?

+
\ No newline at end of file diff --git a/book/rust-docs/viaduct/status_codes/fn.is_server_error_code.html b/book/rust-docs/viaduct/status_codes/fn.is_server_error_code.html new file mode 100644 index 0000000000..c57684005f --- /dev/null +++ b/book/rust-docs/viaduct/status_codes/fn.is_server_error_code.html @@ -0,0 +1,2 @@ +is_server_error_code in viaduct::status_codes - Rust
pub fn is_server_error_code(c: u16) -> bool
Expand description

Is it a 5xx error?

+
\ No newline at end of file diff --git a/book/rust-docs/viaduct/status_codes/fn.is_success_code.html b/book/rust-docs/viaduct/status_codes/fn.is_success_code.html new file mode 100644 index 0000000000..e0bc68fd04 --- /dev/null +++ b/book/rust-docs/viaduct/status_codes/fn.is_success_code.html @@ -0,0 +1,2 @@ +is_success_code in viaduct::status_codes - Rust
pub fn is_success_code(c: u16) -> bool
Expand description

Is it a 2xx status?

+
\ No newline at end of file diff --git a/book/rust-docs/viaduct/status_codes/index.html b/book/rust-docs/viaduct/status_codes/index.html new file mode 100644 index 0000000000..7d6119afc7 --- /dev/null +++ b/book/rust-docs/viaduct/status_codes/index.html @@ -0,0 +1,2 @@ +viaduct::status_codes - Rust
\ No newline at end of file diff --git a/book/rust-docs/viaduct/status_codes/sidebar-items.js b/book/rust-docs/viaduct/status_codes/sidebar-items.js new file mode 100644 index 0000000000..d4a00dd9d9 --- /dev/null +++ b/book/rust-docs/viaduct/status_codes/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"constant":["ACCEPTED","BAD_GATEWAY","BAD_REQUEST","CONFLICT","CONTINUE","CREATED","EXPECTATION_FAILED","FORBIDDEN","FOUND","GATEWAY_TIMEOUT","GONE","HTTP_VERSION_NOT_SUPPORTED","INTERNAL_SERVER_ERROR","LENGTH_REQUIRED","METHOD_NOT_ALLOWED","MOVED_PERMANENTLY","MULTIPLE_CHOICES","NONAUTHORITATIVE_INFORMATION","NOT_ACCEPTABLE","NOT_FOUND","NOT_IMPLEMENTED","NOT_MODIFIED","NO_CONTENT","OK","PARTIAL_CONTENT","PAYMENT_REQUIRED","PRECONDITION_FAILED","PROXY_AUTHENTICATION_REQUIRED","REQUESTED_RANGE_NOT_SATISFIABLE","REQUEST_ENTITY_TOO_LARGE","REQUEST_TIMEOUT","REQUEST_URI_TOO_LONG","RESET_CONTENT","SEE_OTHER","SERVICE_UNAVAILABLE","SWITCHING_PROTOCOLS","TEMPORARY_REDIRECT","TOO_MANY_REQUESTS","UNAUTHORIZED","UNSUPPORTED_MEDIA_TYPE","USE_PROXY"],"fn":["is_client_error_code","is_server_error_code","is_success_code"]}; \ No newline at end of file diff --git a/book/rust-docs/viaduct/struct.Header.html b/book/rust-docs/viaduct/struct.Header.html new file mode 100644 index 0000000000..945b6c14ab --- /dev/null +++ b/book/rust-docs/viaduct/struct.Header.html @@ -0,0 +1,48 @@ +Header in viaduct - Rust

Struct viaduct::Header

source ·
pub struct Header { /* private fields */ }
Expand description

A single header. Headers have a name (case insensitive) and a value. The +character set for header and values are both restrictive.

+
    +
  • +

    Names must only contain a-zA-Z0-9 and and (‘!’ | ‘#’ | ‘$’ | ‘%’ | ‘&’ | +‘'’ | ‘*’ | ‘+’ | ‘-’ | ‘.’ | ‘^’ | ‘_’ | ‘`’ | ‘|’ | ‘~’) characters +(the field-name token production defined at +https://tools.ietf.org/html/rfc7230#section-3.2). +For request headers, we expect these to all be specified statically, +and so we panic if you provide an invalid one. (For response headers, we +ignore headers with invalid names, but emit a warning).

    +

    Header names are case insensitive, and we have several pre-defined ones in +the [header_names] module.

    +
  • +
  • +

    Values may only contain printable ascii characters, and may not contain +\r or \n. Strictly speaking, HTTP is more flexible for header values, +however we don’t need to support binary header values, and so we do not.

    +
  • +
+

Note that typically you should not interact with this directly, and instead +use the methods on [Request] or Headers to manipulate these.

+

Implementations§

source§

impl Header

source

pub fn new<Name, Value>(name: Name, value: Value) -> Result<Self, Error>where + Name: Into<HeaderName>, + Value: AsRef<str> + Into<String>,

source

pub fn new_unchecked<Value>(name: HeaderName, value: Value) -> Selfwhere + Value: AsRef<str> + Into<String>,

source

pub fn name(&self) -> &HeaderName

source

pub fn value(&self) -> &str

Trait Implementations§

source§

impl Clone for Header

source§

fn clone(&self) -> Header

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for Header

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Display for Header

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl FromIterator<Header> for Headers

source§

fn from_iter<T>(iter: T) -> Selfwhere + T: IntoIterator<Item = Header>,

Creates a value from an iterator. Read more
source§

impl Hash for Header

source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · source§

fn hash_slice<H>(data: &[Self], state: &mut H)where + H: Hasher, + Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
source§

impl Ord for Header

source§

fn cmp(&self, other: &Header) -> Ordering

This method returns an Ordering between self and other. Read more
1.21.0 · source§

fn max(self, other: Self) -> Selfwhere + Self: Sized,

Compares and returns the maximum of two values. Read more
1.21.0 · source§

fn min(self, other: Self) -> Selfwhere + Self: Sized,

Compares and returns the minimum of two values. Read more
1.50.0 · source§

fn clamp(self, min: Self, max: Self) -> Selfwhere + Self: Sized + PartialOrd<Self>,

Restrict a value to a certain interval. Read more
source§

impl PartialEq<Header> for Header

source§

fn eq(&self, other: &Header) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl PartialOrd<Header> for Header

source§

fn partial_cmp(&self, other: &Header) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · source§

fn lt(&self, other: &Rhs) -> bool

This method tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · source§

fn le(&self, other: &Rhs) -> bool

This method tests less than or equal to (for self and other) and is used by the <= +operator. Read more
1.0.0 · source§

fn gt(&self, other: &Rhs) -> bool

This method tests greater than (for self and other) and is used by the > operator. Read more
1.0.0 · source§

fn ge(&self, other: &Rhs) -> bool

This method tests greater than or equal to (for self and other) and is used by the >= +operator. Read more
source§

impl Eq for Header

source§

impl StructuralEq for Header

source§

impl StructuralPartialEq for Header

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/viaduct/struct.HeaderName.html b/book/rust-docs/viaduct/struct.HeaderName.html new file mode 100644 index 0000000000..1e1e2a1717 --- /dev/null +++ b/book/rust-docs/viaduct/struct.HeaderName.html @@ -0,0 +1,1218 @@ +HeaderName in viaduct - Rust

Struct viaduct::HeaderName

source ·
pub struct HeaderName(_);
Expand description

Represents a header name that we know to be both valid and lowercase. +Internally, this avoids allocating for headers that are constant strings, +like the predefined ones in this crate, however even without that +optimization, we would still likely have an equivalent of this for use +as a case-insensitive string guaranteed to only have valid characters.

+

Implementations§

source§

impl HeaderName

source

pub fn new<S: Into<Cow<'static, str>>>(s: S) -> Result<Self, InvalidHeaderName>

Create a new header. In general you likely want to use HeaderName::from(s) +instead for headers being specified locally (This will panic instead of +returning a Result, since we have control over headers we specify locally, +and want to know if we specify an illegal one).

+
source

pub fn as_str(&self) -> &str

Methods from Deref<Target = str>§

1.0.0 · source

pub fn len(&self) -> usize

Returns the length of self.

+

This length is in bytes, not chars or graphemes. In other words, +it might not be what a human considers the length of the string.

+
Examples
+
let len = "foo".len();
+assert_eq!(3, len);
+
+assert_eq!("ƒoo".len(), 4); // fancy f!
+assert_eq!("ƒoo".chars().count(), 3);
+
1.0.0 · source

pub fn is_empty(&self) -> bool

Returns true if self has a length of zero bytes.

+
Examples
+
let s = "";
+assert!(s.is_empty());
+
+let s = "not empty";
+assert!(!s.is_empty());
+
1.9.0 · source

pub fn is_char_boundary(&self, index: usize) -> bool

Checks that index-th byte is the first byte in a UTF-8 code point +sequence or the end of the string.

+

The start and end of the string (when index == self.len()) are +considered to be boundaries.

+

Returns false if index is greater than self.len().

+
Examples
+
let s = "Löwe 老虎 Léopard";
+assert!(s.is_char_boundary(0));
+// start of `老`
+assert!(s.is_char_boundary(6));
+assert!(s.is_char_boundary(s.len()));
+
+// second byte of `ö`
+assert!(!s.is_char_boundary(2));
+
+// third byte of `老`
+assert!(!s.is_char_boundary(8));
+
source

pub fn floor_char_boundary(&self, index: usize) -> usize

🔬This is a nightly-only experimental API. (round_char_boundary)

Finds the closest x not exceeding index where is_char_boundary(x) is true.

+

This method can help you truncate a string so that it’s still valid UTF-8, but doesn’t +exceed a given number of bytes. Note that this is done purely at the character level +and can still visually split graphemes, even though the underlying characters aren’t +split. For example, the emoji 🧑‍🔬 (scientist) could be split so that the string only +includes 🧑 (person) instead.

+
Examples
+
#![feature(round_char_boundary)]
+let s = "❤️🧡💛💚💙💜";
+assert_eq!(s.len(), 26);
+assert!(!s.is_char_boundary(13));
+
+let closest = s.floor_char_boundary(13);
+assert_eq!(closest, 10);
+assert_eq!(&s[..closest], "❤️🧡");
+
source

pub fn ceil_char_boundary(&self, index: usize) -> usize

🔬This is a nightly-only experimental API. (round_char_boundary)

Finds the closest x not below index where is_char_boundary(x) is true.

+

This method is the natural complement to floor_char_boundary. See that method +for more details.

+
Panics
+

Panics if index > self.len().

+
Examples
+
#![feature(round_char_boundary)]
+let s = "❤️🧡💛💚💙💜";
+assert_eq!(s.len(), 26);
+assert!(!s.is_char_boundary(13));
+
+let closest = s.ceil_char_boundary(13);
+assert_eq!(closest, 14);
+assert_eq!(&s[..closest], "❤️🧡💛");
+
1.0.0 · source

pub fn as_bytes(&self) -> &[u8]

Converts a string slice to a byte slice. To convert the byte slice back +into a string slice, use the from_utf8 function.

+
Examples
+
let bytes = "bors".as_bytes();
+assert_eq!(b"bors", bytes);
+
1.0.0 · source

pub fn as_ptr(&self) -> *const u8

Converts a string slice to a raw pointer.

+

As string slices are a slice of bytes, the raw pointer points to a +u8. This pointer will be pointing to the first byte of the string +slice.

+

The caller must ensure that the returned pointer is never written to. +If you need to mutate the contents of the string slice, use as_mut_ptr.

+
Examples
+
let s = "Hello";
+let ptr = s.as_ptr();
+
1.20.0 · source

pub fn get<I>(&self, i: I) -> Option<&<I as SliceIndex<str>>::Output>where + I: SliceIndex<str>,

Returns a subslice of str.

+

This is the non-panicking alternative to indexing the str. Returns +None whenever equivalent indexing operation would panic.

+
Examples
+
let v = String::from("🗻∈🌏");
+
+assert_eq!(Some("🗻"), v.get(0..4));
+
+// indices not on UTF-8 sequence boundaries
+assert!(v.get(1..).is_none());
+assert!(v.get(..8).is_none());
+
+// out of bounds
+assert!(v.get(..42).is_none());
+
1.20.0 · source

pub unsafe fn get_unchecked<I>(&self, i: I) -> &<I as SliceIndex<str>>::Outputwhere + I: SliceIndex<str>,

Returns an unchecked subslice of str.

+

This is the unchecked alternative to indexing the str.

+
Safety
+

Callers of this function are responsible that these preconditions are +satisfied:

+
    +
  • The starting index must not exceed the ending index;
  • +
  • Indexes must be within bounds of the original slice;
  • +
  • Indexes must lie on UTF-8 sequence boundaries.
  • +
+

Failing that, the returned string slice may reference invalid memory or +violate the invariants communicated by the str type.

+
Examples
+
let v = "🗻∈🌏";
+unsafe {
+    assert_eq!("🗻", v.get_unchecked(0..4));
+    assert_eq!("∈", v.get_unchecked(4..7));
+    assert_eq!("🌏", v.get_unchecked(7..11));
+}
+
1.0.0 · source

pub unsafe fn slice_unchecked(&self, begin: usize, end: usize) -> &str

👎Deprecated since 1.29.0: use get_unchecked(begin..end) instead

Creates a string slice from another string slice, bypassing safety +checks.

+

This is generally not recommended, use with caution! For a safe +alternative see str and Index.

+

This new slice goes from begin to end, including begin but +excluding end.

+

To get a mutable string slice instead, see the +slice_mut_unchecked method.

+
Safety
+

Callers of this function are responsible that three preconditions are +satisfied:

+
    +
  • begin must not exceed end.
  • +
  • begin and end must be byte positions within the string slice.
  • +
  • begin and end must lie on UTF-8 sequence boundaries.
  • +
+
Examples
+
let s = "Löwe 老虎 Léopard";
+
+unsafe {
+    assert_eq!("Löwe 老虎 Léopard", s.slice_unchecked(0, 21));
+}
+
+let s = "Hello, world!";
+
+unsafe {
+    assert_eq!("world", s.slice_unchecked(7, 12));
+}
+
1.4.0 · source

pub fn split_at(&self, mid: usize) -> (&str, &str)

Divide one string slice into two at an index.

+

The argument, mid, should be a byte offset from the start of the +string. It must also be on the boundary of a UTF-8 code point.

+

The two slices returned go from the start of the string slice to mid, +and from mid to the end of the string slice.

+

To get mutable string slices instead, see the split_at_mut +method.

+
Panics
+

Panics if mid is not on a UTF-8 code point boundary, or if it is +past the end of the last code point of the string slice.

+
Examples
+
let s = "Per Martin-Löf";
+
+let (first, last) = s.split_at(3);
+
+assert_eq!("Per", first);
+assert_eq!(" Martin-Löf", last);
+
1.0.0 · source

pub fn chars(&self) -> Chars<'_>

Returns an iterator over the chars of a string slice.

+

As a string slice consists of valid UTF-8, we can iterate through a +string slice by char. This method returns such an iterator.

+

It’s important to remember that char represents a Unicode Scalar +Value, and might not match your idea of what a ‘character’ is. Iteration +over grapheme clusters may be what you actually want. This functionality +is not provided by Rust’s standard library, check crates.io instead.

+
Examples
+

Basic usage:

+ +
let word = "goodbye";
+
+let count = word.chars().count();
+assert_eq!(7, count);
+
+let mut chars = word.chars();
+
+assert_eq!(Some('g'), chars.next());
+assert_eq!(Some('o'), chars.next());
+assert_eq!(Some('o'), chars.next());
+assert_eq!(Some('d'), chars.next());
+assert_eq!(Some('b'), chars.next());
+assert_eq!(Some('y'), chars.next());
+assert_eq!(Some('e'), chars.next());
+
+assert_eq!(None, chars.next());
+

Remember, chars might not match your intuition about characters:

+ +
let y = "y̆";
+
+let mut chars = y.chars();
+
+assert_eq!(Some('y'), chars.next()); // not 'y̆'
+assert_eq!(Some('\u{0306}'), chars.next());
+
+assert_eq!(None, chars.next());
+
1.0.0 · source

pub fn char_indices(&self) -> CharIndices<'_>

Returns an iterator over the chars of a string slice, and their +positions.

+

As a string slice consists of valid UTF-8, we can iterate through a +string slice by char. This method returns an iterator of both +these chars, as well as their byte positions.

+

The iterator yields tuples. The position is first, the char is +second.

+
Examples
+

Basic usage:

+ +
let word = "goodbye";
+
+let count = word.char_indices().count();
+assert_eq!(7, count);
+
+let mut char_indices = word.char_indices();
+
+assert_eq!(Some((0, 'g')), char_indices.next());
+assert_eq!(Some((1, 'o')), char_indices.next());
+assert_eq!(Some((2, 'o')), char_indices.next());
+assert_eq!(Some((3, 'd')), char_indices.next());
+assert_eq!(Some((4, 'b')), char_indices.next());
+assert_eq!(Some((5, 'y')), char_indices.next());
+assert_eq!(Some((6, 'e')), char_indices.next());
+
+assert_eq!(None, char_indices.next());
+

Remember, chars might not match your intuition about characters:

+ +
let yes = "y̆es";
+
+let mut char_indices = yes.char_indices();
+
+assert_eq!(Some((0, 'y')), char_indices.next()); // not (0, 'y̆')
+assert_eq!(Some((1, '\u{0306}')), char_indices.next());
+
+// note the 3 here - the last character took up two bytes
+assert_eq!(Some((3, 'e')), char_indices.next());
+assert_eq!(Some((4, 's')), char_indices.next());
+
+assert_eq!(None, char_indices.next());
+
1.0.0 · source

pub fn bytes(&self) -> Bytes<'_>

An iterator over the bytes of a string slice.

+

As a string slice consists of a sequence of bytes, we can iterate +through a string slice by byte. This method returns such an iterator.

+
Examples
+
let mut bytes = "bors".bytes();
+
+assert_eq!(Some(b'b'), bytes.next());
+assert_eq!(Some(b'o'), bytes.next());
+assert_eq!(Some(b'r'), bytes.next());
+assert_eq!(Some(b's'), bytes.next());
+
+assert_eq!(None, bytes.next());
+
1.1.0 · source

pub fn split_whitespace(&self) -> SplitWhitespace<'_>

Splits a string slice by whitespace.

+

The iterator returned will return string slices that are sub-slices of +the original string slice, separated by any amount of whitespace.

+

‘Whitespace’ is defined according to the terms of the Unicode Derived +Core Property White_Space. If you only want to split on ASCII whitespace +instead, use split_ascii_whitespace.

+
Examples
+

Basic usage:

+ +
let mut iter = "A few words".split_whitespace();
+
+assert_eq!(Some("A"), iter.next());
+assert_eq!(Some("few"), iter.next());
+assert_eq!(Some("words"), iter.next());
+
+assert_eq!(None, iter.next());
+

All kinds of whitespace are considered:

+ +
let mut iter = " Mary   had\ta\u{2009}little  \n\t lamb".split_whitespace();
+assert_eq!(Some("Mary"), iter.next());
+assert_eq!(Some("had"), iter.next());
+assert_eq!(Some("a"), iter.next());
+assert_eq!(Some("little"), iter.next());
+assert_eq!(Some("lamb"), iter.next());
+
+assert_eq!(None, iter.next());
+

If the string is empty or all whitespace, the iterator yields no string slices:

+ +
assert_eq!("".split_whitespace().next(), None);
+assert_eq!("   ".split_whitespace().next(), None);
+
1.34.0 · source

pub fn split_ascii_whitespace(&self) -> SplitAsciiWhitespace<'_>

Splits a string slice by ASCII whitespace.

+

The iterator returned will return string slices that are sub-slices of +the original string slice, separated by any amount of ASCII whitespace.

+

To split by Unicode Whitespace instead, use split_whitespace.

+
Examples
+

Basic usage:

+ +
let mut iter = "A few words".split_ascii_whitespace();
+
+assert_eq!(Some("A"), iter.next());
+assert_eq!(Some("few"), iter.next());
+assert_eq!(Some("words"), iter.next());
+
+assert_eq!(None, iter.next());
+

All kinds of ASCII whitespace are considered:

+ +
let mut iter = " Mary   had\ta little  \n\t lamb".split_ascii_whitespace();
+assert_eq!(Some("Mary"), iter.next());
+assert_eq!(Some("had"), iter.next());
+assert_eq!(Some("a"), iter.next());
+assert_eq!(Some("little"), iter.next());
+assert_eq!(Some("lamb"), iter.next());
+
+assert_eq!(None, iter.next());
+

If the string is empty or all ASCII whitespace, the iterator yields no string slices:

+ +
assert_eq!("".split_ascii_whitespace().next(), None);
+assert_eq!("   ".split_ascii_whitespace().next(), None);
+
1.0.0 · source

pub fn lines(&self) -> Lines<'_>

An iterator over the lines of a string, as string slices.

+

Lines are split at line endings that are either newlines (\n) or +sequences of a carriage return followed by a line feed (\r\n).

+

Line terminators are not included in the lines returned by the iterator.

+

The final line ending is optional. A string that ends with a final line +ending will return the same lines as an otherwise identical string +without a final line ending.

+
Examples
+

Basic usage:

+ +
let text = "foo\r\nbar\n\nbaz\n";
+let mut lines = text.lines();
+
+assert_eq!(Some("foo"), lines.next());
+assert_eq!(Some("bar"), lines.next());
+assert_eq!(Some(""), lines.next());
+assert_eq!(Some("baz"), lines.next());
+
+assert_eq!(None, lines.next());
+

The final line ending isn’t required:

+ +
let text = "foo\nbar\n\r\nbaz";
+let mut lines = text.lines();
+
+assert_eq!(Some("foo"), lines.next());
+assert_eq!(Some("bar"), lines.next());
+assert_eq!(Some(""), lines.next());
+assert_eq!(Some("baz"), lines.next());
+
+assert_eq!(None, lines.next());
+
1.0.0 · source

pub fn lines_any(&self) -> LinesAny<'_>

👎Deprecated since 1.4.0: use lines() instead now

An iterator over the lines of a string.

+
1.8.0 · source

pub fn encode_utf16(&self) -> EncodeUtf16<'_>

Returns an iterator of u16 over the string encoded as UTF-16.

+
Examples
+
let text = "Zażółć gęślą jaźń";
+
+let utf8_len = text.len();
+let utf16_len = text.encode_utf16().count();
+
+assert!(utf16_len <= utf8_len);
+
1.0.0 · source

pub fn contains<'a, P>(&'a self, pat: P) -> boolwhere + P: Pattern<'a>,

Returns true if the given pattern matches a sub-slice of +this string slice.

+

Returns false if it does not.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Examples
+
let bananas = "bananas";
+
+assert!(bananas.contains("nana"));
+assert!(!bananas.contains("apples"));
+
1.0.0 · source

pub fn starts_with<'a, P>(&'a self, pat: P) -> boolwhere + P: Pattern<'a>,

Returns true if the given pattern matches a prefix of this +string slice.

+

Returns false if it does not.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Examples
+
let bananas = "bananas";
+
+assert!(bananas.starts_with("bana"));
+assert!(!bananas.starts_with("nana"));
+
1.0.0 · source

pub fn ends_with<'a, P>(&'a self, pat: P) -> boolwhere + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,

Returns true if the given pattern matches a suffix of this +string slice.

+

Returns false if it does not.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Examples
+
let bananas = "bananas";
+
+assert!(bananas.ends_with("anas"));
+assert!(!bananas.ends_with("nana"));
+
1.0.0 · source

pub fn find<'a, P>(&'a self, pat: P) -> Option<usize>where + P: Pattern<'a>,

Returns the byte index of the first character of this string slice that +matches the pattern.

+

Returns None if the pattern doesn’t match.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Examples
+

Simple patterns:

+ +
let s = "Löwe 老虎 Léopard Gepardi";
+
+assert_eq!(s.find('L'), Some(0));
+assert_eq!(s.find('é'), Some(14));
+assert_eq!(s.find("pard"), Some(17));
+

More complex patterns using point-free style and closures:

+ +
let s = "Löwe 老虎 Léopard";
+
+assert_eq!(s.find(char::is_whitespace), Some(5));
+assert_eq!(s.find(char::is_lowercase), Some(1));
+assert_eq!(s.find(|c: char| c.is_whitespace() || c.is_lowercase()), Some(1));
+assert_eq!(s.find(|c: char| (c < 'o') && (c > 'a')), Some(4));
+

Not finding the pattern:

+ +
let s = "Löwe 老虎 Léopard";
+let x: &[_] = &['1', '2'];
+
+assert_eq!(s.find(x), None);
+
1.0.0 · source

pub fn rfind<'a, P>(&'a self, pat: P) -> Option<usize>where + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,

Returns the byte index for the first character of the last match of the pattern in +this string slice.

+

Returns None if the pattern doesn’t match.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Examples
+

Simple patterns:

+ +
let s = "Löwe 老虎 Léopard Gepardi";
+
+assert_eq!(s.rfind('L'), Some(13));
+assert_eq!(s.rfind('é'), Some(14));
+assert_eq!(s.rfind("pard"), Some(24));
+

More complex patterns with closures:

+ +
let s = "Löwe 老虎 Léopard";
+
+assert_eq!(s.rfind(char::is_whitespace), Some(12));
+assert_eq!(s.rfind(char::is_lowercase), Some(20));
+

Not finding the pattern:

+ +
let s = "Löwe 老虎 Léopard";
+let x: &[_] = &['1', '2'];
+
+assert_eq!(s.rfind(x), None);
+
1.0.0 · source

pub fn split<'a, P>(&'a self, pat: P) -> Split<'a, P>where + P: Pattern<'a>,

An iterator over substrings of this string slice, separated by +characters matched by a pattern.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Iterator behavior
+

The returned iterator will be a DoubleEndedIterator if the pattern +allows a reverse search and forward/reverse search yields the same +elements. This is true for, e.g., char, but not for &str.

+

If the pattern allows a reverse search but its results might differ +from a forward search, the rsplit method can be used.

+
Examples
+

Simple patterns:

+ +
let v: Vec<&str> = "Mary had a little lamb".split(' ').collect();
+assert_eq!(v, ["Mary", "had", "a", "little", "lamb"]);
+
+let v: Vec<&str> = "".split('X').collect();
+assert_eq!(v, [""]);
+
+let v: Vec<&str> = "lionXXtigerXleopard".split('X').collect();
+assert_eq!(v, ["lion", "", "tiger", "leopard"]);
+
+let v: Vec<&str> = "lion::tiger::leopard".split("::").collect();
+assert_eq!(v, ["lion", "tiger", "leopard"]);
+
+let v: Vec<&str> = "abc1def2ghi".split(char::is_numeric).collect();
+assert_eq!(v, ["abc", "def", "ghi"]);
+
+let v: Vec<&str> = "lionXtigerXleopard".split(char::is_uppercase).collect();
+assert_eq!(v, ["lion", "tiger", "leopard"]);
+

If the pattern is a slice of chars, split on each occurrence of any of the characters:

+ +
let v: Vec<&str> = "2020-11-03 23:59".split(&['-', ' ', ':', '@'][..]).collect();
+assert_eq!(v, ["2020", "11", "03", "23", "59"]);
+

A more complex pattern, using a closure:

+ +
let v: Vec<&str> = "abc1defXghi".split(|c| c == '1' || c == 'X').collect();
+assert_eq!(v, ["abc", "def", "ghi"]);
+

If a string contains multiple contiguous separators, you will end up +with empty strings in the output:

+ +
let x = "||||a||b|c".to_string();
+let d: Vec<_> = x.split('|').collect();
+
+assert_eq!(d, &["", "", "", "", "a", "", "b", "c"]);
+

Contiguous separators are separated by the empty string.

+ +
let x = "(///)".to_string();
+let d: Vec<_> = x.split('/').collect();
+
+assert_eq!(d, &["(", "", "", ")"]);
+

Separators at the start or end of a string are neighbored +by empty strings.

+ +
let d: Vec<_> = "010".split("0").collect();
+assert_eq!(d, &["", "1", ""]);
+

When the empty string is used as a separator, it separates +every character in the string, along with the beginning +and end of the string.

+ +
let f: Vec<_> = "rust".split("").collect();
+assert_eq!(f, &["", "r", "u", "s", "t", ""]);
+

Contiguous separators can lead to possibly surprising behavior +when whitespace is used as the separator. This code is correct:

+ +
let x = "    a  b c".to_string();
+let d: Vec<_> = x.split(' ').collect();
+
+assert_eq!(d, &["", "", "", "", "a", "", "b", "c"]);
+

It does not give you:

+ +
assert_eq!(d, &["a", "b", "c"]);
+

Use split_whitespace for this behavior.

+
1.51.0 · source

pub fn split_inclusive<'a, P>(&'a self, pat: P) -> SplitInclusive<'a, P>where + P: Pattern<'a>,

An iterator over substrings of this string slice, separated by +characters matched by a pattern. Differs from the iterator produced by +split in that split_inclusive leaves the matched part as the +terminator of the substring.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Examples
+
let v: Vec<&str> = "Mary had a little lamb\nlittle lamb\nlittle lamb."
+    .split_inclusive('\n').collect();
+assert_eq!(v, ["Mary had a little lamb\n", "little lamb\n", "little lamb."]);
+

If the last element of the string is matched, +that element will be considered the terminator of the preceding substring. +That substring will be the last item returned by the iterator.

+ +
let v: Vec<&str> = "Mary had a little lamb\nlittle lamb\nlittle lamb.\n"
+    .split_inclusive('\n').collect();
+assert_eq!(v, ["Mary had a little lamb\n", "little lamb\n", "little lamb.\n"]);
+
1.0.0 · source

pub fn rsplit<'a, P>(&'a self, pat: P) -> RSplit<'a, P>where + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,

An iterator over substrings of the given string slice, separated by +characters matched by a pattern and yielded in reverse order.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Iterator behavior
+

The returned iterator requires that the pattern supports a reverse +search, and it will be a DoubleEndedIterator if a forward/reverse +search yields the same elements.

+

For iterating from the front, the split method can be used.

+
Examples
+

Simple patterns:

+ +
let v: Vec<&str> = "Mary had a little lamb".rsplit(' ').collect();
+assert_eq!(v, ["lamb", "little", "a", "had", "Mary"]);
+
+let v: Vec<&str> = "".rsplit('X').collect();
+assert_eq!(v, [""]);
+
+let v: Vec<&str> = "lionXXtigerXleopard".rsplit('X').collect();
+assert_eq!(v, ["leopard", "tiger", "", "lion"]);
+
+let v: Vec<&str> = "lion::tiger::leopard".rsplit("::").collect();
+assert_eq!(v, ["leopard", "tiger", "lion"]);
+

A more complex pattern, using a closure:

+ +
let v: Vec<&str> = "abc1defXghi".rsplit(|c| c == '1' || c == 'X').collect();
+assert_eq!(v, ["ghi", "def", "abc"]);
+
1.0.0 · source

pub fn split_terminator<'a, P>(&'a self, pat: P) -> SplitTerminator<'a, P>where + P: Pattern<'a>,

An iterator over substrings of the given string slice, separated by +characters matched by a pattern.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+

Equivalent to split, except that the trailing substring +is skipped if empty.

+

This method can be used for string data that is terminated, +rather than separated by a pattern.

+
Iterator behavior
+

The returned iterator will be a DoubleEndedIterator if the pattern +allows a reverse search and forward/reverse search yields the same +elements. This is true for, e.g., char, but not for &str.

+

If the pattern allows a reverse search but its results might differ +from a forward search, the rsplit_terminator method can be used.

+
Examples
+
let v: Vec<&str> = "A.B.".split_terminator('.').collect();
+assert_eq!(v, ["A", "B"]);
+
+let v: Vec<&str> = "A..B..".split_terminator(".").collect();
+assert_eq!(v, ["A", "", "B", ""]);
+
+let v: Vec<&str> = "A.B:C.D".split_terminator(&['.', ':'][..]).collect();
+assert_eq!(v, ["A", "B", "C", "D"]);
+
1.0.0 · source

pub fn rsplit_terminator<'a, P>(&'a self, pat: P) -> RSplitTerminator<'a, P>where + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,

An iterator over substrings of self, separated by characters +matched by a pattern and yielded in reverse order.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+

Equivalent to split, except that the trailing substring is +skipped if empty.

+

This method can be used for string data that is terminated, +rather than separated by a pattern.

+
Iterator behavior
+

The returned iterator requires that the pattern supports a +reverse search, and it will be double ended if a forward/reverse +search yields the same elements.

+

For iterating from the front, the split_terminator method can be +used.

+
Examples
+
let v: Vec<&str> = "A.B.".rsplit_terminator('.').collect();
+assert_eq!(v, ["B", "A"]);
+
+let v: Vec<&str> = "A..B..".rsplit_terminator(".").collect();
+assert_eq!(v, ["", "B", "", "A"]);
+
+let v: Vec<&str> = "A.B:C.D".rsplit_terminator(&['.', ':'][..]).collect();
+assert_eq!(v, ["D", "C", "B", "A"]);
+
1.0.0 · source

pub fn splitn<'a, P>(&'a self, n: usize, pat: P) -> SplitN<'a, P>where + P: Pattern<'a>,

An iterator over substrings of the given string slice, separated by a +pattern, restricted to returning at most n items.

+

If n substrings are returned, the last substring (the nth substring) +will contain the remainder of the string.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Iterator behavior
+

The returned iterator will not be double ended, because it is +not efficient to support.

+

If the pattern allows a reverse search, the rsplitn method can be +used.

+
Examples
+

Simple patterns:

+ +
let v: Vec<&str> = "Mary had a little lambda".splitn(3, ' ').collect();
+assert_eq!(v, ["Mary", "had", "a little lambda"]);
+
+let v: Vec<&str> = "lionXXtigerXleopard".splitn(3, "X").collect();
+assert_eq!(v, ["lion", "", "tigerXleopard"]);
+
+let v: Vec<&str> = "abcXdef".splitn(1, 'X').collect();
+assert_eq!(v, ["abcXdef"]);
+
+let v: Vec<&str> = "".splitn(1, 'X').collect();
+assert_eq!(v, [""]);
+

A more complex pattern, using a closure:

+ +
let v: Vec<&str> = "abc1defXghi".splitn(2, |c| c == '1' || c == 'X').collect();
+assert_eq!(v, ["abc", "defXghi"]);
+
1.0.0 · source

pub fn rsplitn<'a, P>(&'a self, n: usize, pat: P) -> RSplitN<'a, P>where + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,

An iterator over substrings of this string slice, separated by a +pattern, starting from the end of the string, restricted to returning +at most n items.

+

If n substrings are returned, the last substring (the nth substring) +will contain the remainder of the string.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Iterator behavior
+

The returned iterator will not be double ended, because it is not +efficient to support.

+

For splitting from the front, the splitn method can be used.

+
Examples
+

Simple patterns:

+ +
let v: Vec<&str> = "Mary had a little lamb".rsplitn(3, ' ').collect();
+assert_eq!(v, ["lamb", "little", "Mary had a"]);
+
+let v: Vec<&str> = "lionXXtigerXleopard".rsplitn(3, 'X').collect();
+assert_eq!(v, ["leopard", "tiger", "lionX"]);
+
+let v: Vec<&str> = "lion::tiger::leopard".rsplitn(2, "::").collect();
+assert_eq!(v, ["leopard", "lion::tiger"]);
+

A more complex pattern, using a closure:

+ +
let v: Vec<&str> = "abc1defXghi".rsplitn(2, |c| c == '1' || c == 'X').collect();
+assert_eq!(v, ["ghi", "abc1def"]);
+
1.52.0 · source

pub fn split_once<'a, P>(&'a self, delimiter: P) -> Option<(&'a str, &'a str)>where + P: Pattern<'a>,

Splits the string on the first occurrence of the specified delimiter and +returns prefix before delimiter and suffix after delimiter.

+
Examples
+
assert_eq!("cfg".split_once('='), None);
+assert_eq!("cfg=".split_once('='), Some(("cfg", "")));
+assert_eq!("cfg=foo".split_once('='), Some(("cfg", "foo")));
+assert_eq!("cfg=foo=bar".split_once('='), Some(("cfg", "foo=bar")));
+
1.52.0 · source

pub fn rsplit_once<'a, P>(&'a self, delimiter: P) -> Option<(&'a str, &'a str)>where + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,

Splits the string on the last occurrence of the specified delimiter and +returns prefix before delimiter and suffix after delimiter.

+
Examples
+
assert_eq!("cfg".rsplit_once('='), None);
+assert_eq!("cfg=foo".rsplit_once('='), Some(("cfg", "foo")));
+assert_eq!("cfg=foo=bar".rsplit_once('='), Some(("cfg=foo", "bar")));
+
1.2.0 · source

pub fn matches<'a, P>(&'a self, pat: P) -> Matches<'a, P>where + P: Pattern<'a>,

An iterator over the disjoint matches of a pattern within the given string +slice.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Iterator behavior
+

The returned iterator will be a DoubleEndedIterator if the pattern +allows a reverse search and forward/reverse search yields the same +elements. This is true for, e.g., char, but not for &str.

+

If the pattern allows a reverse search but its results might differ +from a forward search, the rmatches method can be used.

+
Examples
+
let v: Vec<&str> = "abcXXXabcYYYabc".matches("abc").collect();
+assert_eq!(v, ["abc", "abc", "abc"]);
+
+let v: Vec<&str> = "1abc2abc3".matches(char::is_numeric).collect();
+assert_eq!(v, ["1", "2", "3"]);
+
1.2.0 · source

pub fn rmatches<'a, P>(&'a self, pat: P) -> RMatches<'a, P>where + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,

An iterator over the disjoint matches of a pattern within this string slice, +yielded in reverse order.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Iterator behavior
+

The returned iterator requires that the pattern supports a reverse +search, and it will be a DoubleEndedIterator if a forward/reverse +search yields the same elements.

+

For iterating from the front, the matches method can be used.

+
Examples
+
let v: Vec<&str> = "abcXXXabcYYYabc".rmatches("abc").collect();
+assert_eq!(v, ["abc", "abc", "abc"]);
+
+let v: Vec<&str> = "1abc2abc3".rmatches(char::is_numeric).collect();
+assert_eq!(v, ["3", "2", "1"]);
+
1.5.0 · source

pub fn match_indices<'a, P>(&'a self, pat: P) -> MatchIndices<'a, P>where + P: Pattern<'a>,

An iterator over the disjoint matches of a pattern within this string +slice as well as the index that the match starts at.

+

For matches of pat within self that overlap, only the indices +corresponding to the first match are returned.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Iterator behavior
+

The returned iterator will be a DoubleEndedIterator if the pattern +allows a reverse search and forward/reverse search yields the same +elements. This is true for, e.g., char, but not for &str.

+

If the pattern allows a reverse search but its results might differ +from a forward search, the rmatch_indices method can be used.

+
Examples
+
let v: Vec<_> = "abcXXXabcYYYabc".match_indices("abc").collect();
+assert_eq!(v, [(0, "abc"), (6, "abc"), (12, "abc")]);
+
+let v: Vec<_> = "1abcabc2".match_indices("abc").collect();
+assert_eq!(v, [(1, "abc"), (4, "abc")]);
+
+let v: Vec<_> = "ababa".match_indices("aba").collect();
+assert_eq!(v, [(0, "aba")]); // only the first `aba`
+
1.5.0 · source

pub fn rmatch_indices<'a, P>(&'a self, pat: P) -> RMatchIndices<'a, P>where + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,

An iterator over the disjoint matches of a pattern within self, +yielded in reverse order along with the index of the match.

+

For matches of pat within self that overlap, only the indices +corresponding to the last match are returned.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Iterator behavior
+

The returned iterator requires that the pattern supports a reverse +search, and it will be a DoubleEndedIterator if a forward/reverse +search yields the same elements.

+

For iterating from the front, the match_indices method can be used.

+
Examples
+
let v: Vec<_> = "abcXXXabcYYYabc".rmatch_indices("abc").collect();
+assert_eq!(v, [(12, "abc"), (6, "abc"), (0, "abc")]);
+
+let v: Vec<_> = "1abcabc2".rmatch_indices("abc").collect();
+assert_eq!(v, [(4, "abc"), (1, "abc")]);
+
+let v: Vec<_> = "ababa".rmatch_indices("aba").collect();
+assert_eq!(v, [(2, "aba")]); // only the last `aba`
+
1.0.0 · source

pub fn trim(&self) -> &str

Returns a string slice with leading and trailing whitespace removed.

+

‘Whitespace’ is defined according to the terms of the Unicode Derived +Core Property White_Space, which includes newlines.

+
Examples
+
let s = "\n Hello\tworld\t\n";
+
+assert_eq!("Hello\tworld", s.trim());
+
1.30.0 · source

pub fn trim_start(&self) -> &str

Returns a string slice with leading whitespace removed.

+

‘Whitespace’ is defined according to the terms of the Unicode Derived +Core Property White_Space, which includes newlines.

+
Text directionality
+

A string is a sequence of bytes. start in this context means the first +position of that byte string; for a left-to-right language like English or +Russian, this will be left side, and for right-to-left languages like +Arabic or Hebrew, this will be the right side.

+
Examples
+

Basic usage:

+ +
let s = "\n Hello\tworld\t\n";
+assert_eq!("Hello\tworld\t\n", s.trim_start());
+

Directionality:

+ +
let s = "  English  ";
+assert!(Some('E') == s.trim_start().chars().next());
+
+let s = "  עברית  ";
+assert!(Some('ע') == s.trim_start().chars().next());
+
1.30.0 · source

pub fn trim_end(&self) -> &str

Returns a string slice with trailing whitespace removed.

+

‘Whitespace’ is defined according to the terms of the Unicode Derived +Core Property White_Space, which includes newlines.

+
Text directionality
+

A string is a sequence of bytes. end in this context means the last +position of that byte string; for a left-to-right language like English or +Russian, this will be right side, and for right-to-left languages like +Arabic or Hebrew, this will be the left side.

+
Examples
+

Basic usage:

+ +
let s = "\n Hello\tworld\t\n";
+assert_eq!("\n Hello\tworld", s.trim_end());
+

Directionality:

+ +
let s = "  English  ";
+assert!(Some('h') == s.trim_end().chars().rev().next());
+
+let s = "  עברית  ";
+assert!(Some('ת') == s.trim_end().chars().rev().next());
+
1.0.0 · source

pub fn trim_left(&self) -> &str

👎Deprecated since 1.33.0: superseded by trim_start

Returns a string slice with leading whitespace removed.

+

‘Whitespace’ is defined according to the terms of the Unicode Derived +Core Property White_Space.

+
Text directionality
+

A string is a sequence of bytes. ‘Left’ in this context means the first +position of that byte string; for a language like Arabic or Hebrew +which are ‘right to left’ rather than ‘left to right’, this will be +the right side, not the left.

+
Examples
+

Basic usage:

+ +
let s = " Hello\tworld\t";
+
+assert_eq!("Hello\tworld\t", s.trim_left());
+

Directionality:

+ +
let s = "  English";
+assert!(Some('E') == s.trim_left().chars().next());
+
+let s = "  עברית";
+assert!(Some('ע') == s.trim_left().chars().next());
+
1.0.0 · source

pub fn trim_right(&self) -> &str

👎Deprecated since 1.33.0: superseded by trim_end

Returns a string slice with trailing whitespace removed.

+

‘Whitespace’ is defined according to the terms of the Unicode Derived +Core Property White_Space.

+
Text directionality
+

A string is a sequence of bytes. ‘Right’ in this context means the last +position of that byte string; for a language like Arabic or Hebrew +which are ‘right to left’ rather than ‘left to right’, this will be +the left side, not the right.

+
Examples
+

Basic usage:

+ +
let s = " Hello\tworld\t";
+
+assert_eq!(" Hello\tworld", s.trim_right());
+

Directionality:

+ +
let s = "English  ";
+assert!(Some('h') == s.trim_right().chars().rev().next());
+
+let s = "עברית  ";
+assert!(Some('ת') == s.trim_right().chars().rev().next());
+
1.0.0 · source

pub fn trim_matches<'a, P>(&'a self, pat: P) -> &'a strwhere + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: DoubleEndedSearcher<'a>,

Returns a string slice with all prefixes and suffixes that match a +pattern repeatedly removed.

+

The pattern can be a char, a slice of chars, or a function +or closure that determines if a character matches.

+
Examples
+

Simple patterns:

+ +
assert_eq!("11foo1bar11".trim_matches('1'), "foo1bar");
+assert_eq!("123foo1bar123".trim_matches(char::is_numeric), "foo1bar");
+
+let x: &[_] = &['1', '2'];
+assert_eq!("12foo1bar12".trim_matches(x), "foo1bar");
+

A more complex pattern, using a closure:

+ +
assert_eq!("1foo1barXX".trim_matches(|c| c == '1' || c == 'X'), "foo1bar");
+
1.30.0 · source

pub fn trim_start_matches<'a, P>(&'a self, pat: P) -> &'a strwhere + P: Pattern<'a>,

Returns a string slice with all prefixes that match a pattern +repeatedly removed.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Text directionality
+

A string is a sequence of bytes. start in this context means the first +position of that byte string; for a left-to-right language like English or +Russian, this will be left side, and for right-to-left languages like +Arabic or Hebrew, this will be the right side.

+
Examples
+
assert_eq!("11foo1bar11".trim_start_matches('1'), "foo1bar11");
+assert_eq!("123foo1bar123".trim_start_matches(char::is_numeric), "foo1bar123");
+
+let x: &[_] = &['1', '2'];
+assert_eq!("12foo1bar12".trim_start_matches(x), "foo1bar12");
+
1.45.0 · source

pub fn strip_prefix<'a, P>(&'a self, prefix: P) -> Option<&'a str>where + P: Pattern<'a>,

Returns a string slice with the prefix removed.

+

If the string starts with the pattern prefix, returns substring after the prefix, wrapped +in Some. Unlike trim_start_matches, this method removes the prefix exactly once.

+

If the string does not start with prefix, returns None.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Examples
+
assert_eq!("foo:bar".strip_prefix("foo:"), Some("bar"));
+assert_eq!("foo:bar".strip_prefix("bar"), None);
+assert_eq!("foofoo".strip_prefix("foo"), Some("foo"));
+
1.45.0 · source

pub fn strip_suffix<'a, P>(&'a self, suffix: P) -> Option<&'a str>where + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,

Returns a string slice with the suffix removed.

+

If the string ends with the pattern suffix, returns the substring before the suffix, +wrapped in Some. Unlike trim_end_matches, this method removes the suffix exactly once.

+

If the string does not end with suffix, returns None.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Examples
+
assert_eq!("bar:foo".strip_suffix(":foo"), Some("bar"));
+assert_eq!("bar:foo".strip_suffix("bar"), None);
+assert_eq!("foofoo".strip_suffix("foo"), Some("foo"));
+
1.30.0 · source

pub fn trim_end_matches<'a, P>(&'a self, pat: P) -> &'a strwhere + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,

Returns a string slice with all suffixes that match a pattern +repeatedly removed.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Text directionality
+

A string is a sequence of bytes. end in this context means the last +position of that byte string; for a left-to-right language like English or +Russian, this will be right side, and for right-to-left languages like +Arabic or Hebrew, this will be the left side.

+
Examples
+

Simple patterns:

+ +
assert_eq!("11foo1bar11".trim_end_matches('1'), "11foo1bar");
+assert_eq!("123foo1bar123".trim_end_matches(char::is_numeric), "123foo1bar");
+
+let x: &[_] = &['1', '2'];
+assert_eq!("12foo1bar12".trim_end_matches(x), "12foo1bar");
+

A more complex pattern, using a closure:

+ +
assert_eq!("1fooX".trim_end_matches(|c| c == '1' || c == 'X'), "1foo");
+
1.0.0 · source

pub fn trim_left_matches<'a, P>(&'a self, pat: P) -> &'a strwhere + P: Pattern<'a>,

👎Deprecated since 1.33.0: superseded by trim_start_matches

Returns a string slice with all prefixes that match a pattern +repeatedly removed.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Text directionality
+

A string is a sequence of bytes. ‘Left’ in this context means the first +position of that byte string; for a language like Arabic or Hebrew +which are ‘right to left’ rather than ‘left to right’, this will be +the right side, not the left.

+
Examples
+
assert_eq!("11foo1bar11".trim_left_matches('1'), "foo1bar11");
+assert_eq!("123foo1bar123".trim_left_matches(char::is_numeric), "foo1bar123");
+
+let x: &[_] = &['1', '2'];
+assert_eq!("12foo1bar12".trim_left_matches(x), "foo1bar12");
+
1.0.0 · source

pub fn trim_right_matches<'a, P>(&'a self, pat: P) -> &'a strwhere + P: Pattern<'a>, + <P as Pattern<'a>>::Searcher: ReverseSearcher<'a>,

👎Deprecated since 1.33.0: superseded by trim_end_matches

Returns a string slice with all suffixes that match a pattern +repeatedly removed.

+

The pattern can be a &str, char, a slice of chars, or a +function or closure that determines if a character matches.

+
Text directionality
+

A string is a sequence of bytes. ‘Right’ in this context means the last +position of that byte string; for a language like Arabic or Hebrew +which are ‘right to left’ rather than ‘left to right’, this will be +the left side, not the right.

+
Examples
+

Simple patterns:

+ +
assert_eq!("11foo1bar11".trim_right_matches('1'), "11foo1bar");
+assert_eq!("123foo1bar123".trim_right_matches(char::is_numeric), "123foo1bar");
+
+let x: &[_] = &['1', '2'];
+assert_eq!("12foo1bar12".trim_right_matches(x), "12foo1bar");
+

A more complex pattern, using a closure:

+ +
assert_eq!("1fooX".trim_right_matches(|c| c == '1' || c == 'X'), "1foo");
+
1.0.0 · source

pub fn parse<F>(&self) -> Result<F, <F as FromStr>::Err>where + F: FromStr,

Parses this string slice into another type.

+

Because parse is so general, it can cause problems with type +inference. As such, parse is one of the few times you’ll see +the syntax affectionately known as the ‘turbofish’: ::<>. This +helps the inference algorithm understand specifically which type +you’re trying to parse into.

+

parse can parse into any type that implements the FromStr trait.

+
Errors
+

Will return Err if it’s not possible to parse this string slice into +the desired type.

+
Examples
+

Basic usage

+ +
let four: u32 = "4".parse().unwrap();
+
+assert_eq!(4, four);
+

Using the ‘turbofish’ instead of annotating four:

+ +
let four = "4".parse::<u32>();
+
+assert_eq!(Ok(4), four);
+

Failing to parse:

+ +
let nope = "j".parse::<u32>();
+
+assert!(nope.is_err());
+
1.23.0 · source

pub fn is_ascii(&self) -> bool

Checks if all characters in this string are within the ASCII range.

+
Examples
+
let ascii = "hello!\n";
+let non_ascii = "Grüße, Jürgen ❤";
+
+assert!(ascii.is_ascii());
+assert!(!non_ascii.is_ascii());
+
source

pub fn as_ascii(&self) -> Option<&[AsciiChar]>

🔬This is a nightly-only experimental API. (ascii_char)

If this string slice is_ascii, returns it as a slice +of ASCII characters, otherwise returns None.

+
1.23.0 · source

pub fn eq_ignore_ascii_case(&self, other: &str) -> bool

Checks that two strings are an ASCII case-insensitive match.

+

Same as to_ascii_lowercase(a) == to_ascii_lowercase(b), +but without allocating and copying temporaries.

+
Examples
+
assert!("Ferris".eq_ignore_ascii_case("FERRIS"));
+assert!("Ferrös".eq_ignore_ascii_case("FERRöS"));
+assert!(!"Ferrös".eq_ignore_ascii_case("FERRÖS"));
+
1.34.0 · source

pub fn escape_debug(&self) -> EscapeDebug<'_>

Return an iterator that escapes each char in self with char::escape_debug.

+

Note: only extended grapheme codepoints that begin the string will be +escaped.

+
Examples
+

As an iterator:

+ +
for c in "❤\n!".escape_debug() {
+    print!("{c}");
+}
+println!();
+

Using println! directly:

+ +
println!("{}", "❤\n!".escape_debug());
+

Both are equivalent to:

+ +
println!("❤\\n!");
+

Using to_string:

+ +
assert_eq!("❤\n!".escape_debug().to_string(), "❤\\n!");
+
1.34.0 · source

pub fn escape_default(&self) -> EscapeDefault<'_>

Return an iterator that escapes each char in self with char::escape_default.

+
Examples
+

As an iterator:

+ +
for c in "❤\n!".escape_default() {
+    print!("{c}");
+}
+println!();
+

Using println! directly:

+ +
println!("{}", "❤\n!".escape_default());
+

Both are equivalent to:

+ +
println!("\\u{{2764}}\\n!");
+

Using to_string:

+ +
assert_eq!("❤\n!".escape_default().to_string(), "\\u{2764}\\n!");
+
1.34.0 · source

pub fn escape_unicode(&self) -> EscapeUnicode<'_>

Return an iterator that escapes each char in self with char::escape_unicode.

+
Examples
+

As an iterator:

+ +
for c in "❤\n!".escape_unicode() {
+    print!("{c}");
+}
+println!();
+

Using println! directly:

+ +
println!("{}", "❤\n!".escape_unicode());
+

Both are equivalent to:

+ +
println!("\\u{{2764}}\\u{{a}}\\u{{21}}");
+

Using to_string:

+ +
assert_eq!("❤\n!".escape_unicode().to_string(), "\\u{2764}\\u{a}\\u{21}");
+
1.0.0 · source

pub fn replace<'a, P>(&'a self, from: P, to: &str) -> Stringwhere + P: Pattern<'a>,

Replaces all matches of a pattern with another string.

+

replace creates a new String, and copies the data from this string slice into it. +While doing so, it attempts to find matches of a pattern. If it finds any, it +replaces them with the replacement string slice.

+
Examples
+

Basic usage:

+ +
let s = "this is old";
+
+assert_eq!("this is new", s.replace("old", "new"));
+assert_eq!("than an old", s.replace("is", "an"));
+

When the pattern doesn’t match, it returns this string slice as String:

+ +
let s = "this is old";
+assert_eq!(s, s.replace("cookie monster", "little lamb"));
+
1.16.0 · source

pub fn replacen<'a, P>(&'a self, pat: P, to: &str, count: usize) -> Stringwhere + P: Pattern<'a>,

Replaces first N matches of a pattern with another string.

+

replacen creates a new String, and copies the data from this string slice into it. +While doing so, it attempts to find matches of a pattern. If it finds any, it +replaces them with the replacement string slice at most count times.

+
Examples
+

Basic usage:

+ +
let s = "foo foo 123 foo";
+assert_eq!("new new 123 foo", s.replacen("foo", "new", 2));
+assert_eq!("faa fao 123 foo", s.replacen('o', "a", 3));
+assert_eq!("foo foo new23 foo", s.replacen(char::is_numeric, "new", 1));
+

When the pattern doesn’t match, it returns this string slice as String:

+ +
let s = "this is old";
+assert_eq!(s, s.replacen("cookie monster", "little lamb", 10));
+
1.2.0 · source

pub fn to_lowercase(&self) -> String

Returns the lowercase equivalent of this string slice, as a new String.

+

‘Lowercase’ is defined according to the terms of the Unicode Derived Core Property +Lowercase.

+

Since some characters can expand into multiple characters when changing +the case, this function returns a String instead of modifying the +parameter in-place.

+
Examples
+

Basic usage:

+ +
let s = "HELLO";
+
+assert_eq!("hello", s.to_lowercase());
+

A tricky example, with sigma:

+ +
let sigma = "Σ";
+
+assert_eq!("σ", sigma.to_lowercase());
+
+// but at the end of a word, it's ς, not σ:
+let odysseus = "ὈΔΥΣΣΕΎΣ";
+
+assert_eq!("ὀδυσσεύς", odysseus.to_lowercase());
+

Languages without case are not changed:

+ +
let new_year = "农历新年";
+
+assert_eq!(new_year, new_year.to_lowercase());
+
1.2.0 · source

pub fn to_uppercase(&self) -> String

Returns the uppercase equivalent of this string slice, as a new String.

+

‘Uppercase’ is defined according to the terms of the Unicode Derived Core Property +Uppercase.

+

Since some characters can expand into multiple characters when changing +the case, this function returns a String instead of modifying the +parameter in-place.

+
Examples
+

Basic usage:

+ +
let s = "hello";
+
+assert_eq!("HELLO", s.to_uppercase());
+

Scripts without case are not changed:

+ +
let new_year = "农历新年";
+
+assert_eq!(new_year, new_year.to_uppercase());
+

One character can become multiple:

+ +
let s = "tschüß";
+
+assert_eq!("TSCHÜSS", s.to_uppercase());
+
1.16.0 · source

pub fn repeat(&self, n: usize) -> String

Creates a new String by repeating a string n times.

+
Panics
+

This function will panic if the capacity would overflow.

+
Examples
+

Basic usage:

+ +
assert_eq!("abc".repeat(4), String::from("abcabcabcabc"));
+

A panic upon overflow:

+ +
// this will panic at runtime
+let huge = "0123456789abcdef".repeat(usize::MAX);
+
1.23.0 · source

pub fn to_ascii_uppercase(&self) -> String

Returns a copy of this string where each character is mapped to its +ASCII upper case equivalent.

+

ASCII letters ‘a’ to ‘z’ are mapped to ‘A’ to ‘Z’, +but non-ASCII letters are unchanged.

+

To uppercase the value in-place, use make_ascii_uppercase.

+

To uppercase ASCII characters in addition to non-ASCII characters, use +to_uppercase.

+
Examples
+
let s = "Grüße, Jürgen ❤";
+
+assert_eq!("GRüßE, JüRGEN ❤", s.to_ascii_uppercase());
+
1.23.0 · source

pub fn to_ascii_lowercase(&self) -> String

Returns a copy of this string where each character is mapped to its +ASCII lower case equivalent.

+

ASCII letters ‘A’ to ‘Z’ are mapped to ‘a’ to ‘z’, +but non-ASCII letters are unchanged.

+

To lowercase the value in-place, use make_ascii_lowercase.

+

To lowercase ASCII characters in addition to non-ASCII characters, use +to_lowercase.

+
Examples
+
let s = "Grüße, Jürgen ❤";
+
+assert_eq!("grüße, jürgen ❤", s.to_ascii_lowercase());
+

Trait Implementations§

source§

impl AsRef<[u8]> for HeaderName

source§

fn as_ref(&self) -> &[u8]

Converts this type into a shared reference of the (usually inferred) input type.
source§

impl AsRef<str> for HeaderName

source§

fn as_ref(&self) -> &str

Converts this type into a shared reference of the (usually inferred) input type.
source§

impl Clone for HeaderName

source§

fn clone(&self) -> HeaderName

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for HeaderName

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Deref for HeaderName

§

type Target = str

The resulting type after dereferencing.
source§

fn deref(&self) -> &str

Dereferences the value.
source§

impl Display for HeaderName

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl From<&'static str> for HeaderName

source§

fn from(s: &'static str) -> HeaderName

Converts to this type from the input type.
source§

impl From<Cow<'static, str>> for HeaderName

source§

fn from(s: Cow<'static, str>) -> HeaderName

Converts to this type from the input type.
source§

impl From<HeaderName> for Cow<'static, str>

source§

fn from(h: HeaderName) -> Self

Converts to this type from the input type.
source§

impl From<HeaderName> for String

source§

fn from(h: HeaderName) -> Self

Converts to this type from the input type.
source§

impl From<HeaderName> for Vec<u8>

source§

fn from(h: HeaderName) -> Self

Converts to this type from the input type.
source§

impl From<String> for HeaderName

source§

fn from(s: String) -> HeaderName

Converts to this type from the input type.
source§

impl Hash for HeaderName

source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · source§

fn hash_slice<H>(data: &[Self], state: &mut H)where + H: Hasher, + Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
source§

impl Ord for HeaderName

source§

fn cmp(&self, other: &HeaderName) -> Ordering

This method returns an Ordering between self and other. Read more
1.21.0 · source§

fn max(self, other: Self) -> Selfwhere + Self: Sized,

Compares and returns the maximum of two values. Read more
1.21.0 · source§

fn min(self, other: Self) -> Selfwhere + Self: Sized,

Compares and returns the minimum of two values. Read more
1.50.0 · source§

fn clamp(self, min: Self, max: Self) -> Selfwhere + Self: Sized + PartialOrd<Self>,

Restrict a value to a certain interval. Read more
source§

impl<'a> PartialEq<&'a String> for HeaderName

source§

fn eq(&self, other: &&'a String) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl<'a> PartialEq<&'a str> for HeaderName

source§

fn eq(&self, other: &&'a str) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl<'a> PartialEq<Cow<'a, str>> for HeaderName

source§

fn eq(&self, other: &Cow<'a, str>) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl<'a> PartialEq<HeaderName> for &'a String

source§

fn eq(&self, other: &HeaderName) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl<'a> PartialEq<HeaderName> for &'a str

source§

fn eq(&self, other: &HeaderName) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl<'a> PartialEq<HeaderName> for Cow<'a, str>

source§

fn eq(&self, other: &HeaderName) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl PartialEq<HeaderName> for HeaderName

source§

fn eq(&self, other: &HeaderName) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl<'a> PartialEq<HeaderName> for String

source§

fn eq(&self, other: &HeaderName) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl<'a> PartialEq<HeaderName> for str

source§

fn eq(&self, other: &HeaderName) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl<'a> PartialEq<String> for HeaderName

source§

fn eq(&self, other: &String) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl<'a> PartialEq<str> for HeaderName

source§

fn eq(&self, other: &str) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl PartialOrd<HeaderName> for HeaderName

source§

fn partial_cmp(&self, other: &HeaderName) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · source§

fn lt(&self, other: &Rhs) -> bool

This method tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · source§

fn le(&self, other: &Rhs) -> bool

This method tests less than or equal to (for self and other) and is used by the <= +operator. Read more
1.0.0 · source§

fn gt(&self, other: &Rhs) -> bool

This method tests greater than (for self and other) and is used by the > operator. Read more
1.0.0 · source§

fn ge(&self, other: &Rhs) -> bool

This method tests greater than or equal to (for self and other) and is used by the >= +operator. Read more
source§

impl Eq for HeaderName

source§

impl StructuralEq for HeaderName

source§

impl StructuralPartialEq for HeaderName

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/viaduct/struct.Headers.html b/book/rust-docs/viaduct/struct.Headers.html new file mode 100644 index 0000000000..fe60caf22c --- /dev/null +++ b/book/rust-docs/viaduct/struct.Headers.html @@ -0,0 +1,100 @@ +Headers in viaduct - Rust

Struct viaduct::Headers

source ·
pub struct Headers { /* private fields */ }
Expand description

A list of headers.

+

Implementations§

source§

impl Headers

source

pub fn new() -> Self

Initialize an empty list of headers.

+
source

pub fn with_capacity(c: usize) -> Self

Initialize an empty list of headers backed by a vector with the provided +capacity.

+
source

pub fn into_vec(self) -> Vec<Header>

Convert this list of headers to a Vec

+
source

pub fn len(&self) -> usize

Returns the number of headers.

+
source

pub fn is_empty(&self) -> bool

Returns true if len() is zero.

+
source

pub fn clear(&mut self)

Clear this set of headers.

+
source

pub fn insert<N, V>(&mut self, name: N, value: V) -> Result<&mut Self, Error>where + N: Into<HeaderName> + PartialEq<HeaderName>, + V: Into<String> + AsRef<str>,

Insert or update a new header.

+

This returns an error if you attempt to specify a header with an +invalid value (values must be printable ASCII and may not contain +\r or \n)

+
Example
+
let mut h = Headers::new();
+h.insert("My-Cool-Header", "example")?;
+assert_eq!(h.get("My-Cool-Header"), Some("example"));
+
+// Note: names are sensitive
+assert_eq!(h.get("my-cool-header"), Some("example"));
+
+// Also note, constants for headers are in `viaduct::header_names`, and
+// you can chain the result of this function.
+h.insert(viaduct::header_names::CONTENT_TYPE, "something...")?
+ .insert("Something-Else", "etc")?;
+
source

pub fn insert_if_missing<N, V>( + &mut self, + name: N, + value: V +) -> Result<&mut Self, Error>where + N: Into<HeaderName> + PartialEq<HeaderName>, + V: Into<String> + AsRef<str>,

Insert the provided header unless a header is already specified. +Mostly used internally, e.g. to set “Content-Type: application/json” +in Request::json() unless it has been set specifically.

+
source

pub fn insert_header(&mut self, new: Header) -> &mut Self

Insert or update a header directly. Typically you will want to use +insert over this, as it performs less work if the header needs +updating instead of insertion.

+
source

pub fn extend<I>(&mut self, iter: I) -> &mut Selfwhere + I: IntoIterator<Item = Header>,

Add all the headers in the provided iterator to this list of headers.

+
source

pub fn try_extend<I, E>(&mut self, iter: I) -> Result<&mut Self, E>where + I: IntoIterator<Item = Result<Header, E>>,

Add all the headers in the provided iterator, unless any of them are Err.

+
source

pub fn get_header<S>(&self, name: S) -> Option<&Header>where + S: PartialEq<HeaderName>,

Get the header object with the requested name. Usually, you will +want to use get() or get_as::<T>() instead.

+
source

pub fn get<S>(&self, name: S) -> Option<&str>where + S: PartialEq<HeaderName>,

Get the value of the header with the provided name.

+

See also get_as.

+
Example
+
let mut h = Headers::new();
+h.insert(CONTENT_TYPE, "application/json")?;
+assert_eq!(h.get(CONTENT_TYPE), Some("application/json"));
+assert_eq!(h.get("Something-Else"), None);
+
source

pub fn get_as<T, S>(&self, name: S) -> Option<Result<T, <T as FromStr>::Err>>where + T: FromStr, + S: PartialEq<HeaderName>,

Get the value of the header with the provided name, and +attempt to parse it using std::str::FromStr.

+
    +
  • If the header is missing, it returns None.
  • +
  • If the header is present but parsing failed, returns +Some(Err(<error returned by parsing>)).
  • +
  • Otherwise, returns Some(Ok(result)).
  • +
+

Note that if Option<Result<T, E>> is inconvenient for you, +and you wish this returned Result<Option<T>, E>, you may use +the built-in transpose() method to convert between them.

+ +
let mut h = Headers::new();
+h.insert("Example", "1234")?.insert("Illegal", "abcd")?;
+let v: Option<Result<i64, _>> = h.get_as("Example");
+assert_eq!(v, Some(Ok(1234)));
+assert_eq!(h.get_as::<i64, _>("Example"), Some(Ok(1234)));
+assert_eq!(h.get_as::<i64, _>("Illegal"), Some("abcd".parse::<i64>()));
+assert_eq!(h.get_as::<i64, _>("Something-Else"), None);
+
source

pub fn try_get<T, S>(&self, name: S) -> Option<T>where + T: FromStr, + S: PartialEq<HeaderName>,

Get the value of the header with the provided name, and +attempt to parse it using std::str::FromStr.

+

This is a variant of get_as that returns None on error, +intended to be used for cases where missing and invalid +headers should be treated the same. (With get_as this +requires h.get_as(...).and_then(|r| r.ok()), which is +somewhat opaque.

+
source

pub fn iter(&self) -> <&Headers as IntoIterator>::IntoIter

Get an iterator over the headers in no particular order.

+

Note that we also implement IntoIterator.

+

Trait Implementations§

source§

impl Clone for Headers

source§

fn clone(&self) -> Headers

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for Headers

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for Headers

source§

fn default() -> Headers

Returns the “default value” for a type. Read more
source§

impl From<Headers> for HashMap<String, String>

source§

fn from(headers: Headers) -> HashMap<String, String>

Converts to this type from the input type.
source§

impl FromIterator<Header> for Headers

source§

fn from_iter<T>(iter: T) -> Selfwhere + T: IntoIterator<Item = Header>,

Creates a value from an iterator. Read more
source§

impl<'a> IntoIterator for &'a Headers

§

type IntoIter = <&'a [Header] as IntoIterator>::IntoIter

Which kind of iterator are we turning this into?
§

type Item = &'a Header

The type of the elements being iterated over.
source§

fn into_iter(self) -> Self::IntoIter

Creates an iterator from a value. Read more
source§

impl IntoIterator for Headers

§

type IntoIter = <Vec<Header, Global> as IntoIterator>::IntoIter

Which kind of iterator are we turning this into?
§

type Item = Header

The type of the elements being iterated over.
source§

fn into_iter(self) -> Self::IntoIter

Creates an iterator from a value. Read more
source§

impl PartialEq<Headers> for Headers

source§

fn eq(&self, other: &Headers) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Eq for Headers

source§

impl StructuralEq for Headers

source§

impl StructuralPartialEq for Headers

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/viaduct/struct.InvalidHeaderName.html b/book/rust-docs/viaduct/struct.InvalidHeaderName.html new file mode 100644 index 0000000000..37b5c05f63 --- /dev/null +++ b/book/rust-docs/viaduct/struct.InvalidHeaderName.html @@ -0,0 +1,22 @@ +InvalidHeaderName in viaduct - Rust
pub struct InvalidHeaderName(_);
Expand description

Indicates an invalid header name. Note that we only emit +this for response headers, for request headers, we panic +instead. This is because it would likely come through as +a network error if we emitted it for local headers, when +it’s actually a bug that we’d need to fix.

+

Implementations§

Trait Implementations§

source§

impl Clone for InvalidHeaderName

source§

fn clone(&self) -> InvalidHeaderName

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for InvalidHeaderName

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Display for InvalidHeaderName

source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Error for InvalidHeaderName

1.30.0 · source§

fn source(&self) -> Option<&(dyn Error + 'static)>

The lower-level source of this error, if any. Read more
1.0.0 · source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type based access to context intended for error reports. Read more
source§

impl PartialEq<InvalidHeaderName> for InvalidHeaderName

source§

fn eq(&self, other: &InvalidHeaderName) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Eq for InvalidHeaderName

source§

impl StructuralEq for InvalidHeaderName

source§

impl StructuralPartialEq for InvalidHeaderName

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<E> Provider for Ewhere + E: Error + ?Sized,

source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (provide_any)
Data providers should implement this method to provide all values they are able to +provide by using demand. Read more
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/viaduct/struct.Request.html b/book/rust-docs/viaduct/struct.Request.html new file mode 100644 index 0000000000..77a247e7fd --- /dev/null +++ b/book/rust-docs/viaduct/struct.Request.html @@ -0,0 +1,72 @@ +Request in viaduct - Rust

Struct viaduct::Request

source ·
pub struct Request {
+    pub method: Method,
+    pub url: Url,
+    pub headers: Headers,
+    pub body: Option<Vec<u8>>,
+}

Fields§

§method: Method§url: Url§headers: Headers§body: Option<Vec<u8>>

Implementations§

source§

impl Request

source

pub fn new(method: Method, url: Url) -> Self

Construct a new request to the given url using the given method. +Note that the request is not made until send() is called.

+
source

pub fn send(self) -> Result<Response, Error>

source

pub fn get(url: Url) -> Self

Alias for Request::new(Method::Get, url), for convenience.

+
source

pub fn patch(url: Url) -> Self

Alias for Request::new(Method::Patch, url), for convenience.

+
source

pub fn post(url: Url) -> Self

Alias for Request::new(Method::Post, url), for convenience.

+
source

pub fn put(url: Url) -> Self

Alias for Request::new(Method::Put, url), for convenience.

+
source

pub fn delete(url: Url) -> Self

Alias for Request::new(Method::Delete, url), for convenience.

+
source

pub fn query(self, pairs: &[(&str, &str)]) -> Self

Append the provided query parameters to the URL

+
Example
+
let some_url = url::Url::parse("https://www.example.com/xyz").unwrap();
+
+let req = Request::post(some_url).query(&[("a", "1234"), ("b", "qwerty")]);
+assert_eq!(req.url.as_str(), "https://www.example.com/xyz?a=1234&b=qwerty");
+
+// This appends to the query query instead of replacing `a`.
+let req = req.query(&[("a", "5678")]);
+assert_eq!(req.url.as_str(), "https://www.example.com/xyz?a=1234&b=qwerty&a=5678");
+
source

pub fn set_query<'a, Q: Into<Option<&'a str>>>(self, query: Q) -> Self

Set the query string of the URL. Note that req.set_query(None) will +clear the query.

+

See also Request::query which appends a slice of query pairs, which is +typically more ergonomic when usable.

+
Example
+
let some_url = url::Url::parse("https://www.example.com/xyz").unwrap();
+
+let req = Request::post(some_url).set_query("a=b&c=d");
+assert_eq!(req.url.as_str(), "https://www.example.com/xyz?a=b&c=d");
+
+let req = req.set_query(None);
+assert_eq!(req.url.as_str(), "https://www.example.com/xyz");
+
source

pub fn headers<I>(self, to_add: I) -> Selfwhere + I: IntoIterator<Item = Header>,

Add all the provided headers to the list of headers to send with this +request.

+
source

pub fn header<Name, Val>(self, name: Name, val: Val) -> Result<Self, Error>where + Name: Into<HeaderName> + PartialEq<HeaderName>, + Val: Into<String> + AsRef<str>,

Add the provided header to the list of headers to send with this request.

+

This returns Err if val contains characters that may not appear in +the body of a header.

+
Example
+
Request::post(some_url)
+    .header(header_names::CONTENT_TYPE, "application/json")?
+    .header("My-Header", "Some special value")?;
+// ...
+
source

pub fn body(self, body: impl Into<Vec<u8>>) -> Self

Set this request’s body.

+
source

pub fn json<T: ?Sized + Serialize>(self, val: &T) -> Self

Set body to the result of serializing val, and, unless it has already +been set, set the Content-Type header to “application/json”.

+

Note: This panics if serde_json::to_vec fails. This can only happen +in a couple cases:

+
    +
  1. Trying to serialize a map with non-string keys.
  2. +
  3. We wrote a custom serializer that fails.
  4. +
+

Neither of these are things we do. If they happen, it seems better for +this to fail hard with an easy to track down panic, than for e.g. sync +to fail with a JSON parse error (which we’d probably attribute to +corrupt data on the server, or something).

+

Trait Implementations§

source§

impl Clone for Request

source§

fn clone(&self) -> Request

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for Request

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/viaduct/struct.Response.html b/book/rust-docs/viaduct/struct.Response.html new file mode 100644 index 0000000000..6d47506991 --- /dev/null +++ b/book/rust-docs/viaduct/struct.Response.html @@ -0,0 +1,33 @@ +Response in viaduct - Rust

Struct viaduct::Response

source ·
pub struct Response {
+    pub request_method: Method,
+    pub url: Url,
+    pub status: u16,
+    pub headers: Headers,
+    pub body: Vec<u8>,
+}
Expand description

A response from the server.

+

Fields§

§request_method: Method

The method used to request this response.

+
§url: Url

The URL of this response.

+
§status: u16

The HTTP Status code of this response.

+
§headers: Headers

The headers returned with this response.

+
§body: Vec<u8>

The body of the response.

+

Implementations§

source§

impl Response

source

pub fn json<'a, T>(&'a self) -> Result<T, Error>where + T: Deserialize<'a>,

Parse the body as JSON.

+
source

pub fn text(&self) -> Cow<'_, str>

Get the body as a string. Assumes UTF-8 encoding. Any non-utf8 bytes +are replaced with the replacement character.

+
source

pub fn is_success(&self) -> bool

Returns true if the status code is in the interval [200, 300).

+
source

pub fn is_server_error(&self) -> bool

Returns true if the status code is in the interval [500, 600).

+
source

pub fn is_client_error(&self) -> bool

Returns true if the status code is in the interval [400, 500).

+
source

pub fn require_success(self) -> Result<Self, UnexpectedStatus>

Returns an UnexpectedStatus error if self.is_success() is false, +otherwise returns Ok(self).

+

Trait Implementations§

source§

impl Clone for Response

source§

fn clone(&self) -> Response

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for Response

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
\ No newline at end of file diff --git a/book/rust-docs/viaduct/trait.Backend.html b/book/rust-docs/viaduct/trait.Backend.html new file mode 100644 index 0000000000..caa5e83ade --- /dev/null +++ b/book/rust-docs/viaduct/trait.Backend.html @@ -0,0 +1,4 @@ +Backend in viaduct - Rust

Trait viaduct::Backend

source ·
pub trait Backend: Send + Sync + 'static {
+    // Required method
+    fn send(&self, request: Request) -> Result<Response, Error>;
+}

Required Methods§

source

fn send(&self, request: Request) -> Result<Response, Error>

Implementors§

\ No newline at end of file diff --git a/book/rust-docs/viaduct_reqwest/all.html b/book/rust-docs/viaduct_reqwest/all.html new file mode 100644 index 0000000000..98ddf09a2f --- /dev/null +++ b/book/rust-docs/viaduct_reqwest/all.html @@ -0,0 +1 @@ +List of all items in this crate
\ No newline at end of file diff --git a/book/rust-docs/viaduct_reqwest/fn.use_reqwest_backend.html b/book/rust-docs/viaduct_reqwest/fn.use_reqwest_backend.html new file mode 100644 index 0000000000..5c4c6b2a5a --- /dev/null +++ b/book/rust-docs/viaduct_reqwest/fn.use_reqwest_backend.html @@ -0,0 +1 @@ +use_reqwest_backend in viaduct_reqwest - Rust
pub fn use_reqwest_backend()
\ No newline at end of file diff --git a/book/rust-docs/viaduct_reqwest/fn.viaduct_detect_reqwest_backend.html b/book/rust-docs/viaduct_reqwest/fn.viaduct_detect_reqwest_backend.html new file mode 100644 index 0000000000..1e74c9efbe --- /dev/null +++ b/book/rust-docs/viaduct_reqwest/fn.viaduct_detect_reqwest_backend.html @@ -0,0 +1,4 @@ +viaduct_detect_reqwest_backend in viaduct_reqwest - Rust
#[no_mangle]
+pub extern "C" fn viaduct_detect_reqwest_backend()
Expand description

A dummy symbol we include so that we can detect whether or not the reqwest +backend got compiled in.

+
\ No newline at end of file diff --git a/book/rust-docs/viaduct_reqwest/index.html b/book/rust-docs/viaduct_reqwest/index.html new file mode 100644 index 0000000000..6d8cd05408 --- /dev/null +++ b/book/rust-docs/viaduct_reqwest/index.html @@ -0,0 +1,2 @@ +viaduct_reqwest - Rust

Crate viaduct_reqwest

source ·

Structs

Functions

\ No newline at end of file diff --git a/book/rust-docs/viaduct_reqwest/sidebar-items.js b/book/rust-docs/viaduct_reqwest/sidebar-items.js new file mode 100644 index 0000000000..d51cac0dd7 --- /dev/null +++ b/book/rust-docs/viaduct_reqwest/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"fn":["use_reqwest_backend","viaduct_detect_reqwest_backend"],"struct":["ReqwestBackend"]}; \ No newline at end of file diff --git a/book/rust-docs/viaduct_reqwest/struct.ReqwestBackend.html b/book/rust-docs/viaduct_reqwest/struct.ReqwestBackend.html new file mode 100644 index 0000000000..46fa10d52e --- /dev/null +++ b/book/rust-docs/viaduct_reqwest/struct.ReqwestBackend.html @@ -0,0 +1,16 @@ +ReqwestBackend in viaduct_reqwest - Rust
pub struct ReqwestBackend;

Trait Implementations§

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an +Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an +Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>where + S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a +WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a +WithDispatch wrapper. Read more
\ No newline at end of file diff --git a/book/rust-docs/webext_storage/all.html b/book/rust-docs/webext_storage/all.html new file mode 100644 index 0000000000..0ec0c2fe6c --- /dev/null +++ b/book/rust-docs/webext_storage/all.html @@ -0,0 +1 @@ +List of all items in this crate
\ No newline at end of file diff --git a/book/rust-docs/webext_storage/api/constant.SYNC_MAX_ITEMS.html b/book/rust-docs/webext_storage/api/constant.SYNC_MAX_ITEMS.html new file mode 100644 index 0000000000..994847c6e0 --- /dev/null +++ b/book/rust-docs/webext_storage/api/constant.SYNC_MAX_ITEMS.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../webext_storage/constant.SYNC_MAX_ITEMS.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/webext_storage/api/constant.SYNC_QUOTA_BYTES.html b/book/rust-docs/webext_storage/api/constant.SYNC_QUOTA_BYTES.html new file mode 100644 index 0000000000..4b70a490ca --- /dev/null +++ b/book/rust-docs/webext_storage/api/constant.SYNC_QUOTA_BYTES.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../webext_storage/constant.SYNC_QUOTA_BYTES.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/webext_storage/api/constant.SYNC_QUOTA_BYTES_PER_ITEM.html b/book/rust-docs/webext_storage/api/constant.SYNC_QUOTA_BYTES_PER_ITEM.html new file mode 100644 index 0000000000..5960c5758c --- /dev/null +++ b/book/rust-docs/webext_storage/api/constant.SYNC_QUOTA_BYTES_PER_ITEM.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../webext_storage/constant.SYNC_QUOTA_BYTES_PER_ITEM.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/webext_storage/api/struct.UsageInfo.html b/book/rust-docs/webext_storage/api/struct.UsageInfo.html new file mode 100644 index 0000000000..d671775418 --- /dev/null +++ b/book/rust-docs/webext_storage/api/struct.UsageInfo.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../webext_storage/struct.UsageInfo.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/webext_storage/constant.STORAGE_VERSION.html b/book/rust-docs/webext_storage/constant.STORAGE_VERSION.html new file mode 100644 index 0000000000..815290b4d9 --- /dev/null +++ b/book/rust-docs/webext_storage/constant.STORAGE_VERSION.html @@ -0,0 +1 @@ +STORAGE_VERSION in webext_storage - Rust
pub const STORAGE_VERSION: usize = 1;
\ No newline at end of file diff --git a/book/rust-docs/webext_storage/constant.SYNC_MAX_ITEMS.html b/book/rust-docs/webext_storage/constant.SYNC_MAX_ITEMS.html new file mode 100644 index 0000000000..699c3235ab --- /dev/null +++ b/book/rust-docs/webext_storage/constant.SYNC_MAX_ITEMS.html @@ -0,0 +1 @@ +SYNC_MAX_ITEMS in webext_storage - Rust
pub const SYNC_MAX_ITEMS: usize = 512;
\ No newline at end of file diff --git a/book/rust-docs/webext_storage/constant.SYNC_QUOTA_BYTES.html b/book/rust-docs/webext_storage/constant.SYNC_QUOTA_BYTES.html new file mode 100644 index 0000000000..a5f2775358 --- /dev/null +++ b/book/rust-docs/webext_storage/constant.SYNC_QUOTA_BYTES.html @@ -0,0 +1 @@ +SYNC_QUOTA_BYTES in webext_storage - Rust
pub const SYNC_QUOTA_BYTES: usize = 102_400;
\ No newline at end of file diff --git a/book/rust-docs/webext_storage/constant.SYNC_QUOTA_BYTES_PER_ITEM.html b/book/rust-docs/webext_storage/constant.SYNC_QUOTA_BYTES_PER_ITEM.html new file mode 100644 index 0000000000..4c31f66f6e --- /dev/null +++ b/book/rust-docs/webext_storage/constant.SYNC_QUOTA_BYTES_PER_ITEM.html @@ -0,0 +1 @@ +SYNC_QUOTA_BYTES_PER_ITEM in webext_storage - Rust
pub const SYNC_QUOTA_BYTES_PER_ITEM: usize = 8_192;
\ No newline at end of file diff --git a/book/rust-docs/webext_storage/error/enum.ErrorKind.html b/book/rust-docs/webext_storage/error/enum.ErrorKind.html new file mode 100644 index 0000000000..bea75fd4da --- /dev/null +++ b/book/rust-docs/webext_storage/error/enum.ErrorKind.html @@ -0,0 +1,30 @@ +ErrorKind in webext_storage::error - Rust
pub enum ErrorKind {
+
Show 14 variants QuotaError(QuotaReason), + JsonError(Error), + SqlError(Error), + ConnectionAlreadyOpen, + InvalidConnectionType, + IoError(Error), + InterruptedError(Interrupted), + WrongApiForClose, + IllegalDatabasePath(PathBuf), + Utf8Error(Utf8Error), + OpenDatabaseError(Error), + OtherConnectionReferencesExist, + DatabaseConnectionClosed, + SyncError(String), +
}

Variants§

§

QuotaError(QuotaReason)

§

JsonError(Error)

§

SqlError(Error)

§

ConnectionAlreadyOpen

§

InvalidConnectionType

§

IoError(Error)

§

InterruptedError(Interrupted)

§

WrongApiForClose

§

IllegalDatabasePath(PathBuf)

§

Utf8Error(Utf8Error)

§

OpenDatabaseError(Error)

§

OtherConnectionReferencesExist

§

DatabaseConnectionClosed

§

SyncError(String)

Trait Implementations§

source§

impl Debug for ErrorKind

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Display for ErrorKind

source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Error for ErrorKind

source§

fn source(&self) -> Option<&(dyn Error + 'static)>

The lower-level source of this error, if any. Read more
1.0.0 · source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type based access to context intended for error reports. Read more
source§

impl From<Error> for ErrorKind

source§

fn from(source: Error) -> Self

Converts to this type from the input type.
source§

impl From<Error> for ErrorKind

source§

fn from(source: Error) -> Self

Converts to this type from the input type.
source§

impl From<Error> for ErrorKind

source§

fn from(source: Error) -> Self

Converts to this type from the input type.
source§

impl From<Error> for ErrorKind

source§

fn from(source: Error) -> Self

Converts to this type from the input type.
source§

impl From<ErrorKind> for Error

source§

fn from(ctx: ErrorKind) -> Error

Converts to this type from the input type.
source§

impl From<Interrupted> for ErrorKind

source§

fn from(source: Interrupted) -> Self

Converts to this type from the input type.
source§

impl From<Utf8Error> for ErrorKind

source§

fn from(source: Utf8Error) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<E> Provider for Ewhere + E: Error + ?Sized,

source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (provide_any)
Data providers should implement this method to provide all values they are able to +provide by using demand. Read more
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/webext_storage/error/enum.QuotaReason.html b/book/rust-docs/webext_storage/error/enum.QuotaReason.html new file mode 100644 index 0000000000..8d7ad3415f --- /dev/null +++ b/book/rust-docs/webext_storage/error/enum.QuotaReason.html @@ -0,0 +1,16 @@ +QuotaReason in webext_storage::error - Rust
pub enum QuotaReason {
+    TotalBytes,
+    ItemBytes,
+    MaxItems,
+}

Variants§

§

TotalBytes

§

ItemBytes

§

MaxItems

Trait Implementations§

source§

impl Debug for QuotaReason

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/webext_storage/error/index.html b/book/rust-docs/webext_storage/error/index.html new file mode 100644 index 0000000000..85a5b1ff75 --- /dev/null +++ b/book/rust-docs/webext_storage/error/index.html @@ -0,0 +1 @@ +webext_storage::error - Rust
\ No newline at end of file diff --git a/book/rust-docs/webext_storage/error/sidebar-items.js b/book/rust-docs/webext_storage/error/sidebar-items.js new file mode 100644 index 0000000000..5c529fbddf --- /dev/null +++ b/book/rust-docs/webext_storage/error/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"enum":["ErrorKind","QuotaReason"],"struct":["Error"],"type":["Result"]}; \ No newline at end of file diff --git a/book/rust-docs/webext_storage/error/struct.Error.html b/book/rust-docs/webext_storage/error/struct.Error.html new file mode 100644 index 0000000000..1413c913e5 --- /dev/null +++ b/book/rust-docs/webext_storage/error/struct.Error.html @@ -0,0 +1,15 @@ +Error in webext_storage::error - Rust

Struct webext_storage::error::Error

source ·
pub struct Error(_);

Implementations§

source§

impl Error

source

pub fn kind(&self) -> &ErrorKind

source

pub fn backtrace(&self) -> Option<&Mutex<Backtrace>>

Trait Implementations§

source§

impl Debug for Error

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Display for Error

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Error for Error

1.30.0 · source§

fn source(&self) -> Option<&(dyn Error + 'static)>

The lower-level source of this error, if any. Read more
1.0.0 · source§

fn description(&self) -> &str

👎Deprecated since 1.42.0: use the Display impl or to_string()
1.0.0 · source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0: replaced by Error::source, which can support downcasting
source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type based access to context intended for error reports. Read more
source§

impl From<Error> for Error

source§

fn from(e: Error) -> Self

Converts to this type from the input type.
source§

impl From<Error> for Error

source§

fn from(value: Error) -> Self

Converts to this type from the input type.
source§

impl From<Error> for Error

source§

fn from(e: Error) -> Self

Converts to this type from the input type.
source§

impl From<Error> for Error

source§

fn from(e: Error) -> Self

Converts to this type from the input type.
source§

impl From<Error> for Error

source§

fn from(e: Error) -> Self

Converts to this type from the input type.
source§

impl From<Error> for ExternError

source§

fn from(err: Error) -> ExternError

Converts to this type from the input type.
source§

impl From<ErrorKind> for Error

source§

fn from(ctx: ErrorKind) -> Error

Converts to this type from the input type.
source§

impl From<Interrupted> for Error

source§

fn from(e: Interrupted) -> Self

Converts to this type from the input type.
source§

impl From<Utf8Error> for Error

source§

fn from(e: Utf8Error) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

§

impl !RefUnwindSafe for Error

§

impl Send for Error

§

impl Sync for Error

§

impl Unpin for Error

§

impl !UnwindSafe for Error

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<E> Provider for Ewhere + E: Error + ?Sized,

source§

fn provide<'a>(&'a self, demand: &mut Demand<'a>)

🔬This is a nightly-only experimental API. (provide_any)
Data providers should implement this method to provide all values they are able to +provide by using demand. Read more
source§

impl<T> ToString for Twhere + T: Display + ?Sized,

source§

default fn to_string(&self) -> String

Converts the given value to a String. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/webext_storage/error/type.Result.html b/book/rust-docs/webext_storage/error/type.Result.html new file mode 100644 index 0000000000..f1eec896fc --- /dev/null +++ b/book/rust-docs/webext_storage/error/type.Result.html @@ -0,0 +1 @@ +Result in webext_storage::error - Rust

Type Definition webext_storage::error::Result

source ·
pub type Result<T, E = Error> = Result<T, E>;
\ No newline at end of file diff --git a/book/rust-docs/webext_storage/index.html b/book/rust-docs/webext_storage/index.html new file mode 100644 index 0000000000..e7cb499ac5 --- /dev/null +++ b/book/rust-docs/webext_storage/index.html @@ -0,0 +1 @@ +webext_storage - Rust
\ No newline at end of file diff --git a/book/rust-docs/webext_storage/migration/struct.MigrationInfo.html b/book/rust-docs/webext_storage/migration/struct.MigrationInfo.html new file mode 100644 index 0000000000..8325c0fd72 --- /dev/null +++ b/book/rust-docs/webext_storage/migration/struct.MigrationInfo.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../webext_storage/struct.MigrationInfo.html...

+ + + \ No newline at end of file diff --git a/book/rust-docs/webext_storage/sidebar-items.js b/book/rust-docs/webext_storage/sidebar-items.js new file mode 100644 index 0000000000..5a436c7888 --- /dev/null +++ b/book/rust-docs/webext_storage/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"constant":["STORAGE_VERSION","SYNC_MAX_ITEMS","SYNC_QUOTA_BYTES","SYNC_QUOTA_BYTES_PER_ITEM"],"mod":["error","store"],"struct":["MigrationInfo","UsageInfo"]}; \ No newline at end of file diff --git a/book/rust-docs/webext_storage/store/index.html b/book/rust-docs/webext_storage/store/index.html new file mode 100644 index 0000000000..d95783946b --- /dev/null +++ b/book/rust-docs/webext_storage/store/index.html @@ -0,0 +1,4 @@ +webext_storage::store - Rust

Module webext_storage::store

source ·

Structs

  • A store is used to access storage.sync data. It manages an underlying +database connection, and exposes methods for reading and writing storage +items scoped to an extension ID. Each item is a JSON object, with one or +more string keys, and values of any type that can serialize to JSON.
\ No newline at end of file diff --git a/book/rust-docs/webext_storage/store/sidebar-items.js b/book/rust-docs/webext_storage/store/sidebar-items.js new file mode 100644 index 0000000000..25d50fac1c --- /dev/null +++ b/book/rust-docs/webext_storage/store/sidebar-items.js @@ -0,0 +1 @@ +window.SIDEBAR_ITEMS = {"struct":["Store"]}; \ No newline at end of file diff --git a/book/rust-docs/webext_storage/store/struct.Store.html b/book/rust-docs/webext_storage/store/struct.Store.html new file mode 100644 index 0000000000..9a2736219e --- /dev/null +++ b/book/rust-docs/webext_storage/store/struct.Store.html @@ -0,0 +1,69 @@ +Store in webext_storage::store - Rust

Struct webext_storage::store::Store

source ·
pub struct Store { /* private fields */ }
Expand description

A store is used to access storage.sync data. It manages an underlying +database connection, and exposes methods for reading and writing storage +items scoped to an extension ID. Each item is a JSON object, with one or +more string keys, and values of any type that can serialize to JSON.

+

An application should create only one store, and manage the instance as a +singleton. While this isn’t enforced, if you make multiple stores pointing +to the same database file, you are going to have a bad time: each store will +create its own database connection, using up extra memory and CPU cycles, +and causing write contention. For this reason, you should only call +Store::new() (or webext_store_new(), from the FFI) once.

+

Note that our Db implementation is behind an Arc<> because we share that +connection with our sync engines - ie, these engines also hold an Arc<> +around the same object.

+

Implementations§

source§

impl Store

source

pub fn new(db_path: impl AsRef<Path>) -> Result<Self>

Creates a store backed by a database at db_path. The path can be a +file path or file: URI.

+
source

pub fn interrupt_handle(&self) -> Arc<SqlInterruptHandle>

Returns an interrupt handle for this store.

+
source

pub fn set(&self, ext_id: &str, val: JsonValue) -> Result<StorageChanges>

Sets one or more JSON key-value pairs for an extension ID. Returns a +list of changes, with existing and new values for each key in val.

+
source

pub fn usage(&self) -> Result<Vec<UsageInfo>>

Returns information about per-extension usage

+
source

pub fn get(&self, ext_id: &str, keys: JsonValue) -> Result<JsonValue>

Returns the values for one or more keys keys can be:

+
    +
  • null, in which case all key-value pairs for the extension are +returned, or an empty object if the extension doesn’t have any +stored data.
  • +
  • A single string key, in which case an object with only that key +and its value is returned, or an empty object if the key doesn’t
  • +
  • An array of string keys, in which case an object with only those +keys and their values is returned. Any keys that don’t exist will be +omitted.
  • +
  • An object where the property names are keys, and each value is the +default value to return if the key doesn’t exist.
  • +
+

This method always returns an object (that is, a +serde_json::Value::Object).

+
source

pub fn remove(&self, ext_id: &str, keys: JsonValue) -> Result<StorageChanges>

Deletes the values for one or more keys. As with get, keys can be +either a single string key, or an array of string keys. Returns a list +of changes, where each change contains the old value for each deleted +key.

+
source

pub fn clear(&self, ext_id: &str) -> Result<StorageChanges>

Deletes all key-value pairs for the extension. As with remove, returns +a list of changes, where each change contains the old value for each +deleted key.

+
source

pub fn get_bytes_in_use(&self, ext_id: &str, keys: JsonValue) -> Result<usize>

Returns the bytes in use for the specified items (which can be null, +a string, or an array)

+
source

pub fn bridged_engine(&self) -> BridgedEngine

Returns a bridged sync engine for Desktop for this store.

+
source

pub fn close(self) -> Result<()>

Closes the store and its database connection. See the docs for +StorageDb::close for more details on when this can fail.

+
source

pub fn get_synced_changes(&self) -> Result<Vec<SyncedExtensionChange>>

Gets the changes which the current sync applied. Should be used +immediately after the bridged engine is told to apply incoming changes, +and can be used to notify observers of the StorageArea of the changes +that were applied. +The result is a Vec of already JSON stringified changes.

+
source

pub fn migrate(&self, filename: impl AsRef<Path>) -> Result<()>

Migrates data from a database in the format of the “old” kinto +implementation. Information about how the migration went is stored in +the database, and can be read using Self::take_migration_info.

+

Note that filename isn’t normalized or canonicalized.

+
source

pub fn take_migration_info(&self) -> Result<Option<MigrationInfo>>

Read-and-delete (e.g. take in rust parlance, see Option::take) +operation for any MigrationInfo stored in this database.

+

Auto Trait Implementations§

§

impl !RefUnwindSafe for Store

§

impl Send for Store

§

impl Sync for Store

§

impl Unpin for Store

§

impl !UnwindSafe for Store

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/webext_storage/struct.MigrationInfo.html b/book/rust-docs/webext_storage/struct.MigrationInfo.html new file mode 100644 index 0000000000..d12d0a79c6 --- /dev/null +++ b/book/rust-docs/webext_storage/struct.MigrationInfo.html @@ -0,0 +1,36 @@ +MigrationInfo in webext_storage - Rust
pub struct MigrationInfo {
+    pub entries: usize,
+    pub entries_successful: usize,
+    pub extensions: usize,
+    pub extensions_successful: usize,
+    pub open_failure: bool,
+}

Fields§

§entries: usize

The number of entries (rows in the original table) we attempted to +migrate. Zero if there was some error in computing this number.

+

Note that for the original table, a single row stores a single +preference for one extension. That is, if you view the set of +preferences for a given extension as a HashMap (as we do), it would be a +single entry/key-value-pair in the map.

+
§entries_successful: usize

The number of records we successfully migrated (equal to entries for +entirely successful migrations).

+
§extensions: usize

The number of extensions (distinct extension ids) in the original +table.

+
§extensions_successful: usize

The number of extensions we successfully migrated

+
§open_failure: bool

True iff we failed to open the source DB at all.

+

Trait Implementations§

source§

impl Clone for MigrationInfo

source§

fn clone(&self) -> MigrationInfo

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for MigrationInfo

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Default for MigrationInfo

source§

fn default() -> MigrationInfo

Returns the “default value” for a type. Read more
source§

impl<'de> Deserialize<'de> for MigrationInfo

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where + __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl PartialEq<MigrationInfo> for MigrationInfo

source§

fn eq(&self, other: &MigrationInfo) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Serialize for MigrationInfo

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>where + __S: Serializer,

Serialize this value into the given Serde serializer. Read more
source§

impl Eq for MigrationInfo

source§

impl StructuralEq for MigrationInfo

source§

impl StructuralPartialEq for MigrationInfo

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

source§

impl<T> DeserializeOwned for Twhere + T: for<'de> Deserialize<'de>,

\ No newline at end of file diff --git a/book/rust-docs/webext_storage/struct.UsageInfo.html b/book/rust-docs/webext_storage/struct.UsageInfo.html new file mode 100644 index 0000000000..df220b3f71 --- /dev/null +++ b/book/rust-docs/webext_storage/struct.UsageInfo.html @@ -0,0 +1,27 @@ +UsageInfo in webext_storage - Rust
pub struct UsageInfo {
+    pub ext_id: String,
+    pub num_keys: usize,
+    pub num_bytes: usize,
+}
Expand description

Information about the usage of a single extension.

+

Fields§

§ext_id: String

The extension id.

+
§num_keys: usize

The number of keys the extension uses.

+
§num_bytes: usize

The number of bytes used by the extension. This result is somewhat rough +– it doesn’t bother counting the size of the extension ID, or data in +the mirror, and favors returning the exact number of bytes used by the +column (that is, the size of the JSON object) rather than replicating +the get_bytes_in_use return value for all keys.

+

Trait Implementations§

source§

impl Clone for UsageInfo

source§

fn clone(&self) -> UsageInfo

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for UsageInfo

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl PartialEq<UsageInfo> for UsageInfo

source§

fn eq(&self, other: &UsageInfo) -> bool

This method tests for self and other values to be equal, and is used +by ==.
1.0.0 · source§

fn ne(&self, other: &Rhs) -> bool

This method tests for !=. The default implementation is almost always +sufficient, and should not be overridden without very good reason.
source§

impl Eq for UsageInfo

source§

impl StructuralEq for UsageInfo

source§

impl StructuralPartialEq for UsageInfo

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere + T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere + T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere + T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

+
source§

impl<T, U> Into<U> for Twhere + U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

+

That is, this conversion is whatever the implementation of +From<T> for U chooses to do.

+
source§

impl<T> ToOwned for Twhere + T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for Twhere + U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere + U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for Twhere + V: MultiLane<T>,

§

fn vzip(self) -> V

\ No newline at end of file diff --git a/book/rust-docs/webext_storage/sync/constant.STORAGE_VERSION.html b/book/rust-docs/webext_storage/sync/constant.STORAGE_VERSION.html new file mode 100644 index 0000000000..1974fc6f49 --- /dev/null +++ b/book/rust-docs/webext_storage/sync/constant.STORAGE_VERSION.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../webext_storage/constant.STORAGE_VERSION.html...

+ + + \ No newline at end of file diff --git a/book/searcher.js b/book/searcher.js new file mode 100644 index 0000000000..d2b0aeed38 --- /dev/null +++ b/book/searcher.js @@ -0,0 +1,483 @@ +"use strict"; +window.search = window.search || {}; +(function search(search) { + // Search functionality + // + // You can use !hasFocus() to prevent keyhandling in your key + // event handlers while the user is typing their search. + + if (!Mark || !elasticlunr) { + return; + } + + //IE 11 Compatibility from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith + if (!String.prototype.startsWith) { + String.prototype.startsWith = function(search, pos) { + return this.substr(!pos || pos < 0 ? 0 : +pos, search.length) === search; + }; + } + + var search_wrap = document.getElementById('search-wrapper'), + searchbar = document.getElementById('searchbar'), + searchbar_outer = document.getElementById('searchbar-outer'), + searchresults = document.getElementById('searchresults'), + searchresults_outer = document.getElementById('searchresults-outer'), + searchresults_header = document.getElementById('searchresults-header'), + searchicon = document.getElementById('search-toggle'), + content = document.getElementById('content'), + + searchindex = null, + doc_urls = [], + results_options = { + teaser_word_count: 30, + limit_results: 30, + }, + search_options = { + bool: "AND", + expand: true, + fields: { + title: {boost: 1}, + body: {boost: 1}, + breadcrumbs: {boost: 0} + } + }, + mark_exclude = [], + marker = new Mark(content), + current_searchterm = "", + URL_SEARCH_PARAM = 'search', + URL_MARK_PARAM = 'highlight', + teaser_count = 0, + + SEARCH_HOTKEY_KEYCODE = 83, + ESCAPE_KEYCODE = 27, + DOWN_KEYCODE = 40, + UP_KEYCODE = 38, + SELECT_KEYCODE = 13; + + function hasFocus() { + return searchbar === document.activeElement; + } + + function removeChildren(elem) { + while (elem.firstChild) { + elem.removeChild(elem.firstChild); + } + } + + // Helper to parse a url into its building blocks. + function parseURL(url) { + var a = document.createElement('a'); + a.href = url; + return { + source: url, + protocol: a.protocol.replace(':',''), + host: a.hostname, + port: a.port, + params: (function(){ + var ret = {}; + var seg = a.search.replace(/^\?/,'').split('&'); + var len = seg.length, i = 0, s; + for (;i': '>', + '"': '"', + "'": ''' + }; + var repl = function(c) { return MAP[c]; }; + return function(s) { + return s.replace(/[&<>'"]/g, repl); + }; + })(); + + function formatSearchMetric(count, searchterm) { + if (count == 1) { + return count + " search result for '" + searchterm + "':"; + } else if (count == 0) { + return "No search results for '" + searchterm + "'."; + } else { + return count + " search results for '" + searchterm + "':"; + } + } + + function formatSearchResult(result, searchterms) { + var teaser = makeTeaser(escapeHTML(result.doc.body), searchterms); + teaser_count++; + + // The ?URL_MARK_PARAM= parameter belongs inbetween the page and the #heading-anchor + var url = doc_urls[result.ref].split("#"); + if (url.length == 1) { // no anchor found + url.push(""); + } + + // encodeURIComponent escapes all chars that could allow an XSS except + // for '. Due to that we also manually replace ' with its url-encoded + // representation (%27). + var searchterms = encodeURIComponent(searchterms.join(" ")).replace(/\'/g, "%27"); + + return '' + result.doc.breadcrumbs + '' + + '' + + teaser + ''; + } + + function makeTeaser(body, searchterms) { + // The strategy is as follows: + // First, assign a value to each word in the document: + // Words that correspond to search terms (stemmer aware): 40 + // Normal words: 2 + // First word in a sentence: 8 + // Then use a sliding window with a constant number of words and count the + // sum of the values of the words within the window. Then use the window that got the + // maximum sum. If there are multiple maximas, then get the last one. + // Enclose the terms in . + var stemmed_searchterms = searchterms.map(function(w) { + return elasticlunr.stemmer(w.toLowerCase()); + }); + var searchterm_weight = 40; + var weighted = []; // contains elements of ["word", weight, index_in_document] + // split in sentences, then words + var sentences = body.toLowerCase().split('. '); + var index = 0; + var value = 0; + var searchterm_found = false; + for (var sentenceindex in sentences) { + var words = sentences[sentenceindex].split(' '); + value = 8; + for (var wordindex in words) { + var word = words[wordindex]; + if (word.length > 0) { + for (var searchtermindex in stemmed_searchterms) { + if (elasticlunr.stemmer(word).startsWith(stemmed_searchterms[searchtermindex])) { + value = searchterm_weight; + searchterm_found = true; + } + }; + weighted.push([word, value, index]); + value = 2; + } + index += word.length; + index += 1; // ' ' or '.' if last word in sentence + }; + index += 1; // because we split at a two-char boundary '. ' + }; + + if (weighted.length == 0) { + return body; + } + + var window_weight = []; + var window_size = Math.min(weighted.length, results_options.teaser_word_count); + + var cur_sum = 0; + for (var wordindex = 0; wordindex < window_size; wordindex++) { + cur_sum += weighted[wordindex][1]; + }; + window_weight.push(cur_sum); + for (var wordindex = 0; wordindex < weighted.length - window_size; wordindex++) { + cur_sum -= weighted[wordindex][1]; + cur_sum += weighted[wordindex + window_size][1]; + window_weight.push(cur_sum); + }; + + if (searchterm_found) { + var max_sum = 0; + var max_sum_window_index = 0; + // backwards + for (var i = window_weight.length - 1; i >= 0; i--) { + if (window_weight[i] > max_sum) { + max_sum = window_weight[i]; + max_sum_window_index = i; + } + }; + } else { + max_sum_window_index = 0; + } + + // add around searchterms + var teaser_split = []; + var index = weighted[max_sum_window_index][2]; + for (var i = max_sum_window_index; i < max_sum_window_index+window_size; i++) { + var word = weighted[i]; + if (index < word[2]) { + // missing text from index to start of `word` + teaser_split.push(body.substring(index, word[2])); + index = word[2]; + } + if (word[1] == searchterm_weight) { + teaser_split.push("") + } + index = word[2] + word[0].length; + teaser_split.push(body.substring(word[2], index)); + if (word[1] == searchterm_weight) { + teaser_split.push("") + } + }; + + return teaser_split.join(''); + } + + function init(config) { + results_options = config.results_options; + search_options = config.search_options; + searchbar_outer = config.searchbar_outer; + doc_urls = config.doc_urls; + searchindex = elasticlunr.Index.load(config.index); + + // Set up events + searchicon.addEventListener('click', function(e) { searchIconClickHandler(); }, false); + searchbar.addEventListener('keyup', function(e) { searchbarKeyUpHandler(); }, false); + document.addEventListener('keydown', function(e) { globalKeyHandler(e); }, false); + // If the user uses the browser buttons, do the same as if a reload happened + window.onpopstate = function(e) { doSearchOrMarkFromUrl(); }; + // Suppress "submit" events so the page doesn't reload when the user presses Enter + document.addEventListener('submit', function(e) { e.preventDefault(); }, false); + + // If reloaded, do the search or mark again, depending on the current url parameters + doSearchOrMarkFromUrl(); + } + + function unfocusSearchbar() { + // hacky, but just focusing a div only works once + var tmp = document.createElement('input'); + tmp.setAttribute('style', 'position: absolute; opacity: 0;'); + searchicon.appendChild(tmp); + tmp.focus(); + tmp.remove(); + } + + // On reload or browser history backwards/forwards events, parse the url and do search or mark + function doSearchOrMarkFromUrl() { + // Check current URL for search request + var url = parseURL(window.location.href); + if (url.params.hasOwnProperty(URL_SEARCH_PARAM) + && url.params[URL_SEARCH_PARAM] != "") { + showSearch(true); + searchbar.value = decodeURIComponent( + (url.params[URL_SEARCH_PARAM]+'').replace(/\+/g, '%20')); + searchbarKeyUpHandler(); // -> doSearch() + } else { + showSearch(false); + } + + if (url.params.hasOwnProperty(URL_MARK_PARAM)) { + var words = decodeURIComponent(url.params[URL_MARK_PARAM]).split(' '); + marker.mark(words, { + exclude: mark_exclude + }); + + var markers = document.querySelectorAll("mark"); + function hide() { + for (var i = 0; i < markers.length; i++) { + markers[i].classList.add("fade-out"); + window.setTimeout(function(e) { marker.unmark(); }, 300); + } + } + for (var i = 0; i < markers.length; i++) { + markers[i].addEventListener('click', hide); + } + } + } + + // Eventhandler for keyevents on `document` + function globalKeyHandler(e) { + if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey || e.target.type === 'textarea' || e.target.type === 'text') { return; } + + if (e.keyCode === ESCAPE_KEYCODE) { + e.preventDefault(); + searchbar.classList.remove("active"); + setSearchUrlParameters("", + (searchbar.value.trim() !== "") ? "push" : "replace"); + if (hasFocus()) { + unfocusSearchbar(); + } + showSearch(false); + marker.unmark(); + } else if (!hasFocus() && e.keyCode === SEARCH_HOTKEY_KEYCODE) { + e.preventDefault(); + showSearch(true); + window.scrollTo(0, 0); + searchbar.select(); + } else if (hasFocus() && e.keyCode === DOWN_KEYCODE) { + e.preventDefault(); + unfocusSearchbar(); + searchresults.firstElementChild.classList.add("focus"); + } else if (!hasFocus() && (e.keyCode === DOWN_KEYCODE + || e.keyCode === UP_KEYCODE + || e.keyCode === SELECT_KEYCODE)) { + // not `:focus` because browser does annoying scrolling + var focused = searchresults.querySelector("li.focus"); + if (!focused) return; + e.preventDefault(); + if (e.keyCode === DOWN_KEYCODE) { + var next = focused.nextElementSibling; + if (next) { + focused.classList.remove("focus"); + next.classList.add("focus"); + } + } else if (e.keyCode === UP_KEYCODE) { + focused.classList.remove("focus"); + var prev = focused.previousElementSibling; + if (prev) { + prev.classList.add("focus"); + } else { + searchbar.select(); + } + } else { // SELECT_KEYCODE + window.location.assign(focused.querySelector('a')); + } + } + } + + function showSearch(yes) { + if (yes) { + search_wrap.classList.remove('hidden'); + searchicon.setAttribute('aria-expanded', 'true'); + } else { + search_wrap.classList.add('hidden'); + searchicon.setAttribute('aria-expanded', 'false'); + var results = searchresults.children; + for (var i = 0; i < results.length; i++) { + results[i].classList.remove("focus"); + } + } + } + + function showResults(yes) { + if (yes) { + searchresults_outer.classList.remove('hidden'); + } else { + searchresults_outer.classList.add('hidden'); + } + } + + // Eventhandler for search icon + function searchIconClickHandler() { + if (search_wrap.classList.contains('hidden')) { + showSearch(true); + window.scrollTo(0, 0); + searchbar.select(); + } else { + showSearch(false); + } + } + + // Eventhandler for keyevents while the searchbar is focused + function searchbarKeyUpHandler() { + var searchterm = searchbar.value.trim(); + if (searchterm != "") { + searchbar.classList.add("active"); + doSearch(searchterm); + } else { + searchbar.classList.remove("active"); + showResults(false); + removeChildren(searchresults); + } + + setSearchUrlParameters(searchterm, "push_if_new_search_else_replace"); + + // Remove marks + marker.unmark(); + } + + // Update current url with ?URL_SEARCH_PARAM= parameter, remove ?URL_MARK_PARAM and #heading-anchor . + // `action` can be one of "push", "replace", "push_if_new_search_else_replace" + // and replaces or pushes a new browser history item. + // "push_if_new_search_else_replace" pushes if there is no `?URL_SEARCH_PARAM=abc` yet. + function setSearchUrlParameters(searchterm, action) { + var url = parseURL(window.location.href); + var first_search = ! url.params.hasOwnProperty(URL_SEARCH_PARAM); + if (searchterm != "" || action == "push_if_new_search_else_replace") { + url.params[URL_SEARCH_PARAM] = searchterm; + delete url.params[URL_MARK_PARAM]; + url.hash = ""; + } else { + delete url.params[URL_MARK_PARAM]; + delete url.params[URL_SEARCH_PARAM]; + } + // A new search will also add a new history item, so the user can go back + // to the page prior to searching. A updated search term will only replace + // the url. + if (action == "push" || (action == "push_if_new_search_else_replace" && first_search) ) { + history.pushState({}, document.title, renderURL(url)); + } else if (action == "replace" || (action == "push_if_new_search_else_replace" && !first_search) ) { + history.replaceState({}, document.title, renderURL(url)); + } + } + + function doSearch(searchterm) { + + // Don't search the same twice + if (current_searchterm == searchterm) { return; } + else { current_searchterm = searchterm; } + + if (searchindex == null) { return; } + + // Do the actual search + var results = searchindex.search(searchterm, search_options); + var resultcount = Math.min(results.length, results_options.limit_results); + + // Display search metrics + searchresults_header.innerText = formatSearchMetric(resultcount, searchterm); + + // Clear and insert results + var searchterms = searchterm.split(' '); + removeChildren(searchresults); + for(var i = 0; i < resultcount ; i++){ + var resultElem = document.createElement('li'); + resultElem.innerHTML = formatSearchResult(results[i], searchterms); + searchresults.appendChild(resultElem); + } + + // Display results + showResults(true); + } + + fetch(path_to_root + 'searchindex.json') + .then(response => response.json()) + .then(json => init(json)) + .catch(error => { // Try to load searchindex.js if fetch failed + var script = document.createElement('script'); + script.src = path_to_root + 'searchindex.js'; + script.onload = () => init(window.search); + document.head.appendChild(script); + }); + + // Exported functions + search.hasFocus = hasFocus; +})(window.search); diff --git a/book/searchindex.js b/book/searchindex.js new file mode 100644 index 0000000000..0a1bfdd585 --- /dev/null +++ b/book/searchindex.js @@ -0,0 +1 @@ +Object.assign(window.search, {"doc_urls":["index.html#application-services-rust-components","index.html#contact-us","index.html#license","contributing.html#contributing-to-application-services","contributing.html#bug-reports","contributing.html#building-the-project","contributing.html#finding-issues","contributing.html#sending-pull-requests","contributing.html#code-review","building.html#building-application-services","building.html#first-time-builds","building.html#building-the-rust-components","building.html#building-for-fenix","building.html#windows-setup-for-android-via-wsl","building.html#building-for-firefox-ios","building.html#locally-building-firefox-ios-against-a-local-application-services","howtos/locally-published-components-in-fenix.html#using-locally-published-components-in-fenix","howtos/locally-published-components-in-fenix.html#rusttargets","howtos/locally-published-components-in-fenix.html#using-the-auto-publishing-workflow","howtos/locally-published-components-in-fenix.html#using-windowswsl","howtos/locally-published-components-in-fenix.html#using-a-manual-workflow","howtos/locally-published-components-in-fenix.html#caveats","howtos/locally-published-components-in-fenix.html#adding-support-for-the-auto-publish-workflow","howtos/locally-published-components-in-firefox-ios.html#how-to-locally-test-swift-package-manager-components-on-firefox-ios","howtos/locally-published-components-in-firefox-ios.html#prerequisites","howtos/locally-published-components-in-firefox-ios.html#using-the-automated-flow","howtos/locally-published-components-in-firefox-ios.html#disabling-local-development","howtos/locally-published-components-in-firefox-ios.html#using-the-manual-flow","howtos/locally-published-components-in-firefox-ios.html#building-the-xcframework","howtos/locally-published-components-in-firefox-ios.html#include-the-xcframework-in-a-local-checkout-of-rust-components-swift","howtos/locally-published-components-in-firefox-ios.html#run-the-generation-script-with-a-local-checkout-of-application-services","howtos/locally-published-components-in-firefox-ios.html#include-the-local-checkout-of-rust-components-swift-in-firefox-ios","howtos/locally-published-components-in-focus-ios.html#how-to-locally-test-swift-package-manager-components-on-focus-ios","howtos/locally-published-components-in-focus-ios.html#building-the-xcframework","howtos/locally-published-components-in-focus-ios.html#include-the-xcframework-in-a-local-checkout-of-rust-components-swift","howtos/locally-published-components-in-focus-ios.html#run-the-generation-script-with-a-local-checkout-of-application-services","howtos/locally-published-components-in-focus-ios.html#include-the-local-checkout-of-rust-components-swift-in-focus","howtos/locally-building-jna.html#building-and-using-a-locally-modified-version-of-jna","howtos/branch-builds.html#branch-builds","howtos/branch-builds.html#breaking-changes-in-an-application-services-branch","howtos/branch-builds.html#application-services-nightlies","howtos/testing-a-rust-component.html#guide-to-testing-a-rust-component","howtos/testing-a-rust-component.html#unit-and-functional-tests","howtos/testing-a-rust-component.html#rust-code","howtos/testing-a-rust-component.html#ffi-layer-code","howtos/testing-a-rust-component.html#kotlin-code","howtos/testing-a-rust-component.html#swift-code","howtos/testing-a-rust-component.html#integration-tests","howtos/testing-a-rust-component.html#end-to-end-sync-tests","howtos/testing-a-rust-component.html#android-components-test-suite","howtos/testing-a-rust-component.html#test-coverage","howtos/testing-a-rust-component.html#ideas-for-improvement","howtos/smoke-testing-app-services.html#smoke-testing-application-services-against-end-user-apps","howtos/smoke-testing-app-services.html#dependencies","howtos/smoke-testing-app-services.html#android-components","howtos/smoke-testing-app-services.html#fenix","howtos/smoke-testing-app-services.html#firefox-ios","design/test-faster.html#testing-faster-how-to-avoid-making-compile-times-worse-by-adding-tests","design/test-faster.html#background","design/test-faster.html#the-solution","design/test-faster.html#appendix-how-to-avoid-redundant-compiles-for-benchmarks-and-integration-tests","design/test-faster.html#faq0-why-put-testsbenches-in-src-instead-of-disabling-autotestsautobenches","howtos/debug-sql.html#debugging-sql","dependency-management.html#dependency-management-guidelines","dependency-management.html#rust-code","dependency-management.html#androidkotlin-code","dependency-management.html#iosswift-code","dependency-management.html#other-code","howtos/adding-a-new-component.html#adding-a-new-component-to-application-services","howtos/adding-a-new-component.html#the-rust-code","howtos/adding-a-new-component.html#the-kotlin-bindings","howtos/adding-a-new-component.html#the-swift-bindings","howtos/adding-a-new-component.html#creating-the-directory-structure","howtos/adding-a-new-component.html#adding-your-component-to-the-swift-package-manager-megazord","howtos/adding-a-new-component.html#distribute-your-component-with-rust-components-swift","howtos/building-a-rust-component.html#guide-to-building-a-syncable-rust-component","howtos/building-a-rust-component.html#general-design-and-structure-of-the-component","howtos/building-a-rust-component.html#we-build-libraries-not-frameworks","howtos/building-a-rust-component.html#the-store-is-the-entry-point","howtos/building-a-rust-component.html#using-sqlite","howtos/building-a-rust-component.html#meta-data","howtos/building-a-rust-component.html#schema-management","howtos/building-a-rust-component.html#triggers","howtos/building-a-rust-component.html#general-structure-of-the-rust-code","howtos/building-a-rust-component.html#syncing","howtos/building-a-rust-component.html#syncing-faqs","howtos/building-a-rust-component.html#whats-the-global-sync-id-and-the-collection-sync-id","howtos/building-a-rust-component.html#whats-get_sync_assoc-why-is-it-important-what-is-storesyncassociation","howtos/building-a-rust-component.html#what-is-apply_incoming-versus-sync_finished","howtos/building-a-rust-component.html#whats-the-diff-between-reset-and-wipe","howtos/building-a-rust-component.html#exposing-to-consumers","naming-conventions.html#naming-conventions","naming-conventions.html#rust-code","naming-conventions.html#overview","naming-conventions.html#examples","naming-conventions.html#swift-code","naming-conventions.html#overview-1","naming-conventions.html#examples-1","naming-conventions.html#kotlin-code","naming-conventions.html#overview-2","naming-conventions.html#examples-2","howtos/converting-a-component-to-uniffi.html#converting-an-existing-component-to-use-uniffi","howtos/converting-a-component-to-uniffi.html#first-get-familiar-with-uniffi","howtos/converting-a-component-to-uniffi.html#next-get-familiar-with-the-target-component","howtos/converting-a-component-to-uniffi.html#write-a-first-draft-of-the-udl-file-for-the-components-interface","howtos/converting-a-component-to-uniffi.html#restructure-the-rust-code-to-introduce-uniffi","howtos/converting-a-component-to-uniffi.html#removing-protobuf-messages","howtos/converting-a-component-to-uniffi.html#document-the-public-api-in-the-rust-code","howtos/converting-a-component-to-uniffi.html#set-up-the-kotlin-wrapper","howtos/converting-a-component-to-uniffi.html#set-up-the-swift-wrapper","android-faqs.html#rust--android-faqs","android-faqs.html#how-do-i-expose-rust-code-to-kotlin","android-faqs.html#how-should-i-name-the-package","android-faqs.html#how-do-i-publish-the-resulting-package","android-faqs.html#how-do-i-know-what-library-name-to-load-to-access-the-compiled-rust-code","android-faqs.html#what-challenges-exist-when-calling-back-into-kotlin-from-rust","android-faqs.html#why-are-we-using-jna-rather-than-jni-and-what-tradeoffs-does-that-involve","android-faqs.html#how-do-i-debug-rust-code-with-the-step-debugger-in-android-studio","howtos/breaking-changes.html#breaking-changes-in-application-services-code","howtos/breaking-changes.html#merging","howtos/vendoring-into-mozilla-central.html#vendoring-application-services-into-mozilla-central","howtos/vendoring-into-mozilla-central.html#when-to-vendor","howtos/vendoring-into-mozilla-central.html#updating-existing-components","howtos/vendoring-into-mozilla-central.html#adding-a-new-component","howtos/vendoring-into-mozilla-central.html#vendoring-an-unreleased-version-for-testing-purposes","logging.html#application-services-logging","logging.html#accessing-logs-when-running-fenix","logging.html#accessing-logs-when-running-ios","howtos/uniffi-object-destruction-on-kotlin.html#uniffi-object-destruction-on-kotlin","howtos/uniffi-object-destruction-on-kotlin.html#you-can-create-objects-in-a-function-if-you-also-destroy-them-there","howtos/uniffi-object-destruction-on-kotlin.html#you-can-create-and-store-objects-in-singletons","howtos/uniffi-object-destruction-on-kotlin.html#you-can-create-and-store-objects-in-an-class-then-destroy-them-in-a-corresponding-lifecycle-method","howtos/uniffi-object-destruction-on-kotlin.html#you-can-share-objects","howtos/uniffi-object-destruction-on-kotlin.html#destruction-may-not-always-happen","adr/index.html#architectural-decision-log","adr/0000-use-markdown-architectural-decision-records.html#use-markdown-architectural-decision-records","adr/0000-use-markdown-architectural-decision-records.html#context-and-problem-statement","adr/0000-use-markdown-architectural-decision-records.html#considered-options","adr/0000-use-markdown-architectural-decision-records.html#decision-outcome","adr/0001-update-logins-api.html#update-logins-api","adr/0001-update-logins-api.html#context-and-problem-statement","adr/0001-update-logins-api.html#decision-drivers","adr/0001-update-logins-api.html#considered-options","adr/0001-update-logins-api.html#decision-outcome","adr/0001-update-logins-api.html#pros-and-cons-of-the-options","adr/0001-update-logins-api.html#option-1---reduce-the-api-functions-that-require-the-encryption-key-and-pass-the-key-to-the-remaining-functions","adr/0001-update-logins-api.html#option-2---implement-a-different-key-management-approach","adr/0001-update-logins-api.html#links","adr/0002-database-corruption.html#handling-database-corruption","adr/0002-database-corruption.html#context-and-problem-statement","adr/0002-database-corruption.html#decision-drivers","adr/0002-database-corruption.html#considered-options","adr/0002-database-corruption.html#decision-outcome","adr/0002-database-corruption.html#pros-and-cons-of-the-options","adr/0002-database-corruption.html#a1-assume-all-errors-when-opening-a-database-are-from-corrupt-databases","adr/0002-database-corruption.html#a2-check-for-errors-when-opening-a-database-and-compare-against-known-corruption-error-types-decided","adr/0002-database-corruption.html#a3-check-for-errors-for-all-database-operations-and-compare-against-known-corruption-error-types","adr/0002-database-corruption.html#b1-delete-the-database-file-and-recreate-the-database-decided","adr/0002-database-corruption.html#b2-move-the-database-file-and-recreate-the-database","adr/0002-database-corruption.html#b3-return-a-failure-code","adr/0003-swift-packaging.html#distributing-swift-packages","adr/0003-swift-packaging.html#context-and-problem-statement","adr/0003-swift-packaging.html#decision-drivers","adr/0003-swift-packaging.html#considered-options","adr/0003-swift-packaging.html#decision-outcome","adr/0003-swift-packaging.html#positive-consequences","adr/0003-swift-packaging.html#negative-consequences","adr/0003-swift-packaging.html#implementation-sketch","adr/0003-swift-packaging.html#pros-and-cons-of-the-options","adr/0003-swift-packaging.html#a-do-nothing","adr/0003-swift-packaging.html#b-use-carthage-to-build-xcframework-bundles","adr/0003-swift-packaging.html#c-distribute-a-single-pre-compiled-swift-package","adr/0003-swift-packaging.html#d-distribute-multiple-source-based-swift-packages-with-pre-compiled-rust-code","adr/0003-swift-packaging.html#appendix","adr/0003-swift-packaging.html#further-reading","adr/0003-swift-packaging.html#problems-with-the-current-setup","adr/0004-early-startup-experiments.html#running-experiments-on-first-run-early-startup","adr/0004-early-startup-experiments.html#context-and-problem-statement","adr/0004-early-startup-experiments.html#decision-drivers","adr/0004-early-startup-experiments.html#considered-options","adr/0004-early-startup-experiments.html#decision-outcome","adr/0004-early-startup-experiments.html#pros-and-cons-of-the-options","adr/0004-early-startup-experiments.html#do-nothing","adr/0004-early-startup-experiments.html#bundle-experiment-data-with-app-on-release","adr/0004-early-startup-experiments.html#retrieve-experiment-data-on-first-run-and-deal-with-delay","adr/0004-early-startup-experiments.html#links","adr/0005-remote-settings-client.html#a-remote-settings-client-for-our-mobile-browsers","adr/0005-remote-settings-client.html#context-and-problem-statement","adr/0005-remote-settings-client.html#requirements","adr/0005-remote-settings-client.html#non-requirements","adr/0005-remote-settings-client.html#initial-requirements","adr/0005-remote-settings-client.html#existing-libraries","adr/0005-remote-settings-client.html#remote-settings-on-desktop","adr/0005-remote-settings-client.html#rust-remote-settings-client","adr/0005-remote-settings-client.html#the-nimbus-sdk-client","adr/0005-remote-settings-client.html#considered-options","adr/0005-remote-settings-client.html#option-1-writing-a-new-library","adr/0005-remote-settings-client.html#option-2-use-the-existing-remote-settings-client","adr/0005-remote-settings-client.html#option-3-use-the-existing-nimbus-client","adr/0005-remote-settings-client.html#chosen-option","adr/0005-remote-settings-client.html#specific-plans","adr/0005-remote-settings-client.html#content-signatures","adr/0007-limit-visits-migration-to-10000.html#limit-visits-migrated-to-places-history-in-firefox-ios","adr/0007-limit-visits-migration-to-10000.html#context-and-problem-statement","adr/0007-limit-visits-migration-to-10000.html#observations-from-dry-run-experiment","adr/0007-limit-visits-migration-to-10000.html#problem-statement","adr/0007-limit-visits-migration-to-10000.html#decision-drivers","adr/0007-limit-visits-migration-to-10000.html#considered-options","adr/0007-limit-visits-migration-to-10000.html#decision-outcome","adr/0007-limit-visits-migration-to-10000.html#positive-consequences","adr/0007-limit-visits-migration-to-10000.html#negative-consequences","adr/0007-limit-visits-migration-to-10000.html#pros-and-cons-of-the-other-options","adr/0007-limit-visits-migration-to-10000.html#keep-the-migration-as-is","adr/0007-limit-visits-migration-to-10000.html#introduce-a-date-based-limit-on-visits","adr/0007-limit-visits-migration-to-10000.html#suggested-limit","adr/0007-limit-visits-migration-to-10000.html#user-history-distribution","adr/0007-limit-visits-migration-to-10000.html#dry-run-ending-rate-by-visits","adr/0007-limit-visits-migration-to-10000.html#suggestion","adr/0007-limit-visits-migration-to-10000.html#links","design/index.html#design-documents","design/megazords.html#megazording","design/megazords.html#aar-dependency-graph","design/megazords.html#custom-megazords","design/megazords.html#unit-tests","design/megazords.html#gotchas-and-rough-edges","design/sync-manager.html#sync-manager","design/sync-manager.html#the-responsibilities-of-the-sync-manager","design/sync-manager.html#implementation-details","design/sync-manager.html#current-implementations-and-challenges-with-the-rust-components","design/sync-manager.html#state-state-and-more-state-and-then-some-state","design/sync-manager.html#implementation-plan-for-the-low-level-component","design/sync-manager.html#clients-engine","design/sync-manager.html#collections-vs-engines-vs-stores-vs-preferences-vs-apis","design/sync-manager.html#the-declined-list","design/sync-manager.html#disconnecting-from-sync","design/sync-manager.html#specific-deliverables-for-the-low-level-component","design/sync-manager.html#the-api","design/sync-manager.html#a-command-line-and-possibly-android-utility","design/sync-manager.html#the-clients-engine","design/sync-manager.html#state-work","design/sync-manager.html#tie-it-together-and-other-misc-things","design/sync-manager.html#followup-with-non-rust-engines","design/sync-manager.html#details","design/sync-overview.html#sync-overview","design/sync-overview.html#general-flow-and-architecture","design/sync-overview.html#sync-manager","design/sync-overview.html#sync-engines","design/sync-overview.html#the-apply_incoming-pattern","design/sync-overview.html#database-tables","design/sync-overview.html#apply_incoming-stages","design/sync-overview.html#syncchangecounter","design/swift-package-manager.html#high-level-design-for-shipping-rust-components-as-swift-packages","design/swift-package-manager.html#the-xcframework-and-application-services","design/swift-package-manager.html#the-rust-components-swift-repository","design/components-strategy.html#high-level-firefox-sync-interactions","design/components-strategy.html#multi-platform-sync-diagram","design/components-strategy.html#before-how-sync-was","design/components-strategy.html#now-sync-is-starting-to-streamline-its-components","design/components-strategy.html#future-only-one-implementation-for-each-sync-engine","design/metrics.html#metrics-collected-by-application-services-components","design/rust-versions.html#rust-versions","design/rust-versions.html#mozilla-central-rust-policies","design/rust-versions.html#application-services-rust-version-policy","design/rust-versions.html#implications-of-this","design/db-pragmas.html#sqlite-database-pragma-usage","howtos/releases.html#application-services-release-process","howtos/releases.html#nightly-builds","howtos/releases.html#release-builds","howtos/releases.html#release-management-creating-a-new-release","howtos/releases.html#release-management-creating-a-new-release-via-scripts","howtos/releases.html#cutting-patch-releases-for-uplifted-changes","howtos/releases.html#what-gets-built-in-a-release","howtos/releases.html#nightly-builds-1","howtos/releases.html#release-promotion","build-and-publish-pipeline.html#application-services-build-and-publish-pipeline","build-and-publish-pipeline.html#authentication-and-secrets","build-and-publish-pipeline.html#appsvc-moz-account","build-and-publish-pipeline.html#circleci","howtos/upgrading-nss-guide.html#guide-to-upgrading-nss","howtos/upgrading-nss-guide.html#updating-the-version","howtos/upgrading-nss-guide.html#updating-the-cross-compiled-nss-artifacts","howtos/upgrading-nss-guide.html#exposing-new-functions","howtos/upgrading-nss-guide.html#tips-for-fixing-bustage","rust-docs/fxa_client/index.html","adding-docs.html#developing-documentation","adding-docs.html#building-documentation","adding-docs.html#building-the-narrative-book-documentation"],"index":{"documentStore":{"docInfo":{"0":{"body":32,"breadcrumbs":8,"title":4},"1":{"body":31,"breadcrumbs":5,"title":1},"10":{"body":33,"breadcrumbs":5,"title":3},"100":{"body":12,"breadcrumbs":7,"title":1},"101":{"body":57,"breadcrumbs":13,"title":5},"102":{"body":31,"breadcrumbs":11,"title":3},"103":{"body":226,"breadcrumbs":12,"title":4},"104":{"body":184,"breadcrumbs":15,"title":7},"105":{"body":212,"breadcrumbs":13,"title":5},"106":{"body":191,"breadcrumbs":11,"title":3},"107":{"body":65,"breadcrumbs":13,"title":5},"108":{"body":167,"breadcrumbs":12,"title":4},"109":{"body":198,"breadcrumbs":12,"title":4},"11":{"body":338,"breadcrumbs":5,"title":3},"110":{"body":0,"breadcrumbs":11,"title":3},"111":{"body":53,"breadcrumbs":12,"title":4},"112":{"body":16,"breadcrumbs":10,"title":2},"113":{"body":12,"breadcrumbs":11,"title":3},"114":{"body":20,"breadcrumbs":16,"title":8},"115":{"body":151,"breadcrumbs":14,"title":6},"116":{"body":134,"breadcrumbs":13,"title":5},"117":{"body":99,"breadcrumbs":15,"title":7},"118":{"body":70,"breadcrumbs":9,"title":5},"119":{"body":94,"breadcrumbs":5,"title":1},"12":{"body":130,"breadcrumbs":4,"title":2},"120":{"body":26,"breadcrumbs":11,"title":5},"121":{"body":37,"breadcrumbs":7,"title":1},"122":{"body":108,"breadcrumbs":9,"title":3},"123":{"body":101,"breadcrumbs":9,"title":3},"124":{"body":156,"breadcrumbs":11,"title":5},"125":{"body":27,"breadcrumbs":5,"title":3},"126":{"body":89,"breadcrumbs":6,"title":4},"127":{"body":6,"breadcrumbs":6,"title":4},"128":{"body":63,"breadcrumbs":9,"title":4},"129":{"body":19,"breadcrumbs":9,"title":4},"13":{"body":74,"breadcrumbs":7,"title":5},"130":{"body":15,"breadcrumbs":9,"title":4},"131":{"body":29,"breadcrumbs":13,"title":8},"132":{"body":48,"breadcrumbs":7,"title":2},"133":{"body":30,"breadcrumbs":8,"title":3},"134":{"body":69,"breadcrumbs":6,"title":3},"135":{"body":0,"breadcrumbs":10,"title":5},"136":{"body":10,"breadcrumbs":8,"title":3},"137":{"body":26,"breadcrumbs":7,"title":2},"138":{"body":49,"breadcrumbs":7,"title":2},"139":{"body":9,"breadcrumbs":8,"title":3},"14":{"body":57,"breadcrumbs":5,"title":3},"140":{"body":72,"breadcrumbs":8,"title":3},"141":{"body":32,"breadcrumbs":7,"title":2},"142":{"body":34,"breadcrumbs":7,"title":2},"143":{"body":19,"breadcrumbs":7,"title":2},"144":{"body":0,"breadcrumbs":8,"title":3},"145":{"body":194,"breadcrumbs":17,"title":12},"146":{"body":76,"breadcrumbs":12,"title":7},"147":{"body":10,"breadcrumbs":6,"title":1},"148":{"body":6,"breadcrumbs":8,"title":3},"149":{"body":42,"breadcrumbs":8,"title":3},"15":{"body":16,"breadcrumbs":10,"title":8},"150":{"body":29,"breadcrumbs":7,"title":2},"151":{"body":51,"breadcrumbs":7,"title":2},"152":{"body":68,"breadcrumbs":7,"title":2},"153":{"body":0,"breadcrumbs":8,"title":3},"154":{"body":46,"breadcrumbs":12,"title":7},"155":{"body":38,"breadcrumbs":17,"title":12},"156":{"body":46,"breadcrumbs":16,"title":11},"157":{"body":14,"breadcrumbs":12,"title":7},"158":{"body":84,"breadcrumbs":11,"title":6},"159":{"body":34,"breadcrumbs":9,"title":4},"16":{"body":39,"breadcrumbs":13,"title":5},"160":{"body":8,"breadcrumbs":8,"title":3},"161":{"body":47,"breadcrumbs":8,"title":3},"162":{"body":27,"breadcrumbs":7,"title":2},"163":{"body":75,"breadcrumbs":7,"title":2},"164":{"body":31,"breadcrumbs":7,"title":2},"165":{"body":73,"breadcrumbs":7,"title":2},"166":{"body":45,"breadcrumbs":7,"title":2},"167":{"body":259,"breadcrumbs":7,"title":2},"168":{"body":0,"breadcrumbs":8,"title":3},"169":{"body":73,"breadcrumbs":6,"title":1},"17":{"body":78,"breadcrumbs":9,"title":1},"170":{"body":137,"breadcrumbs":11,"title":6},"171":{"body":139,"breadcrumbs":12,"title":7},"172":{"body":136,"breadcrumbs":16,"title":11},"173":{"body":0,"breadcrumbs":6,"title":1},"174":{"body":68,"breadcrumbs":7,"title":2},"175":{"body":100,"breadcrumbs":8,"title":3},"176":{"body":17,"breadcrumbs":11,"title":6},"177":{"body":24,"breadcrumbs":8,"title":3},"178":{"body":23,"breadcrumbs":7,"title":2},"179":{"body":60,"breadcrumbs":7,"title":2},"18":{"body":101,"breadcrumbs":12,"title":4},"180":{"body":69,"breadcrumbs":7,"title":2},"181":{"body":0,"breadcrumbs":8,"title":3},"182":{"body":27,"breadcrumbs":6,"title":1},"183":{"body":52,"breadcrumbs":10,"title":5},"184":{"body":79,"breadcrumbs":12,"title":7},"185":{"body":34,"breadcrumbs":6,"title":1},"186":{"body":21,"breadcrumbs":10,"title":5},"187":{"body":67,"breadcrumbs":8,"title":3},"188":{"body":26,"breadcrumbs":6,"title":1},"189":{"body":111,"breadcrumbs":7,"title":2},"19":{"body":35,"breadcrumbs":10,"title":2},"190":{"body":78,"breadcrumbs":7,"title":2},"191":{"body":5,"breadcrumbs":7,"title":2},"192":{"body":33,"breadcrumbs":8,"title":3},"193":{"body":55,"breadcrumbs":9,"title":4},"194":{"body":92,"breadcrumbs":8,"title":3},"195":{"body":0,"breadcrumbs":7,"title":2},"196":{"body":62,"breadcrumbs":10,"title":5},"197":{"body":128,"breadcrumbs":12,"title":7},"198":{"body":70,"breadcrumbs":11,"title":6},"199":{"body":30,"breadcrumbs":7,"title":2},"2":{"body":14,"breadcrumbs":5,"title":1},"20":{"body":112,"breadcrumbs":11,"title":3},"200":{"body":299,"breadcrumbs":7,"title":2},"201":{"body":228,"breadcrumbs":7,"title":2},"202":{"body":11,"breadcrumbs":12,"title":7},"203":{"body":60,"breadcrumbs":8,"title":3},"204":{"body":136,"breadcrumbs":9,"title":4},"205":{"body":122,"breadcrumbs":7,"title":2},"206":{"body":68,"breadcrumbs":7,"title":2},"207":{"body":36,"breadcrumbs":7,"title":2},"208":{"body":90,"breadcrumbs":7,"title":2},"209":{"body":61,"breadcrumbs":7,"title":2},"21":{"body":48,"breadcrumbs":9,"title":1},"210":{"body":53,"breadcrumbs":7,"title":2},"211":{"body":0,"breadcrumbs":8,"title":3},"212":{"body":61,"breadcrumbs":7,"title":2},"213":{"body":68,"breadcrumbs":10,"title":5},"214":{"body":86,"breadcrumbs":7,"title":2},"215":{"body":32,"breadcrumbs":8,"title":3},"216":{"body":57,"breadcrumbs":10,"title":5},"217":{"body":47,"breadcrumbs":6,"title":1},"218":{"body":61,"breadcrumbs":6,"title":1},"219":{"body":50,"breadcrumbs":3,"title":2},"22":{"body":229,"breadcrumbs":13,"title":5},"220":{"body":104,"breadcrumbs":3,"title":1},"221":{"body":97,"breadcrumbs":5,"title":3},"222":{"body":79,"breadcrumbs":4,"title":2},"223":{"body":49,"breadcrumbs":4,"title":2},"224":{"body":91,"breadcrumbs":5,"title":3},"225":{"body":226,"breadcrumbs":5,"title":2},"226":{"body":242,"breadcrumbs":6,"title":3},"227":{"body":42,"breadcrumbs":5,"title":2},"228":{"body":127,"breadcrumbs":8,"title":5},"229":{"body":158,"breadcrumbs":8,"title":5},"23":{"body":86,"breadcrumbs":17,"title":8},"230":{"body":23,"breadcrumbs":8,"title":5},"231":{"body":131,"breadcrumbs":5,"title":2},"232":{"body":133,"breadcrumbs":12,"title":9},"233":{"body":203,"breadcrumbs":5,"title":2},"234":{"body":24,"breadcrumbs":5,"title":2},"235":{"body":8,"breadcrumbs":8,"title":5},"236":{"body":68,"breadcrumbs":4,"title":1},"237":{"body":57,"breadcrumbs":8,"title":5},"238":{"body":21,"breadcrumbs":5,"title":2},"239":{"body":15,"breadcrumbs":5,"title":2},"24":{"body":18,"breadcrumbs":10,"title":1},"240":{"body":8,"breadcrumbs":7,"title":4},"241":{"body":83,"breadcrumbs":7,"title":4},"242":{"body":351,"breadcrumbs":4,"title":1},"243":{"body":19,"breadcrumbs":5,"title":2},"244":{"body":55,"breadcrumbs":6,"title":3},"245":{"body":199,"breadcrumbs":5,"title":2},"246":{"body":32,"breadcrumbs":5,"title":2},"247":{"body":11,"breadcrumbs":5,"title":2},"248":{"body":45,"breadcrumbs":5,"title":2},"249":{"body":165,"breadcrumbs":5,"title":2},"25":{"body":142,"breadcrumbs":12,"title":3},"250":{"body":78,"breadcrumbs":4,"title":1},"251":{"body":60,"breadcrumbs":14,"title":8},"252":{"body":130,"breadcrumbs":9,"title":3},"253":{"body":100,"breadcrumbs":10,"title":4},"254":{"body":87,"breadcrumbs":9,"title":5},"255":{"body":27,"breadcrumbs":8,"title":4},"256":{"body":38,"breadcrumbs":6,"title":2},"257":{"body":83,"breadcrumbs":9,"title":5},"258":{"body":78,"breadcrumbs":10,"title":6},"259":{"body":31,"breadcrumbs":9,"title":5},"26":{"body":106,"breadcrumbs":12,"title":3},"260":{"body":43,"breadcrumbs":6,"title":2},"261":{"body":49,"breadcrumbs":8,"title":4},"262":{"body":47,"breadcrumbs":9,"title":5},"263":{"body":96,"breadcrumbs":5,"title":1},"264":{"body":120,"breadcrumbs":9,"title":4},"265":{"body":0,"breadcrumbs":5,"title":4},"266":{"body":43,"breadcrumbs":3,"title":2},"267":{"body":43,"breadcrumbs":3,"title":2},"268":{"body":328,"breadcrumbs":6,"title":5},"269":{"body":52,"breadcrumbs":8,"title":7},"27":{"body":26,"breadcrumbs":12,"title":3},"270":{"body":41,"breadcrumbs":6,"title":5},"271":{"body":84,"breadcrumbs":4,"title":3},"272":{"body":30,"breadcrumbs":3,"title":2},"273":{"body":39,"breadcrumbs":3,"title":2},"274":{"body":510,"breadcrumbs":10,"title":5},"275":{"body":0,"breadcrumbs":7,"title":2},"276":{"body":36,"breadcrumbs":8,"title":3},"277":{"body":46,"breadcrumbs":6,"title":1},"278":{"body":57,"breadcrumbs":6,"title":3},"279":{"body":78,"breadcrumbs":5,"title":2},"28":{"body":41,"breadcrumbs":11,"title":2},"280":{"body":99,"breadcrumbs":8,"title":5},"281":{"body":36,"breadcrumbs":6,"title":3},"282":{"body":126,"breadcrumbs":6,"title":3},"283":{"body":248,"breadcrumbs":2,"title":2},"284":{"body":34,"breadcrumbs":4,"title":2},"285":{"body":0,"breadcrumbs":4,"title":2},"286":{"body":27,"breadcrumbs":6,"title":4},"29":{"body":60,"breadcrumbs":16,"title":7},"3":{"body":22,"breadcrumbs":4,"title":3},"30":{"body":32,"breadcrumbs":16,"title":7},"31":{"body":123,"breadcrumbs":17,"title":8},"32":{"body":83,"breadcrumbs":16,"title":8},"33":{"body":44,"breadcrumbs":10,"title":2},"34":{"body":70,"breadcrumbs":15,"title":7},"35":{"body":32,"breadcrumbs":15,"title":7},"36":{"body":138,"breadcrumbs":15,"title":7},"37":{"body":303,"breadcrumbs":11,"title":6},"38":{"body":20,"breadcrumbs":6,"title":2},"39":{"body":74,"breadcrumbs":9,"title":5},"4":{"body":12,"breadcrumbs":3,"title":2},"40":{"body":118,"breadcrumbs":7,"title":3},"41":{"body":31,"breadcrumbs":8,"title":4},"42":{"body":0,"breadcrumbs":7,"title":3},"43":{"body":94,"breadcrumbs":6,"title":2},"44":{"body":14,"breadcrumbs":7,"title":3},"45":{"body":118,"breadcrumbs":6,"title":2},"46":{"body":94,"breadcrumbs":6,"title":2},"47":{"body":0,"breadcrumbs":6,"title":2},"48":{"body":57,"breadcrumbs":8,"title":4},"49":{"body":53,"breadcrumbs":8,"title":4},"5":{"body":13,"breadcrumbs":3,"title":2},"50":{"body":16,"breadcrumbs":6,"title":2},"51":{"body":54,"breadcrumbs":6,"title":2},"52":{"body":16,"breadcrumbs":17,"title":8},"53":{"body":9,"breadcrumbs":10,"title":1},"54":{"body":26,"breadcrumbs":11,"title":2},"55":{"body":16,"breadcrumbs":10,"title":1},"56":{"body":22,"breadcrumbs":11,"title":2},"57":{"body":0,"breadcrumbs":16,"title":9},"58":{"body":169,"breadcrumbs":8,"title":1},"59":{"body":105,"breadcrumbs":8,"title":1},"6":{"body":82,"breadcrumbs":3,"title":2},"60":{"body":235,"breadcrumbs":14,"title":7},"61":{"body":167,"breadcrumbs":14,"title":7},"62":{"body":305,"breadcrumbs":8,"title":2},"63":{"body":35,"breadcrumbs":6,"title":3},"64":{"body":235,"breadcrumbs":5,"title":2},"65":{"body":30,"breadcrumbs":5,"title":2},"66":{"body":11,"breadcrumbs":5,"title":2},"67":{"body":24,"breadcrumbs":4,"title":1},"68":{"body":31,"breadcrumbs":9,"title":5},"69":{"body":139,"breadcrumbs":6,"title":2},"7":{"body":217,"breadcrumbs":4,"title":3},"70":{"body":133,"breadcrumbs":6,"title":2},"71":{"body":0,"breadcrumbs":6,"title":2},"72":{"body":56,"breadcrumbs":7,"title":3},"73":{"body":267,"breadcrumbs":10,"title":6},"74":{"body":81,"breadcrumbs":9,"title":5},"75":{"body":55,"breadcrumbs":13,"title":5},"76":{"body":5,"breadcrumbs":12,"title":4},"77":{"body":14,"breadcrumbs":11,"title":3},"78":{"body":83,"breadcrumbs":11,"title":3},"79":{"body":132,"breadcrumbs":10,"title":2},"8":{"body":21,"breadcrumbs":3,"title":2},"80":{"body":15,"breadcrumbs":10,"title":2},"81":{"body":122,"breadcrumbs":10,"title":2},"82":{"body":104,"breadcrumbs":9,"title":1},"83":{"body":31,"breadcrumbs":12,"title":4},"84":{"body":103,"breadcrumbs":9,"title":1},"85":{"body":3,"breadcrumbs":10,"title":2},"86":{"body":63,"breadcrumbs":15,"title":7},"87":{"body":33,"breadcrumbs":12,"title":4},"88":{"body":26,"breadcrumbs":11,"title":3},"89":{"body":19,"breadcrumbs":13,"title":5},"9":{"body":16,"breadcrumbs":5,"title":3},"90":{"body":61,"breadcrumbs":10,"title":2},"91":{"body":6,"breadcrumbs":8,"title":2},"92":{"body":13,"breadcrumbs":8,"title":2},"93":{"body":35,"breadcrumbs":7,"title":1},"94":{"body":6,"breadcrumbs":7,"title":1},"95":{"body":0,"breadcrumbs":8,"title":2},"96":{"body":16,"breadcrumbs":7,"title":1},"97":{"body":8,"breadcrumbs":7,"title":1},"98":{"body":33,"breadcrumbs":8,"title":2},"99":{"body":36,"breadcrumbs":7,"title":1}},"docs":{"0":{"body":"Application Services is collection of Rust Components. The components are used to enable Firefox, and related applications to integrate with Firefox accounts, sync and enable experimentation. Each component is built using a core of shared code written in Rust, wrapped with native language bindings for different platforms.","breadcrumbs":"Application Services Rust Components » Application Services Rust Components","id":"0","title":"Application Services Rust Components"},"1":{"body":"To contact the Application Services team you can: Find us in the chat #rust-components:mozilla.org ( How to connect ) To report issues with sync on Firefox Desktop , file a bug in Bugzilla for Firefox :: Sync To report issues with our components, file an issue in the GitHub issue tracker The source code is available on GitHub .","breadcrumbs":"Application Services Rust Components » Contact us","id":"1","title":"Contact us"},"10":{"body":"Building for the first time is more complicated than a typical Rust project. To build for an end-to-end experience that enables you to test changes in client applications like Firefox for Android (Fenix) and Firefox iOS , there are a number of build systems required for all the dependencies. The initial setup is likely to take a number of hours to complete.","breadcrumbs":"Contributing » Building » First time builds","id":"10","title":"First time builds"},"100":{"body":"//FooBar.kt\nclass FooBar{...}\nfun fromJSONString()\npackage mozilla.appservices.places Found a bug? Edit this page on GitHub.","breadcrumbs":"Contributing » How to add a new component » Naming Conventions » Examples:","id":"100","title":"Examples:"},"101":{"body":"When we started building the components in this repo, exposing Rust code to Kotlin and Swift was a manual process and each component had its own hand-written FFI layer and foreign-language bindings. As we've gained more experience with building components in this way, we've started to automate bindings generation and capture best practices in a tool called UniFFI , which is the currently recommended approach when adding a new component from scratch . We expect that existing components will gradually be ported over to use UniFFI, and this document is a guide to doing that port.","breadcrumbs":"Contributing » How to add a new component » How to convert a Rust Component to Uniffi » Converting an existing Component to use UniFFI","id":"101","title":"Converting an existing Component to use UniFFI"},"102":{"body":"First, make sure you've perused the UniFFI guide to understand the overall architecture of a UniFFI component, and take a look at the guide to adding a new component to understand how such components fit in to this repo. The aim of porting will be to have a component that looks like it was added by the process described therein.","breadcrumbs":"Contributing » How to add a new component » How to convert a Rust Component to Uniffi » First, get familiar with UniFFI","id":"102","title":"First, get familiar with UniFFI"},"103":{"body":"Pre-UniFFI components typically consist of four main parts: A Rust crate implementing the core functionality of the component A separate Rust crate that exposes the core functionality over a C-style FFI. An Android package that imports the C-style FFI into idiomatic Kotlin. A Swift module that imports the C-style FFI into idiomatic Swift. The code for these parts will be laid out something like this: components// Cargo.toml src/ Rust code for the core functionality of the component goes here. ffi/ Cargo.toml src/ Rust code specifically for exposing the C-style FFI goes here. android/ build.gradle src/ main/ AndroidManifest.xml java/mozilla/appservices// LibFFI.kt (low-level bindings to the C-style FFI) Higher-level hand-written Kotlin that wraps the FFI. ios/ / RustAPI.h (low-level bindings to the C-style FFI) Higher-level hand-written Swift that wraps the FFI. The goal here is to replace much of the hand-written wrapper layers with autogenerated code: The ./ffi/ crate will disappear entirely, its work is automated by UniFFI If you still need some hand-written pub extern \"C\" functions, perhaps to implement features not currently supported by UniFFI, then they should move into lib.rs of the main component crate. The low-level LibFFI.kt file will disappear entirely, as will some of the code that converts it back into nice high-level Kotlin classes and interfaces. Some of the hand-written Kotlin code may remain, if it provides functionality that cannot be implemented in Rust. The low-level RustAPI.h file will disappear entirely, as will some of the code that converts it back into nice high-level Swift classes and interfaces. Some of the hand-written Swift code may remain, if it provides functionality that cannot be implemented in Rust. You'll aim to end up with a simplified file structure that looks like this: components// Cargo.toml uniffi.toml src/ .udl (abstract interface definition) Rust code here. android/ build.gradle src/ main/ AndroidManifest.xml java/mozilla/appservices// Optional hand-written Kotlin code here. ios/ / Optional hand-written Swift code here.","breadcrumbs":"Contributing » How to add a new component » How to convert a Rust Component to Uniffi » Next, get familiar with the target component","id":"103","title":"Next, get familiar with the target component"},"104":{"body":"Make sure you've got the uniffi-bindgen command available; cargo install uniffi_bindgen will ensure you have the latest version. Create ./src/.udl and try to describe the intended interface for the component using UniFFI's interface definition language . You'll probably need to reverse-engineer it a little bit from the existing hand-written Kotlin and/or Swift code. Don't spend too much time on trying to match every minute detail of the existing hand-written API. There are likely to be small differences between how UniFFI likes to do things and how the hand-written APIs were structured, and it's in everyone's best long-term interests to just push ahead and update consumers to accommodate any breaking API changes, rathern than e.g. trying to convince UniFFI to capitalize enum variant names in the same style that the hand-written code was using. To check whether the .udl file is syntactically valid, you can use uniffi-bindgen to generate the Rust FFI scaffolding like so: uniffi-bindgen scaffolding ./src/.udl If this succeeds, it will generate a file ./src/.uniffi.rs with a bunch of thorny auto-generated Rust code. If it fails, it will likely fail with an inscrutable error message. Unfortunately the error reporting in UniFFI is currently a known pain point, and it can take a bit of trial-and-error to identify what part of the file is causing the issue. Sorry :-( The aim at this point is to ensure that the intended interface of the component can be expressed in terms that UniFFI understands. Most cases should be supported, but you may find some aspect of the existing component that is hard to express in UniFFI, perhaps even uncovering new functionality that needs to be added to UniFFI itself! The .udl file is definitely a first draft at this point. It is normal and expected to need to iterate on this file as you port over the underlying Rust code.","breadcrumbs":"Contributing » How to add a new component » How to convert a Rust Component to Uniffi » Write a first draft of the .udl file for the component's interface","id":"104","title":"Write a first draft of the .udl file for the component's interface"},"105":{"body":"You will now restructure the existing Rust crate so that its public API surface and overall \"shape\" match what you defined in the .udl file. Start by deleting the ./ffi sub-crate, because you're going to use UniFFI to generate all of that code. You'll also need to remove it from the workspace in the top-level Cargo.toml file, as well as change the crates under /megazords to import the core Rust crate for the component rather than importing the FFI sub-crate. Add UniFFI to the crate's dependencies and configure its build.rs script to invoke the UniFFI scaffolding generator, as described in \"adding a new component\" . Now, edit ./lib.rs so that it matches the interface defined in the .udl file as closely as possible. If the .udl has an interface Example then lib.rs should contain a pub struct Example, if the .udl contains an enum ExampleItem then lib.rs should contain a pub enum ExampleItem, and so-on. The details of this step will depend heavily on the specific crate, but some tips include: You may find it useful to move all of the existing code into a sub-module named internal, and then make a brand new lib.rs that imports or re-defines just the pieces it needs in order to implement the interface from the .udl file. The fxa-client crate is an example of a case where this worked out well, though of course your mileage may vary. If the existing crate contains a file named like _msg_types.proto, then it was using Protocol Buffers to serialize data to pass over the FFI. The message types defined in the .proto file will need to be converted into dictionary or enum definitions in your .udl file. See the section below for more details. As noted above, don't be afraid to accept some API churn during the conversion process. We're willing to accept some breaking API changes as the cost of getting bindings generated for free, as long as the core functionality and mental model of the component remain intact. At this point, in theory the crate should be buildable with UniFFI, although it's likely to require some iteration to get it all working! Run cargo check to check for any compilation errors without having to do a full build.","breadcrumbs":"Contributing » How to add a new component » How to convert a Rust Component to Uniffi » Restructure the Rust code to introduce UniFFI","id":"105","title":"Restructure the Rust code to introduce UniFFI"},"106":{"body":"Passing rich structured data over the FFI is the most complex part of our hand-written bindings, and was previously done by serializing data via Protocol Buffers . This is something that UniFFI tries to make as simple as possible. Start by locating the _msg_types.proto file for the component. This file defines the structured messages that can be passed over the FFI, and you should see that they correspond to various types of structured data that the component wants to receive from, or return to, the foreign-language code. Find the places in your .udl interface that correspond to these message types and make sure that you've got a similarly-shaped dictionary or enum for each one. You should find that representing this structured data in UDL is simpler than protobuf in many cases - for example many of our .protobuf files need to use a separate ExampleStructs message in order to pass a list of ExampleStruct messages over the FFI, but in UniFFI this is represented directly as sequence. Find the places in the Rust code that are using these message types to return structured data. In simple cases, you may be able to directly replace uses of msg_types::ExampleStruct with the corresponding crate::ExampleStruct from your public API. For more complex cases, you may find it helpful to define an Into mapping between the UniFFI dictionary/enum in the crate's public interface, and a more complex struct designed for internal use. As noted above, don't be afraid to accept some API churn during this conversion process. Once you have replaced all uses of the msg_types structs in the Rust code: Delete ./src/_msg_types.proto. Delete ./src/mozilla.appservices..protobuf.rs, which is generated from the .proto file. Remote prost and prost-derive from the crate's dependencies. Delete the crate from the list in /tools/protobuf_files.toml. If you happen to find that you've deleted the last crate from the list in protobuf_files.toml, congratulations! You've successfully removed protocol buffers from this repo entirely, and should file a bug to track the complete removal of protobuf from our tooling and dependency chain.","breadcrumbs":"Contributing » How to add a new component » How to convert a Rust Component to Uniffi » Removing Protobuf Messages","id":"106","title":"Removing Protobuf Messages"},"107":{"body":"Write consumer-facing documentation on the public API in lib.rs using Rust's standard rustdoc conventions and tools. The fxa-client crate may serve as a good example. You can view the generated documentation by running: cargo doc --no-deps --open In future, we intend to automatically extract documentation from the Rust code and make it easily available to consumers of the generated bindings. (In fact there is some work-in-progress code in uniffi-rs#416 that can read docs from the Rust code and write them back into the .udl file, which you're welcome to try out if you're feeling adventurous. But it's just a very hacky prototype.)","breadcrumbs":"Contributing » How to add a new component » How to convert a Rust Component to Uniffi » Document the Public API in the Rust code","id":"107","title":"Document the Public API in the Rust code"},"108":{"body":"It's easiest to start by removing all of the hand-written Kotlin code under android/src/main/java and then restoring parts of it later if necessary. Leave the AndroidManifest.xml file and any tests in place. Delete the android/build.gradle file and then follow the instructions for adding Kotlin bindings for a new component to create a new build.gradle file and a corresponding uniffi.toml. This should be all that's required to set up UniFFI to build the Kotlin bindings. Try building the Android package to confirm: ./gradlew :assembleDebug The UniFFI-generated Kotlin code will be under ./android/build/generated/source/uniffi/ and may be useful for debugging. If there are existing Kotlin tests for the component, the next step is to get those passing: ./gradlew :test As noted above, it is normal and expected for the autogenerated bindings to be subtly different from the previous hand-written ones. For example, UniFFI insists on using SHOUTY_SNAKE_CASE variant names in Kotlin enums while the hand-written code may have used CamelCase. Some components also have small naming differences between the Rust code and the hand-written Kotlin bindings, which UniFFI will not allow. If the component had functionality in its Kotlin layer that was not part of the Rust API, then you'll need to add some hand-written Kotlin code under android/src/main/java to implement it. The fxa-client component may be a good example here: its Rust layer exposes a FirefoxAccount struct that the Kotlin code wraps into a PersistedFirefoxAccount class, adding the ability to set a persistence callback. Finally, you will need to try out the new bindings with a consuming app. For Kotlin code you should make a local build of android-components and Fenix , updating them to accomodate any changes in the component's public API.","breadcrumbs":"Contributing » How to add a new component » How to convert a Rust Component to Uniffi » Set up the Kotlin wrapper","id":"108","title":"Set up the Kotlin wrapper"},"109":{"body":"It's easiest to start by removing all of the hand-written Swift code under ./ios and then restoring parts of it later if necessary. Edit /megazords/ios-rust/MozillaTestServices.h to remove any references to RustAPI.h, replacing them with the UniFFI-generated header file name FFI.h. Open /megazords/ios-rust/MozillaTestServices.xcodeproj in Xcode and follow the instructions for adding Swift bindings for a new component to configure Xcode to build your UniFFI-generated bindings. While you are in the Xcode Project Navigator, you should also delete any references to RustAPI.h or to the old hand-written Swift wrappers. (They should be highlighted in red in the Project Navigator, because the files will be missing from disk after you deleted them above). This should be all that's required to set up UniFFI to build the Swift bindings. Try building the project in Xcode to confirm. The UniFFI-generated Swift code will be under ios/Generated and may be useful for debugging. If there are existing Swift tests for the component, the next step is to get those passing: ./automation/run_ios_tests.sh (or run them from the Xcode GUI) As noted above, it is normal and expected for the autogenerated bindings to be subtly different from the previous hand-written ones. Many existing components have small naming differences between the Rust code and the hand-written Swift bindings, which UniFFI will not allow. If the component had functionality in its Swift layer that was not part of the Rust API, then you'll need to add some hand-written Swift code under ./ios/ to implement it. The fxa-client component may be a good example here: its Rust layer exposes a FirefoxAccount struct that the Swift code wraps into a PersistedFirefoxAccount class, adding the ability to set a persistence callback. You will need to add any such file to the \"Compile Sources\" list in Xcode, in the same way that you added the .udl file. Finally, you will need to try out the new bindings with a consuming app. For Swift code you should make a local build of Firefox iOS, you can do that by following the steps in this document Found a bug? Edit this page on GitHub.","breadcrumbs":"Contributing » How to add a new component » How to convert a Rust Component to Uniffi » Set up the Swift wrapper","id":"109","title":"Set up the Swift wrapper"},"11":{"body":"Complete this section before moving to the android/iOS build instructions. Make sure you cloned the repository: $ git clone https://github.com/mozilla/application-services # (or use the ssh link) $ cd application-services $ git submodule update --init --recursive Install Rust: install via rustup Install your system dependencies: Linux Install the system dependencies required for building NSS Install gyp: apt install gyp (required for NSS) Install ninja-build: apt install ninja-build Install python3 (at least 3.6): apt install python3 Install zlib: apt install zlib1g-dev Install perl (needed to build openssl): apt install perl Install patch (to build the libs): apt install patch Install the system dependencies required for SQLcipher Install tcl: apt install tclsh (required for SQLcipher) MacOS Install Xcode: check the ci config for the correct version. Install Xcode tools: xcode-select --install Install homebrew via its installation instructions (it's what we use for ci). Install the system dependencies required for building NSS: Install ninja and python: brew install ninja python Make sure which python3 maps to the freshly installed homebrew python. If it isn't, add the following to your bash/zsh profile and source the profile before continuing: alias python3=$(brew --prefix)/bin/python3 Ensure python maps to the same Python version. You may have to create a symlink: PYPATH=$(which python3); ln -s $PYPATH `dirname $PYPATH`/python Install gyp: wget https://bootstrap.pypa.io/ez_setup.py -O - | python3 -\ngit clone https://chromium.googlesource.com/external/gyp.git ~/tools/gyp\ncd ~/tools/gyp\npython3 setup.py install Add ~/tools/gyp to your path: export PATH=\"~/tools/gyp:$PATH\" If you have additional questions, consult this guide . Make sure your homebrew python's bin folder is on your path by updating your bash/zsh profile with the following: export PATH=\"$PATH:$(brew --prefix)/opt/python@3.9/Frameworks/Python.framework/Versions/3.9/bin\" Windows Install windows build tools Why Windows Subsystem for Linux (WSL) ? It's currently tricky to get some of these builds working on Windows, primarily due to our use of SQLcipher. By using WSL it is possible to get builds working, but still have them published to your \"native\" local maven cache so it's available for use by a \"native\" Android Studio. Install WSL (recommended over native tooling) Install unzip: sudo apt install unzip Install python3: sudo apt install python3 Note: must be python 3.6 or later Install system build tools: sudo apt install build-essential Install zlib: sudo apt-get install zlib1g-dev Install tcl: sudo apt install tcl-dev Check dependencies and environment variables by running: ./libs/verify-desktop-environment.sh Note that this script might instruct you to set some environment variables, set those by adding them to your .zshrc or .bashrc so they are set by default on your terminal. If it does so instruct you, you must run the command again after setting them so the libraries are built. Run cargo test: cargo test Once you have successfully run ./libs/verify-desktop-environment.sh and cargo test you can move to the Building for Fenix and Building for iOS sections below to setup your local environment for testing with our client applications.","breadcrumbs":"Contributing » Building » Building the Rust Components","id":"11","title":"Building the Rust Components"},"110":{"body":"","breadcrumbs":"Contributing » How to add a new component » How to use Rust Components in Android » Rust + Android FAQs","id":"110","title":"Rust + Android FAQs"},"111":{"body":"Use UniFFI , which can produce Kotlin bindings for your Rust code from an interface definition file. If UniFFI doesn't currently meet your needs, please open an issue to discuss how the tool can be improved. As a last resort, you can make hand-written bindings from Rust to Kotlin, essentially manually performing the steps that UniFFI tries to automate for you: flatten your Rust API into a bunch of pub extern \"C\" functions, then use JNA to call them from Kotlin. The details of how to do that are well beyond the scope of this document.","breadcrumbs":"Contributing » How to add a new component » How to use Rust Components in Android » How do I expose Rust code to Kotlin?","id":"111","title":"How do I expose Rust code to Kotlin?"},"112":{"body":"Published packages should be named org.mozilla.appservices.$NAME where $NAME is the name of your component, such as logins. The Java namespace in which your package defines its classes etc should be mozilla.appservices.$NAME.*.","breadcrumbs":"Contributing » How to add a new component » How to use Rust Components in Android » How should I name the package?","id":"112","title":"How should I name the package?"},"113":{"body":"Add it to .buildconfig-android.yml in the root of this repository. This will cause it to be automatically included as part of our release publishing pipeline.","breadcrumbs":"Contributing » How to add a new component » How to use Rust Components in Android » How do I publish the resulting package?","id":"113","title":"How do I publish the resulting package?"},"114":{"body":"Assuming that you're building the Rust code as part of the application-services build and release process, your pub extern \"C\" API should always be available from a file named libmegazord.so.","breadcrumbs":"Contributing » How to add a new component » How to use Rust Components in Android » How do I know what library name to load to access the compiled rust code?","id":"114","title":"How do I know what library name to load to access the compiled rust code?"},"115":{"body":"There are a number of them. The issue boils down to the fact that you need to be completely certain that a JVM is associated with a given thread in order to call java code on it. The difficulty is that the JVM can GC its threads and will not let rust know about it. JNA can work around this for us to some extent, at the cost of some complexity. The approach it takes is essentially to spawn a thread for each callback invocation. If you are certain you’re going to do a lot of callbacks and they all originate on the same thread, you can have them all run on a single thread by using the CallbackThreadInitializer . With the help of JNA's workarounds, calling back from Rust into Kotlin isn’t too bad so long as you ensure that Kotlin cannot GC the callback while rust code holds onto it (perhaps by stashing it in a global variable), and so long as you can either accept the overhead of extra threads being instantiated on each call or are willing to manage the threads explicitly. Note that the situation would be somewhat better if we used JNI directly (and not JNA), but this would cause us to need to generate different Rust FFI code for Android than for iOS. Ultimately, in any case where there is an alternative to using a callback, you should probably pursue that alternative. For example if you're using callbacks to implement async I/O, it's likely better to move to doing a blocking call, and have the calling code dispatch it on a background thread. It’s very easy to run such things on a background thread in Kotlin, is in line with the Android documentation on JNI usage, and in our experience is vastly simpler and less painful than using callbacks. (Of course, not every case is solvable like this).","breadcrumbs":"Contributing » How to add a new component » How to use Rust Components in Android » What challenges exist when calling back into Kotlin from Rust?","id":"115","title":"What challenges exist when calling back into Kotlin from Rust?"},"116":{"body":"We get a couple things from using JNA that we wouldn't with JNI. We are able to use the same Rust FFI code on all platforms. If we used JNI we'd need to generate an Android-specific Rust FFI crate that used the JNI APIs, and a separate Rust FFI crate for exposing to Swift. JNA provides a mapping of threads to callbacks for us, making callbacks over the FFI possible. That said, in practice this is still error prone, and easy to misuse/cause memory safety bugs, but it's required for cases like logging, among others, and so it is a nontrivial piece of complexity we'd have to reimplement. However, it comes with the following downsides: JNA has bugs. In particular, its not safe to use bools with them, it thinks they are 32 bits, when on most platforms (every platform Rust supports) they are 8 bits. They've been unwilling to fix the issue due to it breaking backwards compatibility (which is... somewhat fair, there is a lot of C89 code out there that uses bool as a typedef for a 32-bit int). JNA makes it really easy to do the wrong thing and have it work but corrupt memory. Several of the caveats around this are documented in the ffi_support docs , but a major one is when to use Pointer vs String (getting this wrong will often work, but may corrupt memory). We aim to avoid triggering these bugs by auto-generating the JNA bindings rather than writing them by hand.","breadcrumbs":"Contributing » How to add a new component » How to use Rust Components in Android » Why are we using JNA rather than JNI, and what tradeoffs does that involve?","id":"116","title":"Why are we using JNA rather than JNI, and what tradeoffs does that involve?"},"117":{"body":"Uncomment the packagingOptions { doNotStrip \"**/*.so\" } line from the build.gradle file of the component you want to debug. In the rust code, either: Cause something to crash where you want the breakpoint. Note: Panics don't work here, unfortunately. (I have not found a convenient way to set a breakpoint to rust code, so unsafe { std::ptr::write_volatile(0 as *const _, 1u8) } usually is what I do). If you manage to get an LLDB prompt, you can set a breakpoint using breakpoint set --name foo, or breakpoint set --file foo.rs --line 123. I don't know how to bring up this prompt reliably, so I often do step 1 to get it to appear, delete the crashing code, and then set the breakpoint using the CLI. This is admittedly suboptimal. Click the Debug button in Android Studio, to display the \"Select Deployment Target\" window. Make sure the debugger selection is set to \"Both\". This tends to unset itself, so make sure. Click \"Run\", and debug away. Found a bug? Edit this page on GitHub.","breadcrumbs":"Contributing » How to add a new component » How to use Rust Components in Android » How do I debug Rust code with the step-debugger in Android Studio","id":"117","title":"How do I debug Rust code with the step-debugger in Android Studio"},"118":{"body":"Application-services components are consumed by multiple consumers including Firefox Android, Firefox iOS, Focus Android, and Focus iOS. To minimize the disruption to those projects when making breaking API changes, we follow a simple rule: Have approved PRs ready to land that fix the breakage in the other repos before merging the PR into application-services . This means writing code for the firefox-android and firefox-ios repositories that resolves any breaking changes, creating a PR in those repositories, and waiting for it to be approved. You can test this code locally using the autopublish flow ( Android , iOS ) and use the branch build system to run CI tests.","breadcrumbs":"Contributing » Breaking API changes » Breaking changes in application-services code","id":"118","title":"Breaking changes in application-services code"},"119":{"body":"Do not merge any PRs until all are approved. Once they are all approved then: Merge the application-services PR into main Manually trigger a new nightly build using the taskcluster hook: https://firefox-ci-tc.services.mozilla.com/hooks/project-releng/cron-task-mozilla-application-services%2Fnightly Once the nightly task completes, trigger a new rust-components-swift build using the github action: https://github.com/mozilla/rust-components-swift/actions/workflows/update-as-nightly.yml Update the firefox-android and firefox-ios PRs to use the newly built nightly: example of firefox-android changes example of firefox-ios changes Ideally, get the PRs merged before the firefox-android/firefox-ios nightly bump the next day. If you don't get these merged, then the nightly bump PR will fail. Add a link to your PR in the nightly bump PR so the mobile teams know how to fix this. Found a bug? Edit this page on GitHub.","breadcrumbs":"Contributing » Breaking API changes » Merging","id":"119","title":"Merging"},"12":{"body":"The following instructions assume that you are building application-services for Fenix, and want to take advantage of the Fenix Auto-publication workflow for android-components and application-services . Install Android SDK, JAVA, NDK and set required env vars Clone the firefox-android repository ( not inside the Application Service repository). Install Java 17 for your system Set JAVA_HOME to point to the JDK 17 installation directory. Download and install Android Studio . Set ANDROID_SDK_ROOT and ANDROID_HOME to the Android Studio sdk location and add it to your rc file (either .zshrc or .bashrc depending on the shell you use for your terminal). Configure the required versions of NDK Configure menu > System Settings > Android SDK > SDK Tools > NDK > Show Package Details > NDK (Side by side) 21.4.7075529 (required by Fenix; note: a specific NDK version isn't configured, this maps to default NDK version for the AGP version ) 25.2.9519653 (required by Application Services, as configured ) If you are on Windows using WSL - drop to the section below, Windows setup for Android (WSL) before proceeding. Check dependencies, environment variables Run ./libs/verify-android-environment.sh Follow instructions and rerun until it is successful.","breadcrumbs":"Contributing » Building » Building for Fenix","id":"12","title":"Building for Fenix"},"120":{"body":"Some of these components are used in mozilla-central . This document describes how to update existing components or add new components. The general process for vendoring rust code into mozilla-central has its own documentation - please make sure you read that before continuing.","breadcrumbs":"Contributing » How to vendor application-services into mozilla-central » Vendoring Application Services into mozilla-central","id":"120","title":"Vendoring Application Services into mozilla-central"},"121":{"body":"We want to keep our versions in moz-central relatively up-to-date, but it takes some manual effort to do. The main possibility of breakage is from a dependency mismatch, so our current vendoring policy is: Whenever a 3rd-party dependency is added or updated, the dev who made the change is responsible for vendoring. At the start of the release cycle the triage owner is response for vendoring.","breadcrumbs":"Contributing » How to vendor application-services into mozilla-central » When to vendor","id":"121","title":"When to vendor"},"122":{"body":"To update components which are already in mozilla-central, follow these steps: Ensure your mozilla-central build environment is setup correctly to make \"non-artifact\" builds - check you can get a full working build before starting this process. Run ./tools/update-moz-central-vendoring.py [path-to-moz-central] from the application-services root directory. If this generates errors regarding duplicate crates, you will enter a world of pain, and probably need to ask for advice from the application-services team, and/or the #build channel on matrix . Run ./mach cargo vet to check if there any any new dependencies that need to be vetted. If there are ask for advice from the application-services team. Build and test your tree. Ideally make a try run. Put your patch up to phabricator, requesting review from, at least, someone on the application-services team and one of the \"build peers\" - asking on #build on matrix for a suitable reviewer might help. Alternatively, try and find the bug which made the most recent update and ask the same reviewer in that patch. Profit!","breadcrumbs":"Contributing » How to vendor application-services into mozilla-central » Updating existing components.","id":"122","title":"Updating existing components."},"123":{"body":"Follow the Uniffi documentation on mozilla-central to understand where you'll need to add your crate path and UDL. In general: The consuming component will specify the dependency as a nominal \"version 0.1\" The top-level Cargo.toml will override that dependency with a specific git revision. For example, consider the webext-storage crate: The consuming crate specifies version 0.1 The top-level Cargo.toml specifies the exact revision. Adding a new component implies there will be related mozilla-central changes which leverage it. The best practice here is to land both the vendoring of the new component and the related mozilla-central changes in the same bug, but in different phabricator patches. As noted above, the best-practice is that all application-services components are on the same revision, so adding a new component implies you will generally also be updating all the existing components. For an example of a recently added component, the tabs was recently added to mozilla-central with uniffi and shows a general process to follow.","breadcrumbs":"Contributing » How to vendor application-services into mozilla-central » Adding a new component","id":"123","title":"Adding a new component"},"124":{"body":"Sometimes you will need to make changes in application-services and in mozilla-central simultaneously - for example, you may need to add new features or capabilities to a component, and matching changes in mozilla-central to use that new feature. In that scenario, you don't want to check your changes in and re-vendor as you iterate - it would be far better to use a local checkout of application-services with uncommitted changes with your mozilla-central tree which also has uncommited changes. To do this, you can edit the top-level Cargo.toml to specify a path. Note however that in this scenario, you need to specify the path to the individual component rather than to the top-level of the repo. For example, you might end up with something like: # application-services overrides to make updating them all simpler.\ninterrupt-support = { path = \"../application-services/components/support/interrupt\" }\nsql-support = { path = \"../application-services/components/support/sql\" }\nsync15-traits = { path = \"../application-services/components/support/sync15-traits\" }\nviaduct = { path = \"../application-services/components/viaduct\" }\nwebext-storage = { path = \"../application-services/components/webext-storage\" } Note that when you first do this, it will still be necessary to run ./mach vendor rust and to re-build. After you make a change to the local repository, you do not need to run ./mach vendor rust, but you do still obviously need to rebuild. Once you are happy with all the changes, you would: Open a PR up in application-services and land your changes there. Follow the process above to re-vendor your new changes, and in that same bug (although not necessarily the same phabricator patch), include the other mozilla-central changes which rely on the new version. Found a bug? Edit this page on GitHub.","breadcrumbs":"Contributing » How to vendor application-services into mozilla-central » Vendoring an unreleased version for testing purposes","id":"124","title":"Vendoring an unreleased version for testing purposes"},"125":{"body":"When writing code in application-services, code implemented in Rust, Kotlin, Java, or Swift might have to write debug logs. To do so, one should generally log using the normal logging facilities for the language. Where the logs go depends on the application which is embedding the components.","breadcrumbs":"Contributing » Logging » Application Services Logging","id":"125","title":"Application Services Logging"},"126":{"body":"On android, logs currently go to logcat. (This may change in the future.) Android Studio can be used to view the logcat logs; connect the device over USB and view the Logcat tab at the bottom of Android Studio. Check to make sure you have the right device selected at the top left of the Logcat pane, and the correct process to the right of that. One trick to avoid having to select the correct process (as there are main and content processes) is to choose \"No Filters\" from the menu on the top right of the Logcat pane. Then, use the search box to search for the log messages you are trying to find. There are also many other utilities, command line and graphical, that can be used to view logcat logs from a connected android device in a more flexible manner. Changing the loglevel in Fenix If you need more verbose logging, after the call to RustLog.enable() in FenixApplication, you may call RustLog.setMaxLevel(Log.Priority.DEBUG, true).","breadcrumbs":"Contributing » Logging » Accessing logs when running Fenix","id":"126","title":"Accessing logs when running Fenix"},"127":{"body":"[TODO] Found a bug? Edit this page on GitHub.","breadcrumbs":"Contributing » Logging » Accessing logs when running iOS","id":"127","title":"Accessing logs when running iOS"},"128":{"body":"UniFFI supports interface objects , which are implemented by Boxing a Rust object and sending the raw pointer to the foreign code. Once the objects are no longer in use, the foreign code needs to destroy the object and free the underlying resources. This is slightly tricky on Kotlin. The prevailing Java wisdom is to use explicit destructors and avoid using finalizers for destruction , which means we can't simply rely on the garbage collector to free the pointer. The wisdom seems simple to follow, but in practice it can be difficult to know how to apply it to specific situations. This document examines provides guidelines for handling UniFFI objects.","breadcrumbs":"Contributing » UniFFI Object Destruction on Kotlin » UniFFI object destruction on Kotlin","id":"128","title":"UniFFI object destruction on Kotlin"},"129":{"body":"The simplest way to get destruction right is to create an object and destroy it in the same function. The use function makes this really easy: SomeUniFFIObject() .use { obj -> obj.doSomething() obj.doSomethingElse() }","breadcrumbs":"Contributing » UniFFI Object Destruction on Kotlin » You can create objects in a function if you also destroy them there","id":"129","title":"You can create objects in a function if you also destroy them there"},"13":{"body":"Note: For non-Ubuntu linux versions, it may be necessary to execute $ANDROID_HOME/tools/bin/sdkmanager \"build-tools;26.0.2\" \"platform-tools\" \"platforms;android-26\" \"tools\". See also this gist for additional information. Configure Maven Configure maven to use the native windows maven repository - then, when doing ./gradlew install from WSL, it ends up in the Windows maven repo. This means we can do a number of things with Android Studio in \"native\" windows and have then work correctly with stuff we built in WSL. Install maven: sudo apt install maven Confirm existence of (or create) a ~/.m2 folder In the ~/.m2 create a file called settings.xml Add the content below replacing {username} with your username: /mnt/c/Users/{username}/.m2/repository ","breadcrumbs":"Contributing » Building » Windows setup for Android (via WSL)","id":"13","title":"Windows setup for Android (via WSL)"},"130":{"body":"If we are okay with UniFFI objects living for the entire application lifetime, then they can be stored in singletons. This is how we handle our database connections, for example SyncableLoginsStorage and PlacesReaderConnection .","breadcrumbs":"Contributing » UniFFI Object Destruction on Kotlin » You can create and store objects in singletons","id":"130","title":"You can create and store objects in singletons"},"131":{"body":"UniFFI objects can stored in classes like the Android Fragment class that have a defined lifecycle, with methods called at different stages. Classes can construct UniFFI objects in one of the lifecycle methods, then destroy it in the corresponding one. For example, creating an object in Fragment.onCreate and destroying it in Fragment.onDestroy().","breadcrumbs":"Contributing » UniFFI Object Destruction on Kotlin » You can create and store objects in an class, then destroy them in a corresponding lifecycle method","id":"131","title":"You can create and store objects in an class, then destroy them in a corresponding lifecycle method"},"132":{"body":"Several classes can hold references to an object, as long as (exactly) one class is responsible for managing it and destroying it when it's not used. A good example is the GeckoLoginStorageDelegate . The LoginStorage is initialized and managed by another object, and GeckoLoginStorageDelegate is passed a (lazy) reference to it. Care should be taken to ensure that once the managing class destroys the object, no other class attempts to use it. If they do, then the generate code will raise an IllegalStateException. This clearly should be avoided, although it won't result in memory corruption.","breadcrumbs":"Contributing » UniFFI Object Destruction on Kotlin » You can share objects","id":"132","title":"You can share objects"},"133":{"body":"Destructors may not run when a process is killed, which can easily happen on Android. This is especially true of lifecycle methods. This is normally fine, since the OS will close resources like file handles and network connections on its own. However, be aware that custom code in the destructor may not run. Found a bug? Edit this page on GitHub.","breadcrumbs":"Contributing » UniFFI Object Destruction on Kotlin » Destruction may not always happen","id":"133","title":"Destruction may not always happen"},"134":{"body":"This log lists the architectural decisions for MADR. ADR-0000 - Use Markdown Architectural Decision Records ADR-0001 - Update Logins API ADR-0002 - Handling Database Corruption ADR-0003 - Distributing Swift Packages ADR-0004 - Running experiments on first run early startup ADR-0005 - A remote-settings client for our mobile browsers. ADR-0007 - Limit Visits Migrated to Places History in Firefox iOS For new ADRs, please use template.md as basis. More information on MADR is available at https://adr.github.io/madr/ . General information about architectural decision records is available at https://adr.github.io/ .","breadcrumbs":"Architectural Decision Records » Architectural Decision Log","id":"134","title":"Architectural Decision Log"},"135":{"body":"","breadcrumbs":"Architectural Decision Records » ADR-0000 » Use Markdown Architectural Decision Records","id":"135","title":"Use Markdown Architectural Decision Records"},"136":{"body":"We want to record architectural decisions made in this project. Which format and structure should these records follow?","breadcrumbs":"Architectural Decision Records » ADR-0000 » Context and Problem Statement","id":"136","title":"Context and Problem Statement"},"137":{"body":"MADR 2.1.2 – The Markdown Architectural Decision Records Michael Nygard's template – The first incarnation of the term \"ADR\" Sustainable Architectural Decisions – The Y-Statements Other templates listed at https://github.com/joelparkerhenderson/architecture_decision_record Formless – No conventions for file format and structure","breadcrumbs":"Architectural Decision Records » ADR-0000 » Considered Options","id":"137","title":"Considered Options"},"138":{"body":"Chosen option: \"MADR 2.1.2\", because Implicit assumptions should be made explicit. Design documentation is important to enable people understanding the decisions later on. See also A rational design process: How and why to fake it . The MADR format is lean and fits our development style. The MADR structure is comprehensible and facilitates usage & maintenance. The MADR project is vivid. Version 2.1.2 is the latest one available when starting to document ADRs. Found a bug? Edit this page on GitHub.","breadcrumbs":"Architectural Decision Records » ADR-0000 » Decision Outcome","id":"138","title":"Decision Outcome"},"139":{"body":"Status: accepted Date: 2021-06-17 Technical Story: #4101","breadcrumbs":"Architectural Decision Records » ADR-0001 » Update Logins API","id":"139","title":"Update Logins API"},"14":{"body":"Install xcpretty : gem install xcpretty Run ./libs/verify-ios-environment.sh to check your setup and environment variables. Make any corrections recommended by the script and re-run. Next, run ./megazords/ios-rust/build-xcframework.sh to build all the binaries needed to consume a-s in iOS Once the script passes, you should be able to run the Xcode project. Note: The built Xcode project is located at megazords/ios-rust/MozillaTestServices.xcodeproj. Note: This is mainly for testing the rust components, the artifact generated in the above steps should be all you need for building application with application-services","breadcrumbs":"Contributing » Building » Building for Firefox iOS","id":"14","title":"Building for Firefox iOS"},"140":{"body":"We no longer want to depend on SQLCipher and want to use SQLite directly for build complexity and concerns over the long term future of the rust bindings. The encryption approach taken by SQLCipher means that in practice, the entire database is decrypted at startup, even if the logins functionality is not interacted with, defeating some of the benefits of using an encrypted database. The per-field encryption in autofill, which we are planning to replicate in logins, separates the storage and encryption logic by limiting the storage layer to the management of encrypted data. Applying this approach in logins will break the existing validation and deduping code so we need a way to implement per-field encryption while supporting the validation and de-duping behavior.","breadcrumbs":"Architectural Decision Records » ADR-0001 » Context and Problem Statement","id":"140","title":"Context and Problem Statement"},"141":{"body":"Addressing previously identified deficiencies in the logins API while we are breaking the API for the encryption work Continuing to support the existing logins validation and deduping logic Avoiding the implementation of new security approaches that may require additional time and security resources Establishing a standard encyrption approach across components","breadcrumbs":"Architectural Decision Records » ADR-0001 » Decision Drivers","id":"141","title":"Decision Drivers"},"142":{"body":"Option 1 - Reduce the API functions that require the encryption key and pass the key to the remaining functions Option 2 - Keep the general shape of the API that is in place now - the app can pass the encryption key at any time to \"unlock\" the API, and re-lock it at any time, but the API in its entirety is only available when unlocked","breadcrumbs":"Architectural Decision Records » ADR-0001 » Considered Options","id":"142","title":"Considered Options"},"143":{"body":"Chosen Option: \"Reduce the API functions that require the encryption key and pass the key to the remaining functions\" because it will not require a security review as similar to the approach we have established in the codebase.","breadcrumbs":"Architectural Decision Records » ADR-0001 » Decision Outcome","id":"143","title":"Decision Outcome"},"144":{"body":"","breadcrumbs":"Architectural Decision Records » ADR-0001 » Pros and Cons of the Options","id":"144","title":"Pros and Cons of the Options"},"145":{"body":"Description Currently the below logins API functions would require the per-field encryption key: add update get_all get_by_base_domain get_by_id check_valid_with_no_dupes potential_dupes_ignoring_username import_multiple Note: Functions related to sync have been omitted as it is assumed they will have access to decrypted data. The get_all, get_by_base_domain, and get_by_id functions will require the encryption key because they call the validate and fixup logic, not because we want to return logins with decrypted data. Propsed changes: Combine the add and update functions into a new add_or_update function This will allow the removal of consumer code that distinguishes when a login record should be created or updated Note: This function needs the encryption key for the fixup and deduping logic and for continued support of the accurate population of the time_password_changed field Pass the per-field encryption key to the import_multiple function This function will be removed once the Fennec to Fenix migration period ends Remove both the potential_dupes_ignoring_username and check_valid_with_no_dupes from the API Neither function is called in Firefox iOS Android Components uses both to provide validation and de-duping before logins are added or updated so we can eliminate the need to externalize these functions by replicating this logic in the new add_or_update function Create a decrypt_and_fixup_login function that both decrypts a login and performs the validate and fixup logic This will eliminate the need for the get_all, get_by_base_domain, and get_by_id API functions to perform the fixup logic Making the above changes will reduce the API functions requiring the encryption key to the following: add_or_update decrypt_and_fixup_login import_multiple Pros Improves the logins API for consumers by combining add/update functionality (see #3899 for details) Removes redundant validation and de-duping logic in consumer code Uses the same encryption model as autofill so there is consistency in our approaches Cons Requires consumer code to both encrypt login fields and pass the encryption key when calling either add_or_update and import_multiple","breadcrumbs":"Architectural Decision Records » ADR-0001 » Option 1 - Reduce the API functions that require the encryption key and pass the key to the remaining functions","id":"145","title":"Option 1 - Reduce the API functions that require the encryption key and pass the key to the remaining functions"},"146":{"body":"Description Unlike the first option, the publicly exposed login API would only handle decrypted login records and all encryption is internal (which works because we always have the key). Any attempt to use the API will fail as the login records are not encrypted or decrypted if the key is not available. Proposed changes: Combine the add and update functions into add_or_update Remove both the potential_dupes_ignoring_username and check_valid_with_no_dupes from the API Pros Prevents the consumer from having to encrypt or decrypt login records Maintains our current fixup and validation approach Improves the logins API for consumers by combining add/update functionality Removes redundant validation and de-duping logic in consumer code Cons Makes us responsible for securing the encryption key and will most likely require a security review","breadcrumbs":"Architectural Decision Records » ADR-0001 » Option 2 - Implement a different key management approach","id":"146","title":"Option 2 - Implement a different key management approach"},"147":{"body":"Logins Validate and Fixup Call Tree Found a bug? Edit this page on GitHub.","breadcrumbs":"Architectural Decision Records » ADR-0001 » Links","id":"147","title":"Links"},"148":{"body":"Status: accepted Date: 2021-06-08","breadcrumbs":"Architectural Decision Records » ADR-0002 » Handling Database Corruption","id":"148","title":"Handling Database Corruption"},"149":{"body":"Some of our users have corrupt SQLite databases and this makes the related component unusable. The best way to deal with corrupt databases is to simply delete the database and start fresh (#2628). However, we only want to do this for persistent errors, not transient errors like programming logic errors, disk full, etc. This ADR deals with 2 related questions: A) When and how do we identify corrupted databases? B) What do we do when we identify corrupted databases?","breadcrumbs":"Architectural Decision Records » ADR-0002 » Context and Problem Statement","id":"149","title":"Context and Problem Statement"},"15":{"body":"Detailed steps to build Firefox iOS against a local application services can be found this document Found a bug? Edit this page on GitHub.","breadcrumbs":"Contributing » Building » Locally building Firefox iOS against a local Application Services","id":"15","title":"Locally building Firefox iOS against a local Application Services"},"150":{"body":"Deleting valid user data should be avoided at almost any cost Keeping a corrupted database around is almost as bad. It currently prevents the component from working at all. We don't currently have a good way to distinguish between persistent and transient errors, but this can be improved by reviewing telemetry and sentry data.","breadcrumbs":"Architectural Decision Records » ADR-0002 » Decision Drivers","id":"150","title":"Decision Drivers"},"151":{"body":"A) When and how do we identify corrupted databases? 1: Assume all errors when opening a database are from corrupt databases 2: Check for errors when opening a database and compare against known corruption error types 3: Check for errors for all database operations and compare against known corruption error types B) What do we do when we identify corrupted databases? 1: Delete the database file and recreate the database 2: Move the database file and recreate the database 3: Have the component fail","breadcrumbs":"Architectural Decision Records » ADR-0002 » Considered Options","id":"151","title":"Considered Options"},"152":{"body":"A2: Check for errors when opening a database and compare against known corruption error types B1: Delete the database file and recreate the database Decision B follows from the choice of A. Since we're being conservative in identifying errors, we can delete the database file with relative confidence. \"Check for errors for all database operations and compare against known corruption error types\" also seems like a reasonable solution that we may pursue in the future, but we decided to wait for now. Checking for errors during opening time is the simpler solution to implement and should fix the issue in many cases. The plan is to implement that first, then monitor sentry/telemetry to decide what to do next.","breadcrumbs":"Architectural Decision Records » ADR-0002 » Decision Outcome","id":"152","title":"Decision Outcome"},"153":{"body":"","breadcrumbs":"Architectural Decision Records » ADR-0002 » Pros and Cons of the Options","id":"153","title":"Pros and Cons of the Options"},"154":{"body":"Good, because the sentry data indicates that many errors happen during opening time Good, because migrations are especially likely to trigger corruption errors Good, because it's a natural time to delete the database -- the consumer code hasn't run any queries yet and doesn't have any open connections. Bad, because it will delete valid user data in several situations that are relatively common: migration logic errors, OOM errors, Disk full.","breadcrumbs":"Architectural Decision Records » ADR-0002 » A1: Assume all errors when opening a database are from corrupt databases","id":"154","title":"A1: Assume all errors when opening a database are from corrupt databases"},"155":{"body":"Good, because should eliminate the possibility of deleting valid user data. Good, because the sentry data indicates that many errors happen during opening time Good, because it's a natural time to delete the database -- the consumer code hasn't run any queries yet and doesn't have any open connections. Bad, because we don't currently have a good list corruption errors","breadcrumbs":"Architectural Decision Records » ADR-0002 » A2: Check for errors when opening a database and compare against known corruption error types (Decided)","id":"155","title":"A2: Check for errors when opening a database and compare against known corruption error types (Decided)"},"156":{"body":"Good, because the sentry data indicates that many errors happen outside of opening time Good, because should eliminate the possibility of deleting valid user data. Bad, because the consumer code probably doesn't expect the database to be deleted and recreated in the middle of a query. However, this is just an extreme case of normal database behavior -- for example any given row can be deleted during a sync. Bad, because we don't currently have a good list corruption errors","breadcrumbs":"Architectural Decision Records » ADR-0002 » A3: Check for errors for all database operations and compare against known corruption error types","id":"156","title":"A3: Check for errors for all database operations and compare against known corruption error types"},"157":{"body":"Good, because it would allow users with corrupted databases to use the affected components again Bad, because any misidentification will lead to data loss.","breadcrumbs":"Architectural Decision Records » ADR-0002 » B1: Delete the database file and recreate the database (Decided)","id":"157","title":"B1: Delete the database file and recreate the database (Decided)"},"158":{"body":"This option would be similar to 1, but instead of deleting the file we would move it to a backup location. When we started up, we could look for backup files and try to import lost data. Good, because if we misidentify corrupt databases, then we have the possibility of recovering the data Good, because it allows a way for users to delete their data (in theory). If the consumer code executed a wipe() on the database, we could also delete any backup data. Bad, because it's very difficult to write a recovery function that merged deleted data with any new data. This function would be fairly hard to test and it would be easy to introduce a new logic error. Bad, because it adds significant complexity to the database opening code Bad, because the user experience would be strange. A user would open the app, discover that their data was gone, then sometime later discover that the data is back again.","breadcrumbs":"Architectural Decision Records » ADR-0002 » B2: Move the database file and recreate the database","id":"158","title":"B2: Move the database file and recreate the database"},"159":{"body":"Good, because this option leaves no chance of user data being deleted Good, because it's the simplest to implement Bad, because the component will not be usable if the database is corrupt Bad, because the user's data is potentially exposed in the corrupted database file and we don't provide any way for them to delete it. Found a bug? Edit this page on GitHub.","breadcrumbs":"Architectural Decision Records » ADR-0002 » B3: Return a failure code","id":"159","title":"B3: Return a failure code"},"16":{"body":"It's often important to test work-in-progress changes to Application Services components against a real-world consumer project. The most reliable method of performing such testing is to publish your components to a local Maven repository, and adjust the consuming project to install them from there. With support from the upstream project, it's possible to do this in a single step using our auto-publishing workflow.","breadcrumbs":"Contributing » Building » How to use the local development autopublish flow for Fenix » Using locally published components in Fenix","id":"16","title":"Using locally published components in Fenix"},"160":{"body":"Status: accepted Deciders: rfkelly Date: 2021-07-22","breadcrumbs":"Architectural Decision Records » ADR-0003 » Distributing Swift Packages","id":"160","title":"Distributing Swift Packages"},"161":{"body":"Our iOS consumers currently obtain application-services as a pre-compiled .framework bundle distributed via Carthage . The current setup is not compatible with building on new M1 Apple Silicon machines and has a number of other problems. As part of a broader effort to modernize the build process of iOS applications at Mozilla, we have been asked to re-evaluate how application-services components are dsitributed for iOS. See Problems with the current setup for more details.","breadcrumbs":"Architectural Decision Records » ADR-0003 » Context and Problem Statement","id":"161","title":"Context and Problem Statement"},"162":{"body":"Ease-of-use for iOS consumers. Compatibility with M1 Apple Silicon machines. Consistency with other iOS components being developed at Mozilla. Ability for the Nimbus Swift bindings to easily depend on Glean. Ease of maintainability for application-services developers.","breadcrumbs":"Architectural Decision Records » ADR-0003 » Decision Drivers","id":"162","title":"Decision Drivers"},"163":{"body":"(A) Do Nothing Keep our current build and distribution setup as-is. (B) Use Carthage to build XCFramework bundles Make a minimal change to our Carthage setup so that it builds the newer XCFramework format, which can support M1 Apple Silicon. (C) Distribute a single pre-compiled Swift Package Convert the all-in-one MozillaAppServices Carthage build to a similar all-in-one Swift Package, distributed as a binary artifact. (D) Distribute multiple source-based Swift Package targets, with pre-compiled Rust code Split the all-in-one MozillaAppServices Carthage build into a separate Swift Package target for each component, with a shared dependency on pre-compiled Rust code as a binary artiact.","breadcrumbs":"Architectural Decision Records » ADR-0003 » Considered Options","id":"163","title":"Considered Options"},"164":{"body":"Chosen option: (D) Distribute multiple source-based Swift Packages, with pre-compiled Rust code . This option will provide the best long-term consumer experience for iOS developers, and has the potential to simplify maintenance for application-services developers after an initial investment of effort.","breadcrumbs":"Architectural Decision Records » ADR-0003 » Decision Outcome","id":"164","title":"Decision Outcome"},"165":{"body":"Swift packages are very convenient to consume in newer versions of Xcode. Different iOS apps can choose to import a different subset of the available components, potentiallying helping keep application size down. Avoids issues with mis-matched Swift version between application-services build and consumers, since Swift files are distributed in source form. Encourages better conceptual separation between Swift code for different components; e.g. it will make it possible for two Swift components to define an item of the same name without conflicts. Reduces the need to use Xcode as part of application-services build process, in favour of command-line tools.","breadcrumbs":"Architectural Decision Records » ADR-0003 » Positive Consequences","id":"165","title":"Positive Consequences"},"166":{"body":"More up-front work to move to this new setup. We may be less likely to notice if our build setup breaks when used from within Xcode, because we're not exercising that code path ourselves. May be harder to concurrently publish a Carthage framework for current consumers who aren't able to move to Swift packages. There is likely to be some amount of API breakage for existing consumers, if only in having to replace a single import MozillaAppServices with independent imports of each component.","breadcrumbs":"Architectural Decision Records » ADR-0003 » Negative Consequences","id":"166","title":"Negative Consequences"},"167":{"body":"We will maintain the existing Carthage build infrastructure in the application-services repo and continue publishing a pre-built Carthage framework, to support firefox-ios until they migrate to Swift Packages. We will add an additional iOS build task in the application-services repo, that builds just the Rust code as a .xcframework bundle. An initial prototype shows that this can be achieved using a relatively straightforward shell script, rather than requiring a second Xcode project. It will be published as a .zip artifact on each release in the same way as the current Carthage framework. The Rust code will be built as a static library, so that the linking process of the consuming application can pull in just the subset of the Rust code that is needed for the components it consumes. We will initially include only Nimbus and its dependencies in the .xcframework bundle, but will eventually expand it to include all Rust components (including Glean, which will continue to be included in the application-services repo as a git submodule) We will create a new repository rust-components-swift to serve as the root of the new Swift Package distribution. It will import the application-services repository as a git submodule. This will let us iterate quickly on the Swift packaging setup without impacting existing consumers. We will initially include only Nimbus and its dependencies in this new repository, and the Nimbus swift code it will depend on Glean via the external glean-swift package. In the future we will publish all application-services components that have a Swift interface through this repository, as well as Glean and any future Rust components. (That's why the repository is being given a deliberately generic name). The rust-components-swift repo will contain a Package.swift file that defines: A single binary target that references the pre-built .xcframework bundle of Rust code. One Swift target for each component, that references the Swift code from the git submodule and depends on the pre-built Rust code. We will add automation to the rust-components-swift repo so that it automatically tracks releases made in the application-services repo and creates a corresponding git tag for the Swift package. At some future date when all consumers have migrated to using Swift packages, we will remove the Carthage build setup from the application-services repo. At some future date, we will consider whether to move the Package.swift definition in to the application-services repo, or whether it's better to keep it separate. (Attempting to move it into the application-services will involve non-trivial changes to the release process, because the checksum of the released .xcframework bundle needs to be included in the release taged version of the Package.swift file.)","breadcrumbs":"Architectural Decision Records » ADR-0003 » Implementation Sketch","id":"167","title":"Implementation Sketch"},"168":{"body":"","breadcrumbs":"Architectural Decision Records » ADR-0003 » Pros and Cons of the Options","id":"168","title":"Pros and Cons of the Options"},"169":{"body":"In this option, we would make no changes to our iOS build and publishing process. Good, because it's the least amount of work. Neutral, because it doesn't change the maintainability of the system for appservices developers. Neutral, because it doesn't change the amount of separation between Swift code for our various components. Neutral, because it doesn't address the Swift version incompatibility issues around binary artifacts. Bad, because it will frustrate consumers who want to develop on M1 Apple Silicon. Bad, because it may prevent consumers from migrating to a more modern build setup. Bad, because it would prevent consumers from consuming Glean as a Swift package; we would require them to use the Glean that is bundled in our build. This option isn't really tractable for us, but it's included for completeness.","breadcrumbs":"Architectural Decision Records » ADR-0003 » (A) Do Nothing","id":"169","title":"(A) Do Nothing"},"17":{"body":"Both the auto-publishing and manual workflows can be sped up significantly by using the rust.targets property which limits which architectures the Rust code gets build against. You can set this property by creating/editing the local.properties file in the repository root and adding a line like rust.targets=x86,linux-x86-64. The trick is knowing which targets to put in that comma separated list: Use x86 for running the app on most emulators (in rare cases, when you have a 64-bit emulator, you'll want x86_64) If you're running the android-components or fenix unit tests, then you'll need the architecture of your machine: OSX running Intel chips: darwin-x86-64 OSX running M1 chips: darwin-aarch64 Linux: linux-x86-64","breadcrumbs":"Contributing » Building » How to use the local development autopublish flow for Fenix » rust.targets","id":"17","title":"rust.targets"},"170":{"body":"In this option, we would try to change our iOS build and publising process as little as possible, but use Carthage's recent support for building platform-independent XCFrameworks in order to support consumers running on M1 Apple Silicon. Good, because the size of the change is small. Good, because we can support development on newer Apple machines. Neutral, because it doesn't change the maintainability of the system for appservices developers. Neutral, because it doesn't change the amount of separation between Swift code for our various components. Neutral, because it doesn't address the Swift version incompatibility issues around binary artifacts. Bad, because our iOS consumers have expressed a preference for moving away from Carthage. Bad, because other iOS projects at Mozilla are moving to Swift Packages, making us inconsistent with perceived best practice. Bad, because it would prevent consumers from consuming Glean as a Swift package; we would require them to use the Glean that is bundled in our build. Bad, because consumers don't get to choose which components they want to use (without us building a whole new \"megazord\" with just the components they want). Overall, current circumstances feel like a good opportunity to invest a little more time in order to set ourselves up for better long-term maintainability and happier consumers. The main benefit of this option (it's quicker!) is less attractive under those circumstances.","breadcrumbs":"Architectural Decision Records » ADR-0003 » (B) Use Carthage to build XCFramework bundles","id":"170","title":"(B) Use Carthage to build XCFramework bundles"},"171":{"body":"In this option, we would compile the Rust code and Swift code for all our components into a single .xcframework bundle, and then distribute that as a binary artifact via Swift Package. This is similar to the approach currently taken by Glean (ref Bug 1711447 ) except that they only have a single component. Good, because Swift Packages are the preferred distribution format for new iOS consumers. Good, because we can support development on newer Apple machines. Good, because it aligns with what other iOS component developers are doing at Mozilla. Neutral, because it doesn't change the maintainability of the system for appservices developers. (We'd need to keep the current Xcode project broadly intact). Neutral, because it doesn't change the amount of separation between Swift code for our various components. Neutral, because it doesn't address the Swift version incompatibility issues around binary artifacts. Neutral, because it would prevent consumers from consuming Glean as a separate Swift package; they'd have to get it as part of our all-in-one Swift package. Bad, because it's a larger change and we have to learn about a new package manager. Bad, because consumers don't get to choose which components they want to use (without building a whole new \"megazord\" with just the components they want). Overall, this option would be a marked improvement on the status quo, but leaves out some potential improvements. For not that much more work, we can make some of the \"Neutral\" and \"Bad\" points here into \"Good\" points.","breadcrumbs":"Architectural Decision Records » ADR-0003 » (C) Distribute a single pre-compiled Swift Package","id":"171","title":"(C) Distribute a single pre-compiled Swift Package"},"172":{"body":"In this option, we would compile just the Rust code for all our components into a single .xcframework bundle and distribute that as a binary artifact via Swift Package. We would then declare a separate Swift source target for the Swift wrapper of each component, each depending on the compiled Rust code but appearing as a separate item in the Swift package definition. Good, because Swift Packages are the preferred distribution format for new iOS consumers. Good, because we can support development on newer Apple machines. Good, because it aligns with what other iOS component developers are doing at Mozilla. Good, because it can potentially simplify the maintenance of the system for appservices developers, by removing Xcode in favour of some command-line scripts. Good, because it introduces strict separation between the Swift code for each component, instead of compiling them all together in a single shared namespace. Good, because the Nimbus Swift package could cleanly depend on the Glean Swift package. Good, because consumers can choose which components they want to include. Good, because it avoids issues with Swift version incompatibility in binary artifacts. Bad, because it's a larger change and we have to learn about a new package manager. The only downside to this option appears to be the amount of work involved, but an initial prototype has given us some confidence that the change is tractable and that it may lead to a system that is easier to maintain over time. It is thus our preferred option.","breadcrumbs":"Architectural Decision Records » ADR-0003 » (D) Distribute multiple source-based Swift Packages, with pre-compiled Rust code","id":"172","title":"(D) Distribute multiple source-based Swift Packages, with pre-compiled Rust code"},"173":{"body":"","breadcrumbs":"Architectural Decision Records » ADR-0003 » Appendix","id":"173","title":"Appendix"},"174":{"body":"Bug 1711447 has good historical context on the work to move Glean to using a Swift Package. Some material on swift packages: Managing dependencies using the Swift Package Manager was a useful overview. Understanding Swift Packages and Dependency Declarations gives a bit of a deeper dive into having multiple targets with different names in a single package. Outputs of initial prototype: A prototype of Option (C): Nimbus + Glean as a pre-built XCFramework Swift Package A prototype of Option (D): Rust code as XCFRamework plus a Multi-product Swift Package that depends on it. A video demo of the resulting consumer experience.","breadcrumbs":"Architectural Decision Records » ADR-0003 » Further Reading","id":"174","title":"Further Reading"},"175":{"body":"It doesn't build for M1 Apple Silicon machines, because it's not possible to support both arm64 device builds and arm64 simulator builds in a single binary .framework. Carthage is dispreferred by our current iOS consumers. We don't have much experience with the setup on the current Application Services team, and many of its details are under-documented. Changing the build setup requires Xcode and some baseline knowledge of how to use it. All components are built as a single Swift module, meaning they can see each other's internal symbols and may accidentally conflict when naming things. For example we can't currently have two components that define a structure of the same name. Consumers can only use the pre-built binary artifacts if they are using the same version of Xcode as was used during the application-services build. We are not able to use Swift's BUILD_LIBRARY_FOR_DISTRIBUTION flag to overcome this, because some of our dependencies do not support this flag (specifically, the Swift protobuf lib). Found a bug? Edit this page on GitHub.","breadcrumbs":"Architectural Decision Records » ADR-0003 » Problems with the current setup","id":"175","title":"Problems with the current setup"},"176":{"body":"Status: rejected Deciders: teshaq, travis, k88hudson, jhugman, jaredlockhart Date: 2021-08-16 Technical Story: https://mozilla-hub.atlassian.net/browse/SDK-323","breadcrumbs":"Architectural Decision Records » ADR-0004 » Running experiments on first run early startup","id":"176","title":"Running experiments on first run early startup"},"177":{"body":"As an experimenter, I would like to run experiments early on a user's first run of the application. However, the experiment data is only available on the second run. We would like to have that experiment data available before the user's first run. For more information: https://docs.google.com/document/d/1Qw36_7G6XyHvJZdM-Hxh4nqYZyCsYajG0L5mO33Yd5M/edit","breadcrumbs":"Architectural Decision Records » ADR-0004 » Context and Problem Statement","id":"177","title":"Context and Problem Statement"},"178":{"body":"Availability of experiments early on the first run No impact on experimentation data analysis Flexibility in creating experiments Ability to quickly disable experiments Simplicity of releases Mobile's expectations of Nimbus (The SDK should be idempotent)","breadcrumbs":"Architectural Decision Records » ADR-0004 » Decision Drivers","id":"178","title":"Decision Drivers"},"179":{"body":"(A) Do Nothing Keep everything the way it is, preventing us from experimenting on users early on their first run (B) Bundle Experiment data with app on release On release, have an initial_experiments.json that defines the experiments that will be applied early on the first run Later on the first run, the client would retrieve the actual experiment data from remote-settings and overwrite the bundled data (C) Retrieve Experiment data on first run, and deal with delay We can retrieve the experiment data on the first run, experiment data however will not be available until after a short delay (network I/O + some disk I/O)","breadcrumbs":"Architectural Decision Records » ADR-0004 » Considered Options","id":"179","title":"Considered Options"},"18":{"body":"Some consumers (notably Fenix ) have support for automatically publishing and including a local development version of application-services in their build. The workflow is: Check out the firefox-android mono-repo. Edit (or create) the file fenix/local.properties and tell it where to find your local checkout of application-services, by adding a line like: autoPublish.application-services.dir=relative/path/to/your/checkout/of/application-services Note that the path should be relative from local.properties. For example, if application-services and firefox-android are at the same level, the relative path would be ../../application-services Do the same for android-components/local.properties - so yes, your local checkout of firefox-android requires 2 copies of local.properties, both with identical values for autoPublish.application-services.dir (and probably identical in every other way too) Build the consuming project following its usual build procedure, e.g. via ./gradlew assembleDebug or ./gradlew test. If all goes well, this should automatically build your checkout of application-services, publish it to a local maven repository, and configure the consuming project to install it from there instead of from our published releases.","breadcrumbs":"Contributing » Building » How to use the local development autopublish flow for Fenix » Using the auto-publishing workflow","id":"18","title":"Using the auto-publishing workflow"},"180":{"body":"None of the options were feasible, so for now we are sticking with option (A) Do Nothing until there are experiments planned that are expected to run on early startup on the first run, then we will revaluate our options. The (B) Bundle Experiment data with app on release option was rejected mainly due to difficulty in disabling experiments and pausing enrollments. This can create a negative user experience as it prevents us from disabling any problematic experiments. Additionally, it ties experiment creation with application release cycles. The (C) Retrieve Experiment data on first run, and deal with delay option was rejected due to the fact it changes the Nimbus SDK will no longer be idempotent,and the possibility of introducing undesirable UI.","breadcrumbs":"Architectural Decision Records » ADR-0004 » Decision Outcome","id":"180","title":"Decision Outcome"},"181":{"body":"","breadcrumbs":"Architectural Decision Records » ADR-0004 » Pros and Cons of the Options","id":"181","title":"Pros and Cons of the Options"},"182":{"body":"Good, because it keeps the flexibility in experiment creation Good, because disabling experiments can still done remotely for all experiments Good, because it keeps the Nimbus SDK idempotent. Bad, because it doesn't address the main problem of exposing experiments to user on their first run","breadcrumbs":"Architectural Decision Records » ADR-0004 » Do nothing","id":"182","title":"Do nothing"},"183":{"body":"Good, because it allows us to run experiments early on a user's first run Good, because it prevents us from having to wait for experiments, especially if a user has a slow network connection Bad, because it ties experiment creation with release cycles Bad, because it prevents us from disabling problematic first-run experiments without a dot release Bad, because it prevents us from pausing enrollment on first-run experiments without a dot release Bad, because it requires investment from the console team, and can modify existing flows.","breadcrumbs":"Architectural Decision Records » ADR-0004 » Bundle Experiment data with app on release","id":"183","title":"Bundle Experiment data with app on release"},"184":{"body":"Good, because it enables us to retrieve experiments for users on their first run Good, because it keeps the flexibility in experiment creation Good, because disabling experiments can still done remotely for all experiments Bad, because experiments may not be ready early on the user's experience Bad, because it forces the customer application to deal with either the delay, or changing the configuration shortly after startup. e.g. a loading spinner or a pre-onboarding screen not under experimental control; delaying initialization of onboarding screens until after experiments have been loaded. Bad, because it changes the programming model from Nimbus being an idempotent configuration store to configuration changing non-deterministically. Bad, because the experimentation platform could force the app to add unchangeable user interface for the entire population. This itself may have an effect on key metrics.","breadcrumbs":"Architectural Decision Records » ADR-0004 » Retrieve Experiment data on first run, and deal with delay","id":"184","title":"Retrieve Experiment data on first run, and deal with delay"},"185":{"body":"RFC for bundling into iOS and Fenix Document presented to product managers about (C) Retrieve Experiment data on first run, and deal with delay : https://docs.google.com/document/d/1X1hC3t5zC7-Rp0OPIoiUr_ueLOAI0ez_jqslaNzOHjY/edit Demo presenting option (C) Retrieve Experiment data on first run, and deal with delay : https://drive.google.com/file/d/19HwnlwrabmSNsB7tjW2l4kZD3PWABi4u/view?usp=sharing Found a bug? Edit this page on GitHub.","breadcrumbs":"Architectural Decision Records » ADR-0004 » Links","id":"185","title":"Links"},"186":{"body":"Status: proposed Discussion: https://github.com/mozilla/application-services/pull/5302 Deciders: csadilek for the mobile teams ✔️ leplatrem for the remote-settings team ✔️ mhammond for the application-services team ✔️ Date: 2022-12-16","breadcrumbs":"Architectural Decision Records » ADR-0005 » A remote-settings client for our mobile browsers.","id":"186","title":"A remote-settings client for our mobile browsers."},"187":{"body":"Mozilla’s mobile browsers have a requirement to access the remote settings service, but currently lack any libraries or tools which are suitable without some work. A concrete use case is the management of search engine configurations, which are stored in Remote Settings for Firefox Desktop, but shipped as individual files on our mobile browsers, requiring application releases for all changes. A constraint on any proposed solutions is that this work will be performed by Mozilla's mobile team, who have limited experience with Rust, and that it is required to be completed in Q1 2023. This document identifies the requirements, then identifies tools which already exist and are close to being suitable, then identifies all available options we can take, and outlines our decision.","breadcrumbs":"Architectural Decision Records » ADR-0005 » Context and Problem Statement","id":"187","title":"Context and Problem Statement"},"188":{"body":"The requirements are for a library which is able to access Mozilla’s Remote Settings service and return the results to our mobile browsers. This list of requirements is not exhaustive, but instead focuses on the requirements which will drive our decision making process. As such, it identifies the non-requirements first.","breadcrumbs":"Architectural Decision Records » ADR-0005 » Requirements","id":"188","title":"Requirements"},"189":{"body":"The following items all may have some degree of desirability, but they are not hard requirements for the initial version While the https connection to the server must be validated, there is no requirement to verify the content received by the server - ie, there’s no requirement to check the signature of the body itself. There’s no requirement to validate the result of the server conforms to a pre-defined schema - we trust the server data. There’s no requirement to return strongly-typed data to the applications - returning a JSON string/object is suitable. There’s no requirement to cache server responses to the file-system - if the app requests content, it’s fine for the library to always hit the server. There’s no requirement for any kind of scheduling or awareness of network state - when we are requested for content, we do it immediately and return an appropriate error if it can not be fetched. There’s no requirement to support publishing records, requesting reviews or providing approvals via this new library. There’s no requirement that push be used to communicate changes to the application (eg, to enable rapid-enrolment type features) There’s no requirement to manage buckets, groups and collections via this new library.","breadcrumbs":"Architectural Decision Records » ADR-0005 » Non-requirements","id":"189","title":"Non-requirements"},"19":{"body":"If you are using Windows, there's a good chance you do most application-services work in WSL, but want to run Android Studio on native Windows. In that scenario you must: From the app-services root, in WSL, execute ./automation/publish_to_maven_local_if_modified.py In native Windows, just work as normal - that build process knows to not even attempt to execute the above command automatically.","breadcrumbs":"Contributing » Building » How to use the local development autopublish flow for Fenix » Using Windows/WSL","id":"19","title":"Using Windows/WSL"},"190":{"body":"The requirements we do have for the initial version are: The library should allow fetching records from Mozilla’s Remote Settings servers. This includes support for attachments, and fetching incremental changes . The library should not create threads or run any event loops - the mobile apps themselves are responsible for all threading requirements. While this might change in the future, considering this kind of change to our mobile applications is out of scope for this project. We must use Necko for all networking on Android, must enforce all connections are via valid https hosts (although some test-only exceptions might be helpful for QA, such as allowing localhost connections to be http) The library should be the only remote-settings library used in the browser. Specifically, this means that Nimbus must also be capable of using the library, and the work to move Nimbus to the library must be considered as part of the project.","breadcrumbs":"Architectural Decision Records » ADR-0005 » Initial Requirements","id":"190","title":"Initial Requirements"},"191":{"body":"We have identified the following libraries which may be suitable for this project.","breadcrumbs":"Architectural Decision Records » ADR-0005 » Existing Libraries","id":"191","title":"Existing Libraries"},"192":{"body":"There is a version of the remote settings client in desktop , written in Javascript. It has been used and been relatively stable since at least 2018, so can be considered very capable, but the roadblock to it being suitable for use by our mobile browsers is that it is written in Javascript, so while it might be possible to expose it to Android via geckoview, there’s no reasonable path to have it made available to iOS.","breadcrumbs":"Architectural Decision Records » ADR-0005 » Remote-settings on desktop","id":"192","title":"Remote-settings on desktop"},"193":{"body":"There is an existing remote settings client on github . This client is written in Rust and has evolved over a number of years. The most recent changes were made to support being used in Merino , which was re-written in Python, so there are no known consumers of this library left. The main attributes of this library relevant to this discussion are: It’s written in Rust, but has no FFI - ie, it’s currently only consumable by other Rust code. It has recently been updated to use async rust, so requires an internal event loop. It includes the capability to verify the signatures of the content.","breadcrumbs":"Architectural Decision Records » ADR-0005 » Rust Remote Settings Client","id":"193","title":"Rust Remote Settings Client"},"194":{"body":"The nimbus-sdk is a component in the application-services repository written in Rust. It has client code which talks to the remote-settings server and while this has only actually been used with the \"Nimbus\" collection there's no reason to believe it can't be used in the more general case. The main attributes of this library relevant to this discussion are: It’s consumed by a component which is already consumed by our mobile browsers via UniFFI. It does not verify the signatures of the content - while this could be done, there hasn’t been sufficient justification made for this (ie, there are no realistic threat models which would be solved by this capability.) The client itself does not persist a local cache of remote resources, but instead delegates this responsibility to the consuming application (in this case, nimbus itself, which does persist them via the rkv library ) It does not use async Rust, but instead everything is blocking and run on threads exclusively created by the app itself. It has good test support, which run against a docker image.","breadcrumbs":"Architectural Decision Records » ADR-0005 » The Nimbus-sdk Client","id":"194","title":"The Nimbus-sdk Client"},"195":{"body":"","breadcrumbs":"Architectural Decision Records » ADR-0005 » Considered Options","id":"195","title":"Considered Options"},"196":{"body":"The requirements of this client are such that writing new libraries in Kotlin and Swift is currently a realistic option. However, we are rejecting this option because we don’t want to duplicate the effort required to write and maintain two libraries - inevitably, the features and capabilities will diverge. Future requirements such as supporting content signature verification would lead to significant duplication. Writing a new library from scratch in Rust and exposing it via UniFFI so it can be used by both platforms is also a possibility. However, we are rejecting this option because existing Rust libraries already exist, so we would be better served by modifying or forking one of the existing libraries.","breadcrumbs":"Architectural Decision Records » ADR-0005 » Option 1: Writing a new library","id":"196","title":"Option 1: Writing a new library"},"197":{"body":"Modifying or forking the existing client is an attractive option. It would require a number of changes - the async capabilities would probably need to be removed (using a Rust event loop in our mobile browsers is something we are trying to avoid until we better understand the implications given these browsers already have an event loop and their own threading model). The persistence model used by this library is something that is not a requirement for the new library, which isn’t itself a problem, but it probably would preclude being able to use this library by Nimbus - so the end result is that we would effectively have two remote-settings clients written in Rust and used by our browsers. Some API changes would probably be required to make it suitable for use by UniFFI would also be necessary, but these would be tractable. We would need to update nimbus to use this client, which would almost certainly require moving this client into the application-services repository to avoid the following issues: Marrying the persistence model of this client with the existing rkv-based persistence used by nimbus would be required. Ensuring the upstream version changes continued to work for us. Managing the circular dependency which exists due to this library needing to use viaduct. Complication of our build process because the library needs to end up in our “megazord”. These are the exact reasons why Nimbus itself is in the application-services repo.","breadcrumbs":"Architectural Decision Records » ADR-0005 » Option 2: Use the existing remote settings client","id":"197","title":"Option 2: Use the existing remote settings client"},"198":{"body":"Splitting the existing client out from Nimbus in a way that allows Nimbus to continue to use it, while also making it available for stand-alone use is also an attractive option. In particular, the feature set of that client overlaps with the requirements of the new library - no local persistence is necessary and no signature verification is required. It is already used by a component which is exposed via UniFFI. Note that this option does not preclude both Nimbus and this new crate from moving to the existing remote settings client at some point in the future. A key benefit of this decision is that it keeps nimbus and the new crate using the same client, so updating both to use a different client in the future will always remain an option.","breadcrumbs":"Architectural Decision Records » ADR-0005 » Option 3: Use the existing nimbus client","id":"198","title":"Option 3: Use the existing nimbus client"},"199":{"body":"We have chosen Option 3 because it allows us to reuse the new client in Nimbus, as well as on iOS and on Android with minimal initial development effort. If the new library ends up growing requirements that are already in the existing remote settings client, we remain able to copy that functionality from that library into this.","breadcrumbs":"Architectural Decision Records » ADR-0005 » Chosen Option","id":"199","title":"Chosen Option"},"2":{"body":"The Application Services Source Code is subject to the terms of the Mozilla Public License v2.0. You can obtain a copy of the MPL at https://mozilla.org/MPL/2.0/ .","breadcrumbs":"Application Services Rust Components » License","id":"2","title":"License"},"20":{"body":"Note: This is a bit tedious, and you should first try the auto-publishing workflow described above. But if the auto-publishing workflow fails then it's important to know how to do the publishing process manually. Since most consuming apps get their copy of application-services via a dependency on android-components, this procedure involves three separate repos: Inside the application-services repository root: In version.txt , change the version to end in -TESTING$N 1, where $N is some number that you haven't used for this before. Example: 0.27.0-TESTING3 Run ./gradlew publishToMavenLocal. This may take between 5 and 10 minutes. Inside the consuming project repository root (eg, firefox-android/fenix): Inside build.gradle , add mavenLocal() inside allprojects { repositories { } }. Ensure that local.properties does not contain any configuration to related to auto-publishing the application-services repo. Inside buildSrc/src/main/java/AndroidComponents.kt , change the version numbers for android-components to match the new versions you defined above. Example: const val VERSION = \"0.51.0-TESTING3\" You should now be able to build and run the consuming application (assuming you could do so before all this).","breadcrumbs":"Contributing » Building » How to use the local development autopublish flow for Fenix » Using a manual workflow","id":"20","title":"Using a manual workflow"},"200":{"body":"This section is non-normative - ie, is not strictly part of the ADR, but exists for context. This is a very high-level view of the tasks required here. Create a new top-level component in the application-services repository, identify the exact API we wish to expose for this new library, describe this API using UniFFI, then implement the API with “stubs” (eg, using rust todo!()or similar). This is depicted as RemoteSettings in the diagram. Identify which parts of Nimbus should be factored out into a shared component (depicted as rs-client in the diagram below) and move that functionality to the new shared component. Of note: This component probably will not have a UniFFI .udl file, but is just for consumption by the new component above and the existing nimbus component. There is still some uncertaintly here - if it is a requirement that nimbus and the new component share some configuration or initialization code, we might need to do something more complex here. This seems unlikely, but possible, so is included here for completeness. Identify which of the nimbus tests should move to the new client and move them. Update Nimbus to take a dependency on the new component and use it, including tests. Flesh out the API of the new top-level component using the new shared component (ie, replace the todo!() macros with real code.) Identify any impact on the Nimbus android/swift code - in particular, any shared configuration and initialization code identified above in the application-services repo. Implement the Android and iOS code in the application-services repo desired to make this an ergonomic library for the mobile platforms. Update the mobile code for the UniFFI changes made to Nimbus, if any. Implement the mobile code which consumes the new library, including tests. Profit? This diagram attempts to depict this final layout. Note: rs-client and RemoteSettings are both new components, everything else already exists. Please do not consider these names as suggestions! Names are hard, I'm sure we can do better. Dashed lines are normal Rust dependencies (ie, dependencies listed in Cargo.toml) Solid lines are where the component uses UniFFI Viaduct is a little odd in that it is consumed by the mobile applications indirectly (eg, via Glean), hence it's not in support, but please ignore that anomaly. flowchart RL subgraph app-services-support[Shared components in application-services/components/support] rs-client other-support-components end subgraph app-services-components[Top-level application-services components, in application-services/components] Nimbus RemoteSettings Viaduct end subgraph mobile [Code in the mobile repositories] Fenix Firefox-iOS end Nimbus -- nimbus.udl --> mobile RemoteSettings -- remote_settings.udl --> mobile rs-client -.-> Nimbus other-support-components -.-> Nimbus rs-client -.-> RemoteSettings other-support-components -.-> RemoteSettings Viaduct -.-> rs-client other-support-components -.-> rs-client","breadcrumbs":"Architectural Decision Records » ADR-0005 » Specific Plans","id":"200","title":"Specific Plans"},"201":{"body":"This section is non-normative - ie, is not strictly part of the ADR, but exists for context. Content Signatures have been explicitly called out as a non-requirement. Because this capability was a sticking point in the desktop version of the remote settings client, and because significant effort was spent on it, it's worth expanding on this here. Because https will be enforced for all network requests, the consumers of this library can have a high degree of confidence that: The servers hit by this client are the servers we expect to hit (ie, no man-in-the-middle attacks will be able to redirect to a different server). The response from the server is exactly what was sent by the Mozilla controlled server (ie, no man-in-the-middle attacks will be able to change the content in-flight) Therefore, the content received must be exactly as sent by the Mozilla controlled servers. Content signatures offer an additional capability of checking the content of a remote settings response matches the signature generated with a secret key owned by Mozilla, independenty of the https certificates used for the request itself. This capability was added to the desktop version primarily to protect the integrity of the data at rest. Because the Desktop client cached the responses on disk, there was a risk that this data could be tampered with - so it was effectively impossible to guarantee that the data finally presented to the application is what was initially sent. The main threat-model that required this capability was 3rd party applications installed on the same system where Firefox was installed. Because of the security model enforced by Desktop operating systems (most notably Windows), there was evidence that these 3rd-party applications would locate and modify the cache of remote-settings responses and modify them in a way that benefited them and caused revenue harm to Mozilla - the most obvious example is changing the search provider settings. The reason we are declaring this capability a non-requirement in the initial version is two-fold: We have also declared caching of responses a non-requirement, meaning there's no data at rest managed by this library which is vulnerable to this kind of attack. The mobile operating systems have far stronger application isolation - in the general case, a 3rd party mobile application is prevented from touching any of the files used by other applications. Obviously though, things may change in the future - for example, we might add response caching, so we must be sure to reevaluate this requirement as other requirements change. Found a bug? Edit this page on GitHub.","breadcrumbs":"Architectural Decision Records » ADR-0005 » Content Signatures","id":"201","title":"Content Signatures"},"202":{"body":"Status: accepted Deciders: teshaq, mhammond, lougeniaC64, dnarcese Date: 2023-01-06","breadcrumbs":"Architectural Decision Records » ADR-0007 » Limit Visits Migrated to Places History in Firefox iOS","id":"202","title":"Limit Visits Migrated to Places History in Firefox iOS"},"203":{"body":"The Application-Services team removed a legacy implementation of history in Firefox-ios and replaced it with a maintained implementation that was powering Firefox Android. A significant part of the project is migrating users’ history from the old database to a new one. To measure risk, we ran a dry-run migration. A dry-run migration runs a background thread in the user’s application and attempts to migrate to a fake database. The dry-run was implemented purely to collect telemetry on the migration to evaluate risk. The results can be found in the following Looker dashboard . Below is a list of observations.","breadcrumbs":"Architectural Decision Records » ADR-0007 » Context and Problem Statement","id":"203","title":"Context and Problem Statement"},"204":{"body":"The following is a list of observations from the experiment: 5-6% of migrations do not end. This means for 5-6% of users, the application was terminated before migration ended. For a real migration, this would mean those users lose all of their history unless we attempt the migration multiple times. Out of the migrations that failed (the 5-6% mentioned above) 97% of those users had over 10,000 history visits. Out of migrations that do end, over 99% of migrations are successful. This means that we are not experiencing many errors with the migration beyond the time it takes. The average for visits migrated is around 25,000 - 45,000 visits. The median for visits migrated is around 5,000-15,000 visits. The difference between the average and the median suggests that we have many users with a large number of visits For migrations that did end, the following are the percentiles for how long it took (in milliseconds). We would like to emphasize that the following only includes migrations that did end 10th percentile: 37 ms 25th percentile: 80 ms 50th percentile: 400 ms 75th percentile: 2,500 ms (2.5 seconds) 90th percentile: 6,400 ms (6.4 seconds) 95th percentile: 11,000 ms (11 seconds) 99th percentile: 25,000 ms (25 seconds) 99.9th percentile: 50,000 ms (50 seconds)","breadcrumbs":"Architectural Decision Records » ADR-0007 » Observations from Dry-Run Experiment","id":"204","title":"Observations from Dry-Run Experiment"},"205":{"body":"Given the observations from the dry-run experiment, the rest of the document examines an approach to answer the question: How can we increase the rate of which migrations end, and simultaneously keep the user’s database size at a reasonable size? The user implication of keeping the rate of ended migrations high is that users keep their history, and can interact normally with the URL bar to search history, searching history in the history panel and navigating to sites they visited in the past. The user implication of keeping a reasonable database size is that the database is less likely to lock on long queries. Meaning we reduce performance issues when users use the search bar, the history panel and when navigating to sites. Finally, it’s important to note that power users and daily active users will be more likely to have large histories and thus: Power users are more likely to fail their migration. Power users are more likely to have performance issues with history and the search bar. We saw a version of this with Favicons in an earlier incident, where users were coming across a significant number of database locks, crashing the app. This isn’t to say that the incident is directly related to this, however, large histories could contribute to the state we saw in the incident as it would take longer to run the queries.","breadcrumbs":"Architectural Decision Records » ADR-0007 » Problem Statement","id":"205","title":"Problem Statement"},"206":{"body":"We must not lose users’ recent history. What is considered “recent” is not concretely defined. There is prior art, however: Firefox Sync in Android syncs 5000 visits. Firefox Desktop and Android impose limits on the size of the database, 75 MiB in Android When importing history from chrome to Firefox, we only migrate the latest 2000 visits Chrome only keeps a user’s history for 90 days User’s experience with History must not regress, and ideally should improve. User experience is tightly coupled with the size of the database. The larger the database, the longer queries take. The longer queries take, the longer it would take for a user to observe their searched history and the history panel.","breadcrumbs":"Architectural Decision Records » ADR-0007 » Decision Drivers","id":"206","title":"Decision Drivers"},"207":{"body":"Keep the migration as-is. This option means that we have no limit. We will attempt to migrate all history for our users. Introduce a date-based limit on visits for the migration This option means that we only migrate visits that occurred in the past X days/weeks/months etc Introduce a visit number-based limit for the migration This option means we only migrate the latest X visits","breadcrumbs":"Architectural Decision Records » ADR-0007 » Considered Options","id":"207","title":"Considered Options"},"208":{"body":"Chosen option: Introduce a visit number-based limit for the migration . This option was chosen because given our decision drivers: We must not lose users’ recent history: We have established in the results of the dry-run, that the majority of failed migrations were for users with a large number of visits. By setting a reasonable limit, we can increase the likelihood the migration succeeds. We can set the limit to encompass “recent history” while choosing a limit that has an over 99% success rate. User’s experience with History must not regress, and ideally should improve. We have established in our decision driver that the user’s experience with history is coupled with the database size. By setting a reasonable limit, we can keep the size of the history database controlled. It's also worth noting that with the switch to the new implementation of history, we are also introducing a target size for the database. This means that we have maintenance logic that would compact the database and prune it if it grows beyond the target size.","breadcrumbs":"Architectural Decision Records » ADR-0007 » Decision Outcome","id":"208","title":"Decision Outcome"},"209":{"body":"The migration runs in a shorter time. This means a higher chance of the migration succeeding, thus keeping the user’s recent history without loss. Users who have less than the selected limit, will still get all their history. More on this in the Suggested Limit section. We keep the size of the history database low. This way users with more than the limit, will only keep their recent history. Additionally, when we delete users’ history from the old database, the size of the data the app keeps will decrease dramatically. Keeping the database size low means we lower the chance a user has performance issues with the database.","breadcrumbs":"Architectural Decision Records » ADR-0007 » Positive Consequences","id":"209","title":"Positive Consequences"},"21":{"body":"This assumes you have followed the build instructions for Fenix Make sure you're fully up to date in all repos, unless you know you need to not be. This omits the steps if changes needed because, e.g. application-services made a breaking change to an API used in android-components. These should be understandable to fix, you usually should be able to find a PR with the fixes somewhere in the android-component's list of pending PRs (or, failing that, a description of what to do in the application-services changelog). Contact us if you get stuck.","breadcrumbs":"Contributing » Building » How to use the local development autopublish flow for Fenix » Caveats","id":"21","title":"Caveats"},"210":{"body":"The biggest negative consequence is that Users with more visits than the limit, will lose visits . Since we would only keep the latest X visits for a user, if a user has Y visits, they would lose all of the Y-X visits (assuming Y is greater than X) The effect here is mitigated by the observation that recent history is more important to users than older history. Unfortunately, we do not have any telemetry to demonstrate this claim, but it’s an assumption based on the existing limits on history imposed in Android and Desktop mentioned in the decision drivers section.","breadcrumbs":"Architectural Decision Records » ADR-0007 » Negative Consequences","id":"210","title":"Negative Consequences"},"211":{"body":"","breadcrumbs":"Architectural Decision Records » ADR-0007 » Pros and Cons of the Other Options","id":"211","title":"Pros and Cons of the Other Options"},"212":{"body":"Good because if the migration succeeds, users keep all their history. Bad, because it’s less likely for migrations to succeed. Bad, because even if the migration succeeds it causes the size of the database to be large if a user has a lot of history. Large databases can cause a regression in performance. Users with a lot of history will now have two large databases (the old and new ones) since we won’t delete the data in the old database right away to support downgrades. Bad, because it can take a long time for the migration to finish. Bad because until the migration is over users will experience the app without history.","breadcrumbs":"Architectural Decision Records » ADR-0007 » Keep the migration as-is","id":"212","title":"Keep the migration as-is"},"213":{"body":"Good, because we match users’ usage of the app. Users that use the app more, will keep more of their history. Good, because it’s more likely that the migration ends because we set a limit Bad because it’s hard to predict how large user’s databases will be. This is particularly important for Sync users. As Firefox-iOS syncs all your history, meaning if a user has many visits before the limit across multiple platforms, a large number of visits will be migrated. Bad, because users who haven’t used the app since the limit, will lose all their history For example, if the limit is 3 months, a user who last used the app 3 months ago will suddenly lose all their history","breadcrumbs":"Architectural Decision Records » ADR-0007 » Introduce a date-based limit on visits","id":"213","title":"Introduce a date-based limit on visits"},"214":{"body":"This section describes a suggested limit for the visits. Although it’s backed up with telemetry, the specific number is up for discussion. It’s also important to note that statistical significance was not a part of the analysis. Migration has run for over 16,000 users and although that may not be a statistically significant representation of our population, it’s good enough input to make an educated suggestion. First, we start by observing the distribution of visit counts. This will tell us how many of our users have between 0-10000 visits, 10000-20000, etc. We will identify that most of our users have less than 10,000 visits. Then, we will observe the dry-run migration ended rate based on the above buckets. We will observe that users with under 10,000 visits have a high chance of migration success. Finally, based on the analysis and prior art we’ll suggest 10,000 visits.","breadcrumbs":"Architectural Decision Records » ADR-0007 » Suggested Limit","id":"214","title":"Suggested Limit"},"215":{"body":"We will look at https://mozilla.cloud.looker.com/looks/1078 which demonstrates a distribution of our users based on the number of history visits. Note that the chart is based on our release population. Observations 67% of firefox-ios users have less than 10,000 visits There is a very long tail to the distribution, with 6% of users having over 100,000 visits.","breadcrumbs":"Architectural Decision Records » ADR-0007 » User History Distribution","id":"215","title":"User History Distribution"},"216":{"body":"We will look at https://mozilla.cloud.looker.com/looks/1081 . The chart demonstrates the rate at which migrations end by the number of visits. We bucket users in buckets of 10,000 visits. Observations We observe that for users that have visits under 10,000, the success rate is over 99.6%. We observe that for users with over 100,000 visits, the success rate drops to 75~80%. Users in between, have success rates in between. For example, users with visits between 10,000 and 20,000 have a 98-99% success rate. All success rates for buckets beyond 20,000 visits drop under 96%.","breadcrumbs":"Architectural Decision Records » ADR-0007 » Dry-run Ending Rate by Visits","id":"216","title":"Dry-run Ending Rate by Visits"},"217":{"body":"Based on the above, we’re suggesting a limit of 10,000 visits because 10,000 visits encompass the full history of 67% of our users. Migrations with under 10,000 visits have a success rate of over 99%. For users with over 10,000 visits, they still keep the latest 10,000 visits. The choice is reasonable considering: Sync only retrieves the latest 5000 visits in Android Migrating from Chrome to Firefox only migrates 2000 visits in Desktop","breadcrumbs":"Architectural Decision Records » ADR-0007 » Suggestion","id":"217","title":"Suggestion"},"218":{"body":"Epic for moving iOS’s history implementation to application-services places Dry-run migration experiment Overall dry-run migration looker dashboard Firefox iOS User distribution by history Migration Ended rate by User History Firefox Sync on Android only Syncs 5000 sites Firefox Desktop Limits import from Chrome to 2000 visits Firefox Android limits the size of its places.db to 75MiB Chrome only keeps 90 days of history Performance incident in Firefox iOS Found a bug? Edit this page on GitHub.","breadcrumbs":"Architectural Decision Records » ADR-0007 » Links","id":"218","title":"Links"},"219":{"body":"Megazords - Megazords and how we ship code Sync Manager - Our Sync Manager and how Sync works in using it Shipping Rust Components as Swift Packages - High level design of how use the Swift Package Manager to distribute our Rust components to iOS Sync overview - High level overview of how Firefox sync works Rust Component's Strategy - High level description of our Rust components strategy Metrics - (Glean Telemetry) Rust Version Policy","breadcrumbs":"Design » Design Documents","id":"219","title":"Design Documents"},"22":{"body":"If you had to use the manual workflow above and found it incredibly tedious, you might like to try adding support for the auto-publish workflow to the consuming project! The details will differ depending on the specifics of the project's build setup, but at a high level you will need to: In your settings.gradle , locate (or add) the code for parsing the local.properties file, and add support for loading a directory path from the property autoPublish.application-services.dir. If this property is present, spawn a subprocess to run ./gradlew autoPublishForLocalDevelopment in the specified directory. This automates step (1) of the manual workflow above, publishing your changes to application-services into a local maven repository under a unique version number. In your build.gradle , if the autoPublish.application-services.dir property is present, have each project apply the build script from ./build-scripts/substitute-local-appservices.gradle in the specified directory. This automates steps (2) and (3) of the manual workflow above, using gradle's dependency substitution capabilities to override the verion requirements for application-services components. It may be necessary to experiment with the ordering of this relative to other build configuration steps, in order for the dependency substitution to work correctly. For a single-project build this would look something like: if (gradle.hasProperty('localProperties.autoPublish.application-services.dir')) { ext.appServicesSrcDir = gradle.\"localProperties.autoPublish.application-services.dir\" apply from: \"${appServicesSrcDir}/build-scripts/substitute-local-appservices.gradle\"\n} For a multi-project build it should be applied to all subprojects, like: subprojects { if (gradle.hasProperty('localProperties.autoPublish.application-services.dir')) { ext.appServicesSrcDir = gradle.\"localProperties.autoPublish.application-services.dir\" apply from: \"${rootProject.projectDir}/${appServicesSrcDir}/build-scripts/substitute-local-appservices.gradle\" }\n} Confirm that the setup is working, by adding autoPublish.application-services.dir to your local.properties file and running ./gradlew dependencies for the project. You should be able to see gradle checking the build status of the various application-services dependencies as part of its setup phase. When the command completes, it should print the resolved versions of all dependencies, and you should see that application-services components have a version number in the format 0.0.1-SNAPSHOT-{TIMESTAMP}. [1]: It doesn't have to start with -TESTING, it only needs to have the format -someidentifier. -SNAPSHOT$N is also very common to use, however without the numeric suffix, this has specific meaning to gradle, so we avoid it. Additionally, while the $N we have used in our running example has matched (e.g. all of the identifiers ended in -TESTING3, this is not required, so long as you match everything up correctly at the end. This can be tricky, so I always try to use the same number). Found a bug? Edit this page on GitHub.","breadcrumbs":"Contributing » Building » How to use the local development autopublish flow for Fenix » Adding support for the auto-publish workflow","id":"22","title":"Adding support for the auto-publish workflow"},"220":{"body":"Each Rust component published by Application Services is conceptually a stand-alone library, but for distribution we compile all the rust code for all components together into a single .so file. This has a number of advantages: Easy and direct interoperability between different components at the Rust level Cross-component optimization of generated code Reduced code size thanks to distributing a single copy of the rust stdlib, low-level dependencies, etc. This process is affectionately known as \"megazording\" and the resulting artifact as a megazord library . On Android, the situation is quite complex due to the way packages and dependencies are managed. We need to distribute each component as a separate Android ARchive (AAR) that can be managed as a dependency via gradle, we need to provide a way for the application to avoid shipping rust code for components that it isn't using, and we need to do it in a way that maintanins the advantages listed above. This document describes our current approach to meeting all those requirements on Android. Other platforms such as iOS are not considered.","breadcrumbs":"Design » Megazords » Megazording","id":"220","title":"Megazording"},"221":{"body":"We publish a separate AAR for each component (e.g. fxaclient, places, logins) which contains just the Kotlin wrappers that expose the relevant functionality to Android. Each of these AARs depends on a separate shared \"megazord\" AAR in which all the rust code has been compiled together into a single .so file. The application's dependency graph thus looks like this: megazord dependency diagram This generates a kind of strange inversion of dependencies in our build pipeline: Each individual component defines both a rust crate and an Android AAR. There is a special \"full-megazord\" component that also defines a rust crate and an Android AAR. The full-megazord rust crate depends on the rust crates for each individual component. But the Android AAR for each component depends on the Android AAR of the full-megazord! It's a little odd, but it has the benefit that we can use gradle's dependency-replacement features to easily manage the rust code that is shipping in each application.","breadcrumbs":"Design » Megazords » AAR Dependency Graph","id":"221","title":"AAR Dependency Graph"},"222":{"body":"By default, an application that uses any appservices component will include the compiled rust code for all appservices components. To reduce its overall code size, the application can use gradle's module replacement rules to replace the \"full-megazord\" AAR with a custom-built megazord AAR containing only the components it requires. Such an AAR can be built in the same way as the \"full-megazord\", and simply avoid depending on the rust crates for components that are not required. To help ensure this replacement is done safely at runtime, the mozilla.appservices.support.native package provides helper functions for loading the correct megazord .so file. The Kotlin wrapper for each component should load its shared library by calling mozilla.appservices.support.native.loadIndirect, specifying both the name of the component and the expected version number of the shared library.","breadcrumbs":"Design » Megazords » Custom Megazords","id":"222","title":"Custom Megazords"},"223":{"body":"The full-megazord AAR contains compiled rust code that targets various Android platforms, and is not suitable for running on a Desktop development machine. In order to support integration with unittest suites such as robolectric, each megazord has a corresponding Java ARchive (JAR) distribution named e.g. full-megazord-forUnitTests.jar. This contains the rust code compiled for various Desktop architectures, and consumers can add it to their classpath when running tests on a Desktop machine.","breadcrumbs":"Design » Megazords » Unit Tests","id":"223","title":"Unit Tests"},"224":{"body":"This setup mostly works, but has a handful of rough edges. The build.gradle for each component needs to declare an explicit dependency on project(\":full-megazord\"), otherwise the resulting AAR will not be able to locate the compiled rust code at runtime. It also needs to declare a dependency between its build task and that of the full-megazord, for reasons. Typically this looks something like: tasks[\"generate${productFlavor}${buildType}Assets\"].dependsOn(project(':full-megazord').tasks[\"cargoBuild\"]) In order for unit tests to work correctly, the build.gradle for each component needs to add the rustJniLibs directory of the full-megazord project to its srcDirs, otherwise the unittests will not be able to find and load the compiled rust code. Typically this looks something like: test.resources.srcDirs += \"${project(':full-megazord').buildDir}/rustJniLibs/desktop\" The above also means that unittests will not work correctly when doing local composite builds, because it's unreasonable to expect the main project (e.g. Fenix) to include the above in its build scripts. Found a bug? Edit this page on GitHub.","breadcrumbs":"Design » Megazords » Gotchas and Rough Edges","id":"224","title":"Gotchas and Rough Edges"},"225":{"body":"We've identified the need for a \"sync manager\" (although are yet to identify a good name for it!) This manager will be responsible for managing \"global sync state\" and coordinating each engine. At a very high level, the sync manager is responsible for all syncing. So far, so obvious. However, given our architecture, it's possible to identify a key architectural split. The embedding application will be responsible for most high-level operations. For example, the app itself will choose how often regular syncs should happen, what environmental concerns apply (eg, should I only sync on WiFi?), letting the user choose exactly what to sync, and so forth. A lower-level component will be responsible for the direct interaction with the engines and with the various servers needed to perform a sync. It will also have the ultimate responsibility to not cause harm to the service (for example, it will be likely to enforce some kind of rate limiting or ensuring that service requests for backoff are enforced) Because all application-services engines are written in Rust, it's tempting to suggest that this lower-level component also be written in Rust and everything \"just works\", but there are a couple of complications here: For iOS, we hope to integrate with older engines which aren't written in Rust, even if iOS does move to the new Sync Manager. For Desktop, we hope to start by reusing the existing \"sync manager\" implemented by Desktop, and start moving individual engines across. There may be some cross-crate issues even for the Rust implemented engines. Or more specifically, we'd like to avoid assuming any particular linkage or packaging of Rust implemented engines. Even with these complications, we expect there to be a number of high-level components, each written in a platform specific language (eg, Kotlin or Swift) and a single lower-level component to be implemented in Rust and delivered as part of the application-services library - but that's not a free-pass. Why \"a number of high-level components\"? Because that is the thing which understands the requirements of the embedding application. For example, Android may end up with a single high-level component in the android-components repo and shared between all Android components. Alternatively, the Android teams may decide the sync manager isn't generic enough to share, so each app will have their own. iOS will probably end up with its own and you could imagine a future where Desktop does too - but they should all be able to share the low level component.","breadcrumbs":"Design » Sync Manager » Sync manager","id":"225","title":"Sync manager"},"226":{"body":"The primary responsibilities of the \"high level\" portion of the sync manager are: Manage all FxA interaction. The low-level component will have a way to communicate auth related problems, but it is the high-level component which takes concrete FxA action. Expose all UI for the user to choose what to sync and coordinate this with the low-level component. Note that because these choices can be made on any connected device, these choices must be communicated in both directions. Implement timers or any other mechanism to fully implement the \"sync scheduler\", including any policy decisions such as only syncing on WiFi, etc. Implement a UI so the user can \"sync now\". Collect telemetry from the low-level component, probably augment it, then submit it to the telemetry pipeline. The primary responsibilities of the \"low level\" portion of the sync manager are: Manage the meta/global, crypto/keys and info/collections resources, and interact with each engine as necessary based on the content of these resources. Manage interaction with the token server. Enforce constraints necessary to ensure the entire ecosystem is not subject to undue load. For example, this component should ignore attempts to sync continuously, or to sync when the services have requested backoff. Manage the \"clients\" collection - we probably can't ignore this any longer, especially for bookmarks (as desktop will send a wipe command on bookmark restore, and things will \"be bad\" if we don't see that command). Define a minimal \"service state\" so certain things can be coordinated with the high-level component. Examples of these states are \"everything seems ok\", \"the service requested we backoff for some period\", \"an authentication error occurred\", and possibly others. Perform, or coordinate, the actual sync of the rust implemented engines - from the containing app's POV, there's a single \"sync now\" entry-point (in practice there might be a couple, but conceptually there's a single way to sync). Note that as below, how non-rust implemented engines are managed is TBD. Manage the collection of (but not the submission of) telemetry from the various engines. Expose APIs and otherwise coordinate with the high-level component. Stuff we aren't quite sure where it fits include: Coordination with non-rust implemented engines. These engines are almost certainly going to be implemented in the same language as the high-level component, which will make integration simpler. However, the low-level component will almost certainly need some information about these engines for populating info/collections etc. For now, we are punting on this until things become a bit clearer.","breadcrumbs":"Design » Sync Manager » The responsibilities of the Sync Manager.","id":"226","title":"The responsibilities of the Sync Manager."},"227":{"body":"The above has been carefully written to try and avoid implementation details - the intent is that it's an overview of the architecture without any specific implementation decisions. These next sections start getting specific, so implementation choices need to be made, and thus will possibly be more contentious. In other words, get your spray-cans ready because there's a bikeshed being built! However, let's start small and make some general observations.","breadcrumbs":"Design » Sync Manager » Implementation Details.","id":"227","title":"Implementation Details."},"228":{"body":"Some apps only care about a subset of the engines - lockbox is one such app and only cares about a single collection/engine. It might be the case that lockbox uses a generic application-services library with many engines available, even though it only wants logins. Thus, the embedding application is the only thing which knows which engines should be considered to \"exist\". It may be that the app layer passes an engine to the sync manager, or the sync manager knows via some magic how to obtain these handles. Some apps will use a combination of Rust components and \"legacy\" engines. For example, iOS is moving some of the engines to using Rust components, while other engines will be ported after delivery of the sync manager, if they are ported at all. We also plan to introduce some rust engines into desktop without integrating the \"sync manager\" The rust components themselves are designed to be consumed as individual components - the \"logins\" component doesn't know anything about the \"bookmarks\" component. There are a couple of gotchyas in the current implementations too - there's an issue when certain engines don't yet appear in meta/global - see bug 1479929 for all the details. The tl;dr of the above is that each rust component should be capable of working with different sync managers. That said though, let's not over-engineer this and pretend we can design a single, canonical thing that will not need changing as we consider desktop and iOS.","breadcrumbs":"Design » Sync Manager » Current implementations and challenges with the Rust components","id":"228","title":"Current implementations and challenges with the Rust components"},"229":{"body":"There's loads of state here. The app itself has some state. The high-level Sync Manager component will have state, the low-level component will have state, and each engine has state. Some of this state will need to be persisted (either on the device or on the storage servers) and some of this state can be considered ephemeral and lives only as long as the app. A key challenge will be defining this state in a coherent way with clear boundaries between them, in an effort to allow decoupling of the various bits so Desktop and iOS can fit into this world. This state management should also provide the correct degree of isolation for the various components. For example, each engine should only know about state which directly impacts how it works. For example, the keys used to encrypt a collection should only be exposed to that specific engine, and there's no need for one engine to know what info/collections returns for other engines, nor whether the device is currently connected to WiFi. A thorn here is for persisted state - it would be ideal if the low-level component could avoid needing to persist any state, so it can avoid any kind of storage abstraction. We have a couple of ways of managing this: The state which needs to be persisted is quite small, so we could delegate state storage to the high-level component in an opaque way, as this high-level component almost certainly already has storage requirements, such as storing the \"choose what to sync\" preferences. The low-level component could add its own storage abstraction. This would isolate the high-level component from this storage requirement, but would add complexity to the sync manager - for example, it would need to be passed a directory where it should create a file or database. We'll probably go with the former.","breadcrumbs":"Design » Sync Manager » State, state and more state. And then some state.","id":"229","title":"State, state and more state. And then some state."},"23":{"body":"This is a guide on testing the Swift Package Manager component locally against a local build of Firefox iOS. For more information on our Swift Package Manager design, read the ADR that introduced it This guide assumes the component you want to test is already distributed with the rust-components-swift repository, you can read the guide for adding a new component if you would like to distribute a new component. The goal for this document is to be able to build a local firefox iOS against a local application-services . On a high level, that requires the following: Build an xcframework in a local checkout of application-services Include the xcframework in a local checkout of rust-components-swift Run the generate script in rust-components-swift using a local checkout of application-services Include the local checkout of rust-components-swift in firefox-ios","breadcrumbs":"Contributing » Building » How to use the local development autopublish flow for Firefox iOS » How to locally test Swift Package Manager components on Firefox iOS","id":"23","title":"How to locally test Swift Package Manager components on Firefox iOS"},"230":{"body":"Let's try and move into actionable decisions for the implementation. We expect the implementation of the low-level component to happen first, followed very closely by the implementation of the high-level component for Android. So we focus first on these.","breadcrumbs":"Design » Sync Manager » Implementation plan for the low-level component.","id":"230","title":"Implementation plan for the low-level component."},"231":{"body":"The clients engine includes some meta-data about each client. We've decided we can't replace the clients engine with the FxA device record and we can't simply drop this engine entirely. Of particular interest is \"commands\" - these involve communicating with the engine regarding commands targetting it, and accepting commands to be send to other devices. Note that outgoing commands are likely to not originate from a sync, but instead from other actions, such as \"restore bookmarks\". However, because the only current requirement for commands is to wipe the store, and because you could anticipate \"wipe\" also being used when remotely disconnecting a device (eg, when a device is lost or stolen), our lives would probably be made much simpler by initially supporting only per-engine wipe commands. Note that there has been some discussion about not implementing the client engine and replacing \"commands\" with some other mechanism. However, we have decided to not do that because the implementation isn't considered too difficult, and because desktop will probably require a number of changes to remove it (eg, \"synced tabs\" will not work correctly without a client record with the same guid as the clients engine.) Note however that unlike desktop, we will use the FxA device ID as the client ID. Because FxA device IDs are more ephemeral than sync IDs, it will be necessary for engines using this ID to track the most-recent ID they synced with so the old record can be deleted when a change is detected.","breadcrumbs":"Design » Sync Manager » Clients Engine","id":"231","title":"Clients Engine"},"232":{"body":"For the purposes of the sync manager, we define: An engine is the unit exposed to the user - an \"engine\" can be enabled or disabled. There is a single set of canonical \"engines\" used across the entire sync ecosystem - ie, desktop and mobile devices all need to agree about what engines exist and what the identifier for an engine is. An Api is the unit exposed to the application layer for general application functionality. Application services has 'places' and 'logins' Apis and is the API used by the application to store and fetch items. Each 'Api' may have one or more 'stores' (although the application layer will generally not interact directly with a store) A store is the code which actually syncs. This is largely an implementation detail. There may be multiple stores per engine (eg, the \"history\" engine may have \"history\" and \"forms\" stores) and a single 'Api' may expose multiple stores (eg, the \"places Api\" will expose history and bookmarks stores) A collection is a unit of storage on a server. It's even more of an implementation detail than a store. For example, you might imagine a future where the \"history\" store uses multiple \"collections\" to help with containers. In practice, this means that the high-level component should only need to care about an engine (for exposing a choice of what to sync to the user) and an api (for interacting with the data managed by that api). The low-level component will manage the mapping of engines to stores.","breadcrumbs":"Design » Sync Manager » Collections vs engines vs stores vs preferences vs Apis","id":"232","title":"Collections vs engines vs stores vs preferences vs Apis"},"233":{"body":"This document isn't going to outline the history of how \"declined\" is used, nor talk about how this might change in the future. For the purposes of the sync manager, we have the following hard requirements: The low-level component needs to know what the currently declined set of engines is for the purposes of re-populating meta/global. The low-level component needs to know when the declined set needs to change based on user input (ie, when the user opts in to or out of a particular engine on this device) The high-level component needs to be aware that the set of declined engines may change on every sync (ie, when the user opts in to or out of a particular engine on another device) A complication is that due to networks being unreliable, there's an inherent conflict between \"what is the current state?\" and \"what state changes are requested?\". For example, if the user changes the state of an engine while there's no network, then exits the app, how do we ensure the user's new state is updated next time the app starts? What if the user has since made a different state request on a different device? Is the state as last-known on this device considered canonical? To clarify, consider: User on this device declines logins. This device now believes logins is disabled but history is enabled, but is unable to write this to the server due to no network. The user declines history on a different device, but doesn't change logins. This device does manage to write the new list to the server. This device restarts and the network is up. It believes history is enabled but logins is not - however, the state on the server is the exact opposite. How does this device react? (On the plus side, this is an extreme edge-case which none of our existing implementations handle \"correctly\" - which is easy to say, because there's no real definition for \"correctly\") Regardless, the low-level component will not pretend to hide this complexity (ie, it will ignore it!). The low-level component will allow the app to ask for state changes as part of a sync, and will return what's on the server at the end of every sync. The app is then free to build whatever experience it desires around this.","breadcrumbs":"Design » Sync Manager » The declined list","id":"233","title":"The declined list"},"234":{"body":"The low-level component needs to have the ability to disconnect all engines from Sync. Engines which are declined should also be reset. Because we will need wipe() functionality to implement the clients engine, and because Lockbox wants to wipe on disconnect, we will provide disconnect and wipe functionality.","breadcrumbs":"Design » Sync Manager » Disconnecting from Sync","id":"234","title":"Disconnecting from Sync"},"235":{"body":"Breaking the above down into actionable tasks which can be some somewhat concurrently, we will deliver:","breadcrumbs":"Design » Sync Manager » Specific deliverables for the low-level component.","id":"235","title":"Specific deliverables for the low-level component."},"236":{"body":"A straw-man for the API we will expose to the high-level components. This probably isn't too big, but we should do this as thoroughly as we can. In particular, ensure we have covered: Declined management - how the app changes the declined list and how it learns of changes from other devices. How telemetry gets handed from the low-level to the high-level. The \"state\" - in particular, how the high-level component understands the auth state is wrong, and whether the service is in a degraded mode (eg, server requested backoff) How the high-level component might specify \"special\" syncs, such as \"just one engine\" or \"this is a pre-sleep, quick-as-possible sync\", etc There's a straw-man proposal for this at the end of the document.","breadcrumbs":"Design » Sync Manager » The API","id":"236","title":"The API"},"237":{"body":"We should build a utility (or 2) which can stand in for the high-level component, for testing and demonstration purposes. This is something like places-utils.rs and the little utility Grisha has been using. This utility should act like a real client (ie, it should have an FxA device record, etc) and it should use the low-level component in exactly the same we we expect real products to use it. Because it is just a consumer of the low-level component, it will force us to confront some key issues, such as how to get references to engines stored in multiple crates, how to present a unified \"state\" for things like auth errors, etc.","breadcrumbs":"Design » Sync Manager » A command-line (and possibly Android) utility.","id":"237","title":"A command-line (and possibly Android) utility."},"238":{"body":"The initial work for the clients engine can probably be done without too much regard for how things are tied together - for example, much work could be done without caring how we get a reference to engines across crates.","breadcrumbs":"Design » Sync Manager » The \"clients\" engine","id":"238","title":"The \"clients\" engine"},"239":{"body":"Implementing things needed to we can expose the correct state to the high-level manager for things like auth errors, backoff semantics, etc","breadcrumbs":"Design » Sync Manager » State work","id":"239","title":"State work"},"24":{"body":"A local checkout of firefox-ios that is ready to build A local checkout of rust-components-swift A local checkout of application-services that is ready to build for iOS","breadcrumbs":"Contributing » Building » How to use the local development autopublish flow for Firefox iOS » Prerequisites:","id":"24","title":"Prerequisites:"},"240":{"body":"There will be lots of loose ends to clean up - things like telemetry, etc.","breadcrumbs":"Design » Sync Manager » Tie it together and other misc things.","id":"240","title":"Tie it together and other misc things."},"241":{"body":"We have identified that iOS will, at least in the short term, want the sync manager to be implemented in Swift. This will be responsible for syncing both the Swift and Rust implemented engines. At some point in the future, Desktop may do the same - we will have both Rust and JS implemented engines which need to be coordinated. We ignore this requirement for now. This approach still has a fairly easy time coordinating with the Rust implemented engines - the FFI will need to expose the relevant sync entry-points to be called by Swift, but the Swift code can hard-code the Rust engines it has and make explicit calls to these entry-points. This Swift code will need to create the structures identified below, but this shouldn't be too much of a burden as it already has the information necessary to do so (ie, it already has info/collections etc) TODO: dig into the Swift code and make sure this is sane.","breadcrumbs":"Design » Sync Manager » Followup with non-rust engines.","id":"241","title":"Followup with non-rust engines."},"242":{"body":"While we use rust struct definitions here, it's important to keep in mind that as mentioned above, we'll need to support the manager being written in something other than rust, and to support engines written in other than rust. The structures below are a straw-man, but hopefully capture all the information that needs to be passed around. // We want to define a list of \"engine IDs\" - ie, canonical strings which\n// refer to what the user perceives as an \"enigine\" - but as above, these\n// *do not* correspond 1:1 with either \"stores\" or \"collections\" (eg, \"history\"\n// refers to 2 stores, and in a future world, might involve 3 collections).\nenum Engine { History, // The \"History\" and \"Forms\" stores. Bookmarks, // The \"Bookmark\" store. Passwords,\n} impl Engine { fn as_str(&self) -> &'static str { match self { History => \"history\", // etc }\n} // A struct which reflects engine declined states.\nstruct EngineState { engine: Engine, enabled: bool,\n} // A straw-man for the reasons why a sync is happening.\nenum SyncReason { Scheduled, User, PreSleep, Startup,\n} // A straw man for the general status.\nenum ServiceStatus { Ok, // Some general network issue. NetworkError, // Some apparent issue with the servers. ServiceError, // Some external FxA action needs to be taken. AuthenticationError, // We declined to do anything for backoff or rate-limiting reasons. BackedOff, // Something else - you need to check the logs for more details. OtherError,\n} // Info we need from FxA to sync. This is roughly our Sync15StorageClientInit\n// structure with the FxA device ID.\nstruct AccountInfo { key_id: String, access_token: String, tokenserver_url: Url, device_id: String,\n} // Instead of massive param and result lists, we use structures.\n// This structure is passed to each and every sync.\nstruct SyncParams { // The engines to Sync. None means \"sync all\" engines: Option>, // Why this sync is being done. reason: SyncReason, // Any state changes which should be done as part of this sync. engine_state_changes: Vec, // An opaque state \"blob\". This should be persisted by the app so it is // reused next sync. persisted_state: Option,\n} struct SyncResult { // The general health. service_status: ServiceStatus, // The result for each engine. engine_results: HashMap>, // The list of declined engines, or None if we failed to get that far. declined_engines: Option>, // When we are allowed to sync again. If > now() then there's some kind // of back-off. Note that it's not strictly necessary for the app to // enforce this (ie, it can keep asking us to sync, but we might decline). // But we might not too - eg, we might try a user-initiated sync. next_sync_allowed_at: Timestamp, // New opaque state which should be persisted by the embedding app and supplied // the next time Sync is called. persisted_state: String, // Telemetry. Nailing this down is tbd. telemetry: Option,\n} struct SyncManager {} impl SyncManager { // Initialize the sync manager with the set of Engines known by this // application without regard to the enabled/declined states. // XXX - still TBD is how we will pass \"stores\" around - it may be that // this function ends up taking an `impl Store` fn init(&self, engines: Vec<&str>) -> Result<()>; fn sync(&self, params: SyncParams) -> Result; // Interrupt any current syncs. Note that this can be called from a different // thread. fn interrupt() -> Result<()>; // Disconnect this device from sync. This may \"reset\" the stores, but will // not wipe local data. fn disconnect(&self) -> Result<()>; // Wipe all local data for all local stores. This can be done after // disconnecting. // There's no exposed way to wipe the remote store - while it's possible // stores will want to do this, there's no need to expose this to the user. fn wipe(&self) -> Result<()>;\n} Found a bug? Edit this page on GitHub.","breadcrumbs":"Design » Sync Manager » Details","id":"242","title":"Details"},"243":{"body":"This document provides a high-level overview of how syncing works. Note : each component has its own quirks and will handle sync slightly differently than the general process described here.","breadcrumbs":"Design » Sync overview » Sync Overview","id":"243","title":"Sync Overview"},"244":{"body":"Crates involved : The sync15 and support/sync15-traits handle the general syncing logic and define the SyncEngine trait Individual component crates (logins, places, autofill, etc). These implement SyncEngine. sync_manager manages the overall syncing process. High level sync flow : Sync is initiated by the application that embeds application-services. The application calls SyncManager.sync() to start the sync process. SyncManager creates SyncEngine instances to sync the individual components. Each SyncEngine corresponds to a collection on the sync server.","breadcrumbs":"Design » Sync overview » General flow and architecture","id":"244","title":"General flow and architecture"},"245":{"body":"SyncManager is responsible for performing the high-level parts of the sync process: The consumer code calls it's sync() function to start the sync, passing in a SyncParams object in, which describes what should be synced. SyncManager performs all network operations on behalf of the individual engines. It's also responsible for tracking the general authentication state (primarily by inspecting the responses from these network requests) and fetching tokens from the token server. SyncManager checks if we are currently in a backoff period and should wait before contacting the server again. Before syncing any engines, the sync manager checks the state of the meta/global collection and compares it with the enabled engines specified in the SyncParams. This handles the cases when the user has requested an engine be enabled or disabled on this device, or when it was requested on a different device. (Note that engines enabled and disabled states are state on the account itself and not a per-device setting). Part of this process is comparing the collection's GUID on the server with the GUID known locally - if they are different, it implies some other device has \"reset\" the collection, so the engine drops all metadata and attempts to reconcile with every record on the server (ie, acts as though this is the very first sync this engine has ever done). SyncManager instantiates a SyncEngine for each enabled component. We currently use 2 different methods for this: The older method is for the SyncManager to hold a weakref to a Store use that to create the SyncEngine (tabs and places). The SyncEngine uses the Store for database access, see the TabsStore for an example. The newer method is for the components to provide a function to create the SyncEngine, hiding the details of how that engine gets created (autofill/logins). These components also define a Store instance for the SyncEngine to use, but it's all transparent to the SyncManager. (See autofill::get_registered_sync_engine() and autofill::db::store::Store ) For components that use local encryption, SyncManager passes the local encryption key to their SyncEngine Finally, calls sync_multiple() function from the sync15 crate, sending it the SyncEngine instances. sync_multiple() then calls the sync() function for each individual SyncEngine","breadcrumbs":"Design » Sync overview » Sync manager","id":"245","title":"Sync manager"},"246":{"body":"SyncEngine is defined in the support/sync15-traits crate and defines the interface for syncing a component. A new SyncEngine instance is created for each sync SyncEngine.apply_incoming() does the main work. It is responsible for processing incoming records from the server in order to update the local records and calculating which local records should be synced back.","breadcrumbs":"Design » Sync overview » Sync engines","id":"246","title":"Sync engines"},"247":{"body":"SyncEngine instances are free to implement apply_incoming() any way they want, but the most components follow a general pattern.","breadcrumbs":"Design » Sync overview » The apply_incoming pattern","id":"247","title":"The apply_incoming pattern"},"248":{"body":"The local table stores records for the local application The mirror table stores the last known record from the server The staging temporary table stores the incoming records that we're currently processing The local/mirror/staging tables contains a guid as its primary key. A record will share the same guid for the local/mirror/staging table. The metadata table stores the GUID for the collection as a whole and the the last-known server timestamp of the collection.","breadcrumbs":"Design » Sync overview » Database Tables","id":"248","title":"Database Tables"},"249":{"body":"stage incoming : write out each incoming server record to the staging table fetch states : take the rows from all 3 tables and combine them into a single struct containing Options for the local/mirror/staging records. iterate states : loop through each state, decide how to do change the local records, then execute that plan. reconcile/plan : For each state we create an action plan for it. The action plan is a low-level description of what to change (add this record, delete this one, modify this field, etc). Here are some common situations: A record only appears in the staging table . It's a new record from the server and should be added to the local DB A record only appears in the local table . It's a new record on the local instance and should be synced back to the serve Identical records appear in the local/mirror tables and a changed record is in the staging table . The record was updated remotely and the changes should be propagated to the local DB. A record appears in the mirror table and changed records appear in both the local and staging tables . The record was updated both locally and remotely and we should perform a 3-way merge. apply plan : After we create the action plan, then we execute it. fetch outgoing : Calculate which records need to be sent back to the server Update the mirror table Return those records back to the sync15 code so that it can upload them to the server. The sync15 code returns the timestamp reported by the server in the POST response and hands it back to the engine. The engine persists this timestamp in the metadata table - the next sync will then use this timestamp to only fetch records that have since been changed by other devices","breadcrumbs":"Design » Sync overview » apply_incoming stages","id":"249","title":"apply_incoming stages"},"25":{"body":"For convenience, there is a script that will do all the necessary steps to configure your local firefox-ios build with a local application-services repository. You do not need to do the manual steps if you follow those steps. Run the following to execute the script, the example below assumes all of firefox-ios, rust-components-swift and application-services are in the same directory. Adjust the paths according to where they are on your filesystem. $ cd firefox-ios # This is your local checkout of firefox-ios\n$ ./rust_components_local.sh -a ../application-services ../rust-components-swift Using Xcode, open Client.xcodeproj in firefox-ios Then, make sure to reset packages cache in Xcode. This forces Xcode to remove any previously cached versions of the Rust components. You can reset package caches by going to File -> Packages -> Reset Package Caches If this is not the first time you run the script, make sure to also update package versions. This forces Xcode to pull the latest changes in the rust-components-swift branch. You can update package versions by going to File -> Packages -> Update To Latest Package Versions If this step fails, it's possible that the Reset Package Caches step above left some cruft behind. You can force this step by manually removing ~/Library/Caches/org.swift.swiftpm and ~/Library/Developer/Xcode/DerivedData/Client-{some-long-string} Once the above steps are done, attempt building firefox ios. If you face problems, feel free to contact us","breadcrumbs":"Contributing » Building » How to use the local development autopublish flow for Firefox iOS » Using the automated flow","id":"25","title":"Using the automated flow"},"250":{"body":"The local table has an integer column syncChangeCounter which is incremented every time the embedding app makes a change to a local record (eg, updating a field). Thus, any local record with a non-zero change counter will need to be updated on the server (with either the local record being used, or after it being merged if the record also changed remotely). At the start of the sync, when we are determining what action to take, we take a copy of the change counter, typically in a temp staging table. After we have uploaded the record to the server, we decrement the counter by whatever it was when the sync started. This means that if a record is changed in between staging the record and uploading it, the change counter will not drop to zero, and so it will correctly be seen as locally modified on the next sync Found a bug? Edit this page on GitHub.","breadcrumbs":"Design » Sync overview » syncChangeCounter","id":"250","title":"syncChangeCounter"},"251":{"body":"This is a high level description of the decision highlighted in the ADR that introduced Swift Packages as a strategy to ship our Rust components . That document includes that tradeoffs and why we chose this approach. The strategy includes two main parts: The xcframework that is built from a megazord . The xcframework contains the following, built for all our target iOS platforms. The compiled Rust code for all the crates listed in Cargo.toml as a static library The C header files and Swift module maps for the components The rust-components-swift repository which has a Package.swift that includes the xcframework and acts as the swift package the consumers import","breadcrumbs":"Design » Shipping Rust Components as Swift Packages » High level design for shipping Rust Components as Swift Packages","id":"251","title":"High level design for shipping Rust Components as Swift Packages"},"252":{"body":"In application-services, in the megazords/ios-rust directory, we have the following: A Rust crate that serves as the megazord for our iOS distributions. The megazord depends on all the Rust Component crates and re-exports their public APIs. Some skeleton files for building an xcframework: 1. module.modulemap : The module map tells the Swift compiler how to use C APIs. 1. MozillaRustComponents.h: The header is used by the module map as a shortcut to specify all the available header files 1. Info.plist: The plist file specifies metadata about the resulting xcframework. For example, architectures and subdirectories. The build-xcframework.sh script that stitches things together into a full xcframework bundle: The xcframework format is not well documented; briefly: The xcframework is a directory containing the resources compiled for multiple target architectures. The xcframework is distributed as a .zip file. The top-level directory contains a subdirectory per architecture and an Info.plist. The Info.plist describes what lives in which directory. Each subdirectory represents an architecture. And contains a .framework directory for that architecture. It's a little unusual that we're building the xcframework by hand, rather than defining it as the build output of an Xcode project. It turns out to be simpler for our purposes, but does risk diverging from the expected format if Apple changes the details of xcframeworks in future Xcode releases.","breadcrumbs":"Design » Shipping Rust Components as Swift Packages » The xcframework and application-services","id":"252","title":"The xcframework and application-services"},"253":{"body":"The repository is a Swift Package for distributing releases of Mozilla's various Rust-based application components. It provides the Swift source code packaged in a format understood by the Swift package manager, and depends on a pre-compiled binary release of the underlying Rust code published from application-services The rust-components-swift repo mainly includes the following: Package.swift: Defines all the targets and products the package exposes. Package.swift also includes where the package gets the xcframework that application-services builds make_tag.sh: A script that does the following: Generates any dynamically generated Swift code, mainly: The uniffi generated Swift bindings The Glean metrics Creates and commits a git tag that can be pushed to cut a release Consumers would then import the rust-components-swift swift package, by indicating the url of the package on github (i.e https://github.com/mozilla/rust-components-swift ) and selecting a version using the git tag. Found a bug? Edit this page on GitHub.","breadcrumbs":"Design » Shipping Rust Components as Swift Packages » The rust-components-swift repository","id":"253","title":"The rust-components-swift repository"},"254":{"body":"On a high level, Firefox Sync has three main components: The Firefox Account Server: Which uses oauth to authenticate and provide users with scoped access. The FxA Server also stores input that will be used by the clients to generate the sync keys. Firefox: This is the firefox app itself, which implements the client logic to communicate with the firefox account servers, generate sync keys, use them to encrypt data and send/receive encrypted data to/from the sync storage servers Sync Storage Server: The server that stores encrypted sync data. The clients would retrieve the encrypted data and decrypt it client side Additionally, the token server assists in providing metadata to Firefox, so that it knows which sync server to communicate with. Diagram showing on a high level, how Firefox sync interacts with Firefox Accounts and Sync Services","breadcrumbs":"Design » Rust Component's Strategy » High level firefox sync interactions","id":"254","title":"High level firefox sync interactions"},"255":{"body":"Since we have multiple Firefox apps (Desktop, iOS, Android, Focus, etc) Firefox sync can sync across platforms. Allowing users to access their up-to-date data across apps and devices. Diagram showing how firefox sync is a multi-platform feature","breadcrumbs":"Design » Rust Component's Strategy » Multi-platform sync diagram","id":"255","title":"Multi-platform sync diagram"},"256":{"body":"Before our Rust Components came to life, each application had its own implementation of the sync and FxA client protocols. This lead to duplicate logic across platforms. This was problematic since any modification to the sync or FxA client business logic would need to be modified in all implementations and the likelihood of errors was high. Diagram showing how firefox sync used to be, with each platform having its own implementation","breadcrumbs":"Design » Rust Component's Strategy » Before: How sync was","id":"256","title":"Before: How sync was"},"257":{"body":"Currently, we are in the process of migrating many of the sync implementation to use our Rust Component strategy. Fenix primarily uses our Rust Components and iOS has some integrated as well. Additionally, Firefox Desktop also uses one Rust component (Web Extension Storage). The Rust components not only unify the different implementations of sync, they also provide a convenient local storage for the apps. In other words, the apps can use the components for storage, with or without syncing to the server. Diagram showing how firefox sync is now, with iOS and Fenix platform sharing some implementations Current Status The following table has the status of each of our sync Rust Components | Application\\Component | Bookmarks | History | Tabs | Passwords | Autofill | Web Extension Storage | FxA Client | |-----------------------|-----------|---------|------|-----------|----------|-----------------------|------------| | Fenix | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | | ✔️ | | Firefox iOS | ✔️ | | ✔️ | ✔️ | | | ✔️ | | Firefox Desktop | | | | | | ✔️ | | | Focus | | | | | | | |","breadcrumbs":"Design » Rust Component's Strategy » Now: Sync is starting to streamline its components","id":"257","title":"Now: Sync is starting to streamline its components"},"258":{"body":"In an aspirational future, all the applications would use the same implementation for Sync. However, it's unlikely that we would migrate everything to use the Rust components since some implementations may not be prioritized, this is especially true for desktop which already has stable implementations. That said, we can get close to this future and minimize duplicate logic and the likelihood of errors. Diagram showing how firefox sync should be, with all platforms using one implementation You can edit the diagrams in the following lucid chart (Note: Currently only Mozilla Employees can edit those diagrams): https://lucid.app/lucidchart/invitations/accept/inv_ab72e218-3ad9-4604-a7cd-7e0b0c259aa2 Once they are edited, you can re-import them here by replacing the old diagrams in the docs/diagrams directory on GitHub. As long as the names are the same, you shouldn't need to edit those docs! Found a bug? Edit this page on GitHub.","breadcrumbs":"Design » Rust Component's Strategy » Future: Only one implementation for each sync engine","id":"258","title":"Future: Only one implementation for each sync engine"},"259":{"body":"Some application-services components collect telemetry using the Glean SDK . Products that send telemetry via Glean must request a data-review following the Firefox Data Collection process before integrating any of the components listed below. Found a bug? Edit this page on GitHub.","breadcrumbs":"Design » Metrics - (Glean Telemetry) » Metrics collected by Application Services components","id":"259","title":"Metrics collected by Application Services components"},"26":{"body":"The easiest way to disable local development is to simply revert any changes to firefox-ios/Client.xcodeproj/project.pbxproj. However, if there are other changes to the file that you would like to preserve, you can use the same script. To use the same script, you will need to: Know what version of rust-components-swift was used beforehand. You can find this by checking the git diff on firefox-ios/Client.xcodeproj/project.pbxproj. Run: $ ./rust_components_local.sh --disable ../rust-components-swift Then, make sure to reset packages cache in Xcode. This forces Xcode to remove any previously cached versions of the Rust components. You can reset package caches by going to File -> Packages -> Reset Package Caches If you happen to change branches in rust-components-swift, you will need to disable then re-enable local development. The script is not currently smart enough to switch branches. Alternatively, keep the branch in rust-components-swift the same. rust-components-swift serves only as a release surface so there is little use to switching branches and pushing changes to it, unless you are changing something related to the release process.","breadcrumbs":"Contributing » Building » How to use the local development autopublish flow for Firefox iOS » Disabling local development","id":"26","title":"Disabling local development"},"260":{"body":"Like almost all Rust projects, the entire point of the application-services components is that they be used by external projects. If these components use Rust features available in only the very latest Rust version, this will cause problems for projects which aren't always able to be on that latest version. Given application-services is currently developed and maintained by Mozilla staff, it should be no surprise that an important consideration is mozilla-central (aka, the main Firefox repository).","breadcrumbs":"Design » Rust Version Policy » Rust Versions","id":"260","title":"Rust Versions"},"261":{"body":"It should also come as no surprise that the Rust policy for mozilla-central is somewhat flexible. There is an official Rust Update Policy Document but everything in the future is documented as \"estimated\". Ultimately though, that page defines 2 Rust versions - \"Uses\" and \"Requires\", and our policy revolves around these. To discover the current, actual \"Uses\" version, there is a Meta bug on Bugzilla that keeps track of the latest versions as they are upgraded. To discover the current, actual \"Requires\" version, see searchfox","breadcrumbs":"Design » Rust Version Policy » Mozilla-central Rust policies.","id":"261","title":"Mozilla-central Rust policies."},"262":{"body":"Our official Rust version policy is: All components will ship using, have all tests passing, and have clippy emit no warnings, with the same version mozilla-central currently \"uses\". All components must be capable of building (although not necessarily with all tests passing nor without clippy errors or other warnings) with the same version mozilla-central currently \"requires\". This policy only applies to the \"major\" and \"minor\" versions - a different patch level is still considered compliant with this policy.","breadcrumbs":"Design » Rust Version Policy » application-services Rust version policy","id":"262","title":"application-services Rust version policy"},"263":{"body":"All CI for this project will try and pin itself to this same version. At time of writing, this means that our circle CI integration and rust-toolchain configuration will specify the versions (and where possible, the CI configuration file will avoid duplicating the information in rust-toolchain) We should maintain CI to ensure we still build with the \"Requires\" version. As versions inside mozilla-central change, we will bump these versions accordingly. While newer versions of Rust can be expected to work correctly with our existing code, it's likely that clippy will complain in various ways with the new version. Thus, a PR to bump the minimum version is likely to also require a PR to make changes which keep clippy happy. In the interests of avoiding redundant information which will inevitably become stale, the circleci and rust-toolchain configuration links above should be considered the canonical source of truth for the currently supported official Rust version. Found a bug? Edit this page on GitHub.","breadcrumbs":"Design » Rust Version Policy » Implications of this","id":"263","title":"Implications of this"},"264":{"body":"The data below has been added as a tool for future pragma analysis work and is expected to be useful so long as our pragma usage remains stable or this doc is kept up-to-date. This should help us understand our current pragma usage and where we may be able to make improvements. Pragma Value Component Notes cache_size -6144 places foreign_keys ON autofill, places, tabs, webext-storage journal_mode WAL autofill, places, tabs, webext-storage page_size 32768 places secure_delete true logins temp_store 2 autofill, logins, places, tabs, webext_storage Setting temp_store to 2 (MEMORY) is necessary to avoid SQLITE_IOERR_GETTEMPPATH errors on Android (see here for details) wal_autocheckpoint 62 places wal_checkpoint PASSIVE places Used in the sync finished step in history and bookmarks syncing and in the places run_maintenance function The user_version pragma is excluded because the value varies and sqlite does not do anything with the value. The push component does not implement any of the commonly used pragmas noted above. The sqlcipher pragmas that we set have been excluded from this list as we are trying to remove sqlcipher and do not want to encourage future use. Found a bug? Edit this page on GitHub.","breadcrumbs":"Design » Sqlite Database Pragma Usage » Sqlite Database Pragma Usage","id":"264","title":"Sqlite Database Pragma Usage"},"265":{"body":"","breadcrumbs":"Releases » Application Services Release Process","id":"265","title":"Application Services Release Process"},"266":{"body":"Nightly builds are automatically generated using a taskcluster cron task. The results of the latest successful nightly build is listed here: https://firefox-ci-tc.services.mozilla.com/tasks/index/project.application-services.v2.nightly/latest The latest nightly decision task should be listed here: https://firefox-ci-tc.services.mozilla.com/tasks/index/project.application-services.v2.branch.main.latest.taskgraph/decision-nightly If you don't see a decision task from the day before, then contact releng. It's likely that the cron decision task is broken.","breadcrumbs":"Releases » Nightly builds","id":"266","title":"Nightly builds"},"267":{"body":"Release builds are generated from the release-vXXX branches and triggered in Ship-it Whenever a commit is pushed to a release branch, we build candidate artifacts. These artifacts are shippable -- if we decide that the release is ready, they just need to be copied to the correct location. The push phase of release-promotion copies the candidate to a staging location where they can be tested. The ship phase of release-promotion copies the candidate to their final, published, location.","breadcrumbs":"Releases » Release builds","id":"267","title":"Release builds"},"268":{"body":"This part is 100% covered by the Release Management team. The dev team should not perform these steps. On Merge Day we take a snapshot of the current main, and prepare a release. See Firefox Release Calendar . Create a branch name with the format releases-v[release_version] off of the main branch (for example, release-v118) through the GitHub UI. [release_version] should follow the Firefox release number. See Firefox Release Calendar . Create a PR against the release branch that updates version.txt and updates the CHANGELOG.md as follows: In version.txt , update the version from [release_version].0a1 to [release_version].0. diff --git a/version.txt b/version.txt\n--- a/version.txt\n+++ b/version.txt\n@@ -1 +1 @@\n-118.0a1\n+118.0 In CHANGELOG.md , change In progress to _YYYY-MM-DD_ to match the Merge Day date and add a URL to the release version change log. diff --git a/CHANGELOG.md b/CHANGELOG.md\nindex 7f2c07a1a8..06688fdcab 100644\n--- a/CHANGELOG.md\n+++ b/CHANGELOG.md\n@@ -1,8 +1,7 @@\n-# v118.0 (In progress)\n-\n-[Full Changelog](In progress)\n+# v118.0 (_2023-08-28_) ## General\n+ ### 🦊 What's Changed 🦊 - Backward-incompatible changes to the Suggest database schema to accommodate custom details for providers ([#5745](https://github.com/mozilla/application-services/pull/5745)) and future suggestion types ([#5766](https://github.com/mozilla/application-services/pull/5766)). This only affects prototyping, because we aren't consuming Suggest in any of our products yet.\n@@ -16,7 +15,6 @@ - The Remote Settings client has a new `Client::get_records_with_options()` method ([#5764](https://github.com/mozilla/application-services/pull/5764)). This is for Rust consumers only; it's not exposed to Swift or Kotlin. - `RemoteSettingsRecord` objects have a new `deleted` property that indicates if the record is a tombstone ([#5764](https://github.com/mozilla/application-services/pull/5764)). - ## Rust log forwarder ### 🦊 What's Changed 🦊 @@ -34,6 +32,8 @@ - Removed previously deprecated commands `experimenter`, `ios`, `android`, `intermediate-repr` ([#5784](https://github.com/mozilla/application-services/pull/5784)). +[Full Changelog](https://github.com/mozilla/application-services/compare/v117.0...v118.0)\n+ # v117.0 (_2023-07-31_) Create a commit named 'Cut release v[release_version].0` and a PR for this change. See example PR Create a PR against the main branch that updates version.txt and updates the CHANGELOG.md as follows: In version.txt , update the version from [release_version].0a1 to [next_release_version].0a1. diff --git a/version.txt b/version.txt\n--- a/version.txt\n+++ b/version.txt\n@@ -1 +1@@\n-118.0a1\n+119.0a1 In CHANGELOG.md , change the in progress version from [release_version].0 to [next_release_version].0, add a header for the previous release version, and add a URL to the previous release version change log. diff --git a/CHANGELOG.md b/CHANGELOG.md\n--- a/CHANGELOG.md\n+++ b/CHANGELOG.md\n@@ -1,8 +1,7 @@\n-# v118.0 (In progress)\n+# v119.0 (In progress) [Full Changelog](In progress) +# v118.0 (_2023-08-28_)\n@@ -34,6 +36,8 @@\n+[Full Changelog](https://github.com/mozilla/application-services/compare/v117.0...v118.0)\n+\n# v117.0 (_2023-07-31_) Create a commit named 'Start release v[next_release_version].0` and a PR for this change. See example PR Once all of the above PRs have landed, create a new Application Services release in Ship-It. Promote and Ship the release. Tag the release in the Application Services repo. Inform the Application Services team to cut a release of rust-components-swift The team will tag the repo and let you know the git hash to use when updating the consumer applications Update consumer applications firefox-android: Follow the directions in the release checklist firefox-ios: Follow the directions in the release checklist","breadcrumbs":"Releases » [Release management] Creating a new release","id":"268","title":"[Release management] Creating a new release"},"269":{"body":"Run pip3 install -r automation/requirements.txt to install the required Python packages. Run the automation/prepare-release.py script. This should: Create a new branch named release-vXXX Create a PR against that branch that updates version.txt like this: diff --git a/version.txt b/version.txt\nindex 8cd923873..6482018e0 100644\n--- a/version.txt\n+++ b/version.txt\n@@ -1,4 +1,4 @@\n-114.0a1\n+114.0 Create a PR on main that starts a new CHANGELOG header. Tag the release with automation/tag-release.py [major-version-number]","breadcrumbs":"Releases » [Release management] Creating a new release via scripts:","id":"269","title":"[Release management] Creating a new release via scripts:"},"27":{"body":"It's important to note the automated flow runs through all the necessary steps in a script, so if possible use the script as it's a tedious manual process However, if the script is failing or you would like to run the manual process for any other reason follow the following steps.","breadcrumbs":"Contributing » Building » How to use the local development autopublish flow for Firefox iOS » Using the manual flow","id":"27","title":"Using the manual flow"},"270":{"body":"If you want to uplift changes into the a previous release: Make sure the changes are present in main and have been thoroughly tested. Checkout the release-vXXX branch, where XXX is the major version number. Bump the version in version.txt Cherry-pick any commits that you want to uplift Create a PR for the changes Trigger release-promotion for the branch once the PRs are approved, merged, and CI has completed","breadcrumbs":"Releases » Cutting patch releases for uplifted changes","id":"270","title":"Cutting patch releases for uplifted changes"},"271":{"body":"We build several artifacts for both nightlies and releases: nightly.json / release.json. This is a JSON file containing metadata from successful builds. The metadata for the latest successful build can be found from a taskcluster index: https://firefox-ci-tc.services.mozilla.com/api/index/v1/task/project.application-services.v2.release.latest/artifacts/public%2Fbuild%2Frelease.json The JSON file contains: The version number for the nightly/release The git commit ID The maven channel for Kotlin packages: maven-production: https://maven.mozilla.org/?prefix=maven2/org/mozilla/appservices/ maven-nightly-production: https://maven.mozilla.org/?prefix=maven2/org/mozilla/appservices/nightly/ maven-staging: https://maven-default.stage.mozaws.net/?prefix=maven2/org/mozilla/appservices/ maven-nightly-staging: https://maven-default.stage.mozaws.net/?prefix=maven2/org/mozilla/appservices/nightly/ Links to nimbus-fml.*: used to build Firefox/Focus on Android and iOS Links to *RustComponentsSwift.xcframework.zip: XCFramework archives used to build Firefox/Focus on iOS Link to swift-components.tar.xz: UniFFI-generated swift files which get extracted into the rust-components-swift repository for each release.","breadcrumbs":"Releases » What gets built in a release?","id":"271","title":"What gets built in a release?"},"272":{"body":"For nightly builds, consumers get the artifacts directly from the taskcluster. For, firefox-android, the nightlies are handled by relbot For, firefox-ios, the nightlies are consumed by rust-components-swift . rust-components-swift makes a github release, which is picked up by a Github action in firefox-ios","breadcrumbs":"Releases » Nightly builds","id":"272","title":"Nightly builds"},"273":{"body":"For real releases, we use the taskcluster release-promotion action. Release promotion happens in two phases: promote copies the artifacts from taskcluster and moves them to a staging area. This allows for testing the consumer apps with the artifacts. ship copies the artifacts from the staging area to archive.mozilla.org, which serves as their permanent storage area. Found a bug? Edit this page on GitHub.","breadcrumbs":"Releases » Release promotion","id":"273","title":"Release promotion"},"274":{"body":"This document provides an overview of the build-and-publish pipeline used to make our work in this repo available to consuming applications. It's intended both to document the pipeline for development and maintenance purposes, and to serve as a basic analysis of the integrity protections that it offers (so you'll notice there are notes and open questions in place where we haven't fully hashed out all those details). The key points: We use \"stable\" Rust . CI is pinned to whatever version is currently used on mozilla-central to help with vendoring into that repository. You should check what current values are specified for CircleCI and for TaskCluster We use Cargo for building and testing the core Rust code in isolation, Gradle with rust-android-gradle for combining Rust and Kotlin code into Android components and running tests against them, and XCframeworks driving XCode for combining Rust and Swift code into iOS components. TaskCluster runs on every pull-request, release, and push to main, to ensure Android artifacts build correctly and to execute their tests via gradle. CircleCI runs on every branch, pull-request (including forks), and release, to execute lint checks and automated tests at the Rust and Swift level. Releases align with the Firefox Releases schedules, and nightly releases are automated to run daily see the releases for more information Notifications about build failures are sent to a mailing list at a-s-ci-failures@mozilla.com Our Taskcluster implementation is almost entirely maintained by the Release Engineering team. The proper way to contact them in case of emergency or for new developments is to ask on the #releaseduty-mobile Slack channel. Our main point of contact is @mihai. For Android consumers these are the steps by which Application Services code becomes available, and the integrity-protection mechanisms that apply at each step: Code is developed in branches and lands on main via pull request. GitHub branch protection prevents code being pushed to main without review. CircleCI and TaskCluster run automated tests against the code, but do not have the ability to push modified code back to GitHub thanks to the above branch protection. TaskCluster jobs do not run against PRs opened by the general public, only for PRs from repo collaborators. Contra the github org security guidelines , signing of individual commits is encouraged but is not required . Our experience in practice has been that this adds friction for contributors without sufficient tangible benefit. Developers manually create a release from latest main. The ability to create new releases is managed entirely via github's permission model. TODO: the github org security guidelines recommend signing tags, and auditing all included commits as part of the release process. We should consider some tooling to support this. I don't think there's any way to force githib to only accept signed releases in the same way it can enforce signed commits. TaskCluster checks out the release tag, builds it for all target platforms, and runs automated tests. These tasks run in a pre-built docker image, helping assure integrity of the build environment. TODO: could this step check for signed tags as an additional integrity measure? TaskCluster uploads symbols to Socorro. The access token for this is currently tied to @eoger's LDAP account. TaskCluster uploads built artifacts to maven.mozilla.org Secret key for uploading to maven is provisioned via TaskCluster, guarded by a scope that's only available to this task. TODO: could a malicious dev dependency from step (3) influence the build environment here? TODO: talk about how TC's \"chain of trust\" might be useful here. Consumers fetch the published artifacts from maven.mozilla.org. For iOS consumers the corresponding steps are: Code is developed in branches and lands on main via pull request, as above. Developers manually create a release from latest main, as above. CircleCI checks out the release tag, builds it, and runs automated tests. TODO: These tasks bootstrap their build environment by fetching software over https. could we do more to ensure the integrity of the build environment? TODO: could this step check for signed tags as an additional integrity measure? TODO: can we prevent these steps from being able to see the tokens used for publishing in subsequent steps? CircleCI builds a binary artifact: An XCFramework containing just Rust code and header files, as a zipfile, for use by Swift Packags. TODO: could a malicious dev dependency from step (3) influence the build environment here? CircleCI uses dpl to publish to GitHub as a release artifact. See Authentication and secrets below Consumers add Application services as a dependency from the Rust Components Swift repo using Apple's Swift Package Manager. For consuming in mozilla-central, see how to vendor components into mozilla-central This is a diagram of the pipeline as it exists (and is planned) for the Nimbus SDK, one of the libraries in Application Services: (Source: https://miro.com/app/board/o9J_lWx3jhY=/) Nimbus SDK Build and Publish Pipeline","breadcrumbs":"Releases » CI Publishing tools and flow » Application Services Build and Publish Pipeline","id":"274","title":"Application Services Build and Publish Pipeline"},"275":{"body":"","breadcrumbs":"Releases » CI Publishing tools and flow » Authentication and secrets","id":"275","title":"Authentication and secrets"},"276":{"body":"There's an appsvc-moz github account owned by one of the application-services team (currently markh, but we should consider rotating ownership). Given only 1 2fa device can be connected to a github account, multiple owners doesn't seem practical. In most cases, whenever a github account needs to own a secret for any CI, it will be owned by this account.","breadcrumbs":"Releases » CI Publishing tools and flow » @appsvc-moz account","id":"276","title":"@appsvc-moz account"},"277":{"body":"CircleCI config requires a github token (owned by @appsvc-moz). This is a \"personal access token\" obtained via github's Settings -> Developer Settings -> Personal Access Tokens -> Classic Token. This token: Should be named something like \"circleci\" Have \"no expiration\" (XXX - this seems wrong, should we adjust?) Once you have generated the token, it must be added to https://app.circleci.com/settings/project/github/mozilla/application-services/environment-variables as the environment variable GITHUB_TOKEN Found a bug? Edit this page on GitHub.","breadcrumbs":"Releases » CI Publishing tools and flow » CircleCI","id":"277","title":"CircleCI"},"278":{"body":"Our components rely on cryptographic primitives provided by NSS . Every month or so, a new version of NSS is published and we should try to keep our version as up-to-date as possible. Because it makes unit testing easier on Android, and helps startup performance on iOS, we compile NSS ourselves and link to it statically. Note that NSS is mainly used by Mozilla as a dynamic library and the NSS project is missing related CI jobs (iOS builds, windows cross-compile builds etc.) so you should expect breakage when updating the library (hence this guide).","breadcrumbs":"Releases » How to upgrade NSS » Guide to upgrading NSS","id":"278","title":"Guide to upgrading NSS"},"279":{"body":"The build code is located in the libs/ folder. The version string is located in the beginning of build-all.sh . For most NSS upgrades, you'll need to bump the version number in this file and update the downloaded archive checksum. Then follow the steps for Updating the cross-compiled NSS Artifacts below. The actual build invocations are located in platform-specific script files (e.g. build-nss-ios.sh ) but usually don't require any changes. To test out updating NSS version: Ensure you've bumped the NSS in build-all.sh Clear any old NSS build artifacts: rm -rf ./libs/desktop && cargo clean Install the updates version: ./libs/verify-desktop-environment.sh Try it out: cargo test","breadcrumbs":"Releases » How to upgrade NSS » Updating the Version","id":"279","title":"Updating the Version"},"28":{"body":"To build the xcframework do the following: In your local checkout of application-services, navigate to megazords/ios-rust/ Run the build-xcframework.sh script: $ ./build-xcframework.sh This will produce a file name MozillaRustComponents.xcframework.zip that contains the following, built for all our target iOS platforms. The compiled Rust code for all the crates listed in Cargo.toml as a static library The C header files and Swift module maps for the components","breadcrumbs":"Contributing » Building » How to use the local development autopublish flow for Firefox iOS » Building the xcframework","id":"28","title":"Building the xcframework"},"280":{"body":"We use a Linux TC worker for cross-compiling NSS for iOS, Android and Linux desktop machines. However, due to the complexity of the NSS build process, there is no easy way for cross-compiling MacOS and Windows -- so we currently use pre-built artifacts for MacOS desktop machines (ref #5210 ). Look for the tagged version from the NSS CI usually a description with something like Added tag NSS_3_90_RTM Select the build for the following system(s) (first task with the title \"B\"): For Intel MacOS: mac opt-static Update taskcluster/ci/fetch/kind.yml , specifically nss-artifact task to the appropriate url and checksum and size Note: To get the checksum, you can run shasum -a 256 {path-to-artifact} or you can make a PR and see the output of the failed log. Update the SHA256 value for darwin cross-compile in libs/build-nss-desktop.sh to the same checksum as above. Open a pull request with these changes and it should update the Taskcluster artifact","breadcrumbs":"Releases » How to upgrade NSS » Updating the Cross-Compiled NSS Artifacts","id":"280","title":"Updating the Cross-Compiled NSS Artifacts"},"281":{"body":"If the new version of NSS comes with new functions that you want to expose, you will need to: Add low-level bindings for those functions in the nss_sys crate ; follow the instructions in README for that crate. Expose a safe wrapper API for the functions from the nss crate ; Expose a convenient high-level API for the functions from the rc_crypto crate ;","breadcrumbs":"Releases » How to upgrade NSS » Exposing new functions","id":"281","title":"Exposing new functions"},"282":{"body":"On top of the primitives provided by NSS, we have built a safe Rust wrapper named rc_crypto that links to NSS and makes these cryptographic primitives available to our components. The linkage is done by the nss_build_common crate. Note that it supports a is_gecko feature to link to NSS dynamically on Desktop. Because the NSS static build process does not output a single .a file (it would be great if it did), this file must describe for each architecture which modules should we link against. It is mostly a duplication of logic from the NSS gyp build files . Note that this logic is also duplicated in our NSS lib build steps (e.g. build-nss-desktop.sh ). One of the most common build failures we get when upgrading NSS comes from NSS adding new vectorized/asm versions of a crypto algorithm for a specific architecture in order to improve performance. This new optimized code gets implemented as a new gyp target/module that is emitted only for the supported architectures. When we upgrade our copy of NSS we notice the linking step failing on CI jobs because of undefined symbols. This PR shows how we update nss_common_build and the build scripts to accommodate for these new modules. Checking the changelog for any suspect commit relating to hardware acceleration is rumored to help. Found a bug? Edit this page on GitHub.","breadcrumbs":"Releases » How to upgrade NSS » Tips for Fixing Bustage","id":"282","title":"Tips for Fixing Bustage"},"283":{"body":"fxa_client - Rust<link rel=\"stylesheet\" media=\"(prefers-color-scheme:light)\" href=\"../static.files/light-0f8c037637f9eb3e.css\"><link rel=\"stylesheet\" media=\"(prefers-color-scheme:dark)\" href=\"../static.files/dark-1097f8e92a01e3cf.css\"><link rel=\"stylesheet\" href=\"../static.files/noscript-13285aec31fa243e.css\">☰Crate fxa_clientVersion 0.1.0All ItemsStructsEnumsType Definitions?Crate fxa_clientsource · [−]Expand descriptionFirefox Accounts Client\nThe fxa-client component lets applications integrate with the\nFirefox Accounts\nidentity service. The shape of a typical integration would look\nsomething like: Out-of-band, register your application with the Firefox Accounts service,\nproviding an OAuth redirect_uri controlled by your application and\nobtaining an OAuth client_id. On application startup, create a FirefoxAccount object to represent the\nsigned-in state of the application. On first startup, a new FirefoxAccount can be created by calling\nFirefoxAccount::new and passing the application’s client_id.\nFor subsequent startups the object can be persisted using the\nto_json method and re-created by\ncalling FirefoxAccount::from_json. When the user wants to sign in to your application, direct them through\na web-based OAuth flow using begin_oauth_flow\nor begin_pairing_flow; when they return\nto your registered redirect_uri, pass the resulting authorization state back to\ncomplete_oauth_flow to sign them in. Display information about the signed-in user by using the data from\nget_profile. Access account-related services on behalf of the user by obtaining OAuth\naccess tokens via get_access_token. If the user opts to sign out of the application, calling disconnect\nand then discarding any persisted account data. StructsAccessTokenInfoAn OAuth access token, with its associated keys and metadata.AttachedClientA client connected to the user’s account.AuthorizationInfoInformation about the authorization state of the application.AuthorizationParametersParameters provided in an incoming OAuth request.DeviceA device connected to the user’s account.DevicePushSubscriptionDetails of a web-push subscription endpoint.FirefoxAccountObject representing the signed-in state of an application.FxaConfigLocalDeviceLocal device that’s connecting to FxAMetricsParamsAdditional metrics tracking parameters to include in an OAuth request.ProfileInformation about the user that controls a Firefox Account.ScopedKeyA cryptograpic key associated with an OAuth scope.SendTabPayloadThe payload sent when invoking a “send tab” command.TabHistoryEntryAn individual entry in the navigation history of a sent tab.EnumsAccountEventAn event that happened on the user’s account.DeviceCapabilityA “capability” offered by a device.DeviceTypeEnumeration for the different types of device.ErrorFxA internal error type\nThese are used in the internal code. This error type is never returned to the consumer.FxaErrorPublic error type thrown by many [FirefoxAccount] operations.FxaRustAuthStateHigh-level view of the authorization stateFxaServerIncomingDeviceCommandA command invoked by another device.Type DefinitionsApiResultResult returned by public-facing API functionsResultResult returned by internal functions\nFound a bug? Edit this page on GitHub.","breadcrumbs":"Rustdocs for components","id":"283","title":"Rustdocs for components"},"284":{"body":"The documentation in this repository pertains to the application-services library, primarily the sync and storage components, firefox account client and the nimbus-sdk experimentation client. The markdown is converted to static HTML using mdbook . To add a new document, you need to add it to the SUMMARY.md file which produces the sidebar table of contents.","breadcrumbs":"Adding to these documents » Developing documentation","id":"284","title":"Developing documentation"},"285":{"body":"","breadcrumbs":"Adding to these documents » Building documentation","id":"285","title":"Building documentation"},"286":{"body":"The mdbook crate is required in order to build the documentation: cargo install mdbook mdbook-mermaid mdbook-open-on-gh The repository documents are be built with: ./tools/build.docs.sh The built documentation is saved in build/docs/book. Found a bug? Edit this page on GitHub.","breadcrumbs":"Adding to these documents » Building the narrative (book) documentation","id":"286","title":"Building the narrative (book) documentation"},"29":{"body":"After you generated the MozillaRustComponents.xcframework.zip in the previous step, do the following to include it in a local checkout of rust-components-swift. The file will be in the megazords/ios-rust directory. Unzip the MozillaRustComponents.xcframework.zip into the rust-components-swift repository: (Assuming you are in the root of the rust-components-swift directory and application-services is a neighbor directory) unzip -o ../application-services/megazords/ios-rust/MozillaRustComponents.xcframework.zip -d . Change the Package.swift's reference to the xcframework to point to the unzipped MozillaRustComponents.xcframework that was created in the previous step. You can do this by uncommenting the following line: path: \"./MozillaRustComponents.xcframework\" and commenting out the following lines: url: url, checksum: checksum,","breadcrumbs":"Contributing » Building » How to use the local development autopublish flow for Firefox iOS » Include the xcframework in a local checkout of rust-components-swift","id":"29","title":"Include the xcframework in a local checkout of rust-components-swift"},"3":{"body":"Anyone is welcome to help with the Application Services project. Feel free to get in touch with other community members on Matrix or through issues on GitHub. Participation in this project is governed by the Mozilla Community Participation Guidelines .","breadcrumbs":"Contributing » Contributing to Application Services","id":"3","title":"Contributing to Application Services"},"30":{"body":"For this step, run the following script from inside the rust-components-swift repository (assuming that application-services is a neighboring directory to rust-components-swift). ./generate.sh ../application-services Once that is done, stage and commit the changes the script ran. Xcode can only pick up committed changes.","breadcrumbs":"Contributing » Building » How to use the local development autopublish flow for Firefox iOS » Run the generation script with a local checkout of application services","id":"30","title":"Run the generation script with a local checkout of application services"},"31":{"body":"This is the final step to include your local changes into firefox-ios. Do the following steps: Open Client.xcodeproj in Xcode Navigate to the Swift Packages in Xcode: Screenshot of where to find the setting for Client Remove the dependency on rust-components-swift as listed on Xcode, you can click the dependency then click the - Add a new swift package by clicking the +: On the top right, enter the full path to your rust-components-swift checkout, preceded by file://. If you don't know what that is, run pwd in while in rust-components-swift. For example: file:///Users/tarikeshaq/code/rust-components-swift Change the branch to be the checked-out branch of rust-component-swift you have locally. This is what the dialog should look like: Dialog for including the rust-components-swift package Note: If Xcode prevents you from adding the dependency to reference a local package, you will need to manually modify the Client.xcodeproj/project.pbxproj and replace every occurrence of https://github.com/mozilla/rust-components-swift with the full path to your local checkout. Click Add Package Now include the packages you would like to include, choose MozillaAppServices Finally, attempt to build firefox-ios, and if all goes well it should launch with your code. If you face problems, feel free to contact us Found a bug? Edit this page on GitHub.","breadcrumbs":"Contributing » Building » How to use the local development autopublish flow for Firefox iOS » Include the local checkout of rust-components-swift in firefox-ios","id":"31","title":"Include the local checkout of rust-components-swift in firefox-ios"},"32":{"body":"This is a guide on testing the Swift Package Manager component locally against a local build of Focus iOS. For more information on our Swift Package Manager design, read the ADR that introduced it This guide assumes the component you want to test is already distributed with the rust-components-swift repository, you can read the guide for adding a new component if you would like to distribute a new component. To test a component locally, you will need to do the following: Build an xcframework in a local checkout of application-services Include the xcframework in a local checkout of rust-components-swift Run the make-tag script in rust-components-swift using a local checkout of application-services Include the local checkout of rust-components-swift in Focus Below are more detailed instructions for each step","breadcrumbs":"Contributing » Building » How to use the local development flow for Focus for iOS » How to locally test Swift Package Manager components on Focus iOS","id":"32","title":"How to locally test Swift Package Manager components on Focus iOS"},"33":{"body":"To build the xcframework do the following: In a local checkout of application-services, navigate to megazords/ios-rust/ Run the build-xcframework.sh script: $ ./build-xcframework.sh --focus This will produce a file name FocusRustComponents.xcframework.zip in the focus directory that contains the following, built for all our target iOS platforms. The compiled Rust code for all the crates listed in Cargo.toml as a static library The C header files and Swift module maps for the components","breadcrumbs":"Contributing » Building » How to use the local development flow for Focus for iOS » Building the xcframework","id":"33","title":"Building the xcframework"},"34":{"body":"After you generated the FocusRustComponents.xcframework.zip in the previous step, do the following to include it in a local checkout of rust-components-swift: clone a local checkout of rust-components-swift, not inside the application-services repository: git clone https://github.com/mozilla/rust-components.swift.git Unzip the FocusRustComponents.xcframework.zip into the rust-components-swift repository: (Assuming you are in the root of the rust-components-swift directory and application-services is a neighbor directory) unzip -o ../application-services/megazords/ios-rust/focus/FocusRustComponents.xcframework.zip -d . Change the Package.swift's reference to the xcframework to point to the unzipped FocusRustComponents.xcframework that was created in the previous step. You can do this by uncommenting the following line: path: \"./FocusRustComponents.xcframework\" and commenting out the following lines: url: focusUrl, checksum: focusChecksum,","breadcrumbs":"Contributing » Building » How to use the local development flow for Focus for iOS » Include the xcframework in a local checkout of rust-components-swift","id":"34","title":"Include the xcframework in a local checkout of rust-components-swift"},"35":{"body":"For this step, run the following script from inside the rust-components-swift repository (assuming that application-services is a neighboring directory to rust-components-swift). ./generate.sh ../application-services Once that is done, stage and commit the changes the script ran. Xcode can only pick up committed changes.","breadcrumbs":"Contributing » Building » How to use the local development flow for Focus for iOS » Run the generation script with a local checkout of application services","id":"35","title":"Run the generation script with a local checkout of application services"},"36":{"body":"This is the final step to include your local changes into Focus. Do the following steps: Clone a local checkout of Focus if you haven't already. Make sure you also install the project dependencies, more information in their build instructions Open Blockzilla.xcodeproj in Xcode Navigate to the Swift Packages in Xcode: Screenshot of where to find the setting for Blockzilla Screenshot of where to find the package dependencies Remove the dependency on rust-components-swift as listed on Xcode, you can click the dependency then click the - Add a new swift package by clicking the +: On the top right, enter the full path to your rust-components-swift checkout, preceded by file://. If you don't know what that is, run pwd in while in rust-components-swift. For example: file:///Users/tarikeshaq/code/rust-components-swift Change the branch to be the checked-out branch of rust-component-swift you have locally. This is what the dialog should look like: Dialog for including the rust-components-swift package Click Add Package Now include the FocusAppServices library. Note: If Xcode prevents you from adding the dependency to reference a local package, you will need to manually modify the Blockzilla.xcodeproj/project.pbxproj and replace every occurrence of https://github.com/mozilla/rust-components-swift with the full path to your local checkout. Finally, attempt to build focus, and if all goes well it should launch with your code. If you face any problems, feel free to contact us Found a bug? Edit this page on GitHub.","breadcrumbs":"Contributing » Building » How to use the local development flow for Focus for iOS » Include the local checkout of rust-components-swift in Focus","id":"36","title":"Include the local checkout of rust-components-swift in Focus"},"37":{"body":"Java Native Access is an important dependency for the Application Services components on Android, as it provides the low-level interface from the JVM into the natively-compiled Rust code. If you need to work with a locally-modified version of JNA (e.g. to investigate an apparent JNA bug) then you may find these notes helpful. The JNA docs do have an Android Development Environment guide that is a good starting point, but the instructions did not work for me and appear a little out of date. Here are the steps that worked for me: Modify your environment to specify $NDK_PLATFORM, and to ensure the Android NDK tools for each target platform are in your $PATH. On my Mac with Android Studio the config was as follows: export NDK_ROOT=\"$HOME/Library/Android/sdk/ndk/25.2.9519653\"\nexport NDK_PLATFORM=\"$NDK_ROOT/platforms/android-25\"\nexport PATH=\"$PATH:$NDK_ROOT/toolchains/llvm/prebuilt/darwin-x86_64/bin\"\nexport PATH=\"$PATH:$NDK_ROOT/toolchains/aarch64-linux-android-4.9/prebuilt/darwin-x86_64/bin\"\nexport PATH=\"$PATH:$NDK_ROOT/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64/bin\"\nexport PATH=\"$PATH:$NDK_ROOT/toolchains/x86-4.9/prebuilt/darwin-x86_64/bin\"\nexport PATH=\"$PATH:$NDK_ROOT/toolchains/x86_64-4.9/prebuilt/darwin-x86_64/bin\" You will probably need to tweak the paths and version numbers based on your operating system and the details of how you installed the Android NDK. Install the ant build tool (using brew install ant worked for me). Checkout the JNA source from Github. Try doing a basic build via ant dist and ant test. This won't build for Android but will test the rest of the tooling. Adjust ./native/Makefile for compatibility with your Android NSK install. Here's what I had to do for mine: Adjust the $CC variable to use clang instead of gcc: CC=aarch64-linux-android21-clang. Adjust thd $CCP variable to use the version from your system: CPP=cpp. Add -landroid -llog to the list of libraries to link against in $LIBS. Build the JNA native libraries for the target platforms of interest: ant -Dos.prefix=android-aarch64 ant -Dos.prefix=android-armv7 ant -Dos.prefix=android-x86 ant -Dos.prefix=android-x86-64 Package the newly-built native libraries into a JAR/AAR using ant dist. This should produce ./dist/jna.aar. Configure build.gradle for the consuming application to use the locally-built JNA artifact: // Tell gradle where to look for local artifacts.\nrepositories { flatDir { dirs \"/PATH/TO/YOUR/CHECKOUT/OF/jna/dist\" }\n} // Tell gradle to exclude the published version of JNA.\nconfigurations { implementation { exclude group: \"net.java.dev.jna\", module:\"jna\" }\n} // Take a direct dependency on the local JNA AAR.\ndependencies { implementation name: \"jna\", ext: \"aar\"\n} Rebuild and run your consuming application, and it should be using the locally-built JNA! If you're trying to debug some unexpected JNA behaviour (and if you favour old-school printf-style debugging) then you can this code snippet to print to the Android log from the compiled native code: #ifdef __ANDROID__\n#include \n#define HACKY_ANDROID_LOG(...) __android_log_print(ANDROID_LOG_VERBOSE, \"HACKY-DEBUGGING-FOR-ALL\", __VA_ARGS__)\n#else\n#define HACKY_ANDROID_LOG(MSG)\n#endif HACKY_ANDROID_LOG(\"this will go to the android logcat output\");\nHACKY_ANDROID_LOG(\"it accepts printf-style format sequences, like this: %d\", 42); Found a bug? Edit this page on GitHub.","breadcrumbs":"Contributing » Building » How to locally build JNA » Building and using a locally-modified version of JNA","id":"37","title":"Building and using a locally-modified version of JNA"},"38":{"body":"Branch builds are a way to build and test Fenix using branches from application-services and firefox-android. iOS is not currently supported, although we may add it in the future (see #4966 ).","breadcrumbs":"Contributing » Building » Branch builds » Branch builds","id":"38","title":"Branch builds"},"39":{"body":"When we make breaking changes in an application-services branch, we typically make corresponding changes in an android-components branch. Branch builds allow combining those branches together in order to run CI tests and to produce APKs for manual testing. To trigger a branch build for this: Create the PR for the application-services branch you're working on Add [firefox-android: branch-name] to the PR title The branch build tasks will be listed as checks the Github PR. In particular: branch-build-fenix-test and branch-build-ac-test will run the unit android-components/fenix unit tests branch-build-fenix-build will contain the Fenix APK.","breadcrumbs":"Contributing » Building » Branch builds » Breaking changes in an application-services branch.","id":"39","title":"Breaking changes in an application-services branch."},"4":{"body":"You can file issues on GitHub . Please try to include as much information as you can and under what conditions you saw the issue.","breadcrumbs":"Contributing » Bug Reports","id":"4","title":"Bug Reports"},"40":{"body":"When we make non-breaking changes, we typically merge them into main and let them sit there until the next release. In order to check that the current main really does only have non-breaking changes, we run a nightly branch build from the main branch of application-services, To view the latest branch builds: Open the latest decision task from the task index. Click the \"View Task\" link Click \"Task Group\" in the top-left You should now see a list of tasks from the latest nightly *-build were for building the application. A failure here indicates there's probably a breaking change that needs to be resolved. To get the APK, navigate to branch-build-fenix-build and download app-x86-debug.apk from the artifacts list branch-build-ac-test.* are the android-components tests tasks. These are split up by gradle project, which matches how the android-components CI handles things. Running all the tests together often leads to failures. branch-build-fenix-test is the Fenix tests. These are not split up per-project. These builds are triggered by our .cron.yml file Found a bug? Edit this page on GitHub.","breadcrumbs":"Contributing » Building » Branch builds » Application-services nightlies","id":"40","title":"Application-services nightlies"},"41":{"body":"This document gives a high-level overview of how we test components in application-services. It will be useful to you if you're adding a new component, or working on increasing the test coverage of an existing component. If you are only interested in running the existing test suite, please consult the contributor docs and the tests.py script.","breadcrumbs":"Contributing » How to test Rust Components » Guide to Testing a Rust Component","id":"41","title":"Guide to Testing a Rust Component"},"42":{"body":"","breadcrumbs":"Contributing » How to test Rust Components » Unit and Functional Tests","id":"42","title":"Unit and Functional Tests"},"43":{"body":"Since the core implementations of our components live in rust, so does the core of our testing strategy. Each rust component should be accompanied by a suite of unit tests, following the guidelines for writing tests from the Rust Book . Some additional tips: Where possible, it's better use use the Rust typesystem to make bugs impossible than to write tests to assert that they don't occur in practice. But given that the ultimate consumers of our code are not in Rust, that's sometimes not possible. The best idiomatic Rust API for a feature is not necessarily the best API for consuming it over an FFI boundary. Rust's builtin assertion macros are sparse; we use the more_asserts for some additional helpers. Rust's strict typing can make test mocks difficult. If there's something you need to mock out in tests, make it a Trait and use the mockiato crate to mock it. The Rust tests for a component should be runnable via cargo test.","breadcrumbs":"Contributing » How to test Rust Components » Rust code","id":"43","title":"Rust code"},"44":{"body":"We are currently using uniffi to generate most ((and soon all!) of our FFI code and thus the FFI code itself does not need to be extensively tested.","breadcrumbs":"Contributing » How to test Rust Components » FFI Layer code","id":"44","title":"FFI Layer code"},"45":{"body":"The Kotlin wrapper code for a component should have its own test suite, which should follow the general guidelines for testing Android code in Mozilla projects . In practice that means we use JUnit as the test framework and Robolectric to provide implementations of Android-specific APIs. The Kotlin tests for a component should be runnable via ./gradlew :test. The tests at this layer are designed to ensure that the API binding code is working as intended, and should not repeat tests for functionality that is already well tested at the Rust level. But given that the Kotlin bindings involve a non-trivial amount of hand-written boilerplate code, it's important to exercise that code throughly. One complication with running Kotlin tests is that the code needs to run on your local development machine, but the Kotlin code's native dependencies are typically compiled and packaged for Android devices. The tests need to ensure that an appropriate version of JNA and of the compiled Rust code is available in their library search path at runtime. Our build.gradle files contain a collection of hackery that ensures this, which should be copied into any new components. The majority of our Kotlin bindings are autogenerated using uniffi and do not need extensive testing.","breadcrumbs":"Contributing » How to test Rust Components » Kotlin code","id":"45","title":"Kotlin code"},"46":{"body":"The Swift wrapper code for a component should have its own test suite, using Apple's Xcode unittest framework . Due to the way that all rust components need to be compiled together into a single \"megazord\" framework, this entire repository is a single Xcode project. The Swift tests for each component thus need to live under megazords/ios-rust/MozillaTestServicesTests/ rather than in the directory for the corresponding component. (XXX TODO: is this true? it would be nice to find a way to avoid having them live separately because it makes them easy to overlook). The tests at this layer are designed to ensure that the API binding code is working as intended, and should not repeat tests for functionality that is already well tested at the Rust level. But given that the Swift bindings involve a non-trivial amount of hand-written boilerplate code, it's important to exercise that code thoroughly. The majority of our Swift bindings are autogenerated using uniffi and do not need extensive testing.","breadcrumbs":"Contributing » How to test Rust Components » Swift code","id":"46","title":"Swift code"},"47":{"body":"","breadcrumbs":"Contributing » How to test Rust Components » Integration tests","id":"47","title":"Integration tests"},"48":{"body":"⚠️ Those tests were disabled because of how flakey the stage server was. See #3909 ⚠️ The testing/sync-test directory contains a test harness for running sync-related Rust components against a live Firefox Sync infrastructure, so that we can verifying the functionality end-to-end. Each component that implements a sync engine should have a corresponding suite of tests in this directory. XXX TODO: places doesn't. XXX TODO: send-tab doesn't (not technically a sync engine, but still, it's related) XXX TODO: sync-manager doesn't","breadcrumbs":"Contributing » How to test Rust Components » End-to-end Sync Tests","id":"48","title":"End-to-end Sync Tests"},"49":{"body":"It's important that changes in application-services are tested against upstream consumer code in the android-components repo. This is currently a manual process involving: Configuring your local checkout of android-components to use your local application-services build . Running the android-components test suite via ./gradle test. Manually building and running the android-components sample apps to verify that they're still working. Ideally some or all of this would be automated and run in CI, but we have not yet invested in such automation.","breadcrumbs":"Contributing » How to test Rust Components » Android Components Test Suite","id":"49","title":"Android Components Test Suite"},"5":{"body":"Build instructions are available in the building page. Please let us know if you encounter any pain-points setting up your environment.","breadcrumbs":"Contributing » Building the project","id":"5","title":"Building the project"},"50":{"body":"We currently have code coverage reporting on Github using codecov . However, our code coverage does not tell us how much more coverage is caused by our consumers' tests.","breadcrumbs":"Contributing » How to test Rust Components » Test Coverage","id":"50","title":"Test Coverage"},"51":{"body":"ASan, Memsan, and maybe other sanitizer checks, especially around the points where we cross FFI boundaries. General-purpose fuzzing, such as via https://github.com/jakubadamw/arbitrary-model-tests We could consider making a mocking backend for viaduct, which would also be mockable from Kotlin/Swift. Add more end-to-end integration tests! Live device tests, e.g. actual Fenixes running in an emulator and syncing to each other. Run consumer integration tests in CI against main. Found a bug? Edit this page on GitHub.","breadcrumbs":"Contributing » How to test Rust Components » Ideas for Improvement","id":"51","title":"Ideas for Improvement"},"52":{"body":"This is a great way of finding integration bugs with application-services. The testing can be done manually using substitution scripts, but we also have scripts that will do the smoke-testing for you.","breadcrumbs":"Contributing » How to test Rust Components » How to integration (smoke) test application-services » Smoke testing Application Services against end-user apps","id":"52","title":"Smoke testing Application Services against end-user apps"},"53":{"body":"Run pip3 install -r automation/requirements.txt to install the required Python packages.","breadcrumbs":"Contributing » How to test Rust Components » How to integration (smoke) test application-services » Dependencies","id":"53","title":"Dependencies"},"54":{"body":"The automation/smoke-test-android-components.py script will clone (or use a local version) of android-components and run a subset of its tests against the current application-services worktree. It tries to only run tests that might be relevant to application-services functionality.","breadcrumbs":"Contributing » How to test Rust Components » How to integration (smoke) test application-services » Android Components","id":"54","title":"Android Components"},"55":{"body":"The automation/smoke-test-fenix.py script will clone (or use a local version) of Fenix and run tests against the current application-services worktree.","breadcrumbs":"Contributing » How to test Rust Components » How to integration (smoke) test application-services » Fenix","id":"55","title":"Fenix"},"56":{"body":"The automation/smoke-test-fxios.py script will clone (or use a local version) of Firefox iOS and run tests against the current application-services worktree. Found a bug? Edit this page on GitHub.","breadcrumbs":"Contributing » How to test Rust Components » How to integration (smoke) test application-services » Firefox iOS","id":"56","title":"Firefox iOS"},"57":{"body":"","breadcrumbs":"Contributing » How to test Rust Components » Writing efficient tests » Testing faster: How to avoid making compile times worse by adding tests","id":"57","title":"Testing faster: How to avoid making compile times worse by adding tests"},"58":{"body":"We'd like to keep cargo test, cargo build, cargo check, ... reasonably fast, and we'd really like to keep them fast if you pass -p for a specific project. Unfortunately, there are a few ways this can become unexpectedly slow. The easiest of these problems for us to combat at the moment is the unfortunate placement of dev-dependencies in our build graph. If you perform a cargo test -p foo, all dev-dependencies of foo must be compiled before foo's tests can start. This includes dependencies only used non-test targets, such as examples or benchmarks. In an ideal world, cargo could run your tests as soon as it finished with the dependencies it needs for those tests, instead of waiting for your benchmark suite, or the arg-parser your examples use, or etc. Unfortunately, all cargo knows is that these are dev-dependencies, and not which targets actually use them. Additionally, unqualified invocations of cargo (that is, without -p) might have an even worse time if we aren't careful. If I run, cargo test, cargo knows every crate in the workspace needs to be built with all dev dependencies, if places depends on fxa-client, all of fxa-clients dev-dependencies must be compiled, ready, and linked in at least to the lib target before we can even think about starting on places. We have not been careful about what shape the dependency graph ends up as when example code is taken into consideration (as it is by cargo during certain builds), and as a result, we have this problem. Which isn't really a problem we want to fix: Example code can and should depend on several different components, and use them together in interesting ways. So, because we don't want to change what our examples do, or make major architectural changes of the non-test code for something like this, we need to do something else.","breadcrumbs":"Contributing » How to test Rust Components » Writing efficient tests » Background","id":"58","title":"Background"},"59":{"body":"To fix this, we manually insert \"cuts\" into the dependency graph to help cargo out. That is, we pull some of these build targets (e.g. examples, benchmarks, tests if they cause a substantial compile overhead) into their own dedicated crates so that: They can be built in parallel with each other. Crates depending on the component itself are not waiting on the test/bench/example build in order for their test build to begin. A potentially smaller set of our crates need to be rebuilt -- and a smaller set of possible configurations exist meaning fewer items to add pressure to caches. ... Some rules of thumb for when / when not to do this: All rust examples should be put in examples/*. All rust benchmarks should be put in testing/separated/*. See the section below on how to set your benchmark up to avoid redundant compiles. Rust tests which brings in heavyweight dependencies should be evaluated on an ad-hoc basis. If you're concerned, measure how long compilation takes with/without, and consider how many crates depend on the crate where the test lives (e.g. a slow test in support/foo might be far worse than one in a leaf crate), etc...","breadcrumbs":"Contributing » How to test Rust Components » Writing efficient tests » The Solution","id":"59","title":"The Solution"},"6":{"body":"Below are a few different queries you can use to find appropriate issues to work on. Feel free to reach out if you need any additional clarification before picking up an issue. good first issues - If you are a new contributor, search for issues labeled good-first-issue good second issues - Once you've got that first PR approved and you are looking for something a little more challenging, we are keeping a list of next-level issues. Search for the good-second-issue label. papercuts - A collection of smaller sized issues that may be a bit more advanced than a first or second issue. important, but not urgent - For more advanced contributors, we have a collection of issues that we consider important and would like to resolve sooner, but work isn't currently prioritized by the core team.","breadcrumbs":"Contributing » Finding issues","id":"6","title":"Finding issues"},"60":{"body":"To be clear, this is way more important for benchmarks (which always compile as release and have a costly link phase). Say you have a directory structure like the following: mycrate ├── src │ └── lib.rs | ... ├── benches │ ├── bench0.rs | ├── bench1.rs │ └── bench2.rs ├── tests │ ├── test0.rs | ├── test1.rs │ └── test2.rs └── ... When you run your integration tests or benchmarks, each of test0, test1, test2 or bench0, bench1, bench2 is compiled as it's own crate that runs the tests in question and exits. That means 3 benchmark executables are built on release settings, and 3 integration test executables. If you've ever tried to add a piece of shared utility code into your integration tests, only to have cargo (falsely) complain that it is dead code: this is why. Even if test0.rs and test2.rs both use the utility function, unless every test crate uses every shared utility, the crate that doesn't will complain. (Aside: This turns out to be an unintentional secondary benefit of this approach -- easier shared code among tests, without having to put a #![allow(dead_code)] in your utils.rs. We haven't hit that very much here, since we tend to stick to unit tests, but it came up in mentat several times, and is a frequent complaint people have) Anyway, the solution here is simple: Create a new crate. If you were working in components/mycrate and you want to add some integration tests or benchmarks, you should do cargo new --lib testing/separated/mycrate-test (or .../mycrate-bench). Delete .../mycrate-test/src/lib.rs. Yep, really, we're making a crate that only has integration tests/benchmarks (See the \"FAQ0\" section at the bottom of the file if you're getting incredulous). Now, add a src/tests.rs or a src/benches.rs. This file should contain mod foo; declarations for each submodule containing tests/benchmarks, if any. For benches, this is also where you set up the benchmark harness (refer to benchmark library docs for how). Now, for a test, add: into your Cargo.toml [[test]]\nname = \"mycrate-test\"\npath = \"src/tests.rs\" and for a benchmark, add: [[test]]\nname = \"mycrate-benches\"\npath = \"src/benches.rs\"\nharness = false Because we aren't using src/lib.rs, this is what declares which file is the root of the test/benchmark crate. Because there's only one target (unlike with tests/* / benches/* under default settings), this will compile more quickly. Additionally, src/tests.rs and src/benches.rs will behave like a normal crate, the only difference being that they don't produce a lib, and that they're triggered by cargo test/cargo run respectively.","breadcrumbs":"Contributing » How to test Rust Components » Writing efficient tests » Appendix: How to avoid redundant compiles for benchmarks and integration tests","id":"60","title":"Appendix: How to avoid redundant compiles for benchmarks and integration tests"},"61":{"body":"Instead of putting tests/benchmarks inside src, we could just delete the src dir outright, and place everything in tests/benches. Then, to get the same one-rebuild-per-file behavior that we'll get in src, we need to add autotests = false or autobenches = false to our Cargo.toml, adding a root tests/tests.rs (or benches/benches.rs) containing mod decls for all submodules, and finally by referencing that \"root\" in the Cargo.toml [[tests]] / [[benches]] list, exactly the same way we did for using src/*. This would work, and on the surface, using tests/*.rs and benches/*.rs seems more consistent, so it seems weird to use src/*.rs for these files. My reasoning is as follows: Almost universally, tests/*.rs, examples/*.rs, benches/*.rs, etc. are automatic. If you add a test into the tests folder, it will run without anything else. If we're going to set up one-build-per-{test,bench}suite as I described, this fundamentally cannot be true. In this paradigm, if you add a test file named blah.rs, you must add a mod blah it to the parent module. It seems both confusing and error-prone to use tests/*, but have it behave that way, however this is absolutely the normal behavior for files in src/*.rs -- When you add a file, you then need to add it to it's parent module, and this is something Rust programmers are pretty used to. (In fact, we even replicated this behavior (for no reason) in the places integration tests, and added the mod declarations to a \"controlling\" parent module -- It seems weird to be in an environment where this isn't required) So, that's why. This way, we make it way less likely that you add a test file to some directory, and have it get ignored because you didn't realize that in this one folder, you need to add a mod mytest into a neighboring tests.rs. Found a bug? Edit this page on GitHub.","breadcrumbs":"Contributing » How to test Rust Components » Writing efficient tests » FAQ0: Why put tests/benches in src/* instead of disabling autotests/autobenches","id":"61","title":"FAQ0: Why put tests/benches in src/* instead of disabling autotests/autobenches"},"62":{"body":"It can be quite tricky to debug what is going on with sql statement, especially once the sql gets complicated or many triggers are involved. The sql_support create provides some utilities to help. Note that these utilities are gated behind a debug-tools feature. The module provides docstrings, so you should read them before you start . This document describes how to use these capabilities and we'll use places as an example. First, we must enable the feature: --- a/components/places/Cargo.toml\n+++ b/components/places/Cargo.toml\n@@ -22,7 +22,7 @@ lazy_static = \"1.4\" url = { version = \"2.1\", features = [\"serde\"] } percent-encoding = \"2.1\" caseless = \"0.2\"\n-sql-support = { path = \"../support/sql\" }\n+sql-support = { path = \"../support/sql\", features=[\"debug-tools\"] } and we probably need to make the debug functions available: --- a/components/places/src/db/db.rs\n+++ b/components/places/src/db/db.rs\n@@ -108,6 +108,7 @@ impl ConnectionInitializer for PlacesInitializer { \"; conn.execute_batch(initial_pragmas)?; define_functions(conn, self.api_id)?;\n+ sql_support::debug_tools::define_debug_functions(conn)?; We now have a Rust function print_query() and a SQL function dbg() available. Let's say we were trying to debug a test such as test_bookmark_tombstone_auto_created. We might want to print the entire contents of a table, then instrument a query to check what the value of a query is. We might end up with a patch something like: index 28f19307..225dccbb 100644\n--- a/components/places/src/db/schema.rs\n+++ b/components/places/src/db/schema.rs\n@@ -666,7 +666,8 @@ mod tests { [], ) .expect(\"should insert regular bookmark folder\");\n- conn.execute(\"DELETE FROM moz_bookmarks WHERE guid = 'bookmarkguid'\", [])\n+ sql_support::debug_tools::print_query(&conn, \"select * from moz_bookmarks\").unwrap();\n+ conn.execute(\"DELETE FROM moz_bookmarks WHERE dbg('CHECKING GUID', guid) = 'bookmarkguid'\", []) .expect(\"should delete\"); // should have a tombstone. assert_eq!( There are 2 things of note: We used the print_query function to dump the entire moz_bookmarks table before executing the query. We instrumented the query to print the guid every time sqlite reads a row and compares it against a literal. The output of this test now looks something like: running 1 test\nquery: select * from moz_bookmarks\n+----+------+------+--------+----------+---------+---------------+---------------+--------------+------------+-------------------+\n| id | fk | type | parent | position | title | dateAdded | lastModified | guid | syncStatus | syncChangeCounter |\n+====+======+======+========+==========+=========+===============+===============+==============+============+===================+\n| 1 | null | 2 | null | 0 | root | 1686248350470 | 1686248350470 | root________ | 1 | 1 |\n+----+------+------+--------+----------+---------+---------------+---------------+--------------+------------+-------------------+\n| 2 | null | 2 | 1 | 0 | menu | 1686248350470 | 1686248350470 | menu________ | 1 | 1 |\n+----+------+------+--------+----------+---------+---------------+---------------+--------------+------------+-------------------+\n| 3 | null | 2 | 1 | 1 | toolbar | 1686248350470 | 1686248350470 | toolbar_____ | 1 | 1 |\n+----+------+------+--------+----------+---------+---------------+---------------+--------------+------------+-------------------+\n| 4 | null | 2 | 1 | 2 | unfiled | 1686248350470 | 1686248350470 | unfiled_____ | 1 | 1 |\n+----+------+------+--------+----------+---------+---------------+---------------+--------------+------------+-------------------+\n| 5 | null | 2 | 1 | 3 | mobile | 1686248350470 | 1686248350470 | mobile______ | 1 | 1 |\n+----+------+------+--------+----------+---------+---------------+---------------+--------------+------------+-------------------+\n| 6 | null | 3 | 1 | 0 | null | 1 | 1 | bookmarkguid | 2 | 1 |\n+----+------+------+--------+----------+---------+---------------+---------------+--------------+------------+-------------------+\ntest db::schema::tests::test_bookmark_tombstone_auto_created ... FAILED failures: ---- db::schema::tests::test_bookmark_tombstone_auto_created stdout ----\nCHECKING GUID root________\nCHECKING GUID menu________\nCHECKING GUID toolbar_____\nCHECKING GUID unfiled_____\nCHECKING GUID mobile______\nCHECKING GUID bookmarkguid It's unfortunate that the output of print_table() goes to the tty while the output of dbg goes to stderr, so you might find the output isn't quite intermingled as you would expect, but it's better than nothing! Found a bug? Edit this page on GitHub.","breadcrumbs":"Contributing » How to test Rust Components » How to debug SQL/sqlite » Debugging Sql","id":"62","title":"Debugging Sql"},"63":{"body":"This repository uses third-party code from a variety of sources, so we need to be mindful of how these dependencies will affect our consumers. Considerations include: General code quality. Licensing compatibility . Handling of security vulnerabilities. The potential for supply-chain compromise . We're still evolving our policies in this area, but these are the guidelines we've developed so far.","breadcrumbs":"Contributing » Dependency management » Dependency Management Guidelines","id":"63","title":"Dependency Management Guidelines"},"64":{"body":"Unlike Firefox , we do not vendor third-party source code directly into the repository. Instead we rely on Cargo.lock and its hash validation to ensure that each build uses an identical copy of all third-party crates. These are the measures we use for ongoing maintence of our existing dependencies: Check Cargo.lock into the repository. Generate built artifacts using the --locked flag to cargo build, as an additional assurance that the existing Cargo.lock will be respected. Regularly run cargo-audit in CI to alert us to security problems in our dependencies. It runs on every PR, and once per hour on the main branch Use a home-grown tool to generate a summary of dependency licenses and to check them for compatibility with MPL-2.0. Check these summaries into the repository and have CI alert on unexpected changes, to guard against pulling in new versions of a dependency under a different license. Adding a new dependency, whether we like it or not, is a big deal - that dependency and everything it brings with it will become part of Firefox-branded products that we ship to end users. We try to balance this responsibility against the many benefits of using existing code, as follows: In general, be conservative in adding new third-party dependencies. For trivial functionality, consider just writing it yourself. Remember the cautionary tale of left-pad . Check if we already have a crate in our dependency tree that can provide the needed functionality. Prefer crates that have a a high level of due-dilligence already applied, such as: Crates that are already vendored into Firefox . Crates from rust-lang-nursery . Crates that appear to be widely used in the rust community. Check that it is clearly licensed and is MPL-2.0 compatible . Take the time to investigate the crate's source and ensure it is suitably high-quality. Be especially wary of uses of unsafe, or of code that is unusually resource-intensive to build. Dev dependencies do not require as much scrutiny as dependencies that will ship in consuming applications, but should still be given some thought. There is still the potential for supply-chain compromise with dev dependencies! As part of the PR that introduces the new dependency: Regenerate dependency summary files using the regenerate_dependency_summaries.sh . Explicitly describe your consideration of the above points. Updating to new versions of existing dependencies is a normal part of software development and is not accompanied by any particular ceremony.","breadcrumbs":"Contributing » Dependency management » Rust Code","id":"64","title":"Rust Code"},"65":{"body":"We currently depend only on the following Kotlin dependencies: JNA protobuf-gradle-plugin We currently depend on the following developer dependencies in the Kotlin codebase, but they do not get included in built distribution files: detekt ktlint No additional Kotlin dependencies should be added to the project unless absolutely necessary.","breadcrumbs":"Contributing » Dependency management » Android/Kotlin Code","id":"65","title":"Android/Kotlin Code"},"66":{"body":"We currently do not depend on any Swift dependencies. And no Swift dependencies should be added to the project unless absolutely necessary.","breadcrumbs":"Contributing » Dependency management » iOS/Swift Code","id":"66","title":"iOS/Swift Code"},"67":{"body":"We currently depend on local builds of the following system dependencies: NSS and NSPR SQLCipher Protobuf No additional system dependencies should be added to the project unless absolutely necessary. Found a bug? Edit this page on GitHub.","breadcrumbs":"Contributing » Dependency management » Other Code","id":"67","title":"Other Code"},"68":{"body":"Each component in the Application Services repository has three parts (the Rust code, the Kotlin wrapper, and the Swift wrapper) so there are quite a few moving parts involved in adding a new component. This is a rapid-fire list of all the things you'll need to do if adding a new component from scratch.","breadcrumbs":"Contributing » How to add a new component » Adding a new component to Application Services","id":"68","title":"Adding a new component to Application Services"},"69":{"body":"Your component should live under ./components in this repo. Use cargo new --lib ./components/to create a new library crate, and please try to avoid using hyphens in the crate name. See the Guide to Building a Rust Component for general advice on designing and structuring the actual Rust code, and follow the Dependency Management Guidelines if your crate introduces any new dependencies. Use UniFFI to define how your crate's API will get exposed to foreign-language bindings. By convention, put the interface definition file at ./components//.udl. Use the builtin-bindgen feature of UniFFI to simplify the build process, by putting the following in your Cargo.toml: [build-dependencies]\nuniffi_build = { version = \"\", features=[\"builtin-bindgen\"] } Include your new crate in the application-services workspace, by adding it to the members and default-members lists in the Cargo.toml at the root of the repository. In order to be published to consumers, your crate must be included in the \"megazord\" crate for each target platform: For Android, add it as a dependency in ./megazords/full/Cargo.toml and add a pub use to ./megazords/full/src/lib.rs. For iOS, add it as a dependency in ./megazords/ios-rust/rust/Cargo.toml and add a pub use to ./megazords/ios-rust/src/lib.rs. Run cargo check -p in the repository root to confirm that things are configured properly. This will also have the side-effect of updating Cargo.lock to contain your new crate and its dependencies.","breadcrumbs":"Contributing » How to add a new component » The Rust Code","id":"69","title":"The Rust Code"},"7":{"body":"Patches should be submitted as pull requests (PRs). When submitting PRs, We expect external contributors to push patches to a fork of application-services . For more information about submitting PRs from forks, read GitHub's guide . Before submitting a PR: Your patch should include new tests that cover your changes, or be accompanied by explanation for why it doesn't need any. It is your and your reviewer's responsibility to ensure your patch includes adequate tests. Consult the testing guide for some tips on writing effective tests. Your code should pass all the automated tests before you submit your PR for review. Before pushing your changes, run ./automation/tests.py changes. The script will calculate which components were changed and run test suites, linters and formatters against those components. Because the script runs a limited set of tests, the script should execute in a fairly reasonable amount of time. If you have modified any Swift code, also run swiftformat --swiftversion 5 on the modified code. Your patch should include a changelog entry in CHANGELOG.md or an explanation of why it does not need one. Any breaking changes to Swift or Kotlin binding APIs should be noted explicitly. If your patch adds new dependencies, they must follow our dependency management guidelines . Please include a summary of the due diligence applied in selecting new dependencies. After you open a PR, our Continuous Integration system will run a full test suite. It's possible that this step will result in errors not caught with the script so make sure to check the results. \"Work in progress\" pull requests are welcome, but should be clearly labeled as such and should not be merged until all tests pass and the code has been reviewed. You can label pull requests as \"Work in progress\" by using the Github PR UI to indicate this PR is a draft ( learn more about draft PRs ). When submitting a PR: You agree to license your code under the project's open source license ( MPL 2.0 ). Base your branch off the current main branch. Add both your code and new tests if relevant. Please do not include merge commits in pull requests; include only commits with the new relevant code. We encourage you to GPG sign your commits .","breadcrumbs":"Contributing » Sending Pull Requests","id":"7","title":"Sending Pull Requests"},"70":{"body":"Make a ./components//android subdirectory to contain Kotlin- and Android-specific code. This directory will contain a gradle project for building your Kotlin bindings. Copy the build.gradle file from ./components/crashtest/android/ into your own component's directory, and edit it to replace the references to crashtest.udl with your own component's .udl file. Create a file ./components//uniffi.toml with the following contents: [bindings.kotlin]\npackage_name = \"mozilla.appservices.\"\ncdylib_name = \"megazord\" Create a file ./components//android/src/main/AndroidManifest.xml with the following contents: \" /> In the root of the repository, edit .buildconfig-android.ymlto add your component's metadata. This will cause it to be included in the gradle workspace and in our build and publish pipeline. Check whether it builds correctly by running: ./gradlew :assembleDebug You can include hand-written Kotlin code alongside the automatically generated bindings, by placing `.kt`` files in a directory named: ./android/src/test/java/mozilla/appservices// You can write Kotlin-level tests that consume your component's API, by placing `.kt`` files in a directory named: ./android/src/test/java/mozilla/appservices//. So you would end up with a directory structure something like this: components// Cargo.toml uniffi.toml src/ Rust code here. android/ build.gradle src/ main/ AndroidManifest.xml java/mozilla/appservices// Hand-written Kotlin code here. test/java/mozilla/appservices// Kotlin test-cases here. Run your component's Kotlin tests with ./gradlew :test to confirm that this is all working correctly.","breadcrumbs":"Contributing » How to add a new component » The Kotlin Bindings","id":"70","title":"The Kotlin Bindings"},"71":{"body":"","breadcrumbs":"Contributing » How to add a new component » The Swift Bindings","id":"71","title":"The Swift Bindings"},"72":{"body":"Make a ./components//ios subdirectory to contain Swift- and iOS-specific code. The UniFFI-generated swift bindings will be written to a subdirectory named Generated. You can include hand-written Swift code alongside the automatically generated bindings, by placing .swift files in a directory named: ./ios//. So you would end up with a directory structure something like this: components// Cargo.toml uniffi.toml src/ Rust code here. ios/ / Hand-written Swift code here. Generated/ Generated Swift code will be written into this directory.","breadcrumbs":"Contributing » How to add a new component » Creating the directory structure","id":"72","title":"Creating the directory structure"},"73":{"body":"For more information on our how we ship components using the Swift Package Manager, check the ADR that introduced the Swift Package Manager You will need to do the following steps to include the component in the megazord: Update its uniffi.toml to include the following settings: [bindings.swift]\nffi_module_name = \"MozillaRustComponents\"\nffi_module_filename = \"FFI\" Add the component as a dependency to the Cargo.toml in megazords/ios-rust/ Add a pub use declaration for the component in megazords/ios-rust/src/lib.rs Add logic to the megazords/ios-rust/build-xcframework.sh to copy or generate its header file into the build Add an #import for its header file to megazords/ios-rust/MozillaRustComponents.h Add your component into the iOS \"megazord\" through the Xcode project, which can only really by done using the Xcode application, which can only really be done if you're on a Mac. Open megazords/ios-rust/MozillaTestServices/MozillaTestServices.xcodeproj in Xcode. In the Project navigator, add a new Group for your new component, pointing to the ./ios/ directory you created above. Add the following entries to the Group: The .udl file for you component, from ../src/.udl. Any hand-written .swift files for your component Make sure that the \"Copy items if needed\" option is unchecked , and that nothing is checked in the \"Add to targets\" list. The result should look something like this: Screenshot of Xcode Project Navigator Click on the top-level \"MozillaTestServices\" project in the navigator, then go to \"Build Phases\". Double-check that .udl does not appear in the \"Copy Bundle Resources\" section. Add .udl to the list of \"Compile Sources\". This will trigger an Xcode Build Rule that generates the Swift bindings automatically. Also include any hand-written .swift files in this list. Finally, in the Project navigator, add a sub-group named \"Generated\", pointing to the ./Generated/ subdirectory, and containing entries for the files generated by UniFFI: * .swift * FFI.h Make sure that \"Copy items if needed\" is unchecked, and that nothing is checked in \"Add to targets\". Double-check that .swift does not appear in the \"Compile Sources\" section. The result should look something like this: Screenshot of Xcode Compile Sources list Build the project in Xcode to check whether that all worked correctly. To add Swift tests for your component API, create them in a file under megazords/ios-rust/MozillaTestServicesTests/. Use this syntax to import your component's bindings from the compiled megazord: @testable import MozillaTestServices In Xcode, navigate to the MozillaTestServicesTests Group and add your new test file as an entry. Select the corresponding target, click on \"Build Phases\", and add your test file to the list of \"Compile Sources\". The result should look something like this: Screenshot of Xcode Test Setup Use the Xcode Test Navigator to run your tests and check whether they're passing.","breadcrumbs":"Contributing » How to add a new component » Adding your component to the Swift Package Manager Megazord","id":"73","title":"Adding your component to the Swift Package Manager Megazord"},"74":{"body":"The Swift source code and generated UniFFI bindings are distributed to consumers (eg: Firefox iOS) through rust-components-swift . A nightly taskcluster job prepares the rust-component-swift packages from the source code in the application-services repository. To distribute your component with rust-component-swift, add the following to the taskcluster script in taskcluster/scripts/build-and-test-swift.py: Add the path to the .udl file to BINDINGS_UDL_PATHS Optionally also to FOCUS_UDL_PATHS if your component is also targeting Firefox Focus Add the path to the directory containing any hand-written swift code to SOURCE_TO_COPY Optionally also to FOCUS_SOURCE_TO_COPY if your component is also targeting Firefox Focus Your component should now automatically get included in the next rust-component-swift nightly release. Found a bug? Edit this page on GitHub.","breadcrumbs":"Contributing » How to add a new component » Distribute your component with rust-components-swift","id":"74","title":"Distribute your component with rust-components-swift"},"75":{"body":"This is a guide to creating a new Syncable Rust Component like many of the components in this repo. If you are looking for information how to build (ie,compile, etc) the existing components, you are looking for our build documentation Welcome! It's great that you want to build a Rust Component - this guide should help get you started. It documents some nomenclature, best-practices and other tips and tricks to get you started. This document is just for general guidance - every component will be different and we are still learning how to make these components. Please update this document with these learnings. To repeat with emphasis - please consider this a living document .","breadcrumbs":"Contributing » How to add a new component » How to build a new syncable component » Guide to Building a Syncable Rust Component","id":"75","title":"Guide to Building a Syncable Rust Component"},"76":{"body":"We think components should be structured as described here.","breadcrumbs":"Contributing » How to add a new component » How to build a new syncable component » General design and structure of the component","id":"76","title":"General design and structure of the component"},"77":{"body":"Think of building a \"library\", not a \"framework\" - the application should be in control and calling functions exposed by your component, not providing functions for your component to call.","breadcrumbs":"Contributing » How to add a new component » How to build a new syncable component » We build libraries, not frameworks","id":"77","title":"We build libraries, not frameworks"},"78":{"body":"[Note that some of the older components use the term \"store\" differently; we should rename them! In Places, it's called an \"API\"; in Logins an \"engine\". See webext-storage for a more recent component that uses the term \"Store\" as we think it should be used.] The \"Store\" is the entry-point for the consuming application - it provides the core functionality exposed by the component and manages your databases and other singletons. The responsibilities of the \"Store\" will include things like creating the DB if it doesn't exist, doing schema upgrades etc. The functionality exposed by the \"Store\" will depend on the complexity of the API being exposed. For example, for webext-storage, where there are only a handful of simple public functions, it just directly exposes all the functionality of the component. However, for Places, which has a much more complex API, the (logical) Store instead supplies \"Connection\" instances which expose the actual functionality.","breadcrumbs":"Contributing » How to add a new component » How to build a new syncable component » The \"store\" is the \"entry-point\"","id":"78","title":"The \"store\" is the \"entry-point\""},"79":{"body":"We prefer sqlite instead of (say) JSON files or RKV. Always put sqlite into WAL mode, then have exactly 1 writer connection and as many reader connections you need - which will depend on your use-case - for example, webext_storage has 1 reader, while places has many. (Note that places has 2 writers (one for sync, one for the api), but we believe this was a mistake and should have been able to make things work better with exactly 1 shared between sync and the api) We typically have a \"DB\" abstraction which manages the database itself - the logic for handling schema upgrades etc and enforcing the \"only 1 writer\" rule is done by this. However, this is just a convenience - the DB abstractions aren't really passed around - we just pass raw connections (or transactions) around. For example, if there's a utility function that reads from the DB, it will just have a Rusqlite connection passed. (Again, older components don't really do this well, but webext-storage does) We try and leverage rust to ensure transactions are enforced at the correct boundaries - for example, functions which write data but which must be done as part of a transaction will accept a Rusqlite Transaction reference as the param, whereas something that only reads the Db will accept a Rusqlite Connection - note that because Transaction supports Deref, you can pass a &Transaction wherever a &Connection is needed - but not vice-versa.","breadcrumbs":"Contributing » How to add a new component » How to build a new syncable component » Using sqlite","id":"79","title":"Using sqlite"},"8":{"body":"This project is production Mozilla code and subject to our engineering practices and quality standards . Every patch must be peer reviewed by a member of the Application Services team. Found a bug? Edit this page on GitHub.","breadcrumbs":"Contributing » Code Review","id":"8","title":"Code Review"},"80":{"body":"You are likely to have a table just for key/value metadata, and this table will be used by sync (and possibly other parts of the component) to track the sync IDs, lastModified timestamps etc.","breadcrumbs":"Contributing » How to add a new component » How to build a new syncable component » Meta-data","id":"80","title":"Meta-data"},"81":{"body":"The schemas are stored in the tree in .sql files and pulled into the source at build time via include_str!. Depending on the complexity of your component, there may be a need for different Connections to have different Sql (for example, it may be that only your 'write' connection requires the sql to define triggers or temp tables, so these might be in their own file.) Because requirements evolve, there will be a need to support schema upgrades. This is done by way of sqlite's PRAGMA user_version - which can be thought of as simple metadata for the database itself. In short, immediately after opening the database for the first time, we check this version and if it's less than expected we perform the schema upgrades necessary, then re-write the version to the new version. This is easier to read than explain, so read the upgrade() function in the Places schema code You will need to be a big careful here because schema upgrades are going to block the calling application immediately after they upgrade to a new version, so if your schema change requires a table scan of a massive table, you are going to have a bad time. Apart from that though, you are largely free to do whatever sqlite lets you do! Note that most of our components have very similar schema and database management code - these are screaming out to be refactored so common logic can be shared. Please be brave and have a go at this!","breadcrumbs":"Contributing » How to add a new component » How to build a new syncable component » Schema management","id":"81","title":"Schema management"},"82":{"body":"We tend to like triggers for encompasing application logic - for example, if updating one row means a row in a different table should be updated based on that data, we'd tend to prefer an, eg, AFTER UPDATE trigger than having our code manually implement the logic. However, you should take care here, because functionality based on triggers is difficult to debug (eg, logging in a trigger is difficult) and the functionality can be difficult to locate (eg, users unfamiliar with the component may wonder why they can't find certain functionity in the rust code and may not consider looking in the sqlite triggers) You should also be careful when creating triggers on persistent main tables. For example, bumping the change counter isn't a good use for a trigger, because it'll run for all changes on the table—including those made by Sync. This means Sync will end up tracking its own changes, and getting into infinite syncing loops. Triggers on temporary tables, or ones that are used for bookkeeping where the caller doesn't matter, like bumping the foreign reference count for a URL, are generally okay.","breadcrumbs":"Contributing » How to add a new component » How to build a new syncable component » Triggers","id":"82","title":"Triggers"},"83":{"body":"We prefer flatter module hierarchies where possible. For example, in Places we ended up with sync_history and sync_bookmarks sub-modules rather than a sync submodule itself with history and bookmarks. Note that the raw connections are never exposed to consumers - for example, they will tend to be stored as private fields in, eg, a Mutex.","breadcrumbs":"Contributing » How to add a new component » How to build a new syncable component » General structure of the rust code","id":"83","title":"General structure of the rust code"},"84":{"body":"The traits you need to implement to sync aren't directly covered here. All meta-data related to sync must be stored in the same database as the data itself - often in a meta table. All logic for knowing which records need to be sync must be part of the application logic, and will often be implemented using triggers. It's quite common for components to use a \"change counter\" strategy, which can be summarized as: Every table which defines the \"top level\" items being synced will have a column called something like 'sync_change_counter' - the app will probably track this counter manually instead of using a trigger, because sync itself will need different behavior when it updates the records. At sync time, items with a non-zero change counter are candidates for syncing. As the sync starts, for each item, the current value of the change counter is remembered. At the end of the sync, the counter is decremented by this value. Thus, items which were changed between the time the sync started and completed will be left with a non-zero change counter at the end of the sync.","breadcrumbs":"Contributing » How to add a new component » How to build a new syncable component » Syncing","id":"84","title":"Syncing"},"85":{"body":"This section is stolen from this document","breadcrumbs":"Contributing » How to add a new component » How to build a new syncable component » Syncing FAQs","id":"85","title":"Syncing FAQs"},"86":{"body":"Both guids, both used to identify when the data in the server has changed radically underneath us (eg, when looking at lastModified is no longer a sane thing to do.) The \"global sync ID\" changing means that every collection needs to be assumed as having changed radically, whereas just the \"collection sync ID\" changing means just that one collection. These global IDs are most likely to change on a node reassignment (which should be rare now with durable storage), a password reset, etc. An example of when the collection ID will change is a \"bookmarks restore\" - handling an old version of a database re-appearing is why we store these IDs in the database itself.","breadcrumbs":"Contributing » How to add a new component » How to build a new syncable component » What’s the global sync ID and the collection sync ID?","id":"86","title":"What’s the global sync ID and the collection sync ID?"},"87":{"body":"They are all used to track the guids above. It’s vitally important we know when these guids change. StoreSyncAssociation is a simple enum which reflects the state a sync engine can be in - either Disconnected (ie, we have no idea what the GUIDs are) or Connected where we know what we think the IDs are (but the server may or may not match with this) These GUIDs will typically be stored in the DB in the metadata table.","breadcrumbs":"Contributing » How to add a new component » How to build a new syncable component » What’s get_sync_assoc, why is it important? What is StoreSyncAssociation?","id":"87","title":"What’s get_sync_assoc, why is it important? What is StoreSyncAssociation?"},"88":{"body":"apply_incoming is where any records incoming from the server (ie, possibly all records on the server if this is a first-sync, records with a timestamp later than our last sync otherwise) are processed. sync_finished is where we've done all the sync work other than uploading new changes to the server.","breadcrumbs":"Contributing » How to add a new component » How to build a new syncable component » what is apply_incoming versus sync_finished","id":"88","title":"what is apply_incoming versus sync_finished"},"89":{"body":"Reset means “I don’t know what’s on the server - I need to reconcile everything there with everything I have”. IOW, a “first sync” Wipe means literally “wipe all server data”","breadcrumbs":"Contributing » How to add a new component » How to build a new syncable component » What's the diff between reset and wipe?","id":"89","title":"What's the diff between reset and wipe?"},"9":{"body":"When working on Application Services, it's important to set up your environment for building the Rust code and the Android or iOS code needed by the application.","breadcrumbs":"Contributing » Building » Building Application Services","id":"9","title":"Building Application Services"},"90":{"body":"You will need an FFI or some other way of exposing stuff to your consumers. We use a tool called UniFFI to automatically generate FFI bindings from the Rust code. If UniFFI doesn't work for you, then you'll need to hand-write the FFI layer. Here are some earlier blog posts on the topic which might be helpful: Building and Deploying a Rust library on Android Building and Deploying a Rust library on iOS Blog post re: lessons in binding to Rust code from iOS The above are likely to be superseded by uniffi docs, but for now, good luck! Found a bug? Edit this page on GitHub.","breadcrumbs":"Contributing » How to add a new component » How to build a new syncable component » Exposing to consumers","id":"90","title":"Exposing to consumers"},"91":{"body":"All names in this project should adhere to the guidelines outlined in this document.","breadcrumbs":"Contributing » How to add a new component » Naming Conventions » Naming Conventions","id":"91","title":"Naming Conventions"},"92":{"body":"TL;DR: do what Rust's builtin warnings and clippy lints tell you (and CI will fail if there are any unresolved warnings or clippy lints).","breadcrumbs":"Contributing » How to add a new component » Naming Conventions » Rust Code","id":"92","title":"Rust Code"},"93":{"body":"All variable names, function names, module names, and macros in Rust code should follow typical snake_case conventions. All Rust types, traits, structs, and enum variants must follow UpperCamelCase. Static and constant variables should be written in SCREAMING_SNAKE_CASE. s For more in-depth Rust conventions, see the Rust Style Guide .","breadcrumbs":"Contributing » How to add a new component » Naming Conventions » Overview","id":"93","title":"Overview"},"94":{"body":"fn sync15_passwords_get_all()\nstruct PushConfiguration{...}\nconst COMMON_SQL","breadcrumbs":"Contributing » How to add a new component » Naming Conventions » Examples:","id":"94","title":"Examples:"},"95":{"body":"","breadcrumbs":"Contributing » How to add a new component » Naming Conventions » Swift Code","id":"95","title":"Swift Code"},"96":{"body":"Names of types and protocols are UpperCamelCase. All other uses are lowerCamelCase. For more in-depth Swift conventions, check out the Swift API Design Guidelines .","breadcrumbs":"Contributing » How to add a new component » Naming Conventions » Overview","id":"96","title":"Overview"},"97":{"body":"enum CheckChildren{...}\nfunc checkTree()\npublic var syncKey: String","breadcrumbs":"Contributing » How to add a new component » Naming Conventions » Examples:","id":"97","title":"Examples:"},"98":{"body":"If a source file contains only a top-level class, the source file should reflect the case-sensitive name of the class plus the .kt extension. Otherwise, if the source contains multiple top-level declarations, choose a name that describes the contents of the file, apply UpperCamelCase and append .kt extension.","breadcrumbs":"Contributing » How to add a new component » Naming Conventions » Kotlin Code","id":"98","title":"Kotlin Code"},"99":{"body":"Names of packages are always lower case and do not include underscores. Using multi-word names should be avoided. However, if used, they should be concatenated or use lowerCamelCase. Names of classes and objects use UpperCamelCase. Names of functions, properties, and local variables use lowerCamelCase. For more in-depth Kotlin Conventions, see the Kotlin Style Guide .","breadcrumbs":"Contributing » How to add a new component » Naming Conventions » Overview","id":"99","title":"Overview"}},"length":287,"save":true},"fields":["title","body","breadcrumbs"],"index":{"body":{"root":{"0":{".":{"0":{".":{"1":{"df":1,"docs":{"22":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"1":{".":{"0":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":1,"docs":{"283":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"123":{"tf":1.4142135623730951}}},"2":{"7":{".":{"0":{"df":1,"docs":{"20":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"62":{"tf":1.0}}},"5":{"1":{".":{"0":{"df":1,"docs":{"20":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"0":{"0":{"0":{"df":1,"docs":{"134":{"tf":1.0}}},"1":{"df":1,"docs":{"134":{"tf":1.0}}},"2":{"df":1,"docs":{"134":{"tf":1.0}}},"3":{"df":1,"docs":{"134":{"tf":1.0}}},"4":{"df":1,"docs":{"134":{"tf":1.0}}},"5":{"df":1,"docs":{"134":{"tf":1.0}}},"7":{"df":1,"docs":{"134":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"1":{"df":1,"docs":{"202":{"tf":1.0}}},"6":{"df":3,"docs":{"139":{"tf":1.0},"148":{"tf":1.0},"202":{"tf":1.0}}},"7":{"df":2,"docs":{"160":{"tf":1.0},"268":{"tf":1.4142135623730951}}},"8":{"df":3,"docs":{"148":{"tf":1.0},"176":{"tf":1.0},"268":{"tf":1.4142135623730951}}},"df":2,"docs":{"214":{"tf":1.0},"62":{"tf":1.7320508075688772}},"f":{"8":{"c":{"0":{"3":{"7":{"6":{"3":{"7":{"df":0,"docs":{},"f":{"9":{"df":0,"docs":{},"e":{"b":{"3":{"df":0,"docs":{},"e":{".":{"c":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"\"":{"&":{"df":0,"docs":{},"g":{"df":0,"docs":{},"t":{";":{"&":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{";":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"k":{"df":1,"docs":{"283":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"1":{",":{"4":{"df":1,"docs":{"269":{"tf":1.4142135623730951}}},"7":{"df":1,"docs":{"268":{"tf":1.4142135623730951}}},"8":{"df":1,"docs":{"268":{"tf":1.4142135623730951}}},"df":0,"docs":{}},".":{"4":{"df":1,"docs":{"62":{"tf":1.0}}},"df":0,"docs":{}},"0":{",":{"0":{"0":{"0":{"df":5,"docs":{"204":{"tf":1.0},"214":{"tf":1.7320508075688772},"215":{"tf":1.0},"216":{"tf":1.7320508075688772},"217":{"tf":2.23606797749979}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"0":{",":{"0":{"0":{"0":{"df":2,"docs":{"215":{"tf":1.0},"216":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"0":{"0":{"df":1,"docs":{"214":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"6":{"4":{"4":{"df":3,"docs":{"268":{"tf":1.0},"269":{"tf":1.0},"62":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"268":{"tf":1.0}}},"8":{",":{"6":{"df":1,"docs":{"62":{"tf":1.0}}},"7":{"df":1,"docs":{"62":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"9":{"7":{"df":0,"docs":{},"f":{"8":{"df":0,"docs":{},"e":{"9":{"2":{"a":{"0":{"1":{"df":0,"docs":{},"e":{"3":{"c":{"df":0,"docs":{},"f":{".":{"c":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"\"":{"&":{"df":0,"docs":{},"g":{"df":0,"docs":{},"t":{";":{"&":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{";":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"k":{"df":1,"docs":{"283":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":1,"docs":{"20":{"tf":1.0}},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"204":{"tf":1.0}}}}},"1":{",":{"0":{"0":{"0":{"df":1,"docs":{"204":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"4":{".":{"0":{"a":{"1":{"df":1,"docs":{"269":{"tf":1.0}}},"df":0,"docs":{}},"df":1,"docs":{"269":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"8":{".":{"0":{"a":{"1":{"df":1,"docs":{"268":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"9":{".":{"0":{"a":{"1":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"204":{"tf":1.0}}},"2":{"3":{"df":1,"docs":{"117":{"tf":1.0}}},"df":1,"docs":{"186":{"tf":1.0}}},"3":{"2":{"8":{"5":{"a":{"df":0,"docs":{},"e":{"c":{"3":{"1":{"df":0,"docs":{},"f":{"a":{"2":{"4":{"3":{"df":0,"docs":{},"e":{".":{"c":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"\"":{"&":{"df":0,"docs":{},"g":{"df":0,"docs":{},"t":{";":{"df":0,"docs":{},"☰":{"c":{"df":0,"docs":{},"r":{"df":1,"docs":{"283":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"4":{"7":{"9":{"9":{"2":{"9":{"df":1,"docs":{"228":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"5":{",":{"0":{"0":{"0":{"df":1,"docs":{"204":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"6":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"6":{",":{"0":{"0":{"0":{"df":1,"docs":{"214":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"7":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}},"8":{"6":{"2":{"4":{"8":{"3":{"5":{"0":{"4":{"7":{"0":{"df":1,"docs":{"62":{"tf":3.1622776601683795}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":2,"docs":{"176":{"tf":1.0},"186":{"tf":1.0}}},"7":{"1":{"1":{"4":{"4":{"7":{"df":2,"docs":{"171":{"tf":1.0},"174":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":2,"docs":{"12":{"tf":1.4142135623730951},"139":{"tf":1.0}}},":":{"1":{"df":1,"docs":{"242":{"tf":1.0}}},"df":0,"docs":{}},"df":13,"docs":{"117":{"tf":1.0},"142":{"tf":1.0},"145":{"tf":1.0},"151":{"tf":1.4142135623730951},"158":{"tf":1.0},"196":{"tf":1.0},"20":{"tf":1.0},"22":{"tf":1.4142135623730951},"252":{"tf":1.7320508075688772},"268":{"tf":2.0},"276":{"tf":1.0},"62":{"tf":4.58257569495584},"79":{"tf":2.0}},"u":{"8":{"df":1,"docs":{"117":{"tf":1.0}}},"df":0,"docs":{}}},"2":{",":{"5":{"0":{"0":{"df":1,"docs":{"204":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},".":{"0":{"df":2,"docs":{"64":{"tf":1.4142135623730951},"7":{"tf":1.0}}},"1":{".":{"2":{"df":2,"docs":{"137":{"tf":1.0},"138":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":1,"docs":{"62":{"tf":1.4142135623730951}}},"5":{"df":1,"docs":{"204":{"tf":1.0}}},"df":0,"docs":{}},"0":{",":{"0":{"0":{"0":{"df":1,"docs":{"216":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"0":{"0":{"0":{"df":1,"docs":{"214":{"tf":1.0}}},"df":3,"docs":{"206":{"tf":1.0},"217":{"tf":1.0},"218":{"tf":1.0}}},"df":0,"docs":{}},"1":{"8":{"df":1,"docs":{"192":{"tf":1.0}}},"df":0,"docs":{}},"2":{"1":{"df":4,"docs":{"139":{"tf":1.0},"148":{"tf":1.0},"160":{"tf":1.0},"176":{"tf":1.0}}},"2":{"df":1,"docs":{"186":{"tf":1.0}}},"3":{"df":2,"docs":{"187":{"tf":1.0},"202":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"1":{".":{"4":{".":{"7":{"0":{"7":{"5":{"5":{"2":{"9":{"df":1,"docs":{"12":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"2":{",":{"7":{"df":1,"docs":{"62":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":1,"docs":{"160":{"tf":1.0}}},"5":{",":{"0":{"0":{"0":{"df":1,"docs":{"204":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},".":{"2":{".":{"9":{"5":{"1":{"9":{"6":{"5":{"3":{"df":1,"docs":{"12":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"6":{"df":1,"docs":{"280":{"tf":1.0}}},"df":2,"docs":{"204":{"tf":1.0},"37":{"tf":1.0}},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"204":{"tf":1.0}}}}},"6":{"2":{"8":{"df":1,"docs":{"149":{"tf":1.0}}},"df":0,"docs":{}},"df":1,"docs":{"13":{"tf":1.0}}},"8":{"_":{"df":1,"docs":{"268":{"tf":1.4142135623730951}}},"df":0,"docs":{},"f":{"1":{"9":{"3":{"0":{"7":{".":{".":{"2":{"2":{"5":{"d":{"c":{"c":{"b":{"b":{"df":1,"docs":{"62":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":14,"docs":{"142":{"tf":1.0},"146":{"tf":1.0},"149":{"tf":1.0},"151":{"tf":1.4142135623730951},"18":{"tf":1.0},"197":{"tf":1.0},"22":{"tf":1.0},"237":{"tf":1.0},"242":{"tf":1.0},"245":{"tf":1.0},"261":{"tf":1.0},"264":{"tf":1.4142135623730951},"62":{"tf":3.0},"79":{"tf":1.0}},"f":{"a":{"df":1,"docs":{"276":{"tf":1.0}}},"df":0,"docs":{}}},"3":{".":{"6":{"df":1,"docs":{"11":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"1":{"_":{"df":1,"docs":{"268":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"2":{",":{"8":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}},"3":{"df":1,"docs":{"176":{"tf":1.0}}},"7":{"6":{"8":{"df":1,"docs":{"264":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"116":{"tf":1.4142135623730951}}},"4":{",":{"6":{"df":1,"docs":{"268":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"6":{",":{"8":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"7":{"df":1,"docs":{"204":{"tf":1.0}}},"8":{"9":{"9":{"df":1,"docs":{"145":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"9":{"0":{"9":{"df":1,"docs":{"48":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"a":{"d":{"9":{"df":1,"docs":{"258":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":10,"docs":{"151":{"tf":1.4142135623730951},"198":{"tf":1.0},"199":{"tf":1.0},"213":{"tf":1.4142135623730951},"22":{"tf":1.0},"242":{"tf":1.0},"249":{"tf":1.4142135623730951},"274":{"tf":1.4142135623730951},"60":{"tf":1.4142135623730951},"62":{"tf":1.7320508075688772}},"r":{"d":{"df":2,"docs":{"121":{"tf":1.0},"201":{"tf":1.7320508075688772}}},"df":0,"docs":{}}},"4":{".":{"9":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"/":{"d":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"37":{"tf":2.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"0":{"0":{"df":1,"docs":{"204":{"tf":1.0}}},"df":0,"docs":{}},"1":{"0":{"1":{"df":1,"docs":{"139":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"2":{"df":1,"docs":{"37":{"tf":1.0}}},"5":{",":{"0":{"0":{"0":{"df":1,"docs":{"204":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"6":{"0":{"4":{"df":1,"docs":{"258":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"9":{"6":{"6":{"df":1,"docs":{"38":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"62":{"tf":1.0}}},"5":{",":{"0":{"0":{"0":{"df":1,"docs":{"204":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"0":{",":{"0":{"0":{"0":{"df":1,"docs":{"204":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"0":{"0":{"df":3,"docs":{"206":{"tf":1.0},"217":{"tf":1.0},"218":{"tf":1.0}}},"df":0,"docs":{}},"df":1,"docs":{"204":{"tf":1.0}},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"204":{"tf":1.0}}}}},"2":{"1":{"0":{"df":1,"docs":{"280":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"7":{"4":{"5":{"]":{"(":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{":":{"/":{"/":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"u":{"b":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"/":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"268":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"6":{"4":{"]":{"(":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{":":{"/":{"/":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"u":{"b":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"/":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"268":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"6":{"]":{"(":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{":":{"/":{"/":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"u":{"b":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"/":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"268":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"8":{"4":{"]":{"(":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{":":{"/":{"/":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"u":{"b":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"/":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"268":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":4,"docs":{"20":{"tf":1.0},"204":{"tf":1.7320508075688772},"62":{"tf":1.0},"7":{"tf":1.0}}},"6":{",":{"4":{"0":{"0":{"df":1,"docs":{"204":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},".":{"4":{"df":1,"docs":{"204":{"tf":1.0}}},"df":0,"docs":{}},"1":{"4":{"4":{"df":1,"docs":{"264":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"2":{"df":1,"docs":{"264":{"tf":1.0}}},"4":{"df":2,"docs":{"17":{"tf":2.0},"37":{"tf":1.0}}},"6":{"6":{",":{"7":{"df":1,"docs":{"62":{"tf":1.0}}},"8":{"df":1,"docs":{"62":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"7":{"df":2,"docs":{"215":{"tf":1.0},"217":{"tf":1.0}}},"df":3,"docs":{"204":{"tf":1.7320508075688772},"215":{"tf":1.0},"62":{"tf":1.0}}},"7":{"5":{"df":1,"docs":{"206":{"tf":1.0}},"m":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"218":{"tf":1.0}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"204":{"tf":1.0}}}},"~":{"8":{"0":{"df":1,"docs":{"216":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"0":{"b":{"0":{"c":{"2":{"5":{"9":{"a":{"a":{"2":{"df":1,"docs":{"258":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"f":{"2":{"c":{"0":{"7":{"a":{"1":{"a":{"8":{".":{".":{"0":{"6":{"6":{"8":{"8":{"df":0,"docs":{},"f":{"d":{"c":{"a":{"b":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"8":{"0":{"df":1,"docs":{"204":{"tf":1.0}}},"c":{"d":{"9":{"2":{"3":{"8":{"7":{"3":{".":{".":{"6":{"4":{"8":{"2":{"0":{"1":{"8":{"df":0,"docs":{},"e":{"0":{"df":1,"docs":{"269":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"116":{"tf":1.0}}},"9":{"0":{"df":2,"docs":{"206":{"tf":1.0},"218":{"tf":1.0}},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"204":{"tf":1.0}}}}},"5":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"204":{"tf":1.0}}}}},"6":{"df":1,"docs":{"216":{"tf":1.0}}},"7":{"df":1,"docs":{"204":{"tf":1.0}}},"8":{"df":1,"docs":{"216":{"tf":1.0}}},"9":{".":{"6":{"df":1,"docs":{"216":{"tf":1.0}}},"9":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"204":{"tf":1.0}}}}},"df":0,"docs":{}},"df":4,"docs":{"204":{"tf":1.0},"208":{"tf":1.0},"216":{"tf":1.0},"217":{"tf":1.0}},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"204":{"tf":1.0}}}}},"df":0,"docs":{}},"_":{"2":{"0":{"2":{"3":{"df":1,"docs":{"268":{"tf":2.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"_":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"_":{"_":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"_":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"(":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"_":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"_":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":1,"docs":{"37":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"v":{"a":{"_":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"s":{"_":{"_":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":1,"docs":{"117":{"tf":1.0}},"y":{"df":0,"docs":{},"y":{"df":0,"docs":{},"y":{"df":0,"docs":{},"i":{"df":1,"docs":{"268":{"tf":1.0}}}}}}},"a":{"/":{"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{".":{"df":0,"docs":{},"m":{"d":{"df":1,"docs":{"268":{"tf":2.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"a":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"/":{"c":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":1,"docs":{"62":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{},"s":{"df":0,"docs":{},"r":{"c":{"/":{"d":{"b":{"/":{"d":{"b":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"62":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"62":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":2,"docs":{"268":{"tf":2.0},"269":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}}}}}}}}},"1":{"df":1,"docs":{"154":{"tf":1.0}}},"2":{"df":2,"docs":{"152":{"tf":1.0},"155":{"tf":1.0}}},"3":{"df":1,"docs":{"156":{"tf":1.0}}},"7":{"c":{"d":{"df":1,"docs":{"258":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"a":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"h":{"6":{"4":{"df":2,"docs":{"17":{"tf":1.0},"37":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":6,"docs":{"220":{"tf":1.0},"221":{"tf":2.8284271247461903},"222":{"tf":1.7320508075688772},"223":{"tf":1.0},"224":{"tf":1.0},"37":{"tf":1.4142135623730951}}}},"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":6,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"162":{"tf":1.0},"178":{"tf":1.0},"234":{"tf":1.0},"274":{"tf":1.4142135623730951}}}},"o":{"df":0,"docs":{},"v":{"df":31,"docs":{"105":{"tf":1.0},"106":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.4142135623730951},"123":{"tf":1.0},"124":{"tf":1.0},"14":{"tf":1.0},"145":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.4142135623730951},"200":{"tf":1.4142135623730951},"204":{"tf":1.0},"214":{"tf":1.0},"217":{"tf":1.0},"22":{"tf":1.7320508075688772},"220":{"tf":1.0},"224":{"tf":1.4142135623730951},"227":{"tf":1.0},"228":{"tf":1.0},"235":{"tf":1.0},"242":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951},"263":{"tf":1.0},"264":{"tf":1.0},"268":{"tf":1.0},"274":{"tf":1.7320508075688772},"280":{"tf":1.0},"64":{"tf":1.0},"73":{"tf":1.0},"87":{"tf":1.0},"90":{"tf":1.0}}}},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":4,"docs":{"61":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0},"67":{"tf":1.0}}}}}},"t":{"df":0,"docs":{},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"103":{"tf":1.0},"229":{"tf":1.4142135623730951},"79":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"c":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"282":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"t":{"df":11,"docs":{"105":{"tf":1.4142135623730951},"106":{"tf":1.0},"115":{"tf":1.0},"139":{"tf":1.0},"148":{"tf":1.0},"160":{"tf":1.0},"202":{"tf":1.0},"231":{"tf":1.0},"274":{"tf":1.0},"37":{"tf":1.0},"79":{"tf":1.4142135623730951}}}},"s":{"df":0,"docs":{},"s":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"242":{"tf":1.0}}}}}}}},"df":13,"docs":{"114":{"tf":1.0},"126":{"tf":1.0},"127":{"tf":1.0},"145":{"tf":1.0},"187":{"tf":1.0},"188":{"tf":1.0},"245":{"tf":1.0},"254":{"tf":1.0},"255":{"tf":1.0},"274":{"tf":1.0},"277":{"tf":1.4142135623730951},"283":{"tf":1.7320508075688772},"37":{"tf":1.0}}}}},"i":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"175":{"tf":1.0}}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"d":{"df":3,"docs":{"104":{"tf":1.0},"268":{"tf":1.0},"282":{"tf":1.0}}},"df":0,"docs":{}}},"o":{"d":{"df":1,"docs":{"108":{"tf":1.0}}},"df":0,"docs":{}},"p":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":3,"docs":{"43":{"tf":1.0},"64":{"tf":1.0},"7":{"tf":1.0}}}}},"df":0,"docs":{}}},"r":{"d":{"df":1,"docs":{"25":{"tf":1.0}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"263":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{".":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"z":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":1,"docs":{"283":{"tf":1.0}}}}}}}}}}}}}}}}},"df":0,"docs":{}}}}}}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"c":{"a":{"df":0,"docs":{},"p":{"a":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"a":{"df":1,"docs":{"283":{"tf":1.0}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"283":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"a":{"df":1,"docs":{"283":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":7,"docs":{"0":{"tf":1.0},"245":{"tf":1.0},"254":{"tf":1.7320508075688772},"274":{"tf":1.0},"276":{"tf":2.23606797749979},"283":{"tf":2.23606797749979},"284":{"tf":1.0}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":1,"docs":{"242":{"tf":1.0}}}}}}}}}},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"145":{"tf":1.0}}}}},"df":2,"docs":{"39":{"tf":1.0},"40":{"tf":1.0}},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":1,"docs":{"167":{"tf":1.0}}}}}},"t":{"df":3,"docs":{"237":{"tf":1.0},"245":{"tf":1.0},"251":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":10,"docs":{"119":{"tf":1.0},"226":{"tf":1.0},"230":{"tf":1.0},"231":{"tf":1.0},"235":{"tf":1.0},"242":{"tf":1.0},"249":{"tf":1.7320508075688772},"250":{"tf":1.0},"272":{"tf":1.0},"273":{"tf":1.0}}}},"v":{"df":1,"docs":{"205":{"tf":1.0}}}},"u":{"a":{"df":0,"docs":{},"l":{"df":10,"docs":{"179":{"tf":1.0},"194":{"tf":1.0},"226":{"tf":1.0},"232":{"tf":1.0},"261":{"tf":1.4142135623730951},"279":{"tf":1.0},"51":{"tf":1.0},"58":{"tf":1.0},"69":{"tf":1.0},"78":{"tf":1.0}}}},"df":0,"docs":{}}}},"d":{"d":{"/":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"d":{"df":2,"docs":{"145":{"tf":1.0},"146":{"tf":1.0}}},"df":0,"docs":{}}}},"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"_":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"d":{"df":2,"docs":{"145":{"tf":2.0},"146":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":41,"docs":{"105":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.4142135623730951},"11":{"tf":1.4142135623730951},"113":{"tf":1.0},"119":{"tf":1.0},"12":{"tf":1.0},"120":{"tf":1.0},"123":{"tf":1.0},"124":{"tf":1.0},"13":{"tf":1.0},"145":{"tf":1.4142135623730951},"146":{"tf":1.0},"158":{"tf":1.0},"167":{"tf":1.4142135623730951},"184":{"tf":1.0},"20":{"tf":1.0},"201":{"tf":1.0},"22":{"tf":1.4142135623730951},"223":{"tf":1.0},"224":{"tf":1.0},"229":{"tf":1.4142135623730951},"249":{"tf":1.0},"268":{"tf":1.7320508075688772},"274":{"tf":1.4142135623730951},"281":{"tf":1.0},"284":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"51":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":2.23606797749979},"61":{"tf":2.8284271247461903},"69":{"tf":2.0},"7":{"tf":1.4142135623730951},"70":{"tf":1.0},"73":{"tf":3.7416573867739413},"74":{"tf":1.7320508075688772}},"i":{"df":0,"docs":{},"t":{"df":11,"docs":{"11":{"tf":1.0},"13":{"tf":1.0},"141":{"tf":1.0},"167":{"tf":1.0},"201":{"tf":1.0},"274":{"tf":1.4142135623730951},"43":{"tf":1.4142135623730951},"6":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"67":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":7,"docs":{"180":{"tf":1.0},"209":{"tf":1.0},"22":{"tf":1.0},"254":{"tf":1.0},"257":{"tf":1.0},"58":{"tf":1.0},"60":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":5,"docs":{"141":{"tf":1.0},"169":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"182":{"tf":1.0}}}}}}},"df":34,"docs":{"101":{"tf":1.0},"102":{"tf":1.4142135623730951},"104":{"tf":1.0},"105":{"tf":1.0},"108":{"tf":1.4142135623730951},"109":{"tf":1.7320508075688772},"11":{"tf":1.0},"121":{"tf":1.0},"123":{"tf":2.23606797749979},"145":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":1.0},"201":{"tf":1.0},"22":{"tf":1.7320508075688772},"23":{"tf":1.0},"249":{"tf":1.0},"264":{"tf":1.0},"277":{"tf":1.0},"280":{"tf":1.0},"282":{"tf":1.0},"31":{"tf":1.0},"32":{"tf":1.0},"36":{"tf":1.0},"41":{"tf":1.0},"57":{"tf":1.0},"59":{"tf":1.0},"61":{"tf":1.4142135623730951},"64":{"tf":1.4142135623730951},"65":{"tf":1.0},"66":{"tf":1.0},"67":{"tf":1.0},"68":{"tf":1.7320508075688772},"69":{"tf":1.0},"73":{"tf":1.0}},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":1,"docs":{"7":{"tf":1.0}}}}},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"91":{"tf":1.0}}}}},"j":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":4,"docs":{"16":{"tf":1.0},"25":{"tf":1.0},"277":{"tf":1.0},"37":{"tf":1.7320508075688772}}}}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"117":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"r":{"df":10,"docs":{"134":{"tf":2.8284271247461903},"137":{"tf":1.0},"138":{"tf":1.0},"149":{"tf":1.0},"200":{"tf":1.0},"201":{"tf":1.0},"23":{"tf":1.0},"251":{"tf":1.0},"32":{"tf":1.0},"73":{"tf":1.0}}},"v":{"a":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"6":{"tf":1.4142135623730951}}},"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"g":{"df":2,"docs":{"12":{"tf":1.0},"220":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"107":{"tf":1.0}}}}}}},"i":{"c":{"df":2,"docs":{"122":{"tf":1.4142135623730951},"69":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"157":{"tf":1.0},"268":{"tf":1.0},"63":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"220":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"r":{"a":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"105":{"tf":1.0},"106":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"g":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":6,"docs":{"11":{"tf":1.0},"157":{"tf":1.0},"158":{"tf":1.0},"242":{"tf":1.0},"245":{"tf":1.0},"79":{"tf":1.0}},"s":{"df":0,"docs":{},"t":{"df":25,"docs":{"15":{"tf":1.4142135623730951},"151":{"tf":1.4142135623730951},"152":{"tf":1.4142135623730951},"155":{"tf":1.0},"156":{"tf":1.0},"16":{"tf":1.0},"17":{"tf":1.0},"194":{"tf":1.0},"23":{"tf":1.4142135623730951},"268":{"tf":1.4142135623730951},"269":{"tf":1.0},"274":{"tf":1.7320508075688772},"282":{"tf":1.0},"32":{"tf":1.0},"37":{"tf":1.0},"48":{"tf":1.0},"49":{"tf":1.0},"51":{"tf":1.0},"52":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"62":{"tf":1.0},"64":{"tf":1.4142135623730951},"7":{"tf":1.0}}}}}}},"df":0,"docs":{},"o":{"df":1,"docs":{"213":{"tf":1.0}}},"p":{"df":1,"docs":{"12":{"tf":1.0}}},"r":{"df":0,"docs":{},"e":{"df":2,"docs":{"232":{"tf":1.0},"7":{"tf":1.0}}}}},"h":{"df":0,"docs":{},"e":{"a":{"d":{"df":1,"docs":{"104":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"m":{"df":4,"docs":{"102":{"tf":1.0},"103":{"tf":1.0},"104":{"tf":1.0},"116":{"tf":1.0}}}},"k":{"a":{"df":1,"docs":{"260":{"tf":1.0}}},"df":0,"docs":{}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"64":{"tf":1.4142135623730951}}}}},"g":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"m":{"df":1,"docs":{"282":{"tf":1.0}}}}}}}}},"i":{"a":{"df":1,"docs":{"11":{"tf":1.0}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"df":3,"docs":{"171":{"tf":1.0},"172":{"tf":1.0},"274":{"tf":1.0}}}}},"l":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"279":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"(":{"d":{"df":0,"docs":{},"e":{"a":{"d":{"_":{"c":{"df":0,"docs":{},"o":{"d":{"df":1,"docs":{"60":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":15,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"145":{"tf":1.0},"157":{"tf":1.0},"158":{"tf":1.0},"183":{"tf":1.0},"190":{"tf":1.4142135623730951},"198":{"tf":1.0},"199":{"tf":1.0},"229":{"tf":1.0},"233":{"tf":1.0},"242":{"tf":1.0},"255":{"tf":1.0},"273":{"tf":1.0},"39":{"tf":1.0}}}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"20":{"tf":1.0}}}},"df":0,"docs":{}}}}}}},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"198":{"tf":1.0},"220":{"tf":1.0}},"g":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"70":{"tf":1.0},"72":{"tf":1.0}}},"df":0,"docs":{}}}}}},"r":{"df":0,"docs":{},"e":{"a":{"d":{"df":0,"docs":{},"i":{"df":17,"docs":{"122":{"tf":1.0},"187":{"tf":1.0},"194":{"tf":1.0},"196":{"tf":1.0},"197":{"tf":1.0},"198":{"tf":1.0},"199":{"tf":1.0},"200":{"tf":1.0},"229":{"tf":1.0},"23":{"tf":1.0},"241":{"tf":1.4142135623730951},"258":{"tf":1.0},"32":{"tf":1.0},"36":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"64":{"tf":1.7320508075688772}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":4,"docs":{"115":{"tf":1.4142135623730951},"122":{"tf":1.0},"225":{"tf":1.0},"26":{"tf":1.0}}}}},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":9,"docs":{"105":{"tf":1.0},"124":{"tf":1.0},"132":{"tf":1.0},"190":{"tf":1.0},"214":{"tf":1.4142135623730951},"225":{"tf":1.0},"232":{"tf":1.0},"262":{"tf":1.0},"38":{"tf":1.0}}}}}}}},"w":{"a":{"df":0,"docs":{},"y":{"df":10,"docs":{"114":{"tf":1.0},"133":{"tf":1.0},"146":{"tf":1.0},"189":{"tf":1.0},"198":{"tf":1.0},"22":{"tf":1.0},"260":{"tf":1.0},"60":{"tf":1.0},"79":{"tf":1.0},"99":{"tf":1.0}}}},"df":0,"docs":{}}},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":8,"docs":{"166":{"tf":1.0},"169":{"tf":1.4142135623730951},"170":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"7":{"tf":1.0}}}}}}},"n":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"y":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":4,"docs":{"178":{"tf":1.0},"214":{"tf":1.4142135623730951},"264":{"tf":1.0},"274":{"tf":1.0}}}}}}},"d":{"/":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":2,"docs":{"104":{"tf":1.0},"122":{"tf":1.0}}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{".":{"df":0,"docs":{},"y":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":1,"docs":{"113":{"tf":1.0}},"t":{"df":0,"docs":{},"o":{"df":1,"docs":{"70":{"tf":1.0}}}}}}}},"/":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{".":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"d":{"df":0,"docs":{},"l":{"df":1,"docs":{"108":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"/":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"/":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"e":{"/":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":1,"docs":{"108":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"df":1,"docs":{"20":{"tf":1.0}}}}}},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"x":{"df":1,"docs":{"119":{"tf":1.0}}}}}}}}},"i":{"df":0,"docs":{},"o":{"df":1,"docs":{"11":{"tf":1.0}}}},"k":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"65":{"tf":1.0}}}}}}}},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{".":{"df":0,"docs":{},"h":{"df":1,"docs":{"37":{"tf":1.0}}}},"df":0,"docs":{}}}},"s":{"df":0,"docs":{},"r":{"c":{"/":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"/":{"df":0,"docs":{},"j":{"a":{"df":0,"docs":{},"v":{"a":{"df":1,"docs":{"108":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"/":{"df":0,"docs":{},"j":{"a":{"df":0,"docs":{},"v":{"a":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"/":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"/":{"<":{"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"_":{"c":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"70":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":1,"docs":{"200":{"tf":1.0}}}}}}}},"2":{"1":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":1,"docs":{"12":{"tf":1.0}},"e":{"/":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"s":{"/":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"/":{"df":0,"docs":{},"s":{"d":{"df":0,"docs":{},"k":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"13":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"s":{"d":{"df":0,"docs":{},"k":{"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":1,"docs":{"12":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":55,"docs":{"10":{"tf":1.0},"103":{"tf":1.7320508075688772},"108":{"tf":1.4142135623730951},"11":{"tf":1.0},"110":{"tf":1.0},"115":{"tf":1.4142135623730951},"116":{"tf":1.0},"117":{"tf":1.4142135623730951},"118":{"tf":2.0},"119":{"tf":1.4142135623730951},"12":{"tf":2.8284271247461903},"126":{"tf":2.0},"13":{"tf":1.4142135623730951},"131":{"tf":1.0},"133":{"tf":1.0},"145":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":2.0},"19":{"tf":1.0},"190":{"tf":1.0},"192":{"tf":1.0},"199":{"tf":1.0},"20":{"tf":1.4142135623730951},"200":{"tf":1.0},"203":{"tf":1.0},"206":{"tf":1.7320508075688772},"21":{"tf":1.4142135623730951},"210":{"tf":1.0},"217":{"tf":1.0},"218":{"tf":1.4142135623730951},"220":{"tf":1.7320508075688772},"221":{"tf":2.23606797749979},"223":{"tf":1.0},"225":{"tf":2.0},"230":{"tf":1.0},"237":{"tf":1.0},"255":{"tf":1.0},"264":{"tf":1.0},"268":{"tf":1.4142135623730951},"271":{"tf":1.0},"272":{"tf":1.0},"274":{"tf":2.0},"278":{"tf":1.0},"280":{"tf":1.0},"37":{"tf":3.1622776601683795},"38":{"tf":1.0},"39":{"tf":1.7320508075688772},"40":{"tf":1.4142135623730951},"45":{"tf":1.7320508075688772},"49":{"tf":2.23606797749979},"54":{"tf":1.7320508075688772},"69":{"tf":1.0},"70":{"tf":1.4142135623730951},"9":{"tf":1.0},"90":{"tf":1.0}},"e":{"a":{"b":{"df":0,"docs":{},"i":{"df":1,"docs":{"37":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"m":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"x":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":3,"docs":{"103":{"tf":1.4142135623730951},"108":{"tf":1.0},"70":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"200":{"tf":1.0}}}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"h":{"df":3,"docs":{"132":{"tf":1.0},"233":{"tf":1.0},"283":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"w":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"205":{"tf":1.0}}}}}},"t":{"df":1,"docs":{"37":{"tf":3.0}},"i":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":1,"docs":{"231":{"tf":1.0}}}}},"df":0,"docs":{}}},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"3":{"tf":1.0}}}},"t":{"df":0,"docs":{},"h":{"df":4,"docs":{"228":{"tf":1.0},"242":{"tf":1.0},"264":{"tf":1.0},"61":{"tf":1.0}}}},"w":{"a":{"df":0,"docs":{},"y":{"df":1,"docs":{"60":{"tf":1.0}}}},"df":0,"docs":{}}}},"p":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"81":{"tf":1.0}}}}},"df":0,"docs":{},"i":{"df":37,"docs":{"104":{"tf":1.7320508075688772},"105":{"tf":1.7320508075688772},"106":{"tf":1.4142135623730951},"107":{"tf":1.4142135623730951},"108":{"tf":1.4142135623730951},"109":{"tf":1.0},"111":{"tf":1.0},"114":{"tf":1.0},"116":{"tf":1.0},"118":{"tf":1.0},"134":{"tf":1.0},"139":{"tf":1.0},"141":{"tf":1.4142135623730951},"142":{"tf":2.0},"143":{"tf":1.0},"145":{"tf":2.449489742783178},"146":{"tf":2.0},"166":{"tf":1.0},"197":{"tf":1.0},"200":{"tf":2.0},"21":{"tf":1.0},"226":{"tf":1.0},"232":{"tf":3.0},"236":{"tf":1.4142135623730951},"252":{"tf":1.4142135623730951},"281":{"tf":1.4142135623730951},"283":{"tf":1.0},"43":{"tf":1.4142135623730951},"45":{"tf":1.4142135623730951},"46":{"tf":1.0},"69":{"tf":1.0},"7":{"tf":1.0},"70":{"tf":1.0},"73":{"tf":1.0},"78":{"tf":1.7320508075688772},"79":{"tf":1.4142135623730951},"96":{"tf":1.0}}},"k":{"df":2,"docs":{"39":{"tf":1.4142135623730951},"40":{"tf":1.0}}},"p":{"'":{"df":1,"docs":{"226":{"tf":1.0}}},"a":{"df":0,"docs":{},"r":{"df":2,"docs":{"242":{"tf":1.0},"37":{"tf":1.0}}}},"df":35,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"142":{"tf":1.0},"158":{"tf":1.0},"165":{"tf":1.0},"17":{"tf":1.0},"179":{"tf":1.0},"180":{"tf":1.0},"183":{"tf":1.0},"184":{"tf":1.0},"189":{"tf":1.0},"19":{"tf":1.0},"190":{"tf":1.0},"194":{"tf":1.0},"20":{"tf":1.0},"200":{"tf":1.4142135623730951},"205":{"tf":1.0},"209":{"tf":1.0},"212":{"tf":1.0},"213":{"tf":2.0},"225":{"tf":1.4142135623730951},"228":{"tf":2.0},"229":{"tf":1.4142135623730951},"233":{"tf":2.0},"236":{"tf":1.0},"242":{"tf":1.7320508075688772},"250":{"tf":1.0},"254":{"tf":1.0},"255":{"tf":1.4142135623730951},"257":{"tf":1.4142135623730951},"273":{"tf":1.0},"40":{"tf":1.0},"49":{"tf":1.0},"52":{"tf":1.0},"84":{"tf":1.0}},"e":{"a":{"df":0,"docs":{},"r":{"df":8,"docs":{"117":{"tf":1.0},"172":{"tf":1.4142135623730951},"228":{"tf":1.0},"249":{"tf":2.23606797749979},"37":{"tf":1.0},"64":{"tf":1.0},"73":{"tf":1.4142135623730951},"86":{"tf":1.0}}}},"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"98":{"tf":1.0}},"i":{"df":0,"docs":{},"x":{"df":2,"docs":{"173":{"tf":1.0},"60":{"tf":1.0}}}}},"df":0,"docs":{}}},"l":{"df":9,"docs":{"161":{"tf":1.0},"162":{"tf":1.0},"163":{"tf":1.0},"169":{"tf":1.0},"170":{"tf":1.4142135623730951},"171":{"tf":1.0},"172":{"tf":1.0},"175":{"tf":1.0},"252":{"tf":1.0}},"e":{"'":{"df":2,"docs":{"274":{"tf":1.0},"46":{"tf":1.0}}},"df":0,"docs":{}},"i":{"c":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"'":{"df":1,"docs":{"221":{"tf":1.0}}},".":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"z":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"283":{"tf":1.0}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"x":{"a":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"l":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"df":1,"docs":{"283":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"\\":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"257":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"’":{"df":1,"docs":{"283":{"tf":1.0}}}}}}}},"df":99,"docs":{"0":{"tf":1.7320508075688772},"1":{"tf":1.0},"10":{"tf":1.0},"11":{"tf":1.4142135623730951},"114":{"tf":1.0},"118":{"tf":1.7320508075688772},"119":{"tf":1.4142135623730951},"12":{"tf":2.0},"120":{"tf":1.0},"122":{"tf":2.0},"123":{"tf":1.0},"124":{"tf":3.0},"125":{"tf":1.7320508075688772},"130":{"tf":1.0},"14":{"tf":1.4142135623730951},"15":{"tf":1.4142135623730951},"16":{"tf":1.0},"161":{"tf":1.7320508075688772},"162":{"tf":1.0},"164":{"tf":1.0},"165":{"tf":1.7320508075688772},"167":{"tf":3.1622776601683795},"175":{"tf":1.4142135623730951},"177":{"tf":1.0},"18":{"tf":2.23606797749979},"180":{"tf":1.0},"184":{"tf":1.0},"186":{"tf":1.0},"187":{"tf":1.0},"189":{"tf":1.4142135623730951},"19":{"tf":1.0},"190":{"tf":1.0},"194":{"tf":1.4142135623730951},"197":{"tf":1.4142135623730951},"2":{"tf":1.0},"20":{"tf":2.0},"200":{"tf":2.6457513110645907},"201":{"tf":2.449489742783178},"203":{"tf":1.4142135623730951},"204":{"tf":1.0},"21":{"tf":1.4142135623730951},"218":{"tf":1.0},"22":{"tf":2.0},"220":{"tf":1.4142135623730951},"221":{"tf":1.0},"222":{"tf":1.4142135623730951},"225":{"tf":2.0},"228":{"tf":1.4142135623730951},"23":{"tf":1.7320508075688772},"232":{"tf":2.23606797749979},"24":{"tf":1.0},"242":{"tf":1.0},"244":{"tf":1.7320508075688772},"248":{"tf":1.0},"25":{"tf":1.7320508075688772},"252":{"tf":1.4142135623730951},"253":{"tf":1.7320508075688772},"256":{"tf":1.0},"258":{"tf":1.0},"259":{"tf":1.4142135623730951},"260":{"tf":1.4142135623730951},"262":{"tf":1.0},"265":{"tf":1.0},"268":{"tf":2.23606797749979},"274":{"tf":2.23606797749979},"276":{"tf":1.0},"28":{"tf":1.0},"283":{"tf":2.6457513110645907},"284":{"tf":1.0},"29":{"tf":1.4142135623730951},"3":{"tf":1.4142135623730951},"30":{"tf":1.7320508075688772},"32":{"tf":1.4142135623730951},"33":{"tf":1.0},"34":{"tf":1.7320508075688772},"35":{"tf":1.7320508075688772},"37":{"tf":1.7320508075688772},"38":{"tf":1.0},"39":{"tf":1.7320508075688772},"40":{"tf":1.7320508075688772},"41":{"tf":1.0},"49":{"tf":1.4142135623730951},"52":{"tf":1.4142135623730951},"54":{"tf":1.4142135623730951},"55":{"tf":1.0},"56":{"tf":1.0},"64":{"tf":1.0},"68":{"tf":1.4142135623730951},"69":{"tf":1.0},"7":{"tf":1.0},"73":{"tf":1.0},"74":{"tf":1.0},"77":{"tf":1.0},"78":{"tf":1.0},"8":{"tf":1.0},"81":{"tf":1.0},"82":{"tf":1.0},"84":{"tf":1.0},"9":{"tf":1.7320508075688772}}},"df":11,"docs":{"128":{"tf":1.0},"140":{"tf":1.0},"179":{"tf":1.0},"22":{"tf":2.0},"225":{"tf":1.0},"249":{"tf":1.0},"262":{"tf":1.0},"274":{"tf":1.0},"64":{"tf":1.0},"7":{"tf":1.0},"98":{"tf":1.0}}},"y":{"_":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":3,"docs":{"247":{"tf":1.4142135623730951},"249":{"tf":1.0},"88":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"o":{"a":{"c":{"df":0,"docs":{},"h":{"df":13,"docs":{"101":{"tf":1.0},"115":{"tf":1.0},"140":{"tf":1.4142135623730951},"141":{"tf":1.4142135623730951},"143":{"tf":1.0},"145":{"tf":1.0},"146":{"tf":1.4142135623730951},"171":{"tf":1.0},"205":{"tf":1.0},"220":{"tf":1.0},"241":{"tf":1.0},"251":{"tf":1.0},"60":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":4,"docs":{"189":{"tf":1.0},"280":{"tf":1.0},"45":{"tf":1.0},"6":{"tf":1.0}}}}},"v":{"df":5,"docs":{"118":{"tf":1.4142135623730951},"119":{"tf":1.4142135623730951},"189":{"tf":1.0},"270":{"tf":1.0},"6":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":5,"docs":{"169":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"222":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"d":{"df":0,"docs":{},"l":{"df":1,"docs":{"22":{"tf":1.7320508075688772}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"r":{"c":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"}":{"/":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"22":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"v":{"c":{"df":2,"docs":{"276":{"tf":1.4142135623730951},"277":{"tf":1.0}}},"df":0,"docs":{}}}},"t":{"df":2,"docs":{"11":{"tf":3.4641016151377544},"13":{"tf":1.0}}}},"r":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":13,"docs":{"102":{"tf":1.0},"134":{"tf":2.0},"135":{"tf":1.0},"136":{"tf":1.0},"137":{"tf":1.4142135623730951},"17":{"tf":1.4142135623730951},"223":{"tf":1.0},"225":{"tf":1.4142135623730951},"227":{"tf":1.0},"244":{"tf":1.0},"252":{"tf":2.23606797749979},"282":{"tf":1.7320508075688772},"58":{"tf":1.0}}}}}},"df":0,"docs":{}}},"v":{"df":4,"docs":{"220":{"tf":1.0},"223":{"tf":1.0},"271":{"tf":1.0},"279":{"tf":1.0}},"e":{".":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{".":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":1,"docs":{"273":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"e":{"a":{"df":2,"docs":{"273":{"tf":1.7320508075688772},"63":{"tf":1.0}}},"df":0,"docs":{},"n":{"'":{"df":0,"docs":{},"t":{"df":9,"docs":{"166":{"tf":1.0},"225":{"tf":1.0},"226":{"tf":1.0},"260":{"tf":1.0},"268":{"tf":1.0},"58":{"tf":1.0},"60":{"tf":1.0},"79":{"tf":1.0},"84":{"tf":1.0}}}},"df":0,"docs":{}}},"g":{"df":1,"docs":{"58":{"tf":1.0}}},"m":{"6":{"4":{"df":1,"docs":{"175":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{},"v":{"7":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":12,"docs":{"115":{"tf":1.0},"116":{"tf":1.0},"150":{"tf":1.0},"169":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"204":{"tf":1.4142135623730951},"233":{"tf":1.0},"242":{"tf":1.4142135623730951},"261":{"tf":1.0},"51":{"tf":1.0},"79":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}},"t":{"df":2,"docs":{"206":{"tf":1.0},"214":{"tf":1.0}},"i":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"163":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"f":{"a":{"c":{"df":0,"docs":{},"t":{"df":20,"docs":{"122":{"tf":1.0},"14":{"tf":1.0},"163":{"tf":1.0},"167":{"tf":1.0},"169":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.4142135623730951},"172":{"tf":1.4142135623730951},"175":{"tf":1.0},"220":{"tf":1.0},"267":{"tf":1.4142135623730951},"271":{"tf":1.0},"272":{"tf":1.0},"273":{"tf":1.7320508075688772},"274":{"tf":2.23606797749979},"279":{"tf":1.4142135623730951},"280":{"tf":2.23606797749979},"37":{"tf":1.4142135623730951},"40":{"tf":1.0},"64":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"s":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"242":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"51":{"tf":1.0}}}},"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"60":{"tf":1.0}}},"df":0,"docs":{}},"k":{"df":5,"docs":{"122":{"tf":2.0},"161":{"tf":1.0},"233":{"tf":1.0},"242":{"tf":1.0},"274":{"tf":1.0}}},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"104":{"tf":1.0}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"r":{"df":1,"docs":{"258":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":1,"docs":{"18":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":1,"docs":{"62":{"tf":1.0}}}}},"df":1,"docs":{"43":{"tf":1.4142135623730951}}}}},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"254":{"tf":1.0}}}}},"o":{"c":{"df":0,"docs":{},"i":{"df":2,"docs":{"115":{"tf":1.0},"283":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"m":{"df":17,"docs":{"114":{"tf":1.0},"12":{"tf":1.0},"145":{"tf":1.0},"151":{"tf":1.0},"154":{"tf":1.0},"20":{"tf":1.0},"21":{"tf":1.0},"210":{"tf":1.0},"225":{"tf":1.0},"23":{"tf":1.0},"25":{"tf":1.0},"29":{"tf":1.0},"30":{"tf":1.0},"32":{"tf":1.0},"34":{"tf":1.0},"35":{"tf":1.0},"86":{"tf":1.0}},"p":{"df":0,"docs":{},"t":{"df":2,"docs":{"138":{"tf":1.0},"210":{"tf":1.0}}}}},"r":{"df":2,"docs":{"274":{"tf":1.0},"64":{"tf":1.0}}}}},"y":{"df":0,"docs":{},"n":{"c":{"df":4,"docs":{"115":{"tf":1.0},"193":{"tf":1.0},"194":{"tf":1.0},"197":{"tf":1.0}}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"t":{"a":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"190":{"tf":1.0}}},"k":{"df":1,"docs":{"201":{"tf":1.7320508075688772}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":13,"docs":{"132":{"tf":1.0},"146":{"tf":1.0},"167":{"tf":1.0},"19":{"tf":1.0},"200":{"tf":1.0},"203":{"tf":1.0},"204":{"tf":1.0},"207":{"tf":1.0},"226":{"tf":1.0},"245":{"tf":1.0},"25":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0}}}}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"170":{"tf":1.0},"197":{"tf":1.0},"198":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"193":{"tf":1.0},"194":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"u":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"274":{"tf":1.0},"64":{"tf":1.0}}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"226":{"tf":1.0}}}}}}},"t":{"df":0,"docs":{},"h":{"df":4,"docs":{"226":{"tf":1.0},"236":{"tf":1.0},"237":{"tf":1.0},"239":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":5,"docs":{"226":{"tf":1.0},"245":{"tf":1.0},"254":{"tf":1.0},"274":{"tf":1.0},"275":{"tf":1.0}},"i":{"c":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"242":{"tf":1.0}}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"283":{"tf":1.7320508075688772}}}}},"o":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"61":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":8,"docs":{"104":{"tf":1.0},"116":{"tf":1.0},"12":{"tf":1.0},"16":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":1.0},"20":{"tf":1.7320508075688772},"22":{"tf":1.4142135623730951}},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":5,"docs":{"140":{"tf":1.0},"145":{"tf":1.0},"244":{"tf":1.0},"257":{"tf":1.0},"264":{"tf":1.7320508075688772}},"l":{"/":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"245":{"tf":1.0}}}}}}}},":":{":":{"d":{"b":{":":{":":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{":":{":":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"245":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"d":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"_":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"245":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":5,"docs":{"103":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0}}}}}}},"m":{"a":{"df":0,"docs":{},"t":{"df":12,"docs":{"107":{"tf":1.0},"113":{"tf":1.0},"167":{"tf":1.0},"18":{"tf":1.4142135623730951},"19":{"tf":1.0},"266":{"tf":1.0},"61":{"tf":1.0},"70":{"tf":1.0},"72":{"tf":1.0},"73":{"tf":1.0},"74":{"tf":1.0},"90":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"269":{"tf":1.0}}}},"df":0,"docs":{}}}},"u":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"l":{"_":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"_":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"d":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"19":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":2,"docs":{"269":{"tf":1.0},"53":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}}}}}}}},"u":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"109":{"tf":1.0}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"s":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":3,"docs":{"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0}}}}}},"t":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"269":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"7":{"tf":1.0}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}},"df":10,"docs":{"101":{"tf":1.0},"103":{"tf":1.0},"111":{"tf":1.0},"167":{"tf":1.0},"22":{"tf":1.4142135623730951},"25":{"tf":1.0},"27":{"tf":1.0},"274":{"tf":2.23606797749979},"49":{"tf":1.4142135623730951},"7":{"tf":1.0}}},"p":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{".":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":2,"docs":{"18":{"tf":1.4142135623730951},"22":{"tf":1.7320508075688772}}}}}},"df":0,"docs":{}},"df":1,"docs":{"118":{"tf":1.0}},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"l":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":1,"docs":{"22":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"61":{"tf":1.0}},"s":{"/":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"61":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"v":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":24,"docs":{"1":{"tf":1.0},"104":{"tf":1.0},"107":{"tf":1.0},"11":{"tf":1.0},"114":{"tf":1.0},"134":{"tf":1.4142135623730951},"138":{"tf":1.0},"142":{"tf":1.0},"146":{"tf":1.0},"165":{"tf":1.0},"177":{"tf":1.4142135623730951},"178":{"tf":1.0},"179":{"tf":1.0},"187":{"tf":1.0},"192":{"tf":1.0},"198":{"tf":1.0},"228":{"tf":1.0},"252":{"tf":1.0},"260":{"tf":1.0},"274":{"tf":1.7320508075688772},"282":{"tf":1.0},"45":{"tf":1.0},"5":{"tf":1.0},"62":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"204":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"i":{"d":{"df":23,"docs":{"116":{"tf":1.0},"126":{"tf":1.0},"128":{"tf":1.0},"132":{"tf":1.0},"141":{"tf":1.0},"150":{"tf":1.0},"165":{"tf":1.0},"172":{"tf":1.0},"197":{"tf":1.4142135623730951},"22":{"tf":1.0},"220":{"tf":1.0},"222":{"tf":1.0},"225":{"tf":1.0},"227":{"tf":1.0},"229":{"tf":1.4142135623730951},"263":{"tf":1.4142135623730951},"264":{"tf":1.0},"46":{"tf":1.0},"57":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"69":{"tf":1.0},"99":{"tf":1.0}}},"df":0,"docs":{}}}},"w":{"a":{"df":0,"docs":{},"r":{"df":3,"docs":{"133":{"tf":1.0},"189":{"tf":1.0},"233":{"tf":1.0}}},"y":{"df":3,"docs":{"117":{"tf":1.0},"170":{"tf":1.0},"212":{"tf":1.0}}}},"df":0,"docs":{}}},"b":{"/":{"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{".":{"df":0,"docs":{},"m":{"d":{"df":1,"docs":{"268":{"tf":2.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"a":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"/":{"c":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":1,"docs":{"62":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{},"s":{"df":0,"docs":{},"r":{"c":{"/":{"d":{"b":{"/":{"d":{"b":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"62":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"62":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":2,"docs":{"268":{"tf":2.0},"269":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}}}}}}}}},"1":{"df":2,"docs":{"152":{"tf":1.0},"157":{"tf":1.0}}},"2":{"df":1,"docs":{"158":{"tf":1.0}}},"3":{"df":1,"docs":{"159":{"tf":1.0}}},"a":{"c":{"df":0,"docs":{},"k":{"df":10,"docs":{"103":{"tf":1.4142135623730951},"107":{"tf":1.0},"115":{"tf":1.4142135623730951},"158":{"tf":1.0},"214":{"tf":1.0},"242":{"tf":1.0},"246":{"tf":1.0},"249":{"tf":2.0},"274":{"tf":1.0},"283":{"tf":1.0}},"e":{"d":{"df":0,"docs":{},"o":{"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":1,"docs":{"242":{"tf":1.0}}}}}},"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"51":{"tf":1.0}}},"df":0,"docs":{}}},"g":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":3,"docs":{"115":{"tf":1.4142135623730951},"203":{"tf":1.0},"58":{"tf":1.0}}},"df":0,"docs":{}}}}}},"o":{"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":6,"docs":{"225":{"tf":1.0},"226":{"tf":1.4142135623730951},"236":{"tf":1.0},"239":{"tf":1.0},"242":{"tf":1.0},"245":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"p":{"df":1,"docs":{"158":{"tf":1.7320508075688772}}}},"w":{"a":{"df":0,"docs":{},"r":{"d":{"df":2,"docs":{"116":{"tf":1.0},"268":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"d":{"df":19,"docs":{"115":{"tf":1.0},"150":{"tf":1.0},"154":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.4142135623730951},"157":{"tf":1.0},"158":{"tf":1.7320508075688772},"159":{"tf":1.4142135623730951},"169":{"tf":1.7320508075688772},"170":{"tf":2.0},"171":{"tf":1.7320508075688772},"172":{"tf":1.0},"182":{"tf":1.0},"183":{"tf":2.0},"184":{"tf":2.0},"212":{"tf":2.0},"213":{"tf":1.4142135623730951},"226":{"tf":1.0},"81":{"tf":1.0}}},"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"64":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"n":{"d":{"df":1,"docs":{"283":{"tf":1.0}}},"df":0,"docs":{}},"r":{"df":1,"docs":{"205":{"tf":1.7320508075688772}}},"s":{"df":0,"docs":{},"e":{"df":18,"docs":{"163":{"tf":1.0},"164":{"tf":1.0},"172":{"tf":1.0},"197":{"tf":1.0},"207":{"tf":1.4142135623730951},"208":{"tf":1.0},"210":{"tf":1.0},"213":{"tf":1.0},"214":{"tf":1.4142135623730951},"215":{"tf":1.4142135623730951},"217":{"tf":1.0},"226":{"tf":1.0},"233":{"tf":1.0},"253":{"tf":1.0},"283":{"tf":1.0},"37":{"tf":1.0},"7":{"tf":1.0},"82":{"tf":1.4142135623730951}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"175":{"tf":1.0}}}}}},"h":{"/":{"df":0,"docs":{},"z":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"11":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{},"r":{"c":{"df":2,"docs":{"11":{"tf":1.0},"12":{"tf":1.0}}},"df":0,"docs":{}}},"i":{"c":{"df":2,"docs":{"274":{"tf":1.0},"37":{"tf":1.0}}},"df":2,"docs":{"134":{"tf":1.0},"59":{"tf":1.0}}}}},"df":8,"docs":{"149":{"tf":1.0},"151":{"tf":1.0},"152":{"tf":1.0},"163":{"tf":1.0},"170":{"tf":1.0},"179":{"tf":1.0},"180":{"tf":1.0},"280":{"tf":1.0}},"e":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":5,"docs":{"226":{"tf":1.0},"263":{"tf":1.0},"274":{"tf":1.0},"58":{"tf":1.0},"64":{"tf":1.0}}}}},"df":19,"docs":{"115":{"tf":1.0},"152":{"tf":1.0},"159":{"tf":1.0},"162":{"tf":1.0},"167":{"tf":1.0},"184":{"tf":1.0},"187":{"tf":1.0},"192":{"tf":1.0},"193":{"tf":1.0},"197":{"tf":1.0},"227":{"tf":1.0},"231":{"tf":1.0},"233":{"tf":1.0},"242":{"tf":1.4142135623730951},"250":{"tf":1.4142135623730951},"274":{"tf":1.4142135623730951},"60":{"tf":1.0},"78":{"tf":1.0},"84":{"tf":1.0}},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":19,"docs":{"11":{"tf":1.4142135623730951},"118":{"tf":1.0},"119":{"tf":1.0},"12":{"tf":1.0},"120":{"tf":1.0},"122":{"tf":1.0},"145":{"tf":1.0},"177":{"tf":1.0},"20":{"tf":1.4142135623730951},"204":{"tf":1.0},"213":{"tf":1.0},"245":{"tf":1.4142135623730951},"256":{"tf":1.4142135623730951},"259":{"tf":1.0},"266":{"tf":1.0},"58":{"tf":1.4142135623730951},"6":{"tf":1.0},"62":{"tf":1.4142135623730951},"7":{"tf":1.7320508075688772}},"e":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"26":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"o":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":1,"docs":{"283":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"p":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":1,"docs":{"283":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":2,"docs":{"279":{"tf":1.0},"59":{"tf":1.0}}}}},"h":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":2,"docs":{"245":{"tf":1.0},"283":{"tf":1.0}}}},"v":{"df":2,"docs":{"60":{"tf":1.0},"61":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":4,"docs":{"140":{"tf":1.0},"156":{"tf":1.0},"61":{"tf":1.7320508075688772},"84":{"tf":1.0}}},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"37":{"tf":1.0}}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"25":{"tf":1.0},"62":{"tf":1.0}}},"df":0,"docs":{}}}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":3,"docs":{"194":{"tf":1.0},"233":{"tf":1.4142135623730951},"79":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"w":{"df":18,"docs":{"105":{"tf":1.0},"11":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":1.0},"145":{"tf":1.0},"200":{"tf":1.0},"203":{"tf":1.0},"226":{"tf":1.0},"241":{"tf":1.0},"242":{"tf":1.0},"25":{"tf":1.0},"259":{"tf":1.0},"264":{"tf":1.0},"274":{"tf":1.0},"279":{"tf":1.0},"32":{"tf":1.0},"59":{"tf":1.0},"6":{"tf":1.0}}}}},"n":{"c":{"df":0,"docs":{},"h":{"0":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"60":{"tf":1.0}}}},"df":1,"docs":{"60":{"tf":1.0}}},"1":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"60":{"tf":1.0}}}},"df":1,"docs":{"60":{"tf":1.0}}},"2":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"60":{"tf":1.0}}}},"df":1,"docs":{"60":{"tf":1.0}}},"df":2,"docs":{"60":{"tf":2.23606797749979},"61":{"tf":1.0}},"e":{"df":0,"docs":{},"s":{"/":{"*":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"61":{"tf":1.0}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"m":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":3,"docs":{"58":{"tf":1.4142135623730951},"59":{"tf":1.7320508075688772},"60":{"tf":2.8284271247461903}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":8,"docs":{"140":{"tf":1.0},"170":{"tf":1.0},"198":{"tf":1.0},"201":{"tf":1.0},"221":{"tf":1.0},"274":{"tf":1.0},"60":{"tf":1.0},"64":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"t":{"df":8,"docs":{"101":{"tf":1.0},"104":{"tf":1.0},"123":{"tf":1.4142135623730951},"149":{"tf":1.0},"164":{"tf":1.0},"170":{"tf":1.0},"43":{"tf":1.4142135623730951},"75":{"tf":1.0}}}},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":11,"docs":{"115":{"tf":1.4142135623730951},"124":{"tf":1.0},"165":{"tf":1.0},"167":{"tf":1.0},"170":{"tf":1.0},"196":{"tf":1.0},"197":{"tf":1.0},"200":{"tf":1.0},"43":{"tf":1.0},"62":{"tf":1.0},"79":{"tf":1.0}}}}},"w":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":23,"docs":{"104":{"tf":1.0},"106":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"150":{"tf":1.0},"165":{"tf":1.4142135623730951},"169":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"20":{"tf":1.0},"204":{"tf":1.0},"214":{"tf":1.0},"216":{"tf":1.7320508075688772},"220":{"tf":1.0},"224":{"tf":1.0},"225":{"tf":1.0},"229":{"tf":1.0},"233":{"tf":1.0},"250":{"tf":1.0},"79":{"tf":1.0},"84":{"tf":1.0},"89":{"tf":1.0}}}}}}},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"d":{"df":4,"docs":{"111":{"tf":1.0},"204":{"tf":1.0},"208":{"tf":1.0},"216":{"tf":1.0}}},"df":0,"docs":{}}}}},"i":{"df":0,"docs":{},"g":{"df":3,"docs":{"236":{"tf":1.0},"64":{"tf":1.0},"81":{"tf":1.0}},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"210":{"tf":1.0}}}}}}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"227":{"tf":1.0}}}}}},"n":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":10,"docs":{"14":{"tf":1.0},"163":{"tf":1.4142135623730951},"167":{"tf":1.0},"169":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.4142135623730951},"172":{"tf":1.4142135623730951},"175":{"tf":1.4142135623730951},"253":{"tf":1.0},"274":{"tf":1.0}}}}},"d":{"df":24,"docs":{"0":{"tf":1.0},"101":{"tf":1.4142135623730951},"103":{"tf":1.4142135623730951},"105":{"tf":1.0},"106":{"tf":1.0},"107":{"tf":1.0},"108":{"tf":2.23606797749979},"109":{"tf":2.449489742783178},"111":{"tf":1.4142135623730951},"116":{"tf":1.0},"140":{"tf":1.0},"162":{"tf":1.0},"253":{"tf":1.0},"281":{"tf":1.0},"45":{"tf":1.7320508075688772},"46":{"tf":1.7320508075688772},"69":{"tf":1.0},"7":{"tf":1.0},"70":{"tf":1.7320508075688772},"71":{"tf":1.0},"72":{"tf":1.4142135623730951},"73":{"tf":1.4142135623730951},"74":{"tf":1.0},"90":{"tf":1.4142135623730951}},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":2,"docs":{"104":{"tf":1.7320508075688772},"69":{"tf":1.4142135623730951}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"k":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"70":{"tf":1.0}}}}}}}},"s":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":1,"docs":{"73":{"tf":1.0}}}}}}}},"_":{"df":0,"docs":{},"u":{"d":{"df":0,"docs":{},"l":{"_":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"74":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":1,"docs":{"11":{"tf":1.0}}},"t":{"df":8,"docs":{"104":{"tf":1.4142135623730951},"116":{"tf":1.7320508075688772},"17":{"tf":1.0},"174":{"tf":1.0},"20":{"tf":1.0},"226":{"tf":1.0},"229":{"tf":1.0},"6":{"tf":1.0}}}},"l":{"a":{"df":0,"docs":{},"h":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"61":{"tf":1.0}}}},"df":1,"docs":{"61":{"tf":1.0}}}},"df":0,"docs":{},"o":{"b":{"df":1,"docs":{"242":{"tf":1.0}}},"c":{"df":0,"docs":{},"k":{"df":3,"docs":{"115":{"tf":1.0},"194":{"tf":1.0},"81":{"tf":1.0}},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{".":{"df":0,"docs":{},"x":{"c":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"p":{"b":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"df":1,"docs":{"36":{"tf":1.0}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":1,"docs":{"36":{"tf":1.0}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":1,"docs":{"36":{"tf":1.0}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"g":{"df":1,"docs":{"90":{"tf":1.4142135623730951}}}}},"o":{"d":{"df":0,"docs":{},"i":{"df":1,"docs":{"189":{"tf":1.0}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"115":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":2,"docs":{"45":{"tf":1.0},"46":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"k":{"df":2,"docs":{"286":{"tf":1.0},"43":{"tf":1.0}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":1,"docs":{"82":{"tf":1.0}}}}}},"m":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":10,"docs":{"226":{"tf":1.4142135623730951},"228":{"tf":1.0},"231":{"tf":1.0},"232":{"tf":1.0},"242":{"tf":1.4142135623730951},"257":{"tf":1.0},"264":{"tf":1.0},"62":{"tf":1.0},"83":{"tf":1.0},"86":{"tf":1.0}},"g":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"62":{"tf":2.0}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"l":{"df":2,"docs":{"116":{"tf":1.4142135623730951},"242":{"tf":1.0}}},"t":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":1,"docs":{"274":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"t":{"df":0,"docs":{},"h":{"df":21,"docs":{"117":{"tf":1.0},"123":{"tf":1.0},"145":{"tf":2.0},"146":{"tf":1.0},"17":{"tf":1.0},"175":{"tf":1.0},"18":{"tf":1.0},"196":{"tf":1.0},"198":{"tf":1.4142135623730951},"200":{"tf":1.0},"221":{"tf":1.0},"222":{"tf":1.0},"226":{"tf":1.0},"241":{"tf":1.4142135623730951},"249":{"tf":1.4142135623730951},"271":{"tf":1.0},"274":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"7":{"tf":1.0},"86":{"tf":1.4142135623730951}}},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":2,"docs":{"126":{"tf":1.0},"60":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"n":{"d":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":4,"docs":{"229":{"tf":1.0},"43":{"tf":1.0},"51":{"tf":1.0},"79":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"x":{"df":2,"docs":{"126":{"tf":1.0},"128":{"tf":1.0}}}},"r":{"a":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":15,"docs":{"118":{"tf":1.0},"25":{"tf":1.0},"26":{"tf":2.0},"267":{"tf":1.4142135623730951},"268":{"tf":2.0},"269":{"tf":1.4142135623730951},"270":{"tf":1.4142135623730951},"274":{"tf":2.23606797749979},"31":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"38":{"tf":1.7320508075688772},"39":{"tf":3.4641016151377544},"40":{"tf":2.449489742783178},"64":{"tf":1.0},"7":{"tf":1.4142135623730951}}}},"d":{"df":2,"docs":{"105":{"tf":1.0},"64":{"tf":1.0}}},"df":0,"docs":{}},"v":{"df":0,"docs":{},"e":{"df":1,"docs":{"81":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"k":{"a":{"df":0,"docs":{},"g":{"df":4,"docs":{"118":{"tf":1.0},"121":{"tf":1.0},"166":{"tf":1.0},"278":{"tf":1.0}}}},"df":12,"docs":{"104":{"tf":1.0},"105":{"tf":1.0},"116":{"tf":1.0},"118":{"tf":1.7320508075688772},"140":{"tf":1.0},"141":{"tf":1.0},"166":{"tf":1.0},"21":{"tf":1.0},"235":{"tf":1.0},"39":{"tf":1.4142135623730951},"40":{"tf":1.7320508075688772},"7":{"tf":1.0}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"117":{"tf":2.449489742783178}}}}}}}}},"df":0,"docs":{},"w":{"df":2,"docs":{"11":{"tf":1.0},"37":{"tf":1.0}}}},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"252":{"tf":1.0}}}}}},"n":{"df":0,"docs":{},"g":{"df":3,"docs":{"117":{"tf":1.0},"59":{"tf":1.0},"64":{"tf":1.0}}}}},"o":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"161":{"tf":1.0}}}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"171":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"266":{"tf":1.0}}}}},"w":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":8,"docs":{"134":{"tf":1.0},"186":{"tf":1.0},"187":{"tf":1.4142135623730951},"188":{"tf":1.0},"190":{"tf":1.0},"192":{"tf":1.0},"194":{"tf":1.0},"197":{"tf":1.7320508075688772}}}}}}}},"u":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":3,"docs":{"189":{"tf":1.0},"214":{"tf":1.0},"216":{"tf":1.7320508075688772}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"105":{"tf":1.0},"106":{"tf":1.4142135623730951}}}}}},"g":{"df":53,"docs":{"1":{"tf":1.0},"100":{"tf":1.0},"106":{"tf":1.0},"109":{"tf":1.0},"116":{"tf":1.7320508075688772},"117":{"tf":1.0},"119":{"tf":1.0},"122":{"tf":1.0},"123":{"tf":1.0},"124":{"tf":1.4142135623730951},"127":{"tf":1.0},"133":{"tf":1.0},"138":{"tf":1.0},"147":{"tf":1.0},"15":{"tf":1.0},"159":{"tf":1.0},"171":{"tf":1.0},"174":{"tf":1.0},"175":{"tf":1.0},"185":{"tf":1.0},"201":{"tf":1.0},"218":{"tf":1.0},"22":{"tf":1.0},"224":{"tf":1.0},"228":{"tf":1.0},"242":{"tf":1.0},"250":{"tf":1.0},"253":{"tf":1.0},"258":{"tf":1.0},"259":{"tf":1.0},"261":{"tf":1.0},"263":{"tf":1.0},"264":{"tf":1.0},"273":{"tf":1.0},"277":{"tf":1.0},"282":{"tf":1.0},"283":{"tf":1.0},"286":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.4142135623730951},"4":{"tf":1.0},"40":{"tf":1.0},"43":{"tf":1.0},"51":{"tf":1.0},"52":{"tf":1.0},"56":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0},"67":{"tf":1.0},"74":{"tf":1.0},"8":{"tf":1.0},"90":{"tf":1.0}},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"df":2,"docs":{"1":{"tf":1.0},"261":{"tf":1.0}}},"df":0,"docs":{}}}}}},"i":{"df":0,"docs":{},"l":{"d":{".":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"d":{"df":0,"docs":{},"l":{"df":9,"docs":{"103":{"tf":1.4142135623730951},"108":{"tf":1.0},"117":{"tf":1.0},"20":{"tf":1.0},"22":{"tf":1.0},"224":{"tf":1.4142135623730951},"37":{"tf":1.0},"45":{"tf":1.0},"70":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"r":{"df":1,"docs":{"105":{"tf":1.0}}}},"/":{"d":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"s":{"/":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":1,"docs":{"286":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"_":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"175":{"tf":1.0}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"105":{"tf":1.0}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":2,"docs":{"113":{"tf":1.0},"70":{"tf":1.0}}}}}}}},"df":78,"docs":{"10":{"tf":2.0},"101":{"tf":1.4142135623730951},"105":{"tf":1.0},"108":{"tf":1.7320508075688772},"109":{"tf":2.0},"11":{"tf":3.872983346207417},"114":{"tf":1.4142135623730951},"118":{"tf":1.0},"119":{"tf":1.4142135623730951},"12":{"tf":1.4142135623730951},"122":{"tf":2.6457513110645907},"124":{"tf":1.0},"13":{"tf":1.0},"14":{"tf":1.7320508075688772},"140":{"tf":1.0},"15":{"tf":1.4142135623730951},"161":{"tf":1.4142135623730951},"163":{"tf":2.23606797749979},"165":{"tf":1.4142135623730951},"166":{"tf":1.0},"167":{"tf":2.0},"169":{"tf":1.7320508075688772},"17":{"tf":1.0},"170":{"tf":2.23606797749979},"171":{"tf":1.0},"175":{"tf":2.23606797749979},"18":{"tf":2.0},"19":{"tf":1.0},"197":{"tf":1.0},"20":{"tf":1.0},"21":{"tf":1.0},"22":{"tf":2.6457513110645907},"221":{"tf":1.0},"224":{"tf":1.7320508075688772},"23":{"tf":1.7320508075688772},"233":{"tf":1.0},"237":{"tf":1.0},"24":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951},"252":{"tf":2.0},"253":{"tf":1.0},"262":{"tf":1.0},"263":{"tf":1.0},"266":{"tf":1.7320508075688772},"267":{"tf":1.7320508075688772},"271":{"tf":2.23606797749979},"272":{"tf":1.4142135623730951},"274":{"tf":3.7416573867739413},"278":{"tf":1.4142135623730951},"279":{"tf":2.449489742783178},"28":{"tf":2.0},"280":{"tf":1.4142135623730951},"282":{"tf":2.449489742783178},"285":{"tf":1.0},"286":{"tf":1.4142135623730951},"31":{"tf":1.0},"32":{"tf":1.4142135623730951},"33":{"tf":2.0},"36":{"tf":1.4142135623730951},"37":{"tf":2.23606797749979},"38":{"tf":1.7320508075688772},"39":{"tf":2.6457513110645907},"40":{"tf":3.0},"49":{"tf":1.4142135623730951},"5":{"tf":1.7320508075688772},"58":{"tf":1.7320508075688772},"59":{"tf":1.7320508075688772},"61":{"tf":1.0},"64":{"tf":1.7320508075688772},"67":{"tf":1.0},"69":{"tf":1.7320508075688772},"70":{"tf":1.7320508075688772},"73":{"tf":2.23606797749979},"75":{"tf":2.0},"77":{"tf":1.4142135623730951},"81":{"tf":1.0},"9":{"tf":1.4142135623730951},"90":{"tf":1.4142135623730951}},"s":{"df":0,"docs":{},"r":{"c":{"/":{"df":0,"docs":{},"s":{"df":0,"docs":{},"r":{"c":{"/":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"/":{"df":0,"docs":{},"j":{"a":{"df":0,"docs":{},"v":{"a":{"/":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"k":{"df":0,"docs":{},"t":{"df":1,"docs":{"20":{"tf":1.0}}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{},"t":{"df":24,"docs":{"0":{"tf":1.0},"11":{"tf":1.0},"119":{"tf":1.0},"13":{"tf":1.0},"14":{"tf":1.0},"167":{"tf":2.0},"174":{"tf":1.0},"175":{"tf":1.4142135623730951},"222":{"tf":1.4142135623730951},"227":{"tf":1.0},"251":{"tf":1.4142135623730951},"271":{"tf":1.0},"274":{"tf":1.4142135623730951},"28":{"tf":1.0},"280":{"tf":1.0},"282":{"tf":1.0},"286":{"tf":1.4142135623730951},"33":{"tf":1.0},"37":{"tf":1.7320508075688772},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0}},"i":{"df":0,"docs":{},"n":{"df":3,"docs":{"43":{"tf":1.0},"69":{"tf":1.0},"92":{"tf":1.0}}}}}}},"m":{"df":0,"docs":{},"p":{"df":5,"docs":{"119":{"tf":1.7320508075688772},"263":{"tf":1.4142135623730951},"270":{"tf":1.0},"279":{"tf":1.4142135623730951},"82":{"tf":1.4142135623730951}}}},"n":{"c":{"df":0,"docs":{},"h":{"df":2,"docs":{"104":{"tf":1.0},"111":{"tf":1.0}}}},"d":{"df":0,"docs":{},"l":{"df":13,"docs":{"161":{"tf":1.0},"163":{"tf":1.0},"167":{"tf":2.0},"169":{"tf":1.0},"170":{"tf":1.4142135623730951},"171":{"tf":1.0},"172":{"tf":1.0},"179":{"tf":1.4142135623730951},"180":{"tf":1.0},"183":{"tf":1.0},"185":{"tf":1.0},"252":{"tf":1.0},"73":{"tf":1.0}}}},"df":0,"docs":{}},"r":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"241":{"tf":1.0}}}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"i":{"df":1,"docs":{"256":{"tf":1.0}}},"t":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"282":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"117":{"tf":1.0}}}}}}}},"c":{"8":{"9":{"df":1,"docs":{"116":{"tf":1.0}}},"df":0,"docs":{}},"a":{"c":{"df":0,"docs":{},"h":{"df":7,"docs":{"11":{"tf":1.0},"189":{"tf":1.0},"194":{"tf":1.0},"201":{"tf":2.0},"25":{"tf":2.23606797749979},"26":{"tf":2.0},"59":{"tf":1.0}},"e":{"_":{"df":0,"docs":{},"s":{"df":1,"docs":{"264":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"l":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":3,"docs":{"246":{"tf":1.0},"249":{"tf":1.0},"7":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"268":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"l":{"b":{"a":{"c":{"df":0,"docs":{},"k":{"df":4,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"115":{"tf":2.449489742783178},"116":{"tf":1.4142135623730951}},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"115":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":20,"docs":{"101":{"tf":1.0},"111":{"tf":1.0},"115":{"tf":2.449489742783178},"126":{"tf":1.4142135623730951},"13":{"tf":1.0},"131":{"tf":1.0},"145":{"tf":1.7320508075688772},"147":{"tf":1.0},"201":{"tf":1.0},"222":{"tf":1.0},"241":{"tf":1.4142135623730951},"242":{"tf":1.4142135623730951},"244":{"tf":1.0},"245":{"tf":1.7320508075688772},"283":{"tf":1.7320508075688772},"77":{"tf":1.4142135623730951},"78":{"tf":1.0},"81":{"tf":1.0},"84":{"tf":1.0},"90":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"82":{"tf":1.0}}}}}},"m":{"df":0,"docs":{},"e":{"df":2,"docs":{"256":{"tf":1.0},"60":{"tf":1.0}},"l":{"c":{"a":{"df":0,"docs":{},"s":{"df":1,"docs":{"108":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"n":{"'":{"df":0,"docs":{},"t":{"df":6,"docs":{"128":{"tf":1.0},"175":{"tf":1.0},"194":{"tf":1.0},"226":{"tf":1.0},"231":{"tf":1.4142135623730951},"82":{"tf":1.0}}}},"d":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"267":{"tf":1.7320508075688772},"84":{"tf":1.0}}},"df":0,"docs":{}}},"df":1,"docs":{"227":{"tf":1.0}},"o":{"df":0,"docs":{},"n":{"df":5,"docs":{"228":{"tf":1.0},"232":{"tf":1.0},"233":{"tf":1.0},"242":{"tf":1.0},"263":{"tf":1.0}}}}},"p":{"a":{"b":{"df":0,"docs":{},"l":{"df":13,"docs":{"124":{"tf":1.0},"190":{"tf":1.0},"192":{"tf":1.0},"193":{"tf":1.0},"194":{"tf":1.0},"196":{"tf":1.0},"197":{"tf":1.0},"201":{"tf":2.23606797749979},"22":{"tf":1.0},"228":{"tf":1.0},"262":{"tf":1.0},"283":{"tf":1.0},"62":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"104":{"tf":1.0}}}},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":2,"docs":{"101":{"tf":1.0},"242":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"e":{"df":7,"docs":{"132":{"tf":1.0},"228":{"tf":1.4142135623730951},"232":{"tf":1.0},"238":{"tf":1.0},"58":{"tf":1.4142135623730951},"81":{"tf":1.0},"82":{"tf":1.4142135623730951}},"f":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"227":{"tf":1.0}}}}}}}},"g":{"df":0,"docs":{},"o":{".":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"df":2,"docs":{"64":{"tf":1.7320508075688772},"69":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":14,"docs":{"103":{"tf":1.7320508075688772},"105":{"tf":1.0},"123":{"tf":1.4142135623730951},"124":{"tf":1.0},"200":{"tf":1.0},"251":{"tf":1.0},"28":{"tf":1.0},"33":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.4142135623730951},"69":{"tf":1.4142135623730951},"70":{"tf":1.0},"72":{"tf":1.0},"73":{"tf":1.0}}}}}}},"df":14,"docs":{"104":{"tf":1.0},"105":{"tf":1.0},"107":{"tf":1.0},"11":{"tf":1.7320508075688772},"122":{"tf":1.0},"274":{"tf":1.0},"279":{"tf":1.4142135623730951},"286":{"tf":1.0},"43":{"tf":1.0},"58":{"tf":3.1622776601683795},"59":{"tf":1.0},"60":{"tf":1.7320508075688772},"64":{"tf":1.4142135623730951},"69":{"tf":1.4142135623730951}}}},"t":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"g":{"df":6,"docs":{"161":{"tf":1.0},"163":{"tf":2.0},"166":{"tf":1.0},"167":{"tf":2.0},"170":{"tf":1.4142135623730951},"175":{"tf":1.0}},"e":{"'":{"df":1,"docs":{"170":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"s":{"df":0,"docs":{},"e":{"df":20,"docs":{"104":{"tf":1.0},"105":{"tf":1.0},"106":{"tf":1.7320508075688772},"115":{"tf":1.4142135623730951},"116":{"tf":1.0},"152":{"tf":1.0},"156":{"tf":1.0},"17":{"tf":1.0},"187":{"tf":1.0},"194":{"tf":1.4142135623730951},"201":{"tf":1.0},"228":{"tf":1.0},"233":{"tf":1.0},"245":{"tf":1.0},"274":{"tf":1.0},"276":{"tf":1.0},"70":{"tf":1.0},"79":{"tf":1.0},"98":{"tf":1.0},"99":{"tf":1.0}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"62":{"tf":1.0}}}}}}}},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":1,"docs":{"7":{"tf":1.0}}}}},"s":{"df":11,"docs":{"104":{"tf":1.0},"113":{"tf":1.0},"115":{"tf":1.0},"117":{"tf":1.0},"201":{"tf":1.0},"212":{"tf":1.4142135623730951},"225":{"tf":1.0},"260":{"tf":1.0},"50":{"tf":1.0},"59":{"tf":1.0},"70":{"tf":1.0}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"64":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"v":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":2,"docs":{"116":{"tf":1.0},"21":{"tf":1.0}}}},"df":0,"docs":{}}}},"c":{"=":{"a":{"a":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"h":{"6":{"4":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"37":{"tf":1.0}},"p":{"df":1,"docs":{"37":{"tf":1.0}}}},"d":{"df":2,"docs":{"11":{"tf":1.4142135623730951},"25":{"tf":1.0}},"y":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"b":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"70":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":13,"docs":{"103":{"tf":2.6457513110645907},"111":{"tf":1.0},"114":{"tf":1.0},"163":{"tf":1.0},"171":{"tf":1.0},"174":{"tf":1.0},"179":{"tf":1.0},"180":{"tf":1.0},"185":{"tf":1.4142135623730951},"251":{"tf":1.0},"252":{"tf":1.0},"28":{"tf":1.0},"33":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"l":{"df":10,"docs":{"120":{"tf":1.7320508075688772},"121":{"tf":1.0},"122":{"tf":2.0},"123":{"tf":2.0},"124":{"tf":2.0},"260":{"tf":1.0},"261":{"tf":1.4142135623730951},"262":{"tf":1.4142135623730951},"263":{"tf":1.0},"274":{"tf":1.7320508075688772}}}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":1,"docs":{"64":{"tf":1.0}}}}}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":5,"docs":{"115":{"tf":1.4142135623730951},"226":{"tf":1.0},"228":{"tf":1.0},"58":{"tf":1.0},"82":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":3,"docs":{"197":{"tf":1.0},"226":{"tf":1.4142135623730951},"229":{"tf":1.0}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":1,"docs":{"201":{"tf":1.0}}}}}}},"h":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":4,"docs":{"106":{"tf":1.0},"274":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0}}}},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":4,"docs":{"115":{"tf":1.0},"228":{"tf":1.0},"229":{"tf":1.0},"6":{"tf":1.0}}}}}}},"n":{"c":{"df":4,"docs":{"159":{"tf":1.0},"19":{"tf":1.0},"209":{"tf":1.4142135623730951},"214":{"tf":1.0}}},"df":0,"docs":{},"g":{"df":65,"docs":{"10":{"tf":1.0},"104":{"tf":1.0},"105":{"tf":1.4142135623730951},"108":{"tf":1.0},"118":{"tf":1.7320508075688772},"119":{"tf":1.4142135623730951},"121":{"tf":1.0},"123":{"tf":1.4142135623730951},"124":{"tf":3.1622776601683795},"126":{"tf":1.4142135623730951},"145":{"tf":1.4142135623730951},"146":{"tf":1.0},"16":{"tf":1.0},"163":{"tf":1.0},"167":{"tf":1.0},"169":{"tf":1.7320508075688772},"170":{"tf":2.0},"171":{"tf":1.7320508075688772},"172":{"tf":1.4142135623730951},"175":{"tf":1.0},"180":{"tf":1.0},"184":{"tf":1.7320508075688772},"187":{"tf":1.0},"189":{"tf":1.0},"190":{"tf":1.7320508075688772},"193":{"tf":1.0},"197":{"tf":1.7320508075688772},"20":{"tf":1.4142135623730951},"200":{"tf":1.0},"201":{"tf":2.0},"21":{"tf":1.4142135623730951},"22":{"tf":1.0},"228":{"tf":1.0},"231":{"tf":1.4142135623730951},"233":{"tf":2.6457513110645907},"236":{"tf":1.4142135623730951},"242":{"tf":1.0},"249":{"tf":2.449489742783178},"25":{"tf":1.0},"250":{"tf":2.449489742783178},"252":{"tf":1.0},"26":{"tf":2.23606797749979},"263":{"tf":1.4142135623730951},"268":{"tf":3.0},"270":{"tf":2.0},"279":{"tf":1.0},"280":{"tf":1.0},"29":{"tf":1.0},"30":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951},"34":{"tf":1.0},"35":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"39":{"tf":1.7320508075688772},"40":{"tf":1.7320508075688772},"49":{"tf":1.0},"58":{"tf":1.4142135623730951},"64":{"tf":1.0},"7":{"tf":2.23606797749979},"81":{"tf":1.0},"82":{"tf":1.7320508075688772},"84":{"tf":2.23606797749979},"86":{"tf":2.449489742783178},"87":{"tf":1.0},"88":{"tf":1.0}},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{".":{"df":0,"docs":{},"m":{"d":{"df":2,"docs":{"268":{"tf":2.0},"7":{"tf":1.0}}},"df":0,"docs":{}}},"]":{"(":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{":":{"/":{"/":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"u":{"b":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"/":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"268":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"268":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}},"df":4,"docs":{"21":{"tf":1.0},"269":{"tf":1.0},"282":{"tf":1.0},"7":{"tf":1.0}}}}}}},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":3,"docs":{"122":{"tf":1.0},"271":{"tf":1.0},"274":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"t":{"df":3,"docs":{"215":{"tf":1.0},"216":{"tf":1.0},"258":{"tf":1.0}}}},"t":{"df":1,"docs":{"1":{"tf":1.0}}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"k":{"_":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"d":{"_":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"_":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"_":{"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":2,"docs":{"145":{"tf":1.4142135623730951},"146":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"97":{"tf":1.0}}}}}},"df":0,"docs":{}}}}},"df":35,"docs":{"104":{"tf":1.0},"105":{"tf":1.4142135623730951},"11":{"tf":1.4142135623730951},"12":{"tf":1.0},"122":{"tf":1.4142135623730951},"124":{"tf":1.0},"126":{"tf":1.0},"14":{"tf":1.0},"151":{"tf":1.4142135623730951},"152":{"tf":1.7320508075688772},"155":{"tf":1.0},"156":{"tf":1.0},"18":{"tf":1.0},"189":{"tf":1.0},"201":{"tf":1.0},"22":{"tf":1.0},"242":{"tf":1.0},"245":{"tf":1.4142135623730951},"26":{"tf":1.0},"274":{"tf":2.449489742783178},"282":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0},"51":{"tf":1.0},"58":{"tf":1.0},"62":{"tf":2.6457513110645907},"64":{"tf":2.23606797749979},"69":{"tf":1.0},"7":{"tf":1.0},"70":{"tf":1.0},"73":{"tf":2.6457513110645907},"81":{"tf":1.0},"96":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"268":{"tf":1.4142135623730951}}}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":17,"docs":{"124":{"tf":1.0},"18":{"tf":1.7320508075688772},"23":{"tf":2.0},"24":{"tf":1.7320508075688772},"25":{"tf":1.0},"270":{"tf":1.0},"28":{"tf":1.0},"29":{"tf":1.4142135623730951},"30":{"tf":1.0},"31":{"tf":1.7320508075688772},"32":{"tf":2.0},"33":{"tf":1.0},"34":{"tf":1.7320508075688772},"35":{"tf":1.0},"36":{"tf":2.0},"37":{"tf":1.0},"49":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":5,"docs":{"167":{"tf":1.0},"279":{"tf":1.0},"280":{"tf":1.7320508075688772},"29":{"tf":1.4142135623730951},"34":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":1,"docs":{"97":{"tf":1.0}}}}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"270":{"tf":1.0}}}}}},"i":{"df":0,"docs":{},"p":{"df":1,"docs":{"17":{"tf":1.4142135623730951}}}},"o":{"df":0,"docs":{},"i":{"c":{"df":5,"docs":{"152":{"tf":1.0},"217":{"tf":1.0},"226":{"tf":1.4142135623730951},"227":{"tf":1.0},"232":{"tf":1.0}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"s":{"df":11,"docs":{"126":{"tf":1.0},"165":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"208":{"tf":1.0},"225":{"tf":1.4142135623730951},"226":{"tf":1.0},"229":{"tf":1.0},"31":{"tf":1.0},"98":{"tf":1.0}}}},"s":{"df":0,"docs":{},"e":{"df":1,"docs":{"251":{"tf":1.0}},"n":{"df":5,"docs":{"138":{"tf":1.0},"143":{"tf":1.0},"164":{"tf":1.0},"199":{"tf":1.4142135623730951},"208":{"tf":1.4142135623730951}}}}}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":3,"docs":{"206":{"tf":1.4142135623730951},"217":{"tf":1.0},"218":{"tf":1.4142135623730951}}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":2,"docs":{"105":{"tf":1.0},"106":{"tf":1.0}}}}}},"i":{"df":18,"docs":{"11":{"tf":1.4142135623730951},"118":{"tf":1.0},"119":{"tf":1.0},"263":{"tf":2.0},"266":{"tf":1.4142135623730951},"270":{"tf":1.0},"271":{"tf":1.0},"274":{"tf":1.4142135623730951},"276":{"tf":1.0},"278":{"tf":1.0},"280":{"tf":1.0},"282":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0},"49":{"tf":1.0},"51":{"tf":1.0},"64":{"tf":1.4142135623730951},"92":{"tf":1.0}},"r":{"c":{"df":0,"docs":{},"l":{"df":1,"docs":{"263":{"tf":1.0}},"e":{"c":{"df":0,"docs":{},"i":{"df":3,"docs":{"263":{"tf":1.0},"274":{"tf":2.449489742783178},"277":{"tf":1.7320508075688772}}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"197":{"tf":1.0}}}},"df":0,"docs":{}},"m":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"170":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}}},"l":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"210":{"tf":1.0}}}},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"37":{"tf":1.4142135623730951}}}},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":1,"docs":{"6":{"tf":1.0}},"i":{"df":1,"docs":{"233":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"s":{"df":9,"docs":{"100":{"tf":1.0},"103":{"tf":1.4142135623730951},"108":{"tf":1.0},"109":{"tf":1.0},"112":{"tf":1.0},"131":{"tf":2.0},"132":{"tf":2.0},"98":{"tf":1.4142135623730951},"99":{"tf":1.0}},"i":{"c":{"df":1,"docs":{"277":{"tf":1.0}}},"df":0,"docs":{}},"p":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"223":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"n":{"df":2,"docs":{"240":{"tf":1.0},"279":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"172":{"tf":1.0}}}}},"r":{"df":3,"docs":{"229":{"tf":1.0},"279":{"tf":1.0},"60":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"226":{"tf":1.0}}}},"l":{"df":0,"docs":{},"i":{"df":3,"docs":{"132":{"tf":1.0},"64":{"tf":1.0},"7":{"tf":1.0}}}}}},"df":0,"docs":{}},"i":{"c":{"df":0,"docs":{},"k":{"df":5,"docs":{"117":{"tf":1.4142135623730951},"31":{"tf":2.0},"36":{"tf":2.0},"40":{"tf":1.4142135623730951},"73":{"tf":1.4142135623730951}}}},"df":1,"docs":{"117":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"x":{"c":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"p":{"b":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"df":1,"docs":{"31":{"tf":1.0}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":2,"docs":{"25":{"tf":1.0},"31":{"tf":1.0}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},":":{":":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"d":{"df":0,"docs":{},"s":{"_":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"268":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"283":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":31,"docs":{"10":{"tf":1.0},"105":{"tf":1.0},"107":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"11":{"tf":1.0},"134":{"tf":1.0},"179":{"tf":1.0},"186":{"tf":1.0},"192":{"tf":1.0},"193":{"tf":1.7320508075688772},"194":{"tf":1.7320508075688772},"196":{"tf":1.0},"197":{"tf":2.449489742783178},"198":{"tf":2.449489742783178},"199":{"tf":1.4142135623730951},"200":{"tf":2.8284271247461903},"201":{"tf":1.7320508075688772},"226":{"tf":1.0},"231":{"tf":2.8284271247461903},"234":{"tf":1.0},"237":{"tf":1.0},"238":{"tf":1.4142135623730951},"254":{"tf":2.0},"256":{"tf":1.4142135623730951},"257":{"tf":1.0},"268":{"tf":1.0},"283":{"tf":1.7320508075688772},"284":{"tf":1.4142135623730951},"31":{"tf":1.0},"58":{"tf":1.4142135623730951}}}}},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":3,"docs":{"262":{"tf":1.4142135623730951},"263":{"tf":1.4142135623730951},"92":{"tf":1.4142135623730951}}}}}},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":7,"docs":{"11":{"tf":1.7320508075688772},"12":{"tf":1.0},"34":{"tf":1.4142135623730951},"36":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0}}}},"s":{"df":0,"docs":{},"e":{"df":5,"docs":{"105":{"tf":1.0},"133":{"tf":1.0},"187":{"tf":1.0},"230":{"tf":1.0},"258":{"tf":1.0}}}}}},"o":{"d":{"df":0,"docs":{},"e":{"'":{"df":1,"docs":{"45":{"tf":1.0}}},"b":{"a":{"df":0,"docs":{},"s":{"df":2,"docs":{"143":{"tf":1.0},"65":{"tf":1.0}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":1,"docs":{"50":{"tf":1.0}}}}},"df":96,"docs":{"0":{"tf":1.0},"1":{"tf":1.0},"101":{"tf":1.0},"103":{"tf":3.3166247903554},"104":{"tf":2.0},"105":{"tf":1.7320508075688772},"106":{"tf":1.7320508075688772},"107":{"tf":2.0},"108":{"tf":2.6457513110645907},"109":{"tf":2.449489742783178},"111":{"tf":1.4142135623730951},"114":{"tf":1.4142135623730951},"115":{"tf":2.0},"116":{"tf":1.4142135623730951},"117":{"tf":2.0},"118":{"tf":1.7320508075688772},"120":{"tf":1.0},"125":{"tf":1.4142135623730951},"128":{"tf":1.4142135623730951},"132":{"tf":1.0},"133":{"tf":1.0},"140":{"tf":1.0},"145":{"tf":1.7320508075688772},"146":{"tf":1.0},"154":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0},"158":{"tf":1.4142135623730951},"159":{"tf":1.0},"163":{"tf":1.4142135623730951},"164":{"tf":1.0},"165":{"tf":1.0},"166":{"tf":1.0},"167":{"tf":2.6457513110645907},"169":{"tf":1.0},"17":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.7320508075688772},"172":{"tf":2.0},"174":{"tf":1.0},"193":{"tf":1.0},"194":{"tf":1.0},"2":{"tf":1.0},"200":{"tf":2.8284271247461903},"219":{"tf":1.0},"22":{"tf":1.0},"220":{"tf":2.0},"221":{"tf":1.4142135623730951},"222":{"tf":1.4142135623730951},"223":{"tf":1.4142135623730951},"224":{"tf":1.4142135623730951},"232":{"tf":1.0},"241":{"tf":2.0},"245":{"tf":1.0},"249":{"tf":1.4142135623730951},"251":{"tf":1.0},"253":{"tf":1.7320508075688772},"263":{"tf":1.0},"274":{"tf":3.1622776601683795},"279":{"tf":1.0},"28":{"tf":1.0},"282":{"tf":1.0},"283":{"tf":1.0},"31":{"tf":1.0},"33":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.7320508075688772},"43":{"tf":1.4142135623730951},"44":{"tf":1.7320508075688772},"45":{"tf":2.8284271247461903},"46":{"tf":2.23606797749979},"49":{"tf":1.0},"50":{"tf":1.4142135623730951},"58":{"tf":1.7320508075688772},"60":{"tf":1.7320508075688772},"63":{"tf":1.4142135623730951},"64":{"tf":2.0},"65":{"tf":1.0},"66":{"tf":1.0},"67":{"tf":1.0},"68":{"tf":1.0},"69":{"tf":1.4142135623730951},"7":{"tf":2.6457513110645907},"70":{"tf":2.0},"72":{"tf":2.23606797749979},"74":{"tf":1.7320508075688772},"8":{"tf":1.4142135623730951},"81":{"tf":1.4142135623730951},"82":{"tf":1.4142135623730951},"83":{"tf":1.0},"9":{"tf":1.4142135623730951},"90":{"tf":1.4142135623730951},"92":{"tf":1.0},"93":{"tf":1.0},"95":{"tf":1.0},"98":{"tf":1.0}}}},"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"229":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"l":{"a":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"274":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":15,"docs":{"0":{"tf":1.0},"189":{"tf":1.0},"194":{"tf":1.0},"203":{"tf":1.0},"226":{"tf":1.7320508075688772},"229":{"tf":1.0},"232":{"tf":1.7320508075688772},"242":{"tf":1.4142135623730951},"244":{"tf":1.0},"245":{"tf":1.4142135623730951},"248":{"tf":1.4142135623730951},"259":{"tf":1.7320508075688772},"45":{"tf":1.0},"6":{"tf":1.4142135623730951},"86":{"tf":2.23606797749979}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"'":{"df":1,"docs":{"245":{"tf":1.0}}},"/":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"228":{"tf":1.0}}}}}}}},"df":0,"docs":{}}}},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"128":{"tf":1.0}}}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"283":{"tf":1.4142135623730951}}}},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"n":{"df":2,"docs":{"250":{"tf":1.0},"84":{"tf":1.0}}}}}},"m":{"b":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"58":{"tf":1.0}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":6,"docs":{"145":{"tf":1.4142135623730951},"146":{"tf":1.4142135623730951},"228":{"tf":1.0},"249":{"tf":1.0},"274":{"tf":1.4142135623730951},"39":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":5,"docs":{"116":{"tf":1.0},"205":{"tf":1.0},"261":{"tf":1.0},"281":{"tf":1.0},"282":{"tf":1.0}}},"m":{"a":{"df":1,"docs":{"17":{"tf":1.0}},"n":{"d":{".":{"df":0,"docs":{},"t":{"a":{"b":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"283":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":12,"docs":{"104":{"tf":1.0},"11":{"tf":1.0},"126":{"tf":1.0},"165":{"tf":1.0},"172":{"tf":1.0},"19":{"tf":1.0},"22":{"tf":1.0},"226":{"tf":1.4142135623730951},"231":{"tf":2.6457513110645907},"237":{"tf":1.0},"268":{"tf":1.0},"283":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"29":{"tf":1.0},"34":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"t":{"df":10,"docs":{"253":{"tf":1.0},"267":{"tf":1.0},"268":{"tf":1.4142135623730951},"270":{"tf":1.0},"271":{"tf":1.0},"274":{"tf":1.7320508075688772},"282":{"tf":1.0},"30":{"tf":1.4142135623730951},"35":{"tf":1.4142135623730951},"7":{"tf":1.7320508075688772}}}},"o":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"df":1,"docs":{"94":{"tf":1.0}}}}}},"df":6,"docs":{"154":{"tf":1.0},"22":{"tf":1.0},"249":{"tf":1.0},"282":{"tf":1.0},"81":{"tf":1.0},"84":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"264":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"n":{"df":6,"docs":{"189":{"tf":1.0},"226":{"tf":1.4142135623730951},"231":{"tf":1.0},"254":{"tf":1.4142135623730951},"3":{"tf":1.4142135623730951},"64":{"tf":1.0}}}}},"p":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"208":{"tf":1.0}}}},"df":0,"docs":{},"r":{"df":6,"docs":{"151":{"tf":1.4142135623730951},"152":{"tf":1.4142135623730951},"155":{"tf":1.0},"156":{"tf":1.0},"245":{"tf":1.4142135623730951},"62":{"tf":1.0}}},"t":{"df":6,"docs":{"116":{"tf":1.0},"161":{"tf":1.0},"162":{"tf":1.0},"37":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":29,"docs":{"105":{"tf":1.0},"109":{"tf":1.0},"114":{"tf":1.0},"161":{"tf":1.0},"163":{"tf":1.7320508075688772},"164":{"tf":1.0},"171":{"tf":1.4142135623730951},"172":{"tf":2.0},"220":{"tf":1.0},"221":{"tf":1.0},"222":{"tf":1.0},"223":{"tf":1.4142135623730951},"224":{"tf":1.4142135623730951},"251":{"tf":1.0},"252":{"tf":1.4142135623730951},"253":{"tf":1.0},"278":{"tf":1.4142135623730951},"279":{"tf":1.0},"28":{"tf":1.0},"280":{"tf":2.0},"33":{"tf":1.0},"37":{"tf":1.4142135623730951},"45":{"tf":1.4142135623730951},"46":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.4142135623730951},"59":{"tf":1.7320508075688772},"60":{"tf":2.0},"73":{"tf":2.23606797749979}}}},"l":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":2,"docs":{"263":{"tf":1.0},"60":{"tf":1.4142135623730951}},"t":{"df":1,"docs":{"60":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":11,"docs":{"10":{"tf":1.0},"106":{"tf":1.0},"11":{"tf":1.0},"115":{"tf":1.0},"119":{"tf":1.0},"169":{"tf":1.0},"187":{"tf":1.0},"200":{"tf":1.0},"22":{"tf":1.0},"270":{"tf":1.0},"84":{"tf":1.0}},"e":{"_":{"df":0,"docs":{},"o":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":1,"docs":{"283":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"x":{"df":12,"docs":{"106":{"tf":1.7320508075688772},"115":{"tf":1.0},"116":{"tf":1.0},"140":{"tf":1.0},"158":{"tf":1.0},"200":{"tf":1.0},"220":{"tf":1.0},"229":{"tf":1.0},"233":{"tf":1.0},"280":{"tf":1.0},"78":{"tf":1.4142135623730951},"81":{"tf":1.0}}}},"i":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"262":{"tf":1.0}}}}},"c":{"df":6,"docs":{"10":{"tf":1.0},"197":{"tf":1.0},"225":{"tf":1.4142135623730951},"233":{"tf":1.0},"45":{"tf":1.0},"62":{"tf":1.0}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"n":{"df":127,"docs":{"0":{"tf":2.0},"1":{"tf":1.0},"101":{"tf":2.449489742783178},"102":{"tf":2.0},"103":{"tf":2.23606797749979},"104":{"tf":1.7320508075688772},"105":{"tf":1.7320508075688772},"106":{"tf":1.4142135623730951},"108":{"tf":2.449489742783178},"109":{"tf":2.23606797749979},"11":{"tf":1.0},"112":{"tf":1.0},"117":{"tf":1.0},"118":{"tf":1.0},"119":{"tf":1.4142135623730951},"12":{"tf":1.0},"120":{"tf":1.7320508075688772},"122":{"tf":1.4142135623730951},"123":{"tf":2.8284271247461903},"124":{"tf":1.4142135623730951},"125":{"tf":1.0},"14":{"tf":1.0},"141":{"tf":1.0},"145":{"tf":1.0},"149":{"tf":1.0},"150":{"tf":1.0},"151":{"tf":1.0},"157":{"tf":1.0},"159":{"tf":1.0},"16":{"tf":1.7320508075688772},"161":{"tf":1.0},"162":{"tf":1.0},"163":{"tf":1.0},"165":{"tf":1.7320508075688772},"166":{"tf":1.0},"167":{"tf":2.8284271247461903},"169":{"tf":1.0},"17":{"tf":1.0},"170":{"tf":1.7320508075688772},"171":{"tf":2.449489742783178},"172":{"tf":2.23606797749979},"175":{"tf":1.4142135623730951},"194":{"tf":1.4142135623730951},"198":{"tf":1.0},"20":{"tf":1.4142135623730951},"200":{"tf":4.242640687119285},"21":{"tf":1.0},"219":{"tf":1.7320508075688772},"22":{"tf":1.4142135623730951},"220":{"tf":2.449489742783178},"221":{"tf":2.23606797749979},"222":{"tf":2.449489742783178},"224":{"tf":1.4142135623730951},"225":{"tf":3.0},"226":{"tf":3.0},"228":{"tf":2.8284271247461903},"229":{"tf":2.8284271247461903},"23":{"tf":3.0},"230":{"tf":1.7320508075688772},"232":{"tf":1.4142135623730951},"233":{"tf":2.23606797749979},"234":{"tf":1.0},"235":{"tf":1.0},"236":{"tf":1.7320508075688772},"237":{"tf":1.7320508075688772},"24":{"tf":1.0},"243":{"tf":1.0},"244":{"tf":1.4142135623730951},"245":{"tf":2.0},"246":{"tf":1.0},"247":{"tf":1.0},"25":{"tf":2.0},"251":{"tf":2.0},"252":{"tf":1.0},"253":{"tf":2.23606797749979},"254":{"tf":1.0},"256":{"tf":1.0},"257":{"tf":2.6457513110645907},"258":{"tf":1.0},"259":{"tf":1.7320508075688772},"26":{"tf":2.449489742783178},"260":{"tf":1.4142135623730951},"262":{"tf":1.4142135623730951},"264":{"tf":1.4142135623730951},"268":{"tf":1.0},"271":{"tf":1.0},"272":{"tf":1.4142135623730951},"274":{"tf":2.0},"278":{"tf":1.0},"28":{"tf":1.0},"282":{"tf":1.0},"283":{"tf":1.4142135623730951},"284":{"tf":1.0},"29":{"tf":2.0},"30":{"tf":1.4142135623730951},"31":{"tf":2.8284271247461903},"32":{"tf":3.1622776601683795},"33":{"tf":1.0},"34":{"tf":2.23606797749979},"35":{"tf":1.4142135623730951},"36":{"tf":2.8284271247461903},"37":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.4142135623730951},"41":{"tf":2.0},"43":{"tf":1.7320508075688772},"45":{"tf":1.7320508075688772},"46":{"tf":2.0},"48":{"tf":1.4142135623730951},"49":{"tf":2.23606797749979},"54":{"tf":1.4142135623730951},"58":{"tf":1.0},"59":{"tf":1.0},"68":{"tf":2.0},"69":{"tf":1.7320508075688772},"7":{"tf":1.4142135623730951},"73":{"tf":3.1622776601683795},"74":{"tf":3.1622776601683795},"75":{"tf":2.6457513110645907},"76":{"tf":1.4142135623730951},"77":{"tf":1.4142135623730951},"78":{"tf":2.0},"79":{"tf":1.0},"80":{"tf":1.0},"81":{"tf":1.4142135623730951},"82":{"tf":1.0},"84":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"'":{"df":6,"docs":{"104":{"tf":1.0},"108":{"tf":1.0},"21":{"tf":1.0},"219":{"tf":1.0},"70":{"tf":2.23606797749979},"73":{"tf":1.0}}},">":{":":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"45":{"tf":1.0}}}}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"103":{"tf":1.4142135623730951}},"e":{">":{".":{"df":0,"docs":{},"u":{"d":{"df":0,"docs":{},"l":{"df":1,"docs":{"103":{"tf":1.0}}}},"df":0,"docs":{}}},":":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":1,"docs":{"108":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"108":{"tf":1.0}}}}}}},"_":{"df":0,"docs":{},"m":{"df":0,"docs":{},"s":{"df":0,"docs":{},"g":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":2,"docs":{"105":{"tf":1.0},"106":{"tf":1.0}}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{".":{"df":0,"docs":{},"h":{"df":1,"docs":{"109":{"tf":1.0}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"54":{"tf":1.0}}}},"s":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"34":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}},"t":{"a":{"df":0,"docs":{},"r":{".":{"df":0,"docs":{},"x":{"df":0,"docs":{},"z":{"df":1,"docs":{"271":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"/":{"<":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"103":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"_":{"c":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"70":{"tf":1.0},"72":{"tf":1.0}},"e":{">":{"/":{"<":{"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"_":{"c":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{">":{".":{"df":0,"docs":{},"u":{"d":{"df":0,"docs":{},"l":{"df":1,"docs":{"69":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"/":{"df":0,"docs":{},"s":{"df":0,"docs":{},"r":{"c":{"/":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"/":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"x":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":1,"docs":{"70":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":1,"docs":{"70":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":1,"docs":{"72":{"tf":1.0}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":1,"docs":{"70":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":1,"docs":{"69":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"c":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"/":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"70":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"df":1,"docs":{"39":{"tf":1.0}}}}}}},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"l":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"18":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"m":{"df":0,"docs":{},"y":{"c":{"df":0,"docs":{},"r":{"df":1,"docs":{"60":{"tf":1.0}}}},"df":0,"docs":{}}}},":":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{".":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":1,"docs":{"1":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"[":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":1,"docs":{"200":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"224":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":1,"docs":{"138":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":2,"docs":{"63":{"tf":1.0},"64":{"tf":1.0}}}}}}}}},"n":{"c":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"99":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":3,"docs":{"165":{"tf":1.0},"220":{"tf":1.0},"226":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"n":{"df":3,"docs":{"140":{"tf":1.0},"225":{"tf":1.0},"59":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":3,"docs":{"187":{"tf":1.0},"206":{"tf":1.0},"226":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":2,"docs":{"166":{"tf":1.0},"235":{"tf":1.0}}}}}},"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"4":{"tf":1.0}}}}},"df":7,"docs":{"144":{"tf":1.0},"145":{"tf":1.0},"146":{"tf":1.0},"153":{"tf":1.0},"168":{"tf":1.0},"181":{"tf":1.0},"211":{"tf":1.0}},"f":{"df":0,"docs":{},"i":{"d":{"df":3,"docs":{"152":{"tf":1.0},"172":{"tf":1.0},"201":{"tf":1.0}}},"df":0,"docs":{},"g":{"df":3,"docs":{"11":{"tf":1.0},"277":{"tf":1.0},"37":{"tf":1.0}},"u":{"df":0,"docs":{},"r":{"df":16,"docs":{"105":{"tf":1.0},"109":{"tf":1.0},"12":{"tf":2.0},"13":{"tf":1.4142135623730951},"18":{"tf":1.0},"184":{"tf":1.7320508075688772},"187":{"tf":1.0},"20":{"tf":1.0},"200":{"tf":1.4142135623730951},"22":{"tf":1.0},"25":{"tf":1.0},"263":{"tf":1.7320508075688772},"37":{"tf":1.4142135623730951},"49":{"tf":1.0},"59":{"tf":1.0},"69":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"m":{"df":6,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"13":{"tf":1.0},"22":{"tf":1.0},"69":{"tf":1.0},"70":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"165":{"tf":1.0},"175":{"tf":1.0},"233":{"tf":1.0}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":1,"docs":{"189":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"237":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"s":{"df":1,"docs":{"61":{"tf":1.0}}}}},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":1,"docs":{"106":{"tf":1.0}}}}}},"df":0,"docs":{}}},"n":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"(":{"\"":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"62":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"_":{"b":{"a":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"h":{"(":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"l":{"_":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"m":{"a":{"df":1,"docs":{"62":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":18,"docs":{"1":{"tf":1.0},"126":{"tf":1.4142135623730951},"130":{"tf":1.0},"133":{"tf":1.0},"154":{"tf":1.0},"155":{"tf":1.0},"183":{"tf":1.0},"189":{"tf":1.0},"190":{"tf":1.4142135623730951},"226":{"tf":1.0},"229":{"tf":1.0},"276":{"tf":1.0},"283":{"tf":1.7320508075688772},"78":{"tf":1.0},"79":{"tf":2.6457513110645907},"81":{"tf":1.4142135623730951},"83":{"tf":1.0},"87":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"62":{"tf":1.0}}}}}}}}}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":4,"docs":{"165":{"tf":1.0},"166":{"tf":1.0},"209":{"tf":1.0},"210":{"tf":1.4142135623730951}}}},"r":{"df":0,"docs":{},"v":{"df":2,"docs":{"152":{"tf":1.0},"64":{"tf":1.0}}}}},"i":{"d":{"df":29,"docs":{"123":{"tf":1.0},"137":{"tf":1.0},"142":{"tf":1.0},"151":{"tf":1.0},"163":{"tf":1.0},"167":{"tf":1.0},"179":{"tf":1.0},"190":{"tf":1.4142135623730951},"192":{"tf":1.0},"195":{"tf":1.0},"200":{"tf":1.0},"206":{"tf":1.0},"207":{"tf":1.0},"217":{"tf":1.0},"220":{"tf":1.0},"228":{"tf":1.4142135623730951},"229":{"tf":1.0},"231":{"tf":1.0},"233":{"tf":1.4142135623730951},"262":{"tf":1.0},"263":{"tf":1.0},"274":{"tf":1.0},"276":{"tf":1.0},"51":{"tf":1.0},"59":{"tf":1.0},"6":{"tf":1.0},"64":{"tf":1.0},"75":{"tf":1.0},"82":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":4,"docs":{"260":{"tf":1.0},"58":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0}}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":4,"docs":{"103":{"tf":1.0},"145":{"tf":1.0},"162":{"tf":1.0},"61":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"183":{"tf":1.0}}}},"t":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"93":{"tf":1.0}}}}},"df":3,"docs":{"117":{"tf":1.0},"20":{"tf":1.0},"94":{"tf":1.0}},"r":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"187":{"tf":1.0},"226":{"tf":1.0}}}}}},"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"131":{"tf":1.0}}}},"df":0,"docs":{}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":3,"docs":{"11":{"tf":1.0},"41":{"tf":1.0},"7":{"tf":1.0}}}},"m":{"df":56,"docs":{"104":{"tf":1.0},"107":{"tf":1.4142135623730951},"108":{"tf":1.0},"109":{"tf":1.0},"118":{"tf":1.4142135623730951},"123":{"tf":1.4142135623730951},"14":{"tf":1.0},"145":{"tf":2.0},"146":{"tf":1.7320508075688772},"154":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0},"158":{"tf":1.0},"16":{"tf":1.4142135623730951},"161":{"tf":1.0},"162":{"tf":1.0},"164":{"tf":1.0},"165":{"tf":1.4142135623730951},"166":{"tf":1.4142135623730951},"167":{"tf":2.0},"169":{"tf":2.0},"170":{"tf":2.449489742783178},"171":{"tf":2.0},"172":{"tf":1.4142135623730951},"174":{"tf":1.0},"175":{"tf":1.4142135623730951},"18":{"tf":1.7320508075688772},"193":{"tf":1.4142135623730951},"194":{"tf":1.7320508075688772},"20":{"tf":1.7320508075688772},"200":{"tf":1.4142135623730951},"201":{"tf":1.0},"22":{"tf":1.0},"223":{"tf":1.0},"228":{"tf":1.0},"237":{"tf":1.0},"245":{"tf":1.0},"251":{"tf":1.0},"253":{"tf":1.0},"268":{"tf":2.0},"272":{"tf":1.4142135623730951},"273":{"tf":1.0},"274":{"tf":2.449489742783178},"37":{"tf":1.4142135623730951},"43":{"tf":1.4142135623730951},"49":{"tf":1.0},"50":{"tf":1.0},"51":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"69":{"tf":1.0},"70":{"tf":1.0},"74":{"tf":1.0},"78":{"tf":1.0},"83":{"tf":1.0},"90":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"r":{".":{"df":0,"docs":{},"f":{"df":0,"docs":{},"x":{"a":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"283":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"200":{"tf":1.0}}}}}}},"t":{"a":{"c":{"df":0,"docs":{},"t":{"df":8,"docs":{"1":{"tf":1.4142135623730951},"21":{"tf":1.0},"245":{"tf":1.0},"25":{"tf":1.0},"266":{"tf":1.0},"274":{"tf":1.4142135623730951},"31":{"tf":1.0},"36":{"tf":1.0}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":27,"docs":{"105":{"tf":2.0},"167":{"tf":1.0},"20":{"tf":1.0},"221":{"tf":1.0},"222":{"tf":1.0},"223":{"tf":1.4142135623730951},"226":{"tf":1.0},"232":{"tf":1.0},"248":{"tf":1.0},"249":{"tf":1.0},"251":{"tf":1.0},"252":{"tf":1.7320508075688772},"271":{"tf":1.4142135623730951},"274":{"tf":1.0},"28":{"tf":1.0},"33":{"tf":1.0},"39":{"tf":1.0},"45":{"tf":1.0},"48":{"tf":1.0},"60":{"tf":1.4142135623730951},"61":{"tf":1.0},"69":{"tf":1.0},"70":{"tf":1.4142135623730951},"72":{"tf":1.0},"73":{"tf":1.0},"74":{"tf":1.0},"98":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":12,"docs":{"126":{"tf":1.0},"13":{"tf":1.0},"189":{"tf":1.7320508075688772},"193":{"tf":1.0},"194":{"tf":1.0},"196":{"tf":1.0},"201":{"tf":2.449489742783178},"226":{"tf":1.0},"284":{"tf":1.0},"62":{"tf":1.0},"70":{"tf":1.4142135623730951},"98":{"tf":1.0}},"i":{"df":1,"docs":{"227":{"tf":1.0}}}}},"x":{"df":0,"docs":{},"t":{"df":10,"docs":{"136":{"tf":1.0},"140":{"tf":1.0},"149":{"tf":1.0},"161":{"tf":1.0},"174":{"tf":1.0},"177":{"tf":1.0},"187":{"tf":1.0},"200":{"tf":1.0},"201":{"tf":1.0},"203":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":9,"docs":{"11":{"tf":1.0},"120":{"tf":1.0},"141":{"tf":1.0},"145":{"tf":1.0},"167":{"tf":1.4142135623730951},"197":{"tf":1.0},"198":{"tf":1.0},"226":{"tf":1.0},"7":{"tf":1.0}}}}},"r":{"a":{"df":1,"docs":{"274":{"tf":1.0}}},"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"205":{"tf":1.0},"3":{"tf":1.0}},"o":{"df":0,"docs":{},"r":{"df":4,"docs":{"274":{"tf":1.0},"41":{"tf":1.0},"6":{"tf":1.4142135623730951},"7":{"tf":1.0}}}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"l":{"df":6,"docs":{"184":{"tf":1.0},"201":{"tf":1.4142135623730951},"208":{"tf":1.0},"283":{"tf":1.4142135623730951},"61":{"tf":1.0},"77":{"tf":1.0}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":6,"docs":{"117":{"tf":1.0},"165":{"tf":1.0},"25":{"tf":1.0},"257":{"tf":1.0},"281":{"tf":1.0},"79":{"tf":1.0}}},"t":{"df":7,"docs":{"107":{"tf":1.0},"137":{"tf":1.0},"69":{"tf":1.0},"91":{"tf":1.0},"93":{"tf":1.4142135623730951},"96":{"tf":1.0},"99":{"tf":1.0}}}},"r":{"df":0,"docs":{},"s":{"df":2,"docs":{"105":{"tf":1.0},"106":{"tf":1.0}}},"t":{"df":5,"docs":{"101":{"tf":1.0},"103":{"tf":1.4142135623730951},"105":{"tf":1.0},"163":{"tf":1.0},"284":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"104":{"tf":1.0}}},"df":0,"docs":{}}}}},"o":{"df":0,"docs":{},"r":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":3,"docs":{"225":{"tf":1.0},"226":{"tf":2.23606797749979},"241":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"i":{"df":13,"docs":{"18":{"tf":1.0},"199":{"tf":1.0},"2":{"tf":1.0},"20":{"tf":1.0},"220":{"tf":1.0},"250":{"tf":1.0},"267":{"tf":1.7320508075688772},"273":{"tf":1.4142135623730951},"282":{"tf":1.0},"45":{"tf":1.0},"64":{"tf":1.0},"70":{"tf":1.0},"73":{"tf":2.0}}}},"r":{"df":0,"docs":{},"e":{"df":7,"docs":{"0":{"tf":1.0},"103":{"tf":1.7320508075688772},"105":{"tf":1.4142135623730951},"274":{"tf":1.0},"43":{"tf":1.4142135623730951},"6":{"tf":1.0},"78":{"tf":1.0}}},"r":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":8,"docs":{"11":{"tf":1.0},"126":{"tf":1.4142135623730951},"14":{"tf":1.0},"222":{"tf":1.0},"229":{"tf":1.0},"239":{"tf":1.0},"267":{"tf":1.0},"79":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":11,"docs":{"122":{"tf":1.0},"13":{"tf":1.0},"22":{"tf":1.4142135623730951},"224":{"tf":1.4142135623730951},"231":{"tf":1.0},"233":{"tf":1.4142135623730951},"250":{"tf":1.0},"263":{"tf":1.0},"274":{"tf":1.0},"70":{"tf":1.4142135623730951},"73":{"tf":1.0}}}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"d":{"df":12,"docs":{"106":{"tf":1.7320508075688772},"108":{"tf":1.0},"131":{"tf":1.4142135623730951},"167":{"tf":1.0},"223":{"tf":1.0},"242":{"tf":1.0},"244":{"tf":1.0},"274":{"tf":1.0},"39":{"tf":1.0},"46":{"tf":1.0},"48":{"tf":1.0},"73":{"tf":1.0}}},"df":0,"docs":{}}}}}},"u":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":14,"docs":{"116":{"tf":1.4142135623730951},"132":{"tf":1.0},"134":{"tf":1.0},"148":{"tf":1.0},"149":{"tf":2.0},"150":{"tf":1.0},"151":{"tf":2.23606797749979},"152":{"tf":1.4142135623730951},"154":{"tf":1.4142135623730951},"155":{"tf":1.4142135623730951},"156":{"tf":1.4142135623730951},"157":{"tf":1.0},"158":{"tf":1.0},"159":{"tf":1.4142135623730951}}}}}}},"s":{"df":0,"docs":{},"t":{"df":3,"docs":{"105":{"tf":1.0},"115":{"tf":1.0},"150":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"60":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"214":{"tf":1.0},"82":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":3,"docs":{"250":{"tf":2.0},"82":{"tf":1.0},"84":{"tf":2.449489742783178}}}}}},"p":{"df":0,"docs":{},"l":{"df":7,"docs":{"116":{"tf":1.0},"206":{"tf":1.0},"208":{"tf":1.0},"225":{"tf":1.0},"226":{"tf":1.0},"228":{"tf":1.0},"229":{"tf":1.0}}}},"r":{"df":0,"docs":{},"s":{"df":2,"docs":{"105":{"tf":1.0},"115":{"tf":1.0}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":2,"docs":{"41":{"tf":1.0},"50":{"tf":2.0}}}},"df":4,"docs":{"236":{"tf":1.0},"268":{"tf":1.0},"7":{"tf":1.0},"84":{"tf":1.0}}}}}},"p":{"df":0,"docs":{},"p":{"=":{"c":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":1,"docs":{"37":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"r":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":2,"docs":{"117":{"tf":1.4142135623730951},"205":{"tf":1.0}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"u":{"d":{"df":0,"docs":{},"l":{"df":1,"docs":{"70":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"t":{"df":0,"docs":{},"e":{"'":{"df":4,"docs":{"105":{"tf":1.0},"106":{"tf":1.4142135623730951},"64":{"tf":1.0},"69":{"tf":1.0}}},":":{":":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"106":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{">":{"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":1,"docs":{"73":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":29,"docs":{"103":{"tf":2.0},"105":{"tf":3.0},"106":{"tf":1.4142135623730951},"107":{"tf":1.0},"116":{"tf":1.4142135623730951},"122":{"tf":1.0},"123":{"tf":1.7320508075688772},"198":{"tf":1.4142135623730951},"221":{"tf":2.0},"222":{"tf":1.0},"225":{"tf":1.0},"237":{"tf":1.0},"238":{"tf":1.0},"244":{"tf":1.4142135623730951},"245":{"tf":1.0},"246":{"tf":1.0},"251":{"tf":1.0},"252":{"tf":1.4142135623730951},"28":{"tf":1.0},"281":{"tf":2.0},"282":{"tf":1.0},"286":{"tf":1.0},"33":{"tf":1.0},"43":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":2.449489742783178},"60":{"tf":2.6457513110645907},"64":{"tf":2.449489742783178},"69":{"tf":2.6457513110645907}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":40,"docs":{"104":{"tf":1.0},"108":{"tf":1.0},"11":{"tf":1.0},"118":{"tf":1.0},"129":{"tf":1.4142135623730951},"13":{"tf":1.4142135623730951},"130":{"tf":1.0},"131":{"tf":1.4142135623730951},"145":{"tf":1.4142135623730951},"167":{"tf":1.4142135623730951},"178":{"tf":1.0},"18":{"tf":1.0},"180":{"tf":1.0},"190":{"tf":1.0},"194":{"tf":1.0},"200":{"tf":1.0},"229":{"tf":1.0},"241":{"tf":1.0},"244":{"tf":1.0},"245":{"tf":1.7320508075688772},"246":{"tf":1.0},"249":{"tf":1.4142135623730951},"253":{"tf":1.0},"268":{"tf":2.6457513110645907},"269":{"tf":2.0},"270":{"tf":1.0},"274":{"tf":1.7320508075688772},"283":{"tf":1.7320508075688772},"29":{"tf":1.0},"34":{"tf":1.0},"39":{"tf":1.0},"60":{"tf":1.0},"62":{"tf":1.0},"69":{"tf":1.0},"70":{"tf":1.4142135623730951},"72":{"tf":1.0},"73":{"tf":1.4142135623730951},"75":{"tf":1.0},"78":{"tf":1.0},"82":{"tf":1.0}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"/":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"17":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"n":{"df":4,"docs":{"180":{"tf":1.0},"182":{"tf":1.0},"183":{"tf":1.0},"184":{"tf":1.0}}}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"n":{".":{"df":0,"docs":{},"y":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":1,"docs":{"40":{"tf":1.0}}}}}},"df":1,"docs":{"266":{"tf":1.4142135623730951}}},"s":{"df":0,"docs":{},"s":{"df":6,"docs":{"220":{"tf":1.0},"225":{"tf":1.0},"278":{"tf":1.0},"279":{"tf":1.0},"280":{"tf":2.0},"51":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":1,"docs":{"25":{"tf":1.0}}}}},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"/":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":1,"docs":{"226":{"tf":1.0}}}}}},"df":1,"docs":{"282":{"tf":1.0}},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":1,"docs":{"283":{"tf":1.0}},"h":{"df":2,"docs":{"278":{"tf":1.0},"282":{"tf":1.0}}}}},"df":0,"docs":{}}}}}}}},"s":{"a":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"k":{"df":1,"docs":{"186":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":56,"docs":{"101":{"tf":1.0},"103":{"tf":1.0},"104":{"tf":1.0},"11":{"tf":1.0},"111":{"tf":1.0},"121":{"tf":1.0},"126":{"tf":1.0},"145":{"tf":1.0},"146":{"tf":1.0},"150":{"tf":1.4142135623730951},"155":{"tf":1.0},"156":{"tf":1.0},"161":{"tf":1.7320508075688772},"163":{"tf":1.0},"166":{"tf":1.0},"167":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.4142135623730951},"175":{"tf":2.0},"187":{"tf":1.0},"193":{"tf":1.0},"196":{"tf":1.0},"220":{"tf":1.0},"228":{"tf":1.4142135623730951},"229":{"tf":1.0},"231":{"tf":1.0},"233":{"tf":1.4142135623730951},"242":{"tf":1.0},"245":{"tf":1.4142135623730951},"248":{"tf":1.0},"257":{"tf":1.4142135623730951},"258":{"tf":1.0},"26":{"tf":1.0},"260":{"tf":1.0},"261":{"tf":1.4142135623730951},"262":{"tf":1.4142135623730951},"263":{"tf":1.0},"264":{"tf":1.0},"268":{"tf":1.0},"274":{"tf":1.7320508075688772},"276":{"tf":1.0},"280":{"tf":1.0},"38":{"tf":1.0},"40":{"tf":1.0},"44":{"tf":1.0},"49":{"tf":1.0},"50":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"6":{"tf":1.0},"65":{"tf":1.4142135623730951},"66":{"tf":1.0},"67":{"tf":1.0},"7":{"tf":1.0},"84":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":4,"docs":{"133":{"tf":1.0},"184":{"tf":1.0},"222":{"tf":1.4142135623730951},"268":{"tf":1.0}}}}}},"t":{"df":4,"docs":{"253":{"tf":1.0},"268":{"tf":1.4142135623730951},"270":{"tf":1.0},"59":{"tf":1.0}}}},"y":{"c":{"df":0,"docs":{},"l":{"df":3,"docs":{"121":{"tf":1.0},"180":{"tf":1.0},"183":{"tf":1.0}}}},"df":0,"docs":{}}},"d":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":2,"docs":{"205":{"tf":1.0},"274":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":2,"docs":{"17":{"tf":1.4142135623730951},"280":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"h":{"b":{"df":0,"docs":{},"o":{"a":{"df":0,"docs":{},"r":{"d":{"df":2,"docs":{"203":{"tf":1.0},"218":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":1,"docs":{"200":{"tf":1.0}}}},"t":{"a":{"b":{"a":{"df":0,"docs":{},"s":{"df":31,"docs":{"130":{"tf":1.0},"134":{"tf":1.0},"140":{"tf":1.4142135623730951},"148":{"tf":1.0},"149":{"tf":2.23606797749979},"150":{"tf":1.0},"151":{"tf":3.1622776601683795},"152":{"tf":2.23606797749979},"154":{"tf":1.7320508075688772},"155":{"tf":1.4142135623730951},"156":{"tf":1.7320508075688772},"157":{"tf":1.7320508075688772},"158":{"tf":2.23606797749979},"159":{"tf":1.4142135623730951},"203":{"tf":1.4142135623730951},"205":{"tf":2.0},"206":{"tf":1.7320508075688772},"208":{"tf":2.0},"209":{"tf":2.0},"212":{"tf":2.0},"213":{"tf":1.0},"229":{"tf":1.0},"245":{"tf":1.0},"248":{"tf":1.0},"264":{"tf":1.0},"268":{"tf":1.0},"78":{"tf":1.0},"79":{"tf":1.0},"81":{"tf":1.7320508075688772},"84":{"tf":1.0},"86":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":36,"docs":{"105":{"tf":1.0},"106":{"tf":2.23606797749979},"140":{"tf":1.0},"145":{"tf":1.4142135623730951},"150":{"tf":1.4142135623730951},"154":{"tf":1.4142135623730951},"155":{"tf":1.4142135623730951},"156":{"tf":1.4142135623730951},"157":{"tf":1.0},"158":{"tf":2.8284271247461903},"159":{"tf":1.4142135623730951},"177":{"tf":1.4142135623730951},"178":{"tf":1.0},"179":{"tf":2.449489742783178},"180":{"tf":1.4142135623730951},"183":{"tf":1.0},"184":{"tf":1.0},"185":{"tf":1.4142135623730951},"189":{"tf":1.4142135623730951},"201":{"tf":2.0},"209":{"tf":1.0},"212":{"tf":1.0},"231":{"tf":1.0},"232":{"tf":1.0},"242":{"tf":1.4142135623730951},"254":{"tf":2.0},"255":{"tf":1.0},"259":{"tf":1.4142135623730951},"264":{"tf":1.0},"283":{"tf":1.4142135623730951},"79":{"tf":1.0},"80":{"tf":1.0},"82":{"tf":1.0},"84":{"tf":1.4142135623730951},"86":{"tf":1.0},"89":{"tf":1.0}}},"df":0,"docs":{},"e":{"a":{"d":{"df":1,"docs":{"62":{"tf":1.0}}},"df":0,"docs":{}},"df":16,"docs":{"121":{"tf":1.0},"139":{"tf":1.0},"148":{"tf":1.0},"160":{"tf":1.0},"167":{"tf":1.4142135623730951},"176":{"tf":1.0},"186":{"tf":1.0},"202":{"tf":1.0},"207":{"tf":1.0},"21":{"tf":1.0},"213":{"tf":1.0},"255":{"tf":1.0},"264":{"tf":1.0},"268":{"tf":1.0},"278":{"tf":1.0},"37":{"tf":1.0}}}},"y":{"df":5,"docs":{"119":{"tf":1.0},"206":{"tf":1.0},"218":{"tf":1.0},"266":{"tf":1.0},"268":{"tf":1.4142135623730951}},"s":{"/":{"df":0,"docs":{},"w":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"k":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"207":{"tf":1.0}}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"b":{":":{":":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{":":{":":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{":":{":":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"_":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"b":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"_":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"c":{"df":0,"docs":{},"r":{"df":1,"docs":{"62":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":4,"docs":{"249":{"tf":1.4142135623730951},"78":{"tf":1.0},"79":{"tf":2.0},"87":{"tf":1.0}},"g":{"(":{"'":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"62":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"62":{"tf":1.4142135623730951}}}},"d":{"_":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}},"df":7,"docs":{"163":{"tf":1.0},"164":{"tf":1.0},"172":{"tf":1.0},"174":{"tf":1.0},"29":{"tf":1.0},"34":{"tf":1.0},"37":{"tf":1.0}},"e":{"a":{"d":{"df":1,"docs":{"60":{"tf":1.0}}},"df":0,"docs":{},"l":{"df":6,"docs":{"149":{"tf":1.4142135623730951},"179":{"tf":1.0},"180":{"tf":1.0},"184":{"tf":1.4142135623730951},"185":{"tf":1.4142135623730951},"64":{"tf":1.0}}}},"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{".":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"k":{"df":1,"docs":{"40":{"tf":1.0}}}}},"df":0,"docs":{}},"df":7,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"117":{"tf":2.0},"125":{"tf":1.0},"37":{"tf":1.7320508075688772},"62":{"tf":2.23606797749979},"82":{"tf":1.0}},"g":{"df":1,"docs":{"117":{"tf":1.4142135623730951}}}}}},"c":{"df":0,"docs":{},"i":{"d":{"df":11,"docs":{"152":{"tf":1.4142135623730951},"155":{"tf":1.0},"157":{"tf":1.0},"160":{"tf":1.0},"176":{"tf":1.0},"186":{"tf":1.0},"202":{"tf":1.0},"225":{"tf":1.0},"231":{"tf":1.4142135623730951},"249":{"tf":1.0},"267":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":25,"docs":{"134":{"tf":2.0},"135":{"tf":1.0},"136":{"tf":1.0},"137":{"tf":1.4142135623730951},"138":{"tf":1.4142135623730951},"141":{"tf":1.0},"143":{"tf":1.0},"150":{"tf":1.0},"152":{"tf":1.4142135623730951},"162":{"tf":1.0},"164":{"tf":1.0},"178":{"tf":1.0},"180":{"tf":1.0},"187":{"tf":1.0},"188":{"tf":1.0},"198":{"tf":1.0},"206":{"tf":1.0},"208":{"tf":1.7320508075688772},"210":{"tf":1.0},"226":{"tf":1.0},"227":{"tf":1.0},"230":{"tf":1.0},"251":{"tf":1.0},"266":{"tf":1.7320508075688772},"40":{"tf":1.0}}}},"l":{"a":{"df":0,"docs":{},"r":{"df":8,"docs":{"172":{"tf":1.0},"174":{"tf":1.0},"201":{"tf":1.4142135623730951},"224":{"tf":1.4142135623730951},"60":{"tf":1.4142135623730951},"61":{"tf":1.0},"73":{"tf":1.0},"98":{"tf":1.0}}}},"df":1,"docs":{"61":{"tf":1.0}},"i":{"df":0,"docs":{},"n":{"df":4,"docs":{"233":{"tf":2.6457513110645907},"234":{"tf":1.0},"236":{"tf":1.4142135623730951},"242":{"tf":2.0}},"e":{"d":{"_":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"242":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"229":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":1,"docs":{"209":{"tf":1.0}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"250":{"tf":1.0},"84":{"tf":1.0}}}}}}},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"_":{"a":{"df":0,"docs":{},"n":{"d":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"_":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"145":{"tf":1.4142135623730951}}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":4,"docs":{"140":{"tf":1.0},"145":{"tf":1.7320508075688772},"146":{"tf":1.7320508075688772},"254":{"tf":1.0}}}}}}},"d":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"59":{"tf":1.0}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"p":{"df":3,"docs":{"140":{"tf":1.0},"141":{"tf":1.0},"145":{"tf":1.0}}}}},"df":3,"docs":{"140":{"tf":1.0},"145":{"tf":1.4142135623730951},"146":{"tf":1.0}},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"174":{"tf":1.0}}}}}},"f":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{".":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"a":{"df":0,"docs":{},"w":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"/":{"?":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"=":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"2":{"/":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"/":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"271":{"tf":1.0}},"e":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"271":{"tf":1.0}}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":5,"docs":{"11":{"tf":1.0},"12":{"tf":1.0},"222":{"tf":1.0},"60":{"tf":1.0},"69":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"140":{"tf":1.0}}}},"df":0,"docs":{}},"i":{"c":{"df":0,"docs":{},"i":{"df":1,"docs":{"141":{"tf":1.0}}}},"df":0,"docs":{},"n":{"df":26,"docs":{"105":{"tf":2.0},"106":{"tf":1.4142135623730951},"112":{"tf":1.0},"131":{"tf":1.0},"165":{"tf":1.0},"167":{"tf":1.0},"175":{"tf":1.0},"179":{"tf":1.0},"189":{"tf":1.0},"20":{"tf":1.0},"206":{"tf":1.0},"221":{"tf":1.4142135623730951},"226":{"tf":1.0},"229":{"tf":1.0},"232":{"tf":1.0},"242":{"tf":1.0},"244":{"tf":1.0},"245":{"tf":1.0},"246":{"tf":1.4142135623730951},"252":{"tf":1.0},"253":{"tf":1.0},"261":{"tf":1.0},"37":{"tf":1.4142135623730951},"69":{"tf":1.0},"81":{"tf":1.0},"84":{"tf":1.0}},"e":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"(":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":1,"docs":{"62":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"t":{"df":9,"docs":{"103":{"tf":1.0},"104":{"tf":1.4142135623730951},"105":{"tf":1.0},"111":{"tf":1.0},"167":{"tf":1.0},"172":{"tf":1.0},"233":{"tf":1.0},"242":{"tf":1.0},"69":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"?":{"c":{"df":0,"docs":{},"r":{"df":1,"docs":{"283":{"tf":1.0}}}},"df":0,"docs":{}},"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"283":{"tf":1.0}}}}}}}}}}}}}}}}},"df":0,"docs":{}}}}}}}}}},"g":{"df":0,"docs":{},"r":{"a":{"d":{"df":1,"docs":{"236":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":3,"docs":{"189":{"tf":1.0},"201":{"tf":1.0},"229":{"tf":1.0}}}}},"l":{"a":{"df":0,"docs":{},"y":{"df":4,"docs":{"179":{"tf":1.4142135623730951},"180":{"tf":1.0},"184":{"tf":1.7320508075688772},"185":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":2,"docs":{"194":{"tf":1.0},"229":{"tf":1.0}}},"t":{"df":23,"docs":{"105":{"tf":1.0},"106":{"tf":2.0},"108":{"tf":1.0},"109":{"tf":1.4142135623730951},"117":{"tf":1.0},"149":{"tf":1.0},"150":{"tf":1.0},"151":{"tf":1.0},"152":{"tf":1.4142135623730951},"154":{"tf":1.4142135623730951},"155":{"tf":1.4142135623730951},"156":{"tf":1.7320508075688772},"157":{"tf":1.0},"158":{"tf":2.0},"159":{"tf":1.4142135623730951},"209":{"tf":1.0},"212":{"tf":1.0},"231":{"tf":1.0},"249":{"tf":1.0},"268":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0}}}},"i":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"167":{"tf":1.0}}}}},"df":0,"docs":{},"v":{"df":2,"docs":{"225":{"tf":1.0},"235":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"235":{"tf":1.0}},"i":{"df":1,"docs":{"228":{"tf":1.0}}}}}}}},"m":{"df":0,"docs":{},"o":{"df":2,"docs":{"174":{"tf":1.0},"185":{"tf":1.0}},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":4,"docs":{"210":{"tf":1.0},"215":{"tf":1.0},"216":{"tf":1.0},"237":{"tf":1.0}}}}}}}},"p":{"df":1,"docs":{"107":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"d":{"df":45,"docs":{"10":{"tf":1.0},"105":{"tf":1.4142135623730951},"106":{"tf":1.4142135623730951},"11":{"tf":2.23606797749979},"12":{"tf":1.4142135623730951},"121":{"tf":1.4142135623730951},"122":{"tf":1.0},"123":{"tf":1.4142135623730951},"125":{"tf":1.0},"140":{"tf":1.0},"162":{"tf":1.0},"163":{"tf":1.0},"167":{"tf":2.0},"172":{"tf":1.4142135623730951},"174":{"tf":1.7320508075688772},"175":{"tf":1.0},"197":{"tf":1.0},"20":{"tf":1.0},"200":{"tf":1.7320508075688772},"22":{"tf":2.449489742783178},"220":{"tf":1.7320508075688772},"221":{"tf":2.8284271247461903},"222":{"tf":1.0},"224":{"tf":1.4142135623730951},"252":{"tf":1.0},"253":{"tf":1.0},"274":{"tf":1.7320508075688772},"31":{"tf":1.7320508075688772},"36":{"tf":2.23606797749979},"37":{"tf":1.7320508075688772},"45":{"tf":1.0},"53":{"tf":1.0},"58":{"tf":3.1622776601683795},"59":{"tf":2.0},"63":{"tf":1.4142135623730951},"64":{"tf":3.7416573867739413},"65":{"tf":2.23606797749979},"66":{"tf":1.7320508075688772},"67":{"tf":1.7320508075688772},"69":{"tf":2.449489742783178},"7":{"tf":1.7320508075688772},"73":{"tf":1.0},"78":{"tf":1.0},"79":{"tf":1.0},"81":{"tf":1.0}}},"df":0,"docs":{}}},"i":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"200":{"tf":1.7320508075688772}}}},"df":0,"docs":{}},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"y":{"df":2,"docs":{"117":{"tf":1.0},"90":{"tf":1.4142135623730951}}}}},"r":{"df":0,"docs":{},"e":{"c":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"h":{"df":3,"docs":{"93":{"tf":1.0},"96":{"tf":1.0},"99":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"<":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"79":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"v":{"df":1,"docs":{"106":{"tf":1.0}}}}},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":17,"docs":{"102":{"tf":1.0},"104":{"tf":1.0},"105":{"tf":1.0},"120":{"tf":1.0},"20":{"tf":1.0},"200":{"tf":1.0},"214":{"tf":1.0},"220":{"tf":1.0},"243":{"tf":1.0},"245":{"tf":1.0},"252":{"tf":1.0},"282":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0},"64":{"tf":1.0},"76":{"tf":1.0},"98":{"tf":1.0}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":7,"docs":{"145":{"tf":1.0},"146":{"tf":1.0},"21":{"tf":1.0},"219":{"tf":1.0},"249":{"tf":1.0},"251":{"tf":1.0},"280":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"x":{"df":1,"docs":{"283":{"tf":1.0}}}}}}}}}}}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"df":12,"docs":{"106":{"tf":1.0},"138":{"tf":1.4142135623730951},"219":{"tf":1.4142135623730951},"228":{"tf":1.4142135623730951},"23":{"tf":1.0},"251":{"tf":1.0},"32":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"69":{"tf":1.0},"76":{"tf":1.0},"96":{"tf":1.0}}}},"r":{"df":3,"docs":{"189":{"tf":1.0},"200":{"tf":1.0},"233":{"tf":1.0}}}},"k":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":2,"docs":{"280":{"tf":1.0},"282":{"tf":1.0}}}}},"df":23,"docs":{"1":{"tf":1.0},"11":{"tf":1.4142135623730951},"187":{"tf":1.0},"192":{"tf":1.4142135623730951},"201":{"tf":2.0},"206":{"tf":1.0},"210":{"tf":1.0},"217":{"tf":1.0},"218":{"tf":1.0},"223":{"tf":1.7320508075688772},"225":{"tf":1.7320508075688772},"226":{"tf":1.0},"228":{"tf":1.4142135623730951},"229":{"tf":1.0},"231":{"tf":1.4142135623730951},"232":{"tf":1.0},"241":{"tf":1.0},"255":{"tf":1.0},"257":{"tf":1.4142135623730951},"258":{"tf":1.0},"279":{"tf":1.0},"280":{"tf":1.4142135623730951},"282":{"tf":1.0}}}}}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"y":{"df":4,"docs":{"128":{"tf":1.0},"129":{"tf":1.4142135623730951},"131":{"tf":1.7320508075688772},"132":{"tf":1.4142135623730951}}}},"u":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"128":{"tf":1.4142135623730951},"129":{"tf":1.0},"133":{"tf":1.0}},"o":{"df":0,"docs":{},"r":{"df":2,"docs":{"128":{"tf":1.0},"133":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}}}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":20,"docs":{"104":{"tf":1.0},"105":{"tf":1.4142135623730951},"111":{"tf":1.0},"12":{"tf":1.0},"145":{"tf":1.0},"15":{"tf":1.0},"161":{"tf":1.0},"175":{"tf":1.0},"22":{"tf":1.0},"227":{"tf":1.4142135623730951},"228":{"tf":1.0},"232":{"tf":1.4142135623730951},"242":{"tf":1.4142135623730951},"245":{"tf":1.0},"252":{"tf":1.0},"264":{"tf":1.0},"268":{"tf":1.0},"274":{"tf":1.0},"32":{"tf":1.0},"37":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"231":{"tf":1.0}}}},"df":0,"docs":{},"k":{"df":0,"docs":{},"t":{"df":1,"docs":{"65":{"tf":1.0}}}},"r":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"250":{"tf":1.0}},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"184":{"tf":1.0}}}}}}}}}}},"v":{"df":6,"docs":{"11":{"tf":1.7320508075688772},"121":{"tf":1.0},"268":{"tf":1.0},"274":{"tf":1.4142135623730951},"58":{"tf":2.23606797749979},"64":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":20,"docs":{"138":{"tf":1.0},"162":{"tf":1.4142135623730951},"164":{"tf":1.4142135623730951},"169":{"tf":1.4142135623730951},"170":{"tf":1.4142135623730951},"171":{"tf":1.7320508075688772},"172":{"tf":1.7320508075688772},"18":{"tf":1.0},"199":{"tf":1.0},"223":{"tf":1.0},"26":{"tf":1.7320508075688772},"260":{"tf":1.0},"274":{"tf":2.449489742783178},"277":{"tf":1.0},"284":{"tf":1.0},"37":{"tf":1.0},"45":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0}}}}}},"i":{"c":{"df":17,"docs":{"126":{"tf":1.7320508075688772},"175":{"tf":1.0},"226":{"tf":1.0},"229":{"tf":1.4142135623730951},"231":{"tf":2.449489742783178},"232":{"tf":1.0},"233":{"tf":3.1622776601683795},"236":{"tf":1.0},"237":{"tf":1.0},"242":{"tf":1.4142135623730951},"245":{"tf":2.0},"249":{"tf":1.0},"255":{"tf":1.0},"276":{"tf":1.0},"283":{"tf":1.4142135623730951},"45":{"tf":1.0},"51":{"tf":1.0}},"e":{".":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"283":{"tf":1.0}}}}}}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"f":{"df":0,"docs":{},"x":{"a":{"df":1,"docs":{"283":{"tf":1.0}}},"df":0,"docs":{}}}}}}}},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":1,"docs":{"283":{"tf":1.0}}}}}},"_":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"242":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"i":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":8,"docs":{"200":{"tf":1.7320508075688772},"221":{"tf":1.0},"254":{"tf":1.0},"255":{"tf":1.4142135623730951},"256":{"tf":1.0},"257":{"tf":1.0},"258":{"tf":2.0},"274":{"tf":1.0}}}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":2,"docs":{"31":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951}}}}}},"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":2,"docs":{"105":{"tf":1.0},"106":{"tf":1.0}}},"y":{"/":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"106":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"d":{"df":0,"docs":{},"n":{"'":{"df":0,"docs":{},"t":{"df":1,"docs":{"61":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":4,"docs":{"26":{"tf":1.0},"268":{"tf":2.0},"269":{"tf":1.0},"89":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":32,"docs":{"0":{"tf":1.0},"104":{"tf":1.0},"108":{"tf":1.4142135623730951},"109":{"tf":1.4142135623730951},"115":{"tf":1.0},"123":{"tf":1.0},"131":{"tf":1.0},"146":{"tf":1.0},"165":{"tf":1.7320508075688772},"174":{"tf":1.0},"198":{"tf":1.0},"201":{"tf":1.0},"204":{"tf":1.0},"22":{"tf":1.0},"220":{"tf":1.0},"228":{"tf":1.0},"233":{"tf":1.7320508075688772},"242":{"tf":1.0},"243":{"tf":1.0},"245":{"tf":1.7320508075688772},"257":{"tf":1.0},"262":{"tf":1.0},"283":{"tf":1.0},"58":{"tf":1.0},"6":{"tf":1.0},"60":{"tf":1.0},"64":{"tf":1.0},"75":{"tf":1.0},"78":{"tf":1.0},"81":{"tf":1.4142135623730951},"82":{"tf":1.0},"84":{"tf":1.0}}}},"i":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":5,"docs":{"128":{"tf":1.0},"158":{"tf":1.0},"231":{"tf":1.0},"43":{"tf":1.0},"82":{"tf":1.7320508075688772}},"i":{"df":2,"docs":{"115":{"tf":1.0},"180":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"g":{"df":1,"docs":{"241":{"tf":1.0}}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":1,"docs":{"7":{"tf":1.0}}}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":1,"docs":{"64":{"tf":1.0}}}}}},"r":{"df":2,"docs":{"37":{"tf":1.0},"61":{"tf":1.0}},"e":{"c":{"df":0,"docs":{},"t":{"df":6,"docs":{"220":{"tf":1.0},"225":{"tf":1.0},"226":{"tf":1.0},"268":{"tf":1.4142135623730951},"283":{"tf":1.0},"37":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":10,"docs":{"106":{"tf":1.4142135623730951},"115":{"tf":1.0},"140":{"tf":1.0},"205":{"tf":1.0},"229":{"tf":1.0},"232":{"tf":1.0},"272":{"tf":1.0},"64":{"tf":1.0},"78":{"tf":1.0},"84":{"tf":1.0}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":21,"docs":{"12":{"tf":1.0},"122":{"tf":1.0},"22":{"tf":1.7320508075688772},"224":{"tf":1.0},"229":{"tf":1.0},"25":{"tf":1.0},"252":{"tf":2.23606797749979},"258":{"tf":1.0},"29":{"tf":1.7320508075688772},"30":{"tf":1.0},"33":{"tf":1.0},"34":{"tf":1.4142135623730951},"35":{"tf":1.0},"46":{"tf":1.0},"48":{"tf":1.4142135623730951},"60":{"tf":1.0},"61":{"tf":1.0},"70":{"tf":2.23606797749979},"72":{"tf":2.0},"73":{"tf":1.0},"74":{"tf":1.0}}}}}}},"df":0,"docs":{}},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"11":{"tf":1.0}}}},"df":0,"docs":{}}},"s":{"a":{"b":{"df":0,"docs":{},"l":{"df":11,"docs":{"178":{"tf":1.0},"180":{"tf":1.4142135623730951},"182":{"tf":1.0},"183":{"tf":1.0},"184":{"tf":1.0},"232":{"tf":1.0},"233":{"tf":1.0},"245":{"tf":1.4142135623730951},"26":{"tf":2.0},"48":{"tf":1.0},"61":{"tf":1.0}}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"103":{"tf":1.7320508075688772}}}},"df":0,"docs":{}}}}},"c":{"a":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"283":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"242":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":5,"docs":{"231":{"tf":1.0},"234":{"tf":2.0},"242":{"tf":1.4142135623730951},"283":{"tf":1.0},"87":{"tf":1.0}}}},"df":0,"docs":{}}}},"v":{"df":2,"docs":{"158":{"tf":1.4142135623730951},"261":{"tf":1.4142135623730951}}}},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":6,"docs":{"111":{"tf":1.0},"186":{"tf":1.0},"193":{"tf":1.0},"194":{"tf":1.0},"214":{"tf":1.0},"231":{"tf":1.0}}}}}},"df":0,"docs":{},"k":{"df":5,"docs":{"109":{"tf":1.0},"149":{"tf":1.0},"154":{"tf":1.0},"179":{"tf":1.0},"201":{"tf":1.0}}},"p":{"a":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"115":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"y":{"df":2,"docs":{"117":{"tf":1.0},"283":{"tf":1.0}}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":1,"docs":{"175":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"118":{"tf":1.0}}}}}},"t":{"/":{"df":0,"docs":{},"j":{"df":0,"docs":{},"n":{"a":{".":{"a":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"37":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":1,"docs":{"37":{"tf":1.4142135623730951}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":2,"docs":{"145":{"tf":1.0},"150":{"tf":1.0}}}}}}}}},"r":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":21,"docs":{"134":{"tf":1.0},"160":{"tf":1.0},"161":{"tf":1.0},"163":{"tf":2.0},"164":{"tf":1.0},"165":{"tf":1.0},"167":{"tf":1.0},"171":{"tf":1.7320508075688772},"172":{"tf":1.7320508075688772},"214":{"tf":1.0},"215":{"tf":1.7320508075688772},"218":{"tf":1.0},"219":{"tf":1.0},"220":{"tf":1.7320508075688772},"223":{"tf":1.0},"23":{"tf":1.4142135623730951},"252":{"tf":1.4142135623730951},"253":{"tf":1.0},"32":{"tf":1.4142135623730951},"65":{"tf":1.0},"74":{"tf":1.7320508075688772}}}}},"df":0,"docs":{}}}}},"v":{"df":0,"docs":{},"e":{"df":1,"docs":{"174":{"tf":1.0}},"r":{"df":0,"docs":{},"g":{"df":2,"docs":{"196":{"tf":1.0},"252":{"tf":1.0}}}}}}},"n":{"a":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":1,"docs":{"202":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"o":{"c":{"df":8,"docs":{"107":{"tf":1.4142135623730951},"116":{"tf":1.0},"258":{"tf":1.0},"264":{"tf":1.0},"37":{"tf":1.0},"41":{"tf":1.0},"60":{"tf":1.0},"90":{"tf":1.0}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"194":{"tf":1.0},"274":{"tf":1.0}}}}},"s":{"/":{"d":{"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"258":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"62":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":33,"docs":{"101":{"tf":1.0},"107":{"tf":2.0},"109":{"tf":1.0},"111":{"tf":1.0},"115":{"tf":1.0},"116":{"tf":1.0},"120":{"tf":1.4142135623730951},"123":{"tf":1.0},"128":{"tf":1.0},"138":{"tf":1.4142135623730951},"15":{"tf":1.0},"175":{"tf":1.0},"185":{"tf":1.0},"187":{"tf":1.0},"205":{"tf":1.0},"219":{"tf":1.0},"220":{"tf":1.0},"23":{"tf":1.0},"233":{"tf":1.0},"236":{"tf":1.0},"243":{"tf":1.0},"251":{"tf":1.0},"252":{"tf":1.0},"261":{"tf":1.4142135623730951},"274":{"tf":1.4142135623730951},"284":{"tf":1.7320508075688772},"285":{"tf":1.0},"286":{"tf":2.0},"41":{"tf":1.0},"62":{"tf":1.0},"75":{"tf":2.23606797749979},"85":{"tf":1.0},"91":{"tf":1.0}}}}}}}},"df":8,"docs":{"101":{"tf":1.0},"115":{"tf":1.0},"13":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"224":{"tf":1.0},"37":{"tf":1.0},"78":{"tf":1.0}},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"n":{"'":{"df":0,"docs":{},"t":{"df":19,"docs":{"111":{"tf":1.0},"154":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0},"169":{"tf":1.7320508075688772},"170":{"tf":1.7320508075688772},"171":{"tf":1.7320508075688772},"175":{"tf":1.0},"182":{"tf":1.0},"22":{"tf":1.0},"228":{"tf":1.0},"233":{"tf":1.0},"276":{"tf":1.0},"48":{"tf":1.7320508075688772},"60":{"tf":1.0},"7":{"tf":1.0},"78":{"tf":1.0},"82":{"tf":1.0},"90":{"tf":1.0}}}},"df":0,"docs":{}}}},"n":{"'":{"df":0,"docs":{},"t":{"df":24,"docs":{"104":{"tf":1.0},"105":{"tf":1.0},"106":{"tf":1.0},"117":{"tf":1.4142135623730951},"119":{"tf":1.0},"124":{"tf":1.0},"150":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0},"159":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"175":{"tf":1.0},"226":{"tf":1.0},"228":{"tf":1.0},"266":{"tf":1.0},"274":{"tf":1.0},"279":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"43":{"tf":1.0},"58":{"tf":1.0},"60":{"tf":1.0},"79":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":17,"docs":{"106":{"tf":1.0},"182":{"tf":1.0},"184":{"tf":1.0},"194":{"tf":1.0},"222":{"tf":1.0},"238":{"tf":1.4142135623730951},"242":{"tf":1.7320508075688772},"245":{"tf":1.0},"25":{"tf":1.0},"282":{"tf":1.0},"30":{"tf":1.0},"35":{"tf":1.0},"52":{"tf":1.0},"73":{"tf":1.4142135623730951},"79":{"tf":1.4142135623730951},"81":{"tf":1.0},"88":{"tf":1.0}}},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":1,"docs":{"117":{"tf":1.0}}}}}}}}},"’":{"df":0,"docs":{},"t":{"df":2,"docs":{"196":{"tf":1.0},"89":{"tf":1.0}}}}},"s":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"=":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"37":{"tf":2.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"t":{"df":1,"docs":{"183":{"tf":1.4142135623730951}}},"u":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"73":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"w":{"df":0,"docs":{},"n":{"df":4,"docs":{"115":{"tf":1.0},"165":{"tf":1.0},"235":{"tf":1.0},"242":{"tf":1.0}},"g":{"df":0,"docs":{},"r":{"a":{"d":{"df":1,"docs":{"212":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":3,"docs":{"12":{"tf":1.0},"279":{"tf":1.0},"40":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"116":{"tf":1.0},"172":{"tf":1.0}}},"df":0,"docs":{}}}}}},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"274":{"tf":1.0}}}},"r":{"a":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":2,"docs":{"104":{"tf":1.4142135623730951},"7":{"tf":1.4142135623730951}}}},"m":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"209":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"i":{"df":7,"docs":{"203":{"tf":1.7320508075688772},"204":{"tf":1.0},"205":{"tf":1.0},"208":{"tf":1.0},"214":{"tf":1.0},"216":{"tf":1.0},"218":{"tf":1.4142135623730951}},"v":{"df":0,"docs":{},"e":{"df":2,"docs":{"188":{"tf":1.0},"274":{"tf":1.0}},"r":{"df":7,"docs":{"141":{"tf":1.0},"150":{"tf":1.0},"162":{"tf":1.0},"178":{"tf":1.0},"206":{"tf":1.0},"208":{"tf":1.4142135623730951},"210":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"p":{"df":5,"docs":{"12":{"tf":1.0},"216":{"tf":1.4142135623730951},"231":{"tf":1.0},"245":{"tf":1.0},"250":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"161":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"u":{"df":0,"docs":{},"e":{"df":10,"docs":{"11":{"tf":1.0},"116":{"tf":1.0},"180":{"tf":1.4142135623730951},"197":{"tf":1.0},"220":{"tf":1.0},"233":{"tf":1.4142135623730951},"280":{"tf":1.0},"46":{"tf":1.0},"64":{"tf":1.0},"7":{"tf":1.0}}},"m":{"df":0,"docs":{},"p":{"df":1,"docs":{"62":{"tf":1.0}}}},"p":{"df":0,"docs":{},"e":{"df":3,"docs":{"140":{"tf":1.0},"145":{"tf":1.4142135623730951},"146":{"tf":1.0}}},"l":{"df":0,"docs":{},"i":{"c":{"df":6,"docs":{"122":{"tf":1.0},"196":{"tf":1.4142135623730951},"256":{"tf":1.0},"258":{"tf":1.0},"263":{"tf":1.0},"282":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}},"r":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"86":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":8,"docs":{"105":{"tf":1.0},"106":{"tf":1.0},"152":{"tf":1.0},"154":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0},"175":{"tf":1.0},"58":{"tf":1.0}}}}},"y":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":3,"docs":{"253":{"tf":1.0},"278":{"tf":1.0},"282":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{".":{"df":0,"docs":{},"g":{"df":14,"docs":{"104":{"tf":1.0},"165":{"tf":1.0},"18":{"tf":1.0},"184":{"tf":1.0},"21":{"tf":1.0},"22":{"tf":1.0},"221":{"tf":1.0},"223":{"tf":1.0},"224":{"tf":1.0},"279":{"tf":1.0},"282":{"tf":1.0},"37":{"tf":1.0},"51":{"tf":1.0},"59":{"tf":1.4142135623730951}}}},"a":{"c":{"df":0,"docs":{},"h":{"df":46,"docs":{"0":{"tf":1.0},"101":{"tf":1.0},"106":{"tf":1.0},"115":{"tf":1.4142135623730951},"163":{"tf":1.0},"166":{"tf":1.0},"167":{"tf":1.4142135623730951},"172":{"tf":1.7320508075688772},"175":{"tf":1.0},"22":{"tf":1.0},"220":{"tf":1.4142135623730951},"221":{"tf":2.449489742783178},"222":{"tf":1.0},"223":{"tf":1.0},"224":{"tf":1.4142135623730951},"225":{"tf":1.7320508075688772},"226":{"tf":1.0},"228":{"tf":1.0},"229":{"tf":1.4142135623730951},"231":{"tf":1.0},"232":{"tf":1.0},"242":{"tf":1.4142135623730951},"243":{"tf":1.0},"244":{"tf":1.0},"245":{"tf":1.4142135623730951},"246":{"tf":1.0},"249":{"tf":1.7320508075688772},"252":{"tf":1.0},"256":{"tf":1.4142135623730951},"257":{"tf":1.0},"258":{"tf":1.0},"271":{"tf":1.0},"274":{"tf":1.0},"282":{"tf":1.0},"32":{"tf":1.0},"37":{"tf":1.0},"43":{"tf":1.0},"46":{"tf":1.0},"48":{"tf":1.0},"51":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.4142135623730951},"64":{"tf":1.0},"68":{"tf":1.0},"69":{"tf":1.0},"84":{"tf":1.0}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":8,"docs":{"134":{"tf":1.0},"176":{"tf":1.0},"177":{"tf":1.0},"178":{"tf":1.0},"179":{"tf":1.4142135623730951},"180":{"tf":1.0},"183":{"tf":1.0},"184":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"205":{"tf":1.0},"90":{"tf":1.0}}}}}}},"s":{"df":1,"docs":{"162":{"tf":1.4142135623730951}},"i":{"df":9,"docs":{"115":{"tf":1.0},"116":{"tf":1.4142135623730951},"129":{"tf":1.0},"158":{"tf":1.0},"220":{"tf":1.0},"233":{"tf":1.0},"241":{"tf":1.0},"280":{"tf":1.0},"46":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":4,"docs":{"172":{"tf":1.0},"278":{"tf":1.0},"60":{"tf":1.0},"81":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{"df":4,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"26":{"tf":1.0},"58":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"i":{"df":4,"docs":{"107":{"tf":1.0},"133":{"tf":1.0},"162":{"tf":1.0},"221":{"tf":1.0}}}}}}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":2,"docs":{"226":{"tf":1.0},"232":{"tf":1.0}}}}}}}}}},"d":{"df":0,"docs":{},"g":{"df":2,"docs":{"224":{"tf":1.4142135623730951},"233":{"tf":1.0}}},"i":{"df":0,"docs":{},"t":{"df":44,"docs":{"100":{"tf":1.0},"105":{"tf":1.0},"109":{"tf":1.4142135623730951},"117":{"tf":1.0},"119":{"tf":1.0},"124":{"tf":1.4142135623730951},"127":{"tf":1.0},"133":{"tf":1.0},"138":{"tf":1.0},"147":{"tf":1.0},"15":{"tf":1.0},"159":{"tf":1.0},"175":{"tf":1.0},"18":{"tf":1.0},"185":{"tf":1.0},"201":{"tf":1.0},"218":{"tf":1.0},"22":{"tf":1.0},"224":{"tf":1.0},"242":{"tf":1.0},"250":{"tf":1.0},"253":{"tf":1.0},"258":{"tf":2.23606797749979},"259":{"tf":1.0},"263":{"tf":1.0},"264":{"tf":1.0},"273":{"tf":1.0},"277":{"tf":1.0},"282":{"tf":1.0},"283":{"tf":1.0},"286":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"40":{"tf":1.0},"51":{"tf":1.0},"56":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0},"67":{"tf":1.0},"70":{"tf":1.4142135623730951},"74":{"tf":1.0},"8":{"tf":1.0},"90":{"tf":1.0}}}},"u":{"c":{"df":1,"docs":{"214":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":6,"docs":{"184":{"tf":1.0},"197":{"tf":1.0},"201":{"tf":1.0},"210":{"tf":1.0},"69":{"tf":1.0},"7":{"tf":1.0}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":7,"docs":{"121":{"tf":1.0},"161":{"tf":1.0},"164":{"tf":1.0},"196":{"tf":1.0},"199":{"tf":1.0},"201":{"tf":1.0},"229":{"tf":1.0}}}}}}},"g":{"df":13,"docs":{"189":{"tf":1.0},"20":{"tf":1.0},"200":{"tf":1.4142135623730951},"225":{"tf":1.4142135623730951},"231":{"tf":1.4142135623730951},"232":{"tf":1.4142135623730951},"236":{"tf":1.0},"242":{"tf":1.4142135623730951},"250":{"tf":1.0},"74":{"tf":1.0},"82":{"tf":1.7320508075688772},"83":{"tf":1.0},"86":{"tf":1.0}}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":3,"docs":{"145":{"tf":1.4142135623730951},"155":{"tf":1.0},"156":{"tf":1.0}}}}}}},"m":{"b":{"df":1,"docs":{"244":{"tf":1.0}},"e":{"d":{"df":5,"docs":{"125":{"tf":1.0},"225":{"tf":1.4142135623730951},"228":{"tf":1.0},"242":{"tf":1.0},"250":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":1,"docs":{"274":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"262":{"tf":1.0},"282":{"tf":1.0}}}},"p":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"s":{"df":1,"docs":{"204":{"tf":1.0}},"i":{"df":1,"docs":{"75":{"tf":1.0}}}}},"df":0,"docs":{}},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"y":{"df":0,"docs":{},"e":{"df":1,"docs":{"258":{"tf":1.0}}}}}}},"u":{"df":0,"docs":{},"l":{"df":2,"docs":{"17":{"tf":1.4142135623730951},"51":{"tf":1.0}}}}},"n":{"a":{"b":{"df":0,"docs":{},"l":{"df":11,"docs":{"0":{"tf":1.4142135623730951},"10":{"tf":1.0},"138":{"tf":1.0},"184":{"tf":1.0},"189":{"tf":1.0},"232":{"tf":1.0},"233":{"tf":1.4142135623730951},"242":{"tf":1.0},"245":{"tf":2.0},"26":{"tf":1.0},"62":{"tf":1.0}},"e":{"d":{"/":{"d":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"242":{"tf":1.0}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"o":{"d":{"df":1,"docs":{"62":{"tf":1.0}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"s":{"df":1,"docs":{"82":{"tf":1.0}},"s":{"df":2,"docs":{"208":{"tf":1.0},"217":{"tf":1.0}}}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"5":{"tf":1.0}}}},"r":{"a":{"df":0,"docs":{},"g":{"df":4,"docs":{"165":{"tf":1.0},"264":{"tf":1.0},"274":{"tf":1.0},"7":{"tf":1.0}}}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":9,"docs":{"140":{"tf":2.449489742783178},"141":{"tf":1.0},"142":{"tf":1.4142135623730951},"143":{"tf":1.0},"145":{"tf":3.0},"146":{"tf":2.0},"229":{"tf":1.0},"245":{"tf":1.4142135623730951},"254":{"tf":2.0}}}}}},"y":{"df":0,"docs":{},"r":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"141":{"tf":1.0}}}}}}},"d":{"df":32,"docs":{"10":{"tf":1.4142135623730951},"103":{"tf":1.0},"124":{"tf":1.0},"13":{"tf":1.0},"145":{"tf":1.0},"197":{"tf":1.4142135623730951},"199":{"tf":1.0},"20":{"tf":1.0},"200":{"tf":1.7320508075688772},"204":{"tf":2.23606797749979},"205":{"tf":1.4142135623730951},"213":{"tf":1.0},"214":{"tf":1.0},"216":{"tf":1.4142135623730951},"218":{"tf":1.0},"22":{"tf":1.4142135623730951},"225":{"tf":1.4142135623730951},"233":{"tf":1.0},"236":{"tf":1.0},"240":{"tf":1.0},"242":{"tf":1.0},"48":{"tf":2.0},"51":{"tf":1.4142135623730951},"52":{"tf":1.0},"58":{"tf":1.0},"62":{"tf":1.0},"64":{"tf":1.0},"70":{"tf":1.0},"72":{"tf":1.0},"82":{"tf":1.0},"83":{"tf":1.0},"84":{"tf":1.4142135623730951}},"i":{"df":0,"docs":{},"f":{"df":1,"docs":{"37":{"tf":1.0}}}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"x":{"a":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"283":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"c":{"df":7,"docs":{"190":{"tf":1.0},"201":{"tf":1.4142135623730951},"225":{"tf":1.4142135623730951},"226":{"tf":1.0},"242":{"tf":1.0},"274":{"tf":1.0},"79":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":24,"docs":{"104":{"tf":1.0},"187":{"tf":1.0},"225":{"tf":2.6457513110645907},"226":{"tf":2.6457513110645907},"228":{"tf":3.1622776601683795},"229":{"tf":2.23606797749979},"231":{"tf":3.0},"232":{"tf":3.1622776601683795},"233":{"tf":2.23606797749979},"234":{"tf":1.7320508075688772},"236":{"tf":1.0},"237":{"tf":1.0},"238":{"tf":1.7320508075688772},"241":{"tf":2.23606797749979},"242":{"tf":3.605551275463989},"245":{"tf":2.8284271247461903},"246":{"tf":1.0},"249":{"tf":1.4142135623730951},"258":{"tf":1.0},"274":{"tf":1.0},"48":{"tf":1.4142135623730951},"78":{"tf":1.0},"8":{"tf":1.0},"87":{"tf":1.0}},"e":{"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"242":{"tf":1.0}}}}}}}},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"_":{"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"242":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"242":{"tf":1.0}}}}}}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"242":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":3,"docs":{"214":{"tf":1.0},"225":{"tf":1.0},"26":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":3,"docs":{"180":{"tf":1.0},"183":{"tf":1.0},"189":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":21,"docs":{"104":{"tf":1.4142135623730951},"11":{"tf":1.0},"115":{"tf":1.0},"122":{"tf":1.0},"132":{"tf":1.0},"197":{"tf":1.0},"20":{"tf":1.0},"222":{"tf":1.0},"225":{"tf":1.0},"226":{"tf":1.0},"233":{"tf":1.0},"236":{"tf":1.0},"263":{"tf":1.0},"274":{"tf":1.4142135623730951},"279":{"tf":1.0},"37":{"tf":1.0},"45":{"tf":1.7320508075688772},"46":{"tf":1.0},"64":{"tf":1.4142135623730951},"7":{"tf":1.0},"79":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":3,"docs":{"122":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0}}}},"i":{"df":0,"docs":{},"r":{"df":12,"docs":{"103":{"tf":1.7320508075688772},"106":{"tf":1.0},"130":{"tf":1.0},"140":{"tf":1.0},"184":{"tf":1.0},"226":{"tf":1.0},"231":{"tf":1.0},"232":{"tf":1.0},"260":{"tf":1.0},"274":{"tf":1.4142135623730951},"46":{"tf":1.0},"62":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"142":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"i":{"df":6,"docs":{"226":{"tf":1.0},"241":{"tf":1.4142135623730951},"283":{"tf":1.0},"7":{"tf":1.0},"73":{"tf":1.7320508075688772},"78":{"tf":1.4142135623730951}}}}},"u":{"df":0,"docs":{},"m":{"df":8,"docs":{"104":{"tf":1.0},"105":{"tf":1.7320508075688772},"106":{"tf":1.0},"108":{"tf":1.0},"242":{"tf":1.7320508075688772},"87":{"tf":1.0},"93":{"tf":1.0},"97":{"tf":1.0}}}},"v":{"df":1,"docs":{"12":{"tf":1.0}},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":10,"docs":{"11":{"tf":1.7320508075688772},"12":{"tf":1.0},"122":{"tf":1.0},"14":{"tf":1.0},"274":{"tf":2.23606797749979},"277":{"tf":1.0},"37":{"tf":1.4142135623730951},"5":{"tf":1.0},"61":{"tf":1.0},"9":{"tf":1.0}},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":4,"docs":{"11":{"tf":1.4142135623730951},"12":{"tf":1.0},"14":{"tf":1.0},"279":{"tf":1.0}}}}},"df":1,"docs":{"225":{"tf":1.0}}}}}}}}}}}},"o":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"'":{"df":1,"docs":{"274":{"tf":1.0}}},"df":0,"docs":{}}}}},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"229":{"tf":1.0},"231":{"tf":1.0}}}}}}},"i":{"c":{"df":1,"docs":{"218":{"tf":1.0}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":1,"docs":{"200":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":24,"docs":{"104":{"tf":1.7320508075688772},"105":{"tf":1.0},"116":{"tf":1.0},"122":{"tf":1.0},"149":{"tf":1.7320508075688772},"150":{"tf":1.0},"151":{"tf":2.23606797749979},"152":{"tf":2.449489742783178},"154":{"tf":2.23606797749979},"155":{"tf":2.0},"156":{"tf":2.0},"158":{"tf":1.0},"189":{"tf":1.0},"204":{"tf":1.0},"226":{"tf":1.0},"237":{"tf":1.0},"239":{"tf":1.0},"256":{"tf":1.0},"258":{"tf":1.0},"262":{"tf":1.0},"264":{"tf":1.0},"283":{"tf":1.7320508075688772},"61":{"tf":1.0},"7":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"i":{"df":8,"docs":{"133":{"tf":1.0},"154":{"tf":1.0},"183":{"tf":1.0},"226":{"tf":1.0},"258":{"tf":1.0},"51":{"tf":1.0},"62":{"tf":1.0},"64":{"tf":1.0}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":3,"docs":{"11":{"tf":1.0},"111":{"tf":1.0},"115":{"tf":1.0}}}}}}},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":3,"docs":{"141":{"tf":1.0},"143":{"tf":1.0},"208":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"261":{"tf":1.0}}}}}},"t":{"c":{"df":24,"docs":{"112":{"tf":1.0},"149":{"tf":1.0},"207":{"tf":1.0},"214":{"tf":1.0},"220":{"tf":1.0},"226":{"tf":1.4142135623730951},"236":{"tf":1.0},"237":{"tf":1.4142135623730951},"239":{"tf":1.0},"240":{"tf":1.0},"241":{"tf":1.0},"242":{"tf":1.0},"244":{"tf":1.0},"249":{"tf":1.0},"255":{"tf":1.0},"278":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"61":{"tf":1.0},"75":{"tf":1.0},"78":{"tf":1.0},"79":{"tf":1.0},"80":{"tf":1.0},"86":{"tf":1.0}}},"df":0,"docs":{}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":3,"docs":{"161":{"tf":1.0},"203":{"tf":1.0},"59":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":10,"docs":{"104":{"tf":1.0},"140":{"tf":1.0},"19":{"tf":1.0},"212":{"tf":1.0},"225":{"tf":1.7320508075688772},"228":{"tf":1.0},"232":{"tf":1.0},"58":{"tf":1.4142135623730951},"60":{"tf":1.0},"61":{"tf":1.0}},"t":{"df":4,"docs":{"190":{"tf":1.0},"193":{"tf":1.0},"197":{"tf":1.4142135623730951},"283":{"tf":1.0}},"u":{"df":1,"docs":{"167":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"'":{"df":1,"docs":{"104":{"tf":1.0}}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"h":{"df":11,"docs":{"179":{"tf":1.0},"194":{"tf":1.0},"200":{"tf":1.0},"22":{"tf":1.0},"225":{"tf":1.0},"226":{"tf":1.0},"258":{"tf":1.0},"261":{"tf":1.0},"61":{"tf":1.0},"64":{"tf":1.0},"89":{"tf":1.4142135623730951}}}}}}},"i":{"d":{"df":1,"docs":{"201":{"tf":1.0}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"v":{"df":3,"docs":{"193":{"tf":1.0},"63":{"tf":1.0},"81":{"tf":1.0}}}}}},"x":{"a":{"c":{"df":0,"docs":{},"t":{"df":4,"docs":{"123":{"tf":1.0},"197":{"tf":1.0},"200":{"tf":1.0},"233":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":6,"docs":{"132":{"tf":1.0},"201":{"tf":1.4142135623730951},"225":{"tf":1.0},"237":{"tf":1.0},"61":{"tf":1.0},"79":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":2,"docs":{"128":{"tf":1.0},"205":{"tf":1.0}}}},"p":{"df":0,"docs":{},"l":{"df":45,"docs":{"100":{"tf":1.0},"105":{"tf":1.7320508075688772},"106":{"tf":1.0},"107":{"tf":1.0},"108":{"tf":1.4142135623730951},"109":{"tf":1.0},"115":{"tf":1.0},"119":{"tf":1.4142135623730951},"123":{"tf":1.4142135623730951},"124":{"tf":1.4142135623730951},"130":{"tf":1.0},"131":{"tf":1.0},"132":{"tf":1.0},"156":{"tf":1.0},"175":{"tf":1.0},"18":{"tf":1.0},"20":{"tf":1.4142135623730951},"201":{"tf":1.4142135623730951},"213":{"tf":1.0},"216":{"tf":1.0},"22":{"tf":1.0},"225":{"tf":1.7320508075688772},"226":{"tf":1.4142135623730951},"228":{"tf":1.0},"229":{"tf":1.7320508075688772},"232":{"tf":1.0},"233":{"tf":1.0},"238":{"tf":1.0},"245":{"tf":1.0},"25":{"tf":1.0},"252":{"tf":1.0},"268":{"tf":1.7320508075688772},"31":{"tf":1.0},"36":{"tf":1.0},"58":{"tf":2.23606797749979},"59":{"tf":1.7320508075688772},"62":{"tf":1.0},"78":{"tf":1.0},"79":{"tf":1.7320508075688772},"81":{"tf":1.0},"82":{"tf":1.4142135623730951},"83":{"tf":1.4142135623730951},"86":{"tf":1.0},"94":{"tf":1.0},"97":{"tf":1.0}},"e":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":1,"docs":{"105":{"tf":1.4142135623730951}}}}}},"s":{"/":{"*":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"61":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"106":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}}}}}}}},"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":2,"docs":{"171":{"tf":1.0},"190":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"u":{"d":{"df":2,"docs":{"264":{"tf":1.4142135623730951},"37":{"tf":1.4142135623730951}}},"df":0,"docs":{},"s":{"df":1,"docs":{"194":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":9,"docs":{"13":{"tf":1.0},"158":{"tf":1.0},"19":{"tf":1.4142135623730951},"249":{"tf":1.4142135623730951},"25":{"tf":1.0},"274":{"tf":1.4142135623730951},"60":{"tf":1.4142135623730951},"62":{"tf":1.0},"7":{"tf":1.0}}}}},"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":3,"docs":{"166":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0}}}}},"df":0,"docs":{}}},"h":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"188":{"tf":1.0}}}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":36,"docs":{"101":{"tf":1.4142135623730951},"104":{"tf":1.7320508075688772},"105":{"tf":1.7320508075688772},"108":{"tf":1.0},"109":{"tf":1.4142135623730951},"115":{"tf":1.0},"120":{"tf":1.0},"122":{"tf":1.0},"123":{"tf":1.0},"13":{"tf":1.0},"140":{"tf":1.0},"141":{"tf":1.0},"166":{"tf":1.0},"167":{"tf":1.4142135623730951},"183":{"tf":1.0},"187":{"tf":1.0},"191":{"tf":1.0},"193":{"tf":1.0},"196":{"tf":1.7320508075688772},"197":{"tf":2.0},"198":{"tf":1.7320508075688772},"199":{"tf":1.0},"200":{"tf":1.7320508075688772},"201":{"tf":1.0},"210":{"tf":1.0},"225":{"tf":1.0},"228":{"tf":1.0},"232":{"tf":1.0},"233":{"tf":1.0},"263":{"tf":1.0},"274":{"tf":1.0},"41":{"tf":1.4142135623730951},"59":{"tf":1.0},"64":{"tf":2.0},"75":{"tf":1.0},"78":{"tf":1.0}}}},"t":{"df":2,"docs":{"233":{"tf":1.0},"60":{"tf":1.0}}}},"p":{"a":{"df":0,"docs":{},"n":{"d":{"df":3,"docs":{"167":{"tf":1.0},"201":{"tf":1.0},"283":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"(":{"\"":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"62":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":20,"docs":{"101":{"tf":1.0},"104":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"156":{"tf":1.0},"178":{"tf":1.0},"180":{"tf":1.0},"201":{"tf":1.0},"222":{"tf":1.0},"224":{"tf":1.0},"225":{"tf":1.0},"230":{"tf":1.0},"237":{"tf":1.0},"252":{"tf":1.0},"263":{"tf":1.0},"264":{"tf":1.0},"278":{"tf":1.0},"62":{"tf":1.0},"7":{"tf":1.0},"81":{"tf":1.0}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":27,"docs":{"10":{"tf":1.0},"101":{"tf":1.0},"115":{"tf":1.0},"134":{"tf":1.0},"158":{"tf":1.0},"164":{"tf":1.0},"174":{"tf":1.0},"175":{"tf":1.0},"176":{"tf":1.0},"177":{"tf":1.7320508075688772},"178":{"tf":1.7320508075688772},"179":{"tf":2.6457513110645907},"180":{"tf":2.6457513110645907},"182":{"tf":2.0},"183":{"tf":2.449489742783178},"184":{"tf":2.8284271247461903},"185":{"tf":1.4142135623730951},"187":{"tf":1.0},"204":{"tf":1.4142135623730951},"205":{"tf":1.0},"206":{"tf":1.4142135623730951},"208":{"tf":1.4142135623730951},"212":{"tf":1.0},"218":{"tf":1.0},"22":{"tf":1.0},"233":{"tf":1.0},"274":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"204":{"tf":1.0}}},"df":0,"docs":{}}},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":6,"docs":{"0":{"tf":1.0},"177":{"tf":1.0},"178":{"tf":1.0},"184":{"tf":1.4142135623730951},"268":{"tf":1.0},"284":{"tf":1.0}}}}}}}}},"i":{"df":0,"docs":{},"r":{"df":1,"docs":{"277":{"tf":1.0}}}},"l":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"81":{"tf":1.0}}}},"n":{"df":1,"docs":{"7":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":4,"docs":{"128":{"tf":1.0},"138":{"tf":1.0},"224":{"tf":1.0},"241":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":4,"docs":{"115":{"tf":1.0},"201":{"tf":1.0},"64":{"tf":1.0},"7":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":3,"docs":{"11":{"tf":1.4142135623730951},"252":{"tf":1.0},"37":{"tf":2.6457513110645907}}}},"s":{"df":29,"docs":{"101":{"tf":1.0},"103":{"tf":1.4142135623730951},"108":{"tf":1.0},"109":{"tf":1.0},"111":{"tf":1.0},"116":{"tf":1.0},"146":{"tf":1.0},"159":{"tf":1.0},"182":{"tf":1.0},"192":{"tf":1.0},"196":{"tf":1.0},"198":{"tf":1.0},"200":{"tf":1.0},"221":{"tf":1.0},"226":{"tf":1.4142135623730951},"229":{"tf":1.0},"232":{"tf":2.23606797749979},"236":{"tf":1.0},"239":{"tf":1.0},"241":{"tf":1.0},"242":{"tf":1.4142135623730951},"253":{"tf":1.0},"268":{"tf":1.0},"281":{"tf":2.0},"69":{"tf":1.0},"77":{"tf":1.0},"78":{"tf":2.23606797749979},"83":{"tf":1.0},"90":{"tf":1.4142135623730951}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":2,"docs":{"104":{"tf":1.4142135623730951},"170":{"tf":1.0}}}}}}},"t":{".":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"r":{"c":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":1,"docs":{"22":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"37":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":5,"docs":{"257":{"tf":1.4142135623730951},"44":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"98":{"tf":1.4142135623730951}}},"t":{"df":1,"docs":{"115":{"tf":1.0}}}},"r":{"df":0,"docs":{},"n":{"df":8,"docs":{"103":{"tf":1.0},"111":{"tf":1.0},"114":{"tf":1.0},"145":{"tf":1.0},"167":{"tf":1.0},"242":{"tf":1.0},"260":{"tf":1.0},"7":{"tf":1.0}}}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"107":{"tf":1.0},"271":{"tf":1.0}}}},"df":1,"docs":{"115":{"tf":1.0}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":2,"docs":{"156":{"tf":1.0},"233":{"tf":1.0}}}}}}}},"f":{"a":{"c":{"df":0,"docs":{},"e":{"df":5,"docs":{"107":{"tf":1.0},"25":{"tf":1.0},"283":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0}}},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"125":{"tf":1.0}},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"138":{"tf":1.0}}}}}},"t":{"df":4,"docs":{"107":{"tf":1.0},"115":{"tf":1.0},"180":{"tf":1.0},"61":{"tf":1.0}},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"200":{"tf":1.0}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":16,"docs":{"104":{"tf":1.4142135623730951},"119":{"tf":1.0},"146":{"tf":1.0},"151":{"tf":1.0},"20":{"tf":1.0},"204":{"tf":1.0},"205":{"tf":1.0},"208":{"tf":1.0},"21":{"tf":1.0},"242":{"tf":1.0},"25":{"tf":1.0},"27":{"tf":1.0},"280":{"tf":1.0},"282":{"tf":1.0},"62":{"tf":1.0},"92":{"tf":1.0}},"u":{"df":0,"docs":{},"r":{"df":5,"docs":{"159":{"tf":1.0},"274":{"tf":1.0},"282":{"tf":1.0},"40":{"tf":1.4142135623730951},"62":{"tf":1.0}},"e":{"df":0,"docs":{},"s":{"@":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":1,"docs":{"274":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}}},"r":{"df":1,"docs":{"116":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":3,"docs":{"158":{"tf":1.0},"241":{"tf":1.0},"7":{"tf":1.0}}}}}},"k":{"df":0,"docs":{},"e":{"df":2,"docs":{"138":{"tf":1.0},"203":{"tf":1.0}}}},"l":{"df":0,"docs":{},"s":{"df":2,"docs":{"60":{"tf":1.4142135623730951},"61":{"tf":1.4142135623730951}}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"r":{"df":2,"docs":{"102":{"tf":1.0},"103":{"tf":1.0}}}},"df":0,"docs":{}}}}},"q":{"0":{"df":2,"docs":{"60":{"tf":1.0},"61":{"tf":1.0}}},"df":2,"docs":{"110":{"tf":1.0},"85":{"tf":1.0}}},"r":{"df":6,"docs":{"124":{"tf":1.0},"201":{"tf":1.0},"225":{"tf":1.0},"242":{"tf":1.0},"59":{"tf":1.0},"63":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"58":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"57":{"tf":1.0}}}}}},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"205":{"tf":1.0}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":3,"docs":{"165":{"tf":1.0},"172":{"tf":1.0},"37":{"tf":1.0}}}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"180":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":12,"docs":{"103":{"tf":1.0},"124":{"tf":1.4142135623730951},"189":{"tf":1.0},"196":{"tf":1.0},"198":{"tf":1.0},"221":{"tf":1.0},"255":{"tf":1.0},"260":{"tf":1.0},"282":{"tf":1.0},"43":{"tf":1.0},"62":{"tf":1.7320508075688772},"69":{"tf":1.0}},"e":{"df":0,"docs":{},"s":{"=":{"[":{"\"":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"69":{"tf":1.0}}}}}}}}},"d":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":1,"docs":{"62":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":7,"docs":{"107":{"tf":1.0},"170":{"tf":1.0},"25":{"tf":1.0},"3":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"6":{"tf":1.0}}}},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"55":{"tf":1.0}}}}},"/":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"l":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"18":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"126":{"tf":1.0}}}}}},"df":19,"docs":{"10":{"tf":1.0},"108":{"tf":1.0},"11":{"tf":1.0},"12":{"tf":2.0},"126":{"tf":1.4142135623730951},"145":{"tf":1.0},"16":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":1.0},"185":{"tf":1.0},"200":{"tf":1.0},"21":{"tf":1.0},"224":{"tf":1.0},"257":{"tf":1.7320508075688772},"38":{"tf":1.0},"39":{"tf":1.7320508075688772},"40":{"tf":1.7320508075688772},"51":{"tf":1.0},"55":{"tf":1.4142135623730951}}}},"n":{"df":0,"docs":{},"e":{"c":{"df":1,"docs":{"145":{"tf":1.0}}},"df":0,"docs":{}}}},"t":{"c":{"df":0,"docs":{},"h":{"df":6,"docs":{"189":{"tf":1.0},"190":{"tf":1.4142135623730951},"232":{"tf":1.0},"245":{"tf":1.0},"249":{"tf":1.7320508075688772},"274":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"w":{"df":3,"docs":{"58":{"tf":1.0},"6":{"tf":1.0},"68":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"59":{"tf":1.0}}}}}},"f":{"df":0,"docs":{},"i":{"_":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"73":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"73":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"116":{"tf":1.0}}}}}}}}}},"df":13,"docs":{"101":{"tf":1.0},"103":{"tf":3.1622776601683795},"104":{"tf":1.0},"105":{"tf":1.7320508075688772},"106":{"tf":1.7320508075688772},"115":{"tf":1.0},"116":{"tf":2.0},"193":{"tf":1.0},"241":{"tf":1.0},"43":{"tf":1.0},"44":{"tf":1.7320508075688772},"51":{"tf":1.0},"90":{"tf":1.7320508075688772}}}},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":5,"docs":{"140":{"tf":1.4142135623730951},"145":{"tf":2.0},"249":{"tf":1.0},"250":{"tf":1.0},"83":{"tf":1.0}}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"e":{":":{"/":{"/":{"/":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"q":{"/":{"c":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"e":{"/":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"31":{"tf":1.0},"36":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":63,"docs":{"1":{"tf":1.4142135623730951},"103":{"tf":1.7320508075688772},"104":{"tf":2.449489742783178},"105":{"tf":2.6457513110645907},"106":{"tf":2.23606797749979},"107":{"tf":1.0},"108":{"tf":1.7320508075688772},"109":{"tf":2.0},"111":{"tf":1.0},"114":{"tf":1.0},"117":{"tf":1.4142135623730951},"12":{"tf":1.0},"13":{"tf":1.0},"133":{"tf":1.0},"137":{"tf":1.0},"151":{"tf":1.4142135623730951},"152":{"tf":1.4142135623730951},"157":{"tf":1.0},"158":{"tf":1.7320508075688772},"159":{"tf":1.0},"165":{"tf":1.0},"167":{"tf":1.4142135623730951},"17":{"tf":1.0},"18":{"tf":1.0},"187":{"tf":1.0},"189":{"tf":1.0},"200":{"tf":1.0},"201":{"tf":1.0},"22":{"tf":1.4142135623730951},"220":{"tf":1.0},"221":{"tf":1.0},"222":{"tf":1.0},"229":{"tf":1.0},"25":{"tf":1.4142135623730951},"251":{"tf":1.0},"252":{"tf":2.0},"26":{"tf":1.4142135623730951},"263":{"tf":1.0},"271":{"tf":1.7320508075688772},"274":{"tf":1.0},"279":{"tf":1.4142135623730951},"28":{"tf":1.4142135623730951},"282":{"tf":1.7320508075688772},"284":{"tf":1.0},"29":{"tf":1.0},"31":{"tf":1.0},"33":{"tf":1.4142135623730951},"36":{"tf":1.0},"4":{"tf":1.0},"40":{"tf":1.0},"45":{"tf":1.0},"60":{"tf":1.7320508075688772},"61":{"tf":2.449489742783178},"64":{"tf":1.0},"65":{"tf":1.0},"69":{"tf":1.0},"70":{"tf":2.449489742783178},"72":{"tf":1.0},"73":{"tf":3.0},"74":{"tf":1.0},"79":{"tf":1.0},"81":{"tf":1.4142135623730951},"98":{"tf":1.7320508075688772}},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":1,"docs":{"25":{"tf":1.0}}}}}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"126":{"tf":1.0}}}}}},"n":{"a":{"df":0,"docs":{},"l":{"df":13,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"128":{"tf":1.0},"200":{"tf":1.0},"201":{"tf":1.0},"205":{"tf":1.0},"214":{"tf":1.0},"245":{"tf":1.0},"267":{"tf":1.0},"31":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"61":{"tf":1.0},"73":{"tf":1.0}}}},"d":{"df":18,"docs":{"1":{"tf":1.0},"104":{"tf":1.0},"105":{"tf":1.0},"106":{"tf":2.23606797749979},"122":{"tf":1.0},"126":{"tf":1.0},"18":{"tf":1.0},"21":{"tf":1.0},"224":{"tf":1.0},"26":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.4142135623730951},"37":{"tf":1.0},"46":{"tf":1.0},"52":{"tf":1.0},"6":{"tf":1.4142135623730951},"62":{"tf":1.0},"82":{"tf":1.0}}},"df":0,"docs":{},"e":{"df":2,"docs":{"133":{"tf":1.0},"189":{"tf":1.0}}},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":3,"docs":{"212":{"tf":1.0},"264":{"tf":1.0},"58":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"e":{"df":1,"docs":{"68":{"tf":1.0}},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"x":{"/":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"u":{"df":1,"docs":{"271":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"a":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{":":{":":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"_":{"df":0,"docs":{},"j":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"283":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":1,"docs":{"283":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":3,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"283":{"tf":1.7320508075688772}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":48,"docs":{"0":{"tf":1.4142135623730951},"1":{"tf":1.4142135623730951},"10":{"tf":1.4142135623730951},"109":{"tf":1.0},"118":{"tf":2.0},"119":{"tf":2.23606797749979},"12":{"tf":1.0},"134":{"tf":1.0},"14":{"tf":1.0},"145":{"tf":1.0},"15":{"tf":1.4142135623730951},"167":{"tf":1.0},"18":{"tf":1.7320508075688772},"187":{"tf":1.0},"20":{"tf":1.0},"200":{"tf":1.0},"201":{"tf":1.0},"202":{"tf":1.0},"203":{"tf":1.4142135623730951},"206":{"tf":1.7320508075688772},"213":{"tf":1.0},"215":{"tf":1.0},"217":{"tf":1.0},"218":{"tf":2.23606797749979},"219":{"tf":1.0},"23":{"tf":2.0},"24":{"tf":1.0},"25":{"tf":2.449489742783178},"254":{"tf":3.0},"255":{"tf":1.7320508075688772},"256":{"tf":1.0},"257":{"tf":2.0},"258":{"tf":1.0},"259":{"tf":1.0},"26":{"tf":1.4142135623730951},"260":{"tf":1.0},"268":{"tf":2.23606797749979},"272":{"tf":1.7320508075688772},"274":{"tf":1.0},"283":{"tf":1.7320508075688772},"284":{"tf":1.0},"31":{"tf":1.7320508075688772},"38":{"tf":1.0},"39":{"tf":1.0},"48":{"tf":1.0},"56":{"tf":1.4142135623730951},"64":{"tf":1.7320508075688772},"74":{"tf":1.7320508075688772}}}}}},"s":{"df":0,"docs":{},"t":{"df":30,"docs":{"10":{"tf":1.4142135623730951},"102":{"tf":1.4142135623730951},"104":{"tf":1.4142135623730951},"124":{"tf":1.0},"134":{"tf":1.0},"137":{"tf":1.0},"146":{"tf":1.0},"152":{"tf":1.0},"176":{"tf":1.0},"177":{"tf":1.4142135623730951},"178":{"tf":1.0},"179":{"tf":2.23606797749979},"180":{"tf":1.4142135623730951},"182":{"tf":1.0},"183":{"tf":1.7320508075688772},"184":{"tf":1.4142135623730951},"185":{"tf":1.4142135623730951},"188":{"tf":1.0},"20":{"tf":1.0},"214":{"tf":1.0},"230":{"tf":1.4142135623730951},"245":{"tf":1.0},"25":{"tf":1.0},"280":{"tf":1.0},"283":{"tf":1.0},"6":{"tf":2.0},"62":{"tf":1.0},"81":{"tf":1.0},"88":{"tf":1.0},"89":{"tf":1.0}}}}},"t":{"df":4,"docs":{"102":{"tf":1.0},"138":{"tf":1.0},"226":{"tf":1.0},"229":{"tf":1.0}}},"x":{"df":8,"docs":{"116":{"tf":1.0},"118":{"tf":1.0},"119":{"tf":1.0},"152":{"tf":1.0},"21":{"tf":1.4142135623730951},"282":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0}},"u":{"df":0,"docs":{},"p":{"df":3,"docs":{"145":{"tf":2.0},"146":{"tf":1.0},"147":{"tf":1.0}}}}}},"k":{"df":1,"docs":{"62":{"tf":1.0}}},"l":{"a":{"df":0,"docs":{},"g":{"df":2,"docs":{"175":{"tf":1.4142135623730951},"64":{"tf":1.0}}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":1,"docs":{"48":{"tf":1.0}}}}},"t":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":1,"docs":{"37":{"tf":1.0}}}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"111":{"tf":1.0}}},"r":{"df":1,"docs":{"83":{"tf":1.0}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"200":{"tf":1.0}}}},"x":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"l":{"df":5,"docs":{"126":{"tf":1.0},"178":{"tf":1.0},"182":{"tf":1.0},"184":{"tf":1.0},"261":{"tf":1.0}}}},"df":0,"docs":{}}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":1,"docs":{"201":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"w":{"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"200":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":6,"docs":{"118":{"tf":1.0},"183":{"tf":1.0},"244":{"tf":1.4142135623730951},"25":{"tf":1.0},"27":{"tf":1.4142135623730951},"283":{"tf":1.0}}}}},"m":{"df":0,"docs":{},"l":{"df":1,"docs":{"271":{"tf":1.0}}}},"n":{"df":2,"docs":{"242":{"tf":2.449489742783178},"94":{"tf":1.0}}},"o":{"c":{"df":0,"docs":{},"u":{"df":8,"docs":{"118":{"tf":1.4142135623730951},"230":{"tf":1.0},"255":{"tf":1.0},"257":{"tf":1.0},"32":{"tf":1.7320508075688772},"33":{"tf":1.4142135623730951},"36":{"tf":2.0},"74":{"tf":1.4142135623730951}},"s":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"74":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"u":{"d":{"df":0,"docs":{},"l":{"_":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"74":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"36":{"tf":1.0}}},"df":0,"docs":{}}}}}}}}},"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"34":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"df":1,"docs":{"188":{"tf":1.0}},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"x":{"c":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{".":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":2,"docs":{"33":{"tf":1.0},"34":{"tf":1.4142135623730951}}}}}},"df":1,"docs":{"34":{"tf":1.4142135623730951}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"l":{"df":1,"docs":{"34":{"tf":1.0}}}}}}}},"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"201":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":5,"docs":{"11":{"tf":1.0},"13":{"tf":1.0},"279":{"tf":1.0},"61":{"tf":1.4142135623730951},"62":{"tf":1.0}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":59,"docs":{"108":{"tf":1.0},"109":{"tf":1.4142135623730951},"11":{"tf":1.4142135623730951},"116":{"tf":1.0},"118":{"tf":1.0},"12":{"tf":1.4142135623730951},"122":{"tf":1.0},"123":{"tf":1.4142135623730951},"124":{"tf":1.0},"128":{"tf":1.0},"136":{"tf":1.0},"145":{"tf":1.0},"152":{"tf":1.0},"18":{"tf":1.0},"189":{"tf":1.0},"191":{"tf":1.0},"197":{"tf":1.0},"203":{"tf":1.0},"204":{"tf":1.7320508075688772},"21":{"tf":1.0},"23":{"tf":1.0},"230":{"tf":1.0},"233":{"tf":1.0},"247":{"tf":1.0},"25":{"tf":1.4142135623730951},"251":{"tf":1.0},"252":{"tf":1.0},"253":{"tf":1.4142135623730951},"257":{"tf":1.0},"258":{"tf":1.0},"259":{"tf":1.0},"268":{"tf":2.23606797749979},"27":{"tf":1.4142135623730951},"279":{"tf":1.0},"28":{"tf":1.4142135623730951},"280":{"tf":1.0},"281":{"tf":1.0},"29":{"tf":1.7320508075688772},"30":{"tf":1.0},"31":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.4142135623730951},"34":{"tf":1.7320508075688772},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"43":{"tf":1.0},"45":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.4142135623730951},"67":{"tf":1.0},"69":{"tf":1.4142135623730951},"7":{"tf":1.0},"70":{"tf":1.4142135623730951},"73":{"tf":1.7320508075688772},"74":{"tf":1.0},"93":{"tf":1.4142135623730951}},"u":{"df":0,"docs":{},"p":{"df":1,"docs":{"241":{"tf":1.0}}}}}}}},"o":{"'":{"df":1,"docs":{"58":{"tf":1.0}}},".":{"df":0,"docs":{},"r":{"df":1,"docs":{"117":{"tf":1.0}}}},"b":{"a":{"df":0,"docs":{},"r":{".":{"df":0,"docs":{},"k":{"df":0,"docs":{},"t":{"df":1,"docs":{"100":{"tf":1.0}}}}},"df":1,"docs":{"100":{"tf":1.0}}}},"df":0,"docs":{}},"df":3,"docs":{"117":{"tf":1.0},"58":{"tf":1.4142135623730951},"60":{"tf":1.0}}},"r":{"c":{"df":5,"docs":{"184":{"tf":1.4142135623730951},"237":{"tf":1.0},"25":{"tf":1.7320508075688772},"26":{"tf":1.0},"274":{"tf":1.0}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":1,"docs":{"264":{"tf":1.0}}}}}},"df":5,"docs":{"101":{"tf":1.0},"106":{"tf":1.0},"128":{"tf":1.4142135623730951},"69":{"tf":1.0},"82":{"tf":1.0}}}}}},"k":{"df":4,"docs":{"196":{"tf":1.0},"197":{"tf":1.0},"274":{"tf":1.0},"7":{"tf":1.4142135623730951}}},"m":{"a":{"df":0,"docs":{},"t":{"df":11,"docs":{"136":{"tf":1.0},"137":{"tf":1.0},"138":{"tf":1.0},"163":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"22":{"tf":1.4142135623730951},"252":{"tf":1.4142135623730951},"253":{"tf":1.0},"268":{"tf":1.0},"37":{"tf":1.0}},"t":{"df":1,"docs":{"7":{"tf":1.0}}}}},"df":3,"docs":{"165":{"tf":1.0},"232":{"tf":1.0},"242":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"229":{"tf":1.0}}}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"137":{"tf":1.0}}}}}}},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"225":{"tf":1.0}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"j":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"223":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}},"w":{"a":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"d":{"df":43,"docs":{"100":{"tf":1.0},"109":{"tf":1.0},"117":{"tf":1.4142135623730951},"119":{"tf":1.0},"124":{"tf":1.0},"127":{"tf":1.0},"133":{"tf":1.0},"138":{"tf":1.0},"147":{"tf":1.0},"15":{"tf":1.4142135623730951},"159":{"tf":1.0},"175":{"tf":1.0},"185":{"tf":1.0},"201":{"tf":1.0},"203":{"tf":1.0},"218":{"tf":1.0},"22":{"tf":1.4142135623730951},"224":{"tf":1.0},"242":{"tf":1.0},"250":{"tf":1.0},"253":{"tf":1.0},"258":{"tf":1.0},"259":{"tf":1.0},"263":{"tf":1.0},"264":{"tf":1.0},"271":{"tf":1.0},"273":{"tf":1.0},"277":{"tf":1.0},"282":{"tf":1.0},"283":{"tf":1.0},"286":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"40":{"tf":1.0},"51":{"tf":1.0},"56":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0},"67":{"tf":1.0},"74":{"tf":1.0},"8":{"tf":1.0},"90":{"tf":1.0}}},"df":0,"docs":{}},"r":{"df":1,"docs":{"103":{"tf":1.0}}}}},"r":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":1,"docs":{"131":{"tf":1.0}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"y":{"df":1,"docs":{"131":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}}},"df":1,"docs":{"131":{"tf":1.0}}}}}}},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":8,"docs":{"161":{"tf":1.0},"166":{"tf":1.0},"167":{"tf":1.4142135623730951},"175":{"tf":1.0},"252":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.4142135623730951},"77":{"tf":1.4142135623730951}}}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":11,"docs":{"105":{"tf":1.0},"128":{"tf":1.4142135623730951},"225":{"tf":1.0},"233":{"tf":1.0},"247":{"tf":1.0},"25":{"tf":1.0},"3":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"6":{"tf":1.0},"81":{"tf":1.0}}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"60":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"149":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"11":{"tf":1.0}}}}}}},"i":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"274":{"tf":1.0}}}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"j":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"100":{"tf":1.0}}}}}}}}}},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"166":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"169":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":14,"docs":{"105":{"tf":1.0},"122":{"tf":1.0},"149":{"tf":1.0},"154":{"tf":1.0},"217":{"tf":1.0},"221":{"tf":1.7320508075688772},"222":{"tf":1.4142135623730951},"223":{"tf":1.4142135623730951},"224":{"tf":1.4142135623730951},"252":{"tf":1.0},"268":{"tf":2.0},"31":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"7":{"tf":1.0}},"i":{"df":3,"docs":{"21":{"tf":1.0},"226":{"tf":1.0},"274":{"tf":1.0}}}}},"n":{"c":{"df":1,"docs":{"97":{"tf":1.0}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":39,"docs":{"103":{"tf":2.449489742783178},"104":{"tf":1.0},"105":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"111":{"tf":1.0},"129":{"tf":1.7320508075688772},"140":{"tf":1.0},"142":{"tf":1.4142135623730951},"143":{"tf":1.4142135623730951},"145":{"tf":4.123105625617661},"146":{"tf":1.4142135623730951},"158":{"tf":1.4142135623730951},"199":{"tf":1.0},"200":{"tf":1.0},"221":{"tf":1.0},"222":{"tf":1.0},"232":{"tf":1.0},"234":{"tf":1.4142135623730951},"242":{"tf":1.0},"245":{"tf":2.0},"264":{"tf":1.0},"281":{"tf":2.23606797749979},"283":{"tf":1.0},"42":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"48":{"tf":1.0},"54":{"tf":1.0},"60":{"tf":1.0},"62":{"tf":2.0},"64":{"tf":1.4142135623730951},"77":{"tf":1.4142135623730951},"78":{"tf":2.23606797749979},"79":{"tf":1.4142135623730951},"81":{"tf":1.0},"82":{"tf":1.7320508075688772},"93":{"tf":1.0},"99":{"tf":1.0}},"s":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"283":{"tf":1.0}}}}}}}}}}}}}}}}}}}},"d":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"61":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"100":{"tf":1.0}}},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"174":{"tf":1.0}}}}}}},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":20,"docs":{"107":{"tf":1.0},"126":{"tf":1.0},"140":{"tf":1.0},"152":{"tf":1.0},"167":{"tf":2.0},"190":{"tf":1.0},"196":{"tf":1.0},"198":{"tf":1.4142135623730951},"201":{"tf":1.0},"225":{"tf":1.0},"232":{"tf":1.0},"233":{"tf":1.0},"241":{"tf":1.0},"242":{"tf":1.0},"252":{"tf":1.0},"258":{"tf":1.7320508075688772},"261":{"tf":1.0},"264":{"tf":1.4142135623730951},"268":{"tf":1.0},"38":{"tf":1.0}}}}},"z":{"df":0,"docs":{},"z":{"df":1,"docs":{"51":{"tf":1.0}}}}},"x":{"a":{"_":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"283":{"tf":1.0}},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"c":{"df":1,"docs":{"283":{"tf":1.0}}},"df":0,"docs":{}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":1,"docs":{"283":{"tf":1.0}}}}}}}}}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"221":{"tf":1.0}}}}}}}},"df":13,"docs":{"105":{"tf":1.0},"107":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"226":{"tf":1.4142135623730951},"231":{"tf":1.7320508075688772},"237":{"tf":1.0},"242":{"tf":1.7320508075688772},"254":{"tf":1.0},"256":{"tf":1.4142135623730951},"257":{"tf":1.0},"283":{"tf":1.0},"58":{"tf":1.4142135623730951}},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"s":{"a":{"d":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"283":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"56":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"g":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"101":{"tf":1.0}}}},"r":{"b":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"128":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"e":{"df":1,"docs":{"62":{"tf":1.0}}}}},"c":{"c":{"df":1,"docs":{"37":{"tf":1.0}}},"df":1,"docs":{"115":{"tf":1.4142135623730951}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":1,"docs":{"132":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}}},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":1,"docs":{"192":{"tf":1.0}}}}}}}}},"df":0,"docs":{},"m":{"df":1,"docs":{"14":{"tf":1.0}}},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":2,"docs":{"30":{"tf":1.0},"35":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":59,"docs":{"101":{"tf":1.0},"104":{"tf":1.7320508075688772},"105":{"tf":1.7320508075688772},"106":{"tf":1.0},"107":{"tf":1.4142135623730951},"108":{"tf":1.0},"109":{"tf":1.7320508075688772},"115":{"tf":1.0},"116":{"tf":1.4142135623730951},"120":{"tf":1.0},"122":{"tf":1.0},"123":{"tf":1.7320508075688772},"125":{"tf":1.0},"132":{"tf":1.0},"134":{"tf":1.0},"14":{"tf":1.0},"142":{"tf":1.0},"167":{"tf":1.0},"194":{"tf":1.0},"201":{"tf":1.4142135623730951},"220":{"tf":1.0},"221":{"tf":1.0},"225":{"tf":1.0},"227":{"tf":1.0},"228":{"tf":1.0},"23":{"tf":1.0},"232":{"tf":1.4142135623730951},"242":{"tf":1.7320508075688772},"243":{"tf":1.0},"244":{"tf":1.4142135623730951},"245":{"tf":1.0},"247":{"tf":1.0},"253":{"tf":1.7320508075688772},"254":{"tf":1.4142135623730951},"266":{"tf":1.0},"267":{"tf":1.0},"268":{"tf":1.0},"271":{"tf":1.0},"274":{"tf":1.0},"277":{"tf":1.0},"29":{"tf":1.0},"30":{"tf":1.0},"34":{"tf":1.0},"35":{"tf":1.0},"44":{"tf":1.0},"45":{"tf":1.0},"51":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.7320508075688772},"69":{"tf":1.0},"70":{"tf":1.0},"72":{"tf":2.23606797749979},"73":{"tf":2.23606797749979},"74":{"tf":1.0},"75":{"tf":1.0},"76":{"tf":1.0},"82":{"tf":1.0},"83":{"tf":1.0},"90":{"tf":1.0}}}}},"t":{"_":{"a":{"c":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"283":{"tf":1.0}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"df":1,"docs":{"145":{"tf":1.7320508075688772}}}},"b":{"df":0,"docs":{},"y":{"_":{"b":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"_":{"d":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"145":{"tf":1.7320508075688772}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"145":{"tf":1.7320508075688772}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"283":{"tf":1.0}}}}}}}},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"_":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"c":{"df":1,"docs":{"87":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":12,"docs":{"105":{"tf":1.0},"116":{"tf":1.0},"17":{"tf":1.0},"227":{"tf":1.0},"236":{"tf":1.0},"245":{"tf":1.0},"253":{"tf":1.0},"271":{"tf":1.0},"282":{"tf":1.0},"60":{"tf":1.0},"62":{"tf":1.0},"82":{"tf":1.0}}}},"h":{"df":1,"docs":{"286":{"tf":1.0}}},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"13":{"tf":1.0}}}},"t":{"df":9,"docs":{"11":{"tf":1.7320508075688772},"123":{"tf":1.0},"167":{"tf":2.0},"253":{"tf":1.4142135623730951},"26":{"tf":1.0},"268":{"tf":2.23606797749979},"269":{"tf":1.0},"271":{"tf":1.0},"34":{"tf":1.0}},"h":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"274":{"tf":1.0}}},"df":0,"docs":{}},"u":{"b":{"'":{"df":3,"docs":{"274":{"tf":1.0},"277":{"tf":1.0},"7":{"tf":1.0}}},"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"277":{"tf":1.0}}}}}}}},"df":52,"docs":{"1":{"tf":1.4142135623730951},"100":{"tf":1.0},"109":{"tf":1.0},"117":{"tf":1.0},"119":{"tf":1.4142135623730951},"124":{"tf":1.0},"127":{"tf":1.0},"133":{"tf":1.0},"138":{"tf":1.0},"147":{"tf":1.0},"15":{"tf":1.0},"159":{"tf":1.0},"175":{"tf":1.0},"185":{"tf":1.0},"193":{"tf":1.0},"201":{"tf":1.0},"218":{"tf":1.0},"22":{"tf":1.0},"224":{"tf":1.0},"242":{"tf":1.0},"250":{"tf":1.0},"253":{"tf":1.4142135623730951},"258":{"tf":1.4142135623730951},"259":{"tf":1.0},"263":{"tf":1.0},"264":{"tf":1.0},"268":{"tf":1.0},"272":{"tf":1.4142135623730951},"273":{"tf":1.0},"274":{"tf":2.23606797749979},"276":{"tf":1.7320508075688772},"277":{"tf":1.4142135623730951},"282":{"tf":1.0},"283":{"tf":1.0},"286":{"tf":1.0},"3":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.4142135623730951},"39":{"tf":1.0},"4":{"tf":1.0},"40":{"tf":1.0},"50":{"tf":1.0},"51":{"tf":1.0},"56":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0},"67":{"tf":1.0},"7":{"tf":1.0},"74":{"tf":1.0},"8":{"tf":1.0},"90":{"tf":1.0}}},"df":0,"docs":{}}}},"v":{"df":0,"docs":{},"e":{"df":2,"docs":{"174":{"tf":1.0},"41":{"tf":1.0}},"n":{"df":14,"docs":{"115":{"tf":1.0},"156":{"tf":1.0},"167":{"tf":1.0},"172":{"tf":1.0},"197":{"tf":1.0},"205":{"tf":1.0},"208":{"tf":1.0},"225":{"tf":1.0},"260":{"tf":1.0},"276":{"tf":1.0},"43":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"64":{"tf":1.0}}}}}},"l":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"n":{"df":11,"docs":{"162":{"tf":1.0},"167":{"tf":2.0},"169":{"tf":1.4142135623730951},"170":{"tf":1.4142135623730951},"171":{"tf":1.4142135623730951},"172":{"tf":1.0},"174":{"tf":1.4142135623730951},"200":{"tf":1.0},"219":{"tf":1.0},"253":{"tf":1.0},"259":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"o":{"b":{"a":{"df":0,"docs":{},"l":{"df":3,"docs":{"115":{"tf":1.0},"225":{"tf":1.0},"86":{"tf":1.7320508075688772}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"o":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"103":{"tf":1.0},"23":{"tf":1.0}}}},"df":14,"docs":{"105":{"tf":1.0},"115":{"tf":1.0},"125":{"tf":1.0},"126":{"tf":1.0},"226":{"tf":1.0},"229":{"tf":1.0},"233":{"tf":1.0},"25":{"tf":1.4142135623730951},"26":{"tf":1.0},"37":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0},"73":{"tf":1.0},"81":{"tf":1.7320508075688772}},"e":{"df":5,"docs":{"103":{"tf":1.4142135623730951},"18":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"62":{"tf":1.4142135623730951}}},"n":{"df":0,"docs":{},"e":{"df":1,"docs":{"158":{"tf":1.0}}}},"o":{"d":{"df":29,"docs":{"107":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"132":{"tf":1.0},"150":{"tf":1.0},"154":{"tf":1.7320508075688772},"155":{"tf":2.0},"156":{"tf":1.7320508075688772},"157":{"tf":1.0},"158":{"tf":1.4142135623730951},"159":{"tf":1.4142135623730951},"169":{"tf":1.0},"170":{"tf":1.7320508075688772},"171":{"tf":2.0},"172":{"tf":2.8284271247461903},"174":{"tf":1.0},"182":{"tf":1.7320508075688772},"183":{"tf":1.4142135623730951},"184":{"tf":1.7320508075688772},"19":{"tf":1.0},"194":{"tf":1.0},"212":{"tf":1.0},"213":{"tf":1.4142135623730951},"214":{"tf":1.0},"225":{"tf":1.0},"37":{"tf":1.0},"6":{"tf":2.0},"82":{"tf":1.0},"90":{"tf":1.0}}},"df":0,"docs":{}},"t":{"c":{"df":0,"docs":{},"h":{"a":{"df":1,"docs":{"224":{"tf":1.0}}},"df":0,"docs":{},"y":{"a":{"df":1,"docs":{"228":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":1,"docs":{"3":{"tf":1.0}}}}}}},"p":{"df":0,"docs":{},"g":{"df":1,"docs":{"7":{"tf":1.0}}}},"r":{"a":{"d":{"df":0,"docs":{},"l":{"df":8,"docs":{"22":{"tf":1.4142135623730951},"220":{"tf":1.0},"274":{"tf":1.7320508075688772},"37":{"tf":1.4142135623730951},"40":{"tf":1.0},"49":{"tf":1.0},"65":{"tf":1.0},"70":{"tf":1.4142135623730951}},"e":{"'":{"df":3,"docs":{"22":{"tf":1.0},"221":{"tf":1.0},"222":{"tf":1.0}}},".":{"\"":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{".":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"22":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"(":{"'":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{".":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"22":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"w":{"df":7,"docs":{"108":{"tf":1.4142135623730951},"13":{"tf":1.0},"18":{"tf":1.4142135623730951},"20":{"tf":1.0},"22":{"tf":1.4142135623730951},"45":{"tf":1.0},"70":{"tf":1.4142135623730951}}}}},"u":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"101":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":3,"docs":{"221":{"tf":1.4142135623730951},"58":{"tf":1.4142135623730951},"59":{"tf":1.0}},"i":{"c":{"df":1,"docs":{"126":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":3,"docs":{"282":{"tf":1.0},"52":{"tf":1.0},"75":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"210":{"tf":1.0}}}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"a":{"df":1,"docs":{"237":{"tf":1.0}}},"df":0,"docs":{}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":4,"docs":{"189":{"tf":1.0},"37":{"tf":1.0},"40":{"tf":1.0},"73":{"tf":2.0}}}},"w":{"df":2,"docs":{"199":{"tf":1.0},"208":{"tf":1.0}},"n":{"df":1,"docs":{"64":{"tf":1.0}}}}}},"t":{"df":1,"docs":{"200":{"tf":2.8284271247461903}}},"u":{"a":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":1,"docs":{"201":{"tf":1.0}}}}}},"d":{"df":2,"docs":{"274":{"tf":1.0},"64":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"i":{"d":{"a":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"75":{"tf":1.0}}},"df":0,"docs":{}}},"df":19,"docs":{"101":{"tf":1.0},"102":{"tf":1.4142135623730951},"11":{"tf":1.0},"23":{"tf":1.7320508075688772},"231":{"tf":1.0},"245":{"tf":1.4142135623730951},"248":{"tf":1.7320508075688772},"278":{"tf":1.4142135623730951},"32":{"tf":1.7320508075688772},"37":{"tf":1.0},"41":{"tf":1.0},"62":{"tf":3.3166247903554},"69":{"tf":1.0},"7":{"tf":1.4142135623730951},"75":{"tf":1.7320508075688772},"86":{"tf":1.0},"87":{"tf":2.0},"93":{"tf":1.0},"99":{"tf":1.0}},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":10,"docs":{"128":{"tf":1.0},"274":{"tf":1.4142135623730951},"3":{"tf":1.0},"43":{"tf":1.0},"45":{"tf":1.0},"63":{"tf":1.4142135623730951},"69":{"tf":1.0},"7":{"tf":1.0},"91":{"tf":1.0},"96":{"tf":1.0}}}}}}},"df":1,"docs":{"109":{"tf":1.0}}}},"y":{"df":0,"docs":{},"p":{"df":2,"docs":{"11":{"tf":1.7320508075688772},"282":{"tf":1.4142135623730951}}}}},"h":{"a":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"45":{"tf":1.0}}}}},"i":{"df":2,"docs":{"107":{"tf":1.0},"37":{"tf":1.0}}},"y":{"_":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"_":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"(":{"\"":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"37":{"tf":1.0}}}},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":1,"docs":{"37":{"tf":1.0}}}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"s":{"df":0,"docs":{},"g":{"df":1,"docs":{"37":{"tf":1.0}}}}}},"df":1,"docs":{"37":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{},"n":{"d":{"df":20,"docs":{"101":{"tf":1.0},"103":{"tf":2.8284271247461903},"104":{"tf":2.0},"106":{"tf":1.0},"108":{"tf":2.23606797749979},"109":{"tf":2.23606797749979},"111":{"tf":1.0},"116":{"tf":1.0},"224":{"tf":1.0},"236":{"tf":1.0},"249":{"tf":1.0},"252":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"70":{"tf":1.4142135623730951},"72":{"tf":1.4142135623730951},"73":{"tf":1.4142135623730951},"74":{"tf":1.0},"78":{"tf":1.0},"90":{"tf":1.0}},"l":{"df":16,"docs":{"128":{"tf":1.0},"130":{"tf":1.0},"133":{"tf":1.0},"134":{"tf":1.0},"146":{"tf":1.0},"148":{"tf":1.0},"228":{"tf":1.0},"233":{"tf":1.0},"243":{"tf":1.0},"244":{"tf":1.0},"245":{"tf":1.0},"272":{"tf":1.0},"40":{"tf":1.0},"63":{"tf":1.0},"79":{"tf":1.0},"86":{"tf":1.0}}}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":11,"docs":{"106":{"tf":1.0},"133":{"tf":1.4142135623730951},"154":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0},"225":{"tf":1.0},"230":{"tf":1.0},"242":{"tf":1.0},"26":{"tf":1.0},"273":{"tf":1.0},"283":{"tf":1.0}}}},"i":{"df":2,"docs":{"124":{"tf":1.0},"263":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"170":{"tf":1.0}}}}}}},"r":{"d":{"df":7,"docs":{"104":{"tf":1.0},"158":{"tf":1.0},"189":{"tf":1.0},"200":{"tf":1.0},"213":{"tf":1.0},"233":{"tf":1.0},"241":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"166":{"tf":1.0}}}},"w":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"282":{"tf":1.0}}}},"df":0,"docs":{}}},"df":2,"docs":{"48":{"tf":1.0},"60":{"tf":1.4142135623730951}},"m":{"df":2,"docs":{"201":{"tf":1.0},"225":{"tf":1.0}}}},"s":{"df":0,"docs":{},"h":{"df":3,"docs":{"268":{"tf":1.0},"274":{"tf":1.0},"64":{"tf":1.0}},"m":{"a":{"df":0,"docs":{},"p":{"<":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"242":{"tf":1.0}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"n":{"'":{"df":0,"docs":{},"t":{"df":2,"docs":{"154":{"tf":1.0},"155":{"tf":1.0}}}},"df":0,"docs":{},"’":{"df":0,"docs":{},"t":{"df":1,"docs":{"194":{"tf":1.0}}}}}},"v":{"df":0,"docs":{},"e":{"df":12,"docs":{"105":{"tf":1.0},"126":{"tf":1.0},"146":{"tf":1.0},"166":{"tf":1.0},"174":{"tf":1.0},"183":{"tf":1.0},"215":{"tf":1.0},"256":{"tf":1.0},"46":{"tf":1.0},"60":{"tf":1.0},"82":{"tf":1.0},"86":{"tf":1.0}},"n":{"'":{"df":0,"docs":{},"t":{"df":4,"docs":{"20":{"tf":1.0},"274":{"tf":1.0},"36":{"tf":1.0},"60":{"tf":1.0}}}},"df":0,"docs":{},"’":{"df":0,"docs":{},"t":{"df":1,"docs":{"213":{"tf":1.0}}}}}}}},"df":0,"docs":{},"e":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":9,"docs":{"109":{"tf":1.0},"251":{"tf":1.0},"252":{"tf":1.4142135623730951},"268":{"tf":1.0},"269":{"tf":1.0},"274":{"tf":1.0},"28":{"tf":1.0},"33":{"tf":1.0},"73":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"242":{"tf":1.0}}}}},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"105":{"tf":1.0}}}}},"y":{"df":0,"docs":{},"w":{"df":0,"docs":{},"e":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":1,"docs":{"59":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"p":{"df":17,"docs":{"106":{"tf":1.0},"115":{"tf":1.0},"122":{"tf":1.0},"165":{"tf":1.0},"190":{"tf":1.0},"222":{"tf":1.0},"232":{"tf":1.0},"264":{"tf":1.0},"274":{"tf":1.4142135623730951},"278":{"tf":1.0},"282":{"tf":1.0},"3":{"tf":1.0},"37":{"tf":1.0},"59":{"tf":1.0},"62":{"tf":1.0},"75":{"tf":1.0},"90":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"222":{"tf":1.0},"43":{"tf":1.0}}}}}},"n":{"c":{"df":2,"docs":{"200":{"tf":1.0},"278":{"tf":1.0}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"e":{"'":{"df":1,"docs":{"37":{"tf":1.0}}},"df":30,"docs":{"103":{"tf":2.449489742783178},"108":{"tf":1.0},"109":{"tf":1.0},"117":{"tf":1.0},"123":{"tf":1.0},"171":{"tf":1.0},"20":{"tf":1.0},"200":{"tf":2.0},"201":{"tf":1.0},"210":{"tf":1.0},"225":{"tf":1.0},"229":{"tf":1.4142135623730951},"242":{"tf":1.0},"243":{"tf":1.0},"249":{"tf":1.0},"258":{"tf":1.0},"264":{"tf":1.0},"266":{"tf":1.4142135623730951},"274":{"tf":1.7320508075688772},"37":{"tf":1.0},"40":{"tf":1.0},"60":{"tf":1.4142135623730951},"69":{"tf":1.0},"70":{"tf":1.7320508075688772},"72":{"tf":1.4142135623730951},"76":{"tf":1.0},"81":{"tf":1.0},"82":{"tf":1.0},"84":{"tf":1.0},"90":{"tf":1.0}}}}},"i":{"d":{"df":0,"docs":{},"e":{"df":2,"docs":{"233":{"tf":1.0},"245":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":1,"docs":{"83":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"g":{"df":0,"docs":{},"h":{"df":26,"docs":{"103":{"tf":1.4142135623730951},"200":{"tf":1.0},"201":{"tf":1.0},"205":{"tf":1.0},"214":{"tf":1.0},"219":{"tf":1.7320508075688772},"22":{"tf":1.0},"225":{"tf":2.23606797749979},"226":{"tf":2.23606797749979},"229":{"tf":2.0},"23":{"tf":1.0},"230":{"tf":1.0},"232":{"tf":1.0},"233":{"tf":1.0},"236":{"tf":2.0},"237":{"tf":1.0},"239":{"tf":1.0},"243":{"tf":1.0},"244":{"tf":1.0},"245":{"tf":1.0},"251":{"tf":1.4142135623730951},"254":{"tf":1.7320508075688772},"256":{"tf":1.0},"281":{"tf":1.0},"41":{"tf":1.0},"64":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"103":{"tf":1.4142135623730951},"209":{"tf":1.0}}}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":2,"docs":{"109":{"tf":1.0},"251":{"tf":1.0}}}}}}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"174":{"tf":1.0}},"i":{"df":22,"docs":{"134":{"tf":1.0},"202":{"tf":1.0},"203":{"tf":1.4142135623730951},"204":{"tf":1.4142135623730951},"205":{"tf":2.8284271247461903},"206":{"tf":2.449489742783178},"207":{"tf":1.0},"208":{"tf":2.449489742783178},"209":{"tf":2.23606797749979},"210":{"tf":1.7320508075688772},"212":{"tf":2.0},"213":{"tf":2.0},"215":{"tf":1.4142135623730951},"217":{"tf":1.0},"218":{"tf":2.0},"232":{"tf":2.0},"233":{"tf":2.0},"242":{"tf":2.23606797749979},"257":{"tf":1.0},"264":{"tf":1.0},"283":{"tf":1.0},"83":{"tf":1.0}}}}}}},"t":{"df":3,"docs":{"189":{"tf":1.0},"201":{"tf":1.4142135623730951},"60":{"tf":1.0}}}},"o":{"c":{"df":1,"docs":{"59":{"tf":1.0}}},"df":0,"docs":{},"l":{"d":{"df":3,"docs":{"115":{"tf":1.0},"132":{"tf":1.0},"245":{"tf":1.0}}},"df":0,"docs":{}},"m":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":1,"docs":{"11":{"tf":1.7320508075688772}}}}}},"df":1,"docs":{"64":{"tf":1.0}}}},"o":{"df":0,"docs":{},"k":{"df":1,"docs":{"119":{"tf":1.0}}}},"p":{"df":0,"docs":{},"e":{"df":1,"docs":{"225":{"tf":1.4142135623730951}},"f":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"242":{"tf":1.0}}}}}}}}},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"190":{"tf":1.0}}}},"u":{"df":0,"docs":{},"r":{"df":2,"docs":{"10":{"tf":1.0},"64":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"=":{"\"":{".":{".":{"/":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"c":{".":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"/":{"d":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"283":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":1,"docs":{"283":{"tf":1.0}}}}}}},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"283":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":1,"docs":{"284":{"tf":1.0}}}},"t":{"df":0,"docs":{},"p":{"df":4,"docs":{"189":{"tf":1.0},"190":{"tf":1.4142135623730951},"201":{"tf":1.4142135623730951},"274":{"tf":1.0}},"s":{":":{"/":{"/":{"a":{"d":{"df":0,"docs":{},"r":{".":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"u":{"b":{".":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"/":{"df":0,"docs":{},"m":{"a":{"d":{"df":0,"docs":{},"r":{"df":1,"docs":{"134":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":1,"docs":{"134":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{".":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"i":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"/":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"/":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"u":{"b":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"/":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"277":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"a":{".":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"/":{"df":0,"docs":{},"e":{"df":0,"docs":{},"z":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"11":{"tf":1.0}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{".":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"e":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"/":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"l":{"/":{"df":0,"docs":{},"g":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{".":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"11":{"tf":1.0}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}}}},"df":0,"docs":{}}}}}}}}},"d":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"/":{"d":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"/":{"d":{"/":{"1":{"df":0,"docs":{},"q":{"df":0,"docs":{},"w":{"3":{"6":{"_":{"7":{"df":0,"docs":{},"g":{"6":{"df":0,"docs":{},"x":{"df":0,"docs":{},"y":{"df":0,"docs":{},"h":{"df":0,"docs":{},"v":{"df":0,"docs":{},"j":{"df":0,"docs":{},"z":{"d":{"df":0,"docs":{},"m":{"df":1,"docs":{"177":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"x":{"1":{"df":0,"docs":{},"h":{"c":{"3":{"df":0,"docs":{},"t":{"5":{"df":0,"docs":{},"z":{"c":{"7":{"df":1,"docs":{"185":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"x":{"df":3,"docs":{"119":{"tf":1.0},"266":{"tf":1.4142135623730951},"271":{"tf":1.0}}}}}}}}},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"u":{"b":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"/":{"df":0,"docs":{},"j":{"a":{"df":0,"docs":{},"k":{"df":0,"docs":{},"u":{"b":{"a":{"d":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"w":{"/":{"a":{"df":0,"docs":{},"r":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"51":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"/":{"a":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"_":{"d":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"137":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}}}},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"/":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":2,"docs":{"11":{"tf":1.0},"186":{"tf":1.0}}}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":5,"docs":{"119":{"tf":1.0},"253":{"tf":1.0},"31":{"tf":1.0},"34":{"tf":1.0},"36":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"l":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"i":{"d":{".":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"/":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"i":{"d":{"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"/":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"/":{"a":{"c":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"/":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"v":{"_":{"a":{"b":{"7":{"2":{"df":0,"docs":{},"e":{"2":{"1":{"8":{"df":1,"docs":{"258":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"m":{"a":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{".":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{".":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"/":{"?":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"=":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"2":{"/":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"/":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"271":{"tf":1.0}},"e":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"271":{"tf":1.0}}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":1,"docs":{"271":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"/":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"/":{"b":{"df":0,"docs":{},"o":{"a":{"df":0,"docs":{},"r":{"d":{"/":{"df":0,"docs":{},"o":{"9":{"df":0,"docs":{},"j":{"_":{"df":0,"docs":{},"l":{"df":0,"docs":{},"w":{"df":0,"docs":{},"x":{"3":{"df":0,"docs":{},"j":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":1,"docs":{"274":{"tf":1.0}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{".":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"d":{".":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"/":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"s":{"/":{"1":{"0":{"7":{"8":{"df":1,"docs":{"215":{"tf":1.0}}},"df":0,"docs":{}},"8":{"1":{"df":1,"docs":{"216":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"/":{"2":{".":{"0":{"df":1,"docs":{"2":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"df":1,"docs":{"176":{"tf":1.0}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"u":{"b":{".":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"n":{".":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"/":{"b":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"/":{"df":0,"docs":{},"s":{"d":{"df":0,"docs":{},"k":{"df":1,"docs":{"176":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"x":{"df":0,"docs":{},"h":{"4":{"df":0,"docs":{},"n":{"df":0,"docs":{},"q":{"df":0,"docs":{},"y":{"df":0,"docs":{},"z":{"df":0,"docs":{},"y":{"c":{"df":0,"docs":{},"s":{"df":0,"docs":{},"y":{"a":{"df":0,"docs":{},"j":{"df":0,"docs":{},"g":{"0":{"df":0,"docs":{},"l":{"5":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"3":{"3":{"df":0,"docs":{},"y":{"d":{"5":{"df":0,"docs":{},"m":{"/":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"177":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"69":{"tf":1.0}}}}}}}},"i":{"'":{"df":0,"docs":{},"m":{"df":1,"docs":{"200":{"tf":1.0}}}},".":{"df":1,"docs":{"253":{"tf":1.0}}},"/":{"df":0,"docs":{},"o":{"df":2,"docs":{"115":{"tf":1.0},"179":{"tf":1.4142135623730951}}}},"d":{"df":7,"docs":{"231":{"tf":2.449489742783178},"242":{"tf":1.4142135623730951},"271":{"tf":1.0},"62":{"tf":1.0},"80":{"tf":1.0},"86":{"tf":2.6457513110645907},"87":{"tf":1.0}},"e":{"a":{"df":2,"docs":{"51":{"tf":1.0},"87":{"tf":1.0}},"l":{"df":7,"docs":{"119":{"tf":1.0},"122":{"tf":1.0},"206":{"tf":1.0},"208":{"tf":1.0},"229":{"tf":1.0},"49":{"tf":1.0},"58":{"tf":1.0}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":3,"docs":{"178":{"tf":1.0},"182":{"tf":1.0},"184":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{",":{"a":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"180":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"n":{"df":0,"docs":{},"t":{"df":4,"docs":{"18":{"tf":1.4142135623730951},"249":{"tf":1.0},"283":{"tf":1.0},"64":{"tf":1.0}},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":15,"docs":{"104":{"tf":1.0},"141":{"tf":1.0},"149":{"tf":1.4142135623730951},"151":{"tf":1.4142135623730951},"152":{"tf":1.0},"187":{"tf":1.7320508075688772},"188":{"tf":1.0},"191":{"tf":1.0},"200":{"tf":2.23606797749979},"214":{"tf":1.0},"22":{"tf":1.0},"225":{"tf":1.7320508075688772},"232":{"tf":1.0},"241":{"tf":1.4142135623730951},"86":{"tf":1.0}}}}}}}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"t":{"df":2,"docs":{"103":{"tf":1.4142135623730951},"43":{"tf":1.0}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{",":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"75":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":13,"docs":{"189":{"tf":1.0},"193":{"tf":1.0},"194":{"tf":1.0},"200":{"tf":1.7320508075688772},"201":{"tf":1.7320508075688772},"232":{"tf":1.0},"233":{"tf":1.7320508075688772},"237":{"tf":1.0},"241":{"tf":1.0},"242":{"tf":1.4142135623730951},"245":{"tf":1.0},"87":{"tf":1.0},"88":{"tf":1.0}}},"f":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":1,"docs":{"37":{"tf":1.0}}}}},"df":0,"docs":{}},"g":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":5,"docs":{"200":{"tf":1.0},"226":{"tf":1.4142135623730951},"233":{"tf":1.0},"241":{"tf":1.0},"61":{"tf":1.0}}}}}},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"132":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"m":{"a":{"df":0,"docs":{},"g":{"df":2,"docs":{"194":{"tf":1.0},"274":{"tf":1.0}},"i":{"df":0,"docs":{},"n":{"df":2,"docs":{"225":{"tf":1.0},"232":{"tf":1.0}}}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"i":{"df":2,"docs":{"189":{"tf":1.0},"81":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"p":{"a":{"c":{"df":0,"docs":{},"t":{"df":4,"docs":{"167":{"tf":1.0},"178":{"tf":1.0},"200":{"tf":1.0},"229":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"df":2,"docs":{"242":{"tf":1.7320508075688772},"62":{"tf":1.0}},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":43,"docs":{"103":{"tf":2.0},"105":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"115":{"tf":1.0},"125":{"tf":1.0},"128":{"tf":1.0},"140":{"tf":1.0},"141":{"tf":1.0},"146":{"tf":1.0},"152":{"tf":1.4142135623730951},"159":{"tf":1.0},"167":{"tf":1.0},"200":{"tf":1.7320508075688772},"203":{"tf":1.7320508075688772},"208":{"tf":1.0},"218":{"tf":1.0},"225":{"tf":2.0},"226":{"tf":2.6457513110645907},"227":{"tf":2.0},"228":{"tf":1.4142135623730951},"230":{"tf":2.0},"231":{"tf":1.4142135623730951},"232":{"tf":1.4142135623730951},"233":{"tf":1.0},"234":{"tf":1.0},"239":{"tf":1.0},"241":{"tf":2.0},"244":{"tf":1.0},"247":{"tf":1.0},"254":{"tf":1.0},"256":{"tf":1.7320508075688772},"257":{"tf":1.7320508075688772},"258":{"tf":2.23606797749979},"264":{"tf":1.0},"274":{"tf":1.0},"282":{"tf":1.0},"37":{"tf":1.4142135623730951},"43":{"tf":1.0},"45":{"tf":1.0},"48":{"tf":1.0},"82":{"tf":1.0},"84":{"tf":1.4142135623730951}}}}}}},"i":{"c":{"df":3,"docs":{"197":{"tf":1.0},"205":{"tf":1.4142135623730951},"263":{"tf":1.0}},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"138":{"tf":1.0}}}}},"df":2,"docs":{"123":{"tf":1.4142135623730951},"245":{"tf":1.0}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"m":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"145":{"tf":2.0}}}}}}}}}},"df":30,"docs":{"103":{"tf":1.4142135623730951},"105":{"tf":1.7320508075688772},"138":{"tf":1.0},"158":{"tf":1.0},"16":{"tf":1.0},"165":{"tf":1.0},"166":{"tf":1.4142135623730951},"167":{"tf":1.0},"20":{"tf":1.0},"205":{"tf":1.0},"206":{"tf":1.0},"210":{"tf":1.0},"213":{"tf":1.0},"214":{"tf":1.0},"218":{"tf":1.0},"242":{"tf":1.0},"251":{"tf":1.0},"253":{"tf":1.0},"258":{"tf":1.0},"260":{"tf":1.0},"27":{"tf":1.0},"37":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"49":{"tf":1.0},"6":{"tf":1.4142135623730951},"60":{"tf":1.0},"73":{"tf":1.7320508075688772},"87":{"tf":1.4142135623730951},"9":{"tf":1.0}}}},"s":{"df":2,"docs":{"206":{"tf":1.0},"210":{"tf":1.0}},"s":{"df":2,"docs":{"201":{"tf":1.0},"43":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":10,"docs":{"111":{"tf":1.0},"145":{"tf":1.0},"146":{"tf":1.0},"150":{"tf":1.0},"171":{"tf":1.4142135623730951},"206":{"tf":1.0},"208":{"tf":1.0},"264":{"tf":1.0},"282":{"tf":1.0},"51":{"tf":1.0}}}}}}},"n":{"c":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":1,"docs":{"137":{"tf":1.0}}}}},"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"205":{"tf":1.7320508075688772},"218":{"tf":1.0}}},"df":0,"docs":{}},"l":{"df":0,"docs":{},"u":{"d":{"df":39,"docs":{"105":{"tf":1.0},"113":{"tf":1.0},"118":{"tf":1.0},"124":{"tf":1.0},"167":{"tf":2.449489742783178},"169":{"tf":1.0},"172":{"tf":1.0},"18":{"tf":1.0},"190":{"tf":1.0},"193":{"tf":1.0},"200":{"tf":1.7320508075688772},"204":{"tf":1.0},"222":{"tf":1.0},"224":{"tf":1.0},"226":{"tf":1.4142135623730951},"23":{"tf":1.4142135623730951},"231":{"tf":1.0},"251":{"tf":1.7320508075688772},"253":{"tf":1.4142135623730951},"274":{"tf":1.4142135623730951},"283":{"tf":1.0},"29":{"tf":1.4142135623730951},"31":{"tf":2.23606797749979},"32":{"tf":1.4142135623730951},"34":{"tf":1.4142135623730951},"36":{"tf":2.0},"37":{"tf":1.0},"4":{"tf":1.0},"58":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.0},"69":{"tf":1.4142135623730951},"7":{"tf":2.449489742783178},"70":{"tf":1.4142135623730951},"72":{"tf":1.0},"73":{"tf":1.7320508075688772},"74":{"tf":1.0},"78":{"tf":1.0},"99":{"tf":1.0}},"e":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"81":{"tf":1.0}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"m":{"df":5,"docs":{"246":{"tf":1.0},"248":{"tf":1.0},"249":{"tf":1.4142135623730951},"283":{"tf":1.0},"88":{"tf":1.0}},"p":{"a":{"df":0,"docs":{},"t":{"df":5,"docs":{"169":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"268":{"tf":1.0}}}},"df":0,"docs":{}}},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"170":{"tf":1.0}}}}}}}},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":3,"docs":{"205":{"tf":1.0},"208":{"tf":1.0},"41":{"tf":1.0}}}},"d":{"df":1,"docs":{"22":{"tf":1.0}},"u":{"df":0,"docs":{},"l":{"df":1,"docs":{"60":{"tf":1.0}}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"190":{"tf":1.0},"250":{"tf":1.0}}}}}}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"166":{"tf":1.0},"170":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"201":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"x":{"df":5,"docs":{"268":{"tf":1.0},"269":{"tf":1.0},"271":{"tf":1.0},"40":{"tf":1.0},"62":{"tf":1.0}}}},"i":{"c":{"df":7,"docs":{"154":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0},"253":{"tf":1.0},"268":{"tf":1.0},"40":{"tf":1.0},"7":{"tf":1.0}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"200":{"tf":1.0}}}}}},"df":0,"docs":{}}},"v":{"df":0,"docs":{},"i":{"d":{"df":0,"docs":{},"u":{"df":9,"docs":{"124":{"tf":1.0},"187":{"tf":1.0},"221":{"tf":1.4142135623730951},"225":{"tf":1.0},"228":{"tf":1.0},"244":{"tf":1.4142135623730951},"245":{"tf":1.4142135623730951},"274":{"tf":1.0},"283":{"tf":1.0}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"196":{"tf":1.0},"263":{"tf":1.0}}}}}},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"82":{"tf":1.0}}}}}},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"274":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}},"o":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"252":{"tf":1.7320508075688772}}}}}}}},"/":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"226":{"tf":1.4142135623730951},"229":{"tf":1.0},"241":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":1,"docs":{"242":{"tf":1.0}},"r":{"df":0,"docs":{},"m":{"df":17,"docs":{"13":{"tf":1.0},"134":{"tf":1.4142135623730951},"177":{"tf":1.0},"226":{"tf":1.0},"23":{"tf":1.0},"241":{"tf":1.0},"242":{"tf":1.0},"263":{"tf":1.4142135623730951},"268":{"tf":1.0},"274":{"tf":1.0},"283":{"tf":1.0},"32":{"tf":1.0},"36":{"tf":1.0},"4":{"tf":1.0},"7":{"tf":1.0},"73":{"tf":1.0},"75":{"tf":1.0}}}}},"r":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":2,"docs":{"167":{"tf":1.0},"48":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"233":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"t":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"242":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"11":{"tf":1.0}},"i":{"a":{"df":0,"docs":{},"l":{"_":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"j":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"179":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}}}}}}}}},"df":0,"docs":{}}},"df":16,"docs":{"10":{"tf":1.0},"132":{"tf":1.0},"164":{"tf":1.0},"167":{"tf":1.7320508075688772},"172":{"tf":1.0},"174":{"tf":1.0},"184":{"tf":1.0},"189":{"tf":1.0},"190":{"tf":1.4142135623730951},"199":{"tf":1.0},"200":{"tf":1.4142135623730951},"201":{"tf":1.4142135623730951},"231":{"tf":1.0},"238":{"tf":1.0},"242":{"tf":1.4142135623730951},"244":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":3,"docs":{"214":{"tf":1.0},"233":{"tf":1.0},"254":{"tf":1.0}}}}},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"104":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":2,"docs":{"59":{"tf":1.0},"62":{"tf":1.0}}}}},"i":{"d":{"df":7,"docs":{"12":{"tf":1.0},"20":{"tf":2.23606797749979},"263":{"tf":1.0},"30":{"tf":1.0},"34":{"tf":1.0},"35":{"tf":1.0},"61":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"108":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"245":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"a":{"df":0,"docs":{},"l":{"df":14,"docs":{"104":{"tf":1.0},"11":{"tf":6.48074069840786},"12":{"tf":2.0},"13":{"tf":1.7320508075688772},"14":{"tf":1.4142135623730951},"16":{"tf":1.0},"18":{"tf":1.0},"201":{"tf":1.4142135623730951},"269":{"tf":1.4142135623730951},"279":{"tf":1.0},"286":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":2.0},"53":{"tf":1.4142135623730951}}},"n":{"c":{"df":6,"docs":{"244":{"tf":1.0},"245":{"tf":1.4142135623730951},"246":{"tf":1.0},"247":{"tf":1.0},"249":{"tf":1.0},"78":{"tf":1.0}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":2,"docs":{"115":{"tf":1.0},"245":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"a":{"d":{"df":14,"docs":{"158":{"tf":1.0},"172":{"tf":1.0},"18":{"tf":1.0},"188":{"tf":1.0},"194":{"tf":1.4142135623730951},"231":{"tf":1.0},"242":{"tf":1.0},"37":{"tf":1.0},"58":{"tf":1.0},"61":{"tf":1.4142135623730951},"64":{"tf":1.0},"78":{"tf":1.0},"79":{"tf":1.0},"84":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":10,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"11":{"tf":2.0},"12":{"tf":1.4142135623730951},"21":{"tf":1.0},"281":{"tf":1.0},"32":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"5":{"tf":1.0}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"62":{"tf":1.4142135623730951}}}}}}}}}},"t":{"a":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"105":{"tf":1.0},"171":{"tf":1.0}}}},"df":0,"docs":{}},"df":1,"docs":{"116":{"tf":1.0}},"e":{"df":0,"docs":{},"g":{"df":1,"docs":{"250":{"tf":1.0}},"r":{"df":17,"docs":{"0":{"tf":1.0},"201":{"tf":1.0},"223":{"tf":1.0},"225":{"tf":1.0},"226":{"tf":1.0},"228":{"tf":1.0},"257":{"tf":1.0},"259":{"tf":1.0},"263":{"tf":1.0},"274":{"tf":2.449489742783178},"283":{"tf":1.4142135623730951},"47":{"tf":1.0},"51":{"tf":1.4142135623730951},"52":{"tf":1.0},"60":{"tf":2.449489742783178},"61":{"tf":1.0},"7":{"tf":1.0}}}},"l":{"df":2,"docs":{"17":{"tf":1.0},"280":{"tf":1.0}}},"n":{"d":{"df":5,"docs":{"104":{"tf":1.4142135623730951},"107":{"tf":1.0},"274":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":1,"docs":{"64":{"tf":1.0}}},"t":{"df":1,"docs":{"227":{"tf":1.0}}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":6,"docs":{"140":{"tf":1.0},"205":{"tf":1.0},"225":{"tf":1.0},"226":{"tf":1.7320508075688772},"232":{"tf":1.4142135623730951},"254":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":6,"docs":{"104":{"tf":1.0},"231":{"tf":1.0},"263":{"tf":1.0},"37":{"tf":1.0},"41":{"tf":1.0},"58":{"tf":1.0}}}}},"f":{"a":{"c":{"df":11,"docs":{"103":{"tf":1.7320508075688772},"104":{"tf":2.0},"105":{"tf":1.7320508075688772},"106":{"tf":1.4142135623730951},"111":{"tf":1.0},"128":{"tf":1.0},"167":{"tf":1.0},"184":{"tf":1.0},"246":{"tf":1.0},"37":{"tf":1.0},"69":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"m":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"i":{"df":1,"docs":{"268":{"tf":1.0}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"l":{"df":1,"docs":{"62":{"tf":1.0}}}}}}},"n":{"df":6,"docs":{"105":{"tf":1.0},"106":{"tf":1.0},"146":{"tf":1.0},"175":{"tf":1.0},"193":{"tf":1.0},"283":{"tf":1.7320508075688772}}},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"220":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":2,"docs":{"124":{"tf":1.0},"242":{"tf":1.4142135623730951}}}}}}}},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"c":{"df":14,"docs":{"105":{"tf":1.0},"158":{"tf":1.0},"172":{"tf":1.0},"180":{"tf":1.0},"207":{"tf":1.4142135623730951},"208":{"tf":1.4142135623730951},"213":{"tf":1.0},"228":{"tf":1.0},"23":{"tf":1.0},"251":{"tf":1.0},"32":{"tf":1.0},"64":{"tf":1.0},"69":{"tf":1.0},"73":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":1,"docs":{"221":{"tf":1.0}}}},"s":{"df":0,"docs":{},"t":{"df":4,"docs":{"164":{"tf":1.0},"170":{"tf":1.0},"183":{"tf":1.0},"49":{"tf":1.0}},"i":{"df":0,"docs":{},"g":{"df":2,"docs":{"37":{"tf":1.0},"64":{"tf":1.0}}}}}}},"o":{"c":{"df":3,"docs":{"115":{"tf":1.0},"279":{"tf":1.0},"58":{"tf":1.0}}},"df":0,"docs":{},"k":{"df":2,"docs":{"105":{"tf":1.0},"283":{"tf":1.4142135623730951}}},"l":{"df":0,"docs":{},"v":{"df":12,"docs":{"116":{"tf":1.0},"167":{"tf":1.0},"172":{"tf":1.0},"20":{"tf":1.0},"231":{"tf":1.0},"242":{"tf":1.0},"244":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"49":{"tf":1.0},"62":{"tf":1.0},"68":{"tf":1.0}}}}}}},"o":{"df":62,"docs":{"10":{"tf":1.0},"103":{"tf":1.4142135623730951},"109":{"tf":1.4142135623730951},"11":{"tf":1.0},"115":{"tf":1.0},"118":{"tf":2.0},"119":{"tf":1.7320508075688772},"127":{"tf":1.0},"134":{"tf":1.0},"14":{"tf":1.7320508075688772},"145":{"tf":1.0},"15":{"tf":1.4142135623730951},"161":{"tf":1.7320508075688772},"162":{"tf":1.4142135623730951},"164":{"tf":1.0},"165":{"tf":1.0},"167":{"tf":1.4142135623730951},"169":{"tf":1.0},"170":{"tf":1.7320508075688772},"171":{"tf":1.4142135623730951},"172":{"tf":1.4142135623730951},"175":{"tf":1.0},"185":{"tf":1.0},"192":{"tf":1.0},"199":{"tf":1.0},"200":{"tf":1.4142135623730951},"202":{"tf":1.0},"203":{"tf":1.0},"213":{"tf":1.0},"215":{"tf":1.0},"218":{"tf":1.4142135623730951},"219":{"tf":1.0},"220":{"tf":1.0},"225":{"tf":1.7320508075688772},"228":{"tf":1.4142135623730951},"229":{"tf":1.0},"23":{"tf":2.0},"24":{"tf":1.4142135623730951},"241":{"tf":1.0},"25":{"tf":2.449489742783178},"251":{"tf":1.0},"252":{"tf":1.0},"255":{"tf":1.0},"257":{"tf":1.7320508075688772},"268":{"tf":1.4142135623730951},"271":{"tf":1.4142135623730951},"272":{"tf":1.4142135623730951},"274":{"tf":1.4142135623730951},"278":{"tf":1.4142135623730951},"28":{"tf":1.0},"280":{"tf":1.0},"31":{"tf":1.7320508075688772},"32":{"tf":1.4142135623730951},"33":{"tf":1.0},"38":{"tf":1.0},"56":{"tf":1.4142135623730951},"69":{"tf":1.0},"72":{"tf":1.4142135623730951},"73":{"tf":1.4142135623730951},"74":{"tf":1.0},"9":{"tf":1.0},"90":{"tf":1.4142135623730951}},"s":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"279":{"tf":1.0}}}}},"/":{"<":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"109":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"_":{"c":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"72":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"x":{"c":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"p":{"b":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"df":1,"docs":{"26":{"tf":1.4142135623730951}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"109":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":1,"docs":{"66":{"tf":1.0}}}}}}}},"df":0,"docs":{},"’":{"df":1,"docs":{"218":{"tf":1.0}}}},"w":{"df":1,"docs":{"89":{"tf":1.0}}}},"s":{"_":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"o":{"df":1,"docs":{"282":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"n":{"'":{"df":0,"docs":{},"t":{"df":13,"docs":{"11":{"tf":1.0},"12":{"tf":1.0},"169":{"tf":1.0},"220":{"tf":1.0},"225":{"tf":1.0},"231":{"tf":1.0},"233":{"tf":1.0},"236":{"tf":1.0},"58":{"tf":1.0},"6":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0},"82":{"tf":1.0}}}},"df":0,"docs":{},"’":{"df":0,"docs":{},"t":{"df":3,"docs":{"115":{"tf":1.0},"197":{"tf":1.0},"205":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"l":{"df":3,"docs":{"201":{"tf":1.0},"229":{"tf":1.4142135623730951},"274":{"tf":1.0}}}},"s":{"df":0,"docs":{},"u":{"df":21,"docs":{"1":{"tf":2.0},"104":{"tf":1.0},"111":{"tf":1.0},"115":{"tf":1.0},"116":{"tf":1.0},"152":{"tf":1.0},"165":{"tf":1.0},"169":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"197":{"tf":1.0},"205":{"tf":1.4142135623730951},"209":{"tf":1.0},"225":{"tf":1.0},"228":{"tf":1.0},"237":{"tf":1.0},"242":{"tf":1.4142135623730951},"3":{"tf":1.0},"4":{"tf":1.4142135623730951},"6":{"tf":3.4641016151377544}}}}},"t":{"'":{"df":54,"docs":{"104":{"tf":1.0},"105":{"tf":1.0},"107":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"11":{"tf":1.7320508075688772},"115":{"tf":1.0},"116":{"tf":1.0},"132":{"tf":1.0},"154":{"tf":1.0},"155":{"tf":1.0},"158":{"tf":1.0},"159":{"tf":1.0},"16":{"tf":1.4142135623730951},"167":{"tf":1.0},"169":{"tf":1.4142135623730951},"170":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"175":{"tf":1.0},"20":{"tf":1.0},"200":{"tf":1.0},"201":{"tf":1.0},"208":{"tf":1.0},"221":{"tf":1.0},"224":{"tf":1.0},"225":{"tf":1.4142135623730951},"227":{"tf":1.0},"232":{"tf":1.0},"242":{"tf":1.7320508075688772},"245":{"tf":1.7320508075688772},"249":{"tf":1.4142135623730951},"25":{"tf":1.0},"252":{"tf":1.0},"258":{"tf":1.0},"263":{"tf":1.0},"266":{"tf":1.0},"268":{"tf":1.0},"27":{"tf":1.4142135623730951},"274":{"tf":1.0},"43":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"48":{"tf":1.0},"49":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.4142135623730951},"7":{"tf":1.0},"75":{"tf":1.0},"78":{"tf":1.0},"81":{"tf":1.0},"84":{"tf":1.0},"9":{"tf":1.0}},"l":{"df":0,"docs":{},"l":{"df":1,"docs":{"82":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":7,"docs":{"165":{"tf":1.0},"172":{"tf":1.0},"189":{"tf":1.0},"232":{"tf":1.0},"59":{"tf":1.0},"73":{"tf":1.4142135623730951},"84":{"tf":2.0}},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":1,"docs":{"283":{"tf":1.0}}}}}}}}}}}}},"df":0,"docs":{}}}}}}},"r":{"df":5,"docs":{"104":{"tf":1.0},"105":{"tf":1.0},"124":{"tf":1.0},"167":{"tf":1.0},"249":{"tf":1.0}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":19,"docs":{"104":{"tf":1.0},"117":{"tf":1.0},"184":{"tf":1.0},"189":{"tf":1.0},"194":{"tf":1.7320508075688772},"197":{"tf":1.4142135623730951},"201":{"tf":1.0},"225":{"tf":1.0},"229":{"tf":1.0},"245":{"tf":1.0},"254":{"tf":1.0},"263":{"tf":1.0},"44":{"tf":1.0},"59":{"tf":1.0},"79":{"tf":1.0},"81":{"tf":1.0},"83":{"tf":1.0},"84":{"tf":1.4142135623730951},"86":{"tf":1.0}}}}}},"’":{"df":10,"docs":{"115":{"tf":1.0},"189":{"tf":1.0},"193":{"tf":1.4142135623730951},"194":{"tf":1.0},"205":{"tf":1.0},"210":{"tf":1.0},"212":{"tf":1.0},"213":{"tf":1.4142135623730951},"214":{"tf":1.7320508075688772},"87":{"tf":1.0}}}}},"j":{"a":{"df":0,"docs":{},"r":{"/":{"a":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"37":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"223":{"tf":1.0}},"e":{"d":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"176":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"v":{"a":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"/":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"/":{"<":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"103":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"_":{"c":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"70":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"_":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":1,"docs":{"12":{"tf":1.0}}}}}},"df":7,"docs":{"112":{"tf":1.0},"115":{"tf":1.0},"12":{"tf":1.4142135623730951},"125":{"tf":1.0},"128":{"tf":1.0},"223":{"tf":1.0},"37":{"tf":1.0}},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"192":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"d":{"df":0,"docs":{},"k":{"df":1,"docs":{"12":{"tf":1.0}}}},"df":0,"docs":{},"h":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"176":{"tf":1.0}}}},"df":0,"docs":{}}}}},"n":{"a":{"'":{"df":1,"docs":{"115":{"tf":1.0}}},"df":6,"docs":{"111":{"tf":1.0},"115":{"tf":1.4142135623730951},"116":{"tf":2.449489742783178},"37":{"tf":3.4641016151377544},"45":{"tf":1.0},"65":{"tf":1.0}}},"df":0,"docs":{},"i":{"df":2,"docs":{"115":{"tf":1.4142135623730951},"116":{"tf":2.0}}}},"o":{"b":{"df":4,"docs":{"274":{"tf":1.0},"278":{"tf":1.0},"282":{"tf":1.0},"74":{"tf":1.0}}},"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"l":{"_":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"d":{"df":1,"docs":{"264":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"s":{"df":1,"docs":{"241":{"tf":1.0}},"o":{"df":0,"docs":{},"n":{"df":3,"docs":{"189":{"tf":1.0},"271":{"tf":1.4142135623730951},"79":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"45":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":1,"docs":{"194":{"tf":1.0}}}}}}},"v":{"df":0,"docs":{},"m":{"df":2,"docs":{"115":{"tf":1.4142135623730951},"37":{"tf":1.0}}}}},"k":{"8":{"8":{"df":0,"docs":{},"h":{"df":0,"docs":{},"u":{"d":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"176":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":28,"docs":{"121":{"tf":1.0},"142":{"tf":1.0},"150":{"tf":1.0},"163":{"tf":1.0},"165":{"tf":1.0},"167":{"tf":1.0},"171":{"tf":1.0},"179":{"tf":1.0},"182":{"tf":1.4142135623730951},"184":{"tf":1.0},"198":{"tf":1.0},"205":{"tf":2.0},"206":{"tf":1.0},"207":{"tf":1.0},"208":{"tf":1.0},"209":{"tf":2.23606797749979},"210":{"tf":1.0},"212":{"tf":1.4142135623730951},"213":{"tf":1.0},"217":{"tf":1.0},"218":{"tf":1.0},"242":{"tf":1.4142135623730951},"26":{"tf":1.0},"261":{"tf":1.0},"263":{"tf":1.0},"278":{"tf":1.0},"58":{"tf":1.4142135623730951},"6":{"tf":1.0}}}},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"264":{"tf":1.0}}}},"y":{"/":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":1,"docs":{"80":{"tf":1.0}}}}},"df":0,"docs":{}}},"_":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"242":{"tf":1.0}}},"df":0,"docs":{}}},"df":15,"docs":{"142":{"tf":1.7320508075688772},"143":{"tf":1.4142135623730951},"145":{"tf":2.8284271247461903},"146":{"tf":2.0},"184":{"tf":1.0},"198":{"tf":1.0},"201":{"tf":1.0},"225":{"tf":1.0},"229":{"tf":1.4142135623730951},"237":{"tf":1.0},"245":{"tf":1.0},"248":{"tf":1.0},"254":{"tf":1.4142135623730951},"274":{"tf":1.4142135623730951},"283":{"tf":1.4142135623730951}}}},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":1,"docs":{"133":{"tf":1.0}}}},"n":{"d":{"df":7,"docs":{"189":{"tf":1.0},"190":{"tf":1.0},"201":{"tf":1.0},"221":{"tf":1.0},"225":{"tf":1.0},"229":{"tf":1.0},"242":{"tf":1.0}}},"df":0,"docs":{}}},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":22,"docs":{"114":{"tf":1.0},"115":{"tf":1.0},"117":{"tf":1.0},"119":{"tf":1.0},"128":{"tf":1.0},"17":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.0},"21":{"tf":1.0},"228":{"tf":1.7320508075688772},"229":{"tf":1.4142135623730951},"233":{"tf":1.4142135623730951},"254":{"tf":1.0},"26":{"tf":1.0},"268":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"5":{"tf":1.0},"58":{"tf":1.4142135623730951},"84":{"tf":1.0},"87":{"tf":1.4142135623730951},"89":{"tf":1.0}},"l":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"g":{"df":1,"docs":{"175":{"tf":1.0}}}},"df":0,"docs":{}}},"n":{"df":11,"docs":{"104":{"tf":1.0},"151":{"tf":1.4142135623730951},"152":{"tf":1.4142135623730951},"155":{"tf":1.0},"156":{"tf":1.0},"193":{"tf":1.0},"220":{"tf":1.0},"233":{"tf":1.0},"242":{"tf":1.0},"245":{"tf":1.0},"248":{"tf":1.4142135623730951}}}}}},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"/":{"df":0,"docs":{},"s":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":1,"docs":{"51":{"tf":1.0}}}}}}}},"df":22,"docs":{"101":{"tf":1.0},"103":{"tf":2.23606797749979},"104":{"tf":1.0},"108":{"tf":3.4641016151377544},"111":{"tf":2.0},"115":{"tf":2.0},"125":{"tf":1.0},"128":{"tf":1.4142135623730951},"196":{"tf":1.0},"221":{"tf":1.0},"222":{"tf":1.0},"225":{"tf":1.0},"268":{"tf":1.0},"271":{"tf":1.0},"274":{"tf":1.0},"45":{"tf":2.6457513110645907},"65":{"tf":1.7320508075688772},"68":{"tf":1.0},"7":{"tf":1.0},"70":{"tf":2.8284271247461903},"98":{"tf":1.0},"99":{"tf":1.4142135623730951}}}}}}},"t":{"df":2,"docs":{"70":{"tf":1.4142135623730951},"98":{"tf":1.4142135623730951}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"65":{"tf":1.0}}}}}}}},"l":{"a":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":2,"docs":{"6":{"tf":1.4142135623730951},"7":{"tf":1.4142135623730951}}}}},"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"187":{"tf":1.0}}}},"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"103":{"tf":1.0}}},"df":0,"docs":{}},"n":{"d":{"df":5,"docs":{"118":{"tf":1.0},"123":{"tf":1.0},"124":{"tf":1.0},"268":{"tf":1.0},"274":{"tf":1.4142135623730951}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"g":{"df":1,"docs":{"64":{"tf":1.0}},"u":{"a":{"df":0,"docs":{},"g":{"df":8,"docs":{"0":{"tf":1.0},"101":{"tf":1.0},"104":{"tf":1.0},"106":{"tf":1.0},"125":{"tf":1.0},"225":{"tf":1.0},"226":{"tf":1.0},"69":{"tf":1.0}}}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"g":{"df":7,"docs":{"204":{"tf":1.0},"205":{"tf":1.4142135623730951},"208":{"tf":1.0},"212":{"tf":1.7320508075688772},"213":{"tf":1.4142135623730951},"232":{"tf":1.0},"81":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":3,"docs":{"171":{"tf":1.0},"172":{"tf":1.0},"206":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"t":{"df":6,"docs":{"106":{"tf":1.0},"111":{"tf":1.0},"213":{"tf":1.0},"233":{"tf":1.0},"248":{"tf":1.4142135623730951},"88":{"tf":1.0}},"m":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":3,"docs":{"62":{"tf":1.0},"80":{"tf":1.0},"86":{"tf":1.0}}}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":7,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"11":{"tf":1.0},"138":{"tf":1.0},"158":{"tf":1.0},"179":{"tf":1.0},"88":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{"df":14,"docs":{"104":{"tf":1.0},"138":{"tf":1.0},"206":{"tf":1.0},"207":{"tf":1.0},"210":{"tf":1.0},"217":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951},"260":{"tf":1.4142135623730951},"261":{"tf":1.0},"266":{"tf":1.4142135623730951},"271":{"tf":1.0},"274":{"tf":1.4142135623730951},"40":{"tf":1.7320508075688772},"69":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":2,"docs":{"31":{"tf":1.0},"36":{"tf":1.0}}}},"df":0,"docs":{}}},"y":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":11,"docs":{"101":{"tf":1.0},"103":{"tf":1.0},"108":{"tf":1.4142135623730951},"109":{"tf":1.4142135623730951},"140":{"tf":1.0},"228":{"tf":1.0},"232":{"tf":1.4142135623730951},"44":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"90":{"tf":1.0}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"200":{"tf":1.0}}}}}},"z":{"df":0,"docs":{},"i":{"df":1,"docs":{"132":{"tf":1.0}}},"y":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"62":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"d":{"a":{"df":0,"docs":{},"p":{"df":1,"docs":{"274":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"a":{"d":{"df":5,"docs":{"157":{"tf":1.0},"172":{"tf":1.0},"196":{"tf":1.0},"256":{"tf":1.0},"40":{"tf":1.0}}},"df":0,"docs":{},"f":{"df":1,"docs":{"59":{"tf":1.0}}},"n":{"df":1,"docs":{"138":{"tf":1.0}}},"r":{"df":0,"docs":{},"n":{"df":5,"docs":{"171":{"tf":1.0},"172":{"tf":1.0},"236":{"tf":1.0},"7":{"tf":1.0},"75":{"tf":1.4142135623730951}}}},"v":{"df":3,"docs":{"108":{"tf":1.0},"159":{"tf":1.0},"171":{"tf":1.0}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":6,"docs":{"126":{"tf":1.0},"193":{"tf":1.0},"25":{"tf":1.0},"40":{"tf":1.0},"64":{"tf":1.0},"84":{"tf":1.0}}}},"g":{"a":{"c":{"df":0,"docs":{},"i":{"df":2,"docs":{"203":{"tf":1.0},"228":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":1,"docs":{"186":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"s":{"df":10,"docs":{"115":{"tf":1.0},"166":{"tf":1.0},"170":{"tf":1.0},"205":{"tf":1.0},"209":{"tf":1.0},"212":{"tf":1.0},"214":{"tf":1.0},"215":{"tf":1.0},"61":{"tf":1.0},"81":{"tf":1.0}},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"90":{"tf":1.0}}}}}},"t":{"'":{"df":4,"docs":{"227":{"tf":1.0},"228":{"tf":1.0},"230":{"tf":1.0},"62":{"tf":1.0}}},"df":3,"docs":{"225":{"tf":1.0},"283":{"tf":1.0},"81":{"tf":1.0}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":42,"docs":{"103":{"tf":2.8284271247461903},"105":{"tf":1.0},"123":{"tf":1.4142135623730951},"124":{"tf":1.4142135623730951},"18":{"tf":1.0},"200":{"tf":2.0},"219":{"tf":1.7320508075688772},"22":{"tf":1.0},"220":{"tf":1.4142135623730951},"225":{"tf":3.0},"226":{"tf":3.1622776601683795},"229":{"tf":2.6457513110645907},"23":{"tf":1.0},"230":{"tf":1.7320508075688772},"232":{"tf":1.4142135623730951},"233":{"tf":2.23606797749979},"234":{"tf":1.0},"235":{"tf":1.0},"236":{"tf":2.23606797749979},"237":{"tf":1.7320508075688772},"239":{"tf":1.0},"243":{"tf":1.0},"244":{"tf":1.0},"245":{"tf":1.0},"249":{"tf":1.0},"251":{"tf":1.4142135623730951},"252":{"tf":1.0},"254":{"tf":1.7320508075688772},"262":{"tf":1.0},"274":{"tf":1.0},"281":{"tf":1.4142135623730951},"283":{"tf":1.0},"37":{"tf":1.0},"41":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"6":{"tf":1.0},"64":{"tf":1.0},"70":{"tf":1.0},"73":{"tf":1.0},"84":{"tf":1.0},"98":{"tf":1.4142135623730951}}},"r":{"a":{"df":0,"docs":{},"g":{"df":2,"docs":{"123":{"tf":1.0},"79":{"tf":1.0}}}},"df":0,"docs":{}}}}},"i":{"b":{".":{"df":0,"docs":{},"r":{"df":4,"docs":{"103":{"tf":1.0},"105":{"tf":2.0},"107":{"tf":1.0},"60":{"tf":1.0}}}},"<":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{">":{"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{".":{"df":0,"docs":{},"k":{"df":0,"docs":{},"t":{"df":1,"docs":{"103":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}},"df":8,"docs":{"11":{"tf":1.0},"175":{"tf":1.0},"279":{"tf":1.0},"282":{"tf":1.0},"37":{"tf":1.0},"58":{"tf":1.0},"60":{"tf":1.4142135623730951},"69":{"tf":1.0}},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"a":{"df":0,"docs":{},"z":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"d":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":1,"docs":{"114":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"r":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":33,"docs":{"11":{"tf":1.0},"114":{"tf":1.0},"167":{"tf":1.0},"187":{"tf":1.0},"188":{"tf":1.0},"189":{"tf":1.7320508075688772},"190":{"tf":2.449489742783178},"191":{"tf":1.4142135623730951},"193":{"tf":1.4142135623730951},"194":{"tf":1.4142135623730951},"196":{"tf":2.449489742783178},"197":{"tf":2.23606797749979},"198":{"tf":1.0},"199":{"tf":1.4142135623730951},"200":{"tf":1.7320508075688772},"201":{"tf":1.4142135623730951},"220":{"tf":1.4142135623730951},"222":{"tf":1.4142135623730951},"225":{"tf":1.0},"228":{"tf":1.0},"251":{"tf":1.0},"274":{"tf":1.0},"278":{"tf":1.4142135623730951},"28":{"tf":1.0},"284":{"tf":1.0},"33":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.7320508075688772},"45":{"tf":1.0},"60":{"tf":1.0},"69":{"tf":1.0},"77":{"tf":1.4142135623730951},"90":{"tf":1.4142135623730951}}},"y":{"/":{"c":{"a":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{"df":0,"docs":{},"m":{"df":1,"docs":{"25":{"tf":1.0}}}}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"/":{"df":0,"docs":{},"x":{"c":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"e":{"/":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"d":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"/":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"25":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"s":{"/":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"280":{"tf":1.0}}},"df":0,"docs":{}}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"k":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":1,"docs":{"279":{"tf":1.0}}}}}}}}},"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":4,"docs":{"11":{"tf":1.4142135623730951},"12":{"tf":1.0},"14":{"tf":1.0},"279":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}},"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":4,"docs":{"2":{"tf":1.4142135623730951},"63":{"tf":1.0},"64":{"tf":1.7320508075688772},"7":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"y":{"c":{"df":0,"docs":{},"l":{"df":2,"docs":{"131":{"tf":1.7320508075688772},"133":{"tf":1.0}}}},"df":0,"docs":{}}},"df":1,"docs":{"256":{"tf":1.0}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"130":{"tf":1.0}}}}}}},"k":{"df":0,"docs":{},"e":{"df":1,"docs":{"104":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"d":{"df":3,"docs":{"208":{"tf":1.0},"256":{"tf":1.0},"258":{"tf":1.0}}},"df":0,"docs":{}}}}}}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":17,"docs":{"134":{"tf":1.0},"140":{"tf":1.0},"17":{"tf":1.0},"187":{"tf":1.0},"202":{"tf":1.0},"206":{"tf":1.0},"207":{"tf":1.7320508075688772},"208":{"tf":2.23606797749979},"209":{"tf":1.7320508075688772},"210":{"tf":1.4142135623730951},"213":{"tf":2.23606797749979},"214":{"tf":1.4142135623730951},"217":{"tf":1.0},"218":{"tf":1.4142135623730951},"225":{"tf":1.0},"242":{"tf":1.0},"7":{"tf":1.0}}}}},"n":{"df":0,"docs":{},"e":{"df":11,"docs":{"115":{"tf":1.0},"117":{"tf":1.4142135623730951},"126":{"tf":1.0},"165":{"tf":1.0},"17":{"tf":1.0},"172":{"tf":1.0},"18":{"tf":1.0},"200":{"tf":1.4142135623730951},"237":{"tf":1.0},"29":{"tf":1.4142135623730951},"34":{"tf":1.4142135623730951}}},"k":{"a":{"df":0,"docs":{},"g":{"df":2,"docs":{"225":{"tf":1.0},"282":{"tf":1.0}}}},"df":14,"docs":{"11":{"tf":1.0},"119":{"tf":1.0},"147":{"tf":1.0},"167":{"tf":1.0},"185":{"tf":1.0},"218":{"tf":1.0},"263":{"tf":1.0},"271":{"tf":1.7320508075688772},"278":{"tf":1.0},"282":{"tf":2.0},"37":{"tf":1.0},"40":{"tf":1.0},"58":{"tf":1.0},"60":{"tf":1.0}}},"t":{"df":2,"docs":{"274":{"tf":1.0},"92":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"7":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"x":{"df":5,"docs":{"11":{"tf":1.4142135623730951},"13":{"tf":1.0},"17":{"tf":1.4142135623730951},"280":{"tf":1.4142135623730951},"37":{"tf":1.7320508075688772}}}}},"s":{"df":0,"docs":{},"t":{"df":33,"docs":{"106":{"tf":1.7320508075688772},"109":{"tf":1.0},"134":{"tf":1.0},"137":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0},"17":{"tf":1.0},"188":{"tf":1.0},"200":{"tf":1.0},"203":{"tf":1.0},"204":{"tf":1.0},"21":{"tf":1.0},"220":{"tf":1.0},"233":{"tf":1.4142135623730951},"236":{"tf":1.0},"242":{"tf":1.7320508075688772},"251":{"tf":1.0},"259":{"tf":1.0},"264":{"tf":1.0},"266":{"tf":1.4142135623730951},"274":{"tf":1.0},"28":{"tf":1.0},"31":{"tf":1.0},"33":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.4142135623730951},"6":{"tf":1.0},"61":{"tf":1.0},"68":{"tf":1.0},"69":{"tf":1.0},"73":{"tf":2.23606797749979}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"62":{"tf":1.0},"89":{"tf":1.0}}}},"t":{"df":0,"docs":{},"l":{"df":9,"docs":{"104":{"tf":1.0},"170":{"tf":1.4142135623730951},"200":{"tf":1.0},"221":{"tf":1.0},"237":{"tf":1.0},"252":{"tf":1.0},"26":{"tf":1.0},"37":{"tf":1.0},"6":{"tf":1.0}}}}},"v":{"df":0,"docs":{},"e":{"df":11,"docs":{"130":{"tf":1.0},"229":{"tf":1.0},"231":{"tf":1.0},"252":{"tf":1.0},"43":{"tf":1.0},"46":{"tf":1.4142135623730951},"48":{"tf":1.0},"51":{"tf":1.0},"59":{"tf":1.0},"69":{"tf":1.0},"75":{"tf":1.0}}}}},"l":{"d":{"b":{"df":1,"docs":{"117":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":1,"docs":{"37":{"tf":1.0}}}}},"n":{"df":1,"docs":{"11":{"tf":1.0}}},"o":{"a":{"d":{"df":7,"docs":{"114":{"tf":1.0},"184":{"tf":1.4142135623730951},"22":{"tf":1.0},"222":{"tf":1.4142135623730951},"224":{"tf":1.0},"226":{"tf":1.0},"229":{"tf":1.0}}},"df":0,"docs":{}},"c":{"a":{"df":0,"docs":{},"l":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":4,"docs":{"17":{"tf":1.0},"18":{"tf":1.4142135623730951},"20":{"tf":1.0},"22":{"tf":1.4142135623730951}}}}}}}}}}},"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"/":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"g":{"df":2,"docs":{"248":{"tf":1.4142135623730951},"249":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":1,"docs":{"249":{"tf":1.0}}}}}}}}},"df":40,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"11":{"tf":1.4142135623730951},"118":{"tf":1.0},"124":{"tf":1.4142135623730951},"15":{"tf":1.7320508075688772},"16":{"tf":1.4142135623730951},"18":{"tf":2.0},"194":{"tf":1.0},"198":{"tf":1.0},"22":{"tf":2.0},"224":{"tf":1.0},"23":{"tf":3.0},"24":{"tf":1.7320508075688772},"242":{"tf":1.7320508075688772},"245":{"tf":1.7320508075688772},"246":{"tf":1.4142135623730951},"248":{"tf":1.4142135623730951},"249":{"tf":2.6457513110645907},"25":{"tf":1.7320508075688772},"250":{"tf":2.23606797749979},"257":{"tf":1.0},"26":{"tf":1.7320508075688772},"28":{"tf":1.0},"29":{"tf":1.4142135623730951},"30":{"tf":1.0},"31":{"tf":2.23606797749979},"32":{"tf":2.8284271247461903},"33":{"tf":1.0},"34":{"tf":1.7320508075688772},"35":{"tf":1.0},"36":{"tf":2.449489742783178},"37":{"tf":2.449489742783178},"45":{"tf":1.0},"49":{"tf":1.4142135623730951},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"67":{"tf":1.0},"99":{"tf":1.0}},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"190":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{">":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"/":{"c":{"/":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"{":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"}":{"/":{".":{"df":0,"docs":{},"m":{"2":{"/":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"<":{"/":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"13":{"tf":1.0}}}}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}}},"t":{"df":10,"docs":{"106":{"tf":1.0},"12":{"tf":1.0},"14":{"tf":1.0},"158":{"tf":1.0},"201":{"tf":1.0},"22":{"tf":1.0},"224":{"tf":1.0},"267":{"tf":1.7320508075688772},"279":{"tf":1.7320508075688772},"82":{"tf":1.0}}}},"df":0,"docs":{},"k":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"x":{"df":2,"docs":{"228":{"tf":1.4142135623730951},"234":{"tf":1.0}}}}},"df":3,"docs":{"142":{"tf":1.0},"205":{"tf":1.4142135623730951},"64":{"tf":1.0}}}},"df":0,"docs":{},"g":{"c":{"a":{"df":0,"docs":{},"t":{"df":2,"docs":{"126":{"tf":2.449489742783178},"37":{"tf":1.0}}}},"df":0,"docs":{}},"df":10,"docs":{"116":{"tf":1.0},"125":{"tf":2.23606797749979},"126":{"tf":2.449489742783178},"127":{"tf":1.0},"134":{"tf":1.4142135623730951},"242":{"tf":1.0},"268":{"tf":1.7320508075688772},"280":{"tf":1.0},"37":{"tf":1.0},"82":{"tf":1.0}},"i":{"c":{"df":19,"docs":{"140":{"tf":1.0},"141":{"tf":1.0},"145":{"tf":2.449489742783178},"146":{"tf":1.0},"149":{"tf":1.0},"154":{"tf":1.0},"158":{"tf":1.0},"208":{"tf":1.0},"244":{"tf":1.0},"254":{"tf":1.0},"256":{"tf":1.4142135623730951},"258":{"tf":1.0},"282":{"tf":1.4142135623730951},"73":{"tf":1.0},"78":{"tf":1.0},"79":{"tf":1.0},"81":{"tf":1.0},"82":{"tf":1.4142135623730951},"84":{"tf":1.4142135623730951}}},"df":0,"docs":{},"n":{"df":15,"docs":{"112":{"tf":1.0},"134":{"tf":1.0},"139":{"tf":1.0},"140":{"tf":1.7320508075688772},"141":{"tf":1.4142135623730951},"145":{"tf":2.6457513110645907},"146":{"tf":2.23606797749979},"147":{"tf":1.0},"221":{"tf":1.0},"228":{"tf":1.4142135623730951},"232":{"tf":1.0},"233":{"tf":2.0},"244":{"tf":1.0},"264":{"tf":1.4142135623730951},"78":{"tf":1.0}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"132":{"tf":1.0}}}},"df":0,"docs":{}}}}}}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":1,"docs":{"126":{"tf":1.0}}}}}}}},"n":{"df":0,"docs":{},"g":{"df":17,"docs":{"104":{"tf":1.0},"105":{"tf":1.0},"115":{"tf":1.4142135623730951},"132":{"tf":1.0},"140":{"tf":1.0},"164":{"tf":1.0},"170":{"tf":1.0},"204":{"tf":1.0},"205":{"tf":1.0},"212":{"tf":1.0},"215":{"tf":1.0},"22":{"tf":1.0},"229":{"tf":1.0},"25":{"tf":1.0},"258":{"tf":1.0},"264":{"tf":1.0},"59":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":7,"docs":{"128":{"tf":1.0},"140":{"tf":1.0},"180":{"tf":1.0},"205":{"tf":1.0},"206":{"tf":1.7320508075688772},"226":{"tf":1.0},"86":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"k":{"df":19,"docs":{"102":{"tf":1.4142135623730951},"103":{"tf":1.0},"158":{"tf":1.0},"215":{"tf":1.0},"216":{"tf":1.0},"22":{"tf":1.0},"221":{"tf":1.0},"224":{"tf":1.4142135623730951},"280":{"tf":1.0},"283":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"6":{"tf":1.0},"62":{"tf":1.0},"73":{"tf":1.7320508075688772},"75":{"tf":1.4142135623730951},"82":{"tf":1.0},"86":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"203":{"tf":1.0},"218":{"tf":1.0}}}}},"p":{"df":5,"docs":{"190":{"tf":1.0},"193":{"tf":1.0},"197":{"tf":1.4142135623730951},"249":{"tf":1.0},"82":{"tf":1.0}}},"s":{"df":1,"docs":{"240":{"tf":1.0}}}},"s":{"df":0,"docs":{},"e":{"df":5,"docs":{"204":{"tf":1.0},"206":{"tf":1.0},"208":{"tf":1.0},"210":{"tf":1.4142135623730951},"213":{"tf":1.4142135623730951}}},"s":{"df":2,"docs":{"157":{"tf":1.0},"209":{"tf":1.0}}},"t":{"df":2,"docs":{"158":{"tf":1.0},"231":{"tf":1.0}}}},"t":{"df":4,"docs":{"115":{"tf":1.0},"116":{"tf":1.0},"212":{"tf":1.4142135623730951},"240":{"tf":1.0}}},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"a":{"c":{"6":{"4":{"df":1,"docs":{"202":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"w":{"df":16,"docs":{"103":{"tf":2.0},"209":{"tf":1.4142135623730951},"220":{"tf":1.0},"225":{"tf":1.0},"226":{"tf":2.23606797749979},"229":{"tf":1.7320508075688772},"230":{"tf":1.4142135623730951},"232":{"tf":1.0},"233":{"tf":2.0},"234":{"tf":1.0},"235":{"tf":1.0},"236":{"tf":1.0},"237":{"tf":1.4142135623730951},"249":{"tf":1.0},"281":{"tf":1.0},"37":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"c":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"c":{"a":{"df":0,"docs":{},"s":{"df":2,"docs":{"96":{"tf":1.0},"99":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":3,"docs":{"209":{"tf":1.0},"225":{"tf":1.7320508075688772},"99":{"tf":1.0}}}}}},"u":{"c":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"258":{"tf":1.0}}},"df":0,"docs":{}},"k":{"df":1,"docs":{"90":{"tf":1.0}}}},"df":0,"docs":{}}},"m":{"1":{"df":7,"docs":{"161":{"tf":1.0},"162":{"tf":1.0},"163":{"tf":1.0},"169":{"tf":1.0},"17":{"tf":1.0},"170":{"tf":1.0},"175":{"tf":1.0}}},"2":{"df":1,"docs":{"13":{"tf":1.4142135623730951}}},"a":{"c":{"df":3,"docs":{"280":{"tf":1.0},"37":{"tf":1.0},"73":{"tf":1.0}},"h":{"df":2,"docs":{"122":{"tf":1.0},"124":{"tf":1.4142135623730951}},"i":{"df":0,"docs":{},"n":{"df":10,"docs":{"161":{"tf":1.0},"162":{"tf":1.0},"17":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"175":{"tf":1.0},"223":{"tf":1.4142135623730951},"280":{"tf":1.4142135623730951},"45":{"tf":1.0}}}}},"o":{"df":2,"docs":{"11":{"tf":1.0},"280":{"tf":1.7320508075688772}}},"r":{"df":0,"docs":{},"o":{"df":3,"docs":{"200":{"tf":1.0},"43":{"tf":1.0},"93":{"tf":1.0}}}}},"d":{"df":0,"docs":{},"e":{"df":15,"docs":{"121":{"tf":1.0},"122":{"tf":1.0},"136":{"tf":1.0},"138":{"tf":1.0},"167":{"tf":1.0},"192":{"tf":1.0},"193":{"tf":1.0},"194":{"tf":1.0},"200":{"tf":1.0},"21":{"tf":1.0},"226":{"tf":1.0},"227":{"tf":1.0},"231":{"tf":1.0},"233":{"tf":1.0},"82":{"tf":1.0}}},"r":{"df":3,"docs":{"134":{"tf":1.4142135623730951},"137":{"tf":1.0},"138":{"tf":2.0}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"228":{"tf":1.0}}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"274":{"tf":1.0}}},"n":{"df":24,"docs":{"103":{"tf":2.0},"119":{"tf":1.0},"121":{"tf":1.0},"126":{"tf":1.0},"170":{"tf":1.0},"182":{"tf":1.0},"193":{"tf":1.0},"194":{"tf":1.0},"201":{"tf":1.0},"224":{"tf":1.0},"246":{"tf":1.0},"251":{"tf":1.0},"254":{"tf":1.0},"260":{"tf":1.0},"268":{"tf":1.7320508075688772},"269":{"tf":1.0},"270":{"tf":1.0},"274":{"tf":2.6457513110645907},"40":{"tf":1.7320508075688772},"51":{"tf":1.0},"64":{"tf":1.0},"7":{"tf":1.0},"70":{"tf":1.0},"82":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":4,"docs":{"14":{"tf":1.0},"180":{"tf":1.0},"253":{"tf":1.4142135623730951},"278":{"tf":1.0}}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":12,"docs":{"146":{"tf":1.0},"162":{"tf":1.0},"167":{"tf":1.0},"169":{"tf":1.0},"170":{"tf":1.4142135623730951},"171":{"tf":1.0},"172":{"tf":1.0},"196":{"tf":1.0},"203":{"tf":1.0},"260":{"tf":1.0},"263":{"tf":1.0},"274":{"tf":1.0}}}},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"220":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"64":{"tf":1.0}}},"df":5,"docs":{"138":{"tf":1.0},"164":{"tf":1.0},"172":{"tf":1.0},"208":{"tf":1.0},"274":{"tf":1.0}}}}}}},"j":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":8,"docs":{"116":{"tf":1.0},"208":{"tf":1.0},"262":{"tf":1.0},"269":{"tf":1.0},"270":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"58":{"tf":1.0}}}}},"k":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"g":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"253":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":64,"docs":{"102":{"tf":1.0},"104":{"tf":1.0},"105":{"tf":1.0},"106":{"tf":1.4142135623730951},"107":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"11":{"tf":1.7320508075688772},"111":{"tf":1.0},"116":{"tf":1.4142135623730951},"117":{"tf":1.4142135623730951},"118":{"tf":1.0},"120":{"tf":1.0},"122":{"tf":1.4142135623730951},"124":{"tf":1.7320508075688772},"126":{"tf":1.0},"129":{"tf":1.0},"14":{"tf":1.0},"145":{"tf":1.0},"146":{"tf":1.0},"149":{"tf":1.0},"163":{"tf":1.0},"165":{"tf":1.0},"169":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"188":{"tf":1.0},"197":{"tf":1.0},"198":{"tf":1.0},"200":{"tf":1.0},"21":{"tf":1.0},"214":{"tf":1.0},"226":{"tf":1.0},"227":{"tf":1.0},"241":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951},"250":{"tf":1.0},"26":{"tf":1.0},"263":{"tf":1.0},"264":{"tf":1.0},"270":{"tf":1.0},"272":{"tf":1.0},"274":{"tf":1.0},"278":{"tf":1.0},"280":{"tf":1.0},"282":{"tf":1.0},"32":{"tf":1.0},"36":{"tf":1.0},"39":{"tf":1.4142135623730951},"40":{"tf":1.0},"43":{"tf":1.7320508075688772},"46":{"tf":1.0},"51":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0},"7":{"tf":1.0},"70":{"tf":1.0},"72":{"tf":1.0},"73":{"tf":1.4142135623730951},"75":{"tf":1.0},"79":{"tf":1.0}}}},"l":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":1,"docs":{"274":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"n":{"a":{"df":0,"docs":{},"g":{"df":42,"docs":{"115":{"tf":1.0},"117":{"tf":1.0},"132":{"tf":1.7320508075688772},"140":{"tf":1.0},"146":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"174":{"tf":1.4142135623730951},"185":{"tf":1.0},"187":{"tf":1.0},"189":{"tf":1.0},"197":{"tf":1.0},"201":{"tf":1.0},"219":{"tf":1.7320508075688772},"220":{"tf":1.4142135623730951},"221":{"tf":1.0},"225":{"tf":2.8284271247461903},"226":{"tf":3.0},"228":{"tf":2.23606797749979},"229":{"tf":2.0},"23":{"tf":1.7320508075688772},"232":{"tf":1.7320508075688772},"233":{"tf":1.4142135623730951},"236":{"tf":1.0},"239":{"tf":1.0},"241":{"tf":1.0},"242":{"tf":1.4142135623730951},"244":{"tf":1.0},"245":{"tf":1.4142135623730951},"253":{"tf":1.0},"268":{"tf":1.4142135623730951},"269":{"tf":1.0},"274":{"tf":1.4142135623730951},"32":{"tf":1.7320508075688772},"48":{"tf":1.0},"63":{"tf":1.0},"69":{"tf":1.0},"7":{"tf":1.0},"73":{"tf":1.7320508075688772},"78":{"tf":1.0},"79":{"tf":1.0},"81":{"tf":1.4142135623730951}}}},"df":3,"docs":{"201":{"tf":1.4142135623730951},"236":{"tf":1.4142135623730951},"242":{"tf":1.7320508075688772}},"i":{"df":19,"docs":{"106":{"tf":1.4142135623730951},"109":{"tf":1.0},"126":{"tf":1.0},"152":{"tf":1.0},"154":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0},"175":{"tf":1.0},"204":{"tf":1.4142135623730951},"213":{"tf":1.0},"214":{"tf":1.0},"228":{"tf":1.0},"257":{"tf":1.0},"283":{"tf":1.0},"59":{"tf":1.0},"62":{"tf":1.0},"64":{"tf":1.0},"75":{"tf":1.0},"79":{"tf":1.4142135623730951}},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"70":{"tf":1.0}}}}}}},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"126":{"tf":1.0}}}}},"u":{"a":{"df":0,"docs":{},"l":{"df":18,"docs":{"101":{"tf":1.0},"111":{"tf":1.0},"119":{"tf":1.0},"121":{"tf":1.0},"17":{"tf":1.0},"20":{"tf":1.4142135623730951},"22":{"tf":1.7320508075688772},"25":{"tf":1.4142135623730951},"27":{"tf":1.7320508075688772},"274":{"tf":1.4142135623730951},"31":{"tf":1.0},"36":{"tf":1.0},"39":{"tf":1.0},"49":{"tf":1.4142135623730951},"52":{"tf":1.0},"59":{"tf":1.0},"82":{"tf":1.0},"84":{"tf":1.0}}}},"df":0,"docs":{}}},"p":{"df":9,"docs":{"106":{"tf":1.0},"11":{"tf":1.4142135623730951},"116":{"tf":1.0},"12":{"tf":1.0},"232":{"tf":1.0},"251":{"tf":1.0},"252":{"tf":1.4142135623730951},"28":{"tf":1.0},"33":{"tf":1.0}}},"r":{"df":0,"docs":{},"k":{"d":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":0,"docs":{},"n":{"df":4,"docs":{"134":{"tf":1.0},"135":{"tf":1.0},"137":{"tf":1.0},"284":{"tf":1.0}}}}}},"df":1,"docs":{"171":{"tf":1.0}},"h":{"df":1,"docs":{"276":{"tf":1.0}}}},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"197":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":2,"docs":{"242":{"tf":1.0},"81":{"tf":1.0}}}}}},"t":{"c":{"df":0,"docs":{},"h":{"df":12,"docs":{"104":{"tf":1.0},"105":{"tf":1.4142135623730951},"124":{"tf":1.0},"165":{"tf":1.0},"20":{"tf":1.0},"201":{"tf":1.0},"213":{"tf":1.0},"22":{"tf":1.4142135623730951},"242":{"tf":1.0},"268":{"tf":1.0},"40":{"tf":1.0},"87":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"174":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"df":2,"docs":{"122":{"tf":1.4142135623730951},"3":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"82":{"tf":1.0}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{".":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{".":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":1,"docs":{"274":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":7,"docs":{"11":{"tf":1.0},"13":{"tf":2.449489742783178},"16":{"tf":1.0},"18":{"tf":1.0},"22":{"tf":1.0},"271":{"tf":2.23606797749979},"274":{"tf":1.0}},"l":{"df":0,"docs":{},"o":{"c":{"df":1,"docs":{"20":{"tf":1.0}}},"df":0,"docs":{}}}}}},"y":{"b":{"df":1,"docs":{"51":{"tf":1.0}}},"df":0,"docs":{}}},"d":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":2,"docs":{"284":{"tf":1.0},"286":{"tf":2.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"n":{"df":25,"docs":{"118":{"tf":1.0},"128":{"tf":1.0},"13":{"tf":1.0},"140":{"tf":1.0},"175":{"tf":1.0},"190":{"tf":1.0},"201":{"tf":1.0},"204":{"tf":1.7320508075688772},"205":{"tf":1.0},"207":{"tf":1.7320508075688772},"208":{"tf":1.0},"209":{"tf":1.4142135623730951},"213":{"tf":1.0},"22":{"tf":1.0},"224":{"tf":1.0},"232":{"tf":1.0},"242":{"tf":1.0},"250":{"tf":1.0},"263":{"tf":1.0},"45":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"82":{"tf":1.4142135623730951},"86":{"tf":1.4142135623730951},"89":{"tf":1.4142135623730951}}},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":4,"docs":{"203":{"tf":1.0},"274":{"tf":1.4142135623730951},"59":{"tf":1.0},"64":{"tf":1.0}}}}}},"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"n":{"df":3,"docs":{"226":{"tf":1.0},"231":{"tf":1.0},"274":{"tf":1.0}}}},"df":0,"docs":{}}},"d":{"df":0,"docs":{},"i":{"a":{"=":{"\"":{"(":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":1,"docs":{"283":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"n":{"df":1,"docs":{"204":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":2,"docs":{"111":{"tf":1.0},"220":{"tf":1.0}}}},"g":{"a":{"df":0,"docs":{},"z":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"d":{"'":{")":{".":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"}":{"/":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"j":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"s":{"/":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"k":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":1,"docs":{"224":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"k":{"df":0,"docs":{},"s":{"[":{"\"":{"c":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"224":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":16,"docs":{"105":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"197":{"tf":1.0},"219":{"tf":1.4142135623730951},"220":{"tf":1.7320508075688772},"221":{"tf":2.23606797749979},"222":{"tf":2.23606797749979},"223":{"tf":1.7320508075688772},"224":{"tf":1.7320508075688772},"251":{"tf":1.0},"252":{"tf":1.4142135623730951},"46":{"tf":1.0},"69":{"tf":1.0},"70":{"tf":1.0},"73":{"tf":2.0}},"s":{"/":{"df":0,"docs":{},"f":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"/":{"c":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":1,"docs":{"69":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{},"s":{"df":0,"docs":{},"r":{"c":{"/":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"b":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"69":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"i":{"df":0,"docs":{},"o":{"df":9,"docs":{"109":{"tf":1.4142135623730951},"14":{"tf":1.4142135623730951},"252":{"tf":1.0},"28":{"tf":1.0},"29":{"tf":1.0},"33":{"tf":1.0},"46":{"tf":1.0},"69":{"tf":1.4142135623730951},"73":{"tf":2.449489742783178}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"m":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":3,"docs":{"3":{"tf":1.0},"69":{"tf":1.4142135623730951},"8":{"tf":1.0}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":3,"docs":{"116":{"tf":1.7320508075688772},"132":{"tf":1.0},"264":{"tf":1.0}}}}},"s":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"51":{"tf":1.0}}}},"df":0,"docs":{}}},"n":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"105":{"tf":1.0}}},"t":{"df":1,"docs":{"60":{"tf":1.0}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":3,"docs":{"204":{"tf":1.0},"210":{"tf":1.0},"242":{"tf":1.0}}}}}},"u":{"_":{"_":{"_":{"_":{"_":{"_":{"_":{"_":{"df":1,"docs":{"62":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":3,"docs":{"12":{"tf":1.0},"126":{"tf":1.0},"62":{"tf":1.0}}}},"r":{"df":0,"docs":{},"g":{"df":9,"docs":{"118":{"tf":1.0},"119":{"tf":2.23606797749979},"158":{"tf":1.0},"249":{"tf":1.0},"250":{"tf":1.0},"268":{"tf":1.4142135623730951},"270":{"tf":1.0},"40":{"tf":1.0},"7":{"tf":1.4142135623730951}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":1,"docs":{"193":{"tf":1.0}}}}},"m":{"a":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"286":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"s":{"a":{"df":0,"docs":{},"g":{"df":4,"docs":{"104":{"tf":1.0},"105":{"tf":1.0},"106":{"tf":2.449489742783178},"126":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"a":{"/":{"df":0,"docs":{},"g":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"b":{"df":4,"docs":{"226":{"tf":1.0},"228":{"tf":1.0},"233":{"tf":1.0},"245":{"tf":1.0}}},"df":0,"docs":{}}}}},"d":{"a":{"df":0,"docs":{},"t":{"a":{".":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"a":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"d":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"a":{"df":1,"docs":{"283":{"tf":1.0}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":10,"docs":{"245":{"tf":1.0},"248":{"tf":1.0},"249":{"tf":1.0},"252":{"tf":1.0},"254":{"tf":1.0},"271":{"tf":1.4142135623730951},"70":{"tf":1.0},"80":{"tf":1.0},"81":{"tf":1.0},"87":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":4,"docs":{"231":{"tf":1.0},"261":{"tf":1.0},"80":{"tf":1.0},"84":{"tf":1.4142135623730951}}},"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"d":{"df":6,"docs":{"131":{"tf":1.7320508075688772},"133":{"tf":1.0},"16":{"tf":1.0},"245":{"tf":1.7320508075688772},"268":{"tf":1.0},"283":{"tf":1.0}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"i":{"c":{"df":5,"docs":{"184":{"tf":1.0},"219":{"tf":1.0},"253":{"tf":1.0},"259":{"tf":1.0},"283":{"tf":1.0}}},"df":0,"docs":{}}}}},"h":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"186":{"tf":1.0},"202":{"tf":1.0}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"i":{"b":{"df":1,"docs":{"206":{"tf":1.0}}},"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":1,"docs":{"137":{"tf":1.0}}}}},"df":0,"docs":{}}},"d":{"d":{"df":0,"docs":{},"l":{"df":2,"docs":{"156":{"tf":1.0},"201":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":1,"docs":{"165":{"tf":1.0}},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":21,"docs":{"134":{"tf":1.0},"145":{"tf":1.0},"154":{"tf":1.4142135623730951},"167":{"tf":1.4142135623730951},"169":{"tf":1.0},"202":{"tf":1.0},"203":{"tf":2.23606797749979},"204":{"tf":3.4641016151377544},"205":{"tf":1.7320508075688772},"206":{"tf":1.0},"207":{"tf":2.449489742783178},"208":{"tf":1.7320508075688772},"209":{"tf":1.4142135623730951},"212":{"tf":2.449489742783178},"213":{"tf":1.4142135623730951},"214":{"tf":1.7320508075688772},"216":{"tf":1.0},"217":{"tf":1.7320508075688772},"218":{"tf":1.7320508075688772},"257":{"tf":1.0},"258":{"tf":1.0}}}},"df":0,"docs":{}}},"h":{"a":{"df":0,"docs":{},"i":{"df":1,"docs":{"274":{"tf":1.0}}}},"df":0,"docs":{}},"l":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"105":{"tf":1.0}}}},"df":0,"docs":{}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"204":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"n":{"d":{"df":2,"docs":{"242":{"tf":1.0},"63":{"tf":1.0}}},"df":0,"docs":{},"e":{"df":1,"docs":{"37":{"tf":1.0}}},"i":{"df":0,"docs":{},"m":{"df":5,"docs":{"118":{"tf":1.0},"163":{"tf":1.0},"199":{"tf":1.0},"226":{"tf":1.0},"258":{"tf":1.0}},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"263":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"262":{"tf":1.0}}}},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"104":{"tf":1.0},"20":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":2,"docs":{"248":{"tf":1.0},"249":{"tf":1.4142135623730951}}}}}},"s":{"c":{"df":1,"docs":{"240":{"tf":1.0}}},"df":0,"docs":{},"i":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":1,"docs":{"157":{"tf":1.0}},"i":{"df":1,"docs":{"158":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"m":{"a":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"121":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"s":{"df":2,"docs":{"109":{"tf":1.0},"278":{"tf":1.0}}},"t":{"a":{"df":0,"docs":{},"k":{"df":1,"docs":{"79":{"tf":1.0}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"/":{"c":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":1,"docs":{"116":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":1,"docs":{"210":{"tf":1.0}}}}}},"m":{"df":1,"docs":{"268":{"tf":1.0}}},"o":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":14,"docs":{"119":{"tf":1.0},"134":{"tf":1.0},"186":{"tf":1.4142135623730951},"187":{"tf":1.7320508075688772},"188":{"tf":1.0},"190":{"tf":1.4142135623730951},"192":{"tf":1.0},"194":{"tf":1.0},"197":{"tf":1.0},"200":{"tf":2.8284271247461903},"201":{"tf":1.4142135623730951},"232":{"tf":1.0},"274":{"tf":1.0},"62":{"tf":1.0}},"e":{"'":{"df":1,"docs":{"178":{"tf":1.0}}},"_":{"_":{"_":{"_":{"_":{"_":{"df":1,"docs":{"62":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"c":{"df":0,"docs":{},"k":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"51":{"tf":1.0}}}},"df":0,"docs":{}},"df":2,"docs":{"43":{"tf":1.7320508075688772},"51":{"tf":1.0}},"i":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":1,"docs":{"43":{"tf":1.0}}}}},"df":0,"docs":{}}}},"d":{"df":3,"docs":{"60":{"tf":1.0},"61":{"tf":2.0},"62":{"tf":1.0}},"e":{"df":2,"docs":{"236":{"tf":1.0},"79":{"tf":1.0}},"l":{"df":8,"docs":{"105":{"tf":1.0},"145":{"tf":1.0},"184":{"tf":1.0},"194":{"tf":1.0},"197":{"tf":1.7320508075688772},"201":{"tf":1.4142135623730951},"274":{"tf":1.0},"51":{"tf":1.0}}},"r":{"df":0,"docs":{},"n":{"df":2,"docs":{"161":{"tf":1.0},"169":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"f":{"df":1,"docs":{"256":{"tf":1.0}},"i":{"df":12,"docs":{"183":{"tf":1.0},"196":{"tf":1.0},"197":{"tf":1.0},"201":{"tf":1.4142135623730951},"249":{"tf":1.0},"250":{"tf":1.0},"256":{"tf":1.0},"274":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.7320508075688772},"7":{"tf":1.4142135623730951}}}}},"u":{"df":0,"docs":{},"l":{"df":13,"docs":{"103":{"tf":1.0},"105":{"tf":1.0},"175":{"tf":1.0},"222":{"tf":1.0},"251":{"tf":1.0},"252":{"tf":1.4142135623730951},"28":{"tf":1.0},"282":{"tf":1.4142135623730951},"33":{"tf":1.0},"61":{"tf":1.7320508075688772},"62":{"tf":1.0},"83":{"tf":1.4142135623730951},"93":{"tf":1.0}},"e":{".":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"p":{"df":1,"docs":{"252":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},":":{"\"":{"df":0,"docs":{},"j":{"df":0,"docs":{},"n":{"a":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"58":{"tf":1.0}}}}}},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"152":{"tf":1.0}}}}}},"o":{"df":1,"docs":{"18":{"tf":1.0}}},"t":{"df":0,"docs":{},"h":{"df":2,"docs":{"213":{"tf":1.4142135623730951},"278":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"e":{"_":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"43":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":39,"docs":{"10":{"tf":1.0},"101":{"tf":1.0},"105":{"tf":1.0},"106":{"tf":1.4142135623730951},"126":{"tf":1.4142135623730951},"134":{"tf":1.0},"161":{"tf":1.0},"166":{"tf":1.0},"169":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"177":{"tf":1.0},"194":{"tf":1.0},"200":{"tf":1.0},"205":{"tf":1.7320508075688772},"209":{"tf":1.4142135623730951},"210":{"tf":1.4142135623730951},"213":{"tf":1.7320508075688772},"225":{"tf":1.0},"227":{"tf":1.0},"229":{"tf":1.0},"23":{"tf":1.0},"231":{"tf":1.0},"232":{"tf":1.4142135623730951},"242":{"tf":1.0},"274":{"tf":1.4142135623730951},"32":{"tf":1.4142135623730951},"36":{"tf":1.0},"50":{"tf":1.0},"51":{"tf":1.0},"6":{"tf":1.7320508075688772},"60":{"tf":1.4142135623730951},"61":{"tf":1.0},"7":{"tf":1.4142135623730951},"73":{"tf":1.0},"78":{"tf":1.4142135623730951},"93":{"tf":1.0},"96":{"tf":1.0},"99":{"tf":1.0}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":2,"docs":{"224":{"tf":1.0},"282":{"tf":1.0}}}}}},"v":{"df":0,"docs":{},"e":{"df":20,"docs":{"103":{"tf":1.0},"105":{"tf":1.0},"11":{"tf":1.4142135623730951},"115":{"tf":1.0},"151":{"tf":1.0},"158":{"tf":1.4142135623730951},"166":{"tf":1.4142135623730951},"167":{"tf":1.4142135623730951},"170":{"tf":1.4142135623730951},"174":{"tf":1.0},"190":{"tf":1.0},"197":{"tf":1.0},"198":{"tf":1.0},"200":{"tf":1.7320508075688772},"218":{"tf":1.0},"225":{"tf":1.4142135623730951},"228":{"tf":1.0},"230":{"tf":1.0},"273":{"tf":1.0},"68":{"tf":1.0}}}},"z":{"_":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"62":{"tf":2.0}},"s":{"\"":{")":{".":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"w":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":1,"docs":{"62":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":4,"docs":{"121":{"tf":1.0},"122":{"tf":1.4142135623730951},"276":{"tf":1.4142135623730951},"277":{"tf":1.0}},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"'":{"df":2,"docs":{"187":{"tf":1.0},"253":{"tf":1.0}}},".":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"$":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"112":{"tf":1.0}}}},"df":0,"docs":{}}},"<":{"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"_":{"c":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"70":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"a":{"c":{"df":1,"docs":{"100":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{".":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"222":{"tf":1.0}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":1,"docs":{"222":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":3,"docs":{"163":{"tf":1.4142135623730951},"166":{"tf":1.0},"31":{"tf":1.0}}},"df":0,"docs":{}}}}}}}}},"df":22,"docs":{"119":{"tf":1.0},"120":{"tf":1.7320508075688772},"122":{"tf":1.4142135623730951},"123":{"tf":2.0},"124":{"tf":2.0},"161":{"tf":1.0},"162":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"2":{"tf":1.0},"201":{"tf":2.0},"258":{"tf":1.0},"260":{"tf":1.4142135623730951},"261":{"tf":1.4142135623730951},"262":{"tf":1.4142135623730951},"263":{"tf":1.0},"274":{"tf":1.7320508075688772},"278":{"tf":1.0},"3":{"tf":1.0},"45":{"tf":1.0},"8":{"tf":1.0}},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"73":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"h":{"df":1,"docs":{"252":{"tf":1.0}}},"x":{"c":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{".":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":2,"docs":{"28":{"tf":1.0},"29":{"tf":1.4142135623730951}}}}}},"df":1,"docs":{"29":{"tf":1.4142135623730951}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"73":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"73":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}}}}}}}}},"’":{"df":3,"docs":{"187":{"tf":1.0},"188":{"tf":1.0},"190":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"p":{"df":0,"docs":{},"l":{"df":3,"docs":{"2":{"tf":1.0},"64":{"tf":1.4142135623730951},"7":{"tf":1.0}}}},"s":{"df":1,"docs":{"204":{"tf":2.8284271247461903}},"g":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":1,"docs":{"106":{"tf":1.0}},"s":{":":{":":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"106":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"u":{"c":{"df":0,"docs":{},"h":{"df":12,"docs":{"103":{"tf":1.0},"104":{"tf":1.0},"171":{"tf":1.0},"175":{"tf":1.0},"231":{"tf":1.0},"238":{"tf":1.4142135623730951},"241":{"tf":1.0},"4":{"tf":1.0},"50":{"tf":1.0},"60":{"tf":1.0},"64":{"tf":1.0},"78":{"tf":1.0}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":4,"docs":{"174":{"tf":1.0},"22":{"tf":1.0},"255":{"tf":1.4142135623730951},"99":{"tf":1.0}},"p":{"df":0,"docs":{},"l":{"df":13,"docs":{"118":{"tf":1.0},"163":{"tf":1.0},"164":{"tf":1.0},"172":{"tf":1.0},"174":{"tf":1.0},"204":{"tf":1.0},"213":{"tf":1.0},"232":{"tf":1.7320508075688772},"237":{"tf":1.0},"252":{"tf":1.0},"255":{"tf":1.0},"276":{"tf":1.0},"98":{"tf":1.0}}}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":1,"docs":{"83":{"tf":1.0}}}}}},"y":{"c":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"60":{"tf":2.23606797749979}}}},"df":0,"docs":{}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"61":{"tf":1.0}}}}}}}},"n":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"242":{"tf":1.0}}}},"m":{"df":0,"docs":{},"e":{"df":35,"docs":{"104":{"tf":1.0},"105":{"tf":1.4142135623730951},"108":{"tf":1.4142135623730951},"109":{"tf":1.4142135623730951},"112":{"tf":2.0},"114":{"tf":1.4142135623730951},"117":{"tf":1.0},"165":{"tf":1.0},"167":{"tf":1.0},"174":{"tf":1.0},"175":{"tf":1.4142135623730951},"200":{"tf":1.4142135623730951},"222":{"tf":1.0},"223":{"tf":1.0},"225":{"tf":1.0},"258":{"tf":1.0},"268":{"tf":1.7320508075688772},"269":{"tf":1.0},"277":{"tf":1.0},"28":{"tf":1.0},"282":{"tf":1.0},"33":{"tf":1.0},"37":{"tf":1.0},"39":{"tf":1.0},"60":{"tf":1.4142135623730951},"61":{"tf":1.0},"69":{"tf":1.0},"70":{"tf":1.4142135623730951},"72":{"tf":1.4142135623730951},"73":{"tf":1.0},"91":{"tf":1.4142135623730951},"93":{"tf":1.7320508075688772},"96":{"tf":1.0},"98":{"tf":1.4142135623730951},"99":{"tf":2.0}},"s":{"df":0,"docs":{},"p":{"a":{"c":{"df":2,"docs":{"112":{"tf":1.0},"172":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"r":{"df":0,"docs":{},"r":{"df":1,"docs":{"286":{"tf":1.0}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":6,"docs":{"0":{"tf":1.0},"11":{"tf":1.7320508075688772},"13":{"tf":1.4142135623730951},"19":{"tf":1.4142135623730951},"37":{"tf":2.23606797749979},"45":{"tf":1.0}},"e":{"/":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"37":{"tf":1.0}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"u":{"df":0,"docs":{},"r":{"df":2,"docs":{"154":{"tf":1.0},"155":{"tf":1.0}}}}},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":9,"docs":{"109":{"tf":1.4142135623730951},"205":{"tf":1.4142135623730951},"28":{"tf":1.0},"283":{"tf":1.0},"31":{"tf":1.0},"33":{"tf":1.0},"36":{"tf":1.0},"40":{"tf":1.0},"73":{"tf":2.449489742783178}}}}}},"d":{"df":0,"docs":{},"k":{"_":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"=":{"\"":{"$":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"k":{"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":0,"docs":{},"s":{"/":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"37":{"tf":1.0}}}}}}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"=":{"\"":{"$":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"/":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"/":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"/":{"df":0,"docs":{},"s":{"d":{"df":0,"docs":{},"k":{"/":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"k":{"/":{"2":{"5":{".":{"2":{".":{"9":{"5":{"1":{"9":{"6":{"5":{"3":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":2,"docs":{"12":{"tf":2.449489742783178},"37":{"tf":1.4142135623730951}}}},"df":2,"docs":{"20":{"tf":1.0},"22":{"tf":1.0}},"e":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":18,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"124":{"tf":1.0},"13":{"tf":1.0},"197":{"tf":1.0},"198":{"tf":1.0},"22":{"tf":1.0},"226":{"tf":1.4142135623730951},"231":{"tf":1.0},"241":{"tf":1.0},"242":{"tf":1.0},"25":{"tf":1.0},"264":{"tf":1.0},"27":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0},"67":{"tf":1.0},"81":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":3,"docs":{"124":{"tf":1.0},"262":{"tf":1.0},"43":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"k":{"df":0,"docs":{},"o":{"df":1,"docs":{"190":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"d":{"df":76,"docs":{"103":{"tf":1.0},"104":{"tf":1.7320508075688772},"105":{"tf":1.7320508075688772},"106":{"tf":1.0},"108":{"tf":1.4142135623730951},"109":{"tf":1.7320508075688772},"11":{"tf":1.0},"111":{"tf":1.0},"115":{"tf":1.4142135623730951},"116":{"tf":1.0},"122":{"tf":1.4142135623730951},"123":{"tf":1.0},"124":{"tf":2.23606797749979},"126":{"tf":1.0},"128":{"tf":1.0},"14":{"tf":1.4142135623730951},"140":{"tf":1.0},"145":{"tf":1.7320508075688772},"165":{"tf":1.0},"167":{"tf":1.4142135623730951},"17":{"tf":1.0},"171":{"tf":1.0},"197":{"tf":2.0},"200":{"tf":1.0},"21":{"tf":1.4142135623730951},"22":{"tf":1.4142135623730951},"220":{"tf":1.7320508075688772},"224":{"tf":1.7320508075688772},"225":{"tf":1.4142135623730951},"226":{"tf":1.0},"227":{"tf":1.0},"228":{"tf":1.0},"229":{"tf":2.23606797749979},"232":{"tf":1.4142135623730951},"233":{"tf":2.0},"234":{"tf":1.4142135623730951},"239":{"tf":1.0},"241":{"tf":1.7320508075688772},"242":{"tf":2.449489742783178},"249":{"tf":1.0},"25":{"tf":1.0},"250":{"tf":1.0},"256":{"tf":1.0},"258":{"tf":1.0},"26":{"tf":1.4142135623730951},"267":{"tf":1.0},"276":{"tf":1.0},"279":{"tf":1.0},"281":{"tf":1.0},"284":{"tf":1.0},"31":{"tf":1.0},"32":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.4142135623730951},"40":{"tf":1.0},"43":{"tf":1.0},"44":{"tf":1.0},"45":{"tf":1.7320508075688772},"46":{"tf":1.7320508075688772},"58":{"tf":1.7320508075688772},"59":{"tf":1.0},"6":{"tf":1.0},"61":{"tf":1.7320508075688772},"62":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"68":{"tf":1.0},"7":{"tf":1.4142135623730951},"73":{"tf":1.7320508075688772},"79":{"tf":1.4142135623730951},"81":{"tf":1.7320508075688772},"84":{"tf":1.7320508075688772},"86":{"tf":1.0},"89":{"tf":1.0},"9":{"tf":1.0},"90":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"g":{"df":3,"docs":{"166":{"tf":1.0},"180":{"tf":1.0},"210":{"tf":1.4142135623730951}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":5,"docs":{"29":{"tf":1.0},"30":{"tf":1.0},"34":{"tf":1.0},"35":{"tf":1.0},"61":{"tf":1.0}}}}},"df":0,"docs":{}}}},"t":{".":{"df":0,"docs":{},"j":{"a":{"df":0,"docs":{},"v":{"a":{".":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{".":{"df":0,"docs":{},"j":{"df":0,"docs":{},"n":{"a":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":9,"docs":{"133":{"tf":1.0},"179":{"tf":1.0},"183":{"tf":1.0},"189":{"tf":1.0},"190":{"tf":1.0},"201":{"tf":1.0},"233":{"tf":2.0},"242":{"tf":1.0},"245":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"242":{"tf":1.0}}}}}}}}}}}},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"l":{"df":3,"docs":{"169":{"tf":1.7320508075688772},"170":{"tf":1.7320508075688772},"171":{"tf":2.23606797749979}}}},"df":0,"docs":{}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"283":{"tf":1.0},"83":{"tf":1.0}}}}},"w":{"df":61,"docs":{"101":{"tf":1.0},"102":{"tf":1.0},"104":{"tf":1.0},"105":{"tf":1.4142135623730951},"108":{"tf":1.7320508075688772},"109":{"tf":1.4142135623730951},"119":{"tf":1.4142135623730951},"120":{"tf":1.0},"122":{"tf":1.0},"123":{"tf":2.0},"124":{"tf":2.0},"134":{"tf":1.0},"141":{"tf":1.0},"145":{"tf":1.4142135623730951},"158":{"tf":1.4142135623730951},"161":{"tf":1.0},"166":{"tf":1.0},"167":{"tf":1.7320508075688772},"170":{"tf":1.0},"171":{"tf":1.7320508075688772},"172":{"tf":1.4142135623730951},"189":{"tf":1.4142135623730951},"196":{"tf":1.7320508075688772},"197":{"tf":1.0},"198":{"tf":1.7320508075688772},"199":{"tf":1.4142135623730951},"20":{"tf":1.0},"200":{"tf":3.3166247903554},"203":{"tf":1.0},"208":{"tf":1.0},"212":{"tf":1.0},"225":{"tf":1.0},"23":{"tf":1.4142135623730951},"233":{"tf":1.4142135623730951},"242":{"tf":1.0},"246":{"tf":1.0},"249":{"tf":1.4142135623730951},"263":{"tf":1.0},"268":{"tf":2.0},"269":{"tf":1.7320508075688772},"274":{"tf":1.4142135623730951},"278":{"tf":1.0},"281":{"tf":1.7320508075688772},"282":{"tf":2.0},"283":{"tf":1.0},"284":{"tf":1.0},"31":{"tf":1.0},"32":{"tf":1.4142135623730951},"36":{"tf":1.0},"41":{"tf":1.0},"45":{"tf":1.0},"6":{"tf":1.0},"60":{"tf":1.4142135623730951},"64":{"tf":2.23606797749979},"68":{"tf":1.7320508075688772},"69":{"tf":2.23606797749979},"7":{"tf":2.23606797749979},"73":{"tf":1.7320508075688772},"75":{"tf":1.0},"81":{"tf":1.4142135623730951},"88":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":7,"docs":{"163":{"tf":1.0},"165":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"245":{"tf":1.0},"263":{"tf":1.0}}}},"l":{"df":0,"docs":{},"i":{"df":2,"docs":{"119":{"tf":1.0},"37":{"tf":1.0}}}}},"x":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"]":{".":{"0":{"a":{"1":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}},"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"_":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":0,"docs":{},"e":{"d":{"_":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"242":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":14,"docs":{"103":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"119":{"tf":1.0},"14":{"tf":1.0},"152":{"tf":1.0},"227":{"tf":1.0},"233":{"tf":1.0},"242":{"tf":1.4142135623730951},"249":{"tf":1.0},"250":{"tf":1.0},"40":{"tf":1.0},"6":{"tf":1.0},"74":{"tf":1.0}}}}},"i":{"c":{"df":0,"docs":{},"e":{"df":2,"docs":{"103":{"tf":1.4142135623730951},"46":{"tf":1.0}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":7,"docs":{"119":{"tf":2.449489742783178},"266":{"tf":2.23606797749979},"271":{"tf":1.7320508075688772},"272":{"tf":2.0},"274":{"tf":1.0},"40":{"tf":1.7320508075688772},"74":{"tf":1.4142135623730951}}},"y":{".":{"df":0,"docs":{},"j":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"271":{"tf":1.0}}}}}},"y":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":1,"docs":{"119":{"tf":1.0}}}}}},"/":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":1,"docs":{"271":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"m":{"b":{"df":0,"docs":{},"u":{"df":17,"docs":{"162":{"tf":1.0},"167":{"tf":1.7320508075688772},"172":{"tf":1.0},"174":{"tf":1.0},"178":{"tf":1.0},"180":{"tf":1.0},"182":{"tf":1.0},"184":{"tf":1.0},"190":{"tf":1.4142135623730951},"194":{"tf":2.0},"197":{"tf":2.0},"198":{"tf":2.23606797749979},"199":{"tf":1.0},"200":{"tf":3.3166247903554},"271":{"tf":1.0},"274":{"tf":1.4142135623730951},"284":{"tf":1.0}},"s":{".":{"df":0,"docs":{},"u":{"d":{"df":0,"docs":{},"l":{"df":1,"docs":{"200":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"n":{"df":0,"docs":{},"j":{"a":{"df":1,"docs":{"11":{"tf":2.0}}},"df":0,"docs":{}}}},"o":{"d":{"df":0,"docs":{},"e":{"df":1,"docs":{"86":{"tf":1.0}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"75":{"tf":1.0}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"123":{"tf":1.0}}}}},"n":{"df":16,"docs":{"122":{"tf":1.0},"13":{"tf":1.0},"167":{"tf":1.0},"184":{"tf":1.0},"188":{"tf":1.0},"189":{"tf":1.0},"200":{"tf":1.0},"201":{"tf":2.0},"226":{"tf":1.4142135623730951},"241":{"tf":1.0},"250":{"tf":1.0},"40":{"tf":1.4142135623730951},"45":{"tf":1.0},"46":{"tf":1.0},"58":{"tf":1.4142135623730951},"84":{"tf":1.4142135623730951}},"e":{"df":3,"docs":{"180":{"tf":1.0},"233":{"tf":1.0},"242":{"tf":1.4142135623730951}}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"df":1,"docs":{"116":{"tf":1.0}}}}}}}},"r":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"l":{"df":12,"docs":{"104":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"125":{"tf":1.0},"133":{"tf":1.0},"156":{"tf":1.0},"19":{"tf":1.0},"200":{"tf":1.0},"205":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"64":{"tf":1.0}}}},"df":2,"docs":{"200":{"tf":1.0},"201":{"tf":1.0}}}},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":2,"docs":{"18":{"tf":1.0},"201":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":42,"docs":{"105":{"tf":1.0},"106":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"11":{"tf":1.4142135623730951},"115":{"tf":1.0},"117":{"tf":1.0},"12":{"tf":1.0},"123":{"tf":1.0},"124":{"tf":1.4142135623730951},"13":{"tf":1.0},"14":{"tf":1.4142135623730951},"145":{"tf":1.4142135623730951},"18":{"tf":1.0},"198":{"tf":1.0},"20":{"tf":1.0},"200":{"tf":1.4142135623730951},"205":{"tf":1.0},"208":{"tf":1.0},"214":{"tf":1.0},"215":{"tf":1.0},"226":{"tf":1.4142135623730951},"231":{"tf":1.7320508075688772},"242":{"tf":1.4142135623730951},"243":{"tf":1.0},"245":{"tf":1.0},"258":{"tf":1.0},"264":{"tf":1.4142135623730951},"27":{"tf":1.0},"274":{"tf":1.0},"278":{"tf":1.0},"280":{"tf":1.0},"282":{"tf":1.4142135623730951},"31":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"62":{"tf":1.4142135623730951},"7":{"tf":1.0},"78":{"tf":1.0},"79":{"tf":1.4142135623730951},"81":{"tf":1.0},"83":{"tf":1.0}}},"h":{"df":7,"docs":{"163":{"tf":1.0},"169":{"tf":1.0},"179":{"tf":1.0},"180":{"tf":1.0},"182":{"tf":1.0},"62":{"tf":1.0},"73":{"tf":1.4142135623730951}}},"i":{"c":{"df":3,"docs":{"166":{"tf":1.0},"274":{"tf":1.0},"282":{"tf":1.0}}},"df":0,"docs":{},"f":{"df":1,"docs":{"274":{"tf":1.0}}}}},"w":{"df":19,"docs":{"105":{"tf":1.4142135623730951},"142":{"tf":1.0},"152":{"tf":1.0},"180":{"tf":1.0},"20":{"tf":1.0},"212":{"tf":1.0},"226":{"tf":1.7320508075688772},"233":{"tf":1.0},"241":{"tf":1.0},"242":{"tf":1.0},"257":{"tf":1.4142135623730951},"31":{"tf":1.0},"36":{"tf":1.0},"40":{"tf":1.0},"60":{"tf":1.4142135623730951},"62":{"tf":1.4142135623730951},"74":{"tf":1.0},"86":{"tf":1.0},"90":{"tf":1.0}}}},"s":{"df":0,"docs":{},"k":{"df":1,"docs":{"37":{"tf":1.0}}},"p":{"df":0,"docs":{},"r":{"df":1,"docs":{"67":{"tf":1.0}}}},"s":{"_":{"3":{"_":{"9":{"0":{"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"m":{"df":1,"docs":{"280":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"282":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"_":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"282":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":1,"docs":{"281":{"tf":1.0}}}}},"df":7,"docs":{"11":{"tf":1.7320508075688772},"278":{"tf":2.449489742783178},"279":{"tf":2.449489742783178},"280":{"tf":2.449489742783178},"281":{"tf":1.4142135623730951},"282":{"tf":3.1622776601683795},"67":{"tf":1.0}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":1,"docs":{"62":{"tf":2.8284271247461903}}}},"m":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":26,"docs":{"10":{"tf":1.4142135623730951},"115":{"tf":1.0},"13":{"tf":1.0},"161":{"tf":1.0},"193":{"tf":1.0},"197":{"tf":1.0},"20":{"tf":1.4142135623730951},"204":{"tf":1.0},"205":{"tf":1.0},"207":{"tf":1.0},"208":{"tf":1.4142135623730951},"213":{"tf":1.0},"214":{"tf":1.0},"215":{"tf":1.0},"216":{"tf":1.0},"22":{"tf":1.7320508075688772},"220":{"tf":1.0},"222":{"tf":1.0},"225":{"tf":1.4142135623730951},"231":{"tf":1.0},"268":{"tf":1.0},"269":{"tf":1.0},"270":{"tf":1.0},"271":{"tf":1.0},"279":{"tf":1.0},"37":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"22":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"64":{"tf":1.0}}}}}}}},"y":{"df":0,"docs":{},"g":{"a":{"df":0,"docs":{},"r":{"d":{"'":{"df":1,"docs":{"137":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"o":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":2,"docs":{"254":{"tf":1.0},"283":{"tf":2.8284271247461903}}}}}},"b":{"df":0,"docs":{},"j":{".":{"d":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"129":{"tf":1.0}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"s":{"df":1,"docs":{"129":{"tf":1.0}}}}}}}}}}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"129":{"tf":1.0}},"e":{"c":{"df":0,"docs":{},"t":{"df":9,"docs":{"128":{"tf":2.449489742783178},"129":{"tf":1.4142135623730951},"130":{"tf":1.4142135623730951},"131":{"tf":2.0},"132":{"tf":2.0},"245":{"tf":1.0},"268":{"tf":1.0},"283":{"tf":1.4142135623730951},"99":{"tf":1.0}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":9,"docs":{"203":{"tf":1.0},"204":{"tf":1.4142135623730951},"205":{"tf":1.0},"206":{"tf":1.0},"210":{"tf":1.0},"214":{"tf":1.7320508075688772},"215":{"tf":1.0},"216":{"tf":1.7320508075688772},"227":{"tf":1.0}}}}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":5,"docs":{"161":{"tf":1.0},"2":{"tf":1.0},"228":{"tf":1.0},"277":{"tf":1.0},"283":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":2,"docs":{"201":{"tf":1.0},"225":{"tf":1.0}},"s":{"df":2,"docs":{"124":{"tf":1.0},"201":{"tf":1.0}}}}}}}},"c":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":3,"docs":{"207":{"tf":1.0},"226":{"tf":1.0},"43":{"tf":1.0}},"r":{"df":2,"docs":{"31":{"tf":1.0},"36":{"tf":1.0}}}}}},"df":0,"docs":{}},"d":{"d":{"df":2,"docs":{"200":{"tf":1.0},"221":{"tf":1.0}}},"df":0,"docs":{}},"df":3,"docs":{"11":{"tf":1.0},"29":{"tf":1.0},"34":{"tf":1.0}},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":3,"docs":{"201":{"tf":1.0},"274":{"tf":1.0},"283":{"tf":1.0}}}},"i":{"c":{"df":0,"docs":{},"i":{"df":3,"docs":{"261":{"tf":1.0},"262":{"tf":1.0},"263":{"tf":1.0}}}},"df":0,"docs":{}}}},"k":{"a":{"df":0,"docs":{},"y":{"df":2,"docs":{"130":{"tf":1.0},"82":{"tf":1.0}}}},"df":2,"docs":{"226":{"tf":1.0},"242":{"tf":1.0}}},"l":{"d":{"df":9,"docs":{"109":{"tf":1.0},"203":{"tf":1.0},"209":{"tf":1.0},"212":{"tf":1.4142135623730951},"231":{"tf":1.0},"258":{"tf":1.0},"279":{"tf":1.0},"37":{"tf":1.0},"86":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":5,"docs":{"210":{"tf":1.0},"225":{"tf":1.0},"245":{"tf":1.0},"78":{"tf":1.0},"79":{"tf":1.0}}}}},"df":0,"docs":{}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"145":{"tf":1.0},"21":{"tf":1.0}}}}},"n":{"b":{"df":0,"docs":{},"o":{"a":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"184":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"c":{"df":18,"docs":{"106":{"tf":1.0},"11":{"tf":1.0},"119":{"tf":1.4142135623730951},"124":{"tf":1.0},"128":{"tf":1.0},"132":{"tf":1.0},"14":{"tf":1.0},"145":{"tf":1.0},"25":{"tf":1.0},"258":{"tf":1.0},"268":{"tf":1.0},"270":{"tf":1.0},"277":{"tf":1.0},"30":{"tf":1.0},"35":{"tf":1.0},"6":{"tf":1.0},"62":{"tf":1.0},"64":{"tf":1.0}}},"df":34,"docs":{"106":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"116":{"tf":1.0},"122":{"tf":1.0},"125":{"tf":1.0},"126":{"tf":1.0},"131":{"tf":1.4142135623730951},"132":{"tf":1.0},"138":{"tf":1.0},"163":{"tf":1.7320508075688772},"167":{"tf":1.0},"171":{"tf":1.0},"196":{"tf":1.0},"203":{"tf":1.0},"212":{"tf":1.0},"228":{"tf":1.0},"229":{"tf":1.0},"232":{"tf":1.0},"236":{"tf":1.0},"249":{"tf":1.0},"257":{"tf":1.0},"258":{"tf":1.4142135623730951},"274":{"tf":1.0},"276":{"tf":1.0},"282":{"tf":1.0},"45":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.7320508075688772},"7":{"tf":1.0},"79":{"tf":1.4142135623730951},"82":{"tf":1.4142135623730951},"86":{"tf":1.0}},"g":{"df":0,"docs":{},"o":{"df":1,"docs":{"64":{"tf":1.0}}}},"t":{"df":0,"docs":{},"o":{"df":1,"docs":{"115":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"m":{"df":1,"docs":{"154":{"tf":1.0}}}},"p":{"a":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":2,"docs":{"229":{"tf":1.0},"242":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":20,"docs":{"107":{"tf":1.0},"109":{"tf":1.0},"111":{"tf":1.0},"124":{"tf":1.0},"151":{"tf":1.4142135623730951},"152":{"tf":1.4142135623730951},"154":{"tf":1.7320508075688772},"155":{"tf":1.7320508075688772},"156":{"tf":1.0},"158":{"tf":1.4142135623730951},"25":{"tf":1.0},"274":{"tf":1.4142135623730951},"280":{"tf":1.0},"286":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"40":{"tf":1.0},"7":{"tf":1.4142135623730951},"73":{"tf":1.0},"81":{"tf":1.0}},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"l":{"df":1,"docs":{"11":{"tf":1.0}}}}}},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"f":{"df":0,"docs":{},"x":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":1,"docs":{"283":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"df":7,"docs":{"151":{"tf":1.0},"152":{"tf":1.0},"156":{"tf":1.0},"201":{"tf":1.4142135623730951},"225":{"tf":1.0},"245":{"tf":1.0},"37":{"tf":1.0}}}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":1,"docs":{"170":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"233":{"tf":1.0}}}}}}},"t":{"df":3,"docs":{"233":{"tf":1.4142135623730951},"280":{"tf":1.0},"283":{"tf":1.0}},"i":{"df":0,"docs":{},"m":{"df":2,"docs":{"220":{"tf":1.0},"282":{"tf":1.0}}},"o":{"df":0,"docs":{},"n":{"<":{"df":0,"docs":{},"j":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":1,"docs":{"242":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"242":{"tf":1.0}}}}},"v":{"df":0,"docs":{},"e":{"c":{"<":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"242":{"tf":1.4142135623730951}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":36,"docs":{"103":{"tf":1.4142135623730951},"137":{"tf":1.0},"138":{"tf":1.0},"142":{"tf":1.7320508075688772},"143":{"tf":1.0},"144":{"tf":1.0},"145":{"tf":1.0},"146":{"tf":1.4142135623730951},"151":{"tf":1.0},"153":{"tf":1.0},"158":{"tf":1.0},"159":{"tf":1.0},"163":{"tf":1.0},"164":{"tf":1.4142135623730951},"168":{"tf":1.0},"169":{"tf":1.4142135623730951},"170":{"tf":1.4142135623730951},"171":{"tf":1.4142135623730951},"172":{"tf":1.7320508075688772},"174":{"tf":1.4142135623730951},"179":{"tf":1.0},"180":{"tf":2.23606797749979},"181":{"tf":1.0},"185":{"tf":1.0},"187":{"tf":1.0},"195":{"tf":1.0},"196":{"tf":2.0},"197":{"tf":1.4142135623730951},"198":{"tf":2.0},"199":{"tf":1.4142135623730951},"207":{"tf":2.0},"208":{"tf":1.4142135623730951},"211":{"tf":1.0},"249":{"tf":1.0},"73":{"tf":1.0},"74":{"tf":1.4142135623730951}}}}}}},"r":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":14,"docs":{"105":{"tf":1.0},"106":{"tf":1.0},"115":{"tf":1.0},"170":{"tf":1.4142135623730951},"22":{"tf":1.4142135623730951},"223":{"tf":1.0},"224":{"tf":1.0},"246":{"tf":1.0},"282":{"tf":1.0},"286":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0},"59":{"tf":1.0},"69":{"tf":1.0}}}}},"df":0,"docs":{},"g":{".":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{".":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"$":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"112":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":1,"docs":{"274":{"tf":1.4142135623730951}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":2,"docs":{"115":{"tf":1.0},"231":{"tf":1.0}}}}}}},"s":{"df":1,"docs":{"133":{"tf":1.0}},"x":{"df":1,"docs":{"17":{"tf":1.4142135623730951}}}},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"'":{"df":1,"docs":{"175":{"tf":1.0}}},"df":2,"docs":{"116":{"tf":1.0},"226":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"242":{"tf":1.0}}}}}}},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":4,"docs":{"224":{"tf":1.4142135623730951},"226":{"tf":1.0},"88":{"tf":1.0},"98":{"tf":1.0}}}}}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"v":{"df":3,"docs":{"166":{"tf":1.0},"170":{"tf":1.0},"278":{"tf":1.0}}}}}}},"t":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":6,"docs":{"138":{"tf":1.0},"143":{"tf":1.0},"152":{"tf":1.0},"164":{"tf":1.0},"180":{"tf":1.0},"208":{"tf":1.0}}}}},"df":30,"docs":{"103":{"tf":1.0},"105":{"tf":1.0},"107":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"116":{"tf":1.0},"171":{"tf":1.0},"18":{"tf":1.0},"190":{"tf":1.0},"198":{"tf":1.0},"200":{"tf":1.4142135623730951},"201":{"tf":1.0},"204":{"tf":1.4142135623730951},"233":{"tf":1.4142135623730951},"249":{"tf":1.0},"252":{"tf":1.0},"274":{"tf":1.7320508075688772},"279":{"tf":1.4142135623730951},"283":{"tf":1.4142135623730951},"29":{"tf":1.0},"31":{"tf":1.0},"34":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"43":{"tf":1.0},"59":{"tf":1.0},"6":{"tf":1.0},"60":{"tf":1.0},"81":{"tf":1.0},"96":{"tf":1.0}},"g":{"df":0,"docs":{},"o":{"df":2,"docs":{"231":{"tf":1.0},"249":{"tf":1.0}}}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":3,"docs":{"187":{"tf":1.0},"233":{"tf":1.0},"91":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":6,"docs":{"174":{"tf":1.0},"252":{"tf":1.0},"280":{"tf":1.0},"282":{"tf":1.0},"37":{"tf":1.0},"62":{"tf":2.0}}}}},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":1,"docs":{"61":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"156":{"tf":1.0}}},"df":0,"docs":{}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"l":{"df":7,"docs":{"102":{"tf":1.0},"105":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"218":{"tf":1.0},"222":{"tf":1.0},"244":{"tf":1.0}}}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":1,"docs":{"175":{"tf":1.0}}}}},"df":21,"docs":{"101":{"tf":1.0},"103":{"tf":1.0},"104":{"tf":1.0},"105":{"tf":1.0},"106":{"tf":1.7320508075688772},"11":{"tf":1.0},"116":{"tf":1.0},"126":{"tf":1.0},"140":{"tf":1.0},"172":{"tf":1.0},"193":{"tf":1.0},"204":{"tf":1.4142135623730951},"208":{"tf":1.0},"212":{"tf":1.0},"214":{"tf":1.0},"215":{"tf":1.0},"216":{"tf":1.4142135623730951},"217":{"tf":1.4142135623730951},"228":{"tf":1.0},"274":{"tf":1.0},"43":{"tf":1.0}},"h":{"df":0,"docs":{},"e":{"a":{"d":{"df":2,"docs":{"115":{"tf":1.0},"59":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"l":{"a":{"df":0,"docs":{},"p":{"df":1,"docs":{"198":{"tf":1.0}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":1,"docs":{"46":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"i":{"d":{"df":3,"docs":{"123":{"tf":1.0},"124":{"tf":1.0},"22":{"tf":1.0}}},"df":0,"docs":{}}},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":9,"docs":{"174":{"tf":1.0},"219":{"tf":1.4142135623730951},"227":{"tf":1.0},"243":{"tf":1.4142135623730951},"274":{"tf":1.0},"41":{"tf":1.0},"93":{"tf":1.0},"96":{"tf":1.0},"99":{"tf":1.0}}}}}},"w":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"179":{"tf":1.0}}}}}}}}},"w":{"df":0,"docs":{},"n":{"df":3,"docs":{"201":{"tf":1.0},"276":{"tf":1.4142135623730951},"277":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"121":{"tf":1.0},"276":{"tf":1.0}},"s":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":1,"docs":{"276":{"tf":1.0}}}}}}}}}}},"p":{"a":{"c":{"df":0,"docs":{},"k":{"a":{"df":0,"docs":{},"g":{"df":39,"docs":{"100":{"tf":1.0},"103":{"tf":1.0},"108":{"tf":1.0},"112":{"tf":1.7320508075688772},"113":{"tf":1.0},"12":{"tf":1.0},"134":{"tf":1.0},"160":{"tf":1.0},"163":{"tf":2.0},"164":{"tf":1.0},"165":{"tf":1.0},"166":{"tf":1.0},"167":{"tf":2.449489742783178},"169":{"tf":1.0},"170":{"tf":1.4142135623730951},"171":{"tf":2.449489742783178},"172":{"tf":2.6457513110645907},"174":{"tf":2.6457513110645907},"219":{"tf":1.4142135623730951},"220":{"tf":1.0},"222":{"tf":1.0},"225":{"tf":1.0},"23":{"tf":1.7320508075688772},"25":{"tf":3.0},"251":{"tf":1.7320508075688772},"253":{"tf":2.6457513110645907},"26":{"tf":2.0},"269":{"tf":1.0},"271":{"tf":1.0},"274":{"tf":1.4142135623730951},"31":{"tf":2.449489742783178},"32":{"tf":1.7320508075688772},"36":{"tf":2.449489742783178},"37":{"tf":1.0},"45":{"tf":1.0},"53":{"tf":1.0},"73":{"tf":1.7320508075688772},"74":{"tf":1.0},"99":{"tf":1.0}},"e":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"'":{"df":2,"docs":{"29":{"tf":1.0},"34":{"tf":1.0}}},"df":3,"docs":{"167":{"tf":1.7320508075688772},"251":{"tf":1.0},"253":{"tf":1.4142135623730951}}}}}}}},"=":{"\"":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{".":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{".":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"<":{"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"_":{"c":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"70":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"70":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"117":{"tf":1.0}}}}}}}}}},"df":0,"docs":{}}},"d":{"df":1,"docs":{"64":{"tf":1.0}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"s":{"df":1,"docs":{"264":{"tf":1.0}}}},"df":43,"docs":{"100":{"tf":1.0},"109":{"tf":1.0},"117":{"tf":1.0},"119":{"tf":1.0},"124":{"tf":1.0},"127":{"tf":1.0},"133":{"tf":1.0},"138":{"tf":1.0},"147":{"tf":1.0},"15":{"tf":1.0},"159":{"tf":1.0},"175":{"tf":1.0},"185":{"tf":1.0},"201":{"tf":1.0},"218":{"tf":1.0},"22":{"tf":1.0},"224":{"tf":1.0},"242":{"tf":1.0},"250":{"tf":1.0},"253":{"tf":1.0},"258":{"tf":1.0},"259":{"tf":1.0},"261":{"tf":1.0},"263":{"tf":1.0},"264":{"tf":1.0},"273":{"tf":1.0},"277":{"tf":1.0},"282":{"tf":1.0},"283":{"tf":1.0},"286":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"40":{"tf":1.0},"5":{"tf":1.0},"51":{"tf":1.0},"56":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0},"67":{"tf":1.0},"74":{"tf":1.0},"8":{"tf":1.0},"90":{"tf":1.0}}}},"i":{"df":0,"docs":{},"n":{"df":4,"docs":{"104":{"tf":1.0},"115":{"tf":1.0},"122":{"tf":1.0},"5":{"tf":1.0}}}},"n":{"df":0,"docs":{},"e":{"df":1,"docs":{"126":{"tf":1.4142135623730951}},"l":{"df":2,"docs":{"205":{"tf":1.4142135623730951},"206":{"tf":1.0}}}},"i":{"c":{"df":1,"docs":{"117":{"tf":1.0}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"6":{"tf":1.0}}}}},"df":0,"docs":{}}}},"r":{"a":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"m":{"df":1,"docs":{"61":{"tf":1.0}}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":1,"docs":{"59":{"tf":1.0}}}}}},"m":{"df":2,"docs":{"242":{"tf":1.4142135623730951},"79":{"tf":1.0}},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"283":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"61":{"tf":1.7320508075688772},"62":{"tf":1.0}}}}},"s":{"df":1,"docs":{"22":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"58":{"tf":1.0}}}}},"t":{"df":28,"docs":{"103":{"tf":1.4142135623730951},"104":{"tf":1.0},"106":{"tf":1.0},"108":{"tf":1.4142135623730951},"109":{"tf":1.4142135623730951},"113":{"tf":1.0},"114":{"tf":1.0},"161":{"tf":1.0},"165":{"tf":1.0},"171":{"tf":1.0},"190":{"tf":1.0},"200":{"tf":1.4142135623730951},"201":{"tf":1.0},"203":{"tf":1.0},"214":{"tf":1.0},"22":{"tf":1.0},"225":{"tf":1.0},"233":{"tf":1.0},"242":{"tf":1.0},"245":{"tf":1.4142135623730951},"251":{"tf":1.0},"268":{"tf":1.0},"274":{"tf":1.0},"64":{"tf":1.7320508075688772},"68":{"tf":1.4142135623730951},"79":{"tf":1.0},"80":{"tf":1.0},"84":{"tf":1.0}},"i":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":1,"docs":{"3":{"tf":1.4142135623730951}}}},"u":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"r":{"df":9,"docs":{"116":{"tf":1.0},"198":{"tf":1.0},"200":{"tf":1.0},"225":{"tf":1.0},"231":{"tf":1.0},"233":{"tf":1.4142135623730951},"236":{"tf":1.4142135623730951},"39":{"tf":1.0},"64":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"213":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":4,"docs":{"121":{"tf":1.0},"201":{"tf":1.7320508075688772},"63":{"tf":1.0},"64":{"tf":1.7320508075688772}}}}},"s":{"df":0,"docs":{},"s":{"df":20,"docs":{"105":{"tf":1.0},"106":{"tf":1.7320508075688772},"108":{"tf":1.0},"109":{"tf":1.0},"132":{"tf":1.0},"14":{"tf":1.0},"142":{"tf":1.4142135623730951},"143":{"tf":1.0},"145":{"tf":1.7320508075688772},"225":{"tf":1.0},"228":{"tf":1.0},"229":{"tf":1.0},"242":{"tf":1.7320508075688772},"245":{"tf":1.4142135623730951},"262":{"tf":1.4142135623730951},"283":{"tf":1.4142135623730951},"58":{"tf":1.0},"7":{"tf":1.4142135623730951},"73":{"tf":1.0},"79":{"tf":2.0}},"i":{"df":0,"docs":{},"v":{"df":1,"docs":{"264":{"tf":1.0}}}},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"d":{"df":3,"docs":{"242":{"tf":1.0},"257":{"tf":1.0},"86":{"tf":1.0}}},"df":0,"docs":{}}}}},"t":{"df":2,"docs":{"205":{"tf":1.0},"207":{"tf":1.0}}}},"t":{"c":{"df":0,"docs":{},"h":{"df":9,"docs":{"11":{"tf":1.4142135623730951},"122":{"tf":1.4142135623730951},"123":{"tf":1.0},"124":{"tf":1.0},"262":{"tf":1.0},"270":{"tf":1.0},"62":{"tf":1.0},"7":{"tf":2.449489742783178},"8":{"tf":1.0}}}},"df":0,"docs":{},"h":{"/":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"/":{"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"/":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"/":{"df":0,"docs":{},"o":{"df":0,"docs":{},"f":{"/":{"df":0,"docs":{},"j":{"df":0,"docs":{},"n":{"a":{"/":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"37":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"=":{"\"":{"$":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{":":{"$":{"(":{"b":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":1,"docs":{"11":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"k":{"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"/":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"/":{"a":{"a":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"h":{"6":{"4":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":1,"docs":{"37":{"tf":1.0}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"v":{"df":0,"docs":{},"m":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"/":{"d":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"37":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"x":{"8":{"6":{"_":{"6":{"4":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{},"~":{"/":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"g":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{":":{"$":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"11":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":19,"docs":{"11":{"tf":1.4142135623730951},"122":{"tf":1.0},"123":{"tf":1.0},"124":{"tf":2.6457513110645907},"166":{"tf":1.0},"18":{"tf":1.4142135623730951},"192":{"tf":1.0},"22":{"tf":1.0},"25":{"tf":1.0},"280":{"tf":1.0},"29":{"tf":1.0},"31":{"tf":1.4142135623730951},"34":{"tf":1.0},"36":{"tf":1.4142135623730951},"37":{"tf":1.4142135623730951},"45":{"tf":1.0},"60":{"tf":1.4142135623730951},"62":{"tf":1.4142135623730951},"74":{"tf":1.4142135623730951}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":1,"docs":{"247":{"tf":1.4142135623730951}}}}}}},"u":{"df":0,"docs":{},"s":{"df":2,"docs":{"180":{"tf":1.0},"183":{"tf":1.0}}}},"y":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":1,"docs":{"283":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":2,"docs":{"58":{"tf":1.7320508075688772},"69":{"tf":1.0}},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"122":{"tf":1.0},"8":{"tf":1.0}}}},"n":{"d":{"df":1,"docs":{"21":{"tf":1.0}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":2,"docs":{"138":{"tf":1.0},"60":{"tf":1.0}}}}},"r":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":2,"docs":{"170":{"tf":1.0},"242":{"tf":1.0}}}},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"62":{"tf":1.0}},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"204":{"tf":3.0}}}}}}}},"df":9,"docs":{"140":{"tf":1.4142135623730951},"145":{"tf":1.4142135623730951},"231":{"tf":1.0},"232":{"tf":1.0},"245":{"tf":1.0},"252":{"tf":1.0},"40":{"tf":1.0},"61":{"tf":1.4142135623730951},"64":{"tf":1.0}},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":17,"docs":{"111":{"tf":1.0},"145":{"tf":1.4142135623730951},"16":{"tf":1.0},"187":{"tf":1.0},"205":{"tf":1.4142135623730951},"209":{"tf":1.0},"212":{"tf":1.0},"218":{"tf":1.0},"225":{"tf":1.0},"226":{"tf":1.0},"245":{"tf":1.4142135623730951},"249":{"tf":1.0},"268":{"tf":1.0},"278":{"tf":1.0},"282":{"tf":1.0},"58":{"tf":1.0},"81":{"tf":1.0}}}}}},"h":{"a":{"df":0,"docs":{},"p":{"df":3,"docs":{"103":{"tf":1.0},"104":{"tf":1.0},"115":{"tf":1.0}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"o":{"d":{"df":3,"docs":{"145":{"tf":1.0},"226":{"tf":1.0},"245":{"tf":1.0}}},"df":0,"docs":{}}},"l":{"df":1,"docs":{"11":{"tf":1.4142135623730951}}},"m":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"273":{"tf":1.0}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"274":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":12,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"149":{"tf":1.0},"150":{"tf":1.0},"194":{"tf":1.4142135623730951},"197":{"tf":1.7320508075688772},"198":{"tf":1.0},"229":{"tf":2.0},"242":{"tf":1.4142135623730951},"249":{"tf":1.0},"283":{"tf":1.4142135623730951},"82":{"tf":1.0}},"e":{"d":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"242":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"x":{"a":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"108":{"tf":1.0},"109":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}}},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"277":{"tf":1.4142135623730951}}}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"284":{"tf":1.0}}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"s":{"df":1,"docs":{"102":{"tf":1.0}}}}}},"h":{"a":{"b":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"c":{"df":3,"docs":{"122":{"tf":1.0},"123":{"tf":1.0},"124":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":5,"docs":{"22":{"tf":1.0},"267":{"tf":1.4142135623730951},"273":{"tf":1.0},"60":{"tf":1.0},"73":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}},"i":{"c":{"df":0,"docs":{},"k":{"df":5,"docs":{"270":{"tf":1.0},"272":{"tf":1.0},"30":{"tf":1.0},"35":{"tf":1.0},"6":{"tf":1.0}}}},"df":0,"docs":{},"e":{"c":{"df":3,"docs":{"105":{"tf":1.0},"116":{"tf":1.0},"60":{"tf":1.0}}},"df":0,"docs":{}},"n":{"df":2,"docs":{"263":{"tf":1.0},"274":{"tf":1.0}}},"p":{"3":{"df":2,"docs":{"269":{"tf":1.0},"53":{"tf":1.0}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":5,"docs":{"113":{"tf":1.0},"221":{"tf":1.0},"226":{"tf":1.0},"274":{"tf":2.23606797749979},"70":{"tf":1.0}}}}}}}},"l":{"a":{"c":{"df":0,"docs":{},"e":{"df":23,"docs":{"106":{"tf":1.4142135623730951},"108":{"tf":1.0},"134":{"tf":1.0},"142":{"tf":1.0},"202":{"tf":1.0},"218":{"tf":1.0},"221":{"tf":1.0},"232":{"tf":1.4142135623730951},"237":{"tf":1.0},"244":{"tf":1.0},"245":{"tf":1.0},"264":{"tf":2.8284271247461903},"274":{"tf":1.0},"48":{"tf":1.0},"58":{"tf":1.4142135623730951},"61":{"tf":1.4142135623730951},"62":{"tf":1.0},"70":{"tf":1.4142135623730951},"72":{"tf":1.0},"78":{"tf":1.4142135623730951},"79":{"tf":1.4142135623730951},"81":{"tf":1.0},"83":{"tf":1.0}},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"58":{"tf":1.0}}}}}},"s":{".":{"d":{"b":{"df":1,"docs":{"218":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"62":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"e":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"130":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"n":{"df":8,"docs":{"140":{"tf":1.0},"152":{"tf":1.0},"180":{"tf":1.0},"200":{"tf":1.0},"228":{"tf":1.0},"230":{"tf":1.0},"249":{"tf":2.23606797749979},"274":{"tf":1.0}}},"t":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":22,"docs":{"0":{"tf":1.0},"116":{"tf":1.7320508075688772},"13":{"tf":1.0},"170":{"tf":1.0},"184":{"tf":1.0},"196":{"tf":1.0},"200":{"tf":1.0},"213":{"tf":1.0},"220":{"tf":1.0},"223":{"tf":1.0},"225":{"tf":1.0},"251":{"tf":1.0},"255":{"tf":1.7320508075688772},"256":{"tf":1.4142135623730951},"257":{"tf":1.0},"258":{"tf":1.0},"274":{"tf":1.0},"279":{"tf":1.0},"28":{"tf":1.0},"33":{"tf":1.0},"37":{"tf":1.4142135623730951},"69":{"tf":1.0}},"s":{";":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"13":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":11,"docs":{"111":{"tf":1.0},"120":{"tf":1.0},"134":{"tf":1.0},"200":{"tf":1.4142135623730951},"4":{"tf":1.0},"41":{"tf":1.0},"5":{"tf":1.0},"69":{"tf":1.0},"7":{"tf":1.4142135623730951},"75":{"tf":1.4142135623730951},"81":{"tf":1.0}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"252":{"tf":1.0}}}}},"u":{"df":3,"docs":{"174":{"tf":1.0},"233":{"tf":1.0},"98":{"tf":1.0}},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"65":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":18,"docs":{"104":{"tf":1.7320508075688772},"105":{"tf":1.0},"12":{"tf":1.0},"171":{"tf":1.4142135623730951},"198":{"tf":1.0},"201":{"tf":1.0},"226":{"tf":1.0},"241":{"tf":1.7320508075688772},"260":{"tf":1.0},"274":{"tf":1.4142135623730951},"29":{"tf":1.0},"34":{"tf":1.0},"37":{"tf":1.0},"5":{"tf":1.0},"51":{"tf":1.0},"64":{"tf":1.0},"73":{"tf":1.4142135623730951},"78":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"116":{"tf":1.0},"128":{"tf":1.4142135623730951}}}}}}},"l":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":6,"docs":{"121":{"tf":1.0},"219":{"tf":1.0},"226":{"tf":1.0},"261":{"tf":2.0},"262":{"tf":2.0},"63":{"tf":1.0}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":6,"docs":{"145":{"tf":1.0},"184":{"tf":1.0},"214":{"tf":1.0},"215":{"tf":1.0},"226":{"tf":1.0},"233":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"t":{"df":4,"docs":{"101":{"tf":1.4142135623730951},"102":{"tf":1.0},"104":{"tf":1.0},"228":{"tf":1.4142135623730951}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"226":{"tf":1.4142135623730951}}}}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":3,"docs":{"165":{"tf":1.0},"209":{"tf":1.0},"62":{"tf":1.0}}}},"s":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"l":{"df":32,"docs":{"105":{"tf":1.0},"106":{"tf":1.0},"11":{"tf":1.0},"116":{"tf":1.0},"121":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0},"158":{"tf":1.0},"16":{"tf":1.0},"165":{"tf":1.0},"170":{"tf":1.0},"175":{"tf":1.0},"180":{"tf":1.0},"192":{"tf":1.0},"196":{"tf":1.0},"200":{"tf":1.0},"225":{"tf":1.0},"226":{"tf":1.0},"227":{"tf":1.0},"236":{"tf":1.0},"237":{"tf":1.0},"242":{"tf":1.0},"25":{"tf":1.0},"263":{"tf":1.0},"27":{"tf":1.0},"278":{"tf":1.0},"43":{"tf":1.4142135623730951},"59":{"tf":1.0},"7":{"tf":1.0},"80":{"tf":1.0},"83":{"tf":1.0},"88":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"df":2,"docs":{"249":{"tf":1.0},"90":{"tf":1.4142135623730951}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"l":{"_":{"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"_":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"_":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"145":{"tf":1.4142135623730951},"146":{"tf":1.0}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":8,"docs":{"159":{"tf":1.0},"164":{"tf":1.0},"165":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"59":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0}}}}}}},"v":{"df":1,"docs":{"226":{"tf":1.0}}},"w":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"203":{"tf":1.0},"205":{"tf":1.7320508075688772}}}}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"c":{"df":14,"docs":{"101":{"tf":1.0},"116":{"tf":1.0},"123":{"tf":1.4142135623730951},"128":{"tf":1.0},"140":{"tf":1.0},"170":{"tf":1.0},"226":{"tf":1.0},"232":{"tf":1.0},"274":{"tf":1.0},"276":{"tf":1.0},"43":{"tf":1.0},"45":{"tf":1.0},"75":{"tf":1.0},"8":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"m":{"a":{"df":2,"docs":{"264":{"tf":2.8284271247461903},"81":{"tf":1.0}}},"df":0,"docs":{}}}},"df":15,"docs":{"118":{"tf":1.7320508075688772},"119":{"tf":2.6457513110645907},"124":{"tf":1.0},"21":{"tf":1.4142135623730951},"263":{"tf":1.4142135623730951},"268":{"tf":2.6457513110645907},"269":{"tf":1.4142135623730951},"270":{"tf":1.4142135623730951},"274":{"tf":1.4142135623730951},"280":{"tf":1.0},"282":{"tf":1.0},"39":{"tf":1.7320508075688772},"6":{"tf":1.0},"64":{"tf":1.4142135623730951},"7":{"tf":3.1622776601683795}},"e":{"c":{"df":0,"docs":{},"e":{"d":{"df":2,"docs":{"31":{"tf":1.0},"36":{"tf":1.0}}},"df":0,"docs":{}},"l":{"df":0,"docs":{},"u":{"d":{"df":2,"docs":{"197":{"tf":1.0},"198":{"tf":1.0}}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"213":{"tf":1.0}}}},"df":0,"docs":{}}},"df":15,"docs":{"103":{"tf":1.0},"161":{"tf":1.0},"163":{"tf":1.7320508075688772},"164":{"tf":1.0},"167":{"tf":1.7320508075688772},"171":{"tf":1.0},"172":{"tf":1.0},"174":{"tf":1.0},"175":{"tf":1.0},"184":{"tf":1.0},"189":{"tf":1.0},"236":{"tf":1.0},"253":{"tf":1.0},"274":{"tf":1.0},"280":{"tf":1.0}},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":9,"docs":{"170":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.4142135623730951},"229":{"tf":1.0},"232":{"tf":1.0},"64":{"tf":1.0},"79":{"tf":1.0},"82":{"tf":1.0},"83":{"tf":1.0}}}},"i":{"df":0,"docs":{},"x":{")":{"/":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"y":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"3":{"df":1,"docs":{"11":{"tf":1.0}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"y":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"@":{"3":{".":{"9":{"/":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"y":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{".":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"/":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"/":{"3":{".":{"9":{"/":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"11":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"p":{"a":{"df":0,"docs":{},"r":{"df":2,"docs":{"268":{"tf":1.0},"74":{"tf":1.0}}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"24":{"tf":1.0}}}}}}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":5,"docs":{"185":{"tf":1.4142135623730951},"201":{"tf":1.0},"22":{"tf":1.4142135623730951},"237":{"tf":1.0},"270":{"tf":1.0}}}},"r":{"df":0,"docs":{},"v":{"df":1,"docs":{"26":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":1,"docs":{"242":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"59":{"tf":1.0}}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"228":{"tf":1.0},"233":{"tf":1.0}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"61":{"tf":1.0}}}}},"v":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"128":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":12,"docs":{"146":{"tf":1.0},"150":{"tf":1.0},"169":{"tf":1.4142135623730951},"170":{"tf":1.0},"171":{"tf":1.0},"179":{"tf":1.0},"180":{"tf":1.0},"183":{"tf":1.7320508075688772},"201":{"tf":1.0},"274":{"tf":1.4142135623730951},"31":{"tf":1.0},"36":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":6,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"268":{"tf":1.4142135623730951},"270":{"tf":1.0},"29":{"tf":1.4142135623730951},"34":{"tf":1.4142135623730951}},"s":{"df":5,"docs":{"106":{"tf":1.0},"141":{"tf":1.0},"25":{"tf":1.0},"26":{"tf":1.0},"268":{"tf":1.0}}}}}}}},"i":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":2,"docs":{"226":{"tf":1.4142135623730951},"248":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":5,"docs":{"11":{"tf":1.0},"201":{"tf":1.0},"245":{"tf":1.0},"257":{"tf":1.0},"284":{"tf":1.0}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"278":{"tf":1.0},"282":{"tf":1.4142135623730951}}}}},"n":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"62":{"tf":1.4142135623730951}}}}}}},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"62":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":3,"docs":{"22":{"tf":1.0},"37":{"tf":1.0},"62":{"tf":1.4142135623730951}},"f":{"df":1,"docs":{"37":{"tf":1.4142135623730951}}}}},"o":{"df":0,"docs":{},"r":{"df":2,"docs":{"206":{"tf":1.0},"214":{"tf":1.0}},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"258":{"tf":1.0},"6":{"tf":1.0}}}}}},"v":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"83":{"tf":1.0}}}},"df":0,"docs":{}}},"o":{"b":{"a":{"b":{"df":0,"docs":{},"l":{"df":17,"docs":{"104":{"tf":1.0},"115":{"tf":1.0},"122":{"tf":1.0},"156":{"tf":1.0},"18":{"tf":1.0},"197":{"tf":1.7320508075688772},"200":{"tf":1.0},"225":{"tf":1.0},"226":{"tf":1.4142135623730951},"229":{"tf":1.0},"231":{"tf":1.4142135623730951},"236":{"tf":1.0},"238":{"tf":1.0},"37":{"tf":1.0},"40":{"tf":1.0},"62":{"tf":1.0},"84":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"t":{"df":3,"docs":{"180":{"tf":1.0},"183":{"tf":1.0},"256":{"tf":1.0}}}},"df":18,"docs":{"136":{"tf":1.0},"140":{"tf":1.0},"149":{"tf":1.0},"161":{"tf":1.7320508075688772},"175":{"tf":1.0},"177":{"tf":1.0},"182":{"tf":1.0},"187":{"tf":1.0},"197":{"tf":1.0},"203":{"tf":1.0},"205":{"tf":1.0},"226":{"tf":1.0},"25":{"tf":1.0},"260":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"58":{"tf":1.7320508075688772},"64":{"tf":1.0}}}}}},"c":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":2,"docs":{"18":{"tf":1.0},"20":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"d":{"df":1,"docs":{"12":{"tf":1.0}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"s":{"df":38,"docs":{"101":{"tf":1.0},"102":{"tf":1.0},"105":{"tf":1.0},"106":{"tf":1.0},"114":{"tf":1.0},"120":{"tf":1.0},"122":{"tf":1.0},"123":{"tf":1.0},"124":{"tf":1.0},"126":{"tf":1.7320508075688772},"133":{"tf":1.0},"138":{"tf":1.0},"161":{"tf":1.0},"165":{"tf":1.0},"167":{"tf":1.4142135623730951},"169":{"tf":1.0},"170":{"tf":1.0},"188":{"tf":1.0},"19":{"tf":1.0},"197":{"tf":1.0},"20":{"tf":1.0},"220":{"tf":1.0},"243":{"tf":1.0},"244":{"tf":1.4142135623730951},"245":{"tf":1.4142135623730951},"246":{"tf":1.0},"248":{"tf":1.0},"257":{"tf":1.0},"259":{"tf":1.0},"26":{"tf":1.0},"265":{"tf":1.0},"27":{"tf":1.4142135623730951},"274":{"tf":1.0},"280":{"tf":1.0},"282":{"tf":1.0},"49":{"tf":1.0},"69":{"tf":1.0},"88":{"tf":1.0}}}}}},"d":{"df":0,"docs":{},"u":{"c":{"df":7,"docs":{"111":{"tf":1.0},"28":{"tf":1.0},"284":{"tf":1.0},"33":{"tf":1.0},"37":{"tf":1.0},"39":{"tf":1.0},"60":{"tf":1.0}},"t":{"df":9,"docs":{"174":{"tf":1.0},"185":{"tf":1.0},"237":{"tf":1.0},"253":{"tf":1.0},"259":{"tf":1.0},"268":{"tf":1.0},"271":{"tf":1.4142135623730951},"64":{"tf":1.0},"8":{"tf":1.0}}}},"df":0,"docs":{}}},"df":7,"docs":{"144":{"tf":1.0},"145":{"tf":1.0},"146":{"tf":1.0},"153":{"tf":1.0},"168":{"tf":1.0},"181":{"tf":1.0},"211":{"tf":1.0}},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"11":{"tf":1.7320508075688772}}},"t":{"df":2,"docs":{"122":{"tf":1.0},"200":{"tf":1.0}}}}},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"149":{"tf":1.0},"184":{"tf":1.0}},"m":{"df":1,"docs":{"61":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":4,"docs":{"107":{"tf":1.0},"16":{"tf":1.0},"268":{"tf":2.6457513110645907},"7":{"tf":1.4142135623730951}}}}}}},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"'":{"df":2,"docs":{"22":{"tf":1.0},"7":{"tf":1.0}}},"(":{"\"":{":":{"df":0,"docs":{},"f":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":1,"docs":{"224":{"tf":1.0}}}}}},"df":0,"docs":{}},"'":{":":{"df":0,"docs":{},"f":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":1,"docs":{"224":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":35,"docs":{"10":{"tf":1.0},"109":{"tf":1.7320508075688772},"118":{"tf":1.0},"136":{"tf":1.0},"138":{"tf":1.0},"14":{"tf":1.4142135623730951},"16":{"tf":1.7320508075688772},"167":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"18":{"tf":1.4142135623730951},"190":{"tf":1.4142135623730951},"191":{"tf":1.0},"20":{"tf":1.0},"203":{"tf":1.0},"22":{"tf":2.23606797749979},"224":{"tf":1.4142135623730951},"252":{"tf":1.0},"260":{"tf":1.7320508075688772},"263":{"tf":1.0},"278":{"tf":1.0},"3":{"tf":1.4142135623730951},"36":{"tf":1.0},"40":{"tf":1.4142135623730951},"45":{"tf":1.0},"46":{"tf":1.0},"5":{"tf":1.0},"58":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0},"67":{"tf":1.0},"70":{"tf":1.0},"73":{"tf":2.449489742783178},"8":{"tf":1.0},"91":{"tf":1.0}}}},"df":0,"docs":{}}},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":4,"docs":{"267":{"tf":1.4142135623730951},"268":{"tf":1.0},"270":{"tf":1.0},"273":{"tf":2.0}}}},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"117":{"tf":1.4142135623730951}}}}},"n":{"df":0,"docs":{},"e":{"df":2,"docs":{"116":{"tf":1.0},"61":{"tf":1.0}}}},"p":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"249":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"274":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"69":{"tf":1.0}}}},"t":{"df":0,"docs":{},"i":{"df":4,"docs":{"17":{"tf":1.4142135623730951},"22":{"tf":1.7320508075688772},"268":{"tf":1.0},"99":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"s":{"df":4,"docs":{"146":{"tf":1.0},"186":{"tf":1.0},"187":{"tf":1.0},"236":{"tf":1.0}}}},"s":{"df":1,"docs":{"145":{"tf":1.0}}}},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"106":{"tf":1.4142135623730951}}}},"t":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"201":{"tf":1.0},"274":{"tf":2.0}}}},"df":0,"docs":{}},"o":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"f":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":1,"docs":{"106":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}}},"df":4,"docs":{"106":{"tf":2.0},"175":{"tf":1.0},"65":{"tf":1.0},"67":{"tf":1.0}}}}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":4,"docs":{"105":{"tf":1.0},"106":{"tf":1.4142135623730951},"256":{"tf":1.0},"96":{"tf":1.0}}}}},"df":2,"docs":{"105":{"tf":1.0},"106":{"tf":1.0}},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":5,"docs":{"107":{"tf":1.0},"167":{"tf":1.0},"172":{"tf":1.0},"174":{"tf":1.7320508075688772},"268":{"tf":1.0}}}}}}},"v":{"df":0,"docs":{},"i":{"d":{"df":28,"docs":{"103":{"tf":1.4142135623730951},"116":{"tf":1.0},"128":{"tf":1.0},"145":{"tf":1.0},"159":{"tf":1.0},"164":{"tf":1.0},"189":{"tf":1.0},"201":{"tf":1.0},"220":{"tf":1.0},"222":{"tf":1.0},"229":{"tf":1.0},"234":{"tf":1.0},"243":{"tf":1.0},"245":{"tf":1.0},"253":{"tf":1.0},"254":{"tf":1.4142135623730951},"257":{"tf":1.0},"268":{"tf":1.0},"274":{"tf":1.0},"278":{"tf":1.0},"282":{"tf":1.0},"283":{"tf":1.4142135623730951},"37":{"tf":1.0},"45":{"tf":1.0},"62":{"tf":1.4142135623730951},"64":{"tf":1.0},"77":{"tf":1.0},"78":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":1,"docs":{"274":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":1,"docs":{"208":{"tf":1.0}}}}}},"u":{"b":{"df":6,"docs":{"103":{"tf":1.0},"105":{"tf":1.4142135623730951},"111":{"tf":1.0},"114":{"tf":1.0},"69":{"tf":1.4142135623730951},"73":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"c":{"df":11,"docs":{"105":{"tf":1.0},"106":{"tf":1.4142135623730951},"107":{"tf":1.4142135623730951},"108":{"tf":1.0},"12":{"tf":1.0},"2":{"tf":1.0},"252":{"tf":1.0},"274":{"tf":1.0},"283":{"tf":1.0},"78":{"tf":1.0},"97":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"146":{"tf":1.0}}}}},"df":0,"docs":{},"s":{"df":1,"docs":{"170":{"tf":1.0}},"h":{"df":21,"docs":{"11":{"tf":1.0},"112":{"tf":1.0},"113":{"tf":1.4142135623730951},"16":{"tf":1.7320508075688772},"166":{"tf":1.0},"167":{"tf":1.7320508075688772},"169":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":2.0},"189":{"tf":1.0},"20":{"tf":2.0},"22":{"tf":1.7320508075688772},"220":{"tf":1.0},"221":{"tf":1.0},"253":{"tf":1.0},"267":{"tf":1.0},"274":{"tf":2.449489742783178},"278":{"tf":1.0},"37":{"tf":1.0},"69":{"tf":1.0},"70":{"tf":1.0}},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"df":1,"docs":{"20":{"tf":1.0}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":8,"docs":{"167":{"tf":1.0},"25":{"tf":1.0},"274":{"tf":2.0},"280":{"tf":1.0},"59":{"tf":1.0},"64":{"tf":1.0},"7":{"tf":2.23606797749979},"81":{"tf":1.0}}}},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"226":{"tf":1.0}}}},"r":{"df":0,"docs":{},"e":{"df":1,"docs":{"203":{"tf":1.0}}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":7,"docs":{"124":{"tf":1.0},"232":{"tf":1.0},"233":{"tf":1.4142135623730951},"237":{"tf":1.0},"252":{"tf":1.0},"274":{"tf":1.0},"51":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"u":{"df":2,"docs":{"115":{"tf":1.0},"152":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"h":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"94":{"tf":1.0}}}}}}}}}},"df":9,"docs":{"104":{"tf":1.0},"189":{"tf":1.0},"253":{"tf":1.0},"26":{"tf":1.0},"264":{"tf":1.0},"267":{"tf":1.4142135623730951},"274":{"tf":1.7320508075688772},"283":{"tf":1.0},"7":{"tf":1.4142135623730951}}}},"t":{"df":7,"docs":{"122":{"tf":1.0},"17":{"tf":1.0},"59":{"tf":1.4142135623730951},"60":{"tf":1.0},"61":{"tf":1.4142135623730951},"69":{"tf":1.4142135623730951},"79":{"tf":1.0}}}},"w":{"d":{"df":2,"docs":{"31":{"tf":1.0},"36":{"tf":1.0}}},"df":0,"docs":{}},"y":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"=":{"$":{"(":{"df":0,"docs":{},"w":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"11":{"tf":1.0}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"`":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"y":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"11":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"11":{"tf":1.0}}}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"'":{"df":1,"docs":{"11":{"tf":1.0}}},"3":{"=":{"$":{"(":{"b":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":1,"docs":{"11":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"11":{"tf":2.8284271247461903}}},"df":4,"docs":{"11":{"tf":2.449489742783178},"193":{"tf":1.0},"269":{"tf":1.0},"53":{"tf":1.0}}}}}}}},"q":{"1":{"df":1,"docs":{"187":{"tf":1.0}}},"a":{"df":1,"docs":{"190":{"tf":1.0}}},"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":3,"docs":{"63":{"tf":1.0},"64":{"tf":1.0},"8":{"tf":1.0}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":7,"docs":{"154":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0},"205":{"tf":1.4142135623730951},"206":{"tf":1.4142135623730951},"6":{"tf":1.0},"62":{"tf":2.23606797749979}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":5,"docs":{"11":{"tf":1.0},"149":{"tf":1.0},"205":{"tf":1.0},"274":{"tf":1.0},"60":{"tf":1.0}}}}}}}},"i":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"236":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"170":{"tf":1.0}}}},"l":{"df":0,"docs":{},"i":{"df":3,"docs":{"167":{"tf":1.0},"178":{"tf":1.0},"60":{"tf":1.0}}}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"243":{"tf":1.0}}}},"t":{"df":6,"docs":{"220":{"tf":1.0},"226":{"tf":1.0},"229":{"tf":1.0},"62":{"tf":1.4142135623730951},"68":{"tf":1.0},"84":{"tf":1.0}}}},"o":{"df":1,"docs":{"171":{"tf":1.0}}}}},"r":{"a":{"d":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"86":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":1,"docs":{"132":{"tf":1.0}}}},"n":{"df":3,"docs":{"203":{"tf":1.0},"30":{"tf":1.0},"35":{"tf":1.0}}},"p":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"189":{"tf":1.0},"68":{"tf":1.0}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"e":{"df":2,"docs":{"17":{"tf":1.0},"86":{"tf":1.0}}}},"t":{"df":0,"docs":{},"e":{"df":8,"docs":{"205":{"tf":1.4142135623730951},"208":{"tf":1.0},"214":{"tf":1.0},"216":{"tf":2.6457513110645907},"217":{"tf":1.0},"218":{"tf":1.0},"225":{"tf":1.0},"242":{"tf":1.0}}},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":1,"docs":{"104":{"tf":1.0}}}}}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"138":{"tf":1.0}}}}}},"w":{"df":3,"docs":{"128":{"tf":1.0},"79":{"tf":1.0},"83":{"tf":1.0}}}},"c":{"_":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":2,"docs":{"281":{"tf":1.0},"282":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"12":{"tf":1.0}}},"df":2,"docs":{"269":{"tf":1.0},"53":{"tf":1.0}},"e":{"a":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"6":{"tf":1.0}}},"t":{"df":1,"docs":{"233":{"tf":1.0}}}},"d":{"df":9,"docs":{"107":{"tf":1.0},"120":{"tf":1.0},"174":{"tf":1.0},"23":{"tf":1.4142135623730951},"32":{"tf":1.4142135623730951},"62":{"tf":1.4142135623730951},"7":{"tf":1.0},"79":{"tf":1.4142135623730951},"81":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"79":{"tf":1.4142135623730951}}}},"i":{"df":6,"docs":{"118":{"tf":1.0},"184":{"tf":1.0},"227":{"tf":1.0},"24":{"tf":1.4142135623730951},"267":{"tf":1.0},"58":{"tf":1.0}}},"m":{"df":1,"docs":{"281":{"tf":1.0}}}},"df":0,"docs":{},"l":{"df":6,"docs":{"16":{"tf":1.0},"200":{"tf":1.0},"204":{"tf":1.0},"233":{"tf":1.0},"237":{"tf":1.4142135623730951},"273":{"tf":1.0}},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"194":{"tf":1.0},"196":{"tf":1.0}}}},"z":{"df":1,"docs":{"61":{"tf":1.0}}}},"l":{"df":0,"docs":{},"i":{"df":8,"docs":{"116":{"tf":1.0},"129":{"tf":1.0},"169":{"tf":1.0},"40":{"tf":1.0},"58":{"tf":1.4142135623730951},"60":{"tf":1.0},"73":{"tf":1.4142135623730951},"79":{"tf":1.4142135623730951}}}}},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":14,"docs":{"152":{"tf":1.0},"192":{"tf":1.0},"194":{"tf":1.0},"197":{"tf":1.0},"201":{"tf":1.0},"205":{"tf":1.4142135623730951},"208":{"tf":1.4142135623730951},"217":{"tf":1.0},"224":{"tf":1.0},"242":{"tf":1.7320508075688772},"27":{"tf":1.0},"58":{"tf":1.0},"61":{"tf":1.4142135623730951},"7":{"tf":1.0}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"df":1,"docs":{"86":{"tf":1.0}}}}}}}},"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":3,"docs":{"124":{"tf":1.0},"37":{"tf":1.0},"61":{"tf":1.0}}},"df":0,"docs":{},"t":{"df":1,"docs":{"59":{"tf":1.0}}}}}}},"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":3,"docs":{"106":{"tf":1.0},"189":{"tf":1.0},"201":{"tf":1.0}}}},"n":{"df":0,"docs":{},"t":{"df":10,"docs":{"122":{"tf":1.0},"123":{"tf":1.4142135623730951},"170":{"tf":1.0},"193":{"tf":1.4142135623730951},"206":{"tf":1.4142135623730951},"208":{"tf":1.4142135623730951},"209":{"tf":1.4142135623730951},"210":{"tf":1.0},"231":{"tf":1.0},"78":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":4,"docs":{"101":{"tf":1.0},"11":{"tf":1.0},"14":{"tf":1.0},"274":{"tf":1.0}}},"df":0,"docs":{}}}}},"n":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":2,"docs":{"245":{"tf":1.0},"89":{"tf":1.0}},"e":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"249":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"r":{"d":{"df":18,"docs":{"134":{"tf":1.4142135623730951},"135":{"tf":1.0},"136":{"tf":1.4142135623730951},"137":{"tf":1.0},"145":{"tf":1.0},"146":{"tf":1.7320508075688772},"189":{"tf":1.0},"190":{"tf":1.0},"231":{"tf":1.7320508075688772},"237":{"tf":1.0},"245":{"tf":1.0},"246":{"tf":1.7320508075688772},"248":{"tf":2.0},"249":{"tf":4.123105625617661},"250":{"tf":2.6457513110645907},"268":{"tf":1.0},"84":{"tf":1.4142135623730951},"88":{"tf":1.7320508075688772}}},"df":0,"docs":{}},"v":{"df":1,"docs":{"158":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"158":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":5,"docs":{"151":{"tf":1.4142135623730951},"152":{"tf":1.0},"156":{"tf":1.0},"157":{"tf":1.0},"158":{"tf":1.0}}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":1,"docs":{"11":{"tf":1.0}}}}}},"d":{"df":1,"docs":{"109":{"tf":1.0}},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"283":{"tf":1.4142135623730951}}}}}},"df":1,"docs":{"201":{"tf":1.0}}}},"df":0,"docs":{}}}},"u":{"c":{"df":7,"docs":{"142":{"tf":1.0},"143":{"tf":1.0},"145":{"tf":1.4142135623730951},"165":{"tf":1.0},"205":{"tf":1.0},"220":{"tf":1.0},"222":{"tf":1.0}}},"df":0,"docs":{},"n":{"d":{"df":5,"docs":{"145":{"tf":1.0},"146":{"tf":1.0},"263":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0}}},"df":0,"docs":{}}}},"df":14,"docs":{"105":{"tf":1.0},"124":{"tf":1.7320508075688772},"14":{"tf":1.0},"142":{"tf":1.0},"161":{"tf":1.0},"193":{"tf":1.0},"233":{"tf":1.0},"252":{"tf":1.0},"258":{"tf":1.0},"26":{"tf":1.0},"283":{"tf":1.0},"81":{"tf":1.0},"86":{"tf":1.0},"90":{"tf":1.0}},"e":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":1,"docs":{"201":{"tf":1.0}}}}},"df":0,"docs":{}}},"f":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"81":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":2,"docs":{"171":{"tf":1.0},"280":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":14,"docs":{"109":{"tf":1.4142135623730951},"132":{"tf":1.4142135623730951},"167":{"tf":1.4142135623730951},"237":{"tf":1.0},"238":{"tf":1.0},"242":{"tf":1.4142135623730951},"29":{"tf":1.0},"31":{"tf":1.0},"34":{"tf":1.0},"36":{"tf":1.0},"60":{"tf":1.0},"70":{"tf":1.0},"79":{"tf":1.0},"82":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"61":{"tf":1.0}}},"df":0,"docs":{}}}}},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"242":{"tf":1.0},"87":{"tf":1.0},"98":{"tf":1.0}}}},"df":0,"docs":{}}}},"g":{"a":{"df":0,"docs":{},"r":{"d":{"df":4,"docs":{"122":{"tf":1.0},"231":{"tf":1.0},"238":{"tf":1.0},"242":{"tf":1.0}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"233":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"_":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"y":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"64":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":1,"docs":{"64":{"tf":1.0}}}}}},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"283":{"tf":1.4142135623730951}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":3,"docs":{"206":{"tf":1.0},"208":{"tf":1.0},"212":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"r":{"df":2,"docs":{"225":{"tf":1.0},"62":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"64":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"116":{"tf":1.0}}}}}}}}}}},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"176":{"tf":1.0},"180":{"tf":1.4142135623730951},"196":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"l":{"=":{"\"":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"283":{"tf":1.7320508075688772}}}}}}}}}}}}},"df":0,"docs":{}},"a":{"df":0,"docs":{},"t":{"df":13,"docs":{"0":{"tf":1.0},"123":{"tf":1.4142135623730951},"145":{"tf":1.0},"149":{"tf":1.4142135623730951},"20":{"tf":1.0},"205":{"tf":1.0},"226":{"tf":1.0},"26":{"tf":1.0},"278":{"tf":1.0},"282":{"tf":1.0},"283":{"tf":1.0},"48":{"tf":1.4142135623730951},"84":{"tf":1.0}}}},"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":1,"docs":{"272":{"tf":1.0}}}}},"df":7,"docs":{"121":{"tf":1.0},"152":{"tf":1.0},"154":{"tf":1.0},"167":{"tf":1.0},"18":{"tf":1.4142135623730951},"192":{"tf":1.0},"22":{"tf":1.0}},"e":{"a":{"df":0,"docs":{},"s":{"df":26,"docs":{"113":{"tf":1.0},"114":{"tf":1.0},"121":{"tf":1.0},"167":{"tf":2.23606797749979},"178":{"tf":1.0},"179":{"tf":1.4142135623730951},"18":{"tf":1.0},"180":{"tf":1.4142135623730951},"183":{"tf":2.0},"187":{"tf":1.0},"215":{"tf":1.0},"252":{"tf":1.0},"253":{"tf":1.7320508075688772},"26":{"tf":1.4142135623730951},"265":{"tf":1.0},"267":{"tf":2.6457513110645907},"268":{"tf":4.58257569495584},"269":{"tf":2.0},"270":{"tf":2.0},"271":{"tf":1.7320508075688772},"272":{"tf":1.0},"273":{"tf":2.0},"274":{"tf":3.872983346207417},"40":{"tf":1.0},"60":{"tf":1.4142135623730951},"74":{"tf":1.0}},"e":{".":{"df":0,"docs":{},"j":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"271":{"tf":1.0}}}}}},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"269":{"tf":1.4142135623730951}}}}},"_":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":1,"docs":{"268":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"]":{".":{"0":{"a":{"1":{"df":1,"docs":{"268":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":1,"docs":{"268":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"274":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"/":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"119":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":1,"docs":{"266":{"tf":1.0}}}},"v":{"df":6,"docs":{"193":{"tf":1.0},"194":{"tf":1.0},"221":{"tf":1.0},"241":{"tf":1.0},"54":{"tf":1.0},"7":{"tf":1.4142135623730951}}}},"i":{"a":{"b":{"df":0,"docs":{},"l":{"df":2,"docs":{"117":{"tf":1.0},"16":{"tf":1.0}}}},"df":0,"docs":{}},"df":4,"docs":{"124":{"tf":1.0},"128":{"tf":1.0},"278":{"tf":1.0},"64":{"tf":1.0}}}},"m":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":8,"docs":{"103":{"tf":1.4142135623730951},"105":{"tf":1.0},"142":{"tf":1.0},"143":{"tf":1.0},"145":{"tf":1.0},"198":{"tf":1.0},"199":{"tf":1.0},"264":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"b":{"df":2,"docs":{"64":{"tf":1.0},"84":{"tf":1.0}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"t":{"df":21,"docs":{"106":{"tf":1.0},"134":{"tf":1.0},"179":{"tf":1.0},"182":{"tf":1.0},"184":{"tf":1.0},"186":{"tf":1.4142135623730951},"187":{"tf":1.4142135623730951},"188":{"tf":1.0},"190":{"tf":1.4142135623730951},"192":{"tf":1.4142135623730951},"193":{"tf":1.4142135623730951},"194":{"tf":1.4142135623730951},"197":{"tf":1.4142135623730951},"198":{"tf":1.0},"199":{"tf":1.0},"201":{"tf":1.7320508075688772},"231":{"tf":1.0},"242":{"tf":1.0},"249":{"tf":1.4142135623730951},"250":{"tf":1.0},"268":{"tf":1.0}},"e":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"u":{"d":{"df":0,"docs":{},"l":{"df":1,"docs":{"200":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"200":{"tf":2.449489742783178}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"s":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}}}}}},"v":{"df":17,"docs":{"105":{"tf":1.0},"106":{"tf":1.7320508075688772},"108":{"tf":1.0},"109":{"tf":1.4142135623730951},"145":{"tf":2.0},"146":{"tf":1.4142135623730951},"167":{"tf":1.0},"172":{"tf":1.0},"197":{"tf":1.0},"203":{"tf":1.0},"231":{"tf":1.0},"25":{"tf":1.4142135623730951},"26":{"tf":1.0},"264":{"tf":1.0},"268":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0}}}}},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"78":{"tf":1.0}}}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":3,"docs":{"45":{"tf":1.0},"46":{"tf":1.0},"75":{"tf":1.0}}}},"df":0,"docs":{}},"l":{"a":{"c":{"df":14,"docs":{"103":{"tf":1.0},"106":{"tf":1.4142135623730951},"109":{"tf":1.0},"13":{"tf":1.0},"166":{"tf":1.0},"200":{"tf":1.0},"203":{"tf":1.0},"221":{"tf":1.0},"222":{"tf":1.7320508075688772},"231":{"tf":1.4142135623730951},"258":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"70":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{},"i":{"c":{"df":3,"docs":{"140":{"tf":1.0},"145":{"tf":1.0},"61":{"tf":1.0}}},"df":0,"docs":{}}},"o":{"df":19,"docs":{"101":{"tf":1.0},"102":{"tf":1.0},"106":{"tf":1.0},"118":{"tf":1.0},"124":{"tf":1.0},"13":{"tf":1.0},"167":{"tf":2.8284271247461903},"18":{"tf":1.0},"197":{"tf":1.0},"20":{"tf":1.4142135623730951},"200":{"tf":1.4142135623730951},"21":{"tf":1.0},"225":{"tf":1.0},"253":{"tf":1.0},"268":{"tf":1.4142135623730951},"274":{"tf":1.7320508075688772},"49":{"tf":1.0},"69":{"tf":1.0},"75":{"tf":1.0}},"r":{"df":0,"docs":{},"t":{"df":5,"docs":{"1":{"tf":1.4142135623730951},"104":{"tf":1.0},"249":{"tf":1.0},"4":{"tf":1.0},"50":{"tf":1.0}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":37,"docs":{"11":{"tf":1.0},"113":{"tf":1.0},"118":{"tf":1.4142135623730951},"12":{"tf":1.4142135623730951},"124":{"tf":1.0},"13":{"tf":1.0},"16":{"tf":1.0},"167":{"tf":2.23606797749979},"17":{"tf":1.0},"18":{"tf":1.0},"194":{"tf":1.0},"197":{"tf":1.0},"20":{"tf":1.7320508075688772},"200":{"tf":1.4142135623730951},"22":{"tf":1.0},"23":{"tf":1.0},"25":{"tf":1.0},"251":{"tf":1.0},"253":{"tf":1.4142135623730951},"260":{"tf":1.0},"271":{"tf":1.0},"274":{"tf":1.0},"284":{"tf":1.0},"286":{"tf":1.0},"29":{"tf":1.0},"30":{"tf":1.0},"32":{"tf":1.0},"34":{"tf":1.4142135623730951},"35":{"tf":1.0},"37":{"tf":1.0},"46":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.7320508075688772},"68":{"tf":1.0},"69":{"tf":1.4142135623730951},"70":{"tf":1.0},"74":{"tf":1.0}}}}}}}}},"r":{"df":1,"docs":{"268":{"tf":1.0}},"e":{"df":0,"docs":{},"s":{"df":3,"docs":{"106":{"tf":1.4142135623730951},"252":{"tf":1.0},"283":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"214":{"tf":1.0}}}}}}}}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{".":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"a":{"df":1,"docs":{"283":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":1,"docs":{"283":{"tf":1.0}}}}}}}}}}}}}}}},"df":12,"docs":{"122":{"tf":1.0},"189":{"tf":1.7320508075688772},"201":{"tf":1.4142135623730951},"225":{"tf":1.0},"226":{"tf":1.4142135623730951},"233":{"tf":1.4142135623730951},"236":{"tf":1.0},"245":{"tf":1.7320508075688772},"259":{"tf":1.0},"274":{"tf":2.0},"280":{"tf":1.0},"7":{"tf":2.23606797749979}}}}},"i":{"df":0,"docs":{},"r":{"df":50,"docs":{"10":{"tf":1.0},"105":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"11":{"tf":2.23606797749979},"116":{"tf":1.0},"12":{"tf":2.0},"141":{"tf":1.0},"142":{"tf":1.0},"143":{"tf":1.4142135623730951},"145":{"tf":2.23606797749979},"146":{"tf":1.0},"167":{"tf":1.0},"169":{"tf":1.0},"170":{"tf":1.0},"175":{"tf":1.0},"18":{"tf":1.0},"183":{"tf":1.0},"187":{"tf":2.0},"188":{"tf":2.23606797749979},"189":{"tf":3.3166247903554},"190":{"tf":1.7320508075688772},"193":{"tf":1.0},"196":{"tf":1.7320508075688772},"197":{"tf":2.23606797749979},"198":{"tf":1.4142135623730951},"199":{"tf":1.0},"200":{"tf":1.4142135623730951},"201":{"tf":2.449489742783178},"22":{"tf":1.4142135623730951},"220":{"tf":1.0},"222":{"tf":1.4142135623730951},"225":{"tf":1.0},"229":{"tf":1.4142135623730951},"23":{"tf":1.0},"231":{"tf":1.4142135623730951},"233":{"tf":1.0},"241":{"tf":1.0},"261":{"tf":1.4142135623730951},"262":{"tf":1.0},"263":{"tf":1.4142135623730951},"269":{"tf":1.0},"274":{"tf":1.0},"277":{"tf":1.0},"279":{"tf":1.0},"286":{"tf":1.0},"53":{"tf":1.0},"61":{"tf":1.0},"64":{"tf":1.0},"81":{"tf":1.7320508075688772}}}}}},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":1,"docs":{"12":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":7,"docs":{"234":{"tf":1.0},"242":{"tf":1.0},"245":{"tf":1.0},"25":{"tf":2.0},"26":{"tf":1.7320508075688772},"86":{"tf":1.0},"89":{"tf":1.4142135623730951}}}},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"v":{"df":4,"docs":{"118":{"tf":1.0},"22":{"tf":1.0},"40":{"tf":1.0},"6":{"tf":1.0}}}},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"111":{"tf":1.0}}}},"u":{"df":0,"docs":{},"r":{"c":{"df":8,"docs":{"128":{"tf":1.0},"133":{"tf":1.0},"141":{"tf":1.0},"194":{"tf":1.0},"226":{"tf":1.4142135623730951},"252":{"tf":1.0},"64":{"tf":1.0},"73":{"tf":1.0}}},"df":0,"docs":{}}}},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"60":{"tf":1.0},"64":{"tf":1.0}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":16,"docs":{"121":{"tf":1.4142135623730951},"132":{"tf":1.0},"146":{"tf":1.0},"189":{"tf":1.0},"190":{"tf":1.0},"194":{"tf":1.0},"201":{"tf":2.449489742783178},"225":{"tf":2.23606797749979},"226":{"tf":1.7320508075688772},"241":{"tf":1.0},"245":{"tf":1.7320508075688772},"246":{"tf":1.0},"249":{"tf":1.0},"64":{"tf":1.0},"7":{"tf":1.0},"78":{"tf":1.0}}}}}},"t":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"233":{"tf":1.0}}}}},"df":3,"docs":{"201":{"tf":1.4142135623730951},"205":{"tf":1.0},"37":{"tf":1.0}},"o":{"df":0,"docs":{},"r":{"df":5,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"226":{"tf":1.0},"231":{"tf":1.0},"86":{"tf":1.0}}}},"r":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"105":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"<":{"df":0,"docs":{},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"242":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}}}},"df":17,"docs":{"113":{"tf":1.0},"132":{"tf":1.0},"174":{"tf":1.0},"188":{"tf":1.0},"189":{"tf":1.0},"197":{"tf":1.0},"203":{"tf":1.0},"208":{"tf":1.0},"220":{"tf":1.0},"224":{"tf":1.0},"242":{"tf":2.6457513110645907},"252":{"tf":1.0},"266":{"tf":1.0},"283":{"tf":1.0},"58":{"tf":1.0},"7":{"tf":1.4142135623730951},"73":{"tf":1.7320508075688772}}}}}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":6,"docs":{"179":{"tf":1.7320508075688772},"180":{"tf":1.0},"184":{"tf":1.4142135623730951},"185":{"tf":1.4142135623730951},"217":{"tf":1.0},"254":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":9,"docs":{"106":{"tf":1.4142135623730951},"145":{"tf":1.0},"159":{"tf":1.0},"188":{"tf":1.0},"189":{"tf":1.7320508075688772},"229":{"tf":1.0},"233":{"tf":1.0},"249":{"tf":1.4142135623730951},"283":{"tf":2.0}}}}}},"u":{"df":0,"docs":{},"s":{"df":3,"docs":{"199":{"tf":1.0},"225":{"tf":1.0},"242":{"tf":1.0}}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":1,"docs":{"180":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":1,"docs":{"201":{"tf":1.0}}}},"r":{"df":0,"docs":{},"s":{"df":1,"docs":{"104":{"tf":1.0}}},"t":{"df":1,"docs":{"26":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":9,"docs":{"122":{"tf":1.7320508075688772},"143":{"tf":1.0},"146":{"tf":1.0},"150":{"tf":1.0},"189":{"tf":1.0},"259":{"tf":1.0},"274":{"tf":1.0},"7":{"tf":1.4142135623730951},"8":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"r":{"'":{"df":1,"docs":{"7":{"tf":1.0}}},"df":0,"docs":{}}}}},"s":{"df":1,"docs":{"123":{"tf":1.7320508075688772}}}},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"v":{"df":1,"docs":{"261":{"tf":1.0}}}}}}},"f":{"c":{"df":1,"docs":{"185":{"tf":1.0}}},"df":1,"docs":{"279":{"tf":1.0}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"160":{"tf":1.0}}}}}}}},"i":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"106":{"tf":1.0}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":5,"docs":{"126":{"tf":1.7320508075688772},"129":{"tf":1.0},"212":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"k":{"df":3,"docs":{"201":{"tf":1.0},"203":{"tf":1.4142135623730951},"252":{"tf":1.0}}}}},"k":{"df":0,"docs":{},"v":{"df":3,"docs":{"194":{"tf":1.0},"197":{"tf":1.0},"79":{"tf":1.0}}}},"l":{"df":1,"docs":{"200":{"tf":1.0}}},"m":{"df":1,"docs":{"279":{"tf":1.0}}},"o":{"a":{"d":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"192":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":2,"docs":{"223":{"tf":1.0},"45":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"_":{"_":{"_":{"_":{"_":{"_":{"_":{"_":{"df":1,"docs":{"62":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":13,"docs":{"113":{"tf":1.0},"122":{"tf":1.0},"167":{"tf":1.0},"17":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.4142135623730951},"29":{"tf":1.0},"34":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.4142135623730951},"62":{"tf":1.0},"69":{"tf":1.4142135623730951},"70":{"tf":1.0}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"}":{"/":{"$":{"df":0,"docs":{},"{":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"r":{"c":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"}":{"/":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"22":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"t":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"276":{"tf":1.0}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":1,"docs":{"224":{"tf":1.4142135623730951}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"242":{"tf":1.0}}}}}}},"w":{"df":4,"docs":{"156":{"tf":1.0},"249":{"tf":1.0},"62":{"tf":1.0},"82":{"tf":1.4142135623730951}}}},"p":{"0":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"_":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"df":0,"docs":{},"i":{"0":{"df":0,"docs":{},"e":{"df":0,"docs":{},"z":{"_":{"df":0,"docs":{},"j":{"df":0,"docs":{},"q":{"df":0,"docs":{},"s":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"z":{"df":0,"docs":{},"o":{"df":0,"docs":{},"h":{"df":0,"docs":{},"j":{"df":0,"docs":{},"y":{"/":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"185":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"s":{"#":{"4":{"1":{"6":{"df":1,"docs":{"107":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"200":{"tf":2.6457513110645907}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":5,"docs":{"118":{"tf":1.0},"222":{"tf":1.0},"59":{"tf":1.0},"73":{"tf":1.0},"79":{"tf":1.0}}}},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"282":{"tf":1.0}}}}},"n":{"_":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"264":{"tf":1.0}}}}}}}},"df":0,"docs":{}}},"df":78,"docs":{"105":{"tf":1.0},"107":{"tf":1.0},"109":{"tf":1.0},"11":{"tf":2.0},"115":{"tf":1.4142135623730951},"117":{"tf":1.0},"118":{"tf":1.0},"12":{"tf":1.0},"122":{"tf":1.7320508075688772},"124":{"tf":1.4142135623730951},"126":{"tf":1.0},"127":{"tf":1.0},"133":{"tf":1.4142135623730951},"134":{"tf":1.4142135623730951},"14":{"tf":2.0},"154":{"tf":1.0},"155":{"tf":1.0},"17":{"tf":2.0},"170":{"tf":1.0},"176":{"tf":1.4142135623730951},"177":{"tf":2.0},"178":{"tf":1.0},"179":{"tf":2.23606797749979},"180":{"tf":1.7320508075688772},"182":{"tf":1.0},"183":{"tf":2.0},"184":{"tf":1.4142135623730951},"185":{"tf":1.4142135623730951},"19":{"tf":1.0},"190":{"tf":1.0},"194":{"tf":1.4142135623730951},"20":{"tf":1.4142135623730951},"203":{"tf":2.0},"204":{"tf":1.0},"205":{"tf":1.4142135623730951},"208":{"tf":1.0},"209":{"tf":1.0},"214":{"tf":1.4142135623730951},"216":{"tf":1.0},"218":{"tf":1.4142135623730951},"22":{"tf":1.7320508075688772},"223":{"tf":1.4142135623730951},"23":{"tf":1.0},"25":{"tf":1.4142135623730951},"26":{"tf":1.0},"269":{"tf":1.4142135623730951},"27":{"tf":1.4142135623730951},"274":{"tf":3.0},"28":{"tf":1.0},"280":{"tf":1.0},"30":{"tf":1.4142135623730951},"31":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.0},"35":{"tf":1.4142135623730951},"36":{"tf":1.0},"37":{"tf":1.0},"39":{"tf":1.4142135623730951},"40":{"tf":1.4142135623730951},"41":{"tf":1.0},"45":{"tf":1.4142135623730951},"48":{"tf":1.0},"49":{"tf":1.7320508075688772},"51":{"tf":1.4142135623730951},"53":{"tf":1.0},"54":{"tf":1.4142135623730951},"55":{"tf":1.0},"56":{"tf":1.0},"58":{"tf":1.4142135623730951},"60":{"tf":1.7320508075688772},"61":{"tf":1.0},"62":{"tf":1.0},"64":{"tf":1.4142135623730951},"69":{"tf":1.0},"7":{"tf":2.23606797749979},"70":{"tf":1.4142135623730951},"73":{"tf":1.0},"82":{"tf":1.0}},"n":{"a":{"b":{"df":0,"docs":{},"l":{"df":2,"docs":{"43":{"tf":1.0},"45":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":3,"docs":{"222":{"tf":1.0},"224":{"tf":1.0},"45":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"79":{"tf":1.7320508075688772}}}}}},"t":{"&":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{";":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"k":{"df":1,"docs":{"283":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"'":{"df":3,"docs":{"107":{"tf":1.0},"43":{"tf":1.4142135623730951},"92":{"tf":1.0}}},".":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"17":{"tf":1.4142135623730951}},"s":{"=":{"df":0,"docs":{},"x":{"8":{"6":{",":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"x":{"df":1,"docs":{"17":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"/":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":2,"docs":{"14":{"tf":1.0},"73":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"x":{"c":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{".":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":1,"docs":{"34":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"h":{"df":1,"docs":{"73":{"tf":1.0}}},"x":{"c":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{".":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":1,"docs":{"29":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"h":{"df":1,"docs":{"109":{"tf":1.0}}},"x":{"c":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"df":2,"docs":{"109":{"tf":1.0},"14":{"tf":1.0}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"x":{"c":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"df":1,"docs":{"73":{"tf":1.0}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"46":{"tf":1.0},"73":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}}}}},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"/":{"c":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":1,"docs":{"69":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"s":{"df":0,"docs":{},"r":{"c":{"/":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"b":{".":{"df":0,"docs":{},"r":{"df":2,"docs":{"69":{"tf":1.0},"73":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"<":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{">":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{".":{"df":0,"docs":{},"h":{"df":2,"docs":{"103":{"tf":1.4142135623730951},"109":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}},"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"_":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"l":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":2,"docs":{"25":{"tf":1.0},"26":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"x":{"c":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{".":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":1,"docs":{"271":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}}}}}}}},"d":{"df":0,"docs":{},"o":{"c":{"df":2,"docs":{"107":{"tf":1.0},"283":{"tf":1.0}}},"df":0,"docs":{}}},"df":101,"docs":{"0":{"tf":1.7320508075688772},"1":{"tf":1.0},"10":{"tf":1.0},"101":{"tf":1.0},"103":{"tf":2.6457513110645907},"104":{"tf":1.7320508075688772},"105":{"tf":1.7320508075688772},"106":{"tf":1.4142135623730951},"107":{"tf":1.7320508075688772},"108":{"tf":1.7320508075688772},"109":{"tf":1.7320508075688772},"11":{"tf":1.4142135623730951},"110":{"tf":1.0},"111":{"tf":2.0},"114":{"tf":1.4142135623730951},"115":{"tf":2.23606797749979},"116":{"tf":2.0},"117":{"tf":1.7320508075688772},"119":{"tf":1.0},"120":{"tf":1.0},"124":{"tf":1.4142135623730951},"125":{"tf":1.0},"128":{"tf":1.0},"14":{"tf":1.0},"140":{"tf":1.0},"163":{"tf":1.4142135623730951},"164":{"tf":1.0},"167":{"tf":3.1622776601683795},"17":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.7320508075688772},"174":{"tf":1.0},"187":{"tf":1.0},"193":{"tf":2.23606797749979},"194":{"tf":1.4142135623730951},"196":{"tf":1.4142135623730951},"197":{"tf":1.4142135623730951},"200":{"tf":1.4142135623730951},"219":{"tf":2.23606797749979},"220":{"tf":2.23606797749979},"221":{"tf":2.449489742783178},"222":{"tf":1.4142135623730951},"223":{"tf":1.4142135623730951},"224":{"tf":1.4142135623730951},"225":{"tf":2.449489742783178},"226":{"tf":1.7320508075688772},"228":{"tf":2.449489742783178},"23":{"tf":2.0},"24":{"tf":1.0},"241":{"tf":2.23606797749979},"242":{"tf":1.7320508075688772},"25":{"tf":2.0},"251":{"tf":2.0},"252":{"tf":1.7320508075688772},"253":{"tf":2.23606797749979},"256":{"tf":1.0},"257":{"tf":2.23606797749979},"258":{"tf":1.0},"26":{"tf":2.449489742783178},"260":{"tf":2.0},"261":{"tf":2.0},"262":{"tf":1.4142135623730951},"263":{"tf":2.23606797749979},"268":{"tf":1.7320508075688772},"271":{"tf":1.0},"272":{"tf":1.4142135623730951},"274":{"tf":2.8284271247461903},"28":{"tf":1.4142135623730951},"282":{"tf":1.0},"29":{"tf":2.23606797749979},"30":{"tf":1.4142135623730951},"31":{"tf":2.449489742783178},"32":{"tf":2.0},"33":{"tf":1.4142135623730951},"34":{"tf":2.23606797749979},"35":{"tf":1.4142135623730951},"36":{"tf":2.449489742783178},"37":{"tf":1.0},"41":{"tf":1.0},"43":{"tf":2.8284271247461903},"45":{"tf":1.4142135623730951},"46":{"tf":1.4142135623730951},"48":{"tf":1.0},"59":{"tf":1.7320508075688772},"61":{"tf":1.0},"62":{"tf":1.0},"64":{"tf":1.7320508075688772},"68":{"tf":1.0},"69":{"tf":1.7320508075688772},"70":{"tf":1.0},"72":{"tf":1.0},"73":{"tf":1.0},"74":{"tf":2.23606797749979},"75":{"tf":1.7320508075688772},"79":{"tf":1.0},"82":{"tf":1.0},"83":{"tf":1.0},"9":{"tf":1.0},"90":{"tf":2.0},"92":{"tf":1.0},"93":{"tf":2.0}},"j":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"224":{"tf":1.0}}},"df":0,"docs":{}}}}}},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"126":{"tf":1.0}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"x":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"(":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{".":{"d":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":1,"docs":{"126":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"u":{"df":0,"docs":{},"p":{"df":1,"docs":{"11":{"tf":1.0}}}}}}}},"s":{"a":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":4,"docs":{"116":{"tf":1.0},"222":{"tf":1.0},"281":{"tf":1.0},"282":{"tf":1.0}},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"116":{"tf":1.0}}}}}},"m":{"df":0,"docs":{},"e":{"df":32,"docs":{"104":{"tf":1.0},"109":{"tf":1.0},"11":{"tf":1.0},"115":{"tf":1.0},"116":{"tf":1.0},"122":{"tf":1.0},"123":{"tf":1.4142135623730951},"124":{"tf":1.4142135623730951},"129":{"tf":1.0},"145":{"tf":1.0},"165":{"tf":1.0},"167":{"tf":1.0},"175":{"tf":1.4142135623730951},"18":{"tf":1.4142135623730951},"198":{"tf":1.0},"201":{"tf":1.0},"22":{"tf":1.0},"222":{"tf":1.0},"226":{"tf":1.0},"231":{"tf":1.0},"237":{"tf":1.0},"241":{"tf":1.0},"248":{"tf":1.0},"25":{"tf":1.0},"258":{"tf":1.4142135623730951},"26":{"tf":1.7320508075688772},"262":{"tf":1.4142135623730951},"263":{"tf":1.0},"274":{"tf":1.0},"280":{"tf":1.0},"61":{"tf":1.4142135623730951},"84":{"tf":1.0}}},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"49":{"tf":1.0}}}}},"n":{"df":0,"docs":{},"e":{"df":2,"docs":{"241":{"tf":1.0},"86":{"tf":1.0}}},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"51":{"tf":1.0}}}}},"v":{"df":0,"docs":{},"e":{"df":1,"docs":{"286":{"tf":1.0}}}},"w":{"df":2,"docs":{"205":{"tf":1.4142135623730951},"4":{"tf":1.0}}}},"c":{"a":{"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"d":{"df":2,"docs":{"104":{"tf":1.4142135623730951},"105":{"tf":1.0}}},"df":0,"docs":{}}}}},"n":{"df":1,"docs":{"81":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":2,"docs":{"124":{"tf":1.4142135623730951},"19":{"tf":1.0}}}}}},"df":0,"docs":{}}},"h":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":4,"docs":{"189":{"tf":1.0},"226":{"tf":1.0},"242":{"tf":1.0},"274":{"tf":1.0}}}}},"df":0,"docs":{},"m":{"a":{"df":5,"docs":{"189":{"tf":1.0},"268":{"tf":1.0},"78":{"tf":1.0},"79":{"tf":1.0},"81":{"tf":2.8284271247461903}}},"df":0,"docs":{},"e":{":":{"d":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"283":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":1,"docs":{"283":{"tf":1.0}}}}}}}},"df":0,"docs":{}}}},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"37":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"t":{"a":{"b":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"y":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"283":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":4,"docs":{"111":{"tf":1.0},"190":{"tf":1.0},"254":{"tf":1.0},"274":{"tf":1.0}}}}},"r":{"a":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"h":{"df":3,"docs":{"101":{"tf":1.0},"196":{"tf":1.0},"68":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"81":{"tf":1.0}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"_":{"c":{"a":{"df":0,"docs":{},"s":{"df":1,"docs":{"93":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"184":{"tf":1.4142135623730951}},"s":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":3,"docs":{"31":{"tf":1.0},"36":{"tf":1.4142135623730951},"73":{"tf":1.7320508075688772}}}}}}}}},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":28,"docs":{"105":{"tf":1.0},"11":{"tf":1.0},"14":{"tf":1.4142135623730951},"167":{"tf":1.0},"172":{"tf":1.0},"22":{"tf":1.0},"224":{"tf":1.0},"23":{"tf":1.0},"25":{"tf":1.7320508075688772},"252":{"tf":1.0},"253":{"tf":1.0},"26":{"tf":1.7320508075688772},"269":{"tf":1.4142135623730951},"27":{"tf":1.7320508075688772},"279":{"tf":1.0},"28":{"tf":1.0},"282":{"tf":1.0},"30":{"tf":1.7320508075688772},"32":{"tf":1.0},"33":{"tf":1.0},"35":{"tf":1.7320508075688772},"41":{"tf":1.0},"52":{"tf":1.4142135623730951},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"7":{"tf":2.0},"74":{"tf":1.0}},"s":{"/":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"22":{"tf":1.7320508075688772}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":1,"docs":{"64":{"tf":1.0}}}}}}}}},"d":{"df":0,"docs":{},"k":{"df":8,"docs":{"12":{"tf":2.0},"178":{"tf":1.0},"180":{"tf":1.0},"182":{"tf":1.0},"194":{"tf":1.4142135623730951},"259":{"tf":1.0},"274":{"tf":1.4142135623730951},"284":{"tf":1.0}}}},"df":4,"docs":{"11":{"tf":1.0},"14":{"tf":1.0},"274":{"tf":1.0},"93":{"tf":1.0}},"e":{"a":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"h":{"df":7,"docs":{"126":{"tf":1.4142135623730951},"187":{"tf":1.0},"201":{"tf":1.0},"205":{"tf":2.0},"206":{"tf":1.0},"45":{"tf":1.0},"6":{"tf":1.4142135623730951}},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"x":{"df":1,"docs":{"261":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"d":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"60":{"tf":1.0}}}}},"df":4,"docs":{"167":{"tf":1.0},"177":{"tf":1.0},"204":{"tf":2.23606797749979},"6":{"tf":1.7320508075688772}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":4,"docs":{"201":{"tf":1.0},"274":{"tf":1.4142135623730951},"275":{"tf":1.0},"276":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":13,"docs":{"105":{"tf":1.0},"11":{"tf":1.4142135623730951},"12":{"tf":1.0},"200":{"tf":1.0},"201":{"tf":1.0},"209":{"tf":1.0},"210":{"tf":1.0},"214":{"tf":1.0},"227":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"73":{"tf":1.4142135623730951},"85":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"r":{"df":7,"docs":{"141":{"tf":1.4142135623730951},"143":{"tf":1.0},"146":{"tf":1.4142135623730951},"201":{"tf":1.0},"274":{"tf":1.4142135623730951},"63":{"tf":1.0},"64":{"tf":1.0}},"e":{"_":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"264":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"df":26,"docs":{"105":{"tf":1.0},"106":{"tf":1.0},"13":{"tf":1.0},"138":{"tf":1.0},"145":{"tf":1.0},"161":{"tf":1.0},"175":{"tf":1.0},"22":{"tf":1.4142135623730951},"226":{"tf":1.0},"228":{"tf":1.0},"245":{"tf":1.4142135623730951},"261":{"tf":1.0},"264":{"tf":1.0},"266":{"tf":1.0},"268":{"tf":2.0},"274":{"tf":2.0},"280":{"tf":1.0},"38":{"tf":1.0},"40":{"tf":1.0},"48":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"69":{"tf":1.0},"78":{"tf":1.0},"93":{"tf":1.0},"99":{"tf":1.0}},"m":{"df":7,"docs":{"128":{"tf":1.0},"152":{"tf":1.0},"200":{"tf":1.0},"226":{"tf":1.0},"276":{"tf":1.0},"277":{"tf":1.0},"61":{"tf":2.0}}},"n":{"df":1,"docs":{"250":{"tf":1.0}}}},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":9,"docs":{"11":{"tf":1.0},"117":{"tf":1.4142135623730951},"126":{"tf":1.4142135623730951},"209":{"tf":1.0},"253":{"tf":1.0},"280":{"tf":1.0},"62":{"tf":1.4142135623730951},"7":{"tf":1.0},"73":{"tf":1.0}}}},"df":0,"docs":{}},"f":{".":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"_":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"62":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":1,"docs":{"242":{"tf":1.0}}}},"m":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"239":{"tf":1.0}}}}},"df":0,"docs":{}},"n":{"d":{"/":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"c":{"df":1,"docs":{"254":{"tf":1.0}}},"df":0,"docs":{}}}},"df":8,"docs":{"128":{"tf":1.0},"226":{"tf":1.0},"231":{"tf":1.0},"245":{"tf":1.0},"259":{"tf":1.0},"283":{"tf":1.0},"48":{"tf":1.0},"7":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"98":{"tf":1.0}}}}},"t":{"df":4,"docs":{"201":{"tf":1.7320508075688772},"249":{"tf":1.0},"274":{"tf":1.0},"283":{"tf":1.4142135623730951}},"r":{"df":0,"docs":{},"i":{"df":4,"docs":{"150":{"tf":1.0},"154":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0}}},"y":{"/":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"152":{"tf":1.0}}}}}}}}}}}},"df":0,"docs":{}}}}},"p":{"a":{"df":0,"docs":{},"r":{"df":16,"docs":{"103":{"tf":1.0},"106":{"tf":1.0},"116":{"tf":1.0},"140":{"tf":1.0},"163":{"tf":1.0},"165":{"tf":1.0},"167":{"tf":1.0},"169":{"tf":1.0},"17":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.4142135623730951},"172":{"tf":1.7320508075688772},"20":{"tf":1.0},"220":{"tf":1.0},"221":{"tf":1.4142135623730951},"46":{"tf":1.0}}}},"df":0,"docs":{}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"37":{"tf":1.0}},"e":{"<":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"106":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"r":{"d":{"df":1,"docs":{"62":{"tf":1.0}}},"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"105":{"tf":1.0},"106":{"tf":1.0}}}},"df":0,"docs":{}},"v":{"df":8,"docs":{"107":{"tf":1.0},"167":{"tf":1.0},"196":{"tf":1.0},"249":{"tf":1.0},"252":{"tf":1.0},"26":{"tf":1.0},"273":{"tf":1.0},"274":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":24,"docs":{"189":{"tf":2.449489742783178},"190":{"tf":1.0},"194":{"tf":1.0},"201":{"tf":2.449489742783178},"225":{"tf":1.0},"226":{"tf":1.0},"229":{"tf":1.0},"232":{"tf":1.0},"233":{"tf":2.0},"236":{"tf":1.0},"242":{"tf":1.0},"244":{"tf":1.0},"245":{"tf":2.0},"246":{"tf":1.0},"248":{"tf":1.4142135623730951},"249":{"tf":2.23606797749979},"250":{"tf":1.4142135623730951},"254":{"tf":2.8284271247461903},"257":{"tf":1.0},"48":{"tf":1.0},"86":{"tf":1.0},"87":{"tf":1.0},"88":{"tf":1.7320508075688772},"89":{"tf":1.4142135623730951}}}},"i":{"c":{"df":81,"docs":{"0":{"tf":1.4142135623730951},"1":{"tf":1.0},"11":{"tf":1.4142135623730951},"114":{"tf":1.0},"118":{"tf":1.7320508075688772},"119":{"tf":1.0},"12":{"tf":2.0},"120":{"tf":1.0},"122":{"tf":2.0},"123":{"tf":1.0},"124":{"tf":2.0},"125":{"tf":1.4142135623730951},"14":{"tf":1.0},"15":{"tf":1.4142135623730951},"16":{"tf":1.0},"161":{"tf":1.4142135623730951},"162":{"tf":1.0},"164":{"tf":1.0},"165":{"tf":1.4142135623730951},"167":{"tf":3.0},"175":{"tf":1.4142135623730951},"18":{"tf":2.449489742783178},"186":{"tf":1.0},"187":{"tf":1.0},"188":{"tf":1.0},"19":{"tf":1.4142135623730951},"194":{"tf":1.0},"197":{"tf":1.4142135623730951},"2":{"tf":1.0},"20":{"tf":1.7320508075688772},"200":{"tf":2.449489742783178},"203":{"tf":1.0},"21":{"tf":1.4142135623730951},"218":{"tf":1.0},"22":{"tf":2.0},"220":{"tf":1.0},"225":{"tf":2.0},"226":{"tf":1.7320508075688772},"228":{"tf":1.0},"23":{"tf":1.7320508075688772},"232":{"tf":1.0},"236":{"tf":1.0},"24":{"tf":1.0},"244":{"tf":1.0},"25":{"tf":1.7320508075688772},"252":{"tf":1.4142135623730951},"253":{"tf":1.4142135623730951},"254":{"tf":1.0},"259":{"tf":1.4142135623730951},"260":{"tf":1.4142135623730951},"262":{"tf":1.0},"265":{"tf":1.0},"268":{"tf":1.7320508075688772},"274":{"tf":2.0},"276":{"tf":1.0},"28":{"tf":1.0},"283":{"tf":1.7320508075688772},"284":{"tf":1.0},"29":{"tf":1.0},"3":{"tf":1.4142135623730951},"30":{"tf":1.7320508075688772},"32":{"tf":1.4142135623730951},"33":{"tf":1.0},"34":{"tf":1.4142135623730951},"35":{"tf":1.7320508075688772},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.7320508075688772},"40":{"tf":1.4142135623730951},"41":{"tf":1.0},"49":{"tf":1.4142135623730951},"52":{"tf":1.4142135623730951},"54":{"tf":1.4142135623730951},"55":{"tf":1.0},"56":{"tf":1.0},"68":{"tf":1.4142135623730951},"69":{"tf":1.0},"7":{"tf":1.0},"74":{"tf":1.0},"8":{"tf":1.0},"9":{"tf":1.4142135623730951}},"e":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":1,"docs":{"242":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"242":{"tf":1.0}}}}}}},"s":{"%":{"2":{"df":0,"docs":{},"f":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"119":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{}},".":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"=":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"/":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"/":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"/":{"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"/":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"/":{"df":0,"docs":{},"o":{"df":0,"docs":{},"f":{"/":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"18":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"df":2,"docs":{"18":{"tf":1.0},"22":{"tf":2.6457513110645907}}}}},"df":0,"docs":{},"v":{"2":{".":{"b":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{".":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{".":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"k":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"/":{"d":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":1,"docs":{"266":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"y":{"/":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"266":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{".":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"/":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"c":{"%":{"2":{"df":0,"docs":{},"f":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"%":{"2":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{".":{"df":0,"docs":{},"j":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"271":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"/":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"/":{"df":0,"docs":{},"v":{"1":{"1":{"7":{".":{"0":{".":{".":{".":{"df":0,"docs":{},"v":{"1":{"1":{"8":{".":{"0":{"df":1,"docs":{"268":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"200":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"/":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"124":{"tf":1.0}}}}}}}}}}},"s":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"df":1,"docs":{"124":{"tf":1.0}}}},"y":{"df":0,"docs":{},"n":{"c":{"1":{"5":{"df":1,"docs":{"124":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":1,"docs":{"200":{"tf":1.0}}}}}}}}},"v":{"df":0,"docs":{},"i":{"a":{"d":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"124":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"w":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":1,"docs":{"124":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"277":{"tf":1.0}}}}}}}}},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"a":{"df":0,"docs":{},"z":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"d":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":2,"docs":{"29":{"tf":1.0},"34":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"/":{"5":{"3":{"0":{"2":{"df":1,"docs":{"186":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"7":{"4":{"5":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}},"6":{"4":{"df":1,"docs":{"268":{"tf":1.4142135623730951}}},"6":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}},"8":{"4":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":1,"docs":{"242":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"t":{"df":39,"docs":{"108":{"tf":1.7320508075688772},"109":{"tf":1.7320508075688772},"11":{"tf":2.0},"117":{"tf":2.449489742783178},"12":{"tf":2.0},"13":{"tf":1.4142135623730951},"134":{"tf":1.0},"17":{"tf":1.0},"170":{"tf":1.0},"179":{"tf":1.0},"186":{"tf":1.4142135623730951},"187":{"tf":1.4142135623730951},"188":{"tf":1.0},"190":{"tf":1.4142135623730951},"192":{"tf":1.4142135623730951},"193":{"tf":1.4142135623730951},"194":{"tf":1.0},"197":{"tf":1.4142135623730951},"198":{"tf":1.4142135623730951},"199":{"tf":1.0},"201":{"tf":2.0},"208":{"tf":1.7320508075688772},"213":{"tf":1.0},"232":{"tf":1.0},"233":{"tf":1.7320508075688772},"242":{"tf":1.0},"245":{"tf":1.0},"264":{"tf":1.4142135623730951},"268":{"tf":1.0},"277":{"tf":1.4142135623730951},"31":{"tf":1.0},"36":{"tf":1.0},"5":{"tf":1.0},"59":{"tf":1.7320508075688772},"60":{"tf":1.7320508075688772},"61":{"tf":1.0},"7":{"tf":1.0},"73":{"tf":1.0},"9":{"tf":1.0}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"d":{"df":0,"docs":{},"l":{"df":1,"docs":{"22":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"x":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":1,"docs":{"13":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}},"u":{"df":0,"docs":{},"p":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"11":{"tf":1.0}}}}},"df":15,"docs":{"10":{"tf":1.0},"11":{"tf":1.0},"12":{"tf":1.0},"122":{"tf":1.0},"13":{"tf":1.0},"14":{"tf":1.0},"161":{"tf":1.4142135623730951},"163":{"tf":1.4142135623730951},"166":{"tf":1.4142135623730951},"167":{"tf":1.4142135623730951},"169":{"tf":1.0},"175":{"tf":1.7320508075688772},"22":{"tf":1.7320508075688772},"224":{"tf":1.0},"73":{"tf":1.0}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":6,"docs":{"116":{"tf":1.0},"132":{"tf":1.0},"154":{"tf":1.0},"271":{"tf":1.0},"58":{"tf":1.0},"60":{"tf":1.0}}}}}},"h":{"a":{"2":{"5":{"6":{"df":1,"docs":{"280":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":5,"docs":{"105":{"tf":1.0},"106":{"tf":1.0},"142":{"tf":1.0},"283":{"tf":1.0},"58":{"tf":1.0}}}},"r":{"df":0,"docs":{},"e":{"df":13,"docs":{"0":{"tf":1.0},"132":{"tf":1.0},"163":{"tf":1.0},"172":{"tf":1.0},"200":{"tf":2.23606797749979},"221":{"tf":1.0},"222":{"tf":1.4142135623730951},"225":{"tf":1.7320508075688772},"248":{"tf":1.0},"257":{"tf":1.0},"60":{"tf":1.7320508075688772},"79":{"tf":1.0},"81":{"tf":1.0}}}},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"280":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":2,"docs":{"12":{"tf":1.0},"167":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"p":{"df":11,"docs":{"187":{"tf":1.0},"219":{"tf":1.4142135623730951},"220":{"tf":1.0},"221":{"tf":1.0},"251":{"tf":1.4142135623730951},"262":{"tf":1.0},"267":{"tf":1.4142135623730951},"268":{"tf":1.4142135623730951},"273":{"tf":1.0},"64":{"tf":1.4142135623730951},"73":{"tf":1.0}},"p":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"267":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"252":{"tf":1.0}}}}},"df":3,"docs":{"179":{"tf":1.0},"241":{"tf":1.0},"81":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"209":{"tf":1.0}}}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"184":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"l":{"d":{"df":0,"docs":{},"n":{"'":{"df":0,"docs":{},"t":{"df":2,"docs":{"241":{"tf":1.0},"258":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"y":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"_":{"c":{"a":{"df":0,"docs":{},"s":{"df":1,"docs":{"108":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"w":{"df":9,"docs":{"12":{"tf":1.0},"123":{"tf":1.0},"167":{"tf":1.0},"254":{"tf":1.0},"255":{"tf":1.0},"256":{"tf":1.0},"257":{"tf":1.0},"258":{"tf":1.0},"282":{"tf":1.0}}}}},"i":{"d":{"df":0,"docs":{},"e":{"b":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"284":{"tf":1.0}}}},"df":0,"docs":{}},"df":4,"docs":{"12":{"tf":1.4142135623730951},"233":{"tf":1.0},"254":{"tf":1.0},"69":{"tf":1.0}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":6,"docs":{"189":{"tf":1.0},"193":{"tf":1.0},"194":{"tf":1.0},"196":{"tf":1.0},"198":{"tf":1.0},"201":{"tf":2.0}}}}}},"df":3,"docs":{"274":{"tf":2.449489742783178},"283":{"tf":2.449489742783178},"7":{"tf":1.0}},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"c":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"17":{"tf":1.0}}}}}}},"df":6,"docs":{"158":{"tf":1.0},"196":{"tf":1.0},"201":{"tf":1.0},"203":{"tf":1.0},"205":{"tf":1.0},"214":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}}},"l":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":6,"docs":{"161":{"tf":1.0},"162":{"tf":1.0},"163":{"tf":1.0},"169":{"tf":1.0},"170":{"tf":1.0},"175":{"tf":1.0}}}}},"df":0,"docs":{}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"r":{"df":6,"docs":{"143":{"tf":1.0},"158":{"tf":1.0},"163":{"tf":1.0},"171":{"tf":1.0},"200":{"tf":1.0},"81":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"106":{"tf":1.0}}}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"l":{"df":7,"docs":{"106":{"tf":1.4142135623730951},"118":{"tf":1.0},"128":{"tf":1.0},"60":{"tf":1.0},"78":{"tf":1.0},"81":{"tf":1.0},"87":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":7,"docs":{"106":{"tf":1.0},"115":{"tf":1.0},"124":{"tf":1.0},"152":{"tf":1.0},"226":{"tf":1.0},"231":{"tf":1.0},"252":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"129":{"tf":1.0},"159":{"tf":1.0}}}}},"i":{"c":{"df":1,"docs":{"178":{"tf":1.0}}},"df":5,"docs":{"128":{"tf":1.0},"149":{"tf":1.0},"222":{"tf":1.0},"231":{"tf":1.0},"26":{"tf":1.0}},"f":{"df":0,"docs":{},"i":{"df":4,"docs":{"103":{"tf":1.0},"164":{"tf":1.0},"172":{"tf":1.0},"69":{"tf":1.0}}}}}}},"u":{"df":0,"docs":{},"l":{"df":1,"docs":{"175":{"tf":1.0}},"t":{"a":{"df":0,"docs":{},"n":{"df":2,"docs":{"124":{"tf":1.0},"205":{"tf":1.0}}}},"df":0,"docs":{}}}}},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"l":{"df":19,"docs":{"115":{"tf":1.0},"16":{"tf":1.0},"163":{"tf":1.0},"166":{"tf":1.0},"167":{"tf":1.0},"171":{"tf":1.7320508075688772},"172":{"tf":1.4142135623730951},"174":{"tf":1.0},"175":{"tf":1.4142135623730951},"22":{"tf":1.0},"220":{"tf":1.4142135623730951},"221":{"tf":1.0},"225":{"tf":1.4142135623730951},"226":{"tf":1.4142135623730951},"228":{"tf":1.4142135623730951},"232":{"tf":1.4142135623730951},"249":{"tf":1.0},"282":{"tf":1.0},"46":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"130":{"tf":1.4142135623730951},"78":{"tf":1.0}}}}}}}}},"t":{"df":1,"docs":{"40":{"tf":1.0}},"e":{"df":2,"docs":{"205":{"tf":1.4142135623730951},"218":{"tf":1.0}}},"u":{"a":{"df":0,"docs":{},"t":{"df":5,"docs":{"115":{"tf":1.0},"128":{"tf":1.0},"154":{"tf":1.0},"220":{"tf":1.0},"249":{"tf":1.0}}}},"df":0,"docs":{}}},"z":{"df":0,"docs":{},"e":{"df":12,"docs":{"165":{"tf":1.0},"170":{"tf":1.0},"205":{"tf":1.7320508075688772},"206":{"tf":1.4142135623730951},"208":{"tf":2.0},"209":{"tf":1.7320508075688772},"212":{"tf":1.0},"218":{"tf":1.0},"220":{"tf":1.0},"222":{"tf":1.0},"280":{"tf":1.0},"6":{"tf":1.0}}}}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"252":{"tf":1.0}}}}}}},"t":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"167":{"tf":1.0}}}},"df":0,"docs":{}}}},"l":{"a":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"274":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":1,"docs":{"236":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":2,"docs":{"128":{"tf":1.0},"243":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"w":{"df":3,"docs":{"183":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0}}}}},"m":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":6,"docs":{"104":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"170":{"tf":1.0},"227":{"tf":1.0},"229":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"59":{"tf":1.4142135623730951},"6":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"26":{"tf":1.0}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":1,"docs":{"52":{"tf":1.4142135623730951}}}}}},"n":{"a":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"_":{"c":{"a":{"df":0,"docs":{},"s":{"df":1,"docs":{"93":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"$":{"df":0,"docs":{},"n":{"df":1,"docs":{"22":{"tf":1.0}}}},"df":2,"docs":{"22":{"tf":1.0},"268":{"tf":1.0}}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"37":{"tf":1.0}}}}}}}},"o":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":1,"docs":{"274":{"tf":1.0}}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"r":{"df":2,"docs":{"274":{"tf":1.0},"64":{"tf":1.0}}}},"df":0,"docs":{}}}},"l":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"200":{"tf":1.0}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"t":{"df":4,"docs":{"152":{"tf":1.4142135623730951},"187":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0}}}},"v":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"115":{"tf":1.0}}}},"df":0,"docs":{}},"df":1,"docs":{"194":{"tf":1.0}}}},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"i":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":1,"docs":{"22":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"122":{"tf":1.0}}}},"t":{"df":0,"docs":{},"h":{"df":24,"docs":{"103":{"tf":1.0},"106":{"tf":1.0},"117":{"tf":1.0},"124":{"tf":1.0},"197":{"tf":1.4142135623730951},"200":{"tf":1.0},"22":{"tf":1.0},"224":{"tf":1.4142135623730951},"237":{"tf":1.0},"242":{"tf":1.4142135623730951},"26":{"tf":1.0},"277":{"tf":1.0},"280":{"tf":1.0},"283":{"tf":1.0},"43":{"tf":1.0},"58":{"tf":1.4142135623730951},"6":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.4142135623730951},"70":{"tf":1.0},"72":{"tf":1.0},"73":{"tf":1.7320508075688772},"79":{"tf":1.0},"84":{"tf":1.0}}},"i":{"df":0,"docs":{},"m":{"df":3,"docs":{"124":{"tf":1.0},"158":{"tf":1.0},"43":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"129":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}},"w":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"t":{"df":4,"docs":{"115":{"tf":1.0},"116":{"tf":1.0},"235":{"tf":1.0},"261":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"21":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"44":{"tf":1.0},"58":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"6":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"104":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"r":{"c":{"df":19,"docs":{"1":{"tf":1.0},"109":{"tf":1.0},"11":{"tf":1.0},"163":{"tf":1.0},"164":{"tf":1.0},"165":{"tf":1.0},"172":{"tf":1.4142135623730951},"2":{"tf":1.0},"253":{"tf":1.0},"263":{"tf":1.0},"274":{"tf":1.0},"37":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.4142135623730951},"7":{"tf":1.0},"73":{"tf":2.0},"74":{"tf":1.4142135623730951},"81":{"tf":1.0},"98":{"tf":1.7320508075688772}},"e":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"74":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"p":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":1,"docs":{"43":{"tf":1.0}}}},"w":{"df":0,"docs":{},"n":{"df":2,"docs":{"115":{"tf":1.0},"22":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"221":{"tf":1.0},"236":{"tf":1.0}}}},"df":0,"docs":{},"f":{"df":22,"docs":{"103":{"tf":1.0},"105":{"tf":1.0},"116":{"tf":1.0},"12":{"tf":1.0},"123":{"tf":1.0},"128":{"tf":1.0},"175":{"tf":1.0},"190":{"tf":1.0},"200":{"tf":1.0},"214":{"tf":1.0},"22":{"tf":1.4142135623730951},"225":{"tf":1.4142135623730951},"227":{"tf":1.4142135623730951},"229":{"tf":1.0},"235":{"tf":1.0},"279":{"tf":1.0},"280":{"tf":1.0},"282":{"tf":1.0},"45":{"tf":1.0},"58":{"tf":1.0},"70":{"tf":1.0},"72":{"tf":1.0}},"i":{"df":10,"docs":{"123":{"tf":1.7320508075688772},"124":{"tf":1.4142135623730951},"22":{"tf":1.4142135623730951},"222":{"tf":1.0},"236":{"tf":1.0},"245":{"tf":1.0},"252":{"tf":1.4142135623730951},"263":{"tf":1.0},"274":{"tf":1.0},"37":{"tf":1.0}}}}}},"d":{"df":1,"docs":{"17":{"tf":1.0}}},"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"104":{"tf":1.0}}},"df":0,"docs":{},"t":{"df":1,"docs":{"201":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"184":{"tf":1.0}}}}}}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":4,"docs":{"163":{"tf":1.0},"198":{"tf":1.0},"225":{"tf":1.0},"40":{"tf":1.4142135623730951}}}}},"r":{"a":{"df":0,"docs":{},"y":{"df":1,"docs":{"227":{"tf":1.0}}}},"df":0,"docs":{}}},"q":{"df":0,"docs":{},"l":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{":":{":":{"d":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"s":{":":{":":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"_":{"d":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"(":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":1,"docs":{"62":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"(":{"&":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":1,"docs":{"62":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"62":{"tf":1.0}}}}}}}}}},"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":4,"docs":{"11":{"tf":1.7320508075688772},"140":{"tf":1.4142135623730951},"264":{"tf":1.4142135623730951},"67":{"tf":1.0}}}}}}}},"df":3,"docs":{"124":{"tf":1.0},"62":{"tf":2.449489742783178},"81":{"tf":1.7320508075688772}},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"'":{"df":1,"docs":{"81":{"tf":1.0}}},"_":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"_":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"264":{"tf":1.0}}}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}}}}}}},"df":7,"docs":{"140":{"tf":1.0},"149":{"tf":1.0},"264":{"tf":1.4142135623730951},"62":{"tf":1.0},"79":{"tf":1.7320508075688772},"81":{"tf":1.0},"82":{"tf":1.0}}}}}}},"r":{"c":{"/":{"*":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"<":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{">":{".":{"df":0,"docs":{},"u":{"d":{"df":0,"docs":{},"l":{"df":1,"docs":{"104":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"104":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}},"_":{"df":0,"docs":{},"m":{"df":0,"docs":{},"s":{"df":0,"docs":{},"g":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":1,"docs":{"106":{"tf":1.0}}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"_":{"c":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{">":{".":{"df":0,"docs":{},"u":{"d":{"df":0,"docs":{},"l":{"df":1,"docs":{"73":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"60":{"tf":1.7320508075688772}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"b":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"60":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{".":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"<":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{">":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"f":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"106":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"60":{"tf":1.7320508075688772}}}},"df":0,"docs":{}}}}}}},"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":1,"docs":{"224":{"tf":1.0}}}}},"df":5,"docs":{"103":{"tf":2.23606797749979},"60":{"tf":1.0},"61":{"tf":2.23606797749979},"70":{"tf":1.4142135623730951},"72":{"tf":1.0}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"11":{"tf":1.0}}}},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":4,"docs":{"192":{"tf":1.0},"258":{"tf":1.0},"264":{"tf":1.0},"274":{"tf":1.0}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":1,"docs":{"260":{"tf":1.0}}}},"g":{"df":0,"docs":{},"e":{"df":10,"docs":{"131":{"tf":1.0},"248":{"tf":1.0},"249":{"tf":2.449489742783178},"250":{"tf":1.4142135623730951},"267":{"tf":1.0},"271":{"tf":1.4142135623730951},"273":{"tf":1.4142135623730951},"30":{"tf":1.0},"35":{"tf":1.0},"48":{"tf":1.0}}}},"l":{"df":0,"docs":{},"e":{"df":1,"docs":{"263":{"tf":1.0}}}},"n":{"d":{"a":{"df":0,"docs":{},"r":{"d":{"df":3,"docs":{"107":{"tf":1.0},"141":{"tf":1.0},"8":{"tf":1.0}}},"df":0,"docs":{}}},"df":3,"docs":{"198":{"tf":1.0},"220":{"tf":1.0},"237":{"tf":1.0}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"t":{"df":26,"docs":{"101":{"tf":1.4142135623730951},"105":{"tf":1.0},"106":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"121":{"tf":1.0},"122":{"tf":1.0},"138":{"tf":1.0},"149":{"tf":1.0},"158":{"tf":1.0},"214":{"tf":1.0},"22":{"tf":1.0},"225":{"tf":1.4142135623730951},"227":{"tf":1.4142135623730951},"233":{"tf":1.0},"244":{"tf":1.0},"245":{"tf":1.0},"250":{"tf":1.4142135623730951},"257":{"tf":1.0},"268":{"tf":1.0},"269":{"tf":1.0},"37":{"tf":1.0},"58":{"tf":1.4142135623730951},"62":{"tf":1.0},"75":{"tf":1.4142135623730951},"84":{"tf":1.4142135623730951}},"u":{"df":0,"docs":{},"p":{"df":8,"docs":{"134":{"tf":1.0},"140":{"tf":1.0},"176":{"tf":1.0},"180":{"tf":1.0},"184":{"tf":1.0},"242":{"tf":1.0},"278":{"tf":1.0},"283":{"tf":1.7320508075688772}}}}}},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"115":{"tf":1.0}}}},"t":{"df":0,"docs":{},"e":{"df":14,"docs":{"189":{"tf":1.0},"205":{"tf":1.0},"225":{"tf":1.0},"226":{"tf":1.4142135623730951},"229":{"tf":4.242640687119285},"233":{"tf":2.8284271247461903},"236":{"tf":1.4142135623730951},"237":{"tf":1.0},"239":{"tf":1.4142135623730951},"242":{"tf":2.23606797749979},"245":{"tf":2.0},"249":{"tf":2.0},"283":{"tf":2.0},"87":{"tf":1.0}},"f":{"df":0,"docs":{},"x":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"n":{"d":{"a":{"df":1,"docs":{"283":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}}},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":10,"docs":{"136":{"tf":1.0},"137":{"tf":1.0},"140":{"tf":1.0},"149":{"tf":1.0},"161":{"tf":1.0},"177":{"tf":1.0},"187":{"tf":1.0},"203":{"tf":1.0},"205":{"tf":1.0},"62":{"tf":1.0}}}}}}},"i":{"c":{"df":10,"docs":{"167":{"tf":1.0},"242":{"tf":1.0},"251":{"tf":1.0},"278":{"tf":1.0},"28":{"tf":1.0},"280":{"tf":1.0},"282":{"tf":1.0},"284":{"tf":1.0},"33":{"tf":1.0},"93":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"214":{"tf":1.4142135623730951}}}}},"u":{"df":10,"docs":{"139":{"tf":1.0},"148":{"tf":1.0},"160":{"tf":1.0},"171":{"tf":1.0},"176":{"tf":1.0},"186":{"tf":1.0},"202":{"tf":1.0},"22":{"tf":1.0},"242":{"tf":1.0},"257":{"tf":1.4142135623730951}}}}},"d":{":":{":":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{":":{":":{"df":0,"docs":{},"w":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"v":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"(":{"0":{"df":1,"docs":{"117":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":1,"docs":{"62":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"220":{"tf":1.0}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"62":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":28,"docs":{"105":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.4142135623730951},"111":{"tf":1.0},"117":{"tf":1.4142135623730951},"122":{"tf":1.0},"14":{"tf":1.0},"15":{"tf":1.0},"16":{"tf":1.0},"21":{"tf":1.0},"22":{"tf":1.7320508075688772},"25":{"tf":2.6457513110645907},"264":{"tf":1.0},"268":{"tf":1.0},"27":{"tf":1.4142135623730951},"274":{"tf":3.0},"279":{"tf":1.0},"282":{"tf":1.4142135623730951},"29":{"tf":1.4142135623730951},"30":{"tf":1.0},"31":{"tf":1.4142135623730951},"32":{"tf":1.0},"34":{"tf":1.4142135623730951},"35":{"tf":1.0},"36":{"tf":1.4142135623730951},"37":{"tf":1.0},"7":{"tf":1.0},"73":{"tf":1.0}}}},"i":{"c":{"df":0,"docs":{},"k":{"df":3,"docs":{"180":{"tf":1.0},"201":{"tf":1.0},"60":{"tf":1.0}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":18,"docs":{"103":{"tf":1.0},"11":{"tf":1.0},"116":{"tf":1.0},"124":{"tf":1.4142135623730951},"182":{"tf":1.0},"184":{"tf":1.0},"200":{"tf":1.0},"209":{"tf":1.0},"217":{"tf":1.0},"241":{"tf":1.0},"242":{"tf":1.0},"262":{"tf":1.0},"263":{"tf":1.0},"48":{"tf":1.0},"49":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.4142135623730951},"75":{"tf":1.0}}}},"t":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"252":{"tf":1.0}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":2,"docs":{"231":{"tf":1.0},"85":{"tf":1.0}}}}},"r":{"a":{"df":0,"docs":{},"g":{"df":13,"docs":{"123":{"tf":1.0},"124":{"tf":1.4142135623730951},"140":{"tf":1.4142135623730951},"229":{"tf":2.449489742783178},"232":{"tf":1.0},"254":{"tf":1.4142135623730951},"257":{"tf":2.0},"264":{"tf":1.4142135623730951},"273":{"tf":1.0},"284":{"tf":1.0},"78":{"tf":1.4142135623730951},"79":{"tf":1.0},"86":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":18,"docs":{"130":{"tf":1.4142135623730951},"131":{"tf":1.4142135623730951},"184":{"tf":1.0},"187":{"tf":1.0},"229":{"tf":1.0},"231":{"tf":1.0},"232":{"tf":3.4641016151377544},"237":{"tf":1.0},"242":{"tf":3.1622776601683795},"245":{"tf":1.7320508075688772},"248":{"tf":2.0},"254":{"tf":1.4142135623730951},"78":{"tf":2.6457513110645907},"81":{"tf":1.0},"83":{"tf":1.0},"84":{"tf":1.0},"86":{"tf":1.0},"87":{"tf":1.0}},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"i":{"df":1,"docs":{"87":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"i":{"df":2,"docs":{"139":{"tf":1.0},"176":{"tf":1.0}}}}},"r":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"167":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}},"n":{"df":0,"docs":{},"g":{"df":2,"docs":{"158":{"tf":1.0},"221":{"tf":1.0}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":5,"docs":{"219":{"tf":1.4142135623730951},"251":{"tf":1.4142135623730951},"257":{"tf":1.0},"43":{"tf":1.0},"84":{"tf":1.0}}}}}},"w":{"df":2,"docs":{"236":{"tf":1.4142135623730951},"242":{"tf":1.7320508075688772}}}},"df":1,"docs":{"242":{"tf":1.0}},"e":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"257":{"tf":1.0}}}}}}},"df":0,"docs":{}},"i":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"172":{"tf":1.0},"43":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":3,"docs":{"200":{"tf":1.0},"201":{"tf":1.0},"242":{"tf":1.0}}}}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"/":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"189":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":5,"docs":{"116":{"tf":1.0},"242":{"tf":2.23606797749979},"25":{"tf":1.0},"279":{"tf":1.0},"97":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"201":{"tf":1.0}}}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"189":{"tf":1.0}}}}}}},"u":{"c":{"df":0,"docs":{},"t":{"df":8,"docs":{"105":{"tf":1.0},"106":{"tf":1.4142135623730951},"108":{"tf":1.0},"109":{"tf":1.0},"242":{"tf":2.6457513110645907},"249":{"tf":1.0},"93":{"tf":1.0},"94":{"tf":1.0}},"s":{"a":{"c":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"283":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"r":{"df":15,"docs":{"103":{"tf":1.0},"104":{"tf":1.0},"106":{"tf":2.23606797749979},"136":{"tf":1.0},"137":{"tf":1.0},"138":{"tf":1.0},"175":{"tf":1.0},"241":{"tf":1.0},"242":{"tf":2.0},"60":{"tf":1.0},"69":{"tf":1.0},"70":{"tf":1.0},"72":{"tf":1.4142135623730951},"76":{"tf":1.4142135623730951},"83":{"tf":1.0}}}}}},"df":0,"docs":{}}},"u":{"b":{"df":1,"docs":{"200":{"tf":1.0}}},"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"21":{"tf":1.0}}}},"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":7,"docs":{"11":{"tf":1.0},"117":{"tf":1.4142135623730951},"12":{"tf":1.4142135623730951},"126":{"tf":1.4142135623730951},"13":{"tf":1.0},"19":{"tf":1.0},"37":{"tf":1.0}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":3,"docs":{"13":{"tf":1.0},"226":{"tf":1.0},"90":{"tf":1.0}}}}},"y":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":6,"docs":{"103":{"tf":2.449489742783178},"104":{"tf":1.0},"138":{"tf":1.0},"37":{"tf":1.4142135623730951},"93":{"tf":1.0},"99":{"tf":1.0}}}}}},"u":{"b":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":4,"docs":{"252":{"tf":1.7320508075688772},"70":{"tf":1.0},"72":{"tf":1.4142135623730951},"73":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}},"df":3,"docs":{"105":{"tf":1.7320508075688772},"73":{"tf":1.0},"83":{"tf":1.0}},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":1,"docs":{"200":{"tf":1.7320508075688772}}}}},"df":0,"docs":{}}},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"2":{"tf":1.0},"226":{"tf":1.0},"8":{"tf":1.0}}}},"df":0,"docs":{}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"226":{"tf":1.0}}}},"t":{"df":2,"docs":{"226":{"tf":1.0},"7":{"tf":2.449489742783178}}}},"o":{"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":5,"docs":{"11":{"tf":1.0},"167":{"tf":1.7320508075688772},"60":{"tf":1.0},"61":{"tf":1.0},"83":{"tf":1.0}}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"117":{"tf":1.0}}}}}}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"22":{"tf":1.0}}}}}},"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"22":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}}}},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"283":{"tf":1.0}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":2,"docs":{"274":{"tf":1.0},"283":{"tf":1.0}}}},"t":{"df":4,"docs":{"165":{"tf":1.0},"167":{"tf":1.0},"228":{"tf":1.0},"54":{"tf":1.0}}}},"t":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"59":{"tf":1.0}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"22":{"tf":1.4142135623730951},"52":{"tf":1.0}}}}}}},"y":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":1,"docs":{"11":{"tf":1.0}}}}}}}},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":2,"docs":{"108":{"tf":1.0},"109":{"tf":1.0}}}}}},"c":{"c":{"df":0,"docs":{},"e":{"df":3,"docs":{"104":{"tf":1.0},"208":{"tf":1.0},"212":{"tf":1.7320508075688772}},"e":{"d":{"df":1,"docs":{"209":{"tf":1.0}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"s":{"df":8,"docs":{"12":{"tf":1.0},"204":{"tf":1.0},"208":{"tf":1.0},"214":{"tf":1.0},"216":{"tf":2.23606797749979},"217":{"tf":1.0},"266":{"tf":1.0},"271":{"tf":1.4142135623730951}},"f":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":2,"docs":{"106":{"tf":1.0},"11":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{},"h":{"df":23,"docs":{"102":{"tf":1.0},"109":{"tf":1.0},"112":{"tf":1.0},"115":{"tf":1.0},"16":{"tf":1.0},"188":{"tf":1.0},"190":{"tf":1.0},"196":{"tf":1.4142135623730951},"220":{"tf":1.0},"222":{"tf":1.0},"223":{"tf":1.0},"226":{"tf":1.0},"228":{"tf":1.0},"229":{"tf":1.0},"231":{"tf":1.0},"236":{"tf":1.0},"237":{"tf":1.0},"49":{"tf":1.0},"51":{"tf":1.0},"58":{"tf":1.0},"62":{"tf":1.0},"64":{"tf":1.0},"7":{"tf":1.0}}}},"d":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"213":{"tf":1.0}}}}}}},"df":0,"docs":{},"o":{"df":2,"docs":{"11":{"tf":2.23606797749979},"13":{"tf":1.0}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":2,"docs":{"194":{"tf":1.0},"274":{"tf":1.0}}}},"df":0,"docs":{},"x":{"df":1,"docs":{"22":{"tf":1.0}}}}}},"g":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":7,"docs":{"200":{"tf":1.0},"204":{"tf":1.0},"209":{"tf":1.0},"214":{"tf":2.0},"217":{"tf":1.4142135623730951},"225":{"tf":1.0},"268":{"tf":1.7320508075688772}}}}}}},"i":{"df":0,"docs":{},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":8,"docs":{"122":{"tf":1.0},"187":{"tf":1.4142135623730951},"189":{"tf":1.0},"191":{"tf":1.0},"192":{"tf":1.0},"197":{"tf":1.0},"223":{"tf":1.0},"64":{"tf":1.0}}}},"df":0,"docs":{}},"df":9,"docs":{"223":{"tf":1.0},"41":{"tf":1.0},"43":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"48":{"tf":1.0},"49":{"tf":1.4142135623730951},"58":{"tf":1.0},"7":{"tf":1.4142135623730951}}}},"m":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"84":{"tf":1.0}},"i":{"df":2,"docs":{"64":{"tf":1.7320508075688772},"7":{"tf":1.0}}},"y":{".":{"df":0,"docs":{},"m":{"d":{"df":1,"docs":{"284":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"d":{"df":1,"docs":{"90":{"tf":1.0}}},"df":0,"docs":{}}}}},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":4,"docs":{"242":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"78":{"tf":1.0}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"/":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":1,"docs":{"59":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"df":1,"docs":{"62":{"tf":1.4142135623730951}}}},"y":{"df":0,"docs":{},"n":{"c":{"1":{"5":{"df":2,"docs":{"244":{"tf":1.0},"246":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"[":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"200":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":34,"docs":{"103":{"tf":1.0},"104":{"tf":1.0},"116":{"tf":1.0},"124":{"tf":1.4142135623730951},"128":{"tf":1.0},"140":{"tf":1.0},"141":{"tf":1.0},"145":{"tf":1.0},"16":{"tf":1.0},"163":{"tf":1.0},"167":{"tf":1.0},"170":{"tf":1.7320508075688772},"171":{"tf":1.0},"172":{"tf":1.0},"175":{"tf":1.4142135623730951},"18":{"tf":1.0},"189":{"tf":1.0},"190":{"tf":1.0},"193":{"tf":1.0},"194":{"tf":1.0},"196":{"tf":1.0},"200":{"tf":2.23606797749979},"212":{"tf":1.0},"22":{"tf":1.7320508075688772},"223":{"tf":1.0},"231":{"tf":1.0},"242":{"tf":1.4142135623730951},"263":{"tf":1.0},"274":{"tf":1.0},"282":{"tf":1.4142135623730951},"38":{"tf":1.0},"62":{"tf":1.4142135623730951},"79":{"tf":1.0},"81":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"e":{"df":18,"docs":{"102":{"tf":1.0},"104":{"tf":1.0},"106":{"tf":1.0},"11":{"tf":1.7320508075688772},"117":{"tf":1.4142135623730951},"120":{"tf":1.0},"126":{"tf":1.0},"200":{"tf":1.0},"201":{"tf":1.0},"21":{"tf":1.0},"226":{"tf":1.0},"241":{"tf":1.0},"25":{"tf":1.4142135623730951},"26":{"tf":1.0},"270":{"tf":1.0},"36":{"tf":1.0},"7":{"tf":1.0},"73":{"tf":1.4142135623730951}}},"f":{"a":{"c":{"df":3,"docs":{"105":{"tf":1.0},"26":{"tf":1.0},"61":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":2,"docs":{"260":{"tf":1.0},"261":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"282":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"137":{"tf":1.0}}}}},"df":0,"docs":{}}}},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"'":{"df":1,"docs":{"175":{"tf":1.0}}},".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"74":{"tf":1.0}}}}},"/":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":0,"docs":{},"f":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"d":{"df":1,"docs":{"119":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":55,"docs":{"101":{"tf":1.0},"103":{"tf":2.449489742783178},"104":{"tf":1.0},"109":{"tf":3.4641016151377544},"116":{"tf":1.0},"119":{"tf":1.0},"125":{"tf":1.0},"134":{"tf":1.0},"160":{"tf":1.0},"162":{"tf":1.0},"163":{"tf":2.0},"164":{"tf":1.0},"165":{"tf":2.23606797749979},"166":{"tf":1.0},"167":{"tf":3.605551275463989},"169":{"tf":1.7320508075688772},"170":{"tf":2.0},"171":{"tf":2.8284271247461903},"172":{"tf":3.1622776601683795},"174":{"tf":2.449489742783178},"175":{"tf":1.4142135623730951},"196":{"tf":1.0},"219":{"tf":1.4142135623730951},"225":{"tf":1.0},"23":{"tf":2.6457513110645907},"24":{"tf":1.0},"241":{"tf":2.449489742783178},"25":{"tf":1.7320508075688772},"251":{"tf":2.23606797749979},"252":{"tf":1.0},"253":{"tf":3.1622776601683795},"26":{"tf":2.23606797749979},"268":{"tf":1.4142135623730951},"271":{"tf":1.7320508075688772},"272":{"tf":1.4142135623730951},"274":{"tf":2.23606797749979},"28":{"tf":1.0},"29":{"tf":2.0},"30":{"tf":1.4142135623730951},"31":{"tf":3.1622776601683795},"32":{"tf":2.6457513110645907},"33":{"tf":1.0},"34":{"tf":2.23606797749979},"35":{"tf":1.4142135623730951},"36":{"tf":3.1622776601683795},"46":{"tf":2.23606797749979},"66":{"tf":1.4142135623730951},"68":{"tf":1.0},"7":{"tf":1.4142135623730951},"71":{"tf":1.0},"72":{"tf":2.449489742783178},"73":{"tf":2.6457513110645907},"74":{"tf":2.6457513110645907},"95":{"tf":1.0},"96":{"tf":1.4142135623730951}},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"7":{"tf":1.0}}}},"df":0,"docs":{}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":1,"docs":{"7":{"tf":1.0}}}}}}}},"t":{"c":{"df":0,"docs":{},"h":{"df":2,"docs":{"208":{"tf":1.0},"26":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"y":{"df":0,"docs":{},"m":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":3,"docs":{"175":{"tf":1.0},"274":{"tf":1.0},"282":{"tf":1.0}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"k":{"df":1,"docs":{"11":{"tf":1.0}}}}}}},"n":{"c":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"242":{"tf":1.0}}}}}}},"df":0,"docs":{}},"1":{"5":{"_":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"d":{"df":0,"docs":{},"s":{"_":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"_":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"94":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":4,"docs":{"124":{"tf":1.0},"244":{"tf":1.0},"245":{"tf":1.0},"249":{"tf":1.4142135623730951}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"242":{"tf":1.0}}}}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"_":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"83":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"84":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"88":{"tf":1.4142135623730951}}}}}}}},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"83":{"tf":1.0}}}}}}}}},"m":{"a":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"244":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"245":{"tf":1.4142135623730951}}}}}}}}}},"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"75":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"130":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}}}}}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"250":{"tf":1.4142135623730951},"62":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":45,"docs":{"0":{"tf":1.0},"1":{"tf":1.4142135623730951},"145":{"tf":1.0},"156":{"tf":1.0},"206":{"tf":1.4142135623730951},"213":{"tf":1.4142135623730951},"217":{"tf":1.0},"218":{"tf":1.4142135623730951},"219":{"tf":2.23606797749979},"225":{"tf":3.4641016151377544},"226":{"tf":3.4641016151377544},"228":{"tf":2.23606797749979},"229":{"tf":1.7320508075688772},"231":{"tf":2.0},"232":{"tf":2.0},"233":{"tf":2.0},"234":{"tf":1.4142135623730951},"236":{"tf":1.4142135623730951},"241":{"tf":1.7320508075688772},"242":{"tf":3.872983346207417},"243":{"tf":1.7320508075688772},"244":{"tf":2.6457513110645907},"245":{"tf":3.0},"246":{"tf":2.0},"249":{"tf":1.4142135623730951},"250":{"tf":1.7320508075688772},"254":{"tf":3.1622776601683795},"255":{"tf":2.0},"256":{"tf":2.0},"257":{"tf":2.449489742783178},"258":{"tf":1.7320508075688772},"264":{"tf":1.4142135623730951},"284":{"tf":1.0},"48":{"tf":2.449489742783178},"51":{"tf":1.0},"79":{"tf":1.4142135623730951},"80":{"tf":1.4142135623730951},"82":{"tf":1.7320508075688772},"83":{"tf":1.0},"84":{"tf":3.4641016151377544},"85":{"tf":1.0},"86":{"tf":2.0},"87":{"tf":1.0},"88":{"tf":1.7320508075688772},"89":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":4,"docs":{"244":{"tf":2.0},"245":{"tf":2.8284271247461903},"246":{"tf":1.4142135623730951},"247":{"tf":1.0}},"e":{".":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"y":{"_":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":1,"docs":{"246":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":1,"docs":{"97":{"tf":1.0}}}}},"m":{"a":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"g":{"df":3,"docs":{"242":{"tf":1.4142135623730951},"244":{"tf":1.0},"245":{"tf":2.6457513110645907}},"e":{"df":0,"docs":{},"r":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"244":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"p":{"a":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"242":{"tf":1.4142135623730951},"245":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"242":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"242":{"tf":1.0}}}}}}}},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":1,"docs":{"62":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"t":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"104":{"tf":1.0}}}},"df":0,"docs":{},"x":{"df":1,"docs":{"73":{"tf":1.0}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"(":{"df":1,"docs":{"280":{"tf":1.0}}},"df":13,"docs":{"10":{"tf":1.0},"11":{"tf":2.23606797749979},"118":{"tf":1.0},"12":{"tf":1.4142135623730951},"169":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.4142135623730951},"189":{"tf":1.0},"201":{"tf":1.7320508075688772},"37":{"tf":1.4142135623730951},"67":{"tf":1.4142135623730951},"7":{"tf":1.0}}}}}}}},"t":{"a":{"b":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"s":{"a":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"283":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":8,"docs":{"123":{"tf":1.0},"126":{"tf":1.0},"231":{"tf":1.0},"245":{"tf":1.0},"257":{"tf":1.0},"264":{"tf":1.7320508075688772},"283":{"tf":1.0},"48":{"tf":1.0}},"l":{"df":11,"docs":{"248":{"tf":2.6457513110645907},"249":{"tf":3.1622776601683795},"250":{"tf":1.4142135623730951},"257":{"tf":1.0},"284":{"tf":1.0},"62":{"tf":1.4142135623730951},"80":{"tf":1.4142135623730951},"81":{"tf":1.7320508075688772},"82":{"tf":1.7320508075688772},"84":{"tf":1.4142135623730951},"87":{"tf":1.0}},"e":{"df":0,"docs":{},"—":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"d":{"df":1,"docs":{"82":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"245":{"tf":1.0}}}}}}}},"df":0,"docs":{},"g":{"df":7,"docs":{"167":{"tf":1.0},"253":{"tf":1.4142135623730951},"268":{"tf":1.4142135623730951},"269":{"tf":1.0},"274":{"tf":2.23606797749979},"280":{"tf":1.4142135623730951},"32":{"tf":1.0}},"e":{"df":1,"docs":{"167":{"tf":1.0}}}},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"215":{"tf":1.0}}}},"k":{"df":0,"docs":{},"e":{"df":22,"docs":{"10":{"tf":1.0},"102":{"tf":1.0},"104":{"tf":1.0},"115":{"tf":1.0},"12":{"tf":1.0},"121":{"tf":1.0},"187":{"tf":1.0},"20":{"tf":1.0},"200":{"tf":1.0},"204":{"tf":1.0},"205":{"tf":1.0},"206":{"tf":1.7320508075688772},"212":{"tf":1.0},"226":{"tf":1.0},"242":{"tf":1.0},"249":{"tf":1.0},"250":{"tf":1.4142135623730951},"268":{"tf":1.0},"37":{"tf":1.0},"59":{"tf":1.0},"64":{"tf":1.0},"82":{"tf":1.0}},"n":{"df":5,"docs":{"132":{"tf":1.0},"140":{"tf":1.0},"171":{"tf":1.0},"242":{"tf":1.0},"58":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"e":{"df":1,"docs":{"64":{"tf":1.0}}},"k":{"df":3,"docs":{"194":{"tf":1.0},"233":{"tf":1.0},"274":{"tf":1.0}}}},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"201":{"tf":1.0}}}}}},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"274":{"tf":1.0}}}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":1,"docs":{"282":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":23,"docs":{"103":{"tf":1.0},"117":{"tf":1.0},"163":{"tf":1.4142135623730951},"167":{"tf":1.4142135623730951},"17":{"tf":1.0},"172":{"tf":1.0},"174":{"tf":1.0},"208":{"tf":1.4142135623730951},"223":{"tf":1.0},"231":{"tf":1.0},"251":{"tf":1.0},"252":{"tf":1.0},"253":{"tf":1.0},"274":{"tf":1.0},"28":{"tf":1.0},"33":{"tf":1.0},"37":{"tf":1.4142135623730951},"58":{"tf":1.7320508075688772},"59":{"tf":1.0},"60":{"tf":1.0},"69":{"tf":1.0},"73":{"tf":1.7320508075688772},"74":{"tf":1.4142135623730951}}}}}},"s":{"df":0,"docs":{},"k":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":8,"docs":{"119":{"tf":1.0},"266":{"tf":1.0},"271":{"tf":1.0},"272":{"tf":1.0},"273":{"tf":1.4142135623730951},"274":{"tf":3.0},"280":{"tf":1.0},"74":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"r":{"/":{"c":{"df":0,"docs":{},"i":{"/":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"h":{"/":{"df":0,"docs":{},"k":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{".":{"df":0,"docs":{},"y":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":1,"docs":{"280":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"/":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"74":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":10,"docs":{"119":{"tf":1.4142135623730951},"167":{"tf":1.0},"200":{"tf":1.0},"224":{"tf":1.0},"235":{"tf":1.0},"266":{"tf":2.0},"274":{"tf":1.7320508075688772},"280":{"tf":1.4142135623730951},"39":{"tf":1.0},"40":{"tf":2.449489742783178}},"s":{"[":{"\"":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"$":{"df":0,"docs":{},"{":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"f":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"v":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"}":{"$":{"df":0,"docs":{},"{":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"}":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"\"":{"]":{".":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"(":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"(":{"'":{":":{"df":0,"docs":{},"f":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":1,"docs":{"224":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"b":{"d":{"df":2,"docs":{"226":{"tf":1.0},"242":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"c":{"'":{"df":1,"docs":{"274":{"tf":1.0}}},".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"/":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"/":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"/":{"df":0,"docs":{},"v":{"1":{"/":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"k":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{".":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"271":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"119":{"tf":1.0}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}},"t":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"k":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{".":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"266":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"df":1,"docs":{"280":{"tf":1.0}},"l":{"df":1,"docs":{"11":{"tf":1.7320508075688772}},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"11":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"m":{"df":14,"docs":{"1":{"tf":1.0},"119":{"tf":1.0},"122":{"tf":1.7320508075688772},"175":{"tf":1.0},"183":{"tf":1.0},"186":{"tf":1.7320508075688772},"187":{"tf":1.0},"203":{"tf":1.0},"225":{"tf":1.0},"268":{"tf":2.0},"274":{"tf":1.0},"276":{"tf":1.0},"6":{"tf":1.0},"8":{"tf":1.0}}}},"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"c":{"df":3,"docs":{"139":{"tf":1.0},"176":{"tf":1.0},"48":{"tf":1.0}}},"df":0,"docs":{}}}}},"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":3,"docs":{"20":{"tf":1.0},"22":{"tf":1.0},"27":{"tf":1.0}}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":10,"docs":{"150":{"tf":1.0},"203":{"tf":1.0},"210":{"tf":1.0},"214":{"tf":1.0},"219":{"tf":1.0},"226":{"tf":1.7320508075688772},"236":{"tf":1.0},"240":{"tf":1.0},"242":{"tf":1.4142135623730951},"259":{"tf":1.4142135623730951}}}}}}}},"l":{"df":6,"docs":{"18":{"tf":1.0},"214":{"tf":1.0},"252":{"tf":1.0},"37":{"tf":1.4142135623730951},"50":{"tf":1.0},"92":{"tf":1.0}}}},"m":{"df":0,"docs":{},"p":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"264":{"tf":1.4142135623730951}}}}}}},"df":2,"docs":{"250":{"tf":1.0},"81":{"tf":1.0}},"l":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"137":{"tf":1.4142135623730951}},"e":{".":{"df":0,"docs":{},"m":{"d":{"df":1,"docs":{"134":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":2,"docs":{"248":{"tf":1.0},"82":{"tf":1.0}}}}},"df":0,"docs":{}}},"t":{"df":1,"docs":{"225":{"tf":1.0}}}}},"n":{"d":{"df":4,"docs":{"117":{"tf":1.0},"60":{"tf":1.0},"82":{"tf":1.4142135623730951},"83":{"tf":1.0}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"m":{"df":8,"docs":{"104":{"tf":1.4142135623730951},"137":{"tf":1.0},"140":{"tf":1.0},"164":{"tf":1.0},"170":{"tf":1.0},"2":{"tf":1.0},"241":{"tf":1.0},"78":{"tf":1.4142135623730951}},"i":{"df":0,"docs":{},"n":{"df":3,"docs":{"11":{"tf":1.0},"12":{"tf":1.0},"204":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"q":{"df":2,"docs":{"176":{"tf":1.0},"202":{"tf":1.0}}}},"df":0,"docs":{}},"t":{",":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"}":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"61":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},".":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"r":{"c":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":1,"docs":{"224":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}},"/":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"/":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"59":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"60":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"c":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"df":1,"docs":{"60":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"j":{"a":{"df":0,"docs":{},"v":{"a":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"/":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"/":{"<":{"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"_":{"c":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"70":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"r":{"c":{"/":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"b":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"60":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"0":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"60":{"tf":1.4142135623730951}}}},"df":1,"docs":{"60":{"tf":1.0}}},"1":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"60":{"tf":1.0}}}},"df":1,"docs":{"60":{"tf":1.0}}},"2":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"60":{"tf":1.4142135623730951}}}},"df":1,"docs":{"60":{"tf":1.0}}},"_":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"b":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"_":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"c":{"df":0,"docs":{},"r":{"df":1,"docs":{"62":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"73":{"tf":1.0}}}},"df":0,"docs":{}},"df":57,"docs":{"10":{"tf":1.0},"108":{"tf":1.4142135623730951},"109":{"tf":1.0},"11":{"tf":2.0},"118":{"tf":1.4142135623730951},"122":{"tf":1.0},"124":{"tf":1.0},"14":{"tf":1.0},"158":{"tf":1.0},"16":{"tf":1.4142135623730951},"17":{"tf":1.0},"18":{"tf":1.0},"190":{"tf":1.0},"194":{"tf":1.0},"200":{"tf":1.7320508075688772},"22":{"tf":1.0},"223":{"tf":1.4142135623730951},"224":{"tf":1.0},"23":{"tf":1.7320508075688772},"237":{"tf":1.0},"262":{"tf":1.4142135623730951},"267":{"tf":1.0},"270":{"tf":1.0},"273":{"tf":1.0},"274":{"tf":2.6457513110645907},"278":{"tf":1.0},"279":{"tf":1.4142135623730951},"32":{"tf":2.0},"37":{"tf":1.4142135623730951},"38":{"tf":1.0},"39":{"tf":2.23606797749979},"40":{"tf":2.23606797749979},"41":{"tf":2.0},"42":{"tf":1.0},"43":{"tf":2.8284271247461903},"44":{"tf":1.0},"45":{"tf":3.1622776601683795},"46":{"tf":2.449489742783178},"47":{"tf":1.0},"48":{"tf":2.23606797749979},"49":{"tf":2.0},"50":{"tf":1.4142135623730951},"51":{"tf":2.0},"52":{"tf":1.7320508075688772},"54":{"tf":1.7320508075688772},"55":{"tf":1.4142135623730951},"56":{"tf":1.4142135623730951},"57":{"tf":1.4142135623730951},"58":{"tf":2.8284271247461903},"59":{"tf":2.23606797749979},"60":{"tf":4.0},"61":{"tf":2.6457513110645907},"62":{"tf":2.23606797749979},"7":{"tf":3.1622776601683795},"70":{"tf":1.7320508075688772},"73":{"tf":2.449489742783178},"74":{"tf":1.0}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"$":{"df":0,"docs":{},"n":{"df":1,"docs":{"20":{"tf":1.0}}}},"/":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"y":{"c":{"df":0,"docs":{},"r":{"df":1,"docs":{"60":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":1,"docs":{"59":{"tf":1.0}}}},"df":0,"docs":{}}},"y":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"48":{"tf":1.0}}},"df":0,"docs":{}}}}},"3":{"df":2,"docs":{"20":{"tf":1.4142135623730951},"22":{"tf":1.0}}},"df":0,"docs":{}}}},"s":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"41":{"tf":1.0}}}},"r":{"df":1,"docs":{"61":{"tf":1.0}}}},"/":{"*":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"61":{"tf":1.4142135623730951}},"m":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":2,"docs":{"60":{"tf":1.4142135623730951},"61":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"61":{"tf":1.0}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"h":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"k":{"df":2,"docs":{"220":{"tf":1.0},"274":{"tf":1.0}}}},"t":{"'":{"df":7,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"167":{"tf":1.0},"225":{"tf":1.0},"274":{"tf":1.0},"43":{"tf":1.0},"61":{"tf":1.0}}},"df":0,"docs":{},"’":{"df":1,"docs":{"283":{"tf":1.0}}}}},"d":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"v":{"df":2,"docs":{"190":{"tf":1.0},"228":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":2,"docs":{"105":{"tf":1.0},"158":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"e":{"'":{"df":16,"docs":{"19":{"tf":1.0},"194":{"tf":1.0},"201":{"tf":1.0},"226":{"tf":1.4142135623730951},"227":{"tf":1.0},"228":{"tf":1.0},"229":{"tf":1.4142135623730951},"233":{"tf":1.7320508075688772},"236":{"tf":1.0},"242":{"tf":1.7320508075688772},"274":{"tf":1.0},"276":{"tf":1.0},"40":{"tf":1.0},"43":{"tf":1.0},"60":{"tf":1.0},"79":{"tf":1.0}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"201":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"102":{"tf":1.0}}}},"’":{"df":2,"docs":{"189":{"tf":2.8284271247461903},"192":{"tf":1.0}}}}},"y":{"'":{"d":{"df":1,"docs":{"171":{"tf":1.0}}},"df":0,"docs":{},"r":{"df":3,"docs":{"49":{"tf":1.0},"60":{"tf":1.0},"73":{"tf":1.0}}},"v":{"df":1,"docs":{"116":{"tf":1.0}}}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":21,"docs":{"104":{"tf":1.0},"115":{"tf":1.0},"116":{"tf":1.4142135623730951},"13":{"tf":1.0},"175":{"tf":1.0},"201":{"tf":1.0},"225":{"tf":1.0},"226":{"tf":1.7320508075688772},"228":{"tf":1.4142135623730951},"237":{"tf":1.0},"238":{"tf":1.0},"239":{"tf":1.4142135623730951},"240":{"tf":1.4142135623730951},"252":{"tf":1.0},"40":{"tf":1.0},"62":{"tf":1.0},"68":{"tf":1.0},"69":{"tf":1.0},"78":{"tf":1.0},"79":{"tf":1.0},"86":{"tf":1.0}}},"k":{"df":7,"docs":{"116":{"tf":1.0},"274":{"tf":1.0},"58":{"tf":1.0},"76":{"tf":1.0},"77":{"tf":1.0},"78":{"tf":1.0},"87":{"tf":1.0}}}},"r":{"d":{"df":2,"docs":{"63":{"tf":1.0},"64":{"tf":1.7320508075688772}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":1,"docs":{"229":{"tf":1.0}},"i":{"df":1,"docs":{"104":{"tf":1.0}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":3,"docs":{"236":{"tf":1.0},"270":{"tf":1.0},"46":{"tf":1.0}}}}}}}}},"s":{"df":0,"docs":{},"e":{"df":17,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"11":{"tf":1.0},"118":{"tf":1.4142135623730951},"170":{"tf":1.0},"204":{"tf":1.4142135623730951},"220":{"tf":1.0},"249":{"tf":1.0},"25":{"tf":1.0},"258":{"tf":1.4142135623730951},"274":{"tf":1.0},"281":{"tf":1.0},"39":{"tf":1.0},"48":{"tf":1.0},"58":{"tf":1.0},"7":{"tf":1.0},"82":{"tf":1.0}}}},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":6,"docs":{"105":{"tf":1.0},"201":{"tf":1.0},"228":{"tf":1.4142135623730951},"245":{"tf":1.0},"261":{"tf":1.0},"81":{"tf":1.0}},"t":{"df":2,"docs":{"64":{"tf":1.0},"81":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"e":{"a":{"d":{"df":7,"docs":{"115":{"tf":3.0},"116":{"tf":1.0},"190":{"tf":1.4142135623730951},"194":{"tf":1.0},"197":{"tf":1.0},"203":{"tf":1.0},"242":{"tf":1.0}}},"df":0,"docs":{},"t":{"df":2,"docs":{"194":{"tf":1.0},"201":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":3,"docs":{"20":{"tf":1.0},"254":{"tf":1.0},"68":{"tf":1.0}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":8,"docs":{"167":{"tf":1.0},"249":{"tf":1.0},"268":{"tf":1.0},"27":{"tf":1.0},"283":{"tf":1.0},"3":{"tf":1.0},"73":{"tf":1.0},"74":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"45":{"tf":1.0}}}}}}},"w":{"df":0,"docs":{},"n":{"df":1,"docs":{"283":{"tf":1.0}}}}}},"u":{"df":11,"docs":{"172":{"tf":1.0},"205":{"tf":1.0},"209":{"tf":1.0},"221":{"tf":1.0},"227":{"tf":1.0},"228":{"tf":1.0},"250":{"tf":1.0},"263":{"tf":1.0},"44":{"tf":1.0},"46":{"tf":1.0},"84":{"tf":1.0}},"m":{"b":{"df":1,"docs":{"59":{"tf":1.0}}},"df":0,"docs":{}}}},"i":{"df":4,"docs":{"180":{"tf":1.0},"183":{"tf":1.0},"238":{"tf":1.0},"274":{"tf":1.0}},"e":{"df":1,"docs":{"240":{"tf":1.0}}},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"206":{"tf":1.0}}}}}}},"m":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"d":{"_":{"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"145":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":27,"docs":{"10":{"tf":1.4142135623730951},"104":{"tf":1.0},"141":{"tf":1.0},"142":{"tf":1.4142135623730951},"152":{"tf":1.0},"154":{"tf":1.4142135623730951},"155":{"tf":1.4142135623730951},"156":{"tf":1.0},"170":{"tf":1.0},"172":{"tf":1.0},"204":{"tf":1.4142135623730951},"209":{"tf":1.0},"212":{"tf":1.0},"233":{"tf":1.0},"241":{"tf":1.0},"242":{"tf":1.0},"25":{"tf":1.0},"250":{"tf":1.0},"263":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"60":{"tf":1.0},"62":{"tf":1.0},"64":{"tf":1.0},"7":{"tf":1.0},"81":{"tf":1.7320508075688772},"84":{"tf":1.4142135623730951}},"r":{"df":1,"docs":{"226":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":6,"docs":{"22":{"tf":1.0},"242":{"tf":1.0},"248":{"tf":1.0},"249":{"tf":1.7320508075688772},"80":{"tf":1.0},"88":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"p":{"df":5,"docs":{"105":{"tf":1.0},"282":{"tf":1.0},"43":{"tf":1.0},"7":{"tf":1.0},"75":{"tf":1.0}}},"t":{"df":0,"docs":{},"l":{"df":3,"docs":{"280":{"tf":1.0},"39":{"tf":1.0},"62":{"tf":1.0}}}}},"l":{";":{"d":{"df":0,"docs":{},"r":{"df":2,"docs":{"228":{"tf":1.0},"92":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"o":{"/":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":1,"docs":{"254":{"tf":1.0}}}}}}},"_":{"df":0,"docs":{},"j":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"283":{"tf":1.0}}}}}}},"d":{"df":0,"docs":{},"o":{"!":{"(":{")":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"200":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":6,"docs":{"127":{"tf":1.0},"200":{"tf":1.0},"241":{"tf":1.0},"274":{"tf":2.8284271247461903},"46":{"tf":1.0},"48":{"tf":1.7320508075688772}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":10,"docs":{"172":{"tf":1.0},"220":{"tf":1.0},"221":{"tf":1.0},"238":{"tf":1.0},"240":{"tf":1.0},"252":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0},"46":{"tf":1.0},"58":{"tf":1.0}}}}}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":6,"docs":{"226":{"tf":1.0},"245":{"tf":1.4142135623730951},"254":{"tf":1.0},"274":{"tf":1.4142135623730951},"277":{"tf":2.449489742783178},"283":{"tf":1.4142135623730951}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"_":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"l":{"df":1,"docs":{"242":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}}}}}},"m":{"b":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"268":{"tf":1.0},"62":{"tf":1.0}}}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"k":{"df":1,"docs":{"204":{"tf":1.0}}},"l":{"b":{"a":{"df":0,"docs":{},"r":{"_":{"_":{"_":{"_":{"_":{"df":1,"docs":{"62":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"62":{"tf":1.0}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"263":{"tf":1.7320508075688772}}}}},"df":0,"docs":{}}},"df":15,"docs":{"101":{"tf":1.0},"106":{"tf":1.0},"107":{"tf":1.0},"11":{"tf":2.0},"111":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":1.4142135623730951},"165":{"tf":1.0},"187":{"tf":1.4142135623730951},"264":{"tf":1.0},"274":{"tf":1.0},"37":{"tf":1.7320508075688772},"62":{"tf":1.4142135623730951},"64":{"tf":1.0},"90":{"tf":1.0}},"s":{"/":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{".":{"d":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"286":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":1,"docs":{"11":{"tf":1.7320508075688772}}}}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"f":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":1,"docs":{"106":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"u":{"df":0,"docs":{},"p":{"d":{"df":1,"docs":{"122":{"tf":1.0}}},"df":0,"docs":{}}}},";":{"2":{"6":{".":{"0":{".":{"2":{"df":1,"docs":{"13":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"p":{"df":13,"docs":{"105":{"tf":1.0},"123":{"tf":1.4142135623730951},"124":{"tf":1.4142135623730951},"126":{"tf":1.4142135623730951},"200":{"tf":1.4142135623730951},"252":{"tf":1.0},"282":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"40":{"tf":1.0},"73":{"tf":1.0},"84":{"tf":1.0},"98":{"tf":1.4142135623730951}},"i":{"c":{"df":1,"docs":{"90":{"tf":1.0}}},"df":0,"docs":{}}},"u":{"c":{"df":0,"docs":{},"h":{"df":2,"docs":{"201":{"tf":1.0},"3":{"tf":1.0}}}},"df":0,"docs":{}}},"r":{"a":{"c":{"df":0,"docs":{},"k":{"df":10,"docs":{"106":{"tf":1.0},"167":{"tf":1.0},"231":{"tf":1.0},"245":{"tf":1.0},"261":{"tf":1.0},"283":{"tf":1.0},"80":{"tf":1.0},"82":{"tf":1.0},"84":{"tf":1.0},"87":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"1":{"tf":1.0}}}}},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":3,"docs":{"169":{"tf":1.0},"172":{"tf":1.0},"197":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":2,"docs":{"116":{"tf":1.0},"251":{"tf":1.0}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":6,"docs":{"124":{"tf":1.4142135623730951},"244":{"tf":1.4142135623730951},"246":{"tf":1.0},"43":{"tf":1.0},"84":{"tf":1.0},"93":{"tf":1.0}}}},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"79":{"tf":2.449489742783178}}}},"df":0,"docs":{}},"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"149":{"tf":1.0},"150":{"tf":1.0}}}}}},"p":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"245":{"tf":1.0}}}},"df":0,"docs":{}}}},"v":{"df":0,"docs":{},"i":{"df":1,"docs":{"176":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":5,"docs":{"122":{"tf":1.0},"124":{"tf":1.0},"147":{"tf":1.0},"64":{"tf":1.0},"81":{"tf":1.0}}}},"i":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"121":{"tf":1.0}}},"l":{"df":1,"docs":{"104":{"tf":1.0}}}},"c":{"df":0,"docs":{},"k":{"df":3,"docs":{"126":{"tf":1.0},"17":{"tf":1.0},"75":{"tf":1.0}},"i":{"df":4,"docs":{"11":{"tf":1.0},"128":{"tf":1.0},"22":{"tf":1.0},"62":{"tf":1.0}}}}},"df":28,"docs":{"104":{"tf":1.7320508075688772},"106":{"tf":1.0},"107":{"tf":1.0},"108":{"tf":1.4142135623730951},"109":{"tf":1.4142135623730951},"111":{"tf":1.0},"122":{"tf":1.4142135623730951},"126":{"tf":1.0},"158":{"tf":1.0},"170":{"tf":1.0},"197":{"tf":1.0},"20":{"tf":1.0},"22":{"tf":1.4142135623730951},"227":{"tf":1.0},"230":{"tf":1.0},"242":{"tf":1.0},"263":{"tf":1.0},"264":{"tf":1.0},"278":{"tf":1.0},"279":{"tf":1.0},"37":{"tf":1.4142135623730951},"4":{"tf":1.0},"54":{"tf":1.0},"60":{"tf":1.0},"62":{"tf":1.0},"64":{"tf":1.0},"69":{"tf":1.0},"79":{"tf":1.0}},"g":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":13,"docs":{"116":{"tf":1.0},"119":{"tf":1.4142135623730951},"154":{"tf":1.0},"267":{"tf":1.0},"270":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0},"60":{"tf":1.0},"62":{"tf":1.0},"73":{"tf":1.0},"81":{"tf":1.0},"82":{"tf":3.0},"84":{"tf":1.4142135623730951}}}}}},"v":{"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"l":{"df":4,"docs":{"167":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"64":{"tf":1.0}}}},"df":0,"docs":{}}}},"u":{"df":0,"docs":{},"e":{"df":6,"docs":{"126":{"tf":1.0},"133":{"tf":1.0},"258":{"tf":1.0},"264":{"tf":1.0},"46":{"tf":1.0},"61":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"189":{"tf":1.0},"274":{"tf":1.0}}}},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"263":{"tf":1.0}}}}}},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"62":{"tf":1.0}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":2,"docs":{"252":{"tf":1.0},"60":{"tf":1.0}}}}},"w":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"k":{"df":1,"docs":{"37":{"tf":1.0}}}},"df":0,"docs":{}},"o":{"df":8,"docs":{"165":{"tf":1.0},"175":{"tf":1.0},"196":{"tf":1.0},"197":{"tf":1.0},"201":{"tf":1.0},"212":{"tf":1.0},"251":{"tf":1.0},"273":{"tf":1.0}}}},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":1,"docs":{"116":{"tf":1.0}}}}},"df":13,"docs":{"105":{"tf":1.0},"106":{"tf":1.7320508075688772},"151":{"tf":1.4142135623730951},"152":{"tf":1.4142135623730951},"155":{"tf":1.0},"156":{"tf":1.0},"189":{"tf":1.4142135623730951},"268":{"tf":1.0},"283":{"tf":2.0},"43":{"tf":1.0},"62":{"tf":1.0},"93":{"tf":1.0},"96":{"tf":1.0}},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":1,"docs":{"43":{"tf":1.0}}}}}}}}},"i":{"c":{"df":11,"docs":{"10":{"tf":1.0},"103":{"tf":1.0},"224":{"tf":1.4142135623730951},"250":{"tf":1.0},"283":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0},"45":{"tf":1.0},"79":{"tf":1.0},"87":{"tf":1.0},"93":{"tf":1.0}}},"df":0,"docs":{}}}}},"u":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":1,"docs":{"13":{"tf":1.0}}}}}}},"d":{"df":0,"docs":{},"l":{"df":9,"docs":{"104":{"tf":1.7320508075688772},"105":{"tf":2.449489742783178},"106":{"tf":1.4142135623730951},"107":{"tf":1.0},"109":{"tf":1.0},"123":{"tf":1.0},"200":{"tf":1.0},"70":{"tf":1.0},"73":{"tf":1.0}}}},"df":0,"docs":{},"i":{"df":4,"docs":{"180":{"tf":1.0},"226":{"tf":1.4142135623730951},"268":{"tf":1.0},"7":{"tf":1.0}}},"l":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":4,"docs":{"115":{"tf":1.0},"225":{"tf":1.0},"261":{"tf":1.0},"43":{"tf":1.0}}}}}},"n":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"233":{"tf":1.0}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"200":{"tf":1.0}}}}}}}},"df":0,"docs":{}}}},"h":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"184":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"73":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"m":{"df":3,"docs":{"117":{"tf":1.0},"29":{"tf":1.0},"34":{"tf":1.0}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"124":{"tf":1.4142135623730951}}}}}},"v":{"df":1,"docs":{"104":{"tf":1.0}}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"282":{"tf":1.0}}}}},"r":{"df":17,"docs":{"105":{"tf":1.0},"108":{"tf":1.7320508075688772},"109":{"tf":1.7320508075688772},"170":{"tf":1.0},"175":{"tf":1.0},"184":{"tf":1.0},"214":{"tf":1.0},"216":{"tf":1.4142135623730951},"217":{"tf":1.0},"22":{"tf":1.0},"4":{"tf":1.0},"46":{"tf":1.0},"60":{"tf":1.0},"64":{"tf":1.0},"69":{"tf":1.0},"7":{"tf":1.0},"73":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":3,"docs":{"104":{"tf":1.0},"128":{"tf":1.0},"253":{"tf":1.0}}}},"n":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"86":{"tf":1.0}}}}},"df":0,"docs":{}}},"s":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"99":{"tf":1.0}}}}},"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"n":{"d":{"df":10,"docs":{"102":{"tf":1.4142135623730951},"104":{"tf":1.0},"123":{"tf":1.0},"138":{"tf":1.0},"174":{"tf":1.0},"197":{"tf":1.0},"21":{"tf":1.0},"225":{"tf":1.0},"236":{"tf":1.0},"264":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"d":{"df":1,"docs":{"253":{"tf":1.0}}},"df":0,"docs":{}}}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":1,"docs":{"180":{"tf":1.0}}}}}},"u":{"df":1,"docs":{"226":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"37":{"tf":1.0},"64":{"tf":1.0}},"e":{"d":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"58":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"f":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"82":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"62":{"tf":1.0}},"e":{"d":{"_":{"_":{"_":{"_":{"_":{"df":1,"docs":{"62":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":5,"docs":{"104":{"tf":1.0},"117":{"tf":1.0},"210":{"tf":1.0},"58":{"tf":1.7320508075688772},"62":{"tf":1.0}}}}}}}},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"'":{"df":1,"docs":{"104":{"tf":1.0}}},".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":5,"docs":{"103":{"tf":1.0},"108":{"tf":1.0},"70":{"tf":1.0},"72":{"tf":1.0},"73":{"tf":1.0}}}}}}},"_":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"104":{"tf":1.0}}}}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"69":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":29,"docs":{"101":{"tf":1.7320508075688772},"102":{"tf":1.7320508075688772},"103":{"tf":1.7320508075688772},"104":{"tf":3.0},"105":{"tf":2.23606797749979},"106":{"tf":1.7320508075688772},"107":{"tf":1.0},"108":{"tf":2.0},"109":{"tf":2.23606797749979},"111":{"tf":1.7320508075688772},"123":{"tf":1.4142135623730951},"128":{"tf":1.7320508075688772},"130":{"tf":1.0},"131":{"tf":1.4142135623730951},"194":{"tf":1.0},"196":{"tf":1.0},"197":{"tf":1.0},"198":{"tf":1.0},"200":{"tf":2.0},"253":{"tf":1.0},"271":{"tf":1.0},"44":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"69":{"tf":1.4142135623730951},"72":{"tf":1.0},"73":{"tf":1.0},"74":{"tf":1.0},"90":{"tf":1.7320508075688772}}}},"i":{"df":2,"docs":{"237":{"tf":1.0},"257":{"tf":1.0}}}},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"60":{"tf":1.0}}}}}}},"q":{"df":0,"docs":{},"u":{"df":1,"docs":{"22":{"tf":1.0}}}},"t":{"df":9,"docs":{"17":{"tf":1.0},"223":{"tf":1.0},"224":{"tf":1.0},"232":{"tf":1.7320508075688772},"278":{"tf":1.0},"39":{"tf":1.4142135623730951},"42":{"tf":1.0},"43":{"tf":1.0},"60":{"tf":1.0}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":3,"docs":{"223":{"tf":1.0},"224":{"tf":1.4142135623730951},"46":{"tf":1.0}}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":1,"docs":{"61":{"tf":1.0}}}}}}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":7,"docs":{"204":{"tf":1.0},"21":{"tf":1.0},"26":{"tf":1.0},"60":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0},"67":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"k":{"df":6,"docs":{"146":{"tf":1.0},"200":{"tf":1.0},"231":{"tf":1.0},"258":{"tf":1.0},"60":{"tf":1.0},"64":{"tf":1.0}}}},"o":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"142":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"q":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":1,"docs":{"58":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"224":{"tf":1.0}}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":1,"docs":{"124":{"tf":1.0}}}},"df":0,"docs":{}},"i":{"df":1,"docs":{"233":{"tf":1.0}}}},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"v":{"df":1,"docs":{"92":{"tf":1.0}}}}}}}},"s":{"a":{"df":0,"docs":{},"f":{"df":2,"docs":{"117":{"tf":1.0},"64":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"117":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":11,"docs":{"119":{"tf":1.0},"12":{"tf":1.0},"167":{"tf":1.0},"179":{"tf":1.0},"180":{"tf":1.0},"184":{"tf":1.0},"197":{"tf":1.0},"212":{"tf":1.0},"226":{"tf":1.0},"40":{"tf":1.0},"7":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"s":{"df":1,"docs":{"149":{"tf":1.0}},"u":{"df":2,"docs":{"252":{"tf":1.0},"64":{"tf":1.0}}}}},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"116":{"tf":1.0}}}}},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":3,"docs":{"11":{"tf":1.4142135623730951},"29":{"tf":1.7320508075688772},"34":{"tf":1.7320508075688772}}}}}},"p":{"d":{"a":{"df":0,"docs":{},"t":{"df":35,"docs":{"104":{"tf":1.0},"108":{"tf":1.0},"11":{"tf":1.4142135623730951},"119":{"tf":1.0},"120":{"tf":1.0},"121":{"tf":1.0},"122":{"tf":1.7320508075688772},"123":{"tf":1.0},"124":{"tf":1.0},"134":{"tf":1.0},"139":{"tf":1.0},"145":{"tf":2.0},"146":{"tf":1.0},"193":{"tf":1.0},"197":{"tf":1.0},"198":{"tf":1.0},"200":{"tf":1.4142135623730951},"233":{"tf":1.0},"246":{"tf":1.0},"249":{"tf":1.7320508075688772},"25":{"tf":1.7320508075688772},"250":{"tf":1.4142135623730951},"261":{"tf":1.0},"268":{"tf":2.8284271247461903},"269":{"tf":1.0},"278":{"tf":1.0},"279":{"tf":2.23606797749979},"280":{"tf":2.0},"282":{"tf":1.0},"64":{"tf":1.0},"69":{"tf":1.0},"73":{"tf":1.0},"75":{"tf":1.0},"82":{"tf":1.7320508075688772},"84":{"tf":1.0}}}},"df":0,"docs":{}},"df":40,"docs":{"103":{"tf":1.0},"108":{"tf":1.4142135623730951},"109":{"tf":1.4142135623730951},"117":{"tf":1.0},"121":{"tf":1.0},"122":{"tf":1.0},"124":{"tf":1.4142135623730951},"13":{"tf":1.0},"158":{"tf":1.0},"166":{"tf":1.0},"17":{"tf":1.0},"170":{"tf":1.0},"197":{"tf":1.0},"199":{"tf":1.0},"21":{"tf":1.0},"214":{"tf":1.4142135623730951},"22":{"tf":1.0},"225":{"tf":1.4142135623730951},"233":{"tf":1.0},"240":{"tf":1.0},"242":{"tf":1.0},"255":{"tf":1.0},"264":{"tf":1.0},"272":{"tf":1.0},"278":{"tf":1.0},"30":{"tf":1.0},"35":{"tf":1.0},"40":{"tf":1.4142135623730951},"5":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"6":{"tf":1.0},"60":{"tf":1.4142135623730951},"61":{"tf":1.0},"62":{"tf":1.0},"70":{"tf":1.0},"72":{"tf":1.0},"82":{"tf":1.0},"83":{"tf":1.0},"9":{"tf":1.0}},"g":{"df":0,"docs":{},"r":{"a":{"d":{"df":7,"docs":{"261":{"tf":1.0},"278":{"tf":1.0},"279":{"tf":1.0},"282":{"tf":1.4142135623730951},"78":{"tf":1.0},"79":{"tf":1.0},"81":{"tf":2.23606797749979}}},"df":0,"docs":{}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":1,"docs":{"270":{"tf":1.7320508075688772}}}}},"o":{"a":{"d":{"df":4,"docs":{"249":{"tf":1.0},"250":{"tf":1.4142135623730951},"274":{"tf":1.7320508075688772},"88":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"c":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"c":{"a":{"df":0,"docs":{},"s":{"df":4,"docs":{"93":{"tf":1.0},"96":{"tf":1.0},"98":{"tf":1.0},"99":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"m":{"df":3,"docs":{"16":{"tf":1.0},"197":{"tf":1.0},"49":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"6":{"tf":1.0}}}}}},"l":{"df":9,"docs":{"205":{"tf":1.0},"242":{"tf":1.0},"253":{"tf":1.0},"268":{"tf":1.4142135623730951},"280":{"tf":1.0},"29":{"tf":1.4142135623730951},"34":{"tf":1.0},"62":{"tf":1.0},"82":{"tf":1.0}}}},"s":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"159":{"tf":1.0}}}},"df":0,"docs":{},"g":{"df":4,"docs":{"115":{"tf":1.0},"138":{"tf":1.0},"213":{"tf":1.0},"264":{"tf":1.7320508075688772}}}},"b":{"df":1,"docs":{"126":{"tf":1.0}}},"df":132,"docs":{"0":{"tf":1.4142135623730951},"101":{"tf":1.4142135623730951},"104":{"tf":1.7320508075688772},"105":{"tf":1.7320508075688772},"106":{"tf":2.23606797749979},"107":{"tf":1.0},"108":{"tf":1.7320508075688772},"109":{"tf":1.0},"11":{"tf":2.23606797749979},"111":{"tf":1.4142135623730951},"115":{"tf":2.23606797749979},"116":{"tf":2.8284271247461903},"117":{"tf":1.4142135623730951},"118":{"tf":1.4142135623730951},"119":{"tf":1.7320508075688772},"12":{"tf":1.4142135623730951},"120":{"tf":1.0},"124":{"tf":1.4142135623730951},"125":{"tf":1.0},"126":{"tf":1.7320508075688772},"128":{"tf":1.7320508075688772},"129":{"tf":1.4142135623730951},"13":{"tf":1.0},"132":{"tf":1.4142135623730951},"134":{"tf":1.4142135623730951},"135":{"tf":1.0},"140":{"tf":1.4142135623730951},"145":{"tf":1.4142135623730951},"146":{"tf":1.0},"157":{"tf":1.0},"16":{"tf":1.4142135623730951},"162":{"tf":1.0},"163":{"tf":1.0},"165":{"tf":1.0},"166":{"tf":1.0},"167":{"tf":1.4142135623730951},"169":{"tf":1.0},"17":{"tf":1.4142135623730951},"170":{"tf":2.0},"171":{"tf":1.0},"174":{"tf":1.7320508075688772},"175":{"tf":2.23606797749979},"18":{"tf":1.0},"187":{"tf":1.0},"189":{"tf":1.0},"19":{"tf":1.4142135623730951},"190":{"tf":1.7320508075688772},"192":{"tf":1.4142135623730951},"193":{"tf":1.4142135623730951},"194":{"tf":1.7320508075688772},"196":{"tf":1.0},"197":{"tf":3.0},"198":{"tf":2.449489742783178},"20":{"tf":1.4142135623730951},"200":{"tf":2.23606797749979},"201":{"tf":1.4142135623730951},"205":{"tf":1.0},"21":{"tf":1.0},"213":{"tf":1.7320508075688772},"219":{"tf":1.4142135623730951},"22":{"tf":2.23606797749979},"220":{"tf":1.0},"221":{"tf":1.0},"222":{"tf":1.4142135623730951},"228":{"tf":1.7320508075688772},"229":{"tf":1.0},"23":{"tf":1.0},"231":{"tf":1.7320508075688772},"232":{"tf":1.7320508075688772},"233":{"tf":1.0},"237":{"tf":1.7320508075688772},"242":{"tf":1.4142135623730951},"245":{"tf":2.23606797749979},"249":{"tf":1.0},"25":{"tf":1.4142135623730951},"250":{"tf":1.0},"252":{"tf":1.4142135623730951},"253":{"tf":1.0},"254":{"tf":1.7320508075688772},"256":{"tf":1.0},"257":{"tf":2.0},"258":{"tf":1.7320508075688772},"259":{"tf":1.0},"26":{"tf":2.0},"260":{"tf":1.4142135623730951},"261":{"tf":1.4142135623730951},"262":{"tf":1.4142135623730951},"264":{"tf":2.0},"266":{"tf":1.0},"268":{"tf":1.0},"27":{"tf":1.4142135623730951},"271":{"tf":1.4142135623730951},"273":{"tf":1.0},"274":{"tf":3.0},"278":{"tf":1.0},"280":{"tf":1.4142135623730951},"283":{"tf":2.0},"284":{"tf":1.0},"32":{"tf":1.0},"37":{"tf":2.6457513110645907},"38":{"tf":1.0},"41":{"tf":1.0},"43":{"tf":2.0},"44":{"tf":1.0},"45":{"tf":1.4142135623730951},"46":{"tf":1.4142135623730951},"49":{"tf":1.0},"50":{"tf":1.0},"52":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"58":{"tf":2.0},"6":{"tf":1.0},"60":{"tf":1.7320508075688772},"61":{"tf":2.23606797749979},"62":{"tf":1.7320508075688772},"63":{"tf":1.0},"64":{"tf":2.8284271247461903},"69":{"tf":2.449489742783178},"7":{"tf":1.0},"73":{"tf":2.23606797749979},"78":{"tf":1.7320508075688772},"79":{"tf":1.4142135623730951},"80":{"tf":1.0},"82":{"tf":1.4142135623730951},"84":{"tf":1.7320508075688772},"86":{"tf":1.0},"87":{"tf":1.0},"90":{"tf":1.0},"96":{"tf":1.0},"99":{"tf":2.23606797749979}},"e":{"df":0,"docs":{},"r":{"'":{"df":5,"docs":{"159":{"tf":1.0},"177":{"tf":1.4142135623730951},"183":{"tf":1.0},"184":{"tf":1.0},"233":{"tf":1.0}}},"_":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":2,"docs":{"264":{"tf":1.0},"81":{"tf":1.0}}}}}}},"df":40,"docs":{"149":{"tf":1.0},"150":{"tf":1.0},"154":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0},"157":{"tf":1.0},"158":{"tf":1.7320508075688772},"159":{"tf":1.0},"179":{"tf":1.0},"180":{"tf":1.0},"182":{"tf":1.0},"183":{"tf":1.0},"184":{"tf":1.4142135623730951},"203":{"tf":1.0},"204":{"tf":2.0},"205":{"tf":3.0},"206":{"tf":1.7320508075688772},"207":{"tf":1.0},"208":{"tf":1.4142135623730951},"209":{"tf":2.0},"210":{"tf":2.0},"212":{"tf":2.0},"213":{"tf":2.449489742783178},"214":{"tf":2.0},"215":{"tf":2.0},"216":{"tf":2.23606797749979},"217":{"tf":1.4142135623730951},"218":{"tf":1.4142135623730951},"225":{"tf":1.0},"226":{"tf":1.4142135623730951},"232":{"tf":1.4142135623730951},"233":{"tf":2.6457513110645907},"242":{"tf":2.0},"245":{"tf":1.0},"254":{"tf":1.0},"255":{"tf":1.0},"283":{"tf":2.23606797749979},"52":{"tf":1.0},"64":{"tf":1.0},"82":{"tf":1.0}},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"13":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"’":{"df":7,"docs":{"203":{"tf":1.0},"205":{"tf":1.0},"206":{"tf":1.4142135623730951},"208":{"tf":1.4142135623730951},"209":{"tf":1.0},"213":{"tf":1.0},"283":{"tf":1.7320508075688772}}}}},"u":{"a":{"df":0,"docs":{},"l":{"df":5,"docs":{"117":{"tf":1.0},"18":{"tf":1.0},"21":{"tf":1.0},"279":{"tf":1.0},"280":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":5,"docs":{"126":{"tf":1.0},"237":{"tf":2.0},"60":{"tf":1.7320508075688772},"62":{"tf":1.4142135623730951},"79":{"tf":1.0}},"s":{".":{"df":0,"docs":{},"r":{"df":2,"docs":{"237":{"tf":1.0},"60":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"v":{"1":{"1":{"7":{".":{"0":{"df":1,"docs":{"268":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"8":{".":{"0":{"df":1,"docs":{"268":{"tf":2.0}}},"df":0,"docs":{}},"df":1,"docs":{"268":{"tf":1.0}}},"9":{".":{"0":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"2":{".":{"0":{"df":1,"docs":{"2":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"[":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"]":{".":{"0":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":1,"docs":{"268":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"]":{".":{"0":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"20":{"tf":1.0}},"i":{"d":{"df":13,"docs":{"104":{"tf":1.0},"140":{"tf":1.4142135623730951},"141":{"tf":1.0},"145":{"tf":2.0},"146":{"tf":1.4142135623730951},"147":{"tf":1.0},"150":{"tf":1.0},"154":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0},"189":{"tf":1.4142135623730951},"190":{"tf":1.0},"64":{"tf":1.0}}},"df":0,"docs":{}},"u":{"df":6,"docs":{"18":{"tf":1.0},"264":{"tf":1.7320508075688772},"274":{"tf":1.0},"280":{"tf":1.0},"62":{"tf":1.0},"84":{"tf":1.4142135623730951}}}},"r":{"df":2,"docs":{"12":{"tf":1.0},"97":{"tf":1.0}},"i":{"a":{"b":{"df":0,"docs":{},"l":{"df":8,"docs":{"11":{"tf":1.4142135623730951},"115":{"tf":1.0},"12":{"tf":1.0},"14":{"tf":1.0},"277":{"tf":1.4142135623730951},"37":{"tf":1.4142135623730951},"93":{"tf":1.4142135623730951},"99":{"tf":1.0}}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":3,"docs":{"104":{"tf":1.0},"108":{"tf":1.0},"93":{"tf":1.0}}}}},"df":2,"docs":{"105":{"tf":1.0},"264":{"tf":1.0}},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"63":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"u":{"df":11,"docs":{"106":{"tf":1.0},"169":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"22":{"tf":1.0},"223":{"tf":1.4142135623730951},"225":{"tf":1.0},"226":{"tf":1.0},"229":{"tf":1.4142135623730951},"253":{"tf":1.0},"263":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"115":{"tf":1.0}}}}}}},"df":0,"docs":{},"e":{"c":{"<":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"242":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"242":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"z":{"df":0,"docs":{},"e":{"d":{"/":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"m":{"df":1,"docs":{"282":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":6,"docs":{"120":{"tf":1.4142135623730951},"121":{"tf":2.0},"123":{"tf":1.0},"124":{"tf":2.23606797749979},"274":{"tf":1.4142135623730951},"64":{"tf":1.4142135623730951}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"122":{"tf":1.0}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"r":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":1,"docs":{"126":{"tf":1.0}}}}},"df":0,"docs":{},"i":{"df":14,"docs":{"107":{"tf":1.0},"115":{"tf":1.0},"158":{"tf":1.0},"165":{"tf":1.0},"192":{"tf":1.0},"200":{"tf":1.0},"215":{"tf":1.0},"22":{"tf":1.0},"225":{"tf":1.0},"230":{"tf":1.0},"245":{"tf":1.0},"260":{"tf":1.0},"60":{"tf":1.0},"81":{"tf":1.0}},"f":{"df":2,"docs":{"196":{"tf":1.0},"198":{"tf":1.0}},"i":{"df":5,"docs":{"189":{"tf":1.0},"193":{"tf":1.0},"194":{"tf":1.0},"48":{"tf":1.0},"49":{"tf":1.0}}}},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"22":{"tf":1.0}}}}},"s":{"a":{"df":1,"docs":{"79":{"tf":1.0}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":4,"docs":{"20":{"tf":1.0},"268":{"tf":2.0},"269":{"tf":1.0},"270":{"tf":1.0}}}}}},"df":53,"docs":{"104":{"tf":1.0},"11":{"tf":1.4142135623730951},"12":{"tf":2.0},"121":{"tf":1.0},"123":{"tf":1.4142135623730951},"124":{"tf":1.4142135623730951},"13":{"tf":1.0},"138":{"tf":1.0},"165":{"tf":1.4142135623730951},"167":{"tf":1.0},"169":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"175":{"tf":1.0},"18":{"tf":1.0},"189":{"tf":1.0},"190":{"tf":1.0},"192":{"tf":1.0},"197":{"tf":1.0},"20":{"tf":2.0},"201":{"tf":1.7320508075688772},"205":{"tf":1.0},"219":{"tf":1.0},"22":{"tf":1.7320508075688772},"222":{"tf":1.0},"25":{"tf":2.0},"253":{"tf":1.0},"26":{"tf":1.7320508075688772},"260":{"tf":1.7320508075688772},"261":{"tf":2.0},"262":{"tf":2.23606797749979},"263":{"tf":3.0},"268":{"tf":2.449489742783178},"269":{"tf":1.0},"270":{"tf":1.4142135623730951},"271":{"tf":1.0},"274":{"tf":1.0},"278":{"tf":1.4142135623730951},"279":{"tf":2.23606797749979},"280":{"tf":1.0},"281":{"tf":1.0},"282":{"tf":1.0},"37":{"tf":2.23606797749979},"45":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"62":{"tf":1.0},"64":{"tf":1.4142135623730951},"69":{"tf":1.4142135623730951},"81":{"tf":2.0},"86":{"tf":1.0}}}}},"u":{"df":1,"docs":{"88":{"tf":1.0}}}}},"t":{"df":1,"docs":{"122":{"tf":1.4142135623730951}}}},"i":{"a":{"d":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":4,"docs":{"124":{"tf":1.0},"197":{"tf":1.0},"200":{"tf":1.7320508075688772},"51":{"tf":1.0}}}},"df":0,"docs":{}}},"df":29,"docs":{"106":{"tf":1.0},"11":{"tf":1.4142135623730951},"13":{"tf":1.0},"161":{"tf":1.0},"167":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"18":{"tf":1.0},"189":{"tf":1.4142135623730951},"190":{"tf":1.0},"192":{"tf":1.0},"194":{"tf":1.4142135623730951},"196":{"tf":1.0},"198":{"tf":1.0},"20":{"tf":1.0},"200":{"tf":1.0},"220":{"tf":1.0},"228":{"tf":1.0},"259":{"tf":1.0},"269":{"tf":1.0},"274":{"tf":2.23606797749979},"277":{"tf":1.0},"283":{"tf":1.0},"37":{"tf":1.0},"43":{"tf":1.0},"45":{"tf":1.0},"49":{"tf":1.0},"51":{"tf":1.0},"81":{"tf":1.0}}},"c":{"df":0,"docs":{},"e":{"df":1,"docs":{"79":{"tf":1.0}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"df":1,"docs":{"174":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":5,"docs":{"107":{"tf":1.0},"126":{"tf":1.7320508075688772},"200":{"tf":1.0},"283":{"tf":1.0},"40":{"tf":1.4142135623730951}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":14,"docs":{"134":{"tf":1.0},"202":{"tf":1.0},"204":{"tf":2.449489742783178},"205":{"tf":1.0},"206":{"tf":1.4142135623730951},"207":{"tf":2.0},"208":{"tf":1.4142135623730951},"210":{"tf":2.23606797749979},"213":{"tf":1.7320508075688772},"214":{"tf":2.449489742783178},"215":{"tf":1.7320508075688772},"216":{"tf":2.6457513110645907},"217":{"tf":2.6457513110645907},"218":{"tf":1.0}}}}},"t":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"87":{"tf":1.0}}}},"df":0,"docs":{}},"v":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"138":{"tf":1.0}}},"df":0,"docs":{}}}},"s":{"df":2,"docs":{"116":{"tf":1.0},"232":{"tf":2.0}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"201":{"tf":1.0},"63":{"tf":1.0}}}}}}},"x":{"df":0,"docs":{},"x":{"df":0,"docs":{},"x":{"df":3,"docs":{"267":{"tf":1.0},"269":{"tf":1.0},"270":{"tf":1.0}}}}}},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":6,"docs":{"118":{"tf":1.0},"152":{"tf":1.0},"183":{"tf":1.0},"245":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0}}}},"l":{"_":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"264":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"264":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":2,"docs":{"264":{"tf":1.0},"79":{"tf":1.0}}},"n":{"df":0,"docs":{},"t":{"df":27,"docs":{"117":{"tf":1.4142135623730951},"12":{"tf":1.0},"121":{"tf":1.0},"124":{"tf":1.0},"136":{"tf":1.0},"140":{"tf":1.4142135623730951},"145":{"tf":1.0},"149":{"tf":1.0},"169":{"tf":1.0},"17":{"tf":1.0},"170":{"tf":1.4142135623730951},"171":{"tf":1.4142135623730951},"172":{"tf":1.0},"19":{"tf":1.0},"196":{"tf":1.0},"23":{"tf":1.0},"241":{"tf":1.0},"242":{"tf":1.4142135623730951},"247":{"tf":1.0},"264":{"tf":1.0},"270":{"tf":1.4142135623730951},"281":{"tf":1.0},"32":{"tf":1.0},"58":{"tf":1.4142135623730951},"60":{"tf":1.0},"62":{"tf":1.0},"75":{"tf":1.0}}}},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"64":{"tf":1.0}}},"n":{"df":2,"docs":{"262":{"tf":1.4142135623730951},"92":{"tf":1.4142135623730951}}}},"y":{"df":34,"docs":{"101":{"tf":1.0},"109":{"tf":1.0},"117":{"tf":1.0},"129":{"tf":1.0},"140":{"tf":1.0},"149":{"tf":1.0},"150":{"tf":1.0},"158":{"tf":1.0},"159":{"tf":1.0},"167":{"tf":1.0},"179":{"tf":1.0},"18":{"tf":1.0},"198":{"tf":1.0},"201":{"tf":1.0},"209":{"tf":1.0},"220":{"tf":1.7320508075688772},"222":{"tf":1.0},"226":{"tf":1.4142135623730951},"229":{"tf":1.7320508075688772},"242":{"tf":1.0},"247":{"tf":1.0},"249":{"tf":1.0},"26":{"tf":1.0},"263":{"tf":1.0},"274":{"tf":1.7320508075688772},"280":{"tf":1.0},"38":{"tf":1.0},"46":{"tf":1.4142135623730951},"52":{"tf":1.0},"58":{"tf":1.4142135623730951},"60":{"tf":1.0},"61":{"tf":2.0},"81":{"tf":1.0},"90":{"tf":1.0}}}},"df":0,"docs":{},"e":{"'":{"d":{"df":5,"docs":{"116":{"tf":1.4142135623730951},"171":{"tf":1.0},"225":{"tf":1.0},"58":{"tf":1.4142135623730951},"82":{"tf":1.0}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":4,"docs":{"229":{"tf":1.0},"242":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0}}}},"r":{"df":8,"docs":{"105":{"tf":1.0},"152":{"tf":1.0},"166":{"tf":1.0},"248":{"tf":1.0},"252":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"63":{"tf":1.0}}},"v":{"df":5,"docs":{"101":{"tf":1.4142135623730951},"225":{"tf":1.0},"231":{"tf":1.0},"63":{"tf":1.0},"88":{"tf":1.0}}}},"a":{"df":0,"docs":{},"k":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":1,"docs":{"245":{"tf":1.0}}}}}}},"b":{"df":2,"docs":{"257":{"tf":1.4142135623730951},"283":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":2,"docs":{"264":{"tf":1.0},"79":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":5,"docs":{"123":{"tf":1.0},"124":{"tf":1.0},"264":{"tf":1.4142135623730951},"78":{"tf":1.4142135623730951},"79":{"tf":1.0}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"l":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":4,"docs":{"107":{"tf":1.0},"3":{"tf":1.0},"7":{"tf":1.0},"75":{"tf":1.0}}}}},"df":0,"docs":{},"l":{"df":12,"docs":{"105":{"tf":1.4142135623730951},"111":{"tf":1.0},"167":{"tf":1.0},"18":{"tf":1.0},"199":{"tf":1.0},"252":{"tf":1.0},"257":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"79":{"tf":1.0}}}},"’":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":1,"docs":{"214":{"tf":1.0}}}},"r":{"df":1,"docs":{"217":{"tf":1.0}}}}},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"11":{"tf":1.0}}}}},"h":{"a":{"df":0,"docs":{},"t":{"'":{"df":3,"docs":{"233":{"tf":1.0},"268":{"tf":1.4142135623730951},"89":{"tf":1.0}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":4,"docs":{"233":{"tf":1.0},"250":{"tf":1.0},"274":{"tf":1.0},"81":{"tf":1.0}}}},"’":{"df":3,"docs":{"86":{"tf":1.0},"87":{"tf":1.0},"89":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":3,"docs":{"121":{"tf":1.0},"267":{"tf":1.0},"276":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"e":{"a":{"df":2,"docs":{"79":{"tf":1.0},"86":{"tf":1.0}}},"df":0,"docs":{},"v":{"df":1,"docs":{"79":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":7,"docs":{"104":{"tf":1.0},"167":{"tf":1.4142135623730951},"229":{"tf":1.0},"236":{"tf":1.0},"64":{"tf":1.0},"70":{"tf":1.0},"73":{"tf":1.4142135623730951}}}}}}},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":3,"docs":{"170":{"tf":1.0},"171":{"tf":1.0},"248":{"tf":1.0}}}}}},"i":{"d":{"df":0,"docs":{},"e":{"df":1,"docs":{"64":{"tf":1.0}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":3,"docs":{"225":{"tf":1.0},"226":{"tf":1.0},"229":{"tf":1.0}}}},"l":{"df":0,"docs":{},"l":{"df":2,"docs":{"105":{"tf":1.0},"115":{"tf":1.0}}}},"n":{"d":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":8,"docs":{"11":{"tf":2.0},"117":{"tf":1.0},"12":{"tf":1.4142135623730951},"13":{"tf":2.0},"19":{"tf":1.7320508075688772},"201":{"tf":1.0},"278":{"tf":1.0},"280":{"tf":1.0}},"s":{"/":{"df":0,"docs":{},"w":{"df":0,"docs":{},"s":{"df":0,"docs":{},"l":{"df":1,"docs":{"19":{"tf":1.0}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"e":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"242":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":6,"docs":{"158":{"tf":1.0},"226":{"tf":1.0},"231":{"tf":1.7320508075688772},"234":{"tf":1.7320508075688772},"242":{"tf":1.7320508075688772},"89":{"tf":1.7320508075688772}}}},"s":{"d":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":1,"docs":{"128":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"h":{"df":1,"docs":{"200":{"tf":1.0}}}},"t":{"df":0,"docs":{},"h":{"/":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"59":{"tf":1.0}}}}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"166":{"tf":1.0}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":21,"docs":{"105":{"tf":1.0},"165":{"tf":1.0},"167":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"183":{"tf":1.4142135623730951},"187":{"tf":1.0},"209":{"tf":1.0},"212":{"tf":1.0},"22":{"tf":1.0},"227":{"tf":1.0},"228":{"tf":1.0},"231":{"tf":1.0},"238":{"tf":1.4142135623730951},"242":{"tf":1.0},"257":{"tf":1.0},"262":{"tf":1.0},"274":{"tf":1.4142135623730951},"58":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"n":{"'":{"df":0,"docs":{},"t":{"df":2,"docs":{"132":{"tf":1.0},"37":{"tf":1.0}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"82":{"tf":1.0}}}}},"df":0,"docs":{},"’":{"df":0,"docs":{},"t":{"df":1,"docs":{"212":{"tf":1.0}}}}},"r":{"d":{"df":3,"docs":{"227":{"tf":1.0},"257":{"tf":1.0},"99":{"tf":1.0}}},"df":0,"docs":{},"k":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"115":{"tf":1.0}}},"df":0,"docs":{}}}}}},"df":52,"docs":{"103":{"tf":1.0},"105":{"tf":1.4142135623730951},"107":{"tf":1.0},"11":{"tf":1.4142135623730951},"115":{"tf":1.0},"116":{"tf":1.4142135623730951},"117":{"tf":1.0},"122":{"tf":1.0},"13":{"tf":1.0},"141":{"tf":1.0},"146":{"tf":1.0},"150":{"tf":1.0},"16":{"tf":1.0},"166":{"tf":1.0},"169":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"174":{"tf":1.0},"187":{"tf":1.4142135623730951},"19":{"tf":1.4142135623730951},"190":{"tf":1.0},"197":{"tf":1.0},"219":{"tf":1.4142135623730951},"22":{"tf":1.4142135623730951},"224":{"tf":1.7320508075688772},"225":{"tf":1.0},"228":{"tf":1.0},"229":{"tf":1.0},"231":{"tf":1.0},"238":{"tf":1.4142135623730951},"239":{"tf":1.0},"243":{"tf":1.0},"246":{"tf":1.0},"263":{"tf":1.0},"264":{"tf":1.0},"274":{"tf":1.0},"37":{"tf":2.0},"39":{"tf":1.0},"41":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"49":{"tf":1.0},"6":{"tf":1.4142135623730951},"60":{"tf":1.0},"61":{"tf":1.0},"7":{"tf":1.4142135623730951},"70":{"tf":1.0},"73":{"tf":1.0},"79":{"tf":1.0},"88":{"tf":1.0},"9":{"tf":1.0},"90":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"280":{"tf":1.0}}}},"f":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":6,"docs":{"12":{"tf":1.0},"16":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":1.4142135623730951},"20":{"tf":1.7320508075688772},"22":{"tf":2.23606797749979}}}}}},"s":{"df":0,"docs":{},"p":{"a":{"c":{"df":4,"docs":{"105":{"tf":1.0},"58":{"tf":1.0},"69":{"tf":1.0},"70":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":3,"docs":{"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0}}}}}},"l":{"d":{"df":5,"docs":{"122":{"tf":1.0},"16":{"tf":1.0},"229":{"tf":1.0},"242":{"tf":1.0},"58":{"tf":1.0}}},"df":0,"docs":{}},"s":{"df":3,"docs":{"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0}}},"t":{"df":0,"docs":{},"h":{"df":2,"docs":{"201":{"tf":1.0},"208":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"l":{"d":{"df":0,"docs":{},"n":{"'":{"df":0,"docs":{},"t":{"df":1,"docs":{"116":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"r":{"a":{"df":0,"docs":{},"p":{"df":4,"docs":{"0":{"tf":1.0},"103":{"tf":1.4142135623730951},"108":{"tf":1.0},"109":{"tf":1.0}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":11,"docs":{"103":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.4142135623730951},"172":{"tf":1.0},"221":{"tf":1.0},"222":{"tf":1.0},"281":{"tf":1.0},"282":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"68":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":17,"docs":{"104":{"tf":1.0},"107":{"tf":1.4142135623730951},"116":{"tf":1.0},"118":{"tf":1.0},"125":{"tf":1.4142135623730951},"158":{"tf":1.0},"196":{"tf":2.0},"233":{"tf":1.4142135623730951},"249":{"tf":1.0},"263":{"tf":1.0},"43":{"tf":1.4142135623730951},"64":{"tf":1.0},"7":{"tf":1.0},"70":{"tf":1.0},"79":{"tf":1.0},"81":{"tf":1.4142135623730951},"90":{"tf":1.0}},"r":{"df":1,"docs":{"79":{"tf":1.7320508075688772}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":22,"docs":{"0":{"tf":1.0},"101":{"tf":1.0},"103":{"tf":2.8284271247461903},"104":{"tf":2.0},"106":{"tf":1.0},"108":{"tf":2.23606797749979},"109":{"tf":2.23606797749979},"111":{"tf":1.0},"192":{"tf":1.4142135623730951},"193":{"tf":1.7320508075688772},"194":{"tf":1.0},"197":{"tf":1.0},"225":{"tf":2.0},"227":{"tf":1.0},"242":{"tf":1.4142135623730951},"45":{"tf":1.0},"46":{"tf":1.0},"70":{"tf":1.4142135623730951},"72":{"tf":2.0},"73":{"tf":1.4142135623730951},"74":{"tf":1.0},"93":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":3,"docs":{"116":{"tf":1.4142135623730951},"236":{"tf":1.0},"277":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"l":{"df":4,"docs":{"11":{"tf":1.7320508075688772},"12":{"tf":1.4142135623730951},"13":{"tf":1.7320508075688772},"19":{"tf":1.4142135623730951}}}}},"x":{"8":{"6":{"_":{"6":{"4":{"/":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"37":{"tf":2.23606797749979}}}}},"df":0,"docs":{}},"df":1,"docs":{"17":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":3,"docs":{"17":{"tf":2.0},"37":{"tf":1.4142135623730951},"40":{"tf":1.0}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":5,"docs":{"14":{"tf":1.0},"252":{"tf":1.0},"28":{"tf":1.4142135623730951},"33":{"tf":1.4142135623730951},"73":{"tf":1.0}}}}},"df":17,"docs":{"163":{"tf":1.4142135623730951},"167":{"tf":2.0},"170":{"tf":1.4142135623730951},"171":{"tf":1.0},"172":{"tf":1.0},"174":{"tf":1.4142135623730951},"23":{"tf":1.4142135623730951},"251":{"tf":1.7320508075688772},"252":{"tf":3.0},"253":{"tf":1.0},"271":{"tf":1.0},"274":{"tf":1.4142135623730951},"28":{"tf":1.4142135623730951},"29":{"tf":1.4142135623730951},"32":{"tf":1.4142135623730951},"33":{"tf":1.4142135623730951},"34":{"tf":1.4142135623730951}}}}}}}}},"df":0,"docs":{}}},"o":{"d":{"df":0,"docs":{},"e":{"df":19,"docs":{"109":{"tf":2.449489742783178},"11":{"tf":1.7320508075688772},"14":{"tf":1.4142135623730951},"165":{"tf":1.4142135623730951},"166":{"tf":1.0},"167":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"175":{"tf":1.4142135623730951},"25":{"tf":2.0},"252":{"tf":1.4142135623730951},"26":{"tf":1.4142135623730951},"274":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":2.0},"35":{"tf":1.0},"36":{"tf":2.0},"46":{"tf":1.4142135623730951},"73":{"tf":3.1622776601683795}}}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"14":{"tf":1.4142135623730951}}}}}}}}},"df":2,"docs":{"207":{"tf":1.4142135623730951},"210":{"tf":1.7320508075688772}},"m":{"df":0,"docs":{},"l":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{":":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"=":{"\"":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{":":{"/":{"/":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"s":{".":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"/":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"k":{"/":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"/":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"70":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"x":{"df":0,"docs":{},"x":{"df":5,"docs":{"242":{"tf":1.0},"270":{"tf":1.0},"277":{"tf":1.0},"46":{"tf":1.0},"48":{"tf":1.7320508075688772}}}}},"y":{"df":2,"docs":{"137":{"tf":1.0},"210":{"tf":1.7320508075688772}},"e":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"193":{"tf":1.0}}}},"df":1,"docs":{"18":{"tf":1.0}},"p":{"df":1,"docs":{"60":{"tf":1.0}}}},"o":{"df":0,"docs":{},"u":{"'":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":11,"docs":{"103":{"tf":1.0},"104":{"tf":1.0},"105":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"123":{"tf":1.0},"17":{"tf":1.4142135623730951},"274":{"tf":1.0},"279":{"tf":1.0},"68":{"tf":1.0},"90":{"tf":1.0}}}},"r":{"df":12,"docs":{"105":{"tf":1.0},"107":{"tf":1.4142135623730951},"114":{"tf":1.0},"115":{"tf":1.0},"17":{"tf":1.0},"21":{"tf":1.0},"37":{"tf":1.0},"39":{"tf":1.0},"41":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"73":{"tf":1.0}}},"v":{"df":6,"docs":{"102":{"tf":1.0},"104":{"tf":1.0},"106":{"tf":1.7320508075688772},"279":{"tf":1.0},"6":{"tf":1.0},"60":{"tf":1.0}}}},"df":0,"docs":{},"r":{"_":{"c":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"69":{"tf":1.7320508075688772},"72":{"tf":1.0}},"e":{">":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":1,"docs":{"73":{"tf":1.4142135623730951}}}}}}},"u":{"d":{"df":0,"docs":{},"l":{"df":2,"docs":{"73":{"tf":1.4142135623730951},"74":{"tf":1.0}}}},"df":0,"docs":{}}},":":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":1,"docs":{"70":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"70":{"tf":1.0}}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{".":{"df":0,"docs":{},"h":{"df":1,"docs":{"73":{"tf":1.0}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"64":{"tf":1.0}}}}}}},"’":{"df":0,"docs":{},"r":{"df":1,"docs":{"115":{"tf":1.0}}}}}}},"z":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":2,"docs":{"250":{"tf":1.4142135623730951},"84":{"tf":1.4142135623730951}}}}},"i":{"df":0,"docs":{},"p":{"df":2,"docs":{"167":{"tf":1.0},"252":{"tf":1.0}},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"274":{"tf":1.0}}}}}}},"l":{"df":0,"docs":{},"i":{"b":{"1":{"df":0,"docs":{},"g":{"df":1,"docs":{"11":{"tf":1.4142135623730951}}}},"df":1,"docs":{"11":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"h":{"df":0,"docs":{},"r":{"c":{"df":2,"docs":{"11":{"tf":1.0},"12":{"tf":1.0}}},"df":0,"docs":{}}}}}}},"breadcrumbs":{"root":{"0":{".":{"0":{".":{"1":{"df":1,"docs":{"22":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"1":{".":{"0":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":1,"docs":{"283":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"123":{"tf":1.4142135623730951}}},"2":{"7":{".":{"0":{"df":1,"docs":{"20":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"62":{"tf":1.0}}},"5":{"1":{".":{"0":{"df":1,"docs":{"20":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"0":{"0":{"0":{"df":5,"docs":{"134":{"tf":1.0},"135":{"tf":1.0},"136":{"tf":1.0},"137":{"tf":1.0},"138":{"tf":1.0}}},"1":{"df":10,"docs":{"134":{"tf":1.0},"139":{"tf":1.0},"140":{"tf":1.0},"141":{"tf":1.0},"142":{"tf":1.0},"143":{"tf":1.0},"144":{"tf":1.0},"145":{"tf":1.0},"146":{"tf":1.0},"147":{"tf":1.0}}},"2":{"df":13,"docs":{"134":{"tf":1.0},"148":{"tf":1.0},"149":{"tf":1.0},"150":{"tf":1.0},"151":{"tf":1.0},"152":{"tf":1.0},"153":{"tf":1.0},"154":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0},"157":{"tf":1.0},"158":{"tf":1.0},"159":{"tf":1.0}}},"3":{"df":17,"docs":{"134":{"tf":1.0},"160":{"tf":1.0},"161":{"tf":1.0},"162":{"tf":1.0},"163":{"tf":1.0},"164":{"tf":1.0},"165":{"tf":1.0},"166":{"tf":1.0},"167":{"tf":1.0},"168":{"tf":1.0},"169":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"173":{"tf":1.0},"174":{"tf":1.0},"175":{"tf":1.0}}},"4":{"df":11,"docs":{"134":{"tf":1.0},"176":{"tf":1.0},"177":{"tf":1.0},"178":{"tf":1.0},"179":{"tf":1.0},"180":{"tf":1.0},"181":{"tf":1.0},"182":{"tf":1.0},"183":{"tf":1.0},"184":{"tf":1.0},"185":{"tf":1.0}}},"5":{"df":17,"docs":{"134":{"tf":1.0},"186":{"tf":1.0},"187":{"tf":1.0},"188":{"tf":1.0},"189":{"tf":1.0},"190":{"tf":1.0},"191":{"tf":1.0},"192":{"tf":1.0},"193":{"tf":1.0},"194":{"tf":1.0},"195":{"tf":1.0},"196":{"tf":1.0},"197":{"tf":1.0},"198":{"tf":1.0},"199":{"tf":1.0},"200":{"tf":1.0},"201":{"tf":1.0}}},"7":{"df":18,"docs":{"134":{"tf":1.0},"202":{"tf":1.0},"203":{"tf":1.0},"204":{"tf":1.0},"205":{"tf":1.0},"206":{"tf":1.0},"207":{"tf":1.0},"208":{"tf":1.0},"209":{"tf":1.0},"210":{"tf":1.0},"211":{"tf":1.0},"212":{"tf":1.0},"213":{"tf":1.0},"214":{"tf":1.0},"215":{"tf":1.0},"216":{"tf":1.0},"217":{"tf":1.0},"218":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"1":{"df":1,"docs":{"202":{"tf":1.0}}},"6":{"df":3,"docs":{"139":{"tf":1.0},"148":{"tf":1.0},"202":{"tf":1.0}}},"7":{"df":2,"docs":{"160":{"tf":1.0},"268":{"tf":1.4142135623730951}}},"8":{"df":3,"docs":{"148":{"tf":1.0},"176":{"tf":1.0},"268":{"tf":1.4142135623730951}}},"df":2,"docs":{"214":{"tf":1.0},"62":{"tf":1.7320508075688772}},"f":{"8":{"c":{"0":{"3":{"7":{"6":{"3":{"7":{"df":0,"docs":{},"f":{"9":{"df":0,"docs":{},"e":{"b":{"3":{"df":0,"docs":{},"e":{".":{"c":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"\"":{"&":{"df":0,"docs":{},"g":{"df":0,"docs":{},"t":{";":{"&":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{";":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"k":{"df":1,"docs":{"283":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"1":{",":{"4":{"df":1,"docs":{"269":{"tf":1.4142135623730951}}},"7":{"df":1,"docs":{"268":{"tf":1.4142135623730951}}},"8":{"df":1,"docs":{"268":{"tf":1.4142135623730951}}},"df":0,"docs":{}},".":{"4":{"df":1,"docs":{"62":{"tf":1.0}}},"df":0,"docs":{}},"0":{",":{"0":{"0":{"0":{"df":5,"docs":{"204":{"tf":1.0},"214":{"tf":1.7320508075688772},"215":{"tf":1.0},"216":{"tf":1.7320508075688772},"217":{"tf":2.23606797749979}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"0":{",":{"0":{"0":{"0":{"df":2,"docs":{"215":{"tf":1.0},"216":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"0":{"0":{"df":1,"docs":{"214":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"6":{"4":{"4":{"df":3,"docs":{"268":{"tf":1.0},"269":{"tf":1.0},"62":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"268":{"tf":1.0}}},"8":{",":{"6":{"df":1,"docs":{"62":{"tf":1.0}}},"7":{"df":1,"docs":{"62":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"9":{"7":{"df":0,"docs":{},"f":{"8":{"df":0,"docs":{},"e":{"9":{"2":{"a":{"0":{"1":{"df":0,"docs":{},"e":{"3":{"c":{"df":0,"docs":{},"f":{".":{"c":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"\"":{"&":{"df":0,"docs":{},"g":{"df":0,"docs":{},"t":{";":{"&":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{";":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"k":{"df":1,"docs":{"283":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":1,"docs":{"20":{"tf":1.0}},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"204":{"tf":1.0}}}}},"1":{",":{"0":{"0":{"0":{"df":1,"docs":{"204":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"4":{".":{"0":{"a":{"1":{"df":1,"docs":{"269":{"tf":1.0}}},"df":0,"docs":{}},"df":1,"docs":{"269":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"8":{".":{"0":{"a":{"1":{"df":1,"docs":{"268":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"9":{".":{"0":{"a":{"1":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"204":{"tf":1.0}}},"2":{"3":{"df":1,"docs":{"117":{"tf":1.0}}},"df":1,"docs":{"186":{"tf":1.0}}},"3":{"2":{"8":{"5":{"a":{"df":0,"docs":{},"e":{"c":{"3":{"1":{"df":0,"docs":{},"f":{"a":{"2":{"4":{"3":{"df":0,"docs":{},"e":{".":{"c":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"\"":{"&":{"df":0,"docs":{},"g":{"df":0,"docs":{},"t":{";":{"df":0,"docs":{},"☰":{"c":{"df":0,"docs":{},"r":{"df":1,"docs":{"283":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"4":{"7":{"9":{"9":{"2":{"9":{"df":1,"docs":{"228":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"5":{",":{"0":{"0":{"0":{"df":1,"docs":{"204":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"6":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"6":{",":{"0":{"0":{"0":{"df":1,"docs":{"214":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"7":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}},"8":{"6":{"2":{"4":{"8":{"3":{"5":{"0":{"4":{"7":{"0":{"df":1,"docs":{"62":{"tf":3.1622776601683795}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":2,"docs":{"176":{"tf":1.0},"186":{"tf":1.0}}},"7":{"1":{"1":{"4":{"4":{"7":{"df":2,"docs":{"171":{"tf":1.0},"174":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":2,"docs":{"12":{"tf":1.4142135623730951},"139":{"tf":1.0}}},":":{"1":{"df":1,"docs":{"242":{"tf":1.0}}},"df":0,"docs":{}},"df":13,"docs":{"117":{"tf":1.0},"142":{"tf":1.0},"145":{"tf":1.4142135623730951},"151":{"tf":1.4142135623730951},"158":{"tf":1.0},"196":{"tf":1.4142135623730951},"20":{"tf":1.0},"22":{"tf":1.4142135623730951},"252":{"tf":1.7320508075688772},"268":{"tf":2.0},"276":{"tf":1.0},"62":{"tf":4.58257569495584},"79":{"tf":2.0}},"u":{"8":{"df":1,"docs":{"117":{"tf":1.0}}},"df":0,"docs":{}}},"2":{",":{"5":{"0":{"0":{"df":1,"docs":{"204":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},".":{"0":{"df":2,"docs":{"64":{"tf":1.4142135623730951},"7":{"tf":1.0}}},"1":{".":{"2":{"df":2,"docs":{"137":{"tf":1.0},"138":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":1,"docs":{"62":{"tf":1.4142135623730951}}},"5":{"df":1,"docs":{"204":{"tf":1.0}}},"df":0,"docs":{}},"0":{",":{"0":{"0":{"0":{"df":1,"docs":{"216":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"0":{"0":{"0":{"df":1,"docs":{"214":{"tf":1.0}}},"df":3,"docs":{"206":{"tf":1.0},"217":{"tf":1.0},"218":{"tf":1.0}}},"df":0,"docs":{}},"1":{"8":{"df":1,"docs":{"192":{"tf":1.0}}},"df":0,"docs":{}},"2":{"1":{"df":4,"docs":{"139":{"tf":1.0},"148":{"tf":1.0},"160":{"tf":1.0},"176":{"tf":1.0}}},"2":{"df":1,"docs":{"186":{"tf":1.0}}},"3":{"df":2,"docs":{"187":{"tf":1.0},"202":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"1":{".":{"4":{".":{"7":{"0":{"7":{"5":{"5":{"2":{"9":{"df":1,"docs":{"12":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"2":{",":{"7":{"df":1,"docs":{"62":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":1,"docs":{"160":{"tf":1.0}}},"5":{",":{"0":{"0":{"0":{"df":1,"docs":{"204":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},".":{"2":{".":{"9":{"5":{"1":{"9":{"6":{"5":{"3":{"df":1,"docs":{"12":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"6":{"df":1,"docs":{"280":{"tf":1.0}}},"df":2,"docs":{"204":{"tf":1.0},"37":{"tf":1.0}},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"204":{"tf":1.0}}}}},"6":{"2":{"8":{"df":1,"docs":{"149":{"tf":1.0}}},"df":0,"docs":{}},"df":1,"docs":{"13":{"tf":1.0}}},"8":{"_":{"df":1,"docs":{"268":{"tf":1.4142135623730951}}},"df":0,"docs":{},"f":{"1":{"9":{"3":{"0":{"7":{".":{".":{"2":{"2":{"5":{"d":{"c":{"c":{"b":{"b":{"df":1,"docs":{"62":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":14,"docs":{"142":{"tf":1.0},"146":{"tf":1.4142135623730951},"149":{"tf":1.0},"151":{"tf":1.4142135623730951},"18":{"tf":1.0},"197":{"tf":1.4142135623730951},"22":{"tf":1.0},"237":{"tf":1.0},"242":{"tf":1.0},"245":{"tf":1.0},"261":{"tf":1.0},"264":{"tf":1.4142135623730951},"62":{"tf":3.0},"79":{"tf":1.0}},"f":{"a":{"df":1,"docs":{"276":{"tf":1.0}}},"df":0,"docs":{}}},"3":{".":{"6":{"df":1,"docs":{"11":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"1":{"_":{"df":1,"docs":{"268":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"2":{",":{"8":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}},"3":{"df":1,"docs":{"176":{"tf":1.0}}},"7":{"6":{"8":{"df":1,"docs":{"264":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"116":{"tf":1.4142135623730951}}},"4":{",":{"6":{"df":1,"docs":{"268":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"6":{",":{"8":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"7":{"df":1,"docs":{"204":{"tf":1.0}}},"8":{"9":{"9":{"df":1,"docs":{"145":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"9":{"0":{"9":{"df":1,"docs":{"48":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"a":{"d":{"9":{"df":1,"docs":{"258":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":10,"docs":{"151":{"tf":1.4142135623730951},"198":{"tf":1.4142135623730951},"199":{"tf":1.0},"213":{"tf":1.4142135623730951},"22":{"tf":1.0},"242":{"tf":1.0},"249":{"tf":1.4142135623730951},"274":{"tf":1.4142135623730951},"60":{"tf":1.4142135623730951},"62":{"tf":1.7320508075688772}},"r":{"d":{"df":2,"docs":{"121":{"tf":1.0},"201":{"tf":1.7320508075688772}}},"df":0,"docs":{}}},"4":{".":{"9":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"/":{"d":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"37":{"tf":2.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"0":{"0":{"df":1,"docs":{"204":{"tf":1.0}}},"df":0,"docs":{}},"1":{"0":{"1":{"df":1,"docs":{"139":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"2":{"df":1,"docs":{"37":{"tf":1.0}}},"5":{",":{"0":{"0":{"0":{"df":1,"docs":{"204":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"6":{"0":{"4":{"df":1,"docs":{"258":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"9":{"6":{"6":{"df":1,"docs":{"38":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"62":{"tf":1.0}}},"5":{",":{"0":{"0":{"0":{"df":1,"docs":{"204":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"0":{",":{"0":{"0":{"0":{"df":1,"docs":{"204":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"0":{"0":{"df":3,"docs":{"206":{"tf":1.0},"217":{"tf":1.0},"218":{"tf":1.0}}},"df":0,"docs":{}},"df":1,"docs":{"204":{"tf":1.0}},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"204":{"tf":1.0}}}}},"2":{"1":{"0":{"df":1,"docs":{"280":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"7":{"4":{"5":{"]":{"(":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{":":{"/":{"/":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"u":{"b":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"/":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"268":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"6":{"4":{"]":{"(":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{":":{"/":{"/":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"u":{"b":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"/":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"268":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"6":{"]":{"(":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{":":{"/":{"/":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"u":{"b":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"/":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"268":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"8":{"4":{"]":{"(":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{":":{"/":{"/":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"u":{"b":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"/":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"268":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":4,"docs":{"20":{"tf":1.0},"204":{"tf":1.7320508075688772},"62":{"tf":1.0},"7":{"tf":1.0}}},"6":{",":{"4":{"0":{"0":{"df":1,"docs":{"204":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},".":{"4":{"df":1,"docs":{"204":{"tf":1.0}}},"df":0,"docs":{}},"1":{"4":{"4":{"df":1,"docs":{"264":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"2":{"df":1,"docs":{"264":{"tf":1.0}}},"4":{"df":2,"docs":{"17":{"tf":2.0},"37":{"tf":1.0}}},"6":{"6":{",":{"7":{"df":1,"docs":{"62":{"tf":1.0}}},"8":{"df":1,"docs":{"62":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"7":{"df":2,"docs":{"215":{"tf":1.0},"217":{"tf":1.0}}},"df":3,"docs":{"204":{"tf":1.7320508075688772},"215":{"tf":1.0},"62":{"tf":1.0}}},"7":{"5":{"df":1,"docs":{"206":{"tf":1.0}},"m":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"218":{"tf":1.0}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"204":{"tf":1.0}}}},"~":{"8":{"0":{"df":1,"docs":{"216":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"0":{"b":{"0":{"c":{"2":{"5":{"9":{"a":{"a":{"2":{"df":1,"docs":{"258":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"f":{"2":{"c":{"0":{"7":{"a":{"1":{"a":{"8":{".":{".":{"0":{"6":{"6":{"8":{"8":{"df":0,"docs":{},"f":{"d":{"c":{"a":{"b":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"8":{"0":{"df":1,"docs":{"204":{"tf":1.0}}},"c":{"d":{"9":{"2":{"3":{"8":{"7":{"3":{".":{".":{"6":{"4":{"8":{"2":{"0":{"1":{"8":{"df":0,"docs":{},"e":{"0":{"df":1,"docs":{"269":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"116":{"tf":1.0}}},"9":{"0":{"df":2,"docs":{"206":{"tf":1.0},"218":{"tf":1.0}},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"204":{"tf":1.0}}}}},"5":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"204":{"tf":1.0}}}}},"6":{"df":1,"docs":{"216":{"tf":1.0}}},"7":{"df":1,"docs":{"204":{"tf":1.0}}},"8":{"df":1,"docs":{"216":{"tf":1.0}}},"9":{".":{"6":{"df":1,"docs":{"216":{"tf":1.0}}},"9":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"204":{"tf":1.0}}}}},"df":0,"docs":{}},"df":4,"docs":{"204":{"tf":1.0},"208":{"tf":1.0},"216":{"tf":1.0},"217":{"tf":1.0}},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"204":{"tf":1.0}}}}},"df":0,"docs":{}},"_":{"2":{"0":{"2":{"3":{"df":1,"docs":{"268":{"tf":2.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"_":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"_":{"_":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"_":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"(":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"_":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"_":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":1,"docs":{"37":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"v":{"a":{"_":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"s":{"_":{"_":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":1,"docs":{"117":{"tf":1.0}},"y":{"df":0,"docs":{},"y":{"df":0,"docs":{},"y":{"df":0,"docs":{},"i":{"df":1,"docs":{"268":{"tf":1.0}}}}}}},"a":{"/":{"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{".":{"df":0,"docs":{},"m":{"d":{"df":1,"docs":{"268":{"tf":2.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"a":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"/":{"c":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":1,"docs":{"62":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{},"s":{"df":0,"docs":{},"r":{"c":{"/":{"d":{"b":{"/":{"d":{"b":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"62":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"62":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":2,"docs":{"268":{"tf":2.0},"269":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}}}}}}}}},"1":{"df":1,"docs":{"154":{"tf":1.4142135623730951}}},"2":{"df":2,"docs":{"152":{"tf":1.0},"155":{"tf":1.4142135623730951}}},"3":{"df":1,"docs":{"156":{"tf":1.4142135623730951}}},"7":{"c":{"d":{"df":1,"docs":{"258":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"a":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"h":{"6":{"4":{"df":2,"docs":{"17":{"tf":1.0},"37":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":6,"docs":{"220":{"tf":1.0},"221":{"tf":3.0},"222":{"tf":1.7320508075688772},"223":{"tf":1.0},"224":{"tf":1.0},"37":{"tf":1.4142135623730951}}}},"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":6,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"162":{"tf":1.0},"178":{"tf":1.0},"234":{"tf":1.0},"274":{"tf":1.4142135623730951}}}},"o":{"df":0,"docs":{},"v":{"df":31,"docs":{"105":{"tf":1.0},"106":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.4142135623730951},"123":{"tf":1.0},"124":{"tf":1.0},"14":{"tf":1.0},"145":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.4142135623730951},"200":{"tf":1.4142135623730951},"204":{"tf":1.0},"214":{"tf":1.0},"217":{"tf":1.0},"22":{"tf":1.7320508075688772},"220":{"tf":1.0},"224":{"tf":1.4142135623730951},"227":{"tf":1.0},"228":{"tf":1.0},"235":{"tf":1.0},"242":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951},"263":{"tf":1.0},"264":{"tf":1.0},"268":{"tf":1.0},"274":{"tf":1.7320508075688772},"280":{"tf":1.0},"64":{"tf":1.0},"73":{"tf":1.0},"87":{"tf":1.0},"90":{"tf":1.0}}}},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":4,"docs":{"61":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0},"67":{"tf":1.0}}}}}},"t":{"df":0,"docs":{},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"103":{"tf":1.0},"229":{"tf":1.4142135623730951},"79":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"c":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"282":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"t":{"df":11,"docs":{"105":{"tf":1.4142135623730951},"106":{"tf":1.0},"115":{"tf":1.0},"139":{"tf":1.0},"148":{"tf":1.0},"160":{"tf":1.0},"202":{"tf":1.0},"231":{"tf":1.0},"274":{"tf":1.0},"37":{"tf":1.0},"79":{"tf":1.4142135623730951}}}},"s":{"df":0,"docs":{},"s":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"242":{"tf":1.0}}}}}}}},"df":13,"docs":{"114":{"tf":1.4142135623730951},"126":{"tf":1.4142135623730951},"127":{"tf":1.4142135623730951},"145":{"tf":1.0},"187":{"tf":1.0},"188":{"tf":1.0},"245":{"tf":1.0},"254":{"tf":1.0},"255":{"tf":1.0},"274":{"tf":1.0},"277":{"tf":1.4142135623730951},"283":{"tf":1.7320508075688772},"37":{"tf":1.0}}}}},"i":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"175":{"tf":1.0}}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"d":{"df":3,"docs":{"104":{"tf":1.0},"268":{"tf":1.0},"282":{"tf":1.0}}},"df":0,"docs":{}}},"o":{"d":{"df":1,"docs":{"108":{"tf":1.0}}},"df":0,"docs":{}},"p":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":3,"docs":{"43":{"tf":1.0},"64":{"tf":1.0},"7":{"tf":1.0}}}}},"df":0,"docs":{}}},"r":{"d":{"df":1,"docs":{"25":{"tf":1.0}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"263":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{".":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"z":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":1,"docs":{"283":{"tf":1.0}}}}}}}}}}}}}}}}},"df":0,"docs":{}}}}}}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"c":{"a":{"df":0,"docs":{},"p":{"a":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"a":{"df":1,"docs":{"283":{"tf":1.0}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"283":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"a":{"df":1,"docs":{"283":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":7,"docs":{"0":{"tf":1.0},"245":{"tf":1.0},"254":{"tf":1.7320508075688772},"274":{"tf":1.0},"276":{"tf":2.449489742783178},"283":{"tf":2.23606797749979},"284":{"tf":1.0}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":1,"docs":{"242":{"tf":1.0}}}}}}}}}},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"145":{"tf":1.0}}}}},"df":2,"docs":{"39":{"tf":1.0},"40":{"tf":1.0}},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":1,"docs":{"167":{"tf":1.0}}}}}},"t":{"df":3,"docs":{"237":{"tf":1.0},"245":{"tf":1.0},"251":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":10,"docs":{"119":{"tf":1.0},"226":{"tf":1.0},"230":{"tf":1.0},"231":{"tf":1.0},"235":{"tf":1.0},"242":{"tf":1.0},"249":{"tf":1.7320508075688772},"250":{"tf":1.0},"272":{"tf":1.0},"273":{"tf":1.0}}}},"v":{"df":1,"docs":{"205":{"tf":1.0}}}},"u":{"a":{"df":0,"docs":{},"l":{"df":10,"docs":{"179":{"tf":1.0},"194":{"tf":1.0},"226":{"tf":1.0},"232":{"tf":1.0},"261":{"tf":1.4142135623730951},"279":{"tf":1.0},"51":{"tf":1.0},"58":{"tf":1.0},"69":{"tf":1.0},"78":{"tf":1.0}}}},"df":0,"docs":{}}}},"d":{"d":{"/":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"d":{"df":2,"docs":{"145":{"tf":1.0},"146":{"tf":1.0}}},"df":0,"docs":{}}}},"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"_":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"d":{"df":2,"docs":{"145":{"tf":2.0},"146":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":83,"docs":{"100":{"tf":1.0},"101":{"tf":1.0},"102":{"tf":1.0},"103":{"tf":1.0},"104":{"tf":1.0},"105":{"tf":1.4142135623730951},"106":{"tf":1.0},"107":{"tf":1.0},"108":{"tf":1.4142135623730951},"109":{"tf":1.7320508075688772},"11":{"tf":1.4142135623730951},"110":{"tf":1.0},"111":{"tf":1.0},"112":{"tf":1.0},"113":{"tf":1.4142135623730951},"114":{"tf":1.0},"115":{"tf":1.0},"116":{"tf":1.0},"117":{"tf":1.0},"119":{"tf":1.0},"12":{"tf":1.0},"120":{"tf":1.0},"123":{"tf":1.0},"124":{"tf":1.0},"13":{"tf":1.0},"145":{"tf":1.4142135623730951},"146":{"tf":1.0},"158":{"tf":1.0},"167":{"tf":1.4142135623730951},"184":{"tf":1.0},"20":{"tf":1.0},"201":{"tf":1.0},"22":{"tf":1.4142135623730951},"223":{"tf":1.0},"224":{"tf":1.0},"229":{"tf":1.4142135623730951},"249":{"tf":1.0},"268":{"tf":1.7320508075688772},"274":{"tf":1.4142135623730951},"281":{"tf":1.0},"284":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"51":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":2.23606797749979},"61":{"tf":2.8284271247461903},"68":{"tf":1.0},"69":{"tf":2.23606797749979},"7":{"tf":1.4142135623730951},"70":{"tf":1.4142135623730951},"71":{"tf":1.0},"72":{"tf":1.0},"73":{"tf":3.872983346207417},"74":{"tf":2.0},"75":{"tf":1.0},"76":{"tf":1.0},"77":{"tf":1.0},"78":{"tf":1.0},"79":{"tf":1.0},"80":{"tf":1.0},"81":{"tf":1.0},"82":{"tf":1.0},"83":{"tf":1.0},"84":{"tf":1.0},"85":{"tf":1.0},"86":{"tf":1.0},"87":{"tf":1.0},"88":{"tf":1.0},"89":{"tf":1.0},"90":{"tf":1.0},"91":{"tf":1.0},"92":{"tf":1.0},"93":{"tf":1.0},"94":{"tf":1.0},"95":{"tf":1.0},"96":{"tf":1.0},"97":{"tf":1.0},"98":{"tf":1.0},"99":{"tf":1.0}},"i":{"df":0,"docs":{},"t":{"df":11,"docs":{"11":{"tf":1.0},"13":{"tf":1.0},"141":{"tf":1.0},"167":{"tf":1.0},"201":{"tf":1.0},"274":{"tf":1.4142135623730951},"43":{"tf":1.4142135623730951},"6":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"67":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":7,"docs":{"180":{"tf":1.0},"209":{"tf":1.0},"22":{"tf":1.0},"254":{"tf":1.0},"257":{"tf":1.0},"58":{"tf":1.0},"60":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":5,"docs":{"141":{"tf":1.0},"169":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"182":{"tf":1.0}}}}}}},"df":37,"docs":{"101":{"tf":1.0},"102":{"tf":1.4142135623730951},"104":{"tf":1.0},"105":{"tf":1.0},"108":{"tf":1.4142135623730951},"109":{"tf":1.7320508075688772},"11":{"tf":1.0},"121":{"tf":1.0},"123":{"tf":2.449489742783178},"145":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":1.0},"201":{"tf":1.0},"22":{"tf":2.0},"23":{"tf":1.0},"249":{"tf":1.0},"264":{"tf":1.0},"277":{"tf":1.0},"280":{"tf":1.0},"282":{"tf":1.0},"284":{"tf":1.0},"285":{"tf":1.0},"286":{"tf":1.0},"31":{"tf":1.0},"32":{"tf":1.0},"36":{"tf":1.0},"41":{"tf":1.0},"57":{"tf":1.4142135623730951},"59":{"tf":1.0},"61":{"tf":1.4142135623730951},"64":{"tf":1.4142135623730951},"65":{"tf":1.0},"66":{"tf":1.0},"67":{"tf":1.0},"68":{"tf":2.0},"69":{"tf":1.0},"73":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":1,"docs":{"7":{"tf":1.0}}}}},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"91":{"tf":1.0}}}}},"j":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":4,"docs":{"16":{"tf":1.0},"25":{"tf":1.0},"277":{"tf":1.0},"37":{"tf":1.7320508075688772}}}}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"117":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"r":{"df":89,"docs":{"134":{"tf":2.8284271247461903},"135":{"tf":1.0},"136":{"tf":1.0},"137":{"tf":1.4142135623730951},"138":{"tf":1.4142135623730951},"139":{"tf":1.0},"140":{"tf":1.0},"141":{"tf":1.0},"142":{"tf":1.0},"143":{"tf":1.0},"144":{"tf":1.0},"145":{"tf":1.0},"146":{"tf":1.0},"147":{"tf":1.0},"148":{"tf":1.0},"149":{"tf":1.4142135623730951},"150":{"tf":1.0},"151":{"tf":1.0},"152":{"tf":1.0},"153":{"tf":1.0},"154":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0},"157":{"tf":1.0},"158":{"tf":1.0},"159":{"tf":1.0},"160":{"tf":1.0},"161":{"tf":1.0},"162":{"tf":1.0},"163":{"tf":1.0},"164":{"tf":1.0},"165":{"tf":1.0},"166":{"tf":1.0},"167":{"tf":1.0},"168":{"tf":1.0},"169":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"173":{"tf":1.0},"174":{"tf":1.0},"175":{"tf":1.0},"176":{"tf":1.0},"177":{"tf":1.0},"178":{"tf":1.0},"179":{"tf":1.0},"180":{"tf":1.0},"181":{"tf":1.0},"182":{"tf":1.0},"183":{"tf":1.0},"184":{"tf":1.0},"185":{"tf":1.0},"186":{"tf":1.0},"187":{"tf":1.0},"188":{"tf":1.0},"189":{"tf":1.0},"190":{"tf":1.0},"191":{"tf":1.0},"192":{"tf":1.0},"193":{"tf":1.0},"194":{"tf":1.0},"195":{"tf":1.0},"196":{"tf":1.0},"197":{"tf":1.0},"198":{"tf":1.0},"199":{"tf":1.0},"200":{"tf":1.4142135623730951},"201":{"tf":1.4142135623730951},"202":{"tf":1.0},"203":{"tf":1.0},"204":{"tf":1.0},"205":{"tf":1.0},"206":{"tf":1.0},"207":{"tf":1.0},"208":{"tf":1.0},"209":{"tf":1.0},"210":{"tf":1.0},"211":{"tf":1.0},"212":{"tf":1.0},"213":{"tf":1.0},"214":{"tf":1.0},"215":{"tf":1.0},"216":{"tf":1.0},"217":{"tf":1.0},"218":{"tf":1.0},"23":{"tf":1.0},"251":{"tf":1.0},"32":{"tf":1.0},"73":{"tf":1.0}}},"v":{"a":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"6":{"tf":1.4142135623730951}}},"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"g":{"df":2,"docs":{"12":{"tf":1.0},"220":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"107":{"tf":1.0}}}}}}},"i":{"c":{"df":2,"docs":{"122":{"tf":1.4142135623730951},"69":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"157":{"tf":1.0},"268":{"tf":1.0},"63":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"220":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"r":{"a":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"105":{"tf":1.0},"106":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"g":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":6,"docs":{"11":{"tf":1.0},"157":{"tf":1.0},"158":{"tf":1.0},"242":{"tf":1.0},"245":{"tf":1.0},"79":{"tf":1.0}},"s":{"df":0,"docs":{},"t":{"df":25,"docs":{"15":{"tf":1.7320508075688772},"151":{"tf":1.4142135623730951},"152":{"tf":1.4142135623730951},"155":{"tf":1.4142135623730951},"156":{"tf":1.4142135623730951},"16":{"tf":1.0},"17":{"tf":1.0},"194":{"tf":1.0},"23":{"tf":1.4142135623730951},"268":{"tf":1.4142135623730951},"269":{"tf":1.0},"274":{"tf":1.7320508075688772},"282":{"tf":1.0},"32":{"tf":1.0},"37":{"tf":1.0},"48":{"tf":1.0},"49":{"tf":1.0},"51":{"tf":1.0},"52":{"tf":1.4142135623730951},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"62":{"tf":1.0},"64":{"tf":1.4142135623730951},"7":{"tf":1.0}}}}}}},"df":0,"docs":{},"o":{"df":1,"docs":{"213":{"tf":1.0}}},"p":{"df":1,"docs":{"12":{"tf":1.0}}},"r":{"df":0,"docs":{},"e":{"df":2,"docs":{"232":{"tf":1.0},"7":{"tf":1.0}}}}},"h":{"df":0,"docs":{},"e":{"a":{"d":{"df":1,"docs":{"104":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"m":{"df":4,"docs":{"102":{"tf":1.0},"103":{"tf":1.0},"104":{"tf":1.0},"116":{"tf":1.0}}}},"k":{"a":{"df":1,"docs":{"260":{"tf":1.0}}},"df":0,"docs":{}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"64":{"tf":1.4142135623730951}}}}},"g":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"m":{"df":1,"docs":{"282":{"tf":1.0}}}}}}}}},"i":{"a":{"df":1,"docs":{"11":{"tf":1.0}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"df":3,"docs":{"171":{"tf":1.0},"172":{"tf":1.0},"274":{"tf":1.0}}}}},"l":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"279":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"(":{"d":{"df":0,"docs":{},"e":{"a":{"d":{"_":{"c":{"df":0,"docs":{},"o":{"d":{"df":1,"docs":{"60":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":15,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"145":{"tf":1.0},"157":{"tf":1.0},"158":{"tf":1.0},"183":{"tf":1.0},"190":{"tf":1.4142135623730951},"198":{"tf":1.0},"199":{"tf":1.0},"229":{"tf":1.0},"233":{"tf":1.0},"242":{"tf":1.0},"255":{"tf":1.0},"273":{"tf":1.0},"39":{"tf":1.0}}}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"20":{"tf":1.0}}}},"df":0,"docs":{}}}}}}},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"198":{"tf":1.0},"220":{"tf":1.0}},"g":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"70":{"tf":1.0},"72":{"tf":1.0}}},"df":0,"docs":{}}}}}},"r":{"df":0,"docs":{},"e":{"a":{"d":{"df":0,"docs":{},"i":{"df":17,"docs":{"122":{"tf":1.0},"187":{"tf":1.0},"194":{"tf":1.0},"196":{"tf":1.0},"197":{"tf":1.0},"198":{"tf":1.0},"199":{"tf":1.0},"200":{"tf":1.0},"229":{"tf":1.0},"23":{"tf":1.0},"241":{"tf":1.4142135623730951},"258":{"tf":1.0},"32":{"tf":1.0},"36":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"64":{"tf":1.7320508075688772}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":4,"docs":{"115":{"tf":1.4142135623730951},"122":{"tf":1.0},"225":{"tf":1.0},"26":{"tf":1.0}}}}},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":9,"docs":{"105":{"tf":1.0},"124":{"tf":1.0},"132":{"tf":1.0},"190":{"tf":1.0},"214":{"tf":1.4142135623730951},"225":{"tf":1.0},"232":{"tf":1.0},"262":{"tf":1.0},"38":{"tf":1.0}}}}}}}},"w":{"a":{"df":0,"docs":{},"y":{"df":10,"docs":{"114":{"tf":1.0},"133":{"tf":1.4142135623730951},"146":{"tf":1.0},"189":{"tf":1.0},"198":{"tf":1.0},"22":{"tf":1.0},"260":{"tf":1.0},"60":{"tf":1.0},"79":{"tf":1.0},"99":{"tf":1.0}}}},"df":0,"docs":{}}},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":8,"docs":{"166":{"tf":1.0},"169":{"tf":1.4142135623730951},"170":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"7":{"tf":1.0}}}}}}},"n":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"y":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":4,"docs":{"178":{"tf":1.0},"214":{"tf":1.4142135623730951},"264":{"tf":1.0},"274":{"tf":1.0}}}}}}},"d":{"/":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":2,"docs":{"104":{"tf":1.0},"122":{"tf":1.0}}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{".":{"df":0,"docs":{},"y":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":1,"docs":{"113":{"tf":1.0}},"t":{"df":0,"docs":{},"o":{"df":1,"docs":{"70":{"tf":1.0}}}}}}}},"/":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{".":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"d":{"df":0,"docs":{},"l":{"df":1,"docs":{"108":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"/":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"/":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"e":{"/":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":1,"docs":{"108":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"df":1,"docs":{"20":{"tf":1.0}}}}}},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"x":{"df":1,"docs":{"119":{"tf":1.0}}}}}}}}},"i":{"df":0,"docs":{},"o":{"df":1,"docs":{"11":{"tf":1.0}}}},"k":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"65":{"tf":1.4142135623730951}}}}}}}},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{".":{"df":0,"docs":{},"h":{"df":1,"docs":{"37":{"tf":1.0}}}},"df":0,"docs":{}}}},"s":{"df":0,"docs":{},"r":{"c":{"/":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"/":{"df":0,"docs":{},"j":{"a":{"df":0,"docs":{},"v":{"a":{"df":1,"docs":{"108":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"/":{"df":0,"docs":{},"j":{"a":{"df":0,"docs":{},"v":{"a":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"/":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"/":{"<":{"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"_":{"c":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"70":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":1,"docs":{"200":{"tf":1.0}}}}}}}},"2":{"1":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":1,"docs":{"12":{"tf":1.0}},"e":{"/":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"s":{"/":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"/":{"df":0,"docs":{},"s":{"d":{"df":0,"docs":{},"k":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"13":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"s":{"d":{"df":0,"docs":{},"k":{"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":1,"docs":{"12":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":59,"docs":{"10":{"tf":1.0},"103":{"tf":1.7320508075688772},"108":{"tf":1.4142135623730951},"11":{"tf":1.0},"110":{"tf":1.7320508075688772},"111":{"tf":1.0},"112":{"tf":1.0},"113":{"tf":1.0},"114":{"tf":1.0},"115":{"tf":1.7320508075688772},"116":{"tf":1.4142135623730951},"117":{"tf":2.0},"118":{"tf":2.0},"119":{"tf":1.4142135623730951},"12":{"tf":2.8284271247461903},"126":{"tf":2.0},"13":{"tf":1.7320508075688772},"131":{"tf":1.0},"133":{"tf":1.0},"145":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":2.0},"19":{"tf":1.0},"190":{"tf":1.0},"192":{"tf":1.0},"199":{"tf":1.0},"20":{"tf":1.4142135623730951},"200":{"tf":1.0},"203":{"tf":1.0},"206":{"tf":1.7320508075688772},"21":{"tf":1.4142135623730951},"210":{"tf":1.0},"217":{"tf":1.0},"218":{"tf":1.4142135623730951},"220":{"tf":1.7320508075688772},"221":{"tf":2.23606797749979},"223":{"tf":1.0},"225":{"tf":2.0},"230":{"tf":1.0},"237":{"tf":1.4142135623730951},"255":{"tf":1.0},"264":{"tf":1.0},"268":{"tf":1.4142135623730951},"271":{"tf":1.0},"272":{"tf":1.0},"274":{"tf":2.0},"278":{"tf":1.0},"280":{"tf":1.0},"37":{"tf":3.1622776601683795},"38":{"tf":1.0},"39":{"tf":1.7320508075688772},"40":{"tf":1.4142135623730951},"45":{"tf":1.7320508075688772},"49":{"tf":2.449489742783178},"54":{"tf":2.0},"69":{"tf":1.0},"70":{"tf":1.4142135623730951},"9":{"tf":1.0},"90":{"tf":1.0}},"e":{"a":{"b":{"df":0,"docs":{},"i":{"df":1,"docs":{"37":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"m":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"x":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":3,"docs":{"103":{"tf":1.4142135623730951},"108":{"tf":1.0},"70":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"200":{"tf":1.0}}}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"h":{"df":3,"docs":{"132":{"tf":1.0},"233":{"tf":1.0},"283":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"w":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"205":{"tf":1.0}}}}}},"t":{"df":1,"docs":{"37":{"tf":3.0}},"i":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":1,"docs":{"231":{"tf":1.0}}}}},"df":0,"docs":{}}},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"3":{"tf":1.0}}}},"t":{"df":0,"docs":{},"h":{"df":4,"docs":{"228":{"tf":1.0},"242":{"tf":1.0},"264":{"tf":1.0},"61":{"tf":1.0}}}},"w":{"a":{"df":0,"docs":{},"y":{"df":1,"docs":{"60":{"tf":1.0}}}},"df":0,"docs":{}}}},"p":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"81":{"tf":1.0}}}}},"df":0,"docs":{},"i":{"df":38,"docs":{"104":{"tf":1.7320508075688772},"105":{"tf":1.7320508075688772},"106":{"tf":1.4142135623730951},"107":{"tf":1.7320508075688772},"108":{"tf":1.4142135623730951},"109":{"tf":1.0},"111":{"tf":1.0},"114":{"tf":1.0},"116":{"tf":1.0},"118":{"tf":1.4142135623730951},"119":{"tf":1.0},"134":{"tf":1.0},"139":{"tf":1.4142135623730951},"141":{"tf":1.4142135623730951},"142":{"tf":2.0},"143":{"tf":1.0},"145":{"tf":2.6457513110645907},"146":{"tf":2.0},"166":{"tf":1.0},"197":{"tf":1.0},"200":{"tf":2.0},"21":{"tf":1.0},"226":{"tf":1.0},"232":{"tf":3.1622776601683795},"236":{"tf":1.7320508075688772},"252":{"tf":1.4142135623730951},"281":{"tf":1.4142135623730951},"283":{"tf":1.0},"43":{"tf":1.4142135623730951},"45":{"tf":1.4142135623730951},"46":{"tf":1.0},"69":{"tf":1.0},"7":{"tf":1.0},"70":{"tf":1.0},"73":{"tf":1.0},"78":{"tf":1.7320508075688772},"79":{"tf":1.4142135623730951},"96":{"tf":1.0}}},"k":{"df":2,"docs":{"39":{"tf":1.4142135623730951},"40":{"tf":1.0}}},"p":{"'":{"df":1,"docs":{"226":{"tf":1.0}}},"a":{"df":0,"docs":{},"r":{"df":2,"docs":{"242":{"tf":1.0},"37":{"tf":1.0}}}},"df":35,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"142":{"tf":1.0},"158":{"tf":1.0},"165":{"tf":1.0},"17":{"tf":1.0},"179":{"tf":1.0},"180":{"tf":1.0},"183":{"tf":1.4142135623730951},"184":{"tf":1.0},"189":{"tf":1.0},"19":{"tf":1.0},"190":{"tf":1.0},"194":{"tf":1.0},"20":{"tf":1.0},"200":{"tf":1.4142135623730951},"205":{"tf":1.0},"209":{"tf":1.0},"212":{"tf":1.0},"213":{"tf":2.0},"225":{"tf":1.4142135623730951},"228":{"tf":2.0},"229":{"tf":1.4142135623730951},"233":{"tf":2.0},"236":{"tf":1.0},"242":{"tf":1.7320508075688772},"250":{"tf":1.0},"254":{"tf":1.0},"255":{"tf":1.4142135623730951},"257":{"tf":1.4142135623730951},"273":{"tf":1.0},"40":{"tf":1.0},"49":{"tf":1.0},"52":{"tf":1.4142135623730951},"84":{"tf":1.0}},"e":{"a":{"df":0,"docs":{},"r":{"df":8,"docs":{"117":{"tf":1.0},"172":{"tf":1.4142135623730951},"228":{"tf":1.0},"249":{"tf":2.23606797749979},"37":{"tf":1.0},"64":{"tf":1.0},"73":{"tf":1.4142135623730951},"86":{"tf":1.0}}}},"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"98":{"tf":1.0}},"i":{"df":0,"docs":{},"x":{"df":2,"docs":{"173":{"tf":1.4142135623730951},"60":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}},"l":{"df":9,"docs":{"161":{"tf":1.0},"162":{"tf":1.0},"163":{"tf":1.0},"169":{"tf":1.0},"170":{"tf":1.4142135623730951},"171":{"tf":1.0},"172":{"tf":1.0},"175":{"tf":1.0},"252":{"tf":1.0}},"e":{"'":{"df":2,"docs":{"274":{"tf":1.0},"46":{"tf":1.0}}},"df":0,"docs":{}},"i":{"c":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"'":{"df":1,"docs":{"221":{"tf":1.0}}},".":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"z":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"283":{"tf":1.0}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"x":{"a":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"l":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"df":1,"docs":{"283":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"\\":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"257":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"’":{"df":1,"docs":{"283":{"tf":1.0}}}}}}}},"df":101,"docs":{"0":{"tf":2.23606797749979},"1":{"tf":1.4142135623730951},"10":{"tf":1.0},"11":{"tf":1.4142135623730951},"114":{"tf":1.0},"118":{"tf":2.0},"119":{"tf":1.4142135623730951},"12":{"tf":2.0},"120":{"tf":1.7320508075688772},"121":{"tf":1.0},"122":{"tf":2.23606797749979},"123":{"tf":1.4142135623730951},"124":{"tf":3.1622776601683795},"125":{"tf":2.0},"130":{"tf":1.0},"14":{"tf":1.4142135623730951},"15":{"tf":1.7320508075688772},"16":{"tf":1.0},"161":{"tf":1.7320508075688772},"162":{"tf":1.0},"164":{"tf":1.0},"165":{"tf":1.7320508075688772},"167":{"tf":3.1622776601683795},"175":{"tf":1.4142135623730951},"177":{"tf":1.0},"18":{"tf":2.23606797749979},"180":{"tf":1.0},"184":{"tf":1.0},"186":{"tf":1.0},"187":{"tf":1.0},"189":{"tf":1.4142135623730951},"19":{"tf":1.0},"190":{"tf":1.0},"194":{"tf":1.4142135623730951},"197":{"tf":1.4142135623730951},"2":{"tf":1.4142135623730951},"20":{"tf":2.0},"200":{"tf":2.6457513110645907},"201":{"tf":2.449489742783178},"203":{"tf":1.4142135623730951},"204":{"tf":1.0},"21":{"tf":1.4142135623730951},"218":{"tf":1.0},"22":{"tf":2.0},"220":{"tf":1.4142135623730951},"221":{"tf":1.0},"222":{"tf":1.4142135623730951},"225":{"tf":2.0},"228":{"tf":1.4142135623730951},"23":{"tf":1.7320508075688772},"232":{"tf":2.23606797749979},"24":{"tf":1.0},"242":{"tf":1.0},"244":{"tf":1.7320508075688772},"248":{"tf":1.0},"25":{"tf":1.7320508075688772},"252":{"tf":1.7320508075688772},"253":{"tf":1.7320508075688772},"256":{"tf":1.0},"258":{"tf":1.0},"259":{"tf":1.7320508075688772},"260":{"tf":1.4142135623730951},"262":{"tf":1.4142135623730951},"265":{"tf":1.4142135623730951},"268":{"tf":2.23606797749979},"274":{"tf":2.449489742783178},"276":{"tf":1.0},"28":{"tf":1.0},"283":{"tf":2.6457513110645907},"284":{"tf":1.0},"29":{"tf":1.4142135623730951},"3":{"tf":1.7320508075688772},"30":{"tf":2.0},"32":{"tf":1.4142135623730951},"33":{"tf":1.0},"34":{"tf":1.7320508075688772},"35":{"tf":2.0},"37":{"tf":1.7320508075688772},"38":{"tf":1.0},"39":{"tf":2.0},"40":{"tf":2.0},"41":{"tf":1.0},"49":{"tf":1.4142135623730951},"52":{"tf":2.0},"53":{"tf":1.0},"54":{"tf":1.7320508075688772},"55":{"tf":1.4142135623730951},"56":{"tf":1.4142135623730951},"64":{"tf":1.0},"68":{"tf":1.7320508075688772},"69":{"tf":1.0},"7":{"tf":1.0},"73":{"tf":1.0},"74":{"tf":1.0},"77":{"tf":1.0},"78":{"tf":1.0},"8":{"tf":1.0},"81":{"tf":1.0},"82":{"tf":1.0},"84":{"tf":1.0},"9":{"tf":2.0}}},"df":11,"docs":{"128":{"tf":1.0},"140":{"tf":1.0},"179":{"tf":1.0},"22":{"tf":2.0},"225":{"tf":1.0},"249":{"tf":1.0},"262":{"tf":1.0},"274":{"tf":1.0},"64":{"tf":1.0},"7":{"tf":1.0},"98":{"tf":1.0}}},"y":{"_":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":3,"docs":{"247":{"tf":1.7320508075688772},"249":{"tf":1.4142135623730951},"88":{"tf":1.7320508075688772}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"o":{"a":{"c":{"df":0,"docs":{},"h":{"df":13,"docs":{"101":{"tf":1.0},"115":{"tf":1.0},"140":{"tf":1.4142135623730951},"141":{"tf":1.4142135623730951},"143":{"tf":1.0},"145":{"tf":1.0},"146":{"tf":1.7320508075688772},"171":{"tf":1.0},"205":{"tf":1.0},"220":{"tf":1.0},"241":{"tf":1.0},"251":{"tf":1.0},"60":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":4,"docs":{"189":{"tf":1.0},"280":{"tf":1.0},"45":{"tf":1.0},"6":{"tf":1.0}}}}},"v":{"df":5,"docs":{"118":{"tf":1.4142135623730951},"119":{"tf":1.4142135623730951},"189":{"tf":1.0},"270":{"tf":1.0},"6":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":5,"docs":{"169":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"222":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"d":{"df":0,"docs":{},"l":{"df":1,"docs":{"22":{"tf":1.7320508075688772}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"r":{"c":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"}":{"/":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"22":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"v":{"c":{"df":2,"docs":{"276":{"tf":1.7320508075688772},"277":{"tf":1.0}}},"df":0,"docs":{}}}},"t":{"df":2,"docs":{"11":{"tf":3.4641016151377544},"13":{"tf":1.0}}}},"r":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":94,"docs":{"102":{"tf":1.0},"134":{"tf":2.449489742783178},"135":{"tf":1.7320508075688772},"136":{"tf":1.4142135623730951},"137":{"tf":1.7320508075688772},"138":{"tf":1.0},"139":{"tf":1.0},"140":{"tf":1.0},"141":{"tf":1.0},"142":{"tf":1.0},"143":{"tf":1.0},"144":{"tf":1.0},"145":{"tf":1.0},"146":{"tf":1.0},"147":{"tf":1.0},"148":{"tf":1.0},"149":{"tf":1.0},"150":{"tf":1.0},"151":{"tf":1.0},"152":{"tf":1.0},"153":{"tf":1.0},"154":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0},"157":{"tf":1.0},"158":{"tf":1.0},"159":{"tf":1.0},"160":{"tf":1.0},"161":{"tf":1.0},"162":{"tf":1.0},"163":{"tf":1.0},"164":{"tf":1.0},"165":{"tf":1.0},"166":{"tf":1.0},"167":{"tf":1.0},"168":{"tf":1.0},"169":{"tf":1.0},"17":{"tf":1.4142135623730951},"170":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"173":{"tf":1.0},"174":{"tf":1.0},"175":{"tf":1.0},"176":{"tf":1.0},"177":{"tf":1.0},"178":{"tf":1.0},"179":{"tf":1.0},"180":{"tf":1.0},"181":{"tf":1.0},"182":{"tf":1.0},"183":{"tf":1.0},"184":{"tf":1.0},"185":{"tf":1.0},"186":{"tf":1.0},"187":{"tf":1.0},"188":{"tf":1.0},"189":{"tf":1.0},"190":{"tf":1.0},"191":{"tf":1.0},"192":{"tf":1.0},"193":{"tf":1.0},"194":{"tf":1.0},"195":{"tf":1.0},"196":{"tf":1.0},"197":{"tf":1.0},"198":{"tf":1.0},"199":{"tf":1.0},"200":{"tf":1.0},"201":{"tf":1.0},"202":{"tf":1.0},"203":{"tf":1.0},"204":{"tf":1.0},"205":{"tf":1.0},"206":{"tf":1.0},"207":{"tf":1.0},"208":{"tf":1.0},"209":{"tf":1.0},"210":{"tf":1.0},"211":{"tf":1.0},"212":{"tf":1.0},"213":{"tf":1.0},"214":{"tf":1.0},"215":{"tf":1.0},"216":{"tf":1.0},"217":{"tf":1.0},"218":{"tf":1.0},"223":{"tf":1.0},"225":{"tf":1.4142135623730951},"227":{"tf":1.0},"244":{"tf":1.4142135623730951},"252":{"tf":2.23606797749979},"282":{"tf":1.7320508075688772},"58":{"tf":1.0}}}}}},"df":0,"docs":{}}},"v":{"df":4,"docs":{"220":{"tf":1.0},"223":{"tf":1.0},"271":{"tf":1.0},"279":{"tf":1.0}},"e":{".":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{".":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":1,"docs":{"273":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"e":{"a":{"df":2,"docs":{"273":{"tf":1.7320508075688772},"63":{"tf":1.0}}},"df":0,"docs":{},"n":{"'":{"df":0,"docs":{},"t":{"df":9,"docs":{"166":{"tf":1.0},"225":{"tf":1.0},"226":{"tf":1.0},"260":{"tf":1.0},"268":{"tf":1.0},"58":{"tf":1.0},"60":{"tf":1.0},"79":{"tf":1.0},"84":{"tf":1.0}}}},"df":0,"docs":{}}},"g":{"df":1,"docs":{"58":{"tf":1.0}}},"m":{"6":{"4":{"df":1,"docs":{"175":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{},"v":{"7":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":12,"docs":{"115":{"tf":1.0},"116":{"tf":1.0},"150":{"tf":1.0},"169":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"204":{"tf":1.4142135623730951},"233":{"tf":1.0},"242":{"tf":1.4142135623730951},"261":{"tf":1.0},"51":{"tf":1.0},"79":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}},"t":{"df":2,"docs":{"206":{"tf":1.0},"214":{"tf":1.0}},"i":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"163":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"f":{"a":{"c":{"df":0,"docs":{},"t":{"df":20,"docs":{"122":{"tf":1.0},"14":{"tf":1.0},"163":{"tf":1.0},"167":{"tf":1.0},"169":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.4142135623730951},"172":{"tf":1.4142135623730951},"175":{"tf":1.0},"220":{"tf":1.0},"267":{"tf":1.4142135623730951},"271":{"tf":1.0},"272":{"tf":1.0},"273":{"tf":1.7320508075688772},"274":{"tf":2.23606797749979},"279":{"tf":1.4142135623730951},"280":{"tf":2.449489742783178},"37":{"tf":1.4142135623730951},"40":{"tf":1.0},"64":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"s":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"242":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"51":{"tf":1.0}}}},"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"60":{"tf":1.0}}},"df":0,"docs":{}},"k":{"df":5,"docs":{"122":{"tf":2.0},"161":{"tf":1.0},"233":{"tf":1.0},"242":{"tf":1.0},"274":{"tf":1.0}}},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"104":{"tf":1.0}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"r":{"df":1,"docs":{"258":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":1,"docs":{"18":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":1,"docs":{"62":{"tf":1.0}}}}},"df":1,"docs":{"43":{"tf":1.4142135623730951}}}}},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"254":{"tf":1.0}}}}},"o":{"c":{"df":0,"docs":{},"i":{"df":2,"docs":{"115":{"tf":1.0},"283":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"m":{"df":17,"docs":{"114":{"tf":1.0},"12":{"tf":1.0},"145":{"tf":1.0},"151":{"tf":1.0},"154":{"tf":1.4142135623730951},"20":{"tf":1.0},"21":{"tf":1.0},"210":{"tf":1.0},"225":{"tf":1.0},"23":{"tf":1.0},"25":{"tf":1.0},"29":{"tf":1.0},"30":{"tf":1.0},"32":{"tf":1.0},"34":{"tf":1.0},"35":{"tf":1.0},"86":{"tf":1.0}},"p":{"df":0,"docs":{},"t":{"df":2,"docs":{"138":{"tf":1.0},"210":{"tf":1.0}}}}},"r":{"df":2,"docs":{"274":{"tf":1.0},"64":{"tf":1.0}}}}},"y":{"df":0,"docs":{},"n":{"c":{"df":4,"docs":{"115":{"tf":1.0},"193":{"tf":1.0},"194":{"tf":1.0},"197":{"tf":1.0}}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"t":{"a":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"190":{"tf":1.0}}},"k":{"df":1,"docs":{"201":{"tf":1.7320508075688772}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":13,"docs":{"132":{"tf":1.0},"146":{"tf":1.0},"167":{"tf":1.0},"19":{"tf":1.0},"200":{"tf":1.0},"203":{"tf":1.0},"204":{"tf":1.0},"207":{"tf":1.0},"226":{"tf":1.0},"245":{"tf":1.0},"25":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0}}}}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"170":{"tf":1.0},"197":{"tf":1.0},"198":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"193":{"tf":1.0},"194":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"u":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"274":{"tf":1.0},"64":{"tf":1.0}}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"226":{"tf":1.0}}}}}}},"t":{"df":0,"docs":{},"h":{"df":4,"docs":{"226":{"tf":1.0},"236":{"tf":1.0},"237":{"tf":1.0},"239":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":5,"docs":{"226":{"tf":1.0},"245":{"tf":1.0},"254":{"tf":1.0},"274":{"tf":1.0},"275":{"tf":1.4142135623730951}},"i":{"c":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"242":{"tf":1.0}}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"283":{"tf":1.7320508075688772}}}}},"o":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"61":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":8,"docs":{"104":{"tf":1.0},"116":{"tf":1.0},"12":{"tf":1.0},"16":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":1.4142135623730951},"20":{"tf":1.7320508075688772},"22":{"tf":1.7320508075688772}},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":5,"docs":{"140":{"tf":1.0},"145":{"tf":1.0},"244":{"tf":1.0},"257":{"tf":1.0},"264":{"tf":1.7320508075688772}},"l":{"/":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"245":{"tf":1.0}}}}}}}},":":{":":{"d":{"b":{":":{":":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{":":{":":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"245":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"d":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"_":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"245":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":5,"docs":{"103":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0}}}}}}},"m":{"a":{"df":0,"docs":{},"t":{"df":12,"docs":{"107":{"tf":1.0},"113":{"tf":1.0},"167":{"tf":1.0},"18":{"tf":1.4142135623730951},"19":{"tf":1.0},"266":{"tf":1.0},"61":{"tf":1.0},"70":{"tf":1.0},"72":{"tf":1.0},"73":{"tf":1.0},"74":{"tf":1.0},"90":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"269":{"tf":1.0}}}},"df":0,"docs":{}}}},"u":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"l":{"_":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"_":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"d":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"19":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":2,"docs":{"269":{"tf":1.0},"53":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}}}}}}}},"u":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"109":{"tf":1.0}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"s":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":3,"docs":{"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0}}}}}},"t":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"269":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"7":{"tf":1.0}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}},"df":10,"docs":{"101":{"tf":1.0},"103":{"tf":1.0},"111":{"tf":1.0},"167":{"tf":1.0},"22":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951},"27":{"tf":1.0},"274":{"tf":2.23606797749979},"49":{"tf":1.4142135623730951},"7":{"tf":1.0}}},"p":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{".":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":2,"docs":{"18":{"tf":1.4142135623730951},"22":{"tf":1.7320508075688772}}}}}},"df":0,"docs":{}},"df":17,"docs":{"118":{"tf":1.0},"16":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.0},"21":{"tf":1.0},"22":{"tf":1.0},"23":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"26":{"tf":1.0},"27":{"tf":1.0},"28":{"tf":1.0},"29":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.0}},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"l":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":1,"docs":{"22":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"61":{"tf":1.0}},"s":{"/":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"v":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":24,"docs":{"1":{"tf":1.0},"104":{"tf":1.0},"107":{"tf":1.0},"11":{"tf":1.0},"114":{"tf":1.0},"134":{"tf":1.4142135623730951},"138":{"tf":1.0},"142":{"tf":1.0},"146":{"tf":1.0},"165":{"tf":1.0},"177":{"tf":1.4142135623730951},"178":{"tf":1.0},"179":{"tf":1.0},"187":{"tf":1.0},"192":{"tf":1.0},"198":{"tf":1.0},"228":{"tf":1.0},"252":{"tf":1.0},"260":{"tf":1.0},"274":{"tf":1.7320508075688772},"282":{"tf":1.0},"45":{"tf":1.0},"5":{"tf":1.0},"62":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"204":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"i":{"d":{"df":23,"docs":{"116":{"tf":1.0},"126":{"tf":1.0},"128":{"tf":1.0},"132":{"tf":1.0},"141":{"tf":1.0},"150":{"tf":1.0},"165":{"tf":1.0},"172":{"tf":1.0},"197":{"tf":1.4142135623730951},"22":{"tf":1.0},"220":{"tf":1.0},"222":{"tf":1.0},"225":{"tf":1.0},"227":{"tf":1.0},"229":{"tf":1.4142135623730951},"263":{"tf":1.4142135623730951},"264":{"tf":1.0},"46":{"tf":1.0},"57":{"tf":1.4142135623730951},"59":{"tf":1.0},"60":{"tf":1.4142135623730951},"69":{"tf":1.0},"99":{"tf":1.0}}},"df":0,"docs":{}}}},"w":{"a":{"df":0,"docs":{},"r":{"df":3,"docs":{"133":{"tf":1.0},"189":{"tf":1.0},"233":{"tf":1.0}}},"y":{"df":3,"docs":{"117":{"tf":1.0},"170":{"tf":1.0},"212":{"tf":1.0}}}},"df":0,"docs":{}}},"b":{"/":{"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{".":{"df":0,"docs":{},"m":{"d":{"df":1,"docs":{"268":{"tf":2.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"a":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"/":{"c":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":1,"docs":{"62":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{},"s":{"df":0,"docs":{},"r":{"c":{"/":{"d":{"b":{"/":{"d":{"b":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"62":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"62":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":2,"docs":{"268":{"tf":2.0},"269":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}}}}}}}}},"1":{"df":2,"docs":{"152":{"tf":1.0},"157":{"tf":1.4142135623730951}}},"2":{"df":1,"docs":{"158":{"tf":1.4142135623730951}}},"3":{"df":1,"docs":{"159":{"tf":1.4142135623730951}}},"a":{"c":{"df":0,"docs":{},"k":{"df":10,"docs":{"103":{"tf":1.4142135623730951},"107":{"tf":1.0},"115":{"tf":1.7320508075688772},"158":{"tf":1.0},"214":{"tf":1.0},"242":{"tf":1.0},"246":{"tf":1.0},"249":{"tf":2.0},"274":{"tf":1.0},"283":{"tf":1.0}},"e":{"d":{"df":0,"docs":{},"o":{"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":1,"docs":{"242":{"tf":1.0}}}}}},"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"51":{"tf":1.0}}},"df":0,"docs":{}}},"g":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":3,"docs":{"115":{"tf":1.4142135623730951},"203":{"tf":1.0},"58":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}}},"o":{"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":6,"docs":{"225":{"tf":1.0},"226":{"tf":1.4142135623730951},"236":{"tf":1.0},"239":{"tf":1.0},"242":{"tf":1.0},"245":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"p":{"df":1,"docs":{"158":{"tf":1.7320508075688772}}}},"w":{"a":{"df":0,"docs":{},"r":{"d":{"df":2,"docs":{"116":{"tf":1.0},"268":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"d":{"df":19,"docs":{"115":{"tf":1.0},"150":{"tf":1.0},"154":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.4142135623730951},"157":{"tf":1.0},"158":{"tf":1.7320508075688772},"159":{"tf":1.4142135623730951},"169":{"tf":1.7320508075688772},"170":{"tf":2.0},"171":{"tf":1.7320508075688772},"172":{"tf":1.0},"182":{"tf":1.0},"183":{"tf":2.0},"184":{"tf":2.0},"212":{"tf":2.0},"213":{"tf":1.4142135623730951},"226":{"tf":1.0},"81":{"tf":1.0}}},"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"64":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"n":{"d":{"df":1,"docs":{"283":{"tf":1.0}}},"df":0,"docs":{}},"r":{"df":1,"docs":{"205":{"tf":1.7320508075688772}}},"s":{"df":0,"docs":{},"e":{"df":18,"docs":{"163":{"tf":1.0},"164":{"tf":1.0},"172":{"tf":1.4142135623730951},"197":{"tf":1.0},"207":{"tf":1.4142135623730951},"208":{"tf":1.0},"210":{"tf":1.0},"213":{"tf":1.4142135623730951},"214":{"tf":1.4142135623730951},"215":{"tf":1.4142135623730951},"217":{"tf":1.0},"226":{"tf":1.0},"233":{"tf":1.0},"253":{"tf":1.0},"283":{"tf":1.0},"37":{"tf":1.0},"7":{"tf":1.0},"82":{"tf":1.4142135623730951}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"175":{"tf":1.0}}}}}},"h":{"/":{"df":0,"docs":{},"z":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"11":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{},"r":{"c":{"df":2,"docs":{"11":{"tf":1.0},"12":{"tf":1.0}}},"df":0,"docs":{}}},"i":{"c":{"df":2,"docs":{"274":{"tf":1.0},"37":{"tf":1.0}}},"df":2,"docs":{"134":{"tf":1.0},"59":{"tf":1.0}}}}},"df":8,"docs":{"149":{"tf":1.0},"151":{"tf":1.0},"152":{"tf":1.0},"163":{"tf":1.0},"170":{"tf":1.4142135623730951},"179":{"tf":1.0},"180":{"tf":1.0},"280":{"tf":1.0}},"e":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":5,"docs":{"226":{"tf":1.0},"263":{"tf":1.0},"274":{"tf":1.0},"58":{"tf":1.0},"64":{"tf":1.0}}}}},"df":19,"docs":{"115":{"tf":1.0},"152":{"tf":1.0},"159":{"tf":1.0},"162":{"tf":1.0},"167":{"tf":1.0},"184":{"tf":1.0},"187":{"tf":1.0},"192":{"tf":1.0},"193":{"tf":1.0},"197":{"tf":1.0},"227":{"tf":1.0},"231":{"tf":1.0},"233":{"tf":1.0},"242":{"tf":1.4142135623730951},"250":{"tf":1.4142135623730951},"274":{"tf":1.4142135623730951},"60":{"tf":1.0},"78":{"tf":1.0},"84":{"tf":1.0}},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":19,"docs":{"11":{"tf":1.4142135623730951},"118":{"tf":1.0},"119":{"tf":1.0},"12":{"tf":1.0},"120":{"tf":1.0},"122":{"tf":1.0},"145":{"tf":1.0},"177":{"tf":1.0},"20":{"tf":1.4142135623730951},"204":{"tf":1.0},"213":{"tf":1.0},"245":{"tf":1.4142135623730951},"256":{"tf":1.7320508075688772},"259":{"tf":1.0},"266":{"tf":1.0},"58":{"tf":1.4142135623730951},"6":{"tf":1.0},"62":{"tf":1.4142135623730951},"7":{"tf":1.7320508075688772}},"e":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"26":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"o":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":1,"docs":{"283":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"p":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":1,"docs":{"283":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":2,"docs":{"279":{"tf":1.0},"59":{"tf":1.0}}}}},"h":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":2,"docs":{"245":{"tf":1.0},"283":{"tf":1.0}}}},"v":{"df":2,"docs":{"60":{"tf":1.0},"61":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":4,"docs":{"140":{"tf":1.0},"156":{"tf":1.0},"61":{"tf":1.7320508075688772},"84":{"tf":1.0}}},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"37":{"tf":1.0}}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"25":{"tf":1.0},"62":{"tf":1.0}}},"df":0,"docs":{}}}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":3,"docs":{"194":{"tf":1.0},"233":{"tf":1.4142135623730951},"79":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"w":{"df":18,"docs":{"105":{"tf":1.0},"11":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":1.0},"145":{"tf":1.0},"200":{"tf":1.0},"203":{"tf":1.0},"226":{"tf":1.0},"241":{"tf":1.0},"242":{"tf":1.0},"25":{"tf":1.0},"259":{"tf":1.0},"264":{"tf":1.0},"274":{"tf":1.0},"279":{"tf":1.0},"32":{"tf":1.0},"59":{"tf":1.0},"6":{"tf":1.0}}}}},"n":{"c":{"df":0,"docs":{},"h":{"0":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"60":{"tf":1.0}}}},"df":1,"docs":{"60":{"tf":1.0}}},"1":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"60":{"tf":1.0}}}},"df":1,"docs":{"60":{"tf":1.0}}},"2":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"60":{"tf":1.0}}}},"df":1,"docs":{"60":{"tf":1.0}}},"df":2,"docs":{"60":{"tf":2.23606797749979},"61":{"tf":1.0}},"e":{"df":0,"docs":{},"s":{"/":{"*":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"61":{"tf":1.0}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"m":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":3,"docs":{"58":{"tf":1.4142135623730951},"59":{"tf":1.7320508075688772},"60":{"tf":3.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":8,"docs":{"140":{"tf":1.0},"170":{"tf":1.0},"198":{"tf":1.0},"201":{"tf":1.0},"221":{"tf":1.0},"274":{"tf":1.0},"60":{"tf":1.0},"64":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"t":{"df":8,"docs":{"101":{"tf":1.0},"104":{"tf":1.0},"123":{"tf":1.4142135623730951},"149":{"tf":1.0},"164":{"tf":1.0},"170":{"tf":1.0},"43":{"tf":1.4142135623730951},"75":{"tf":1.0}}}},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":11,"docs":{"115":{"tf":1.4142135623730951},"124":{"tf":1.0},"165":{"tf":1.0},"167":{"tf":1.0},"170":{"tf":1.0},"196":{"tf":1.0},"197":{"tf":1.0},"200":{"tf":1.0},"43":{"tf":1.0},"62":{"tf":1.0},"79":{"tf":1.0}}}}},"w":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":23,"docs":{"104":{"tf":1.0},"106":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"150":{"tf":1.0},"165":{"tf":1.4142135623730951},"169":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"20":{"tf":1.0},"204":{"tf":1.0},"214":{"tf":1.0},"216":{"tf":1.7320508075688772},"220":{"tf":1.0},"224":{"tf":1.0},"225":{"tf":1.0},"229":{"tf":1.0},"233":{"tf":1.0},"250":{"tf":1.0},"79":{"tf":1.0},"84":{"tf":1.0},"89":{"tf":1.4142135623730951}}}}}}},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"d":{"df":4,"docs":{"111":{"tf":1.0},"204":{"tf":1.0},"208":{"tf":1.0},"216":{"tf":1.0}}},"df":0,"docs":{}}}}},"i":{"df":0,"docs":{},"g":{"df":3,"docs":{"236":{"tf":1.0},"64":{"tf":1.0},"81":{"tf":1.0}},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"210":{"tf":1.0}}}}}}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"227":{"tf":1.0}}}}}},"n":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":10,"docs":{"14":{"tf":1.0},"163":{"tf":1.4142135623730951},"167":{"tf":1.0},"169":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.4142135623730951},"172":{"tf":1.4142135623730951},"175":{"tf":1.4142135623730951},"253":{"tf":1.0},"274":{"tf":1.0}}}}},"d":{"df":24,"docs":{"0":{"tf":1.0},"101":{"tf":1.4142135623730951},"103":{"tf":1.4142135623730951},"105":{"tf":1.0},"106":{"tf":1.0},"107":{"tf":1.0},"108":{"tf":2.23606797749979},"109":{"tf":2.449489742783178},"111":{"tf":1.4142135623730951},"116":{"tf":1.0},"140":{"tf":1.0},"162":{"tf":1.0},"253":{"tf":1.0},"281":{"tf":1.0},"45":{"tf":1.7320508075688772},"46":{"tf":1.7320508075688772},"69":{"tf":1.0},"7":{"tf":1.0},"70":{"tf":2.0},"71":{"tf":1.4142135623730951},"72":{"tf":1.4142135623730951},"73":{"tf":1.4142135623730951},"74":{"tf":1.0},"90":{"tf":1.4142135623730951}},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":2,"docs":{"104":{"tf":1.7320508075688772},"69":{"tf":1.4142135623730951}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"k":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"70":{"tf":1.0}}}}}}}},"s":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":1,"docs":{"73":{"tf":1.0}}}}}}}},"_":{"df":0,"docs":{},"u":{"d":{"df":0,"docs":{},"l":{"_":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"74":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":1,"docs":{"11":{"tf":1.0}}},"t":{"df":8,"docs":{"104":{"tf":1.4142135623730951},"116":{"tf":1.7320508075688772},"17":{"tf":1.0},"174":{"tf":1.0},"20":{"tf":1.0},"226":{"tf":1.0},"229":{"tf":1.0},"6":{"tf":1.0}}}},"l":{"a":{"df":0,"docs":{},"h":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"61":{"tf":1.0}}}},"df":1,"docs":{"61":{"tf":1.0}}}},"df":0,"docs":{},"o":{"b":{"df":1,"docs":{"242":{"tf":1.0}}},"c":{"df":0,"docs":{},"k":{"df":3,"docs":{"115":{"tf":1.0},"194":{"tf":1.0},"81":{"tf":1.0}},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{".":{"df":0,"docs":{},"x":{"c":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"p":{"b":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"df":1,"docs":{"36":{"tf":1.0}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":1,"docs":{"36":{"tf":1.0}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":1,"docs":{"36":{"tf":1.0}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"g":{"df":1,"docs":{"90":{"tf":1.4142135623730951}}}}},"o":{"d":{"df":0,"docs":{},"i":{"df":1,"docs":{"189":{"tf":1.0}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"115":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":2,"docs":{"45":{"tf":1.0},"46":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"k":{"df":2,"docs":{"286":{"tf":1.4142135623730951},"43":{"tf":1.0}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":1,"docs":{"82":{"tf":1.0}}}}}},"m":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":10,"docs":{"226":{"tf":1.4142135623730951},"228":{"tf":1.0},"231":{"tf":1.0},"232":{"tf":1.0},"242":{"tf":1.4142135623730951},"257":{"tf":1.0},"264":{"tf":1.0},"62":{"tf":1.0},"83":{"tf":1.0},"86":{"tf":1.0}},"g":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"62":{"tf":2.0}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"l":{"df":2,"docs":{"116":{"tf":1.4142135623730951},"242":{"tf":1.0}}},"t":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":1,"docs":{"274":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"t":{"df":0,"docs":{},"h":{"df":21,"docs":{"117":{"tf":1.0},"123":{"tf":1.0},"145":{"tf":2.0},"146":{"tf":1.0},"17":{"tf":1.0},"175":{"tf":1.0},"18":{"tf":1.0},"196":{"tf":1.0},"198":{"tf":1.4142135623730951},"200":{"tf":1.0},"221":{"tf":1.0},"222":{"tf":1.0},"226":{"tf":1.0},"241":{"tf":1.4142135623730951},"249":{"tf":1.4142135623730951},"271":{"tf":1.0},"274":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"7":{"tf":1.0},"86":{"tf":1.4142135623730951}}},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":2,"docs":{"126":{"tf":1.0},"60":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"n":{"d":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":4,"docs":{"229":{"tf":1.0},"43":{"tf":1.0},"51":{"tf":1.0},"79":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"x":{"df":2,"docs":{"126":{"tf":1.0},"128":{"tf":1.0}}}},"r":{"a":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":15,"docs":{"118":{"tf":1.0},"25":{"tf":1.0},"26":{"tf":2.0},"267":{"tf":1.4142135623730951},"268":{"tf":2.0},"269":{"tf":1.4142135623730951},"270":{"tf":1.4142135623730951},"274":{"tf":2.23606797749979},"31":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"38":{"tf":2.23606797749979},"39":{"tf":3.7416573867739413},"40":{"tf":2.6457513110645907},"64":{"tf":1.0},"7":{"tf":1.4142135623730951}}}},"d":{"df":2,"docs":{"105":{"tf":1.0},"64":{"tf":1.0}}},"df":0,"docs":{}},"v":{"df":0,"docs":{},"e":{"df":1,"docs":{"81":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"k":{"a":{"df":0,"docs":{},"g":{"df":4,"docs":{"118":{"tf":1.0},"121":{"tf":1.0},"166":{"tf":1.0},"278":{"tf":1.0}}}},"df":13,"docs":{"104":{"tf":1.0},"105":{"tf":1.0},"116":{"tf":1.0},"118":{"tf":2.23606797749979},"119":{"tf":1.0},"140":{"tf":1.0},"141":{"tf":1.0},"166":{"tf":1.0},"21":{"tf":1.0},"235":{"tf":1.0},"39":{"tf":1.7320508075688772},"40":{"tf":1.7320508075688772},"7":{"tf":1.0}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"117":{"tf":2.449489742783178}}}}}}}}},"df":0,"docs":{},"w":{"df":2,"docs":{"11":{"tf":1.0},"37":{"tf":1.0}}}},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"252":{"tf":1.0}}}}}},"n":{"df":0,"docs":{},"g":{"df":3,"docs":{"117":{"tf":1.0},"59":{"tf":1.0},"64":{"tf":1.0}}}}},"o":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"161":{"tf":1.0}}}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"171":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"266":{"tf":1.0}}}}},"w":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":8,"docs":{"134":{"tf":1.0},"186":{"tf":1.4142135623730951},"187":{"tf":1.4142135623730951},"188":{"tf":1.0},"190":{"tf":1.0},"192":{"tf":1.0},"194":{"tf":1.0},"197":{"tf":1.7320508075688772}}}}}}}},"u":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":3,"docs":{"189":{"tf":1.0},"214":{"tf":1.0},"216":{"tf":1.7320508075688772}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"105":{"tf":1.0},"106":{"tf":1.4142135623730951}}}}}},"g":{"df":53,"docs":{"1":{"tf":1.0},"100":{"tf":1.0},"106":{"tf":1.0},"109":{"tf":1.0},"116":{"tf":1.7320508075688772},"117":{"tf":1.0},"119":{"tf":1.0},"122":{"tf":1.0},"123":{"tf":1.0},"124":{"tf":1.4142135623730951},"127":{"tf":1.0},"133":{"tf":1.0},"138":{"tf":1.0},"147":{"tf":1.0},"15":{"tf":1.0},"159":{"tf":1.0},"171":{"tf":1.0},"174":{"tf":1.0},"175":{"tf":1.0},"185":{"tf":1.0},"201":{"tf":1.0},"218":{"tf":1.0},"22":{"tf":1.0},"224":{"tf":1.0},"228":{"tf":1.0},"242":{"tf":1.0},"250":{"tf":1.0},"253":{"tf":1.0},"258":{"tf":1.0},"259":{"tf":1.0},"261":{"tf":1.0},"263":{"tf":1.0},"264":{"tf":1.0},"273":{"tf":1.0},"277":{"tf":1.0},"282":{"tf":1.0},"283":{"tf":1.0},"286":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.4142135623730951},"4":{"tf":1.4142135623730951},"40":{"tf":1.0},"43":{"tf":1.0},"51":{"tf":1.0},"52":{"tf":1.0},"56":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0},"67":{"tf":1.0},"74":{"tf":1.0},"8":{"tf":1.0},"90":{"tf":1.0}},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"df":2,"docs":{"1":{"tf":1.0},"261":{"tf":1.0}}},"df":0,"docs":{}}}}}},"i":{"df":0,"docs":{},"l":{"d":{".":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"d":{"df":0,"docs":{},"l":{"df":9,"docs":{"103":{"tf":1.4142135623730951},"108":{"tf":1.0},"117":{"tf":1.0},"20":{"tf":1.0},"22":{"tf":1.0},"224":{"tf":1.4142135623730951},"37":{"tf":1.0},"45":{"tf":1.0},"70":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"r":{"df":1,"docs":{"105":{"tf":1.0}}}},"/":{"d":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"s":{"/":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":1,"docs":{"286":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"_":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"175":{"tf":1.0}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"105":{"tf":1.0}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":2,"docs":{"113":{"tf":1.0},"70":{"tf":1.0}}}}}}}},"df":97,"docs":{"10":{"tf":2.449489742783178},"101":{"tf":1.4142135623730951},"105":{"tf":1.0},"108":{"tf":1.7320508075688772},"109":{"tf":2.0},"11":{"tf":4.123105625617661},"114":{"tf":1.4142135623730951},"118":{"tf":1.0},"119":{"tf":1.4142135623730951},"12":{"tf":2.0},"122":{"tf":2.6457513110645907},"124":{"tf":1.0},"13":{"tf":1.4142135623730951},"14":{"tf":2.23606797749979},"140":{"tf":1.0},"15":{"tf":2.0},"16":{"tf":1.0},"161":{"tf":1.4142135623730951},"163":{"tf":2.23606797749979},"165":{"tf":1.4142135623730951},"166":{"tf":1.0},"167":{"tf":2.0},"169":{"tf":1.7320508075688772},"17":{"tf":1.4142135623730951},"170":{"tf":2.449489742783178},"171":{"tf":1.0},"175":{"tf":2.23606797749979},"18":{"tf":2.23606797749979},"19":{"tf":1.4142135623730951},"197":{"tf":1.0},"20":{"tf":1.4142135623730951},"21":{"tf":1.4142135623730951},"22":{"tf":2.8284271247461903},"221":{"tf":1.0},"224":{"tf":1.7320508075688772},"23":{"tf":2.0},"233":{"tf":1.0},"237":{"tf":1.0},"24":{"tf":1.7320508075688772},"25":{"tf":1.7320508075688772},"252":{"tf":2.0},"253":{"tf":1.0},"26":{"tf":1.0},"262":{"tf":1.0},"263":{"tf":1.0},"266":{"tf":2.0},"267":{"tf":2.0},"27":{"tf":1.0},"271":{"tf":2.23606797749979},"272":{"tf":1.7320508075688772},"274":{"tf":3.872983346207417},"278":{"tf":1.4142135623730951},"279":{"tf":2.449489742783178},"28":{"tf":2.449489742783178},"280":{"tf":1.4142135623730951},"282":{"tf":2.449489742783178},"285":{"tf":1.4142135623730951},"286":{"tf":1.7320508075688772},"29":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.4142135623730951},"32":{"tf":1.7320508075688772},"33":{"tf":2.449489742783178},"34":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.7320508075688772},"37":{"tf":2.8284271247461903},"38":{"tf":2.449489742783178},"39":{"tf":3.0},"40":{"tf":3.3166247903554},"49":{"tf":1.4142135623730951},"5":{"tf":2.0},"58":{"tf":1.7320508075688772},"59":{"tf":1.7320508075688772},"61":{"tf":1.0},"64":{"tf":1.7320508075688772},"67":{"tf":1.0},"69":{"tf":1.7320508075688772},"70":{"tf":1.7320508075688772},"73":{"tf":2.23606797749979},"75":{"tf":2.449489742783178},"76":{"tf":1.0},"77":{"tf":2.0},"78":{"tf":1.0},"79":{"tf":1.0},"80":{"tf":1.0},"81":{"tf":1.4142135623730951},"82":{"tf":1.0},"83":{"tf":1.0},"84":{"tf":1.0},"85":{"tf":1.0},"86":{"tf":1.0},"87":{"tf":1.0},"88":{"tf":1.0},"89":{"tf":1.0},"9":{"tf":2.0},"90":{"tf":1.7320508075688772}},"s":{"df":0,"docs":{},"r":{"c":{"/":{"df":0,"docs":{},"s":{"df":0,"docs":{},"r":{"c":{"/":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"/":{"df":0,"docs":{},"j":{"a":{"df":0,"docs":{},"v":{"a":{"/":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"k":{"df":0,"docs":{},"t":{"df":1,"docs":{"20":{"tf":1.0}}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{},"t":{"df":24,"docs":{"0":{"tf":1.0},"11":{"tf":1.0},"119":{"tf":1.0},"13":{"tf":1.0},"14":{"tf":1.0},"167":{"tf":2.0},"174":{"tf":1.0},"175":{"tf":1.4142135623730951},"222":{"tf":1.4142135623730951},"227":{"tf":1.0},"251":{"tf":1.4142135623730951},"271":{"tf":1.4142135623730951},"274":{"tf":1.4142135623730951},"28":{"tf":1.0},"280":{"tf":1.0},"282":{"tf":1.0},"286":{"tf":1.4142135623730951},"33":{"tf":1.0},"37":{"tf":1.7320508075688772},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0}},"i":{"df":0,"docs":{},"n":{"df":3,"docs":{"43":{"tf":1.0},"69":{"tf":1.0},"92":{"tf":1.0}}}}}}},"m":{"df":0,"docs":{},"p":{"df":5,"docs":{"119":{"tf":1.7320508075688772},"263":{"tf":1.4142135623730951},"270":{"tf":1.0},"279":{"tf":1.4142135623730951},"82":{"tf":1.4142135623730951}}}},"n":{"c":{"df":0,"docs":{},"h":{"df":2,"docs":{"104":{"tf":1.0},"111":{"tf":1.0}}}},"d":{"df":0,"docs":{},"l":{"df":13,"docs":{"161":{"tf":1.0},"163":{"tf":1.0},"167":{"tf":2.0},"169":{"tf":1.0},"170":{"tf":1.7320508075688772},"171":{"tf":1.0},"172":{"tf":1.0},"179":{"tf":1.4142135623730951},"180":{"tf":1.0},"183":{"tf":1.4142135623730951},"185":{"tf":1.0},"252":{"tf":1.0},"73":{"tf":1.0}}}},"df":0,"docs":{}},"r":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"241":{"tf":1.0}}}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"i":{"df":1,"docs":{"256":{"tf":1.0}}},"t":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"282":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"117":{"tf":1.0}}}}}}}},"c":{"8":{"9":{"df":1,"docs":{"116":{"tf":1.0}}},"df":0,"docs":{}},"a":{"c":{"df":0,"docs":{},"h":{"df":7,"docs":{"11":{"tf":1.0},"189":{"tf":1.0},"194":{"tf":1.0},"201":{"tf":2.0},"25":{"tf":2.23606797749979},"26":{"tf":2.0},"59":{"tf":1.0}},"e":{"_":{"df":0,"docs":{},"s":{"df":1,"docs":{"264":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"l":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":3,"docs":{"246":{"tf":1.0},"249":{"tf":1.0},"7":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"268":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"l":{"b":{"a":{"c":{"df":0,"docs":{},"k":{"df":4,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"115":{"tf":2.449489742783178},"116":{"tf":1.4142135623730951}},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"115":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":20,"docs":{"101":{"tf":1.0},"111":{"tf":1.0},"115":{"tf":2.6457513110645907},"126":{"tf":1.4142135623730951},"13":{"tf":1.0},"131":{"tf":1.0},"145":{"tf":1.7320508075688772},"147":{"tf":1.0},"201":{"tf":1.0},"222":{"tf":1.0},"241":{"tf":1.4142135623730951},"242":{"tf":1.4142135623730951},"244":{"tf":1.0},"245":{"tf":1.7320508075688772},"283":{"tf":1.7320508075688772},"77":{"tf":1.4142135623730951},"78":{"tf":1.0},"81":{"tf":1.0},"84":{"tf":1.0},"90":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"82":{"tf":1.0}}}}}},"m":{"df":0,"docs":{},"e":{"df":2,"docs":{"256":{"tf":1.0},"60":{"tf":1.0}},"l":{"c":{"a":{"df":0,"docs":{},"s":{"df":1,"docs":{"108":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"n":{"'":{"df":0,"docs":{},"t":{"df":6,"docs":{"128":{"tf":1.0},"175":{"tf":1.0},"194":{"tf":1.0},"226":{"tf":1.0},"231":{"tf":1.4142135623730951},"82":{"tf":1.0}}}},"d":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"267":{"tf":1.7320508075688772},"84":{"tf":1.0}}},"df":0,"docs":{}}},"df":1,"docs":{"227":{"tf":1.0}},"o":{"df":0,"docs":{},"n":{"df":5,"docs":{"228":{"tf":1.0},"232":{"tf":1.0},"233":{"tf":1.0},"242":{"tf":1.0},"263":{"tf":1.0}}}}},"p":{"a":{"b":{"df":0,"docs":{},"l":{"df":13,"docs":{"124":{"tf":1.0},"190":{"tf":1.0},"192":{"tf":1.0},"193":{"tf":1.0},"194":{"tf":1.0},"196":{"tf":1.0},"197":{"tf":1.0},"201":{"tf":2.23606797749979},"22":{"tf":1.0},"228":{"tf":1.0},"262":{"tf":1.0},"283":{"tf":1.0},"62":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"104":{"tf":1.0}}}},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":2,"docs":{"101":{"tf":1.0},"242":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"e":{"df":7,"docs":{"132":{"tf":1.0},"228":{"tf":1.4142135623730951},"232":{"tf":1.0},"238":{"tf":1.0},"58":{"tf":1.4142135623730951},"81":{"tf":1.0},"82":{"tf":1.4142135623730951}},"f":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"227":{"tf":1.0}}}}}}}},"g":{"df":0,"docs":{},"o":{".":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"df":2,"docs":{"64":{"tf":1.7320508075688772},"69":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":14,"docs":{"103":{"tf":1.7320508075688772},"105":{"tf":1.0},"123":{"tf":1.4142135623730951},"124":{"tf":1.0},"200":{"tf":1.0},"251":{"tf":1.0},"28":{"tf":1.0},"33":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.4142135623730951},"69":{"tf":1.4142135623730951},"70":{"tf":1.0},"72":{"tf":1.0},"73":{"tf":1.0}}}}}}},"df":14,"docs":{"104":{"tf":1.0},"105":{"tf":1.0},"107":{"tf":1.0},"11":{"tf":1.7320508075688772},"122":{"tf":1.0},"274":{"tf":1.0},"279":{"tf":1.4142135623730951},"286":{"tf":1.0},"43":{"tf":1.0},"58":{"tf":3.1622776601683795},"59":{"tf":1.0},"60":{"tf":1.7320508075688772},"64":{"tf":1.4142135623730951},"69":{"tf":1.4142135623730951}}}},"t":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"g":{"df":6,"docs":{"161":{"tf":1.0},"163":{"tf":2.0},"166":{"tf":1.0},"167":{"tf":2.0},"170":{"tf":1.7320508075688772},"175":{"tf":1.0}},"e":{"'":{"df":1,"docs":{"170":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"s":{"df":0,"docs":{},"e":{"df":20,"docs":{"104":{"tf":1.0},"105":{"tf":1.0},"106":{"tf":1.7320508075688772},"115":{"tf":1.4142135623730951},"116":{"tf":1.0},"152":{"tf":1.0},"156":{"tf":1.0},"17":{"tf":1.0},"187":{"tf":1.0},"194":{"tf":1.4142135623730951},"201":{"tf":1.0},"228":{"tf":1.0},"233":{"tf":1.0},"245":{"tf":1.0},"274":{"tf":1.0},"276":{"tf":1.0},"70":{"tf":1.0},"79":{"tf":1.0},"98":{"tf":1.0},"99":{"tf":1.0}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"62":{"tf":1.0}}}}}}}},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":1,"docs":{"7":{"tf":1.0}}}}},"s":{"df":11,"docs":{"104":{"tf":1.0},"113":{"tf":1.0},"115":{"tf":1.0},"117":{"tf":1.0},"201":{"tf":1.0},"212":{"tf":1.4142135623730951},"225":{"tf":1.0},"260":{"tf":1.0},"50":{"tf":1.0},"59":{"tf":1.0},"70":{"tf":1.0}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"64":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"v":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":2,"docs":{"116":{"tf":1.0},"21":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"c":{"=":{"a":{"a":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"h":{"6":{"4":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"37":{"tf":1.0}},"p":{"df":1,"docs":{"37":{"tf":1.0}}}},"d":{"df":2,"docs":{"11":{"tf":1.4142135623730951},"25":{"tf":1.0}},"y":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"b":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"70":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":13,"docs":{"103":{"tf":2.6457513110645907},"111":{"tf":1.0},"114":{"tf":1.0},"163":{"tf":1.0},"171":{"tf":1.4142135623730951},"174":{"tf":1.0},"179":{"tf":1.0},"180":{"tf":1.0},"185":{"tf":1.4142135623730951},"251":{"tf":1.0},"252":{"tf":1.0},"28":{"tf":1.0},"33":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"l":{"df":10,"docs":{"120":{"tf":2.23606797749979},"121":{"tf":1.4142135623730951},"122":{"tf":2.23606797749979},"123":{"tf":2.23606797749979},"124":{"tf":2.23606797749979},"260":{"tf":1.0},"261":{"tf":1.7320508075688772},"262":{"tf":1.4142135623730951},"263":{"tf":1.0},"274":{"tf":1.7320508075688772}}}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":1,"docs":{"64":{"tf":1.0}}}}}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":5,"docs":{"115":{"tf":1.4142135623730951},"226":{"tf":1.0},"228":{"tf":1.0},"58":{"tf":1.0},"82":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":3,"docs":{"197":{"tf":1.0},"226":{"tf":1.4142135623730951},"229":{"tf":1.0}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":1,"docs":{"201":{"tf":1.0}}}}}}},"h":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":4,"docs":{"106":{"tf":1.0},"274":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0}}}},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":4,"docs":{"115":{"tf":1.4142135623730951},"228":{"tf":1.4142135623730951},"229":{"tf":1.0},"6":{"tf":1.0}}}}}}},"n":{"c":{"df":4,"docs":{"159":{"tf":1.0},"19":{"tf":1.0},"209":{"tf":1.4142135623730951},"214":{"tf":1.0}}},"df":0,"docs":{},"g":{"df":65,"docs":{"10":{"tf":1.0},"104":{"tf":1.0},"105":{"tf":1.4142135623730951},"108":{"tf":1.0},"118":{"tf":2.23606797749979},"119":{"tf":1.7320508075688772},"121":{"tf":1.0},"123":{"tf":1.4142135623730951},"124":{"tf":3.1622776601683795},"126":{"tf":1.4142135623730951},"145":{"tf":1.4142135623730951},"146":{"tf":1.0},"16":{"tf":1.0},"163":{"tf":1.0},"167":{"tf":1.0},"169":{"tf":1.7320508075688772},"170":{"tf":2.0},"171":{"tf":1.7320508075688772},"172":{"tf":1.4142135623730951},"175":{"tf":1.0},"180":{"tf":1.0},"184":{"tf":1.7320508075688772},"187":{"tf":1.0},"189":{"tf":1.0},"190":{"tf":1.7320508075688772},"193":{"tf":1.0},"197":{"tf":1.7320508075688772},"20":{"tf":1.4142135623730951},"200":{"tf":1.0},"201":{"tf":2.0},"21":{"tf":1.4142135623730951},"22":{"tf":1.0},"228":{"tf":1.0},"231":{"tf":1.4142135623730951},"233":{"tf":2.6457513110645907},"236":{"tf":1.4142135623730951},"242":{"tf":1.0},"249":{"tf":2.449489742783178},"25":{"tf":1.0},"250":{"tf":2.449489742783178},"252":{"tf":1.0},"26":{"tf":2.23606797749979},"263":{"tf":1.4142135623730951},"268":{"tf":3.0},"270":{"tf":2.23606797749979},"279":{"tf":1.0},"280":{"tf":1.0},"29":{"tf":1.0},"30":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951},"34":{"tf":1.0},"35":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"39":{"tf":2.0},"40":{"tf":1.7320508075688772},"49":{"tf":1.0},"58":{"tf":1.4142135623730951},"64":{"tf":1.0},"7":{"tf":2.23606797749979},"81":{"tf":1.0},"82":{"tf":1.7320508075688772},"84":{"tf":2.23606797749979},"86":{"tf":2.449489742783178},"87":{"tf":1.0},"88":{"tf":1.0}},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{".":{"df":0,"docs":{},"m":{"d":{"df":2,"docs":{"268":{"tf":2.0},"7":{"tf":1.0}}},"df":0,"docs":{}}},"]":{"(":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{":":{"/":{"/":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"u":{"b":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"/":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"268":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"268":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}},"df":4,"docs":{"21":{"tf":1.0},"269":{"tf":1.0},"282":{"tf":1.0},"7":{"tf":1.0}}}}}}},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":3,"docs":{"122":{"tf":1.0},"271":{"tf":1.0},"274":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"t":{"df":3,"docs":{"215":{"tf":1.0},"216":{"tf":1.0},"258":{"tf":1.0}}}},"t":{"df":1,"docs":{"1":{"tf":1.0}}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"k":{"_":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"d":{"_":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"_":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"_":{"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":2,"docs":{"145":{"tf":1.4142135623730951},"146":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"97":{"tf":1.0}}}}}},"df":0,"docs":{}}}}},"df":35,"docs":{"104":{"tf":1.0},"105":{"tf":1.4142135623730951},"11":{"tf":1.4142135623730951},"12":{"tf":1.0},"122":{"tf":1.4142135623730951},"124":{"tf":1.0},"126":{"tf":1.0},"14":{"tf":1.0},"151":{"tf":1.4142135623730951},"152":{"tf":1.7320508075688772},"155":{"tf":1.4142135623730951},"156":{"tf":1.4142135623730951},"18":{"tf":1.0},"189":{"tf":1.0},"201":{"tf":1.0},"22":{"tf":1.0},"242":{"tf":1.0},"245":{"tf":1.4142135623730951},"26":{"tf":1.0},"274":{"tf":2.449489742783178},"282":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0},"51":{"tf":1.0},"58":{"tf":1.0},"62":{"tf":2.6457513110645907},"64":{"tf":2.23606797749979},"69":{"tf":1.0},"7":{"tf":1.0},"70":{"tf":1.0},"73":{"tf":2.6457513110645907},"81":{"tf":1.0},"96":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"268":{"tf":1.4142135623730951}}}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":17,"docs":{"124":{"tf":1.0},"18":{"tf":1.7320508075688772},"23":{"tf":2.0},"24":{"tf":1.7320508075688772},"25":{"tf":1.0},"270":{"tf":1.0},"28":{"tf":1.0},"29":{"tf":1.7320508075688772},"30":{"tf":1.4142135623730951},"31":{"tf":2.0},"32":{"tf":2.0},"33":{"tf":1.0},"34":{"tf":2.0},"35":{"tf":1.4142135623730951},"36":{"tf":2.23606797749979},"37":{"tf":1.0},"49":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":5,"docs":{"167":{"tf":1.0},"279":{"tf":1.0},"280":{"tf":1.7320508075688772},"29":{"tf":1.4142135623730951},"34":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":1,"docs":{"97":{"tf":1.0}}}}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"270":{"tf":1.0}}}}}},"i":{"df":0,"docs":{},"p":{"df":1,"docs":{"17":{"tf":1.4142135623730951}}}},"o":{"df":0,"docs":{},"i":{"c":{"df":5,"docs":{"152":{"tf":1.0},"217":{"tf":1.0},"226":{"tf":1.4142135623730951},"227":{"tf":1.0},"232":{"tf":1.0}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"s":{"df":11,"docs":{"126":{"tf":1.0},"165":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"208":{"tf":1.0},"225":{"tf":1.4142135623730951},"226":{"tf":1.0},"229":{"tf":1.0},"31":{"tf":1.0},"98":{"tf":1.0}}}},"s":{"df":0,"docs":{},"e":{"df":1,"docs":{"251":{"tf":1.0}},"n":{"df":5,"docs":{"138":{"tf":1.0},"143":{"tf":1.0},"164":{"tf":1.0},"199":{"tf":1.7320508075688772},"208":{"tf":1.4142135623730951}}}}}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":3,"docs":{"206":{"tf":1.4142135623730951},"217":{"tf":1.0},"218":{"tf":1.4142135623730951}}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":2,"docs":{"105":{"tf":1.0},"106":{"tf":1.0}}}}}},"i":{"df":20,"docs":{"11":{"tf":1.4142135623730951},"118":{"tf":1.0},"119":{"tf":1.0},"263":{"tf":2.0},"266":{"tf":1.4142135623730951},"270":{"tf":1.0},"271":{"tf":1.0},"274":{"tf":1.7320508075688772},"275":{"tf":1.0},"276":{"tf":1.4142135623730951},"277":{"tf":1.0},"278":{"tf":1.0},"280":{"tf":1.0},"282":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0},"49":{"tf":1.0},"51":{"tf":1.0},"64":{"tf":1.4142135623730951},"92":{"tf":1.0}},"r":{"c":{"df":0,"docs":{},"l":{"df":1,"docs":{"263":{"tf":1.0}},"e":{"c":{"df":0,"docs":{},"i":{"df":3,"docs":{"263":{"tf":1.0},"274":{"tf":2.449489742783178},"277":{"tf":2.0}}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"197":{"tf":1.0}}}},"df":0,"docs":{}},"m":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"170":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}}},"l":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"210":{"tf":1.0}}}},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"37":{"tf":1.4142135623730951}}}},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":1,"docs":{"6":{"tf":1.0}},"i":{"df":1,"docs":{"233":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"s":{"df":9,"docs":{"100":{"tf":1.0},"103":{"tf":1.4142135623730951},"108":{"tf":1.0},"109":{"tf":1.0},"112":{"tf":1.0},"131":{"tf":2.23606797749979},"132":{"tf":2.0},"98":{"tf":1.4142135623730951},"99":{"tf":1.0}},"i":{"c":{"df":1,"docs":{"277":{"tf":1.0}}},"df":0,"docs":{}},"p":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"223":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"n":{"df":2,"docs":{"240":{"tf":1.0},"279":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"172":{"tf":1.0}}}}},"r":{"df":3,"docs":{"229":{"tf":1.0},"279":{"tf":1.0},"60":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"226":{"tf":1.0}}}},"l":{"df":0,"docs":{},"i":{"df":3,"docs":{"132":{"tf":1.0},"64":{"tf":1.0},"7":{"tf":1.0}}}}}},"df":0,"docs":{}},"i":{"c":{"df":0,"docs":{},"k":{"df":5,"docs":{"117":{"tf":1.4142135623730951},"31":{"tf":2.0},"36":{"tf":2.0},"40":{"tf":1.4142135623730951},"73":{"tf":1.4142135623730951}}}},"df":1,"docs":{"117":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"x":{"c":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"p":{"b":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"df":1,"docs":{"31":{"tf":1.0}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":2,"docs":{"25":{"tf":1.0},"31":{"tf":1.0}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},":":{":":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"d":{"df":0,"docs":{},"s":{"_":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"268":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"283":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":31,"docs":{"10":{"tf":1.0},"105":{"tf":1.0},"107":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"11":{"tf":1.0},"134":{"tf":1.0},"179":{"tf":1.0},"186":{"tf":1.4142135623730951},"192":{"tf":1.0},"193":{"tf":2.0},"194":{"tf":2.0},"196":{"tf":1.0},"197":{"tf":2.6457513110645907},"198":{"tf":2.6457513110645907},"199":{"tf":1.4142135623730951},"200":{"tf":2.8284271247461903},"201":{"tf":1.7320508075688772},"226":{"tf":1.0},"231":{"tf":3.0},"234":{"tf":1.0},"237":{"tf":1.0},"238":{"tf":1.7320508075688772},"254":{"tf":2.0},"256":{"tf":1.4142135623730951},"257":{"tf":1.0},"268":{"tf":1.0},"283":{"tf":1.7320508075688772},"284":{"tf":1.4142135623730951},"31":{"tf":1.0},"58":{"tf":1.4142135623730951}}}}},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":3,"docs":{"262":{"tf":1.4142135623730951},"263":{"tf":1.4142135623730951},"92":{"tf":1.4142135623730951}}}}}},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":7,"docs":{"11":{"tf":1.7320508075688772},"12":{"tf":1.0},"34":{"tf":1.4142135623730951},"36":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0}}}},"s":{"df":0,"docs":{},"e":{"df":5,"docs":{"105":{"tf":1.0},"133":{"tf":1.0},"187":{"tf":1.0},"230":{"tf":1.0},"258":{"tf":1.0}}}}}},"o":{"d":{"df":0,"docs":{},"e":{"'":{"df":1,"docs":{"45":{"tf":1.0}}},"b":{"a":{"df":0,"docs":{},"s":{"df":2,"docs":{"143":{"tf":1.0},"65":{"tf":1.0}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":1,"docs":{"50":{"tf":1.0}}}}},"df":96,"docs":{"0":{"tf":1.0},"1":{"tf":1.0},"101":{"tf":1.0},"103":{"tf":3.3166247903554},"104":{"tf":2.0},"105":{"tf":2.0},"106":{"tf":1.7320508075688772},"107":{"tf":2.23606797749979},"108":{"tf":2.6457513110645907},"109":{"tf":2.449489742783178},"111":{"tf":1.7320508075688772},"114":{"tf":1.7320508075688772},"115":{"tf":2.0},"116":{"tf":1.4142135623730951},"117":{"tf":2.23606797749979},"118":{"tf":2.0},"120":{"tf":1.0},"125":{"tf":1.4142135623730951},"128":{"tf":1.4142135623730951},"132":{"tf":1.0},"133":{"tf":1.0},"140":{"tf":1.0},"145":{"tf":1.7320508075688772},"146":{"tf":1.0},"154":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0},"158":{"tf":1.4142135623730951},"159":{"tf":1.4142135623730951},"163":{"tf":1.4142135623730951},"164":{"tf":1.0},"165":{"tf":1.0},"166":{"tf":1.0},"167":{"tf":2.6457513110645907},"169":{"tf":1.0},"17":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.7320508075688772},"172":{"tf":2.23606797749979},"174":{"tf":1.0},"193":{"tf":1.0},"194":{"tf":1.0},"2":{"tf":1.0},"200":{"tf":2.8284271247461903},"219":{"tf":1.0},"22":{"tf":1.0},"220":{"tf":2.0},"221":{"tf":1.4142135623730951},"222":{"tf":1.4142135623730951},"223":{"tf":1.4142135623730951},"224":{"tf":1.4142135623730951},"232":{"tf":1.0},"241":{"tf":2.0},"245":{"tf":1.0},"249":{"tf":1.4142135623730951},"251":{"tf":1.0},"253":{"tf":1.7320508075688772},"263":{"tf":1.0},"274":{"tf":3.1622776601683795},"279":{"tf":1.0},"28":{"tf":1.0},"282":{"tf":1.0},"283":{"tf":1.0},"31":{"tf":1.0},"33":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.7320508075688772},"43":{"tf":1.7320508075688772},"44":{"tf":2.0},"45":{"tf":3.0},"46":{"tf":2.449489742783178},"49":{"tf":1.0},"50":{"tf":1.4142135623730951},"58":{"tf":1.7320508075688772},"60":{"tf":1.7320508075688772},"63":{"tf":1.4142135623730951},"64":{"tf":2.23606797749979},"65":{"tf":1.4142135623730951},"66":{"tf":1.4142135623730951},"67":{"tf":1.4142135623730951},"68":{"tf":1.0},"69":{"tf":1.7320508075688772},"7":{"tf":2.6457513110645907},"70":{"tf":2.0},"72":{"tf":2.23606797749979},"74":{"tf":1.7320508075688772},"8":{"tf":1.7320508075688772},"81":{"tf":1.4142135623730951},"82":{"tf":1.4142135623730951},"83":{"tf":1.4142135623730951},"9":{"tf":1.4142135623730951},"90":{"tf":1.4142135623730951},"92":{"tf":1.4142135623730951},"93":{"tf":1.0},"95":{"tf":1.4142135623730951},"98":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"229":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"l":{"a":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"274":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":15,"docs":{"0":{"tf":1.0},"189":{"tf":1.0},"194":{"tf":1.0},"203":{"tf":1.0},"226":{"tf":1.7320508075688772},"229":{"tf":1.0},"232":{"tf":2.0},"242":{"tf":1.4142135623730951},"244":{"tf":1.0},"245":{"tf":1.4142135623730951},"248":{"tf":1.4142135623730951},"259":{"tf":2.0},"45":{"tf":1.0},"6":{"tf":1.4142135623730951},"86":{"tf":2.449489742783178}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"'":{"df":1,"docs":{"245":{"tf":1.0}}},"/":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"228":{"tf":1.0}}}}}}}},"df":0,"docs":{}}}},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"128":{"tf":1.0}}}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"283":{"tf":1.4142135623730951}}}},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"n":{"df":2,"docs":{"250":{"tf":1.0},"84":{"tf":1.0}}}}}},"m":{"b":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"58":{"tf":1.0}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":6,"docs":{"145":{"tf":1.4142135623730951},"146":{"tf":1.4142135623730951},"228":{"tf":1.0},"249":{"tf":1.0},"274":{"tf":1.4142135623730951},"39":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":5,"docs":{"116":{"tf":1.0},"205":{"tf":1.0},"261":{"tf":1.0},"281":{"tf":1.0},"282":{"tf":1.0}}},"m":{"a":{"df":1,"docs":{"17":{"tf":1.0}},"n":{"d":{".":{"df":0,"docs":{},"t":{"a":{"b":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"283":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":12,"docs":{"104":{"tf":1.0},"11":{"tf":1.0},"126":{"tf":1.0},"165":{"tf":1.0},"172":{"tf":1.0},"19":{"tf":1.0},"22":{"tf":1.0},"226":{"tf":1.4142135623730951},"231":{"tf":2.6457513110645907},"237":{"tf":1.4142135623730951},"268":{"tf":1.0},"283":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"29":{"tf":1.0},"34":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"t":{"df":10,"docs":{"253":{"tf":1.0},"267":{"tf":1.0},"268":{"tf":1.4142135623730951},"270":{"tf":1.0},"271":{"tf":1.0},"274":{"tf":1.7320508075688772},"282":{"tf":1.0},"30":{"tf":1.4142135623730951},"35":{"tf":1.4142135623730951},"7":{"tf":1.7320508075688772}}}},"o":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"df":1,"docs":{"94":{"tf":1.0}}}}}},"df":6,"docs":{"154":{"tf":1.0},"22":{"tf":1.0},"249":{"tf":1.0},"282":{"tf":1.0},"81":{"tf":1.0},"84":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"264":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"n":{"df":6,"docs":{"189":{"tf":1.0},"226":{"tf":1.4142135623730951},"231":{"tf":1.0},"254":{"tf":1.4142135623730951},"3":{"tf":1.4142135623730951},"64":{"tf":1.0}}}}},"p":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"208":{"tf":1.0}}}},"df":0,"docs":{},"r":{"df":6,"docs":{"151":{"tf":1.4142135623730951},"152":{"tf":1.4142135623730951},"155":{"tf":1.4142135623730951},"156":{"tf":1.4142135623730951},"245":{"tf":1.4142135623730951},"62":{"tf":1.0}}},"t":{"df":6,"docs":{"116":{"tf":1.0},"161":{"tf":1.0},"162":{"tf":1.0},"37":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":29,"docs":{"105":{"tf":1.0},"109":{"tf":1.0},"114":{"tf":1.4142135623730951},"161":{"tf":1.0},"163":{"tf":1.7320508075688772},"164":{"tf":1.0},"171":{"tf":1.7320508075688772},"172":{"tf":2.23606797749979},"220":{"tf":1.0},"221":{"tf":1.0},"222":{"tf":1.0},"223":{"tf":1.4142135623730951},"224":{"tf":1.4142135623730951},"251":{"tf":1.0},"252":{"tf":1.4142135623730951},"253":{"tf":1.0},"278":{"tf":1.4142135623730951},"279":{"tf":1.0},"28":{"tf":1.0},"280":{"tf":2.23606797749979},"33":{"tf":1.0},"37":{"tf":1.4142135623730951},"45":{"tf":1.4142135623730951},"46":{"tf":1.0},"57":{"tf":1.4142135623730951},"58":{"tf":1.4142135623730951},"59":{"tf":1.7320508075688772},"60":{"tf":2.23606797749979},"73":{"tf":2.23606797749979}}}},"l":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":2,"docs":{"263":{"tf":1.0},"60":{"tf":1.4142135623730951}},"t":{"df":1,"docs":{"60":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":11,"docs":{"10":{"tf":1.0},"106":{"tf":1.0},"11":{"tf":1.0},"115":{"tf":1.0},"119":{"tf":1.0},"169":{"tf":1.0},"187":{"tf":1.0},"200":{"tf":1.0},"22":{"tf":1.0},"270":{"tf":1.0},"84":{"tf":1.0}},"e":{"_":{"df":0,"docs":{},"o":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":1,"docs":{"283":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"x":{"df":12,"docs":{"106":{"tf":1.7320508075688772},"115":{"tf":1.0},"116":{"tf":1.0},"140":{"tf":1.0},"158":{"tf":1.0},"200":{"tf":1.0},"220":{"tf":1.0},"229":{"tf":1.0},"233":{"tf":1.0},"280":{"tf":1.0},"78":{"tf":1.4142135623730951},"81":{"tf":1.0}}}},"i":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"262":{"tf":1.0}}}}},"c":{"df":6,"docs":{"10":{"tf":1.0},"197":{"tf":1.0},"225":{"tf":1.4142135623730951},"233":{"tf":1.0},"45":{"tf":1.0},"62":{"tf":1.0}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"n":{"df":168,"docs":{"0":{"tf":2.449489742783178},"1":{"tf":1.4142135623730951},"100":{"tf":1.0},"101":{"tf":3.0},"102":{"tf":2.449489742783178},"103":{"tf":2.8284271247461903},"104":{"tf":2.23606797749979},"105":{"tf":2.23606797749979},"106":{"tf":2.0},"107":{"tf":1.4142135623730951},"108":{"tf":2.8284271247461903},"109":{"tf":2.6457513110645907},"11":{"tf":1.4142135623730951},"110":{"tf":1.4142135623730951},"111":{"tf":1.4142135623730951},"112":{"tf":1.7320508075688772},"113":{"tf":1.4142135623730951},"114":{"tf":1.4142135623730951},"115":{"tf":1.4142135623730951},"116":{"tf":1.4142135623730951},"117":{"tf":1.7320508075688772},"118":{"tf":1.0},"119":{"tf":1.4142135623730951},"12":{"tf":1.0},"120":{"tf":1.7320508075688772},"122":{"tf":1.7320508075688772},"123":{"tf":3.0},"124":{"tf":1.4142135623730951},"125":{"tf":1.0},"14":{"tf":1.0},"141":{"tf":1.0},"145":{"tf":1.0},"149":{"tf":1.0},"150":{"tf":1.0},"151":{"tf":1.0},"157":{"tf":1.0},"159":{"tf":1.0},"16":{"tf":2.0},"161":{"tf":1.0},"162":{"tf":1.0},"163":{"tf":1.0},"165":{"tf":1.7320508075688772},"166":{"tf":1.0},"167":{"tf":2.8284271247461903},"169":{"tf":1.0},"17":{"tf":1.0},"170":{"tf":1.7320508075688772},"171":{"tf":2.449489742783178},"172":{"tf":2.23606797749979},"175":{"tf":1.4142135623730951},"194":{"tf":1.4142135623730951},"198":{"tf":1.0},"2":{"tf":1.0},"20":{"tf":1.4142135623730951},"200":{"tf":4.242640687119285},"21":{"tf":1.0},"219":{"tf":1.7320508075688772},"22":{"tf":1.4142135623730951},"220":{"tf":2.449489742783178},"221":{"tf":2.23606797749979},"222":{"tf":2.449489742783178},"224":{"tf":1.4142135623730951},"225":{"tf":3.0},"226":{"tf":3.0},"228":{"tf":3.0},"229":{"tf":2.8284271247461903},"23":{"tf":3.1622776601683795},"230":{"tf":2.0},"232":{"tf":1.4142135623730951},"233":{"tf":2.23606797749979},"234":{"tf":1.0},"235":{"tf":1.4142135623730951},"236":{"tf":1.7320508075688772},"237":{"tf":1.7320508075688772},"24":{"tf":1.0},"243":{"tf":1.0},"244":{"tf":1.4142135623730951},"245":{"tf":2.0},"246":{"tf":1.0},"247":{"tf":1.0},"25":{"tf":2.0},"251":{"tf":2.449489742783178},"252":{"tf":1.4142135623730951},"253":{"tf":2.6457513110645907},"254":{"tf":1.0},"256":{"tf":1.0},"257":{"tf":2.8284271247461903},"258":{"tf":1.0},"259":{"tf":2.0},"26":{"tf":2.449489742783178},"260":{"tf":1.4142135623730951},"262":{"tf":1.4142135623730951},"264":{"tf":1.4142135623730951},"268":{"tf":1.0},"271":{"tf":1.0},"272":{"tf":1.4142135623730951},"274":{"tf":2.0},"278":{"tf":1.0},"28":{"tf":1.0},"282":{"tf":1.0},"283":{"tf":1.7320508075688772},"284":{"tf":1.0},"29":{"tf":2.23606797749979},"30":{"tf":1.4142135623730951},"31":{"tf":3.0},"32":{"tf":3.3166247903554},"33":{"tf":1.0},"34":{"tf":2.449489742783178},"35":{"tf":1.4142135623730951},"36":{"tf":3.0},"37":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.4142135623730951},"41":{"tf":2.449489742783178},"42":{"tf":1.0},"43":{"tf":2.0},"44":{"tf":1.0},"45":{"tf":2.0},"46":{"tf":2.23606797749979},"47":{"tf":1.0},"48":{"tf":1.7320508075688772},"49":{"tf":2.6457513110645907},"50":{"tf":1.0},"51":{"tf":1.0},"52":{"tf":1.0},"53":{"tf":1.0},"54":{"tf":2.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.4142135623730951},"59":{"tf":1.4142135623730951},"60":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0},"68":{"tf":2.449489742783178},"69":{"tf":2.0},"7":{"tf":1.4142135623730951},"70":{"tf":1.0},"71":{"tf":1.0},"72":{"tf":1.0},"73":{"tf":3.4641016151377544},"74":{"tf":3.605551275463989},"75":{"tf":3.1622776601683795},"76":{"tf":2.23606797749979},"77":{"tf":2.0},"78":{"tf":2.449489742783178},"79":{"tf":1.7320508075688772},"80":{"tf":1.7320508075688772},"81":{"tf":2.0},"82":{"tf":1.7320508075688772},"83":{"tf":1.4142135623730951},"84":{"tf":1.7320508075688772},"85":{"tf":1.4142135623730951},"86":{"tf":1.4142135623730951},"87":{"tf":1.4142135623730951},"88":{"tf":1.4142135623730951},"89":{"tf":1.4142135623730951},"90":{"tf":1.4142135623730951},"91":{"tf":1.0},"92":{"tf":1.0},"93":{"tf":1.0},"94":{"tf":1.0},"95":{"tf":1.0},"96":{"tf":1.0},"97":{"tf":1.0},"98":{"tf":1.0},"99":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"'":{"df":11,"docs":{"104":{"tf":1.4142135623730951},"108":{"tf":1.0},"21":{"tf":1.0},"219":{"tf":1.0},"254":{"tf":1.0},"255":{"tf":1.0},"256":{"tf":1.0},"257":{"tf":1.0},"258":{"tf":1.0},"70":{"tf":2.23606797749979},"73":{"tf":1.0}}},">":{":":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"45":{"tf":1.0}}}}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"103":{"tf":1.4142135623730951}},"e":{">":{".":{"df":0,"docs":{},"u":{"d":{"df":0,"docs":{},"l":{"df":1,"docs":{"103":{"tf":1.0}}}},"df":0,"docs":{}}},":":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":1,"docs":{"108":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"108":{"tf":1.0}}}}}}},"_":{"df":0,"docs":{},"m":{"df":0,"docs":{},"s":{"df":0,"docs":{},"g":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":2,"docs":{"105":{"tf":1.0},"106":{"tf":1.0}}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{".":{"df":0,"docs":{},"h":{"df":1,"docs":{"109":{"tf":1.0}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"54":{"tf":1.0}}}},"s":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"34":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}},"t":{"a":{"df":0,"docs":{},"r":{".":{"df":0,"docs":{},"x":{"df":0,"docs":{},"z":{"df":1,"docs":{"271":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"/":{"<":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"103":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"_":{"c":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"70":{"tf":1.0},"72":{"tf":1.0}},"e":{">":{"/":{"<":{"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"_":{"c":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{">":{".":{"df":0,"docs":{},"u":{"d":{"df":0,"docs":{},"l":{"df":1,"docs":{"69":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"/":{"df":0,"docs":{},"s":{"df":0,"docs":{},"r":{"c":{"/":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"/":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"x":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":1,"docs":{"70":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":1,"docs":{"70":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":1,"docs":{"72":{"tf":1.0}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":1,"docs":{"70":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":1,"docs":{"69":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"c":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"/":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"70":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"df":1,"docs":{"39":{"tf":1.0}}}}}}},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"l":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"18":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"m":{"df":0,"docs":{},"y":{"c":{"df":0,"docs":{},"r":{"df":1,"docs":{"60":{"tf":1.0}}}},"df":0,"docs":{}}}},":":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{".":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":1,"docs":{"1":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"[":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":1,"docs":{"200":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"224":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":1,"docs":{"138":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":2,"docs":{"63":{"tf":1.0},"64":{"tf":1.0}}}}}}}}},"n":{"c":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"99":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":3,"docs":{"165":{"tf":1.0},"220":{"tf":1.0},"226":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"n":{"df":3,"docs":{"140":{"tf":1.0},"225":{"tf":1.0},"59":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":3,"docs":{"187":{"tf":1.0},"206":{"tf":1.0},"226":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":2,"docs":{"166":{"tf":1.0},"235":{"tf":1.0}}}}}},"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"4":{"tf":1.0}}}}},"df":7,"docs":{"144":{"tf":1.4142135623730951},"145":{"tf":1.0},"146":{"tf":1.0},"153":{"tf":1.4142135623730951},"168":{"tf":1.4142135623730951},"181":{"tf":1.4142135623730951},"211":{"tf":1.4142135623730951}},"f":{"df":0,"docs":{},"i":{"d":{"df":3,"docs":{"152":{"tf":1.0},"172":{"tf":1.0},"201":{"tf":1.0}}},"df":0,"docs":{},"g":{"df":3,"docs":{"11":{"tf":1.0},"277":{"tf":1.0},"37":{"tf":1.0}},"u":{"df":0,"docs":{},"r":{"df":16,"docs":{"105":{"tf":1.0},"109":{"tf":1.0},"12":{"tf":2.0},"13":{"tf":1.4142135623730951},"18":{"tf":1.0},"184":{"tf":1.7320508075688772},"187":{"tf":1.0},"20":{"tf":1.0},"200":{"tf":1.4142135623730951},"22":{"tf":1.0},"25":{"tf":1.0},"263":{"tf":1.7320508075688772},"37":{"tf":1.4142135623730951},"49":{"tf":1.0},"59":{"tf":1.0},"69":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"m":{"df":6,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"13":{"tf":1.0},"22":{"tf":1.0},"69":{"tf":1.0},"70":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"165":{"tf":1.0},"175":{"tf":1.0},"233":{"tf":1.0}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":1,"docs":{"189":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"237":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"s":{"df":1,"docs":{"61":{"tf":1.0}}}}},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":1,"docs":{"106":{"tf":1.0}}}}}},"df":0,"docs":{}}},"n":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"(":{"\"":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"62":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"_":{"b":{"a":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"h":{"(":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"l":{"_":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"m":{"a":{"df":1,"docs":{"62":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":18,"docs":{"1":{"tf":1.0},"126":{"tf":1.4142135623730951},"130":{"tf":1.0},"133":{"tf":1.0},"154":{"tf":1.0},"155":{"tf":1.0},"183":{"tf":1.0},"189":{"tf":1.0},"190":{"tf":1.4142135623730951},"226":{"tf":1.0},"229":{"tf":1.0},"276":{"tf":1.0},"283":{"tf":1.7320508075688772},"78":{"tf":1.0},"79":{"tf":2.6457513110645907},"81":{"tf":1.4142135623730951},"83":{"tf":1.0},"87":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"62":{"tf":1.0}}}}}}}}}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":4,"docs":{"165":{"tf":1.4142135623730951},"166":{"tf":1.4142135623730951},"209":{"tf":1.4142135623730951},"210":{"tf":1.7320508075688772}}}},"r":{"df":0,"docs":{},"v":{"df":2,"docs":{"152":{"tf":1.0},"64":{"tf":1.0}}}}},"i":{"d":{"df":29,"docs":{"123":{"tf":1.0},"137":{"tf":1.4142135623730951},"142":{"tf":1.4142135623730951},"151":{"tf":1.4142135623730951},"163":{"tf":1.4142135623730951},"167":{"tf":1.0},"179":{"tf":1.4142135623730951},"190":{"tf":1.4142135623730951},"192":{"tf":1.0},"195":{"tf":1.4142135623730951},"200":{"tf":1.0},"206":{"tf":1.0},"207":{"tf":1.4142135623730951},"217":{"tf":1.0},"220":{"tf":1.0},"228":{"tf":1.4142135623730951},"229":{"tf":1.0},"231":{"tf":1.0},"233":{"tf":1.4142135623730951},"262":{"tf":1.0},"263":{"tf":1.0},"274":{"tf":1.0},"276":{"tf":1.0},"51":{"tf":1.0},"59":{"tf":1.0},"6":{"tf":1.0},"64":{"tf":1.0},"75":{"tf":1.0},"82":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":4,"docs":{"260":{"tf":1.0},"58":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0}}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":4,"docs":{"103":{"tf":1.0},"145":{"tf":1.0},"162":{"tf":1.0},"61":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"183":{"tf":1.0}}}},"t":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"93":{"tf":1.0}}}}},"df":3,"docs":{"117":{"tf":1.0},"20":{"tf":1.0},"94":{"tf":1.0}},"r":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"187":{"tf":1.0},"226":{"tf":1.0}}}}}},"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"131":{"tf":1.0}}}},"df":0,"docs":{}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":3,"docs":{"11":{"tf":1.0},"41":{"tf":1.0},"7":{"tf":1.0}}}},"m":{"df":56,"docs":{"104":{"tf":1.0},"107":{"tf":1.4142135623730951},"108":{"tf":1.0},"109":{"tf":1.0},"118":{"tf":1.4142135623730951},"123":{"tf":1.4142135623730951},"14":{"tf":1.0},"145":{"tf":2.0},"146":{"tf":1.7320508075688772},"154":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0},"158":{"tf":1.0},"16":{"tf":1.4142135623730951},"161":{"tf":1.0},"162":{"tf":1.0},"164":{"tf":1.0},"165":{"tf":1.4142135623730951},"166":{"tf":1.4142135623730951},"167":{"tf":2.0},"169":{"tf":2.0},"170":{"tf":2.449489742783178},"171":{"tf":2.0},"172":{"tf":1.4142135623730951},"174":{"tf":1.0},"175":{"tf":1.4142135623730951},"18":{"tf":1.7320508075688772},"193":{"tf":1.4142135623730951},"194":{"tf":1.7320508075688772},"20":{"tf":1.7320508075688772},"200":{"tf":1.4142135623730951},"201":{"tf":1.0},"22":{"tf":1.0},"223":{"tf":1.0},"228":{"tf":1.0},"237":{"tf":1.0},"245":{"tf":1.0},"251":{"tf":1.0},"253":{"tf":1.0},"268":{"tf":2.0},"272":{"tf":1.4142135623730951},"273":{"tf":1.0},"274":{"tf":2.449489742783178},"37":{"tf":1.4142135623730951},"43":{"tf":1.4142135623730951},"49":{"tf":1.0},"50":{"tf":1.0},"51":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"69":{"tf":1.0},"70":{"tf":1.0},"74":{"tf":1.0},"78":{"tf":1.0},"83":{"tf":1.0},"90":{"tf":1.7320508075688772}},"e":{"df":0,"docs":{},"r":{".":{"df":0,"docs":{},"f":{"df":0,"docs":{},"x":{"a":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"283":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"200":{"tf":1.0}}}}}}},"t":{"a":{"c":{"df":0,"docs":{},"t":{"df":8,"docs":{"1":{"tf":1.7320508075688772},"21":{"tf":1.0},"245":{"tf":1.0},"25":{"tf":1.0},"266":{"tf":1.0},"274":{"tf":1.4142135623730951},"31":{"tf":1.0},"36":{"tf":1.0}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":27,"docs":{"105":{"tf":2.0},"167":{"tf":1.0},"20":{"tf":1.0},"221":{"tf":1.0},"222":{"tf":1.0},"223":{"tf":1.4142135623730951},"226":{"tf":1.0},"232":{"tf":1.0},"248":{"tf":1.0},"249":{"tf":1.0},"251":{"tf":1.0},"252":{"tf":1.7320508075688772},"271":{"tf":1.4142135623730951},"274":{"tf":1.0},"28":{"tf":1.0},"33":{"tf":1.0},"39":{"tf":1.0},"45":{"tf":1.0},"48":{"tf":1.0},"60":{"tf":1.4142135623730951},"61":{"tf":1.0},"69":{"tf":1.0},"70":{"tf":1.4142135623730951},"72":{"tf":1.0},"73":{"tf":1.0},"74":{"tf":1.0},"98":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":12,"docs":{"126":{"tf":1.0},"13":{"tf":1.0},"189":{"tf":1.7320508075688772},"193":{"tf":1.0},"194":{"tf":1.0},"196":{"tf":1.0},"201":{"tf":2.6457513110645907},"226":{"tf":1.0},"284":{"tf":1.0},"62":{"tf":1.0},"70":{"tf":1.4142135623730951},"98":{"tf":1.0}},"i":{"df":1,"docs":{"227":{"tf":1.0}}}}},"x":{"df":0,"docs":{},"t":{"df":10,"docs":{"136":{"tf":1.4142135623730951},"140":{"tf":1.4142135623730951},"149":{"tf":1.4142135623730951},"161":{"tf":1.4142135623730951},"174":{"tf":1.0},"177":{"tf":1.4142135623730951},"187":{"tf":1.4142135623730951},"200":{"tf":1.0},"201":{"tf":1.0},"203":{"tf":1.4142135623730951}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":9,"docs":{"11":{"tf":1.0},"120":{"tf":1.0},"141":{"tf":1.0},"145":{"tf":1.0},"167":{"tf":1.4142135623730951},"197":{"tf":1.0},"198":{"tf":1.0},"226":{"tf":1.0},"7":{"tf":1.0}}}}},"r":{"a":{"df":1,"docs":{"274":{"tf":1.0}}},"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":132,"docs":{"10":{"tf":1.0},"100":{"tf":1.0},"101":{"tf":1.0},"102":{"tf":1.0},"103":{"tf":1.0},"104":{"tf":1.0},"105":{"tf":1.0},"106":{"tf":1.0},"107":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"11":{"tf":1.0},"110":{"tf":1.0},"111":{"tf":1.0},"112":{"tf":1.0},"113":{"tf":1.0},"114":{"tf":1.0},"115":{"tf":1.0},"116":{"tf":1.0},"117":{"tf":1.0},"118":{"tf":1.0},"119":{"tf":1.0},"12":{"tf":1.0},"120":{"tf":1.0},"121":{"tf":1.0},"122":{"tf":1.0},"123":{"tf":1.0},"124":{"tf":1.0},"125":{"tf":1.0},"126":{"tf":1.0},"127":{"tf":1.0},"128":{"tf":1.0},"129":{"tf":1.0},"13":{"tf":1.0},"130":{"tf":1.0},"131":{"tf":1.0},"132":{"tf":1.0},"133":{"tf":1.0},"14":{"tf":1.0},"15":{"tf":1.0},"16":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.0},"205":{"tf":1.0},"21":{"tf":1.0},"22":{"tf":1.0},"23":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"26":{"tf":1.0},"27":{"tf":1.0},"28":{"tf":1.0},"29":{"tf":1.0},"3":{"tf":1.7320508075688772},"30":{"tf":1.0},"31":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.0},"34":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"4":{"tf":1.0},"40":{"tf":1.0},"41":{"tf":1.0},"42":{"tf":1.0},"43":{"tf":1.0},"44":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"47":{"tf":1.0},"48":{"tf":1.0},"49":{"tf":1.0},"5":{"tf":1.0},"50":{"tf":1.0},"51":{"tf":1.0},"52":{"tf":1.0},"53":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"6":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0},"67":{"tf":1.0},"68":{"tf":1.0},"69":{"tf":1.0},"7":{"tf":1.0},"70":{"tf":1.0},"71":{"tf":1.0},"72":{"tf":1.0},"73":{"tf":1.0},"74":{"tf":1.0},"75":{"tf":1.0},"76":{"tf":1.0},"77":{"tf":1.0},"78":{"tf":1.0},"79":{"tf":1.0},"8":{"tf":1.0},"80":{"tf":1.0},"81":{"tf":1.0},"82":{"tf":1.0},"83":{"tf":1.0},"84":{"tf":1.0},"85":{"tf":1.0},"86":{"tf":1.0},"87":{"tf":1.0},"88":{"tf":1.0},"89":{"tf":1.0},"9":{"tf":1.0},"90":{"tf":1.0},"91":{"tf":1.0},"92":{"tf":1.0},"93":{"tf":1.0},"94":{"tf":1.0},"95":{"tf":1.0},"96":{"tf":1.0},"97":{"tf":1.0},"98":{"tf":1.0},"99":{"tf":1.0}},"o":{"df":0,"docs":{},"r":{"df":4,"docs":{"274":{"tf":1.0},"41":{"tf":1.0},"6":{"tf":1.4142135623730951},"7":{"tf":1.0}}}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"l":{"df":6,"docs":{"184":{"tf":1.0},"201":{"tf":1.4142135623730951},"208":{"tf":1.0},"283":{"tf":1.4142135623730951},"61":{"tf":1.0},"77":{"tf":1.0}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":6,"docs":{"117":{"tf":1.0},"165":{"tf":1.0},"25":{"tf":1.0},"257":{"tf":1.0},"281":{"tf":1.0},"79":{"tf":1.0}}},"t":{"df":13,"docs":{"100":{"tf":1.0},"107":{"tf":1.0},"137":{"tf":1.0},"69":{"tf":1.0},"91":{"tf":1.7320508075688772},"92":{"tf":1.0},"93":{"tf":1.7320508075688772},"94":{"tf":1.0},"95":{"tf":1.0},"96":{"tf":1.4142135623730951},"97":{"tf":1.0},"98":{"tf":1.0},"99":{"tf":1.4142135623730951}}}},"r":{"df":0,"docs":{},"s":{"df":2,"docs":{"105":{"tf":1.0},"106":{"tf":1.0}}},"t":{"df":11,"docs":{"101":{"tf":1.7320508075688772},"102":{"tf":1.0},"103":{"tf":1.7320508075688772},"104":{"tf":1.0},"105":{"tf":1.4142135623730951},"106":{"tf":1.0},"107":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"163":{"tf":1.0},"284":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"104":{"tf":1.0}}},"df":0,"docs":{}}}}},"o":{"df":0,"docs":{},"r":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":3,"docs":{"225":{"tf":1.0},"226":{"tf":2.23606797749979},"241":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"i":{"df":13,"docs":{"18":{"tf":1.0},"199":{"tf":1.0},"2":{"tf":1.0},"20":{"tf":1.0},"220":{"tf":1.0},"250":{"tf":1.0},"267":{"tf":1.7320508075688772},"273":{"tf":1.4142135623730951},"282":{"tf":1.0},"45":{"tf":1.0},"64":{"tf":1.0},"70":{"tf":1.0},"73":{"tf":2.0}}}},"r":{"df":0,"docs":{},"e":{"df":7,"docs":{"0":{"tf":1.0},"103":{"tf":1.7320508075688772},"105":{"tf":1.4142135623730951},"274":{"tf":1.0},"43":{"tf":1.4142135623730951},"6":{"tf":1.0},"78":{"tf":1.0}}},"r":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":8,"docs":{"11":{"tf":1.0},"126":{"tf":1.4142135623730951},"14":{"tf":1.0},"222":{"tf":1.0},"229":{"tf":1.0},"239":{"tf":1.0},"267":{"tf":1.0},"79":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":11,"docs":{"122":{"tf":1.0},"13":{"tf":1.0},"22":{"tf":1.4142135623730951},"224":{"tf":1.4142135623730951},"231":{"tf":1.0},"233":{"tf":1.4142135623730951},"250":{"tf":1.0},"263":{"tf":1.0},"274":{"tf":1.0},"70":{"tf":1.4142135623730951},"73":{"tf":1.0}}}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"d":{"df":12,"docs":{"106":{"tf":1.7320508075688772},"108":{"tf":1.0},"131":{"tf":1.7320508075688772},"167":{"tf":1.0},"223":{"tf":1.0},"242":{"tf":1.0},"244":{"tf":1.0},"274":{"tf":1.0},"39":{"tf":1.0},"46":{"tf":1.0},"48":{"tf":1.0},"73":{"tf":1.0}}},"df":0,"docs":{}}}}}},"u":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":14,"docs":{"116":{"tf":1.4142135623730951},"132":{"tf":1.0},"134":{"tf":1.0},"148":{"tf":1.4142135623730951},"149":{"tf":2.0},"150":{"tf":1.0},"151":{"tf":2.23606797749979},"152":{"tf":1.4142135623730951},"154":{"tf":1.7320508075688772},"155":{"tf":1.7320508075688772},"156":{"tf":1.7320508075688772},"157":{"tf":1.0},"158":{"tf":1.0},"159":{"tf":1.4142135623730951}}}}}}},"s":{"df":0,"docs":{},"t":{"df":3,"docs":{"105":{"tf":1.0},"115":{"tf":1.0},"150":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"60":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"214":{"tf":1.0},"82":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":3,"docs":{"250":{"tf":2.0},"82":{"tf":1.0},"84":{"tf":2.449489742783178}}}}}},"p":{"df":0,"docs":{},"l":{"df":7,"docs":{"116":{"tf":1.0},"206":{"tf":1.0},"208":{"tf":1.0},"225":{"tf":1.0},"226":{"tf":1.0},"228":{"tf":1.0},"229":{"tf":1.0}}}},"r":{"df":0,"docs":{},"s":{"df":2,"docs":{"105":{"tf":1.0},"115":{"tf":1.0}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":2,"docs":{"41":{"tf":1.0},"50":{"tf":2.23606797749979}}}},"df":4,"docs":{"236":{"tf":1.0},"268":{"tf":1.0},"7":{"tf":1.0},"84":{"tf":1.0}}}}}},"p":{"df":0,"docs":{},"p":{"=":{"c":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":1,"docs":{"37":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"r":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":2,"docs":{"117":{"tf":1.4142135623730951},"205":{"tf":1.0}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"u":{"d":{"df":0,"docs":{},"l":{"df":1,"docs":{"70":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"t":{"df":0,"docs":{},"e":{"'":{"df":4,"docs":{"105":{"tf":1.0},"106":{"tf":1.4142135623730951},"64":{"tf":1.0},"69":{"tf":1.0}}},":":{":":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"106":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{">":{"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":1,"docs":{"73":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":29,"docs":{"103":{"tf":2.0},"105":{"tf":3.0},"106":{"tf":1.4142135623730951},"107":{"tf":1.0},"116":{"tf":1.4142135623730951},"122":{"tf":1.0},"123":{"tf":1.7320508075688772},"198":{"tf":1.4142135623730951},"221":{"tf":2.0},"222":{"tf":1.0},"225":{"tf":1.0},"237":{"tf":1.0},"238":{"tf":1.0},"244":{"tf":1.4142135623730951},"245":{"tf":1.0},"246":{"tf":1.0},"251":{"tf":1.0},"252":{"tf":1.4142135623730951},"28":{"tf":1.0},"281":{"tf":2.0},"282":{"tf":1.0},"286":{"tf":1.0},"33":{"tf":1.0},"43":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":2.449489742783178},"60":{"tf":2.6457513110645907},"64":{"tf":2.449489742783178},"69":{"tf":2.6457513110645907}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":40,"docs":{"104":{"tf":1.0},"108":{"tf":1.0},"11":{"tf":1.0},"118":{"tf":1.0},"129":{"tf":1.7320508075688772},"13":{"tf":1.4142135623730951},"130":{"tf":1.4142135623730951},"131":{"tf":1.7320508075688772},"145":{"tf":1.4142135623730951},"167":{"tf":1.4142135623730951},"178":{"tf":1.0},"18":{"tf":1.0},"180":{"tf":1.0},"190":{"tf":1.0},"194":{"tf":1.0},"200":{"tf":1.0},"229":{"tf":1.0},"241":{"tf":1.0},"244":{"tf":1.0},"245":{"tf":1.7320508075688772},"246":{"tf":1.0},"249":{"tf":1.4142135623730951},"253":{"tf":1.0},"268":{"tf":2.8284271247461903},"269":{"tf":2.23606797749979},"270":{"tf":1.0},"274":{"tf":1.7320508075688772},"283":{"tf":1.7320508075688772},"29":{"tf":1.0},"34":{"tf":1.0},"39":{"tf":1.0},"60":{"tf":1.0},"62":{"tf":1.0},"69":{"tf":1.0},"70":{"tf":1.4142135623730951},"72":{"tf":1.4142135623730951},"73":{"tf":1.4142135623730951},"75":{"tf":1.0},"78":{"tf":1.0},"82":{"tf":1.0}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"/":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"17":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"n":{"df":4,"docs":{"180":{"tf":1.0},"182":{"tf":1.0},"183":{"tf":1.0},"184":{"tf":1.0}}}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"n":{".":{"df":0,"docs":{},"y":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":1,"docs":{"40":{"tf":1.0}}}}}},"df":1,"docs":{"266":{"tf":1.4142135623730951}}},"s":{"df":0,"docs":{},"s":{"df":6,"docs":{"220":{"tf":1.0},"225":{"tf":1.0},"278":{"tf":1.0},"279":{"tf":1.0},"280":{"tf":2.23606797749979},"51":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":1,"docs":{"25":{"tf":1.0}}}}},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"/":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":1,"docs":{"226":{"tf":1.0}}}}}},"df":1,"docs":{"282":{"tf":1.0}},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":1,"docs":{"283":{"tf":1.0}},"h":{"df":2,"docs":{"278":{"tf":1.0},"282":{"tf":1.0}}}}},"df":0,"docs":{}}}}}}}},"s":{"a":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"k":{"df":1,"docs":{"186":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":56,"docs":{"101":{"tf":1.0},"103":{"tf":1.0},"104":{"tf":1.0},"11":{"tf":1.0},"111":{"tf":1.0},"121":{"tf":1.0},"126":{"tf":1.0},"145":{"tf":1.0},"146":{"tf":1.0},"150":{"tf":1.4142135623730951},"155":{"tf":1.0},"156":{"tf":1.0},"161":{"tf":1.7320508075688772},"163":{"tf":1.0},"166":{"tf":1.0},"167":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.4142135623730951},"175":{"tf":2.23606797749979},"187":{"tf":1.0},"193":{"tf":1.0},"196":{"tf":1.0},"220":{"tf":1.0},"228":{"tf":1.7320508075688772},"229":{"tf":1.0},"231":{"tf":1.0},"233":{"tf":1.4142135623730951},"242":{"tf":1.0},"245":{"tf":1.4142135623730951},"248":{"tf":1.0},"257":{"tf":1.4142135623730951},"258":{"tf":1.0},"26":{"tf":1.0},"260":{"tf":1.0},"261":{"tf":1.4142135623730951},"262":{"tf":1.4142135623730951},"263":{"tf":1.0},"264":{"tf":1.0},"268":{"tf":1.0},"274":{"tf":1.7320508075688772},"276":{"tf":1.0},"280":{"tf":1.0},"38":{"tf":1.0},"40":{"tf":1.0},"44":{"tf":1.0},"49":{"tf":1.0},"50":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"6":{"tf":1.0},"65":{"tf":1.4142135623730951},"66":{"tf":1.0},"67":{"tf":1.0},"7":{"tf":1.0},"84":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":4,"docs":{"133":{"tf":1.0},"184":{"tf":1.0},"222":{"tf":1.7320508075688772},"268":{"tf":1.0}}}}}},"t":{"df":4,"docs":{"253":{"tf":1.0},"268":{"tf":1.4142135623730951},"270":{"tf":1.4142135623730951},"59":{"tf":1.0}}}},"y":{"c":{"df":0,"docs":{},"l":{"df":3,"docs":{"121":{"tf":1.0},"180":{"tf":1.0},"183":{"tf":1.0}}}},"df":0,"docs":{}}},"d":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":2,"docs":{"205":{"tf":1.0},"274":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":2,"docs":{"17":{"tf":1.4142135623730951},"280":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"h":{"b":{"df":0,"docs":{},"o":{"a":{"df":0,"docs":{},"r":{"d":{"df":2,"docs":{"203":{"tf":1.0},"218":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":1,"docs":{"200":{"tf":1.0}}}},"t":{"a":{"b":{"a":{"df":0,"docs":{},"s":{"df":31,"docs":{"130":{"tf":1.0},"134":{"tf":1.0},"140":{"tf":1.4142135623730951},"148":{"tf":1.4142135623730951},"149":{"tf":2.23606797749979},"150":{"tf":1.0},"151":{"tf":3.1622776601683795},"152":{"tf":2.23606797749979},"154":{"tf":2.23606797749979},"155":{"tf":1.7320508075688772},"156":{"tf":2.0},"157":{"tf":2.23606797749979},"158":{"tf":2.6457513110645907},"159":{"tf":1.4142135623730951},"203":{"tf":1.4142135623730951},"205":{"tf":2.0},"206":{"tf":1.7320508075688772},"208":{"tf":2.0},"209":{"tf":2.0},"212":{"tf":2.0},"213":{"tf":1.0},"229":{"tf":1.0},"245":{"tf":1.0},"248":{"tf":1.4142135623730951},"264":{"tf":1.7320508075688772},"268":{"tf":1.0},"78":{"tf":1.0},"79":{"tf":1.0},"81":{"tf":1.7320508075688772},"84":{"tf":1.0},"86":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":36,"docs":{"105":{"tf":1.0},"106":{"tf":2.23606797749979},"140":{"tf":1.0},"145":{"tf":1.4142135623730951},"150":{"tf":1.4142135623730951},"154":{"tf":1.4142135623730951},"155":{"tf":1.4142135623730951},"156":{"tf":1.4142135623730951},"157":{"tf":1.0},"158":{"tf":2.8284271247461903},"159":{"tf":1.4142135623730951},"177":{"tf":1.4142135623730951},"178":{"tf":1.0},"179":{"tf":2.449489742783178},"180":{"tf":1.4142135623730951},"183":{"tf":1.4142135623730951},"184":{"tf":1.4142135623730951},"185":{"tf":1.4142135623730951},"189":{"tf":1.4142135623730951},"201":{"tf":2.0},"209":{"tf":1.0},"212":{"tf":1.0},"231":{"tf":1.0},"232":{"tf":1.0},"242":{"tf":1.4142135623730951},"254":{"tf":2.0},"255":{"tf":1.0},"259":{"tf":1.4142135623730951},"264":{"tf":1.0},"283":{"tf":1.4142135623730951},"79":{"tf":1.0},"80":{"tf":1.4142135623730951},"82":{"tf":1.0},"84":{"tf":1.4142135623730951},"86":{"tf":1.0},"89":{"tf":1.0}}},"df":0,"docs":{},"e":{"a":{"d":{"df":1,"docs":{"62":{"tf":1.0}}},"df":0,"docs":{}},"df":16,"docs":{"121":{"tf":1.0},"139":{"tf":1.0},"148":{"tf":1.0},"160":{"tf":1.0},"167":{"tf":1.4142135623730951},"176":{"tf":1.0},"186":{"tf":1.0},"202":{"tf":1.0},"207":{"tf":1.0},"21":{"tf":1.0},"213":{"tf":1.4142135623730951},"255":{"tf":1.0},"264":{"tf":1.0},"268":{"tf":1.0},"278":{"tf":1.0},"37":{"tf":1.0}}}},"y":{"df":5,"docs":{"119":{"tf":1.0},"206":{"tf":1.0},"218":{"tf":1.0},"266":{"tf":1.0},"268":{"tf":1.4142135623730951}},"s":{"/":{"df":0,"docs":{},"w":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"k":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"207":{"tf":1.0}}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"b":{":":{":":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{":":{":":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{":":{":":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"_":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"b":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"_":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"c":{"df":0,"docs":{},"r":{"df":1,"docs":{"62":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":4,"docs":{"249":{"tf":1.4142135623730951},"78":{"tf":1.0},"79":{"tf":2.0},"87":{"tf":1.0}},"g":{"(":{"'":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"62":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"62":{"tf":1.4142135623730951}}}},"d":{"_":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}},"df":7,"docs":{"163":{"tf":1.0},"164":{"tf":1.0},"172":{"tf":1.4142135623730951},"174":{"tf":1.0},"29":{"tf":1.0},"34":{"tf":1.0},"37":{"tf":1.0}},"e":{"a":{"d":{"df":1,"docs":{"60":{"tf":1.0}}},"df":0,"docs":{},"l":{"df":6,"docs":{"149":{"tf":1.4142135623730951},"179":{"tf":1.0},"180":{"tf":1.0},"184":{"tf":1.7320508075688772},"185":{"tf":1.4142135623730951},"64":{"tf":1.0}}}},"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{".":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"k":{"df":1,"docs":{"40":{"tf":1.0}}}}},"df":0,"docs":{}},"df":7,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"117":{"tf":2.23606797749979},"125":{"tf":1.0},"37":{"tf":1.7320508075688772},"62":{"tf":2.6457513110645907},"82":{"tf":1.0}},"g":{"df":1,"docs":{"117":{"tf":1.7320508075688772}}}}}},"c":{"df":0,"docs":{},"i":{"d":{"df":11,"docs":{"152":{"tf":1.4142135623730951},"155":{"tf":1.4142135623730951},"157":{"tf":1.4142135623730951},"160":{"tf":1.0},"176":{"tf":1.0},"186":{"tf":1.0},"202":{"tf":1.0},"225":{"tf":1.0},"231":{"tf":1.4142135623730951},"249":{"tf":1.0},"267":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":91,"docs":{"134":{"tf":2.449489742783178},"135":{"tf":1.7320508075688772},"136":{"tf":1.4142135623730951},"137":{"tf":1.7320508075688772},"138":{"tf":2.0},"139":{"tf":1.0},"140":{"tf":1.0},"141":{"tf":1.7320508075688772},"142":{"tf":1.0},"143":{"tf":1.7320508075688772},"144":{"tf":1.0},"145":{"tf":1.0},"146":{"tf":1.0},"147":{"tf":1.0},"148":{"tf":1.0},"149":{"tf":1.0},"150":{"tf":1.7320508075688772},"151":{"tf":1.0},"152":{"tf":2.0},"153":{"tf":1.0},"154":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0},"157":{"tf":1.0},"158":{"tf":1.0},"159":{"tf":1.0},"160":{"tf":1.0},"161":{"tf":1.0},"162":{"tf":1.7320508075688772},"163":{"tf":1.0},"164":{"tf":1.7320508075688772},"165":{"tf":1.0},"166":{"tf":1.0},"167":{"tf":1.0},"168":{"tf":1.0},"169":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"173":{"tf":1.0},"174":{"tf":1.0},"175":{"tf":1.0},"176":{"tf":1.0},"177":{"tf":1.0},"178":{"tf":1.7320508075688772},"179":{"tf":1.0},"180":{"tf":1.7320508075688772},"181":{"tf":1.0},"182":{"tf":1.0},"183":{"tf":1.0},"184":{"tf":1.0},"185":{"tf":1.0},"186":{"tf":1.0},"187":{"tf":1.4142135623730951},"188":{"tf":1.4142135623730951},"189":{"tf":1.0},"190":{"tf":1.0},"191":{"tf":1.0},"192":{"tf":1.0},"193":{"tf":1.0},"194":{"tf":1.0},"195":{"tf":1.0},"196":{"tf":1.0},"197":{"tf":1.0},"198":{"tf":1.4142135623730951},"199":{"tf":1.0},"200":{"tf":1.0},"201":{"tf":1.0},"202":{"tf":1.0},"203":{"tf":1.0},"204":{"tf":1.0},"205":{"tf":1.0},"206":{"tf":1.7320508075688772},"207":{"tf":1.0},"208":{"tf":2.23606797749979},"209":{"tf":1.0},"210":{"tf":1.4142135623730951},"211":{"tf":1.0},"212":{"tf":1.0},"213":{"tf":1.0},"214":{"tf":1.0},"215":{"tf":1.0},"216":{"tf":1.0},"217":{"tf":1.0},"218":{"tf":1.0},"226":{"tf":1.0},"227":{"tf":1.0},"230":{"tf":1.0},"251":{"tf":1.0},"266":{"tf":1.7320508075688772},"40":{"tf":1.0}}}},"l":{"a":{"df":0,"docs":{},"r":{"df":8,"docs":{"172":{"tf":1.0},"174":{"tf":1.0},"201":{"tf":1.4142135623730951},"224":{"tf":1.4142135623730951},"60":{"tf":1.4142135623730951},"61":{"tf":1.0},"73":{"tf":1.0},"98":{"tf":1.0}}}},"df":1,"docs":{"61":{"tf":1.0}},"i":{"df":0,"docs":{},"n":{"df":4,"docs":{"233":{"tf":2.8284271247461903},"234":{"tf":1.0},"236":{"tf":1.4142135623730951},"242":{"tf":2.0}},"e":{"d":{"_":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"242":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"229":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":1,"docs":{"209":{"tf":1.0}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"250":{"tf":1.0},"84":{"tf":1.0}}}}}}},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"_":{"a":{"df":0,"docs":{},"n":{"d":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"_":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"145":{"tf":1.4142135623730951}}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":4,"docs":{"140":{"tf":1.0},"145":{"tf":1.7320508075688772},"146":{"tf":1.7320508075688772},"254":{"tf":1.0}}}}}}},"d":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"59":{"tf":1.0}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"p":{"df":3,"docs":{"140":{"tf":1.0},"141":{"tf":1.0},"145":{"tf":1.0}}}}},"df":3,"docs":{"140":{"tf":1.0},"145":{"tf":1.4142135623730951},"146":{"tf":1.0}},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"174":{"tf":1.0}}}}}},"f":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{".":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"a":{"df":0,"docs":{},"w":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"/":{"?":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"=":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"2":{"/":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"/":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"271":{"tf":1.0}},"e":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"271":{"tf":1.0}}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":5,"docs":{"11":{"tf":1.0},"12":{"tf":1.0},"222":{"tf":1.0},"60":{"tf":1.0},"69":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"140":{"tf":1.0}}}},"df":0,"docs":{}},"i":{"c":{"df":0,"docs":{},"i":{"df":1,"docs":{"141":{"tf":1.0}}}},"df":0,"docs":{},"n":{"df":26,"docs":{"105":{"tf":2.0},"106":{"tf":1.4142135623730951},"112":{"tf":1.0},"131":{"tf":1.0},"165":{"tf":1.0},"167":{"tf":1.0},"175":{"tf":1.0},"179":{"tf":1.0},"189":{"tf":1.0},"20":{"tf":1.0},"206":{"tf":1.0},"221":{"tf":1.4142135623730951},"226":{"tf":1.0},"229":{"tf":1.0},"232":{"tf":1.0},"242":{"tf":1.0},"244":{"tf":1.0},"245":{"tf":1.0},"246":{"tf":1.4142135623730951},"252":{"tf":1.0},"253":{"tf":1.0},"261":{"tf":1.0},"37":{"tf":1.4142135623730951},"69":{"tf":1.0},"81":{"tf":1.0},"84":{"tf":1.0}},"e":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"(":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":1,"docs":{"62":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"t":{"df":9,"docs":{"103":{"tf":1.0},"104":{"tf":1.4142135623730951},"105":{"tf":1.0},"111":{"tf":1.0},"167":{"tf":1.0},"172":{"tf":1.0},"233":{"tf":1.0},"242":{"tf":1.0},"69":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"?":{"c":{"df":0,"docs":{},"r":{"df":1,"docs":{"283":{"tf":1.0}}}},"df":0,"docs":{}},"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"283":{"tf":1.0}}}}}}}}}}}}}}}}},"df":0,"docs":{}}}}}}}}}},"g":{"df":0,"docs":{},"r":{"a":{"d":{"df":1,"docs":{"236":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":3,"docs":{"189":{"tf":1.0},"201":{"tf":1.0},"229":{"tf":1.0}}}}},"l":{"a":{"df":0,"docs":{},"y":{"df":4,"docs":{"179":{"tf":1.4142135623730951},"180":{"tf":1.0},"184":{"tf":2.0},"185":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":2,"docs":{"194":{"tf":1.0},"229":{"tf":1.0}}},"t":{"df":23,"docs":{"105":{"tf":1.0},"106":{"tf":2.0},"108":{"tf":1.0},"109":{"tf":1.4142135623730951},"117":{"tf":1.0},"149":{"tf":1.0},"150":{"tf":1.0},"151":{"tf":1.0},"152":{"tf":1.4142135623730951},"154":{"tf":1.4142135623730951},"155":{"tf":1.4142135623730951},"156":{"tf":1.7320508075688772},"157":{"tf":1.4142135623730951},"158":{"tf":2.0},"159":{"tf":1.4142135623730951},"209":{"tf":1.0},"212":{"tf":1.0},"231":{"tf":1.0},"249":{"tf":1.0},"268":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0}}}},"i":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"167":{"tf":1.0}}}}},"df":0,"docs":{},"v":{"df":2,"docs":{"225":{"tf":1.0},"235":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"235":{"tf":1.4142135623730951}},"i":{"df":1,"docs":{"228":{"tf":1.0}}}}}}}},"m":{"df":0,"docs":{},"o":{"df":2,"docs":{"174":{"tf":1.0},"185":{"tf":1.0}},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":4,"docs":{"210":{"tf":1.0},"215":{"tf":1.0},"216":{"tf":1.0},"237":{"tf":1.0}}}}}}}},"p":{"df":1,"docs":{"107":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"d":{"df":45,"docs":{"10":{"tf":1.0},"105":{"tf":1.4142135623730951},"106":{"tf":1.4142135623730951},"11":{"tf":2.23606797749979},"12":{"tf":1.4142135623730951},"121":{"tf":1.4142135623730951},"122":{"tf":1.0},"123":{"tf":1.4142135623730951},"125":{"tf":1.0},"140":{"tf":1.0},"162":{"tf":1.0},"163":{"tf":1.0},"167":{"tf":2.0},"172":{"tf":1.4142135623730951},"174":{"tf":1.7320508075688772},"175":{"tf":1.0},"197":{"tf":1.0},"20":{"tf":1.0},"200":{"tf":1.7320508075688772},"22":{"tf":2.449489742783178},"220":{"tf":1.7320508075688772},"221":{"tf":3.0},"222":{"tf":1.0},"224":{"tf":1.4142135623730951},"252":{"tf":1.0},"253":{"tf":1.0},"274":{"tf":1.7320508075688772},"31":{"tf":1.7320508075688772},"36":{"tf":2.23606797749979},"37":{"tf":1.7320508075688772},"45":{"tf":1.0},"53":{"tf":1.4142135623730951},"58":{"tf":3.1622776601683795},"59":{"tf":2.0},"63":{"tf":2.0},"64":{"tf":3.872983346207417},"65":{"tf":2.449489742783178},"66":{"tf":2.0},"67":{"tf":2.0},"69":{"tf":2.449489742783178},"7":{"tf":1.7320508075688772},"73":{"tf":1.0},"78":{"tf":1.0},"79":{"tf":1.0},"81":{"tf":1.0}}},"df":0,"docs":{}}},"i":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"200":{"tf":1.7320508075688772}}}},"df":0,"docs":{}},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"y":{"df":2,"docs":{"117":{"tf":1.0},"90":{"tf":1.4142135623730951}}}}},"r":{"df":0,"docs":{},"e":{"c":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"h":{"df":3,"docs":{"93":{"tf":1.0},"96":{"tf":1.0},"99":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"<":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"79":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"v":{"df":1,"docs":{"106":{"tf":1.0}}}}},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":17,"docs":{"102":{"tf":1.0},"104":{"tf":1.0},"105":{"tf":1.0},"120":{"tf":1.0},"20":{"tf":1.0},"200":{"tf":1.0},"214":{"tf":1.0},"220":{"tf":1.0},"243":{"tf":1.0},"245":{"tf":1.0},"252":{"tf":1.0},"282":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0},"64":{"tf":1.0},"76":{"tf":1.0},"98":{"tf":1.0}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":7,"docs":{"145":{"tf":1.0},"146":{"tf":1.0},"21":{"tf":1.0},"219":{"tf":1.0},"249":{"tf":1.0},"251":{"tf":1.0},"280":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"x":{"df":1,"docs":{"283":{"tf":1.0}}}}}}}}}}}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"df":55,"docs":{"106":{"tf":1.0},"138":{"tf":1.4142135623730951},"219":{"tf":2.0},"220":{"tf":1.0},"221":{"tf":1.0},"222":{"tf":1.0},"223":{"tf":1.0},"224":{"tf":1.0},"225":{"tf":1.0},"226":{"tf":1.0},"227":{"tf":1.0},"228":{"tf":1.7320508075688772},"229":{"tf":1.0},"23":{"tf":1.0},"230":{"tf":1.0},"231":{"tf":1.0},"232":{"tf":1.0},"233":{"tf":1.0},"234":{"tf":1.0},"235":{"tf":1.0},"236":{"tf":1.0},"237":{"tf":1.0},"238":{"tf":1.0},"239":{"tf":1.0},"240":{"tf":1.0},"241":{"tf":1.0},"242":{"tf":1.0},"243":{"tf":1.0},"244":{"tf":1.0},"245":{"tf":1.0},"246":{"tf":1.0},"247":{"tf":1.0},"248":{"tf":1.0},"249":{"tf":1.0},"250":{"tf":1.0},"251":{"tf":1.7320508075688772},"252":{"tf":1.0},"253":{"tf":1.0},"254":{"tf":1.0},"255":{"tf":1.0},"256":{"tf":1.0},"257":{"tf":1.0},"258":{"tf":1.0},"259":{"tf":1.0},"260":{"tf":1.0},"261":{"tf":1.0},"262":{"tf":1.0},"263":{"tf":1.0},"264":{"tf":1.0},"32":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"69":{"tf":1.0},"76":{"tf":1.4142135623730951},"96":{"tf":1.0}}}},"r":{"df":3,"docs":{"189":{"tf":1.0},"200":{"tf":1.0},"233":{"tf":1.0}}}},"k":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":2,"docs":{"280":{"tf":1.0},"282":{"tf":1.0}}}}},"df":23,"docs":{"1":{"tf":1.0},"11":{"tf":1.4142135623730951},"187":{"tf":1.0},"192":{"tf":1.7320508075688772},"201":{"tf":2.0},"206":{"tf":1.0},"210":{"tf":1.0},"217":{"tf":1.0},"218":{"tf":1.0},"223":{"tf":1.7320508075688772},"225":{"tf":1.7320508075688772},"226":{"tf":1.0},"228":{"tf":1.4142135623730951},"229":{"tf":1.0},"231":{"tf":1.4142135623730951},"232":{"tf":1.0},"241":{"tf":1.0},"255":{"tf":1.0},"257":{"tf":1.4142135623730951},"258":{"tf":1.0},"279":{"tf":1.0},"280":{"tf":1.4142135623730951},"282":{"tf":1.0}}}}}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"y":{"df":4,"docs":{"128":{"tf":1.0},"129":{"tf":1.7320508075688772},"131":{"tf":2.0},"132":{"tf":1.4142135623730951}}}},"u":{"c":{"df":0,"docs":{},"t":{"df":6,"docs":{"128":{"tf":2.0},"129":{"tf":1.4142135623730951},"130":{"tf":1.0},"131":{"tf":1.0},"132":{"tf":1.0},"133":{"tf":1.7320508075688772}},"o":{"df":0,"docs":{},"r":{"df":2,"docs":{"128":{"tf":1.0},"133":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}}}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":20,"docs":{"104":{"tf":1.0},"105":{"tf":1.4142135623730951},"111":{"tf":1.0},"12":{"tf":1.0},"145":{"tf":1.0},"15":{"tf":1.0},"161":{"tf":1.0},"175":{"tf":1.0},"22":{"tf":1.0},"227":{"tf":1.7320508075688772},"228":{"tf":1.0},"232":{"tf":1.4142135623730951},"242":{"tf":1.7320508075688772},"245":{"tf":1.0},"252":{"tf":1.0},"264":{"tf":1.0},"268":{"tf":1.0},"274":{"tf":1.0},"32":{"tf":1.0},"37":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"231":{"tf":1.0}}}},"df":0,"docs":{},"k":{"df":0,"docs":{},"t":{"df":1,"docs":{"65":{"tf":1.0}}}},"r":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"250":{"tf":1.0}},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"184":{"tf":1.0}}}}}}}}}}},"v":{"df":6,"docs":{"11":{"tf":1.7320508075688772},"121":{"tf":1.0},"268":{"tf":1.0},"274":{"tf":1.4142135623730951},"58":{"tf":2.23606797749979},"64":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":39,"docs":{"138":{"tf":1.0},"16":{"tf":1.0},"162":{"tf":1.4142135623730951},"164":{"tf":1.4142135623730951},"169":{"tf":1.4142135623730951},"17":{"tf":1.0},"170":{"tf":1.4142135623730951},"171":{"tf":1.7320508075688772},"172":{"tf":1.7320508075688772},"18":{"tf":1.4142135623730951},"19":{"tf":1.0},"199":{"tf":1.0},"20":{"tf":1.0},"21":{"tf":1.0},"22":{"tf":1.0},"223":{"tf":1.0},"23":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"26":{"tf":2.23606797749979},"260":{"tf":1.0},"27":{"tf":1.0},"274":{"tf":2.449489742783178},"277":{"tf":1.0},"28":{"tf":1.0},"284":{"tf":1.4142135623730951},"29":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.0},"34":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"45":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0}}}}}},"i":{"c":{"df":17,"docs":{"126":{"tf":1.7320508075688772},"175":{"tf":1.0},"226":{"tf":1.0},"229":{"tf":1.4142135623730951},"231":{"tf":2.449489742783178},"232":{"tf":1.0},"233":{"tf":3.1622776601683795},"236":{"tf":1.0},"237":{"tf":1.0},"242":{"tf":1.4142135623730951},"245":{"tf":2.0},"249":{"tf":1.0},"255":{"tf":1.0},"276":{"tf":1.0},"283":{"tf":1.4142135623730951},"45":{"tf":1.0},"51":{"tf":1.0}},"e":{".":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"283":{"tf":1.0}}}}}}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"f":{"df":0,"docs":{},"x":{"a":{"df":1,"docs":{"283":{"tf":1.0}}},"df":0,"docs":{}}}}}}}},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":1,"docs":{"283":{"tf":1.0}}}}}},"_":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"242":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"i":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":8,"docs":{"200":{"tf":1.7320508075688772},"221":{"tf":1.0},"254":{"tf":1.0},"255":{"tf":1.7320508075688772},"256":{"tf":1.0},"257":{"tf":1.0},"258":{"tf":2.0},"274":{"tf":1.0}}}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":2,"docs":{"31":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951}}}}}},"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":2,"docs":{"105":{"tf":1.0},"106":{"tf":1.0}}},"y":{"/":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"106":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"d":{"df":0,"docs":{},"n":{"'":{"df":0,"docs":{},"t":{"df":1,"docs":{"61":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":4,"docs":{"26":{"tf":1.0},"268":{"tf":2.0},"269":{"tf":1.0},"89":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"r":{"df":32,"docs":{"0":{"tf":1.0},"104":{"tf":1.0},"108":{"tf":1.4142135623730951},"109":{"tf":1.4142135623730951},"115":{"tf":1.0},"123":{"tf":1.0},"131":{"tf":1.0},"146":{"tf":1.4142135623730951},"165":{"tf":1.7320508075688772},"174":{"tf":1.0},"198":{"tf":1.0},"201":{"tf":1.0},"204":{"tf":1.0},"22":{"tf":1.0},"220":{"tf":1.0},"228":{"tf":1.0},"233":{"tf":1.7320508075688772},"242":{"tf":1.0},"243":{"tf":1.0},"245":{"tf":1.7320508075688772},"257":{"tf":1.0},"262":{"tf":1.0},"283":{"tf":1.0},"58":{"tf":1.0},"6":{"tf":1.0},"60":{"tf":1.0},"64":{"tf":1.0},"75":{"tf":1.0},"78":{"tf":1.0},"81":{"tf":1.4142135623730951},"82":{"tf":1.0},"84":{"tf":1.0}}}},"i":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":5,"docs":{"128":{"tf":1.0},"158":{"tf":1.0},"231":{"tf":1.0},"43":{"tf":1.0},"82":{"tf":1.7320508075688772}},"i":{"df":2,"docs":{"115":{"tf":1.0},"180":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"g":{"df":1,"docs":{"241":{"tf":1.0}}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":1,"docs":{"7":{"tf":1.0}}}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":1,"docs":{"64":{"tf":1.0}}}}}},"r":{"df":2,"docs":{"37":{"tf":1.0},"61":{"tf":1.0}},"e":{"c":{"df":0,"docs":{},"t":{"df":6,"docs":{"220":{"tf":1.0},"225":{"tf":1.0},"226":{"tf":1.0},"268":{"tf":1.4142135623730951},"283":{"tf":1.0},"37":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":10,"docs":{"106":{"tf":1.4142135623730951},"115":{"tf":1.0},"140":{"tf":1.0},"205":{"tf":1.0},"229":{"tf":1.0},"232":{"tf":1.0},"272":{"tf":1.0},"64":{"tf":1.0},"78":{"tf":1.0},"84":{"tf":1.0}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":21,"docs":{"12":{"tf":1.0},"122":{"tf":1.0},"22":{"tf":1.7320508075688772},"224":{"tf":1.0},"229":{"tf":1.0},"25":{"tf":1.0},"252":{"tf":2.23606797749979},"258":{"tf":1.0},"29":{"tf":1.7320508075688772},"30":{"tf":1.0},"33":{"tf":1.0},"34":{"tf":1.4142135623730951},"35":{"tf":1.0},"46":{"tf":1.0},"48":{"tf":1.4142135623730951},"60":{"tf":1.0},"61":{"tf":1.0},"70":{"tf":2.23606797749979},"72":{"tf":2.23606797749979},"73":{"tf":1.0},"74":{"tf":1.0}}}}}}},"df":0,"docs":{}},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"11":{"tf":1.0}}}},"df":0,"docs":{}}},"s":{"a":{"b":{"df":0,"docs":{},"l":{"df":11,"docs":{"178":{"tf":1.0},"180":{"tf":1.4142135623730951},"182":{"tf":1.0},"183":{"tf":1.0},"184":{"tf":1.0},"232":{"tf":1.0},"233":{"tf":1.0},"245":{"tf":1.4142135623730951},"26":{"tf":2.23606797749979},"48":{"tf":1.0},"61":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"103":{"tf":1.7320508075688772}}}},"df":0,"docs":{}}}}},"c":{"a":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"283":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"242":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":5,"docs":{"231":{"tf":1.0},"234":{"tf":2.23606797749979},"242":{"tf":1.4142135623730951},"283":{"tf":1.0},"87":{"tf":1.0}}}},"df":0,"docs":{}}}},"v":{"df":2,"docs":{"158":{"tf":1.4142135623730951},"261":{"tf":1.4142135623730951}}}},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":6,"docs":{"111":{"tf":1.0},"186":{"tf":1.0},"193":{"tf":1.0},"194":{"tf":1.0},"214":{"tf":1.0},"231":{"tf":1.0}}}}}},"df":0,"docs":{},"k":{"df":5,"docs":{"109":{"tf":1.0},"149":{"tf":1.0},"154":{"tf":1.0},"179":{"tf":1.0},"201":{"tf":1.0}}},"p":{"a":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"115":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"y":{"df":2,"docs":{"117":{"tf":1.0},"283":{"tf":1.0}}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":1,"docs":{"175":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"118":{"tf":1.0}}}}}},"t":{"/":{"df":0,"docs":{},"j":{"df":0,"docs":{},"n":{"a":{".":{"a":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"37":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":1,"docs":{"37":{"tf":1.4142135623730951}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":2,"docs":{"145":{"tf":1.0},"150":{"tf":1.0}}}}}}}}},"r":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":21,"docs":{"134":{"tf":1.0},"160":{"tf":1.4142135623730951},"161":{"tf":1.0},"163":{"tf":2.0},"164":{"tf":1.0},"165":{"tf":1.0},"167":{"tf":1.0},"171":{"tf":2.0},"172":{"tf":2.0},"214":{"tf":1.0},"215":{"tf":2.0},"218":{"tf":1.0},"219":{"tf":1.0},"220":{"tf":1.7320508075688772},"223":{"tf":1.0},"23":{"tf":1.4142135623730951},"252":{"tf":1.4142135623730951},"253":{"tf":1.0},"32":{"tf":1.4142135623730951},"65":{"tf":1.0},"74":{"tf":2.0}}}}},"df":0,"docs":{}}}}},"v":{"df":0,"docs":{},"e":{"df":1,"docs":{"174":{"tf":1.0}},"r":{"df":0,"docs":{},"g":{"df":2,"docs":{"196":{"tf":1.0},"252":{"tf":1.0}}}}}}},"n":{"a":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":1,"docs":{"202":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"o":{"c":{"df":8,"docs":{"107":{"tf":1.4142135623730951},"116":{"tf":1.0},"258":{"tf":1.0},"264":{"tf":1.0},"37":{"tf":1.0},"41":{"tf":1.0},"60":{"tf":1.0},"90":{"tf":1.0}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"194":{"tf":1.0},"274":{"tf":1.0}}}}},"s":{"/":{"d":{"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"258":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"62":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":33,"docs":{"101":{"tf":1.0},"107":{"tf":2.23606797749979},"109":{"tf":1.0},"111":{"tf":1.0},"115":{"tf":1.0},"116":{"tf":1.0},"120":{"tf":1.4142135623730951},"123":{"tf":1.0},"128":{"tf":1.0},"138":{"tf":1.4142135623730951},"15":{"tf":1.0},"175":{"tf":1.0},"185":{"tf":1.0},"187":{"tf":1.0},"205":{"tf":1.0},"219":{"tf":1.4142135623730951},"220":{"tf":1.0},"23":{"tf":1.0},"233":{"tf":1.0},"236":{"tf":1.0},"243":{"tf":1.0},"251":{"tf":1.0},"252":{"tf":1.0},"261":{"tf":1.4142135623730951},"274":{"tf":1.4142135623730951},"284":{"tf":2.23606797749979},"285":{"tf":1.7320508075688772},"286":{"tf":2.449489742783178},"41":{"tf":1.0},"62":{"tf":1.0},"75":{"tf":2.23606797749979},"85":{"tf":1.0},"91":{"tf":1.0}}}}}}}},"df":8,"docs":{"101":{"tf":1.0},"115":{"tf":1.0},"13":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"224":{"tf":1.0},"37":{"tf":1.0},"78":{"tf":1.0}},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"n":{"'":{"df":0,"docs":{},"t":{"df":19,"docs":{"111":{"tf":1.0},"154":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0},"169":{"tf":1.7320508075688772},"170":{"tf":1.7320508075688772},"171":{"tf":1.7320508075688772},"175":{"tf":1.0},"182":{"tf":1.0},"22":{"tf":1.0},"228":{"tf":1.0},"233":{"tf":1.0},"276":{"tf":1.0},"48":{"tf":1.7320508075688772},"60":{"tf":1.0},"7":{"tf":1.0},"78":{"tf":1.0},"82":{"tf":1.0},"90":{"tf":1.0}}}},"df":0,"docs":{}}}},"n":{"'":{"df":0,"docs":{},"t":{"df":24,"docs":{"104":{"tf":1.0},"105":{"tf":1.0},"106":{"tf":1.0},"117":{"tf":1.4142135623730951},"119":{"tf":1.0},"124":{"tf":1.0},"150":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0},"159":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"175":{"tf":1.0},"226":{"tf":1.0},"228":{"tf":1.0},"266":{"tf":1.0},"274":{"tf":1.0},"279":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"43":{"tf":1.0},"58":{"tf":1.0},"60":{"tf":1.0},"79":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":17,"docs":{"106":{"tf":1.0},"182":{"tf":1.0},"184":{"tf":1.0},"194":{"tf":1.0},"222":{"tf":1.0},"238":{"tf":1.4142135623730951},"242":{"tf":1.7320508075688772},"245":{"tf":1.0},"25":{"tf":1.0},"282":{"tf":1.0},"30":{"tf":1.0},"35":{"tf":1.0},"52":{"tf":1.0},"73":{"tf":1.4142135623730951},"79":{"tf":1.4142135623730951},"81":{"tf":1.0},"88":{"tf":1.0}}},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":1,"docs":{"117":{"tf":1.0}}}}}}}}},"’":{"df":0,"docs":{},"t":{"df":2,"docs":{"196":{"tf":1.0},"89":{"tf":1.0}}}}},"s":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"=":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"37":{"tf":2.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"t":{"df":1,"docs":{"183":{"tf":1.4142135623730951}}},"u":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"73":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"w":{"df":0,"docs":{},"n":{"df":4,"docs":{"115":{"tf":1.0},"165":{"tf":1.0},"235":{"tf":1.0},"242":{"tf":1.0}},"g":{"df":0,"docs":{},"r":{"a":{"d":{"df":1,"docs":{"212":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":3,"docs":{"12":{"tf":1.0},"279":{"tf":1.0},"40":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"116":{"tf":1.0},"172":{"tf":1.0}}},"df":0,"docs":{}}}}}},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"274":{"tf":1.0}}}},"r":{"a":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":2,"docs":{"104":{"tf":1.7320508075688772},"7":{"tf":1.4142135623730951}}}},"m":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"209":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"i":{"df":7,"docs":{"203":{"tf":1.7320508075688772},"204":{"tf":1.4142135623730951},"205":{"tf":1.0},"208":{"tf":1.0},"214":{"tf":1.0},"216":{"tf":1.4142135623730951},"218":{"tf":1.4142135623730951}},"v":{"df":0,"docs":{},"e":{"df":2,"docs":{"188":{"tf":1.0},"274":{"tf":1.0}},"r":{"df":7,"docs":{"141":{"tf":1.4142135623730951},"150":{"tf":1.4142135623730951},"162":{"tf":1.4142135623730951},"178":{"tf":1.4142135623730951},"206":{"tf":1.4142135623730951},"208":{"tf":1.4142135623730951},"210":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"p":{"df":5,"docs":{"12":{"tf":1.0},"216":{"tf":1.4142135623730951},"231":{"tf":1.0},"245":{"tf":1.0},"250":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"161":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"u":{"df":0,"docs":{},"e":{"df":10,"docs":{"11":{"tf":1.0},"116":{"tf":1.0},"180":{"tf":1.4142135623730951},"197":{"tf":1.0},"220":{"tf":1.0},"233":{"tf":1.4142135623730951},"280":{"tf":1.0},"46":{"tf":1.0},"64":{"tf":1.0},"7":{"tf":1.0}}},"m":{"df":0,"docs":{},"p":{"df":1,"docs":{"62":{"tf":1.0}}}},"p":{"df":0,"docs":{},"e":{"df":3,"docs":{"140":{"tf":1.0},"145":{"tf":1.4142135623730951},"146":{"tf":1.0}}},"l":{"df":0,"docs":{},"i":{"c":{"df":6,"docs":{"122":{"tf":1.0},"196":{"tf":1.4142135623730951},"256":{"tf":1.0},"258":{"tf":1.0},"263":{"tf":1.0},"282":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}},"r":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"86":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":8,"docs":{"105":{"tf":1.0},"106":{"tf":1.0},"152":{"tf":1.0},"154":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0},"175":{"tf":1.0},"58":{"tf":1.0}}}}},"y":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":3,"docs":{"253":{"tf":1.0},"278":{"tf":1.0},"282":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{".":{"df":0,"docs":{},"g":{"df":14,"docs":{"104":{"tf":1.0},"165":{"tf":1.0},"18":{"tf":1.0},"184":{"tf":1.0},"21":{"tf":1.0},"22":{"tf":1.0},"221":{"tf":1.0},"223":{"tf":1.0},"224":{"tf":1.0},"279":{"tf":1.0},"282":{"tf":1.0},"37":{"tf":1.0},"51":{"tf":1.0},"59":{"tf":1.4142135623730951}}}},"a":{"c":{"df":0,"docs":{},"h":{"df":46,"docs":{"0":{"tf":1.0},"101":{"tf":1.0},"106":{"tf":1.0},"115":{"tf":1.4142135623730951},"163":{"tf":1.0},"166":{"tf":1.0},"167":{"tf":1.4142135623730951},"172":{"tf":1.7320508075688772},"175":{"tf":1.0},"22":{"tf":1.0},"220":{"tf":1.4142135623730951},"221":{"tf":2.449489742783178},"222":{"tf":1.0},"223":{"tf":1.0},"224":{"tf":1.4142135623730951},"225":{"tf":1.7320508075688772},"226":{"tf":1.0},"228":{"tf":1.0},"229":{"tf":1.4142135623730951},"231":{"tf":1.0},"232":{"tf":1.0},"242":{"tf":1.4142135623730951},"243":{"tf":1.0},"244":{"tf":1.0},"245":{"tf":1.4142135623730951},"246":{"tf":1.0},"249":{"tf":1.7320508075688772},"252":{"tf":1.0},"256":{"tf":1.4142135623730951},"257":{"tf":1.0},"258":{"tf":1.4142135623730951},"271":{"tf":1.0},"274":{"tf":1.0},"282":{"tf":1.0},"32":{"tf":1.0},"37":{"tf":1.0},"43":{"tf":1.0},"46":{"tf":1.0},"48":{"tf":1.0},"51":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.4142135623730951},"64":{"tf":1.0},"68":{"tf":1.0},"69":{"tf":1.0},"84":{"tf":1.0}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":8,"docs":{"134":{"tf":1.0},"176":{"tf":1.4142135623730951},"177":{"tf":1.0},"178":{"tf":1.0},"179":{"tf":1.4142135623730951},"180":{"tf":1.0},"183":{"tf":1.0},"184":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"205":{"tf":1.0},"90":{"tf":1.0}}}}}}},"s":{"df":1,"docs":{"162":{"tf":1.4142135623730951}},"i":{"df":9,"docs":{"115":{"tf":1.0},"116":{"tf":1.4142135623730951},"129":{"tf":1.0},"158":{"tf":1.0},"220":{"tf":1.0},"233":{"tf":1.0},"241":{"tf":1.0},"280":{"tf":1.0},"46":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":4,"docs":{"172":{"tf":1.0},"278":{"tf":1.0},"60":{"tf":1.0},"81":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{"df":4,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"26":{"tf":1.0},"58":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"i":{"df":4,"docs":{"107":{"tf":1.0},"133":{"tf":1.0},"162":{"tf":1.0},"221":{"tf":1.0}}}}}}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":2,"docs":{"226":{"tf":1.0},"232":{"tf":1.0}}}}}}}}}},"d":{"df":0,"docs":{},"g":{"df":2,"docs":{"224":{"tf":1.7320508075688772},"233":{"tf":1.0}}},"i":{"df":0,"docs":{},"t":{"df":44,"docs":{"100":{"tf":1.0},"105":{"tf":1.0},"109":{"tf":1.4142135623730951},"117":{"tf":1.0},"119":{"tf":1.0},"124":{"tf":1.4142135623730951},"127":{"tf":1.0},"133":{"tf":1.0},"138":{"tf":1.0},"147":{"tf":1.0},"15":{"tf":1.0},"159":{"tf":1.0},"175":{"tf":1.0},"18":{"tf":1.0},"185":{"tf":1.0},"201":{"tf":1.0},"218":{"tf":1.0},"22":{"tf":1.0},"224":{"tf":1.0},"242":{"tf":1.0},"250":{"tf":1.0},"253":{"tf":1.0},"258":{"tf":2.23606797749979},"259":{"tf":1.0},"263":{"tf":1.0},"264":{"tf":1.0},"273":{"tf":1.0},"277":{"tf":1.0},"282":{"tf":1.0},"283":{"tf":1.0},"286":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"40":{"tf":1.0},"51":{"tf":1.0},"56":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0},"67":{"tf":1.0},"70":{"tf":1.4142135623730951},"74":{"tf":1.0},"8":{"tf":1.0},"90":{"tf":1.0}}}},"u":{"c":{"df":1,"docs":{"214":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":6,"docs":{"184":{"tf":1.0},"197":{"tf":1.0},"201":{"tf":1.0},"210":{"tf":1.0},"69":{"tf":1.0},"7":{"tf":1.0}}}},"df":0,"docs":{}},"i":{"c":{"df":0,"docs":{},"i":{"df":5,"docs":{"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":7,"docs":{"121":{"tf":1.0},"161":{"tf":1.0},"164":{"tf":1.0},"196":{"tf":1.0},"199":{"tf":1.0},"201":{"tf":1.0},"229":{"tf":1.0}}}}}}},"g":{"df":13,"docs":{"189":{"tf":1.0},"20":{"tf":1.0},"200":{"tf":1.4142135623730951},"225":{"tf":1.4142135623730951},"231":{"tf":1.4142135623730951},"232":{"tf":1.4142135623730951},"236":{"tf":1.0},"242":{"tf":1.4142135623730951},"250":{"tf":1.0},"74":{"tf":1.0},"82":{"tf":1.7320508075688772},"83":{"tf":1.0},"86":{"tf":1.0}}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":3,"docs":{"145":{"tf":1.4142135623730951},"155":{"tf":1.0},"156":{"tf":1.0}}}}}}},"m":{"b":{"df":1,"docs":{"244":{"tf":1.0}},"e":{"d":{"df":5,"docs":{"125":{"tf":1.0},"225":{"tf":1.4142135623730951},"228":{"tf":1.0},"242":{"tf":1.0},"250":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":1,"docs":{"274":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"262":{"tf":1.0},"282":{"tf":1.0}}}},"p":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"s":{"df":1,"docs":{"204":{"tf":1.0}},"i":{"df":1,"docs":{"75":{"tf":1.0}}}}},"df":0,"docs":{}},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"y":{"df":0,"docs":{},"e":{"df":1,"docs":{"258":{"tf":1.0}}}}}}},"u":{"df":0,"docs":{},"l":{"df":2,"docs":{"17":{"tf":1.4142135623730951},"51":{"tf":1.0}}}}},"n":{"a":{"b":{"df":0,"docs":{},"l":{"df":11,"docs":{"0":{"tf":1.4142135623730951},"10":{"tf":1.0},"138":{"tf":1.0},"184":{"tf":1.0},"189":{"tf":1.0},"232":{"tf":1.0},"233":{"tf":1.4142135623730951},"242":{"tf":1.0},"245":{"tf":2.0},"26":{"tf":1.0},"62":{"tf":1.0}},"e":{"d":{"/":{"d":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"242":{"tf":1.0}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"o":{"d":{"df":1,"docs":{"62":{"tf":1.0}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"s":{"df":1,"docs":{"82":{"tf":1.0}},"s":{"df":2,"docs":{"208":{"tf":1.0},"217":{"tf":1.0}}}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"5":{"tf":1.0}}}},"r":{"a":{"df":0,"docs":{},"g":{"df":4,"docs":{"165":{"tf":1.0},"264":{"tf":1.0},"274":{"tf":1.0},"7":{"tf":1.0}}}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":9,"docs":{"140":{"tf":2.449489742783178},"141":{"tf":1.0},"142":{"tf":1.4142135623730951},"143":{"tf":1.0},"145":{"tf":3.1622776601683795},"146":{"tf":2.0},"229":{"tf":1.0},"245":{"tf":1.4142135623730951},"254":{"tf":2.0}}}}}},"y":{"df":0,"docs":{},"r":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"141":{"tf":1.0}}}}}}},"d":{"df":32,"docs":{"10":{"tf":1.4142135623730951},"103":{"tf":1.0},"124":{"tf":1.0},"13":{"tf":1.0},"145":{"tf":1.0},"197":{"tf":1.4142135623730951},"199":{"tf":1.0},"20":{"tf":1.0},"200":{"tf":1.7320508075688772},"204":{"tf":2.23606797749979},"205":{"tf":1.4142135623730951},"213":{"tf":1.0},"214":{"tf":1.0},"216":{"tf":1.7320508075688772},"218":{"tf":1.0},"22":{"tf":1.4142135623730951},"225":{"tf":1.4142135623730951},"233":{"tf":1.0},"236":{"tf":1.0},"240":{"tf":1.0},"242":{"tf":1.0},"48":{"tf":2.449489742783178},"51":{"tf":1.4142135623730951},"52":{"tf":1.4142135623730951},"58":{"tf":1.0},"62":{"tf":1.0},"64":{"tf":1.0},"70":{"tf":1.0},"72":{"tf":1.0},"82":{"tf":1.0},"83":{"tf":1.0},"84":{"tf":1.4142135623730951}},"i":{"df":0,"docs":{},"f":{"df":1,"docs":{"37":{"tf":1.0}}}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"x":{"a":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"283":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"c":{"df":7,"docs":{"190":{"tf":1.0},"201":{"tf":1.4142135623730951},"225":{"tf":1.4142135623730951},"226":{"tf":1.0},"242":{"tf":1.0},"274":{"tf":1.0},"79":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":24,"docs":{"104":{"tf":1.0},"187":{"tf":1.0},"225":{"tf":2.6457513110645907},"226":{"tf":2.6457513110645907},"228":{"tf":3.1622776601683795},"229":{"tf":2.23606797749979},"231":{"tf":3.1622776601683795},"232":{"tf":3.3166247903554},"233":{"tf":2.23606797749979},"234":{"tf":1.7320508075688772},"236":{"tf":1.0},"237":{"tf":1.0},"238":{"tf":2.0},"241":{"tf":2.449489742783178},"242":{"tf":3.605551275463989},"245":{"tf":2.8284271247461903},"246":{"tf":1.4142135623730951},"249":{"tf":1.4142135623730951},"258":{"tf":1.4142135623730951},"274":{"tf":1.0},"48":{"tf":1.4142135623730951},"78":{"tf":1.0},"8":{"tf":1.0},"87":{"tf":1.0}},"e":{"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"242":{"tf":1.0}}}}}}}},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"_":{"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"242":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"242":{"tf":1.0}}}}}}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"242":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":3,"docs":{"214":{"tf":1.0},"225":{"tf":1.0},"26":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":3,"docs":{"180":{"tf":1.0},"183":{"tf":1.0},"189":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":21,"docs":{"104":{"tf":1.4142135623730951},"11":{"tf":1.0},"115":{"tf":1.0},"122":{"tf":1.0},"132":{"tf":1.0},"197":{"tf":1.0},"20":{"tf":1.0},"222":{"tf":1.0},"225":{"tf":1.0},"226":{"tf":1.0},"233":{"tf":1.0},"236":{"tf":1.0},"263":{"tf":1.0},"274":{"tf":1.4142135623730951},"279":{"tf":1.0},"37":{"tf":1.0},"45":{"tf":1.7320508075688772},"46":{"tf":1.0},"64":{"tf":1.4142135623730951},"7":{"tf":1.0},"79":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":3,"docs":{"122":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0}}}},"i":{"df":0,"docs":{},"r":{"df":12,"docs":{"103":{"tf":1.7320508075688772},"106":{"tf":1.0},"130":{"tf":1.0},"140":{"tf":1.0},"184":{"tf":1.0},"226":{"tf":1.0},"231":{"tf":1.0},"232":{"tf":1.0},"260":{"tf":1.0},"274":{"tf":1.4142135623730951},"46":{"tf":1.0},"62":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"142":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"i":{"df":6,"docs":{"226":{"tf":1.0},"241":{"tf":1.4142135623730951},"283":{"tf":1.0},"7":{"tf":1.0},"73":{"tf":1.7320508075688772},"78":{"tf":1.7320508075688772}}}}},"u":{"df":0,"docs":{},"m":{"df":8,"docs":{"104":{"tf":1.0},"105":{"tf":1.7320508075688772},"106":{"tf":1.0},"108":{"tf":1.0},"242":{"tf":1.7320508075688772},"87":{"tf":1.0},"93":{"tf":1.0},"97":{"tf":1.0}}}},"v":{"df":1,"docs":{"12":{"tf":1.0}},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":10,"docs":{"11":{"tf":1.7320508075688772},"12":{"tf":1.0},"122":{"tf":1.0},"14":{"tf":1.0},"274":{"tf":2.23606797749979},"277":{"tf":1.0},"37":{"tf":1.4142135623730951},"5":{"tf":1.0},"61":{"tf":1.0},"9":{"tf":1.0}},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":4,"docs":{"11":{"tf":1.4142135623730951},"12":{"tf":1.0},"14":{"tf":1.0},"279":{"tf":1.0}}}}},"df":1,"docs":{"225":{"tf":1.0}}}}}}}}}}}},"o":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"'":{"df":1,"docs":{"274":{"tf":1.0}}},"df":0,"docs":{}}}}},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"229":{"tf":1.0},"231":{"tf":1.0}}}}}}},"i":{"c":{"df":1,"docs":{"218":{"tf":1.0}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":1,"docs":{"200":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":24,"docs":{"104":{"tf":1.7320508075688772},"105":{"tf":1.0},"116":{"tf":1.0},"122":{"tf":1.0},"149":{"tf":1.7320508075688772},"150":{"tf":1.0},"151":{"tf":2.23606797749979},"152":{"tf":2.449489742783178},"154":{"tf":2.449489742783178},"155":{"tf":2.449489742783178},"156":{"tf":2.449489742783178},"158":{"tf":1.0},"189":{"tf":1.0},"204":{"tf":1.0},"226":{"tf":1.0},"237":{"tf":1.0},"239":{"tf":1.0},"256":{"tf":1.0},"258":{"tf":1.0},"262":{"tf":1.0},"264":{"tf":1.0},"283":{"tf":1.7320508075688772},"61":{"tf":1.0},"7":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"i":{"df":8,"docs":{"133":{"tf":1.0},"154":{"tf":1.0},"183":{"tf":1.0},"226":{"tf":1.0},"258":{"tf":1.0},"51":{"tf":1.0},"62":{"tf":1.0},"64":{"tf":1.0}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":3,"docs":{"11":{"tf":1.0},"111":{"tf":1.0},"115":{"tf":1.0}}}}}}},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":3,"docs":{"141":{"tf":1.0},"143":{"tf":1.0},"208":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"261":{"tf":1.0}}}}}},"t":{"c":{"df":24,"docs":{"112":{"tf":1.0},"149":{"tf":1.0},"207":{"tf":1.0},"214":{"tf":1.0},"220":{"tf":1.0},"226":{"tf":1.4142135623730951},"236":{"tf":1.0},"237":{"tf":1.4142135623730951},"239":{"tf":1.0},"240":{"tf":1.0},"241":{"tf":1.0},"242":{"tf":1.0},"244":{"tf":1.0},"249":{"tf":1.0},"255":{"tf":1.0},"278":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"61":{"tf":1.0},"75":{"tf":1.0},"78":{"tf":1.0},"79":{"tf":1.0},"80":{"tf":1.0},"86":{"tf":1.0}}},"df":0,"docs":{}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":3,"docs":{"161":{"tf":1.0},"203":{"tf":1.0},"59":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":10,"docs":{"104":{"tf":1.0},"140":{"tf":1.0},"19":{"tf":1.0},"212":{"tf":1.0},"225":{"tf":1.7320508075688772},"228":{"tf":1.0},"232":{"tf":1.0},"58":{"tf":1.4142135623730951},"60":{"tf":1.0},"61":{"tf":1.0}},"t":{"df":4,"docs":{"190":{"tf":1.0},"193":{"tf":1.0},"197":{"tf":1.4142135623730951},"283":{"tf":1.0}},"u":{"df":1,"docs":{"167":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"'":{"df":1,"docs":{"104":{"tf":1.0}}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"h":{"df":11,"docs":{"179":{"tf":1.0},"194":{"tf":1.0},"200":{"tf":1.0},"22":{"tf":1.0},"225":{"tf":1.0},"226":{"tf":1.0},"258":{"tf":1.0},"261":{"tf":1.0},"61":{"tf":1.0},"64":{"tf":1.0},"89":{"tf":1.4142135623730951}}}}}}},"i":{"d":{"df":1,"docs":{"201":{"tf":1.0}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"v":{"df":3,"docs":{"193":{"tf":1.0},"63":{"tf":1.0},"81":{"tf":1.0}}}}}},"x":{"a":{"c":{"df":0,"docs":{},"t":{"df":4,"docs":{"123":{"tf":1.0},"197":{"tf":1.0},"200":{"tf":1.0},"233":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":6,"docs":{"132":{"tf":1.0},"201":{"tf":1.4142135623730951},"225":{"tf":1.0},"237":{"tf":1.0},"61":{"tf":1.0},"79":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":2,"docs":{"128":{"tf":1.0},"205":{"tf":1.0}}}},"p":{"df":0,"docs":{},"l":{"df":45,"docs":{"100":{"tf":1.4142135623730951},"105":{"tf":1.7320508075688772},"106":{"tf":1.0},"107":{"tf":1.0},"108":{"tf":1.4142135623730951},"109":{"tf":1.0},"115":{"tf":1.0},"119":{"tf":1.4142135623730951},"123":{"tf":1.4142135623730951},"124":{"tf":1.4142135623730951},"130":{"tf":1.0},"131":{"tf":1.0},"132":{"tf":1.0},"156":{"tf":1.0},"175":{"tf":1.0},"18":{"tf":1.0},"20":{"tf":1.4142135623730951},"201":{"tf":1.4142135623730951},"213":{"tf":1.0},"216":{"tf":1.0},"22":{"tf":1.0},"225":{"tf":1.7320508075688772},"226":{"tf":1.4142135623730951},"228":{"tf":1.0},"229":{"tf":1.7320508075688772},"232":{"tf":1.0},"233":{"tf":1.0},"238":{"tf":1.0},"245":{"tf":1.0},"25":{"tf":1.0},"252":{"tf":1.0},"268":{"tf":1.7320508075688772},"31":{"tf":1.0},"36":{"tf":1.0},"58":{"tf":2.23606797749979},"59":{"tf":1.7320508075688772},"62":{"tf":1.0},"78":{"tf":1.0},"79":{"tf":1.7320508075688772},"81":{"tf":1.0},"82":{"tf":1.4142135623730951},"83":{"tf":1.4142135623730951},"86":{"tf":1.0},"94":{"tf":1.4142135623730951},"97":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":1,"docs":{"105":{"tf":1.4142135623730951}}}}}},"s":{"/":{"*":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"61":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"106":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}}}}}}}},"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":2,"docs":{"171":{"tf":1.0},"190":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"u":{"d":{"df":2,"docs":{"264":{"tf":1.4142135623730951},"37":{"tf":1.4142135623730951}}},"df":0,"docs":{},"s":{"df":1,"docs":{"194":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":9,"docs":{"13":{"tf":1.0},"158":{"tf":1.0},"19":{"tf":1.4142135623730951},"249":{"tf":1.4142135623730951},"25":{"tf":1.0},"274":{"tf":1.4142135623730951},"60":{"tf":1.4142135623730951},"62":{"tf":1.0},"7":{"tf":1.0}}}}},"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":3,"docs":{"166":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0}}}}},"df":0,"docs":{}}},"h":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"188":{"tf":1.0}}}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":36,"docs":{"101":{"tf":1.7320508075688772},"104":{"tf":1.7320508075688772},"105":{"tf":1.7320508075688772},"108":{"tf":1.0},"109":{"tf":1.4142135623730951},"115":{"tf":1.4142135623730951},"120":{"tf":1.0},"122":{"tf":1.4142135623730951},"123":{"tf":1.0},"13":{"tf":1.0},"140":{"tf":1.0},"141":{"tf":1.0},"166":{"tf":1.0},"167":{"tf":1.4142135623730951},"183":{"tf":1.0},"187":{"tf":1.0},"191":{"tf":1.4142135623730951},"193":{"tf":1.0},"196":{"tf":1.7320508075688772},"197":{"tf":2.23606797749979},"198":{"tf":2.0},"199":{"tf":1.0},"200":{"tf":1.7320508075688772},"201":{"tf":1.0},"210":{"tf":1.0},"225":{"tf":1.0},"228":{"tf":1.0},"232":{"tf":1.0},"233":{"tf":1.0},"263":{"tf":1.0},"274":{"tf":1.0},"41":{"tf":1.4142135623730951},"59":{"tf":1.0},"64":{"tf":2.0},"75":{"tf":1.0},"78":{"tf":1.0}}}},"t":{"df":2,"docs":{"233":{"tf":1.0},"60":{"tf":1.0}}}},"p":{"a":{"df":0,"docs":{},"n":{"d":{"df":3,"docs":{"167":{"tf":1.0},"201":{"tf":1.0},"283":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"(":{"\"":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"62":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":20,"docs":{"101":{"tf":1.0},"104":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"156":{"tf":1.0},"178":{"tf":1.0},"180":{"tf":1.0},"201":{"tf":1.0},"222":{"tf":1.0},"224":{"tf":1.0},"225":{"tf":1.0},"230":{"tf":1.0},"237":{"tf":1.0},"252":{"tf":1.0},"263":{"tf":1.0},"264":{"tf":1.0},"278":{"tf":1.0},"62":{"tf":1.0},"7":{"tf":1.0},"81":{"tf":1.0}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":27,"docs":{"10":{"tf":1.0},"101":{"tf":1.0},"115":{"tf":1.0},"134":{"tf":1.0},"158":{"tf":1.0},"164":{"tf":1.0},"174":{"tf":1.0},"175":{"tf":1.0},"176":{"tf":1.4142135623730951},"177":{"tf":1.7320508075688772},"178":{"tf":1.7320508075688772},"179":{"tf":2.6457513110645907},"180":{"tf":2.6457513110645907},"182":{"tf":2.0},"183":{"tf":2.6457513110645907},"184":{"tf":3.0},"185":{"tf":1.4142135623730951},"187":{"tf":1.0},"204":{"tf":1.7320508075688772},"205":{"tf":1.0},"206":{"tf":1.4142135623730951},"208":{"tf":1.4142135623730951},"212":{"tf":1.0},"218":{"tf":1.0},"22":{"tf":1.0},"233":{"tf":1.0},"274":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"204":{"tf":1.0}}},"df":0,"docs":{}}},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":6,"docs":{"0":{"tf":1.0},"177":{"tf":1.0},"178":{"tf":1.0},"184":{"tf":1.4142135623730951},"268":{"tf":1.0},"284":{"tf":1.0}}}}}}}}},"i":{"df":0,"docs":{},"r":{"df":1,"docs":{"277":{"tf":1.0}}}},"l":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"81":{"tf":1.0}}}},"n":{"df":1,"docs":{"7":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":4,"docs":{"128":{"tf":1.0},"138":{"tf":1.0},"224":{"tf":1.0},"241":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":4,"docs":{"115":{"tf":1.0},"201":{"tf":1.0},"64":{"tf":1.0},"7":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":3,"docs":{"11":{"tf":1.4142135623730951},"252":{"tf":1.0},"37":{"tf":2.6457513110645907}}}},"s":{"df":29,"docs":{"101":{"tf":1.0},"103":{"tf":1.4142135623730951},"108":{"tf":1.0},"109":{"tf":1.0},"111":{"tf":1.4142135623730951},"116":{"tf":1.0},"146":{"tf":1.0},"159":{"tf":1.0},"182":{"tf":1.0},"192":{"tf":1.0},"196":{"tf":1.0},"198":{"tf":1.0},"200":{"tf":1.0},"221":{"tf":1.0},"226":{"tf":1.4142135623730951},"229":{"tf":1.0},"232":{"tf":2.23606797749979},"236":{"tf":1.0},"239":{"tf":1.0},"241":{"tf":1.0},"242":{"tf":1.4142135623730951},"253":{"tf":1.0},"268":{"tf":1.0},"281":{"tf":2.23606797749979},"69":{"tf":1.0},"77":{"tf":1.0},"78":{"tf":2.23606797749979},"83":{"tf":1.0},"90":{"tf":1.7320508075688772}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":2,"docs":{"104":{"tf":1.4142135623730951},"170":{"tf":1.0}}}}}}},"t":{".":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"r":{"c":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":1,"docs":{"22":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"37":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":5,"docs":{"257":{"tf":1.4142135623730951},"44":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"98":{"tf":1.4142135623730951}}},"t":{"df":1,"docs":{"115":{"tf":1.0}}}},"r":{"df":0,"docs":{},"n":{"df":8,"docs":{"103":{"tf":1.0},"111":{"tf":1.0},"114":{"tf":1.0},"145":{"tf":1.0},"167":{"tf":1.0},"242":{"tf":1.0},"260":{"tf":1.0},"7":{"tf":1.0}}}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"107":{"tf":1.0},"271":{"tf":1.0}}}},"df":1,"docs":{"115":{"tf":1.0}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":2,"docs":{"156":{"tf":1.0},"233":{"tf":1.0}}}}}}}},"f":{"a":{"c":{"df":0,"docs":{},"e":{"df":5,"docs":{"107":{"tf":1.0},"25":{"tf":1.0},"283":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0}}},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"125":{"tf":1.0}},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"138":{"tf":1.0}}}}}},"t":{"df":4,"docs":{"107":{"tf":1.0},"115":{"tf":1.0},"180":{"tf":1.0},"61":{"tf":1.0}},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"200":{"tf":1.0}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":16,"docs":{"104":{"tf":1.4142135623730951},"119":{"tf":1.0},"146":{"tf":1.0},"151":{"tf":1.0},"20":{"tf":1.0},"204":{"tf":1.0},"205":{"tf":1.0},"208":{"tf":1.0},"21":{"tf":1.0},"242":{"tf":1.0},"25":{"tf":1.0},"27":{"tf":1.0},"280":{"tf":1.0},"282":{"tf":1.0},"62":{"tf":1.0},"92":{"tf":1.0}},"u":{"df":0,"docs":{},"r":{"df":5,"docs":{"159":{"tf":1.4142135623730951},"274":{"tf":1.0},"282":{"tf":1.0},"40":{"tf":1.4142135623730951},"62":{"tf":1.0}},"e":{"df":0,"docs":{},"s":{"@":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":1,"docs":{"274":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}}},"r":{"df":1,"docs":{"116":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":3,"docs":{"158":{"tf":1.0},"241":{"tf":1.0},"7":{"tf":1.0}}}}}},"k":{"df":0,"docs":{},"e":{"df":2,"docs":{"138":{"tf":1.0},"203":{"tf":1.0}}}},"l":{"df":0,"docs":{},"s":{"df":2,"docs":{"60":{"tf":1.4142135623730951},"61":{"tf":1.4142135623730951}}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"r":{"df":2,"docs":{"102":{"tf":1.4142135623730951},"103":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}}},"q":{"0":{"df":2,"docs":{"60":{"tf":1.0},"61":{"tf":1.4142135623730951}}},"df":2,"docs":{"110":{"tf":1.4142135623730951},"85":{"tf":1.4142135623730951}}},"r":{"df":6,"docs":{"124":{"tf":1.0},"201":{"tf":1.0},"225":{"tf":1.0},"242":{"tf":1.0},"59":{"tf":1.0},"63":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"58":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"57":{"tf":1.4142135623730951}}}}}},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"205":{"tf":1.0}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":3,"docs":{"165":{"tf":1.0},"172":{"tf":1.0},"37":{"tf":1.0}}}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"180":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":12,"docs":{"103":{"tf":1.0},"124":{"tf":1.4142135623730951},"189":{"tf":1.0},"196":{"tf":1.0},"198":{"tf":1.0},"221":{"tf":1.0},"255":{"tf":1.0},"260":{"tf":1.0},"282":{"tf":1.0},"43":{"tf":1.0},"62":{"tf":1.7320508075688772},"69":{"tf":1.0}},"e":{"df":0,"docs":{},"s":{"=":{"[":{"\"":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"69":{"tf":1.0}}}}}}}}},"d":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":1,"docs":{"62":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":7,"docs":{"107":{"tf":1.0},"170":{"tf":1.0},"25":{"tf":1.0},"3":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"6":{"tf":1.0}}}},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"55":{"tf":1.0}}}}},"/":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"l":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"18":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"126":{"tf":1.0}}}}}},"df":22,"docs":{"10":{"tf":1.0},"108":{"tf":1.0},"11":{"tf":1.0},"12":{"tf":2.23606797749979},"126":{"tf":1.7320508075688772},"145":{"tf":1.0},"16":{"tf":1.7320508075688772},"17":{"tf":1.4142135623730951},"18":{"tf":1.4142135623730951},"185":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.0},"200":{"tf":1.0},"21":{"tf":1.4142135623730951},"22":{"tf":1.0},"224":{"tf":1.0},"257":{"tf":1.7320508075688772},"38":{"tf":1.0},"39":{"tf":1.7320508075688772},"40":{"tf":1.7320508075688772},"51":{"tf":1.0},"55":{"tf":1.7320508075688772}}}},"n":{"df":0,"docs":{},"e":{"c":{"df":1,"docs":{"145":{"tf":1.0}}},"df":0,"docs":{}}}},"t":{"c":{"df":0,"docs":{},"h":{"df":6,"docs":{"189":{"tf":1.0},"190":{"tf":1.4142135623730951},"232":{"tf":1.0},"245":{"tf":1.0},"249":{"tf":1.7320508075688772},"274":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"w":{"df":3,"docs":{"58":{"tf":1.0},"6":{"tf":1.0},"68":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"59":{"tf":1.0}}}}}},"f":{"df":0,"docs":{},"i":{"_":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"73":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"73":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"116":{"tf":1.0}}}}}}}}}},"df":13,"docs":{"101":{"tf":1.0},"103":{"tf":3.1622776601683795},"104":{"tf":1.0},"105":{"tf":1.7320508075688772},"106":{"tf":1.7320508075688772},"115":{"tf":1.0},"116":{"tf":2.0},"193":{"tf":1.0},"241":{"tf":1.0},"43":{"tf":1.0},"44":{"tf":2.0},"51":{"tf":1.0},"90":{"tf":1.7320508075688772}}}},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":5,"docs":{"140":{"tf":1.4142135623730951},"145":{"tf":2.0},"249":{"tf":1.0},"250":{"tf":1.0},"83":{"tf":1.0}}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"e":{":":{"/":{"/":{"/":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"q":{"/":{"c":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"e":{"/":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"31":{"tf":1.0},"36":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":63,"docs":{"1":{"tf":1.4142135623730951},"103":{"tf":1.7320508075688772},"104":{"tf":2.6457513110645907},"105":{"tf":2.6457513110645907},"106":{"tf":2.23606797749979},"107":{"tf":1.0},"108":{"tf":1.7320508075688772},"109":{"tf":2.0},"111":{"tf":1.0},"114":{"tf":1.0},"117":{"tf":1.4142135623730951},"12":{"tf":1.0},"13":{"tf":1.0},"133":{"tf":1.0},"137":{"tf":1.0},"151":{"tf":1.4142135623730951},"152":{"tf":1.4142135623730951},"157":{"tf":1.4142135623730951},"158":{"tf":2.0},"159":{"tf":1.0},"165":{"tf":1.0},"167":{"tf":1.4142135623730951},"17":{"tf":1.0},"18":{"tf":1.0},"187":{"tf":1.0},"189":{"tf":1.0},"200":{"tf":1.0},"201":{"tf":1.0},"22":{"tf":1.4142135623730951},"220":{"tf":1.0},"221":{"tf":1.0},"222":{"tf":1.0},"229":{"tf":1.0},"25":{"tf":1.4142135623730951},"251":{"tf":1.0},"252":{"tf":2.0},"26":{"tf":1.4142135623730951},"263":{"tf":1.0},"271":{"tf":1.7320508075688772},"274":{"tf":1.0},"279":{"tf":1.4142135623730951},"28":{"tf":1.4142135623730951},"282":{"tf":1.7320508075688772},"284":{"tf":1.0},"29":{"tf":1.0},"31":{"tf":1.0},"33":{"tf":1.4142135623730951},"36":{"tf":1.0},"4":{"tf":1.0},"40":{"tf":1.0},"45":{"tf":1.0},"60":{"tf":1.7320508075688772},"61":{"tf":2.449489742783178},"64":{"tf":1.0},"65":{"tf":1.0},"69":{"tf":1.0},"70":{"tf":2.449489742783178},"72":{"tf":1.0},"73":{"tf":3.0},"74":{"tf":1.0},"79":{"tf":1.0},"81":{"tf":1.4142135623730951},"98":{"tf":1.7320508075688772}},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":1,"docs":{"25":{"tf":1.0}}}}}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"126":{"tf":1.0}}}}}},"n":{"a":{"df":0,"docs":{},"l":{"df":13,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"128":{"tf":1.0},"200":{"tf":1.0},"201":{"tf":1.0},"205":{"tf":1.0},"214":{"tf":1.0},"245":{"tf":1.0},"267":{"tf":1.0},"31":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"61":{"tf":1.0},"73":{"tf":1.0}}}},"d":{"df":18,"docs":{"1":{"tf":1.0},"104":{"tf":1.0},"105":{"tf":1.0},"106":{"tf":2.23606797749979},"122":{"tf":1.0},"126":{"tf":1.0},"18":{"tf":1.0},"21":{"tf":1.0},"224":{"tf":1.0},"26":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.4142135623730951},"37":{"tf":1.0},"46":{"tf":1.0},"52":{"tf":1.0},"6":{"tf":1.7320508075688772},"62":{"tf":1.0},"82":{"tf":1.0}}},"df":0,"docs":{},"e":{"df":2,"docs":{"133":{"tf":1.0},"189":{"tf":1.0}}},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":3,"docs":{"212":{"tf":1.0},"264":{"tf":1.0},"58":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"e":{"df":1,"docs":{"68":{"tf":1.0}},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"x":{"/":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"u":{"df":1,"docs":{"271":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"a":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{":":{":":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"_":{"df":0,"docs":{},"j":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"283":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":1,"docs":{"283":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":3,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"283":{"tf":1.7320508075688772}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":52,"docs":{"0":{"tf":1.4142135623730951},"1":{"tf":1.4142135623730951},"10":{"tf":1.4142135623730951},"109":{"tf":1.0},"118":{"tf":2.0},"119":{"tf":2.23606797749979},"12":{"tf":1.0},"134":{"tf":1.0},"14":{"tf":1.4142135623730951},"145":{"tf":1.0},"15":{"tf":1.7320508075688772},"167":{"tf":1.0},"18":{"tf":1.7320508075688772},"187":{"tf":1.0},"20":{"tf":1.0},"200":{"tf":1.0},"201":{"tf":1.0},"202":{"tf":1.4142135623730951},"203":{"tf":1.4142135623730951},"206":{"tf":1.7320508075688772},"213":{"tf":1.0},"215":{"tf":1.0},"217":{"tf":1.0},"218":{"tf":2.23606797749979},"219":{"tf":1.0},"23":{"tf":2.449489742783178},"24":{"tf":1.4142135623730951},"25":{"tf":2.6457513110645907},"254":{"tf":3.1622776601683795},"255":{"tf":1.7320508075688772},"256":{"tf":1.0},"257":{"tf":2.0},"258":{"tf":1.0},"259":{"tf":1.0},"26":{"tf":1.7320508075688772},"260":{"tf":1.0},"268":{"tf":2.23606797749979},"27":{"tf":1.0},"272":{"tf":1.7320508075688772},"274":{"tf":1.0},"28":{"tf":1.0},"283":{"tf":1.7320508075688772},"284":{"tf":1.0},"29":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":2.23606797749979},"38":{"tf":1.0},"39":{"tf":1.0},"48":{"tf":1.0},"56":{"tf":1.7320508075688772},"64":{"tf":1.7320508075688772},"74":{"tf":1.7320508075688772}}}}}},"s":{"df":0,"docs":{},"t":{"df":30,"docs":{"10":{"tf":1.7320508075688772},"102":{"tf":1.7320508075688772},"104":{"tf":1.7320508075688772},"124":{"tf":1.0},"134":{"tf":1.0},"137":{"tf":1.0},"146":{"tf":1.0},"152":{"tf":1.0},"176":{"tf":1.4142135623730951},"177":{"tf":1.4142135623730951},"178":{"tf":1.0},"179":{"tf":2.23606797749979},"180":{"tf":1.4142135623730951},"182":{"tf":1.0},"183":{"tf":1.7320508075688772},"184":{"tf":1.7320508075688772},"185":{"tf":1.4142135623730951},"188":{"tf":1.0},"20":{"tf":1.0},"214":{"tf":1.0},"230":{"tf":1.4142135623730951},"245":{"tf":1.0},"25":{"tf":1.0},"280":{"tf":1.0},"283":{"tf":1.0},"6":{"tf":2.0},"62":{"tf":1.0},"81":{"tf":1.0},"88":{"tf":1.0},"89":{"tf":1.0}}}}},"t":{"df":4,"docs":{"102":{"tf":1.0},"138":{"tf":1.0},"226":{"tf":1.0},"229":{"tf":1.0}}},"x":{"df":8,"docs":{"116":{"tf":1.0},"118":{"tf":1.0},"119":{"tf":1.0},"152":{"tf":1.0},"21":{"tf":1.4142135623730951},"282":{"tf":1.4142135623730951},"58":{"tf":1.0},"59":{"tf":1.0}},"u":{"df":0,"docs":{},"p":{"df":3,"docs":{"145":{"tf":2.0},"146":{"tf":1.0},"147":{"tf":1.0}}}}}},"k":{"df":1,"docs":{"62":{"tf":1.0}}},"l":{"a":{"df":0,"docs":{},"g":{"df":2,"docs":{"175":{"tf":1.4142135623730951},"64":{"tf":1.0}}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":1,"docs":{"48":{"tf":1.0}}}}},"t":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":1,"docs":{"37":{"tf":1.0}}}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"111":{"tf":1.0}}},"r":{"df":1,"docs":{"83":{"tf":1.0}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"200":{"tf":1.0}}}},"x":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"l":{"df":5,"docs":{"126":{"tf":1.0},"178":{"tf":1.0},"182":{"tf":1.0},"184":{"tf":1.0},"261":{"tf":1.0}}}},"df":0,"docs":{}}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":1,"docs":{"201":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"w":{"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"200":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":29,"docs":{"118":{"tf":1.0},"16":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":1.0},"183":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.0},"21":{"tf":1.0},"22":{"tf":1.0},"23":{"tf":1.0},"24":{"tf":1.0},"244":{"tf":1.7320508075688772},"25":{"tf":1.7320508075688772},"26":{"tf":1.0},"27":{"tf":2.0},"274":{"tf":1.0},"275":{"tf":1.0},"276":{"tf":1.0},"277":{"tf":1.0},"28":{"tf":1.0},"283":{"tf":1.0},"29":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.0},"34":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0}}}}},"m":{"df":0,"docs":{},"l":{"df":1,"docs":{"271":{"tf":1.0}}}},"n":{"df":2,"docs":{"242":{"tf":2.449489742783178},"94":{"tf":1.0}}},"o":{"c":{"df":0,"docs":{},"u":{"df":10,"docs":{"118":{"tf":1.4142135623730951},"230":{"tf":1.0},"255":{"tf":1.0},"257":{"tf":1.0},"32":{"tf":2.23606797749979},"33":{"tf":1.7320508075688772},"34":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":2.449489742783178},"74":{"tf":1.4142135623730951}},"s":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"74":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"u":{"d":{"df":0,"docs":{},"l":{"_":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"74":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"36":{"tf":1.0}}},"df":0,"docs":{}}}}}}}}},"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"34":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"df":1,"docs":{"188":{"tf":1.0}},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"x":{"c":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{".":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":2,"docs":{"33":{"tf":1.0},"34":{"tf":1.4142135623730951}}}}}},"df":1,"docs":{"34":{"tf":1.4142135623730951}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"l":{"df":1,"docs":{"34":{"tf":1.0}}}}}}}},"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"201":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":5,"docs":{"11":{"tf":1.0},"13":{"tf":1.0},"279":{"tf":1.0},"61":{"tf":1.4142135623730951},"62":{"tf":1.0}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":59,"docs":{"108":{"tf":1.0},"109":{"tf":1.4142135623730951},"11":{"tf":1.4142135623730951},"116":{"tf":1.0},"118":{"tf":1.0},"12":{"tf":1.4142135623730951},"122":{"tf":1.0},"123":{"tf":1.4142135623730951},"124":{"tf":1.0},"128":{"tf":1.0},"136":{"tf":1.0},"145":{"tf":1.0},"152":{"tf":1.0},"18":{"tf":1.0},"189":{"tf":1.0},"191":{"tf":1.0},"197":{"tf":1.0},"203":{"tf":1.0},"204":{"tf":1.7320508075688772},"21":{"tf":1.0},"23":{"tf":1.0},"230":{"tf":1.0},"233":{"tf":1.0},"247":{"tf":1.0},"25":{"tf":1.4142135623730951},"251":{"tf":1.0},"252":{"tf":1.0},"253":{"tf":1.4142135623730951},"257":{"tf":1.0},"258":{"tf":1.0},"259":{"tf":1.0},"268":{"tf":2.23606797749979},"27":{"tf":1.4142135623730951},"279":{"tf":1.0},"28":{"tf":1.4142135623730951},"280":{"tf":1.0},"281":{"tf":1.0},"29":{"tf":1.7320508075688772},"30":{"tf":1.0},"31":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.4142135623730951},"34":{"tf":1.7320508075688772},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"43":{"tf":1.0},"45":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.4142135623730951},"67":{"tf":1.0},"69":{"tf":1.4142135623730951},"7":{"tf":1.0},"70":{"tf":1.4142135623730951},"73":{"tf":1.7320508075688772},"74":{"tf":1.0},"93":{"tf":1.4142135623730951}},"u":{"df":0,"docs":{},"p":{"df":1,"docs":{"241":{"tf":1.4142135623730951}}}}}}}},"o":{"'":{"df":1,"docs":{"58":{"tf":1.0}}},".":{"df":0,"docs":{},"r":{"df":1,"docs":{"117":{"tf":1.0}}}},"b":{"a":{"df":0,"docs":{},"r":{".":{"df":0,"docs":{},"k":{"df":0,"docs":{},"t":{"df":1,"docs":{"100":{"tf":1.0}}}}},"df":1,"docs":{"100":{"tf":1.0}}}},"df":0,"docs":{}},"df":3,"docs":{"117":{"tf":1.0},"58":{"tf":1.4142135623730951},"60":{"tf":1.0}}},"r":{"c":{"df":5,"docs":{"184":{"tf":1.4142135623730951},"237":{"tf":1.0},"25":{"tf":1.7320508075688772},"26":{"tf":1.0},"274":{"tf":1.0}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":1,"docs":{"264":{"tf":1.0}}}}}},"df":5,"docs":{"101":{"tf":1.0},"106":{"tf":1.0},"128":{"tf":1.4142135623730951},"69":{"tf":1.0},"82":{"tf":1.0}}}}}},"k":{"df":4,"docs":{"196":{"tf":1.0},"197":{"tf":1.0},"274":{"tf":1.0},"7":{"tf":1.4142135623730951}}},"m":{"a":{"df":0,"docs":{},"t":{"df":11,"docs":{"136":{"tf":1.0},"137":{"tf":1.0},"138":{"tf":1.0},"163":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"22":{"tf":1.4142135623730951},"252":{"tf":1.4142135623730951},"253":{"tf":1.0},"268":{"tf":1.0},"37":{"tf":1.0}},"t":{"df":1,"docs":{"7":{"tf":1.0}}}}},"df":3,"docs":{"165":{"tf":1.0},"232":{"tf":1.0},"242":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"229":{"tf":1.0}}}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"137":{"tf":1.0}}}}}}},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"225":{"tf":1.0}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"j":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"223":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}},"w":{"a":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"d":{"df":43,"docs":{"100":{"tf":1.0},"109":{"tf":1.0},"117":{"tf":1.4142135623730951},"119":{"tf":1.0},"124":{"tf":1.0},"127":{"tf":1.0},"133":{"tf":1.0},"138":{"tf":1.0},"147":{"tf":1.0},"15":{"tf":1.4142135623730951},"159":{"tf":1.0},"175":{"tf":1.0},"185":{"tf":1.0},"201":{"tf":1.0},"203":{"tf":1.0},"218":{"tf":1.0},"22":{"tf":1.4142135623730951},"224":{"tf":1.0},"242":{"tf":1.0},"250":{"tf":1.0},"253":{"tf":1.0},"258":{"tf":1.0},"259":{"tf":1.0},"263":{"tf":1.0},"264":{"tf":1.0},"271":{"tf":1.0},"273":{"tf":1.0},"277":{"tf":1.0},"282":{"tf":1.0},"283":{"tf":1.0},"286":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"40":{"tf":1.0},"51":{"tf":1.0},"56":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0},"67":{"tf":1.0},"74":{"tf":1.0},"8":{"tf":1.0},"90":{"tf":1.0}}},"df":0,"docs":{}},"r":{"df":1,"docs":{"103":{"tf":1.0}}}}},"r":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":1,"docs":{"131":{"tf":1.0}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"y":{"df":1,"docs":{"131":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}}},"df":1,"docs":{"131":{"tf":1.0}}}}}}},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":8,"docs":{"161":{"tf":1.0},"166":{"tf":1.0},"167":{"tf":1.4142135623730951},"175":{"tf":1.0},"252":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.4142135623730951},"77":{"tf":1.7320508075688772}}}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":11,"docs":{"105":{"tf":1.0},"128":{"tf":1.4142135623730951},"225":{"tf":1.0},"233":{"tf":1.0},"247":{"tf":1.0},"25":{"tf":1.0},"3":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"6":{"tf":1.0},"81":{"tf":1.0}}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"60":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"149":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"11":{"tf":1.0}}}}}}},"i":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"274":{"tf":1.0}}}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"j":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"100":{"tf":1.0}}}}}}}}}},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"166":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"169":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":14,"docs":{"105":{"tf":1.0},"122":{"tf":1.0},"149":{"tf":1.0},"154":{"tf":1.0},"217":{"tf":1.0},"221":{"tf":1.7320508075688772},"222":{"tf":1.4142135623730951},"223":{"tf":1.4142135623730951},"224":{"tf":1.4142135623730951},"252":{"tf":1.0},"268":{"tf":2.0},"31":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"7":{"tf":1.0}},"i":{"df":3,"docs":{"21":{"tf":1.0},"226":{"tf":1.0},"274":{"tf":1.0}}}}},"n":{"c":{"df":1,"docs":{"97":{"tf":1.0}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":39,"docs":{"103":{"tf":2.449489742783178},"104":{"tf":1.0},"105":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"111":{"tf":1.0},"129":{"tf":2.0},"140":{"tf":1.0},"142":{"tf":1.4142135623730951},"143":{"tf":1.4142135623730951},"145":{"tf":4.358898943540674},"146":{"tf":1.4142135623730951},"158":{"tf":1.4142135623730951},"199":{"tf":1.0},"200":{"tf":1.0},"221":{"tf":1.0},"222":{"tf":1.0},"232":{"tf":1.0},"234":{"tf":1.4142135623730951},"242":{"tf":1.0},"245":{"tf":2.0},"264":{"tf":1.0},"281":{"tf":2.449489742783178},"283":{"tf":1.0},"42":{"tf":1.4142135623730951},"45":{"tf":1.0},"46":{"tf":1.0},"48":{"tf":1.0},"54":{"tf":1.0},"60":{"tf":1.0},"62":{"tf":2.0},"64":{"tf":1.4142135623730951},"77":{"tf":1.4142135623730951},"78":{"tf":2.23606797749979},"79":{"tf":1.4142135623730951},"81":{"tf":1.0},"82":{"tf":1.7320508075688772},"93":{"tf":1.0},"99":{"tf":1.0}},"s":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"283":{"tf":1.0}}}}}}}}}}}}}}}}}}}},"d":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"61":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"100":{"tf":1.0}}},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"174":{"tf":1.4142135623730951}}}}}}},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":20,"docs":{"107":{"tf":1.0},"126":{"tf":1.0},"140":{"tf":1.0},"152":{"tf":1.0},"167":{"tf":2.0},"190":{"tf":1.0},"196":{"tf":1.0},"198":{"tf":1.4142135623730951},"201":{"tf":1.0},"225":{"tf":1.0},"232":{"tf":1.0},"233":{"tf":1.0},"241":{"tf":1.0},"242":{"tf":1.0},"252":{"tf":1.0},"258":{"tf":2.0},"261":{"tf":1.0},"264":{"tf":1.4142135623730951},"268":{"tf":1.0},"38":{"tf":1.0}}}}},"z":{"df":0,"docs":{},"z":{"df":1,"docs":{"51":{"tf":1.0}}}}},"x":{"a":{"_":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"283":{"tf":1.0}},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"c":{"df":1,"docs":{"283":{"tf":1.0}}},"df":0,"docs":{}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":1,"docs":{"283":{"tf":1.0}}}}}}}}}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"221":{"tf":1.0}}}}}}}},"df":13,"docs":{"105":{"tf":1.0},"107":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"226":{"tf":1.4142135623730951},"231":{"tf":1.7320508075688772},"237":{"tf":1.0},"242":{"tf":1.7320508075688772},"254":{"tf":1.0},"256":{"tf":1.4142135623730951},"257":{"tf":1.0},"283":{"tf":1.0},"58":{"tf":1.4142135623730951}},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"s":{"a":{"d":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"283":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"56":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"g":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"101":{"tf":1.0}}}},"r":{"b":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"128":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"e":{"df":1,"docs":{"62":{"tf":1.0}}}}},"c":{"c":{"df":1,"docs":{"37":{"tf":1.0}}},"df":1,"docs":{"115":{"tf":1.4142135623730951}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":1,"docs":{"132":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}}},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":1,"docs":{"192":{"tf":1.0}}}}}}}}},"df":0,"docs":{},"m":{"df":1,"docs":{"14":{"tf":1.0}}},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":2,"docs":{"30":{"tf":1.0},"35":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":59,"docs":{"101":{"tf":1.0},"104":{"tf":1.7320508075688772},"105":{"tf":1.7320508075688772},"106":{"tf":1.0},"107":{"tf":1.4142135623730951},"108":{"tf":1.0},"109":{"tf":1.7320508075688772},"115":{"tf":1.0},"116":{"tf":1.4142135623730951},"120":{"tf":1.0},"122":{"tf":1.0},"123":{"tf":1.7320508075688772},"125":{"tf":1.0},"132":{"tf":1.0},"134":{"tf":1.0},"14":{"tf":1.0},"142":{"tf":1.0},"167":{"tf":1.0},"194":{"tf":1.0},"201":{"tf":1.4142135623730951},"220":{"tf":1.0},"221":{"tf":1.0},"225":{"tf":1.0},"227":{"tf":1.0},"228":{"tf":1.0},"23":{"tf":1.0},"232":{"tf":1.4142135623730951},"242":{"tf":1.7320508075688772},"243":{"tf":1.0},"244":{"tf":1.7320508075688772},"245":{"tf":1.0},"247":{"tf":1.0},"253":{"tf":1.7320508075688772},"254":{"tf":1.4142135623730951},"266":{"tf":1.0},"267":{"tf":1.0},"268":{"tf":1.0},"271":{"tf":1.0},"274":{"tf":1.0},"277":{"tf":1.0},"29":{"tf":1.0},"30":{"tf":1.4142135623730951},"34":{"tf":1.0},"35":{"tf":1.4142135623730951},"44":{"tf":1.0},"45":{"tf":1.0},"51":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.7320508075688772},"69":{"tf":1.0},"70":{"tf":1.0},"72":{"tf":2.23606797749979},"73":{"tf":2.23606797749979},"74":{"tf":1.0},"75":{"tf":1.0},"76":{"tf":1.4142135623730951},"82":{"tf":1.0},"83":{"tf":1.4142135623730951},"90":{"tf":1.0}}}}},"t":{"_":{"a":{"c":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"283":{"tf":1.0}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"df":1,"docs":{"145":{"tf":1.7320508075688772}}}},"b":{"df":0,"docs":{},"y":{"_":{"b":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"_":{"d":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"145":{"tf":1.7320508075688772}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"145":{"tf":1.7320508075688772}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"283":{"tf":1.0}}}}}}}},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"_":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"c":{"df":1,"docs":{"87":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":12,"docs":{"105":{"tf":1.0},"116":{"tf":1.0},"17":{"tf":1.0},"227":{"tf":1.0},"236":{"tf":1.0},"245":{"tf":1.0},"253":{"tf":1.0},"271":{"tf":1.4142135623730951},"282":{"tf":1.0},"60":{"tf":1.0},"62":{"tf":1.0},"82":{"tf":1.0}}}},"h":{"df":1,"docs":{"286":{"tf":1.0}}},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"13":{"tf":1.0}}}},"t":{"df":9,"docs":{"11":{"tf":1.7320508075688772},"123":{"tf":1.0},"167":{"tf":2.0},"253":{"tf":1.4142135623730951},"26":{"tf":1.0},"268":{"tf":2.23606797749979},"269":{"tf":1.0},"271":{"tf":1.0},"34":{"tf":1.0}},"h":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"274":{"tf":1.0}}},"df":0,"docs":{}},"u":{"b":{"'":{"df":3,"docs":{"274":{"tf":1.0},"277":{"tf":1.0},"7":{"tf":1.0}}},"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"277":{"tf":1.0}}}}}}}},"df":52,"docs":{"1":{"tf":1.4142135623730951},"100":{"tf":1.0},"109":{"tf":1.0},"117":{"tf":1.0},"119":{"tf":1.4142135623730951},"124":{"tf":1.0},"127":{"tf":1.0},"133":{"tf":1.0},"138":{"tf":1.0},"147":{"tf":1.0},"15":{"tf":1.0},"159":{"tf":1.0},"175":{"tf":1.0},"185":{"tf":1.0},"193":{"tf":1.0},"201":{"tf":1.0},"218":{"tf":1.0},"22":{"tf":1.0},"224":{"tf":1.0},"242":{"tf":1.0},"250":{"tf":1.0},"253":{"tf":1.4142135623730951},"258":{"tf":1.4142135623730951},"259":{"tf":1.0},"263":{"tf":1.0},"264":{"tf":1.0},"268":{"tf":1.0},"272":{"tf":1.4142135623730951},"273":{"tf":1.0},"274":{"tf":2.23606797749979},"276":{"tf":1.7320508075688772},"277":{"tf":1.4142135623730951},"282":{"tf":1.0},"283":{"tf":1.0},"286":{"tf":1.0},"3":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.4142135623730951},"39":{"tf":1.0},"4":{"tf":1.0},"40":{"tf":1.0},"50":{"tf":1.0},"51":{"tf":1.0},"56":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0},"67":{"tf":1.0},"7":{"tf":1.0},"74":{"tf":1.0},"8":{"tf":1.0},"90":{"tf":1.0}}},"df":0,"docs":{}}}},"v":{"df":0,"docs":{},"e":{"df":2,"docs":{"174":{"tf":1.0},"41":{"tf":1.0}},"n":{"df":14,"docs":{"115":{"tf":1.0},"156":{"tf":1.0},"167":{"tf":1.0},"172":{"tf":1.0},"197":{"tf":1.0},"205":{"tf":1.0},"208":{"tf":1.0},"225":{"tf":1.0},"260":{"tf":1.0},"276":{"tf":1.0},"43":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"64":{"tf":1.0}}}}}},"l":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"n":{"df":11,"docs":{"162":{"tf":1.0},"167":{"tf":2.0},"169":{"tf":1.4142135623730951},"170":{"tf":1.4142135623730951},"171":{"tf":1.4142135623730951},"172":{"tf":1.0},"174":{"tf":1.4142135623730951},"200":{"tf":1.0},"219":{"tf":1.0},"253":{"tf":1.0},"259":{"tf":1.7320508075688772}}}},"df":0,"docs":{}},"o":{"b":{"a":{"df":0,"docs":{},"l":{"df":3,"docs":{"115":{"tf":1.0},"225":{"tf":1.0},"86":{"tf":2.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"o":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"103":{"tf":1.0},"23":{"tf":1.0}}}},"df":14,"docs":{"105":{"tf":1.0},"115":{"tf":1.0},"125":{"tf":1.0},"126":{"tf":1.0},"226":{"tf":1.0},"229":{"tf":1.0},"233":{"tf":1.0},"25":{"tf":1.4142135623730951},"26":{"tf":1.0},"37":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0},"73":{"tf":1.0},"81":{"tf":1.7320508075688772}},"e":{"df":5,"docs":{"103":{"tf":1.4142135623730951},"18":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"62":{"tf":1.4142135623730951}}},"n":{"df":0,"docs":{},"e":{"df":1,"docs":{"158":{"tf":1.0}}}},"o":{"d":{"df":29,"docs":{"107":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"132":{"tf":1.0},"150":{"tf":1.0},"154":{"tf":1.7320508075688772},"155":{"tf":2.0},"156":{"tf":1.7320508075688772},"157":{"tf":1.0},"158":{"tf":1.4142135623730951},"159":{"tf":1.4142135623730951},"169":{"tf":1.0},"170":{"tf":1.7320508075688772},"171":{"tf":2.0},"172":{"tf":2.8284271247461903},"174":{"tf":1.0},"182":{"tf":1.7320508075688772},"183":{"tf":1.4142135623730951},"184":{"tf":1.7320508075688772},"19":{"tf":1.0},"194":{"tf":1.0},"212":{"tf":1.0},"213":{"tf":1.4142135623730951},"214":{"tf":1.0},"225":{"tf":1.0},"37":{"tf":1.0},"6":{"tf":2.0},"82":{"tf":1.0},"90":{"tf":1.0}}},"df":0,"docs":{}},"t":{"c":{"df":0,"docs":{},"h":{"a":{"df":1,"docs":{"224":{"tf":1.4142135623730951}}},"df":0,"docs":{},"y":{"a":{"df":1,"docs":{"228":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":1,"docs":{"3":{"tf":1.0}}}}}}},"p":{"df":0,"docs":{},"g":{"df":1,"docs":{"7":{"tf":1.0}}}},"r":{"a":{"d":{"df":0,"docs":{},"l":{"df":8,"docs":{"22":{"tf":1.4142135623730951},"220":{"tf":1.0},"274":{"tf":1.7320508075688772},"37":{"tf":1.4142135623730951},"40":{"tf":1.0},"49":{"tf":1.0},"65":{"tf":1.0},"70":{"tf":1.4142135623730951}},"e":{"'":{"df":3,"docs":{"22":{"tf":1.0},"221":{"tf":1.0},"222":{"tf":1.0}}},".":{"\"":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{".":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"22":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"(":{"'":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{".":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"22":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"w":{"df":7,"docs":{"108":{"tf":1.4142135623730951},"13":{"tf":1.0},"18":{"tf":1.4142135623730951},"20":{"tf":1.0},"22":{"tf":1.4142135623730951},"45":{"tf":1.0},"70":{"tf":1.4142135623730951}}}}},"u":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"101":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":3,"docs":{"221":{"tf":1.7320508075688772},"58":{"tf":1.4142135623730951},"59":{"tf":1.0}},"i":{"c":{"df":1,"docs":{"126":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":3,"docs":{"282":{"tf":1.0},"52":{"tf":1.0},"75":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"210":{"tf":1.0}}}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"a":{"df":1,"docs":{"237":{"tf":1.0}}},"df":0,"docs":{}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":4,"docs":{"189":{"tf":1.0},"37":{"tf":1.0},"40":{"tf":1.0},"73":{"tf":2.0}}}},"w":{"df":2,"docs":{"199":{"tf":1.0},"208":{"tf":1.0}},"n":{"df":1,"docs":{"64":{"tf":1.0}}}}}},"t":{"df":1,"docs":{"200":{"tf":2.8284271247461903}}},"u":{"a":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":1,"docs":{"201":{"tf":1.0}}}}}},"d":{"df":2,"docs":{"274":{"tf":1.0},"64":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"i":{"d":{"a":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"75":{"tf":1.0}}},"df":0,"docs":{}}},"df":19,"docs":{"101":{"tf":1.0},"102":{"tf":1.4142135623730951},"11":{"tf":1.0},"23":{"tf":1.7320508075688772},"231":{"tf":1.0},"245":{"tf":1.4142135623730951},"248":{"tf":1.7320508075688772},"278":{"tf":1.7320508075688772},"32":{"tf":1.7320508075688772},"37":{"tf":1.0},"41":{"tf":1.4142135623730951},"62":{"tf":3.3166247903554},"69":{"tf":1.0},"7":{"tf":1.4142135623730951},"75":{"tf":2.0},"86":{"tf":1.0},"87":{"tf":2.0},"93":{"tf":1.0},"99":{"tf":1.0}},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":10,"docs":{"128":{"tf":1.0},"274":{"tf":1.4142135623730951},"3":{"tf":1.0},"43":{"tf":1.0},"45":{"tf":1.0},"63":{"tf":1.7320508075688772},"69":{"tf":1.0},"7":{"tf":1.0},"91":{"tf":1.0},"96":{"tf":1.0}}}}}}},"df":1,"docs":{"109":{"tf":1.0}}}},"y":{"df":0,"docs":{},"p":{"df":2,"docs":{"11":{"tf":1.7320508075688772},"282":{"tf":1.4142135623730951}}}}},"h":{"a":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"45":{"tf":1.0}}}}},"i":{"df":2,"docs":{"107":{"tf":1.0},"37":{"tf":1.0}}},"y":{"_":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"_":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"(":{"\"":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"37":{"tf":1.0}}}},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":1,"docs":{"37":{"tf":1.0}}}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"s":{"df":0,"docs":{},"g":{"df":1,"docs":{"37":{"tf":1.0}}}}}},"df":1,"docs":{"37":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{},"n":{"d":{"df":20,"docs":{"101":{"tf":1.0},"103":{"tf":2.8284271247461903},"104":{"tf":2.0},"106":{"tf":1.0},"108":{"tf":2.23606797749979},"109":{"tf":2.23606797749979},"111":{"tf":1.0},"116":{"tf":1.0},"224":{"tf":1.0},"236":{"tf":1.0},"249":{"tf":1.0},"252":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"70":{"tf":1.4142135623730951},"72":{"tf":1.4142135623730951},"73":{"tf":1.4142135623730951},"74":{"tf":1.0},"78":{"tf":1.0},"90":{"tf":1.0}},"l":{"df":16,"docs":{"128":{"tf":1.0},"130":{"tf":1.0},"133":{"tf":1.0},"134":{"tf":1.0},"146":{"tf":1.0},"148":{"tf":1.4142135623730951},"228":{"tf":1.0},"233":{"tf":1.0},"243":{"tf":1.0},"244":{"tf":1.0},"245":{"tf":1.0},"272":{"tf":1.0},"40":{"tf":1.0},"63":{"tf":1.0},"79":{"tf":1.0},"86":{"tf":1.0}}}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":11,"docs":{"106":{"tf":1.0},"133":{"tf":1.7320508075688772},"154":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0},"225":{"tf":1.0},"230":{"tf":1.0},"242":{"tf":1.0},"26":{"tf":1.0},"273":{"tf":1.0},"283":{"tf":1.0}}}},"i":{"df":2,"docs":{"124":{"tf":1.0},"263":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"170":{"tf":1.0}}}}}}},"r":{"d":{"df":7,"docs":{"104":{"tf":1.0},"158":{"tf":1.0},"189":{"tf":1.0},"200":{"tf":1.0},"213":{"tf":1.0},"233":{"tf":1.0},"241":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"166":{"tf":1.0}}}},"w":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"282":{"tf":1.0}}}},"df":0,"docs":{}}},"df":2,"docs":{"48":{"tf":1.0},"60":{"tf":1.4142135623730951}},"m":{"df":2,"docs":{"201":{"tf":1.0},"225":{"tf":1.0}}}},"s":{"df":0,"docs":{},"h":{"df":3,"docs":{"268":{"tf":1.0},"274":{"tf":1.0},"64":{"tf":1.0}},"m":{"a":{"df":0,"docs":{},"p":{"<":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"242":{"tf":1.0}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"n":{"'":{"df":0,"docs":{},"t":{"df":2,"docs":{"154":{"tf":1.0},"155":{"tf":1.0}}}},"df":0,"docs":{},"’":{"df":0,"docs":{},"t":{"df":1,"docs":{"194":{"tf":1.0}}}}}},"v":{"df":0,"docs":{},"e":{"df":12,"docs":{"105":{"tf":1.0},"126":{"tf":1.0},"146":{"tf":1.0},"166":{"tf":1.0},"174":{"tf":1.0},"183":{"tf":1.0},"215":{"tf":1.0},"256":{"tf":1.0},"46":{"tf":1.0},"60":{"tf":1.0},"82":{"tf":1.0},"86":{"tf":1.0}},"n":{"'":{"df":0,"docs":{},"t":{"df":4,"docs":{"20":{"tf":1.0},"274":{"tf":1.0},"36":{"tf":1.0},"60":{"tf":1.0}}}},"df":0,"docs":{},"’":{"df":0,"docs":{},"t":{"df":1,"docs":{"213":{"tf":1.0}}}}}}}},"df":0,"docs":{},"e":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":9,"docs":{"109":{"tf":1.0},"251":{"tf":1.0},"252":{"tf":1.4142135623730951},"268":{"tf":1.0},"269":{"tf":1.0},"274":{"tf":1.0},"28":{"tf":1.0},"33":{"tf":1.0},"73":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"242":{"tf":1.0}}}}},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"105":{"tf":1.0}}}}},"y":{"df":0,"docs":{},"w":{"df":0,"docs":{},"e":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":1,"docs":{"59":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"p":{"df":17,"docs":{"106":{"tf":1.0},"115":{"tf":1.0},"122":{"tf":1.0},"165":{"tf":1.0},"190":{"tf":1.0},"222":{"tf":1.0},"232":{"tf":1.0},"264":{"tf":1.0},"274":{"tf":1.4142135623730951},"278":{"tf":1.0},"282":{"tf":1.0},"3":{"tf":1.0},"37":{"tf":1.0},"59":{"tf":1.0},"62":{"tf":1.0},"75":{"tf":1.0},"90":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"222":{"tf":1.0},"43":{"tf":1.0}}}}}},"n":{"c":{"df":2,"docs":{"200":{"tf":1.0},"278":{"tf":1.0}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"e":{"'":{"df":1,"docs":{"37":{"tf":1.0}}},"df":30,"docs":{"103":{"tf":2.449489742783178},"108":{"tf":1.0},"109":{"tf":1.0},"117":{"tf":1.0},"123":{"tf":1.0},"171":{"tf":1.0},"20":{"tf":1.0},"200":{"tf":2.0},"201":{"tf":1.0},"210":{"tf":1.0},"225":{"tf":1.0},"229":{"tf":1.4142135623730951},"242":{"tf":1.0},"243":{"tf":1.0},"249":{"tf":1.0},"258":{"tf":1.0},"264":{"tf":1.0},"266":{"tf":1.4142135623730951},"274":{"tf":1.7320508075688772},"37":{"tf":1.0},"40":{"tf":1.0},"60":{"tf":1.4142135623730951},"69":{"tf":1.0},"70":{"tf":1.7320508075688772},"72":{"tf":1.4142135623730951},"76":{"tf":1.0},"81":{"tf":1.0},"82":{"tf":1.0},"84":{"tf":1.0},"90":{"tf":1.0}}}}},"i":{"d":{"df":0,"docs":{},"e":{"df":2,"docs":{"233":{"tf":1.0},"245":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":1,"docs":{"83":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"g":{"df":0,"docs":{},"h":{"df":26,"docs":{"103":{"tf":1.4142135623730951},"200":{"tf":1.0},"201":{"tf":1.0},"205":{"tf":1.0},"214":{"tf":1.0},"219":{"tf":1.7320508075688772},"22":{"tf":1.0},"225":{"tf":2.23606797749979},"226":{"tf":2.23606797749979},"229":{"tf":2.0},"23":{"tf":1.0},"230":{"tf":1.0},"232":{"tf":1.0},"233":{"tf":1.0},"236":{"tf":2.0},"237":{"tf":1.0},"239":{"tf":1.0},"243":{"tf":1.0},"244":{"tf":1.0},"245":{"tf":1.0},"251":{"tf":1.7320508075688772},"254":{"tf":2.0},"256":{"tf":1.0},"281":{"tf":1.0},"41":{"tf":1.0},"64":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"103":{"tf":1.4142135623730951},"209":{"tf":1.0}}}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":2,"docs":{"109":{"tf":1.0},"251":{"tf":1.0}}}}}}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"174":{"tf":1.0}},"i":{"df":22,"docs":{"134":{"tf":1.0},"202":{"tf":1.4142135623730951},"203":{"tf":1.4142135623730951},"204":{"tf":1.4142135623730951},"205":{"tf":2.8284271247461903},"206":{"tf":2.449489742783178},"207":{"tf":1.0},"208":{"tf":2.449489742783178},"209":{"tf":2.23606797749979},"210":{"tf":1.7320508075688772},"212":{"tf":2.0},"213":{"tf":2.0},"215":{"tf":1.7320508075688772},"217":{"tf":1.0},"218":{"tf":2.0},"232":{"tf":2.0},"233":{"tf":2.0},"242":{"tf":2.23606797749979},"257":{"tf":1.0},"264":{"tf":1.0},"283":{"tf":1.0},"83":{"tf":1.0}}}}}}},"t":{"df":3,"docs":{"189":{"tf":1.0},"201":{"tf":1.4142135623730951},"60":{"tf":1.0}}}},"o":{"c":{"df":1,"docs":{"59":{"tf":1.0}}},"df":0,"docs":{},"l":{"d":{"df":3,"docs":{"115":{"tf":1.0},"132":{"tf":1.0},"245":{"tf":1.0}}},"df":0,"docs":{}},"m":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":1,"docs":{"11":{"tf":1.7320508075688772}}}}}},"df":1,"docs":{"64":{"tf":1.0}}}},"o":{"df":0,"docs":{},"k":{"df":1,"docs":{"119":{"tf":1.0}}}},"p":{"df":0,"docs":{},"e":{"df":1,"docs":{"225":{"tf":1.4142135623730951}},"f":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"242":{"tf":1.0}}}}}}}}},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"190":{"tf":1.0}}}},"u":{"df":0,"docs":{},"r":{"df":2,"docs":{"10":{"tf":1.0},"64":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"=":{"\"":{".":{".":{"/":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"c":{".":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"/":{"d":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"283":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":1,"docs":{"283":{"tf":1.0}}}}}}},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"283":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":1,"docs":{"284":{"tf":1.0}}}},"t":{"df":0,"docs":{},"p":{"df":4,"docs":{"189":{"tf":1.0},"190":{"tf":1.4142135623730951},"201":{"tf":1.4142135623730951},"274":{"tf":1.0}},"s":{":":{"/":{"/":{"a":{"d":{"df":0,"docs":{},"r":{".":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"u":{"b":{".":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"/":{"df":0,"docs":{},"m":{"a":{"d":{"df":0,"docs":{},"r":{"df":1,"docs":{"134":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":1,"docs":{"134":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{".":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"i":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"/":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"/":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"u":{"b":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"/":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"277":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"a":{".":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"/":{"df":0,"docs":{},"e":{"df":0,"docs":{},"z":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"11":{"tf":1.0}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{".":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"e":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"/":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"l":{"/":{"df":0,"docs":{},"g":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{".":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"11":{"tf":1.0}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}}}},"df":0,"docs":{}}}}}}}}},"d":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"/":{"d":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"/":{"d":{"/":{"1":{"df":0,"docs":{},"q":{"df":0,"docs":{},"w":{"3":{"6":{"_":{"7":{"df":0,"docs":{},"g":{"6":{"df":0,"docs":{},"x":{"df":0,"docs":{},"y":{"df":0,"docs":{},"h":{"df":0,"docs":{},"v":{"df":0,"docs":{},"j":{"df":0,"docs":{},"z":{"d":{"df":0,"docs":{},"m":{"df":1,"docs":{"177":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"x":{"1":{"df":0,"docs":{},"h":{"c":{"3":{"df":0,"docs":{},"t":{"5":{"df":0,"docs":{},"z":{"c":{"7":{"df":1,"docs":{"185":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"x":{"df":3,"docs":{"119":{"tf":1.0},"266":{"tf":1.4142135623730951},"271":{"tf":1.0}}}}}}}}},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"u":{"b":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"/":{"df":0,"docs":{},"j":{"a":{"df":0,"docs":{},"k":{"df":0,"docs":{},"u":{"b":{"a":{"d":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"w":{"/":{"a":{"df":0,"docs":{},"r":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"51":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"/":{"a":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"_":{"d":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"137":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}}}},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"/":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":2,"docs":{"11":{"tf":1.0},"186":{"tf":1.0}}}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":5,"docs":{"119":{"tf":1.0},"253":{"tf":1.0},"31":{"tf":1.0},"34":{"tf":1.0},"36":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"l":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"i":{"d":{".":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"/":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"i":{"d":{"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"/":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"/":{"a":{"c":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"/":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"v":{"_":{"a":{"b":{"7":{"2":{"df":0,"docs":{},"e":{"2":{"1":{"8":{"df":1,"docs":{"258":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"m":{"a":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{".":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{".":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"/":{"?":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"=":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"2":{"/":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"/":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"271":{"tf":1.0}},"e":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"271":{"tf":1.0}}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":1,"docs":{"271":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"/":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"/":{"b":{"df":0,"docs":{},"o":{"a":{"df":0,"docs":{},"r":{"d":{"/":{"df":0,"docs":{},"o":{"9":{"df":0,"docs":{},"j":{"_":{"df":0,"docs":{},"l":{"df":0,"docs":{},"w":{"df":0,"docs":{},"x":{"3":{"df":0,"docs":{},"j":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":1,"docs":{"274":{"tf":1.0}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{".":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"d":{".":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"/":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"s":{"/":{"1":{"0":{"7":{"8":{"df":1,"docs":{"215":{"tf":1.0}}},"df":0,"docs":{}},"8":{"1":{"df":1,"docs":{"216":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"/":{"2":{".":{"0":{"df":1,"docs":{"2":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"df":1,"docs":{"176":{"tf":1.0}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"u":{"b":{".":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"n":{".":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"/":{"b":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"/":{"df":0,"docs":{},"s":{"d":{"df":0,"docs":{},"k":{"df":1,"docs":{"176":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"x":{"df":0,"docs":{},"h":{"4":{"df":0,"docs":{},"n":{"df":0,"docs":{},"q":{"df":0,"docs":{},"y":{"df":0,"docs":{},"z":{"df":0,"docs":{},"y":{"c":{"df":0,"docs":{},"s":{"df":0,"docs":{},"y":{"a":{"df":0,"docs":{},"j":{"df":0,"docs":{},"g":{"0":{"df":0,"docs":{},"l":{"5":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"3":{"3":{"df":0,"docs":{},"y":{"d":{"5":{"df":0,"docs":{},"m":{"/":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"177":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"69":{"tf":1.0}}}}}}}},"i":{"'":{"df":0,"docs":{},"m":{"df":1,"docs":{"200":{"tf":1.0}}}},".":{"df":1,"docs":{"253":{"tf":1.0}}},"/":{"df":0,"docs":{},"o":{"df":2,"docs":{"115":{"tf":1.0},"179":{"tf":1.4142135623730951}}}},"d":{"df":7,"docs":{"231":{"tf":2.449489742783178},"242":{"tf":1.4142135623730951},"271":{"tf":1.0},"62":{"tf":1.0},"80":{"tf":1.0},"86":{"tf":3.0},"87":{"tf":1.0}},"e":{"a":{"df":2,"docs":{"51":{"tf":1.4142135623730951},"87":{"tf":1.0}},"l":{"df":7,"docs":{"119":{"tf":1.0},"122":{"tf":1.0},"206":{"tf":1.0},"208":{"tf":1.0},"229":{"tf":1.0},"49":{"tf":1.0},"58":{"tf":1.0}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":3,"docs":{"178":{"tf":1.0},"182":{"tf":1.0},"184":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{",":{"a":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"180":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"n":{"df":0,"docs":{},"t":{"df":4,"docs":{"18":{"tf":1.4142135623730951},"249":{"tf":1.0},"283":{"tf":1.0},"64":{"tf":1.0}},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":15,"docs":{"104":{"tf":1.0},"141":{"tf":1.0},"149":{"tf":1.4142135623730951},"151":{"tf":1.4142135623730951},"152":{"tf":1.0},"187":{"tf":1.7320508075688772},"188":{"tf":1.0},"191":{"tf":1.0},"200":{"tf":2.23606797749979},"214":{"tf":1.0},"22":{"tf":1.0},"225":{"tf":1.7320508075688772},"232":{"tf":1.0},"241":{"tf":1.4142135623730951},"86":{"tf":1.0}}}}}}}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"t":{"df":2,"docs":{"103":{"tf":1.4142135623730951},"43":{"tf":1.0}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{",":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"75":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":13,"docs":{"189":{"tf":1.0},"193":{"tf":1.0},"194":{"tf":1.0},"200":{"tf":1.7320508075688772},"201":{"tf":1.7320508075688772},"232":{"tf":1.0},"233":{"tf":1.7320508075688772},"237":{"tf":1.0},"241":{"tf":1.0},"242":{"tf":1.4142135623730951},"245":{"tf":1.0},"87":{"tf":1.0},"88":{"tf":1.0}}},"f":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":1,"docs":{"37":{"tf":1.0}}}}},"df":0,"docs":{}},"g":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":5,"docs":{"200":{"tf":1.0},"226":{"tf":1.4142135623730951},"233":{"tf":1.0},"241":{"tf":1.0},"61":{"tf":1.0}}}}}},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"132":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"m":{"a":{"df":0,"docs":{},"g":{"df":2,"docs":{"194":{"tf":1.0},"274":{"tf":1.0}},"i":{"df":0,"docs":{},"n":{"df":2,"docs":{"225":{"tf":1.0},"232":{"tf":1.0}}}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"i":{"df":2,"docs":{"189":{"tf":1.0},"81":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"p":{"a":{"c":{"df":0,"docs":{},"t":{"df":4,"docs":{"167":{"tf":1.0},"178":{"tf":1.0},"200":{"tf":1.0},"229":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"df":2,"docs":{"242":{"tf":1.7320508075688772},"62":{"tf":1.0}},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":43,"docs":{"103":{"tf":2.0},"105":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"115":{"tf":1.0},"125":{"tf":1.0},"128":{"tf":1.0},"140":{"tf":1.0},"141":{"tf":1.0},"146":{"tf":1.4142135623730951},"152":{"tf":1.4142135623730951},"159":{"tf":1.0},"167":{"tf":1.4142135623730951},"200":{"tf":1.7320508075688772},"203":{"tf":1.7320508075688772},"208":{"tf":1.0},"218":{"tf":1.0},"225":{"tf":2.0},"226":{"tf":2.6457513110645907},"227":{"tf":2.23606797749979},"228":{"tf":1.7320508075688772},"230":{"tf":2.23606797749979},"231":{"tf":1.4142135623730951},"232":{"tf":1.4142135623730951},"233":{"tf":1.0},"234":{"tf":1.0},"239":{"tf":1.0},"241":{"tf":2.0},"244":{"tf":1.0},"247":{"tf":1.0},"254":{"tf":1.0},"256":{"tf":1.7320508075688772},"257":{"tf":1.7320508075688772},"258":{"tf":2.449489742783178},"264":{"tf":1.0},"274":{"tf":1.0},"282":{"tf":1.0},"37":{"tf":1.4142135623730951},"43":{"tf":1.0},"45":{"tf":1.0},"48":{"tf":1.0},"82":{"tf":1.0},"84":{"tf":1.4142135623730951}}}}}}},"i":{"c":{"df":3,"docs":{"197":{"tf":1.0},"205":{"tf":1.4142135623730951},"263":{"tf":1.4142135623730951}},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"138":{"tf":1.0}}}}},"df":2,"docs":{"123":{"tf":1.4142135623730951},"245":{"tf":1.0}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"m":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"145":{"tf":2.0}}}}}}}}}},"df":30,"docs":{"103":{"tf":1.4142135623730951},"105":{"tf":1.7320508075688772},"138":{"tf":1.0},"158":{"tf":1.0},"16":{"tf":1.0},"165":{"tf":1.0},"166":{"tf":1.4142135623730951},"167":{"tf":1.0},"20":{"tf":1.0},"205":{"tf":1.0},"206":{"tf":1.0},"210":{"tf":1.0},"213":{"tf":1.0},"214":{"tf":1.0},"218":{"tf":1.0},"242":{"tf":1.0},"251":{"tf":1.0},"253":{"tf":1.0},"258":{"tf":1.0},"260":{"tf":1.0},"27":{"tf":1.0},"37":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"49":{"tf":1.0},"6":{"tf":1.4142135623730951},"60":{"tf":1.0},"73":{"tf":1.7320508075688772},"87":{"tf":1.7320508075688772},"9":{"tf":1.0}}}},"s":{"df":2,"docs":{"206":{"tf":1.0},"210":{"tf":1.0}},"s":{"df":2,"docs":{"201":{"tf":1.0},"43":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":10,"docs":{"111":{"tf":1.0},"145":{"tf":1.0},"146":{"tf":1.0},"150":{"tf":1.0},"171":{"tf":1.4142135623730951},"206":{"tf":1.0},"208":{"tf":1.0},"264":{"tf":1.0},"282":{"tf":1.0},"51":{"tf":1.4142135623730951}}}}}}},"n":{"c":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":1,"docs":{"137":{"tf":1.0}}}}},"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"205":{"tf":1.7320508075688772},"218":{"tf":1.0}}},"df":0,"docs":{}},"l":{"df":0,"docs":{},"u":{"d":{"df":39,"docs":{"105":{"tf":1.0},"113":{"tf":1.0},"118":{"tf":1.0},"124":{"tf":1.0},"167":{"tf":2.449489742783178},"169":{"tf":1.0},"172":{"tf":1.0},"18":{"tf":1.0},"190":{"tf":1.0},"193":{"tf":1.0},"200":{"tf":1.7320508075688772},"204":{"tf":1.0},"222":{"tf":1.0},"224":{"tf":1.0},"226":{"tf":1.4142135623730951},"23":{"tf":1.4142135623730951},"231":{"tf":1.0},"251":{"tf":1.7320508075688772},"253":{"tf":1.4142135623730951},"274":{"tf":1.4142135623730951},"283":{"tf":1.0},"29":{"tf":1.7320508075688772},"31":{"tf":2.449489742783178},"32":{"tf":1.4142135623730951},"34":{"tf":1.7320508075688772},"36":{"tf":2.23606797749979},"37":{"tf":1.0},"4":{"tf":1.0},"58":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.0},"69":{"tf":1.4142135623730951},"7":{"tf":2.449489742783178},"70":{"tf":1.4142135623730951},"72":{"tf":1.0},"73":{"tf":1.7320508075688772},"74":{"tf":1.0},"78":{"tf":1.0},"99":{"tf":1.0}},"e":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"81":{"tf":1.0}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"m":{"df":5,"docs":{"246":{"tf":1.0},"248":{"tf":1.0},"249":{"tf":1.4142135623730951},"283":{"tf":1.0},"88":{"tf":1.0}},"p":{"a":{"df":0,"docs":{},"t":{"df":5,"docs":{"169":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"268":{"tf":1.0}}}},"df":0,"docs":{}}},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"170":{"tf":1.0}}}}}}}},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":3,"docs":{"205":{"tf":1.0},"208":{"tf":1.0},"41":{"tf":1.0}}}},"d":{"df":1,"docs":{"22":{"tf":1.0}},"u":{"df":0,"docs":{},"l":{"df":1,"docs":{"60":{"tf":1.0}}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"190":{"tf":1.0},"250":{"tf":1.0}}}}}}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"166":{"tf":1.0},"170":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"201":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"x":{"df":5,"docs":{"268":{"tf":1.0},"269":{"tf":1.0},"271":{"tf":1.0},"40":{"tf":1.0},"62":{"tf":1.0}}}},"i":{"c":{"df":7,"docs":{"154":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0},"253":{"tf":1.0},"268":{"tf":1.0},"40":{"tf":1.0},"7":{"tf":1.0}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"200":{"tf":1.0}}}}}},"df":0,"docs":{}}},"v":{"df":0,"docs":{},"i":{"d":{"df":0,"docs":{},"u":{"df":9,"docs":{"124":{"tf":1.0},"187":{"tf":1.0},"221":{"tf":1.4142135623730951},"225":{"tf":1.0},"228":{"tf":1.0},"244":{"tf":1.4142135623730951},"245":{"tf":1.4142135623730951},"274":{"tf":1.0},"283":{"tf":1.0}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"196":{"tf":1.0},"263":{"tf":1.0}}}}}},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"82":{"tf":1.0}}}}}},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"274":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}},"o":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"252":{"tf":1.7320508075688772}}}}}}}},"/":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"226":{"tf":1.4142135623730951},"229":{"tf":1.0},"241":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":1,"docs":{"242":{"tf":1.0}},"r":{"df":0,"docs":{},"m":{"df":17,"docs":{"13":{"tf":1.0},"134":{"tf":1.4142135623730951},"177":{"tf":1.0},"226":{"tf":1.0},"23":{"tf":1.0},"241":{"tf":1.0},"242":{"tf":1.0},"263":{"tf":1.4142135623730951},"268":{"tf":1.0},"274":{"tf":1.0},"283":{"tf":1.0},"32":{"tf":1.0},"36":{"tf":1.0},"4":{"tf":1.0},"7":{"tf":1.0},"73":{"tf":1.0},"75":{"tf":1.0}}}}},"r":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":2,"docs":{"167":{"tf":1.0},"48":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"233":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"t":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"242":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"11":{"tf":1.0}},"i":{"a":{"df":0,"docs":{},"l":{"_":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"j":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"179":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}}}}}}}}},"df":0,"docs":{}}},"df":16,"docs":{"10":{"tf":1.0},"132":{"tf":1.0},"164":{"tf":1.0},"167":{"tf":1.7320508075688772},"172":{"tf":1.0},"174":{"tf":1.0},"184":{"tf":1.0},"189":{"tf":1.0},"190":{"tf":1.7320508075688772},"199":{"tf":1.0},"200":{"tf":1.4142135623730951},"201":{"tf":1.4142135623730951},"231":{"tf":1.0},"238":{"tf":1.0},"242":{"tf":1.4142135623730951},"244":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":3,"docs":{"214":{"tf":1.0},"233":{"tf":1.0},"254":{"tf":1.0}}}}},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"104":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":2,"docs":{"59":{"tf":1.0},"62":{"tf":1.0}}}}},"i":{"d":{"df":7,"docs":{"12":{"tf":1.0},"20":{"tf":2.23606797749979},"263":{"tf":1.0},"30":{"tf":1.0},"34":{"tf":1.0},"35":{"tf":1.0},"61":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"108":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"245":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"a":{"df":0,"docs":{},"l":{"df":14,"docs":{"104":{"tf":1.0},"11":{"tf":6.48074069840786},"12":{"tf":2.0},"13":{"tf":1.7320508075688772},"14":{"tf":1.4142135623730951},"16":{"tf":1.0},"18":{"tf":1.0},"201":{"tf":1.4142135623730951},"269":{"tf":1.4142135623730951},"279":{"tf":1.0},"286":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":2.0},"53":{"tf":1.4142135623730951}}},"n":{"c":{"df":6,"docs":{"244":{"tf":1.0},"245":{"tf":1.4142135623730951},"246":{"tf":1.0},"247":{"tf":1.0},"249":{"tf":1.0},"78":{"tf":1.0}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":2,"docs":{"115":{"tf":1.0},"245":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"a":{"d":{"df":14,"docs":{"158":{"tf":1.0},"172":{"tf":1.0},"18":{"tf":1.0},"188":{"tf":1.0},"194":{"tf":1.4142135623730951},"231":{"tf":1.0},"242":{"tf":1.0},"37":{"tf":1.0},"58":{"tf":1.0},"61":{"tf":1.7320508075688772},"64":{"tf":1.0},"78":{"tf":1.0},"79":{"tf":1.0},"84":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":10,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"11":{"tf":2.0},"12":{"tf":1.4142135623730951},"21":{"tf":1.0},"281":{"tf":1.0},"32":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"5":{"tf":1.0}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"62":{"tf":1.4142135623730951}}}}}}}}}},"t":{"a":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"105":{"tf":1.0},"171":{"tf":1.0}}}},"df":0,"docs":{}},"df":1,"docs":{"116":{"tf":1.0}},"e":{"df":0,"docs":{},"g":{"df":1,"docs":{"250":{"tf":1.0}},"r":{"df":21,"docs":{"0":{"tf":1.0},"201":{"tf":1.0},"223":{"tf":1.0},"225":{"tf":1.0},"226":{"tf":1.0},"228":{"tf":1.0},"257":{"tf":1.0},"259":{"tf":1.0},"263":{"tf":1.0},"274":{"tf":2.449489742783178},"283":{"tf":1.4142135623730951},"47":{"tf":1.4142135623730951},"51":{"tf":1.4142135623730951},"52":{"tf":1.4142135623730951},"53":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"60":{"tf":2.6457513110645907},"61":{"tf":1.0},"7":{"tf":1.0}}}},"l":{"df":2,"docs":{"17":{"tf":1.0},"280":{"tf":1.0}}},"n":{"d":{"df":5,"docs":{"104":{"tf":1.4142135623730951},"107":{"tf":1.0},"274":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":1,"docs":{"64":{"tf":1.0}}},"t":{"df":1,"docs":{"227":{"tf":1.0}}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":6,"docs":{"140":{"tf":1.0},"205":{"tf":1.0},"225":{"tf":1.0},"226":{"tf":1.7320508075688772},"232":{"tf":1.4142135623730951},"254":{"tf":1.7320508075688772}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":6,"docs":{"104":{"tf":1.0},"231":{"tf":1.0},"263":{"tf":1.0},"37":{"tf":1.0},"41":{"tf":1.0},"58":{"tf":1.0}}}}},"f":{"a":{"c":{"df":11,"docs":{"103":{"tf":1.7320508075688772},"104":{"tf":2.23606797749979},"105":{"tf":1.7320508075688772},"106":{"tf":1.4142135623730951},"111":{"tf":1.0},"128":{"tf":1.0},"167":{"tf":1.0},"184":{"tf":1.0},"246":{"tf":1.0},"37":{"tf":1.0},"69":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"m":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"i":{"df":1,"docs":{"268":{"tf":1.0}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"l":{"df":1,"docs":{"62":{"tf":1.0}}}}}}},"n":{"df":6,"docs":{"105":{"tf":1.0},"106":{"tf":1.0},"146":{"tf":1.0},"175":{"tf":1.0},"193":{"tf":1.0},"283":{"tf":1.7320508075688772}}},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"220":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":2,"docs":{"124":{"tf":1.0},"242":{"tf":1.4142135623730951}}}}}}}},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"c":{"df":14,"docs":{"105":{"tf":1.4142135623730951},"158":{"tf":1.0},"172":{"tf":1.0},"180":{"tf":1.0},"207":{"tf":1.4142135623730951},"208":{"tf":1.4142135623730951},"213":{"tf":1.4142135623730951},"228":{"tf":1.0},"23":{"tf":1.0},"251":{"tf":1.0},"32":{"tf":1.0},"64":{"tf":1.0},"69":{"tf":1.0},"73":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":1,"docs":{"221":{"tf":1.0}}}},"s":{"df":0,"docs":{},"t":{"df":4,"docs":{"164":{"tf":1.0},"170":{"tf":1.0},"183":{"tf":1.0},"49":{"tf":1.0}},"i":{"df":0,"docs":{},"g":{"df":2,"docs":{"37":{"tf":1.0},"64":{"tf":1.0}}}}}}},"o":{"c":{"df":3,"docs":{"115":{"tf":1.0},"279":{"tf":1.0},"58":{"tf":1.0}}},"df":0,"docs":{},"k":{"df":2,"docs":{"105":{"tf":1.0},"283":{"tf":1.4142135623730951}}},"l":{"df":0,"docs":{},"v":{"df":12,"docs":{"116":{"tf":1.4142135623730951},"167":{"tf":1.0},"172":{"tf":1.0},"20":{"tf":1.0},"231":{"tf":1.0},"242":{"tf":1.0},"244":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"49":{"tf":1.0},"62":{"tf":1.0},"68":{"tf":1.0}}}}}}},"o":{"df":69,"docs":{"10":{"tf":1.0},"103":{"tf":1.4142135623730951},"109":{"tf":1.4142135623730951},"11":{"tf":1.0},"115":{"tf":1.0},"118":{"tf":2.0},"119":{"tf":1.7320508075688772},"127":{"tf":1.4142135623730951},"134":{"tf":1.0},"14":{"tf":2.0},"145":{"tf":1.0},"15":{"tf":1.7320508075688772},"161":{"tf":1.7320508075688772},"162":{"tf":1.4142135623730951},"164":{"tf":1.0},"165":{"tf":1.0},"167":{"tf":1.4142135623730951},"169":{"tf":1.0},"170":{"tf":1.7320508075688772},"171":{"tf":1.4142135623730951},"172":{"tf":1.4142135623730951},"175":{"tf":1.0},"185":{"tf":1.0},"192":{"tf":1.0},"199":{"tf":1.0},"200":{"tf":1.4142135623730951},"202":{"tf":1.4142135623730951},"203":{"tf":1.0},"213":{"tf":1.0},"215":{"tf":1.0},"218":{"tf":1.4142135623730951},"219":{"tf":1.0},"220":{"tf":1.0},"225":{"tf":1.7320508075688772},"228":{"tf":1.4142135623730951},"229":{"tf":1.0},"23":{"tf":2.449489742783178},"24":{"tf":1.7320508075688772},"241":{"tf":1.0},"25":{"tf":2.6457513110645907},"251":{"tf":1.0},"252":{"tf":1.0},"255":{"tf":1.0},"257":{"tf":1.7320508075688772},"26":{"tf":1.0},"268":{"tf":1.4142135623730951},"27":{"tf":1.0},"271":{"tf":1.4142135623730951},"272":{"tf":1.4142135623730951},"274":{"tf":1.4142135623730951},"278":{"tf":1.4142135623730951},"28":{"tf":1.4142135623730951},"280":{"tf":1.0},"29":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":2.23606797749979},"32":{"tf":2.0},"33":{"tf":1.4142135623730951},"34":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0},"38":{"tf":1.0},"56":{"tf":1.7320508075688772},"69":{"tf":1.0},"72":{"tf":1.4142135623730951},"73":{"tf":1.4142135623730951},"74":{"tf":1.0},"9":{"tf":1.0},"90":{"tf":1.4142135623730951}},"s":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"279":{"tf":1.0}}}}},"/":{"<":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"109":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"_":{"c":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"72":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"x":{"c":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"p":{"b":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"df":1,"docs":{"26":{"tf":1.4142135623730951}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"109":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":1,"docs":{"66":{"tf":1.4142135623730951}}}}}}}},"df":0,"docs":{},"’":{"df":1,"docs":{"218":{"tf":1.0}}}},"w":{"df":1,"docs":{"89":{"tf":1.0}}}},"s":{"_":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"o":{"df":1,"docs":{"282":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"n":{"'":{"df":0,"docs":{},"t":{"df":13,"docs":{"11":{"tf":1.0},"12":{"tf":1.0},"169":{"tf":1.0},"220":{"tf":1.0},"225":{"tf":1.0},"231":{"tf":1.0},"233":{"tf":1.0},"236":{"tf":1.0},"58":{"tf":1.0},"6":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0},"82":{"tf":1.0}}}},"df":0,"docs":{},"’":{"df":0,"docs":{},"t":{"df":3,"docs":{"115":{"tf":1.0},"197":{"tf":1.0},"205":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"l":{"df":3,"docs":{"201":{"tf":1.0},"229":{"tf":1.4142135623730951},"274":{"tf":1.0}}}},"s":{"df":0,"docs":{},"u":{"df":21,"docs":{"1":{"tf":2.0},"104":{"tf":1.0},"111":{"tf":1.0},"115":{"tf":1.0},"116":{"tf":1.0},"152":{"tf":1.0},"165":{"tf":1.0},"169":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"197":{"tf":1.0},"205":{"tf":1.4142135623730951},"209":{"tf":1.0},"225":{"tf":1.0},"228":{"tf":1.0},"237":{"tf":1.0},"242":{"tf":1.4142135623730951},"3":{"tf":1.0},"4":{"tf":1.4142135623730951},"6":{"tf":3.605551275463989}}}}},"t":{"'":{"df":54,"docs":{"104":{"tf":1.0},"105":{"tf":1.0},"107":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"11":{"tf":1.7320508075688772},"115":{"tf":1.0},"116":{"tf":1.0},"132":{"tf":1.0},"154":{"tf":1.0},"155":{"tf":1.0},"158":{"tf":1.0},"159":{"tf":1.0},"16":{"tf":1.4142135623730951},"167":{"tf":1.0},"169":{"tf":1.4142135623730951},"170":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"175":{"tf":1.0},"20":{"tf":1.0},"200":{"tf":1.0},"201":{"tf":1.0},"208":{"tf":1.0},"221":{"tf":1.0},"224":{"tf":1.0},"225":{"tf":1.4142135623730951},"227":{"tf":1.0},"232":{"tf":1.0},"242":{"tf":1.7320508075688772},"245":{"tf":1.7320508075688772},"249":{"tf":1.4142135623730951},"25":{"tf":1.0},"252":{"tf":1.0},"258":{"tf":1.0},"263":{"tf":1.0},"266":{"tf":1.0},"268":{"tf":1.0},"27":{"tf":1.4142135623730951},"274":{"tf":1.0},"43":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"48":{"tf":1.0},"49":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.4142135623730951},"7":{"tf":1.0},"75":{"tf":1.0},"78":{"tf":1.0},"81":{"tf":1.0},"84":{"tf":1.0},"9":{"tf":1.0}},"l":{"df":0,"docs":{},"l":{"df":1,"docs":{"82":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":7,"docs":{"165":{"tf":1.0},"172":{"tf":1.0},"189":{"tf":1.0},"232":{"tf":1.0},"59":{"tf":1.0},"73":{"tf":1.4142135623730951},"84":{"tf":2.0}},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":1,"docs":{"283":{"tf":1.0}}}}}}}}}}}}},"df":0,"docs":{}}}}}}},"r":{"df":5,"docs":{"104":{"tf":1.0},"105":{"tf":1.0},"124":{"tf":1.0},"167":{"tf":1.0},"249":{"tf":1.0}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":19,"docs":{"104":{"tf":1.0},"117":{"tf":1.0},"184":{"tf":1.0},"189":{"tf":1.0},"194":{"tf":1.7320508075688772},"197":{"tf":1.4142135623730951},"201":{"tf":1.0},"225":{"tf":1.0},"229":{"tf":1.0},"245":{"tf":1.0},"254":{"tf":1.0},"263":{"tf":1.0},"44":{"tf":1.0},"59":{"tf":1.0},"79":{"tf":1.0},"81":{"tf":1.0},"83":{"tf":1.0},"84":{"tf":1.4142135623730951},"86":{"tf":1.0}}}}}},"’":{"df":10,"docs":{"115":{"tf":1.0},"189":{"tf":1.0},"193":{"tf":1.4142135623730951},"194":{"tf":1.0},"205":{"tf":1.0},"210":{"tf":1.0},"212":{"tf":1.0},"213":{"tf":1.4142135623730951},"214":{"tf":1.7320508075688772},"87":{"tf":1.0}}}}},"j":{"a":{"df":0,"docs":{},"r":{"/":{"a":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"37":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"223":{"tf":1.0}},"e":{"d":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"176":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"v":{"a":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"/":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"/":{"<":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"103":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"_":{"c":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"70":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"_":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":1,"docs":{"12":{"tf":1.0}}}}}},"df":7,"docs":{"112":{"tf":1.0},"115":{"tf":1.0},"12":{"tf":1.4142135623730951},"125":{"tf":1.0},"128":{"tf":1.0},"223":{"tf":1.0},"37":{"tf":1.0}},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"192":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"d":{"df":0,"docs":{},"k":{"df":1,"docs":{"12":{"tf":1.0}}}},"df":0,"docs":{},"h":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"176":{"tf":1.0}}}},"df":0,"docs":{}}}}},"n":{"a":{"'":{"df":1,"docs":{"115":{"tf":1.0}}},"df":6,"docs":{"111":{"tf":1.0},"115":{"tf":1.4142135623730951},"116":{"tf":2.6457513110645907},"37":{"tf":3.7416573867739413},"45":{"tf":1.0},"65":{"tf":1.0}}},"df":0,"docs":{},"i":{"df":2,"docs":{"115":{"tf":1.4142135623730951},"116":{"tf":2.23606797749979}}}},"o":{"b":{"df":4,"docs":{"274":{"tf":1.0},"278":{"tf":1.0},"282":{"tf":1.0},"74":{"tf":1.0}}},"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"l":{"_":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"d":{"df":1,"docs":{"264":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"s":{"df":1,"docs":{"241":{"tf":1.0}},"o":{"df":0,"docs":{},"n":{"df":3,"docs":{"189":{"tf":1.0},"271":{"tf":1.4142135623730951},"79":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"45":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":1,"docs":{"194":{"tf":1.0}}}}}}},"v":{"df":0,"docs":{},"m":{"df":2,"docs":{"115":{"tf":1.4142135623730951},"37":{"tf":1.0}}}}},"k":{"8":{"8":{"df":0,"docs":{},"h":{"df":0,"docs":{},"u":{"d":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"176":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":28,"docs":{"121":{"tf":1.0},"142":{"tf":1.0},"150":{"tf":1.0},"163":{"tf":1.0},"165":{"tf":1.0},"167":{"tf":1.0},"171":{"tf":1.0},"179":{"tf":1.0},"182":{"tf":1.4142135623730951},"184":{"tf":1.0},"198":{"tf":1.0},"205":{"tf":2.0},"206":{"tf":1.0},"207":{"tf":1.0},"208":{"tf":1.0},"209":{"tf":2.23606797749979},"210":{"tf":1.0},"212":{"tf":1.7320508075688772},"213":{"tf":1.0},"217":{"tf":1.0},"218":{"tf":1.0},"242":{"tf":1.4142135623730951},"26":{"tf":1.0},"261":{"tf":1.0},"263":{"tf":1.0},"278":{"tf":1.0},"58":{"tf":1.4142135623730951},"6":{"tf":1.0}}}},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"264":{"tf":1.0}}}},"y":{"/":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":1,"docs":{"80":{"tf":1.0}}}}},"df":0,"docs":{}}},"_":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"242":{"tf":1.0}}},"df":0,"docs":{}}},"df":15,"docs":{"142":{"tf":1.7320508075688772},"143":{"tf":1.4142135623730951},"145":{"tf":3.1622776601683795},"146":{"tf":2.23606797749979},"184":{"tf":1.0},"198":{"tf":1.0},"201":{"tf":1.0},"225":{"tf":1.0},"229":{"tf":1.4142135623730951},"237":{"tf":1.0},"245":{"tf":1.0},"248":{"tf":1.0},"254":{"tf":1.4142135623730951},"274":{"tf":1.4142135623730951},"283":{"tf":1.4142135623730951}}}},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":1,"docs":{"133":{"tf":1.0}}}},"n":{"d":{"df":7,"docs":{"189":{"tf":1.0},"190":{"tf":1.0},"201":{"tf":1.0},"221":{"tf":1.0},"225":{"tf":1.0},"229":{"tf":1.0},"242":{"tf":1.0}}},"df":0,"docs":{}}},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":22,"docs":{"114":{"tf":1.4142135623730951},"115":{"tf":1.0},"117":{"tf":1.0},"119":{"tf":1.0},"128":{"tf":1.0},"17":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.0},"21":{"tf":1.0},"228":{"tf":1.7320508075688772},"229":{"tf":1.4142135623730951},"233":{"tf":1.4142135623730951},"254":{"tf":1.0},"26":{"tf":1.0},"268":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"5":{"tf":1.0},"58":{"tf":1.4142135623730951},"84":{"tf":1.0},"87":{"tf":1.4142135623730951},"89":{"tf":1.0}},"l":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"g":{"df":1,"docs":{"175":{"tf":1.0}}}},"df":0,"docs":{}}},"n":{"df":11,"docs":{"104":{"tf":1.0},"151":{"tf":1.4142135623730951},"152":{"tf":1.4142135623730951},"155":{"tf":1.4142135623730951},"156":{"tf":1.4142135623730951},"193":{"tf":1.0},"220":{"tf":1.0},"233":{"tf":1.0},"242":{"tf":1.0},"245":{"tf":1.0},"248":{"tf":1.4142135623730951}}}}}},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"/":{"df":0,"docs":{},"s":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":1,"docs":{"51":{"tf":1.0}}}}}}}},"df":27,"docs":{"101":{"tf":1.0},"103":{"tf":2.23606797749979},"104":{"tf":1.0},"108":{"tf":3.605551275463989},"111":{"tf":2.23606797749979},"115":{"tf":2.23606797749979},"125":{"tf":1.0},"128":{"tf":2.0},"129":{"tf":1.0},"130":{"tf":1.0},"131":{"tf":1.0},"132":{"tf":1.0},"133":{"tf":1.0},"196":{"tf":1.0},"221":{"tf":1.0},"222":{"tf":1.0},"225":{"tf":1.0},"268":{"tf":1.0},"271":{"tf":1.0},"274":{"tf":1.0},"45":{"tf":2.8284271247461903},"65":{"tf":1.7320508075688772},"68":{"tf":1.0},"7":{"tf":1.0},"70":{"tf":3.0},"98":{"tf":1.4142135623730951},"99":{"tf":1.4142135623730951}}}}}}},"t":{"df":2,"docs":{"70":{"tf":1.4142135623730951},"98":{"tf":1.4142135623730951}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"65":{"tf":1.0}}}}}}}},"l":{"a":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":2,"docs":{"6":{"tf":1.4142135623730951},"7":{"tf":1.4142135623730951}}}}},"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"187":{"tf":1.0}}}},"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"103":{"tf":1.0}}},"df":0,"docs":{}},"n":{"d":{"df":5,"docs":{"118":{"tf":1.0},"123":{"tf":1.0},"124":{"tf":1.0},"268":{"tf":1.0},"274":{"tf":1.4142135623730951}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"g":{"df":1,"docs":{"64":{"tf":1.0}},"u":{"a":{"df":0,"docs":{},"g":{"df":8,"docs":{"0":{"tf":1.0},"101":{"tf":1.0},"104":{"tf":1.0},"106":{"tf":1.0},"125":{"tf":1.0},"225":{"tf":1.0},"226":{"tf":1.0},"69":{"tf":1.0}}}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"g":{"df":7,"docs":{"204":{"tf":1.0},"205":{"tf":1.4142135623730951},"208":{"tf":1.0},"212":{"tf":1.7320508075688772},"213":{"tf":1.4142135623730951},"232":{"tf":1.0},"81":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":3,"docs":{"171":{"tf":1.0},"172":{"tf":1.0},"206":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"t":{"df":6,"docs":{"106":{"tf":1.0},"111":{"tf":1.0},"213":{"tf":1.0},"233":{"tf":1.0},"248":{"tf":1.4142135623730951},"88":{"tf":1.0}},"m":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":3,"docs":{"62":{"tf":1.0},"80":{"tf":1.0},"86":{"tf":1.0}}}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":7,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"11":{"tf":1.0},"138":{"tf":1.0},"158":{"tf":1.0},"179":{"tf":1.0},"88":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{"df":14,"docs":{"104":{"tf":1.0},"138":{"tf":1.0},"206":{"tf":1.0},"207":{"tf":1.0},"210":{"tf":1.0},"217":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951},"260":{"tf":1.4142135623730951},"261":{"tf":1.0},"266":{"tf":1.4142135623730951},"271":{"tf":1.0},"274":{"tf":1.4142135623730951},"40":{"tf":1.7320508075688772},"69":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":2,"docs":{"31":{"tf":1.0},"36":{"tf":1.0}}}},"df":0,"docs":{}}},"y":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":11,"docs":{"101":{"tf":1.0},"103":{"tf":1.0},"108":{"tf":1.4142135623730951},"109":{"tf":1.4142135623730951},"140":{"tf":1.0},"228":{"tf":1.0},"232":{"tf":1.4142135623730951},"44":{"tf":1.4142135623730951},"45":{"tf":1.0},"46":{"tf":1.0},"90":{"tf":1.0}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"200":{"tf":1.0}}}}}},"z":{"df":0,"docs":{},"i":{"df":1,"docs":{"132":{"tf":1.0}}},"y":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"62":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"d":{"a":{"df":0,"docs":{},"p":{"df":1,"docs":{"274":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"a":{"d":{"df":5,"docs":{"157":{"tf":1.0},"172":{"tf":1.0},"196":{"tf":1.0},"256":{"tf":1.0},"40":{"tf":1.0}}},"df":0,"docs":{},"f":{"df":1,"docs":{"59":{"tf":1.0}}},"n":{"df":1,"docs":{"138":{"tf":1.0}}},"r":{"df":0,"docs":{},"n":{"df":5,"docs":{"171":{"tf":1.0},"172":{"tf":1.0},"236":{"tf":1.0},"7":{"tf":1.0},"75":{"tf":1.4142135623730951}}}},"v":{"df":3,"docs":{"108":{"tf":1.0},"159":{"tf":1.0},"171":{"tf":1.0}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":6,"docs":{"126":{"tf":1.0},"193":{"tf":1.0},"25":{"tf":1.0},"40":{"tf":1.0},"64":{"tf":1.0},"84":{"tf":1.0}}}},"g":{"a":{"c":{"df":0,"docs":{},"i":{"df":2,"docs":{"203":{"tf":1.0},"228":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":1,"docs":{"186":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"s":{"df":10,"docs":{"115":{"tf":1.0},"166":{"tf":1.0},"170":{"tf":1.0},"205":{"tf":1.0},"209":{"tf":1.0},"212":{"tf":1.0},"214":{"tf":1.0},"215":{"tf":1.0},"61":{"tf":1.0},"81":{"tf":1.0}},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"90":{"tf":1.0}}}}}},"t":{"'":{"df":4,"docs":{"227":{"tf":1.0},"228":{"tf":1.0},"230":{"tf":1.0},"62":{"tf":1.0}}},"df":3,"docs":{"225":{"tf":1.0},"283":{"tf":1.0},"81":{"tf":1.0}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":42,"docs":{"103":{"tf":2.8284271247461903},"105":{"tf":1.0},"123":{"tf":1.4142135623730951},"124":{"tf":1.4142135623730951},"18":{"tf":1.0},"200":{"tf":2.0},"219":{"tf":1.7320508075688772},"22":{"tf":1.0},"220":{"tf":1.4142135623730951},"225":{"tf":3.0},"226":{"tf":3.1622776601683795},"229":{"tf":2.6457513110645907},"23":{"tf":1.0},"230":{"tf":2.0},"232":{"tf":1.4142135623730951},"233":{"tf":2.23606797749979},"234":{"tf":1.0},"235":{"tf":1.4142135623730951},"236":{"tf":2.23606797749979},"237":{"tf":1.7320508075688772},"239":{"tf":1.0},"243":{"tf":1.0},"244":{"tf":1.0},"245":{"tf":1.0},"249":{"tf":1.0},"251":{"tf":1.7320508075688772},"252":{"tf":1.0},"254":{"tf":2.0},"262":{"tf":1.0},"274":{"tf":1.0},"281":{"tf":1.4142135623730951},"283":{"tf":1.0},"37":{"tf":1.0},"41":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"6":{"tf":1.0},"64":{"tf":1.0},"70":{"tf":1.0},"73":{"tf":1.0},"84":{"tf":1.0},"98":{"tf":1.4142135623730951}}},"r":{"a":{"df":0,"docs":{},"g":{"df":2,"docs":{"123":{"tf":1.0},"79":{"tf":1.0}}}},"df":0,"docs":{}}}}},"i":{"b":{".":{"df":0,"docs":{},"r":{"df":4,"docs":{"103":{"tf":1.0},"105":{"tf":2.0},"107":{"tf":1.0},"60":{"tf":1.0}}}},"<":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{">":{"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{".":{"df":0,"docs":{},"k":{"df":0,"docs":{},"t":{"df":1,"docs":{"103":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}},"df":8,"docs":{"11":{"tf":1.0},"175":{"tf":1.0},"279":{"tf":1.0},"282":{"tf":1.0},"37":{"tf":1.0},"58":{"tf":1.0},"60":{"tf":1.4142135623730951},"69":{"tf":1.0}},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"a":{"df":0,"docs":{},"z":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"d":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":1,"docs":{"114":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"r":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":33,"docs":{"11":{"tf":1.0},"114":{"tf":1.4142135623730951},"167":{"tf":1.0},"187":{"tf":1.0},"188":{"tf":1.0},"189":{"tf":1.7320508075688772},"190":{"tf":2.449489742783178},"191":{"tf":1.7320508075688772},"193":{"tf":1.4142135623730951},"194":{"tf":1.4142135623730951},"196":{"tf":2.6457513110645907},"197":{"tf":2.23606797749979},"198":{"tf":1.0},"199":{"tf":1.4142135623730951},"200":{"tf":1.7320508075688772},"201":{"tf":1.4142135623730951},"220":{"tf":1.4142135623730951},"222":{"tf":1.4142135623730951},"225":{"tf":1.0},"228":{"tf":1.0},"251":{"tf":1.0},"274":{"tf":1.0},"278":{"tf":1.4142135623730951},"28":{"tf":1.0},"284":{"tf":1.0},"33":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.7320508075688772},"45":{"tf":1.0},"60":{"tf":1.0},"69":{"tf":1.0},"77":{"tf":1.7320508075688772},"90":{"tf":1.4142135623730951}}},"y":{"/":{"c":{"a":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{"df":0,"docs":{},"m":{"df":1,"docs":{"25":{"tf":1.0}}}}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"/":{"df":0,"docs":{},"x":{"c":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"e":{"/":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"d":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"/":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"25":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"s":{"/":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"280":{"tf":1.0}}},"df":0,"docs":{}}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"k":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":1,"docs":{"279":{"tf":1.0}}}}}}}}},"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":4,"docs":{"11":{"tf":1.4142135623730951},"12":{"tf":1.0},"14":{"tf":1.0},"279":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}},"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":4,"docs":{"2":{"tf":1.7320508075688772},"63":{"tf":1.0},"64":{"tf":1.7320508075688772},"7":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"y":{"c":{"df":0,"docs":{},"l":{"df":2,"docs":{"131":{"tf":2.0},"133":{"tf":1.0}}}},"df":0,"docs":{}}},"df":1,"docs":{"256":{"tf":1.0}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"130":{"tf":1.0}}}}}}},"k":{"df":0,"docs":{},"e":{"df":1,"docs":{"104":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"d":{"df":3,"docs":{"208":{"tf":1.0},"256":{"tf":1.0},"258":{"tf":1.0}}},"df":0,"docs":{}}}}}}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":17,"docs":{"134":{"tf":1.0},"140":{"tf":1.0},"17":{"tf":1.0},"187":{"tf":1.0},"202":{"tf":1.4142135623730951},"206":{"tf":1.0},"207":{"tf":1.7320508075688772},"208":{"tf":2.23606797749979},"209":{"tf":1.7320508075688772},"210":{"tf":1.4142135623730951},"213":{"tf":2.449489742783178},"214":{"tf":1.7320508075688772},"217":{"tf":1.0},"218":{"tf":1.4142135623730951},"225":{"tf":1.0},"242":{"tf":1.0},"7":{"tf":1.0}}}}},"n":{"df":0,"docs":{},"e":{"df":11,"docs":{"115":{"tf":1.0},"117":{"tf":1.4142135623730951},"126":{"tf":1.0},"165":{"tf":1.0},"17":{"tf":1.0},"172":{"tf":1.0},"18":{"tf":1.0},"200":{"tf":1.4142135623730951},"237":{"tf":1.4142135623730951},"29":{"tf":1.4142135623730951},"34":{"tf":1.4142135623730951}}},"k":{"a":{"df":0,"docs":{},"g":{"df":2,"docs":{"225":{"tf":1.0},"282":{"tf":1.0}}}},"df":14,"docs":{"11":{"tf":1.0},"119":{"tf":1.0},"147":{"tf":1.4142135623730951},"167":{"tf":1.0},"185":{"tf":1.4142135623730951},"218":{"tf":1.4142135623730951},"263":{"tf":1.0},"271":{"tf":1.7320508075688772},"278":{"tf":1.0},"282":{"tf":2.0},"37":{"tf":1.0},"40":{"tf":1.0},"58":{"tf":1.0},"60":{"tf":1.0}}},"t":{"df":2,"docs":{"274":{"tf":1.0},"92":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"7":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"x":{"df":5,"docs":{"11":{"tf":1.4142135623730951},"13":{"tf":1.0},"17":{"tf":1.4142135623730951},"280":{"tf":1.4142135623730951},"37":{"tf":1.7320508075688772}}}}},"s":{"df":0,"docs":{},"t":{"df":33,"docs":{"106":{"tf":1.7320508075688772},"109":{"tf":1.0},"134":{"tf":1.0},"137":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0},"17":{"tf":1.0},"188":{"tf":1.0},"200":{"tf":1.0},"203":{"tf":1.0},"204":{"tf":1.0},"21":{"tf":1.0},"220":{"tf":1.0},"233":{"tf":1.7320508075688772},"236":{"tf":1.0},"242":{"tf":1.7320508075688772},"251":{"tf":1.0},"259":{"tf":1.0},"264":{"tf":1.0},"266":{"tf":1.4142135623730951},"274":{"tf":1.0},"28":{"tf":1.0},"31":{"tf":1.0},"33":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.4142135623730951},"6":{"tf":1.0},"61":{"tf":1.0},"68":{"tf":1.0},"69":{"tf":1.0},"73":{"tf":2.23606797749979}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"62":{"tf":1.0},"89":{"tf":1.0}}}},"t":{"df":0,"docs":{},"l":{"df":9,"docs":{"104":{"tf":1.0},"170":{"tf":1.4142135623730951},"200":{"tf":1.0},"221":{"tf":1.0},"237":{"tf":1.0},"252":{"tf":1.0},"26":{"tf":1.0},"37":{"tf":1.0},"6":{"tf":1.0}}}}},"v":{"df":0,"docs":{},"e":{"df":11,"docs":{"130":{"tf":1.0},"229":{"tf":1.0},"231":{"tf":1.0},"252":{"tf":1.0},"43":{"tf":1.0},"46":{"tf":1.4142135623730951},"48":{"tf":1.0},"51":{"tf":1.0},"59":{"tf":1.0},"69":{"tf":1.0},"75":{"tf":1.0}}}}},"l":{"d":{"b":{"df":1,"docs":{"117":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":1,"docs":{"37":{"tf":1.0}}}}},"n":{"df":1,"docs":{"11":{"tf":1.0}}},"o":{"a":{"d":{"df":7,"docs":{"114":{"tf":1.4142135623730951},"184":{"tf":1.4142135623730951},"22":{"tf":1.0},"222":{"tf":1.4142135623730951},"224":{"tf":1.0},"226":{"tf":1.0},"229":{"tf":1.0}}},"df":0,"docs":{}},"c":{"a":{"df":0,"docs":{},"l":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":4,"docs":{"17":{"tf":1.0},"18":{"tf":1.4142135623730951},"20":{"tf":1.0},"22":{"tf":1.4142135623730951}}}}}}}}}}},"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"/":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"g":{"df":2,"docs":{"248":{"tf":1.4142135623730951},"249":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":1,"docs":{"249":{"tf":1.0}}}}}}}}},"df":45,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"11":{"tf":1.4142135623730951},"118":{"tf":1.0},"124":{"tf":1.4142135623730951},"15":{"tf":2.23606797749979},"16":{"tf":2.0},"17":{"tf":1.0},"18":{"tf":2.23606797749979},"19":{"tf":1.0},"194":{"tf":1.0},"198":{"tf":1.0},"20":{"tf":1.0},"21":{"tf":1.0},"22":{"tf":2.23606797749979},"224":{"tf":1.0},"23":{"tf":3.3166247903554},"24":{"tf":2.0},"242":{"tf":1.7320508075688772},"245":{"tf":1.7320508075688772},"246":{"tf":1.4142135623730951},"248":{"tf":1.4142135623730951},"249":{"tf":2.6457513110645907},"25":{"tf":2.0},"250":{"tf":2.23606797749979},"257":{"tf":1.0},"26":{"tf":2.23606797749979},"27":{"tf":1.0},"28":{"tf":1.4142135623730951},"29":{"tf":2.0},"30":{"tf":1.7320508075688772},"31":{"tf":2.6457513110645907},"32":{"tf":3.1622776601683795},"33":{"tf":1.4142135623730951},"34":{"tf":2.23606797749979},"35":{"tf":1.7320508075688772},"36":{"tf":2.8284271247461903},"37":{"tf":2.8284271247461903},"45":{"tf":1.0},"49":{"tf":1.4142135623730951},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"67":{"tf":1.0},"99":{"tf":1.0}},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"190":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{">":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"/":{"c":{"/":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"{":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"}":{"/":{".":{"df":0,"docs":{},"m":{"2":{"/":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"<":{"/":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"13":{"tf":1.0}}}}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}}},"t":{"df":10,"docs":{"106":{"tf":1.0},"12":{"tf":1.0},"14":{"tf":1.0},"158":{"tf":1.0},"201":{"tf":1.0},"22":{"tf":1.0},"224":{"tf":1.0},"267":{"tf":1.7320508075688772},"279":{"tf":1.7320508075688772},"82":{"tf":1.0}}}},"df":0,"docs":{},"k":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"x":{"df":2,"docs":{"228":{"tf":1.4142135623730951},"234":{"tf":1.0}}}}},"df":3,"docs":{"142":{"tf":1.0},"205":{"tf":1.4142135623730951},"64":{"tf":1.0}}}},"df":0,"docs":{},"g":{"c":{"a":{"df":0,"docs":{},"t":{"df":2,"docs":{"126":{"tf":2.449489742783178},"37":{"tf":1.0}}}},"df":0,"docs":{}},"df":10,"docs":{"116":{"tf":1.0},"125":{"tf":2.6457513110645907},"126":{"tf":2.8284271247461903},"127":{"tf":1.7320508075688772},"134":{"tf":1.7320508075688772},"242":{"tf":1.0},"268":{"tf":1.7320508075688772},"280":{"tf":1.0},"37":{"tf":1.0},"82":{"tf":1.0}},"i":{"c":{"df":19,"docs":{"140":{"tf":1.0},"141":{"tf":1.0},"145":{"tf":2.449489742783178},"146":{"tf":1.0},"149":{"tf":1.0},"154":{"tf":1.0},"158":{"tf":1.0},"208":{"tf":1.0},"244":{"tf":1.0},"254":{"tf":1.0},"256":{"tf":1.4142135623730951},"258":{"tf":1.0},"282":{"tf":1.4142135623730951},"73":{"tf":1.0},"78":{"tf":1.0},"79":{"tf":1.0},"81":{"tf":1.0},"82":{"tf":1.4142135623730951},"84":{"tf":1.4142135623730951}}},"df":0,"docs":{},"n":{"df":15,"docs":{"112":{"tf":1.0},"134":{"tf":1.0},"139":{"tf":1.4142135623730951},"140":{"tf":1.7320508075688772},"141":{"tf":1.4142135623730951},"145":{"tf":2.6457513110645907},"146":{"tf":2.23606797749979},"147":{"tf":1.0},"221":{"tf":1.0},"228":{"tf":1.4142135623730951},"232":{"tf":1.0},"233":{"tf":2.0},"244":{"tf":1.0},"264":{"tf":1.4142135623730951},"78":{"tf":1.0}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"132":{"tf":1.0}}}},"df":0,"docs":{}}}}}}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":1,"docs":{"126":{"tf":1.0}}}}}}}},"n":{"df":0,"docs":{},"g":{"df":17,"docs":{"104":{"tf":1.0},"105":{"tf":1.0},"115":{"tf":1.4142135623730951},"132":{"tf":1.0},"140":{"tf":1.0},"164":{"tf":1.0},"170":{"tf":1.0},"204":{"tf":1.0},"205":{"tf":1.0},"212":{"tf":1.0},"215":{"tf":1.0},"22":{"tf":1.0},"229":{"tf":1.0},"25":{"tf":1.0},"258":{"tf":1.0},"264":{"tf":1.0},"59":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":7,"docs":{"128":{"tf":1.0},"140":{"tf":1.0},"180":{"tf":1.0},"205":{"tf":1.0},"206":{"tf":1.7320508075688772},"226":{"tf":1.0},"86":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"k":{"df":19,"docs":{"102":{"tf":1.4142135623730951},"103":{"tf":1.0},"158":{"tf":1.0},"215":{"tf":1.0},"216":{"tf":1.0},"22":{"tf":1.0},"221":{"tf":1.0},"224":{"tf":1.4142135623730951},"280":{"tf":1.0},"283":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"6":{"tf":1.0},"62":{"tf":1.0},"73":{"tf":1.7320508075688772},"75":{"tf":1.4142135623730951},"82":{"tf":1.0},"86":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"203":{"tf":1.0},"218":{"tf":1.0}}}}},"p":{"df":5,"docs":{"190":{"tf":1.0},"193":{"tf":1.0},"197":{"tf":1.4142135623730951},"249":{"tf":1.0},"82":{"tf":1.0}}},"s":{"df":1,"docs":{"240":{"tf":1.0}}}},"s":{"df":0,"docs":{},"e":{"df":5,"docs":{"204":{"tf":1.0},"206":{"tf":1.0},"208":{"tf":1.0},"210":{"tf":1.4142135623730951},"213":{"tf":1.4142135623730951}}},"s":{"df":2,"docs":{"157":{"tf":1.0},"209":{"tf":1.0}}},"t":{"df":2,"docs":{"158":{"tf":1.0},"231":{"tf":1.0}}}},"t":{"df":4,"docs":{"115":{"tf":1.0},"116":{"tf":1.0},"212":{"tf":1.4142135623730951},"240":{"tf":1.0}}},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"a":{"c":{"6":{"4":{"df":1,"docs":{"202":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"w":{"df":16,"docs":{"103":{"tf":2.0},"209":{"tf":1.4142135623730951},"220":{"tf":1.0},"225":{"tf":1.0},"226":{"tf":2.23606797749979},"229":{"tf":1.7320508075688772},"230":{"tf":1.7320508075688772},"232":{"tf":1.0},"233":{"tf":2.0},"234":{"tf":1.0},"235":{"tf":1.4142135623730951},"236":{"tf":1.0},"237":{"tf":1.4142135623730951},"249":{"tf":1.0},"281":{"tf":1.0},"37":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"c":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"c":{"a":{"df":0,"docs":{},"s":{"df":2,"docs":{"96":{"tf":1.0},"99":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":3,"docs":{"209":{"tf":1.0},"225":{"tf":1.7320508075688772},"99":{"tf":1.0}}}}}},"u":{"c":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"258":{"tf":1.0}}},"df":0,"docs":{}},"k":{"df":1,"docs":{"90":{"tf":1.0}}}},"df":0,"docs":{}}},"m":{"1":{"df":7,"docs":{"161":{"tf":1.0},"162":{"tf":1.0},"163":{"tf":1.0},"169":{"tf":1.0},"17":{"tf":1.0},"170":{"tf":1.0},"175":{"tf":1.0}}},"2":{"df":1,"docs":{"13":{"tf":1.4142135623730951}}},"a":{"c":{"df":3,"docs":{"280":{"tf":1.0},"37":{"tf":1.0},"73":{"tf":1.0}},"h":{"df":2,"docs":{"122":{"tf":1.0},"124":{"tf":1.4142135623730951}},"i":{"df":0,"docs":{},"n":{"df":10,"docs":{"161":{"tf":1.0},"162":{"tf":1.0},"17":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"175":{"tf":1.0},"223":{"tf":1.4142135623730951},"280":{"tf":1.4142135623730951},"45":{"tf":1.0}}}}},"o":{"df":2,"docs":{"11":{"tf":1.0},"280":{"tf":1.7320508075688772}}},"r":{"df":0,"docs":{},"o":{"df":3,"docs":{"200":{"tf":1.0},"43":{"tf":1.0},"93":{"tf":1.0}}}}},"d":{"df":0,"docs":{},"e":{"df":15,"docs":{"121":{"tf":1.0},"122":{"tf":1.0},"136":{"tf":1.0},"138":{"tf":1.0},"167":{"tf":1.0},"192":{"tf":1.0},"193":{"tf":1.0},"194":{"tf":1.0},"200":{"tf":1.0},"21":{"tf":1.0},"226":{"tf":1.0},"227":{"tf":1.0},"231":{"tf":1.0},"233":{"tf":1.0},"82":{"tf":1.0}}},"r":{"df":3,"docs":{"134":{"tf":1.4142135623730951},"137":{"tf":1.0},"138":{"tf":2.0}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"228":{"tf":1.0}}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"274":{"tf":1.0}}},"n":{"df":24,"docs":{"103":{"tf":2.0},"119":{"tf":1.0},"121":{"tf":1.0},"126":{"tf":1.0},"170":{"tf":1.0},"182":{"tf":1.0},"193":{"tf":1.0},"194":{"tf":1.0},"201":{"tf":1.0},"224":{"tf":1.0},"246":{"tf":1.0},"251":{"tf":1.0},"254":{"tf":1.0},"260":{"tf":1.0},"268":{"tf":1.7320508075688772},"269":{"tf":1.0},"270":{"tf":1.0},"274":{"tf":2.6457513110645907},"40":{"tf":1.7320508075688772},"51":{"tf":1.0},"64":{"tf":1.0},"7":{"tf":1.0},"70":{"tf":1.0},"82":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":4,"docs":{"14":{"tf":1.0},"180":{"tf":1.0},"253":{"tf":1.4142135623730951},"278":{"tf":1.0}}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":12,"docs":{"146":{"tf":1.0},"162":{"tf":1.0},"167":{"tf":1.0},"169":{"tf":1.0},"170":{"tf":1.4142135623730951},"171":{"tf":1.0},"172":{"tf":1.0},"196":{"tf":1.0},"203":{"tf":1.0},"260":{"tf":1.0},"263":{"tf":1.0},"274":{"tf":1.0}}}},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"220":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"64":{"tf":1.0}}},"df":5,"docs":{"138":{"tf":1.0},"164":{"tf":1.0},"172":{"tf":1.0},"208":{"tf":1.0},"274":{"tf":1.0}}}}}}},"j":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":8,"docs":{"116":{"tf":1.0},"208":{"tf":1.0},"262":{"tf":1.0},"269":{"tf":1.0},"270":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"58":{"tf":1.0}}}}},"k":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"g":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"253":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":64,"docs":{"102":{"tf":1.0},"104":{"tf":1.0},"105":{"tf":1.0},"106":{"tf":1.4142135623730951},"107":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"11":{"tf":1.7320508075688772},"111":{"tf":1.0},"116":{"tf":1.4142135623730951},"117":{"tf":1.4142135623730951},"118":{"tf":1.0},"120":{"tf":1.0},"122":{"tf":1.4142135623730951},"124":{"tf":1.7320508075688772},"126":{"tf":1.0},"129":{"tf":1.0},"14":{"tf":1.0},"145":{"tf":1.0},"146":{"tf":1.0},"149":{"tf":1.0},"163":{"tf":1.0},"165":{"tf":1.0},"169":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"188":{"tf":1.0},"197":{"tf":1.0},"198":{"tf":1.0},"200":{"tf":1.0},"21":{"tf":1.0},"214":{"tf":1.0},"226":{"tf":1.0},"227":{"tf":1.0},"241":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951},"250":{"tf":1.0},"26":{"tf":1.0},"263":{"tf":1.0},"264":{"tf":1.0},"270":{"tf":1.0},"272":{"tf":1.0},"274":{"tf":1.0},"278":{"tf":1.0},"280":{"tf":1.0},"282":{"tf":1.0},"32":{"tf":1.0},"36":{"tf":1.0},"39":{"tf":1.4142135623730951},"40":{"tf":1.0},"43":{"tf":1.7320508075688772},"46":{"tf":1.0},"51":{"tf":1.0},"57":{"tf":1.4142135623730951},"58":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0},"7":{"tf":1.0},"70":{"tf":1.0},"72":{"tf":1.0},"73":{"tf":1.4142135623730951},"75":{"tf":1.0},"79":{"tf":1.0}}}},"l":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":1,"docs":{"274":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"n":{"a":{"df":0,"docs":{},"g":{"df":54,"docs":{"115":{"tf":1.0},"117":{"tf":1.0},"132":{"tf":1.7320508075688772},"140":{"tf":1.0},"146":{"tf":1.4142135623730951},"171":{"tf":1.0},"172":{"tf":1.0},"174":{"tf":1.4142135623730951},"185":{"tf":1.0},"187":{"tf":1.0},"189":{"tf":1.0},"197":{"tf":1.0},"201":{"tf":1.0},"219":{"tf":1.7320508075688772},"220":{"tf":1.4142135623730951},"221":{"tf":1.0},"225":{"tf":3.1622776601683795},"226":{"tf":3.3166247903554},"227":{"tf":1.0},"228":{"tf":2.449489742783178},"229":{"tf":2.23606797749979},"23":{"tf":2.0},"230":{"tf":1.0},"231":{"tf":1.0},"232":{"tf":2.0},"233":{"tf":1.7320508075688772},"234":{"tf":1.0},"235":{"tf":1.0},"236":{"tf":1.4142135623730951},"237":{"tf":1.0},"238":{"tf":1.0},"239":{"tf":1.4142135623730951},"240":{"tf":1.0},"241":{"tf":1.4142135623730951},"242":{"tf":1.7320508075688772},"244":{"tf":1.0},"245":{"tf":1.7320508075688772},"253":{"tf":1.0},"268":{"tf":1.7320508075688772},"269":{"tf":1.4142135623730951},"274":{"tf":1.4142135623730951},"32":{"tf":2.0},"48":{"tf":1.0},"63":{"tf":1.7320508075688772},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0},"67":{"tf":1.0},"69":{"tf":1.0},"7":{"tf":1.0},"73":{"tf":2.0},"78":{"tf":1.0},"79":{"tf":1.0},"81":{"tf":1.7320508075688772}}}},"df":3,"docs":{"201":{"tf":1.4142135623730951},"236":{"tf":1.4142135623730951},"242":{"tf":1.7320508075688772}},"i":{"df":19,"docs":{"106":{"tf":1.4142135623730951},"109":{"tf":1.0},"126":{"tf":1.0},"152":{"tf":1.0},"154":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0},"175":{"tf":1.0},"204":{"tf":1.4142135623730951},"213":{"tf":1.0},"214":{"tf":1.0},"228":{"tf":1.0},"257":{"tf":1.0},"283":{"tf":1.0},"59":{"tf":1.0},"62":{"tf":1.0},"64":{"tf":1.0},"75":{"tf":1.0},"79":{"tf":1.4142135623730951}},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"70":{"tf":1.0}}}}}}},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"126":{"tf":1.0}}}}},"u":{"a":{"df":0,"docs":{},"l":{"df":18,"docs":{"101":{"tf":1.0},"111":{"tf":1.0},"119":{"tf":1.0},"121":{"tf":1.0},"17":{"tf":1.0},"20":{"tf":1.7320508075688772},"22":{"tf":1.7320508075688772},"25":{"tf":1.4142135623730951},"27":{"tf":2.0},"274":{"tf":1.4142135623730951},"31":{"tf":1.0},"36":{"tf":1.0},"39":{"tf":1.0},"49":{"tf":1.4142135623730951},"52":{"tf":1.0},"59":{"tf":1.0},"82":{"tf":1.0},"84":{"tf":1.0}}}},"df":0,"docs":{}}},"p":{"df":9,"docs":{"106":{"tf":1.0},"11":{"tf":1.4142135623730951},"116":{"tf":1.0},"12":{"tf":1.0},"232":{"tf":1.0},"251":{"tf":1.0},"252":{"tf":1.4142135623730951},"28":{"tf":1.0},"33":{"tf":1.0}}},"r":{"df":0,"docs":{},"k":{"d":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":0,"docs":{},"n":{"df":4,"docs":{"134":{"tf":1.0},"135":{"tf":1.4142135623730951},"137":{"tf":1.0},"284":{"tf":1.0}}}}}},"df":1,"docs":{"171":{"tf":1.0}},"h":{"df":1,"docs":{"276":{"tf":1.0}}}},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"197":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":2,"docs":{"242":{"tf":1.0},"81":{"tf":1.0}}}}}},"t":{"c":{"df":0,"docs":{},"h":{"df":12,"docs":{"104":{"tf":1.0},"105":{"tf":1.4142135623730951},"124":{"tf":1.0},"165":{"tf":1.0},"20":{"tf":1.0},"201":{"tf":1.0},"213":{"tf":1.0},"22":{"tf":1.4142135623730951},"242":{"tf":1.0},"268":{"tf":1.0},"40":{"tf":1.0},"87":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"174":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"df":2,"docs":{"122":{"tf":1.4142135623730951},"3":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"82":{"tf":1.0}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{".":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{".":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":1,"docs":{"274":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":7,"docs":{"11":{"tf":1.0},"13":{"tf":2.449489742783178},"16":{"tf":1.0},"18":{"tf":1.0},"22":{"tf":1.0},"271":{"tf":2.23606797749979},"274":{"tf":1.0}},"l":{"df":0,"docs":{},"o":{"c":{"df":1,"docs":{"20":{"tf":1.0}}},"df":0,"docs":{}}}}}},"y":{"b":{"df":1,"docs":{"51":{"tf":1.0}}},"df":0,"docs":{}}},"d":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":2,"docs":{"284":{"tf":1.0},"286":{"tf":2.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"n":{"df":25,"docs":{"118":{"tf":1.0},"128":{"tf":1.0},"13":{"tf":1.0},"140":{"tf":1.0},"175":{"tf":1.0},"190":{"tf":1.0},"201":{"tf":1.0},"204":{"tf":1.7320508075688772},"205":{"tf":1.0},"207":{"tf":1.7320508075688772},"208":{"tf":1.0},"209":{"tf":1.4142135623730951},"213":{"tf":1.0},"22":{"tf":1.0},"224":{"tf":1.0},"232":{"tf":1.0},"242":{"tf":1.0},"250":{"tf":1.0},"263":{"tf":1.0},"45":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"82":{"tf":1.4142135623730951},"86":{"tf":1.4142135623730951},"89":{"tf":1.4142135623730951}}},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":4,"docs":{"203":{"tf":1.0},"274":{"tf":1.4142135623730951},"59":{"tf":1.0},"64":{"tf":1.0}}}}}},"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"n":{"df":3,"docs":{"226":{"tf":1.0},"231":{"tf":1.0},"274":{"tf":1.0}}}},"df":0,"docs":{}}},"d":{"df":0,"docs":{},"i":{"a":{"=":{"\"":{"(":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":1,"docs":{"283":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"n":{"df":1,"docs":{"204":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":2,"docs":{"111":{"tf":1.0},"220":{"tf":1.0}}}},"g":{"a":{"df":0,"docs":{},"z":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"d":{"'":{")":{".":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"}":{"/":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"j":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"s":{"/":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"k":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":1,"docs":{"224":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"k":{"df":0,"docs":{},"s":{"[":{"\"":{"c":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"224":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":16,"docs":{"105":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"197":{"tf":1.0},"219":{"tf":1.4142135623730951},"220":{"tf":2.23606797749979},"221":{"tf":2.449489742783178},"222":{"tf":2.6457513110645907},"223":{"tf":2.0},"224":{"tf":2.0},"251":{"tf":1.0},"252":{"tf":1.4142135623730951},"46":{"tf":1.0},"69":{"tf":1.0},"70":{"tf":1.0},"73":{"tf":2.23606797749979}},"s":{"/":{"df":0,"docs":{},"f":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"/":{"c":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":1,"docs":{"69":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{},"s":{"df":0,"docs":{},"r":{"c":{"/":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"b":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"69":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"i":{"df":0,"docs":{},"o":{"df":9,"docs":{"109":{"tf":1.4142135623730951},"14":{"tf":1.4142135623730951},"252":{"tf":1.0},"28":{"tf":1.0},"29":{"tf":1.0},"33":{"tf":1.0},"46":{"tf":1.0},"69":{"tf":1.4142135623730951},"73":{"tf":2.449489742783178}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"m":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":3,"docs":{"3":{"tf":1.0},"69":{"tf":1.4142135623730951},"8":{"tf":1.0}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":3,"docs":{"116":{"tf":1.7320508075688772},"132":{"tf":1.0},"264":{"tf":1.0}}}}},"s":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"51":{"tf":1.0}}}},"df":0,"docs":{}}},"n":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"105":{"tf":1.0}}},"t":{"df":1,"docs":{"60":{"tf":1.0}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":3,"docs":{"204":{"tf":1.0},"210":{"tf":1.0},"242":{"tf":1.0}}}}}},"u":{"_":{"_":{"_":{"_":{"_":{"_":{"_":{"_":{"df":1,"docs":{"62":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":3,"docs":{"12":{"tf":1.0},"126":{"tf":1.0},"62":{"tf":1.0}}}},"r":{"df":0,"docs":{},"g":{"df":9,"docs":{"118":{"tf":1.0},"119":{"tf":2.449489742783178},"158":{"tf":1.0},"249":{"tf":1.0},"250":{"tf":1.0},"268":{"tf":1.4142135623730951},"270":{"tf":1.0},"40":{"tf":1.0},"7":{"tf":1.4142135623730951}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":1,"docs":{"193":{"tf":1.0}}}}},"m":{"a":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"286":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"s":{"a":{"df":0,"docs":{},"g":{"df":4,"docs":{"104":{"tf":1.0},"105":{"tf":1.0},"106":{"tf":2.6457513110645907},"126":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"a":{"/":{"df":0,"docs":{},"g":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"b":{"df":4,"docs":{"226":{"tf":1.0},"228":{"tf":1.0},"233":{"tf":1.0},"245":{"tf":1.0}}},"df":0,"docs":{}}}}},"d":{"a":{"df":0,"docs":{},"t":{"a":{".":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"a":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"d":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"a":{"df":1,"docs":{"283":{"tf":1.0}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":10,"docs":{"245":{"tf":1.0},"248":{"tf":1.0},"249":{"tf":1.0},"252":{"tf":1.0},"254":{"tf":1.0},"271":{"tf":1.4142135623730951},"70":{"tf":1.0},"80":{"tf":1.0},"81":{"tf":1.0},"87":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":4,"docs":{"231":{"tf":1.0},"261":{"tf":1.0},"80":{"tf":1.4142135623730951},"84":{"tf":1.4142135623730951}}},"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"d":{"df":6,"docs":{"131":{"tf":2.0},"133":{"tf":1.0},"16":{"tf":1.0},"245":{"tf":1.7320508075688772},"268":{"tf":1.0},"283":{"tf":1.0}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"i":{"c":{"df":5,"docs":{"184":{"tf":1.0},"219":{"tf":1.0},"253":{"tf":1.0},"259":{"tf":1.7320508075688772},"283":{"tf":1.0}}},"df":0,"docs":{}}}}},"h":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"186":{"tf":1.0},"202":{"tf":1.0}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"i":{"b":{"df":1,"docs":{"206":{"tf":1.0}}},"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":1,"docs":{"137":{"tf":1.0}}}}},"df":0,"docs":{}}},"d":{"d":{"df":0,"docs":{},"l":{"df":2,"docs":{"156":{"tf":1.0},"201":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":1,"docs":{"165":{"tf":1.0}},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":21,"docs":{"134":{"tf":1.0},"145":{"tf":1.0},"154":{"tf":1.4142135623730951},"167":{"tf":1.4142135623730951},"169":{"tf":1.0},"202":{"tf":1.4142135623730951},"203":{"tf":2.23606797749979},"204":{"tf":3.4641016151377544},"205":{"tf":1.7320508075688772},"206":{"tf":1.0},"207":{"tf":2.449489742783178},"208":{"tf":1.7320508075688772},"209":{"tf":1.4142135623730951},"212":{"tf":2.6457513110645907},"213":{"tf":1.4142135623730951},"214":{"tf":1.7320508075688772},"216":{"tf":1.0},"217":{"tf":1.7320508075688772},"218":{"tf":1.7320508075688772},"257":{"tf":1.0},"258":{"tf":1.0}}}},"df":0,"docs":{}}},"h":{"a":{"df":0,"docs":{},"i":{"df":1,"docs":{"274":{"tf":1.0}}}},"df":0,"docs":{}},"l":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"105":{"tf":1.0}}}},"df":0,"docs":{}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"204":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"n":{"d":{"df":2,"docs":{"242":{"tf":1.0},"63":{"tf":1.0}}},"df":0,"docs":{},"e":{"df":1,"docs":{"37":{"tf":1.0}}},"i":{"df":0,"docs":{},"m":{"df":5,"docs":{"118":{"tf":1.0},"163":{"tf":1.0},"199":{"tf":1.0},"226":{"tf":1.0},"258":{"tf":1.0}},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"263":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"262":{"tf":1.0}}}},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"104":{"tf":1.0},"20":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":2,"docs":{"248":{"tf":1.0},"249":{"tf":1.4142135623730951}}}}}},"s":{"c":{"df":1,"docs":{"240":{"tf":1.4142135623730951}}},"df":0,"docs":{},"i":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":1,"docs":{"157":{"tf":1.0}},"i":{"df":1,"docs":{"158":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"m":{"a":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"121":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"s":{"df":2,"docs":{"109":{"tf":1.0},"278":{"tf":1.0}}},"t":{"a":{"df":0,"docs":{},"k":{"df":1,"docs":{"79":{"tf":1.0}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"/":{"c":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":1,"docs":{"116":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":1,"docs":{"210":{"tf":1.0}}}}}},"m":{"df":1,"docs":{"268":{"tf":1.0}}},"o":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":14,"docs":{"119":{"tf":1.0},"134":{"tf":1.0},"186":{"tf":1.7320508075688772},"187":{"tf":1.7320508075688772},"188":{"tf":1.0},"190":{"tf":1.4142135623730951},"192":{"tf":1.0},"194":{"tf":1.0},"197":{"tf":1.0},"200":{"tf":2.8284271247461903},"201":{"tf":1.4142135623730951},"232":{"tf":1.0},"274":{"tf":1.0},"62":{"tf":1.0}},"e":{"'":{"df":1,"docs":{"178":{"tf":1.0}}},"_":{"_":{"_":{"_":{"_":{"_":{"df":1,"docs":{"62":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"c":{"df":0,"docs":{},"k":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"51":{"tf":1.0}}}},"df":0,"docs":{}},"df":2,"docs":{"43":{"tf":1.7320508075688772},"51":{"tf":1.0}},"i":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":1,"docs":{"43":{"tf":1.0}}}}},"df":0,"docs":{}}}},"d":{"df":3,"docs":{"60":{"tf":1.0},"61":{"tf":2.0},"62":{"tf":1.0}},"e":{"df":2,"docs":{"236":{"tf":1.0},"79":{"tf":1.0}},"l":{"df":8,"docs":{"105":{"tf":1.0},"145":{"tf":1.0},"184":{"tf":1.0},"194":{"tf":1.0},"197":{"tf":1.7320508075688772},"201":{"tf":1.4142135623730951},"274":{"tf":1.0},"51":{"tf":1.0}}},"r":{"df":0,"docs":{},"n":{"df":2,"docs":{"161":{"tf":1.0},"169":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"f":{"df":1,"docs":{"256":{"tf":1.0}},"i":{"df":12,"docs":{"183":{"tf":1.0},"196":{"tf":1.0},"197":{"tf":1.0},"201":{"tf":1.4142135623730951},"249":{"tf":1.0},"250":{"tf":1.0},"256":{"tf":1.0},"274":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":2.0},"7":{"tf":1.4142135623730951}}}}},"u":{"df":0,"docs":{},"l":{"df":13,"docs":{"103":{"tf":1.0},"105":{"tf":1.0},"175":{"tf":1.0},"222":{"tf":1.0},"251":{"tf":1.0},"252":{"tf":1.4142135623730951},"28":{"tf":1.0},"282":{"tf":1.4142135623730951},"33":{"tf":1.0},"61":{"tf":1.7320508075688772},"62":{"tf":1.0},"83":{"tf":1.4142135623730951},"93":{"tf":1.0}},"e":{".":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"p":{"df":1,"docs":{"252":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},":":{"\"":{"df":0,"docs":{},"j":{"df":0,"docs":{},"n":{"a":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"58":{"tf":1.0}}}}}},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"152":{"tf":1.0}}}}}},"o":{"df":1,"docs":{"18":{"tf":1.0}}},"t":{"df":0,"docs":{},"h":{"df":2,"docs":{"213":{"tf":1.4142135623730951},"278":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"e":{"_":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"43":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":39,"docs":{"10":{"tf":1.0},"101":{"tf":1.0},"105":{"tf":1.0},"106":{"tf":1.4142135623730951},"126":{"tf":1.4142135623730951},"134":{"tf":1.0},"161":{"tf":1.0},"166":{"tf":1.0},"169":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"177":{"tf":1.0},"194":{"tf":1.0},"200":{"tf":1.0},"205":{"tf":1.7320508075688772},"209":{"tf":1.4142135623730951},"210":{"tf":1.4142135623730951},"213":{"tf":1.7320508075688772},"225":{"tf":1.0},"227":{"tf":1.0},"229":{"tf":1.4142135623730951},"23":{"tf":1.0},"231":{"tf":1.0},"232":{"tf":1.4142135623730951},"242":{"tf":1.0},"274":{"tf":1.4142135623730951},"32":{"tf":1.4142135623730951},"36":{"tf":1.0},"50":{"tf":1.0},"51":{"tf":1.0},"6":{"tf":1.7320508075688772},"60":{"tf":1.4142135623730951},"61":{"tf":1.0},"7":{"tf":1.4142135623730951},"73":{"tf":1.0},"78":{"tf":1.4142135623730951},"93":{"tf":1.0},"96":{"tf":1.0},"99":{"tf":1.0}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":2,"docs":{"224":{"tf":1.0},"282":{"tf":1.0}}}}}},"v":{"df":0,"docs":{},"e":{"df":20,"docs":{"103":{"tf":1.0},"105":{"tf":1.0},"11":{"tf":1.4142135623730951},"115":{"tf":1.0},"151":{"tf":1.0},"158":{"tf":1.7320508075688772},"166":{"tf":1.4142135623730951},"167":{"tf":1.4142135623730951},"170":{"tf":1.4142135623730951},"174":{"tf":1.0},"190":{"tf":1.0},"197":{"tf":1.0},"198":{"tf":1.0},"200":{"tf":1.7320508075688772},"218":{"tf":1.0},"225":{"tf":1.4142135623730951},"228":{"tf":1.0},"230":{"tf":1.0},"273":{"tf":1.0},"68":{"tf":1.0}}}},"z":{"_":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"62":{"tf":2.0}},"s":{"\"":{")":{".":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"w":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":1,"docs":{"62":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":4,"docs":{"121":{"tf":1.0},"122":{"tf":1.4142135623730951},"276":{"tf":1.7320508075688772},"277":{"tf":1.0}},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"'":{"df":2,"docs":{"187":{"tf":1.0},"253":{"tf":1.0}}},".":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"$":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"112":{"tf":1.0}}}},"df":0,"docs":{}}},"<":{"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"_":{"c":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"70":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"a":{"c":{"df":1,"docs":{"100":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{".":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"222":{"tf":1.0}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":1,"docs":{"222":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":3,"docs":{"163":{"tf":1.4142135623730951},"166":{"tf":1.0},"31":{"tf":1.0}}},"df":0,"docs":{}}}}}}}}},"df":23,"docs":{"119":{"tf":1.0},"120":{"tf":2.23606797749979},"121":{"tf":1.0},"122":{"tf":1.7320508075688772},"123":{"tf":2.23606797749979},"124":{"tf":2.23606797749979},"161":{"tf":1.0},"162":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"2":{"tf":1.0},"201":{"tf":2.0},"258":{"tf":1.0},"260":{"tf":1.4142135623730951},"261":{"tf":1.7320508075688772},"262":{"tf":1.4142135623730951},"263":{"tf":1.0},"274":{"tf":1.7320508075688772},"278":{"tf":1.0},"3":{"tf":1.0},"45":{"tf":1.0},"8":{"tf":1.0}},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"73":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"h":{"df":1,"docs":{"252":{"tf":1.0}}},"x":{"c":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{".":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":2,"docs":{"28":{"tf":1.0},"29":{"tf":1.4142135623730951}}}}}},"df":1,"docs":{"29":{"tf":1.4142135623730951}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"73":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"73":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}}}}}}}}},"’":{"df":3,"docs":{"187":{"tf":1.0},"188":{"tf":1.0},"190":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"p":{"df":0,"docs":{},"l":{"df":3,"docs":{"2":{"tf":1.0},"64":{"tf":1.4142135623730951},"7":{"tf":1.0}}}},"s":{"df":1,"docs":{"204":{"tf":2.8284271247461903}},"g":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":1,"docs":{"106":{"tf":1.0}},"s":{":":{":":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"106":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"u":{"c":{"df":0,"docs":{},"h":{"df":12,"docs":{"103":{"tf":1.0},"104":{"tf":1.0},"171":{"tf":1.0},"175":{"tf":1.0},"231":{"tf":1.0},"238":{"tf":1.4142135623730951},"241":{"tf":1.0},"4":{"tf":1.0},"50":{"tf":1.0},"60":{"tf":1.0},"64":{"tf":1.0},"78":{"tf":1.0}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":4,"docs":{"174":{"tf":1.0},"22":{"tf":1.0},"255":{"tf":1.7320508075688772},"99":{"tf":1.0}},"p":{"df":0,"docs":{},"l":{"df":13,"docs":{"118":{"tf":1.0},"163":{"tf":1.0},"164":{"tf":1.0},"172":{"tf":1.4142135623730951},"174":{"tf":1.0},"204":{"tf":1.0},"213":{"tf":1.0},"232":{"tf":1.7320508075688772},"237":{"tf":1.0},"252":{"tf":1.0},"255":{"tf":1.0},"276":{"tf":1.0},"98":{"tf":1.0}}}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":1,"docs":{"83":{"tf":1.0}}}}}},"y":{"c":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"60":{"tf":2.23606797749979}}}},"df":0,"docs":{}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"61":{"tf":1.0}}}}}}}},"n":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"242":{"tf":1.0}}}},"m":{"df":0,"docs":{},"e":{"df":40,"docs":{"100":{"tf":1.0},"104":{"tf":1.0},"105":{"tf":1.4142135623730951},"108":{"tf":1.4142135623730951},"109":{"tf":1.4142135623730951},"112":{"tf":2.23606797749979},"114":{"tf":1.7320508075688772},"117":{"tf":1.0},"165":{"tf":1.0},"167":{"tf":1.0},"174":{"tf":1.0},"175":{"tf":1.4142135623730951},"200":{"tf":1.4142135623730951},"222":{"tf":1.0},"223":{"tf":1.0},"225":{"tf":1.0},"258":{"tf":1.0},"268":{"tf":1.7320508075688772},"269":{"tf":1.0},"277":{"tf":1.0},"28":{"tf":1.0},"282":{"tf":1.0},"33":{"tf":1.0},"37":{"tf":1.0},"39":{"tf":1.0},"60":{"tf":1.4142135623730951},"61":{"tf":1.0},"69":{"tf":1.0},"70":{"tf":1.4142135623730951},"72":{"tf":1.4142135623730951},"73":{"tf":1.0},"91":{"tf":2.0},"92":{"tf":1.0},"93":{"tf":2.0},"94":{"tf":1.0},"95":{"tf":1.0},"96":{"tf":1.4142135623730951},"97":{"tf":1.0},"98":{"tf":1.7320508075688772},"99":{"tf":2.23606797749979}},"s":{"df":0,"docs":{},"p":{"a":{"c":{"df":2,"docs":{"112":{"tf":1.0},"172":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"r":{"df":0,"docs":{},"r":{"df":1,"docs":{"286":{"tf":1.4142135623730951}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":6,"docs":{"0":{"tf":1.0},"11":{"tf":1.7320508075688772},"13":{"tf":1.4142135623730951},"19":{"tf":1.4142135623730951},"37":{"tf":2.23606797749979},"45":{"tf":1.0}},"e":{"/":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"37":{"tf":1.0}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"u":{"df":0,"docs":{},"r":{"df":2,"docs":{"154":{"tf":1.0},"155":{"tf":1.0}}}}},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":9,"docs":{"109":{"tf":1.4142135623730951},"205":{"tf":1.4142135623730951},"28":{"tf":1.0},"283":{"tf":1.0},"31":{"tf":1.0},"33":{"tf":1.0},"36":{"tf":1.0},"40":{"tf":1.0},"73":{"tf":2.449489742783178}}}}}},"d":{"df":0,"docs":{},"k":{"_":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"=":{"\"":{"$":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"k":{"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":0,"docs":{},"s":{"/":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"37":{"tf":1.0}}}}}}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"=":{"\"":{"$":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"/":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"/":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"/":{"df":0,"docs":{},"s":{"d":{"df":0,"docs":{},"k":{"/":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"k":{"/":{"2":{"5":{".":{"2":{".":{"9":{"5":{"1":{"9":{"6":{"5":{"3":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":2,"docs":{"12":{"tf":2.449489742783178},"37":{"tf":1.4142135623730951}}}},"df":2,"docs":{"20":{"tf":1.0},"22":{"tf":1.0}},"e":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":18,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"124":{"tf":1.0},"13":{"tf":1.0},"197":{"tf":1.0},"198":{"tf":1.0},"22":{"tf":1.0},"226":{"tf":1.4142135623730951},"231":{"tf":1.0},"241":{"tf":1.0},"242":{"tf":1.0},"25":{"tf":1.0},"264":{"tf":1.0},"27":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0},"67":{"tf":1.0},"81":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":3,"docs":{"124":{"tf":1.0},"262":{"tf":1.0},"43":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"k":{"df":0,"docs":{},"o":{"df":1,"docs":{"190":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"d":{"df":76,"docs":{"103":{"tf":1.0},"104":{"tf":1.7320508075688772},"105":{"tf":1.7320508075688772},"106":{"tf":1.0},"108":{"tf":1.4142135623730951},"109":{"tf":1.7320508075688772},"11":{"tf":1.0},"111":{"tf":1.0},"115":{"tf":1.4142135623730951},"116":{"tf":1.0},"122":{"tf":1.4142135623730951},"123":{"tf":1.0},"124":{"tf":2.23606797749979},"126":{"tf":1.0},"128":{"tf":1.0},"14":{"tf":1.4142135623730951},"140":{"tf":1.0},"145":{"tf":1.7320508075688772},"165":{"tf":1.0},"167":{"tf":1.4142135623730951},"17":{"tf":1.0},"171":{"tf":1.0},"197":{"tf":2.0},"200":{"tf":1.0},"21":{"tf":1.4142135623730951},"22":{"tf":1.4142135623730951},"220":{"tf":1.7320508075688772},"224":{"tf":1.7320508075688772},"225":{"tf":1.4142135623730951},"226":{"tf":1.0},"227":{"tf":1.0},"228":{"tf":1.0},"229":{"tf":2.23606797749979},"232":{"tf":1.4142135623730951},"233":{"tf":2.0},"234":{"tf":1.4142135623730951},"239":{"tf":1.0},"241":{"tf":1.7320508075688772},"242":{"tf":2.449489742783178},"249":{"tf":1.0},"25":{"tf":1.0},"250":{"tf":1.0},"256":{"tf":1.0},"258":{"tf":1.0},"26":{"tf":1.4142135623730951},"267":{"tf":1.0},"276":{"tf":1.0},"279":{"tf":1.0},"281":{"tf":1.0},"284":{"tf":1.0},"31":{"tf":1.0},"32":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.4142135623730951},"40":{"tf":1.0},"43":{"tf":1.0},"44":{"tf":1.0},"45":{"tf":1.7320508075688772},"46":{"tf":1.7320508075688772},"58":{"tf":1.7320508075688772},"59":{"tf":1.0},"6":{"tf":1.0},"61":{"tf":1.7320508075688772},"62":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"68":{"tf":1.0},"7":{"tf":1.4142135623730951},"73":{"tf":1.7320508075688772},"79":{"tf":1.4142135623730951},"81":{"tf":1.7320508075688772},"84":{"tf":1.7320508075688772},"86":{"tf":1.0},"89":{"tf":1.0},"9":{"tf":1.0},"90":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"g":{"df":3,"docs":{"166":{"tf":1.4142135623730951},"180":{"tf":1.0},"210":{"tf":1.7320508075688772}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":5,"docs":{"29":{"tf":1.0},"30":{"tf":1.0},"34":{"tf":1.0},"35":{"tf":1.0},"61":{"tf":1.0}}}}},"df":0,"docs":{}}}},"t":{".":{"df":0,"docs":{},"j":{"a":{"df":0,"docs":{},"v":{"a":{".":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{".":{"df":0,"docs":{},"j":{"df":0,"docs":{},"n":{"a":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":9,"docs":{"133":{"tf":1.0},"179":{"tf":1.0},"183":{"tf":1.0},"189":{"tf":1.0},"190":{"tf":1.0},"201":{"tf":1.0},"233":{"tf":2.0},"242":{"tf":1.0},"245":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"242":{"tf":1.0}}}}}}}}}}}},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"l":{"df":3,"docs":{"169":{"tf":1.7320508075688772},"170":{"tf":1.7320508075688772},"171":{"tf":2.23606797749979}}}},"df":0,"docs":{}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"283":{"tf":1.0},"83":{"tf":1.0}}}}},"w":{"df":99,"docs":{"100":{"tf":1.0},"101":{"tf":1.4142135623730951},"102":{"tf":1.4142135623730951},"103":{"tf":1.0},"104":{"tf":1.4142135623730951},"105":{"tf":1.7320508075688772},"106":{"tf":1.0},"107":{"tf":1.0},"108":{"tf":2.0},"109":{"tf":1.7320508075688772},"110":{"tf":1.0},"111":{"tf":1.0},"112":{"tf":1.0},"113":{"tf":1.0},"114":{"tf":1.0},"115":{"tf":1.0},"116":{"tf":1.0},"117":{"tf":1.0},"119":{"tf":1.4142135623730951},"120":{"tf":1.0},"122":{"tf":1.0},"123":{"tf":2.23606797749979},"124":{"tf":2.0},"134":{"tf":1.0},"141":{"tf":1.0},"145":{"tf":1.4142135623730951},"158":{"tf":1.4142135623730951},"161":{"tf":1.0},"166":{"tf":1.0},"167":{"tf":1.7320508075688772},"170":{"tf":1.0},"171":{"tf":1.7320508075688772},"172":{"tf":1.4142135623730951},"189":{"tf":1.4142135623730951},"196":{"tf":2.0},"197":{"tf":1.0},"198":{"tf":1.7320508075688772},"199":{"tf":1.4142135623730951},"20":{"tf":1.0},"200":{"tf":3.3166247903554},"203":{"tf":1.0},"208":{"tf":1.0},"212":{"tf":1.0},"225":{"tf":1.0},"23":{"tf":1.4142135623730951},"233":{"tf":1.4142135623730951},"242":{"tf":1.0},"246":{"tf":1.0},"249":{"tf":1.4142135623730951},"263":{"tf":1.0},"268":{"tf":2.23606797749979},"269":{"tf":2.0},"274":{"tf":1.4142135623730951},"278":{"tf":1.0},"281":{"tf":2.0},"282":{"tf":2.0},"283":{"tf":1.0},"284":{"tf":1.0},"31":{"tf":1.0},"32":{"tf":1.4142135623730951},"36":{"tf":1.0},"41":{"tf":1.0},"45":{"tf":1.0},"6":{"tf":1.0},"60":{"tf":1.4142135623730951},"64":{"tf":2.23606797749979},"68":{"tf":2.23606797749979},"69":{"tf":2.449489742783178},"7":{"tf":2.23606797749979},"70":{"tf":1.0},"71":{"tf":1.0},"72":{"tf":1.0},"73":{"tf":2.0},"74":{"tf":1.0},"75":{"tf":1.7320508075688772},"76":{"tf":1.4142135623730951},"77":{"tf":1.4142135623730951},"78":{"tf":1.4142135623730951},"79":{"tf":1.4142135623730951},"80":{"tf":1.4142135623730951},"81":{"tf":2.0},"82":{"tf":1.4142135623730951},"83":{"tf":1.4142135623730951},"84":{"tf":1.4142135623730951},"85":{"tf":1.4142135623730951},"86":{"tf":1.4142135623730951},"87":{"tf":1.4142135623730951},"88":{"tf":1.7320508075688772},"89":{"tf":1.4142135623730951},"90":{"tf":1.4142135623730951},"91":{"tf":1.0},"92":{"tf":1.0},"93":{"tf":1.0},"94":{"tf":1.0},"95":{"tf":1.0},"96":{"tf":1.0},"97":{"tf":1.0},"98":{"tf":1.0},"99":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":7,"docs":{"163":{"tf":1.0},"165":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"245":{"tf":1.0},"263":{"tf":1.0}}}},"l":{"df":0,"docs":{},"i":{"df":2,"docs":{"119":{"tf":1.0},"37":{"tf":1.0}}}}},"x":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"]":{".":{"0":{"a":{"1":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}},"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"_":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":0,"docs":{},"e":{"d":{"_":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"242":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":14,"docs":{"103":{"tf":1.4142135623730951},"108":{"tf":1.0},"109":{"tf":1.0},"119":{"tf":1.0},"14":{"tf":1.0},"152":{"tf":1.0},"227":{"tf":1.0},"233":{"tf":1.0},"242":{"tf":1.4142135623730951},"249":{"tf":1.0},"250":{"tf":1.0},"40":{"tf":1.0},"6":{"tf":1.0},"74":{"tf":1.0}}}}},"i":{"c":{"df":0,"docs":{},"e":{"df":2,"docs":{"103":{"tf":1.4142135623730951},"46":{"tf":1.0}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":7,"docs":{"119":{"tf":2.449489742783178},"266":{"tf":2.449489742783178},"271":{"tf":1.7320508075688772},"272":{"tf":2.23606797749979},"274":{"tf":1.0},"40":{"tf":2.0},"74":{"tf":1.4142135623730951}}},"y":{".":{"df":0,"docs":{},"j":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"271":{"tf":1.0}}}}}},"y":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":1,"docs":{"119":{"tf":1.0}}}}}},"/":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":1,"docs":{"271":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"m":{"b":{"df":0,"docs":{},"u":{"df":17,"docs":{"162":{"tf":1.0},"167":{"tf":1.7320508075688772},"172":{"tf":1.0},"174":{"tf":1.0},"178":{"tf":1.0},"180":{"tf":1.0},"182":{"tf":1.0},"184":{"tf":1.0},"190":{"tf":1.4142135623730951},"194":{"tf":2.23606797749979},"197":{"tf":2.0},"198":{"tf":2.449489742783178},"199":{"tf":1.0},"200":{"tf":3.3166247903554},"271":{"tf":1.0},"274":{"tf":1.4142135623730951},"284":{"tf":1.0}},"s":{".":{"df":0,"docs":{},"u":{"d":{"df":0,"docs":{},"l":{"df":1,"docs":{"200":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"n":{"df":0,"docs":{},"j":{"a":{"df":1,"docs":{"11":{"tf":2.0}}},"df":0,"docs":{}}}},"o":{"d":{"df":0,"docs":{},"e":{"df":1,"docs":{"86":{"tf":1.0}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"75":{"tf":1.0}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"123":{"tf":1.0}}}}},"n":{"df":16,"docs":{"122":{"tf":1.0},"13":{"tf":1.0},"167":{"tf":1.0},"184":{"tf":1.0},"188":{"tf":1.0},"189":{"tf":1.4142135623730951},"200":{"tf":1.0},"201":{"tf":2.0},"226":{"tf":1.4142135623730951},"241":{"tf":1.4142135623730951},"250":{"tf":1.0},"40":{"tf":1.4142135623730951},"45":{"tf":1.0},"46":{"tf":1.0},"58":{"tf":1.4142135623730951},"84":{"tf":1.4142135623730951}},"e":{"df":3,"docs":{"180":{"tf":1.0},"233":{"tf":1.0},"242":{"tf":1.4142135623730951}}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"df":1,"docs":{"116":{"tf":1.0}}}}}}}},"r":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"l":{"df":12,"docs":{"104":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"125":{"tf":1.0},"133":{"tf":1.0},"156":{"tf":1.0},"19":{"tf":1.0},"200":{"tf":1.0},"205":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"64":{"tf":1.0}}}},"df":2,"docs":{"200":{"tf":1.0},"201":{"tf":1.0}}}},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":2,"docs":{"18":{"tf":1.0},"201":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":42,"docs":{"105":{"tf":1.0},"106":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"11":{"tf":1.4142135623730951},"115":{"tf":1.0},"117":{"tf":1.0},"12":{"tf":1.0},"123":{"tf":1.0},"124":{"tf":1.4142135623730951},"13":{"tf":1.0},"14":{"tf":1.4142135623730951},"145":{"tf":1.4142135623730951},"18":{"tf":1.0},"198":{"tf":1.0},"20":{"tf":1.0},"200":{"tf":1.4142135623730951},"205":{"tf":1.0},"208":{"tf":1.0},"214":{"tf":1.0},"215":{"tf":1.0},"226":{"tf":1.4142135623730951},"231":{"tf":1.7320508075688772},"242":{"tf":1.4142135623730951},"243":{"tf":1.0},"245":{"tf":1.0},"258":{"tf":1.0},"264":{"tf":1.4142135623730951},"27":{"tf":1.0},"274":{"tf":1.0},"278":{"tf":1.0},"280":{"tf":1.0},"282":{"tf":1.4142135623730951},"31":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"62":{"tf":1.4142135623730951},"7":{"tf":1.0},"78":{"tf":1.0},"79":{"tf":1.4142135623730951},"81":{"tf":1.0},"83":{"tf":1.0}}},"h":{"df":7,"docs":{"163":{"tf":1.0},"169":{"tf":1.4142135623730951},"179":{"tf":1.0},"180":{"tf":1.0},"182":{"tf":1.4142135623730951},"62":{"tf":1.0},"73":{"tf":1.4142135623730951}}},"i":{"c":{"df":3,"docs":{"166":{"tf":1.0},"274":{"tf":1.0},"282":{"tf":1.0}}},"df":0,"docs":{},"f":{"df":1,"docs":{"274":{"tf":1.0}}}}},"w":{"df":19,"docs":{"105":{"tf":1.4142135623730951},"142":{"tf":1.0},"152":{"tf":1.0},"180":{"tf":1.0},"20":{"tf":1.0},"212":{"tf":1.0},"226":{"tf":1.7320508075688772},"233":{"tf":1.0},"241":{"tf":1.0},"242":{"tf":1.0},"257":{"tf":1.7320508075688772},"31":{"tf":1.0},"36":{"tf":1.0},"40":{"tf":1.0},"60":{"tf":1.4142135623730951},"62":{"tf":1.4142135623730951},"74":{"tf":1.0},"86":{"tf":1.0},"90":{"tf":1.0}}}},"s":{"df":0,"docs":{},"k":{"df":1,"docs":{"37":{"tf":1.0}}},"p":{"df":0,"docs":{},"r":{"df":1,"docs":{"67":{"tf":1.0}}}},"s":{"_":{"3":{"_":{"9":{"0":{"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"m":{"df":1,"docs":{"280":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"282":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"_":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"282":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":1,"docs":{"281":{"tf":1.0}}}}},"df":7,"docs":{"11":{"tf":1.7320508075688772},"278":{"tf":2.8284271247461903},"279":{"tf":2.6457513110645907},"280":{"tf":2.8284271247461903},"281":{"tf":1.7320508075688772},"282":{"tf":3.3166247903554},"67":{"tf":1.0}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":1,"docs":{"62":{"tf":2.8284271247461903}}}},"m":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":26,"docs":{"10":{"tf":1.4142135623730951},"115":{"tf":1.0},"13":{"tf":1.0},"161":{"tf":1.0},"193":{"tf":1.0},"197":{"tf":1.0},"20":{"tf":1.4142135623730951},"204":{"tf":1.0},"205":{"tf":1.0},"207":{"tf":1.0},"208":{"tf":1.4142135623730951},"213":{"tf":1.0},"214":{"tf":1.0},"215":{"tf":1.0},"216":{"tf":1.0},"22":{"tf":1.7320508075688772},"220":{"tf":1.0},"222":{"tf":1.0},"225":{"tf":1.4142135623730951},"231":{"tf":1.0},"268":{"tf":1.0},"269":{"tf":1.0},"270":{"tf":1.0},"271":{"tf":1.0},"279":{"tf":1.0},"37":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"22":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"64":{"tf":1.0}}}}}}}},"y":{"df":0,"docs":{},"g":{"a":{"df":0,"docs":{},"r":{"d":{"'":{"df":1,"docs":{"137":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"o":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":2,"docs":{"254":{"tf":1.0},"283":{"tf":2.8284271247461903}}}}}},"b":{"df":0,"docs":{},"j":{".":{"d":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"129":{"tf":1.0}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"s":{"df":1,"docs":{"129":{"tf":1.0}}}}}}}}}}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"129":{"tf":1.0}},"e":{"c":{"df":0,"docs":{},"t":{"df":10,"docs":{"128":{"tf":2.8284271247461903},"129":{"tf":2.0},"130":{"tf":2.0},"131":{"tf":2.449489742783178},"132":{"tf":2.449489742783178},"133":{"tf":1.0},"245":{"tf":1.0},"268":{"tf":1.0},"283":{"tf":1.4142135623730951},"99":{"tf":1.0}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":9,"docs":{"203":{"tf":1.0},"204":{"tf":1.7320508075688772},"205":{"tf":1.0},"206":{"tf":1.0},"210":{"tf":1.0},"214":{"tf":1.7320508075688772},"215":{"tf":1.0},"216":{"tf":1.7320508075688772},"227":{"tf":1.0}}}}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":5,"docs":{"161":{"tf":1.0},"2":{"tf":1.0},"228":{"tf":1.0},"277":{"tf":1.0},"283":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":2,"docs":{"201":{"tf":1.0},"225":{"tf":1.0}},"s":{"df":2,"docs":{"124":{"tf":1.0},"201":{"tf":1.0}}}}}}}},"c":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":3,"docs":{"207":{"tf":1.0},"226":{"tf":1.0},"43":{"tf":1.0}},"r":{"df":2,"docs":{"31":{"tf":1.0},"36":{"tf":1.0}}}}}},"df":0,"docs":{}},"d":{"d":{"df":2,"docs":{"200":{"tf":1.0},"221":{"tf":1.0}}},"df":0,"docs":{}},"df":3,"docs":{"11":{"tf":1.0},"29":{"tf":1.0},"34":{"tf":1.0}},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":3,"docs":{"201":{"tf":1.0},"274":{"tf":1.0},"283":{"tf":1.0}}}},"i":{"c":{"df":0,"docs":{},"i":{"df":3,"docs":{"261":{"tf":1.0},"262":{"tf":1.0},"263":{"tf":1.0}}}},"df":0,"docs":{}}}},"k":{"a":{"df":0,"docs":{},"y":{"df":2,"docs":{"130":{"tf":1.0},"82":{"tf":1.0}}}},"df":2,"docs":{"226":{"tf":1.0},"242":{"tf":1.0}}},"l":{"d":{"df":9,"docs":{"109":{"tf":1.0},"203":{"tf":1.0},"209":{"tf":1.0},"212":{"tf":1.4142135623730951},"231":{"tf":1.0},"258":{"tf":1.0},"279":{"tf":1.0},"37":{"tf":1.0},"86":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":5,"docs":{"210":{"tf":1.0},"225":{"tf":1.0},"245":{"tf":1.0},"78":{"tf":1.0},"79":{"tf":1.0}}}}},"df":0,"docs":{}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"145":{"tf":1.0},"21":{"tf":1.0}}}}},"n":{"b":{"df":0,"docs":{},"o":{"a":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"184":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"c":{"df":18,"docs":{"106":{"tf":1.0},"11":{"tf":1.0},"119":{"tf":1.4142135623730951},"124":{"tf":1.0},"128":{"tf":1.0},"132":{"tf":1.0},"14":{"tf":1.0},"145":{"tf":1.0},"25":{"tf":1.0},"258":{"tf":1.0},"268":{"tf":1.0},"270":{"tf":1.0},"277":{"tf":1.0},"30":{"tf":1.0},"35":{"tf":1.0},"6":{"tf":1.0},"62":{"tf":1.0},"64":{"tf":1.0}}},"df":34,"docs":{"106":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"116":{"tf":1.0},"122":{"tf":1.0},"125":{"tf":1.0},"126":{"tf":1.0},"131":{"tf":1.4142135623730951},"132":{"tf":1.0},"138":{"tf":1.0},"163":{"tf":1.7320508075688772},"167":{"tf":1.0},"171":{"tf":1.0},"196":{"tf":1.0},"203":{"tf":1.0},"212":{"tf":1.0},"228":{"tf":1.0},"229":{"tf":1.0},"232":{"tf":1.0},"236":{"tf":1.0},"249":{"tf":1.0},"257":{"tf":1.0},"258":{"tf":1.7320508075688772},"274":{"tf":1.0},"276":{"tf":1.0},"282":{"tf":1.0},"45":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.7320508075688772},"7":{"tf":1.0},"79":{"tf":1.4142135623730951},"82":{"tf":1.4142135623730951},"86":{"tf":1.0}},"g":{"df":0,"docs":{},"o":{"df":1,"docs":{"64":{"tf":1.0}}}},"t":{"df":0,"docs":{},"o":{"df":1,"docs":{"115":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"m":{"df":1,"docs":{"154":{"tf":1.0}}}},"p":{"a":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":2,"docs":{"229":{"tf":1.0},"242":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":20,"docs":{"107":{"tf":1.0},"109":{"tf":1.0},"111":{"tf":1.0},"124":{"tf":1.0},"151":{"tf":1.4142135623730951},"152":{"tf":1.4142135623730951},"154":{"tf":2.0},"155":{"tf":2.0},"156":{"tf":1.0},"158":{"tf":1.4142135623730951},"25":{"tf":1.0},"274":{"tf":1.4142135623730951},"280":{"tf":1.0},"286":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"40":{"tf":1.0},"7":{"tf":1.4142135623730951},"73":{"tf":1.0},"81":{"tf":1.0}},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"l":{"df":1,"docs":{"11":{"tf":1.0}}}}}},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"f":{"df":0,"docs":{},"x":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":1,"docs":{"283":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"df":7,"docs":{"151":{"tf":1.0},"152":{"tf":1.0},"156":{"tf":1.4142135623730951},"201":{"tf":1.4142135623730951},"225":{"tf":1.0},"245":{"tf":1.0},"37":{"tf":1.0}}}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":1,"docs":{"170":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"233":{"tf":1.0}}}}}}},"t":{"df":3,"docs":{"233":{"tf":1.4142135623730951},"280":{"tf":1.0},"283":{"tf":1.0}},"i":{"df":0,"docs":{},"m":{"df":2,"docs":{"220":{"tf":1.0},"282":{"tf":1.0}}},"o":{"df":0,"docs":{},"n":{"<":{"df":0,"docs":{},"j":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":1,"docs":{"242":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"242":{"tf":1.0}}}}},"v":{"df":0,"docs":{},"e":{"c":{"<":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"242":{"tf":1.4142135623730951}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":36,"docs":{"103":{"tf":1.4142135623730951},"137":{"tf":1.4142135623730951},"138":{"tf":1.0},"142":{"tf":2.0},"143":{"tf":1.0},"144":{"tf":1.4142135623730951},"145":{"tf":1.4142135623730951},"146":{"tf":1.7320508075688772},"151":{"tf":1.4142135623730951},"153":{"tf":1.4142135623730951},"158":{"tf":1.0},"159":{"tf":1.0},"163":{"tf":1.4142135623730951},"164":{"tf":1.4142135623730951},"168":{"tf":1.4142135623730951},"169":{"tf":1.4142135623730951},"170":{"tf":1.4142135623730951},"171":{"tf":1.4142135623730951},"172":{"tf":1.7320508075688772},"174":{"tf":1.4142135623730951},"179":{"tf":1.4142135623730951},"180":{"tf":2.23606797749979},"181":{"tf":1.4142135623730951},"185":{"tf":1.0},"187":{"tf":1.0},"195":{"tf":1.4142135623730951},"196":{"tf":2.23606797749979},"197":{"tf":1.7320508075688772},"198":{"tf":2.23606797749979},"199":{"tf":1.7320508075688772},"207":{"tf":2.23606797749979},"208":{"tf":1.4142135623730951},"211":{"tf":1.4142135623730951},"249":{"tf":1.0},"73":{"tf":1.0},"74":{"tf":1.4142135623730951}}}}}}},"r":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":14,"docs":{"105":{"tf":1.0},"106":{"tf":1.0},"115":{"tf":1.0},"170":{"tf":1.4142135623730951},"22":{"tf":1.4142135623730951},"223":{"tf":1.0},"224":{"tf":1.0},"246":{"tf":1.0},"282":{"tf":1.0},"286":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0},"59":{"tf":1.0},"69":{"tf":1.0}}}}},"df":0,"docs":{},"g":{".":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{".":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"$":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"112":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":1,"docs":{"274":{"tf":1.4142135623730951}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":2,"docs":{"115":{"tf":1.0},"231":{"tf":1.0}}}}}}},"s":{"df":1,"docs":{"133":{"tf":1.0}},"x":{"df":1,"docs":{"17":{"tf":1.4142135623730951}}}},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"'":{"df":1,"docs":{"175":{"tf":1.0}}},"df":2,"docs":{"116":{"tf":1.0},"226":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"242":{"tf":1.0}}}}}}},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":4,"docs":{"224":{"tf":1.4142135623730951},"226":{"tf":1.0},"88":{"tf":1.0},"98":{"tf":1.0}}}}}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"v":{"df":3,"docs":{"166":{"tf":1.0},"170":{"tf":1.0},"278":{"tf":1.0}}}}}}},"t":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":6,"docs":{"138":{"tf":1.4142135623730951},"143":{"tf":1.4142135623730951},"152":{"tf":1.4142135623730951},"164":{"tf":1.4142135623730951},"180":{"tf":1.4142135623730951},"208":{"tf":1.4142135623730951}}}}},"df":30,"docs":{"103":{"tf":1.0},"105":{"tf":1.0},"107":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"116":{"tf":1.0},"171":{"tf":1.0},"18":{"tf":1.0},"190":{"tf":1.0},"198":{"tf":1.0},"200":{"tf":1.4142135623730951},"201":{"tf":1.0},"204":{"tf":1.4142135623730951},"233":{"tf":1.4142135623730951},"249":{"tf":1.0},"252":{"tf":1.0},"274":{"tf":1.7320508075688772},"279":{"tf":1.4142135623730951},"283":{"tf":1.4142135623730951},"29":{"tf":1.0},"31":{"tf":1.0},"34":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"43":{"tf":1.0},"59":{"tf":1.0},"6":{"tf":1.0},"60":{"tf":1.0},"81":{"tf":1.0},"96":{"tf":1.0}},"g":{"df":0,"docs":{},"o":{"df":2,"docs":{"231":{"tf":1.0},"249":{"tf":1.0}}}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":3,"docs":{"187":{"tf":1.0},"233":{"tf":1.0},"91":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":6,"docs":{"174":{"tf":1.0},"252":{"tf":1.0},"280":{"tf":1.0},"282":{"tf":1.0},"37":{"tf":1.0},"62":{"tf":2.0}}}}},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":1,"docs":{"61":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"156":{"tf":1.0}}},"df":0,"docs":{}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"l":{"df":7,"docs":{"102":{"tf":1.0},"105":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"218":{"tf":1.0},"222":{"tf":1.0},"244":{"tf":1.0}}}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":1,"docs":{"175":{"tf":1.0}}}}},"df":21,"docs":{"101":{"tf":1.0},"103":{"tf":1.0},"104":{"tf":1.0},"105":{"tf":1.0},"106":{"tf":1.7320508075688772},"11":{"tf":1.0},"116":{"tf":1.0},"126":{"tf":1.0},"140":{"tf":1.0},"172":{"tf":1.0},"193":{"tf":1.0},"204":{"tf":1.4142135623730951},"208":{"tf":1.0},"212":{"tf":1.0},"214":{"tf":1.0},"215":{"tf":1.0},"216":{"tf":1.4142135623730951},"217":{"tf":1.4142135623730951},"228":{"tf":1.0},"274":{"tf":1.0},"43":{"tf":1.0}},"h":{"df":0,"docs":{},"e":{"a":{"d":{"df":2,"docs":{"115":{"tf":1.0},"59":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"l":{"a":{"df":0,"docs":{},"p":{"df":1,"docs":{"198":{"tf":1.0}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":1,"docs":{"46":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"i":{"d":{"df":3,"docs":{"123":{"tf":1.0},"124":{"tf":1.0},"22":{"tf":1.0}}},"df":0,"docs":{}}},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":16,"docs":{"174":{"tf":1.0},"219":{"tf":1.4142135623730951},"227":{"tf":1.0},"243":{"tf":2.0},"244":{"tf":1.0},"245":{"tf":1.0},"246":{"tf":1.0},"247":{"tf":1.0},"248":{"tf":1.0},"249":{"tf":1.0},"250":{"tf":1.0},"274":{"tf":1.0},"41":{"tf":1.0},"93":{"tf":1.4142135623730951},"96":{"tf":1.4142135623730951},"99":{"tf":1.4142135623730951}}}}}},"w":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"179":{"tf":1.0}}}}}}}}},"w":{"df":0,"docs":{},"n":{"df":3,"docs":{"201":{"tf":1.0},"276":{"tf":1.4142135623730951},"277":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"121":{"tf":1.0},"276":{"tf":1.0}},"s":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":1,"docs":{"276":{"tf":1.0}}}}}}}}}}},"p":{"a":{"c":{"df":0,"docs":{},"k":{"a":{"df":0,"docs":{},"g":{"df":40,"docs":{"100":{"tf":1.0},"103":{"tf":1.0},"108":{"tf":1.0},"112":{"tf":2.0},"113":{"tf":1.4142135623730951},"12":{"tf":1.0},"134":{"tf":1.0},"160":{"tf":1.4142135623730951},"163":{"tf":2.0},"164":{"tf":1.0},"165":{"tf":1.0},"166":{"tf":1.0},"167":{"tf":2.449489742783178},"169":{"tf":1.0},"170":{"tf":1.4142135623730951},"171":{"tf":2.6457513110645907},"172":{"tf":2.8284271247461903},"174":{"tf":2.6457513110645907},"219":{"tf":1.4142135623730951},"220":{"tf":1.0},"222":{"tf":1.0},"225":{"tf":1.0},"23":{"tf":2.0},"25":{"tf":3.0},"251":{"tf":2.23606797749979},"252":{"tf":1.0},"253":{"tf":2.8284271247461903},"26":{"tf":2.0},"269":{"tf":1.0},"271":{"tf":1.0},"274":{"tf":1.4142135623730951},"31":{"tf":2.449489742783178},"32":{"tf":2.0},"36":{"tf":2.449489742783178},"37":{"tf":1.0},"45":{"tf":1.0},"53":{"tf":1.0},"73":{"tf":2.0},"74":{"tf":1.0},"99":{"tf":1.0}},"e":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"'":{"df":2,"docs":{"29":{"tf":1.0},"34":{"tf":1.0}}},"df":3,"docs":{"167":{"tf":1.7320508075688772},"251":{"tf":1.0},"253":{"tf":1.4142135623730951}}}}}}}},"=":{"\"":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{".":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{".":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"<":{"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"_":{"c":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"70":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"70":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"117":{"tf":1.0}}}}}}}}}},"df":0,"docs":{}}},"d":{"df":1,"docs":{"64":{"tf":1.0}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"s":{"df":1,"docs":{"264":{"tf":1.0}}}},"df":43,"docs":{"100":{"tf":1.0},"109":{"tf":1.0},"117":{"tf":1.0},"119":{"tf":1.0},"124":{"tf":1.0},"127":{"tf":1.0},"133":{"tf":1.0},"138":{"tf":1.0},"147":{"tf":1.0},"15":{"tf":1.0},"159":{"tf":1.0},"175":{"tf":1.0},"185":{"tf":1.0},"201":{"tf":1.0},"218":{"tf":1.0},"22":{"tf":1.0},"224":{"tf":1.0},"242":{"tf":1.0},"250":{"tf":1.0},"253":{"tf":1.0},"258":{"tf":1.0},"259":{"tf":1.0},"261":{"tf":1.0},"263":{"tf":1.0},"264":{"tf":1.0},"273":{"tf":1.0},"277":{"tf":1.0},"282":{"tf":1.0},"283":{"tf":1.0},"286":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"40":{"tf":1.0},"5":{"tf":1.0},"51":{"tf":1.0},"56":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0},"67":{"tf":1.0},"74":{"tf":1.0},"8":{"tf":1.0},"90":{"tf":1.0}}}},"i":{"df":0,"docs":{},"n":{"df":4,"docs":{"104":{"tf":1.0},"115":{"tf":1.0},"122":{"tf":1.0},"5":{"tf":1.0}}}},"n":{"df":0,"docs":{},"e":{"df":1,"docs":{"126":{"tf":1.4142135623730951}},"l":{"df":2,"docs":{"205":{"tf":1.4142135623730951},"206":{"tf":1.0}}}},"i":{"c":{"df":1,"docs":{"117":{"tf":1.0}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"6":{"tf":1.0}}}}},"df":0,"docs":{}}}},"r":{"a":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"m":{"df":1,"docs":{"61":{"tf":1.0}}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":1,"docs":{"59":{"tf":1.0}}}}}},"m":{"df":2,"docs":{"242":{"tf":1.4142135623730951},"79":{"tf":1.0}},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"283":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"61":{"tf":1.7320508075688772},"62":{"tf":1.0}}}}},"s":{"df":1,"docs":{"22":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"58":{"tf":1.0}}}}},"t":{"df":28,"docs":{"103":{"tf":1.4142135623730951},"104":{"tf":1.0},"106":{"tf":1.0},"108":{"tf":1.4142135623730951},"109":{"tf":1.4142135623730951},"113":{"tf":1.0},"114":{"tf":1.0},"161":{"tf":1.0},"165":{"tf":1.0},"171":{"tf":1.0},"190":{"tf":1.0},"200":{"tf":1.4142135623730951},"201":{"tf":1.0},"203":{"tf":1.0},"214":{"tf":1.0},"22":{"tf":1.0},"225":{"tf":1.0},"233":{"tf":1.0},"242":{"tf":1.0},"245":{"tf":1.4142135623730951},"251":{"tf":1.0},"268":{"tf":1.0},"274":{"tf":1.0},"64":{"tf":1.7320508075688772},"68":{"tf":1.4142135623730951},"79":{"tf":1.0},"80":{"tf":1.0},"84":{"tf":1.0}},"i":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":1,"docs":{"3":{"tf":1.4142135623730951}}}},"u":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"r":{"df":9,"docs":{"116":{"tf":1.0},"198":{"tf":1.0},"200":{"tf":1.0},"225":{"tf":1.0},"231":{"tf":1.0},"233":{"tf":1.4142135623730951},"236":{"tf":1.4142135623730951},"39":{"tf":1.0},"64":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"213":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":4,"docs":{"121":{"tf":1.0},"201":{"tf":1.7320508075688772},"63":{"tf":1.0},"64":{"tf":1.7320508075688772}}}}},"s":{"df":0,"docs":{},"s":{"df":20,"docs":{"105":{"tf":1.0},"106":{"tf":1.7320508075688772},"108":{"tf":1.0},"109":{"tf":1.0},"132":{"tf":1.0},"14":{"tf":1.0},"142":{"tf":1.4142135623730951},"143":{"tf":1.0},"145":{"tf":2.0},"225":{"tf":1.0},"228":{"tf":1.0},"229":{"tf":1.0},"242":{"tf":1.7320508075688772},"245":{"tf":1.4142135623730951},"262":{"tf":1.4142135623730951},"283":{"tf":1.4142135623730951},"58":{"tf":1.0},"7":{"tf":1.4142135623730951},"73":{"tf":1.0},"79":{"tf":2.0}},"i":{"df":0,"docs":{},"v":{"df":1,"docs":{"264":{"tf":1.0}}}},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"d":{"df":3,"docs":{"242":{"tf":1.0},"257":{"tf":1.0},"86":{"tf":1.0}}},"df":0,"docs":{}}}}},"t":{"df":2,"docs":{"205":{"tf":1.0},"207":{"tf":1.0}}}},"t":{"c":{"df":0,"docs":{},"h":{"df":9,"docs":{"11":{"tf":1.4142135623730951},"122":{"tf":1.4142135623730951},"123":{"tf":1.0},"124":{"tf":1.0},"262":{"tf":1.0},"270":{"tf":1.4142135623730951},"62":{"tf":1.0},"7":{"tf":2.449489742783178},"8":{"tf":1.0}}}},"df":0,"docs":{},"h":{"/":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"/":{"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"/":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"/":{"df":0,"docs":{},"o":{"df":0,"docs":{},"f":{"/":{"df":0,"docs":{},"j":{"df":0,"docs":{},"n":{"a":{"/":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"37":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"=":{"\"":{"$":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{":":{"$":{"(":{"b":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":1,"docs":{"11":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"k":{"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"/":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"/":{"a":{"a":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"h":{"6":{"4":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":1,"docs":{"37":{"tf":1.0}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"v":{"df":0,"docs":{},"m":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"/":{"d":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"37":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"x":{"8":{"6":{"_":{"6":{"4":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{},"~":{"/":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"g":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{":":{"$":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"11":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":19,"docs":{"11":{"tf":1.4142135623730951},"122":{"tf":1.0},"123":{"tf":1.0},"124":{"tf":2.6457513110645907},"166":{"tf":1.0},"18":{"tf":1.4142135623730951},"192":{"tf":1.0},"22":{"tf":1.0},"25":{"tf":1.0},"280":{"tf":1.0},"29":{"tf":1.0},"31":{"tf":1.4142135623730951},"34":{"tf":1.0},"36":{"tf":1.4142135623730951},"37":{"tf":1.4142135623730951},"45":{"tf":1.0},"60":{"tf":1.4142135623730951},"62":{"tf":1.4142135623730951},"74":{"tf":1.4142135623730951}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":1,"docs":{"247":{"tf":1.7320508075688772}}}}}}},"u":{"df":0,"docs":{},"s":{"df":2,"docs":{"180":{"tf":1.0},"183":{"tf":1.0}}}},"y":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":1,"docs":{"283":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":2,"docs":{"58":{"tf":1.7320508075688772},"69":{"tf":1.0}},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"122":{"tf":1.0},"8":{"tf":1.0}}}},"n":{"d":{"df":1,"docs":{"21":{"tf":1.0}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":2,"docs":{"138":{"tf":1.0},"60":{"tf":1.0}}}}},"r":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":2,"docs":{"170":{"tf":1.0},"242":{"tf":1.0}}}},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"62":{"tf":1.0}},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"204":{"tf":3.0}}}}}}}},"df":9,"docs":{"140":{"tf":1.4142135623730951},"145":{"tf":1.4142135623730951},"231":{"tf":1.0},"232":{"tf":1.0},"245":{"tf":1.0},"252":{"tf":1.0},"40":{"tf":1.0},"61":{"tf":1.4142135623730951},"64":{"tf":1.0}},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":17,"docs":{"111":{"tf":1.0},"145":{"tf":1.4142135623730951},"16":{"tf":1.0},"187":{"tf":1.0},"205":{"tf":1.4142135623730951},"209":{"tf":1.0},"212":{"tf":1.0},"218":{"tf":1.0},"225":{"tf":1.0},"226":{"tf":1.0},"245":{"tf":1.4142135623730951},"249":{"tf":1.0},"268":{"tf":1.0},"278":{"tf":1.0},"282":{"tf":1.0},"58":{"tf":1.0},"81":{"tf":1.0}}}}}},"h":{"a":{"df":0,"docs":{},"p":{"df":3,"docs":{"103":{"tf":1.0},"104":{"tf":1.0},"115":{"tf":1.0}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"o":{"d":{"df":3,"docs":{"145":{"tf":1.0},"226":{"tf":1.0},"245":{"tf":1.0}}},"df":0,"docs":{}}},"l":{"df":1,"docs":{"11":{"tf":1.4142135623730951}}},"m":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"273":{"tf":1.0}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"274":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":12,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"149":{"tf":1.0},"150":{"tf":1.0},"194":{"tf":1.4142135623730951},"197":{"tf":1.7320508075688772},"198":{"tf":1.0},"229":{"tf":2.0},"242":{"tf":1.4142135623730951},"249":{"tf":1.0},"283":{"tf":1.4142135623730951},"82":{"tf":1.0}},"e":{"d":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"242":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"x":{"a":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"108":{"tf":1.0},"109":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}}},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"277":{"tf":1.4142135623730951}}}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"284":{"tf":1.0}}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"s":{"df":1,"docs":{"102":{"tf":1.0}}}}}},"h":{"a":{"b":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"c":{"df":3,"docs":{"122":{"tf":1.0},"123":{"tf":1.0},"124":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":5,"docs":{"22":{"tf":1.0},"267":{"tf":1.4142135623730951},"273":{"tf":1.0},"60":{"tf":1.0},"73":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}},"i":{"c":{"df":0,"docs":{},"k":{"df":5,"docs":{"270":{"tf":1.0},"272":{"tf":1.0},"30":{"tf":1.0},"35":{"tf":1.0},"6":{"tf":1.0}}}},"df":0,"docs":{},"e":{"c":{"df":3,"docs":{"105":{"tf":1.0},"116":{"tf":1.0},"60":{"tf":1.0}}},"df":0,"docs":{}},"n":{"df":2,"docs":{"263":{"tf":1.0},"274":{"tf":1.0}}},"p":{"3":{"df":2,"docs":{"269":{"tf":1.0},"53":{"tf":1.0}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":5,"docs":{"113":{"tf":1.0},"221":{"tf":1.0},"226":{"tf":1.0},"274":{"tf":2.449489742783178},"70":{"tf":1.0}}}}}}}},"l":{"a":{"c":{"df":0,"docs":{},"e":{"df":23,"docs":{"106":{"tf":1.4142135623730951},"108":{"tf":1.0},"134":{"tf":1.0},"142":{"tf":1.0},"202":{"tf":1.4142135623730951},"218":{"tf":1.0},"221":{"tf":1.0},"232":{"tf":1.4142135623730951},"237":{"tf":1.0},"244":{"tf":1.0},"245":{"tf":1.0},"264":{"tf":2.8284271247461903},"274":{"tf":1.0},"48":{"tf":1.0},"58":{"tf":1.4142135623730951},"61":{"tf":1.4142135623730951},"62":{"tf":1.0},"70":{"tf":1.4142135623730951},"72":{"tf":1.0},"78":{"tf":1.4142135623730951},"79":{"tf":1.4142135623730951},"81":{"tf":1.0},"83":{"tf":1.0}},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"58":{"tf":1.0}}}}}},"s":{".":{"d":{"b":{"df":1,"docs":{"218":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"62":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"e":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"130":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"n":{"df":8,"docs":{"140":{"tf":1.0},"152":{"tf":1.0},"180":{"tf":1.0},"200":{"tf":1.4142135623730951},"228":{"tf":1.0},"230":{"tf":1.4142135623730951},"249":{"tf":2.23606797749979},"274":{"tf":1.0}}},"t":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":22,"docs":{"0":{"tf":1.0},"116":{"tf":1.7320508075688772},"13":{"tf":1.0},"170":{"tf":1.0},"184":{"tf":1.0},"196":{"tf":1.0},"200":{"tf":1.0},"213":{"tf":1.0},"220":{"tf":1.0},"223":{"tf":1.0},"225":{"tf":1.0},"251":{"tf":1.0},"255":{"tf":2.0},"256":{"tf":1.4142135623730951},"257":{"tf":1.0},"258":{"tf":1.0},"274":{"tf":1.0},"279":{"tf":1.0},"28":{"tf":1.0},"33":{"tf":1.0},"37":{"tf":1.4142135623730951},"69":{"tf":1.0}},"s":{";":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"13":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":11,"docs":{"111":{"tf":1.0},"120":{"tf":1.0},"134":{"tf":1.0},"200":{"tf":1.4142135623730951},"4":{"tf":1.0},"41":{"tf":1.0},"5":{"tf":1.0},"69":{"tf":1.0},"7":{"tf":1.4142135623730951},"75":{"tf":1.4142135623730951},"81":{"tf":1.0}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"252":{"tf":1.0}}}}},"u":{"df":3,"docs":{"174":{"tf":1.0},"233":{"tf":1.0},"98":{"tf":1.0}},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"65":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":18,"docs":{"104":{"tf":1.7320508075688772},"105":{"tf":1.0},"12":{"tf":1.0},"171":{"tf":1.4142135623730951},"198":{"tf":1.0},"201":{"tf":1.0},"226":{"tf":1.0},"241":{"tf":1.7320508075688772},"260":{"tf":1.0},"274":{"tf":1.4142135623730951},"29":{"tf":1.0},"34":{"tf":1.0},"37":{"tf":1.0},"5":{"tf":1.0},"51":{"tf":1.0},"64":{"tf":1.0},"73":{"tf":1.4142135623730951},"78":{"tf":1.7320508075688772}},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"116":{"tf":1.0},"128":{"tf":1.4142135623730951}}}}}}},"l":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":8,"docs":{"121":{"tf":1.0},"219":{"tf":1.0},"226":{"tf":1.0},"260":{"tf":1.0},"261":{"tf":2.449489742783178},"262":{"tf":2.449489742783178},"263":{"tf":1.0},"63":{"tf":1.0}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":6,"docs":{"145":{"tf":1.0},"184":{"tf":1.0},"214":{"tf":1.0},"215":{"tf":1.0},"226":{"tf":1.0},"233":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"t":{"df":4,"docs":{"101":{"tf":1.4142135623730951},"102":{"tf":1.0},"104":{"tf":1.0},"228":{"tf":1.4142135623730951}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"226":{"tf":1.4142135623730951}}}}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":3,"docs":{"165":{"tf":1.4142135623730951},"209":{"tf":1.4142135623730951},"62":{"tf":1.0}}}},"s":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"l":{"df":32,"docs":{"105":{"tf":1.0},"106":{"tf":1.0},"11":{"tf":1.0},"116":{"tf":1.0},"121":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0},"158":{"tf":1.0},"16":{"tf":1.0},"165":{"tf":1.0},"170":{"tf":1.0},"175":{"tf":1.0},"180":{"tf":1.0},"192":{"tf":1.0},"196":{"tf":1.0},"200":{"tf":1.0},"225":{"tf":1.0},"226":{"tf":1.0},"227":{"tf":1.0},"236":{"tf":1.0},"237":{"tf":1.4142135623730951},"242":{"tf":1.0},"25":{"tf":1.0},"263":{"tf":1.0},"27":{"tf":1.0},"278":{"tf":1.0},"43":{"tf":1.4142135623730951},"59":{"tf":1.0},"7":{"tf":1.0},"80":{"tf":1.0},"83":{"tf":1.0},"88":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"df":2,"docs":{"249":{"tf":1.0},"90":{"tf":1.4142135623730951}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"l":{"_":{"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"_":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"_":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"145":{"tf":1.4142135623730951},"146":{"tf":1.0}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":8,"docs":{"159":{"tf":1.0},"164":{"tf":1.0},"165":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"59":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0}}}}}}},"v":{"df":1,"docs":{"226":{"tf":1.0}}},"w":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"203":{"tf":1.0},"205":{"tf":1.7320508075688772}}}}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"c":{"df":14,"docs":{"101":{"tf":1.0},"116":{"tf":1.0},"123":{"tf":1.4142135623730951},"128":{"tf":1.0},"140":{"tf":1.0},"170":{"tf":1.0},"226":{"tf":1.0},"232":{"tf":1.0},"274":{"tf":1.0},"276":{"tf":1.0},"43":{"tf":1.0},"45":{"tf":1.0},"75":{"tf":1.0},"8":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"m":{"a":{"df":2,"docs":{"264":{"tf":3.1622776601683795},"81":{"tf":1.0}}},"df":0,"docs":{}}}},"df":15,"docs":{"118":{"tf":1.7320508075688772},"119":{"tf":2.6457513110645907},"124":{"tf":1.0},"21":{"tf":1.4142135623730951},"263":{"tf":1.4142135623730951},"268":{"tf":2.6457513110645907},"269":{"tf":1.4142135623730951},"270":{"tf":1.4142135623730951},"274":{"tf":1.4142135623730951},"280":{"tf":1.0},"282":{"tf":1.0},"39":{"tf":1.7320508075688772},"6":{"tf":1.0},"64":{"tf":1.4142135623730951},"7":{"tf":3.1622776601683795}},"e":{"c":{"df":0,"docs":{},"e":{"d":{"df":2,"docs":{"31":{"tf":1.0},"36":{"tf":1.0}}},"df":0,"docs":{}},"l":{"df":0,"docs":{},"u":{"d":{"df":2,"docs":{"197":{"tf":1.0},"198":{"tf":1.0}}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"213":{"tf":1.0}}}},"df":0,"docs":{}}},"df":15,"docs":{"103":{"tf":1.0},"161":{"tf":1.0},"163":{"tf":1.7320508075688772},"164":{"tf":1.0},"167":{"tf":1.7320508075688772},"171":{"tf":1.4142135623730951},"172":{"tf":1.4142135623730951},"174":{"tf":1.0},"175":{"tf":1.0},"184":{"tf":1.0},"189":{"tf":1.0},"236":{"tf":1.0},"253":{"tf":1.0},"274":{"tf":1.0},"280":{"tf":1.0}},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":9,"docs":{"170":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.4142135623730951},"229":{"tf":1.0},"232":{"tf":1.4142135623730951},"64":{"tf":1.0},"79":{"tf":1.0},"82":{"tf":1.0},"83":{"tf":1.0}}}},"i":{"df":0,"docs":{},"x":{")":{"/":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"y":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"3":{"df":1,"docs":{"11":{"tf":1.0}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"y":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"@":{"3":{".":{"9":{"/":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"y":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{".":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"/":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"/":{"3":{".":{"9":{"/":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"11":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"p":{"a":{"df":0,"docs":{},"r":{"df":2,"docs":{"268":{"tf":1.0},"74":{"tf":1.0}}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"24":{"tf":1.4142135623730951}}}}}}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":5,"docs":{"185":{"tf":1.4142135623730951},"201":{"tf":1.0},"22":{"tf":1.4142135623730951},"237":{"tf":1.0},"270":{"tf":1.0}}}},"r":{"df":0,"docs":{},"v":{"df":1,"docs":{"26":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":1,"docs":{"242":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"59":{"tf":1.0}}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"228":{"tf":1.0},"233":{"tf":1.0}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"61":{"tf":1.0}}}}},"v":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"128":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":12,"docs":{"146":{"tf":1.0},"150":{"tf":1.0},"169":{"tf":1.4142135623730951},"170":{"tf":1.0},"171":{"tf":1.0},"179":{"tf":1.0},"180":{"tf":1.0},"183":{"tf":1.7320508075688772},"201":{"tf":1.0},"274":{"tf":1.4142135623730951},"31":{"tf":1.0},"36":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":6,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"268":{"tf":1.4142135623730951},"270":{"tf":1.0},"29":{"tf":1.4142135623730951},"34":{"tf":1.4142135623730951}},"s":{"df":5,"docs":{"106":{"tf":1.0},"141":{"tf":1.0},"25":{"tf":1.0},"26":{"tf":1.0},"268":{"tf":1.0}}}}}}}},"i":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":2,"docs":{"226":{"tf":1.4142135623730951},"248":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":5,"docs":{"11":{"tf":1.0},"201":{"tf":1.0},"245":{"tf":1.0},"257":{"tf":1.0},"284":{"tf":1.0}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"278":{"tf":1.0},"282":{"tf":1.4142135623730951}}}}},"n":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"62":{"tf":1.4142135623730951}}}}}}},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"62":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":3,"docs":{"22":{"tf":1.0},"37":{"tf":1.0},"62":{"tf":1.4142135623730951}},"f":{"df":1,"docs":{"37":{"tf":1.4142135623730951}}}}},"o":{"df":0,"docs":{},"r":{"df":2,"docs":{"206":{"tf":1.0},"214":{"tf":1.0}},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"258":{"tf":1.0},"6":{"tf":1.0}}}}}},"v":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"83":{"tf":1.0}}}},"df":0,"docs":{}}},"o":{"b":{"a":{"b":{"df":0,"docs":{},"l":{"df":17,"docs":{"104":{"tf":1.0},"115":{"tf":1.0},"122":{"tf":1.0},"156":{"tf":1.0},"18":{"tf":1.0},"197":{"tf":1.7320508075688772},"200":{"tf":1.0},"225":{"tf":1.0},"226":{"tf":1.4142135623730951},"229":{"tf":1.0},"231":{"tf":1.4142135623730951},"236":{"tf":1.0},"238":{"tf":1.0},"37":{"tf":1.0},"40":{"tf":1.0},"62":{"tf":1.0},"84":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"t":{"df":3,"docs":{"180":{"tf":1.0},"183":{"tf":1.0},"256":{"tf":1.0}}}},"df":18,"docs":{"136":{"tf":1.4142135623730951},"140":{"tf":1.4142135623730951},"149":{"tf":1.4142135623730951},"161":{"tf":2.0},"175":{"tf":1.4142135623730951},"177":{"tf":1.4142135623730951},"182":{"tf":1.0},"187":{"tf":1.4142135623730951},"197":{"tf":1.0},"203":{"tf":1.4142135623730951},"205":{"tf":1.4142135623730951},"226":{"tf":1.0},"25":{"tf":1.0},"260":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"58":{"tf":1.7320508075688772},"64":{"tf":1.0}}}}}},"c":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":2,"docs":{"18":{"tf":1.0},"20":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"d":{"df":1,"docs":{"12":{"tf":1.0}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"s":{"df":38,"docs":{"101":{"tf":1.0},"102":{"tf":1.0},"105":{"tf":1.0},"106":{"tf":1.0},"114":{"tf":1.0},"120":{"tf":1.0},"122":{"tf":1.0},"123":{"tf":1.0},"124":{"tf":1.0},"126":{"tf":1.7320508075688772},"133":{"tf":1.0},"138":{"tf":1.0},"161":{"tf":1.0},"165":{"tf":1.0},"167":{"tf":1.4142135623730951},"169":{"tf":1.0},"170":{"tf":1.0},"188":{"tf":1.0},"19":{"tf":1.0},"197":{"tf":1.0},"20":{"tf":1.0},"220":{"tf":1.0},"243":{"tf":1.0},"244":{"tf":1.4142135623730951},"245":{"tf":1.4142135623730951},"246":{"tf":1.0},"248":{"tf":1.0},"257":{"tf":1.0},"259":{"tf":1.0},"26":{"tf":1.0},"265":{"tf":1.4142135623730951},"27":{"tf":1.4142135623730951},"274":{"tf":1.0},"280":{"tf":1.0},"282":{"tf":1.0},"49":{"tf":1.0},"69":{"tf":1.0},"88":{"tf":1.0}}}}}},"d":{"df":0,"docs":{},"u":{"c":{"df":7,"docs":{"111":{"tf":1.0},"28":{"tf":1.0},"284":{"tf":1.0},"33":{"tf":1.0},"37":{"tf":1.0},"39":{"tf":1.0},"60":{"tf":1.0}},"t":{"df":9,"docs":{"174":{"tf":1.0},"185":{"tf":1.0},"237":{"tf":1.0},"253":{"tf":1.0},"259":{"tf":1.0},"268":{"tf":1.0},"271":{"tf":1.4142135623730951},"64":{"tf":1.0},"8":{"tf":1.0}}}},"df":0,"docs":{}}},"df":7,"docs":{"144":{"tf":1.4142135623730951},"145":{"tf":1.0},"146":{"tf":1.0},"153":{"tf":1.4142135623730951},"168":{"tf":1.4142135623730951},"181":{"tf":1.4142135623730951},"211":{"tf":1.4142135623730951}},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"11":{"tf":1.7320508075688772}}},"t":{"df":2,"docs":{"122":{"tf":1.0},"200":{"tf":1.0}}}}},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"149":{"tf":1.0},"184":{"tf":1.0}},"m":{"df":1,"docs":{"61":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":4,"docs":{"107":{"tf":1.0},"16":{"tf":1.0},"268":{"tf":2.6457513110645907},"7":{"tf":1.4142135623730951}}}}}}},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"'":{"df":2,"docs":{"22":{"tf":1.0},"7":{"tf":1.0}}},"(":{"\"":{":":{"df":0,"docs":{},"f":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":1,"docs":{"224":{"tf":1.0}}}}}},"df":0,"docs":{}},"'":{":":{"df":0,"docs":{},"f":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":1,"docs":{"224":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":35,"docs":{"10":{"tf":1.0},"109":{"tf":1.7320508075688772},"118":{"tf":1.0},"136":{"tf":1.0},"138":{"tf":1.0},"14":{"tf":1.4142135623730951},"16":{"tf":1.7320508075688772},"167":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"18":{"tf":1.4142135623730951},"190":{"tf":1.4142135623730951},"191":{"tf":1.0},"20":{"tf":1.0},"203":{"tf":1.0},"22":{"tf":2.23606797749979},"224":{"tf":1.4142135623730951},"252":{"tf":1.0},"260":{"tf":1.7320508075688772},"263":{"tf":1.0},"278":{"tf":1.0},"3":{"tf":1.4142135623730951},"36":{"tf":1.0},"40":{"tf":1.4142135623730951},"45":{"tf":1.0},"46":{"tf":1.0},"5":{"tf":1.4142135623730951},"58":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0},"67":{"tf":1.0},"70":{"tf":1.0},"73":{"tf":2.449489742783178},"8":{"tf":1.0},"91":{"tf":1.0}}}},"df":0,"docs":{}}},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":4,"docs":{"267":{"tf":1.4142135623730951},"268":{"tf":1.0},"270":{"tf":1.0},"273":{"tf":2.23606797749979}}}},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"117":{"tf":1.4142135623730951}}}}},"n":{"df":0,"docs":{},"e":{"df":2,"docs":{"116":{"tf":1.0},"61":{"tf":1.0}}}},"p":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"249":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"274":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"69":{"tf":1.0}}}},"t":{"df":0,"docs":{},"i":{"df":4,"docs":{"17":{"tf":1.4142135623730951},"22":{"tf":1.7320508075688772},"268":{"tf":1.0},"99":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"s":{"df":4,"docs":{"146":{"tf":1.0},"186":{"tf":1.0},"187":{"tf":1.0},"236":{"tf":1.0}}}},"s":{"df":1,"docs":{"145":{"tf":1.0}}}},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"106":{"tf":1.4142135623730951}}}},"t":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"201":{"tf":1.0},"274":{"tf":2.0}}}},"df":0,"docs":{}},"o":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"f":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":1,"docs":{"106":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}}},"df":4,"docs":{"106":{"tf":2.23606797749979},"175":{"tf":1.0},"65":{"tf":1.0},"67":{"tf":1.0}}}}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":4,"docs":{"105":{"tf":1.0},"106":{"tf":1.4142135623730951},"256":{"tf":1.0},"96":{"tf":1.0}}}}},"df":2,"docs":{"105":{"tf":1.0},"106":{"tf":1.0}},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":5,"docs":{"107":{"tf":1.0},"167":{"tf":1.0},"172":{"tf":1.0},"174":{"tf":1.7320508075688772},"268":{"tf":1.0}}}}}}},"v":{"df":0,"docs":{},"i":{"d":{"df":28,"docs":{"103":{"tf":1.4142135623730951},"116":{"tf":1.0},"128":{"tf":1.0},"145":{"tf":1.0},"159":{"tf":1.0},"164":{"tf":1.0},"189":{"tf":1.0},"201":{"tf":1.0},"220":{"tf":1.0},"222":{"tf":1.0},"229":{"tf":1.0},"234":{"tf":1.0},"243":{"tf":1.0},"245":{"tf":1.0},"253":{"tf":1.0},"254":{"tf":1.4142135623730951},"257":{"tf":1.0},"268":{"tf":1.0},"274":{"tf":1.0},"278":{"tf":1.0},"282":{"tf":1.0},"283":{"tf":1.4142135623730951},"37":{"tf":1.0},"45":{"tf":1.0},"62":{"tf":1.4142135623730951},"64":{"tf":1.0},"77":{"tf":1.0},"78":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":1,"docs":{"274":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":1,"docs":{"208":{"tf":1.0}}}}}},"u":{"b":{"df":6,"docs":{"103":{"tf":1.0},"105":{"tf":1.4142135623730951},"111":{"tf":1.0},"114":{"tf":1.0},"69":{"tf":1.4142135623730951},"73":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"c":{"df":11,"docs":{"105":{"tf":1.0},"106":{"tf":1.4142135623730951},"107":{"tf":1.7320508075688772},"108":{"tf":1.0},"12":{"tf":1.0},"2":{"tf":1.0},"252":{"tf":1.0},"274":{"tf":1.0},"283":{"tf":1.0},"78":{"tf":1.0},"97":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"146":{"tf":1.0}}}}},"df":0,"docs":{},"s":{"df":1,"docs":{"170":{"tf":1.0}},"h":{"df":24,"docs":{"11":{"tf":1.0},"112":{"tf":1.0},"113":{"tf":1.7320508075688772},"16":{"tf":2.0},"166":{"tf":1.0},"167":{"tf":1.7320508075688772},"169":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":2.23606797749979},"189":{"tf":1.0},"20":{"tf":2.0},"22":{"tf":2.0},"220":{"tf":1.0},"221":{"tf":1.0},"253":{"tf":1.0},"267":{"tf":1.0},"274":{"tf":2.8284271247461903},"275":{"tf":1.0},"276":{"tf":1.0},"277":{"tf":1.0},"278":{"tf":1.0},"37":{"tf":1.0},"69":{"tf":1.0},"70":{"tf":1.0}},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"df":1,"docs":{"20":{"tf":1.0}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":8,"docs":{"167":{"tf":1.0},"25":{"tf":1.0},"274":{"tf":2.0},"280":{"tf":1.0},"59":{"tf":1.0},"64":{"tf":1.0},"7":{"tf":2.449489742783178},"81":{"tf":1.0}}}},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"226":{"tf":1.0}}}},"r":{"df":0,"docs":{},"e":{"df":1,"docs":{"203":{"tf":1.0}}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":7,"docs":{"124":{"tf":1.4142135623730951},"232":{"tf":1.0},"233":{"tf":1.4142135623730951},"237":{"tf":1.0},"252":{"tf":1.0},"274":{"tf":1.0},"51":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"u":{"df":2,"docs":{"115":{"tf":1.0},"152":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"h":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"94":{"tf":1.0}}}}}}}}}},"df":9,"docs":{"104":{"tf":1.0},"189":{"tf":1.0},"253":{"tf":1.0},"26":{"tf":1.0},"264":{"tf":1.0},"267":{"tf":1.4142135623730951},"274":{"tf":1.7320508075688772},"283":{"tf":1.0},"7":{"tf":1.4142135623730951}}}},"t":{"df":7,"docs":{"122":{"tf":1.0},"17":{"tf":1.0},"59":{"tf":1.4142135623730951},"60":{"tf":1.0},"61":{"tf":1.7320508075688772},"69":{"tf":1.4142135623730951},"79":{"tf":1.0}}}},"w":{"d":{"df":2,"docs":{"31":{"tf":1.0},"36":{"tf":1.0}}},"df":0,"docs":{}},"y":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"=":{"$":{"(":{"df":0,"docs":{},"w":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"11":{"tf":1.0}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"`":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"y":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"11":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"11":{"tf":1.0}}}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"'":{"df":1,"docs":{"11":{"tf":1.0}}},"3":{"=":{"$":{"(":{"b":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":1,"docs":{"11":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"11":{"tf":2.8284271247461903}}},"df":4,"docs":{"11":{"tf":2.449489742783178},"193":{"tf":1.0},"269":{"tf":1.0},"53":{"tf":1.0}}}}}}}},"q":{"1":{"df":1,"docs":{"187":{"tf":1.0}}},"a":{"df":1,"docs":{"190":{"tf":1.0}}},"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":3,"docs":{"63":{"tf":1.0},"64":{"tf":1.0},"8":{"tf":1.0}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":7,"docs":{"154":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0},"205":{"tf":1.4142135623730951},"206":{"tf":1.4142135623730951},"6":{"tf":1.0},"62":{"tf":2.23606797749979}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":5,"docs":{"11":{"tf":1.0},"149":{"tf":1.0},"205":{"tf":1.0},"274":{"tf":1.0},"60":{"tf":1.0}}}}}}}},"i":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"236":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"170":{"tf":1.0}}}},"l":{"df":0,"docs":{},"i":{"df":3,"docs":{"167":{"tf":1.0},"178":{"tf":1.0},"60":{"tf":1.0}}}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"243":{"tf":1.0}}}},"t":{"df":6,"docs":{"220":{"tf":1.0},"226":{"tf":1.0},"229":{"tf":1.0},"62":{"tf":1.4142135623730951},"68":{"tf":1.0},"84":{"tf":1.0}}}},"o":{"df":1,"docs":{"171":{"tf":1.0}}}}},"r":{"a":{"d":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"86":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":1,"docs":{"132":{"tf":1.0}}}},"n":{"df":3,"docs":{"203":{"tf":1.0},"30":{"tf":1.0},"35":{"tf":1.0}}},"p":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"189":{"tf":1.0},"68":{"tf":1.0}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"e":{"df":2,"docs":{"17":{"tf":1.0},"86":{"tf":1.0}}}},"t":{"df":0,"docs":{},"e":{"df":8,"docs":{"205":{"tf":1.4142135623730951},"208":{"tf":1.0},"214":{"tf":1.0},"216":{"tf":2.8284271247461903},"217":{"tf":1.0},"218":{"tf":1.0},"225":{"tf":1.0},"242":{"tf":1.0}}},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":1,"docs":{"104":{"tf":1.0}}}}}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"138":{"tf":1.0}}}}}},"w":{"df":3,"docs":{"128":{"tf":1.0},"79":{"tf":1.0},"83":{"tf":1.0}}}},"c":{"_":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":2,"docs":{"281":{"tf":1.0},"282":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"12":{"tf":1.0}}},"df":2,"docs":{"269":{"tf":1.0},"53":{"tf":1.0}},"e":{"a":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"6":{"tf":1.0}}},"t":{"df":1,"docs":{"233":{"tf":1.0}}}},"d":{"df":9,"docs":{"107":{"tf":1.0},"120":{"tf":1.0},"174":{"tf":1.4142135623730951},"23":{"tf":1.4142135623730951},"32":{"tf":1.4142135623730951},"62":{"tf":1.4142135623730951},"7":{"tf":1.0},"79":{"tf":1.4142135623730951},"81":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"79":{"tf":1.4142135623730951}}}},"i":{"df":6,"docs":{"118":{"tf":1.0},"184":{"tf":1.0},"227":{"tf":1.0},"24":{"tf":1.4142135623730951},"267":{"tf":1.0},"58":{"tf":1.0}}},"m":{"df":1,"docs":{"281":{"tf":1.0}}}},"df":0,"docs":{},"l":{"df":6,"docs":{"16":{"tf":1.0},"200":{"tf":1.0},"204":{"tf":1.0},"233":{"tf":1.0},"237":{"tf":1.4142135623730951},"273":{"tf":1.0}},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"194":{"tf":1.0},"196":{"tf":1.0}}}},"z":{"df":1,"docs":{"61":{"tf":1.0}}}},"l":{"df":0,"docs":{},"i":{"df":8,"docs":{"116":{"tf":1.0},"129":{"tf":1.0},"169":{"tf":1.0},"40":{"tf":1.0},"58":{"tf":1.4142135623730951},"60":{"tf":1.0},"73":{"tf":1.4142135623730951},"79":{"tf":1.4142135623730951}}}}},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":14,"docs":{"152":{"tf":1.0},"192":{"tf":1.0},"194":{"tf":1.0},"197":{"tf":1.0},"201":{"tf":1.0},"205":{"tf":1.4142135623730951},"208":{"tf":1.4142135623730951},"217":{"tf":1.0},"224":{"tf":1.0},"242":{"tf":1.7320508075688772},"27":{"tf":1.0},"58":{"tf":1.0},"61":{"tf":1.4142135623730951},"7":{"tf":1.0}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"df":1,"docs":{"86":{"tf":1.0}}}}}}}},"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":3,"docs":{"124":{"tf":1.0},"37":{"tf":1.0},"61":{"tf":1.0}}},"df":0,"docs":{},"t":{"df":1,"docs":{"59":{"tf":1.0}}}}}}},"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":3,"docs":{"106":{"tf":1.0},"189":{"tf":1.0},"201":{"tf":1.0}}}},"n":{"df":0,"docs":{},"t":{"df":10,"docs":{"122":{"tf":1.0},"123":{"tf":1.4142135623730951},"170":{"tf":1.0},"193":{"tf":1.4142135623730951},"206":{"tf":1.4142135623730951},"208":{"tf":1.4142135623730951},"209":{"tf":1.4142135623730951},"210":{"tf":1.0},"231":{"tf":1.0},"78":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":4,"docs":{"101":{"tf":1.0},"11":{"tf":1.0},"14":{"tf":1.0},"274":{"tf":1.0}}},"df":0,"docs":{}}}}},"n":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":2,"docs":{"245":{"tf":1.0},"89":{"tf":1.0}},"e":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"249":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"r":{"d":{"df":95,"docs":{"134":{"tf":1.7320508075688772},"135":{"tf":1.7320508075688772},"136":{"tf":1.7320508075688772},"137":{"tf":1.4142135623730951},"138":{"tf":1.0},"139":{"tf":1.0},"140":{"tf":1.0},"141":{"tf":1.0},"142":{"tf":1.0},"143":{"tf":1.0},"144":{"tf":1.0},"145":{"tf":1.4142135623730951},"146":{"tf":2.0},"147":{"tf":1.0},"148":{"tf":1.0},"149":{"tf":1.0},"150":{"tf":1.0},"151":{"tf":1.0},"152":{"tf":1.0},"153":{"tf":1.0},"154":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0},"157":{"tf":1.0},"158":{"tf":1.0},"159":{"tf":1.0},"160":{"tf":1.0},"161":{"tf":1.0},"162":{"tf":1.0},"163":{"tf":1.0},"164":{"tf":1.0},"165":{"tf":1.0},"166":{"tf":1.0},"167":{"tf":1.0},"168":{"tf":1.0},"169":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"173":{"tf":1.0},"174":{"tf":1.0},"175":{"tf":1.0},"176":{"tf":1.0},"177":{"tf":1.0},"178":{"tf":1.0},"179":{"tf":1.0},"180":{"tf":1.0},"181":{"tf":1.0},"182":{"tf":1.0},"183":{"tf":1.0},"184":{"tf":1.0},"185":{"tf":1.0},"186":{"tf":1.0},"187":{"tf":1.0},"188":{"tf":1.0},"189":{"tf":1.4142135623730951},"190":{"tf":1.4142135623730951},"191":{"tf":1.0},"192":{"tf":1.0},"193":{"tf":1.0},"194":{"tf":1.0},"195":{"tf":1.0},"196":{"tf":1.0},"197":{"tf":1.0},"198":{"tf":1.0},"199":{"tf":1.0},"200":{"tf":1.0},"201":{"tf":1.0},"202":{"tf":1.0},"203":{"tf":1.0},"204":{"tf":1.0},"205":{"tf":1.0},"206":{"tf":1.0},"207":{"tf":1.0},"208":{"tf":1.0},"209":{"tf":1.0},"210":{"tf":1.0},"211":{"tf":1.0},"212":{"tf":1.0},"213":{"tf":1.0},"214":{"tf":1.0},"215":{"tf":1.0},"216":{"tf":1.0},"217":{"tf":1.0},"218":{"tf":1.0},"231":{"tf":1.7320508075688772},"237":{"tf":1.0},"245":{"tf":1.0},"246":{"tf":1.7320508075688772},"248":{"tf":2.0},"249":{"tf":4.123105625617661},"250":{"tf":2.6457513110645907},"268":{"tf":1.0},"84":{"tf":1.4142135623730951},"88":{"tf":1.7320508075688772}}},"df":0,"docs":{}},"v":{"df":1,"docs":{"158":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"158":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":5,"docs":{"151":{"tf":1.4142135623730951},"152":{"tf":1.0},"156":{"tf":1.0},"157":{"tf":1.4142135623730951},"158":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":1,"docs":{"11":{"tf":1.0}}}}}},"d":{"df":1,"docs":{"109":{"tf":1.0}},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"283":{"tf":1.4142135623730951}}}}}},"df":1,"docs":{"201":{"tf":1.0}}}},"df":0,"docs":{}}}},"u":{"c":{"df":7,"docs":{"142":{"tf":1.0},"143":{"tf":1.0},"145":{"tf":1.7320508075688772},"165":{"tf":1.0},"205":{"tf":1.0},"220":{"tf":1.0},"222":{"tf":1.0}}},"df":0,"docs":{},"n":{"d":{"df":5,"docs":{"145":{"tf":1.0},"146":{"tf":1.0},"263":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}},"df":14,"docs":{"105":{"tf":1.0},"124":{"tf":1.7320508075688772},"14":{"tf":1.0},"142":{"tf":1.0},"161":{"tf":1.0},"193":{"tf":1.0},"233":{"tf":1.0},"252":{"tf":1.0},"258":{"tf":1.0},"26":{"tf":1.0},"283":{"tf":1.0},"81":{"tf":1.0},"86":{"tf":1.0},"90":{"tf":1.0}},"e":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":1,"docs":{"201":{"tf":1.0}}}}},"df":0,"docs":{}}},"f":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"81":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":2,"docs":{"171":{"tf":1.0},"280":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":14,"docs":{"109":{"tf":1.4142135623730951},"132":{"tf":1.4142135623730951},"167":{"tf":1.4142135623730951},"237":{"tf":1.0},"238":{"tf":1.0},"242":{"tf":1.4142135623730951},"29":{"tf":1.0},"31":{"tf":1.0},"34":{"tf":1.0},"36":{"tf":1.0},"60":{"tf":1.0},"70":{"tf":1.0},"79":{"tf":1.0},"82":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"61":{"tf":1.0}}},"df":0,"docs":{}}}}},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"242":{"tf":1.0},"87":{"tf":1.0},"98":{"tf":1.0}}}},"df":0,"docs":{}}}},"g":{"a":{"df":0,"docs":{},"r":{"d":{"df":4,"docs":{"122":{"tf":1.0},"231":{"tf":1.0},"238":{"tf":1.0},"242":{"tf":1.0}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"233":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"_":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"y":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"64":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":1,"docs":{"64":{"tf":1.0}}}}}},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"283":{"tf":1.4142135623730951}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":3,"docs":{"206":{"tf":1.0},"208":{"tf":1.0},"212":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"r":{"df":2,"docs":{"225":{"tf":1.0},"62":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"64":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"116":{"tf":1.0}}}}}}}}}}},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"176":{"tf":1.0},"180":{"tf":1.4142135623730951},"196":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"l":{"=":{"\"":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"283":{"tf":1.7320508075688772}}}}}}}}}}}}},"df":0,"docs":{}},"a":{"df":0,"docs":{},"t":{"df":13,"docs":{"0":{"tf":1.0},"123":{"tf":1.4142135623730951},"145":{"tf":1.0},"149":{"tf":1.4142135623730951},"20":{"tf":1.0},"205":{"tf":1.0},"226":{"tf":1.0},"26":{"tf":1.0},"278":{"tf":1.0},"282":{"tf":1.0},"283":{"tf":1.0},"48":{"tf":1.4142135623730951},"84":{"tf":1.0}}}},"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":1,"docs":{"272":{"tf":1.0}}}}},"df":7,"docs":{"121":{"tf":1.0},"152":{"tf":1.0},"154":{"tf":1.0},"167":{"tf":1.0},"18":{"tf":1.4142135623730951},"192":{"tf":1.0},"22":{"tf":1.0}},"e":{"a":{"df":0,"docs":{},"s":{"df":35,"docs":{"113":{"tf":1.0},"114":{"tf":1.0},"121":{"tf":1.0},"167":{"tf":2.23606797749979},"178":{"tf":1.0},"179":{"tf":1.4142135623730951},"18":{"tf":1.0},"180":{"tf":1.4142135623730951},"183":{"tf":2.23606797749979},"187":{"tf":1.0},"215":{"tf":1.0},"252":{"tf":1.0},"253":{"tf":1.7320508075688772},"26":{"tf":1.4142135623730951},"265":{"tf":1.7320508075688772},"266":{"tf":1.0},"267":{"tf":3.0},"268":{"tf":4.898979485566356},"269":{"tf":2.6457513110645907},"270":{"tf":2.449489742783178},"271":{"tf":2.23606797749979},"272":{"tf":1.4142135623730951},"273":{"tf":2.449489742783178},"274":{"tf":4.0},"275":{"tf":1.0},"276":{"tf":1.0},"277":{"tf":1.0},"278":{"tf":1.0},"279":{"tf":1.0},"280":{"tf":1.0},"281":{"tf":1.0},"282":{"tf":1.0},"40":{"tf":1.0},"60":{"tf":1.4142135623730951},"74":{"tf":1.0}},"e":{".":{"df":0,"docs":{},"j":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"271":{"tf":1.0}}}}}},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"269":{"tf":1.4142135623730951}}}}},"_":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":1,"docs":{"268":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"]":{".":{"0":{"a":{"1":{"df":1,"docs":{"268":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":1,"docs":{"268":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"274":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"/":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"119":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":1,"docs":{"266":{"tf":1.0}}}},"v":{"df":6,"docs":{"193":{"tf":1.0},"194":{"tf":1.0},"221":{"tf":1.0},"241":{"tf":1.0},"54":{"tf":1.0},"7":{"tf":1.4142135623730951}}}},"i":{"a":{"b":{"df":0,"docs":{},"l":{"df":2,"docs":{"117":{"tf":1.0},"16":{"tf":1.0}}}},"df":0,"docs":{}},"df":4,"docs":{"124":{"tf":1.0},"128":{"tf":1.0},"278":{"tf":1.0},"64":{"tf":1.0}}}},"m":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":8,"docs":{"103":{"tf":1.4142135623730951},"105":{"tf":1.0},"142":{"tf":1.0},"143":{"tf":1.0},"145":{"tf":1.4142135623730951},"198":{"tf":1.0},"199":{"tf":1.0},"264":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"b":{"df":2,"docs":{"64":{"tf":1.0},"84":{"tf":1.0}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"t":{"df":21,"docs":{"106":{"tf":1.0},"134":{"tf":1.0},"179":{"tf":1.0},"182":{"tf":1.0},"184":{"tf":1.0},"186":{"tf":1.7320508075688772},"187":{"tf":1.4142135623730951},"188":{"tf":1.0},"190":{"tf":1.4142135623730951},"192":{"tf":1.7320508075688772},"193":{"tf":1.7320508075688772},"194":{"tf":1.4142135623730951},"197":{"tf":1.7320508075688772},"198":{"tf":1.0},"199":{"tf":1.0},"201":{"tf":1.7320508075688772},"231":{"tf":1.0},"242":{"tf":1.0},"249":{"tf":1.4142135623730951},"250":{"tf":1.0},"268":{"tf":1.0}},"e":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"u":{"d":{"df":0,"docs":{},"l":{"df":1,"docs":{"200":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"200":{"tf":2.449489742783178}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"s":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}}}}}},"v":{"df":17,"docs":{"105":{"tf":1.0},"106":{"tf":2.0},"108":{"tf":1.0},"109":{"tf":1.4142135623730951},"145":{"tf":2.0},"146":{"tf":1.4142135623730951},"167":{"tf":1.0},"172":{"tf":1.0},"197":{"tf":1.0},"203":{"tf":1.0},"231":{"tf":1.0},"25":{"tf":1.4142135623730951},"26":{"tf":1.0},"264":{"tf":1.0},"268":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0}}}}},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"78":{"tf":1.0}}}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":3,"docs":{"45":{"tf":1.0},"46":{"tf":1.0},"75":{"tf":1.0}}}},"df":0,"docs":{}},"l":{"a":{"c":{"df":14,"docs":{"103":{"tf":1.0},"106":{"tf":1.4142135623730951},"109":{"tf":1.0},"13":{"tf":1.0},"166":{"tf":1.0},"200":{"tf":1.0},"203":{"tf":1.0},"221":{"tf":1.0},"222":{"tf":1.7320508075688772},"231":{"tf":1.4142135623730951},"258":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"70":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{},"i":{"c":{"df":3,"docs":{"140":{"tf":1.0},"145":{"tf":1.0},"61":{"tf":1.0}}},"df":0,"docs":{}}},"o":{"df":19,"docs":{"101":{"tf":1.0},"102":{"tf":1.0},"106":{"tf":1.0},"118":{"tf":1.0},"124":{"tf":1.0},"13":{"tf":1.0},"167":{"tf":2.8284271247461903},"18":{"tf":1.0},"197":{"tf":1.0},"20":{"tf":1.4142135623730951},"200":{"tf":1.4142135623730951},"21":{"tf":1.0},"225":{"tf":1.0},"253":{"tf":1.0},"268":{"tf":1.4142135623730951},"274":{"tf":1.7320508075688772},"49":{"tf":1.0},"69":{"tf":1.0},"75":{"tf":1.0}},"r":{"df":0,"docs":{},"t":{"df":5,"docs":{"1":{"tf":1.4142135623730951},"104":{"tf":1.0},"249":{"tf":1.0},"4":{"tf":1.4142135623730951},"50":{"tf":1.0}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":37,"docs":{"11":{"tf":1.0},"113":{"tf":1.0},"118":{"tf":1.4142135623730951},"12":{"tf":1.4142135623730951},"124":{"tf":1.0},"13":{"tf":1.0},"16":{"tf":1.0},"167":{"tf":2.23606797749979},"17":{"tf":1.0},"18":{"tf":1.0},"194":{"tf":1.0},"197":{"tf":1.0},"20":{"tf":1.7320508075688772},"200":{"tf":1.4142135623730951},"22":{"tf":1.0},"23":{"tf":1.0},"25":{"tf":1.0},"251":{"tf":1.0},"253":{"tf":1.7320508075688772},"260":{"tf":1.0},"271":{"tf":1.0},"274":{"tf":1.0},"284":{"tf":1.0},"286":{"tf":1.0},"29":{"tf":1.0},"30":{"tf":1.0},"32":{"tf":1.0},"34":{"tf":1.4142135623730951},"35":{"tf":1.0},"37":{"tf":1.0},"46":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.7320508075688772},"68":{"tf":1.0},"69":{"tf":1.4142135623730951},"70":{"tf":1.0},"74":{"tf":1.0}}}}}}}}},"r":{"df":1,"docs":{"268":{"tf":1.0}},"e":{"df":0,"docs":{},"s":{"df":3,"docs":{"106":{"tf":1.4142135623730951},"252":{"tf":1.0},"283":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"214":{"tf":1.0}}}}}}}}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{".":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"a":{"df":1,"docs":{"283":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":1,"docs":{"283":{"tf":1.0}}}}}}}}}}}}}}}},"df":12,"docs":{"122":{"tf":1.0},"189":{"tf":1.7320508075688772},"201":{"tf":1.4142135623730951},"225":{"tf":1.0},"226":{"tf":1.4142135623730951},"233":{"tf":1.4142135623730951},"236":{"tf":1.0},"245":{"tf":1.7320508075688772},"259":{"tf":1.0},"274":{"tf":2.0},"280":{"tf":1.0},"7":{"tf":2.449489742783178}}}}},"i":{"df":0,"docs":{},"r":{"df":50,"docs":{"10":{"tf":1.0},"105":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"11":{"tf":2.23606797749979},"116":{"tf":1.0},"12":{"tf":2.0},"141":{"tf":1.0},"142":{"tf":1.0},"143":{"tf":1.4142135623730951},"145":{"tf":2.449489742783178},"146":{"tf":1.0},"167":{"tf":1.0},"169":{"tf":1.0},"170":{"tf":1.0},"175":{"tf":1.0},"18":{"tf":1.0},"183":{"tf":1.0},"187":{"tf":2.0},"188":{"tf":2.449489742783178},"189":{"tf":3.4641016151377544},"190":{"tf":2.0},"193":{"tf":1.0},"196":{"tf":1.7320508075688772},"197":{"tf":2.23606797749979},"198":{"tf":1.4142135623730951},"199":{"tf":1.0},"200":{"tf":1.4142135623730951},"201":{"tf":2.449489742783178},"22":{"tf":1.4142135623730951},"220":{"tf":1.0},"222":{"tf":1.4142135623730951},"225":{"tf":1.0},"229":{"tf":1.4142135623730951},"23":{"tf":1.0},"231":{"tf":1.4142135623730951},"233":{"tf":1.0},"241":{"tf":1.0},"261":{"tf":1.4142135623730951},"262":{"tf":1.0},"263":{"tf":1.4142135623730951},"269":{"tf":1.0},"274":{"tf":1.0},"277":{"tf":1.0},"279":{"tf":1.0},"286":{"tf":1.0},"53":{"tf":1.0},"61":{"tf":1.0},"64":{"tf":1.0},"81":{"tf":1.7320508075688772}}}}}},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":1,"docs":{"12":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":7,"docs":{"234":{"tf":1.0},"242":{"tf":1.0},"245":{"tf":1.0},"25":{"tf":2.0},"26":{"tf":1.7320508075688772},"86":{"tf":1.0},"89":{"tf":1.7320508075688772}}}},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"v":{"df":4,"docs":{"118":{"tf":1.0},"22":{"tf":1.0},"40":{"tf":1.0},"6":{"tf":1.0}}}},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"111":{"tf":1.0}}}},"u":{"df":0,"docs":{},"r":{"c":{"df":8,"docs":{"128":{"tf":1.0},"133":{"tf":1.0},"141":{"tf":1.0},"194":{"tf":1.0},"226":{"tf":1.4142135623730951},"252":{"tf":1.0},"64":{"tf":1.0},"73":{"tf":1.0}}},"df":0,"docs":{}}}},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"60":{"tf":1.0},"64":{"tf":1.0}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":16,"docs":{"121":{"tf":1.4142135623730951},"132":{"tf":1.0},"146":{"tf":1.0},"189":{"tf":1.0},"190":{"tf":1.0},"194":{"tf":1.0},"201":{"tf":2.449489742783178},"225":{"tf":2.23606797749979},"226":{"tf":2.0},"241":{"tf":1.0},"245":{"tf":1.7320508075688772},"246":{"tf":1.0},"249":{"tf":1.0},"64":{"tf":1.0},"7":{"tf":1.0},"78":{"tf":1.0}}}}}},"t":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"233":{"tf":1.0}}}}},"df":3,"docs":{"201":{"tf":1.4142135623730951},"205":{"tf":1.0},"37":{"tf":1.0}},"o":{"df":0,"docs":{},"r":{"df":5,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"226":{"tf":1.0},"231":{"tf":1.0},"86":{"tf":1.0}}}},"r":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"105":{"tf":1.7320508075688772}}}}}},"df":0,"docs":{}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"<":{"df":0,"docs":{},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"242":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}}}},"df":17,"docs":{"113":{"tf":1.4142135623730951},"132":{"tf":1.0},"174":{"tf":1.0},"188":{"tf":1.0},"189":{"tf":1.0},"197":{"tf":1.0},"203":{"tf":1.0},"208":{"tf":1.0},"220":{"tf":1.0},"224":{"tf":1.0},"242":{"tf":2.6457513110645907},"252":{"tf":1.0},"266":{"tf":1.0},"283":{"tf":1.0},"58":{"tf":1.0},"7":{"tf":1.4142135623730951},"73":{"tf":1.7320508075688772}}}}}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":6,"docs":{"179":{"tf":1.7320508075688772},"180":{"tf":1.0},"184":{"tf":1.7320508075688772},"185":{"tf":1.4142135623730951},"217":{"tf":1.0},"254":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":9,"docs":{"106":{"tf":1.4142135623730951},"145":{"tf":1.0},"159":{"tf":1.4142135623730951},"188":{"tf":1.0},"189":{"tf":1.7320508075688772},"229":{"tf":1.0},"233":{"tf":1.0},"249":{"tf":1.4142135623730951},"283":{"tf":2.0}}}}}},"u":{"df":0,"docs":{},"s":{"df":3,"docs":{"199":{"tf":1.0},"225":{"tf":1.0},"242":{"tf":1.0}}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":1,"docs":{"180":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":1,"docs":{"201":{"tf":1.0}}}},"r":{"df":0,"docs":{},"s":{"df":1,"docs":{"104":{"tf":1.0}}},"t":{"df":1,"docs":{"26":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":9,"docs":{"122":{"tf":1.7320508075688772},"143":{"tf":1.0},"146":{"tf":1.0},"150":{"tf":1.0},"189":{"tf":1.0},"259":{"tf":1.0},"274":{"tf":1.0},"7":{"tf":1.4142135623730951},"8":{"tf":1.7320508075688772}},"e":{"df":0,"docs":{},"r":{"'":{"df":1,"docs":{"7":{"tf":1.0}}},"df":0,"docs":{}}}}},"s":{"df":1,"docs":{"123":{"tf":1.7320508075688772}}}},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"v":{"df":1,"docs":{"261":{"tf":1.0}}}}}}},"f":{"c":{"df":1,"docs":{"185":{"tf":1.0}}},"df":1,"docs":{"279":{"tf":1.0}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"160":{"tf":1.0}}}}}}}},"i":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"106":{"tf":1.0}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":5,"docs":{"126":{"tf":1.7320508075688772},"129":{"tf":1.0},"212":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"k":{"df":3,"docs":{"201":{"tf":1.0},"203":{"tf":1.4142135623730951},"252":{"tf":1.0}}}}},"k":{"df":0,"docs":{},"v":{"df":3,"docs":{"194":{"tf":1.0},"197":{"tf":1.0},"79":{"tf":1.0}}}},"l":{"df":1,"docs":{"200":{"tf":1.0}}},"m":{"df":1,"docs":{"279":{"tf":1.0}}},"o":{"a":{"d":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"192":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":2,"docs":{"223":{"tf":1.0},"45":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"_":{"_":{"_":{"_":{"_":{"_":{"_":{"_":{"df":1,"docs":{"62":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":13,"docs":{"113":{"tf":1.0},"122":{"tf":1.0},"167":{"tf":1.0},"17":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.4142135623730951},"29":{"tf":1.0},"34":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.4142135623730951},"62":{"tf":1.0},"69":{"tf":1.4142135623730951},"70":{"tf":1.0}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"}":{"/":{"$":{"df":0,"docs":{},"{":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"r":{"c":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"}":{"/":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"22":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"t":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"276":{"tf":1.0}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":1,"docs":{"224":{"tf":1.7320508075688772}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"242":{"tf":1.0}}}}}}},"w":{"df":4,"docs":{"156":{"tf":1.0},"249":{"tf":1.0},"62":{"tf":1.0},"82":{"tf":1.4142135623730951}}}},"p":{"0":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"_":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"df":0,"docs":{},"i":{"0":{"df":0,"docs":{},"e":{"df":0,"docs":{},"z":{"_":{"df":0,"docs":{},"j":{"df":0,"docs":{},"q":{"df":0,"docs":{},"s":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"z":{"df":0,"docs":{},"o":{"df":0,"docs":{},"h":{"df":0,"docs":{},"j":{"df":0,"docs":{},"y":{"/":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"185":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"s":{"#":{"4":{"1":{"6":{"df":1,"docs":{"107":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"200":{"tf":2.6457513110645907}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":5,"docs":{"118":{"tf":1.0},"222":{"tf":1.0},"59":{"tf":1.0},"73":{"tf":1.0},"79":{"tf":1.0}}}},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"282":{"tf":1.0}}}}},"n":{"_":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"264":{"tf":1.0}}}}}}}},"df":0,"docs":{}}},"df":78,"docs":{"105":{"tf":1.0},"107":{"tf":1.0},"109":{"tf":1.0},"11":{"tf":2.0},"115":{"tf":1.4142135623730951},"117":{"tf":1.0},"118":{"tf":1.0},"12":{"tf":1.0},"122":{"tf":1.7320508075688772},"124":{"tf":1.4142135623730951},"126":{"tf":1.4142135623730951},"127":{"tf":1.4142135623730951},"133":{"tf":1.4142135623730951},"134":{"tf":1.4142135623730951},"14":{"tf":2.0},"154":{"tf":1.0},"155":{"tf":1.0},"17":{"tf":2.0},"170":{"tf":1.0},"176":{"tf":2.0},"177":{"tf":2.0},"178":{"tf":1.0},"179":{"tf":2.23606797749979},"180":{"tf":1.7320508075688772},"182":{"tf":1.0},"183":{"tf":2.0},"184":{"tf":1.7320508075688772},"185":{"tf":1.4142135623730951},"19":{"tf":1.0},"190":{"tf":1.0},"194":{"tf":1.4142135623730951},"20":{"tf":1.4142135623730951},"203":{"tf":2.0},"204":{"tf":1.4142135623730951},"205":{"tf":1.4142135623730951},"208":{"tf":1.0},"209":{"tf":1.0},"214":{"tf":1.4142135623730951},"216":{"tf":1.4142135623730951},"218":{"tf":1.4142135623730951},"22":{"tf":1.7320508075688772},"223":{"tf":1.4142135623730951},"23":{"tf":1.0},"25":{"tf":1.4142135623730951},"26":{"tf":1.0},"269":{"tf":1.4142135623730951},"27":{"tf":1.4142135623730951},"274":{"tf":3.0},"28":{"tf":1.0},"280":{"tf":1.0},"30":{"tf":1.7320508075688772},"31":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.0},"35":{"tf":1.7320508075688772},"36":{"tf":1.0},"37":{"tf":1.0},"39":{"tf":1.4142135623730951},"40":{"tf":1.4142135623730951},"41":{"tf":1.0},"45":{"tf":1.4142135623730951},"48":{"tf":1.0},"49":{"tf":1.7320508075688772},"51":{"tf":1.4142135623730951},"53":{"tf":1.0},"54":{"tf":1.4142135623730951},"55":{"tf":1.0},"56":{"tf":1.0},"58":{"tf":1.4142135623730951},"60":{"tf":1.7320508075688772},"61":{"tf":1.0},"62":{"tf":1.0},"64":{"tf":1.4142135623730951},"69":{"tf":1.0},"7":{"tf":2.23606797749979},"70":{"tf":1.4142135623730951},"73":{"tf":1.0},"82":{"tf":1.0}},"n":{"a":{"b":{"df":0,"docs":{},"l":{"df":2,"docs":{"43":{"tf":1.0},"45":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":3,"docs":{"222":{"tf":1.0},"224":{"tf":1.0},"45":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"79":{"tf":1.7320508075688772}}}}}},"t":{"&":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{";":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"k":{"df":1,"docs":{"283":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"'":{"df":3,"docs":{"107":{"tf":1.0},"43":{"tf":1.4142135623730951},"92":{"tf":1.0}}},".":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"17":{"tf":1.7320508075688772}},"s":{"=":{"df":0,"docs":{},"x":{"8":{"6":{",":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"x":{"df":1,"docs":{"17":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"/":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":2,"docs":{"14":{"tf":1.0},"73":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"x":{"c":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{".":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":1,"docs":{"34":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"h":{"df":1,"docs":{"73":{"tf":1.0}}},"x":{"c":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{".":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":1,"docs":{"29":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"h":{"df":1,"docs":{"109":{"tf":1.0}}},"x":{"c":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"df":2,"docs":{"109":{"tf":1.0},"14":{"tf":1.0}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"x":{"c":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"df":1,"docs":{"73":{"tf":1.0}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"46":{"tf":1.0},"73":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}}}}},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"/":{"c":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":1,"docs":{"69":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"s":{"df":0,"docs":{},"r":{"c":{"/":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"b":{".":{"df":0,"docs":{},"r":{"df":2,"docs":{"69":{"tf":1.0},"73":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"<":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{">":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{".":{"df":0,"docs":{},"h":{"df":2,"docs":{"103":{"tf":1.4142135623730951},"109":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}},"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"_":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"l":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":2,"docs":{"25":{"tf":1.0},"26":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"x":{"c":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{".":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":1,"docs":{"271":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}}}}}}}},"d":{"df":0,"docs":{},"o":{"c":{"df":2,"docs":{"107":{"tf":1.0},"283":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":121,"docs":{"0":{"tf":2.23606797749979},"1":{"tf":1.4142135623730951},"10":{"tf":1.0},"101":{"tf":1.4142135623730951},"102":{"tf":1.0},"103":{"tf":2.8284271247461903},"104":{"tf":2.0},"105":{"tf":2.23606797749979},"106":{"tf":1.7320508075688772},"107":{"tf":2.23606797749979},"108":{"tf":2.0},"109":{"tf":2.0},"11":{"tf":1.7320508075688772},"110":{"tf":1.7320508075688772},"111":{"tf":2.449489742783178},"112":{"tf":1.0},"113":{"tf":1.0},"114":{"tf":2.0},"115":{"tf":2.6457513110645907},"116":{"tf":2.23606797749979},"117":{"tf":2.23606797749979},"119":{"tf":1.0},"120":{"tf":1.0},"124":{"tf":1.4142135623730951},"125":{"tf":1.0},"128":{"tf":1.0},"14":{"tf":1.0},"140":{"tf":1.0},"163":{"tf":1.4142135623730951},"164":{"tf":1.0},"167":{"tf":3.1622776601683795},"17":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":2.0},"174":{"tf":1.0},"187":{"tf":1.0},"193":{"tf":2.449489742783178},"194":{"tf":1.4142135623730951},"196":{"tf":1.4142135623730951},"197":{"tf":1.4142135623730951},"2":{"tf":1.0},"200":{"tf":1.4142135623730951},"219":{"tf":2.23606797749979},"220":{"tf":2.23606797749979},"221":{"tf":2.449489742783178},"222":{"tf":1.4142135623730951},"223":{"tf":1.4142135623730951},"224":{"tf":1.4142135623730951},"225":{"tf":2.449489742783178},"226":{"tf":1.7320508075688772},"228":{"tf":2.6457513110645907},"23":{"tf":2.0},"24":{"tf":1.0},"241":{"tf":2.449489742783178},"242":{"tf":1.7320508075688772},"25":{"tf":2.0},"251":{"tf":2.449489742783178},"252":{"tf":2.0},"253":{"tf":2.6457513110645907},"254":{"tf":1.0},"255":{"tf":1.0},"256":{"tf":1.4142135623730951},"257":{"tf":2.449489742783178},"258":{"tf":1.4142135623730951},"26":{"tf":2.449489742783178},"260":{"tf":2.449489742783178},"261":{"tf":2.449489742783178},"262":{"tf":2.0},"263":{"tf":2.449489742783178},"268":{"tf":1.7320508075688772},"271":{"tf":1.0},"272":{"tf":1.4142135623730951},"274":{"tf":2.8284271247461903},"28":{"tf":1.4142135623730951},"282":{"tf":1.0},"29":{"tf":2.449489742783178},"30":{"tf":1.4142135623730951},"31":{"tf":2.6457513110645907},"32":{"tf":2.0},"33":{"tf":1.4142135623730951},"34":{"tf":2.449489742783178},"35":{"tf":1.4142135623730951},"36":{"tf":2.6457513110645907},"37":{"tf":1.0},"41":{"tf":1.7320508075688772},"42":{"tf":1.0},"43":{"tf":3.1622776601683795},"44":{"tf":1.0},"45":{"tf":1.7320508075688772},"46":{"tf":1.7320508075688772},"47":{"tf":1.0},"48":{"tf":1.4142135623730951},"49":{"tf":1.0},"50":{"tf":1.0},"51":{"tf":1.0},"52":{"tf":1.0},"53":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":2.0},"60":{"tf":1.0},"61":{"tf":1.4142135623730951},"62":{"tf":1.4142135623730951},"64":{"tf":2.0},"68":{"tf":1.0},"69":{"tf":2.0},"70":{"tf":1.0},"72":{"tf":1.0},"73":{"tf":1.0},"74":{"tf":2.449489742783178},"75":{"tf":2.0},"79":{"tf":1.0},"82":{"tf":1.0},"83":{"tf":1.4142135623730951},"9":{"tf":1.0},"90":{"tf":2.0},"92":{"tf":1.4142135623730951},"93":{"tf":2.0}},"j":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"224":{"tf":1.0}}},"df":0,"docs":{}}}}}},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"126":{"tf":1.0}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"x":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"(":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{".":{"d":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":1,"docs":{"126":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"u":{"df":0,"docs":{},"p":{"df":1,"docs":{"11":{"tf":1.0}}}}}}}},"s":{"a":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":4,"docs":{"116":{"tf":1.0},"222":{"tf":1.0},"281":{"tf":1.0},"282":{"tf":1.0}},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"116":{"tf":1.0}}}}}},"m":{"df":0,"docs":{},"e":{"df":32,"docs":{"104":{"tf":1.0},"109":{"tf":1.0},"11":{"tf":1.0},"115":{"tf":1.0},"116":{"tf":1.0},"122":{"tf":1.0},"123":{"tf":1.4142135623730951},"124":{"tf":1.4142135623730951},"129":{"tf":1.0},"145":{"tf":1.0},"165":{"tf":1.0},"167":{"tf":1.0},"175":{"tf":1.4142135623730951},"18":{"tf":1.4142135623730951},"198":{"tf":1.0},"201":{"tf":1.0},"22":{"tf":1.0},"222":{"tf":1.0},"226":{"tf":1.0},"231":{"tf":1.0},"237":{"tf":1.0},"241":{"tf":1.0},"248":{"tf":1.0},"25":{"tf":1.0},"258":{"tf":1.4142135623730951},"26":{"tf":1.7320508075688772},"262":{"tf":1.4142135623730951},"263":{"tf":1.0},"274":{"tf":1.0},"280":{"tf":1.0},"61":{"tf":1.4142135623730951},"84":{"tf":1.0}}},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"49":{"tf":1.0}}}}},"n":{"df":0,"docs":{},"e":{"df":2,"docs":{"241":{"tf":1.0},"86":{"tf":1.0}}},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"51":{"tf":1.0}}}}},"v":{"df":0,"docs":{},"e":{"df":1,"docs":{"286":{"tf":1.0}}}},"w":{"df":2,"docs":{"205":{"tf":1.4142135623730951},"4":{"tf":1.0}}}},"c":{"a":{"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"d":{"df":2,"docs":{"104":{"tf":1.4142135623730951},"105":{"tf":1.0}}},"df":0,"docs":{}}}}},"n":{"df":1,"docs":{"81":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":2,"docs":{"124":{"tf":1.4142135623730951},"19":{"tf":1.0}}}}}},"df":0,"docs":{}}},"h":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":4,"docs":{"189":{"tf":1.0},"226":{"tf":1.0},"242":{"tf":1.0},"274":{"tf":1.0}}}}},"df":0,"docs":{},"m":{"a":{"df":5,"docs":{"189":{"tf":1.0},"268":{"tf":1.0},"78":{"tf":1.0},"79":{"tf":1.0},"81":{"tf":3.0}}},"df":0,"docs":{},"e":{":":{"d":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"283":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":1,"docs":{"283":{"tf":1.0}}}}}}}},"df":0,"docs":{}}}},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"37":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"t":{"a":{"b":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"y":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"283":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":4,"docs":{"111":{"tf":1.0},"190":{"tf":1.0},"254":{"tf":1.0},"274":{"tf":1.0}}}}},"r":{"a":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"h":{"df":3,"docs":{"101":{"tf":1.0},"196":{"tf":1.0},"68":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"81":{"tf":1.0}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"_":{"c":{"a":{"df":0,"docs":{},"s":{"df":1,"docs":{"93":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"184":{"tf":1.4142135623730951}},"s":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":3,"docs":{"31":{"tf":1.0},"36":{"tf":1.4142135623730951},"73":{"tf":1.7320508075688772}}}}}}}}},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":28,"docs":{"105":{"tf":1.0},"11":{"tf":1.0},"14":{"tf":1.4142135623730951},"167":{"tf":1.0},"172":{"tf":1.0},"22":{"tf":1.0},"224":{"tf":1.0},"23":{"tf":1.0},"25":{"tf":1.7320508075688772},"252":{"tf":1.0},"253":{"tf":1.0},"26":{"tf":1.7320508075688772},"269":{"tf":1.7320508075688772},"27":{"tf":1.7320508075688772},"279":{"tf":1.0},"28":{"tf":1.0},"282":{"tf":1.0},"30":{"tf":2.0},"32":{"tf":1.0},"33":{"tf":1.0},"35":{"tf":2.0},"41":{"tf":1.0},"52":{"tf":1.4142135623730951},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"7":{"tf":2.0},"74":{"tf":1.0}},"s":{"/":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"22":{"tf":1.7320508075688772}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":1,"docs":{"64":{"tf":1.0}}}}}}}}},"d":{"df":0,"docs":{},"k":{"df":8,"docs":{"12":{"tf":2.0},"178":{"tf":1.0},"180":{"tf":1.0},"182":{"tf":1.0},"194":{"tf":1.7320508075688772},"259":{"tf":1.0},"274":{"tf":1.4142135623730951},"284":{"tf":1.0}}}},"df":4,"docs":{"11":{"tf":1.0},"14":{"tf":1.0},"274":{"tf":1.0},"93":{"tf":1.0}},"e":{"a":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"h":{"df":7,"docs":{"126":{"tf":1.4142135623730951},"187":{"tf":1.0},"201":{"tf":1.0},"205":{"tf":2.0},"206":{"tf":1.0},"45":{"tf":1.0},"6":{"tf":1.4142135623730951}},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"x":{"df":1,"docs":{"261":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"d":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"60":{"tf":1.0}}}}},"df":4,"docs":{"167":{"tf":1.0},"177":{"tf":1.0},"204":{"tf":2.23606797749979},"6":{"tf":1.7320508075688772}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":4,"docs":{"201":{"tf":1.0},"274":{"tf":1.4142135623730951},"275":{"tf":1.4142135623730951},"276":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":13,"docs":{"105":{"tf":1.0},"11":{"tf":1.4142135623730951},"12":{"tf":1.0},"200":{"tf":1.0},"201":{"tf":1.0},"209":{"tf":1.0},"210":{"tf":1.0},"214":{"tf":1.0},"227":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"73":{"tf":1.4142135623730951},"85":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"r":{"df":7,"docs":{"141":{"tf":1.4142135623730951},"143":{"tf":1.0},"146":{"tf":1.4142135623730951},"201":{"tf":1.0},"274":{"tf":1.4142135623730951},"63":{"tf":1.0},"64":{"tf":1.0}},"e":{"_":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"264":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"df":26,"docs":{"105":{"tf":1.0},"106":{"tf":1.0},"13":{"tf":1.0},"138":{"tf":1.0},"145":{"tf":1.0},"161":{"tf":1.0},"175":{"tf":1.0},"22":{"tf":1.4142135623730951},"226":{"tf":1.0},"228":{"tf":1.0},"245":{"tf":1.4142135623730951},"261":{"tf":1.0},"264":{"tf":1.0},"266":{"tf":1.0},"268":{"tf":2.0},"274":{"tf":2.0},"280":{"tf":1.0},"38":{"tf":1.0},"40":{"tf":1.0},"48":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"69":{"tf":1.0},"78":{"tf":1.0},"93":{"tf":1.0},"99":{"tf":1.0}},"m":{"df":7,"docs":{"128":{"tf":1.0},"152":{"tf":1.0},"200":{"tf":1.0},"226":{"tf":1.0},"276":{"tf":1.0},"277":{"tf":1.0},"61":{"tf":2.0}}},"n":{"df":1,"docs":{"250":{"tf":1.0}}}},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":9,"docs":{"11":{"tf":1.0},"117":{"tf":1.4142135623730951},"126":{"tf":1.4142135623730951},"209":{"tf":1.0},"253":{"tf":1.0},"280":{"tf":1.0},"62":{"tf":1.4142135623730951},"7":{"tf":1.0},"73":{"tf":1.0}}}},"df":0,"docs":{}},"f":{".":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"_":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"62":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":1,"docs":{"242":{"tf":1.0}}}},"m":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"239":{"tf":1.0}}}}},"df":0,"docs":{}},"n":{"d":{"/":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"c":{"df":1,"docs":{"254":{"tf":1.0}}},"df":0,"docs":{}}}},"df":8,"docs":{"128":{"tf":1.0},"226":{"tf":1.0},"231":{"tf":1.0},"245":{"tf":1.0},"259":{"tf":1.0},"283":{"tf":1.0},"48":{"tf":1.0},"7":{"tf":1.4142135623730951}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"98":{"tf":1.0}}}}},"t":{"df":4,"docs":{"201":{"tf":1.7320508075688772},"249":{"tf":1.0},"274":{"tf":1.0},"283":{"tf":1.4142135623730951}},"r":{"df":0,"docs":{},"i":{"df":4,"docs":{"150":{"tf":1.0},"154":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0}}},"y":{"/":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"152":{"tf":1.0}}}}}}}}}}}},"df":0,"docs":{}}}}},"p":{"a":{"df":0,"docs":{},"r":{"df":16,"docs":{"103":{"tf":1.0},"106":{"tf":1.0},"116":{"tf":1.0},"140":{"tf":1.0},"163":{"tf":1.0},"165":{"tf":1.0},"167":{"tf":1.0},"169":{"tf":1.0},"17":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.4142135623730951},"172":{"tf":1.7320508075688772},"20":{"tf":1.0},"220":{"tf":1.0},"221":{"tf":1.4142135623730951},"46":{"tf":1.0}}}},"df":0,"docs":{}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"37":{"tf":1.0}},"e":{"<":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"106":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"r":{"d":{"df":1,"docs":{"62":{"tf":1.0}}},"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"105":{"tf":1.0},"106":{"tf":1.0}}}},"df":0,"docs":{}},"v":{"df":8,"docs":{"107":{"tf":1.0},"167":{"tf":1.0},"196":{"tf":1.0},"249":{"tf":1.0},"252":{"tf":1.0},"26":{"tf":1.0},"273":{"tf":1.0},"274":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":24,"docs":{"189":{"tf":2.449489742783178},"190":{"tf":1.0},"194":{"tf":1.0},"201":{"tf":2.449489742783178},"225":{"tf":1.0},"226":{"tf":1.0},"229":{"tf":1.0},"232":{"tf":1.0},"233":{"tf":2.0},"236":{"tf":1.0},"242":{"tf":1.0},"244":{"tf":1.0},"245":{"tf":2.0},"246":{"tf":1.0},"248":{"tf":1.4142135623730951},"249":{"tf":2.23606797749979},"250":{"tf":1.4142135623730951},"254":{"tf":2.8284271247461903},"257":{"tf":1.0},"48":{"tf":1.0},"86":{"tf":1.0},"87":{"tf":1.0},"88":{"tf":1.7320508075688772},"89":{"tf":1.4142135623730951}}}},"i":{"c":{"df":83,"docs":{"0":{"tf":2.0},"1":{"tf":1.4142135623730951},"11":{"tf":1.4142135623730951},"114":{"tf":1.0},"118":{"tf":2.0},"119":{"tf":1.0},"12":{"tf":2.0},"120":{"tf":1.7320508075688772},"121":{"tf":1.0},"122":{"tf":2.23606797749979},"123":{"tf":1.4142135623730951},"124":{"tf":2.23606797749979},"125":{"tf":1.7320508075688772},"14":{"tf":1.0},"15":{"tf":1.7320508075688772},"16":{"tf":1.0},"161":{"tf":1.4142135623730951},"162":{"tf":1.0},"164":{"tf":1.0},"165":{"tf":1.4142135623730951},"167":{"tf":3.0},"175":{"tf":1.4142135623730951},"18":{"tf":2.449489742783178},"186":{"tf":1.0},"187":{"tf":1.0},"188":{"tf":1.0},"19":{"tf":1.4142135623730951},"194":{"tf":1.0},"197":{"tf":1.4142135623730951},"2":{"tf":1.4142135623730951},"20":{"tf":1.7320508075688772},"200":{"tf":2.449489742783178},"203":{"tf":1.0},"21":{"tf":1.4142135623730951},"218":{"tf":1.0},"22":{"tf":2.0},"220":{"tf":1.0},"225":{"tf":2.0},"226":{"tf":1.7320508075688772},"228":{"tf":1.0},"23":{"tf":1.7320508075688772},"232":{"tf":1.0},"236":{"tf":1.0},"24":{"tf":1.0},"244":{"tf":1.0},"25":{"tf":1.7320508075688772},"252":{"tf":1.7320508075688772},"253":{"tf":1.4142135623730951},"254":{"tf":1.0},"259":{"tf":1.7320508075688772},"260":{"tf":1.4142135623730951},"262":{"tf":1.4142135623730951},"265":{"tf":1.4142135623730951},"268":{"tf":1.7320508075688772},"274":{"tf":2.23606797749979},"276":{"tf":1.0},"28":{"tf":1.0},"283":{"tf":1.7320508075688772},"284":{"tf":1.0},"29":{"tf":1.0},"3":{"tf":1.7320508075688772},"30":{"tf":2.0},"32":{"tf":1.4142135623730951},"33":{"tf":1.0},"34":{"tf":1.4142135623730951},"35":{"tf":2.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":2.0},"40":{"tf":1.7320508075688772},"41":{"tf":1.0},"49":{"tf":1.4142135623730951},"52":{"tf":2.0},"53":{"tf":1.0},"54":{"tf":1.7320508075688772},"55":{"tf":1.4142135623730951},"56":{"tf":1.4142135623730951},"68":{"tf":1.7320508075688772},"69":{"tf":1.0},"7":{"tf":1.0},"74":{"tf":1.0},"8":{"tf":1.0},"9":{"tf":1.7320508075688772}},"e":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":1,"docs":{"242":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"242":{"tf":1.0}}}}}}},"s":{"%":{"2":{"df":0,"docs":{},"f":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"119":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{}},".":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"=":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"/":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"/":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"/":{"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"/":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"/":{"df":0,"docs":{},"o":{"df":0,"docs":{},"f":{"/":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"18":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"df":2,"docs":{"18":{"tf":1.0},"22":{"tf":2.6457513110645907}}}}},"df":0,"docs":{},"v":{"2":{".":{"b":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{".":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{".":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"k":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"/":{"d":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":1,"docs":{"266":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"y":{"/":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"266":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{".":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"/":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"c":{"%":{"2":{"df":0,"docs":{},"f":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"%":{"2":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{".":{"df":0,"docs":{},"j":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"271":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"/":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"/":{"df":0,"docs":{},"v":{"1":{"1":{"7":{".":{"0":{".":{".":{".":{"df":0,"docs":{},"v":{"1":{"1":{"8":{".":{"0":{"df":1,"docs":{"268":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"200":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"/":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"124":{"tf":1.0}}}}}}}}}}},"s":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"df":1,"docs":{"124":{"tf":1.0}}}},"y":{"df":0,"docs":{},"n":{"c":{"1":{"5":{"df":1,"docs":{"124":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":1,"docs":{"200":{"tf":1.0}}}}}}}}},"v":{"df":0,"docs":{},"i":{"a":{"d":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"124":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"w":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":1,"docs":{"124":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"277":{"tf":1.0}}}}}}}}},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"a":{"df":0,"docs":{},"z":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"d":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":2,"docs":{"29":{"tf":1.0},"34":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"/":{"5":{"3":{"0":{"2":{"df":1,"docs":{"186":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"7":{"4":{"5":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}},"6":{"4":{"df":1,"docs":{"268":{"tf":1.4142135623730951}}},"6":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}},"8":{"4":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":1,"docs":{"242":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"t":{"df":39,"docs":{"108":{"tf":2.0},"109":{"tf":2.0},"11":{"tf":2.0},"117":{"tf":2.449489742783178},"12":{"tf":2.0},"13":{"tf":1.4142135623730951},"134":{"tf":1.0},"17":{"tf":1.0},"170":{"tf":1.0},"179":{"tf":1.0},"186":{"tf":1.7320508075688772},"187":{"tf":1.4142135623730951},"188":{"tf":1.0},"190":{"tf":1.4142135623730951},"192":{"tf":1.7320508075688772},"193":{"tf":1.7320508075688772},"194":{"tf":1.0},"197":{"tf":1.7320508075688772},"198":{"tf":1.4142135623730951},"199":{"tf":1.0},"201":{"tf":2.0},"208":{"tf":1.7320508075688772},"213":{"tf":1.0},"232":{"tf":1.0},"233":{"tf":1.7320508075688772},"242":{"tf":1.0},"245":{"tf":1.0},"264":{"tf":1.4142135623730951},"268":{"tf":1.0},"277":{"tf":1.4142135623730951},"31":{"tf":1.0},"36":{"tf":1.0},"5":{"tf":1.0},"59":{"tf":1.7320508075688772},"60":{"tf":1.7320508075688772},"61":{"tf":1.0},"7":{"tf":1.0},"73":{"tf":1.0},"9":{"tf":1.0}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"d":{"df":0,"docs":{},"l":{"df":1,"docs":{"22":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"x":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":1,"docs":{"13":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}},"u":{"df":0,"docs":{},"p":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"11":{"tf":1.0}}}}},"df":15,"docs":{"10":{"tf":1.0},"11":{"tf":1.0},"12":{"tf":1.0},"122":{"tf":1.0},"13":{"tf":1.4142135623730951},"14":{"tf":1.0},"161":{"tf":1.4142135623730951},"163":{"tf":1.4142135623730951},"166":{"tf":1.4142135623730951},"167":{"tf":1.4142135623730951},"169":{"tf":1.0},"175":{"tf":2.0},"22":{"tf":1.7320508075688772},"224":{"tf":1.0},"73":{"tf":1.0}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":6,"docs":{"116":{"tf":1.0},"132":{"tf":1.0},"154":{"tf":1.0},"271":{"tf":1.0},"58":{"tf":1.0},"60":{"tf":1.0}}}}}},"h":{"a":{"2":{"5":{"6":{"df":1,"docs":{"280":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":5,"docs":{"105":{"tf":1.0},"106":{"tf":1.0},"142":{"tf":1.0},"283":{"tf":1.0},"58":{"tf":1.0}}}},"r":{"df":0,"docs":{},"e":{"df":13,"docs":{"0":{"tf":1.0},"132":{"tf":1.4142135623730951},"163":{"tf":1.0},"172":{"tf":1.0},"200":{"tf":2.23606797749979},"221":{"tf":1.0},"222":{"tf":1.4142135623730951},"225":{"tf":1.7320508075688772},"248":{"tf":1.0},"257":{"tf":1.0},"60":{"tf":1.7320508075688772},"79":{"tf":1.0},"81":{"tf":1.0}}}},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"280":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":2,"docs":{"12":{"tf":1.0},"167":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"p":{"df":13,"docs":{"187":{"tf":1.0},"219":{"tf":1.4142135623730951},"220":{"tf":1.0},"221":{"tf":1.0},"251":{"tf":2.0},"252":{"tf":1.0},"253":{"tf":1.0},"262":{"tf":1.0},"267":{"tf":1.4142135623730951},"268":{"tf":1.4142135623730951},"273":{"tf":1.0},"64":{"tf":1.4142135623730951},"73":{"tf":1.0}},"p":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"267":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"252":{"tf":1.0}}}}},"df":3,"docs":{"179":{"tf":1.0},"241":{"tf":1.0},"81":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"209":{"tf":1.0}}}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"184":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"l":{"d":{"df":0,"docs":{},"n":{"'":{"df":0,"docs":{},"t":{"df":2,"docs":{"241":{"tf":1.0},"258":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"y":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"_":{"c":{"a":{"df":0,"docs":{},"s":{"df":1,"docs":{"108":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"w":{"df":9,"docs":{"12":{"tf":1.0},"123":{"tf":1.0},"167":{"tf":1.0},"254":{"tf":1.0},"255":{"tf":1.0},"256":{"tf":1.0},"257":{"tf":1.0},"258":{"tf":1.0},"282":{"tf":1.0}}}}},"i":{"d":{"df":0,"docs":{},"e":{"b":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"284":{"tf":1.0}}}},"df":0,"docs":{}},"df":4,"docs":{"12":{"tf":1.4142135623730951},"233":{"tf":1.0},"254":{"tf":1.0},"69":{"tf":1.0}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":6,"docs":{"189":{"tf":1.0},"193":{"tf":1.0},"194":{"tf":1.0},"196":{"tf":1.0},"198":{"tf":1.0},"201":{"tf":2.23606797749979}}}}}},"df":3,"docs":{"274":{"tf":2.449489742783178},"283":{"tf":2.449489742783178},"7":{"tf":1.0}},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"c":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"17":{"tf":1.0}}}}}}},"df":6,"docs":{"158":{"tf":1.0},"196":{"tf":1.0},"201":{"tf":1.0},"203":{"tf":1.0},"205":{"tf":1.0},"214":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}}},"l":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":6,"docs":{"161":{"tf":1.0},"162":{"tf":1.0},"163":{"tf":1.0},"169":{"tf":1.0},"170":{"tf":1.0},"175":{"tf":1.0}}}}},"df":0,"docs":{}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"r":{"df":6,"docs":{"143":{"tf":1.0},"158":{"tf":1.0},"163":{"tf":1.0},"171":{"tf":1.0},"200":{"tf":1.0},"81":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"106":{"tf":1.0}}}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"l":{"df":7,"docs":{"106":{"tf":1.4142135623730951},"118":{"tf":1.0},"128":{"tf":1.0},"60":{"tf":1.0},"78":{"tf":1.0},"81":{"tf":1.0},"87":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":7,"docs":{"106":{"tf":1.0},"115":{"tf":1.0},"124":{"tf":1.0},"152":{"tf":1.0},"226":{"tf":1.0},"231":{"tf":1.0},"252":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"129":{"tf":1.0},"159":{"tf":1.0}}}}},"i":{"c":{"df":1,"docs":{"178":{"tf":1.0}}},"df":5,"docs":{"128":{"tf":1.0},"149":{"tf":1.0},"222":{"tf":1.0},"231":{"tf":1.0},"26":{"tf":1.0}},"f":{"df":0,"docs":{},"i":{"df":4,"docs":{"103":{"tf":1.0},"164":{"tf":1.0},"172":{"tf":1.0},"69":{"tf":1.0}}}}}}},"u":{"df":0,"docs":{},"l":{"df":1,"docs":{"175":{"tf":1.0}},"t":{"a":{"df":0,"docs":{},"n":{"df":2,"docs":{"124":{"tf":1.0},"205":{"tf":1.0}}}},"df":0,"docs":{}}}}},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"l":{"df":19,"docs":{"115":{"tf":1.0},"16":{"tf":1.0},"163":{"tf":1.0},"166":{"tf":1.0},"167":{"tf":1.0},"171":{"tf":2.0},"172":{"tf":1.4142135623730951},"174":{"tf":1.0},"175":{"tf":1.4142135623730951},"22":{"tf":1.0},"220":{"tf":1.4142135623730951},"221":{"tf":1.0},"225":{"tf":1.4142135623730951},"226":{"tf":1.4142135623730951},"228":{"tf":1.4142135623730951},"232":{"tf":1.4142135623730951},"249":{"tf":1.0},"282":{"tf":1.0},"46":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"130":{"tf":1.7320508075688772},"78":{"tf":1.0}}}}}}}}},"t":{"df":1,"docs":{"40":{"tf":1.0}},"e":{"df":2,"docs":{"205":{"tf":1.4142135623730951},"218":{"tf":1.0}}},"u":{"a":{"df":0,"docs":{},"t":{"df":5,"docs":{"115":{"tf":1.0},"128":{"tf":1.0},"154":{"tf":1.0},"220":{"tf":1.0},"249":{"tf":1.0}}}},"df":0,"docs":{}}},"z":{"df":0,"docs":{},"e":{"df":12,"docs":{"165":{"tf":1.0},"170":{"tf":1.0},"205":{"tf":1.7320508075688772},"206":{"tf":1.4142135623730951},"208":{"tf":2.0},"209":{"tf":1.7320508075688772},"212":{"tf":1.0},"218":{"tf":1.0},"220":{"tf":1.0},"222":{"tf":1.0},"280":{"tf":1.0},"6":{"tf":1.0}}}}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"252":{"tf":1.0}}}}}}},"t":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"167":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"l":{"a":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"274":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":1,"docs":{"236":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":2,"docs":{"128":{"tf":1.0},"243":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"w":{"df":3,"docs":{"183":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0}}}}},"m":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":6,"docs":{"104":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"170":{"tf":1.0},"227":{"tf":1.0},"229":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"59":{"tf":1.4142135623730951},"6":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"26":{"tf":1.0}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":5,"docs":{"52":{"tf":2.0},"53":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0}}}}}},"n":{"a":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"_":{"c":{"a":{"df":0,"docs":{},"s":{"df":1,"docs":{"93":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"$":{"df":0,"docs":{},"n":{"df":1,"docs":{"22":{"tf":1.0}}}},"df":2,"docs":{"22":{"tf":1.0},"268":{"tf":1.0}}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"37":{"tf":1.0}}}}}}}},"o":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":1,"docs":{"274":{"tf":1.0}}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"r":{"df":2,"docs":{"274":{"tf":1.0},"64":{"tf":1.0}}}},"df":0,"docs":{}}}},"l":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"200":{"tf":1.0}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"t":{"df":4,"docs":{"152":{"tf":1.4142135623730951},"187":{"tf":1.0},"59":{"tf":1.4142135623730951},"60":{"tf":1.0}}}},"v":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"115":{"tf":1.0}}}},"df":0,"docs":{}},"df":1,"docs":{"194":{"tf":1.0}}}},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"i":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":1,"docs":{"22":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"122":{"tf":1.0}}}},"t":{"df":0,"docs":{},"h":{"df":24,"docs":{"103":{"tf":1.0},"106":{"tf":1.0},"117":{"tf":1.0},"124":{"tf":1.0},"197":{"tf":1.4142135623730951},"200":{"tf":1.0},"22":{"tf":1.0},"224":{"tf":1.4142135623730951},"237":{"tf":1.0},"242":{"tf":1.4142135623730951},"26":{"tf":1.0},"277":{"tf":1.0},"280":{"tf":1.0},"283":{"tf":1.0},"43":{"tf":1.0},"58":{"tf":1.4142135623730951},"6":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.4142135623730951},"70":{"tf":1.0},"72":{"tf":1.0},"73":{"tf":1.7320508075688772},"79":{"tf":1.0},"84":{"tf":1.0}}},"i":{"df":0,"docs":{},"m":{"df":3,"docs":{"124":{"tf":1.0},"158":{"tf":1.0},"43":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"129":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}},"w":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"t":{"df":4,"docs":{"115":{"tf":1.0},"116":{"tf":1.0},"235":{"tf":1.0},"261":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"21":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"44":{"tf":1.0},"58":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"6":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"104":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"r":{"c":{"df":19,"docs":{"1":{"tf":1.0},"109":{"tf":1.0},"11":{"tf":1.0},"163":{"tf":1.0},"164":{"tf":1.0},"165":{"tf":1.0},"172":{"tf":1.7320508075688772},"2":{"tf":1.0},"253":{"tf":1.0},"263":{"tf":1.0},"274":{"tf":1.0},"37":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.4142135623730951},"7":{"tf":1.0},"73":{"tf":2.0},"74":{"tf":1.4142135623730951},"81":{"tf":1.0},"98":{"tf":1.7320508075688772}},"e":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"74":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"p":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":1,"docs":{"43":{"tf":1.0}}}},"w":{"df":0,"docs":{},"n":{"df":2,"docs":{"115":{"tf":1.0},"22":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"221":{"tf":1.0},"236":{"tf":1.0}}}},"df":0,"docs":{},"f":{"df":22,"docs":{"103":{"tf":1.0},"105":{"tf":1.0},"116":{"tf":1.0},"12":{"tf":1.0},"123":{"tf":1.0},"128":{"tf":1.0},"175":{"tf":1.0},"190":{"tf":1.0},"200":{"tf":1.4142135623730951},"214":{"tf":1.0},"22":{"tf":1.4142135623730951},"225":{"tf":1.4142135623730951},"227":{"tf":1.4142135623730951},"229":{"tf":1.0},"235":{"tf":1.4142135623730951},"279":{"tf":1.0},"280":{"tf":1.0},"282":{"tf":1.0},"45":{"tf":1.0},"58":{"tf":1.0},"70":{"tf":1.0},"72":{"tf":1.0}},"i":{"df":10,"docs":{"123":{"tf":1.7320508075688772},"124":{"tf":1.4142135623730951},"22":{"tf":1.4142135623730951},"222":{"tf":1.0},"236":{"tf":1.0},"245":{"tf":1.0},"252":{"tf":1.4142135623730951},"263":{"tf":1.0},"274":{"tf":1.0},"37":{"tf":1.0}}}}}},"d":{"df":1,"docs":{"17":{"tf":1.0}}},"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"104":{"tf":1.0}}},"df":0,"docs":{},"t":{"df":1,"docs":{"201":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"184":{"tf":1.0}}}}}}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":4,"docs":{"163":{"tf":1.0},"198":{"tf":1.0},"225":{"tf":1.0},"40":{"tf":1.4142135623730951}}}}},"r":{"a":{"df":0,"docs":{},"y":{"df":1,"docs":{"227":{"tf":1.0}}}},"df":0,"docs":{}}},"q":{"df":0,"docs":{},"l":{"/":{"df":0,"docs":{},"s":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":1,"docs":{"62":{"tf":1.0}}}}}}}}},"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{":":{":":{"d":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"s":{":":{":":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"_":{"d":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"(":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":1,"docs":{"62":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"(":{"&":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":1,"docs":{"62":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"62":{"tf":1.0}}}}}}}}}},"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":4,"docs":{"11":{"tf":1.7320508075688772},"140":{"tf":1.4142135623730951},"264":{"tf":1.4142135623730951},"67":{"tf":1.0}}}}}}}},"df":3,"docs":{"124":{"tf":1.0},"62":{"tf":2.6457513110645907},"81":{"tf":1.7320508075688772}},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"'":{"df":1,"docs":{"81":{"tf":1.0}}},"_":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"_":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"264":{"tf":1.0}}}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}}}}}}},"df":7,"docs":{"140":{"tf":1.0},"149":{"tf":1.0},"264":{"tf":2.0},"62":{"tf":1.0},"79":{"tf":2.0},"81":{"tf":1.0},"82":{"tf":1.0}}}}}}},"r":{"c":{"/":{"*":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"<":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{">":{".":{"df":0,"docs":{},"u":{"d":{"df":0,"docs":{},"l":{"df":1,"docs":{"104":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"104":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}},"_":{"df":0,"docs":{},"m":{"df":0,"docs":{},"s":{"df":0,"docs":{},"g":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":1,"docs":{"106":{"tf":1.0}}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"_":{"c":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{">":{".":{"df":0,"docs":{},"u":{"d":{"df":0,"docs":{},"l":{"df":1,"docs":{"73":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"60":{"tf":1.7320508075688772}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"b":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"60":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{".":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"<":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{">":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"f":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"106":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"60":{"tf":1.7320508075688772}}}},"df":0,"docs":{}}}}}}},"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":1,"docs":{"224":{"tf":1.0}}}}},"df":5,"docs":{"103":{"tf":2.23606797749979},"60":{"tf":1.0},"61":{"tf":2.449489742783178},"70":{"tf":1.4142135623730951},"72":{"tf":1.0}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"11":{"tf":1.0}}}},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":4,"docs":{"192":{"tf":1.0},"258":{"tf":1.0},"264":{"tf":1.0},"274":{"tf":1.0}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":1,"docs":{"260":{"tf":1.0}}}},"g":{"df":0,"docs":{},"e":{"df":10,"docs":{"131":{"tf":1.0},"248":{"tf":1.0},"249":{"tf":2.6457513110645907},"250":{"tf":1.4142135623730951},"267":{"tf":1.0},"271":{"tf":1.4142135623730951},"273":{"tf":1.4142135623730951},"30":{"tf":1.0},"35":{"tf":1.0},"48":{"tf":1.0}}}},"l":{"df":0,"docs":{},"e":{"df":1,"docs":{"263":{"tf":1.0}}}},"n":{"d":{"a":{"df":0,"docs":{},"r":{"d":{"df":3,"docs":{"107":{"tf":1.0},"141":{"tf":1.0},"8":{"tf":1.0}}},"df":0,"docs":{}}},"df":3,"docs":{"198":{"tf":1.0},"220":{"tf":1.0},"237":{"tf":1.0}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"t":{"df":26,"docs":{"101":{"tf":1.4142135623730951},"105":{"tf":1.0},"106":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"121":{"tf":1.0},"122":{"tf":1.0},"138":{"tf":1.0},"149":{"tf":1.0},"158":{"tf":1.0},"214":{"tf":1.0},"22":{"tf":1.0},"225":{"tf":1.4142135623730951},"227":{"tf":1.4142135623730951},"233":{"tf":1.0},"244":{"tf":1.0},"245":{"tf":1.0},"250":{"tf":1.4142135623730951},"257":{"tf":1.4142135623730951},"268":{"tf":1.0},"269":{"tf":1.0},"37":{"tf":1.0},"58":{"tf":1.4142135623730951},"62":{"tf":1.0},"75":{"tf":1.4142135623730951},"84":{"tf":1.4142135623730951}},"u":{"df":0,"docs":{},"p":{"df":8,"docs":{"134":{"tf":1.0},"140":{"tf":1.0},"176":{"tf":1.4142135623730951},"180":{"tf":1.0},"184":{"tf":1.0},"242":{"tf":1.0},"278":{"tf":1.0},"283":{"tf":1.7320508075688772}}}}}},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"115":{"tf":1.0}}}},"t":{"df":0,"docs":{},"e":{"df":14,"docs":{"189":{"tf":1.0},"205":{"tf":1.0},"225":{"tf":1.0},"226":{"tf":1.4142135623730951},"229":{"tf":4.69041575982343},"233":{"tf":2.8284271247461903},"236":{"tf":1.4142135623730951},"237":{"tf":1.0},"239":{"tf":1.7320508075688772},"242":{"tf":2.23606797749979},"245":{"tf":2.0},"249":{"tf":2.0},"283":{"tf":2.0},"87":{"tf":1.0}},"f":{"df":0,"docs":{},"x":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"n":{"d":{"a":{"df":1,"docs":{"283":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}}},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":10,"docs":{"136":{"tf":1.4142135623730951},"137":{"tf":1.0},"140":{"tf":1.4142135623730951},"149":{"tf":1.4142135623730951},"161":{"tf":1.4142135623730951},"177":{"tf":1.4142135623730951},"187":{"tf":1.4142135623730951},"203":{"tf":1.4142135623730951},"205":{"tf":1.4142135623730951},"62":{"tf":1.0}}}}}}},"i":{"c":{"df":10,"docs":{"167":{"tf":1.0},"242":{"tf":1.0},"251":{"tf":1.0},"278":{"tf":1.0},"28":{"tf":1.0},"280":{"tf":1.0},"282":{"tf":1.0},"284":{"tf":1.0},"33":{"tf":1.0},"93":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"214":{"tf":1.4142135623730951}}}}},"u":{"df":10,"docs":{"139":{"tf":1.0},"148":{"tf":1.0},"160":{"tf":1.0},"171":{"tf":1.0},"176":{"tf":1.0},"186":{"tf":1.0},"202":{"tf":1.0},"22":{"tf":1.0},"242":{"tf":1.0},"257":{"tf":1.4142135623730951}}}}},"d":{":":{":":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{":":{":":{"df":0,"docs":{},"w":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"v":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"(":{"0":{"df":1,"docs":{"117":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":1,"docs":{"62":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"220":{"tf":1.0}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"62":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":28,"docs":{"105":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.4142135623730951},"111":{"tf":1.0},"117":{"tf":1.7320508075688772},"122":{"tf":1.0},"14":{"tf":1.0},"15":{"tf":1.0},"16":{"tf":1.0},"21":{"tf":1.0},"22":{"tf":1.7320508075688772},"25":{"tf":2.6457513110645907},"264":{"tf":1.0},"268":{"tf":1.0},"27":{"tf":1.4142135623730951},"274":{"tf":3.0},"279":{"tf":1.0},"282":{"tf":1.4142135623730951},"29":{"tf":1.4142135623730951},"30":{"tf":1.0},"31":{"tf":1.4142135623730951},"32":{"tf":1.0},"34":{"tf":1.4142135623730951},"35":{"tf":1.0},"36":{"tf":1.4142135623730951},"37":{"tf":1.0},"7":{"tf":1.0},"73":{"tf":1.0}}}},"i":{"c":{"df":0,"docs":{},"k":{"df":3,"docs":{"180":{"tf":1.0},"201":{"tf":1.0},"60":{"tf":1.0}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":18,"docs":{"103":{"tf":1.0},"11":{"tf":1.0},"116":{"tf":1.0},"124":{"tf":1.4142135623730951},"182":{"tf":1.0},"184":{"tf":1.0},"200":{"tf":1.0},"209":{"tf":1.0},"217":{"tf":1.0},"241":{"tf":1.0},"242":{"tf":1.0},"262":{"tf":1.0},"263":{"tf":1.0},"48":{"tf":1.0},"49":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.4142135623730951},"75":{"tf":1.0}}}},"t":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"252":{"tf":1.0}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":2,"docs":{"231":{"tf":1.0},"85":{"tf":1.0}}}}},"r":{"a":{"df":0,"docs":{},"g":{"df":13,"docs":{"123":{"tf":1.0},"124":{"tf":1.4142135623730951},"140":{"tf":1.4142135623730951},"229":{"tf":2.449489742783178},"232":{"tf":1.0},"254":{"tf":1.4142135623730951},"257":{"tf":2.0},"264":{"tf":1.4142135623730951},"273":{"tf":1.0},"284":{"tf":1.0},"78":{"tf":1.4142135623730951},"79":{"tf":1.0},"86":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":18,"docs":{"130":{"tf":1.7320508075688772},"131":{"tf":1.7320508075688772},"184":{"tf":1.0},"187":{"tf":1.0},"229":{"tf":1.0},"231":{"tf":1.0},"232":{"tf":3.605551275463989},"237":{"tf":1.0},"242":{"tf":3.1622776601683795},"245":{"tf":1.7320508075688772},"248":{"tf":2.0},"254":{"tf":1.4142135623730951},"78":{"tf":2.8284271247461903},"81":{"tf":1.0},"83":{"tf":1.0},"84":{"tf":1.0},"86":{"tf":1.0},"87":{"tf":1.0}},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"i":{"df":1,"docs":{"87":{"tf":1.7320508075688772}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"i":{"df":2,"docs":{"139":{"tf":1.0},"176":{"tf":1.0}}}}},"r":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"167":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}},"n":{"df":0,"docs":{},"g":{"df":2,"docs":{"158":{"tf":1.0},"221":{"tf":1.0}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":9,"docs":{"219":{"tf":1.4142135623730951},"251":{"tf":1.4142135623730951},"254":{"tf":1.0},"255":{"tf":1.0},"256":{"tf":1.0},"257":{"tf":1.4142135623730951},"258":{"tf":1.0},"43":{"tf":1.0},"84":{"tf":1.0}}}}}},"w":{"df":2,"docs":{"236":{"tf":1.4142135623730951},"242":{"tf":1.7320508075688772}}}},"df":1,"docs":{"242":{"tf":1.0}},"e":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"257":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"i":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"172":{"tf":1.0},"43":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":3,"docs":{"200":{"tf":1.0},"201":{"tf":1.0},"242":{"tf":1.0}}}}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"/":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"189":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":5,"docs":{"116":{"tf":1.0},"242":{"tf":2.23606797749979},"25":{"tf":1.0},"279":{"tf":1.0},"97":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"201":{"tf":1.0}}}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"189":{"tf":1.0}}}}}}},"u":{"c":{"df":0,"docs":{},"t":{"df":8,"docs":{"105":{"tf":1.0},"106":{"tf":1.4142135623730951},"108":{"tf":1.0},"109":{"tf":1.0},"242":{"tf":2.6457513110645907},"249":{"tf":1.0},"93":{"tf":1.0},"94":{"tf":1.0}},"s":{"a":{"c":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"283":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"r":{"df":15,"docs":{"103":{"tf":1.0},"104":{"tf":1.0},"106":{"tf":2.23606797749979},"136":{"tf":1.0},"137":{"tf":1.0},"138":{"tf":1.0},"175":{"tf":1.0},"241":{"tf":1.0},"242":{"tf":2.0},"60":{"tf":1.0},"69":{"tf":1.0},"70":{"tf":1.0},"72":{"tf":1.7320508075688772},"76":{"tf":1.7320508075688772},"83":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}}},"u":{"b":{"df":1,"docs":{"200":{"tf":1.0}}},"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"21":{"tf":1.0}}}},"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":7,"docs":{"11":{"tf":1.0},"117":{"tf":1.7320508075688772},"12":{"tf":1.4142135623730951},"126":{"tf":1.4142135623730951},"13":{"tf":1.0},"19":{"tf":1.0},"37":{"tf":1.0}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":3,"docs":{"13":{"tf":1.0},"226":{"tf":1.0},"90":{"tf":1.0}}}}},"y":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":6,"docs":{"103":{"tf":2.449489742783178},"104":{"tf":1.0},"138":{"tf":1.0},"37":{"tf":1.4142135623730951},"93":{"tf":1.0},"99":{"tf":1.0}}}}}},"u":{"b":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":4,"docs":{"252":{"tf":1.7320508075688772},"70":{"tf":1.0},"72":{"tf":1.4142135623730951},"73":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}},"df":3,"docs":{"105":{"tf":1.7320508075688772},"73":{"tf":1.0},"83":{"tf":1.0}},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":1,"docs":{"200":{"tf":1.7320508075688772}}}}},"df":0,"docs":{}}},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"2":{"tf":1.0},"226":{"tf":1.0},"8":{"tf":1.0}}}},"df":0,"docs":{}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"226":{"tf":1.0}}}},"t":{"df":2,"docs":{"226":{"tf":1.0},"7":{"tf":2.449489742783178}}}},"o":{"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":5,"docs":{"11":{"tf":1.0},"167":{"tf":1.7320508075688772},"60":{"tf":1.0},"61":{"tf":1.0},"83":{"tf":1.0}}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"117":{"tf":1.0}}}}}}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"22":{"tf":1.0}}}}}},"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"22":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}}}},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"283":{"tf":1.0}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":2,"docs":{"274":{"tf":1.0},"283":{"tf":1.0}}}},"t":{"df":4,"docs":{"165":{"tf":1.0},"167":{"tf":1.0},"228":{"tf":1.0},"54":{"tf":1.0}}}},"t":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"59":{"tf":1.0}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"22":{"tf":1.4142135623730951},"52":{"tf":1.0}}}}}}},"y":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":1,"docs":{"11":{"tf":1.0}}}}}}}},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":2,"docs":{"108":{"tf":1.0},"109":{"tf":1.0}}}}}},"c":{"c":{"df":0,"docs":{},"e":{"df":3,"docs":{"104":{"tf":1.0},"208":{"tf":1.0},"212":{"tf":1.7320508075688772}},"e":{"d":{"df":1,"docs":{"209":{"tf":1.0}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"s":{"df":8,"docs":{"12":{"tf":1.0},"204":{"tf":1.0},"208":{"tf":1.0},"214":{"tf":1.0},"216":{"tf":2.23606797749979},"217":{"tf":1.0},"266":{"tf":1.0},"271":{"tf":1.4142135623730951}},"f":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":2,"docs":{"106":{"tf":1.0},"11":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{},"h":{"df":23,"docs":{"102":{"tf":1.0},"109":{"tf":1.0},"112":{"tf":1.0},"115":{"tf":1.0},"16":{"tf":1.0},"188":{"tf":1.0},"190":{"tf":1.0},"196":{"tf":1.4142135623730951},"220":{"tf":1.0},"222":{"tf":1.0},"223":{"tf":1.0},"226":{"tf":1.0},"228":{"tf":1.0},"229":{"tf":1.0},"231":{"tf":1.0},"236":{"tf":1.0},"237":{"tf":1.0},"49":{"tf":1.0},"51":{"tf":1.0},"58":{"tf":1.0},"62":{"tf":1.0},"64":{"tf":1.0},"7":{"tf":1.0}}}},"d":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"213":{"tf":1.0}}}}}}},"df":0,"docs":{},"o":{"df":2,"docs":{"11":{"tf":2.23606797749979},"13":{"tf":1.0}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":2,"docs":{"194":{"tf":1.0},"274":{"tf":1.0}}}},"df":0,"docs":{},"x":{"df":1,"docs":{"22":{"tf":1.0}}}}}},"g":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":7,"docs":{"200":{"tf":1.0},"204":{"tf":1.0},"209":{"tf":1.0},"214":{"tf":2.23606797749979},"217":{"tf":1.7320508075688772},"225":{"tf":1.0},"268":{"tf":1.7320508075688772}}}}}}},"i":{"df":0,"docs":{},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":8,"docs":{"122":{"tf":1.0},"187":{"tf":1.4142135623730951},"189":{"tf":1.0},"191":{"tf":1.0},"192":{"tf":1.0},"197":{"tf":1.0},"223":{"tf":1.0},"64":{"tf":1.0}}}},"df":0,"docs":{}},"df":9,"docs":{"223":{"tf":1.0},"41":{"tf":1.0},"43":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"48":{"tf":1.0},"49":{"tf":1.7320508075688772},"58":{"tf":1.0},"7":{"tf":1.4142135623730951}}}},"m":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"84":{"tf":1.0}},"i":{"df":2,"docs":{"64":{"tf":1.7320508075688772},"7":{"tf":1.0}}},"y":{".":{"df":0,"docs":{},"m":{"d":{"df":1,"docs":{"284":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"d":{"df":1,"docs":{"90":{"tf":1.0}}},"df":0,"docs":{}}}}},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":4,"docs":{"242":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"78":{"tf":1.0}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"/":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":1,"docs":{"59":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"df":1,"docs":{"62":{"tf":1.4142135623730951}}}},"y":{"df":0,"docs":{},"n":{"c":{"1":{"5":{"df":2,"docs":{"244":{"tf":1.0},"246":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"[":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"200":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":34,"docs":{"103":{"tf":1.0},"104":{"tf":1.0},"116":{"tf":1.0},"124":{"tf":1.4142135623730951},"128":{"tf":1.0},"140":{"tf":1.0},"141":{"tf":1.0},"145":{"tf":1.0},"16":{"tf":1.0},"163":{"tf":1.0},"167":{"tf":1.0},"170":{"tf":1.7320508075688772},"171":{"tf":1.0},"172":{"tf":1.0},"175":{"tf":1.4142135623730951},"18":{"tf":1.0},"189":{"tf":1.0},"190":{"tf":1.0},"193":{"tf":1.0},"194":{"tf":1.0},"196":{"tf":1.0},"200":{"tf":2.23606797749979},"212":{"tf":1.0},"22":{"tf":2.0},"223":{"tf":1.0},"231":{"tf":1.0},"242":{"tf":1.4142135623730951},"263":{"tf":1.0},"274":{"tf":1.0},"282":{"tf":1.4142135623730951},"38":{"tf":1.0},"62":{"tf":1.4142135623730951},"79":{"tf":1.0},"81":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"e":{"df":18,"docs":{"102":{"tf":1.0},"104":{"tf":1.0},"106":{"tf":1.0},"11":{"tf":1.7320508075688772},"117":{"tf":1.4142135623730951},"120":{"tf":1.0},"126":{"tf":1.0},"200":{"tf":1.0},"201":{"tf":1.0},"21":{"tf":1.0},"226":{"tf":1.0},"241":{"tf":1.0},"25":{"tf":1.4142135623730951},"26":{"tf":1.0},"270":{"tf":1.0},"36":{"tf":1.0},"7":{"tf":1.0},"73":{"tf":1.4142135623730951}}},"f":{"a":{"c":{"df":3,"docs":{"105":{"tf":1.0},"26":{"tf":1.0},"61":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":2,"docs":{"260":{"tf":1.0},"261":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"282":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"137":{"tf":1.0}}}}},"df":0,"docs":{}}}},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"'":{"df":1,"docs":{"175":{"tf":1.0}}},".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"74":{"tf":1.0}}}}},"/":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":0,"docs":{},"f":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"d":{"df":1,"docs":{"119":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":55,"docs":{"101":{"tf":1.0},"103":{"tf":2.449489742783178},"104":{"tf":1.0},"109":{"tf":3.605551275463989},"116":{"tf":1.0},"119":{"tf":1.0},"125":{"tf":1.0},"134":{"tf":1.0},"160":{"tf":1.4142135623730951},"162":{"tf":1.0},"163":{"tf":2.0},"164":{"tf":1.0},"165":{"tf":2.23606797749979},"166":{"tf":1.0},"167":{"tf":3.605551275463989},"169":{"tf":1.7320508075688772},"170":{"tf":2.0},"171":{"tf":3.0},"172":{"tf":3.3166247903554},"174":{"tf":2.449489742783178},"175":{"tf":1.4142135623730951},"196":{"tf":1.0},"219":{"tf":1.4142135623730951},"225":{"tf":1.0},"23":{"tf":2.8284271247461903},"24":{"tf":1.0},"241":{"tf":2.449489742783178},"25":{"tf":1.7320508075688772},"251":{"tf":2.6457513110645907},"252":{"tf":1.4142135623730951},"253":{"tf":3.4641016151377544},"26":{"tf":2.23606797749979},"268":{"tf":1.4142135623730951},"271":{"tf":1.7320508075688772},"272":{"tf":1.4142135623730951},"274":{"tf":2.23606797749979},"28":{"tf":1.0},"29":{"tf":2.23606797749979},"30":{"tf":1.4142135623730951},"31":{"tf":3.3166247903554},"32":{"tf":2.8284271247461903},"33":{"tf":1.0},"34":{"tf":2.449489742783178},"35":{"tf":1.4142135623730951},"36":{"tf":3.3166247903554},"46":{"tf":2.449489742783178},"66":{"tf":1.4142135623730951},"68":{"tf":1.0},"7":{"tf":1.4142135623730951},"71":{"tf":1.4142135623730951},"72":{"tf":2.449489742783178},"73":{"tf":2.8284271247461903},"74":{"tf":2.8284271247461903},"95":{"tf":1.4142135623730951},"96":{"tf":1.4142135623730951}},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"7":{"tf":1.0}}}},"df":0,"docs":{}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":1,"docs":{"7":{"tf":1.0}}}}}}}},"t":{"c":{"df":0,"docs":{},"h":{"df":2,"docs":{"208":{"tf":1.0},"26":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"y":{"df":0,"docs":{},"m":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":3,"docs":{"175":{"tf":1.0},"274":{"tf":1.0},"282":{"tf":1.0}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"k":{"df":1,"docs":{"11":{"tf":1.0}}}}}}},"n":{"c":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"242":{"tf":1.0}}}}}}},"df":0,"docs":{}},"1":{"5":{"_":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"d":{"df":0,"docs":{},"s":{"_":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"_":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"94":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":4,"docs":{"124":{"tf":1.0},"244":{"tf":1.0},"245":{"tf":1.0},"249":{"tf":1.4142135623730951}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"242":{"tf":1.0}}}}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"_":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"83":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"84":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"88":{"tf":1.7320508075688772}}}}}}}},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"83":{"tf":1.0}}}}}}}}},"m":{"a":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"244":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"245":{"tf":1.4142135623730951}}}}}}}}}},"a":{"b":{"df":0,"docs":{},"l":{"df":16,"docs":{"75":{"tf":2.0},"76":{"tf":1.0},"77":{"tf":1.0},"78":{"tf":1.0},"79":{"tf":1.0},"80":{"tf":1.0},"81":{"tf":1.0},"82":{"tf":1.0},"83":{"tf":1.0},"84":{"tf":1.0},"85":{"tf":1.0},"86":{"tf":1.0},"87":{"tf":1.0},"88":{"tf":1.0},"89":{"tf":1.0},"90":{"tf":1.0}},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"130":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}}}}}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"250":{"tf":1.7320508075688772},"62":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":54,"docs":{"0":{"tf":1.0},"1":{"tf":1.4142135623730951},"145":{"tf":1.0},"156":{"tf":1.0},"206":{"tf":1.4142135623730951},"213":{"tf":1.4142135623730951},"217":{"tf":1.0},"218":{"tf":1.4142135623730951},"219":{"tf":2.23606797749979},"225":{"tf":3.7416573867739413},"226":{"tf":3.7416573867739413},"227":{"tf":1.0},"228":{"tf":2.449489742783178},"229":{"tf":2.0},"230":{"tf":1.0},"231":{"tf":2.23606797749979},"232":{"tf":2.23606797749979},"233":{"tf":2.23606797749979},"234":{"tf":2.0},"235":{"tf":1.0},"236":{"tf":1.7320508075688772},"237":{"tf":1.0},"238":{"tf":1.0},"239":{"tf":1.0},"240":{"tf":1.0},"241":{"tf":2.0},"242":{"tf":4.0},"243":{"tf":2.23606797749979},"244":{"tf":2.8284271247461903},"245":{"tf":3.3166247903554},"246":{"tf":2.449489742783178},"247":{"tf":1.0},"248":{"tf":1.0},"249":{"tf":1.7320508075688772},"250":{"tf":2.0},"254":{"tf":3.3166247903554},"255":{"tf":2.23606797749979},"256":{"tf":2.23606797749979},"257":{"tf":2.6457513110645907},"258":{"tf":2.0},"264":{"tf":1.4142135623730951},"284":{"tf":1.0},"48":{"tf":2.6457513110645907},"51":{"tf":1.0},"79":{"tf":1.4142135623730951},"80":{"tf":1.4142135623730951},"82":{"tf":1.7320508075688772},"83":{"tf":1.0},"84":{"tf":3.605551275463989},"85":{"tf":1.4142135623730951},"86":{"tf":2.449489742783178},"87":{"tf":1.0},"88":{"tf":1.7320508075688772},"89":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":4,"docs":{"244":{"tf":2.0},"245":{"tf":2.8284271247461903},"246":{"tf":1.4142135623730951},"247":{"tf":1.0}},"e":{".":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"y":{"_":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":1,"docs":{"246":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":1,"docs":{"97":{"tf":1.0}}}}},"m":{"a":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"g":{"df":3,"docs":{"242":{"tf":1.4142135623730951},"244":{"tf":1.0},"245":{"tf":2.6457513110645907}},"e":{"df":0,"docs":{},"r":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"244":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"p":{"a":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"242":{"tf":1.4142135623730951},"245":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"242":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"242":{"tf":1.0}}}}}}}},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":1,"docs":{"62":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"t":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"104":{"tf":1.0}}}},"df":0,"docs":{},"x":{"df":1,"docs":{"73":{"tf":1.0}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"(":{"df":1,"docs":{"280":{"tf":1.0}}},"df":13,"docs":{"10":{"tf":1.0},"11":{"tf":2.23606797749979},"118":{"tf":1.0},"12":{"tf":1.4142135623730951},"169":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.4142135623730951},"189":{"tf":1.0},"201":{"tf":1.7320508075688772},"37":{"tf":1.4142135623730951},"67":{"tf":1.4142135623730951},"7":{"tf":1.0}}}}}}}},"t":{"a":{"b":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"s":{"a":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"283":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":8,"docs":{"123":{"tf":1.0},"126":{"tf":1.0},"231":{"tf":1.0},"245":{"tf":1.0},"257":{"tf":1.0},"264":{"tf":1.7320508075688772},"283":{"tf":1.0},"48":{"tf":1.0}},"l":{"df":11,"docs":{"248":{"tf":2.8284271247461903},"249":{"tf":3.1622776601683795},"250":{"tf":1.4142135623730951},"257":{"tf":1.0},"284":{"tf":1.0},"62":{"tf":1.4142135623730951},"80":{"tf":1.4142135623730951},"81":{"tf":1.7320508075688772},"82":{"tf":1.7320508075688772},"84":{"tf":1.4142135623730951},"87":{"tf":1.0}},"e":{"df":0,"docs":{},"—":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"d":{"df":1,"docs":{"82":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"245":{"tf":1.0}}}}}}}},"df":0,"docs":{},"g":{"df":7,"docs":{"167":{"tf":1.0},"253":{"tf":1.4142135623730951},"268":{"tf":1.4142135623730951},"269":{"tf":1.0},"274":{"tf":2.23606797749979},"280":{"tf":1.4142135623730951},"32":{"tf":1.0}},"e":{"df":1,"docs":{"167":{"tf":1.0}}}},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"215":{"tf":1.0}}}},"k":{"df":0,"docs":{},"e":{"df":22,"docs":{"10":{"tf":1.0},"102":{"tf":1.0},"104":{"tf":1.0},"115":{"tf":1.0},"12":{"tf":1.0},"121":{"tf":1.0},"187":{"tf":1.0},"20":{"tf":1.0},"200":{"tf":1.0},"204":{"tf":1.0},"205":{"tf":1.0},"206":{"tf":1.7320508075688772},"212":{"tf":1.0},"226":{"tf":1.0},"242":{"tf":1.0},"249":{"tf":1.0},"250":{"tf":1.4142135623730951},"268":{"tf":1.0},"37":{"tf":1.0},"59":{"tf":1.0},"64":{"tf":1.0},"82":{"tf":1.0}},"n":{"df":5,"docs":{"132":{"tf":1.0},"140":{"tf":1.0},"171":{"tf":1.0},"242":{"tf":1.0},"58":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"e":{"df":1,"docs":{"64":{"tf":1.0}}},"k":{"df":3,"docs":{"194":{"tf":1.0},"233":{"tf":1.0},"274":{"tf":1.0}}}},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"201":{"tf":1.0}}}}}},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"274":{"tf":1.0}}}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":1,"docs":{"282":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":23,"docs":{"103":{"tf":1.4142135623730951},"117":{"tf":1.0},"163":{"tf":1.4142135623730951},"167":{"tf":1.4142135623730951},"17":{"tf":1.0},"172":{"tf":1.0},"174":{"tf":1.0},"208":{"tf":1.4142135623730951},"223":{"tf":1.0},"231":{"tf":1.0},"251":{"tf":1.0},"252":{"tf":1.0},"253":{"tf":1.0},"274":{"tf":1.0},"28":{"tf":1.0},"33":{"tf":1.0},"37":{"tf":1.4142135623730951},"58":{"tf":1.7320508075688772},"59":{"tf":1.0},"60":{"tf":1.0},"69":{"tf":1.0},"73":{"tf":1.7320508075688772},"74":{"tf":1.4142135623730951}}}}}},"s":{"df":0,"docs":{},"k":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":8,"docs":{"119":{"tf":1.0},"266":{"tf":1.0},"271":{"tf":1.0},"272":{"tf":1.0},"273":{"tf":1.4142135623730951},"274":{"tf":3.0},"280":{"tf":1.0},"74":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"r":{"/":{"c":{"df":0,"docs":{},"i":{"/":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"h":{"/":{"df":0,"docs":{},"k":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{".":{"df":0,"docs":{},"y":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":1,"docs":{"280":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"/":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"74":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":10,"docs":{"119":{"tf":1.4142135623730951},"167":{"tf":1.0},"200":{"tf":1.0},"224":{"tf":1.0},"235":{"tf":1.0},"266":{"tf":2.0},"274":{"tf":1.7320508075688772},"280":{"tf":1.4142135623730951},"39":{"tf":1.0},"40":{"tf":2.449489742783178}},"s":{"[":{"\"":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"$":{"df":0,"docs":{},"{":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"f":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"v":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"}":{"$":{"df":0,"docs":{},"{":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"}":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"\"":{"]":{".":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"(":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"(":{"'":{":":{"df":0,"docs":{},"f":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":1,"docs":{"224":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"b":{"d":{"df":2,"docs":{"226":{"tf":1.0},"242":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"c":{"'":{"df":1,"docs":{"274":{"tf":1.0}}},".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"/":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"/":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"/":{"df":0,"docs":{},"v":{"1":{"/":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"k":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{".":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"271":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"119":{"tf":1.0}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}},"t":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"k":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{".":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"266":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"df":1,"docs":{"280":{"tf":1.0}},"l":{"df":1,"docs":{"11":{"tf":1.7320508075688772}},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"11":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"m":{"df":14,"docs":{"1":{"tf":1.0},"119":{"tf":1.0},"122":{"tf":1.7320508075688772},"175":{"tf":1.0},"183":{"tf":1.0},"186":{"tf":1.7320508075688772},"187":{"tf":1.0},"203":{"tf":1.0},"225":{"tf":1.0},"268":{"tf":2.0},"274":{"tf":1.0},"276":{"tf":1.0},"6":{"tf":1.0},"8":{"tf":1.0}}}},"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"c":{"df":3,"docs":{"139":{"tf":1.0},"176":{"tf":1.0},"48":{"tf":1.0}}},"df":0,"docs":{}}}}},"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":3,"docs":{"20":{"tf":1.0},"22":{"tf":1.0},"27":{"tf":1.0}}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":10,"docs":{"150":{"tf":1.0},"203":{"tf":1.0},"210":{"tf":1.0},"214":{"tf":1.0},"219":{"tf":1.0},"226":{"tf":1.7320508075688772},"236":{"tf":1.0},"240":{"tf":1.0},"242":{"tf":1.4142135623730951},"259":{"tf":1.7320508075688772}}}}}}}},"l":{"df":6,"docs":{"18":{"tf":1.0},"214":{"tf":1.0},"252":{"tf":1.0},"37":{"tf":1.4142135623730951},"50":{"tf":1.0},"92":{"tf":1.0}}}},"m":{"df":0,"docs":{},"p":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"264":{"tf":1.4142135623730951}}}}}}},"df":2,"docs":{"250":{"tf":1.0},"81":{"tf":1.0}},"l":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"137":{"tf":1.4142135623730951}},"e":{".":{"df":0,"docs":{},"m":{"d":{"df":1,"docs":{"134":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":2,"docs":{"248":{"tf":1.0},"82":{"tf":1.0}}}}},"df":0,"docs":{}}},"t":{"df":1,"docs":{"225":{"tf":1.0}}}}},"n":{"d":{"df":4,"docs":{"117":{"tf":1.0},"60":{"tf":1.0},"82":{"tf":1.4142135623730951},"83":{"tf":1.0}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"m":{"df":8,"docs":{"104":{"tf":1.4142135623730951},"137":{"tf":1.0},"140":{"tf":1.0},"164":{"tf":1.0},"170":{"tf":1.0},"2":{"tf":1.0},"241":{"tf":1.0},"78":{"tf":1.4142135623730951}},"i":{"df":0,"docs":{},"n":{"df":3,"docs":{"11":{"tf":1.0},"12":{"tf":1.0},"204":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"q":{"df":2,"docs":{"176":{"tf":1.0},"202":{"tf":1.0}}}},"df":0,"docs":{}},"t":{",":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"}":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"61":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},".":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"r":{"c":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":1,"docs":{"224":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}},"/":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"/":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"59":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"60":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"c":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"df":1,"docs":{"60":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"j":{"a":{"df":0,"docs":{},"v":{"a":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"/":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"/":{"<":{"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"_":{"c":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"70":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"r":{"c":{"/":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"b":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"60":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"0":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"60":{"tf":1.4142135623730951}}}},"df":1,"docs":{"60":{"tf":1.0}}},"1":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"60":{"tf":1.0}}}},"df":1,"docs":{"60":{"tf":1.0}}},"2":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"60":{"tf":1.4142135623730951}}}},"df":1,"docs":{"60":{"tf":1.0}}},"_":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"b":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"_":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"c":{"df":0,"docs":{},"r":{"df":1,"docs":{"62":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"73":{"tf":1.0}}}},"df":0,"docs":{}},"df":58,"docs":{"10":{"tf":1.0},"108":{"tf":1.4142135623730951},"109":{"tf":1.0},"11":{"tf":2.0},"118":{"tf":1.4142135623730951},"122":{"tf":1.0},"124":{"tf":1.4142135623730951},"14":{"tf":1.0},"158":{"tf":1.0},"16":{"tf":1.4142135623730951},"17":{"tf":1.0},"18":{"tf":1.0},"190":{"tf":1.0},"194":{"tf":1.0},"200":{"tf":1.7320508075688772},"22":{"tf":1.0},"223":{"tf":1.7320508075688772},"224":{"tf":1.0},"23":{"tf":2.0},"237":{"tf":1.0},"262":{"tf":1.4142135623730951},"267":{"tf":1.0},"270":{"tf":1.0},"273":{"tf":1.0},"274":{"tf":2.6457513110645907},"278":{"tf":1.0},"279":{"tf":1.4142135623730951},"32":{"tf":2.23606797749979},"37":{"tf":1.4142135623730951},"38":{"tf":1.0},"39":{"tf":2.23606797749979},"40":{"tf":2.23606797749979},"41":{"tf":2.449489742783178},"42":{"tf":1.7320508075688772},"43":{"tf":3.0},"44":{"tf":1.4142135623730951},"45":{"tf":3.3166247903554},"46":{"tf":2.6457513110645907},"47":{"tf":1.7320508075688772},"48":{"tf":2.6457513110645907},"49":{"tf":2.449489742783178},"50":{"tf":2.0},"51":{"tf":2.23606797749979},"52":{"tf":2.449489742783178},"53":{"tf":1.4142135623730951},"54":{"tf":2.23606797749979},"55":{"tf":2.0},"56":{"tf":2.0},"57":{"tf":2.449489742783178},"58":{"tf":3.1622776601683795},"59":{"tf":2.6457513110645907},"60":{"tf":4.358898943540674},"61":{"tf":3.0},"62":{"tf":2.449489742783178},"7":{"tf":3.1622776601683795},"70":{"tf":1.7320508075688772},"73":{"tf":2.449489742783178},"74":{"tf":1.0}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"$":{"df":0,"docs":{},"n":{"df":1,"docs":{"20":{"tf":1.0}}}},"/":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"y":{"c":{"df":0,"docs":{},"r":{"df":1,"docs":{"60":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":1,"docs":{"59":{"tf":1.0}}}},"df":0,"docs":{}}},"y":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"48":{"tf":1.0}}},"df":0,"docs":{}}}}},"3":{"df":2,"docs":{"20":{"tf":1.4142135623730951},"22":{"tf":1.0}}},"df":0,"docs":{}}}},"s":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"41":{"tf":1.0}}}},"r":{"df":1,"docs":{"61":{"tf":1.0}}}},"/":{"*":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"61":{"tf":1.7320508075688772}},"m":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":2,"docs":{"60":{"tf":1.4142135623730951},"61":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"61":{"tf":1.0}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"h":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"k":{"df":2,"docs":{"220":{"tf":1.0},"274":{"tf":1.0}}}},"t":{"'":{"df":7,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"167":{"tf":1.0},"225":{"tf":1.0},"274":{"tf":1.0},"43":{"tf":1.0},"61":{"tf":1.0}}},"df":0,"docs":{},"’":{"df":1,"docs":{"283":{"tf":1.0}}}}},"d":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"v":{"df":2,"docs":{"190":{"tf":1.0},"228":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":2,"docs":{"105":{"tf":1.0},"158":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"e":{"'":{"df":16,"docs":{"19":{"tf":1.0},"194":{"tf":1.0},"201":{"tf":1.0},"226":{"tf":1.4142135623730951},"227":{"tf":1.0},"228":{"tf":1.0},"229":{"tf":1.4142135623730951},"233":{"tf":1.7320508075688772},"236":{"tf":1.0},"242":{"tf":1.7320508075688772},"274":{"tf":1.0},"276":{"tf":1.0},"40":{"tf":1.0},"43":{"tf":1.0},"60":{"tf":1.0},"79":{"tf":1.0}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"201":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"102":{"tf":1.0}}}},"’":{"df":2,"docs":{"189":{"tf":2.8284271247461903},"192":{"tf":1.0}}}}},"y":{"'":{"d":{"df":1,"docs":{"171":{"tf":1.0}}},"df":0,"docs":{},"r":{"df":3,"docs":{"49":{"tf":1.0},"60":{"tf":1.0},"73":{"tf":1.0}}},"v":{"df":1,"docs":{"116":{"tf":1.0}}}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":21,"docs":{"104":{"tf":1.0},"115":{"tf":1.0},"116":{"tf":1.4142135623730951},"13":{"tf":1.0},"175":{"tf":1.0},"201":{"tf":1.0},"225":{"tf":1.0},"226":{"tf":1.7320508075688772},"228":{"tf":1.4142135623730951},"237":{"tf":1.0},"238":{"tf":1.0},"239":{"tf":1.4142135623730951},"240":{"tf":1.7320508075688772},"252":{"tf":1.0},"40":{"tf":1.0},"62":{"tf":1.0},"68":{"tf":1.0},"69":{"tf":1.0},"78":{"tf":1.0},"79":{"tf":1.0},"86":{"tf":1.0}}},"k":{"df":7,"docs":{"116":{"tf":1.0},"274":{"tf":1.0},"58":{"tf":1.0},"76":{"tf":1.0},"77":{"tf":1.0},"78":{"tf":1.0},"87":{"tf":1.0}}}},"r":{"d":{"df":2,"docs":{"63":{"tf":1.0},"64":{"tf":1.7320508075688772}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":1,"docs":{"229":{"tf":1.0}},"i":{"df":1,"docs":{"104":{"tf":1.0}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":3,"docs":{"236":{"tf":1.0},"270":{"tf":1.0},"46":{"tf":1.0}}}}}}}}},"s":{"df":0,"docs":{},"e":{"df":17,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"11":{"tf":1.0},"118":{"tf":1.4142135623730951},"170":{"tf":1.0},"204":{"tf":1.4142135623730951},"220":{"tf":1.0},"249":{"tf":1.0},"25":{"tf":1.0},"258":{"tf":1.4142135623730951},"274":{"tf":1.0},"281":{"tf":1.0},"39":{"tf":1.0},"48":{"tf":1.0},"58":{"tf":1.0},"7":{"tf":1.0},"82":{"tf":1.0}}}},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":6,"docs":{"105":{"tf":1.0},"201":{"tf":1.0},"228":{"tf":1.4142135623730951},"245":{"tf":1.0},"261":{"tf":1.0},"81":{"tf":1.0}},"t":{"df":2,"docs":{"64":{"tf":1.0},"81":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"e":{"a":{"d":{"df":7,"docs":{"115":{"tf":3.0},"116":{"tf":1.0},"190":{"tf":1.4142135623730951},"194":{"tf":1.0},"197":{"tf":1.0},"203":{"tf":1.0},"242":{"tf":1.0}}},"df":0,"docs":{},"t":{"df":2,"docs":{"194":{"tf":1.0},"201":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":3,"docs":{"20":{"tf":1.0},"254":{"tf":1.0},"68":{"tf":1.0}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":8,"docs":{"167":{"tf":1.0},"249":{"tf":1.0},"268":{"tf":1.0},"27":{"tf":1.0},"283":{"tf":1.0},"3":{"tf":1.0},"73":{"tf":1.0},"74":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"45":{"tf":1.0}}}}}}},"w":{"df":0,"docs":{},"n":{"df":1,"docs":{"283":{"tf":1.0}}}}}},"u":{"df":11,"docs":{"172":{"tf":1.0},"205":{"tf":1.0},"209":{"tf":1.0},"221":{"tf":1.0},"227":{"tf":1.0},"228":{"tf":1.0},"250":{"tf":1.0},"263":{"tf":1.0},"44":{"tf":1.0},"46":{"tf":1.0},"84":{"tf":1.0}},"m":{"b":{"df":1,"docs":{"59":{"tf":1.0}}},"df":0,"docs":{}}}},"i":{"df":4,"docs":{"180":{"tf":1.0},"183":{"tf":1.0},"238":{"tf":1.0},"274":{"tf":1.0}},"e":{"df":1,"docs":{"240":{"tf":1.4142135623730951}}},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"206":{"tf":1.0}}}}}}},"m":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"d":{"_":{"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"145":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":27,"docs":{"10":{"tf":1.7320508075688772},"104":{"tf":1.0},"141":{"tf":1.0},"142":{"tf":1.4142135623730951},"152":{"tf":1.0},"154":{"tf":1.4142135623730951},"155":{"tf":1.4142135623730951},"156":{"tf":1.0},"170":{"tf":1.0},"172":{"tf":1.0},"204":{"tf":1.4142135623730951},"209":{"tf":1.0},"212":{"tf":1.0},"233":{"tf":1.0},"241":{"tf":1.0},"242":{"tf":1.0},"25":{"tf":1.0},"250":{"tf":1.0},"263":{"tf":1.0},"57":{"tf":1.4142135623730951},"58":{"tf":1.0},"60":{"tf":1.0},"62":{"tf":1.0},"64":{"tf":1.0},"7":{"tf":1.0},"81":{"tf":1.7320508075688772},"84":{"tf":1.4142135623730951}},"r":{"df":1,"docs":{"226":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":6,"docs":{"22":{"tf":1.0},"242":{"tf":1.0},"248":{"tf":1.0},"249":{"tf":1.7320508075688772},"80":{"tf":1.0},"88":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"p":{"df":5,"docs":{"105":{"tf":1.0},"282":{"tf":1.4142135623730951},"43":{"tf":1.0},"7":{"tf":1.0},"75":{"tf":1.0}}},"t":{"df":0,"docs":{},"l":{"df":3,"docs":{"280":{"tf":1.0},"39":{"tf":1.0},"62":{"tf":1.0}}}}},"l":{";":{"d":{"df":0,"docs":{},"r":{"df":2,"docs":{"228":{"tf":1.0},"92":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"o":{"/":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":1,"docs":{"254":{"tf":1.0}}}}}}},"_":{"df":0,"docs":{},"j":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"283":{"tf":1.0}}}}}}},"d":{"df":0,"docs":{},"o":{"!":{"(":{")":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"200":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":6,"docs":{"127":{"tf":1.0},"200":{"tf":1.0},"241":{"tf":1.0},"274":{"tf":2.8284271247461903},"46":{"tf":1.0},"48":{"tf":1.7320508075688772}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":10,"docs":{"172":{"tf":1.0},"220":{"tf":1.0},"221":{"tf":1.0},"238":{"tf":1.0},"240":{"tf":1.4142135623730951},"252":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0},"46":{"tf":1.0},"58":{"tf":1.0}}}}}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":6,"docs":{"226":{"tf":1.0},"245":{"tf":1.4142135623730951},"254":{"tf":1.0},"274":{"tf":1.4142135623730951},"277":{"tf":2.449489742783178},"283":{"tf":1.4142135623730951}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"_":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"l":{"df":1,"docs":{"242":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}}}}}},"m":{"b":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"268":{"tf":1.0},"62":{"tf":1.0}}}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"k":{"df":1,"docs":{"204":{"tf":1.0}}},"l":{"b":{"a":{"df":0,"docs":{},"r":{"_":{"_":{"_":{"_":{"_":{"df":1,"docs":{"62":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"62":{"tf":1.0}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"263":{"tf":1.7320508075688772}}}}},"df":0,"docs":{}}},"df":18,"docs":{"101":{"tf":1.0},"106":{"tf":1.0},"107":{"tf":1.0},"11":{"tf":2.0},"111":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":1.4142135623730951},"165":{"tf":1.0},"187":{"tf":1.4142135623730951},"264":{"tf":1.0},"274":{"tf":1.4142135623730951},"275":{"tf":1.0},"276":{"tf":1.0},"277":{"tf":1.0},"37":{"tf":1.7320508075688772},"62":{"tf":1.4142135623730951},"64":{"tf":1.0},"90":{"tf":1.0}},"s":{"/":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{".":{"d":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"286":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":1,"docs":{"11":{"tf":1.7320508075688772}}}}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"f":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":1,"docs":{"106":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"u":{"df":0,"docs":{},"p":{"d":{"df":1,"docs":{"122":{"tf":1.0}}},"df":0,"docs":{}}}},";":{"2":{"6":{".":{"0":{".":{"2":{"df":1,"docs":{"13":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"p":{"df":13,"docs":{"105":{"tf":1.0},"123":{"tf":1.4142135623730951},"124":{"tf":1.4142135623730951},"126":{"tf":1.4142135623730951},"200":{"tf":1.4142135623730951},"252":{"tf":1.0},"282":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"40":{"tf":1.0},"73":{"tf":1.0},"84":{"tf":1.0},"98":{"tf":1.4142135623730951}},"i":{"c":{"df":1,"docs":{"90":{"tf":1.0}}},"df":0,"docs":{}}},"u":{"c":{"df":0,"docs":{},"h":{"df":2,"docs":{"201":{"tf":1.0},"3":{"tf":1.0}}}},"df":0,"docs":{}}},"r":{"a":{"c":{"df":0,"docs":{},"k":{"df":10,"docs":{"106":{"tf":1.0},"167":{"tf":1.0},"231":{"tf":1.0},"245":{"tf":1.0},"261":{"tf":1.0},"283":{"tf":1.0},"80":{"tf":1.0},"82":{"tf":1.0},"84":{"tf":1.0},"87":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"1":{"tf":1.0}}}}},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":3,"docs":{"169":{"tf":1.0},"172":{"tf":1.0},"197":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":2,"docs":{"116":{"tf":1.4142135623730951},"251":{"tf":1.0}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":6,"docs":{"124":{"tf":1.4142135623730951},"244":{"tf":1.4142135623730951},"246":{"tf":1.0},"43":{"tf":1.0},"84":{"tf":1.0},"93":{"tf":1.0}}}},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"79":{"tf":2.449489742783178}}}},"df":0,"docs":{}},"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"149":{"tf":1.0},"150":{"tf":1.0}}}}}},"p":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"245":{"tf":1.0}}}},"df":0,"docs":{}}}},"v":{"df":0,"docs":{},"i":{"df":1,"docs":{"176":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":5,"docs":{"122":{"tf":1.0},"124":{"tf":1.0},"147":{"tf":1.0},"64":{"tf":1.0},"81":{"tf":1.0}}}},"i":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"121":{"tf":1.0}}},"l":{"df":1,"docs":{"104":{"tf":1.0}}}},"c":{"df":0,"docs":{},"k":{"df":3,"docs":{"126":{"tf":1.0},"17":{"tf":1.0},"75":{"tf":1.0}},"i":{"df":4,"docs":{"11":{"tf":1.0},"128":{"tf":1.0},"22":{"tf":1.0},"62":{"tf":1.0}}}}},"df":28,"docs":{"104":{"tf":1.7320508075688772},"106":{"tf":1.0},"107":{"tf":1.0},"108":{"tf":1.4142135623730951},"109":{"tf":1.4142135623730951},"111":{"tf":1.0},"122":{"tf":1.4142135623730951},"126":{"tf":1.0},"158":{"tf":1.0},"170":{"tf":1.0},"197":{"tf":1.0},"20":{"tf":1.0},"22":{"tf":1.4142135623730951},"227":{"tf":1.0},"230":{"tf":1.0},"242":{"tf":1.0},"263":{"tf":1.0},"264":{"tf":1.0},"278":{"tf":1.0},"279":{"tf":1.0},"37":{"tf":1.4142135623730951},"4":{"tf":1.0},"54":{"tf":1.0},"60":{"tf":1.0},"62":{"tf":1.0},"64":{"tf":1.0},"69":{"tf":1.0},"79":{"tf":1.0}},"g":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":13,"docs":{"116":{"tf":1.0},"119":{"tf":1.4142135623730951},"154":{"tf":1.0},"267":{"tf":1.0},"270":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0},"60":{"tf":1.0},"62":{"tf":1.0},"73":{"tf":1.0},"81":{"tf":1.0},"82":{"tf":3.1622776601683795},"84":{"tf":1.4142135623730951}}}}}},"v":{"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"l":{"df":4,"docs":{"167":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"64":{"tf":1.0}}}},"df":0,"docs":{}}}},"u":{"df":0,"docs":{},"e":{"df":6,"docs":{"126":{"tf":1.0},"133":{"tf":1.0},"258":{"tf":1.0},"264":{"tf":1.0},"46":{"tf":1.0},"61":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"189":{"tf":1.0},"274":{"tf":1.0}}}},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"263":{"tf":1.0}}}}}},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"62":{"tf":1.0}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":2,"docs":{"252":{"tf":1.0},"60":{"tf":1.0}}}}},"w":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"k":{"df":1,"docs":{"37":{"tf":1.0}}}},"df":0,"docs":{}},"o":{"df":8,"docs":{"165":{"tf":1.0},"175":{"tf":1.0},"196":{"tf":1.0},"197":{"tf":1.0},"201":{"tf":1.0},"212":{"tf":1.0},"251":{"tf":1.0},"273":{"tf":1.0}}}},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":1,"docs":{"116":{"tf":1.0}}}}},"df":13,"docs":{"105":{"tf":1.0},"106":{"tf":1.7320508075688772},"151":{"tf":1.4142135623730951},"152":{"tf":1.4142135623730951},"155":{"tf":1.4142135623730951},"156":{"tf":1.4142135623730951},"189":{"tf":1.4142135623730951},"268":{"tf":1.0},"283":{"tf":2.0},"43":{"tf":1.0},"62":{"tf":1.0},"93":{"tf":1.0},"96":{"tf":1.0}},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":1,"docs":{"43":{"tf":1.0}}}}}}}}},"i":{"c":{"df":11,"docs":{"10":{"tf":1.0},"103":{"tf":1.0},"224":{"tf":1.4142135623730951},"250":{"tf":1.0},"283":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0},"45":{"tf":1.0},"79":{"tf":1.0},"87":{"tf":1.0},"93":{"tf":1.0}}},"df":0,"docs":{}}}}},"u":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":1,"docs":{"13":{"tf":1.0}}}}}}},"d":{"df":0,"docs":{},"l":{"df":9,"docs":{"104":{"tf":2.0},"105":{"tf":2.449489742783178},"106":{"tf":1.4142135623730951},"107":{"tf":1.0},"109":{"tf":1.0},"123":{"tf":1.0},"200":{"tf":1.0},"70":{"tf":1.0},"73":{"tf":1.0}}}},"df":0,"docs":{},"i":{"df":4,"docs":{"180":{"tf":1.0},"226":{"tf":1.4142135623730951},"268":{"tf":1.0},"7":{"tf":1.0}}},"l":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":4,"docs":{"115":{"tf":1.0},"225":{"tf":1.0},"261":{"tf":1.0},"43":{"tf":1.0}}}}}},"n":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"233":{"tf":1.0}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"200":{"tf":1.0}}}}}}}},"df":0,"docs":{}}}},"h":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"184":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"73":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"m":{"df":3,"docs":{"117":{"tf":1.0},"29":{"tf":1.0},"34":{"tf":1.0}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"124":{"tf":1.4142135623730951}}}}}},"v":{"df":1,"docs":{"104":{"tf":1.0}}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"282":{"tf":1.0}}}}},"r":{"df":17,"docs":{"105":{"tf":1.0},"108":{"tf":1.7320508075688772},"109":{"tf":1.7320508075688772},"170":{"tf":1.0},"175":{"tf":1.0},"184":{"tf":1.0},"214":{"tf":1.0},"216":{"tf":1.4142135623730951},"217":{"tf":1.0},"22":{"tf":1.0},"4":{"tf":1.0},"46":{"tf":1.0},"60":{"tf":1.0},"64":{"tf":1.0},"69":{"tf":1.0},"7":{"tf":1.0},"73":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":3,"docs":{"104":{"tf":1.0},"128":{"tf":1.0},"253":{"tf":1.0}}}},"n":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"86":{"tf":1.0}}}}},"df":0,"docs":{}}},"s":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"99":{"tf":1.0}}}}},"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"n":{"d":{"df":10,"docs":{"102":{"tf":1.4142135623730951},"104":{"tf":1.0},"123":{"tf":1.0},"138":{"tf":1.0},"174":{"tf":1.0},"197":{"tf":1.0},"21":{"tf":1.0},"225":{"tf":1.0},"236":{"tf":1.0},"264":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"d":{"df":1,"docs":{"253":{"tf":1.0}}},"df":0,"docs":{}}}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":1,"docs":{"180":{"tf":1.0}}}}}},"u":{"df":1,"docs":{"226":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"37":{"tf":1.0},"64":{"tf":1.0}},"e":{"d":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"58":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"f":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"82":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"62":{"tf":1.0}},"e":{"d":{"_":{"_":{"_":{"_":{"_":{"df":1,"docs":{"62":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":5,"docs":{"104":{"tf":1.0},"117":{"tf":1.0},"210":{"tf":1.0},"58":{"tf":1.7320508075688772},"62":{"tf":1.0}}}}}}}},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"'":{"df":1,"docs":{"104":{"tf":1.0}}},".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":5,"docs":{"103":{"tf":1.0},"108":{"tf":1.0},"70":{"tf":1.0},"72":{"tf":1.0},"73":{"tf":1.0}}}}}}},"_":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"104":{"tf":1.0}}}}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"69":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":32,"docs":{"101":{"tf":2.23606797749979},"102":{"tf":2.23606797749979},"103":{"tf":2.0},"104":{"tf":3.1622776601683795},"105":{"tf":2.6457513110645907},"106":{"tf":2.0},"107":{"tf":1.4142135623730951},"108":{"tf":2.23606797749979},"109":{"tf":2.449489742783178},"111":{"tf":1.7320508075688772},"123":{"tf":1.4142135623730951},"128":{"tf":2.23606797749979},"129":{"tf":1.0},"130":{"tf":1.4142135623730951},"131":{"tf":1.7320508075688772},"132":{"tf":1.0},"133":{"tf":1.0},"194":{"tf":1.0},"196":{"tf":1.0},"197":{"tf":1.0},"198":{"tf":1.0},"200":{"tf":2.0},"253":{"tf":1.0},"271":{"tf":1.0},"44":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"69":{"tf":1.4142135623730951},"72":{"tf":1.0},"73":{"tf":1.0},"74":{"tf":1.0},"90":{"tf":1.7320508075688772}}}},"i":{"df":2,"docs":{"237":{"tf":1.0},"257":{"tf":1.0}}}},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"60":{"tf":1.0}}}}}}},"q":{"df":0,"docs":{},"u":{"df":1,"docs":{"22":{"tf":1.0}}}},"t":{"df":9,"docs":{"17":{"tf":1.0},"223":{"tf":1.4142135623730951},"224":{"tf":1.0},"232":{"tf":1.7320508075688772},"278":{"tf":1.0},"39":{"tf":1.4142135623730951},"42":{"tf":1.4142135623730951},"43":{"tf":1.0},"60":{"tf":1.0}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":3,"docs":{"223":{"tf":1.0},"224":{"tf":1.4142135623730951},"46":{"tf":1.0}}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":1,"docs":{"61":{"tf":1.0}}}}}}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":7,"docs":{"204":{"tf":1.0},"21":{"tf":1.0},"26":{"tf":1.0},"60":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0},"67":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"k":{"df":6,"docs":{"146":{"tf":1.0},"200":{"tf":1.0},"231":{"tf":1.0},"258":{"tf":1.0},"60":{"tf":1.0},"64":{"tf":1.0}}}},"o":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"142":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"q":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":1,"docs":{"58":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"224":{"tf":1.0}}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":1,"docs":{"124":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"i":{"df":1,"docs":{"233":{"tf":1.0}}}},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"v":{"df":1,"docs":{"92":{"tf":1.0}}}}}}}},"s":{"a":{"df":0,"docs":{},"f":{"df":2,"docs":{"117":{"tf":1.0},"64":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"117":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":11,"docs":{"119":{"tf":1.0},"12":{"tf":1.0},"167":{"tf":1.0},"179":{"tf":1.0},"180":{"tf":1.0},"184":{"tf":1.0},"197":{"tf":1.0},"212":{"tf":1.0},"226":{"tf":1.0},"40":{"tf":1.0},"7":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"s":{"df":1,"docs":{"149":{"tf":1.0}},"u":{"df":2,"docs":{"252":{"tf":1.0},"64":{"tf":1.0}}}}},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"116":{"tf":1.0}}}}},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":3,"docs":{"11":{"tf":1.4142135623730951},"29":{"tf":1.7320508075688772},"34":{"tf":1.7320508075688772}}}}}},"p":{"d":{"a":{"df":0,"docs":{},"t":{"df":35,"docs":{"104":{"tf":1.0},"108":{"tf":1.0},"11":{"tf":1.4142135623730951},"119":{"tf":1.0},"120":{"tf":1.0},"121":{"tf":1.0},"122":{"tf":2.0},"123":{"tf":1.0},"124":{"tf":1.0},"134":{"tf":1.0},"139":{"tf":1.4142135623730951},"145":{"tf":2.0},"146":{"tf":1.0},"193":{"tf":1.0},"197":{"tf":1.0},"198":{"tf":1.0},"200":{"tf":1.4142135623730951},"233":{"tf":1.0},"246":{"tf":1.0},"249":{"tf":1.7320508075688772},"25":{"tf":1.7320508075688772},"250":{"tf":1.4142135623730951},"261":{"tf":1.0},"268":{"tf":2.8284271247461903},"269":{"tf":1.0},"278":{"tf":1.0},"279":{"tf":2.449489742783178},"280":{"tf":2.23606797749979},"282":{"tf":1.0},"64":{"tf":1.0},"69":{"tf":1.0},"73":{"tf":1.0},"75":{"tf":1.0},"82":{"tf":1.7320508075688772},"84":{"tf":1.0}}}},"df":0,"docs":{}},"df":40,"docs":{"103":{"tf":1.0},"108":{"tf":1.7320508075688772},"109":{"tf":1.7320508075688772},"117":{"tf":1.0},"121":{"tf":1.0},"122":{"tf":1.0},"124":{"tf":1.4142135623730951},"13":{"tf":1.0},"158":{"tf":1.0},"166":{"tf":1.0},"17":{"tf":1.0},"170":{"tf":1.0},"197":{"tf":1.0},"199":{"tf":1.0},"21":{"tf":1.0},"214":{"tf":1.4142135623730951},"22":{"tf":1.0},"225":{"tf":1.4142135623730951},"233":{"tf":1.0},"240":{"tf":1.0},"242":{"tf":1.0},"255":{"tf":1.0},"264":{"tf":1.0},"272":{"tf":1.0},"278":{"tf":1.0},"30":{"tf":1.0},"35":{"tf":1.0},"40":{"tf":1.4142135623730951},"5":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"6":{"tf":1.0},"60":{"tf":1.4142135623730951},"61":{"tf":1.0},"62":{"tf":1.0},"70":{"tf":1.0},"72":{"tf":1.0},"82":{"tf":1.0},"83":{"tf":1.0},"9":{"tf":1.0}},"g":{"df":0,"docs":{},"r":{"a":{"d":{"df":9,"docs":{"261":{"tf":1.0},"278":{"tf":1.7320508075688772},"279":{"tf":1.4142135623730951},"280":{"tf":1.0},"281":{"tf":1.0},"282":{"tf":1.7320508075688772},"78":{"tf":1.0},"79":{"tf":1.0},"81":{"tf":2.23606797749979}}},"df":0,"docs":{}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":1,"docs":{"270":{"tf":2.0}}}}},"o":{"a":{"d":{"df":4,"docs":{"249":{"tf":1.0},"250":{"tf":1.4142135623730951},"274":{"tf":1.7320508075688772},"88":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"c":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"c":{"a":{"df":0,"docs":{},"s":{"df":4,"docs":{"93":{"tf":1.0},"96":{"tf":1.0},"98":{"tf":1.0},"99":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"m":{"df":3,"docs":{"16":{"tf":1.0},"197":{"tf":1.0},"49":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"6":{"tf":1.0}}}}}},"l":{"df":9,"docs":{"205":{"tf":1.0},"242":{"tf":1.0},"253":{"tf":1.0},"268":{"tf":1.4142135623730951},"280":{"tf":1.0},"29":{"tf":1.4142135623730951},"34":{"tf":1.0},"62":{"tf":1.0},"82":{"tf":1.0}}}},"s":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"159":{"tf":1.0}}}},"df":0,"docs":{},"g":{"df":4,"docs":{"115":{"tf":1.0},"138":{"tf":1.0},"213":{"tf":1.0},"264":{"tf":2.23606797749979}}}},"b":{"df":1,"docs":{"126":{"tf":1.0}}},"df":145,"docs":{"0":{"tf":1.4142135623730951},"101":{"tf":1.7320508075688772},"104":{"tf":1.7320508075688772},"105":{"tf":1.7320508075688772},"106":{"tf":2.23606797749979},"107":{"tf":1.0},"108":{"tf":1.7320508075688772},"109":{"tf":1.0},"11":{"tf":2.23606797749979},"110":{"tf":1.0},"111":{"tf":1.7320508075688772},"112":{"tf":1.0},"113":{"tf":1.0},"114":{"tf":1.0},"115":{"tf":2.449489742783178},"116":{"tf":3.1622776601683795},"117":{"tf":1.7320508075688772},"118":{"tf":1.4142135623730951},"119":{"tf":1.7320508075688772},"12":{"tf":1.4142135623730951},"120":{"tf":1.0},"124":{"tf":1.4142135623730951},"125":{"tf":1.0},"126":{"tf":1.7320508075688772},"128":{"tf":1.7320508075688772},"129":{"tf":1.4142135623730951},"13":{"tf":1.0},"132":{"tf":1.4142135623730951},"134":{"tf":1.4142135623730951},"135":{"tf":1.4142135623730951},"140":{"tf":1.4142135623730951},"145":{"tf":1.4142135623730951},"146":{"tf":1.0},"157":{"tf":1.0},"16":{"tf":2.0},"162":{"tf":1.0},"163":{"tf":1.0},"165":{"tf":1.0},"166":{"tf":1.0},"167":{"tf":1.4142135623730951},"169":{"tf":1.0},"17":{"tf":1.7320508075688772},"170":{"tf":2.23606797749979},"171":{"tf":1.0},"174":{"tf":1.7320508075688772},"175":{"tf":2.23606797749979},"18":{"tf":1.7320508075688772},"187":{"tf":1.0},"189":{"tf":1.0},"19":{"tf":2.0},"190":{"tf":1.7320508075688772},"192":{"tf":1.4142135623730951},"193":{"tf":1.4142135623730951},"194":{"tf":1.7320508075688772},"196":{"tf":1.0},"197":{"tf":3.1622776601683795},"198":{"tf":2.6457513110645907},"20":{"tf":2.0},"200":{"tf":2.23606797749979},"201":{"tf":1.4142135623730951},"205":{"tf":1.0},"21":{"tf":1.4142135623730951},"213":{"tf":1.7320508075688772},"219":{"tf":1.4142135623730951},"22":{"tf":2.449489742783178},"220":{"tf":1.0},"221":{"tf":1.0},"222":{"tf":1.4142135623730951},"228":{"tf":1.7320508075688772},"229":{"tf":1.0},"23":{"tf":1.4142135623730951},"231":{"tf":1.7320508075688772},"232":{"tf":1.7320508075688772},"233":{"tf":1.0},"237":{"tf":1.7320508075688772},"24":{"tf":1.0},"242":{"tf":1.4142135623730951},"245":{"tf":2.23606797749979},"249":{"tf":1.0},"25":{"tf":2.0},"250":{"tf":1.0},"252":{"tf":1.4142135623730951},"253":{"tf":1.0},"254":{"tf":1.7320508075688772},"256":{"tf":1.0},"257":{"tf":2.0},"258":{"tf":1.7320508075688772},"259":{"tf":1.0},"26":{"tf":2.23606797749979},"260":{"tf":1.4142135623730951},"261":{"tf":1.4142135623730951},"262":{"tf":1.4142135623730951},"264":{"tf":2.0},"266":{"tf":1.0},"268":{"tf":1.0},"27":{"tf":2.0},"271":{"tf":1.4142135623730951},"273":{"tf":1.0},"274":{"tf":3.0},"278":{"tf":1.0},"28":{"tf":1.0},"280":{"tf":1.4142135623730951},"283":{"tf":2.0},"284":{"tf":1.0},"29":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.0},"32":{"tf":1.4142135623730951},"33":{"tf":1.0},"34":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":2.8284271247461903},"38":{"tf":1.0},"41":{"tf":1.0},"43":{"tf":2.0},"44":{"tf":1.0},"45":{"tf":1.4142135623730951},"46":{"tf":1.4142135623730951},"49":{"tf":1.0},"50":{"tf":1.0},"52":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"58":{"tf":2.0},"6":{"tf":1.0},"60":{"tf":1.7320508075688772},"61":{"tf":2.23606797749979},"62":{"tf":1.7320508075688772},"63":{"tf":1.0},"64":{"tf":2.8284271247461903},"69":{"tf":2.449489742783178},"7":{"tf":1.0},"73":{"tf":2.23606797749979},"78":{"tf":1.7320508075688772},"79":{"tf":1.7320508075688772},"80":{"tf":1.0},"82":{"tf":1.4142135623730951},"84":{"tf":1.7320508075688772},"86":{"tf":1.0},"87":{"tf":1.0},"90":{"tf":1.0},"96":{"tf":1.0},"99":{"tf":2.23606797749979}},"e":{"df":0,"docs":{},"r":{"'":{"df":5,"docs":{"159":{"tf":1.0},"177":{"tf":1.4142135623730951},"183":{"tf":1.0},"184":{"tf":1.0},"233":{"tf":1.0}}},"_":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":2,"docs":{"264":{"tf":1.0},"81":{"tf":1.0}}}}}}},"df":40,"docs":{"149":{"tf":1.0},"150":{"tf":1.0},"154":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0},"157":{"tf":1.0},"158":{"tf":1.7320508075688772},"159":{"tf":1.0},"179":{"tf":1.0},"180":{"tf":1.0},"182":{"tf":1.0},"183":{"tf":1.0},"184":{"tf":1.4142135623730951},"203":{"tf":1.0},"204":{"tf":2.0},"205":{"tf":3.0},"206":{"tf":1.7320508075688772},"207":{"tf":1.0},"208":{"tf":1.4142135623730951},"209":{"tf":2.0},"210":{"tf":2.0},"212":{"tf":2.0},"213":{"tf":2.449489742783178},"214":{"tf":2.0},"215":{"tf":2.23606797749979},"216":{"tf":2.23606797749979},"217":{"tf":1.4142135623730951},"218":{"tf":1.4142135623730951},"225":{"tf":1.0},"226":{"tf":1.4142135623730951},"232":{"tf":1.4142135623730951},"233":{"tf":2.6457513110645907},"242":{"tf":2.0},"245":{"tf":1.0},"254":{"tf":1.0},"255":{"tf":1.0},"283":{"tf":2.23606797749979},"52":{"tf":1.4142135623730951},"64":{"tf":1.0},"82":{"tf":1.0}},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"13":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"’":{"df":7,"docs":{"203":{"tf":1.0},"205":{"tf":1.0},"206":{"tf":1.4142135623730951},"208":{"tf":1.4142135623730951},"209":{"tf":1.0},"213":{"tf":1.0},"283":{"tf":1.7320508075688772}}}}},"u":{"a":{"df":0,"docs":{},"l":{"df":5,"docs":{"117":{"tf":1.0},"18":{"tf":1.0},"21":{"tf":1.0},"279":{"tf":1.0},"280":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":5,"docs":{"126":{"tf":1.0},"237":{"tf":2.23606797749979},"60":{"tf":1.7320508075688772},"62":{"tf":1.4142135623730951},"79":{"tf":1.0}},"s":{".":{"df":0,"docs":{},"r":{"df":2,"docs":{"237":{"tf":1.0},"60":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"v":{"1":{"1":{"7":{".":{"0":{"df":1,"docs":{"268":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"8":{".":{"0":{"df":1,"docs":{"268":{"tf":2.0}}},"df":0,"docs":{}},"df":1,"docs":{"268":{"tf":1.0}}},"9":{".":{"0":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"2":{".":{"0":{"df":1,"docs":{"2":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"[":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"]":{".":{"0":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":1,"docs":{"268":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"]":{".":{"0":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"20":{"tf":1.0}},"i":{"d":{"df":13,"docs":{"104":{"tf":1.0},"140":{"tf":1.4142135623730951},"141":{"tf":1.0},"145":{"tf":2.0},"146":{"tf":1.4142135623730951},"147":{"tf":1.0},"150":{"tf":1.0},"154":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0},"189":{"tf":1.4142135623730951},"190":{"tf":1.0},"64":{"tf":1.0}}},"df":0,"docs":{}},"u":{"df":6,"docs":{"18":{"tf":1.0},"264":{"tf":1.7320508075688772},"274":{"tf":1.0},"280":{"tf":1.0},"62":{"tf":1.0},"84":{"tf":1.4142135623730951}}}},"r":{"df":2,"docs":{"12":{"tf":1.0},"97":{"tf":1.0}},"i":{"a":{"b":{"df":0,"docs":{},"l":{"df":8,"docs":{"11":{"tf":1.4142135623730951},"115":{"tf":1.0},"12":{"tf":1.0},"14":{"tf":1.0},"277":{"tf":1.4142135623730951},"37":{"tf":1.4142135623730951},"93":{"tf":1.4142135623730951},"99":{"tf":1.0}}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":3,"docs":{"104":{"tf":1.0},"108":{"tf":1.0},"93":{"tf":1.0}}}}},"df":2,"docs":{"105":{"tf":1.0},"264":{"tf":1.0}},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"63":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"u":{"df":11,"docs":{"106":{"tf":1.0},"169":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"22":{"tf":1.0},"223":{"tf":1.4142135623730951},"225":{"tf":1.0},"226":{"tf":1.0},"229":{"tf":1.4142135623730951},"253":{"tf":1.0},"263":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"115":{"tf":1.0}}}}}}},"df":0,"docs":{},"e":{"c":{"<":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"242":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"242":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"z":{"df":0,"docs":{},"e":{"d":{"/":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"m":{"df":1,"docs":{"282":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":7,"docs":{"120":{"tf":2.0},"121":{"tf":2.449489742783178},"122":{"tf":1.0},"123":{"tf":1.4142135623730951},"124":{"tf":2.6457513110645907},"274":{"tf":1.4142135623730951},"64":{"tf":1.4142135623730951}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"122":{"tf":1.0}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"r":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":1,"docs":{"126":{"tf":1.0}}}}},"df":0,"docs":{},"i":{"df":14,"docs":{"107":{"tf":1.0},"115":{"tf":1.0},"158":{"tf":1.0},"165":{"tf":1.0},"192":{"tf":1.0},"200":{"tf":1.0},"215":{"tf":1.0},"22":{"tf":1.0},"225":{"tf":1.0},"230":{"tf":1.0},"245":{"tf":1.0},"260":{"tf":1.0},"60":{"tf":1.0},"81":{"tf":1.0}},"f":{"df":2,"docs":{"196":{"tf":1.0},"198":{"tf":1.0}},"i":{"df":5,"docs":{"189":{"tf":1.0},"193":{"tf":1.0},"194":{"tf":1.0},"48":{"tf":1.0},"49":{"tf":1.0}}}},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"22":{"tf":1.0}}}}},"s":{"a":{"df":1,"docs":{"79":{"tf":1.0}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":4,"docs":{"20":{"tf":1.0},"268":{"tf":2.0},"269":{"tf":1.0},"270":{"tf":1.0}}}}}},"df":53,"docs":{"104":{"tf":1.0},"11":{"tf":1.4142135623730951},"12":{"tf":2.0},"121":{"tf":1.0},"123":{"tf":1.4142135623730951},"124":{"tf":1.7320508075688772},"13":{"tf":1.0},"138":{"tf":1.0},"165":{"tf":1.4142135623730951},"167":{"tf":1.0},"169":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"175":{"tf":1.0},"18":{"tf":1.0},"189":{"tf":1.0},"190":{"tf":1.0},"192":{"tf":1.0},"197":{"tf":1.0},"20":{"tf":2.0},"201":{"tf":1.7320508075688772},"205":{"tf":1.0},"219":{"tf":1.0},"22":{"tf":1.7320508075688772},"222":{"tf":1.0},"25":{"tf":2.0},"253":{"tf":1.0},"26":{"tf":1.7320508075688772},"260":{"tf":2.23606797749979},"261":{"tf":2.23606797749979},"262":{"tf":2.6457513110645907},"263":{"tf":3.1622776601683795},"268":{"tf":2.449489742783178},"269":{"tf":1.0},"270":{"tf":1.4142135623730951},"271":{"tf":1.0},"274":{"tf":1.0},"278":{"tf":1.4142135623730951},"279":{"tf":2.449489742783178},"280":{"tf":1.0},"281":{"tf":1.0},"282":{"tf":1.0},"37":{"tf":2.449489742783178},"45":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"62":{"tf":1.0},"64":{"tf":1.4142135623730951},"69":{"tf":1.4142135623730951},"81":{"tf":2.0},"86":{"tf":1.0}}}}},"u":{"df":1,"docs":{"88":{"tf":1.4142135623730951}}}}},"t":{"df":1,"docs":{"122":{"tf":1.4142135623730951}}}},"i":{"a":{"d":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":4,"docs":{"124":{"tf":1.0},"197":{"tf":1.0},"200":{"tf":1.7320508075688772},"51":{"tf":1.0}}}},"df":0,"docs":{}}},"df":29,"docs":{"106":{"tf":1.0},"11":{"tf":1.4142135623730951},"13":{"tf":1.4142135623730951},"161":{"tf":1.0},"167":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"18":{"tf":1.0},"189":{"tf":1.4142135623730951},"190":{"tf":1.0},"192":{"tf":1.0},"194":{"tf":1.4142135623730951},"196":{"tf":1.0},"198":{"tf":1.0},"20":{"tf":1.0},"200":{"tf":1.0},"220":{"tf":1.0},"228":{"tf":1.0},"259":{"tf":1.0},"269":{"tf":1.4142135623730951},"274":{"tf":2.23606797749979},"277":{"tf":1.0},"283":{"tf":1.0},"37":{"tf":1.0},"43":{"tf":1.0},"45":{"tf":1.0},"49":{"tf":1.0},"51":{"tf":1.0},"81":{"tf":1.0}}},"c":{"df":0,"docs":{},"e":{"df":1,"docs":{"79":{"tf":1.0}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"df":1,"docs":{"174":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":5,"docs":{"107":{"tf":1.0},"126":{"tf":1.7320508075688772},"200":{"tf":1.0},"283":{"tf":1.0},"40":{"tf":1.4142135623730951}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":14,"docs":{"134":{"tf":1.0},"202":{"tf":1.4142135623730951},"204":{"tf":2.449489742783178},"205":{"tf":1.0},"206":{"tf":1.4142135623730951},"207":{"tf":2.0},"208":{"tf":1.4142135623730951},"210":{"tf":2.23606797749979},"213":{"tf":2.0},"214":{"tf":2.449489742783178},"215":{"tf":1.7320508075688772},"216":{"tf":2.8284271247461903},"217":{"tf":2.6457513110645907},"218":{"tf":1.0}}}}},"t":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"87":{"tf":1.0}}}},"df":0,"docs":{}},"v":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"138":{"tf":1.0}}},"df":0,"docs":{}}}},"s":{"df":2,"docs":{"116":{"tf":1.0},"232":{"tf":2.8284271247461903}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"201":{"tf":1.0},"63":{"tf":1.0}}}}}}},"x":{"df":0,"docs":{},"x":{"df":0,"docs":{},"x":{"df":3,"docs":{"267":{"tf":1.0},"269":{"tf":1.0},"270":{"tf":1.0}}}}}},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":6,"docs":{"118":{"tf":1.0},"152":{"tf":1.0},"183":{"tf":1.0},"245":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0}}}},"l":{"_":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"264":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"264":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":2,"docs":{"264":{"tf":1.0},"79":{"tf":1.0}}},"n":{"df":0,"docs":{},"t":{"df":27,"docs":{"117":{"tf":1.4142135623730951},"12":{"tf":1.0},"121":{"tf":1.0},"124":{"tf":1.0},"136":{"tf":1.0},"140":{"tf":1.4142135623730951},"145":{"tf":1.0},"149":{"tf":1.0},"169":{"tf":1.0},"17":{"tf":1.0},"170":{"tf":1.4142135623730951},"171":{"tf":1.4142135623730951},"172":{"tf":1.0},"19":{"tf":1.0},"196":{"tf":1.0},"23":{"tf":1.0},"241":{"tf":1.0},"242":{"tf":1.4142135623730951},"247":{"tf":1.0},"264":{"tf":1.0},"270":{"tf":1.4142135623730951},"281":{"tf":1.0},"32":{"tf":1.0},"58":{"tf":1.4142135623730951},"60":{"tf":1.0},"62":{"tf":1.0},"75":{"tf":1.0}}}},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"64":{"tf":1.0}}},"n":{"df":2,"docs":{"262":{"tf":1.4142135623730951},"92":{"tf":1.4142135623730951}}}},"y":{"df":34,"docs":{"101":{"tf":1.0},"109":{"tf":1.0},"117":{"tf":1.0},"129":{"tf":1.0},"140":{"tf":1.0},"149":{"tf":1.0},"150":{"tf":1.0},"158":{"tf":1.0},"159":{"tf":1.0},"167":{"tf":1.0},"179":{"tf":1.0},"18":{"tf":1.0},"198":{"tf":1.0},"201":{"tf":1.0},"209":{"tf":1.0},"220":{"tf":1.7320508075688772},"222":{"tf":1.0},"226":{"tf":1.4142135623730951},"229":{"tf":1.7320508075688772},"242":{"tf":1.0},"247":{"tf":1.0},"249":{"tf":1.0},"26":{"tf":1.0},"263":{"tf":1.0},"274":{"tf":1.7320508075688772},"280":{"tf":1.0},"38":{"tf":1.0},"46":{"tf":1.4142135623730951},"52":{"tf":1.0},"58":{"tf":1.4142135623730951},"60":{"tf":1.0},"61":{"tf":2.0},"81":{"tf":1.0},"90":{"tf":1.0}}}},"df":0,"docs":{},"e":{"'":{"d":{"df":5,"docs":{"116":{"tf":1.4142135623730951},"171":{"tf":1.0},"225":{"tf":1.0},"58":{"tf":1.4142135623730951},"82":{"tf":1.0}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":4,"docs":{"229":{"tf":1.0},"242":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0}}}},"r":{"df":8,"docs":{"105":{"tf":1.0},"152":{"tf":1.0},"166":{"tf":1.0},"248":{"tf":1.0},"252":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"63":{"tf":1.0}}},"v":{"df":5,"docs":{"101":{"tf":1.4142135623730951},"225":{"tf":1.0},"231":{"tf":1.0},"63":{"tf":1.0},"88":{"tf":1.0}}}},"a":{"df":0,"docs":{},"k":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":1,"docs":{"245":{"tf":1.0}}}}}}},"b":{"df":2,"docs":{"257":{"tf":1.4142135623730951},"283":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":2,"docs":{"264":{"tf":1.0},"79":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":5,"docs":{"123":{"tf":1.0},"124":{"tf":1.0},"264":{"tf":1.4142135623730951},"78":{"tf":1.4142135623730951},"79":{"tf":1.0}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"l":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":4,"docs":{"107":{"tf":1.0},"3":{"tf":1.0},"7":{"tf":1.0},"75":{"tf":1.0}}}}},"df":0,"docs":{},"l":{"df":12,"docs":{"105":{"tf":1.4142135623730951},"111":{"tf":1.0},"167":{"tf":1.0},"18":{"tf":1.0},"199":{"tf":1.0},"252":{"tf":1.0},"257":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"79":{"tf":1.0}}}},"’":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":1,"docs":{"214":{"tf":1.0}}}},"r":{"df":1,"docs":{"217":{"tf":1.0}}}}},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"11":{"tf":1.0}}}}},"h":{"a":{"df":0,"docs":{},"t":{"'":{"df":3,"docs":{"233":{"tf":1.0},"268":{"tf":1.4142135623730951},"89":{"tf":1.4142135623730951}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":4,"docs":{"233":{"tf":1.0},"250":{"tf":1.0},"274":{"tf":1.0},"81":{"tf":1.0}}}},"’":{"df":3,"docs":{"86":{"tf":1.4142135623730951},"87":{"tf":1.4142135623730951},"89":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":3,"docs":{"121":{"tf":1.0},"267":{"tf":1.0},"276":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"e":{"a":{"df":2,"docs":{"79":{"tf":1.0},"86":{"tf":1.0}}},"df":0,"docs":{},"v":{"df":1,"docs":{"79":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":7,"docs":{"104":{"tf":1.0},"167":{"tf":1.4142135623730951},"229":{"tf":1.0},"236":{"tf":1.0},"64":{"tf":1.0},"70":{"tf":1.0},"73":{"tf":1.4142135623730951}}}}}}},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":3,"docs":{"170":{"tf":1.0},"171":{"tf":1.0},"248":{"tf":1.0}}}}}},"i":{"d":{"df":0,"docs":{},"e":{"df":1,"docs":{"64":{"tf":1.0}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":3,"docs":{"225":{"tf":1.0},"226":{"tf":1.0},"229":{"tf":1.0}}}},"l":{"df":0,"docs":{},"l":{"df":2,"docs":{"105":{"tf":1.0},"115":{"tf":1.0}}}},"n":{"d":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":8,"docs":{"11":{"tf":2.0},"117":{"tf":1.0},"12":{"tf":1.4142135623730951},"13":{"tf":2.23606797749979},"19":{"tf":1.7320508075688772},"201":{"tf":1.0},"278":{"tf":1.0},"280":{"tf":1.0}},"s":{"/":{"df":0,"docs":{},"w":{"df":0,"docs":{},"s":{"df":0,"docs":{},"l":{"df":1,"docs":{"19":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"e":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"242":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":6,"docs":{"158":{"tf":1.0},"226":{"tf":1.0},"231":{"tf":1.7320508075688772},"234":{"tf":1.7320508075688772},"242":{"tf":1.7320508075688772},"89":{"tf":2.0}}}},"s":{"d":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":1,"docs":{"128":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"h":{"df":1,"docs":{"200":{"tf":1.0}}}},"t":{"df":0,"docs":{},"h":{"/":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"59":{"tf":1.0}}}}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"166":{"tf":1.0}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":21,"docs":{"105":{"tf":1.0},"165":{"tf":1.0},"167":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"183":{"tf":1.4142135623730951},"187":{"tf":1.0},"209":{"tf":1.0},"212":{"tf":1.0},"22":{"tf":1.0},"227":{"tf":1.0},"228":{"tf":1.0},"231":{"tf":1.0},"238":{"tf":1.4142135623730951},"242":{"tf":1.0},"257":{"tf":1.0},"262":{"tf":1.0},"274":{"tf":1.4142135623730951},"58":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"n":{"'":{"df":0,"docs":{},"t":{"df":2,"docs":{"132":{"tf":1.0},"37":{"tf":1.0}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"82":{"tf":1.0}}}}},"df":0,"docs":{},"’":{"df":0,"docs":{},"t":{"df":1,"docs":{"212":{"tf":1.0}}}}},"r":{"d":{"df":3,"docs":{"227":{"tf":1.0},"257":{"tf":1.0},"99":{"tf":1.0}}},"df":0,"docs":{},"k":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"115":{"tf":1.0}}},"df":0,"docs":{}}}}}},"df":52,"docs":{"103":{"tf":1.0},"105":{"tf":1.4142135623730951},"107":{"tf":1.0},"11":{"tf":1.4142135623730951},"115":{"tf":1.0},"116":{"tf":1.4142135623730951},"117":{"tf":1.0},"122":{"tf":1.0},"13":{"tf":1.0},"141":{"tf":1.0},"146":{"tf":1.0},"150":{"tf":1.0},"16":{"tf":1.0},"166":{"tf":1.0},"169":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"174":{"tf":1.0},"187":{"tf":1.4142135623730951},"19":{"tf":1.4142135623730951},"190":{"tf":1.0},"197":{"tf":1.0},"219":{"tf":1.4142135623730951},"22":{"tf":1.4142135623730951},"224":{"tf":1.7320508075688772},"225":{"tf":1.0},"228":{"tf":1.0},"229":{"tf":1.0},"231":{"tf":1.0},"238":{"tf":1.4142135623730951},"239":{"tf":1.4142135623730951},"243":{"tf":1.0},"246":{"tf":1.0},"263":{"tf":1.0},"264":{"tf":1.0},"274":{"tf":1.0},"37":{"tf":2.0},"39":{"tf":1.0},"41":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"49":{"tf":1.0},"6":{"tf":1.4142135623730951},"60":{"tf":1.0},"61":{"tf":1.0},"7":{"tf":1.4142135623730951},"70":{"tf":1.0},"73":{"tf":1.0},"79":{"tf":1.0},"88":{"tf":1.0},"9":{"tf":1.0},"90":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"280":{"tf":1.0}}}},"f":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":6,"docs":{"12":{"tf":1.0},"16":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":1.7320508075688772},"20":{"tf":2.0},"22":{"tf":2.449489742783178}}}}}},"s":{"df":0,"docs":{},"p":{"a":{"c":{"df":4,"docs":{"105":{"tf":1.0},"58":{"tf":1.0},"69":{"tf":1.0},"70":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":3,"docs":{"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0}}}}}},"l":{"d":{"df":5,"docs":{"122":{"tf":1.0},"16":{"tf":1.0},"229":{"tf":1.0},"242":{"tf":1.0},"58":{"tf":1.0}}},"df":0,"docs":{}},"s":{"df":3,"docs":{"57":{"tf":1.4142135623730951},"58":{"tf":1.0},"59":{"tf":1.0}}},"t":{"df":0,"docs":{},"h":{"df":2,"docs":{"201":{"tf":1.0},"208":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"l":{"d":{"df":0,"docs":{},"n":{"'":{"df":0,"docs":{},"t":{"df":1,"docs":{"116":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"r":{"a":{"df":0,"docs":{},"p":{"df":4,"docs":{"0":{"tf":1.0},"103":{"tf":1.4142135623730951},"108":{"tf":1.0},"109":{"tf":1.0}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":11,"docs":{"103":{"tf":1.0},"108":{"tf":1.4142135623730951},"109":{"tf":1.7320508075688772},"172":{"tf":1.0},"221":{"tf":1.0},"222":{"tf":1.0},"281":{"tf":1.0},"282":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"68":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":22,"docs":{"104":{"tf":1.4142135623730951},"107":{"tf":1.4142135623730951},"116":{"tf":1.0},"118":{"tf":1.0},"125":{"tf":1.4142135623730951},"158":{"tf":1.0},"196":{"tf":2.23606797749979},"233":{"tf":1.4142135623730951},"249":{"tf":1.0},"263":{"tf":1.0},"43":{"tf":1.4142135623730951},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"64":{"tf":1.0},"7":{"tf":1.0},"70":{"tf":1.0},"79":{"tf":1.0},"81":{"tf":1.4142135623730951},"90":{"tf":1.0}},"r":{"df":1,"docs":{"79":{"tf":1.7320508075688772}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":22,"docs":{"0":{"tf":1.0},"101":{"tf":1.0},"103":{"tf":2.8284271247461903},"104":{"tf":2.0},"106":{"tf":1.0},"108":{"tf":2.23606797749979},"109":{"tf":2.23606797749979},"111":{"tf":1.0},"192":{"tf":1.4142135623730951},"193":{"tf":1.7320508075688772},"194":{"tf":1.0},"197":{"tf":1.0},"225":{"tf":2.0},"227":{"tf":1.0},"242":{"tf":1.4142135623730951},"45":{"tf":1.0},"46":{"tf":1.0},"70":{"tf":1.4142135623730951},"72":{"tf":2.0},"73":{"tf":1.4142135623730951},"74":{"tf":1.0},"93":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":3,"docs":{"116":{"tf":1.4142135623730951},"236":{"tf":1.0},"277":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"l":{"df":4,"docs":{"11":{"tf":1.7320508075688772},"12":{"tf":1.4142135623730951},"13":{"tf":2.0},"19":{"tf":1.4142135623730951}}}}},"x":{"8":{"6":{"_":{"6":{"4":{"/":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"37":{"tf":2.23606797749979}}}}},"df":0,"docs":{}},"df":1,"docs":{"17":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":3,"docs":{"17":{"tf":2.0},"37":{"tf":1.4142135623730951},"40":{"tf":1.0}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":5,"docs":{"14":{"tf":1.0},"252":{"tf":1.0},"28":{"tf":1.4142135623730951},"33":{"tf":1.4142135623730951},"73":{"tf":1.0}}}}},"df":17,"docs":{"163":{"tf":1.4142135623730951},"167":{"tf":2.0},"170":{"tf":1.7320508075688772},"171":{"tf":1.0},"172":{"tf":1.0},"174":{"tf":1.4142135623730951},"23":{"tf":1.4142135623730951},"251":{"tf":1.7320508075688772},"252":{"tf":3.1622776601683795},"253":{"tf":1.0},"271":{"tf":1.0},"274":{"tf":1.4142135623730951},"28":{"tf":1.7320508075688772},"29":{"tf":1.7320508075688772},"32":{"tf":1.4142135623730951},"33":{"tf":1.7320508075688772},"34":{"tf":1.7320508075688772}}}}}}}}},"df":0,"docs":{}}},"o":{"d":{"df":0,"docs":{},"e":{"df":19,"docs":{"109":{"tf":2.449489742783178},"11":{"tf":1.7320508075688772},"14":{"tf":1.4142135623730951},"165":{"tf":1.4142135623730951},"166":{"tf":1.0},"167":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"175":{"tf":1.4142135623730951},"25":{"tf":2.0},"252":{"tf":1.4142135623730951},"26":{"tf":1.4142135623730951},"274":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":2.0},"35":{"tf":1.0},"36":{"tf":2.0},"46":{"tf":1.4142135623730951},"73":{"tf":3.1622776601683795}}}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"14":{"tf":1.4142135623730951}}}}}}}}},"df":2,"docs":{"207":{"tf":1.4142135623730951},"210":{"tf":1.7320508075688772}},"m":{"df":0,"docs":{},"l":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{":":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"=":{"\"":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{":":{"/":{"/":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"s":{".":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"/":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"k":{"/":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"/":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"70":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"x":{"df":0,"docs":{},"x":{"df":5,"docs":{"242":{"tf":1.0},"270":{"tf":1.0},"277":{"tf":1.0},"46":{"tf":1.0},"48":{"tf":1.7320508075688772}}}}},"y":{"df":2,"docs":{"137":{"tf":1.0},"210":{"tf":1.7320508075688772}},"e":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"193":{"tf":1.0}}}},"df":1,"docs":{"18":{"tf":1.0}},"p":{"df":1,"docs":{"60":{"tf":1.0}}}},"o":{"df":0,"docs":{},"u":{"'":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":11,"docs":{"103":{"tf":1.0},"104":{"tf":1.0},"105":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"123":{"tf":1.0},"17":{"tf":1.4142135623730951},"274":{"tf":1.0},"279":{"tf":1.0},"68":{"tf":1.0},"90":{"tf":1.0}}}},"r":{"df":12,"docs":{"105":{"tf":1.0},"107":{"tf":1.4142135623730951},"114":{"tf":1.0},"115":{"tf":1.0},"17":{"tf":1.0},"21":{"tf":1.0},"37":{"tf":1.0},"39":{"tf":1.0},"41":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"73":{"tf":1.0}}},"v":{"df":6,"docs":{"102":{"tf":1.0},"104":{"tf":1.0},"106":{"tf":1.7320508075688772},"279":{"tf":1.0},"6":{"tf":1.0},"60":{"tf":1.0}}}},"df":0,"docs":{},"r":{"_":{"c":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"69":{"tf":1.7320508075688772},"72":{"tf":1.0}},"e":{">":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":1,"docs":{"73":{"tf":1.4142135623730951}}}}}}},"u":{"d":{"df":0,"docs":{},"l":{"df":2,"docs":{"73":{"tf":1.4142135623730951},"74":{"tf":1.0}}}},"df":0,"docs":{}}},":":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":1,"docs":{"70":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"70":{"tf":1.0}}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{".":{"df":0,"docs":{},"h":{"df":1,"docs":{"73":{"tf":1.0}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"64":{"tf":1.0}}}}}}},"’":{"df":0,"docs":{},"r":{"df":1,"docs":{"115":{"tf":1.0}}}}}}},"z":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":2,"docs":{"250":{"tf":1.4142135623730951},"84":{"tf":1.4142135623730951}}}}},"i":{"df":0,"docs":{},"p":{"df":2,"docs":{"167":{"tf":1.0},"252":{"tf":1.0}},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"274":{"tf":1.0}}}}}}},"l":{"df":0,"docs":{},"i":{"b":{"1":{"df":0,"docs":{},"g":{"df":1,"docs":{"11":{"tf":1.4142135623730951}}}},"df":1,"docs":{"11":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"h":{"df":0,"docs":{},"r":{"c":{"df":2,"docs":{"11":{"tf":1.0},"12":{"tf":1.0}}},"df":0,"docs":{}}}}}}},"title":{"root":{"1":{"df":2,"docs":{"145":{"tf":1.0},"196":{"tf":1.0}}},"2":{"df":2,"docs":{"146":{"tf":1.0},"197":{"tf":1.0}}},"3":{"df":1,"docs":{"198":{"tf":1.0}}},"a":{"1":{"df":1,"docs":{"154":{"tf":1.0}}},"2":{"df":1,"docs":{"155":{"tf":1.0}}},"3":{"df":1,"docs":{"156":{"tf":1.0}}},"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"221":{"tf":1.0}}}},"c":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":3,"docs":{"114":{"tf":1.0},"126":{"tf":1.0},"127":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"276":{"tf":1.0}}}}}}},"df":0,"docs":{}},"d":{"df":5,"docs":{"123":{"tf":1.0},"22":{"tf":1.0},"57":{"tf":1.0},"68":{"tf":1.0},"73":{"tf":1.0}}},"df":0,"docs":{},"g":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":4,"docs":{"15":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0},"52":{"tf":1.0}}}}}}},"df":0,"docs":{}},"l":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"y":{"df":1,"docs":{"133":{"tf":1.0}}}},"df":0,"docs":{}}},"n":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"/":{"df":0,"docs":{},"k":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"65":{"tf":1.0}}}}}}}}},"df":6,"docs":{"110":{"tf":1.0},"117":{"tf":1.0},"13":{"tf":1.0},"237":{"tf":1.0},"49":{"tf":1.0},"54":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"i":{"df":5,"docs":{"107":{"tf":1.0},"139":{"tf":1.0},"145":{"tf":1.0},"232":{"tf":1.0},"236":{"tf":1.0}}},"p":{"df":2,"docs":{"183":{"tf":1.0},"52":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"df":2,"docs":{"173":{"tf":1.0},"60":{"tf":1.0}}}}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"i":{"c":{"df":18,"docs":{"0":{"tf":1.0},"118":{"tf":1.0},"120":{"tf":1.0},"125":{"tf":1.0},"15":{"tf":1.0},"252":{"tf":1.0},"259":{"tf":1.0},"262":{"tf":1.0},"265":{"tf":1.0},"274":{"tf":1.0},"3":{"tf":1.0},"30":{"tf":1.0},"35":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0},"52":{"tf":1.0},"68":{"tf":1.0},"9":{"tf":1.0}}},"df":0,"docs":{}},"y":{"_":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":3,"docs":{"247":{"tf":1.0},"249":{"tf":1.0},"88":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"o":{"a":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"146":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"v":{"c":{"df":1,"docs":{"276":{"tf":1.0}}},"df":0,"docs":{}}}}},"r":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":3,"docs":{"134":{"tf":1.0},"135":{"tf":1.0},"244":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"280":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"154":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"275":{"tf":1.0}}}}}},"o":{"df":2,"docs":{"18":{"tf":1.0},"22":{"tf":1.0}},"m":{"df":1,"docs":{"25":{"tf":1.0}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"/":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"61":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"v":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"57":{"tf":1.0},"60":{"tf":1.0}}},"df":0,"docs":{}}}}},"b":{"1":{"df":1,"docs":{"157":{"tf":1.0}}},"2":{"df":1,"docs":{"158":{"tf":1.0}}},"3":{"df":1,"docs":{"159":{"tf":1.0}}},"a":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"115":{"tf":1.0}},"g":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"58":{"tf":1.0}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":2,"docs":{"172":{"tf":1.0},"213":{"tf":1.0}}}}},"df":1,"docs":{"170":{"tf":1.0}},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"256":{"tf":1.0}}}}},"n":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"60":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"w":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"89":{"tf":1.0}}}}}}}},"i":{"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"70":{"tf":1.0},"71":{"tf":1.0}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":1,"docs":{"286":{"tf":1.0}}}}},"r":{"a":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":2,"docs":{"38":{"tf":1.0},"39":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"k":{"df":2,"docs":{"118":{"tf":1.0},"39":{"tf":1.0}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"w":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"186":{"tf":1.0}}}}}}}},"u":{"df":0,"docs":{},"g":{"df":1,"docs":{"4":{"tf":1.0}}},"i":{"df":0,"docs":{},"l":{"d":{"df":20,"docs":{"10":{"tf":1.0},"11":{"tf":1.0},"12":{"tf":1.0},"14":{"tf":1.0},"15":{"tf":1.0},"170":{"tf":1.0},"266":{"tf":1.0},"267":{"tf":1.0},"272":{"tf":1.0},"274":{"tf":1.0},"28":{"tf":1.0},"285":{"tf":1.0},"286":{"tf":1.0},"33":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"5":{"tf":1.0},"75":{"tf":1.0},"77":{"tf":1.0},"9":{"tf":1.0}}},"df":0,"docs":{},"t":{"df":1,"docs":{"271":{"tf":1.0}}}}},"n":{"d":{"df":0,"docs":{},"l":{"df":2,"docs":{"170":{"tf":1.0},"183":{"tf":1.0}}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"282":{"tf":1.0}}}},"df":0,"docs":{}}}}},"c":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":1,"docs":{"115":{"tf":1.0}}}},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"170":{"tf":1.0}}}},"df":0,"docs":{}}}},"v":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"21":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":1,"docs":{"171":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"120":{"tf":1.0},"261":{"tf":1.0}}}},"df":0,"docs":{}}}}},"h":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":2,"docs":{"115":{"tf":1.0},"228":{"tf":1.0}}}}}}},"n":{"df":0,"docs":{},"g":{"df":3,"docs":{"118":{"tf":1.0},"270":{"tf":1.0},"39":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"k":{"df":2,"docs":{"155":{"tf":1.0},"156":{"tf":1.0}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":6,"docs":{"29":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.0},"34":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0}}}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"199":{"tf":1.0}}}}}}},"i":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"i":{"df":1,"docs":{"277":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"l":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"131":{"tf":1.0}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":7,"docs":{"186":{"tf":1.0},"193":{"tf":1.0},"194":{"tf":1.0},"197":{"tf":1.0},"198":{"tf":1.0},"231":{"tf":1.0},"238":{"tf":1.0}}}}}}},"o":{"d":{"df":0,"docs":{},"e":{"df":22,"docs":{"105":{"tf":1.0},"107":{"tf":1.0},"111":{"tf":1.0},"114":{"tf":1.0},"117":{"tf":1.0},"118":{"tf":1.0},"159":{"tf":1.0},"172":{"tf":1.0},"43":{"tf":1.0},"44":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0},"67":{"tf":1.0},"69":{"tf":1.0},"8":{"tf":1.0},"83":{"tf":1.0},"92":{"tf":1.0},"95":{"tf":1.0},"98":{"tf":1.0}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"232":{"tf":1.0},"259":{"tf":1.0},"86":{"tf":1.0}}}},"df":0,"docs":{}}}},"m":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"237":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"p":{"a":{"df":0,"docs":{},"r":{"df":2,"docs":{"155":{"tf":1.0},"156":{"tf":1.0}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":6,"docs":{"114":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"280":{"tf":1.0},"57":{"tf":1.0},"60":{"tf":1.0}}}},"o":{"df":0,"docs":{},"n":{"df":29,"docs":{"0":{"tf":1.0},"101":{"tf":1.0},"103":{"tf":1.0},"11":{"tf":1.0},"122":{"tf":1.0},"123":{"tf":1.0},"16":{"tf":1.0},"228":{"tf":1.0},"23":{"tf":1.0},"230":{"tf":1.0},"235":{"tf":1.0},"251":{"tf":1.0},"253":{"tf":1.0},"257":{"tf":1.0},"259":{"tf":1.0},"283":{"tf":1.0},"29":{"tf":1.0},"31":{"tf":1.0},"32":{"tf":1.0},"34":{"tf":1.0},"36":{"tf":1.0},"41":{"tf":1.0},"49":{"tf":1.0},"54":{"tf":1.0},"68":{"tf":1.0},"73":{"tf":1.0},"74":{"tf":1.4142135623730951},"75":{"tf":1.0},"76":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"'":{"df":1,"docs":{"104":{"tf":1.0}}},"df":0,"docs":{}}}}}}}},"n":{"df":5,"docs":{"144":{"tf":1.0},"153":{"tf":1.0},"168":{"tf":1.0},"181":{"tf":1.0},"211":{"tf":1.0}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":4,"docs":{"165":{"tf":1.0},"166":{"tf":1.0},"209":{"tf":1.0},"210":{"tf":1.0}}}}},"i":{"d":{"df":7,"docs":{"137":{"tf":1.0},"142":{"tf":1.0},"151":{"tf":1.0},"163":{"tf":1.0},"179":{"tf":1.0},"195":{"tf":1.0},"207":{"tf":1.0}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"90":{"tf":1.0}}}}},"t":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"201":{"tf":1.0}}}},"x":{"df":0,"docs":{},"t":{"df":7,"docs":{"136":{"tf":1.0},"140":{"tf":1.0},"149":{"tf":1.0},"161":{"tf":1.0},"177":{"tf":1.0},"187":{"tf":1.0},"203":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"3":{"tf":1.0}}}}},"df":0,"docs":{}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"91":{"tf":1.0}}}},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"101":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"131":{"tf":1.0}}},"df":0,"docs":{}}}}}},"u":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":4,"docs":{"148":{"tf":1.0},"154":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0}}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"50":{"tf":1.0}}}},"df":0,"docs":{}}}}},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":6,"docs":{"129":{"tf":1.0},"130":{"tf":1.0},"131":{"tf":1.0},"268":{"tf":1.0},"269":{"tf":1.0},"72":{"tf":1.0}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"280":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"175":{"tf":1.0},"228":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":1,"docs":{"222":{"tf":1.0}}}}}},"t":{"df":1,"docs":{"270":{"tf":1.0}}}}},"d":{"a":{"df":0,"docs":{},"t":{"a":{"b":{"a":{"df":0,"docs":{},"s":{"df":8,"docs":{"148":{"tf":1.0},"154":{"tf":1.4142135623730951},"155":{"tf":1.0},"156":{"tf":1.0},"157":{"tf":1.4142135623730951},"158":{"tf":1.4142135623730951},"248":{"tf":1.0},"264":{"tf":1.0}}}},"df":0,"docs":{}},"df":3,"docs":{"183":{"tf":1.0},"184":{"tf":1.0},"80":{"tf":1.0}}},"df":0,"docs":{},"e":{"df":1,"docs":{"213":{"tf":1.0}}}}},"df":1,"docs":{"172":{"tf":1.0}},"e":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"184":{"tf":1.0}}}},"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":2,"docs":{"117":{"tf":1.0},"62":{"tf":1.0}},"g":{"df":1,"docs":{"117":{"tf":1.0}}}}}},"c":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"155":{"tf":1.0},"157":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":13,"docs":{"134":{"tf":1.0},"135":{"tf":1.0},"138":{"tf":1.0},"141":{"tf":1.0},"143":{"tf":1.0},"150":{"tf":1.0},"152":{"tf":1.0},"162":{"tf":1.0},"164":{"tf":1.0},"178":{"tf":1.0},"180":{"tf":1.0},"206":{"tf":1.0},"208":{"tf":1.0}}}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"233":{"tf":1.0}}}}}},"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"y":{"df":1,"docs":{"184":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"157":{"tf":1.0}}}},"i":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"235":{"tf":1.0}}}}}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":3,"docs":{"221":{"tf":1.0},"53":{"tf":1.0},"63":{"tf":1.0}}},"df":0,"docs":{}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"df":3,"docs":{"219":{"tf":1.0},"251":{"tf":1.0},"76":{"tf":1.0}}}}},"k":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":1,"docs":{"192":{"tf":1.0}}}}}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"y":{"df":2,"docs":{"129":{"tf":1.0},"131":{"tf":1.0}}}},"u":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"128":{"tf":1.0},"133":{"tf":1.0}}}},"df":0,"docs":{}}}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":2,"docs":{"227":{"tf":1.0},"242":{"tf":1.0}}}}},"df":0,"docs":{}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":2,"docs":{"26":{"tf":1.0},"284":{"tf":1.0}}}}}}}},"i":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"255":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":1,"docs":{"89":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"146":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"72":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"s":{"a":{"b":{"df":0,"docs":{},"l":{"df":2,"docs":{"26":{"tf":1.0},"61":{"tf":1.0}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"234":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":5,"docs":{"160":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"215":{"tf":1.0},"74":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"o":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":5,"docs":{"107":{"tf":1.0},"219":{"tf":1.0},"284":{"tf":1.0},"285":{"tf":1.0},"286":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"r":{"a":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":1,"docs":{"104":{"tf":1.0}}}}},"df":0,"docs":{},"i":{"df":2,"docs":{"204":{"tf":1.0},"216":{"tf":1.0}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":5,"docs":{"141":{"tf":1.0},"150":{"tf":1.0},"162":{"tf":1.0},"178":{"tf":1.0},"206":{"tf":1.0}}}}}}}},"df":0,"docs":{},"e":{"a":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"258":{"tf":1.0}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"176":{"tf":1.0}}}}}},"d":{"df":0,"docs":{},"g":{"df":1,"docs":{"224":{"tf":1.0}}}},"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"145":{"tf":1.0}}}}}}},"d":{"df":3,"docs":{"216":{"tf":1.0},"48":{"tf":1.4142135623730951},"52":{"tf":1.0}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":6,"docs":{"231":{"tf":1.0},"232":{"tf":1.0},"238":{"tf":1.0},"241":{"tf":1.0},"246":{"tf":1.0},"258":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"78":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":3,"docs":{"154":{"tf":1.0},"155":{"tf":1.4142135623730951},"156":{"tf":1.4142135623730951}}}}}},"x":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":3,"docs":{"100":{"tf":1.0},"94":{"tf":1.0},"97":{"tf":1.0}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":6,"docs":{"101":{"tf":1.0},"115":{"tf":1.0},"122":{"tf":1.0},"191":{"tf":1.0},"197":{"tf":1.0},"198":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":4,"docs":{"176":{"tf":1.0},"183":{"tf":1.0},"184":{"tf":1.0},"204":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"s":{"df":3,"docs":{"111":{"tf":1.0},"281":{"tf":1.0},"90":{"tf":1.0}}}}}}},"f":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"159":{"tf":1.0}}}}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"r":{"df":2,"docs":{"102":{"tf":1.0},"103":{"tf":1.0}}}},"df":0,"docs":{}}}}},"q":{"0":{"df":1,"docs":{"61":{"tf":1.0}}},"df":2,"docs":{"110":{"tf":1.0},"85":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"57":{"tf":1.0}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"df":4,"docs":{"12":{"tf":1.0},"126":{"tf":1.0},"16":{"tf":1.0},"55":{"tf":1.0}}}}}},"f":{"df":0,"docs":{},"i":{"df":1,"docs":{"44":{"tf":1.0}}}},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":3,"docs":{"104":{"tf":1.0},"157":{"tf":1.0},"158":{"tf":1.0}}}},"n":{"d":{"df":1,"docs":{"6":{"tf":1.0}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"x":{"df":7,"docs":{"14":{"tf":1.0},"15":{"tf":1.0},"202":{"tf":1.0},"23":{"tf":1.0},"254":{"tf":1.0},"31":{"tf":1.0},"56":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"t":{"df":5,"docs":{"10":{"tf":1.0},"102":{"tf":1.0},"104":{"tf":1.0},"176":{"tf":1.0},"184":{"tf":1.0}}}}},"x":{"df":1,"docs":{"282":{"tf":1.0}}}},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":3,"docs":{"244":{"tf":1.0},"25":{"tf":1.0},"27":{"tf":1.0}}}}},"o":{"c":{"df":0,"docs":{},"u":{"df":2,"docs":{"32":{"tf":1.0},"36":{"tf":1.0}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":1,"docs":{"241":{"tf":1.0}}}}}}}}},"r":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"77":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":4,"docs":{"129":{"tf":1.0},"145":{"tf":1.4142135623730951},"281":{"tf":1.0},"42":{"tf":1.0}}}}}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"174":{"tf":1.0}}}}}}},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"258":{"tf":1.0}}}}}}},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":5,"docs":{"244":{"tf":1.0},"30":{"tf":1.0},"35":{"tf":1.0},"76":{"tf":1.0},"83":{"tf":1.0}}}}},"t":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"_":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"c":{"df":1,"docs":{"87":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":1,"docs":{"271":{"tf":1.0}}}},"l":{"df":0,"docs":{},"o":{"b":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"86":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"h":{"a":{"df":1,"docs":{"224":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":1,"docs":{"221":{"tf":1.0}}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"i":{"d":{"df":3,"docs":{"278":{"tf":1.0},"41":{"tf":1.0},"75":{"tf":1.0}},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"63":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"h":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"l":{"df":1,"docs":{"148":{"tf":1.0}}}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"133":{"tf":1.0}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":2,"docs":{"251":{"tf":1.0},"254":{"tf":1.0}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":2,"docs":{"202":{"tf":1.0},"215":{"tf":1.0}}}}}}}}},"i":{"d":{"df":1,"docs":{"86":{"tf":1.4142135623730951}},"e":{"a":{"df":1,"docs":{"51":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":6,"docs":{"146":{"tf":1.0},"167":{"tf":1.0},"227":{"tf":1.0},"228":{"tf":1.0},"230":{"tf":1.0},"258":{"tf":1.0}}}}}}},"i":{"c":{"df":1,"docs":{"263":{"tf":1.0}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"87":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":1,"docs":{"51":{"tf":1.0}}}}}}},"n":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"d":{"df":4,"docs":{"29":{"tf":1.0},"31":{"tf":1.0},"34":{"tf":1.0},"36":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"190":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"a":{"d":{"df":1,"docs":{"61":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"df":2,"docs":{"47":{"tf":1.0},"60":{"tf":1.0}}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"254":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"f":{"a":{"c":{"df":1,"docs":{"104":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"c":{"df":2,"docs":{"105":{"tf":1.0},"213":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"v":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"v":{"df":1,"docs":{"116":{"tf":1.0}}}}}}},"o":{"df":8,"docs":{"127":{"tf":1.0},"14":{"tf":1.0},"15":{"tf":1.0},"202":{"tf":1.0},"23":{"tf":1.0},"31":{"tf":1.0},"32":{"tf":1.0},"56":{"tf":1.0}},"s":{"/":{"df":0,"docs":{},"s":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":1,"docs":{"66":{"tf":1.0}}}}}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":1,"docs":{"6":{"tf":1.0}}}}}},"j":{"df":0,"docs":{},"n":{"a":{"df":2,"docs":{"116":{"tf":1.0},"37":{"tf":1.0}}},"df":0,"docs":{},"i":{"df":1,"docs":{"116":{"tf":1.0}}}}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":1,"docs":{"212":{"tf":1.0}}}},"y":{"df":2,"docs":{"145":{"tf":1.4142135623730951},"146":{"tf":1.0}}}},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":1,"docs":{"114":{"tf":1.0}},"n":{"df":2,"docs":{"155":{"tf":1.0},"156":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":7,"docs":{"108":{"tf":1.0},"111":{"tf":1.0},"115":{"tf":1.0},"128":{"tf":1.0},"45":{"tf":1.0},"70":{"tf":1.0},"98":{"tf":1.0}}}}}}}},"l":{"a":{"df":0,"docs":{},"y":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"44":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":4,"docs":{"230":{"tf":1.0},"235":{"tf":1.0},"251":{"tf":1.0},"254":{"tf":1.0}}}}}},"i":{"b":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":4,"docs":{"114":{"tf":1.0},"191":{"tf":1.0},"196":{"tf":1.0},"77":{"tf":1.0}}}}},"df":0,"docs":{}}},"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":1,"docs":{"2":{"tf":1.0}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"y":{"c":{"df":0,"docs":{},"l":{"df":1,"docs":{"131":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":3,"docs":{"202":{"tf":1.0},"213":{"tf":1.0},"214":{"tf":1.0}}}}},"n":{"df":0,"docs":{},"e":{"df":1,"docs":{"237":{"tf":1.0}}},"k":{"df":3,"docs":{"147":{"tf":1.0},"185":{"tf":1.0},"218":{"tf":1.0}}}},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"233":{"tf":1.0}}}}},"o":{"a":{"d":{"df":1,"docs":{"114":{"tf":1.0}}},"df":0,"docs":{}},"c":{"a":{"df":0,"docs":{},"l":{"df":12,"docs":{"15":{"tf":1.4142135623730951},"16":{"tf":1.0},"23":{"tf":1.0},"26":{"tf":1.0},"29":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.0},"32":{"tf":1.0},"34":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"g":{"df":4,"docs":{"125":{"tf":1.0},"126":{"tf":1.0},"127":{"tf":1.0},"134":{"tf":1.0}},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"139":{"tf":1.0}}}}},"w":{"df":2,"docs":{"230":{"tf":1.0},"235":{"tf":1.0}}}}},"m":{"a":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":1,"docs":{"57":{"tf":1.0}}}},"n":{"a":{"df":0,"docs":{},"g":{"df":11,"docs":{"146":{"tf":1.0},"225":{"tf":1.0},"226":{"tf":1.0},"23":{"tf":1.0},"245":{"tf":1.0},"268":{"tf":1.0},"269":{"tf":1.0},"32":{"tf":1.0},"63":{"tf":1.0},"73":{"tf":1.0},"81":{"tf":1.0}}}},"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"20":{"tf":1.0},"27":{"tf":1.0}}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"k":{"d":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":0,"docs":{},"n":{"df":1,"docs":{"135":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"a":{"df":0,"docs":{},"z":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"d":{"df":3,"docs":{"220":{"tf":1.0},"222":{"tf":1.0},"73":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"g":{"df":1,"docs":{"119":{"tf":1.0}}}},"s":{"df":0,"docs":{},"s":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"106":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"a":{"df":1,"docs":{"80":{"tf":1.0}}},"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"d":{"df":1,"docs":{"131":{"tf":1.0}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"259":{"tf":1.0}}},"df":0,"docs":{}}}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":2,"docs":{"202":{"tf":1.0},"212":{"tf":1.0}}}},"df":0,"docs":{}}},"s":{"c":{"df":1,"docs":{"240":{"tf":1.0}}},"df":0,"docs":{}}},"o":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"186":{"tf":1.0}}}}},"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":1,"docs":{"37":{"tf":1.0}}}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":1,"docs":{"229":{"tf":1.0}}}},"v":{"df":0,"docs":{},"e":{"df":1,"docs":{"158":{"tf":1.0}}}},"z":{"df":1,"docs":{"276":{"tf":1.0}},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"df":2,"docs":{"120":{"tf":1.0},"261":{"tf":1.0}}},"df":0,"docs":{}}}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"255":{"tf":1.0}},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"172":{"tf":1.0}}}}}}}}},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":3,"docs":{"112":{"tf":1.0},"114":{"tf":1.0},"91":{"tf":1.0}}}},"r":{"df":0,"docs":{},"r":{"df":1,"docs":{"286":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":2,"docs":{"166":{"tf":1.0},"210":{"tf":1.0}}},"w":{"df":6,"docs":{"123":{"tf":1.0},"196":{"tf":1.0},"268":{"tf":1.0},"269":{"tf":1.0},"281":{"tf":1.0},"68":{"tf":1.0}}},"x":{"df":0,"docs":{},"t":{"df":1,"docs":{"103":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":3,"docs":{"266":{"tf":1.0},"272":{"tf":1.0},"40":{"tf":1.0}}}}}}},"m":{"b":{"df":0,"docs":{},"u":{"df":2,"docs":{"194":{"tf":1.0},"198":{"tf":1.0}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"189":{"tf":1.0},"241":{"tf":1.0}}},"t":{"df":0,"docs":{},"h":{"df":2,"docs":{"169":{"tf":1.0},"182":{"tf":1.0}}}},"w":{"df":1,"docs":{"257":{"tf":1.0}}}},"s":{"df":0,"docs":{},"s":{"df":2,"docs":{"278":{"tf":1.0},"280":{"tf":1.0}}}}},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":5,"docs":{"128":{"tf":1.0},"129":{"tf":1.0},"130":{"tf":1.0},"131":{"tf":1.0},"132":{"tf":1.0}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":1,"docs":{"204":{"tf":1.0}}}}}}},"df":0,"docs":{},"n":{"df":1,"docs":{"258":{"tf":1.0}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":2,"docs":{"154":{"tf":1.0},"155":{"tf":1.0}}},"r":{"df":1,"docs":{"156":{"tf":1.0}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":18,"docs":{"137":{"tf":1.0},"142":{"tf":1.0},"144":{"tf":1.0},"145":{"tf":1.0},"146":{"tf":1.0},"151":{"tf":1.0},"153":{"tf":1.0},"163":{"tf":1.0},"168":{"tf":1.0},"179":{"tf":1.0},"181":{"tf":1.0},"195":{"tf":1.0},"196":{"tf":1.0},"197":{"tf":1.0},"198":{"tf":1.0},"199":{"tf":1.0},"207":{"tf":1.0},"211":{"tf":1.0}}}}}}},"u":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":6,"docs":{"138":{"tf":1.0},"143":{"tf":1.0},"152":{"tf":1.0},"164":{"tf":1.0},"180":{"tf":1.0},"208":{"tf":1.0}}}}},"df":0,"docs":{}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":4,"docs":{"243":{"tf":1.0},"93":{"tf":1.0},"96":{"tf":1.0},"99":{"tf":1.0}}}}}}}}}},"p":{"a":{"c":{"df":0,"docs":{},"k":{"a":{"df":0,"docs":{},"g":{"df":9,"docs":{"112":{"tf":1.0},"113":{"tf":1.0},"160":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"23":{"tf":1.0},"251":{"tf":1.0},"32":{"tf":1.0},"73":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"145":{"tf":1.0}}}},"t":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"270":{"tf":1.0}}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":1,"docs":{"247":{"tf":1.0}}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"274":{"tf":1.0}}}}}}}},"l":{"a":{"c":{"df":0,"docs":{},"e":{"df":1,"docs":{"202":{"tf":1.0}}}},"df":0,"docs":{},"n":{"df":2,"docs":{"200":{"tf":1.0},"230":{"tf":1.0}}},"t":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":1,"docs":{"255":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"78":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":2,"docs":{"261":{"tf":1.0},"262":{"tf":1.0}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"165":{"tf":1.0},"209":{"tf":1.0}}}},"s":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"237":{"tf":1.0}}}},"df":0,"docs":{}}}}},"r":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"m":{"a":{"df":1,"docs":{"264":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"df":2,"docs":{"171":{"tf":1.0},"172":{"tf":1.0}},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"232":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"24":{"tf":1.0}}}}}}}}}}},"o":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":9,"docs":{"136":{"tf":1.0},"140":{"tf":1.0},"149":{"tf":1.0},"161":{"tf":1.0},"175":{"tf":1.0},"177":{"tf":1.0},"187":{"tf":1.0},"203":{"tf":1.0},"205":{"tf":1.0}}}}}},"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"265":{"tf":1.0}}}}}},"df":5,"docs":{"144":{"tf":1.0},"153":{"tf":1.0},"168":{"tf":1.0},"181":{"tf":1.0},"211":{"tf":1.0}},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"5":{"tf":1.0}}}},"df":0,"docs":{}}},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":1,"docs":{"273":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"f":{"df":1,"docs":{"106":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"u":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"107":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":5,"docs":{"113":{"tf":1.0},"16":{"tf":1.0},"18":{"tf":1.0},"22":{"tf":1.0},"274":{"tf":1.0}}}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":1,"docs":{"7":{"tf":1.0}}}},"r":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":1,"docs":{"124":{"tf":1.0}}}}}},"t":{"df":1,"docs":{"61":{"tf":1.0}}}}},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":1,"docs":{"216":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"a":{"d":{"df":1,"docs":{"174":{"tf":1.0}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"135":{"tf":1.0}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":2,"docs":{"157":{"tf":1.0},"158":{"tf":1.0}}}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"u":{"c":{"df":1,"docs":{"145":{"tf":1.0}}},"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"60":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":8,"docs":{"183":{"tf":1.0},"265":{"tf":1.0},"267":{"tf":1.0},"268":{"tf":1.4142135623730951},"269":{"tf":1.4142135623730951},"270":{"tf":1.0},"271":{"tf":1.0},"273":{"tf":1.0}}}},"df":0,"docs":{}}},"m":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"145":{"tf":1.0}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":4,"docs":{"186":{"tf":1.0},"192":{"tf":1.0},"193":{"tf":1.0},"197":{"tf":1.0}}},"v":{"df":1,"docs":{"106":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"4":{"tf":1.0}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"253":{"tf":1.0}}}}}}}}}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"7":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"r":{"df":4,"docs":{"145":{"tf":1.0},"188":{"tf":1.0},"189":{"tf":1.0},"190":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"89":{"tf":1.0}}}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":1,"docs":{"226":{"tf":1.0}}}}}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"105":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"113":{"tf":1.0}}}}}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":1,"docs":{"184":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":1,"docs":{"159":{"tf":1.0}}}}}},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":1,"docs":{"8":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":1,"docs":{"224":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"n":{"df":8,"docs":{"126":{"tf":1.0},"127":{"tf":1.0},"176":{"tf":1.4142135623730951},"184":{"tf":1.0},"204":{"tf":1.0},"216":{"tf":1.0},"30":{"tf":1.0},"35":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"17":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"d":{"df":0,"docs":{},"o":{"c":{"df":1,"docs":{"283":{"tf":1.0}}},"df":0,"docs":{}}},"df":30,"docs":{"0":{"tf":1.0},"105":{"tf":1.0},"107":{"tf":1.0},"11":{"tf":1.0},"110":{"tf":1.0},"111":{"tf":1.0},"114":{"tf":1.0},"115":{"tf":1.0},"117":{"tf":1.0},"172":{"tf":1.0},"193":{"tf":1.0},"228":{"tf":1.0},"241":{"tf":1.0},"251":{"tf":1.0},"253":{"tf":1.0},"260":{"tf":1.0},"261":{"tf":1.0},"262":{"tf":1.0},"29":{"tf":1.0},"31":{"tf":1.0},"34":{"tf":1.0},"36":{"tf":1.0},"41":{"tf":1.0},"43":{"tf":1.0},"64":{"tf":1.0},"69":{"tf":1.0},"74":{"tf":1.0},"75":{"tf":1.0},"83":{"tf":1.0},"92":{"tf":1.0}}}}}},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{"df":1,"docs":{"81":{"tf":1.0}}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":3,"docs":{"269":{"tf":1.0},"30":{"tf":1.0},"35":{"tf":1.0}}}}}}},"d":{"df":0,"docs":{},"k":{"df":1,"docs":{"194":{"tf":1.0}}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"275":{"tf":1.0}}}}}},"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"7":{"tf":1.0}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":18,"docs":{"0":{"tf":1.0},"118":{"tf":1.0},"120":{"tf":1.0},"125":{"tf":1.0},"15":{"tf":1.0},"252":{"tf":1.0},"259":{"tf":1.0},"262":{"tf":1.0},"265":{"tf":1.0},"274":{"tf":1.0},"3":{"tf":1.0},"30":{"tf":1.0},"35":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0},"52":{"tf":1.0},"68":{"tf":1.0},"9":{"tf":1.0}}},"df":0,"docs":{}}}},"t":{"df":6,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"186":{"tf":1.0},"192":{"tf":1.0},"193":{"tf":1.0},"197":{"tf":1.0}},"u":{"df":0,"docs":{},"p":{"df":2,"docs":{"13":{"tf":1.0},"175":{"tf":1.0}}}}}},"h":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":1,"docs":{"132":{"tf":1.0}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":1,"docs":{"251":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"201":{"tf":1.0}}}}}},"df":0,"docs":{}}},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"l":{"df":1,"docs":{"171":{"tf":1.0}},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"130":{"tf":1.0}}}}}}}}}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"167":{"tf":1.0}}}},"df":0,"docs":{}}}},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":1,"docs":{"52":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"59":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"r":{"c":{"df":1,"docs":{"172":{"tf":1.0}}},"df":0,"docs":{}}}},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":2,"docs":{"200":{"tf":1.0},"235":{"tf":1.0}}}}},"df":0,"docs":{}}},"q":{"df":0,"docs":{},"l":{"df":1,"docs":{"62":{"tf":1.0}},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":2,"docs":{"264":{"tf":1.0},"79":{"tf":1.0}}}}}}},"r":{"c":{"df":1,"docs":{"61":{"tf":1.0}}},"df":0,"docs":{}},"t":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":1,"docs":{"249":{"tf":1.0}}}},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"257":{"tf":1.0}},"u":{"df":0,"docs":{},"p":{"df":1,"docs":{"176":{"tf":1.0}}}}}},"t":{"df":0,"docs":{},"e":{"df":2,"docs":{"229":{"tf":2.0},"239":{"tf":1.0}},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":8,"docs":{"136":{"tf":1.0},"140":{"tf":1.0},"149":{"tf":1.0},"161":{"tf":1.0},"177":{"tf":1.0},"187":{"tf":1.0},"203":{"tf":1.0},"205":{"tf":1.0}}}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":1,"docs":{"117":{"tf":1.0}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":4,"docs":{"130":{"tf":1.0},"131":{"tf":1.0},"232":{"tf":1.0},"78":{"tf":1.0}},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"i":{"df":1,"docs":{"87":{"tf":1.0}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"257":{"tf":1.0}}}}}}},"df":0,"docs":{}},"u":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":3,"docs":{"72":{"tf":1.0},"76":{"tf":1.0},"83":{"tf":1.0}}}}}},"df":0,"docs":{}}},"u":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":1,"docs":{"117":{"tf":1.0}}}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"214":{"tf":1.0},"217":{"tf":1.0}}}}}}},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"49":{"tf":1.0}}}},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"22":{"tf":1.0}}}}}}}},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":17,"docs":{"109":{"tf":1.0},"160":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"23":{"tf":1.0},"251":{"tf":1.0},"253":{"tf":1.0},"29":{"tf":1.0},"31":{"tf":1.0},"32":{"tf":1.0},"34":{"tf":1.0},"36":{"tf":1.0},"46":{"tf":1.0},"71":{"tf":1.0},"73":{"tf":1.0},"74":{"tf":1.0},"95":{"tf":1.0}}}}}},"y":{"df":0,"docs":{},"n":{"c":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"88":{"tf":1.0}}}}}}}}},"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"75":{"tf":1.0}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"250":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":15,"docs":{"225":{"tf":1.0},"226":{"tf":1.0},"234":{"tf":1.0},"243":{"tf":1.0},"245":{"tf":1.0},"246":{"tf":1.0},"254":{"tf":1.0},"255":{"tf":1.0},"256":{"tf":1.0},"257":{"tf":1.0},"258":{"tf":1.0},"48":{"tf":1.0},"84":{"tf":1.0},"85":{"tf":1.0},"86":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"248":{"tf":1.0}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"103":{"tf":1.0}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":13,"docs":{"124":{"tf":1.0},"223":{"tf":1.0},"23":{"tf":1.0},"32":{"tf":1.0},"41":{"tf":1.0},"42":{"tf":1.0},"47":{"tf":1.0},"48":{"tf":1.0},"49":{"tf":1.0},"50":{"tf":1.0},"52":{"tf":1.0},"57":{"tf":1.4142135623730951},"60":{"tf":1.0}},"s":{"/":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"61":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"240":{"tf":1.0}}}}}},"i":{"df":0,"docs":{},"e":{"df":1,"docs":{"240":{"tf":1.0}}},"m":{"df":0,"docs":{},"e":{"df":2,"docs":{"10":{"tf":1.0},"57":{"tf":1.0}}}},"p":{"df":1,"docs":{"282":{"tf":1.0}}}},"o":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"240":{"tf":1.0}}}}}}},"r":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":1,"docs":{"116":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"82":{"tf":1.0}}}}}}}},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":2,"docs":{"155":{"tf":1.0},"156":{"tf":1.0}}}}}},"u":{"d":{"df":0,"docs":{},"l":{"df":1,"docs":{"104":{"tf":1.0}}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":4,"docs":{"101":{"tf":1.0},"102":{"tf":1.0},"105":{"tf":1.0},"128":{"tf":1.0}}}}},"t":{"df":2,"docs":{"223":{"tf":1.0},"42":{"tf":1.0}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":1,"docs":{"124":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"p":{"d":{"a":{"df":0,"docs":{},"t":{"df":4,"docs":{"122":{"tf":1.0},"139":{"tf":1.0},"279":{"tf":1.0},"280":{"tf":1.0}}}},"df":0,"docs":{}},"df":2,"docs":{"108":{"tf":1.0},"109":{"tf":1.0}},"g":{"df":0,"docs":{},"r":{"a":{"d":{"df":1,"docs":{"278":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":1,"docs":{"270":{"tf":1.0}}}}}}},"s":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"264":{"tf":1.0}}}},"df":14,"docs":{"101":{"tf":1.0},"116":{"tf":1.0},"135":{"tf":1.0},"16":{"tf":1.0},"170":{"tf":1.0},"18":{"tf":1.0},"19":{"tf":1.0},"197":{"tf":1.0},"198":{"tf":1.0},"20":{"tf":1.0},"25":{"tf":1.0},"27":{"tf":1.0},"37":{"tf":1.0},"79":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"215":{"tf":1.0},"52":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"237":{"tf":1.0}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":3,"docs":{"120":{"tf":1.0},"121":{"tf":1.0},"124":{"tf":1.0}}}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":5,"docs":{"124":{"tf":1.0},"260":{"tf":1.0},"262":{"tf":1.0},"279":{"tf":1.0},"37":{"tf":1.0}}}}},"u":{"df":1,"docs":{"88":{"tf":1.0}}}}}},"i":{"a":{"df":2,"docs":{"13":{"tf":1.0},"269":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":3,"docs":{"202":{"tf":1.0},"213":{"tf":1.0},"216":{"tf":1.0}}}}}},"s":{"df":1,"docs":{"232":{"tf":2.0}}}},"w":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"t":{"'":{"df":1,"docs":{"89":{"tf":1.0}}},"df":0,"docs":{},"’":{"df":2,"docs":{"86":{"tf":1.0},"87":{"tf":1.0}}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":1,"docs":{"13":{"tf":1.0}},"s":{"/":{"df":0,"docs":{},"w":{"df":0,"docs":{},"s":{"df":0,"docs":{},"l":{"df":1,"docs":{"19":{"tf":1.0}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"e":{"df":1,"docs":{"89":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"239":{"tf":1.0}},"f":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":3,"docs":{"18":{"tf":1.0},"20":{"tf":1.0},"22":{"tf":1.0}}}}}}},"s":{"df":1,"docs":{"57":{"tf":1.0}}}}},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"108":{"tf":1.0},"109":{"tf":1.0}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":2,"docs":{"104":{"tf":1.0},"196":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"l":{"df":1,"docs":{"13":{"tf":1.0}}}}},"x":{"c":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":6,"docs":{"170":{"tf":1.0},"252":{"tf":1.0},"28":{"tf":1.0},"29":{"tf":1.0},"33":{"tf":1.0},"34":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"lang":"English","pipeline":["trimmer","stopWordFilter","stemmer"],"ref":"id","version":"0.9.5"},"results_options":{"limit_results":30,"teaser_word_count":30},"search_options":{"bool":"OR","expand":true,"fields":{"body":{"boost":1},"breadcrumbs":{"boost":1},"title":{"boost":2}}}}); \ No newline at end of file diff --git a/book/searchindex.json b/book/searchindex.json new file mode 100644 index 0000000000..b55e9e2357 --- /dev/null +++ b/book/searchindex.json @@ -0,0 +1 @@ +{"doc_urls":["index.html#application-services-rust-components","index.html#contact-us","index.html#license","contributing.html#contributing-to-application-services","contributing.html#bug-reports","contributing.html#building-the-project","contributing.html#finding-issues","contributing.html#sending-pull-requests","contributing.html#code-review","building.html#building-application-services","building.html#first-time-builds","building.html#building-the-rust-components","building.html#building-for-fenix","building.html#windows-setup-for-android-via-wsl","building.html#building-for-firefox-ios","building.html#locally-building-firefox-ios-against-a-local-application-services","howtos/locally-published-components-in-fenix.html#using-locally-published-components-in-fenix","howtos/locally-published-components-in-fenix.html#rusttargets","howtos/locally-published-components-in-fenix.html#using-the-auto-publishing-workflow","howtos/locally-published-components-in-fenix.html#using-windowswsl","howtos/locally-published-components-in-fenix.html#using-a-manual-workflow","howtos/locally-published-components-in-fenix.html#caveats","howtos/locally-published-components-in-fenix.html#adding-support-for-the-auto-publish-workflow","howtos/locally-published-components-in-firefox-ios.html#how-to-locally-test-swift-package-manager-components-on-firefox-ios","howtos/locally-published-components-in-firefox-ios.html#prerequisites","howtos/locally-published-components-in-firefox-ios.html#using-the-automated-flow","howtos/locally-published-components-in-firefox-ios.html#disabling-local-development","howtos/locally-published-components-in-firefox-ios.html#using-the-manual-flow","howtos/locally-published-components-in-firefox-ios.html#building-the-xcframework","howtos/locally-published-components-in-firefox-ios.html#include-the-xcframework-in-a-local-checkout-of-rust-components-swift","howtos/locally-published-components-in-firefox-ios.html#run-the-generation-script-with-a-local-checkout-of-application-services","howtos/locally-published-components-in-firefox-ios.html#include-the-local-checkout-of-rust-components-swift-in-firefox-ios","howtos/locally-published-components-in-focus-ios.html#how-to-locally-test-swift-package-manager-components-on-focus-ios","howtos/locally-published-components-in-focus-ios.html#building-the-xcframework","howtos/locally-published-components-in-focus-ios.html#include-the-xcframework-in-a-local-checkout-of-rust-components-swift","howtos/locally-published-components-in-focus-ios.html#run-the-generation-script-with-a-local-checkout-of-application-services","howtos/locally-published-components-in-focus-ios.html#include-the-local-checkout-of-rust-components-swift-in-focus","howtos/locally-building-jna.html#building-and-using-a-locally-modified-version-of-jna","howtos/branch-builds.html#branch-builds","howtos/branch-builds.html#breaking-changes-in-an-application-services-branch","howtos/branch-builds.html#application-services-nightlies","howtos/testing-a-rust-component.html#guide-to-testing-a-rust-component","howtos/testing-a-rust-component.html#unit-and-functional-tests","howtos/testing-a-rust-component.html#rust-code","howtos/testing-a-rust-component.html#ffi-layer-code","howtos/testing-a-rust-component.html#kotlin-code","howtos/testing-a-rust-component.html#swift-code","howtos/testing-a-rust-component.html#integration-tests","howtos/testing-a-rust-component.html#end-to-end-sync-tests","howtos/testing-a-rust-component.html#android-components-test-suite","howtos/testing-a-rust-component.html#test-coverage","howtos/testing-a-rust-component.html#ideas-for-improvement","howtos/smoke-testing-app-services.html#smoke-testing-application-services-against-end-user-apps","howtos/smoke-testing-app-services.html#dependencies","howtos/smoke-testing-app-services.html#android-components","howtos/smoke-testing-app-services.html#fenix","howtos/smoke-testing-app-services.html#firefox-ios","design/test-faster.html#testing-faster-how-to-avoid-making-compile-times-worse-by-adding-tests","design/test-faster.html#background","design/test-faster.html#the-solution","design/test-faster.html#appendix-how-to-avoid-redundant-compiles-for-benchmarks-and-integration-tests","design/test-faster.html#faq0-why-put-testsbenches-in-src-instead-of-disabling-autotestsautobenches","howtos/debug-sql.html#debugging-sql","dependency-management.html#dependency-management-guidelines","dependency-management.html#rust-code","dependency-management.html#androidkotlin-code","dependency-management.html#iosswift-code","dependency-management.html#other-code","howtos/adding-a-new-component.html#adding-a-new-component-to-application-services","howtos/adding-a-new-component.html#the-rust-code","howtos/adding-a-new-component.html#the-kotlin-bindings","howtos/adding-a-new-component.html#the-swift-bindings","howtos/adding-a-new-component.html#creating-the-directory-structure","howtos/adding-a-new-component.html#adding-your-component-to-the-swift-package-manager-megazord","howtos/adding-a-new-component.html#distribute-your-component-with-rust-components-swift","howtos/building-a-rust-component.html#guide-to-building-a-syncable-rust-component","howtos/building-a-rust-component.html#general-design-and-structure-of-the-component","howtos/building-a-rust-component.html#we-build-libraries-not-frameworks","howtos/building-a-rust-component.html#the-store-is-the-entry-point","howtos/building-a-rust-component.html#using-sqlite","howtos/building-a-rust-component.html#meta-data","howtos/building-a-rust-component.html#schema-management","howtos/building-a-rust-component.html#triggers","howtos/building-a-rust-component.html#general-structure-of-the-rust-code","howtos/building-a-rust-component.html#syncing","howtos/building-a-rust-component.html#syncing-faqs","howtos/building-a-rust-component.html#whats-the-global-sync-id-and-the-collection-sync-id","howtos/building-a-rust-component.html#whats-get_sync_assoc-why-is-it-important-what-is-storesyncassociation","howtos/building-a-rust-component.html#what-is-apply_incoming-versus-sync_finished","howtos/building-a-rust-component.html#whats-the-diff-between-reset-and-wipe","howtos/building-a-rust-component.html#exposing-to-consumers","naming-conventions.html#naming-conventions","naming-conventions.html#rust-code","naming-conventions.html#overview","naming-conventions.html#examples","naming-conventions.html#swift-code","naming-conventions.html#overview-1","naming-conventions.html#examples-1","naming-conventions.html#kotlin-code","naming-conventions.html#overview-2","naming-conventions.html#examples-2","howtos/converting-a-component-to-uniffi.html#converting-an-existing-component-to-use-uniffi","howtos/converting-a-component-to-uniffi.html#first-get-familiar-with-uniffi","howtos/converting-a-component-to-uniffi.html#next-get-familiar-with-the-target-component","howtos/converting-a-component-to-uniffi.html#write-a-first-draft-of-the-udl-file-for-the-components-interface","howtos/converting-a-component-to-uniffi.html#restructure-the-rust-code-to-introduce-uniffi","howtos/converting-a-component-to-uniffi.html#removing-protobuf-messages","howtos/converting-a-component-to-uniffi.html#document-the-public-api-in-the-rust-code","howtos/converting-a-component-to-uniffi.html#set-up-the-kotlin-wrapper","howtos/converting-a-component-to-uniffi.html#set-up-the-swift-wrapper","android-faqs.html#rust--android-faqs","android-faqs.html#how-do-i-expose-rust-code-to-kotlin","android-faqs.html#how-should-i-name-the-package","android-faqs.html#how-do-i-publish-the-resulting-package","android-faqs.html#how-do-i-know-what-library-name-to-load-to-access-the-compiled-rust-code","android-faqs.html#what-challenges-exist-when-calling-back-into-kotlin-from-rust","android-faqs.html#why-are-we-using-jna-rather-than-jni-and-what-tradeoffs-does-that-involve","android-faqs.html#how-do-i-debug-rust-code-with-the-step-debugger-in-android-studio","howtos/breaking-changes.html#breaking-changes-in-application-services-code","howtos/breaking-changes.html#merging","howtos/vendoring-into-mozilla-central.html#vendoring-application-services-into-mozilla-central","howtos/vendoring-into-mozilla-central.html#when-to-vendor","howtos/vendoring-into-mozilla-central.html#updating-existing-components","howtos/vendoring-into-mozilla-central.html#adding-a-new-component","howtos/vendoring-into-mozilla-central.html#vendoring-an-unreleased-version-for-testing-purposes","logging.html#application-services-logging","logging.html#accessing-logs-when-running-fenix","logging.html#accessing-logs-when-running-ios","howtos/uniffi-object-destruction-on-kotlin.html#uniffi-object-destruction-on-kotlin","howtos/uniffi-object-destruction-on-kotlin.html#you-can-create-objects-in-a-function-if-you-also-destroy-them-there","howtos/uniffi-object-destruction-on-kotlin.html#you-can-create-and-store-objects-in-singletons","howtos/uniffi-object-destruction-on-kotlin.html#you-can-create-and-store-objects-in-an-class-then-destroy-them-in-a-corresponding-lifecycle-method","howtos/uniffi-object-destruction-on-kotlin.html#you-can-share-objects","howtos/uniffi-object-destruction-on-kotlin.html#destruction-may-not-always-happen","adr/index.html#architectural-decision-log","adr/0000-use-markdown-architectural-decision-records.html#use-markdown-architectural-decision-records","adr/0000-use-markdown-architectural-decision-records.html#context-and-problem-statement","adr/0000-use-markdown-architectural-decision-records.html#considered-options","adr/0000-use-markdown-architectural-decision-records.html#decision-outcome","adr/0001-update-logins-api.html#update-logins-api","adr/0001-update-logins-api.html#context-and-problem-statement","adr/0001-update-logins-api.html#decision-drivers","adr/0001-update-logins-api.html#considered-options","adr/0001-update-logins-api.html#decision-outcome","adr/0001-update-logins-api.html#pros-and-cons-of-the-options","adr/0001-update-logins-api.html#option-1---reduce-the-api-functions-that-require-the-encryption-key-and-pass-the-key-to-the-remaining-functions","adr/0001-update-logins-api.html#option-2---implement-a-different-key-management-approach","adr/0001-update-logins-api.html#links","adr/0002-database-corruption.html#handling-database-corruption","adr/0002-database-corruption.html#context-and-problem-statement","adr/0002-database-corruption.html#decision-drivers","adr/0002-database-corruption.html#considered-options","adr/0002-database-corruption.html#decision-outcome","adr/0002-database-corruption.html#pros-and-cons-of-the-options","adr/0002-database-corruption.html#a1-assume-all-errors-when-opening-a-database-are-from-corrupt-databases","adr/0002-database-corruption.html#a2-check-for-errors-when-opening-a-database-and-compare-against-known-corruption-error-types-decided","adr/0002-database-corruption.html#a3-check-for-errors-for-all-database-operations-and-compare-against-known-corruption-error-types","adr/0002-database-corruption.html#b1-delete-the-database-file-and-recreate-the-database-decided","adr/0002-database-corruption.html#b2-move-the-database-file-and-recreate-the-database","adr/0002-database-corruption.html#b3-return-a-failure-code","adr/0003-swift-packaging.html#distributing-swift-packages","adr/0003-swift-packaging.html#context-and-problem-statement","adr/0003-swift-packaging.html#decision-drivers","adr/0003-swift-packaging.html#considered-options","adr/0003-swift-packaging.html#decision-outcome","adr/0003-swift-packaging.html#positive-consequences","adr/0003-swift-packaging.html#negative-consequences","adr/0003-swift-packaging.html#implementation-sketch","adr/0003-swift-packaging.html#pros-and-cons-of-the-options","adr/0003-swift-packaging.html#a-do-nothing","adr/0003-swift-packaging.html#b-use-carthage-to-build-xcframework-bundles","adr/0003-swift-packaging.html#c-distribute-a-single-pre-compiled-swift-package","adr/0003-swift-packaging.html#d-distribute-multiple-source-based-swift-packages-with-pre-compiled-rust-code","adr/0003-swift-packaging.html#appendix","adr/0003-swift-packaging.html#further-reading","adr/0003-swift-packaging.html#problems-with-the-current-setup","adr/0004-early-startup-experiments.html#running-experiments-on-first-run-early-startup","adr/0004-early-startup-experiments.html#context-and-problem-statement","adr/0004-early-startup-experiments.html#decision-drivers","adr/0004-early-startup-experiments.html#considered-options","adr/0004-early-startup-experiments.html#decision-outcome","adr/0004-early-startup-experiments.html#pros-and-cons-of-the-options","adr/0004-early-startup-experiments.html#do-nothing","adr/0004-early-startup-experiments.html#bundle-experiment-data-with-app-on-release","adr/0004-early-startup-experiments.html#retrieve-experiment-data-on-first-run-and-deal-with-delay","adr/0004-early-startup-experiments.html#links","adr/0005-remote-settings-client.html#a-remote-settings-client-for-our-mobile-browsers","adr/0005-remote-settings-client.html#context-and-problem-statement","adr/0005-remote-settings-client.html#requirements","adr/0005-remote-settings-client.html#non-requirements","adr/0005-remote-settings-client.html#initial-requirements","adr/0005-remote-settings-client.html#existing-libraries","adr/0005-remote-settings-client.html#remote-settings-on-desktop","adr/0005-remote-settings-client.html#rust-remote-settings-client","adr/0005-remote-settings-client.html#the-nimbus-sdk-client","adr/0005-remote-settings-client.html#considered-options","adr/0005-remote-settings-client.html#option-1-writing-a-new-library","adr/0005-remote-settings-client.html#option-2-use-the-existing-remote-settings-client","adr/0005-remote-settings-client.html#option-3-use-the-existing-nimbus-client","adr/0005-remote-settings-client.html#chosen-option","adr/0005-remote-settings-client.html#specific-plans","adr/0005-remote-settings-client.html#content-signatures","adr/0007-limit-visits-migration-to-10000.html#limit-visits-migrated-to-places-history-in-firefox-ios","adr/0007-limit-visits-migration-to-10000.html#context-and-problem-statement","adr/0007-limit-visits-migration-to-10000.html#observations-from-dry-run-experiment","adr/0007-limit-visits-migration-to-10000.html#problem-statement","adr/0007-limit-visits-migration-to-10000.html#decision-drivers","adr/0007-limit-visits-migration-to-10000.html#considered-options","adr/0007-limit-visits-migration-to-10000.html#decision-outcome","adr/0007-limit-visits-migration-to-10000.html#positive-consequences","adr/0007-limit-visits-migration-to-10000.html#negative-consequences","adr/0007-limit-visits-migration-to-10000.html#pros-and-cons-of-the-other-options","adr/0007-limit-visits-migration-to-10000.html#keep-the-migration-as-is","adr/0007-limit-visits-migration-to-10000.html#introduce-a-date-based-limit-on-visits","adr/0007-limit-visits-migration-to-10000.html#suggested-limit","adr/0007-limit-visits-migration-to-10000.html#user-history-distribution","adr/0007-limit-visits-migration-to-10000.html#dry-run-ending-rate-by-visits","adr/0007-limit-visits-migration-to-10000.html#suggestion","adr/0007-limit-visits-migration-to-10000.html#links","design/index.html#design-documents","design/megazords.html#megazording","design/megazords.html#aar-dependency-graph","design/megazords.html#custom-megazords","design/megazords.html#unit-tests","design/megazords.html#gotchas-and-rough-edges","design/sync-manager.html#sync-manager","design/sync-manager.html#the-responsibilities-of-the-sync-manager","design/sync-manager.html#implementation-details","design/sync-manager.html#current-implementations-and-challenges-with-the-rust-components","design/sync-manager.html#state-state-and-more-state-and-then-some-state","design/sync-manager.html#implementation-plan-for-the-low-level-component","design/sync-manager.html#clients-engine","design/sync-manager.html#collections-vs-engines-vs-stores-vs-preferences-vs-apis","design/sync-manager.html#the-declined-list","design/sync-manager.html#disconnecting-from-sync","design/sync-manager.html#specific-deliverables-for-the-low-level-component","design/sync-manager.html#the-api","design/sync-manager.html#a-command-line-and-possibly-android-utility","design/sync-manager.html#the-clients-engine","design/sync-manager.html#state-work","design/sync-manager.html#tie-it-together-and-other-misc-things","design/sync-manager.html#followup-with-non-rust-engines","design/sync-manager.html#details","design/sync-overview.html#sync-overview","design/sync-overview.html#general-flow-and-architecture","design/sync-overview.html#sync-manager","design/sync-overview.html#sync-engines","design/sync-overview.html#the-apply_incoming-pattern","design/sync-overview.html#database-tables","design/sync-overview.html#apply_incoming-stages","design/sync-overview.html#syncchangecounter","design/swift-package-manager.html#high-level-design-for-shipping-rust-components-as-swift-packages","design/swift-package-manager.html#the-xcframework-and-application-services","design/swift-package-manager.html#the-rust-components-swift-repository","design/components-strategy.html#high-level-firefox-sync-interactions","design/components-strategy.html#multi-platform-sync-diagram","design/components-strategy.html#before-how-sync-was","design/components-strategy.html#now-sync-is-starting-to-streamline-its-components","design/components-strategy.html#future-only-one-implementation-for-each-sync-engine","design/metrics.html#metrics-collected-by-application-services-components","design/rust-versions.html#rust-versions","design/rust-versions.html#mozilla-central-rust-policies","design/rust-versions.html#application-services-rust-version-policy","design/rust-versions.html#implications-of-this","design/db-pragmas.html#sqlite-database-pragma-usage","howtos/releases.html#application-services-release-process","howtos/releases.html#nightly-builds","howtos/releases.html#release-builds","howtos/releases.html#release-management-creating-a-new-release","howtos/releases.html#release-management-creating-a-new-release-via-scripts","howtos/releases.html#cutting-patch-releases-for-uplifted-changes","howtos/releases.html#what-gets-built-in-a-release","howtos/releases.html#nightly-builds-1","howtos/releases.html#release-promotion","build-and-publish-pipeline.html#application-services-build-and-publish-pipeline","build-and-publish-pipeline.html#authentication-and-secrets","build-and-publish-pipeline.html#appsvc-moz-account","build-and-publish-pipeline.html#circleci","howtos/upgrading-nss-guide.html#guide-to-upgrading-nss","howtos/upgrading-nss-guide.html#updating-the-version","howtos/upgrading-nss-guide.html#updating-the-cross-compiled-nss-artifacts","howtos/upgrading-nss-guide.html#exposing-new-functions","howtos/upgrading-nss-guide.html#tips-for-fixing-bustage","rust-docs/fxa_client/index.html","adding-docs.html#developing-documentation","adding-docs.html#building-documentation","adding-docs.html#building-the-narrative-book-documentation"],"index":{"documentStore":{"docInfo":{"0":{"body":32,"breadcrumbs":8,"title":4},"1":{"body":31,"breadcrumbs":5,"title":1},"10":{"body":33,"breadcrumbs":5,"title":3},"100":{"body":12,"breadcrumbs":7,"title":1},"101":{"body":57,"breadcrumbs":13,"title":5},"102":{"body":31,"breadcrumbs":11,"title":3},"103":{"body":226,"breadcrumbs":12,"title":4},"104":{"body":184,"breadcrumbs":15,"title":7},"105":{"body":212,"breadcrumbs":13,"title":5},"106":{"body":191,"breadcrumbs":11,"title":3},"107":{"body":65,"breadcrumbs":13,"title":5},"108":{"body":167,"breadcrumbs":12,"title":4},"109":{"body":198,"breadcrumbs":12,"title":4},"11":{"body":338,"breadcrumbs":5,"title":3},"110":{"body":0,"breadcrumbs":11,"title":3},"111":{"body":53,"breadcrumbs":12,"title":4},"112":{"body":16,"breadcrumbs":10,"title":2},"113":{"body":12,"breadcrumbs":11,"title":3},"114":{"body":20,"breadcrumbs":16,"title":8},"115":{"body":151,"breadcrumbs":14,"title":6},"116":{"body":134,"breadcrumbs":13,"title":5},"117":{"body":99,"breadcrumbs":15,"title":7},"118":{"body":70,"breadcrumbs":9,"title":5},"119":{"body":94,"breadcrumbs":5,"title":1},"12":{"body":130,"breadcrumbs":4,"title":2},"120":{"body":26,"breadcrumbs":11,"title":5},"121":{"body":37,"breadcrumbs":7,"title":1},"122":{"body":108,"breadcrumbs":9,"title":3},"123":{"body":101,"breadcrumbs":9,"title":3},"124":{"body":156,"breadcrumbs":11,"title":5},"125":{"body":27,"breadcrumbs":5,"title":3},"126":{"body":89,"breadcrumbs":6,"title":4},"127":{"body":6,"breadcrumbs":6,"title":4},"128":{"body":63,"breadcrumbs":9,"title":4},"129":{"body":19,"breadcrumbs":9,"title":4},"13":{"body":74,"breadcrumbs":7,"title":5},"130":{"body":15,"breadcrumbs":9,"title":4},"131":{"body":29,"breadcrumbs":13,"title":8},"132":{"body":48,"breadcrumbs":7,"title":2},"133":{"body":30,"breadcrumbs":8,"title":3},"134":{"body":69,"breadcrumbs":6,"title":3},"135":{"body":0,"breadcrumbs":10,"title":5},"136":{"body":10,"breadcrumbs":8,"title":3},"137":{"body":26,"breadcrumbs":7,"title":2},"138":{"body":49,"breadcrumbs":7,"title":2},"139":{"body":9,"breadcrumbs":8,"title":3},"14":{"body":57,"breadcrumbs":5,"title":3},"140":{"body":72,"breadcrumbs":8,"title":3},"141":{"body":32,"breadcrumbs":7,"title":2},"142":{"body":34,"breadcrumbs":7,"title":2},"143":{"body":19,"breadcrumbs":7,"title":2},"144":{"body":0,"breadcrumbs":8,"title":3},"145":{"body":194,"breadcrumbs":17,"title":12},"146":{"body":76,"breadcrumbs":12,"title":7},"147":{"body":10,"breadcrumbs":6,"title":1},"148":{"body":6,"breadcrumbs":8,"title":3},"149":{"body":42,"breadcrumbs":8,"title":3},"15":{"body":16,"breadcrumbs":10,"title":8},"150":{"body":29,"breadcrumbs":7,"title":2},"151":{"body":51,"breadcrumbs":7,"title":2},"152":{"body":68,"breadcrumbs":7,"title":2},"153":{"body":0,"breadcrumbs":8,"title":3},"154":{"body":46,"breadcrumbs":12,"title":7},"155":{"body":38,"breadcrumbs":17,"title":12},"156":{"body":46,"breadcrumbs":16,"title":11},"157":{"body":14,"breadcrumbs":12,"title":7},"158":{"body":84,"breadcrumbs":11,"title":6},"159":{"body":34,"breadcrumbs":9,"title":4},"16":{"body":39,"breadcrumbs":13,"title":5},"160":{"body":8,"breadcrumbs":8,"title":3},"161":{"body":47,"breadcrumbs":8,"title":3},"162":{"body":27,"breadcrumbs":7,"title":2},"163":{"body":75,"breadcrumbs":7,"title":2},"164":{"body":31,"breadcrumbs":7,"title":2},"165":{"body":73,"breadcrumbs":7,"title":2},"166":{"body":45,"breadcrumbs":7,"title":2},"167":{"body":259,"breadcrumbs":7,"title":2},"168":{"body":0,"breadcrumbs":8,"title":3},"169":{"body":73,"breadcrumbs":6,"title":1},"17":{"body":78,"breadcrumbs":9,"title":1},"170":{"body":137,"breadcrumbs":11,"title":6},"171":{"body":139,"breadcrumbs":12,"title":7},"172":{"body":136,"breadcrumbs":16,"title":11},"173":{"body":0,"breadcrumbs":6,"title":1},"174":{"body":68,"breadcrumbs":7,"title":2},"175":{"body":100,"breadcrumbs":8,"title":3},"176":{"body":17,"breadcrumbs":11,"title":6},"177":{"body":24,"breadcrumbs":8,"title":3},"178":{"body":23,"breadcrumbs":7,"title":2},"179":{"body":60,"breadcrumbs":7,"title":2},"18":{"body":101,"breadcrumbs":12,"title":4},"180":{"body":69,"breadcrumbs":7,"title":2},"181":{"body":0,"breadcrumbs":8,"title":3},"182":{"body":27,"breadcrumbs":6,"title":1},"183":{"body":52,"breadcrumbs":10,"title":5},"184":{"body":79,"breadcrumbs":12,"title":7},"185":{"body":34,"breadcrumbs":6,"title":1},"186":{"body":21,"breadcrumbs":10,"title":5},"187":{"body":67,"breadcrumbs":8,"title":3},"188":{"body":26,"breadcrumbs":6,"title":1},"189":{"body":111,"breadcrumbs":7,"title":2},"19":{"body":35,"breadcrumbs":10,"title":2},"190":{"body":78,"breadcrumbs":7,"title":2},"191":{"body":5,"breadcrumbs":7,"title":2},"192":{"body":33,"breadcrumbs":8,"title":3},"193":{"body":55,"breadcrumbs":9,"title":4},"194":{"body":92,"breadcrumbs":8,"title":3},"195":{"body":0,"breadcrumbs":7,"title":2},"196":{"body":62,"breadcrumbs":10,"title":5},"197":{"body":128,"breadcrumbs":12,"title":7},"198":{"body":70,"breadcrumbs":11,"title":6},"199":{"body":30,"breadcrumbs":7,"title":2},"2":{"body":14,"breadcrumbs":5,"title":1},"20":{"body":112,"breadcrumbs":11,"title":3},"200":{"body":299,"breadcrumbs":7,"title":2},"201":{"body":228,"breadcrumbs":7,"title":2},"202":{"body":11,"breadcrumbs":12,"title":7},"203":{"body":60,"breadcrumbs":8,"title":3},"204":{"body":136,"breadcrumbs":9,"title":4},"205":{"body":122,"breadcrumbs":7,"title":2},"206":{"body":68,"breadcrumbs":7,"title":2},"207":{"body":36,"breadcrumbs":7,"title":2},"208":{"body":90,"breadcrumbs":7,"title":2},"209":{"body":61,"breadcrumbs":7,"title":2},"21":{"body":48,"breadcrumbs":9,"title":1},"210":{"body":53,"breadcrumbs":7,"title":2},"211":{"body":0,"breadcrumbs":8,"title":3},"212":{"body":61,"breadcrumbs":7,"title":2},"213":{"body":68,"breadcrumbs":10,"title":5},"214":{"body":86,"breadcrumbs":7,"title":2},"215":{"body":32,"breadcrumbs":8,"title":3},"216":{"body":57,"breadcrumbs":10,"title":5},"217":{"body":47,"breadcrumbs":6,"title":1},"218":{"body":61,"breadcrumbs":6,"title":1},"219":{"body":50,"breadcrumbs":3,"title":2},"22":{"body":229,"breadcrumbs":13,"title":5},"220":{"body":104,"breadcrumbs":3,"title":1},"221":{"body":97,"breadcrumbs":5,"title":3},"222":{"body":79,"breadcrumbs":4,"title":2},"223":{"body":49,"breadcrumbs":4,"title":2},"224":{"body":91,"breadcrumbs":5,"title":3},"225":{"body":226,"breadcrumbs":5,"title":2},"226":{"body":242,"breadcrumbs":6,"title":3},"227":{"body":42,"breadcrumbs":5,"title":2},"228":{"body":127,"breadcrumbs":8,"title":5},"229":{"body":158,"breadcrumbs":8,"title":5},"23":{"body":86,"breadcrumbs":17,"title":8},"230":{"body":23,"breadcrumbs":8,"title":5},"231":{"body":131,"breadcrumbs":5,"title":2},"232":{"body":133,"breadcrumbs":12,"title":9},"233":{"body":203,"breadcrumbs":5,"title":2},"234":{"body":24,"breadcrumbs":5,"title":2},"235":{"body":8,"breadcrumbs":8,"title":5},"236":{"body":68,"breadcrumbs":4,"title":1},"237":{"body":57,"breadcrumbs":8,"title":5},"238":{"body":21,"breadcrumbs":5,"title":2},"239":{"body":15,"breadcrumbs":5,"title":2},"24":{"body":18,"breadcrumbs":10,"title":1},"240":{"body":8,"breadcrumbs":7,"title":4},"241":{"body":83,"breadcrumbs":7,"title":4},"242":{"body":351,"breadcrumbs":4,"title":1},"243":{"body":19,"breadcrumbs":5,"title":2},"244":{"body":55,"breadcrumbs":6,"title":3},"245":{"body":199,"breadcrumbs":5,"title":2},"246":{"body":32,"breadcrumbs":5,"title":2},"247":{"body":11,"breadcrumbs":5,"title":2},"248":{"body":45,"breadcrumbs":5,"title":2},"249":{"body":165,"breadcrumbs":5,"title":2},"25":{"body":142,"breadcrumbs":12,"title":3},"250":{"body":78,"breadcrumbs":4,"title":1},"251":{"body":60,"breadcrumbs":14,"title":8},"252":{"body":130,"breadcrumbs":9,"title":3},"253":{"body":100,"breadcrumbs":10,"title":4},"254":{"body":87,"breadcrumbs":9,"title":5},"255":{"body":27,"breadcrumbs":8,"title":4},"256":{"body":38,"breadcrumbs":6,"title":2},"257":{"body":83,"breadcrumbs":9,"title":5},"258":{"body":78,"breadcrumbs":10,"title":6},"259":{"body":31,"breadcrumbs":9,"title":5},"26":{"body":106,"breadcrumbs":12,"title":3},"260":{"body":43,"breadcrumbs":6,"title":2},"261":{"body":49,"breadcrumbs":8,"title":4},"262":{"body":47,"breadcrumbs":9,"title":5},"263":{"body":96,"breadcrumbs":5,"title":1},"264":{"body":120,"breadcrumbs":9,"title":4},"265":{"body":0,"breadcrumbs":5,"title":4},"266":{"body":43,"breadcrumbs":3,"title":2},"267":{"body":43,"breadcrumbs":3,"title":2},"268":{"body":328,"breadcrumbs":6,"title":5},"269":{"body":52,"breadcrumbs":8,"title":7},"27":{"body":26,"breadcrumbs":12,"title":3},"270":{"body":41,"breadcrumbs":6,"title":5},"271":{"body":84,"breadcrumbs":4,"title":3},"272":{"body":30,"breadcrumbs":3,"title":2},"273":{"body":39,"breadcrumbs":3,"title":2},"274":{"body":510,"breadcrumbs":10,"title":5},"275":{"body":0,"breadcrumbs":7,"title":2},"276":{"body":36,"breadcrumbs":8,"title":3},"277":{"body":46,"breadcrumbs":6,"title":1},"278":{"body":57,"breadcrumbs":6,"title":3},"279":{"body":78,"breadcrumbs":5,"title":2},"28":{"body":41,"breadcrumbs":11,"title":2},"280":{"body":99,"breadcrumbs":8,"title":5},"281":{"body":36,"breadcrumbs":6,"title":3},"282":{"body":126,"breadcrumbs":6,"title":3},"283":{"body":248,"breadcrumbs":2,"title":2},"284":{"body":34,"breadcrumbs":4,"title":2},"285":{"body":0,"breadcrumbs":4,"title":2},"286":{"body":27,"breadcrumbs":6,"title":4},"29":{"body":60,"breadcrumbs":16,"title":7},"3":{"body":22,"breadcrumbs":4,"title":3},"30":{"body":32,"breadcrumbs":16,"title":7},"31":{"body":123,"breadcrumbs":17,"title":8},"32":{"body":83,"breadcrumbs":16,"title":8},"33":{"body":44,"breadcrumbs":10,"title":2},"34":{"body":70,"breadcrumbs":15,"title":7},"35":{"body":32,"breadcrumbs":15,"title":7},"36":{"body":138,"breadcrumbs":15,"title":7},"37":{"body":303,"breadcrumbs":11,"title":6},"38":{"body":20,"breadcrumbs":6,"title":2},"39":{"body":74,"breadcrumbs":9,"title":5},"4":{"body":12,"breadcrumbs":3,"title":2},"40":{"body":118,"breadcrumbs":7,"title":3},"41":{"body":31,"breadcrumbs":8,"title":4},"42":{"body":0,"breadcrumbs":7,"title":3},"43":{"body":94,"breadcrumbs":6,"title":2},"44":{"body":14,"breadcrumbs":7,"title":3},"45":{"body":118,"breadcrumbs":6,"title":2},"46":{"body":94,"breadcrumbs":6,"title":2},"47":{"body":0,"breadcrumbs":6,"title":2},"48":{"body":57,"breadcrumbs":8,"title":4},"49":{"body":53,"breadcrumbs":8,"title":4},"5":{"body":13,"breadcrumbs":3,"title":2},"50":{"body":16,"breadcrumbs":6,"title":2},"51":{"body":54,"breadcrumbs":6,"title":2},"52":{"body":16,"breadcrumbs":17,"title":8},"53":{"body":9,"breadcrumbs":10,"title":1},"54":{"body":26,"breadcrumbs":11,"title":2},"55":{"body":16,"breadcrumbs":10,"title":1},"56":{"body":22,"breadcrumbs":11,"title":2},"57":{"body":0,"breadcrumbs":16,"title":9},"58":{"body":169,"breadcrumbs":8,"title":1},"59":{"body":105,"breadcrumbs":8,"title":1},"6":{"body":82,"breadcrumbs":3,"title":2},"60":{"body":235,"breadcrumbs":14,"title":7},"61":{"body":167,"breadcrumbs":14,"title":7},"62":{"body":305,"breadcrumbs":8,"title":2},"63":{"body":35,"breadcrumbs":6,"title":3},"64":{"body":235,"breadcrumbs":5,"title":2},"65":{"body":30,"breadcrumbs":5,"title":2},"66":{"body":11,"breadcrumbs":5,"title":2},"67":{"body":24,"breadcrumbs":4,"title":1},"68":{"body":31,"breadcrumbs":9,"title":5},"69":{"body":139,"breadcrumbs":6,"title":2},"7":{"body":217,"breadcrumbs":4,"title":3},"70":{"body":133,"breadcrumbs":6,"title":2},"71":{"body":0,"breadcrumbs":6,"title":2},"72":{"body":56,"breadcrumbs":7,"title":3},"73":{"body":267,"breadcrumbs":10,"title":6},"74":{"body":81,"breadcrumbs":9,"title":5},"75":{"body":55,"breadcrumbs":13,"title":5},"76":{"body":5,"breadcrumbs":12,"title":4},"77":{"body":14,"breadcrumbs":11,"title":3},"78":{"body":83,"breadcrumbs":11,"title":3},"79":{"body":132,"breadcrumbs":10,"title":2},"8":{"body":21,"breadcrumbs":3,"title":2},"80":{"body":15,"breadcrumbs":10,"title":2},"81":{"body":122,"breadcrumbs":10,"title":2},"82":{"body":104,"breadcrumbs":9,"title":1},"83":{"body":31,"breadcrumbs":12,"title":4},"84":{"body":103,"breadcrumbs":9,"title":1},"85":{"body":3,"breadcrumbs":10,"title":2},"86":{"body":63,"breadcrumbs":15,"title":7},"87":{"body":33,"breadcrumbs":12,"title":4},"88":{"body":26,"breadcrumbs":11,"title":3},"89":{"body":19,"breadcrumbs":13,"title":5},"9":{"body":16,"breadcrumbs":5,"title":3},"90":{"body":61,"breadcrumbs":10,"title":2},"91":{"body":6,"breadcrumbs":8,"title":2},"92":{"body":13,"breadcrumbs":8,"title":2},"93":{"body":35,"breadcrumbs":7,"title":1},"94":{"body":6,"breadcrumbs":7,"title":1},"95":{"body":0,"breadcrumbs":8,"title":2},"96":{"body":16,"breadcrumbs":7,"title":1},"97":{"body":8,"breadcrumbs":7,"title":1},"98":{"body":33,"breadcrumbs":8,"title":2},"99":{"body":36,"breadcrumbs":7,"title":1}},"docs":{"0":{"body":"Application Services is collection of Rust Components. The components are used to enable Firefox, and related applications to integrate with Firefox accounts, sync and enable experimentation. Each component is built using a core of shared code written in Rust, wrapped with native language bindings for different platforms.","breadcrumbs":"Application Services Rust Components » Application Services Rust Components","id":"0","title":"Application Services Rust Components"},"1":{"body":"To contact the Application Services team you can: Find us in the chat #rust-components:mozilla.org ( How to connect ) To report issues with sync on Firefox Desktop , file a bug in Bugzilla for Firefox :: Sync To report issues with our components, file an issue in the GitHub issue tracker The source code is available on GitHub .","breadcrumbs":"Application Services Rust Components » Contact us","id":"1","title":"Contact us"},"10":{"body":"Building for the first time is more complicated than a typical Rust project. To build for an end-to-end experience that enables you to test changes in client applications like Firefox for Android (Fenix) and Firefox iOS , there are a number of build systems required for all the dependencies. The initial setup is likely to take a number of hours to complete.","breadcrumbs":"Contributing » Building » First time builds","id":"10","title":"First time builds"},"100":{"body":"//FooBar.kt\nclass FooBar{...}\nfun fromJSONString()\npackage mozilla.appservices.places Found a bug? Edit this page on GitHub.","breadcrumbs":"Contributing » How to add a new component » Naming Conventions » Examples:","id":"100","title":"Examples:"},"101":{"body":"When we started building the components in this repo, exposing Rust code to Kotlin and Swift was a manual process and each component had its own hand-written FFI layer and foreign-language bindings. As we've gained more experience with building components in this way, we've started to automate bindings generation and capture best practices in a tool called UniFFI , which is the currently recommended approach when adding a new component from scratch . We expect that existing components will gradually be ported over to use UniFFI, and this document is a guide to doing that port.","breadcrumbs":"Contributing » How to add a new component » How to convert a Rust Component to Uniffi » Converting an existing Component to use UniFFI","id":"101","title":"Converting an existing Component to use UniFFI"},"102":{"body":"First, make sure you've perused the UniFFI guide to understand the overall architecture of a UniFFI component, and take a look at the guide to adding a new component to understand how such components fit in to this repo. The aim of porting will be to have a component that looks like it was added by the process described therein.","breadcrumbs":"Contributing » How to add a new component » How to convert a Rust Component to Uniffi » First, get familiar with UniFFI","id":"102","title":"First, get familiar with UniFFI"},"103":{"body":"Pre-UniFFI components typically consist of four main parts: A Rust crate implementing the core functionality of the component A separate Rust crate that exposes the core functionality over a C-style FFI. An Android package that imports the C-style FFI into idiomatic Kotlin. A Swift module that imports the C-style FFI into idiomatic Swift. The code for these parts will be laid out something like this: components// Cargo.toml src/ Rust code for the core functionality of the component goes here. ffi/ Cargo.toml src/ Rust code specifically for exposing the C-style FFI goes here. android/ build.gradle src/ main/ AndroidManifest.xml java/mozilla/appservices// LibFFI.kt (low-level bindings to the C-style FFI) Higher-level hand-written Kotlin that wraps the FFI. ios/ / RustAPI.h (low-level bindings to the C-style FFI) Higher-level hand-written Swift that wraps the FFI. The goal here is to replace much of the hand-written wrapper layers with autogenerated code: The ./ffi/ crate will disappear entirely, its work is automated by UniFFI If you still need some hand-written pub extern \"C\" functions, perhaps to implement features not currently supported by UniFFI, then they should move into lib.rs of the main component crate. The low-level LibFFI.kt file will disappear entirely, as will some of the code that converts it back into nice high-level Kotlin classes and interfaces. Some of the hand-written Kotlin code may remain, if it provides functionality that cannot be implemented in Rust. The low-level RustAPI.h file will disappear entirely, as will some of the code that converts it back into nice high-level Swift classes and interfaces. Some of the hand-written Swift code may remain, if it provides functionality that cannot be implemented in Rust. You'll aim to end up with a simplified file structure that looks like this: components// Cargo.toml uniffi.toml src/ .udl (abstract interface definition) Rust code here. android/ build.gradle src/ main/ AndroidManifest.xml java/mozilla/appservices// Optional hand-written Kotlin code here. ios/ / Optional hand-written Swift code here.","breadcrumbs":"Contributing » How to add a new component » How to convert a Rust Component to Uniffi » Next, get familiar with the target component","id":"103","title":"Next, get familiar with the target component"},"104":{"body":"Make sure you've got the uniffi-bindgen command available; cargo install uniffi_bindgen will ensure you have the latest version. Create ./src/.udl and try to describe the intended interface for the component using UniFFI's interface definition language . You'll probably need to reverse-engineer it a little bit from the existing hand-written Kotlin and/or Swift code. Don't spend too much time on trying to match every minute detail of the existing hand-written API. There are likely to be small differences between how UniFFI likes to do things and how the hand-written APIs were structured, and it's in everyone's best long-term interests to just push ahead and update consumers to accommodate any breaking API changes, rathern than e.g. trying to convince UniFFI to capitalize enum variant names in the same style that the hand-written code was using. To check whether the .udl file is syntactically valid, you can use uniffi-bindgen to generate the Rust FFI scaffolding like so: uniffi-bindgen scaffolding ./src/.udl If this succeeds, it will generate a file ./src/.uniffi.rs with a bunch of thorny auto-generated Rust code. If it fails, it will likely fail with an inscrutable error message. Unfortunately the error reporting in UniFFI is currently a known pain point, and it can take a bit of trial-and-error to identify what part of the file is causing the issue. Sorry :-( The aim at this point is to ensure that the intended interface of the component can be expressed in terms that UniFFI understands. Most cases should be supported, but you may find some aspect of the existing component that is hard to express in UniFFI, perhaps even uncovering new functionality that needs to be added to UniFFI itself! The .udl file is definitely a first draft at this point. It is normal and expected to need to iterate on this file as you port over the underlying Rust code.","breadcrumbs":"Contributing » How to add a new component » How to convert a Rust Component to Uniffi » Write a first draft of the .udl file for the component's interface","id":"104","title":"Write a first draft of the .udl file for the component's interface"},"105":{"body":"You will now restructure the existing Rust crate so that its public API surface and overall \"shape\" match what you defined in the .udl file. Start by deleting the ./ffi sub-crate, because you're going to use UniFFI to generate all of that code. You'll also need to remove it from the workspace in the top-level Cargo.toml file, as well as change the crates under /megazords to import the core Rust crate for the component rather than importing the FFI sub-crate. Add UniFFI to the crate's dependencies and configure its build.rs script to invoke the UniFFI scaffolding generator, as described in \"adding a new component\" . Now, edit ./lib.rs so that it matches the interface defined in the .udl file as closely as possible. If the .udl has an interface Example then lib.rs should contain a pub struct Example, if the .udl contains an enum ExampleItem then lib.rs should contain a pub enum ExampleItem, and so-on. The details of this step will depend heavily on the specific crate, but some tips include: You may find it useful to move all of the existing code into a sub-module named internal, and then make a brand new lib.rs that imports or re-defines just the pieces it needs in order to implement the interface from the .udl file. The fxa-client crate is an example of a case where this worked out well, though of course your mileage may vary. If the existing crate contains a file named like _msg_types.proto, then it was using Protocol Buffers to serialize data to pass over the FFI. The message types defined in the .proto file will need to be converted into dictionary or enum definitions in your .udl file. See the section below for more details. As noted above, don't be afraid to accept some API churn during the conversion process. We're willing to accept some breaking API changes as the cost of getting bindings generated for free, as long as the core functionality and mental model of the component remain intact. At this point, in theory the crate should be buildable with UniFFI, although it's likely to require some iteration to get it all working! Run cargo check to check for any compilation errors without having to do a full build.","breadcrumbs":"Contributing » How to add a new component » How to convert a Rust Component to Uniffi » Restructure the Rust code to introduce UniFFI","id":"105","title":"Restructure the Rust code to introduce UniFFI"},"106":{"body":"Passing rich structured data over the FFI is the most complex part of our hand-written bindings, and was previously done by serializing data via Protocol Buffers . This is something that UniFFI tries to make as simple as possible. Start by locating the _msg_types.proto file for the component. This file defines the structured messages that can be passed over the FFI, and you should see that they correspond to various types of structured data that the component wants to receive from, or return to, the foreign-language code. Find the places in your .udl interface that correspond to these message types and make sure that you've got a similarly-shaped dictionary or enum for each one. You should find that representing this structured data in UDL is simpler than protobuf in many cases - for example many of our .protobuf files need to use a separate ExampleStructs message in order to pass a list of ExampleStruct messages over the FFI, but in UniFFI this is represented directly as sequence. Find the places in the Rust code that are using these message types to return structured data. In simple cases, you may be able to directly replace uses of msg_types::ExampleStruct with the corresponding crate::ExampleStruct from your public API. For more complex cases, you may find it helpful to define an Into mapping between the UniFFI dictionary/enum in the crate's public interface, and a more complex struct designed for internal use. As noted above, don't be afraid to accept some API churn during this conversion process. Once you have replaced all uses of the msg_types structs in the Rust code: Delete ./src/_msg_types.proto. Delete ./src/mozilla.appservices..protobuf.rs, which is generated from the .proto file. Remote prost and prost-derive from the crate's dependencies. Delete the crate from the list in /tools/protobuf_files.toml. If you happen to find that you've deleted the last crate from the list in protobuf_files.toml, congratulations! You've successfully removed protocol buffers from this repo entirely, and should file a bug to track the complete removal of protobuf from our tooling and dependency chain.","breadcrumbs":"Contributing » How to add a new component » How to convert a Rust Component to Uniffi » Removing Protobuf Messages","id":"106","title":"Removing Protobuf Messages"},"107":{"body":"Write consumer-facing documentation on the public API in lib.rs using Rust's standard rustdoc conventions and tools. The fxa-client crate may serve as a good example. You can view the generated documentation by running: cargo doc --no-deps --open In future, we intend to automatically extract documentation from the Rust code and make it easily available to consumers of the generated bindings. (In fact there is some work-in-progress code in uniffi-rs#416 that can read docs from the Rust code and write them back into the .udl file, which you're welcome to try out if you're feeling adventurous. But it's just a very hacky prototype.)","breadcrumbs":"Contributing » How to add a new component » How to convert a Rust Component to Uniffi » Document the Public API in the Rust code","id":"107","title":"Document the Public API in the Rust code"},"108":{"body":"It's easiest to start by removing all of the hand-written Kotlin code under android/src/main/java and then restoring parts of it later if necessary. Leave the AndroidManifest.xml file and any tests in place. Delete the android/build.gradle file and then follow the instructions for adding Kotlin bindings for a new component to create a new build.gradle file and a corresponding uniffi.toml. This should be all that's required to set up UniFFI to build the Kotlin bindings. Try building the Android package to confirm: ./gradlew :assembleDebug The UniFFI-generated Kotlin code will be under ./android/build/generated/source/uniffi/ and may be useful for debugging. If there are existing Kotlin tests for the component, the next step is to get those passing: ./gradlew :test As noted above, it is normal and expected for the autogenerated bindings to be subtly different from the previous hand-written ones. For example, UniFFI insists on using SHOUTY_SNAKE_CASE variant names in Kotlin enums while the hand-written code may have used CamelCase. Some components also have small naming differences between the Rust code and the hand-written Kotlin bindings, which UniFFI will not allow. If the component had functionality in its Kotlin layer that was not part of the Rust API, then you'll need to add some hand-written Kotlin code under android/src/main/java to implement it. The fxa-client component may be a good example here: its Rust layer exposes a FirefoxAccount struct that the Kotlin code wraps into a PersistedFirefoxAccount class, adding the ability to set a persistence callback. Finally, you will need to try out the new bindings with a consuming app. For Kotlin code you should make a local build of android-components and Fenix , updating them to accomodate any changes in the component's public API.","breadcrumbs":"Contributing » How to add a new component » How to convert a Rust Component to Uniffi » Set up the Kotlin wrapper","id":"108","title":"Set up the Kotlin wrapper"},"109":{"body":"It's easiest to start by removing all of the hand-written Swift code under ./ios and then restoring parts of it later if necessary. Edit /megazords/ios-rust/MozillaTestServices.h to remove any references to RustAPI.h, replacing them with the UniFFI-generated header file name FFI.h. Open /megazords/ios-rust/MozillaTestServices.xcodeproj in Xcode and follow the instructions for adding Swift bindings for a new component to configure Xcode to build your UniFFI-generated bindings. While you are in the Xcode Project Navigator, you should also delete any references to RustAPI.h or to the old hand-written Swift wrappers. (They should be highlighted in red in the Project Navigator, because the files will be missing from disk after you deleted them above). This should be all that's required to set up UniFFI to build the Swift bindings. Try building the project in Xcode to confirm. The UniFFI-generated Swift code will be under ios/Generated and may be useful for debugging. If there are existing Swift tests for the component, the next step is to get those passing: ./automation/run_ios_tests.sh (or run them from the Xcode GUI) As noted above, it is normal and expected for the autogenerated bindings to be subtly different from the previous hand-written ones. Many existing components have small naming differences between the Rust code and the hand-written Swift bindings, which UniFFI will not allow. If the component had functionality in its Swift layer that was not part of the Rust API, then you'll need to add some hand-written Swift code under ./ios/ to implement it. The fxa-client component may be a good example here: its Rust layer exposes a FirefoxAccount struct that the Swift code wraps into a PersistedFirefoxAccount class, adding the ability to set a persistence callback. You will need to add any such file to the \"Compile Sources\" list in Xcode, in the same way that you added the .udl file. Finally, you will need to try out the new bindings with a consuming app. For Swift code you should make a local build of Firefox iOS, you can do that by following the steps in this document Found a bug? Edit this page on GitHub.","breadcrumbs":"Contributing » How to add a new component » How to convert a Rust Component to Uniffi » Set up the Swift wrapper","id":"109","title":"Set up the Swift wrapper"},"11":{"body":"Complete this section before moving to the android/iOS build instructions. Make sure you cloned the repository: $ git clone https://github.com/mozilla/application-services # (or use the ssh link) $ cd application-services $ git submodule update --init --recursive Install Rust: install via rustup Install your system dependencies: Linux Install the system dependencies required for building NSS Install gyp: apt install gyp (required for NSS) Install ninja-build: apt install ninja-build Install python3 (at least 3.6): apt install python3 Install zlib: apt install zlib1g-dev Install perl (needed to build openssl): apt install perl Install patch (to build the libs): apt install patch Install the system dependencies required for SQLcipher Install tcl: apt install tclsh (required for SQLcipher) MacOS Install Xcode: check the ci config for the correct version. Install Xcode tools: xcode-select --install Install homebrew via its installation instructions (it's what we use for ci). Install the system dependencies required for building NSS: Install ninja and python: brew install ninja python Make sure which python3 maps to the freshly installed homebrew python. If it isn't, add the following to your bash/zsh profile and source the profile before continuing: alias python3=$(brew --prefix)/bin/python3 Ensure python maps to the same Python version. You may have to create a symlink: PYPATH=$(which python3); ln -s $PYPATH `dirname $PYPATH`/python Install gyp: wget https://bootstrap.pypa.io/ez_setup.py -O - | python3 -\ngit clone https://chromium.googlesource.com/external/gyp.git ~/tools/gyp\ncd ~/tools/gyp\npython3 setup.py install Add ~/tools/gyp to your path: export PATH=\"~/tools/gyp:$PATH\" If you have additional questions, consult this guide . Make sure your homebrew python's bin folder is on your path by updating your bash/zsh profile with the following: export PATH=\"$PATH:$(brew --prefix)/opt/python@3.9/Frameworks/Python.framework/Versions/3.9/bin\" Windows Install windows build tools Why Windows Subsystem for Linux (WSL) ? It's currently tricky to get some of these builds working on Windows, primarily due to our use of SQLcipher. By using WSL it is possible to get builds working, but still have them published to your \"native\" local maven cache so it's available for use by a \"native\" Android Studio. Install WSL (recommended over native tooling) Install unzip: sudo apt install unzip Install python3: sudo apt install python3 Note: must be python 3.6 or later Install system build tools: sudo apt install build-essential Install zlib: sudo apt-get install zlib1g-dev Install tcl: sudo apt install tcl-dev Check dependencies and environment variables by running: ./libs/verify-desktop-environment.sh Note that this script might instruct you to set some environment variables, set those by adding them to your .zshrc or .bashrc so they are set by default on your terminal. If it does so instruct you, you must run the command again after setting them so the libraries are built. Run cargo test: cargo test Once you have successfully run ./libs/verify-desktop-environment.sh and cargo test you can move to the Building for Fenix and Building for iOS sections below to setup your local environment for testing with our client applications.","breadcrumbs":"Contributing » Building » Building the Rust Components","id":"11","title":"Building the Rust Components"},"110":{"body":"","breadcrumbs":"Contributing » How to add a new component » How to use Rust Components in Android » Rust + Android FAQs","id":"110","title":"Rust + Android FAQs"},"111":{"body":"Use UniFFI , which can produce Kotlin bindings for your Rust code from an interface definition file. If UniFFI doesn't currently meet your needs, please open an issue to discuss how the tool can be improved. As a last resort, you can make hand-written bindings from Rust to Kotlin, essentially manually performing the steps that UniFFI tries to automate for you: flatten your Rust API into a bunch of pub extern \"C\" functions, then use JNA to call them from Kotlin. The details of how to do that are well beyond the scope of this document.","breadcrumbs":"Contributing » How to add a new component » How to use Rust Components in Android » How do I expose Rust code to Kotlin?","id":"111","title":"How do I expose Rust code to Kotlin?"},"112":{"body":"Published packages should be named org.mozilla.appservices.$NAME where $NAME is the name of your component, such as logins. The Java namespace in which your package defines its classes etc should be mozilla.appservices.$NAME.*.","breadcrumbs":"Contributing » How to add a new component » How to use Rust Components in Android » How should I name the package?","id":"112","title":"How should I name the package?"},"113":{"body":"Add it to .buildconfig-android.yml in the root of this repository. This will cause it to be automatically included as part of our release publishing pipeline.","breadcrumbs":"Contributing » How to add a new component » How to use Rust Components in Android » How do I publish the resulting package?","id":"113","title":"How do I publish the resulting package?"},"114":{"body":"Assuming that you're building the Rust code as part of the application-services build and release process, your pub extern \"C\" API should always be available from a file named libmegazord.so.","breadcrumbs":"Contributing » How to add a new component » How to use Rust Components in Android » How do I know what library name to load to access the compiled rust code?","id":"114","title":"How do I know what library name to load to access the compiled rust code?"},"115":{"body":"There are a number of them. The issue boils down to the fact that you need to be completely certain that a JVM is associated with a given thread in order to call java code on it. The difficulty is that the JVM can GC its threads and will not let rust know about it. JNA can work around this for us to some extent, at the cost of some complexity. The approach it takes is essentially to spawn a thread for each callback invocation. If you are certain you’re going to do a lot of callbacks and they all originate on the same thread, you can have them all run on a single thread by using the CallbackThreadInitializer . With the help of JNA's workarounds, calling back from Rust into Kotlin isn’t too bad so long as you ensure that Kotlin cannot GC the callback while rust code holds onto it (perhaps by stashing it in a global variable), and so long as you can either accept the overhead of extra threads being instantiated on each call or are willing to manage the threads explicitly. Note that the situation would be somewhat better if we used JNI directly (and not JNA), but this would cause us to need to generate different Rust FFI code for Android than for iOS. Ultimately, in any case where there is an alternative to using a callback, you should probably pursue that alternative. For example if you're using callbacks to implement async I/O, it's likely better to move to doing a blocking call, and have the calling code dispatch it on a background thread. It’s very easy to run such things on a background thread in Kotlin, is in line with the Android documentation on JNI usage, and in our experience is vastly simpler and less painful than using callbacks. (Of course, not every case is solvable like this).","breadcrumbs":"Contributing » How to add a new component » How to use Rust Components in Android » What challenges exist when calling back into Kotlin from Rust?","id":"115","title":"What challenges exist when calling back into Kotlin from Rust?"},"116":{"body":"We get a couple things from using JNA that we wouldn't with JNI. We are able to use the same Rust FFI code on all platforms. If we used JNI we'd need to generate an Android-specific Rust FFI crate that used the JNI APIs, and a separate Rust FFI crate for exposing to Swift. JNA provides a mapping of threads to callbacks for us, making callbacks over the FFI possible. That said, in practice this is still error prone, and easy to misuse/cause memory safety bugs, but it's required for cases like logging, among others, and so it is a nontrivial piece of complexity we'd have to reimplement. However, it comes with the following downsides: JNA has bugs. In particular, its not safe to use bools with them, it thinks they are 32 bits, when on most platforms (every platform Rust supports) they are 8 bits. They've been unwilling to fix the issue due to it breaking backwards compatibility (which is... somewhat fair, there is a lot of C89 code out there that uses bool as a typedef for a 32-bit int). JNA makes it really easy to do the wrong thing and have it work but corrupt memory. Several of the caveats around this are documented in the ffi_support docs , but a major one is when to use Pointer vs String (getting this wrong will often work, but may corrupt memory). We aim to avoid triggering these bugs by auto-generating the JNA bindings rather than writing them by hand.","breadcrumbs":"Contributing » How to add a new component » How to use Rust Components in Android » Why are we using JNA rather than JNI, and what tradeoffs does that involve?","id":"116","title":"Why are we using JNA rather than JNI, and what tradeoffs does that involve?"},"117":{"body":"Uncomment the packagingOptions { doNotStrip \"**/*.so\" } line from the build.gradle file of the component you want to debug. In the rust code, either: Cause something to crash where you want the breakpoint. Note: Panics don't work here, unfortunately. (I have not found a convenient way to set a breakpoint to rust code, so unsafe { std::ptr::write_volatile(0 as *const _, 1u8) } usually is what I do). If you manage to get an LLDB prompt, you can set a breakpoint using breakpoint set --name foo, or breakpoint set --file foo.rs --line 123. I don't know how to bring up this prompt reliably, so I often do step 1 to get it to appear, delete the crashing code, and then set the breakpoint using the CLI. This is admittedly suboptimal. Click the Debug button in Android Studio, to display the \"Select Deployment Target\" window. Make sure the debugger selection is set to \"Both\". This tends to unset itself, so make sure. Click \"Run\", and debug away. Found a bug? Edit this page on GitHub.","breadcrumbs":"Contributing » How to add a new component » How to use Rust Components in Android » How do I debug Rust code with the step-debugger in Android Studio","id":"117","title":"How do I debug Rust code with the step-debugger in Android Studio"},"118":{"body":"Application-services components are consumed by multiple consumers including Firefox Android, Firefox iOS, Focus Android, and Focus iOS. To minimize the disruption to those projects when making breaking API changes, we follow a simple rule: Have approved PRs ready to land that fix the breakage in the other repos before merging the PR into application-services . This means writing code for the firefox-android and firefox-ios repositories that resolves any breaking changes, creating a PR in those repositories, and waiting for it to be approved. You can test this code locally using the autopublish flow ( Android , iOS ) and use the branch build system to run CI tests.","breadcrumbs":"Contributing » Breaking API changes » Breaking changes in application-services code","id":"118","title":"Breaking changes in application-services code"},"119":{"body":"Do not merge any PRs until all are approved. Once they are all approved then: Merge the application-services PR into main Manually trigger a new nightly build using the taskcluster hook: https://firefox-ci-tc.services.mozilla.com/hooks/project-releng/cron-task-mozilla-application-services%2Fnightly Once the nightly task completes, trigger a new rust-components-swift build using the github action: https://github.com/mozilla/rust-components-swift/actions/workflows/update-as-nightly.yml Update the firefox-android and firefox-ios PRs to use the newly built nightly: example of firefox-android changes example of firefox-ios changes Ideally, get the PRs merged before the firefox-android/firefox-ios nightly bump the next day. If you don't get these merged, then the nightly bump PR will fail. Add a link to your PR in the nightly bump PR so the mobile teams know how to fix this. Found a bug? Edit this page on GitHub.","breadcrumbs":"Contributing » Breaking API changes » Merging","id":"119","title":"Merging"},"12":{"body":"The following instructions assume that you are building application-services for Fenix, and want to take advantage of the Fenix Auto-publication workflow for android-components and application-services . Install Android SDK, JAVA, NDK and set required env vars Clone the firefox-android repository ( not inside the Application Service repository). Install Java 17 for your system Set JAVA_HOME to point to the JDK 17 installation directory. Download and install Android Studio . Set ANDROID_SDK_ROOT and ANDROID_HOME to the Android Studio sdk location and add it to your rc file (either .zshrc or .bashrc depending on the shell you use for your terminal). Configure the required versions of NDK Configure menu > System Settings > Android SDK > SDK Tools > NDK > Show Package Details > NDK (Side by side) 21.4.7075529 (required by Fenix; note: a specific NDK version isn't configured, this maps to default NDK version for the AGP version ) 25.2.9519653 (required by Application Services, as configured ) If you are on Windows using WSL - drop to the section below, Windows setup for Android (WSL) before proceeding. Check dependencies, environment variables Run ./libs/verify-android-environment.sh Follow instructions and rerun until it is successful.","breadcrumbs":"Contributing » Building » Building for Fenix","id":"12","title":"Building for Fenix"},"120":{"body":"Some of these components are used in mozilla-central . This document describes how to update existing components or add new components. The general process for vendoring rust code into mozilla-central has its own documentation - please make sure you read that before continuing.","breadcrumbs":"Contributing » How to vendor application-services into mozilla-central » Vendoring Application Services into mozilla-central","id":"120","title":"Vendoring Application Services into mozilla-central"},"121":{"body":"We want to keep our versions in moz-central relatively up-to-date, but it takes some manual effort to do. The main possibility of breakage is from a dependency mismatch, so our current vendoring policy is: Whenever a 3rd-party dependency is added or updated, the dev who made the change is responsible for vendoring. At the start of the release cycle the triage owner is response for vendoring.","breadcrumbs":"Contributing » How to vendor application-services into mozilla-central » When to vendor","id":"121","title":"When to vendor"},"122":{"body":"To update components which are already in mozilla-central, follow these steps: Ensure your mozilla-central build environment is setup correctly to make \"non-artifact\" builds - check you can get a full working build before starting this process. Run ./tools/update-moz-central-vendoring.py [path-to-moz-central] from the application-services root directory. If this generates errors regarding duplicate crates, you will enter a world of pain, and probably need to ask for advice from the application-services team, and/or the #build channel on matrix . Run ./mach cargo vet to check if there any any new dependencies that need to be vetted. If there are ask for advice from the application-services team. Build and test your tree. Ideally make a try run. Put your patch up to phabricator, requesting review from, at least, someone on the application-services team and one of the \"build peers\" - asking on #build on matrix for a suitable reviewer might help. Alternatively, try and find the bug which made the most recent update and ask the same reviewer in that patch. Profit!","breadcrumbs":"Contributing » How to vendor application-services into mozilla-central » Updating existing components.","id":"122","title":"Updating existing components."},"123":{"body":"Follow the Uniffi documentation on mozilla-central to understand where you'll need to add your crate path and UDL. In general: The consuming component will specify the dependency as a nominal \"version 0.1\" The top-level Cargo.toml will override that dependency with a specific git revision. For example, consider the webext-storage crate: The consuming crate specifies version 0.1 The top-level Cargo.toml specifies the exact revision. Adding a new component implies there will be related mozilla-central changes which leverage it. The best practice here is to land both the vendoring of the new component and the related mozilla-central changes in the same bug, but in different phabricator patches. As noted above, the best-practice is that all application-services components are on the same revision, so adding a new component implies you will generally also be updating all the existing components. For an example of a recently added component, the tabs was recently added to mozilla-central with uniffi and shows a general process to follow.","breadcrumbs":"Contributing » How to vendor application-services into mozilla-central » Adding a new component","id":"123","title":"Adding a new component"},"124":{"body":"Sometimes you will need to make changes in application-services and in mozilla-central simultaneously - for example, you may need to add new features or capabilities to a component, and matching changes in mozilla-central to use that new feature. In that scenario, you don't want to check your changes in and re-vendor as you iterate - it would be far better to use a local checkout of application-services with uncommitted changes with your mozilla-central tree which also has uncommited changes. To do this, you can edit the top-level Cargo.toml to specify a path. Note however that in this scenario, you need to specify the path to the individual component rather than to the top-level of the repo. For example, you might end up with something like: # application-services overrides to make updating them all simpler.\ninterrupt-support = { path = \"../application-services/components/support/interrupt\" }\nsql-support = { path = \"../application-services/components/support/sql\" }\nsync15-traits = { path = \"../application-services/components/support/sync15-traits\" }\nviaduct = { path = \"../application-services/components/viaduct\" }\nwebext-storage = { path = \"../application-services/components/webext-storage\" } Note that when you first do this, it will still be necessary to run ./mach vendor rust and to re-build. After you make a change to the local repository, you do not need to run ./mach vendor rust, but you do still obviously need to rebuild. Once you are happy with all the changes, you would: Open a PR up in application-services and land your changes there. Follow the process above to re-vendor your new changes, and in that same bug (although not necessarily the same phabricator patch), include the other mozilla-central changes which rely on the new version. Found a bug? Edit this page on GitHub.","breadcrumbs":"Contributing » How to vendor application-services into mozilla-central » Vendoring an unreleased version for testing purposes","id":"124","title":"Vendoring an unreleased version for testing purposes"},"125":{"body":"When writing code in application-services, code implemented in Rust, Kotlin, Java, or Swift might have to write debug logs. To do so, one should generally log using the normal logging facilities for the language. Where the logs go depends on the application which is embedding the components.","breadcrumbs":"Contributing » Logging » Application Services Logging","id":"125","title":"Application Services Logging"},"126":{"body":"On android, logs currently go to logcat. (This may change in the future.) Android Studio can be used to view the logcat logs; connect the device over USB and view the Logcat tab at the bottom of Android Studio. Check to make sure you have the right device selected at the top left of the Logcat pane, and the correct process to the right of that. One trick to avoid having to select the correct process (as there are main and content processes) is to choose \"No Filters\" from the menu on the top right of the Logcat pane. Then, use the search box to search for the log messages you are trying to find. There are also many other utilities, command line and graphical, that can be used to view logcat logs from a connected android device in a more flexible manner. Changing the loglevel in Fenix If you need more verbose logging, after the call to RustLog.enable() in FenixApplication, you may call RustLog.setMaxLevel(Log.Priority.DEBUG, true).","breadcrumbs":"Contributing » Logging » Accessing logs when running Fenix","id":"126","title":"Accessing logs when running Fenix"},"127":{"body":"[TODO] Found a bug? Edit this page on GitHub.","breadcrumbs":"Contributing » Logging » Accessing logs when running iOS","id":"127","title":"Accessing logs when running iOS"},"128":{"body":"UniFFI supports interface objects , which are implemented by Boxing a Rust object and sending the raw pointer to the foreign code. Once the objects are no longer in use, the foreign code needs to destroy the object and free the underlying resources. This is slightly tricky on Kotlin. The prevailing Java wisdom is to use explicit destructors and avoid using finalizers for destruction , which means we can't simply rely on the garbage collector to free the pointer. The wisdom seems simple to follow, but in practice it can be difficult to know how to apply it to specific situations. This document examines provides guidelines for handling UniFFI objects.","breadcrumbs":"Contributing » UniFFI Object Destruction on Kotlin » UniFFI object destruction on Kotlin","id":"128","title":"UniFFI object destruction on Kotlin"},"129":{"body":"The simplest way to get destruction right is to create an object and destroy it in the same function. The use function makes this really easy: SomeUniFFIObject() .use { obj -> obj.doSomething() obj.doSomethingElse() }","breadcrumbs":"Contributing » UniFFI Object Destruction on Kotlin » You can create objects in a function if you also destroy them there","id":"129","title":"You can create objects in a function if you also destroy them there"},"13":{"body":"Note: For non-Ubuntu linux versions, it may be necessary to execute $ANDROID_HOME/tools/bin/sdkmanager \"build-tools;26.0.2\" \"platform-tools\" \"platforms;android-26\" \"tools\". See also this gist for additional information. Configure Maven Configure maven to use the native windows maven repository - then, when doing ./gradlew install from WSL, it ends up in the Windows maven repo. This means we can do a number of things with Android Studio in \"native\" windows and have then work correctly with stuff we built in WSL. Install maven: sudo apt install maven Confirm existence of (or create) a ~/.m2 folder In the ~/.m2 create a file called settings.xml Add the content below replacing {username} with your username: /mnt/c/Users/{username}/.m2/repository ","breadcrumbs":"Contributing » Building » Windows setup for Android (via WSL)","id":"13","title":"Windows setup for Android (via WSL)"},"130":{"body":"If we are okay with UniFFI objects living for the entire application lifetime, then they can be stored in singletons. This is how we handle our database connections, for example SyncableLoginsStorage and PlacesReaderConnection .","breadcrumbs":"Contributing » UniFFI Object Destruction on Kotlin » You can create and store objects in singletons","id":"130","title":"You can create and store objects in singletons"},"131":{"body":"UniFFI objects can stored in classes like the Android Fragment class that have a defined lifecycle, with methods called at different stages. Classes can construct UniFFI objects in one of the lifecycle methods, then destroy it in the corresponding one. For example, creating an object in Fragment.onCreate and destroying it in Fragment.onDestroy().","breadcrumbs":"Contributing » UniFFI Object Destruction on Kotlin » You can create and store objects in an class, then destroy them in a corresponding lifecycle method","id":"131","title":"You can create and store objects in an class, then destroy them in a corresponding lifecycle method"},"132":{"body":"Several classes can hold references to an object, as long as (exactly) one class is responsible for managing it and destroying it when it's not used. A good example is the GeckoLoginStorageDelegate . The LoginStorage is initialized and managed by another object, and GeckoLoginStorageDelegate is passed a (lazy) reference to it. Care should be taken to ensure that once the managing class destroys the object, no other class attempts to use it. If they do, then the generate code will raise an IllegalStateException. This clearly should be avoided, although it won't result in memory corruption.","breadcrumbs":"Contributing » UniFFI Object Destruction on Kotlin » You can share objects","id":"132","title":"You can share objects"},"133":{"body":"Destructors may not run when a process is killed, which can easily happen on Android. This is especially true of lifecycle methods. This is normally fine, since the OS will close resources like file handles and network connections on its own. However, be aware that custom code in the destructor may not run. Found a bug? Edit this page on GitHub.","breadcrumbs":"Contributing » UniFFI Object Destruction on Kotlin » Destruction may not always happen","id":"133","title":"Destruction may not always happen"},"134":{"body":"This log lists the architectural decisions for MADR. ADR-0000 - Use Markdown Architectural Decision Records ADR-0001 - Update Logins API ADR-0002 - Handling Database Corruption ADR-0003 - Distributing Swift Packages ADR-0004 - Running experiments on first run early startup ADR-0005 - A remote-settings client for our mobile browsers. ADR-0007 - Limit Visits Migrated to Places History in Firefox iOS For new ADRs, please use template.md as basis. More information on MADR is available at https://adr.github.io/madr/ . General information about architectural decision records is available at https://adr.github.io/ .","breadcrumbs":"Architectural Decision Records » Architectural Decision Log","id":"134","title":"Architectural Decision Log"},"135":{"body":"","breadcrumbs":"Architectural Decision Records » ADR-0000 » Use Markdown Architectural Decision Records","id":"135","title":"Use Markdown Architectural Decision Records"},"136":{"body":"We want to record architectural decisions made in this project. Which format and structure should these records follow?","breadcrumbs":"Architectural Decision Records » ADR-0000 » Context and Problem Statement","id":"136","title":"Context and Problem Statement"},"137":{"body":"MADR 2.1.2 – The Markdown Architectural Decision Records Michael Nygard's template – The first incarnation of the term \"ADR\" Sustainable Architectural Decisions – The Y-Statements Other templates listed at https://github.com/joelparkerhenderson/architecture_decision_record Formless – No conventions for file format and structure","breadcrumbs":"Architectural Decision Records » ADR-0000 » Considered Options","id":"137","title":"Considered Options"},"138":{"body":"Chosen option: \"MADR 2.1.2\", because Implicit assumptions should be made explicit. Design documentation is important to enable people understanding the decisions later on. See also A rational design process: How and why to fake it . The MADR format is lean and fits our development style. The MADR structure is comprehensible and facilitates usage & maintenance. The MADR project is vivid. Version 2.1.2 is the latest one available when starting to document ADRs. Found a bug? Edit this page on GitHub.","breadcrumbs":"Architectural Decision Records » ADR-0000 » Decision Outcome","id":"138","title":"Decision Outcome"},"139":{"body":"Status: accepted Date: 2021-06-17 Technical Story: #4101","breadcrumbs":"Architectural Decision Records » ADR-0001 » Update Logins API","id":"139","title":"Update Logins API"},"14":{"body":"Install xcpretty : gem install xcpretty Run ./libs/verify-ios-environment.sh to check your setup and environment variables. Make any corrections recommended by the script and re-run. Next, run ./megazords/ios-rust/build-xcframework.sh to build all the binaries needed to consume a-s in iOS Once the script passes, you should be able to run the Xcode project. Note: The built Xcode project is located at megazords/ios-rust/MozillaTestServices.xcodeproj. Note: This is mainly for testing the rust components, the artifact generated in the above steps should be all you need for building application with application-services","breadcrumbs":"Contributing » Building » Building for Firefox iOS","id":"14","title":"Building for Firefox iOS"},"140":{"body":"We no longer want to depend on SQLCipher and want to use SQLite directly for build complexity and concerns over the long term future of the rust bindings. The encryption approach taken by SQLCipher means that in practice, the entire database is decrypted at startup, even if the logins functionality is not interacted with, defeating some of the benefits of using an encrypted database. The per-field encryption in autofill, which we are planning to replicate in logins, separates the storage and encryption logic by limiting the storage layer to the management of encrypted data. Applying this approach in logins will break the existing validation and deduping code so we need a way to implement per-field encryption while supporting the validation and de-duping behavior.","breadcrumbs":"Architectural Decision Records » ADR-0001 » Context and Problem Statement","id":"140","title":"Context and Problem Statement"},"141":{"body":"Addressing previously identified deficiencies in the logins API while we are breaking the API for the encryption work Continuing to support the existing logins validation and deduping logic Avoiding the implementation of new security approaches that may require additional time and security resources Establishing a standard encyrption approach across components","breadcrumbs":"Architectural Decision Records » ADR-0001 » Decision Drivers","id":"141","title":"Decision Drivers"},"142":{"body":"Option 1 - Reduce the API functions that require the encryption key and pass the key to the remaining functions Option 2 - Keep the general shape of the API that is in place now - the app can pass the encryption key at any time to \"unlock\" the API, and re-lock it at any time, but the API in its entirety is only available when unlocked","breadcrumbs":"Architectural Decision Records » ADR-0001 » Considered Options","id":"142","title":"Considered Options"},"143":{"body":"Chosen Option: \"Reduce the API functions that require the encryption key and pass the key to the remaining functions\" because it will not require a security review as similar to the approach we have established in the codebase.","breadcrumbs":"Architectural Decision Records » ADR-0001 » Decision Outcome","id":"143","title":"Decision Outcome"},"144":{"body":"","breadcrumbs":"Architectural Decision Records » ADR-0001 » Pros and Cons of the Options","id":"144","title":"Pros and Cons of the Options"},"145":{"body":"Description Currently the below logins API functions would require the per-field encryption key: add update get_all get_by_base_domain get_by_id check_valid_with_no_dupes potential_dupes_ignoring_username import_multiple Note: Functions related to sync have been omitted as it is assumed they will have access to decrypted data. The get_all, get_by_base_domain, and get_by_id functions will require the encryption key because they call the validate and fixup logic, not because we want to return logins with decrypted data. Propsed changes: Combine the add and update functions into a new add_or_update function This will allow the removal of consumer code that distinguishes when a login record should be created or updated Note: This function needs the encryption key for the fixup and deduping logic and for continued support of the accurate population of the time_password_changed field Pass the per-field encryption key to the import_multiple function This function will be removed once the Fennec to Fenix migration period ends Remove both the potential_dupes_ignoring_username and check_valid_with_no_dupes from the API Neither function is called in Firefox iOS Android Components uses both to provide validation and de-duping before logins are added or updated so we can eliminate the need to externalize these functions by replicating this logic in the new add_or_update function Create a decrypt_and_fixup_login function that both decrypts a login and performs the validate and fixup logic This will eliminate the need for the get_all, get_by_base_domain, and get_by_id API functions to perform the fixup logic Making the above changes will reduce the API functions requiring the encryption key to the following: add_or_update decrypt_and_fixup_login import_multiple Pros Improves the logins API for consumers by combining add/update functionality (see #3899 for details) Removes redundant validation and de-duping logic in consumer code Uses the same encryption model as autofill so there is consistency in our approaches Cons Requires consumer code to both encrypt login fields and pass the encryption key when calling either add_or_update and import_multiple","breadcrumbs":"Architectural Decision Records » ADR-0001 » Option 1 - Reduce the API functions that require the encryption key and pass the key to the remaining functions","id":"145","title":"Option 1 - Reduce the API functions that require the encryption key and pass the key to the remaining functions"},"146":{"body":"Description Unlike the first option, the publicly exposed login API would only handle decrypted login records and all encryption is internal (which works because we always have the key). Any attempt to use the API will fail as the login records are not encrypted or decrypted if the key is not available. Proposed changes: Combine the add and update functions into add_or_update Remove both the potential_dupes_ignoring_username and check_valid_with_no_dupes from the API Pros Prevents the consumer from having to encrypt or decrypt login records Maintains our current fixup and validation approach Improves the logins API for consumers by combining add/update functionality Removes redundant validation and de-duping logic in consumer code Cons Makes us responsible for securing the encryption key and will most likely require a security review","breadcrumbs":"Architectural Decision Records » ADR-0001 » Option 2 - Implement a different key management approach","id":"146","title":"Option 2 - Implement a different key management approach"},"147":{"body":"Logins Validate and Fixup Call Tree Found a bug? Edit this page on GitHub.","breadcrumbs":"Architectural Decision Records » ADR-0001 » Links","id":"147","title":"Links"},"148":{"body":"Status: accepted Date: 2021-06-08","breadcrumbs":"Architectural Decision Records » ADR-0002 » Handling Database Corruption","id":"148","title":"Handling Database Corruption"},"149":{"body":"Some of our users have corrupt SQLite databases and this makes the related component unusable. The best way to deal with corrupt databases is to simply delete the database and start fresh (#2628). However, we only want to do this for persistent errors, not transient errors like programming logic errors, disk full, etc. This ADR deals with 2 related questions: A) When and how do we identify corrupted databases? B) What do we do when we identify corrupted databases?","breadcrumbs":"Architectural Decision Records » ADR-0002 » Context and Problem Statement","id":"149","title":"Context and Problem Statement"},"15":{"body":"Detailed steps to build Firefox iOS against a local application services can be found this document Found a bug? Edit this page on GitHub.","breadcrumbs":"Contributing » Building » Locally building Firefox iOS against a local Application Services","id":"15","title":"Locally building Firefox iOS against a local Application Services"},"150":{"body":"Deleting valid user data should be avoided at almost any cost Keeping a corrupted database around is almost as bad. It currently prevents the component from working at all. We don't currently have a good way to distinguish between persistent and transient errors, but this can be improved by reviewing telemetry and sentry data.","breadcrumbs":"Architectural Decision Records » ADR-0002 » Decision Drivers","id":"150","title":"Decision Drivers"},"151":{"body":"A) When and how do we identify corrupted databases? 1: Assume all errors when opening a database are from corrupt databases 2: Check for errors when opening a database and compare against known corruption error types 3: Check for errors for all database operations and compare against known corruption error types B) What do we do when we identify corrupted databases? 1: Delete the database file and recreate the database 2: Move the database file and recreate the database 3: Have the component fail","breadcrumbs":"Architectural Decision Records » ADR-0002 » Considered Options","id":"151","title":"Considered Options"},"152":{"body":"A2: Check for errors when opening a database and compare against known corruption error types B1: Delete the database file and recreate the database Decision B follows from the choice of A. Since we're being conservative in identifying errors, we can delete the database file with relative confidence. \"Check for errors for all database operations and compare against known corruption error types\" also seems like a reasonable solution that we may pursue in the future, but we decided to wait for now. Checking for errors during opening time is the simpler solution to implement and should fix the issue in many cases. The plan is to implement that first, then monitor sentry/telemetry to decide what to do next.","breadcrumbs":"Architectural Decision Records » ADR-0002 » Decision Outcome","id":"152","title":"Decision Outcome"},"153":{"body":"","breadcrumbs":"Architectural Decision Records » ADR-0002 » Pros and Cons of the Options","id":"153","title":"Pros and Cons of the Options"},"154":{"body":"Good, because the sentry data indicates that many errors happen during opening time Good, because migrations are especially likely to trigger corruption errors Good, because it's a natural time to delete the database -- the consumer code hasn't run any queries yet and doesn't have any open connections. Bad, because it will delete valid user data in several situations that are relatively common: migration logic errors, OOM errors, Disk full.","breadcrumbs":"Architectural Decision Records » ADR-0002 » A1: Assume all errors when opening a database are from corrupt databases","id":"154","title":"A1: Assume all errors when opening a database are from corrupt databases"},"155":{"body":"Good, because should eliminate the possibility of deleting valid user data. Good, because the sentry data indicates that many errors happen during opening time Good, because it's a natural time to delete the database -- the consumer code hasn't run any queries yet and doesn't have any open connections. Bad, because we don't currently have a good list corruption errors","breadcrumbs":"Architectural Decision Records » ADR-0002 » A2: Check for errors when opening a database and compare against known corruption error types (Decided)","id":"155","title":"A2: Check for errors when opening a database and compare against known corruption error types (Decided)"},"156":{"body":"Good, because the sentry data indicates that many errors happen outside of opening time Good, because should eliminate the possibility of deleting valid user data. Bad, because the consumer code probably doesn't expect the database to be deleted and recreated in the middle of a query. However, this is just an extreme case of normal database behavior -- for example any given row can be deleted during a sync. Bad, because we don't currently have a good list corruption errors","breadcrumbs":"Architectural Decision Records » ADR-0002 » A3: Check for errors for all database operations and compare against known corruption error types","id":"156","title":"A3: Check for errors for all database operations and compare against known corruption error types"},"157":{"body":"Good, because it would allow users with corrupted databases to use the affected components again Bad, because any misidentification will lead to data loss.","breadcrumbs":"Architectural Decision Records » ADR-0002 » B1: Delete the database file and recreate the database (Decided)","id":"157","title":"B1: Delete the database file and recreate the database (Decided)"},"158":{"body":"This option would be similar to 1, but instead of deleting the file we would move it to a backup location. When we started up, we could look for backup files and try to import lost data. Good, because if we misidentify corrupt databases, then we have the possibility of recovering the data Good, because it allows a way for users to delete their data (in theory). If the consumer code executed a wipe() on the database, we could also delete any backup data. Bad, because it's very difficult to write a recovery function that merged deleted data with any new data. This function would be fairly hard to test and it would be easy to introduce a new logic error. Bad, because it adds significant complexity to the database opening code Bad, because the user experience would be strange. A user would open the app, discover that their data was gone, then sometime later discover that the data is back again.","breadcrumbs":"Architectural Decision Records » ADR-0002 » B2: Move the database file and recreate the database","id":"158","title":"B2: Move the database file and recreate the database"},"159":{"body":"Good, because this option leaves no chance of user data being deleted Good, because it's the simplest to implement Bad, because the component will not be usable if the database is corrupt Bad, because the user's data is potentially exposed in the corrupted database file and we don't provide any way for them to delete it. Found a bug? Edit this page on GitHub.","breadcrumbs":"Architectural Decision Records » ADR-0002 » B3: Return a failure code","id":"159","title":"B3: Return a failure code"},"16":{"body":"It's often important to test work-in-progress changes to Application Services components against a real-world consumer project. The most reliable method of performing such testing is to publish your components to a local Maven repository, and adjust the consuming project to install them from there. With support from the upstream project, it's possible to do this in a single step using our auto-publishing workflow.","breadcrumbs":"Contributing » Building » How to use the local development autopublish flow for Fenix » Using locally published components in Fenix","id":"16","title":"Using locally published components in Fenix"},"160":{"body":"Status: accepted Deciders: rfkelly Date: 2021-07-22","breadcrumbs":"Architectural Decision Records » ADR-0003 » Distributing Swift Packages","id":"160","title":"Distributing Swift Packages"},"161":{"body":"Our iOS consumers currently obtain application-services as a pre-compiled .framework bundle distributed via Carthage . The current setup is not compatible with building on new M1 Apple Silicon machines and has a number of other problems. As part of a broader effort to modernize the build process of iOS applications at Mozilla, we have been asked to re-evaluate how application-services components are dsitributed for iOS. See Problems with the current setup for more details.","breadcrumbs":"Architectural Decision Records » ADR-0003 » Context and Problem Statement","id":"161","title":"Context and Problem Statement"},"162":{"body":"Ease-of-use for iOS consumers. Compatibility with M1 Apple Silicon machines. Consistency with other iOS components being developed at Mozilla. Ability for the Nimbus Swift bindings to easily depend on Glean. Ease of maintainability for application-services developers.","breadcrumbs":"Architectural Decision Records » ADR-0003 » Decision Drivers","id":"162","title":"Decision Drivers"},"163":{"body":"(A) Do Nothing Keep our current build and distribution setup as-is. (B) Use Carthage to build XCFramework bundles Make a minimal change to our Carthage setup so that it builds the newer XCFramework format, which can support M1 Apple Silicon. (C) Distribute a single pre-compiled Swift Package Convert the all-in-one MozillaAppServices Carthage build to a similar all-in-one Swift Package, distributed as a binary artifact. (D) Distribute multiple source-based Swift Package targets, with pre-compiled Rust code Split the all-in-one MozillaAppServices Carthage build into a separate Swift Package target for each component, with a shared dependency on pre-compiled Rust code as a binary artiact.","breadcrumbs":"Architectural Decision Records » ADR-0003 » Considered Options","id":"163","title":"Considered Options"},"164":{"body":"Chosen option: (D) Distribute multiple source-based Swift Packages, with pre-compiled Rust code . This option will provide the best long-term consumer experience for iOS developers, and has the potential to simplify maintenance for application-services developers after an initial investment of effort.","breadcrumbs":"Architectural Decision Records » ADR-0003 » Decision Outcome","id":"164","title":"Decision Outcome"},"165":{"body":"Swift packages are very convenient to consume in newer versions of Xcode. Different iOS apps can choose to import a different subset of the available components, potentiallying helping keep application size down. Avoids issues with mis-matched Swift version between application-services build and consumers, since Swift files are distributed in source form. Encourages better conceptual separation between Swift code for different components; e.g. it will make it possible for two Swift components to define an item of the same name without conflicts. Reduces the need to use Xcode as part of application-services build process, in favour of command-line tools.","breadcrumbs":"Architectural Decision Records » ADR-0003 » Positive Consequences","id":"165","title":"Positive Consequences"},"166":{"body":"More up-front work to move to this new setup. We may be less likely to notice if our build setup breaks when used from within Xcode, because we're not exercising that code path ourselves. May be harder to concurrently publish a Carthage framework for current consumers who aren't able to move to Swift packages. There is likely to be some amount of API breakage for existing consumers, if only in having to replace a single import MozillaAppServices with independent imports of each component.","breadcrumbs":"Architectural Decision Records » ADR-0003 » Negative Consequences","id":"166","title":"Negative Consequences"},"167":{"body":"We will maintain the existing Carthage build infrastructure in the application-services repo and continue publishing a pre-built Carthage framework, to support firefox-ios until they migrate to Swift Packages. We will add an additional iOS build task in the application-services repo, that builds just the Rust code as a .xcframework bundle. An initial prototype shows that this can be achieved using a relatively straightforward shell script, rather than requiring a second Xcode project. It will be published as a .zip artifact on each release in the same way as the current Carthage framework. The Rust code will be built as a static library, so that the linking process of the consuming application can pull in just the subset of the Rust code that is needed for the components it consumes. We will initially include only Nimbus and its dependencies in the .xcframework bundle, but will eventually expand it to include all Rust components (including Glean, which will continue to be included in the application-services repo as a git submodule) We will create a new repository rust-components-swift to serve as the root of the new Swift Package distribution. It will import the application-services repository as a git submodule. This will let us iterate quickly on the Swift packaging setup without impacting existing consumers. We will initially include only Nimbus and its dependencies in this new repository, and the Nimbus swift code it will depend on Glean via the external glean-swift package. In the future we will publish all application-services components that have a Swift interface through this repository, as well as Glean and any future Rust components. (That's why the repository is being given a deliberately generic name). The rust-components-swift repo will contain a Package.swift file that defines: A single binary target that references the pre-built .xcframework bundle of Rust code. One Swift target for each component, that references the Swift code from the git submodule and depends on the pre-built Rust code. We will add automation to the rust-components-swift repo so that it automatically tracks releases made in the application-services repo and creates a corresponding git tag for the Swift package. At some future date when all consumers have migrated to using Swift packages, we will remove the Carthage build setup from the application-services repo. At some future date, we will consider whether to move the Package.swift definition in to the application-services repo, or whether it's better to keep it separate. (Attempting to move it into the application-services will involve non-trivial changes to the release process, because the checksum of the released .xcframework bundle needs to be included in the release taged version of the Package.swift file.)","breadcrumbs":"Architectural Decision Records » ADR-0003 » Implementation Sketch","id":"167","title":"Implementation Sketch"},"168":{"body":"","breadcrumbs":"Architectural Decision Records » ADR-0003 » Pros and Cons of the Options","id":"168","title":"Pros and Cons of the Options"},"169":{"body":"In this option, we would make no changes to our iOS build and publishing process. Good, because it's the least amount of work. Neutral, because it doesn't change the maintainability of the system for appservices developers. Neutral, because it doesn't change the amount of separation between Swift code for our various components. Neutral, because it doesn't address the Swift version incompatibility issues around binary artifacts. Bad, because it will frustrate consumers who want to develop on M1 Apple Silicon. Bad, because it may prevent consumers from migrating to a more modern build setup. Bad, because it would prevent consumers from consuming Glean as a Swift package; we would require them to use the Glean that is bundled in our build. This option isn't really tractable for us, but it's included for completeness.","breadcrumbs":"Architectural Decision Records » ADR-0003 » (A) Do Nothing","id":"169","title":"(A) Do Nothing"},"17":{"body":"Both the auto-publishing and manual workflows can be sped up significantly by using the rust.targets property which limits which architectures the Rust code gets build against. You can set this property by creating/editing the local.properties file in the repository root and adding a line like rust.targets=x86,linux-x86-64. The trick is knowing which targets to put in that comma separated list: Use x86 for running the app on most emulators (in rare cases, when you have a 64-bit emulator, you'll want x86_64) If you're running the android-components or fenix unit tests, then you'll need the architecture of your machine: OSX running Intel chips: darwin-x86-64 OSX running M1 chips: darwin-aarch64 Linux: linux-x86-64","breadcrumbs":"Contributing » Building » How to use the local development autopublish flow for Fenix » rust.targets","id":"17","title":"rust.targets"},"170":{"body":"In this option, we would try to change our iOS build and publising process as little as possible, but use Carthage's recent support for building platform-independent XCFrameworks in order to support consumers running on M1 Apple Silicon. Good, because the size of the change is small. Good, because we can support development on newer Apple machines. Neutral, because it doesn't change the maintainability of the system for appservices developers. Neutral, because it doesn't change the amount of separation between Swift code for our various components. Neutral, because it doesn't address the Swift version incompatibility issues around binary artifacts. Bad, because our iOS consumers have expressed a preference for moving away from Carthage. Bad, because other iOS projects at Mozilla are moving to Swift Packages, making us inconsistent with perceived best practice. Bad, because it would prevent consumers from consuming Glean as a Swift package; we would require them to use the Glean that is bundled in our build. Bad, because consumers don't get to choose which components they want to use (without us building a whole new \"megazord\" with just the components they want). Overall, current circumstances feel like a good opportunity to invest a little more time in order to set ourselves up for better long-term maintainability and happier consumers. The main benefit of this option (it's quicker!) is less attractive under those circumstances.","breadcrumbs":"Architectural Decision Records » ADR-0003 » (B) Use Carthage to build XCFramework bundles","id":"170","title":"(B) Use Carthage to build XCFramework bundles"},"171":{"body":"In this option, we would compile the Rust code and Swift code for all our components into a single .xcframework bundle, and then distribute that as a binary artifact via Swift Package. This is similar to the approach currently taken by Glean (ref Bug 1711447 ) except that they only have a single component. Good, because Swift Packages are the preferred distribution format for new iOS consumers. Good, because we can support development on newer Apple machines. Good, because it aligns with what other iOS component developers are doing at Mozilla. Neutral, because it doesn't change the maintainability of the system for appservices developers. (We'd need to keep the current Xcode project broadly intact). Neutral, because it doesn't change the amount of separation between Swift code for our various components. Neutral, because it doesn't address the Swift version incompatibility issues around binary artifacts. Neutral, because it would prevent consumers from consuming Glean as a separate Swift package; they'd have to get it as part of our all-in-one Swift package. Bad, because it's a larger change and we have to learn about a new package manager. Bad, because consumers don't get to choose which components they want to use (without building a whole new \"megazord\" with just the components they want). Overall, this option would be a marked improvement on the status quo, but leaves out some potential improvements. For not that much more work, we can make some of the \"Neutral\" and \"Bad\" points here into \"Good\" points.","breadcrumbs":"Architectural Decision Records » ADR-0003 » (C) Distribute a single pre-compiled Swift Package","id":"171","title":"(C) Distribute a single pre-compiled Swift Package"},"172":{"body":"In this option, we would compile just the Rust code for all our components into a single .xcframework bundle and distribute that as a binary artifact via Swift Package. We would then declare a separate Swift source target for the Swift wrapper of each component, each depending on the compiled Rust code but appearing as a separate item in the Swift package definition. Good, because Swift Packages are the preferred distribution format for new iOS consumers. Good, because we can support development on newer Apple machines. Good, because it aligns with what other iOS component developers are doing at Mozilla. Good, because it can potentially simplify the maintenance of the system for appservices developers, by removing Xcode in favour of some command-line scripts. Good, because it introduces strict separation between the Swift code for each component, instead of compiling them all together in a single shared namespace. Good, because the Nimbus Swift package could cleanly depend on the Glean Swift package. Good, because consumers can choose which components they want to include. Good, because it avoids issues with Swift version incompatibility in binary artifacts. Bad, because it's a larger change and we have to learn about a new package manager. The only downside to this option appears to be the amount of work involved, but an initial prototype has given us some confidence that the change is tractable and that it may lead to a system that is easier to maintain over time. It is thus our preferred option.","breadcrumbs":"Architectural Decision Records » ADR-0003 » (D) Distribute multiple source-based Swift Packages, with pre-compiled Rust code","id":"172","title":"(D) Distribute multiple source-based Swift Packages, with pre-compiled Rust code"},"173":{"body":"","breadcrumbs":"Architectural Decision Records » ADR-0003 » Appendix","id":"173","title":"Appendix"},"174":{"body":"Bug 1711447 has good historical context on the work to move Glean to using a Swift Package. Some material on swift packages: Managing dependencies using the Swift Package Manager was a useful overview. Understanding Swift Packages and Dependency Declarations gives a bit of a deeper dive into having multiple targets with different names in a single package. Outputs of initial prototype: A prototype of Option (C): Nimbus + Glean as a pre-built XCFramework Swift Package A prototype of Option (D): Rust code as XCFRamework plus a Multi-product Swift Package that depends on it. A video demo of the resulting consumer experience.","breadcrumbs":"Architectural Decision Records » ADR-0003 » Further Reading","id":"174","title":"Further Reading"},"175":{"body":"It doesn't build for M1 Apple Silicon machines, because it's not possible to support both arm64 device builds and arm64 simulator builds in a single binary .framework. Carthage is dispreferred by our current iOS consumers. We don't have much experience with the setup on the current Application Services team, and many of its details are under-documented. Changing the build setup requires Xcode and some baseline knowledge of how to use it. All components are built as a single Swift module, meaning they can see each other's internal symbols and may accidentally conflict when naming things. For example we can't currently have two components that define a structure of the same name. Consumers can only use the pre-built binary artifacts if they are using the same version of Xcode as was used during the application-services build. We are not able to use Swift's BUILD_LIBRARY_FOR_DISTRIBUTION flag to overcome this, because some of our dependencies do not support this flag (specifically, the Swift protobuf lib). Found a bug? Edit this page on GitHub.","breadcrumbs":"Architectural Decision Records » ADR-0003 » Problems with the current setup","id":"175","title":"Problems with the current setup"},"176":{"body":"Status: rejected Deciders: teshaq, travis, k88hudson, jhugman, jaredlockhart Date: 2021-08-16 Technical Story: https://mozilla-hub.atlassian.net/browse/SDK-323","breadcrumbs":"Architectural Decision Records » ADR-0004 » Running experiments on first run early startup","id":"176","title":"Running experiments on first run early startup"},"177":{"body":"As an experimenter, I would like to run experiments early on a user's first run of the application. However, the experiment data is only available on the second run. We would like to have that experiment data available before the user's first run. For more information: https://docs.google.com/document/d/1Qw36_7G6XyHvJZdM-Hxh4nqYZyCsYajG0L5mO33Yd5M/edit","breadcrumbs":"Architectural Decision Records » ADR-0004 » Context and Problem Statement","id":"177","title":"Context and Problem Statement"},"178":{"body":"Availability of experiments early on the first run No impact on experimentation data analysis Flexibility in creating experiments Ability to quickly disable experiments Simplicity of releases Mobile's expectations of Nimbus (The SDK should be idempotent)","breadcrumbs":"Architectural Decision Records » ADR-0004 » Decision Drivers","id":"178","title":"Decision Drivers"},"179":{"body":"(A) Do Nothing Keep everything the way it is, preventing us from experimenting on users early on their first run (B) Bundle Experiment data with app on release On release, have an initial_experiments.json that defines the experiments that will be applied early on the first run Later on the first run, the client would retrieve the actual experiment data from remote-settings and overwrite the bundled data (C) Retrieve Experiment data on first run, and deal with delay We can retrieve the experiment data on the first run, experiment data however will not be available until after a short delay (network I/O + some disk I/O)","breadcrumbs":"Architectural Decision Records » ADR-0004 » Considered Options","id":"179","title":"Considered Options"},"18":{"body":"Some consumers (notably Fenix ) have support for automatically publishing and including a local development version of application-services in their build. The workflow is: Check out the firefox-android mono-repo. Edit (or create) the file fenix/local.properties and tell it where to find your local checkout of application-services, by adding a line like: autoPublish.application-services.dir=relative/path/to/your/checkout/of/application-services Note that the path should be relative from local.properties. For example, if application-services and firefox-android are at the same level, the relative path would be ../../application-services Do the same for android-components/local.properties - so yes, your local checkout of firefox-android requires 2 copies of local.properties, both with identical values for autoPublish.application-services.dir (and probably identical in every other way too) Build the consuming project following its usual build procedure, e.g. via ./gradlew assembleDebug or ./gradlew test. If all goes well, this should automatically build your checkout of application-services, publish it to a local maven repository, and configure the consuming project to install it from there instead of from our published releases.","breadcrumbs":"Contributing » Building » How to use the local development autopublish flow for Fenix » Using the auto-publishing workflow","id":"18","title":"Using the auto-publishing workflow"},"180":{"body":"None of the options were feasible, so for now we are sticking with option (A) Do Nothing until there are experiments planned that are expected to run on early startup on the first run, then we will revaluate our options. The (B) Bundle Experiment data with app on release option was rejected mainly due to difficulty in disabling experiments and pausing enrollments. This can create a negative user experience as it prevents us from disabling any problematic experiments. Additionally, it ties experiment creation with application release cycles. The (C) Retrieve Experiment data on first run, and deal with delay option was rejected due to the fact it changes the Nimbus SDK will no longer be idempotent,and the possibility of introducing undesirable UI.","breadcrumbs":"Architectural Decision Records » ADR-0004 » Decision Outcome","id":"180","title":"Decision Outcome"},"181":{"body":"","breadcrumbs":"Architectural Decision Records » ADR-0004 » Pros and Cons of the Options","id":"181","title":"Pros and Cons of the Options"},"182":{"body":"Good, because it keeps the flexibility in experiment creation Good, because disabling experiments can still done remotely for all experiments Good, because it keeps the Nimbus SDK idempotent. Bad, because it doesn't address the main problem of exposing experiments to user on their first run","breadcrumbs":"Architectural Decision Records » ADR-0004 » Do nothing","id":"182","title":"Do nothing"},"183":{"body":"Good, because it allows us to run experiments early on a user's first run Good, because it prevents us from having to wait for experiments, especially if a user has a slow network connection Bad, because it ties experiment creation with release cycles Bad, because it prevents us from disabling problematic first-run experiments without a dot release Bad, because it prevents us from pausing enrollment on first-run experiments without a dot release Bad, because it requires investment from the console team, and can modify existing flows.","breadcrumbs":"Architectural Decision Records » ADR-0004 » Bundle Experiment data with app on release","id":"183","title":"Bundle Experiment data with app on release"},"184":{"body":"Good, because it enables us to retrieve experiments for users on their first run Good, because it keeps the flexibility in experiment creation Good, because disabling experiments can still done remotely for all experiments Bad, because experiments may not be ready early on the user's experience Bad, because it forces the customer application to deal with either the delay, or changing the configuration shortly after startup. e.g. a loading spinner or a pre-onboarding screen not under experimental control; delaying initialization of onboarding screens until after experiments have been loaded. Bad, because it changes the programming model from Nimbus being an idempotent configuration store to configuration changing non-deterministically. Bad, because the experimentation platform could force the app to add unchangeable user interface for the entire population. This itself may have an effect on key metrics.","breadcrumbs":"Architectural Decision Records » ADR-0004 » Retrieve Experiment data on first run, and deal with delay","id":"184","title":"Retrieve Experiment data on first run, and deal with delay"},"185":{"body":"RFC for bundling into iOS and Fenix Document presented to product managers about (C) Retrieve Experiment data on first run, and deal with delay : https://docs.google.com/document/d/1X1hC3t5zC7-Rp0OPIoiUr_ueLOAI0ez_jqslaNzOHjY/edit Demo presenting option (C) Retrieve Experiment data on first run, and deal with delay : https://drive.google.com/file/d/19HwnlwrabmSNsB7tjW2l4kZD3PWABi4u/view?usp=sharing Found a bug? Edit this page on GitHub.","breadcrumbs":"Architectural Decision Records » ADR-0004 » Links","id":"185","title":"Links"},"186":{"body":"Status: proposed Discussion: https://github.com/mozilla/application-services/pull/5302 Deciders: csadilek for the mobile teams ✔️ leplatrem for the remote-settings team ✔️ mhammond for the application-services team ✔️ Date: 2022-12-16","breadcrumbs":"Architectural Decision Records » ADR-0005 » A remote-settings client for our mobile browsers.","id":"186","title":"A remote-settings client for our mobile browsers."},"187":{"body":"Mozilla’s mobile browsers have a requirement to access the remote settings service, but currently lack any libraries or tools which are suitable without some work. A concrete use case is the management of search engine configurations, which are stored in Remote Settings for Firefox Desktop, but shipped as individual files on our mobile browsers, requiring application releases for all changes. A constraint on any proposed solutions is that this work will be performed by Mozilla's mobile team, who have limited experience with Rust, and that it is required to be completed in Q1 2023. This document identifies the requirements, then identifies tools which already exist and are close to being suitable, then identifies all available options we can take, and outlines our decision.","breadcrumbs":"Architectural Decision Records » ADR-0005 » Context and Problem Statement","id":"187","title":"Context and Problem Statement"},"188":{"body":"The requirements are for a library which is able to access Mozilla’s Remote Settings service and return the results to our mobile browsers. This list of requirements is not exhaustive, but instead focuses on the requirements which will drive our decision making process. As such, it identifies the non-requirements first.","breadcrumbs":"Architectural Decision Records » ADR-0005 » Requirements","id":"188","title":"Requirements"},"189":{"body":"The following items all may have some degree of desirability, but they are not hard requirements for the initial version While the https connection to the server must be validated, there is no requirement to verify the content received by the server - ie, there’s no requirement to check the signature of the body itself. There’s no requirement to validate the result of the server conforms to a pre-defined schema - we trust the server data. There’s no requirement to return strongly-typed data to the applications - returning a JSON string/object is suitable. There’s no requirement to cache server responses to the file-system - if the app requests content, it’s fine for the library to always hit the server. There’s no requirement for any kind of scheduling or awareness of network state - when we are requested for content, we do it immediately and return an appropriate error if it can not be fetched. There’s no requirement to support publishing records, requesting reviews or providing approvals via this new library. There’s no requirement that push be used to communicate changes to the application (eg, to enable rapid-enrolment type features) There’s no requirement to manage buckets, groups and collections via this new library.","breadcrumbs":"Architectural Decision Records » ADR-0005 » Non-requirements","id":"189","title":"Non-requirements"},"19":{"body":"If you are using Windows, there's a good chance you do most application-services work in WSL, but want to run Android Studio on native Windows. In that scenario you must: From the app-services root, in WSL, execute ./automation/publish_to_maven_local_if_modified.py In native Windows, just work as normal - that build process knows to not even attempt to execute the above command automatically.","breadcrumbs":"Contributing » Building » How to use the local development autopublish flow for Fenix » Using Windows/WSL","id":"19","title":"Using Windows/WSL"},"190":{"body":"The requirements we do have for the initial version are: The library should allow fetching records from Mozilla’s Remote Settings servers. This includes support for attachments, and fetching incremental changes . The library should not create threads or run any event loops - the mobile apps themselves are responsible for all threading requirements. While this might change in the future, considering this kind of change to our mobile applications is out of scope for this project. We must use Necko for all networking on Android, must enforce all connections are via valid https hosts (although some test-only exceptions might be helpful for QA, such as allowing localhost connections to be http) The library should be the only remote-settings library used in the browser. Specifically, this means that Nimbus must also be capable of using the library, and the work to move Nimbus to the library must be considered as part of the project.","breadcrumbs":"Architectural Decision Records » ADR-0005 » Initial Requirements","id":"190","title":"Initial Requirements"},"191":{"body":"We have identified the following libraries which may be suitable for this project.","breadcrumbs":"Architectural Decision Records » ADR-0005 » Existing Libraries","id":"191","title":"Existing Libraries"},"192":{"body":"There is a version of the remote settings client in desktop , written in Javascript. It has been used and been relatively stable since at least 2018, so can be considered very capable, but the roadblock to it being suitable for use by our mobile browsers is that it is written in Javascript, so while it might be possible to expose it to Android via geckoview, there’s no reasonable path to have it made available to iOS.","breadcrumbs":"Architectural Decision Records » ADR-0005 » Remote-settings on desktop","id":"192","title":"Remote-settings on desktop"},"193":{"body":"There is an existing remote settings client on github . This client is written in Rust and has evolved over a number of years. The most recent changes were made to support being used in Merino , which was re-written in Python, so there are no known consumers of this library left. The main attributes of this library relevant to this discussion are: It’s written in Rust, but has no FFI - ie, it’s currently only consumable by other Rust code. It has recently been updated to use async rust, so requires an internal event loop. It includes the capability to verify the signatures of the content.","breadcrumbs":"Architectural Decision Records » ADR-0005 » Rust Remote Settings Client","id":"193","title":"Rust Remote Settings Client"},"194":{"body":"The nimbus-sdk is a component in the application-services repository written in Rust. It has client code which talks to the remote-settings server and while this has only actually been used with the \"Nimbus\" collection there's no reason to believe it can't be used in the more general case. The main attributes of this library relevant to this discussion are: It’s consumed by a component which is already consumed by our mobile browsers via UniFFI. It does not verify the signatures of the content - while this could be done, there hasn’t been sufficient justification made for this (ie, there are no realistic threat models which would be solved by this capability.) The client itself does not persist a local cache of remote resources, but instead delegates this responsibility to the consuming application (in this case, nimbus itself, which does persist them via the rkv library ) It does not use async Rust, but instead everything is blocking and run on threads exclusively created by the app itself. It has good test support, which run against a docker image.","breadcrumbs":"Architectural Decision Records » ADR-0005 » The Nimbus-sdk Client","id":"194","title":"The Nimbus-sdk Client"},"195":{"body":"","breadcrumbs":"Architectural Decision Records » ADR-0005 » Considered Options","id":"195","title":"Considered Options"},"196":{"body":"The requirements of this client are such that writing new libraries in Kotlin and Swift is currently a realistic option. However, we are rejecting this option because we don’t want to duplicate the effort required to write and maintain two libraries - inevitably, the features and capabilities will diverge. Future requirements such as supporting content signature verification would lead to significant duplication. Writing a new library from scratch in Rust and exposing it via UniFFI so it can be used by both platforms is also a possibility. However, we are rejecting this option because existing Rust libraries already exist, so we would be better served by modifying or forking one of the existing libraries.","breadcrumbs":"Architectural Decision Records » ADR-0005 » Option 1: Writing a new library","id":"196","title":"Option 1: Writing a new library"},"197":{"body":"Modifying or forking the existing client is an attractive option. It would require a number of changes - the async capabilities would probably need to be removed (using a Rust event loop in our mobile browsers is something we are trying to avoid until we better understand the implications given these browsers already have an event loop and their own threading model). The persistence model used by this library is something that is not a requirement for the new library, which isn’t itself a problem, but it probably would preclude being able to use this library by Nimbus - so the end result is that we would effectively have two remote-settings clients written in Rust and used by our browsers. Some API changes would probably be required to make it suitable for use by UniFFI would also be necessary, but these would be tractable. We would need to update nimbus to use this client, which would almost certainly require moving this client into the application-services repository to avoid the following issues: Marrying the persistence model of this client with the existing rkv-based persistence used by nimbus would be required. Ensuring the upstream version changes continued to work for us. Managing the circular dependency which exists due to this library needing to use viaduct. Complication of our build process because the library needs to end up in our “megazord”. These are the exact reasons why Nimbus itself is in the application-services repo.","breadcrumbs":"Architectural Decision Records » ADR-0005 » Option 2: Use the existing remote settings client","id":"197","title":"Option 2: Use the existing remote settings client"},"198":{"body":"Splitting the existing client out from Nimbus in a way that allows Nimbus to continue to use it, while also making it available for stand-alone use is also an attractive option. In particular, the feature set of that client overlaps with the requirements of the new library - no local persistence is necessary and no signature verification is required. It is already used by a component which is exposed via UniFFI. Note that this option does not preclude both Nimbus and this new crate from moving to the existing remote settings client at some point in the future. A key benefit of this decision is that it keeps nimbus and the new crate using the same client, so updating both to use a different client in the future will always remain an option.","breadcrumbs":"Architectural Decision Records » ADR-0005 » Option 3: Use the existing nimbus client","id":"198","title":"Option 3: Use the existing nimbus client"},"199":{"body":"We have chosen Option 3 because it allows us to reuse the new client in Nimbus, as well as on iOS and on Android with minimal initial development effort. If the new library ends up growing requirements that are already in the existing remote settings client, we remain able to copy that functionality from that library into this.","breadcrumbs":"Architectural Decision Records » ADR-0005 » Chosen Option","id":"199","title":"Chosen Option"},"2":{"body":"The Application Services Source Code is subject to the terms of the Mozilla Public License v2.0. You can obtain a copy of the MPL at https://mozilla.org/MPL/2.0/ .","breadcrumbs":"Application Services Rust Components » License","id":"2","title":"License"},"20":{"body":"Note: This is a bit tedious, and you should first try the auto-publishing workflow described above. But if the auto-publishing workflow fails then it's important to know how to do the publishing process manually. Since most consuming apps get their copy of application-services via a dependency on android-components, this procedure involves three separate repos: Inside the application-services repository root: In version.txt , change the version to end in -TESTING$N 1, where $N is some number that you haven't used for this before. Example: 0.27.0-TESTING3 Run ./gradlew publishToMavenLocal. This may take between 5 and 10 minutes. Inside the consuming project repository root (eg, firefox-android/fenix): Inside build.gradle , add mavenLocal() inside allprojects { repositories { } }. Ensure that local.properties does not contain any configuration to related to auto-publishing the application-services repo. Inside buildSrc/src/main/java/AndroidComponents.kt , change the version numbers for android-components to match the new versions you defined above. Example: const val VERSION = \"0.51.0-TESTING3\" You should now be able to build and run the consuming application (assuming you could do so before all this).","breadcrumbs":"Contributing » Building » How to use the local development autopublish flow for Fenix » Using a manual workflow","id":"20","title":"Using a manual workflow"},"200":{"body":"This section is non-normative - ie, is not strictly part of the ADR, but exists for context. This is a very high-level view of the tasks required here. Create a new top-level component in the application-services repository, identify the exact API we wish to expose for this new library, describe this API using UniFFI, then implement the API with “stubs” (eg, using rust todo!()or similar). This is depicted as RemoteSettings in the diagram. Identify which parts of Nimbus should be factored out into a shared component (depicted as rs-client in the diagram below) and move that functionality to the new shared component. Of note: This component probably will not have a UniFFI .udl file, but is just for consumption by the new component above and the existing nimbus component. There is still some uncertaintly here - if it is a requirement that nimbus and the new component share some configuration or initialization code, we might need to do something more complex here. This seems unlikely, but possible, so is included here for completeness. Identify which of the nimbus tests should move to the new client and move them. Update Nimbus to take a dependency on the new component and use it, including tests. Flesh out the API of the new top-level component using the new shared component (ie, replace the todo!() macros with real code.) Identify any impact on the Nimbus android/swift code - in particular, any shared configuration and initialization code identified above in the application-services repo. Implement the Android and iOS code in the application-services repo desired to make this an ergonomic library for the mobile platforms. Update the mobile code for the UniFFI changes made to Nimbus, if any. Implement the mobile code which consumes the new library, including tests. Profit? This diagram attempts to depict this final layout. Note: rs-client and RemoteSettings are both new components, everything else already exists. Please do not consider these names as suggestions! Names are hard, I'm sure we can do better. Dashed lines are normal Rust dependencies (ie, dependencies listed in Cargo.toml) Solid lines are where the component uses UniFFI Viaduct is a little odd in that it is consumed by the mobile applications indirectly (eg, via Glean), hence it's not in support, but please ignore that anomaly. flowchart RL subgraph app-services-support[Shared components in application-services/components/support] rs-client other-support-components end subgraph app-services-components[Top-level application-services components, in application-services/components] Nimbus RemoteSettings Viaduct end subgraph mobile [Code in the mobile repositories] Fenix Firefox-iOS end Nimbus -- nimbus.udl --> mobile RemoteSettings -- remote_settings.udl --> mobile rs-client -.-> Nimbus other-support-components -.-> Nimbus rs-client -.-> RemoteSettings other-support-components -.-> RemoteSettings Viaduct -.-> rs-client other-support-components -.-> rs-client","breadcrumbs":"Architectural Decision Records » ADR-0005 » Specific Plans","id":"200","title":"Specific Plans"},"201":{"body":"This section is non-normative - ie, is not strictly part of the ADR, but exists for context. Content Signatures have been explicitly called out as a non-requirement. Because this capability was a sticking point in the desktop version of the remote settings client, and because significant effort was spent on it, it's worth expanding on this here. Because https will be enforced for all network requests, the consumers of this library can have a high degree of confidence that: The servers hit by this client are the servers we expect to hit (ie, no man-in-the-middle attacks will be able to redirect to a different server). The response from the server is exactly what was sent by the Mozilla controlled server (ie, no man-in-the-middle attacks will be able to change the content in-flight) Therefore, the content received must be exactly as sent by the Mozilla controlled servers. Content signatures offer an additional capability of checking the content of a remote settings response matches the signature generated with a secret key owned by Mozilla, independenty of the https certificates used for the request itself. This capability was added to the desktop version primarily to protect the integrity of the data at rest. Because the Desktop client cached the responses on disk, there was a risk that this data could be tampered with - so it was effectively impossible to guarantee that the data finally presented to the application is what was initially sent. The main threat-model that required this capability was 3rd party applications installed on the same system where Firefox was installed. Because of the security model enforced by Desktop operating systems (most notably Windows), there was evidence that these 3rd-party applications would locate and modify the cache of remote-settings responses and modify them in a way that benefited them and caused revenue harm to Mozilla - the most obvious example is changing the search provider settings. The reason we are declaring this capability a non-requirement in the initial version is two-fold: We have also declared caching of responses a non-requirement, meaning there's no data at rest managed by this library which is vulnerable to this kind of attack. The mobile operating systems have far stronger application isolation - in the general case, a 3rd party mobile application is prevented from touching any of the files used by other applications. Obviously though, things may change in the future - for example, we might add response caching, so we must be sure to reevaluate this requirement as other requirements change. Found a bug? Edit this page on GitHub.","breadcrumbs":"Architectural Decision Records » ADR-0005 » Content Signatures","id":"201","title":"Content Signatures"},"202":{"body":"Status: accepted Deciders: teshaq, mhammond, lougeniaC64, dnarcese Date: 2023-01-06","breadcrumbs":"Architectural Decision Records » ADR-0007 » Limit Visits Migrated to Places History in Firefox iOS","id":"202","title":"Limit Visits Migrated to Places History in Firefox iOS"},"203":{"body":"The Application-Services team removed a legacy implementation of history in Firefox-ios and replaced it with a maintained implementation that was powering Firefox Android. A significant part of the project is migrating users’ history from the old database to a new one. To measure risk, we ran a dry-run migration. A dry-run migration runs a background thread in the user’s application and attempts to migrate to a fake database. The dry-run was implemented purely to collect telemetry on the migration to evaluate risk. The results can be found in the following Looker dashboard . Below is a list of observations.","breadcrumbs":"Architectural Decision Records » ADR-0007 » Context and Problem Statement","id":"203","title":"Context and Problem Statement"},"204":{"body":"The following is a list of observations from the experiment: 5-6% of migrations do not end. This means for 5-6% of users, the application was terminated before migration ended. For a real migration, this would mean those users lose all of their history unless we attempt the migration multiple times. Out of the migrations that failed (the 5-6% mentioned above) 97% of those users had over 10,000 history visits. Out of migrations that do end, over 99% of migrations are successful. This means that we are not experiencing many errors with the migration beyond the time it takes. The average for visits migrated is around 25,000 - 45,000 visits. The median for visits migrated is around 5,000-15,000 visits. The difference between the average and the median suggests that we have many users with a large number of visits For migrations that did end, the following are the percentiles for how long it took (in milliseconds). We would like to emphasize that the following only includes migrations that did end 10th percentile: 37 ms 25th percentile: 80 ms 50th percentile: 400 ms 75th percentile: 2,500 ms (2.5 seconds) 90th percentile: 6,400 ms (6.4 seconds) 95th percentile: 11,000 ms (11 seconds) 99th percentile: 25,000 ms (25 seconds) 99.9th percentile: 50,000 ms (50 seconds)","breadcrumbs":"Architectural Decision Records » ADR-0007 » Observations from Dry-Run Experiment","id":"204","title":"Observations from Dry-Run Experiment"},"205":{"body":"Given the observations from the dry-run experiment, the rest of the document examines an approach to answer the question: How can we increase the rate of which migrations end, and simultaneously keep the user’s database size at a reasonable size? The user implication of keeping the rate of ended migrations high is that users keep their history, and can interact normally with the URL bar to search history, searching history in the history panel and navigating to sites they visited in the past. The user implication of keeping a reasonable database size is that the database is less likely to lock on long queries. Meaning we reduce performance issues when users use the search bar, the history panel and when navigating to sites. Finally, it’s important to note that power users and daily active users will be more likely to have large histories and thus: Power users are more likely to fail their migration. Power users are more likely to have performance issues with history and the search bar. We saw a version of this with Favicons in an earlier incident, where users were coming across a significant number of database locks, crashing the app. This isn’t to say that the incident is directly related to this, however, large histories could contribute to the state we saw in the incident as it would take longer to run the queries.","breadcrumbs":"Architectural Decision Records » ADR-0007 » Problem Statement","id":"205","title":"Problem Statement"},"206":{"body":"We must not lose users’ recent history. What is considered “recent” is not concretely defined. There is prior art, however: Firefox Sync in Android syncs 5000 visits. Firefox Desktop and Android impose limits on the size of the database, 75 MiB in Android When importing history from chrome to Firefox, we only migrate the latest 2000 visits Chrome only keeps a user’s history for 90 days User’s experience with History must not regress, and ideally should improve. User experience is tightly coupled with the size of the database. The larger the database, the longer queries take. The longer queries take, the longer it would take for a user to observe their searched history and the history panel.","breadcrumbs":"Architectural Decision Records » ADR-0007 » Decision Drivers","id":"206","title":"Decision Drivers"},"207":{"body":"Keep the migration as-is. This option means that we have no limit. We will attempt to migrate all history for our users. Introduce a date-based limit on visits for the migration This option means that we only migrate visits that occurred in the past X days/weeks/months etc Introduce a visit number-based limit for the migration This option means we only migrate the latest X visits","breadcrumbs":"Architectural Decision Records » ADR-0007 » Considered Options","id":"207","title":"Considered Options"},"208":{"body":"Chosen option: Introduce a visit number-based limit for the migration . This option was chosen because given our decision drivers: We must not lose users’ recent history: We have established in the results of the dry-run, that the majority of failed migrations were for users with a large number of visits. By setting a reasonable limit, we can increase the likelihood the migration succeeds. We can set the limit to encompass “recent history” while choosing a limit that has an over 99% success rate. User’s experience with History must not regress, and ideally should improve. We have established in our decision driver that the user’s experience with history is coupled with the database size. By setting a reasonable limit, we can keep the size of the history database controlled. It's also worth noting that with the switch to the new implementation of history, we are also introducing a target size for the database. This means that we have maintenance logic that would compact the database and prune it if it grows beyond the target size.","breadcrumbs":"Architectural Decision Records » ADR-0007 » Decision Outcome","id":"208","title":"Decision Outcome"},"209":{"body":"The migration runs in a shorter time. This means a higher chance of the migration succeeding, thus keeping the user’s recent history without loss. Users who have less than the selected limit, will still get all their history. More on this in the Suggested Limit section. We keep the size of the history database low. This way users with more than the limit, will only keep their recent history. Additionally, when we delete users’ history from the old database, the size of the data the app keeps will decrease dramatically. Keeping the database size low means we lower the chance a user has performance issues with the database.","breadcrumbs":"Architectural Decision Records » ADR-0007 » Positive Consequences","id":"209","title":"Positive Consequences"},"21":{"body":"This assumes you have followed the build instructions for Fenix Make sure you're fully up to date in all repos, unless you know you need to not be. This omits the steps if changes needed because, e.g. application-services made a breaking change to an API used in android-components. These should be understandable to fix, you usually should be able to find a PR with the fixes somewhere in the android-component's list of pending PRs (or, failing that, a description of what to do in the application-services changelog). Contact us if you get stuck.","breadcrumbs":"Contributing » Building » How to use the local development autopublish flow for Fenix » Caveats","id":"21","title":"Caveats"},"210":{"body":"The biggest negative consequence is that Users with more visits than the limit, will lose visits . Since we would only keep the latest X visits for a user, if a user has Y visits, they would lose all of the Y-X visits (assuming Y is greater than X) The effect here is mitigated by the observation that recent history is more important to users than older history. Unfortunately, we do not have any telemetry to demonstrate this claim, but it’s an assumption based on the existing limits on history imposed in Android and Desktop mentioned in the decision drivers section.","breadcrumbs":"Architectural Decision Records » ADR-0007 » Negative Consequences","id":"210","title":"Negative Consequences"},"211":{"body":"","breadcrumbs":"Architectural Decision Records » ADR-0007 » Pros and Cons of the Other Options","id":"211","title":"Pros and Cons of the Other Options"},"212":{"body":"Good because if the migration succeeds, users keep all their history. Bad, because it’s less likely for migrations to succeed. Bad, because even if the migration succeeds it causes the size of the database to be large if a user has a lot of history. Large databases can cause a regression in performance. Users with a lot of history will now have two large databases (the old and new ones) since we won’t delete the data in the old database right away to support downgrades. Bad, because it can take a long time for the migration to finish. Bad because until the migration is over users will experience the app without history.","breadcrumbs":"Architectural Decision Records » ADR-0007 » Keep the migration as-is","id":"212","title":"Keep the migration as-is"},"213":{"body":"Good, because we match users’ usage of the app. Users that use the app more, will keep more of their history. Good, because it’s more likely that the migration ends because we set a limit Bad because it’s hard to predict how large user’s databases will be. This is particularly important for Sync users. As Firefox-iOS syncs all your history, meaning if a user has many visits before the limit across multiple platforms, a large number of visits will be migrated. Bad, because users who haven’t used the app since the limit, will lose all their history For example, if the limit is 3 months, a user who last used the app 3 months ago will suddenly lose all their history","breadcrumbs":"Architectural Decision Records » ADR-0007 » Introduce a date-based limit on visits","id":"213","title":"Introduce a date-based limit on visits"},"214":{"body":"This section describes a suggested limit for the visits. Although it’s backed up with telemetry, the specific number is up for discussion. It’s also important to note that statistical significance was not a part of the analysis. Migration has run for over 16,000 users and although that may not be a statistically significant representation of our population, it’s good enough input to make an educated suggestion. First, we start by observing the distribution of visit counts. This will tell us how many of our users have between 0-10000 visits, 10000-20000, etc. We will identify that most of our users have less than 10,000 visits. Then, we will observe the dry-run migration ended rate based on the above buckets. We will observe that users with under 10,000 visits have a high chance of migration success. Finally, based on the analysis and prior art we’ll suggest 10,000 visits.","breadcrumbs":"Architectural Decision Records » ADR-0007 » Suggested Limit","id":"214","title":"Suggested Limit"},"215":{"body":"We will look at https://mozilla.cloud.looker.com/looks/1078 which demonstrates a distribution of our users based on the number of history visits. Note that the chart is based on our release population. Observations 67% of firefox-ios users have less than 10,000 visits There is a very long tail to the distribution, with 6% of users having over 100,000 visits.","breadcrumbs":"Architectural Decision Records » ADR-0007 » User History Distribution","id":"215","title":"User History Distribution"},"216":{"body":"We will look at https://mozilla.cloud.looker.com/looks/1081 . The chart demonstrates the rate at which migrations end by the number of visits. We bucket users in buckets of 10,000 visits. Observations We observe that for users that have visits under 10,000, the success rate is over 99.6%. We observe that for users with over 100,000 visits, the success rate drops to 75~80%. Users in between, have success rates in between. For example, users with visits between 10,000 and 20,000 have a 98-99% success rate. All success rates for buckets beyond 20,000 visits drop under 96%.","breadcrumbs":"Architectural Decision Records » ADR-0007 » Dry-run Ending Rate by Visits","id":"216","title":"Dry-run Ending Rate by Visits"},"217":{"body":"Based on the above, we’re suggesting a limit of 10,000 visits because 10,000 visits encompass the full history of 67% of our users. Migrations with under 10,000 visits have a success rate of over 99%. For users with over 10,000 visits, they still keep the latest 10,000 visits. The choice is reasonable considering: Sync only retrieves the latest 5000 visits in Android Migrating from Chrome to Firefox only migrates 2000 visits in Desktop","breadcrumbs":"Architectural Decision Records » ADR-0007 » Suggestion","id":"217","title":"Suggestion"},"218":{"body":"Epic for moving iOS’s history implementation to application-services places Dry-run migration experiment Overall dry-run migration looker dashboard Firefox iOS User distribution by history Migration Ended rate by User History Firefox Sync on Android only Syncs 5000 sites Firefox Desktop Limits import from Chrome to 2000 visits Firefox Android limits the size of its places.db to 75MiB Chrome only keeps 90 days of history Performance incident in Firefox iOS Found a bug? Edit this page on GitHub.","breadcrumbs":"Architectural Decision Records » ADR-0007 » Links","id":"218","title":"Links"},"219":{"body":"Megazords - Megazords and how we ship code Sync Manager - Our Sync Manager and how Sync works in using it Shipping Rust Components as Swift Packages - High level design of how use the Swift Package Manager to distribute our Rust components to iOS Sync overview - High level overview of how Firefox sync works Rust Component's Strategy - High level description of our Rust components strategy Metrics - (Glean Telemetry) Rust Version Policy","breadcrumbs":"Design » Design Documents","id":"219","title":"Design Documents"},"22":{"body":"If you had to use the manual workflow above and found it incredibly tedious, you might like to try adding support for the auto-publish workflow to the consuming project! The details will differ depending on the specifics of the project's build setup, but at a high level you will need to: In your settings.gradle , locate (or add) the code for parsing the local.properties file, and add support for loading a directory path from the property autoPublish.application-services.dir. If this property is present, spawn a subprocess to run ./gradlew autoPublishForLocalDevelopment in the specified directory. This automates step (1) of the manual workflow above, publishing your changes to application-services into a local maven repository under a unique version number. In your build.gradle , if the autoPublish.application-services.dir property is present, have each project apply the build script from ./build-scripts/substitute-local-appservices.gradle in the specified directory. This automates steps (2) and (3) of the manual workflow above, using gradle's dependency substitution capabilities to override the verion requirements for application-services components. It may be necessary to experiment with the ordering of this relative to other build configuration steps, in order for the dependency substitution to work correctly. For a single-project build this would look something like: if (gradle.hasProperty('localProperties.autoPublish.application-services.dir')) { ext.appServicesSrcDir = gradle.\"localProperties.autoPublish.application-services.dir\" apply from: \"${appServicesSrcDir}/build-scripts/substitute-local-appservices.gradle\"\n} For a multi-project build it should be applied to all subprojects, like: subprojects { if (gradle.hasProperty('localProperties.autoPublish.application-services.dir')) { ext.appServicesSrcDir = gradle.\"localProperties.autoPublish.application-services.dir\" apply from: \"${rootProject.projectDir}/${appServicesSrcDir}/build-scripts/substitute-local-appservices.gradle\" }\n} Confirm that the setup is working, by adding autoPublish.application-services.dir to your local.properties file and running ./gradlew dependencies for the project. You should be able to see gradle checking the build status of the various application-services dependencies as part of its setup phase. When the command completes, it should print the resolved versions of all dependencies, and you should see that application-services components have a version number in the format 0.0.1-SNAPSHOT-{TIMESTAMP}. [1]: It doesn't have to start with -TESTING, it only needs to have the format -someidentifier. -SNAPSHOT$N is also very common to use, however without the numeric suffix, this has specific meaning to gradle, so we avoid it. Additionally, while the $N we have used in our running example has matched (e.g. all of the identifiers ended in -TESTING3, this is not required, so long as you match everything up correctly at the end. This can be tricky, so I always try to use the same number). Found a bug? Edit this page on GitHub.","breadcrumbs":"Contributing » Building » How to use the local development autopublish flow for Fenix » Adding support for the auto-publish workflow","id":"22","title":"Adding support for the auto-publish workflow"},"220":{"body":"Each Rust component published by Application Services is conceptually a stand-alone library, but for distribution we compile all the rust code for all components together into a single .so file. This has a number of advantages: Easy and direct interoperability between different components at the Rust level Cross-component optimization of generated code Reduced code size thanks to distributing a single copy of the rust stdlib, low-level dependencies, etc. This process is affectionately known as \"megazording\" and the resulting artifact as a megazord library . On Android, the situation is quite complex due to the way packages and dependencies are managed. We need to distribute each component as a separate Android ARchive (AAR) that can be managed as a dependency via gradle, we need to provide a way for the application to avoid shipping rust code for components that it isn't using, and we need to do it in a way that maintanins the advantages listed above. This document describes our current approach to meeting all those requirements on Android. Other platforms such as iOS are not considered.","breadcrumbs":"Design » Megazords » Megazording","id":"220","title":"Megazording"},"221":{"body":"We publish a separate AAR for each component (e.g. fxaclient, places, logins) which contains just the Kotlin wrappers that expose the relevant functionality to Android. Each of these AARs depends on a separate shared \"megazord\" AAR in which all the rust code has been compiled together into a single .so file. The application's dependency graph thus looks like this: megazord dependency diagram This generates a kind of strange inversion of dependencies in our build pipeline: Each individual component defines both a rust crate and an Android AAR. There is a special \"full-megazord\" component that also defines a rust crate and an Android AAR. The full-megazord rust crate depends on the rust crates for each individual component. But the Android AAR for each component depends on the Android AAR of the full-megazord! It's a little odd, but it has the benefit that we can use gradle's dependency-replacement features to easily manage the rust code that is shipping in each application.","breadcrumbs":"Design » Megazords » AAR Dependency Graph","id":"221","title":"AAR Dependency Graph"},"222":{"body":"By default, an application that uses any appservices component will include the compiled rust code for all appservices components. To reduce its overall code size, the application can use gradle's module replacement rules to replace the \"full-megazord\" AAR with a custom-built megazord AAR containing only the components it requires. Such an AAR can be built in the same way as the \"full-megazord\", and simply avoid depending on the rust crates for components that are not required. To help ensure this replacement is done safely at runtime, the mozilla.appservices.support.native package provides helper functions for loading the correct megazord .so file. The Kotlin wrapper for each component should load its shared library by calling mozilla.appservices.support.native.loadIndirect, specifying both the name of the component and the expected version number of the shared library.","breadcrumbs":"Design » Megazords » Custom Megazords","id":"222","title":"Custom Megazords"},"223":{"body":"The full-megazord AAR contains compiled rust code that targets various Android platforms, and is not suitable for running on a Desktop development machine. In order to support integration with unittest suites such as robolectric, each megazord has a corresponding Java ARchive (JAR) distribution named e.g. full-megazord-forUnitTests.jar. This contains the rust code compiled for various Desktop architectures, and consumers can add it to their classpath when running tests on a Desktop machine.","breadcrumbs":"Design » Megazords » Unit Tests","id":"223","title":"Unit Tests"},"224":{"body":"This setup mostly works, but has a handful of rough edges. The build.gradle for each component needs to declare an explicit dependency on project(\":full-megazord\"), otherwise the resulting AAR will not be able to locate the compiled rust code at runtime. It also needs to declare a dependency between its build task and that of the full-megazord, for reasons. Typically this looks something like: tasks[\"generate${productFlavor}${buildType}Assets\"].dependsOn(project(':full-megazord').tasks[\"cargoBuild\"]) In order for unit tests to work correctly, the build.gradle for each component needs to add the rustJniLibs directory of the full-megazord project to its srcDirs, otherwise the unittests will not be able to find and load the compiled rust code. Typically this looks something like: test.resources.srcDirs += \"${project(':full-megazord').buildDir}/rustJniLibs/desktop\" The above also means that unittests will not work correctly when doing local composite builds, because it's unreasonable to expect the main project (e.g. Fenix) to include the above in its build scripts. Found a bug? Edit this page on GitHub.","breadcrumbs":"Design » Megazords » Gotchas and Rough Edges","id":"224","title":"Gotchas and Rough Edges"},"225":{"body":"We've identified the need for a \"sync manager\" (although are yet to identify a good name for it!) This manager will be responsible for managing \"global sync state\" and coordinating each engine. At a very high level, the sync manager is responsible for all syncing. So far, so obvious. However, given our architecture, it's possible to identify a key architectural split. The embedding application will be responsible for most high-level operations. For example, the app itself will choose how often regular syncs should happen, what environmental concerns apply (eg, should I only sync on WiFi?), letting the user choose exactly what to sync, and so forth. A lower-level component will be responsible for the direct interaction with the engines and with the various servers needed to perform a sync. It will also have the ultimate responsibility to not cause harm to the service (for example, it will be likely to enforce some kind of rate limiting or ensuring that service requests for backoff are enforced) Because all application-services engines are written in Rust, it's tempting to suggest that this lower-level component also be written in Rust and everything \"just works\", but there are a couple of complications here: For iOS, we hope to integrate with older engines which aren't written in Rust, even if iOS does move to the new Sync Manager. For Desktop, we hope to start by reusing the existing \"sync manager\" implemented by Desktop, and start moving individual engines across. There may be some cross-crate issues even for the Rust implemented engines. Or more specifically, we'd like to avoid assuming any particular linkage or packaging of Rust implemented engines. Even with these complications, we expect there to be a number of high-level components, each written in a platform specific language (eg, Kotlin or Swift) and a single lower-level component to be implemented in Rust and delivered as part of the application-services library - but that's not a free-pass. Why \"a number of high-level components\"? Because that is the thing which understands the requirements of the embedding application. For example, Android may end up with a single high-level component in the android-components repo and shared between all Android components. Alternatively, the Android teams may decide the sync manager isn't generic enough to share, so each app will have their own. iOS will probably end up with its own and you could imagine a future where Desktop does too - but they should all be able to share the low level component.","breadcrumbs":"Design » Sync Manager » Sync manager","id":"225","title":"Sync manager"},"226":{"body":"The primary responsibilities of the \"high level\" portion of the sync manager are: Manage all FxA interaction. The low-level component will have a way to communicate auth related problems, but it is the high-level component which takes concrete FxA action. Expose all UI for the user to choose what to sync and coordinate this with the low-level component. Note that because these choices can be made on any connected device, these choices must be communicated in both directions. Implement timers or any other mechanism to fully implement the \"sync scheduler\", including any policy decisions such as only syncing on WiFi, etc. Implement a UI so the user can \"sync now\". Collect telemetry from the low-level component, probably augment it, then submit it to the telemetry pipeline. The primary responsibilities of the \"low level\" portion of the sync manager are: Manage the meta/global, crypto/keys and info/collections resources, and interact with each engine as necessary based on the content of these resources. Manage interaction with the token server. Enforce constraints necessary to ensure the entire ecosystem is not subject to undue load. For example, this component should ignore attempts to sync continuously, or to sync when the services have requested backoff. Manage the \"clients\" collection - we probably can't ignore this any longer, especially for bookmarks (as desktop will send a wipe command on bookmark restore, and things will \"be bad\" if we don't see that command). Define a minimal \"service state\" so certain things can be coordinated with the high-level component. Examples of these states are \"everything seems ok\", \"the service requested we backoff for some period\", \"an authentication error occurred\", and possibly others. Perform, or coordinate, the actual sync of the rust implemented engines - from the containing app's POV, there's a single \"sync now\" entry-point (in practice there might be a couple, but conceptually there's a single way to sync). Note that as below, how non-rust implemented engines are managed is TBD. Manage the collection of (but not the submission of) telemetry from the various engines. Expose APIs and otherwise coordinate with the high-level component. Stuff we aren't quite sure where it fits include: Coordination with non-rust implemented engines. These engines are almost certainly going to be implemented in the same language as the high-level component, which will make integration simpler. However, the low-level component will almost certainly need some information about these engines for populating info/collections etc. For now, we are punting on this until things become a bit clearer.","breadcrumbs":"Design » Sync Manager » The responsibilities of the Sync Manager.","id":"226","title":"The responsibilities of the Sync Manager."},"227":{"body":"The above has been carefully written to try and avoid implementation details - the intent is that it's an overview of the architecture without any specific implementation decisions. These next sections start getting specific, so implementation choices need to be made, and thus will possibly be more contentious. In other words, get your spray-cans ready because there's a bikeshed being built! However, let's start small and make some general observations.","breadcrumbs":"Design » Sync Manager » Implementation Details.","id":"227","title":"Implementation Details."},"228":{"body":"Some apps only care about a subset of the engines - lockbox is one such app and only cares about a single collection/engine. It might be the case that lockbox uses a generic application-services library with many engines available, even though it only wants logins. Thus, the embedding application is the only thing which knows which engines should be considered to \"exist\". It may be that the app layer passes an engine to the sync manager, or the sync manager knows via some magic how to obtain these handles. Some apps will use a combination of Rust components and \"legacy\" engines. For example, iOS is moving some of the engines to using Rust components, while other engines will be ported after delivery of the sync manager, if they are ported at all. We also plan to introduce some rust engines into desktop without integrating the \"sync manager\" The rust components themselves are designed to be consumed as individual components - the \"logins\" component doesn't know anything about the \"bookmarks\" component. There are a couple of gotchyas in the current implementations too - there's an issue when certain engines don't yet appear in meta/global - see bug 1479929 for all the details. The tl;dr of the above is that each rust component should be capable of working with different sync managers. That said though, let's not over-engineer this and pretend we can design a single, canonical thing that will not need changing as we consider desktop and iOS.","breadcrumbs":"Design » Sync Manager » Current implementations and challenges with the Rust components","id":"228","title":"Current implementations and challenges with the Rust components"},"229":{"body":"There's loads of state here. The app itself has some state. The high-level Sync Manager component will have state, the low-level component will have state, and each engine has state. Some of this state will need to be persisted (either on the device or on the storage servers) and some of this state can be considered ephemeral and lives only as long as the app. A key challenge will be defining this state in a coherent way with clear boundaries between them, in an effort to allow decoupling of the various bits so Desktop and iOS can fit into this world. This state management should also provide the correct degree of isolation for the various components. For example, each engine should only know about state which directly impacts how it works. For example, the keys used to encrypt a collection should only be exposed to that specific engine, and there's no need for one engine to know what info/collections returns for other engines, nor whether the device is currently connected to WiFi. A thorn here is for persisted state - it would be ideal if the low-level component could avoid needing to persist any state, so it can avoid any kind of storage abstraction. We have a couple of ways of managing this: The state which needs to be persisted is quite small, so we could delegate state storage to the high-level component in an opaque way, as this high-level component almost certainly already has storage requirements, such as storing the \"choose what to sync\" preferences. The low-level component could add its own storage abstraction. This would isolate the high-level component from this storage requirement, but would add complexity to the sync manager - for example, it would need to be passed a directory where it should create a file or database. We'll probably go with the former.","breadcrumbs":"Design » Sync Manager » State, state and more state. And then some state.","id":"229","title":"State, state and more state. And then some state."},"23":{"body":"This is a guide on testing the Swift Package Manager component locally against a local build of Firefox iOS. For more information on our Swift Package Manager design, read the ADR that introduced it This guide assumes the component you want to test is already distributed with the rust-components-swift repository, you can read the guide for adding a new component if you would like to distribute a new component. The goal for this document is to be able to build a local firefox iOS against a local application-services . On a high level, that requires the following: Build an xcframework in a local checkout of application-services Include the xcframework in a local checkout of rust-components-swift Run the generate script in rust-components-swift using a local checkout of application-services Include the local checkout of rust-components-swift in firefox-ios","breadcrumbs":"Contributing » Building » How to use the local development autopublish flow for Firefox iOS » How to locally test Swift Package Manager components on Firefox iOS","id":"23","title":"How to locally test Swift Package Manager components on Firefox iOS"},"230":{"body":"Let's try and move into actionable decisions for the implementation. We expect the implementation of the low-level component to happen first, followed very closely by the implementation of the high-level component for Android. So we focus first on these.","breadcrumbs":"Design » Sync Manager » Implementation plan for the low-level component.","id":"230","title":"Implementation plan for the low-level component."},"231":{"body":"The clients engine includes some meta-data about each client. We've decided we can't replace the clients engine with the FxA device record and we can't simply drop this engine entirely. Of particular interest is \"commands\" - these involve communicating with the engine regarding commands targetting it, and accepting commands to be send to other devices. Note that outgoing commands are likely to not originate from a sync, but instead from other actions, such as \"restore bookmarks\". However, because the only current requirement for commands is to wipe the store, and because you could anticipate \"wipe\" also being used when remotely disconnecting a device (eg, when a device is lost or stolen), our lives would probably be made much simpler by initially supporting only per-engine wipe commands. Note that there has been some discussion about not implementing the client engine and replacing \"commands\" with some other mechanism. However, we have decided to not do that because the implementation isn't considered too difficult, and because desktop will probably require a number of changes to remove it (eg, \"synced tabs\" will not work correctly without a client record with the same guid as the clients engine.) Note however that unlike desktop, we will use the FxA device ID as the client ID. Because FxA device IDs are more ephemeral than sync IDs, it will be necessary for engines using this ID to track the most-recent ID they synced with so the old record can be deleted when a change is detected.","breadcrumbs":"Design » Sync Manager » Clients Engine","id":"231","title":"Clients Engine"},"232":{"body":"For the purposes of the sync manager, we define: An engine is the unit exposed to the user - an \"engine\" can be enabled or disabled. There is a single set of canonical \"engines\" used across the entire sync ecosystem - ie, desktop and mobile devices all need to agree about what engines exist and what the identifier for an engine is. An Api is the unit exposed to the application layer for general application functionality. Application services has 'places' and 'logins' Apis and is the API used by the application to store and fetch items. Each 'Api' may have one or more 'stores' (although the application layer will generally not interact directly with a store) A store is the code which actually syncs. This is largely an implementation detail. There may be multiple stores per engine (eg, the \"history\" engine may have \"history\" and \"forms\" stores) and a single 'Api' may expose multiple stores (eg, the \"places Api\" will expose history and bookmarks stores) A collection is a unit of storage on a server. It's even more of an implementation detail than a store. For example, you might imagine a future where the \"history\" store uses multiple \"collections\" to help with containers. In practice, this means that the high-level component should only need to care about an engine (for exposing a choice of what to sync to the user) and an api (for interacting with the data managed by that api). The low-level component will manage the mapping of engines to stores.","breadcrumbs":"Design » Sync Manager » Collections vs engines vs stores vs preferences vs Apis","id":"232","title":"Collections vs engines vs stores vs preferences vs Apis"},"233":{"body":"This document isn't going to outline the history of how \"declined\" is used, nor talk about how this might change in the future. For the purposes of the sync manager, we have the following hard requirements: The low-level component needs to know what the currently declined set of engines is for the purposes of re-populating meta/global. The low-level component needs to know when the declined set needs to change based on user input (ie, when the user opts in to or out of a particular engine on this device) The high-level component needs to be aware that the set of declined engines may change on every sync (ie, when the user opts in to or out of a particular engine on another device) A complication is that due to networks being unreliable, there's an inherent conflict between \"what is the current state?\" and \"what state changes are requested?\". For example, if the user changes the state of an engine while there's no network, then exits the app, how do we ensure the user's new state is updated next time the app starts? What if the user has since made a different state request on a different device? Is the state as last-known on this device considered canonical? To clarify, consider: User on this device declines logins. This device now believes logins is disabled but history is enabled, but is unable to write this to the server due to no network. The user declines history on a different device, but doesn't change logins. This device does manage to write the new list to the server. This device restarts and the network is up. It believes history is enabled but logins is not - however, the state on the server is the exact opposite. How does this device react? (On the plus side, this is an extreme edge-case which none of our existing implementations handle \"correctly\" - which is easy to say, because there's no real definition for \"correctly\") Regardless, the low-level component will not pretend to hide this complexity (ie, it will ignore it!). The low-level component will allow the app to ask for state changes as part of a sync, and will return what's on the server at the end of every sync. The app is then free to build whatever experience it desires around this.","breadcrumbs":"Design » Sync Manager » The declined list","id":"233","title":"The declined list"},"234":{"body":"The low-level component needs to have the ability to disconnect all engines from Sync. Engines which are declined should also be reset. Because we will need wipe() functionality to implement the clients engine, and because Lockbox wants to wipe on disconnect, we will provide disconnect and wipe functionality.","breadcrumbs":"Design » Sync Manager » Disconnecting from Sync","id":"234","title":"Disconnecting from Sync"},"235":{"body":"Breaking the above down into actionable tasks which can be some somewhat concurrently, we will deliver:","breadcrumbs":"Design » Sync Manager » Specific deliverables for the low-level component.","id":"235","title":"Specific deliverables for the low-level component."},"236":{"body":"A straw-man for the API we will expose to the high-level components. This probably isn't too big, but we should do this as thoroughly as we can. In particular, ensure we have covered: Declined management - how the app changes the declined list and how it learns of changes from other devices. How telemetry gets handed from the low-level to the high-level. The \"state\" - in particular, how the high-level component understands the auth state is wrong, and whether the service is in a degraded mode (eg, server requested backoff) How the high-level component might specify \"special\" syncs, such as \"just one engine\" or \"this is a pre-sleep, quick-as-possible sync\", etc There's a straw-man proposal for this at the end of the document.","breadcrumbs":"Design » Sync Manager » The API","id":"236","title":"The API"},"237":{"body":"We should build a utility (or 2) which can stand in for the high-level component, for testing and demonstration purposes. This is something like places-utils.rs and the little utility Grisha has been using. This utility should act like a real client (ie, it should have an FxA device record, etc) and it should use the low-level component in exactly the same we we expect real products to use it. Because it is just a consumer of the low-level component, it will force us to confront some key issues, such as how to get references to engines stored in multiple crates, how to present a unified \"state\" for things like auth errors, etc.","breadcrumbs":"Design » Sync Manager » A command-line (and possibly Android) utility.","id":"237","title":"A command-line (and possibly Android) utility."},"238":{"body":"The initial work for the clients engine can probably be done without too much regard for how things are tied together - for example, much work could be done without caring how we get a reference to engines across crates.","breadcrumbs":"Design » Sync Manager » The \"clients\" engine","id":"238","title":"The \"clients\" engine"},"239":{"body":"Implementing things needed to we can expose the correct state to the high-level manager for things like auth errors, backoff semantics, etc","breadcrumbs":"Design » Sync Manager » State work","id":"239","title":"State work"},"24":{"body":"A local checkout of firefox-ios that is ready to build A local checkout of rust-components-swift A local checkout of application-services that is ready to build for iOS","breadcrumbs":"Contributing » Building » How to use the local development autopublish flow for Firefox iOS » Prerequisites:","id":"24","title":"Prerequisites:"},"240":{"body":"There will be lots of loose ends to clean up - things like telemetry, etc.","breadcrumbs":"Design » Sync Manager » Tie it together and other misc things.","id":"240","title":"Tie it together and other misc things."},"241":{"body":"We have identified that iOS will, at least in the short term, want the sync manager to be implemented in Swift. This will be responsible for syncing both the Swift and Rust implemented engines. At some point in the future, Desktop may do the same - we will have both Rust and JS implemented engines which need to be coordinated. We ignore this requirement for now. This approach still has a fairly easy time coordinating with the Rust implemented engines - the FFI will need to expose the relevant sync entry-points to be called by Swift, but the Swift code can hard-code the Rust engines it has and make explicit calls to these entry-points. This Swift code will need to create the structures identified below, but this shouldn't be too much of a burden as it already has the information necessary to do so (ie, it already has info/collections etc) TODO: dig into the Swift code and make sure this is sane.","breadcrumbs":"Design » Sync Manager » Followup with non-rust engines.","id":"241","title":"Followup with non-rust engines."},"242":{"body":"While we use rust struct definitions here, it's important to keep in mind that as mentioned above, we'll need to support the manager being written in something other than rust, and to support engines written in other than rust. The structures below are a straw-man, but hopefully capture all the information that needs to be passed around. // We want to define a list of \"engine IDs\" - ie, canonical strings which\n// refer to what the user perceives as an \"enigine\" - but as above, these\n// *do not* correspond 1:1 with either \"stores\" or \"collections\" (eg, \"history\"\n// refers to 2 stores, and in a future world, might involve 3 collections).\nenum Engine { History, // The \"History\" and \"Forms\" stores. Bookmarks, // The \"Bookmark\" store. Passwords,\n} impl Engine { fn as_str(&self) -> &'static str { match self { History => \"history\", // etc }\n} // A struct which reflects engine declined states.\nstruct EngineState { engine: Engine, enabled: bool,\n} // A straw-man for the reasons why a sync is happening.\nenum SyncReason { Scheduled, User, PreSleep, Startup,\n} // A straw man for the general status.\nenum ServiceStatus { Ok, // Some general network issue. NetworkError, // Some apparent issue with the servers. ServiceError, // Some external FxA action needs to be taken. AuthenticationError, // We declined to do anything for backoff or rate-limiting reasons. BackedOff, // Something else - you need to check the logs for more details. OtherError,\n} // Info we need from FxA to sync. This is roughly our Sync15StorageClientInit\n// structure with the FxA device ID.\nstruct AccountInfo { key_id: String, access_token: String, tokenserver_url: Url, device_id: String,\n} // Instead of massive param and result lists, we use structures.\n// This structure is passed to each and every sync.\nstruct SyncParams { // The engines to Sync. None means \"sync all\" engines: Option>, // Why this sync is being done. reason: SyncReason, // Any state changes which should be done as part of this sync. engine_state_changes: Vec, // An opaque state \"blob\". This should be persisted by the app so it is // reused next sync. persisted_state: Option,\n} struct SyncResult { // The general health. service_status: ServiceStatus, // The result for each engine. engine_results: HashMap>, // The list of declined engines, or None if we failed to get that far. declined_engines: Option>, // When we are allowed to sync again. If > now() then there's some kind // of back-off. Note that it's not strictly necessary for the app to // enforce this (ie, it can keep asking us to sync, but we might decline). // But we might not too - eg, we might try a user-initiated sync. next_sync_allowed_at: Timestamp, // New opaque state which should be persisted by the embedding app and supplied // the next time Sync is called. persisted_state: String, // Telemetry. Nailing this down is tbd. telemetry: Option,\n} struct SyncManager {} impl SyncManager { // Initialize the sync manager with the set of Engines known by this // application without regard to the enabled/declined states. // XXX - still TBD is how we will pass \"stores\" around - it may be that // this function ends up taking an `impl Store` fn init(&self, engines: Vec<&str>) -> Result<()>; fn sync(&self, params: SyncParams) -> Result; // Interrupt any current syncs. Note that this can be called from a different // thread. fn interrupt() -> Result<()>; // Disconnect this device from sync. This may \"reset\" the stores, but will // not wipe local data. fn disconnect(&self) -> Result<()>; // Wipe all local data for all local stores. This can be done after // disconnecting. // There's no exposed way to wipe the remote store - while it's possible // stores will want to do this, there's no need to expose this to the user. fn wipe(&self) -> Result<()>;\n} Found a bug? Edit this page on GitHub.","breadcrumbs":"Design » Sync Manager » Details","id":"242","title":"Details"},"243":{"body":"This document provides a high-level overview of how syncing works. Note : each component has its own quirks and will handle sync slightly differently than the general process described here.","breadcrumbs":"Design » Sync overview » Sync Overview","id":"243","title":"Sync Overview"},"244":{"body":"Crates involved : The sync15 and support/sync15-traits handle the general syncing logic and define the SyncEngine trait Individual component crates (logins, places, autofill, etc). These implement SyncEngine. sync_manager manages the overall syncing process. High level sync flow : Sync is initiated by the application that embeds application-services. The application calls SyncManager.sync() to start the sync process. SyncManager creates SyncEngine instances to sync the individual components. Each SyncEngine corresponds to a collection on the sync server.","breadcrumbs":"Design » Sync overview » General flow and architecture","id":"244","title":"General flow and architecture"},"245":{"body":"SyncManager is responsible for performing the high-level parts of the sync process: The consumer code calls it's sync() function to start the sync, passing in a SyncParams object in, which describes what should be synced. SyncManager performs all network operations on behalf of the individual engines. It's also responsible for tracking the general authentication state (primarily by inspecting the responses from these network requests) and fetching tokens from the token server. SyncManager checks if we are currently in a backoff period and should wait before contacting the server again. Before syncing any engines, the sync manager checks the state of the meta/global collection and compares it with the enabled engines specified in the SyncParams. This handles the cases when the user has requested an engine be enabled or disabled on this device, or when it was requested on a different device. (Note that engines enabled and disabled states are state on the account itself and not a per-device setting). Part of this process is comparing the collection's GUID on the server with the GUID known locally - if they are different, it implies some other device has \"reset\" the collection, so the engine drops all metadata and attempts to reconcile with every record on the server (ie, acts as though this is the very first sync this engine has ever done). SyncManager instantiates a SyncEngine for each enabled component. We currently use 2 different methods for this: The older method is for the SyncManager to hold a weakref to a Store use that to create the SyncEngine (tabs and places). The SyncEngine uses the Store for database access, see the TabsStore for an example. The newer method is for the components to provide a function to create the SyncEngine, hiding the details of how that engine gets created (autofill/logins). These components also define a Store instance for the SyncEngine to use, but it's all transparent to the SyncManager. (See autofill::get_registered_sync_engine() and autofill::db::store::Store ) For components that use local encryption, SyncManager passes the local encryption key to their SyncEngine Finally, calls sync_multiple() function from the sync15 crate, sending it the SyncEngine instances. sync_multiple() then calls the sync() function for each individual SyncEngine","breadcrumbs":"Design » Sync overview » Sync manager","id":"245","title":"Sync manager"},"246":{"body":"SyncEngine is defined in the support/sync15-traits crate and defines the interface for syncing a component. A new SyncEngine instance is created for each sync SyncEngine.apply_incoming() does the main work. It is responsible for processing incoming records from the server in order to update the local records and calculating which local records should be synced back.","breadcrumbs":"Design » Sync overview » Sync engines","id":"246","title":"Sync engines"},"247":{"body":"SyncEngine instances are free to implement apply_incoming() any way they want, but the most components follow a general pattern.","breadcrumbs":"Design » Sync overview » The apply_incoming pattern","id":"247","title":"The apply_incoming pattern"},"248":{"body":"The local table stores records for the local application The mirror table stores the last known record from the server The staging temporary table stores the incoming records that we're currently processing The local/mirror/staging tables contains a guid as its primary key. A record will share the same guid for the local/mirror/staging table. The metadata table stores the GUID for the collection as a whole and the the last-known server timestamp of the collection.","breadcrumbs":"Design » Sync overview » Database Tables","id":"248","title":"Database Tables"},"249":{"body":"stage incoming : write out each incoming server record to the staging table fetch states : take the rows from all 3 tables and combine them into a single struct containing Options for the local/mirror/staging records. iterate states : loop through each state, decide how to do change the local records, then execute that plan. reconcile/plan : For each state we create an action plan for it. The action plan is a low-level description of what to change (add this record, delete this one, modify this field, etc). Here are some common situations: A record only appears in the staging table . It's a new record from the server and should be added to the local DB A record only appears in the local table . It's a new record on the local instance and should be synced back to the serve Identical records appear in the local/mirror tables and a changed record is in the staging table . The record was updated remotely and the changes should be propagated to the local DB. A record appears in the mirror table and changed records appear in both the local and staging tables . The record was updated both locally and remotely and we should perform a 3-way merge. apply plan : After we create the action plan, then we execute it. fetch outgoing : Calculate which records need to be sent back to the server Update the mirror table Return those records back to the sync15 code so that it can upload them to the server. The sync15 code returns the timestamp reported by the server in the POST response and hands it back to the engine. The engine persists this timestamp in the metadata table - the next sync will then use this timestamp to only fetch records that have since been changed by other devices","breadcrumbs":"Design » Sync overview » apply_incoming stages","id":"249","title":"apply_incoming stages"},"25":{"body":"For convenience, there is a script that will do all the necessary steps to configure your local firefox-ios build with a local application-services repository. You do not need to do the manual steps if you follow those steps. Run the following to execute the script, the example below assumes all of firefox-ios, rust-components-swift and application-services are in the same directory. Adjust the paths according to where they are on your filesystem. $ cd firefox-ios # This is your local checkout of firefox-ios\n$ ./rust_components_local.sh -a ../application-services ../rust-components-swift Using Xcode, open Client.xcodeproj in firefox-ios Then, make sure to reset packages cache in Xcode. This forces Xcode to remove any previously cached versions of the Rust components. You can reset package caches by going to File -> Packages -> Reset Package Caches If this is not the first time you run the script, make sure to also update package versions. This forces Xcode to pull the latest changes in the rust-components-swift branch. You can update package versions by going to File -> Packages -> Update To Latest Package Versions If this step fails, it's possible that the Reset Package Caches step above left some cruft behind. You can force this step by manually removing ~/Library/Caches/org.swift.swiftpm and ~/Library/Developer/Xcode/DerivedData/Client-{some-long-string} Once the above steps are done, attempt building firefox ios. If you face problems, feel free to contact us","breadcrumbs":"Contributing » Building » How to use the local development autopublish flow for Firefox iOS » Using the automated flow","id":"25","title":"Using the automated flow"},"250":{"body":"The local table has an integer column syncChangeCounter which is incremented every time the embedding app makes a change to a local record (eg, updating a field). Thus, any local record with a non-zero change counter will need to be updated on the server (with either the local record being used, or after it being merged if the record also changed remotely). At the start of the sync, when we are determining what action to take, we take a copy of the change counter, typically in a temp staging table. After we have uploaded the record to the server, we decrement the counter by whatever it was when the sync started. This means that if a record is changed in between staging the record and uploading it, the change counter will not drop to zero, and so it will correctly be seen as locally modified on the next sync Found a bug? Edit this page on GitHub.","breadcrumbs":"Design » Sync overview » syncChangeCounter","id":"250","title":"syncChangeCounter"},"251":{"body":"This is a high level description of the decision highlighted in the ADR that introduced Swift Packages as a strategy to ship our Rust components . That document includes that tradeoffs and why we chose this approach. The strategy includes two main parts: The xcframework that is built from a megazord . The xcframework contains the following, built for all our target iOS platforms. The compiled Rust code for all the crates listed in Cargo.toml as a static library The C header files and Swift module maps for the components The rust-components-swift repository which has a Package.swift that includes the xcframework and acts as the swift package the consumers import","breadcrumbs":"Design » Shipping Rust Components as Swift Packages » High level design for shipping Rust Components as Swift Packages","id":"251","title":"High level design for shipping Rust Components as Swift Packages"},"252":{"body":"In application-services, in the megazords/ios-rust directory, we have the following: A Rust crate that serves as the megazord for our iOS distributions. The megazord depends on all the Rust Component crates and re-exports their public APIs. Some skeleton files for building an xcframework: 1. module.modulemap : The module map tells the Swift compiler how to use C APIs. 1. MozillaRustComponents.h: The header is used by the module map as a shortcut to specify all the available header files 1. Info.plist: The plist file specifies metadata about the resulting xcframework. For example, architectures and subdirectories. The build-xcframework.sh script that stitches things together into a full xcframework bundle: The xcframework format is not well documented; briefly: The xcframework is a directory containing the resources compiled for multiple target architectures. The xcframework is distributed as a .zip file. The top-level directory contains a subdirectory per architecture and an Info.plist. The Info.plist describes what lives in which directory. Each subdirectory represents an architecture. And contains a .framework directory for that architecture. It's a little unusual that we're building the xcframework by hand, rather than defining it as the build output of an Xcode project. It turns out to be simpler for our purposes, but does risk diverging from the expected format if Apple changes the details of xcframeworks in future Xcode releases.","breadcrumbs":"Design » Shipping Rust Components as Swift Packages » The xcframework and application-services","id":"252","title":"The xcframework and application-services"},"253":{"body":"The repository is a Swift Package for distributing releases of Mozilla's various Rust-based application components. It provides the Swift source code packaged in a format understood by the Swift package manager, and depends on a pre-compiled binary release of the underlying Rust code published from application-services The rust-components-swift repo mainly includes the following: Package.swift: Defines all the targets and products the package exposes. Package.swift also includes where the package gets the xcframework that application-services builds make_tag.sh: A script that does the following: Generates any dynamically generated Swift code, mainly: The uniffi generated Swift bindings The Glean metrics Creates and commits a git tag that can be pushed to cut a release Consumers would then import the rust-components-swift swift package, by indicating the url of the package on github (i.e https://github.com/mozilla/rust-components-swift ) and selecting a version using the git tag. Found a bug? Edit this page on GitHub.","breadcrumbs":"Design » Shipping Rust Components as Swift Packages » The rust-components-swift repository","id":"253","title":"The rust-components-swift repository"},"254":{"body":"On a high level, Firefox Sync has three main components: The Firefox Account Server: Which uses oauth to authenticate and provide users with scoped access. The FxA Server also stores input that will be used by the clients to generate the sync keys. Firefox: This is the firefox app itself, which implements the client logic to communicate with the firefox account servers, generate sync keys, use them to encrypt data and send/receive encrypted data to/from the sync storage servers Sync Storage Server: The server that stores encrypted sync data. The clients would retrieve the encrypted data and decrypt it client side Additionally, the token server assists in providing metadata to Firefox, so that it knows which sync server to communicate with. Diagram showing on a high level, how Firefox sync interacts with Firefox Accounts and Sync Services","breadcrumbs":"Design » Rust Component's Strategy » High level firefox sync interactions","id":"254","title":"High level firefox sync interactions"},"255":{"body":"Since we have multiple Firefox apps (Desktop, iOS, Android, Focus, etc) Firefox sync can sync across platforms. Allowing users to access their up-to-date data across apps and devices. Diagram showing how firefox sync is a multi-platform feature","breadcrumbs":"Design » Rust Component's Strategy » Multi-platform sync diagram","id":"255","title":"Multi-platform sync diagram"},"256":{"body":"Before our Rust Components came to life, each application had its own implementation of the sync and FxA client protocols. This lead to duplicate logic across platforms. This was problematic since any modification to the sync or FxA client business logic would need to be modified in all implementations and the likelihood of errors was high. Diagram showing how firefox sync used to be, with each platform having its own implementation","breadcrumbs":"Design » Rust Component's Strategy » Before: How sync was","id":"256","title":"Before: How sync was"},"257":{"body":"Currently, we are in the process of migrating many of the sync implementation to use our Rust Component strategy. Fenix primarily uses our Rust Components and iOS has some integrated as well. Additionally, Firefox Desktop also uses one Rust component (Web Extension Storage). The Rust components not only unify the different implementations of sync, they also provide a convenient local storage for the apps. In other words, the apps can use the components for storage, with or without syncing to the server. Diagram showing how firefox sync is now, with iOS and Fenix platform sharing some implementations Current Status The following table has the status of each of our sync Rust Components | Application\\Component | Bookmarks | History | Tabs | Passwords | Autofill | Web Extension Storage | FxA Client | |-----------------------|-----------|---------|------|-----------|----------|-----------------------|------------| | Fenix | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | | ✔️ | | Firefox iOS | ✔️ | | ✔️ | ✔️ | | | ✔️ | | Firefox Desktop | | | | | | ✔️ | | | Focus | | | | | | | |","breadcrumbs":"Design » Rust Component's Strategy » Now: Sync is starting to streamline its components","id":"257","title":"Now: Sync is starting to streamline its components"},"258":{"body":"In an aspirational future, all the applications would use the same implementation for Sync. However, it's unlikely that we would migrate everything to use the Rust components since some implementations may not be prioritized, this is especially true for desktop which already has stable implementations. That said, we can get close to this future and minimize duplicate logic and the likelihood of errors. Diagram showing how firefox sync should be, with all platforms using one implementation You can edit the diagrams in the following lucid chart (Note: Currently only Mozilla Employees can edit those diagrams): https://lucid.app/lucidchart/invitations/accept/inv_ab72e218-3ad9-4604-a7cd-7e0b0c259aa2 Once they are edited, you can re-import them here by replacing the old diagrams in the docs/diagrams directory on GitHub. As long as the names are the same, you shouldn't need to edit those docs! Found a bug? Edit this page on GitHub.","breadcrumbs":"Design » Rust Component's Strategy » Future: Only one implementation for each sync engine","id":"258","title":"Future: Only one implementation for each sync engine"},"259":{"body":"Some application-services components collect telemetry using the Glean SDK . Products that send telemetry via Glean must request a data-review following the Firefox Data Collection process before integrating any of the components listed below. Found a bug? Edit this page on GitHub.","breadcrumbs":"Design » Metrics - (Glean Telemetry) » Metrics collected by Application Services components","id":"259","title":"Metrics collected by Application Services components"},"26":{"body":"The easiest way to disable local development is to simply revert any changes to firefox-ios/Client.xcodeproj/project.pbxproj. However, if there are other changes to the file that you would like to preserve, you can use the same script. To use the same script, you will need to: Know what version of rust-components-swift was used beforehand. You can find this by checking the git diff on firefox-ios/Client.xcodeproj/project.pbxproj. Run: $ ./rust_components_local.sh --disable ../rust-components-swift Then, make sure to reset packages cache in Xcode. This forces Xcode to remove any previously cached versions of the Rust components. You can reset package caches by going to File -> Packages -> Reset Package Caches If you happen to change branches in rust-components-swift, you will need to disable then re-enable local development. The script is not currently smart enough to switch branches. Alternatively, keep the branch in rust-components-swift the same. rust-components-swift serves only as a release surface so there is little use to switching branches and pushing changes to it, unless you are changing something related to the release process.","breadcrumbs":"Contributing » Building » How to use the local development autopublish flow for Firefox iOS » Disabling local development","id":"26","title":"Disabling local development"},"260":{"body":"Like almost all Rust projects, the entire point of the application-services components is that they be used by external projects. If these components use Rust features available in only the very latest Rust version, this will cause problems for projects which aren't always able to be on that latest version. Given application-services is currently developed and maintained by Mozilla staff, it should be no surprise that an important consideration is mozilla-central (aka, the main Firefox repository).","breadcrumbs":"Design » Rust Version Policy » Rust Versions","id":"260","title":"Rust Versions"},"261":{"body":"It should also come as no surprise that the Rust policy for mozilla-central is somewhat flexible. There is an official Rust Update Policy Document but everything in the future is documented as \"estimated\". Ultimately though, that page defines 2 Rust versions - \"Uses\" and \"Requires\", and our policy revolves around these. To discover the current, actual \"Uses\" version, there is a Meta bug on Bugzilla that keeps track of the latest versions as they are upgraded. To discover the current, actual \"Requires\" version, see searchfox","breadcrumbs":"Design » Rust Version Policy » Mozilla-central Rust policies.","id":"261","title":"Mozilla-central Rust policies."},"262":{"body":"Our official Rust version policy is: All components will ship using, have all tests passing, and have clippy emit no warnings, with the same version mozilla-central currently \"uses\". All components must be capable of building (although not necessarily with all tests passing nor without clippy errors or other warnings) with the same version mozilla-central currently \"requires\". This policy only applies to the \"major\" and \"minor\" versions - a different patch level is still considered compliant with this policy.","breadcrumbs":"Design » Rust Version Policy » application-services Rust version policy","id":"262","title":"application-services Rust version policy"},"263":{"body":"All CI for this project will try and pin itself to this same version. At time of writing, this means that our circle CI integration and rust-toolchain configuration will specify the versions (and where possible, the CI configuration file will avoid duplicating the information in rust-toolchain) We should maintain CI to ensure we still build with the \"Requires\" version. As versions inside mozilla-central change, we will bump these versions accordingly. While newer versions of Rust can be expected to work correctly with our existing code, it's likely that clippy will complain in various ways with the new version. Thus, a PR to bump the minimum version is likely to also require a PR to make changes which keep clippy happy. In the interests of avoiding redundant information which will inevitably become stale, the circleci and rust-toolchain configuration links above should be considered the canonical source of truth for the currently supported official Rust version. Found a bug? Edit this page on GitHub.","breadcrumbs":"Design » Rust Version Policy » Implications of this","id":"263","title":"Implications of this"},"264":{"body":"The data below has been added as a tool for future pragma analysis work and is expected to be useful so long as our pragma usage remains stable or this doc is kept up-to-date. This should help us understand our current pragma usage and where we may be able to make improvements. Pragma Value Component Notes cache_size -6144 places foreign_keys ON autofill, places, tabs, webext-storage journal_mode WAL autofill, places, tabs, webext-storage page_size 32768 places secure_delete true logins temp_store 2 autofill, logins, places, tabs, webext_storage Setting temp_store to 2 (MEMORY) is necessary to avoid SQLITE_IOERR_GETTEMPPATH errors on Android (see here for details) wal_autocheckpoint 62 places wal_checkpoint PASSIVE places Used in the sync finished step in history and bookmarks syncing and in the places run_maintenance function The user_version pragma is excluded because the value varies and sqlite does not do anything with the value. The push component does not implement any of the commonly used pragmas noted above. The sqlcipher pragmas that we set have been excluded from this list as we are trying to remove sqlcipher and do not want to encourage future use. Found a bug? Edit this page on GitHub.","breadcrumbs":"Design » Sqlite Database Pragma Usage » Sqlite Database Pragma Usage","id":"264","title":"Sqlite Database Pragma Usage"},"265":{"body":"","breadcrumbs":"Releases » Application Services Release Process","id":"265","title":"Application Services Release Process"},"266":{"body":"Nightly builds are automatically generated using a taskcluster cron task. The results of the latest successful nightly build is listed here: https://firefox-ci-tc.services.mozilla.com/tasks/index/project.application-services.v2.nightly/latest The latest nightly decision task should be listed here: https://firefox-ci-tc.services.mozilla.com/tasks/index/project.application-services.v2.branch.main.latest.taskgraph/decision-nightly If you don't see a decision task from the day before, then contact releng. It's likely that the cron decision task is broken.","breadcrumbs":"Releases » Nightly builds","id":"266","title":"Nightly builds"},"267":{"body":"Release builds are generated from the release-vXXX branches and triggered in Ship-it Whenever a commit is pushed to a release branch, we build candidate artifacts. These artifacts are shippable -- if we decide that the release is ready, they just need to be copied to the correct location. The push phase of release-promotion copies the candidate to a staging location where they can be tested. The ship phase of release-promotion copies the candidate to their final, published, location.","breadcrumbs":"Releases » Release builds","id":"267","title":"Release builds"},"268":{"body":"This part is 100% covered by the Release Management team. The dev team should not perform these steps. On Merge Day we take a snapshot of the current main, and prepare a release. See Firefox Release Calendar . Create a branch name with the format releases-v[release_version] off of the main branch (for example, release-v118) through the GitHub UI. [release_version] should follow the Firefox release number. See Firefox Release Calendar . Create a PR against the release branch that updates version.txt and updates the CHANGELOG.md as follows: In version.txt , update the version from [release_version].0a1 to [release_version].0. diff --git a/version.txt b/version.txt\n--- a/version.txt\n+++ b/version.txt\n@@ -1 +1 @@\n-118.0a1\n+118.0 In CHANGELOG.md , change In progress to _YYYY-MM-DD_ to match the Merge Day date and add a URL to the release version change log. diff --git a/CHANGELOG.md b/CHANGELOG.md\nindex 7f2c07a1a8..06688fdcab 100644\n--- a/CHANGELOG.md\n+++ b/CHANGELOG.md\n@@ -1,8 +1,7 @@\n-# v118.0 (In progress)\n-\n-[Full Changelog](In progress)\n+# v118.0 (_2023-08-28_) ## General\n+ ### 🦊 What's Changed 🦊 - Backward-incompatible changes to the Suggest database schema to accommodate custom details for providers ([#5745](https://github.com/mozilla/application-services/pull/5745)) and future suggestion types ([#5766](https://github.com/mozilla/application-services/pull/5766)). This only affects prototyping, because we aren't consuming Suggest in any of our products yet.\n@@ -16,7 +15,6 @@ - The Remote Settings client has a new `Client::get_records_with_options()` method ([#5764](https://github.com/mozilla/application-services/pull/5764)). This is for Rust consumers only; it's not exposed to Swift or Kotlin. - `RemoteSettingsRecord` objects have a new `deleted` property that indicates if the record is a tombstone ([#5764](https://github.com/mozilla/application-services/pull/5764)). - ## Rust log forwarder ### 🦊 What's Changed 🦊 @@ -34,6 +32,8 @@ - Removed previously deprecated commands `experimenter`, `ios`, `android`, `intermediate-repr` ([#5784](https://github.com/mozilla/application-services/pull/5784)). +[Full Changelog](https://github.com/mozilla/application-services/compare/v117.0...v118.0)\n+ # v117.0 (_2023-07-31_) Create a commit named 'Cut release v[release_version].0` and a PR for this change. See example PR Create a PR against the main branch that updates version.txt and updates the CHANGELOG.md as follows: In version.txt , update the version from [release_version].0a1 to [next_release_version].0a1. diff --git a/version.txt b/version.txt\n--- a/version.txt\n+++ b/version.txt\n@@ -1 +1@@\n-118.0a1\n+119.0a1 In CHANGELOG.md , change the in progress version from [release_version].0 to [next_release_version].0, add a header for the previous release version, and add a URL to the previous release version change log. diff --git a/CHANGELOG.md b/CHANGELOG.md\n--- a/CHANGELOG.md\n+++ b/CHANGELOG.md\n@@ -1,8 +1,7 @@\n-# v118.0 (In progress)\n+# v119.0 (In progress) [Full Changelog](In progress) +# v118.0 (_2023-08-28_)\n@@ -34,6 +36,8 @@\n+[Full Changelog](https://github.com/mozilla/application-services/compare/v117.0...v118.0)\n+\n# v117.0 (_2023-07-31_) Create a commit named 'Start release v[next_release_version].0` and a PR for this change. See example PR Once all of the above PRs have landed, create a new Application Services release in Ship-It. Promote and Ship the release. Tag the release in the Application Services repo. Inform the Application Services team to cut a release of rust-components-swift The team will tag the repo and let you know the git hash to use when updating the consumer applications Update consumer applications firefox-android: Follow the directions in the release checklist firefox-ios: Follow the directions in the release checklist","breadcrumbs":"Releases » [Release management] Creating a new release","id":"268","title":"[Release management] Creating a new release"},"269":{"body":"Run pip3 install -r automation/requirements.txt to install the required Python packages. Run the automation/prepare-release.py script. This should: Create a new branch named release-vXXX Create a PR against that branch that updates version.txt like this: diff --git a/version.txt b/version.txt\nindex 8cd923873..6482018e0 100644\n--- a/version.txt\n+++ b/version.txt\n@@ -1,4 +1,4 @@\n-114.0a1\n+114.0 Create a PR on main that starts a new CHANGELOG header. Tag the release with automation/tag-release.py [major-version-number]","breadcrumbs":"Releases » [Release management] Creating a new release via scripts:","id":"269","title":"[Release management] Creating a new release via scripts:"},"27":{"body":"It's important to note the automated flow runs through all the necessary steps in a script, so if possible use the script as it's a tedious manual process However, if the script is failing or you would like to run the manual process for any other reason follow the following steps.","breadcrumbs":"Contributing » Building » How to use the local development autopublish flow for Firefox iOS » Using the manual flow","id":"27","title":"Using the manual flow"},"270":{"body":"If you want to uplift changes into the a previous release: Make sure the changes are present in main and have been thoroughly tested. Checkout the release-vXXX branch, where XXX is the major version number. Bump the version in version.txt Cherry-pick any commits that you want to uplift Create a PR for the changes Trigger release-promotion for the branch once the PRs are approved, merged, and CI has completed","breadcrumbs":"Releases » Cutting patch releases for uplifted changes","id":"270","title":"Cutting patch releases for uplifted changes"},"271":{"body":"We build several artifacts for both nightlies and releases: nightly.json / release.json. This is a JSON file containing metadata from successful builds. The metadata for the latest successful build can be found from a taskcluster index: https://firefox-ci-tc.services.mozilla.com/api/index/v1/task/project.application-services.v2.release.latest/artifacts/public%2Fbuild%2Frelease.json The JSON file contains: The version number for the nightly/release The git commit ID The maven channel for Kotlin packages: maven-production: https://maven.mozilla.org/?prefix=maven2/org/mozilla/appservices/ maven-nightly-production: https://maven.mozilla.org/?prefix=maven2/org/mozilla/appservices/nightly/ maven-staging: https://maven-default.stage.mozaws.net/?prefix=maven2/org/mozilla/appservices/ maven-nightly-staging: https://maven-default.stage.mozaws.net/?prefix=maven2/org/mozilla/appservices/nightly/ Links to nimbus-fml.*: used to build Firefox/Focus on Android and iOS Links to *RustComponentsSwift.xcframework.zip: XCFramework archives used to build Firefox/Focus on iOS Link to swift-components.tar.xz: UniFFI-generated swift files which get extracted into the rust-components-swift repository for each release.","breadcrumbs":"Releases » What gets built in a release?","id":"271","title":"What gets built in a release?"},"272":{"body":"For nightly builds, consumers get the artifacts directly from the taskcluster. For, firefox-android, the nightlies are handled by relbot For, firefox-ios, the nightlies are consumed by rust-components-swift . rust-components-swift makes a github release, which is picked up by a Github action in firefox-ios","breadcrumbs":"Releases » Nightly builds","id":"272","title":"Nightly builds"},"273":{"body":"For real releases, we use the taskcluster release-promotion action. Release promotion happens in two phases: promote copies the artifacts from taskcluster and moves them to a staging area. This allows for testing the consumer apps with the artifacts. ship copies the artifacts from the staging area to archive.mozilla.org, which serves as their permanent storage area. Found a bug? Edit this page on GitHub.","breadcrumbs":"Releases » Release promotion","id":"273","title":"Release promotion"},"274":{"body":"This document provides an overview of the build-and-publish pipeline used to make our work in this repo available to consuming applications. It's intended both to document the pipeline for development and maintenance purposes, and to serve as a basic analysis of the integrity protections that it offers (so you'll notice there are notes and open questions in place where we haven't fully hashed out all those details). The key points: We use \"stable\" Rust . CI is pinned to whatever version is currently used on mozilla-central to help with vendoring into that repository. You should check what current values are specified for CircleCI and for TaskCluster We use Cargo for building and testing the core Rust code in isolation, Gradle with rust-android-gradle for combining Rust and Kotlin code into Android components and running tests against them, and XCframeworks driving XCode for combining Rust and Swift code into iOS components. TaskCluster runs on every pull-request, release, and push to main, to ensure Android artifacts build correctly and to execute their tests via gradle. CircleCI runs on every branch, pull-request (including forks), and release, to execute lint checks and automated tests at the Rust and Swift level. Releases align with the Firefox Releases schedules, and nightly releases are automated to run daily see the releases for more information Notifications about build failures are sent to a mailing list at a-s-ci-failures@mozilla.com Our Taskcluster implementation is almost entirely maintained by the Release Engineering team. The proper way to contact them in case of emergency or for new developments is to ask on the #releaseduty-mobile Slack channel. Our main point of contact is @mihai. For Android consumers these are the steps by which Application Services code becomes available, and the integrity-protection mechanisms that apply at each step: Code is developed in branches and lands on main via pull request. GitHub branch protection prevents code being pushed to main without review. CircleCI and TaskCluster run automated tests against the code, but do not have the ability to push modified code back to GitHub thanks to the above branch protection. TaskCluster jobs do not run against PRs opened by the general public, only for PRs from repo collaborators. Contra the github org security guidelines , signing of individual commits is encouraged but is not required . Our experience in practice has been that this adds friction for contributors without sufficient tangible benefit. Developers manually create a release from latest main. The ability to create new releases is managed entirely via github's permission model. TODO: the github org security guidelines recommend signing tags, and auditing all included commits as part of the release process. We should consider some tooling to support this. I don't think there's any way to force githib to only accept signed releases in the same way it can enforce signed commits. TaskCluster checks out the release tag, builds it for all target platforms, and runs automated tests. These tasks run in a pre-built docker image, helping assure integrity of the build environment. TODO: could this step check for signed tags as an additional integrity measure? TaskCluster uploads symbols to Socorro. The access token for this is currently tied to @eoger's LDAP account. TaskCluster uploads built artifacts to maven.mozilla.org Secret key for uploading to maven is provisioned via TaskCluster, guarded by a scope that's only available to this task. TODO: could a malicious dev dependency from step (3) influence the build environment here? TODO: talk about how TC's \"chain of trust\" might be useful here. Consumers fetch the published artifacts from maven.mozilla.org. For iOS consumers the corresponding steps are: Code is developed in branches and lands on main via pull request, as above. Developers manually create a release from latest main, as above. CircleCI checks out the release tag, builds it, and runs automated tests. TODO: These tasks bootstrap their build environment by fetching software over https. could we do more to ensure the integrity of the build environment? TODO: could this step check for signed tags as an additional integrity measure? TODO: can we prevent these steps from being able to see the tokens used for publishing in subsequent steps? CircleCI builds a binary artifact: An XCFramework containing just Rust code and header files, as a zipfile, for use by Swift Packags. TODO: could a malicious dev dependency from step (3) influence the build environment here? CircleCI uses dpl to publish to GitHub as a release artifact. See Authentication and secrets below Consumers add Application services as a dependency from the Rust Components Swift repo using Apple's Swift Package Manager. For consuming in mozilla-central, see how to vendor components into mozilla-central This is a diagram of the pipeline as it exists (and is planned) for the Nimbus SDK, one of the libraries in Application Services: (Source: https://miro.com/app/board/o9J_lWx3jhY=/) Nimbus SDK Build and Publish Pipeline","breadcrumbs":"Releases » CI Publishing tools and flow » Application Services Build and Publish Pipeline","id":"274","title":"Application Services Build and Publish Pipeline"},"275":{"body":"","breadcrumbs":"Releases » CI Publishing tools and flow » Authentication and secrets","id":"275","title":"Authentication and secrets"},"276":{"body":"There's an appsvc-moz github account owned by one of the application-services team (currently markh, but we should consider rotating ownership). Given only 1 2fa device can be connected to a github account, multiple owners doesn't seem practical. In most cases, whenever a github account needs to own a secret for any CI, it will be owned by this account.","breadcrumbs":"Releases » CI Publishing tools and flow » @appsvc-moz account","id":"276","title":"@appsvc-moz account"},"277":{"body":"CircleCI config requires a github token (owned by @appsvc-moz). This is a \"personal access token\" obtained via github's Settings -> Developer Settings -> Personal Access Tokens -> Classic Token. This token: Should be named something like \"circleci\" Have \"no expiration\" (XXX - this seems wrong, should we adjust?) Once you have generated the token, it must be added to https://app.circleci.com/settings/project/github/mozilla/application-services/environment-variables as the environment variable GITHUB_TOKEN Found a bug? Edit this page on GitHub.","breadcrumbs":"Releases » CI Publishing tools and flow » CircleCI","id":"277","title":"CircleCI"},"278":{"body":"Our components rely on cryptographic primitives provided by NSS . Every month or so, a new version of NSS is published and we should try to keep our version as up-to-date as possible. Because it makes unit testing easier on Android, and helps startup performance on iOS, we compile NSS ourselves and link to it statically. Note that NSS is mainly used by Mozilla as a dynamic library and the NSS project is missing related CI jobs (iOS builds, windows cross-compile builds etc.) so you should expect breakage when updating the library (hence this guide).","breadcrumbs":"Releases » How to upgrade NSS » Guide to upgrading NSS","id":"278","title":"Guide to upgrading NSS"},"279":{"body":"The build code is located in the libs/ folder. The version string is located in the beginning of build-all.sh . For most NSS upgrades, you'll need to bump the version number in this file and update the downloaded archive checksum. Then follow the steps for Updating the cross-compiled NSS Artifacts below. The actual build invocations are located in platform-specific script files (e.g. build-nss-ios.sh ) but usually don't require any changes. To test out updating NSS version: Ensure you've bumped the NSS in build-all.sh Clear any old NSS build artifacts: rm -rf ./libs/desktop && cargo clean Install the updates version: ./libs/verify-desktop-environment.sh Try it out: cargo test","breadcrumbs":"Releases » How to upgrade NSS » Updating the Version","id":"279","title":"Updating the Version"},"28":{"body":"To build the xcframework do the following: In your local checkout of application-services, navigate to megazords/ios-rust/ Run the build-xcframework.sh script: $ ./build-xcframework.sh This will produce a file name MozillaRustComponents.xcframework.zip that contains the following, built for all our target iOS platforms. The compiled Rust code for all the crates listed in Cargo.toml as a static library The C header files and Swift module maps for the components","breadcrumbs":"Contributing » Building » How to use the local development autopublish flow for Firefox iOS » Building the xcframework","id":"28","title":"Building the xcframework"},"280":{"body":"We use a Linux TC worker for cross-compiling NSS for iOS, Android and Linux desktop machines. However, due to the complexity of the NSS build process, there is no easy way for cross-compiling MacOS and Windows -- so we currently use pre-built artifacts for MacOS desktop machines (ref #5210 ). Look for the tagged version from the NSS CI usually a description with something like Added tag NSS_3_90_RTM Select the build for the following system(s) (first task with the title \"B\"): For Intel MacOS: mac opt-static Update taskcluster/ci/fetch/kind.yml , specifically nss-artifact task to the appropriate url and checksum and size Note: To get the checksum, you can run shasum -a 256 {path-to-artifact} or you can make a PR and see the output of the failed log. Update the SHA256 value for darwin cross-compile in libs/build-nss-desktop.sh to the same checksum as above. Open a pull request with these changes and it should update the Taskcluster artifact","breadcrumbs":"Releases » How to upgrade NSS » Updating the Cross-Compiled NSS Artifacts","id":"280","title":"Updating the Cross-Compiled NSS Artifacts"},"281":{"body":"If the new version of NSS comes with new functions that you want to expose, you will need to: Add low-level bindings for those functions in the nss_sys crate ; follow the instructions in README for that crate. Expose a safe wrapper API for the functions from the nss crate ; Expose a convenient high-level API for the functions from the rc_crypto crate ;","breadcrumbs":"Releases » How to upgrade NSS » Exposing new functions","id":"281","title":"Exposing new functions"},"282":{"body":"On top of the primitives provided by NSS, we have built a safe Rust wrapper named rc_crypto that links to NSS and makes these cryptographic primitives available to our components. The linkage is done by the nss_build_common crate. Note that it supports a is_gecko feature to link to NSS dynamically on Desktop. Because the NSS static build process does not output a single .a file (it would be great if it did), this file must describe for each architecture which modules should we link against. It is mostly a duplication of logic from the NSS gyp build files . Note that this logic is also duplicated in our NSS lib build steps (e.g. build-nss-desktop.sh ). One of the most common build failures we get when upgrading NSS comes from NSS adding new vectorized/asm versions of a crypto algorithm for a specific architecture in order to improve performance. This new optimized code gets implemented as a new gyp target/module that is emitted only for the supported architectures. When we upgrade our copy of NSS we notice the linking step failing on CI jobs because of undefined symbols. This PR shows how we update nss_common_build and the build scripts to accommodate for these new modules. Checking the changelog for any suspect commit relating to hardware acceleration is rumored to help. Found a bug? Edit this page on GitHub.","breadcrumbs":"Releases » How to upgrade NSS » Tips for Fixing Bustage","id":"282","title":"Tips for Fixing Bustage"},"283":{"body":"fxa_client - Rust<link rel=\"stylesheet\" media=\"(prefers-color-scheme:light)\" href=\"../static.files/light-0f8c037637f9eb3e.css\"><link rel=\"stylesheet\" media=\"(prefers-color-scheme:dark)\" href=\"../static.files/dark-1097f8e92a01e3cf.css\"><link rel=\"stylesheet\" href=\"../static.files/noscript-13285aec31fa243e.css\">☰Crate fxa_clientVersion 0.1.0All ItemsStructsEnumsType Definitions?Crate fxa_clientsource · [−]Expand descriptionFirefox Accounts Client\nThe fxa-client component lets applications integrate with the\nFirefox Accounts\nidentity service. The shape of a typical integration would look\nsomething like: Out-of-band, register your application with the Firefox Accounts service,\nproviding an OAuth redirect_uri controlled by your application and\nobtaining an OAuth client_id. On application startup, create a FirefoxAccount object to represent the\nsigned-in state of the application. On first startup, a new FirefoxAccount can be created by calling\nFirefoxAccount::new and passing the application’s client_id.\nFor subsequent startups the object can be persisted using the\nto_json method and re-created by\ncalling FirefoxAccount::from_json. When the user wants to sign in to your application, direct them through\na web-based OAuth flow using begin_oauth_flow\nor begin_pairing_flow; when they return\nto your registered redirect_uri, pass the resulting authorization state back to\ncomplete_oauth_flow to sign them in. Display information about the signed-in user by using the data from\nget_profile. Access account-related services on behalf of the user by obtaining OAuth\naccess tokens via get_access_token. If the user opts to sign out of the application, calling disconnect\nand then discarding any persisted account data. StructsAccessTokenInfoAn OAuth access token, with its associated keys and metadata.AttachedClientA client connected to the user’s account.AuthorizationInfoInformation about the authorization state of the application.AuthorizationParametersParameters provided in an incoming OAuth request.DeviceA device connected to the user’s account.DevicePushSubscriptionDetails of a web-push subscription endpoint.FirefoxAccountObject representing the signed-in state of an application.FxaConfigLocalDeviceLocal device that’s connecting to FxAMetricsParamsAdditional metrics tracking parameters to include in an OAuth request.ProfileInformation about the user that controls a Firefox Account.ScopedKeyA cryptograpic key associated with an OAuth scope.SendTabPayloadThe payload sent when invoking a “send tab” command.TabHistoryEntryAn individual entry in the navigation history of a sent tab.EnumsAccountEventAn event that happened on the user’s account.DeviceCapabilityA “capability” offered by a device.DeviceTypeEnumeration for the different types of device.ErrorFxA internal error type\nThese are used in the internal code. This error type is never returned to the consumer.FxaErrorPublic error type thrown by many [FirefoxAccount] operations.FxaRustAuthStateHigh-level view of the authorization stateFxaServerIncomingDeviceCommandA command invoked by another device.Type DefinitionsApiResultResult returned by public-facing API functionsResultResult returned by internal functions\nFound a bug? Edit this page on GitHub.","breadcrumbs":"Rustdocs for components","id":"283","title":"Rustdocs for components"},"284":{"body":"The documentation in this repository pertains to the application-services library, primarily the sync and storage components, firefox account client and the nimbus-sdk experimentation client. The markdown is converted to static HTML using mdbook . To add a new document, you need to add it to the SUMMARY.md file which produces the sidebar table of contents.","breadcrumbs":"Adding to these documents » Developing documentation","id":"284","title":"Developing documentation"},"285":{"body":"","breadcrumbs":"Adding to these documents » Building documentation","id":"285","title":"Building documentation"},"286":{"body":"The mdbook crate is required in order to build the documentation: cargo install mdbook mdbook-mermaid mdbook-open-on-gh The repository documents are be built with: ./tools/build.docs.sh The built documentation is saved in build/docs/book. Found a bug? Edit this page on GitHub.","breadcrumbs":"Adding to these documents » Building the narrative (book) documentation","id":"286","title":"Building the narrative (book) documentation"},"29":{"body":"After you generated the MozillaRustComponents.xcframework.zip in the previous step, do the following to include it in a local checkout of rust-components-swift. The file will be in the megazords/ios-rust directory. Unzip the MozillaRustComponents.xcframework.zip into the rust-components-swift repository: (Assuming you are in the root of the rust-components-swift directory and application-services is a neighbor directory) unzip -o ../application-services/megazords/ios-rust/MozillaRustComponents.xcframework.zip -d . Change the Package.swift's reference to the xcframework to point to the unzipped MozillaRustComponents.xcframework that was created in the previous step. You can do this by uncommenting the following line: path: \"./MozillaRustComponents.xcframework\" and commenting out the following lines: url: url, checksum: checksum,","breadcrumbs":"Contributing » Building » How to use the local development autopublish flow for Firefox iOS » Include the xcframework in a local checkout of rust-components-swift","id":"29","title":"Include the xcframework in a local checkout of rust-components-swift"},"3":{"body":"Anyone is welcome to help with the Application Services project. Feel free to get in touch with other community members on Matrix or through issues on GitHub. Participation in this project is governed by the Mozilla Community Participation Guidelines .","breadcrumbs":"Contributing » Contributing to Application Services","id":"3","title":"Contributing to Application Services"},"30":{"body":"For this step, run the following script from inside the rust-components-swift repository (assuming that application-services is a neighboring directory to rust-components-swift). ./generate.sh ../application-services Once that is done, stage and commit the changes the script ran. Xcode can only pick up committed changes.","breadcrumbs":"Contributing » Building » How to use the local development autopublish flow for Firefox iOS » Run the generation script with a local checkout of application services","id":"30","title":"Run the generation script with a local checkout of application services"},"31":{"body":"This is the final step to include your local changes into firefox-ios. Do the following steps: Open Client.xcodeproj in Xcode Navigate to the Swift Packages in Xcode: Screenshot of where to find the setting for Client Remove the dependency on rust-components-swift as listed on Xcode, you can click the dependency then click the - Add a new swift package by clicking the +: On the top right, enter the full path to your rust-components-swift checkout, preceded by file://. If you don't know what that is, run pwd in while in rust-components-swift. For example: file:///Users/tarikeshaq/code/rust-components-swift Change the branch to be the checked-out branch of rust-component-swift you have locally. This is what the dialog should look like: Dialog for including the rust-components-swift package Note: If Xcode prevents you from adding the dependency to reference a local package, you will need to manually modify the Client.xcodeproj/project.pbxproj and replace every occurrence of https://github.com/mozilla/rust-components-swift with the full path to your local checkout. Click Add Package Now include the packages you would like to include, choose MozillaAppServices Finally, attempt to build firefox-ios, and if all goes well it should launch with your code. If you face problems, feel free to contact us Found a bug? Edit this page on GitHub.","breadcrumbs":"Contributing » Building » How to use the local development autopublish flow for Firefox iOS » Include the local checkout of rust-components-swift in firefox-ios","id":"31","title":"Include the local checkout of rust-components-swift in firefox-ios"},"32":{"body":"This is a guide on testing the Swift Package Manager component locally against a local build of Focus iOS. For more information on our Swift Package Manager design, read the ADR that introduced it This guide assumes the component you want to test is already distributed with the rust-components-swift repository, you can read the guide for adding a new component if you would like to distribute a new component. To test a component locally, you will need to do the following: Build an xcframework in a local checkout of application-services Include the xcframework in a local checkout of rust-components-swift Run the make-tag script in rust-components-swift using a local checkout of application-services Include the local checkout of rust-components-swift in Focus Below are more detailed instructions for each step","breadcrumbs":"Contributing » Building » How to use the local development flow for Focus for iOS » How to locally test Swift Package Manager components on Focus iOS","id":"32","title":"How to locally test Swift Package Manager components on Focus iOS"},"33":{"body":"To build the xcframework do the following: In a local checkout of application-services, navigate to megazords/ios-rust/ Run the build-xcframework.sh script: $ ./build-xcframework.sh --focus This will produce a file name FocusRustComponents.xcframework.zip in the focus directory that contains the following, built for all our target iOS platforms. The compiled Rust code for all the crates listed in Cargo.toml as a static library The C header files and Swift module maps for the components","breadcrumbs":"Contributing » Building » How to use the local development flow for Focus for iOS » Building the xcframework","id":"33","title":"Building the xcframework"},"34":{"body":"After you generated the FocusRustComponents.xcframework.zip in the previous step, do the following to include it in a local checkout of rust-components-swift: clone a local checkout of rust-components-swift, not inside the application-services repository: git clone https://github.com/mozilla/rust-components.swift.git Unzip the FocusRustComponents.xcframework.zip into the rust-components-swift repository: (Assuming you are in the root of the rust-components-swift directory and application-services is a neighbor directory) unzip -o ../application-services/megazords/ios-rust/focus/FocusRustComponents.xcframework.zip -d . Change the Package.swift's reference to the xcframework to point to the unzipped FocusRustComponents.xcframework that was created in the previous step. You can do this by uncommenting the following line: path: \"./FocusRustComponents.xcframework\" and commenting out the following lines: url: focusUrl, checksum: focusChecksum,","breadcrumbs":"Contributing » Building » How to use the local development flow for Focus for iOS » Include the xcframework in a local checkout of rust-components-swift","id":"34","title":"Include the xcframework in a local checkout of rust-components-swift"},"35":{"body":"For this step, run the following script from inside the rust-components-swift repository (assuming that application-services is a neighboring directory to rust-components-swift). ./generate.sh ../application-services Once that is done, stage and commit the changes the script ran. Xcode can only pick up committed changes.","breadcrumbs":"Contributing » Building » How to use the local development flow for Focus for iOS » Run the generation script with a local checkout of application services","id":"35","title":"Run the generation script with a local checkout of application services"},"36":{"body":"This is the final step to include your local changes into Focus. Do the following steps: Clone a local checkout of Focus if you haven't already. Make sure you also install the project dependencies, more information in their build instructions Open Blockzilla.xcodeproj in Xcode Navigate to the Swift Packages in Xcode: Screenshot of where to find the setting for Blockzilla Screenshot of where to find the package dependencies Remove the dependency on rust-components-swift as listed on Xcode, you can click the dependency then click the - Add a new swift package by clicking the +: On the top right, enter the full path to your rust-components-swift checkout, preceded by file://. If you don't know what that is, run pwd in while in rust-components-swift. For example: file:///Users/tarikeshaq/code/rust-components-swift Change the branch to be the checked-out branch of rust-component-swift you have locally. This is what the dialog should look like: Dialog for including the rust-components-swift package Click Add Package Now include the FocusAppServices library. Note: If Xcode prevents you from adding the dependency to reference a local package, you will need to manually modify the Blockzilla.xcodeproj/project.pbxproj and replace every occurrence of https://github.com/mozilla/rust-components-swift with the full path to your local checkout. Finally, attempt to build focus, and if all goes well it should launch with your code. If you face any problems, feel free to contact us Found a bug? Edit this page on GitHub.","breadcrumbs":"Contributing » Building » How to use the local development flow for Focus for iOS » Include the local checkout of rust-components-swift in Focus","id":"36","title":"Include the local checkout of rust-components-swift in Focus"},"37":{"body":"Java Native Access is an important dependency for the Application Services components on Android, as it provides the low-level interface from the JVM into the natively-compiled Rust code. If you need to work with a locally-modified version of JNA (e.g. to investigate an apparent JNA bug) then you may find these notes helpful. The JNA docs do have an Android Development Environment guide that is a good starting point, but the instructions did not work for me and appear a little out of date. Here are the steps that worked for me: Modify your environment to specify $NDK_PLATFORM, and to ensure the Android NDK tools for each target platform are in your $PATH. On my Mac with Android Studio the config was as follows: export NDK_ROOT=\"$HOME/Library/Android/sdk/ndk/25.2.9519653\"\nexport NDK_PLATFORM=\"$NDK_ROOT/platforms/android-25\"\nexport PATH=\"$PATH:$NDK_ROOT/toolchains/llvm/prebuilt/darwin-x86_64/bin\"\nexport PATH=\"$PATH:$NDK_ROOT/toolchains/aarch64-linux-android-4.9/prebuilt/darwin-x86_64/bin\"\nexport PATH=\"$PATH:$NDK_ROOT/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64/bin\"\nexport PATH=\"$PATH:$NDK_ROOT/toolchains/x86-4.9/prebuilt/darwin-x86_64/bin\"\nexport PATH=\"$PATH:$NDK_ROOT/toolchains/x86_64-4.9/prebuilt/darwin-x86_64/bin\" You will probably need to tweak the paths and version numbers based on your operating system and the details of how you installed the Android NDK. Install the ant build tool (using brew install ant worked for me). Checkout the JNA source from Github. Try doing a basic build via ant dist and ant test. This won't build for Android but will test the rest of the tooling. Adjust ./native/Makefile for compatibility with your Android NSK install. Here's what I had to do for mine: Adjust the $CC variable to use clang instead of gcc: CC=aarch64-linux-android21-clang. Adjust thd $CCP variable to use the version from your system: CPP=cpp. Add -landroid -llog to the list of libraries to link against in $LIBS. Build the JNA native libraries for the target platforms of interest: ant -Dos.prefix=android-aarch64 ant -Dos.prefix=android-armv7 ant -Dos.prefix=android-x86 ant -Dos.prefix=android-x86-64 Package the newly-built native libraries into a JAR/AAR using ant dist. This should produce ./dist/jna.aar. Configure build.gradle for the consuming application to use the locally-built JNA artifact: // Tell gradle where to look for local artifacts.\nrepositories { flatDir { dirs \"/PATH/TO/YOUR/CHECKOUT/OF/jna/dist\" }\n} // Tell gradle to exclude the published version of JNA.\nconfigurations { implementation { exclude group: \"net.java.dev.jna\", module:\"jna\" }\n} // Take a direct dependency on the local JNA AAR.\ndependencies { implementation name: \"jna\", ext: \"aar\"\n} Rebuild and run your consuming application, and it should be using the locally-built JNA! If you're trying to debug some unexpected JNA behaviour (and if you favour old-school printf-style debugging) then you can this code snippet to print to the Android log from the compiled native code: #ifdef __ANDROID__\n#include \n#define HACKY_ANDROID_LOG(...) __android_log_print(ANDROID_LOG_VERBOSE, \"HACKY-DEBUGGING-FOR-ALL\", __VA_ARGS__)\n#else\n#define HACKY_ANDROID_LOG(MSG)\n#endif HACKY_ANDROID_LOG(\"this will go to the android logcat output\");\nHACKY_ANDROID_LOG(\"it accepts printf-style format sequences, like this: %d\", 42); Found a bug? Edit this page on GitHub.","breadcrumbs":"Contributing » Building » How to locally build JNA » Building and using a locally-modified version of JNA","id":"37","title":"Building and using a locally-modified version of JNA"},"38":{"body":"Branch builds are a way to build and test Fenix using branches from application-services and firefox-android. iOS is not currently supported, although we may add it in the future (see #4966 ).","breadcrumbs":"Contributing » Building » Branch builds » Branch builds","id":"38","title":"Branch builds"},"39":{"body":"When we make breaking changes in an application-services branch, we typically make corresponding changes in an android-components branch. Branch builds allow combining those branches together in order to run CI tests and to produce APKs for manual testing. To trigger a branch build for this: Create the PR for the application-services branch you're working on Add [firefox-android: branch-name] to the PR title The branch build tasks will be listed as checks the Github PR. In particular: branch-build-fenix-test and branch-build-ac-test will run the unit android-components/fenix unit tests branch-build-fenix-build will contain the Fenix APK.","breadcrumbs":"Contributing » Building » Branch builds » Breaking changes in an application-services branch.","id":"39","title":"Breaking changes in an application-services branch."},"4":{"body":"You can file issues on GitHub . Please try to include as much information as you can and under what conditions you saw the issue.","breadcrumbs":"Contributing » Bug Reports","id":"4","title":"Bug Reports"},"40":{"body":"When we make non-breaking changes, we typically merge them into main and let them sit there until the next release. In order to check that the current main really does only have non-breaking changes, we run a nightly branch build from the main branch of application-services, To view the latest branch builds: Open the latest decision task from the task index. Click the \"View Task\" link Click \"Task Group\" in the top-left You should now see a list of tasks from the latest nightly *-build were for building the application. A failure here indicates there's probably a breaking change that needs to be resolved. To get the APK, navigate to branch-build-fenix-build and download app-x86-debug.apk from the artifacts list branch-build-ac-test.* are the android-components tests tasks. These are split up by gradle project, which matches how the android-components CI handles things. Running all the tests together often leads to failures. branch-build-fenix-test is the Fenix tests. These are not split up per-project. These builds are triggered by our .cron.yml file Found a bug? Edit this page on GitHub.","breadcrumbs":"Contributing » Building » Branch builds » Application-services nightlies","id":"40","title":"Application-services nightlies"},"41":{"body":"This document gives a high-level overview of how we test components in application-services. It will be useful to you if you're adding a new component, or working on increasing the test coverage of an existing component. If you are only interested in running the existing test suite, please consult the contributor docs and the tests.py script.","breadcrumbs":"Contributing » How to test Rust Components » Guide to Testing a Rust Component","id":"41","title":"Guide to Testing a Rust Component"},"42":{"body":"","breadcrumbs":"Contributing » How to test Rust Components » Unit and Functional Tests","id":"42","title":"Unit and Functional Tests"},"43":{"body":"Since the core implementations of our components live in rust, so does the core of our testing strategy. Each rust component should be accompanied by a suite of unit tests, following the guidelines for writing tests from the Rust Book . Some additional tips: Where possible, it's better use use the Rust typesystem to make bugs impossible than to write tests to assert that they don't occur in practice. But given that the ultimate consumers of our code are not in Rust, that's sometimes not possible. The best idiomatic Rust API for a feature is not necessarily the best API for consuming it over an FFI boundary. Rust's builtin assertion macros are sparse; we use the more_asserts for some additional helpers. Rust's strict typing can make test mocks difficult. If there's something you need to mock out in tests, make it a Trait and use the mockiato crate to mock it. The Rust tests for a component should be runnable via cargo test.","breadcrumbs":"Contributing » How to test Rust Components » Rust code","id":"43","title":"Rust code"},"44":{"body":"We are currently using uniffi to generate most ((and soon all!) of our FFI code and thus the FFI code itself does not need to be extensively tested.","breadcrumbs":"Contributing » How to test Rust Components » FFI Layer code","id":"44","title":"FFI Layer code"},"45":{"body":"The Kotlin wrapper code for a component should have its own test suite, which should follow the general guidelines for testing Android code in Mozilla projects . In practice that means we use JUnit as the test framework and Robolectric to provide implementations of Android-specific APIs. The Kotlin tests for a component should be runnable via ./gradlew :test. The tests at this layer are designed to ensure that the API binding code is working as intended, and should not repeat tests for functionality that is already well tested at the Rust level. But given that the Kotlin bindings involve a non-trivial amount of hand-written boilerplate code, it's important to exercise that code throughly. One complication with running Kotlin tests is that the code needs to run on your local development machine, but the Kotlin code's native dependencies are typically compiled and packaged for Android devices. The tests need to ensure that an appropriate version of JNA and of the compiled Rust code is available in their library search path at runtime. Our build.gradle files contain a collection of hackery that ensures this, which should be copied into any new components. The majority of our Kotlin bindings are autogenerated using uniffi and do not need extensive testing.","breadcrumbs":"Contributing » How to test Rust Components » Kotlin code","id":"45","title":"Kotlin code"},"46":{"body":"The Swift wrapper code for a component should have its own test suite, using Apple's Xcode unittest framework . Due to the way that all rust components need to be compiled together into a single \"megazord\" framework, this entire repository is a single Xcode project. The Swift tests for each component thus need to live under megazords/ios-rust/MozillaTestServicesTests/ rather than in the directory for the corresponding component. (XXX TODO: is this true? it would be nice to find a way to avoid having them live separately because it makes them easy to overlook). The tests at this layer are designed to ensure that the API binding code is working as intended, and should not repeat tests for functionality that is already well tested at the Rust level. But given that the Swift bindings involve a non-trivial amount of hand-written boilerplate code, it's important to exercise that code thoroughly. The majority of our Swift bindings are autogenerated using uniffi and do not need extensive testing.","breadcrumbs":"Contributing » How to test Rust Components » Swift code","id":"46","title":"Swift code"},"47":{"body":"","breadcrumbs":"Contributing » How to test Rust Components » Integration tests","id":"47","title":"Integration tests"},"48":{"body":"⚠️ Those tests were disabled because of how flakey the stage server was. See #3909 ⚠️ The testing/sync-test directory contains a test harness for running sync-related Rust components against a live Firefox Sync infrastructure, so that we can verifying the functionality end-to-end. Each component that implements a sync engine should have a corresponding suite of tests in this directory. XXX TODO: places doesn't. XXX TODO: send-tab doesn't (not technically a sync engine, but still, it's related) XXX TODO: sync-manager doesn't","breadcrumbs":"Contributing » How to test Rust Components » End-to-end Sync Tests","id":"48","title":"End-to-end Sync Tests"},"49":{"body":"It's important that changes in application-services are tested against upstream consumer code in the android-components repo. This is currently a manual process involving: Configuring your local checkout of android-components to use your local application-services build . Running the android-components test suite via ./gradle test. Manually building and running the android-components sample apps to verify that they're still working. Ideally some or all of this would be automated and run in CI, but we have not yet invested in such automation.","breadcrumbs":"Contributing » How to test Rust Components » Android Components Test Suite","id":"49","title":"Android Components Test Suite"},"5":{"body":"Build instructions are available in the building page. Please let us know if you encounter any pain-points setting up your environment.","breadcrumbs":"Contributing » Building the project","id":"5","title":"Building the project"},"50":{"body":"We currently have code coverage reporting on Github using codecov . However, our code coverage does not tell us how much more coverage is caused by our consumers' tests.","breadcrumbs":"Contributing » How to test Rust Components » Test Coverage","id":"50","title":"Test Coverage"},"51":{"body":"ASan, Memsan, and maybe other sanitizer checks, especially around the points where we cross FFI boundaries. General-purpose fuzzing, such as via https://github.com/jakubadamw/arbitrary-model-tests We could consider making a mocking backend for viaduct, which would also be mockable from Kotlin/Swift. Add more end-to-end integration tests! Live device tests, e.g. actual Fenixes running in an emulator and syncing to each other. Run consumer integration tests in CI against main. Found a bug? Edit this page on GitHub.","breadcrumbs":"Contributing » How to test Rust Components » Ideas for Improvement","id":"51","title":"Ideas for Improvement"},"52":{"body":"This is a great way of finding integration bugs with application-services. The testing can be done manually using substitution scripts, but we also have scripts that will do the smoke-testing for you.","breadcrumbs":"Contributing » How to test Rust Components » How to integration (smoke) test application-services » Smoke testing Application Services against end-user apps","id":"52","title":"Smoke testing Application Services against end-user apps"},"53":{"body":"Run pip3 install -r automation/requirements.txt to install the required Python packages.","breadcrumbs":"Contributing » How to test Rust Components » How to integration (smoke) test application-services » Dependencies","id":"53","title":"Dependencies"},"54":{"body":"The automation/smoke-test-android-components.py script will clone (or use a local version) of android-components and run a subset of its tests against the current application-services worktree. It tries to only run tests that might be relevant to application-services functionality.","breadcrumbs":"Contributing » How to test Rust Components » How to integration (smoke) test application-services » Android Components","id":"54","title":"Android Components"},"55":{"body":"The automation/smoke-test-fenix.py script will clone (or use a local version) of Fenix and run tests against the current application-services worktree.","breadcrumbs":"Contributing » How to test Rust Components » How to integration (smoke) test application-services » Fenix","id":"55","title":"Fenix"},"56":{"body":"The automation/smoke-test-fxios.py script will clone (or use a local version) of Firefox iOS and run tests against the current application-services worktree. Found a bug? Edit this page on GitHub.","breadcrumbs":"Contributing » How to test Rust Components » How to integration (smoke) test application-services » Firefox iOS","id":"56","title":"Firefox iOS"},"57":{"body":"","breadcrumbs":"Contributing » How to test Rust Components » Writing efficient tests » Testing faster: How to avoid making compile times worse by adding tests","id":"57","title":"Testing faster: How to avoid making compile times worse by adding tests"},"58":{"body":"We'd like to keep cargo test, cargo build, cargo check, ... reasonably fast, and we'd really like to keep them fast if you pass -p for a specific project. Unfortunately, there are a few ways this can become unexpectedly slow. The easiest of these problems for us to combat at the moment is the unfortunate placement of dev-dependencies in our build graph. If you perform a cargo test -p foo, all dev-dependencies of foo must be compiled before foo's tests can start. This includes dependencies only used non-test targets, such as examples or benchmarks. In an ideal world, cargo could run your tests as soon as it finished with the dependencies it needs for those tests, instead of waiting for your benchmark suite, or the arg-parser your examples use, or etc. Unfortunately, all cargo knows is that these are dev-dependencies, and not which targets actually use them. Additionally, unqualified invocations of cargo (that is, without -p) might have an even worse time if we aren't careful. If I run, cargo test, cargo knows every crate in the workspace needs to be built with all dev dependencies, if places depends on fxa-client, all of fxa-clients dev-dependencies must be compiled, ready, and linked in at least to the lib target before we can even think about starting on places. We have not been careful about what shape the dependency graph ends up as when example code is taken into consideration (as it is by cargo during certain builds), and as a result, we have this problem. Which isn't really a problem we want to fix: Example code can and should depend on several different components, and use them together in interesting ways. So, because we don't want to change what our examples do, or make major architectural changes of the non-test code for something like this, we need to do something else.","breadcrumbs":"Contributing » How to test Rust Components » Writing efficient tests » Background","id":"58","title":"Background"},"59":{"body":"To fix this, we manually insert \"cuts\" into the dependency graph to help cargo out. That is, we pull some of these build targets (e.g. examples, benchmarks, tests if they cause a substantial compile overhead) into their own dedicated crates so that: They can be built in parallel with each other. Crates depending on the component itself are not waiting on the test/bench/example build in order for their test build to begin. A potentially smaller set of our crates need to be rebuilt -- and a smaller set of possible configurations exist meaning fewer items to add pressure to caches. ... Some rules of thumb for when / when not to do this: All rust examples should be put in examples/*. All rust benchmarks should be put in testing/separated/*. See the section below on how to set your benchmark up to avoid redundant compiles. Rust tests which brings in heavyweight dependencies should be evaluated on an ad-hoc basis. If you're concerned, measure how long compilation takes with/without, and consider how many crates depend on the crate where the test lives (e.g. a slow test in support/foo might be far worse than one in a leaf crate), etc...","breadcrumbs":"Contributing » How to test Rust Components » Writing efficient tests » The Solution","id":"59","title":"The Solution"},"6":{"body":"Below are a few different queries you can use to find appropriate issues to work on. Feel free to reach out if you need any additional clarification before picking up an issue. good first issues - If you are a new contributor, search for issues labeled good-first-issue good second issues - Once you've got that first PR approved and you are looking for something a little more challenging, we are keeping a list of next-level issues. Search for the good-second-issue label. papercuts - A collection of smaller sized issues that may be a bit more advanced than a first or second issue. important, but not urgent - For more advanced contributors, we have a collection of issues that we consider important and would like to resolve sooner, but work isn't currently prioritized by the core team.","breadcrumbs":"Contributing » Finding issues","id":"6","title":"Finding issues"},"60":{"body":"To be clear, this is way more important for benchmarks (which always compile as release and have a costly link phase). Say you have a directory structure like the following: mycrate ├── src │ └── lib.rs | ... ├── benches │ ├── bench0.rs | ├── bench1.rs │ └── bench2.rs ├── tests │ ├── test0.rs | ├── test1.rs │ └── test2.rs └── ... When you run your integration tests or benchmarks, each of test0, test1, test2 or bench0, bench1, bench2 is compiled as it's own crate that runs the tests in question and exits. That means 3 benchmark executables are built on release settings, and 3 integration test executables. If you've ever tried to add a piece of shared utility code into your integration tests, only to have cargo (falsely) complain that it is dead code: this is why. Even if test0.rs and test2.rs both use the utility function, unless every test crate uses every shared utility, the crate that doesn't will complain. (Aside: This turns out to be an unintentional secondary benefit of this approach -- easier shared code among tests, without having to put a #![allow(dead_code)] in your utils.rs. We haven't hit that very much here, since we tend to stick to unit tests, but it came up in mentat several times, and is a frequent complaint people have) Anyway, the solution here is simple: Create a new crate. If you were working in components/mycrate and you want to add some integration tests or benchmarks, you should do cargo new --lib testing/separated/mycrate-test (or .../mycrate-bench). Delete .../mycrate-test/src/lib.rs. Yep, really, we're making a crate that only has integration tests/benchmarks (See the \"FAQ0\" section at the bottom of the file if you're getting incredulous). Now, add a src/tests.rs or a src/benches.rs. This file should contain mod foo; declarations for each submodule containing tests/benchmarks, if any. For benches, this is also where you set up the benchmark harness (refer to benchmark library docs for how). Now, for a test, add: into your Cargo.toml [[test]]\nname = \"mycrate-test\"\npath = \"src/tests.rs\" and for a benchmark, add: [[test]]\nname = \"mycrate-benches\"\npath = \"src/benches.rs\"\nharness = false Because we aren't using src/lib.rs, this is what declares which file is the root of the test/benchmark crate. Because there's only one target (unlike with tests/* / benches/* under default settings), this will compile more quickly. Additionally, src/tests.rs and src/benches.rs will behave like a normal crate, the only difference being that they don't produce a lib, and that they're triggered by cargo test/cargo run respectively.","breadcrumbs":"Contributing » How to test Rust Components » Writing efficient tests » Appendix: How to avoid redundant compiles for benchmarks and integration tests","id":"60","title":"Appendix: How to avoid redundant compiles for benchmarks and integration tests"},"61":{"body":"Instead of putting tests/benchmarks inside src, we could just delete the src dir outright, and place everything in tests/benches. Then, to get the same one-rebuild-per-file behavior that we'll get in src, we need to add autotests = false or autobenches = false to our Cargo.toml, adding a root tests/tests.rs (or benches/benches.rs) containing mod decls for all submodules, and finally by referencing that \"root\" in the Cargo.toml [[tests]] / [[benches]] list, exactly the same way we did for using src/*. This would work, and on the surface, using tests/*.rs and benches/*.rs seems more consistent, so it seems weird to use src/*.rs for these files. My reasoning is as follows: Almost universally, tests/*.rs, examples/*.rs, benches/*.rs, etc. are automatic. If you add a test into the tests folder, it will run without anything else. If we're going to set up one-build-per-{test,bench}suite as I described, this fundamentally cannot be true. In this paradigm, if you add a test file named blah.rs, you must add a mod blah it to the parent module. It seems both confusing and error-prone to use tests/*, but have it behave that way, however this is absolutely the normal behavior for files in src/*.rs -- When you add a file, you then need to add it to it's parent module, and this is something Rust programmers are pretty used to. (In fact, we even replicated this behavior (for no reason) in the places integration tests, and added the mod declarations to a \"controlling\" parent module -- It seems weird to be in an environment where this isn't required) So, that's why. This way, we make it way less likely that you add a test file to some directory, and have it get ignored because you didn't realize that in this one folder, you need to add a mod mytest into a neighboring tests.rs. Found a bug? Edit this page on GitHub.","breadcrumbs":"Contributing » How to test Rust Components » Writing efficient tests » FAQ0: Why put tests/benches in src/* instead of disabling autotests/autobenches","id":"61","title":"FAQ0: Why put tests/benches in src/* instead of disabling autotests/autobenches"},"62":{"body":"It can be quite tricky to debug what is going on with sql statement, especially once the sql gets complicated or many triggers are involved. The sql_support create provides some utilities to help. Note that these utilities are gated behind a debug-tools feature. The module provides docstrings, so you should read them before you start . This document describes how to use these capabilities and we'll use places as an example. First, we must enable the feature: --- a/components/places/Cargo.toml\n+++ b/components/places/Cargo.toml\n@@ -22,7 +22,7 @@ lazy_static = \"1.4\" url = { version = \"2.1\", features = [\"serde\"] } percent-encoding = \"2.1\" caseless = \"0.2\"\n-sql-support = { path = \"../support/sql\" }\n+sql-support = { path = \"../support/sql\", features=[\"debug-tools\"] } and we probably need to make the debug functions available: --- a/components/places/src/db/db.rs\n+++ b/components/places/src/db/db.rs\n@@ -108,6 +108,7 @@ impl ConnectionInitializer for PlacesInitializer { \"; conn.execute_batch(initial_pragmas)?; define_functions(conn, self.api_id)?;\n+ sql_support::debug_tools::define_debug_functions(conn)?; We now have a Rust function print_query() and a SQL function dbg() available. Let's say we were trying to debug a test such as test_bookmark_tombstone_auto_created. We might want to print the entire contents of a table, then instrument a query to check what the value of a query is. We might end up with a patch something like: index 28f19307..225dccbb 100644\n--- a/components/places/src/db/schema.rs\n+++ b/components/places/src/db/schema.rs\n@@ -666,7 +666,8 @@ mod tests { [], ) .expect(\"should insert regular bookmark folder\");\n- conn.execute(\"DELETE FROM moz_bookmarks WHERE guid = 'bookmarkguid'\", [])\n+ sql_support::debug_tools::print_query(&conn, \"select * from moz_bookmarks\").unwrap();\n+ conn.execute(\"DELETE FROM moz_bookmarks WHERE dbg('CHECKING GUID', guid) = 'bookmarkguid'\", []) .expect(\"should delete\"); // should have a tombstone. assert_eq!( There are 2 things of note: We used the print_query function to dump the entire moz_bookmarks table before executing the query. We instrumented the query to print the guid every time sqlite reads a row and compares it against a literal. The output of this test now looks something like: running 1 test\nquery: select * from moz_bookmarks\n+----+------+------+--------+----------+---------+---------------+---------------+--------------+------------+-------------------+\n| id | fk | type | parent | position | title | dateAdded | lastModified | guid | syncStatus | syncChangeCounter |\n+====+======+======+========+==========+=========+===============+===============+==============+============+===================+\n| 1 | null | 2 | null | 0 | root | 1686248350470 | 1686248350470 | root________ | 1 | 1 |\n+----+------+------+--------+----------+---------+---------------+---------------+--------------+------------+-------------------+\n| 2 | null | 2 | 1 | 0 | menu | 1686248350470 | 1686248350470 | menu________ | 1 | 1 |\n+----+------+------+--------+----------+---------+---------------+---------------+--------------+------------+-------------------+\n| 3 | null | 2 | 1 | 1 | toolbar | 1686248350470 | 1686248350470 | toolbar_____ | 1 | 1 |\n+----+------+------+--------+----------+---------+---------------+---------------+--------------+------------+-------------------+\n| 4 | null | 2 | 1 | 2 | unfiled | 1686248350470 | 1686248350470 | unfiled_____ | 1 | 1 |\n+----+------+------+--------+----------+---------+---------------+---------------+--------------+------------+-------------------+\n| 5 | null | 2 | 1 | 3 | mobile | 1686248350470 | 1686248350470 | mobile______ | 1 | 1 |\n+----+------+------+--------+----------+---------+---------------+---------------+--------------+------------+-------------------+\n| 6 | null | 3 | 1 | 0 | null | 1 | 1 | bookmarkguid | 2 | 1 |\n+----+------+------+--------+----------+---------+---------------+---------------+--------------+------------+-------------------+\ntest db::schema::tests::test_bookmark_tombstone_auto_created ... FAILED failures: ---- db::schema::tests::test_bookmark_tombstone_auto_created stdout ----\nCHECKING GUID root________\nCHECKING GUID menu________\nCHECKING GUID toolbar_____\nCHECKING GUID unfiled_____\nCHECKING GUID mobile______\nCHECKING GUID bookmarkguid It's unfortunate that the output of print_table() goes to the tty while the output of dbg goes to stderr, so you might find the output isn't quite intermingled as you would expect, but it's better than nothing! Found a bug? Edit this page on GitHub.","breadcrumbs":"Contributing » How to test Rust Components » How to debug SQL/sqlite » Debugging Sql","id":"62","title":"Debugging Sql"},"63":{"body":"This repository uses third-party code from a variety of sources, so we need to be mindful of how these dependencies will affect our consumers. Considerations include: General code quality. Licensing compatibility . Handling of security vulnerabilities. The potential for supply-chain compromise . We're still evolving our policies in this area, but these are the guidelines we've developed so far.","breadcrumbs":"Contributing » Dependency management » Dependency Management Guidelines","id":"63","title":"Dependency Management Guidelines"},"64":{"body":"Unlike Firefox , we do not vendor third-party source code directly into the repository. Instead we rely on Cargo.lock and its hash validation to ensure that each build uses an identical copy of all third-party crates. These are the measures we use for ongoing maintence of our existing dependencies: Check Cargo.lock into the repository. Generate built artifacts using the --locked flag to cargo build, as an additional assurance that the existing Cargo.lock will be respected. Regularly run cargo-audit in CI to alert us to security problems in our dependencies. It runs on every PR, and once per hour on the main branch Use a home-grown tool to generate a summary of dependency licenses and to check them for compatibility with MPL-2.0. Check these summaries into the repository and have CI alert on unexpected changes, to guard against pulling in new versions of a dependency under a different license. Adding a new dependency, whether we like it or not, is a big deal - that dependency and everything it brings with it will become part of Firefox-branded products that we ship to end users. We try to balance this responsibility against the many benefits of using existing code, as follows: In general, be conservative in adding new third-party dependencies. For trivial functionality, consider just writing it yourself. Remember the cautionary tale of left-pad . Check if we already have a crate in our dependency tree that can provide the needed functionality. Prefer crates that have a a high level of due-dilligence already applied, such as: Crates that are already vendored into Firefox . Crates from rust-lang-nursery . Crates that appear to be widely used in the rust community. Check that it is clearly licensed and is MPL-2.0 compatible . Take the time to investigate the crate's source and ensure it is suitably high-quality. Be especially wary of uses of unsafe, or of code that is unusually resource-intensive to build. Dev dependencies do not require as much scrutiny as dependencies that will ship in consuming applications, but should still be given some thought. There is still the potential for supply-chain compromise with dev dependencies! As part of the PR that introduces the new dependency: Regenerate dependency summary files using the regenerate_dependency_summaries.sh . Explicitly describe your consideration of the above points. Updating to new versions of existing dependencies is a normal part of software development and is not accompanied by any particular ceremony.","breadcrumbs":"Contributing » Dependency management » Rust Code","id":"64","title":"Rust Code"},"65":{"body":"We currently depend only on the following Kotlin dependencies: JNA protobuf-gradle-plugin We currently depend on the following developer dependencies in the Kotlin codebase, but they do not get included in built distribution files: detekt ktlint No additional Kotlin dependencies should be added to the project unless absolutely necessary.","breadcrumbs":"Contributing » Dependency management » Android/Kotlin Code","id":"65","title":"Android/Kotlin Code"},"66":{"body":"We currently do not depend on any Swift dependencies. And no Swift dependencies should be added to the project unless absolutely necessary.","breadcrumbs":"Contributing » Dependency management » iOS/Swift Code","id":"66","title":"iOS/Swift Code"},"67":{"body":"We currently depend on local builds of the following system dependencies: NSS and NSPR SQLCipher Protobuf No additional system dependencies should be added to the project unless absolutely necessary. Found a bug? Edit this page on GitHub.","breadcrumbs":"Contributing » Dependency management » Other Code","id":"67","title":"Other Code"},"68":{"body":"Each component in the Application Services repository has three parts (the Rust code, the Kotlin wrapper, and the Swift wrapper) so there are quite a few moving parts involved in adding a new component. This is a rapid-fire list of all the things you'll need to do if adding a new component from scratch.","breadcrumbs":"Contributing » How to add a new component » Adding a new component to Application Services","id":"68","title":"Adding a new component to Application Services"},"69":{"body":"Your component should live under ./components in this repo. Use cargo new --lib ./components/to create a new library crate, and please try to avoid using hyphens in the crate name. See the Guide to Building a Rust Component for general advice on designing and structuring the actual Rust code, and follow the Dependency Management Guidelines if your crate introduces any new dependencies. Use UniFFI to define how your crate's API will get exposed to foreign-language bindings. By convention, put the interface definition file at ./components//.udl. Use the builtin-bindgen feature of UniFFI to simplify the build process, by putting the following in your Cargo.toml: [build-dependencies]\nuniffi_build = { version = \"\", features=[\"builtin-bindgen\"] } Include your new crate in the application-services workspace, by adding it to the members and default-members lists in the Cargo.toml at the root of the repository. In order to be published to consumers, your crate must be included in the \"megazord\" crate for each target platform: For Android, add it as a dependency in ./megazords/full/Cargo.toml and add a pub use to ./megazords/full/src/lib.rs. For iOS, add it as a dependency in ./megazords/ios-rust/rust/Cargo.toml and add a pub use to ./megazords/ios-rust/src/lib.rs. Run cargo check -p in the repository root to confirm that things are configured properly. This will also have the side-effect of updating Cargo.lock to contain your new crate and its dependencies.","breadcrumbs":"Contributing » How to add a new component » The Rust Code","id":"69","title":"The Rust Code"},"7":{"body":"Patches should be submitted as pull requests (PRs). When submitting PRs, We expect external contributors to push patches to a fork of application-services . For more information about submitting PRs from forks, read GitHub's guide . Before submitting a PR: Your patch should include new tests that cover your changes, or be accompanied by explanation for why it doesn't need any. It is your and your reviewer's responsibility to ensure your patch includes adequate tests. Consult the testing guide for some tips on writing effective tests. Your code should pass all the automated tests before you submit your PR for review. Before pushing your changes, run ./automation/tests.py changes. The script will calculate which components were changed and run test suites, linters and formatters against those components. Because the script runs a limited set of tests, the script should execute in a fairly reasonable amount of time. If you have modified any Swift code, also run swiftformat --swiftversion 5 on the modified code. Your patch should include a changelog entry in CHANGELOG.md or an explanation of why it does not need one. Any breaking changes to Swift or Kotlin binding APIs should be noted explicitly. If your patch adds new dependencies, they must follow our dependency management guidelines . Please include a summary of the due diligence applied in selecting new dependencies. After you open a PR, our Continuous Integration system will run a full test suite. It's possible that this step will result in errors not caught with the script so make sure to check the results. \"Work in progress\" pull requests are welcome, but should be clearly labeled as such and should not be merged until all tests pass and the code has been reviewed. You can label pull requests as \"Work in progress\" by using the Github PR UI to indicate this PR is a draft ( learn more about draft PRs ). When submitting a PR: You agree to license your code under the project's open source license ( MPL 2.0 ). Base your branch off the current main branch. Add both your code and new tests if relevant. Please do not include merge commits in pull requests; include only commits with the new relevant code. We encourage you to GPG sign your commits .","breadcrumbs":"Contributing » Sending Pull Requests","id":"7","title":"Sending Pull Requests"},"70":{"body":"Make a ./components//android subdirectory to contain Kotlin- and Android-specific code. This directory will contain a gradle project for building your Kotlin bindings. Copy the build.gradle file from ./components/crashtest/android/ into your own component's directory, and edit it to replace the references to crashtest.udl with your own component's .udl file. Create a file ./components//uniffi.toml with the following contents: [bindings.kotlin]\npackage_name = \"mozilla.appservices.\"\ncdylib_name = \"megazord\" Create a file ./components//android/src/main/AndroidManifest.xml with the following contents: \" /> In the root of the repository, edit .buildconfig-android.ymlto add your component's metadata. This will cause it to be included in the gradle workspace and in our build and publish pipeline. Check whether it builds correctly by running: ./gradlew :assembleDebug You can include hand-written Kotlin code alongside the automatically generated bindings, by placing `.kt`` files in a directory named: ./android/src/test/java/mozilla/appservices// You can write Kotlin-level tests that consume your component's API, by placing `.kt`` files in a directory named: ./android/src/test/java/mozilla/appservices//. So you would end up with a directory structure something like this: components// Cargo.toml uniffi.toml src/ Rust code here. android/ build.gradle src/ main/ AndroidManifest.xml java/mozilla/appservices// Hand-written Kotlin code here. test/java/mozilla/appservices// Kotlin test-cases here. Run your component's Kotlin tests with ./gradlew :test to confirm that this is all working correctly.","breadcrumbs":"Contributing » How to add a new component » The Kotlin Bindings","id":"70","title":"The Kotlin Bindings"},"71":{"body":"","breadcrumbs":"Contributing » How to add a new component » The Swift Bindings","id":"71","title":"The Swift Bindings"},"72":{"body":"Make a ./components//ios subdirectory to contain Swift- and iOS-specific code. The UniFFI-generated swift bindings will be written to a subdirectory named Generated. You can include hand-written Swift code alongside the automatically generated bindings, by placing .swift files in a directory named: ./ios//. So you would end up with a directory structure something like this: components// Cargo.toml uniffi.toml src/ Rust code here. ios/ / Hand-written Swift code here. Generated/ Generated Swift code will be written into this directory.","breadcrumbs":"Contributing » How to add a new component » Creating the directory structure","id":"72","title":"Creating the directory structure"},"73":{"body":"For more information on our how we ship components using the Swift Package Manager, check the ADR that introduced the Swift Package Manager You will need to do the following steps to include the component in the megazord: Update its uniffi.toml to include the following settings: [bindings.swift]\nffi_module_name = \"MozillaRustComponents\"\nffi_module_filename = \"FFI\" Add the component as a dependency to the Cargo.toml in megazords/ios-rust/ Add a pub use declaration for the component in megazords/ios-rust/src/lib.rs Add logic to the megazords/ios-rust/build-xcframework.sh to copy or generate its header file into the build Add an #import for its header file to megazords/ios-rust/MozillaRustComponents.h Add your component into the iOS \"megazord\" through the Xcode project, which can only really by done using the Xcode application, which can only really be done if you're on a Mac. Open megazords/ios-rust/MozillaTestServices/MozillaTestServices.xcodeproj in Xcode. In the Project navigator, add a new Group for your new component, pointing to the ./ios/ directory you created above. Add the following entries to the Group: The .udl file for you component, from ../src/.udl. Any hand-written .swift files for your component Make sure that the \"Copy items if needed\" option is unchecked , and that nothing is checked in the \"Add to targets\" list. The result should look something like this: Screenshot of Xcode Project Navigator Click on the top-level \"MozillaTestServices\" project in the navigator, then go to \"Build Phases\". Double-check that .udl does not appear in the \"Copy Bundle Resources\" section. Add .udl to the list of \"Compile Sources\". This will trigger an Xcode Build Rule that generates the Swift bindings automatically. Also include any hand-written .swift files in this list. Finally, in the Project navigator, add a sub-group named \"Generated\", pointing to the ./Generated/ subdirectory, and containing entries for the files generated by UniFFI: * .swift * FFI.h Make sure that \"Copy items if needed\" is unchecked, and that nothing is checked in \"Add to targets\". Double-check that .swift does not appear in the \"Compile Sources\" section. The result should look something like this: Screenshot of Xcode Compile Sources list Build the project in Xcode to check whether that all worked correctly. To add Swift tests for your component API, create them in a file under megazords/ios-rust/MozillaTestServicesTests/. Use this syntax to import your component's bindings from the compiled megazord: @testable import MozillaTestServices In Xcode, navigate to the MozillaTestServicesTests Group and add your new test file as an entry. Select the corresponding target, click on \"Build Phases\", and add your test file to the list of \"Compile Sources\". The result should look something like this: Screenshot of Xcode Test Setup Use the Xcode Test Navigator to run your tests and check whether they're passing.","breadcrumbs":"Contributing » How to add a new component » Adding your component to the Swift Package Manager Megazord","id":"73","title":"Adding your component to the Swift Package Manager Megazord"},"74":{"body":"The Swift source code and generated UniFFI bindings are distributed to consumers (eg: Firefox iOS) through rust-components-swift . A nightly taskcluster job prepares the rust-component-swift packages from the source code in the application-services repository. To distribute your component with rust-component-swift, add the following to the taskcluster script in taskcluster/scripts/build-and-test-swift.py: Add the path to the .udl file to BINDINGS_UDL_PATHS Optionally also to FOCUS_UDL_PATHS if your component is also targeting Firefox Focus Add the path to the directory containing any hand-written swift code to SOURCE_TO_COPY Optionally also to FOCUS_SOURCE_TO_COPY if your component is also targeting Firefox Focus Your component should now automatically get included in the next rust-component-swift nightly release. Found a bug? Edit this page on GitHub.","breadcrumbs":"Contributing » How to add a new component » Distribute your component with rust-components-swift","id":"74","title":"Distribute your component with rust-components-swift"},"75":{"body":"This is a guide to creating a new Syncable Rust Component like many of the components in this repo. If you are looking for information how to build (ie,compile, etc) the existing components, you are looking for our build documentation Welcome! It's great that you want to build a Rust Component - this guide should help get you started. It documents some nomenclature, best-practices and other tips and tricks to get you started. This document is just for general guidance - every component will be different and we are still learning how to make these components. Please update this document with these learnings. To repeat with emphasis - please consider this a living document .","breadcrumbs":"Contributing » How to add a new component » How to build a new syncable component » Guide to Building a Syncable Rust Component","id":"75","title":"Guide to Building a Syncable Rust Component"},"76":{"body":"We think components should be structured as described here.","breadcrumbs":"Contributing » How to add a new component » How to build a new syncable component » General design and structure of the component","id":"76","title":"General design and structure of the component"},"77":{"body":"Think of building a \"library\", not a \"framework\" - the application should be in control and calling functions exposed by your component, not providing functions for your component to call.","breadcrumbs":"Contributing » How to add a new component » How to build a new syncable component » We build libraries, not frameworks","id":"77","title":"We build libraries, not frameworks"},"78":{"body":"[Note that some of the older components use the term \"store\" differently; we should rename them! In Places, it's called an \"API\"; in Logins an \"engine\". See webext-storage for a more recent component that uses the term \"Store\" as we think it should be used.] The \"Store\" is the entry-point for the consuming application - it provides the core functionality exposed by the component and manages your databases and other singletons. The responsibilities of the \"Store\" will include things like creating the DB if it doesn't exist, doing schema upgrades etc. The functionality exposed by the \"Store\" will depend on the complexity of the API being exposed. For example, for webext-storage, where there are only a handful of simple public functions, it just directly exposes all the functionality of the component. However, for Places, which has a much more complex API, the (logical) Store instead supplies \"Connection\" instances which expose the actual functionality.","breadcrumbs":"Contributing » How to add a new component » How to build a new syncable component » The \"store\" is the \"entry-point\"","id":"78","title":"The \"store\" is the \"entry-point\""},"79":{"body":"We prefer sqlite instead of (say) JSON files or RKV. Always put sqlite into WAL mode, then have exactly 1 writer connection and as many reader connections you need - which will depend on your use-case - for example, webext_storage has 1 reader, while places has many. (Note that places has 2 writers (one for sync, one for the api), but we believe this was a mistake and should have been able to make things work better with exactly 1 shared between sync and the api) We typically have a \"DB\" abstraction which manages the database itself - the logic for handling schema upgrades etc and enforcing the \"only 1 writer\" rule is done by this. However, this is just a convenience - the DB abstractions aren't really passed around - we just pass raw connections (or transactions) around. For example, if there's a utility function that reads from the DB, it will just have a Rusqlite connection passed. (Again, older components don't really do this well, but webext-storage does) We try and leverage rust to ensure transactions are enforced at the correct boundaries - for example, functions which write data but which must be done as part of a transaction will accept a Rusqlite Transaction reference as the param, whereas something that only reads the Db will accept a Rusqlite Connection - note that because Transaction supports Deref, you can pass a &Transaction wherever a &Connection is needed - but not vice-versa.","breadcrumbs":"Contributing » How to add a new component » How to build a new syncable component » Using sqlite","id":"79","title":"Using sqlite"},"8":{"body":"This project is production Mozilla code and subject to our engineering practices and quality standards . Every patch must be peer reviewed by a member of the Application Services team. Found a bug? Edit this page on GitHub.","breadcrumbs":"Contributing » Code Review","id":"8","title":"Code Review"},"80":{"body":"You are likely to have a table just for key/value metadata, and this table will be used by sync (and possibly other parts of the component) to track the sync IDs, lastModified timestamps etc.","breadcrumbs":"Contributing » How to add a new component » How to build a new syncable component » Meta-data","id":"80","title":"Meta-data"},"81":{"body":"The schemas are stored in the tree in .sql files and pulled into the source at build time via include_str!. Depending on the complexity of your component, there may be a need for different Connections to have different Sql (for example, it may be that only your 'write' connection requires the sql to define triggers or temp tables, so these might be in their own file.) Because requirements evolve, there will be a need to support schema upgrades. This is done by way of sqlite's PRAGMA user_version - which can be thought of as simple metadata for the database itself. In short, immediately after opening the database for the first time, we check this version and if it's less than expected we perform the schema upgrades necessary, then re-write the version to the new version. This is easier to read than explain, so read the upgrade() function in the Places schema code You will need to be a big careful here because schema upgrades are going to block the calling application immediately after they upgrade to a new version, so if your schema change requires a table scan of a massive table, you are going to have a bad time. Apart from that though, you are largely free to do whatever sqlite lets you do! Note that most of our components have very similar schema and database management code - these are screaming out to be refactored so common logic can be shared. Please be brave and have a go at this!","breadcrumbs":"Contributing » How to add a new component » How to build a new syncable component » Schema management","id":"81","title":"Schema management"},"82":{"body":"We tend to like triggers for encompasing application logic - for example, if updating one row means a row in a different table should be updated based on that data, we'd tend to prefer an, eg, AFTER UPDATE trigger than having our code manually implement the logic. However, you should take care here, because functionality based on triggers is difficult to debug (eg, logging in a trigger is difficult) and the functionality can be difficult to locate (eg, users unfamiliar with the component may wonder why they can't find certain functionity in the rust code and may not consider looking in the sqlite triggers) You should also be careful when creating triggers on persistent main tables. For example, bumping the change counter isn't a good use for a trigger, because it'll run for all changes on the table—including those made by Sync. This means Sync will end up tracking its own changes, and getting into infinite syncing loops. Triggers on temporary tables, or ones that are used for bookkeeping where the caller doesn't matter, like bumping the foreign reference count for a URL, are generally okay.","breadcrumbs":"Contributing » How to add a new component » How to build a new syncable component » Triggers","id":"82","title":"Triggers"},"83":{"body":"We prefer flatter module hierarchies where possible. For example, in Places we ended up with sync_history and sync_bookmarks sub-modules rather than a sync submodule itself with history and bookmarks. Note that the raw connections are never exposed to consumers - for example, they will tend to be stored as private fields in, eg, a Mutex.","breadcrumbs":"Contributing » How to add a new component » How to build a new syncable component » General structure of the rust code","id":"83","title":"General structure of the rust code"},"84":{"body":"The traits you need to implement to sync aren't directly covered here. All meta-data related to sync must be stored in the same database as the data itself - often in a meta table. All logic for knowing which records need to be sync must be part of the application logic, and will often be implemented using triggers. It's quite common for components to use a \"change counter\" strategy, which can be summarized as: Every table which defines the \"top level\" items being synced will have a column called something like 'sync_change_counter' - the app will probably track this counter manually instead of using a trigger, because sync itself will need different behavior when it updates the records. At sync time, items with a non-zero change counter are candidates for syncing. As the sync starts, for each item, the current value of the change counter is remembered. At the end of the sync, the counter is decremented by this value. Thus, items which were changed between the time the sync started and completed will be left with a non-zero change counter at the end of the sync.","breadcrumbs":"Contributing » How to add a new component » How to build a new syncable component » Syncing","id":"84","title":"Syncing"},"85":{"body":"This section is stolen from this document","breadcrumbs":"Contributing » How to add a new component » How to build a new syncable component » Syncing FAQs","id":"85","title":"Syncing FAQs"},"86":{"body":"Both guids, both used to identify when the data in the server has changed radically underneath us (eg, when looking at lastModified is no longer a sane thing to do.) The \"global sync ID\" changing means that every collection needs to be assumed as having changed radically, whereas just the \"collection sync ID\" changing means just that one collection. These global IDs are most likely to change on a node reassignment (which should be rare now with durable storage), a password reset, etc. An example of when the collection ID will change is a \"bookmarks restore\" - handling an old version of a database re-appearing is why we store these IDs in the database itself.","breadcrumbs":"Contributing » How to add a new component » How to build a new syncable component » What’s the global sync ID and the collection sync ID?","id":"86","title":"What’s the global sync ID and the collection sync ID?"},"87":{"body":"They are all used to track the guids above. It’s vitally important we know when these guids change. StoreSyncAssociation is a simple enum which reflects the state a sync engine can be in - either Disconnected (ie, we have no idea what the GUIDs are) or Connected where we know what we think the IDs are (but the server may or may not match with this) These GUIDs will typically be stored in the DB in the metadata table.","breadcrumbs":"Contributing » How to add a new component » How to build a new syncable component » What’s get_sync_assoc, why is it important? What is StoreSyncAssociation?","id":"87","title":"What’s get_sync_assoc, why is it important? What is StoreSyncAssociation?"},"88":{"body":"apply_incoming is where any records incoming from the server (ie, possibly all records on the server if this is a first-sync, records with a timestamp later than our last sync otherwise) are processed. sync_finished is where we've done all the sync work other than uploading new changes to the server.","breadcrumbs":"Contributing » How to add a new component » How to build a new syncable component » what is apply_incoming versus sync_finished","id":"88","title":"what is apply_incoming versus sync_finished"},"89":{"body":"Reset means “I don’t know what’s on the server - I need to reconcile everything there with everything I have”. IOW, a “first sync” Wipe means literally “wipe all server data”","breadcrumbs":"Contributing » How to add a new component » How to build a new syncable component » What's the diff between reset and wipe?","id":"89","title":"What's the diff between reset and wipe?"},"9":{"body":"When working on Application Services, it's important to set up your environment for building the Rust code and the Android or iOS code needed by the application.","breadcrumbs":"Contributing » Building » Building Application Services","id":"9","title":"Building Application Services"},"90":{"body":"You will need an FFI or some other way of exposing stuff to your consumers. We use a tool called UniFFI to automatically generate FFI bindings from the Rust code. If UniFFI doesn't work for you, then you'll need to hand-write the FFI layer. Here are some earlier blog posts on the topic which might be helpful: Building and Deploying a Rust library on Android Building and Deploying a Rust library on iOS Blog post re: lessons in binding to Rust code from iOS The above are likely to be superseded by uniffi docs, but for now, good luck! Found a bug? Edit this page on GitHub.","breadcrumbs":"Contributing » How to add a new component » How to build a new syncable component » Exposing to consumers","id":"90","title":"Exposing to consumers"},"91":{"body":"All names in this project should adhere to the guidelines outlined in this document.","breadcrumbs":"Contributing » How to add a new component » Naming Conventions » Naming Conventions","id":"91","title":"Naming Conventions"},"92":{"body":"TL;DR: do what Rust's builtin warnings and clippy lints tell you (and CI will fail if there are any unresolved warnings or clippy lints).","breadcrumbs":"Contributing » How to add a new component » Naming Conventions » Rust Code","id":"92","title":"Rust Code"},"93":{"body":"All variable names, function names, module names, and macros in Rust code should follow typical snake_case conventions. All Rust types, traits, structs, and enum variants must follow UpperCamelCase. Static and constant variables should be written in SCREAMING_SNAKE_CASE. s For more in-depth Rust conventions, see the Rust Style Guide .","breadcrumbs":"Contributing » How to add a new component » Naming Conventions » Overview","id":"93","title":"Overview"},"94":{"body":"fn sync15_passwords_get_all()\nstruct PushConfiguration{...}\nconst COMMON_SQL","breadcrumbs":"Contributing » How to add a new component » Naming Conventions » Examples:","id":"94","title":"Examples:"},"95":{"body":"","breadcrumbs":"Contributing » How to add a new component » Naming Conventions » Swift Code","id":"95","title":"Swift Code"},"96":{"body":"Names of types and protocols are UpperCamelCase. All other uses are lowerCamelCase. For more in-depth Swift conventions, check out the Swift API Design Guidelines .","breadcrumbs":"Contributing » How to add a new component » Naming Conventions » Overview","id":"96","title":"Overview"},"97":{"body":"enum CheckChildren{...}\nfunc checkTree()\npublic var syncKey: String","breadcrumbs":"Contributing » How to add a new component » Naming Conventions » Examples:","id":"97","title":"Examples:"},"98":{"body":"If a source file contains only a top-level class, the source file should reflect the case-sensitive name of the class plus the .kt extension. Otherwise, if the source contains multiple top-level declarations, choose a name that describes the contents of the file, apply UpperCamelCase and append .kt extension.","breadcrumbs":"Contributing » How to add a new component » Naming Conventions » Kotlin Code","id":"98","title":"Kotlin Code"},"99":{"body":"Names of packages are always lower case and do not include underscores. Using multi-word names should be avoided. However, if used, they should be concatenated or use lowerCamelCase. Names of classes and objects use UpperCamelCase. Names of functions, properties, and local variables use lowerCamelCase. For more in-depth Kotlin Conventions, see the Kotlin Style Guide .","breadcrumbs":"Contributing » How to add a new component » Naming Conventions » Overview","id":"99","title":"Overview"}},"length":287,"save":true},"fields":["title","body","breadcrumbs"],"index":{"body":{"root":{"0":{".":{"0":{".":{"1":{"df":1,"docs":{"22":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"1":{".":{"0":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":1,"docs":{"283":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"123":{"tf":1.4142135623730951}}},"2":{"7":{".":{"0":{"df":1,"docs":{"20":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"62":{"tf":1.0}}},"5":{"1":{".":{"0":{"df":1,"docs":{"20":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"0":{"0":{"0":{"df":1,"docs":{"134":{"tf":1.0}}},"1":{"df":1,"docs":{"134":{"tf":1.0}}},"2":{"df":1,"docs":{"134":{"tf":1.0}}},"3":{"df":1,"docs":{"134":{"tf":1.0}}},"4":{"df":1,"docs":{"134":{"tf":1.0}}},"5":{"df":1,"docs":{"134":{"tf":1.0}}},"7":{"df":1,"docs":{"134":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"1":{"df":1,"docs":{"202":{"tf":1.0}}},"6":{"df":3,"docs":{"139":{"tf":1.0},"148":{"tf":1.0},"202":{"tf":1.0}}},"7":{"df":2,"docs":{"160":{"tf":1.0},"268":{"tf":1.4142135623730951}}},"8":{"df":3,"docs":{"148":{"tf":1.0},"176":{"tf":1.0},"268":{"tf":1.4142135623730951}}},"df":2,"docs":{"214":{"tf":1.0},"62":{"tf":1.7320508075688772}},"f":{"8":{"c":{"0":{"3":{"7":{"6":{"3":{"7":{"df":0,"docs":{},"f":{"9":{"df":0,"docs":{},"e":{"b":{"3":{"df":0,"docs":{},"e":{".":{"c":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"\"":{"&":{"df":0,"docs":{},"g":{"df":0,"docs":{},"t":{";":{"&":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{";":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"k":{"df":1,"docs":{"283":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"1":{",":{"4":{"df":1,"docs":{"269":{"tf":1.4142135623730951}}},"7":{"df":1,"docs":{"268":{"tf":1.4142135623730951}}},"8":{"df":1,"docs":{"268":{"tf":1.4142135623730951}}},"df":0,"docs":{}},".":{"4":{"df":1,"docs":{"62":{"tf":1.0}}},"df":0,"docs":{}},"0":{",":{"0":{"0":{"0":{"df":5,"docs":{"204":{"tf":1.0},"214":{"tf":1.7320508075688772},"215":{"tf":1.0},"216":{"tf":1.7320508075688772},"217":{"tf":2.23606797749979}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"0":{",":{"0":{"0":{"0":{"df":2,"docs":{"215":{"tf":1.0},"216":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"0":{"0":{"df":1,"docs":{"214":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"6":{"4":{"4":{"df":3,"docs":{"268":{"tf":1.0},"269":{"tf":1.0},"62":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"268":{"tf":1.0}}},"8":{",":{"6":{"df":1,"docs":{"62":{"tf":1.0}}},"7":{"df":1,"docs":{"62":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"9":{"7":{"df":0,"docs":{},"f":{"8":{"df":0,"docs":{},"e":{"9":{"2":{"a":{"0":{"1":{"df":0,"docs":{},"e":{"3":{"c":{"df":0,"docs":{},"f":{".":{"c":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"\"":{"&":{"df":0,"docs":{},"g":{"df":0,"docs":{},"t":{";":{"&":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{";":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"k":{"df":1,"docs":{"283":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":1,"docs":{"20":{"tf":1.0}},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"204":{"tf":1.0}}}}},"1":{",":{"0":{"0":{"0":{"df":1,"docs":{"204":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"4":{".":{"0":{"a":{"1":{"df":1,"docs":{"269":{"tf":1.0}}},"df":0,"docs":{}},"df":1,"docs":{"269":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"8":{".":{"0":{"a":{"1":{"df":1,"docs":{"268":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"9":{".":{"0":{"a":{"1":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"204":{"tf":1.0}}},"2":{"3":{"df":1,"docs":{"117":{"tf":1.0}}},"df":1,"docs":{"186":{"tf":1.0}}},"3":{"2":{"8":{"5":{"a":{"df":0,"docs":{},"e":{"c":{"3":{"1":{"df":0,"docs":{},"f":{"a":{"2":{"4":{"3":{"df":0,"docs":{},"e":{".":{"c":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"\"":{"&":{"df":0,"docs":{},"g":{"df":0,"docs":{},"t":{";":{"df":0,"docs":{},"☰":{"c":{"df":0,"docs":{},"r":{"df":1,"docs":{"283":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"4":{"7":{"9":{"9":{"2":{"9":{"df":1,"docs":{"228":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"5":{",":{"0":{"0":{"0":{"df":1,"docs":{"204":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"6":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"6":{",":{"0":{"0":{"0":{"df":1,"docs":{"214":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"7":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}},"8":{"6":{"2":{"4":{"8":{"3":{"5":{"0":{"4":{"7":{"0":{"df":1,"docs":{"62":{"tf":3.1622776601683795}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":2,"docs":{"176":{"tf":1.0},"186":{"tf":1.0}}},"7":{"1":{"1":{"4":{"4":{"7":{"df":2,"docs":{"171":{"tf":1.0},"174":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":2,"docs":{"12":{"tf":1.4142135623730951},"139":{"tf":1.0}}},":":{"1":{"df":1,"docs":{"242":{"tf":1.0}}},"df":0,"docs":{}},"df":13,"docs":{"117":{"tf":1.0},"142":{"tf":1.0},"145":{"tf":1.0},"151":{"tf":1.4142135623730951},"158":{"tf":1.0},"196":{"tf":1.0},"20":{"tf":1.0},"22":{"tf":1.4142135623730951},"252":{"tf":1.7320508075688772},"268":{"tf":2.0},"276":{"tf":1.0},"62":{"tf":4.58257569495584},"79":{"tf":2.0}},"u":{"8":{"df":1,"docs":{"117":{"tf":1.0}}},"df":0,"docs":{}}},"2":{",":{"5":{"0":{"0":{"df":1,"docs":{"204":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},".":{"0":{"df":2,"docs":{"64":{"tf":1.4142135623730951},"7":{"tf":1.0}}},"1":{".":{"2":{"df":2,"docs":{"137":{"tf":1.0},"138":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":1,"docs":{"62":{"tf":1.4142135623730951}}},"5":{"df":1,"docs":{"204":{"tf":1.0}}},"df":0,"docs":{}},"0":{",":{"0":{"0":{"0":{"df":1,"docs":{"216":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"0":{"0":{"0":{"df":1,"docs":{"214":{"tf":1.0}}},"df":3,"docs":{"206":{"tf":1.0},"217":{"tf":1.0},"218":{"tf":1.0}}},"df":0,"docs":{}},"1":{"8":{"df":1,"docs":{"192":{"tf":1.0}}},"df":0,"docs":{}},"2":{"1":{"df":4,"docs":{"139":{"tf":1.0},"148":{"tf":1.0},"160":{"tf":1.0},"176":{"tf":1.0}}},"2":{"df":1,"docs":{"186":{"tf":1.0}}},"3":{"df":2,"docs":{"187":{"tf":1.0},"202":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"1":{".":{"4":{".":{"7":{"0":{"7":{"5":{"5":{"2":{"9":{"df":1,"docs":{"12":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"2":{",":{"7":{"df":1,"docs":{"62":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":1,"docs":{"160":{"tf":1.0}}},"5":{",":{"0":{"0":{"0":{"df":1,"docs":{"204":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},".":{"2":{".":{"9":{"5":{"1":{"9":{"6":{"5":{"3":{"df":1,"docs":{"12":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"6":{"df":1,"docs":{"280":{"tf":1.0}}},"df":2,"docs":{"204":{"tf":1.0},"37":{"tf":1.0}},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"204":{"tf":1.0}}}}},"6":{"2":{"8":{"df":1,"docs":{"149":{"tf":1.0}}},"df":0,"docs":{}},"df":1,"docs":{"13":{"tf":1.0}}},"8":{"_":{"df":1,"docs":{"268":{"tf":1.4142135623730951}}},"df":0,"docs":{},"f":{"1":{"9":{"3":{"0":{"7":{".":{".":{"2":{"2":{"5":{"d":{"c":{"c":{"b":{"b":{"df":1,"docs":{"62":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":14,"docs":{"142":{"tf":1.0},"146":{"tf":1.0},"149":{"tf":1.0},"151":{"tf":1.4142135623730951},"18":{"tf":1.0},"197":{"tf":1.0},"22":{"tf":1.0},"237":{"tf":1.0},"242":{"tf":1.0},"245":{"tf":1.0},"261":{"tf":1.0},"264":{"tf":1.4142135623730951},"62":{"tf":3.0},"79":{"tf":1.0}},"f":{"a":{"df":1,"docs":{"276":{"tf":1.0}}},"df":0,"docs":{}}},"3":{".":{"6":{"df":1,"docs":{"11":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"1":{"_":{"df":1,"docs":{"268":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"2":{",":{"8":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}},"3":{"df":1,"docs":{"176":{"tf":1.0}}},"7":{"6":{"8":{"df":1,"docs":{"264":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"116":{"tf":1.4142135623730951}}},"4":{",":{"6":{"df":1,"docs":{"268":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"6":{",":{"8":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"7":{"df":1,"docs":{"204":{"tf":1.0}}},"8":{"9":{"9":{"df":1,"docs":{"145":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"9":{"0":{"9":{"df":1,"docs":{"48":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"a":{"d":{"9":{"df":1,"docs":{"258":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":10,"docs":{"151":{"tf":1.4142135623730951},"198":{"tf":1.0},"199":{"tf":1.0},"213":{"tf":1.4142135623730951},"22":{"tf":1.0},"242":{"tf":1.0},"249":{"tf":1.4142135623730951},"274":{"tf":1.4142135623730951},"60":{"tf":1.4142135623730951},"62":{"tf":1.7320508075688772}},"r":{"d":{"df":2,"docs":{"121":{"tf":1.0},"201":{"tf":1.7320508075688772}}},"df":0,"docs":{}}},"4":{".":{"9":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"/":{"d":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"37":{"tf":2.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"0":{"0":{"df":1,"docs":{"204":{"tf":1.0}}},"df":0,"docs":{}},"1":{"0":{"1":{"df":1,"docs":{"139":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"2":{"df":1,"docs":{"37":{"tf":1.0}}},"5":{",":{"0":{"0":{"0":{"df":1,"docs":{"204":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"6":{"0":{"4":{"df":1,"docs":{"258":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"9":{"6":{"6":{"df":1,"docs":{"38":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"62":{"tf":1.0}}},"5":{",":{"0":{"0":{"0":{"df":1,"docs":{"204":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"0":{",":{"0":{"0":{"0":{"df":1,"docs":{"204":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"0":{"0":{"df":3,"docs":{"206":{"tf":1.0},"217":{"tf":1.0},"218":{"tf":1.0}}},"df":0,"docs":{}},"df":1,"docs":{"204":{"tf":1.0}},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"204":{"tf":1.0}}}}},"2":{"1":{"0":{"df":1,"docs":{"280":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"7":{"4":{"5":{"]":{"(":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{":":{"/":{"/":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"u":{"b":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"/":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"268":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"6":{"4":{"]":{"(":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{":":{"/":{"/":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"u":{"b":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"/":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"268":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"6":{"]":{"(":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{":":{"/":{"/":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"u":{"b":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"/":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"268":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"8":{"4":{"]":{"(":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{":":{"/":{"/":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"u":{"b":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"/":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"268":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":4,"docs":{"20":{"tf":1.0},"204":{"tf":1.7320508075688772},"62":{"tf":1.0},"7":{"tf":1.0}}},"6":{",":{"4":{"0":{"0":{"df":1,"docs":{"204":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},".":{"4":{"df":1,"docs":{"204":{"tf":1.0}}},"df":0,"docs":{}},"1":{"4":{"4":{"df":1,"docs":{"264":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"2":{"df":1,"docs":{"264":{"tf":1.0}}},"4":{"df":2,"docs":{"17":{"tf":2.0},"37":{"tf":1.0}}},"6":{"6":{",":{"7":{"df":1,"docs":{"62":{"tf":1.0}}},"8":{"df":1,"docs":{"62":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"7":{"df":2,"docs":{"215":{"tf":1.0},"217":{"tf":1.0}}},"df":3,"docs":{"204":{"tf":1.7320508075688772},"215":{"tf":1.0},"62":{"tf":1.0}}},"7":{"5":{"df":1,"docs":{"206":{"tf":1.0}},"m":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"218":{"tf":1.0}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"204":{"tf":1.0}}}},"~":{"8":{"0":{"df":1,"docs":{"216":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"0":{"b":{"0":{"c":{"2":{"5":{"9":{"a":{"a":{"2":{"df":1,"docs":{"258":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"f":{"2":{"c":{"0":{"7":{"a":{"1":{"a":{"8":{".":{".":{"0":{"6":{"6":{"8":{"8":{"df":0,"docs":{},"f":{"d":{"c":{"a":{"b":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"8":{"0":{"df":1,"docs":{"204":{"tf":1.0}}},"c":{"d":{"9":{"2":{"3":{"8":{"7":{"3":{".":{".":{"6":{"4":{"8":{"2":{"0":{"1":{"8":{"df":0,"docs":{},"e":{"0":{"df":1,"docs":{"269":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"116":{"tf":1.0}}},"9":{"0":{"df":2,"docs":{"206":{"tf":1.0},"218":{"tf":1.0}},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"204":{"tf":1.0}}}}},"5":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"204":{"tf":1.0}}}}},"6":{"df":1,"docs":{"216":{"tf":1.0}}},"7":{"df":1,"docs":{"204":{"tf":1.0}}},"8":{"df":1,"docs":{"216":{"tf":1.0}}},"9":{".":{"6":{"df":1,"docs":{"216":{"tf":1.0}}},"9":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"204":{"tf":1.0}}}}},"df":0,"docs":{}},"df":4,"docs":{"204":{"tf":1.0},"208":{"tf":1.0},"216":{"tf":1.0},"217":{"tf":1.0}},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"204":{"tf":1.0}}}}},"df":0,"docs":{}},"_":{"2":{"0":{"2":{"3":{"df":1,"docs":{"268":{"tf":2.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"_":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"_":{"_":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"_":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"(":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"_":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"_":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":1,"docs":{"37":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"v":{"a":{"_":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"s":{"_":{"_":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":1,"docs":{"117":{"tf":1.0}},"y":{"df":0,"docs":{},"y":{"df":0,"docs":{},"y":{"df":0,"docs":{},"i":{"df":1,"docs":{"268":{"tf":1.0}}}}}}},"a":{"/":{"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{".":{"df":0,"docs":{},"m":{"d":{"df":1,"docs":{"268":{"tf":2.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"a":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"/":{"c":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":1,"docs":{"62":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{},"s":{"df":0,"docs":{},"r":{"c":{"/":{"d":{"b":{"/":{"d":{"b":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"62":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"62":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":2,"docs":{"268":{"tf":2.0},"269":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}}}}}}}}},"1":{"df":1,"docs":{"154":{"tf":1.0}}},"2":{"df":2,"docs":{"152":{"tf":1.0},"155":{"tf":1.0}}},"3":{"df":1,"docs":{"156":{"tf":1.0}}},"7":{"c":{"d":{"df":1,"docs":{"258":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"a":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"h":{"6":{"4":{"df":2,"docs":{"17":{"tf":1.0},"37":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":6,"docs":{"220":{"tf":1.0},"221":{"tf":2.8284271247461903},"222":{"tf":1.7320508075688772},"223":{"tf":1.0},"224":{"tf":1.0},"37":{"tf":1.4142135623730951}}}},"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":6,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"162":{"tf":1.0},"178":{"tf":1.0},"234":{"tf":1.0},"274":{"tf":1.4142135623730951}}}},"o":{"df":0,"docs":{},"v":{"df":31,"docs":{"105":{"tf":1.0},"106":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.4142135623730951},"123":{"tf":1.0},"124":{"tf":1.0},"14":{"tf":1.0},"145":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.4142135623730951},"200":{"tf":1.4142135623730951},"204":{"tf":1.0},"214":{"tf":1.0},"217":{"tf":1.0},"22":{"tf":1.7320508075688772},"220":{"tf":1.0},"224":{"tf":1.4142135623730951},"227":{"tf":1.0},"228":{"tf":1.0},"235":{"tf":1.0},"242":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951},"263":{"tf":1.0},"264":{"tf":1.0},"268":{"tf":1.0},"274":{"tf":1.7320508075688772},"280":{"tf":1.0},"64":{"tf":1.0},"73":{"tf":1.0},"87":{"tf":1.0},"90":{"tf":1.0}}}},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":4,"docs":{"61":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0},"67":{"tf":1.0}}}}}},"t":{"df":0,"docs":{},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"103":{"tf":1.0},"229":{"tf":1.4142135623730951},"79":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"c":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"282":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"t":{"df":11,"docs":{"105":{"tf":1.4142135623730951},"106":{"tf":1.0},"115":{"tf":1.0},"139":{"tf":1.0},"148":{"tf":1.0},"160":{"tf":1.0},"202":{"tf":1.0},"231":{"tf":1.0},"274":{"tf":1.0},"37":{"tf":1.0},"79":{"tf":1.4142135623730951}}}},"s":{"df":0,"docs":{},"s":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"242":{"tf":1.0}}}}}}}},"df":13,"docs":{"114":{"tf":1.0},"126":{"tf":1.0},"127":{"tf":1.0},"145":{"tf":1.0},"187":{"tf":1.0},"188":{"tf":1.0},"245":{"tf":1.0},"254":{"tf":1.0},"255":{"tf":1.0},"274":{"tf":1.0},"277":{"tf":1.4142135623730951},"283":{"tf":1.7320508075688772},"37":{"tf":1.0}}}}},"i":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"175":{"tf":1.0}}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"d":{"df":3,"docs":{"104":{"tf":1.0},"268":{"tf":1.0},"282":{"tf":1.0}}},"df":0,"docs":{}}},"o":{"d":{"df":1,"docs":{"108":{"tf":1.0}}},"df":0,"docs":{}},"p":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":3,"docs":{"43":{"tf":1.0},"64":{"tf":1.0},"7":{"tf":1.0}}}}},"df":0,"docs":{}}},"r":{"d":{"df":1,"docs":{"25":{"tf":1.0}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"263":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{".":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"z":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":1,"docs":{"283":{"tf":1.0}}}}}}}}}}}}}}}}},"df":0,"docs":{}}}}}}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"c":{"a":{"df":0,"docs":{},"p":{"a":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"a":{"df":1,"docs":{"283":{"tf":1.0}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"283":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"a":{"df":1,"docs":{"283":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":7,"docs":{"0":{"tf":1.0},"245":{"tf":1.0},"254":{"tf":1.7320508075688772},"274":{"tf":1.0},"276":{"tf":2.23606797749979},"283":{"tf":2.23606797749979},"284":{"tf":1.0}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":1,"docs":{"242":{"tf":1.0}}}}}}}}}},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"145":{"tf":1.0}}}}},"df":2,"docs":{"39":{"tf":1.0},"40":{"tf":1.0}},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":1,"docs":{"167":{"tf":1.0}}}}}},"t":{"df":3,"docs":{"237":{"tf":1.0},"245":{"tf":1.0},"251":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":10,"docs":{"119":{"tf":1.0},"226":{"tf":1.0},"230":{"tf":1.0},"231":{"tf":1.0},"235":{"tf":1.0},"242":{"tf":1.0},"249":{"tf":1.7320508075688772},"250":{"tf":1.0},"272":{"tf":1.0},"273":{"tf":1.0}}}},"v":{"df":1,"docs":{"205":{"tf":1.0}}}},"u":{"a":{"df":0,"docs":{},"l":{"df":10,"docs":{"179":{"tf":1.0},"194":{"tf":1.0},"226":{"tf":1.0},"232":{"tf":1.0},"261":{"tf":1.4142135623730951},"279":{"tf":1.0},"51":{"tf":1.0},"58":{"tf":1.0},"69":{"tf":1.0},"78":{"tf":1.0}}}},"df":0,"docs":{}}}},"d":{"d":{"/":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"d":{"df":2,"docs":{"145":{"tf":1.0},"146":{"tf":1.0}}},"df":0,"docs":{}}}},"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"_":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"d":{"df":2,"docs":{"145":{"tf":2.0},"146":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":41,"docs":{"105":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.4142135623730951},"11":{"tf":1.4142135623730951},"113":{"tf":1.0},"119":{"tf":1.0},"12":{"tf":1.0},"120":{"tf":1.0},"123":{"tf":1.0},"124":{"tf":1.0},"13":{"tf":1.0},"145":{"tf":1.4142135623730951},"146":{"tf":1.0},"158":{"tf":1.0},"167":{"tf":1.4142135623730951},"184":{"tf":1.0},"20":{"tf":1.0},"201":{"tf":1.0},"22":{"tf":1.4142135623730951},"223":{"tf":1.0},"224":{"tf":1.0},"229":{"tf":1.4142135623730951},"249":{"tf":1.0},"268":{"tf":1.7320508075688772},"274":{"tf":1.4142135623730951},"281":{"tf":1.0},"284":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"51":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":2.23606797749979},"61":{"tf":2.8284271247461903},"69":{"tf":2.0},"7":{"tf":1.4142135623730951},"70":{"tf":1.0},"73":{"tf":3.7416573867739413},"74":{"tf":1.7320508075688772}},"i":{"df":0,"docs":{},"t":{"df":11,"docs":{"11":{"tf":1.0},"13":{"tf":1.0},"141":{"tf":1.0},"167":{"tf":1.0},"201":{"tf":1.0},"274":{"tf":1.4142135623730951},"43":{"tf":1.4142135623730951},"6":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"67":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":7,"docs":{"180":{"tf":1.0},"209":{"tf":1.0},"22":{"tf":1.0},"254":{"tf":1.0},"257":{"tf":1.0},"58":{"tf":1.0},"60":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":5,"docs":{"141":{"tf":1.0},"169":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"182":{"tf":1.0}}}}}}},"df":34,"docs":{"101":{"tf":1.0},"102":{"tf":1.4142135623730951},"104":{"tf":1.0},"105":{"tf":1.0},"108":{"tf":1.4142135623730951},"109":{"tf":1.7320508075688772},"11":{"tf":1.0},"121":{"tf":1.0},"123":{"tf":2.23606797749979},"145":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":1.0},"201":{"tf":1.0},"22":{"tf":1.7320508075688772},"23":{"tf":1.0},"249":{"tf":1.0},"264":{"tf":1.0},"277":{"tf":1.0},"280":{"tf":1.0},"282":{"tf":1.0},"31":{"tf":1.0},"32":{"tf":1.0},"36":{"tf":1.0},"41":{"tf":1.0},"57":{"tf":1.0},"59":{"tf":1.0},"61":{"tf":1.4142135623730951},"64":{"tf":1.4142135623730951},"65":{"tf":1.0},"66":{"tf":1.0},"67":{"tf":1.0},"68":{"tf":1.7320508075688772},"69":{"tf":1.0},"73":{"tf":1.0}},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":1,"docs":{"7":{"tf":1.0}}}}},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"91":{"tf":1.0}}}}},"j":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":4,"docs":{"16":{"tf":1.0},"25":{"tf":1.0},"277":{"tf":1.0},"37":{"tf":1.7320508075688772}}}}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"117":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"r":{"df":10,"docs":{"134":{"tf":2.8284271247461903},"137":{"tf":1.0},"138":{"tf":1.0},"149":{"tf":1.0},"200":{"tf":1.0},"201":{"tf":1.0},"23":{"tf":1.0},"251":{"tf":1.0},"32":{"tf":1.0},"73":{"tf":1.0}}},"v":{"a":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"6":{"tf":1.4142135623730951}}},"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"g":{"df":2,"docs":{"12":{"tf":1.0},"220":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"107":{"tf":1.0}}}}}}},"i":{"c":{"df":2,"docs":{"122":{"tf":1.4142135623730951},"69":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"157":{"tf":1.0},"268":{"tf":1.0},"63":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"220":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"r":{"a":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"105":{"tf":1.0},"106":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"g":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":6,"docs":{"11":{"tf":1.0},"157":{"tf":1.0},"158":{"tf":1.0},"242":{"tf":1.0},"245":{"tf":1.0},"79":{"tf":1.0}},"s":{"df":0,"docs":{},"t":{"df":25,"docs":{"15":{"tf":1.4142135623730951},"151":{"tf":1.4142135623730951},"152":{"tf":1.4142135623730951},"155":{"tf":1.0},"156":{"tf":1.0},"16":{"tf":1.0},"17":{"tf":1.0},"194":{"tf":1.0},"23":{"tf":1.4142135623730951},"268":{"tf":1.4142135623730951},"269":{"tf":1.0},"274":{"tf":1.7320508075688772},"282":{"tf":1.0},"32":{"tf":1.0},"37":{"tf":1.0},"48":{"tf":1.0},"49":{"tf":1.0},"51":{"tf":1.0},"52":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"62":{"tf":1.0},"64":{"tf":1.4142135623730951},"7":{"tf":1.0}}}}}}},"df":0,"docs":{},"o":{"df":1,"docs":{"213":{"tf":1.0}}},"p":{"df":1,"docs":{"12":{"tf":1.0}}},"r":{"df":0,"docs":{},"e":{"df":2,"docs":{"232":{"tf":1.0},"7":{"tf":1.0}}}}},"h":{"df":0,"docs":{},"e":{"a":{"d":{"df":1,"docs":{"104":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"m":{"df":4,"docs":{"102":{"tf":1.0},"103":{"tf":1.0},"104":{"tf":1.0},"116":{"tf":1.0}}}},"k":{"a":{"df":1,"docs":{"260":{"tf":1.0}}},"df":0,"docs":{}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"64":{"tf":1.4142135623730951}}}}},"g":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"m":{"df":1,"docs":{"282":{"tf":1.0}}}}}}}}},"i":{"a":{"df":1,"docs":{"11":{"tf":1.0}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"df":3,"docs":{"171":{"tf":1.0},"172":{"tf":1.0},"274":{"tf":1.0}}}}},"l":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"279":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"(":{"d":{"df":0,"docs":{},"e":{"a":{"d":{"_":{"c":{"df":0,"docs":{},"o":{"d":{"df":1,"docs":{"60":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":15,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"145":{"tf":1.0},"157":{"tf":1.0},"158":{"tf":1.0},"183":{"tf":1.0},"190":{"tf":1.4142135623730951},"198":{"tf":1.0},"199":{"tf":1.0},"229":{"tf":1.0},"233":{"tf":1.0},"242":{"tf":1.0},"255":{"tf":1.0},"273":{"tf":1.0},"39":{"tf":1.0}}}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"20":{"tf":1.0}}}},"df":0,"docs":{}}}}}}},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"198":{"tf":1.0},"220":{"tf":1.0}},"g":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"70":{"tf":1.0},"72":{"tf":1.0}}},"df":0,"docs":{}}}}}},"r":{"df":0,"docs":{},"e":{"a":{"d":{"df":0,"docs":{},"i":{"df":17,"docs":{"122":{"tf":1.0},"187":{"tf":1.0},"194":{"tf":1.0},"196":{"tf":1.0},"197":{"tf":1.0},"198":{"tf":1.0},"199":{"tf":1.0},"200":{"tf":1.0},"229":{"tf":1.0},"23":{"tf":1.0},"241":{"tf":1.4142135623730951},"258":{"tf":1.0},"32":{"tf":1.0},"36":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"64":{"tf":1.7320508075688772}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":4,"docs":{"115":{"tf":1.4142135623730951},"122":{"tf":1.0},"225":{"tf":1.0},"26":{"tf":1.0}}}}},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":9,"docs":{"105":{"tf":1.0},"124":{"tf":1.0},"132":{"tf":1.0},"190":{"tf":1.0},"214":{"tf":1.4142135623730951},"225":{"tf":1.0},"232":{"tf":1.0},"262":{"tf":1.0},"38":{"tf":1.0}}}}}}}},"w":{"a":{"df":0,"docs":{},"y":{"df":10,"docs":{"114":{"tf":1.0},"133":{"tf":1.0},"146":{"tf":1.0},"189":{"tf":1.0},"198":{"tf":1.0},"22":{"tf":1.0},"260":{"tf":1.0},"60":{"tf":1.0},"79":{"tf":1.0},"99":{"tf":1.0}}}},"df":0,"docs":{}}},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":8,"docs":{"166":{"tf":1.0},"169":{"tf":1.4142135623730951},"170":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"7":{"tf":1.0}}}}}}},"n":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"y":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":4,"docs":{"178":{"tf":1.0},"214":{"tf":1.4142135623730951},"264":{"tf":1.0},"274":{"tf":1.0}}}}}}},"d":{"/":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":2,"docs":{"104":{"tf":1.0},"122":{"tf":1.0}}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{".":{"df":0,"docs":{},"y":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":1,"docs":{"113":{"tf":1.0}},"t":{"df":0,"docs":{},"o":{"df":1,"docs":{"70":{"tf":1.0}}}}}}}},"/":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{".":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"d":{"df":0,"docs":{},"l":{"df":1,"docs":{"108":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"/":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"/":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"e":{"/":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":1,"docs":{"108":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"df":1,"docs":{"20":{"tf":1.0}}}}}},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"x":{"df":1,"docs":{"119":{"tf":1.0}}}}}}}}},"i":{"df":0,"docs":{},"o":{"df":1,"docs":{"11":{"tf":1.0}}}},"k":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"65":{"tf":1.0}}}}}}}},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{".":{"df":0,"docs":{},"h":{"df":1,"docs":{"37":{"tf":1.0}}}},"df":0,"docs":{}}}},"s":{"df":0,"docs":{},"r":{"c":{"/":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"/":{"df":0,"docs":{},"j":{"a":{"df":0,"docs":{},"v":{"a":{"df":1,"docs":{"108":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"/":{"df":0,"docs":{},"j":{"a":{"df":0,"docs":{},"v":{"a":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"/":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"/":{"<":{"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"_":{"c":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"70":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":1,"docs":{"200":{"tf":1.0}}}}}}}},"2":{"1":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":1,"docs":{"12":{"tf":1.0}},"e":{"/":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"s":{"/":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"/":{"df":0,"docs":{},"s":{"d":{"df":0,"docs":{},"k":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"13":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"s":{"d":{"df":0,"docs":{},"k":{"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":1,"docs":{"12":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":55,"docs":{"10":{"tf":1.0},"103":{"tf":1.7320508075688772},"108":{"tf":1.4142135623730951},"11":{"tf":1.0},"110":{"tf":1.0},"115":{"tf":1.4142135623730951},"116":{"tf":1.0},"117":{"tf":1.4142135623730951},"118":{"tf":2.0},"119":{"tf":1.4142135623730951},"12":{"tf":2.8284271247461903},"126":{"tf":2.0},"13":{"tf":1.4142135623730951},"131":{"tf":1.0},"133":{"tf":1.0},"145":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":2.0},"19":{"tf":1.0},"190":{"tf":1.0},"192":{"tf":1.0},"199":{"tf":1.0},"20":{"tf":1.4142135623730951},"200":{"tf":1.0},"203":{"tf":1.0},"206":{"tf":1.7320508075688772},"21":{"tf":1.4142135623730951},"210":{"tf":1.0},"217":{"tf":1.0},"218":{"tf":1.4142135623730951},"220":{"tf":1.7320508075688772},"221":{"tf":2.23606797749979},"223":{"tf":1.0},"225":{"tf":2.0},"230":{"tf":1.0},"237":{"tf":1.0},"255":{"tf":1.0},"264":{"tf":1.0},"268":{"tf":1.4142135623730951},"271":{"tf":1.0},"272":{"tf":1.0},"274":{"tf":2.0},"278":{"tf":1.0},"280":{"tf":1.0},"37":{"tf":3.1622776601683795},"38":{"tf":1.0},"39":{"tf":1.7320508075688772},"40":{"tf":1.4142135623730951},"45":{"tf":1.7320508075688772},"49":{"tf":2.23606797749979},"54":{"tf":1.7320508075688772},"69":{"tf":1.0},"70":{"tf":1.4142135623730951},"9":{"tf":1.0},"90":{"tf":1.0}},"e":{"a":{"b":{"df":0,"docs":{},"i":{"df":1,"docs":{"37":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"m":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"x":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":3,"docs":{"103":{"tf":1.4142135623730951},"108":{"tf":1.0},"70":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"200":{"tf":1.0}}}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"h":{"df":3,"docs":{"132":{"tf":1.0},"233":{"tf":1.0},"283":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"w":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"205":{"tf":1.0}}}}}},"t":{"df":1,"docs":{"37":{"tf":3.0}},"i":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":1,"docs":{"231":{"tf":1.0}}}}},"df":0,"docs":{}}},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"3":{"tf":1.0}}}},"t":{"df":0,"docs":{},"h":{"df":4,"docs":{"228":{"tf":1.0},"242":{"tf":1.0},"264":{"tf":1.0},"61":{"tf":1.0}}}},"w":{"a":{"df":0,"docs":{},"y":{"df":1,"docs":{"60":{"tf":1.0}}}},"df":0,"docs":{}}}},"p":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"81":{"tf":1.0}}}}},"df":0,"docs":{},"i":{"df":37,"docs":{"104":{"tf":1.7320508075688772},"105":{"tf":1.7320508075688772},"106":{"tf":1.4142135623730951},"107":{"tf":1.4142135623730951},"108":{"tf":1.4142135623730951},"109":{"tf":1.0},"111":{"tf":1.0},"114":{"tf":1.0},"116":{"tf":1.0},"118":{"tf":1.0},"134":{"tf":1.0},"139":{"tf":1.0},"141":{"tf":1.4142135623730951},"142":{"tf":2.0},"143":{"tf":1.0},"145":{"tf":2.449489742783178},"146":{"tf":2.0},"166":{"tf":1.0},"197":{"tf":1.0},"200":{"tf":2.0},"21":{"tf":1.0},"226":{"tf":1.0},"232":{"tf":3.0},"236":{"tf":1.4142135623730951},"252":{"tf":1.4142135623730951},"281":{"tf":1.4142135623730951},"283":{"tf":1.0},"43":{"tf":1.4142135623730951},"45":{"tf":1.4142135623730951},"46":{"tf":1.0},"69":{"tf":1.0},"7":{"tf":1.0},"70":{"tf":1.0},"73":{"tf":1.0},"78":{"tf":1.7320508075688772},"79":{"tf":1.4142135623730951},"96":{"tf":1.0}}},"k":{"df":2,"docs":{"39":{"tf":1.4142135623730951},"40":{"tf":1.0}}},"p":{"'":{"df":1,"docs":{"226":{"tf":1.0}}},"a":{"df":0,"docs":{},"r":{"df":2,"docs":{"242":{"tf":1.0},"37":{"tf":1.0}}}},"df":35,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"142":{"tf":1.0},"158":{"tf":1.0},"165":{"tf":1.0},"17":{"tf":1.0},"179":{"tf":1.0},"180":{"tf":1.0},"183":{"tf":1.0},"184":{"tf":1.0},"189":{"tf":1.0},"19":{"tf":1.0},"190":{"tf":1.0},"194":{"tf":1.0},"20":{"tf":1.0},"200":{"tf":1.4142135623730951},"205":{"tf":1.0},"209":{"tf":1.0},"212":{"tf":1.0},"213":{"tf":2.0},"225":{"tf":1.4142135623730951},"228":{"tf":2.0},"229":{"tf":1.4142135623730951},"233":{"tf":2.0},"236":{"tf":1.0},"242":{"tf":1.7320508075688772},"250":{"tf":1.0},"254":{"tf":1.0},"255":{"tf":1.4142135623730951},"257":{"tf":1.4142135623730951},"273":{"tf":1.0},"40":{"tf":1.0},"49":{"tf":1.0},"52":{"tf":1.0},"84":{"tf":1.0}},"e":{"a":{"df":0,"docs":{},"r":{"df":8,"docs":{"117":{"tf":1.0},"172":{"tf":1.4142135623730951},"228":{"tf":1.0},"249":{"tf":2.23606797749979},"37":{"tf":1.0},"64":{"tf":1.0},"73":{"tf":1.4142135623730951},"86":{"tf":1.0}}}},"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"98":{"tf":1.0}},"i":{"df":0,"docs":{},"x":{"df":2,"docs":{"173":{"tf":1.0},"60":{"tf":1.0}}}}},"df":0,"docs":{}}},"l":{"df":9,"docs":{"161":{"tf":1.0},"162":{"tf":1.0},"163":{"tf":1.0},"169":{"tf":1.0},"170":{"tf":1.4142135623730951},"171":{"tf":1.0},"172":{"tf":1.0},"175":{"tf":1.0},"252":{"tf":1.0}},"e":{"'":{"df":2,"docs":{"274":{"tf":1.0},"46":{"tf":1.0}}},"df":0,"docs":{}},"i":{"c":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"'":{"df":1,"docs":{"221":{"tf":1.0}}},".":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"z":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"283":{"tf":1.0}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"x":{"a":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"l":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"df":1,"docs":{"283":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"\\":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"257":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"’":{"df":1,"docs":{"283":{"tf":1.0}}}}}}}},"df":99,"docs":{"0":{"tf":1.7320508075688772},"1":{"tf":1.0},"10":{"tf":1.0},"11":{"tf":1.4142135623730951},"114":{"tf":1.0},"118":{"tf":1.7320508075688772},"119":{"tf":1.4142135623730951},"12":{"tf":2.0},"120":{"tf":1.0},"122":{"tf":2.0},"123":{"tf":1.0},"124":{"tf":3.0},"125":{"tf":1.7320508075688772},"130":{"tf":1.0},"14":{"tf":1.4142135623730951},"15":{"tf":1.4142135623730951},"16":{"tf":1.0},"161":{"tf":1.7320508075688772},"162":{"tf":1.0},"164":{"tf":1.0},"165":{"tf":1.7320508075688772},"167":{"tf":3.1622776601683795},"175":{"tf":1.4142135623730951},"177":{"tf":1.0},"18":{"tf":2.23606797749979},"180":{"tf":1.0},"184":{"tf":1.0},"186":{"tf":1.0},"187":{"tf":1.0},"189":{"tf":1.4142135623730951},"19":{"tf":1.0},"190":{"tf":1.0},"194":{"tf":1.4142135623730951},"197":{"tf":1.4142135623730951},"2":{"tf":1.0},"20":{"tf":2.0},"200":{"tf":2.6457513110645907},"201":{"tf":2.449489742783178},"203":{"tf":1.4142135623730951},"204":{"tf":1.0},"21":{"tf":1.4142135623730951},"218":{"tf":1.0},"22":{"tf":2.0},"220":{"tf":1.4142135623730951},"221":{"tf":1.0},"222":{"tf":1.4142135623730951},"225":{"tf":2.0},"228":{"tf":1.4142135623730951},"23":{"tf":1.7320508075688772},"232":{"tf":2.23606797749979},"24":{"tf":1.0},"242":{"tf":1.0},"244":{"tf":1.7320508075688772},"248":{"tf":1.0},"25":{"tf":1.7320508075688772},"252":{"tf":1.4142135623730951},"253":{"tf":1.7320508075688772},"256":{"tf":1.0},"258":{"tf":1.0},"259":{"tf":1.4142135623730951},"260":{"tf":1.4142135623730951},"262":{"tf":1.0},"265":{"tf":1.0},"268":{"tf":2.23606797749979},"274":{"tf":2.23606797749979},"276":{"tf":1.0},"28":{"tf":1.0},"283":{"tf":2.6457513110645907},"284":{"tf":1.0},"29":{"tf":1.4142135623730951},"3":{"tf":1.4142135623730951},"30":{"tf":1.7320508075688772},"32":{"tf":1.4142135623730951},"33":{"tf":1.0},"34":{"tf":1.7320508075688772},"35":{"tf":1.7320508075688772},"37":{"tf":1.7320508075688772},"38":{"tf":1.0},"39":{"tf":1.7320508075688772},"40":{"tf":1.7320508075688772},"41":{"tf":1.0},"49":{"tf":1.4142135623730951},"52":{"tf":1.4142135623730951},"54":{"tf":1.4142135623730951},"55":{"tf":1.0},"56":{"tf":1.0},"64":{"tf":1.0},"68":{"tf":1.4142135623730951},"69":{"tf":1.0},"7":{"tf":1.0},"73":{"tf":1.0},"74":{"tf":1.0},"77":{"tf":1.0},"78":{"tf":1.0},"8":{"tf":1.0},"81":{"tf":1.0},"82":{"tf":1.0},"84":{"tf":1.0},"9":{"tf":1.7320508075688772}}},"df":11,"docs":{"128":{"tf":1.0},"140":{"tf":1.0},"179":{"tf":1.0},"22":{"tf":2.0},"225":{"tf":1.0},"249":{"tf":1.0},"262":{"tf":1.0},"274":{"tf":1.0},"64":{"tf":1.0},"7":{"tf":1.0},"98":{"tf":1.0}}},"y":{"_":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":3,"docs":{"247":{"tf":1.4142135623730951},"249":{"tf":1.0},"88":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"o":{"a":{"c":{"df":0,"docs":{},"h":{"df":13,"docs":{"101":{"tf":1.0},"115":{"tf":1.0},"140":{"tf":1.4142135623730951},"141":{"tf":1.4142135623730951},"143":{"tf":1.0},"145":{"tf":1.0},"146":{"tf":1.4142135623730951},"171":{"tf":1.0},"205":{"tf":1.0},"220":{"tf":1.0},"241":{"tf":1.0},"251":{"tf":1.0},"60":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":4,"docs":{"189":{"tf":1.0},"280":{"tf":1.0},"45":{"tf":1.0},"6":{"tf":1.0}}}}},"v":{"df":5,"docs":{"118":{"tf":1.4142135623730951},"119":{"tf":1.4142135623730951},"189":{"tf":1.0},"270":{"tf":1.0},"6":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":5,"docs":{"169":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"222":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"d":{"df":0,"docs":{},"l":{"df":1,"docs":{"22":{"tf":1.7320508075688772}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"r":{"c":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"}":{"/":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"22":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"v":{"c":{"df":2,"docs":{"276":{"tf":1.4142135623730951},"277":{"tf":1.0}}},"df":0,"docs":{}}}},"t":{"df":2,"docs":{"11":{"tf":3.4641016151377544},"13":{"tf":1.0}}}},"r":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":13,"docs":{"102":{"tf":1.0},"134":{"tf":2.0},"135":{"tf":1.0},"136":{"tf":1.0},"137":{"tf":1.4142135623730951},"17":{"tf":1.4142135623730951},"223":{"tf":1.0},"225":{"tf":1.4142135623730951},"227":{"tf":1.0},"244":{"tf":1.0},"252":{"tf":2.23606797749979},"282":{"tf":1.7320508075688772},"58":{"tf":1.0}}}}}},"df":0,"docs":{}}},"v":{"df":4,"docs":{"220":{"tf":1.0},"223":{"tf":1.0},"271":{"tf":1.0},"279":{"tf":1.0}},"e":{".":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{".":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":1,"docs":{"273":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"e":{"a":{"df":2,"docs":{"273":{"tf":1.7320508075688772},"63":{"tf":1.0}}},"df":0,"docs":{},"n":{"'":{"df":0,"docs":{},"t":{"df":9,"docs":{"166":{"tf":1.0},"225":{"tf":1.0},"226":{"tf":1.0},"260":{"tf":1.0},"268":{"tf":1.0},"58":{"tf":1.0},"60":{"tf":1.0},"79":{"tf":1.0},"84":{"tf":1.0}}}},"df":0,"docs":{}}},"g":{"df":1,"docs":{"58":{"tf":1.0}}},"m":{"6":{"4":{"df":1,"docs":{"175":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{},"v":{"7":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":12,"docs":{"115":{"tf":1.0},"116":{"tf":1.0},"150":{"tf":1.0},"169":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"204":{"tf":1.4142135623730951},"233":{"tf":1.0},"242":{"tf":1.4142135623730951},"261":{"tf":1.0},"51":{"tf":1.0},"79":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}},"t":{"df":2,"docs":{"206":{"tf":1.0},"214":{"tf":1.0}},"i":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"163":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"f":{"a":{"c":{"df":0,"docs":{},"t":{"df":20,"docs":{"122":{"tf":1.0},"14":{"tf":1.0},"163":{"tf":1.0},"167":{"tf":1.0},"169":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.4142135623730951},"172":{"tf":1.4142135623730951},"175":{"tf":1.0},"220":{"tf":1.0},"267":{"tf":1.4142135623730951},"271":{"tf":1.0},"272":{"tf":1.0},"273":{"tf":1.7320508075688772},"274":{"tf":2.23606797749979},"279":{"tf":1.4142135623730951},"280":{"tf":2.23606797749979},"37":{"tf":1.4142135623730951},"40":{"tf":1.0},"64":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"s":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"242":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"51":{"tf":1.0}}}},"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"60":{"tf":1.0}}},"df":0,"docs":{}},"k":{"df":5,"docs":{"122":{"tf":2.0},"161":{"tf":1.0},"233":{"tf":1.0},"242":{"tf":1.0},"274":{"tf":1.0}}},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"104":{"tf":1.0}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"r":{"df":1,"docs":{"258":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":1,"docs":{"18":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":1,"docs":{"62":{"tf":1.0}}}}},"df":1,"docs":{"43":{"tf":1.4142135623730951}}}}},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"254":{"tf":1.0}}}}},"o":{"c":{"df":0,"docs":{},"i":{"df":2,"docs":{"115":{"tf":1.0},"283":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"m":{"df":17,"docs":{"114":{"tf":1.0},"12":{"tf":1.0},"145":{"tf":1.0},"151":{"tf":1.0},"154":{"tf":1.0},"20":{"tf":1.0},"21":{"tf":1.0},"210":{"tf":1.0},"225":{"tf":1.0},"23":{"tf":1.0},"25":{"tf":1.0},"29":{"tf":1.0},"30":{"tf":1.0},"32":{"tf":1.0},"34":{"tf":1.0},"35":{"tf":1.0},"86":{"tf":1.0}},"p":{"df":0,"docs":{},"t":{"df":2,"docs":{"138":{"tf":1.0},"210":{"tf":1.0}}}}},"r":{"df":2,"docs":{"274":{"tf":1.0},"64":{"tf":1.0}}}}},"y":{"df":0,"docs":{},"n":{"c":{"df":4,"docs":{"115":{"tf":1.0},"193":{"tf":1.0},"194":{"tf":1.0},"197":{"tf":1.0}}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"t":{"a":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"190":{"tf":1.0}}},"k":{"df":1,"docs":{"201":{"tf":1.7320508075688772}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":13,"docs":{"132":{"tf":1.0},"146":{"tf":1.0},"167":{"tf":1.0},"19":{"tf":1.0},"200":{"tf":1.0},"203":{"tf":1.0},"204":{"tf":1.0},"207":{"tf":1.0},"226":{"tf":1.0},"245":{"tf":1.0},"25":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0}}}}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"170":{"tf":1.0},"197":{"tf":1.0},"198":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"193":{"tf":1.0},"194":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"u":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"274":{"tf":1.0},"64":{"tf":1.0}}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"226":{"tf":1.0}}}}}}},"t":{"df":0,"docs":{},"h":{"df":4,"docs":{"226":{"tf":1.0},"236":{"tf":1.0},"237":{"tf":1.0},"239":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":5,"docs":{"226":{"tf":1.0},"245":{"tf":1.0},"254":{"tf":1.0},"274":{"tf":1.0},"275":{"tf":1.0}},"i":{"c":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"242":{"tf":1.0}}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"283":{"tf":1.7320508075688772}}}}},"o":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"61":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":8,"docs":{"104":{"tf":1.0},"116":{"tf":1.0},"12":{"tf":1.0},"16":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":1.0},"20":{"tf":1.7320508075688772},"22":{"tf":1.4142135623730951}},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":5,"docs":{"140":{"tf":1.0},"145":{"tf":1.0},"244":{"tf":1.0},"257":{"tf":1.0},"264":{"tf":1.7320508075688772}},"l":{"/":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"245":{"tf":1.0}}}}}}}},":":{":":{"d":{"b":{":":{":":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{":":{":":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"245":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"d":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"_":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"245":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":5,"docs":{"103":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0}}}}}}},"m":{"a":{"df":0,"docs":{},"t":{"df":12,"docs":{"107":{"tf":1.0},"113":{"tf":1.0},"167":{"tf":1.0},"18":{"tf":1.4142135623730951},"19":{"tf":1.0},"266":{"tf":1.0},"61":{"tf":1.0},"70":{"tf":1.0},"72":{"tf":1.0},"73":{"tf":1.0},"74":{"tf":1.0},"90":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"269":{"tf":1.0}}}},"df":0,"docs":{}}}},"u":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"l":{"_":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"_":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"d":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"19":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":2,"docs":{"269":{"tf":1.0},"53":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}}}}}}}},"u":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"109":{"tf":1.0}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"s":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":3,"docs":{"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0}}}}}},"t":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"269":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"7":{"tf":1.0}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}},"df":10,"docs":{"101":{"tf":1.0},"103":{"tf":1.0},"111":{"tf":1.0},"167":{"tf":1.0},"22":{"tf":1.4142135623730951},"25":{"tf":1.0},"27":{"tf":1.0},"274":{"tf":2.23606797749979},"49":{"tf":1.4142135623730951},"7":{"tf":1.0}}},"p":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{".":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":2,"docs":{"18":{"tf":1.4142135623730951},"22":{"tf":1.7320508075688772}}}}}},"df":0,"docs":{}},"df":1,"docs":{"118":{"tf":1.0}},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"l":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":1,"docs":{"22":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"61":{"tf":1.0}},"s":{"/":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"61":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"v":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":24,"docs":{"1":{"tf":1.0},"104":{"tf":1.0},"107":{"tf":1.0},"11":{"tf":1.0},"114":{"tf":1.0},"134":{"tf":1.4142135623730951},"138":{"tf":1.0},"142":{"tf":1.0},"146":{"tf":1.0},"165":{"tf":1.0},"177":{"tf":1.4142135623730951},"178":{"tf":1.0},"179":{"tf":1.0},"187":{"tf":1.0},"192":{"tf":1.0},"198":{"tf":1.0},"228":{"tf":1.0},"252":{"tf":1.0},"260":{"tf":1.0},"274":{"tf":1.7320508075688772},"282":{"tf":1.0},"45":{"tf":1.0},"5":{"tf":1.0},"62":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"204":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"i":{"d":{"df":23,"docs":{"116":{"tf":1.0},"126":{"tf":1.0},"128":{"tf":1.0},"132":{"tf":1.0},"141":{"tf":1.0},"150":{"tf":1.0},"165":{"tf":1.0},"172":{"tf":1.0},"197":{"tf":1.4142135623730951},"22":{"tf":1.0},"220":{"tf":1.0},"222":{"tf":1.0},"225":{"tf":1.0},"227":{"tf":1.0},"229":{"tf":1.4142135623730951},"263":{"tf":1.4142135623730951},"264":{"tf":1.0},"46":{"tf":1.0},"57":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"69":{"tf":1.0},"99":{"tf":1.0}}},"df":0,"docs":{}}}},"w":{"a":{"df":0,"docs":{},"r":{"df":3,"docs":{"133":{"tf":1.0},"189":{"tf":1.0},"233":{"tf":1.0}}},"y":{"df":3,"docs":{"117":{"tf":1.0},"170":{"tf":1.0},"212":{"tf":1.0}}}},"df":0,"docs":{}}},"b":{"/":{"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{".":{"df":0,"docs":{},"m":{"d":{"df":1,"docs":{"268":{"tf":2.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"a":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"/":{"c":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":1,"docs":{"62":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{},"s":{"df":0,"docs":{},"r":{"c":{"/":{"d":{"b":{"/":{"d":{"b":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"62":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"62":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":2,"docs":{"268":{"tf":2.0},"269":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}}}}}}}}},"1":{"df":2,"docs":{"152":{"tf":1.0},"157":{"tf":1.0}}},"2":{"df":1,"docs":{"158":{"tf":1.0}}},"3":{"df":1,"docs":{"159":{"tf":1.0}}},"a":{"c":{"df":0,"docs":{},"k":{"df":10,"docs":{"103":{"tf":1.4142135623730951},"107":{"tf":1.0},"115":{"tf":1.4142135623730951},"158":{"tf":1.0},"214":{"tf":1.0},"242":{"tf":1.0},"246":{"tf":1.0},"249":{"tf":2.0},"274":{"tf":1.0},"283":{"tf":1.0}},"e":{"d":{"df":0,"docs":{},"o":{"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":1,"docs":{"242":{"tf":1.0}}}}}},"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"51":{"tf":1.0}}},"df":0,"docs":{}}},"g":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":3,"docs":{"115":{"tf":1.4142135623730951},"203":{"tf":1.0},"58":{"tf":1.0}}},"df":0,"docs":{}}}}}},"o":{"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":6,"docs":{"225":{"tf":1.0},"226":{"tf":1.4142135623730951},"236":{"tf":1.0},"239":{"tf":1.0},"242":{"tf":1.0},"245":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"p":{"df":1,"docs":{"158":{"tf":1.7320508075688772}}}},"w":{"a":{"df":0,"docs":{},"r":{"d":{"df":2,"docs":{"116":{"tf":1.0},"268":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"d":{"df":19,"docs":{"115":{"tf":1.0},"150":{"tf":1.0},"154":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.4142135623730951},"157":{"tf":1.0},"158":{"tf":1.7320508075688772},"159":{"tf":1.4142135623730951},"169":{"tf":1.7320508075688772},"170":{"tf":2.0},"171":{"tf":1.7320508075688772},"172":{"tf":1.0},"182":{"tf":1.0},"183":{"tf":2.0},"184":{"tf":2.0},"212":{"tf":2.0},"213":{"tf":1.4142135623730951},"226":{"tf":1.0},"81":{"tf":1.0}}},"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"64":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"n":{"d":{"df":1,"docs":{"283":{"tf":1.0}}},"df":0,"docs":{}},"r":{"df":1,"docs":{"205":{"tf":1.7320508075688772}}},"s":{"df":0,"docs":{},"e":{"df":18,"docs":{"163":{"tf":1.0},"164":{"tf":1.0},"172":{"tf":1.0},"197":{"tf":1.0},"207":{"tf":1.4142135623730951},"208":{"tf":1.0},"210":{"tf":1.0},"213":{"tf":1.0},"214":{"tf":1.4142135623730951},"215":{"tf":1.4142135623730951},"217":{"tf":1.0},"226":{"tf":1.0},"233":{"tf":1.0},"253":{"tf":1.0},"283":{"tf":1.0},"37":{"tf":1.0},"7":{"tf":1.0},"82":{"tf":1.4142135623730951}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"175":{"tf":1.0}}}}}},"h":{"/":{"df":0,"docs":{},"z":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"11":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{},"r":{"c":{"df":2,"docs":{"11":{"tf":1.0},"12":{"tf":1.0}}},"df":0,"docs":{}}},"i":{"c":{"df":2,"docs":{"274":{"tf":1.0},"37":{"tf":1.0}}},"df":2,"docs":{"134":{"tf":1.0},"59":{"tf":1.0}}}}},"df":8,"docs":{"149":{"tf":1.0},"151":{"tf":1.0},"152":{"tf":1.0},"163":{"tf":1.0},"170":{"tf":1.0},"179":{"tf":1.0},"180":{"tf":1.0},"280":{"tf":1.0}},"e":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":5,"docs":{"226":{"tf":1.0},"263":{"tf":1.0},"274":{"tf":1.0},"58":{"tf":1.0},"64":{"tf":1.0}}}}},"df":19,"docs":{"115":{"tf":1.0},"152":{"tf":1.0},"159":{"tf":1.0},"162":{"tf":1.0},"167":{"tf":1.0},"184":{"tf":1.0},"187":{"tf":1.0},"192":{"tf":1.0},"193":{"tf":1.0},"197":{"tf":1.0},"227":{"tf":1.0},"231":{"tf":1.0},"233":{"tf":1.0},"242":{"tf":1.4142135623730951},"250":{"tf":1.4142135623730951},"274":{"tf":1.4142135623730951},"60":{"tf":1.0},"78":{"tf":1.0},"84":{"tf":1.0}},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":19,"docs":{"11":{"tf":1.4142135623730951},"118":{"tf":1.0},"119":{"tf":1.0},"12":{"tf":1.0},"120":{"tf":1.0},"122":{"tf":1.0},"145":{"tf":1.0},"177":{"tf":1.0},"20":{"tf":1.4142135623730951},"204":{"tf":1.0},"213":{"tf":1.0},"245":{"tf":1.4142135623730951},"256":{"tf":1.4142135623730951},"259":{"tf":1.0},"266":{"tf":1.0},"58":{"tf":1.4142135623730951},"6":{"tf":1.0},"62":{"tf":1.4142135623730951},"7":{"tf":1.7320508075688772}},"e":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"26":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"o":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":1,"docs":{"283":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"p":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":1,"docs":{"283":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":2,"docs":{"279":{"tf":1.0},"59":{"tf":1.0}}}}},"h":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":2,"docs":{"245":{"tf":1.0},"283":{"tf":1.0}}}},"v":{"df":2,"docs":{"60":{"tf":1.0},"61":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":4,"docs":{"140":{"tf":1.0},"156":{"tf":1.0},"61":{"tf":1.7320508075688772},"84":{"tf":1.0}}},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"37":{"tf":1.0}}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"25":{"tf":1.0},"62":{"tf":1.0}}},"df":0,"docs":{}}}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":3,"docs":{"194":{"tf":1.0},"233":{"tf":1.4142135623730951},"79":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"w":{"df":18,"docs":{"105":{"tf":1.0},"11":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":1.0},"145":{"tf":1.0},"200":{"tf":1.0},"203":{"tf":1.0},"226":{"tf":1.0},"241":{"tf":1.0},"242":{"tf":1.0},"25":{"tf":1.0},"259":{"tf":1.0},"264":{"tf":1.0},"274":{"tf":1.0},"279":{"tf":1.0},"32":{"tf":1.0},"59":{"tf":1.0},"6":{"tf":1.0}}}}},"n":{"c":{"df":0,"docs":{},"h":{"0":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"60":{"tf":1.0}}}},"df":1,"docs":{"60":{"tf":1.0}}},"1":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"60":{"tf":1.0}}}},"df":1,"docs":{"60":{"tf":1.0}}},"2":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"60":{"tf":1.0}}}},"df":1,"docs":{"60":{"tf":1.0}}},"df":2,"docs":{"60":{"tf":2.23606797749979},"61":{"tf":1.0}},"e":{"df":0,"docs":{},"s":{"/":{"*":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"61":{"tf":1.0}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"m":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":3,"docs":{"58":{"tf":1.4142135623730951},"59":{"tf":1.7320508075688772},"60":{"tf":2.8284271247461903}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":8,"docs":{"140":{"tf":1.0},"170":{"tf":1.0},"198":{"tf":1.0},"201":{"tf":1.0},"221":{"tf":1.0},"274":{"tf":1.0},"60":{"tf":1.0},"64":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"t":{"df":8,"docs":{"101":{"tf":1.0},"104":{"tf":1.0},"123":{"tf":1.4142135623730951},"149":{"tf":1.0},"164":{"tf":1.0},"170":{"tf":1.0},"43":{"tf":1.4142135623730951},"75":{"tf":1.0}}}},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":11,"docs":{"115":{"tf":1.4142135623730951},"124":{"tf":1.0},"165":{"tf":1.0},"167":{"tf":1.0},"170":{"tf":1.0},"196":{"tf":1.0},"197":{"tf":1.0},"200":{"tf":1.0},"43":{"tf":1.0},"62":{"tf":1.0},"79":{"tf":1.0}}}}},"w":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":23,"docs":{"104":{"tf":1.0},"106":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"150":{"tf":1.0},"165":{"tf":1.4142135623730951},"169":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"20":{"tf":1.0},"204":{"tf":1.0},"214":{"tf":1.0},"216":{"tf":1.7320508075688772},"220":{"tf":1.0},"224":{"tf":1.0},"225":{"tf":1.0},"229":{"tf":1.0},"233":{"tf":1.0},"250":{"tf":1.0},"79":{"tf":1.0},"84":{"tf":1.0},"89":{"tf":1.0}}}}}}},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"d":{"df":4,"docs":{"111":{"tf":1.0},"204":{"tf":1.0},"208":{"tf":1.0},"216":{"tf":1.0}}},"df":0,"docs":{}}}}},"i":{"df":0,"docs":{},"g":{"df":3,"docs":{"236":{"tf":1.0},"64":{"tf":1.0},"81":{"tf":1.0}},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"210":{"tf":1.0}}}}}}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"227":{"tf":1.0}}}}}},"n":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":10,"docs":{"14":{"tf":1.0},"163":{"tf":1.4142135623730951},"167":{"tf":1.0},"169":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.4142135623730951},"172":{"tf":1.4142135623730951},"175":{"tf":1.4142135623730951},"253":{"tf":1.0},"274":{"tf":1.0}}}}},"d":{"df":24,"docs":{"0":{"tf":1.0},"101":{"tf":1.4142135623730951},"103":{"tf":1.4142135623730951},"105":{"tf":1.0},"106":{"tf":1.0},"107":{"tf":1.0},"108":{"tf":2.23606797749979},"109":{"tf":2.449489742783178},"111":{"tf":1.4142135623730951},"116":{"tf":1.0},"140":{"tf":1.0},"162":{"tf":1.0},"253":{"tf":1.0},"281":{"tf":1.0},"45":{"tf":1.7320508075688772},"46":{"tf":1.7320508075688772},"69":{"tf":1.0},"7":{"tf":1.0},"70":{"tf":1.7320508075688772},"71":{"tf":1.0},"72":{"tf":1.4142135623730951},"73":{"tf":1.4142135623730951},"74":{"tf":1.0},"90":{"tf":1.4142135623730951}},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":2,"docs":{"104":{"tf":1.7320508075688772},"69":{"tf":1.4142135623730951}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"k":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"70":{"tf":1.0}}}}}}}},"s":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":1,"docs":{"73":{"tf":1.0}}}}}}}},"_":{"df":0,"docs":{},"u":{"d":{"df":0,"docs":{},"l":{"_":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"74":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":1,"docs":{"11":{"tf":1.0}}},"t":{"df":8,"docs":{"104":{"tf":1.4142135623730951},"116":{"tf":1.7320508075688772},"17":{"tf":1.0},"174":{"tf":1.0},"20":{"tf":1.0},"226":{"tf":1.0},"229":{"tf":1.0},"6":{"tf":1.0}}}},"l":{"a":{"df":0,"docs":{},"h":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"61":{"tf":1.0}}}},"df":1,"docs":{"61":{"tf":1.0}}}},"df":0,"docs":{},"o":{"b":{"df":1,"docs":{"242":{"tf":1.0}}},"c":{"df":0,"docs":{},"k":{"df":3,"docs":{"115":{"tf":1.0},"194":{"tf":1.0},"81":{"tf":1.0}},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{".":{"df":0,"docs":{},"x":{"c":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"p":{"b":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"df":1,"docs":{"36":{"tf":1.0}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":1,"docs":{"36":{"tf":1.0}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":1,"docs":{"36":{"tf":1.0}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"g":{"df":1,"docs":{"90":{"tf":1.4142135623730951}}}}},"o":{"d":{"df":0,"docs":{},"i":{"df":1,"docs":{"189":{"tf":1.0}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"115":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":2,"docs":{"45":{"tf":1.0},"46":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"k":{"df":2,"docs":{"286":{"tf":1.0},"43":{"tf":1.0}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":1,"docs":{"82":{"tf":1.0}}}}}},"m":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":10,"docs":{"226":{"tf":1.4142135623730951},"228":{"tf":1.0},"231":{"tf":1.0},"232":{"tf":1.0},"242":{"tf":1.4142135623730951},"257":{"tf":1.0},"264":{"tf":1.0},"62":{"tf":1.0},"83":{"tf":1.0},"86":{"tf":1.0}},"g":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"62":{"tf":2.0}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"l":{"df":2,"docs":{"116":{"tf":1.4142135623730951},"242":{"tf":1.0}}},"t":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":1,"docs":{"274":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"t":{"df":0,"docs":{},"h":{"df":21,"docs":{"117":{"tf":1.0},"123":{"tf":1.0},"145":{"tf":2.0},"146":{"tf":1.0},"17":{"tf":1.0},"175":{"tf":1.0},"18":{"tf":1.0},"196":{"tf":1.0},"198":{"tf":1.4142135623730951},"200":{"tf":1.0},"221":{"tf":1.0},"222":{"tf":1.0},"226":{"tf":1.0},"241":{"tf":1.4142135623730951},"249":{"tf":1.4142135623730951},"271":{"tf":1.0},"274":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"7":{"tf":1.0},"86":{"tf":1.4142135623730951}}},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":2,"docs":{"126":{"tf":1.0},"60":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"n":{"d":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":4,"docs":{"229":{"tf":1.0},"43":{"tf":1.0},"51":{"tf":1.0},"79":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"x":{"df":2,"docs":{"126":{"tf":1.0},"128":{"tf":1.0}}}},"r":{"a":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":15,"docs":{"118":{"tf":1.0},"25":{"tf":1.0},"26":{"tf":2.0},"267":{"tf":1.4142135623730951},"268":{"tf":2.0},"269":{"tf":1.4142135623730951},"270":{"tf":1.4142135623730951},"274":{"tf":2.23606797749979},"31":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"38":{"tf":1.7320508075688772},"39":{"tf":3.4641016151377544},"40":{"tf":2.449489742783178},"64":{"tf":1.0},"7":{"tf":1.4142135623730951}}}},"d":{"df":2,"docs":{"105":{"tf":1.0},"64":{"tf":1.0}}},"df":0,"docs":{}},"v":{"df":0,"docs":{},"e":{"df":1,"docs":{"81":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"k":{"a":{"df":0,"docs":{},"g":{"df":4,"docs":{"118":{"tf":1.0},"121":{"tf":1.0},"166":{"tf":1.0},"278":{"tf":1.0}}}},"df":12,"docs":{"104":{"tf":1.0},"105":{"tf":1.0},"116":{"tf":1.0},"118":{"tf":1.7320508075688772},"140":{"tf":1.0},"141":{"tf":1.0},"166":{"tf":1.0},"21":{"tf":1.0},"235":{"tf":1.0},"39":{"tf":1.4142135623730951},"40":{"tf":1.7320508075688772},"7":{"tf":1.0}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"117":{"tf":2.449489742783178}}}}}}}}},"df":0,"docs":{},"w":{"df":2,"docs":{"11":{"tf":1.0},"37":{"tf":1.0}}}},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"252":{"tf":1.0}}}}}},"n":{"df":0,"docs":{},"g":{"df":3,"docs":{"117":{"tf":1.0},"59":{"tf":1.0},"64":{"tf":1.0}}}}},"o":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"161":{"tf":1.0}}}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"171":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"266":{"tf":1.0}}}}},"w":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":8,"docs":{"134":{"tf":1.0},"186":{"tf":1.0},"187":{"tf":1.4142135623730951},"188":{"tf":1.0},"190":{"tf":1.0},"192":{"tf":1.0},"194":{"tf":1.0},"197":{"tf":1.7320508075688772}}}}}}}},"u":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":3,"docs":{"189":{"tf":1.0},"214":{"tf":1.0},"216":{"tf":1.7320508075688772}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"105":{"tf":1.0},"106":{"tf":1.4142135623730951}}}}}},"g":{"df":53,"docs":{"1":{"tf":1.0},"100":{"tf":1.0},"106":{"tf":1.0},"109":{"tf":1.0},"116":{"tf":1.7320508075688772},"117":{"tf":1.0},"119":{"tf":1.0},"122":{"tf":1.0},"123":{"tf":1.0},"124":{"tf":1.4142135623730951},"127":{"tf":1.0},"133":{"tf":1.0},"138":{"tf":1.0},"147":{"tf":1.0},"15":{"tf":1.0},"159":{"tf":1.0},"171":{"tf":1.0},"174":{"tf":1.0},"175":{"tf":1.0},"185":{"tf":1.0},"201":{"tf":1.0},"218":{"tf":1.0},"22":{"tf":1.0},"224":{"tf":1.0},"228":{"tf":1.0},"242":{"tf":1.0},"250":{"tf":1.0},"253":{"tf":1.0},"258":{"tf":1.0},"259":{"tf":1.0},"261":{"tf":1.0},"263":{"tf":1.0},"264":{"tf":1.0},"273":{"tf":1.0},"277":{"tf":1.0},"282":{"tf":1.0},"283":{"tf":1.0},"286":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.4142135623730951},"4":{"tf":1.0},"40":{"tf":1.0},"43":{"tf":1.0},"51":{"tf":1.0},"52":{"tf":1.0},"56":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0},"67":{"tf":1.0},"74":{"tf":1.0},"8":{"tf":1.0},"90":{"tf":1.0}},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"df":2,"docs":{"1":{"tf":1.0},"261":{"tf":1.0}}},"df":0,"docs":{}}}}}},"i":{"df":0,"docs":{},"l":{"d":{".":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"d":{"df":0,"docs":{},"l":{"df":9,"docs":{"103":{"tf":1.4142135623730951},"108":{"tf":1.0},"117":{"tf":1.0},"20":{"tf":1.0},"22":{"tf":1.0},"224":{"tf":1.4142135623730951},"37":{"tf":1.0},"45":{"tf":1.0},"70":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"r":{"df":1,"docs":{"105":{"tf":1.0}}}},"/":{"d":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"s":{"/":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":1,"docs":{"286":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"_":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"175":{"tf":1.0}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"105":{"tf":1.0}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":2,"docs":{"113":{"tf":1.0},"70":{"tf":1.0}}}}}}}},"df":78,"docs":{"10":{"tf":2.0},"101":{"tf":1.4142135623730951},"105":{"tf":1.0},"108":{"tf":1.7320508075688772},"109":{"tf":2.0},"11":{"tf":3.872983346207417},"114":{"tf":1.4142135623730951},"118":{"tf":1.0},"119":{"tf":1.4142135623730951},"12":{"tf":1.4142135623730951},"122":{"tf":2.6457513110645907},"124":{"tf":1.0},"13":{"tf":1.0},"14":{"tf":1.7320508075688772},"140":{"tf":1.0},"15":{"tf":1.4142135623730951},"161":{"tf":1.4142135623730951},"163":{"tf":2.23606797749979},"165":{"tf":1.4142135623730951},"166":{"tf":1.0},"167":{"tf":2.0},"169":{"tf":1.7320508075688772},"17":{"tf":1.0},"170":{"tf":2.23606797749979},"171":{"tf":1.0},"175":{"tf":2.23606797749979},"18":{"tf":2.0},"19":{"tf":1.0},"197":{"tf":1.0},"20":{"tf":1.0},"21":{"tf":1.0},"22":{"tf":2.6457513110645907},"221":{"tf":1.0},"224":{"tf":1.7320508075688772},"23":{"tf":1.7320508075688772},"233":{"tf":1.0},"237":{"tf":1.0},"24":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951},"252":{"tf":2.0},"253":{"tf":1.0},"262":{"tf":1.0},"263":{"tf":1.0},"266":{"tf":1.7320508075688772},"267":{"tf":1.7320508075688772},"271":{"tf":2.23606797749979},"272":{"tf":1.4142135623730951},"274":{"tf":3.7416573867739413},"278":{"tf":1.4142135623730951},"279":{"tf":2.449489742783178},"28":{"tf":2.0},"280":{"tf":1.4142135623730951},"282":{"tf":2.449489742783178},"285":{"tf":1.0},"286":{"tf":1.4142135623730951},"31":{"tf":1.0},"32":{"tf":1.4142135623730951},"33":{"tf":2.0},"36":{"tf":1.4142135623730951},"37":{"tf":2.23606797749979},"38":{"tf":1.7320508075688772},"39":{"tf":2.6457513110645907},"40":{"tf":3.0},"49":{"tf":1.4142135623730951},"5":{"tf":1.7320508075688772},"58":{"tf":1.7320508075688772},"59":{"tf":1.7320508075688772},"61":{"tf":1.0},"64":{"tf":1.7320508075688772},"67":{"tf":1.0},"69":{"tf":1.7320508075688772},"70":{"tf":1.7320508075688772},"73":{"tf":2.23606797749979},"75":{"tf":2.0},"77":{"tf":1.4142135623730951},"81":{"tf":1.0},"9":{"tf":1.4142135623730951},"90":{"tf":1.4142135623730951}},"s":{"df":0,"docs":{},"r":{"c":{"/":{"df":0,"docs":{},"s":{"df":0,"docs":{},"r":{"c":{"/":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"/":{"df":0,"docs":{},"j":{"a":{"df":0,"docs":{},"v":{"a":{"/":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"k":{"df":0,"docs":{},"t":{"df":1,"docs":{"20":{"tf":1.0}}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{},"t":{"df":24,"docs":{"0":{"tf":1.0},"11":{"tf":1.0},"119":{"tf":1.0},"13":{"tf":1.0},"14":{"tf":1.0},"167":{"tf":2.0},"174":{"tf":1.0},"175":{"tf":1.4142135623730951},"222":{"tf":1.4142135623730951},"227":{"tf":1.0},"251":{"tf":1.4142135623730951},"271":{"tf":1.0},"274":{"tf":1.4142135623730951},"28":{"tf":1.0},"280":{"tf":1.0},"282":{"tf":1.0},"286":{"tf":1.4142135623730951},"33":{"tf":1.0},"37":{"tf":1.7320508075688772},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0}},"i":{"df":0,"docs":{},"n":{"df":3,"docs":{"43":{"tf":1.0},"69":{"tf":1.0},"92":{"tf":1.0}}}}}}},"m":{"df":0,"docs":{},"p":{"df":5,"docs":{"119":{"tf":1.7320508075688772},"263":{"tf":1.4142135623730951},"270":{"tf":1.0},"279":{"tf":1.4142135623730951},"82":{"tf":1.4142135623730951}}}},"n":{"c":{"df":0,"docs":{},"h":{"df":2,"docs":{"104":{"tf":1.0},"111":{"tf":1.0}}}},"d":{"df":0,"docs":{},"l":{"df":13,"docs":{"161":{"tf":1.0},"163":{"tf":1.0},"167":{"tf":2.0},"169":{"tf":1.0},"170":{"tf":1.4142135623730951},"171":{"tf":1.0},"172":{"tf":1.0},"179":{"tf":1.4142135623730951},"180":{"tf":1.0},"183":{"tf":1.0},"185":{"tf":1.0},"252":{"tf":1.0},"73":{"tf":1.0}}}},"df":0,"docs":{}},"r":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"241":{"tf":1.0}}}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"i":{"df":1,"docs":{"256":{"tf":1.0}}},"t":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"282":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"117":{"tf":1.0}}}}}}}},"c":{"8":{"9":{"df":1,"docs":{"116":{"tf":1.0}}},"df":0,"docs":{}},"a":{"c":{"df":0,"docs":{},"h":{"df":7,"docs":{"11":{"tf":1.0},"189":{"tf":1.0},"194":{"tf":1.0},"201":{"tf":2.0},"25":{"tf":2.23606797749979},"26":{"tf":2.0},"59":{"tf":1.0}},"e":{"_":{"df":0,"docs":{},"s":{"df":1,"docs":{"264":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"l":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":3,"docs":{"246":{"tf":1.0},"249":{"tf":1.0},"7":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"268":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"l":{"b":{"a":{"c":{"df":0,"docs":{},"k":{"df":4,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"115":{"tf":2.449489742783178},"116":{"tf":1.4142135623730951}},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"115":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":20,"docs":{"101":{"tf":1.0},"111":{"tf":1.0},"115":{"tf":2.449489742783178},"126":{"tf":1.4142135623730951},"13":{"tf":1.0},"131":{"tf":1.0},"145":{"tf":1.7320508075688772},"147":{"tf":1.0},"201":{"tf":1.0},"222":{"tf":1.0},"241":{"tf":1.4142135623730951},"242":{"tf":1.4142135623730951},"244":{"tf":1.0},"245":{"tf":1.7320508075688772},"283":{"tf":1.7320508075688772},"77":{"tf":1.4142135623730951},"78":{"tf":1.0},"81":{"tf":1.0},"84":{"tf":1.0},"90":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"82":{"tf":1.0}}}}}},"m":{"df":0,"docs":{},"e":{"df":2,"docs":{"256":{"tf":1.0},"60":{"tf":1.0}},"l":{"c":{"a":{"df":0,"docs":{},"s":{"df":1,"docs":{"108":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"n":{"'":{"df":0,"docs":{},"t":{"df":6,"docs":{"128":{"tf":1.0},"175":{"tf":1.0},"194":{"tf":1.0},"226":{"tf":1.0},"231":{"tf":1.4142135623730951},"82":{"tf":1.0}}}},"d":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"267":{"tf":1.7320508075688772},"84":{"tf":1.0}}},"df":0,"docs":{}}},"df":1,"docs":{"227":{"tf":1.0}},"o":{"df":0,"docs":{},"n":{"df":5,"docs":{"228":{"tf":1.0},"232":{"tf":1.0},"233":{"tf":1.0},"242":{"tf":1.0},"263":{"tf":1.0}}}}},"p":{"a":{"b":{"df":0,"docs":{},"l":{"df":13,"docs":{"124":{"tf":1.0},"190":{"tf":1.0},"192":{"tf":1.0},"193":{"tf":1.0},"194":{"tf":1.0},"196":{"tf":1.0},"197":{"tf":1.0},"201":{"tf":2.23606797749979},"22":{"tf":1.0},"228":{"tf":1.0},"262":{"tf":1.0},"283":{"tf":1.0},"62":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"104":{"tf":1.0}}}},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":2,"docs":{"101":{"tf":1.0},"242":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"e":{"df":7,"docs":{"132":{"tf":1.0},"228":{"tf":1.4142135623730951},"232":{"tf":1.0},"238":{"tf":1.0},"58":{"tf":1.4142135623730951},"81":{"tf":1.0},"82":{"tf":1.4142135623730951}},"f":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"227":{"tf":1.0}}}}}}}},"g":{"df":0,"docs":{},"o":{".":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"df":2,"docs":{"64":{"tf":1.7320508075688772},"69":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":14,"docs":{"103":{"tf":1.7320508075688772},"105":{"tf":1.0},"123":{"tf":1.4142135623730951},"124":{"tf":1.0},"200":{"tf":1.0},"251":{"tf":1.0},"28":{"tf":1.0},"33":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.4142135623730951},"69":{"tf":1.4142135623730951},"70":{"tf":1.0},"72":{"tf":1.0},"73":{"tf":1.0}}}}}}},"df":14,"docs":{"104":{"tf":1.0},"105":{"tf":1.0},"107":{"tf":1.0},"11":{"tf":1.7320508075688772},"122":{"tf":1.0},"274":{"tf":1.0},"279":{"tf":1.4142135623730951},"286":{"tf":1.0},"43":{"tf":1.0},"58":{"tf":3.1622776601683795},"59":{"tf":1.0},"60":{"tf":1.7320508075688772},"64":{"tf":1.4142135623730951},"69":{"tf":1.4142135623730951}}}},"t":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"g":{"df":6,"docs":{"161":{"tf":1.0},"163":{"tf":2.0},"166":{"tf":1.0},"167":{"tf":2.0},"170":{"tf":1.4142135623730951},"175":{"tf":1.0}},"e":{"'":{"df":1,"docs":{"170":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"s":{"df":0,"docs":{},"e":{"df":20,"docs":{"104":{"tf":1.0},"105":{"tf":1.0},"106":{"tf":1.7320508075688772},"115":{"tf":1.4142135623730951},"116":{"tf":1.0},"152":{"tf":1.0},"156":{"tf":1.0},"17":{"tf":1.0},"187":{"tf":1.0},"194":{"tf":1.4142135623730951},"201":{"tf":1.0},"228":{"tf":1.0},"233":{"tf":1.0},"245":{"tf":1.0},"274":{"tf":1.0},"276":{"tf":1.0},"70":{"tf":1.0},"79":{"tf":1.0},"98":{"tf":1.0},"99":{"tf":1.0}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"62":{"tf":1.0}}}}}}}},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":1,"docs":{"7":{"tf":1.0}}}}},"s":{"df":11,"docs":{"104":{"tf":1.0},"113":{"tf":1.0},"115":{"tf":1.0},"117":{"tf":1.0},"201":{"tf":1.0},"212":{"tf":1.4142135623730951},"225":{"tf":1.0},"260":{"tf":1.0},"50":{"tf":1.0},"59":{"tf":1.0},"70":{"tf":1.0}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"64":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"v":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":2,"docs":{"116":{"tf":1.0},"21":{"tf":1.0}}}},"df":0,"docs":{}}}},"c":{"=":{"a":{"a":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"h":{"6":{"4":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"37":{"tf":1.0}},"p":{"df":1,"docs":{"37":{"tf":1.0}}}},"d":{"df":2,"docs":{"11":{"tf":1.4142135623730951},"25":{"tf":1.0}},"y":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"b":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"70":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":13,"docs":{"103":{"tf":2.6457513110645907},"111":{"tf":1.0},"114":{"tf":1.0},"163":{"tf":1.0},"171":{"tf":1.0},"174":{"tf":1.0},"179":{"tf":1.0},"180":{"tf":1.0},"185":{"tf":1.4142135623730951},"251":{"tf":1.0},"252":{"tf":1.0},"28":{"tf":1.0},"33":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"l":{"df":10,"docs":{"120":{"tf":1.7320508075688772},"121":{"tf":1.0},"122":{"tf":2.0},"123":{"tf":2.0},"124":{"tf":2.0},"260":{"tf":1.0},"261":{"tf":1.4142135623730951},"262":{"tf":1.4142135623730951},"263":{"tf":1.0},"274":{"tf":1.7320508075688772}}}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":1,"docs":{"64":{"tf":1.0}}}}}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":5,"docs":{"115":{"tf":1.4142135623730951},"226":{"tf":1.0},"228":{"tf":1.0},"58":{"tf":1.0},"82":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":3,"docs":{"197":{"tf":1.0},"226":{"tf":1.4142135623730951},"229":{"tf":1.0}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":1,"docs":{"201":{"tf":1.0}}}}}}},"h":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":4,"docs":{"106":{"tf":1.0},"274":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0}}}},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":4,"docs":{"115":{"tf":1.0},"228":{"tf":1.0},"229":{"tf":1.0},"6":{"tf":1.0}}}}}}},"n":{"c":{"df":4,"docs":{"159":{"tf":1.0},"19":{"tf":1.0},"209":{"tf":1.4142135623730951},"214":{"tf":1.0}}},"df":0,"docs":{},"g":{"df":65,"docs":{"10":{"tf":1.0},"104":{"tf":1.0},"105":{"tf":1.4142135623730951},"108":{"tf":1.0},"118":{"tf":1.7320508075688772},"119":{"tf":1.4142135623730951},"121":{"tf":1.0},"123":{"tf":1.4142135623730951},"124":{"tf":3.1622776601683795},"126":{"tf":1.4142135623730951},"145":{"tf":1.4142135623730951},"146":{"tf":1.0},"16":{"tf":1.0},"163":{"tf":1.0},"167":{"tf":1.0},"169":{"tf":1.7320508075688772},"170":{"tf":2.0},"171":{"tf":1.7320508075688772},"172":{"tf":1.4142135623730951},"175":{"tf":1.0},"180":{"tf":1.0},"184":{"tf":1.7320508075688772},"187":{"tf":1.0},"189":{"tf":1.0},"190":{"tf":1.7320508075688772},"193":{"tf":1.0},"197":{"tf":1.7320508075688772},"20":{"tf":1.4142135623730951},"200":{"tf":1.0},"201":{"tf":2.0},"21":{"tf":1.4142135623730951},"22":{"tf":1.0},"228":{"tf":1.0},"231":{"tf":1.4142135623730951},"233":{"tf":2.6457513110645907},"236":{"tf":1.4142135623730951},"242":{"tf":1.0},"249":{"tf":2.449489742783178},"25":{"tf":1.0},"250":{"tf":2.449489742783178},"252":{"tf":1.0},"26":{"tf":2.23606797749979},"263":{"tf":1.4142135623730951},"268":{"tf":3.0},"270":{"tf":2.0},"279":{"tf":1.0},"280":{"tf":1.0},"29":{"tf":1.0},"30":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951},"34":{"tf":1.0},"35":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"39":{"tf":1.7320508075688772},"40":{"tf":1.7320508075688772},"49":{"tf":1.0},"58":{"tf":1.4142135623730951},"64":{"tf":1.0},"7":{"tf":2.23606797749979},"81":{"tf":1.0},"82":{"tf":1.7320508075688772},"84":{"tf":2.23606797749979},"86":{"tf":2.449489742783178},"87":{"tf":1.0},"88":{"tf":1.0}},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{".":{"df":0,"docs":{},"m":{"d":{"df":2,"docs":{"268":{"tf":2.0},"7":{"tf":1.0}}},"df":0,"docs":{}}},"]":{"(":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{":":{"/":{"/":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"u":{"b":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"/":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"268":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"268":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}},"df":4,"docs":{"21":{"tf":1.0},"269":{"tf":1.0},"282":{"tf":1.0},"7":{"tf":1.0}}}}}}},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":3,"docs":{"122":{"tf":1.0},"271":{"tf":1.0},"274":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"t":{"df":3,"docs":{"215":{"tf":1.0},"216":{"tf":1.0},"258":{"tf":1.0}}}},"t":{"df":1,"docs":{"1":{"tf":1.0}}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"k":{"_":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"d":{"_":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"_":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"_":{"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":2,"docs":{"145":{"tf":1.4142135623730951},"146":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"97":{"tf":1.0}}}}}},"df":0,"docs":{}}}}},"df":35,"docs":{"104":{"tf":1.0},"105":{"tf":1.4142135623730951},"11":{"tf":1.4142135623730951},"12":{"tf":1.0},"122":{"tf":1.4142135623730951},"124":{"tf":1.0},"126":{"tf":1.0},"14":{"tf":1.0},"151":{"tf":1.4142135623730951},"152":{"tf":1.7320508075688772},"155":{"tf":1.0},"156":{"tf":1.0},"18":{"tf":1.0},"189":{"tf":1.0},"201":{"tf":1.0},"22":{"tf":1.0},"242":{"tf":1.0},"245":{"tf":1.4142135623730951},"26":{"tf":1.0},"274":{"tf":2.449489742783178},"282":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0},"51":{"tf":1.0},"58":{"tf":1.0},"62":{"tf":2.6457513110645907},"64":{"tf":2.23606797749979},"69":{"tf":1.0},"7":{"tf":1.0},"70":{"tf":1.0},"73":{"tf":2.6457513110645907},"81":{"tf":1.0},"96":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"268":{"tf":1.4142135623730951}}}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":17,"docs":{"124":{"tf":1.0},"18":{"tf":1.7320508075688772},"23":{"tf":2.0},"24":{"tf":1.7320508075688772},"25":{"tf":1.0},"270":{"tf":1.0},"28":{"tf":1.0},"29":{"tf":1.4142135623730951},"30":{"tf":1.0},"31":{"tf":1.7320508075688772},"32":{"tf":2.0},"33":{"tf":1.0},"34":{"tf":1.7320508075688772},"35":{"tf":1.0},"36":{"tf":2.0},"37":{"tf":1.0},"49":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":5,"docs":{"167":{"tf":1.0},"279":{"tf":1.0},"280":{"tf":1.7320508075688772},"29":{"tf":1.4142135623730951},"34":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":1,"docs":{"97":{"tf":1.0}}}}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"270":{"tf":1.0}}}}}},"i":{"df":0,"docs":{},"p":{"df":1,"docs":{"17":{"tf":1.4142135623730951}}}},"o":{"df":0,"docs":{},"i":{"c":{"df":5,"docs":{"152":{"tf":1.0},"217":{"tf":1.0},"226":{"tf":1.4142135623730951},"227":{"tf":1.0},"232":{"tf":1.0}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"s":{"df":11,"docs":{"126":{"tf":1.0},"165":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"208":{"tf":1.0},"225":{"tf":1.4142135623730951},"226":{"tf":1.0},"229":{"tf":1.0},"31":{"tf":1.0},"98":{"tf":1.0}}}},"s":{"df":0,"docs":{},"e":{"df":1,"docs":{"251":{"tf":1.0}},"n":{"df":5,"docs":{"138":{"tf":1.0},"143":{"tf":1.0},"164":{"tf":1.0},"199":{"tf":1.4142135623730951},"208":{"tf":1.4142135623730951}}}}}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":3,"docs":{"206":{"tf":1.4142135623730951},"217":{"tf":1.0},"218":{"tf":1.4142135623730951}}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":2,"docs":{"105":{"tf":1.0},"106":{"tf":1.0}}}}}},"i":{"df":18,"docs":{"11":{"tf":1.4142135623730951},"118":{"tf":1.0},"119":{"tf":1.0},"263":{"tf":2.0},"266":{"tf":1.4142135623730951},"270":{"tf":1.0},"271":{"tf":1.0},"274":{"tf":1.4142135623730951},"276":{"tf":1.0},"278":{"tf":1.0},"280":{"tf":1.0},"282":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0},"49":{"tf":1.0},"51":{"tf":1.0},"64":{"tf":1.4142135623730951},"92":{"tf":1.0}},"r":{"c":{"df":0,"docs":{},"l":{"df":1,"docs":{"263":{"tf":1.0}},"e":{"c":{"df":0,"docs":{},"i":{"df":3,"docs":{"263":{"tf":1.0},"274":{"tf":2.449489742783178},"277":{"tf":1.7320508075688772}}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"197":{"tf":1.0}}}},"df":0,"docs":{}},"m":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"170":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}}},"l":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"210":{"tf":1.0}}}},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"37":{"tf":1.4142135623730951}}}},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":1,"docs":{"6":{"tf":1.0}},"i":{"df":1,"docs":{"233":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"s":{"df":9,"docs":{"100":{"tf":1.0},"103":{"tf":1.4142135623730951},"108":{"tf":1.0},"109":{"tf":1.0},"112":{"tf":1.0},"131":{"tf":2.0},"132":{"tf":2.0},"98":{"tf":1.4142135623730951},"99":{"tf":1.0}},"i":{"c":{"df":1,"docs":{"277":{"tf":1.0}}},"df":0,"docs":{}},"p":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"223":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"n":{"df":2,"docs":{"240":{"tf":1.0},"279":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"172":{"tf":1.0}}}}},"r":{"df":3,"docs":{"229":{"tf":1.0},"279":{"tf":1.0},"60":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"226":{"tf":1.0}}}},"l":{"df":0,"docs":{},"i":{"df":3,"docs":{"132":{"tf":1.0},"64":{"tf":1.0},"7":{"tf":1.0}}}}}},"df":0,"docs":{}},"i":{"c":{"df":0,"docs":{},"k":{"df":5,"docs":{"117":{"tf":1.4142135623730951},"31":{"tf":2.0},"36":{"tf":2.0},"40":{"tf":1.4142135623730951},"73":{"tf":1.4142135623730951}}}},"df":1,"docs":{"117":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"x":{"c":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"p":{"b":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"df":1,"docs":{"31":{"tf":1.0}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":2,"docs":{"25":{"tf":1.0},"31":{"tf":1.0}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},":":{":":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"d":{"df":0,"docs":{},"s":{"_":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"268":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"283":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":31,"docs":{"10":{"tf":1.0},"105":{"tf":1.0},"107":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"11":{"tf":1.0},"134":{"tf":1.0},"179":{"tf":1.0},"186":{"tf":1.0},"192":{"tf":1.0},"193":{"tf":1.7320508075688772},"194":{"tf":1.7320508075688772},"196":{"tf":1.0},"197":{"tf":2.449489742783178},"198":{"tf":2.449489742783178},"199":{"tf":1.4142135623730951},"200":{"tf":2.8284271247461903},"201":{"tf":1.7320508075688772},"226":{"tf":1.0},"231":{"tf":2.8284271247461903},"234":{"tf":1.0},"237":{"tf":1.0},"238":{"tf":1.4142135623730951},"254":{"tf":2.0},"256":{"tf":1.4142135623730951},"257":{"tf":1.0},"268":{"tf":1.0},"283":{"tf":1.7320508075688772},"284":{"tf":1.4142135623730951},"31":{"tf":1.0},"58":{"tf":1.4142135623730951}}}}},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":3,"docs":{"262":{"tf":1.4142135623730951},"263":{"tf":1.4142135623730951},"92":{"tf":1.4142135623730951}}}}}},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":7,"docs":{"11":{"tf":1.7320508075688772},"12":{"tf":1.0},"34":{"tf":1.4142135623730951},"36":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0}}}},"s":{"df":0,"docs":{},"e":{"df":5,"docs":{"105":{"tf":1.0},"133":{"tf":1.0},"187":{"tf":1.0},"230":{"tf":1.0},"258":{"tf":1.0}}}}}},"o":{"d":{"df":0,"docs":{},"e":{"'":{"df":1,"docs":{"45":{"tf":1.0}}},"b":{"a":{"df":0,"docs":{},"s":{"df":2,"docs":{"143":{"tf":1.0},"65":{"tf":1.0}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":1,"docs":{"50":{"tf":1.0}}}}},"df":96,"docs":{"0":{"tf":1.0},"1":{"tf":1.0},"101":{"tf":1.0},"103":{"tf":3.3166247903554},"104":{"tf":2.0},"105":{"tf":1.7320508075688772},"106":{"tf":1.7320508075688772},"107":{"tf":2.0},"108":{"tf":2.6457513110645907},"109":{"tf":2.449489742783178},"111":{"tf":1.4142135623730951},"114":{"tf":1.4142135623730951},"115":{"tf":2.0},"116":{"tf":1.4142135623730951},"117":{"tf":2.0},"118":{"tf":1.7320508075688772},"120":{"tf":1.0},"125":{"tf":1.4142135623730951},"128":{"tf":1.4142135623730951},"132":{"tf":1.0},"133":{"tf":1.0},"140":{"tf":1.0},"145":{"tf":1.7320508075688772},"146":{"tf":1.0},"154":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0},"158":{"tf":1.4142135623730951},"159":{"tf":1.0},"163":{"tf":1.4142135623730951},"164":{"tf":1.0},"165":{"tf":1.0},"166":{"tf":1.0},"167":{"tf":2.6457513110645907},"169":{"tf":1.0},"17":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.7320508075688772},"172":{"tf":2.0},"174":{"tf":1.0},"193":{"tf":1.0},"194":{"tf":1.0},"2":{"tf":1.0},"200":{"tf":2.8284271247461903},"219":{"tf":1.0},"22":{"tf":1.0},"220":{"tf":2.0},"221":{"tf":1.4142135623730951},"222":{"tf":1.4142135623730951},"223":{"tf":1.4142135623730951},"224":{"tf":1.4142135623730951},"232":{"tf":1.0},"241":{"tf":2.0},"245":{"tf":1.0},"249":{"tf":1.4142135623730951},"251":{"tf":1.0},"253":{"tf":1.7320508075688772},"263":{"tf":1.0},"274":{"tf":3.1622776601683795},"279":{"tf":1.0},"28":{"tf":1.0},"282":{"tf":1.0},"283":{"tf":1.0},"31":{"tf":1.0},"33":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.7320508075688772},"43":{"tf":1.4142135623730951},"44":{"tf":1.7320508075688772},"45":{"tf":2.8284271247461903},"46":{"tf":2.23606797749979},"49":{"tf":1.0},"50":{"tf":1.4142135623730951},"58":{"tf":1.7320508075688772},"60":{"tf":1.7320508075688772},"63":{"tf":1.4142135623730951},"64":{"tf":2.0},"65":{"tf":1.0},"66":{"tf":1.0},"67":{"tf":1.0},"68":{"tf":1.0},"69":{"tf":1.4142135623730951},"7":{"tf":2.6457513110645907},"70":{"tf":2.0},"72":{"tf":2.23606797749979},"74":{"tf":1.7320508075688772},"8":{"tf":1.4142135623730951},"81":{"tf":1.4142135623730951},"82":{"tf":1.4142135623730951},"83":{"tf":1.0},"9":{"tf":1.4142135623730951},"90":{"tf":1.4142135623730951},"92":{"tf":1.0},"93":{"tf":1.0},"95":{"tf":1.0},"98":{"tf":1.0}}}},"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"229":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"l":{"a":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"274":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":15,"docs":{"0":{"tf":1.0},"189":{"tf":1.0},"194":{"tf":1.0},"203":{"tf":1.0},"226":{"tf":1.7320508075688772},"229":{"tf":1.0},"232":{"tf":1.7320508075688772},"242":{"tf":1.4142135623730951},"244":{"tf":1.0},"245":{"tf":1.4142135623730951},"248":{"tf":1.4142135623730951},"259":{"tf":1.7320508075688772},"45":{"tf":1.0},"6":{"tf":1.4142135623730951},"86":{"tf":2.23606797749979}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"'":{"df":1,"docs":{"245":{"tf":1.0}}},"/":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"228":{"tf":1.0}}}}}}}},"df":0,"docs":{}}}},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"128":{"tf":1.0}}}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"283":{"tf":1.4142135623730951}}}},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"n":{"df":2,"docs":{"250":{"tf":1.0},"84":{"tf":1.0}}}}}},"m":{"b":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"58":{"tf":1.0}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":6,"docs":{"145":{"tf":1.4142135623730951},"146":{"tf":1.4142135623730951},"228":{"tf":1.0},"249":{"tf":1.0},"274":{"tf":1.4142135623730951},"39":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":5,"docs":{"116":{"tf":1.0},"205":{"tf":1.0},"261":{"tf":1.0},"281":{"tf":1.0},"282":{"tf":1.0}}},"m":{"a":{"df":1,"docs":{"17":{"tf":1.0}},"n":{"d":{".":{"df":0,"docs":{},"t":{"a":{"b":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"283":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":12,"docs":{"104":{"tf":1.0},"11":{"tf":1.0},"126":{"tf":1.0},"165":{"tf":1.0},"172":{"tf":1.0},"19":{"tf":1.0},"22":{"tf":1.0},"226":{"tf":1.4142135623730951},"231":{"tf":2.6457513110645907},"237":{"tf":1.0},"268":{"tf":1.0},"283":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"29":{"tf":1.0},"34":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"t":{"df":10,"docs":{"253":{"tf":1.0},"267":{"tf":1.0},"268":{"tf":1.4142135623730951},"270":{"tf":1.0},"271":{"tf":1.0},"274":{"tf":1.7320508075688772},"282":{"tf":1.0},"30":{"tf":1.4142135623730951},"35":{"tf":1.4142135623730951},"7":{"tf":1.7320508075688772}}}},"o":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"df":1,"docs":{"94":{"tf":1.0}}}}}},"df":6,"docs":{"154":{"tf":1.0},"22":{"tf":1.0},"249":{"tf":1.0},"282":{"tf":1.0},"81":{"tf":1.0},"84":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"264":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"n":{"df":6,"docs":{"189":{"tf":1.0},"226":{"tf":1.4142135623730951},"231":{"tf":1.0},"254":{"tf":1.4142135623730951},"3":{"tf":1.4142135623730951},"64":{"tf":1.0}}}}},"p":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"208":{"tf":1.0}}}},"df":0,"docs":{},"r":{"df":6,"docs":{"151":{"tf":1.4142135623730951},"152":{"tf":1.4142135623730951},"155":{"tf":1.0},"156":{"tf":1.0},"245":{"tf":1.4142135623730951},"62":{"tf":1.0}}},"t":{"df":6,"docs":{"116":{"tf":1.0},"161":{"tf":1.0},"162":{"tf":1.0},"37":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":29,"docs":{"105":{"tf":1.0},"109":{"tf":1.0},"114":{"tf":1.0},"161":{"tf":1.0},"163":{"tf":1.7320508075688772},"164":{"tf":1.0},"171":{"tf":1.4142135623730951},"172":{"tf":2.0},"220":{"tf":1.0},"221":{"tf":1.0},"222":{"tf":1.0},"223":{"tf":1.4142135623730951},"224":{"tf":1.4142135623730951},"251":{"tf":1.0},"252":{"tf":1.4142135623730951},"253":{"tf":1.0},"278":{"tf":1.4142135623730951},"279":{"tf":1.0},"28":{"tf":1.0},"280":{"tf":2.0},"33":{"tf":1.0},"37":{"tf":1.4142135623730951},"45":{"tf":1.4142135623730951},"46":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.4142135623730951},"59":{"tf":1.7320508075688772},"60":{"tf":2.0},"73":{"tf":2.23606797749979}}}},"l":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":2,"docs":{"263":{"tf":1.0},"60":{"tf":1.4142135623730951}},"t":{"df":1,"docs":{"60":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":11,"docs":{"10":{"tf":1.0},"106":{"tf":1.0},"11":{"tf":1.0},"115":{"tf":1.0},"119":{"tf":1.0},"169":{"tf":1.0},"187":{"tf":1.0},"200":{"tf":1.0},"22":{"tf":1.0},"270":{"tf":1.0},"84":{"tf":1.0}},"e":{"_":{"df":0,"docs":{},"o":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":1,"docs":{"283":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"x":{"df":12,"docs":{"106":{"tf":1.7320508075688772},"115":{"tf":1.0},"116":{"tf":1.0},"140":{"tf":1.0},"158":{"tf":1.0},"200":{"tf":1.0},"220":{"tf":1.0},"229":{"tf":1.0},"233":{"tf":1.0},"280":{"tf":1.0},"78":{"tf":1.4142135623730951},"81":{"tf":1.0}}}},"i":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"262":{"tf":1.0}}}}},"c":{"df":6,"docs":{"10":{"tf":1.0},"197":{"tf":1.0},"225":{"tf":1.4142135623730951},"233":{"tf":1.0},"45":{"tf":1.0},"62":{"tf":1.0}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"n":{"df":127,"docs":{"0":{"tf":2.0},"1":{"tf":1.0},"101":{"tf":2.449489742783178},"102":{"tf":2.0},"103":{"tf":2.23606797749979},"104":{"tf":1.7320508075688772},"105":{"tf":1.7320508075688772},"106":{"tf":1.4142135623730951},"108":{"tf":2.449489742783178},"109":{"tf":2.23606797749979},"11":{"tf":1.0},"112":{"tf":1.0},"117":{"tf":1.0},"118":{"tf":1.0},"119":{"tf":1.4142135623730951},"12":{"tf":1.0},"120":{"tf":1.7320508075688772},"122":{"tf":1.4142135623730951},"123":{"tf":2.8284271247461903},"124":{"tf":1.4142135623730951},"125":{"tf":1.0},"14":{"tf":1.0},"141":{"tf":1.0},"145":{"tf":1.0},"149":{"tf":1.0},"150":{"tf":1.0},"151":{"tf":1.0},"157":{"tf":1.0},"159":{"tf":1.0},"16":{"tf":1.7320508075688772},"161":{"tf":1.0},"162":{"tf":1.0},"163":{"tf":1.0},"165":{"tf":1.7320508075688772},"166":{"tf":1.0},"167":{"tf":2.8284271247461903},"169":{"tf":1.0},"17":{"tf":1.0},"170":{"tf":1.7320508075688772},"171":{"tf":2.449489742783178},"172":{"tf":2.23606797749979},"175":{"tf":1.4142135623730951},"194":{"tf":1.4142135623730951},"198":{"tf":1.0},"20":{"tf":1.4142135623730951},"200":{"tf":4.242640687119285},"21":{"tf":1.0},"219":{"tf":1.7320508075688772},"22":{"tf":1.4142135623730951},"220":{"tf":2.449489742783178},"221":{"tf":2.23606797749979},"222":{"tf":2.449489742783178},"224":{"tf":1.4142135623730951},"225":{"tf":3.0},"226":{"tf":3.0},"228":{"tf":2.8284271247461903},"229":{"tf":2.8284271247461903},"23":{"tf":3.0},"230":{"tf":1.7320508075688772},"232":{"tf":1.4142135623730951},"233":{"tf":2.23606797749979},"234":{"tf":1.0},"235":{"tf":1.0},"236":{"tf":1.7320508075688772},"237":{"tf":1.7320508075688772},"24":{"tf":1.0},"243":{"tf":1.0},"244":{"tf":1.4142135623730951},"245":{"tf":2.0},"246":{"tf":1.0},"247":{"tf":1.0},"25":{"tf":2.0},"251":{"tf":2.0},"252":{"tf":1.0},"253":{"tf":2.23606797749979},"254":{"tf":1.0},"256":{"tf":1.0},"257":{"tf":2.6457513110645907},"258":{"tf":1.0},"259":{"tf":1.7320508075688772},"26":{"tf":2.449489742783178},"260":{"tf":1.4142135623730951},"262":{"tf":1.4142135623730951},"264":{"tf":1.4142135623730951},"268":{"tf":1.0},"271":{"tf":1.0},"272":{"tf":1.4142135623730951},"274":{"tf":2.0},"278":{"tf":1.0},"28":{"tf":1.0},"282":{"tf":1.0},"283":{"tf":1.4142135623730951},"284":{"tf":1.0},"29":{"tf":2.0},"30":{"tf":1.4142135623730951},"31":{"tf":2.8284271247461903},"32":{"tf":3.1622776601683795},"33":{"tf":1.0},"34":{"tf":2.23606797749979},"35":{"tf":1.4142135623730951},"36":{"tf":2.8284271247461903},"37":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.4142135623730951},"41":{"tf":2.0},"43":{"tf":1.7320508075688772},"45":{"tf":1.7320508075688772},"46":{"tf":2.0},"48":{"tf":1.4142135623730951},"49":{"tf":2.23606797749979},"54":{"tf":1.4142135623730951},"58":{"tf":1.0},"59":{"tf":1.0},"68":{"tf":2.0},"69":{"tf":1.7320508075688772},"7":{"tf":1.4142135623730951},"73":{"tf":3.1622776601683795},"74":{"tf":3.1622776601683795},"75":{"tf":2.6457513110645907},"76":{"tf":1.4142135623730951},"77":{"tf":1.4142135623730951},"78":{"tf":2.0},"79":{"tf":1.0},"80":{"tf":1.0},"81":{"tf":1.4142135623730951},"82":{"tf":1.0},"84":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"'":{"df":6,"docs":{"104":{"tf":1.0},"108":{"tf":1.0},"21":{"tf":1.0},"219":{"tf":1.0},"70":{"tf":2.23606797749979},"73":{"tf":1.0}}},">":{":":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"45":{"tf":1.0}}}}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"103":{"tf":1.4142135623730951}},"e":{">":{".":{"df":0,"docs":{},"u":{"d":{"df":0,"docs":{},"l":{"df":1,"docs":{"103":{"tf":1.0}}}},"df":0,"docs":{}}},":":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":1,"docs":{"108":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"108":{"tf":1.0}}}}}}},"_":{"df":0,"docs":{},"m":{"df":0,"docs":{},"s":{"df":0,"docs":{},"g":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":2,"docs":{"105":{"tf":1.0},"106":{"tf":1.0}}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{".":{"df":0,"docs":{},"h":{"df":1,"docs":{"109":{"tf":1.0}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"54":{"tf":1.0}}}},"s":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"34":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}},"t":{"a":{"df":0,"docs":{},"r":{".":{"df":0,"docs":{},"x":{"df":0,"docs":{},"z":{"df":1,"docs":{"271":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"/":{"<":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"103":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"_":{"c":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"70":{"tf":1.0},"72":{"tf":1.0}},"e":{">":{"/":{"<":{"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"_":{"c":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{">":{".":{"df":0,"docs":{},"u":{"d":{"df":0,"docs":{},"l":{"df":1,"docs":{"69":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"/":{"df":0,"docs":{},"s":{"df":0,"docs":{},"r":{"c":{"/":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"/":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"x":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":1,"docs":{"70":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":1,"docs":{"70":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":1,"docs":{"72":{"tf":1.0}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":1,"docs":{"70":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":1,"docs":{"69":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"c":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"/":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"70":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"df":1,"docs":{"39":{"tf":1.0}}}}}}},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"l":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"18":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"m":{"df":0,"docs":{},"y":{"c":{"df":0,"docs":{},"r":{"df":1,"docs":{"60":{"tf":1.0}}}},"df":0,"docs":{}}}},":":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{".":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":1,"docs":{"1":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"[":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":1,"docs":{"200":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"224":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":1,"docs":{"138":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":2,"docs":{"63":{"tf":1.0},"64":{"tf":1.0}}}}}}}}},"n":{"c":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"99":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":3,"docs":{"165":{"tf":1.0},"220":{"tf":1.0},"226":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"n":{"df":3,"docs":{"140":{"tf":1.0},"225":{"tf":1.0},"59":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":3,"docs":{"187":{"tf":1.0},"206":{"tf":1.0},"226":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":2,"docs":{"166":{"tf":1.0},"235":{"tf":1.0}}}}}},"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"4":{"tf":1.0}}}}},"df":7,"docs":{"144":{"tf":1.0},"145":{"tf":1.0},"146":{"tf":1.0},"153":{"tf":1.0},"168":{"tf":1.0},"181":{"tf":1.0},"211":{"tf":1.0}},"f":{"df":0,"docs":{},"i":{"d":{"df":3,"docs":{"152":{"tf":1.0},"172":{"tf":1.0},"201":{"tf":1.0}}},"df":0,"docs":{},"g":{"df":3,"docs":{"11":{"tf":1.0},"277":{"tf":1.0},"37":{"tf":1.0}},"u":{"df":0,"docs":{},"r":{"df":16,"docs":{"105":{"tf":1.0},"109":{"tf":1.0},"12":{"tf":2.0},"13":{"tf":1.4142135623730951},"18":{"tf":1.0},"184":{"tf":1.7320508075688772},"187":{"tf":1.0},"20":{"tf":1.0},"200":{"tf":1.4142135623730951},"22":{"tf":1.0},"25":{"tf":1.0},"263":{"tf":1.7320508075688772},"37":{"tf":1.4142135623730951},"49":{"tf":1.0},"59":{"tf":1.0},"69":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"m":{"df":6,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"13":{"tf":1.0},"22":{"tf":1.0},"69":{"tf":1.0},"70":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"165":{"tf":1.0},"175":{"tf":1.0},"233":{"tf":1.0}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":1,"docs":{"189":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"237":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"s":{"df":1,"docs":{"61":{"tf":1.0}}}}},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":1,"docs":{"106":{"tf":1.0}}}}}},"df":0,"docs":{}}},"n":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"(":{"\"":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"62":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"_":{"b":{"a":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"h":{"(":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"l":{"_":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"m":{"a":{"df":1,"docs":{"62":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":18,"docs":{"1":{"tf":1.0},"126":{"tf":1.4142135623730951},"130":{"tf":1.0},"133":{"tf":1.0},"154":{"tf":1.0},"155":{"tf":1.0},"183":{"tf":1.0},"189":{"tf":1.0},"190":{"tf":1.4142135623730951},"226":{"tf":1.0},"229":{"tf":1.0},"276":{"tf":1.0},"283":{"tf":1.7320508075688772},"78":{"tf":1.0},"79":{"tf":2.6457513110645907},"81":{"tf":1.4142135623730951},"83":{"tf":1.0},"87":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"62":{"tf":1.0}}}}}}}}}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":4,"docs":{"165":{"tf":1.0},"166":{"tf":1.0},"209":{"tf":1.0},"210":{"tf":1.4142135623730951}}}},"r":{"df":0,"docs":{},"v":{"df":2,"docs":{"152":{"tf":1.0},"64":{"tf":1.0}}}}},"i":{"d":{"df":29,"docs":{"123":{"tf":1.0},"137":{"tf":1.0},"142":{"tf":1.0},"151":{"tf":1.0},"163":{"tf":1.0},"167":{"tf":1.0},"179":{"tf":1.0},"190":{"tf":1.4142135623730951},"192":{"tf":1.0},"195":{"tf":1.0},"200":{"tf":1.0},"206":{"tf":1.0},"207":{"tf":1.0},"217":{"tf":1.0},"220":{"tf":1.0},"228":{"tf":1.4142135623730951},"229":{"tf":1.0},"231":{"tf":1.0},"233":{"tf":1.4142135623730951},"262":{"tf":1.0},"263":{"tf":1.0},"274":{"tf":1.0},"276":{"tf":1.0},"51":{"tf":1.0},"59":{"tf":1.0},"6":{"tf":1.0},"64":{"tf":1.0},"75":{"tf":1.0},"82":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":4,"docs":{"260":{"tf":1.0},"58":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0}}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":4,"docs":{"103":{"tf":1.0},"145":{"tf":1.0},"162":{"tf":1.0},"61":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"183":{"tf":1.0}}}},"t":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"93":{"tf":1.0}}}}},"df":3,"docs":{"117":{"tf":1.0},"20":{"tf":1.0},"94":{"tf":1.0}},"r":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"187":{"tf":1.0},"226":{"tf":1.0}}}}}},"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"131":{"tf":1.0}}}},"df":0,"docs":{}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":3,"docs":{"11":{"tf":1.0},"41":{"tf":1.0},"7":{"tf":1.0}}}},"m":{"df":56,"docs":{"104":{"tf":1.0},"107":{"tf":1.4142135623730951},"108":{"tf":1.0},"109":{"tf":1.0},"118":{"tf":1.4142135623730951},"123":{"tf":1.4142135623730951},"14":{"tf":1.0},"145":{"tf":2.0},"146":{"tf":1.7320508075688772},"154":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0},"158":{"tf":1.0},"16":{"tf":1.4142135623730951},"161":{"tf":1.0},"162":{"tf":1.0},"164":{"tf":1.0},"165":{"tf":1.4142135623730951},"166":{"tf":1.4142135623730951},"167":{"tf":2.0},"169":{"tf":2.0},"170":{"tf":2.449489742783178},"171":{"tf":2.0},"172":{"tf":1.4142135623730951},"174":{"tf":1.0},"175":{"tf":1.4142135623730951},"18":{"tf":1.7320508075688772},"193":{"tf":1.4142135623730951},"194":{"tf":1.7320508075688772},"20":{"tf":1.7320508075688772},"200":{"tf":1.4142135623730951},"201":{"tf":1.0},"22":{"tf":1.0},"223":{"tf":1.0},"228":{"tf":1.0},"237":{"tf":1.0},"245":{"tf":1.0},"251":{"tf":1.0},"253":{"tf":1.0},"268":{"tf":2.0},"272":{"tf":1.4142135623730951},"273":{"tf":1.0},"274":{"tf":2.449489742783178},"37":{"tf":1.4142135623730951},"43":{"tf":1.4142135623730951},"49":{"tf":1.0},"50":{"tf":1.0},"51":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"69":{"tf":1.0},"70":{"tf":1.0},"74":{"tf":1.0},"78":{"tf":1.0},"83":{"tf":1.0},"90":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"r":{".":{"df":0,"docs":{},"f":{"df":0,"docs":{},"x":{"a":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"283":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"200":{"tf":1.0}}}}}}},"t":{"a":{"c":{"df":0,"docs":{},"t":{"df":8,"docs":{"1":{"tf":1.4142135623730951},"21":{"tf":1.0},"245":{"tf":1.0},"25":{"tf":1.0},"266":{"tf":1.0},"274":{"tf":1.4142135623730951},"31":{"tf":1.0},"36":{"tf":1.0}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":27,"docs":{"105":{"tf":2.0},"167":{"tf":1.0},"20":{"tf":1.0},"221":{"tf":1.0},"222":{"tf":1.0},"223":{"tf":1.4142135623730951},"226":{"tf":1.0},"232":{"tf":1.0},"248":{"tf":1.0},"249":{"tf":1.0},"251":{"tf":1.0},"252":{"tf":1.7320508075688772},"271":{"tf":1.4142135623730951},"274":{"tf":1.0},"28":{"tf":1.0},"33":{"tf":1.0},"39":{"tf":1.0},"45":{"tf":1.0},"48":{"tf":1.0},"60":{"tf":1.4142135623730951},"61":{"tf":1.0},"69":{"tf":1.0},"70":{"tf":1.4142135623730951},"72":{"tf":1.0},"73":{"tf":1.0},"74":{"tf":1.0},"98":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":12,"docs":{"126":{"tf":1.0},"13":{"tf":1.0},"189":{"tf":1.7320508075688772},"193":{"tf":1.0},"194":{"tf":1.0},"196":{"tf":1.0},"201":{"tf":2.449489742783178},"226":{"tf":1.0},"284":{"tf":1.0},"62":{"tf":1.0},"70":{"tf":1.4142135623730951},"98":{"tf":1.0}},"i":{"df":1,"docs":{"227":{"tf":1.0}}}}},"x":{"df":0,"docs":{},"t":{"df":10,"docs":{"136":{"tf":1.0},"140":{"tf":1.0},"149":{"tf":1.0},"161":{"tf":1.0},"174":{"tf":1.0},"177":{"tf":1.0},"187":{"tf":1.0},"200":{"tf":1.0},"201":{"tf":1.0},"203":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":9,"docs":{"11":{"tf":1.0},"120":{"tf":1.0},"141":{"tf":1.0},"145":{"tf":1.0},"167":{"tf":1.4142135623730951},"197":{"tf":1.0},"198":{"tf":1.0},"226":{"tf":1.0},"7":{"tf":1.0}}}}},"r":{"a":{"df":1,"docs":{"274":{"tf":1.0}}},"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"205":{"tf":1.0},"3":{"tf":1.0}},"o":{"df":0,"docs":{},"r":{"df":4,"docs":{"274":{"tf":1.0},"41":{"tf":1.0},"6":{"tf":1.4142135623730951},"7":{"tf":1.0}}}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"l":{"df":6,"docs":{"184":{"tf":1.0},"201":{"tf":1.4142135623730951},"208":{"tf":1.0},"283":{"tf":1.4142135623730951},"61":{"tf":1.0},"77":{"tf":1.0}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":6,"docs":{"117":{"tf":1.0},"165":{"tf":1.0},"25":{"tf":1.0},"257":{"tf":1.0},"281":{"tf":1.0},"79":{"tf":1.0}}},"t":{"df":7,"docs":{"107":{"tf":1.0},"137":{"tf":1.0},"69":{"tf":1.0},"91":{"tf":1.0},"93":{"tf":1.4142135623730951},"96":{"tf":1.0},"99":{"tf":1.0}}}},"r":{"df":0,"docs":{},"s":{"df":2,"docs":{"105":{"tf":1.0},"106":{"tf":1.0}}},"t":{"df":5,"docs":{"101":{"tf":1.0},"103":{"tf":1.4142135623730951},"105":{"tf":1.0},"163":{"tf":1.0},"284":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"104":{"tf":1.0}}},"df":0,"docs":{}}}}},"o":{"df":0,"docs":{},"r":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":3,"docs":{"225":{"tf":1.0},"226":{"tf":2.23606797749979},"241":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"i":{"df":13,"docs":{"18":{"tf":1.0},"199":{"tf":1.0},"2":{"tf":1.0},"20":{"tf":1.0},"220":{"tf":1.0},"250":{"tf":1.0},"267":{"tf":1.7320508075688772},"273":{"tf":1.4142135623730951},"282":{"tf":1.0},"45":{"tf":1.0},"64":{"tf":1.0},"70":{"tf":1.0},"73":{"tf":2.0}}}},"r":{"df":0,"docs":{},"e":{"df":7,"docs":{"0":{"tf":1.0},"103":{"tf":1.7320508075688772},"105":{"tf":1.4142135623730951},"274":{"tf":1.0},"43":{"tf":1.4142135623730951},"6":{"tf":1.0},"78":{"tf":1.0}}},"r":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":8,"docs":{"11":{"tf":1.0},"126":{"tf":1.4142135623730951},"14":{"tf":1.0},"222":{"tf":1.0},"229":{"tf":1.0},"239":{"tf":1.0},"267":{"tf":1.0},"79":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":11,"docs":{"122":{"tf":1.0},"13":{"tf":1.0},"22":{"tf":1.4142135623730951},"224":{"tf":1.4142135623730951},"231":{"tf":1.0},"233":{"tf":1.4142135623730951},"250":{"tf":1.0},"263":{"tf":1.0},"274":{"tf":1.0},"70":{"tf":1.4142135623730951},"73":{"tf":1.0}}}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"d":{"df":12,"docs":{"106":{"tf":1.7320508075688772},"108":{"tf":1.0},"131":{"tf":1.4142135623730951},"167":{"tf":1.0},"223":{"tf":1.0},"242":{"tf":1.0},"244":{"tf":1.0},"274":{"tf":1.0},"39":{"tf":1.0},"46":{"tf":1.0},"48":{"tf":1.0},"73":{"tf":1.0}}},"df":0,"docs":{}}}}}},"u":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":14,"docs":{"116":{"tf":1.4142135623730951},"132":{"tf":1.0},"134":{"tf":1.0},"148":{"tf":1.0},"149":{"tf":2.0},"150":{"tf":1.0},"151":{"tf":2.23606797749979},"152":{"tf":1.4142135623730951},"154":{"tf":1.4142135623730951},"155":{"tf":1.4142135623730951},"156":{"tf":1.4142135623730951},"157":{"tf":1.0},"158":{"tf":1.0},"159":{"tf":1.4142135623730951}}}}}}},"s":{"df":0,"docs":{},"t":{"df":3,"docs":{"105":{"tf":1.0},"115":{"tf":1.0},"150":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"60":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"214":{"tf":1.0},"82":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":3,"docs":{"250":{"tf":2.0},"82":{"tf":1.0},"84":{"tf":2.449489742783178}}}}}},"p":{"df":0,"docs":{},"l":{"df":7,"docs":{"116":{"tf":1.0},"206":{"tf":1.0},"208":{"tf":1.0},"225":{"tf":1.0},"226":{"tf":1.0},"228":{"tf":1.0},"229":{"tf":1.0}}}},"r":{"df":0,"docs":{},"s":{"df":2,"docs":{"105":{"tf":1.0},"115":{"tf":1.0}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":2,"docs":{"41":{"tf":1.0},"50":{"tf":2.0}}}},"df":4,"docs":{"236":{"tf":1.0},"268":{"tf":1.0},"7":{"tf":1.0},"84":{"tf":1.0}}}}}},"p":{"df":0,"docs":{},"p":{"=":{"c":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":1,"docs":{"37":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"r":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":2,"docs":{"117":{"tf":1.4142135623730951},"205":{"tf":1.0}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"u":{"d":{"df":0,"docs":{},"l":{"df":1,"docs":{"70":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"t":{"df":0,"docs":{},"e":{"'":{"df":4,"docs":{"105":{"tf":1.0},"106":{"tf":1.4142135623730951},"64":{"tf":1.0},"69":{"tf":1.0}}},":":{":":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"106":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{">":{"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":1,"docs":{"73":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":29,"docs":{"103":{"tf":2.0},"105":{"tf":3.0},"106":{"tf":1.4142135623730951},"107":{"tf":1.0},"116":{"tf":1.4142135623730951},"122":{"tf":1.0},"123":{"tf":1.7320508075688772},"198":{"tf":1.4142135623730951},"221":{"tf":2.0},"222":{"tf":1.0},"225":{"tf":1.0},"237":{"tf":1.0},"238":{"tf":1.0},"244":{"tf":1.4142135623730951},"245":{"tf":1.0},"246":{"tf":1.0},"251":{"tf":1.0},"252":{"tf":1.4142135623730951},"28":{"tf":1.0},"281":{"tf":2.0},"282":{"tf":1.0},"286":{"tf":1.0},"33":{"tf":1.0},"43":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":2.449489742783178},"60":{"tf":2.6457513110645907},"64":{"tf":2.449489742783178},"69":{"tf":2.6457513110645907}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":40,"docs":{"104":{"tf":1.0},"108":{"tf":1.0},"11":{"tf":1.0},"118":{"tf":1.0},"129":{"tf":1.4142135623730951},"13":{"tf":1.4142135623730951},"130":{"tf":1.0},"131":{"tf":1.4142135623730951},"145":{"tf":1.4142135623730951},"167":{"tf":1.4142135623730951},"178":{"tf":1.0},"18":{"tf":1.0},"180":{"tf":1.0},"190":{"tf":1.0},"194":{"tf":1.0},"200":{"tf":1.0},"229":{"tf":1.0},"241":{"tf":1.0},"244":{"tf":1.0},"245":{"tf":1.7320508075688772},"246":{"tf":1.0},"249":{"tf":1.4142135623730951},"253":{"tf":1.0},"268":{"tf":2.6457513110645907},"269":{"tf":2.0},"270":{"tf":1.0},"274":{"tf":1.7320508075688772},"283":{"tf":1.7320508075688772},"29":{"tf":1.0},"34":{"tf":1.0},"39":{"tf":1.0},"60":{"tf":1.0},"62":{"tf":1.0},"69":{"tf":1.0},"70":{"tf":1.4142135623730951},"72":{"tf":1.0},"73":{"tf":1.4142135623730951},"75":{"tf":1.0},"78":{"tf":1.0},"82":{"tf":1.0}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"/":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"17":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"n":{"df":4,"docs":{"180":{"tf":1.0},"182":{"tf":1.0},"183":{"tf":1.0},"184":{"tf":1.0}}}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"n":{".":{"df":0,"docs":{},"y":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":1,"docs":{"40":{"tf":1.0}}}}}},"df":1,"docs":{"266":{"tf":1.4142135623730951}}},"s":{"df":0,"docs":{},"s":{"df":6,"docs":{"220":{"tf":1.0},"225":{"tf":1.0},"278":{"tf":1.0},"279":{"tf":1.0},"280":{"tf":2.0},"51":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":1,"docs":{"25":{"tf":1.0}}}}},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"/":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":1,"docs":{"226":{"tf":1.0}}}}}},"df":1,"docs":{"282":{"tf":1.0}},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":1,"docs":{"283":{"tf":1.0}},"h":{"df":2,"docs":{"278":{"tf":1.0},"282":{"tf":1.0}}}}},"df":0,"docs":{}}}}}}}},"s":{"a":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"k":{"df":1,"docs":{"186":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":56,"docs":{"101":{"tf":1.0},"103":{"tf":1.0},"104":{"tf":1.0},"11":{"tf":1.0},"111":{"tf":1.0},"121":{"tf":1.0},"126":{"tf":1.0},"145":{"tf":1.0},"146":{"tf":1.0},"150":{"tf":1.4142135623730951},"155":{"tf":1.0},"156":{"tf":1.0},"161":{"tf":1.7320508075688772},"163":{"tf":1.0},"166":{"tf":1.0},"167":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.4142135623730951},"175":{"tf":2.0},"187":{"tf":1.0},"193":{"tf":1.0},"196":{"tf":1.0},"220":{"tf":1.0},"228":{"tf":1.4142135623730951},"229":{"tf":1.0},"231":{"tf":1.0},"233":{"tf":1.4142135623730951},"242":{"tf":1.0},"245":{"tf":1.4142135623730951},"248":{"tf":1.0},"257":{"tf":1.4142135623730951},"258":{"tf":1.0},"26":{"tf":1.0},"260":{"tf":1.0},"261":{"tf":1.4142135623730951},"262":{"tf":1.4142135623730951},"263":{"tf":1.0},"264":{"tf":1.0},"268":{"tf":1.0},"274":{"tf":1.7320508075688772},"276":{"tf":1.0},"280":{"tf":1.0},"38":{"tf":1.0},"40":{"tf":1.0},"44":{"tf":1.0},"49":{"tf":1.0},"50":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"6":{"tf":1.0},"65":{"tf":1.4142135623730951},"66":{"tf":1.0},"67":{"tf":1.0},"7":{"tf":1.0},"84":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":4,"docs":{"133":{"tf":1.0},"184":{"tf":1.0},"222":{"tf":1.4142135623730951},"268":{"tf":1.0}}}}}},"t":{"df":4,"docs":{"253":{"tf":1.0},"268":{"tf":1.4142135623730951},"270":{"tf":1.0},"59":{"tf":1.0}}}},"y":{"c":{"df":0,"docs":{},"l":{"df":3,"docs":{"121":{"tf":1.0},"180":{"tf":1.0},"183":{"tf":1.0}}}},"df":0,"docs":{}}},"d":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":2,"docs":{"205":{"tf":1.0},"274":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":2,"docs":{"17":{"tf":1.4142135623730951},"280":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"h":{"b":{"df":0,"docs":{},"o":{"a":{"df":0,"docs":{},"r":{"d":{"df":2,"docs":{"203":{"tf":1.0},"218":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":1,"docs":{"200":{"tf":1.0}}}},"t":{"a":{"b":{"a":{"df":0,"docs":{},"s":{"df":31,"docs":{"130":{"tf":1.0},"134":{"tf":1.0},"140":{"tf":1.4142135623730951},"148":{"tf":1.0},"149":{"tf":2.23606797749979},"150":{"tf":1.0},"151":{"tf":3.1622776601683795},"152":{"tf":2.23606797749979},"154":{"tf":1.7320508075688772},"155":{"tf":1.4142135623730951},"156":{"tf":1.7320508075688772},"157":{"tf":1.7320508075688772},"158":{"tf":2.23606797749979},"159":{"tf":1.4142135623730951},"203":{"tf":1.4142135623730951},"205":{"tf":2.0},"206":{"tf":1.7320508075688772},"208":{"tf":2.0},"209":{"tf":2.0},"212":{"tf":2.0},"213":{"tf":1.0},"229":{"tf":1.0},"245":{"tf":1.0},"248":{"tf":1.0},"264":{"tf":1.0},"268":{"tf":1.0},"78":{"tf":1.0},"79":{"tf":1.0},"81":{"tf":1.7320508075688772},"84":{"tf":1.0},"86":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":36,"docs":{"105":{"tf":1.0},"106":{"tf":2.23606797749979},"140":{"tf":1.0},"145":{"tf":1.4142135623730951},"150":{"tf":1.4142135623730951},"154":{"tf":1.4142135623730951},"155":{"tf":1.4142135623730951},"156":{"tf":1.4142135623730951},"157":{"tf":1.0},"158":{"tf":2.8284271247461903},"159":{"tf":1.4142135623730951},"177":{"tf":1.4142135623730951},"178":{"tf":1.0},"179":{"tf":2.449489742783178},"180":{"tf":1.4142135623730951},"183":{"tf":1.0},"184":{"tf":1.0},"185":{"tf":1.4142135623730951},"189":{"tf":1.4142135623730951},"201":{"tf":2.0},"209":{"tf":1.0},"212":{"tf":1.0},"231":{"tf":1.0},"232":{"tf":1.0},"242":{"tf":1.4142135623730951},"254":{"tf":2.0},"255":{"tf":1.0},"259":{"tf":1.4142135623730951},"264":{"tf":1.0},"283":{"tf":1.4142135623730951},"79":{"tf":1.0},"80":{"tf":1.0},"82":{"tf":1.0},"84":{"tf":1.4142135623730951},"86":{"tf":1.0},"89":{"tf":1.0}}},"df":0,"docs":{},"e":{"a":{"d":{"df":1,"docs":{"62":{"tf":1.0}}},"df":0,"docs":{}},"df":16,"docs":{"121":{"tf":1.0},"139":{"tf":1.0},"148":{"tf":1.0},"160":{"tf":1.0},"167":{"tf":1.4142135623730951},"176":{"tf":1.0},"186":{"tf":1.0},"202":{"tf":1.0},"207":{"tf":1.0},"21":{"tf":1.0},"213":{"tf":1.0},"255":{"tf":1.0},"264":{"tf":1.0},"268":{"tf":1.0},"278":{"tf":1.0},"37":{"tf":1.0}}}},"y":{"df":5,"docs":{"119":{"tf":1.0},"206":{"tf":1.0},"218":{"tf":1.0},"266":{"tf":1.0},"268":{"tf":1.4142135623730951}},"s":{"/":{"df":0,"docs":{},"w":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"k":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"207":{"tf":1.0}}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"b":{":":{":":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{":":{":":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{":":{":":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"_":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"b":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"_":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"c":{"df":0,"docs":{},"r":{"df":1,"docs":{"62":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":4,"docs":{"249":{"tf":1.4142135623730951},"78":{"tf":1.0},"79":{"tf":2.0},"87":{"tf":1.0}},"g":{"(":{"'":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"62":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"62":{"tf":1.4142135623730951}}}},"d":{"_":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}},"df":7,"docs":{"163":{"tf":1.0},"164":{"tf":1.0},"172":{"tf":1.0},"174":{"tf":1.0},"29":{"tf":1.0},"34":{"tf":1.0},"37":{"tf":1.0}},"e":{"a":{"d":{"df":1,"docs":{"60":{"tf":1.0}}},"df":0,"docs":{},"l":{"df":6,"docs":{"149":{"tf":1.4142135623730951},"179":{"tf":1.0},"180":{"tf":1.0},"184":{"tf":1.4142135623730951},"185":{"tf":1.4142135623730951},"64":{"tf":1.0}}}},"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{".":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"k":{"df":1,"docs":{"40":{"tf":1.0}}}}},"df":0,"docs":{}},"df":7,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"117":{"tf":2.0},"125":{"tf":1.0},"37":{"tf":1.7320508075688772},"62":{"tf":2.23606797749979},"82":{"tf":1.0}},"g":{"df":1,"docs":{"117":{"tf":1.4142135623730951}}}}}},"c":{"df":0,"docs":{},"i":{"d":{"df":11,"docs":{"152":{"tf":1.4142135623730951},"155":{"tf":1.0},"157":{"tf":1.0},"160":{"tf":1.0},"176":{"tf":1.0},"186":{"tf":1.0},"202":{"tf":1.0},"225":{"tf":1.0},"231":{"tf":1.4142135623730951},"249":{"tf":1.0},"267":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":25,"docs":{"134":{"tf":2.0},"135":{"tf":1.0},"136":{"tf":1.0},"137":{"tf":1.4142135623730951},"138":{"tf":1.4142135623730951},"141":{"tf":1.0},"143":{"tf":1.0},"150":{"tf":1.0},"152":{"tf":1.4142135623730951},"162":{"tf":1.0},"164":{"tf":1.0},"178":{"tf":1.0},"180":{"tf":1.0},"187":{"tf":1.0},"188":{"tf":1.0},"198":{"tf":1.0},"206":{"tf":1.0},"208":{"tf":1.7320508075688772},"210":{"tf":1.0},"226":{"tf":1.0},"227":{"tf":1.0},"230":{"tf":1.0},"251":{"tf":1.0},"266":{"tf":1.7320508075688772},"40":{"tf":1.0}}}},"l":{"a":{"df":0,"docs":{},"r":{"df":8,"docs":{"172":{"tf":1.0},"174":{"tf":1.0},"201":{"tf":1.4142135623730951},"224":{"tf":1.4142135623730951},"60":{"tf":1.4142135623730951},"61":{"tf":1.0},"73":{"tf":1.0},"98":{"tf":1.0}}}},"df":1,"docs":{"61":{"tf":1.0}},"i":{"df":0,"docs":{},"n":{"df":4,"docs":{"233":{"tf":2.6457513110645907},"234":{"tf":1.0},"236":{"tf":1.4142135623730951},"242":{"tf":2.0}},"e":{"d":{"_":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"242":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"229":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":1,"docs":{"209":{"tf":1.0}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"250":{"tf":1.0},"84":{"tf":1.0}}}}}}},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"_":{"a":{"df":0,"docs":{},"n":{"d":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"_":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"145":{"tf":1.4142135623730951}}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":4,"docs":{"140":{"tf":1.0},"145":{"tf":1.7320508075688772},"146":{"tf":1.7320508075688772},"254":{"tf":1.0}}}}}}},"d":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"59":{"tf":1.0}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"p":{"df":3,"docs":{"140":{"tf":1.0},"141":{"tf":1.0},"145":{"tf":1.0}}}}},"df":3,"docs":{"140":{"tf":1.0},"145":{"tf":1.4142135623730951},"146":{"tf":1.0}},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"174":{"tf":1.0}}}}}},"f":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{".":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"a":{"df":0,"docs":{},"w":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"/":{"?":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"=":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"2":{"/":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"/":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"271":{"tf":1.0}},"e":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"271":{"tf":1.0}}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":5,"docs":{"11":{"tf":1.0},"12":{"tf":1.0},"222":{"tf":1.0},"60":{"tf":1.0},"69":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"140":{"tf":1.0}}}},"df":0,"docs":{}},"i":{"c":{"df":0,"docs":{},"i":{"df":1,"docs":{"141":{"tf":1.0}}}},"df":0,"docs":{},"n":{"df":26,"docs":{"105":{"tf":2.0},"106":{"tf":1.4142135623730951},"112":{"tf":1.0},"131":{"tf":1.0},"165":{"tf":1.0},"167":{"tf":1.0},"175":{"tf":1.0},"179":{"tf":1.0},"189":{"tf":1.0},"20":{"tf":1.0},"206":{"tf":1.0},"221":{"tf":1.4142135623730951},"226":{"tf":1.0},"229":{"tf":1.0},"232":{"tf":1.0},"242":{"tf":1.0},"244":{"tf":1.0},"245":{"tf":1.0},"246":{"tf":1.4142135623730951},"252":{"tf":1.0},"253":{"tf":1.0},"261":{"tf":1.0},"37":{"tf":1.4142135623730951},"69":{"tf":1.0},"81":{"tf":1.0},"84":{"tf":1.0}},"e":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"(":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":1,"docs":{"62":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"t":{"df":9,"docs":{"103":{"tf":1.0},"104":{"tf":1.4142135623730951},"105":{"tf":1.0},"111":{"tf":1.0},"167":{"tf":1.0},"172":{"tf":1.0},"233":{"tf":1.0},"242":{"tf":1.0},"69":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"?":{"c":{"df":0,"docs":{},"r":{"df":1,"docs":{"283":{"tf":1.0}}}},"df":0,"docs":{}},"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"283":{"tf":1.0}}}}}}}}}}}}}}}}},"df":0,"docs":{}}}}}}}}}},"g":{"df":0,"docs":{},"r":{"a":{"d":{"df":1,"docs":{"236":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":3,"docs":{"189":{"tf":1.0},"201":{"tf":1.0},"229":{"tf":1.0}}}}},"l":{"a":{"df":0,"docs":{},"y":{"df":4,"docs":{"179":{"tf":1.4142135623730951},"180":{"tf":1.0},"184":{"tf":1.7320508075688772},"185":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":2,"docs":{"194":{"tf":1.0},"229":{"tf":1.0}}},"t":{"df":23,"docs":{"105":{"tf":1.0},"106":{"tf":2.0},"108":{"tf":1.0},"109":{"tf":1.4142135623730951},"117":{"tf":1.0},"149":{"tf":1.0},"150":{"tf":1.0},"151":{"tf":1.0},"152":{"tf":1.4142135623730951},"154":{"tf":1.4142135623730951},"155":{"tf":1.4142135623730951},"156":{"tf":1.7320508075688772},"157":{"tf":1.0},"158":{"tf":2.0},"159":{"tf":1.4142135623730951},"209":{"tf":1.0},"212":{"tf":1.0},"231":{"tf":1.0},"249":{"tf":1.0},"268":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0}}}},"i":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"167":{"tf":1.0}}}}},"df":0,"docs":{},"v":{"df":2,"docs":{"225":{"tf":1.0},"235":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"235":{"tf":1.0}},"i":{"df":1,"docs":{"228":{"tf":1.0}}}}}}}},"m":{"df":0,"docs":{},"o":{"df":2,"docs":{"174":{"tf":1.0},"185":{"tf":1.0}},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":4,"docs":{"210":{"tf":1.0},"215":{"tf":1.0},"216":{"tf":1.0},"237":{"tf":1.0}}}}}}}},"p":{"df":1,"docs":{"107":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"d":{"df":45,"docs":{"10":{"tf":1.0},"105":{"tf":1.4142135623730951},"106":{"tf":1.4142135623730951},"11":{"tf":2.23606797749979},"12":{"tf":1.4142135623730951},"121":{"tf":1.4142135623730951},"122":{"tf":1.0},"123":{"tf":1.4142135623730951},"125":{"tf":1.0},"140":{"tf":1.0},"162":{"tf":1.0},"163":{"tf":1.0},"167":{"tf":2.0},"172":{"tf":1.4142135623730951},"174":{"tf":1.7320508075688772},"175":{"tf":1.0},"197":{"tf":1.0},"20":{"tf":1.0},"200":{"tf":1.7320508075688772},"22":{"tf":2.449489742783178},"220":{"tf":1.7320508075688772},"221":{"tf":2.8284271247461903},"222":{"tf":1.0},"224":{"tf":1.4142135623730951},"252":{"tf":1.0},"253":{"tf":1.0},"274":{"tf":1.7320508075688772},"31":{"tf":1.7320508075688772},"36":{"tf":2.23606797749979},"37":{"tf":1.7320508075688772},"45":{"tf":1.0},"53":{"tf":1.0},"58":{"tf":3.1622776601683795},"59":{"tf":2.0},"63":{"tf":1.4142135623730951},"64":{"tf":3.7416573867739413},"65":{"tf":2.23606797749979},"66":{"tf":1.7320508075688772},"67":{"tf":1.7320508075688772},"69":{"tf":2.449489742783178},"7":{"tf":1.7320508075688772},"73":{"tf":1.0},"78":{"tf":1.0},"79":{"tf":1.0},"81":{"tf":1.0}}},"df":0,"docs":{}}},"i":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"200":{"tf":1.7320508075688772}}}},"df":0,"docs":{}},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"y":{"df":2,"docs":{"117":{"tf":1.0},"90":{"tf":1.4142135623730951}}}}},"r":{"df":0,"docs":{},"e":{"c":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"h":{"df":3,"docs":{"93":{"tf":1.0},"96":{"tf":1.0},"99":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"<":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"79":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"v":{"df":1,"docs":{"106":{"tf":1.0}}}}},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":17,"docs":{"102":{"tf":1.0},"104":{"tf":1.0},"105":{"tf":1.0},"120":{"tf":1.0},"20":{"tf":1.0},"200":{"tf":1.0},"214":{"tf":1.0},"220":{"tf":1.0},"243":{"tf":1.0},"245":{"tf":1.0},"252":{"tf":1.0},"282":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0},"64":{"tf":1.0},"76":{"tf":1.0},"98":{"tf":1.0}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":7,"docs":{"145":{"tf":1.0},"146":{"tf":1.0},"21":{"tf":1.0},"219":{"tf":1.0},"249":{"tf":1.0},"251":{"tf":1.0},"280":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"x":{"df":1,"docs":{"283":{"tf":1.0}}}}}}}}}}}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"df":12,"docs":{"106":{"tf":1.0},"138":{"tf":1.4142135623730951},"219":{"tf":1.4142135623730951},"228":{"tf":1.4142135623730951},"23":{"tf":1.0},"251":{"tf":1.0},"32":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"69":{"tf":1.0},"76":{"tf":1.0},"96":{"tf":1.0}}}},"r":{"df":3,"docs":{"189":{"tf":1.0},"200":{"tf":1.0},"233":{"tf":1.0}}}},"k":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":2,"docs":{"280":{"tf":1.0},"282":{"tf":1.0}}}}},"df":23,"docs":{"1":{"tf":1.0},"11":{"tf":1.4142135623730951},"187":{"tf":1.0},"192":{"tf":1.4142135623730951},"201":{"tf":2.0},"206":{"tf":1.0},"210":{"tf":1.0},"217":{"tf":1.0},"218":{"tf":1.0},"223":{"tf":1.7320508075688772},"225":{"tf":1.7320508075688772},"226":{"tf":1.0},"228":{"tf":1.4142135623730951},"229":{"tf":1.0},"231":{"tf":1.4142135623730951},"232":{"tf":1.0},"241":{"tf":1.0},"255":{"tf":1.0},"257":{"tf":1.4142135623730951},"258":{"tf":1.0},"279":{"tf":1.0},"280":{"tf":1.4142135623730951},"282":{"tf":1.0}}}}}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"y":{"df":4,"docs":{"128":{"tf":1.0},"129":{"tf":1.4142135623730951},"131":{"tf":1.7320508075688772},"132":{"tf":1.4142135623730951}}}},"u":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"128":{"tf":1.4142135623730951},"129":{"tf":1.0},"133":{"tf":1.0}},"o":{"df":0,"docs":{},"r":{"df":2,"docs":{"128":{"tf":1.0},"133":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}}}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":20,"docs":{"104":{"tf":1.0},"105":{"tf":1.4142135623730951},"111":{"tf":1.0},"12":{"tf":1.0},"145":{"tf":1.0},"15":{"tf":1.0},"161":{"tf":1.0},"175":{"tf":1.0},"22":{"tf":1.0},"227":{"tf":1.4142135623730951},"228":{"tf":1.0},"232":{"tf":1.4142135623730951},"242":{"tf":1.4142135623730951},"245":{"tf":1.0},"252":{"tf":1.0},"264":{"tf":1.0},"268":{"tf":1.0},"274":{"tf":1.0},"32":{"tf":1.0},"37":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"231":{"tf":1.0}}}},"df":0,"docs":{},"k":{"df":0,"docs":{},"t":{"df":1,"docs":{"65":{"tf":1.0}}}},"r":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"250":{"tf":1.0}},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"184":{"tf":1.0}}}}}}}}}}},"v":{"df":6,"docs":{"11":{"tf":1.7320508075688772},"121":{"tf":1.0},"268":{"tf":1.0},"274":{"tf":1.4142135623730951},"58":{"tf":2.23606797749979},"64":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":20,"docs":{"138":{"tf":1.0},"162":{"tf":1.4142135623730951},"164":{"tf":1.4142135623730951},"169":{"tf":1.4142135623730951},"170":{"tf":1.4142135623730951},"171":{"tf":1.7320508075688772},"172":{"tf":1.7320508075688772},"18":{"tf":1.0},"199":{"tf":1.0},"223":{"tf":1.0},"26":{"tf":1.7320508075688772},"260":{"tf":1.0},"274":{"tf":2.449489742783178},"277":{"tf":1.0},"284":{"tf":1.0},"37":{"tf":1.0},"45":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0}}}}}},"i":{"c":{"df":17,"docs":{"126":{"tf":1.7320508075688772},"175":{"tf":1.0},"226":{"tf":1.0},"229":{"tf":1.4142135623730951},"231":{"tf":2.449489742783178},"232":{"tf":1.0},"233":{"tf":3.1622776601683795},"236":{"tf":1.0},"237":{"tf":1.0},"242":{"tf":1.4142135623730951},"245":{"tf":2.0},"249":{"tf":1.0},"255":{"tf":1.0},"276":{"tf":1.0},"283":{"tf":1.4142135623730951},"45":{"tf":1.0},"51":{"tf":1.0}},"e":{".":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"283":{"tf":1.0}}}}}}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"f":{"df":0,"docs":{},"x":{"a":{"df":1,"docs":{"283":{"tf":1.0}}},"df":0,"docs":{}}}}}}}},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":1,"docs":{"283":{"tf":1.0}}}}}},"_":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"242":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"i":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":8,"docs":{"200":{"tf":1.7320508075688772},"221":{"tf":1.0},"254":{"tf":1.0},"255":{"tf":1.4142135623730951},"256":{"tf":1.0},"257":{"tf":1.0},"258":{"tf":2.0},"274":{"tf":1.0}}}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":2,"docs":{"31":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951}}}}}},"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":2,"docs":{"105":{"tf":1.0},"106":{"tf":1.0}}},"y":{"/":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"106":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"d":{"df":0,"docs":{},"n":{"'":{"df":0,"docs":{},"t":{"df":1,"docs":{"61":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":4,"docs":{"26":{"tf":1.0},"268":{"tf":2.0},"269":{"tf":1.0},"89":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":32,"docs":{"0":{"tf":1.0},"104":{"tf":1.0},"108":{"tf":1.4142135623730951},"109":{"tf":1.4142135623730951},"115":{"tf":1.0},"123":{"tf":1.0},"131":{"tf":1.0},"146":{"tf":1.0},"165":{"tf":1.7320508075688772},"174":{"tf":1.0},"198":{"tf":1.0},"201":{"tf":1.0},"204":{"tf":1.0},"22":{"tf":1.0},"220":{"tf":1.0},"228":{"tf":1.0},"233":{"tf":1.7320508075688772},"242":{"tf":1.0},"243":{"tf":1.0},"245":{"tf":1.7320508075688772},"257":{"tf":1.0},"262":{"tf":1.0},"283":{"tf":1.0},"58":{"tf":1.0},"6":{"tf":1.0},"60":{"tf":1.0},"64":{"tf":1.0},"75":{"tf":1.0},"78":{"tf":1.0},"81":{"tf":1.4142135623730951},"82":{"tf":1.0},"84":{"tf":1.0}}}},"i":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":5,"docs":{"128":{"tf":1.0},"158":{"tf":1.0},"231":{"tf":1.0},"43":{"tf":1.0},"82":{"tf":1.7320508075688772}},"i":{"df":2,"docs":{"115":{"tf":1.0},"180":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"g":{"df":1,"docs":{"241":{"tf":1.0}}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":1,"docs":{"7":{"tf":1.0}}}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":1,"docs":{"64":{"tf":1.0}}}}}},"r":{"df":2,"docs":{"37":{"tf":1.0},"61":{"tf":1.0}},"e":{"c":{"df":0,"docs":{},"t":{"df":6,"docs":{"220":{"tf":1.0},"225":{"tf":1.0},"226":{"tf":1.0},"268":{"tf":1.4142135623730951},"283":{"tf":1.0},"37":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":10,"docs":{"106":{"tf":1.4142135623730951},"115":{"tf":1.0},"140":{"tf":1.0},"205":{"tf":1.0},"229":{"tf":1.0},"232":{"tf":1.0},"272":{"tf":1.0},"64":{"tf":1.0},"78":{"tf":1.0},"84":{"tf":1.0}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":21,"docs":{"12":{"tf":1.0},"122":{"tf":1.0},"22":{"tf":1.7320508075688772},"224":{"tf":1.0},"229":{"tf":1.0},"25":{"tf":1.0},"252":{"tf":2.23606797749979},"258":{"tf":1.0},"29":{"tf":1.7320508075688772},"30":{"tf":1.0},"33":{"tf":1.0},"34":{"tf":1.4142135623730951},"35":{"tf":1.0},"46":{"tf":1.0},"48":{"tf":1.4142135623730951},"60":{"tf":1.0},"61":{"tf":1.0},"70":{"tf":2.23606797749979},"72":{"tf":2.0},"73":{"tf":1.0},"74":{"tf":1.0}}}}}}},"df":0,"docs":{}},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"11":{"tf":1.0}}}},"df":0,"docs":{}}},"s":{"a":{"b":{"df":0,"docs":{},"l":{"df":11,"docs":{"178":{"tf":1.0},"180":{"tf":1.4142135623730951},"182":{"tf":1.0},"183":{"tf":1.0},"184":{"tf":1.0},"232":{"tf":1.0},"233":{"tf":1.0},"245":{"tf":1.4142135623730951},"26":{"tf":2.0},"48":{"tf":1.0},"61":{"tf":1.0}}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"103":{"tf":1.7320508075688772}}}},"df":0,"docs":{}}}}},"c":{"a":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"283":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"242":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":5,"docs":{"231":{"tf":1.0},"234":{"tf":2.0},"242":{"tf":1.4142135623730951},"283":{"tf":1.0},"87":{"tf":1.0}}}},"df":0,"docs":{}}}},"v":{"df":2,"docs":{"158":{"tf":1.4142135623730951},"261":{"tf":1.4142135623730951}}}},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":6,"docs":{"111":{"tf":1.0},"186":{"tf":1.0},"193":{"tf":1.0},"194":{"tf":1.0},"214":{"tf":1.0},"231":{"tf":1.0}}}}}},"df":0,"docs":{},"k":{"df":5,"docs":{"109":{"tf":1.0},"149":{"tf":1.0},"154":{"tf":1.0},"179":{"tf":1.0},"201":{"tf":1.0}}},"p":{"a":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"115":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"y":{"df":2,"docs":{"117":{"tf":1.0},"283":{"tf":1.0}}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":1,"docs":{"175":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"118":{"tf":1.0}}}}}},"t":{"/":{"df":0,"docs":{},"j":{"df":0,"docs":{},"n":{"a":{".":{"a":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"37":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":1,"docs":{"37":{"tf":1.4142135623730951}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":2,"docs":{"145":{"tf":1.0},"150":{"tf":1.0}}}}}}}}},"r":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":21,"docs":{"134":{"tf":1.0},"160":{"tf":1.0},"161":{"tf":1.0},"163":{"tf":2.0},"164":{"tf":1.0},"165":{"tf":1.0},"167":{"tf":1.0},"171":{"tf":1.7320508075688772},"172":{"tf":1.7320508075688772},"214":{"tf":1.0},"215":{"tf":1.7320508075688772},"218":{"tf":1.0},"219":{"tf":1.0},"220":{"tf":1.7320508075688772},"223":{"tf":1.0},"23":{"tf":1.4142135623730951},"252":{"tf":1.4142135623730951},"253":{"tf":1.0},"32":{"tf":1.4142135623730951},"65":{"tf":1.0},"74":{"tf":1.7320508075688772}}}}},"df":0,"docs":{}}}}},"v":{"df":0,"docs":{},"e":{"df":1,"docs":{"174":{"tf":1.0}},"r":{"df":0,"docs":{},"g":{"df":2,"docs":{"196":{"tf":1.0},"252":{"tf":1.0}}}}}}},"n":{"a":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":1,"docs":{"202":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"o":{"c":{"df":8,"docs":{"107":{"tf":1.4142135623730951},"116":{"tf":1.0},"258":{"tf":1.0},"264":{"tf":1.0},"37":{"tf":1.0},"41":{"tf":1.0},"60":{"tf":1.0},"90":{"tf":1.0}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"194":{"tf":1.0},"274":{"tf":1.0}}}}},"s":{"/":{"d":{"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"258":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"62":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":33,"docs":{"101":{"tf":1.0},"107":{"tf":2.0},"109":{"tf":1.0},"111":{"tf":1.0},"115":{"tf":1.0},"116":{"tf":1.0},"120":{"tf":1.4142135623730951},"123":{"tf":1.0},"128":{"tf":1.0},"138":{"tf":1.4142135623730951},"15":{"tf":1.0},"175":{"tf":1.0},"185":{"tf":1.0},"187":{"tf":1.0},"205":{"tf":1.0},"219":{"tf":1.0},"220":{"tf":1.0},"23":{"tf":1.0},"233":{"tf":1.0},"236":{"tf":1.0},"243":{"tf":1.0},"251":{"tf":1.0},"252":{"tf":1.0},"261":{"tf":1.4142135623730951},"274":{"tf":1.4142135623730951},"284":{"tf":1.7320508075688772},"285":{"tf":1.0},"286":{"tf":2.0},"41":{"tf":1.0},"62":{"tf":1.0},"75":{"tf":2.23606797749979},"85":{"tf":1.0},"91":{"tf":1.0}}}}}}}},"df":8,"docs":{"101":{"tf":1.0},"115":{"tf":1.0},"13":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"224":{"tf":1.0},"37":{"tf":1.0},"78":{"tf":1.0}},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"n":{"'":{"df":0,"docs":{},"t":{"df":19,"docs":{"111":{"tf":1.0},"154":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0},"169":{"tf":1.7320508075688772},"170":{"tf":1.7320508075688772},"171":{"tf":1.7320508075688772},"175":{"tf":1.0},"182":{"tf":1.0},"22":{"tf":1.0},"228":{"tf":1.0},"233":{"tf":1.0},"276":{"tf":1.0},"48":{"tf":1.7320508075688772},"60":{"tf":1.0},"7":{"tf":1.0},"78":{"tf":1.0},"82":{"tf":1.0},"90":{"tf":1.0}}}},"df":0,"docs":{}}}},"n":{"'":{"df":0,"docs":{},"t":{"df":24,"docs":{"104":{"tf":1.0},"105":{"tf":1.0},"106":{"tf":1.0},"117":{"tf":1.4142135623730951},"119":{"tf":1.0},"124":{"tf":1.0},"150":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0},"159":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"175":{"tf":1.0},"226":{"tf":1.0},"228":{"tf":1.0},"266":{"tf":1.0},"274":{"tf":1.0},"279":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"43":{"tf":1.0},"58":{"tf":1.0},"60":{"tf":1.0},"79":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":17,"docs":{"106":{"tf":1.0},"182":{"tf":1.0},"184":{"tf":1.0},"194":{"tf":1.0},"222":{"tf":1.0},"238":{"tf":1.4142135623730951},"242":{"tf":1.7320508075688772},"245":{"tf":1.0},"25":{"tf":1.0},"282":{"tf":1.0},"30":{"tf":1.0},"35":{"tf":1.0},"52":{"tf":1.0},"73":{"tf":1.4142135623730951},"79":{"tf":1.4142135623730951},"81":{"tf":1.0},"88":{"tf":1.0}}},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":1,"docs":{"117":{"tf":1.0}}}}}}}}},"’":{"df":0,"docs":{},"t":{"df":2,"docs":{"196":{"tf":1.0},"89":{"tf":1.0}}}}},"s":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"=":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"37":{"tf":2.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"t":{"df":1,"docs":{"183":{"tf":1.4142135623730951}}},"u":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"73":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"w":{"df":0,"docs":{},"n":{"df":4,"docs":{"115":{"tf":1.0},"165":{"tf":1.0},"235":{"tf":1.0},"242":{"tf":1.0}},"g":{"df":0,"docs":{},"r":{"a":{"d":{"df":1,"docs":{"212":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":3,"docs":{"12":{"tf":1.0},"279":{"tf":1.0},"40":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"116":{"tf":1.0},"172":{"tf":1.0}}},"df":0,"docs":{}}}}}},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"274":{"tf":1.0}}}},"r":{"a":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":2,"docs":{"104":{"tf":1.4142135623730951},"7":{"tf":1.4142135623730951}}}},"m":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"209":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"i":{"df":7,"docs":{"203":{"tf":1.7320508075688772},"204":{"tf":1.0},"205":{"tf":1.0},"208":{"tf":1.0},"214":{"tf":1.0},"216":{"tf":1.0},"218":{"tf":1.4142135623730951}},"v":{"df":0,"docs":{},"e":{"df":2,"docs":{"188":{"tf":1.0},"274":{"tf":1.0}},"r":{"df":7,"docs":{"141":{"tf":1.0},"150":{"tf":1.0},"162":{"tf":1.0},"178":{"tf":1.0},"206":{"tf":1.0},"208":{"tf":1.4142135623730951},"210":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"p":{"df":5,"docs":{"12":{"tf":1.0},"216":{"tf":1.4142135623730951},"231":{"tf":1.0},"245":{"tf":1.0},"250":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"161":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"u":{"df":0,"docs":{},"e":{"df":10,"docs":{"11":{"tf":1.0},"116":{"tf":1.0},"180":{"tf":1.4142135623730951},"197":{"tf":1.0},"220":{"tf":1.0},"233":{"tf":1.4142135623730951},"280":{"tf":1.0},"46":{"tf":1.0},"64":{"tf":1.0},"7":{"tf":1.0}}},"m":{"df":0,"docs":{},"p":{"df":1,"docs":{"62":{"tf":1.0}}}},"p":{"df":0,"docs":{},"e":{"df":3,"docs":{"140":{"tf":1.0},"145":{"tf":1.4142135623730951},"146":{"tf":1.0}}},"l":{"df":0,"docs":{},"i":{"c":{"df":6,"docs":{"122":{"tf":1.0},"196":{"tf":1.4142135623730951},"256":{"tf":1.0},"258":{"tf":1.0},"263":{"tf":1.0},"282":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}},"r":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"86":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":8,"docs":{"105":{"tf":1.0},"106":{"tf":1.0},"152":{"tf":1.0},"154":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0},"175":{"tf":1.0},"58":{"tf":1.0}}}}},"y":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":3,"docs":{"253":{"tf":1.0},"278":{"tf":1.0},"282":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{".":{"df":0,"docs":{},"g":{"df":14,"docs":{"104":{"tf":1.0},"165":{"tf":1.0},"18":{"tf":1.0},"184":{"tf":1.0},"21":{"tf":1.0},"22":{"tf":1.0},"221":{"tf":1.0},"223":{"tf":1.0},"224":{"tf":1.0},"279":{"tf":1.0},"282":{"tf":1.0},"37":{"tf":1.0},"51":{"tf":1.0},"59":{"tf":1.4142135623730951}}}},"a":{"c":{"df":0,"docs":{},"h":{"df":46,"docs":{"0":{"tf":1.0},"101":{"tf":1.0},"106":{"tf":1.0},"115":{"tf":1.4142135623730951},"163":{"tf":1.0},"166":{"tf":1.0},"167":{"tf":1.4142135623730951},"172":{"tf":1.7320508075688772},"175":{"tf":1.0},"22":{"tf":1.0},"220":{"tf":1.4142135623730951},"221":{"tf":2.449489742783178},"222":{"tf":1.0},"223":{"tf":1.0},"224":{"tf":1.4142135623730951},"225":{"tf":1.7320508075688772},"226":{"tf":1.0},"228":{"tf":1.0},"229":{"tf":1.4142135623730951},"231":{"tf":1.0},"232":{"tf":1.0},"242":{"tf":1.4142135623730951},"243":{"tf":1.0},"244":{"tf":1.0},"245":{"tf":1.4142135623730951},"246":{"tf":1.0},"249":{"tf":1.7320508075688772},"252":{"tf":1.0},"256":{"tf":1.4142135623730951},"257":{"tf":1.0},"258":{"tf":1.0},"271":{"tf":1.0},"274":{"tf":1.0},"282":{"tf":1.0},"32":{"tf":1.0},"37":{"tf":1.0},"43":{"tf":1.0},"46":{"tf":1.0},"48":{"tf":1.0},"51":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.4142135623730951},"64":{"tf":1.0},"68":{"tf":1.0},"69":{"tf":1.0},"84":{"tf":1.0}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":8,"docs":{"134":{"tf":1.0},"176":{"tf":1.0},"177":{"tf":1.0},"178":{"tf":1.0},"179":{"tf":1.4142135623730951},"180":{"tf":1.0},"183":{"tf":1.0},"184":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"205":{"tf":1.0},"90":{"tf":1.0}}}}}}},"s":{"df":1,"docs":{"162":{"tf":1.4142135623730951}},"i":{"df":9,"docs":{"115":{"tf":1.0},"116":{"tf":1.4142135623730951},"129":{"tf":1.0},"158":{"tf":1.0},"220":{"tf":1.0},"233":{"tf":1.0},"241":{"tf":1.0},"280":{"tf":1.0},"46":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":4,"docs":{"172":{"tf":1.0},"278":{"tf":1.0},"60":{"tf":1.0},"81":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{"df":4,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"26":{"tf":1.0},"58":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"i":{"df":4,"docs":{"107":{"tf":1.0},"133":{"tf":1.0},"162":{"tf":1.0},"221":{"tf":1.0}}}}}}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":2,"docs":{"226":{"tf":1.0},"232":{"tf":1.0}}}}}}}}}},"d":{"df":0,"docs":{},"g":{"df":2,"docs":{"224":{"tf":1.4142135623730951},"233":{"tf":1.0}}},"i":{"df":0,"docs":{},"t":{"df":44,"docs":{"100":{"tf":1.0},"105":{"tf":1.0},"109":{"tf":1.4142135623730951},"117":{"tf":1.0},"119":{"tf":1.0},"124":{"tf":1.4142135623730951},"127":{"tf":1.0},"133":{"tf":1.0},"138":{"tf":1.0},"147":{"tf":1.0},"15":{"tf":1.0},"159":{"tf":1.0},"175":{"tf":1.0},"18":{"tf":1.0},"185":{"tf":1.0},"201":{"tf":1.0},"218":{"tf":1.0},"22":{"tf":1.0},"224":{"tf":1.0},"242":{"tf":1.0},"250":{"tf":1.0},"253":{"tf":1.0},"258":{"tf":2.23606797749979},"259":{"tf":1.0},"263":{"tf":1.0},"264":{"tf":1.0},"273":{"tf":1.0},"277":{"tf":1.0},"282":{"tf":1.0},"283":{"tf":1.0},"286":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"40":{"tf":1.0},"51":{"tf":1.0},"56":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0},"67":{"tf":1.0},"70":{"tf":1.4142135623730951},"74":{"tf":1.0},"8":{"tf":1.0},"90":{"tf":1.0}}}},"u":{"c":{"df":1,"docs":{"214":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":6,"docs":{"184":{"tf":1.0},"197":{"tf":1.0},"201":{"tf":1.0},"210":{"tf":1.0},"69":{"tf":1.0},"7":{"tf":1.0}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":7,"docs":{"121":{"tf":1.0},"161":{"tf":1.0},"164":{"tf":1.0},"196":{"tf":1.0},"199":{"tf":1.0},"201":{"tf":1.0},"229":{"tf":1.0}}}}}}},"g":{"df":13,"docs":{"189":{"tf":1.0},"20":{"tf":1.0},"200":{"tf":1.4142135623730951},"225":{"tf":1.4142135623730951},"231":{"tf":1.4142135623730951},"232":{"tf":1.4142135623730951},"236":{"tf":1.0},"242":{"tf":1.4142135623730951},"250":{"tf":1.0},"74":{"tf":1.0},"82":{"tf":1.7320508075688772},"83":{"tf":1.0},"86":{"tf":1.0}}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":3,"docs":{"145":{"tf":1.4142135623730951},"155":{"tf":1.0},"156":{"tf":1.0}}}}}}},"m":{"b":{"df":1,"docs":{"244":{"tf":1.0}},"e":{"d":{"df":5,"docs":{"125":{"tf":1.0},"225":{"tf":1.4142135623730951},"228":{"tf":1.0},"242":{"tf":1.0},"250":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":1,"docs":{"274":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"262":{"tf":1.0},"282":{"tf":1.0}}}},"p":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"s":{"df":1,"docs":{"204":{"tf":1.0}},"i":{"df":1,"docs":{"75":{"tf":1.0}}}}},"df":0,"docs":{}},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"y":{"df":0,"docs":{},"e":{"df":1,"docs":{"258":{"tf":1.0}}}}}}},"u":{"df":0,"docs":{},"l":{"df":2,"docs":{"17":{"tf":1.4142135623730951},"51":{"tf":1.0}}}}},"n":{"a":{"b":{"df":0,"docs":{},"l":{"df":11,"docs":{"0":{"tf":1.4142135623730951},"10":{"tf":1.0},"138":{"tf":1.0},"184":{"tf":1.0},"189":{"tf":1.0},"232":{"tf":1.0},"233":{"tf":1.4142135623730951},"242":{"tf":1.0},"245":{"tf":2.0},"26":{"tf":1.0},"62":{"tf":1.0}},"e":{"d":{"/":{"d":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"242":{"tf":1.0}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"o":{"d":{"df":1,"docs":{"62":{"tf":1.0}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"s":{"df":1,"docs":{"82":{"tf":1.0}},"s":{"df":2,"docs":{"208":{"tf":1.0},"217":{"tf":1.0}}}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"5":{"tf":1.0}}}},"r":{"a":{"df":0,"docs":{},"g":{"df":4,"docs":{"165":{"tf":1.0},"264":{"tf":1.0},"274":{"tf":1.0},"7":{"tf":1.0}}}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":9,"docs":{"140":{"tf":2.449489742783178},"141":{"tf":1.0},"142":{"tf":1.4142135623730951},"143":{"tf":1.0},"145":{"tf":3.0},"146":{"tf":2.0},"229":{"tf":1.0},"245":{"tf":1.4142135623730951},"254":{"tf":2.0}}}}}},"y":{"df":0,"docs":{},"r":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"141":{"tf":1.0}}}}}}},"d":{"df":32,"docs":{"10":{"tf":1.4142135623730951},"103":{"tf":1.0},"124":{"tf":1.0},"13":{"tf":1.0},"145":{"tf":1.0},"197":{"tf":1.4142135623730951},"199":{"tf":1.0},"20":{"tf":1.0},"200":{"tf":1.7320508075688772},"204":{"tf":2.23606797749979},"205":{"tf":1.4142135623730951},"213":{"tf":1.0},"214":{"tf":1.0},"216":{"tf":1.4142135623730951},"218":{"tf":1.0},"22":{"tf":1.4142135623730951},"225":{"tf":1.4142135623730951},"233":{"tf":1.0},"236":{"tf":1.0},"240":{"tf":1.0},"242":{"tf":1.0},"48":{"tf":2.0},"51":{"tf":1.4142135623730951},"52":{"tf":1.0},"58":{"tf":1.0},"62":{"tf":1.0},"64":{"tf":1.0},"70":{"tf":1.0},"72":{"tf":1.0},"82":{"tf":1.0},"83":{"tf":1.0},"84":{"tf":1.4142135623730951}},"i":{"df":0,"docs":{},"f":{"df":1,"docs":{"37":{"tf":1.0}}}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"x":{"a":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"283":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"c":{"df":7,"docs":{"190":{"tf":1.0},"201":{"tf":1.4142135623730951},"225":{"tf":1.4142135623730951},"226":{"tf":1.0},"242":{"tf":1.0},"274":{"tf":1.0},"79":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":24,"docs":{"104":{"tf":1.0},"187":{"tf":1.0},"225":{"tf":2.6457513110645907},"226":{"tf":2.6457513110645907},"228":{"tf":3.1622776601683795},"229":{"tf":2.23606797749979},"231":{"tf":3.0},"232":{"tf":3.1622776601683795},"233":{"tf":2.23606797749979},"234":{"tf":1.7320508075688772},"236":{"tf":1.0},"237":{"tf":1.0},"238":{"tf":1.7320508075688772},"241":{"tf":2.23606797749979},"242":{"tf":3.605551275463989},"245":{"tf":2.8284271247461903},"246":{"tf":1.0},"249":{"tf":1.4142135623730951},"258":{"tf":1.0},"274":{"tf":1.0},"48":{"tf":1.4142135623730951},"78":{"tf":1.0},"8":{"tf":1.0},"87":{"tf":1.0}},"e":{"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"242":{"tf":1.0}}}}}}}},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"_":{"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"242":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"242":{"tf":1.0}}}}}}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"242":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":3,"docs":{"214":{"tf":1.0},"225":{"tf":1.0},"26":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":3,"docs":{"180":{"tf":1.0},"183":{"tf":1.0},"189":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":21,"docs":{"104":{"tf":1.4142135623730951},"11":{"tf":1.0},"115":{"tf":1.0},"122":{"tf":1.0},"132":{"tf":1.0},"197":{"tf":1.0},"20":{"tf":1.0},"222":{"tf":1.0},"225":{"tf":1.0},"226":{"tf":1.0},"233":{"tf":1.0},"236":{"tf":1.0},"263":{"tf":1.0},"274":{"tf":1.4142135623730951},"279":{"tf":1.0},"37":{"tf":1.0},"45":{"tf":1.7320508075688772},"46":{"tf":1.0},"64":{"tf":1.4142135623730951},"7":{"tf":1.0},"79":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":3,"docs":{"122":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0}}}},"i":{"df":0,"docs":{},"r":{"df":12,"docs":{"103":{"tf":1.7320508075688772},"106":{"tf":1.0},"130":{"tf":1.0},"140":{"tf":1.0},"184":{"tf":1.0},"226":{"tf":1.0},"231":{"tf":1.0},"232":{"tf":1.0},"260":{"tf":1.0},"274":{"tf":1.4142135623730951},"46":{"tf":1.0},"62":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"142":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"i":{"df":6,"docs":{"226":{"tf":1.0},"241":{"tf":1.4142135623730951},"283":{"tf":1.0},"7":{"tf":1.0},"73":{"tf":1.7320508075688772},"78":{"tf":1.4142135623730951}}}}},"u":{"df":0,"docs":{},"m":{"df":8,"docs":{"104":{"tf":1.0},"105":{"tf":1.7320508075688772},"106":{"tf":1.0},"108":{"tf":1.0},"242":{"tf":1.7320508075688772},"87":{"tf":1.0},"93":{"tf":1.0},"97":{"tf":1.0}}}},"v":{"df":1,"docs":{"12":{"tf":1.0}},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":10,"docs":{"11":{"tf":1.7320508075688772},"12":{"tf":1.0},"122":{"tf":1.0},"14":{"tf":1.0},"274":{"tf":2.23606797749979},"277":{"tf":1.0},"37":{"tf":1.4142135623730951},"5":{"tf":1.0},"61":{"tf":1.0},"9":{"tf":1.0}},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":4,"docs":{"11":{"tf":1.4142135623730951},"12":{"tf":1.0},"14":{"tf":1.0},"279":{"tf":1.0}}}}},"df":1,"docs":{"225":{"tf":1.0}}}}}}}}}}}},"o":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"'":{"df":1,"docs":{"274":{"tf":1.0}}},"df":0,"docs":{}}}}},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"229":{"tf":1.0},"231":{"tf":1.0}}}}}}},"i":{"c":{"df":1,"docs":{"218":{"tf":1.0}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":1,"docs":{"200":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":24,"docs":{"104":{"tf":1.7320508075688772},"105":{"tf":1.0},"116":{"tf":1.0},"122":{"tf":1.0},"149":{"tf":1.7320508075688772},"150":{"tf":1.0},"151":{"tf":2.23606797749979},"152":{"tf":2.449489742783178},"154":{"tf":2.23606797749979},"155":{"tf":2.0},"156":{"tf":2.0},"158":{"tf":1.0},"189":{"tf":1.0},"204":{"tf":1.0},"226":{"tf":1.0},"237":{"tf":1.0},"239":{"tf":1.0},"256":{"tf":1.0},"258":{"tf":1.0},"262":{"tf":1.0},"264":{"tf":1.0},"283":{"tf":1.7320508075688772},"61":{"tf":1.0},"7":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"i":{"df":8,"docs":{"133":{"tf":1.0},"154":{"tf":1.0},"183":{"tf":1.0},"226":{"tf":1.0},"258":{"tf":1.0},"51":{"tf":1.0},"62":{"tf":1.0},"64":{"tf":1.0}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":3,"docs":{"11":{"tf":1.0},"111":{"tf":1.0},"115":{"tf":1.0}}}}}}},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":3,"docs":{"141":{"tf":1.0},"143":{"tf":1.0},"208":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"261":{"tf":1.0}}}}}},"t":{"c":{"df":24,"docs":{"112":{"tf":1.0},"149":{"tf":1.0},"207":{"tf":1.0},"214":{"tf":1.0},"220":{"tf":1.0},"226":{"tf":1.4142135623730951},"236":{"tf":1.0},"237":{"tf":1.4142135623730951},"239":{"tf":1.0},"240":{"tf":1.0},"241":{"tf":1.0},"242":{"tf":1.0},"244":{"tf":1.0},"249":{"tf":1.0},"255":{"tf":1.0},"278":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"61":{"tf":1.0},"75":{"tf":1.0},"78":{"tf":1.0},"79":{"tf":1.0},"80":{"tf":1.0},"86":{"tf":1.0}}},"df":0,"docs":{}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":3,"docs":{"161":{"tf":1.0},"203":{"tf":1.0},"59":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":10,"docs":{"104":{"tf":1.0},"140":{"tf":1.0},"19":{"tf":1.0},"212":{"tf":1.0},"225":{"tf":1.7320508075688772},"228":{"tf":1.0},"232":{"tf":1.0},"58":{"tf":1.4142135623730951},"60":{"tf":1.0},"61":{"tf":1.0}},"t":{"df":4,"docs":{"190":{"tf":1.0},"193":{"tf":1.0},"197":{"tf":1.4142135623730951},"283":{"tf":1.0}},"u":{"df":1,"docs":{"167":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"'":{"df":1,"docs":{"104":{"tf":1.0}}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"h":{"df":11,"docs":{"179":{"tf":1.0},"194":{"tf":1.0},"200":{"tf":1.0},"22":{"tf":1.0},"225":{"tf":1.0},"226":{"tf":1.0},"258":{"tf":1.0},"261":{"tf":1.0},"61":{"tf":1.0},"64":{"tf":1.0},"89":{"tf":1.4142135623730951}}}}}}},"i":{"d":{"df":1,"docs":{"201":{"tf":1.0}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"v":{"df":3,"docs":{"193":{"tf":1.0},"63":{"tf":1.0},"81":{"tf":1.0}}}}}},"x":{"a":{"c":{"df":0,"docs":{},"t":{"df":4,"docs":{"123":{"tf":1.0},"197":{"tf":1.0},"200":{"tf":1.0},"233":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":6,"docs":{"132":{"tf":1.0},"201":{"tf":1.4142135623730951},"225":{"tf":1.0},"237":{"tf":1.0},"61":{"tf":1.0},"79":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":2,"docs":{"128":{"tf":1.0},"205":{"tf":1.0}}}},"p":{"df":0,"docs":{},"l":{"df":45,"docs":{"100":{"tf":1.0},"105":{"tf":1.7320508075688772},"106":{"tf":1.0},"107":{"tf":1.0},"108":{"tf":1.4142135623730951},"109":{"tf":1.0},"115":{"tf":1.0},"119":{"tf":1.4142135623730951},"123":{"tf":1.4142135623730951},"124":{"tf":1.4142135623730951},"130":{"tf":1.0},"131":{"tf":1.0},"132":{"tf":1.0},"156":{"tf":1.0},"175":{"tf":1.0},"18":{"tf":1.0},"20":{"tf":1.4142135623730951},"201":{"tf":1.4142135623730951},"213":{"tf":1.0},"216":{"tf":1.0},"22":{"tf":1.0},"225":{"tf":1.7320508075688772},"226":{"tf":1.4142135623730951},"228":{"tf":1.0},"229":{"tf":1.7320508075688772},"232":{"tf":1.0},"233":{"tf":1.0},"238":{"tf":1.0},"245":{"tf":1.0},"25":{"tf":1.0},"252":{"tf":1.0},"268":{"tf":1.7320508075688772},"31":{"tf":1.0},"36":{"tf":1.0},"58":{"tf":2.23606797749979},"59":{"tf":1.7320508075688772},"62":{"tf":1.0},"78":{"tf":1.0},"79":{"tf":1.7320508075688772},"81":{"tf":1.0},"82":{"tf":1.4142135623730951},"83":{"tf":1.4142135623730951},"86":{"tf":1.0},"94":{"tf":1.0},"97":{"tf":1.0}},"e":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":1,"docs":{"105":{"tf":1.4142135623730951}}}}}},"s":{"/":{"*":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"61":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"106":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}}}}}}}},"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":2,"docs":{"171":{"tf":1.0},"190":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"u":{"d":{"df":2,"docs":{"264":{"tf":1.4142135623730951},"37":{"tf":1.4142135623730951}}},"df":0,"docs":{},"s":{"df":1,"docs":{"194":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":9,"docs":{"13":{"tf":1.0},"158":{"tf":1.0},"19":{"tf":1.4142135623730951},"249":{"tf":1.4142135623730951},"25":{"tf":1.0},"274":{"tf":1.4142135623730951},"60":{"tf":1.4142135623730951},"62":{"tf":1.0},"7":{"tf":1.0}}}}},"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":3,"docs":{"166":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0}}}}},"df":0,"docs":{}}},"h":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"188":{"tf":1.0}}}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":36,"docs":{"101":{"tf":1.4142135623730951},"104":{"tf":1.7320508075688772},"105":{"tf":1.7320508075688772},"108":{"tf":1.0},"109":{"tf":1.4142135623730951},"115":{"tf":1.0},"120":{"tf":1.0},"122":{"tf":1.0},"123":{"tf":1.0},"13":{"tf":1.0},"140":{"tf":1.0},"141":{"tf":1.0},"166":{"tf":1.0},"167":{"tf":1.4142135623730951},"183":{"tf":1.0},"187":{"tf":1.0},"191":{"tf":1.0},"193":{"tf":1.0},"196":{"tf":1.7320508075688772},"197":{"tf":2.0},"198":{"tf":1.7320508075688772},"199":{"tf":1.0},"200":{"tf":1.7320508075688772},"201":{"tf":1.0},"210":{"tf":1.0},"225":{"tf":1.0},"228":{"tf":1.0},"232":{"tf":1.0},"233":{"tf":1.0},"263":{"tf":1.0},"274":{"tf":1.0},"41":{"tf":1.4142135623730951},"59":{"tf":1.0},"64":{"tf":2.0},"75":{"tf":1.0},"78":{"tf":1.0}}}},"t":{"df":2,"docs":{"233":{"tf":1.0},"60":{"tf":1.0}}}},"p":{"a":{"df":0,"docs":{},"n":{"d":{"df":3,"docs":{"167":{"tf":1.0},"201":{"tf":1.0},"283":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"(":{"\"":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"62":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":20,"docs":{"101":{"tf":1.0},"104":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"156":{"tf":1.0},"178":{"tf":1.0},"180":{"tf":1.0},"201":{"tf":1.0},"222":{"tf":1.0},"224":{"tf":1.0},"225":{"tf":1.0},"230":{"tf":1.0},"237":{"tf":1.0},"252":{"tf":1.0},"263":{"tf":1.0},"264":{"tf":1.0},"278":{"tf":1.0},"62":{"tf":1.0},"7":{"tf":1.0},"81":{"tf":1.0}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":27,"docs":{"10":{"tf":1.0},"101":{"tf":1.0},"115":{"tf":1.0},"134":{"tf":1.0},"158":{"tf":1.0},"164":{"tf":1.0},"174":{"tf":1.0},"175":{"tf":1.0},"176":{"tf":1.0},"177":{"tf":1.7320508075688772},"178":{"tf":1.7320508075688772},"179":{"tf":2.6457513110645907},"180":{"tf":2.6457513110645907},"182":{"tf":2.0},"183":{"tf":2.449489742783178},"184":{"tf":2.8284271247461903},"185":{"tf":1.4142135623730951},"187":{"tf":1.0},"204":{"tf":1.4142135623730951},"205":{"tf":1.0},"206":{"tf":1.4142135623730951},"208":{"tf":1.4142135623730951},"212":{"tf":1.0},"218":{"tf":1.0},"22":{"tf":1.0},"233":{"tf":1.0},"274":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"204":{"tf":1.0}}},"df":0,"docs":{}}},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":6,"docs":{"0":{"tf":1.0},"177":{"tf":1.0},"178":{"tf":1.0},"184":{"tf":1.4142135623730951},"268":{"tf":1.0},"284":{"tf":1.0}}}}}}}}},"i":{"df":0,"docs":{},"r":{"df":1,"docs":{"277":{"tf":1.0}}}},"l":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"81":{"tf":1.0}}}},"n":{"df":1,"docs":{"7":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":4,"docs":{"128":{"tf":1.0},"138":{"tf":1.0},"224":{"tf":1.0},"241":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":4,"docs":{"115":{"tf":1.0},"201":{"tf":1.0},"64":{"tf":1.0},"7":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":3,"docs":{"11":{"tf":1.4142135623730951},"252":{"tf":1.0},"37":{"tf":2.6457513110645907}}}},"s":{"df":29,"docs":{"101":{"tf":1.0},"103":{"tf":1.4142135623730951},"108":{"tf":1.0},"109":{"tf":1.0},"111":{"tf":1.0},"116":{"tf":1.0},"146":{"tf":1.0},"159":{"tf":1.0},"182":{"tf":1.0},"192":{"tf":1.0},"196":{"tf":1.0},"198":{"tf":1.0},"200":{"tf":1.0},"221":{"tf":1.0},"226":{"tf":1.4142135623730951},"229":{"tf":1.0},"232":{"tf":2.23606797749979},"236":{"tf":1.0},"239":{"tf":1.0},"241":{"tf":1.0},"242":{"tf":1.4142135623730951},"253":{"tf":1.0},"268":{"tf":1.0},"281":{"tf":2.0},"69":{"tf":1.0},"77":{"tf":1.0},"78":{"tf":2.23606797749979},"83":{"tf":1.0},"90":{"tf":1.4142135623730951}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":2,"docs":{"104":{"tf":1.4142135623730951},"170":{"tf":1.0}}}}}}},"t":{".":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"r":{"c":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":1,"docs":{"22":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"37":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":5,"docs":{"257":{"tf":1.4142135623730951},"44":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"98":{"tf":1.4142135623730951}}},"t":{"df":1,"docs":{"115":{"tf":1.0}}}},"r":{"df":0,"docs":{},"n":{"df":8,"docs":{"103":{"tf":1.0},"111":{"tf":1.0},"114":{"tf":1.0},"145":{"tf":1.0},"167":{"tf":1.0},"242":{"tf":1.0},"260":{"tf":1.0},"7":{"tf":1.0}}}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"107":{"tf":1.0},"271":{"tf":1.0}}}},"df":1,"docs":{"115":{"tf":1.0}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":2,"docs":{"156":{"tf":1.0},"233":{"tf":1.0}}}}}}}},"f":{"a":{"c":{"df":0,"docs":{},"e":{"df":5,"docs":{"107":{"tf":1.0},"25":{"tf":1.0},"283":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0}}},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"125":{"tf":1.0}},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"138":{"tf":1.0}}}}}},"t":{"df":4,"docs":{"107":{"tf":1.0},"115":{"tf":1.0},"180":{"tf":1.0},"61":{"tf":1.0}},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"200":{"tf":1.0}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":16,"docs":{"104":{"tf":1.4142135623730951},"119":{"tf":1.0},"146":{"tf":1.0},"151":{"tf":1.0},"20":{"tf":1.0},"204":{"tf":1.0},"205":{"tf":1.0},"208":{"tf":1.0},"21":{"tf":1.0},"242":{"tf":1.0},"25":{"tf":1.0},"27":{"tf":1.0},"280":{"tf":1.0},"282":{"tf":1.0},"62":{"tf":1.0},"92":{"tf":1.0}},"u":{"df":0,"docs":{},"r":{"df":5,"docs":{"159":{"tf":1.0},"274":{"tf":1.0},"282":{"tf":1.0},"40":{"tf":1.4142135623730951},"62":{"tf":1.0}},"e":{"df":0,"docs":{},"s":{"@":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":1,"docs":{"274":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}}},"r":{"df":1,"docs":{"116":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":3,"docs":{"158":{"tf":1.0},"241":{"tf":1.0},"7":{"tf":1.0}}}}}},"k":{"df":0,"docs":{},"e":{"df":2,"docs":{"138":{"tf":1.0},"203":{"tf":1.0}}}},"l":{"df":0,"docs":{},"s":{"df":2,"docs":{"60":{"tf":1.4142135623730951},"61":{"tf":1.4142135623730951}}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"r":{"df":2,"docs":{"102":{"tf":1.0},"103":{"tf":1.0}}}},"df":0,"docs":{}}}}},"q":{"0":{"df":2,"docs":{"60":{"tf":1.0},"61":{"tf":1.0}}},"df":2,"docs":{"110":{"tf":1.0},"85":{"tf":1.0}}},"r":{"df":6,"docs":{"124":{"tf":1.0},"201":{"tf":1.0},"225":{"tf":1.0},"242":{"tf":1.0},"59":{"tf":1.0},"63":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"58":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"57":{"tf":1.0}}}}}},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"205":{"tf":1.0}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":3,"docs":{"165":{"tf":1.0},"172":{"tf":1.0},"37":{"tf":1.0}}}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"180":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":12,"docs":{"103":{"tf":1.0},"124":{"tf":1.4142135623730951},"189":{"tf":1.0},"196":{"tf":1.0},"198":{"tf":1.0},"221":{"tf":1.0},"255":{"tf":1.0},"260":{"tf":1.0},"282":{"tf":1.0},"43":{"tf":1.0},"62":{"tf":1.7320508075688772},"69":{"tf":1.0}},"e":{"df":0,"docs":{},"s":{"=":{"[":{"\"":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"69":{"tf":1.0}}}}}}}}},"d":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":1,"docs":{"62":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":7,"docs":{"107":{"tf":1.0},"170":{"tf":1.0},"25":{"tf":1.0},"3":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"6":{"tf":1.0}}}},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"55":{"tf":1.0}}}}},"/":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"l":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"18":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"126":{"tf":1.0}}}}}},"df":19,"docs":{"10":{"tf":1.0},"108":{"tf":1.0},"11":{"tf":1.0},"12":{"tf":2.0},"126":{"tf":1.4142135623730951},"145":{"tf":1.0},"16":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":1.0},"185":{"tf":1.0},"200":{"tf":1.0},"21":{"tf":1.0},"224":{"tf":1.0},"257":{"tf":1.7320508075688772},"38":{"tf":1.0},"39":{"tf":1.7320508075688772},"40":{"tf":1.7320508075688772},"51":{"tf":1.0},"55":{"tf":1.4142135623730951}}}},"n":{"df":0,"docs":{},"e":{"c":{"df":1,"docs":{"145":{"tf":1.0}}},"df":0,"docs":{}}}},"t":{"c":{"df":0,"docs":{},"h":{"df":6,"docs":{"189":{"tf":1.0},"190":{"tf":1.4142135623730951},"232":{"tf":1.0},"245":{"tf":1.0},"249":{"tf":1.7320508075688772},"274":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"w":{"df":3,"docs":{"58":{"tf":1.0},"6":{"tf":1.0},"68":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"59":{"tf":1.0}}}}}},"f":{"df":0,"docs":{},"i":{"_":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"73":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"73":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"116":{"tf":1.0}}}}}}}}}},"df":13,"docs":{"101":{"tf":1.0},"103":{"tf":3.1622776601683795},"104":{"tf":1.0},"105":{"tf":1.7320508075688772},"106":{"tf":1.7320508075688772},"115":{"tf":1.0},"116":{"tf":2.0},"193":{"tf":1.0},"241":{"tf":1.0},"43":{"tf":1.0},"44":{"tf":1.7320508075688772},"51":{"tf":1.0},"90":{"tf":1.7320508075688772}}}},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":5,"docs":{"140":{"tf":1.4142135623730951},"145":{"tf":2.0},"249":{"tf":1.0},"250":{"tf":1.0},"83":{"tf":1.0}}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"e":{":":{"/":{"/":{"/":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"q":{"/":{"c":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"e":{"/":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"31":{"tf":1.0},"36":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":63,"docs":{"1":{"tf":1.4142135623730951},"103":{"tf":1.7320508075688772},"104":{"tf":2.449489742783178},"105":{"tf":2.6457513110645907},"106":{"tf":2.23606797749979},"107":{"tf":1.0},"108":{"tf":1.7320508075688772},"109":{"tf":2.0},"111":{"tf":1.0},"114":{"tf":1.0},"117":{"tf":1.4142135623730951},"12":{"tf":1.0},"13":{"tf":1.0},"133":{"tf":1.0},"137":{"tf":1.0},"151":{"tf":1.4142135623730951},"152":{"tf":1.4142135623730951},"157":{"tf":1.0},"158":{"tf":1.7320508075688772},"159":{"tf":1.0},"165":{"tf":1.0},"167":{"tf":1.4142135623730951},"17":{"tf":1.0},"18":{"tf":1.0},"187":{"tf":1.0},"189":{"tf":1.0},"200":{"tf":1.0},"201":{"tf":1.0},"22":{"tf":1.4142135623730951},"220":{"tf":1.0},"221":{"tf":1.0},"222":{"tf":1.0},"229":{"tf":1.0},"25":{"tf":1.4142135623730951},"251":{"tf":1.0},"252":{"tf":2.0},"26":{"tf":1.4142135623730951},"263":{"tf":1.0},"271":{"tf":1.7320508075688772},"274":{"tf":1.0},"279":{"tf":1.4142135623730951},"28":{"tf":1.4142135623730951},"282":{"tf":1.7320508075688772},"284":{"tf":1.0},"29":{"tf":1.0},"31":{"tf":1.0},"33":{"tf":1.4142135623730951},"36":{"tf":1.0},"4":{"tf":1.0},"40":{"tf":1.0},"45":{"tf":1.0},"60":{"tf":1.7320508075688772},"61":{"tf":2.449489742783178},"64":{"tf":1.0},"65":{"tf":1.0},"69":{"tf":1.0},"70":{"tf":2.449489742783178},"72":{"tf":1.0},"73":{"tf":3.0},"74":{"tf":1.0},"79":{"tf":1.0},"81":{"tf":1.4142135623730951},"98":{"tf":1.7320508075688772}},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":1,"docs":{"25":{"tf":1.0}}}}}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"126":{"tf":1.0}}}}}},"n":{"a":{"df":0,"docs":{},"l":{"df":13,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"128":{"tf":1.0},"200":{"tf":1.0},"201":{"tf":1.0},"205":{"tf":1.0},"214":{"tf":1.0},"245":{"tf":1.0},"267":{"tf":1.0},"31":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"61":{"tf":1.0},"73":{"tf":1.0}}}},"d":{"df":18,"docs":{"1":{"tf":1.0},"104":{"tf":1.0},"105":{"tf":1.0},"106":{"tf":2.23606797749979},"122":{"tf":1.0},"126":{"tf":1.0},"18":{"tf":1.0},"21":{"tf":1.0},"224":{"tf":1.0},"26":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.4142135623730951},"37":{"tf":1.0},"46":{"tf":1.0},"52":{"tf":1.0},"6":{"tf":1.4142135623730951},"62":{"tf":1.0},"82":{"tf":1.0}}},"df":0,"docs":{},"e":{"df":2,"docs":{"133":{"tf":1.0},"189":{"tf":1.0}}},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":3,"docs":{"212":{"tf":1.0},"264":{"tf":1.0},"58":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"e":{"df":1,"docs":{"68":{"tf":1.0}},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"x":{"/":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"u":{"df":1,"docs":{"271":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"a":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{":":{":":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"_":{"df":0,"docs":{},"j":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"283":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":1,"docs":{"283":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":3,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"283":{"tf":1.7320508075688772}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":48,"docs":{"0":{"tf":1.4142135623730951},"1":{"tf":1.4142135623730951},"10":{"tf":1.4142135623730951},"109":{"tf":1.0},"118":{"tf":2.0},"119":{"tf":2.23606797749979},"12":{"tf":1.0},"134":{"tf":1.0},"14":{"tf":1.0},"145":{"tf":1.0},"15":{"tf":1.4142135623730951},"167":{"tf":1.0},"18":{"tf":1.7320508075688772},"187":{"tf":1.0},"20":{"tf":1.0},"200":{"tf":1.0},"201":{"tf":1.0},"202":{"tf":1.0},"203":{"tf":1.4142135623730951},"206":{"tf":1.7320508075688772},"213":{"tf":1.0},"215":{"tf":1.0},"217":{"tf":1.0},"218":{"tf":2.23606797749979},"219":{"tf":1.0},"23":{"tf":2.0},"24":{"tf":1.0},"25":{"tf":2.449489742783178},"254":{"tf":3.0},"255":{"tf":1.7320508075688772},"256":{"tf":1.0},"257":{"tf":2.0},"258":{"tf":1.0},"259":{"tf":1.0},"26":{"tf":1.4142135623730951},"260":{"tf":1.0},"268":{"tf":2.23606797749979},"272":{"tf":1.7320508075688772},"274":{"tf":1.0},"283":{"tf":1.7320508075688772},"284":{"tf":1.0},"31":{"tf":1.7320508075688772},"38":{"tf":1.0},"39":{"tf":1.0},"48":{"tf":1.0},"56":{"tf":1.4142135623730951},"64":{"tf":1.7320508075688772},"74":{"tf":1.7320508075688772}}}}}},"s":{"df":0,"docs":{},"t":{"df":30,"docs":{"10":{"tf":1.4142135623730951},"102":{"tf":1.4142135623730951},"104":{"tf":1.4142135623730951},"124":{"tf":1.0},"134":{"tf":1.0},"137":{"tf":1.0},"146":{"tf":1.0},"152":{"tf":1.0},"176":{"tf":1.0},"177":{"tf":1.4142135623730951},"178":{"tf":1.0},"179":{"tf":2.23606797749979},"180":{"tf":1.4142135623730951},"182":{"tf":1.0},"183":{"tf":1.7320508075688772},"184":{"tf":1.4142135623730951},"185":{"tf":1.4142135623730951},"188":{"tf":1.0},"20":{"tf":1.0},"214":{"tf":1.0},"230":{"tf":1.4142135623730951},"245":{"tf":1.0},"25":{"tf":1.0},"280":{"tf":1.0},"283":{"tf":1.0},"6":{"tf":2.0},"62":{"tf":1.0},"81":{"tf":1.0},"88":{"tf":1.0},"89":{"tf":1.0}}}}},"t":{"df":4,"docs":{"102":{"tf":1.0},"138":{"tf":1.0},"226":{"tf":1.0},"229":{"tf":1.0}}},"x":{"df":8,"docs":{"116":{"tf":1.0},"118":{"tf":1.0},"119":{"tf":1.0},"152":{"tf":1.0},"21":{"tf":1.4142135623730951},"282":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0}},"u":{"df":0,"docs":{},"p":{"df":3,"docs":{"145":{"tf":2.0},"146":{"tf":1.0},"147":{"tf":1.0}}}}}},"k":{"df":1,"docs":{"62":{"tf":1.0}}},"l":{"a":{"df":0,"docs":{},"g":{"df":2,"docs":{"175":{"tf":1.4142135623730951},"64":{"tf":1.0}}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":1,"docs":{"48":{"tf":1.0}}}}},"t":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":1,"docs":{"37":{"tf":1.0}}}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"111":{"tf":1.0}}},"r":{"df":1,"docs":{"83":{"tf":1.0}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"200":{"tf":1.0}}}},"x":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"l":{"df":5,"docs":{"126":{"tf":1.0},"178":{"tf":1.0},"182":{"tf":1.0},"184":{"tf":1.0},"261":{"tf":1.0}}}},"df":0,"docs":{}}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":1,"docs":{"201":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"w":{"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"200":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":6,"docs":{"118":{"tf":1.0},"183":{"tf":1.0},"244":{"tf":1.4142135623730951},"25":{"tf":1.0},"27":{"tf":1.4142135623730951},"283":{"tf":1.0}}}}},"m":{"df":0,"docs":{},"l":{"df":1,"docs":{"271":{"tf":1.0}}}},"n":{"df":2,"docs":{"242":{"tf":2.449489742783178},"94":{"tf":1.0}}},"o":{"c":{"df":0,"docs":{},"u":{"df":8,"docs":{"118":{"tf":1.4142135623730951},"230":{"tf":1.0},"255":{"tf":1.0},"257":{"tf":1.0},"32":{"tf":1.7320508075688772},"33":{"tf":1.4142135623730951},"36":{"tf":2.0},"74":{"tf":1.4142135623730951}},"s":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"74":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"u":{"d":{"df":0,"docs":{},"l":{"_":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"74":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"36":{"tf":1.0}}},"df":0,"docs":{}}}}}}}}},"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"34":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"df":1,"docs":{"188":{"tf":1.0}},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"x":{"c":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{".":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":2,"docs":{"33":{"tf":1.0},"34":{"tf":1.4142135623730951}}}}}},"df":1,"docs":{"34":{"tf":1.4142135623730951}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"l":{"df":1,"docs":{"34":{"tf":1.0}}}}}}}},"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"201":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":5,"docs":{"11":{"tf":1.0},"13":{"tf":1.0},"279":{"tf":1.0},"61":{"tf":1.4142135623730951},"62":{"tf":1.0}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":59,"docs":{"108":{"tf":1.0},"109":{"tf":1.4142135623730951},"11":{"tf":1.4142135623730951},"116":{"tf":1.0},"118":{"tf":1.0},"12":{"tf":1.4142135623730951},"122":{"tf":1.0},"123":{"tf":1.4142135623730951},"124":{"tf":1.0},"128":{"tf":1.0},"136":{"tf":1.0},"145":{"tf":1.0},"152":{"tf":1.0},"18":{"tf":1.0},"189":{"tf":1.0},"191":{"tf":1.0},"197":{"tf":1.0},"203":{"tf":1.0},"204":{"tf":1.7320508075688772},"21":{"tf":1.0},"23":{"tf":1.0},"230":{"tf":1.0},"233":{"tf":1.0},"247":{"tf":1.0},"25":{"tf":1.4142135623730951},"251":{"tf":1.0},"252":{"tf":1.0},"253":{"tf":1.4142135623730951},"257":{"tf":1.0},"258":{"tf":1.0},"259":{"tf":1.0},"268":{"tf":2.23606797749979},"27":{"tf":1.4142135623730951},"279":{"tf":1.0},"28":{"tf":1.4142135623730951},"280":{"tf":1.0},"281":{"tf":1.0},"29":{"tf":1.7320508075688772},"30":{"tf":1.0},"31":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.4142135623730951},"34":{"tf":1.7320508075688772},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"43":{"tf":1.0},"45":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.4142135623730951},"67":{"tf":1.0},"69":{"tf":1.4142135623730951},"7":{"tf":1.0},"70":{"tf":1.4142135623730951},"73":{"tf":1.7320508075688772},"74":{"tf":1.0},"93":{"tf":1.4142135623730951}},"u":{"df":0,"docs":{},"p":{"df":1,"docs":{"241":{"tf":1.0}}}}}}}},"o":{"'":{"df":1,"docs":{"58":{"tf":1.0}}},".":{"df":0,"docs":{},"r":{"df":1,"docs":{"117":{"tf":1.0}}}},"b":{"a":{"df":0,"docs":{},"r":{".":{"df":0,"docs":{},"k":{"df":0,"docs":{},"t":{"df":1,"docs":{"100":{"tf":1.0}}}}},"df":1,"docs":{"100":{"tf":1.0}}}},"df":0,"docs":{}},"df":3,"docs":{"117":{"tf":1.0},"58":{"tf":1.4142135623730951},"60":{"tf":1.0}}},"r":{"c":{"df":5,"docs":{"184":{"tf":1.4142135623730951},"237":{"tf":1.0},"25":{"tf":1.7320508075688772},"26":{"tf":1.0},"274":{"tf":1.0}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":1,"docs":{"264":{"tf":1.0}}}}}},"df":5,"docs":{"101":{"tf":1.0},"106":{"tf":1.0},"128":{"tf":1.4142135623730951},"69":{"tf":1.0},"82":{"tf":1.0}}}}}},"k":{"df":4,"docs":{"196":{"tf":1.0},"197":{"tf":1.0},"274":{"tf":1.0},"7":{"tf":1.4142135623730951}}},"m":{"a":{"df":0,"docs":{},"t":{"df":11,"docs":{"136":{"tf":1.0},"137":{"tf":1.0},"138":{"tf":1.0},"163":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"22":{"tf":1.4142135623730951},"252":{"tf":1.4142135623730951},"253":{"tf":1.0},"268":{"tf":1.0},"37":{"tf":1.0}},"t":{"df":1,"docs":{"7":{"tf":1.0}}}}},"df":3,"docs":{"165":{"tf":1.0},"232":{"tf":1.0},"242":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"229":{"tf":1.0}}}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"137":{"tf":1.0}}}}}}},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"225":{"tf":1.0}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"j":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"223":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}},"w":{"a":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"d":{"df":43,"docs":{"100":{"tf":1.0},"109":{"tf":1.0},"117":{"tf":1.4142135623730951},"119":{"tf":1.0},"124":{"tf":1.0},"127":{"tf":1.0},"133":{"tf":1.0},"138":{"tf":1.0},"147":{"tf":1.0},"15":{"tf":1.4142135623730951},"159":{"tf":1.0},"175":{"tf":1.0},"185":{"tf":1.0},"201":{"tf":1.0},"203":{"tf":1.0},"218":{"tf":1.0},"22":{"tf":1.4142135623730951},"224":{"tf":1.0},"242":{"tf":1.0},"250":{"tf":1.0},"253":{"tf":1.0},"258":{"tf":1.0},"259":{"tf":1.0},"263":{"tf":1.0},"264":{"tf":1.0},"271":{"tf":1.0},"273":{"tf":1.0},"277":{"tf":1.0},"282":{"tf":1.0},"283":{"tf":1.0},"286":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"40":{"tf":1.0},"51":{"tf":1.0},"56":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0},"67":{"tf":1.0},"74":{"tf":1.0},"8":{"tf":1.0},"90":{"tf":1.0}}},"df":0,"docs":{}},"r":{"df":1,"docs":{"103":{"tf":1.0}}}}},"r":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":1,"docs":{"131":{"tf":1.0}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"y":{"df":1,"docs":{"131":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}}},"df":1,"docs":{"131":{"tf":1.0}}}}}}},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":8,"docs":{"161":{"tf":1.0},"166":{"tf":1.0},"167":{"tf":1.4142135623730951},"175":{"tf":1.0},"252":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.4142135623730951},"77":{"tf":1.4142135623730951}}}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":11,"docs":{"105":{"tf":1.0},"128":{"tf":1.4142135623730951},"225":{"tf":1.0},"233":{"tf":1.0},"247":{"tf":1.0},"25":{"tf":1.0},"3":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"6":{"tf":1.0},"81":{"tf":1.0}}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"60":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"149":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"11":{"tf":1.0}}}}}}},"i":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"274":{"tf":1.0}}}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"j":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"100":{"tf":1.0}}}}}}}}}},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"166":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"169":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":14,"docs":{"105":{"tf":1.0},"122":{"tf":1.0},"149":{"tf":1.0},"154":{"tf":1.0},"217":{"tf":1.0},"221":{"tf":1.7320508075688772},"222":{"tf":1.4142135623730951},"223":{"tf":1.4142135623730951},"224":{"tf":1.4142135623730951},"252":{"tf":1.0},"268":{"tf":2.0},"31":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"7":{"tf":1.0}},"i":{"df":3,"docs":{"21":{"tf":1.0},"226":{"tf":1.0},"274":{"tf":1.0}}}}},"n":{"c":{"df":1,"docs":{"97":{"tf":1.0}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":39,"docs":{"103":{"tf":2.449489742783178},"104":{"tf":1.0},"105":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"111":{"tf":1.0},"129":{"tf":1.7320508075688772},"140":{"tf":1.0},"142":{"tf":1.4142135623730951},"143":{"tf":1.4142135623730951},"145":{"tf":4.123105625617661},"146":{"tf":1.4142135623730951},"158":{"tf":1.4142135623730951},"199":{"tf":1.0},"200":{"tf":1.0},"221":{"tf":1.0},"222":{"tf":1.0},"232":{"tf":1.0},"234":{"tf":1.4142135623730951},"242":{"tf":1.0},"245":{"tf":2.0},"264":{"tf":1.0},"281":{"tf":2.23606797749979},"283":{"tf":1.0},"42":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"48":{"tf":1.0},"54":{"tf":1.0},"60":{"tf":1.0},"62":{"tf":2.0},"64":{"tf":1.4142135623730951},"77":{"tf":1.4142135623730951},"78":{"tf":2.23606797749979},"79":{"tf":1.4142135623730951},"81":{"tf":1.0},"82":{"tf":1.7320508075688772},"93":{"tf":1.0},"99":{"tf":1.0}},"s":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"283":{"tf":1.0}}}}}}}}}}}}}}}}}}}},"d":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"61":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"100":{"tf":1.0}}},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"174":{"tf":1.0}}}}}}},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":20,"docs":{"107":{"tf":1.0},"126":{"tf":1.0},"140":{"tf":1.0},"152":{"tf":1.0},"167":{"tf":2.0},"190":{"tf":1.0},"196":{"tf":1.0},"198":{"tf":1.4142135623730951},"201":{"tf":1.0},"225":{"tf":1.0},"232":{"tf":1.0},"233":{"tf":1.0},"241":{"tf":1.0},"242":{"tf":1.0},"252":{"tf":1.0},"258":{"tf":1.7320508075688772},"261":{"tf":1.0},"264":{"tf":1.4142135623730951},"268":{"tf":1.0},"38":{"tf":1.0}}}}},"z":{"df":0,"docs":{},"z":{"df":1,"docs":{"51":{"tf":1.0}}}}},"x":{"a":{"_":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"283":{"tf":1.0}},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"c":{"df":1,"docs":{"283":{"tf":1.0}}},"df":0,"docs":{}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":1,"docs":{"283":{"tf":1.0}}}}}}}}}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"221":{"tf":1.0}}}}}}}},"df":13,"docs":{"105":{"tf":1.0},"107":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"226":{"tf":1.4142135623730951},"231":{"tf":1.7320508075688772},"237":{"tf":1.0},"242":{"tf":1.7320508075688772},"254":{"tf":1.0},"256":{"tf":1.4142135623730951},"257":{"tf":1.0},"283":{"tf":1.0},"58":{"tf":1.4142135623730951}},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"s":{"a":{"d":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"283":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"56":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"g":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"101":{"tf":1.0}}}},"r":{"b":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"128":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"e":{"df":1,"docs":{"62":{"tf":1.0}}}}},"c":{"c":{"df":1,"docs":{"37":{"tf":1.0}}},"df":1,"docs":{"115":{"tf":1.4142135623730951}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":1,"docs":{"132":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}}},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":1,"docs":{"192":{"tf":1.0}}}}}}}}},"df":0,"docs":{},"m":{"df":1,"docs":{"14":{"tf":1.0}}},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":2,"docs":{"30":{"tf":1.0},"35":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":59,"docs":{"101":{"tf":1.0},"104":{"tf":1.7320508075688772},"105":{"tf":1.7320508075688772},"106":{"tf":1.0},"107":{"tf":1.4142135623730951},"108":{"tf":1.0},"109":{"tf":1.7320508075688772},"115":{"tf":1.0},"116":{"tf":1.4142135623730951},"120":{"tf":1.0},"122":{"tf":1.0},"123":{"tf":1.7320508075688772},"125":{"tf":1.0},"132":{"tf":1.0},"134":{"tf":1.0},"14":{"tf":1.0},"142":{"tf":1.0},"167":{"tf":1.0},"194":{"tf":1.0},"201":{"tf":1.4142135623730951},"220":{"tf":1.0},"221":{"tf":1.0},"225":{"tf":1.0},"227":{"tf":1.0},"228":{"tf":1.0},"23":{"tf":1.0},"232":{"tf":1.4142135623730951},"242":{"tf":1.7320508075688772},"243":{"tf":1.0},"244":{"tf":1.4142135623730951},"245":{"tf":1.0},"247":{"tf":1.0},"253":{"tf":1.7320508075688772},"254":{"tf":1.4142135623730951},"266":{"tf":1.0},"267":{"tf":1.0},"268":{"tf":1.0},"271":{"tf":1.0},"274":{"tf":1.0},"277":{"tf":1.0},"29":{"tf":1.0},"30":{"tf":1.0},"34":{"tf":1.0},"35":{"tf":1.0},"44":{"tf":1.0},"45":{"tf":1.0},"51":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.7320508075688772},"69":{"tf":1.0},"70":{"tf":1.0},"72":{"tf":2.23606797749979},"73":{"tf":2.23606797749979},"74":{"tf":1.0},"75":{"tf":1.0},"76":{"tf":1.0},"82":{"tf":1.0},"83":{"tf":1.0},"90":{"tf":1.0}}}}},"t":{"_":{"a":{"c":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"283":{"tf":1.0}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"df":1,"docs":{"145":{"tf":1.7320508075688772}}}},"b":{"df":0,"docs":{},"y":{"_":{"b":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"_":{"d":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"145":{"tf":1.7320508075688772}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"145":{"tf":1.7320508075688772}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"283":{"tf":1.0}}}}}}}},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"_":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"c":{"df":1,"docs":{"87":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":12,"docs":{"105":{"tf":1.0},"116":{"tf":1.0},"17":{"tf":1.0},"227":{"tf":1.0},"236":{"tf":1.0},"245":{"tf":1.0},"253":{"tf":1.0},"271":{"tf":1.0},"282":{"tf":1.0},"60":{"tf":1.0},"62":{"tf":1.0},"82":{"tf":1.0}}}},"h":{"df":1,"docs":{"286":{"tf":1.0}}},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"13":{"tf":1.0}}}},"t":{"df":9,"docs":{"11":{"tf":1.7320508075688772},"123":{"tf":1.0},"167":{"tf":2.0},"253":{"tf":1.4142135623730951},"26":{"tf":1.0},"268":{"tf":2.23606797749979},"269":{"tf":1.0},"271":{"tf":1.0},"34":{"tf":1.0}},"h":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"274":{"tf":1.0}}},"df":0,"docs":{}},"u":{"b":{"'":{"df":3,"docs":{"274":{"tf":1.0},"277":{"tf":1.0},"7":{"tf":1.0}}},"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"277":{"tf":1.0}}}}}}}},"df":52,"docs":{"1":{"tf":1.4142135623730951},"100":{"tf":1.0},"109":{"tf":1.0},"117":{"tf":1.0},"119":{"tf":1.4142135623730951},"124":{"tf":1.0},"127":{"tf":1.0},"133":{"tf":1.0},"138":{"tf":1.0},"147":{"tf":1.0},"15":{"tf":1.0},"159":{"tf":1.0},"175":{"tf":1.0},"185":{"tf":1.0},"193":{"tf":1.0},"201":{"tf":1.0},"218":{"tf":1.0},"22":{"tf":1.0},"224":{"tf":1.0},"242":{"tf":1.0},"250":{"tf":1.0},"253":{"tf":1.4142135623730951},"258":{"tf":1.4142135623730951},"259":{"tf":1.0},"263":{"tf":1.0},"264":{"tf":1.0},"268":{"tf":1.0},"272":{"tf":1.4142135623730951},"273":{"tf":1.0},"274":{"tf":2.23606797749979},"276":{"tf":1.7320508075688772},"277":{"tf":1.4142135623730951},"282":{"tf":1.0},"283":{"tf":1.0},"286":{"tf":1.0},"3":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.4142135623730951},"39":{"tf":1.0},"4":{"tf":1.0},"40":{"tf":1.0},"50":{"tf":1.0},"51":{"tf":1.0},"56":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0},"67":{"tf":1.0},"7":{"tf":1.0},"74":{"tf":1.0},"8":{"tf":1.0},"90":{"tf":1.0}}},"df":0,"docs":{}}}},"v":{"df":0,"docs":{},"e":{"df":2,"docs":{"174":{"tf":1.0},"41":{"tf":1.0}},"n":{"df":14,"docs":{"115":{"tf":1.0},"156":{"tf":1.0},"167":{"tf":1.0},"172":{"tf":1.0},"197":{"tf":1.0},"205":{"tf":1.0},"208":{"tf":1.0},"225":{"tf":1.0},"260":{"tf":1.0},"276":{"tf":1.0},"43":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"64":{"tf":1.0}}}}}},"l":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"n":{"df":11,"docs":{"162":{"tf":1.0},"167":{"tf":2.0},"169":{"tf":1.4142135623730951},"170":{"tf":1.4142135623730951},"171":{"tf":1.4142135623730951},"172":{"tf":1.0},"174":{"tf":1.4142135623730951},"200":{"tf":1.0},"219":{"tf":1.0},"253":{"tf":1.0},"259":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"o":{"b":{"a":{"df":0,"docs":{},"l":{"df":3,"docs":{"115":{"tf":1.0},"225":{"tf":1.0},"86":{"tf":1.7320508075688772}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"o":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"103":{"tf":1.0},"23":{"tf":1.0}}}},"df":14,"docs":{"105":{"tf":1.0},"115":{"tf":1.0},"125":{"tf":1.0},"126":{"tf":1.0},"226":{"tf":1.0},"229":{"tf":1.0},"233":{"tf":1.0},"25":{"tf":1.4142135623730951},"26":{"tf":1.0},"37":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0},"73":{"tf":1.0},"81":{"tf":1.7320508075688772}},"e":{"df":5,"docs":{"103":{"tf":1.4142135623730951},"18":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"62":{"tf":1.4142135623730951}}},"n":{"df":0,"docs":{},"e":{"df":1,"docs":{"158":{"tf":1.0}}}},"o":{"d":{"df":29,"docs":{"107":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"132":{"tf":1.0},"150":{"tf":1.0},"154":{"tf":1.7320508075688772},"155":{"tf":2.0},"156":{"tf":1.7320508075688772},"157":{"tf":1.0},"158":{"tf":1.4142135623730951},"159":{"tf":1.4142135623730951},"169":{"tf":1.0},"170":{"tf":1.7320508075688772},"171":{"tf":2.0},"172":{"tf":2.8284271247461903},"174":{"tf":1.0},"182":{"tf":1.7320508075688772},"183":{"tf":1.4142135623730951},"184":{"tf":1.7320508075688772},"19":{"tf":1.0},"194":{"tf":1.0},"212":{"tf":1.0},"213":{"tf":1.4142135623730951},"214":{"tf":1.0},"225":{"tf":1.0},"37":{"tf":1.0},"6":{"tf":2.0},"82":{"tf":1.0},"90":{"tf":1.0}}},"df":0,"docs":{}},"t":{"c":{"df":0,"docs":{},"h":{"a":{"df":1,"docs":{"224":{"tf":1.0}}},"df":0,"docs":{},"y":{"a":{"df":1,"docs":{"228":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":1,"docs":{"3":{"tf":1.0}}}}}}},"p":{"df":0,"docs":{},"g":{"df":1,"docs":{"7":{"tf":1.0}}}},"r":{"a":{"d":{"df":0,"docs":{},"l":{"df":8,"docs":{"22":{"tf":1.4142135623730951},"220":{"tf":1.0},"274":{"tf":1.7320508075688772},"37":{"tf":1.4142135623730951},"40":{"tf":1.0},"49":{"tf":1.0},"65":{"tf":1.0},"70":{"tf":1.4142135623730951}},"e":{"'":{"df":3,"docs":{"22":{"tf":1.0},"221":{"tf":1.0},"222":{"tf":1.0}}},".":{"\"":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{".":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"22":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"(":{"'":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{".":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"22":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"w":{"df":7,"docs":{"108":{"tf":1.4142135623730951},"13":{"tf":1.0},"18":{"tf":1.4142135623730951},"20":{"tf":1.0},"22":{"tf":1.4142135623730951},"45":{"tf":1.0},"70":{"tf":1.4142135623730951}}}}},"u":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"101":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":3,"docs":{"221":{"tf":1.4142135623730951},"58":{"tf":1.4142135623730951},"59":{"tf":1.0}},"i":{"c":{"df":1,"docs":{"126":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":3,"docs":{"282":{"tf":1.0},"52":{"tf":1.0},"75":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"210":{"tf":1.0}}}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"a":{"df":1,"docs":{"237":{"tf":1.0}}},"df":0,"docs":{}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":4,"docs":{"189":{"tf":1.0},"37":{"tf":1.0},"40":{"tf":1.0},"73":{"tf":2.0}}}},"w":{"df":2,"docs":{"199":{"tf":1.0},"208":{"tf":1.0}},"n":{"df":1,"docs":{"64":{"tf":1.0}}}}}},"t":{"df":1,"docs":{"200":{"tf":2.8284271247461903}}},"u":{"a":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":1,"docs":{"201":{"tf":1.0}}}}}},"d":{"df":2,"docs":{"274":{"tf":1.0},"64":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"i":{"d":{"a":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"75":{"tf":1.0}}},"df":0,"docs":{}}},"df":19,"docs":{"101":{"tf":1.0},"102":{"tf":1.4142135623730951},"11":{"tf":1.0},"23":{"tf":1.7320508075688772},"231":{"tf":1.0},"245":{"tf":1.4142135623730951},"248":{"tf":1.7320508075688772},"278":{"tf":1.4142135623730951},"32":{"tf":1.7320508075688772},"37":{"tf":1.0},"41":{"tf":1.0},"62":{"tf":3.3166247903554},"69":{"tf":1.0},"7":{"tf":1.4142135623730951},"75":{"tf":1.7320508075688772},"86":{"tf":1.0},"87":{"tf":2.0},"93":{"tf":1.0},"99":{"tf":1.0}},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":10,"docs":{"128":{"tf":1.0},"274":{"tf":1.4142135623730951},"3":{"tf":1.0},"43":{"tf":1.0},"45":{"tf":1.0},"63":{"tf":1.4142135623730951},"69":{"tf":1.0},"7":{"tf":1.0},"91":{"tf":1.0},"96":{"tf":1.0}}}}}}},"df":1,"docs":{"109":{"tf":1.0}}}},"y":{"df":0,"docs":{},"p":{"df":2,"docs":{"11":{"tf":1.7320508075688772},"282":{"tf":1.4142135623730951}}}}},"h":{"a":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"45":{"tf":1.0}}}}},"i":{"df":2,"docs":{"107":{"tf":1.0},"37":{"tf":1.0}}},"y":{"_":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"_":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"(":{"\"":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"37":{"tf":1.0}}}},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":1,"docs":{"37":{"tf":1.0}}}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"s":{"df":0,"docs":{},"g":{"df":1,"docs":{"37":{"tf":1.0}}}}}},"df":1,"docs":{"37":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{},"n":{"d":{"df":20,"docs":{"101":{"tf":1.0},"103":{"tf":2.8284271247461903},"104":{"tf":2.0},"106":{"tf":1.0},"108":{"tf":2.23606797749979},"109":{"tf":2.23606797749979},"111":{"tf":1.0},"116":{"tf":1.0},"224":{"tf":1.0},"236":{"tf":1.0},"249":{"tf":1.0},"252":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"70":{"tf":1.4142135623730951},"72":{"tf":1.4142135623730951},"73":{"tf":1.4142135623730951},"74":{"tf":1.0},"78":{"tf":1.0},"90":{"tf":1.0}},"l":{"df":16,"docs":{"128":{"tf":1.0},"130":{"tf":1.0},"133":{"tf":1.0},"134":{"tf":1.0},"146":{"tf":1.0},"148":{"tf":1.0},"228":{"tf":1.0},"233":{"tf":1.0},"243":{"tf":1.0},"244":{"tf":1.0},"245":{"tf":1.0},"272":{"tf":1.0},"40":{"tf":1.0},"63":{"tf":1.0},"79":{"tf":1.0},"86":{"tf":1.0}}}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":11,"docs":{"106":{"tf":1.0},"133":{"tf":1.4142135623730951},"154":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0},"225":{"tf":1.0},"230":{"tf":1.0},"242":{"tf":1.0},"26":{"tf":1.0},"273":{"tf":1.0},"283":{"tf":1.0}}}},"i":{"df":2,"docs":{"124":{"tf":1.0},"263":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"170":{"tf":1.0}}}}}}},"r":{"d":{"df":7,"docs":{"104":{"tf":1.0},"158":{"tf":1.0},"189":{"tf":1.0},"200":{"tf":1.0},"213":{"tf":1.0},"233":{"tf":1.0},"241":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"166":{"tf":1.0}}}},"w":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"282":{"tf":1.0}}}},"df":0,"docs":{}}},"df":2,"docs":{"48":{"tf":1.0},"60":{"tf":1.4142135623730951}},"m":{"df":2,"docs":{"201":{"tf":1.0},"225":{"tf":1.0}}}},"s":{"df":0,"docs":{},"h":{"df":3,"docs":{"268":{"tf":1.0},"274":{"tf":1.0},"64":{"tf":1.0}},"m":{"a":{"df":0,"docs":{},"p":{"<":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"242":{"tf":1.0}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"n":{"'":{"df":0,"docs":{},"t":{"df":2,"docs":{"154":{"tf":1.0},"155":{"tf":1.0}}}},"df":0,"docs":{},"’":{"df":0,"docs":{},"t":{"df":1,"docs":{"194":{"tf":1.0}}}}}},"v":{"df":0,"docs":{},"e":{"df":12,"docs":{"105":{"tf":1.0},"126":{"tf":1.0},"146":{"tf":1.0},"166":{"tf":1.0},"174":{"tf":1.0},"183":{"tf":1.0},"215":{"tf":1.0},"256":{"tf":1.0},"46":{"tf":1.0},"60":{"tf":1.0},"82":{"tf":1.0},"86":{"tf":1.0}},"n":{"'":{"df":0,"docs":{},"t":{"df":4,"docs":{"20":{"tf":1.0},"274":{"tf":1.0},"36":{"tf":1.0},"60":{"tf":1.0}}}},"df":0,"docs":{},"’":{"df":0,"docs":{},"t":{"df":1,"docs":{"213":{"tf":1.0}}}}}}}},"df":0,"docs":{},"e":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":9,"docs":{"109":{"tf":1.0},"251":{"tf":1.0},"252":{"tf":1.4142135623730951},"268":{"tf":1.0},"269":{"tf":1.0},"274":{"tf":1.0},"28":{"tf":1.0},"33":{"tf":1.0},"73":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"242":{"tf":1.0}}}}},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"105":{"tf":1.0}}}}},"y":{"df":0,"docs":{},"w":{"df":0,"docs":{},"e":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":1,"docs":{"59":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"p":{"df":17,"docs":{"106":{"tf":1.0},"115":{"tf":1.0},"122":{"tf":1.0},"165":{"tf":1.0},"190":{"tf":1.0},"222":{"tf":1.0},"232":{"tf":1.0},"264":{"tf":1.0},"274":{"tf":1.4142135623730951},"278":{"tf":1.0},"282":{"tf":1.0},"3":{"tf":1.0},"37":{"tf":1.0},"59":{"tf":1.0},"62":{"tf":1.0},"75":{"tf":1.0},"90":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"222":{"tf":1.0},"43":{"tf":1.0}}}}}},"n":{"c":{"df":2,"docs":{"200":{"tf":1.0},"278":{"tf":1.0}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"e":{"'":{"df":1,"docs":{"37":{"tf":1.0}}},"df":30,"docs":{"103":{"tf":2.449489742783178},"108":{"tf":1.0},"109":{"tf":1.0},"117":{"tf":1.0},"123":{"tf":1.0},"171":{"tf":1.0},"20":{"tf":1.0},"200":{"tf":2.0},"201":{"tf":1.0},"210":{"tf":1.0},"225":{"tf":1.0},"229":{"tf":1.4142135623730951},"242":{"tf":1.0},"243":{"tf":1.0},"249":{"tf":1.0},"258":{"tf":1.0},"264":{"tf":1.0},"266":{"tf":1.4142135623730951},"274":{"tf":1.7320508075688772},"37":{"tf":1.0},"40":{"tf":1.0},"60":{"tf":1.4142135623730951},"69":{"tf":1.0},"70":{"tf":1.7320508075688772},"72":{"tf":1.4142135623730951},"76":{"tf":1.0},"81":{"tf":1.0},"82":{"tf":1.0},"84":{"tf":1.0},"90":{"tf":1.0}}}}},"i":{"d":{"df":0,"docs":{},"e":{"df":2,"docs":{"233":{"tf":1.0},"245":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":1,"docs":{"83":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"g":{"df":0,"docs":{},"h":{"df":26,"docs":{"103":{"tf":1.4142135623730951},"200":{"tf":1.0},"201":{"tf":1.0},"205":{"tf":1.0},"214":{"tf":1.0},"219":{"tf":1.7320508075688772},"22":{"tf":1.0},"225":{"tf":2.23606797749979},"226":{"tf":2.23606797749979},"229":{"tf":2.0},"23":{"tf":1.0},"230":{"tf":1.0},"232":{"tf":1.0},"233":{"tf":1.0},"236":{"tf":2.0},"237":{"tf":1.0},"239":{"tf":1.0},"243":{"tf":1.0},"244":{"tf":1.0},"245":{"tf":1.0},"251":{"tf":1.4142135623730951},"254":{"tf":1.7320508075688772},"256":{"tf":1.0},"281":{"tf":1.0},"41":{"tf":1.0},"64":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"103":{"tf":1.4142135623730951},"209":{"tf":1.0}}}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":2,"docs":{"109":{"tf":1.0},"251":{"tf":1.0}}}}}}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"174":{"tf":1.0}},"i":{"df":22,"docs":{"134":{"tf":1.0},"202":{"tf":1.0},"203":{"tf":1.4142135623730951},"204":{"tf":1.4142135623730951},"205":{"tf":2.8284271247461903},"206":{"tf":2.449489742783178},"207":{"tf":1.0},"208":{"tf":2.449489742783178},"209":{"tf":2.23606797749979},"210":{"tf":1.7320508075688772},"212":{"tf":2.0},"213":{"tf":2.0},"215":{"tf":1.4142135623730951},"217":{"tf":1.0},"218":{"tf":2.0},"232":{"tf":2.0},"233":{"tf":2.0},"242":{"tf":2.23606797749979},"257":{"tf":1.0},"264":{"tf":1.0},"283":{"tf":1.0},"83":{"tf":1.0}}}}}}},"t":{"df":3,"docs":{"189":{"tf":1.0},"201":{"tf":1.4142135623730951},"60":{"tf":1.0}}}},"o":{"c":{"df":1,"docs":{"59":{"tf":1.0}}},"df":0,"docs":{},"l":{"d":{"df":3,"docs":{"115":{"tf":1.0},"132":{"tf":1.0},"245":{"tf":1.0}}},"df":0,"docs":{}},"m":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":1,"docs":{"11":{"tf":1.7320508075688772}}}}}},"df":1,"docs":{"64":{"tf":1.0}}}},"o":{"df":0,"docs":{},"k":{"df":1,"docs":{"119":{"tf":1.0}}}},"p":{"df":0,"docs":{},"e":{"df":1,"docs":{"225":{"tf":1.4142135623730951}},"f":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"242":{"tf":1.0}}}}}}}}},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"190":{"tf":1.0}}}},"u":{"df":0,"docs":{},"r":{"df":2,"docs":{"10":{"tf":1.0},"64":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"=":{"\"":{".":{".":{"/":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"c":{".":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"/":{"d":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"283":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":1,"docs":{"283":{"tf":1.0}}}}}}},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"283":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":1,"docs":{"284":{"tf":1.0}}}},"t":{"df":0,"docs":{},"p":{"df":4,"docs":{"189":{"tf":1.0},"190":{"tf":1.4142135623730951},"201":{"tf":1.4142135623730951},"274":{"tf":1.0}},"s":{":":{"/":{"/":{"a":{"d":{"df":0,"docs":{},"r":{".":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"u":{"b":{".":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"/":{"df":0,"docs":{},"m":{"a":{"d":{"df":0,"docs":{},"r":{"df":1,"docs":{"134":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":1,"docs":{"134":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{".":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"i":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"/":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"/":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"u":{"b":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"/":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"277":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"a":{".":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"/":{"df":0,"docs":{},"e":{"df":0,"docs":{},"z":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"11":{"tf":1.0}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{".":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"e":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"/":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"l":{"/":{"df":0,"docs":{},"g":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{".":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"11":{"tf":1.0}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}}}},"df":0,"docs":{}}}}}}}}},"d":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"/":{"d":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"/":{"d":{"/":{"1":{"df":0,"docs":{},"q":{"df":0,"docs":{},"w":{"3":{"6":{"_":{"7":{"df":0,"docs":{},"g":{"6":{"df":0,"docs":{},"x":{"df":0,"docs":{},"y":{"df":0,"docs":{},"h":{"df":0,"docs":{},"v":{"df":0,"docs":{},"j":{"df":0,"docs":{},"z":{"d":{"df":0,"docs":{},"m":{"df":1,"docs":{"177":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"x":{"1":{"df":0,"docs":{},"h":{"c":{"3":{"df":0,"docs":{},"t":{"5":{"df":0,"docs":{},"z":{"c":{"7":{"df":1,"docs":{"185":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"x":{"df":3,"docs":{"119":{"tf":1.0},"266":{"tf":1.4142135623730951},"271":{"tf":1.0}}}}}}}}},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"u":{"b":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"/":{"df":0,"docs":{},"j":{"a":{"df":0,"docs":{},"k":{"df":0,"docs":{},"u":{"b":{"a":{"d":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"w":{"/":{"a":{"df":0,"docs":{},"r":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"51":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"/":{"a":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"_":{"d":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"137":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}}}},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"/":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":2,"docs":{"11":{"tf":1.0},"186":{"tf":1.0}}}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":5,"docs":{"119":{"tf":1.0},"253":{"tf":1.0},"31":{"tf":1.0},"34":{"tf":1.0},"36":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"l":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"i":{"d":{".":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"/":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"i":{"d":{"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"/":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"/":{"a":{"c":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"/":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"v":{"_":{"a":{"b":{"7":{"2":{"df":0,"docs":{},"e":{"2":{"1":{"8":{"df":1,"docs":{"258":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"m":{"a":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{".":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{".":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"/":{"?":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"=":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"2":{"/":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"/":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"271":{"tf":1.0}},"e":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"271":{"tf":1.0}}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":1,"docs":{"271":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"/":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"/":{"b":{"df":0,"docs":{},"o":{"a":{"df":0,"docs":{},"r":{"d":{"/":{"df":0,"docs":{},"o":{"9":{"df":0,"docs":{},"j":{"_":{"df":0,"docs":{},"l":{"df":0,"docs":{},"w":{"df":0,"docs":{},"x":{"3":{"df":0,"docs":{},"j":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":1,"docs":{"274":{"tf":1.0}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{".":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"d":{".":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"/":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"s":{"/":{"1":{"0":{"7":{"8":{"df":1,"docs":{"215":{"tf":1.0}}},"df":0,"docs":{}},"8":{"1":{"df":1,"docs":{"216":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"/":{"2":{".":{"0":{"df":1,"docs":{"2":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"df":1,"docs":{"176":{"tf":1.0}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"u":{"b":{".":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"n":{".":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"/":{"b":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"/":{"df":0,"docs":{},"s":{"d":{"df":0,"docs":{},"k":{"df":1,"docs":{"176":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"x":{"df":0,"docs":{},"h":{"4":{"df":0,"docs":{},"n":{"df":0,"docs":{},"q":{"df":0,"docs":{},"y":{"df":0,"docs":{},"z":{"df":0,"docs":{},"y":{"c":{"df":0,"docs":{},"s":{"df":0,"docs":{},"y":{"a":{"df":0,"docs":{},"j":{"df":0,"docs":{},"g":{"0":{"df":0,"docs":{},"l":{"5":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"3":{"3":{"df":0,"docs":{},"y":{"d":{"5":{"df":0,"docs":{},"m":{"/":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"177":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"69":{"tf":1.0}}}}}}}},"i":{"'":{"df":0,"docs":{},"m":{"df":1,"docs":{"200":{"tf":1.0}}}},".":{"df":1,"docs":{"253":{"tf":1.0}}},"/":{"df":0,"docs":{},"o":{"df":2,"docs":{"115":{"tf":1.0},"179":{"tf":1.4142135623730951}}}},"d":{"df":7,"docs":{"231":{"tf":2.449489742783178},"242":{"tf":1.4142135623730951},"271":{"tf":1.0},"62":{"tf":1.0},"80":{"tf":1.0},"86":{"tf":2.6457513110645907},"87":{"tf":1.0}},"e":{"a":{"df":2,"docs":{"51":{"tf":1.0},"87":{"tf":1.0}},"l":{"df":7,"docs":{"119":{"tf":1.0},"122":{"tf":1.0},"206":{"tf":1.0},"208":{"tf":1.0},"229":{"tf":1.0},"49":{"tf":1.0},"58":{"tf":1.0}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":3,"docs":{"178":{"tf":1.0},"182":{"tf":1.0},"184":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{",":{"a":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"180":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"n":{"df":0,"docs":{},"t":{"df":4,"docs":{"18":{"tf":1.4142135623730951},"249":{"tf":1.0},"283":{"tf":1.0},"64":{"tf":1.0}},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":15,"docs":{"104":{"tf":1.0},"141":{"tf":1.0},"149":{"tf":1.4142135623730951},"151":{"tf":1.4142135623730951},"152":{"tf":1.0},"187":{"tf":1.7320508075688772},"188":{"tf":1.0},"191":{"tf":1.0},"200":{"tf":2.23606797749979},"214":{"tf":1.0},"22":{"tf":1.0},"225":{"tf":1.7320508075688772},"232":{"tf":1.0},"241":{"tf":1.4142135623730951},"86":{"tf":1.0}}}}}}}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"t":{"df":2,"docs":{"103":{"tf":1.4142135623730951},"43":{"tf":1.0}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{",":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"75":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":13,"docs":{"189":{"tf":1.0},"193":{"tf":1.0},"194":{"tf":1.0},"200":{"tf":1.7320508075688772},"201":{"tf":1.7320508075688772},"232":{"tf":1.0},"233":{"tf":1.7320508075688772},"237":{"tf":1.0},"241":{"tf":1.0},"242":{"tf":1.4142135623730951},"245":{"tf":1.0},"87":{"tf":1.0},"88":{"tf":1.0}}},"f":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":1,"docs":{"37":{"tf":1.0}}}}},"df":0,"docs":{}},"g":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":5,"docs":{"200":{"tf":1.0},"226":{"tf":1.4142135623730951},"233":{"tf":1.0},"241":{"tf":1.0},"61":{"tf":1.0}}}}}},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"132":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"m":{"a":{"df":0,"docs":{},"g":{"df":2,"docs":{"194":{"tf":1.0},"274":{"tf":1.0}},"i":{"df":0,"docs":{},"n":{"df":2,"docs":{"225":{"tf":1.0},"232":{"tf":1.0}}}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"i":{"df":2,"docs":{"189":{"tf":1.0},"81":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"p":{"a":{"c":{"df":0,"docs":{},"t":{"df":4,"docs":{"167":{"tf":1.0},"178":{"tf":1.0},"200":{"tf":1.0},"229":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"df":2,"docs":{"242":{"tf":1.7320508075688772},"62":{"tf":1.0}},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":43,"docs":{"103":{"tf":2.0},"105":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"115":{"tf":1.0},"125":{"tf":1.0},"128":{"tf":1.0},"140":{"tf":1.0},"141":{"tf":1.0},"146":{"tf":1.0},"152":{"tf":1.4142135623730951},"159":{"tf":1.0},"167":{"tf":1.0},"200":{"tf":1.7320508075688772},"203":{"tf":1.7320508075688772},"208":{"tf":1.0},"218":{"tf":1.0},"225":{"tf":2.0},"226":{"tf":2.6457513110645907},"227":{"tf":2.0},"228":{"tf":1.4142135623730951},"230":{"tf":2.0},"231":{"tf":1.4142135623730951},"232":{"tf":1.4142135623730951},"233":{"tf":1.0},"234":{"tf":1.0},"239":{"tf":1.0},"241":{"tf":2.0},"244":{"tf":1.0},"247":{"tf":1.0},"254":{"tf":1.0},"256":{"tf":1.7320508075688772},"257":{"tf":1.7320508075688772},"258":{"tf":2.23606797749979},"264":{"tf":1.0},"274":{"tf":1.0},"282":{"tf":1.0},"37":{"tf":1.4142135623730951},"43":{"tf":1.0},"45":{"tf":1.0},"48":{"tf":1.0},"82":{"tf":1.0},"84":{"tf":1.4142135623730951}}}}}}},"i":{"c":{"df":3,"docs":{"197":{"tf":1.0},"205":{"tf":1.4142135623730951},"263":{"tf":1.0}},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"138":{"tf":1.0}}}}},"df":2,"docs":{"123":{"tf":1.4142135623730951},"245":{"tf":1.0}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"m":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"145":{"tf":2.0}}}}}}}}}},"df":30,"docs":{"103":{"tf":1.4142135623730951},"105":{"tf":1.7320508075688772},"138":{"tf":1.0},"158":{"tf":1.0},"16":{"tf":1.0},"165":{"tf":1.0},"166":{"tf":1.4142135623730951},"167":{"tf":1.0},"20":{"tf":1.0},"205":{"tf":1.0},"206":{"tf":1.0},"210":{"tf":1.0},"213":{"tf":1.0},"214":{"tf":1.0},"218":{"tf":1.0},"242":{"tf":1.0},"251":{"tf":1.0},"253":{"tf":1.0},"258":{"tf":1.0},"260":{"tf":1.0},"27":{"tf":1.0},"37":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"49":{"tf":1.0},"6":{"tf":1.4142135623730951},"60":{"tf":1.0},"73":{"tf":1.7320508075688772},"87":{"tf":1.4142135623730951},"9":{"tf":1.0}}}},"s":{"df":2,"docs":{"206":{"tf":1.0},"210":{"tf":1.0}},"s":{"df":2,"docs":{"201":{"tf":1.0},"43":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":10,"docs":{"111":{"tf":1.0},"145":{"tf":1.0},"146":{"tf":1.0},"150":{"tf":1.0},"171":{"tf":1.4142135623730951},"206":{"tf":1.0},"208":{"tf":1.0},"264":{"tf":1.0},"282":{"tf":1.0},"51":{"tf":1.0}}}}}}},"n":{"c":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":1,"docs":{"137":{"tf":1.0}}}}},"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"205":{"tf":1.7320508075688772},"218":{"tf":1.0}}},"df":0,"docs":{}},"l":{"df":0,"docs":{},"u":{"d":{"df":39,"docs":{"105":{"tf":1.0},"113":{"tf":1.0},"118":{"tf":1.0},"124":{"tf":1.0},"167":{"tf":2.449489742783178},"169":{"tf":1.0},"172":{"tf":1.0},"18":{"tf":1.0},"190":{"tf":1.0},"193":{"tf":1.0},"200":{"tf":1.7320508075688772},"204":{"tf":1.0},"222":{"tf":1.0},"224":{"tf":1.0},"226":{"tf":1.4142135623730951},"23":{"tf":1.4142135623730951},"231":{"tf":1.0},"251":{"tf":1.7320508075688772},"253":{"tf":1.4142135623730951},"274":{"tf":1.4142135623730951},"283":{"tf":1.0},"29":{"tf":1.4142135623730951},"31":{"tf":2.23606797749979},"32":{"tf":1.4142135623730951},"34":{"tf":1.4142135623730951},"36":{"tf":2.0},"37":{"tf":1.0},"4":{"tf":1.0},"58":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.0},"69":{"tf":1.4142135623730951},"7":{"tf":2.449489742783178},"70":{"tf":1.4142135623730951},"72":{"tf":1.0},"73":{"tf":1.7320508075688772},"74":{"tf":1.0},"78":{"tf":1.0},"99":{"tf":1.0}},"e":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"81":{"tf":1.0}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"m":{"df":5,"docs":{"246":{"tf":1.0},"248":{"tf":1.0},"249":{"tf":1.4142135623730951},"283":{"tf":1.0},"88":{"tf":1.0}},"p":{"a":{"df":0,"docs":{},"t":{"df":5,"docs":{"169":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"268":{"tf":1.0}}}},"df":0,"docs":{}}},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"170":{"tf":1.0}}}}}}}},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":3,"docs":{"205":{"tf":1.0},"208":{"tf":1.0},"41":{"tf":1.0}}}},"d":{"df":1,"docs":{"22":{"tf":1.0}},"u":{"df":0,"docs":{},"l":{"df":1,"docs":{"60":{"tf":1.0}}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"190":{"tf":1.0},"250":{"tf":1.0}}}}}}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"166":{"tf":1.0},"170":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"201":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"x":{"df":5,"docs":{"268":{"tf":1.0},"269":{"tf":1.0},"271":{"tf":1.0},"40":{"tf":1.0},"62":{"tf":1.0}}}},"i":{"c":{"df":7,"docs":{"154":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0},"253":{"tf":1.0},"268":{"tf":1.0},"40":{"tf":1.0},"7":{"tf":1.0}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"200":{"tf":1.0}}}}}},"df":0,"docs":{}}},"v":{"df":0,"docs":{},"i":{"d":{"df":0,"docs":{},"u":{"df":9,"docs":{"124":{"tf":1.0},"187":{"tf":1.0},"221":{"tf":1.4142135623730951},"225":{"tf":1.0},"228":{"tf":1.0},"244":{"tf":1.4142135623730951},"245":{"tf":1.4142135623730951},"274":{"tf":1.0},"283":{"tf":1.0}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"196":{"tf":1.0},"263":{"tf":1.0}}}}}},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"82":{"tf":1.0}}}}}},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"274":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}},"o":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"252":{"tf":1.7320508075688772}}}}}}}},"/":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"226":{"tf":1.4142135623730951},"229":{"tf":1.0},"241":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":1,"docs":{"242":{"tf":1.0}},"r":{"df":0,"docs":{},"m":{"df":17,"docs":{"13":{"tf":1.0},"134":{"tf":1.4142135623730951},"177":{"tf":1.0},"226":{"tf":1.0},"23":{"tf":1.0},"241":{"tf":1.0},"242":{"tf":1.0},"263":{"tf":1.4142135623730951},"268":{"tf":1.0},"274":{"tf":1.0},"283":{"tf":1.0},"32":{"tf":1.0},"36":{"tf":1.0},"4":{"tf":1.0},"7":{"tf":1.0},"73":{"tf":1.0},"75":{"tf":1.0}}}}},"r":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":2,"docs":{"167":{"tf":1.0},"48":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"233":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"t":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"242":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"11":{"tf":1.0}},"i":{"a":{"df":0,"docs":{},"l":{"_":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"j":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"179":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}}}}}}}}},"df":0,"docs":{}}},"df":16,"docs":{"10":{"tf":1.0},"132":{"tf":1.0},"164":{"tf":1.0},"167":{"tf":1.7320508075688772},"172":{"tf":1.0},"174":{"tf":1.0},"184":{"tf":1.0},"189":{"tf":1.0},"190":{"tf":1.4142135623730951},"199":{"tf":1.0},"200":{"tf":1.4142135623730951},"201":{"tf":1.4142135623730951},"231":{"tf":1.0},"238":{"tf":1.0},"242":{"tf":1.4142135623730951},"244":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":3,"docs":{"214":{"tf":1.0},"233":{"tf":1.0},"254":{"tf":1.0}}}}},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"104":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":2,"docs":{"59":{"tf":1.0},"62":{"tf":1.0}}}}},"i":{"d":{"df":7,"docs":{"12":{"tf":1.0},"20":{"tf":2.23606797749979},"263":{"tf":1.0},"30":{"tf":1.0},"34":{"tf":1.0},"35":{"tf":1.0},"61":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"108":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"245":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"a":{"df":0,"docs":{},"l":{"df":14,"docs":{"104":{"tf":1.0},"11":{"tf":6.48074069840786},"12":{"tf":2.0},"13":{"tf":1.7320508075688772},"14":{"tf":1.4142135623730951},"16":{"tf":1.0},"18":{"tf":1.0},"201":{"tf":1.4142135623730951},"269":{"tf":1.4142135623730951},"279":{"tf":1.0},"286":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":2.0},"53":{"tf":1.4142135623730951}}},"n":{"c":{"df":6,"docs":{"244":{"tf":1.0},"245":{"tf":1.4142135623730951},"246":{"tf":1.0},"247":{"tf":1.0},"249":{"tf":1.0},"78":{"tf":1.0}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":2,"docs":{"115":{"tf":1.0},"245":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"a":{"d":{"df":14,"docs":{"158":{"tf":1.0},"172":{"tf":1.0},"18":{"tf":1.0},"188":{"tf":1.0},"194":{"tf":1.4142135623730951},"231":{"tf":1.0},"242":{"tf":1.0},"37":{"tf":1.0},"58":{"tf":1.0},"61":{"tf":1.4142135623730951},"64":{"tf":1.0},"78":{"tf":1.0},"79":{"tf":1.0},"84":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":10,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"11":{"tf":2.0},"12":{"tf":1.4142135623730951},"21":{"tf":1.0},"281":{"tf":1.0},"32":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"5":{"tf":1.0}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"62":{"tf":1.4142135623730951}}}}}}}}}},"t":{"a":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"105":{"tf":1.0},"171":{"tf":1.0}}}},"df":0,"docs":{}},"df":1,"docs":{"116":{"tf":1.0}},"e":{"df":0,"docs":{},"g":{"df":1,"docs":{"250":{"tf":1.0}},"r":{"df":17,"docs":{"0":{"tf":1.0},"201":{"tf":1.0},"223":{"tf":1.0},"225":{"tf":1.0},"226":{"tf":1.0},"228":{"tf":1.0},"257":{"tf":1.0},"259":{"tf":1.0},"263":{"tf":1.0},"274":{"tf":2.449489742783178},"283":{"tf":1.4142135623730951},"47":{"tf":1.0},"51":{"tf":1.4142135623730951},"52":{"tf":1.0},"60":{"tf":2.449489742783178},"61":{"tf":1.0},"7":{"tf":1.0}}}},"l":{"df":2,"docs":{"17":{"tf":1.0},"280":{"tf":1.0}}},"n":{"d":{"df":5,"docs":{"104":{"tf":1.4142135623730951},"107":{"tf":1.0},"274":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":1,"docs":{"64":{"tf":1.0}}},"t":{"df":1,"docs":{"227":{"tf":1.0}}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":6,"docs":{"140":{"tf":1.0},"205":{"tf":1.0},"225":{"tf":1.0},"226":{"tf":1.7320508075688772},"232":{"tf":1.4142135623730951},"254":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":6,"docs":{"104":{"tf":1.0},"231":{"tf":1.0},"263":{"tf":1.0},"37":{"tf":1.0},"41":{"tf":1.0},"58":{"tf":1.0}}}}},"f":{"a":{"c":{"df":11,"docs":{"103":{"tf":1.7320508075688772},"104":{"tf":2.0},"105":{"tf":1.7320508075688772},"106":{"tf":1.4142135623730951},"111":{"tf":1.0},"128":{"tf":1.0},"167":{"tf":1.0},"184":{"tf":1.0},"246":{"tf":1.0},"37":{"tf":1.0},"69":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"m":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"i":{"df":1,"docs":{"268":{"tf":1.0}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"l":{"df":1,"docs":{"62":{"tf":1.0}}}}}}},"n":{"df":6,"docs":{"105":{"tf":1.0},"106":{"tf":1.0},"146":{"tf":1.0},"175":{"tf":1.0},"193":{"tf":1.0},"283":{"tf":1.7320508075688772}}},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"220":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":2,"docs":{"124":{"tf":1.0},"242":{"tf":1.4142135623730951}}}}}}}},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"c":{"df":14,"docs":{"105":{"tf":1.0},"158":{"tf":1.0},"172":{"tf":1.0},"180":{"tf":1.0},"207":{"tf":1.4142135623730951},"208":{"tf":1.4142135623730951},"213":{"tf":1.0},"228":{"tf":1.0},"23":{"tf":1.0},"251":{"tf":1.0},"32":{"tf":1.0},"64":{"tf":1.0},"69":{"tf":1.0},"73":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":1,"docs":{"221":{"tf":1.0}}}},"s":{"df":0,"docs":{},"t":{"df":4,"docs":{"164":{"tf":1.0},"170":{"tf":1.0},"183":{"tf":1.0},"49":{"tf":1.0}},"i":{"df":0,"docs":{},"g":{"df":2,"docs":{"37":{"tf":1.0},"64":{"tf":1.0}}}}}}},"o":{"c":{"df":3,"docs":{"115":{"tf":1.0},"279":{"tf":1.0},"58":{"tf":1.0}}},"df":0,"docs":{},"k":{"df":2,"docs":{"105":{"tf":1.0},"283":{"tf":1.4142135623730951}}},"l":{"df":0,"docs":{},"v":{"df":12,"docs":{"116":{"tf":1.0},"167":{"tf":1.0},"172":{"tf":1.0},"20":{"tf":1.0},"231":{"tf":1.0},"242":{"tf":1.0},"244":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"49":{"tf":1.0},"62":{"tf":1.0},"68":{"tf":1.0}}}}}}},"o":{"df":62,"docs":{"10":{"tf":1.0},"103":{"tf":1.4142135623730951},"109":{"tf":1.4142135623730951},"11":{"tf":1.0},"115":{"tf":1.0},"118":{"tf":2.0},"119":{"tf":1.7320508075688772},"127":{"tf":1.0},"134":{"tf":1.0},"14":{"tf":1.7320508075688772},"145":{"tf":1.0},"15":{"tf":1.4142135623730951},"161":{"tf":1.7320508075688772},"162":{"tf":1.4142135623730951},"164":{"tf":1.0},"165":{"tf":1.0},"167":{"tf":1.4142135623730951},"169":{"tf":1.0},"170":{"tf":1.7320508075688772},"171":{"tf":1.4142135623730951},"172":{"tf":1.4142135623730951},"175":{"tf":1.0},"185":{"tf":1.0},"192":{"tf":1.0},"199":{"tf":1.0},"200":{"tf":1.4142135623730951},"202":{"tf":1.0},"203":{"tf":1.0},"213":{"tf":1.0},"215":{"tf":1.0},"218":{"tf":1.4142135623730951},"219":{"tf":1.0},"220":{"tf":1.0},"225":{"tf":1.7320508075688772},"228":{"tf":1.4142135623730951},"229":{"tf":1.0},"23":{"tf":2.0},"24":{"tf":1.4142135623730951},"241":{"tf":1.0},"25":{"tf":2.449489742783178},"251":{"tf":1.0},"252":{"tf":1.0},"255":{"tf":1.0},"257":{"tf":1.7320508075688772},"268":{"tf":1.4142135623730951},"271":{"tf":1.4142135623730951},"272":{"tf":1.4142135623730951},"274":{"tf":1.4142135623730951},"278":{"tf":1.4142135623730951},"28":{"tf":1.0},"280":{"tf":1.0},"31":{"tf":1.7320508075688772},"32":{"tf":1.4142135623730951},"33":{"tf":1.0},"38":{"tf":1.0},"56":{"tf":1.4142135623730951},"69":{"tf":1.0},"72":{"tf":1.4142135623730951},"73":{"tf":1.4142135623730951},"74":{"tf":1.0},"9":{"tf":1.0},"90":{"tf":1.4142135623730951}},"s":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"279":{"tf":1.0}}}}},"/":{"<":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"109":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"_":{"c":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"72":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"x":{"c":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"p":{"b":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"df":1,"docs":{"26":{"tf":1.4142135623730951}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"109":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":1,"docs":{"66":{"tf":1.0}}}}}}}},"df":0,"docs":{},"’":{"df":1,"docs":{"218":{"tf":1.0}}}},"w":{"df":1,"docs":{"89":{"tf":1.0}}}},"s":{"_":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"o":{"df":1,"docs":{"282":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"n":{"'":{"df":0,"docs":{},"t":{"df":13,"docs":{"11":{"tf":1.0},"12":{"tf":1.0},"169":{"tf":1.0},"220":{"tf":1.0},"225":{"tf":1.0},"231":{"tf":1.0},"233":{"tf":1.0},"236":{"tf":1.0},"58":{"tf":1.0},"6":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0},"82":{"tf":1.0}}}},"df":0,"docs":{},"’":{"df":0,"docs":{},"t":{"df":3,"docs":{"115":{"tf":1.0},"197":{"tf":1.0},"205":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"l":{"df":3,"docs":{"201":{"tf":1.0},"229":{"tf":1.4142135623730951},"274":{"tf":1.0}}}},"s":{"df":0,"docs":{},"u":{"df":21,"docs":{"1":{"tf":2.0},"104":{"tf":1.0},"111":{"tf":1.0},"115":{"tf":1.0},"116":{"tf":1.0},"152":{"tf":1.0},"165":{"tf":1.0},"169":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"197":{"tf":1.0},"205":{"tf":1.4142135623730951},"209":{"tf":1.0},"225":{"tf":1.0},"228":{"tf":1.0},"237":{"tf":1.0},"242":{"tf":1.4142135623730951},"3":{"tf":1.0},"4":{"tf":1.4142135623730951},"6":{"tf":3.4641016151377544}}}}},"t":{"'":{"df":54,"docs":{"104":{"tf":1.0},"105":{"tf":1.0},"107":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"11":{"tf":1.7320508075688772},"115":{"tf":1.0},"116":{"tf":1.0},"132":{"tf":1.0},"154":{"tf":1.0},"155":{"tf":1.0},"158":{"tf":1.0},"159":{"tf":1.0},"16":{"tf":1.4142135623730951},"167":{"tf":1.0},"169":{"tf":1.4142135623730951},"170":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"175":{"tf":1.0},"20":{"tf":1.0},"200":{"tf":1.0},"201":{"tf":1.0},"208":{"tf":1.0},"221":{"tf":1.0},"224":{"tf":1.0},"225":{"tf":1.4142135623730951},"227":{"tf":1.0},"232":{"tf":1.0},"242":{"tf":1.7320508075688772},"245":{"tf":1.7320508075688772},"249":{"tf":1.4142135623730951},"25":{"tf":1.0},"252":{"tf":1.0},"258":{"tf":1.0},"263":{"tf":1.0},"266":{"tf":1.0},"268":{"tf":1.0},"27":{"tf":1.4142135623730951},"274":{"tf":1.0},"43":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"48":{"tf":1.0},"49":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.4142135623730951},"7":{"tf":1.0},"75":{"tf":1.0},"78":{"tf":1.0},"81":{"tf":1.0},"84":{"tf":1.0},"9":{"tf":1.0}},"l":{"df":0,"docs":{},"l":{"df":1,"docs":{"82":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":7,"docs":{"165":{"tf":1.0},"172":{"tf":1.0},"189":{"tf":1.0},"232":{"tf":1.0},"59":{"tf":1.0},"73":{"tf":1.4142135623730951},"84":{"tf":2.0}},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":1,"docs":{"283":{"tf":1.0}}}}}}}}}}}}},"df":0,"docs":{}}}}}}},"r":{"df":5,"docs":{"104":{"tf":1.0},"105":{"tf":1.0},"124":{"tf":1.0},"167":{"tf":1.0},"249":{"tf":1.0}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":19,"docs":{"104":{"tf":1.0},"117":{"tf":1.0},"184":{"tf":1.0},"189":{"tf":1.0},"194":{"tf":1.7320508075688772},"197":{"tf":1.4142135623730951},"201":{"tf":1.0},"225":{"tf":1.0},"229":{"tf":1.0},"245":{"tf":1.0},"254":{"tf":1.0},"263":{"tf":1.0},"44":{"tf":1.0},"59":{"tf":1.0},"79":{"tf":1.0},"81":{"tf":1.0},"83":{"tf":1.0},"84":{"tf":1.4142135623730951},"86":{"tf":1.0}}}}}},"’":{"df":10,"docs":{"115":{"tf":1.0},"189":{"tf":1.0},"193":{"tf":1.4142135623730951},"194":{"tf":1.0},"205":{"tf":1.0},"210":{"tf":1.0},"212":{"tf":1.0},"213":{"tf":1.4142135623730951},"214":{"tf":1.7320508075688772},"87":{"tf":1.0}}}}},"j":{"a":{"df":0,"docs":{},"r":{"/":{"a":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"37":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"223":{"tf":1.0}},"e":{"d":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"176":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"v":{"a":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"/":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"/":{"<":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"103":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"_":{"c":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"70":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"_":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":1,"docs":{"12":{"tf":1.0}}}}}},"df":7,"docs":{"112":{"tf":1.0},"115":{"tf":1.0},"12":{"tf":1.4142135623730951},"125":{"tf":1.0},"128":{"tf":1.0},"223":{"tf":1.0},"37":{"tf":1.0}},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"192":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"d":{"df":0,"docs":{},"k":{"df":1,"docs":{"12":{"tf":1.0}}}},"df":0,"docs":{},"h":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"176":{"tf":1.0}}}},"df":0,"docs":{}}}}},"n":{"a":{"'":{"df":1,"docs":{"115":{"tf":1.0}}},"df":6,"docs":{"111":{"tf":1.0},"115":{"tf":1.4142135623730951},"116":{"tf":2.449489742783178},"37":{"tf":3.4641016151377544},"45":{"tf":1.0},"65":{"tf":1.0}}},"df":0,"docs":{},"i":{"df":2,"docs":{"115":{"tf":1.4142135623730951},"116":{"tf":2.0}}}},"o":{"b":{"df":4,"docs":{"274":{"tf":1.0},"278":{"tf":1.0},"282":{"tf":1.0},"74":{"tf":1.0}}},"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"l":{"_":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"d":{"df":1,"docs":{"264":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"s":{"df":1,"docs":{"241":{"tf":1.0}},"o":{"df":0,"docs":{},"n":{"df":3,"docs":{"189":{"tf":1.0},"271":{"tf":1.4142135623730951},"79":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"45":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":1,"docs":{"194":{"tf":1.0}}}}}}},"v":{"df":0,"docs":{},"m":{"df":2,"docs":{"115":{"tf":1.4142135623730951},"37":{"tf":1.0}}}}},"k":{"8":{"8":{"df":0,"docs":{},"h":{"df":0,"docs":{},"u":{"d":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"176":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":28,"docs":{"121":{"tf":1.0},"142":{"tf":1.0},"150":{"tf":1.0},"163":{"tf":1.0},"165":{"tf":1.0},"167":{"tf":1.0},"171":{"tf":1.0},"179":{"tf":1.0},"182":{"tf":1.4142135623730951},"184":{"tf":1.0},"198":{"tf":1.0},"205":{"tf":2.0},"206":{"tf":1.0},"207":{"tf":1.0},"208":{"tf":1.0},"209":{"tf":2.23606797749979},"210":{"tf":1.0},"212":{"tf":1.4142135623730951},"213":{"tf":1.0},"217":{"tf":1.0},"218":{"tf":1.0},"242":{"tf":1.4142135623730951},"26":{"tf":1.0},"261":{"tf":1.0},"263":{"tf":1.0},"278":{"tf":1.0},"58":{"tf":1.4142135623730951},"6":{"tf":1.0}}}},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"264":{"tf":1.0}}}},"y":{"/":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":1,"docs":{"80":{"tf":1.0}}}}},"df":0,"docs":{}}},"_":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"242":{"tf":1.0}}},"df":0,"docs":{}}},"df":15,"docs":{"142":{"tf":1.7320508075688772},"143":{"tf":1.4142135623730951},"145":{"tf":2.8284271247461903},"146":{"tf":2.0},"184":{"tf":1.0},"198":{"tf":1.0},"201":{"tf":1.0},"225":{"tf":1.0},"229":{"tf":1.4142135623730951},"237":{"tf":1.0},"245":{"tf":1.0},"248":{"tf":1.0},"254":{"tf":1.4142135623730951},"274":{"tf":1.4142135623730951},"283":{"tf":1.4142135623730951}}}},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":1,"docs":{"133":{"tf":1.0}}}},"n":{"d":{"df":7,"docs":{"189":{"tf":1.0},"190":{"tf":1.0},"201":{"tf":1.0},"221":{"tf":1.0},"225":{"tf":1.0},"229":{"tf":1.0},"242":{"tf":1.0}}},"df":0,"docs":{}}},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":22,"docs":{"114":{"tf":1.0},"115":{"tf":1.0},"117":{"tf":1.0},"119":{"tf":1.0},"128":{"tf":1.0},"17":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.0},"21":{"tf":1.0},"228":{"tf":1.7320508075688772},"229":{"tf":1.4142135623730951},"233":{"tf":1.4142135623730951},"254":{"tf":1.0},"26":{"tf":1.0},"268":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"5":{"tf":1.0},"58":{"tf":1.4142135623730951},"84":{"tf":1.0},"87":{"tf":1.4142135623730951},"89":{"tf":1.0}},"l":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"g":{"df":1,"docs":{"175":{"tf":1.0}}}},"df":0,"docs":{}}},"n":{"df":11,"docs":{"104":{"tf":1.0},"151":{"tf":1.4142135623730951},"152":{"tf":1.4142135623730951},"155":{"tf":1.0},"156":{"tf":1.0},"193":{"tf":1.0},"220":{"tf":1.0},"233":{"tf":1.0},"242":{"tf":1.0},"245":{"tf":1.0},"248":{"tf":1.4142135623730951}}}}}},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"/":{"df":0,"docs":{},"s":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":1,"docs":{"51":{"tf":1.0}}}}}}}},"df":22,"docs":{"101":{"tf":1.0},"103":{"tf":2.23606797749979},"104":{"tf":1.0},"108":{"tf":3.4641016151377544},"111":{"tf":2.0},"115":{"tf":2.0},"125":{"tf":1.0},"128":{"tf":1.4142135623730951},"196":{"tf":1.0},"221":{"tf":1.0},"222":{"tf":1.0},"225":{"tf":1.0},"268":{"tf":1.0},"271":{"tf":1.0},"274":{"tf":1.0},"45":{"tf":2.6457513110645907},"65":{"tf":1.7320508075688772},"68":{"tf":1.0},"7":{"tf":1.0},"70":{"tf":2.8284271247461903},"98":{"tf":1.0},"99":{"tf":1.4142135623730951}}}}}}},"t":{"df":2,"docs":{"70":{"tf":1.4142135623730951},"98":{"tf":1.4142135623730951}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"65":{"tf":1.0}}}}}}}},"l":{"a":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":2,"docs":{"6":{"tf":1.4142135623730951},"7":{"tf":1.4142135623730951}}}}},"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"187":{"tf":1.0}}}},"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"103":{"tf":1.0}}},"df":0,"docs":{}},"n":{"d":{"df":5,"docs":{"118":{"tf":1.0},"123":{"tf":1.0},"124":{"tf":1.0},"268":{"tf":1.0},"274":{"tf":1.4142135623730951}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"g":{"df":1,"docs":{"64":{"tf":1.0}},"u":{"a":{"df":0,"docs":{},"g":{"df":8,"docs":{"0":{"tf":1.0},"101":{"tf":1.0},"104":{"tf":1.0},"106":{"tf":1.0},"125":{"tf":1.0},"225":{"tf":1.0},"226":{"tf":1.0},"69":{"tf":1.0}}}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"g":{"df":7,"docs":{"204":{"tf":1.0},"205":{"tf":1.4142135623730951},"208":{"tf":1.0},"212":{"tf":1.7320508075688772},"213":{"tf":1.4142135623730951},"232":{"tf":1.0},"81":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":3,"docs":{"171":{"tf":1.0},"172":{"tf":1.0},"206":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"t":{"df":6,"docs":{"106":{"tf":1.0},"111":{"tf":1.0},"213":{"tf":1.0},"233":{"tf":1.0},"248":{"tf":1.4142135623730951},"88":{"tf":1.0}},"m":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":3,"docs":{"62":{"tf":1.0},"80":{"tf":1.0},"86":{"tf":1.0}}}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":7,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"11":{"tf":1.0},"138":{"tf":1.0},"158":{"tf":1.0},"179":{"tf":1.0},"88":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{"df":14,"docs":{"104":{"tf":1.0},"138":{"tf":1.0},"206":{"tf":1.0},"207":{"tf":1.0},"210":{"tf":1.0},"217":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951},"260":{"tf":1.4142135623730951},"261":{"tf":1.0},"266":{"tf":1.4142135623730951},"271":{"tf":1.0},"274":{"tf":1.4142135623730951},"40":{"tf":1.7320508075688772},"69":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":2,"docs":{"31":{"tf":1.0},"36":{"tf":1.0}}}},"df":0,"docs":{}}},"y":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":11,"docs":{"101":{"tf":1.0},"103":{"tf":1.0},"108":{"tf":1.4142135623730951},"109":{"tf":1.4142135623730951},"140":{"tf":1.0},"228":{"tf":1.0},"232":{"tf":1.4142135623730951},"44":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"90":{"tf":1.0}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"200":{"tf":1.0}}}}}},"z":{"df":0,"docs":{},"i":{"df":1,"docs":{"132":{"tf":1.0}}},"y":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"62":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"d":{"a":{"df":0,"docs":{},"p":{"df":1,"docs":{"274":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"a":{"d":{"df":5,"docs":{"157":{"tf":1.0},"172":{"tf":1.0},"196":{"tf":1.0},"256":{"tf":1.0},"40":{"tf":1.0}}},"df":0,"docs":{},"f":{"df":1,"docs":{"59":{"tf":1.0}}},"n":{"df":1,"docs":{"138":{"tf":1.0}}},"r":{"df":0,"docs":{},"n":{"df":5,"docs":{"171":{"tf":1.0},"172":{"tf":1.0},"236":{"tf":1.0},"7":{"tf":1.0},"75":{"tf":1.4142135623730951}}}},"v":{"df":3,"docs":{"108":{"tf":1.0},"159":{"tf":1.0},"171":{"tf":1.0}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":6,"docs":{"126":{"tf":1.0},"193":{"tf":1.0},"25":{"tf":1.0},"40":{"tf":1.0},"64":{"tf":1.0},"84":{"tf":1.0}}}},"g":{"a":{"c":{"df":0,"docs":{},"i":{"df":2,"docs":{"203":{"tf":1.0},"228":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":1,"docs":{"186":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"s":{"df":10,"docs":{"115":{"tf":1.0},"166":{"tf":1.0},"170":{"tf":1.0},"205":{"tf":1.0},"209":{"tf":1.0},"212":{"tf":1.0},"214":{"tf":1.0},"215":{"tf":1.0},"61":{"tf":1.0},"81":{"tf":1.0}},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"90":{"tf":1.0}}}}}},"t":{"'":{"df":4,"docs":{"227":{"tf":1.0},"228":{"tf":1.0},"230":{"tf":1.0},"62":{"tf":1.0}}},"df":3,"docs":{"225":{"tf":1.0},"283":{"tf":1.0},"81":{"tf":1.0}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":42,"docs":{"103":{"tf":2.8284271247461903},"105":{"tf":1.0},"123":{"tf":1.4142135623730951},"124":{"tf":1.4142135623730951},"18":{"tf":1.0},"200":{"tf":2.0},"219":{"tf":1.7320508075688772},"22":{"tf":1.0},"220":{"tf":1.4142135623730951},"225":{"tf":3.0},"226":{"tf":3.1622776601683795},"229":{"tf":2.6457513110645907},"23":{"tf":1.0},"230":{"tf":1.7320508075688772},"232":{"tf":1.4142135623730951},"233":{"tf":2.23606797749979},"234":{"tf":1.0},"235":{"tf":1.0},"236":{"tf":2.23606797749979},"237":{"tf":1.7320508075688772},"239":{"tf":1.0},"243":{"tf":1.0},"244":{"tf":1.0},"245":{"tf":1.0},"249":{"tf":1.0},"251":{"tf":1.4142135623730951},"252":{"tf":1.0},"254":{"tf":1.7320508075688772},"262":{"tf":1.0},"274":{"tf":1.0},"281":{"tf":1.4142135623730951},"283":{"tf":1.0},"37":{"tf":1.0},"41":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"6":{"tf":1.0},"64":{"tf":1.0},"70":{"tf":1.0},"73":{"tf":1.0},"84":{"tf":1.0},"98":{"tf":1.4142135623730951}}},"r":{"a":{"df":0,"docs":{},"g":{"df":2,"docs":{"123":{"tf":1.0},"79":{"tf":1.0}}}},"df":0,"docs":{}}}}},"i":{"b":{".":{"df":0,"docs":{},"r":{"df":4,"docs":{"103":{"tf":1.0},"105":{"tf":2.0},"107":{"tf":1.0},"60":{"tf":1.0}}}},"<":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{">":{"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{".":{"df":0,"docs":{},"k":{"df":0,"docs":{},"t":{"df":1,"docs":{"103":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}},"df":8,"docs":{"11":{"tf":1.0},"175":{"tf":1.0},"279":{"tf":1.0},"282":{"tf":1.0},"37":{"tf":1.0},"58":{"tf":1.0},"60":{"tf":1.4142135623730951},"69":{"tf":1.0}},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"a":{"df":0,"docs":{},"z":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"d":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":1,"docs":{"114":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"r":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":33,"docs":{"11":{"tf":1.0},"114":{"tf":1.0},"167":{"tf":1.0},"187":{"tf":1.0},"188":{"tf":1.0},"189":{"tf":1.7320508075688772},"190":{"tf":2.449489742783178},"191":{"tf":1.4142135623730951},"193":{"tf":1.4142135623730951},"194":{"tf":1.4142135623730951},"196":{"tf":2.449489742783178},"197":{"tf":2.23606797749979},"198":{"tf":1.0},"199":{"tf":1.4142135623730951},"200":{"tf":1.7320508075688772},"201":{"tf":1.4142135623730951},"220":{"tf":1.4142135623730951},"222":{"tf":1.4142135623730951},"225":{"tf":1.0},"228":{"tf":1.0},"251":{"tf":1.0},"274":{"tf":1.0},"278":{"tf":1.4142135623730951},"28":{"tf":1.0},"284":{"tf":1.0},"33":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.7320508075688772},"45":{"tf":1.0},"60":{"tf":1.0},"69":{"tf":1.0},"77":{"tf":1.4142135623730951},"90":{"tf":1.4142135623730951}}},"y":{"/":{"c":{"a":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{"df":0,"docs":{},"m":{"df":1,"docs":{"25":{"tf":1.0}}}}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"/":{"df":0,"docs":{},"x":{"c":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"e":{"/":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"d":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"/":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"25":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"s":{"/":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"280":{"tf":1.0}}},"df":0,"docs":{}}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"k":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":1,"docs":{"279":{"tf":1.0}}}}}}}}},"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":4,"docs":{"11":{"tf":1.4142135623730951},"12":{"tf":1.0},"14":{"tf":1.0},"279":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}},"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":4,"docs":{"2":{"tf":1.4142135623730951},"63":{"tf":1.0},"64":{"tf":1.7320508075688772},"7":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"y":{"c":{"df":0,"docs":{},"l":{"df":2,"docs":{"131":{"tf":1.7320508075688772},"133":{"tf":1.0}}}},"df":0,"docs":{}}},"df":1,"docs":{"256":{"tf":1.0}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"130":{"tf":1.0}}}}}}},"k":{"df":0,"docs":{},"e":{"df":1,"docs":{"104":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"d":{"df":3,"docs":{"208":{"tf":1.0},"256":{"tf":1.0},"258":{"tf":1.0}}},"df":0,"docs":{}}}}}}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":17,"docs":{"134":{"tf":1.0},"140":{"tf":1.0},"17":{"tf":1.0},"187":{"tf":1.0},"202":{"tf":1.0},"206":{"tf":1.0},"207":{"tf":1.7320508075688772},"208":{"tf":2.23606797749979},"209":{"tf":1.7320508075688772},"210":{"tf":1.4142135623730951},"213":{"tf":2.23606797749979},"214":{"tf":1.4142135623730951},"217":{"tf":1.0},"218":{"tf":1.4142135623730951},"225":{"tf":1.0},"242":{"tf":1.0},"7":{"tf":1.0}}}}},"n":{"df":0,"docs":{},"e":{"df":11,"docs":{"115":{"tf":1.0},"117":{"tf":1.4142135623730951},"126":{"tf":1.0},"165":{"tf":1.0},"17":{"tf":1.0},"172":{"tf":1.0},"18":{"tf":1.0},"200":{"tf":1.4142135623730951},"237":{"tf":1.0},"29":{"tf":1.4142135623730951},"34":{"tf":1.4142135623730951}}},"k":{"a":{"df":0,"docs":{},"g":{"df":2,"docs":{"225":{"tf":1.0},"282":{"tf":1.0}}}},"df":14,"docs":{"11":{"tf":1.0},"119":{"tf":1.0},"147":{"tf":1.0},"167":{"tf":1.0},"185":{"tf":1.0},"218":{"tf":1.0},"263":{"tf":1.0},"271":{"tf":1.7320508075688772},"278":{"tf":1.0},"282":{"tf":2.0},"37":{"tf":1.0},"40":{"tf":1.0},"58":{"tf":1.0},"60":{"tf":1.0}}},"t":{"df":2,"docs":{"274":{"tf":1.0},"92":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"7":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"x":{"df":5,"docs":{"11":{"tf":1.4142135623730951},"13":{"tf":1.0},"17":{"tf":1.4142135623730951},"280":{"tf":1.4142135623730951},"37":{"tf":1.7320508075688772}}}}},"s":{"df":0,"docs":{},"t":{"df":33,"docs":{"106":{"tf":1.7320508075688772},"109":{"tf":1.0},"134":{"tf":1.0},"137":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0},"17":{"tf":1.0},"188":{"tf":1.0},"200":{"tf":1.0},"203":{"tf":1.0},"204":{"tf":1.0},"21":{"tf":1.0},"220":{"tf":1.0},"233":{"tf":1.4142135623730951},"236":{"tf":1.0},"242":{"tf":1.7320508075688772},"251":{"tf":1.0},"259":{"tf":1.0},"264":{"tf":1.0},"266":{"tf":1.4142135623730951},"274":{"tf":1.0},"28":{"tf":1.0},"31":{"tf":1.0},"33":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.4142135623730951},"6":{"tf":1.0},"61":{"tf":1.0},"68":{"tf":1.0},"69":{"tf":1.0},"73":{"tf":2.23606797749979}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"62":{"tf":1.0},"89":{"tf":1.0}}}},"t":{"df":0,"docs":{},"l":{"df":9,"docs":{"104":{"tf":1.0},"170":{"tf":1.4142135623730951},"200":{"tf":1.0},"221":{"tf":1.0},"237":{"tf":1.0},"252":{"tf":1.0},"26":{"tf":1.0},"37":{"tf":1.0},"6":{"tf":1.0}}}}},"v":{"df":0,"docs":{},"e":{"df":11,"docs":{"130":{"tf":1.0},"229":{"tf":1.0},"231":{"tf":1.0},"252":{"tf":1.0},"43":{"tf":1.0},"46":{"tf":1.4142135623730951},"48":{"tf":1.0},"51":{"tf":1.0},"59":{"tf":1.0},"69":{"tf":1.0},"75":{"tf":1.0}}}}},"l":{"d":{"b":{"df":1,"docs":{"117":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":1,"docs":{"37":{"tf":1.0}}}}},"n":{"df":1,"docs":{"11":{"tf":1.0}}},"o":{"a":{"d":{"df":7,"docs":{"114":{"tf":1.0},"184":{"tf":1.4142135623730951},"22":{"tf":1.0},"222":{"tf":1.4142135623730951},"224":{"tf":1.0},"226":{"tf":1.0},"229":{"tf":1.0}}},"df":0,"docs":{}},"c":{"a":{"df":0,"docs":{},"l":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":4,"docs":{"17":{"tf":1.0},"18":{"tf":1.4142135623730951},"20":{"tf":1.0},"22":{"tf":1.4142135623730951}}}}}}}}}}},"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"/":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"g":{"df":2,"docs":{"248":{"tf":1.4142135623730951},"249":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":1,"docs":{"249":{"tf":1.0}}}}}}}}},"df":40,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"11":{"tf":1.4142135623730951},"118":{"tf":1.0},"124":{"tf":1.4142135623730951},"15":{"tf":1.7320508075688772},"16":{"tf":1.4142135623730951},"18":{"tf":2.0},"194":{"tf":1.0},"198":{"tf":1.0},"22":{"tf":2.0},"224":{"tf":1.0},"23":{"tf":3.0},"24":{"tf":1.7320508075688772},"242":{"tf":1.7320508075688772},"245":{"tf":1.7320508075688772},"246":{"tf":1.4142135623730951},"248":{"tf":1.4142135623730951},"249":{"tf":2.6457513110645907},"25":{"tf":1.7320508075688772},"250":{"tf":2.23606797749979},"257":{"tf":1.0},"26":{"tf":1.7320508075688772},"28":{"tf":1.0},"29":{"tf":1.4142135623730951},"30":{"tf":1.0},"31":{"tf":2.23606797749979},"32":{"tf":2.8284271247461903},"33":{"tf":1.0},"34":{"tf":1.7320508075688772},"35":{"tf":1.0},"36":{"tf":2.449489742783178},"37":{"tf":2.449489742783178},"45":{"tf":1.0},"49":{"tf":1.4142135623730951},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"67":{"tf":1.0},"99":{"tf":1.0}},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"190":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{">":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"/":{"c":{"/":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"{":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"}":{"/":{".":{"df":0,"docs":{},"m":{"2":{"/":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"<":{"/":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"13":{"tf":1.0}}}}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}}},"t":{"df":10,"docs":{"106":{"tf":1.0},"12":{"tf":1.0},"14":{"tf":1.0},"158":{"tf":1.0},"201":{"tf":1.0},"22":{"tf":1.0},"224":{"tf":1.0},"267":{"tf":1.7320508075688772},"279":{"tf":1.7320508075688772},"82":{"tf":1.0}}}},"df":0,"docs":{},"k":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"x":{"df":2,"docs":{"228":{"tf":1.4142135623730951},"234":{"tf":1.0}}}}},"df":3,"docs":{"142":{"tf":1.0},"205":{"tf":1.4142135623730951},"64":{"tf":1.0}}}},"df":0,"docs":{},"g":{"c":{"a":{"df":0,"docs":{},"t":{"df":2,"docs":{"126":{"tf":2.449489742783178},"37":{"tf":1.0}}}},"df":0,"docs":{}},"df":10,"docs":{"116":{"tf":1.0},"125":{"tf":2.23606797749979},"126":{"tf":2.449489742783178},"127":{"tf":1.0},"134":{"tf":1.4142135623730951},"242":{"tf":1.0},"268":{"tf":1.7320508075688772},"280":{"tf":1.0},"37":{"tf":1.0},"82":{"tf":1.0}},"i":{"c":{"df":19,"docs":{"140":{"tf":1.0},"141":{"tf":1.0},"145":{"tf":2.449489742783178},"146":{"tf":1.0},"149":{"tf":1.0},"154":{"tf":1.0},"158":{"tf":1.0},"208":{"tf":1.0},"244":{"tf":1.0},"254":{"tf":1.0},"256":{"tf":1.4142135623730951},"258":{"tf":1.0},"282":{"tf":1.4142135623730951},"73":{"tf":1.0},"78":{"tf":1.0},"79":{"tf":1.0},"81":{"tf":1.0},"82":{"tf":1.4142135623730951},"84":{"tf":1.4142135623730951}}},"df":0,"docs":{},"n":{"df":15,"docs":{"112":{"tf":1.0},"134":{"tf":1.0},"139":{"tf":1.0},"140":{"tf":1.7320508075688772},"141":{"tf":1.4142135623730951},"145":{"tf":2.6457513110645907},"146":{"tf":2.23606797749979},"147":{"tf":1.0},"221":{"tf":1.0},"228":{"tf":1.4142135623730951},"232":{"tf":1.0},"233":{"tf":2.0},"244":{"tf":1.0},"264":{"tf":1.4142135623730951},"78":{"tf":1.0}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"132":{"tf":1.0}}}},"df":0,"docs":{}}}}}}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":1,"docs":{"126":{"tf":1.0}}}}}}}},"n":{"df":0,"docs":{},"g":{"df":17,"docs":{"104":{"tf":1.0},"105":{"tf":1.0},"115":{"tf":1.4142135623730951},"132":{"tf":1.0},"140":{"tf":1.0},"164":{"tf":1.0},"170":{"tf":1.0},"204":{"tf":1.0},"205":{"tf":1.0},"212":{"tf":1.0},"215":{"tf":1.0},"22":{"tf":1.0},"229":{"tf":1.0},"25":{"tf":1.0},"258":{"tf":1.0},"264":{"tf":1.0},"59":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":7,"docs":{"128":{"tf":1.0},"140":{"tf":1.0},"180":{"tf":1.0},"205":{"tf":1.0},"206":{"tf":1.7320508075688772},"226":{"tf":1.0},"86":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"k":{"df":19,"docs":{"102":{"tf":1.4142135623730951},"103":{"tf":1.0},"158":{"tf":1.0},"215":{"tf":1.0},"216":{"tf":1.0},"22":{"tf":1.0},"221":{"tf":1.0},"224":{"tf":1.4142135623730951},"280":{"tf":1.0},"283":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"6":{"tf":1.0},"62":{"tf":1.0},"73":{"tf":1.7320508075688772},"75":{"tf":1.4142135623730951},"82":{"tf":1.0},"86":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"203":{"tf":1.0},"218":{"tf":1.0}}}}},"p":{"df":5,"docs":{"190":{"tf":1.0},"193":{"tf":1.0},"197":{"tf":1.4142135623730951},"249":{"tf":1.0},"82":{"tf":1.0}}},"s":{"df":1,"docs":{"240":{"tf":1.0}}}},"s":{"df":0,"docs":{},"e":{"df":5,"docs":{"204":{"tf":1.0},"206":{"tf":1.0},"208":{"tf":1.0},"210":{"tf":1.4142135623730951},"213":{"tf":1.4142135623730951}}},"s":{"df":2,"docs":{"157":{"tf":1.0},"209":{"tf":1.0}}},"t":{"df":2,"docs":{"158":{"tf":1.0},"231":{"tf":1.0}}}},"t":{"df":4,"docs":{"115":{"tf":1.0},"116":{"tf":1.0},"212":{"tf":1.4142135623730951},"240":{"tf":1.0}}},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"a":{"c":{"6":{"4":{"df":1,"docs":{"202":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"w":{"df":16,"docs":{"103":{"tf":2.0},"209":{"tf":1.4142135623730951},"220":{"tf":1.0},"225":{"tf":1.0},"226":{"tf":2.23606797749979},"229":{"tf":1.7320508075688772},"230":{"tf":1.4142135623730951},"232":{"tf":1.0},"233":{"tf":2.0},"234":{"tf":1.0},"235":{"tf":1.0},"236":{"tf":1.0},"237":{"tf":1.4142135623730951},"249":{"tf":1.0},"281":{"tf":1.0},"37":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"c":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"c":{"a":{"df":0,"docs":{},"s":{"df":2,"docs":{"96":{"tf":1.0},"99":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":3,"docs":{"209":{"tf":1.0},"225":{"tf":1.7320508075688772},"99":{"tf":1.0}}}}}},"u":{"c":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"258":{"tf":1.0}}},"df":0,"docs":{}},"k":{"df":1,"docs":{"90":{"tf":1.0}}}},"df":0,"docs":{}}},"m":{"1":{"df":7,"docs":{"161":{"tf":1.0},"162":{"tf":1.0},"163":{"tf":1.0},"169":{"tf":1.0},"17":{"tf":1.0},"170":{"tf":1.0},"175":{"tf":1.0}}},"2":{"df":1,"docs":{"13":{"tf":1.4142135623730951}}},"a":{"c":{"df":3,"docs":{"280":{"tf":1.0},"37":{"tf":1.0},"73":{"tf":1.0}},"h":{"df":2,"docs":{"122":{"tf":1.0},"124":{"tf":1.4142135623730951}},"i":{"df":0,"docs":{},"n":{"df":10,"docs":{"161":{"tf":1.0},"162":{"tf":1.0},"17":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"175":{"tf":1.0},"223":{"tf":1.4142135623730951},"280":{"tf":1.4142135623730951},"45":{"tf":1.0}}}}},"o":{"df":2,"docs":{"11":{"tf":1.0},"280":{"tf":1.7320508075688772}}},"r":{"df":0,"docs":{},"o":{"df":3,"docs":{"200":{"tf":1.0},"43":{"tf":1.0},"93":{"tf":1.0}}}}},"d":{"df":0,"docs":{},"e":{"df":15,"docs":{"121":{"tf":1.0},"122":{"tf":1.0},"136":{"tf":1.0},"138":{"tf":1.0},"167":{"tf":1.0},"192":{"tf":1.0},"193":{"tf":1.0},"194":{"tf":1.0},"200":{"tf":1.0},"21":{"tf":1.0},"226":{"tf":1.0},"227":{"tf":1.0},"231":{"tf":1.0},"233":{"tf":1.0},"82":{"tf":1.0}}},"r":{"df":3,"docs":{"134":{"tf":1.4142135623730951},"137":{"tf":1.0},"138":{"tf":2.0}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"228":{"tf":1.0}}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"274":{"tf":1.0}}},"n":{"df":24,"docs":{"103":{"tf":2.0},"119":{"tf":1.0},"121":{"tf":1.0},"126":{"tf":1.0},"170":{"tf":1.0},"182":{"tf":1.0},"193":{"tf":1.0},"194":{"tf":1.0},"201":{"tf":1.0},"224":{"tf":1.0},"246":{"tf":1.0},"251":{"tf":1.0},"254":{"tf":1.0},"260":{"tf":1.0},"268":{"tf":1.7320508075688772},"269":{"tf":1.0},"270":{"tf":1.0},"274":{"tf":2.6457513110645907},"40":{"tf":1.7320508075688772},"51":{"tf":1.0},"64":{"tf":1.0},"7":{"tf":1.0},"70":{"tf":1.0},"82":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":4,"docs":{"14":{"tf":1.0},"180":{"tf":1.0},"253":{"tf":1.4142135623730951},"278":{"tf":1.0}}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":12,"docs":{"146":{"tf":1.0},"162":{"tf":1.0},"167":{"tf":1.0},"169":{"tf":1.0},"170":{"tf":1.4142135623730951},"171":{"tf":1.0},"172":{"tf":1.0},"196":{"tf":1.0},"203":{"tf":1.0},"260":{"tf":1.0},"263":{"tf":1.0},"274":{"tf":1.0}}}},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"220":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"64":{"tf":1.0}}},"df":5,"docs":{"138":{"tf":1.0},"164":{"tf":1.0},"172":{"tf":1.0},"208":{"tf":1.0},"274":{"tf":1.0}}}}}}},"j":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":8,"docs":{"116":{"tf":1.0},"208":{"tf":1.0},"262":{"tf":1.0},"269":{"tf":1.0},"270":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"58":{"tf":1.0}}}}},"k":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"g":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"253":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":64,"docs":{"102":{"tf":1.0},"104":{"tf":1.0},"105":{"tf":1.0},"106":{"tf":1.4142135623730951},"107":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"11":{"tf":1.7320508075688772},"111":{"tf":1.0},"116":{"tf":1.4142135623730951},"117":{"tf":1.4142135623730951},"118":{"tf":1.0},"120":{"tf":1.0},"122":{"tf":1.4142135623730951},"124":{"tf":1.7320508075688772},"126":{"tf":1.0},"129":{"tf":1.0},"14":{"tf":1.0},"145":{"tf":1.0},"146":{"tf":1.0},"149":{"tf":1.0},"163":{"tf":1.0},"165":{"tf":1.0},"169":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"188":{"tf":1.0},"197":{"tf":1.0},"198":{"tf":1.0},"200":{"tf":1.0},"21":{"tf":1.0},"214":{"tf":1.0},"226":{"tf":1.0},"227":{"tf":1.0},"241":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951},"250":{"tf":1.0},"26":{"tf":1.0},"263":{"tf":1.0},"264":{"tf":1.0},"270":{"tf":1.0},"272":{"tf":1.0},"274":{"tf":1.0},"278":{"tf":1.0},"280":{"tf":1.0},"282":{"tf":1.0},"32":{"tf":1.0},"36":{"tf":1.0},"39":{"tf":1.4142135623730951},"40":{"tf":1.0},"43":{"tf":1.7320508075688772},"46":{"tf":1.0},"51":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0},"7":{"tf":1.0},"70":{"tf":1.0},"72":{"tf":1.0},"73":{"tf":1.4142135623730951},"75":{"tf":1.0},"79":{"tf":1.0}}}},"l":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":1,"docs":{"274":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"n":{"a":{"df":0,"docs":{},"g":{"df":42,"docs":{"115":{"tf":1.0},"117":{"tf":1.0},"132":{"tf":1.7320508075688772},"140":{"tf":1.0},"146":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"174":{"tf":1.4142135623730951},"185":{"tf":1.0},"187":{"tf":1.0},"189":{"tf":1.0},"197":{"tf":1.0},"201":{"tf":1.0},"219":{"tf":1.7320508075688772},"220":{"tf":1.4142135623730951},"221":{"tf":1.0},"225":{"tf":2.8284271247461903},"226":{"tf":3.0},"228":{"tf":2.23606797749979},"229":{"tf":2.0},"23":{"tf":1.7320508075688772},"232":{"tf":1.7320508075688772},"233":{"tf":1.4142135623730951},"236":{"tf":1.0},"239":{"tf":1.0},"241":{"tf":1.0},"242":{"tf":1.4142135623730951},"244":{"tf":1.0},"245":{"tf":1.4142135623730951},"253":{"tf":1.0},"268":{"tf":1.4142135623730951},"269":{"tf":1.0},"274":{"tf":1.4142135623730951},"32":{"tf":1.7320508075688772},"48":{"tf":1.0},"63":{"tf":1.0},"69":{"tf":1.0},"7":{"tf":1.0},"73":{"tf":1.7320508075688772},"78":{"tf":1.0},"79":{"tf":1.0},"81":{"tf":1.4142135623730951}}}},"df":3,"docs":{"201":{"tf":1.4142135623730951},"236":{"tf":1.4142135623730951},"242":{"tf":1.7320508075688772}},"i":{"df":19,"docs":{"106":{"tf":1.4142135623730951},"109":{"tf":1.0},"126":{"tf":1.0},"152":{"tf":1.0},"154":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0},"175":{"tf":1.0},"204":{"tf":1.4142135623730951},"213":{"tf":1.0},"214":{"tf":1.0},"228":{"tf":1.0},"257":{"tf":1.0},"283":{"tf":1.0},"59":{"tf":1.0},"62":{"tf":1.0},"64":{"tf":1.0},"75":{"tf":1.0},"79":{"tf":1.4142135623730951}},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"70":{"tf":1.0}}}}}}},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"126":{"tf":1.0}}}}},"u":{"a":{"df":0,"docs":{},"l":{"df":18,"docs":{"101":{"tf":1.0},"111":{"tf":1.0},"119":{"tf":1.0},"121":{"tf":1.0},"17":{"tf":1.0},"20":{"tf":1.4142135623730951},"22":{"tf":1.7320508075688772},"25":{"tf":1.4142135623730951},"27":{"tf":1.7320508075688772},"274":{"tf":1.4142135623730951},"31":{"tf":1.0},"36":{"tf":1.0},"39":{"tf":1.0},"49":{"tf":1.4142135623730951},"52":{"tf":1.0},"59":{"tf":1.0},"82":{"tf":1.0},"84":{"tf":1.0}}}},"df":0,"docs":{}}},"p":{"df":9,"docs":{"106":{"tf":1.0},"11":{"tf":1.4142135623730951},"116":{"tf":1.0},"12":{"tf":1.0},"232":{"tf":1.0},"251":{"tf":1.0},"252":{"tf":1.4142135623730951},"28":{"tf":1.0},"33":{"tf":1.0}}},"r":{"df":0,"docs":{},"k":{"d":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":0,"docs":{},"n":{"df":4,"docs":{"134":{"tf":1.0},"135":{"tf":1.0},"137":{"tf":1.0},"284":{"tf":1.0}}}}}},"df":1,"docs":{"171":{"tf":1.0}},"h":{"df":1,"docs":{"276":{"tf":1.0}}}},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"197":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":2,"docs":{"242":{"tf":1.0},"81":{"tf":1.0}}}}}},"t":{"c":{"df":0,"docs":{},"h":{"df":12,"docs":{"104":{"tf":1.0},"105":{"tf":1.4142135623730951},"124":{"tf":1.0},"165":{"tf":1.0},"20":{"tf":1.0},"201":{"tf":1.0},"213":{"tf":1.0},"22":{"tf":1.4142135623730951},"242":{"tf":1.0},"268":{"tf":1.0},"40":{"tf":1.0},"87":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"174":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"df":2,"docs":{"122":{"tf":1.4142135623730951},"3":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"82":{"tf":1.0}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{".":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{".":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":1,"docs":{"274":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":7,"docs":{"11":{"tf":1.0},"13":{"tf":2.449489742783178},"16":{"tf":1.0},"18":{"tf":1.0},"22":{"tf":1.0},"271":{"tf":2.23606797749979},"274":{"tf":1.0}},"l":{"df":0,"docs":{},"o":{"c":{"df":1,"docs":{"20":{"tf":1.0}}},"df":0,"docs":{}}}}}},"y":{"b":{"df":1,"docs":{"51":{"tf":1.0}}},"df":0,"docs":{}}},"d":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":2,"docs":{"284":{"tf":1.0},"286":{"tf":2.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"n":{"df":25,"docs":{"118":{"tf":1.0},"128":{"tf":1.0},"13":{"tf":1.0},"140":{"tf":1.0},"175":{"tf":1.0},"190":{"tf":1.0},"201":{"tf":1.0},"204":{"tf":1.7320508075688772},"205":{"tf":1.0},"207":{"tf":1.7320508075688772},"208":{"tf":1.0},"209":{"tf":1.4142135623730951},"213":{"tf":1.0},"22":{"tf":1.0},"224":{"tf":1.0},"232":{"tf":1.0},"242":{"tf":1.0},"250":{"tf":1.0},"263":{"tf":1.0},"45":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"82":{"tf":1.4142135623730951},"86":{"tf":1.4142135623730951},"89":{"tf":1.4142135623730951}}},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":4,"docs":{"203":{"tf":1.0},"274":{"tf":1.4142135623730951},"59":{"tf":1.0},"64":{"tf":1.0}}}}}},"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"n":{"df":3,"docs":{"226":{"tf":1.0},"231":{"tf":1.0},"274":{"tf":1.0}}}},"df":0,"docs":{}}},"d":{"df":0,"docs":{},"i":{"a":{"=":{"\"":{"(":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":1,"docs":{"283":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"n":{"df":1,"docs":{"204":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":2,"docs":{"111":{"tf":1.0},"220":{"tf":1.0}}}},"g":{"a":{"df":0,"docs":{},"z":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"d":{"'":{")":{".":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"}":{"/":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"j":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"s":{"/":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"k":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":1,"docs":{"224":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"k":{"df":0,"docs":{},"s":{"[":{"\"":{"c":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"224":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":16,"docs":{"105":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"197":{"tf":1.0},"219":{"tf":1.4142135623730951},"220":{"tf":1.7320508075688772},"221":{"tf":2.23606797749979},"222":{"tf":2.23606797749979},"223":{"tf":1.7320508075688772},"224":{"tf":1.7320508075688772},"251":{"tf":1.0},"252":{"tf":1.4142135623730951},"46":{"tf":1.0},"69":{"tf":1.0},"70":{"tf":1.0},"73":{"tf":2.0}},"s":{"/":{"df":0,"docs":{},"f":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"/":{"c":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":1,"docs":{"69":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{},"s":{"df":0,"docs":{},"r":{"c":{"/":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"b":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"69":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"i":{"df":0,"docs":{},"o":{"df":9,"docs":{"109":{"tf":1.4142135623730951},"14":{"tf":1.4142135623730951},"252":{"tf":1.0},"28":{"tf":1.0},"29":{"tf":1.0},"33":{"tf":1.0},"46":{"tf":1.0},"69":{"tf":1.4142135623730951},"73":{"tf":2.449489742783178}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"m":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":3,"docs":{"3":{"tf":1.0},"69":{"tf":1.4142135623730951},"8":{"tf":1.0}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":3,"docs":{"116":{"tf":1.7320508075688772},"132":{"tf":1.0},"264":{"tf":1.0}}}}},"s":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"51":{"tf":1.0}}}},"df":0,"docs":{}}},"n":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"105":{"tf":1.0}}},"t":{"df":1,"docs":{"60":{"tf":1.0}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":3,"docs":{"204":{"tf":1.0},"210":{"tf":1.0},"242":{"tf":1.0}}}}}},"u":{"_":{"_":{"_":{"_":{"_":{"_":{"_":{"_":{"df":1,"docs":{"62":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":3,"docs":{"12":{"tf":1.0},"126":{"tf":1.0},"62":{"tf":1.0}}}},"r":{"df":0,"docs":{},"g":{"df":9,"docs":{"118":{"tf":1.0},"119":{"tf":2.23606797749979},"158":{"tf":1.0},"249":{"tf":1.0},"250":{"tf":1.0},"268":{"tf":1.4142135623730951},"270":{"tf":1.0},"40":{"tf":1.0},"7":{"tf":1.4142135623730951}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":1,"docs":{"193":{"tf":1.0}}}}},"m":{"a":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"286":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"s":{"a":{"df":0,"docs":{},"g":{"df":4,"docs":{"104":{"tf":1.0},"105":{"tf":1.0},"106":{"tf":2.449489742783178},"126":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"a":{"/":{"df":0,"docs":{},"g":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"b":{"df":4,"docs":{"226":{"tf":1.0},"228":{"tf":1.0},"233":{"tf":1.0},"245":{"tf":1.0}}},"df":0,"docs":{}}}}},"d":{"a":{"df":0,"docs":{},"t":{"a":{".":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"a":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"d":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"a":{"df":1,"docs":{"283":{"tf":1.0}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":10,"docs":{"245":{"tf":1.0},"248":{"tf":1.0},"249":{"tf":1.0},"252":{"tf":1.0},"254":{"tf":1.0},"271":{"tf":1.4142135623730951},"70":{"tf":1.0},"80":{"tf":1.0},"81":{"tf":1.0},"87":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":4,"docs":{"231":{"tf":1.0},"261":{"tf":1.0},"80":{"tf":1.0},"84":{"tf":1.4142135623730951}}},"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"d":{"df":6,"docs":{"131":{"tf":1.7320508075688772},"133":{"tf":1.0},"16":{"tf":1.0},"245":{"tf":1.7320508075688772},"268":{"tf":1.0},"283":{"tf":1.0}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"i":{"c":{"df":5,"docs":{"184":{"tf":1.0},"219":{"tf":1.0},"253":{"tf":1.0},"259":{"tf":1.0},"283":{"tf":1.0}}},"df":0,"docs":{}}}}},"h":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"186":{"tf":1.0},"202":{"tf":1.0}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"i":{"b":{"df":1,"docs":{"206":{"tf":1.0}}},"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":1,"docs":{"137":{"tf":1.0}}}}},"df":0,"docs":{}}},"d":{"d":{"df":0,"docs":{},"l":{"df":2,"docs":{"156":{"tf":1.0},"201":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":1,"docs":{"165":{"tf":1.0}},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":21,"docs":{"134":{"tf":1.0},"145":{"tf":1.0},"154":{"tf":1.4142135623730951},"167":{"tf":1.4142135623730951},"169":{"tf":1.0},"202":{"tf":1.0},"203":{"tf":2.23606797749979},"204":{"tf":3.4641016151377544},"205":{"tf":1.7320508075688772},"206":{"tf":1.0},"207":{"tf":2.449489742783178},"208":{"tf":1.7320508075688772},"209":{"tf":1.4142135623730951},"212":{"tf":2.449489742783178},"213":{"tf":1.4142135623730951},"214":{"tf":1.7320508075688772},"216":{"tf":1.0},"217":{"tf":1.7320508075688772},"218":{"tf":1.7320508075688772},"257":{"tf":1.0},"258":{"tf":1.0}}}},"df":0,"docs":{}}},"h":{"a":{"df":0,"docs":{},"i":{"df":1,"docs":{"274":{"tf":1.0}}}},"df":0,"docs":{}},"l":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"105":{"tf":1.0}}}},"df":0,"docs":{}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"204":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"n":{"d":{"df":2,"docs":{"242":{"tf":1.0},"63":{"tf":1.0}}},"df":0,"docs":{},"e":{"df":1,"docs":{"37":{"tf":1.0}}},"i":{"df":0,"docs":{},"m":{"df":5,"docs":{"118":{"tf":1.0},"163":{"tf":1.0},"199":{"tf":1.0},"226":{"tf":1.0},"258":{"tf":1.0}},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"263":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"262":{"tf":1.0}}}},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"104":{"tf":1.0},"20":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":2,"docs":{"248":{"tf":1.0},"249":{"tf":1.4142135623730951}}}}}},"s":{"c":{"df":1,"docs":{"240":{"tf":1.0}}},"df":0,"docs":{},"i":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":1,"docs":{"157":{"tf":1.0}},"i":{"df":1,"docs":{"158":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"m":{"a":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"121":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"s":{"df":2,"docs":{"109":{"tf":1.0},"278":{"tf":1.0}}},"t":{"a":{"df":0,"docs":{},"k":{"df":1,"docs":{"79":{"tf":1.0}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"/":{"c":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":1,"docs":{"116":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":1,"docs":{"210":{"tf":1.0}}}}}},"m":{"df":1,"docs":{"268":{"tf":1.0}}},"o":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":14,"docs":{"119":{"tf":1.0},"134":{"tf":1.0},"186":{"tf":1.4142135623730951},"187":{"tf":1.7320508075688772},"188":{"tf":1.0},"190":{"tf":1.4142135623730951},"192":{"tf":1.0},"194":{"tf":1.0},"197":{"tf":1.0},"200":{"tf":2.8284271247461903},"201":{"tf":1.4142135623730951},"232":{"tf":1.0},"274":{"tf":1.0},"62":{"tf":1.0}},"e":{"'":{"df":1,"docs":{"178":{"tf":1.0}}},"_":{"_":{"_":{"_":{"_":{"_":{"df":1,"docs":{"62":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"c":{"df":0,"docs":{},"k":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"51":{"tf":1.0}}}},"df":0,"docs":{}},"df":2,"docs":{"43":{"tf":1.7320508075688772},"51":{"tf":1.0}},"i":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":1,"docs":{"43":{"tf":1.0}}}}},"df":0,"docs":{}}}},"d":{"df":3,"docs":{"60":{"tf":1.0},"61":{"tf":2.0},"62":{"tf":1.0}},"e":{"df":2,"docs":{"236":{"tf":1.0},"79":{"tf":1.0}},"l":{"df":8,"docs":{"105":{"tf":1.0},"145":{"tf":1.0},"184":{"tf":1.0},"194":{"tf":1.0},"197":{"tf":1.7320508075688772},"201":{"tf":1.4142135623730951},"274":{"tf":1.0},"51":{"tf":1.0}}},"r":{"df":0,"docs":{},"n":{"df":2,"docs":{"161":{"tf":1.0},"169":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"f":{"df":1,"docs":{"256":{"tf":1.0}},"i":{"df":12,"docs":{"183":{"tf":1.0},"196":{"tf":1.0},"197":{"tf":1.0},"201":{"tf":1.4142135623730951},"249":{"tf":1.0},"250":{"tf":1.0},"256":{"tf":1.0},"274":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.7320508075688772},"7":{"tf":1.4142135623730951}}}}},"u":{"df":0,"docs":{},"l":{"df":13,"docs":{"103":{"tf":1.0},"105":{"tf":1.0},"175":{"tf":1.0},"222":{"tf":1.0},"251":{"tf":1.0},"252":{"tf":1.4142135623730951},"28":{"tf":1.0},"282":{"tf":1.4142135623730951},"33":{"tf":1.0},"61":{"tf":1.7320508075688772},"62":{"tf":1.0},"83":{"tf":1.4142135623730951},"93":{"tf":1.0}},"e":{".":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"p":{"df":1,"docs":{"252":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},":":{"\"":{"df":0,"docs":{},"j":{"df":0,"docs":{},"n":{"a":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"58":{"tf":1.0}}}}}},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"152":{"tf":1.0}}}}}},"o":{"df":1,"docs":{"18":{"tf":1.0}}},"t":{"df":0,"docs":{},"h":{"df":2,"docs":{"213":{"tf":1.4142135623730951},"278":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"e":{"_":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"43":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":39,"docs":{"10":{"tf":1.0},"101":{"tf":1.0},"105":{"tf":1.0},"106":{"tf":1.4142135623730951},"126":{"tf":1.4142135623730951},"134":{"tf":1.0},"161":{"tf":1.0},"166":{"tf":1.0},"169":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"177":{"tf":1.0},"194":{"tf":1.0},"200":{"tf":1.0},"205":{"tf":1.7320508075688772},"209":{"tf":1.4142135623730951},"210":{"tf":1.4142135623730951},"213":{"tf":1.7320508075688772},"225":{"tf":1.0},"227":{"tf":1.0},"229":{"tf":1.0},"23":{"tf":1.0},"231":{"tf":1.0},"232":{"tf":1.4142135623730951},"242":{"tf":1.0},"274":{"tf":1.4142135623730951},"32":{"tf":1.4142135623730951},"36":{"tf":1.0},"50":{"tf":1.0},"51":{"tf":1.0},"6":{"tf":1.7320508075688772},"60":{"tf":1.4142135623730951},"61":{"tf":1.0},"7":{"tf":1.4142135623730951},"73":{"tf":1.0},"78":{"tf":1.4142135623730951},"93":{"tf":1.0},"96":{"tf":1.0},"99":{"tf":1.0}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":2,"docs":{"224":{"tf":1.0},"282":{"tf":1.0}}}}}},"v":{"df":0,"docs":{},"e":{"df":20,"docs":{"103":{"tf":1.0},"105":{"tf":1.0},"11":{"tf":1.4142135623730951},"115":{"tf":1.0},"151":{"tf":1.0},"158":{"tf":1.4142135623730951},"166":{"tf":1.4142135623730951},"167":{"tf":1.4142135623730951},"170":{"tf":1.4142135623730951},"174":{"tf":1.0},"190":{"tf":1.0},"197":{"tf":1.0},"198":{"tf":1.0},"200":{"tf":1.7320508075688772},"218":{"tf":1.0},"225":{"tf":1.4142135623730951},"228":{"tf":1.0},"230":{"tf":1.0},"273":{"tf":1.0},"68":{"tf":1.0}}}},"z":{"_":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"62":{"tf":2.0}},"s":{"\"":{")":{".":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"w":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":1,"docs":{"62":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":4,"docs":{"121":{"tf":1.0},"122":{"tf":1.4142135623730951},"276":{"tf":1.4142135623730951},"277":{"tf":1.0}},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"'":{"df":2,"docs":{"187":{"tf":1.0},"253":{"tf":1.0}}},".":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"$":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"112":{"tf":1.0}}}},"df":0,"docs":{}}},"<":{"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"_":{"c":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"70":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"a":{"c":{"df":1,"docs":{"100":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{".":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"222":{"tf":1.0}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":1,"docs":{"222":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":3,"docs":{"163":{"tf":1.4142135623730951},"166":{"tf":1.0},"31":{"tf":1.0}}},"df":0,"docs":{}}}}}}}}},"df":22,"docs":{"119":{"tf":1.0},"120":{"tf":1.7320508075688772},"122":{"tf":1.4142135623730951},"123":{"tf":2.0},"124":{"tf":2.0},"161":{"tf":1.0},"162":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"2":{"tf":1.0},"201":{"tf":2.0},"258":{"tf":1.0},"260":{"tf":1.4142135623730951},"261":{"tf":1.4142135623730951},"262":{"tf":1.4142135623730951},"263":{"tf":1.0},"274":{"tf":1.7320508075688772},"278":{"tf":1.0},"3":{"tf":1.0},"45":{"tf":1.0},"8":{"tf":1.0}},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"73":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"h":{"df":1,"docs":{"252":{"tf":1.0}}},"x":{"c":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{".":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":2,"docs":{"28":{"tf":1.0},"29":{"tf":1.4142135623730951}}}}}},"df":1,"docs":{"29":{"tf":1.4142135623730951}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"73":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"73":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}}}}}}}}},"’":{"df":3,"docs":{"187":{"tf":1.0},"188":{"tf":1.0},"190":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"p":{"df":0,"docs":{},"l":{"df":3,"docs":{"2":{"tf":1.0},"64":{"tf":1.4142135623730951},"7":{"tf":1.0}}}},"s":{"df":1,"docs":{"204":{"tf":2.8284271247461903}},"g":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":1,"docs":{"106":{"tf":1.0}},"s":{":":{":":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"106":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"u":{"c":{"df":0,"docs":{},"h":{"df":12,"docs":{"103":{"tf":1.0},"104":{"tf":1.0},"171":{"tf":1.0},"175":{"tf":1.0},"231":{"tf":1.0},"238":{"tf":1.4142135623730951},"241":{"tf":1.0},"4":{"tf":1.0},"50":{"tf":1.0},"60":{"tf":1.0},"64":{"tf":1.0},"78":{"tf":1.0}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":4,"docs":{"174":{"tf":1.0},"22":{"tf":1.0},"255":{"tf":1.4142135623730951},"99":{"tf":1.0}},"p":{"df":0,"docs":{},"l":{"df":13,"docs":{"118":{"tf":1.0},"163":{"tf":1.0},"164":{"tf":1.0},"172":{"tf":1.0},"174":{"tf":1.0},"204":{"tf":1.0},"213":{"tf":1.0},"232":{"tf":1.7320508075688772},"237":{"tf":1.0},"252":{"tf":1.0},"255":{"tf":1.0},"276":{"tf":1.0},"98":{"tf":1.0}}}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":1,"docs":{"83":{"tf":1.0}}}}}},"y":{"c":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"60":{"tf":2.23606797749979}}}},"df":0,"docs":{}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"61":{"tf":1.0}}}}}}}},"n":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"242":{"tf":1.0}}}},"m":{"df":0,"docs":{},"e":{"df":35,"docs":{"104":{"tf":1.0},"105":{"tf":1.4142135623730951},"108":{"tf":1.4142135623730951},"109":{"tf":1.4142135623730951},"112":{"tf":2.0},"114":{"tf":1.4142135623730951},"117":{"tf":1.0},"165":{"tf":1.0},"167":{"tf":1.0},"174":{"tf":1.0},"175":{"tf":1.4142135623730951},"200":{"tf":1.4142135623730951},"222":{"tf":1.0},"223":{"tf":1.0},"225":{"tf":1.0},"258":{"tf":1.0},"268":{"tf":1.7320508075688772},"269":{"tf":1.0},"277":{"tf":1.0},"28":{"tf":1.0},"282":{"tf":1.0},"33":{"tf":1.0},"37":{"tf":1.0},"39":{"tf":1.0},"60":{"tf":1.4142135623730951},"61":{"tf":1.0},"69":{"tf":1.0},"70":{"tf":1.4142135623730951},"72":{"tf":1.4142135623730951},"73":{"tf":1.0},"91":{"tf":1.4142135623730951},"93":{"tf":1.7320508075688772},"96":{"tf":1.0},"98":{"tf":1.4142135623730951},"99":{"tf":2.0}},"s":{"df":0,"docs":{},"p":{"a":{"c":{"df":2,"docs":{"112":{"tf":1.0},"172":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"r":{"df":0,"docs":{},"r":{"df":1,"docs":{"286":{"tf":1.0}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":6,"docs":{"0":{"tf":1.0},"11":{"tf":1.7320508075688772},"13":{"tf":1.4142135623730951},"19":{"tf":1.4142135623730951},"37":{"tf":2.23606797749979},"45":{"tf":1.0}},"e":{"/":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"37":{"tf":1.0}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"u":{"df":0,"docs":{},"r":{"df":2,"docs":{"154":{"tf":1.0},"155":{"tf":1.0}}}}},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":9,"docs":{"109":{"tf":1.4142135623730951},"205":{"tf":1.4142135623730951},"28":{"tf":1.0},"283":{"tf":1.0},"31":{"tf":1.0},"33":{"tf":1.0},"36":{"tf":1.0},"40":{"tf":1.0},"73":{"tf":2.449489742783178}}}}}},"d":{"df":0,"docs":{},"k":{"_":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"=":{"\"":{"$":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"k":{"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":0,"docs":{},"s":{"/":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"37":{"tf":1.0}}}}}}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"=":{"\"":{"$":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"/":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"/":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"/":{"df":0,"docs":{},"s":{"d":{"df":0,"docs":{},"k":{"/":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"k":{"/":{"2":{"5":{".":{"2":{".":{"9":{"5":{"1":{"9":{"6":{"5":{"3":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":2,"docs":{"12":{"tf":2.449489742783178},"37":{"tf":1.4142135623730951}}}},"df":2,"docs":{"20":{"tf":1.0},"22":{"tf":1.0}},"e":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":18,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"124":{"tf":1.0},"13":{"tf":1.0},"197":{"tf":1.0},"198":{"tf":1.0},"22":{"tf":1.0},"226":{"tf":1.4142135623730951},"231":{"tf":1.0},"241":{"tf":1.0},"242":{"tf":1.0},"25":{"tf":1.0},"264":{"tf":1.0},"27":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0},"67":{"tf":1.0},"81":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":3,"docs":{"124":{"tf":1.0},"262":{"tf":1.0},"43":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"k":{"df":0,"docs":{},"o":{"df":1,"docs":{"190":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"d":{"df":76,"docs":{"103":{"tf":1.0},"104":{"tf":1.7320508075688772},"105":{"tf":1.7320508075688772},"106":{"tf":1.0},"108":{"tf":1.4142135623730951},"109":{"tf":1.7320508075688772},"11":{"tf":1.0},"111":{"tf":1.0},"115":{"tf":1.4142135623730951},"116":{"tf":1.0},"122":{"tf":1.4142135623730951},"123":{"tf":1.0},"124":{"tf":2.23606797749979},"126":{"tf":1.0},"128":{"tf":1.0},"14":{"tf":1.4142135623730951},"140":{"tf":1.0},"145":{"tf":1.7320508075688772},"165":{"tf":1.0},"167":{"tf":1.4142135623730951},"17":{"tf":1.0},"171":{"tf":1.0},"197":{"tf":2.0},"200":{"tf":1.0},"21":{"tf":1.4142135623730951},"22":{"tf":1.4142135623730951},"220":{"tf":1.7320508075688772},"224":{"tf":1.7320508075688772},"225":{"tf":1.4142135623730951},"226":{"tf":1.0},"227":{"tf":1.0},"228":{"tf":1.0},"229":{"tf":2.23606797749979},"232":{"tf":1.4142135623730951},"233":{"tf":2.0},"234":{"tf":1.4142135623730951},"239":{"tf":1.0},"241":{"tf":1.7320508075688772},"242":{"tf":2.449489742783178},"249":{"tf":1.0},"25":{"tf":1.0},"250":{"tf":1.0},"256":{"tf":1.0},"258":{"tf":1.0},"26":{"tf":1.4142135623730951},"267":{"tf":1.0},"276":{"tf":1.0},"279":{"tf":1.0},"281":{"tf":1.0},"284":{"tf":1.0},"31":{"tf":1.0},"32":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.4142135623730951},"40":{"tf":1.0},"43":{"tf":1.0},"44":{"tf":1.0},"45":{"tf":1.7320508075688772},"46":{"tf":1.7320508075688772},"58":{"tf":1.7320508075688772},"59":{"tf":1.0},"6":{"tf":1.0},"61":{"tf":1.7320508075688772},"62":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"68":{"tf":1.0},"7":{"tf":1.4142135623730951},"73":{"tf":1.7320508075688772},"79":{"tf":1.4142135623730951},"81":{"tf":1.7320508075688772},"84":{"tf":1.7320508075688772},"86":{"tf":1.0},"89":{"tf":1.0},"9":{"tf":1.0},"90":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"g":{"df":3,"docs":{"166":{"tf":1.0},"180":{"tf":1.0},"210":{"tf":1.4142135623730951}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":5,"docs":{"29":{"tf":1.0},"30":{"tf":1.0},"34":{"tf":1.0},"35":{"tf":1.0},"61":{"tf":1.0}}}}},"df":0,"docs":{}}}},"t":{".":{"df":0,"docs":{},"j":{"a":{"df":0,"docs":{},"v":{"a":{".":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{".":{"df":0,"docs":{},"j":{"df":0,"docs":{},"n":{"a":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":9,"docs":{"133":{"tf":1.0},"179":{"tf":1.0},"183":{"tf":1.0},"189":{"tf":1.0},"190":{"tf":1.0},"201":{"tf":1.0},"233":{"tf":2.0},"242":{"tf":1.0},"245":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"242":{"tf":1.0}}}}}}}}}}}},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"l":{"df":3,"docs":{"169":{"tf":1.7320508075688772},"170":{"tf":1.7320508075688772},"171":{"tf":2.23606797749979}}}},"df":0,"docs":{}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"283":{"tf":1.0},"83":{"tf":1.0}}}}},"w":{"df":61,"docs":{"101":{"tf":1.0},"102":{"tf":1.0},"104":{"tf":1.0},"105":{"tf":1.4142135623730951},"108":{"tf":1.7320508075688772},"109":{"tf":1.4142135623730951},"119":{"tf":1.4142135623730951},"120":{"tf":1.0},"122":{"tf":1.0},"123":{"tf":2.0},"124":{"tf":2.0},"134":{"tf":1.0},"141":{"tf":1.0},"145":{"tf":1.4142135623730951},"158":{"tf":1.4142135623730951},"161":{"tf":1.0},"166":{"tf":1.0},"167":{"tf":1.7320508075688772},"170":{"tf":1.0},"171":{"tf":1.7320508075688772},"172":{"tf":1.4142135623730951},"189":{"tf":1.4142135623730951},"196":{"tf":1.7320508075688772},"197":{"tf":1.0},"198":{"tf":1.7320508075688772},"199":{"tf":1.4142135623730951},"20":{"tf":1.0},"200":{"tf":3.3166247903554},"203":{"tf":1.0},"208":{"tf":1.0},"212":{"tf":1.0},"225":{"tf":1.0},"23":{"tf":1.4142135623730951},"233":{"tf":1.4142135623730951},"242":{"tf":1.0},"246":{"tf":1.0},"249":{"tf":1.4142135623730951},"263":{"tf":1.0},"268":{"tf":2.0},"269":{"tf":1.7320508075688772},"274":{"tf":1.4142135623730951},"278":{"tf":1.0},"281":{"tf":1.7320508075688772},"282":{"tf":2.0},"283":{"tf":1.0},"284":{"tf":1.0},"31":{"tf":1.0},"32":{"tf":1.4142135623730951},"36":{"tf":1.0},"41":{"tf":1.0},"45":{"tf":1.0},"6":{"tf":1.0},"60":{"tf":1.4142135623730951},"64":{"tf":2.23606797749979},"68":{"tf":1.7320508075688772},"69":{"tf":2.23606797749979},"7":{"tf":2.23606797749979},"73":{"tf":1.7320508075688772},"75":{"tf":1.0},"81":{"tf":1.4142135623730951},"88":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":7,"docs":{"163":{"tf":1.0},"165":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"245":{"tf":1.0},"263":{"tf":1.0}}}},"l":{"df":0,"docs":{},"i":{"df":2,"docs":{"119":{"tf":1.0},"37":{"tf":1.0}}}}},"x":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"]":{".":{"0":{"a":{"1":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}},"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"_":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":0,"docs":{},"e":{"d":{"_":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"242":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":14,"docs":{"103":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"119":{"tf":1.0},"14":{"tf":1.0},"152":{"tf":1.0},"227":{"tf":1.0},"233":{"tf":1.0},"242":{"tf":1.4142135623730951},"249":{"tf":1.0},"250":{"tf":1.0},"40":{"tf":1.0},"6":{"tf":1.0},"74":{"tf":1.0}}}}},"i":{"c":{"df":0,"docs":{},"e":{"df":2,"docs":{"103":{"tf":1.4142135623730951},"46":{"tf":1.0}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":7,"docs":{"119":{"tf":2.449489742783178},"266":{"tf":2.23606797749979},"271":{"tf":1.7320508075688772},"272":{"tf":2.0},"274":{"tf":1.0},"40":{"tf":1.7320508075688772},"74":{"tf":1.4142135623730951}}},"y":{".":{"df":0,"docs":{},"j":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"271":{"tf":1.0}}}}}},"y":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":1,"docs":{"119":{"tf":1.0}}}}}},"/":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":1,"docs":{"271":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"m":{"b":{"df":0,"docs":{},"u":{"df":17,"docs":{"162":{"tf":1.0},"167":{"tf":1.7320508075688772},"172":{"tf":1.0},"174":{"tf":1.0},"178":{"tf":1.0},"180":{"tf":1.0},"182":{"tf":1.0},"184":{"tf":1.0},"190":{"tf":1.4142135623730951},"194":{"tf":2.0},"197":{"tf":2.0},"198":{"tf":2.23606797749979},"199":{"tf":1.0},"200":{"tf":3.3166247903554},"271":{"tf":1.0},"274":{"tf":1.4142135623730951},"284":{"tf":1.0}},"s":{".":{"df":0,"docs":{},"u":{"d":{"df":0,"docs":{},"l":{"df":1,"docs":{"200":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"n":{"df":0,"docs":{},"j":{"a":{"df":1,"docs":{"11":{"tf":2.0}}},"df":0,"docs":{}}}},"o":{"d":{"df":0,"docs":{},"e":{"df":1,"docs":{"86":{"tf":1.0}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"75":{"tf":1.0}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"123":{"tf":1.0}}}}},"n":{"df":16,"docs":{"122":{"tf":1.0},"13":{"tf":1.0},"167":{"tf":1.0},"184":{"tf":1.0},"188":{"tf":1.0},"189":{"tf":1.0},"200":{"tf":1.0},"201":{"tf":2.0},"226":{"tf":1.4142135623730951},"241":{"tf":1.0},"250":{"tf":1.0},"40":{"tf":1.4142135623730951},"45":{"tf":1.0},"46":{"tf":1.0},"58":{"tf":1.4142135623730951},"84":{"tf":1.4142135623730951}},"e":{"df":3,"docs":{"180":{"tf":1.0},"233":{"tf":1.0},"242":{"tf":1.4142135623730951}}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"df":1,"docs":{"116":{"tf":1.0}}}}}}}},"r":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"l":{"df":12,"docs":{"104":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"125":{"tf":1.0},"133":{"tf":1.0},"156":{"tf":1.0},"19":{"tf":1.0},"200":{"tf":1.0},"205":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"64":{"tf":1.0}}}},"df":2,"docs":{"200":{"tf":1.0},"201":{"tf":1.0}}}},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":2,"docs":{"18":{"tf":1.0},"201":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":42,"docs":{"105":{"tf":1.0},"106":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"11":{"tf":1.4142135623730951},"115":{"tf":1.0},"117":{"tf":1.0},"12":{"tf":1.0},"123":{"tf":1.0},"124":{"tf":1.4142135623730951},"13":{"tf":1.0},"14":{"tf":1.4142135623730951},"145":{"tf":1.4142135623730951},"18":{"tf":1.0},"198":{"tf":1.0},"20":{"tf":1.0},"200":{"tf":1.4142135623730951},"205":{"tf":1.0},"208":{"tf":1.0},"214":{"tf":1.0},"215":{"tf":1.0},"226":{"tf":1.4142135623730951},"231":{"tf":1.7320508075688772},"242":{"tf":1.4142135623730951},"243":{"tf":1.0},"245":{"tf":1.0},"258":{"tf":1.0},"264":{"tf":1.4142135623730951},"27":{"tf":1.0},"274":{"tf":1.0},"278":{"tf":1.0},"280":{"tf":1.0},"282":{"tf":1.4142135623730951},"31":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"62":{"tf":1.4142135623730951},"7":{"tf":1.0},"78":{"tf":1.0},"79":{"tf":1.4142135623730951},"81":{"tf":1.0},"83":{"tf":1.0}}},"h":{"df":7,"docs":{"163":{"tf":1.0},"169":{"tf":1.0},"179":{"tf":1.0},"180":{"tf":1.0},"182":{"tf":1.0},"62":{"tf":1.0},"73":{"tf":1.4142135623730951}}},"i":{"c":{"df":3,"docs":{"166":{"tf":1.0},"274":{"tf":1.0},"282":{"tf":1.0}}},"df":0,"docs":{},"f":{"df":1,"docs":{"274":{"tf":1.0}}}}},"w":{"df":19,"docs":{"105":{"tf":1.4142135623730951},"142":{"tf":1.0},"152":{"tf":1.0},"180":{"tf":1.0},"20":{"tf":1.0},"212":{"tf":1.0},"226":{"tf":1.7320508075688772},"233":{"tf":1.0},"241":{"tf":1.0},"242":{"tf":1.0},"257":{"tf":1.4142135623730951},"31":{"tf":1.0},"36":{"tf":1.0},"40":{"tf":1.0},"60":{"tf":1.4142135623730951},"62":{"tf":1.4142135623730951},"74":{"tf":1.0},"86":{"tf":1.0},"90":{"tf":1.0}}}},"s":{"df":0,"docs":{},"k":{"df":1,"docs":{"37":{"tf":1.0}}},"p":{"df":0,"docs":{},"r":{"df":1,"docs":{"67":{"tf":1.0}}}},"s":{"_":{"3":{"_":{"9":{"0":{"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"m":{"df":1,"docs":{"280":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"282":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"_":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"282":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":1,"docs":{"281":{"tf":1.0}}}}},"df":7,"docs":{"11":{"tf":1.7320508075688772},"278":{"tf":2.449489742783178},"279":{"tf":2.449489742783178},"280":{"tf":2.449489742783178},"281":{"tf":1.4142135623730951},"282":{"tf":3.1622776601683795},"67":{"tf":1.0}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":1,"docs":{"62":{"tf":2.8284271247461903}}}},"m":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":26,"docs":{"10":{"tf":1.4142135623730951},"115":{"tf":1.0},"13":{"tf":1.0},"161":{"tf":1.0},"193":{"tf":1.0},"197":{"tf":1.0},"20":{"tf":1.4142135623730951},"204":{"tf":1.0},"205":{"tf":1.0},"207":{"tf":1.0},"208":{"tf":1.4142135623730951},"213":{"tf":1.0},"214":{"tf":1.0},"215":{"tf":1.0},"216":{"tf":1.0},"22":{"tf":1.7320508075688772},"220":{"tf":1.0},"222":{"tf":1.0},"225":{"tf":1.4142135623730951},"231":{"tf":1.0},"268":{"tf":1.0},"269":{"tf":1.0},"270":{"tf":1.0},"271":{"tf":1.0},"279":{"tf":1.0},"37":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"22":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"64":{"tf":1.0}}}}}}}},"y":{"df":0,"docs":{},"g":{"a":{"df":0,"docs":{},"r":{"d":{"'":{"df":1,"docs":{"137":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"o":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":2,"docs":{"254":{"tf":1.0},"283":{"tf":2.8284271247461903}}}}}},"b":{"df":0,"docs":{},"j":{".":{"d":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"129":{"tf":1.0}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"s":{"df":1,"docs":{"129":{"tf":1.0}}}}}}}}}}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"129":{"tf":1.0}},"e":{"c":{"df":0,"docs":{},"t":{"df":9,"docs":{"128":{"tf":2.449489742783178},"129":{"tf":1.4142135623730951},"130":{"tf":1.4142135623730951},"131":{"tf":2.0},"132":{"tf":2.0},"245":{"tf":1.0},"268":{"tf":1.0},"283":{"tf":1.4142135623730951},"99":{"tf":1.0}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":9,"docs":{"203":{"tf":1.0},"204":{"tf":1.4142135623730951},"205":{"tf":1.0},"206":{"tf":1.0},"210":{"tf":1.0},"214":{"tf":1.7320508075688772},"215":{"tf":1.0},"216":{"tf":1.7320508075688772},"227":{"tf":1.0}}}}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":5,"docs":{"161":{"tf":1.0},"2":{"tf":1.0},"228":{"tf":1.0},"277":{"tf":1.0},"283":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":2,"docs":{"201":{"tf":1.0},"225":{"tf":1.0}},"s":{"df":2,"docs":{"124":{"tf":1.0},"201":{"tf":1.0}}}}}}}},"c":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":3,"docs":{"207":{"tf":1.0},"226":{"tf":1.0},"43":{"tf":1.0}},"r":{"df":2,"docs":{"31":{"tf":1.0},"36":{"tf":1.0}}}}}},"df":0,"docs":{}},"d":{"d":{"df":2,"docs":{"200":{"tf":1.0},"221":{"tf":1.0}}},"df":0,"docs":{}},"df":3,"docs":{"11":{"tf":1.0},"29":{"tf":1.0},"34":{"tf":1.0}},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":3,"docs":{"201":{"tf":1.0},"274":{"tf":1.0},"283":{"tf":1.0}}}},"i":{"c":{"df":0,"docs":{},"i":{"df":3,"docs":{"261":{"tf":1.0},"262":{"tf":1.0},"263":{"tf":1.0}}}},"df":0,"docs":{}}}},"k":{"a":{"df":0,"docs":{},"y":{"df":2,"docs":{"130":{"tf":1.0},"82":{"tf":1.0}}}},"df":2,"docs":{"226":{"tf":1.0},"242":{"tf":1.0}}},"l":{"d":{"df":9,"docs":{"109":{"tf":1.0},"203":{"tf":1.0},"209":{"tf":1.0},"212":{"tf":1.4142135623730951},"231":{"tf":1.0},"258":{"tf":1.0},"279":{"tf":1.0},"37":{"tf":1.0},"86":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":5,"docs":{"210":{"tf":1.0},"225":{"tf":1.0},"245":{"tf":1.0},"78":{"tf":1.0},"79":{"tf":1.0}}}}},"df":0,"docs":{}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"145":{"tf":1.0},"21":{"tf":1.0}}}}},"n":{"b":{"df":0,"docs":{},"o":{"a":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"184":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"c":{"df":18,"docs":{"106":{"tf":1.0},"11":{"tf":1.0},"119":{"tf":1.4142135623730951},"124":{"tf":1.0},"128":{"tf":1.0},"132":{"tf":1.0},"14":{"tf":1.0},"145":{"tf":1.0},"25":{"tf":1.0},"258":{"tf":1.0},"268":{"tf":1.0},"270":{"tf":1.0},"277":{"tf":1.0},"30":{"tf":1.0},"35":{"tf":1.0},"6":{"tf":1.0},"62":{"tf":1.0},"64":{"tf":1.0}}},"df":34,"docs":{"106":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"116":{"tf":1.0},"122":{"tf":1.0},"125":{"tf":1.0},"126":{"tf":1.0},"131":{"tf":1.4142135623730951},"132":{"tf":1.0},"138":{"tf":1.0},"163":{"tf":1.7320508075688772},"167":{"tf":1.0},"171":{"tf":1.0},"196":{"tf":1.0},"203":{"tf":1.0},"212":{"tf":1.0},"228":{"tf":1.0},"229":{"tf":1.0},"232":{"tf":1.0},"236":{"tf":1.0},"249":{"tf":1.0},"257":{"tf":1.0},"258":{"tf":1.4142135623730951},"274":{"tf":1.0},"276":{"tf":1.0},"282":{"tf":1.0},"45":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.7320508075688772},"7":{"tf":1.0},"79":{"tf":1.4142135623730951},"82":{"tf":1.4142135623730951},"86":{"tf":1.0}},"g":{"df":0,"docs":{},"o":{"df":1,"docs":{"64":{"tf":1.0}}}},"t":{"df":0,"docs":{},"o":{"df":1,"docs":{"115":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"m":{"df":1,"docs":{"154":{"tf":1.0}}}},"p":{"a":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":2,"docs":{"229":{"tf":1.0},"242":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":20,"docs":{"107":{"tf":1.0},"109":{"tf":1.0},"111":{"tf":1.0},"124":{"tf":1.0},"151":{"tf":1.4142135623730951},"152":{"tf":1.4142135623730951},"154":{"tf":1.7320508075688772},"155":{"tf":1.7320508075688772},"156":{"tf":1.0},"158":{"tf":1.4142135623730951},"25":{"tf":1.0},"274":{"tf":1.4142135623730951},"280":{"tf":1.0},"286":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"40":{"tf":1.0},"7":{"tf":1.4142135623730951},"73":{"tf":1.0},"81":{"tf":1.0}},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"l":{"df":1,"docs":{"11":{"tf":1.0}}}}}},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"f":{"df":0,"docs":{},"x":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":1,"docs":{"283":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"df":7,"docs":{"151":{"tf":1.0},"152":{"tf":1.0},"156":{"tf":1.0},"201":{"tf":1.4142135623730951},"225":{"tf":1.0},"245":{"tf":1.0},"37":{"tf":1.0}}}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":1,"docs":{"170":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"233":{"tf":1.0}}}}}}},"t":{"df":3,"docs":{"233":{"tf":1.4142135623730951},"280":{"tf":1.0},"283":{"tf":1.0}},"i":{"df":0,"docs":{},"m":{"df":2,"docs":{"220":{"tf":1.0},"282":{"tf":1.0}}},"o":{"df":0,"docs":{},"n":{"<":{"df":0,"docs":{},"j":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":1,"docs":{"242":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"242":{"tf":1.0}}}}},"v":{"df":0,"docs":{},"e":{"c":{"<":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"242":{"tf":1.4142135623730951}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":36,"docs":{"103":{"tf":1.4142135623730951},"137":{"tf":1.0},"138":{"tf":1.0},"142":{"tf":1.7320508075688772},"143":{"tf":1.0},"144":{"tf":1.0},"145":{"tf":1.0},"146":{"tf":1.4142135623730951},"151":{"tf":1.0},"153":{"tf":1.0},"158":{"tf":1.0},"159":{"tf":1.0},"163":{"tf":1.0},"164":{"tf":1.4142135623730951},"168":{"tf":1.0},"169":{"tf":1.4142135623730951},"170":{"tf":1.4142135623730951},"171":{"tf":1.4142135623730951},"172":{"tf":1.7320508075688772},"174":{"tf":1.4142135623730951},"179":{"tf":1.0},"180":{"tf":2.23606797749979},"181":{"tf":1.0},"185":{"tf":1.0},"187":{"tf":1.0},"195":{"tf":1.0},"196":{"tf":2.0},"197":{"tf":1.4142135623730951},"198":{"tf":2.0},"199":{"tf":1.4142135623730951},"207":{"tf":2.0},"208":{"tf":1.4142135623730951},"211":{"tf":1.0},"249":{"tf":1.0},"73":{"tf":1.0},"74":{"tf":1.4142135623730951}}}}}}},"r":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":14,"docs":{"105":{"tf":1.0},"106":{"tf":1.0},"115":{"tf":1.0},"170":{"tf":1.4142135623730951},"22":{"tf":1.4142135623730951},"223":{"tf":1.0},"224":{"tf":1.0},"246":{"tf":1.0},"282":{"tf":1.0},"286":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0},"59":{"tf":1.0},"69":{"tf":1.0}}}}},"df":0,"docs":{},"g":{".":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{".":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"$":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"112":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":1,"docs":{"274":{"tf":1.4142135623730951}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":2,"docs":{"115":{"tf":1.0},"231":{"tf":1.0}}}}}}},"s":{"df":1,"docs":{"133":{"tf":1.0}},"x":{"df":1,"docs":{"17":{"tf":1.4142135623730951}}}},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"'":{"df":1,"docs":{"175":{"tf":1.0}}},"df":2,"docs":{"116":{"tf":1.0},"226":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"242":{"tf":1.0}}}}}}},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":4,"docs":{"224":{"tf":1.4142135623730951},"226":{"tf":1.0},"88":{"tf":1.0},"98":{"tf":1.0}}}}}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"v":{"df":3,"docs":{"166":{"tf":1.0},"170":{"tf":1.0},"278":{"tf":1.0}}}}}}},"t":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":6,"docs":{"138":{"tf":1.0},"143":{"tf":1.0},"152":{"tf":1.0},"164":{"tf":1.0},"180":{"tf":1.0},"208":{"tf":1.0}}}}},"df":30,"docs":{"103":{"tf":1.0},"105":{"tf":1.0},"107":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"116":{"tf":1.0},"171":{"tf":1.0},"18":{"tf":1.0},"190":{"tf":1.0},"198":{"tf":1.0},"200":{"tf":1.4142135623730951},"201":{"tf":1.0},"204":{"tf":1.4142135623730951},"233":{"tf":1.4142135623730951},"249":{"tf":1.0},"252":{"tf":1.0},"274":{"tf":1.7320508075688772},"279":{"tf":1.4142135623730951},"283":{"tf":1.4142135623730951},"29":{"tf":1.0},"31":{"tf":1.0},"34":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"43":{"tf":1.0},"59":{"tf":1.0},"6":{"tf":1.0},"60":{"tf":1.0},"81":{"tf":1.0},"96":{"tf":1.0}},"g":{"df":0,"docs":{},"o":{"df":2,"docs":{"231":{"tf":1.0},"249":{"tf":1.0}}}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":3,"docs":{"187":{"tf":1.0},"233":{"tf":1.0},"91":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":6,"docs":{"174":{"tf":1.0},"252":{"tf":1.0},"280":{"tf":1.0},"282":{"tf":1.0},"37":{"tf":1.0},"62":{"tf":2.0}}}}},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":1,"docs":{"61":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"156":{"tf":1.0}}},"df":0,"docs":{}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"l":{"df":7,"docs":{"102":{"tf":1.0},"105":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"218":{"tf":1.0},"222":{"tf":1.0},"244":{"tf":1.0}}}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":1,"docs":{"175":{"tf":1.0}}}}},"df":21,"docs":{"101":{"tf":1.0},"103":{"tf":1.0},"104":{"tf":1.0},"105":{"tf":1.0},"106":{"tf":1.7320508075688772},"11":{"tf":1.0},"116":{"tf":1.0},"126":{"tf":1.0},"140":{"tf":1.0},"172":{"tf":1.0},"193":{"tf":1.0},"204":{"tf":1.4142135623730951},"208":{"tf":1.0},"212":{"tf":1.0},"214":{"tf":1.0},"215":{"tf":1.0},"216":{"tf":1.4142135623730951},"217":{"tf":1.4142135623730951},"228":{"tf":1.0},"274":{"tf":1.0},"43":{"tf":1.0}},"h":{"df":0,"docs":{},"e":{"a":{"d":{"df":2,"docs":{"115":{"tf":1.0},"59":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"l":{"a":{"df":0,"docs":{},"p":{"df":1,"docs":{"198":{"tf":1.0}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":1,"docs":{"46":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"i":{"d":{"df":3,"docs":{"123":{"tf":1.0},"124":{"tf":1.0},"22":{"tf":1.0}}},"df":0,"docs":{}}},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":9,"docs":{"174":{"tf":1.0},"219":{"tf":1.4142135623730951},"227":{"tf":1.0},"243":{"tf":1.4142135623730951},"274":{"tf":1.0},"41":{"tf":1.0},"93":{"tf":1.0},"96":{"tf":1.0},"99":{"tf":1.0}}}}}},"w":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"179":{"tf":1.0}}}}}}}}},"w":{"df":0,"docs":{},"n":{"df":3,"docs":{"201":{"tf":1.0},"276":{"tf":1.4142135623730951},"277":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"121":{"tf":1.0},"276":{"tf":1.0}},"s":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":1,"docs":{"276":{"tf":1.0}}}}}}}}}}},"p":{"a":{"c":{"df":0,"docs":{},"k":{"a":{"df":0,"docs":{},"g":{"df":39,"docs":{"100":{"tf":1.0},"103":{"tf":1.0},"108":{"tf":1.0},"112":{"tf":1.7320508075688772},"113":{"tf":1.0},"12":{"tf":1.0},"134":{"tf":1.0},"160":{"tf":1.0},"163":{"tf":2.0},"164":{"tf":1.0},"165":{"tf":1.0},"166":{"tf":1.0},"167":{"tf":2.449489742783178},"169":{"tf":1.0},"170":{"tf":1.4142135623730951},"171":{"tf":2.449489742783178},"172":{"tf":2.6457513110645907},"174":{"tf":2.6457513110645907},"219":{"tf":1.4142135623730951},"220":{"tf":1.0},"222":{"tf":1.0},"225":{"tf":1.0},"23":{"tf":1.7320508075688772},"25":{"tf":3.0},"251":{"tf":1.7320508075688772},"253":{"tf":2.6457513110645907},"26":{"tf":2.0},"269":{"tf":1.0},"271":{"tf":1.0},"274":{"tf":1.4142135623730951},"31":{"tf":2.449489742783178},"32":{"tf":1.7320508075688772},"36":{"tf":2.449489742783178},"37":{"tf":1.0},"45":{"tf":1.0},"53":{"tf":1.0},"73":{"tf":1.7320508075688772},"74":{"tf":1.0},"99":{"tf":1.0}},"e":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"'":{"df":2,"docs":{"29":{"tf":1.0},"34":{"tf":1.0}}},"df":3,"docs":{"167":{"tf":1.7320508075688772},"251":{"tf":1.0},"253":{"tf":1.4142135623730951}}}}}}}},"=":{"\"":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{".":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{".":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"<":{"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"_":{"c":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"70":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"70":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"117":{"tf":1.0}}}}}}}}}},"df":0,"docs":{}}},"d":{"df":1,"docs":{"64":{"tf":1.0}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"s":{"df":1,"docs":{"264":{"tf":1.0}}}},"df":43,"docs":{"100":{"tf":1.0},"109":{"tf":1.0},"117":{"tf":1.0},"119":{"tf":1.0},"124":{"tf":1.0},"127":{"tf":1.0},"133":{"tf":1.0},"138":{"tf":1.0},"147":{"tf":1.0},"15":{"tf":1.0},"159":{"tf":1.0},"175":{"tf":1.0},"185":{"tf":1.0},"201":{"tf":1.0},"218":{"tf":1.0},"22":{"tf":1.0},"224":{"tf":1.0},"242":{"tf":1.0},"250":{"tf":1.0},"253":{"tf":1.0},"258":{"tf":1.0},"259":{"tf":1.0},"261":{"tf":1.0},"263":{"tf":1.0},"264":{"tf":1.0},"273":{"tf":1.0},"277":{"tf":1.0},"282":{"tf":1.0},"283":{"tf":1.0},"286":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"40":{"tf":1.0},"5":{"tf":1.0},"51":{"tf":1.0},"56":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0},"67":{"tf":1.0},"74":{"tf":1.0},"8":{"tf":1.0},"90":{"tf":1.0}}}},"i":{"df":0,"docs":{},"n":{"df":4,"docs":{"104":{"tf":1.0},"115":{"tf":1.0},"122":{"tf":1.0},"5":{"tf":1.0}}}},"n":{"df":0,"docs":{},"e":{"df":1,"docs":{"126":{"tf":1.4142135623730951}},"l":{"df":2,"docs":{"205":{"tf":1.4142135623730951},"206":{"tf":1.0}}}},"i":{"c":{"df":1,"docs":{"117":{"tf":1.0}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"6":{"tf":1.0}}}}},"df":0,"docs":{}}}},"r":{"a":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"m":{"df":1,"docs":{"61":{"tf":1.0}}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":1,"docs":{"59":{"tf":1.0}}}}}},"m":{"df":2,"docs":{"242":{"tf":1.4142135623730951},"79":{"tf":1.0}},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"283":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"61":{"tf":1.7320508075688772},"62":{"tf":1.0}}}}},"s":{"df":1,"docs":{"22":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"58":{"tf":1.0}}}}},"t":{"df":28,"docs":{"103":{"tf":1.4142135623730951},"104":{"tf":1.0},"106":{"tf":1.0},"108":{"tf":1.4142135623730951},"109":{"tf":1.4142135623730951},"113":{"tf":1.0},"114":{"tf":1.0},"161":{"tf":1.0},"165":{"tf":1.0},"171":{"tf":1.0},"190":{"tf":1.0},"200":{"tf":1.4142135623730951},"201":{"tf":1.0},"203":{"tf":1.0},"214":{"tf":1.0},"22":{"tf":1.0},"225":{"tf":1.0},"233":{"tf":1.0},"242":{"tf":1.0},"245":{"tf":1.4142135623730951},"251":{"tf":1.0},"268":{"tf":1.0},"274":{"tf":1.0},"64":{"tf":1.7320508075688772},"68":{"tf":1.4142135623730951},"79":{"tf":1.0},"80":{"tf":1.0},"84":{"tf":1.0}},"i":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":1,"docs":{"3":{"tf":1.4142135623730951}}}},"u":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"r":{"df":9,"docs":{"116":{"tf":1.0},"198":{"tf":1.0},"200":{"tf":1.0},"225":{"tf":1.0},"231":{"tf":1.0},"233":{"tf":1.4142135623730951},"236":{"tf":1.4142135623730951},"39":{"tf":1.0},"64":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"213":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":4,"docs":{"121":{"tf":1.0},"201":{"tf":1.7320508075688772},"63":{"tf":1.0},"64":{"tf":1.7320508075688772}}}}},"s":{"df":0,"docs":{},"s":{"df":20,"docs":{"105":{"tf":1.0},"106":{"tf":1.7320508075688772},"108":{"tf":1.0},"109":{"tf":1.0},"132":{"tf":1.0},"14":{"tf":1.0},"142":{"tf":1.4142135623730951},"143":{"tf":1.0},"145":{"tf":1.7320508075688772},"225":{"tf":1.0},"228":{"tf":1.0},"229":{"tf":1.0},"242":{"tf":1.7320508075688772},"245":{"tf":1.4142135623730951},"262":{"tf":1.4142135623730951},"283":{"tf":1.4142135623730951},"58":{"tf":1.0},"7":{"tf":1.4142135623730951},"73":{"tf":1.0},"79":{"tf":2.0}},"i":{"df":0,"docs":{},"v":{"df":1,"docs":{"264":{"tf":1.0}}}},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"d":{"df":3,"docs":{"242":{"tf":1.0},"257":{"tf":1.0},"86":{"tf":1.0}}},"df":0,"docs":{}}}}},"t":{"df":2,"docs":{"205":{"tf":1.0},"207":{"tf":1.0}}}},"t":{"c":{"df":0,"docs":{},"h":{"df":9,"docs":{"11":{"tf":1.4142135623730951},"122":{"tf":1.4142135623730951},"123":{"tf":1.0},"124":{"tf":1.0},"262":{"tf":1.0},"270":{"tf":1.0},"62":{"tf":1.0},"7":{"tf":2.449489742783178},"8":{"tf":1.0}}}},"df":0,"docs":{},"h":{"/":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"/":{"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"/":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"/":{"df":0,"docs":{},"o":{"df":0,"docs":{},"f":{"/":{"df":0,"docs":{},"j":{"df":0,"docs":{},"n":{"a":{"/":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"37":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"=":{"\"":{"$":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{":":{"$":{"(":{"b":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":1,"docs":{"11":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"k":{"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"/":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"/":{"a":{"a":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"h":{"6":{"4":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":1,"docs":{"37":{"tf":1.0}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"v":{"df":0,"docs":{},"m":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"/":{"d":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"37":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"x":{"8":{"6":{"_":{"6":{"4":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{},"~":{"/":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"g":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{":":{"$":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"11":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":19,"docs":{"11":{"tf":1.4142135623730951},"122":{"tf":1.0},"123":{"tf":1.0},"124":{"tf":2.6457513110645907},"166":{"tf":1.0},"18":{"tf":1.4142135623730951},"192":{"tf":1.0},"22":{"tf":1.0},"25":{"tf":1.0},"280":{"tf":1.0},"29":{"tf":1.0},"31":{"tf":1.4142135623730951},"34":{"tf":1.0},"36":{"tf":1.4142135623730951},"37":{"tf":1.4142135623730951},"45":{"tf":1.0},"60":{"tf":1.4142135623730951},"62":{"tf":1.4142135623730951},"74":{"tf":1.4142135623730951}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":1,"docs":{"247":{"tf":1.4142135623730951}}}}}}},"u":{"df":0,"docs":{},"s":{"df":2,"docs":{"180":{"tf":1.0},"183":{"tf":1.0}}}},"y":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":1,"docs":{"283":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":2,"docs":{"58":{"tf":1.7320508075688772},"69":{"tf":1.0}},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"122":{"tf":1.0},"8":{"tf":1.0}}}},"n":{"d":{"df":1,"docs":{"21":{"tf":1.0}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":2,"docs":{"138":{"tf":1.0},"60":{"tf":1.0}}}}},"r":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":2,"docs":{"170":{"tf":1.0},"242":{"tf":1.0}}}},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"62":{"tf":1.0}},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"204":{"tf":3.0}}}}}}}},"df":9,"docs":{"140":{"tf":1.4142135623730951},"145":{"tf":1.4142135623730951},"231":{"tf":1.0},"232":{"tf":1.0},"245":{"tf":1.0},"252":{"tf":1.0},"40":{"tf":1.0},"61":{"tf":1.4142135623730951},"64":{"tf":1.0}},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":17,"docs":{"111":{"tf":1.0},"145":{"tf":1.4142135623730951},"16":{"tf":1.0},"187":{"tf":1.0},"205":{"tf":1.4142135623730951},"209":{"tf":1.0},"212":{"tf":1.0},"218":{"tf":1.0},"225":{"tf":1.0},"226":{"tf":1.0},"245":{"tf":1.4142135623730951},"249":{"tf":1.0},"268":{"tf":1.0},"278":{"tf":1.0},"282":{"tf":1.0},"58":{"tf":1.0},"81":{"tf":1.0}}}}}},"h":{"a":{"df":0,"docs":{},"p":{"df":3,"docs":{"103":{"tf":1.0},"104":{"tf":1.0},"115":{"tf":1.0}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"o":{"d":{"df":3,"docs":{"145":{"tf":1.0},"226":{"tf":1.0},"245":{"tf":1.0}}},"df":0,"docs":{}}},"l":{"df":1,"docs":{"11":{"tf":1.4142135623730951}}},"m":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"273":{"tf":1.0}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"274":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":12,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"149":{"tf":1.0},"150":{"tf":1.0},"194":{"tf":1.4142135623730951},"197":{"tf":1.7320508075688772},"198":{"tf":1.0},"229":{"tf":2.0},"242":{"tf":1.4142135623730951},"249":{"tf":1.0},"283":{"tf":1.4142135623730951},"82":{"tf":1.0}},"e":{"d":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"242":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"x":{"a":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"108":{"tf":1.0},"109":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}}},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"277":{"tf":1.4142135623730951}}}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"284":{"tf":1.0}}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"s":{"df":1,"docs":{"102":{"tf":1.0}}}}}},"h":{"a":{"b":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"c":{"df":3,"docs":{"122":{"tf":1.0},"123":{"tf":1.0},"124":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":5,"docs":{"22":{"tf":1.0},"267":{"tf":1.4142135623730951},"273":{"tf":1.0},"60":{"tf":1.0},"73":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}},"i":{"c":{"df":0,"docs":{},"k":{"df":5,"docs":{"270":{"tf":1.0},"272":{"tf":1.0},"30":{"tf":1.0},"35":{"tf":1.0},"6":{"tf":1.0}}}},"df":0,"docs":{},"e":{"c":{"df":3,"docs":{"105":{"tf":1.0},"116":{"tf":1.0},"60":{"tf":1.0}}},"df":0,"docs":{}},"n":{"df":2,"docs":{"263":{"tf":1.0},"274":{"tf":1.0}}},"p":{"3":{"df":2,"docs":{"269":{"tf":1.0},"53":{"tf":1.0}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":5,"docs":{"113":{"tf":1.0},"221":{"tf":1.0},"226":{"tf":1.0},"274":{"tf":2.23606797749979},"70":{"tf":1.0}}}}}}}},"l":{"a":{"c":{"df":0,"docs":{},"e":{"df":23,"docs":{"106":{"tf":1.4142135623730951},"108":{"tf":1.0},"134":{"tf":1.0},"142":{"tf":1.0},"202":{"tf":1.0},"218":{"tf":1.0},"221":{"tf":1.0},"232":{"tf":1.4142135623730951},"237":{"tf":1.0},"244":{"tf":1.0},"245":{"tf":1.0},"264":{"tf":2.8284271247461903},"274":{"tf":1.0},"48":{"tf":1.0},"58":{"tf":1.4142135623730951},"61":{"tf":1.4142135623730951},"62":{"tf":1.0},"70":{"tf":1.4142135623730951},"72":{"tf":1.0},"78":{"tf":1.4142135623730951},"79":{"tf":1.4142135623730951},"81":{"tf":1.0},"83":{"tf":1.0}},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"58":{"tf":1.0}}}}}},"s":{".":{"d":{"b":{"df":1,"docs":{"218":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"62":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"e":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"130":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"n":{"df":8,"docs":{"140":{"tf":1.0},"152":{"tf":1.0},"180":{"tf":1.0},"200":{"tf":1.0},"228":{"tf":1.0},"230":{"tf":1.0},"249":{"tf":2.23606797749979},"274":{"tf":1.0}}},"t":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":22,"docs":{"0":{"tf":1.0},"116":{"tf":1.7320508075688772},"13":{"tf":1.0},"170":{"tf":1.0},"184":{"tf":1.0},"196":{"tf":1.0},"200":{"tf":1.0},"213":{"tf":1.0},"220":{"tf":1.0},"223":{"tf":1.0},"225":{"tf":1.0},"251":{"tf":1.0},"255":{"tf":1.7320508075688772},"256":{"tf":1.4142135623730951},"257":{"tf":1.0},"258":{"tf":1.0},"274":{"tf":1.0},"279":{"tf":1.0},"28":{"tf":1.0},"33":{"tf":1.0},"37":{"tf":1.4142135623730951},"69":{"tf":1.0}},"s":{";":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"13":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":11,"docs":{"111":{"tf":1.0},"120":{"tf":1.0},"134":{"tf":1.0},"200":{"tf":1.4142135623730951},"4":{"tf":1.0},"41":{"tf":1.0},"5":{"tf":1.0},"69":{"tf":1.0},"7":{"tf":1.4142135623730951},"75":{"tf":1.4142135623730951},"81":{"tf":1.0}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"252":{"tf":1.0}}}}},"u":{"df":3,"docs":{"174":{"tf":1.0},"233":{"tf":1.0},"98":{"tf":1.0}},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"65":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":18,"docs":{"104":{"tf":1.7320508075688772},"105":{"tf":1.0},"12":{"tf":1.0},"171":{"tf":1.4142135623730951},"198":{"tf":1.0},"201":{"tf":1.0},"226":{"tf":1.0},"241":{"tf":1.7320508075688772},"260":{"tf":1.0},"274":{"tf":1.4142135623730951},"29":{"tf":1.0},"34":{"tf":1.0},"37":{"tf":1.0},"5":{"tf":1.0},"51":{"tf":1.0},"64":{"tf":1.0},"73":{"tf":1.4142135623730951},"78":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"116":{"tf":1.0},"128":{"tf":1.4142135623730951}}}}}}},"l":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":6,"docs":{"121":{"tf":1.0},"219":{"tf":1.0},"226":{"tf":1.0},"261":{"tf":2.0},"262":{"tf":2.0},"63":{"tf":1.0}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":6,"docs":{"145":{"tf":1.0},"184":{"tf":1.0},"214":{"tf":1.0},"215":{"tf":1.0},"226":{"tf":1.0},"233":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"t":{"df":4,"docs":{"101":{"tf":1.4142135623730951},"102":{"tf":1.0},"104":{"tf":1.0},"228":{"tf":1.4142135623730951}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"226":{"tf":1.4142135623730951}}}}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":3,"docs":{"165":{"tf":1.0},"209":{"tf":1.0},"62":{"tf":1.0}}}},"s":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"l":{"df":32,"docs":{"105":{"tf":1.0},"106":{"tf":1.0},"11":{"tf":1.0},"116":{"tf":1.0},"121":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0},"158":{"tf":1.0},"16":{"tf":1.0},"165":{"tf":1.0},"170":{"tf":1.0},"175":{"tf":1.0},"180":{"tf":1.0},"192":{"tf":1.0},"196":{"tf":1.0},"200":{"tf":1.0},"225":{"tf":1.0},"226":{"tf":1.0},"227":{"tf":1.0},"236":{"tf":1.0},"237":{"tf":1.0},"242":{"tf":1.0},"25":{"tf":1.0},"263":{"tf":1.0},"27":{"tf":1.0},"278":{"tf":1.0},"43":{"tf":1.4142135623730951},"59":{"tf":1.0},"7":{"tf":1.0},"80":{"tf":1.0},"83":{"tf":1.0},"88":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"df":2,"docs":{"249":{"tf":1.0},"90":{"tf":1.4142135623730951}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"l":{"_":{"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"_":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"_":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"145":{"tf":1.4142135623730951},"146":{"tf":1.0}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":8,"docs":{"159":{"tf":1.0},"164":{"tf":1.0},"165":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"59":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0}}}}}}},"v":{"df":1,"docs":{"226":{"tf":1.0}}},"w":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"203":{"tf":1.0},"205":{"tf":1.7320508075688772}}}}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"c":{"df":14,"docs":{"101":{"tf":1.0},"116":{"tf":1.0},"123":{"tf":1.4142135623730951},"128":{"tf":1.0},"140":{"tf":1.0},"170":{"tf":1.0},"226":{"tf":1.0},"232":{"tf":1.0},"274":{"tf":1.0},"276":{"tf":1.0},"43":{"tf":1.0},"45":{"tf":1.0},"75":{"tf":1.0},"8":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"m":{"a":{"df":2,"docs":{"264":{"tf":2.8284271247461903},"81":{"tf":1.0}}},"df":0,"docs":{}}}},"df":15,"docs":{"118":{"tf":1.7320508075688772},"119":{"tf":2.6457513110645907},"124":{"tf":1.0},"21":{"tf":1.4142135623730951},"263":{"tf":1.4142135623730951},"268":{"tf":2.6457513110645907},"269":{"tf":1.4142135623730951},"270":{"tf":1.4142135623730951},"274":{"tf":1.4142135623730951},"280":{"tf":1.0},"282":{"tf":1.0},"39":{"tf":1.7320508075688772},"6":{"tf":1.0},"64":{"tf":1.4142135623730951},"7":{"tf":3.1622776601683795}},"e":{"c":{"df":0,"docs":{},"e":{"d":{"df":2,"docs":{"31":{"tf":1.0},"36":{"tf":1.0}}},"df":0,"docs":{}},"l":{"df":0,"docs":{},"u":{"d":{"df":2,"docs":{"197":{"tf":1.0},"198":{"tf":1.0}}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"213":{"tf":1.0}}}},"df":0,"docs":{}}},"df":15,"docs":{"103":{"tf":1.0},"161":{"tf":1.0},"163":{"tf":1.7320508075688772},"164":{"tf":1.0},"167":{"tf":1.7320508075688772},"171":{"tf":1.0},"172":{"tf":1.0},"174":{"tf":1.0},"175":{"tf":1.0},"184":{"tf":1.0},"189":{"tf":1.0},"236":{"tf":1.0},"253":{"tf":1.0},"274":{"tf":1.0},"280":{"tf":1.0}},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":9,"docs":{"170":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.4142135623730951},"229":{"tf":1.0},"232":{"tf":1.0},"64":{"tf":1.0},"79":{"tf":1.0},"82":{"tf":1.0},"83":{"tf":1.0}}}},"i":{"df":0,"docs":{},"x":{")":{"/":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"y":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"3":{"df":1,"docs":{"11":{"tf":1.0}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"y":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"@":{"3":{".":{"9":{"/":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"y":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{".":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"/":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"/":{"3":{".":{"9":{"/":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"11":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"p":{"a":{"df":0,"docs":{},"r":{"df":2,"docs":{"268":{"tf":1.0},"74":{"tf":1.0}}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"24":{"tf":1.0}}}}}}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":5,"docs":{"185":{"tf":1.4142135623730951},"201":{"tf":1.0},"22":{"tf":1.4142135623730951},"237":{"tf":1.0},"270":{"tf":1.0}}}},"r":{"df":0,"docs":{},"v":{"df":1,"docs":{"26":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":1,"docs":{"242":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"59":{"tf":1.0}}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"228":{"tf":1.0},"233":{"tf":1.0}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"61":{"tf":1.0}}}}},"v":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"128":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":12,"docs":{"146":{"tf":1.0},"150":{"tf":1.0},"169":{"tf":1.4142135623730951},"170":{"tf":1.0},"171":{"tf":1.0},"179":{"tf":1.0},"180":{"tf":1.0},"183":{"tf":1.7320508075688772},"201":{"tf":1.0},"274":{"tf":1.4142135623730951},"31":{"tf":1.0},"36":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":6,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"268":{"tf":1.4142135623730951},"270":{"tf":1.0},"29":{"tf":1.4142135623730951},"34":{"tf":1.4142135623730951}},"s":{"df":5,"docs":{"106":{"tf":1.0},"141":{"tf":1.0},"25":{"tf":1.0},"26":{"tf":1.0},"268":{"tf":1.0}}}}}}}},"i":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":2,"docs":{"226":{"tf":1.4142135623730951},"248":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":5,"docs":{"11":{"tf":1.0},"201":{"tf":1.0},"245":{"tf":1.0},"257":{"tf":1.0},"284":{"tf":1.0}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"278":{"tf":1.0},"282":{"tf":1.4142135623730951}}}}},"n":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"62":{"tf":1.4142135623730951}}}}}}},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"62":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":3,"docs":{"22":{"tf":1.0},"37":{"tf":1.0},"62":{"tf":1.4142135623730951}},"f":{"df":1,"docs":{"37":{"tf":1.4142135623730951}}}}},"o":{"df":0,"docs":{},"r":{"df":2,"docs":{"206":{"tf":1.0},"214":{"tf":1.0}},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"258":{"tf":1.0},"6":{"tf":1.0}}}}}},"v":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"83":{"tf":1.0}}}},"df":0,"docs":{}}},"o":{"b":{"a":{"b":{"df":0,"docs":{},"l":{"df":17,"docs":{"104":{"tf":1.0},"115":{"tf":1.0},"122":{"tf":1.0},"156":{"tf":1.0},"18":{"tf":1.0},"197":{"tf":1.7320508075688772},"200":{"tf":1.0},"225":{"tf":1.0},"226":{"tf":1.4142135623730951},"229":{"tf":1.0},"231":{"tf":1.4142135623730951},"236":{"tf":1.0},"238":{"tf":1.0},"37":{"tf":1.0},"40":{"tf":1.0},"62":{"tf":1.0},"84":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"t":{"df":3,"docs":{"180":{"tf":1.0},"183":{"tf":1.0},"256":{"tf":1.0}}}},"df":18,"docs":{"136":{"tf":1.0},"140":{"tf":1.0},"149":{"tf":1.0},"161":{"tf":1.7320508075688772},"175":{"tf":1.0},"177":{"tf":1.0},"182":{"tf":1.0},"187":{"tf":1.0},"197":{"tf":1.0},"203":{"tf":1.0},"205":{"tf":1.0},"226":{"tf":1.0},"25":{"tf":1.0},"260":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"58":{"tf":1.7320508075688772},"64":{"tf":1.0}}}}}},"c":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":2,"docs":{"18":{"tf":1.0},"20":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"d":{"df":1,"docs":{"12":{"tf":1.0}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"s":{"df":38,"docs":{"101":{"tf":1.0},"102":{"tf":1.0},"105":{"tf":1.0},"106":{"tf":1.0},"114":{"tf":1.0},"120":{"tf":1.0},"122":{"tf":1.0},"123":{"tf":1.0},"124":{"tf":1.0},"126":{"tf":1.7320508075688772},"133":{"tf":1.0},"138":{"tf":1.0},"161":{"tf":1.0},"165":{"tf":1.0},"167":{"tf":1.4142135623730951},"169":{"tf":1.0},"170":{"tf":1.0},"188":{"tf":1.0},"19":{"tf":1.0},"197":{"tf":1.0},"20":{"tf":1.0},"220":{"tf":1.0},"243":{"tf":1.0},"244":{"tf":1.4142135623730951},"245":{"tf":1.4142135623730951},"246":{"tf":1.0},"248":{"tf":1.0},"257":{"tf":1.0},"259":{"tf":1.0},"26":{"tf":1.0},"265":{"tf":1.0},"27":{"tf":1.4142135623730951},"274":{"tf":1.0},"280":{"tf":1.0},"282":{"tf":1.0},"49":{"tf":1.0},"69":{"tf":1.0},"88":{"tf":1.0}}}}}},"d":{"df":0,"docs":{},"u":{"c":{"df":7,"docs":{"111":{"tf":1.0},"28":{"tf":1.0},"284":{"tf":1.0},"33":{"tf":1.0},"37":{"tf":1.0},"39":{"tf":1.0},"60":{"tf":1.0}},"t":{"df":9,"docs":{"174":{"tf":1.0},"185":{"tf":1.0},"237":{"tf":1.0},"253":{"tf":1.0},"259":{"tf":1.0},"268":{"tf":1.0},"271":{"tf":1.4142135623730951},"64":{"tf":1.0},"8":{"tf":1.0}}}},"df":0,"docs":{}}},"df":7,"docs":{"144":{"tf":1.0},"145":{"tf":1.0},"146":{"tf":1.0},"153":{"tf":1.0},"168":{"tf":1.0},"181":{"tf":1.0},"211":{"tf":1.0}},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"11":{"tf":1.7320508075688772}}},"t":{"df":2,"docs":{"122":{"tf":1.0},"200":{"tf":1.0}}}}},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"149":{"tf":1.0},"184":{"tf":1.0}},"m":{"df":1,"docs":{"61":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":4,"docs":{"107":{"tf":1.0},"16":{"tf":1.0},"268":{"tf":2.6457513110645907},"7":{"tf":1.4142135623730951}}}}}}},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"'":{"df":2,"docs":{"22":{"tf":1.0},"7":{"tf":1.0}}},"(":{"\"":{":":{"df":0,"docs":{},"f":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":1,"docs":{"224":{"tf":1.0}}}}}},"df":0,"docs":{}},"'":{":":{"df":0,"docs":{},"f":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":1,"docs":{"224":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":35,"docs":{"10":{"tf":1.0},"109":{"tf":1.7320508075688772},"118":{"tf":1.0},"136":{"tf":1.0},"138":{"tf":1.0},"14":{"tf":1.4142135623730951},"16":{"tf":1.7320508075688772},"167":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"18":{"tf":1.4142135623730951},"190":{"tf":1.4142135623730951},"191":{"tf":1.0},"20":{"tf":1.0},"203":{"tf":1.0},"22":{"tf":2.23606797749979},"224":{"tf":1.4142135623730951},"252":{"tf":1.0},"260":{"tf":1.7320508075688772},"263":{"tf":1.0},"278":{"tf":1.0},"3":{"tf":1.4142135623730951},"36":{"tf":1.0},"40":{"tf":1.4142135623730951},"45":{"tf":1.0},"46":{"tf":1.0},"5":{"tf":1.0},"58":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0},"67":{"tf":1.0},"70":{"tf":1.0},"73":{"tf":2.449489742783178},"8":{"tf":1.0},"91":{"tf":1.0}}}},"df":0,"docs":{}}},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":4,"docs":{"267":{"tf":1.4142135623730951},"268":{"tf":1.0},"270":{"tf":1.0},"273":{"tf":2.0}}}},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"117":{"tf":1.4142135623730951}}}}},"n":{"df":0,"docs":{},"e":{"df":2,"docs":{"116":{"tf":1.0},"61":{"tf":1.0}}}},"p":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"249":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"274":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"69":{"tf":1.0}}}},"t":{"df":0,"docs":{},"i":{"df":4,"docs":{"17":{"tf":1.4142135623730951},"22":{"tf":1.7320508075688772},"268":{"tf":1.0},"99":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"s":{"df":4,"docs":{"146":{"tf":1.0},"186":{"tf":1.0},"187":{"tf":1.0},"236":{"tf":1.0}}}},"s":{"df":1,"docs":{"145":{"tf":1.0}}}},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"106":{"tf":1.4142135623730951}}}},"t":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"201":{"tf":1.0},"274":{"tf":2.0}}}},"df":0,"docs":{}},"o":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"f":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":1,"docs":{"106":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}}},"df":4,"docs":{"106":{"tf":2.0},"175":{"tf":1.0},"65":{"tf":1.0},"67":{"tf":1.0}}}}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":4,"docs":{"105":{"tf":1.0},"106":{"tf":1.4142135623730951},"256":{"tf":1.0},"96":{"tf":1.0}}}}},"df":2,"docs":{"105":{"tf":1.0},"106":{"tf":1.0}},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":5,"docs":{"107":{"tf":1.0},"167":{"tf":1.0},"172":{"tf":1.0},"174":{"tf":1.7320508075688772},"268":{"tf":1.0}}}}}}},"v":{"df":0,"docs":{},"i":{"d":{"df":28,"docs":{"103":{"tf":1.4142135623730951},"116":{"tf":1.0},"128":{"tf":1.0},"145":{"tf":1.0},"159":{"tf":1.0},"164":{"tf":1.0},"189":{"tf":1.0},"201":{"tf":1.0},"220":{"tf":1.0},"222":{"tf":1.0},"229":{"tf":1.0},"234":{"tf":1.0},"243":{"tf":1.0},"245":{"tf":1.0},"253":{"tf":1.0},"254":{"tf":1.4142135623730951},"257":{"tf":1.0},"268":{"tf":1.0},"274":{"tf":1.0},"278":{"tf":1.0},"282":{"tf":1.0},"283":{"tf":1.4142135623730951},"37":{"tf":1.0},"45":{"tf":1.0},"62":{"tf":1.4142135623730951},"64":{"tf":1.0},"77":{"tf":1.0},"78":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":1,"docs":{"274":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":1,"docs":{"208":{"tf":1.0}}}}}},"u":{"b":{"df":6,"docs":{"103":{"tf":1.0},"105":{"tf":1.4142135623730951},"111":{"tf":1.0},"114":{"tf":1.0},"69":{"tf":1.4142135623730951},"73":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"c":{"df":11,"docs":{"105":{"tf":1.0},"106":{"tf":1.4142135623730951},"107":{"tf":1.4142135623730951},"108":{"tf":1.0},"12":{"tf":1.0},"2":{"tf":1.0},"252":{"tf":1.0},"274":{"tf":1.0},"283":{"tf":1.0},"78":{"tf":1.0},"97":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"146":{"tf":1.0}}}}},"df":0,"docs":{},"s":{"df":1,"docs":{"170":{"tf":1.0}},"h":{"df":21,"docs":{"11":{"tf":1.0},"112":{"tf":1.0},"113":{"tf":1.4142135623730951},"16":{"tf":1.7320508075688772},"166":{"tf":1.0},"167":{"tf":1.7320508075688772},"169":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":2.0},"189":{"tf":1.0},"20":{"tf":2.0},"22":{"tf":1.7320508075688772},"220":{"tf":1.0},"221":{"tf":1.0},"253":{"tf":1.0},"267":{"tf":1.0},"274":{"tf":2.449489742783178},"278":{"tf":1.0},"37":{"tf":1.0},"69":{"tf":1.0},"70":{"tf":1.0}},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"df":1,"docs":{"20":{"tf":1.0}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":8,"docs":{"167":{"tf":1.0},"25":{"tf":1.0},"274":{"tf":2.0},"280":{"tf":1.0},"59":{"tf":1.0},"64":{"tf":1.0},"7":{"tf":2.23606797749979},"81":{"tf":1.0}}}},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"226":{"tf":1.0}}}},"r":{"df":0,"docs":{},"e":{"df":1,"docs":{"203":{"tf":1.0}}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":7,"docs":{"124":{"tf":1.0},"232":{"tf":1.0},"233":{"tf":1.4142135623730951},"237":{"tf":1.0},"252":{"tf":1.0},"274":{"tf":1.0},"51":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"u":{"df":2,"docs":{"115":{"tf":1.0},"152":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"h":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"94":{"tf":1.0}}}}}}}}}},"df":9,"docs":{"104":{"tf":1.0},"189":{"tf":1.0},"253":{"tf":1.0},"26":{"tf":1.0},"264":{"tf":1.0},"267":{"tf":1.4142135623730951},"274":{"tf":1.7320508075688772},"283":{"tf":1.0},"7":{"tf":1.4142135623730951}}}},"t":{"df":7,"docs":{"122":{"tf":1.0},"17":{"tf":1.0},"59":{"tf":1.4142135623730951},"60":{"tf":1.0},"61":{"tf":1.4142135623730951},"69":{"tf":1.4142135623730951},"79":{"tf":1.0}}}},"w":{"d":{"df":2,"docs":{"31":{"tf":1.0},"36":{"tf":1.0}}},"df":0,"docs":{}},"y":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"=":{"$":{"(":{"df":0,"docs":{},"w":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"11":{"tf":1.0}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"`":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"y":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"11":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"11":{"tf":1.0}}}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"'":{"df":1,"docs":{"11":{"tf":1.0}}},"3":{"=":{"$":{"(":{"b":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":1,"docs":{"11":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"11":{"tf":2.8284271247461903}}},"df":4,"docs":{"11":{"tf":2.449489742783178},"193":{"tf":1.0},"269":{"tf":1.0},"53":{"tf":1.0}}}}}}}},"q":{"1":{"df":1,"docs":{"187":{"tf":1.0}}},"a":{"df":1,"docs":{"190":{"tf":1.0}}},"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":3,"docs":{"63":{"tf":1.0},"64":{"tf":1.0},"8":{"tf":1.0}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":7,"docs":{"154":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0},"205":{"tf":1.4142135623730951},"206":{"tf":1.4142135623730951},"6":{"tf":1.0},"62":{"tf":2.23606797749979}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":5,"docs":{"11":{"tf":1.0},"149":{"tf":1.0},"205":{"tf":1.0},"274":{"tf":1.0},"60":{"tf":1.0}}}}}}}},"i":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"236":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"170":{"tf":1.0}}}},"l":{"df":0,"docs":{},"i":{"df":3,"docs":{"167":{"tf":1.0},"178":{"tf":1.0},"60":{"tf":1.0}}}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"243":{"tf":1.0}}}},"t":{"df":6,"docs":{"220":{"tf":1.0},"226":{"tf":1.0},"229":{"tf":1.0},"62":{"tf":1.4142135623730951},"68":{"tf":1.0},"84":{"tf":1.0}}}},"o":{"df":1,"docs":{"171":{"tf":1.0}}}}},"r":{"a":{"d":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"86":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":1,"docs":{"132":{"tf":1.0}}}},"n":{"df":3,"docs":{"203":{"tf":1.0},"30":{"tf":1.0},"35":{"tf":1.0}}},"p":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"189":{"tf":1.0},"68":{"tf":1.0}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"e":{"df":2,"docs":{"17":{"tf":1.0},"86":{"tf":1.0}}}},"t":{"df":0,"docs":{},"e":{"df":8,"docs":{"205":{"tf":1.4142135623730951},"208":{"tf":1.0},"214":{"tf":1.0},"216":{"tf":2.6457513110645907},"217":{"tf":1.0},"218":{"tf":1.0},"225":{"tf":1.0},"242":{"tf":1.0}}},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":1,"docs":{"104":{"tf":1.0}}}}}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"138":{"tf":1.0}}}}}},"w":{"df":3,"docs":{"128":{"tf":1.0},"79":{"tf":1.0},"83":{"tf":1.0}}}},"c":{"_":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":2,"docs":{"281":{"tf":1.0},"282":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"12":{"tf":1.0}}},"df":2,"docs":{"269":{"tf":1.0},"53":{"tf":1.0}},"e":{"a":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"6":{"tf":1.0}}},"t":{"df":1,"docs":{"233":{"tf":1.0}}}},"d":{"df":9,"docs":{"107":{"tf":1.0},"120":{"tf":1.0},"174":{"tf":1.0},"23":{"tf":1.4142135623730951},"32":{"tf":1.4142135623730951},"62":{"tf":1.4142135623730951},"7":{"tf":1.0},"79":{"tf":1.4142135623730951},"81":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"79":{"tf":1.4142135623730951}}}},"i":{"df":6,"docs":{"118":{"tf":1.0},"184":{"tf":1.0},"227":{"tf":1.0},"24":{"tf":1.4142135623730951},"267":{"tf":1.0},"58":{"tf":1.0}}},"m":{"df":1,"docs":{"281":{"tf":1.0}}}},"df":0,"docs":{},"l":{"df":6,"docs":{"16":{"tf":1.0},"200":{"tf":1.0},"204":{"tf":1.0},"233":{"tf":1.0},"237":{"tf":1.4142135623730951},"273":{"tf":1.0}},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"194":{"tf":1.0},"196":{"tf":1.0}}}},"z":{"df":1,"docs":{"61":{"tf":1.0}}}},"l":{"df":0,"docs":{},"i":{"df":8,"docs":{"116":{"tf":1.0},"129":{"tf":1.0},"169":{"tf":1.0},"40":{"tf":1.0},"58":{"tf":1.4142135623730951},"60":{"tf":1.0},"73":{"tf":1.4142135623730951},"79":{"tf":1.4142135623730951}}}}},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":14,"docs":{"152":{"tf":1.0},"192":{"tf":1.0},"194":{"tf":1.0},"197":{"tf":1.0},"201":{"tf":1.0},"205":{"tf":1.4142135623730951},"208":{"tf":1.4142135623730951},"217":{"tf":1.0},"224":{"tf":1.0},"242":{"tf":1.7320508075688772},"27":{"tf":1.0},"58":{"tf":1.0},"61":{"tf":1.4142135623730951},"7":{"tf":1.0}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"df":1,"docs":{"86":{"tf":1.0}}}}}}}},"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":3,"docs":{"124":{"tf":1.0},"37":{"tf":1.0},"61":{"tf":1.0}}},"df":0,"docs":{},"t":{"df":1,"docs":{"59":{"tf":1.0}}}}}}},"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":3,"docs":{"106":{"tf":1.0},"189":{"tf":1.0},"201":{"tf":1.0}}}},"n":{"df":0,"docs":{},"t":{"df":10,"docs":{"122":{"tf":1.0},"123":{"tf":1.4142135623730951},"170":{"tf":1.0},"193":{"tf":1.4142135623730951},"206":{"tf":1.4142135623730951},"208":{"tf":1.4142135623730951},"209":{"tf":1.4142135623730951},"210":{"tf":1.0},"231":{"tf":1.0},"78":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":4,"docs":{"101":{"tf":1.0},"11":{"tf":1.0},"14":{"tf":1.0},"274":{"tf":1.0}}},"df":0,"docs":{}}}}},"n":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":2,"docs":{"245":{"tf":1.0},"89":{"tf":1.0}},"e":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"249":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"r":{"d":{"df":18,"docs":{"134":{"tf":1.4142135623730951},"135":{"tf":1.0},"136":{"tf":1.4142135623730951},"137":{"tf":1.0},"145":{"tf":1.0},"146":{"tf":1.7320508075688772},"189":{"tf":1.0},"190":{"tf":1.0},"231":{"tf":1.7320508075688772},"237":{"tf":1.0},"245":{"tf":1.0},"246":{"tf":1.7320508075688772},"248":{"tf":2.0},"249":{"tf":4.123105625617661},"250":{"tf":2.6457513110645907},"268":{"tf":1.0},"84":{"tf":1.4142135623730951},"88":{"tf":1.7320508075688772}}},"df":0,"docs":{}},"v":{"df":1,"docs":{"158":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"158":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":5,"docs":{"151":{"tf":1.4142135623730951},"152":{"tf":1.0},"156":{"tf":1.0},"157":{"tf":1.0},"158":{"tf":1.0}}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":1,"docs":{"11":{"tf":1.0}}}}}},"d":{"df":1,"docs":{"109":{"tf":1.0}},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"283":{"tf":1.4142135623730951}}}}}},"df":1,"docs":{"201":{"tf":1.0}}}},"df":0,"docs":{}}}},"u":{"c":{"df":7,"docs":{"142":{"tf":1.0},"143":{"tf":1.0},"145":{"tf":1.4142135623730951},"165":{"tf":1.0},"205":{"tf":1.0},"220":{"tf":1.0},"222":{"tf":1.0}}},"df":0,"docs":{},"n":{"d":{"df":5,"docs":{"145":{"tf":1.0},"146":{"tf":1.0},"263":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0}}},"df":0,"docs":{}}}},"df":14,"docs":{"105":{"tf":1.0},"124":{"tf":1.7320508075688772},"14":{"tf":1.0},"142":{"tf":1.0},"161":{"tf":1.0},"193":{"tf":1.0},"233":{"tf":1.0},"252":{"tf":1.0},"258":{"tf":1.0},"26":{"tf":1.0},"283":{"tf":1.0},"81":{"tf":1.0},"86":{"tf":1.0},"90":{"tf":1.0}},"e":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":1,"docs":{"201":{"tf":1.0}}}}},"df":0,"docs":{}}},"f":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"81":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":2,"docs":{"171":{"tf":1.0},"280":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":14,"docs":{"109":{"tf":1.4142135623730951},"132":{"tf":1.4142135623730951},"167":{"tf":1.4142135623730951},"237":{"tf":1.0},"238":{"tf":1.0},"242":{"tf":1.4142135623730951},"29":{"tf":1.0},"31":{"tf":1.0},"34":{"tf":1.0},"36":{"tf":1.0},"60":{"tf":1.0},"70":{"tf":1.0},"79":{"tf":1.0},"82":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"61":{"tf":1.0}}},"df":0,"docs":{}}}}},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"242":{"tf":1.0},"87":{"tf":1.0},"98":{"tf":1.0}}}},"df":0,"docs":{}}}},"g":{"a":{"df":0,"docs":{},"r":{"d":{"df":4,"docs":{"122":{"tf":1.0},"231":{"tf":1.0},"238":{"tf":1.0},"242":{"tf":1.0}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"233":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"_":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"y":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"64":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":1,"docs":{"64":{"tf":1.0}}}}}},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"283":{"tf":1.4142135623730951}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":3,"docs":{"206":{"tf":1.0},"208":{"tf":1.0},"212":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"r":{"df":2,"docs":{"225":{"tf":1.0},"62":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"64":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"116":{"tf":1.0}}}}}}}}}}},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"176":{"tf":1.0},"180":{"tf":1.4142135623730951},"196":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"l":{"=":{"\"":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"283":{"tf":1.7320508075688772}}}}}}}}}}}}},"df":0,"docs":{}},"a":{"df":0,"docs":{},"t":{"df":13,"docs":{"0":{"tf":1.0},"123":{"tf":1.4142135623730951},"145":{"tf":1.0},"149":{"tf":1.4142135623730951},"20":{"tf":1.0},"205":{"tf":1.0},"226":{"tf":1.0},"26":{"tf":1.0},"278":{"tf":1.0},"282":{"tf":1.0},"283":{"tf":1.0},"48":{"tf":1.4142135623730951},"84":{"tf":1.0}}}},"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":1,"docs":{"272":{"tf":1.0}}}}},"df":7,"docs":{"121":{"tf":1.0},"152":{"tf":1.0},"154":{"tf":1.0},"167":{"tf":1.0},"18":{"tf":1.4142135623730951},"192":{"tf":1.0},"22":{"tf":1.0}},"e":{"a":{"df":0,"docs":{},"s":{"df":26,"docs":{"113":{"tf":1.0},"114":{"tf":1.0},"121":{"tf":1.0},"167":{"tf":2.23606797749979},"178":{"tf":1.0},"179":{"tf":1.4142135623730951},"18":{"tf":1.0},"180":{"tf":1.4142135623730951},"183":{"tf":2.0},"187":{"tf":1.0},"215":{"tf":1.0},"252":{"tf":1.0},"253":{"tf":1.7320508075688772},"26":{"tf":1.4142135623730951},"265":{"tf":1.0},"267":{"tf":2.6457513110645907},"268":{"tf":4.58257569495584},"269":{"tf":2.0},"270":{"tf":2.0},"271":{"tf":1.7320508075688772},"272":{"tf":1.0},"273":{"tf":2.0},"274":{"tf":3.872983346207417},"40":{"tf":1.0},"60":{"tf":1.4142135623730951},"74":{"tf":1.0}},"e":{".":{"df":0,"docs":{},"j":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"271":{"tf":1.0}}}}}},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"269":{"tf":1.4142135623730951}}}}},"_":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":1,"docs":{"268":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"]":{".":{"0":{"a":{"1":{"df":1,"docs":{"268":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":1,"docs":{"268":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"274":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"/":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"119":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":1,"docs":{"266":{"tf":1.0}}}},"v":{"df":6,"docs":{"193":{"tf":1.0},"194":{"tf":1.0},"221":{"tf":1.0},"241":{"tf":1.0},"54":{"tf":1.0},"7":{"tf":1.4142135623730951}}}},"i":{"a":{"b":{"df":0,"docs":{},"l":{"df":2,"docs":{"117":{"tf":1.0},"16":{"tf":1.0}}}},"df":0,"docs":{}},"df":4,"docs":{"124":{"tf":1.0},"128":{"tf":1.0},"278":{"tf":1.0},"64":{"tf":1.0}}}},"m":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":8,"docs":{"103":{"tf":1.4142135623730951},"105":{"tf":1.0},"142":{"tf":1.0},"143":{"tf":1.0},"145":{"tf":1.0},"198":{"tf":1.0},"199":{"tf":1.0},"264":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"b":{"df":2,"docs":{"64":{"tf":1.0},"84":{"tf":1.0}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"t":{"df":21,"docs":{"106":{"tf":1.0},"134":{"tf":1.0},"179":{"tf":1.0},"182":{"tf":1.0},"184":{"tf":1.0},"186":{"tf":1.4142135623730951},"187":{"tf":1.4142135623730951},"188":{"tf":1.0},"190":{"tf":1.4142135623730951},"192":{"tf":1.4142135623730951},"193":{"tf":1.4142135623730951},"194":{"tf":1.4142135623730951},"197":{"tf":1.4142135623730951},"198":{"tf":1.0},"199":{"tf":1.0},"201":{"tf":1.7320508075688772},"231":{"tf":1.0},"242":{"tf":1.0},"249":{"tf":1.4142135623730951},"250":{"tf":1.0},"268":{"tf":1.0}},"e":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"u":{"d":{"df":0,"docs":{},"l":{"df":1,"docs":{"200":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"200":{"tf":2.449489742783178}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"s":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}}}}}},"v":{"df":17,"docs":{"105":{"tf":1.0},"106":{"tf":1.7320508075688772},"108":{"tf":1.0},"109":{"tf":1.4142135623730951},"145":{"tf":2.0},"146":{"tf":1.4142135623730951},"167":{"tf":1.0},"172":{"tf":1.0},"197":{"tf":1.0},"203":{"tf":1.0},"231":{"tf":1.0},"25":{"tf":1.4142135623730951},"26":{"tf":1.0},"264":{"tf":1.0},"268":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0}}}}},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"78":{"tf":1.0}}}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":3,"docs":{"45":{"tf":1.0},"46":{"tf":1.0},"75":{"tf":1.0}}}},"df":0,"docs":{}},"l":{"a":{"c":{"df":14,"docs":{"103":{"tf":1.0},"106":{"tf":1.4142135623730951},"109":{"tf":1.0},"13":{"tf":1.0},"166":{"tf":1.0},"200":{"tf":1.0},"203":{"tf":1.0},"221":{"tf":1.0},"222":{"tf":1.7320508075688772},"231":{"tf":1.4142135623730951},"258":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"70":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{},"i":{"c":{"df":3,"docs":{"140":{"tf":1.0},"145":{"tf":1.0},"61":{"tf":1.0}}},"df":0,"docs":{}}},"o":{"df":19,"docs":{"101":{"tf":1.0},"102":{"tf":1.0},"106":{"tf":1.0},"118":{"tf":1.0},"124":{"tf":1.0},"13":{"tf":1.0},"167":{"tf":2.8284271247461903},"18":{"tf":1.0},"197":{"tf":1.0},"20":{"tf":1.4142135623730951},"200":{"tf":1.4142135623730951},"21":{"tf":1.0},"225":{"tf":1.0},"253":{"tf":1.0},"268":{"tf":1.4142135623730951},"274":{"tf":1.7320508075688772},"49":{"tf":1.0},"69":{"tf":1.0},"75":{"tf":1.0}},"r":{"df":0,"docs":{},"t":{"df":5,"docs":{"1":{"tf":1.4142135623730951},"104":{"tf":1.0},"249":{"tf":1.0},"4":{"tf":1.0},"50":{"tf":1.0}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":37,"docs":{"11":{"tf":1.0},"113":{"tf":1.0},"118":{"tf":1.4142135623730951},"12":{"tf":1.4142135623730951},"124":{"tf":1.0},"13":{"tf":1.0},"16":{"tf":1.0},"167":{"tf":2.23606797749979},"17":{"tf":1.0},"18":{"tf":1.0},"194":{"tf":1.0},"197":{"tf":1.0},"20":{"tf":1.7320508075688772},"200":{"tf":1.4142135623730951},"22":{"tf":1.0},"23":{"tf":1.0},"25":{"tf":1.0},"251":{"tf":1.0},"253":{"tf":1.4142135623730951},"260":{"tf":1.0},"271":{"tf":1.0},"274":{"tf":1.0},"284":{"tf":1.0},"286":{"tf":1.0},"29":{"tf":1.0},"30":{"tf":1.0},"32":{"tf":1.0},"34":{"tf":1.4142135623730951},"35":{"tf":1.0},"37":{"tf":1.0},"46":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.7320508075688772},"68":{"tf":1.0},"69":{"tf":1.4142135623730951},"70":{"tf":1.0},"74":{"tf":1.0}}}}}}}}},"r":{"df":1,"docs":{"268":{"tf":1.0}},"e":{"df":0,"docs":{},"s":{"df":3,"docs":{"106":{"tf":1.4142135623730951},"252":{"tf":1.0},"283":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"214":{"tf":1.0}}}}}}}}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{".":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"a":{"df":1,"docs":{"283":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":1,"docs":{"283":{"tf":1.0}}}}}}}}}}}}}}}},"df":12,"docs":{"122":{"tf":1.0},"189":{"tf":1.7320508075688772},"201":{"tf":1.4142135623730951},"225":{"tf":1.0},"226":{"tf":1.4142135623730951},"233":{"tf":1.4142135623730951},"236":{"tf":1.0},"245":{"tf":1.7320508075688772},"259":{"tf":1.0},"274":{"tf":2.0},"280":{"tf":1.0},"7":{"tf":2.23606797749979}}}}},"i":{"df":0,"docs":{},"r":{"df":50,"docs":{"10":{"tf":1.0},"105":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"11":{"tf":2.23606797749979},"116":{"tf":1.0},"12":{"tf":2.0},"141":{"tf":1.0},"142":{"tf":1.0},"143":{"tf":1.4142135623730951},"145":{"tf":2.23606797749979},"146":{"tf":1.0},"167":{"tf":1.0},"169":{"tf":1.0},"170":{"tf":1.0},"175":{"tf":1.0},"18":{"tf":1.0},"183":{"tf":1.0},"187":{"tf":2.0},"188":{"tf":2.23606797749979},"189":{"tf":3.3166247903554},"190":{"tf":1.7320508075688772},"193":{"tf":1.0},"196":{"tf":1.7320508075688772},"197":{"tf":2.23606797749979},"198":{"tf":1.4142135623730951},"199":{"tf":1.0},"200":{"tf":1.4142135623730951},"201":{"tf":2.449489742783178},"22":{"tf":1.4142135623730951},"220":{"tf":1.0},"222":{"tf":1.4142135623730951},"225":{"tf":1.0},"229":{"tf":1.4142135623730951},"23":{"tf":1.0},"231":{"tf":1.4142135623730951},"233":{"tf":1.0},"241":{"tf":1.0},"261":{"tf":1.4142135623730951},"262":{"tf":1.0},"263":{"tf":1.4142135623730951},"269":{"tf":1.0},"274":{"tf":1.0},"277":{"tf":1.0},"279":{"tf":1.0},"286":{"tf":1.0},"53":{"tf":1.0},"61":{"tf":1.0},"64":{"tf":1.0},"81":{"tf":1.7320508075688772}}}}}},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":1,"docs":{"12":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":7,"docs":{"234":{"tf":1.0},"242":{"tf":1.0},"245":{"tf":1.0},"25":{"tf":2.0},"26":{"tf":1.7320508075688772},"86":{"tf":1.0},"89":{"tf":1.4142135623730951}}}},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"v":{"df":4,"docs":{"118":{"tf":1.0},"22":{"tf":1.0},"40":{"tf":1.0},"6":{"tf":1.0}}}},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"111":{"tf":1.0}}}},"u":{"df":0,"docs":{},"r":{"c":{"df":8,"docs":{"128":{"tf":1.0},"133":{"tf":1.0},"141":{"tf":1.0},"194":{"tf":1.0},"226":{"tf":1.4142135623730951},"252":{"tf":1.0},"64":{"tf":1.0},"73":{"tf":1.0}}},"df":0,"docs":{}}}},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"60":{"tf":1.0},"64":{"tf":1.0}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":16,"docs":{"121":{"tf":1.4142135623730951},"132":{"tf":1.0},"146":{"tf":1.0},"189":{"tf":1.0},"190":{"tf":1.0},"194":{"tf":1.0},"201":{"tf":2.449489742783178},"225":{"tf":2.23606797749979},"226":{"tf":1.7320508075688772},"241":{"tf":1.0},"245":{"tf":1.7320508075688772},"246":{"tf":1.0},"249":{"tf":1.0},"64":{"tf":1.0},"7":{"tf":1.0},"78":{"tf":1.0}}}}}},"t":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"233":{"tf":1.0}}}}},"df":3,"docs":{"201":{"tf":1.4142135623730951},"205":{"tf":1.0},"37":{"tf":1.0}},"o":{"df":0,"docs":{},"r":{"df":5,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"226":{"tf":1.0},"231":{"tf":1.0},"86":{"tf":1.0}}}},"r":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"105":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"<":{"df":0,"docs":{},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"242":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}}}},"df":17,"docs":{"113":{"tf":1.0},"132":{"tf":1.0},"174":{"tf":1.0},"188":{"tf":1.0},"189":{"tf":1.0},"197":{"tf":1.0},"203":{"tf":1.0},"208":{"tf":1.0},"220":{"tf":1.0},"224":{"tf":1.0},"242":{"tf":2.6457513110645907},"252":{"tf":1.0},"266":{"tf":1.0},"283":{"tf":1.0},"58":{"tf":1.0},"7":{"tf":1.4142135623730951},"73":{"tf":1.7320508075688772}}}}}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":6,"docs":{"179":{"tf":1.7320508075688772},"180":{"tf":1.0},"184":{"tf":1.4142135623730951},"185":{"tf":1.4142135623730951},"217":{"tf":1.0},"254":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":9,"docs":{"106":{"tf":1.4142135623730951},"145":{"tf":1.0},"159":{"tf":1.0},"188":{"tf":1.0},"189":{"tf":1.7320508075688772},"229":{"tf":1.0},"233":{"tf":1.0},"249":{"tf":1.4142135623730951},"283":{"tf":2.0}}}}}},"u":{"df":0,"docs":{},"s":{"df":3,"docs":{"199":{"tf":1.0},"225":{"tf":1.0},"242":{"tf":1.0}}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":1,"docs":{"180":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":1,"docs":{"201":{"tf":1.0}}}},"r":{"df":0,"docs":{},"s":{"df":1,"docs":{"104":{"tf":1.0}}},"t":{"df":1,"docs":{"26":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":9,"docs":{"122":{"tf":1.7320508075688772},"143":{"tf":1.0},"146":{"tf":1.0},"150":{"tf":1.0},"189":{"tf":1.0},"259":{"tf":1.0},"274":{"tf":1.0},"7":{"tf":1.4142135623730951},"8":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"r":{"'":{"df":1,"docs":{"7":{"tf":1.0}}},"df":0,"docs":{}}}}},"s":{"df":1,"docs":{"123":{"tf":1.7320508075688772}}}},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"v":{"df":1,"docs":{"261":{"tf":1.0}}}}}}},"f":{"c":{"df":1,"docs":{"185":{"tf":1.0}}},"df":1,"docs":{"279":{"tf":1.0}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"160":{"tf":1.0}}}}}}}},"i":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"106":{"tf":1.0}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":5,"docs":{"126":{"tf":1.7320508075688772},"129":{"tf":1.0},"212":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"k":{"df":3,"docs":{"201":{"tf":1.0},"203":{"tf":1.4142135623730951},"252":{"tf":1.0}}}}},"k":{"df":0,"docs":{},"v":{"df":3,"docs":{"194":{"tf":1.0},"197":{"tf":1.0},"79":{"tf":1.0}}}},"l":{"df":1,"docs":{"200":{"tf":1.0}}},"m":{"df":1,"docs":{"279":{"tf":1.0}}},"o":{"a":{"d":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"192":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":2,"docs":{"223":{"tf":1.0},"45":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"_":{"_":{"_":{"_":{"_":{"_":{"_":{"_":{"df":1,"docs":{"62":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":13,"docs":{"113":{"tf":1.0},"122":{"tf":1.0},"167":{"tf":1.0},"17":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.4142135623730951},"29":{"tf":1.0},"34":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.4142135623730951},"62":{"tf":1.0},"69":{"tf":1.4142135623730951},"70":{"tf":1.0}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"}":{"/":{"$":{"df":0,"docs":{},"{":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"r":{"c":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"}":{"/":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"22":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"t":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"276":{"tf":1.0}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":1,"docs":{"224":{"tf":1.4142135623730951}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"242":{"tf":1.0}}}}}}},"w":{"df":4,"docs":{"156":{"tf":1.0},"249":{"tf":1.0},"62":{"tf":1.0},"82":{"tf":1.4142135623730951}}}},"p":{"0":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"_":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"df":0,"docs":{},"i":{"0":{"df":0,"docs":{},"e":{"df":0,"docs":{},"z":{"_":{"df":0,"docs":{},"j":{"df":0,"docs":{},"q":{"df":0,"docs":{},"s":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"z":{"df":0,"docs":{},"o":{"df":0,"docs":{},"h":{"df":0,"docs":{},"j":{"df":0,"docs":{},"y":{"/":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"185":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"s":{"#":{"4":{"1":{"6":{"df":1,"docs":{"107":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"200":{"tf":2.6457513110645907}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":5,"docs":{"118":{"tf":1.0},"222":{"tf":1.0},"59":{"tf":1.0},"73":{"tf":1.0},"79":{"tf":1.0}}}},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"282":{"tf":1.0}}}}},"n":{"_":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"264":{"tf":1.0}}}}}}}},"df":0,"docs":{}}},"df":78,"docs":{"105":{"tf":1.0},"107":{"tf":1.0},"109":{"tf":1.0},"11":{"tf":2.0},"115":{"tf":1.4142135623730951},"117":{"tf":1.0},"118":{"tf":1.0},"12":{"tf":1.0},"122":{"tf":1.7320508075688772},"124":{"tf":1.4142135623730951},"126":{"tf":1.0},"127":{"tf":1.0},"133":{"tf":1.4142135623730951},"134":{"tf":1.4142135623730951},"14":{"tf":2.0},"154":{"tf":1.0},"155":{"tf":1.0},"17":{"tf":2.0},"170":{"tf":1.0},"176":{"tf":1.4142135623730951},"177":{"tf":2.0},"178":{"tf":1.0},"179":{"tf":2.23606797749979},"180":{"tf":1.7320508075688772},"182":{"tf":1.0},"183":{"tf":2.0},"184":{"tf":1.4142135623730951},"185":{"tf":1.4142135623730951},"19":{"tf":1.0},"190":{"tf":1.0},"194":{"tf":1.4142135623730951},"20":{"tf":1.4142135623730951},"203":{"tf":2.0},"204":{"tf":1.0},"205":{"tf":1.4142135623730951},"208":{"tf":1.0},"209":{"tf":1.0},"214":{"tf":1.4142135623730951},"216":{"tf":1.0},"218":{"tf":1.4142135623730951},"22":{"tf":1.7320508075688772},"223":{"tf":1.4142135623730951},"23":{"tf":1.0},"25":{"tf":1.4142135623730951},"26":{"tf":1.0},"269":{"tf":1.4142135623730951},"27":{"tf":1.4142135623730951},"274":{"tf":3.0},"28":{"tf":1.0},"280":{"tf":1.0},"30":{"tf":1.4142135623730951},"31":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.0},"35":{"tf":1.4142135623730951},"36":{"tf":1.0},"37":{"tf":1.0},"39":{"tf":1.4142135623730951},"40":{"tf":1.4142135623730951},"41":{"tf":1.0},"45":{"tf":1.4142135623730951},"48":{"tf":1.0},"49":{"tf":1.7320508075688772},"51":{"tf":1.4142135623730951},"53":{"tf":1.0},"54":{"tf":1.4142135623730951},"55":{"tf":1.0},"56":{"tf":1.0},"58":{"tf":1.4142135623730951},"60":{"tf":1.7320508075688772},"61":{"tf":1.0},"62":{"tf":1.0},"64":{"tf":1.4142135623730951},"69":{"tf":1.0},"7":{"tf":2.23606797749979},"70":{"tf":1.4142135623730951},"73":{"tf":1.0},"82":{"tf":1.0}},"n":{"a":{"b":{"df":0,"docs":{},"l":{"df":2,"docs":{"43":{"tf":1.0},"45":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":3,"docs":{"222":{"tf":1.0},"224":{"tf":1.0},"45":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"79":{"tf":1.7320508075688772}}}}}},"t":{"&":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{";":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"k":{"df":1,"docs":{"283":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"'":{"df":3,"docs":{"107":{"tf":1.0},"43":{"tf":1.4142135623730951},"92":{"tf":1.0}}},".":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"17":{"tf":1.4142135623730951}},"s":{"=":{"df":0,"docs":{},"x":{"8":{"6":{",":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"x":{"df":1,"docs":{"17":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"/":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":2,"docs":{"14":{"tf":1.0},"73":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"x":{"c":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{".":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":1,"docs":{"34":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"h":{"df":1,"docs":{"73":{"tf":1.0}}},"x":{"c":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{".":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":1,"docs":{"29":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"h":{"df":1,"docs":{"109":{"tf":1.0}}},"x":{"c":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"df":2,"docs":{"109":{"tf":1.0},"14":{"tf":1.0}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"x":{"c":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"df":1,"docs":{"73":{"tf":1.0}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"46":{"tf":1.0},"73":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}}}}},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"/":{"c":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":1,"docs":{"69":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"s":{"df":0,"docs":{},"r":{"c":{"/":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"b":{".":{"df":0,"docs":{},"r":{"df":2,"docs":{"69":{"tf":1.0},"73":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"<":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{">":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{".":{"df":0,"docs":{},"h":{"df":2,"docs":{"103":{"tf":1.4142135623730951},"109":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}},"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"_":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"l":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":2,"docs":{"25":{"tf":1.0},"26":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"x":{"c":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{".":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":1,"docs":{"271":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}}}}}}}},"d":{"df":0,"docs":{},"o":{"c":{"df":2,"docs":{"107":{"tf":1.0},"283":{"tf":1.0}}},"df":0,"docs":{}}},"df":101,"docs":{"0":{"tf":1.7320508075688772},"1":{"tf":1.0},"10":{"tf":1.0},"101":{"tf":1.0},"103":{"tf":2.6457513110645907},"104":{"tf":1.7320508075688772},"105":{"tf":1.7320508075688772},"106":{"tf":1.4142135623730951},"107":{"tf":1.7320508075688772},"108":{"tf":1.7320508075688772},"109":{"tf":1.7320508075688772},"11":{"tf":1.4142135623730951},"110":{"tf":1.0},"111":{"tf":2.0},"114":{"tf":1.4142135623730951},"115":{"tf":2.23606797749979},"116":{"tf":2.0},"117":{"tf":1.7320508075688772},"119":{"tf":1.0},"120":{"tf":1.0},"124":{"tf":1.4142135623730951},"125":{"tf":1.0},"128":{"tf":1.0},"14":{"tf":1.0},"140":{"tf":1.0},"163":{"tf":1.4142135623730951},"164":{"tf":1.0},"167":{"tf":3.1622776601683795},"17":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.7320508075688772},"174":{"tf":1.0},"187":{"tf":1.0},"193":{"tf":2.23606797749979},"194":{"tf":1.4142135623730951},"196":{"tf":1.4142135623730951},"197":{"tf":1.4142135623730951},"200":{"tf":1.4142135623730951},"219":{"tf":2.23606797749979},"220":{"tf":2.23606797749979},"221":{"tf":2.449489742783178},"222":{"tf":1.4142135623730951},"223":{"tf":1.4142135623730951},"224":{"tf":1.4142135623730951},"225":{"tf":2.449489742783178},"226":{"tf":1.7320508075688772},"228":{"tf":2.449489742783178},"23":{"tf":2.0},"24":{"tf":1.0},"241":{"tf":2.23606797749979},"242":{"tf":1.7320508075688772},"25":{"tf":2.0},"251":{"tf":2.0},"252":{"tf":1.7320508075688772},"253":{"tf":2.23606797749979},"256":{"tf":1.0},"257":{"tf":2.23606797749979},"258":{"tf":1.0},"26":{"tf":2.449489742783178},"260":{"tf":2.0},"261":{"tf":2.0},"262":{"tf":1.4142135623730951},"263":{"tf":2.23606797749979},"268":{"tf":1.7320508075688772},"271":{"tf":1.0},"272":{"tf":1.4142135623730951},"274":{"tf":2.8284271247461903},"28":{"tf":1.4142135623730951},"282":{"tf":1.0},"29":{"tf":2.23606797749979},"30":{"tf":1.4142135623730951},"31":{"tf":2.449489742783178},"32":{"tf":2.0},"33":{"tf":1.4142135623730951},"34":{"tf":2.23606797749979},"35":{"tf":1.4142135623730951},"36":{"tf":2.449489742783178},"37":{"tf":1.0},"41":{"tf":1.0},"43":{"tf":2.8284271247461903},"45":{"tf":1.4142135623730951},"46":{"tf":1.4142135623730951},"48":{"tf":1.0},"59":{"tf":1.7320508075688772},"61":{"tf":1.0},"62":{"tf":1.0},"64":{"tf":1.7320508075688772},"68":{"tf":1.0},"69":{"tf":1.7320508075688772},"70":{"tf":1.0},"72":{"tf":1.0},"73":{"tf":1.0},"74":{"tf":2.23606797749979},"75":{"tf":1.7320508075688772},"79":{"tf":1.0},"82":{"tf":1.0},"83":{"tf":1.0},"9":{"tf":1.0},"90":{"tf":2.0},"92":{"tf":1.0},"93":{"tf":2.0}},"j":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"224":{"tf":1.0}}},"df":0,"docs":{}}}}}},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"126":{"tf":1.0}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"x":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"(":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{".":{"d":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":1,"docs":{"126":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"u":{"df":0,"docs":{},"p":{"df":1,"docs":{"11":{"tf":1.0}}}}}}}},"s":{"a":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":4,"docs":{"116":{"tf":1.0},"222":{"tf":1.0},"281":{"tf":1.0},"282":{"tf":1.0}},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"116":{"tf":1.0}}}}}},"m":{"df":0,"docs":{},"e":{"df":32,"docs":{"104":{"tf":1.0},"109":{"tf":1.0},"11":{"tf":1.0},"115":{"tf":1.0},"116":{"tf":1.0},"122":{"tf":1.0},"123":{"tf":1.4142135623730951},"124":{"tf":1.4142135623730951},"129":{"tf":1.0},"145":{"tf":1.0},"165":{"tf":1.0},"167":{"tf":1.0},"175":{"tf":1.4142135623730951},"18":{"tf":1.4142135623730951},"198":{"tf":1.0},"201":{"tf":1.0},"22":{"tf":1.0},"222":{"tf":1.0},"226":{"tf":1.0},"231":{"tf":1.0},"237":{"tf":1.0},"241":{"tf":1.0},"248":{"tf":1.0},"25":{"tf":1.0},"258":{"tf":1.4142135623730951},"26":{"tf":1.7320508075688772},"262":{"tf":1.4142135623730951},"263":{"tf":1.0},"274":{"tf":1.0},"280":{"tf":1.0},"61":{"tf":1.4142135623730951},"84":{"tf":1.0}}},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"49":{"tf":1.0}}}}},"n":{"df":0,"docs":{},"e":{"df":2,"docs":{"241":{"tf":1.0},"86":{"tf":1.0}}},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"51":{"tf":1.0}}}}},"v":{"df":0,"docs":{},"e":{"df":1,"docs":{"286":{"tf":1.0}}}},"w":{"df":2,"docs":{"205":{"tf":1.4142135623730951},"4":{"tf":1.0}}}},"c":{"a":{"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"d":{"df":2,"docs":{"104":{"tf":1.4142135623730951},"105":{"tf":1.0}}},"df":0,"docs":{}}}}},"n":{"df":1,"docs":{"81":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":2,"docs":{"124":{"tf":1.4142135623730951},"19":{"tf":1.0}}}}}},"df":0,"docs":{}}},"h":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":4,"docs":{"189":{"tf":1.0},"226":{"tf":1.0},"242":{"tf":1.0},"274":{"tf":1.0}}}}},"df":0,"docs":{},"m":{"a":{"df":5,"docs":{"189":{"tf":1.0},"268":{"tf":1.0},"78":{"tf":1.0},"79":{"tf":1.0},"81":{"tf":2.8284271247461903}}},"df":0,"docs":{},"e":{":":{"d":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"283":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":1,"docs":{"283":{"tf":1.0}}}}}}}},"df":0,"docs":{}}}},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"37":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"t":{"a":{"b":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"y":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"283":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":4,"docs":{"111":{"tf":1.0},"190":{"tf":1.0},"254":{"tf":1.0},"274":{"tf":1.0}}}}},"r":{"a":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"h":{"df":3,"docs":{"101":{"tf":1.0},"196":{"tf":1.0},"68":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"81":{"tf":1.0}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"_":{"c":{"a":{"df":0,"docs":{},"s":{"df":1,"docs":{"93":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"184":{"tf":1.4142135623730951}},"s":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":3,"docs":{"31":{"tf":1.0},"36":{"tf":1.4142135623730951},"73":{"tf":1.7320508075688772}}}}}}}}},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":28,"docs":{"105":{"tf":1.0},"11":{"tf":1.0},"14":{"tf":1.4142135623730951},"167":{"tf":1.0},"172":{"tf":1.0},"22":{"tf":1.0},"224":{"tf":1.0},"23":{"tf":1.0},"25":{"tf":1.7320508075688772},"252":{"tf":1.0},"253":{"tf":1.0},"26":{"tf":1.7320508075688772},"269":{"tf":1.4142135623730951},"27":{"tf":1.7320508075688772},"279":{"tf":1.0},"28":{"tf":1.0},"282":{"tf":1.0},"30":{"tf":1.7320508075688772},"32":{"tf":1.0},"33":{"tf":1.0},"35":{"tf":1.7320508075688772},"41":{"tf":1.0},"52":{"tf":1.4142135623730951},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"7":{"tf":2.0},"74":{"tf":1.0}},"s":{"/":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"22":{"tf":1.7320508075688772}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":1,"docs":{"64":{"tf":1.0}}}}}}}}},"d":{"df":0,"docs":{},"k":{"df":8,"docs":{"12":{"tf":2.0},"178":{"tf":1.0},"180":{"tf":1.0},"182":{"tf":1.0},"194":{"tf":1.4142135623730951},"259":{"tf":1.0},"274":{"tf":1.4142135623730951},"284":{"tf":1.0}}}},"df":4,"docs":{"11":{"tf":1.0},"14":{"tf":1.0},"274":{"tf":1.0},"93":{"tf":1.0}},"e":{"a":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"h":{"df":7,"docs":{"126":{"tf":1.4142135623730951},"187":{"tf":1.0},"201":{"tf":1.0},"205":{"tf":2.0},"206":{"tf":1.0},"45":{"tf":1.0},"6":{"tf":1.4142135623730951}},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"x":{"df":1,"docs":{"261":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"d":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"60":{"tf":1.0}}}}},"df":4,"docs":{"167":{"tf":1.0},"177":{"tf":1.0},"204":{"tf":2.23606797749979},"6":{"tf":1.7320508075688772}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":4,"docs":{"201":{"tf":1.0},"274":{"tf":1.4142135623730951},"275":{"tf":1.0},"276":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":13,"docs":{"105":{"tf":1.0},"11":{"tf":1.4142135623730951},"12":{"tf":1.0},"200":{"tf":1.0},"201":{"tf":1.0},"209":{"tf":1.0},"210":{"tf":1.0},"214":{"tf":1.0},"227":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"73":{"tf":1.4142135623730951},"85":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"r":{"df":7,"docs":{"141":{"tf":1.4142135623730951},"143":{"tf":1.0},"146":{"tf":1.4142135623730951},"201":{"tf":1.0},"274":{"tf":1.4142135623730951},"63":{"tf":1.0},"64":{"tf":1.0}},"e":{"_":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"264":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"df":26,"docs":{"105":{"tf":1.0},"106":{"tf":1.0},"13":{"tf":1.0},"138":{"tf":1.0},"145":{"tf":1.0},"161":{"tf":1.0},"175":{"tf":1.0},"22":{"tf":1.4142135623730951},"226":{"tf":1.0},"228":{"tf":1.0},"245":{"tf":1.4142135623730951},"261":{"tf":1.0},"264":{"tf":1.0},"266":{"tf":1.0},"268":{"tf":2.0},"274":{"tf":2.0},"280":{"tf":1.0},"38":{"tf":1.0},"40":{"tf":1.0},"48":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"69":{"tf":1.0},"78":{"tf":1.0},"93":{"tf":1.0},"99":{"tf":1.0}},"m":{"df":7,"docs":{"128":{"tf":1.0},"152":{"tf":1.0},"200":{"tf":1.0},"226":{"tf":1.0},"276":{"tf":1.0},"277":{"tf":1.0},"61":{"tf":2.0}}},"n":{"df":1,"docs":{"250":{"tf":1.0}}}},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":9,"docs":{"11":{"tf":1.0},"117":{"tf":1.4142135623730951},"126":{"tf":1.4142135623730951},"209":{"tf":1.0},"253":{"tf":1.0},"280":{"tf":1.0},"62":{"tf":1.4142135623730951},"7":{"tf":1.0},"73":{"tf":1.0}}}},"df":0,"docs":{}},"f":{".":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"_":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"62":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":1,"docs":{"242":{"tf":1.0}}}},"m":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"239":{"tf":1.0}}}}},"df":0,"docs":{}},"n":{"d":{"/":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"c":{"df":1,"docs":{"254":{"tf":1.0}}},"df":0,"docs":{}}}},"df":8,"docs":{"128":{"tf":1.0},"226":{"tf":1.0},"231":{"tf":1.0},"245":{"tf":1.0},"259":{"tf":1.0},"283":{"tf":1.0},"48":{"tf":1.0},"7":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"98":{"tf":1.0}}}}},"t":{"df":4,"docs":{"201":{"tf":1.7320508075688772},"249":{"tf":1.0},"274":{"tf":1.0},"283":{"tf":1.4142135623730951}},"r":{"df":0,"docs":{},"i":{"df":4,"docs":{"150":{"tf":1.0},"154":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0}}},"y":{"/":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"152":{"tf":1.0}}}}}}}}}}}},"df":0,"docs":{}}}}},"p":{"a":{"df":0,"docs":{},"r":{"df":16,"docs":{"103":{"tf":1.0},"106":{"tf":1.0},"116":{"tf":1.0},"140":{"tf":1.0},"163":{"tf":1.0},"165":{"tf":1.0},"167":{"tf":1.0},"169":{"tf":1.0},"17":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.4142135623730951},"172":{"tf":1.7320508075688772},"20":{"tf":1.0},"220":{"tf":1.0},"221":{"tf":1.4142135623730951},"46":{"tf":1.0}}}},"df":0,"docs":{}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"37":{"tf":1.0}},"e":{"<":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"106":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"r":{"d":{"df":1,"docs":{"62":{"tf":1.0}}},"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"105":{"tf":1.0},"106":{"tf":1.0}}}},"df":0,"docs":{}},"v":{"df":8,"docs":{"107":{"tf":1.0},"167":{"tf":1.0},"196":{"tf":1.0},"249":{"tf":1.0},"252":{"tf":1.0},"26":{"tf":1.0},"273":{"tf":1.0},"274":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":24,"docs":{"189":{"tf":2.449489742783178},"190":{"tf":1.0},"194":{"tf":1.0},"201":{"tf":2.449489742783178},"225":{"tf":1.0},"226":{"tf":1.0},"229":{"tf":1.0},"232":{"tf":1.0},"233":{"tf":2.0},"236":{"tf":1.0},"242":{"tf":1.0},"244":{"tf":1.0},"245":{"tf":2.0},"246":{"tf":1.0},"248":{"tf":1.4142135623730951},"249":{"tf":2.23606797749979},"250":{"tf":1.4142135623730951},"254":{"tf":2.8284271247461903},"257":{"tf":1.0},"48":{"tf":1.0},"86":{"tf":1.0},"87":{"tf":1.0},"88":{"tf":1.7320508075688772},"89":{"tf":1.4142135623730951}}}},"i":{"c":{"df":81,"docs":{"0":{"tf":1.4142135623730951},"1":{"tf":1.0},"11":{"tf":1.4142135623730951},"114":{"tf":1.0},"118":{"tf":1.7320508075688772},"119":{"tf":1.0},"12":{"tf":2.0},"120":{"tf":1.0},"122":{"tf":2.0},"123":{"tf":1.0},"124":{"tf":2.0},"125":{"tf":1.4142135623730951},"14":{"tf":1.0},"15":{"tf":1.4142135623730951},"16":{"tf":1.0},"161":{"tf":1.4142135623730951},"162":{"tf":1.0},"164":{"tf":1.0},"165":{"tf":1.4142135623730951},"167":{"tf":3.0},"175":{"tf":1.4142135623730951},"18":{"tf":2.449489742783178},"186":{"tf":1.0},"187":{"tf":1.0},"188":{"tf":1.0},"19":{"tf":1.4142135623730951},"194":{"tf":1.0},"197":{"tf":1.4142135623730951},"2":{"tf":1.0},"20":{"tf":1.7320508075688772},"200":{"tf":2.449489742783178},"203":{"tf":1.0},"21":{"tf":1.4142135623730951},"218":{"tf":1.0},"22":{"tf":2.0},"220":{"tf":1.0},"225":{"tf":2.0},"226":{"tf":1.7320508075688772},"228":{"tf":1.0},"23":{"tf":1.7320508075688772},"232":{"tf":1.0},"236":{"tf":1.0},"24":{"tf":1.0},"244":{"tf":1.0},"25":{"tf":1.7320508075688772},"252":{"tf":1.4142135623730951},"253":{"tf":1.4142135623730951},"254":{"tf":1.0},"259":{"tf":1.4142135623730951},"260":{"tf":1.4142135623730951},"262":{"tf":1.0},"265":{"tf":1.0},"268":{"tf":1.7320508075688772},"274":{"tf":2.0},"276":{"tf":1.0},"28":{"tf":1.0},"283":{"tf":1.7320508075688772},"284":{"tf":1.0},"29":{"tf":1.0},"3":{"tf":1.4142135623730951},"30":{"tf":1.7320508075688772},"32":{"tf":1.4142135623730951},"33":{"tf":1.0},"34":{"tf":1.4142135623730951},"35":{"tf":1.7320508075688772},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.7320508075688772},"40":{"tf":1.4142135623730951},"41":{"tf":1.0},"49":{"tf":1.4142135623730951},"52":{"tf":1.4142135623730951},"54":{"tf":1.4142135623730951},"55":{"tf":1.0},"56":{"tf":1.0},"68":{"tf":1.4142135623730951},"69":{"tf":1.0},"7":{"tf":1.0},"74":{"tf":1.0},"8":{"tf":1.0},"9":{"tf":1.4142135623730951}},"e":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":1,"docs":{"242":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"242":{"tf":1.0}}}}}}},"s":{"%":{"2":{"df":0,"docs":{},"f":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"119":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{}},".":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"=":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"/":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"/":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"/":{"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"/":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"/":{"df":0,"docs":{},"o":{"df":0,"docs":{},"f":{"/":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"18":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"df":2,"docs":{"18":{"tf":1.0},"22":{"tf":2.6457513110645907}}}}},"df":0,"docs":{},"v":{"2":{".":{"b":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{".":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{".":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"k":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"/":{"d":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":1,"docs":{"266":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"y":{"/":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"266":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{".":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"/":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"c":{"%":{"2":{"df":0,"docs":{},"f":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"%":{"2":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{".":{"df":0,"docs":{},"j":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"271":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"/":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"/":{"df":0,"docs":{},"v":{"1":{"1":{"7":{".":{"0":{".":{".":{".":{"df":0,"docs":{},"v":{"1":{"1":{"8":{".":{"0":{"df":1,"docs":{"268":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"200":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"/":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"124":{"tf":1.0}}}}}}}}}}},"s":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"df":1,"docs":{"124":{"tf":1.0}}}},"y":{"df":0,"docs":{},"n":{"c":{"1":{"5":{"df":1,"docs":{"124":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":1,"docs":{"200":{"tf":1.0}}}}}}}}},"v":{"df":0,"docs":{},"i":{"a":{"d":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"124":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"w":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":1,"docs":{"124":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"277":{"tf":1.0}}}}}}}}},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"a":{"df":0,"docs":{},"z":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"d":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":2,"docs":{"29":{"tf":1.0},"34":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"/":{"5":{"3":{"0":{"2":{"df":1,"docs":{"186":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"7":{"4":{"5":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}},"6":{"4":{"df":1,"docs":{"268":{"tf":1.4142135623730951}}},"6":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}},"8":{"4":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":1,"docs":{"242":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"t":{"df":39,"docs":{"108":{"tf":1.7320508075688772},"109":{"tf":1.7320508075688772},"11":{"tf":2.0},"117":{"tf":2.449489742783178},"12":{"tf":2.0},"13":{"tf":1.4142135623730951},"134":{"tf":1.0},"17":{"tf":1.0},"170":{"tf":1.0},"179":{"tf":1.0},"186":{"tf":1.4142135623730951},"187":{"tf":1.4142135623730951},"188":{"tf":1.0},"190":{"tf":1.4142135623730951},"192":{"tf":1.4142135623730951},"193":{"tf":1.4142135623730951},"194":{"tf":1.0},"197":{"tf":1.4142135623730951},"198":{"tf":1.4142135623730951},"199":{"tf":1.0},"201":{"tf":2.0},"208":{"tf":1.7320508075688772},"213":{"tf":1.0},"232":{"tf":1.0},"233":{"tf":1.7320508075688772},"242":{"tf":1.0},"245":{"tf":1.0},"264":{"tf":1.4142135623730951},"268":{"tf":1.0},"277":{"tf":1.4142135623730951},"31":{"tf":1.0},"36":{"tf":1.0},"5":{"tf":1.0},"59":{"tf":1.7320508075688772},"60":{"tf":1.7320508075688772},"61":{"tf":1.0},"7":{"tf":1.0},"73":{"tf":1.0},"9":{"tf":1.0}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"d":{"df":0,"docs":{},"l":{"df":1,"docs":{"22":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"x":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":1,"docs":{"13":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}},"u":{"df":0,"docs":{},"p":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"11":{"tf":1.0}}}}},"df":15,"docs":{"10":{"tf":1.0},"11":{"tf":1.0},"12":{"tf":1.0},"122":{"tf":1.0},"13":{"tf":1.0},"14":{"tf":1.0},"161":{"tf":1.4142135623730951},"163":{"tf":1.4142135623730951},"166":{"tf":1.4142135623730951},"167":{"tf":1.4142135623730951},"169":{"tf":1.0},"175":{"tf":1.7320508075688772},"22":{"tf":1.7320508075688772},"224":{"tf":1.0},"73":{"tf":1.0}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":6,"docs":{"116":{"tf":1.0},"132":{"tf":1.0},"154":{"tf":1.0},"271":{"tf":1.0},"58":{"tf":1.0},"60":{"tf":1.0}}}}}},"h":{"a":{"2":{"5":{"6":{"df":1,"docs":{"280":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":5,"docs":{"105":{"tf":1.0},"106":{"tf":1.0},"142":{"tf":1.0},"283":{"tf":1.0},"58":{"tf":1.0}}}},"r":{"df":0,"docs":{},"e":{"df":13,"docs":{"0":{"tf":1.0},"132":{"tf":1.0},"163":{"tf":1.0},"172":{"tf":1.0},"200":{"tf":2.23606797749979},"221":{"tf":1.0},"222":{"tf":1.4142135623730951},"225":{"tf":1.7320508075688772},"248":{"tf":1.0},"257":{"tf":1.0},"60":{"tf":1.7320508075688772},"79":{"tf":1.0},"81":{"tf":1.0}}}},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"280":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":2,"docs":{"12":{"tf":1.0},"167":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"p":{"df":11,"docs":{"187":{"tf":1.0},"219":{"tf":1.4142135623730951},"220":{"tf":1.0},"221":{"tf":1.0},"251":{"tf":1.4142135623730951},"262":{"tf":1.0},"267":{"tf":1.4142135623730951},"268":{"tf":1.4142135623730951},"273":{"tf":1.0},"64":{"tf":1.4142135623730951},"73":{"tf":1.0}},"p":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"267":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"252":{"tf":1.0}}}}},"df":3,"docs":{"179":{"tf":1.0},"241":{"tf":1.0},"81":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"209":{"tf":1.0}}}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"184":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"l":{"d":{"df":0,"docs":{},"n":{"'":{"df":0,"docs":{},"t":{"df":2,"docs":{"241":{"tf":1.0},"258":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"y":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"_":{"c":{"a":{"df":0,"docs":{},"s":{"df":1,"docs":{"108":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"w":{"df":9,"docs":{"12":{"tf":1.0},"123":{"tf":1.0},"167":{"tf":1.0},"254":{"tf":1.0},"255":{"tf":1.0},"256":{"tf":1.0},"257":{"tf":1.0},"258":{"tf":1.0},"282":{"tf":1.0}}}}},"i":{"d":{"df":0,"docs":{},"e":{"b":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"284":{"tf":1.0}}}},"df":0,"docs":{}},"df":4,"docs":{"12":{"tf":1.4142135623730951},"233":{"tf":1.0},"254":{"tf":1.0},"69":{"tf":1.0}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":6,"docs":{"189":{"tf":1.0},"193":{"tf":1.0},"194":{"tf":1.0},"196":{"tf":1.0},"198":{"tf":1.0},"201":{"tf":2.0}}}}}},"df":3,"docs":{"274":{"tf":2.449489742783178},"283":{"tf":2.449489742783178},"7":{"tf":1.0}},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"c":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"17":{"tf":1.0}}}}}}},"df":6,"docs":{"158":{"tf":1.0},"196":{"tf":1.0},"201":{"tf":1.0},"203":{"tf":1.0},"205":{"tf":1.0},"214":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}}},"l":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":6,"docs":{"161":{"tf":1.0},"162":{"tf":1.0},"163":{"tf":1.0},"169":{"tf":1.0},"170":{"tf":1.0},"175":{"tf":1.0}}}}},"df":0,"docs":{}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"r":{"df":6,"docs":{"143":{"tf":1.0},"158":{"tf":1.0},"163":{"tf":1.0},"171":{"tf":1.0},"200":{"tf":1.0},"81":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"106":{"tf":1.0}}}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"l":{"df":7,"docs":{"106":{"tf":1.4142135623730951},"118":{"tf":1.0},"128":{"tf":1.0},"60":{"tf":1.0},"78":{"tf":1.0},"81":{"tf":1.0},"87":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":7,"docs":{"106":{"tf":1.0},"115":{"tf":1.0},"124":{"tf":1.0},"152":{"tf":1.0},"226":{"tf":1.0},"231":{"tf":1.0},"252":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"129":{"tf":1.0},"159":{"tf":1.0}}}}},"i":{"c":{"df":1,"docs":{"178":{"tf":1.0}}},"df":5,"docs":{"128":{"tf":1.0},"149":{"tf":1.0},"222":{"tf":1.0},"231":{"tf":1.0},"26":{"tf":1.0}},"f":{"df":0,"docs":{},"i":{"df":4,"docs":{"103":{"tf":1.0},"164":{"tf":1.0},"172":{"tf":1.0},"69":{"tf":1.0}}}}}}},"u":{"df":0,"docs":{},"l":{"df":1,"docs":{"175":{"tf":1.0}},"t":{"a":{"df":0,"docs":{},"n":{"df":2,"docs":{"124":{"tf":1.0},"205":{"tf":1.0}}}},"df":0,"docs":{}}}}},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"l":{"df":19,"docs":{"115":{"tf":1.0},"16":{"tf":1.0},"163":{"tf":1.0},"166":{"tf":1.0},"167":{"tf":1.0},"171":{"tf":1.7320508075688772},"172":{"tf":1.4142135623730951},"174":{"tf":1.0},"175":{"tf":1.4142135623730951},"22":{"tf":1.0},"220":{"tf":1.4142135623730951},"221":{"tf":1.0},"225":{"tf":1.4142135623730951},"226":{"tf":1.4142135623730951},"228":{"tf":1.4142135623730951},"232":{"tf":1.4142135623730951},"249":{"tf":1.0},"282":{"tf":1.0},"46":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"130":{"tf":1.4142135623730951},"78":{"tf":1.0}}}}}}}}},"t":{"df":1,"docs":{"40":{"tf":1.0}},"e":{"df":2,"docs":{"205":{"tf":1.4142135623730951},"218":{"tf":1.0}}},"u":{"a":{"df":0,"docs":{},"t":{"df":5,"docs":{"115":{"tf":1.0},"128":{"tf":1.0},"154":{"tf":1.0},"220":{"tf":1.0},"249":{"tf":1.0}}}},"df":0,"docs":{}}},"z":{"df":0,"docs":{},"e":{"df":12,"docs":{"165":{"tf":1.0},"170":{"tf":1.0},"205":{"tf":1.7320508075688772},"206":{"tf":1.4142135623730951},"208":{"tf":2.0},"209":{"tf":1.7320508075688772},"212":{"tf":1.0},"218":{"tf":1.0},"220":{"tf":1.0},"222":{"tf":1.0},"280":{"tf":1.0},"6":{"tf":1.0}}}}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"252":{"tf":1.0}}}}}}},"t":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"167":{"tf":1.0}}}},"df":0,"docs":{}}}},"l":{"a":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"274":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":1,"docs":{"236":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":2,"docs":{"128":{"tf":1.0},"243":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"w":{"df":3,"docs":{"183":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0}}}}},"m":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":6,"docs":{"104":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"170":{"tf":1.0},"227":{"tf":1.0},"229":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"59":{"tf":1.4142135623730951},"6":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"26":{"tf":1.0}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":1,"docs":{"52":{"tf":1.4142135623730951}}}}}},"n":{"a":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"_":{"c":{"a":{"df":0,"docs":{},"s":{"df":1,"docs":{"93":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"$":{"df":0,"docs":{},"n":{"df":1,"docs":{"22":{"tf":1.0}}}},"df":2,"docs":{"22":{"tf":1.0},"268":{"tf":1.0}}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"37":{"tf":1.0}}}}}}}},"o":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":1,"docs":{"274":{"tf":1.0}}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"r":{"df":2,"docs":{"274":{"tf":1.0},"64":{"tf":1.0}}}},"df":0,"docs":{}}}},"l":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"200":{"tf":1.0}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"t":{"df":4,"docs":{"152":{"tf":1.4142135623730951},"187":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0}}}},"v":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"115":{"tf":1.0}}}},"df":0,"docs":{}},"df":1,"docs":{"194":{"tf":1.0}}}},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"i":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":1,"docs":{"22":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"122":{"tf":1.0}}}},"t":{"df":0,"docs":{},"h":{"df":24,"docs":{"103":{"tf":1.0},"106":{"tf":1.0},"117":{"tf":1.0},"124":{"tf":1.0},"197":{"tf":1.4142135623730951},"200":{"tf":1.0},"22":{"tf":1.0},"224":{"tf":1.4142135623730951},"237":{"tf":1.0},"242":{"tf":1.4142135623730951},"26":{"tf":1.0},"277":{"tf":1.0},"280":{"tf":1.0},"283":{"tf":1.0},"43":{"tf":1.0},"58":{"tf":1.4142135623730951},"6":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.4142135623730951},"70":{"tf":1.0},"72":{"tf":1.0},"73":{"tf":1.7320508075688772},"79":{"tf":1.0},"84":{"tf":1.0}}},"i":{"df":0,"docs":{},"m":{"df":3,"docs":{"124":{"tf":1.0},"158":{"tf":1.0},"43":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"129":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}},"w":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"t":{"df":4,"docs":{"115":{"tf":1.0},"116":{"tf":1.0},"235":{"tf":1.0},"261":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"21":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"44":{"tf":1.0},"58":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"6":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"104":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"r":{"c":{"df":19,"docs":{"1":{"tf":1.0},"109":{"tf":1.0},"11":{"tf":1.0},"163":{"tf":1.0},"164":{"tf":1.0},"165":{"tf":1.0},"172":{"tf":1.4142135623730951},"2":{"tf":1.0},"253":{"tf":1.0},"263":{"tf":1.0},"274":{"tf":1.0},"37":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.4142135623730951},"7":{"tf":1.0},"73":{"tf":2.0},"74":{"tf":1.4142135623730951},"81":{"tf":1.0},"98":{"tf":1.7320508075688772}},"e":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"74":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"p":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":1,"docs":{"43":{"tf":1.0}}}},"w":{"df":0,"docs":{},"n":{"df":2,"docs":{"115":{"tf":1.0},"22":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"221":{"tf":1.0},"236":{"tf":1.0}}}},"df":0,"docs":{},"f":{"df":22,"docs":{"103":{"tf":1.0},"105":{"tf":1.0},"116":{"tf":1.0},"12":{"tf":1.0},"123":{"tf":1.0},"128":{"tf":1.0},"175":{"tf":1.0},"190":{"tf":1.0},"200":{"tf":1.0},"214":{"tf":1.0},"22":{"tf":1.4142135623730951},"225":{"tf":1.4142135623730951},"227":{"tf":1.4142135623730951},"229":{"tf":1.0},"235":{"tf":1.0},"279":{"tf":1.0},"280":{"tf":1.0},"282":{"tf":1.0},"45":{"tf":1.0},"58":{"tf":1.0},"70":{"tf":1.0},"72":{"tf":1.0}},"i":{"df":10,"docs":{"123":{"tf":1.7320508075688772},"124":{"tf":1.4142135623730951},"22":{"tf":1.4142135623730951},"222":{"tf":1.0},"236":{"tf":1.0},"245":{"tf":1.0},"252":{"tf":1.4142135623730951},"263":{"tf":1.0},"274":{"tf":1.0},"37":{"tf":1.0}}}}}},"d":{"df":1,"docs":{"17":{"tf":1.0}}},"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"104":{"tf":1.0}}},"df":0,"docs":{},"t":{"df":1,"docs":{"201":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"184":{"tf":1.0}}}}}}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":4,"docs":{"163":{"tf":1.0},"198":{"tf":1.0},"225":{"tf":1.0},"40":{"tf":1.4142135623730951}}}}},"r":{"a":{"df":0,"docs":{},"y":{"df":1,"docs":{"227":{"tf":1.0}}}},"df":0,"docs":{}}},"q":{"df":0,"docs":{},"l":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{":":{":":{"d":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"s":{":":{":":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"_":{"d":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"(":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":1,"docs":{"62":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"(":{"&":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":1,"docs":{"62":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"62":{"tf":1.0}}}}}}}}}},"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":4,"docs":{"11":{"tf":1.7320508075688772},"140":{"tf":1.4142135623730951},"264":{"tf":1.4142135623730951},"67":{"tf":1.0}}}}}}}},"df":3,"docs":{"124":{"tf":1.0},"62":{"tf":2.449489742783178},"81":{"tf":1.7320508075688772}},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"'":{"df":1,"docs":{"81":{"tf":1.0}}},"_":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"_":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"264":{"tf":1.0}}}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}}}}}}},"df":7,"docs":{"140":{"tf":1.0},"149":{"tf":1.0},"264":{"tf":1.4142135623730951},"62":{"tf":1.0},"79":{"tf":1.7320508075688772},"81":{"tf":1.0},"82":{"tf":1.0}}}}}}},"r":{"c":{"/":{"*":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"<":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{">":{".":{"df":0,"docs":{},"u":{"d":{"df":0,"docs":{},"l":{"df":1,"docs":{"104":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"104":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}},"_":{"df":0,"docs":{},"m":{"df":0,"docs":{},"s":{"df":0,"docs":{},"g":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":1,"docs":{"106":{"tf":1.0}}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"_":{"c":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{">":{".":{"df":0,"docs":{},"u":{"d":{"df":0,"docs":{},"l":{"df":1,"docs":{"73":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"60":{"tf":1.7320508075688772}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"b":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"60":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{".":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"<":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{">":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"f":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"106":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"60":{"tf":1.7320508075688772}}}},"df":0,"docs":{}}}}}}},"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":1,"docs":{"224":{"tf":1.0}}}}},"df":5,"docs":{"103":{"tf":2.23606797749979},"60":{"tf":1.0},"61":{"tf":2.23606797749979},"70":{"tf":1.4142135623730951},"72":{"tf":1.0}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"11":{"tf":1.0}}}},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":4,"docs":{"192":{"tf":1.0},"258":{"tf":1.0},"264":{"tf":1.0},"274":{"tf":1.0}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":1,"docs":{"260":{"tf":1.0}}}},"g":{"df":0,"docs":{},"e":{"df":10,"docs":{"131":{"tf":1.0},"248":{"tf":1.0},"249":{"tf":2.449489742783178},"250":{"tf":1.4142135623730951},"267":{"tf":1.0},"271":{"tf":1.4142135623730951},"273":{"tf":1.4142135623730951},"30":{"tf":1.0},"35":{"tf":1.0},"48":{"tf":1.0}}}},"l":{"df":0,"docs":{},"e":{"df":1,"docs":{"263":{"tf":1.0}}}},"n":{"d":{"a":{"df":0,"docs":{},"r":{"d":{"df":3,"docs":{"107":{"tf":1.0},"141":{"tf":1.0},"8":{"tf":1.0}}},"df":0,"docs":{}}},"df":3,"docs":{"198":{"tf":1.0},"220":{"tf":1.0},"237":{"tf":1.0}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"t":{"df":26,"docs":{"101":{"tf":1.4142135623730951},"105":{"tf":1.0},"106":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"121":{"tf":1.0},"122":{"tf":1.0},"138":{"tf":1.0},"149":{"tf":1.0},"158":{"tf":1.0},"214":{"tf":1.0},"22":{"tf":1.0},"225":{"tf":1.4142135623730951},"227":{"tf":1.4142135623730951},"233":{"tf":1.0},"244":{"tf":1.0},"245":{"tf":1.0},"250":{"tf":1.4142135623730951},"257":{"tf":1.0},"268":{"tf":1.0},"269":{"tf":1.0},"37":{"tf":1.0},"58":{"tf":1.4142135623730951},"62":{"tf":1.0},"75":{"tf":1.4142135623730951},"84":{"tf":1.4142135623730951}},"u":{"df":0,"docs":{},"p":{"df":8,"docs":{"134":{"tf":1.0},"140":{"tf":1.0},"176":{"tf":1.0},"180":{"tf":1.0},"184":{"tf":1.0},"242":{"tf":1.0},"278":{"tf":1.0},"283":{"tf":1.7320508075688772}}}}}},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"115":{"tf":1.0}}}},"t":{"df":0,"docs":{},"e":{"df":14,"docs":{"189":{"tf":1.0},"205":{"tf":1.0},"225":{"tf":1.0},"226":{"tf":1.4142135623730951},"229":{"tf":4.242640687119285},"233":{"tf":2.8284271247461903},"236":{"tf":1.4142135623730951},"237":{"tf":1.0},"239":{"tf":1.4142135623730951},"242":{"tf":2.23606797749979},"245":{"tf":2.0},"249":{"tf":2.0},"283":{"tf":2.0},"87":{"tf":1.0}},"f":{"df":0,"docs":{},"x":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"n":{"d":{"a":{"df":1,"docs":{"283":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}}},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":10,"docs":{"136":{"tf":1.0},"137":{"tf":1.0},"140":{"tf":1.0},"149":{"tf":1.0},"161":{"tf":1.0},"177":{"tf":1.0},"187":{"tf":1.0},"203":{"tf":1.0},"205":{"tf":1.0},"62":{"tf":1.0}}}}}}},"i":{"c":{"df":10,"docs":{"167":{"tf":1.0},"242":{"tf":1.0},"251":{"tf":1.0},"278":{"tf":1.0},"28":{"tf":1.0},"280":{"tf":1.0},"282":{"tf":1.0},"284":{"tf":1.0},"33":{"tf":1.0},"93":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"214":{"tf":1.4142135623730951}}}}},"u":{"df":10,"docs":{"139":{"tf":1.0},"148":{"tf":1.0},"160":{"tf":1.0},"171":{"tf":1.0},"176":{"tf":1.0},"186":{"tf":1.0},"202":{"tf":1.0},"22":{"tf":1.0},"242":{"tf":1.0},"257":{"tf":1.4142135623730951}}}}},"d":{":":{":":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{":":{":":{"df":0,"docs":{},"w":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"v":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"(":{"0":{"df":1,"docs":{"117":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":1,"docs":{"62":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"220":{"tf":1.0}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"62":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":28,"docs":{"105":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.4142135623730951},"111":{"tf":1.0},"117":{"tf":1.4142135623730951},"122":{"tf":1.0},"14":{"tf":1.0},"15":{"tf":1.0},"16":{"tf":1.0},"21":{"tf":1.0},"22":{"tf":1.7320508075688772},"25":{"tf":2.6457513110645907},"264":{"tf":1.0},"268":{"tf":1.0},"27":{"tf":1.4142135623730951},"274":{"tf":3.0},"279":{"tf":1.0},"282":{"tf":1.4142135623730951},"29":{"tf":1.4142135623730951},"30":{"tf":1.0},"31":{"tf":1.4142135623730951},"32":{"tf":1.0},"34":{"tf":1.4142135623730951},"35":{"tf":1.0},"36":{"tf":1.4142135623730951},"37":{"tf":1.0},"7":{"tf":1.0},"73":{"tf":1.0}}}},"i":{"c":{"df":0,"docs":{},"k":{"df":3,"docs":{"180":{"tf":1.0},"201":{"tf":1.0},"60":{"tf":1.0}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":18,"docs":{"103":{"tf":1.0},"11":{"tf":1.0},"116":{"tf":1.0},"124":{"tf":1.4142135623730951},"182":{"tf":1.0},"184":{"tf":1.0},"200":{"tf":1.0},"209":{"tf":1.0},"217":{"tf":1.0},"241":{"tf":1.0},"242":{"tf":1.0},"262":{"tf":1.0},"263":{"tf":1.0},"48":{"tf":1.0},"49":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.4142135623730951},"75":{"tf":1.0}}}},"t":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"252":{"tf":1.0}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":2,"docs":{"231":{"tf":1.0},"85":{"tf":1.0}}}}},"r":{"a":{"df":0,"docs":{},"g":{"df":13,"docs":{"123":{"tf":1.0},"124":{"tf":1.4142135623730951},"140":{"tf":1.4142135623730951},"229":{"tf":2.449489742783178},"232":{"tf":1.0},"254":{"tf":1.4142135623730951},"257":{"tf":2.0},"264":{"tf":1.4142135623730951},"273":{"tf":1.0},"284":{"tf":1.0},"78":{"tf":1.4142135623730951},"79":{"tf":1.0},"86":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":18,"docs":{"130":{"tf":1.4142135623730951},"131":{"tf":1.4142135623730951},"184":{"tf":1.0},"187":{"tf":1.0},"229":{"tf":1.0},"231":{"tf":1.0},"232":{"tf":3.4641016151377544},"237":{"tf":1.0},"242":{"tf":3.1622776601683795},"245":{"tf":1.7320508075688772},"248":{"tf":2.0},"254":{"tf":1.4142135623730951},"78":{"tf":2.6457513110645907},"81":{"tf":1.0},"83":{"tf":1.0},"84":{"tf":1.0},"86":{"tf":1.0},"87":{"tf":1.0}},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"i":{"df":1,"docs":{"87":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"i":{"df":2,"docs":{"139":{"tf":1.0},"176":{"tf":1.0}}}}},"r":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"167":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}},"n":{"df":0,"docs":{},"g":{"df":2,"docs":{"158":{"tf":1.0},"221":{"tf":1.0}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":5,"docs":{"219":{"tf":1.4142135623730951},"251":{"tf":1.4142135623730951},"257":{"tf":1.0},"43":{"tf":1.0},"84":{"tf":1.0}}}}}},"w":{"df":2,"docs":{"236":{"tf":1.4142135623730951},"242":{"tf":1.7320508075688772}}}},"df":1,"docs":{"242":{"tf":1.0}},"e":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"257":{"tf":1.0}}}}}}},"df":0,"docs":{}},"i":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"172":{"tf":1.0},"43":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":3,"docs":{"200":{"tf":1.0},"201":{"tf":1.0},"242":{"tf":1.0}}}}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"/":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"189":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":5,"docs":{"116":{"tf":1.0},"242":{"tf":2.23606797749979},"25":{"tf":1.0},"279":{"tf":1.0},"97":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"201":{"tf":1.0}}}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"189":{"tf":1.0}}}}}}},"u":{"c":{"df":0,"docs":{},"t":{"df":8,"docs":{"105":{"tf":1.0},"106":{"tf":1.4142135623730951},"108":{"tf":1.0},"109":{"tf":1.0},"242":{"tf":2.6457513110645907},"249":{"tf":1.0},"93":{"tf":1.0},"94":{"tf":1.0}},"s":{"a":{"c":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"283":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"r":{"df":15,"docs":{"103":{"tf":1.0},"104":{"tf":1.0},"106":{"tf":2.23606797749979},"136":{"tf":1.0},"137":{"tf":1.0},"138":{"tf":1.0},"175":{"tf":1.0},"241":{"tf":1.0},"242":{"tf":2.0},"60":{"tf":1.0},"69":{"tf":1.0},"70":{"tf":1.0},"72":{"tf":1.4142135623730951},"76":{"tf":1.4142135623730951},"83":{"tf":1.0}}}}}},"df":0,"docs":{}}},"u":{"b":{"df":1,"docs":{"200":{"tf":1.0}}},"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"21":{"tf":1.0}}}},"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":7,"docs":{"11":{"tf":1.0},"117":{"tf":1.4142135623730951},"12":{"tf":1.4142135623730951},"126":{"tf":1.4142135623730951},"13":{"tf":1.0},"19":{"tf":1.0},"37":{"tf":1.0}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":3,"docs":{"13":{"tf":1.0},"226":{"tf":1.0},"90":{"tf":1.0}}}}},"y":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":6,"docs":{"103":{"tf":2.449489742783178},"104":{"tf":1.0},"138":{"tf":1.0},"37":{"tf":1.4142135623730951},"93":{"tf":1.0},"99":{"tf":1.0}}}}}},"u":{"b":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":4,"docs":{"252":{"tf":1.7320508075688772},"70":{"tf":1.0},"72":{"tf":1.4142135623730951},"73":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}},"df":3,"docs":{"105":{"tf":1.7320508075688772},"73":{"tf":1.0},"83":{"tf":1.0}},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":1,"docs":{"200":{"tf":1.7320508075688772}}}}},"df":0,"docs":{}}},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"2":{"tf":1.0},"226":{"tf":1.0},"8":{"tf":1.0}}}},"df":0,"docs":{}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"226":{"tf":1.0}}}},"t":{"df":2,"docs":{"226":{"tf":1.0},"7":{"tf":2.449489742783178}}}},"o":{"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":5,"docs":{"11":{"tf":1.0},"167":{"tf":1.7320508075688772},"60":{"tf":1.0},"61":{"tf":1.0},"83":{"tf":1.0}}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"117":{"tf":1.0}}}}}}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"22":{"tf":1.0}}}}}},"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"22":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}}}},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"283":{"tf":1.0}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":2,"docs":{"274":{"tf":1.0},"283":{"tf":1.0}}}},"t":{"df":4,"docs":{"165":{"tf":1.0},"167":{"tf":1.0},"228":{"tf":1.0},"54":{"tf":1.0}}}},"t":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"59":{"tf":1.0}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"22":{"tf":1.4142135623730951},"52":{"tf":1.0}}}}}}},"y":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":1,"docs":{"11":{"tf":1.0}}}}}}}},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":2,"docs":{"108":{"tf":1.0},"109":{"tf":1.0}}}}}},"c":{"c":{"df":0,"docs":{},"e":{"df":3,"docs":{"104":{"tf":1.0},"208":{"tf":1.0},"212":{"tf":1.7320508075688772}},"e":{"d":{"df":1,"docs":{"209":{"tf":1.0}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"s":{"df":8,"docs":{"12":{"tf":1.0},"204":{"tf":1.0},"208":{"tf":1.0},"214":{"tf":1.0},"216":{"tf":2.23606797749979},"217":{"tf":1.0},"266":{"tf":1.0},"271":{"tf":1.4142135623730951}},"f":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":2,"docs":{"106":{"tf":1.0},"11":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{},"h":{"df":23,"docs":{"102":{"tf":1.0},"109":{"tf":1.0},"112":{"tf":1.0},"115":{"tf":1.0},"16":{"tf":1.0},"188":{"tf":1.0},"190":{"tf":1.0},"196":{"tf":1.4142135623730951},"220":{"tf":1.0},"222":{"tf":1.0},"223":{"tf":1.0},"226":{"tf":1.0},"228":{"tf":1.0},"229":{"tf":1.0},"231":{"tf":1.0},"236":{"tf":1.0},"237":{"tf":1.0},"49":{"tf":1.0},"51":{"tf":1.0},"58":{"tf":1.0},"62":{"tf":1.0},"64":{"tf":1.0},"7":{"tf":1.0}}}},"d":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"213":{"tf":1.0}}}}}}},"df":0,"docs":{},"o":{"df":2,"docs":{"11":{"tf":2.23606797749979},"13":{"tf":1.0}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":2,"docs":{"194":{"tf":1.0},"274":{"tf":1.0}}}},"df":0,"docs":{},"x":{"df":1,"docs":{"22":{"tf":1.0}}}}}},"g":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":7,"docs":{"200":{"tf":1.0},"204":{"tf":1.0},"209":{"tf":1.0},"214":{"tf":2.0},"217":{"tf":1.4142135623730951},"225":{"tf":1.0},"268":{"tf":1.7320508075688772}}}}}}},"i":{"df":0,"docs":{},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":8,"docs":{"122":{"tf":1.0},"187":{"tf":1.4142135623730951},"189":{"tf":1.0},"191":{"tf":1.0},"192":{"tf":1.0},"197":{"tf":1.0},"223":{"tf":1.0},"64":{"tf":1.0}}}},"df":0,"docs":{}},"df":9,"docs":{"223":{"tf":1.0},"41":{"tf":1.0},"43":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"48":{"tf":1.0},"49":{"tf":1.4142135623730951},"58":{"tf":1.0},"7":{"tf":1.4142135623730951}}}},"m":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"84":{"tf":1.0}},"i":{"df":2,"docs":{"64":{"tf":1.7320508075688772},"7":{"tf":1.0}}},"y":{".":{"df":0,"docs":{},"m":{"d":{"df":1,"docs":{"284":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"d":{"df":1,"docs":{"90":{"tf":1.0}}},"df":0,"docs":{}}}}},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":4,"docs":{"242":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"78":{"tf":1.0}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"/":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":1,"docs":{"59":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"df":1,"docs":{"62":{"tf":1.4142135623730951}}}},"y":{"df":0,"docs":{},"n":{"c":{"1":{"5":{"df":2,"docs":{"244":{"tf":1.0},"246":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"[":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"200":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":34,"docs":{"103":{"tf":1.0},"104":{"tf":1.0},"116":{"tf":1.0},"124":{"tf":1.4142135623730951},"128":{"tf":1.0},"140":{"tf":1.0},"141":{"tf":1.0},"145":{"tf":1.0},"16":{"tf":1.0},"163":{"tf":1.0},"167":{"tf":1.0},"170":{"tf":1.7320508075688772},"171":{"tf":1.0},"172":{"tf":1.0},"175":{"tf":1.4142135623730951},"18":{"tf":1.0},"189":{"tf":1.0},"190":{"tf":1.0},"193":{"tf":1.0},"194":{"tf":1.0},"196":{"tf":1.0},"200":{"tf":2.23606797749979},"212":{"tf":1.0},"22":{"tf":1.7320508075688772},"223":{"tf":1.0},"231":{"tf":1.0},"242":{"tf":1.4142135623730951},"263":{"tf":1.0},"274":{"tf":1.0},"282":{"tf":1.4142135623730951},"38":{"tf":1.0},"62":{"tf":1.4142135623730951},"79":{"tf":1.0},"81":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"e":{"df":18,"docs":{"102":{"tf":1.0},"104":{"tf":1.0},"106":{"tf":1.0},"11":{"tf":1.7320508075688772},"117":{"tf":1.4142135623730951},"120":{"tf":1.0},"126":{"tf":1.0},"200":{"tf":1.0},"201":{"tf":1.0},"21":{"tf":1.0},"226":{"tf":1.0},"241":{"tf":1.0},"25":{"tf":1.4142135623730951},"26":{"tf":1.0},"270":{"tf":1.0},"36":{"tf":1.0},"7":{"tf":1.0},"73":{"tf":1.4142135623730951}}},"f":{"a":{"c":{"df":3,"docs":{"105":{"tf":1.0},"26":{"tf":1.0},"61":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":2,"docs":{"260":{"tf":1.0},"261":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"282":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"137":{"tf":1.0}}}}},"df":0,"docs":{}}}},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"'":{"df":1,"docs":{"175":{"tf":1.0}}},".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"74":{"tf":1.0}}}}},"/":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":0,"docs":{},"f":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"d":{"df":1,"docs":{"119":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":55,"docs":{"101":{"tf":1.0},"103":{"tf":2.449489742783178},"104":{"tf":1.0},"109":{"tf":3.4641016151377544},"116":{"tf":1.0},"119":{"tf":1.0},"125":{"tf":1.0},"134":{"tf":1.0},"160":{"tf":1.0},"162":{"tf":1.0},"163":{"tf":2.0},"164":{"tf":1.0},"165":{"tf":2.23606797749979},"166":{"tf":1.0},"167":{"tf":3.605551275463989},"169":{"tf":1.7320508075688772},"170":{"tf":2.0},"171":{"tf":2.8284271247461903},"172":{"tf":3.1622776601683795},"174":{"tf":2.449489742783178},"175":{"tf":1.4142135623730951},"196":{"tf":1.0},"219":{"tf":1.4142135623730951},"225":{"tf":1.0},"23":{"tf":2.6457513110645907},"24":{"tf":1.0},"241":{"tf":2.449489742783178},"25":{"tf":1.7320508075688772},"251":{"tf":2.23606797749979},"252":{"tf":1.0},"253":{"tf":3.1622776601683795},"26":{"tf":2.23606797749979},"268":{"tf":1.4142135623730951},"271":{"tf":1.7320508075688772},"272":{"tf":1.4142135623730951},"274":{"tf":2.23606797749979},"28":{"tf":1.0},"29":{"tf":2.0},"30":{"tf":1.4142135623730951},"31":{"tf":3.1622776601683795},"32":{"tf":2.6457513110645907},"33":{"tf":1.0},"34":{"tf":2.23606797749979},"35":{"tf":1.4142135623730951},"36":{"tf":3.1622776601683795},"46":{"tf":2.23606797749979},"66":{"tf":1.4142135623730951},"68":{"tf":1.0},"7":{"tf":1.4142135623730951},"71":{"tf":1.0},"72":{"tf":2.449489742783178},"73":{"tf":2.6457513110645907},"74":{"tf":2.6457513110645907},"95":{"tf":1.0},"96":{"tf":1.4142135623730951}},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"7":{"tf":1.0}}}},"df":0,"docs":{}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":1,"docs":{"7":{"tf":1.0}}}}}}}},"t":{"c":{"df":0,"docs":{},"h":{"df":2,"docs":{"208":{"tf":1.0},"26":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"y":{"df":0,"docs":{},"m":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":3,"docs":{"175":{"tf":1.0},"274":{"tf":1.0},"282":{"tf":1.0}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"k":{"df":1,"docs":{"11":{"tf":1.0}}}}}}},"n":{"c":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"242":{"tf":1.0}}}}}}},"df":0,"docs":{}},"1":{"5":{"_":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"d":{"df":0,"docs":{},"s":{"_":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"_":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"94":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":4,"docs":{"124":{"tf":1.0},"244":{"tf":1.0},"245":{"tf":1.0},"249":{"tf":1.4142135623730951}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"242":{"tf":1.0}}}}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"_":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"83":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"84":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"88":{"tf":1.4142135623730951}}}}}}}},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"83":{"tf":1.0}}}}}}}}},"m":{"a":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"244":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"245":{"tf":1.4142135623730951}}}}}}}}}},"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"75":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"130":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}}}}}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"250":{"tf":1.4142135623730951},"62":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":45,"docs":{"0":{"tf":1.0},"1":{"tf":1.4142135623730951},"145":{"tf":1.0},"156":{"tf":1.0},"206":{"tf":1.4142135623730951},"213":{"tf":1.4142135623730951},"217":{"tf":1.0},"218":{"tf":1.4142135623730951},"219":{"tf":2.23606797749979},"225":{"tf":3.4641016151377544},"226":{"tf":3.4641016151377544},"228":{"tf":2.23606797749979},"229":{"tf":1.7320508075688772},"231":{"tf":2.0},"232":{"tf":2.0},"233":{"tf":2.0},"234":{"tf":1.4142135623730951},"236":{"tf":1.4142135623730951},"241":{"tf":1.7320508075688772},"242":{"tf":3.872983346207417},"243":{"tf":1.7320508075688772},"244":{"tf":2.6457513110645907},"245":{"tf":3.0},"246":{"tf":2.0},"249":{"tf":1.4142135623730951},"250":{"tf":1.7320508075688772},"254":{"tf":3.1622776601683795},"255":{"tf":2.0},"256":{"tf":2.0},"257":{"tf":2.449489742783178},"258":{"tf":1.7320508075688772},"264":{"tf":1.4142135623730951},"284":{"tf":1.0},"48":{"tf":2.449489742783178},"51":{"tf":1.0},"79":{"tf":1.4142135623730951},"80":{"tf":1.4142135623730951},"82":{"tf":1.7320508075688772},"83":{"tf":1.0},"84":{"tf":3.4641016151377544},"85":{"tf":1.0},"86":{"tf":2.0},"87":{"tf":1.0},"88":{"tf":1.7320508075688772},"89":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":4,"docs":{"244":{"tf":2.0},"245":{"tf":2.8284271247461903},"246":{"tf":1.4142135623730951},"247":{"tf":1.0}},"e":{".":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"y":{"_":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":1,"docs":{"246":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":1,"docs":{"97":{"tf":1.0}}}}},"m":{"a":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"g":{"df":3,"docs":{"242":{"tf":1.4142135623730951},"244":{"tf":1.0},"245":{"tf":2.6457513110645907}},"e":{"df":0,"docs":{},"r":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"244":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"p":{"a":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"242":{"tf":1.4142135623730951},"245":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"242":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"242":{"tf":1.0}}}}}}}},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":1,"docs":{"62":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"t":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"104":{"tf":1.0}}}},"df":0,"docs":{},"x":{"df":1,"docs":{"73":{"tf":1.0}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"(":{"df":1,"docs":{"280":{"tf":1.0}}},"df":13,"docs":{"10":{"tf":1.0},"11":{"tf":2.23606797749979},"118":{"tf":1.0},"12":{"tf":1.4142135623730951},"169":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.4142135623730951},"189":{"tf":1.0},"201":{"tf":1.7320508075688772},"37":{"tf":1.4142135623730951},"67":{"tf":1.4142135623730951},"7":{"tf":1.0}}}}}}}},"t":{"a":{"b":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"s":{"a":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"283":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":8,"docs":{"123":{"tf":1.0},"126":{"tf":1.0},"231":{"tf":1.0},"245":{"tf":1.0},"257":{"tf":1.0},"264":{"tf":1.7320508075688772},"283":{"tf":1.0},"48":{"tf":1.0}},"l":{"df":11,"docs":{"248":{"tf":2.6457513110645907},"249":{"tf":3.1622776601683795},"250":{"tf":1.4142135623730951},"257":{"tf":1.0},"284":{"tf":1.0},"62":{"tf":1.4142135623730951},"80":{"tf":1.4142135623730951},"81":{"tf":1.7320508075688772},"82":{"tf":1.7320508075688772},"84":{"tf":1.4142135623730951},"87":{"tf":1.0}},"e":{"df":0,"docs":{},"—":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"d":{"df":1,"docs":{"82":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"245":{"tf":1.0}}}}}}}},"df":0,"docs":{},"g":{"df":7,"docs":{"167":{"tf":1.0},"253":{"tf":1.4142135623730951},"268":{"tf":1.4142135623730951},"269":{"tf":1.0},"274":{"tf":2.23606797749979},"280":{"tf":1.4142135623730951},"32":{"tf":1.0}},"e":{"df":1,"docs":{"167":{"tf":1.0}}}},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"215":{"tf":1.0}}}},"k":{"df":0,"docs":{},"e":{"df":22,"docs":{"10":{"tf":1.0},"102":{"tf":1.0},"104":{"tf":1.0},"115":{"tf":1.0},"12":{"tf":1.0},"121":{"tf":1.0},"187":{"tf":1.0},"20":{"tf":1.0},"200":{"tf":1.0},"204":{"tf":1.0},"205":{"tf":1.0},"206":{"tf":1.7320508075688772},"212":{"tf":1.0},"226":{"tf":1.0},"242":{"tf":1.0},"249":{"tf":1.0},"250":{"tf":1.4142135623730951},"268":{"tf":1.0},"37":{"tf":1.0},"59":{"tf":1.0},"64":{"tf":1.0},"82":{"tf":1.0}},"n":{"df":5,"docs":{"132":{"tf":1.0},"140":{"tf":1.0},"171":{"tf":1.0},"242":{"tf":1.0},"58":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"e":{"df":1,"docs":{"64":{"tf":1.0}}},"k":{"df":3,"docs":{"194":{"tf":1.0},"233":{"tf":1.0},"274":{"tf":1.0}}}},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"201":{"tf":1.0}}}}}},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"274":{"tf":1.0}}}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":1,"docs":{"282":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":23,"docs":{"103":{"tf":1.0},"117":{"tf":1.0},"163":{"tf":1.4142135623730951},"167":{"tf":1.4142135623730951},"17":{"tf":1.0},"172":{"tf":1.0},"174":{"tf":1.0},"208":{"tf":1.4142135623730951},"223":{"tf":1.0},"231":{"tf":1.0},"251":{"tf":1.0},"252":{"tf":1.0},"253":{"tf":1.0},"274":{"tf":1.0},"28":{"tf":1.0},"33":{"tf":1.0},"37":{"tf":1.4142135623730951},"58":{"tf":1.7320508075688772},"59":{"tf":1.0},"60":{"tf":1.0},"69":{"tf":1.0},"73":{"tf":1.7320508075688772},"74":{"tf":1.4142135623730951}}}}}},"s":{"df":0,"docs":{},"k":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":8,"docs":{"119":{"tf":1.0},"266":{"tf":1.0},"271":{"tf":1.0},"272":{"tf":1.0},"273":{"tf":1.4142135623730951},"274":{"tf":3.0},"280":{"tf":1.0},"74":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"r":{"/":{"c":{"df":0,"docs":{},"i":{"/":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"h":{"/":{"df":0,"docs":{},"k":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{".":{"df":0,"docs":{},"y":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":1,"docs":{"280":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"/":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"74":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":10,"docs":{"119":{"tf":1.4142135623730951},"167":{"tf":1.0},"200":{"tf":1.0},"224":{"tf":1.0},"235":{"tf":1.0},"266":{"tf":2.0},"274":{"tf":1.7320508075688772},"280":{"tf":1.4142135623730951},"39":{"tf":1.0},"40":{"tf":2.449489742783178}},"s":{"[":{"\"":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"$":{"df":0,"docs":{},"{":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"f":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"v":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"}":{"$":{"df":0,"docs":{},"{":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"}":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"\"":{"]":{".":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"(":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"(":{"'":{":":{"df":0,"docs":{},"f":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":1,"docs":{"224":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"b":{"d":{"df":2,"docs":{"226":{"tf":1.0},"242":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"c":{"'":{"df":1,"docs":{"274":{"tf":1.0}}},".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"/":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"/":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"/":{"df":0,"docs":{},"v":{"1":{"/":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"k":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{".":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"271":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"119":{"tf":1.0}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}},"t":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"k":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{".":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"266":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"df":1,"docs":{"280":{"tf":1.0}},"l":{"df":1,"docs":{"11":{"tf":1.7320508075688772}},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"11":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"m":{"df":14,"docs":{"1":{"tf":1.0},"119":{"tf":1.0},"122":{"tf":1.7320508075688772},"175":{"tf":1.0},"183":{"tf":1.0},"186":{"tf":1.7320508075688772},"187":{"tf":1.0},"203":{"tf":1.0},"225":{"tf":1.0},"268":{"tf":2.0},"274":{"tf":1.0},"276":{"tf":1.0},"6":{"tf":1.0},"8":{"tf":1.0}}}},"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"c":{"df":3,"docs":{"139":{"tf":1.0},"176":{"tf":1.0},"48":{"tf":1.0}}},"df":0,"docs":{}}}}},"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":3,"docs":{"20":{"tf":1.0},"22":{"tf":1.0},"27":{"tf":1.0}}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":10,"docs":{"150":{"tf":1.0},"203":{"tf":1.0},"210":{"tf":1.0},"214":{"tf":1.0},"219":{"tf":1.0},"226":{"tf":1.7320508075688772},"236":{"tf":1.0},"240":{"tf":1.0},"242":{"tf":1.4142135623730951},"259":{"tf":1.4142135623730951}}}}}}}},"l":{"df":6,"docs":{"18":{"tf":1.0},"214":{"tf":1.0},"252":{"tf":1.0},"37":{"tf":1.4142135623730951},"50":{"tf":1.0},"92":{"tf":1.0}}}},"m":{"df":0,"docs":{},"p":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"264":{"tf":1.4142135623730951}}}}}}},"df":2,"docs":{"250":{"tf":1.0},"81":{"tf":1.0}},"l":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"137":{"tf":1.4142135623730951}},"e":{".":{"df":0,"docs":{},"m":{"d":{"df":1,"docs":{"134":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":2,"docs":{"248":{"tf":1.0},"82":{"tf":1.0}}}}},"df":0,"docs":{}}},"t":{"df":1,"docs":{"225":{"tf":1.0}}}}},"n":{"d":{"df":4,"docs":{"117":{"tf":1.0},"60":{"tf":1.0},"82":{"tf":1.4142135623730951},"83":{"tf":1.0}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"m":{"df":8,"docs":{"104":{"tf":1.4142135623730951},"137":{"tf":1.0},"140":{"tf":1.0},"164":{"tf":1.0},"170":{"tf":1.0},"2":{"tf":1.0},"241":{"tf":1.0},"78":{"tf":1.4142135623730951}},"i":{"df":0,"docs":{},"n":{"df":3,"docs":{"11":{"tf":1.0},"12":{"tf":1.0},"204":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"q":{"df":2,"docs":{"176":{"tf":1.0},"202":{"tf":1.0}}}},"df":0,"docs":{}},"t":{",":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"}":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"61":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},".":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"r":{"c":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":1,"docs":{"224":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}},"/":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"/":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"59":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"60":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"c":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"df":1,"docs":{"60":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"j":{"a":{"df":0,"docs":{},"v":{"a":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"/":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"/":{"<":{"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"_":{"c":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"70":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"r":{"c":{"/":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"b":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"60":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"0":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"60":{"tf":1.4142135623730951}}}},"df":1,"docs":{"60":{"tf":1.0}}},"1":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"60":{"tf":1.0}}}},"df":1,"docs":{"60":{"tf":1.0}}},"2":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"60":{"tf":1.4142135623730951}}}},"df":1,"docs":{"60":{"tf":1.0}}},"_":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"b":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"_":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"c":{"df":0,"docs":{},"r":{"df":1,"docs":{"62":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"73":{"tf":1.0}}}},"df":0,"docs":{}},"df":57,"docs":{"10":{"tf":1.0},"108":{"tf":1.4142135623730951},"109":{"tf":1.0},"11":{"tf":2.0},"118":{"tf":1.4142135623730951},"122":{"tf":1.0},"124":{"tf":1.0},"14":{"tf":1.0},"158":{"tf":1.0},"16":{"tf":1.4142135623730951},"17":{"tf":1.0},"18":{"tf":1.0},"190":{"tf":1.0},"194":{"tf":1.0},"200":{"tf":1.7320508075688772},"22":{"tf":1.0},"223":{"tf":1.4142135623730951},"224":{"tf":1.0},"23":{"tf":1.7320508075688772},"237":{"tf":1.0},"262":{"tf":1.4142135623730951},"267":{"tf":1.0},"270":{"tf":1.0},"273":{"tf":1.0},"274":{"tf":2.6457513110645907},"278":{"tf":1.0},"279":{"tf":1.4142135623730951},"32":{"tf":2.0},"37":{"tf":1.4142135623730951},"38":{"tf":1.0},"39":{"tf":2.23606797749979},"40":{"tf":2.23606797749979},"41":{"tf":2.0},"42":{"tf":1.0},"43":{"tf":2.8284271247461903},"44":{"tf":1.0},"45":{"tf":3.1622776601683795},"46":{"tf":2.449489742783178},"47":{"tf":1.0},"48":{"tf":2.23606797749979},"49":{"tf":2.0},"50":{"tf":1.4142135623730951},"51":{"tf":2.0},"52":{"tf":1.7320508075688772},"54":{"tf":1.7320508075688772},"55":{"tf":1.4142135623730951},"56":{"tf":1.4142135623730951},"57":{"tf":1.4142135623730951},"58":{"tf":2.8284271247461903},"59":{"tf":2.23606797749979},"60":{"tf":4.0},"61":{"tf":2.6457513110645907},"62":{"tf":2.23606797749979},"7":{"tf":3.1622776601683795},"70":{"tf":1.7320508075688772},"73":{"tf":2.449489742783178},"74":{"tf":1.0}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"$":{"df":0,"docs":{},"n":{"df":1,"docs":{"20":{"tf":1.0}}}},"/":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"y":{"c":{"df":0,"docs":{},"r":{"df":1,"docs":{"60":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":1,"docs":{"59":{"tf":1.0}}}},"df":0,"docs":{}}},"y":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"48":{"tf":1.0}}},"df":0,"docs":{}}}}},"3":{"df":2,"docs":{"20":{"tf":1.4142135623730951},"22":{"tf":1.0}}},"df":0,"docs":{}}}},"s":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"41":{"tf":1.0}}}},"r":{"df":1,"docs":{"61":{"tf":1.0}}}},"/":{"*":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"61":{"tf":1.4142135623730951}},"m":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":2,"docs":{"60":{"tf":1.4142135623730951},"61":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"61":{"tf":1.0}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"h":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"k":{"df":2,"docs":{"220":{"tf":1.0},"274":{"tf":1.0}}}},"t":{"'":{"df":7,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"167":{"tf":1.0},"225":{"tf":1.0},"274":{"tf":1.0},"43":{"tf":1.0},"61":{"tf":1.0}}},"df":0,"docs":{},"’":{"df":1,"docs":{"283":{"tf":1.0}}}}},"d":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"v":{"df":2,"docs":{"190":{"tf":1.0},"228":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":2,"docs":{"105":{"tf":1.0},"158":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"e":{"'":{"df":16,"docs":{"19":{"tf":1.0},"194":{"tf":1.0},"201":{"tf":1.0},"226":{"tf":1.4142135623730951},"227":{"tf":1.0},"228":{"tf":1.0},"229":{"tf":1.4142135623730951},"233":{"tf":1.7320508075688772},"236":{"tf":1.0},"242":{"tf":1.7320508075688772},"274":{"tf":1.0},"276":{"tf":1.0},"40":{"tf":1.0},"43":{"tf":1.0},"60":{"tf":1.0},"79":{"tf":1.0}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"201":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"102":{"tf":1.0}}}},"’":{"df":2,"docs":{"189":{"tf":2.8284271247461903},"192":{"tf":1.0}}}}},"y":{"'":{"d":{"df":1,"docs":{"171":{"tf":1.0}}},"df":0,"docs":{},"r":{"df":3,"docs":{"49":{"tf":1.0},"60":{"tf":1.0},"73":{"tf":1.0}}},"v":{"df":1,"docs":{"116":{"tf":1.0}}}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":21,"docs":{"104":{"tf":1.0},"115":{"tf":1.0},"116":{"tf":1.4142135623730951},"13":{"tf":1.0},"175":{"tf":1.0},"201":{"tf":1.0},"225":{"tf":1.0},"226":{"tf":1.7320508075688772},"228":{"tf":1.4142135623730951},"237":{"tf":1.0},"238":{"tf":1.0},"239":{"tf":1.4142135623730951},"240":{"tf":1.4142135623730951},"252":{"tf":1.0},"40":{"tf":1.0},"62":{"tf":1.0},"68":{"tf":1.0},"69":{"tf":1.0},"78":{"tf":1.0},"79":{"tf":1.0},"86":{"tf":1.0}}},"k":{"df":7,"docs":{"116":{"tf":1.0},"274":{"tf":1.0},"58":{"tf":1.0},"76":{"tf":1.0},"77":{"tf":1.0},"78":{"tf":1.0},"87":{"tf":1.0}}}},"r":{"d":{"df":2,"docs":{"63":{"tf":1.0},"64":{"tf":1.7320508075688772}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":1,"docs":{"229":{"tf":1.0}},"i":{"df":1,"docs":{"104":{"tf":1.0}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":3,"docs":{"236":{"tf":1.0},"270":{"tf":1.0},"46":{"tf":1.0}}}}}}}}},"s":{"df":0,"docs":{},"e":{"df":17,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"11":{"tf":1.0},"118":{"tf":1.4142135623730951},"170":{"tf":1.0},"204":{"tf":1.4142135623730951},"220":{"tf":1.0},"249":{"tf":1.0},"25":{"tf":1.0},"258":{"tf":1.4142135623730951},"274":{"tf":1.0},"281":{"tf":1.0},"39":{"tf":1.0},"48":{"tf":1.0},"58":{"tf":1.0},"7":{"tf":1.0},"82":{"tf":1.0}}}},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":6,"docs":{"105":{"tf":1.0},"201":{"tf":1.0},"228":{"tf":1.4142135623730951},"245":{"tf":1.0},"261":{"tf":1.0},"81":{"tf":1.0}},"t":{"df":2,"docs":{"64":{"tf":1.0},"81":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"e":{"a":{"d":{"df":7,"docs":{"115":{"tf":3.0},"116":{"tf":1.0},"190":{"tf":1.4142135623730951},"194":{"tf":1.0},"197":{"tf":1.0},"203":{"tf":1.0},"242":{"tf":1.0}}},"df":0,"docs":{},"t":{"df":2,"docs":{"194":{"tf":1.0},"201":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":3,"docs":{"20":{"tf":1.0},"254":{"tf":1.0},"68":{"tf":1.0}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":8,"docs":{"167":{"tf":1.0},"249":{"tf":1.0},"268":{"tf":1.0},"27":{"tf":1.0},"283":{"tf":1.0},"3":{"tf":1.0},"73":{"tf":1.0},"74":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"45":{"tf":1.0}}}}}}},"w":{"df":0,"docs":{},"n":{"df":1,"docs":{"283":{"tf":1.0}}}}}},"u":{"df":11,"docs":{"172":{"tf":1.0},"205":{"tf":1.0},"209":{"tf":1.0},"221":{"tf":1.0},"227":{"tf":1.0},"228":{"tf":1.0},"250":{"tf":1.0},"263":{"tf":1.0},"44":{"tf":1.0},"46":{"tf":1.0},"84":{"tf":1.0}},"m":{"b":{"df":1,"docs":{"59":{"tf":1.0}}},"df":0,"docs":{}}}},"i":{"df":4,"docs":{"180":{"tf":1.0},"183":{"tf":1.0},"238":{"tf":1.0},"274":{"tf":1.0}},"e":{"df":1,"docs":{"240":{"tf":1.0}}},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"206":{"tf":1.0}}}}}}},"m":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"d":{"_":{"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"145":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":27,"docs":{"10":{"tf":1.4142135623730951},"104":{"tf":1.0},"141":{"tf":1.0},"142":{"tf":1.4142135623730951},"152":{"tf":1.0},"154":{"tf":1.4142135623730951},"155":{"tf":1.4142135623730951},"156":{"tf":1.0},"170":{"tf":1.0},"172":{"tf":1.0},"204":{"tf":1.4142135623730951},"209":{"tf":1.0},"212":{"tf":1.0},"233":{"tf":1.0},"241":{"tf":1.0},"242":{"tf":1.0},"25":{"tf":1.0},"250":{"tf":1.0},"263":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"60":{"tf":1.0},"62":{"tf":1.0},"64":{"tf":1.0},"7":{"tf":1.0},"81":{"tf":1.7320508075688772},"84":{"tf":1.4142135623730951}},"r":{"df":1,"docs":{"226":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":6,"docs":{"22":{"tf":1.0},"242":{"tf":1.0},"248":{"tf":1.0},"249":{"tf":1.7320508075688772},"80":{"tf":1.0},"88":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"p":{"df":5,"docs":{"105":{"tf":1.0},"282":{"tf":1.0},"43":{"tf":1.0},"7":{"tf":1.0},"75":{"tf":1.0}}},"t":{"df":0,"docs":{},"l":{"df":3,"docs":{"280":{"tf":1.0},"39":{"tf":1.0},"62":{"tf":1.0}}}}},"l":{";":{"d":{"df":0,"docs":{},"r":{"df":2,"docs":{"228":{"tf":1.0},"92":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"o":{"/":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":1,"docs":{"254":{"tf":1.0}}}}}}},"_":{"df":0,"docs":{},"j":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"283":{"tf":1.0}}}}}}},"d":{"df":0,"docs":{},"o":{"!":{"(":{")":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"200":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":6,"docs":{"127":{"tf":1.0},"200":{"tf":1.0},"241":{"tf":1.0},"274":{"tf":2.8284271247461903},"46":{"tf":1.0},"48":{"tf":1.7320508075688772}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":10,"docs":{"172":{"tf":1.0},"220":{"tf":1.0},"221":{"tf":1.0},"238":{"tf":1.0},"240":{"tf":1.0},"252":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0},"46":{"tf":1.0},"58":{"tf":1.0}}}}}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":6,"docs":{"226":{"tf":1.0},"245":{"tf":1.4142135623730951},"254":{"tf":1.0},"274":{"tf":1.4142135623730951},"277":{"tf":2.449489742783178},"283":{"tf":1.4142135623730951}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"_":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"l":{"df":1,"docs":{"242":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}}}}}},"m":{"b":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"268":{"tf":1.0},"62":{"tf":1.0}}}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"k":{"df":1,"docs":{"204":{"tf":1.0}}},"l":{"b":{"a":{"df":0,"docs":{},"r":{"_":{"_":{"_":{"_":{"_":{"df":1,"docs":{"62":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"62":{"tf":1.0}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"263":{"tf":1.7320508075688772}}}}},"df":0,"docs":{}}},"df":15,"docs":{"101":{"tf":1.0},"106":{"tf":1.0},"107":{"tf":1.0},"11":{"tf":2.0},"111":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":1.4142135623730951},"165":{"tf":1.0},"187":{"tf":1.4142135623730951},"264":{"tf":1.0},"274":{"tf":1.0},"37":{"tf":1.7320508075688772},"62":{"tf":1.4142135623730951},"64":{"tf":1.0},"90":{"tf":1.0}},"s":{"/":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{".":{"d":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"286":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":1,"docs":{"11":{"tf":1.7320508075688772}}}}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"f":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":1,"docs":{"106":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"u":{"df":0,"docs":{},"p":{"d":{"df":1,"docs":{"122":{"tf":1.0}}},"df":0,"docs":{}}}},";":{"2":{"6":{".":{"0":{".":{"2":{"df":1,"docs":{"13":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"p":{"df":13,"docs":{"105":{"tf":1.0},"123":{"tf":1.4142135623730951},"124":{"tf":1.4142135623730951},"126":{"tf":1.4142135623730951},"200":{"tf":1.4142135623730951},"252":{"tf":1.0},"282":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"40":{"tf":1.0},"73":{"tf":1.0},"84":{"tf":1.0},"98":{"tf":1.4142135623730951}},"i":{"c":{"df":1,"docs":{"90":{"tf":1.0}}},"df":0,"docs":{}}},"u":{"c":{"df":0,"docs":{},"h":{"df":2,"docs":{"201":{"tf":1.0},"3":{"tf":1.0}}}},"df":0,"docs":{}}},"r":{"a":{"c":{"df":0,"docs":{},"k":{"df":10,"docs":{"106":{"tf":1.0},"167":{"tf":1.0},"231":{"tf":1.0},"245":{"tf":1.0},"261":{"tf":1.0},"283":{"tf":1.0},"80":{"tf":1.0},"82":{"tf":1.0},"84":{"tf":1.0},"87":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"1":{"tf":1.0}}}}},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":3,"docs":{"169":{"tf":1.0},"172":{"tf":1.0},"197":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":2,"docs":{"116":{"tf":1.0},"251":{"tf":1.0}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":6,"docs":{"124":{"tf":1.4142135623730951},"244":{"tf":1.4142135623730951},"246":{"tf":1.0},"43":{"tf":1.0},"84":{"tf":1.0},"93":{"tf":1.0}}}},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"79":{"tf":2.449489742783178}}}},"df":0,"docs":{}},"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"149":{"tf":1.0},"150":{"tf":1.0}}}}}},"p":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"245":{"tf":1.0}}}},"df":0,"docs":{}}}},"v":{"df":0,"docs":{},"i":{"df":1,"docs":{"176":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":5,"docs":{"122":{"tf":1.0},"124":{"tf":1.0},"147":{"tf":1.0},"64":{"tf":1.0},"81":{"tf":1.0}}}},"i":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"121":{"tf":1.0}}},"l":{"df":1,"docs":{"104":{"tf":1.0}}}},"c":{"df":0,"docs":{},"k":{"df":3,"docs":{"126":{"tf":1.0},"17":{"tf":1.0},"75":{"tf":1.0}},"i":{"df":4,"docs":{"11":{"tf":1.0},"128":{"tf":1.0},"22":{"tf":1.0},"62":{"tf":1.0}}}}},"df":28,"docs":{"104":{"tf":1.7320508075688772},"106":{"tf":1.0},"107":{"tf":1.0},"108":{"tf":1.4142135623730951},"109":{"tf":1.4142135623730951},"111":{"tf":1.0},"122":{"tf":1.4142135623730951},"126":{"tf":1.0},"158":{"tf":1.0},"170":{"tf":1.0},"197":{"tf":1.0},"20":{"tf":1.0},"22":{"tf":1.4142135623730951},"227":{"tf":1.0},"230":{"tf":1.0},"242":{"tf":1.0},"263":{"tf":1.0},"264":{"tf":1.0},"278":{"tf":1.0},"279":{"tf":1.0},"37":{"tf":1.4142135623730951},"4":{"tf":1.0},"54":{"tf":1.0},"60":{"tf":1.0},"62":{"tf":1.0},"64":{"tf":1.0},"69":{"tf":1.0},"79":{"tf":1.0}},"g":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":13,"docs":{"116":{"tf":1.0},"119":{"tf":1.4142135623730951},"154":{"tf":1.0},"267":{"tf":1.0},"270":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0},"60":{"tf":1.0},"62":{"tf":1.0},"73":{"tf":1.0},"81":{"tf":1.0},"82":{"tf":3.0},"84":{"tf":1.4142135623730951}}}}}},"v":{"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"l":{"df":4,"docs":{"167":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"64":{"tf":1.0}}}},"df":0,"docs":{}}}},"u":{"df":0,"docs":{},"e":{"df":6,"docs":{"126":{"tf":1.0},"133":{"tf":1.0},"258":{"tf":1.0},"264":{"tf":1.0},"46":{"tf":1.0},"61":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"189":{"tf":1.0},"274":{"tf":1.0}}}},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"263":{"tf":1.0}}}}}},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"62":{"tf":1.0}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":2,"docs":{"252":{"tf":1.0},"60":{"tf":1.0}}}}},"w":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"k":{"df":1,"docs":{"37":{"tf":1.0}}}},"df":0,"docs":{}},"o":{"df":8,"docs":{"165":{"tf":1.0},"175":{"tf":1.0},"196":{"tf":1.0},"197":{"tf":1.0},"201":{"tf":1.0},"212":{"tf":1.0},"251":{"tf":1.0},"273":{"tf":1.0}}}},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":1,"docs":{"116":{"tf":1.0}}}}},"df":13,"docs":{"105":{"tf":1.0},"106":{"tf":1.7320508075688772},"151":{"tf":1.4142135623730951},"152":{"tf":1.4142135623730951},"155":{"tf":1.0},"156":{"tf":1.0},"189":{"tf":1.4142135623730951},"268":{"tf":1.0},"283":{"tf":2.0},"43":{"tf":1.0},"62":{"tf":1.0},"93":{"tf":1.0},"96":{"tf":1.0}},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":1,"docs":{"43":{"tf":1.0}}}}}}}}},"i":{"c":{"df":11,"docs":{"10":{"tf":1.0},"103":{"tf":1.0},"224":{"tf":1.4142135623730951},"250":{"tf":1.0},"283":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0},"45":{"tf":1.0},"79":{"tf":1.0},"87":{"tf":1.0},"93":{"tf":1.0}}},"df":0,"docs":{}}}}},"u":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":1,"docs":{"13":{"tf":1.0}}}}}}},"d":{"df":0,"docs":{},"l":{"df":9,"docs":{"104":{"tf":1.7320508075688772},"105":{"tf":2.449489742783178},"106":{"tf":1.4142135623730951},"107":{"tf":1.0},"109":{"tf":1.0},"123":{"tf":1.0},"200":{"tf":1.0},"70":{"tf":1.0},"73":{"tf":1.0}}}},"df":0,"docs":{},"i":{"df":4,"docs":{"180":{"tf":1.0},"226":{"tf":1.4142135623730951},"268":{"tf":1.0},"7":{"tf":1.0}}},"l":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":4,"docs":{"115":{"tf":1.0},"225":{"tf":1.0},"261":{"tf":1.0},"43":{"tf":1.0}}}}}},"n":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"233":{"tf":1.0}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"200":{"tf":1.0}}}}}}}},"df":0,"docs":{}}}},"h":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"184":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"73":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"m":{"df":3,"docs":{"117":{"tf":1.0},"29":{"tf":1.0},"34":{"tf":1.0}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"124":{"tf":1.4142135623730951}}}}}},"v":{"df":1,"docs":{"104":{"tf":1.0}}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"282":{"tf":1.0}}}}},"r":{"df":17,"docs":{"105":{"tf":1.0},"108":{"tf":1.7320508075688772},"109":{"tf":1.7320508075688772},"170":{"tf":1.0},"175":{"tf":1.0},"184":{"tf":1.0},"214":{"tf":1.0},"216":{"tf":1.4142135623730951},"217":{"tf":1.0},"22":{"tf":1.0},"4":{"tf":1.0},"46":{"tf":1.0},"60":{"tf":1.0},"64":{"tf":1.0},"69":{"tf":1.0},"7":{"tf":1.0},"73":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":3,"docs":{"104":{"tf":1.0},"128":{"tf":1.0},"253":{"tf":1.0}}}},"n":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"86":{"tf":1.0}}}}},"df":0,"docs":{}}},"s":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"99":{"tf":1.0}}}}},"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"n":{"d":{"df":10,"docs":{"102":{"tf":1.4142135623730951},"104":{"tf":1.0},"123":{"tf":1.0},"138":{"tf":1.0},"174":{"tf":1.0},"197":{"tf":1.0},"21":{"tf":1.0},"225":{"tf":1.0},"236":{"tf":1.0},"264":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"d":{"df":1,"docs":{"253":{"tf":1.0}}},"df":0,"docs":{}}}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":1,"docs":{"180":{"tf":1.0}}}}}},"u":{"df":1,"docs":{"226":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"37":{"tf":1.0},"64":{"tf":1.0}},"e":{"d":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"58":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"f":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"82":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"62":{"tf":1.0}},"e":{"d":{"_":{"_":{"_":{"_":{"_":{"df":1,"docs":{"62":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":5,"docs":{"104":{"tf":1.0},"117":{"tf":1.0},"210":{"tf":1.0},"58":{"tf":1.7320508075688772},"62":{"tf":1.0}}}}}}}},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"'":{"df":1,"docs":{"104":{"tf":1.0}}},".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":5,"docs":{"103":{"tf":1.0},"108":{"tf":1.0},"70":{"tf":1.0},"72":{"tf":1.0},"73":{"tf":1.0}}}}}}},"_":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"104":{"tf":1.0}}}}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"69":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":29,"docs":{"101":{"tf":1.7320508075688772},"102":{"tf":1.7320508075688772},"103":{"tf":1.7320508075688772},"104":{"tf":3.0},"105":{"tf":2.23606797749979},"106":{"tf":1.7320508075688772},"107":{"tf":1.0},"108":{"tf":2.0},"109":{"tf":2.23606797749979},"111":{"tf":1.7320508075688772},"123":{"tf":1.4142135623730951},"128":{"tf":1.7320508075688772},"130":{"tf":1.0},"131":{"tf":1.4142135623730951},"194":{"tf":1.0},"196":{"tf":1.0},"197":{"tf":1.0},"198":{"tf":1.0},"200":{"tf":2.0},"253":{"tf":1.0},"271":{"tf":1.0},"44":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"69":{"tf":1.4142135623730951},"72":{"tf":1.0},"73":{"tf":1.0},"74":{"tf":1.0},"90":{"tf":1.7320508075688772}}}},"i":{"df":2,"docs":{"237":{"tf":1.0},"257":{"tf":1.0}}}},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"60":{"tf":1.0}}}}}}},"q":{"df":0,"docs":{},"u":{"df":1,"docs":{"22":{"tf":1.0}}}},"t":{"df":9,"docs":{"17":{"tf":1.0},"223":{"tf":1.0},"224":{"tf":1.0},"232":{"tf":1.7320508075688772},"278":{"tf":1.0},"39":{"tf":1.4142135623730951},"42":{"tf":1.0},"43":{"tf":1.0},"60":{"tf":1.0}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":3,"docs":{"223":{"tf":1.0},"224":{"tf":1.4142135623730951},"46":{"tf":1.0}}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":1,"docs":{"61":{"tf":1.0}}}}}}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":7,"docs":{"204":{"tf":1.0},"21":{"tf":1.0},"26":{"tf":1.0},"60":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0},"67":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"k":{"df":6,"docs":{"146":{"tf":1.0},"200":{"tf":1.0},"231":{"tf":1.0},"258":{"tf":1.0},"60":{"tf":1.0},"64":{"tf":1.0}}}},"o":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"142":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"q":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":1,"docs":{"58":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"224":{"tf":1.0}}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":1,"docs":{"124":{"tf":1.0}}}},"df":0,"docs":{}},"i":{"df":1,"docs":{"233":{"tf":1.0}}}},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"v":{"df":1,"docs":{"92":{"tf":1.0}}}}}}}},"s":{"a":{"df":0,"docs":{},"f":{"df":2,"docs":{"117":{"tf":1.0},"64":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"117":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":11,"docs":{"119":{"tf":1.0},"12":{"tf":1.0},"167":{"tf":1.0},"179":{"tf":1.0},"180":{"tf":1.0},"184":{"tf":1.0},"197":{"tf":1.0},"212":{"tf":1.0},"226":{"tf":1.0},"40":{"tf":1.0},"7":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"s":{"df":1,"docs":{"149":{"tf":1.0}},"u":{"df":2,"docs":{"252":{"tf":1.0},"64":{"tf":1.0}}}}},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"116":{"tf":1.0}}}}},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":3,"docs":{"11":{"tf":1.4142135623730951},"29":{"tf":1.7320508075688772},"34":{"tf":1.7320508075688772}}}}}},"p":{"d":{"a":{"df":0,"docs":{},"t":{"df":35,"docs":{"104":{"tf":1.0},"108":{"tf":1.0},"11":{"tf":1.4142135623730951},"119":{"tf":1.0},"120":{"tf":1.0},"121":{"tf":1.0},"122":{"tf":1.7320508075688772},"123":{"tf":1.0},"124":{"tf":1.0},"134":{"tf":1.0},"139":{"tf":1.0},"145":{"tf":2.0},"146":{"tf":1.0},"193":{"tf":1.0},"197":{"tf":1.0},"198":{"tf":1.0},"200":{"tf":1.4142135623730951},"233":{"tf":1.0},"246":{"tf":1.0},"249":{"tf":1.7320508075688772},"25":{"tf":1.7320508075688772},"250":{"tf":1.4142135623730951},"261":{"tf":1.0},"268":{"tf":2.8284271247461903},"269":{"tf":1.0},"278":{"tf":1.0},"279":{"tf":2.23606797749979},"280":{"tf":2.0},"282":{"tf":1.0},"64":{"tf":1.0},"69":{"tf":1.0},"73":{"tf":1.0},"75":{"tf":1.0},"82":{"tf":1.7320508075688772},"84":{"tf":1.0}}}},"df":0,"docs":{}},"df":40,"docs":{"103":{"tf":1.0},"108":{"tf":1.4142135623730951},"109":{"tf":1.4142135623730951},"117":{"tf":1.0},"121":{"tf":1.0},"122":{"tf":1.0},"124":{"tf":1.4142135623730951},"13":{"tf":1.0},"158":{"tf":1.0},"166":{"tf":1.0},"17":{"tf":1.0},"170":{"tf":1.0},"197":{"tf":1.0},"199":{"tf":1.0},"21":{"tf":1.0},"214":{"tf":1.4142135623730951},"22":{"tf":1.0},"225":{"tf":1.4142135623730951},"233":{"tf":1.0},"240":{"tf":1.0},"242":{"tf":1.0},"255":{"tf":1.0},"264":{"tf":1.0},"272":{"tf":1.0},"278":{"tf":1.0},"30":{"tf":1.0},"35":{"tf":1.0},"40":{"tf":1.4142135623730951},"5":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"6":{"tf":1.0},"60":{"tf":1.4142135623730951},"61":{"tf":1.0},"62":{"tf":1.0},"70":{"tf":1.0},"72":{"tf":1.0},"82":{"tf":1.0},"83":{"tf":1.0},"9":{"tf":1.0}},"g":{"df":0,"docs":{},"r":{"a":{"d":{"df":7,"docs":{"261":{"tf":1.0},"278":{"tf":1.0},"279":{"tf":1.0},"282":{"tf":1.4142135623730951},"78":{"tf":1.0},"79":{"tf":1.0},"81":{"tf":2.23606797749979}}},"df":0,"docs":{}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":1,"docs":{"270":{"tf":1.7320508075688772}}}}},"o":{"a":{"d":{"df":4,"docs":{"249":{"tf":1.0},"250":{"tf":1.4142135623730951},"274":{"tf":1.7320508075688772},"88":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"c":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"c":{"a":{"df":0,"docs":{},"s":{"df":4,"docs":{"93":{"tf":1.0},"96":{"tf":1.0},"98":{"tf":1.0},"99":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"m":{"df":3,"docs":{"16":{"tf":1.0},"197":{"tf":1.0},"49":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"6":{"tf":1.0}}}}}},"l":{"df":9,"docs":{"205":{"tf":1.0},"242":{"tf":1.0},"253":{"tf":1.0},"268":{"tf":1.4142135623730951},"280":{"tf":1.0},"29":{"tf":1.4142135623730951},"34":{"tf":1.0},"62":{"tf":1.0},"82":{"tf":1.0}}}},"s":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"159":{"tf":1.0}}}},"df":0,"docs":{},"g":{"df":4,"docs":{"115":{"tf":1.0},"138":{"tf":1.0},"213":{"tf":1.0},"264":{"tf":1.7320508075688772}}}},"b":{"df":1,"docs":{"126":{"tf":1.0}}},"df":132,"docs":{"0":{"tf":1.4142135623730951},"101":{"tf":1.4142135623730951},"104":{"tf":1.7320508075688772},"105":{"tf":1.7320508075688772},"106":{"tf":2.23606797749979},"107":{"tf":1.0},"108":{"tf":1.7320508075688772},"109":{"tf":1.0},"11":{"tf":2.23606797749979},"111":{"tf":1.4142135623730951},"115":{"tf":2.23606797749979},"116":{"tf":2.8284271247461903},"117":{"tf":1.4142135623730951},"118":{"tf":1.4142135623730951},"119":{"tf":1.7320508075688772},"12":{"tf":1.4142135623730951},"120":{"tf":1.0},"124":{"tf":1.4142135623730951},"125":{"tf":1.0},"126":{"tf":1.7320508075688772},"128":{"tf":1.7320508075688772},"129":{"tf":1.4142135623730951},"13":{"tf":1.0},"132":{"tf":1.4142135623730951},"134":{"tf":1.4142135623730951},"135":{"tf":1.0},"140":{"tf":1.4142135623730951},"145":{"tf":1.4142135623730951},"146":{"tf":1.0},"157":{"tf":1.0},"16":{"tf":1.4142135623730951},"162":{"tf":1.0},"163":{"tf":1.0},"165":{"tf":1.0},"166":{"tf":1.0},"167":{"tf":1.4142135623730951},"169":{"tf":1.0},"17":{"tf":1.4142135623730951},"170":{"tf":2.0},"171":{"tf":1.0},"174":{"tf":1.7320508075688772},"175":{"tf":2.23606797749979},"18":{"tf":1.0},"187":{"tf":1.0},"189":{"tf":1.0},"19":{"tf":1.4142135623730951},"190":{"tf":1.7320508075688772},"192":{"tf":1.4142135623730951},"193":{"tf":1.4142135623730951},"194":{"tf":1.7320508075688772},"196":{"tf":1.0},"197":{"tf":3.0},"198":{"tf":2.449489742783178},"20":{"tf":1.4142135623730951},"200":{"tf":2.23606797749979},"201":{"tf":1.4142135623730951},"205":{"tf":1.0},"21":{"tf":1.0},"213":{"tf":1.7320508075688772},"219":{"tf":1.4142135623730951},"22":{"tf":2.23606797749979},"220":{"tf":1.0},"221":{"tf":1.0},"222":{"tf":1.4142135623730951},"228":{"tf":1.7320508075688772},"229":{"tf":1.0},"23":{"tf":1.0},"231":{"tf":1.7320508075688772},"232":{"tf":1.7320508075688772},"233":{"tf":1.0},"237":{"tf":1.7320508075688772},"242":{"tf":1.4142135623730951},"245":{"tf":2.23606797749979},"249":{"tf":1.0},"25":{"tf":1.4142135623730951},"250":{"tf":1.0},"252":{"tf":1.4142135623730951},"253":{"tf":1.0},"254":{"tf":1.7320508075688772},"256":{"tf":1.0},"257":{"tf":2.0},"258":{"tf":1.7320508075688772},"259":{"tf":1.0},"26":{"tf":2.0},"260":{"tf":1.4142135623730951},"261":{"tf":1.4142135623730951},"262":{"tf":1.4142135623730951},"264":{"tf":2.0},"266":{"tf":1.0},"268":{"tf":1.0},"27":{"tf":1.4142135623730951},"271":{"tf":1.4142135623730951},"273":{"tf":1.0},"274":{"tf":3.0},"278":{"tf":1.0},"280":{"tf":1.4142135623730951},"283":{"tf":2.0},"284":{"tf":1.0},"32":{"tf":1.0},"37":{"tf":2.6457513110645907},"38":{"tf":1.0},"41":{"tf":1.0},"43":{"tf":2.0},"44":{"tf":1.0},"45":{"tf":1.4142135623730951},"46":{"tf":1.4142135623730951},"49":{"tf":1.0},"50":{"tf":1.0},"52":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"58":{"tf":2.0},"6":{"tf":1.0},"60":{"tf":1.7320508075688772},"61":{"tf":2.23606797749979},"62":{"tf":1.7320508075688772},"63":{"tf":1.0},"64":{"tf":2.8284271247461903},"69":{"tf":2.449489742783178},"7":{"tf":1.0},"73":{"tf":2.23606797749979},"78":{"tf":1.7320508075688772},"79":{"tf":1.4142135623730951},"80":{"tf":1.0},"82":{"tf":1.4142135623730951},"84":{"tf":1.7320508075688772},"86":{"tf":1.0},"87":{"tf":1.0},"90":{"tf":1.0},"96":{"tf":1.0},"99":{"tf":2.23606797749979}},"e":{"df":0,"docs":{},"r":{"'":{"df":5,"docs":{"159":{"tf":1.0},"177":{"tf":1.4142135623730951},"183":{"tf":1.0},"184":{"tf":1.0},"233":{"tf":1.0}}},"_":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":2,"docs":{"264":{"tf":1.0},"81":{"tf":1.0}}}}}}},"df":40,"docs":{"149":{"tf":1.0},"150":{"tf":1.0},"154":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0},"157":{"tf":1.0},"158":{"tf":1.7320508075688772},"159":{"tf":1.0},"179":{"tf":1.0},"180":{"tf":1.0},"182":{"tf":1.0},"183":{"tf":1.0},"184":{"tf":1.4142135623730951},"203":{"tf":1.0},"204":{"tf":2.0},"205":{"tf":3.0},"206":{"tf":1.7320508075688772},"207":{"tf":1.0},"208":{"tf":1.4142135623730951},"209":{"tf":2.0},"210":{"tf":2.0},"212":{"tf":2.0},"213":{"tf":2.449489742783178},"214":{"tf":2.0},"215":{"tf":2.0},"216":{"tf":2.23606797749979},"217":{"tf":1.4142135623730951},"218":{"tf":1.4142135623730951},"225":{"tf":1.0},"226":{"tf":1.4142135623730951},"232":{"tf":1.4142135623730951},"233":{"tf":2.6457513110645907},"242":{"tf":2.0},"245":{"tf":1.0},"254":{"tf":1.0},"255":{"tf":1.0},"283":{"tf":2.23606797749979},"52":{"tf":1.0},"64":{"tf":1.0},"82":{"tf":1.0}},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"13":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"’":{"df":7,"docs":{"203":{"tf":1.0},"205":{"tf":1.0},"206":{"tf":1.4142135623730951},"208":{"tf":1.4142135623730951},"209":{"tf":1.0},"213":{"tf":1.0},"283":{"tf":1.7320508075688772}}}}},"u":{"a":{"df":0,"docs":{},"l":{"df":5,"docs":{"117":{"tf":1.0},"18":{"tf":1.0},"21":{"tf":1.0},"279":{"tf":1.0},"280":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":5,"docs":{"126":{"tf":1.0},"237":{"tf":2.0},"60":{"tf":1.7320508075688772},"62":{"tf":1.4142135623730951},"79":{"tf":1.0}},"s":{".":{"df":0,"docs":{},"r":{"df":2,"docs":{"237":{"tf":1.0},"60":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"v":{"1":{"1":{"7":{".":{"0":{"df":1,"docs":{"268":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"8":{".":{"0":{"df":1,"docs":{"268":{"tf":2.0}}},"df":0,"docs":{}},"df":1,"docs":{"268":{"tf":1.0}}},"9":{".":{"0":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"2":{".":{"0":{"df":1,"docs":{"2":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"[":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"]":{".":{"0":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":1,"docs":{"268":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"]":{".":{"0":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"20":{"tf":1.0}},"i":{"d":{"df":13,"docs":{"104":{"tf":1.0},"140":{"tf":1.4142135623730951},"141":{"tf":1.0},"145":{"tf":2.0},"146":{"tf":1.4142135623730951},"147":{"tf":1.0},"150":{"tf":1.0},"154":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0},"189":{"tf":1.4142135623730951},"190":{"tf":1.0},"64":{"tf":1.0}}},"df":0,"docs":{}},"u":{"df":6,"docs":{"18":{"tf":1.0},"264":{"tf":1.7320508075688772},"274":{"tf":1.0},"280":{"tf":1.0},"62":{"tf":1.0},"84":{"tf":1.4142135623730951}}}},"r":{"df":2,"docs":{"12":{"tf":1.0},"97":{"tf":1.0}},"i":{"a":{"b":{"df":0,"docs":{},"l":{"df":8,"docs":{"11":{"tf":1.4142135623730951},"115":{"tf":1.0},"12":{"tf":1.0},"14":{"tf":1.0},"277":{"tf":1.4142135623730951},"37":{"tf":1.4142135623730951},"93":{"tf":1.4142135623730951},"99":{"tf":1.0}}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":3,"docs":{"104":{"tf":1.0},"108":{"tf":1.0},"93":{"tf":1.0}}}}},"df":2,"docs":{"105":{"tf":1.0},"264":{"tf":1.0}},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"63":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"u":{"df":11,"docs":{"106":{"tf":1.0},"169":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"22":{"tf":1.0},"223":{"tf":1.4142135623730951},"225":{"tf":1.0},"226":{"tf":1.0},"229":{"tf":1.4142135623730951},"253":{"tf":1.0},"263":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"115":{"tf":1.0}}}}}}},"df":0,"docs":{},"e":{"c":{"<":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"242":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"242":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"z":{"df":0,"docs":{},"e":{"d":{"/":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"m":{"df":1,"docs":{"282":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":6,"docs":{"120":{"tf":1.4142135623730951},"121":{"tf":2.0},"123":{"tf":1.0},"124":{"tf":2.23606797749979},"274":{"tf":1.4142135623730951},"64":{"tf":1.4142135623730951}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"122":{"tf":1.0}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"r":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":1,"docs":{"126":{"tf":1.0}}}}},"df":0,"docs":{},"i":{"df":14,"docs":{"107":{"tf":1.0},"115":{"tf":1.0},"158":{"tf":1.0},"165":{"tf":1.0},"192":{"tf":1.0},"200":{"tf":1.0},"215":{"tf":1.0},"22":{"tf":1.0},"225":{"tf":1.0},"230":{"tf":1.0},"245":{"tf":1.0},"260":{"tf":1.0},"60":{"tf":1.0},"81":{"tf":1.0}},"f":{"df":2,"docs":{"196":{"tf":1.0},"198":{"tf":1.0}},"i":{"df":5,"docs":{"189":{"tf":1.0},"193":{"tf":1.0},"194":{"tf":1.0},"48":{"tf":1.0},"49":{"tf":1.0}}}},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"22":{"tf":1.0}}}}},"s":{"a":{"df":1,"docs":{"79":{"tf":1.0}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":4,"docs":{"20":{"tf":1.0},"268":{"tf":2.0},"269":{"tf":1.0},"270":{"tf":1.0}}}}}},"df":53,"docs":{"104":{"tf":1.0},"11":{"tf":1.4142135623730951},"12":{"tf":2.0},"121":{"tf":1.0},"123":{"tf":1.4142135623730951},"124":{"tf":1.4142135623730951},"13":{"tf":1.0},"138":{"tf":1.0},"165":{"tf":1.4142135623730951},"167":{"tf":1.0},"169":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"175":{"tf":1.0},"18":{"tf":1.0},"189":{"tf":1.0},"190":{"tf":1.0},"192":{"tf":1.0},"197":{"tf":1.0},"20":{"tf":2.0},"201":{"tf":1.7320508075688772},"205":{"tf":1.0},"219":{"tf":1.0},"22":{"tf":1.7320508075688772},"222":{"tf":1.0},"25":{"tf":2.0},"253":{"tf":1.0},"26":{"tf":1.7320508075688772},"260":{"tf":1.7320508075688772},"261":{"tf":2.0},"262":{"tf":2.23606797749979},"263":{"tf":3.0},"268":{"tf":2.449489742783178},"269":{"tf":1.0},"270":{"tf":1.4142135623730951},"271":{"tf":1.0},"274":{"tf":1.0},"278":{"tf":1.4142135623730951},"279":{"tf":2.23606797749979},"280":{"tf":1.0},"281":{"tf":1.0},"282":{"tf":1.0},"37":{"tf":2.23606797749979},"45":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"62":{"tf":1.0},"64":{"tf":1.4142135623730951},"69":{"tf":1.4142135623730951},"81":{"tf":2.0},"86":{"tf":1.0}}}}},"u":{"df":1,"docs":{"88":{"tf":1.0}}}}},"t":{"df":1,"docs":{"122":{"tf":1.4142135623730951}}}},"i":{"a":{"d":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":4,"docs":{"124":{"tf":1.0},"197":{"tf":1.0},"200":{"tf":1.7320508075688772},"51":{"tf":1.0}}}},"df":0,"docs":{}}},"df":29,"docs":{"106":{"tf":1.0},"11":{"tf":1.4142135623730951},"13":{"tf":1.0},"161":{"tf":1.0},"167":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"18":{"tf":1.0},"189":{"tf":1.4142135623730951},"190":{"tf":1.0},"192":{"tf":1.0},"194":{"tf":1.4142135623730951},"196":{"tf":1.0},"198":{"tf":1.0},"20":{"tf":1.0},"200":{"tf":1.0},"220":{"tf":1.0},"228":{"tf":1.0},"259":{"tf":1.0},"269":{"tf":1.0},"274":{"tf":2.23606797749979},"277":{"tf":1.0},"283":{"tf":1.0},"37":{"tf":1.0},"43":{"tf":1.0},"45":{"tf":1.0},"49":{"tf":1.0},"51":{"tf":1.0},"81":{"tf":1.0}}},"c":{"df":0,"docs":{},"e":{"df":1,"docs":{"79":{"tf":1.0}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"df":1,"docs":{"174":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":5,"docs":{"107":{"tf":1.0},"126":{"tf":1.7320508075688772},"200":{"tf":1.0},"283":{"tf":1.0},"40":{"tf":1.4142135623730951}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":14,"docs":{"134":{"tf":1.0},"202":{"tf":1.0},"204":{"tf":2.449489742783178},"205":{"tf":1.0},"206":{"tf":1.4142135623730951},"207":{"tf":2.0},"208":{"tf":1.4142135623730951},"210":{"tf":2.23606797749979},"213":{"tf":1.7320508075688772},"214":{"tf":2.449489742783178},"215":{"tf":1.7320508075688772},"216":{"tf":2.6457513110645907},"217":{"tf":2.6457513110645907},"218":{"tf":1.0}}}}},"t":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"87":{"tf":1.0}}}},"df":0,"docs":{}},"v":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"138":{"tf":1.0}}},"df":0,"docs":{}}}},"s":{"df":2,"docs":{"116":{"tf":1.0},"232":{"tf":2.0}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"201":{"tf":1.0},"63":{"tf":1.0}}}}}}},"x":{"df":0,"docs":{},"x":{"df":0,"docs":{},"x":{"df":3,"docs":{"267":{"tf":1.0},"269":{"tf":1.0},"270":{"tf":1.0}}}}}},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":6,"docs":{"118":{"tf":1.0},"152":{"tf":1.0},"183":{"tf":1.0},"245":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0}}}},"l":{"_":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"264":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"264":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":2,"docs":{"264":{"tf":1.0},"79":{"tf":1.0}}},"n":{"df":0,"docs":{},"t":{"df":27,"docs":{"117":{"tf":1.4142135623730951},"12":{"tf":1.0},"121":{"tf":1.0},"124":{"tf":1.0},"136":{"tf":1.0},"140":{"tf":1.4142135623730951},"145":{"tf":1.0},"149":{"tf":1.0},"169":{"tf":1.0},"17":{"tf":1.0},"170":{"tf":1.4142135623730951},"171":{"tf":1.4142135623730951},"172":{"tf":1.0},"19":{"tf":1.0},"196":{"tf":1.0},"23":{"tf":1.0},"241":{"tf":1.0},"242":{"tf":1.4142135623730951},"247":{"tf":1.0},"264":{"tf":1.0},"270":{"tf":1.4142135623730951},"281":{"tf":1.0},"32":{"tf":1.0},"58":{"tf":1.4142135623730951},"60":{"tf":1.0},"62":{"tf":1.0},"75":{"tf":1.0}}}},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"64":{"tf":1.0}}},"n":{"df":2,"docs":{"262":{"tf":1.4142135623730951},"92":{"tf":1.4142135623730951}}}},"y":{"df":34,"docs":{"101":{"tf":1.0},"109":{"tf":1.0},"117":{"tf":1.0},"129":{"tf":1.0},"140":{"tf":1.0},"149":{"tf":1.0},"150":{"tf":1.0},"158":{"tf":1.0},"159":{"tf":1.0},"167":{"tf":1.0},"179":{"tf":1.0},"18":{"tf":1.0},"198":{"tf":1.0},"201":{"tf":1.0},"209":{"tf":1.0},"220":{"tf":1.7320508075688772},"222":{"tf":1.0},"226":{"tf":1.4142135623730951},"229":{"tf":1.7320508075688772},"242":{"tf":1.0},"247":{"tf":1.0},"249":{"tf":1.0},"26":{"tf":1.0},"263":{"tf":1.0},"274":{"tf":1.7320508075688772},"280":{"tf":1.0},"38":{"tf":1.0},"46":{"tf":1.4142135623730951},"52":{"tf":1.0},"58":{"tf":1.4142135623730951},"60":{"tf":1.0},"61":{"tf":2.0},"81":{"tf":1.0},"90":{"tf":1.0}}}},"df":0,"docs":{},"e":{"'":{"d":{"df":5,"docs":{"116":{"tf":1.4142135623730951},"171":{"tf":1.0},"225":{"tf":1.0},"58":{"tf":1.4142135623730951},"82":{"tf":1.0}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":4,"docs":{"229":{"tf":1.0},"242":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0}}}},"r":{"df":8,"docs":{"105":{"tf":1.0},"152":{"tf":1.0},"166":{"tf":1.0},"248":{"tf":1.0},"252":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"63":{"tf":1.0}}},"v":{"df":5,"docs":{"101":{"tf":1.4142135623730951},"225":{"tf":1.0},"231":{"tf":1.0},"63":{"tf":1.0},"88":{"tf":1.0}}}},"a":{"df":0,"docs":{},"k":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":1,"docs":{"245":{"tf":1.0}}}}}}},"b":{"df":2,"docs":{"257":{"tf":1.4142135623730951},"283":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":2,"docs":{"264":{"tf":1.0},"79":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":5,"docs":{"123":{"tf":1.0},"124":{"tf":1.0},"264":{"tf":1.4142135623730951},"78":{"tf":1.4142135623730951},"79":{"tf":1.0}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"l":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":4,"docs":{"107":{"tf":1.0},"3":{"tf":1.0},"7":{"tf":1.0},"75":{"tf":1.0}}}}},"df":0,"docs":{},"l":{"df":12,"docs":{"105":{"tf":1.4142135623730951},"111":{"tf":1.0},"167":{"tf":1.0},"18":{"tf":1.0},"199":{"tf":1.0},"252":{"tf":1.0},"257":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"79":{"tf":1.0}}}},"’":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":1,"docs":{"214":{"tf":1.0}}}},"r":{"df":1,"docs":{"217":{"tf":1.0}}}}},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"11":{"tf":1.0}}}}},"h":{"a":{"df":0,"docs":{},"t":{"'":{"df":3,"docs":{"233":{"tf":1.0},"268":{"tf":1.4142135623730951},"89":{"tf":1.0}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":4,"docs":{"233":{"tf":1.0},"250":{"tf":1.0},"274":{"tf":1.0},"81":{"tf":1.0}}}},"’":{"df":3,"docs":{"86":{"tf":1.0},"87":{"tf":1.0},"89":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":3,"docs":{"121":{"tf":1.0},"267":{"tf":1.0},"276":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"e":{"a":{"df":2,"docs":{"79":{"tf":1.0},"86":{"tf":1.0}}},"df":0,"docs":{},"v":{"df":1,"docs":{"79":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":7,"docs":{"104":{"tf":1.0},"167":{"tf":1.4142135623730951},"229":{"tf":1.0},"236":{"tf":1.0},"64":{"tf":1.0},"70":{"tf":1.0},"73":{"tf":1.4142135623730951}}}}}}},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":3,"docs":{"170":{"tf":1.0},"171":{"tf":1.0},"248":{"tf":1.0}}}}}},"i":{"d":{"df":0,"docs":{},"e":{"df":1,"docs":{"64":{"tf":1.0}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":3,"docs":{"225":{"tf":1.0},"226":{"tf":1.0},"229":{"tf":1.0}}}},"l":{"df":0,"docs":{},"l":{"df":2,"docs":{"105":{"tf":1.0},"115":{"tf":1.0}}}},"n":{"d":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":8,"docs":{"11":{"tf":2.0},"117":{"tf":1.0},"12":{"tf":1.4142135623730951},"13":{"tf":2.0},"19":{"tf":1.7320508075688772},"201":{"tf":1.0},"278":{"tf":1.0},"280":{"tf":1.0}},"s":{"/":{"df":0,"docs":{},"w":{"df":0,"docs":{},"s":{"df":0,"docs":{},"l":{"df":1,"docs":{"19":{"tf":1.0}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"e":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"242":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":6,"docs":{"158":{"tf":1.0},"226":{"tf":1.0},"231":{"tf":1.7320508075688772},"234":{"tf":1.7320508075688772},"242":{"tf":1.7320508075688772},"89":{"tf":1.7320508075688772}}}},"s":{"d":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":1,"docs":{"128":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"h":{"df":1,"docs":{"200":{"tf":1.0}}}},"t":{"df":0,"docs":{},"h":{"/":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"59":{"tf":1.0}}}}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"166":{"tf":1.0}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":21,"docs":{"105":{"tf":1.0},"165":{"tf":1.0},"167":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"183":{"tf":1.4142135623730951},"187":{"tf":1.0},"209":{"tf":1.0},"212":{"tf":1.0},"22":{"tf":1.0},"227":{"tf":1.0},"228":{"tf":1.0},"231":{"tf":1.0},"238":{"tf":1.4142135623730951},"242":{"tf":1.0},"257":{"tf":1.0},"262":{"tf":1.0},"274":{"tf":1.4142135623730951},"58":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"n":{"'":{"df":0,"docs":{},"t":{"df":2,"docs":{"132":{"tf":1.0},"37":{"tf":1.0}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"82":{"tf":1.0}}}}},"df":0,"docs":{},"’":{"df":0,"docs":{},"t":{"df":1,"docs":{"212":{"tf":1.0}}}}},"r":{"d":{"df":3,"docs":{"227":{"tf":1.0},"257":{"tf":1.0},"99":{"tf":1.0}}},"df":0,"docs":{},"k":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"115":{"tf":1.0}}},"df":0,"docs":{}}}}}},"df":52,"docs":{"103":{"tf":1.0},"105":{"tf":1.4142135623730951},"107":{"tf":1.0},"11":{"tf":1.4142135623730951},"115":{"tf":1.0},"116":{"tf":1.4142135623730951},"117":{"tf":1.0},"122":{"tf":1.0},"13":{"tf":1.0},"141":{"tf":1.0},"146":{"tf":1.0},"150":{"tf":1.0},"16":{"tf":1.0},"166":{"tf":1.0},"169":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"174":{"tf":1.0},"187":{"tf":1.4142135623730951},"19":{"tf":1.4142135623730951},"190":{"tf":1.0},"197":{"tf":1.0},"219":{"tf":1.4142135623730951},"22":{"tf":1.4142135623730951},"224":{"tf":1.7320508075688772},"225":{"tf":1.0},"228":{"tf":1.0},"229":{"tf":1.0},"231":{"tf":1.0},"238":{"tf":1.4142135623730951},"239":{"tf":1.0},"243":{"tf":1.0},"246":{"tf":1.0},"263":{"tf":1.0},"264":{"tf":1.0},"274":{"tf":1.0},"37":{"tf":2.0},"39":{"tf":1.0},"41":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"49":{"tf":1.0},"6":{"tf":1.4142135623730951},"60":{"tf":1.0},"61":{"tf":1.0},"7":{"tf":1.4142135623730951},"70":{"tf":1.0},"73":{"tf":1.0},"79":{"tf":1.0},"88":{"tf":1.0},"9":{"tf":1.0},"90":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"280":{"tf":1.0}}}},"f":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":6,"docs":{"12":{"tf":1.0},"16":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":1.4142135623730951},"20":{"tf":1.7320508075688772},"22":{"tf":2.23606797749979}}}}}},"s":{"df":0,"docs":{},"p":{"a":{"c":{"df":4,"docs":{"105":{"tf":1.0},"58":{"tf":1.0},"69":{"tf":1.0},"70":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":3,"docs":{"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0}}}}}},"l":{"d":{"df":5,"docs":{"122":{"tf":1.0},"16":{"tf":1.0},"229":{"tf":1.0},"242":{"tf":1.0},"58":{"tf":1.0}}},"df":0,"docs":{}},"s":{"df":3,"docs":{"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0}}},"t":{"df":0,"docs":{},"h":{"df":2,"docs":{"201":{"tf":1.0},"208":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"l":{"d":{"df":0,"docs":{},"n":{"'":{"df":0,"docs":{},"t":{"df":1,"docs":{"116":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"r":{"a":{"df":0,"docs":{},"p":{"df":4,"docs":{"0":{"tf":1.0},"103":{"tf":1.4142135623730951},"108":{"tf":1.0},"109":{"tf":1.0}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":11,"docs":{"103":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.4142135623730951},"172":{"tf":1.0},"221":{"tf":1.0},"222":{"tf":1.0},"281":{"tf":1.0},"282":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"68":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":17,"docs":{"104":{"tf":1.0},"107":{"tf":1.4142135623730951},"116":{"tf":1.0},"118":{"tf":1.0},"125":{"tf":1.4142135623730951},"158":{"tf":1.0},"196":{"tf":2.0},"233":{"tf":1.4142135623730951},"249":{"tf":1.0},"263":{"tf":1.0},"43":{"tf":1.4142135623730951},"64":{"tf":1.0},"7":{"tf":1.0},"70":{"tf":1.0},"79":{"tf":1.0},"81":{"tf":1.4142135623730951},"90":{"tf":1.0}},"r":{"df":1,"docs":{"79":{"tf":1.7320508075688772}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":22,"docs":{"0":{"tf":1.0},"101":{"tf":1.0},"103":{"tf":2.8284271247461903},"104":{"tf":2.0},"106":{"tf":1.0},"108":{"tf":2.23606797749979},"109":{"tf":2.23606797749979},"111":{"tf":1.0},"192":{"tf":1.4142135623730951},"193":{"tf":1.7320508075688772},"194":{"tf":1.0},"197":{"tf":1.0},"225":{"tf":2.0},"227":{"tf":1.0},"242":{"tf":1.4142135623730951},"45":{"tf":1.0},"46":{"tf":1.0},"70":{"tf":1.4142135623730951},"72":{"tf":2.0},"73":{"tf":1.4142135623730951},"74":{"tf":1.0},"93":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":3,"docs":{"116":{"tf":1.4142135623730951},"236":{"tf":1.0},"277":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"l":{"df":4,"docs":{"11":{"tf":1.7320508075688772},"12":{"tf":1.4142135623730951},"13":{"tf":1.7320508075688772},"19":{"tf":1.4142135623730951}}}}},"x":{"8":{"6":{"_":{"6":{"4":{"/":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"37":{"tf":2.23606797749979}}}}},"df":0,"docs":{}},"df":1,"docs":{"17":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":3,"docs":{"17":{"tf":2.0},"37":{"tf":1.4142135623730951},"40":{"tf":1.0}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":5,"docs":{"14":{"tf":1.0},"252":{"tf":1.0},"28":{"tf":1.4142135623730951},"33":{"tf":1.4142135623730951},"73":{"tf":1.0}}}}},"df":17,"docs":{"163":{"tf":1.4142135623730951},"167":{"tf":2.0},"170":{"tf":1.4142135623730951},"171":{"tf":1.0},"172":{"tf":1.0},"174":{"tf":1.4142135623730951},"23":{"tf":1.4142135623730951},"251":{"tf":1.7320508075688772},"252":{"tf":3.0},"253":{"tf":1.0},"271":{"tf":1.0},"274":{"tf":1.4142135623730951},"28":{"tf":1.4142135623730951},"29":{"tf":1.4142135623730951},"32":{"tf":1.4142135623730951},"33":{"tf":1.4142135623730951},"34":{"tf":1.4142135623730951}}}}}}}}},"df":0,"docs":{}}},"o":{"d":{"df":0,"docs":{},"e":{"df":19,"docs":{"109":{"tf":2.449489742783178},"11":{"tf":1.7320508075688772},"14":{"tf":1.4142135623730951},"165":{"tf":1.4142135623730951},"166":{"tf":1.0},"167":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"175":{"tf":1.4142135623730951},"25":{"tf":2.0},"252":{"tf":1.4142135623730951},"26":{"tf":1.4142135623730951},"274":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":2.0},"35":{"tf":1.0},"36":{"tf":2.0},"46":{"tf":1.4142135623730951},"73":{"tf":3.1622776601683795}}}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"14":{"tf":1.4142135623730951}}}}}}}}},"df":2,"docs":{"207":{"tf":1.4142135623730951},"210":{"tf":1.7320508075688772}},"m":{"df":0,"docs":{},"l":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{":":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"=":{"\"":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{":":{"/":{"/":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"s":{".":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"/":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"k":{"/":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"/":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"70":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"x":{"df":0,"docs":{},"x":{"df":5,"docs":{"242":{"tf":1.0},"270":{"tf":1.0},"277":{"tf":1.0},"46":{"tf":1.0},"48":{"tf":1.7320508075688772}}}}},"y":{"df":2,"docs":{"137":{"tf":1.0},"210":{"tf":1.7320508075688772}},"e":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"193":{"tf":1.0}}}},"df":1,"docs":{"18":{"tf":1.0}},"p":{"df":1,"docs":{"60":{"tf":1.0}}}},"o":{"df":0,"docs":{},"u":{"'":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":11,"docs":{"103":{"tf":1.0},"104":{"tf":1.0},"105":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"123":{"tf":1.0},"17":{"tf":1.4142135623730951},"274":{"tf":1.0},"279":{"tf":1.0},"68":{"tf":1.0},"90":{"tf":1.0}}}},"r":{"df":12,"docs":{"105":{"tf":1.0},"107":{"tf":1.4142135623730951},"114":{"tf":1.0},"115":{"tf":1.0},"17":{"tf":1.0},"21":{"tf":1.0},"37":{"tf":1.0},"39":{"tf":1.0},"41":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"73":{"tf":1.0}}},"v":{"df":6,"docs":{"102":{"tf":1.0},"104":{"tf":1.0},"106":{"tf":1.7320508075688772},"279":{"tf":1.0},"6":{"tf":1.0},"60":{"tf":1.0}}}},"df":0,"docs":{},"r":{"_":{"c":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"69":{"tf":1.7320508075688772},"72":{"tf":1.0}},"e":{">":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":1,"docs":{"73":{"tf":1.4142135623730951}}}}}}},"u":{"d":{"df":0,"docs":{},"l":{"df":2,"docs":{"73":{"tf":1.4142135623730951},"74":{"tf":1.0}}}},"df":0,"docs":{}}},":":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":1,"docs":{"70":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"70":{"tf":1.0}}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{".":{"df":0,"docs":{},"h":{"df":1,"docs":{"73":{"tf":1.0}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"64":{"tf":1.0}}}}}}},"’":{"df":0,"docs":{},"r":{"df":1,"docs":{"115":{"tf":1.0}}}}}}},"z":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":2,"docs":{"250":{"tf":1.4142135623730951},"84":{"tf":1.4142135623730951}}}}},"i":{"df":0,"docs":{},"p":{"df":2,"docs":{"167":{"tf":1.0},"252":{"tf":1.0}},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"274":{"tf":1.0}}}}}}},"l":{"df":0,"docs":{},"i":{"b":{"1":{"df":0,"docs":{},"g":{"df":1,"docs":{"11":{"tf":1.4142135623730951}}}},"df":1,"docs":{"11":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"h":{"df":0,"docs":{},"r":{"c":{"df":2,"docs":{"11":{"tf":1.0},"12":{"tf":1.0}}},"df":0,"docs":{}}}}}}},"breadcrumbs":{"root":{"0":{".":{"0":{".":{"1":{"df":1,"docs":{"22":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"1":{".":{"0":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":1,"docs":{"283":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"123":{"tf":1.4142135623730951}}},"2":{"7":{".":{"0":{"df":1,"docs":{"20":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"62":{"tf":1.0}}},"5":{"1":{".":{"0":{"df":1,"docs":{"20":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"0":{"0":{"0":{"df":5,"docs":{"134":{"tf":1.0},"135":{"tf":1.0},"136":{"tf":1.0},"137":{"tf":1.0},"138":{"tf":1.0}}},"1":{"df":10,"docs":{"134":{"tf":1.0},"139":{"tf":1.0},"140":{"tf":1.0},"141":{"tf":1.0},"142":{"tf":1.0},"143":{"tf":1.0},"144":{"tf":1.0},"145":{"tf":1.0},"146":{"tf":1.0},"147":{"tf":1.0}}},"2":{"df":13,"docs":{"134":{"tf":1.0},"148":{"tf":1.0},"149":{"tf":1.0},"150":{"tf":1.0},"151":{"tf":1.0},"152":{"tf":1.0},"153":{"tf":1.0},"154":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0},"157":{"tf":1.0},"158":{"tf":1.0},"159":{"tf":1.0}}},"3":{"df":17,"docs":{"134":{"tf":1.0},"160":{"tf":1.0},"161":{"tf":1.0},"162":{"tf":1.0},"163":{"tf":1.0},"164":{"tf":1.0},"165":{"tf":1.0},"166":{"tf":1.0},"167":{"tf":1.0},"168":{"tf":1.0},"169":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"173":{"tf":1.0},"174":{"tf":1.0},"175":{"tf":1.0}}},"4":{"df":11,"docs":{"134":{"tf":1.0},"176":{"tf":1.0},"177":{"tf":1.0},"178":{"tf":1.0},"179":{"tf":1.0},"180":{"tf":1.0},"181":{"tf":1.0},"182":{"tf":1.0},"183":{"tf":1.0},"184":{"tf":1.0},"185":{"tf":1.0}}},"5":{"df":17,"docs":{"134":{"tf":1.0},"186":{"tf":1.0},"187":{"tf":1.0},"188":{"tf":1.0},"189":{"tf":1.0},"190":{"tf":1.0},"191":{"tf":1.0},"192":{"tf":1.0},"193":{"tf":1.0},"194":{"tf":1.0},"195":{"tf":1.0},"196":{"tf":1.0},"197":{"tf":1.0},"198":{"tf":1.0},"199":{"tf":1.0},"200":{"tf":1.0},"201":{"tf":1.0}}},"7":{"df":18,"docs":{"134":{"tf":1.0},"202":{"tf":1.0},"203":{"tf":1.0},"204":{"tf":1.0},"205":{"tf":1.0},"206":{"tf":1.0},"207":{"tf":1.0},"208":{"tf":1.0},"209":{"tf":1.0},"210":{"tf":1.0},"211":{"tf":1.0},"212":{"tf":1.0},"213":{"tf":1.0},"214":{"tf":1.0},"215":{"tf":1.0},"216":{"tf":1.0},"217":{"tf":1.0},"218":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"1":{"df":1,"docs":{"202":{"tf":1.0}}},"6":{"df":3,"docs":{"139":{"tf":1.0},"148":{"tf":1.0},"202":{"tf":1.0}}},"7":{"df":2,"docs":{"160":{"tf":1.0},"268":{"tf":1.4142135623730951}}},"8":{"df":3,"docs":{"148":{"tf":1.0},"176":{"tf":1.0},"268":{"tf":1.4142135623730951}}},"df":2,"docs":{"214":{"tf":1.0},"62":{"tf":1.7320508075688772}},"f":{"8":{"c":{"0":{"3":{"7":{"6":{"3":{"7":{"df":0,"docs":{},"f":{"9":{"df":0,"docs":{},"e":{"b":{"3":{"df":0,"docs":{},"e":{".":{"c":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"\"":{"&":{"df":0,"docs":{},"g":{"df":0,"docs":{},"t":{";":{"&":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{";":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"k":{"df":1,"docs":{"283":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"1":{",":{"4":{"df":1,"docs":{"269":{"tf":1.4142135623730951}}},"7":{"df":1,"docs":{"268":{"tf":1.4142135623730951}}},"8":{"df":1,"docs":{"268":{"tf":1.4142135623730951}}},"df":0,"docs":{}},".":{"4":{"df":1,"docs":{"62":{"tf":1.0}}},"df":0,"docs":{}},"0":{",":{"0":{"0":{"0":{"df":5,"docs":{"204":{"tf":1.0},"214":{"tf":1.7320508075688772},"215":{"tf":1.0},"216":{"tf":1.7320508075688772},"217":{"tf":2.23606797749979}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"0":{",":{"0":{"0":{"0":{"df":2,"docs":{"215":{"tf":1.0},"216":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"0":{"0":{"df":1,"docs":{"214":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"6":{"4":{"4":{"df":3,"docs":{"268":{"tf":1.0},"269":{"tf":1.0},"62":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"268":{"tf":1.0}}},"8":{",":{"6":{"df":1,"docs":{"62":{"tf":1.0}}},"7":{"df":1,"docs":{"62":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"9":{"7":{"df":0,"docs":{},"f":{"8":{"df":0,"docs":{},"e":{"9":{"2":{"a":{"0":{"1":{"df":0,"docs":{},"e":{"3":{"c":{"df":0,"docs":{},"f":{".":{"c":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"\"":{"&":{"df":0,"docs":{},"g":{"df":0,"docs":{},"t":{";":{"&":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{";":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"k":{"df":1,"docs":{"283":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":1,"docs":{"20":{"tf":1.0}},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"204":{"tf":1.0}}}}},"1":{",":{"0":{"0":{"0":{"df":1,"docs":{"204":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"4":{".":{"0":{"a":{"1":{"df":1,"docs":{"269":{"tf":1.0}}},"df":0,"docs":{}},"df":1,"docs":{"269":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"8":{".":{"0":{"a":{"1":{"df":1,"docs":{"268":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"9":{".":{"0":{"a":{"1":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"204":{"tf":1.0}}},"2":{"3":{"df":1,"docs":{"117":{"tf":1.0}}},"df":1,"docs":{"186":{"tf":1.0}}},"3":{"2":{"8":{"5":{"a":{"df":0,"docs":{},"e":{"c":{"3":{"1":{"df":0,"docs":{},"f":{"a":{"2":{"4":{"3":{"df":0,"docs":{},"e":{".":{"c":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"\"":{"&":{"df":0,"docs":{},"g":{"df":0,"docs":{},"t":{";":{"df":0,"docs":{},"☰":{"c":{"df":0,"docs":{},"r":{"df":1,"docs":{"283":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"4":{"7":{"9":{"9":{"2":{"9":{"df":1,"docs":{"228":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"5":{",":{"0":{"0":{"0":{"df":1,"docs":{"204":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"6":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"6":{",":{"0":{"0":{"0":{"df":1,"docs":{"214":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"7":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}},"8":{"6":{"2":{"4":{"8":{"3":{"5":{"0":{"4":{"7":{"0":{"df":1,"docs":{"62":{"tf":3.1622776601683795}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":2,"docs":{"176":{"tf":1.0},"186":{"tf":1.0}}},"7":{"1":{"1":{"4":{"4":{"7":{"df":2,"docs":{"171":{"tf":1.0},"174":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":2,"docs":{"12":{"tf":1.4142135623730951},"139":{"tf":1.0}}},":":{"1":{"df":1,"docs":{"242":{"tf":1.0}}},"df":0,"docs":{}},"df":13,"docs":{"117":{"tf":1.0},"142":{"tf":1.0},"145":{"tf":1.4142135623730951},"151":{"tf":1.4142135623730951},"158":{"tf":1.0},"196":{"tf":1.4142135623730951},"20":{"tf":1.0},"22":{"tf":1.4142135623730951},"252":{"tf":1.7320508075688772},"268":{"tf":2.0},"276":{"tf":1.0},"62":{"tf":4.58257569495584},"79":{"tf":2.0}},"u":{"8":{"df":1,"docs":{"117":{"tf":1.0}}},"df":0,"docs":{}}},"2":{",":{"5":{"0":{"0":{"df":1,"docs":{"204":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},".":{"0":{"df":2,"docs":{"64":{"tf":1.4142135623730951},"7":{"tf":1.0}}},"1":{".":{"2":{"df":2,"docs":{"137":{"tf":1.0},"138":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":1,"docs":{"62":{"tf":1.4142135623730951}}},"5":{"df":1,"docs":{"204":{"tf":1.0}}},"df":0,"docs":{}},"0":{",":{"0":{"0":{"0":{"df":1,"docs":{"216":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"0":{"0":{"0":{"df":1,"docs":{"214":{"tf":1.0}}},"df":3,"docs":{"206":{"tf":1.0},"217":{"tf":1.0},"218":{"tf":1.0}}},"df":0,"docs":{}},"1":{"8":{"df":1,"docs":{"192":{"tf":1.0}}},"df":0,"docs":{}},"2":{"1":{"df":4,"docs":{"139":{"tf":1.0},"148":{"tf":1.0},"160":{"tf":1.0},"176":{"tf":1.0}}},"2":{"df":1,"docs":{"186":{"tf":1.0}}},"3":{"df":2,"docs":{"187":{"tf":1.0},"202":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"1":{".":{"4":{".":{"7":{"0":{"7":{"5":{"5":{"2":{"9":{"df":1,"docs":{"12":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"2":{",":{"7":{"df":1,"docs":{"62":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":1,"docs":{"160":{"tf":1.0}}},"5":{",":{"0":{"0":{"0":{"df":1,"docs":{"204":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},".":{"2":{".":{"9":{"5":{"1":{"9":{"6":{"5":{"3":{"df":1,"docs":{"12":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"6":{"df":1,"docs":{"280":{"tf":1.0}}},"df":2,"docs":{"204":{"tf":1.0},"37":{"tf":1.0}},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"204":{"tf":1.0}}}}},"6":{"2":{"8":{"df":1,"docs":{"149":{"tf":1.0}}},"df":0,"docs":{}},"df":1,"docs":{"13":{"tf":1.0}}},"8":{"_":{"df":1,"docs":{"268":{"tf":1.4142135623730951}}},"df":0,"docs":{},"f":{"1":{"9":{"3":{"0":{"7":{".":{".":{"2":{"2":{"5":{"d":{"c":{"c":{"b":{"b":{"df":1,"docs":{"62":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":14,"docs":{"142":{"tf":1.0},"146":{"tf":1.4142135623730951},"149":{"tf":1.0},"151":{"tf":1.4142135623730951},"18":{"tf":1.0},"197":{"tf":1.4142135623730951},"22":{"tf":1.0},"237":{"tf":1.0},"242":{"tf":1.0},"245":{"tf":1.0},"261":{"tf":1.0},"264":{"tf":1.4142135623730951},"62":{"tf":3.0},"79":{"tf":1.0}},"f":{"a":{"df":1,"docs":{"276":{"tf":1.0}}},"df":0,"docs":{}}},"3":{".":{"6":{"df":1,"docs":{"11":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"1":{"_":{"df":1,"docs":{"268":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"2":{",":{"8":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}},"3":{"df":1,"docs":{"176":{"tf":1.0}}},"7":{"6":{"8":{"df":1,"docs":{"264":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"116":{"tf":1.4142135623730951}}},"4":{",":{"6":{"df":1,"docs":{"268":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"6":{",":{"8":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"7":{"df":1,"docs":{"204":{"tf":1.0}}},"8":{"9":{"9":{"df":1,"docs":{"145":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"9":{"0":{"9":{"df":1,"docs":{"48":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"a":{"d":{"9":{"df":1,"docs":{"258":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":10,"docs":{"151":{"tf":1.4142135623730951},"198":{"tf":1.4142135623730951},"199":{"tf":1.0},"213":{"tf":1.4142135623730951},"22":{"tf":1.0},"242":{"tf":1.0},"249":{"tf":1.4142135623730951},"274":{"tf":1.4142135623730951},"60":{"tf":1.4142135623730951},"62":{"tf":1.7320508075688772}},"r":{"d":{"df":2,"docs":{"121":{"tf":1.0},"201":{"tf":1.7320508075688772}}},"df":0,"docs":{}}},"4":{".":{"9":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"/":{"d":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"37":{"tf":2.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"0":{"0":{"df":1,"docs":{"204":{"tf":1.0}}},"df":0,"docs":{}},"1":{"0":{"1":{"df":1,"docs":{"139":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"2":{"df":1,"docs":{"37":{"tf":1.0}}},"5":{",":{"0":{"0":{"0":{"df":1,"docs":{"204":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"6":{"0":{"4":{"df":1,"docs":{"258":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"9":{"6":{"6":{"df":1,"docs":{"38":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"62":{"tf":1.0}}},"5":{",":{"0":{"0":{"0":{"df":1,"docs":{"204":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"0":{",":{"0":{"0":{"0":{"df":1,"docs":{"204":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"0":{"0":{"df":3,"docs":{"206":{"tf":1.0},"217":{"tf":1.0},"218":{"tf":1.0}}},"df":0,"docs":{}},"df":1,"docs":{"204":{"tf":1.0}},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"204":{"tf":1.0}}}}},"2":{"1":{"0":{"df":1,"docs":{"280":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"7":{"4":{"5":{"]":{"(":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{":":{"/":{"/":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"u":{"b":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"/":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"268":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"6":{"4":{"]":{"(":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{":":{"/":{"/":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"u":{"b":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"/":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"268":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"6":{"]":{"(":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{":":{"/":{"/":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"u":{"b":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"/":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"268":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"8":{"4":{"]":{"(":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{":":{"/":{"/":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"u":{"b":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"/":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"268":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":4,"docs":{"20":{"tf":1.0},"204":{"tf":1.7320508075688772},"62":{"tf":1.0},"7":{"tf":1.0}}},"6":{",":{"4":{"0":{"0":{"df":1,"docs":{"204":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},".":{"4":{"df":1,"docs":{"204":{"tf":1.0}}},"df":0,"docs":{}},"1":{"4":{"4":{"df":1,"docs":{"264":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"2":{"df":1,"docs":{"264":{"tf":1.0}}},"4":{"df":2,"docs":{"17":{"tf":2.0},"37":{"tf":1.0}}},"6":{"6":{",":{"7":{"df":1,"docs":{"62":{"tf":1.0}}},"8":{"df":1,"docs":{"62":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"7":{"df":2,"docs":{"215":{"tf":1.0},"217":{"tf":1.0}}},"df":3,"docs":{"204":{"tf":1.7320508075688772},"215":{"tf":1.0},"62":{"tf":1.0}}},"7":{"5":{"df":1,"docs":{"206":{"tf":1.0}},"m":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"218":{"tf":1.0}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"204":{"tf":1.0}}}},"~":{"8":{"0":{"df":1,"docs":{"216":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"0":{"b":{"0":{"c":{"2":{"5":{"9":{"a":{"a":{"2":{"df":1,"docs":{"258":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"f":{"2":{"c":{"0":{"7":{"a":{"1":{"a":{"8":{".":{".":{"0":{"6":{"6":{"8":{"8":{"df":0,"docs":{},"f":{"d":{"c":{"a":{"b":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"8":{"0":{"df":1,"docs":{"204":{"tf":1.0}}},"c":{"d":{"9":{"2":{"3":{"8":{"7":{"3":{".":{".":{"6":{"4":{"8":{"2":{"0":{"1":{"8":{"df":0,"docs":{},"e":{"0":{"df":1,"docs":{"269":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"116":{"tf":1.0}}},"9":{"0":{"df":2,"docs":{"206":{"tf":1.0},"218":{"tf":1.0}},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"204":{"tf":1.0}}}}},"5":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"204":{"tf":1.0}}}}},"6":{"df":1,"docs":{"216":{"tf":1.0}}},"7":{"df":1,"docs":{"204":{"tf":1.0}}},"8":{"df":1,"docs":{"216":{"tf":1.0}}},"9":{".":{"6":{"df":1,"docs":{"216":{"tf":1.0}}},"9":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"204":{"tf":1.0}}}}},"df":0,"docs":{}},"df":4,"docs":{"204":{"tf":1.0},"208":{"tf":1.0},"216":{"tf":1.0},"217":{"tf":1.0}},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"204":{"tf":1.0}}}}},"df":0,"docs":{}},"_":{"2":{"0":{"2":{"3":{"df":1,"docs":{"268":{"tf":2.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"_":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"_":{"_":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"_":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"(":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"_":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"_":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":1,"docs":{"37":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"v":{"a":{"_":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"s":{"_":{"_":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":1,"docs":{"117":{"tf":1.0}},"y":{"df":0,"docs":{},"y":{"df":0,"docs":{},"y":{"df":0,"docs":{},"i":{"df":1,"docs":{"268":{"tf":1.0}}}}}}},"a":{"/":{"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{".":{"df":0,"docs":{},"m":{"d":{"df":1,"docs":{"268":{"tf":2.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"a":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"/":{"c":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":1,"docs":{"62":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{},"s":{"df":0,"docs":{},"r":{"c":{"/":{"d":{"b":{"/":{"d":{"b":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"62":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"62":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":2,"docs":{"268":{"tf":2.0},"269":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}}}}}}}}},"1":{"df":1,"docs":{"154":{"tf":1.4142135623730951}}},"2":{"df":2,"docs":{"152":{"tf":1.0},"155":{"tf":1.4142135623730951}}},"3":{"df":1,"docs":{"156":{"tf":1.4142135623730951}}},"7":{"c":{"d":{"df":1,"docs":{"258":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"a":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"h":{"6":{"4":{"df":2,"docs":{"17":{"tf":1.0},"37":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":6,"docs":{"220":{"tf":1.0},"221":{"tf":3.0},"222":{"tf":1.7320508075688772},"223":{"tf":1.0},"224":{"tf":1.0},"37":{"tf":1.4142135623730951}}}},"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":6,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"162":{"tf":1.0},"178":{"tf":1.0},"234":{"tf":1.0},"274":{"tf":1.4142135623730951}}}},"o":{"df":0,"docs":{},"v":{"df":31,"docs":{"105":{"tf":1.0},"106":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.4142135623730951},"123":{"tf":1.0},"124":{"tf":1.0},"14":{"tf":1.0},"145":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.4142135623730951},"200":{"tf":1.4142135623730951},"204":{"tf":1.0},"214":{"tf":1.0},"217":{"tf":1.0},"22":{"tf":1.7320508075688772},"220":{"tf":1.0},"224":{"tf":1.4142135623730951},"227":{"tf":1.0},"228":{"tf":1.0},"235":{"tf":1.0},"242":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951},"263":{"tf":1.0},"264":{"tf":1.0},"268":{"tf":1.0},"274":{"tf":1.7320508075688772},"280":{"tf":1.0},"64":{"tf":1.0},"73":{"tf":1.0},"87":{"tf":1.0},"90":{"tf":1.0}}}},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":4,"docs":{"61":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0},"67":{"tf":1.0}}}}}},"t":{"df":0,"docs":{},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"103":{"tf":1.0},"229":{"tf":1.4142135623730951},"79":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"c":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"282":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"t":{"df":11,"docs":{"105":{"tf":1.4142135623730951},"106":{"tf":1.0},"115":{"tf":1.0},"139":{"tf":1.0},"148":{"tf":1.0},"160":{"tf":1.0},"202":{"tf":1.0},"231":{"tf":1.0},"274":{"tf":1.0},"37":{"tf":1.0},"79":{"tf":1.4142135623730951}}}},"s":{"df":0,"docs":{},"s":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"242":{"tf":1.0}}}}}}}},"df":13,"docs":{"114":{"tf":1.4142135623730951},"126":{"tf":1.4142135623730951},"127":{"tf":1.4142135623730951},"145":{"tf":1.0},"187":{"tf":1.0},"188":{"tf":1.0},"245":{"tf":1.0},"254":{"tf":1.0},"255":{"tf":1.0},"274":{"tf":1.0},"277":{"tf":1.4142135623730951},"283":{"tf":1.7320508075688772},"37":{"tf":1.0}}}}},"i":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"175":{"tf":1.0}}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"d":{"df":3,"docs":{"104":{"tf":1.0},"268":{"tf":1.0},"282":{"tf":1.0}}},"df":0,"docs":{}}},"o":{"d":{"df":1,"docs":{"108":{"tf":1.0}}},"df":0,"docs":{}},"p":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":3,"docs":{"43":{"tf":1.0},"64":{"tf":1.0},"7":{"tf":1.0}}}}},"df":0,"docs":{}}},"r":{"d":{"df":1,"docs":{"25":{"tf":1.0}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"263":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{".":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"z":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":1,"docs":{"283":{"tf":1.0}}}}}}}}}}}}}}}}},"df":0,"docs":{}}}}}}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"c":{"a":{"df":0,"docs":{},"p":{"a":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"a":{"df":1,"docs":{"283":{"tf":1.0}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"283":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"a":{"df":1,"docs":{"283":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":7,"docs":{"0":{"tf":1.0},"245":{"tf":1.0},"254":{"tf":1.7320508075688772},"274":{"tf":1.0},"276":{"tf":2.449489742783178},"283":{"tf":2.23606797749979},"284":{"tf":1.0}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":1,"docs":{"242":{"tf":1.0}}}}}}}}}},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"145":{"tf":1.0}}}}},"df":2,"docs":{"39":{"tf":1.0},"40":{"tf":1.0}},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":1,"docs":{"167":{"tf":1.0}}}}}},"t":{"df":3,"docs":{"237":{"tf":1.0},"245":{"tf":1.0},"251":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":10,"docs":{"119":{"tf":1.0},"226":{"tf":1.0},"230":{"tf":1.0},"231":{"tf":1.0},"235":{"tf":1.0},"242":{"tf":1.0},"249":{"tf":1.7320508075688772},"250":{"tf":1.0},"272":{"tf":1.0},"273":{"tf":1.0}}}},"v":{"df":1,"docs":{"205":{"tf":1.0}}}},"u":{"a":{"df":0,"docs":{},"l":{"df":10,"docs":{"179":{"tf":1.0},"194":{"tf":1.0},"226":{"tf":1.0},"232":{"tf":1.0},"261":{"tf":1.4142135623730951},"279":{"tf":1.0},"51":{"tf":1.0},"58":{"tf":1.0},"69":{"tf":1.0},"78":{"tf":1.0}}}},"df":0,"docs":{}}}},"d":{"d":{"/":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"d":{"df":2,"docs":{"145":{"tf":1.0},"146":{"tf":1.0}}},"df":0,"docs":{}}}},"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"_":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"d":{"df":2,"docs":{"145":{"tf":2.0},"146":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":83,"docs":{"100":{"tf":1.0},"101":{"tf":1.0},"102":{"tf":1.0},"103":{"tf":1.0},"104":{"tf":1.0},"105":{"tf":1.4142135623730951},"106":{"tf":1.0},"107":{"tf":1.0},"108":{"tf":1.4142135623730951},"109":{"tf":1.7320508075688772},"11":{"tf":1.4142135623730951},"110":{"tf":1.0},"111":{"tf":1.0},"112":{"tf":1.0},"113":{"tf":1.4142135623730951},"114":{"tf":1.0},"115":{"tf":1.0},"116":{"tf":1.0},"117":{"tf":1.0},"119":{"tf":1.0},"12":{"tf":1.0},"120":{"tf":1.0},"123":{"tf":1.0},"124":{"tf":1.0},"13":{"tf":1.0},"145":{"tf":1.4142135623730951},"146":{"tf":1.0},"158":{"tf":1.0},"167":{"tf":1.4142135623730951},"184":{"tf":1.0},"20":{"tf":1.0},"201":{"tf":1.0},"22":{"tf":1.4142135623730951},"223":{"tf":1.0},"224":{"tf":1.0},"229":{"tf":1.4142135623730951},"249":{"tf":1.0},"268":{"tf":1.7320508075688772},"274":{"tf":1.4142135623730951},"281":{"tf":1.0},"284":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"51":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":2.23606797749979},"61":{"tf":2.8284271247461903},"68":{"tf":1.0},"69":{"tf":2.23606797749979},"7":{"tf":1.4142135623730951},"70":{"tf":1.4142135623730951},"71":{"tf":1.0},"72":{"tf":1.0},"73":{"tf":3.872983346207417},"74":{"tf":2.0},"75":{"tf":1.0},"76":{"tf":1.0},"77":{"tf":1.0},"78":{"tf":1.0},"79":{"tf":1.0},"80":{"tf":1.0},"81":{"tf":1.0},"82":{"tf":1.0},"83":{"tf":1.0},"84":{"tf":1.0},"85":{"tf":1.0},"86":{"tf":1.0},"87":{"tf":1.0},"88":{"tf":1.0},"89":{"tf":1.0},"90":{"tf":1.0},"91":{"tf":1.0},"92":{"tf":1.0},"93":{"tf":1.0},"94":{"tf":1.0},"95":{"tf":1.0},"96":{"tf":1.0},"97":{"tf":1.0},"98":{"tf":1.0},"99":{"tf":1.0}},"i":{"df":0,"docs":{},"t":{"df":11,"docs":{"11":{"tf":1.0},"13":{"tf":1.0},"141":{"tf":1.0},"167":{"tf":1.0},"201":{"tf":1.0},"274":{"tf":1.4142135623730951},"43":{"tf":1.4142135623730951},"6":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"67":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":7,"docs":{"180":{"tf":1.0},"209":{"tf":1.0},"22":{"tf":1.0},"254":{"tf":1.0},"257":{"tf":1.0},"58":{"tf":1.0},"60":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":5,"docs":{"141":{"tf":1.0},"169":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"182":{"tf":1.0}}}}}}},"df":37,"docs":{"101":{"tf":1.0},"102":{"tf":1.4142135623730951},"104":{"tf":1.0},"105":{"tf":1.0},"108":{"tf":1.4142135623730951},"109":{"tf":1.7320508075688772},"11":{"tf":1.0},"121":{"tf":1.0},"123":{"tf":2.449489742783178},"145":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":1.0},"201":{"tf":1.0},"22":{"tf":2.0},"23":{"tf":1.0},"249":{"tf":1.0},"264":{"tf":1.0},"277":{"tf":1.0},"280":{"tf":1.0},"282":{"tf":1.0},"284":{"tf":1.0},"285":{"tf":1.0},"286":{"tf":1.0},"31":{"tf":1.0},"32":{"tf":1.0},"36":{"tf":1.0},"41":{"tf":1.0},"57":{"tf":1.4142135623730951},"59":{"tf":1.0},"61":{"tf":1.4142135623730951},"64":{"tf":1.4142135623730951},"65":{"tf":1.0},"66":{"tf":1.0},"67":{"tf":1.0},"68":{"tf":2.0},"69":{"tf":1.0},"73":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":1,"docs":{"7":{"tf":1.0}}}}},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"91":{"tf":1.0}}}}},"j":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":4,"docs":{"16":{"tf":1.0},"25":{"tf":1.0},"277":{"tf":1.0},"37":{"tf":1.7320508075688772}}}}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"117":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"r":{"df":89,"docs":{"134":{"tf":2.8284271247461903},"135":{"tf":1.0},"136":{"tf":1.0},"137":{"tf":1.4142135623730951},"138":{"tf":1.4142135623730951},"139":{"tf":1.0},"140":{"tf":1.0},"141":{"tf":1.0},"142":{"tf":1.0},"143":{"tf":1.0},"144":{"tf":1.0},"145":{"tf":1.0},"146":{"tf":1.0},"147":{"tf":1.0},"148":{"tf":1.0},"149":{"tf":1.4142135623730951},"150":{"tf":1.0},"151":{"tf":1.0},"152":{"tf":1.0},"153":{"tf":1.0},"154":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0},"157":{"tf":1.0},"158":{"tf":1.0},"159":{"tf":1.0},"160":{"tf":1.0},"161":{"tf":1.0},"162":{"tf":1.0},"163":{"tf":1.0},"164":{"tf":1.0},"165":{"tf":1.0},"166":{"tf":1.0},"167":{"tf":1.0},"168":{"tf":1.0},"169":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"173":{"tf":1.0},"174":{"tf":1.0},"175":{"tf":1.0},"176":{"tf":1.0},"177":{"tf":1.0},"178":{"tf":1.0},"179":{"tf":1.0},"180":{"tf":1.0},"181":{"tf":1.0},"182":{"tf":1.0},"183":{"tf":1.0},"184":{"tf":1.0},"185":{"tf":1.0},"186":{"tf":1.0},"187":{"tf":1.0},"188":{"tf":1.0},"189":{"tf":1.0},"190":{"tf":1.0},"191":{"tf":1.0},"192":{"tf":1.0},"193":{"tf":1.0},"194":{"tf":1.0},"195":{"tf":1.0},"196":{"tf":1.0},"197":{"tf":1.0},"198":{"tf":1.0},"199":{"tf":1.0},"200":{"tf":1.4142135623730951},"201":{"tf":1.4142135623730951},"202":{"tf":1.0},"203":{"tf":1.0},"204":{"tf":1.0},"205":{"tf":1.0},"206":{"tf":1.0},"207":{"tf":1.0},"208":{"tf":1.0},"209":{"tf":1.0},"210":{"tf":1.0},"211":{"tf":1.0},"212":{"tf":1.0},"213":{"tf":1.0},"214":{"tf":1.0},"215":{"tf":1.0},"216":{"tf":1.0},"217":{"tf":1.0},"218":{"tf":1.0},"23":{"tf":1.0},"251":{"tf":1.0},"32":{"tf":1.0},"73":{"tf":1.0}}},"v":{"a":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"6":{"tf":1.4142135623730951}}},"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"g":{"df":2,"docs":{"12":{"tf":1.0},"220":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"107":{"tf":1.0}}}}}}},"i":{"c":{"df":2,"docs":{"122":{"tf":1.4142135623730951},"69":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"157":{"tf":1.0},"268":{"tf":1.0},"63":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"220":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"r":{"a":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"105":{"tf":1.0},"106":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"g":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":6,"docs":{"11":{"tf":1.0},"157":{"tf":1.0},"158":{"tf":1.0},"242":{"tf":1.0},"245":{"tf":1.0},"79":{"tf":1.0}},"s":{"df":0,"docs":{},"t":{"df":25,"docs":{"15":{"tf":1.7320508075688772},"151":{"tf":1.4142135623730951},"152":{"tf":1.4142135623730951},"155":{"tf":1.4142135623730951},"156":{"tf":1.4142135623730951},"16":{"tf":1.0},"17":{"tf":1.0},"194":{"tf":1.0},"23":{"tf":1.4142135623730951},"268":{"tf":1.4142135623730951},"269":{"tf":1.0},"274":{"tf":1.7320508075688772},"282":{"tf":1.0},"32":{"tf":1.0},"37":{"tf":1.0},"48":{"tf":1.0},"49":{"tf":1.0},"51":{"tf":1.0},"52":{"tf":1.4142135623730951},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"62":{"tf":1.0},"64":{"tf":1.4142135623730951},"7":{"tf":1.0}}}}}}},"df":0,"docs":{},"o":{"df":1,"docs":{"213":{"tf":1.0}}},"p":{"df":1,"docs":{"12":{"tf":1.0}}},"r":{"df":0,"docs":{},"e":{"df":2,"docs":{"232":{"tf":1.0},"7":{"tf":1.0}}}}},"h":{"df":0,"docs":{},"e":{"a":{"d":{"df":1,"docs":{"104":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"m":{"df":4,"docs":{"102":{"tf":1.0},"103":{"tf":1.0},"104":{"tf":1.0},"116":{"tf":1.0}}}},"k":{"a":{"df":1,"docs":{"260":{"tf":1.0}}},"df":0,"docs":{}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"64":{"tf":1.4142135623730951}}}}},"g":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"m":{"df":1,"docs":{"282":{"tf":1.0}}}}}}}}},"i":{"a":{"df":1,"docs":{"11":{"tf":1.0}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"df":3,"docs":{"171":{"tf":1.0},"172":{"tf":1.0},"274":{"tf":1.0}}}}},"l":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"279":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"(":{"d":{"df":0,"docs":{},"e":{"a":{"d":{"_":{"c":{"df":0,"docs":{},"o":{"d":{"df":1,"docs":{"60":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":15,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"145":{"tf":1.0},"157":{"tf":1.0},"158":{"tf":1.0},"183":{"tf":1.0},"190":{"tf":1.4142135623730951},"198":{"tf":1.0},"199":{"tf":1.0},"229":{"tf":1.0},"233":{"tf":1.0},"242":{"tf":1.0},"255":{"tf":1.0},"273":{"tf":1.0},"39":{"tf":1.0}}}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"20":{"tf":1.0}}}},"df":0,"docs":{}}}}}}},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"198":{"tf":1.0},"220":{"tf":1.0}},"g":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"70":{"tf":1.0},"72":{"tf":1.0}}},"df":0,"docs":{}}}}}},"r":{"df":0,"docs":{},"e":{"a":{"d":{"df":0,"docs":{},"i":{"df":17,"docs":{"122":{"tf":1.0},"187":{"tf":1.0},"194":{"tf":1.0},"196":{"tf":1.0},"197":{"tf":1.0},"198":{"tf":1.0},"199":{"tf":1.0},"200":{"tf":1.0},"229":{"tf":1.0},"23":{"tf":1.0},"241":{"tf":1.4142135623730951},"258":{"tf":1.0},"32":{"tf":1.0},"36":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"64":{"tf":1.7320508075688772}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":4,"docs":{"115":{"tf":1.4142135623730951},"122":{"tf":1.0},"225":{"tf":1.0},"26":{"tf":1.0}}}}},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":9,"docs":{"105":{"tf":1.0},"124":{"tf":1.0},"132":{"tf":1.0},"190":{"tf":1.0},"214":{"tf":1.4142135623730951},"225":{"tf":1.0},"232":{"tf":1.0},"262":{"tf":1.0},"38":{"tf":1.0}}}}}}}},"w":{"a":{"df":0,"docs":{},"y":{"df":10,"docs":{"114":{"tf":1.0},"133":{"tf":1.4142135623730951},"146":{"tf":1.0},"189":{"tf":1.0},"198":{"tf":1.0},"22":{"tf":1.0},"260":{"tf":1.0},"60":{"tf":1.0},"79":{"tf":1.0},"99":{"tf":1.0}}}},"df":0,"docs":{}}},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":8,"docs":{"166":{"tf":1.0},"169":{"tf":1.4142135623730951},"170":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"7":{"tf":1.0}}}}}}},"n":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"y":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":4,"docs":{"178":{"tf":1.0},"214":{"tf":1.4142135623730951},"264":{"tf":1.0},"274":{"tf":1.0}}}}}}},"d":{"/":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":2,"docs":{"104":{"tf":1.0},"122":{"tf":1.0}}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{".":{"df":0,"docs":{},"y":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":1,"docs":{"113":{"tf":1.0}},"t":{"df":0,"docs":{},"o":{"df":1,"docs":{"70":{"tf":1.0}}}}}}}},"/":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{".":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"d":{"df":0,"docs":{},"l":{"df":1,"docs":{"108":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"/":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"/":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"e":{"/":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":1,"docs":{"108":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"df":1,"docs":{"20":{"tf":1.0}}}}}},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"x":{"df":1,"docs":{"119":{"tf":1.0}}}}}}}}},"i":{"df":0,"docs":{},"o":{"df":1,"docs":{"11":{"tf":1.0}}}},"k":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"65":{"tf":1.4142135623730951}}}}}}}},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{".":{"df":0,"docs":{},"h":{"df":1,"docs":{"37":{"tf":1.0}}}},"df":0,"docs":{}}}},"s":{"df":0,"docs":{},"r":{"c":{"/":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"/":{"df":0,"docs":{},"j":{"a":{"df":0,"docs":{},"v":{"a":{"df":1,"docs":{"108":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"/":{"df":0,"docs":{},"j":{"a":{"df":0,"docs":{},"v":{"a":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"/":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"/":{"<":{"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"_":{"c":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"70":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":1,"docs":{"200":{"tf":1.0}}}}}}}},"2":{"1":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":1,"docs":{"12":{"tf":1.0}},"e":{"/":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"s":{"/":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"/":{"df":0,"docs":{},"s":{"d":{"df":0,"docs":{},"k":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"13":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"s":{"d":{"df":0,"docs":{},"k":{"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":1,"docs":{"12":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":59,"docs":{"10":{"tf":1.0},"103":{"tf":1.7320508075688772},"108":{"tf":1.4142135623730951},"11":{"tf":1.0},"110":{"tf":1.7320508075688772},"111":{"tf":1.0},"112":{"tf":1.0},"113":{"tf":1.0},"114":{"tf":1.0},"115":{"tf":1.7320508075688772},"116":{"tf":1.4142135623730951},"117":{"tf":2.0},"118":{"tf":2.0},"119":{"tf":1.4142135623730951},"12":{"tf":2.8284271247461903},"126":{"tf":2.0},"13":{"tf":1.7320508075688772},"131":{"tf":1.0},"133":{"tf":1.0},"145":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":2.0},"19":{"tf":1.0},"190":{"tf":1.0},"192":{"tf":1.0},"199":{"tf":1.0},"20":{"tf":1.4142135623730951},"200":{"tf":1.0},"203":{"tf":1.0},"206":{"tf":1.7320508075688772},"21":{"tf":1.4142135623730951},"210":{"tf":1.0},"217":{"tf":1.0},"218":{"tf":1.4142135623730951},"220":{"tf":1.7320508075688772},"221":{"tf":2.23606797749979},"223":{"tf":1.0},"225":{"tf":2.0},"230":{"tf":1.0},"237":{"tf":1.4142135623730951},"255":{"tf":1.0},"264":{"tf":1.0},"268":{"tf":1.4142135623730951},"271":{"tf":1.0},"272":{"tf":1.0},"274":{"tf":2.0},"278":{"tf":1.0},"280":{"tf":1.0},"37":{"tf":3.1622776601683795},"38":{"tf":1.0},"39":{"tf":1.7320508075688772},"40":{"tf":1.4142135623730951},"45":{"tf":1.7320508075688772},"49":{"tf":2.449489742783178},"54":{"tf":2.0},"69":{"tf":1.0},"70":{"tf":1.4142135623730951},"9":{"tf":1.0},"90":{"tf":1.0}},"e":{"a":{"b":{"df":0,"docs":{},"i":{"df":1,"docs":{"37":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"m":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"x":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":3,"docs":{"103":{"tf":1.4142135623730951},"108":{"tf":1.0},"70":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"200":{"tf":1.0}}}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"h":{"df":3,"docs":{"132":{"tf":1.0},"233":{"tf":1.0},"283":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"w":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"205":{"tf":1.0}}}}}},"t":{"df":1,"docs":{"37":{"tf":3.0}},"i":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":1,"docs":{"231":{"tf":1.0}}}}},"df":0,"docs":{}}},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"3":{"tf":1.0}}}},"t":{"df":0,"docs":{},"h":{"df":4,"docs":{"228":{"tf":1.0},"242":{"tf":1.0},"264":{"tf":1.0},"61":{"tf":1.0}}}},"w":{"a":{"df":0,"docs":{},"y":{"df":1,"docs":{"60":{"tf":1.0}}}},"df":0,"docs":{}}}},"p":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"81":{"tf":1.0}}}}},"df":0,"docs":{},"i":{"df":38,"docs":{"104":{"tf":1.7320508075688772},"105":{"tf":1.7320508075688772},"106":{"tf":1.4142135623730951},"107":{"tf":1.7320508075688772},"108":{"tf":1.4142135623730951},"109":{"tf":1.0},"111":{"tf":1.0},"114":{"tf":1.0},"116":{"tf":1.0},"118":{"tf":1.4142135623730951},"119":{"tf":1.0},"134":{"tf":1.0},"139":{"tf":1.4142135623730951},"141":{"tf":1.4142135623730951},"142":{"tf":2.0},"143":{"tf":1.0},"145":{"tf":2.6457513110645907},"146":{"tf":2.0},"166":{"tf":1.0},"197":{"tf":1.0},"200":{"tf":2.0},"21":{"tf":1.0},"226":{"tf":1.0},"232":{"tf":3.1622776601683795},"236":{"tf":1.7320508075688772},"252":{"tf":1.4142135623730951},"281":{"tf":1.4142135623730951},"283":{"tf":1.0},"43":{"tf":1.4142135623730951},"45":{"tf":1.4142135623730951},"46":{"tf":1.0},"69":{"tf":1.0},"7":{"tf":1.0},"70":{"tf":1.0},"73":{"tf":1.0},"78":{"tf":1.7320508075688772},"79":{"tf":1.4142135623730951},"96":{"tf":1.0}}},"k":{"df":2,"docs":{"39":{"tf":1.4142135623730951},"40":{"tf":1.0}}},"p":{"'":{"df":1,"docs":{"226":{"tf":1.0}}},"a":{"df":0,"docs":{},"r":{"df":2,"docs":{"242":{"tf":1.0},"37":{"tf":1.0}}}},"df":35,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"142":{"tf":1.0},"158":{"tf":1.0},"165":{"tf":1.0},"17":{"tf":1.0},"179":{"tf":1.0},"180":{"tf":1.0},"183":{"tf":1.4142135623730951},"184":{"tf":1.0},"189":{"tf":1.0},"19":{"tf":1.0},"190":{"tf":1.0},"194":{"tf":1.0},"20":{"tf":1.0},"200":{"tf":1.4142135623730951},"205":{"tf":1.0},"209":{"tf":1.0},"212":{"tf":1.0},"213":{"tf":2.0},"225":{"tf":1.4142135623730951},"228":{"tf":2.0},"229":{"tf":1.4142135623730951},"233":{"tf":2.0},"236":{"tf":1.0},"242":{"tf":1.7320508075688772},"250":{"tf":1.0},"254":{"tf":1.0},"255":{"tf":1.4142135623730951},"257":{"tf":1.4142135623730951},"273":{"tf":1.0},"40":{"tf":1.0},"49":{"tf":1.0},"52":{"tf":1.4142135623730951},"84":{"tf":1.0}},"e":{"a":{"df":0,"docs":{},"r":{"df":8,"docs":{"117":{"tf":1.0},"172":{"tf":1.4142135623730951},"228":{"tf":1.0},"249":{"tf":2.23606797749979},"37":{"tf":1.0},"64":{"tf":1.0},"73":{"tf":1.4142135623730951},"86":{"tf":1.0}}}},"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"98":{"tf":1.0}},"i":{"df":0,"docs":{},"x":{"df":2,"docs":{"173":{"tf":1.4142135623730951},"60":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}},"l":{"df":9,"docs":{"161":{"tf":1.0},"162":{"tf":1.0},"163":{"tf":1.0},"169":{"tf":1.0},"170":{"tf":1.4142135623730951},"171":{"tf":1.0},"172":{"tf":1.0},"175":{"tf":1.0},"252":{"tf":1.0}},"e":{"'":{"df":2,"docs":{"274":{"tf":1.0},"46":{"tf":1.0}}},"df":0,"docs":{}},"i":{"c":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"'":{"df":1,"docs":{"221":{"tf":1.0}}},".":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"z":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"283":{"tf":1.0}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"x":{"a":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"l":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"df":1,"docs":{"283":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"\\":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"257":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"’":{"df":1,"docs":{"283":{"tf":1.0}}}}}}}},"df":101,"docs":{"0":{"tf":2.23606797749979},"1":{"tf":1.4142135623730951},"10":{"tf":1.0},"11":{"tf":1.4142135623730951},"114":{"tf":1.0},"118":{"tf":2.0},"119":{"tf":1.4142135623730951},"12":{"tf":2.0},"120":{"tf":1.7320508075688772},"121":{"tf":1.0},"122":{"tf":2.23606797749979},"123":{"tf":1.4142135623730951},"124":{"tf":3.1622776601683795},"125":{"tf":2.0},"130":{"tf":1.0},"14":{"tf":1.4142135623730951},"15":{"tf":1.7320508075688772},"16":{"tf":1.0},"161":{"tf":1.7320508075688772},"162":{"tf":1.0},"164":{"tf":1.0},"165":{"tf":1.7320508075688772},"167":{"tf":3.1622776601683795},"175":{"tf":1.4142135623730951},"177":{"tf":1.0},"18":{"tf":2.23606797749979},"180":{"tf":1.0},"184":{"tf":1.0},"186":{"tf":1.0},"187":{"tf":1.0},"189":{"tf":1.4142135623730951},"19":{"tf":1.0},"190":{"tf":1.0},"194":{"tf":1.4142135623730951},"197":{"tf":1.4142135623730951},"2":{"tf":1.4142135623730951},"20":{"tf":2.0},"200":{"tf":2.6457513110645907},"201":{"tf":2.449489742783178},"203":{"tf":1.4142135623730951},"204":{"tf":1.0},"21":{"tf":1.4142135623730951},"218":{"tf":1.0},"22":{"tf":2.0},"220":{"tf":1.4142135623730951},"221":{"tf":1.0},"222":{"tf":1.4142135623730951},"225":{"tf":2.0},"228":{"tf":1.4142135623730951},"23":{"tf":1.7320508075688772},"232":{"tf":2.23606797749979},"24":{"tf":1.0},"242":{"tf":1.0},"244":{"tf":1.7320508075688772},"248":{"tf":1.0},"25":{"tf":1.7320508075688772},"252":{"tf":1.7320508075688772},"253":{"tf":1.7320508075688772},"256":{"tf":1.0},"258":{"tf":1.0},"259":{"tf":1.7320508075688772},"260":{"tf":1.4142135623730951},"262":{"tf":1.4142135623730951},"265":{"tf":1.4142135623730951},"268":{"tf":2.23606797749979},"274":{"tf":2.449489742783178},"276":{"tf":1.0},"28":{"tf":1.0},"283":{"tf":2.6457513110645907},"284":{"tf":1.0},"29":{"tf":1.4142135623730951},"3":{"tf":1.7320508075688772},"30":{"tf":2.0},"32":{"tf":1.4142135623730951},"33":{"tf":1.0},"34":{"tf":1.7320508075688772},"35":{"tf":2.0},"37":{"tf":1.7320508075688772},"38":{"tf":1.0},"39":{"tf":2.0},"40":{"tf":2.0},"41":{"tf":1.0},"49":{"tf":1.4142135623730951},"52":{"tf":2.0},"53":{"tf":1.0},"54":{"tf":1.7320508075688772},"55":{"tf":1.4142135623730951},"56":{"tf":1.4142135623730951},"64":{"tf":1.0},"68":{"tf":1.7320508075688772},"69":{"tf":1.0},"7":{"tf":1.0},"73":{"tf":1.0},"74":{"tf":1.0},"77":{"tf":1.0},"78":{"tf":1.0},"8":{"tf":1.0},"81":{"tf":1.0},"82":{"tf":1.0},"84":{"tf":1.0},"9":{"tf":2.0}}},"df":11,"docs":{"128":{"tf":1.0},"140":{"tf":1.0},"179":{"tf":1.0},"22":{"tf":2.0},"225":{"tf":1.0},"249":{"tf":1.0},"262":{"tf":1.0},"274":{"tf":1.0},"64":{"tf":1.0},"7":{"tf":1.0},"98":{"tf":1.0}}},"y":{"_":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":3,"docs":{"247":{"tf":1.7320508075688772},"249":{"tf":1.4142135623730951},"88":{"tf":1.7320508075688772}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"o":{"a":{"c":{"df":0,"docs":{},"h":{"df":13,"docs":{"101":{"tf":1.0},"115":{"tf":1.0},"140":{"tf":1.4142135623730951},"141":{"tf":1.4142135623730951},"143":{"tf":1.0},"145":{"tf":1.0},"146":{"tf":1.7320508075688772},"171":{"tf":1.0},"205":{"tf":1.0},"220":{"tf":1.0},"241":{"tf":1.0},"251":{"tf":1.0},"60":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":4,"docs":{"189":{"tf":1.0},"280":{"tf":1.0},"45":{"tf":1.0},"6":{"tf":1.0}}}}},"v":{"df":5,"docs":{"118":{"tf":1.4142135623730951},"119":{"tf":1.4142135623730951},"189":{"tf":1.0},"270":{"tf":1.0},"6":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":5,"docs":{"169":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"222":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"d":{"df":0,"docs":{},"l":{"df":1,"docs":{"22":{"tf":1.7320508075688772}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"r":{"c":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"}":{"/":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"22":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"v":{"c":{"df":2,"docs":{"276":{"tf":1.7320508075688772},"277":{"tf":1.0}}},"df":0,"docs":{}}}},"t":{"df":2,"docs":{"11":{"tf":3.4641016151377544},"13":{"tf":1.0}}}},"r":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":94,"docs":{"102":{"tf":1.0},"134":{"tf":2.449489742783178},"135":{"tf":1.7320508075688772},"136":{"tf":1.4142135623730951},"137":{"tf":1.7320508075688772},"138":{"tf":1.0},"139":{"tf":1.0},"140":{"tf":1.0},"141":{"tf":1.0},"142":{"tf":1.0},"143":{"tf":1.0},"144":{"tf":1.0},"145":{"tf":1.0},"146":{"tf":1.0},"147":{"tf":1.0},"148":{"tf":1.0},"149":{"tf":1.0},"150":{"tf":1.0},"151":{"tf":1.0},"152":{"tf":1.0},"153":{"tf":1.0},"154":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0},"157":{"tf":1.0},"158":{"tf":1.0},"159":{"tf":1.0},"160":{"tf":1.0},"161":{"tf":1.0},"162":{"tf":1.0},"163":{"tf":1.0},"164":{"tf":1.0},"165":{"tf":1.0},"166":{"tf":1.0},"167":{"tf":1.0},"168":{"tf":1.0},"169":{"tf":1.0},"17":{"tf":1.4142135623730951},"170":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"173":{"tf":1.0},"174":{"tf":1.0},"175":{"tf":1.0},"176":{"tf":1.0},"177":{"tf":1.0},"178":{"tf":1.0},"179":{"tf":1.0},"180":{"tf":1.0},"181":{"tf":1.0},"182":{"tf":1.0},"183":{"tf":1.0},"184":{"tf":1.0},"185":{"tf":1.0},"186":{"tf":1.0},"187":{"tf":1.0},"188":{"tf":1.0},"189":{"tf":1.0},"190":{"tf":1.0},"191":{"tf":1.0},"192":{"tf":1.0},"193":{"tf":1.0},"194":{"tf":1.0},"195":{"tf":1.0},"196":{"tf":1.0},"197":{"tf":1.0},"198":{"tf":1.0},"199":{"tf":1.0},"200":{"tf":1.0},"201":{"tf":1.0},"202":{"tf":1.0},"203":{"tf":1.0},"204":{"tf":1.0},"205":{"tf":1.0},"206":{"tf":1.0},"207":{"tf":1.0},"208":{"tf":1.0},"209":{"tf":1.0},"210":{"tf":1.0},"211":{"tf":1.0},"212":{"tf":1.0},"213":{"tf":1.0},"214":{"tf":1.0},"215":{"tf":1.0},"216":{"tf":1.0},"217":{"tf":1.0},"218":{"tf":1.0},"223":{"tf":1.0},"225":{"tf":1.4142135623730951},"227":{"tf":1.0},"244":{"tf":1.4142135623730951},"252":{"tf":2.23606797749979},"282":{"tf":1.7320508075688772},"58":{"tf":1.0}}}}}},"df":0,"docs":{}}},"v":{"df":4,"docs":{"220":{"tf":1.0},"223":{"tf":1.0},"271":{"tf":1.0},"279":{"tf":1.0}},"e":{".":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{".":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":1,"docs":{"273":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"e":{"a":{"df":2,"docs":{"273":{"tf":1.7320508075688772},"63":{"tf":1.0}}},"df":0,"docs":{},"n":{"'":{"df":0,"docs":{},"t":{"df":9,"docs":{"166":{"tf":1.0},"225":{"tf":1.0},"226":{"tf":1.0},"260":{"tf":1.0},"268":{"tf":1.0},"58":{"tf":1.0},"60":{"tf":1.0},"79":{"tf":1.0},"84":{"tf":1.0}}}},"df":0,"docs":{}}},"g":{"df":1,"docs":{"58":{"tf":1.0}}},"m":{"6":{"4":{"df":1,"docs":{"175":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{},"v":{"7":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":12,"docs":{"115":{"tf":1.0},"116":{"tf":1.0},"150":{"tf":1.0},"169":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"204":{"tf":1.4142135623730951},"233":{"tf":1.0},"242":{"tf":1.4142135623730951},"261":{"tf":1.0},"51":{"tf":1.0},"79":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}},"t":{"df":2,"docs":{"206":{"tf":1.0},"214":{"tf":1.0}},"i":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"163":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"f":{"a":{"c":{"df":0,"docs":{},"t":{"df":20,"docs":{"122":{"tf":1.0},"14":{"tf":1.0},"163":{"tf":1.0},"167":{"tf":1.0},"169":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.4142135623730951},"172":{"tf":1.4142135623730951},"175":{"tf":1.0},"220":{"tf":1.0},"267":{"tf":1.4142135623730951},"271":{"tf":1.0},"272":{"tf":1.0},"273":{"tf":1.7320508075688772},"274":{"tf":2.23606797749979},"279":{"tf":1.4142135623730951},"280":{"tf":2.449489742783178},"37":{"tf":1.4142135623730951},"40":{"tf":1.0},"64":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"s":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"242":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"51":{"tf":1.0}}}},"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"60":{"tf":1.0}}},"df":0,"docs":{}},"k":{"df":5,"docs":{"122":{"tf":2.0},"161":{"tf":1.0},"233":{"tf":1.0},"242":{"tf":1.0},"274":{"tf":1.0}}},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"104":{"tf":1.0}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"r":{"df":1,"docs":{"258":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":1,"docs":{"18":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":1,"docs":{"62":{"tf":1.0}}}}},"df":1,"docs":{"43":{"tf":1.4142135623730951}}}}},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"254":{"tf":1.0}}}}},"o":{"c":{"df":0,"docs":{},"i":{"df":2,"docs":{"115":{"tf":1.0},"283":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"m":{"df":17,"docs":{"114":{"tf":1.0},"12":{"tf":1.0},"145":{"tf":1.0},"151":{"tf":1.0},"154":{"tf":1.4142135623730951},"20":{"tf":1.0},"21":{"tf":1.0},"210":{"tf":1.0},"225":{"tf":1.0},"23":{"tf":1.0},"25":{"tf":1.0},"29":{"tf":1.0},"30":{"tf":1.0},"32":{"tf":1.0},"34":{"tf":1.0},"35":{"tf":1.0},"86":{"tf":1.0}},"p":{"df":0,"docs":{},"t":{"df":2,"docs":{"138":{"tf":1.0},"210":{"tf":1.0}}}}},"r":{"df":2,"docs":{"274":{"tf":1.0},"64":{"tf":1.0}}}}},"y":{"df":0,"docs":{},"n":{"c":{"df":4,"docs":{"115":{"tf":1.0},"193":{"tf":1.0},"194":{"tf":1.0},"197":{"tf":1.0}}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"t":{"a":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"190":{"tf":1.0}}},"k":{"df":1,"docs":{"201":{"tf":1.7320508075688772}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":13,"docs":{"132":{"tf":1.0},"146":{"tf":1.0},"167":{"tf":1.0},"19":{"tf":1.0},"200":{"tf":1.0},"203":{"tf":1.0},"204":{"tf":1.0},"207":{"tf":1.0},"226":{"tf":1.0},"245":{"tf":1.0},"25":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0}}}}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"170":{"tf":1.0},"197":{"tf":1.0},"198":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"193":{"tf":1.0},"194":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"u":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"274":{"tf":1.0},"64":{"tf":1.0}}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"226":{"tf":1.0}}}}}}},"t":{"df":0,"docs":{},"h":{"df":4,"docs":{"226":{"tf":1.0},"236":{"tf":1.0},"237":{"tf":1.0},"239":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":5,"docs":{"226":{"tf":1.0},"245":{"tf":1.0},"254":{"tf":1.0},"274":{"tf":1.0},"275":{"tf":1.4142135623730951}},"i":{"c":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"242":{"tf":1.0}}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"283":{"tf":1.7320508075688772}}}}},"o":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"61":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":8,"docs":{"104":{"tf":1.0},"116":{"tf":1.0},"12":{"tf":1.0},"16":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":1.4142135623730951},"20":{"tf":1.7320508075688772},"22":{"tf":1.7320508075688772}},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":5,"docs":{"140":{"tf":1.0},"145":{"tf":1.0},"244":{"tf":1.0},"257":{"tf":1.0},"264":{"tf":1.7320508075688772}},"l":{"/":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"245":{"tf":1.0}}}}}}}},":":{":":{"d":{"b":{":":{":":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{":":{":":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"245":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"d":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"_":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"245":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":5,"docs":{"103":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0}}}}}}},"m":{"a":{"df":0,"docs":{},"t":{"df":12,"docs":{"107":{"tf":1.0},"113":{"tf":1.0},"167":{"tf":1.0},"18":{"tf":1.4142135623730951},"19":{"tf":1.0},"266":{"tf":1.0},"61":{"tf":1.0},"70":{"tf":1.0},"72":{"tf":1.0},"73":{"tf":1.0},"74":{"tf":1.0},"90":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"269":{"tf":1.0}}}},"df":0,"docs":{}}}},"u":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"l":{"_":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"_":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"d":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"19":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":2,"docs":{"269":{"tf":1.0},"53":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}}}}}}}},"u":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"109":{"tf":1.0}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"s":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":3,"docs":{"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0}}}}}},"t":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"269":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"7":{"tf":1.0}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}},"df":10,"docs":{"101":{"tf":1.0},"103":{"tf":1.0},"111":{"tf":1.0},"167":{"tf":1.0},"22":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951},"27":{"tf":1.0},"274":{"tf":2.23606797749979},"49":{"tf":1.4142135623730951},"7":{"tf":1.0}}},"p":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{".":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":2,"docs":{"18":{"tf":1.4142135623730951},"22":{"tf":1.7320508075688772}}}}}},"df":0,"docs":{}},"df":17,"docs":{"118":{"tf":1.0},"16":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.0},"21":{"tf":1.0},"22":{"tf":1.0},"23":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"26":{"tf":1.0},"27":{"tf":1.0},"28":{"tf":1.0},"29":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.0}},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"l":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":1,"docs":{"22":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"61":{"tf":1.0}},"s":{"/":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"v":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":24,"docs":{"1":{"tf":1.0},"104":{"tf":1.0},"107":{"tf":1.0},"11":{"tf":1.0},"114":{"tf":1.0},"134":{"tf":1.4142135623730951},"138":{"tf":1.0},"142":{"tf":1.0},"146":{"tf":1.0},"165":{"tf":1.0},"177":{"tf":1.4142135623730951},"178":{"tf":1.0},"179":{"tf":1.0},"187":{"tf":1.0},"192":{"tf":1.0},"198":{"tf":1.0},"228":{"tf":1.0},"252":{"tf":1.0},"260":{"tf":1.0},"274":{"tf":1.7320508075688772},"282":{"tf":1.0},"45":{"tf":1.0},"5":{"tf":1.0},"62":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"204":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"i":{"d":{"df":23,"docs":{"116":{"tf":1.0},"126":{"tf":1.0},"128":{"tf":1.0},"132":{"tf":1.0},"141":{"tf":1.0},"150":{"tf":1.0},"165":{"tf":1.0},"172":{"tf":1.0},"197":{"tf":1.4142135623730951},"22":{"tf":1.0},"220":{"tf":1.0},"222":{"tf":1.0},"225":{"tf":1.0},"227":{"tf":1.0},"229":{"tf":1.4142135623730951},"263":{"tf":1.4142135623730951},"264":{"tf":1.0},"46":{"tf":1.0},"57":{"tf":1.4142135623730951},"59":{"tf":1.0},"60":{"tf":1.4142135623730951},"69":{"tf":1.0},"99":{"tf":1.0}}},"df":0,"docs":{}}}},"w":{"a":{"df":0,"docs":{},"r":{"df":3,"docs":{"133":{"tf":1.0},"189":{"tf":1.0},"233":{"tf":1.0}}},"y":{"df":3,"docs":{"117":{"tf":1.0},"170":{"tf":1.0},"212":{"tf":1.0}}}},"df":0,"docs":{}}},"b":{"/":{"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{".":{"df":0,"docs":{},"m":{"d":{"df":1,"docs":{"268":{"tf":2.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"a":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"/":{"c":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":1,"docs":{"62":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{},"s":{"df":0,"docs":{},"r":{"c":{"/":{"d":{"b":{"/":{"d":{"b":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"62":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"62":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":2,"docs":{"268":{"tf":2.0},"269":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}}}}}}}}},"1":{"df":2,"docs":{"152":{"tf":1.0},"157":{"tf":1.4142135623730951}}},"2":{"df":1,"docs":{"158":{"tf":1.4142135623730951}}},"3":{"df":1,"docs":{"159":{"tf":1.4142135623730951}}},"a":{"c":{"df":0,"docs":{},"k":{"df":10,"docs":{"103":{"tf":1.4142135623730951},"107":{"tf":1.0},"115":{"tf":1.7320508075688772},"158":{"tf":1.0},"214":{"tf":1.0},"242":{"tf":1.0},"246":{"tf":1.0},"249":{"tf":2.0},"274":{"tf":1.0},"283":{"tf":1.0}},"e":{"d":{"df":0,"docs":{},"o":{"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":1,"docs":{"242":{"tf":1.0}}}}}},"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"51":{"tf":1.0}}},"df":0,"docs":{}}},"g":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":3,"docs":{"115":{"tf":1.4142135623730951},"203":{"tf":1.0},"58":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}}},"o":{"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":6,"docs":{"225":{"tf":1.0},"226":{"tf":1.4142135623730951},"236":{"tf":1.0},"239":{"tf":1.0},"242":{"tf":1.0},"245":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"p":{"df":1,"docs":{"158":{"tf":1.7320508075688772}}}},"w":{"a":{"df":0,"docs":{},"r":{"d":{"df":2,"docs":{"116":{"tf":1.0},"268":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"d":{"df":19,"docs":{"115":{"tf":1.0},"150":{"tf":1.0},"154":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.4142135623730951},"157":{"tf":1.0},"158":{"tf":1.7320508075688772},"159":{"tf":1.4142135623730951},"169":{"tf":1.7320508075688772},"170":{"tf":2.0},"171":{"tf":1.7320508075688772},"172":{"tf":1.0},"182":{"tf":1.0},"183":{"tf":2.0},"184":{"tf":2.0},"212":{"tf":2.0},"213":{"tf":1.4142135623730951},"226":{"tf":1.0},"81":{"tf":1.0}}},"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"64":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"n":{"d":{"df":1,"docs":{"283":{"tf":1.0}}},"df":0,"docs":{}},"r":{"df":1,"docs":{"205":{"tf":1.7320508075688772}}},"s":{"df":0,"docs":{},"e":{"df":18,"docs":{"163":{"tf":1.0},"164":{"tf":1.0},"172":{"tf":1.4142135623730951},"197":{"tf":1.0},"207":{"tf":1.4142135623730951},"208":{"tf":1.0},"210":{"tf":1.0},"213":{"tf":1.4142135623730951},"214":{"tf":1.4142135623730951},"215":{"tf":1.4142135623730951},"217":{"tf":1.0},"226":{"tf":1.0},"233":{"tf":1.0},"253":{"tf":1.0},"283":{"tf":1.0},"37":{"tf":1.0},"7":{"tf":1.0},"82":{"tf":1.4142135623730951}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"175":{"tf":1.0}}}}}},"h":{"/":{"df":0,"docs":{},"z":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"11":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{},"r":{"c":{"df":2,"docs":{"11":{"tf":1.0},"12":{"tf":1.0}}},"df":0,"docs":{}}},"i":{"c":{"df":2,"docs":{"274":{"tf":1.0},"37":{"tf":1.0}}},"df":2,"docs":{"134":{"tf":1.0},"59":{"tf":1.0}}}}},"df":8,"docs":{"149":{"tf":1.0},"151":{"tf":1.0},"152":{"tf":1.0},"163":{"tf":1.0},"170":{"tf":1.4142135623730951},"179":{"tf":1.0},"180":{"tf":1.0},"280":{"tf":1.0}},"e":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":5,"docs":{"226":{"tf":1.0},"263":{"tf":1.0},"274":{"tf":1.0},"58":{"tf":1.0},"64":{"tf":1.0}}}}},"df":19,"docs":{"115":{"tf":1.0},"152":{"tf":1.0},"159":{"tf":1.0},"162":{"tf":1.0},"167":{"tf":1.0},"184":{"tf":1.0},"187":{"tf":1.0},"192":{"tf":1.0},"193":{"tf":1.0},"197":{"tf":1.0},"227":{"tf":1.0},"231":{"tf":1.0},"233":{"tf":1.0},"242":{"tf":1.4142135623730951},"250":{"tf":1.4142135623730951},"274":{"tf":1.4142135623730951},"60":{"tf":1.0},"78":{"tf":1.0},"84":{"tf":1.0}},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":19,"docs":{"11":{"tf":1.4142135623730951},"118":{"tf":1.0},"119":{"tf":1.0},"12":{"tf":1.0},"120":{"tf":1.0},"122":{"tf":1.0},"145":{"tf":1.0},"177":{"tf":1.0},"20":{"tf":1.4142135623730951},"204":{"tf":1.0},"213":{"tf":1.0},"245":{"tf":1.4142135623730951},"256":{"tf":1.7320508075688772},"259":{"tf":1.0},"266":{"tf":1.0},"58":{"tf":1.4142135623730951},"6":{"tf":1.0},"62":{"tf":1.4142135623730951},"7":{"tf":1.7320508075688772}},"e":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"26":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"o":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":1,"docs":{"283":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"p":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":1,"docs":{"283":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":2,"docs":{"279":{"tf":1.0},"59":{"tf":1.0}}}}},"h":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":2,"docs":{"245":{"tf":1.0},"283":{"tf":1.0}}}},"v":{"df":2,"docs":{"60":{"tf":1.0},"61":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":4,"docs":{"140":{"tf":1.0},"156":{"tf":1.0},"61":{"tf":1.7320508075688772},"84":{"tf":1.0}}},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"37":{"tf":1.0}}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"25":{"tf":1.0},"62":{"tf":1.0}}},"df":0,"docs":{}}}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":3,"docs":{"194":{"tf":1.0},"233":{"tf":1.4142135623730951},"79":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"w":{"df":18,"docs":{"105":{"tf":1.0},"11":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":1.0},"145":{"tf":1.0},"200":{"tf":1.0},"203":{"tf":1.0},"226":{"tf":1.0},"241":{"tf":1.0},"242":{"tf":1.0},"25":{"tf":1.0},"259":{"tf":1.0},"264":{"tf":1.0},"274":{"tf":1.0},"279":{"tf":1.0},"32":{"tf":1.0},"59":{"tf":1.0},"6":{"tf":1.0}}}}},"n":{"c":{"df":0,"docs":{},"h":{"0":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"60":{"tf":1.0}}}},"df":1,"docs":{"60":{"tf":1.0}}},"1":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"60":{"tf":1.0}}}},"df":1,"docs":{"60":{"tf":1.0}}},"2":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"60":{"tf":1.0}}}},"df":1,"docs":{"60":{"tf":1.0}}},"df":2,"docs":{"60":{"tf":2.23606797749979},"61":{"tf":1.0}},"e":{"df":0,"docs":{},"s":{"/":{"*":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"61":{"tf":1.0}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"m":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":3,"docs":{"58":{"tf":1.4142135623730951},"59":{"tf":1.7320508075688772},"60":{"tf":3.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":8,"docs":{"140":{"tf":1.0},"170":{"tf":1.0},"198":{"tf":1.0},"201":{"tf":1.0},"221":{"tf":1.0},"274":{"tf":1.0},"60":{"tf":1.0},"64":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"t":{"df":8,"docs":{"101":{"tf":1.0},"104":{"tf":1.0},"123":{"tf":1.4142135623730951},"149":{"tf":1.0},"164":{"tf":1.0},"170":{"tf":1.0},"43":{"tf":1.4142135623730951},"75":{"tf":1.0}}}},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":11,"docs":{"115":{"tf":1.4142135623730951},"124":{"tf":1.0},"165":{"tf":1.0},"167":{"tf":1.0},"170":{"tf":1.0},"196":{"tf":1.0},"197":{"tf":1.0},"200":{"tf":1.0},"43":{"tf":1.0},"62":{"tf":1.0},"79":{"tf":1.0}}}}},"w":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":23,"docs":{"104":{"tf":1.0},"106":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"150":{"tf":1.0},"165":{"tf":1.4142135623730951},"169":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"20":{"tf":1.0},"204":{"tf":1.0},"214":{"tf":1.0},"216":{"tf":1.7320508075688772},"220":{"tf":1.0},"224":{"tf":1.0},"225":{"tf":1.0},"229":{"tf":1.0},"233":{"tf":1.0},"250":{"tf":1.0},"79":{"tf":1.0},"84":{"tf":1.0},"89":{"tf":1.4142135623730951}}}}}}},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"d":{"df":4,"docs":{"111":{"tf":1.0},"204":{"tf":1.0},"208":{"tf":1.0},"216":{"tf":1.0}}},"df":0,"docs":{}}}}},"i":{"df":0,"docs":{},"g":{"df":3,"docs":{"236":{"tf":1.0},"64":{"tf":1.0},"81":{"tf":1.0}},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"210":{"tf":1.0}}}}}}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"227":{"tf":1.0}}}}}},"n":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":10,"docs":{"14":{"tf":1.0},"163":{"tf":1.4142135623730951},"167":{"tf":1.0},"169":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.4142135623730951},"172":{"tf":1.4142135623730951},"175":{"tf":1.4142135623730951},"253":{"tf":1.0},"274":{"tf":1.0}}}}},"d":{"df":24,"docs":{"0":{"tf":1.0},"101":{"tf":1.4142135623730951},"103":{"tf":1.4142135623730951},"105":{"tf":1.0},"106":{"tf":1.0},"107":{"tf":1.0},"108":{"tf":2.23606797749979},"109":{"tf":2.449489742783178},"111":{"tf":1.4142135623730951},"116":{"tf":1.0},"140":{"tf":1.0},"162":{"tf":1.0},"253":{"tf":1.0},"281":{"tf":1.0},"45":{"tf":1.7320508075688772},"46":{"tf":1.7320508075688772},"69":{"tf":1.0},"7":{"tf":1.0},"70":{"tf":2.0},"71":{"tf":1.4142135623730951},"72":{"tf":1.4142135623730951},"73":{"tf":1.4142135623730951},"74":{"tf":1.0},"90":{"tf":1.4142135623730951}},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":2,"docs":{"104":{"tf":1.7320508075688772},"69":{"tf":1.4142135623730951}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"k":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"70":{"tf":1.0}}}}}}}},"s":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":1,"docs":{"73":{"tf":1.0}}}}}}}},"_":{"df":0,"docs":{},"u":{"d":{"df":0,"docs":{},"l":{"_":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"74":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":1,"docs":{"11":{"tf":1.0}}},"t":{"df":8,"docs":{"104":{"tf":1.4142135623730951},"116":{"tf":1.7320508075688772},"17":{"tf":1.0},"174":{"tf":1.0},"20":{"tf":1.0},"226":{"tf":1.0},"229":{"tf":1.0},"6":{"tf":1.0}}}},"l":{"a":{"df":0,"docs":{},"h":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"61":{"tf":1.0}}}},"df":1,"docs":{"61":{"tf":1.0}}}},"df":0,"docs":{},"o":{"b":{"df":1,"docs":{"242":{"tf":1.0}}},"c":{"df":0,"docs":{},"k":{"df":3,"docs":{"115":{"tf":1.0},"194":{"tf":1.0},"81":{"tf":1.0}},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{".":{"df":0,"docs":{},"x":{"c":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"p":{"b":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"df":1,"docs":{"36":{"tf":1.0}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":1,"docs":{"36":{"tf":1.0}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":1,"docs":{"36":{"tf":1.0}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"g":{"df":1,"docs":{"90":{"tf":1.4142135623730951}}}}},"o":{"d":{"df":0,"docs":{},"i":{"df":1,"docs":{"189":{"tf":1.0}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"115":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":2,"docs":{"45":{"tf":1.0},"46":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"k":{"df":2,"docs":{"286":{"tf":1.4142135623730951},"43":{"tf":1.0}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":1,"docs":{"82":{"tf":1.0}}}}}},"m":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":10,"docs":{"226":{"tf":1.4142135623730951},"228":{"tf":1.0},"231":{"tf":1.0},"232":{"tf":1.0},"242":{"tf":1.4142135623730951},"257":{"tf":1.0},"264":{"tf":1.0},"62":{"tf":1.0},"83":{"tf":1.0},"86":{"tf":1.0}},"g":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"62":{"tf":2.0}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"l":{"df":2,"docs":{"116":{"tf":1.4142135623730951},"242":{"tf":1.0}}},"t":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":1,"docs":{"274":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"t":{"df":0,"docs":{},"h":{"df":21,"docs":{"117":{"tf":1.0},"123":{"tf":1.0},"145":{"tf":2.0},"146":{"tf":1.0},"17":{"tf":1.0},"175":{"tf":1.0},"18":{"tf":1.0},"196":{"tf":1.0},"198":{"tf":1.4142135623730951},"200":{"tf":1.0},"221":{"tf":1.0},"222":{"tf":1.0},"226":{"tf":1.0},"241":{"tf":1.4142135623730951},"249":{"tf":1.4142135623730951},"271":{"tf":1.0},"274":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"7":{"tf":1.0},"86":{"tf":1.4142135623730951}}},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":2,"docs":{"126":{"tf":1.0},"60":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"n":{"d":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":4,"docs":{"229":{"tf":1.0},"43":{"tf":1.0},"51":{"tf":1.0},"79":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"x":{"df":2,"docs":{"126":{"tf":1.0},"128":{"tf":1.0}}}},"r":{"a":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":15,"docs":{"118":{"tf":1.0},"25":{"tf":1.0},"26":{"tf":2.0},"267":{"tf":1.4142135623730951},"268":{"tf":2.0},"269":{"tf":1.4142135623730951},"270":{"tf":1.4142135623730951},"274":{"tf":2.23606797749979},"31":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"38":{"tf":2.23606797749979},"39":{"tf":3.7416573867739413},"40":{"tf":2.6457513110645907},"64":{"tf":1.0},"7":{"tf":1.4142135623730951}}}},"d":{"df":2,"docs":{"105":{"tf":1.0},"64":{"tf":1.0}}},"df":0,"docs":{}},"v":{"df":0,"docs":{},"e":{"df":1,"docs":{"81":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"k":{"a":{"df":0,"docs":{},"g":{"df":4,"docs":{"118":{"tf":1.0},"121":{"tf":1.0},"166":{"tf":1.0},"278":{"tf":1.0}}}},"df":13,"docs":{"104":{"tf":1.0},"105":{"tf":1.0},"116":{"tf":1.0},"118":{"tf":2.23606797749979},"119":{"tf":1.0},"140":{"tf":1.0},"141":{"tf":1.0},"166":{"tf":1.0},"21":{"tf":1.0},"235":{"tf":1.0},"39":{"tf":1.7320508075688772},"40":{"tf":1.7320508075688772},"7":{"tf":1.0}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"117":{"tf":2.449489742783178}}}}}}}}},"df":0,"docs":{},"w":{"df":2,"docs":{"11":{"tf":1.0},"37":{"tf":1.0}}}},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"252":{"tf":1.0}}}}}},"n":{"df":0,"docs":{},"g":{"df":3,"docs":{"117":{"tf":1.0},"59":{"tf":1.0},"64":{"tf":1.0}}}}},"o":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"161":{"tf":1.0}}}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"171":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"266":{"tf":1.0}}}}},"w":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":8,"docs":{"134":{"tf":1.0},"186":{"tf":1.4142135623730951},"187":{"tf":1.4142135623730951},"188":{"tf":1.0},"190":{"tf":1.0},"192":{"tf":1.0},"194":{"tf":1.0},"197":{"tf":1.7320508075688772}}}}}}}},"u":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":3,"docs":{"189":{"tf":1.0},"214":{"tf":1.0},"216":{"tf":1.7320508075688772}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"105":{"tf":1.0},"106":{"tf":1.4142135623730951}}}}}},"g":{"df":53,"docs":{"1":{"tf":1.0},"100":{"tf":1.0},"106":{"tf":1.0},"109":{"tf":1.0},"116":{"tf":1.7320508075688772},"117":{"tf":1.0},"119":{"tf":1.0},"122":{"tf":1.0},"123":{"tf":1.0},"124":{"tf":1.4142135623730951},"127":{"tf":1.0},"133":{"tf":1.0},"138":{"tf":1.0},"147":{"tf":1.0},"15":{"tf":1.0},"159":{"tf":1.0},"171":{"tf":1.0},"174":{"tf":1.0},"175":{"tf":1.0},"185":{"tf":1.0},"201":{"tf":1.0},"218":{"tf":1.0},"22":{"tf":1.0},"224":{"tf":1.0},"228":{"tf":1.0},"242":{"tf":1.0},"250":{"tf":1.0},"253":{"tf":1.0},"258":{"tf":1.0},"259":{"tf":1.0},"261":{"tf":1.0},"263":{"tf":1.0},"264":{"tf":1.0},"273":{"tf":1.0},"277":{"tf":1.0},"282":{"tf":1.0},"283":{"tf":1.0},"286":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.4142135623730951},"4":{"tf":1.4142135623730951},"40":{"tf":1.0},"43":{"tf":1.0},"51":{"tf":1.0},"52":{"tf":1.0},"56":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0},"67":{"tf":1.0},"74":{"tf":1.0},"8":{"tf":1.0},"90":{"tf":1.0}},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"df":2,"docs":{"1":{"tf":1.0},"261":{"tf":1.0}}},"df":0,"docs":{}}}}}},"i":{"df":0,"docs":{},"l":{"d":{".":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"d":{"df":0,"docs":{},"l":{"df":9,"docs":{"103":{"tf":1.4142135623730951},"108":{"tf":1.0},"117":{"tf":1.0},"20":{"tf":1.0},"22":{"tf":1.0},"224":{"tf":1.4142135623730951},"37":{"tf":1.0},"45":{"tf":1.0},"70":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"r":{"df":1,"docs":{"105":{"tf":1.0}}}},"/":{"d":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"s":{"/":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":1,"docs":{"286":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"_":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"175":{"tf":1.0}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"105":{"tf":1.0}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":2,"docs":{"113":{"tf":1.0},"70":{"tf":1.0}}}}}}}},"df":97,"docs":{"10":{"tf":2.449489742783178},"101":{"tf":1.4142135623730951},"105":{"tf":1.0},"108":{"tf":1.7320508075688772},"109":{"tf":2.0},"11":{"tf":4.123105625617661},"114":{"tf":1.4142135623730951},"118":{"tf":1.0},"119":{"tf":1.4142135623730951},"12":{"tf":2.0},"122":{"tf":2.6457513110645907},"124":{"tf":1.0},"13":{"tf":1.4142135623730951},"14":{"tf":2.23606797749979},"140":{"tf":1.0},"15":{"tf":2.0},"16":{"tf":1.0},"161":{"tf":1.4142135623730951},"163":{"tf":2.23606797749979},"165":{"tf":1.4142135623730951},"166":{"tf":1.0},"167":{"tf":2.0},"169":{"tf":1.7320508075688772},"17":{"tf":1.4142135623730951},"170":{"tf":2.449489742783178},"171":{"tf":1.0},"175":{"tf":2.23606797749979},"18":{"tf":2.23606797749979},"19":{"tf":1.4142135623730951},"197":{"tf":1.0},"20":{"tf":1.4142135623730951},"21":{"tf":1.4142135623730951},"22":{"tf":2.8284271247461903},"221":{"tf":1.0},"224":{"tf":1.7320508075688772},"23":{"tf":2.0},"233":{"tf":1.0},"237":{"tf":1.0},"24":{"tf":1.7320508075688772},"25":{"tf":1.7320508075688772},"252":{"tf":2.0},"253":{"tf":1.0},"26":{"tf":1.0},"262":{"tf":1.0},"263":{"tf":1.0},"266":{"tf":2.0},"267":{"tf":2.0},"27":{"tf":1.0},"271":{"tf":2.23606797749979},"272":{"tf":1.7320508075688772},"274":{"tf":3.872983346207417},"278":{"tf":1.4142135623730951},"279":{"tf":2.449489742783178},"28":{"tf":2.449489742783178},"280":{"tf":1.4142135623730951},"282":{"tf":2.449489742783178},"285":{"tf":1.4142135623730951},"286":{"tf":1.7320508075688772},"29":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.4142135623730951},"32":{"tf":1.7320508075688772},"33":{"tf":2.449489742783178},"34":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.7320508075688772},"37":{"tf":2.8284271247461903},"38":{"tf":2.449489742783178},"39":{"tf":3.0},"40":{"tf":3.3166247903554},"49":{"tf":1.4142135623730951},"5":{"tf":2.0},"58":{"tf":1.7320508075688772},"59":{"tf":1.7320508075688772},"61":{"tf":1.0},"64":{"tf":1.7320508075688772},"67":{"tf":1.0},"69":{"tf":1.7320508075688772},"70":{"tf":1.7320508075688772},"73":{"tf":2.23606797749979},"75":{"tf":2.449489742783178},"76":{"tf":1.0},"77":{"tf":2.0},"78":{"tf":1.0},"79":{"tf":1.0},"80":{"tf":1.0},"81":{"tf":1.4142135623730951},"82":{"tf":1.0},"83":{"tf":1.0},"84":{"tf":1.0},"85":{"tf":1.0},"86":{"tf":1.0},"87":{"tf":1.0},"88":{"tf":1.0},"89":{"tf":1.0},"9":{"tf":2.0},"90":{"tf":1.7320508075688772}},"s":{"df":0,"docs":{},"r":{"c":{"/":{"df":0,"docs":{},"s":{"df":0,"docs":{},"r":{"c":{"/":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"/":{"df":0,"docs":{},"j":{"a":{"df":0,"docs":{},"v":{"a":{"/":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"k":{"df":0,"docs":{},"t":{"df":1,"docs":{"20":{"tf":1.0}}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{},"t":{"df":24,"docs":{"0":{"tf":1.0},"11":{"tf":1.0},"119":{"tf":1.0},"13":{"tf":1.0},"14":{"tf":1.0},"167":{"tf":2.0},"174":{"tf":1.0},"175":{"tf":1.4142135623730951},"222":{"tf":1.4142135623730951},"227":{"tf":1.0},"251":{"tf":1.4142135623730951},"271":{"tf":1.4142135623730951},"274":{"tf":1.4142135623730951},"28":{"tf":1.0},"280":{"tf":1.0},"282":{"tf":1.0},"286":{"tf":1.4142135623730951},"33":{"tf":1.0},"37":{"tf":1.7320508075688772},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0}},"i":{"df":0,"docs":{},"n":{"df":3,"docs":{"43":{"tf":1.0},"69":{"tf":1.0},"92":{"tf":1.0}}}}}}},"m":{"df":0,"docs":{},"p":{"df":5,"docs":{"119":{"tf":1.7320508075688772},"263":{"tf":1.4142135623730951},"270":{"tf":1.0},"279":{"tf":1.4142135623730951},"82":{"tf":1.4142135623730951}}}},"n":{"c":{"df":0,"docs":{},"h":{"df":2,"docs":{"104":{"tf":1.0},"111":{"tf":1.0}}}},"d":{"df":0,"docs":{},"l":{"df":13,"docs":{"161":{"tf":1.0},"163":{"tf":1.0},"167":{"tf":2.0},"169":{"tf":1.0},"170":{"tf":1.7320508075688772},"171":{"tf":1.0},"172":{"tf":1.0},"179":{"tf":1.4142135623730951},"180":{"tf":1.0},"183":{"tf":1.4142135623730951},"185":{"tf":1.0},"252":{"tf":1.0},"73":{"tf":1.0}}}},"df":0,"docs":{}},"r":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"241":{"tf":1.0}}}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"i":{"df":1,"docs":{"256":{"tf":1.0}}},"t":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"282":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"117":{"tf":1.0}}}}}}}},"c":{"8":{"9":{"df":1,"docs":{"116":{"tf":1.0}}},"df":0,"docs":{}},"a":{"c":{"df":0,"docs":{},"h":{"df":7,"docs":{"11":{"tf":1.0},"189":{"tf":1.0},"194":{"tf":1.0},"201":{"tf":2.0},"25":{"tf":2.23606797749979},"26":{"tf":2.0},"59":{"tf":1.0}},"e":{"_":{"df":0,"docs":{},"s":{"df":1,"docs":{"264":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"l":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":3,"docs":{"246":{"tf":1.0},"249":{"tf":1.0},"7":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"268":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"l":{"b":{"a":{"c":{"df":0,"docs":{},"k":{"df":4,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"115":{"tf":2.449489742783178},"116":{"tf":1.4142135623730951}},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"115":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":20,"docs":{"101":{"tf":1.0},"111":{"tf":1.0},"115":{"tf":2.6457513110645907},"126":{"tf":1.4142135623730951},"13":{"tf":1.0},"131":{"tf":1.0},"145":{"tf":1.7320508075688772},"147":{"tf":1.0},"201":{"tf":1.0},"222":{"tf":1.0},"241":{"tf":1.4142135623730951},"242":{"tf":1.4142135623730951},"244":{"tf":1.0},"245":{"tf":1.7320508075688772},"283":{"tf":1.7320508075688772},"77":{"tf":1.4142135623730951},"78":{"tf":1.0},"81":{"tf":1.0},"84":{"tf":1.0},"90":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"82":{"tf":1.0}}}}}},"m":{"df":0,"docs":{},"e":{"df":2,"docs":{"256":{"tf":1.0},"60":{"tf":1.0}},"l":{"c":{"a":{"df":0,"docs":{},"s":{"df":1,"docs":{"108":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"n":{"'":{"df":0,"docs":{},"t":{"df":6,"docs":{"128":{"tf":1.0},"175":{"tf":1.0},"194":{"tf":1.0},"226":{"tf":1.0},"231":{"tf":1.4142135623730951},"82":{"tf":1.0}}}},"d":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"267":{"tf":1.7320508075688772},"84":{"tf":1.0}}},"df":0,"docs":{}}},"df":1,"docs":{"227":{"tf":1.0}},"o":{"df":0,"docs":{},"n":{"df":5,"docs":{"228":{"tf":1.0},"232":{"tf":1.0},"233":{"tf":1.0},"242":{"tf":1.0},"263":{"tf":1.0}}}}},"p":{"a":{"b":{"df":0,"docs":{},"l":{"df":13,"docs":{"124":{"tf":1.0},"190":{"tf":1.0},"192":{"tf":1.0},"193":{"tf":1.0},"194":{"tf":1.0},"196":{"tf":1.0},"197":{"tf":1.0},"201":{"tf":2.23606797749979},"22":{"tf":1.0},"228":{"tf":1.0},"262":{"tf":1.0},"283":{"tf":1.0},"62":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"104":{"tf":1.0}}}},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":2,"docs":{"101":{"tf":1.0},"242":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"e":{"df":7,"docs":{"132":{"tf":1.0},"228":{"tf":1.4142135623730951},"232":{"tf":1.0},"238":{"tf":1.0},"58":{"tf":1.4142135623730951},"81":{"tf":1.0},"82":{"tf":1.4142135623730951}},"f":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"227":{"tf":1.0}}}}}}}},"g":{"df":0,"docs":{},"o":{".":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"df":2,"docs":{"64":{"tf":1.7320508075688772},"69":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":14,"docs":{"103":{"tf":1.7320508075688772},"105":{"tf":1.0},"123":{"tf":1.4142135623730951},"124":{"tf":1.0},"200":{"tf":1.0},"251":{"tf":1.0},"28":{"tf":1.0},"33":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.4142135623730951},"69":{"tf":1.4142135623730951},"70":{"tf":1.0},"72":{"tf":1.0},"73":{"tf":1.0}}}}}}},"df":14,"docs":{"104":{"tf":1.0},"105":{"tf":1.0},"107":{"tf":1.0},"11":{"tf":1.7320508075688772},"122":{"tf":1.0},"274":{"tf":1.0},"279":{"tf":1.4142135623730951},"286":{"tf":1.0},"43":{"tf":1.0},"58":{"tf":3.1622776601683795},"59":{"tf":1.0},"60":{"tf":1.7320508075688772},"64":{"tf":1.4142135623730951},"69":{"tf":1.4142135623730951}}}},"t":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"g":{"df":6,"docs":{"161":{"tf":1.0},"163":{"tf":2.0},"166":{"tf":1.0},"167":{"tf":2.0},"170":{"tf":1.7320508075688772},"175":{"tf":1.0}},"e":{"'":{"df":1,"docs":{"170":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"s":{"df":0,"docs":{},"e":{"df":20,"docs":{"104":{"tf":1.0},"105":{"tf":1.0},"106":{"tf":1.7320508075688772},"115":{"tf":1.4142135623730951},"116":{"tf":1.0},"152":{"tf":1.0},"156":{"tf":1.0},"17":{"tf":1.0},"187":{"tf":1.0},"194":{"tf":1.4142135623730951},"201":{"tf":1.0},"228":{"tf":1.0},"233":{"tf":1.0},"245":{"tf":1.0},"274":{"tf":1.0},"276":{"tf":1.0},"70":{"tf":1.0},"79":{"tf":1.0},"98":{"tf":1.0},"99":{"tf":1.0}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"62":{"tf":1.0}}}}}}}},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":1,"docs":{"7":{"tf":1.0}}}}},"s":{"df":11,"docs":{"104":{"tf":1.0},"113":{"tf":1.0},"115":{"tf":1.0},"117":{"tf":1.0},"201":{"tf":1.0},"212":{"tf":1.4142135623730951},"225":{"tf":1.0},"260":{"tf":1.0},"50":{"tf":1.0},"59":{"tf":1.0},"70":{"tf":1.0}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"64":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"v":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":2,"docs":{"116":{"tf":1.0},"21":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"c":{"=":{"a":{"a":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"h":{"6":{"4":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"37":{"tf":1.0}},"p":{"df":1,"docs":{"37":{"tf":1.0}}}},"d":{"df":2,"docs":{"11":{"tf":1.4142135623730951},"25":{"tf":1.0}},"y":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"b":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"70":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":13,"docs":{"103":{"tf":2.6457513110645907},"111":{"tf":1.0},"114":{"tf":1.0},"163":{"tf":1.0},"171":{"tf":1.4142135623730951},"174":{"tf":1.0},"179":{"tf":1.0},"180":{"tf":1.0},"185":{"tf":1.4142135623730951},"251":{"tf":1.0},"252":{"tf":1.0},"28":{"tf":1.0},"33":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"l":{"df":10,"docs":{"120":{"tf":2.23606797749979},"121":{"tf":1.4142135623730951},"122":{"tf":2.23606797749979},"123":{"tf":2.23606797749979},"124":{"tf":2.23606797749979},"260":{"tf":1.0},"261":{"tf":1.7320508075688772},"262":{"tf":1.4142135623730951},"263":{"tf":1.0},"274":{"tf":1.7320508075688772}}}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":1,"docs":{"64":{"tf":1.0}}}}}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":5,"docs":{"115":{"tf":1.4142135623730951},"226":{"tf":1.0},"228":{"tf":1.0},"58":{"tf":1.0},"82":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":3,"docs":{"197":{"tf":1.0},"226":{"tf":1.4142135623730951},"229":{"tf":1.0}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":1,"docs":{"201":{"tf":1.0}}}}}}},"h":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":4,"docs":{"106":{"tf":1.0},"274":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0}}}},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":4,"docs":{"115":{"tf":1.4142135623730951},"228":{"tf":1.4142135623730951},"229":{"tf":1.0},"6":{"tf":1.0}}}}}}},"n":{"c":{"df":4,"docs":{"159":{"tf":1.0},"19":{"tf":1.0},"209":{"tf":1.4142135623730951},"214":{"tf":1.0}}},"df":0,"docs":{},"g":{"df":65,"docs":{"10":{"tf":1.0},"104":{"tf":1.0},"105":{"tf":1.4142135623730951},"108":{"tf":1.0},"118":{"tf":2.23606797749979},"119":{"tf":1.7320508075688772},"121":{"tf":1.0},"123":{"tf":1.4142135623730951},"124":{"tf":3.1622776601683795},"126":{"tf":1.4142135623730951},"145":{"tf":1.4142135623730951},"146":{"tf":1.0},"16":{"tf":1.0},"163":{"tf":1.0},"167":{"tf":1.0},"169":{"tf":1.7320508075688772},"170":{"tf":2.0},"171":{"tf":1.7320508075688772},"172":{"tf":1.4142135623730951},"175":{"tf":1.0},"180":{"tf":1.0},"184":{"tf":1.7320508075688772},"187":{"tf":1.0},"189":{"tf":1.0},"190":{"tf":1.7320508075688772},"193":{"tf":1.0},"197":{"tf":1.7320508075688772},"20":{"tf":1.4142135623730951},"200":{"tf":1.0},"201":{"tf":2.0},"21":{"tf":1.4142135623730951},"22":{"tf":1.0},"228":{"tf":1.0},"231":{"tf":1.4142135623730951},"233":{"tf":2.6457513110645907},"236":{"tf":1.4142135623730951},"242":{"tf":1.0},"249":{"tf":2.449489742783178},"25":{"tf":1.0},"250":{"tf":2.449489742783178},"252":{"tf":1.0},"26":{"tf":2.23606797749979},"263":{"tf":1.4142135623730951},"268":{"tf":3.0},"270":{"tf":2.23606797749979},"279":{"tf":1.0},"280":{"tf":1.0},"29":{"tf":1.0},"30":{"tf":1.4142135623730951},"31":{"tf":1.4142135623730951},"34":{"tf":1.0},"35":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"39":{"tf":2.0},"40":{"tf":1.7320508075688772},"49":{"tf":1.0},"58":{"tf":1.4142135623730951},"64":{"tf":1.0},"7":{"tf":2.23606797749979},"81":{"tf":1.0},"82":{"tf":1.7320508075688772},"84":{"tf":2.23606797749979},"86":{"tf":2.449489742783178},"87":{"tf":1.0},"88":{"tf":1.0}},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{".":{"df":0,"docs":{},"m":{"d":{"df":2,"docs":{"268":{"tf":2.0},"7":{"tf":1.0}}},"df":0,"docs":{}}},"]":{"(":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{":":{"/":{"/":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"u":{"b":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"/":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"268":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"268":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}},"df":4,"docs":{"21":{"tf":1.0},"269":{"tf":1.0},"282":{"tf":1.0},"7":{"tf":1.0}}}}}}},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":3,"docs":{"122":{"tf":1.0},"271":{"tf":1.0},"274":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"t":{"df":3,"docs":{"215":{"tf":1.0},"216":{"tf":1.0},"258":{"tf":1.0}}}},"t":{"df":1,"docs":{"1":{"tf":1.0}}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"k":{"_":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"d":{"_":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"_":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"_":{"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":2,"docs":{"145":{"tf":1.4142135623730951},"146":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"97":{"tf":1.0}}}}}},"df":0,"docs":{}}}}},"df":35,"docs":{"104":{"tf":1.0},"105":{"tf":1.4142135623730951},"11":{"tf":1.4142135623730951},"12":{"tf":1.0},"122":{"tf":1.4142135623730951},"124":{"tf":1.0},"126":{"tf":1.0},"14":{"tf":1.0},"151":{"tf":1.4142135623730951},"152":{"tf":1.7320508075688772},"155":{"tf":1.4142135623730951},"156":{"tf":1.4142135623730951},"18":{"tf":1.0},"189":{"tf":1.0},"201":{"tf":1.0},"22":{"tf":1.0},"242":{"tf":1.0},"245":{"tf":1.4142135623730951},"26":{"tf":1.0},"274":{"tf":2.449489742783178},"282":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0},"51":{"tf":1.0},"58":{"tf":1.0},"62":{"tf":2.6457513110645907},"64":{"tf":2.23606797749979},"69":{"tf":1.0},"7":{"tf":1.0},"70":{"tf":1.0},"73":{"tf":2.6457513110645907},"81":{"tf":1.0},"96":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"268":{"tf":1.4142135623730951}}}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":17,"docs":{"124":{"tf":1.0},"18":{"tf":1.7320508075688772},"23":{"tf":2.0},"24":{"tf":1.7320508075688772},"25":{"tf":1.0},"270":{"tf":1.0},"28":{"tf":1.0},"29":{"tf":1.7320508075688772},"30":{"tf":1.4142135623730951},"31":{"tf":2.0},"32":{"tf":2.0},"33":{"tf":1.0},"34":{"tf":2.0},"35":{"tf":1.4142135623730951},"36":{"tf":2.23606797749979},"37":{"tf":1.0},"49":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":5,"docs":{"167":{"tf":1.0},"279":{"tf":1.0},"280":{"tf":1.7320508075688772},"29":{"tf":1.4142135623730951},"34":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":1,"docs":{"97":{"tf":1.0}}}}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"270":{"tf":1.0}}}}}},"i":{"df":0,"docs":{},"p":{"df":1,"docs":{"17":{"tf":1.4142135623730951}}}},"o":{"df":0,"docs":{},"i":{"c":{"df":5,"docs":{"152":{"tf":1.0},"217":{"tf":1.0},"226":{"tf":1.4142135623730951},"227":{"tf":1.0},"232":{"tf":1.0}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"s":{"df":11,"docs":{"126":{"tf":1.0},"165":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"208":{"tf":1.0},"225":{"tf":1.4142135623730951},"226":{"tf":1.0},"229":{"tf":1.0},"31":{"tf":1.0},"98":{"tf":1.0}}}},"s":{"df":0,"docs":{},"e":{"df":1,"docs":{"251":{"tf":1.0}},"n":{"df":5,"docs":{"138":{"tf":1.0},"143":{"tf":1.0},"164":{"tf":1.0},"199":{"tf":1.7320508075688772},"208":{"tf":1.4142135623730951}}}}}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":3,"docs":{"206":{"tf":1.4142135623730951},"217":{"tf":1.0},"218":{"tf":1.4142135623730951}}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":2,"docs":{"105":{"tf":1.0},"106":{"tf":1.0}}}}}},"i":{"df":20,"docs":{"11":{"tf":1.4142135623730951},"118":{"tf":1.0},"119":{"tf":1.0},"263":{"tf":2.0},"266":{"tf":1.4142135623730951},"270":{"tf":1.0},"271":{"tf":1.0},"274":{"tf":1.7320508075688772},"275":{"tf":1.0},"276":{"tf":1.4142135623730951},"277":{"tf":1.0},"278":{"tf":1.0},"280":{"tf":1.0},"282":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0},"49":{"tf":1.0},"51":{"tf":1.0},"64":{"tf":1.4142135623730951},"92":{"tf":1.0}},"r":{"c":{"df":0,"docs":{},"l":{"df":1,"docs":{"263":{"tf":1.0}},"e":{"c":{"df":0,"docs":{},"i":{"df":3,"docs":{"263":{"tf":1.0},"274":{"tf":2.449489742783178},"277":{"tf":2.0}}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"197":{"tf":1.0}}}},"df":0,"docs":{}},"m":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"170":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}}},"l":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"210":{"tf":1.0}}}},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"37":{"tf":1.4142135623730951}}}},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":1,"docs":{"6":{"tf":1.0}},"i":{"df":1,"docs":{"233":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"s":{"df":9,"docs":{"100":{"tf":1.0},"103":{"tf":1.4142135623730951},"108":{"tf":1.0},"109":{"tf":1.0},"112":{"tf":1.0},"131":{"tf":2.23606797749979},"132":{"tf":2.0},"98":{"tf":1.4142135623730951},"99":{"tf":1.0}},"i":{"c":{"df":1,"docs":{"277":{"tf":1.0}}},"df":0,"docs":{}},"p":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"223":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"n":{"df":2,"docs":{"240":{"tf":1.0},"279":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"172":{"tf":1.0}}}}},"r":{"df":3,"docs":{"229":{"tf":1.0},"279":{"tf":1.0},"60":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"226":{"tf":1.0}}}},"l":{"df":0,"docs":{},"i":{"df":3,"docs":{"132":{"tf":1.0},"64":{"tf":1.0},"7":{"tf":1.0}}}}}},"df":0,"docs":{}},"i":{"c":{"df":0,"docs":{},"k":{"df":5,"docs":{"117":{"tf":1.4142135623730951},"31":{"tf":2.0},"36":{"tf":2.0},"40":{"tf":1.4142135623730951},"73":{"tf":1.4142135623730951}}}},"df":1,"docs":{"117":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"x":{"c":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"p":{"b":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"df":1,"docs":{"31":{"tf":1.0}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":2,"docs":{"25":{"tf":1.0},"31":{"tf":1.0}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},":":{":":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"d":{"df":0,"docs":{},"s":{"_":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"_":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"268":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"283":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":31,"docs":{"10":{"tf":1.0},"105":{"tf":1.0},"107":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"11":{"tf":1.0},"134":{"tf":1.0},"179":{"tf":1.0},"186":{"tf":1.4142135623730951},"192":{"tf":1.0},"193":{"tf":2.0},"194":{"tf":2.0},"196":{"tf":1.0},"197":{"tf":2.6457513110645907},"198":{"tf":2.6457513110645907},"199":{"tf":1.4142135623730951},"200":{"tf":2.8284271247461903},"201":{"tf":1.7320508075688772},"226":{"tf":1.0},"231":{"tf":3.0},"234":{"tf":1.0},"237":{"tf":1.0},"238":{"tf":1.7320508075688772},"254":{"tf":2.0},"256":{"tf":1.4142135623730951},"257":{"tf":1.0},"268":{"tf":1.0},"283":{"tf":1.7320508075688772},"284":{"tf":1.4142135623730951},"31":{"tf":1.0},"58":{"tf":1.4142135623730951}}}}},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":3,"docs":{"262":{"tf":1.4142135623730951},"263":{"tf":1.4142135623730951},"92":{"tf":1.4142135623730951}}}}}},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":7,"docs":{"11":{"tf":1.7320508075688772},"12":{"tf":1.0},"34":{"tf":1.4142135623730951},"36":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0}}}},"s":{"df":0,"docs":{},"e":{"df":5,"docs":{"105":{"tf":1.0},"133":{"tf":1.0},"187":{"tf":1.0},"230":{"tf":1.0},"258":{"tf":1.0}}}}}},"o":{"d":{"df":0,"docs":{},"e":{"'":{"df":1,"docs":{"45":{"tf":1.0}}},"b":{"a":{"df":0,"docs":{},"s":{"df":2,"docs":{"143":{"tf":1.0},"65":{"tf":1.0}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":1,"docs":{"50":{"tf":1.0}}}}},"df":96,"docs":{"0":{"tf":1.0},"1":{"tf":1.0},"101":{"tf":1.0},"103":{"tf":3.3166247903554},"104":{"tf":2.0},"105":{"tf":2.0},"106":{"tf":1.7320508075688772},"107":{"tf":2.23606797749979},"108":{"tf":2.6457513110645907},"109":{"tf":2.449489742783178},"111":{"tf":1.7320508075688772},"114":{"tf":1.7320508075688772},"115":{"tf":2.0},"116":{"tf":1.4142135623730951},"117":{"tf":2.23606797749979},"118":{"tf":2.0},"120":{"tf":1.0},"125":{"tf":1.4142135623730951},"128":{"tf":1.4142135623730951},"132":{"tf":1.0},"133":{"tf":1.0},"140":{"tf":1.0},"145":{"tf":1.7320508075688772},"146":{"tf":1.0},"154":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0},"158":{"tf":1.4142135623730951},"159":{"tf":1.4142135623730951},"163":{"tf":1.4142135623730951},"164":{"tf":1.0},"165":{"tf":1.0},"166":{"tf":1.0},"167":{"tf":2.6457513110645907},"169":{"tf":1.0},"17":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.7320508075688772},"172":{"tf":2.23606797749979},"174":{"tf":1.0},"193":{"tf":1.0},"194":{"tf":1.0},"2":{"tf":1.0},"200":{"tf":2.8284271247461903},"219":{"tf":1.0},"22":{"tf":1.0},"220":{"tf":2.0},"221":{"tf":1.4142135623730951},"222":{"tf":1.4142135623730951},"223":{"tf":1.4142135623730951},"224":{"tf":1.4142135623730951},"232":{"tf":1.0},"241":{"tf":2.0},"245":{"tf":1.0},"249":{"tf":1.4142135623730951},"251":{"tf":1.0},"253":{"tf":1.7320508075688772},"263":{"tf":1.0},"274":{"tf":3.1622776601683795},"279":{"tf":1.0},"28":{"tf":1.0},"282":{"tf":1.0},"283":{"tf":1.0},"31":{"tf":1.0},"33":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.7320508075688772},"43":{"tf":1.7320508075688772},"44":{"tf":2.0},"45":{"tf":3.0},"46":{"tf":2.449489742783178},"49":{"tf":1.0},"50":{"tf":1.4142135623730951},"58":{"tf":1.7320508075688772},"60":{"tf":1.7320508075688772},"63":{"tf":1.4142135623730951},"64":{"tf":2.23606797749979},"65":{"tf":1.4142135623730951},"66":{"tf":1.4142135623730951},"67":{"tf":1.4142135623730951},"68":{"tf":1.0},"69":{"tf":1.7320508075688772},"7":{"tf":2.6457513110645907},"70":{"tf":2.0},"72":{"tf":2.23606797749979},"74":{"tf":1.7320508075688772},"8":{"tf":1.7320508075688772},"81":{"tf":1.4142135623730951},"82":{"tf":1.4142135623730951},"83":{"tf":1.4142135623730951},"9":{"tf":1.4142135623730951},"90":{"tf":1.4142135623730951},"92":{"tf":1.4142135623730951},"93":{"tf":1.0},"95":{"tf":1.4142135623730951},"98":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"229":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"l":{"a":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"274":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":15,"docs":{"0":{"tf":1.0},"189":{"tf":1.0},"194":{"tf":1.0},"203":{"tf":1.0},"226":{"tf":1.7320508075688772},"229":{"tf":1.0},"232":{"tf":2.0},"242":{"tf":1.4142135623730951},"244":{"tf":1.0},"245":{"tf":1.4142135623730951},"248":{"tf":1.4142135623730951},"259":{"tf":2.0},"45":{"tf":1.0},"6":{"tf":1.4142135623730951},"86":{"tf":2.449489742783178}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"'":{"df":1,"docs":{"245":{"tf":1.0}}},"/":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"228":{"tf":1.0}}}}}}}},"df":0,"docs":{}}}},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"128":{"tf":1.0}}}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"283":{"tf":1.4142135623730951}}}},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"n":{"df":2,"docs":{"250":{"tf":1.0},"84":{"tf":1.0}}}}}},"m":{"b":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"58":{"tf":1.0}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":6,"docs":{"145":{"tf":1.4142135623730951},"146":{"tf":1.4142135623730951},"228":{"tf":1.0},"249":{"tf":1.0},"274":{"tf":1.4142135623730951},"39":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":5,"docs":{"116":{"tf":1.0},"205":{"tf":1.0},"261":{"tf":1.0},"281":{"tf":1.0},"282":{"tf":1.0}}},"m":{"a":{"df":1,"docs":{"17":{"tf":1.0}},"n":{"d":{".":{"df":0,"docs":{},"t":{"a":{"b":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"283":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":12,"docs":{"104":{"tf":1.0},"11":{"tf":1.0},"126":{"tf":1.0},"165":{"tf":1.0},"172":{"tf":1.0},"19":{"tf":1.0},"22":{"tf":1.0},"226":{"tf":1.4142135623730951},"231":{"tf":2.6457513110645907},"237":{"tf":1.4142135623730951},"268":{"tf":1.0},"283":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"29":{"tf":1.0},"34":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"t":{"df":10,"docs":{"253":{"tf":1.0},"267":{"tf":1.0},"268":{"tf":1.4142135623730951},"270":{"tf":1.0},"271":{"tf":1.0},"274":{"tf":1.7320508075688772},"282":{"tf":1.0},"30":{"tf":1.4142135623730951},"35":{"tf":1.4142135623730951},"7":{"tf":1.7320508075688772}}}},"o":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"df":1,"docs":{"94":{"tf":1.0}}}}}},"df":6,"docs":{"154":{"tf":1.0},"22":{"tf":1.0},"249":{"tf":1.0},"282":{"tf":1.0},"81":{"tf":1.0},"84":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"264":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"n":{"df":6,"docs":{"189":{"tf":1.0},"226":{"tf":1.4142135623730951},"231":{"tf":1.0},"254":{"tf":1.4142135623730951},"3":{"tf":1.4142135623730951},"64":{"tf":1.0}}}}},"p":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"208":{"tf":1.0}}}},"df":0,"docs":{},"r":{"df":6,"docs":{"151":{"tf":1.4142135623730951},"152":{"tf":1.4142135623730951},"155":{"tf":1.4142135623730951},"156":{"tf":1.4142135623730951},"245":{"tf":1.4142135623730951},"62":{"tf":1.0}}},"t":{"df":6,"docs":{"116":{"tf":1.0},"161":{"tf":1.0},"162":{"tf":1.0},"37":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":29,"docs":{"105":{"tf":1.0},"109":{"tf":1.0},"114":{"tf":1.4142135623730951},"161":{"tf":1.0},"163":{"tf":1.7320508075688772},"164":{"tf":1.0},"171":{"tf":1.7320508075688772},"172":{"tf":2.23606797749979},"220":{"tf":1.0},"221":{"tf":1.0},"222":{"tf":1.0},"223":{"tf":1.4142135623730951},"224":{"tf":1.4142135623730951},"251":{"tf":1.0},"252":{"tf":1.4142135623730951},"253":{"tf":1.0},"278":{"tf":1.4142135623730951},"279":{"tf":1.0},"28":{"tf":1.0},"280":{"tf":2.23606797749979},"33":{"tf":1.0},"37":{"tf":1.4142135623730951},"45":{"tf":1.4142135623730951},"46":{"tf":1.0},"57":{"tf":1.4142135623730951},"58":{"tf":1.4142135623730951},"59":{"tf":1.7320508075688772},"60":{"tf":2.23606797749979},"73":{"tf":2.23606797749979}}}},"l":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":2,"docs":{"263":{"tf":1.0},"60":{"tf":1.4142135623730951}},"t":{"df":1,"docs":{"60":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":11,"docs":{"10":{"tf":1.0},"106":{"tf":1.0},"11":{"tf":1.0},"115":{"tf":1.0},"119":{"tf":1.0},"169":{"tf":1.0},"187":{"tf":1.0},"200":{"tf":1.0},"22":{"tf":1.0},"270":{"tf":1.0},"84":{"tf":1.0}},"e":{"_":{"df":0,"docs":{},"o":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":1,"docs":{"283":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"x":{"df":12,"docs":{"106":{"tf":1.7320508075688772},"115":{"tf":1.0},"116":{"tf":1.0},"140":{"tf":1.0},"158":{"tf":1.0},"200":{"tf":1.0},"220":{"tf":1.0},"229":{"tf":1.0},"233":{"tf":1.0},"280":{"tf":1.0},"78":{"tf":1.4142135623730951},"81":{"tf":1.0}}}},"i":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"262":{"tf":1.0}}}}},"c":{"df":6,"docs":{"10":{"tf":1.0},"197":{"tf":1.0},"225":{"tf":1.4142135623730951},"233":{"tf":1.0},"45":{"tf":1.0},"62":{"tf":1.0}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"n":{"df":168,"docs":{"0":{"tf":2.449489742783178},"1":{"tf":1.4142135623730951},"100":{"tf":1.0},"101":{"tf":3.0},"102":{"tf":2.449489742783178},"103":{"tf":2.8284271247461903},"104":{"tf":2.23606797749979},"105":{"tf":2.23606797749979},"106":{"tf":2.0},"107":{"tf":1.4142135623730951},"108":{"tf":2.8284271247461903},"109":{"tf":2.6457513110645907},"11":{"tf":1.4142135623730951},"110":{"tf":1.4142135623730951},"111":{"tf":1.4142135623730951},"112":{"tf":1.7320508075688772},"113":{"tf":1.4142135623730951},"114":{"tf":1.4142135623730951},"115":{"tf":1.4142135623730951},"116":{"tf":1.4142135623730951},"117":{"tf":1.7320508075688772},"118":{"tf":1.0},"119":{"tf":1.4142135623730951},"12":{"tf":1.0},"120":{"tf":1.7320508075688772},"122":{"tf":1.7320508075688772},"123":{"tf":3.0},"124":{"tf":1.4142135623730951},"125":{"tf":1.0},"14":{"tf":1.0},"141":{"tf":1.0},"145":{"tf":1.0},"149":{"tf":1.0},"150":{"tf":1.0},"151":{"tf":1.0},"157":{"tf":1.0},"159":{"tf":1.0},"16":{"tf":2.0},"161":{"tf":1.0},"162":{"tf":1.0},"163":{"tf":1.0},"165":{"tf":1.7320508075688772},"166":{"tf":1.0},"167":{"tf":2.8284271247461903},"169":{"tf":1.0},"17":{"tf":1.0},"170":{"tf":1.7320508075688772},"171":{"tf":2.449489742783178},"172":{"tf":2.23606797749979},"175":{"tf":1.4142135623730951},"194":{"tf":1.4142135623730951},"198":{"tf":1.0},"2":{"tf":1.0},"20":{"tf":1.4142135623730951},"200":{"tf":4.242640687119285},"21":{"tf":1.0},"219":{"tf":1.7320508075688772},"22":{"tf":1.4142135623730951},"220":{"tf":2.449489742783178},"221":{"tf":2.23606797749979},"222":{"tf":2.449489742783178},"224":{"tf":1.4142135623730951},"225":{"tf":3.0},"226":{"tf":3.0},"228":{"tf":3.0},"229":{"tf":2.8284271247461903},"23":{"tf":3.1622776601683795},"230":{"tf":2.0},"232":{"tf":1.4142135623730951},"233":{"tf":2.23606797749979},"234":{"tf":1.0},"235":{"tf":1.4142135623730951},"236":{"tf":1.7320508075688772},"237":{"tf":1.7320508075688772},"24":{"tf":1.0},"243":{"tf":1.0},"244":{"tf":1.4142135623730951},"245":{"tf":2.0},"246":{"tf":1.0},"247":{"tf":1.0},"25":{"tf":2.0},"251":{"tf":2.449489742783178},"252":{"tf":1.4142135623730951},"253":{"tf":2.6457513110645907},"254":{"tf":1.0},"256":{"tf":1.0},"257":{"tf":2.8284271247461903},"258":{"tf":1.0},"259":{"tf":2.0},"26":{"tf":2.449489742783178},"260":{"tf":1.4142135623730951},"262":{"tf":1.4142135623730951},"264":{"tf":1.4142135623730951},"268":{"tf":1.0},"271":{"tf":1.0},"272":{"tf":1.4142135623730951},"274":{"tf":2.0},"278":{"tf":1.0},"28":{"tf":1.0},"282":{"tf":1.0},"283":{"tf":1.7320508075688772},"284":{"tf":1.0},"29":{"tf":2.23606797749979},"30":{"tf":1.4142135623730951},"31":{"tf":3.0},"32":{"tf":3.3166247903554},"33":{"tf":1.0},"34":{"tf":2.449489742783178},"35":{"tf":1.4142135623730951},"36":{"tf":3.0},"37":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.4142135623730951},"41":{"tf":2.449489742783178},"42":{"tf":1.0},"43":{"tf":2.0},"44":{"tf":1.0},"45":{"tf":2.0},"46":{"tf":2.23606797749979},"47":{"tf":1.0},"48":{"tf":1.7320508075688772},"49":{"tf":2.6457513110645907},"50":{"tf":1.0},"51":{"tf":1.0},"52":{"tf":1.0},"53":{"tf":1.0},"54":{"tf":2.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.4142135623730951},"59":{"tf":1.4142135623730951},"60":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0},"68":{"tf":2.449489742783178},"69":{"tf":2.0},"7":{"tf":1.4142135623730951},"70":{"tf":1.0},"71":{"tf":1.0},"72":{"tf":1.0},"73":{"tf":3.4641016151377544},"74":{"tf":3.605551275463989},"75":{"tf":3.1622776601683795},"76":{"tf":2.23606797749979},"77":{"tf":2.0},"78":{"tf":2.449489742783178},"79":{"tf":1.7320508075688772},"80":{"tf":1.7320508075688772},"81":{"tf":2.0},"82":{"tf":1.7320508075688772},"83":{"tf":1.4142135623730951},"84":{"tf":1.7320508075688772},"85":{"tf":1.4142135623730951},"86":{"tf":1.4142135623730951},"87":{"tf":1.4142135623730951},"88":{"tf":1.4142135623730951},"89":{"tf":1.4142135623730951},"90":{"tf":1.4142135623730951},"91":{"tf":1.0},"92":{"tf":1.0},"93":{"tf":1.0},"94":{"tf":1.0},"95":{"tf":1.0},"96":{"tf":1.0},"97":{"tf":1.0},"98":{"tf":1.0},"99":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"'":{"df":11,"docs":{"104":{"tf":1.4142135623730951},"108":{"tf":1.0},"21":{"tf":1.0},"219":{"tf":1.0},"254":{"tf":1.0},"255":{"tf":1.0},"256":{"tf":1.0},"257":{"tf":1.0},"258":{"tf":1.0},"70":{"tf":2.23606797749979},"73":{"tf":1.0}}},">":{":":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"45":{"tf":1.0}}}}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"103":{"tf":1.4142135623730951}},"e":{">":{".":{"df":0,"docs":{},"u":{"d":{"df":0,"docs":{},"l":{"df":1,"docs":{"103":{"tf":1.0}}}},"df":0,"docs":{}}},":":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":1,"docs":{"108":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"108":{"tf":1.0}}}}}}},"_":{"df":0,"docs":{},"m":{"df":0,"docs":{},"s":{"df":0,"docs":{},"g":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":2,"docs":{"105":{"tf":1.0},"106":{"tf":1.0}}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{".":{"df":0,"docs":{},"h":{"df":1,"docs":{"109":{"tf":1.0}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"54":{"tf":1.0}}}},"s":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"34":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}},"t":{"a":{"df":0,"docs":{},"r":{".":{"df":0,"docs":{},"x":{"df":0,"docs":{},"z":{"df":1,"docs":{"271":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"/":{"<":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"103":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"_":{"c":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"70":{"tf":1.0},"72":{"tf":1.0}},"e":{">":{"/":{"<":{"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"_":{"c":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{">":{".":{"df":0,"docs":{},"u":{"d":{"df":0,"docs":{},"l":{"df":1,"docs":{"69":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"/":{"df":0,"docs":{},"s":{"df":0,"docs":{},"r":{"c":{"/":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"/":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"x":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":1,"docs":{"70":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":1,"docs":{"70":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":1,"docs":{"72":{"tf":1.0}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":1,"docs":{"70":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":1,"docs":{"69":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"c":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"/":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"70":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"df":1,"docs":{"39":{"tf":1.0}}}}}}},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"l":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"18":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"m":{"df":0,"docs":{},"y":{"c":{"df":0,"docs":{},"r":{"df":1,"docs":{"60":{"tf":1.0}}}},"df":0,"docs":{}}}},":":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{".":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":1,"docs":{"1":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"[":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":1,"docs":{"200":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"224":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":1,"docs":{"138":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":2,"docs":{"63":{"tf":1.0},"64":{"tf":1.0}}}}}}}}},"n":{"c":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"99":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":3,"docs":{"165":{"tf":1.0},"220":{"tf":1.0},"226":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"n":{"df":3,"docs":{"140":{"tf":1.0},"225":{"tf":1.0},"59":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":3,"docs":{"187":{"tf":1.0},"206":{"tf":1.0},"226":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":2,"docs":{"166":{"tf":1.0},"235":{"tf":1.0}}}}}},"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"4":{"tf":1.0}}}}},"df":7,"docs":{"144":{"tf":1.4142135623730951},"145":{"tf":1.0},"146":{"tf":1.0},"153":{"tf":1.4142135623730951},"168":{"tf":1.4142135623730951},"181":{"tf":1.4142135623730951},"211":{"tf":1.4142135623730951}},"f":{"df":0,"docs":{},"i":{"d":{"df":3,"docs":{"152":{"tf":1.0},"172":{"tf":1.0},"201":{"tf":1.0}}},"df":0,"docs":{},"g":{"df":3,"docs":{"11":{"tf":1.0},"277":{"tf":1.0},"37":{"tf":1.0}},"u":{"df":0,"docs":{},"r":{"df":16,"docs":{"105":{"tf":1.0},"109":{"tf":1.0},"12":{"tf":2.0},"13":{"tf":1.4142135623730951},"18":{"tf":1.0},"184":{"tf":1.7320508075688772},"187":{"tf":1.0},"20":{"tf":1.0},"200":{"tf":1.4142135623730951},"22":{"tf":1.0},"25":{"tf":1.0},"263":{"tf":1.7320508075688772},"37":{"tf":1.4142135623730951},"49":{"tf":1.0},"59":{"tf":1.0},"69":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"m":{"df":6,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"13":{"tf":1.0},"22":{"tf":1.0},"69":{"tf":1.0},"70":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"165":{"tf":1.0},"175":{"tf":1.0},"233":{"tf":1.0}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":1,"docs":{"189":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"237":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"s":{"df":1,"docs":{"61":{"tf":1.0}}}}},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":1,"docs":{"106":{"tf":1.0}}}}}},"df":0,"docs":{}}},"n":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"(":{"\"":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"62":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"_":{"b":{"a":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"h":{"(":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"l":{"_":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"m":{"a":{"df":1,"docs":{"62":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":18,"docs":{"1":{"tf":1.0},"126":{"tf":1.4142135623730951},"130":{"tf":1.0},"133":{"tf":1.0},"154":{"tf":1.0},"155":{"tf":1.0},"183":{"tf":1.0},"189":{"tf":1.0},"190":{"tf":1.4142135623730951},"226":{"tf":1.0},"229":{"tf":1.0},"276":{"tf":1.0},"283":{"tf":1.7320508075688772},"78":{"tf":1.0},"79":{"tf":2.6457513110645907},"81":{"tf":1.4142135623730951},"83":{"tf":1.0},"87":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"62":{"tf":1.0}}}}}}}}}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":4,"docs":{"165":{"tf":1.4142135623730951},"166":{"tf":1.4142135623730951},"209":{"tf":1.4142135623730951},"210":{"tf":1.7320508075688772}}}},"r":{"df":0,"docs":{},"v":{"df":2,"docs":{"152":{"tf":1.0},"64":{"tf":1.0}}}}},"i":{"d":{"df":29,"docs":{"123":{"tf":1.0},"137":{"tf":1.4142135623730951},"142":{"tf":1.4142135623730951},"151":{"tf":1.4142135623730951},"163":{"tf":1.4142135623730951},"167":{"tf":1.0},"179":{"tf":1.4142135623730951},"190":{"tf":1.4142135623730951},"192":{"tf":1.0},"195":{"tf":1.4142135623730951},"200":{"tf":1.0},"206":{"tf":1.0},"207":{"tf":1.4142135623730951},"217":{"tf":1.0},"220":{"tf":1.0},"228":{"tf":1.4142135623730951},"229":{"tf":1.0},"231":{"tf":1.0},"233":{"tf":1.4142135623730951},"262":{"tf":1.0},"263":{"tf":1.0},"274":{"tf":1.0},"276":{"tf":1.0},"51":{"tf":1.0},"59":{"tf":1.0},"6":{"tf":1.0},"64":{"tf":1.0},"75":{"tf":1.0},"82":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":4,"docs":{"260":{"tf":1.0},"58":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0}}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":4,"docs":{"103":{"tf":1.0},"145":{"tf":1.0},"162":{"tf":1.0},"61":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"183":{"tf":1.0}}}},"t":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"93":{"tf":1.0}}}}},"df":3,"docs":{"117":{"tf":1.0},"20":{"tf":1.0},"94":{"tf":1.0}},"r":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"187":{"tf":1.0},"226":{"tf":1.0}}}}}},"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"131":{"tf":1.0}}}},"df":0,"docs":{}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":3,"docs":{"11":{"tf":1.0},"41":{"tf":1.0},"7":{"tf":1.0}}}},"m":{"df":56,"docs":{"104":{"tf":1.0},"107":{"tf":1.4142135623730951},"108":{"tf":1.0},"109":{"tf":1.0},"118":{"tf":1.4142135623730951},"123":{"tf":1.4142135623730951},"14":{"tf":1.0},"145":{"tf":2.0},"146":{"tf":1.7320508075688772},"154":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0},"158":{"tf":1.0},"16":{"tf":1.4142135623730951},"161":{"tf":1.0},"162":{"tf":1.0},"164":{"tf":1.0},"165":{"tf":1.4142135623730951},"166":{"tf":1.4142135623730951},"167":{"tf":2.0},"169":{"tf":2.0},"170":{"tf":2.449489742783178},"171":{"tf":2.0},"172":{"tf":1.4142135623730951},"174":{"tf":1.0},"175":{"tf":1.4142135623730951},"18":{"tf":1.7320508075688772},"193":{"tf":1.4142135623730951},"194":{"tf":1.7320508075688772},"20":{"tf":1.7320508075688772},"200":{"tf":1.4142135623730951},"201":{"tf":1.0},"22":{"tf":1.0},"223":{"tf":1.0},"228":{"tf":1.0},"237":{"tf":1.0},"245":{"tf":1.0},"251":{"tf":1.0},"253":{"tf":1.0},"268":{"tf":2.0},"272":{"tf":1.4142135623730951},"273":{"tf":1.0},"274":{"tf":2.449489742783178},"37":{"tf":1.4142135623730951},"43":{"tf":1.4142135623730951},"49":{"tf":1.0},"50":{"tf":1.0},"51":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"69":{"tf":1.0},"70":{"tf":1.0},"74":{"tf":1.0},"78":{"tf":1.0},"83":{"tf":1.0},"90":{"tf":1.7320508075688772}},"e":{"df":0,"docs":{},"r":{".":{"df":0,"docs":{},"f":{"df":0,"docs":{},"x":{"a":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"283":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"200":{"tf":1.0}}}}}}},"t":{"a":{"c":{"df":0,"docs":{},"t":{"df":8,"docs":{"1":{"tf":1.7320508075688772},"21":{"tf":1.0},"245":{"tf":1.0},"25":{"tf":1.0},"266":{"tf":1.0},"274":{"tf":1.4142135623730951},"31":{"tf":1.0},"36":{"tf":1.0}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":27,"docs":{"105":{"tf":2.0},"167":{"tf":1.0},"20":{"tf":1.0},"221":{"tf":1.0},"222":{"tf":1.0},"223":{"tf":1.4142135623730951},"226":{"tf":1.0},"232":{"tf":1.0},"248":{"tf":1.0},"249":{"tf":1.0},"251":{"tf":1.0},"252":{"tf":1.7320508075688772},"271":{"tf":1.4142135623730951},"274":{"tf":1.0},"28":{"tf":1.0},"33":{"tf":1.0},"39":{"tf":1.0},"45":{"tf":1.0},"48":{"tf":1.0},"60":{"tf":1.4142135623730951},"61":{"tf":1.0},"69":{"tf":1.0},"70":{"tf":1.4142135623730951},"72":{"tf":1.0},"73":{"tf":1.0},"74":{"tf":1.0},"98":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":12,"docs":{"126":{"tf":1.0},"13":{"tf":1.0},"189":{"tf":1.7320508075688772},"193":{"tf":1.0},"194":{"tf":1.0},"196":{"tf":1.0},"201":{"tf":2.6457513110645907},"226":{"tf":1.0},"284":{"tf":1.0},"62":{"tf":1.0},"70":{"tf":1.4142135623730951},"98":{"tf":1.0}},"i":{"df":1,"docs":{"227":{"tf":1.0}}}}},"x":{"df":0,"docs":{},"t":{"df":10,"docs":{"136":{"tf":1.4142135623730951},"140":{"tf":1.4142135623730951},"149":{"tf":1.4142135623730951},"161":{"tf":1.4142135623730951},"174":{"tf":1.0},"177":{"tf":1.4142135623730951},"187":{"tf":1.4142135623730951},"200":{"tf":1.0},"201":{"tf":1.0},"203":{"tf":1.4142135623730951}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":9,"docs":{"11":{"tf":1.0},"120":{"tf":1.0},"141":{"tf":1.0},"145":{"tf":1.0},"167":{"tf":1.4142135623730951},"197":{"tf":1.0},"198":{"tf":1.0},"226":{"tf":1.0},"7":{"tf":1.0}}}}},"r":{"a":{"df":1,"docs":{"274":{"tf":1.0}}},"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":132,"docs":{"10":{"tf":1.0},"100":{"tf":1.0},"101":{"tf":1.0},"102":{"tf":1.0},"103":{"tf":1.0},"104":{"tf":1.0},"105":{"tf":1.0},"106":{"tf":1.0},"107":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"11":{"tf":1.0},"110":{"tf":1.0},"111":{"tf":1.0},"112":{"tf":1.0},"113":{"tf":1.0},"114":{"tf":1.0},"115":{"tf":1.0},"116":{"tf":1.0},"117":{"tf":1.0},"118":{"tf":1.0},"119":{"tf":1.0},"12":{"tf":1.0},"120":{"tf":1.0},"121":{"tf":1.0},"122":{"tf":1.0},"123":{"tf":1.0},"124":{"tf":1.0},"125":{"tf":1.0},"126":{"tf":1.0},"127":{"tf":1.0},"128":{"tf":1.0},"129":{"tf":1.0},"13":{"tf":1.0},"130":{"tf":1.0},"131":{"tf":1.0},"132":{"tf":1.0},"133":{"tf":1.0},"14":{"tf":1.0},"15":{"tf":1.0},"16":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.0},"205":{"tf":1.0},"21":{"tf":1.0},"22":{"tf":1.0},"23":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"26":{"tf":1.0},"27":{"tf":1.0},"28":{"tf":1.0},"29":{"tf":1.0},"3":{"tf":1.7320508075688772},"30":{"tf":1.0},"31":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.0},"34":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":1.0},"4":{"tf":1.0},"40":{"tf":1.0},"41":{"tf":1.0},"42":{"tf":1.0},"43":{"tf":1.0},"44":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"47":{"tf":1.0},"48":{"tf":1.0},"49":{"tf":1.0},"5":{"tf":1.0},"50":{"tf":1.0},"51":{"tf":1.0},"52":{"tf":1.0},"53":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"6":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0},"67":{"tf":1.0},"68":{"tf":1.0},"69":{"tf":1.0},"7":{"tf":1.0},"70":{"tf":1.0},"71":{"tf":1.0},"72":{"tf":1.0},"73":{"tf":1.0},"74":{"tf":1.0},"75":{"tf":1.0},"76":{"tf":1.0},"77":{"tf":1.0},"78":{"tf":1.0},"79":{"tf":1.0},"8":{"tf":1.0},"80":{"tf":1.0},"81":{"tf":1.0},"82":{"tf":1.0},"83":{"tf":1.0},"84":{"tf":1.0},"85":{"tf":1.0},"86":{"tf":1.0},"87":{"tf":1.0},"88":{"tf":1.0},"89":{"tf":1.0},"9":{"tf":1.0},"90":{"tf":1.0},"91":{"tf":1.0},"92":{"tf":1.0},"93":{"tf":1.0},"94":{"tf":1.0},"95":{"tf":1.0},"96":{"tf":1.0},"97":{"tf":1.0},"98":{"tf":1.0},"99":{"tf":1.0}},"o":{"df":0,"docs":{},"r":{"df":4,"docs":{"274":{"tf":1.0},"41":{"tf":1.0},"6":{"tf":1.4142135623730951},"7":{"tf":1.0}}}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"l":{"df":6,"docs":{"184":{"tf":1.0},"201":{"tf":1.4142135623730951},"208":{"tf":1.0},"283":{"tf":1.4142135623730951},"61":{"tf":1.0},"77":{"tf":1.0}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":6,"docs":{"117":{"tf":1.0},"165":{"tf":1.0},"25":{"tf":1.0},"257":{"tf":1.0},"281":{"tf":1.0},"79":{"tf":1.0}}},"t":{"df":13,"docs":{"100":{"tf":1.0},"107":{"tf":1.0},"137":{"tf":1.0},"69":{"tf":1.0},"91":{"tf":1.7320508075688772},"92":{"tf":1.0},"93":{"tf":1.7320508075688772},"94":{"tf":1.0},"95":{"tf":1.0},"96":{"tf":1.4142135623730951},"97":{"tf":1.0},"98":{"tf":1.0},"99":{"tf":1.4142135623730951}}}},"r":{"df":0,"docs":{},"s":{"df":2,"docs":{"105":{"tf":1.0},"106":{"tf":1.0}}},"t":{"df":11,"docs":{"101":{"tf":1.7320508075688772},"102":{"tf":1.0},"103":{"tf":1.7320508075688772},"104":{"tf":1.0},"105":{"tf":1.4142135623730951},"106":{"tf":1.0},"107":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"163":{"tf":1.0},"284":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"104":{"tf":1.0}}},"df":0,"docs":{}}}}},"o":{"df":0,"docs":{},"r":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":3,"docs":{"225":{"tf":1.0},"226":{"tf":2.23606797749979},"241":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"i":{"df":13,"docs":{"18":{"tf":1.0},"199":{"tf":1.0},"2":{"tf":1.0},"20":{"tf":1.0},"220":{"tf":1.0},"250":{"tf":1.0},"267":{"tf":1.7320508075688772},"273":{"tf":1.4142135623730951},"282":{"tf":1.0},"45":{"tf":1.0},"64":{"tf":1.0},"70":{"tf":1.0},"73":{"tf":2.0}}}},"r":{"df":0,"docs":{},"e":{"df":7,"docs":{"0":{"tf":1.0},"103":{"tf":1.7320508075688772},"105":{"tf":1.4142135623730951},"274":{"tf":1.0},"43":{"tf":1.4142135623730951},"6":{"tf":1.0},"78":{"tf":1.0}}},"r":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":8,"docs":{"11":{"tf":1.0},"126":{"tf":1.4142135623730951},"14":{"tf":1.0},"222":{"tf":1.0},"229":{"tf":1.0},"239":{"tf":1.0},"267":{"tf":1.0},"79":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":11,"docs":{"122":{"tf":1.0},"13":{"tf":1.0},"22":{"tf":1.4142135623730951},"224":{"tf":1.4142135623730951},"231":{"tf":1.0},"233":{"tf":1.4142135623730951},"250":{"tf":1.0},"263":{"tf":1.0},"274":{"tf":1.0},"70":{"tf":1.4142135623730951},"73":{"tf":1.0}}}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"d":{"df":12,"docs":{"106":{"tf":1.7320508075688772},"108":{"tf":1.0},"131":{"tf":1.7320508075688772},"167":{"tf":1.0},"223":{"tf":1.0},"242":{"tf":1.0},"244":{"tf":1.0},"274":{"tf":1.0},"39":{"tf":1.0},"46":{"tf":1.0},"48":{"tf":1.0},"73":{"tf":1.0}}},"df":0,"docs":{}}}}}},"u":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":14,"docs":{"116":{"tf":1.4142135623730951},"132":{"tf":1.0},"134":{"tf":1.0},"148":{"tf":1.4142135623730951},"149":{"tf":2.0},"150":{"tf":1.0},"151":{"tf":2.23606797749979},"152":{"tf":1.4142135623730951},"154":{"tf":1.7320508075688772},"155":{"tf":1.7320508075688772},"156":{"tf":1.7320508075688772},"157":{"tf":1.0},"158":{"tf":1.0},"159":{"tf":1.4142135623730951}}}}}}},"s":{"df":0,"docs":{},"t":{"df":3,"docs":{"105":{"tf":1.0},"115":{"tf":1.0},"150":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"60":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"214":{"tf":1.0},"82":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":3,"docs":{"250":{"tf":2.0},"82":{"tf":1.0},"84":{"tf":2.449489742783178}}}}}},"p":{"df":0,"docs":{},"l":{"df":7,"docs":{"116":{"tf":1.0},"206":{"tf":1.0},"208":{"tf":1.0},"225":{"tf":1.0},"226":{"tf":1.0},"228":{"tf":1.0},"229":{"tf":1.0}}}},"r":{"df":0,"docs":{},"s":{"df":2,"docs":{"105":{"tf":1.0},"115":{"tf":1.0}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":2,"docs":{"41":{"tf":1.0},"50":{"tf":2.23606797749979}}}},"df":4,"docs":{"236":{"tf":1.0},"268":{"tf":1.0},"7":{"tf":1.0},"84":{"tf":1.0}}}}}},"p":{"df":0,"docs":{},"p":{"=":{"c":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":1,"docs":{"37":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"r":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":2,"docs":{"117":{"tf":1.4142135623730951},"205":{"tf":1.0}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"u":{"d":{"df":0,"docs":{},"l":{"df":1,"docs":{"70":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"t":{"df":0,"docs":{},"e":{"'":{"df":4,"docs":{"105":{"tf":1.0},"106":{"tf":1.4142135623730951},"64":{"tf":1.0},"69":{"tf":1.0}}},":":{":":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"106":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{">":{"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":1,"docs":{"73":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":29,"docs":{"103":{"tf":2.0},"105":{"tf":3.0},"106":{"tf":1.4142135623730951},"107":{"tf":1.0},"116":{"tf":1.4142135623730951},"122":{"tf":1.0},"123":{"tf":1.7320508075688772},"198":{"tf":1.4142135623730951},"221":{"tf":2.0},"222":{"tf":1.0},"225":{"tf":1.0},"237":{"tf":1.0},"238":{"tf":1.0},"244":{"tf":1.4142135623730951},"245":{"tf":1.0},"246":{"tf":1.0},"251":{"tf":1.0},"252":{"tf":1.4142135623730951},"28":{"tf":1.0},"281":{"tf":2.0},"282":{"tf":1.0},"286":{"tf":1.0},"33":{"tf":1.0},"43":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":2.449489742783178},"60":{"tf":2.6457513110645907},"64":{"tf":2.449489742783178},"69":{"tf":2.6457513110645907}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":40,"docs":{"104":{"tf":1.0},"108":{"tf":1.0},"11":{"tf":1.0},"118":{"tf":1.0},"129":{"tf":1.7320508075688772},"13":{"tf":1.4142135623730951},"130":{"tf":1.4142135623730951},"131":{"tf":1.7320508075688772},"145":{"tf":1.4142135623730951},"167":{"tf":1.4142135623730951},"178":{"tf":1.0},"18":{"tf":1.0},"180":{"tf":1.0},"190":{"tf":1.0},"194":{"tf":1.0},"200":{"tf":1.0},"229":{"tf":1.0},"241":{"tf":1.0},"244":{"tf":1.0},"245":{"tf":1.7320508075688772},"246":{"tf":1.0},"249":{"tf":1.4142135623730951},"253":{"tf":1.0},"268":{"tf":2.8284271247461903},"269":{"tf":2.23606797749979},"270":{"tf":1.0},"274":{"tf":1.7320508075688772},"283":{"tf":1.7320508075688772},"29":{"tf":1.0},"34":{"tf":1.0},"39":{"tf":1.0},"60":{"tf":1.0},"62":{"tf":1.0},"69":{"tf":1.0},"70":{"tf":1.4142135623730951},"72":{"tf":1.4142135623730951},"73":{"tf":1.4142135623730951},"75":{"tf":1.0},"78":{"tf":1.0},"82":{"tf":1.0}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"/":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"17":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"n":{"df":4,"docs":{"180":{"tf":1.0},"182":{"tf":1.0},"183":{"tf":1.0},"184":{"tf":1.0}}}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"n":{".":{"df":0,"docs":{},"y":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":1,"docs":{"40":{"tf":1.0}}}}}},"df":1,"docs":{"266":{"tf":1.4142135623730951}}},"s":{"df":0,"docs":{},"s":{"df":6,"docs":{"220":{"tf":1.0},"225":{"tf":1.0},"278":{"tf":1.0},"279":{"tf":1.0},"280":{"tf":2.23606797749979},"51":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":1,"docs":{"25":{"tf":1.0}}}}},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"/":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":1,"docs":{"226":{"tf":1.0}}}}}},"df":1,"docs":{"282":{"tf":1.0}},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":1,"docs":{"283":{"tf":1.0}},"h":{"df":2,"docs":{"278":{"tf":1.0},"282":{"tf":1.0}}}}},"df":0,"docs":{}}}}}}}},"s":{"a":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"k":{"df":1,"docs":{"186":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":56,"docs":{"101":{"tf":1.0},"103":{"tf":1.0},"104":{"tf":1.0},"11":{"tf":1.0},"111":{"tf":1.0},"121":{"tf":1.0},"126":{"tf":1.0},"145":{"tf":1.0},"146":{"tf":1.0},"150":{"tf":1.4142135623730951},"155":{"tf":1.0},"156":{"tf":1.0},"161":{"tf":1.7320508075688772},"163":{"tf":1.0},"166":{"tf":1.0},"167":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.4142135623730951},"175":{"tf":2.23606797749979},"187":{"tf":1.0},"193":{"tf":1.0},"196":{"tf":1.0},"220":{"tf":1.0},"228":{"tf":1.7320508075688772},"229":{"tf":1.0},"231":{"tf":1.0},"233":{"tf":1.4142135623730951},"242":{"tf":1.0},"245":{"tf":1.4142135623730951},"248":{"tf":1.0},"257":{"tf":1.4142135623730951},"258":{"tf":1.0},"26":{"tf":1.0},"260":{"tf":1.0},"261":{"tf":1.4142135623730951},"262":{"tf":1.4142135623730951},"263":{"tf":1.0},"264":{"tf":1.0},"268":{"tf":1.0},"274":{"tf":1.7320508075688772},"276":{"tf":1.0},"280":{"tf":1.0},"38":{"tf":1.0},"40":{"tf":1.0},"44":{"tf":1.0},"49":{"tf":1.0},"50":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"6":{"tf":1.0},"65":{"tf":1.4142135623730951},"66":{"tf":1.0},"67":{"tf":1.0},"7":{"tf":1.0},"84":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":4,"docs":{"133":{"tf":1.0},"184":{"tf":1.0},"222":{"tf":1.7320508075688772},"268":{"tf":1.0}}}}}},"t":{"df":4,"docs":{"253":{"tf":1.0},"268":{"tf":1.4142135623730951},"270":{"tf":1.4142135623730951},"59":{"tf":1.0}}}},"y":{"c":{"df":0,"docs":{},"l":{"df":3,"docs":{"121":{"tf":1.0},"180":{"tf":1.0},"183":{"tf":1.0}}}},"df":0,"docs":{}}},"d":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":2,"docs":{"205":{"tf":1.0},"274":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":2,"docs":{"17":{"tf":1.4142135623730951},"280":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"h":{"b":{"df":0,"docs":{},"o":{"a":{"df":0,"docs":{},"r":{"d":{"df":2,"docs":{"203":{"tf":1.0},"218":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":1,"docs":{"200":{"tf":1.0}}}},"t":{"a":{"b":{"a":{"df":0,"docs":{},"s":{"df":31,"docs":{"130":{"tf":1.0},"134":{"tf":1.0},"140":{"tf":1.4142135623730951},"148":{"tf":1.4142135623730951},"149":{"tf":2.23606797749979},"150":{"tf":1.0},"151":{"tf":3.1622776601683795},"152":{"tf":2.23606797749979},"154":{"tf":2.23606797749979},"155":{"tf":1.7320508075688772},"156":{"tf":2.0},"157":{"tf":2.23606797749979},"158":{"tf":2.6457513110645907},"159":{"tf":1.4142135623730951},"203":{"tf":1.4142135623730951},"205":{"tf":2.0},"206":{"tf":1.7320508075688772},"208":{"tf":2.0},"209":{"tf":2.0},"212":{"tf":2.0},"213":{"tf":1.0},"229":{"tf":1.0},"245":{"tf":1.0},"248":{"tf":1.4142135623730951},"264":{"tf":1.7320508075688772},"268":{"tf":1.0},"78":{"tf":1.0},"79":{"tf":1.0},"81":{"tf":1.7320508075688772},"84":{"tf":1.0},"86":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":36,"docs":{"105":{"tf":1.0},"106":{"tf":2.23606797749979},"140":{"tf":1.0},"145":{"tf":1.4142135623730951},"150":{"tf":1.4142135623730951},"154":{"tf":1.4142135623730951},"155":{"tf":1.4142135623730951},"156":{"tf":1.4142135623730951},"157":{"tf":1.0},"158":{"tf":2.8284271247461903},"159":{"tf":1.4142135623730951},"177":{"tf":1.4142135623730951},"178":{"tf":1.0},"179":{"tf":2.449489742783178},"180":{"tf":1.4142135623730951},"183":{"tf":1.4142135623730951},"184":{"tf":1.4142135623730951},"185":{"tf":1.4142135623730951},"189":{"tf":1.4142135623730951},"201":{"tf":2.0},"209":{"tf":1.0},"212":{"tf":1.0},"231":{"tf":1.0},"232":{"tf":1.0},"242":{"tf":1.4142135623730951},"254":{"tf":2.0},"255":{"tf":1.0},"259":{"tf":1.4142135623730951},"264":{"tf":1.0},"283":{"tf":1.4142135623730951},"79":{"tf":1.0},"80":{"tf":1.4142135623730951},"82":{"tf":1.0},"84":{"tf":1.4142135623730951},"86":{"tf":1.0},"89":{"tf":1.0}}},"df":0,"docs":{},"e":{"a":{"d":{"df":1,"docs":{"62":{"tf":1.0}}},"df":0,"docs":{}},"df":16,"docs":{"121":{"tf":1.0},"139":{"tf":1.0},"148":{"tf":1.0},"160":{"tf":1.0},"167":{"tf":1.4142135623730951},"176":{"tf":1.0},"186":{"tf":1.0},"202":{"tf":1.0},"207":{"tf":1.0},"21":{"tf":1.0},"213":{"tf":1.4142135623730951},"255":{"tf":1.0},"264":{"tf":1.0},"268":{"tf":1.0},"278":{"tf":1.0},"37":{"tf":1.0}}}},"y":{"df":5,"docs":{"119":{"tf":1.0},"206":{"tf":1.0},"218":{"tf":1.0},"266":{"tf":1.0},"268":{"tf":1.4142135623730951}},"s":{"/":{"df":0,"docs":{},"w":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"k":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"207":{"tf":1.0}}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"b":{":":{":":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{":":{":":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{":":{":":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"_":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"b":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"_":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"c":{"df":0,"docs":{},"r":{"df":1,"docs":{"62":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":4,"docs":{"249":{"tf":1.4142135623730951},"78":{"tf":1.0},"79":{"tf":2.0},"87":{"tf":1.0}},"g":{"(":{"'":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"62":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"62":{"tf":1.4142135623730951}}}},"d":{"_":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}},"df":7,"docs":{"163":{"tf":1.0},"164":{"tf":1.0},"172":{"tf":1.4142135623730951},"174":{"tf":1.0},"29":{"tf":1.0},"34":{"tf":1.0},"37":{"tf":1.0}},"e":{"a":{"d":{"df":1,"docs":{"60":{"tf":1.0}}},"df":0,"docs":{},"l":{"df":6,"docs":{"149":{"tf":1.4142135623730951},"179":{"tf":1.0},"180":{"tf":1.0},"184":{"tf":1.7320508075688772},"185":{"tf":1.4142135623730951},"64":{"tf":1.0}}}},"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{".":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"k":{"df":1,"docs":{"40":{"tf":1.0}}}}},"df":0,"docs":{}},"df":7,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"117":{"tf":2.23606797749979},"125":{"tf":1.0},"37":{"tf":1.7320508075688772},"62":{"tf":2.6457513110645907},"82":{"tf":1.0}},"g":{"df":1,"docs":{"117":{"tf":1.7320508075688772}}}}}},"c":{"df":0,"docs":{},"i":{"d":{"df":11,"docs":{"152":{"tf":1.4142135623730951},"155":{"tf":1.4142135623730951},"157":{"tf":1.4142135623730951},"160":{"tf":1.0},"176":{"tf":1.0},"186":{"tf":1.0},"202":{"tf":1.0},"225":{"tf":1.0},"231":{"tf":1.4142135623730951},"249":{"tf":1.0},"267":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":91,"docs":{"134":{"tf":2.449489742783178},"135":{"tf":1.7320508075688772},"136":{"tf":1.4142135623730951},"137":{"tf":1.7320508075688772},"138":{"tf":2.0},"139":{"tf":1.0},"140":{"tf":1.0},"141":{"tf":1.7320508075688772},"142":{"tf":1.0},"143":{"tf":1.7320508075688772},"144":{"tf":1.0},"145":{"tf":1.0},"146":{"tf":1.0},"147":{"tf":1.0},"148":{"tf":1.0},"149":{"tf":1.0},"150":{"tf":1.7320508075688772},"151":{"tf":1.0},"152":{"tf":2.0},"153":{"tf":1.0},"154":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0},"157":{"tf":1.0},"158":{"tf":1.0},"159":{"tf":1.0},"160":{"tf":1.0},"161":{"tf":1.0},"162":{"tf":1.7320508075688772},"163":{"tf":1.0},"164":{"tf":1.7320508075688772},"165":{"tf":1.0},"166":{"tf":1.0},"167":{"tf":1.0},"168":{"tf":1.0},"169":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"173":{"tf":1.0},"174":{"tf":1.0},"175":{"tf":1.0},"176":{"tf":1.0},"177":{"tf":1.0},"178":{"tf":1.7320508075688772},"179":{"tf":1.0},"180":{"tf":1.7320508075688772},"181":{"tf":1.0},"182":{"tf":1.0},"183":{"tf":1.0},"184":{"tf":1.0},"185":{"tf":1.0},"186":{"tf":1.0},"187":{"tf":1.4142135623730951},"188":{"tf":1.4142135623730951},"189":{"tf":1.0},"190":{"tf":1.0},"191":{"tf":1.0},"192":{"tf":1.0},"193":{"tf":1.0},"194":{"tf":1.0},"195":{"tf":1.0},"196":{"tf":1.0},"197":{"tf":1.0},"198":{"tf":1.4142135623730951},"199":{"tf":1.0},"200":{"tf":1.0},"201":{"tf":1.0},"202":{"tf":1.0},"203":{"tf":1.0},"204":{"tf":1.0},"205":{"tf":1.0},"206":{"tf":1.7320508075688772},"207":{"tf":1.0},"208":{"tf":2.23606797749979},"209":{"tf":1.0},"210":{"tf":1.4142135623730951},"211":{"tf":1.0},"212":{"tf":1.0},"213":{"tf":1.0},"214":{"tf":1.0},"215":{"tf":1.0},"216":{"tf":1.0},"217":{"tf":1.0},"218":{"tf":1.0},"226":{"tf":1.0},"227":{"tf":1.0},"230":{"tf":1.0},"251":{"tf":1.0},"266":{"tf":1.7320508075688772},"40":{"tf":1.0}}}},"l":{"a":{"df":0,"docs":{},"r":{"df":8,"docs":{"172":{"tf":1.0},"174":{"tf":1.0},"201":{"tf":1.4142135623730951},"224":{"tf":1.4142135623730951},"60":{"tf":1.4142135623730951},"61":{"tf":1.0},"73":{"tf":1.0},"98":{"tf":1.0}}}},"df":1,"docs":{"61":{"tf":1.0}},"i":{"df":0,"docs":{},"n":{"df":4,"docs":{"233":{"tf":2.8284271247461903},"234":{"tf":1.0},"236":{"tf":1.4142135623730951},"242":{"tf":2.0}},"e":{"d":{"_":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"242":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"229":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":1,"docs":{"209":{"tf":1.0}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"250":{"tf":1.0},"84":{"tf":1.0}}}}}}},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"_":{"a":{"df":0,"docs":{},"n":{"d":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"_":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"145":{"tf":1.4142135623730951}}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":4,"docs":{"140":{"tf":1.0},"145":{"tf":1.7320508075688772},"146":{"tf":1.7320508075688772},"254":{"tf":1.0}}}}}}},"d":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"59":{"tf":1.0}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"p":{"df":3,"docs":{"140":{"tf":1.0},"141":{"tf":1.0},"145":{"tf":1.0}}}}},"df":3,"docs":{"140":{"tf":1.0},"145":{"tf":1.4142135623730951},"146":{"tf":1.0}},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"174":{"tf":1.0}}}}}},"f":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{".":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"a":{"df":0,"docs":{},"w":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"/":{"?":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"=":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"2":{"/":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"/":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"271":{"tf":1.0}},"e":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"271":{"tf":1.0}}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":5,"docs":{"11":{"tf":1.0},"12":{"tf":1.0},"222":{"tf":1.0},"60":{"tf":1.0},"69":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"140":{"tf":1.0}}}},"df":0,"docs":{}},"i":{"c":{"df":0,"docs":{},"i":{"df":1,"docs":{"141":{"tf":1.0}}}},"df":0,"docs":{},"n":{"df":26,"docs":{"105":{"tf":2.0},"106":{"tf":1.4142135623730951},"112":{"tf":1.0},"131":{"tf":1.0},"165":{"tf":1.0},"167":{"tf":1.0},"175":{"tf":1.0},"179":{"tf":1.0},"189":{"tf":1.0},"20":{"tf":1.0},"206":{"tf":1.0},"221":{"tf":1.4142135623730951},"226":{"tf":1.0},"229":{"tf":1.0},"232":{"tf":1.0},"242":{"tf":1.0},"244":{"tf":1.0},"245":{"tf":1.0},"246":{"tf":1.4142135623730951},"252":{"tf":1.0},"253":{"tf":1.0},"261":{"tf":1.0},"37":{"tf":1.4142135623730951},"69":{"tf":1.0},"81":{"tf":1.0},"84":{"tf":1.0}},"e":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"(":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":1,"docs":{"62":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"t":{"df":9,"docs":{"103":{"tf":1.0},"104":{"tf":1.4142135623730951},"105":{"tf":1.0},"111":{"tf":1.0},"167":{"tf":1.0},"172":{"tf":1.0},"233":{"tf":1.0},"242":{"tf":1.0},"69":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"?":{"c":{"df":0,"docs":{},"r":{"df":1,"docs":{"283":{"tf":1.0}}}},"df":0,"docs":{}},"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"283":{"tf":1.0}}}}}}}}}}}}}}}}},"df":0,"docs":{}}}}}}}}}},"g":{"df":0,"docs":{},"r":{"a":{"d":{"df":1,"docs":{"236":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":3,"docs":{"189":{"tf":1.0},"201":{"tf":1.0},"229":{"tf":1.0}}}}},"l":{"a":{"df":0,"docs":{},"y":{"df":4,"docs":{"179":{"tf":1.4142135623730951},"180":{"tf":1.0},"184":{"tf":2.0},"185":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":2,"docs":{"194":{"tf":1.0},"229":{"tf":1.0}}},"t":{"df":23,"docs":{"105":{"tf":1.0},"106":{"tf":2.0},"108":{"tf":1.0},"109":{"tf":1.4142135623730951},"117":{"tf":1.0},"149":{"tf":1.0},"150":{"tf":1.0},"151":{"tf":1.0},"152":{"tf":1.4142135623730951},"154":{"tf":1.4142135623730951},"155":{"tf":1.4142135623730951},"156":{"tf":1.7320508075688772},"157":{"tf":1.4142135623730951},"158":{"tf":2.0},"159":{"tf":1.4142135623730951},"209":{"tf":1.0},"212":{"tf":1.0},"231":{"tf":1.0},"249":{"tf":1.0},"268":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0}}}},"i":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"167":{"tf":1.0}}}}},"df":0,"docs":{},"v":{"df":2,"docs":{"225":{"tf":1.0},"235":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"235":{"tf":1.4142135623730951}},"i":{"df":1,"docs":{"228":{"tf":1.0}}}}}}}},"m":{"df":0,"docs":{},"o":{"df":2,"docs":{"174":{"tf":1.0},"185":{"tf":1.0}},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":4,"docs":{"210":{"tf":1.0},"215":{"tf":1.0},"216":{"tf":1.0},"237":{"tf":1.0}}}}}}}},"p":{"df":1,"docs":{"107":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"d":{"df":45,"docs":{"10":{"tf":1.0},"105":{"tf":1.4142135623730951},"106":{"tf":1.4142135623730951},"11":{"tf":2.23606797749979},"12":{"tf":1.4142135623730951},"121":{"tf":1.4142135623730951},"122":{"tf":1.0},"123":{"tf":1.4142135623730951},"125":{"tf":1.0},"140":{"tf":1.0},"162":{"tf":1.0},"163":{"tf":1.0},"167":{"tf":2.0},"172":{"tf":1.4142135623730951},"174":{"tf":1.7320508075688772},"175":{"tf":1.0},"197":{"tf":1.0},"20":{"tf":1.0},"200":{"tf":1.7320508075688772},"22":{"tf":2.449489742783178},"220":{"tf":1.7320508075688772},"221":{"tf":3.0},"222":{"tf":1.0},"224":{"tf":1.4142135623730951},"252":{"tf":1.0},"253":{"tf":1.0},"274":{"tf":1.7320508075688772},"31":{"tf":1.7320508075688772},"36":{"tf":2.23606797749979},"37":{"tf":1.7320508075688772},"45":{"tf":1.0},"53":{"tf":1.4142135623730951},"58":{"tf":3.1622776601683795},"59":{"tf":2.0},"63":{"tf":2.0},"64":{"tf":3.872983346207417},"65":{"tf":2.449489742783178},"66":{"tf":2.0},"67":{"tf":2.0},"69":{"tf":2.449489742783178},"7":{"tf":1.7320508075688772},"73":{"tf":1.0},"78":{"tf":1.0},"79":{"tf":1.0},"81":{"tf":1.0}}},"df":0,"docs":{}}},"i":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"200":{"tf":1.7320508075688772}}}},"df":0,"docs":{}},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"y":{"df":2,"docs":{"117":{"tf":1.0},"90":{"tf":1.4142135623730951}}}}},"r":{"df":0,"docs":{},"e":{"c":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"h":{"df":3,"docs":{"93":{"tf":1.0},"96":{"tf":1.0},"99":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"<":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"79":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"v":{"df":1,"docs":{"106":{"tf":1.0}}}}},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":17,"docs":{"102":{"tf":1.0},"104":{"tf":1.0},"105":{"tf":1.0},"120":{"tf":1.0},"20":{"tf":1.0},"200":{"tf":1.0},"214":{"tf":1.0},"220":{"tf":1.0},"243":{"tf":1.0},"245":{"tf":1.0},"252":{"tf":1.0},"282":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0},"64":{"tf":1.0},"76":{"tf":1.0},"98":{"tf":1.0}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":7,"docs":{"145":{"tf":1.0},"146":{"tf":1.0},"21":{"tf":1.0},"219":{"tf":1.0},"249":{"tf":1.0},"251":{"tf":1.0},"280":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"x":{"df":1,"docs":{"283":{"tf":1.0}}}}}}}}}}}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"df":55,"docs":{"106":{"tf":1.0},"138":{"tf":1.4142135623730951},"219":{"tf":2.0},"220":{"tf":1.0},"221":{"tf":1.0},"222":{"tf":1.0},"223":{"tf":1.0},"224":{"tf":1.0},"225":{"tf":1.0},"226":{"tf":1.0},"227":{"tf":1.0},"228":{"tf":1.7320508075688772},"229":{"tf":1.0},"23":{"tf":1.0},"230":{"tf":1.0},"231":{"tf":1.0},"232":{"tf":1.0},"233":{"tf":1.0},"234":{"tf":1.0},"235":{"tf":1.0},"236":{"tf":1.0},"237":{"tf":1.0},"238":{"tf":1.0},"239":{"tf":1.0},"240":{"tf":1.0},"241":{"tf":1.0},"242":{"tf":1.0},"243":{"tf":1.0},"244":{"tf":1.0},"245":{"tf":1.0},"246":{"tf":1.0},"247":{"tf":1.0},"248":{"tf":1.0},"249":{"tf":1.0},"250":{"tf":1.0},"251":{"tf":1.7320508075688772},"252":{"tf":1.0},"253":{"tf":1.0},"254":{"tf":1.0},"255":{"tf":1.0},"256":{"tf":1.0},"257":{"tf":1.0},"258":{"tf":1.0},"259":{"tf":1.0},"260":{"tf":1.0},"261":{"tf":1.0},"262":{"tf":1.0},"263":{"tf":1.0},"264":{"tf":1.0},"32":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"69":{"tf":1.0},"76":{"tf":1.4142135623730951},"96":{"tf":1.0}}}},"r":{"df":3,"docs":{"189":{"tf":1.0},"200":{"tf":1.0},"233":{"tf":1.0}}}},"k":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":2,"docs":{"280":{"tf":1.0},"282":{"tf":1.0}}}}},"df":23,"docs":{"1":{"tf":1.0},"11":{"tf":1.4142135623730951},"187":{"tf":1.0},"192":{"tf":1.7320508075688772},"201":{"tf":2.0},"206":{"tf":1.0},"210":{"tf":1.0},"217":{"tf":1.0},"218":{"tf":1.0},"223":{"tf":1.7320508075688772},"225":{"tf":1.7320508075688772},"226":{"tf":1.0},"228":{"tf":1.4142135623730951},"229":{"tf":1.0},"231":{"tf":1.4142135623730951},"232":{"tf":1.0},"241":{"tf":1.0},"255":{"tf":1.0},"257":{"tf":1.4142135623730951},"258":{"tf":1.0},"279":{"tf":1.0},"280":{"tf":1.4142135623730951},"282":{"tf":1.0}}}}}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"y":{"df":4,"docs":{"128":{"tf":1.0},"129":{"tf":1.7320508075688772},"131":{"tf":2.0},"132":{"tf":1.4142135623730951}}}},"u":{"c":{"df":0,"docs":{},"t":{"df":6,"docs":{"128":{"tf":2.0},"129":{"tf":1.4142135623730951},"130":{"tf":1.0},"131":{"tf":1.0},"132":{"tf":1.0},"133":{"tf":1.7320508075688772}},"o":{"df":0,"docs":{},"r":{"df":2,"docs":{"128":{"tf":1.0},"133":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}}}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":20,"docs":{"104":{"tf":1.0},"105":{"tf":1.4142135623730951},"111":{"tf":1.0},"12":{"tf":1.0},"145":{"tf":1.0},"15":{"tf":1.0},"161":{"tf":1.0},"175":{"tf":1.0},"22":{"tf":1.0},"227":{"tf":1.7320508075688772},"228":{"tf":1.0},"232":{"tf":1.4142135623730951},"242":{"tf":1.7320508075688772},"245":{"tf":1.0},"252":{"tf":1.0},"264":{"tf":1.0},"268":{"tf":1.0},"274":{"tf":1.0},"32":{"tf":1.0},"37":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"231":{"tf":1.0}}}},"df":0,"docs":{},"k":{"df":0,"docs":{},"t":{"df":1,"docs":{"65":{"tf":1.0}}}},"r":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"250":{"tf":1.0}},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"184":{"tf":1.0}}}}}}}}}}},"v":{"df":6,"docs":{"11":{"tf":1.7320508075688772},"121":{"tf":1.0},"268":{"tf":1.0},"274":{"tf":1.4142135623730951},"58":{"tf":2.23606797749979},"64":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":39,"docs":{"138":{"tf":1.0},"16":{"tf":1.0},"162":{"tf":1.4142135623730951},"164":{"tf":1.4142135623730951},"169":{"tf":1.4142135623730951},"17":{"tf":1.0},"170":{"tf":1.4142135623730951},"171":{"tf":1.7320508075688772},"172":{"tf":1.7320508075688772},"18":{"tf":1.4142135623730951},"19":{"tf":1.0},"199":{"tf":1.0},"20":{"tf":1.0},"21":{"tf":1.0},"22":{"tf":1.0},"223":{"tf":1.0},"23":{"tf":1.0},"24":{"tf":1.0},"25":{"tf":1.0},"26":{"tf":2.23606797749979},"260":{"tf":1.0},"27":{"tf":1.0},"274":{"tf":2.449489742783178},"277":{"tf":1.0},"28":{"tf":1.0},"284":{"tf":1.4142135623730951},"29":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.0},"34":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"45":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0}}}}}},"i":{"c":{"df":17,"docs":{"126":{"tf":1.7320508075688772},"175":{"tf":1.0},"226":{"tf":1.0},"229":{"tf":1.4142135623730951},"231":{"tf":2.449489742783178},"232":{"tf":1.0},"233":{"tf":3.1622776601683795},"236":{"tf":1.0},"237":{"tf":1.0},"242":{"tf":1.4142135623730951},"245":{"tf":2.0},"249":{"tf":1.0},"255":{"tf":1.0},"276":{"tf":1.0},"283":{"tf":1.4142135623730951},"45":{"tf":1.0},"51":{"tf":1.0}},"e":{".":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"283":{"tf":1.0}}}}}}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"f":{"df":0,"docs":{},"x":{"a":{"df":1,"docs":{"283":{"tf":1.0}}},"df":0,"docs":{}}}}}}}},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":1,"docs":{"283":{"tf":1.0}}}}}},"_":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"242":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"i":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":8,"docs":{"200":{"tf":1.7320508075688772},"221":{"tf":1.0},"254":{"tf":1.0},"255":{"tf":1.7320508075688772},"256":{"tf":1.0},"257":{"tf":1.0},"258":{"tf":2.0},"274":{"tf":1.0}}}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":2,"docs":{"31":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951}}}}}},"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":2,"docs":{"105":{"tf":1.0},"106":{"tf":1.0}}},"y":{"/":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"106":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"d":{"df":0,"docs":{},"n":{"'":{"df":0,"docs":{},"t":{"df":1,"docs":{"61":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":4,"docs":{"26":{"tf":1.0},"268":{"tf":2.0},"269":{"tf":1.0},"89":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"r":{"df":32,"docs":{"0":{"tf":1.0},"104":{"tf":1.0},"108":{"tf":1.4142135623730951},"109":{"tf":1.4142135623730951},"115":{"tf":1.0},"123":{"tf":1.0},"131":{"tf":1.0},"146":{"tf":1.4142135623730951},"165":{"tf":1.7320508075688772},"174":{"tf":1.0},"198":{"tf":1.0},"201":{"tf":1.0},"204":{"tf":1.0},"22":{"tf":1.0},"220":{"tf":1.0},"228":{"tf":1.0},"233":{"tf":1.7320508075688772},"242":{"tf":1.0},"243":{"tf":1.0},"245":{"tf":1.7320508075688772},"257":{"tf":1.0},"262":{"tf":1.0},"283":{"tf":1.0},"58":{"tf":1.0},"6":{"tf":1.0},"60":{"tf":1.0},"64":{"tf":1.0},"75":{"tf":1.0},"78":{"tf":1.0},"81":{"tf":1.4142135623730951},"82":{"tf":1.0},"84":{"tf":1.0}}}},"i":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":5,"docs":{"128":{"tf":1.0},"158":{"tf":1.0},"231":{"tf":1.0},"43":{"tf":1.0},"82":{"tf":1.7320508075688772}},"i":{"df":2,"docs":{"115":{"tf":1.0},"180":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"g":{"df":1,"docs":{"241":{"tf":1.0}}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":1,"docs":{"7":{"tf":1.0}}}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":1,"docs":{"64":{"tf":1.0}}}}}},"r":{"df":2,"docs":{"37":{"tf":1.0},"61":{"tf":1.0}},"e":{"c":{"df":0,"docs":{},"t":{"df":6,"docs":{"220":{"tf":1.0},"225":{"tf":1.0},"226":{"tf":1.0},"268":{"tf":1.4142135623730951},"283":{"tf":1.0},"37":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":10,"docs":{"106":{"tf":1.4142135623730951},"115":{"tf":1.0},"140":{"tf":1.0},"205":{"tf":1.0},"229":{"tf":1.0},"232":{"tf":1.0},"272":{"tf":1.0},"64":{"tf":1.0},"78":{"tf":1.0},"84":{"tf":1.0}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":21,"docs":{"12":{"tf":1.0},"122":{"tf":1.0},"22":{"tf":1.7320508075688772},"224":{"tf":1.0},"229":{"tf":1.0},"25":{"tf":1.0},"252":{"tf":2.23606797749979},"258":{"tf":1.0},"29":{"tf":1.7320508075688772},"30":{"tf":1.0},"33":{"tf":1.0},"34":{"tf":1.4142135623730951},"35":{"tf":1.0},"46":{"tf":1.0},"48":{"tf":1.4142135623730951},"60":{"tf":1.0},"61":{"tf":1.0},"70":{"tf":2.23606797749979},"72":{"tf":2.23606797749979},"73":{"tf":1.0},"74":{"tf":1.0}}}}}}},"df":0,"docs":{}},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"11":{"tf":1.0}}}},"df":0,"docs":{}}},"s":{"a":{"b":{"df":0,"docs":{},"l":{"df":11,"docs":{"178":{"tf":1.0},"180":{"tf":1.4142135623730951},"182":{"tf":1.0},"183":{"tf":1.0},"184":{"tf":1.0},"232":{"tf":1.0},"233":{"tf":1.0},"245":{"tf":1.4142135623730951},"26":{"tf":2.23606797749979},"48":{"tf":1.0},"61":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"103":{"tf":1.7320508075688772}}}},"df":0,"docs":{}}}}},"c":{"a":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"283":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"242":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":5,"docs":{"231":{"tf":1.0},"234":{"tf":2.23606797749979},"242":{"tf":1.4142135623730951},"283":{"tf":1.0},"87":{"tf":1.0}}}},"df":0,"docs":{}}}},"v":{"df":2,"docs":{"158":{"tf":1.4142135623730951},"261":{"tf":1.4142135623730951}}}},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":6,"docs":{"111":{"tf":1.0},"186":{"tf":1.0},"193":{"tf":1.0},"194":{"tf":1.0},"214":{"tf":1.0},"231":{"tf":1.0}}}}}},"df":0,"docs":{},"k":{"df":5,"docs":{"109":{"tf":1.0},"149":{"tf":1.0},"154":{"tf":1.0},"179":{"tf":1.0},"201":{"tf":1.0}}},"p":{"a":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"115":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"y":{"df":2,"docs":{"117":{"tf":1.0},"283":{"tf":1.0}}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":1,"docs":{"175":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"118":{"tf":1.0}}}}}},"t":{"/":{"df":0,"docs":{},"j":{"df":0,"docs":{},"n":{"a":{".":{"a":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"37":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":1,"docs":{"37":{"tf":1.4142135623730951}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":2,"docs":{"145":{"tf":1.0},"150":{"tf":1.0}}}}}}}}},"r":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":21,"docs":{"134":{"tf":1.0},"160":{"tf":1.4142135623730951},"161":{"tf":1.0},"163":{"tf":2.0},"164":{"tf":1.0},"165":{"tf":1.0},"167":{"tf":1.0},"171":{"tf":2.0},"172":{"tf":2.0},"214":{"tf":1.0},"215":{"tf":2.0},"218":{"tf":1.0},"219":{"tf":1.0},"220":{"tf":1.7320508075688772},"223":{"tf":1.0},"23":{"tf":1.4142135623730951},"252":{"tf":1.4142135623730951},"253":{"tf":1.0},"32":{"tf":1.4142135623730951},"65":{"tf":1.0},"74":{"tf":2.0}}}}},"df":0,"docs":{}}}}},"v":{"df":0,"docs":{},"e":{"df":1,"docs":{"174":{"tf":1.0}},"r":{"df":0,"docs":{},"g":{"df":2,"docs":{"196":{"tf":1.0},"252":{"tf":1.0}}}}}}},"n":{"a":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":1,"docs":{"202":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"o":{"c":{"df":8,"docs":{"107":{"tf":1.4142135623730951},"116":{"tf":1.0},"258":{"tf":1.0},"264":{"tf":1.0},"37":{"tf":1.0},"41":{"tf":1.0},"60":{"tf":1.0},"90":{"tf":1.0}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"194":{"tf":1.0},"274":{"tf":1.0}}}}},"s":{"/":{"d":{"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"258":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"62":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":33,"docs":{"101":{"tf":1.0},"107":{"tf":2.23606797749979},"109":{"tf":1.0},"111":{"tf":1.0},"115":{"tf":1.0},"116":{"tf":1.0},"120":{"tf":1.4142135623730951},"123":{"tf":1.0},"128":{"tf":1.0},"138":{"tf":1.4142135623730951},"15":{"tf":1.0},"175":{"tf":1.0},"185":{"tf":1.0},"187":{"tf":1.0},"205":{"tf":1.0},"219":{"tf":1.4142135623730951},"220":{"tf":1.0},"23":{"tf":1.0},"233":{"tf":1.0},"236":{"tf":1.0},"243":{"tf":1.0},"251":{"tf":1.0},"252":{"tf":1.0},"261":{"tf":1.4142135623730951},"274":{"tf":1.4142135623730951},"284":{"tf":2.23606797749979},"285":{"tf":1.7320508075688772},"286":{"tf":2.449489742783178},"41":{"tf":1.0},"62":{"tf":1.0},"75":{"tf":2.23606797749979},"85":{"tf":1.0},"91":{"tf":1.0}}}}}}}},"df":8,"docs":{"101":{"tf":1.0},"115":{"tf":1.0},"13":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"224":{"tf":1.0},"37":{"tf":1.0},"78":{"tf":1.0}},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"n":{"'":{"df":0,"docs":{},"t":{"df":19,"docs":{"111":{"tf":1.0},"154":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0},"169":{"tf":1.7320508075688772},"170":{"tf":1.7320508075688772},"171":{"tf":1.7320508075688772},"175":{"tf":1.0},"182":{"tf":1.0},"22":{"tf":1.0},"228":{"tf":1.0},"233":{"tf":1.0},"276":{"tf":1.0},"48":{"tf":1.7320508075688772},"60":{"tf":1.0},"7":{"tf":1.0},"78":{"tf":1.0},"82":{"tf":1.0},"90":{"tf":1.0}}}},"df":0,"docs":{}}}},"n":{"'":{"df":0,"docs":{},"t":{"df":24,"docs":{"104":{"tf":1.0},"105":{"tf":1.0},"106":{"tf":1.0},"117":{"tf":1.4142135623730951},"119":{"tf":1.0},"124":{"tf":1.0},"150":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0},"159":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"175":{"tf":1.0},"226":{"tf":1.0},"228":{"tf":1.0},"266":{"tf":1.0},"274":{"tf":1.0},"279":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"43":{"tf":1.0},"58":{"tf":1.0},"60":{"tf":1.0},"79":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":17,"docs":{"106":{"tf":1.0},"182":{"tf":1.0},"184":{"tf":1.0},"194":{"tf":1.0},"222":{"tf":1.0},"238":{"tf":1.4142135623730951},"242":{"tf":1.7320508075688772},"245":{"tf":1.0},"25":{"tf":1.0},"282":{"tf":1.0},"30":{"tf":1.0},"35":{"tf":1.0},"52":{"tf":1.0},"73":{"tf":1.4142135623730951},"79":{"tf":1.4142135623730951},"81":{"tf":1.0},"88":{"tf":1.0}}},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":1,"docs":{"117":{"tf":1.0}}}}}}}}},"’":{"df":0,"docs":{},"t":{"df":2,"docs":{"196":{"tf":1.0},"89":{"tf":1.0}}}}},"s":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"=":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"37":{"tf":2.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"t":{"df":1,"docs":{"183":{"tf":1.4142135623730951}}},"u":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"73":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"w":{"df":0,"docs":{},"n":{"df":4,"docs":{"115":{"tf":1.0},"165":{"tf":1.0},"235":{"tf":1.0},"242":{"tf":1.0}},"g":{"df":0,"docs":{},"r":{"a":{"d":{"df":1,"docs":{"212":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":3,"docs":{"12":{"tf":1.0},"279":{"tf":1.0},"40":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"116":{"tf":1.0},"172":{"tf":1.0}}},"df":0,"docs":{}}}}}},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"274":{"tf":1.0}}}},"r":{"a":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":2,"docs":{"104":{"tf":1.7320508075688772},"7":{"tf":1.4142135623730951}}}},"m":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"209":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"i":{"df":7,"docs":{"203":{"tf":1.7320508075688772},"204":{"tf":1.4142135623730951},"205":{"tf":1.0},"208":{"tf":1.0},"214":{"tf":1.0},"216":{"tf":1.4142135623730951},"218":{"tf":1.4142135623730951}},"v":{"df":0,"docs":{},"e":{"df":2,"docs":{"188":{"tf":1.0},"274":{"tf":1.0}},"r":{"df":7,"docs":{"141":{"tf":1.4142135623730951},"150":{"tf":1.4142135623730951},"162":{"tf":1.4142135623730951},"178":{"tf":1.4142135623730951},"206":{"tf":1.4142135623730951},"208":{"tf":1.4142135623730951},"210":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"p":{"df":5,"docs":{"12":{"tf":1.0},"216":{"tf":1.4142135623730951},"231":{"tf":1.0},"245":{"tf":1.0},"250":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"161":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"u":{"df":0,"docs":{},"e":{"df":10,"docs":{"11":{"tf":1.0},"116":{"tf":1.0},"180":{"tf":1.4142135623730951},"197":{"tf":1.0},"220":{"tf":1.0},"233":{"tf":1.4142135623730951},"280":{"tf":1.0},"46":{"tf":1.0},"64":{"tf":1.0},"7":{"tf":1.0}}},"m":{"df":0,"docs":{},"p":{"df":1,"docs":{"62":{"tf":1.0}}}},"p":{"df":0,"docs":{},"e":{"df":3,"docs":{"140":{"tf":1.0},"145":{"tf":1.4142135623730951},"146":{"tf":1.0}}},"l":{"df":0,"docs":{},"i":{"c":{"df":6,"docs":{"122":{"tf":1.0},"196":{"tf":1.4142135623730951},"256":{"tf":1.0},"258":{"tf":1.0},"263":{"tf":1.0},"282":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}},"r":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"86":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":8,"docs":{"105":{"tf":1.0},"106":{"tf":1.0},"152":{"tf":1.0},"154":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0},"175":{"tf":1.0},"58":{"tf":1.0}}}}},"y":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":3,"docs":{"253":{"tf":1.0},"278":{"tf":1.0},"282":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{".":{"df":0,"docs":{},"g":{"df":14,"docs":{"104":{"tf":1.0},"165":{"tf":1.0},"18":{"tf":1.0},"184":{"tf":1.0},"21":{"tf":1.0},"22":{"tf":1.0},"221":{"tf":1.0},"223":{"tf":1.0},"224":{"tf":1.0},"279":{"tf":1.0},"282":{"tf":1.0},"37":{"tf":1.0},"51":{"tf":1.0},"59":{"tf":1.4142135623730951}}}},"a":{"c":{"df":0,"docs":{},"h":{"df":46,"docs":{"0":{"tf":1.0},"101":{"tf":1.0},"106":{"tf":1.0},"115":{"tf":1.4142135623730951},"163":{"tf":1.0},"166":{"tf":1.0},"167":{"tf":1.4142135623730951},"172":{"tf":1.7320508075688772},"175":{"tf":1.0},"22":{"tf":1.0},"220":{"tf":1.4142135623730951},"221":{"tf":2.449489742783178},"222":{"tf":1.0},"223":{"tf":1.0},"224":{"tf":1.4142135623730951},"225":{"tf":1.7320508075688772},"226":{"tf":1.0},"228":{"tf":1.0},"229":{"tf":1.4142135623730951},"231":{"tf":1.0},"232":{"tf":1.0},"242":{"tf":1.4142135623730951},"243":{"tf":1.0},"244":{"tf":1.0},"245":{"tf":1.4142135623730951},"246":{"tf":1.0},"249":{"tf":1.7320508075688772},"252":{"tf":1.0},"256":{"tf":1.4142135623730951},"257":{"tf":1.0},"258":{"tf":1.4142135623730951},"271":{"tf":1.0},"274":{"tf":1.0},"282":{"tf":1.0},"32":{"tf":1.0},"37":{"tf":1.0},"43":{"tf":1.0},"46":{"tf":1.0},"48":{"tf":1.0},"51":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.4142135623730951},"64":{"tf":1.0},"68":{"tf":1.0},"69":{"tf":1.0},"84":{"tf":1.0}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":8,"docs":{"134":{"tf":1.0},"176":{"tf":1.4142135623730951},"177":{"tf":1.0},"178":{"tf":1.0},"179":{"tf":1.4142135623730951},"180":{"tf":1.0},"183":{"tf":1.0},"184":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"205":{"tf":1.0},"90":{"tf":1.0}}}}}}},"s":{"df":1,"docs":{"162":{"tf":1.4142135623730951}},"i":{"df":9,"docs":{"115":{"tf":1.0},"116":{"tf":1.4142135623730951},"129":{"tf":1.0},"158":{"tf":1.0},"220":{"tf":1.0},"233":{"tf":1.0},"241":{"tf":1.0},"280":{"tf":1.0},"46":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":4,"docs":{"172":{"tf":1.0},"278":{"tf":1.0},"60":{"tf":1.0},"81":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{"df":4,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"26":{"tf":1.0},"58":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"i":{"df":4,"docs":{"107":{"tf":1.0},"133":{"tf":1.0},"162":{"tf":1.0},"221":{"tf":1.0}}}}}}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":2,"docs":{"226":{"tf":1.0},"232":{"tf":1.0}}}}}}}}}},"d":{"df":0,"docs":{},"g":{"df":2,"docs":{"224":{"tf":1.7320508075688772},"233":{"tf":1.0}}},"i":{"df":0,"docs":{},"t":{"df":44,"docs":{"100":{"tf":1.0},"105":{"tf":1.0},"109":{"tf":1.4142135623730951},"117":{"tf":1.0},"119":{"tf":1.0},"124":{"tf":1.4142135623730951},"127":{"tf":1.0},"133":{"tf":1.0},"138":{"tf":1.0},"147":{"tf":1.0},"15":{"tf":1.0},"159":{"tf":1.0},"175":{"tf":1.0},"18":{"tf":1.0},"185":{"tf":1.0},"201":{"tf":1.0},"218":{"tf":1.0},"22":{"tf":1.0},"224":{"tf":1.0},"242":{"tf":1.0},"250":{"tf":1.0},"253":{"tf":1.0},"258":{"tf":2.23606797749979},"259":{"tf":1.0},"263":{"tf":1.0},"264":{"tf":1.0},"273":{"tf":1.0},"277":{"tf":1.0},"282":{"tf":1.0},"283":{"tf":1.0},"286":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"40":{"tf":1.0},"51":{"tf":1.0},"56":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0},"67":{"tf":1.0},"70":{"tf":1.4142135623730951},"74":{"tf":1.0},"8":{"tf":1.0},"90":{"tf":1.0}}}},"u":{"c":{"df":1,"docs":{"214":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":6,"docs":{"184":{"tf":1.0},"197":{"tf":1.0},"201":{"tf":1.0},"210":{"tf":1.0},"69":{"tf":1.0},"7":{"tf":1.0}}}},"df":0,"docs":{}},"i":{"c":{"df":0,"docs":{},"i":{"df":5,"docs":{"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":7,"docs":{"121":{"tf":1.0},"161":{"tf":1.0},"164":{"tf":1.0},"196":{"tf":1.0},"199":{"tf":1.0},"201":{"tf":1.0},"229":{"tf":1.0}}}}}}},"g":{"df":13,"docs":{"189":{"tf":1.0},"20":{"tf":1.0},"200":{"tf":1.4142135623730951},"225":{"tf":1.4142135623730951},"231":{"tf":1.4142135623730951},"232":{"tf":1.4142135623730951},"236":{"tf":1.0},"242":{"tf":1.4142135623730951},"250":{"tf":1.0},"74":{"tf":1.0},"82":{"tf":1.7320508075688772},"83":{"tf":1.0},"86":{"tf":1.0}}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":3,"docs":{"145":{"tf":1.4142135623730951},"155":{"tf":1.0},"156":{"tf":1.0}}}}}}},"m":{"b":{"df":1,"docs":{"244":{"tf":1.0}},"e":{"d":{"df":5,"docs":{"125":{"tf":1.0},"225":{"tf":1.4142135623730951},"228":{"tf":1.0},"242":{"tf":1.0},"250":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":1,"docs":{"274":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"262":{"tf":1.0},"282":{"tf":1.0}}}},"p":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"s":{"df":1,"docs":{"204":{"tf":1.0}},"i":{"df":1,"docs":{"75":{"tf":1.0}}}}},"df":0,"docs":{}},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"y":{"df":0,"docs":{},"e":{"df":1,"docs":{"258":{"tf":1.0}}}}}}},"u":{"df":0,"docs":{},"l":{"df":2,"docs":{"17":{"tf":1.4142135623730951},"51":{"tf":1.0}}}}},"n":{"a":{"b":{"df":0,"docs":{},"l":{"df":11,"docs":{"0":{"tf":1.4142135623730951},"10":{"tf":1.0},"138":{"tf":1.0},"184":{"tf":1.0},"189":{"tf":1.0},"232":{"tf":1.0},"233":{"tf":1.4142135623730951},"242":{"tf":1.0},"245":{"tf":2.0},"26":{"tf":1.0},"62":{"tf":1.0}},"e":{"d":{"/":{"d":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"242":{"tf":1.0}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"o":{"d":{"df":1,"docs":{"62":{"tf":1.0}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"s":{"df":1,"docs":{"82":{"tf":1.0}},"s":{"df":2,"docs":{"208":{"tf":1.0},"217":{"tf":1.0}}}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"5":{"tf":1.0}}}},"r":{"a":{"df":0,"docs":{},"g":{"df":4,"docs":{"165":{"tf":1.0},"264":{"tf":1.0},"274":{"tf":1.0},"7":{"tf":1.0}}}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":9,"docs":{"140":{"tf":2.449489742783178},"141":{"tf":1.0},"142":{"tf":1.4142135623730951},"143":{"tf":1.0},"145":{"tf":3.1622776601683795},"146":{"tf":2.0},"229":{"tf":1.0},"245":{"tf":1.4142135623730951},"254":{"tf":2.0}}}}}},"y":{"df":0,"docs":{},"r":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"141":{"tf":1.0}}}}}}},"d":{"df":32,"docs":{"10":{"tf":1.4142135623730951},"103":{"tf":1.0},"124":{"tf":1.0},"13":{"tf":1.0},"145":{"tf":1.0},"197":{"tf":1.4142135623730951},"199":{"tf":1.0},"20":{"tf":1.0},"200":{"tf":1.7320508075688772},"204":{"tf":2.23606797749979},"205":{"tf":1.4142135623730951},"213":{"tf":1.0},"214":{"tf":1.0},"216":{"tf":1.7320508075688772},"218":{"tf":1.0},"22":{"tf":1.4142135623730951},"225":{"tf":1.4142135623730951},"233":{"tf":1.0},"236":{"tf":1.0},"240":{"tf":1.0},"242":{"tf":1.0},"48":{"tf":2.449489742783178},"51":{"tf":1.4142135623730951},"52":{"tf":1.4142135623730951},"58":{"tf":1.0},"62":{"tf":1.0},"64":{"tf":1.0},"70":{"tf":1.0},"72":{"tf":1.0},"82":{"tf":1.0},"83":{"tf":1.0},"84":{"tf":1.4142135623730951}},"i":{"df":0,"docs":{},"f":{"df":1,"docs":{"37":{"tf":1.0}}}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"x":{"a":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"283":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"c":{"df":7,"docs":{"190":{"tf":1.0},"201":{"tf":1.4142135623730951},"225":{"tf":1.4142135623730951},"226":{"tf":1.0},"242":{"tf":1.0},"274":{"tf":1.0},"79":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":24,"docs":{"104":{"tf":1.0},"187":{"tf":1.0},"225":{"tf":2.6457513110645907},"226":{"tf":2.6457513110645907},"228":{"tf":3.1622776601683795},"229":{"tf":2.23606797749979},"231":{"tf":3.1622776601683795},"232":{"tf":3.3166247903554},"233":{"tf":2.23606797749979},"234":{"tf":1.7320508075688772},"236":{"tf":1.0},"237":{"tf":1.0},"238":{"tf":2.0},"241":{"tf":2.449489742783178},"242":{"tf":3.605551275463989},"245":{"tf":2.8284271247461903},"246":{"tf":1.4142135623730951},"249":{"tf":1.4142135623730951},"258":{"tf":1.4142135623730951},"274":{"tf":1.0},"48":{"tf":1.4142135623730951},"78":{"tf":1.0},"8":{"tf":1.0},"87":{"tf":1.0}},"e":{"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"242":{"tf":1.0}}}}}}}},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"_":{"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"242":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"242":{"tf":1.0}}}}}}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"242":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":3,"docs":{"214":{"tf":1.0},"225":{"tf":1.0},"26":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":3,"docs":{"180":{"tf":1.0},"183":{"tf":1.0},"189":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":21,"docs":{"104":{"tf":1.4142135623730951},"11":{"tf":1.0},"115":{"tf":1.0},"122":{"tf":1.0},"132":{"tf":1.0},"197":{"tf":1.0},"20":{"tf":1.0},"222":{"tf":1.0},"225":{"tf":1.0},"226":{"tf":1.0},"233":{"tf":1.0},"236":{"tf":1.0},"263":{"tf":1.0},"274":{"tf":1.4142135623730951},"279":{"tf":1.0},"37":{"tf":1.0},"45":{"tf":1.7320508075688772},"46":{"tf":1.0},"64":{"tf":1.4142135623730951},"7":{"tf":1.0},"79":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":3,"docs":{"122":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0}}}},"i":{"df":0,"docs":{},"r":{"df":12,"docs":{"103":{"tf":1.7320508075688772},"106":{"tf":1.0},"130":{"tf":1.0},"140":{"tf":1.0},"184":{"tf":1.0},"226":{"tf":1.0},"231":{"tf":1.0},"232":{"tf":1.0},"260":{"tf":1.0},"274":{"tf":1.4142135623730951},"46":{"tf":1.0},"62":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"142":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"i":{"df":6,"docs":{"226":{"tf":1.0},"241":{"tf":1.4142135623730951},"283":{"tf":1.0},"7":{"tf":1.0},"73":{"tf":1.7320508075688772},"78":{"tf":1.7320508075688772}}}}},"u":{"df":0,"docs":{},"m":{"df":8,"docs":{"104":{"tf":1.0},"105":{"tf":1.7320508075688772},"106":{"tf":1.0},"108":{"tf":1.0},"242":{"tf":1.7320508075688772},"87":{"tf":1.0},"93":{"tf":1.0},"97":{"tf":1.0}}}},"v":{"df":1,"docs":{"12":{"tf":1.0}},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":10,"docs":{"11":{"tf":1.7320508075688772},"12":{"tf":1.0},"122":{"tf":1.0},"14":{"tf":1.0},"274":{"tf":2.23606797749979},"277":{"tf":1.0},"37":{"tf":1.4142135623730951},"5":{"tf":1.0},"61":{"tf":1.0},"9":{"tf":1.0}},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":4,"docs":{"11":{"tf":1.4142135623730951},"12":{"tf":1.0},"14":{"tf":1.0},"279":{"tf":1.0}}}}},"df":1,"docs":{"225":{"tf":1.0}}}}}}}}}}}},"o":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"'":{"df":1,"docs":{"274":{"tf":1.0}}},"df":0,"docs":{}}}}},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"229":{"tf":1.0},"231":{"tf":1.0}}}}}}},"i":{"c":{"df":1,"docs":{"218":{"tf":1.0}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":1,"docs":{"200":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":24,"docs":{"104":{"tf":1.7320508075688772},"105":{"tf":1.0},"116":{"tf":1.0},"122":{"tf":1.0},"149":{"tf":1.7320508075688772},"150":{"tf":1.0},"151":{"tf":2.23606797749979},"152":{"tf":2.449489742783178},"154":{"tf":2.449489742783178},"155":{"tf":2.449489742783178},"156":{"tf":2.449489742783178},"158":{"tf":1.0},"189":{"tf":1.0},"204":{"tf":1.0},"226":{"tf":1.0},"237":{"tf":1.0},"239":{"tf":1.0},"256":{"tf":1.0},"258":{"tf":1.0},"262":{"tf":1.0},"264":{"tf":1.0},"283":{"tf":1.7320508075688772},"61":{"tf":1.0},"7":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"i":{"df":8,"docs":{"133":{"tf":1.0},"154":{"tf":1.0},"183":{"tf":1.0},"226":{"tf":1.0},"258":{"tf":1.0},"51":{"tf":1.0},"62":{"tf":1.0},"64":{"tf":1.0}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":3,"docs":{"11":{"tf":1.0},"111":{"tf":1.0},"115":{"tf":1.0}}}}}}},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":3,"docs":{"141":{"tf":1.0},"143":{"tf":1.0},"208":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"261":{"tf":1.0}}}}}},"t":{"c":{"df":24,"docs":{"112":{"tf":1.0},"149":{"tf":1.0},"207":{"tf":1.0},"214":{"tf":1.0},"220":{"tf":1.0},"226":{"tf":1.4142135623730951},"236":{"tf":1.0},"237":{"tf":1.4142135623730951},"239":{"tf":1.0},"240":{"tf":1.0},"241":{"tf":1.0},"242":{"tf":1.0},"244":{"tf":1.0},"249":{"tf":1.0},"255":{"tf":1.0},"278":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"61":{"tf":1.0},"75":{"tf":1.0},"78":{"tf":1.0},"79":{"tf":1.0},"80":{"tf":1.0},"86":{"tf":1.0}}},"df":0,"docs":{}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":3,"docs":{"161":{"tf":1.0},"203":{"tf":1.0},"59":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":10,"docs":{"104":{"tf":1.0},"140":{"tf":1.0},"19":{"tf":1.0},"212":{"tf":1.0},"225":{"tf":1.7320508075688772},"228":{"tf":1.0},"232":{"tf":1.0},"58":{"tf":1.4142135623730951},"60":{"tf":1.0},"61":{"tf":1.0}},"t":{"df":4,"docs":{"190":{"tf":1.0},"193":{"tf":1.0},"197":{"tf":1.4142135623730951},"283":{"tf":1.0}},"u":{"df":1,"docs":{"167":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"'":{"df":1,"docs":{"104":{"tf":1.0}}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"h":{"df":11,"docs":{"179":{"tf":1.0},"194":{"tf":1.0},"200":{"tf":1.0},"22":{"tf":1.0},"225":{"tf":1.0},"226":{"tf":1.0},"258":{"tf":1.0},"261":{"tf":1.0},"61":{"tf":1.0},"64":{"tf":1.0},"89":{"tf":1.4142135623730951}}}}}}},"i":{"d":{"df":1,"docs":{"201":{"tf":1.0}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"v":{"df":3,"docs":{"193":{"tf":1.0},"63":{"tf":1.0},"81":{"tf":1.0}}}}}},"x":{"a":{"c":{"df":0,"docs":{},"t":{"df":4,"docs":{"123":{"tf":1.0},"197":{"tf":1.0},"200":{"tf":1.0},"233":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":6,"docs":{"132":{"tf":1.0},"201":{"tf":1.4142135623730951},"225":{"tf":1.0},"237":{"tf":1.0},"61":{"tf":1.0},"79":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":2,"docs":{"128":{"tf":1.0},"205":{"tf":1.0}}}},"p":{"df":0,"docs":{},"l":{"df":45,"docs":{"100":{"tf":1.4142135623730951},"105":{"tf":1.7320508075688772},"106":{"tf":1.0},"107":{"tf":1.0},"108":{"tf":1.4142135623730951},"109":{"tf":1.0},"115":{"tf":1.0},"119":{"tf":1.4142135623730951},"123":{"tf":1.4142135623730951},"124":{"tf":1.4142135623730951},"130":{"tf":1.0},"131":{"tf":1.0},"132":{"tf":1.0},"156":{"tf":1.0},"175":{"tf":1.0},"18":{"tf":1.0},"20":{"tf":1.4142135623730951},"201":{"tf":1.4142135623730951},"213":{"tf":1.0},"216":{"tf":1.0},"22":{"tf":1.0},"225":{"tf":1.7320508075688772},"226":{"tf":1.4142135623730951},"228":{"tf":1.0},"229":{"tf":1.7320508075688772},"232":{"tf":1.0},"233":{"tf":1.0},"238":{"tf":1.0},"245":{"tf":1.0},"25":{"tf":1.0},"252":{"tf":1.0},"268":{"tf":1.7320508075688772},"31":{"tf":1.0},"36":{"tf":1.0},"58":{"tf":2.23606797749979},"59":{"tf":1.7320508075688772},"62":{"tf":1.0},"78":{"tf":1.0},"79":{"tf":1.7320508075688772},"81":{"tf":1.0},"82":{"tf":1.4142135623730951},"83":{"tf":1.4142135623730951},"86":{"tf":1.0},"94":{"tf":1.4142135623730951},"97":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":1,"docs":{"105":{"tf":1.4142135623730951}}}}}},"s":{"/":{"*":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"61":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"106":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}}}}}}}},"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":2,"docs":{"171":{"tf":1.0},"190":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"u":{"d":{"df":2,"docs":{"264":{"tf":1.4142135623730951},"37":{"tf":1.4142135623730951}}},"df":0,"docs":{},"s":{"df":1,"docs":{"194":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":9,"docs":{"13":{"tf":1.0},"158":{"tf":1.0},"19":{"tf":1.4142135623730951},"249":{"tf":1.4142135623730951},"25":{"tf":1.0},"274":{"tf":1.4142135623730951},"60":{"tf":1.4142135623730951},"62":{"tf":1.0},"7":{"tf":1.0}}}}},"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":3,"docs":{"166":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0}}}}},"df":0,"docs":{}}},"h":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"188":{"tf":1.0}}}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":36,"docs":{"101":{"tf":1.7320508075688772},"104":{"tf":1.7320508075688772},"105":{"tf":1.7320508075688772},"108":{"tf":1.0},"109":{"tf":1.4142135623730951},"115":{"tf":1.4142135623730951},"120":{"tf":1.0},"122":{"tf":1.4142135623730951},"123":{"tf":1.0},"13":{"tf":1.0},"140":{"tf":1.0},"141":{"tf":1.0},"166":{"tf":1.0},"167":{"tf":1.4142135623730951},"183":{"tf":1.0},"187":{"tf":1.0},"191":{"tf":1.4142135623730951},"193":{"tf":1.0},"196":{"tf":1.7320508075688772},"197":{"tf":2.23606797749979},"198":{"tf":2.0},"199":{"tf":1.0},"200":{"tf":1.7320508075688772},"201":{"tf":1.0},"210":{"tf":1.0},"225":{"tf":1.0},"228":{"tf":1.0},"232":{"tf":1.0},"233":{"tf":1.0},"263":{"tf":1.0},"274":{"tf":1.0},"41":{"tf":1.4142135623730951},"59":{"tf":1.0},"64":{"tf":2.0},"75":{"tf":1.0},"78":{"tf":1.0}}}},"t":{"df":2,"docs":{"233":{"tf":1.0},"60":{"tf":1.0}}}},"p":{"a":{"df":0,"docs":{},"n":{"d":{"df":3,"docs":{"167":{"tf":1.0},"201":{"tf":1.0},"283":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"(":{"\"":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"62":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":20,"docs":{"101":{"tf":1.0},"104":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"156":{"tf":1.0},"178":{"tf":1.0},"180":{"tf":1.0},"201":{"tf":1.0},"222":{"tf":1.0},"224":{"tf":1.0},"225":{"tf":1.0},"230":{"tf":1.0},"237":{"tf":1.0},"252":{"tf":1.0},"263":{"tf":1.0},"264":{"tf":1.0},"278":{"tf":1.0},"62":{"tf":1.0},"7":{"tf":1.0},"81":{"tf":1.0}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":27,"docs":{"10":{"tf":1.0},"101":{"tf":1.0},"115":{"tf":1.0},"134":{"tf":1.0},"158":{"tf":1.0},"164":{"tf":1.0},"174":{"tf":1.0},"175":{"tf":1.0},"176":{"tf":1.4142135623730951},"177":{"tf":1.7320508075688772},"178":{"tf":1.7320508075688772},"179":{"tf":2.6457513110645907},"180":{"tf":2.6457513110645907},"182":{"tf":2.0},"183":{"tf":2.6457513110645907},"184":{"tf":3.0},"185":{"tf":1.4142135623730951},"187":{"tf":1.0},"204":{"tf":1.7320508075688772},"205":{"tf":1.0},"206":{"tf":1.4142135623730951},"208":{"tf":1.4142135623730951},"212":{"tf":1.0},"218":{"tf":1.0},"22":{"tf":1.0},"233":{"tf":1.0},"274":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"204":{"tf":1.0}}},"df":0,"docs":{}}},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":6,"docs":{"0":{"tf":1.0},"177":{"tf":1.0},"178":{"tf":1.0},"184":{"tf":1.4142135623730951},"268":{"tf":1.0},"284":{"tf":1.0}}}}}}}}},"i":{"df":0,"docs":{},"r":{"df":1,"docs":{"277":{"tf":1.0}}}},"l":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"81":{"tf":1.0}}}},"n":{"df":1,"docs":{"7":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":4,"docs":{"128":{"tf":1.0},"138":{"tf":1.0},"224":{"tf":1.0},"241":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":4,"docs":{"115":{"tf":1.0},"201":{"tf":1.0},"64":{"tf":1.0},"7":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":3,"docs":{"11":{"tf":1.4142135623730951},"252":{"tf":1.0},"37":{"tf":2.6457513110645907}}}},"s":{"df":29,"docs":{"101":{"tf":1.0},"103":{"tf":1.4142135623730951},"108":{"tf":1.0},"109":{"tf":1.0},"111":{"tf":1.4142135623730951},"116":{"tf":1.0},"146":{"tf":1.0},"159":{"tf":1.0},"182":{"tf":1.0},"192":{"tf":1.0},"196":{"tf":1.0},"198":{"tf":1.0},"200":{"tf":1.0},"221":{"tf":1.0},"226":{"tf":1.4142135623730951},"229":{"tf":1.0},"232":{"tf":2.23606797749979},"236":{"tf":1.0},"239":{"tf":1.0},"241":{"tf":1.0},"242":{"tf":1.4142135623730951},"253":{"tf":1.0},"268":{"tf":1.0},"281":{"tf":2.23606797749979},"69":{"tf":1.0},"77":{"tf":1.0},"78":{"tf":2.23606797749979},"83":{"tf":1.0},"90":{"tf":1.7320508075688772}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":2,"docs":{"104":{"tf":1.4142135623730951},"170":{"tf":1.0}}}}}}},"t":{".":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"r":{"c":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":1,"docs":{"22":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"37":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":5,"docs":{"257":{"tf":1.4142135623730951},"44":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"98":{"tf":1.4142135623730951}}},"t":{"df":1,"docs":{"115":{"tf":1.0}}}},"r":{"df":0,"docs":{},"n":{"df":8,"docs":{"103":{"tf":1.0},"111":{"tf":1.0},"114":{"tf":1.0},"145":{"tf":1.0},"167":{"tf":1.0},"242":{"tf":1.0},"260":{"tf":1.0},"7":{"tf":1.0}}}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"107":{"tf":1.0},"271":{"tf":1.0}}}},"df":1,"docs":{"115":{"tf":1.0}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":2,"docs":{"156":{"tf":1.0},"233":{"tf":1.0}}}}}}}},"f":{"a":{"c":{"df":0,"docs":{},"e":{"df":5,"docs":{"107":{"tf":1.0},"25":{"tf":1.0},"283":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0}}},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"125":{"tf":1.0}},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"138":{"tf":1.0}}}}}},"t":{"df":4,"docs":{"107":{"tf":1.0},"115":{"tf":1.0},"180":{"tf":1.0},"61":{"tf":1.0}},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"200":{"tf":1.0}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":16,"docs":{"104":{"tf":1.4142135623730951},"119":{"tf":1.0},"146":{"tf":1.0},"151":{"tf":1.0},"20":{"tf":1.0},"204":{"tf":1.0},"205":{"tf":1.0},"208":{"tf":1.0},"21":{"tf":1.0},"242":{"tf":1.0},"25":{"tf":1.0},"27":{"tf":1.0},"280":{"tf":1.0},"282":{"tf":1.0},"62":{"tf":1.0},"92":{"tf":1.0}},"u":{"df":0,"docs":{},"r":{"df":5,"docs":{"159":{"tf":1.4142135623730951},"274":{"tf":1.0},"282":{"tf":1.0},"40":{"tf":1.4142135623730951},"62":{"tf":1.0}},"e":{"df":0,"docs":{},"s":{"@":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":1,"docs":{"274":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}}},"r":{"df":1,"docs":{"116":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":3,"docs":{"158":{"tf":1.0},"241":{"tf":1.0},"7":{"tf":1.0}}}}}},"k":{"df":0,"docs":{},"e":{"df":2,"docs":{"138":{"tf":1.0},"203":{"tf":1.0}}}},"l":{"df":0,"docs":{},"s":{"df":2,"docs":{"60":{"tf":1.4142135623730951},"61":{"tf":1.4142135623730951}}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"r":{"df":2,"docs":{"102":{"tf":1.4142135623730951},"103":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}}},"q":{"0":{"df":2,"docs":{"60":{"tf":1.0},"61":{"tf":1.4142135623730951}}},"df":2,"docs":{"110":{"tf":1.4142135623730951},"85":{"tf":1.4142135623730951}}},"r":{"df":6,"docs":{"124":{"tf":1.0},"201":{"tf":1.0},"225":{"tf":1.0},"242":{"tf":1.0},"59":{"tf":1.0},"63":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"58":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"57":{"tf":1.4142135623730951}}}}}},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"205":{"tf":1.0}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":3,"docs":{"165":{"tf":1.0},"172":{"tf":1.0},"37":{"tf":1.0}}}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"180":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":12,"docs":{"103":{"tf":1.0},"124":{"tf":1.4142135623730951},"189":{"tf":1.0},"196":{"tf":1.0},"198":{"tf":1.0},"221":{"tf":1.0},"255":{"tf":1.0},"260":{"tf":1.0},"282":{"tf":1.0},"43":{"tf":1.0},"62":{"tf":1.7320508075688772},"69":{"tf":1.0}},"e":{"df":0,"docs":{},"s":{"=":{"[":{"\"":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"69":{"tf":1.0}}}}}}}}},"d":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":1,"docs":{"62":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":7,"docs":{"107":{"tf":1.0},"170":{"tf":1.0},"25":{"tf":1.0},"3":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"6":{"tf":1.0}}}},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"55":{"tf":1.0}}}}},"/":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"l":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"18":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"126":{"tf":1.0}}}}}},"df":22,"docs":{"10":{"tf":1.0},"108":{"tf":1.0},"11":{"tf":1.0},"12":{"tf":2.23606797749979},"126":{"tf":1.7320508075688772},"145":{"tf":1.0},"16":{"tf":1.7320508075688772},"17":{"tf":1.4142135623730951},"18":{"tf":1.4142135623730951},"185":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.0},"200":{"tf":1.0},"21":{"tf":1.4142135623730951},"22":{"tf":1.0},"224":{"tf":1.0},"257":{"tf":1.7320508075688772},"38":{"tf":1.0},"39":{"tf":1.7320508075688772},"40":{"tf":1.7320508075688772},"51":{"tf":1.0},"55":{"tf":1.7320508075688772}}}},"n":{"df":0,"docs":{},"e":{"c":{"df":1,"docs":{"145":{"tf":1.0}}},"df":0,"docs":{}}}},"t":{"c":{"df":0,"docs":{},"h":{"df":6,"docs":{"189":{"tf":1.0},"190":{"tf":1.4142135623730951},"232":{"tf":1.0},"245":{"tf":1.0},"249":{"tf":1.7320508075688772},"274":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"w":{"df":3,"docs":{"58":{"tf":1.0},"6":{"tf":1.0},"68":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"59":{"tf":1.0}}}}}},"f":{"df":0,"docs":{},"i":{"_":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"73":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"73":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"116":{"tf":1.0}}}}}}}}}},"df":13,"docs":{"101":{"tf":1.0},"103":{"tf":3.1622776601683795},"104":{"tf":1.0},"105":{"tf":1.7320508075688772},"106":{"tf":1.7320508075688772},"115":{"tf":1.0},"116":{"tf":2.0},"193":{"tf":1.0},"241":{"tf":1.0},"43":{"tf":1.0},"44":{"tf":2.0},"51":{"tf":1.0},"90":{"tf":1.7320508075688772}}}},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"d":{"df":5,"docs":{"140":{"tf":1.4142135623730951},"145":{"tf":2.0},"249":{"tf":1.0},"250":{"tf":1.0},"83":{"tf":1.0}}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"e":{":":{"/":{"/":{"/":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"q":{"/":{"c":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"e":{"/":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"31":{"tf":1.0},"36":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":63,"docs":{"1":{"tf":1.4142135623730951},"103":{"tf":1.7320508075688772},"104":{"tf":2.6457513110645907},"105":{"tf":2.6457513110645907},"106":{"tf":2.23606797749979},"107":{"tf":1.0},"108":{"tf":1.7320508075688772},"109":{"tf":2.0},"111":{"tf":1.0},"114":{"tf":1.0},"117":{"tf":1.4142135623730951},"12":{"tf":1.0},"13":{"tf":1.0},"133":{"tf":1.0},"137":{"tf":1.0},"151":{"tf":1.4142135623730951},"152":{"tf":1.4142135623730951},"157":{"tf":1.4142135623730951},"158":{"tf":2.0},"159":{"tf":1.0},"165":{"tf":1.0},"167":{"tf":1.4142135623730951},"17":{"tf":1.0},"18":{"tf":1.0},"187":{"tf":1.0},"189":{"tf":1.0},"200":{"tf":1.0},"201":{"tf":1.0},"22":{"tf":1.4142135623730951},"220":{"tf":1.0},"221":{"tf":1.0},"222":{"tf":1.0},"229":{"tf":1.0},"25":{"tf":1.4142135623730951},"251":{"tf":1.0},"252":{"tf":2.0},"26":{"tf":1.4142135623730951},"263":{"tf":1.0},"271":{"tf":1.7320508075688772},"274":{"tf":1.0},"279":{"tf":1.4142135623730951},"28":{"tf":1.4142135623730951},"282":{"tf":1.7320508075688772},"284":{"tf":1.0},"29":{"tf":1.0},"31":{"tf":1.0},"33":{"tf":1.4142135623730951},"36":{"tf":1.0},"4":{"tf":1.0},"40":{"tf":1.0},"45":{"tf":1.0},"60":{"tf":1.7320508075688772},"61":{"tf":2.449489742783178},"64":{"tf":1.0},"65":{"tf":1.0},"69":{"tf":1.0},"70":{"tf":2.449489742783178},"72":{"tf":1.0},"73":{"tf":3.0},"74":{"tf":1.0},"79":{"tf":1.0},"81":{"tf":1.4142135623730951},"98":{"tf":1.7320508075688772}},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":1,"docs":{"25":{"tf":1.0}}}}}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"126":{"tf":1.0}}}}}},"n":{"a":{"df":0,"docs":{},"l":{"df":13,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"128":{"tf":1.0},"200":{"tf":1.0},"201":{"tf":1.0},"205":{"tf":1.0},"214":{"tf":1.0},"245":{"tf":1.0},"267":{"tf":1.0},"31":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"61":{"tf":1.0},"73":{"tf":1.0}}}},"d":{"df":18,"docs":{"1":{"tf":1.0},"104":{"tf":1.0},"105":{"tf":1.0},"106":{"tf":2.23606797749979},"122":{"tf":1.0},"126":{"tf":1.0},"18":{"tf":1.0},"21":{"tf":1.0},"224":{"tf":1.0},"26":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.4142135623730951},"37":{"tf":1.0},"46":{"tf":1.0},"52":{"tf":1.0},"6":{"tf":1.7320508075688772},"62":{"tf":1.0},"82":{"tf":1.0}}},"df":0,"docs":{},"e":{"df":2,"docs":{"133":{"tf":1.0},"189":{"tf":1.0}}},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":3,"docs":{"212":{"tf":1.0},"264":{"tf":1.0},"58":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"e":{"df":1,"docs":{"68":{"tf":1.0}},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"x":{"/":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"u":{"df":1,"docs":{"271":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"a":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{":":{":":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"_":{"df":0,"docs":{},"j":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"283":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":1,"docs":{"283":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":3,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"283":{"tf":1.7320508075688772}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":52,"docs":{"0":{"tf":1.4142135623730951},"1":{"tf":1.4142135623730951},"10":{"tf":1.4142135623730951},"109":{"tf":1.0},"118":{"tf":2.0},"119":{"tf":2.23606797749979},"12":{"tf":1.0},"134":{"tf":1.0},"14":{"tf":1.4142135623730951},"145":{"tf":1.0},"15":{"tf":1.7320508075688772},"167":{"tf":1.0},"18":{"tf":1.7320508075688772},"187":{"tf":1.0},"20":{"tf":1.0},"200":{"tf":1.0},"201":{"tf":1.0},"202":{"tf":1.4142135623730951},"203":{"tf":1.4142135623730951},"206":{"tf":1.7320508075688772},"213":{"tf":1.0},"215":{"tf":1.0},"217":{"tf":1.0},"218":{"tf":2.23606797749979},"219":{"tf":1.0},"23":{"tf":2.449489742783178},"24":{"tf":1.4142135623730951},"25":{"tf":2.6457513110645907},"254":{"tf":3.1622776601683795},"255":{"tf":1.7320508075688772},"256":{"tf":1.0},"257":{"tf":2.0},"258":{"tf":1.0},"259":{"tf":1.0},"26":{"tf":1.7320508075688772},"260":{"tf":1.0},"268":{"tf":2.23606797749979},"27":{"tf":1.0},"272":{"tf":1.7320508075688772},"274":{"tf":1.0},"28":{"tf":1.0},"283":{"tf":1.7320508075688772},"284":{"tf":1.0},"29":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":2.23606797749979},"38":{"tf":1.0},"39":{"tf":1.0},"48":{"tf":1.0},"56":{"tf":1.7320508075688772},"64":{"tf":1.7320508075688772},"74":{"tf":1.7320508075688772}}}}}},"s":{"df":0,"docs":{},"t":{"df":30,"docs":{"10":{"tf":1.7320508075688772},"102":{"tf":1.7320508075688772},"104":{"tf":1.7320508075688772},"124":{"tf":1.0},"134":{"tf":1.0},"137":{"tf":1.0},"146":{"tf":1.0},"152":{"tf":1.0},"176":{"tf":1.4142135623730951},"177":{"tf":1.4142135623730951},"178":{"tf":1.0},"179":{"tf":2.23606797749979},"180":{"tf":1.4142135623730951},"182":{"tf":1.0},"183":{"tf":1.7320508075688772},"184":{"tf":1.7320508075688772},"185":{"tf":1.4142135623730951},"188":{"tf":1.0},"20":{"tf":1.0},"214":{"tf":1.0},"230":{"tf":1.4142135623730951},"245":{"tf":1.0},"25":{"tf":1.0},"280":{"tf":1.0},"283":{"tf":1.0},"6":{"tf":2.0},"62":{"tf":1.0},"81":{"tf":1.0},"88":{"tf":1.0},"89":{"tf":1.0}}}}},"t":{"df":4,"docs":{"102":{"tf":1.0},"138":{"tf":1.0},"226":{"tf":1.0},"229":{"tf":1.0}}},"x":{"df":8,"docs":{"116":{"tf":1.0},"118":{"tf":1.0},"119":{"tf":1.0},"152":{"tf":1.0},"21":{"tf":1.4142135623730951},"282":{"tf":1.4142135623730951},"58":{"tf":1.0},"59":{"tf":1.0}},"u":{"df":0,"docs":{},"p":{"df":3,"docs":{"145":{"tf":2.0},"146":{"tf":1.0},"147":{"tf":1.0}}}}}},"k":{"df":1,"docs":{"62":{"tf":1.0}}},"l":{"a":{"df":0,"docs":{},"g":{"df":2,"docs":{"175":{"tf":1.4142135623730951},"64":{"tf":1.0}}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":1,"docs":{"48":{"tf":1.0}}}}},"t":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":1,"docs":{"37":{"tf":1.0}}}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"111":{"tf":1.0}}},"r":{"df":1,"docs":{"83":{"tf":1.0}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"200":{"tf":1.0}}}},"x":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"l":{"df":5,"docs":{"126":{"tf":1.0},"178":{"tf":1.0},"182":{"tf":1.0},"184":{"tf":1.0},"261":{"tf":1.0}}}},"df":0,"docs":{}}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":1,"docs":{"201":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"w":{"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"200":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":29,"docs":{"118":{"tf":1.0},"16":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":1.0},"183":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.0},"21":{"tf":1.0},"22":{"tf":1.0},"23":{"tf":1.0},"24":{"tf":1.0},"244":{"tf":1.7320508075688772},"25":{"tf":1.7320508075688772},"26":{"tf":1.0},"27":{"tf":2.0},"274":{"tf":1.0},"275":{"tf":1.0},"276":{"tf":1.0},"277":{"tf":1.0},"28":{"tf":1.0},"283":{"tf":1.0},"29":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.0},"34":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0}}}}},"m":{"df":0,"docs":{},"l":{"df":1,"docs":{"271":{"tf":1.0}}}},"n":{"df":2,"docs":{"242":{"tf":2.449489742783178},"94":{"tf":1.0}}},"o":{"c":{"df":0,"docs":{},"u":{"df":10,"docs":{"118":{"tf":1.4142135623730951},"230":{"tf":1.0},"255":{"tf":1.0},"257":{"tf":1.0},"32":{"tf":2.23606797749979},"33":{"tf":1.7320508075688772},"34":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":2.449489742783178},"74":{"tf":1.4142135623730951}},"s":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"74":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"u":{"d":{"df":0,"docs":{},"l":{"_":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"74":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"36":{"tf":1.0}}},"df":0,"docs":{}}}}}}}}},"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"34":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"df":1,"docs":{"188":{"tf":1.0}},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"x":{"c":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{".":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":2,"docs":{"33":{"tf":1.0},"34":{"tf":1.4142135623730951}}}}}},"df":1,"docs":{"34":{"tf":1.4142135623730951}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"l":{"df":1,"docs":{"34":{"tf":1.0}}}}}}}},"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"201":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":5,"docs":{"11":{"tf":1.0},"13":{"tf":1.0},"279":{"tf":1.0},"61":{"tf":1.4142135623730951},"62":{"tf":1.0}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":59,"docs":{"108":{"tf":1.0},"109":{"tf":1.4142135623730951},"11":{"tf":1.4142135623730951},"116":{"tf":1.0},"118":{"tf":1.0},"12":{"tf":1.4142135623730951},"122":{"tf":1.0},"123":{"tf":1.4142135623730951},"124":{"tf":1.0},"128":{"tf":1.0},"136":{"tf":1.0},"145":{"tf":1.0},"152":{"tf":1.0},"18":{"tf":1.0},"189":{"tf":1.0},"191":{"tf":1.0},"197":{"tf":1.0},"203":{"tf":1.0},"204":{"tf":1.7320508075688772},"21":{"tf":1.0},"23":{"tf":1.0},"230":{"tf":1.0},"233":{"tf":1.0},"247":{"tf":1.0},"25":{"tf":1.4142135623730951},"251":{"tf":1.0},"252":{"tf":1.0},"253":{"tf":1.4142135623730951},"257":{"tf":1.0},"258":{"tf":1.0},"259":{"tf":1.0},"268":{"tf":2.23606797749979},"27":{"tf":1.4142135623730951},"279":{"tf":1.0},"28":{"tf":1.4142135623730951},"280":{"tf":1.0},"281":{"tf":1.0},"29":{"tf":1.7320508075688772},"30":{"tf":1.0},"31":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.4142135623730951},"34":{"tf":1.7320508075688772},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"43":{"tf":1.0},"45":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.4142135623730951},"67":{"tf":1.0},"69":{"tf":1.4142135623730951},"7":{"tf":1.0},"70":{"tf":1.4142135623730951},"73":{"tf":1.7320508075688772},"74":{"tf":1.0},"93":{"tf":1.4142135623730951}},"u":{"df":0,"docs":{},"p":{"df":1,"docs":{"241":{"tf":1.4142135623730951}}}}}}}},"o":{"'":{"df":1,"docs":{"58":{"tf":1.0}}},".":{"df":0,"docs":{},"r":{"df":1,"docs":{"117":{"tf":1.0}}}},"b":{"a":{"df":0,"docs":{},"r":{".":{"df":0,"docs":{},"k":{"df":0,"docs":{},"t":{"df":1,"docs":{"100":{"tf":1.0}}}}},"df":1,"docs":{"100":{"tf":1.0}}}},"df":0,"docs":{}},"df":3,"docs":{"117":{"tf":1.0},"58":{"tf":1.4142135623730951},"60":{"tf":1.0}}},"r":{"c":{"df":5,"docs":{"184":{"tf":1.4142135623730951},"237":{"tf":1.0},"25":{"tf":1.7320508075688772},"26":{"tf":1.0},"274":{"tf":1.0}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":1,"docs":{"264":{"tf":1.0}}}}}},"df":5,"docs":{"101":{"tf":1.0},"106":{"tf":1.0},"128":{"tf":1.4142135623730951},"69":{"tf":1.0},"82":{"tf":1.0}}}}}},"k":{"df":4,"docs":{"196":{"tf":1.0},"197":{"tf":1.0},"274":{"tf":1.0},"7":{"tf":1.4142135623730951}}},"m":{"a":{"df":0,"docs":{},"t":{"df":11,"docs":{"136":{"tf":1.0},"137":{"tf":1.0},"138":{"tf":1.0},"163":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"22":{"tf":1.4142135623730951},"252":{"tf":1.4142135623730951},"253":{"tf":1.0},"268":{"tf":1.0},"37":{"tf":1.0}},"t":{"df":1,"docs":{"7":{"tf":1.0}}}}},"df":3,"docs":{"165":{"tf":1.0},"232":{"tf":1.0},"242":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"229":{"tf":1.0}}}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"137":{"tf":1.0}}}}}}},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"225":{"tf":1.0}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"j":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"223":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}},"w":{"a":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"n":{"d":{"df":43,"docs":{"100":{"tf":1.0},"109":{"tf":1.0},"117":{"tf":1.4142135623730951},"119":{"tf":1.0},"124":{"tf":1.0},"127":{"tf":1.0},"133":{"tf":1.0},"138":{"tf":1.0},"147":{"tf":1.0},"15":{"tf":1.4142135623730951},"159":{"tf":1.0},"175":{"tf":1.0},"185":{"tf":1.0},"201":{"tf":1.0},"203":{"tf":1.0},"218":{"tf":1.0},"22":{"tf":1.4142135623730951},"224":{"tf":1.0},"242":{"tf":1.0},"250":{"tf":1.0},"253":{"tf":1.0},"258":{"tf":1.0},"259":{"tf":1.0},"263":{"tf":1.0},"264":{"tf":1.0},"271":{"tf":1.0},"273":{"tf":1.0},"277":{"tf":1.0},"282":{"tf":1.0},"283":{"tf":1.0},"286":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"40":{"tf":1.0},"51":{"tf":1.0},"56":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0},"67":{"tf":1.0},"74":{"tf":1.0},"8":{"tf":1.0},"90":{"tf":1.0}}},"df":0,"docs":{}},"r":{"df":1,"docs":{"103":{"tf":1.0}}}}},"r":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":1,"docs":{"131":{"tf":1.0}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"y":{"df":1,"docs":{"131":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}}},"df":1,"docs":{"131":{"tf":1.0}}}}}}},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":8,"docs":{"161":{"tf":1.0},"166":{"tf":1.0},"167":{"tf":1.4142135623730951},"175":{"tf":1.0},"252":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.4142135623730951},"77":{"tf":1.7320508075688772}}}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":11,"docs":{"105":{"tf":1.0},"128":{"tf":1.4142135623730951},"225":{"tf":1.0},"233":{"tf":1.0},"247":{"tf":1.0},"25":{"tf":1.0},"3":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"6":{"tf":1.0},"81":{"tf":1.0}}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"60":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"149":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"11":{"tf":1.0}}}}}}},"i":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"274":{"tf":1.0}}}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"j":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"100":{"tf":1.0}}}}}}}}}},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"166":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"169":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":14,"docs":{"105":{"tf":1.0},"122":{"tf":1.0},"149":{"tf":1.0},"154":{"tf":1.0},"217":{"tf":1.0},"221":{"tf":1.7320508075688772},"222":{"tf":1.4142135623730951},"223":{"tf":1.4142135623730951},"224":{"tf":1.4142135623730951},"252":{"tf":1.0},"268":{"tf":2.0},"31":{"tf":1.4142135623730951},"36":{"tf":1.4142135623730951},"7":{"tf":1.0}},"i":{"df":3,"docs":{"21":{"tf":1.0},"226":{"tf":1.0},"274":{"tf":1.0}}}}},"n":{"c":{"df":1,"docs":{"97":{"tf":1.0}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":39,"docs":{"103":{"tf":2.449489742783178},"104":{"tf":1.0},"105":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"111":{"tf":1.0},"129":{"tf":2.0},"140":{"tf":1.0},"142":{"tf":1.4142135623730951},"143":{"tf":1.4142135623730951},"145":{"tf":4.358898943540674},"146":{"tf":1.4142135623730951},"158":{"tf":1.4142135623730951},"199":{"tf":1.0},"200":{"tf":1.0},"221":{"tf":1.0},"222":{"tf":1.0},"232":{"tf":1.0},"234":{"tf":1.4142135623730951},"242":{"tf":1.0},"245":{"tf":2.0},"264":{"tf":1.0},"281":{"tf":2.449489742783178},"283":{"tf":1.0},"42":{"tf":1.4142135623730951},"45":{"tf":1.0},"46":{"tf":1.0},"48":{"tf":1.0},"54":{"tf":1.0},"60":{"tf":1.0},"62":{"tf":2.0},"64":{"tf":1.4142135623730951},"77":{"tf":1.4142135623730951},"78":{"tf":2.23606797749979},"79":{"tf":1.4142135623730951},"81":{"tf":1.0},"82":{"tf":1.7320508075688772},"93":{"tf":1.0},"99":{"tf":1.0}},"s":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"283":{"tf":1.0}}}}}}}}}}}}}}}}}}}},"d":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"61":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"100":{"tf":1.0}}},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"174":{"tf":1.4142135623730951}}}}}}},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":20,"docs":{"107":{"tf":1.0},"126":{"tf":1.0},"140":{"tf":1.0},"152":{"tf":1.0},"167":{"tf":2.0},"190":{"tf":1.0},"196":{"tf":1.0},"198":{"tf":1.4142135623730951},"201":{"tf":1.0},"225":{"tf":1.0},"232":{"tf":1.0},"233":{"tf":1.0},"241":{"tf":1.0},"242":{"tf":1.0},"252":{"tf":1.0},"258":{"tf":2.0},"261":{"tf":1.0},"264":{"tf":1.4142135623730951},"268":{"tf":1.0},"38":{"tf":1.0}}}}},"z":{"df":0,"docs":{},"z":{"df":1,"docs":{"51":{"tf":1.0}}}}},"x":{"a":{"_":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"283":{"tf":1.0}},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"c":{"df":1,"docs":{"283":{"tf":1.0}}},"df":0,"docs":{}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":1,"docs":{"283":{"tf":1.0}}}}}}}}}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"221":{"tf":1.0}}}}}}}},"df":13,"docs":{"105":{"tf":1.0},"107":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"226":{"tf":1.4142135623730951},"231":{"tf":1.7320508075688772},"237":{"tf":1.0},"242":{"tf":1.7320508075688772},"254":{"tf":1.0},"256":{"tf":1.4142135623730951},"257":{"tf":1.0},"283":{"tf":1.0},"58":{"tf":1.4142135623730951}},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"s":{"a":{"d":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"283":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"56":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"g":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"101":{"tf":1.0}}}},"r":{"b":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"128":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"e":{"df":1,"docs":{"62":{"tf":1.0}}}}},"c":{"c":{"df":1,"docs":{"37":{"tf":1.0}}},"df":1,"docs":{"115":{"tf":1.4142135623730951}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":1,"docs":{"132":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}}},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":1,"docs":{"192":{"tf":1.0}}}}}}}}},"df":0,"docs":{},"m":{"df":1,"docs":{"14":{"tf":1.0}}},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":2,"docs":{"30":{"tf":1.0},"35":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":59,"docs":{"101":{"tf":1.0},"104":{"tf":1.7320508075688772},"105":{"tf":1.7320508075688772},"106":{"tf":1.0},"107":{"tf":1.4142135623730951},"108":{"tf":1.0},"109":{"tf":1.7320508075688772},"115":{"tf":1.0},"116":{"tf":1.4142135623730951},"120":{"tf":1.0},"122":{"tf":1.0},"123":{"tf":1.7320508075688772},"125":{"tf":1.0},"132":{"tf":1.0},"134":{"tf":1.0},"14":{"tf":1.0},"142":{"tf":1.0},"167":{"tf":1.0},"194":{"tf":1.0},"201":{"tf":1.4142135623730951},"220":{"tf":1.0},"221":{"tf":1.0},"225":{"tf":1.0},"227":{"tf":1.0},"228":{"tf":1.0},"23":{"tf":1.0},"232":{"tf":1.4142135623730951},"242":{"tf":1.7320508075688772},"243":{"tf":1.0},"244":{"tf":1.7320508075688772},"245":{"tf":1.0},"247":{"tf":1.0},"253":{"tf":1.7320508075688772},"254":{"tf":1.4142135623730951},"266":{"tf":1.0},"267":{"tf":1.0},"268":{"tf":1.0},"271":{"tf":1.0},"274":{"tf":1.0},"277":{"tf":1.0},"29":{"tf":1.0},"30":{"tf":1.4142135623730951},"34":{"tf":1.0},"35":{"tf":1.4142135623730951},"44":{"tf":1.0},"45":{"tf":1.0},"51":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.7320508075688772},"69":{"tf":1.0},"70":{"tf":1.0},"72":{"tf":2.23606797749979},"73":{"tf":2.23606797749979},"74":{"tf":1.0},"75":{"tf":1.0},"76":{"tf":1.4142135623730951},"82":{"tf":1.0},"83":{"tf":1.4142135623730951},"90":{"tf":1.0}}}}},"t":{"_":{"a":{"c":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"283":{"tf":1.0}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"df":1,"docs":{"145":{"tf":1.7320508075688772}}}},"b":{"df":0,"docs":{},"y":{"_":{"b":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"_":{"d":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"145":{"tf":1.7320508075688772}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"145":{"tf":1.7320508075688772}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"283":{"tf":1.0}}}}}}}},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"_":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"c":{"df":1,"docs":{"87":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":12,"docs":{"105":{"tf":1.0},"116":{"tf":1.0},"17":{"tf":1.0},"227":{"tf":1.0},"236":{"tf":1.0},"245":{"tf":1.0},"253":{"tf":1.0},"271":{"tf":1.4142135623730951},"282":{"tf":1.0},"60":{"tf":1.0},"62":{"tf":1.0},"82":{"tf":1.0}}}},"h":{"df":1,"docs":{"286":{"tf":1.0}}},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"13":{"tf":1.0}}}},"t":{"df":9,"docs":{"11":{"tf":1.7320508075688772},"123":{"tf":1.0},"167":{"tf":2.0},"253":{"tf":1.4142135623730951},"26":{"tf":1.0},"268":{"tf":2.23606797749979},"269":{"tf":1.0},"271":{"tf":1.0},"34":{"tf":1.0}},"h":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"274":{"tf":1.0}}},"df":0,"docs":{}},"u":{"b":{"'":{"df":3,"docs":{"274":{"tf":1.0},"277":{"tf":1.0},"7":{"tf":1.0}}},"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"277":{"tf":1.0}}}}}}}},"df":52,"docs":{"1":{"tf":1.4142135623730951},"100":{"tf":1.0},"109":{"tf":1.0},"117":{"tf":1.0},"119":{"tf":1.4142135623730951},"124":{"tf":1.0},"127":{"tf":1.0},"133":{"tf":1.0},"138":{"tf":1.0},"147":{"tf":1.0},"15":{"tf":1.0},"159":{"tf":1.0},"175":{"tf":1.0},"185":{"tf":1.0},"193":{"tf":1.0},"201":{"tf":1.0},"218":{"tf":1.0},"22":{"tf":1.0},"224":{"tf":1.0},"242":{"tf":1.0},"250":{"tf":1.0},"253":{"tf":1.4142135623730951},"258":{"tf":1.4142135623730951},"259":{"tf":1.0},"263":{"tf":1.0},"264":{"tf":1.0},"268":{"tf":1.0},"272":{"tf":1.4142135623730951},"273":{"tf":1.0},"274":{"tf":2.23606797749979},"276":{"tf":1.7320508075688772},"277":{"tf":1.4142135623730951},"282":{"tf":1.0},"283":{"tf":1.0},"286":{"tf":1.0},"3":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.4142135623730951},"39":{"tf":1.0},"4":{"tf":1.0},"40":{"tf":1.0},"50":{"tf":1.0},"51":{"tf":1.0},"56":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0},"67":{"tf":1.0},"7":{"tf":1.0},"74":{"tf":1.0},"8":{"tf":1.0},"90":{"tf":1.0}}},"df":0,"docs":{}}}},"v":{"df":0,"docs":{},"e":{"df":2,"docs":{"174":{"tf":1.0},"41":{"tf":1.0}},"n":{"df":14,"docs":{"115":{"tf":1.0},"156":{"tf":1.0},"167":{"tf":1.0},"172":{"tf":1.0},"197":{"tf":1.0},"205":{"tf":1.0},"208":{"tf":1.0},"225":{"tf":1.0},"260":{"tf":1.0},"276":{"tf":1.0},"43":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"64":{"tf":1.0}}}}}},"l":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"n":{"df":11,"docs":{"162":{"tf":1.0},"167":{"tf":2.0},"169":{"tf":1.4142135623730951},"170":{"tf":1.4142135623730951},"171":{"tf":1.4142135623730951},"172":{"tf":1.0},"174":{"tf":1.4142135623730951},"200":{"tf":1.0},"219":{"tf":1.0},"253":{"tf":1.0},"259":{"tf":1.7320508075688772}}}},"df":0,"docs":{}},"o":{"b":{"a":{"df":0,"docs":{},"l":{"df":3,"docs":{"115":{"tf":1.0},"225":{"tf":1.0},"86":{"tf":2.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"o":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"103":{"tf":1.0},"23":{"tf":1.0}}}},"df":14,"docs":{"105":{"tf":1.0},"115":{"tf":1.0},"125":{"tf":1.0},"126":{"tf":1.0},"226":{"tf":1.0},"229":{"tf":1.0},"233":{"tf":1.0},"25":{"tf":1.4142135623730951},"26":{"tf":1.0},"37":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0},"73":{"tf":1.0},"81":{"tf":1.7320508075688772}},"e":{"df":5,"docs":{"103":{"tf":1.4142135623730951},"18":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"62":{"tf":1.4142135623730951}}},"n":{"df":0,"docs":{},"e":{"df":1,"docs":{"158":{"tf":1.0}}}},"o":{"d":{"df":29,"docs":{"107":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"132":{"tf":1.0},"150":{"tf":1.0},"154":{"tf":1.7320508075688772},"155":{"tf":2.0},"156":{"tf":1.7320508075688772},"157":{"tf":1.0},"158":{"tf":1.4142135623730951},"159":{"tf":1.4142135623730951},"169":{"tf":1.0},"170":{"tf":1.7320508075688772},"171":{"tf":2.0},"172":{"tf":2.8284271247461903},"174":{"tf":1.0},"182":{"tf":1.7320508075688772},"183":{"tf":1.4142135623730951},"184":{"tf":1.7320508075688772},"19":{"tf":1.0},"194":{"tf":1.0},"212":{"tf":1.0},"213":{"tf":1.4142135623730951},"214":{"tf":1.0},"225":{"tf":1.0},"37":{"tf":1.0},"6":{"tf":2.0},"82":{"tf":1.0},"90":{"tf":1.0}}},"df":0,"docs":{}},"t":{"c":{"df":0,"docs":{},"h":{"a":{"df":1,"docs":{"224":{"tf":1.4142135623730951}}},"df":0,"docs":{},"y":{"a":{"df":1,"docs":{"228":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":1,"docs":{"3":{"tf":1.0}}}}}}},"p":{"df":0,"docs":{},"g":{"df":1,"docs":{"7":{"tf":1.0}}}},"r":{"a":{"d":{"df":0,"docs":{},"l":{"df":8,"docs":{"22":{"tf":1.4142135623730951},"220":{"tf":1.0},"274":{"tf":1.7320508075688772},"37":{"tf":1.4142135623730951},"40":{"tf":1.0},"49":{"tf":1.0},"65":{"tf":1.0},"70":{"tf":1.4142135623730951}},"e":{"'":{"df":3,"docs":{"22":{"tf":1.0},"221":{"tf":1.0},"222":{"tf":1.0}}},".":{"\"":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{".":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"22":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"(":{"'":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{".":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"22":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"w":{"df":7,"docs":{"108":{"tf":1.4142135623730951},"13":{"tf":1.0},"18":{"tf":1.4142135623730951},"20":{"tf":1.0},"22":{"tf":1.4142135623730951},"45":{"tf":1.0},"70":{"tf":1.4142135623730951}}}}},"u":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"101":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":3,"docs":{"221":{"tf":1.7320508075688772},"58":{"tf":1.4142135623730951},"59":{"tf":1.0}},"i":{"c":{"df":1,"docs":{"126":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":3,"docs":{"282":{"tf":1.0},"52":{"tf":1.0},"75":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"210":{"tf":1.0}}}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"a":{"df":1,"docs":{"237":{"tf":1.0}}},"df":0,"docs":{}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":4,"docs":{"189":{"tf":1.0},"37":{"tf":1.0},"40":{"tf":1.0},"73":{"tf":2.0}}}},"w":{"df":2,"docs":{"199":{"tf":1.0},"208":{"tf":1.0}},"n":{"df":1,"docs":{"64":{"tf":1.0}}}}}},"t":{"df":1,"docs":{"200":{"tf":2.8284271247461903}}},"u":{"a":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":1,"docs":{"201":{"tf":1.0}}}}}},"d":{"df":2,"docs":{"274":{"tf":1.0},"64":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"i":{"d":{"a":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"75":{"tf":1.0}}},"df":0,"docs":{}}},"df":19,"docs":{"101":{"tf":1.0},"102":{"tf":1.4142135623730951},"11":{"tf":1.0},"23":{"tf":1.7320508075688772},"231":{"tf":1.0},"245":{"tf":1.4142135623730951},"248":{"tf":1.7320508075688772},"278":{"tf":1.7320508075688772},"32":{"tf":1.7320508075688772},"37":{"tf":1.0},"41":{"tf":1.4142135623730951},"62":{"tf":3.3166247903554},"69":{"tf":1.0},"7":{"tf":1.4142135623730951},"75":{"tf":2.0},"86":{"tf":1.0},"87":{"tf":2.0},"93":{"tf":1.0},"99":{"tf":1.0}},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":10,"docs":{"128":{"tf":1.0},"274":{"tf":1.4142135623730951},"3":{"tf":1.0},"43":{"tf":1.0},"45":{"tf":1.0},"63":{"tf":1.7320508075688772},"69":{"tf":1.0},"7":{"tf":1.0},"91":{"tf":1.0},"96":{"tf":1.0}}}}}}},"df":1,"docs":{"109":{"tf":1.0}}}},"y":{"df":0,"docs":{},"p":{"df":2,"docs":{"11":{"tf":1.7320508075688772},"282":{"tf":1.4142135623730951}}}}},"h":{"a":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"45":{"tf":1.0}}}}},"i":{"df":2,"docs":{"107":{"tf":1.0},"37":{"tf":1.0}}},"y":{"_":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"_":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"(":{"\"":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"37":{"tf":1.0}}}},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":1,"docs":{"37":{"tf":1.0}}}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"s":{"df":0,"docs":{},"g":{"df":1,"docs":{"37":{"tf":1.0}}}}}},"df":1,"docs":{"37":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{},"n":{"d":{"df":20,"docs":{"101":{"tf":1.0},"103":{"tf":2.8284271247461903},"104":{"tf":2.0},"106":{"tf":1.0},"108":{"tf":2.23606797749979},"109":{"tf":2.23606797749979},"111":{"tf":1.0},"116":{"tf":1.0},"224":{"tf":1.0},"236":{"tf":1.0},"249":{"tf":1.0},"252":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"70":{"tf":1.4142135623730951},"72":{"tf":1.4142135623730951},"73":{"tf":1.4142135623730951},"74":{"tf":1.0},"78":{"tf":1.0},"90":{"tf":1.0}},"l":{"df":16,"docs":{"128":{"tf":1.0},"130":{"tf":1.0},"133":{"tf":1.0},"134":{"tf":1.0},"146":{"tf":1.0},"148":{"tf":1.4142135623730951},"228":{"tf":1.0},"233":{"tf":1.0},"243":{"tf":1.0},"244":{"tf":1.0},"245":{"tf":1.0},"272":{"tf":1.0},"40":{"tf":1.0},"63":{"tf":1.0},"79":{"tf":1.0},"86":{"tf":1.0}}}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":11,"docs":{"106":{"tf":1.0},"133":{"tf":1.7320508075688772},"154":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0},"225":{"tf":1.0},"230":{"tf":1.0},"242":{"tf":1.0},"26":{"tf":1.0},"273":{"tf":1.0},"283":{"tf":1.0}}}},"i":{"df":2,"docs":{"124":{"tf":1.0},"263":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"170":{"tf":1.0}}}}}}},"r":{"d":{"df":7,"docs":{"104":{"tf":1.0},"158":{"tf":1.0},"189":{"tf":1.0},"200":{"tf":1.0},"213":{"tf":1.0},"233":{"tf":1.0},"241":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"166":{"tf":1.0}}}},"w":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"282":{"tf":1.0}}}},"df":0,"docs":{}}},"df":2,"docs":{"48":{"tf":1.0},"60":{"tf":1.4142135623730951}},"m":{"df":2,"docs":{"201":{"tf":1.0},"225":{"tf":1.0}}}},"s":{"df":0,"docs":{},"h":{"df":3,"docs":{"268":{"tf":1.0},"274":{"tf":1.0},"64":{"tf":1.0}},"m":{"a":{"df":0,"docs":{},"p":{"<":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"242":{"tf":1.0}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"n":{"'":{"df":0,"docs":{},"t":{"df":2,"docs":{"154":{"tf":1.0},"155":{"tf":1.0}}}},"df":0,"docs":{},"’":{"df":0,"docs":{},"t":{"df":1,"docs":{"194":{"tf":1.0}}}}}},"v":{"df":0,"docs":{},"e":{"df":12,"docs":{"105":{"tf":1.0},"126":{"tf":1.0},"146":{"tf":1.0},"166":{"tf":1.0},"174":{"tf":1.0},"183":{"tf":1.0},"215":{"tf":1.0},"256":{"tf":1.0},"46":{"tf":1.0},"60":{"tf":1.0},"82":{"tf":1.0},"86":{"tf":1.0}},"n":{"'":{"df":0,"docs":{},"t":{"df":4,"docs":{"20":{"tf":1.0},"274":{"tf":1.0},"36":{"tf":1.0},"60":{"tf":1.0}}}},"df":0,"docs":{},"’":{"df":0,"docs":{},"t":{"df":1,"docs":{"213":{"tf":1.0}}}}}}}},"df":0,"docs":{},"e":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":9,"docs":{"109":{"tf":1.0},"251":{"tf":1.0},"252":{"tf":1.4142135623730951},"268":{"tf":1.0},"269":{"tf":1.0},"274":{"tf":1.0},"28":{"tf":1.0},"33":{"tf":1.0},"73":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"242":{"tf":1.0}}}}},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"105":{"tf":1.0}}}}},"y":{"df":0,"docs":{},"w":{"df":0,"docs":{},"e":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":1,"docs":{"59":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"p":{"df":17,"docs":{"106":{"tf":1.0},"115":{"tf":1.0},"122":{"tf":1.0},"165":{"tf":1.0},"190":{"tf":1.0},"222":{"tf":1.0},"232":{"tf":1.0},"264":{"tf":1.0},"274":{"tf":1.4142135623730951},"278":{"tf":1.0},"282":{"tf":1.0},"3":{"tf":1.0},"37":{"tf":1.0},"59":{"tf":1.0},"62":{"tf":1.0},"75":{"tf":1.0},"90":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"222":{"tf":1.0},"43":{"tf":1.0}}}}}},"n":{"c":{"df":2,"docs":{"200":{"tf":1.0},"278":{"tf":1.0}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"e":{"'":{"df":1,"docs":{"37":{"tf":1.0}}},"df":30,"docs":{"103":{"tf":2.449489742783178},"108":{"tf":1.0},"109":{"tf":1.0},"117":{"tf":1.0},"123":{"tf":1.0},"171":{"tf":1.0},"20":{"tf":1.0},"200":{"tf":2.0},"201":{"tf":1.0},"210":{"tf":1.0},"225":{"tf":1.0},"229":{"tf":1.4142135623730951},"242":{"tf":1.0},"243":{"tf":1.0},"249":{"tf":1.0},"258":{"tf":1.0},"264":{"tf":1.0},"266":{"tf":1.4142135623730951},"274":{"tf":1.7320508075688772},"37":{"tf":1.0},"40":{"tf":1.0},"60":{"tf":1.4142135623730951},"69":{"tf":1.0},"70":{"tf":1.7320508075688772},"72":{"tf":1.4142135623730951},"76":{"tf":1.0},"81":{"tf":1.0},"82":{"tf":1.0},"84":{"tf":1.0},"90":{"tf":1.0}}}}},"i":{"d":{"df":0,"docs":{},"e":{"df":2,"docs":{"233":{"tf":1.0},"245":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":1,"docs":{"83":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"g":{"df":0,"docs":{},"h":{"df":26,"docs":{"103":{"tf":1.4142135623730951},"200":{"tf":1.0},"201":{"tf":1.0},"205":{"tf":1.0},"214":{"tf":1.0},"219":{"tf":1.7320508075688772},"22":{"tf":1.0},"225":{"tf":2.23606797749979},"226":{"tf":2.23606797749979},"229":{"tf":2.0},"23":{"tf":1.0},"230":{"tf":1.0},"232":{"tf":1.0},"233":{"tf":1.0},"236":{"tf":2.0},"237":{"tf":1.0},"239":{"tf":1.0},"243":{"tf":1.0},"244":{"tf":1.0},"245":{"tf":1.0},"251":{"tf":1.7320508075688772},"254":{"tf":2.0},"256":{"tf":1.0},"281":{"tf":1.0},"41":{"tf":1.0},"64":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"103":{"tf":1.4142135623730951},"209":{"tf":1.0}}}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":2,"docs":{"109":{"tf":1.0},"251":{"tf":1.0}}}}}}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"174":{"tf":1.0}},"i":{"df":22,"docs":{"134":{"tf":1.0},"202":{"tf":1.4142135623730951},"203":{"tf":1.4142135623730951},"204":{"tf":1.4142135623730951},"205":{"tf":2.8284271247461903},"206":{"tf":2.449489742783178},"207":{"tf":1.0},"208":{"tf":2.449489742783178},"209":{"tf":2.23606797749979},"210":{"tf":1.7320508075688772},"212":{"tf":2.0},"213":{"tf":2.0},"215":{"tf":1.7320508075688772},"217":{"tf":1.0},"218":{"tf":2.0},"232":{"tf":2.0},"233":{"tf":2.0},"242":{"tf":2.23606797749979},"257":{"tf":1.0},"264":{"tf":1.0},"283":{"tf":1.0},"83":{"tf":1.0}}}}}}},"t":{"df":3,"docs":{"189":{"tf":1.0},"201":{"tf":1.4142135623730951},"60":{"tf":1.0}}}},"o":{"c":{"df":1,"docs":{"59":{"tf":1.0}}},"df":0,"docs":{},"l":{"d":{"df":3,"docs":{"115":{"tf":1.0},"132":{"tf":1.0},"245":{"tf":1.0}}},"df":0,"docs":{}},"m":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":1,"docs":{"11":{"tf":1.7320508075688772}}}}}},"df":1,"docs":{"64":{"tf":1.0}}}},"o":{"df":0,"docs":{},"k":{"df":1,"docs":{"119":{"tf":1.0}}}},"p":{"df":0,"docs":{},"e":{"df":1,"docs":{"225":{"tf":1.4142135623730951}},"f":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"242":{"tf":1.0}}}}}}}}},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"190":{"tf":1.0}}}},"u":{"df":0,"docs":{},"r":{"df":2,"docs":{"10":{"tf":1.0},"64":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"=":{"\"":{".":{".":{"/":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"c":{".":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"/":{"d":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"283":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":1,"docs":{"283":{"tf":1.0}}}}}}},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"283":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":1,"docs":{"284":{"tf":1.0}}}},"t":{"df":0,"docs":{},"p":{"df":4,"docs":{"189":{"tf":1.0},"190":{"tf":1.4142135623730951},"201":{"tf":1.4142135623730951},"274":{"tf":1.0}},"s":{":":{"/":{"/":{"a":{"d":{"df":0,"docs":{},"r":{".":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"u":{"b":{".":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"/":{"df":0,"docs":{},"m":{"a":{"d":{"df":0,"docs":{},"r":{"df":1,"docs":{"134":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":1,"docs":{"134":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{".":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"i":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"/":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"/":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"u":{"b":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"/":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"277":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"a":{".":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"/":{"df":0,"docs":{},"e":{"df":0,"docs":{},"z":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"11":{"tf":1.0}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{".":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"e":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"/":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"l":{"/":{"df":0,"docs":{},"g":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{".":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"11":{"tf":1.0}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}}}},"df":0,"docs":{}}}}}}}}},"d":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"/":{"d":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"/":{"d":{"/":{"1":{"df":0,"docs":{},"q":{"df":0,"docs":{},"w":{"3":{"6":{"_":{"7":{"df":0,"docs":{},"g":{"6":{"df":0,"docs":{},"x":{"df":0,"docs":{},"y":{"df":0,"docs":{},"h":{"df":0,"docs":{},"v":{"df":0,"docs":{},"j":{"df":0,"docs":{},"z":{"d":{"df":0,"docs":{},"m":{"df":1,"docs":{"177":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"x":{"1":{"df":0,"docs":{},"h":{"c":{"3":{"df":0,"docs":{},"t":{"5":{"df":0,"docs":{},"z":{"c":{"7":{"df":1,"docs":{"185":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"x":{"df":3,"docs":{"119":{"tf":1.0},"266":{"tf":1.4142135623730951},"271":{"tf":1.0}}}}}}}}},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"u":{"b":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"/":{"df":0,"docs":{},"j":{"a":{"df":0,"docs":{},"k":{"df":0,"docs":{},"u":{"b":{"a":{"d":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"w":{"/":{"a":{"df":0,"docs":{},"r":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"51":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"/":{"a":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"_":{"d":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"137":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}}}},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"/":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":2,"docs":{"11":{"tf":1.0},"186":{"tf":1.0}}}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":5,"docs":{"119":{"tf":1.0},"253":{"tf":1.0},"31":{"tf":1.0},"34":{"tf":1.0},"36":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"l":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"i":{"d":{".":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"/":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"i":{"d":{"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"/":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"/":{"a":{"c":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"/":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"v":{"_":{"a":{"b":{"7":{"2":{"df":0,"docs":{},"e":{"2":{"1":{"8":{"df":1,"docs":{"258":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"m":{"a":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{".":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{".":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"/":{"?":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"=":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"2":{"/":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"/":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"271":{"tf":1.0}},"e":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"271":{"tf":1.0}}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":1,"docs":{"271":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"/":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"/":{"b":{"df":0,"docs":{},"o":{"a":{"df":0,"docs":{},"r":{"d":{"/":{"df":0,"docs":{},"o":{"9":{"df":0,"docs":{},"j":{"_":{"df":0,"docs":{},"l":{"df":0,"docs":{},"w":{"df":0,"docs":{},"x":{"3":{"df":0,"docs":{},"j":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":1,"docs":{"274":{"tf":1.0}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{".":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"d":{".":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"/":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"s":{"/":{"1":{"0":{"7":{"8":{"df":1,"docs":{"215":{"tf":1.0}}},"df":0,"docs":{}},"8":{"1":{"df":1,"docs":{"216":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"/":{"2":{".":{"0":{"df":1,"docs":{"2":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"df":1,"docs":{"176":{"tf":1.0}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"u":{"b":{".":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"n":{".":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"/":{"b":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"/":{"df":0,"docs":{},"s":{"d":{"df":0,"docs":{},"k":{"df":1,"docs":{"176":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"x":{"df":0,"docs":{},"h":{"4":{"df":0,"docs":{},"n":{"df":0,"docs":{},"q":{"df":0,"docs":{},"y":{"df":0,"docs":{},"z":{"df":0,"docs":{},"y":{"c":{"df":0,"docs":{},"s":{"df":0,"docs":{},"y":{"a":{"df":0,"docs":{},"j":{"df":0,"docs":{},"g":{"0":{"df":0,"docs":{},"l":{"5":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"3":{"3":{"df":0,"docs":{},"y":{"d":{"5":{"df":0,"docs":{},"m":{"/":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"177":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"69":{"tf":1.0}}}}}}}},"i":{"'":{"df":0,"docs":{},"m":{"df":1,"docs":{"200":{"tf":1.0}}}},".":{"df":1,"docs":{"253":{"tf":1.0}}},"/":{"df":0,"docs":{},"o":{"df":2,"docs":{"115":{"tf":1.0},"179":{"tf":1.4142135623730951}}}},"d":{"df":7,"docs":{"231":{"tf":2.449489742783178},"242":{"tf":1.4142135623730951},"271":{"tf":1.0},"62":{"tf":1.0},"80":{"tf":1.0},"86":{"tf":3.0},"87":{"tf":1.0}},"e":{"a":{"df":2,"docs":{"51":{"tf":1.4142135623730951},"87":{"tf":1.0}},"l":{"df":7,"docs":{"119":{"tf":1.0},"122":{"tf":1.0},"206":{"tf":1.0},"208":{"tf":1.0},"229":{"tf":1.0},"49":{"tf":1.0},"58":{"tf":1.0}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":3,"docs":{"178":{"tf":1.0},"182":{"tf":1.0},"184":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{",":{"a":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"180":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"n":{"df":0,"docs":{},"t":{"df":4,"docs":{"18":{"tf":1.4142135623730951},"249":{"tf":1.0},"283":{"tf":1.0},"64":{"tf":1.0}},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":15,"docs":{"104":{"tf":1.0},"141":{"tf":1.0},"149":{"tf":1.4142135623730951},"151":{"tf":1.4142135623730951},"152":{"tf":1.0},"187":{"tf":1.7320508075688772},"188":{"tf":1.0},"191":{"tf":1.0},"200":{"tf":2.23606797749979},"214":{"tf":1.0},"22":{"tf":1.0},"225":{"tf":1.7320508075688772},"232":{"tf":1.0},"241":{"tf":1.4142135623730951},"86":{"tf":1.0}}}}}}}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"t":{"df":2,"docs":{"103":{"tf":1.4142135623730951},"43":{"tf":1.0}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{",":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"75":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":13,"docs":{"189":{"tf":1.0},"193":{"tf":1.0},"194":{"tf":1.0},"200":{"tf":1.7320508075688772},"201":{"tf":1.7320508075688772},"232":{"tf":1.0},"233":{"tf":1.7320508075688772},"237":{"tf":1.0},"241":{"tf":1.0},"242":{"tf":1.4142135623730951},"245":{"tf":1.0},"87":{"tf":1.0},"88":{"tf":1.0}}},"f":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":1,"docs":{"37":{"tf":1.0}}}}},"df":0,"docs":{}},"g":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":5,"docs":{"200":{"tf":1.0},"226":{"tf":1.4142135623730951},"233":{"tf":1.0},"241":{"tf":1.0},"61":{"tf":1.0}}}}}},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"132":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"m":{"a":{"df":0,"docs":{},"g":{"df":2,"docs":{"194":{"tf":1.0},"274":{"tf":1.0}},"i":{"df":0,"docs":{},"n":{"df":2,"docs":{"225":{"tf":1.0},"232":{"tf":1.0}}}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"i":{"df":2,"docs":{"189":{"tf":1.0},"81":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"p":{"a":{"c":{"df":0,"docs":{},"t":{"df":4,"docs":{"167":{"tf":1.0},"178":{"tf":1.0},"200":{"tf":1.0},"229":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"df":2,"docs":{"242":{"tf":1.7320508075688772},"62":{"tf":1.0}},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":43,"docs":{"103":{"tf":2.0},"105":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"115":{"tf":1.0},"125":{"tf":1.0},"128":{"tf":1.0},"140":{"tf":1.0},"141":{"tf":1.0},"146":{"tf":1.4142135623730951},"152":{"tf":1.4142135623730951},"159":{"tf":1.0},"167":{"tf":1.4142135623730951},"200":{"tf":1.7320508075688772},"203":{"tf":1.7320508075688772},"208":{"tf":1.0},"218":{"tf":1.0},"225":{"tf":2.0},"226":{"tf":2.6457513110645907},"227":{"tf":2.23606797749979},"228":{"tf":1.7320508075688772},"230":{"tf":2.23606797749979},"231":{"tf":1.4142135623730951},"232":{"tf":1.4142135623730951},"233":{"tf":1.0},"234":{"tf":1.0},"239":{"tf":1.0},"241":{"tf":2.0},"244":{"tf":1.0},"247":{"tf":1.0},"254":{"tf":1.0},"256":{"tf":1.7320508075688772},"257":{"tf":1.7320508075688772},"258":{"tf":2.449489742783178},"264":{"tf":1.0},"274":{"tf":1.0},"282":{"tf":1.0},"37":{"tf":1.4142135623730951},"43":{"tf":1.0},"45":{"tf":1.0},"48":{"tf":1.0},"82":{"tf":1.0},"84":{"tf":1.4142135623730951}}}}}}},"i":{"c":{"df":3,"docs":{"197":{"tf":1.0},"205":{"tf":1.4142135623730951},"263":{"tf":1.4142135623730951}},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"138":{"tf":1.0}}}}},"df":2,"docs":{"123":{"tf":1.4142135623730951},"245":{"tf":1.0}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"m":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"145":{"tf":2.0}}}}}}}}}},"df":30,"docs":{"103":{"tf":1.4142135623730951},"105":{"tf":1.7320508075688772},"138":{"tf":1.0},"158":{"tf":1.0},"16":{"tf":1.0},"165":{"tf":1.0},"166":{"tf":1.4142135623730951},"167":{"tf":1.0},"20":{"tf":1.0},"205":{"tf":1.0},"206":{"tf":1.0},"210":{"tf":1.0},"213":{"tf":1.0},"214":{"tf":1.0},"218":{"tf":1.0},"242":{"tf":1.0},"251":{"tf":1.0},"253":{"tf":1.0},"258":{"tf":1.0},"260":{"tf":1.0},"27":{"tf":1.0},"37":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"49":{"tf":1.0},"6":{"tf":1.4142135623730951},"60":{"tf":1.0},"73":{"tf":1.7320508075688772},"87":{"tf":1.7320508075688772},"9":{"tf":1.0}}}},"s":{"df":2,"docs":{"206":{"tf":1.0},"210":{"tf":1.0}},"s":{"df":2,"docs":{"201":{"tf":1.0},"43":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":10,"docs":{"111":{"tf":1.0},"145":{"tf":1.0},"146":{"tf":1.0},"150":{"tf":1.0},"171":{"tf":1.4142135623730951},"206":{"tf":1.0},"208":{"tf":1.0},"264":{"tf":1.0},"282":{"tf":1.0},"51":{"tf":1.4142135623730951}}}}}}},"n":{"c":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":1,"docs":{"137":{"tf":1.0}}}}},"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"205":{"tf":1.7320508075688772},"218":{"tf":1.0}}},"df":0,"docs":{}},"l":{"df":0,"docs":{},"u":{"d":{"df":39,"docs":{"105":{"tf":1.0},"113":{"tf":1.0},"118":{"tf":1.0},"124":{"tf":1.0},"167":{"tf":2.449489742783178},"169":{"tf":1.0},"172":{"tf":1.0},"18":{"tf":1.0},"190":{"tf":1.0},"193":{"tf":1.0},"200":{"tf":1.7320508075688772},"204":{"tf":1.0},"222":{"tf":1.0},"224":{"tf":1.0},"226":{"tf":1.4142135623730951},"23":{"tf":1.4142135623730951},"231":{"tf":1.0},"251":{"tf":1.7320508075688772},"253":{"tf":1.4142135623730951},"274":{"tf":1.4142135623730951},"283":{"tf":1.0},"29":{"tf":1.7320508075688772},"31":{"tf":2.449489742783178},"32":{"tf":1.4142135623730951},"34":{"tf":1.7320508075688772},"36":{"tf":2.23606797749979},"37":{"tf":1.0},"4":{"tf":1.0},"58":{"tf":1.0},"63":{"tf":1.0},"65":{"tf":1.0},"69":{"tf":1.4142135623730951},"7":{"tf":2.449489742783178},"70":{"tf":1.4142135623730951},"72":{"tf":1.0},"73":{"tf":1.7320508075688772},"74":{"tf":1.0},"78":{"tf":1.0},"99":{"tf":1.0}},"e":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"81":{"tf":1.0}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"m":{"df":5,"docs":{"246":{"tf":1.0},"248":{"tf":1.0},"249":{"tf":1.4142135623730951},"283":{"tf":1.0},"88":{"tf":1.0}},"p":{"a":{"df":0,"docs":{},"t":{"df":5,"docs":{"169":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"268":{"tf":1.0}}}},"df":0,"docs":{}}},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"170":{"tf":1.0}}}}}}}},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":3,"docs":{"205":{"tf":1.0},"208":{"tf":1.0},"41":{"tf":1.0}}}},"d":{"df":1,"docs":{"22":{"tf":1.0}},"u":{"df":0,"docs":{},"l":{"df":1,"docs":{"60":{"tf":1.0}}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"190":{"tf":1.0},"250":{"tf":1.0}}}}}}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"166":{"tf":1.0},"170":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"201":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"x":{"df":5,"docs":{"268":{"tf":1.0},"269":{"tf":1.0},"271":{"tf":1.0},"40":{"tf":1.0},"62":{"tf":1.0}}}},"i":{"c":{"df":7,"docs":{"154":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0},"253":{"tf":1.0},"268":{"tf":1.0},"40":{"tf":1.0},"7":{"tf":1.0}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"200":{"tf":1.0}}}}}},"df":0,"docs":{}}},"v":{"df":0,"docs":{},"i":{"d":{"df":0,"docs":{},"u":{"df":9,"docs":{"124":{"tf":1.0},"187":{"tf":1.0},"221":{"tf":1.4142135623730951},"225":{"tf":1.0},"228":{"tf":1.0},"244":{"tf":1.4142135623730951},"245":{"tf":1.4142135623730951},"274":{"tf":1.0},"283":{"tf":1.0}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"196":{"tf":1.0},"263":{"tf":1.0}}}}}},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"82":{"tf":1.0}}}}}},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"274":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}},"o":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"252":{"tf":1.7320508075688772}}}}}}}},"/":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"226":{"tf":1.4142135623730951},"229":{"tf":1.0},"241":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":1,"docs":{"242":{"tf":1.0}},"r":{"df":0,"docs":{},"m":{"df":17,"docs":{"13":{"tf":1.0},"134":{"tf":1.4142135623730951},"177":{"tf":1.0},"226":{"tf":1.0},"23":{"tf":1.0},"241":{"tf":1.0},"242":{"tf":1.0},"263":{"tf":1.4142135623730951},"268":{"tf":1.0},"274":{"tf":1.0},"283":{"tf":1.0},"32":{"tf":1.0},"36":{"tf":1.0},"4":{"tf":1.0},"7":{"tf":1.0},"73":{"tf":1.0},"75":{"tf":1.0}}}}},"r":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":2,"docs":{"167":{"tf":1.0},"48":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"233":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"t":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"242":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"11":{"tf":1.0}},"i":{"a":{"df":0,"docs":{},"l":{"_":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"j":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"179":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}}}}}}}}},"df":0,"docs":{}}},"df":16,"docs":{"10":{"tf":1.0},"132":{"tf":1.0},"164":{"tf":1.0},"167":{"tf":1.7320508075688772},"172":{"tf":1.0},"174":{"tf":1.0},"184":{"tf":1.0},"189":{"tf":1.0},"190":{"tf":1.7320508075688772},"199":{"tf":1.0},"200":{"tf":1.4142135623730951},"201":{"tf":1.4142135623730951},"231":{"tf":1.0},"238":{"tf":1.0},"242":{"tf":1.4142135623730951},"244":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":3,"docs":{"214":{"tf":1.0},"233":{"tf":1.0},"254":{"tf":1.0}}}}},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"104":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":2,"docs":{"59":{"tf":1.0},"62":{"tf":1.0}}}}},"i":{"d":{"df":7,"docs":{"12":{"tf":1.0},"20":{"tf":2.23606797749979},"263":{"tf":1.0},"30":{"tf":1.0},"34":{"tf":1.0},"35":{"tf":1.0},"61":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"108":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"245":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"a":{"df":0,"docs":{},"l":{"df":14,"docs":{"104":{"tf":1.0},"11":{"tf":6.48074069840786},"12":{"tf":2.0},"13":{"tf":1.7320508075688772},"14":{"tf":1.4142135623730951},"16":{"tf":1.0},"18":{"tf":1.0},"201":{"tf":1.4142135623730951},"269":{"tf":1.4142135623730951},"279":{"tf":1.0},"286":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":2.0},"53":{"tf":1.4142135623730951}}},"n":{"c":{"df":6,"docs":{"244":{"tf":1.0},"245":{"tf":1.4142135623730951},"246":{"tf":1.0},"247":{"tf":1.0},"249":{"tf":1.0},"78":{"tf":1.0}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":2,"docs":{"115":{"tf":1.0},"245":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"a":{"d":{"df":14,"docs":{"158":{"tf":1.0},"172":{"tf":1.0},"18":{"tf":1.0},"188":{"tf":1.0},"194":{"tf":1.4142135623730951},"231":{"tf":1.0},"242":{"tf":1.0},"37":{"tf":1.0},"58":{"tf":1.0},"61":{"tf":1.7320508075688772},"64":{"tf":1.0},"78":{"tf":1.0},"79":{"tf":1.0},"84":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":10,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"11":{"tf":2.0},"12":{"tf":1.4142135623730951},"21":{"tf":1.0},"281":{"tf":1.0},"32":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"5":{"tf":1.0}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"62":{"tf":1.4142135623730951}}}}}}}}}},"t":{"a":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"105":{"tf":1.0},"171":{"tf":1.0}}}},"df":0,"docs":{}},"df":1,"docs":{"116":{"tf":1.0}},"e":{"df":0,"docs":{},"g":{"df":1,"docs":{"250":{"tf":1.0}},"r":{"df":21,"docs":{"0":{"tf":1.0},"201":{"tf":1.0},"223":{"tf":1.0},"225":{"tf":1.0},"226":{"tf":1.0},"228":{"tf":1.0},"257":{"tf":1.0},"259":{"tf":1.0},"263":{"tf":1.0},"274":{"tf":2.449489742783178},"283":{"tf":1.4142135623730951},"47":{"tf":1.4142135623730951},"51":{"tf":1.4142135623730951},"52":{"tf":1.4142135623730951},"53":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"60":{"tf":2.6457513110645907},"61":{"tf":1.0},"7":{"tf":1.0}}}},"l":{"df":2,"docs":{"17":{"tf":1.0},"280":{"tf":1.0}}},"n":{"d":{"df":5,"docs":{"104":{"tf":1.4142135623730951},"107":{"tf":1.0},"274":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":1,"docs":{"64":{"tf":1.0}}},"t":{"df":1,"docs":{"227":{"tf":1.0}}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":6,"docs":{"140":{"tf":1.0},"205":{"tf":1.0},"225":{"tf":1.0},"226":{"tf":1.7320508075688772},"232":{"tf":1.4142135623730951},"254":{"tf":1.7320508075688772}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":6,"docs":{"104":{"tf":1.0},"231":{"tf":1.0},"263":{"tf":1.0},"37":{"tf":1.0},"41":{"tf":1.0},"58":{"tf":1.0}}}}},"f":{"a":{"c":{"df":11,"docs":{"103":{"tf":1.7320508075688772},"104":{"tf":2.23606797749979},"105":{"tf":1.7320508075688772},"106":{"tf":1.4142135623730951},"111":{"tf":1.0},"128":{"tf":1.0},"167":{"tf":1.0},"184":{"tf":1.0},"246":{"tf":1.0},"37":{"tf":1.0},"69":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"m":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"i":{"df":1,"docs":{"268":{"tf":1.0}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"l":{"df":1,"docs":{"62":{"tf":1.0}}}}}}},"n":{"df":6,"docs":{"105":{"tf":1.0},"106":{"tf":1.0},"146":{"tf":1.0},"175":{"tf":1.0},"193":{"tf":1.0},"283":{"tf":1.7320508075688772}}},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"220":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":2,"docs":{"124":{"tf":1.0},"242":{"tf":1.4142135623730951}}}}}}}},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"c":{"df":14,"docs":{"105":{"tf":1.4142135623730951},"158":{"tf":1.0},"172":{"tf":1.0},"180":{"tf":1.0},"207":{"tf":1.4142135623730951},"208":{"tf":1.4142135623730951},"213":{"tf":1.4142135623730951},"228":{"tf":1.0},"23":{"tf":1.0},"251":{"tf":1.0},"32":{"tf":1.0},"64":{"tf":1.0},"69":{"tf":1.0},"73":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":1,"docs":{"221":{"tf":1.0}}}},"s":{"df":0,"docs":{},"t":{"df":4,"docs":{"164":{"tf":1.0},"170":{"tf":1.0},"183":{"tf":1.0},"49":{"tf":1.0}},"i":{"df":0,"docs":{},"g":{"df":2,"docs":{"37":{"tf":1.0},"64":{"tf":1.0}}}}}}},"o":{"c":{"df":3,"docs":{"115":{"tf":1.0},"279":{"tf":1.0},"58":{"tf":1.0}}},"df":0,"docs":{},"k":{"df":2,"docs":{"105":{"tf":1.0},"283":{"tf":1.4142135623730951}}},"l":{"df":0,"docs":{},"v":{"df":12,"docs":{"116":{"tf":1.4142135623730951},"167":{"tf":1.0},"172":{"tf":1.0},"20":{"tf":1.0},"231":{"tf":1.0},"242":{"tf":1.0},"244":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"49":{"tf":1.0},"62":{"tf":1.0},"68":{"tf":1.0}}}}}}},"o":{"df":69,"docs":{"10":{"tf":1.0},"103":{"tf":1.4142135623730951},"109":{"tf":1.4142135623730951},"11":{"tf":1.0},"115":{"tf":1.0},"118":{"tf":2.0},"119":{"tf":1.7320508075688772},"127":{"tf":1.4142135623730951},"134":{"tf":1.0},"14":{"tf":2.0},"145":{"tf":1.0},"15":{"tf":1.7320508075688772},"161":{"tf":1.7320508075688772},"162":{"tf":1.4142135623730951},"164":{"tf":1.0},"165":{"tf":1.0},"167":{"tf":1.4142135623730951},"169":{"tf":1.0},"170":{"tf":1.7320508075688772},"171":{"tf":1.4142135623730951},"172":{"tf":1.4142135623730951},"175":{"tf":1.0},"185":{"tf":1.0},"192":{"tf":1.0},"199":{"tf":1.0},"200":{"tf":1.4142135623730951},"202":{"tf":1.4142135623730951},"203":{"tf":1.0},"213":{"tf":1.0},"215":{"tf":1.0},"218":{"tf":1.4142135623730951},"219":{"tf":1.0},"220":{"tf":1.0},"225":{"tf":1.7320508075688772},"228":{"tf":1.4142135623730951},"229":{"tf":1.0},"23":{"tf":2.449489742783178},"24":{"tf":1.7320508075688772},"241":{"tf":1.0},"25":{"tf":2.6457513110645907},"251":{"tf":1.0},"252":{"tf":1.0},"255":{"tf":1.0},"257":{"tf":1.7320508075688772},"26":{"tf":1.0},"268":{"tf":1.4142135623730951},"27":{"tf":1.0},"271":{"tf":1.4142135623730951},"272":{"tf":1.4142135623730951},"274":{"tf":1.4142135623730951},"278":{"tf":1.4142135623730951},"28":{"tf":1.4142135623730951},"280":{"tf":1.0},"29":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":2.23606797749979},"32":{"tf":2.0},"33":{"tf":1.4142135623730951},"34":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0},"38":{"tf":1.0},"56":{"tf":1.7320508075688772},"69":{"tf":1.0},"72":{"tf":1.4142135623730951},"73":{"tf":1.4142135623730951},"74":{"tf":1.0},"9":{"tf":1.0},"90":{"tf":1.4142135623730951}},"s":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"279":{"tf":1.0}}}}},"/":{"<":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"109":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"_":{"c":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"72":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"x":{"c":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"p":{"b":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"df":1,"docs":{"26":{"tf":1.4142135623730951}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"109":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":1,"docs":{"66":{"tf":1.4142135623730951}}}}}}}},"df":0,"docs":{},"’":{"df":1,"docs":{"218":{"tf":1.0}}}},"w":{"df":1,"docs":{"89":{"tf":1.0}}}},"s":{"_":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"o":{"df":1,"docs":{"282":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"n":{"'":{"df":0,"docs":{},"t":{"df":13,"docs":{"11":{"tf":1.0},"12":{"tf":1.0},"169":{"tf":1.0},"220":{"tf":1.0},"225":{"tf":1.0},"231":{"tf":1.0},"233":{"tf":1.0},"236":{"tf":1.0},"58":{"tf":1.0},"6":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0},"82":{"tf":1.0}}}},"df":0,"docs":{},"’":{"df":0,"docs":{},"t":{"df":3,"docs":{"115":{"tf":1.0},"197":{"tf":1.0},"205":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"l":{"df":3,"docs":{"201":{"tf":1.0},"229":{"tf":1.4142135623730951},"274":{"tf":1.0}}}},"s":{"df":0,"docs":{},"u":{"df":21,"docs":{"1":{"tf":2.0},"104":{"tf":1.0},"111":{"tf":1.0},"115":{"tf":1.0},"116":{"tf":1.0},"152":{"tf":1.0},"165":{"tf":1.0},"169":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"197":{"tf":1.0},"205":{"tf":1.4142135623730951},"209":{"tf":1.0},"225":{"tf":1.0},"228":{"tf":1.0},"237":{"tf":1.0},"242":{"tf":1.4142135623730951},"3":{"tf":1.0},"4":{"tf":1.4142135623730951},"6":{"tf":3.605551275463989}}}}},"t":{"'":{"df":54,"docs":{"104":{"tf":1.0},"105":{"tf":1.0},"107":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"11":{"tf":1.7320508075688772},"115":{"tf":1.0},"116":{"tf":1.0},"132":{"tf":1.0},"154":{"tf":1.0},"155":{"tf":1.0},"158":{"tf":1.0},"159":{"tf":1.0},"16":{"tf":1.4142135623730951},"167":{"tf":1.0},"169":{"tf":1.4142135623730951},"170":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"175":{"tf":1.0},"20":{"tf":1.0},"200":{"tf":1.0},"201":{"tf":1.0},"208":{"tf":1.0},"221":{"tf":1.0},"224":{"tf":1.0},"225":{"tf":1.4142135623730951},"227":{"tf":1.0},"232":{"tf":1.0},"242":{"tf":1.7320508075688772},"245":{"tf":1.7320508075688772},"249":{"tf":1.4142135623730951},"25":{"tf":1.0},"252":{"tf":1.0},"258":{"tf":1.0},"263":{"tf":1.0},"266":{"tf":1.0},"268":{"tf":1.0},"27":{"tf":1.4142135623730951},"274":{"tf":1.0},"43":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"48":{"tf":1.0},"49":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.4142135623730951},"7":{"tf":1.0},"75":{"tf":1.0},"78":{"tf":1.0},"81":{"tf":1.0},"84":{"tf":1.0},"9":{"tf":1.0}},"l":{"df":0,"docs":{},"l":{"df":1,"docs":{"82":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":7,"docs":{"165":{"tf":1.0},"172":{"tf":1.0},"189":{"tf":1.0},"232":{"tf":1.0},"59":{"tf":1.0},"73":{"tf":1.4142135623730951},"84":{"tf":2.0}},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":1,"docs":{"283":{"tf":1.0}}}}}}}}}}}}},"df":0,"docs":{}}}}}}},"r":{"df":5,"docs":{"104":{"tf":1.0},"105":{"tf":1.0},"124":{"tf":1.0},"167":{"tf":1.0},"249":{"tf":1.0}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":19,"docs":{"104":{"tf":1.0},"117":{"tf":1.0},"184":{"tf":1.0},"189":{"tf":1.0},"194":{"tf":1.7320508075688772},"197":{"tf":1.4142135623730951},"201":{"tf":1.0},"225":{"tf":1.0},"229":{"tf":1.0},"245":{"tf":1.0},"254":{"tf":1.0},"263":{"tf":1.0},"44":{"tf":1.0},"59":{"tf":1.0},"79":{"tf":1.0},"81":{"tf":1.0},"83":{"tf":1.0},"84":{"tf":1.4142135623730951},"86":{"tf":1.0}}}}}},"’":{"df":10,"docs":{"115":{"tf":1.0},"189":{"tf":1.0},"193":{"tf":1.4142135623730951},"194":{"tf":1.0},"205":{"tf":1.0},"210":{"tf":1.0},"212":{"tf":1.0},"213":{"tf":1.4142135623730951},"214":{"tf":1.7320508075688772},"87":{"tf":1.0}}}}},"j":{"a":{"df":0,"docs":{},"r":{"/":{"a":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"37":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"223":{"tf":1.0}},"e":{"d":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"176":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"v":{"a":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"/":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"/":{"<":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"103":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"_":{"c":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"70":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"_":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":1,"docs":{"12":{"tf":1.0}}}}}},"df":7,"docs":{"112":{"tf":1.0},"115":{"tf":1.0},"12":{"tf":1.4142135623730951},"125":{"tf":1.0},"128":{"tf":1.0},"223":{"tf":1.0},"37":{"tf":1.0}},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"192":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"d":{"df":0,"docs":{},"k":{"df":1,"docs":{"12":{"tf":1.0}}}},"df":0,"docs":{},"h":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"176":{"tf":1.0}}}},"df":0,"docs":{}}}}},"n":{"a":{"'":{"df":1,"docs":{"115":{"tf":1.0}}},"df":6,"docs":{"111":{"tf":1.0},"115":{"tf":1.4142135623730951},"116":{"tf":2.6457513110645907},"37":{"tf":3.7416573867739413},"45":{"tf":1.0},"65":{"tf":1.0}}},"df":0,"docs":{},"i":{"df":2,"docs":{"115":{"tf":1.4142135623730951},"116":{"tf":2.23606797749979}}}},"o":{"b":{"df":4,"docs":{"274":{"tf":1.0},"278":{"tf":1.0},"282":{"tf":1.0},"74":{"tf":1.0}}},"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"l":{"_":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"d":{"df":1,"docs":{"264":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"s":{"df":1,"docs":{"241":{"tf":1.0}},"o":{"df":0,"docs":{},"n":{"df":3,"docs":{"189":{"tf":1.0},"271":{"tf":1.4142135623730951},"79":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"45":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":1,"docs":{"194":{"tf":1.0}}}}}}},"v":{"df":0,"docs":{},"m":{"df":2,"docs":{"115":{"tf":1.4142135623730951},"37":{"tf":1.0}}}}},"k":{"8":{"8":{"df":0,"docs":{},"h":{"df":0,"docs":{},"u":{"d":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"176":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":28,"docs":{"121":{"tf":1.0},"142":{"tf":1.0},"150":{"tf":1.0},"163":{"tf":1.0},"165":{"tf":1.0},"167":{"tf":1.0},"171":{"tf":1.0},"179":{"tf":1.0},"182":{"tf":1.4142135623730951},"184":{"tf":1.0},"198":{"tf":1.0},"205":{"tf":2.0},"206":{"tf":1.0},"207":{"tf":1.0},"208":{"tf":1.0},"209":{"tf":2.23606797749979},"210":{"tf":1.0},"212":{"tf":1.7320508075688772},"213":{"tf":1.0},"217":{"tf":1.0},"218":{"tf":1.0},"242":{"tf":1.4142135623730951},"26":{"tf":1.0},"261":{"tf":1.0},"263":{"tf":1.0},"278":{"tf":1.0},"58":{"tf":1.4142135623730951},"6":{"tf":1.0}}}},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"264":{"tf":1.0}}}},"y":{"/":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":1,"docs":{"80":{"tf":1.0}}}}},"df":0,"docs":{}}},"_":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"242":{"tf":1.0}}},"df":0,"docs":{}}},"df":15,"docs":{"142":{"tf":1.7320508075688772},"143":{"tf":1.4142135623730951},"145":{"tf":3.1622776601683795},"146":{"tf":2.23606797749979},"184":{"tf":1.0},"198":{"tf":1.0},"201":{"tf":1.0},"225":{"tf":1.0},"229":{"tf":1.4142135623730951},"237":{"tf":1.0},"245":{"tf":1.0},"248":{"tf":1.0},"254":{"tf":1.4142135623730951},"274":{"tf":1.4142135623730951},"283":{"tf":1.4142135623730951}}}},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":1,"docs":{"133":{"tf":1.0}}}},"n":{"d":{"df":7,"docs":{"189":{"tf":1.0},"190":{"tf":1.0},"201":{"tf":1.0},"221":{"tf":1.0},"225":{"tf":1.0},"229":{"tf":1.0},"242":{"tf":1.0}}},"df":0,"docs":{}}},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":22,"docs":{"114":{"tf":1.4142135623730951},"115":{"tf":1.0},"117":{"tf":1.0},"119":{"tf":1.0},"128":{"tf":1.0},"17":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.0},"21":{"tf":1.0},"228":{"tf":1.7320508075688772},"229":{"tf":1.4142135623730951},"233":{"tf":1.4142135623730951},"254":{"tf":1.0},"26":{"tf":1.0},"268":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"5":{"tf":1.0},"58":{"tf":1.4142135623730951},"84":{"tf":1.0},"87":{"tf":1.4142135623730951},"89":{"tf":1.0}},"l":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"g":{"df":1,"docs":{"175":{"tf":1.0}}}},"df":0,"docs":{}}},"n":{"df":11,"docs":{"104":{"tf":1.0},"151":{"tf":1.4142135623730951},"152":{"tf":1.4142135623730951},"155":{"tf":1.4142135623730951},"156":{"tf":1.4142135623730951},"193":{"tf":1.0},"220":{"tf":1.0},"233":{"tf":1.0},"242":{"tf":1.0},"245":{"tf":1.0},"248":{"tf":1.4142135623730951}}}}}},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"/":{"df":0,"docs":{},"s":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":1,"docs":{"51":{"tf":1.0}}}}}}}},"df":27,"docs":{"101":{"tf":1.0},"103":{"tf":2.23606797749979},"104":{"tf":1.0},"108":{"tf":3.605551275463989},"111":{"tf":2.23606797749979},"115":{"tf":2.23606797749979},"125":{"tf":1.0},"128":{"tf":2.0},"129":{"tf":1.0},"130":{"tf":1.0},"131":{"tf":1.0},"132":{"tf":1.0},"133":{"tf":1.0},"196":{"tf":1.0},"221":{"tf":1.0},"222":{"tf":1.0},"225":{"tf":1.0},"268":{"tf":1.0},"271":{"tf":1.0},"274":{"tf":1.0},"45":{"tf":2.8284271247461903},"65":{"tf":1.7320508075688772},"68":{"tf":1.0},"7":{"tf":1.0},"70":{"tf":3.0},"98":{"tf":1.4142135623730951},"99":{"tf":1.4142135623730951}}}}}}},"t":{"df":2,"docs":{"70":{"tf":1.4142135623730951},"98":{"tf":1.4142135623730951}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"65":{"tf":1.0}}}}}}}},"l":{"a":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":2,"docs":{"6":{"tf":1.4142135623730951},"7":{"tf":1.4142135623730951}}}}},"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"187":{"tf":1.0}}}},"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"103":{"tf":1.0}}},"df":0,"docs":{}},"n":{"d":{"df":5,"docs":{"118":{"tf":1.0},"123":{"tf":1.0},"124":{"tf":1.0},"268":{"tf":1.0},"274":{"tf":1.4142135623730951}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"g":{"df":1,"docs":{"64":{"tf":1.0}},"u":{"a":{"df":0,"docs":{},"g":{"df":8,"docs":{"0":{"tf":1.0},"101":{"tf":1.0},"104":{"tf":1.0},"106":{"tf":1.0},"125":{"tf":1.0},"225":{"tf":1.0},"226":{"tf":1.0},"69":{"tf":1.0}}}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"g":{"df":7,"docs":{"204":{"tf":1.0},"205":{"tf":1.4142135623730951},"208":{"tf":1.0},"212":{"tf":1.7320508075688772},"213":{"tf":1.4142135623730951},"232":{"tf":1.0},"81":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":3,"docs":{"171":{"tf":1.0},"172":{"tf":1.0},"206":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"t":{"df":6,"docs":{"106":{"tf":1.0},"111":{"tf":1.0},"213":{"tf":1.0},"233":{"tf":1.0},"248":{"tf":1.4142135623730951},"88":{"tf":1.0}},"m":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":3,"docs":{"62":{"tf":1.0},"80":{"tf":1.0},"86":{"tf":1.0}}}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":7,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"11":{"tf":1.0},"138":{"tf":1.0},"158":{"tf":1.0},"179":{"tf":1.0},"88":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{"df":14,"docs":{"104":{"tf":1.0},"138":{"tf":1.0},"206":{"tf":1.0},"207":{"tf":1.0},"210":{"tf":1.0},"217":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951},"260":{"tf":1.4142135623730951},"261":{"tf":1.0},"266":{"tf":1.4142135623730951},"271":{"tf":1.0},"274":{"tf":1.4142135623730951},"40":{"tf":1.7320508075688772},"69":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":2,"docs":{"31":{"tf":1.0},"36":{"tf":1.0}}}},"df":0,"docs":{}}},"y":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":11,"docs":{"101":{"tf":1.0},"103":{"tf":1.0},"108":{"tf":1.4142135623730951},"109":{"tf":1.4142135623730951},"140":{"tf":1.0},"228":{"tf":1.0},"232":{"tf":1.4142135623730951},"44":{"tf":1.4142135623730951},"45":{"tf":1.0},"46":{"tf":1.0},"90":{"tf":1.0}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"200":{"tf":1.0}}}}}},"z":{"df":0,"docs":{},"i":{"df":1,"docs":{"132":{"tf":1.0}}},"y":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"62":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"d":{"a":{"df":0,"docs":{},"p":{"df":1,"docs":{"274":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"a":{"d":{"df":5,"docs":{"157":{"tf":1.0},"172":{"tf":1.0},"196":{"tf":1.0},"256":{"tf":1.0},"40":{"tf":1.0}}},"df":0,"docs":{},"f":{"df":1,"docs":{"59":{"tf":1.0}}},"n":{"df":1,"docs":{"138":{"tf":1.0}}},"r":{"df":0,"docs":{},"n":{"df":5,"docs":{"171":{"tf":1.0},"172":{"tf":1.0},"236":{"tf":1.0},"7":{"tf":1.0},"75":{"tf":1.4142135623730951}}}},"v":{"df":3,"docs":{"108":{"tf":1.0},"159":{"tf":1.0},"171":{"tf":1.0}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":6,"docs":{"126":{"tf":1.0},"193":{"tf":1.0},"25":{"tf":1.0},"40":{"tf":1.0},"64":{"tf":1.0},"84":{"tf":1.0}}}},"g":{"a":{"c":{"df":0,"docs":{},"i":{"df":2,"docs":{"203":{"tf":1.0},"228":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":1,"docs":{"186":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"s":{"df":10,"docs":{"115":{"tf":1.0},"166":{"tf":1.0},"170":{"tf":1.0},"205":{"tf":1.0},"209":{"tf":1.0},"212":{"tf":1.0},"214":{"tf":1.0},"215":{"tf":1.0},"61":{"tf":1.0},"81":{"tf":1.0}},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"90":{"tf":1.0}}}}}},"t":{"'":{"df":4,"docs":{"227":{"tf":1.0},"228":{"tf":1.0},"230":{"tf":1.0},"62":{"tf":1.0}}},"df":3,"docs":{"225":{"tf":1.0},"283":{"tf":1.0},"81":{"tf":1.0}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":42,"docs":{"103":{"tf":2.8284271247461903},"105":{"tf":1.0},"123":{"tf":1.4142135623730951},"124":{"tf":1.4142135623730951},"18":{"tf":1.0},"200":{"tf":2.0},"219":{"tf":1.7320508075688772},"22":{"tf":1.0},"220":{"tf":1.4142135623730951},"225":{"tf":3.0},"226":{"tf":3.1622776601683795},"229":{"tf":2.6457513110645907},"23":{"tf":1.0},"230":{"tf":2.0},"232":{"tf":1.4142135623730951},"233":{"tf":2.23606797749979},"234":{"tf":1.0},"235":{"tf":1.4142135623730951},"236":{"tf":2.23606797749979},"237":{"tf":1.7320508075688772},"239":{"tf":1.0},"243":{"tf":1.0},"244":{"tf":1.0},"245":{"tf":1.0},"249":{"tf":1.0},"251":{"tf":1.7320508075688772},"252":{"tf":1.0},"254":{"tf":2.0},"262":{"tf":1.0},"274":{"tf":1.0},"281":{"tf":1.4142135623730951},"283":{"tf":1.0},"37":{"tf":1.0},"41":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"6":{"tf":1.0},"64":{"tf":1.0},"70":{"tf":1.0},"73":{"tf":1.0},"84":{"tf":1.0},"98":{"tf":1.4142135623730951}}},"r":{"a":{"df":0,"docs":{},"g":{"df":2,"docs":{"123":{"tf":1.0},"79":{"tf":1.0}}}},"df":0,"docs":{}}}}},"i":{"b":{".":{"df":0,"docs":{},"r":{"df":4,"docs":{"103":{"tf":1.0},"105":{"tf":2.0},"107":{"tf":1.0},"60":{"tf":1.0}}}},"<":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{">":{"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{".":{"df":0,"docs":{},"k":{"df":0,"docs":{},"t":{"df":1,"docs":{"103":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}},"df":8,"docs":{"11":{"tf":1.0},"175":{"tf":1.0},"279":{"tf":1.0},"282":{"tf":1.0},"37":{"tf":1.0},"58":{"tf":1.0},"60":{"tf":1.4142135623730951},"69":{"tf":1.0}},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"a":{"df":0,"docs":{},"z":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"d":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":1,"docs":{"114":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"r":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":33,"docs":{"11":{"tf":1.0},"114":{"tf":1.4142135623730951},"167":{"tf":1.0},"187":{"tf":1.0},"188":{"tf":1.0},"189":{"tf":1.7320508075688772},"190":{"tf":2.449489742783178},"191":{"tf":1.7320508075688772},"193":{"tf":1.4142135623730951},"194":{"tf":1.4142135623730951},"196":{"tf":2.6457513110645907},"197":{"tf":2.23606797749979},"198":{"tf":1.0},"199":{"tf":1.4142135623730951},"200":{"tf":1.7320508075688772},"201":{"tf":1.4142135623730951},"220":{"tf":1.4142135623730951},"222":{"tf":1.4142135623730951},"225":{"tf":1.0},"228":{"tf":1.0},"251":{"tf":1.0},"274":{"tf":1.0},"278":{"tf":1.4142135623730951},"28":{"tf":1.0},"284":{"tf":1.0},"33":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.7320508075688772},"45":{"tf":1.0},"60":{"tf":1.0},"69":{"tf":1.0},"77":{"tf":1.7320508075688772},"90":{"tf":1.4142135623730951}}},"y":{"/":{"c":{"a":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{"df":0,"docs":{},"m":{"df":1,"docs":{"25":{"tf":1.0}}}}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"/":{"df":0,"docs":{},"x":{"c":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"e":{"/":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"d":{"d":{"a":{"df":0,"docs":{},"t":{"a":{"/":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"25":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"s":{"/":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"280":{"tf":1.0}}},"df":0,"docs":{}}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"k":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":1,"docs":{"279":{"tf":1.0}}}}}}}}},"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":4,"docs":{"11":{"tf":1.4142135623730951},"12":{"tf":1.0},"14":{"tf":1.0},"279":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}},"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":4,"docs":{"2":{"tf":1.7320508075688772},"63":{"tf":1.0},"64":{"tf":1.7320508075688772},"7":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"y":{"c":{"df":0,"docs":{},"l":{"df":2,"docs":{"131":{"tf":2.0},"133":{"tf":1.0}}}},"df":0,"docs":{}}},"df":1,"docs":{"256":{"tf":1.0}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"130":{"tf":1.0}}}}}}},"k":{"df":0,"docs":{},"e":{"df":1,"docs":{"104":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"d":{"df":3,"docs":{"208":{"tf":1.0},"256":{"tf":1.0},"258":{"tf":1.0}}},"df":0,"docs":{}}}}}}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":17,"docs":{"134":{"tf":1.0},"140":{"tf":1.0},"17":{"tf":1.0},"187":{"tf":1.0},"202":{"tf":1.4142135623730951},"206":{"tf":1.0},"207":{"tf":1.7320508075688772},"208":{"tf":2.23606797749979},"209":{"tf":1.7320508075688772},"210":{"tf":1.4142135623730951},"213":{"tf":2.449489742783178},"214":{"tf":1.7320508075688772},"217":{"tf":1.0},"218":{"tf":1.4142135623730951},"225":{"tf":1.0},"242":{"tf":1.0},"7":{"tf":1.0}}}}},"n":{"df":0,"docs":{},"e":{"df":11,"docs":{"115":{"tf":1.0},"117":{"tf":1.4142135623730951},"126":{"tf":1.0},"165":{"tf":1.0},"17":{"tf":1.0},"172":{"tf":1.0},"18":{"tf":1.0},"200":{"tf":1.4142135623730951},"237":{"tf":1.4142135623730951},"29":{"tf":1.4142135623730951},"34":{"tf":1.4142135623730951}}},"k":{"a":{"df":0,"docs":{},"g":{"df":2,"docs":{"225":{"tf":1.0},"282":{"tf":1.0}}}},"df":14,"docs":{"11":{"tf":1.0},"119":{"tf":1.0},"147":{"tf":1.4142135623730951},"167":{"tf":1.0},"185":{"tf":1.4142135623730951},"218":{"tf":1.4142135623730951},"263":{"tf":1.0},"271":{"tf":1.7320508075688772},"278":{"tf":1.0},"282":{"tf":2.0},"37":{"tf":1.0},"40":{"tf":1.0},"58":{"tf":1.0},"60":{"tf":1.0}}},"t":{"df":2,"docs":{"274":{"tf":1.0},"92":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"7":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"x":{"df":5,"docs":{"11":{"tf":1.4142135623730951},"13":{"tf":1.0},"17":{"tf":1.4142135623730951},"280":{"tf":1.4142135623730951},"37":{"tf":1.7320508075688772}}}}},"s":{"df":0,"docs":{},"t":{"df":33,"docs":{"106":{"tf":1.7320508075688772},"109":{"tf":1.0},"134":{"tf":1.0},"137":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0},"17":{"tf":1.0},"188":{"tf":1.0},"200":{"tf":1.0},"203":{"tf":1.0},"204":{"tf":1.0},"21":{"tf":1.0},"220":{"tf":1.0},"233":{"tf":1.7320508075688772},"236":{"tf":1.0},"242":{"tf":1.7320508075688772},"251":{"tf":1.0},"259":{"tf":1.0},"264":{"tf":1.0},"266":{"tf":1.4142135623730951},"274":{"tf":1.0},"28":{"tf":1.0},"31":{"tf":1.0},"33":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.4142135623730951},"6":{"tf":1.0},"61":{"tf":1.0},"68":{"tf":1.0},"69":{"tf":1.0},"73":{"tf":2.23606797749979}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"62":{"tf":1.0},"89":{"tf":1.0}}}},"t":{"df":0,"docs":{},"l":{"df":9,"docs":{"104":{"tf":1.0},"170":{"tf":1.4142135623730951},"200":{"tf":1.0},"221":{"tf":1.0},"237":{"tf":1.0},"252":{"tf":1.0},"26":{"tf":1.0},"37":{"tf":1.0},"6":{"tf":1.0}}}}},"v":{"df":0,"docs":{},"e":{"df":11,"docs":{"130":{"tf":1.0},"229":{"tf":1.0},"231":{"tf":1.0},"252":{"tf":1.0},"43":{"tf":1.0},"46":{"tf":1.4142135623730951},"48":{"tf":1.0},"51":{"tf":1.0},"59":{"tf":1.0},"69":{"tf":1.0},"75":{"tf":1.0}}}}},"l":{"d":{"b":{"df":1,"docs":{"117":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":1,"docs":{"37":{"tf":1.0}}}}},"n":{"df":1,"docs":{"11":{"tf":1.0}}},"o":{"a":{"d":{"df":7,"docs":{"114":{"tf":1.4142135623730951},"184":{"tf":1.4142135623730951},"22":{"tf":1.0},"222":{"tf":1.4142135623730951},"224":{"tf":1.0},"226":{"tf":1.0},"229":{"tf":1.0}}},"df":0,"docs":{}},"c":{"a":{"df":0,"docs":{},"l":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":4,"docs":{"17":{"tf":1.0},"18":{"tf":1.4142135623730951},"20":{"tf":1.0},"22":{"tf":1.4142135623730951}}}}}}}}}}},"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"/":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"g":{"df":2,"docs":{"248":{"tf":1.4142135623730951},"249":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":1,"docs":{"249":{"tf":1.0}}}}}}}}},"df":45,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"11":{"tf":1.4142135623730951},"118":{"tf":1.0},"124":{"tf":1.4142135623730951},"15":{"tf":2.23606797749979},"16":{"tf":2.0},"17":{"tf":1.0},"18":{"tf":2.23606797749979},"19":{"tf":1.0},"194":{"tf":1.0},"198":{"tf":1.0},"20":{"tf":1.0},"21":{"tf":1.0},"22":{"tf":2.23606797749979},"224":{"tf":1.0},"23":{"tf":3.3166247903554},"24":{"tf":2.0},"242":{"tf":1.7320508075688772},"245":{"tf":1.7320508075688772},"246":{"tf":1.4142135623730951},"248":{"tf":1.4142135623730951},"249":{"tf":2.6457513110645907},"25":{"tf":2.0},"250":{"tf":2.23606797749979},"257":{"tf":1.0},"26":{"tf":2.23606797749979},"27":{"tf":1.0},"28":{"tf":1.4142135623730951},"29":{"tf":2.0},"30":{"tf":1.7320508075688772},"31":{"tf":2.6457513110645907},"32":{"tf":3.1622776601683795},"33":{"tf":1.4142135623730951},"34":{"tf":2.23606797749979},"35":{"tf":1.7320508075688772},"36":{"tf":2.8284271247461903},"37":{"tf":2.8284271247461903},"45":{"tf":1.0},"49":{"tf":1.4142135623730951},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"67":{"tf":1.0},"99":{"tf":1.0}},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"190":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{">":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"/":{"c":{"/":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"{":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"}":{"/":{".":{"df":0,"docs":{},"m":{"2":{"/":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"<":{"/":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"13":{"tf":1.0}}}}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}}}},"t":{"df":10,"docs":{"106":{"tf":1.0},"12":{"tf":1.0},"14":{"tf":1.0},"158":{"tf":1.0},"201":{"tf":1.0},"22":{"tf":1.0},"224":{"tf":1.0},"267":{"tf":1.7320508075688772},"279":{"tf":1.7320508075688772},"82":{"tf":1.0}}}},"df":0,"docs":{},"k":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"x":{"df":2,"docs":{"228":{"tf":1.4142135623730951},"234":{"tf":1.0}}}}},"df":3,"docs":{"142":{"tf":1.0},"205":{"tf":1.4142135623730951},"64":{"tf":1.0}}}},"df":0,"docs":{},"g":{"c":{"a":{"df":0,"docs":{},"t":{"df":2,"docs":{"126":{"tf":2.449489742783178},"37":{"tf":1.0}}}},"df":0,"docs":{}},"df":10,"docs":{"116":{"tf":1.0},"125":{"tf":2.6457513110645907},"126":{"tf":2.8284271247461903},"127":{"tf":1.7320508075688772},"134":{"tf":1.7320508075688772},"242":{"tf":1.0},"268":{"tf":1.7320508075688772},"280":{"tf":1.0},"37":{"tf":1.0},"82":{"tf":1.0}},"i":{"c":{"df":19,"docs":{"140":{"tf":1.0},"141":{"tf":1.0},"145":{"tf":2.449489742783178},"146":{"tf":1.0},"149":{"tf":1.0},"154":{"tf":1.0},"158":{"tf":1.0},"208":{"tf":1.0},"244":{"tf":1.0},"254":{"tf":1.0},"256":{"tf":1.4142135623730951},"258":{"tf":1.0},"282":{"tf":1.4142135623730951},"73":{"tf":1.0},"78":{"tf":1.0},"79":{"tf":1.0},"81":{"tf":1.0},"82":{"tf":1.4142135623730951},"84":{"tf":1.4142135623730951}}},"df":0,"docs":{},"n":{"df":15,"docs":{"112":{"tf":1.0},"134":{"tf":1.0},"139":{"tf":1.4142135623730951},"140":{"tf":1.7320508075688772},"141":{"tf":1.4142135623730951},"145":{"tf":2.6457513110645907},"146":{"tf":2.23606797749979},"147":{"tf":1.0},"221":{"tf":1.0},"228":{"tf":1.4142135623730951},"232":{"tf":1.0},"233":{"tf":2.0},"244":{"tf":1.0},"264":{"tf":1.4142135623730951},"78":{"tf":1.0}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"132":{"tf":1.0}}}},"df":0,"docs":{}}}}}}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":1,"docs":{"126":{"tf":1.0}}}}}}}},"n":{"df":0,"docs":{},"g":{"df":17,"docs":{"104":{"tf":1.0},"105":{"tf":1.0},"115":{"tf":1.4142135623730951},"132":{"tf":1.0},"140":{"tf":1.0},"164":{"tf":1.0},"170":{"tf":1.0},"204":{"tf":1.0},"205":{"tf":1.0},"212":{"tf":1.0},"215":{"tf":1.0},"22":{"tf":1.0},"229":{"tf":1.0},"25":{"tf":1.0},"258":{"tf":1.0},"264":{"tf":1.0},"59":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":7,"docs":{"128":{"tf":1.0},"140":{"tf":1.0},"180":{"tf":1.0},"205":{"tf":1.0},"206":{"tf":1.7320508075688772},"226":{"tf":1.0},"86":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"k":{"df":19,"docs":{"102":{"tf":1.4142135623730951},"103":{"tf":1.0},"158":{"tf":1.0},"215":{"tf":1.0},"216":{"tf":1.0},"22":{"tf":1.0},"221":{"tf":1.0},"224":{"tf":1.4142135623730951},"280":{"tf":1.0},"283":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"6":{"tf":1.0},"62":{"tf":1.0},"73":{"tf":1.7320508075688772},"75":{"tf":1.4142135623730951},"82":{"tf":1.0},"86":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"203":{"tf":1.0},"218":{"tf":1.0}}}}},"p":{"df":5,"docs":{"190":{"tf":1.0},"193":{"tf":1.0},"197":{"tf":1.4142135623730951},"249":{"tf":1.0},"82":{"tf":1.0}}},"s":{"df":1,"docs":{"240":{"tf":1.0}}}},"s":{"df":0,"docs":{},"e":{"df":5,"docs":{"204":{"tf":1.0},"206":{"tf":1.0},"208":{"tf":1.0},"210":{"tf":1.4142135623730951},"213":{"tf":1.4142135623730951}}},"s":{"df":2,"docs":{"157":{"tf":1.0},"209":{"tf":1.0}}},"t":{"df":2,"docs":{"158":{"tf":1.0},"231":{"tf":1.0}}}},"t":{"df":4,"docs":{"115":{"tf":1.0},"116":{"tf":1.0},"212":{"tf":1.4142135623730951},"240":{"tf":1.0}}},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"a":{"c":{"6":{"4":{"df":1,"docs":{"202":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"w":{"df":16,"docs":{"103":{"tf":2.0},"209":{"tf":1.4142135623730951},"220":{"tf":1.0},"225":{"tf":1.0},"226":{"tf":2.23606797749979},"229":{"tf":1.7320508075688772},"230":{"tf":1.7320508075688772},"232":{"tf":1.0},"233":{"tf":2.0},"234":{"tf":1.0},"235":{"tf":1.4142135623730951},"236":{"tf":1.0},"237":{"tf":1.4142135623730951},"249":{"tf":1.0},"281":{"tf":1.0},"37":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"c":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"c":{"a":{"df":0,"docs":{},"s":{"df":2,"docs":{"96":{"tf":1.0},"99":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":3,"docs":{"209":{"tf":1.0},"225":{"tf":1.7320508075688772},"99":{"tf":1.0}}}}}},"u":{"c":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"258":{"tf":1.0}}},"df":0,"docs":{}},"k":{"df":1,"docs":{"90":{"tf":1.0}}}},"df":0,"docs":{}}},"m":{"1":{"df":7,"docs":{"161":{"tf":1.0},"162":{"tf":1.0},"163":{"tf":1.0},"169":{"tf":1.0},"17":{"tf":1.0},"170":{"tf":1.0},"175":{"tf":1.0}}},"2":{"df":1,"docs":{"13":{"tf":1.4142135623730951}}},"a":{"c":{"df":3,"docs":{"280":{"tf":1.0},"37":{"tf":1.0},"73":{"tf":1.0}},"h":{"df":2,"docs":{"122":{"tf":1.0},"124":{"tf":1.4142135623730951}},"i":{"df":0,"docs":{},"n":{"df":10,"docs":{"161":{"tf":1.0},"162":{"tf":1.0},"17":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"175":{"tf":1.0},"223":{"tf":1.4142135623730951},"280":{"tf":1.4142135623730951},"45":{"tf":1.0}}}}},"o":{"df":2,"docs":{"11":{"tf":1.0},"280":{"tf":1.7320508075688772}}},"r":{"df":0,"docs":{},"o":{"df":3,"docs":{"200":{"tf":1.0},"43":{"tf":1.0},"93":{"tf":1.0}}}}},"d":{"df":0,"docs":{},"e":{"df":15,"docs":{"121":{"tf":1.0},"122":{"tf":1.0},"136":{"tf":1.0},"138":{"tf":1.0},"167":{"tf":1.0},"192":{"tf":1.0},"193":{"tf":1.0},"194":{"tf":1.0},"200":{"tf":1.0},"21":{"tf":1.0},"226":{"tf":1.0},"227":{"tf":1.0},"231":{"tf":1.0},"233":{"tf":1.0},"82":{"tf":1.0}}},"r":{"df":3,"docs":{"134":{"tf":1.4142135623730951},"137":{"tf":1.0},"138":{"tf":2.0}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"228":{"tf":1.0}}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"274":{"tf":1.0}}},"n":{"df":24,"docs":{"103":{"tf":2.0},"119":{"tf":1.0},"121":{"tf":1.0},"126":{"tf":1.0},"170":{"tf":1.0},"182":{"tf":1.0},"193":{"tf":1.0},"194":{"tf":1.0},"201":{"tf":1.0},"224":{"tf":1.0},"246":{"tf":1.0},"251":{"tf":1.0},"254":{"tf":1.0},"260":{"tf":1.0},"268":{"tf":1.7320508075688772},"269":{"tf":1.0},"270":{"tf":1.0},"274":{"tf":2.6457513110645907},"40":{"tf":1.7320508075688772},"51":{"tf":1.0},"64":{"tf":1.0},"7":{"tf":1.0},"70":{"tf":1.0},"82":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":4,"docs":{"14":{"tf":1.0},"180":{"tf":1.0},"253":{"tf":1.4142135623730951},"278":{"tf":1.0}}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":12,"docs":{"146":{"tf":1.0},"162":{"tf":1.0},"167":{"tf":1.0},"169":{"tf":1.0},"170":{"tf":1.4142135623730951},"171":{"tf":1.0},"172":{"tf":1.0},"196":{"tf":1.0},"203":{"tf":1.0},"260":{"tf":1.0},"263":{"tf":1.0},"274":{"tf":1.0}}}},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"220":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"64":{"tf":1.0}}},"df":5,"docs":{"138":{"tf":1.0},"164":{"tf":1.0},"172":{"tf":1.0},"208":{"tf":1.0},"274":{"tf":1.0}}}}}}},"j":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":8,"docs":{"116":{"tf":1.0},"208":{"tf":1.0},"262":{"tf":1.0},"269":{"tf":1.0},"270":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"58":{"tf":1.0}}}}},"k":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"g":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"253":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":64,"docs":{"102":{"tf":1.0},"104":{"tf":1.0},"105":{"tf":1.0},"106":{"tf":1.4142135623730951},"107":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"11":{"tf":1.7320508075688772},"111":{"tf":1.0},"116":{"tf":1.4142135623730951},"117":{"tf":1.4142135623730951},"118":{"tf":1.0},"120":{"tf":1.0},"122":{"tf":1.4142135623730951},"124":{"tf":1.7320508075688772},"126":{"tf":1.0},"129":{"tf":1.0},"14":{"tf":1.0},"145":{"tf":1.0},"146":{"tf":1.0},"149":{"tf":1.0},"163":{"tf":1.0},"165":{"tf":1.0},"169":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"188":{"tf":1.0},"197":{"tf":1.0},"198":{"tf":1.0},"200":{"tf":1.0},"21":{"tf":1.0},"214":{"tf":1.0},"226":{"tf":1.0},"227":{"tf":1.0},"241":{"tf":1.4142135623730951},"25":{"tf":1.4142135623730951},"250":{"tf":1.0},"26":{"tf":1.0},"263":{"tf":1.0},"264":{"tf":1.0},"270":{"tf":1.0},"272":{"tf":1.0},"274":{"tf":1.0},"278":{"tf":1.0},"280":{"tf":1.0},"282":{"tf":1.0},"32":{"tf":1.0},"36":{"tf":1.0},"39":{"tf":1.4142135623730951},"40":{"tf":1.0},"43":{"tf":1.7320508075688772},"46":{"tf":1.0},"51":{"tf":1.0},"57":{"tf":1.4142135623730951},"58":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0},"7":{"tf":1.0},"70":{"tf":1.0},"72":{"tf":1.0},"73":{"tf":1.4142135623730951},"75":{"tf":1.0},"79":{"tf":1.0}}}},"l":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":1,"docs":{"274":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"n":{"a":{"df":0,"docs":{},"g":{"df":54,"docs":{"115":{"tf":1.0},"117":{"tf":1.0},"132":{"tf":1.7320508075688772},"140":{"tf":1.0},"146":{"tf":1.4142135623730951},"171":{"tf":1.0},"172":{"tf":1.0},"174":{"tf":1.4142135623730951},"185":{"tf":1.0},"187":{"tf":1.0},"189":{"tf":1.0},"197":{"tf":1.0},"201":{"tf":1.0},"219":{"tf":1.7320508075688772},"220":{"tf":1.4142135623730951},"221":{"tf":1.0},"225":{"tf":3.1622776601683795},"226":{"tf":3.3166247903554},"227":{"tf":1.0},"228":{"tf":2.449489742783178},"229":{"tf":2.23606797749979},"23":{"tf":2.0},"230":{"tf":1.0},"231":{"tf":1.0},"232":{"tf":2.0},"233":{"tf":1.7320508075688772},"234":{"tf":1.0},"235":{"tf":1.0},"236":{"tf":1.4142135623730951},"237":{"tf":1.0},"238":{"tf":1.0},"239":{"tf":1.4142135623730951},"240":{"tf":1.0},"241":{"tf":1.4142135623730951},"242":{"tf":1.7320508075688772},"244":{"tf":1.0},"245":{"tf":1.7320508075688772},"253":{"tf":1.0},"268":{"tf":1.7320508075688772},"269":{"tf":1.4142135623730951},"274":{"tf":1.4142135623730951},"32":{"tf":2.0},"48":{"tf":1.0},"63":{"tf":1.7320508075688772},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0},"67":{"tf":1.0},"69":{"tf":1.0},"7":{"tf":1.0},"73":{"tf":2.0},"78":{"tf":1.0},"79":{"tf":1.0},"81":{"tf":1.7320508075688772}}}},"df":3,"docs":{"201":{"tf":1.4142135623730951},"236":{"tf":1.4142135623730951},"242":{"tf":1.7320508075688772}},"i":{"df":19,"docs":{"106":{"tf":1.4142135623730951},"109":{"tf":1.0},"126":{"tf":1.0},"152":{"tf":1.0},"154":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0},"175":{"tf":1.0},"204":{"tf":1.4142135623730951},"213":{"tf":1.0},"214":{"tf":1.0},"228":{"tf":1.0},"257":{"tf":1.0},"283":{"tf":1.0},"59":{"tf":1.0},"62":{"tf":1.0},"64":{"tf":1.0},"75":{"tf":1.0},"79":{"tf":1.4142135623730951}},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"70":{"tf":1.0}}}}}}},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"126":{"tf":1.0}}}}},"u":{"a":{"df":0,"docs":{},"l":{"df":18,"docs":{"101":{"tf":1.0},"111":{"tf":1.0},"119":{"tf":1.0},"121":{"tf":1.0},"17":{"tf":1.0},"20":{"tf":1.7320508075688772},"22":{"tf":1.7320508075688772},"25":{"tf":1.4142135623730951},"27":{"tf":2.0},"274":{"tf":1.4142135623730951},"31":{"tf":1.0},"36":{"tf":1.0},"39":{"tf":1.0},"49":{"tf":1.4142135623730951},"52":{"tf":1.0},"59":{"tf":1.0},"82":{"tf":1.0},"84":{"tf":1.0}}}},"df":0,"docs":{}}},"p":{"df":9,"docs":{"106":{"tf":1.0},"11":{"tf":1.4142135623730951},"116":{"tf":1.0},"12":{"tf":1.0},"232":{"tf":1.0},"251":{"tf":1.0},"252":{"tf":1.4142135623730951},"28":{"tf":1.0},"33":{"tf":1.0}}},"r":{"df":0,"docs":{},"k":{"d":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":0,"docs":{},"n":{"df":4,"docs":{"134":{"tf":1.0},"135":{"tf":1.4142135623730951},"137":{"tf":1.0},"284":{"tf":1.0}}}}}},"df":1,"docs":{"171":{"tf":1.0}},"h":{"df":1,"docs":{"276":{"tf":1.0}}}},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"197":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":2,"docs":{"242":{"tf":1.0},"81":{"tf":1.0}}}}}},"t":{"c":{"df":0,"docs":{},"h":{"df":12,"docs":{"104":{"tf":1.0},"105":{"tf":1.4142135623730951},"124":{"tf":1.0},"165":{"tf":1.0},"20":{"tf":1.0},"201":{"tf":1.0},"213":{"tf":1.0},"22":{"tf":1.4142135623730951},"242":{"tf":1.0},"268":{"tf":1.0},"40":{"tf":1.0},"87":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"174":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"df":2,"docs":{"122":{"tf":1.4142135623730951},"3":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"82":{"tf":1.0}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{".":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{".":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":1,"docs":{"274":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":7,"docs":{"11":{"tf":1.0},"13":{"tf":2.449489742783178},"16":{"tf":1.0},"18":{"tf":1.0},"22":{"tf":1.0},"271":{"tf":2.23606797749979},"274":{"tf":1.0}},"l":{"df":0,"docs":{},"o":{"c":{"df":1,"docs":{"20":{"tf":1.0}}},"df":0,"docs":{}}}}}},"y":{"b":{"df":1,"docs":{"51":{"tf":1.0}}},"df":0,"docs":{}}},"d":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":2,"docs":{"284":{"tf":1.0},"286":{"tf":2.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"n":{"df":25,"docs":{"118":{"tf":1.0},"128":{"tf":1.0},"13":{"tf":1.0},"140":{"tf":1.0},"175":{"tf":1.0},"190":{"tf":1.0},"201":{"tf":1.0},"204":{"tf":1.7320508075688772},"205":{"tf":1.0},"207":{"tf":1.7320508075688772},"208":{"tf":1.0},"209":{"tf":1.4142135623730951},"213":{"tf":1.0},"22":{"tf":1.0},"224":{"tf":1.0},"232":{"tf":1.0},"242":{"tf":1.0},"250":{"tf":1.0},"263":{"tf":1.0},"45":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"82":{"tf":1.4142135623730951},"86":{"tf":1.4142135623730951},"89":{"tf":1.4142135623730951}}},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":4,"docs":{"203":{"tf":1.0},"274":{"tf":1.4142135623730951},"59":{"tf":1.0},"64":{"tf":1.0}}}}}},"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"n":{"df":3,"docs":{"226":{"tf":1.0},"231":{"tf":1.0},"274":{"tf":1.0}}}},"df":0,"docs":{}}},"d":{"df":0,"docs":{},"i":{"a":{"=":{"\"":{"(":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":1,"docs":{"283":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"n":{"df":1,"docs":{"204":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":2,"docs":{"111":{"tf":1.0},"220":{"tf":1.0}}}},"g":{"a":{"df":0,"docs":{},"z":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"d":{"'":{")":{".":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"}":{"/":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"j":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"s":{"/":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"k":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":1,"docs":{"224":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"k":{"df":0,"docs":{},"s":{"[":{"\"":{"c":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"224":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":16,"docs":{"105":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"197":{"tf":1.0},"219":{"tf":1.4142135623730951},"220":{"tf":2.23606797749979},"221":{"tf":2.449489742783178},"222":{"tf":2.6457513110645907},"223":{"tf":2.0},"224":{"tf":2.0},"251":{"tf":1.0},"252":{"tf":1.4142135623730951},"46":{"tf":1.0},"69":{"tf":1.0},"70":{"tf":1.0},"73":{"tf":2.23606797749979}},"s":{"/":{"df":0,"docs":{},"f":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"/":{"c":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":1,"docs":{"69":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{},"s":{"df":0,"docs":{},"r":{"c":{"/":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"b":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"69":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"i":{"df":0,"docs":{},"o":{"df":9,"docs":{"109":{"tf":1.4142135623730951},"14":{"tf":1.4142135623730951},"252":{"tf":1.0},"28":{"tf":1.0},"29":{"tf":1.0},"33":{"tf":1.0},"46":{"tf":1.0},"69":{"tf":1.4142135623730951},"73":{"tf":2.449489742783178}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"m":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":3,"docs":{"3":{"tf":1.0},"69":{"tf":1.4142135623730951},"8":{"tf":1.0}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":3,"docs":{"116":{"tf":1.7320508075688772},"132":{"tf":1.0},"264":{"tf":1.0}}}}},"s":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"51":{"tf":1.0}}}},"df":0,"docs":{}}},"n":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"105":{"tf":1.0}}},"t":{"df":1,"docs":{"60":{"tf":1.0}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":3,"docs":{"204":{"tf":1.0},"210":{"tf":1.0},"242":{"tf":1.0}}}}}},"u":{"_":{"_":{"_":{"_":{"_":{"_":{"_":{"_":{"df":1,"docs":{"62":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":3,"docs":{"12":{"tf":1.0},"126":{"tf":1.0},"62":{"tf":1.0}}}},"r":{"df":0,"docs":{},"g":{"df":9,"docs":{"118":{"tf":1.0},"119":{"tf":2.449489742783178},"158":{"tf":1.0},"249":{"tf":1.0},"250":{"tf":1.0},"268":{"tf":1.4142135623730951},"270":{"tf":1.0},"40":{"tf":1.0},"7":{"tf":1.4142135623730951}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":1,"docs":{"193":{"tf":1.0}}}}},"m":{"a":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"286":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"s":{"a":{"df":0,"docs":{},"g":{"df":4,"docs":{"104":{"tf":1.0},"105":{"tf":1.0},"106":{"tf":2.6457513110645907},"126":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"a":{"/":{"df":0,"docs":{},"g":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"b":{"df":4,"docs":{"226":{"tf":1.0},"228":{"tf":1.0},"233":{"tf":1.0},"245":{"tf":1.0}}},"df":0,"docs":{}}}}},"d":{"a":{"df":0,"docs":{},"t":{"a":{".":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"a":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"d":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"a":{"df":1,"docs":{"283":{"tf":1.0}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":10,"docs":{"245":{"tf":1.0},"248":{"tf":1.0},"249":{"tf":1.0},"252":{"tf":1.0},"254":{"tf":1.0},"271":{"tf":1.4142135623730951},"70":{"tf":1.0},"80":{"tf":1.0},"81":{"tf":1.0},"87":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":4,"docs":{"231":{"tf":1.0},"261":{"tf":1.0},"80":{"tf":1.4142135623730951},"84":{"tf":1.4142135623730951}}},"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"d":{"df":6,"docs":{"131":{"tf":2.0},"133":{"tf":1.0},"16":{"tf":1.0},"245":{"tf":1.7320508075688772},"268":{"tf":1.0},"283":{"tf":1.0}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"i":{"c":{"df":5,"docs":{"184":{"tf":1.0},"219":{"tf":1.0},"253":{"tf":1.0},"259":{"tf":1.7320508075688772},"283":{"tf":1.0}}},"df":0,"docs":{}}}}},"h":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"186":{"tf":1.0},"202":{"tf":1.0}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"i":{"b":{"df":1,"docs":{"206":{"tf":1.0}}},"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":1,"docs":{"137":{"tf":1.0}}}}},"df":0,"docs":{}}},"d":{"d":{"df":0,"docs":{},"l":{"df":2,"docs":{"156":{"tf":1.0},"201":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"df":1,"docs":{"165":{"tf":1.0}},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":21,"docs":{"134":{"tf":1.0},"145":{"tf":1.0},"154":{"tf":1.4142135623730951},"167":{"tf":1.4142135623730951},"169":{"tf":1.0},"202":{"tf":1.4142135623730951},"203":{"tf":2.23606797749979},"204":{"tf":3.4641016151377544},"205":{"tf":1.7320508075688772},"206":{"tf":1.0},"207":{"tf":2.449489742783178},"208":{"tf":1.7320508075688772},"209":{"tf":1.4142135623730951},"212":{"tf":2.6457513110645907},"213":{"tf":1.4142135623730951},"214":{"tf":1.7320508075688772},"216":{"tf":1.0},"217":{"tf":1.7320508075688772},"218":{"tf":1.7320508075688772},"257":{"tf":1.0},"258":{"tf":1.0}}}},"df":0,"docs":{}}},"h":{"a":{"df":0,"docs":{},"i":{"df":1,"docs":{"274":{"tf":1.0}}}},"df":0,"docs":{}},"l":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"105":{"tf":1.0}}}},"df":0,"docs":{}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"204":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"n":{"d":{"df":2,"docs":{"242":{"tf":1.0},"63":{"tf":1.0}}},"df":0,"docs":{},"e":{"df":1,"docs":{"37":{"tf":1.0}}},"i":{"df":0,"docs":{},"m":{"df":5,"docs":{"118":{"tf":1.0},"163":{"tf":1.0},"199":{"tf":1.0},"226":{"tf":1.0},"258":{"tf":1.0}},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"263":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"262":{"tf":1.0}}}},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"104":{"tf":1.0},"20":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":2,"docs":{"248":{"tf":1.0},"249":{"tf":1.4142135623730951}}}}}},"s":{"c":{"df":1,"docs":{"240":{"tf":1.4142135623730951}}},"df":0,"docs":{},"i":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":1,"docs":{"157":{"tf":1.0}},"i":{"df":1,"docs":{"158":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"m":{"a":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"121":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"s":{"df":2,"docs":{"109":{"tf":1.0},"278":{"tf":1.0}}},"t":{"a":{"df":0,"docs":{},"k":{"df":1,"docs":{"79":{"tf":1.0}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"/":{"c":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":1,"docs":{"116":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":1,"docs":{"210":{"tf":1.0}}}}}},"m":{"df":1,"docs":{"268":{"tf":1.0}}},"o":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":14,"docs":{"119":{"tf":1.0},"134":{"tf":1.0},"186":{"tf":1.7320508075688772},"187":{"tf":1.7320508075688772},"188":{"tf":1.0},"190":{"tf":1.4142135623730951},"192":{"tf":1.0},"194":{"tf":1.0},"197":{"tf":1.0},"200":{"tf":2.8284271247461903},"201":{"tf":1.4142135623730951},"232":{"tf":1.0},"274":{"tf":1.0},"62":{"tf":1.0}},"e":{"'":{"df":1,"docs":{"178":{"tf":1.0}}},"_":{"_":{"_":{"_":{"_":{"_":{"df":1,"docs":{"62":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"c":{"df":0,"docs":{},"k":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"51":{"tf":1.0}}}},"df":0,"docs":{}},"df":2,"docs":{"43":{"tf":1.7320508075688772},"51":{"tf":1.0}},"i":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":1,"docs":{"43":{"tf":1.0}}}}},"df":0,"docs":{}}}},"d":{"df":3,"docs":{"60":{"tf":1.0},"61":{"tf":2.0},"62":{"tf":1.0}},"e":{"df":2,"docs":{"236":{"tf":1.0},"79":{"tf":1.0}},"l":{"df":8,"docs":{"105":{"tf":1.0},"145":{"tf":1.0},"184":{"tf":1.0},"194":{"tf":1.0},"197":{"tf":1.7320508075688772},"201":{"tf":1.4142135623730951},"274":{"tf":1.0},"51":{"tf":1.0}}},"r":{"df":0,"docs":{},"n":{"df":2,"docs":{"161":{"tf":1.0},"169":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"f":{"df":1,"docs":{"256":{"tf":1.0}},"i":{"df":12,"docs":{"183":{"tf":1.0},"196":{"tf":1.0},"197":{"tf":1.0},"201":{"tf":1.4142135623730951},"249":{"tf":1.0},"250":{"tf":1.0},"256":{"tf":1.0},"274":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":2.0},"7":{"tf":1.4142135623730951}}}}},"u":{"df":0,"docs":{},"l":{"df":13,"docs":{"103":{"tf":1.0},"105":{"tf":1.0},"175":{"tf":1.0},"222":{"tf":1.0},"251":{"tf":1.0},"252":{"tf":1.4142135623730951},"28":{"tf":1.0},"282":{"tf":1.4142135623730951},"33":{"tf":1.0},"61":{"tf":1.7320508075688772},"62":{"tf":1.0},"83":{"tf":1.4142135623730951},"93":{"tf":1.0}},"e":{".":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"p":{"df":1,"docs":{"252":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},":":{"\"":{"df":0,"docs":{},"j":{"df":0,"docs":{},"n":{"a":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"58":{"tf":1.0}}}}}},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"152":{"tf":1.0}}}}}},"o":{"df":1,"docs":{"18":{"tf":1.0}}},"t":{"df":0,"docs":{},"h":{"df":2,"docs":{"213":{"tf":1.4142135623730951},"278":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"e":{"_":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"43":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":39,"docs":{"10":{"tf":1.0},"101":{"tf":1.0},"105":{"tf":1.0},"106":{"tf":1.4142135623730951},"126":{"tf":1.4142135623730951},"134":{"tf":1.0},"161":{"tf":1.0},"166":{"tf":1.0},"169":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"177":{"tf":1.0},"194":{"tf":1.0},"200":{"tf":1.0},"205":{"tf":1.7320508075688772},"209":{"tf":1.4142135623730951},"210":{"tf":1.4142135623730951},"213":{"tf":1.7320508075688772},"225":{"tf":1.0},"227":{"tf":1.0},"229":{"tf":1.4142135623730951},"23":{"tf":1.0},"231":{"tf":1.0},"232":{"tf":1.4142135623730951},"242":{"tf":1.0},"274":{"tf":1.4142135623730951},"32":{"tf":1.4142135623730951},"36":{"tf":1.0},"50":{"tf":1.0},"51":{"tf":1.0},"6":{"tf":1.7320508075688772},"60":{"tf":1.4142135623730951},"61":{"tf":1.0},"7":{"tf":1.4142135623730951},"73":{"tf":1.0},"78":{"tf":1.4142135623730951},"93":{"tf":1.0},"96":{"tf":1.0},"99":{"tf":1.0}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":2,"docs":{"224":{"tf":1.0},"282":{"tf":1.0}}}}}},"v":{"df":0,"docs":{},"e":{"df":20,"docs":{"103":{"tf":1.0},"105":{"tf":1.0},"11":{"tf":1.4142135623730951},"115":{"tf":1.0},"151":{"tf":1.0},"158":{"tf":1.7320508075688772},"166":{"tf":1.4142135623730951},"167":{"tf":1.4142135623730951},"170":{"tf":1.4142135623730951},"174":{"tf":1.0},"190":{"tf":1.0},"197":{"tf":1.0},"198":{"tf":1.0},"200":{"tf":1.7320508075688772},"218":{"tf":1.0},"225":{"tf":1.4142135623730951},"228":{"tf":1.0},"230":{"tf":1.0},"273":{"tf":1.0},"68":{"tf":1.0}}}},"z":{"_":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"62":{"tf":2.0}},"s":{"\"":{")":{".":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"w":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":1,"docs":{"62":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":4,"docs":{"121":{"tf":1.0},"122":{"tf":1.4142135623730951},"276":{"tf":1.7320508075688772},"277":{"tf":1.0}},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"'":{"df":2,"docs":{"187":{"tf":1.0},"253":{"tf":1.0}}},".":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"$":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"112":{"tf":1.0}}}},"df":0,"docs":{}}},"<":{"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"_":{"c":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"70":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"a":{"c":{"df":1,"docs":{"100":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{".":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"222":{"tf":1.0}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":1,"docs":{"222":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":3,"docs":{"163":{"tf":1.4142135623730951},"166":{"tf":1.0},"31":{"tf":1.0}}},"df":0,"docs":{}}}}}}}}},"df":23,"docs":{"119":{"tf":1.0},"120":{"tf":2.23606797749979},"121":{"tf":1.0},"122":{"tf":1.7320508075688772},"123":{"tf":2.23606797749979},"124":{"tf":2.23606797749979},"161":{"tf":1.0},"162":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"2":{"tf":1.0},"201":{"tf":2.0},"258":{"tf":1.0},"260":{"tf":1.4142135623730951},"261":{"tf":1.7320508075688772},"262":{"tf":1.4142135623730951},"263":{"tf":1.0},"274":{"tf":1.7320508075688772},"278":{"tf":1.0},"3":{"tf":1.0},"45":{"tf":1.0},"8":{"tf":1.0}},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"73":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"h":{"df":1,"docs":{"252":{"tf":1.0}}},"x":{"c":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{".":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":2,"docs":{"28":{"tf":1.0},"29":{"tf":1.4142135623730951}}}}}},"df":1,"docs":{"29":{"tf":1.4142135623730951}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"73":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"73":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}}}}}}}}},"’":{"df":3,"docs":{"187":{"tf":1.0},"188":{"tf":1.0},"190":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"p":{"df":0,"docs":{},"l":{"df":3,"docs":{"2":{"tf":1.0},"64":{"tf":1.4142135623730951},"7":{"tf":1.0}}}},"s":{"df":1,"docs":{"204":{"tf":2.8284271247461903}},"g":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":1,"docs":{"106":{"tf":1.0}},"s":{":":{":":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"106":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"u":{"c":{"df":0,"docs":{},"h":{"df":12,"docs":{"103":{"tf":1.0},"104":{"tf":1.0},"171":{"tf":1.0},"175":{"tf":1.0},"231":{"tf":1.0},"238":{"tf":1.4142135623730951},"241":{"tf":1.0},"4":{"tf":1.0},"50":{"tf":1.0},"60":{"tf":1.0},"64":{"tf":1.0},"78":{"tf":1.0}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":4,"docs":{"174":{"tf":1.0},"22":{"tf":1.0},"255":{"tf":1.7320508075688772},"99":{"tf":1.0}},"p":{"df":0,"docs":{},"l":{"df":13,"docs":{"118":{"tf":1.0},"163":{"tf":1.0},"164":{"tf":1.0},"172":{"tf":1.4142135623730951},"174":{"tf":1.0},"204":{"tf":1.0},"213":{"tf":1.0},"232":{"tf":1.7320508075688772},"237":{"tf":1.0},"252":{"tf":1.0},"255":{"tf":1.0},"276":{"tf":1.0},"98":{"tf":1.0}}}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":1,"docs":{"83":{"tf":1.0}}}}}},"y":{"c":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"60":{"tf":2.23606797749979}}}},"df":0,"docs":{}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"61":{"tf":1.0}}}}}}}},"n":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"242":{"tf":1.0}}}},"m":{"df":0,"docs":{},"e":{"df":40,"docs":{"100":{"tf":1.0},"104":{"tf":1.0},"105":{"tf":1.4142135623730951},"108":{"tf":1.4142135623730951},"109":{"tf":1.4142135623730951},"112":{"tf":2.23606797749979},"114":{"tf":1.7320508075688772},"117":{"tf":1.0},"165":{"tf":1.0},"167":{"tf":1.0},"174":{"tf":1.0},"175":{"tf":1.4142135623730951},"200":{"tf":1.4142135623730951},"222":{"tf":1.0},"223":{"tf":1.0},"225":{"tf":1.0},"258":{"tf":1.0},"268":{"tf":1.7320508075688772},"269":{"tf":1.0},"277":{"tf":1.0},"28":{"tf":1.0},"282":{"tf":1.0},"33":{"tf":1.0},"37":{"tf":1.0},"39":{"tf":1.0},"60":{"tf":1.4142135623730951},"61":{"tf":1.0},"69":{"tf":1.0},"70":{"tf":1.4142135623730951},"72":{"tf":1.4142135623730951},"73":{"tf":1.0},"91":{"tf":2.0},"92":{"tf":1.0},"93":{"tf":2.0},"94":{"tf":1.0},"95":{"tf":1.0},"96":{"tf":1.4142135623730951},"97":{"tf":1.0},"98":{"tf":1.7320508075688772},"99":{"tf":2.23606797749979}},"s":{"df":0,"docs":{},"p":{"a":{"c":{"df":2,"docs":{"112":{"tf":1.0},"172":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"r":{"df":0,"docs":{},"r":{"df":1,"docs":{"286":{"tf":1.4142135623730951}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":6,"docs":{"0":{"tf":1.0},"11":{"tf":1.7320508075688772},"13":{"tf":1.4142135623730951},"19":{"tf":1.4142135623730951},"37":{"tf":2.23606797749979},"45":{"tf":1.0}},"e":{"/":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"37":{"tf":1.0}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"u":{"df":0,"docs":{},"r":{"df":2,"docs":{"154":{"tf":1.0},"155":{"tf":1.0}}}}},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":9,"docs":{"109":{"tf":1.4142135623730951},"205":{"tf":1.4142135623730951},"28":{"tf":1.0},"283":{"tf":1.0},"31":{"tf":1.0},"33":{"tf":1.0},"36":{"tf":1.0},"40":{"tf":1.0},"73":{"tf":2.449489742783178}}}}}},"d":{"df":0,"docs":{},"k":{"_":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"=":{"\"":{"$":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"k":{"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":0,"docs":{},"s":{"/":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"37":{"tf":1.0}}}}}}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"=":{"\"":{"$":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"/":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"/":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"/":{"df":0,"docs":{},"s":{"d":{"df":0,"docs":{},"k":{"/":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"k":{"/":{"2":{"5":{".":{"2":{".":{"9":{"5":{"1":{"9":{"6":{"5":{"3":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":2,"docs":{"12":{"tf":2.449489742783178},"37":{"tf":1.4142135623730951}}}},"df":2,"docs":{"20":{"tf":1.0},"22":{"tf":1.0}},"e":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":18,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"124":{"tf":1.0},"13":{"tf":1.0},"197":{"tf":1.0},"198":{"tf":1.0},"22":{"tf":1.0},"226":{"tf":1.4142135623730951},"231":{"tf":1.0},"241":{"tf":1.0},"242":{"tf":1.0},"25":{"tf":1.0},"264":{"tf":1.0},"27":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0},"67":{"tf":1.0},"81":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":3,"docs":{"124":{"tf":1.0},"262":{"tf":1.0},"43":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"k":{"df":0,"docs":{},"o":{"df":1,"docs":{"190":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"d":{"df":76,"docs":{"103":{"tf":1.0},"104":{"tf":1.7320508075688772},"105":{"tf":1.7320508075688772},"106":{"tf":1.0},"108":{"tf":1.4142135623730951},"109":{"tf":1.7320508075688772},"11":{"tf":1.0},"111":{"tf":1.0},"115":{"tf":1.4142135623730951},"116":{"tf":1.0},"122":{"tf":1.4142135623730951},"123":{"tf":1.0},"124":{"tf":2.23606797749979},"126":{"tf":1.0},"128":{"tf":1.0},"14":{"tf":1.4142135623730951},"140":{"tf":1.0},"145":{"tf":1.7320508075688772},"165":{"tf":1.0},"167":{"tf":1.4142135623730951},"17":{"tf":1.0},"171":{"tf":1.0},"197":{"tf":2.0},"200":{"tf":1.0},"21":{"tf":1.4142135623730951},"22":{"tf":1.4142135623730951},"220":{"tf":1.7320508075688772},"224":{"tf":1.7320508075688772},"225":{"tf":1.4142135623730951},"226":{"tf":1.0},"227":{"tf":1.0},"228":{"tf":1.0},"229":{"tf":2.23606797749979},"232":{"tf":1.4142135623730951},"233":{"tf":2.0},"234":{"tf":1.4142135623730951},"239":{"tf":1.0},"241":{"tf":1.7320508075688772},"242":{"tf":2.449489742783178},"249":{"tf":1.0},"25":{"tf":1.0},"250":{"tf":1.0},"256":{"tf":1.0},"258":{"tf":1.0},"26":{"tf":1.4142135623730951},"267":{"tf":1.0},"276":{"tf":1.0},"279":{"tf":1.0},"281":{"tf":1.0},"284":{"tf":1.0},"31":{"tf":1.0},"32":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.4142135623730951},"40":{"tf":1.0},"43":{"tf":1.0},"44":{"tf":1.0},"45":{"tf":1.7320508075688772},"46":{"tf":1.7320508075688772},"58":{"tf":1.7320508075688772},"59":{"tf":1.0},"6":{"tf":1.0},"61":{"tf":1.7320508075688772},"62":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"68":{"tf":1.0},"7":{"tf":1.4142135623730951},"73":{"tf":1.7320508075688772},"79":{"tf":1.4142135623730951},"81":{"tf":1.7320508075688772},"84":{"tf":1.7320508075688772},"86":{"tf":1.0},"89":{"tf":1.0},"9":{"tf":1.0},"90":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"g":{"df":3,"docs":{"166":{"tf":1.4142135623730951},"180":{"tf":1.0},"210":{"tf":1.7320508075688772}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":5,"docs":{"29":{"tf":1.0},"30":{"tf":1.0},"34":{"tf":1.0},"35":{"tf":1.0},"61":{"tf":1.0}}}}},"df":0,"docs":{}}}},"t":{".":{"df":0,"docs":{},"j":{"a":{"df":0,"docs":{},"v":{"a":{".":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{".":{"df":0,"docs":{},"j":{"df":0,"docs":{},"n":{"a":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":9,"docs":{"133":{"tf":1.0},"179":{"tf":1.0},"183":{"tf":1.0},"189":{"tf":1.0},"190":{"tf":1.0},"201":{"tf":1.0},"233":{"tf":2.0},"242":{"tf":1.0},"245":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"242":{"tf":1.0}}}}}}}}}}}},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"l":{"df":3,"docs":{"169":{"tf":1.7320508075688772},"170":{"tf":1.7320508075688772},"171":{"tf":2.23606797749979}}}},"df":0,"docs":{}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"283":{"tf":1.0},"83":{"tf":1.0}}}}},"w":{"df":99,"docs":{"100":{"tf":1.0},"101":{"tf":1.4142135623730951},"102":{"tf":1.4142135623730951},"103":{"tf":1.0},"104":{"tf":1.4142135623730951},"105":{"tf":1.7320508075688772},"106":{"tf":1.0},"107":{"tf":1.0},"108":{"tf":2.0},"109":{"tf":1.7320508075688772},"110":{"tf":1.0},"111":{"tf":1.0},"112":{"tf":1.0},"113":{"tf":1.0},"114":{"tf":1.0},"115":{"tf":1.0},"116":{"tf":1.0},"117":{"tf":1.0},"119":{"tf":1.4142135623730951},"120":{"tf":1.0},"122":{"tf":1.0},"123":{"tf":2.23606797749979},"124":{"tf":2.0},"134":{"tf":1.0},"141":{"tf":1.0},"145":{"tf":1.4142135623730951},"158":{"tf":1.4142135623730951},"161":{"tf":1.0},"166":{"tf":1.0},"167":{"tf":1.7320508075688772},"170":{"tf":1.0},"171":{"tf":1.7320508075688772},"172":{"tf":1.4142135623730951},"189":{"tf":1.4142135623730951},"196":{"tf":2.0},"197":{"tf":1.0},"198":{"tf":1.7320508075688772},"199":{"tf":1.4142135623730951},"20":{"tf":1.0},"200":{"tf":3.3166247903554},"203":{"tf":1.0},"208":{"tf":1.0},"212":{"tf":1.0},"225":{"tf":1.0},"23":{"tf":1.4142135623730951},"233":{"tf":1.4142135623730951},"242":{"tf":1.0},"246":{"tf":1.0},"249":{"tf":1.4142135623730951},"263":{"tf":1.0},"268":{"tf":2.23606797749979},"269":{"tf":2.0},"274":{"tf":1.4142135623730951},"278":{"tf":1.0},"281":{"tf":2.0},"282":{"tf":2.0},"283":{"tf":1.0},"284":{"tf":1.0},"31":{"tf":1.0},"32":{"tf":1.4142135623730951},"36":{"tf":1.0},"41":{"tf":1.0},"45":{"tf":1.0},"6":{"tf":1.0},"60":{"tf":1.4142135623730951},"64":{"tf":2.23606797749979},"68":{"tf":2.23606797749979},"69":{"tf":2.449489742783178},"7":{"tf":2.23606797749979},"70":{"tf":1.0},"71":{"tf":1.0},"72":{"tf":1.0},"73":{"tf":2.0},"74":{"tf":1.0},"75":{"tf":1.7320508075688772},"76":{"tf":1.4142135623730951},"77":{"tf":1.4142135623730951},"78":{"tf":1.4142135623730951},"79":{"tf":1.4142135623730951},"80":{"tf":1.4142135623730951},"81":{"tf":2.0},"82":{"tf":1.4142135623730951},"83":{"tf":1.4142135623730951},"84":{"tf":1.4142135623730951},"85":{"tf":1.4142135623730951},"86":{"tf":1.4142135623730951},"87":{"tf":1.4142135623730951},"88":{"tf":1.7320508075688772},"89":{"tf":1.4142135623730951},"90":{"tf":1.4142135623730951},"91":{"tf":1.0},"92":{"tf":1.0},"93":{"tf":1.0},"94":{"tf":1.0},"95":{"tf":1.0},"96":{"tf":1.0},"97":{"tf":1.0},"98":{"tf":1.0},"99":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":7,"docs":{"163":{"tf":1.0},"165":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"245":{"tf":1.0},"263":{"tf":1.0}}}},"l":{"df":0,"docs":{},"i":{"df":2,"docs":{"119":{"tf":1.0},"37":{"tf":1.0}}}}},"x":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"]":{".":{"0":{"a":{"1":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}},"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"_":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":0,"docs":{},"e":{"d":{"_":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"242":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":14,"docs":{"103":{"tf":1.4142135623730951},"108":{"tf":1.0},"109":{"tf":1.0},"119":{"tf":1.0},"14":{"tf":1.0},"152":{"tf":1.0},"227":{"tf":1.0},"233":{"tf":1.0},"242":{"tf":1.4142135623730951},"249":{"tf":1.0},"250":{"tf":1.0},"40":{"tf":1.0},"6":{"tf":1.0},"74":{"tf":1.0}}}}},"i":{"c":{"df":0,"docs":{},"e":{"df":2,"docs":{"103":{"tf":1.4142135623730951},"46":{"tf":1.0}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":7,"docs":{"119":{"tf":2.449489742783178},"266":{"tf":2.449489742783178},"271":{"tf":1.7320508075688772},"272":{"tf":2.23606797749979},"274":{"tf":1.0},"40":{"tf":2.0},"74":{"tf":1.4142135623730951}}},"y":{".":{"df":0,"docs":{},"j":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"271":{"tf":1.0}}}}}},"y":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":1,"docs":{"119":{"tf":1.0}}}}}},"/":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":1,"docs":{"271":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"m":{"b":{"df":0,"docs":{},"u":{"df":17,"docs":{"162":{"tf":1.0},"167":{"tf":1.7320508075688772},"172":{"tf":1.0},"174":{"tf":1.0},"178":{"tf":1.0},"180":{"tf":1.0},"182":{"tf":1.0},"184":{"tf":1.0},"190":{"tf":1.4142135623730951},"194":{"tf":2.23606797749979},"197":{"tf":2.0},"198":{"tf":2.449489742783178},"199":{"tf":1.0},"200":{"tf":3.3166247903554},"271":{"tf":1.0},"274":{"tf":1.4142135623730951},"284":{"tf":1.0}},"s":{".":{"df":0,"docs":{},"u":{"d":{"df":0,"docs":{},"l":{"df":1,"docs":{"200":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"n":{"df":0,"docs":{},"j":{"a":{"df":1,"docs":{"11":{"tf":2.0}}},"df":0,"docs":{}}}},"o":{"d":{"df":0,"docs":{},"e":{"df":1,"docs":{"86":{"tf":1.0}}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"75":{"tf":1.0}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"123":{"tf":1.0}}}}},"n":{"df":16,"docs":{"122":{"tf":1.0},"13":{"tf":1.0},"167":{"tf":1.0},"184":{"tf":1.0},"188":{"tf":1.0},"189":{"tf":1.4142135623730951},"200":{"tf":1.0},"201":{"tf":2.0},"226":{"tf":1.4142135623730951},"241":{"tf":1.4142135623730951},"250":{"tf":1.0},"40":{"tf":1.4142135623730951},"45":{"tf":1.0},"46":{"tf":1.0},"58":{"tf":1.4142135623730951},"84":{"tf":1.4142135623730951}},"e":{"df":3,"docs":{"180":{"tf":1.0},"233":{"tf":1.0},"242":{"tf":1.4142135623730951}}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"df":1,"docs":{"116":{"tf":1.0}}}}}}}},"r":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"l":{"df":12,"docs":{"104":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"125":{"tf":1.0},"133":{"tf":1.0},"156":{"tf":1.0},"19":{"tf":1.0},"200":{"tf":1.0},"205":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"64":{"tf":1.0}}}},"df":2,"docs":{"200":{"tf":1.0},"201":{"tf":1.0}}}},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":2,"docs":{"18":{"tf":1.0},"201":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":42,"docs":{"105":{"tf":1.0},"106":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"11":{"tf":1.4142135623730951},"115":{"tf":1.0},"117":{"tf":1.0},"12":{"tf":1.0},"123":{"tf":1.0},"124":{"tf":1.4142135623730951},"13":{"tf":1.0},"14":{"tf":1.4142135623730951},"145":{"tf":1.4142135623730951},"18":{"tf":1.0},"198":{"tf":1.0},"20":{"tf":1.0},"200":{"tf":1.4142135623730951},"205":{"tf":1.0},"208":{"tf":1.0},"214":{"tf":1.0},"215":{"tf":1.0},"226":{"tf":1.4142135623730951},"231":{"tf":1.7320508075688772},"242":{"tf":1.4142135623730951},"243":{"tf":1.0},"245":{"tf":1.0},"258":{"tf":1.0},"264":{"tf":1.4142135623730951},"27":{"tf":1.0},"274":{"tf":1.0},"278":{"tf":1.0},"280":{"tf":1.0},"282":{"tf":1.4142135623730951},"31":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"62":{"tf":1.4142135623730951},"7":{"tf":1.0},"78":{"tf":1.0},"79":{"tf":1.4142135623730951},"81":{"tf":1.0},"83":{"tf":1.0}}},"h":{"df":7,"docs":{"163":{"tf":1.0},"169":{"tf":1.4142135623730951},"179":{"tf":1.0},"180":{"tf":1.0},"182":{"tf":1.4142135623730951},"62":{"tf":1.0},"73":{"tf":1.4142135623730951}}},"i":{"c":{"df":3,"docs":{"166":{"tf":1.0},"274":{"tf":1.0},"282":{"tf":1.0}}},"df":0,"docs":{},"f":{"df":1,"docs":{"274":{"tf":1.0}}}}},"w":{"df":19,"docs":{"105":{"tf":1.4142135623730951},"142":{"tf":1.0},"152":{"tf":1.0},"180":{"tf":1.0},"20":{"tf":1.0},"212":{"tf":1.0},"226":{"tf":1.7320508075688772},"233":{"tf":1.0},"241":{"tf":1.0},"242":{"tf":1.0},"257":{"tf":1.7320508075688772},"31":{"tf":1.0},"36":{"tf":1.0},"40":{"tf":1.0},"60":{"tf":1.4142135623730951},"62":{"tf":1.4142135623730951},"74":{"tf":1.0},"86":{"tf":1.0},"90":{"tf":1.0}}}},"s":{"df":0,"docs":{},"k":{"df":1,"docs":{"37":{"tf":1.0}}},"p":{"df":0,"docs":{},"r":{"df":1,"docs":{"67":{"tf":1.0}}}},"s":{"_":{"3":{"_":{"9":{"0":{"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"m":{"df":1,"docs":{"280":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"282":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"_":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"282":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":1,"docs":{"281":{"tf":1.0}}}}},"df":7,"docs":{"11":{"tf":1.7320508075688772},"278":{"tf":2.8284271247461903},"279":{"tf":2.6457513110645907},"280":{"tf":2.8284271247461903},"281":{"tf":1.7320508075688772},"282":{"tf":3.3166247903554},"67":{"tf":1.0}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":1,"docs":{"62":{"tf":2.8284271247461903}}}},"m":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":26,"docs":{"10":{"tf":1.4142135623730951},"115":{"tf":1.0},"13":{"tf":1.0},"161":{"tf":1.0},"193":{"tf":1.0},"197":{"tf":1.0},"20":{"tf":1.4142135623730951},"204":{"tf":1.0},"205":{"tf":1.0},"207":{"tf":1.0},"208":{"tf":1.4142135623730951},"213":{"tf":1.0},"214":{"tf":1.0},"215":{"tf":1.0},"216":{"tf":1.0},"22":{"tf":1.7320508075688772},"220":{"tf":1.0},"222":{"tf":1.0},"225":{"tf":1.4142135623730951},"231":{"tf":1.0},"268":{"tf":1.0},"269":{"tf":1.0},"270":{"tf":1.0},"271":{"tf":1.0},"279":{"tf":1.0},"37":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"22":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"64":{"tf":1.0}}}}}}}},"y":{"df":0,"docs":{},"g":{"a":{"df":0,"docs":{},"r":{"d":{"'":{"df":1,"docs":{"137":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"o":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":2,"docs":{"254":{"tf":1.0},"283":{"tf":2.8284271247461903}}}}}},"b":{"df":0,"docs":{},"j":{".":{"d":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"129":{"tf":1.0}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"s":{"df":1,"docs":{"129":{"tf":1.0}}}}}}}}}}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"129":{"tf":1.0}},"e":{"c":{"df":0,"docs":{},"t":{"df":10,"docs":{"128":{"tf":2.8284271247461903},"129":{"tf":2.0},"130":{"tf":2.0},"131":{"tf":2.449489742783178},"132":{"tf":2.449489742783178},"133":{"tf":1.0},"245":{"tf":1.0},"268":{"tf":1.0},"283":{"tf":1.4142135623730951},"99":{"tf":1.0}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":9,"docs":{"203":{"tf":1.0},"204":{"tf":1.7320508075688772},"205":{"tf":1.0},"206":{"tf":1.0},"210":{"tf":1.0},"214":{"tf":1.7320508075688772},"215":{"tf":1.0},"216":{"tf":1.7320508075688772},"227":{"tf":1.0}}}}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":5,"docs":{"161":{"tf":1.0},"2":{"tf":1.0},"228":{"tf":1.0},"277":{"tf":1.0},"283":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":2,"docs":{"201":{"tf":1.0},"225":{"tf":1.0}},"s":{"df":2,"docs":{"124":{"tf":1.0},"201":{"tf":1.0}}}}}}}},"c":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":3,"docs":{"207":{"tf":1.0},"226":{"tf":1.0},"43":{"tf":1.0}},"r":{"df":2,"docs":{"31":{"tf":1.0},"36":{"tf":1.0}}}}}},"df":0,"docs":{}},"d":{"d":{"df":2,"docs":{"200":{"tf":1.0},"221":{"tf":1.0}}},"df":0,"docs":{}},"df":3,"docs":{"11":{"tf":1.0},"29":{"tf":1.0},"34":{"tf":1.0}},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":3,"docs":{"201":{"tf":1.0},"274":{"tf":1.0},"283":{"tf":1.0}}}},"i":{"c":{"df":0,"docs":{},"i":{"df":3,"docs":{"261":{"tf":1.0},"262":{"tf":1.0},"263":{"tf":1.0}}}},"df":0,"docs":{}}}},"k":{"a":{"df":0,"docs":{},"y":{"df":2,"docs":{"130":{"tf":1.0},"82":{"tf":1.0}}}},"df":2,"docs":{"226":{"tf":1.0},"242":{"tf":1.0}}},"l":{"d":{"df":9,"docs":{"109":{"tf":1.0},"203":{"tf":1.0},"209":{"tf":1.0},"212":{"tf":1.4142135623730951},"231":{"tf":1.0},"258":{"tf":1.0},"279":{"tf":1.0},"37":{"tf":1.0},"86":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":5,"docs":{"210":{"tf":1.0},"225":{"tf":1.0},"245":{"tf":1.0},"78":{"tf":1.0},"79":{"tf":1.0}}}}},"df":0,"docs":{}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"145":{"tf":1.0},"21":{"tf":1.0}}}}},"n":{"b":{"df":0,"docs":{},"o":{"a":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"184":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"c":{"df":18,"docs":{"106":{"tf":1.0},"11":{"tf":1.0},"119":{"tf":1.4142135623730951},"124":{"tf":1.0},"128":{"tf":1.0},"132":{"tf":1.0},"14":{"tf":1.0},"145":{"tf":1.0},"25":{"tf":1.0},"258":{"tf":1.0},"268":{"tf":1.0},"270":{"tf":1.0},"277":{"tf":1.0},"30":{"tf":1.0},"35":{"tf":1.0},"6":{"tf":1.0},"62":{"tf":1.0},"64":{"tf":1.0}}},"df":34,"docs":{"106":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"116":{"tf":1.0},"122":{"tf":1.0},"125":{"tf":1.0},"126":{"tf":1.0},"131":{"tf":1.4142135623730951},"132":{"tf":1.0},"138":{"tf":1.0},"163":{"tf":1.7320508075688772},"167":{"tf":1.0},"171":{"tf":1.0},"196":{"tf":1.0},"203":{"tf":1.0},"212":{"tf":1.0},"228":{"tf":1.0},"229":{"tf":1.0},"232":{"tf":1.0},"236":{"tf":1.0},"249":{"tf":1.0},"257":{"tf":1.0},"258":{"tf":1.7320508075688772},"274":{"tf":1.0},"276":{"tf":1.0},"282":{"tf":1.0},"45":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.7320508075688772},"7":{"tf":1.0},"79":{"tf":1.4142135623730951},"82":{"tf":1.4142135623730951},"86":{"tf":1.0}},"g":{"df":0,"docs":{},"o":{"df":1,"docs":{"64":{"tf":1.0}}}},"t":{"df":0,"docs":{},"o":{"df":1,"docs":{"115":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"m":{"df":1,"docs":{"154":{"tf":1.0}}}},"p":{"a":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":2,"docs":{"229":{"tf":1.0},"242":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":20,"docs":{"107":{"tf":1.0},"109":{"tf":1.0},"111":{"tf":1.0},"124":{"tf":1.0},"151":{"tf":1.4142135623730951},"152":{"tf":1.4142135623730951},"154":{"tf":2.0},"155":{"tf":2.0},"156":{"tf":1.0},"158":{"tf":1.4142135623730951},"25":{"tf":1.0},"274":{"tf":1.4142135623730951},"280":{"tf":1.0},"286":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"40":{"tf":1.0},"7":{"tf":1.4142135623730951},"73":{"tf":1.0},"81":{"tf":1.0}},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"l":{"df":1,"docs":{"11":{"tf":1.0}}}}}},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"f":{"df":0,"docs":{},"x":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":1,"docs":{"283":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"df":7,"docs":{"151":{"tf":1.0},"152":{"tf":1.0},"156":{"tf":1.4142135623730951},"201":{"tf":1.4142135623730951},"225":{"tf":1.0},"245":{"tf":1.0},"37":{"tf":1.0}}}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":1,"docs":{"170":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"233":{"tf":1.0}}}}}}},"t":{"df":3,"docs":{"233":{"tf":1.4142135623730951},"280":{"tf":1.0},"283":{"tf":1.0}},"i":{"df":0,"docs":{},"m":{"df":2,"docs":{"220":{"tf":1.0},"282":{"tf":1.0}}},"o":{"df":0,"docs":{},"n":{"<":{"df":0,"docs":{},"j":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":1,"docs":{"242":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"242":{"tf":1.0}}}}},"v":{"df":0,"docs":{},"e":{"c":{"<":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"242":{"tf":1.4142135623730951}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":36,"docs":{"103":{"tf":1.4142135623730951},"137":{"tf":1.4142135623730951},"138":{"tf":1.0},"142":{"tf":2.0},"143":{"tf":1.0},"144":{"tf":1.4142135623730951},"145":{"tf":1.4142135623730951},"146":{"tf":1.7320508075688772},"151":{"tf":1.4142135623730951},"153":{"tf":1.4142135623730951},"158":{"tf":1.0},"159":{"tf":1.0},"163":{"tf":1.4142135623730951},"164":{"tf":1.4142135623730951},"168":{"tf":1.4142135623730951},"169":{"tf":1.4142135623730951},"170":{"tf":1.4142135623730951},"171":{"tf":1.4142135623730951},"172":{"tf":1.7320508075688772},"174":{"tf":1.4142135623730951},"179":{"tf":1.4142135623730951},"180":{"tf":2.23606797749979},"181":{"tf":1.4142135623730951},"185":{"tf":1.0},"187":{"tf":1.0},"195":{"tf":1.4142135623730951},"196":{"tf":2.23606797749979},"197":{"tf":1.7320508075688772},"198":{"tf":2.23606797749979},"199":{"tf":1.7320508075688772},"207":{"tf":2.23606797749979},"208":{"tf":1.4142135623730951},"211":{"tf":1.4142135623730951},"249":{"tf":1.0},"73":{"tf":1.0},"74":{"tf":1.4142135623730951}}}}}}},"r":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":14,"docs":{"105":{"tf":1.0},"106":{"tf":1.0},"115":{"tf":1.0},"170":{"tf":1.4142135623730951},"22":{"tf":1.4142135623730951},"223":{"tf":1.0},"224":{"tf":1.0},"246":{"tf":1.0},"282":{"tf":1.0},"286":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0},"59":{"tf":1.0},"69":{"tf":1.0}}}}},"df":0,"docs":{},"g":{".":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{".":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"$":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"112":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":1,"docs":{"274":{"tf":1.4142135623730951}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":2,"docs":{"115":{"tf":1.0},"231":{"tf":1.0}}}}}}},"s":{"df":1,"docs":{"133":{"tf":1.0}},"x":{"df":1,"docs":{"17":{"tf":1.4142135623730951}}}},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"'":{"df":1,"docs":{"175":{"tf":1.0}}},"df":2,"docs":{"116":{"tf":1.0},"226":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"242":{"tf":1.0}}}}}}},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":4,"docs":{"224":{"tf":1.4142135623730951},"226":{"tf":1.0},"88":{"tf":1.0},"98":{"tf":1.0}}}}}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"v":{"df":3,"docs":{"166":{"tf":1.0},"170":{"tf":1.0},"278":{"tf":1.0}}}}}}},"t":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":6,"docs":{"138":{"tf":1.4142135623730951},"143":{"tf":1.4142135623730951},"152":{"tf":1.4142135623730951},"164":{"tf":1.4142135623730951},"180":{"tf":1.4142135623730951},"208":{"tf":1.4142135623730951}}}}},"df":30,"docs":{"103":{"tf":1.0},"105":{"tf":1.0},"107":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"116":{"tf":1.0},"171":{"tf":1.0},"18":{"tf":1.0},"190":{"tf":1.0},"198":{"tf":1.0},"200":{"tf":1.4142135623730951},"201":{"tf":1.0},"204":{"tf":1.4142135623730951},"233":{"tf":1.4142135623730951},"249":{"tf":1.0},"252":{"tf":1.0},"274":{"tf":1.7320508075688772},"279":{"tf":1.4142135623730951},"283":{"tf":1.4142135623730951},"29":{"tf":1.0},"31":{"tf":1.0},"34":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"43":{"tf":1.0},"59":{"tf":1.0},"6":{"tf":1.0},"60":{"tf":1.0},"81":{"tf":1.0},"96":{"tf":1.0}},"g":{"df":0,"docs":{},"o":{"df":2,"docs":{"231":{"tf":1.0},"249":{"tf":1.0}}}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":3,"docs":{"187":{"tf":1.0},"233":{"tf":1.0},"91":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":6,"docs":{"174":{"tf":1.0},"252":{"tf":1.0},"280":{"tf":1.0},"282":{"tf":1.0},"37":{"tf":1.0},"62":{"tf":2.0}}}}},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":1,"docs":{"61":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"156":{"tf":1.0}}},"df":0,"docs":{}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"l":{"df":7,"docs":{"102":{"tf":1.0},"105":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"218":{"tf":1.0},"222":{"tf":1.0},"244":{"tf":1.0}}}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":1,"docs":{"175":{"tf":1.0}}}}},"df":21,"docs":{"101":{"tf":1.0},"103":{"tf":1.0},"104":{"tf":1.0},"105":{"tf":1.0},"106":{"tf":1.7320508075688772},"11":{"tf":1.0},"116":{"tf":1.0},"126":{"tf":1.0},"140":{"tf":1.0},"172":{"tf":1.0},"193":{"tf":1.0},"204":{"tf":1.4142135623730951},"208":{"tf":1.0},"212":{"tf":1.0},"214":{"tf":1.0},"215":{"tf":1.0},"216":{"tf":1.4142135623730951},"217":{"tf":1.4142135623730951},"228":{"tf":1.0},"274":{"tf":1.0},"43":{"tf":1.0}},"h":{"df":0,"docs":{},"e":{"a":{"d":{"df":2,"docs":{"115":{"tf":1.0},"59":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"l":{"a":{"df":0,"docs":{},"p":{"df":1,"docs":{"198":{"tf":1.0}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":1,"docs":{"46":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"i":{"d":{"df":3,"docs":{"123":{"tf":1.0},"124":{"tf":1.0},"22":{"tf":1.0}}},"df":0,"docs":{}}},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":16,"docs":{"174":{"tf":1.0},"219":{"tf":1.4142135623730951},"227":{"tf":1.0},"243":{"tf":2.0},"244":{"tf":1.0},"245":{"tf":1.0},"246":{"tf":1.0},"247":{"tf":1.0},"248":{"tf":1.0},"249":{"tf":1.0},"250":{"tf":1.0},"274":{"tf":1.0},"41":{"tf":1.0},"93":{"tf":1.4142135623730951},"96":{"tf":1.4142135623730951},"99":{"tf":1.4142135623730951}}}}}},"w":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"179":{"tf":1.0}}}}}}}}},"w":{"df":0,"docs":{},"n":{"df":3,"docs":{"201":{"tf":1.0},"276":{"tf":1.4142135623730951},"277":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"121":{"tf":1.0},"276":{"tf":1.0}},"s":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":1,"docs":{"276":{"tf":1.0}}}}}}}}}}},"p":{"a":{"c":{"df":0,"docs":{},"k":{"a":{"df":0,"docs":{},"g":{"df":40,"docs":{"100":{"tf":1.0},"103":{"tf":1.0},"108":{"tf":1.0},"112":{"tf":2.0},"113":{"tf":1.4142135623730951},"12":{"tf":1.0},"134":{"tf":1.0},"160":{"tf":1.4142135623730951},"163":{"tf":2.0},"164":{"tf":1.0},"165":{"tf":1.0},"166":{"tf":1.0},"167":{"tf":2.449489742783178},"169":{"tf":1.0},"170":{"tf":1.4142135623730951},"171":{"tf":2.6457513110645907},"172":{"tf":2.8284271247461903},"174":{"tf":2.6457513110645907},"219":{"tf":1.4142135623730951},"220":{"tf":1.0},"222":{"tf":1.0},"225":{"tf":1.0},"23":{"tf":2.0},"25":{"tf":3.0},"251":{"tf":2.23606797749979},"252":{"tf":1.0},"253":{"tf":2.8284271247461903},"26":{"tf":2.0},"269":{"tf":1.0},"271":{"tf":1.0},"274":{"tf":1.4142135623730951},"31":{"tf":2.449489742783178},"32":{"tf":2.0},"36":{"tf":2.449489742783178},"37":{"tf":1.0},"45":{"tf":1.0},"53":{"tf":1.0},"73":{"tf":2.0},"74":{"tf":1.0},"99":{"tf":1.0}},"e":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"'":{"df":2,"docs":{"29":{"tf":1.0},"34":{"tf":1.0}}},"df":3,"docs":{"167":{"tf":1.7320508075688772},"251":{"tf":1.0},"253":{"tf":1.4142135623730951}}}}}}}},"=":{"\"":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{".":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{".":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"<":{"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"_":{"c":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"70":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"70":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"117":{"tf":1.0}}}}}}}}}},"df":0,"docs":{}}},"d":{"df":1,"docs":{"64":{"tf":1.0}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"s":{"df":1,"docs":{"264":{"tf":1.0}}}},"df":43,"docs":{"100":{"tf":1.0},"109":{"tf":1.0},"117":{"tf":1.0},"119":{"tf":1.0},"124":{"tf":1.0},"127":{"tf":1.0},"133":{"tf":1.0},"138":{"tf":1.0},"147":{"tf":1.0},"15":{"tf":1.0},"159":{"tf":1.0},"175":{"tf":1.0},"185":{"tf":1.0},"201":{"tf":1.0},"218":{"tf":1.0},"22":{"tf":1.0},"224":{"tf":1.0},"242":{"tf":1.0},"250":{"tf":1.0},"253":{"tf":1.0},"258":{"tf":1.0},"259":{"tf":1.0},"261":{"tf":1.0},"263":{"tf":1.0},"264":{"tf":1.0},"273":{"tf":1.0},"277":{"tf":1.0},"282":{"tf":1.0},"283":{"tf":1.0},"286":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0},"40":{"tf":1.0},"5":{"tf":1.0},"51":{"tf":1.0},"56":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0},"67":{"tf":1.0},"74":{"tf":1.0},"8":{"tf":1.0},"90":{"tf":1.0}}}},"i":{"df":0,"docs":{},"n":{"df":4,"docs":{"104":{"tf":1.0},"115":{"tf":1.0},"122":{"tf":1.0},"5":{"tf":1.0}}}},"n":{"df":0,"docs":{},"e":{"df":1,"docs":{"126":{"tf":1.4142135623730951}},"l":{"df":2,"docs":{"205":{"tf":1.4142135623730951},"206":{"tf":1.0}}}},"i":{"c":{"df":1,"docs":{"117":{"tf":1.0}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"6":{"tf":1.0}}}}},"df":0,"docs":{}}}},"r":{"a":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"m":{"df":1,"docs":{"61":{"tf":1.0}}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":1,"docs":{"59":{"tf":1.0}}}}}},"m":{"df":2,"docs":{"242":{"tf":1.4142135623730951},"79":{"tf":1.0}},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"283":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"61":{"tf":1.7320508075688772},"62":{"tf":1.0}}}}},"s":{"df":1,"docs":{"22":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"58":{"tf":1.0}}}}},"t":{"df":28,"docs":{"103":{"tf":1.4142135623730951},"104":{"tf":1.0},"106":{"tf":1.0},"108":{"tf":1.4142135623730951},"109":{"tf":1.4142135623730951},"113":{"tf":1.0},"114":{"tf":1.0},"161":{"tf":1.0},"165":{"tf":1.0},"171":{"tf":1.0},"190":{"tf":1.0},"200":{"tf":1.4142135623730951},"201":{"tf":1.0},"203":{"tf":1.0},"214":{"tf":1.0},"22":{"tf":1.0},"225":{"tf":1.0},"233":{"tf":1.0},"242":{"tf":1.0},"245":{"tf":1.4142135623730951},"251":{"tf":1.0},"268":{"tf":1.0},"274":{"tf":1.0},"64":{"tf":1.7320508075688772},"68":{"tf":1.4142135623730951},"79":{"tf":1.0},"80":{"tf":1.0},"84":{"tf":1.0}},"i":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":1,"docs":{"3":{"tf":1.4142135623730951}}}},"u":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"r":{"df":9,"docs":{"116":{"tf":1.0},"198":{"tf":1.0},"200":{"tf":1.0},"225":{"tf":1.0},"231":{"tf":1.0},"233":{"tf":1.4142135623730951},"236":{"tf":1.4142135623730951},"39":{"tf":1.0},"64":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"213":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":4,"docs":{"121":{"tf":1.0},"201":{"tf":1.7320508075688772},"63":{"tf":1.0},"64":{"tf":1.7320508075688772}}}}},"s":{"df":0,"docs":{},"s":{"df":20,"docs":{"105":{"tf":1.0},"106":{"tf":1.7320508075688772},"108":{"tf":1.0},"109":{"tf":1.0},"132":{"tf":1.0},"14":{"tf":1.0},"142":{"tf":1.4142135623730951},"143":{"tf":1.0},"145":{"tf":2.0},"225":{"tf":1.0},"228":{"tf":1.0},"229":{"tf":1.0},"242":{"tf":1.7320508075688772},"245":{"tf":1.4142135623730951},"262":{"tf":1.4142135623730951},"283":{"tf":1.4142135623730951},"58":{"tf":1.0},"7":{"tf":1.4142135623730951},"73":{"tf":1.0},"79":{"tf":2.0}},"i":{"df":0,"docs":{},"v":{"df":1,"docs":{"264":{"tf":1.0}}}},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"d":{"df":3,"docs":{"242":{"tf":1.0},"257":{"tf":1.0},"86":{"tf":1.0}}},"df":0,"docs":{}}}}},"t":{"df":2,"docs":{"205":{"tf":1.0},"207":{"tf":1.0}}}},"t":{"c":{"df":0,"docs":{},"h":{"df":9,"docs":{"11":{"tf":1.4142135623730951},"122":{"tf":1.4142135623730951},"123":{"tf":1.0},"124":{"tf":1.0},"262":{"tf":1.0},"270":{"tf":1.4142135623730951},"62":{"tf":1.0},"7":{"tf":2.449489742783178},"8":{"tf":1.0}}}},"df":0,"docs":{},"h":{"/":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"/":{"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"/":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"/":{"df":0,"docs":{},"o":{"df":0,"docs":{},"f":{"/":{"df":0,"docs":{},"j":{"df":0,"docs":{},"n":{"a":{"/":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"37":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"=":{"\"":{"$":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{":":{"$":{"(":{"b":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":1,"docs":{"11":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"k":{"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"/":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"/":{"a":{"a":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"h":{"6":{"4":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":1,"docs":{"37":{"tf":1.0}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"v":{"df":0,"docs":{},"m":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"/":{"d":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"37":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"x":{"8":{"6":{"_":{"6":{"4":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{},"~":{"/":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"g":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{":":{"$":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"11":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":19,"docs":{"11":{"tf":1.4142135623730951},"122":{"tf":1.0},"123":{"tf":1.0},"124":{"tf":2.6457513110645907},"166":{"tf":1.0},"18":{"tf":1.4142135623730951},"192":{"tf":1.0},"22":{"tf":1.0},"25":{"tf":1.0},"280":{"tf":1.0},"29":{"tf":1.0},"31":{"tf":1.4142135623730951},"34":{"tf":1.0},"36":{"tf":1.4142135623730951},"37":{"tf":1.4142135623730951},"45":{"tf":1.0},"60":{"tf":1.4142135623730951},"62":{"tf":1.4142135623730951},"74":{"tf":1.4142135623730951}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":1,"docs":{"247":{"tf":1.7320508075688772}}}}}}},"u":{"df":0,"docs":{},"s":{"df":2,"docs":{"180":{"tf":1.0},"183":{"tf":1.0}}}},"y":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":1,"docs":{"283":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":2,"docs":{"58":{"tf":1.7320508075688772},"69":{"tf":1.0}},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"122":{"tf":1.0},"8":{"tf":1.0}}}},"n":{"d":{"df":1,"docs":{"21":{"tf":1.0}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":2,"docs":{"138":{"tf":1.0},"60":{"tf":1.0}}}}},"r":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":2,"docs":{"170":{"tf":1.0},"242":{"tf":1.0}}}},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"62":{"tf":1.0}},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"204":{"tf":3.0}}}}}}}},"df":9,"docs":{"140":{"tf":1.4142135623730951},"145":{"tf":1.4142135623730951},"231":{"tf":1.0},"232":{"tf":1.0},"245":{"tf":1.0},"252":{"tf":1.0},"40":{"tf":1.0},"61":{"tf":1.4142135623730951},"64":{"tf":1.0}},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":17,"docs":{"111":{"tf":1.0},"145":{"tf":1.4142135623730951},"16":{"tf":1.0},"187":{"tf":1.0},"205":{"tf":1.4142135623730951},"209":{"tf":1.0},"212":{"tf":1.0},"218":{"tf":1.0},"225":{"tf":1.0},"226":{"tf":1.0},"245":{"tf":1.4142135623730951},"249":{"tf":1.0},"268":{"tf":1.0},"278":{"tf":1.0},"282":{"tf":1.0},"58":{"tf":1.0},"81":{"tf":1.0}}}}}},"h":{"a":{"df":0,"docs":{},"p":{"df":3,"docs":{"103":{"tf":1.0},"104":{"tf":1.0},"115":{"tf":1.0}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"o":{"d":{"df":3,"docs":{"145":{"tf":1.0},"226":{"tf":1.0},"245":{"tf":1.0}}},"df":0,"docs":{}}},"l":{"df":1,"docs":{"11":{"tf":1.4142135623730951}}},"m":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"273":{"tf":1.0}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"274":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":12,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"149":{"tf":1.0},"150":{"tf":1.0},"194":{"tf":1.4142135623730951},"197":{"tf":1.7320508075688772},"198":{"tf":1.0},"229":{"tf":2.0},"242":{"tf":1.4142135623730951},"249":{"tf":1.0},"283":{"tf":1.4142135623730951},"82":{"tf":1.0}},"e":{"d":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"242":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"x":{"a":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"108":{"tf":1.0},"109":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}}},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"277":{"tf":1.4142135623730951}}}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"284":{"tf":1.0}}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"s":{"df":1,"docs":{"102":{"tf":1.0}}}}}},"h":{"a":{"b":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"c":{"df":3,"docs":{"122":{"tf":1.0},"123":{"tf":1.0},"124":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":5,"docs":{"22":{"tf":1.0},"267":{"tf":1.4142135623730951},"273":{"tf":1.0},"60":{"tf":1.0},"73":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}},"i":{"c":{"df":0,"docs":{},"k":{"df":5,"docs":{"270":{"tf":1.0},"272":{"tf":1.0},"30":{"tf":1.0},"35":{"tf":1.0},"6":{"tf":1.0}}}},"df":0,"docs":{},"e":{"c":{"df":3,"docs":{"105":{"tf":1.0},"116":{"tf":1.0},"60":{"tf":1.0}}},"df":0,"docs":{}},"n":{"df":2,"docs":{"263":{"tf":1.0},"274":{"tf":1.0}}},"p":{"3":{"df":2,"docs":{"269":{"tf":1.0},"53":{"tf":1.0}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":5,"docs":{"113":{"tf":1.0},"221":{"tf":1.0},"226":{"tf":1.0},"274":{"tf":2.449489742783178},"70":{"tf":1.0}}}}}}}},"l":{"a":{"c":{"df":0,"docs":{},"e":{"df":23,"docs":{"106":{"tf":1.4142135623730951},"108":{"tf":1.0},"134":{"tf":1.0},"142":{"tf":1.0},"202":{"tf":1.4142135623730951},"218":{"tf":1.0},"221":{"tf":1.0},"232":{"tf":1.4142135623730951},"237":{"tf":1.0},"244":{"tf":1.0},"245":{"tf":1.0},"264":{"tf":2.8284271247461903},"274":{"tf":1.0},"48":{"tf":1.0},"58":{"tf":1.4142135623730951},"61":{"tf":1.4142135623730951},"62":{"tf":1.0},"70":{"tf":1.4142135623730951},"72":{"tf":1.0},"78":{"tf":1.4142135623730951},"79":{"tf":1.4142135623730951},"81":{"tf":1.0},"83":{"tf":1.0}},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"58":{"tf":1.0}}}}}},"s":{".":{"d":{"b":{"df":1,"docs":{"218":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"62":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"e":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"130":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"n":{"df":8,"docs":{"140":{"tf":1.0},"152":{"tf":1.0},"180":{"tf":1.0},"200":{"tf":1.4142135623730951},"228":{"tf":1.0},"230":{"tf":1.4142135623730951},"249":{"tf":2.23606797749979},"274":{"tf":1.0}}},"t":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":22,"docs":{"0":{"tf":1.0},"116":{"tf":1.7320508075688772},"13":{"tf":1.0},"170":{"tf":1.0},"184":{"tf":1.0},"196":{"tf":1.0},"200":{"tf":1.0},"213":{"tf":1.0},"220":{"tf":1.0},"223":{"tf":1.0},"225":{"tf":1.0},"251":{"tf":1.0},"255":{"tf":2.0},"256":{"tf":1.4142135623730951},"257":{"tf":1.0},"258":{"tf":1.0},"274":{"tf":1.0},"279":{"tf":1.0},"28":{"tf":1.0},"33":{"tf":1.0},"37":{"tf":1.4142135623730951},"69":{"tf":1.0}},"s":{";":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"13":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":11,"docs":{"111":{"tf":1.0},"120":{"tf":1.0},"134":{"tf":1.0},"200":{"tf":1.4142135623730951},"4":{"tf":1.0},"41":{"tf":1.0},"5":{"tf":1.0},"69":{"tf":1.0},"7":{"tf":1.4142135623730951},"75":{"tf":1.4142135623730951},"81":{"tf":1.0}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"252":{"tf":1.0}}}}},"u":{"df":3,"docs":{"174":{"tf":1.0},"233":{"tf":1.0},"98":{"tf":1.0}},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"65":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":18,"docs":{"104":{"tf":1.7320508075688772},"105":{"tf":1.0},"12":{"tf":1.0},"171":{"tf":1.4142135623730951},"198":{"tf":1.0},"201":{"tf":1.0},"226":{"tf":1.0},"241":{"tf":1.7320508075688772},"260":{"tf":1.0},"274":{"tf":1.4142135623730951},"29":{"tf":1.0},"34":{"tf":1.0},"37":{"tf":1.0},"5":{"tf":1.0},"51":{"tf":1.0},"64":{"tf":1.0},"73":{"tf":1.4142135623730951},"78":{"tf":1.7320508075688772}},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"116":{"tf":1.0},"128":{"tf":1.4142135623730951}}}}}}},"l":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":8,"docs":{"121":{"tf":1.0},"219":{"tf":1.0},"226":{"tf":1.0},"260":{"tf":1.0},"261":{"tf":2.449489742783178},"262":{"tf":2.449489742783178},"263":{"tf":1.0},"63":{"tf":1.0}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":6,"docs":{"145":{"tf":1.0},"184":{"tf":1.0},"214":{"tf":1.0},"215":{"tf":1.0},"226":{"tf":1.0},"233":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"t":{"df":4,"docs":{"101":{"tf":1.4142135623730951},"102":{"tf":1.0},"104":{"tf":1.0},"228":{"tf":1.4142135623730951}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"226":{"tf":1.4142135623730951}}}}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":3,"docs":{"165":{"tf":1.4142135623730951},"209":{"tf":1.4142135623730951},"62":{"tf":1.0}}}},"s":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"l":{"df":32,"docs":{"105":{"tf":1.0},"106":{"tf":1.0},"11":{"tf":1.0},"116":{"tf":1.0},"121":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0},"158":{"tf":1.0},"16":{"tf":1.0},"165":{"tf":1.0},"170":{"tf":1.0},"175":{"tf":1.0},"180":{"tf":1.0},"192":{"tf":1.0},"196":{"tf":1.0},"200":{"tf":1.0},"225":{"tf":1.0},"226":{"tf":1.0},"227":{"tf":1.0},"236":{"tf":1.0},"237":{"tf":1.4142135623730951},"242":{"tf":1.0},"25":{"tf":1.0},"263":{"tf":1.0},"27":{"tf":1.0},"278":{"tf":1.0},"43":{"tf":1.4142135623730951},"59":{"tf":1.0},"7":{"tf":1.0},"80":{"tf":1.0},"83":{"tf":1.0},"88":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"df":2,"docs":{"249":{"tf":1.0},"90":{"tf":1.4142135623730951}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"l":{"_":{"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"_":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"_":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"145":{"tf":1.4142135623730951},"146":{"tf":1.0}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":8,"docs":{"159":{"tf":1.0},"164":{"tf":1.0},"165":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"59":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0}}}}}}},"v":{"df":1,"docs":{"226":{"tf":1.0}}},"w":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"203":{"tf":1.0},"205":{"tf":1.7320508075688772}}}}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"c":{"df":14,"docs":{"101":{"tf":1.0},"116":{"tf":1.0},"123":{"tf":1.4142135623730951},"128":{"tf":1.0},"140":{"tf":1.0},"170":{"tf":1.0},"226":{"tf":1.0},"232":{"tf":1.0},"274":{"tf":1.0},"276":{"tf":1.0},"43":{"tf":1.0},"45":{"tf":1.0},"75":{"tf":1.0},"8":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"m":{"a":{"df":2,"docs":{"264":{"tf":3.1622776601683795},"81":{"tf":1.0}}},"df":0,"docs":{}}}},"df":15,"docs":{"118":{"tf":1.7320508075688772},"119":{"tf":2.6457513110645907},"124":{"tf":1.0},"21":{"tf":1.4142135623730951},"263":{"tf":1.4142135623730951},"268":{"tf":2.6457513110645907},"269":{"tf":1.4142135623730951},"270":{"tf":1.4142135623730951},"274":{"tf":1.4142135623730951},"280":{"tf":1.0},"282":{"tf":1.0},"39":{"tf":1.7320508075688772},"6":{"tf":1.0},"64":{"tf":1.4142135623730951},"7":{"tf":3.1622776601683795}},"e":{"c":{"df":0,"docs":{},"e":{"d":{"df":2,"docs":{"31":{"tf":1.0},"36":{"tf":1.0}}},"df":0,"docs":{}},"l":{"df":0,"docs":{},"u":{"d":{"df":2,"docs":{"197":{"tf":1.0},"198":{"tf":1.0}}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"213":{"tf":1.0}}}},"df":0,"docs":{}}},"df":15,"docs":{"103":{"tf":1.0},"161":{"tf":1.0},"163":{"tf":1.7320508075688772},"164":{"tf":1.0},"167":{"tf":1.7320508075688772},"171":{"tf":1.4142135623730951},"172":{"tf":1.4142135623730951},"174":{"tf":1.0},"175":{"tf":1.0},"184":{"tf":1.0},"189":{"tf":1.0},"236":{"tf":1.0},"253":{"tf":1.0},"274":{"tf":1.0},"280":{"tf":1.0}},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":9,"docs":{"170":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.4142135623730951},"229":{"tf":1.0},"232":{"tf":1.4142135623730951},"64":{"tf":1.0},"79":{"tf":1.0},"82":{"tf":1.0},"83":{"tf":1.0}}}},"i":{"df":0,"docs":{},"x":{")":{"/":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"y":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"3":{"df":1,"docs":{"11":{"tf":1.0}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"y":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"@":{"3":{".":{"9":{"/":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"y":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{".":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"/":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"/":{"3":{".":{"9":{"/":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"11":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"p":{"a":{"df":0,"docs":{},"r":{"df":2,"docs":{"268":{"tf":1.0},"74":{"tf":1.0}}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"24":{"tf":1.4142135623730951}}}}}}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":5,"docs":{"185":{"tf":1.4142135623730951},"201":{"tf":1.0},"22":{"tf":1.4142135623730951},"237":{"tf":1.0},"270":{"tf":1.0}}}},"r":{"df":0,"docs":{},"v":{"df":1,"docs":{"26":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":1,"docs":{"242":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"59":{"tf":1.0}}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"228":{"tf":1.0},"233":{"tf":1.0}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"61":{"tf":1.0}}}}},"v":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"128":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":12,"docs":{"146":{"tf":1.0},"150":{"tf":1.0},"169":{"tf":1.4142135623730951},"170":{"tf":1.0},"171":{"tf":1.0},"179":{"tf":1.0},"180":{"tf":1.0},"183":{"tf":1.7320508075688772},"201":{"tf":1.0},"274":{"tf":1.4142135623730951},"31":{"tf":1.0},"36":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":6,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"268":{"tf":1.4142135623730951},"270":{"tf":1.0},"29":{"tf":1.4142135623730951},"34":{"tf":1.4142135623730951}},"s":{"df":5,"docs":{"106":{"tf":1.0},"141":{"tf":1.0},"25":{"tf":1.0},"26":{"tf":1.0},"268":{"tf":1.0}}}}}}}},"i":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":2,"docs":{"226":{"tf":1.4142135623730951},"248":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":5,"docs":{"11":{"tf":1.0},"201":{"tf":1.0},"245":{"tf":1.0},"257":{"tf":1.0},"284":{"tf":1.0}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"278":{"tf":1.0},"282":{"tf":1.4142135623730951}}}}},"n":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"62":{"tf":1.4142135623730951}}}}}}},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"62":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":3,"docs":{"22":{"tf":1.0},"37":{"tf":1.0},"62":{"tf":1.4142135623730951}},"f":{"df":1,"docs":{"37":{"tf":1.4142135623730951}}}}},"o":{"df":0,"docs":{},"r":{"df":2,"docs":{"206":{"tf":1.0},"214":{"tf":1.0}},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"258":{"tf":1.0},"6":{"tf":1.0}}}}}},"v":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"83":{"tf":1.0}}}},"df":0,"docs":{}}},"o":{"b":{"a":{"b":{"df":0,"docs":{},"l":{"df":17,"docs":{"104":{"tf":1.0},"115":{"tf":1.0},"122":{"tf":1.0},"156":{"tf":1.0},"18":{"tf":1.0},"197":{"tf":1.7320508075688772},"200":{"tf":1.0},"225":{"tf":1.0},"226":{"tf":1.4142135623730951},"229":{"tf":1.0},"231":{"tf":1.4142135623730951},"236":{"tf":1.0},"238":{"tf":1.0},"37":{"tf":1.0},"40":{"tf":1.0},"62":{"tf":1.0},"84":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"t":{"df":3,"docs":{"180":{"tf":1.0},"183":{"tf":1.0},"256":{"tf":1.0}}}},"df":18,"docs":{"136":{"tf":1.4142135623730951},"140":{"tf":1.4142135623730951},"149":{"tf":1.4142135623730951},"161":{"tf":2.0},"175":{"tf":1.4142135623730951},"177":{"tf":1.4142135623730951},"182":{"tf":1.0},"187":{"tf":1.4142135623730951},"197":{"tf":1.0},"203":{"tf":1.4142135623730951},"205":{"tf":1.4142135623730951},"226":{"tf":1.0},"25":{"tf":1.0},"260":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"58":{"tf":1.7320508075688772},"64":{"tf":1.0}}}}}},"c":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":2,"docs":{"18":{"tf":1.0},"20":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"d":{"df":1,"docs":{"12":{"tf":1.0}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"s":{"df":38,"docs":{"101":{"tf":1.0},"102":{"tf":1.0},"105":{"tf":1.0},"106":{"tf":1.0},"114":{"tf":1.0},"120":{"tf":1.0},"122":{"tf":1.0},"123":{"tf":1.0},"124":{"tf":1.0},"126":{"tf":1.7320508075688772},"133":{"tf":1.0},"138":{"tf":1.0},"161":{"tf":1.0},"165":{"tf":1.0},"167":{"tf":1.4142135623730951},"169":{"tf":1.0},"170":{"tf":1.0},"188":{"tf":1.0},"19":{"tf":1.0},"197":{"tf":1.0},"20":{"tf":1.0},"220":{"tf":1.0},"243":{"tf":1.0},"244":{"tf":1.4142135623730951},"245":{"tf":1.4142135623730951},"246":{"tf":1.0},"248":{"tf":1.0},"257":{"tf":1.0},"259":{"tf":1.0},"26":{"tf":1.0},"265":{"tf":1.4142135623730951},"27":{"tf":1.4142135623730951},"274":{"tf":1.0},"280":{"tf":1.0},"282":{"tf":1.0},"49":{"tf":1.0},"69":{"tf":1.0},"88":{"tf":1.0}}}}}},"d":{"df":0,"docs":{},"u":{"c":{"df":7,"docs":{"111":{"tf":1.0},"28":{"tf":1.0},"284":{"tf":1.0},"33":{"tf":1.0},"37":{"tf":1.0},"39":{"tf":1.0},"60":{"tf":1.0}},"t":{"df":9,"docs":{"174":{"tf":1.0},"185":{"tf":1.0},"237":{"tf":1.0},"253":{"tf":1.0},"259":{"tf":1.0},"268":{"tf":1.0},"271":{"tf":1.4142135623730951},"64":{"tf":1.0},"8":{"tf":1.0}}}},"df":0,"docs":{}}},"df":7,"docs":{"144":{"tf":1.4142135623730951},"145":{"tf":1.0},"146":{"tf":1.0},"153":{"tf":1.4142135623730951},"168":{"tf":1.4142135623730951},"181":{"tf":1.4142135623730951},"211":{"tf":1.4142135623730951}},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"11":{"tf":1.7320508075688772}}},"t":{"df":2,"docs":{"122":{"tf":1.0},"200":{"tf":1.0}}}}},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"149":{"tf":1.0},"184":{"tf":1.0}},"m":{"df":1,"docs":{"61":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":4,"docs":{"107":{"tf":1.0},"16":{"tf":1.0},"268":{"tf":2.6457513110645907},"7":{"tf":1.4142135623730951}}}}}}},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"'":{"df":2,"docs":{"22":{"tf":1.0},"7":{"tf":1.0}}},"(":{"\"":{":":{"df":0,"docs":{},"f":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":1,"docs":{"224":{"tf":1.0}}}}}},"df":0,"docs":{}},"'":{":":{"df":0,"docs":{},"f":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":1,"docs":{"224":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":35,"docs":{"10":{"tf":1.0},"109":{"tf":1.7320508075688772},"118":{"tf":1.0},"136":{"tf":1.0},"138":{"tf":1.0},"14":{"tf":1.4142135623730951},"16":{"tf":1.7320508075688772},"167":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"18":{"tf":1.4142135623730951},"190":{"tf":1.4142135623730951},"191":{"tf":1.0},"20":{"tf":1.0},"203":{"tf":1.0},"22":{"tf":2.23606797749979},"224":{"tf":1.4142135623730951},"252":{"tf":1.0},"260":{"tf":1.7320508075688772},"263":{"tf":1.0},"278":{"tf":1.0},"3":{"tf":1.4142135623730951},"36":{"tf":1.0},"40":{"tf":1.4142135623730951},"45":{"tf":1.0},"46":{"tf":1.0},"5":{"tf":1.4142135623730951},"58":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0},"67":{"tf":1.0},"70":{"tf":1.0},"73":{"tf":2.449489742783178},"8":{"tf":1.0},"91":{"tf":1.0}}}},"df":0,"docs":{}}},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":4,"docs":{"267":{"tf":1.4142135623730951},"268":{"tf":1.0},"270":{"tf":1.0},"273":{"tf":2.23606797749979}}}},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"117":{"tf":1.4142135623730951}}}}},"n":{"df":0,"docs":{},"e":{"df":2,"docs":{"116":{"tf":1.0},"61":{"tf":1.0}}}},"p":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"249":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"274":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"69":{"tf":1.0}}}},"t":{"df":0,"docs":{},"i":{"df":4,"docs":{"17":{"tf":1.4142135623730951},"22":{"tf":1.7320508075688772},"268":{"tf":1.0},"99":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"s":{"df":4,"docs":{"146":{"tf":1.0},"186":{"tf":1.0},"187":{"tf":1.0},"236":{"tf":1.0}}}},"s":{"df":1,"docs":{"145":{"tf":1.0}}}},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"106":{"tf":1.4142135623730951}}}},"t":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"201":{"tf":1.0},"274":{"tf":2.0}}}},"df":0,"docs":{}},"o":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"f":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":1,"docs":{"106":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}}},"df":4,"docs":{"106":{"tf":2.23606797749979},"175":{"tf":1.0},"65":{"tf":1.0},"67":{"tf":1.0}}}}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":4,"docs":{"105":{"tf":1.0},"106":{"tf":1.4142135623730951},"256":{"tf":1.0},"96":{"tf":1.0}}}}},"df":2,"docs":{"105":{"tf":1.0},"106":{"tf":1.0}},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":5,"docs":{"107":{"tf":1.0},"167":{"tf":1.0},"172":{"tf":1.0},"174":{"tf":1.7320508075688772},"268":{"tf":1.0}}}}}}},"v":{"df":0,"docs":{},"i":{"d":{"df":28,"docs":{"103":{"tf":1.4142135623730951},"116":{"tf":1.0},"128":{"tf":1.0},"145":{"tf":1.0},"159":{"tf":1.0},"164":{"tf":1.0},"189":{"tf":1.0},"201":{"tf":1.0},"220":{"tf":1.0},"222":{"tf":1.0},"229":{"tf":1.0},"234":{"tf":1.0},"243":{"tf":1.0},"245":{"tf":1.0},"253":{"tf":1.0},"254":{"tf":1.4142135623730951},"257":{"tf":1.0},"268":{"tf":1.0},"274":{"tf":1.0},"278":{"tf":1.0},"282":{"tf":1.0},"283":{"tf":1.4142135623730951},"37":{"tf":1.0},"45":{"tf":1.0},"62":{"tf":1.4142135623730951},"64":{"tf":1.0},"77":{"tf":1.0},"78":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":1,"docs":{"274":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":1,"docs":{"208":{"tf":1.0}}}}}},"u":{"b":{"df":6,"docs":{"103":{"tf":1.0},"105":{"tf":1.4142135623730951},"111":{"tf":1.0},"114":{"tf":1.0},"69":{"tf":1.4142135623730951},"73":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"c":{"df":11,"docs":{"105":{"tf":1.0},"106":{"tf":1.4142135623730951},"107":{"tf":1.7320508075688772},"108":{"tf":1.0},"12":{"tf":1.0},"2":{"tf":1.0},"252":{"tf":1.0},"274":{"tf":1.0},"283":{"tf":1.0},"78":{"tf":1.0},"97":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"146":{"tf":1.0}}}}},"df":0,"docs":{},"s":{"df":1,"docs":{"170":{"tf":1.0}},"h":{"df":24,"docs":{"11":{"tf":1.0},"112":{"tf":1.0},"113":{"tf":1.7320508075688772},"16":{"tf":2.0},"166":{"tf":1.0},"167":{"tf":1.7320508075688772},"169":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":2.23606797749979},"189":{"tf":1.0},"20":{"tf":2.0},"22":{"tf":2.0},"220":{"tf":1.0},"221":{"tf":1.0},"253":{"tf":1.0},"267":{"tf":1.0},"274":{"tf":2.8284271247461903},"275":{"tf":1.0},"276":{"tf":1.0},"277":{"tf":1.0},"278":{"tf":1.0},"37":{"tf":1.0},"69":{"tf":1.0},"70":{"tf":1.0}},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"df":1,"docs":{"20":{"tf":1.0}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":8,"docs":{"167":{"tf":1.0},"25":{"tf":1.0},"274":{"tf":2.0},"280":{"tf":1.0},"59":{"tf":1.0},"64":{"tf":1.0},"7":{"tf":2.449489742783178},"81":{"tf":1.0}}}},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"226":{"tf":1.0}}}},"r":{"df":0,"docs":{},"e":{"df":1,"docs":{"203":{"tf":1.0}}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":7,"docs":{"124":{"tf":1.4142135623730951},"232":{"tf":1.0},"233":{"tf":1.4142135623730951},"237":{"tf":1.0},"252":{"tf":1.0},"274":{"tf":1.0},"51":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"u":{"df":2,"docs":{"115":{"tf":1.0},"152":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"h":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"94":{"tf":1.0}}}}}}}}}},"df":9,"docs":{"104":{"tf":1.0},"189":{"tf":1.0},"253":{"tf":1.0},"26":{"tf":1.0},"264":{"tf":1.0},"267":{"tf":1.4142135623730951},"274":{"tf":1.7320508075688772},"283":{"tf":1.0},"7":{"tf":1.4142135623730951}}}},"t":{"df":7,"docs":{"122":{"tf":1.0},"17":{"tf":1.0},"59":{"tf":1.4142135623730951},"60":{"tf":1.0},"61":{"tf":1.7320508075688772},"69":{"tf":1.4142135623730951},"79":{"tf":1.0}}}},"w":{"d":{"df":2,"docs":{"31":{"tf":1.0},"36":{"tf":1.0}}},"df":0,"docs":{}},"y":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"=":{"$":{"(":{"df":0,"docs":{},"w":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"11":{"tf":1.0}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"`":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"y":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"11":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"11":{"tf":1.0}}}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"'":{"df":1,"docs":{"11":{"tf":1.0}}},"3":{"=":{"$":{"(":{"b":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":1,"docs":{"11":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"11":{"tf":2.8284271247461903}}},"df":4,"docs":{"11":{"tf":2.449489742783178},"193":{"tf":1.0},"269":{"tf":1.0},"53":{"tf":1.0}}}}}}}},"q":{"1":{"df":1,"docs":{"187":{"tf":1.0}}},"a":{"df":1,"docs":{"190":{"tf":1.0}}},"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":3,"docs":{"63":{"tf":1.0},"64":{"tf":1.0},"8":{"tf":1.0}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":7,"docs":{"154":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0},"205":{"tf":1.4142135623730951},"206":{"tf":1.4142135623730951},"6":{"tf":1.0},"62":{"tf":2.23606797749979}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":5,"docs":{"11":{"tf":1.0},"149":{"tf":1.0},"205":{"tf":1.0},"274":{"tf":1.0},"60":{"tf":1.0}}}}}}}},"i":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"236":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"170":{"tf":1.0}}}},"l":{"df":0,"docs":{},"i":{"df":3,"docs":{"167":{"tf":1.0},"178":{"tf":1.0},"60":{"tf":1.0}}}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"243":{"tf":1.0}}}},"t":{"df":6,"docs":{"220":{"tf":1.0},"226":{"tf":1.0},"229":{"tf":1.0},"62":{"tf":1.4142135623730951},"68":{"tf":1.0},"84":{"tf":1.0}}}},"o":{"df":1,"docs":{"171":{"tf":1.0}}}}},"r":{"a":{"d":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"86":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":1,"docs":{"132":{"tf":1.0}}}},"n":{"df":3,"docs":{"203":{"tf":1.0},"30":{"tf":1.0},"35":{"tf":1.0}}},"p":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"189":{"tf":1.0},"68":{"tf":1.0}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"e":{"df":2,"docs":{"17":{"tf":1.0},"86":{"tf":1.0}}}},"t":{"df":0,"docs":{},"e":{"df":8,"docs":{"205":{"tf":1.4142135623730951},"208":{"tf":1.0},"214":{"tf":1.0},"216":{"tf":2.8284271247461903},"217":{"tf":1.0},"218":{"tf":1.0},"225":{"tf":1.0},"242":{"tf":1.0}}},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":1,"docs":{"104":{"tf":1.0}}}}}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"138":{"tf":1.0}}}}}},"w":{"df":3,"docs":{"128":{"tf":1.0},"79":{"tf":1.0},"83":{"tf":1.0}}}},"c":{"_":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":2,"docs":{"281":{"tf":1.0},"282":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":1,"docs":{"12":{"tf":1.0}}},"df":2,"docs":{"269":{"tf":1.0},"53":{"tf":1.0}},"e":{"a":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"6":{"tf":1.0}}},"t":{"df":1,"docs":{"233":{"tf":1.0}}}},"d":{"df":9,"docs":{"107":{"tf":1.0},"120":{"tf":1.0},"174":{"tf":1.4142135623730951},"23":{"tf":1.4142135623730951},"32":{"tf":1.4142135623730951},"62":{"tf":1.4142135623730951},"7":{"tf":1.0},"79":{"tf":1.4142135623730951},"81":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"79":{"tf":1.4142135623730951}}}},"i":{"df":6,"docs":{"118":{"tf":1.0},"184":{"tf":1.0},"227":{"tf":1.0},"24":{"tf":1.4142135623730951},"267":{"tf":1.0},"58":{"tf":1.0}}},"m":{"df":1,"docs":{"281":{"tf":1.0}}}},"df":0,"docs":{},"l":{"df":6,"docs":{"16":{"tf":1.0},"200":{"tf":1.0},"204":{"tf":1.0},"233":{"tf":1.0},"237":{"tf":1.4142135623730951},"273":{"tf":1.0}},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"194":{"tf":1.0},"196":{"tf":1.0}}}},"z":{"df":1,"docs":{"61":{"tf":1.0}}}},"l":{"df":0,"docs":{},"i":{"df":8,"docs":{"116":{"tf":1.0},"129":{"tf":1.0},"169":{"tf":1.0},"40":{"tf":1.0},"58":{"tf":1.4142135623730951},"60":{"tf":1.0},"73":{"tf":1.4142135623730951},"79":{"tf":1.4142135623730951}}}}},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":14,"docs":{"152":{"tf":1.0},"192":{"tf":1.0},"194":{"tf":1.0},"197":{"tf":1.0},"201":{"tf":1.0},"205":{"tf":1.4142135623730951},"208":{"tf":1.4142135623730951},"217":{"tf":1.0},"224":{"tf":1.0},"242":{"tf":1.7320508075688772},"27":{"tf":1.0},"58":{"tf":1.0},"61":{"tf":1.4142135623730951},"7":{"tf":1.0}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"df":1,"docs":{"86":{"tf":1.0}}}}}}}},"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":3,"docs":{"124":{"tf":1.0},"37":{"tf":1.0},"61":{"tf":1.0}}},"df":0,"docs":{},"t":{"df":1,"docs":{"59":{"tf":1.0}}}}}}},"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":3,"docs":{"106":{"tf":1.0},"189":{"tf":1.0},"201":{"tf":1.0}}}},"n":{"df":0,"docs":{},"t":{"df":10,"docs":{"122":{"tf":1.0},"123":{"tf":1.4142135623730951},"170":{"tf":1.0},"193":{"tf":1.4142135623730951},"206":{"tf":1.4142135623730951},"208":{"tf":1.4142135623730951},"209":{"tf":1.4142135623730951},"210":{"tf":1.0},"231":{"tf":1.0},"78":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":4,"docs":{"101":{"tf":1.0},"11":{"tf":1.0},"14":{"tf":1.0},"274":{"tf":1.0}}},"df":0,"docs":{}}}}},"n":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":2,"docs":{"245":{"tf":1.0},"89":{"tf":1.0}},"e":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"249":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"r":{"d":{"df":95,"docs":{"134":{"tf":1.7320508075688772},"135":{"tf":1.7320508075688772},"136":{"tf":1.7320508075688772},"137":{"tf":1.4142135623730951},"138":{"tf":1.0},"139":{"tf":1.0},"140":{"tf":1.0},"141":{"tf":1.0},"142":{"tf":1.0},"143":{"tf":1.0},"144":{"tf":1.0},"145":{"tf":1.4142135623730951},"146":{"tf":2.0},"147":{"tf":1.0},"148":{"tf":1.0},"149":{"tf":1.0},"150":{"tf":1.0},"151":{"tf":1.0},"152":{"tf":1.0},"153":{"tf":1.0},"154":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0},"157":{"tf":1.0},"158":{"tf":1.0},"159":{"tf":1.0},"160":{"tf":1.0},"161":{"tf":1.0},"162":{"tf":1.0},"163":{"tf":1.0},"164":{"tf":1.0},"165":{"tf":1.0},"166":{"tf":1.0},"167":{"tf":1.0},"168":{"tf":1.0},"169":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"173":{"tf":1.0},"174":{"tf":1.0},"175":{"tf":1.0},"176":{"tf":1.0},"177":{"tf":1.0},"178":{"tf":1.0},"179":{"tf":1.0},"180":{"tf":1.0},"181":{"tf":1.0},"182":{"tf":1.0},"183":{"tf":1.0},"184":{"tf":1.0},"185":{"tf":1.0},"186":{"tf":1.0},"187":{"tf":1.0},"188":{"tf":1.0},"189":{"tf":1.4142135623730951},"190":{"tf":1.4142135623730951},"191":{"tf":1.0},"192":{"tf":1.0},"193":{"tf":1.0},"194":{"tf":1.0},"195":{"tf":1.0},"196":{"tf":1.0},"197":{"tf":1.0},"198":{"tf":1.0},"199":{"tf":1.0},"200":{"tf":1.0},"201":{"tf":1.0},"202":{"tf":1.0},"203":{"tf":1.0},"204":{"tf":1.0},"205":{"tf":1.0},"206":{"tf":1.0},"207":{"tf":1.0},"208":{"tf":1.0},"209":{"tf":1.0},"210":{"tf":1.0},"211":{"tf":1.0},"212":{"tf":1.0},"213":{"tf":1.0},"214":{"tf":1.0},"215":{"tf":1.0},"216":{"tf":1.0},"217":{"tf":1.0},"218":{"tf":1.0},"231":{"tf":1.7320508075688772},"237":{"tf":1.0},"245":{"tf":1.0},"246":{"tf":1.7320508075688772},"248":{"tf":2.0},"249":{"tf":4.123105625617661},"250":{"tf":2.6457513110645907},"268":{"tf":1.0},"84":{"tf":1.4142135623730951},"88":{"tf":1.7320508075688772}}},"df":0,"docs":{}},"v":{"df":1,"docs":{"158":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"158":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":5,"docs":{"151":{"tf":1.4142135623730951},"152":{"tf":1.0},"156":{"tf":1.0},"157":{"tf":1.4142135623730951},"158":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":1,"docs":{"11":{"tf":1.0}}}}}},"d":{"df":1,"docs":{"109":{"tf":1.0}},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"283":{"tf":1.4142135623730951}}}}}},"df":1,"docs":{"201":{"tf":1.0}}}},"df":0,"docs":{}}}},"u":{"c":{"df":7,"docs":{"142":{"tf":1.0},"143":{"tf":1.0},"145":{"tf":1.7320508075688772},"165":{"tf":1.0},"205":{"tf":1.0},"220":{"tf":1.0},"222":{"tf":1.0}}},"df":0,"docs":{},"n":{"d":{"df":5,"docs":{"145":{"tf":1.0},"146":{"tf":1.0},"263":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}},"df":14,"docs":{"105":{"tf":1.0},"124":{"tf":1.7320508075688772},"14":{"tf":1.0},"142":{"tf":1.0},"161":{"tf":1.0},"193":{"tf":1.0},"233":{"tf":1.0},"252":{"tf":1.0},"258":{"tf":1.0},"26":{"tf":1.0},"283":{"tf":1.0},"81":{"tf":1.0},"86":{"tf":1.0},"90":{"tf":1.0}},"e":{"df":0,"docs":{},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":1,"docs":{"201":{"tf":1.0}}}}},"df":0,"docs":{}}},"f":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"81":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":2,"docs":{"171":{"tf":1.0},"280":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":14,"docs":{"109":{"tf":1.4142135623730951},"132":{"tf":1.4142135623730951},"167":{"tf":1.4142135623730951},"237":{"tf":1.0},"238":{"tf":1.0},"242":{"tf":1.4142135623730951},"29":{"tf":1.0},"31":{"tf":1.0},"34":{"tf":1.0},"36":{"tf":1.0},"60":{"tf":1.0},"70":{"tf":1.0},"79":{"tf":1.0},"82":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"61":{"tf":1.0}}},"df":0,"docs":{}}}}},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"242":{"tf":1.0},"87":{"tf":1.0},"98":{"tf":1.0}}}},"df":0,"docs":{}}}},"g":{"a":{"df":0,"docs":{},"r":{"d":{"df":4,"docs":{"122":{"tf":1.0},"231":{"tf":1.0},"238":{"tf":1.0},"242":{"tf":1.0}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"233":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"_":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"y":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"64":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":1,"docs":{"64":{"tf":1.0}}}}}},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"283":{"tf":1.4142135623730951}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":3,"docs":{"206":{"tf":1.0},"208":{"tf":1.0},"212":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"r":{"df":2,"docs":{"225":{"tf":1.0},"62":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"64":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"i":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"116":{"tf":1.0}}}}}}}}}}},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"176":{"tf":1.0},"180":{"tf":1.4142135623730951},"196":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"l":{"=":{"\"":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"283":{"tf":1.7320508075688772}}}}}}}}}}}}},"df":0,"docs":{}},"a":{"df":0,"docs":{},"t":{"df":13,"docs":{"0":{"tf":1.0},"123":{"tf":1.4142135623730951},"145":{"tf":1.0},"149":{"tf":1.4142135623730951},"20":{"tf":1.0},"205":{"tf":1.0},"226":{"tf":1.0},"26":{"tf":1.0},"278":{"tf":1.0},"282":{"tf":1.0},"283":{"tf":1.0},"48":{"tf":1.4142135623730951},"84":{"tf":1.0}}}},"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":1,"docs":{"272":{"tf":1.0}}}}},"df":7,"docs":{"121":{"tf":1.0},"152":{"tf":1.0},"154":{"tf":1.0},"167":{"tf":1.0},"18":{"tf":1.4142135623730951},"192":{"tf":1.0},"22":{"tf":1.0}},"e":{"a":{"df":0,"docs":{},"s":{"df":35,"docs":{"113":{"tf":1.0},"114":{"tf":1.0},"121":{"tf":1.0},"167":{"tf":2.23606797749979},"178":{"tf":1.0},"179":{"tf":1.4142135623730951},"18":{"tf":1.0},"180":{"tf":1.4142135623730951},"183":{"tf":2.23606797749979},"187":{"tf":1.0},"215":{"tf":1.0},"252":{"tf":1.0},"253":{"tf":1.7320508075688772},"26":{"tf":1.4142135623730951},"265":{"tf":1.7320508075688772},"266":{"tf":1.0},"267":{"tf":3.0},"268":{"tf":4.898979485566356},"269":{"tf":2.6457513110645907},"270":{"tf":2.449489742783178},"271":{"tf":2.23606797749979},"272":{"tf":1.4142135623730951},"273":{"tf":2.449489742783178},"274":{"tf":4.0},"275":{"tf":1.0},"276":{"tf":1.0},"277":{"tf":1.0},"278":{"tf":1.0},"279":{"tf":1.0},"280":{"tf":1.0},"281":{"tf":1.0},"282":{"tf":1.0},"40":{"tf":1.0},"60":{"tf":1.4142135623730951},"74":{"tf":1.0}},"e":{".":{"df":0,"docs":{},"j":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"271":{"tf":1.0}}}}}},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"269":{"tf":1.4142135623730951}}}}},"_":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":1,"docs":{"268":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"]":{".":{"0":{"a":{"1":{"df":1,"docs":{"268":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":1,"docs":{"268":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"274":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"/":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"119":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":1,"docs":{"266":{"tf":1.0}}}},"v":{"df":6,"docs":{"193":{"tf":1.0},"194":{"tf":1.0},"221":{"tf":1.0},"241":{"tf":1.0},"54":{"tf":1.0},"7":{"tf":1.4142135623730951}}}},"i":{"a":{"b":{"df":0,"docs":{},"l":{"df":2,"docs":{"117":{"tf":1.0},"16":{"tf":1.0}}}},"df":0,"docs":{}},"df":4,"docs":{"124":{"tf":1.0},"128":{"tf":1.0},"278":{"tf":1.0},"64":{"tf":1.0}}}},"m":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":8,"docs":{"103":{"tf":1.4142135623730951},"105":{"tf":1.0},"142":{"tf":1.0},"143":{"tf":1.0},"145":{"tf":1.4142135623730951},"198":{"tf":1.0},"199":{"tf":1.0},"264":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"b":{"df":2,"docs":{"64":{"tf":1.0},"84":{"tf":1.0}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"t":{"df":21,"docs":{"106":{"tf":1.0},"134":{"tf":1.0},"179":{"tf":1.0},"182":{"tf":1.0},"184":{"tf":1.0},"186":{"tf":1.7320508075688772},"187":{"tf":1.4142135623730951},"188":{"tf":1.0},"190":{"tf":1.4142135623730951},"192":{"tf":1.7320508075688772},"193":{"tf":1.7320508075688772},"194":{"tf":1.4142135623730951},"197":{"tf":1.7320508075688772},"198":{"tf":1.0},"199":{"tf":1.0},"201":{"tf":1.7320508075688772},"231":{"tf":1.0},"242":{"tf":1.0},"249":{"tf":1.4142135623730951},"250":{"tf":1.0},"268":{"tf":1.0}},"e":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"u":{"d":{"df":0,"docs":{},"l":{"df":1,"docs":{"200":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"200":{"tf":2.449489742783178}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"s":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}}}}}},"v":{"df":17,"docs":{"105":{"tf":1.0},"106":{"tf":2.0},"108":{"tf":1.0},"109":{"tf":1.4142135623730951},"145":{"tf":2.0},"146":{"tf":1.4142135623730951},"167":{"tf":1.0},"172":{"tf":1.0},"197":{"tf":1.0},"203":{"tf":1.0},"231":{"tf":1.0},"25":{"tf":1.4142135623730951},"26":{"tf":1.0},"264":{"tf":1.0},"268":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0}}}}},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"78":{"tf":1.0}}}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":3,"docs":{"45":{"tf":1.0},"46":{"tf":1.0},"75":{"tf":1.0}}}},"df":0,"docs":{}},"l":{"a":{"c":{"df":14,"docs":{"103":{"tf":1.0},"106":{"tf":1.4142135623730951},"109":{"tf":1.0},"13":{"tf":1.0},"166":{"tf":1.0},"200":{"tf":1.0},"203":{"tf":1.0},"221":{"tf":1.0},"222":{"tf":1.7320508075688772},"231":{"tf":1.4142135623730951},"258":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"70":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{},"i":{"c":{"df":3,"docs":{"140":{"tf":1.0},"145":{"tf":1.0},"61":{"tf":1.0}}},"df":0,"docs":{}}},"o":{"df":19,"docs":{"101":{"tf":1.0},"102":{"tf":1.0},"106":{"tf":1.0},"118":{"tf":1.0},"124":{"tf":1.0},"13":{"tf":1.0},"167":{"tf":2.8284271247461903},"18":{"tf":1.0},"197":{"tf":1.0},"20":{"tf":1.4142135623730951},"200":{"tf":1.4142135623730951},"21":{"tf":1.0},"225":{"tf":1.0},"253":{"tf":1.0},"268":{"tf":1.4142135623730951},"274":{"tf":1.7320508075688772},"49":{"tf":1.0},"69":{"tf":1.0},"75":{"tf":1.0}},"r":{"df":0,"docs":{},"t":{"df":5,"docs":{"1":{"tf":1.4142135623730951},"104":{"tf":1.0},"249":{"tf":1.0},"4":{"tf":1.4142135623730951},"50":{"tf":1.0}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":37,"docs":{"11":{"tf":1.0},"113":{"tf":1.0},"118":{"tf":1.4142135623730951},"12":{"tf":1.4142135623730951},"124":{"tf":1.0},"13":{"tf":1.0},"16":{"tf":1.0},"167":{"tf":2.23606797749979},"17":{"tf":1.0},"18":{"tf":1.0},"194":{"tf":1.0},"197":{"tf":1.0},"20":{"tf":1.7320508075688772},"200":{"tf":1.4142135623730951},"22":{"tf":1.0},"23":{"tf":1.0},"25":{"tf":1.0},"251":{"tf":1.0},"253":{"tf":1.7320508075688772},"260":{"tf":1.0},"271":{"tf":1.0},"274":{"tf":1.0},"284":{"tf":1.0},"286":{"tf":1.0},"29":{"tf":1.0},"30":{"tf":1.0},"32":{"tf":1.0},"34":{"tf":1.4142135623730951},"35":{"tf":1.0},"37":{"tf":1.0},"46":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.7320508075688772},"68":{"tf":1.0},"69":{"tf":1.4142135623730951},"70":{"tf":1.0},"74":{"tf":1.0}}}}}}}}},"r":{"df":1,"docs":{"268":{"tf":1.0}},"e":{"df":0,"docs":{},"s":{"df":3,"docs":{"106":{"tf":1.4142135623730951},"252":{"tf":1.0},"283":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"214":{"tf":1.0}}}}}}}}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{".":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"a":{"df":1,"docs":{"283":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":1,"docs":{"283":{"tf":1.0}}}}}}}}}}}}}}}},"df":12,"docs":{"122":{"tf":1.0},"189":{"tf":1.7320508075688772},"201":{"tf":1.4142135623730951},"225":{"tf":1.0},"226":{"tf":1.4142135623730951},"233":{"tf":1.4142135623730951},"236":{"tf":1.0},"245":{"tf":1.7320508075688772},"259":{"tf":1.0},"274":{"tf":2.0},"280":{"tf":1.0},"7":{"tf":2.449489742783178}}}}},"i":{"df":0,"docs":{},"r":{"df":50,"docs":{"10":{"tf":1.0},"105":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"11":{"tf":2.23606797749979},"116":{"tf":1.0},"12":{"tf":2.0},"141":{"tf":1.0},"142":{"tf":1.0},"143":{"tf":1.4142135623730951},"145":{"tf":2.449489742783178},"146":{"tf":1.0},"167":{"tf":1.0},"169":{"tf":1.0},"170":{"tf":1.0},"175":{"tf":1.0},"18":{"tf":1.0},"183":{"tf":1.0},"187":{"tf":2.0},"188":{"tf":2.449489742783178},"189":{"tf":3.4641016151377544},"190":{"tf":2.0},"193":{"tf":1.0},"196":{"tf":1.7320508075688772},"197":{"tf":2.23606797749979},"198":{"tf":1.4142135623730951},"199":{"tf":1.0},"200":{"tf":1.4142135623730951},"201":{"tf":2.449489742783178},"22":{"tf":1.4142135623730951},"220":{"tf":1.0},"222":{"tf":1.4142135623730951},"225":{"tf":1.0},"229":{"tf":1.4142135623730951},"23":{"tf":1.0},"231":{"tf":1.4142135623730951},"233":{"tf":1.0},"241":{"tf":1.0},"261":{"tf":1.4142135623730951},"262":{"tf":1.0},"263":{"tf":1.4142135623730951},"269":{"tf":1.0},"274":{"tf":1.0},"277":{"tf":1.0},"279":{"tf":1.0},"286":{"tf":1.0},"53":{"tf":1.0},"61":{"tf":1.0},"64":{"tf":1.0},"81":{"tf":1.7320508075688772}}}}}},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":1,"docs":{"12":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":7,"docs":{"234":{"tf":1.0},"242":{"tf":1.0},"245":{"tf":1.0},"25":{"tf":2.0},"26":{"tf":1.7320508075688772},"86":{"tf":1.0},"89":{"tf":1.7320508075688772}}}},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"v":{"df":4,"docs":{"118":{"tf":1.0},"22":{"tf":1.0},"40":{"tf":1.0},"6":{"tf":1.0}}}},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"111":{"tf":1.0}}}},"u":{"df":0,"docs":{},"r":{"c":{"df":8,"docs":{"128":{"tf":1.0},"133":{"tf":1.0},"141":{"tf":1.0},"194":{"tf":1.0},"226":{"tf":1.4142135623730951},"252":{"tf":1.0},"64":{"tf":1.0},"73":{"tf":1.0}}},"df":0,"docs":{}}}},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"60":{"tf":1.0},"64":{"tf":1.0}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":16,"docs":{"121":{"tf":1.4142135623730951},"132":{"tf":1.0},"146":{"tf":1.0},"189":{"tf":1.0},"190":{"tf":1.0},"194":{"tf":1.0},"201":{"tf":2.449489742783178},"225":{"tf":2.23606797749979},"226":{"tf":2.0},"241":{"tf":1.0},"245":{"tf":1.7320508075688772},"246":{"tf":1.0},"249":{"tf":1.0},"64":{"tf":1.0},"7":{"tf":1.0},"78":{"tf":1.0}}}}}},"t":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"233":{"tf":1.0}}}}},"df":3,"docs":{"201":{"tf":1.4142135623730951},"205":{"tf":1.0},"37":{"tf":1.0}},"o":{"df":0,"docs":{},"r":{"df":5,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"226":{"tf":1.0},"231":{"tf":1.0},"86":{"tf":1.0}}}},"r":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"105":{"tf":1.7320508075688772}}}}}},"df":0,"docs":{}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"<":{"df":0,"docs":{},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"242":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}}}},"df":17,"docs":{"113":{"tf":1.4142135623730951},"132":{"tf":1.0},"174":{"tf":1.0},"188":{"tf":1.0},"189":{"tf":1.0},"197":{"tf":1.0},"203":{"tf":1.0},"208":{"tf":1.0},"220":{"tf":1.0},"224":{"tf":1.0},"242":{"tf":2.6457513110645907},"252":{"tf":1.0},"266":{"tf":1.0},"283":{"tf":1.0},"58":{"tf":1.0},"7":{"tf":1.4142135623730951},"73":{"tf":1.7320508075688772}}}}}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":6,"docs":{"179":{"tf":1.7320508075688772},"180":{"tf":1.0},"184":{"tf":1.7320508075688772},"185":{"tf":1.4142135623730951},"217":{"tf":1.0},"254":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":9,"docs":{"106":{"tf":1.4142135623730951},"145":{"tf":1.0},"159":{"tf":1.4142135623730951},"188":{"tf":1.0},"189":{"tf":1.7320508075688772},"229":{"tf":1.0},"233":{"tf":1.0},"249":{"tf":1.4142135623730951},"283":{"tf":2.0}}}}}},"u":{"df":0,"docs":{},"s":{"df":3,"docs":{"199":{"tf":1.0},"225":{"tf":1.0},"242":{"tf":1.0}}}},"v":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":1,"docs":{"180":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":1,"docs":{"201":{"tf":1.0}}}},"r":{"df":0,"docs":{},"s":{"df":1,"docs":{"104":{"tf":1.0}}},"t":{"df":1,"docs":{"26":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":9,"docs":{"122":{"tf":1.7320508075688772},"143":{"tf":1.0},"146":{"tf":1.0},"150":{"tf":1.0},"189":{"tf":1.0},"259":{"tf":1.0},"274":{"tf":1.0},"7":{"tf":1.4142135623730951},"8":{"tf":1.7320508075688772}},"e":{"df":0,"docs":{},"r":{"'":{"df":1,"docs":{"7":{"tf":1.0}}},"df":0,"docs":{}}}}},"s":{"df":1,"docs":{"123":{"tf":1.7320508075688772}}}},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"v":{"df":1,"docs":{"261":{"tf":1.0}}}}}}},"f":{"c":{"df":1,"docs":{"185":{"tf":1.0}}},"df":1,"docs":{"279":{"tf":1.0}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"160":{"tf":1.0}}}}}}}},"i":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"106":{"tf":1.0}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":5,"docs":{"126":{"tf":1.7320508075688772},"129":{"tf":1.0},"212":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"k":{"df":3,"docs":{"201":{"tf":1.0},"203":{"tf":1.4142135623730951},"252":{"tf":1.0}}}}},"k":{"df":0,"docs":{},"v":{"df":3,"docs":{"194":{"tf":1.0},"197":{"tf":1.0},"79":{"tf":1.0}}}},"l":{"df":1,"docs":{"200":{"tf":1.0}}},"m":{"df":1,"docs":{"279":{"tf":1.0}}},"o":{"a":{"d":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"192":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":2,"docs":{"223":{"tf":1.0},"45":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"_":{"_":{"_":{"_":{"_":{"_":{"_":{"_":{"df":1,"docs":{"62":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":13,"docs":{"113":{"tf":1.0},"122":{"tf":1.0},"167":{"tf":1.0},"17":{"tf":1.0},"19":{"tf":1.0},"20":{"tf":1.4142135623730951},"29":{"tf":1.0},"34":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.4142135623730951},"62":{"tf":1.0},"69":{"tf":1.4142135623730951},"70":{"tf":1.0}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"}":{"/":{"$":{"df":0,"docs":{},"{":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"r":{"c":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"}":{"/":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"22":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"t":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"276":{"tf":1.0}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":1,"docs":{"224":{"tf":1.7320508075688772}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"242":{"tf":1.0}}}}}}},"w":{"df":4,"docs":{"156":{"tf":1.0},"249":{"tf":1.0},"62":{"tf":1.0},"82":{"tf":1.4142135623730951}}}},"p":{"0":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"_":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"df":0,"docs":{},"i":{"0":{"df":0,"docs":{},"e":{"df":0,"docs":{},"z":{"_":{"df":0,"docs":{},"j":{"df":0,"docs":{},"q":{"df":0,"docs":{},"s":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"z":{"df":0,"docs":{},"o":{"df":0,"docs":{},"h":{"df":0,"docs":{},"j":{"df":0,"docs":{},"y":{"/":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"185":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"s":{"#":{"4":{"1":{"6":{"df":1,"docs":{"107":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"200":{"tf":2.6457513110645907}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":5,"docs":{"118":{"tf":1.0},"222":{"tf":1.0},"59":{"tf":1.0},"73":{"tf":1.0},"79":{"tf":1.0}}}},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"282":{"tf":1.0}}}}},"n":{"_":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"264":{"tf":1.0}}}}}}}},"df":0,"docs":{}}},"df":78,"docs":{"105":{"tf":1.0},"107":{"tf":1.0},"109":{"tf":1.0},"11":{"tf":2.0},"115":{"tf":1.4142135623730951},"117":{"tf":1.0},"118":{"tf":1.0},"12":{"tf":1.0},"122":{"tf":1.7320508075688772},"124":{"tf":1.4142135623730951},"126":{"tf":1.4142135623730951},"127":{"tf":1.4142135623730951},"133":{"tf":1.4142135623730951},"134":{"tf":1.4142135623730951},"14":{"tf":2.0},"154":{"tf":1.0},"155":{"tf":1.0},"17":{"tf":2.0},"170":{"tf":1.0},"176":{"tf":2.0},"177":{"tf":2.0},"178":{"tf":1.0},"179":{"tf":2.23606797749979},"180":{"tf":1.7320508075688772},"182":{"tf":1.0},"183":{"tf":2.0},"184":{"tf":1.7320508075688772},"185":{"tf":1.4142135623730951},"19":{"tf":1.0},"190":{"tf":1.0},"194":{"tf":1.4142135623730951},"20":{"tf":1.4142135623730951},"203":{"tf":2.0},"204":{"tf":1.4142135623730951},"205":{"tf":1.4142135623730951},"208":{"tf":1.0},"209":{"tf":1.0},"214":{"tf":1.4142135623730951},"216":{"tf":1.4142135623730951},"218":{"tf":1.4142135623730951},"22":{"tf":1.7320508075688772},"223":{"tf":1.4142135623730951},"23":{"tf":1.0},"25":{"tf":1.4142135623730951},"26":{"tf":1.0},"269":{"tf":1.4142135623730951},"27":{"tf":1.4142135623730951},"274":{"tf":3.0},"28":{"tf":1.0},"280":{"tf":1.0},"30":{"tf":1.7320508075688772},"31":{"tf":1.0},"32":{"tf":1.0},"33":{"tf":1.0},"35":{"tf":1.7320508075688772},"36":{"tf":1.0},"37":{"tf":1.0},"39":{"tf":1.4142135623730951},"40":{"tf":1.4142135623730951},"41":{"tf":1.0},"45":{"tf":1.4142135623730951},"48":{"tf":1.0},"49":{"tf":1.7320508075688772},"51":{"tf":1.4142135623730951},"53":{"tf":1.0},"54":{"tf":1.4142135623730951},"55":{"tf":1.0},"56":{"tf":1.0},"58":{"tf":1.4142135623730951},"60":{"tf":1.7320508075688772},"61":{"tf":1.0},"62":{"tf":1.0},"64":{"tf":1.4142135623730951},"69":{"tf":1.0},"7":{"tf":2.23606797749979},"70":{"tf":1.4142135623730951},"73":{"tf":1.0},"82":{"tf":1.0}},"n":{"a":{"b":{"df":0,"docs":{},"l":{"df":2,"docs":{"43":{"tf":1.0},"45":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":3,"docs":{"222":{"tf":1.0},"224":{"tf":1.0},"45":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"79":{"tf":1.7320508075688772}}}}}},"t":{"&":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{";":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"k":{"df":1,"docs":{"283":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"'":{"df":3,"docs":{"107":{"tf":1.0},"43":{"tf":1.4142135623730951},"92":{"tf":1.0}}},".":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"17":{"tf":1.7320508075688772}},"s":{"=":{"df":0,"docs":{},"x":{"8":{"6":{",":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"x":{"df":1,"docs":{"17":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"/":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":2,"docs":{"14":{"tf":1.0},"73":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"x":{"c":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{".":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":1,"docs":{"34":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"h":{"df":1,"docs":{"73":{"tf":1.0}}},"x":{"c":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{".":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":1,"docs":{"29":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"h":{"df":1,"docs":{"109":{"tf":1.0}}},"x":{"c":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"df":2,"docs":{"109":{"tf":1.0},"14":{"tf":1.0}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"x":{"c":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"df":1,"docs":{"73":{"tf":1.0}}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"46":{"tf":1.0},"73":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}}}}},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"/":{"c":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":1,"docs":{"69":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"s":{"df":0,"docs":{},"r":{"c":{"/":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"b":{".":{"df":0,"docs":{},"r":{"df":2,"docs":{"69":{"tf":1.0},"73":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"<":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{">":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{".":{"df":0,"docs":{},"h":{"df":2,"docs":{"103":{"tf":1.4142135623730951},"109":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}},"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"_":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"c":{"a":{"df":0,"docs":{},"l":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":2,"docs":{"25":{"tf":1.0},"26":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"x":{"c":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{".":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":1,"docs":{"271":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}}}}}}}},"d":{"df":0,"docs":{},"o":{"c":{"df":2,"docs":{"107":{"tf":1.0},"283":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"df":121,"docs":{"0":{"tf":2.23606797749979},"1":{"tf":1.4142135623730951},"10":{"tf":1.0},"101":{"tf":1.4142135623730951},"102":{"tf":1.0},"103":{"tf":2.8284271247461903},"104":{"tf":2.0},"105":{"tf":2.23606797749979},"106":{"tf":1.7320508075688772},"107":{"tf":2.23606797749979},"108":{"tf":2.0},"109":{"tf":2.0},"11":{"tf":1.7320508075688772},"110":{"tf":1.7320508075688772},"111":{"tf":2.449489742783178},"112":{"tf":1.0},"113":{"tf":1.0},"114":{"tf":2.0},"115":{"tf":2.6457513110645907},"116":{"tf":2.23606797749979},"117":{"tf":2.23606797749979},"119":{"tf":1.0},"120":{"tf":1.0},"124":{"tf":1.4142135623730951},"125":{"tf":1.0},"128":{"tf":1.0},"14":{"tf":1.0},"140":{"tf":1.0},"163":{"tf":1.4142135623730951},"164":{"tf":1.0},"167":{"tf":3.1622776601683795},"17":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":2.0},"174":{"tf":1.0},"187":{"tf":1.0},"193":{"tf":2.449489742783178},"194":{"tf":1.4142135623730951},"196":{"tf":1.4142135623730951},"197":{"tf":1.4142135623730951},"2":{"tf":1.0},"200":{"tf":1.4142135623730951},"219":{"tf":2.23606797749979},"220":{"tf":2.23606797749979},"221":{"tf":2.449489742783178},"222":{"tf":1.4142135623730951},"223":{"tf":1.4142135623730951},"224":{"tf":1.4142135623730951},"225":{"tf":2.449489742783178},"226":{"tf":1.7320508075688772},"228":{"tf":2.6457513110645907},"23":{"tf":2.0},"24":{"tf":1.0},"241":{"tf":2.449489742783178},"242":{"tf":1.7320508075688772},"25":{"tf":2.0},"251":{"tf":2.449489742783178},"252":{"tf":2.0},"253":{"tf":2.6457513110645907},"254":{"tf":1.0},"255":{"tf":1.0},"256":{"tf":1.4142135623730951},"257":{"tf":2.449489742783178},"258":{"tf":1.4142135623730951},"26":{"tf":2.449489742783178},"260":{"tf":2.449489742783178},"261":{"tf":2.449489742783178},"262":{"tf":2.0},"263":{"tf":2.449489742783178},"268":{"tf":1.7320508075688772},"271":{"tf":1.0},"272":{"tf":1.4142135623730951},"274":{"tf":2.8284271247461903},"28":{"tf":1.4142135623730951},"282":{"tf":1.0},"29":{"tf":2.449489742783178},"30":{"tf":1.4142135623730951},"31":{"tf":2.6457513110645907},"32":{"tf":2.0},"33":{"tf":1.4142135623730951},"34":{"tf":2.449489742783178},"35":{"tf":1.4142135623730951},"36":{"tf":2.6457513110645907},"37":{"tf":1.0},"41":{"tf":1.7320508075688772},"42":{"tf":1.0},"43":{"tf":3.1622776601683795},"44":{"tf":1.0},"45":{"tf":1.7320508075688772},"46":{"tf":1.7320508075688772},"47":{"tf":1.0},"48":{"tf":1.4142135623730951},"49":{"tf":1.0},"50":{"tf":1.0},"51":{"tf":1.0},"52":{"tf":1.0},"53":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":2.0},"60":{"tf":1.0},"61":{"tf":1.4142135623730951},"62":{"tf":1.4142135623730951},"64":{"tf":2.0},"68":{"tf":1.0},"69":{"tf":2.0},"70":{"tf":1.0},"72":{"tf":1.0},"73":{"tf":1.0},"74":{"tf":2.449489742783178},"75":{"tf":2.0},"79":{"tf":1.0},"82":{"tf":1.0},"83":{"tf":1.4142135623730951},"9":{"tf":1.0},"90":{"tf":2.0},"92":{"tf":1.4142135623730951},"93":{"tf":2.0}},"j":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"224":{"tf":1.0}}},"df":0,"docs":{}}}}}},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"126":{"tf":1.0}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"x":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"(":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{".":{"d":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":1,"docs":{"126":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"u":{"df":0,"docs":{},"p":{"df":1,"docs":{"11":{"tf":1.0}}}}}}}},"s":{"a":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":4,"docs":{"116":{"tf":1.0},"222":{"tf":1.0},"281":{"tf":1.0},"282":{"tf":1.0}},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"116":{"tf":1.0}}}}}},"m":{"df":0,"docs":{},"e":{"df":32,"docs":{"104":{"tf":1.0},"109":{"tf":1.0},"11":{"tf":1.0},"115":{"tf":1.0},"116":{"tf":1.0},"122":{"tf":1.0},"123":{"tf":1.4142135623730951},"124":{"tf":1.4142135623730951},"129":{"tf":1.0},"145":{"tf":1.0},"165":{"tf":1.0},"167":{"tf":1.0},"175":{"tf":1.4142135623730951},"18":{"tf":1.4142135623730951},"198":{"tf":1.0},"201":{"tf":1.0},"22":{"tf":1.0},"222":{"tf":1.0},"226":{"tf":1.0},"231":{"tf":1.0},"237":{"tf":1.0},"241":{"tf":1.0},"248":{"tf":1.0},"25":{"tf":1.0},"258":{"tf":1.4142135623730951},"26":{"tf":1.7320508075688772},"262":{"tf":1.4142135623730951},"263":{"tf":1.0},"274":{"tf":1.0},"280":{"tf":1.0},"61":{"tf":1.4142135623730951},"84":{"tf":1.0}}},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"49":{"tf":1.0}}}}},"n":{"df":0,"docs":{},"e":{"df":2,"docs":{"241":{"tf":1.0},"86":{"tf":1.0}}},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"51":{"tf":1.0}}}}},"v":{"df":0,"docs":{},"e":{"df":1,"docs":{"286":{"tf":1.0}}}},"w":{"df":2,"docs":{"205":{"tf":1.4142135623730951},"4":{"tf":1.0}}}},"c":{"a":{"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"d":{"df":2,"docs":{"104":{"tf":1.4142135623730951},"105":{"tf":1.0}}},"df":0,"docs":{}}}}},"n":{"df":1,"docs":{"81":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":2,"docs":{"124":{"tf":1.4142135623730951},"19":{"tf":1.0}}}}}},"df":0,"docs":{}}},"h":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":4,"docs":{"189":{"tf":1.0},"226":{"tf":1.0},"242":{"tf":1.0},"274":{"tf":1.0}}}}},"df":0,"docs":{},"m":{"a":{"df":5,"docs":{"189":{"tf":1.0},"268":{"tf":1.0},"78":{"tf":1.0},"79":{"tf":1.0},"81":{"tf":3.0}}},"df":0,"docs":{},"e":{":":{"d":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"283":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":1,"docs":{"283":{"tf":1.0}}}}}}}},"df":0,"docs":{}}}},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":1,"docs":{"37":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"t":{"a":{"b":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"y":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"a":{"d":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"283":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":4,"docs":{"111":{"tf":1.0},"190":{"tf":1.0},"254":{"tf":1.0},"274":{"tf":1.0}}}}},"r":{"a":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"h":{"df":3,"docs":{"101":{"tf":1.0},"196":{"tf":1.0},"68":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"81":{"tf":1.0}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"_":{"c":{"a":{"df":0,"docs":{},"s":{"df":1,"docs":{"93":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"184":{"tf":1.4142135623730951}},"s":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":3,"docs":{"31":{"tf":1.0},"36":{"tf":1.4142135623730951},"73":{"tf":1.7320508075688772}}}}}}}}},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":28,"docs":{"105":{"tf":1.0},"11":{"tf":1.0},"14":{"tf":1.4142135623730951},"167":{"tf":1.0},"172":{"tf":1.0},"22":{"tf":1.0},"224":{"tf":1.0},"23":{"tf":1.0},"25":{"tf":1.7320508075688772},"252":{"tf":1.0},"253":{"tf":1.0},"26":{"tf":1.7320508075688772},"269":{"tf":1.7320508075688772},"27":{"tf":1.7320508075688772},"279":{"tf":1.0},"28":{"tf":1.0},"282":{"tf":1.0},"30":{"tf":2.0},"32":{"tf":1.0},"33":{"tf":1.0},"35":{"tf":2.0},"41":{"tf":1.0},"52":{"tf":1.4142135623730951},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"7":{"tf":2.0},"74":{"tf":1.0}},"s":{"/":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"22":{"tf":1.7320508075688772}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":1,"docs":{"64":{"tf":1.0}}}}}}}}},"d":{"df":0,"docs":{},"k":{"df":8,"docs":{"12":{"tf":2.0},"178":{"tf":1.0},"180":{"tf":1.0},"182":{"tf":1.0},"194":{"tf":1.7320508075688772},"259":{"tf":1.0},"274":{"tf":1.4142135623730951},"284":{"tf":1.0}}}},"df":4,"docs":{"11":{"tf":1.0},"14":{"tf":1.0},"274":{"tf":1.0},"93":{"tf":1.0}},"e":{"a":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"h":{"df":7,"docs":{"126":{"tf":1.4142135623730951},"187":{"tf":1.0},"201":{"tf":1.0},"205":{"tf":2.0},"206":{"tf":1.0},"45":{"tf":1.0},"6":{"tf":1.4142135623730951}},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"x":{"df":1,"docs":{"261":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"d":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"60":{"tf":1.0}}}}},"df":4,"docs":{"167":{"tf":1.0},"177":{"tf":1.0},"204":{"tf":2.23606797749979},"6":{"tf":1.7320508075688772}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":4,"docs":{"201":{"tf":1.0},"274":{"tf":1.4142135623730951},"275":{"tf":1.4142135623730951},"276":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":13,"docs":{"105":{"tf":1.0},"11":{"tf":1.4142135623730951},"12":{"tf":1.0},"200":{"tf":1.0},"201":{"tf":1.0},"209":{"tf":1.0},"210":{"tf":1.0},"214":{"tf":1.0},"227":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"73":{"tf":1.4142135623730951},"85":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"r":{"df":7,"docs":{"141":{"tf":1.4142135623730951},"143":{"tf":1.0},"146":{"tf":1.4142135623730951},"201":{"tf":1.0},"274":{"tf":1.4142135623730951},"63":{"tf":1.0},"64":{"tf":1.0}},"e":{"_":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"264":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{},"e":{"df":26,"docs":{"105":{"tf":1.0},"106":{"tf":1.0},"13":{"tf":1.0},"138":{"tf":1.0},"145":{"tf":1.0},"161":{"tf":1.0},"175":{"tf":1.0},"22":{"tf":1.4142135623730951},"226":{"tf":1.0},"228":{"tf":1.0},"245":{"tf":1.4142135623730951},"261":{"tf":1.0},"264":{"tf":1.0},"266":{"tf":1.0},"268":{"tf":2.0},"274":{"tf":2.0},"280":{"tf":1.0},"38":{"tf":1.0},"40":{"tf":1.0},"48":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"69":{"tf":1.0},"78":{"tf":1.0},"93":{"tf":1.0},"99":{"tf":1.0}},"m":{"df":7,"docs":{"128":{"tf":1.0},"152":{"tf":1.0},"200":{"tf":1.0},"226":{"tf":1.0},"276":{"tf":1.0},"277":{"tf":1.0},"61":{"tf":2.0}}},"n":{"df":1,"docs":{"250":{"tf":1.0}}}},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":9,"docs":{"11":{"tf":1.0},"117":{"tf":1.4142135623730951},"126":{"tf":1.4142135623730951},"209":{"tf":1.0},"253":{"tf":1.0},"280":{"tf":1.0},"62":{"tf":1.4142135623730951},"7":{"tf":1.0},"73":{"tf":1.0}}}},"df":0,"docs":{}},"f":{".":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"_":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"62":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":1,"docs":{"242":{"tf":1.0}}}},"m":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"239":{"tf":1.0}}}}},"df":0,"docs":{}},"n":{"d":{"/":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"c":{"df":1,"docs":{"254":{"tf":1.0}}},"df":0,"docs":{}}}},"df":8,"docs":{"128":{"tf":1.0},"226":{"tf":1.0},"231":{"tf":1.0},"245":{"tf":1.0},"259":{"tf":1.0},"283":{"tf":1.0},"48":{"tf":1.0},"7":{"tf":1.4142135623730951}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"98":{"tf":1.0}}}}},"t":{"df":4,"docs":{"201":{"tf":1.7320508075688772},"249":{"tf":1.0},"274":{"tf":1.0},"283":{"tf":1.4142135623730951}},"r":{"df":0,"docs":{},"i":{"df":4,"docs":{"150":{"tf":1.0},"154":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0}}},"y":{"/":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"152":{"tf":1.0}}}}}}}}}}}},"df":0,"docs":{}}}}},"p":{"a":{"df":0,"docs":{},"r":{"df":16,"docs":{"103":{"tf":1.0},"106":{"tf":1.0},"116":{"tf":1.0},"140":{"tf":1.0},"163":{"tf":1.0},"165":{"tf":1.0},"167":{"tf":1.0},"169":{"tf":1.0},"17":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.4142135623730951},"172":{"tf":1.7320508075688772},"20":{"tf":1.0},"220":{"tf":1.0},"221":{"tf":1.4142135623730951},"46":{"tf":1.0}}}},"df":0,"docs":{}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"37":{"tf":1.0}},"e":{"<":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"106":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"r":{"d":{"df":1,"docs":{"62":{"tf":1.0}}},"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"105":{"tf":1.0},"106":{"tf":1.0}}}},"df":0,"docs":{}},"v":{"df":8,"docs":{"107":{"tf":1.0},"167":{"tf":1.0},"196":{"tf":1.0},"249":{"tf":1.0},"252":{"tf":1.0},"26":{"tf":1.0},"273":{"tf":1.0},"274":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":24,"docs":{"189":{"tf":2.449489742783178},"190":{"tf":1.0},"194":{"tf":1.0},"201":{"tf":2.449489742783178},"225":{"tf":1.0},"226":{"tf":1.0},"229":{"tf":1.0},"232":{"tf":1.0},"233":{"tf":2.0},"236":{"tf":1.0},"242":{"tf":1.0},"244":{"tf":1.0},"245":{"tf":2.0},"246":{"tf":1.0},"248":{"tf":1.4142135623730951},"249":{"tf":2.23606797749979},"250":{"tf":1.4142135623730951},"254":{"tf":2.8284271247461903},"257":{"tf":1.0},"48":{"tf":1.0},"86":{"tf":1.0},"87":{"tf":1.0},"88":{"tf":1.7320508075688772},"89":{"tf":1.4142135623730951}}}},"i":{"c":{"df":83,"docs":{"0":{"tf":2.0},"1":{"tf":1.4142135623730951},"11":{"tf":1.4142135623730951},"114":{"tf":1.0},"118":{"tf":2.0},"119":{"tf":1.0},"12":{"tf":2.0},"120":{"tf":1.7320508075688772},"121":{"tf":1.0},"122":{"tf":2.23606797749979},"123":{"tf":1.4142135623730951},"124":{"tf":2.23606797749979},"125":{"tf":1.7320508075688772},"14":{"tf":1.0},"15":{"tf":1.7320508075688772},"16":{"tf":1.0},"161":{"tf":1.4142135623730951},"162":{"tf":1.0},"164":{"tf":1.0},"165":{"tf":1.4142135623730951},"167":{"tf":3.0},"175":{"tf":1.4142135623730951},"18":{"tf":2.449489742783178},"186":{"tf":1.0},"187":{"tf":1.0},"188":{"tf":1.0},"19":{"tf":1.4142135623730951},"194":{"tf":1.0},"197":{"tf":1.4142135623730951},"2":{"tf":1.4142135623730951},"20":{"tf":1.7320508075688772},"200":{"tf":2.449489742783178},"203":{"tf":1.0},"21":{"tf":1.4142135623730951},"218":{"tf":1.0},"22":{"tf":2.0},"220":{"tf":1.0},"225":{"tf":2.0},"226":{"tf":1.7320508075688772},"228":{"tf":1.0},"23":{"tf":1.7320508075688772},"232":{"tf":1.0},"236":{"tf":1.0},"24":{"tf":1.0},"244":{"tf":1.0},"25":{"tf":1.7320508075688772},"252":{"tf":1.7320508075688772},"253":{"tf":1.4142135623730951},"254":{"tf":1.0},"259":{"tf":1.7320508075688772},"260":{"tf":1.4142135623730951},"262":{"tf":1.4142135623730951},"265":{"tf":1.4142135623730951},"268":{"tf":1.7320508075688772},"274":{"tf":2.23606797749979},"276":{"tf":1.0},"28":{"tf":1.0},"283":{"tf":1.7320508075688772},"284":{"tf":1.0},"29":{"tf":1.0},"3":{"tf":1.7320508075688772},"30":{"tf":2.0},"32":{"tf":1.4142135623730951},"33":{"tf":1.0},"34":{"tf":1.4142135623730951},"35":{"tf":2.0},"37":{"tf":1.0},"38":{"tf":1.0},"39":{"tf":2.0},"40":{"tf":1.7320508075688772},"41":{"tf":1.0},"49":{"tf":1.4142135623730951},"52":{"tf":2.0},"53":{"tf":1.0},"54":{"tf":1.7320508075688772},"55":{"tf":1.4142135623730951},"56":{"tf":1.4142135623730951},"68":{"tf":1.7320508075688772},"69":{"tf":1.0},"7":{"tf":1.0},"74":{"tf":1.0},"8":{"tf":1.0},"9":{"tf":1.7320508075688772}},"e":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":1,"docs":{"242":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"242":{"tf":1.0}}}}}}},"s":{"%":{"2":{"df":0,"docs":{},"f":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"119":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{}},".":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"=":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"/":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"/":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"/":{"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"/":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"/":{"df":0,"docs":{},"o":{"df":0,"docs":{},"f":{"/":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"18":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"df":2,"docs":{"18":{"tf":1.0},"22":{"tf":2.6457513110645907}}}}},"df":0,"docs":{},"v":{"2":{".":{"b":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{".":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{".":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"k":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"/":{"d":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":1,"docs":{"266":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"y":{"/":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"266":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{".":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"/":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"u":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"c":{"%":{"2":{"df":0,"docs":{},"f":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"%":{"2":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{".":{"df":0,"docs":{},"j":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"271":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"/":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"/":{"df":0,"docs":{},"v":{"1":{"1":{"7":{".":{"0":{".":{".":{".":{"df":0,"docs":{},"v":{"1":{"1":{"8":{".":{"0":{"df":1,"docs":{"268":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"200":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"/":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"124":{"tf":1.0}}}}}}}}}}},"s":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"df":1,"docs":{"124":{"tf":1.0}}}},"y":{"df":0,"docs":{},"n":{"c":{"1":{"5":{"df":1,"docs":{"124":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":1,"docs":{"200":{"tf":1.0}}}}}}}}},"v":{"df":0,"docs":{},"i":{"a":{"d":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"124":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"w":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":1,"docs":{"124":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"277":{"tf":1.0}}}}}}}}},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"a":{"df":0,"docs":{},"z":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"d":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":2,"docs":{"29":{"tf":1.0},"34":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"p":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"/":{"5":{"3":{"0":{"2":{"df":1,"docs":{"186":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"7":{"4":{"5":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}},"6":{"4":{"df":1,"docs":{"268":{"tf":1.4142135623730951}}},"6":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}},"8":{"4":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":1,"docs":{"242":{"tf":1.4142135623730951}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"t":{"df":39,"docs":{"108":{"tf":2.0},"109":{"tf":2.0},"11":{"tf":2.0},"117":{"tf":2.449489742783178},"12":{"tf":2.0},"13":{"tf":1.4142135623730951},"134":{"tf":1.0},"17":{"tf":1.0},"170":{"tf":1.0},"179":{"tf":1.0},"186":{"tf":1.7320508075688772},"187":{"tf":1.4142135623730951},"188":{"tf":1.0},"190":{"tf":1.4142135623730951},"192":{"tf":1.7320508075688772},"193":{"tf":1.7320508075688772},"194":{"tf":1.0},"197":{"tf":1.7320508075688772},"198":{"tf":1.4142135623730951},"199":{"tf":1.0},"201":{"tf":2.0},"208":{"tf":1.7320508075688772},"213":{"tf":1.0},"232":{"tf":1.0},"233":{"tf":1.7320508075688772},"242":{"tf":1.0},"245":{"tf":1.0},"264":{"tf":1.4142135623730951},"268":{"tf":1.0},"277":{"tf":1.4142135623730951},"31":{"tf":1.0},"36":{"tf":1.0},"5":{"tf":1.0},"59":{"tf":1.7320508075688772},"60":{"tf":1.7320508075688772},"61":{"tf":1.0},"7":{"tf":1.0},"73":{"tf":1.0},"9":{"tf":1.0}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"d":{"df":0,"docs":{},"l":{"df":1,"docs":{"22":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"x":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":1,"docs":{"13":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}},"u":{"df":0,"docs":{},"p":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"11":{"tf":1.0}}}}},"df":15,"docs":{"10":{"tf":1.0},"11":{"tf":1.0},"12":{"tf":1.0},"122":{"tf":1.0},"13":{"tf":1.4142135623730951},"14":{"tf":1.0},"161":{"tf":1.4142135623730951},"163":{"tf":1.4142135623730951},"166":{"tf":1.4142135623730951},"167":{"tf":1.4142135623730951},"169":{"tf":1.0},"175":{"tf":2.0},"22":{"tf":1.7320508075688772},"224":{"tf":1.0},"73":{"tf":1.0}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":6,"docs":{"116":{"tf":1.0},"132":{"tf":1.0},"154":{"tf":1.0},"271":{"tf":1.0},"58":{"tf":1.0},"60":{"tf":1.0}}}}}},"h":{"a":{"2":{"5":{"6":{"df":1,"docs":{"280":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":5,"docs":{"105":{"tf":1.0},"106":{"tf":1.0},"142":{"tf":1.0},"283":{"tf":1.0},"58":{"tf":1.0}}}},"r":{"df":0,"docs":{},"e":{"df":13,"docs":{"0":{"tf":1.0},"132":{"tf":1.4142135623730951},"163":{"tf":1.0},"172":{"tf":1.0},"200":{"tf":2.23606797749979},"221":{"tf":1.0},"222":{"tf":1.4142135623730951},"225":{"tf":1.7320508075688772},"248":{"tf":1.0},"257":{"tf":1.0},"60":{"tf":1.7320508075688772},"79":{"tf":1.0},"81":{"tf":1.0}}}},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"280":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":2,"docs":{"12":{"tf":1.0},"167":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"p":{"df":13,"docs":{"187":{"tf":1.0},"219":{"tf":1.4142135623730951},"220":{"tf":1.0},"221":{"tf":1.0},"251":{"tf":2.0},"252":{"tf":1.0},"253":{"tf":1.0},"262":{"tf":1.0},"267":{"tf":1.4142135623730951},"268":{"tf":1.4142135623730951},"273":{"tf":1.0},"64":{"tf":1.4142135623730951},"73":{"tf":1.0}},"p":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"267":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"252":{"tf":1.0}}}}},"df":3,"docs":{"179":{"tf":1.0},"241":{"tf":1.0},"81":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"209":{"tf":1.0}}}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"184":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"l":{"d":{"df":0,"docs":{},"n":{"'":{"df":0,"docs":{},"t":{"df":2,"docs":{"241":{"tf":1.0},"258":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"y":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"_":{"c":{"a":{"df":0,"docs":{},"s":{"df":1,"docs":{"108":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"w":{"df":9,"docs":{"12":{"tf":1.0},"123":{"tf":1.0},"167":{"tf":1.0},"254":{"tf":1.0},"255":{"tf":1.0},"256":{"tf":1.0},"257":{"tf":1.0},"258":{"tf":1.0},"282":{"tf":1.0}}}}},"i":{"d":{"df":0,"docs":{},"e":{"b":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"284":{"tf":1.0}}}},"df":0,"docs":{}},"df":4,"docs":{"12":{"tf":1.4142135623730951},"233":{"tf":1.0},"254":{"tf":1.0},"69":{"tf":1.0}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":6,"docs":{"189":{"tf":1.0},"193":{"tf":1.0},"194":{"tf":1.0},"196":{"tf":1.0},"198":{"tf":1.0},"201":{"tf":2.23606797749979}}}}}},"df":3,"docs":{"274":{"tf":2.449489742783178},"283":{"tf":2.449489742783178},"7":{"tf":1.0}},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"c":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"17":{"tf":1.0}}}}}}},"df":6,"docs":{"158":{"tf":1.0},"196":{"tf":1.0},"201":{"tf":1.0},"203":{"tf":1.0},"205":{"tf":1.0},"214":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}}}},"l":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":6,"docs":{"161":{"tf":1.0},"162":{"tf":1.0},"163":{"tf":1.0},"169":{"tf":1.0},"170":{"tf":1.0},"175":{"tf":1.0}}}}},"df":0,"docs":{}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"r":{"df":6,"docs":{"143":{"tf":1.0},"158":{"tf":1.0},"163":{"tf":1.0},"171":{"tf":1.0},"200":{"tf":1.0},"81":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"106":{"tf":1.0}}}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"l":{"df":7,"docs":{"106":{"tf":1.4142135623730951},"118":{"tf":1.0},"128":{"tf":1.0},"60":{"tf":1.0},"78":{"tf":1.0},"81":{"tf":1.0},"87":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":7,"docs":{"106":{"tf":1.0},"115":{"tf":1.0},"124":{"tf":1.0},"152":{"tf":1.0},"226":{"tf":1.0},"231":{"tf":1.0},"252":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"129":{"tf":1.0},"159":{"tf":1.0}}}}},"i":{"c":{"df":1,"docs":{"178":{"tf":1.0}}},"df":5,"docs":{"128":{"tf":1.0},"149":{"tf":1.0},"222":{"tf":1.0},"231":{"tf":1.0},"26":{"tf":1.0}},"f":{"df":0,"docs":{},"i":{"df":4,"docs":{"103":{"tf":1.0},"164":{"tf":1.0},"172":{"tf":1.0},"69":{"tf":1.0}}}}}}},"u":{"df":0,"docs":{},"l":{"df":1,"docs":{"175":{"tf":1.0}},"t":{"a":{"df":0,"docs":{},"n":{"df":2,"docs":{"124":{"tf":1.0},"205":{"tf":1.0}}}},"df":0,"docs":{}}}}},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"l":{"df":19,"docs":{"115":{"tf":1.0},"16":{"tf":1.0},"163":{"tf":1.0},"166":{"tf":1.0},"167":{"tf":1.0},"171":{"tf":2.0},"172":{"tf":1.4142135623730951},"174":{"tf":1.0},"175":{"tf":1.4142135623730951},"22":{"tf":1.0},"220":{"tf":1.4142135623730951},"221":{"tf":1.0},"225":{"tf":1.4142135623730951},"226":{"tf":1.4142135623730951},"228":{"tf":1.4142135623730951},"232":{"tf":1.4142135623730951},"249":{"tf":1.0},"282":{"tf":1.0},"46":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"130":{"tf":1.7320508075688772},"78":{"tf":1.0}}}}}}}}},"t":{"df":1,"docs":{"40":{"tf":1.0}},"e":{"df":2,"docs":{"205":{"tf":1.4142135623730951},"218":{"tf":1.0}}},"u":{"a":{"df":0,"docs":{},"t":{"df":5,"docs":{"115":{"tf":1.0},"128":{"tf":1.0},"154":{"tf":1.0},"220":{"tf":1.0},"249":{"tf":1.0}}}},"df":0,"docs":{}}},"z":{"df":0,"docs":{},"e":{"df":12,"docs":{"165":{"tf":1.0},"170":{"tf":1.0},"205":{"tf":1.7320508075688772},"206":{"tf":1.4142135623730951},"208":{"tf":2.0},"209":{"tf":1.7320508075688772},"212":{"tf":1.0},"218":{"tf":1.0},"220":{"tf":1.0},"222":{"tf":1.0},"280":{"tf":1.0},"6":{"tf":1.0}}}}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"252":{"tf":1.0}}}}}}},"t":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"167":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"l":{"a":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"274":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":1,"docs":{"236":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":2,"docs":{"128":{"tf":1.0},"243":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"w":{"df":3,"docs":{"183":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0}}}}},"m":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":6,"docs":{"104":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"170":{"tf":1.0},"227":{"tf":1.0},"229":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"59":{"tf":1.4142135623730951},"6":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"26":{"tf":1.0}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":5,"docs":{"52":{"tf":2.0},"53":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0}}}}}},"n":{"a":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"_":{"c":{"a":{"df":0,"docs":{},"s":{"df":1,"docs":{"93":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"$":{"df":0,"docs":{},"n":{"df":1,"docs":{"22":{"tf":1.0}}}},"df":2,"docs":{"22":{"tf":1.0},"268":{"tf":1.0}}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"37":{"tf":1.0}}}}}}}},"o":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":1,"docs":{"274":{"tf":1.0}}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"r":{"df":2,"docs":{"274":{"tf":1.0},"64":{"tf":1.0}}}},"df":0,"docs":{}}}},"l":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"200":{"tf":1.0}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"t":{"df":4,"docs":{"152":{"tf":1.4142135623730951},"187":{"tf":1.0},"59":{"tf":1.4142135623730951},"60":{"tf":1.0}}}},"v":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"115":{"tf":1.0}}}},"df":0,"docs":{}},"df":1,"docs":{"194":{"tf":1.0}}}},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"i":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":1,"docs":{"22":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"122":{"tf":1.0}}}},"t":{"df":0,"docs":{},"h":{"df":24,"docs":{"103":{"tf":1.0},"106":{"tf":1.0},"117":{"tf":1.0},"124":{"tf":1.0},"197":{"tf":1.4142135623730951},"200":{"tf":1.0},"22":{"tf":1.0},"224":{"tf":1.4142135623730951},"237":{"tf":1.0},"242":{"tf":1.4142135623730951},"26":{"tf":1.0},"277":{"tf":1.0},"280":{"tf":1.0},"283":{"tf":1.0},"43":{"tf":1.0},"58":{"tf":1.4142135623730951},"6":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.4142135623730951},"70":{"tf":1.0},"72":{"tf":1.0},"73":{"tf":1.7320508075688772},"79":{"tf":1.0},"84":{"tf":1.0}}},"i":{"df":0,"docs":{},"m":{"df":3,"docs":{"124":{"tf":1.0},"158":{"tf":1.0},"43":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"129":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}},"w":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"t":{"df":4,"docs":{"115":{"tf":1.0},"116":{"tf":1.0},"235":{"tf":1.0},"261":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"21":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"44":{"tf":1.0},"58":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"6":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"104":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"r":{"c":{"df":19,"docs":{"1":{"tf":1.0},"109":{"tf":1.0},"11":{"tf":1.0},"163":{"tf":1.0},"164":{"tf":1.0},"165":{"tf":1.0},"172":{"tf":1.7320508075688772},"2":{"tf":1.0},"253":{"tf":1.0},"263":{"tf":1.0},"274":{"tf":1.0},"37":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.4142135623730951},"7":{"tf":1.0},"73":{"tf":2.0},"74":{"tf":1.4142135623730951},"81":{"tf":1.0},"98":{"tf":1.7320508075688772}},"e":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"74":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"p":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":1,"docs":{"43":{"tf":1.0}}}},"w":{"df":0,"docs":{},"n":{"df":2,"docs":{"115":{"tf":1.0},"22":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"221":{"tf":1.0},"236":{"tf":1.0}}}},"df":0,"docs":{},"f":{"df":22,"docs":{"103":{"tf":1.0},"105":{"tf":1.0},"116":{"tf":1.0},"12":{"tf":1.0},"123":{"tf":1.0},"128":{"tf":1.0},"175":{"tf":1.0},"190":{"tf":1.0},"200":{"tf":1.4142135623730951},"214":{"tf":1.0},"22":{"tf":1.4142135623730951},"225":{"tf":1.4142135623730951},"227":{"tf":1.4142135623730951},"229":{"tf":1.0},"235":{"tf":1.4142135623730951},"279":{"tf":1.0},"280":{"tf":1.0},"282":{"tf":1.0},"45":{"tf":1.0},"58":{"tf":1.0},"70":{"tf":1.0},"72":{"tf":1.0}},"i":{"df":10,"docs":{"123":{"tf":1.7320508075688772},"124":{"tf":1.4142135623730951},"22":{"tf":1.4142135623730951},"222":{"tf":1.0},"236":{"tf":1.0},"245":{"tf":1.0},"252":{"tf":1.4142135623730951},"263":{"tf":1.0},"274":{"tf":1.0},"37":{"tf":1.0}}}}}},"d":{"df":1,"docs":{"17":{"tf":1.0}}},"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"104":{"tf":1.0}}},"df":0,"docs":{},"t":{"df":1,"docs":{"201":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"184":{"tf":1.0}}}}}}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":4,"docs":{"163":{"tf":1.0},"198":{"tf":1.0},"225":{"tf":1.0},"40":{"tf":1.4142135623730951}}}}},"r":{"a":{"df":0,"docs":{},"y":{"df":1,"docs":{"227":{"tf":1.0}}}},"df":0,"docs":{}}},"q":{"df":0,"docs":{},"l":{"/":{"df":0,"docs":{},"s":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":1,"docs":{"62":{"tf":1.0}}}}}}}}},"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{":":{":":{"d":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"s":{":":{":":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"_":{"d":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"(":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":1,"docs":{"62":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"(":{"&":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":1,"docs":{"62":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"62":{"tf":1.0}}}}}}}}}},"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":4,"docs":{"11":{"tf":1.7320508075688772},"140":{"tf":1.4142135623730951},"264":{"tf":1.4142135623730951},"67":{"tf":1.0}}}}}}}},"df":3,"docs":{"124":{"tf":1.0},"62":{"tf":2.6457513110645907},"81":{"tf":1.7320508075688772}},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"'":{"df":1,"docs":{"81":{"tf":1.0}}},"_":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"_":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"264":{"tf":1.0}}}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}}}}}}},"df":7,"docs":{"140":{"tf":1.0},"149":{"tf":1.0},"264":{"tf":2.0},"62":{"tf":1.0},"79":{"tf":2.0},"81":{"tf":1.0},"82":{"tf":1.0}}}}}}},"r":{"c":{"/":{"*":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"<":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{">":{".":{"df":0,"docs":{},"u":{"d":{"df":0,"docs":{},"l":{"df":1,"docs":{"104":{"tf":1.4142135623730951}}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"104":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}},"_":{"df":0,"docs":{},"m":{"df":0,"docs":{},"s":{"df":0,"docs":{},"g":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":1,"docs":{"106":{"tf":1.0}}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"_":{"c":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{">":{".":{"df":0,"docs":{},"u":{"d":{"df":0,"docs":{},"l":{"df":1,"docs":{"73":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"60":{"tf":1.7320508075688772}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"b":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"60":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{".":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"<":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{">":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"f":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"106":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"60":{"tf":1.7320508075688772}}}},"df":0,"docs":{}}}}}}},"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":1,"docs":{"224":{"tf":1.0}}}}},"df":5,"docs":{"103":{"tf":2.23606797749979},"60":{"tf":1.0},"61":{"tf":2.449489742783178},"70":{"tf":1.4142135623730951},"72":{"tf":1.0}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"11":{"tf":1.0}}}},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":4,"docs":{"192":{"tf":1.0},"258":{"tf":1.0},"264":{"tf":1.0},"274":{"tf":1.0}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":1,"docs":{"260":{"tf":1.0}}}},"g":{"df":0,"docs":{},"e":{"df":10,"docs":{"131":{"tf":1.0},"248":{"tf":1.0},"249":{"tf":2.6457513110645907},"250":{"tf":1.4142135623730951},"267":{"tf":1.0},"271":{"tf":1.4142135623730951},"273":{"tf":1.4142135623730951},"30":{"tf":1.0},"35":{"tf":1.0},"48":{"tf":1.0}}}},"l":{"df":0,"docs":{},"e":{"df":1,"docs":{"263":{"tf":1.0}}}},"n":{"d":{"a":{"df":0,"docs":{},"r":{"d":{"df":3,"docs":{"107":{"tf":1.0},"141":{"tf":1.0},"8":{"tf":1.0}}},"df":0,"docs":{}}},"df":3,"docs":{"198":{"tf":1.0},"220":{"tf":1.0},"237":{"tf":1.0}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"t":{"df":26,"docs":{"101":{"tf":1.4142135623730951},"105":{"tf":1.0},"106":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"121":{"tf":1.0},"122":{"tf":1.0},"138":{"tf":1.0},"149":{"tf":1.0},"158":{"tf":1.0},"214":{"tf":1.0},"22":{"tf":1.0},"225":{"tf":1.4142135623730951},"227":{"tf":1.4142135623730951},"233":{"tf":1.0},"244":{"tf":1.0},"245":{"tf":1.0},"250":{"tf":1.4142135623730951},"257":{"tf":1.4142135623730951},"268":{"tf":1.0},"269":{"tf":1.0},"37":{"tf":1.0},"58":{"tf":1.4142135623730951},"62":{"tf":1.0},"75":{"tf":1.4142135623730951},"84":{"tf":1.4142135623730951}},"u":{"df":0,"docs":{},"p":{"df":8,"docs":{"134":{"tf":1.0},"140":{"tf":1.0},"176":{"tf":1.4142135623730951},"180":{"tf":1.0},"184":{"tf":1.0},"242":{"tf":1.0},"278":{"tf":1.0},"283":{"tf":1.7320508075688772}}}}}},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"115":{"tf":1.0}}}},"t":{"df":0,"docs":{},"e":{"df":14,"docs":{"189":{"tf":1.0},"205":{"tf":1.0},"225":{"tf":1.0},"226":{"tf":1.4142135623730951},"229":{"tf":4.69041575982343},"233":{"tf":2.8284271247461903},"236":{"tf":1.4142135623730951},"237":{"tf":1.0},"239":{"tf":1.7320508075688772},"242":{"tf":2.23606797749979},"245":{"tf":2.0},"249":{"tf":2.0},"283":{"tf":2.0},"87":{"tf":1.0}},"f":{"df":0,"docs":{},"x":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"n":{"d":{"a":{"df":1,"docs":{"283":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}}}}},"df":0,"docs":{}}},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":10,"docs":{"136":{"tf":1.4142135623730951},"137":{"tf":1.0},"140":{"tf":1.4142135623730951},"149":{"tf":1.4142135623730951},"161":{"tf":1.4142135623730951},"177":{"tf":1.4142135623730951},"187":{"tf":1.4142135623730951},"203":{"tf":1.4142135623730951},"205":{"tf":1.4142135623730951},"62":{"tf":1.0}}}}}}},"i":{"c":{"df":10,"docs":{"167":{"tf":1.0},"242":{"tf":1.0},"251":{"tf":1.0},"278":{"tf":1.0},"28":{"tf":1.0},"280":{"tf":1.0},"282":{"tf":1.0},"284":{"tf":1.0},"33":{"tf":1.0},"93":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"214":{"tf":1.4142135623730951}}}}},"u":{"df":10,"docs":{"139":{"tf":1.0},"148":{"tf":1.0},"160":{"tf":1.0},"171":{"tf":1.0},"176":{"tf":1.0},"186":{"tf":1.0},"202":{"tf":1.0},"22":{"tf":1.0},"242":{"tf":1.0},"257":{"tf":1.4142135623730951}}}}},"d":{":":{":":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{":":{":":{"df":0,"docs":{},"w":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"v":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"(":{"0":{"df":1,"docs":{"117":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":1,"docs":{"62":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"i":{"b":{"df":1,"docs":{"220":{"tf":1.0}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"62":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":28,"docs":{"105":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.4142135623730951},"111":{"tf":1.0},"117":{"tf":1.7320508075688772},"122":{"tf":1.0},"14":{"tf":1.0},"15":{"tf":1.0},"16":{"tf":1.0},"21":{"tf":1.0},"22":{"tf":1.7320508075688772},"25":{"tf":2.6457513110645907},"264":{"tf":1.0},"268":{"tf":1.0},"27":{"tf":1.4142135623730951},"274":{"tf":3.0},"279":{"tf":1.0},"282":{"tf":1.4142135623730951},"29":{"tf":1.4142135623730951},"30":{"tf":1.0},"31":{"tf":1.4142135623730951},"32":{"tf":1.0},"34":{"tf":1.4142135623730951},"35":{"tf":1.0},"36":{"tf":1.4142135623730951},"37":{"tf":1.0},"7":{"tf":1.0},"73":{"tf":1.0}}}},"i":{"c":{"df":0,"docs":{},"k":{"df":3,"docs":{"180":{"tf":1.0},"201":{"tf":1.0},"60":{"tf":1.0}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":18,"docs":{"103":{"tf":1.0},"11":{"tf":1.0},"116":{"tf":1.0},"124":{"tf":1.4142135623730951},"182":{"tf":1.0},"184":{"tf":1.0},"200":{"tf":1.0},"209":{"tf":1.0},"217":{"tf":1.0},"241":{"tf":1.0},"242":{"tf":1.0},"262":{"tf":1.0},"263":{"tf":1.0},"48":{"tf":1.0},"49":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.4142135623730951},"75":{"tf":1.0}}}},"t":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"252":{"tf":1.0}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":2,"docs":{"231":{"tf":1.0},"85":{"tf":1.0}}}}},"r":{"a":{"df":0,"docs":{},"g":{"df":13,"docs":{"123":{"tf":1.0},"124":{"tf":1.4142135623730951},"140":{"tf":1.4142135623730951},"229":{"tf":2.449489742783178},"232":{"tf":1.0},"254":{"tf":1.4142135623730951},"257":{"tf":2.0},"264":{"tf":1.4142135623730951},"273":{"tf":1.0},"284":{"tf":1.0},"78":{"tf":1.4142135623730951},"79":{"tf":1.0},"86":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":18,"docs":{"130":{"tf":1.7320508075688772},"131":{"tf":1.7320508075688772},"184":{"tf":1.0},"187":{"tf":1.0},"229":{"tf":1.0},"231":{"tf":1.0},"232":{"tf":3.605551275463989},"237":{"tf":1.0},"242":{"tf":3.1622776601683795},"245":{"tf":1.7320508075688772},"248":{"tf":2.0},"254":{"tf":1.4142135623730951},"78":{"tf":2.8284271247461903},"81":{"tf":1.0},"83":{"tf":1.0},"84":{"tf":1.0},"86":{"tf":1.0},"87":{"tf":1.0}},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"i":{"df":1,"docs":{"87":{"tf":1.7320508075688772}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"i":{"df":2,"docs":{"139":{"tf":1.0},"176":{"tf":1.0}}}}},"r":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"167":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}}},"n":{"df":0,"docs":{},"g":{"df":2,"docs":{"158":{"tf":1.0},"221":{"tf":1.0}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":9,"docs":{"219":{"tf":1.4142135623730951},"251":{"tf":1.4142135623730951},"254":{"tf":1.0},"255":{"tf":1.0},"256":{"tf":1.0},"257":{"tf":1.4142135623730951},"258":{"tf":1.0},"43":{"tf":1.0},"84":{"tf":1.0}}}}}},"w":{"df":2,"docs":{"236":{"tf":1.4142135623730951},"242":{"tf":1.7320508075688772}}}},"df":1,"docs":{"242":{"tf":1.0}},"e":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"257":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{}},"i":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"172":{"tf":1.0},"43":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":3,"docs":{"200":{"tf":1.0},"201":{"tf":1.0},"242":{"tf":1.0}}}}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"/":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"189":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":5,"docs":{"116":{"tf":1.0},"242":{"tf":2.23606797749979},"25":{"tf":1.0},"279":{"tf":1.0},"97":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"201":{"tf":1.0}}}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"189":{"tf":1.0}}}}}}},"u":{"c":{"df":0,"docs":{},"t":{"df":8,"docs":{"105":{"tf":1.0},"106":{"tf":1.4142135623730951},"108":{"tf":1.0},"109":{"tf":1.0},"242":{"tf":2.6457513110645907},"249":{"tf":1.0},"93":{"tf":1.0},"94":{"tf":1.0}},"s":{"a":{"c":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"283":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"r":{"df":15,"docs":{"103":{"tf":1.0},"104":{"tf":1.0},"106":{"tf":2.23606797749979},"136":{"tf":1.0},"137":{"tf":1.0},"138":{"tf":1.0},"175":{"tf":1.0},"241":{"tf":1.0},"242":{"tf":2.0},"60":{"tf":1.0},"69":{"tf":1.0},"70":{"tf":1.0},"72":{"tf":1.7320508075688772},"76":{"tf":1.7320508075688772},"83":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}}},"u":{"b":{"df":1,"docs":{"200":{"tf":1.0}}},"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"21":{"tf":1.0}}}},"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":7,"docs":{"11":{"tf":1.0},"117":{"tf":1.7320508075688772},"12":{"tf":1.4142135623730951},"126":{"tf":1.4142135623730951},"13":{"tf":1.0},"19":{"tf":1.0},"37":{"tf":1.0}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":3,"docs":{"13":{"tf":1.0},"226":{"tf":1.0},"90":{"tf":1.0}}}}},"y":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":6,"docs":{"103":{"tf":2.449489742783178},"104":{"tf":1.0},"138":{"tf":1.0},"37":{"tf":1.4142135623730951},"93":{"tf":1.0},"99":{"tf":1.0}}}}}},"u":{"b":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":4,"docs":{"252":{"tf":1.7320508075688772},"70":{"tf":1.0},"72":{"tf":1.4142135623730951},"73":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}},"df":3,"docs":{"105":{"tf":1.7320508075688772},"73":{"tf":1.0},"83":{"tf":1.0}},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":1,"docs":{"200":{"tf":1.7320508075688772}}}}},"df":0,"docs":{}}},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"2":{"tf":1.0},"226":{"tf":1.0},"8":{"tf":1.0}}}},"df":0,"docs":{}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"226":{"tf":1.0}}}},"t":{"df":2,"docs":{"226":{"tf":1.0},"7":{"tf":2.449489742783178}}}},"o":{"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":5,"docs":{"11":{"tf":1.0},"167":{"tf":1.7320508075688772},"60":{"tf":1.0},"61":{"tf":1.0},"83":{"tf":1.0}}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":1,"docs":{"117":{"tf":1.0}}}}}}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"22":{"tf":1.0}}}}}},"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"22":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}}}},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"283":{"tf":1.0}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":2,"docs":{"274":{"tf":1.0},"283":{"tf":1.0}}}},"t":{"df":4,"docs":{"165":{"tf":1.0},"167":{"tf":1.0},"228":{"tf":1.0},"54":{"tf":1.0}}}},"t":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"59":{"tf":1.0}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":2,"docs":{"22":{"tf":1.4142135623730951},"52":{"tf":1.0}}}}}}},"y":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":1,"docs":{"11":{"tf":1.0}}}}}}}},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":2,"docs":{"108":{"tf":1.0},"109":{"tf":1.0}}}}}},"c":{"c":{"df":0,"docs":{},"e":{"df":3,"docs":{"104":{"tf":1.0},"208":{"tf":1.0},"212":{"tf":1.7320508075688772}},"e":{"d":{"df":1,"docs":{"209":{"tf":1.0}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"s":{"df":8,"docs":{"12":{"tf":1.0},"204":{"tf":1.0},"208":{"tf":1.0},"214":{"tf":1.0},"216":{"tf":2.23606797749979},"217":{"tf":1.0},"266":{"tf":1.0},"271":{"tf":1.4142135623730951}},"f":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":2,"docs":{"106":{"tf":1.0},"11":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{},"h":{"df":23,"docs":{"102":{"tf":1.0},"109":{"tf":1.0},"112":{"tf":1.0},"115":{"tf":1.0},"16":{"tf":1.0},"188":{"tf":1.0},"190":{"tf":1.0},"196":{"tf":1.4142135623730951},"220":{"tf":1.0},"222":{"tf":1.0},"223":{"tf":1.0},"226":{"tf":1.0},"228":{"tf":1.0},"229":{"tf":1.0},"231":{"tf":1.0},"236":{"tf":1.0},"237":{"tf":1.0},"49":{"tf":1.0},"51":{"tf":1.0},"58":{"tf":1.0},"62":{"tf":1.0},"64":{"tf":1.0},"7":{"tf":1.0}}}},"d":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"213":{"tf":1.0}}}}}}},"df":0,"docs":{},"o":{"df":2,"docs":{"11":{"tf":2.23606797749979},"13":{"tf":1.0}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":2,"docs":{"194":{"tf":1.0},"274":{"tf":1.0}}}},"df":0,"docs":{},"x":{"df":1,"docs":{"22":{"tf":1.0}}}}}},"g":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":7,"docs":{"200":{"tf":1.0},"204":{"tf":1.0},"209":{"tf":1.0},"214":{"tf":2.23606797749979},"217":{"tf":1.7320508075688772},"225":{"tf":1.0},"268":{"tf":1.7320508075688772}}}}}}},"i":{"df":0,"docs":{},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":8,"docs":{"122":{"tf":1.0},"187":{"tf":1.4142135623730951},"189":{"tf":1.0},"191":{"tf":1.0},"192":{"tf":1.0},"197":{"tf":1.0},"223":{"tf":1.0},"64":{"tf":1.0}}}},"df":0,"docs":{}},"df":9,"docs":{"223":{"tf":1.0},"41":{"tf":1.0},"43":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"48":{"tf":1.0},"49":{"tf":1.7320508075688772},"58":{"tf":1.0},"7":{"tf":1.4142135623730951}}}},"m":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"84":{"tf":1.0}},"i":{"df":2,"docs":{"64":{"tf":1.7320508075688772},"7":{"tf":1.0}}},"y":{".":{"df":0,"docs":{},"m":{"d":{"df":1,"docs":{"284":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"d":{"df":1,"docs":{"90":{"tf":1.0}}},"df":0,"docs":{}}}}},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":4,"docs":{"242":{"tf":1.0},"63":{"tf":1.0},"64":{"tf":1.0},"78":{"tf":1.0}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"/":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":1,"docs":{"59":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"q":{"df":0,"docs":{},"l":{"df":1,"docs":{"62":{"tf":1.4142135623730951}}}},"y":{"df":0,"docs":{},"n":{"c":{"1":{"5":{"df":2,"docs":{"244":{"tf":1.0},"246":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"[":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"200":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":34,"docs":{"103":{"tf":1.0},"104":{"tf":1.0},"116":{"tf":1.0},"124":{"tf":1.4142135623730951},"128":{"tf":1.0},"140":{"tf":1.0},"141":{"tf":1.0},"145":{"tf":1.0},"16":{"tf":1.0},"163":{"tf":1.0},"167":{"tf":1.0},"170":{"tf":1.7320508075688772},"171":{"tf":1.0},"172":{"tf":1.0},"175":{"tf":1.4142135623730951},"18":{"tf":1.0},"189":{"tf":1.0},"190":{"tf":1.0},"193":{"tf":1.0},"194":{"tf":1.0},"196":{"tf":1.0},"200":{"tf":2.23606797749979},"212":{"tf":1.0},"22":{"tf":2.0},"223":{"tf":1.0},"231":{"tf":1.0},"242":{"tf":1.4142135623730951},"263":{"tf":1.0},"274":{"tf":1.0},"282":{"tf":1.4142135623730951},"38":{"tf":1.0},"62":{"tf":1.4142135623730951},"79":{"tf":1.0},"81":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"e":{"df":18,"docs":{"102":{"tf":1.0},"104":{"tf":1.0},"106":{"tf":1.0},"11":{"tf":1.7320508075688772},"117":{"tf":1.4142135623730951},"120":{"tf":1.0},"126":{"tf":1.0},"200":{"tf":1.0},"201":{"tf":1.0},"21":{"tf":1.0},"226":{"tf":1.0},"241":{"tf":1.0},"25":{"tf":1.4142135623730951},"26":{"tf":1.0},"270":{"tf":1.0},"36":{"tf":1.0},"7":{"tf":1.0},"73":{"tf":1.4142135623730951}}},"f":{"a":{"c":{"df":3,"docs":{"105":{"tf":1.0},"26":{"tf":1.0},"61":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":2,"docs":{"260":{"tf":1.0},"261":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"282":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"137":{"tf":1.0}}}}},"df":0,"docs":{}}}},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"'":{"df":1,"docs":{"175":{"tf":1.0}}},".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"74":{"tf":1.0}}}}},"/":{"a":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":0,"docs":{},"f":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"d":{"df":1,"docs":{"119":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":55,"docs":{"101":{"tf":1.0},"103":{"tf":2.449489742783178},"104":{"tf":1.0},"109":{"tf":3.605551275463989},"116":{"tf":1.0},"119":{"tf":1.0},"125":{"tf":1.0},"134":{"tf":1.0},"160":{"tf":1.4142135623730951},"162":{"tf":1.0},"163":{"tf":2.0},"164":{"tf":1.0},"165":{"tf":2.23606797749979},"166":{"tf":1.0},"167":{"tf":3.605551275463989},"169":{"tf":1.7320508075688772},"170":{"tf":2.0},"171":{"tf":3.0},"172":{"tf":3.3166247903554},"174":{"tf":2.449489742783178},"175":{"tf":1.4142135623730951},"196":{"tf":1.0},"219":{"tf":1.4142135623730951},"225":{"tf":1.0},"23":{"tf":2.8284271247461903},"24":{"tf":1.0},"241":{"tf":2.449489742783178},"25":{"tf":1.7320508075688772},"251":{"tf":2.6457513110645907},"252":{"tf":1.4142135623730951},"253":{"tf":3.4641016151377544},"26":{"tf":2.23606797749979},"268":{"tf":1.4142135623730951},"271":{"tf":1.7320508075688772},"272":{"tf":1.4142135623730951},"274":{"tf":2.23606797749979},"28":{"tf":1.0},"29":{"tf":2.23606797749979},"30":{"tf":1.4142135623730951},"31":{"tf":3.3166247903554},"32":{"tf":2.8284271247461903},"33":{"tf":1.0},"34":{"tf":2.449489742783178},"35":{"tf":1.4142135623730951},"36":{"tf":3.3166247903554},"46":{"tf":2.449489742783178},"66":{"tf":1.4142135623730951},"68":{"tf":1.0},"7":{"tf":1.4142135623730951},"71":{"tf":1.4142135623730951},"72":{"tf":2.449489742783178},"73":{"tf":2.8284271247461903},"74":{"tf":2.8284271247461903},"95":{"tf":1.4142135623730951},"96":{"tf":1.4142135623730951}},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"7":{"tf":1.0}}}},"df":0,"docs":{}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":1,"docs":{"7":{"tf":1.0}}}}}}}},"t":{"c":{"df":0,"docs":{},"h":{"df":2,"docs":{"208":{"tf":1.0},"26":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}}},"y":{"df":0,"docs":{},"m":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":3,"docs":{"175":{"tf":1.0},"274":{"tf":1.0},"282":{"tf":1.0}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"k":{"df":1,"docs":{"11":{"tf":1.0}}}}}}},"n":{"c":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"242":{"tf":1.0}}}}}}},"df":0,"docs":{}},"1":{"5":{"_":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"d":{"df":0,"docs":{},"s":{"_":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"_":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"94":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":4,"docs":{"124":{"tf":1.0},"244":{"tf":1.0},"245":{"tf":1.0},"249":{"tf":1.4142135623730951}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"242":{"tf":1.0}}}}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"_":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"83":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"_":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"84":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"88":{"tf":1.7320508075688772}}}}}}}},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"83":{"tf":1.0}}}}}}}}},"m":{"a":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"244":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"245":{"tf":1.4142135623730951}}}}}}}}}},"a":{"b":{"df":0,"docs":{},"l":{"df":16,"docs":{"75":{"tf":2.0},"76":{"tf":1.0},"77":{"tf":1.0},"78":{"tf":1.0},"79":{"tf":1.0},"80":{"tf":1.0},"81":{"tf":1.0},"82":{"tf":1.0},"83":{"tf":1.0},"84":{"tf":1.0},"85":{"tf":1.0},"86":{"tf":1.0},"87":{"tf":1.0},"88":{"tf":1.0},"89":{"tf":1.0},"90":{"tf":1.0}},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"130":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}}}}}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"250":{"tf":1.7320508075688772},"62":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":54,"docs":{"0":{"tf":1.0},"1":{"tf":1.4142135623730951},"145":{"tf":1.0},"156":{"tf":1.0},"206":{"tf":1.4142135623730951},"213":{"tf":1.4142135623730951},"217":{"tf":1.0},"218":{"tf":1.4142135623730951},"219":{"tf":2.23606797749979},"225":{"tf":3.7416573867739413},"226":{"tf":3.7416573867739413},"227":{"tf":1.0},"228":{"tf":2.449489742783178},"229":{"tf":2.0},"230":{"tf":1.0},"231":{"tf":2.23606797749979},"232":{"tf":2.23606797749979},"233":{"tf":2.23606797749979},"234":{"tf":2.0},"235":{"tf":1.0},"236":{"tf":1.7320508075688772},"237":{"tf":1.0},"238":{"tf":1.0},"239":{"tf":1.0},"240":{"tf":1.0},"241":{"tf":2.0},"242":{"tf":4.0},"243":{"tf":2.23606797749979},"244":{"tf":2.8284271247461903},"245":{"tf":3.3166247903554},"246":{"tf":2.449489742783178},"247":{"tf":1.0},"248":{"tf":1.0},"249":{"tf":1.7320508075688772},"250":{"tf":2.0},"254":{"tf":3.3166247903554},"255":{"tf":2.23606797749979},"256":{"tf":2.23606797749979},"257":{"tf":2.6457513110645907},"258":{"tf":2.0},"264":{"tf":1.4142135623730951},"284":{"tf":1.0},"48":{"tf":2.6457513110645907},"51":{"tf":1.0},"79":{"tf":1.4142135623730951},"80":{"tf":1.4142135623730951},"82":{"tf":1.7320508075688772},"83":{"tf":1.0},"84":{"tf":3.605551275463989},"85":{"tf":1.4142135623730951},"86":{"tf":2.449489742783178},"87":{"tf":1.0},"88":{"tf":1.7320508075688772},"89":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":4,"docs":{"244":{"tf":2.0},"245":{"tf":2.8284271247461903},"246":{"tf":1.4142135623730951},"247":{"tf":1.0}},"e":{".":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"y":{"_":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":1,"docs":{"246":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"y":{"df":1,"docs":{"97":{"tf":1.0}}}}},"m":{"a":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"g":{"df":3,"docs":{"242":{"tf":1.4142135623730951},"244":{"tf":1.0},"245":{"tf":2.6457513110645907}},"e":{"df":0,"docs":{},"r":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"244":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"p":{"a":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"242":{"tf":1.4142135623730951},"245":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"242":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"242":{"tf":1.0}}}}}}}},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":1,"docs":{"62":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"t":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"104":{"tf":1.0}}}},"df":0,"docs":{},"x":{"df":1,"docs":{"73":{"tf":1.0}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"(":{"df":1,"docs":{"280":{"tf":1.0}}},"df":13,"docs":{"10":{"tf":1.0},"11":{"tf":2.23606797749979},"118":{"tf":1.0},"12":{"tf":1.4142135623730951},"169":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.4142135623730951},"189":{"tf":1.0},"201":{"tf":1.7320508075688772},"37":{"tf":1.4142135623730951},"67":{"tf":1.4142135623730951},"7":{"tf":1.0}}}}}}}},"t":{"a":{"b":{".":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"s":{"a":{"c":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"n":{"df":1,"docs":{"283":{"tf":1.0}}}},"df":0,"docs":{}}}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":8,"docs":{"123":{"tf":1.0},"126":{"tf":1.0},"231":{"tf":1.0},"245":{"tf":1.0},"257":{"tf":1.0},"264":{"tf":1.7320508075688772},"283":{"tf":1.0},"48":{"tf":1.0}},"l":{"df":11,"docs":{"248":{"tf":2.8284271247461903},"249":{"tf":3.1622776601683795},"250":{"tf":1.4142135623730951},"257":{"tf":1.0},"284":{"tf":1.0},"62":{"tf":1.4142135623730951},"80":{"tf":1.4142135623730951},"81":{"tf":1.7320508075688772},"82":{"tf":1.7320508075688772},"84":{"tf":1.4142135623730951},"87":{"tf":1.0}},"e":{"df":0,"docs":{},"—":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"d":{"df":1,"docs":{"82":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"245":{"tf":1.0}}}}}}}},"df":0,"docs":{},"g":{"df":7,"docs":{"167":{"tf":1.0},"253":{"tf":1.4142135623730951},"268":{"tf":1.4142135623730951},"269":{"tf":1.0},"274":{"tf":2.23606797749979},"280":{"tf":1.4142135623730951},"32":{"tf":1.0}},"e":{"df":1,"docs":{"167":{"tf":1.0}}}},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"215":{"tf":1.0}}}},"k":{"df":0,"docs":{},"e":{"df":22,"docs":{"10":{"tf":1.0},"102":{"tf":1.0},"104":{"tf":1.0},"115":{"tf":1.0},"12":{"tf":1.0},"121":{"tf":1.0},"187":{"tf":1.0},"20":{"tf":1.0},"200":{"tf":1.0},"204":{"tf":1.0},"205":{"tf":1.0},"206":{"tf":1.7320508075688772},"212":{"tf":1.0},"226":{"tf":1.0},"242":{"tf":1.0},"249":{"tf":1.0},"250":{"tf":1.4142135623730951},"268":{"tf":1.0},"37":{"tf":1.0},"59":{"tf":1.0},"64":{"tf":1.0},"82":{"tf":1.0}},"n":{"df":5,"docs":{"132":{"tf":1.0},"140":{"tf":1.0},"171":{"tf":1.0},"242":{"tf":1.0},"58":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"e":{"df":1,"docs":{"64":{"tf":1.0}}},"k":{"df":3,"docs":{"194":{"tf":1.0},"233":{"tf":1.0},"274":{"tf":1.0}}}},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"201":{"tf":1.0}}}}}},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"274":{"tf":1.0}}}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":1,"docs":{"282":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":23,"docs":{"103":{"tf":1.4142135623730951},"117":{"tf":1.0},"163":{"tf":1.4142135623730951},"167":{"tf":1.4142135623730951},"17":{"tf":1.0},"172":{"tf":1.0},"174":{"tf":1.0},"208":{"tf":1.4142135623730951},"223":{"tf":1.0},"231":{"tf":1.0},"251":{"tf":1.0},"252":{"tf":1.0},"253":{"tf":1.0},"274":{"tf":1.0},"28":{"tf":1.0},"33":{"tf":1.0},"37":{"tf":1.4142135623730951},"58":{"tf":1.7320508075688772},"59":{"tf":1.0},"60":{"tf":1.0},"69":{"tf":1.0},"73":{"tf":1.7320508075688772},"74":{"tf":1.4142135623730951}}}}}},"s":{"df":0,"docs":{},"k":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":8,"docs":{"119":{"tf":1.0},"266":{"tf":1.0},"271":{"tf":1.0},"272":{"tf":1.0},"273":{"tf":1.4142135623730951},"274":{"tf":3.0},"280":{"tf":1.0},"74":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"r":{"/":{"c":{"df":0,"docs":{},"i":{"/":{"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"h":{"/":{"df":0,"docs":{},"k":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{".":{"df":0,"docs":{},"y":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":1,"docs":{"280":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"/":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"74":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}}},"df":10,"docs":{"119":{"tf":1.4142135623730951},"167":{"tf":1.0},"200":{"tf":1.0},"224":{"tf":1.0},"235":{"tf":1.0},"266":{"tf":2.0},"274":{"tf":1.7320508075688772},"280":{"tf":1.4142135623730951},"39":{"tf":1.0},"40":{"tf":2.449489742783178}},"s":{"[":{"\"":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"$":{"df":0,"docs":{},"{":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"f":{"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"v":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"}":{"$":{"df":0,"docs":{},"{":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":0,"docs":{},"t":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"}":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"\"":{"]":{".":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"(":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"(":{"'":{":":{"df":0,"docs":{},"f":{"df":0,"docs":{},"u":{"df":0,"docs":{},"l":{"df":1,"docs":{"224":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"b":{"d":{"df":2,"docs":{"226":{"tf":1.0},"242":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"c":{"'":{"df":1,"docs":{"274":{"tf":1.0}}},".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"/":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"/":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"/":{"df":0,"docs":{},"v":{"1":{"/":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"k":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{".":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"271":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"119":{"tf":1.0}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}}},"t":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"k":{"df":0,"docs":{},"s":{"/":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"/":{"df":0,"docs":{},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{".":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"266":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}},"df":1,"docs":{"280":{"tf":1.0}},"l":{"df":1,"docs":{"11":{"tf":1.7320508075688772}},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"11":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"m":{"df":14,"docs":{"1":{"tf":1.0},"119":{"tf":1.0},"122":{"tf":1.7320508075688772},"175":{"tf":1.0},"183":{"tf":1.0},"186":{"tf":1.7320508075688772},"187":{"tf":1.0},"203":{"tf":1.0},"225":{"tf":1.0},"268":{"tf":2.0},"274":{"tf":1.0},"276":{"tf":1.0},"6":{"tf":1.0},"8":{"tf":1.0}}}},"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"c":{"df":3,"docs":{"139":{"tf":1.0},"176":{"tf":1.0},"48":{"tf":1.0}}},"df":0,"docs":{}}}}},"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":3,"docs":{"20":{"tf":1.0},"22":{"tf":1.0},"27":{"tf":1.0}}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":10,"docs":{"150":{"tf":1.0},"203":{"tf":1.0},"210":{"tf":1.0},"214":{"tf":1.0},"219":{"tf":1.0},"226":{"tf":1.7320508075688772},"236":{"tf":1.0},"240":{"tf":1.0},"242":{"tf":1.4142135623730951},"259":{"tf":1.7320508075688772}}}}}}}},"l":{"df":6,"docs":{"18":{"tf":1.0},"214":{"tf":1.0},"252":{"tf":1.0},"37":{"tf":1.4142135623730951},"50":{"tf":1.0},"92":{"tf":1.0}}}},"m":{"df":0,"docs":{},"p":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"264":{"tf":1.4142135623730951}}}}}}},"df":2,"docs":{"250":{"tf":1.0},"81":{"tf":1.0}},"l":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"137":{"tf":1.4142135623730951}},"e":{".":{"df":0,"docs":{},"m":{"d":{"df":1,"docs":{"134":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":2,"docs":{"248":{"tf":1.0},"82":{"tf":1.0}}}}},"df":0,"docs":{}}},"t":{"df":1,"docs":{"225":{"tf":1.0}}}}},"n":{"d":{"df":4,"docs":{"117":{"tf":1.0},"60":{"tf":1.0},"82":{"tf":1.4142135623730951},"83":{"tf":1.0}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"m":{"df":8,"docs":{"104":{"tf":1.4142135623730951},"137":{"tf":1.0},"140":{"tf":1.0},"164":{"tf":1.0},"170":{"tf":1.0},"2":{"tf":1.0},"241":{"tf":1.0},"78":{"tf":1.4142135623730951}},"i":{"df":0,"docs":{},"n":{"df":3,"docs":{"11":{"tf":1.0},"12":{"tf":1.0},"204":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"q":{"df":2,"docs":{"176":{"tf":1.0},"202":{"tf":1.0}}}},"df":0,"docs":{}},"t":{",":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"}":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"61":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},".":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"r":{"c":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":1,"docs":{"224":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}},"/":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"/":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"59":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"60":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"c":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"o":{"df":1,"docs":{"60":{"tf":1.0}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"j":{"a":{"df":0,"docs":{},"v":{"a":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"/":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"/":{"<":{"df":0,"docs":{},"y":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"_":{"c":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"70":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"r":{"c":{"/":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"b":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"60":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"0":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"60":{"tf":1.4142135623730951}}}},"df":1,"docs":{"60":{"tf":1.0}}},"1":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"60":{"tf":1.0}}}},"df":1,"docs":{"60":{"tf":1.0}}},"2":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"60":{"tf":1.4142135623730951}}}},"df":1,"docs":{"60":{"tf":1.0}}},"_":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"_":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"b":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"_":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"_":{"c":{"df":0,"docs":{},"r":{"df":1,"docs":{"62":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"73":{"tf":1.0}}}},"df":0,"docs":{}},"df":58,"docs":{"10":{"tf":1.0},"108":{"tf":1.4142135623730951},"109":{"tf":1.0},"11":{"tf":2.0},"118":{"tf":1.4142135623730951},"122":{"tf":1.0},"124":{"tf":1.4142135623730951},"14":{"tf":1.0},"158":{"tf":1.0},"16":{"tf":1.4142135623730951},"17":{"tf":1.0},"18":{"tf":1.0},"190":{"tf":1.0},"194":{"tf":1.0},"200":{"tf":1.7320508075688772},"22":{"tf":1.0},"223":{"tf":1.7320508075688772},"224":{"tf":1.0},"23":{"tf":2.0},"237":{"tf":1.0},"262":{"tf":1.4142135623730951},"267":{"tf":1.0},"270":{"tf":1.0},"273":{"tf":1.0},"274":{"tf":2.6457513110645907},"278":{"tf":1.0},"279":{"tf":1.4142135623730951},"32":{"tf":2.23606797749979},"37":{"tf":1.4142135623730951},"38":{"tf":1.0},"39":{"tf":2.23606797749979},"40":{"tf":2.23606797749979},"41":{"tf":2.449489742783178},"42":{"tf":1.7320508075688772},"43":{"tf":3.0},"44":{"tf":1.4142135623730951},"45":{"tf":3.3166247903554},"46":{"tf":2.6457513110645907},"47":{"tf":1.7320508075688772},"48":{"tf":2.6457513110645907},"49":{"tf":2.449489742783178},"50":{"tf":2.0},"51":{"tf":2.23606797749979},"52":{"tf":2.449489742783178},"53":{"tf":1.4142135623730951},"54":{"tf":2.23606797749979},"55":{"tf":2.0},"56":{"tf":2.0},"57":{"tf":2.449489742783178},"58":{"tf":3.1622776601683795},"59":{"tf":2.6457513110645907},"60":{"tf":4.358898943540674},"61":{"tf":3.0},"62":{"tf":2.449489742783178},"7":{"tf":3.1622776601683795},"70":{"tf":1.7320508075688772},"73":{"tf":2.449489742783178},"74":{"tf":1.0}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"$":{"df":0,"docs":{},"n":{"df":1,"docs":{"20":{"tf":1.0}}}},"/":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"d":{"/":{"df":0,"docs":{},"m":{"df":0,"docs":{},"y":{"c":{"df":0,"docs":{},"r":{"df":1,"docs":{"60":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":1,"docs":{"59":{"tf":1.0}}}},"df":0,"docs":{}}},"y":{"df":0,"docs":{},"n":{"c":{"df":1,"docs":{"48":{"tf":1.0}}},"df":0,"docs":{}}}}},"3":{"df":2,"docs":{"20":{"tf":1.4142135623730951},"22":{"tf":1.0}}},"df":0,"docs":{}}}},"s":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"41":{"tf":1.0}}}},"r":{"df":1,"docs":{"61":{"tf":1.0}}}},"/":{"*":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"61":{"tf":1.7320508075688772}},"m":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":2,"docs":{"60":{"tf":1.4142135623730951},"61":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"r":{"df":1,"docs":{"61":{"tf":1.0}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}}},"h":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"k":{"df":2,"docs":{"220":{"tf":1.0},"274":{"tf":1.0}}}},"t":{"'":{"df":7,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"167":{"tf":1.0},"225":{"tf":1.0},"274":{"tf":1.0},"43":{"tf":1.0},"61":{"tf":1.0}}},"df":0,"docs":{},"’":{"df":1,"docs":{"283":{"tf":1.0}}}}},"d":{"df":1,"docs":{"37":{"tf":1.0}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"v":{"df":2,"docs":{"190":{"tf":1.0},"228":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":2,"docs":{"105":{"tf":1.0},"158":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"e":{"'":{"df":16,"docs":{"19":{"tf":1.0},"194":{"tf":1.0},"201":{"tf":1.0},"226":{"tf":1.4142135623730951},"227":{"tf":1.0},"228":{"tf":1.0},"229":{"tf":1.4142135623730951},"233":{"tf":1.7320508075688772},"236":{"tf":1.0},"242":{"tf":1.7320508075688772},"274":{"tf":1.0},"276":{"tf":1.0},"40":{"tf":1.0},"43":{"tf":1.0},"60":{"tf":1.0},"79":{"tf":1.0}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"201":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"102":{"tf":1.0}}}},"’":{"df":2,"docs":{"189":{"tf":2.8284271247461903},"192":{"tf":1.0}}}}},"y":{"'":{"d":{"df":1,"docs":{"171":{"tf":1.0}}},"df":0,"docs":{},"r":{"df":3,"docs":{"49":{"tf":1.0},"60":{"tf":1.0},"73":{"tf":1.0}}},"v":{"df":1,"docs":{"116":{"tf":1.0}}}},"df":0,"docs":{}}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":21,"docs":{"104":{"tf":1.0},"115":{"tf":1.0},"116":{"tf":1.4142135623730951},"13":{"tf":1.0},"175":{"tf":1.0},"201":{"tf":1.0},"225":{"tf":1.0},"226":{"tf":1.7320508075688772},"228":{"tf":1.4142135623730951},"237":{"tf":1.0},"238":{"tf":1.0},"239":{"tf":1.4142135623730951},"240":{"tf":1.7320508075688772},"252":{"tf":1.0},"40":{"tf":1.0},"62":{"tf":1.0},"68":{"tf":1.0},"69":{"tf":1.0},"78":{"tf":1.0},"79":{"tf":1.0},"86":{"tf":1.0}}},"k":{"df":7,"docs":{"116":{"tf":1.0},"274":{"tf":1.0},"58":{"tf":1.0},"76":{"tf":1.0},"77":{"tf":1.0},"78":{"tf":1.0},"87":{"tf":1.0}}}},"r":{"d":{"df":2,"docs":{"63":{"tf":1.0},"64":{"tf":1.7320508075688772}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":1,"docs":{"229":{"tf":1.0}},"i":{"df":1,"docs":{"104":{"tf":1.0}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":3,"docs":{"236":{"tf":1.0},"270":{"tf":1.0},"46":{"tf":1.0}}}}}}}}},"s":{"df":0,"docs":{},"e":{"df":17,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"11":{"tf":1.0},"118":{"tf":1.4142135623730951},"170":{"tf":1.0},"204":{"tf":1.4142135623730951},"220":{"tf":1.0},"249":{"tf":1.0},"25":{"tf":1.0},"258":{"tf":1.4142135623730951},"274":{"tf":1.0},"281":{"tf":1.0},"39":{"tf":1.0},"48":{"tf":1.0},"58":{"tf":1.0},"7":{"tf":1.0},"82":{"tf":1.0}}}},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":6,"docs":{"105":{"tf":1.0},"201":{"tf":1.0},"228":{"tf":1.4142135623730951},"245":{"tf":1.0},"261":{"tf":1.0},"81":{"tf":1.0}},"t":{"df":2,"docs":{"64":{"tf":1.0},"81":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"e":{"a":{"d":{"df":7,"docs":{"115":{"tf":3.0},"116":{"tf":1.0},"190":{"tf":1.4142135623730951},"194":{"tf":1.0},"197":{"tf":1.0},"203":{"tf":1.0},"242":{"tf":1.0}}},"df":0,"docs":{},"t":{"df":2,"docs":{"194":{"tf":1.0},"201":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":3,"docs":{"20":{"tf":1.0},"254":{"tf":1.0},"68":{"tf":1.0}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":8,"docs":{"167":{"tf":1.0},"249":{"tf":1.0},"268":{"tf":1.0},"27":{"tf":1.0},"283":{"tf":1.0},"3":{"tf":1.0},"73":{"tf":1.0},"74":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"45":{"tf":1.0}}}}}}},"w":{"df":0,"docs":{},"n":{"df":1,"docs":{"283":{"tf":1.0}}}}}},"u":{"df":11,"docs":{"172":{"tf":1.0},"205":{"tf":1.0},"209":{"tf":1.0},"221":{"tf":1.0},"227":{"tf":1.0},"228":{"tf":1.0},"250":{"tf":1.0},"263":{"tf":1.0},"44":{"tf":1.0},"46":{"tf":1.0},"84":{"tf":1.0}},"m":{"b":{"df":1,"docs":{"59":{"tf":1.0}}},"df":0,"docs":{}}}},"i":{"df":4,"docs":{"180":{"tf":1.0},"183":{"tf":1.0},"238":{"tf":1.0},"274":{"tf":1.0}},"e":{"df":1,"docs":{"240":{"tf":1.4142135623730951}}},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"206":{"tf":1.0}}}}}}},"m":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"p":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"d":{"_":{"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"145":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}},"df":27,"docs":{"10":{"tf":1.7320508075688772},"104":{"tf":1.0},"141":{"tf":1.0},"142":{"tf":1.4142135623730951},"152":{"tf":1.0},"154":{"tf":1.4142135623730951},"155":{"tf":1.4142135623730951},"156":{"tf":1.0},"170":{"tf":1.0},"172":{"tf":1.0},"204":{"tf":1.4142135623730951},"209":{"tf":1.0},"212":{"tf":1.0},"233":{"tf":1.0},"241":{"tf":1.0},"242":{"tf":1.0},"25":{"tf":1.0},"250":{"tf":1.0},"263":{"tf":1.0},"57":{"tf":1.4142135623730951},"58":{"tf":1.0},"60":{"tf":1.0},"62":{"tf":1.0},"64":{"tf":1.0},"7":{"tf":1.0},"81":{"tf":1.7320508075688772},"84":{"tf":1.4142135623730951}},"r":{"df":1,"docs":{"226":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":6,"docs":{"22":{"tf":1.0},"242":{"tf":1.0},"248":{"tf":1.0},"249":{"tf":1.7320508075688772},"80":{"tf":1.0},"88":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"p":{"df":5,"docs":{"105":{"tf":1.0},"282":{"tf":1.4142135623730951},"43":{"tf":1.0},"7":{"tf":1.0},"75":{"tf":1.0}}},"t":{"df":0,"docs":{},"l":{"df":3,"docs":{"280":{"tf":1.0},"39":{"tf":1.0},"62":{"tf":1.0}}}}},"l":{";":{"d":{"df":0,"docs":{},"r":{"df":2,"docs":{"228":{"tf":1.0},"92":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}},"o":{"/":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":1,"docs":{"254":{"tf":1.0}}}}}}},"_":{"df":0,"docs":{},"j":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"283":{"tf":1.0}}}}}}},"d":{"df":0,"docs":{},"o":{"!":{"(":{")":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"200":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":6,"docs":{"127":{"tf":1.0},"200":{"tf":1.0},"241":{"tf":1.0},"274":{"tf":2.8284271247461903},"46":{"tf":1.0},"48":{"tf":1.7320508075688772}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":10,"docs":{"172":{"tf":1.0},"220":{"tf":1.0},"221":{"tf":1.0},"238":{"tf":1.0},"240":{"tf":1.4142135623730951},"252":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0},"46":{"tf":1.0},"58":{"tf":1.0}}}}}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":6,"docs":{"226":{"tf":1.0},"245":{"tf":1.4142135623730951},"254":{"tf":1.0},"274":{"tf":1.4142135623730951},"277":{"tf":2.449489742783178},"283":{"tf":1.4142135623730951}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"_":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"l":{"df":1,"docs":{"242":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}}}}}},"m":{"b":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"268":{"tf":1.0},"62":{"tf":1.0}}}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"k":{"df":1,"docs":{"204":{"tf":1.0}}},"l":{"b":{"a":{"df":0,"docs":{},"r":{"_":{"_":{"_":{"_":{"_":{"df":1,"docs":{"62":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":1,"docs":{"62":{"tf":1.0}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"263":{"tf":1.7320508075688772}}}}},"df":0,"docs":{}}},"df":18,"docs":{"101":{"tf":1.0},"106":{"tf":1.0},"107":{"tf":1.0},"11":{"tf":2.0},"111":{"tf":1.0},"12":{"tf":1.0},"13":{"tf":1.4142135623730951},"165":{"tf":1.0},"187":{"tf":1.4142135623730951},"264":{"tf":1.0},"274":{"tf":1.4142135623730951},"275":{"tf":1.0},"276":{"tf":1.0},"277":{"tf":1.0},"37":{"tf":1.7320508075688772},"62":{"tf":1.4142135623730951},"64":{"tf":1.0},"90":{"tf":1.0}},"s":{"/":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{".":{"d":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"286":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":1,"docs":{"11":{"tf":1.7320508075688772}}}}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"f":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":1,"docs":{"106":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"u":{"df":0,"docs":{},"p":{"d":{"df":1,"docs":{"122":{"tf":1.0}}},"df":0,"docs":{}}}},";":{"2":{"6":{".":{"0":{".":{"2":{"df":1,"docs":{"13":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"p":{"df":13,"docs":{"105":{"tf":1.0},"123":{"tf":1.4142135623730951},"124":{"tf":1.4142135623730951},"126":{"tf":1.4142135623730951},"200":{"tf":1.4142135623730951},"252":{"tf":1.0},"282":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"40":{"tf":1.0},"73":{"tf":1.0},"84":{"tf":1.0},"98":{"tf":1.4142135623730951}},"i":{"c":{"df":1,"docs":{"90":{"tf":1.0}}},"df":0,"docs":{}}},"u":{"c":{"df":0,"docs":{},"h":{"df":2,"docs":{"201":{"tf":1.0},"3":{"tf":1.0}}}},"df":0,"docs":{}}},"r":{"a":{"c":{"df":0,"docs":{},"k":{"df":10,"docs":{"106":{"tf":1.0},"167":{"tf":1.0},"231":{"tf":1.0},"245":{"tf":1.0},"261":{"tf":1.0},"283":{"tf":1.0},"80":{"tf":1.0},"82":{"tf":1.0},"84":{"tf":1.0},"87":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"1":{"tf":1.0}}}}},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":3,"docs":{"169":{"tf":1.0},"172":{"tf":1.0},"197":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":2,"docs":{"116":{"tf":1.4142135623730951},"251":{"tf":1.0}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":6,"docs":{"124":{"tf":1.4142135623730951},"244":{"tf":1.4142135623730951},"246":{"tf":1.0},"43":{"tf":1.0},"84":{"tf":1.0},"93":{"tf":1.0}}}},"n":{"df":0,"docs":{},"s":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"79":{"tf":2.449489742783178}}}},"df":0,"docs":{}},"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"149":{"tf":1.0},"150":{"tf":1.0}}}}}},"p":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"245":{"tf":1.0}}}},"df":0,"docs":{}}}},"v":{"df":0,"docs":{},"i":{"df":1,"docs":{"176":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":5,"docs":{"122":{"tf":1.0},"124":{"tf":1.0},"147":{"tf":1.0},"64":{"tf":1.0},"81":{"tf":1.0}}}},"i":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"121":{"tf":1.0}}},"l":{"df":1,"docs":{"104":{"tf":1.0}}}},"c":{"df":0,"docs":{},"k":{"df":3,"docs":{"126":{"tf":1.0},"17":{"tf":1.0},"75":{"tf":1.0}},"i":{"df":4,"docs":{"11":{"tf":1.0},"128":{"tf":1.0},"22":{"tf":1.0},"62":{"tf":1.0}}}}},"df":28,"docs":{"104":{"tf":1.7320508075688772},"106":{"tf":1.0},"107":{"tf":1.0},"108":{"tf":1.4142135623730951},"109":{"tf":1.4142135623730951},"111":{"tf":1.0},"122":{"tf":1.4142135623730951},"126":{"tf":1.0},"158":{"tf":1.0},"170":{"tf":1.0},"197":{"tf":1.0},"20":{"tf":1.0},"22":{"tf":1.4142135623730951},"227":{"tf":1.0},"230":{"tf":1.0},"242":{"tf":1.0},"263":{"tf":1.0},"264":{"tf":1.0},"278":{"tf":1.0},"279":{"tf":1.0},"37":{"tf":1.4142135623730951},"4":{"tf":1.0},"54":{"tf":1.0},"60":{"tf":1.0},"62":{"tf":1.0},"64":{"tf":1.0},"69":{"tf":1.0},"79":{"tf":1.0}},"g":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":13,"docs":{"116":{"tf":1.0},"119":{"tf":1.4142135623730951},"154":{"tf":1.0},"267":{"tf":1.0},"270":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0},"60":{"tf":1.0},"62":{"tf":1.0},"73":{"tf":1.0},"81":{"tf":1.0},"82":{"tf":3.1622776601683795},"84":{"tf":1.4142135623730951}}}}}},"v":{"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"l":{"df":4,"docs":{"167":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"64":{"tf":1.0}}}},"df":0,"docs":{}}}},"u":{"df":0,"docs":{},"e":{"df":6,"docs":{"126":{"tf":1.0},"133":{"tf":1.0},"258":{"tf":1.0},"264":{"tf":1.0},"46":{"tf":1.0},"61":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"189":{"tf":1.0},"274":{"tf":1.0}}}},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"263":{"tf":1.0}}}}}},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"62":{"tf":1.0}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":2,"docs":{"252":{"tf":1.0},"60":{"tf":1.0}}}}},"w":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"k":{"df":1,"docs":{"37":{"tf":1.0}}}},"df":0,"docs":{}},"o":{"df":8,"docs":{"165":{"tf":1.0},"175":{"tf":1.0},"196":{"tf":1.0},"197":{"tf":1.0},"201":{"tf":1.0},"212":{"tf":1.0},"251":{"tf":1.0},"273":{"tf":1.0}}}},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":1,"docs":{"116":{"tf":1.0}}}}},"df":13,"docs":{"105":{"tf":1.0},"106":{"tf":1.7320508075688772},"151":{"tf":1.4142135623730951},"152":{"tf":1.4142135623730951},"155":{"tf":1.4142135623730951},"156":{"tf":1.4142135623730951},"189":{"tf":1.4142135623730951},"268":{"tf":1.0},"283":{"tf":2.0},"43":{"tf":1.0},"62":{"tf":1.0},"93":{"tf":1.0},"96":{"tf":1.0}},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":1,"docs":{"43":{"tf":1.0}}}}}}}}},"i":{"c":{"df":11,"docs":{"10":{"tf":1.0},"103":{"tf":1.0},"224":{"tf":1.4142135623730951},"250":{"tf":1.0},"283":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0},"45":{"tf":1.0},"79":{"tf":1.0},"87":{"tf":1.0},"93":{"tf":1.0}}},"df":0,"docs":{}}}}},"u":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":1,"docs":{"13":{"tf":1.0}}}}}}},"d":{"df":0,"docs":{},"l":{"df":9,"docs":{"104":{"tf":2.0},"105":{"tf":2.449489742783178},"106":{"tf":1.4142135623730951},"107":{"tf":1.0},"109":{"tf":1.0},"123":{"tf":1.0},"200":{"tf":1.0},"70":{"tf":1.0},"73":{"tf":1.0}}}},"df":0,"docs":{},"i":{"df":4,"docs":{"180":{"tf":1.0},"226":{"tf":1.4142135623730951},"268":{"tf":1.0},"7":{"tf":1.0}}},"l":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"m":{"df":4,"docs":{"115":{"tf":1.0},"225":{"tf":1.0},"261":{"tf":1.0},"43":{"tf":1.0}}}}}},"n":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"233":{"tf":1.0}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"200":{"tf":1.0}}}}}}}},"df":0,"docs":{}}}},"h":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"184":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"73":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"m":{"df":3,"docs":{"117":{"tf":1.0},"29":{"tf":1.0},"34":{"tf":1.0}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"124":{"tf":1.4142135623730951}}}}}},"v":{"df":1,"docs":{"104":{"tf":1.0}}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"282":{"tf":1.0}}}}},"r":{"df":17,"docs":{"105":{"tf":1.0},"108":{"tf":1.7320508075688772},"109":{"tf":1.7320508075688772},"170":{"tf":1.0},"175":{"tf":1.0},"184":{"tf":1.0},"214":{"tf":1.0},"216":{"tf":1.4142135623730951},"217":{"tf":1.0},"22":{"tf":1.0},"4":{"tf":1.0},"46":{"tf":1.0},"60":{"tf":1.0},"64":{"tf":1.0},"69":{"tf":1.0},"7":{"tf":1.0},"73":{"tf":1.0}},"l":{"df":0,"docs":{},"i":{"df":3,"docs":{"104":{"tf":1.0},"128":{"tf":1.0},"253":{"tf":1.0}}}},"n":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"86":{"tf":1.0}}}}},"df":0,"docs":{}}},"s":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"99":{"tf":1.0}}}}},"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"n":{"d":{"df":10,"docs":{"102":{"tf":1.4142135623730951},"104":{"tf":1.0},"123":{"tf":1.0},"138":{"tf":1.0},"174":{"tf":1.0},"197":{"tf":1.0},"21":{"tf":1.0},"225":{"tf":1.0},"236":{"tf":1.0},"264":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"o":{"d":{"df":1,"docs":{"253":{"tf":1.0}}},"df":0,"docs":{}}}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"df":1,"docs":{"180":{"tf":1.0}}}}}},"u":{"df":1,"docs":{"226":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"37":{"tf":1.0},"64":{"tf":1.0}},"e":{"d":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"58":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"f":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"82":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"62":{"tf":1.0}},"e":{"d":{"_":{"_":{"_":{"_":{"_":{"df":1,"docs":{"62":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":5,"docs":{"104":{"tf":1.0},"117":{"tf":1.0},"210":{"tf":1.0},"58":{"tf":1.7320508075688772},"62":{"tf":1.0}}}}}}}},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"'":{"df":1,"docs":{"104":{"tf":1.0}}},".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":5,"docs":{"103":{"tf":1.0},"108":{"tf":1.0},"70":{"tf":1.0},"72":{"tf":1.0},"73":{"tf":1.0}}}}}}},"_":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"104":{"tf":1.0}}}}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"d":{"df":1,"docs":{"69":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":32,"docs":{"101":{"tf":2.23606797749979},"102":{"tf":2.23606797749979},"103":{"tf":2.0},"104":{"tf":3.1622776601683795},"105":{"tf":2.6457513110645907},"106":{"tf":2.0},"107":{"tf":1.4142135623730951},"108":{"tf":2.23606797749979},"109":{"tf":2.449489742783178},"111":{"tf":1.7320508075688772},"123":{"tf":1.4142135623730951},"128":{"tf":2.23606797749979},"129":{"tf":1.0},"130":{"tf":1.4142135623730951},"131":{"tf":1.7320508075688772},"132":{"tf":1.0},"133":{"tf":1.0},"194":{"tf":1.0},"196":{"tf":1.0},"197":{"tf":1.0},"198":{"tf":1.0},"200":{"tf":2.0},"253":{"tf":1.0},"271":{"tf":1.0},"44":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"69":{"tf":1.4142135623730951},"72":{"tf":1.0},"73":{"tf":1.0},"74":{"tf":1.0},"90":{"tf":1.7320508075688772}}}},"i":{"df":2,"docs":{"237":{"tf":1.0},"257":{"tf":1.0}}}},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"60":{"tf":1.0}}}}}}},"q":{"df":0,"docs":{},"u":{"df":1,"docs":{"22":{"tf":1.0}}}},"t":{"df":9,"docs":{"17":{"tf":1.0},"223":{"tf":1.4142135623730951},"224":{"tf":1.0},"232":{"tf":1.7320508075688772},"278":{"tf":1.0},"39":{"tf":1.4142135623730951},"42":{"tf":1.4142135623730951},"43":{"tf":1.0},"60":{"tf":1.0}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":3,"docs":{"223":{"tf":1.0},"224":{"tf":1.4142135623730951},"46":{"tf":1.0}}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":1,"docs":{"61":{"tf":1.0}}}}}}},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":7,"docs":{"204":{"tf":1.0},"21":{"tf":1.0},"26":{"tf":1.0},"60":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0},"67":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"k":{"df":6,"docs":{"146":{"tf":1.0},"200":{"tf":1.0},"231":{"tf":1.0},"258":{"tf":1.0},"60":{"tf":1.0},"64":{"tf":1.0}}}},"o":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"142":{"tf":1.4142135623730951}}}},"df":0,"docs":{}}},"q":{"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":1,"docs":{"58":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"224":{"tf":1.0}}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":1,"docs":{"124":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"i":{"df":1,"docs":{"233":{"tf":1.0}}}},"s":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"v":{"df":1,"docs":{"92":{"tf":1.0}}}}}}}},"s":{"a":{"df":0,"docs":{},"f":{"df":2,"docs":{"117":{"tf":1.0},"64":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"117":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":11,"docs":{"119":{"tf":1.0},"12":{"tf":1.0},"167":{"tf":1.0},"179":{"tf":1.0},"180":{"tf":1.0},"184":{"tf":1.0},"197":{"tf":1.0},"212":{"tf":1.0},"226":{"tf":1.0},"40":{"tf":1.0},"7":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"s":{"df":1,"docs":{"149":{"tf":1.0}},"u":{"df":2,"docs":{"252":{"tf":1.0},"64":{"tf":1.0}}}}},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"116":{"tf":1.0}}}}},"z":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":3,"docs":{"11":{"tf":1.4142135623730951},"29":{"tf":1.7320508075688772},"34":{"tf":1.7320508075688772}}}}}},"p":{"d":{"a":{"df":0,"docs":{},"t":{"df":35,"docs":{"104":{"tf":1.0},"108":{"tf":1.0},"11":{"tf":1.4142135623730951},"119":{"tf":1.0},"120":{"tf":1.0},"121":{"tf":1.0},"122":{"tf":2.0},"123":{"tf":1.0},"124":{"tf":1.0},"134":{"tf":1.0},"139":{"tf":1.4142135623730951},"145":{"tf":2.0},"146":{"tf":1.0},"193":{"tf":1.0},"197":{"tf":1.0},"198":{"tf":1.0},"200":{"tf":1.4142135623730951},"233":{"tf":1.0},"246":{"tf":1.0},"249":{"tf":1.7320508075688772},"25":{"tf":1.7320508075688772},"250":{"tf":1.4142135623730951},"261":{"tf":1.0},"268":{"tf":2.8284271247461903},"269":{"tf":1.0},"278":{"tf":1.0},"279":{"tf":2.449489742783178},"280":{"tf":2.23606797749979},"282":{"tf":1.0},"64":{"tf":1.0},"69":{"tf":1.0},"73":{"tf":1.0},"75":{"tf":1.0},"82":{"tf":1.7320508075688772},"84":{"tf":1.0}}}},"df":0,"docs":{}},"df":40,"docs":{"103":{"tf":1.0},"108":{"tf":1.7320508075688772},"109":{"tf":1.7320508075688772},"117":{"tf":1.0},"121":{"tf":1.0},"122":{"tf":1.0},"124":{"tf":1.4142135623730951},"13":{"tf":1.0},"158":{"tf":1.0},"166":{"tf":1.0},"17":{"tf":1.0},"170":{"tf":1.0},"197":{"tf":1.0},"199":{"tf":1.0},"21":{"tf":1.0},"214":{"tf":1.4142135623730951},"22":{"tf":1.0},"225":{"tf":1.4142135623730951},"233":{"tf":1.0},"240":{"tf":1.0},"242":{"tf":1.0},"255":{"tf":1.0},"264":{"tf":1.0},"272":{"tf":1.0},"278":{"tf":1.0},"30":{"tf":1.0},"35":{"tf":1.0},"40":{"tf":1.4142135623730951},"5":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"6":{"tf":1.0},"60":{"tf":1.4142135623730951},"61":{"tf":1.0},"62":{"tf":1.0},"70":{"tf":1.0},"72":{"tf":1.0},"82":{"tf":1.0},"83":{"tf":1.0},"9":{"tf":1.0}},"g":{"df":0,"docs":{},"r":{"a":{"d":{"df":9,"docs":{"261":{"tf":1.0},"278":{"tf":1.7320508075688772},"279":{"tf":1.4142135623730951},"280":{"tf":1.0},"281":{"tf":1.0},"282":{"tf":1.7320508075688772},"78":{"tf":1.0},"79":{"tf":1.0},"81":{"tf":2.23606797749979}}},"df":0,"docs":{}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":1,"docs":{"270":{"tf":2.0}}}}},"o":{"a":{"d":{"df":4,"docs":{"249":{"tf":1.0},"250":{"tf":1.4142135623730951},"274":{"tf":1.7320508075688772},"88":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"c":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"c":{"a":{"df":0,"docs":{},"s":{"df":4,"docs":{"93":{"tf":1.0},"96":{"tf":1.0},"98":{"tf":1.0},"99":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"m":{"df":3,"docs":{"16":{"tf":1.0},"197":{"tf":1.0},"49":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"6":{"tf":1.0}}}}}},"l":{"df":9,"docs":{"205":{"tf":1.0},"242":{"tf":1.0},"253":{"tf":1.0},"268":{"tf":1.4142135623730951},"280":{"tf":1.0},"29":{"tf":1.4142135623730951},"34":{"tf":1.0},"62":{"tf":1.0},"82":{"tf":1.0}}}},"s":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"159":{"tf":1.0}}}},"df":0,"docs":{},"g":{"df":4,"docs":{"115":{"tf":1.0},"138":{"tf":1.0},"213":{"tf":1.0},"264":{"tf":2.23606797749979}}}},"b":{"df":1,"docs":{"126":{"tf":1.0}}},"df":145,"docs":{"0":{"tf":1.4142135623730951},"101":{"tf":1.7320508075688772},"104":{"tf":1.7320508075688772},"105":{"tf":1.7320508075688772},"106":{"tf":2.23606797749979},"107":{"tf":1.0},"108":{"tf":1.7320508075688772},"109":{"tf":1.0},"11":{"tf":2.23606797749979},"110":{"tf":1.0},"111":{"tf":1.7320508075688772},"112":{"tf":1.0},"113":{"tf":1.0},"114":{"tf":1.0},"115":{"tf":2.449489742783178},"116":{"tf":3.1622776601683795},"117":{"tf":1.7320508075688772},"118":{"tf":1.4142135623730951},"119":{"tf":1.7320508075688772},"12":{"tf":1.4142135623730951},"120":{"tf":1.0},"124":{"tf":1.4142135623730951},"125":{"tf":1.0},"126":{"tf":1.7320508075688772},"128":{"tf":1.7320508075688772},"129":{"tf":1.4142135623730951},"13":{"tf":1.0},"132":{"tf":1.4142135623730951},"134":{"tf":1.4142135623730951},"135":{"tf":1.4142135623730951},"140":{"tf":1.4142135623730951},"145":{"tf":1.4142135623730951},"146":{"tf":1.0},"157":{"tf":1.0},"16":{"tf":2.0},"162":{"tf":1.0},"163":{"tf":1.0},"165":{"tf":1.0},"166":{"tf":1.0},"167":{"tf":1.4142135623730951},"169":{"tf":1.0},"17":{"tf":1.7320508075688772},"170":{"tf":2.23606797749979},"171":{"tf":1.0},"174":{"tf":1.7320508075688772},"175":{"tf":2.23606797749979},"18":{"tf":1.7320508075688772},"187":{"tf":1.0},"189":{"tf":1.0},"19":{"tf":2.0},"190":{"tf":1.7320508075688772},"192":{"tf":1.4142135623730951},"193":{"tf":1.4142135623730951},"194":{"tf":1.7320508075688772},"196":{"tf":1.0},"197":{"tf":3.1622776601683795},"198":{"tf":2.6457513110645907},"20":{"tf":2.0},"200":{"tf":2.23606797749979},"201":{"tf":1.4142135623730951},"205":{"tf":1.0},"21":{"tf":1.4142135623730951},"213":{"tf":1.7320508075688772},"219":{"tf":1.4142135623730951},"22":{"tf":2.449489742783178},"220":{"tf":1.0},"221":{"tf":1.0},"222":{"tf":1.4142135623730951},"228":{"tf":1.7320508075688772},"229":{"tf":1.0},"23":{"tf":1.4142135623730951},"231":{"tf":1.7320508075688772},"232":{"tf":1.7320508075688772},"233":{"tf":1.0},"237":{"tf":1.7320508075688772},"24":{"tf":1.0},"242":{"tf":1.4142135623730951},"245":{"tf":2.23606797749979},"249":{"tf":1.0},"25":{"tf":2.0},"250":{"tf":1.0},"252":{"tf":1.4142135623730951},"253":{"tf":1.0},"254":{"tf":1.7320508075688772},"256":{"tf":1.0},"257":{"tf":2.0},"258":{"tf":1.7320508075688772},"259":{"tf":1.0},"26":{"tf":2.23606797749979},"260":{"tf":1.4142135623730951},"261":{"tf":1.4142135623730951},"262":{"tf":1.4142135623730951},"264":{"tf":2.0},"266":{"tf":1.0},"268":{"tf":1.0},"27":{"tf":2.0},"271":{"tf":1.4142135623730951},"273":{"tf":1.0},"274":{"tf":3.0},"278":{"tf":1.0},"28":{"tf":1.0},"280":{"tf":1.4142135623730951},"283":{"tf":2.0},"284":{"tf":1.0},"29":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.0},"32":{"tf":1.4142135623730951},"33":{"tf":1.0},"34":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":2.8284271247461903},"38":{"tf":1.0},"41":{"tf":1.0},"43":{"tf":2.0},"44":{"tf":1.0},"45":{"tf":1.4142135623730951},"46":{"tf":1.4142135623730951},"49":{"tf":1.0},"50":{"tf":1.0},"52":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"58":{"tf":2.0},"6":{"tf":1.0},"60":{"tf":1.7320508075688772},"61":{"tf":2.23606797749979},"62":{"tf":1.7320508075688772},"63":{"tf":1.0},"64":{"tf":2.8284271247461903},"69":{"tf":2.449489742783178},"7":{"tf":1.0},"73":{"tf":2.23606797749979},"78":{"tf":1.7320508075688772},"79":{"tf":1.7320508075688772},"80":{"tf":1.0},"82":{"tf":1.4142135623730951},"84":{"tf":1.7320508075688772},"86":{"tf":1.0},"87":{"tf":1.0},"90":{"tf":1.0},"96":{"tf":1.0},"99":{"tf":2.23606797749979}},"e":{"df":0,"docs":{},"r":{"'":{"df":5,"docs":{"159":{"tf":1.0},"177":{"tf":1.4142135623730951},"183":{"tf":1.0},"184":{"tf":1.0},"233":{"tf":1.0}}},"_":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":2,"docs":{"264":{"tf":1.0},"81":{"tf":1.0}}}}}}},"df":40,"docs":{"149":{"tf":1.0},"150":{"tf":1.0},"154":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0},"157":{"tf":1.0},"158":{"tf":1.7320508075688772},"159":{"tf":1.0},"179":{"tf":1.0},"180":{"tf":1.0},"182":{"tf":1.0},"183":{"tf":1.0},"184":{"tf":1.4142135623730951},"203":{"tf":1.0},"204":{"tf":2.0},"205":{"tf":3.0},"206":{"tf":1.7320508075688772},"207":{"tf":1.0},"208":{"tf":1.4142135623730951},"209":{"tf":2.0},"210":{"tf":2.0},"212":{"tf":2.0},"213":{"tf":2.449489742783178},"214":{"tf":2.0},"215":{"tf":2.23606797749979},"216":{"tf":2.23606797749979},"217":{"tf":1.4142135623730951},"218":{"tf":1.4142135623730951},"225":{"tf":1.0},"226":{"tf":1.4142135623730951},"232":{"tf":1.4142135623730951},"233":{"tf":2.6457513110645907},"242":{"tf":2.0},"245":{"tf":1.0},"254":{"tf":1.0},"255":{"tf":1.0},"283":{"tf":2.23606797749979},"52":{"tf":1.4142135623730951},"64":{"tf":1.0},"82":{"tf":1.0}},"n":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"13":{"tf":1.4142135623730951}}}},"df":0,"docs":{}},"’":{"df":7,"docs":{"203":{"tf":1.0},"205":{"tf":1.0},"206":{"tf":1.4142135623730951},"208":{"tf":1.4142135623730951},"209":{"tf":1.0},"213":{"tf":1.0},"283":{"tf":1.7320508075688772}}}}},"u":{"a":{"df":0,"docs":{},"l":{"df":5,"docs":{"117":{"tf":1.0},"18":{"tf":1.0},"21":{"tf":1.0},"279":{"tf":1.0},"280":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":5,"docs":{"126":{"tf":1.0},"237":{"tf":2.23606797749979},"60":{"tf":1.7320508075688772},"62":{"tf":1.4142135623730951},"79":{"tf":1.0}},"s":{".":{"df":0,"docs":{},"r":{"df":2,"docs":{"237":{"tf":1.0},"60":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"v":{"1":{"1":{"7":{".":{"0":{"df":1,"docs":{"268":{"tf":1.4142135623730951}}},"df":0,"docs":{}},"df":0,"docs":{}},"8":{".":{"0":{"df":1,"docs":{"268":{"tf":2.0}}},"df":0,"docs":{}},"df":1,"docs":{"268":{"tf":1.0}}},"9":{".":{"0":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}},"2":{".":{"0":{"df":1,"docs":{"2":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"[":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"]":{".":{"0":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"s":{"df":1,"docs":{"268":{"tf":1.0}},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"]":{".":{"0":{"df":1,"docs":{"268":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"20":{"tf":1.0}},"i":{"d":{"df":13,"docs":{"104":{"tf":1.0},"140":{"tf":1.4142135623730951},"141":{"tf":1.0},"145":{"tf":2.0},"146":{"tf":1.4142135623730951},"147":{"tf":1.0},"150":{"tf":1.0},"154":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0},"189":{"tf":1.4142135623730951},"190":{"tf":1.0},"64":{"tf":1.0}}},"df":0,"docs":{}},"u":{"df":6,"docs":{"18":{"tf":1.0},"264":{"tf":1.7320508075688772},"274":{"tf":1.0},"280":{"tf":1.0},"62":{"tf":1.0},"84":{"tf":1.4142135623730951}}}},"r":{"df":2,"docs":{"12":{"tf":1.0},"97":{"tf":1.0}},"i":{"a":{"b":{"df":0,"docs":{},"l":{"df":8,"docs":{"11":{"tf":1.4142135623730951},"115":{"tf":1.0},"12":{"tf":1.0},"14":{"tf":1.0},"277":{"tf":1.4142135623730951},"37":{"tf":1.4142135623730951},"93":{"tf":1.4142135623730951},"99":{"tf":1.0}}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":3,"docs":{"104":{"tf":1.0},"108":{"tf":1.0},"93":{"tf":1.0}}}}},"df":2,"docs":{"105":{"tf":1.0},"264":{"tf":1.0}},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"63":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"u":{"df":11,"docs":{"106":{"tf":1.0},"169":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"22":{"tf":1.0},"223":{"tf":1.4142135623730951},"225":{"tf":1.0},"226":{"tf":1.0},"229":{"tf":1.4142135623730951},"253":{"tf":1.0},"263":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"115":{"tf":1.0}}}}}}},"df":0,"docs":{},"e":{"c":{"<":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":1,"docs":{"242":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"242":{"tf":1.0}}}}}}}}}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"z":{"df":0,"docs":{},"e":{"d":{"/":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"m":{"df":1,"docs":{"282":{"tf":1.0}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":7,"docs":{"120":{"tf":2.0},"121":{"tf":2.449489742783178},"122":{"tf":1.0},"123":{"tf":1.4142135623730951},"124":{"tf":2.6457513110645907},"274":{"tf":1.4142135623730951},"64":{"tf":1.4142135623730951}},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{".":{"df":0,"docs":{},"p":{"df":0,"docs":{},"i":{"df":1,"docs":{"122":{"tf":1.0}}}}},"df":0,"docs":{}}}}}}},"df":0,"docs":{}},"r":{"b":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":1,"docs":{"126":{"tf":1.0}}}}},"df":0,"docs":{},"i":{"df":14,"docs":{"107":{"tf":1.0},"115":{"tf":1.0},"158":{"tf":1.0},"165":{"tf":1.0},"192":{"tf":1.0},"200":{"tf":1.0},"215":{"tf":1.0},"22":{"tf":1.0},"225":{"tf":1.0},"230":{"tf":1.0},"245":{"tf":1.0},"260":{"tf":1.0},"60":{"tf":1.0},"81":{"tf":1.0}},"f":{"df":2,"docs":{"196":{"tf":1.0},"198":{"tf":1.0}},"i":{"df":5,"docs":{"189":{"tf":1.0},"193":{"tf":1.0},"194":{"tf":1.0},"48":{"tf":1.0},"49":{"tf":1.0}}}},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"22":{"tf":1.0}}}}},"s":{"a":{"df":1,"docs":{"79":{"tf":1.0}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{".":{"df":0,"docs":{},"t":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"df":4,"docs":{"20":{"tf":1.0},"268":{"tf":2.0},"269":{"tf":1.0},"270":{"tf":1.0}}}}}},"df":53,"docs":{"104":{"tf":1.0},"11":{"tf":1.4142135623730951},"12":{"tf":2.0},"121":{"tf":1.0},"123":{"tf":1.4142135623730951},"124":{"tf":1.7320508075688772},"13":{"tf":1.0},"138":{"tf":1.0},"165":{"tf":1.4142135623730951},"167":{"tf":1.0},"169":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"175":{"tf":1.0},"18":{"tf":1.0},"189":{"tf":1.0},"190":{"tf":1.0},"192":{"tf":1.0},"197":{"tf":1.0},"20":{"tf":2.0},"201":{"tf":1.7320508075688772},"205":{"tf":1.0},"219":{"tf":1.0},"22":{"tf":1.7320508075688772},"222":{"tf":1.0},"25":{"tf":2.0},"253":{"tf":1.0},"26":{"tf":1.7320508075688772},"260":{"tf":2.23606797749979},"261":{"tf":2.23606797749979},"262":{"tf":2.6457513110645907},"263":{"tf":3.1622776601683795},"268":{"tf":2.449489742783178},"269":{"tf":1.0},"270":{"tf":1.4142135623730951},"271":{"tf":1.0},"274":{"tf":1.0},"278":{"tf":1.4142135623730951},"279":{"tf":2.449489742783178},"280":{"tf":1.0},"281":{"tf":1.0},"282":{"tf":1.0},"37":{"tf":2.449489742783178},"45":{"tf":1.0},"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0},"62":{"tf":1.0},"64":{"tf":1.4142135623730951},"69":{"tf":1.4142135623730951},"81":{"tf":2.0},"86":{"tf":1.0}}}}},"u":{"df":1,"docs":{"88":{"tf":1.4142135623730951}}}}},"t":{"df":1,"docs":{"122":{"tf":1.4142135623730951}}}},"i":{"a":{"d":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":4,"docs":{"124":{"tf":1.0},"197":{"tf":1.0},"200":{"tf":1.7320508075688772},"51":{"tf":1.0}}}},"df":0,"docs":{}}},"df":29,"docs":{"106":{"tf":1.0},"11":{"tf":1.4142135623730951},"13":{"tf":1.4142135623730951},"161":{"tf":1.0},"167":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"18":{"tf":1.0},"189":{"tf":1.4142135623730951},"190":{"tf":1.0},"192":{"tf":1.0},"194":{"tf":1.4142135623730951},"196":{"tf":1.0},"198":{"tf":1.0},"20":{"tf":1.0},"200":{"tf":1.0},"220":{"tf":1.0},"228":{"tf":1.0},"259":{"tf":1.0},"269":{"tf":1.4142135623730951},"274":{"tf":2.23606797749979},"277":{"tf":1.0},"283":{"tf":1.0},"37":{"tf":1.0},"43":{"tf":1.0},"45":{"tf":1.0},"49":{"tf":1.0},"51":{"tf":1.0},"81":{"tf":1.0}}},"c":{"df":0,"docs":{},"e":{"df":1,"docs":{"79":{"tf":1.0}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"df":1,"docs":{"174":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":5,"docs":{"107":{"tf":1.0},"126":{"tf":1.7320508075688772},"200":{"tf":1.0},"283":{"tf":1.0},"40":{"tf":1.4142135623730951}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":14,"docs":{"134":{"tf":1.0},"202":{"tf":1.4142135623730951},"204":{"tf":2.449489742783178},"205":{"tf":1.0},"206":{"tf":1.4142135623730951},"207":{"tf":2.0},"208":{"tf":1.4142135623730951},"210":{"tf":2.23606797749979},"213":{"tf":2.0},"214":{"tf":2.449489742783178},"215":{"tf":1.7320508075688772},"216":{"tf":2.8284271247461903},"217":{"tf":2.6457513110645907},"218":{"tf":1.0}}}}},"t":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"87":{"tf":1.0}}}},"df":0,"docs":{}},"v":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"138":{"tf":1.0}}},"df":0,"docs":{}}}},"s":{"df":2,"docs":{"116":{"tf":1.0},"232":{"tf":2.8284271247461903}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"201":{"tf":1.0},"63":{"tf":1.0}}}}}}},"x":{"df":0,"docs":{},"x":{"df":0,"docs":{},"x":{"df":3,"docs":{"267":{"tf":1.0},"269":{"tf":1.0},"270":{"tf":1.0}}}}}},"w":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":6,"docs":{"118":{"tf":1.0},"152":{"tf":1.0},"183":{"tf":1.0},"245":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0}}}},"l":{"_":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"264":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"k":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"264":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":2,"docs":{"264":{"tf":1.0},"79":{"tf":1.0}}},"n":{"df":0,"docs":{},"t":{"df":27,"docs":{"117":{"tf":1.4142135623730951},"12":{"tf":1.0},"121":{"tf":1.0},"124":{"tf":1.0},"136":{"tf":1.0},"140":{"tf":1.4142135623730951},"145":{"tf":1.0},"149":{"tf":1.0},"169":{"tf":1.0},"17":{"tf":1.0},"170":{"tf":1.4142135623730951},"171":{"tf":1.4142135623730951},"172":{"tf":1.0},"19":{"tf":1.0},"196":{"tf":1.0},"23":{"tf":1.0},"241":{"tf":1.0},"242":{"tf":1.4142135623730951},"247":{"tf":1.0},"264":{"tf":1.0},"270":{"tf":1.4142135623730951},"281":{"tf":1.0},"32":{"tf":1.0},"58":{"tf":1.4142135623730951},"60":{"tf":1.0},"62":{"tf":1.0},"75":{"tf":1.0}}}},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"64":{"tf":1.0}}},"n":{"df":2,"docs":{"262":{"tf":1.4142135623730951},"92":{"tf":1.4142135623730951}}}},"y":{"df":34,"docs":{"101":{"tf":1.0},"109":{"tf":1.0},"117":{"tf":1.0},"129":{"tf":1.0},"140":{"tf":1.0},"149":{"tf":1.0},"150":{"tf":1.0},"158":{"tf":1.0},"159":{"tf":1.0},"167":{"tf":1.0},"179":{"tf":1.0},"18":{"tf":1.0},"198":{"tf":1.0},"201":{"tf":1.0},"209":{"tf":1.0},"220":{"tf":1.7320508075688772},"222":{"tf":1.0},"226":{"tf":1.4142135623730951},"229":{"tf":1.7320508075688772},"242":{"tf":1.0},"247":{"tf":1.0},"249":{"tf":1.0},"26":{"tf":1.0},"263":{"tf":1.0},"274":{"tf":1.7320508075688772},"280":{"tf":1.0},"38":{"tf":1.0},"46":{"tf":1.4142135623730951},"52":{"tf":1.0},"58":{"tf":1.4142135623730951},"60":{"tf":1.0},"61":{"tf":2.0},"81":{"tf":1.0},"90":{"tf":1.0}}}},"df":0,"docs":{},"e":{"'":{"d":{"df":5,"docs":{"116":{"tf":1.4142135623730951},"171":{"tf":1.0},"225":{"tf":1.0},"58":{"tf":1.4142135623730951},"82":{"tf":1.0}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":4,"docs":{"229":{"tf":1.0},"242":{"tf":1.0},"61":{"tf":1.0},"62":{"tf":1.0}}}},"r":{"df":8,"docs":{"105":{"tf":1.0},"152":{"tf":1.0},"166":{"tf":1.0},"248":{"tf":1.0},"252":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"63":{"tf":1.0}}},"v":{"df":5,"docs":{"101":{"tf":1.4142135623730951},"225":{"tf":1.0},"231":{"tf":1.0},"63":{"tf":1.0},"88":{"tf":1.0}}}},"a":{"df":0,"docs":{},"k":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":1,"docs":{"245":{"tf":1.0}}}}}}},"b":{"df":2,"docs":{"257":{"tf":1.4142135623730951},"283":{"tf":1.4142135623730951}},"e":{"df":0,"docs":{},"x":{"df":0,"docs":{},"t":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":2,"docs":{"264":{"tf":1.0},"79":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":5,"docs":{"123":{"tf":1.0},"124":{"tf":1.0},"264":{"tf":1.4142135623730951},"78":{"tf":1.4142135623730951},"79":{"tf":1.0}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"61":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"l":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":4,"docs":{"107":{"tf":1.0},"3":{"tf":1.0},"7":{"tf":1.0},"75":{"tf":1.0}}}}},"df":0,"docs":{},"l":{"df":12,"docs":{"105":{"tf":1.4142135623730951},"111":{"tf":1.0},"167":{"tf":1.0},"18":{"tf":1.0},"199":{"tf":1.0},"252":{"tf":1.0},"257":{"tf":1.0},"31":{"tf":1.0},"36":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"79":{"tf":1.0}}}},"’":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":1,"docs":{"214":{"tf":1.0}}}},"r":{"df":1,"docs":{"217":{"tf":1.0}}}}},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"11":{"tf":1.0}}}}},"h":{"a":{"df":0,"docs":{},"t":{"'":{"df":3,"docs":{"233":{"tf":1.0},"268":{"tf":1.4142135623730951},"89":{"tf":1.4142135623730951}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":4,"docs":{"233":{"tf":1.0},"250":{"tf":1.0},"274":{"tf":1.0},"81":{"tf":1.0}}}},"’":{"df":3,"docs":{"86":{"tf":1.4142135623730951},"87":{"tf":1.4142135623730951},"89":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":3,"docs":{"121":{"tf":1.0},"267":{"tf":1.0},"276":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"e":{"a":{"df":2,"docs":{"79":{"tf":1.0},"86":{"tf":1.0}}},"df":0,"docs":{},"v":{"df":1,"docs":{"79":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":7,"docs":{"104":{"tf":1.0},"167":{"tf":1.4142135623730951},"229":{"tf":1.0},"236":{"tf":1.0},"64":{"tf":1.0},"70":{"tf":1.0},"73":{"tf":1.4142135623730951}}}}}}},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":3,"docs":{"170":{"tf":1.0},"171":{"tf":1.0},"248":{"tf":1.0}}}}}},"i":{"d":{"df":0,"docs":{},"e":{"df":1,"docs":{"64":{"tf":1.0}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":3,"docs":{"225":{"tf":1.0},"226":{"tf":1.0},"229":{"tf":1.0}}}},"l":{"df":0,"docs":{},"l":{"df":2,"docs":{"105":{"tf":1.0},"115":{"tf":1.0}}}},"n":{"d":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":8,"docs":{"11":{"tf":2.0},"117":{"tf":1.0},"12":{"tf":1.4142135623730951},"13":{"tf":2.23606797749979},"19":{"tf":1.7320508075688772},"201":{"tf":1.0},"278":{"tf":1.0},"280":{"tf":1.0}},"s":{"/":{"df":0,"docs":{},"w":{"df":0,"docs":{},"s":{"df":0,"docs":{},"l":{"df":1,"docs":{"19":{"tf":1.4142135623730951}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"e":{"(":{"&":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"242":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":6,"docs":{"158":{"tf":1.0},"226":{"tf":1.0},"231":{"tf":1.7320508075688772},"234":{"tf":1.7320508075688772},"242":{"tf":1.7320508075688772},"89":{"tf":2.0}}}},"s":{"d":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":1,"docs":{"128":{"tf":1.4142135623730951}}}}},"df":0,"docs":{},"h":{"df":1,"docs":{"200":{"tf":1.0}}}},"t":{"df":0,"docs":{},"h":{"/":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"59":{"tf":1.0}}}}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"166":{"tf":1.0}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":21,"docs":{"105":{"tf":1.0},"165":{"tf":1.0},"167":{"tf":1.0},"170":{"tf":1.0},"171":{"tf":1.0},"183":{"tf":1.4142135623730951},"187":{"tf":1.0},"209":{"tf":1.0},"212":{"tf":1.0},"22":{"tf":1.0},"227":{"tf":1.0},"228":{"tf":1.0},"231":{"tf":1.0},"238":{"tf":1.4142135623730951},"242":{"tf":1.0},"257":{"tf":1.0},"262":{"tf":1.0},"274":{"tf":1.4142135623730951},"58":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0}}}}}}}},"o":{"df":0,"docs":{},"n":{"'":{"df":0,"docs":{},"t":{"df":2,"docs":{"132":{"tf":1.0},"37":{"tf":1.0}}}},"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"82":{"tf":1.0}}}}},"df":0,"docs":{},"’":{"df":0,"docs":{},"t":{"df":1,"docs":{"212":{"tf":1.0}}}}},"r":{"d":{"df":3,"docs":{"227":{"tf":1.0},"257":{"tf":1.0},"99":{"tf":1.0}}},"df":0,"docs":{},"k":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"115":{"tf":1.0}}},"df":0,"docs":{}}}}}},"df":52,"docs":{"103":{"tf":1.0},"105":{"tf":1.4142135623730951},"107":{"tf":1.0},"11":{"tf":1.4142135623730951},"115":{"tf":1.0},"116":{"tf":1.4142135623730951},"117":{"tf":1.0},"122":{"tf":1.0},"13":{"tf":1.0},"141":{"tf":1.0},"146":{"tf":1.0},"150":{"tf":1.0},"16":{"tf":1.0},"166":{"tf":1.0},"169":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"174":{"tf":1.0},"187":{"tf":1.4142135623730951},"19":{"tf":1.4142135623730951},"190":{"tf":1.0},"197":{"tf":1.0},"219":{"tf":1.4142135623730951},"22":{"tf":1.4142135623730951},"224":{"tf":1.7320508075688772},"225":{"tf":1.0},"228":{"tf":1.0},"229":{"tf":1.0},"231":{"tf":1.0},"238":{"tf":1.4142135623730951},"239":{"tf":1.4142135623730951},"243":{"tf":1.0},"246":{"tf":1.0},"263":{"tf":1.0},"264":{"tf":1.0},"274":{"tf":1.0},"37":{"tf":2.0},"39":{"tf":1.0},"41":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"49":{"tf":1.0},"6":{"tf":1.4142135623730951},"60":{"tf":1.0},"61":{"tf":1.0},"7":{"tf":1.4142135623730951},"70":{"tf":1.0},"73":{"tf":1.0},"79":{"tf":1.0},"88":{"tf":1.0},"9":{"tf":1.0},"90":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"280":{"tf":1.0}}}},"f":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":6,"docs":{"12":{"tf":1.0},"16":{"tf":1.0},"17":{"tf":1.0},"18":{"tf":1.7320508075688772},"20":{"tf":2.0},"22":{"tf":2.449489742783178}}}}}},"s":{"df":0,"docs":{},"p":{"a":{"c":{"df":4,"docs":{"105":{"tf":1.0},"58":{"tf":1.0},"69":{"tf":1.0},"70":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":3,"docs":{"54":{"tf":1.0},"55":{"tf":1.0},"56":{"tf":1.0}}}}}},"l":{"d":{"df":5,"docs":{"122":{"tf":1.0},"16":{"tf":1.0},"229":{"tf":1.0},"242":{"tf":1.0},"58":{"tf":1.0}}},"df":0,"docs":{}},"s":{"df":3,"docs":{"57":{"tf":1.4142135623730951},"58":{"tf":1.0},"59":{"tf":1.0}}},"t":{"df":0,"docs":{},"h":{"df":2,"docs":{"201":{"tf":1.0},"208":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"l":{"d":{"df":0,"docs":{},"n":{"'":{"df":0,"docs":{},"t":{"df":1,"docs":{"116":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"r":{"a":{"df":0,"docs":{},"p":{"df":4,"docs":{"0":{"tf":1.0},"103":{"tf":1.4142135623730951},"108":{"tf":1.0},"109":{"tf":1.0}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":11,"docs":{"103":{"tf":1.0},"108":{"tf":1.4142135623730951},"109":{"tf":1.7320508075688772},"172":{"tf":1.0},"221":{"tf":1.0},"222":{"tf":1.0},"281":{"tf":1.0},"282":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"68":{"tf":1.4142135623730951}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":22,"docs":{"104":{"tf":1.4142135623730951},"107":{"tf":1.4142135623730951},"116":{"tf":1.0},"118":{"tf":1.0},"125":{"tf":1.4142135623730951},"158":{"tf":1.0},"196":{"tf":2.23606797749979},"233":{"tf":1.4142135623730951},"249":{"tf":1.0},"263":{"tf":1.0},"43":{"tf":1.4142135623730951},"57":{"tf":1.0},"58":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"61":{"tf":1.0},"64":{"tf":1.0},"7":{"tf":1.0},"70":{"tf":1.0},"79":{"tf":1.0},"81":{"tf":1.4142135623730951},"90":{"tf":1.0}},"r":{"df":1,"docs":{"79":{"tf":1.7320508075688772}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":22,"docs":{"0":{"tf":1.0},"101":{"tf":1.0},"103":{"tf":2.8284271247461903},"104":{"tf":2.0},"106":{"tf":1.0},"108":{"tf":2.23606797749979},"109":{"tf":2.23606797749979},"111":{"tf":1.0},"192":{"tf":1.4142135623730951},"193":{"tf":1.7320508075688772},"194":{"tf":1.0},"197":{"tf":1.0},"225":{"tf":2.0},"227":{"tf":1.0},"242":{"tf":1.4142135623730951},"45":{"tf":1.0},"46":{"tf":1.0},"70":{"tf":1.4142135623730951},"72":{"tf":2.0},"73":{"tf":1.4142135623730951},"74":{"tf":1.0},"93":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":3,"docs":{"116":{"tf":1.4142135623730951},"236":{"tf":1.0},"277":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"l":{"df":4,"docs":{"11":{"tf":1.7320508075688772},"12":{"tf":1.4142135623730951},"13":{"tf":2.0},"19":{"tf":1.4142135623730951}}}}},"x":{"8":{"6":{"_":{"6":{"4":{"/":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"37":{"tf":2.23606797749979}}}}},"df":0,"docs":{}},"df":1,"docs":{"17":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":3,"docs":{"17":{"tf":2.0},"37":{"tf":1.4142135623730951},"40":{"tf":1.0}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":5,"docs":{"14":{"tf":1.0},"252":{"tf":1.0},"28":{"tf":1.4142135623730951},"33":{"tf":1.4142135623730951},"73":{"tf":1.0}}}}},"df":17,"docs":{"163":{"tf":1.4142135623730951},"167":{"tf":2.0},"170":{"tf":1.7320508075688772},"171":{"tf":1.0},"172":{"tf":1.0},"174":{"tf":1.4142135623730951},"23":{"tf":1.4142135623730951},"251":{"tf":1.7320508075688772},"252":{"tf":3.1622776601683795},"253":{"tf":1.0},"271":{"tf":1.0},"274":{"tf":1.4142135623730951},"28":{"tf":1.7320508075688772},"29":{"tf":1.7320508075688772},"32":{"tf":1.4142135623730951},"33":{"tf":1.7320508075688772},"34":{"tf":1.7320508075688772}}}}}}}}},"df":0,"docs":{}}},"o":{"d":{"df":0,"docs":{},"e":{"df":19,"docs":{"109":{"tf":2.449489742783178},"11":{"tf":1.7320508075688772},"14":{"tf":1.4142135623730951},"165":{"tf":1.4142135623730951},"166":{"tf":1.0},"167":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"175":{"tf":1.4142135623730951},"25":{"tf":2.0},"252":{"tf":1.4142135623730951},"26":{"tf":1.4142135623730951},"274":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":2.0},"35":{"tf":1.0},"36":{"tf":2.0},"46":{"tf":1.4142135623730951},"73":{"tf":3.1622776601683795}}}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"14":{"tf":1.4142135623730951}}}}}}}}},"df":2,"docs":{"207":{"tf":1.4142135623730951},"210":{"tf":1.7320508075688772}},"m":{"df":0,"docs":{},"l":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{":":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"=":{"\"":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"t":{"df":0,"docs":{},"p":{":":{"/":{"/":{"df":0,"docs":{},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"s":{".":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{".":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"/":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"k":{"/":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"/":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"df":1,"docs":{"70":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"x":{"df":0,"docs":{},"x":{"df":5,"docs":{"242":{"tf":1.0},"270":{"tf":1.0},"277":{"tf":1.0},"46":{"tf":1.0},"48":{"tf":1.7320508075688772}}}}},"y":{"df":2,"docs":{"137":{"tf":1.0},"210":{"tf":1.7320508075688772}},"e":{"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"193":{"tf":1.0}}}},"df":1,"docs":{"18":{"tf":1.0}},"p":{"df":1,"docs":{"60":{"tf":1.0}}}},"o":{"df":0,"docs":{},"u":{"'":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":11,"docs":{"103":{"tf":1.0},"104":{"tf":1.0},"105":{"tf":1.0},"108":{"tf":1.0},"109":{"tf":1.0},"123":{"tf":1.0},"17":{"tf":1.4142135623730951},"274":{"tf":1.0},"279":{"tf":1.0},"68":{"tf":1.0},"90":{"tf":1.0}}}},"r":{"df":12,"docs":{"105":{"tf":1.0},"107":{"tf":1.4142135623730951},"114":{"tf":1.0},"115":{"tf":1.0},"17":{"tf":1.0},"21":{"tf":1.0},"37":{"tf":1.0},"39":{"tf":1.0},"41":{"tf":1.0},"59":{"tf":1.0},"60":{"tf":1.0},"73":{"tf":1.0}}},"v":{"df":6,"docs":{"102":{"tf":1.0},"104":{"tf":1.0},"106":{"tf":1.7320508075688772},"279":{"tf":1.0},"6":{"tf":1.0},"60":{"tf":1.0}}}},"df":0,"docs":{},"r":{"_":{"c":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"_":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"m":{"df":2,"docs":{"69":{"tf":1.7320508075688772},"72":{"tf":1.0}},"e":{">":{".":{"df":0,"docs":{},"s":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":1,"docs":{"73":{"tf":1.4142135623730951}}}}}}},"u":{"d":{"df":0,"docs":{},"l":{"df":2,"docs":{"73":{"tf":1.4142135623730951},"74":{"tf":1.0}}}},"df":0,"docs":{}}},":":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"d":{"df":0,"docs":{},"e":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":1,"docs":{"70":{"tf":1.0}}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"70":{"tf":1.0}}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{".":{"df":0,"docs":{},"h":{"df":1,"docs":{"73":{"tf":1.0}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"df":0,"docs":{}},"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"f":{"df":1,"docs":{"64":{"tf":1.0}}}}}}},"’":{"df":0,"docs":{},"r":{"df":1,"docs":{"115":{"tf":1.0}}}}}}},"z":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":2,"docs":{"250":{"tf":1.4142135623730951},"84":{"tf":1.4142135623730951}}}}},"i":{"df":0,"docs":{},"p":{"df":2,"docs":{"167":{"tf":1.0},"252":{"tf":1.0}},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"274":{"tf":1.0}}}}}}},"l":{"df":0,"docs":{},"i":{"b":{"1":{"df":0,"docs":{},"g":{"df":1,"docs":{"11":{"tf":1.4142135623730951}}}},"df":1,"docs":{"11":{"tf":1.4142135623730951}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"h":{"df":0,"docs":{},"r":{"c":{"df":2,"docs":{"11":{"tf":1.0},"12":{"tf":1.0}}},"df":0,"docs":{}}}}}}},"title":{"root":{"1":{"df":2,"docs":{"145":{"tf":1.0},"196":{"tf":1.0}}},"2":{"df":2,"docs":{"146":{"tf":1.0},"197":{"tf":1.0}}},"3":{"df":1,"docs":{"198":{"tf":1.0}}},"a":{"1":{"df":1,"docs":{"154":{"tf":1.0}}},"2":{"df":1,"docs":{"155":{"tf":1.0}}},"3":{"df":1,"docs":{"156":{"tf":1.0}}},"a":{"df":0,"docs":{},"r":{"df":1,"docs":{"221":{"tf":1.0}}}},"c":{"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":3,"docs":{"114":{"tf":1.0},"126":{"tf":1.0},"127":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"276":{"tf":1.0}}}}}}},"df":0,"docs":{}},"d":{"df":5,"docs":{"123":{"tf":1.0},"22":{"tf":1.0},"57":{"tf":1.0},"68":{"tf":1.0},"73":{"tf":1.0}}},"df":0,"docs":{},"g":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":4,"docs":{"15":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0},"52":{"tf":1.0}}}}}}},"df":0,"docs":{}},"l":{"df":0,"docs":{},"w":{"a":{"df":0,"docs":{},"y":{"df":1,"docs":{"133":{"tf":1.0}}}},"df":0,"docs":{}}},"n":{"d":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"/":{"df":0,"docs":{},"k":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"65":{"tf":1.0}}}}}}}}},"df":6,"docs":{"110":{"tf":1.0},"117":{"tf":1.0},"13":{"tf":1.0},"237":{"tf":1.0},"49":{"tf":1.0},"54":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"i":{"df":5,"docs":{"107":{"tf":1.0},"139":{"tf":1.0},"145":{"tf":1.0},"232":{"tf":1.0},"236":{"tf":1.0}}},"p":{"df":2,"docs":{"183":{"tf":1.0},"52":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"df":2,"docs":{"173":{"tf":1.0},"60":{"tf":1.0}}}}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"i":{"c":{"df":18,"docs":{"0":{"tf":1.0},"118":{"tf":1.0},"120":{"tf":1.0},"125":{"tf":1.0},"15":{"tf":1.0},"252":{"tf":1.0},"259":{"tf":1.0},"262":{"tf":1.0},"265":{"tf":1.0},"274":{"tf":1.0},"3":{"tf":1.0},"30":{"tf":1.0},"35":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0},"52":{"tf":1.0},"68":{"tf":1.0},"9":{"tf":1.0}}},"df":0,"docs":{}},"y":{"_":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":3,"docs":{"247":{"tf":1.0},"249":{"tf":1.0},"88":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"o":{"a":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"146":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"v":{"c":{"df":1,"docs":{"276":{"tf":1.0}}},"df":0,"docs":{}}}}},"r":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":3,"docs":{"134":{"tf":1.0},"135":{"tf":1.0},"244":{"tf":1.0}}}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"280":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"154":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"275":{"tf":1.0}}}}}},"o":{"df":2,"docs":{"18":{"tf":1.0},"22":{"tf":1.0}},"m":{"df":1,"docs":{"25":{"tf":1.0}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"s":{"/":{"a":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"61":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}}}},"v":{"df":0,"docs":{},"o":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"57":{"tf":1.0},"60":{"tf":1.0}}},"df":0,"docs":{}}}}},"b":{"1":{"df":1,"docs":{"157":{"tf":1.0}}},"2":{"df":1,"docs":{"158":{"tf":1.0}}},"3":{"df":1,"docs":{"159":{"tf":1.0}}},"a":{"c":{"df":0,"docs":{},"k":{"df":1,"docs":{"115":{"tf":1.0}},"g":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"58":{"tf":1.0}}},"df":0,"docs":{}}}}}}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":2,"docs":{"172":{"tf":1.0},"213":{"tf":1.0}}}}},"df":1,"docs":{"170":{"tf":1.0}},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":1,"docs":{"256":{"tf":1.0}}}}},"n":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"60":{"tf":1.0}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"t":{"df":0,"docs":{},"w":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"89":{"tf":1.0}}}}}}}},"i":{"df":0,"docs":{},"n":{"d":{"df":2,"docs":{"70":{"tf":1.0},"71":{"tf":1.0}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":1,"docs":{"286":{"tf":1.0}}}}},"r":{"a":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":2,"docs":{"38":{"tf":1.0},"39":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"k":{"df":2,"docs":{"118":{"tf":1.0},"39":{"tf":1.0}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"w":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"186":{"tf":1.0}}}}}}}},"u":{"df":0,"docs":{},"g":{"df":1,"docs":{"4":{"tf":1.0}}},"i":{"df":0,"docs":{},"l":{"d":{"df":20,"docs":{"10":{"tf":1.0},"11":{"tf":1.0},"12":{"tf":1.0},"14":{"tf":1.0},"15":{"tf":1.0},"170":{"tf":1.0},"266":{"tf":1.0},"267":{"tf":1.0},"272":{"tf":1.0},"274":{"tf":1.0},"28":{"tf":1.0},"285":{"tf":1.0},"286":{"tf":1.0},"33":{"tf":1.0},"37":{"tf":1.0},"38":{"tf":1.0},"5":{"tf":1.0},"75":{"tf":1.0},"77":{"tf":1.0},"9":{"tf":1.0}}},"df":0,"docs":{},"t":{"df":1,"docs":{"271":{"tf":1.0}}}}},"n":{"d":{"df":0,"docs":{},"l":{"df":2,"docs":{"170":{"tf":1.0},"183":{"tf":1.0}}}},"df":0,"docs":{}},"s":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"282":{"tf":1.0}}}},"df":0,"docs":{}}}}},"c":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":1,"docs":{"115":{"tf":1.0}}}},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"170":{"tf":1.0}}}},"df":0,"docs":{}}}},"v":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":1,"docs":{"21":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":1,"docs":{"171":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"120":{"tf":1.0},"261":{"tf":1.0}}}},"df":0,"docs":{}}}}},"h":{"a":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":2,"docs":{"115":{"tf":1.0},"228":{"tf":1.0}}}}}}},"n":{"df":0,"docs":{},"g":{"df":3,"docs":{"118":{"tf":1.0},"270":{"tf":1.0},"39":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"k":{"df":2,"docs":{"155":{"tf":1.0},"156":{"tf":1.0}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":6,"docs":{"29":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.0},"34":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0}}}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"199":{"tf":1.0}}}}}}},"i":{"df":0,"docs":{},"r":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"i":{"df":1,"docs":{"277":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}},"l":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"131":{"tf":1.0}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":7,"docs":{"186":{"tf":1.0},"193":{"tf":1.0},"194":{"tf":1.0},"197":{"tf":1.0},"198":{"tf":1.0},"231":{"tf":1.0},"238":{"tf":1.0}}}}}}},"o":{"d":{"df":0,"docs":{},"e":{"df":22,"docs":{"105":{"tf":1.0},"107":{"tf":1.0},"111":{"tf":1.0},"114":{"tf":1.0},"117":{"tf":1.0},"118":{"tf":1.0},"159":{"tf":1.0},"172":{"tf":1.0},"43":{"tf":1.0},"44":{"tf":1.0},"45":{"tf":1.0},"46":{"tf":1.0},"64":{"tf":1.0},"65":{"tf":1.0},"66":{"tf":1.0},"67":{"tf":1.0},"69":{"tf":1.0},"8":{"tf":1.0},"83":{"tf":1.0},"92":{"tf":1.0},"95":{"tf":1.0},"98":{"tf":1.0}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":3,"docs":{"232":{"tf":1.0},"259":{"tf":1.0},"86":{"tf":1.0}}}},"df":0,"docs":{}}}},"m":{"df":0,"docs":{},"m":{"a":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"237":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}},"p":{"a":{"df":0,"docs":{},"r":{"df":2,"docs":{"155":{"tf":1.0},"156":{"tf":1.0}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":6,"docs":{"114":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"280":{"tf":1.0},"57":{"tf":1.0},"60":{"tf":1.0}}}},"o":{"df":0,"docs":{},"n":{"df":29,"docs":{"0":{"tf":1.0},"101":{"tf":1.0},"103":{"tf":1.0},"11":{"tf":1.0},"122":{"tf":1.0},"123":{"tf":1.0},"16":{"tf":1.0},"228":{"tf":1.0},"23":{"tf":1.0},"230":{"tf":1.0},"235":{"tf":1.0},"251":{"tf":1.0},"253":{"tf":1.0},"257":{"tf":1.0},"259":{"tf":1.0},"283":{"tf":1.0},"29":{"tf":1.0},"31":{"tf":1.0},"32":{"tf":1.0},"34":{"tf":1.0},"36":{"tf":1.0},"41":{"tf":1.0},"49":{"tf":1.0},"54":{"tf":1.0},"68":{"tf":1.0},"73":{"tf":1.0},"74":{"tf":1.4142135623730951},"75":{"tf":1.0},"76":{"tf":1.0}},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"'":{"df":1,"docs":{"104":{"tf":1.0}}},"df":0,"docs":{}}}}}}}},"n":{"df":5,"docs":{"144":{"tf":1.0},"153":{"tf":1.0},"168":{"tf":1.0},"181":{"tf":1.0},"211":{"tf":1.0}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":4,"docs":{"165":{"tf":1.0},"166":{"tf":1.0},"209":{"tf":1.0},"210":{"tf":1.0}}}}},"i":{"d":{"df":7,"docs":{"137":{"tf":1.0},"142":{"tf":1.0},"151":{"tf":1.0},"163":{"tf":1.0},"179":{"tf":1.0},"195":{"tf":1.0},"207":{"tf":1.0}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"m":{"df":1,"docs":{"90":{"tf":1.0}}}}},"t":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"1":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"201":{"tf":1.0}}}},"x":{"df":0,"docs":{},"t":{"df":7,"docs":{"136":{"tf":1.0},"140":{"tf":1.0},"149":{"tf":1.0},"161":{"tf":1.0},"177":{"tf":1.0},"187":{"tf":1.0},"203":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"3":{"tf":1.0}}}}},"df":0,"docs":{}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"91":{"tf":1.0}}}},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"101":{"tf":1.0}}}}}}},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"131":{"tf":1.0}}},"df":0,"docs":{}}}}}},"u":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":4,"docs":{"148":{"tf":1.0},"154":{"tf":1.0},"155":{"tf":1.0},"156":{"tf":1.0}}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"50":{"tf":1.0}}}},"df":0,"docs":{}}}}},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":6,"docs":{"129":{"tf":1.0},"130":{"tf":1.0},"131":{"tf":1.0},"268":{"tf":1.0},"269":{"tf":1.0},"72":{"tf":1.0}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"280":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":2,"docs":{"175":{"tf":1.0},"228":{"tf":1.0}}}}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":1,"docs":{"222":{"tf":1.0}}}}}},"t":{"df":1,"docs":{"270":{"tf":1.0}}}}},"d":{"a":{"df":0,"docs":{},"t":{"a":{"b":{"a":{"df":0,"docs":{},"s":{"df":8,"docs":{"148":{"tf":1.0},"154":{"tf":1.4142135623730951},"155":{"tf":1.0},"156":{"tf":1.0},"157":{"tf":1.4142135623730951},"158":{"tf":1.4142135623730951},"248":{"tf":1.0},"264":{"tf":1.0}}}},"df":0,"docs":{}},"df":3,"docs":{"183":{"tf":1.0},"184":{"tf":1.0},"80":{"tf":1.0}}},"df":0,"docs":{},"e":{"df":1,"docs":{"213":{"tf":1.0}}}}},"df":1,"docs":{"172":{"tf":1.0}},"e":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"184":{"tf":1.0}}}},"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":2,"docs":{"117":{"tf":1.0},"62":{"tf":1.0}},"g":{"df":1,"docs":{"117":{"tf":1.0}}}}}},"c":{"df":0,"docs":{},"i":{"d":{"df":2,"docs":{"155":{"tf":1.0},"157":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":13,"docs":{"134":{"tf":1.0},"135":{"tf":1.0},"138":{"tf":1.0},"141":{"tf":1.0},"143":{"tf":1.0},"150":{"tf":1.0},"152":{"tf":1.0},"162":{"tf":1.0},"164":{"tf":1.0},"178":{"tf":1.0},"180":{"tf":1.0},"206":{"tf":1.0},"208":{"tf":1.0}}}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"233":{"tf":1.0}}}}}},"df":0,"docs":{},"l":{"a":{"df":0,"docs":{},"y":{"df":1,"docs":{"184":{"tf":1.0}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"157":{"tf":1.0}}}},"i":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"235":{"tf":1.0}}}}}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":3,"docs":{"221":{"tf":1.0},"53":{"tf":1.0},"63":{"tf":1.0}}},"df":0,"docs":{}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"df":3,"docs":{"219":{"tf":1.0},"251":{"tf":1.0},"76":{"tf":1.0}}}}},"k":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":1,"docs":{"192":{"tf":1.0}}}}}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"y":{"df":2,"docs":{"129":{"tf":1.0},"131":{"tf":1.0}}}},"u":{"c":{"df":0,"docs":{},"t":{"df":2,"docs":{"128":{"tf":1.0},"133":{"tf":1.0}}}},"df":0,"docs":{}}}}},"t":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":2,"docs":{"227":{"tf":1.0},"242":{"tf":1.0}}}}},"df":0,"docs":{}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"p":{"df":2,"docs":{"26":{"tf":1.0},"284":{"tf":1.0}}}}}}}},"i":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":1,"docs":{"255":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":1,"docs":{"89":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"146":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"72":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"s":{"a":{"b":{"df":0,"docs":{},"l":{"df":2,"docs":{"26":{"tf":1.0},"61":{"tf":1.0}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"234":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":5,"docs":{"160":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"215":{"tf":1.0},"74":{"tf":1.0}}}}},"df":0,"docs":{}}}}}},"o":{"c":{"df":0,"docs":{},"u":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":5,"docs":{"107":{"tf":1.0},"219":{"tf":1.0},"284":{"tf":1.0},"285":{"tf":1.0},"286":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"r":{"a":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":1,"docs":{"104":{"tf":1.0}}}}},"df":0,"docs":{},"i":{"df":2,"docs":{"204":{"tf":1.0},"216":{"tf":1.0}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":5,"docs":{"141":{"tf":1.0},"150":{"tf":1.0},"162":{"tf":1.0},"178":{"tf":1.0},"206":{"tf":1.0}}}}}}}},"df":0,"docs":{},"e":{"a":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"258":{"tf":1.0}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":1,"docs":{"176":{"tf":1.0}}}}}},"d":{"df":0,"docs":{},"g":{"df":1,"docs":{"224":{"tf":1.0}}}},"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":1,"docs":{"145":{"tf":1.0}}}}}}},"d":{"df":3,"docs":{"216":{"tf":1.0},"48":{"tf":1.4142135623730951},"52":{"tf":1.0}}},"df":0,"docs":{},"g":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":6,"docs":{"231":{"tf":1.0},"232":{"tf":1.0},"238":{"tf":1.0},"241":{"tf":1.0},"246":{"tf":1.0},"258":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"78":{"tf":1.0}}}}}},"r":{"df":0,"docs":{},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":3,"docs":{"154":{"tf":1.0},"155":{"tf":1.4142135623730951},"156":{"tf":1.4142135623730951}}}}}},"x":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":3,"docs":{"100":{"tf":1.0},"94":{"tf":1.0},"97":{"tf":1.0}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":6,"docs":{"101":{"tf":1.0},"115":{"tf":1.0},"122":{"tf":1.0},"191":{"tf":1.0},"197":{"tf":1.0},"198":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":4,"docs":{"176":{"tf":1.0},"183":{"tf":1.0},"184":{"tf":1.0},"204":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"s":{"df":3,"docs":{"111":{"tf":1.0},"281":{"tf":1.0},"90":{"tf":1.0}}}}}}},"f":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"159":{"tf":1.0}}}}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"a":{"df":0,"docs":{},"r":{"df":2,"docs":{"102":{"tf":1.0},"103":{"tf":1.0}}}},"df":0,"docs":{}}}}},"q":{"0":{"df":1,"docs":{"61":{"tf":1.0}}},"df":2,"docs":{"110":{"tf":1.0},"85":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"57":{"tf":1.0}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"x":{"df":4,"docs":{"12":{"tf":1.0},"126":{"tf":1.0},"16":{"tf":1.0},"55":{"tf":1.0}}}}}},"f":{"df":0,"docs":{},"i":{"df":1,"docs":{"44":{"tf":1.0}}}},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":3,"docs":{"104":{"tf":1.0},"157":{"tf":1.0},"158":{"tf":1.0}}}},"n":{"d":{"df":1,"docs":{"6":{"tf":1.0}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"x":{"df":7,"docs":{"14":{"tf":1.0},"15":{"tf":1.0},"202":{"tf":1.0},"23":{"tf":1.0},"254":{"tf":1.0},"31":{"tf":1.0},"56":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"t":{"df":5,"docs":{"10":{"tf":1.0},"102":{"tf":1.0},"104":{"tf":1.0},"176":{"tf":1.0},"184":{"tf":1.0}}}}},"x":{"df":1,"docs":{"282":{"tf":1.0}}}},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":3,"docs":{"244":{"tf":1.0},"25":{"tf":1.0},"27":{"tf":1.0}}}}},"o":{"c":{"df":0,"docs":{},"u":{"df":2,"docs":{"32":{"tf":1.0},"36":{"tf":1.0}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":0,"docs":{},"u":{"df":0,"docs":{},"p":{"df":1,"docs":{"241":{"tf":1.0}}}}}}}}},"r":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"77":{"tf":1.0}}}}}}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":4,"docs":{"129":{"tf":1.0},"145":{"tf":1.4142135623730951},"281":{"tf":1.0},"42":{"tf":1.0}}}}}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"174":{"tf":1.0}}}}}}},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"258":{"tf":1.0}}}}}}},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":5,"docs":{"244":{"tf":1.0},"30":{"tf":1.0},"35":{"tf":1.0},"76":{"tf":1.0},"83":{"tf":1.0}}}}},"t":{"_":{"df":0,"docs":{},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"_":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"c":{"df":1,"docs":{"87":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"df":1,"docs":{"271":{"tf":1.0}}}},"l":{"df":0,"docs":{},"o":{"b":{"a":{"df":0,"docs":{},"l":{"df":1,"docs":{"86":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"h":{"a":{"df":1,"docs":{"224":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"h":{"df":1,"docs":{"221":{"tf":1.0}}}}},"df":0,"docs":{}},"u":{"df":0,"docs":{},"i":{"d":{"df":3,"docs":{"278":{"tf":1.0},"41":{"tf":1.0},"75":{"tf":1.0}},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"63":{"tf":1.0}}}}}}},"df":0,"docs":{}}}},"h":{"a":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"l":{"df":1,"docs":{"148":{"tf":1.0}}}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":1,"docs":{"133":{"tf":1.0}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":2,"docs":{"251":{"tf":1.0},"254":{"tf":1.0}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":2,"docs":{"202":{"tf":1.0},"215":{"tf":1.0}}}}}}}}},"i":{"d":{"df":1,"docs":{"86":{"tf":1.4142135623730951}},"e":{"a":{"df":1,"docs":{"51":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{},"m":{"df":0,"docs":{},"p":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":6,"docs":{"146":{"tf":1.0},"167":{"tf":1.0},"227":{"tf":1.0},"228":{"tf":1.0},"230":{"tf":1.0},"258":{"tf":1.0}}}}}}},"i":{"c":{"df":1,"docs":{"263":{"tf":1.0}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"87":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"o":{"df":0,"docs":{},"v":{"df":1,"docs":{"51":{"tf":1.0}}}}}}},"n":{"c":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"d":{"df":4,"docs":{"29":{"tf":1.0},"31":{"tf":1.0},"34":{"tf":1.0},"36":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"190":{"tf":1.0}}}}},"s":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"a":{"d":{"df":1,"docs":{"61":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"df":2,"docs":{"47":{"tf":1.0},"60":{"tf":1.0}}}},"r":{"a":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"254":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"f":{"a":{"c":{"df":1,"docs":{"104":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"o":{"d":{"df":0,"docs":{},"u":{"c":{"df":2,"docs":{"105":{"tf":1.0},"213":{"tf":1.0}}},"df":0,"docs":{}}},"df":0,"docs":{}}}},"v":{"df":0,"docs":{},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"v":{"df":1,"docs":{"116":{"tf":1.0}}}}}}},"o":{"df":8,"docs":{"127":{"tf":1.0},"14":{"tf":1.0},"15":{"tf":1.0},"202":{"tf":1.0},"23":{"tf":1.0},"31":{"tf":1.0},"32":{"tf":1.0},"56":{"tf":1.0}},"s":{"/":{"df":0,"docs":{},"s":{"df":0,"docs":{},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":1,"docs":{"66":{"tf":1.0}}}}}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"u":{"df":1,"docs":{"6":{"tf":1.0}}}}}},"j":{"df":0,"docs":{},"n":{"a":{"df":2,"docs":{"116":{"tf":1.0},"37":{"tf":1.0}}},"df":0,"docs":{},"i":{"df":1,"docs":{"116":{"tf":1.0}}}}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":1,"docs":{"212":{"tf":1.0}}}},"y":{"df":2,"docs":{"145":{"tf":1.4142135623730951},"146":{"tf":1.0}}}},"n":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":1,"docs":{"114":{"tf":1.0}},"n":{"df":2,"docs":{"155":{"tf":1.0},"156":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":7,"docs":{"108":{"tf":1.0},"111":{"tf":1.0},"115":{"tf":1.0},"128":{"tf":1.0},"45":{"tf":1.0},"70":{"tf":1.0},"98":{"tf":1.0}}}}}}}},"l":{"a":{"df":0,"docs":{},"y":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"44":{"tf":1.0}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":4,"docs":{"230":{"tf":1.0},"235":{"tf":1.0},"251":{"tf":1.0},"254":{"tf":1.0}}}}}},"i":{"b":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":4,"docs":{"114":{"tf":1.0},"191":{"tf":1.0},"196":{"tf":1.0},"77":{"tf":1.0}}}}},"df":0,"docs":{}}},"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":1,"docs":{"2":{"tf":1.0}}}}}},"df":0,"docs":{},"f":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"y":{"c":{"df":0,"docs":{},"l":{"df":1,"docs":{"131":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{}}},"m":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":3,"docs":{"202":{"tf":1.0},"213":{"tf":1.0},"214":{"tf":1.0}}}}},"n":{"df":0,"docs":{},"e":{"df":1,"docs":{"237":{"tf":1.0}}},"k":{"df":3,"docs":{"147":{"tf":1.0},"185":{"tf":1.0},"218":{"tf":1.0}}}},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"233":{"tf":1.0}}}}},"o":{"a":{"d":{"df":1,"docs":{"114":{"tf":1.0}}},"df":0,"docs":{}},"c":{"a":{"df":0,"docs":{},"l":{"df":12,"docs":{"15":{"tf":1.4142135623730951},"16":{"tf":1.0},"23":{"tf":1.0},"26":{"tf":1.0},"29":{"tf":1.0},"30":{"tf":1.0},"31":{"tf":1.0},"32":{"tf":1.0},"34":{"tf":1.0},"35":{"tf":1.0},"36":{"tf":1.0},"37":{"tf":1.0}}}},"df":0,"docs":{}},"df":0,"docs":{},"g":{"df":4,"docs":{"125":{"tf":1.0},"126":{"tf":1.0},"127":{"tf":1.0},"134":{"tf":1.0}},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"139":{"tf":1.0}}}}},"w":{"df":2,"docs":{"230":{"tf":1.0},"235":{"tf":1.0}}}}},"m":{"a":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":1,"docs":{"57":{"tf":1.0}}}},"n":{"a":{"df":0,"docs":{},"g":{"df":11,"docs":{"146":{"tf":1.0},"225":{"tf":1.0},"226":{"tf":1.0},"23":{"tf":1.0},"245":{"tf":1.0},"268":{"tf":1.0},"269":{"tf":1.0},"32":{"tf":1.0},"63":{"tf":1.0},"73":{"tf":1.0},"81":{"tf":1.0}}}},"df":0,"docs":{},"u":{"a":{"df":0,"docs":{},"l":{"df":2,"docs":{"20":{"tf":1.0},"27":{"tf":1.0}}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"k":{"d":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":0,"docs":{},"n":{"df":1,"docs":{"135":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"a":{"df":0,"docs":{},"z":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"d":{"df":3,"docs":{"220":{"tf":1.0},"222":{"tf":1.0},"73":{"tf":1.0}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"g":{"df":1,"docs":{"119":{"tf":1.0}}}},"s":{"df":0,"docs":{},"s":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"106":{"tf":1.0}}}},"df":0,"docs":{}}},"t":{"a":{"df":1,"docs":{"80":{"tf":1.0}}},"df":0,"docs":{},"h":{"df":0,"docs":{},"o":{"d":{"df":1,"docs":{"131":{"tf":1.0}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"259":{"tf":1.0}}},"df":0,"docs":{}}}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"t":{"df":2,"docs":{"202":{"tf":1.0},"212":{"tf":1.0}}}},"df":0,"docs":{}}},"s":{"c":{"df":1,"docs":{"240":{"tf":1.0}}},"df":0,"docs":{}}},"o":{"b":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"186":{"tf":1.0}}}}},"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":1,"docs":{"37":{"tf":1.0}}}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":1,"docs":{"229":{"tf":1.0}}}},"v":{"df":0,"docs":{},"e":{"df":1,"docs":{"158":{"tf":1.0}}}},"z":{"df":1,"docs":{"276":{"tf":1.0}},"i":{"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"a":{"df":2,"docs":{"120":{"tf":1.0},"261":{"tf":1.0}}},"df":0,"docs":{}}}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":0,"docs":{},"i":{"df":1,"docs":{"255":{"tf":1.0}},"p":{"df":0,"docs":{},"l":{"df":1,"docs":{"172":{"tf":1.0}}}}}}}}},"n":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":3,"docs":{"112":{"tf":1.0},"114":{"tf":1.0},"91":{"tf":1.0}}}},"r":{"df":0,"docs":{},"r":{"df":1,"docs":{"286":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"g":{"df":2,"docs":{"166":{"tf":1.0},"210":{"tf":1.0}}},"w":{"df":6,"docs":{"123":{"tf":1.0},"196":{"tf":1.0},"268":{"tf":1.0},"269":{"tf":1.0},"281":{"tf":1.0},"68":{"tf":1.0}}},"x":{"df":0,"docs":{},"t":{"df":1,"docs":{"103":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":0,"docs":{},"t":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":3,"docs":{"266":{"tf":1.0},"272":{"tf":1.0},"40":{"tf":1.0}}}}}}},"m":{"b":{"df":0,"docs":{},"u":{"df":2,"docs":{"194":{"tf":1.0},"198":{"tf":1.0}}}},"df":0,"docs":{}}},"o":{"df":0,"docs":{},"n":{"df":2,"docs":{"189":{"tf":1.0},"241":{"tf":1.0}}},"t":{"df":0,"docs":{},"h":{"df":2,"docs":{"169":{"tf":1.0},"182":{"tf":1.0}}}},"w":{"df":1,"docs":{"257":{"tf":1.0}}}},"s":{"df":0,"docs":{},"s":{"df":2,"docs":{"278":{"tf":1.0},"280":{"tf":1.0}}}}},"o":{"b":{"df":0,"docs":{},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":5,"docs":{"128":{"tf":1.0},"129":{"tf":1.0},"130":{"tf":1.0},"131":{"tf":1.0},"132":{"tf":1.0}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":1,"docs":{"204":{"tf":1.0}}}}}}},"df":0,"docs":{},"n":{"df":1,"docs":{"258":{"tf":1.0}}},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":2,"docs":{"154":{"tf":1.0},"155":{"tf":1.0}}},"r":{"df":1,"docs":{"156":{"tf":1.0}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":18,"docs":{"137":{"tf":1.0},"142":{"tf":1.0},"144":{"tf":1.0},"145":{"tf":1.0},"146":{"tf":1.0},"151":{"tf":1.0},"153":{"tf":1.0},"163":{"tf":1.0},"168":{"tf":1.0},"179":{"tf":1.0},"181":{"tf":1.0},"195":{"tf":1.0},"196":{"tf":1.0},"197":{"tf":1.0},"198":{"tf":1.0},"199":{"tf":1.0},"207":{"tf":1.0},"211":{"tf":1.0}}}}}}},"u":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"m":{"df":6,"docs":{"138":{"tf":1.0},"143":{"tf":1.0},"152":{"tf":1.0},"164":{"tf":1.0},"180":{"tf":1.0},"208":{"tf":1.0}}}}},"df":0,"docs":{}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":4,"docs":{"243":{"tf":1.0},"93":{"tf":1.0},"96":{"tf":1.0},"99":{"tf":1.0}}}}}}}}}},"p":{"a":{"c":{"df":0,"docs":{},"k":{"a":{"df":0,"docs":{},"g":{"df":9,"docs":{"112":{"tf":1.0},"113":{"tf":1.0},"160":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"23":{"tf":1.0},"251":{"tf":1.0},"32":{"tf":1.0},"73":{"tf":1.0}}}},"df":0,"docs":{}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"145":{"tf":1.0}}}},"t":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"270":{"tf":1.0}}}},"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":1,"docs":{"247":{"tf":1.0}}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"274":{"tf":1.0}}}}}}}},"l":{"a":{"c":{"df":0,"docs":{},"e":{"df":1,"docs":{"202":{"tf":1.0}}}},"df":0,"docs":{},"n":{"df":2,"docs":{"200":{"tf":1.0},"230":{"tf":1.0}}},"t":{"df":0,"docs":{},"f":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"m":{"df":1,"docs":{"255":{"tf":1.0}}}}}}}},"df":0,"docs":{}},"o":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"78":{"tf":1.0}}}}},"l":{"df":0,"docs":{},"i":{"c":{"df":0,"docs":{},"i":{"df":2,"docs":{"261":{"tf":1.0},"262":{"tf":1.0}}}},"df":0,"docs":{}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":2,"docs":{"165":{"tf":1.0},"209":{"tf":1.0}}}},"s":{"df":0,"docs":{},"i":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"237":{"tf":1.0}}}},"df":0,"docs":{}}}}},"r":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"m":{"a":{"df":1,"docs":{"264":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{},"e":{"df":2,"docs":{"171":{"tf":1.0},"172":{"tf":1.0}},"f":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"232":{"tf":1.0}}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"24":{"tf":1.0}}}}}}}}}}},"o":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"df":9,"docs":{"136":{"tf":1.0},"140":{"tf":1.0},"149":{"tf":1.0},"161":{"tf":1.0},"175":{"tf":1.0},"177":{"tf":1.0},"187":{"tf":1.0},"203":{"tf":1.0},"205":{"tf":1.0}}}}}},"c":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":1,"docs":{"265":{"tf":1.0}}}}}},"df":5,"docs":{"144":{"tf":1.0},"153":{"tf":1.0},"168":{"tf":1.0},"181":{"tf":1.0},"211":{"tf":1.0}},"j":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"t":{"df":1,"docs":{"5":{"tf":1.0}}}},"df":0,"docs":{}}},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":1,"docs":{"273":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"o":{"b":{"df":0,"docs":{},"u":{"df":0,"docs":{},"f":{"df":1,"docs":{"106":{"tf":1.0}}}}},"df":0,"docs":{}}}}},"u":{"b":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"c":{"df":1,"docs":{"107":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":5,"docs":{"113":{"tf":1.0},"16":{"tf":1.0},"18":{"tf":1.0},"22":{"tf":1.0},"274":{"tf":1.0}}}}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"l":{"df":1,"docs":{"7":{"tf":1.0}}}},"r":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"s":{"df":1,"docs":{"124":{"tf":1.0}}}}}},"t":{"df":1,"docs":{"61":{"tf":1.0}}}}},"r":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":1,"docs":{"216":{"tf":1.0}}}}},"df":0,"docs":{},"e":{"a":{"d":{"df":1,"docs":{"174":{"tf":1.0}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"d":{"df":1,"docs":{"135":{"tf":1.0}}},"df":0,"docs":{}}},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"t":{"df":2,"docs":{"157":{"tf":1.0},"158":{"tf":1.0}}}},"df":0,"docs":{}}}},"d":{"df":0,"docs":{},"u":{"c":{"df":1,"docs":{"145":{"tf":1.0}}},"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"60":{"tf":1.0}}},"df":0,"docs":{}}}},"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":8,"docs":{"183":{"tf":1.0},"265":{"tf":1.0},"267":{"tf":1.0},"268":{"tf":1.4142135623730951},"269":{"tf":1.4142135623730951},"270":{"tf":1.0},"271":{"tf":1.0},"273":{"tf":1.0}}}},"df":0,"docs":{}}},"m":{"a":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"145":{"tf":1.0}}}}},"df":0,"docs":{},"o":{"df":0,"docs":{},"t":{"df":4,"docs":{"186":{"tf":1.0},"192":{"tf":1.0},"193":{"tf":1.0},"197":{"tf":1.0}}},"v":{"df":1,"docs":{"106":{"tf":1.0}}}}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"4":{"tf":1.0}}}},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":1,"docs":{"253":{"tf":1.0}}}}}}}}}},"q":{"df":0,"docs":{},"u":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":1,"docs":{"7":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"r":{"df":4,"docs":{"145":{"tf":1.0},"188":{"tf":1.0},"189":{"tf":1.0},"190":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"89":{"tf":1.0}}}},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":0,"docs":{},"s":{"df":1,"docs":{"226":{"tf":1.0}}}}}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"u":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"105":{"tf":1.0}}}}}},"df":0,"docs":{}}}},"u":{"df":0,"docs":{},"l":{"df":0,"docs":{},"t":{"df":1,"docs":{"113":{"tf":1.0}}}}}},"t":{"df":0,"docs":{},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"v":{"df":1,"docs":{"184":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"r":{"df":0,"docs":{},"n":{"df":1,"docs":{"159":{"tf":1.0}}}}}},"v":{"df":0,"docs":{},"i":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":1,"docs":{"8":{"tf":1.0}}}}}}},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"h":{"df":1,"docs":{"224":{"tf":1.0}}}}}},"u":{"df":0,"docs":{},"n":{"df":8,"docs":{"126":{"tf":1.0},"127":{"tf":1.0},"176":{"tf":1.4142135623730951},"184":{"tf":1.0},"204":{"tf":1.0},"216":{"tf":1.0},"30":{"tf":1.0},"35":{"tf":1.0}}},"s":{"df":0,"docs":{},"t":{".":{"df":0,"docs":{},"t":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"17":{"tf":1.0}}}}}}},"df":0,"docs":{}}},"d":{"df":0,"docs":{},"o":{"c":{"df":1,"docs":{"283":{"tf":1.0}}},"df":0,"docs":{}}},"df":30,"docs":{"0":{"tf":1.0},"105":{"tf":1.0},"107":{"tf":1.0},"11":{"tf":1.0},"110":{"tf":1.0},"111":{"tf":1.0},"114":{"tf":1.0},"115":{"tf":1.0},"117":{"tf":1.0},"172":{"tf":1.0},"193":{"tf":1.0},"228":{"tf":1.0},"241":{"tf":1.0},"251":{"tf":1.0},"253":{"tf":1.0},"260":{"tf":1.0},"261":{"tf":1.0},"262":{"tf":1.0},"29":{"tf":1.0},"31":{"tf":1.0},"34":{"tf":1.0},"36":{"tf":1.0},"41":{"tf":1.0},"43":{"tf":1.0},"64":{"tf":1.0},"69":{"tf":1.0},"74":{"tf":1.0},"75":{"tf":1.0},"83":{"tf":1.0},"92":{"tf":1.0}}}}}},"s":{"c":{"df":0,"docs":{},"h":{"df":0,"docs":{},"e":{"df":0,"docs":{},"m":{"a":{"df":1,"docs":{"81":{"tf":1.0}}},"df":0,"docs":{}}}},"r":{"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":0,"docs":{},"t":{"df":3,"docs":{"269":{"tf":1.0},"30":{"tf":1.0},"35":{"tf":1.0}}}}}}},"d":{"df":0,"docs":{},"k":{"df":1,"docs":{"194":{"tf":1.0}}}},"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"275":{"tf":1.0}}}}}},"df":0,"docs":{},"n":{"d":{"df":1,"docs":{"7":{"tf":1.0}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"v":{"df":0,"docs":{},"i":{"c":{"df":18,"docs":{"0":{"tf":1.0},"118":{"tf":1.0},"120":{"tf":1.0},"125":{"tf":1.0},"15":{"tf":1.0},"252":{"tf":1.0},"259":{"tf":1.0},"262":{"tf":1.0},"265":{"tf":1.0},"274":{"tf":1.0},"3":{"tf":1.0},"30":{"tf":1.0},"35":{"tf":1.0},"39":{"tf":1.0},"40":{"tf":1.0},"52":{"tf":1.0},"68":{"tf":1.0},"9":{"tf":1.0}}},"df":0,"docs":{}}}},"t":{"df":6,"docs":{"108":{"tf":1.0},"109":{"tf":1.0},"186":{"tf":1.0},"192":{"tf":1.0},"193":{"tf":1.0},"197":{"tf":1.0}},"u":{"df":0,"docs":{},"p":{"df":2,"docs":{"13":{"tf":1.0},"175":{"tf":1.0}}}}}},"h":{"a":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":1,"docs":{"132":{"tf":1.0}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"p":{"df":1,"docs":{"251":{"tf":1.0}}}}},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"n":{"a":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":1,"docs":{"201":{"tf":1.0}}}}}},"df":0,"docs":{}}},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"l":{"df":1,"docs":{"171":{"tf":1.0}},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":1,"docs":{"130":{"tf":1.0}}}}}}}}}},"k":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"167":{"tf":1.0}}}},"df":0,"docs":{}}}},"m":{"df":0,"docs":{},"o":{"df":0,"docs":{},"k":{"df":0,"docs":{},"e":{"df":1,"docs":{"52":{"tf":1.0}}}}}},"o":{"df":0,"docs":{},"l":{"df":0,"docs":{},"u":{"df":0,"docs":{},"t":{"df":1,"docs":{"59":{"tf":1.0}}}}},"u":{"df":0,"docs":{},"r":{"c":{"df":1,"docs":{"172":{"tf":1.0}}},"df":0,"docs":{}}}},"p":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":2,"docs":{"200":{"tf":1.0},"235":{"tf":1.0}}}}},"df":0,"docs":{}}},"q":{"df":0,"docs":{},"l":{"df":1,"docs":{"62":{"tf":1.0}},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":2,"docs":{"264":{"tf":1.0},"79":{"tf":1.0}}}}}}},"r":{"c":{"df":1,"docs":{"61":{"tf":1.0}}},"df":0,"docs":{}},"t":{"a":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":1,"docs":{"249":{"tf":1.0}}}},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"257":{"tf":1.0}},"u":{"df":0,"docs":{},"p":{"df":1,"docs":{"176":{"tf":1.0}}}}}},"t":{"df":0,"docs":{},"e":{"df":2,"docs":{"229":{"tf":2.0},"239":{"tf":1.0}},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":8,"docs":{"136":{"tf":1.0},"140":{"tf":1.0},"149":{"tf":1.0},"161":{"tf":1.0},"177":{"tf":1.0},"187":{"tf":1.0},"203":{"tf":1.0},"205":{"tf":1.0}}}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"p":{"df":1,"docs":{"117":{"tf":1.0}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"e":{"df":4,"docs":{"130":{"tf":1.0},"131":{"tf":1.0},"232":{"tf":1.0},"78":{"tf":1.0}},"s":{"df":0,"docs":{},"y":{"df":0,"docs":{},"n":{"c":{"a":{"df":0,"docs":{},"s":{"df":0,"docs":{},"s":{"df":0,"docs":{},"o":{"c":{"df":0,"docs":{},"i":{"df":1,"docs":{"87":{"tf":1.0}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}}}},"r":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":1,"docs":{"257":{"tf":1.0}}}}}}},"df":0,"docs":{}},"u":{"c":{"df":0,"docs":{},"t":{"df":0,"docs":{},"u":{"df":0,"docs":{},"r":{"df":3,"docs":{"72":{"tf":1.0},"76":{"tf":1.0},"83":{"tf":1.0}}}}}},"df":0,"docs":{}}},"u":{"d":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":1,"docs":{"117":{"tf":1.0}}}}},"df":0,"docs":{}}},"u":{"df":0,"docs":{},"g":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":2,"docs":{"214":{"tf":1.0},"217":{"tf":1.0}}}}}}},"i":{"df":0,"docs":{},"t":{"df":1,"docs":{"49":{"tf":1.0}}}},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"t":{"df":1,"docs":{"22":{"tf":1.0}}}}}}}},"w":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":17,"docs":{"109":{"tf":1.0},"160":{"tf":1.0},"171":{"tf":1.0},"172":{"tf":1.0},"23":{"tf":1.0},"251":{"tf":1.0},"253":{"tf":1.0},"29":{"tf":1.0},"31":{"tf":1.0},"32":{"tf":1.0},"34":{"tf":1.0},"36":{"tf":1.0},"46":{"tf":1.0},"71":{"tf":1.0},"73":{"tf":1.0},"74":{"tf":1.0},"95":{"tf":1.0}}}}}},"y":{"df":0,"docs":{},"n":{"c":{"_":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"s":{"df":0,"docs":{},"h":{"df":1,"docs":{"88":{"tf":1.0}}}}}}}}},"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"75":{"tf":1.0}}}},"df":0,"docs":{}},"c":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"c":{"df":0,"docs":{},"o":{"df":0,"docs":{},"u":{"df":0,"docs":{},"n":{"df":0,"docs":{},"t":{"df":1,"docs":{"250":{"tf":1.0}}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}}},"df":15,"docs":{"225":{"tf":1.0},"226":{"tf":1.0},"234":{"tf":1.0},"243":{"tf":1.0},"245":{"tf":1.0},"246":{"tf":1.0},"254":{"tf":1.0},"255":{"tf":1.0},"256":{"tf":1.0},"257":{"tf":1.0},"258":{"tf":1.0},"48":{"tf":1.0},"84":{"tf":1.0},"85":{"tf":1.0},"86":{"tf":1.4142135623730951}}},"df":0,"docs":{}}}},"t":{"a":{"b":{"df":0,"docs":{},"l":{"df":1,"docs":{"248":{"tf":1.0}}}},"df":0,"docs":{},"r":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":1,"docs":{"103":{"tf":1.0}}}}}}},"df":0,"docs":{},"e":{"df":0,"docs":{},"s":{"df":0,"docs":{},"t":{"df":13,"docs":{"124":{"tf":1.0},"223":{"tf":1.0},"23":{"tf":1.0},"32":{"tf":1.0},"41":{"tf":1.0},"42":{"tf":1.0},"47":{"tf":1.0},"48":{"tf":1.0},"49":{"tf":1.0},"50":{"tf":1.0},"52":{"tf":1.0},"57":{"tf":1.4142135623730951},"60":{"tf":1.0}},"s":{"/":{"b":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"c":{"df":0,"docs":{},"h":{"df":1,"docs":{"61":{"tf":1.0}}}},"df":0,"docs":{}}}},"df":0,"docs":{}},"df":0,"docs":{}}}}},"h":{"df":0,"docs":{},"i":{"df":0,"docs":{},"n":{"df":0,"docs":{},"g":{"df":1,"docs":{"240":{"tf":1.0}}}}}},"i":{"df":0,"docs":{},"e":{"df":1,"docs":{"240":{"tf":1.0}}},"m":{"df":0,"docs":{},"e":{"df":2,"docs":{"10":{"tf":1.0},"57":{"tf":1.0}}}},"p":{"df":1,"docs":{"282":{"tf":1.0}}}},"o":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"t":{"df":0,"docs":{},"h":{"df":1,"docs":{"240":{"tf":1.0}}}}}}},"r":{"a":{"d":{"df":0,"docs":{},"e":{"df":0,"docs":{},"o":{"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":1,"docs":{"116":{"tf":1.0}}}}}}},"df":0,"docs":{}},"df":0,"docs":{},"i":{"df":0,"docs":{},"g":{"df":0,"docs":{},"g":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":1,"docs":{"82":{"tf":1.0}}}}}}}},"y":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":2,"docs":{"155":{"tf":1.0},"156":{"tf":1.0}}}}}},"u":{"d":{"df":0,"docs":{},"l":{"df":1,"docs":{"104":{"tf":1.0}}}},"df":0,"docs":{},"n":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"f":{"df":0,"docs":{},"i":{"df":4,"docs":{"101":{"tf":1.0},"102":{"tf":1.0},"105":{"tf":1.0},"128":{"tf":1.0}}}}},"t":{"df":2,"docs":{"223":{"tf":1.0},"42":{"tf":1.0}}}},"r":{"df":0,"docs":{},"e":{"df":0,"docs":{},"l":{"df":0,"docs":{},"e":{"a":{"df":0,"docs":{},"s":{"df":1,"docs":{"124":{"tf":1.0}}}},"df":0,"docs":{}}}}}},"p":{"d":{"a":{"df":0,"docs":{},"t":{"df":4,"docs":{"122":{"tf":1.0},"139":{"tf":1.0},"279":{"tf":1.0},"280":{"tf":1.0}}}},"df":0,"docs":{}},"df":2,"docs":{"108":{"tf":1.0},"109":{"tf":1.0}},"g":{"df":0,"docs":{},"r":{"a":{"d":{"df":1,"docs":{"278":{"tf":1.0}}},"df":0,"docs":{}},"df":0,"docs":{}}},"l":{"df":0,"docs":{},"i":{"df":0,"docs":{},"f":{"df":0,"docs":{},"t":{"df":1,"docs":{"270":{"tf":1.0}}}}}}},"s":{"a":{"df":0,"docs":{},"g":{"df":1,"docs":{"264":{"tf":1.0}}}},"df":14,"docs":{"101":{"tf":1.0},"116":{"tf":1.0},"135":{"tf":1.0},"16":{"tf":1.0},"170":{"tf":1.0},"18":{"tf":1.0},"19":{"tf":1.0},"197":{"tf":1.0},"198":{"tf":1.0},"20":{"tf":1.0},"25":{"tf":1.0},"27":{"tf":1.0},"37":{"tf":1.0},"79":{"tf":1.0}},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"215":{"tf":1.0},"52":{"tf":1.0}}}}},"t":{"df":0,"docs":{},"i":{"df":0,"docs":{},"l":{"df":1,"docs":{"237":{"tf":1.0}}}}}},"v":{"df":0,"docs":{},"e":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":3,"docs":{"120":{"tf":1.0},"121":{"tf":1.0},"124":{"tf":1.0}}}}},"df":0,"docs":{}},"r":{"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"o":{"df":0,"docs":{},"n":{"df":5,"docs":{"124":{"tf":1.0},"260":{"tf":1.0},"262":{"tf":1.0},"279":{"tf":1.0},"37":{"tf":1.0}}}}},"u":{"df":1,"docs":{"88":{"tf":1.0}}}}}},"i":{"a":{"df":2,"docs":{"13":{"tf":1.0},"269":{"tf":1.0}}},"df":0,"docs":{},"s":{"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":3,"docs":{"202":{"tf":1.0},"213":{"tf":1.0},"216":{"tf":1.0}}}}}},"s":{"df":1,"docs":{"232":{"tf":2.0}}}},"w":{"df":0,"docs":{},"h":{"a":{"df":0,"docs":{},"t":{"'":{"df":1,"docs":{"89":{"tf":1.0}}},"df":0,"docs":{},"’":{"df":2,"docs":{"86":{"tf":1.0},"87":{"tf":1.0}}}}},"df":0,"docs":{}},"i":{"df":0,"docs":{},"n":{"d":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":1,"docs":{"13":{"tf":1.0}},"s":{"/":{"df":0,"docs":{},"w":{"df":0,"docs":{},"s":{"df":0,"docs":{},"l":{"df":1,"docs":{"19":{"tf":1.0}}}}}},"df":0,"docs":{}}}}},"df":0,"docs":{}},"p":{"df":0,"docs":{},"e":{"df":1,"docs":{"89":{"tf":1.0}}}}},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":1,"docs":{"239":{"tf":1.0}},"f":{"df":0,"docs":{},"l":{"df":0,"docs":{},"o":{"df":0,"docs":{},"w":{"df":3,"docs":{"18":{"tf":1.0},"20":{"tf":1.0},"22":{"tf":1.0}}}}}}},"s":{"df":1,"docs":{"57":{"tf":1.0}}}}},"r":{"a":{"df":0,"docs":{},"p":{"df":0,"docs":{},"p":{"df":0,"docs":{},"e":{"df":0,"docs":{},"r":{"df":2,"docs":{"108":{"tf":1.0},"109":{"tf":1.0}}}}}}},"df":0,"docs":{},"i":{"df":0,"docs":{},"t":{"df":0,"docs":{},"e":{"df":2,"docs":{"104":{"tf":1.0},"196":{"tf":1.0}}}}}},"s":{"df":0,"docs":{},"l":{"df":1,"docs":{"13":{"tf":1.0}}}}},"x":{"c":{"df":0,"docs":{},"f":{"df":0,"docs":{},"r":{"a":{"df":0,"docs":{},"m":{"df":0,"docs":{},"e":{"df":0,"docs":{},"w":{"df":0,"docs":{},"o":{"df":0,"docs":{},"r":{"df":0,"docs":{},"k":{"df":6,"docs":{"170":{"tf":1.0},"252":{"tf":1.0},"28":{"tf":1.0},"29":{"tf":1.0},"33":{"tf":1.0},"34":{"tf":1.0}}}}}}}}},"df":0,"docs":{}}}},"df":0,"docs":{}}}}},"lang":"English","pipeline":["trimmer","stopWordFilter","stemmer"],"ref":"id","version":"0.9.5"},"results_options":{"limit_results":30,"teaser_word_count":30},"search_options":{"bool":"OR","expand":true,"fields":{"body":{"boost":1},"breadcrumbs":{"boost":1},"title":{"boost":2}}}} \ No newline at end of file diff --git a/book/shared/a-s.css b/book/shared/a-s.css new file mode 100644 index 0000000000..0eacd2aff0 --- /dev/null +++ b/book/shared/a-s.css @@ -0,0 +1,169 @@ +/* Style the tab */ +.tabbar { + overflow: hidden; + border: 1px solid #ccc; + background-color: #f1f1f1; +} + +/* Style the buttons that are used to open the tab content */ +.tabbar button { + background-color: inherit; + float: left; + border: none; + outline: none; + cursor: pointer; + padding: 14px 16px; + transition: 0.3s; +} + +/* Change background color of buttons on hover */ +.tabbar button:hover { + background-color: #ddd; +} + +/* Create an active/current tablink class */ +.tabbar button.active { + background-color: #ccc; +} + +/* The container that holds all of the tab contents */ +.tabcontents { + display: flex; +} + +/* The container for each individual language */ +.tab { + display: none; + width: 100%; + border: 1px solid #ccc; + border-top: none; + padding: 6px 12px; +} + +.tab.active { + display: block; +} + +/* The footer with the "Open on GitHub" link */ +footer#open-on-gh { + font-size: 0.8em; + text-align: center; + border-top: 1px solid black; + padding: 5px 0; +} + +/* Distribution simulator styles */ + +#simulator-container { + display: flex; + flex-wrap: wrap; + overflow: hidden; +} + +#simulator-container h3 { + margin-top: 20px; +} + +#simulator-container .input-group { + width: 100%; +} + +#simulator-container .input-group label { + display: block; + font-weight: bold; + font-size: 14px; + margin-bottom: 7px; +} + +#simulator-container .input-group input, +#simulator-container .input-group select, +#custom-data-modal textarea { + display: block; + width: 100%; + padding: 5px; + margin-bottom: 10px; + border-radius: 3px; + border: 1px solid #e0e0e0; + box-sizing: border-box; +} + +#histogram-props, +#data-options { + width: 50%; + box-sizing: border-box; +} + +#data-options { + padding-right: 50px; +} + +#data-options .input-group { + margin-bottom: 10px; +} + +#data-options .input-group:first-of-type { + margin-top: 20px; +} + +#data-options .input-group:last-of-type { + margin-bottom: 0; +} + +#data-options .input-group label { + display: inline-block; +} + +#data-options .input-group input { + display: inline; + width: auto; +} + +#histogram-chart-container { + width: 100%; + padding: 30px; + border: 1px solid #e0e0e0; + margin: 30px 0; + overflow: hidden; + position: relative; +} + +#histogram-chart { + margin-top: 50px; + width: 100%; +} + +#histogram-chart-legend { + font-size: 14px; + text-align: center; + width: 100%; +} + +#histogram-functional-props, +#histogram-non-functional-props { + display: none; +} + +#custom-data-modal-overlay { + background-color: rgba(0, 0, 0, .5); + position: fixed; + width: 100vw; + height: 100vh; + top: 0; + left: 0; + z-index: 999; + display: none; +} + +#custom-data-modal { + width: 50%; + background-color: white; + border-radius: 5px; + position: relative; + top: 15%; + left: 25%; + padding: 50px; +} + +.hide { + display: none !important; +} diff --git a/book/shared/mermaid-init.js b/book/shared/mermaid-init.js new file mode 100644 index 0000000000..4da13c04b0 --- /dev/null +++ b/book/shared/mermaid-init.js @@ -0,0 +1,4 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +mermaid.initialize({startOnLoad:true}); diff --git a/book/shared/mermaid.css b/book/shared/mermaid.css new file mode 100644 index 0000000000..13a8ce8776 --- /dev/null +++ b/book/shared/mermaid.css @@ -0,0 +1,356 @@ +/* +Licensed under the MIT License (MIT). +Copyright (c) 2014 - 2018 Knut Sveidqvist +Full license: https://github.com/mermaid-js/mermaid/blob/develop/LICENSE +*/ +/* Flowchart variables */ +/* Sequence Diagram variables */ +/* Gantt chart variables */ +.mermaid .mermaid .label { + color: #333; +} +.mermaid .node rect, +.mermaid .node circle, +.mermaid .node ellipse, +.mermaid .node polygon { + fill: #ECECFF; + stroke: #CCCCFF; + stroke-width: 1px; +} +.mermaid .arrowheadPath { + fill: #333333; +} +.mermaid .edgePath .path { + stroke: #333333; +} +.mermaid .edgeLabel { + background-color: #e8e8e8; +} +.mermaid .cluster rect { + fill: #ffffde !important; + rx: 4 !important; + stroke: #aaaa33 !important; + stroke-width: 1px !important; +} +.mermaid .cluster text { + fill: #333; +} +.mermaid .actor { + stroke: #CCCCFF; + fill: #ECECFF; +} +.mermaid text.actor { + fill: black; + stroke: none; +} +.mermaid .actor-line { + stroke: grey; +} +.mermaid .messageLine0 { + stroke-width: 1.5; + stroke-dasharray: "2 2"; + marker-end: "url(#arrowhead)"; + stroke: #333; +} +.mermaid .messageLine1 { + stroke-width: 1.5; + stroke-dasharray: "2 2"; + stroke: #333; +} +.mermaid #arrowhead { + fill: #333; +} +.mermaid #crosshead path { + fill: #333 !important; + stroke: #333 !important; +} +.mermaid .messageText { + fill: #333; + stroke: none; +} +.mermaid .labelBox { + stroke: #CCCCFF; + fill: #ECECFF; +} +.mermaid .labelText { + fill: black; + stroke: none; +} +.mermaid .loopText { + fill: black; + stroke: none; +} +.mermaid .loopLine { + stroke-width: 2; + stroke-dasharray: "2 2"; + marker-end: "url(#arrowhead)"; + stroke: #CCCCFF; +} +.mermaid .note { + stroke: #aaaa33; + fill: #fff5ad; +} +.mermaid .noteText { + fill: black; + stroke: none; + font-family: 'trebuchet ms', verdana, arial; + font-size: 14px; +} +/** Section styling */ +.mermaid .section { + stroke: none; + opacity: 0.2; +} +.mermaid .section0 { + fill: rgba(102, 102, 255, 0.49); +} +.mermaid .section2 { + fill: #fff400; +} +.mermaid .section1, +.mermaid .section3 { + fill: white; + opacity: 0.2; +} +.mermaid .sectionTitle0 { + fill: #333; +} +.mermaid .sectionTitle1 { + fill: #333; +} +.mermaid .sectionTitle2 { + fill: #333; +} +.mermaid .sectionTitle3 { + fill: #333; +} +.mermaid .sectionTitle { + text-anchor: start; + font-size: 11px; + text-height: 14px; +} +/* Grid and axis */ +.mermaid .grid .tick { + stroke: lightgrey; + opacity: 0.3; + shape-rendering: crispEdges; +} +.mermaid .grid path { + stroke-width: 0; +} +/* Today line */ +.mermaid .today { + fill: none; + stroke: red; + stroke-width: 2px; +} +/* Task styling */ +/* Default task */ +.mermaid .task { + stroke-width: 2; +} +.mermaid .taskText { + text-anchor: middle; + font-size: 11px; +} +.mermaid .taskTextOutsideRight { + fill: black; + text-anchor: start; + font-size: 11px; +} +.mermaid .taskTextOutsideLeft { + fill: black; + text-anchor: end; + font-size: 11px; +} +/* Specific task settings for the sections*/ +.mermaid .taskText0, +.mermaid .taskText1, +.mermaid .taskText2, +.mermaid .taskText3 { + fill: white; +} +.mermaid .task0, +.mermaid .task1, +.mermaid .task2, +.mermaid .task3 { + fill: #8a90dd; + stroke: #534fbc; +} +.mermaid .taskTextOutside0, +.mermaid .taskTextOutside2 { + fill: black; +} +.mermaid .taskTextOutside1, +.mermaid .taskTextOutside3 { + fill: black; +} +/* Active task */ +.mermaid .active0, +.mermaid .active1, +.mermaid .active2, +.mermaid .active3 { + fill: #bfc7ff; + stroke: #534fbc; +} +.mermaid .activeText0, +.mermaid .activeText1, +.mermaid .activeText2, +.mermaid .activeText3 { + fill: black !important; +} +/* Completed task */ +.mermaid .done0, +.mermaid .done1, +.mermaid .done2, +.mermaid .done3 { + stroke: grey; + fill: lightgrey; + stroke-width: 2; +} +.mermaid .doneText0, +.mermaid .doneText1, +.mermaid .doneText2, +.mermaid .doneText3 { + fill: black !important; +} +/* Tasks on the critical line */ +.mermaid .crit0, +.mermaid .crit1, +.mermaid .crit2, +.mermaid .crit3 { + stroke: #ff8888; + fill: red; + stroke-width: 2; +} +.mermaid .activeCrit0, +.mermaid .activeCrit1, +.mermaid .activeCrit2, +.mermaid .activeCrit3 { + stroke: #ff8888; + fill: #bfc7ff; + stroke-width: 2; +} +.mermaid .doneCrit0, +.mermaid .doneCrit1, +.mermaid .doneCrit2, +.mermaid .doneCrit3 { + stroke: #ff8888; + fill: lightgrey; + stroke-width: 2; + cursor: pointer; + shape-rendering: crispEdges; +} +.mermaid .doneCritText0, +.mermaid .doneCritText1, +.mermaid .doneCritText2, +.mermaid .doneCritText3 { + fill: black !important; +} +.mermaid .activeCritText0, +.mermaid .activeCritText1, +.mermaid .activeCritText2, +.mermaid .activeCritText3 { + fill: black !important; +} +.mermaid .titleText { + text-anchor: middle; + font-size: 18px; + fill: black; +} +.mermaid g.classGroup text { + fill: #9370DB; + stroke: none; + font-family: 'trebuchet ms', verdana, arial; + font-size: 10px; +} +.mermaid g.classGroup rect { + fill: #ECECFF; + stroke: #9370DB; +} +.mermaid g.classGroup line { + stroke: #9370DB; + stroke-width: 1; +} +.mermaid svg .classLabel .box { + stroke: none; + stroke-width: 0; + fill: #ECECFF; + opacity: 0.5; +} +.mermaid svg .classLabel .label { + fill: #9370DB; + font-size: 10px; +} +.mermaid .relation { + stroke: #9370DB; + stroke-width: 1; + fill: none; +} +.mermaid .composition { + fill: #9370DB; + stroke: #9370DB; + stroke-width: 1; +} +.mermaid #compositionStart { + fill: #9370DB; + stroke: #9370DB; + stroke-width: 1; +} +.mermaid #compositionEnd { + fill: #9370DB; + stroke: #9370DB; + stroke-width: 1; +} +.mermaid .aggregation { + fill: #ECECFF; + stroke: #9370DB; + stroke-width: 1; +} +.mermaid #aggregationStart { + fill: #ECECFF; + stroke: #9370DB; + stroke-width: 1; +} +.mermaid #aggregationEnd { + fill: #ECECFF; + stroke: #9370DB; + stroke-width: 1; +} +.mermaid #dependencyStart { + fill: #9370DB; + stroke: #9370DB; + stroke-width: 1; +} +.mermaid #dependencyEnd { + fill: #9370DB; + stroke: #9370DB; + stroke-width: 1; +} +.mermaid #extensionStart { + fill: #9370DB; + stroke: #9370DB; + stroke-width: 1; +} +.mermaid #extensionEnd { + fill: #9370DB; + stroke: #9370DB; + stroke-width: 1; +} +.mermaid .node text { + font-family: 'trebuchet ms', verdana, arial; + font-size: 14px; +} +.mermaid div.mermaidTooltip { + position: absolute; + text-align: center; + max-width: 200px; + padding: 2px; + font-family: 'trebuchet ms', verdana, arial; + font-size: 12px; + background: #ffffde; + border: 1px solid #aaaa33; + border-radius: 2px; + pointer-events: none; + z-index: 100; +} diff --git a/book/shared/mermaid.min.js b/book/shared/mermaid.min.js new file mode 100644 index 0000000000..0a72f3373b --- /dev/null +++ b/book/shared/mermaid.min.js @@ -0,0 +1,49 @@ +!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.mermaid=e():t.mermaid=e()}(window,(function(){return function(t){var e={};function n(r){if(e[r])return e[r].exports;var i=e[r]={i:r,l:!1,exports:{}};return t[r].call(i.exports,i,i.exports,n),i.l=!0,i.exports}return n.m=t,n.c=e,n.d=function(t,e,r){n.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:r})},n.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},n.t=function(t,e){if(1&e&&(t=n(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var i in t)n.d(r,i,function(e){return t[e]}.bind(null,i));return r},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s=509)}([function(t,e,n){"use strict";n.r(e);var r=function(t,e){return te?1:t>=e?0:NaN},i=function(t){var e;return 1===t.length&&(e=t,t=function(t,n){return r(e(t),n)}),{left:function(e,n,r,i){for(null==r&&(r=0),null==i&&(i=e.length);r>>1;t(e[o],n)<0?r=o+1:i=o}return r},right:function(e,n,r,i){for(null==r&&(r=0),null==i&&(i=e.length);r>>1;t(e[o],n)>0?i=o:r=o+1}return r}}};var o=i(r),a=o.right,s=o.left,u=a,c=function(t,e){null==e&&(e=f);for(var n=0,r=t.length-1,i=t[0],o=new Array(r<0?0:r);nt?1:e>=t?0:NaN},d=function(t){return null===t?NaN:+t},p=function(t,e){var n,r,i=t.length,o=0,a=-1,s=0,u=0;if(null==e)for(;++a1)return u/(o-1)},g=function(t,e){var n=p(t,e);return n?Math.sqrt(n):n},y=function(t,e){var n,r,i,o=t.length,a=-1;if(null==e){for(;++a=n)for(r=i=n;++an&&(r=n),i=n)for(r=i=n;++an&&(r=n),i0)return[t];if((r=e0)for(t=Math.ceil(t/a),e=Math.floor(e/a),o=new Array(i=Math.ceil(e-t+1));++s=0?(o>=k?10:o>=E?5:o>=A?2:1)*Math.pow(10,i):-Math.pow(10,-i)/(o>=k?10:o>=E?5:o>=A?2:1)}function M(t,e,n){var r=Math.abs(e-t)/Math.max(0,n),i=Math.pow(10,Math.floor(Math.log(r)/Math.LN10)),o=r/i;return o>=k?i*=10:o>=E?i*=5:o>=A&&(i*=2),el;)h.pop(),--d;var p,g=new Array(d+1);for(i=0;i<=d;++i)(p=g[i]=[]).x0=i>0?h[i-1]:f,p.x1=i=1)return+n(t[r-1],r-1,t);var r,i=(r-1)*e,o=Math.floor(i),a=+n(t[o],o,t);return a+(+n(t[o+1],o+1,t)-a)*(i-o)}},R=function(t,e,n){return t=v.call(t,d).sort(r),Math.ceil((n-e)/(2*(O(t,.75)-O(t,.25))*Math.pow(t.length,-1/3)))},I=function(t,e,n){return Math.ceil((n-e)/(3.5*g(t)*Math.pow(t.length,-1/3)))},N=function(t,e){var n,r,i=t.length,o=-1;if(null==e){for(;++o=n)for(r=n;++or&&(r=n)}else for(;++o=n)for(r=n;++or&&(r=n);return r},B=function(t,e){var n,r=t.length,i=r,o=-1,a=0;if(null==e)for(;++o=0;)for(e=(r=t[i]).length;--e>=0;)n[--a]=r[e];return n},F=function(t,e){var n,r,i=t.length,o=-1;if(null==e){for(;++o=n)for(r=n;++on&&(r=n)}else for(;++o=n)for(r=n;++on&&(r=n);return r},q=function(t,e){for(var n=e.length,r=new Array(n);n--;)r[n]=t[e[n]];return r},j=function(t,e){if(n=t.length){var n,i,o=0,a=0,s=t[a];for(null==e&&(e=r);++o=0&&(n=t.slice(r+1),t=t.slice(0,r)),t&&!e.hasOwnProperty(t))throw new Error("unknown type: "+t);return{type:t,name:n}}))}function dt(t,e){for(var n,r=0,i=t.length;r0)for(var n,r,i=new Array(n),o=0;o=0&&"xmlns"!==(e=t.slice(0,n))&&(t=t.slice(n+1)),bt.hasOwnProperty(e)?{space:bt[e],local:t}:t};function vt(t){return function(){var e=this.ownerDocument,n=this.namespaceURI;return n===yt&&e.documentElement.namespaceURI===yt?e.createElement(t):e.createElementNS(n,t)}}function _t(t){return function(){return this.ownerDocument.createElementNS(t.space,t.local)}}var wt=function(t){var e=mt(t);return(e.local?_t:vt)(e)};function xt(){}var kt=function(t){return null==t?xt:function(){return this.querySelector(t)}};function Et(){return[]}var At=function(t){return null==t?Et:function(){return this.querySelectorAll(t)}},St=function(t){return function(){return this.matches(t)}},Tt=function(t){return new Array(t.length)};function Mt(t,e){this.ownerDocument=t.ownerDocument,this.namespaceURI=t.namespaceURI,this._next=null,this._parent=t,this.__data__=e}Mt.prototype={constructor:Mt,appendChild:function(t){return this._parent.insertBefore(t,this._next)},insertBefore:function(t,e){return this._parent.insertBefore(t,e)},querySelector:function(t){return this._parent.querySelector(t)},querySelectorAll:function(t){return this._parent.querySelectorAll(t)}};var Dt="$";function Ct(t,e,n,r,i,o){for(var a,s=0,u=e.length,c=o.length;se?1:t>=e?0:NaN}function It(t){return function(){this.removeAttribute(t)}}function Nt(t){return function(){this.removeAttributeNS(t.space,t.local)}}function Bt(t,e){return function(){this.setAttribute(t,e)}}function Lt(t,e){return function(){this.setAttributeNS(t.space,t.local,e)}}function Pt(t,e){return function(){var n=e.apply(this,arguments);null==n?this.removeAttribute(t):this.setAttribute(t,n)}}function Ft(t,e){return function(){var n=e.apply(this,arguments);null==n?this.removeAttributeNS(t.space,t.local):this.setAttributeNS(t.space,t.local,n)}}var qt=function(t){return t.ownerDocument&&t.ownerDocument.defaultView||t.document&&t||t.defaultView};function jt(t){return function(){this.style.removeProperty(t)}}function Ut(t,e,n){return function(){this.style.setProperty(t,e,n)}}function zt(t,e,n){return function(){var r=e.apply(this,arguments);null==r?this.style.removeProperty(t):this.style.setProperty(t,r,n)}}function Yt(t,e){return t.style.getPropertyValue(e)||qt(t).getComputedStyle(t,null).getPropertyValue(e)}function Vt(t){return function(){delete this[t]}}function Ht(t,e){return function(){this[t]=e}}function $t(t,e){return function(){var n=e.apply(this,arguments);null==n?delete this[t]:this[t]=n}}function Gt(t){return t.trim().split(/^|\s+/)}function Wt(t){return t.classList||new Kt(t)}function Kt(t){this._node=t,this._names=Gt(t.getAttribute("class")||"")}function Xt(t,e){for(var n=Wt(t),r=-1,i=e.length;++r=0&&(this._names.splice(e,1),this._node.setAttribute("class",this._names.join(" ")))},contains:function(t){return this._names.indexOf(t)>=0}};function ee(){this.textContent=""}function ne(t){return function(){this.textContent=t}}function re(t){return function(){var e=t.apply(this,arguments);this.textContent=null==e?"":e}}function ie(){this.innerHTML=""}function oe(t){return function(){this.innerHTML=t}}function ae(t){return function(){var e=t.apply(this,arguments);this.innerHTML=null==e?"":e}}function se(){this.nextSibling&&this.parentNode.appendChild(this)}function ue(){this.previousSibling&&this.parentNode.insertBefore(this,this.parentNode.firstChild)}function ce(){return null}function fe(){var t=this.parentNode;t&&t.removeChild(this)}function le(){return this.parentNode.insertBefore(this.cloneNode(!1),this.nextSibling)}function he(){return this.parentNode.insertBefore(this.cloneNode(!0),this.nextSibling)}var de={},pe=null;"undefined"!=typeof document&&("onmouseenter"in document.documentElement||(de={mouseenter:"mouseover",mouseleave:"mouseout"}));function ge(t,e,n){return t=ye(t,e,n),function(e){var n=e.relatedTarget;n&&(n===this||8&n.compareDocumentPosition(this))||t.call(this,e)}}function ye(t,e,n){return function(r){var i=pe;pe=r;try{t.call(this,this.__data__,e,n)}finally{pe=i}}}function be(t){return t.trim().split(/^|\s+/).map((function(t){var e="",n=t.indexOf(".");return n>=0&&(e=t.slice(n+1),t=t.slice(0,n)),{type:t,name:e}}))}function me(t){return function(){var e=this.__on;if(e){for(var n,r=0,i=-1,o=e.length;r=w&&(w=_+1);!(v=b[w])&&++w=0;)(r=i[o])&&(a&&4^r.compareDocumentPosition(a)&&a.parentNode.insertBefore(r,a),a=r);return this},sort:function(t){function e(e,n){return e&&n?t(e.__data__,n.__data__):!e-!n}t||(t=Rt);for(var n=this._groups,r=n.length,i=new Array(r),o=0;o1?this.each((null==e?jt:"function"==typeof e?zt:Ut)(t,e,null==n?"":n)):Yt(this.node(),t)},property:function(t,e){return arguments.length>1?this.each((null==e?Vt:"function"==typeof e?$t:Ht)(t,e)):this.node()[t]},classed:function(t,e){var n=Gt(t+"");if(arguments.length<2){for(var r=Wt(this.node()),i=-1,o=n.length;++il}u.mouse("drag")}function g(){Me(pe.view).on("mousemove.drag mouseup.drag",null),ze(pe.view,n),je(),u.mouse("end")}function y(){if(i.apply(this,arguments)){var t,e,n=pe.changedTouches,r=o.apply(this,arguments),a=n.length;for(t=0;t>8&15|e>>4&240,e>>4&15|240&e,(15&e)<<4|15&e,1):8===n?new mn(e>>24&255,e>>16&255,e>>8&255,(255&e)/255):4===n?new mn(e>>12&15|e>>8&240,e>>8&15|e>>4&240,e>>4&15|240&e,((15&e)<<4|15&e)/255):null):(e=rn.exec(t))?new mn(e[1],e[2],e[3],1):(e=on.exec(t))?new mn(255*e[1]/100,255*e[2]/100,255*e[3]/100,1):(e=an.exec(t))?gn(e[1],e[2],e[3],e[4]):(e=sn.exec(t))?gn(255*e[1]/100,255*e[2]/100,255*e[3]/100,e[4]):(e=un.exec(t))?xn(e[1],e[2]/100,e[3]/100,1):(e=cn.exec(t))?xn(e[1],e[2]/100,e[3]/100,e[4]):fn.hasOwnProperty(t)?pn(fn[t]):"transparent"===t?new mn(NaN,NaN,NaN,0):null}function pn(t){return new mn(t>>16&255,t>>8&255,255&t,1)}function gn(t,e,n,r){return r<=0&&(t=e=n=NaN),new mn(t,e,n,r)}function yn(t){return t instanceof Je||(t=dn(t)),t?new mn((t=t.rgb()).r,t.g,t.b,t.opacity):new mn}function bn(t,e,n,r){return 1===arguments.length?yn(t):new mn(t,e,n,null==r?1:r)}function mn(t,e,n,r){this.r=+t,this.g=+e,this.b=+n,this.opacity=+r}function vn(){return"#"+wn(this.r)+wn(this.g)+wn(this.b)}function _n(){var t=this.opacity;return(1===(t=isNaN(t)?1:Math.max(0,Math.min(1,t)))?"rgb(":"rgba(")+Math.max(0,Math.min(255,Math.round(this.r)||0))+", "+Math.max(0,Math.min(255,Math.round(this.g)||0))+", "+Math.max(0,Math.min(255,Math.round(this.b)||0))+(1===t?")":", "+t+")")}function wn(t){return((t=Math.max(0,Math.min(255,Math.round(t)||0)))<16?"0":"")+t.toString(16)}function xn(t,e,n,r){return r<=0?t=e=n=NaN:n<=0||n>=1?t=e=NaN:e<=0&&(t=NaN),new An(t,e,n,r)}function kn(t){if(t instanceof An)return new An(t.h,t.s,t.l,t.opacity);if(t instanceof Je||(t=dn(t)),!t)return new An;if(t instanceof An)return t;var e=(t=t.rgb()).r/255,n=t.g/255,r=t.b/255,i=Math.min(e,n,r),o=Math.max(e,n,r),a=NaN,s=o-i,u=(o+i)/2;return s?(a=e===o?(n-r)/s+6*(n0&&u<1?0:a,new An(a,s,u,t.opacity)}function En(t,e,n,r){return 1===arguments.length?kn(t):new An(t,e,n,null==r?1:r)}function An(t,e,n,r){this.h=+t,this.s=+e,this.l=+n,this.opacity=+r}function Sn(t,e,n){return 255*(t<60?e+(n-e)*t/60:t<180?n:t<240?e+(n-e)*(240-t)/60:e)}function Tn(t,e,n,r,i){var o=t*t,a=o*t;return((1-3*t+3*o-a)*e+(4-6*o+3*a)*n+(1+3*t+3*o-3*a)*r+a*i)/6}Xe(Je,dn,{copy:function(t){return Object.assign(new this.constructor,this,t)},displayable:function(){return this.rgb().displayable()},hex:ln,formatHex:ln,formatHsl:function(){return kn(this).formatHsl()},formatRgb:hn,toString:hn}),Xe(mn,bn,Ze(Je,{brighter:function(t){return t=null==t?1/.7:Math.pow(1/.7,t),new mn(this.r*t,this.g*t,this.b*t,this.opacity)},darker:function(t){return t=null==t?.7:Math.pow(.7,t),new mn(this.r*t,this.g*t,this.b*t,this.opacity)},rgb:function(){return this},displayable:function(){return-.5<=this.r&&this.r<255.5&&-.5<=this.g&&this.g<255.5&&-.5<=this.b&&this.b<255.5&&0<=this.opacity&&this.opacity<=1},hex:vn,formatHex:vn,formatRgb:_n,toString:_n})),Xe(An,En,Ze(Je,{brighter:function(t){return t=null==t?1/.7:Math.pow(1/.7,t),new An(this.h,this.s,this.l*t,this.opacity)},darker:function(t){return t=null==t?.7:Math.pow(.7,t),new An(this.h,this.s,this.l*t,this.opacity)},rgb:function(){var t=this.h%360+360*(this.h<0),e=isNaN(t)||isNaN(this.s)?0:this.s,n=this.l,r=n+(n<.5?n:1-n)*e,i=2*n-r;return new mn(Sn(t>=240?t-240:t+120,i,r),Sn(t,i,r),Sn(t<120?t+240:t-120,i,r),this.opacity)},displayable:function(){return(0<=this.s&&this.s<=1||isNaN(this.s))&&0<=this.l&&this.l<=1&&0<=this.opacity&&this.opacity<=1},formatHsl:function(){var t=this.opacity;return(1===(t=isNaN(t)?1:Math.max(0,Math.min(1,t)))?"hsl(":"hsla(")+(this.h||0)+", "+100*(this.s||0)+"%, "+100*(this.l||0)+"%"+(1===t?")":", "+t+")")}}));var Mn=function(t){var e=t.length-1;return function(n){var r=n<=0?n=0:n>=1?(n=1,e-1):Math.floor(n*e),i=t[r],o=t[r+1],a=r>0?t[r-1]:2*i-o,s=r180||n<-180?n-360*Math.round(n/360):n):Cn(isNaN(t)?e:t)}function In(t){return 1==(t=+t)?Nn:function(e,n){return n-e?function(t,e,n){return t=Math.pow(t,n),e=Math.pow(e,n)-t,n=1/n,function(r){return Math.pow(t+r*e,n)}}(e,n,t):Cn(isNaN(e)?n:e)}}function Nn(t,e){var n=e-t;return n?On(t,n):Cn(isNaN(t)?e:t)}var Bn=function t(e){var n=In(e);function r(t,e){var r=n((t=bn(t)).r,(e=bn(e)).r),i=n(t.g,e.g),o=n(t.b,e.b),a=Nn(t.opacity,e.opacity);return function(e){return t.r=r(e),t.g=i(e),t.b=o(e),t.opacity=a(e),t+""}}return r.gamma=t,r}(1);function Ln(t){return function(e){var n,r,i=e.length,o=new Array(i),a=new Array(i),s=new Array(i);for(n=0;no&&(i=e.slice(o,i),s[a]?s[a]+=i:s[++a]=i),(n=n[0])===(r=r[0])?s[a]?s[a]+=r:s[++a]=r:(s[++a]=null,u.push({i:a,x:Un(n,r)})),o=Vn.lastIndex;return o180?e+=360:e-t>180&&(t+=360),o.push({i:n.push(i(n)+"rotate(",null,r)-2,x:Un(t,e)})):e&&n.push(i(n)+"rotate("+e+r)}(o.rotate,a.rotate,s,u),function(t,e,n,o){t!==e?o.push({i:n.push(i(n)+"skewX(",null,r)-2,x:Un(t,e)}):e&&n.push(i(n)+"skewX("+e+r)}(o.skewX,a.skewX,s,u),function(t,e,n,r,o,a){if(t!==n||e!==r){var s=o.push(i(o)+"scale(",null,",",null,")");a.push({i:s-4,x:Un(t,n)},{i:s-2,x:Un(e,r)})}else 1===n&&1===r||o.push(i(o)+"scale("+n+","+r+")")}(o.scaleX,o.scaleY,a.scaleX,a.scaleY,s,u),o=a=null,function(t){for(var e,n=-1,r=u.length;++n_r?Math.pow(t,1/3):t/vr+br}function Sr(t){return t>mr?t*t*t:vr*(t-br)}function Tr(t){return 255*(t<=.0031308?12.92*t:1.055*Math.pow(t,1/2.4)-.055)}function Mr(t){return(t/=255)<=.04045?t/12.92:Math.pow((t+.055)/1.055,2.4)}function Dr(t){if(t instanceof Rr)return new Rr(t.h,t.c,t.l,t.opacity);if(t instanceof Er||(t=wr(t)),0===t.a&&0===t.b)return new Rr(NaN,0=0&&e._call.call(null,t),e=e._next;--Qr}function di(){ii=(ri=ai.now())+oi,Qr=ti=0;try{hi()}finally{Qr=0,function(){var t,e,n=Xr,r=1/0;for(;n;)n._call?(r>n._time&&(r=n._time),t=n,n=n._next):(e=n._next,n._next=null,n=t?t._next=e:Xr=e);Zr=t,gi(r)}(),ii=0}}function pi(){var t=ai.now(),e=t-ri;e>ni&&(oi-=e,ri=t)}function gi(t){Qr||(ti&&(ti=clearTimeout(ti)),t-ii>24?(t<1/0&&(ti=setTimeout(di,t-ai.now()-oi)),ei&&(ei=clearInterval(ei))):(ei||(ri=ai.now(),ei=setInterval(pi,ni)),Qr=1,si(di)))}fi.prototype=li.prototype={constructor:fi,restart:function(t,e,n){if("function"!=typeof t)throw new TypeError("callback is not a function");n=(null==n?ui():+n)+(null==e?0:+e),this._next||Zr===this||(Zr?Zr._next=this:Xr=this,Zr=this),this._call=t,this._time=n,gi()},stop:function(){this._call&&(this._call=null,this._time=1/0,gi())}};var yi=function(t,e,n){var r=new fi;return e=null==e?0:+e,r.restart((function(n){r.stop(),t(n+e)}),e,n),r},bi=function(t,e,n){var r=new fi,i=e;return null==e?(r.restart(t,e,n),r):(e=+e,n=null==n?ui():+n,r.restart((function o(a){a+=i,r.restart(o,i+=e,n),t(a)}),e,n),r)},mi=gt("start","end","cancel","interrupt"),vi=[],_i=0,wi=1,xi=2,ki=3,Ei=4,Ai=5,Si=6,Ti=function(t,e,n,r,i,o){var a=t.__transition;if(a){if(n in a)return}else t.__transition={};!function(t,e,n){var r,i=t.__transition;function o(u){var c,f,l,h;if(n.state!==wi)return s();for(c in i)if((h=i[c]).name===n.name){if(h.state===ki)return yi(o);h.state===Ei?(h.state=Si,h.timer.stop(),h.on.call("interrupt",t,t.__data__,h.index,h.group),delete i[c]):+c_i)throw new Error("too late; already scheduled");return n}function Di(t,e){var n=Ci(t,e);if(n.state>ki)throw new Error("too late; already running");return n}function Ci(t,e){var n=t.__transition;if(!n||!(n=n[e]))throw new Error("transition not found");return n}var Oi=function(t,e){var n,r,i,o=t.__transition,a=!0;if(o){for(i in e=null==e?null:e+"",o)(n=o[i]).name===e?(r=n.state>xi&&n.state=0&&(t=t.slice(0,e)),!t||"start"===t}))}(e)?Mi:Di;return function(){var a=o(this,t),s=a.on;s!==r&&(i=(r=s).copy()).on(e,n),a.on=i}}var Xi=Te.prototype.constructor;function Zi(t){return function(){this.style.removeProperty(t)}}function Ji(t,e,n){var r,i;function o(){var o=e.apply(this,arguments);return o!==i&&(r=(i=o)&&function(t,e,n){return function(r){this.style.setProperty(t,e(r),n)}}(t,o,n)),r}return o._value=e,o}var Qi=0;function to(t,e,n,r){this._groups=t,this._parents=e,this._name=n,this._id=r}function eo(t){return Te().transition(t)}function no(){return++Qi}var ro=Te.prototype;function io(t){return+t}function oo(t){return t*t}function ao(t){return t*(2-t)}function so(t){return((t*=2)<=1?t*t:--t*(2-t)+1)/2}function uo(t){return t*t*t}function co(t){return--t*t*t+1}function fo(t){return((t*=2)<=1?t*t*t:(t-=2)*t*t+2)/2}to.prototype=eo.prototype={constructor:to,select:function(t){var e=this._name,n=this._id;"function"!=typeof t&&(t=kt(t));for(var r=this._groups,i=r.length,o=new Array(i),a=0;awi&&n.name===e)return new to([[t]],Wo,e,+r);return null},Xo=function(t){return function(){return t}},Zo=function(t,e,n){this.target=t,this.type=e,this.selection=n};function Jo(){pe.stopImmediatePropagation()}var Qo=function(){pe.preventDefault(),pe.stopImmediatePropagation()},ta={name:"drag"},ea={name:"space"},na={name:"handle"},ra={name:"center"};function ia(t){return[+t[0],+t[1]]}function oa(t){return[ia(t[0]),ia(t[1])]}var aa={name:"x",handles:["w","e"].map(pa),input:function(t,e){return null==t?null:[[+t[0],e[0][1]],[+t[1],e[1][1]]]},output:function(t){return t&&[t[0][0],t[1][0]]}},sa={name:"y",handles:["n","s"].map(pa),input:function(t,e){return null==t?null:[[e[0][0],+t[0]],[e[1][0],+t[1]]]},output:function(t){return t&&[t[0][1],t[1][1]]}},ua={name:"xy",handles:["n","w","e","s","nw","ne","sw","se"].map(pa),input:function(t){return null==t?null:oa(t)},output:function(t){return t}},ca={overlay:"crosshair",selection:"move",n:"ns-resize",e:"ew-resize",s:"ns-resize",w:"ew-resize",nw:"nwse-resize",ne:"nesw-resize",se:"nwse-resize",sw:"nesw-resize"},fa={e:"w",w:"e",nw:"ne",ne:"nw",se:"sw",sw:"se"},la={n:"s",s:"n",nw:"sw",ne:"se",se:"ne",sw:"nw"},ha={overlay:1,selection:1,n:null,e:1,s:null,w:-1,nw:-1,ne:1,se:1,sw:-1},da={overlay:1,selection:1,n:-1,e:null,s:1,w:null,nw:-1,ne:-1,se:1,sw:1};function pa(t){return{type:t}}function ga(){return!pe.ctrlKey&&!pe.button}function ya(){var t=this.ownerSVGElement||this;return t.hasAttribute("viewBox")?[[(t=t.viewBox.baseVal).x,t.y],[t.x+t.width,t.y+t.height]]:[[0,0],[t.width.baseVal.value,t.height.baseVal.value]]}function ba(){return navigator.maxTouchPoints||"ontouchstart"in this}function ma(t){for(;!t.__brush;)if(!(t=t.parentNode))return;return t.__brush}function va(t){var e=t.__brush;return e?e.dim.output(e.selection):null}function _a(){return ka(aa)}function wa(){return ka(sa)}var xa=function(){return ka(ua)};function ka(t){var e,n=ya,r=ga,i=ba,o=!0,a=gt(u,"start","brush","end"),s=6;function u(e){var n=e.property("__brush",g).selectAll(".overlay").data([pa("overlay")]);n.enter().append("rect").attr("class","overlay").attr("pointer-events","all").attr("cursor",ca.overlay).merge(n).each((function(){var t=ma(this).extent;Me(this).attr("x",t[0][0]).attr("y",t[0][1]).attr("width",t[1][0]-t[0][0]).attr("height",t[1][1]-t[0][1])})),e.selectAll(".selection").data([pa("selection")]).enter().append("rect").attr("class","selection").attr("cursor",ca.selection).attr("fill","#777").attr("fill-opacity",.3).attr("stroke","#fff").attr("shape-rendering","crispEdges");var r=e.selectAll(".handle").data(t.handles,(function(t){return t.type}));r.exit().remove(),r.enter().append("rect").attr("class",(function(t){return"handle handle--"+t.type})).attr("cursor",(function(t){return ca[t.type]})),e.each(c).attr("fill","none").attr("pointer-events","all").on("mousedown.brush",h).filter(i).on("touchstart.brush",h).on("touchmove.brush",d).on("touchend.brush touchcancel.brush",p).style("touch-action","none").style("-webkit-tap-highlight-color","rgba(0,0,0,0)")}function c(){var t=Me(this),e=ma(this).selection;e?(t.selectAll(".selection").style("display",null).attr("x",e[0][0]).attr("y",e[0][1]).attr("width",e[1][0]-e[0][0]).attr("height",e[1][1]-e[0][1]),t.selectAll(".handle").style("display",null).attr("x",(function(t){return"e"===t.type[t.type.length-1]?e[1][0]-s/2:e[0][0]-s/2})).attr("y",(function(t){return"s"===t.type[0]?e[1][1]-s/2:e[0][1]-s/2})).attr("width",(function(t){return"n"===t.type||"s"===t.type?e[1][0]-e[0][0]+s:s})).attr("height",(function(t){return"e"===t.type||"w"===t.type?e[1][1]-e[0][1]+s:s}))):t.selectAll(".selection,.handle").style("display","none").attr("x",null).attr("y",null).attr("width",null).attr("height",null)}function f(t,e,n){return!n&&t.__brush.emitter||new l(t,e)}function l(t,e){this.that=t,this.args=e,this.state=t.__brush,this.active=0}function h(){if((!e||pe.touches)&&r.apply(this,arguments)){var n,i,a,s,u,l,h,d,p,g,y,b,m=this,v=pe.target.__data__.type,_="selection"===(o&&pe.metaKey?v="overlay":v)?ta:o&&pe.altKey?ra:na,w=t===sa?null:ha[v],x=t===aa?null:da[v],k=ma(m),E=k.extent,A=k.selection,S=E[0][0],T=E[0][1],M=E[1][0],D=E[1][1],C=0,O=0,R=w&&x&&o&&pe.shiftKey,I=pe.touches?(b=pe.changedTouches[0].identifier,function(t){return Pe(t,pe.touches,b)}):Be,N=I(m),B=N,L=f(m,arguments,!0).beforestart();"overlay"===v?(A&&(p=!0),k.selection=A=[[n=t===sa?S:N[0],a=t===aa?T:N[1]],[u=t===sa?M:n,h=t===aa?D:a]]):(n=A[0][0],a=A[0][1],u=A[1][0],h=A[1][1]),i=n,s=a,l=u,d=h;var P=Me(m).attr("pointer-events","none"),F=P.selectAll(".overlay").attr("cursor",ca[v]);if(pe.touches)L.moved=j,L.ended=z;else{var q=Me(pe.view).on("mousemove.brush",j,!0).on("mouseup.brush",z,!0);o&&q.on("keydown.brush",(function(){switch(pe.keyCode){case 16:R=w&&x;break;case 18:_===na&&(w&&(u=l-C*w,n=i+C*w),x&&(h=d-O*x,a=s+O*x),_=ra,U());break;case 32:_!==na&&_!==ra||(w<0?u=l-C:w>0&&(n=i-C),x<0?h=d-O:x>0&&(a=s-O),_=ea,F.attr("cursor",ca.selection),U());break;default:return}Qo()}),!0).on("keyup.brush",(function(){switch(pe.keyCode){case 16:R&&(g=y=R=!1,U());break;case 18:_===ra&&(w<0?u=l:w>0&&(n=i),x<0?h=d:x>0&&(a=s),_=na,U());break;case 32:_===ea&&(pe.altKey?(w&&(u=l-C*w,n=i+C*w),x&&(h=d-O*x,a=s+O*x),_=ra):(w<0?u=l:w>0&&(n=i),x<0?h=d:x>0&&(a=s),_=na),F.attr("cursor",ca[v]),U());break;default:return}Qo()}),!0),Ue(pe.view)}Jo(),Oi(m),c.call(m),L.start()}function j(){var t=I(m);!R||g||y||(Math.abs(t[0]-B[0])>Math.abs(t[1]-B[1])?y=!0:g=!0),B=t,p=!0,Qo(),U()}function U(){var t;switch(C=B[0]-N[0],O=B[1]-N[1],_){case ea:case ta:w&&(C=Math.max(S-n,Math.min(M-u,C)),i=n+C,l=u+C),x&&(O=Math.max(T-a,Math.min(D-h,O)),s=a+O,d=h+O);break;case na:w<0?(C=Math.max(S-n,Math.min(M-n,C)),i=n+C,l=u):w>0&&(C=Math.max(S-u,Math.min(M-u,C)),i=n,l=u+C),x<0?(O=Math.max(T-a,Math.min(D-a,O)),s=a+O,d=h):x>0&&(O=Math.max(T-h,Math.min(D-h,O)),s=a,d=h+O);break;case ra:w&&(i=Math.max(S,Math.min(M,n-C*w)),l=Math.max(S,Math.min(M,u+C*w))),x&&(s=Math.max(T,Math.min(D,a-O*x)),d=Math.max(T,Math.min(D,h+O*x)))}l1e-6)if(Math.abs(f*s-u*c)>1e-6&&i){var h=n-o,d=r-a,p=s*s+u*u,g=h*h+d*d,y=Math.sqrt(p),b=Math.sqrt(l),m=i*Math.tan((Na-Math.acos((p+l-g)/(2*y*b)))/2),v=m/b,_=m/y;Math.abs(v-1)>1e-6&&(this._+="L"+(t+v*c)+","+(e+v*f)),this._+="A"+i+","+i+",0,0,"+ +(f*h>c*d)+","+(this._x1=t+_*s)+","+(this._y1=e+_*u)}else this._+="L"+(this._x1=t)+","+(this._y1=e);else;},arc:function(t,e,n,r,i,o){t=+t,e=+e,o=!!o;var a=(n=+n)*Math.cos(r),s=n*Math.sin(r),u=t+a,c=e+s,f=1^o,l=o?r-i:i-r;if(n<0)throw new Error("negative radius: "+n);null===this._x1?this._+="M"+u+","+c:(Math.abs(this._x1-u)>1e-6||Math.abs(this._y1-c)>1e-6)&&(this._+="L"+u+","+c),n&&(l<0&&(l=l%Ba+Ba),l>La?this._+="A"+n+","+n+",0,1,"+f+","+(t-a)+","+(e-s)+"A"+n+","+n+",0,1,"+f+","+(this._x1=u)+","+(this._y1=c):l>1e-6&&(this._+="A"+n+","+n+",0,"+ +(l>=Na)+","+f+","+(this._x1=t+n*Math.cos(i))+","+(this._y1=e+n*Math.sin(i))))},rect:function(t,e,n,r){this._+="M"+(this._x0=this._x1=+t)+","+(this._y0=this._y1=+e)+"h"+ +n+"v"+ +r+"h"+-n+"Z"},toString:function(){return this._}};var qa=Fa;function ja(t){return t.source}function Ua(t){return t.target}function za(t){return t.radius}function Ya(t){return t.startAngle}function Va(t){return t.endAngle}var Ha=function(){var t=ja,e=Ua,n=za,r=Ya,i=Va,o=null;function a(){var a,s=Ra.call(arguments),u=t.apply(this,s),c=e.apply(this,s),f=+n.apply(this,(s[0]=u,s)),l=r.apply(this,s)-Ta,h=i.apply(this,s)-Ta,d=f*Ea(l),p=f*Aa(l),g=+n.apply(this,(s[0]=c,s)),y=r.apply(this,s)-Ta,b=i.apply(this,s)-Ta;if(o||(o=a=qa()),o.moveTo(d,p),o.arc(0,0,f,l,h),l===y&&h===b||(o.quadraticCurveTo(0,0,g*Ea(y),g*Aa(y)),o.arc(0,0,g,y,b)),o.quadraticCurveTo(0,0,d,p),o.closePath(),a)return o=null,a+""||null}return a.radius=function(t){return arguments.length?(n="function"==typeof t?t:Ia(+t),a):n},a.startAngle=function(t){return arguments.length?(r="function"==typeof t?t:Ia(+t),a):r},a.endAngle=function(t){return arguments.length?(i="function"==typeof t?t:Ia(+t),a):i},a.source=function(e){return arguments.length?(t=e,a):t},a.target=function(t){return arguments.length?(e=t,a):e},a.context=function(t){return arguments.length?(o=null==t?null:t,a):o},a};function $a(){}function Ga(t,e){var n=new $a;if(t instanceof $a)t.each((function(t,e){n.set(e,t)}));else if(Array.isArray(t)){var r,i=-1,o=t.length;if(null==e)for(;++i=r.length)return null!=t&&n.sort(t),null!=e?e(n):n;for(var u,c,f,l=-1,h=n.length,d=r[i++],p=Wa(),g=a();++lr.length)return n;var a,s=i[o-1];return null!=e&&o>=r.length?a=n.entries():(a=[],n.each((function(e,n){a.push({key:n,values:t(e,o)})}))),null!=s?a.sort((function(t,e){return s(t.key,e.key)})):a}(o(t,0,Ja,Qa),0)},key:function(t){return r.push(t),n},sortKeys:function(t){return i[r.length-1]=t,n},sortValues:function(e){return t=e,n},rollup:function(t){return e=t,n}}};function Xa(){return{}}function Za(t,e,n){t[e]=n}function Ja(){return Wa()}function Qa(t,e,n){t.set(e,n)}function ts(){}var es=Wa.prototype;function ns(t,e){var n=new ts;if(t instanceof ts)t.each((function(t){n.add(t)}));else if(t){var r=-1,i=t.length;if(null==e)for(;++rr!=d>r&&n<(h-c)*(r-f)/(d-f)+c&&(i=-i)}return i}function ds(t,e,n){var r,i,o,a;return function(t,e,n){return(e[0]-t[0])*(n[1]-t[1])==(n[0]-t[0])*(e[1]-t[1])}(t,e,n)&&(i=t[r=+(t[0]===e[0])],o=n[r],a=e[r],i<=o&&o<=a||a<=o&&o<=i)}var ps=function(){},gs=[[],[[[1,1.5],[.5,1]]],[[[1.5,1],[1,1.5]]],[[[1.5,1],[.5,1]]],[[[1,.5],[1.5,1]]],[[[1,1.5],[.5,1]],[[1,.5],[1.5,1]]],[[[1,.5],[1,1.5]]],[[[1,.5],[.5,1]]],[[[.5,1],[1,.5]]],[[[1,1.5],[1,.5]]],[[[.5,1],[1,.5]],[[1.5,1],[1,1.5]]],[[[1.5,1],[1,.5]]],[[[.5,1],[1.5,1]]],[[[1,1.5],[1.5,1]]],[[[.5,1],[1,1.5]]],[]],ys=function(){var t=1,e=1,n=D,r=s;function i(t){var e=n(t);if(Array.isArray(e))e=e.slice().sort(us);else{var r=y(t),i=r[0],a=r[1];e=M(i,a,e),e=x(Math.floor(i/e)*e,Math.floor(a/e)*e,e)}return e.map((function(e){return o(t,e)}))}function o(n,i){var o=[],s=[];return function(n,r,i){var o,s,u,c,f,l,h=new Array,d=new Array;o=s=-1,c=n[0]>=r,gs[c<<1].forEach(p);for(;++o=r,gs[u|c<<1].forEach(p);gs[c<<0].forEach(p);for(;++s=r,f=n[s*t]>=r,gs[c<<1|f<<2].forEach(p);++o=r,l=f,f=n[s*t+o+1]>=r,gs[u|c<<1|f<<2|l<<3].forEach(p);gs[c|f<<3].forEach(p)}o=-1,f=n[s*t]>=r,gs[f<<2].forEach(p);for(;++o=r,gs[f<<2|l<<3].forEach(p);function p(t){var e,n,r=[t[0][0]+o,t[0][1]+s],u=[t[1][0]+o,t[1][1]+s],c=a(r),f=a(u);(e=d[c])?(n=h[f])?(delete d[e.end],delete h[n.start],e===n?(e.ring.push(u),i(e.ring)):h[e.start]=d[n.end]={start:e.start,end:n.end,ring:e.ring.concat(n.ring)}):(delete d[e.end],e.ring.push(u),d[e.end=f]=e):(e=h[f])?(n=d[c])?(delete h[e.start],delete d[n.end],e===n?(e.ring.push(u),i(e.ring)):h[n.start]=d[e.end]={start:n.start,end:e.end,ring:n.ring.concat(e.ring)}):(delete h[e.start],e.ring.unshift(r),h[e.start=c]=e):h[c]=d[f]={start:c,end:f,ring:[r,u]}}gs[f<<3].forEach(p)}(n,i,(function(t){r(t,n,i),cs(t)>0?o.push([t]):s.push(t)})),s.forEach((function(t){for(var e,n=0,r=o.length;n0&&a0&&s0&&o>0))throw new Error("invalid size");return t=r,e=o,i},i.thresholds=function(t){return arguments.length?(n="function"==typeof t?t:Array.isArray(t)?fs(ss.call(t)):fs(t),i):n},i.smooth=function(t){return arguments.length?(r=t?s:ps,i):r===s},i};function bs(t,e,n){for(var r=t.width,i=t.height,o=1+(n<<1),a=0;a=n&&(s>=o&&(u-=t.data[s-o+a*r]),e.data[s-n+a*r]=u/Math.min(s+1,r-1+o-s,o))}function ms(t,e,n){for(var r=t.width,i=t.height,o=1+(n<<1),a=0;a=n&&(s>=o&&(u-=t.data[a+(s-o)*r]),e.data[a+(s-n)*r]=u/Math.min(s+1,i-1+o-s,o))}function vs(t){return t[0]}function _s(t){return t[1]}function ws(){return 1}var xs=function(){var t=vs,e=_s,n=ws,r=960,i=500,o=20,a=2,s=3*o,u=r+2*s>>a,c=i+2*s>>a,f=fs(20);function l(r){var i=new Float32Array(u*c),l=new Float32Array(u*c);r.forEach((function(r,o,f){var l=+t(r,o,f)+s>>a,h=+e(r,o,f)+s>>a,d=+n(r,o,f);l>=0&&l=0&&h>a),ms({width:u,height:c,data:l},{width:u,height:c,data:i},o>>a),bs({width:u,height:c,data:i},{width:u,height:c,data:l},o>>a),ms({width:u,height:c,data:l},{width:u,height:c,data:i},o>>a),bs({width:u,height:c,data:i},{width:u,height:c,data:l},o>>a),ms({width:u,height:c,data:l},{width:u,height:c,data:i},o>>a);var d=f(i);if(!Array.isArray(d)){var p=N(i);d=M(0,p,d),(d=x(0,Math.floor(p/d)*d,d)).shift()}return ys().thresholds(d).size([u,c])(i).map(h)}function h(t){return t.value*=Math.pow(2,-2*a),t.coordinates.forEach(d),t}function d(t){t.forEach(p)}function p(t){t.forEach(g)}function g(t){t[0]=t[0]*Math.pow(2,a)-s,t[1]=t[1]*Math.pow(2,a)-s}function y(){return u=r+2*(s=3*o)>>a,c=i+2*s>>a,l}return l.x=function(e){return arguments.length?(t="function"==typeof e?e:fs(+e),l):t},l.y=function(t){return arguments.length?(e="function"==typeof t?t:fs(+t),l):e},l.weight=function(t){return arguments.length?(n="function"==typeof t?t:fs(+t),l):n},l.size=function(t){if(!arguments.length)return[r,i];var e=Math.ceil(t[0]),n=Math.ceil(t[1]);if(!(e>=0||e>=0))throw new Error("invalid size");return r=e,i=n,y()},l.cellSize=function(t){if(!arguments.length)return 1<=1))throw new Error("invalid cell size");return a=Math.floor(Math.log(t)/Math.LN2),y()},l.thresholds=function(t){return arguments.length?(f="function"==typeof t?t:Array.isArray(t)?fs(ss.call(t)):fs(t),l):f},l.bandwidth=function(t){if(!arguments.length)return Math.sqrt(o*(o+1));if(!((t=+t)>=0))throw new Error("invalid bandwidth");return o=Math.round((Math.sqrt(4*t*t+1)-1)/2),y()},l},ks={},Es={},As=34,Ss=10,Ts=13;function Ms(t){return new Function("d","return {"+t.map((function(t,e){return JSON.stringify(t)+": d["+e+"]"})).join(",")+"}")}function Ds(t){var e=Object.create(null),n=[];return t.forEach((function(t){for(var r in t)r in e||n.push(e[r]=r)})),n}function Cs(t,e){var n=t+"",r=n.length;return r9999?"+"+Cs(e,6):Cs(e,4))+"-"+Cs(t.getUTCMonth()+1,2)+"-"+Cs(t.getUTCDate(),2)+(o?"T"+Cs(n,2)+":"+Cs(r,2)+":"+Cs(i,2)+"."+Cs(o,3)+"Z":i?"T"+Cs(n,2)+":"+Cs(r,2)+":"+Cs(i,2)+"Z":r||n?"T"+Cs(n,2)+":"+Cs(r,2)+"Z":"")}var Rs=function(t){var e=new RegExp('["'+t+"\n\r]"),n=t.charCodeAt(0);function r(t,e){var r,i=[],o=t.length,a=0,s=0,u=o<=0,c=!1;function f(){if(u)return Es;if(c)return c=!1,ks;var e,r,i=a;if(t.charCodeAt(i)===As){for(;a++=o?u=!0:(r=t.charCodeAt(a++))===Ss?c=!0:r===Ts&&(c=!0,t.charCodeAt(a)===Ss&&++a),t.slice(i+1,e-1).replace(/""/g,'"')}for(;a=(o=(g+b)/2))?g=o:b=o,(f=n>=(a=(y+m)/2))?y=a:m=a,i=d,!(d=d[l=f<<1|c]))return i[l]=p,t;if(s=+t._x.call(null,d.data),u=+t._y.call(null,d.data),e===s&&n===u)return p.next=d,i?i[l]=p:t._root=p,t;do{i=i?i[l]=new Array(4):t._root=new Array(4),(c=e>=(o=(g+b)/2))?g=o:b=o,(f=n>=(a=(y+m)/2))?y=a:m=a}while((l=f<<1|c)==(h=(u>=a)<<1|s>=o));return i[h]=d,i[l]=p,t}var du=function(t,e,n,r,i){this.node=t,this.x0=e,this.y0=n,this.x1=r,this.y1=i};function pu(t){return t[0]}function gu(t){return t[1]}function yu(t,e,n){var r=new bu(null==e?pu:e,null==n?gu:n,NaN,NaN,NaN,NaN);return null==t?r:r.addAll(t)}function bu(t,e,n,r,i,o){this._x=t,this._y=e,this._x0=n,this._y0=r,this._x1=i,this._y1=o,this._root=void 0}function mu(t){for(var e={data:t.data},n=e;t=t.next;)n=n.next={data:t.data};return e}var vu=yu.prototype=bu.prototype;function _u(t){return t.x+t.vx}function wu(t){return t.y+t.vy}vu.copy=function(){var t,e,n=new bu(this._x,this._y,this._x0,this._y0,this._x1,this._y1),r=this._root;if(!r)return n;if(!r.length)return n._root=mu(r),n;for(t=[{source:r,target:n._root=new Array(4)}];r=t.pop();)for(var i=0;i<4;++i)(e=r.source[i])&&(e.length?t.push({source:e,target:r.target[i]=new Array(4)}):r.target[i]=mu(e));return n},vu.add=function(t){var e=+this._x.call(null,t),n=+this._y.call(null,t);return hu(this.cover(e,n),e,n,t)},vu.addAll=function(t){var e,n,r,i,o=t.length,a=new Array(o),s=new Array(o),u=1/0,c=1/0,f=-1/0,l=-1/0;for(n=0;nf&&(f=r),il&&(l=i));if(u>f||c>l)return this;for(this.cover(u,c).cover(f,l),n=0;nt||t>=i||r>e||e>=o;)switch(s=(eh||(o=u.y0)>d||(a=u.x1)=b)<<1|t>=y)&&(u=p[p.length-1],p[p.length-1]=p[p.length-1-c],p[p.length-1-c]=u)}else{var m=t-+this._x.call(null,g.data),v=e-+this._y.call(null,g.data),_=m*m+v*v;if(_=(s=(p+y)/2))?p=s:y=s,(f=a>=(u=(g+b)/2))?g=u:b=u,e=d,!(d=d[l=f<<1|c]))return this;if(!d.length)break;(e[l+1&3]||e[l+2&3]||e[l+3&3])&&(n=e,h=l)}for(;d.data!==t;)if(r=d,!(d=d.next))return this;return(i=d.next)&&delete d.next,r?(i?r.next=i:delete r.next,this):e?(i?e[l]=i:delete e[l],(d=e[0]||e[1]||e[2]||e[3])&&d===(e[3]||e[2]||e[1]||e[0])&&!d.length&&(n?n[h]=d:this._root=d),this):(this._root=i,this)},vu.removeAll=function(t){for(var e=0,n=t.length;eu+d||ic+d||os.index){var p=u-a.x-a.vx,g=c-a.y-a.vy,y=p*p+g*g;yt.r&&(t.r=t[e].r)}function s(){if(e){var r,i,o=e.length;for(n=new Array(o),r=0;r1?(null==n?s.remove(t):s.set(t,d(n)),e):s.get(t)},find:function(e,n,r){var i,o,a,s,u,c=0,f=t.length;for(null==r?r=1/0:r*=r,c=0;c1?(c.on(t,n),e):c.on(t)}}},Ou=function(){var t,e,n,r,i=fu(-30),o=1,a=1/0,s=.81;function u(r){var i,o=t.length,a=yu(t,Su,Tu).visitAfter(f);for(n=r,i=0;i=a)){(t.data!==e||t.next)&&(0===f&&(d+=(f=lu())*f),0===l&&(d+=(l=lu())*l),d1?r[0]+r.slice(2):r,+t.slice(n+1)]},Lu=function(t){return(t=Bu(Math.abs(t)))?t[1]:NaN},Pu=/^(?:(.)?([<>=^]))?([+\-( ])?([$#])?(0)?(\d+)?(,)?(\.\d+)?(~)?([a-z%])?$/i;function Fu(t){if(!(e=Pu.exec(t)))throw new Error("invalid format: "+t);var e;return new qu({fill:e[1],align:e[2],sign:e[3],symbol:e[4],zero:e[5],width:e[6],comma:e[7],precision:e[8]&&e[8].slice(1),trim:e[9],type:e[10]})}function qu(t){this.fill=void 0===t.fill?" ":t.fill+"",this.align=void 0===t.align?">":t.align+"",this.sign=void 0===t.sign?"-":t.sign+"",this.symbol=void 0===t.symbol?"":t.symbol+"",this.zero=!!t.zero,this.width=void 0===t.width?void 0:+t.width,this.comma=!!t.comma,this.precision=void 0===t.precision?void 0:+t.precision,this.trim=!!t.trim,this.type=void 0===t.type?"":t.type+""}Fu.prototype=qu.prototype,qu.prototype.toString=function(){return this.fill+this.align+this.sign+this.symbol+(this.zero?"0":"")+(void 0===this.width?"":Math.max(1,0|this.width))+(this.comma?",":"")+(void 0===this.precision?"":"."+Math.max(0,0|this.precision))+(this.trim?"~":"")+this.type};var ju,Uu,zu,Yu,Vu=function(t){t:for(var e,n=t.length,r=1,i=-1;r0){if(!+t[r])break t;i=0}}return i>0?t.slice(0,i)+t.slice(e+1):t},Hu=function(t,e){var n=Bu(t,e);if(!n)return t+"";var r=n[0],i=n[1];return i<0?"0."+new Array(-i).join("0")+r:r.length>i+1?r.slice(0,i+1)+"."+r.slice(i+1):r+new Array(i-r.length+2).join("0")},$u={"%":function(t,e){return(100*t).toFixed(e)},b:function(t){return Math.round(t).toString(2)},c:function(t){return t+""},d:function(t){return Math.round(t).toString(10)},e:function(t,e){return t.toExponential(e)},f:function(t,e){return t.toFixed(e)},g:function(t,e){return t.toPrecision(e)},o:function(t){return Math.round(t).toString(8)},p:function(t,e){return Hu(100*t,e)},r:Hu,s:function(t,e){var n=Bu(t,e);if(!n)return t+"";var r=n[0],i=n[1],o=i-(ju=3*Math.max(-8,Math.min(8,Math.floor(i/3))))+1,a=r.length;return o===a?r:o>a?r+new Array(o-a+1).join("0"):o>0?r.slice(0,o)+"."+r.slice(o):"0."+new Array(1-o).join("0")+Bu(t,Math.max(0,e+o-1))[0]},X:function(t){return Math.round(t).toString(16).toUpperCase()},x:function(t){return Math.round(t).toString(16)}},Gu=function(t){return t},Wu=Array.prototype.map,Ku=["y","z","a","f","p","n","µ","m","","k","M","G","T","P","E","Z","Y"],Xu=function(t){var e,n,r=void 0===t.grouping||void 0===t.thousands?Gu:(e=Wu.call(t.grouping,Number),n=t.thousands+"",function(t,r){for(var i=t.length,o=[],a=0,s=e[0],u=0;i>0&&s>0&&(u+s+1>r&&(s=Math.max(1,r-u)),o.push(t.substring(i-=s,i+s)),!((u+=s+1)>r));)s=e[a=(a+1)%e.length];return o.reverse().join(n)}),i=void 0===t.currency?"":t.currency[0]+"",o=void 0===t.currency?"":t.currency[1]+"",a=void 0===t.decimal?".":t.decimal+"",s=void 0===t.numerals?Gu:function(t){return function(e){return e.replace(/[0-9]/g,(function(e){return t[+e]}))}}(Wu.call(t.numerals,String)),u=void 0===t.percent?"%":t.percent+"",c=void 0===t.minus?"-":t.minus+"",f=void 0===t.nan?"NaN":t.nan+"";function l(t){var e=(t=Fu(t)).fill,n=t.align,l=t.sign,h=t.symbol,d=t.zero,p=t.width,g=t.comma,y=t.precision,b=t.trim,m=t.type;"n"===m?(g=!0,m="g"):$u[m]||(void 0===y&&(y=12),b=!0,m="g"),(d||"0"===e&&"="===n)&&(d=!0,e="0",n="=");var v="$"===h?i:"#"===h&&/[boxX]/.test(m)?"0"+m.toLowerCase():"",_="$"===h?o:/[%p]/.test(m)?u:"",w=$u[m],x=/[defgprs%]/.test(m);function k(t){var i,o,u,h=v,k=_;if("c"===m)k=w(t)+k,t="";else{var E=(t=+t)<0;if(t=isNaN(t)?f:w(Math.abs(t),y),b&&(t=Vu(t)),E&&0==+t&&(E=!1),h=(E?"("===l?l:c:"-"===l||"("===l?"":l)+h,k=("s"===m?Ku[8+ju/3]:"")+k+(E&&"("===l?")":""),x)for(i=-1,o=t.length;++i(u=t.charCodeAt(i))||u>57){k=(46===u?a+t.slice(i+1):t.slice(i))+k,t=t.slice(0,i);break}}g&&!d&&(t=r(t,1/0));var A=h.length+t.length+k.length,S=A>1)+h+t+k+S.slice(A);break;default:t=S+h+t+k}return s(t)}return y=void 0===y?6:/[gprs]/.test(m)?Math.max(1,Math.min(21,y)):Math.max(0,Math.min(20,y)),k.toString=function(){return t+""},k}return{format:l,formatPrefix:function(t,e){var n=l(((t=Fu(t)).type="f",t)),r=3*Math.max(-8,Math.min(8,Math.floor(Lu(e)/3))),i=Math.pow(10,-r),o=Ku[8+r/3];return function(t){return n(i*t)+o}}}};function Zu(t){return Uu=Xu(t),zu=Uu.format,Yu=Uu.formatPrefix,Uu}Zu({decimal:".",thousands:",",grouping:[3],currency:["$",""],minus:"-"});var Ju=function(t){return Math.max(0,-Lu(Math.abs(t)))},Qu=function(t,e){return Math.max(0,3*Math.max(-8,Math.min(8,Math.floor(Lu(e)/3)))-Lu(Math.abs(t)))},tc=function(t,e){return t=Math.abs(t),e=Math.abs(e)-t,Math.max(0,Lu(e)-Lu(t))+1},ec=function(){return new nc};function nc(){this.reset()}nc.prototype={constructor:nc,reset:function(){this.s=this.t=0},add:function(t){ic(rc,t,this.t),ic(this,rc.s,this.s),this.s?this.t+=rc.t:this.s=rc.t},valueOf:function(){return this.s}};var rc=new nc;function ic(t,e,n){var r=t.s=e+n,i=r-e,o=r-i;t.t=e-o+(n-i)}var oc=1e-6,ac=1e-12,sc=Math.PI,uc=sc/2,cc=sc/4,fc=2*sc,lc=180/sc,hc=sc/180,dc=Math.abs,pc=Math.atan,gc=Math.atan2,yc=Math.cos,bc=Math.ceil,mc=Math.exp,vc=(Math.floor,Math.log),_c=Math.pow,wc=Math.sin,xc=Math.sign||function(t){return t>0?1:t<0?-1:0},kc=Math.sqrt,Ec=Math.tan;function Ac(t){return t>1?0:t<-1?sc:Math.acos(t)}function Sc(t){return t>1?uc:t<-1?-uc:Math.asin(t)}function Tc(t){return(t=wc(t/2))*t}function Mc(){}function Dc(t,e){t&&Oc.hasOwnProperty(t.type)&&Oc[t.type](t,e)}var Cc={Feature:function(t,e){Dc(t.geometry,e)},FeatureCollection:function(t,e){for(var n=t.features,r=-1,i=n.length;++r=0?1:-1,i=r*n,o=yc(e=(e*=hc)/2+cc),a=wc(e),s=Fc*a,u=Pc*o+s*yc(i),c=s*r*wc(i);jc.add(gc(c,u)),Lc=t,Pc=o,Fc=a}var Gc=function(t){return Uc.reset(),qc(t,zc),2*Uc};function Wc(t){return[gc(t[1],t[0]),Sc(t[2])]}function Kc(t){var e=t[0],n=t[1],r=yc(n);return[r*yc(e),r*wc(e),wc(n)]}function Xc(t,e){return t[0]*e[0]+t[1]*e[1]+t[2]*e[2]}function Zc(t,e){return[t[1]*e[2]-t[2]*e[1],t[2]*e[0]-t[0]*e[2],t[0]*e[1]-t[1]*e[0]]}function Jc(t,e){t[0]+=e[0],t[1]+=e[1],t[2]+=e[2]}function Qc(t,e){return[t[0]*e,t[1]*e,t[2]*e]}function tf(t){var e=kc(t[0]*t[0]+t[1]*t[1]+t[2]*t[2]);t[0]/=e,t[1]/=e,t[2]/=e}var ef,nf,rf,of,af,sf,uf,cf,ff,lf,hf=ec(),df={point:pf,lineStart:yf,lineEnd:bf,polygonStart:function(){df.point=mf,df.lineStart=vf,df.lineEnd=_f,hf.reset(),zc.polygonStart()},polygonEnd:function(){zc.polygonEnd(),df.point=pf,df.lineStart=yf,df.lineEnd=bf,jc<0?(ef=-(rf=180),nf=-(of=90)):hf>oc?of=90:hf<-oc&&(nf=-90),lf[0]=ef,lf[1]=rf},sphere:function(){ef=-(rf=180),nf=-(of=90)}};function pf(t,e){ff.push(lf=[ef=t,rf=t]),eof&&(of=e)}function gf(t,e){var n=Kc([t*hc,e*hc]);if(cf){var r=Zc(cf,n),i=Zc([r[1],-r[0],0],r);tf(i),i=Wc(i);var o,a=t-af,s=a>0?1:-1,u=i[0]*lc*s,c=dc(a)>180;c^(s*afof&&(of=o):c^(s*af<(u=(u+360)%360-180)&&uof&&(of=e)),c?twf(ef,rf)&&(rf=t):wf(t,rf)>wf(ef,rf)&&(ef=t):rf>=ef?(trf&&(rf=t)):t>af?wf(ef,t)>wf(ef,rf)&&(rf=t):wf(t,rf)>wf(ef,rf)&&(ef=t)}else ff.push(lf=[ef=t,rf=t]);eof&&(of=e),cf=n,af=t}function yf(){df.point=gf}function bf(){lf[0]=ef,lf[1]=rf,df.point=pf,cf=null}function mf(t,e){if(cf){var n=t-af;hf.add(dc(n)>180?n+(n>0?360:-360):n)}else sf=t,uf=e;zc.point(t,e),gf(t,e)}function vf(){zc.lineStart()}function _f(){mf(sf,uf),zc.lineEnd(),dc(hf)>oc&&(ef=-(rf=180)),lf[0]=ef,lf[1]=rf,cf=null}function wf(t,e){return(e-=t)<0?e+360:e}function xf(t,e){return t[0]-e[0]}function kf(t,e){return t[0]<=t[1]?t[0]<=e&&e<=t[1]:ewf(r[0],r[1])&&(r[1]=i[1]),wf(i[0],r[1])>wf(r[0],r[1])&&(r[0]=i[0])):o.push(r=i);for(a=-1/0,e=0,r=o[n=o.length-1];e<=n;r=i,++e)i=o[e],(s=wf(r[1],i[0]))>a&&(a=s,ef=i[0],rf=r[1])}return ff=lf=null,ef===1/0||nf===1/0?[[NaN,NaN],[NaN,NaN]]:[[ef,nf],[rf,of]]},Uf={sphere:Mc,point:zf,lineStart:Vf,lineEnd:Gf,polygonStart:function(){Uf.lineStart=Wf,Uf.lineEnd=Kf},polygonEnd:function(){Uf.lineStart=Vf,Uf.lineEnd=Gf}};function zf(t,e){t*=hc;var n=yc(e*=hc);Yf(n*yc(t),n*wc(t),wc(e))}function Yf(t,e,n){Sf+=(t-Sf)/++Ef,Tf+=(e-Tf)/Ef,Mf+=(n-Mf)/Ef}function Vf(){Uf.point=Hf}function Hf(t,e){t*=hc;var n=yc(e*=hc);Pf=n*yc(t),Ff=n*wc(t),qf=wc(e),Uf.point=$f,Yf(Pf,Ff,qf)}function $f(t,e){t*=hc;var n=yc(e*=hc),r=n*yc(t),i=n*wc(t),o=wc(e),a=gc(kc((a=Ff*o-qf*i)*a+(a=qf*r-Pf*o)*a+(a=Pf*i-Ff*r)*a),Pf*r+Ff*i+qf*o);Af+=a,Df+=a*(Pf+(Pf=r)),Cf+=a*(Ff+(Ff=i)),Of+=a*(qf+(qf=o)),Yf(Pf,Ff,qf)}function Gf(){Uf.point=zf}function Wf(){Uf.point=Xf}function Kf(){Zf(Bf,Lf),Uf.point=zf}function Xf(t,e){Bf=t,Lf=e,t*=hc,e*=hc,Uf.point=Zf;var n=yc(e);Pf=n*yc(t),Ff=n*wc(t),qf=wc(e),Yf(Pf,Ff,qf)}function Zf(t,e){t*=hc;var n=yc(e*=hc),r=n*yc(t),i=n*wc(t),o=wc(e),a=Ff*o-qf*i,s=qf*r-Pf*o,u=Pf*i-Ff*r,c=kc(a*a+s*s+u*u),f=Sc(c),l=c&&-f/c;Rf+=l*a,If+=l*s,Nf+=l*u,Af+=f,Df+=f*(Pf+(Pf=r)),Cf+=f*(Ff+(Ff=i)),Of+=f*(qf+(qf=o)),Yf(Pf,Ff,qf)}var Jf=function(t){Ef=Af=Sf=Tf=Mf=Df=Cf=Of=Rf=If=Nf=0,qc(t,Uf);var e=Rf,n=If,r=Nf,i=e*e+n*n+r*r;return isc?t+Math.round(-t/fc)*fc:t,e]}function nl(t,e,n){return(t%=fc)?e||n?tl(il(t),ol(e,n)):il(t):e||n?ol(e,n):el}function rl(t){return function(e,n){return[(e+=t)>sc?e-fc:e<-sc?e+fc:e,n]}}function il(t){var e=rl(t);return e.invert=rl(-t),e}function ol(t,e){var n=yc(t),r=wc(t),i=yc(e),o=wc(e);function a(t,e){var a=yc(e),s=yc(t)*a,u=wc(t)*a,c=wc(e),f=c*n+s*r;return[gc(u*i-f*o,s*n-c*r),Sc(f*i+u*o)]}return a.invert=function(t,e){var a=yc(e),s=yc(t)*a,u=wc(t)*a,c=wc(e),f=c*i-u*o;return[gc(u*i+c*o,s*n+f*r),Sc(f*n-s*r)]},a}el.invert=el;var al=function(t){function e(e){return(e=t(e[0]*hc,e[1]*hc))[0]*=lc,e[1]*=lc,e}return t=nl(t[0]*hc,t[1]*hc,t.length>2?t[2]*hc:0),e.invert=function(e){return(e=t.invert(e[0]*hc,e[1]*hc))[0]*=lc,e[1]*=lc,e},e};function sl(t,e,n,r,i,o){if(n){var a=yc(e),s=wc(e),u=r*n;null==i?(i=e+r*fc,o=e-u/2):(i=ul(a,i),o=ul(a,o),(r>0?io)&&(i+=r*fc));for(var c,f=i;r>0?f>o:f1&&e.push(e.pop().concat(e.shift()))},result:function(){var n=e;return e=[],t=null,n}}},ll=function(t,e){return dc(t[0]-e[0])=0;--o)i.point((f=c[o])[0],f[1]);else r(h.x,h.p.x,-1,i);h=h.p}c=(h=h.o).z,d=!d}while(!h.v);i.lineEnd()}}};function pl(t){if(e=t.length){for(var e,n,r=0,i=t[0];++r=0?1:-1,A=E*k,S=A>sc,T=g*w;if(gl.add(gc(T*E*wc(A),y*x+T*yc(A))),a+=S?k+E*fc:k,S^d>=n^v>=n){var M=Zc(Kc(h),Kc(m));tf(M);var D=Zc(o,M);tf(D);var C=(S^k>=0?-1:1)*Sc(D[2]);(r>C||r===C&&(M[0]||M[1]))&&(s+=S^k>=0?1:-1)}}return(a<-oc||a0){for(l||(i.polygonStart(),l=!0),i.lineStart(),t=0;t1&&2&u&&h.push(h.pop().concat(h.shift())),a.push(h.filter(vl))}return h}};function vl(t){return t.length>1}function _l(t,e){return((t=t.x)[0]<0?t[1]-uc-oc:uc-t[1])-((e=e.x)[0]<0?e[1]-uc-oc:uc-e[1])}var wl=ml((function(){return!0}),(function(t){var e,n=NaN,r=NaN,i=NaN;return{lineStart:function(){t.lineStart(),e=1},point:function(o,a){var s=o>0?sc:-sc,u=dc(o-n);dc(u-sc)0?uc:-uc),t.point(i,r),t.lineEnd(),t.lineStart(),t.point(s,r),t.point(o,r),e=0):i!==s&&u>=sc&&(dc(n-i)oc?pc((wc(e)*(o=yc(r))*wc(n)-wc(r)*(i=yc(e))*wc(t))/(i*o*a)):(e+r)/2}(n,r,o,a),t.point(i,r),t.lineEnd(),t.lineStart(),t.point(s,r),e=0),t.point(n=o,r=a),i=s},lineEnd:function(){t.lineEnd(),n=r=NaN},clean:function(){return 2-e}}}),(function(t,e,n,r){var i;if(null==t)i=n*uc,r.point(-sc,i),r.point(0,i),r.point(sc,i),r.point(sc,0),r.point(sc,-i),r.point(0,-i),r.point(-sc,-i),r.point(-sc,0),r.point(-sc,i);else if(dc(t[0]-e[0])>oc){var o=t[0]0,i=dc(e)>oc;function o(t,n){return yc(t)*yc(n)>e}function a(t,n,r){var i=[1,0,0],o=Zc(Kc(t),Kc(n)),a=Xc(o,o),s=o[0],u=a-s*s;if(!u)return!r&&t;var c=e*a/u,f=-e*s/u,l=Zc(i,o),h=Qc(i,c);Jc(h,Qc(o,f));var d=l,p=Xc(h,d),g=Xc(d,d),y=p*p-g*(Xc(h,h)-1);if(!(y<0)){var b=kc(y),m=Qc(d,(-p-b)/g);if(Jc(m,h),m=Wc(m),!r)return m;var v,_=t[0],w=n[0],x=t[1],k=n[1];w<_&&(v=_,_=w,w=v);var E=w-_,A=dc(E-sc)0^m[1]<(dc(m[0]-_)sc^(_<=m[0]&&m[0]<=w)){var S=Qc(d,(-p+b)/g);return Jc(S,h),[m,Wc(S)]}}}function s(e,n){var i=r?t:sc-t,o=0;return e<-i?o|=1:e>i&&(o|=2),n<-i?o|=4:n>i&&(o|=8),o}return ml(o,(function(t){var e,n,u,c,f;return{lineStart:function(){c=u=!1,f=1},point:function(l,h){var d,p=[l,h],g=o(l,h),y=r?g?0:s(l,h):g?s(l+(l<0?sc:-sc),h):0;if(!e&&(c=u=g)&&t.lineStart(),g!==u&&(!(d=a(e,p))||ll(e,d)||ll(p,d))&&(p[0]+=oc,p[1]+=oc,g=o(p[0],p[1])),g!==u)f=0,g?(t.lineStart(),d=a(p,e),t.point(d[0],d[1])):(d=a(e,p),t.point(d[0],d[1]),t.lineEnd()),e=d;else if(i&&e&&r^g){var b;y&n||!(b=a(p,e,!0))||(f=0,r?(t.lineStart(),t.point(b[0][0],b[0][1]),t.point(b[1][0],b[1][1]),t.lineEnd()):(t.point(b[1][0],b[1][1]),t.lineEnd(),t.lineStart(),t.point(b[0][0],b[0][1])))}!g||e&&ll(e,p)||t.point(p[0],p[1]),e=p,u=g,n=y},lineEnd:function(){u&&t.lineEnd(),e=null},clean:function(){return f|(c&&u)<<1}}}),(function(e,r,i,o){sl(o,t,n,i,e,r)}),r?[0,-t]:[-sc,t-sc])},kl=function(t,e,n,r,i,o){var a,s=t[0],u=t[1],c=0,f=1,l=e[0]-s,h=e[1]-u;if(a=n-s,l||!(a>0)){if(a/=l,l<0){if(a0){if(a>f)return;a>c&&(c=a)}if(a=i-s,l||!(a<0)){if(a/=l,l<0){if(a>f)return;a>c&&(c=a)}else if(l>0){if(a0)){if(a/=h,h<0){if(a0){if(a>f)return;a>c&&(c=a)}if(a=o-u,h||!(a<0)){if(a/=h,h<0){if(a>f)return;a>c&&(c=a)}else if(h>0){if(a0&&(t[0]=s+c*l,t[1]=u+c*h),f<1&&(e[0]=s+f*l,e[1]=u+f*h),!0}}}}},El=1e9,Al=-El;function Sl(t,e,n,r){function i(i,o){return t<=i&&i<=n&&e<=o&&o<=r}function o(i,o,s,c){var f=0,l=0;if(null==i||(f=a(i,s))!==(l=a(o,s))||u(i,o)<0^s>0)do{c.point(0===f||3===f?t:n,f>1?r:e)}while((f=(f+s+4)%4)!==l);else c.point(o[0],o[1])}function a(r,i){return dc(r[0]-t)0?0:3:dc(r[0]-n)0?2:1:dc(r[1]-e)0?1:0:i>0?3:2}function s(t,e){return u(t.x,e.x)}function u(t,e){var n=a(t,1),r=a(e,1);return n!==r?n-r:0===n?e[1]-t[1]:1===n?t[0]-e[0]:2===n?t[1]-e[1]:e[0]-t[0]}return function(a){var u,c,f,l,h,d,p,g,y,b,m,v=a,_=fl(),w={point:x,lineStart:function(){w.point=k,c&&c.push(f=[]);b=!0,y=!1,p=g=NaN},lineEnd:function(){u&&(k(l,h),d&&y&&_.rejoin(),u.push(_.result()));w.point=x,y&&v.lineEnd()},polygonStart:function(){v=_,u=[],c=[],m=!0},polygonEnd:function(){var e=function(){for(var e=0,n=0,i=c.length;nr&&(h-o)*(r-a)>(d-a)*(t-o)&&++e:d<=r&&(h-o)*(r-a)<(d-a)*(t-o)&&--e;return e}(),n=m&&e,i=(u=P(u)).length;(n||i)&&(a.polygonStart(),n&&(a.lineStart(),o(null,null,1,a),a.lineEnd()),i&&dl(u,s,e,o,a),a.polygonEnd());v=a,u=c=f=null}};function x(t,e){i(t,e)&&v.point(t,e)}function k(o,a){var s=i(o,a);if(c&&f.push([o,a]),b)l=o,h=a,d=s,b=!1,s&&(v.lineStart(),v.point(o,a));else if(s&&y)v.point(o,a);else{var u=[p=Math.max(Al,Math.min(El,p)),g=Math.max(Al,Math.min(El,g))],_=[o=Math.max(Al,Math.min(El,o)),a=Math.max(Al,Math.min(El,a))];kl(u,_,t,e,n,r)?(y||(v.lineStart(),v.point(u[0],u[1])),v.point(_[0],_[1]),s||v.lineEnd(),m=!1):s&&(v.lineStart(),v.point(o,a),m=!1)}p=o,g=a,y=s}return w}}var Tl,Ml,Dl,Cl=function(){var t,e,n,r=0,i=0,o=960,a=500;return n={stream:function(n){return t&&e===n?t:t=Sl(r,i,o,a)(e=n)},extent:function(s){return arguments.length?(r=+s[0][0],i=+s[0][1],o=+s[1][0],a=+s[1][1],t=e=null,n):[[r,i],[o,a]]}}},Ol=ec(),Rl={sphere:Mc,point:Mc,lineStart:function(){Rl.point=Nl,Rl.lineEnd=Il},lineEnd:Mc,polygonStart:Mc,polygonEnd:Mc};function Il(){Rl.point=Rl.lineEnd=Mc}function Nl(t,e){Tl=t*=hc,Ml=wc(e*=hc),Dl=yc(e),Rl.point=Bl}function Bl(t,e){t*=hc;var n=wc(e*=hc),r=yc(e),i=dc(t-Tl),o=yc(i),a=r*wc(i),s=Dl*n-Ml*r*o,u=Ml*n+Dl*r*o;Ol.add(gc(kc(a*a+s*s),u)),Tl=t,Ml=n,Dl=r}var Ll=function(t){return Ol.reset(),qc(t,Rl),+Ol},Pl=[null,null],Fl={type:"LineString",coordinates:Pl},ql=function(t,e){return Pl[0]=t,Pl[1]=e,Ll(Fl)},jl={Feature:function(t,e){return zl(t.geometry,e)},FeatureCollection:function(t,e){for(var n=t.features,r=-1,i=n.length;++r0&&(i=ql(t[o],t[o-1]))>0&&n<=i&&r<=i&&(n+r-i)*(1-Math.pow((n-r)/i,2))oc})).map(u)).concat(x(bc(o/d)*d,i,d).filter((function(t){return dc(t%g)>oc})).map(c))}return b.lines=function(){return m().map((function(t){return{type:"LineString",coordinates:t}}))},b.outline=function(){return{type:"Polygon",coordinates:[f(r).concat(l(a).slice(1),f(n).reverse().slice(1),l(s).reverse().slice(1))]}},b.extent=function(t){return arguments.length?b.extentMajor(t).extentMinor(t):b.extentMinor()},b.extentMajor=function(t){return arguments.length?(r=+t[0][0],n=+t[1][0],s=+t[0][1],a=+t[1][1],r>n&&(t=r,r=n,n=t),s>a&&(t=s,s=a,a=t),b.precision(y)):[[r,s],[n,a]]},b.extentMinor=function(n){return arguments.length?(e=+n[0][0],t=+n[1][0],o=+n[0][1],i=+n[1][1],e>t&&(n=e,e=t,t=n),o>i&&(n=o,o=i,i=n),b.precision(y)):[[e,o],[t,i]]},b.step=function(t){return arguments.length?b.stepMajor(t).stepMinor(t):b.stepMinor()},b.stepMajor=function(t){return arguments.length?(p=+t[0],g=+t[1],b):[p,g]},b.stepMinor=function(t){return arguments.length?(h=+t[0],d=+t[1],b):[h,d]},b.precision=function(h){return arguments.length?(y=+h,u=Kl(o,i,90),c=Xl(e,t,y),f=Kl(s,a,90),l=Xl(r,n,y),b):y},b.extentMajor([[-180,-90+oc],[180,90-oc]]).extentMinor([[-180,-80-oc],[180,80+oc]])}function Jl(){return Zl()()}var Ql,th,eh,nh,rh=function(t,e){var n=t[0]*hc,r=t[1]*hc,i=e[0]*hc,o=e[1]*hc,a=yc(r),s=wc(r),u=yc(o),c=wc(o),f=a*yc(n),l=a*wc(n),h=u*yc(i),d=u*wc(i),p=2*Sc(kc(Tc(o-r)+a*u*Tc(i-n))),g=wc(p),y=p?function(t){var e=wc(t*=p)/g,n=wc(p-t)/g,r=n*f+e*h,i=n*l+e*d,o=n*s+e*c;return[gc(i,r)*lc,gc(o,kc(r*r+i*i))*lc]}:function(){return[n*lc,r*lc]};return y.distance=p,y},ih=function(t){return t},oh=ec(),ah=ec(),sh={point:Mc,lineStart:Mc,lineEnd:Mc,polygonStart:function(){sh.lineStart=uh,sh.lineEnd=lh},polygonEnd:function(){sh.lineStart=sh.lineEnd=sh.point=Mc,oh.add(dc(ah)),ah.reset()},result:function(){var t=oh/2;return oh.reset(),t}};function uh(){sh.point=ch}function ch(t,e){sh.point=fh,Ql=eh=t,th=nh=e}function fh(t,e){ah.add(nh*t-eh*e),eh=t,nh=e}function lh(){fh(Ql,th)}var hh=sh,dh=1/0,ph=dh,gh=-dh,yh=gh;var bh,mh,vh,_h,wh={point:function(t,e){tgh&&(gh=t);eyh&&(yh=e)},lineStart:Mc,lineEnd:Mc,polygonStart:Mc,polygonEnd:Mc,result:function(){var t=[[dh,ph],[gh,yh]];return gh=yh=-(ph=dh=1/0),t}},xh=0,kh=0,Eh=0,Ah=0,Sh=0,Th=0,Mh=0,Dh=0,Ch=0,Oh={point:Rh,lineStart:Ih,lineEnd:Lh,polygonStart:function(){Oh.lineStart=Ph,Oh.lineEnd=Fh},polygonEnd:function(){Oh.point=Rh,Oh.lineStart=Ih,Oh.lineEnd=Lh},result:function(){var t=Ch?[Mh/Ch,Dh/Ch]:Th?[Ah/Th,Sh/Th]:Eh?[xh/Eh,kh/Eh]:[NaN,NaN];return xh=kh=Eh=Ah=Sh=Th=Mh=Dh=Ch=0,t}};function Rh(t,e){xh+=t,kh+=e,++Eh}function Ih(){Oh.point=Nh}function Nh(t,e){Oh.point=Bh,Rh(vh=t,_h=e)}function Bh(t,e){var n=t-vh,r=e-_h,i=kc(n*n+r*r);Ah+=i*(vh+t)/2,Sh+=i*(_h+e)/2,Th+=i,Rh(vh=t,_h=e)}function Lh(){Oh.point=Rh}function Ph(){Oh.point=qh}function Fh(){jh(bh,mh)}function qh(t,e){Oh.point=jh,Rh(bh=vh=t,mh=_h=e)}function jh(t,e){var n=t-vh,r=e-_h,i=kc(n*n+r*r);Ah+=i*(vh+t)/2,Sh+=i*(_h+e)/2,Th+=i,Mh+=(i=_h*t-vh*e)*(vh+t),Dh+=i*(_h+e),Ch+=3*i,Rh(vh=t,_h=e)}var Uh=Oh;function zh(t){this._context=t}zh.prototype={_radius:4.5,pointRadius:function(t){return this._radius=t,this},polygonStart:function(){this._line=0},polygonEnd:function(){this._line=NaN},lineStart:function(){this._point=0},lineEnd:function(){0===this._line&&this._context.closePath(),this._point=NaN},point:function(t,e){switch(this._point){case 0:this._context.moveTo(t,e),this._point=1;break;case 1:this._context.lineTo(t,e);break;default:this._context.moveTo(t+this._radius,e),this._context.arc(t,e,this._radius,0,fc)}},result:Mc};var Yh,Vh,Hh,$h,Gh,Wh=ec(),Kh={point:Mc,lineStart:function(){Kh.point=Xh},lineEnd:function(){Yh&&Zh(Vh,Hh),Kh.point=Mc},polygonStart:function(){Yh=!0},polygonEnd:function(){Yh=null},result:function(){var t=+Wh;return Wh.reset(),t}};function Xh(t,e){Kh.point=Zh,Vh=$h=t,Hh=Gh=e}function Zh(t,e){$h-=t,Gh-=e,Wh.add(kc($h*$h+Gh*Gh)),$h=t,Gh=e}var Jh=Kh;function Qh(){this._string=[]}function td(t){return"m0,"+t+"a"+t+","+t+" 0 1,1 0,"+-2*t+"a"+t+","+t+" 0 1,1 0,"+2*t+"z"}Qh.prototype={_radius:4.5,_circle:td(4.5),pointRadius:function(t){return(t=+t)!==this._radius&&(this._radius=t,this._circle=null),this},polygonStart:function(){this._line=0},polygonEnd:function(){this._line=NaN},lineStart:function(){this._point=0},lineEnd:function(){0===this._line&&this._string.push("Z"),this._point=NaN},point:function(t,e){switch(this._point){case 0:this._string.push("M",t,",",e),this._point=1;break;case 1:this._string.push("L",t,",",e);break;default:null==this._circle&&(this._circle=td(this._radius)),this._string.push("M",t,",",e,this._circle)}},result:function(){if(this._string.length){var t=this._string.join("");return this._string=[],t}return null}};var ed=function(t,e){var n,r,i=4.5;function o(t){return t&&("function"==typeof i&&r.pointRadius(+i.apply(this,arguments)),qc(t,n(r))),r.result()}return o.area=function(t){return qc(t,n(hh)),hh.result()},o.measure=function(t){return qc(t,n(Jh)),Jh.result()},o.bounds=function(t){return qc(t,n(wh)),wh.result()},o.centroid=function(t){return qc(t,n(Uh)),Uh.result()},o.projection=function(e){return arguments.length?(n=null==e?(t=null,ih):(t=e).stream,o):t},o.context=function(t){return arguments.length?(r=null==t?(e=null,new Qh):new zh(e=t),"function"!=typeof i&&r.pointRadius(i),o):e},o.pointRadius=function(t){return arguments.length?(i="function"==typeof t?t:(r.pointRadius(+t),+t),o):i},o.projection(t).context(e)},nd=function(t){return{stream:rd(t)}};function rd(t){return function(e){var n=new id;for(var r in t)n[r]=t[r];return n.stream=e,n}}function id(){}function od(t,e,n){var r=t.clipExtent&&t.clipExtent();return t.scale(150).translate([0,0]),null!=r&&t.clipExtent(null),qc(n,t.stream(wh)),e(wh.result()),null!=r&&t.clipExtent(r),t}function ad(t,e,n){return od(t,(function(n){var r=e[1][0]-e[0][0],i=e[1][1]-e[0][1],o=Math.min(r/(n[1][0]-n[0][0]),i/(n[1][1]-n[0][1])),a=+e[0][0]+(r-o*(n[1][0]+n[0][0]))/2,s=+e[0][1]+(i-o*(n[1][1]+n[0][1]))/2;t.scale(150*o).translate([a,s])}),n)}function sd(t,e,n){return ad(t,[[0,0],e],n)}function ud(t,e,n){return od(t,(function(n){var r=+e,i=r/(n[1][0]-n[0][0]),o=(r-i*(n[1][0]+n[0][0]))/2,a=-i*n[0][1];t.scale(150*i).translate([o,a])}),n)}function cd(t,e,n){return od(t,(function(n){var r=+e,i=r/(n[1][1]-n[0][1]),o=-i*n[0][0],a=(r-i*(n[1][1]+n[0][1]))/2;t.scale(150*i).translate([o,a])}),n)}id.prototype={constructor:id,point:function(t,e){this.stream.point(t,e)},sphere:function(){this.stream.sphere()},lineStart:function(){this.stream.lineStart()},lineEnd:function(){this.stream.lineEnd()},polygonStart:function(){this.stream.polygonStart()},polygonEnd:function(){this.stream.polygonEnd()}};var fd=16,ld=yc(30*hc),hd=function(t,e){return+e?function(t,e){function n(r,i,o,a,s,u,c,f,l,h,d,p,g,y){var b=c-r,m=f-i,v=b*b+m*m;if(v>4*e&&g--){var _=a+h,w=s+d,x=u+p,k=kc(_*_+w*w+x*x),E=Sc(x/=k),A=dc(dc(x)-1)e||dc((b*D+m*C)/v-.5)>.3||a*h+s*d+u*p2?t[2]%360*hc:0,T()):[y*lc,b*lc,m*lc]},A.angle=function(t){return arguments.length?(v=t%360*hc,T()):v*lc},A.precision=function(t){return arguments.length?(a=hd(s,E=t*t),M()):kc(E)},A.fitExtent=function(t,e){return ad(A,t,e)},A.fitSize=function(t,e){return sd(A,t,e)},A.fitWidth=function(t,e){return ud(A,t,e)},A.fitHeight=function(t,e){return cd(A,t,e)},function(){return e=t.apply(this,arguments),A.invert=e.invert&&S,T()}}function md(t){var e=0,n=sc/3,r=bd(t),i=r(e,n);return i.parallels=function(t){return arguments.length?r(e=t[0]*hc,n=t[1]*hc):[e*lc,n*lc]},i}function vd(t,e){var n=wc(t),r=(n+wc(e))/2;if(dc(r)=.12&&i<.234&&r>=-.425&&r<-.214?s:i>=.166&&i<.234&&r>=-.214&&r<-.115?u:a).invert(t)},f.stream=function(n){return t&&e===n?t:(r=[a.stream(e=n),s.stream(n),u.stream(n)],i=r.length,t={point:function(t,e){for(var n=-1;++n0?e<-uc+oc&&(e=-uc+oc):e>uc-oc&&(e=uc-oc);var n=i/_c(Rd(e),r);return[n*wc(r*t),i-n*yc(r*t)]}return o.invert=function(t,e){var n=i-e,o=xc(r)*kc(t*t+n*n);return[gc(t,dc(n))/r*xc(n),2*pc(_c(i/o,1/r))-uc]},o}var Nd=function(){return md(Id).scale(109.5).parallels([30,30])};function Bd(t,e){return[t,e]}Bd.invert=Bd;var Ld=function(){return yd(Bd).scale(152.63)};function Pd(t,e){var n=yc(t),r=t===e?wc(t):(n-yc(e))/(e-t),i=n/r+t;if(dc(r)oc&&--i>0);return[t/(.8707+(o=r*r)*(o*(o*o*o*(.003971-.001529*o)-.013791)-.131979)),r]};var Zd=function(){return yd(Xd).scale(175.295)};function Jd(t,e){return[yc(e)*wc(t),wc(e)]}Jd.invert=Ed(Sc);var Qd=function(){return yd(Jd).scale(249.5).clipAngle(90+oc)};function tp(t,e){var n=yc(e),r=1+yc(t)*n;return[n*wc(t)/r,wc(e)/r]}tp.invert=Ed((function(t){return 2*pc(t)}));var ep=function(){return yd(tp).scale(250).clipAngle(142)};function np(t,e){return[vc(Ec((uc+e)/2)),-t]}np.invert=function(t,e){return[-e,2*pc(mc(t))-uc]};var rp=function(){var t=Od(np),e=t.center,n=t.rotate;return t.center=function(t){return arguments.length?e([-t[1],t[0]]):[(t=e())[1],-t[0]]},t.rotate=function(t){return arguments.length?n([t[0],t[1],t.length>2?t[2]+90:90]):[(t=n())[0],t[1],t[2]-90]},n([0,0,90]).scale(159.155)};function ip(t,e){return t.parent===e.parent?1:2}function op(t,e){return t+e.x}function ap(t,e){return Math.max(t,e.y)}var sp=function(){var t=ip,e=1,n=1,r=!1;function i(i){var o,a=0;i.eachAfter((function(e){var n=e.children;n?(e.x=function(t){return t.reduce(op,0)/t.length}(n),e.y=function(t){return 1+t.reduce(ap,0)}(n)):(e.x=o?a+=t(e,o):0,e.y=0,o=e)}));var s=function(t){for(var e;e=t.children;)t=e[0];return t}(i),u=function(t){for(var e;e=t.children;)t=e[e.length-1];return t}(i),c=s.x-t(s,u)/2,f=u.x+t(u,s)/2;return i.eachAfter(r?function(t){t.x=(t.x-i.x)*e,t.y=(i.y-t.y)*n}:function(t){t.x=(t.x-c)/(f-c)*e,t.y=(1-(i.y?t.y/i.y:1))*n})}return i.separation=function(e){return arguments.length?(t=e,i):t},i.size=function(t){return arguments.length?(r=!1,e=+t[0],n=+t[1],i):r?null:[e,n]},i.nodeSize=function(t){return arguments.length?(r=!0,e=+t[0],n=+t[1],i):r?[e,n]:null},i};function up(t){var e=0,n=t.children,r=n&&n.length;if(r)for(;--r>=0;)e+=n[r].value;else e=1;t.value=e}function cp(t,e){var n,r,i,o,a,s=new dp(t),u=+t.value&&(s.value=t.value),c=[s];for(null==e&&(e=fp);n=c.pop();)if(u&&(n.value=+n.data.value),(i=e(n.data))&&(a=i.length))for(n.children=new Array(a),o=a-1;o>=0;--o)c.push(r=n.children[o]=new dp(i[o])),r.parent=n,r.depth=n.depth+1;return s.eachBefore(hp)}function fp(t){return t.children}function lp(t){t.data=t.data.data}function hp(t){var e=0;do{t.height=e}while((t=t.parent)&&t.height<++e)}function dp(t){this.data=t,this.depth=this.height=0,this.parent=null}dp.prototype=cp.prototype={constructor:dp,count:function(){return this.eachAfter(up)},each:function(t){var e,n,r,i,o=this,a=[o];do{for(e=a.reverse(),a=[];o=e.pop();)if(t(o),n=o.children)for(r=0,i=n.length;r=0;--n)i.push(e[n]);return this},sum:function(t){return this.eachAfter((function(e){for(var n=+t(e.data)||0,r=e.children,i=r&&r.length;--i>=0;)n+=r[i].value;e.value=n}))},sort:function(t){return this.eachBefore((function(e){e.children&&e.children.sort(t)}))},path:function(t){for(var e=this,n=function(t,e){if(t===e)return t;var n=t.ancestors(),r=e.ancestors(),i=null;t=n.pop(),e=r.pop();for(;t===e;)i=t,t=n.pop(),e=r.pop();return i}(e,t),r=[e];e!==n;)e=e.parent,r.push(e);for(var i=r.length;t!==n;)r.splice(i,0,t),t=t.parent;return r},ancestors:function(){for(var t=this,e=[t];t=t.parent;)e.push(t);return e},descendants:function(){var t=[];return this.each((function(e){t.push(e)})),t},leaves:function(){var t=[];return this.eachBefore((function(e){e.children||t.push(e)})),t},links:function(){var t=this,e=[];return t.each((function(n){n!==t&&e.push({source:n.parent,target:n})})),e},copy:function(){return cp(this).eachBefore(lp)}};var pp=Array.prototype.slice;var gp=function(t){for(var e,n,r=0,i=(t=function(t){for(var e,n,r=t.length;r;)n=Math.random()*r--|0,e=t[r],t[r]=t[n],t[n]=e;return t}(pp.call(t))).length,o=[];r0&&n*n>r*r+i*i}function vp(t,e){for(var n=0;n(a*=a)?(r=(c+a-i)/(2*c),o=Math.sqrt(Math.max(0,a/c-r*r)),n.x=t.x-r*s-o*u,n.y=t.y-r*u+o*s):(r=(c+i-a)/(2*c),o=Math.sqrt(Math.max(0,i/c-r*r)),n.x=e.x+r*s-o*u,n.y=e.y+r*u+o*s)):(n.x=e.x+n.r,n.y=e.y)}function Ep(t,e){var n=t.r+e.r-1e-6,r=e.x-t.x,i=e.y-t.y;return n>0&&n*n>r*r+i*i}function Ap(t){var e=t._,n=t.next._,r=e.r+n.r,i=(e.x*n.r+n.x*e.r)/r,o=(e.y*n.r+n.y*e.r)/r;return i*i+o*o}function Sp(t){this._=t,this.next=null,this.previous=null}function Tp(t){if(!(i=t.length))return 0;var e,n,r,i,o,a,s,u,c,f,l;if((e=t[0]).x=0,e.y=0,!(i>1))return e.r;if(n=t[1],e.x=-n.r,n.x=e.r,n.y=0,!(i>2))return e.r+n.r;kp(n,e,r=t[2]),e=new Sp(e),n=new Sp(n),r=new Sp(r),e.next=r.previous=n,n.next=e.previous=r,r.next=n.previous=e;t:for(s=3;s0)throw new Error("cycle");return o}return n.id=function(e){return arguments.length?(t=Cp(e),n):t},n.parentId=function(t){return arguments.length?(e=Cp(t),n):e},n};function Gp(t,e){return t.parent===e.parent?1:2}function Wp(t){var e=t.children;return e?e[0]:t.t}function Kp(t){var e=t.children;return e?e[e.length-1]:t.t}function Xp(t,e,n){var r=n/(e.i-t.i);e.c-=r,e.s+=n,t.c+=r,e.z+=n,e.m+=n}function Zp(t,e,n){return t.a.parent===e.parent?t.a:n}function Jp(t,e){this._=t,this.parent=null,this.children=null,this.A=null,this.a=this,this.z=0,this.m=0,this.c=0,this.s=0,this.t=null,this.i=e}Jp.prototype=Object.create(dp.prototype);var Qp=function(){var t=Gp,e=1,n=1,r=null;function i(i){var u=function(t){for(var e,n,r,i,o,a=new Jp(t,0),s=[a];e=s.pop();)if(r=e._.children)for(e.children=new Array(o=r.length),i=o-1;i>=0;--i)s.push(n=e.children[i]=new Jp(r[i],i)),n.parent=e;return(a.parent=new Jp(null,0)).children=[a],a}(i);if(u.eachAfter(o),u.parent.m=-u.z,u.eachBefore(a),r)i.eachBefore(s);else{var c=i,f=i,l=i;i.eachBefore((function(t){t.xf.x&&(f=t),t.depth>l.depth&&(l=t)}));var h=c===f?1:t(c,f)/2,d=h-c.x,p=e/(f.x+h+d),g=n/(l.depth||1);i.eachBefore((function(t){t.x=(t.x+d)*p,t.y=t.depth*g}))}return i}function o(e){var n=e.children,r=e.parent.children,i=e.i?r[e.i-1]:null;if(n){!function(t){for(var e,n=0,r=0,i=t.children,o=i.length;--o>=0;)(e=i[o]).z+=n,e.m+=n,n+=e.s+(r+=e.c)}(e);var o=(n[0].z+n[n.length-1].z)/2;i?(e.z=i.z+t(e._,i._),e.m=e.z-o):e.z=o}else i&&(e.z=i.z+t(e._,i._));e.parent.A=function(e,n,r){if(n){for(var i,o=e,a=e,s=n,u=o.parent.children[0],c=o.m,f=a.m,l=s.m,h=u.m;s=Kp(s),o=Wp(o),s&&o;)u=Wp(u),(a=Kp(a)).a=e,(i=s.z+l-o.z-c+t(s._,o._))>0&&(Xp(Zp(s,e,r),e,i),c+=i,f+=i),l+=s.m,c+=o.m,h+=u.m,f+=a.m;s&&!Kp(a)&&(a.t=s,a.m+=l-f),o&&!Wp(u)&&(u.t=o,u.m+=c-h,r=e)}return r}(e,i,e.parent.A||r[0])}function a(t){t._.x=t.z+t.parent.m,t.m+=t.parent.m}function s(t){t.x*=e,t.y=t.depth*n}return i.separation=function(e){return arguments.length?(t=e,i):t},i.size=function(t){return arguments.length?(r=!1,e=+t[0],n=+t[1],i):r?null:[e,n]},i.nodeSize=function(t){return arguments.length?(r=!0,e=+t[0],n=+t[1],i):r?[e,n]:null},i},tg=function(t,e,n,r,i){for(var o,a=t.children,s=-1,u=a.length,c=t.value&&(i-n)/t.value;++sh&&(h=s),y=f*f*g,(d=Math.max(h/y,y/l))>p){f-=s;break}p=d}b.push(a={value:f,dice:u1?e:1)},n}(eg),ig=function(){var t=rg,e=!1,n=1,r=1,i=[0],o=Op,a=Op,s=Op,u=Op,c=Op;function f(t){return t.x0=t.y0=0,t.x1=n,t.y1=r,t.eachBefore(l),i=[0],e&&t.eachBefore(Fp),t}function l(e){var n=i[e.depth],r=e.x0+n,f=e.y0+n,l=e.x1-n,h=e.y1-n;l=n-1){var f=s[e];return f.x0=i,f.y0=o,f.x1=a,void(f.y1=u)}var l=c[e],h=r/2+l,d=e+1,p=n-1;for(;d>>1;c[g]u-o){var m=(i*b+a*y)/r;t(e,d,y,i,o,m,u),t(d,n,b,m,o,a,u)}else{var v=(o*b+u*y)/r;t(e,d,y,i,o,a,v),t(d,n,b,i,v,a,u)}}(0,u,t.value,e,n,r,i)},ag=function(t,e,n,r,i){(1&t.depth?tg:qp)(t,e,n,r,i)},sg=function t(e){function n(t,n,r,i,o){if((a=t._squarify)&&a.ratio===e)for(var a,s,u,c,f,l=-1,h=a.length,d=t.value;++l1?e:1)},n}(eg),ug=function(t){for(var e,n=-1,r=t.length,i=t[r-1],o=0;++n1&&fg(t[n[r-2]],t[n[r-1]],t[i])<=0;)--r;n[r++]=i}return n.slice(0,r)}var dg=function(t){if((n=t.length)<3)return null;var e,n,r=new Array(n),i=new Array(n);for(e=0;e=0;--e)c.push(t[r[o[e]][2]]);for(e=+s;es!=c>s&&a<(u-n)*(s-r)/(c-r)+n&&(f=!f),u=n,c=r;return f},gg=function(t){for(var e,n,r=-1,i=t.length,o=t[i-1],a=o[0],s=o[1],u=0;++r1);return t+n*o*Math.sqrt(-2*Math.log(i)/i)}}return n.source=t,n}(yg),vg=function t(e){function n(){var t=mg.source(e).apply(this,arguments);return function(){return Math.exp(t())}}return n.source=t,n}(yg),_g=function t(e){function n(t){return function(){for(var n=0,r=0;rr&&(e=n,n=r,r=e),function(t){return Math.max(n,Math.min(r,t))}}function Fg(t,e,n){var r=t[0],i=t[1],o=e[0],a=e[1];return i2?qg:Fg,i=o=null,l}function l(e){return isNaN(e=+e)?n:(i||(i=r(a.map(t),s,u)))(t(c(e)))}return l.invert=function(n){return c(e((o||(o=r(s,a.map(t),Un)))(n)))},l.domain=function(t){return arguments.length?(a=Sg.call(t,Ig),c===Bg||(c=Pg(a)),f()):a.slice()},l.range=function(t){return arguments.length?(s=Tg.call(t),f()):s.slice()},l.rangeRound=function(t){return s=Tg.call(t),u=Qn,f()},l.clamp=function(t){return arguments.length?(c=t?Pg(a):Bg,l):c!==Bg},l.interpolate=function(t){return arguments.length?(u=t,f()):u},l.unknown=function(t){return arguments.length?(n=t,l):n},function(n,r){return t=n,e=r,f()}}function zg(t,e){return Ug()(t,e)}var Yg=function(t,e,n,r){var i,o=M(t,e,n);switch((r=Fu(null==r?",f":r)).type){case"s":var a=Math.max(Math.abs(t),Math.abs(e));return null!=r.precision||isNaN(i=Qu(o,a))||(r.precision=i),Yu(r,a);case"":case"e":case"g":case"p":case"r":null!=r.precision||isNaN(i=tc(o,Math.max(Math.abs(t),Math.abs(e))))||(r.precision=i-("e"===r.type));break;case"f":case"%":null!=r.precision||isNaN(i=Ju(o))||(r.precision=i-2*("%"===r.type))}return zu(r)};function Vg(t){var e=t.domain;return t.ticks=function(t){var n=e();return S(n[0],n[n.length-1],null==t?10:t)},t.tickFormat=function(t,n){var r=e();return Yg(r[0],r[r.length-1],null==t?10:t,n)},t.nice=function(n){null==n&&(n=10);var r,i=e(),o=0,a=i.length-1,s=i[o],u=i[a];return u0?r=T(s=Math.floor(s/r)*r,u=Math.ceil(u/r)*r,n):r<0&&(r=T(s=Math.ceil(s*r)/r,u=Math.floor(u*r)/r,n)),r>0?(i[o]=Math.floor(s/r)*r,i[a]=Math.ceil(u/r)*r,e(i)):r<0&&(i[o]=Math.ceil(s*r)/r,i[a]=Math.floor(u*r)/r,e(i)),t},t}function Hg(){var t=zg(Bg,Bg);return t.copy=function(){return jg(t,Hg())},kg.apply(t,arguments),Vg(t)}function $g(t){var e;function n(t){return isNaN(t=+t)?e:t}return n.invert=n,n.domain=n.range=function(e){return arguments.length?(t=Sg.call(e,Ig),n):t.slice()},n.unknown=function(t){return arguments.length?(e=t,n):e},n.copy=function(){return $g(t).unknown(e)},t=arguments.length?Sg.call(t,Ig):[0,1],Vg(n)}var Gg=function(t,e){var n,r=0,i=(t=t.slice()).length-1,o=t[r],a=t[i];return a0){for(;hu)break;g.push(l)}}else for(;h=1;--f)if(!((l=c*f)u)break;g.push(l)}}else g=S(h,d,Math.min(d-h,p)).map(n);return r?g.reverse():g},r.tickFormat=function(t,i){if(null==i&&(i=10===o?".0e":","),"function"!=typeof i&&(i=zu(i)),t===1/0)return i;null==t&&(t=10);var a=Math.max(1,o*t/r.ticks().length);return function(t){var r=t/n(Math.round(e(t)));return r*o0?i[r-1]:e[0],r=r?[i[r-1],n]:[i[a-1],i[a]]},a.unknown=function(e){return arguments.length?(t=e,a):a},a.thresholds=function(){return i.slice()},a.copy=function(){return dy().domain([e,n]).range(o).unknown(t)},kg.apply(Vg(a),arguments)}function py(){var t,e=[.5],n=[0,1],r=1;function i(i){return i<=i?n[u(e,i,0,r)]:t}return i.domain=function(t){return arguments.length?(e=Tg.call(t),r=Math.min(e.length,n.length-1),i):e.slice()},i.range=function(t){return arguments.length?(n=Tg.call(t),r=Math.min(e.length,n.length-1),i):n.slice()},i.invertExtent=function(t){var r=n.indexOf(t);return[e[r-1],e[r]]},i.unknown=function(e){return arguments.length?(t=e,i):t},i.copy=function(){return py().domain(e).range(n).unknown(t)},kg.apply(i,arguments)}var gy=new Date,yy=new Date;function by(t,e,n,r){function i(e){return t(e=0===arguments.length?new Date:new Date(+e)),e}return i.floor=function(e){return t(e=new Date(+e)),e},i.ceil=function(n){return t(n=new Date(n-1)),e(n,1),t(n),n},i.round=function(t){var e=i(t),n=i.ceil(t);return t-e0))return s;do{s.push(a=new Date(+n)),e(n,o),t(n)}while(a=e)for(;t(e),!n(e);)e.setTime(e-1)}),(function(t,r){if(t>=t)if(r<0)for(;++r<=0;)for(;e(t,-1),!n(t););else for(;--r>=0;)for(;e(t,1),!n(t););}))},n&&(i.count=function(e,r){return gy.setTime(+e),yy.setTime(+r),t(gy),t(yy),Math.floor(n(gy,yy))},i.every=function(t){return t=Math.floor(t),isFinite(t)&&t>0?t>1?i.filter(r?function(e){return r(e)%t==0}:function(e){return i.count(0,e)%t==0}):i:null}),i}var my=by((function(t){t.setMonth(0,1),t.setHours(0,0,0,0)}),(function(t,e){t.setFullYear(t.getFullYear()+e)}),(function(t,e){return e.getFullYear()-t.getFullYear()}),(function(t){return t.getFullYear()}));my.every=function(t){return isFinite(t=Math.floor(t))&&t>0?by((function(e){e.setFullYear(Math.floor(e.getFullYear()/t)*t),e.setMonth(0,1),e.setHours(0,0,0,0)}),(function(e,n){e.setFullYear(e.getFullYear()+n*t)})):null};var vy=my,_y=my.range,wy=by((function(t){t.setDate(1),t.setHours(0,0,0,0)}),(function(t,e){t.setMonth(t.getMonth()+e)}),(function(t,e){return e.getMonth()-t.getMonth()+12*(e.getFullYear()-t.getFullYear())}),(function(t){return t.getMonth()})),xy=wy,ky=wy.range,Ey=6e4,Ay=6048e5;function Sy(t){return by((function(e){e.setDate(e.getDate()-(e.getDay()+7-t)%7),e.setHours(0,0,0,0)}),(function(t,e){t.setDate(t.getDate()+7*e)}),(function(t,e){return(e-t-(e.getTimezoneOffset()-t.getTimezoneOffset())*Ey)/Ay}))}var Ty=Sy(0),My=Sy(1),Dy=Sy(2),Cy=Sy(3),Oy=Sy(4),Ry=Sy(5),Iy=Sy(6),Ny=Ty.range,By=My.range,Ly=Dy.range,Py=Cy.range,Fy=Oy.range,qy=Ry.range,jy=Iy.range,Uy=by((function(t){t.setHours(0,0,0,0)}),(function(t,e){t.setDate(t.getDate()+e)}),(function(t,e){return(e-t-(e.getTimezoneOffset()-t.getTimezoneOffset())*Ey)/864e5}),(function(t){return t.getDate()-1})),zy=Uy,Yy=Uy.range,Vy=by((function(t){t.setTime(t-t.getMilliseconds()-1e3*t.getSeconds()-t.getMinutes()*Ey)}),(function(t,e){t.setTime(+t+36e5*e)}),(function(t,e){return(e-t)/36e5}),(function(t){return t.getHours()})),Hy=Vy,$y=Vy.range,Gy=by((function(t){t.setTime(t-t.getMilliseconds()-1e3*t.getSeconds())}),(function(t,e){t.setTime(+t+e*Ey)}),(function(t,e){return(e-t)/Ey}),(function(t){return t.getMinutes()})),Wy=Gy,Ky=Gy.range,Xy=by((function(t){t.setTime(t-t.getMilliseconds())}),(function(t,e){t.setTime(+t+1e3*e)}),(function(t,e){return(e-t)/1e3}),(function(t){return t.getUTCSeconds()})),Zy=Xy,Jy=Xy.range,Qy=by((function(){}),(function(t,e){t.setTime(+t+e)}),(function(t,e){return e-t}));Qy.every=function(t){return t=Math.floor(t),isFinite(t)&&t>0?t>1?by((function(e){e.setTime(Math.floor(e/t)*t)}),(function(e,n){e.setTime(+e+n*t)}),(function(e,n){return(n-e)/t})):Qy:null};var tb=Qy,eb=Qy.range;function nb(t){return by((function(e){e.setUTCDate(e.getUTCDate()-(e.getUTCDay()+7-t)%7),e.setUTCHours(0,0,0,0)}),(function(t,e){t.setUTCDate(t.getUTCDate()+7*e)}),(function(t,e){return(e-t)/Ay}))}var rb=nb(0),ib=nb(1),ob=nb(2),ab=nb(3),sb=nb(4),ub=nb(5),cb=nb(6),fb=rb.range,lb=ib.range,hb=ob.range,db=ab.range,pb=sb.range,gb=ub.range,yb=cb.range,bb=by((function(t){t.setUTCHours(0,0,0,0)}),(function(t,e){t.setUTCDate(t.getUTCDate()+e)}),(function(t,e){return(e-t)/864e5}),(function(t){return t.getUTCDate()-1})),mb=bb,vb=bb.range,_b=by((function(t){t.setUTCMonth(0,1),t.setUTCHours(0,0,0,0)}),(function(t,e){t.setUTCFullYear(t.getUTCFullYear()+e)}),(function(t,e){return e.getUTCFullYear()-t.getUTCFullYear()}),(function(t){return t.getUTCFullYear()}));_b.every=function(t){return isFinite(t=Math.floor(t))&&t>0?by((function(e){e.setUTCFullYear(Math.floor(e.getUTCFullYear()/t)*t),e.setUTCMonth(0,1),e.setUTCHours(0,0,0,0)}),(function(e,n){e.setUTCFullYear(e.getUTCFullYear()+n*t)})):null};var wb=_b,xb=_b.range;function kb(t){if(0<=t.y&&t.y<100){var e=new Date(-1,t.m,t.d,t.H,t.M,t.S,t.L);return e.setFullYear(t.y),e}return new Date(t.y,t.m,t.d,t.H,t.M,t.S,t.L)}function Eb(t){if(0<=t.y&&t.y<100){var e=new Date(Date.UTC(-1,t.m,t.d,t.H,t.M,t.S,t.L));return e.setUTCFullYear(t.y),e}return new Date(Date.UTC(t.y,t.m,t.d,t.H,t.M,t.S,t.L))}function Ab(t){return{y:t,m:0,d:1,H:0,M:0,S:0,L:0}}function Sb(t){var e=t.dateTime,n=t.date,r=t.time,i=t.periods,o=t.days,a=t.shortDays,s=t.months,u=t.shortMonths,c=Fb(i),f=qb(i),l=Fb(o),h=qb(o),d=Fb(a),p=qb(a),g=Fb(s),y=qb(s),b=Fb(u),m=qb(u),v={a:function(t){return a[t.getDay()]},A:function(t){return o[t.getDay()]},b:function(t){return u[t.getMonth()]},B:function(t){return s[t.getMonth()]},c:null,d:om,e:om,f:fm,H:am,I:sm,j:um,L:cm,m:lm,M:hm,p:function(t){return i[+(t.getHours()>=12)]},Q:jm,s:Um,S:dm,u:pm,U:gm,V:ym,w:bm,W:mm,x:null,X:null,y:vm,Y:_m,Z:wm,"%":qm},_={a:function(t){return a[t.getUTCDay()]},A:function(t){return o[t.getUTCDay()]},b:function(t){return u[t.getUTCMonth()]},B:function(t){return s[t.getUTCMonth()]},c:null,d:xm,e:xm,f:Tm,H:km,I:Em,j:Am,L:Sm,m:Mm,M:Dm,p:function(t){return i[+(t.getUTCHours()>=12)]},Q:jm,s:Um,S:Cm,u:Om,U:Rm,V:Im,w:Nm,W:Bm,x:null,X:null,y:Lm,Y:Pm,Z:Fm,"%":qm},w={a:function(t,e,n){var r=d.exec(e.slice(n));return r?(t.w=p[r[0].toLowerCase()],n+r[0].length):-1},A:function(t,e,n){var r=l.exec(e.slice(n));return r?(t.w=h[r[0].toLowerCase()],n+r[0].length):-1},b:function(t,e,n){var r=b.exec(e.slice(n));return r?(t.m=m[r[0].toLowerCase()],n+r[0].length):-1},B:function(t,e,n){var r=g.exec(e.slice(n));return r?(t.m=y[r[0].toLowerCase()],n+r[0].length):-1},c:function(t,n,r){return E(t,e,n,r)},d:Kb,e:Kb,f:em,H:Zb,I:Zb,j:Xb,L:tm,m:Wb,M:Jb,p:function(t,e,n){var r=c.exec(e.slice(n));return r?(t.p=f[r[0].toLowerCase()],n+r[0].length):-1},Q:rm,s:im,S:Qb,u:Ub,U:zb,V:Yb,w:jb,W:Vb,x:function(t,e,r){return E(t,n,e,r)},X:function(t,e,n){return E(t,r,e,n)},y:$b,Y:Hb,Z:Gb,"%":nm};function x(t,e){return function(n){var r,i,o,a=[],s=-1,u=0,c=t.length;for(n instanceof Date||(n=new Date(+n));++s53)return null;"w"in o||(o.w=1),"Z"in o?(i=(r=Eb(Ab(o.y))).getUTCDay(),r=i>4||0===i?ib.ceil(r):ib(r),r=mb.offset(r,7*(o.V-1)),o.y=r.getUTCFullYear(),o.m=r.getUTCMonth(),o.d=r.getUTCDate()+(o.w+6)%7):(i=(r=e(Ab(o.y))).getDay(),r=i>4||0===i?My.ceil(r):My(r),r=zy.offset(r,7*(o.V-1)),o.y=r.getFullYear(),o.m=r.getMonth(),o.d=r.getDate()+(o.w+6)%7)}else("W"in o||"U"in o)&&("w"in o||(o.w="u"in o?o.u%7:"W"in o?1:0),i="Z"in o?Eb(Ab(o.y)).getUTCDay():e(Ab(o.y)).getDay(),o.m=0,o.d="W"in o?(o.w+6)%7+7*o.W-(i+5)%7:o.w+7*o.U-(i+6)%7);return"Z"in o?(o.H+=o.Z/100|0,o.M+=o.Z%100,Eb(o)):e(o)}}function E(t,e,n,r){for(var i,o,a=0,s=e.length,u=n.length;a=u)return-1;if(37===(i=e.charCodeAt(a++))){if(i=e.charAt(a++),!(o=w[i in Rb?e.charAt(a++):i])||(r=o(t,n,r))<0)return-1}else if(i!=n.charCodeAt(r++))return-1}return r}return(v.x=x(n,v),v.X=x(r,v),v.c=x(e,v),_.x=x(n,_),_.X=x(r,_),_.c=x(e,_),{format:function(t){var e=x(t+="",v);return e.toString=function(){return t},e},parse:function(t){var e=k(t+="",kb);return e.toString=function(){return t},e},utcFormat:function(t){var e=x(t+="",_);return e.toString=function(){return t},e},utcParse:function(t){var e=k(t,Eb);return e.toString=function(){return t},e}})}var Tb,Mb,Db,Cb,Ob,Rb={"-":"",_:" ",0:"0"},Ib=/^\s*\d+/,Nb=/^%/,Bb=/[\\^$*+?|[\]().{}]/g;function Lb(t,e,n){var r=t<0?"-":"",i=(r?-t:t)+"",o=i.length;return r+(o68?1900:2e3),n+r[0].length):-1}function Gb(t,e,n){var r=/^(Z)|([+-]\d\d)(?::?(\d\d))?/.exec(e.slice(n,n+6));return r?(t.Z=r[1]?0:-(r[2]+(r[3]||"00")),n+r[0].length):-1}function Wb(t,e,n){var r=Ib.exec(e.slice(n,n+2));return r?(t.m=r[0]-1,n+r[0].length):-1}function Kb(t,e,n){var r=Ib.exec(e.slice(n,n+2));return r?(t.d=+r[0],n+r[0].length):-1}function Xb(t,e,n){var r=Ib.exec(e.slice(n,n+3));return r?(t.m=0,t.d=+r[0],n+r[0].length):-1}function Zb(t,e,n){var r=Ib.exec(e.slice(n,n+2));return r?(t.H=+r[0],n+r[0].length):-1}function Jb(t,e,n){var r=Ib.exec(e.slice(n,n+2));return r?(t.M=+r[0],n+r[0].length):-1}function Qb(t,e,n){var r=Ib.exec(e.slice(n,n+2));return r?(t.S=+r[0],n+r[0].length):-1}function tm(t,e,n){var r=Ib.exec(e.slice(n,n+3));return r?(t.L=+r[0],n+r[0].length):-1}function em(t,e,n){var r=Ib.exec(e.slice(n,n+6));return r?(t.L=Math.floor(r[0]/1e3),n+r[0].length):-1}function nm(t,e,n){var r=Nb.exec(e.slice(n,n+1));return r?n+r[0].length:-1}function rm(t,e,n){var r=Ib.exec(e.slice(n));return r?(t.Q=+r[0],n+r[0].length):-1}function im(t,e,n){var r=Ib.exec(e.slice(n));return r?(t.Q=1e3*+r[0],n+r[0].length):-1}function om(t,e){return Lb(t.getDate(),e,2)}function am(t,e){return Lb(t.getHours(),e,2)}function sm(t,e){return Lb(t.getHours()%12||12,e,2)}function um(t,e){return Lb(1+zy.count(vy(t),t),e,3)}function cm(t,e){return Lb(t.getMilliseconds(),e,3)}function fm(t,e){return cm(t,e)+"000"}function lm(t,e){return Lb(t.getMonth()+1,e,2)}function hm(t,e){return Lb(t.getMinutes(),e,2)}function dm(t,e){return Lb(t.getSeconds(),e,2)}function pm(t){var e=t.getDay();return 0===e?7:e}function gm(t,e){return Lb(Ty.count(vy(t),t),e,2)}function ym(t,e){var n=t.getDay();return t=n>=4||0===n?Oy(t):Oy.ceil(t),Lb(Oy.count(vy(t),t)+(4===vy(t).getDay()),e,2)}function bm(t){return t.getDay()}function mm(t,e){return Lb(My.count(vy(t),t),e,2)}function vm(t,e){return Lb(t.getFullYear()%100,e,2)}function _m(t,e){return Lb(t.getFullYear()%1e4,e,4)}function wm(t){var e=t.getTimezoneOffset();return(e>0?"-":(e*=-1,"+"))+Lb(e/60|0,"0",2)+Lb(e%60,"0",2)}function xm(t,e){return Lb(t.getUTCDate(),e,2)}function km(t,e){return Lb(t.getUTCHours(),e,2)}function Em(t,e){return Lb(t.getUTCHours()%12||12,e,2)}function Am(t,e){return Lb(1+mb.count(wb(t),t),e,3)}function Sm(t,e){return Lb(t.getUTCMilliseconds(),e,3)}function Tm(t,e){return Sm(t,e)+"000"}function Mm(t,e){return Lb(t.getUTCMonth()+1,e,2)}function Dm(t,e){return Lb(t.getUTCMinutes(),e,2)}function Cm(t,e){return Lb(t.getUTCSeconds(),e,2)}function Om(t){var e=t.getUTCDay();return 0===e?7:e}function Rm(t,e){return Lb(rb.count(wb(t),t),e,2)}function Im(t,e){var n=t.getUTCDay();return t=n>=4||0===n?sb(t):sb.ceil(t),Lb(sb.count(wb(t),t)+(4===wb(t).getUTCDay()),e,2)}function Nm(t){return t.getUTCDay()}function Bm(t,e){return Lb(ib.count(wb(t),t),e,2)}function Lm(t,e){return Lb(t.getUTCFullYear()%100,e,2)}function Pm(t,e){return Lb(t.getUTCFullYear()%1e4,e,4)}function Fm(){return"+0000"}function qm(){return"%"}function jm(t){return+t}function Um(t){return Math.floor(+t/1e3)}function zm(t){return Tb=Sb(t),Mb=Tb.format,Db=Tb.parse,Cb=Tb.utcFormat,Ob=Tb.utcParse,Tb}zm({dateTime:"%x, %X",date:"%-m/%-d/%Y",time:"%-I:%M:%S %p",periods:["AM","PM"],days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]});var Ym=Date.prototype.toISOString?function(t){return t.toISOString()}:Cb("%Y-%m-%dT%H:%M:%S.%LZ");var Vm=+new Date("2000-01-01T00:00:00.000Z")?function(t){var e=new Date(t);return isNaN(e)?null:e}:Ob("%Y-%m-%dT%H:%M:%S.%LZ"),Hm=1e3,$m=60*Hm,Gm=60*$m,Wm=24*Gm,Km=7*Wm,Xm=30*Wm,Zm=365*Wm;function Jm(t){return new Date(t)}function Qm(t){return t instanceof Date?+t:+new Date(+t)}function tv(t,e,n,r,o,a,s,u,c){var f=zg(Bg,Bg),l=f.invert,h=f.domain,d=c(".%L"),p=c(":%S"),g=c("%I:%M"),y=c("%I %p"),b=c("%a %d"),m=c("%b %d"),v=c("%B"),_=c("%Y"),w=[[s,1,Hm],[s,5,5*Hm],[s,15,15*Hm],[s,30,30*Hm],[a,1,$m],[a,5,5*$m],[a,15,15*$m],[a,30,30*$m],[o,1,Gm],[o,3,3*Gm],[o,6,6*Gm],[o,12,12*Gm],[r,1,Wm],[r,2,2*Wm],[n,1,Km],[e,1,Xm],[e,3,3*Xm],[t,1,Zm]];function x(i){return(s(i)1)&&(t-=Math.floor(t));var e=Math.abs(t-.5);return z_.h=360*t-100,z_.s=1.5-1.5*e,z_.l=.8-.9*e,z_+""},V_=bn(),H_=Math.PI/3,$_=2*Math.PI/3,G_=function(t){var e;return t=(.5-t)*Math.PI,V_.r=255*(e=Math.sin(t))*e,V_.g=255*(e=Math.sin(t+H_))*e,V_.b=255*(e=Math.sin(t+$_))*e,V_+""},W_=function(t){return t=Math.max(0,Math.min(1,t)),"rgb("+Math.max(0,Math.min(255,Math.round(34.61+t*(1172.33-t*(10793.56-t*(33300.12-t*(38394.49-14825.05*t)))))))+", "+Math.max(0,Math.min(255,Math.round(23.31+t*(557.33+t*(1225.33-t*(3574.96-t*(1073.77+707.56*t)))))))+", "+Math.max(0,Math.min(255,Math.round(27.2+t*(3211.1-t*(15327.97-t*(27814-t*(22569.18-6838.66*t)))))))+")"};function K_(t){var e=t.length;return function(n){return t[Math.max(0,Math.min(e-1,Math.floor(n*e)))]}}var X_=K_(Sv("44015444025645045745055946075a46085c460a5d460b5e470d60470e6147106347116447136548146748166848176948186a481a6c481b6d481c6e481d6f481f70482071482173482374482475482576482677482878482979472a7a472c7a472d7b472e7c472f7d46307e46327e46337f463480453581453781453882443983443a83443b84433d84433e85423f854240864241864142874144874045884046883f47883f48893e49893e4a893e4c8a3d4d8a3d4e8a3c4f8a3c508b3b518b3b528b3a538b3a548c39558c39568c38588c38598c375a8c375b8d365c8d365d8d355e8d355f8d34608d34618d33628d33638d32648e32658e31668e31678e31688e30698e306a8e2f6b8e2f6c8e2e6d8e2e6e8e2e6f8e2d708e2d718e2c718e2c728e2c738e2b748e2b758e2a768e2a778e2a788e29798e297a8e297b8e287c8e287d8e277e8e277f8e27808e26818e26828e26828e25838e25848e25858e24868e24878e23888e23898e238a8d228b8d228c8d228d8d218e8d218f8d21908d21918c20928c20928c20938c1f948c1f958b1f968b1f978b1f988b1f998a1f9a8a1e9b8a1e9c891e9d891f9e891f9f881fa0881fa1881fa1871fa28720a38620a48621a58521a68522a78522a88423a98324aa8325ab8225ac8226ad8127ad8128ae8029af7f2ab07f2cb17e2db27d2eb37c2fb47c31b57b32b67a34b67935b77937b87838b9773aba763bbb753dbc743fbc7340bd7242be7144bf7046c06f48c16e4ac16d4cc26c4ec36b50c46a52c56954c56856c66758c7655ac8645cc8635ec96260ca6063cb5f65cb5e67cc5c69cd5b6ccd5a6ece5870cf5773d05675d05477d1537ad1517cd2507fd34e81d34d84d44b86d54989d5488bd6468ed64590d74393d74195d84098d83e9bd93c9dd93ba0da39a2da37a5db36a8db34aadc32addc30b0dd2fb2dd2db5de2bb8de29bade28bddf26c0df25c2df23c5e021c8e020cae11fcde11dd0e11cd2e21bd5e21ad8e219dae319dde318dfe318e2e418e5e419e7e419eae51aece51befe51cf1e51df4e61ef6e620f8e621fbe723fde725")),Z_=K_(Sv("00000401000501010601010802010902020b02020d03030f03031204041405041606051806051a07061c08071e0907200a08220b09240c09260d0a290e0b2b100b2d110c2f120d31130d34140e36150e38160f3b180f3d19103f1a10421c10441d11471e114920114b21114e22115024125325125527125829115a2a115c2c115f2d11612f116331116533106734106936106b38106c390f6e3b0f703d0f713f0f72400f74420f75440f764510774710784910784a10794c117a4e117b4f127b51127c52137c54137d56147d57157e59157e5a167e5c167f5d177f5f187f601880621980641a80651a80671b80681c816a1c816b1d816d1d816e1e81701f81721f817320817521817621817822817922827b23827c23827e24828025828125818326818426818627818827818928818b29818c29818e2a81902a81912b81932b80942c80962c80982d80992d809b2e7f9c2e7f9e2f7fa02f7fa1307ea3307ea5317ea6317da8327daa337dab337cad347cae347bb0357bb2357bb3367ab5367ab73779b83779ba3878bc3978bd3977bf3a77c03a76c23b75c43c75c53c74c73d73c83e73ca3e72cc3f71cd4071cf4070d0416fd2426fd3436ed5446dd6456cd8456cd9466bdb476adc4869de4968df4a68e04c67e24d66e34e65e44f64e55064e75263e85362e95462ea5661eb5760ec5860ed5a5fee5b5eef5d5ef05f5ef1605df2625df2645cf3655cf4675cf4695cf56b5cf66c5cf66e5cf7705cf7725cf8745cf8765cf9785df9795df97b5dfa7d5efa7f5efa815ffb835ffb8560fb8761fc8961fc8a62fc8c63fc8e64fc9065fd9266fd9467fd9668fd9869fd9a6afd9b6bfe9d6cfe9f6dfea16efea36ffea571fea772fea973feaa74feac76feae77feb078feb27afeb47bfeb67cfeb77efeb97ffebb81febd82febf84fec185fec287fec488fec68afec88cfeca8dfecc8ffecd90fecf92fed194fed395fed597fed799fed89afdda9cfddc9efddea0fde0a1fde2a3fde3a5fde5a7fde7a9fde9aafdebacfcecaefceeb0fcf0b2fcf2b4fcf4b6fcf6b8fcf7b9fcf9bbfcfbbdfcfdbf")),J_=K_(Sv("00000401000501010601010802010a02020c02020e03021004031204031405041706041907051b08051d09061f0a07220b07240c08260d08290e092b10092d110a30120a32140b34150b37160b39180c3c190c3e1b0c411c0c431e0c451f0c48210c4a230c4c240c4f260c51280b53290b552b0b572d0b592f0a5b310a5c320a5e340a5f3609613809623909633b09643d09653e0966400a67420a68440a68450a69470b6a490b6a4a0c6b4c0c6b4d0d6c4f0d6c510e6c520e6d540f6d550f6d57106e59106e5a116e5c126e5d126e5f136e61136e62146e64156e65156e67166e69166e6a176e6c186e6d186e6f196e71196e721a6e741a6e751b6e771c6d781c6d7a1d6d7c1d6d7d1e6d7f1e6c801f6c82206c84206b85216b87216b88226a8a226a8c23698d23698f24699025689225689326679526679727669827669a28659b29649d29649f2a63a02a63a22b62a32c61a52c60a62d60a82e5fa92e5eab2f5ead305dae305cb0315bb1325ab3325ab43359b63458b73557b93556ba3655bc3754bd3853bf3952c03a51c13a50c33b4fc43c4ec63d4dc73e4cc83f4bca404acb4149cc4248ce4347cf4446d04545d24644d34743d44842d54a41d74b3fd84c3ed94d3dda4e3cdb503bdd513ade5238df5337e05536e15635e25734e35933e45a31e55c30e65d2fe75e2ee8602de9612bea632aeb6429eb6628ec6726ed6925ee6a24ef6c23ef6e21f06f20f1711ff1731df2741cf3761bf37819f47918f57b17f57d15f67e14f68013f78212f78410f8850ff8870ef8890cf98b0bf98c0af98e09fa9008fa9207fa9407fb9606fb9706fb9906fb9b06fb9d07fc9f07fca108fca309fca50afca60cfca80dfcaa0ffcac11fcae12fcb014fcb216fcb418fbb61afbb81dfbba1ffbbc21fbbe23fac026fac228fac42afac62df9c72ff9c932f9cb35f8cd37f8cf3af7d13df7d340f6d543f6d746f5d949f5db4cf4dd4ff4df53f4e156f3e35af3e55df2e661f2e865f2ea69f1ec6df1ed71f1ef75f1f179f2f27df2f482f3f586f3f68af4f88ef5f992f6fa96f8fb9af9fc9dfafda1fcffa4")),Q_=K_(Sv("0d088710078813078916078a19068c1b068d1d068e20068f2206902406912605912805922a05932c05942e05952f059631059733059735049837049938049a3a049a3c049b3e049c3f049c41049d43039e44039e46039f48039f4903a04b03a14c02a14e02a25002a25102a35302a35502a45601a45801a45901a55b01a55c01a65e01a66001a66100a76300a76400a76600a76700a86900a86a00a86c00a86e00a86f00a87100a87201a87401a87501a87701a87801a87a02a87b02a87d03a87e03a88004a88104a78305a78405a78606a68707a68808a68a09a58b0aa58d0ba58e0ca48f0da4910ea3920fa39410a29511a19613a19814a099159f9a169f9c179e9d189d9e199da01a9ca11b9ba21d9aa31e9aa51f99a62098a72197a82296aa2395ab2494ac2694ad2793ae2892b02991b12a90b22b8fb32c8eb42e8db52f8cb6308bb7318ab83289ba3388bb3488bc3587bd3786be3885bf3984c03a83c13b82c23c81c33d80c43e7fc5407ec6417dc7427cc8437bc9447aca457acb4679cc4778cc4977cd4a76ce4b75cf4c74d04d73d14e72d24f71d35171d45270d5536fd5546ed6556dd7566cd8576bd9586ada5a6ada5b69db5c68dc5d67dd5e66de5f65de6164df6263e06363e16462e26561e26660e3685fe4695ee56a5de56b5de66c5ce76e5be76f5ae87059e97158e97257ea7457eb7556eb7655ec7754ed7953ed7a52ee7b51ef7c51ef7e50f07f4ff0804ef1814df1834cf2844bf3854bf3874af48849f48948f58b47f58c46f68d45f68f44f79044f79143f79342f89441f89540f9973ff9983ef99a3efa9b3dfa9c3cfa9e3bfb9f3afba139fba238fca338fca537fca636fca835fca934fdab33fdac33fdae32fdaf31fdb130fdb22ffdb42ffdb52efeb72dfeb82cfeba2cfebb2bfebd2afebe2afec029fdc229fdc328fdc527fdc627fdc827fdca26fdcb26fccd25fcce25fcd025fcd225fbd324fbd524fbd724fad824fada24f9dc24f9dd25f8df25f8e125f7e225f7e425f6e626f6e826f5e926f5eb27f4ed27f3ee27f3f027f2f227f1f426f1f525f0f724f0f921")),tw=function(t){return function(){return t}},ew=Math.abs,nw=Math.atan2,rw=Math.cos,iw=Math.max,ow=Math.min,aw=Math.sin,sw=Math.sqrt,uw=1e-12,cw=Math.PI,fw=cw/2,lw=2*cw;function hw(t){return t>=1?fw:t<=-1?-fw:Math.asin(t)}function dw(t){return t.innerRadius}function pw(t){return t.outerRadius}function gw(t){return t.startAngle}function yw(t){return t.endAngle}function bw(t){return t&&t.padAngle}function mw(t,e,n,r,i,o,a){var s=t-n,u=e-r,c=(a?o:-o)/sw(s*s+u*u),f=c*u,l=-c*s,h=t+f,d=e+l,p=n+f,g=r+l,y=(h+p)/2,b=(d+g)/2,m=p-h,v=g-d,_=m*m+v*v,w=i-o,x=h*g-p*d,k=(v<0?-1:1)*sw(iw(0,w*w*_-x*x)),E=(x*v-m*k)/_,A=(-x*m-v*k)/_,S=(x*v+m*k)/_,T=(-x*m+v*k)/_,M=E-y,D=A-b,C=S-y,O=T-b;return M*M+D*D>C*C+O*O&&(E=S,A=T),{cx:E,cy:A,x01:-f,y01:-l,x11:E*(i/w-1),y11:A*(i/w-1)}}var vw=function(){var t=dw,e=pw,n=tw(0),r=null,i=gw,o=yw,a=bw,s=null;function u(){var u,c,f,l=+t.apply(this,arguments),h=+e.apply(this,arguments),d=i.apply(this,arguments)-fw,p=o.apply(this,arguments)-fw,g=ew(p-d),y=p>d;if(s||(s=u=qa()),huw)if(g>lw-uw)s.moveTo(h*rw(d),h*aw(d)),s.arc(0,0,h,d,p,!y),l>uw&&(s.moveTo(l*rw(p),l*aw(p)),s.arc(0,0,l,p,d,y));else{var b,m,v=d,_=p,w=d,x=p,k=g,E=g,A=a.apply(this,arguments)/2,S=A>uw&&(r?+r.apply(this,arguments):sw(l*l+h*h)),T=ow(ew(h-l)/2,+n.apply(this,arguments)),M=T,D=T;if(S>uw){var C=hw(S/l*aw(A)),O=hw(S/h*aw(A));(k-=2*C)>uw?(w+=C*=y?1:-1,x-=C):(k=0,w=x=(d+p)/2),(E-=2*O)>uw?(v+=O*=y?1:-1,_-=O):(E=0,v=_=(d+p)/2)}var R=h*rw(v),I=h*aw(v),N=l*rw(x),B=l*aw(x);if(T>uw){var L,P=h*rw(_),F=h*aw(_),q=l*rw(w),j=l*aw(w);if(g1?0:f<-1?cw:Math.acos(f))/2),$=sw(L[0]*L[0]+L[1]*L[1]);M=ow(T,(l-$)/(H-1)),D=ow(T,(h-$)/(H+1))}}E>uw?D>uw?(b=mw(q,j,R,I,h,D,y),m=mw(P,F,N,B,h,D,y),s.moveTo(b.cx+b.x01,b.cy+b.y01),Duw&&k>uw?M>uw?(b=mw(N,B,P,F,l,-M,y),m=mw(R,I,q,j,l,-M,y),s.lineTo(b.cx+b.x01,b.cy+b.y01),M=f;--l)s.point(y[l],b[l]);s.lineEnd(),s.areaEnd()}g&&(y[c]=+t(h,c,u),b[c]=+n(h,c,u),s.point(e?+e(h,c,u):y[c],r?+r(h,c,u):b[c]))}if(d)return s=null,d+""||null}function c(){return Ew().defined(i).curve(a).context(o)}return u.x=function(n){return arguments.length?(t="function"==typeof n?n:tw(+n),e=null,u):t},u.x0=function(e){return arguments.length?(t="function"==typeof e?e:tw(+e),u):t},u.x1=function(t){return arguments.length?(e=null==t?null:"function"==typeof t?t:tw(+t),u):e},u.y=function(t){return arguments.length?(n="function"==typeof t?t:tw(+t),r=null,u):n},u.y0=function(t){return arguments.length?(n="function"==typeof t?t:tw(+t),u):n},u.y1=function(t){return arguments.length?(r=null==t?null:"function"==typeof t?t:tw(+t),u):r},u.lineX0=u.lineY0=function(){return c().x(t).y(n)},u.lineY1=function(){return c().x(t).y(r)},u.lineX1=function(){return c().x(e).y(n)},u.defined=function(t){return arguments.length?(i="function"==typeof t?t:tw(!!t),u):i},u.curve=function(t){return arguments.length?(a=t,null!=o&&(s=a(o)),u):a},u.context=function(t){return arguments.length?(null==t?o=s=null:s=a(o=t),u):o},u},Sw=function(t,e){return et?1:e>=t?0:NaN},Tw=function(t){return t},Mw=function(){var t=Tw,e=Sw,n=null,r=tw(0),i=tw(lw),o=tw(0);function a(a){var s,u,c,f,l,h=a.length,d=0,p=new Array(h),g=new Array(h),y=+r.apply(this,arguments),b=Math.min(lw,Math.max(-lw,i.apply(this,arguments)-y)),m=Math.min(Math.abs(b)/h,o.apply(this,arguments)),v=m*(b<0?-1:1);for(s=0;s0&&(d+=l);for(null!=e?p.sort((function(t,n){return e(g[t],g[n])})):null!=n&&p.sort((function(t,e){return n(a[t],a[e])})),s=0,c=d?(b-h*v)/d:0;s0?l*c:0)+v,g[u]={data:a[u],index:s,value:l,startAngle:y,endAngle:f,padAngle:m};return g}return a.value=function(e){return arguments.length?(t="function"==typeof e?e:tw(+e),a):t},a.sortValues=function(t){return arguments.length?(e=t,n=null,a):e},a.sort=function(t){return arguments.length?(n=t,e=null,a):n},a.startAngle=function(t){return arguments.length?(r="function"==typeof t?t:tw(+t),a):r},a.endAngle=function(t){return arguments.length?(i="function"==typeof t?t:tw(+t),a):i},a.padAngle=function(t){return arguments.length?(o="function"==typeof t?t:tw(+t),a):o},a},Dw=Ow(ww);function Cw(t){this._curve=t}function Ow(t){function e(e){return new Cw(t(e))}return e._curve=t,e}function Rw(t){var e=t.curve;return t.angle=t.x,delete t.x,t.radius=t.y,delete t.y,t.curve=function(t){return arguments.length?e(Ow(t)):e()._curve},t}Cw.prototype={areaStart:function(){this._curve.areaStart()},areaEnd:function(){this._curve.areaEnd()},lineStart:function(){this._curve.lineStart()},lineEnd:function(){this._curve.lineEnd()},point:function(t,e){this._curve.point(e*Math.sin(t),e*-Math.cos(t))}};var Iw=function(){return Rw(Ew().curve(Dw))},Nw=function(){var t=Aw().curve(Dw),e=t.curve,n=t.lineX0,r=t.lineX1,i=t.lineY0,o=t.lineY1;return t.angle=t.x,delete t.x,t.startAngle=t.x0,delete t.x0,t.endAngle=t.x1,delete t.x1,t.radius=t.y,delete t.y,t.innerRadius=t.y0,delete t.y0,t.outerRadius=t.y1,delete t.y1,t.lineStartAngle=function(){return Rw(n())},delete t.lineX0,t.lineEndAngle=function(){return Rw(r())},delete t.lineX1,t.lineInnerRadius=function(){return Rw(i())},delete t.lineY0,t.lineOuterRadius=function(){return Rw(o())},delete t.lineY1,t.curve=function(t){return arguments.length?e(Ow(t)):e()._curve},t},Bw=function(t,e){return[(e=+e)*Math.cos(t-=Math.PI/2),e*Math.sin(t)]},Lw=Array.prototype.slice;function Pw(t){return t.source}function Fw(t){return t.target}function qw(t){var e=Pw,n=Fw,r=xw,i=kw,o=null;function a(){var a,s=Lw.call(arguments),u=e.apply(this,s),c=n.apply(this,s);if(o||(o=a=qa()),t(o,+r.apply(this,(s[0]=u,s)),+i.apply(this,s),+r.apply(this,(s[0]=c,s)),+i.apply(this,s)),a)return o=null,a+""||null}return a.source=function(t){return arguments.length?(e=t,a):e},a.target=function(t){return arguments.length?(n=t,a):n},a.x=function(t){return arguments.length?(r="function"==typeof t?t:tw(+t),a):r},a.y=function(t){return arguments.length?(i="function"==typeof t?t:tw(+t),a):i},a.context=function(t){return arguments.length?(o=null==t?null:t,a):o},a}function jw(t,e,n,r,i){t.moveTo(e,n),t.bezierCurveTo(e=(e+r)/2,n,e,i,r,i)}function Uw(t,e,n,r,i){t.moveTo(e,n),t.bezierCurveTo(e,n=(n+i)/2,r,n,r,i)}function zw(t,e,n,r,i){var o=Bw(e,n),a=Bw(e,n=(n+i)/2),s=Bw(r,n),u=Bw(r,i);t.moveTo(o[0],o[1]),t.bezierCurveTo(a[0],a[1],s[0],s[1],u[0],u[1])}function Yw(){return qw(jw)}function Vw(){return qw(Uw)}function Hw(){var t=qw(zw);return t.angle=t.x,delete t.x,t.radius=t.y,delete t.y,t}var $w={draw:function(t,e){var n=Math.sqrt(e/cw);t.moveTo(n,0),t.arc(0,0,n,0,lw)}},Gw={draw:function(t,e){var n=Math.sqrt(e/5)/2;t.moveTo(-3*n,-n),t.lineTo(-n,-n),t.lineTo(-n,-3*n),t.lineTo(n,-3*n),t.lineTo(n,-n),t.lineTo(3*n,-n),t.lineTo(3*n,n),t.lineTo(n,n),t.lineTo(n,3*n),t.lineTo(-n,3*n),t.lineTo(-n,n),t.lineTo(-3*n,n),t.closePath()}},Ww=Math.sqrt(1/3),Kw=2*Ww,Xw={draw:function(t,e){var n=Math.sqrt(e/Kw),r=n*Ww;t.moveTo(0,-n),t.lineTo(r,0),t.lineTo(0,n),t.lineTo(-r,0),t.closePath()}},Zw=Math.sin(cw/10)/Math.sin(7*cw/10),Jw=Math.sin(lw/10)*Zw,Qw=-Math.cos(lw/10)*Zw,tx={draw:function(t,e){var n=Math.sqrt(.8908130915292852*e),r=Jw*n,i=Qw*n;t.moveTo(0,-n),t.lineTo(r,i);for(var o=1;o<5;++o){var a=lw*o/5,s=Math.cos(a),u=Math.sin(a);t.lineTo(u*n,-s*n),t.lineTo(s*r-u*i,u*r+s*i)}t.closePath()}},ex={draw:function(t,e){var n=Math.sqrt(e),r=-n/2;t.rect(r,r,n,n)}},nx=Math.sqrt(3),rx={draw:function(t,e){var n=-Math.sqrt(e/(3*nx));t.moveTo(0,2*n),t.lineTo(-nx*n,-n),t.lineTo(nx*n,-n),t.closePath()}},ix=Math.sqrt(3)/2,ox=1/Math.sqrt(12),ax=3*(ox/2+1),sx={draw:function(t,e){var n=Math.sqrt(e/ax),r=n/2,i=n*ox,o=r,a=n*ox+n,s=-o,u=a;t.moveTo(r,i),t.lineTo(o,a),t.lineTo(s,u),t.lineTo(-.5*r-ix*i,ix*r+-.5*i),t.lineTo(-.5*o-ix*a,ix*o+-.5*a),t.lineTo(-.5*s-ix*u,ix*s+-.5*u),t.lineTo(-.5*r+ix*i,-.5*i-ix*r),t.lineTo(-.5*o+ix*a,-.5*a-ix*o),t.lineTo(-.5*s+ix*u,-.5*u-ix*s),t.closePath()}},ux=[$w,Gw,Xw,ex,tx,rx,sx],cx=function(){var t=tw($w),e=tw(64),n=null;function r(){var r;if(n||(n=r=qa()),t.apply(this,arguments).draw(n,+e.apply(this,arguments)),r)return n=null,r+""||null}return r.type=function(e){return arguments.length?(t="function"==typeof e?e:tw(e),r):t},r.size=function(t){return arguments.length?(e="function"==typeof t?t:tw(+t),r):e},r.context=function(t){return arguments.length?(n=null==t?null:t,r):n},r},fx=function(){};function lx(t,e,n){t._context.bezierCurveTo((2*t._x0+t._x1)/3,(2*t._y0+t._y1)/3,(t._x0+2*t._x1)/3,(t._y0+2*t._y1)/3,(t._x0+4*t._x1+e)/6,(t._y0+4*t._y1+n)/6)}function hx(t){this._context=t}hx.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=NaN,this._point=0},lineEnd:function(){switch(this._point){case 3:lx(this,this._x1,this._y1);case 2:this._context.lineTo(this._x1,this._y1)}(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,e):this._context.moveTo(t,e);break;case 1:this._point=2;break;case 2:this._point=3,this._context.lineTo((5*this._x0+this._x1)/6,(5*this._y0+this._y1)/6);default:lx(this,t,e)}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=e}};var dx=function(t){return new hx(t)};function px(t){this._context=t}px.prototype={areaStart:fx,areaEnd:fx,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._y0=this._y1=this._y2=this._y3=this._y4=NaN,this._point=0},lineEnd:function(){switch(this._point){case 1:this._context.moveTo(this._x2,this._y2),this._context.closePath();break;case 2:this._context.moveTo((this._x2+2*this._x3)/3,(this._y2+2*this._y3)/3),this._context.lineTo((this._x3+2*this._x2)/3,(this._y3+2*this._y2)/3),this._context.closePath();break;case 3:this.point(this._x2,this._y2),this.point(this._x3,this._y3),this.point(this._x4,this._y4)}},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1,this._x2=t,this._y2=e;break;case 1:this._point=2,this._x3=t,this._y3=e;break;case 2:this._point=3,this._x4=t,this._y4=e,this._context.moveTo((this._x0+4*this._x1+t)/6,(this._y0+4*this._y1+e)/6);break;default:lx(this,t,e)}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=e}};var gx=function(t){return new px(t)};function yx(t){this._context=t}yx.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=NaN,this._point=0},lineEnd:function(){(this._line||0!==this._line&&3===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3;var n=(this._x0+4*this._x1+t)/6,r=(this._y0+4*this._y1+e)/6;this._line?this._context.lineTo(n,r):this._context.moveTo(n,r);break;case 3:this._point=4;default:lx(this,t,e)}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=e}};var bx=function(t){return new yx(t)};function mx(t,e){this._basis=new hx(t),this._beta=e}mx.prototype={lineStart:function(){this._x=[],this._y=[],this._basis.lineStart()},lineEnd:function(){var t=this._x,e=this._y,n=t.length-1;if(n>0)for(var r,i=t[0],o=e[0],a=t[n]-i,s=e[n]-o,u=-1;++u<=n;)r=u/n,this._basis.point(this._beta*t[u]+(1-this._beta)*(i+r*a),this._beta*e[u]+(1-this._beta)*(o+r*s));this._x=this._y=null,this._basis.lineEnd()},point:function(t,e){this._x.push(+t),this._y.push(+e)}};var vx=function t(e){function n(t){return 1===e?new hx(t):new mx(t,e)}return n.beta=function(e){return t(+e)},n}(.85);function _x(t,e,n){t._context.bezierCurveTo(t._x1+t._k*(t._x2-t._x0),t._y1+t._k*(t._y2-t._y0),t._x2+t._k*(t._x1-e),t._y2+t._k*(t._y1-n),t._x2,t._y2)}function wx(t,e){this._context=t,this._k=(1-e)/6}wx.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._point=0},lineEnd:function(){switch(this._point){case 2:this._context.lineTo(this._x2,this._y2);break;case 3:_x(this,this._x1,this._y1)}(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,e):this._context.moveTo(t,e);break;case 1:this._point=2,this._x1=t,this._y1=e;break;case 2:this._point=3;default:_x(this,t,e)}this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=e}};var xx=function t(e){function n(t){return new wx(t,e)}return n.tension=function(e){return t(+e)},n}(0);function kx(t,e){this._context=t,this._k=(1-e)/6}kx.prototype={areaStart:fx,areaEnd:fx,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._x5=this._y0=this._y1=this._y2=this._y3=this._y4=this._y5=NaN,this._point=0},lineEnd:function(){switch(this._point){case 1:this._context.moveTo(this._x3,this._y3),this._context.closePath();break;case 2:this._context.lineTo(this._x3,this._y3),this._context.closePath();break;case 3:this.point(this._x3,this._y3),this.point(this._x4,this._y4),this.point(this._x5,this._y5)}},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1,this._x3=t,this._y3=e;break;case 1:this._point=2,this._context.moveTo(this._x4=t,this._y4=e);break;case 2:this._point=3,this._x5=t,this._y5=e;break;default:_x(this,t,e)}this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=e}};var Ex=function t(e){function n(t){return new kx(t,e)}return n.tension=function(e){return t(+e)},n}(0);function Ax(t,e){this._context=t,this._k=(1-e)/6}Ax.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._point=0},lineEnd:function(){(this._line||0!==this._line&&3===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3,this._line?this._context.lineTo(this._x2,this._y2):this._context.moveTo(this._x2,this._y2);break;case 3:this._point=4;default:_x(this,t,e)}this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=e}};var Sx=function t(e){function n(t){return new Ax(t,e)}return n.tension=function(e){return t(+e)},n}(0);function Tx(t,e,n){var r=t._x1,i=t._y1,o=t._x2,a=t._y2;if(t._l01_a>uw){var s=2*t._l01_2a+3*t._l01_a*t._l12_a+t._l12_2a,u=3*t._l01_a*(t._l01_a+t._l12_a);r=(r*s-t._x0*t._l12_2a+t._x2*t._l01_2a)/u,i=(i*s-t._y0*t._l12_2a+t._y2*t._l01_2a)/u}if(t._l23_a>uw){var c=2*t._l23_2a+3*t._l23_a*t._l12_a+t._l12_2a,f=3*t._l23_a*(t._l23_a+t._l12_a);o=(o*c+t._x1*t._l23_2a-e*t._l12_2a)/f,a=(a*c+t._y1*t._l23_2a-n*t._l12_2a)/f}t._context.bezierCurveTo(r,i,o,a,t._x2,t._y2)}function Mx(t,e){this._context=t,this._alpha=e}Mx.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._l01_a=this._l12_a=this._l23_a=this._l01_2a=this._l12_2a=this._l23_2a=this._point=0},lineEnd:function(){switch(this._point){case 2:this._context.lineTo(this._x2,this._y2);break;case 3:this.point(this._x2,this._y2)}(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,e){if(t=+t,e=+e,this._point){var n=this._x2-t,r=this._y2-e;this._l23_a=Math.sqrt(this._l23_2a=Math.pow(n*n+r*r,this._alpha))}switch(this._point){case 0:this._point=1,this._line?this._context.lineTo(t,e):this._context.moveTo(t,e);break;case 1:this._point=2;break;case 2:this._point=3;default:Tx(this,t,e)}this._l01_a=this._l12_a,this._l12_a=this._l23_a,this._l01_2a=this._l12_2a,this._l12_2a=this._l23_2a,this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=e}};var Dx=function t(e){function n(t){return e?new Mx(t,e):new wx(t,0)}return n.alpha=function(e){return t(+e)},n}(.5);function Cx(t,e){this._context=t,this._alpha=e}Cx.prototype={areaStart:fx,areaEnd:fx,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._x5=this._y0=this._y1=this._y2=this._y3=this._y4=this._y5=NaN,this._l01_a=this._l12_a=this._l23_a=this._l01_2a=this._l12_2a=this._l23_2a=this._point=0},lineEnd:function(){switch(this._point){case 1:this._context.moveTo(this._x3,this._y3),this._context.closePath();break;case 2:this._context.lineTo(this._x3,this._y3),this._context.closePath();break;case 3:this.point(this._x3,this._y3),this.point(this._x4,this._y4),this.point(this._x5,this._y5)}},point:function(t,e){if(t=+t,e=+e,this._point){var n=this._x2-t,r=this._y2-e;this._l23_a=Math.sqrt(this._l23_2a=Math.pow(n*n+r*r,this._alpha))}switch(this._point){case 0:this._point=1,this._x3=t,this._y3=e;break;case 1:this._point=2,this._context.moveTo(this._x4=t,this._y4=e);break;case 2:this._point=3,this._x5=t,this._y5=e;break;default:Tx(this,t,e)}this._l01_a=this._l12_a,this._l12_a=this._l23_a,this._l01_2a=this._l12_2a,this._l12_2a=this._l23_2a,this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=e}};var Ox=function t(e){function n(t){return e?new Cx(t,e):new kx(t,0)}return n.alpha=function(e){return t(+e)},n}(.5);function Rx(t,e){this._context=t,this._alpha=e}Rx.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._l01_a=this._l12_a=this._l23_a=this._l01_2a=this._l12_2a=this._l23_2a=this._point=0},lineEnd:function(){(this._line||0!==this._line&&3===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,e){if(t=+t,e=+e,this._point){var n=this._x2-t,r=this._y2-e;this._l23_a=Math.sqrt(this._l23_2a=Math.pow(n*n+r*r,this._alpha))}switch(this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3,this._line?this._context.lineTo(this._x2,this._y2):this._context.moveTo(this._x2,this._y2);break;case 3:this._point=4;default:Tx(this,t,e)}this._l01_a=this._l12_a,this._l12_a=this._l23_a,this._l01_2a=this._l12_2a,this._l12_2a=this._l23_2a,this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=e}};var Ix=function t(e){function n(t){return e?new Rx(t,e):new Ax(t,0)}return n.alpha=function(e){return t(+e)},n}(.5);function Nx(t){this._context=t}Nx.prototype={areaStart:fx,areaEnd:fx,lineStart:function(){this._point=0},lineEnd:function(){this._point&&this._context.closePath()},point:function(t,e){t=+t,e=+e,this._point?this._context.lineTo(t,e):(this._point=1,this._context.moveTo(t,e))}};var Bx=function(t){return new Nx(t)};function Lx(t){return t<0?-1:1}function Px(t,e,n){var r=t._x1-t._x0,i=e-t._x1,o=(t._y1-t._y0)/(r||i<0&&-0),a=(n-t._y1)/(i||r<0&&-0),s=(o*i+a*r)/(r+i);return(Lx(o)+Lx(a))*Math.min(Math.abs(o),Math.abs(a),.5*Math.abs(s))||0}function Fx(t,e){var n=t._x1-t._x0;return n?(3*(t._y1-t._y0)/n-e)/2:e}function qx(t,e,n){var r=t._x0,i=t._y0,o=t._x1,a=t._y1,s=(o-r)/3;t._context.bezierCurveTo(r+s,i+s*e,o-s,a-s*n,o,a)}function jx(t){this._context=t}function Ux(t){this._context=new zx(t)}function zx(t){this._context=t}function Yx(t){return new jx(t)}function Vx(t){return new Ux(t)}function Hx(t){this._context=t}function $x(t){var e,n,r=t.length-1,i=new Array(r),o=new Array(r),a=new Array(r);for(i[0]=0,o[0]=2,a[0]=t[0]+2*t[1],e=1;e=0;--e)i[e]=(a[e]-i[e+1])/o[e];for(o[r-1]=(t[r]+i[r-1])/2,e=0;e=0&&(this._t=1-this._t,this._line=1-this._line)},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,e):this._context.moveTo(t,e);break;case 1:this._point=2;default:if(this._t<=0)this._context.lineTo(this._x,e),this._context.lineTo(t,e);else{var n=this._x*(1-this._t)+t*this._t;this._context.lineTo(n,this._y),this._context.lineTo(n,e)}}this._x=t,this._y=e}};var Kx=function(t){return new Wx(t,.5)};function Xx(t){return new Wx(t,0)}function Zx(t){return new Wx(t,1)}var Jx=function(t,e){if((i=t.length)>1)for(var n,r,i,o=1,a=t[e[0]],s=a.length;o=0;)n[e]=e;return n};function tk(t,e){return t[e]}var ek=function(){var t=tw([]),e=Qx,n=Jx,r=tk;function i(i){var o,a,s=t.apply(this,arguments),u=i.length,c=s.length,f=new Array(c);for(o=0;o0){for(var n,r,i,o=0,a=t[0].length;o0)for(var n,r,i,o,a,s,u=0,c=t[e[0]].length;u=0?(r[0]=o,r[1]=o+=i):i<0?(r[1]=a,r[0]=a+=i):r[0]=o},ik=function(t,e){if((n=t.length)>0){for(var n,r=0,i=t[e[0]],o=i.length;r0&&(r=(n=t[e[0]]).length)>0){for(var n,r,i,o=0,a=1;ao&&(o=e,r=n);return r}var uk=function(t){var e=t.map(ck);return Qx(t).sort((function(t,n){return e[t]-e[n]}))};function ck(t){for(var e,n=0,r=-1,i=t.length;++r0)){if(o/=h,h<0){if(o0){if(o>l)return;o>f&&(f=o)}if(o=r-u,h||!(o<0)){if(o/=h,h<0){if(o>l)return;o>f&&(f=o)}else if(h>0){if(o0)){if(o/=d,d<0){if(o0){if(o>l)return;o>f&&(f=o)}if(o=i-c,d||!(o<0)){if(o/=d,d<0){if(o>l)return;o>f&&(f=o)}else if(d>0){if(o0||l<1)||(f>0&&(t[0]=[u+f*h,c+f*d]),l<1&&(t[1]=[u+l*h,c+l*d]),!0)}}}}}function Sk(t,e,n,r,i){var o=t[1];if(o)return!0;var a,s,u=t[0],c=t.left,f=t.right,l=c[0],h=c[1],d=f[0],p=f[1],g=(l+d)/2,y=(h+p)/2;if(p===h){if(g=r)return;if(l>d){if(u){if(u[1]>=i)return}else u=[g,n];o=[g,i]}else{if(u){if(u[1]1)if(l>d){if(u){if(u[1]>=i)return}else u=[(n-s)/a,n];o=[(i-s)/a,i]}else{if(u){if(u[1]=r)return}else u=[e,a*e+s];o=[r,a*r+s]}else{if(u){if(u[0]=-Wk)){var d=u*u+c*c,p=f*f+l*l,g=(l*d-c*p)/h,y=(u*p-f*d)/h,b=Ok.pop()||new Rk;b.arc=t,b.site=i,b.x=g+a,b.y=(b.cy=y+s)+Math.sqrt(g*g+y*y),t.circle=b;for(var m=null,v=Hk._;v;)if(b.yGk)s=s.L;else{if(!((i=o-zk(s,a))>Gk)){r>-Gk?(e=s.P,n=s):i>-Gk?(e=s,n=s.N):e=n=s;break}if(!s.R){e=s;break}s=s.R}!function(t){Vk[t.index]={site:t,halfedges:[]}}(t);var u=Pk(t);if(Yk.insert(e,u),e||n){if(e===n)return Nk(e),n=Pk(e.site),Yk.insert(u,n),u.edge=n.edge=xk(e.site,u.site),Ik(e),void Ik(n);if(n){Nk(e),Nk(n);var c=e.site,f=c[0],l=c[1],h=t[0]-f,d=t[1]-l,p=n.site,g=p[0]-f,y=p[1]-l,b=2*(h*y-d*g),m=h*h+d*d,v=g*g+y*y,_=[(y*m-d*v)/b+f,(h*v-g*m)/b+l];Ek(n.edge,c,p,_),u.edge=xk(c,t,null,_),n.edge=xk(t,p,null,_),Ik(e),Ik(n)}else u.edge=xk(e.site,u.site)}}function Uk(t,e){var n=t.site,r=n[0],i=n[1],o=i-e;if(!o)return r;var a=t.P;if(!a)return-1/0;var s=(n=a.site)[0],u=n[1],c=u-e;if(!c)return s;var f=s-r,l=1/o-1/c,h=f/c;return l?(-h+Math.sqrt(h*h-2*l*(f*f/(-2*c)-u+c/2+i-o/2)))/l+r:(r+s)/2}function zk(t,e){var n=t.N;if(n)return Uk(n,e);var r=t.site;return r[1]===e?r[0]:1/0}var Yk,Vk,Hk,$k,Gk=1e-6,Wk=1e-12;function Kk(t,e){return e[1]-t[1]||e[0]-t[0]}function Xk(t,e){var n,r,i,o=t.sort(Kk).pop();for($k=[],Vk=new Array(t.length),Yk=new wk,Hk=new wk;;)if(i=Ck,o&&(!i||o[1]Gk||Math.abs(i[0][1]-i[1][1])>Gk)||delete $k[o]}(a,s,u,c),function(t,e,n,r){var i,o,a,s,u,c,f,l,h,d,p,g,y=Vk.length,b=!0;for(i=0;iGk||Math.abs(g-h)>Gk)&&(u.splice(s,0,$k.push(kk(a,d,Math.abs(p-t)Gk?[t,Math.abs(l-t)Gk?[Math.abs(h-r)Gk?[n,Math.abs(l-n)Gk?[Math.abs(h-e)=s)return null;var u=t-i.site[0],c=e-i.site[1],f=u*u+c*c;do{i=o.cells[r=a],a=null,i.halfedges.forEach((function(n){var r=o.edges[n],s=r.left;if(s!==i.site&&s||(s=r.right)){var u=t-s[0],c=e-s[1],l=u*u+c*c;lr?(r+i)/2:Math.min(0,r)||Math.max(0,i),a>o?(o+a)/2:Math.min(0,o)||Math.max(0,a))}var lE=function(){var t,e,n=oE,r=aE,i=fE,o=uE,a=cE,s=[0,1/0],u=[[-1/0,-1/0],[1/0,1/0]],c=250,f=ur,l=gt("start","zoom","end"),h=500,d=150,p=0;function g(t){t.property("__zoom",sE).on("wheel.zoom",x).on("mousedown.zoom",k).on("dblclick.zoom",E).filter(a).on("touchstart.zoom",A).on("touchmove.zoom",S).on("touchend.zoom touchcancel.zoom",T).style("touch-action","none").style("-webkit-tap-highlight-color","rgba(0,0,0,0)")}function y(t,e){return(e=Math.max(s[0],Math.min(s[1],e)))===t.k?t:new tE(e,t.x,t.y)}function b(t,e,n){var r=e[0]-n[0]*t.k,i=e[1]-n[1]*t.k;return r===t.x&&i===t.y?t:new tE(t.k,r,i)}function m(t){return[(+t[0][0]+ +t[1][0])/2,(+t[0][1]+ +t[1][1])/2]}function v(t,e,n){t.on("start.zoom",(function(){_(this,arguments).start()})).on("interrupt.zoom end.zoom",(function(){_(this,arguments).end()})).tween("zoom",(function(){var t=this,i=arguments,o=_(t,i),a=r.apply(t,i),s=null==n?m(a):"function"==typeof n?n.apply(t,i):n,u=Math.max(a[1][0]-a[0][0],a[1][1]-a[0][1]),c=t.__zoom,l="function"==typeof e?e.apply(t,i):e,h=f(c.invert(s).concat(u/c.k),l.invert(s).concat(u/l.k));return function(t){if(1===t)t=l;else{var e=h(t),n=u/e[2];t=new tE(n,s[0]-e[0]*n,s[1]-e[1]*n)}o.zoom(null,t)}}))}function _(t,e,n){return!n&&t.__zooming||new w(t,e)}function w(t,e){this.that=t,this.args=e,this.active=0,this.extent=r.apply(t,e),this.taps=0}function x(){if(n.apply(this,arguments)){var t=_(this,arguments),e=this.__zoom,r=Math.max(s[0],Math.min(s[1],e.k*Math.pow(2,o.apply(this,arguments)))),a=Be(this);if(t.wheel)t.mouse[0][0]===a[0]&&t.mouse[0][1]===a[1]||(t.mouse[1]=e.invert(t.mouse[0]=a)),clearTimeout(t.wheel);else{if(e.k===r)return;t.mouse=[a,e.invert(a)],Oi(this),t.start()}iE(),t.wheel=setTimeout((function(){t.wheel=null,t.end()}),d),t.zoom("mouse",i(b(y(e,r),t.mouse[0],t.mouse[1]),t.extent,u))}}function k(){if(!e&&n.apply(this,arguments)){var t=_(this,arguments,!0),r=Me(pe.view).on("mousemove.zoom",(function(){if(iE(),!t.moved){var e=pe.clientX-a,n=pe.clientY-s;t.moved=e*e+n*n>p}t.zoom("mouse",i(b(t.that.__zoom,t.mouse[0]=Be(t.that),t.mouse[1]),t.extent,u))}),!0).on("mouseup.zoom",(function(){r.on("mousemove.zoom mouseup.zoom",null),ze(pe.view,t.moved),iE(),t.end()}),!0),o=Be(this),a=pe.clientX,s=pe.clientY;Ue(pe.view),rE(),t.mouse=[o,this.__zoom.invert(o)],Oi(this),t.start()}}function E(){if(n.apply(this,arguments)){var t=this.__zoom,e=Be(this),o=t.invert(e),a=t.k*(pe.shiftKey?.5:2),s=i(b(y(t,a),e,o),r.apply(this,arguments),u);iE(),c>0?Me(this).transition().duration(c).call(v,s,e):Me(this).call(g.transform,s)}}function A(){if(n.apply(this,arguments)){var e,r,i,o,a=pe.touches,s=a.length,u=_(this,arguments,pe.changedTouches.length===s);for(rE(),r=0;rl&&M.push("'"+this.terminals_[A]+"'");C=p.showPosition?"Parse error on line "+(u+1)+":\n"+p.showPosition()+"\nExpecting "+M.join(", ")+", got '"+(this.terminals_[_]||_)+"'":"Parse error on line "+(u+1)+": Unexpected "+(_==h?"end of input":"'"+(this.terminals_[_]||_)+"'"),this.parseError(C,{text:p.match,token:this.terminals_[_]||_,line:p.yylineno,loc:b,expected:M})}if(k[0]instanceof Array&&k.length>1)throw new Error("Parse Error: multiple actions possible at state: "+x+", token: "+_);switch(k[0]){case 1:n.push(_),i.push(p.yytext),o.push(p.yylloc),n.push(k[1]),_=null,w?(_=w,w=null):(c=p.yyleng,s=p.yytext,u=p.yylineno,b=p.yylloc,f>0&&f--);break;case 2:if(S=this.productions_[k[1]][1],D.$=i[i.length-S],D._$={first_line:o[o.length-(S||1)].first_line,last_line:o[o.length-1].last_line,first_column:o[o.length-(S||1)].first_column,last_column:o[o.length-1].last_column},m&&(D._$.range=[o[o.length-(S||1)].range[0],o[o.length-1].range[1]]),void 0!==(E=this.performAction.apply(D,[s,c,u,g.yy,k[1],i,o].concat(d))))return E;S&&(n=n.slice(0,-1*S*2),i=i.slice(0,-1*S),o=o.slice(0,-1*S)),n.push(this.productions_[k[1]][0]),i.push(D.$),o.push(D._$),T=a[n[n.length-2]][n[n.length-1]],n.push(T);break;case 3:return!0}}return!0}},A={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var o in i)this[o]=i[o];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),o=0;oe[0].length)){if(e=n,r=o,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[o])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var t=this.next();return t||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{"case-insensitive":!0},performAction:function(t,e,n,r){switch(n){case 0:return 5;case 1:case 2:case 3:case 4:break;case 5:return this.begin("ID"),10;case 6:return e.yytext=e.yytext.trim(),this.begin("ALIAS"),41;case 7:return this.popState(),this.popState(),this.begin("LINE"),12;case 8:return this.popState(),this.popState(),5;case 9:return this.begin("LINE"),20;case 10:return this.begin("LINE"),22;case 11:return this.begin("LINE"),23;case 12:return this.begin("LINE"),24;case 13:return this.begin("LINE"),29;case 14:return this.begin("LINE"),26;case 15:return this.begin("LINE"),28;case 16:return this.popState(),13;case 17:return 21;case 18:return 36;case 19:return 37;case 20:return 32;case 21:return 30;case 22:return this.begin("ID"),15;case 23:return this.begin("ID"),16;case 24:return 18;case 25:return 6;case 26:return 35;case 27:return 5;case 28:return e.yytext=e.yytext.trim(),41;case 29:return 44;case 30:return 45;case 31:return 42;case 32:return 43;case 33:return 46;case 34:return 47;case 35:return 48;case 36:return 39;case 37:return 40;case 38:return 5;case 39:return"INVALID"}},rules:[/^(?:[\n]+)/i,/^(?:\s+)/i,/^(?:((?!\n)\s)+)/i,/^(?:#[^\n]*)/i,/^(?:%[^\n]*)/i,/^(?:participant\b)/i,/^(?:[^\->:\n,;]+?(?=((?!\n)\s)+as(?!\n)\s|[#\n;]|$))/i,/^(?:as\b)/i,/^(?:(?:))/i,/^(?:loop\b)/i,/^(?:rect\b)/i,/^(?:opt\b)/i,/^(?:alt\b)/i,/^(?:else\b)/i,/^(?:par\b)/i,/^(?:and\b)/i,/^(?:[^#\n;]*)/i,/^(?:end\b)/i,/^(?:left of\b)/i,/^(?:right of\b)/i,/^(?:over\b)/i,/^(?:note\b)/i,/^(?:activate\b)/i,/^(?:deactivate\b)/i,/^(?:title\b)/i,/^(?:sequenceDiagram\b)/i,/^(?:,)/i,/^(?:;)/i,/^(?:[^\+\->:\n,;]+)/i,/^(?:->>)/i,/^(?:-->>)/i,/^(?:->)/i,/^(?:-->)/i,/^(?:-[x])/i,/^(?:--[x])/i,/^(?::[^#\n;]+)/i,/^(?:\+)/i,/^(?:-)/i,/^(?:$)/i,/^(?:.)/i],conditions:{LINE:{rules:[2,3,16],inclusive:!1},ALIAS:{rules:[2,3,7,8],inclusive:!1},ID:{rules:[2,3,6],inclusive:!1},INITIAL:{rules:[0,1,3,4,5,9,10,11,12,13,14,15,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39],inclusive:!0}}};function S(){this.yy={}}return E.lexer=A,S.prototype=E,E.Parser=S,new S}();e.parser=i,e.Parser=i.Parser,e.parse=function(){return i.parse.apply(i,arguments)},e.main=function(r){r[1]||(console.log("Usage: "+r[0]+" FILE"),t.exit(1));var i=n(28).readFileSync(n(29).normalize(r[1]),"utf8");return e.parser.parse(i)},n.c[n.s]===r&&e.main(t.argv.slice(1))}).call(this,n(7),n(9)(t))},function(t,e){"function"==typeof Object.create?t.exports=function(t,e){e&&(t.super_=e,t.prototype=Object.create(e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}))}:t.exports=function(t,e){if(e){t.super_=e;var n=function(){};n.prototype=e.prototype,t.prototype=new n,t.prototype.constructor=t}}},function(t,e,n){var r=n(8),i=r.Buffer;function o(t,e){for(var n in t)e[n]=t[n]}function a(t,e,n){return i(t,e,n)}i.from&&i.alloc&&i.allocUnsafe&&i.allocUnsafeSlow?t.exports=r:(o(r,e),e.Buffer=a),a.prototype=Object.create(i.prototype),o(i,a),a.from=function(t,e,n){if("number"==typeof t)throw new TypeError("Argument must not be a number");return i(t,e,n)},a.alloc=function(t,e,n){if("number"!=typeof t)throw new TypeError("Argument must be a number");var r=i(t);return void 0!==e?"string"==typeof n?r.fill(e,n):r.fill(e):r.fill(0),r},a.allocUnsafe=function(t){if("number"!=typeof t)throw new TypeError("Argument must be a number");return i(t)},a.allocUnsafeSlow=function(t){if("number"!=typeof t)throw new TypeError("Argument must be a number");return r.SlowBuffer(t)}},function(t,e,n){var r;try{r={cloneDeep:n(348),constant:n(99),defaults:n(176),each:n(100),filter:n(150),find:n(349),flatten:n(178),forEach:n(148),forIn:n(354),has:n(106),isUndefined:n(161),last:n(355),map:n(162),mapValues:n(356),max:n(357),merge:n(359),min:n(364),minBy:n(365),now:n(366),pick:n(183),range:n(184),reduce:n(164),sortBy:n(373),uniqueId:n(185),values:n(169),zipObject:n(378)}}catch(t){}r||(r=window._),t.exports=r},function(t,e,n){(function(t){!function(t,e){"use strict";function r(t,e){if(!t)throw new Error(e||"Assertion failed")}function i(t,e){t.super_=e;var n=function(){};n.prototype=e.prototype,t.prototype=new n,t.prototype.constructor=t}function o(t,e,n){if(o.isBN(t))return t;this.negative=0,this.words=null,this.length=0,this.red=null,null!==t&&("le"!==e&&"be"!==e||(n=e,e=10),this._init(t||0,e||10,n||"be"))}var a;"object"==typeof t?t.exports=o:e.BN=o,o.BN=o,o.wordSize=26;try{a=n(457).Buffer}catch(t){}function s(t,e,n){for(var r=0,i=Math.min(t.length,n),o=e;o=49&&a<=54?a-49+10:a>=17&&a<=22?a-17+10:15&a}return r}function u(t,e,n,r){for(var i=0,o=Math.min(t.length,n),a=e;a=49?s-49+10:s>=17?s-17+10:s}return i}o.isBN=function(t){return t instanceof o||null!==t&&"object"==typeof t&&t.constructor.wordSize===o.wordSize&&Array.isArray(t.words)},o.max=function(t,e){return t.cmp(e)>0?t:e},o.min=function(t,e){return t.cmp(e)<0?t:e},o.prototype._init=function(t,e,n){if("number"==typeof t)return this._initNumber(t,e,n);if("object"==typeof t)return this._initArray(t,e,n);"hex"===e&&(e=16),r(e===(0|e)&&e>=2&&e<=36);var i=0;"-"===(t=t.toString().replace(/\s+/g,""))[0]&&i++,16===e?this._parseHex(t,i):this._parseBase(t,e,i),"-"===t[0]&&(this.negative=1),this.strip(),"le"===n&&this._initArray(this.toArray(),e,n)},o.prototype._initNumber=function(t,e,n){t<0&&(this.negative=1,t=-t),t<67108864?(this.words=[67108863&t],this.length=1):t<4503599627370496?(this.words=[67108863&t,t/67108864&67108863],this.length=2):(r(t<9007199254740992),this.words=[67108863&t,t/67108864&67108863,1],this.length=3),"le"===n&&this._initArray(this.toArray(),e,n)},o.prototype._initArray=function(t,e,n){if(r("number"==typeof t.length),t.length<=0)return this.words=[0],this.length=1,this;this.length=Math.ceil(t.length/3),this.words=new Array(this.length);for(var i=0;i=0;i-=3)a=t[i]|t[i-1]<<8|t[i-2]<<16,this.words[o]|=a<>>26-s&67108863,(s+=24)>=26&&(s-=26,o++);else if("le"===n)for(i=0,o=0;i>>26-s&67108863,(s+=24)>=26&&(s-=26,o++);return this.strip()},o.prototype._parseHex=function(t,e){this.length=Math.ceil((t.length-e)/6),this.words=new Array(this.length);for(var n=0;n=e;n-=6)i=s(t,n,n+6),this.words[r]|=i<>>26-o&4194303,(o+=24)>=26&&(o-=26,r++);n+6!==e&&(i=s(t,e,n+6),this.words[r]|=i<>>26-o&4194303),this.strip()},o.prototype._parseBase=function(t,e,n){this.words=[0],this.length=1;for(var r=0,i=1;i<=67108863;i*=e)r++;r--,i=i/e|0;for(var o=t.length-n,a=o%r,s=Math.min(o,o-a)+n,c=0,f=n;f1&&0===this.words[this.length-1];)this.length--;return this._normSign()},o.prototype._normSign=function(){return 1===this.length&&0===this.words[0]&&(this.negative=0),this},o.prototype.inspect=function(){return(this.red?""};var c=["","0","00","000","0000","00000","000000","0000000","00000000","000000000","0000000000","00000000000","000000000000","0000000000000","00000000000000","000000000000000","0000000000000000","00000000000000000","000000000000000000","0000000000000000000","00000000000000000000","000000000000000000000","0000000000000000000000","00000000000000000000000","000000000000000000000000","0000000000000000000000000"],f=[0,0,25,16,12,11,10,9,8,8,7,7,7,7,6,6,6,6,6,6,6,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5],l=[0,0,33554432,43046721,16777216,48828125,60466176,40353607,16777216,43046721,1e7,19487171,35831808,62748517,7529536,11390625,16777216,24137569,34012224,47045881,64e6,4084101,5153632,6436343,7962624,9765625,11881376,14348907,17210368,20511149,243e5,28629151,33554432,39135393,45435424,52521875,60466176];function h(t,e,n){n.negative=e.negative^t.negative;var r=t.length+e.length|0;n.length=r,r=r-1|0;var i=0|t.words[0],o=0|e.words[0],a=i*o,s=67108863&a,u=a/67108864|0;n.words[0]=s;for(var c=1;c>>26,l=67108863&u,h=Math.min(c,e.length-1),d=Math.max(0,c-t.length+1);d<=h;d++){var p=c-d|0;f+=(a=(i=0|t.words[p])*(o=0|e.words[d])+l)/67108864|0,l=67108863&a}n.words[c]=0|l,u=0|f}return 0!==u?n.words[c]=0|u:n.length--,n.strip()}o.prototype.toString=function(t,e){var n;if(e=0|e||1,16===(t=t||10)||"hex"===t){n="";for(var i=0,o=0,a=0;a>>24-i&16777215)||a!==this.length-1?c[6-u.length]+u+n:u+n,(i+=2)>=26&&(i-=26,a--)}for(0!==o&&(n=o.toString(16)+n);n.length%e!=0;)n="0"+n;return 0!==this.negative&&(n="-"+n),n}if(t===(0|t)&&t>=2&&t<=36){var h=f[t],d=l[t];n="";var p=this.clone();for(p.negative=0;!p.isZero();){var g=p.modn(d).toString(t);n=(p=p.idivn(d)).isZero()?g+n:c[h-g.length]+g+n}for(this.isZero()&&(n="0"+n);n.length%e!=0;)n="0"+n;return 0!==this.negative&&(n="-"+n),n}r(!1,"Base should be between 2 and 36")},o.prototype.toNumber=function(){var t=this.words[0];return 2===this.length?t+=67108864*this.words[1]:3===this.length&&1===this.words[2]?t+=4503599627370496+67108864*this.words[1]:this.length>2&&r(!1,"Number can only safely store up to 53 bits"),0!==this.negative?-t:t},o.prototype.toJSON=function(){return this.toString(16)},o.prototype.toBuffer=function(t,e){return r(void 0!==a),this.toArrayLike(a,t,e)},o.prototype.toArray=function(t,e){return this.toArrayLike(Array,t,e)},o.prototype.toArrayLike=function(t,e,n){var i=this.byteLength(),o=n||Math.max(1,i);r(i<=o,"byte array longer than desired length"),r(o>0,"Requested array length <= 0"),this.strip();var a,s,u="le"===e,c=new t(o),f=this.clone();if(u){for(s=0;!f.isZero();s++)a=f.andln(255),f.iushrn(8),c[s]=a;for(;s=4096&&(n+=13,e>>>=13),e>=64&&(n+=7,e>>>=7),e>=8&&(n+=4,e>>>=4),e>=2&&(n+=2,e>>>=2),n+e},o.prototype._zeroBits=function(t){if(0===t)return 26;var e=t,n=0;return 0==(8191&e)&&(n+=13,e>>>=13),0==(127&e)&&(n+=7,e>>>=7),0==(15&e)&&(n+=4,e>>>=4),0==(3&e)&&(n+=2,e>>>=2),0==(1&e)&&n++,n},o.prototype.bitLength=function(){var t=this.words[this.length-1],e=this._countBits(t);return 26*(this.length-1)+e},o.prototype.zeroBits=function(){if(this.isZero())return 0;for(var t=0,e=0;et.length?this.clone().ior(t):t.clone().ior(this)},o.prototype.uor=function(t){return this.length>t.length?this.clone().iuor(t):t.clone().iuor(this)},o.prototype.iuand=function(t){var e;e=this.length>t.length?t:this;for(var n=0;nt.length?this.clone().iand(t):t.clone().iand(this)},o.prototype.uand=function(t){return this.length>t.length?this.clone().iuand(t):t.clone().iuand(this)},o.prototype.iuxor=function(t){var e,n;this.length>t.length?(e=this,n=t):(e=t,n=this);for(var r=0;rt.length?this.clone().ixor(t):t.clone().ixor(this)},o.prototype.uxor=function(t){return this.length>t.length?this.clone().iuxor(t):t.clone().iuxor(this)},o.prototype.inotn=function(t){r("number"==typeof t&&t>=0);var e=0|Math.ceil(t/26),n=t%26;this._expand(e),n>0&&e--;for(var i=0;i0&&(this.words[i]=~this.words[i]&67108863>>26-n),this.strip()},o.prototype.notn=function(t){return this.clone().inotn(t)},o.prototype.setn=function(t,e){r("number"==typeof t&&t>=0);var n=t/26|0,i=t%26;return this._expand(n+1),this.words[n]=e?this.words[n]|1<t.length?(n=this,r=t):(n=t,r=this);for(var i=0,o=0;o>>26;for(;0!==i&&o>>26;if(this.length=n.length,0!==i)this.words[this.length]=i,this.length++;else if(n!==this)for(;ot.length?this.clone().iadd(t):t.clone().iadd(this)},o.prototype.isub=function(t){if(0!==t.negative){t.negative=0;var e=this.iadd(t);return t.negative=1,e._normSign()}if(0!==this.negative)return this.negative=0,this.iadd(t),this.negative=1,this._normSign();var n,r,i=this.cmp(t);if(0===i)return this.negative=0,this.length=1,this.words[0]=0,this;i>0?(n=this,r=t):(n=t,r=this);for(var o=0,a=0;a>26,this.words[a]=67108863&e;for(;0!==o&&a>26,this.words[a]=67108863&e;if(0===o&&a>>13,d=0|a[1],p=8191&d,g=d>>>13,y=0|a[2],b=8191&y,m=y>>>13,v=0|a[3],_=8191&v,w=v>>>13,x=0|a[4],k=8191&x,E=x>>>13,A=0|a[5],S=8191&A,T=A>>>13,M=0|a[6],D=8191&M,C=M>>>13,O=0|a[7],R=8191&O,I=O>>>13,N=0|a[8],B=8191&N,L=N>>>13,P=0|a[9],F=8191&P,q=P>>>13,j=0|s[0],U=8191&j,z=j>>>13,Y=0|s[1],V=8191&Y,H=Y>>>13,$=0|s[2],G=8191&$,W=$>>>13,K=0|s[3],X=8191&K,Z=K>>>13,J=0|s[4],Q=8191&J,tt=J>>>13,et=0|s[5],nt=8191&et,rt=et>>>13,it=0|s[6],ot=8191&it,at=it>>>13,st=0|s[7],ut=8191&st,ct=st>>>13,ft=0|s[8],lt=8191&ft,ht=ft>>>13,dt=0|s[9],pt=8191&dt,gt=dt>>>13;n.negative=t.negative^e.negative,n.length=19;var yt=(c+(r=Math.imul(l,U))|0)+((8191&(i=(i=Math.imul(l,z))+Math.imul(h,U)|0))<<13)|0;c=((o=Math.imul(h,z))+(i>>>13)|0)+(yt>>>26)|0,yt&=67108863,r=Math.imul(p,U),i=(i=Math.imul(p,z))+Math.imul(g,U)|0,o=Math.imul(g,z);var bt=(c+(r=r+Math.imul(l,V)|0)|0)+((8191&(i=(i=i+Math.imul(l,H)|0)+Math.imul(h,V)|0))<<13)|0;c=((o=o+Math.imul(h,H)|0)+(i>>>13)|0)+(bt>>>26)|0,bt&=67108863,r=Math.imul(b,U),i=(i=Math.imul(b,z))+Math.imul(m,U)|0,o=Math.imul(m,z),r=r+Math.imul(p,V)|0,i=(i=i+Math.imul(p,H)|0)+Math.imul(g,V)|0,o=o+Math.imul(g,H)|0;var mt=(c+(r=r+Math.imul(l,G)|0)|0)+((8191&(i=(i=i+Math.imul(l,W)|0)+Math.imul(h,G)|0))<<13)|0;c=((o=o+Math.imul(h,W)|0)+(i>>>13)|0)+(mt>>>26)|0,mt&=67108863,r=Math.imul(_,U),i=(i=Math.imul(_,z))+Math.imul(w,U)|0,o=Math.imul(w,z),r=r+Math.imul(b,V)|0,i=(i=i+Math.imul(b,H)|0)+Math.imul(m,V)|0,o=o+Math.imul(m,H)|0,r=r+Math.imul(p,G)|0,i=(i=i+Math.imul(p,W)|0)+Math.imul(g,G)|0,o=o+Math.imul(g,W)|0;var vt=(c+(r=r+Math.imul(l,X)|0)|0)+((8191&(i=(i=i+Math.imul(l,Z)|0)+Math.imul(h,X)|0))<<13)|0;c=((o=o+Math.imul(h,Z)|0)+(i>>>13)|0)+(vt>>>26)|0,vt&=67108863,r=Math.imul(k,U),i=(i=Math.imul(k,z))+Math.imul(E,U)|0,o=Math.imul(E,z),r=r+Math.imul(_,V)|0,i=(i=i+Math.imul(_,H)|0)+Math.imul(w,V)|0,o=o+Math.imul(w,H)|0,r=r+Math.imul(b,G)|0,i=(i=i+Math.imul(b,W)|0)+Math.imul(m,G)|0,o=o+Math.imul(m,W)|0,r=r+Math.imul(p,X)|0,i=(i=i+Math.imul(p,Z)|0)+Math.imul(g,X)|0,o=o+Math.imul(g,Z)|0;var _t=(c+(r=r+Math.imul(l,Q)|0)|0)+((8191&(i=(i=i+Math.imul(l,tt)|0)+Math.imul(h,Q)|0))<<13)|0;c=((o=o+Math.imul(h,tt)|0)+(i>>>13)|0)+(_t>>>26)|0,_t&=67108863,r=Math.imul(S,U),i=(i=Math.imul(S,z))+Math.imul(T,U)|0,o=Math.imul(T,z),r=r+Math.imul(k,V)|0,i=(i=i+Math.imul(k,H)|0)+Math.imul(E,V)|0,o=o+Math.imul(E,H)|0,r=r+Math.imul(_,G)|0,i=(i=i+Math.imul(_,W)|0)+Math.imul(w,G)|0,o=o+Math.imul(w,W)|0,r=r+Math.imul(b,X)|0,i=(i=i+Math.imul(b,Z)|0)+Math.imul(m,X)|0,o=o+Math.imul(m,Z)|0,r=r+Math.imul(p,Q)|0,i=(i=i+Math.imul(p,tt)|0)+Math.imul(g,Q)|0,o=o+Math.imul(g,tt)|0;var wt=(c+(r=r+Math.imul(l,nt)|0)|0)+((8191&(i=(i=i+Math.imul(l,rt)|0)+Math.imul(h,nt)|0))<<13)|0;c=((o=o+Math.imul(h,rt)|0)+(i>>>13)|0)+(wt>>>26)|0,wt&=67108863,r=Math.imul(D,U),i=(i=Math.imul(D,z))+Math.imul(C,U)|0,o=Math.imul(C,z),r=r+Math.imul(S,V)|0,i=(i=i+Math.imul(S,H)|0)+Math.imul(T,V)|0,o=o+Math.imul(T,H)|0,r=r+Math.imul(k,G)|0,i=(i=i+Math.imul(k,W)|0)+Math.imul(E,G)|0,o=o+Math.imul(E,W)|0,r=r+Math.imul(_,X)|0,i=(i=i+Math.imul(_,Z)|0)+Math.imul(w,X)|0,o=o+Math.imul(w,Z)|0,r=r+Math.imul(b,Q)|0,i=(i=i+Math.imul(b,tt)|0)+Math.imul(m,Q)|0,o=o+Math.imul(m,tt)|0,r=r+Math.imul(p,nt)|0,i=(i=i+Math.imul(p,rt)|0)+Math.imul(g,nt)|0,o=o+Math.imul(g,rt)|0;var xt=(c+(r=r+Math.imul(l,ot)|0)|0)+((8191&(i=(i=i+Math.imul(l,at)|0)+Math.imul(h,ot)|0))<<13)|0;c=((o=o+Math.imul(h,at)|0)+(i>>>13)|0)+(xt>>>26)|0,xt&=67108863,r=Math.imul(R,U),i=(i=Math.imul(R,z))+Math.imul(I,U)|0,o=Math.imul(I,z),r=r+Math.imul(D,V)|0,i=(i=i+Math.imul(D,H)|0)+Math.imul(C,V)|0,o=o+Math.imul(C,H)|0,r=r+Math.imul(S,G)|0,i=(i=i+Math.imul(S,W)|0)+Math.imul(T,G)|0,o=o+Math.imul(T,W)|0,r=r+Math.imul(k,X)|0,i=(i=i+Math.imul(k,Z)|0)+Math.imul(E,X)|0,o=o+Math.imul(E,Z)|0,r=r+Math.imul(_,Q)|0,i=(i=i+Math.imul(_,tt)|0)+Math.imul(w,Q)|0,o=o+Math.imul(w,tt)|0,r=r+Math.imul(b,nt)|0,i=(i=i+Math.imul(b,rt)|0)+Math.imul(m,nt)|0,o=o+Math.imul(m,rt)|0,r=r+Math.imul(p,ot)|0,i=(i=i+Math.imul(p,at)|0)+Math.imul(g,ot)|0,o=o+Math.imul(g,at)|0;var kt=(c+(r=r+Math.imul(l,ut)|0)|0)+((8191&(i=(i=i+Math.imul(l,ct)|0)+Math.imul(h,ut)|0))<<13)|0;c=((o=o+Math.imul(h,ct)|0)+(i>>>13)|0)+(kt>>>26)|0,kt&=67108863,r=Math.imul(B,U),i=(i=Math.imul(B,z))+Math.imul(L,U)|0,o=Math.imul(L,z),r=r+Math.imul(R,V)|0,i=(i=i+Math.imul(R,H)|0)+Math.imul(I,V)|0,o=o+Math.imul(I,H)|0,r=r+Math.imul(D,G)|0,i=(i=i+Math.imul(D,W)|0)+Math.imul(C,G)|0,o=o+Math.imul(C,W)|0,r=r+Math.imul(S,X)|0,i=(i=i+Math.imul(S,Z)|0)+Math.imul(T,X)|0,o=o+Math.imul(T,Z)|0,r=r+Math.imul(k,Q)|0,i=(i=i+Math.imul(k,tt)|0)+Math.imul(E,Q)|0,o=o+Math.imul(E,tt)|0,r=r+Math.imul(_,nt)|0,i=(i=i+Math.imul(_,rt)|0)+Math.imul(w,nt)|0,o=o+Math.imul(w,rt)|0,r=r+Math.imul(b,ot)|0,i=(i=i+Math.imul(b,at)|0)+Math.imul(m,ot)|0,o=o+Math.imul(m,at)|0,r=r+Math.imul(p,ut)|0,i=(i=i+Math.imul(p,ct)|0)+Math.imul(g,ut)|0,o=o+Math.imul(g,ct)|0;var Et=(c+(r=r+Math.imul(l,lt)|0)|0)+((8191&(i=(i=i+Math.imul(l,ht)|0)+Math.imul(h,lt)|0))<<13)|0;c=((o=o+Math.imul(h,ht)|0)+(i>>>13)|0)+(Et>>>26)|0,Et&=67108863,r=Math.imul(F,U),i=(i=Math.imul(F,z))+Math.imul(q,U)|0,o=Math.imul(q,z),r=r+Math.imul(B,V)|0,i=(i=i+Math.imul(B,H)|0)+Math.imul(L,V)|0,o=o+Math.imul(L,H)|0,r=r+Math.imul(R,G)|0,i=(i=i+Math.imul(R,W)|0)+Math.imul(I,G)|0,o=o+Math.imul(I,W)|0,r=r+Math.imul(D,X)|0,i=(i=i+Math.imul(D,Z)|0)+Math.imul(C,X)|0,o=o+Math.imul(C,Z)|0,r=r+Math.imul(S,Q)|0,i=(i=i+Math.imul(S,tt)|0)+Math.imul(T,Q)|0,o=o+Math.imul(T,tt)|0,r=r+Math.imul(k,nt)|0,i=(i=i+Math.imul(k,rt)|0)+Math.imul(E,nt)|0,o=o+Math.imul(E,rt)|0,r=r+Math.imul(_,ot)|0,i=(i=i+Math.imul(_,at)|0)+Math.imul(w,ot)|0,o=o+Math.imul(w,at)|0,r=r+Math.imul(b,ut)|0,i=(i=i+Math.imul(b,ct)|0)+Math.imul(m,ut)|0,o=o+Math.imul(m,ct)|0,r=r+Math.imul(p,lt)|0,i=(i=i+Math.imul(p,ht)|0)+Math.imul(g,lt)|0,o=o+Math.imul(g,ht)|0;var At=(c+(r=r+Math.imul(l,pt)|0)|0)+((8191&(i=(i=i+Math.imul(l,gt)|0)+Math.imul(h,pt)|0))<<13)|0;c=((o=o+Math.imul(h,gt)|0)+(i>>>13)|0)+(At>>>26)|0,At&=67108863,r=Math.imul(F,V),i=(i=Math.imul(F,H))+Math.imul(q,V)|0,o=Math.imul(q,H),r=r+Math.imul(B,G)|0,i=(i=i+Math.imul(B,W)|0)+Math.imul(L,G)|0,o=o+Math.imul(L,W)|0,r=r+Math.imul(R,X)|0,i=(i=i+Math.imul(R,Z)|0)+Math.imul(I,X)|0,o=o+Math.imul(I,Z)|0,r=r+Math.imul(D,Q)|0,i=(i=i+Math.imul(D,tt)|0)+Math.imul(C,Q)|0,o=o+Math.imul(C,tt)|0,r=r+Math.imul(S,nt)|0,i=(i=i+Math.imul(S,rt)|0)+Math.imul(T,nt)|0,o=o+Math.imul(T,rt)|0,r=r+Math.imul(k,ot)|0,i=(i=i+Math.imul(k,at)|0)+Math.imul(E,ot)|0,o=o+Math.imul(E,at)|0,r=r+Math.imul(_,ut)|0,i=(i=i+Math.imul(_,ct)|0)+Math.imul(w,ut)|0,o=o+Math.imul(w,ct)|0,r=r+Math.imul(b,lt)|0,i=(i=i+Math.imul(b,ht)|0)+Math.imul(m,lt)|0,o=o+Math.imul(m,ht)|0;var St=(c+(r=r+Math.imul(p,pt)|0)|0)+((8191&(i=(i=i+Math.imul(p,gt)|0)+Math.imul(g,pt)|0))<<13)|0;c=((o=o+Math.imul(g,gt)|0)+(i>>>13)|0)+(St>>>26)|0,St&=67108863,r=Math.imul(F,G),i=(i=Math.imul(F,W))+Math.imul(q,G)|0,o=Math.imul(q,W),r=r+Math.imul(B,X)|0,i=(i=i+Math.imul(B,Z)|0)+Math.imul(L,X)|0,o=o+Math.imul(L,Z)|0,r=r+Math.imul(R,Q)|0,i=(i=i+Math.imul(R,tt)|0)+Math.imul(I,Q)|0,o=o+Math.imul(I,tt)|0,r=r+Math.imul(D,nt)|0,i=(i=i+Math.imul(D,rt)|0)+Math.imul(C,nt)|0,o=o+Math.imul(C,rt)|0,r=r+Math.imul(S,ot)|0,i=(i=i+Math.imul(S,at)|0)+Math.imul(T,ot)|0,o=o+Math.imul(T,at)|0,r=r+Math.imul(k,ut)|0,i=(i=i+Math.imul(k,ct)|0)+Math.imul(E,ut)|0,o=o+Math.imul(E,ct)|0,r=r+Math.imul(_,lt)|0,i=(i=i+Math.imul(_,ht)|0)+Math.imul(w,lt)|0,o=o+Math.imul(w,ht)|0;var Tt=(c+(r=r+Math.imul(b,pt)|0)|0)+((8191&(i=(i=i+Math.imul(b,gt)|0)+Math.imul(m,pt)|0))<<13)|0;c=((o=o+Math.imul(m,gt)|0)+(i>>>13)|0)+(Tt>>>26)|0,Tt&=67108863,r=Math.imul(F,X),i=(i=Math.imul(F,Z))+Math.imul(q,X)|0,o=Math.imul(q,Z),r=r+Math.imul(B,Q)|0,i=(i=i+Math.imul(B,tt)|0)+Math.imul(L,Q)|0,o=o+Math.imul(L,tt)|0,r=r+Math.imul(R,nt)|0,i=(i=i+Math.imul(R,rt)|0)+Math.imul(I,nt)|0,o=o+Math.imul(I,rt)|0,r=r+Math.imul(D,ot)|0,i=(i=i+Math.imul(D,at)|0)+Math.imul(C,ot)|0,o=o+Math.imul(C,at)|0,r=r+Math.imul(S,ut)|0,i=(i=i+Math.imul(S,ct)|0)+Math.imul(T,ut)|0,o=o+Math.imul(T,ct)|0,r=r+Math.imul(k,lt)|0,i=(i=i+Math.imul(k,ht)|0)+Math.imul(E,lt)|0,o=o+Math.imul(E,ht)|0;var Mt=(c+(r=r+Math.imul(_,pt)|0)|0)+((8191&(i=(i=i+Math.imul(_,gt)|0)+Math.imul(w,pt)|0))<<13)|0;c=((o=o+Math.imul(w,gt)|0)+(i>>>13)|0)+(Mt>>>26)|0,Mt&=67108863,r=Math.imul(F,Q),i=(i=Math.imul(F,tt))+Math.imul(q,Q)|0,o=Math.imul(q,tt),r=r+Math.imul(B,nt)|0,i=(i=i+Math.imul(B,rt)|0)+Math.imul(L,nt)|0,o=o+Math.imul(L,rt)|0,r=r+Math.imul(R,ot)|0,i=(i=i+Math.imul(R,at)|0)+Math.imul(I,ot)|0,o=o+Math.imul(I,at)|0,r=r+Math.imul(D,ut)|0,i=(i=i+Math.imul(D,ct)|0)+Math.imul(C,ut)|0,o=o+Math.imul(C,ct)|0,r=r+Math.imul(S,lt)|0,i=(i=i+Math.imul(S,ht)|0)+Math.imul(T,lt)|0,o=o+Math.imul(T,ht)|0;var Dt=(c+(r=r+Math.imul(k,pt)|0)|0)+((8191&(i=(i=i+Math.imul(k,gt)|0)+Math.imul(E,pt)|0))<<13)|0;c=((o=o+Math.imul(E,gt)|0)+(i>>>13)|0)+(Dt>>>26)|0,Dt&=67108863,r=Math.imul(F,nt),i=(i=Math.imul(F,rt))+Math.imul(q,nt)|0,o=Math.imul(q,rt),r=r+Math.imul(B,ot)|0,i=(i=i+Math.imul(B,at)|0)+Math.imul(L,ot)|0,o=o+Math.imul(L,at)|0,r=r+Math.imul(R,ut)|0,i=(i=i+Math.imul(R,ct)|0)+Math.imul(I,ut)|0,o=o+Math.imul(I,ct)|0,r=r+Math.imul(D,lt)|0,i=(i=i+Math.imul(D,ht)|0)+Math.imul(C,lt)|0,o=o+Math.imul(C,ht)|0;var Ct=(c+(r=r+Math.imul(S,pt)|0)|0)+((8191&(i=(i=i+Math.imul(S,gt)|0)+Math.imul(T,pt)|0))<<13)|0;c=((o=o+Math.imul(T,gt)|0)+(i>>>13)|0)+(Ct>>>26)|0,Ct&=67108863,r=Math.imul(F,ot),i=(i=Math.imul(F,at))+Math.imul(q,ot)|0,o=Math.imul(q,at),r=r+Math.imul(B,ut)|0,i=(i=i+Math.imul(B,ct)|0)+Math.imul(L,ut)|0,o=o+Math.imul(L,ct)|0,r=r+Math.imul(R,lt)|0,i=(i=i+Math.imul(R,ht)|0)+Math.imul(I,lt)|0,o=o+Math.imul(I,ht)|0;var Ot=(c+(r=r+Math.imul(D,pt)|0)|0)+((8191&(i=(i=i+Math.imul(D,gt)|0)+Math.imul(C,pt)|0))<<13)|0;c=((o=o+Math.imul(C,gt)|0)+(i>>>13)|0)+(Ot>>>26)|0,Ot&=67108863,r=Math.imul(F,ut),i=(i=Math.imul(F,ct))+Math.imul(q,ut)|0,o=Math.imul(q,ct),r=r+Math.imul(B,lt)|0,i=(i=i+Math.imul(B,ht)|0)+Math.imul(L,lt)|0,o=o+Math.imul(L,ht)|0;var Rt=(c+(r=r+Math.imul(R,pt)|0)|0)+((8191&(i=(i=i+Math.imul(R,gt)|0)+Math.imul(I,pt)|0))<<13)|0;c=((o=o+Math.imul(I,gt)|0)+(i>>>13)|0)+(Rt>>>26)|0,Rt&=67108863,r=Math.imul(F,lt),i=(i=Math.imul(F,ht))+Math.imul(q,lt)|0,o=Math.imul(q,ht);var It=(c+(r=r+Math.imul(B,pt)|0)|0)+((8191&(i=(i=i+Math.imul(B,gt)|0)+Math.imul(L,pt)|0))<<13)|0;c=((o=o+Math.imul(L,gt)|0)+(i>>>13)|0)+(It>>>26)|0,It&=67108863;var Nt=(c+(r=Math.imul(F,pt))|0)+((8191&(i=(i=Math.imul(F,gt))+Math.imul(q,pt)|0))<<13)|0;return c=((o=Math.imul(q,gt))+(i>>>13)|0)+(Nt>>>26)|0,Nt&=67108863,u[0]=yt,u[1]=bt,u[2]=mt,u[3]=vt,u[4]=_t,u[5]=wt,u[6]=xt,u[7]=kt,u[8]=Et,u[9]=At,u[10]=St,u[11]=Tt,u[12]=Mt,u[13]=Dt,u[14]=Ct,u[15]=Ot,u[16]=Rt,u[17]=It,u[18]=Nt,0!==c&&(u[19]=c,n.length++),n};function p(t,e,n){return(new g).mulp(t,e,n)}function g(t,e){this.x=t,this.y=e}Math.imul||(d=h),o.prototype.mulTo=function(t,e){var n=this.length+t.length;return 10===this.length&&10===t.length?d(this,t,e):n<63?h(this,t,e):n<1024?function(t,e,n){n.negative=e.negative^t.negative,n.length=t.length+e.length;for(var r=0,i=0,o=0;o>>26)|0)>>>26,a&=67108863}n.words[o]=s,r=a,a=i}return 0!==r?n.words[o]=r:n.length--,n.strip()}(this,t,e):p(this,t,e)},g.prototype.makeRBT=function(t){for(var e=new Array(t),n=o.prototype._countBits(t)-1,r=0;r>=1;return r},g.prototype.permute=function(t,e,n,r,i,o){for(var a=0;a>>=1)i++;return 1<>>=13,n[2*a+1]=8191&o,o>>>=13;for(a=2*e;a>=26,e+=i/67108864|0,e+=o>>>26,this.words[n]=67108863&o}return 0!==e&&(this.words[n]=e,this.length++),this},o.prototype.muln=function(t){return this.clone().imuln(t)},o.prototype.sqr=function(){return this.mul(this)},o.prototype.isqr=function(){return this.imul(this.clone())},o.prototype.pow=function(t){var e=function(t){for(var e=new Array(t.bitLength()),n=0;n>>i}return e}(t);if(0===e.length)return new o(1);for(var n=this,r=0;r=0);var e,n=t%26,i=(t-n)/26,o=67108863>>>26-n<<26-n;if(0!==n){var a=0;for(e=0;e>>26-n}a&&(this.words[e]=a,this.length++)}if(0!==i){for(e=this.length-1;e>=0;e--)this.words[e+i]=this.words[e];for(e=0;e=0),i=e?(e-e%26)/26:0;var o=t%26,a=Math.min((t-o)/26,this.length),s=67108863^67108863>>>o<a)for(this.length-=a,c=0;c=0&&(0!==f||c>=i);c--){var l=0|this.words[c];this.words[c]=f<<26-o|l>>>o,f=l&s}return u&&0!==f&&(u.words[u.length++]=f),0===this.length&&(this.words[0]=0,this.length=1),this.strip()},o.prototype.ishrn=function(t,e,n){return r(0===this.negative),this.iushrn(t,e,n)},o.prototype.shln=function(t){return this.clone().ishln(t)},o.prototype.ushln=function(t){return this.clone().iushln(t)},o.prototype.shrn=function(t){return this.clone().ishrn(t)},o.prototype.ushrn=function(t){return this.clone().iushrn(t)},o.prototype.testn=function(t){r("number"==typeof t&&t>=0);var e=t%26,n=(t-e)/26,i=1<=0);var e=t%26,n=(t-e)/26;if(r(0===this.negative,"imaskn works only with positive numbers"),this.length<=n)return this;if(0!==e&&n++,this.length=Math.min(n,this.length),0!==e){var i=67108863^67108863>>>e<=67108864;e++)this.words[e]-=67108864,e===this.length-1?this.words[e+1]=1:this.words[e+1]++;return this.length=Math.max(this.length,e+1),this},o.prototype.isubn=function(t){if(r("number"==typeof t),r(t<67108864),t<0)return this.iaddn(-t);if(0!==this.negative)return this.negative=0,this.iaddn(t),this.negative=1,this;if(this.words[0]-=t,1===this.length&&this.words[0]<0)this.words[0]=-this.words[0],this.negative=1;else for(var e=0;e>26)-(u/67108864|0),this.words[i+n]=67108863&o}for(;i>26,this.words[i+n]=67108863&o;if(0===s)return this.strip();for(r(-1===s),s=0,i=0;i>26,this.words[i]=67108863&o;return this.negative=1,this.strip()},o.prototype._wordDiv=function(t,e){var n=(this.length,t.length),r=this.clone(),i=t,a=0|i.words[i.length-1];0!==(n=26-this._countBits(a))&&(i=i.ushln(n),r.iushln(n),a=0|i.words[i.length-1]);var s,u=r.length-i.length;if("mod"!==e){(s=new o(null)).length=u+1,s.words=new Array(s.length);for(var c=0;c=0;l--){var h=67108864*(0|r.words[i.length+l])+(0|r.words[i.length+l-1]);for(h=Math.min(h/a|0,67108863),r._ishlnsubmul(i,h,l);0!==r.negative;)h--,r.negative=0,r._ishlnsubmul(i,1,l),r.isZero()||(r.negative^=1);s&&(s.words[l]=h)}return s&&s.strip(),r.strip(),"div"!==e&&0!==n&&r.iushrn(n),{div:s||null,mod:r}},o.prototype.divmod=function(t,e,n){return r(!t.isZero()),this.isZero()?{div:new o(0),mod:new o(0)}:0!==this.negative&&0===t.negative?(s=this.neg().divmod(t,e),"mod"!==e&&(i=s.div.neg()),"div"!==e&&(a=s.mod.neg(),n&&0!==a.negative&&a.iadd(t)),{div:i,mod:a}):0===this.negative&&0!==t.negative?(s=this.divmod(t.neg(),e),"mod"!==e&&(i=s.div.neg()),{div:i,mod:s.mod}):0!=(this.negative&t.negative)?(s=this.neg().divmod(t.neg(),e),"div"!==e&&(a=s.mod.neg(),n&&0!==a.negative&&a.isub(t)),{div:s.div,mod:a}):t.length>this.length||this.cmp(t)<0?{div:new o(0),mod:this}:1===t.length?"div"===e?{div:this.divn(t.words[0]),mod:null}:"mod"===e?{div:null,mod:new o(this.modn(t.words[0]))}:{div:this.divn(t.words[0]),mod:new o(this.modn(t.words[0]))}:this._wordDiv(t,e);var i,a,s},o.prototype.div=function(t){return this.divmod(t,"div",!1).div},o.prototype.mod=function(t){return this.divmod(t,"mod",!1).mod},o.prototype.umod=function(t){return this.divmod(t,"mod",!0).mod},o.prototype.divRound=function(t){var e=this.divmod(t);if(e.mod.isZero())return e.div;var n=0!==e.div.negative?e.mod.isub(t):e.mod,r=t.ushrn(1),i=t.andln(1),o=n.cmp(r);return o<0||1===i&&0===o?e.div:0!==e.div.negative?e.div.isubn(1):e.div.iaddn(1)},o.prototype.modn=function(t){r(t<=67108863);for(var e=(1<<26)%t,n=0,i=this.length-1;i>=0;i--)n=(e*n+(0|this.words[i]))%t;return n},o.prototype.idivn=function(t){r(t<=67108863);for(var e=0,n=this.length-1;n>=0;n--){var i=(0|this.words[n])+67108864*e;this.words[n]=i/t|0,e=i%t}return this.strip()},o.prototype.divn=function(t){return this.clone().idivn(t)},o.prototype.egcd=function(t){r(0===t.negative),r(!t.isZero());var e=this,n=t.clone();e=0!==e.negative?e.umod(t):e.clone();for(var i=new o(1),a=new o(0),s=new o(0),u=new o(1),c=0;e.isEven()&&n.isEven();)e.iushrn(1),n.iushrn(1),++c;for(var f=n.clone(),l=e.clone();!e.isZero();){for(var h=0,d=1;0==(e.words[0]&d)&&h<26;++h,d<<=1);if(h>0)for(e.iushrn(h);h-- >0;)(i.isOdd()||a.isOdd())&&(i.iadd(f),a.isub(l)),i.iushrn(1),a.iushrn(1);for(var p=0,g=1;0==(n.words[0]&g)&&p<26;++p,g<<=1);if(p>0)for(n.iushrn(p);p-- >0;)(s.isOdd()||u.isOdd())&&(s.iadd(f),u.isub(l)),s.iushrn(1),u.iushrn(1);e.cmp(n)>=0?(e.isub(n),i.isub(s),a.isub(u)):(n.isub(e),s.isub(i),u.isub(a))}return{a:s,b:u,gcd:n.iushln(c)}},o.prototype._invmp=function(t){r(0===t.negative),r(!t.isZero());var e=this,n=t.clone();e=0!==e.negative?e.umod(t):e.clone();for(var i,a=new o(1),s=new o(0),u=n.clone();e.cmpn(1)>0&&n.cmpn(1)>0;){for(var c=0,f=1;0==(e.words[0]&f)&&c<26;++c,f<<=1);if(c>0)for(e.iushrn(c);c-- >0;)a.isOdd()&&a.iadd(u),a.iushrn(1);for(var l=0,h=1;0==(n.words[0]&h)&&l<26;++l,h<<=1);if(l>0)for(n.iushrn(l);l-- >0;)s.isOdd()&&s.iadd(u),s.iushrn(1);e.cmp(n)>=0?(e.isub(n),a.isub(s)):(n.isub(e),s.isub(a))}return(i=0===e.cmpn(1)?a:s).cmpn(0)<0&&i.iadd(t),i},o.prototype.gcd=function(t){if(this.isZero())return t.abs();if(t.isZero())return this.abs();var e=this.clone(),n=t.clone();e.negative=0,n.negative=0;for(var r=0;e.isEven()&&n.isEven();r++)e.iushrn(1),n.iushrn(1);for(;;){for(;e.isEven();)e.iushrn(1);for(;n.isEven();)n.iushrn(1);var i=e.cmp(n);if(i<0){var o=e;e=n,n=o}else if(0===i||0===n.cmpn(1))break;e.isub(n)}return n.iushln(r)},o.prototype.invm=function(t){return this.egcd(t).a.umod(t)},o.prototype.isEven=function(){return 0==(1&this.words[0])},o.prototype.isOdd=function(){return 1==(1&this.words[0])},o.prototype.andln=function(t){return this.words[0]&t},o.prototype.bincn=function(t){r("number"==typeof t);var e=t%26,n=(t-e)/26,i=1<>>26,s&=67108863,this.words[a]=s}return 0!==o&&(this.words[a]=o,this.length++),this},o.prototype.isZero=function(){return 1===this.length&&0===this.words[0]},o.prototype.cmpn=function(t){var e,n=t<0;if(0!==this.negative&&!n)return-1;if(0===this.negative&&n)return 1;if(this.strip(),this.length>1)e=1;else{n&&(t=-t),r(t<=67108863,"Number is too big");var i=0|this.words[0];e=i===t?0:it.length)return 1;if(this.length=0;n--){var r=0|this.words[n],i=0|t.words[n];if(r!==i){ri&&(e=1);break}}return e},o.prototype.gtn=function(t){return 1===this.cmpn(t)},o.prototype.gt=function(t){return 1===this.cmp(t)},o.prototype.gten=function(t){return this.cmpn(t)>=0},o.prototype.gte=function(t){return this.cmp(t)>=0},o.prototype.ltn=function(t){return-1===this.cmpn(t)},o.prototype.lt=function(t){return-1===this.cmp(t)},o.prototype.lten=function(t){return this.cmpn(t)<=0},o.prototype.lte=function(t){return this.cmp(t)<=0},o.prototype.eqn=function(t){return 0===this.cmpn(t)},o.prototype.eq=function(t){return 0===this.cmp(t)},o.red=function(t){return new x(t)},o.prototype.toRed=function(t){return r(!this.red,"Already a number in reduction context"),r(0===this.negative,"red works only with positives"),t.convertTo(this)._forceRed(t)},o.prototype.fromRed=function(){return r(this.red,"fromRed works only with numbers in reduction context"),this.red.convertFrom(this)},o.prototype._forceRed=function(t){return this.red=t,this},o.prototype.forceRed=function(t){return r(!this.red,"Already a number in reduction context"),this._forceRed(t)},o.prototype.redAdd=function(t){return r(this.red,"redAdd works only with red numbers"),this.red.add(this,t)},o.prototype.redIAdd=function(t){return r(this.red,"redIAdd works only with red numbers"),this.red.iadd(this,t)},o.prototype.redSub=function(t){return r(this.red,"redSub works only with red numbers"),this.red.sub(this,t)},o.prototype.redISub=function(t){return r(this.red,"redISub works only with red numbers"),this.red.isub(this,t)},o.prototype.redShl=function(t){return r(this.red,"redShl works only with red numbers"),this.red.shl(this,t)},o.prototype.redMul=function(t){return r(this.red,"redMul works only with red numbers"),this.red._verify2(this,t),this.red.mul(this,t)},o.prototype.redIMul=function(t){return r(this.red,"redMul works only with red numbers"),this.red._verify2(this,t),this.red.imul(this,t)},o.prototype.redSqr=function(){return r(this.red,"redSqr works only with red numbers"),this.red._verify1(this),this.red.sqr(this)},o.prototype.redISqr=function(){return r(this.red,"redISqr works only with red numbers"),this.red._verify1(this),this.red.isqr(this)},o.prototype.redSqrt=function(){return r(this.red,"redSqrt works only with red numbers"),this.red._verify1(this),this.red.sqrt(this)},o.prototype.redInvm=function(){return r(this.red,"redInvm works only with red numbers"),this.red._verify1(this),this.red.invm(this)},o.prototype.redNeg=function(){return r(this.red,"redNeg works only with red numbers"),this.red._verify1(this),this.red.neg(this)},o.prototype.redPow=function(t){return r(this.red&&!t.red,"redPow(normalNum)"),this.red._verify1(this),this.red.pow(this,t)};var y={k256:null,p224:null,p192:null,p25519:null};function b(t,e){this.name=t,this.p=new o(e,16),this.n=this.p.bitLength(),this.k=new o(1).iushln(this.n).isub(this.p),this.tmp=this._tmp()}function m(){b.call(this,"k256","ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff fffffffe fffffc2f")}function v(){b.call(this,"p224","ffffffff ffffffff ffffffff ffffffff 00000000 00000000 00000001")}function _(){b.call(this,"p192","ffffffff ffffffff ffffffff fffffffe ffffffff ffffffff")}function w(){b.call(this,"25519","7fffffffffffffff ffffffffffffffff ffffffffffffffff ffffffffffffffed")}function x(t){if("string"==typeof t){var e=o._prime(t);this.m=e.p,this.prime=e}else r(t.gtn(1),"modulus must be greater than 1"),this.m=t,this.prime=null}function k(t){x.call(this,t),this.shift=this.m.bitLength(),this.shift%26!=0&&(this.shift+=26-this.shift%26),this.r=new o(1).iushln(this.shift),this.r2=this.imod(this.r.sqr()),this.rinv=this.r._invmp(this.m),this.minv=this.rinv.mul(this.r).isubn(1).div(this.m),this.minv=this.minv.umod(this.r),this.minv=this.r.sub(this.minv)}b.prototype._tmp=function(){var t=new o(null);return t.words=new Array(Math.ceil(this.n/13)),t},b.prototype.ireduce=function(t){var e,n=t;do{this.split(n,this.tmp),e=(n=(n=this.imulK(n)).iadd(this.tmp)).bitLength()}while(e>this.n);var r=e0?n.isub(this.p):n.strip(),n},b.prototype.split=function(t,e){t.iushrn(this.n,0,e)},b.prototype.imulK=function(t){return t.imul(this.k)},i(m,b),m.prototype.split=function(t,e){for(var n=Math.min(t.length,9),r=0;r>>22,i=o}i>>>=22,t.words[r-10]=i,0===i&&t.length>10?t.length-=10:t.length-=9},m.prototype.imulK=function(t){t.words[t.length]=0,t.words[t.length+1]=0,t.length+=2;for(var e=0,n=0;n>>=26,t.words[n]=i,e=r}return 0!==e&&(t.words[t.length++]=e),t},o._prime=function(t){if(y[t])return y[t];var e;if("k256"===t)e=new m;else if("p224"===t)e=new v;else if("p192"===t)e=new _;else{if("p25519"!==t)throw new Error("Unknown prime "+t);e=new w}return y[t]=e,e},x.prototype._verify1=function(t){r(0===t.negative,"red works only with positives"),r(t.red,"red works only with red numbers")},x.prototype._verify2=function(t,e){r(0==(t.negative|e.negative),"red works only with positives"),r(t.red&&t.red===e.red,"red works only with red numbers")},x.prototype.imod=function(t){return this.prime?this.prime.ireduce(t)._forceRed(this):t.umod(this.m)._forceRed(this)},x.prototype.neg=function(t){return t.isZero()?t.clone():this.m.sub(t)._forceRed(this)},x.prototype.add=function(t,e){this._verify2(t,e);var n=t.add(e);return n.cmp(this.m)>=0&&n.isub(this.m),n._forceRed(this)},x.prototype.iadd=function(t,e){this._verify2(t,e);var n=t.iadd(e);return n.cmp(this.m)>=0&&n.isub(this.m),n},x.prototype.sub=function(t,e){this._verify2(t,e);var n=t.sub(e);return n.cmpn(0)<0&&n.iadd(this.m),n._forceRed(this)},x.prototype.isub=function(t,e){this._verify2(t,e);var n=t.isub(e);return n.cmpn(0)<0&&n.iadd(this.m),n},x.prototype.shl=function(t,e){return this._verify1(t),this.imod(t.ushln(e))},x.prototype.imul=function(t,e){return this._verify2(t,e),this.imod(t.imul(e))},x.prototype.mul=function(t,e){return this._verify2(t,e),this.imod(t.mul(e))},x.prototype.isqr=function(t){return this.imul(t,t.clone())},x.prototype.sqr=function(t){return this.mul(t,t)},x.prototype.sqrt=function(t){if(t.isZero())return t.clone();var e=this.m.andln(3);if(r(e%2==1),3===e){var n=this.m.add(new o(1)).iushrn(2);return this.pow(t,n)}for(var i=this.m.subn(1),a=0;!i.isZero()&&0===i.andln(1);)a++,i.iushrn(1);r(!i.isZero());var s=new o(1).toRed(this),u=s.redNeg(),c=this.m.subn(1).iushrn(1),f=this.m.bitLength();for(f=new o(2*f*f).toRed(this);0!==this.pow(f,c).cmp(u);)f.redIAdd(u);for(var l=this.pow(f,i),h=this.pow(t,i.addn(1).iushrn(1)),d=this.pow(t,i),p=a;0!==d.cmp(s);){for(var g=d,y=0;0!==g.cmp(s);y++)g=g.redSqr();r(y=0;r--){for(var c=e.words[r],f=u-1;f>=0;f--){var l=c>>f&1;i!==n[0]&&(i=this.sqr(i)),0!==l||0!==a?(a<<=1,a|=l,(4===++s||0===r&&0===f)&&(i=this.mul(i,n[a]),s=0,a=0)):s=0}u=26}return i},x.prototype.convertTo=function(t){var e=t.umod(this.m);return e===t?e.clone():e},x.prototype.convertFrom=function(t){var e=t.clone();return e.red=null,e},o.mont=function(t){return new k(t)},i(k,x),k.prototype.convertTo=function(t){return this.imod(t.ushln(this.shift))},k.prototype.convertFrom=function(t){var e=this.imod(t.mul(this.rinv));return e.red=null,e},k.prototype.imul=function(t,e){if(t.isZero()||e.isZero())return t.words[0]=0,t.length=1,t;var n=t.imul(e),r=n.maskn(this.shift).mul(this.minv).imaskn(this.shift).mul(this.m),i=n.isub(r).iushrn(this.shift),o=i;return i.cmp(this.m)>=0?o=i.isub(this.m):i.cmpn(0)<0&&(o=i.iadd(this.m)),o._forceRed(this)},k.prototype.mul=function(t,e){if(t.isZero()||e.isZero())return new o(0)._forceRed(this);var n=t.mul(e),r=n.maskn(this.shift).mul(this.minv).imaskn(this.shift).mul(this.m),i=n.isub(r).iushrn(this.shift),a=i;return i.cmp(this.m)>=0?a=i.isub(this.m):i.cmpn(0)<0&&(a=i.iadd(this.m)),a._forceRed(this)},k.prototype.invm=function(t){return this.imod(t._invmp(this.m).mul(this.r2))._forceRed(this)}}(t,this)}).call(this,n(9)(t))},function(t,e){var n=Array.isArray;t.exports=n},function(t,e){var n,r,i=t.exports={};function o(){throw new Error("setTimeout has not been defined")}function a(){throw new Error("clearTimeout has not been defined")}function s(t){if(n===setTimeout)return setTimeout(t,0);if((n===o||!n)&&setTimeout)return n=setTimeout,setTimeout(t,0);try{return n(t,0)}catch(e){try{return n.call(null,t,0)}catch(e){return n.call(this,t,0)}}}!function(){try{n="function"==typeof setTimeout?setTimeout:o}catch(t){n=o}try{r="function"==typeof clearTimeout?clearTimeout:a}catch(t){r=a}}();var u,c=[],f=!1,l=-1;function h(){f&&u&&(f=!1,u.length?c=u.concat(c):l=-1,c.length&&d())}function d(){if(!f){var t=s(h);f=!0;for(var e=c.length;e;){for(u=c,c=[];++l1)for(var n=1;n + * @license MIT + */ +var r=n(419),i=n(420),o=n(191);function a(){return u.TYPED_ARRAY_SUPPORT?2147483647:1073741823}function s(t,e){if(a()=a())throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+a().toString(16)+" bytes");return 0|t}function p(t,e){if(u.isBuffer(t))return t.length;if("undefined"!=typeof ArrayBuffer&&"function"==typeof ArrayBuffer.isView&&(ArrayBuffer.isView(t)||t instanceof ArrayBuffer))return t.byteLength;"string"!=typeof t&&(t=""+t);var n=t.length;if(0===n)return 0;for(var r=!1;;)switch(e){case"ascii":case"latin1":case"binary":return n;case"utf8":case"utf-8":case void 0:return U(t).length;case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return 2*n;case"hex":return n>>>1;case"base64":return z(t).length;default:if(r)return U(t).length;e=(""+e).toLowerCase(),r=!0}}function g(t,e,n){var r=!1;if((void 0===e||e<0)&&(e=0),e>this.length)return"";if((void 0===n||n>this.length)&&(n=this.length),n<=0)return"";if((n>>>=0)<=(e>>>=0))return"";for(t||(t="utf8");;)switch(t){case"hex":return C(this,e,n);case"utf8":case"utf-8":return S(this,e,n);case"ascii":return M(this,e,n);case"latin1":case"binary":return D(this,e,n);case"base64":return A(this,e,n);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return O(this,e,n);default:if(r)throw new TypeError("Unknown encoding: "+t);t=(t+"").toLowerCase(),r=!0}}function y(t,e,n){var r=t[e];t[e]=t[n],t[n]=r}function b(t,e,n,r,i){if(0===t.length)return-1;if("string"==typeof n?(r=n,n=0):n>2147483647?n=2147483647:n<-2147483648&&(n=-2147483648),n=+n,isNaN(n)&&(n=i?0:t.length-1),n<0&&(n=t.length+n),n>=t.length){if(i)return-1;n=t.length-1}else if(n<0){if(!i)return-1;n=0}if("string"==typeof e&&(e=u.from(e,r)),u.isBuffer(e))return 0===e.length?-1:m(t,e,n,r,i);if("number"==typeof e)return e&=255,u.TYPED_ARRAY_SUPPORT&&"function"==typeof Uint8Array.prototype.indexOf?i?Uint8Array.prototype.indexOf.call(t,e,n):Uint8Array.prototype.lastIndexOf.call(t,e,n):m(t,[e],n,r,i);throw new TypeError("val must be string, number or Buffer")}function m(t,e,n,r,i){var o,a=1,s=t.length,u=e.length;if(void 0!==r&&("ucs2"===(r=String(r).toLowerCase())||"ucs-2"===r||"utf16le"===r||"utf-16le"===r)){if(t.length<2||e.length<2)return-1;a=2,s/=2,u/=2,n/=2}function c(t,e){return 1===a?t[e]:t.readUInt16BE(e*a)}if(i){var f=-1;for(o=n;os&&(n=s-u),o=n;o>=0;o--){for(var l=!0,h=0;hi&&(r=i):r=i;var o=e.length;if(o%2!=0)throw new TypeError("Invalid hex string");r>o/2&&(r=o/2);for(var a=0;a>8,i=n%256,o.push(i),o.push(r);return o}(e,t.length-n),t,n,r)}function A(t,e,n){return 0===e&&n===t.length?r.fromByteArray(t):r.fromByteArray(t.slice(e,n))}function S(t,e,n){n=Math.min(t.length,n);for(var r=[],i=e;i239?4:c>223?3:c>191?2:1;if(i+l<=n)switch(l){case 1:c<128&&(f=c);break;case 2:128==(192&(o=t[i+1]))&&(u=(31&c)<<6|63&o)>127&&(f=u);break;case 3:o=t[i+1],a=t[i+2],128==(192&o)&&128==(192&a)&&(u=(15&c)<<12|(63&o)<<6|63&a)>2047&&(u<55296||u>57343)&&(f=u);break;case 4:o=t[i+1],a=t[i+2],s=t[i+3],128==(192&o)&&128==(192&a)&&128==(192&s)&&(u=(15&c)<<18|(63&o)<<12|(63&a)<<6|63&s)>65535&&u<1114112&&(f=u)}null===f?(f=65533,l=1):f>65535&&(f-=65536,r.push(f>>>10&1023|55296),f=56320|1023&f),r.push(f),i+=l}return function(t){var e=t.length;if(e<=T)return String.fromCharCode.apply(String,t);var n="",r=0;for(;r0&&(t=this.toString("hex",0,n).match(/.{2}/g).join(" "),this.length>n&&(t+=" ... ")),""},u.prototype.compare=function(t,e,n,r,i){if(!u.isBuffer(t))throw new TypeError("Argument must be a Buffer");if(void 0===e&&(e=0),void 0===n&&(n=t?t.length:0),void 0===r&&(r=0),void 0===i&&(i=this.length),e<0||n>t.length||r<0||i>this.length)throw new RangeError("out of range index");if(r>=i&&e>=n)return 0;if(r>=i)return-1;if(e>=n)return 1;if(this===t)return 0;for(var o=(i>>>=0)-(r>>>=0),a=(n>>>=0)-(e>>>=0),s=Math.min(o,a),c=this.slice(r,i),f=t.slice(e,n),l=0;li)&&(n=i),t.length>0&&(n<0||e<0)||e>this.length)throw new RangeError("Attempt to write outside buffer bounds");r||(r="utf8");for(var o=!1;;)switch(r){case"hex":return v(this,t,e,n);case"utf8":case"utf-8":return _(this,t,e,n);case"ascii":return w(this,t,e,n);case"latin1":case"binary":return x(this,t,e,n);case"base64":return k(this,t,e,n);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return E(this,t,e,n);default:if(o)throw new TypeError("Unknown encoding: "+r);r=(""+r).toLowerCase(),o=!0}},u.prototype.toJSON=function(){return{type:"Buffer",data:Array.prototype.slice.call(this._arr||this,0)}};var T=4096;function M(t,e,n){var r="";n=Math.min(t.length,n);for(var i=e;ir)&&(n=r);for(var i="",o=e;on)throw new RangeError("Trying to access beyond buffer length")}function I(t,e,n,r,i,o){if(!u.isBuffer(t))throw new TypeError('"buffer" argument must be a Buffer instance');if(e>i||et.length)throw new RangeError("Index out of range")}function N(t,e,n,r){e<0&&(e=65535+e+1);for(var i=0,o=Math.min(t.length-n,2);i>>8*(r?i:1-i)}function B(t,e,n,r){e<0&&(e=4294967295+e+1);for(var i=0,o=Math.min(t.length-n,4);i>>8*(r?i:3-i)&255}function L(t,e,n,r,i,o){if(n+r>t.length)throw new RangeError("Index out of range");if(n<0)throw new RangeError("Index out of range")}function P(t,e,n,r,o){return o||L(t,0,n,4),i.write(t,e,n,r,23,4),n+4}function F(t,e,n,r,o){return o||L(t,0,n,8),i.write(t,e,n,r,52,8),n+8}u.prototype.slice=function(t,e){var n,r=this.length;if((t=~~t)<0?(t+=r)<0&&(t=0):t>r&&(t=r),(e=void 0===e?r:~~e)<0?(e+=r)<0&&(e=0):e>r&&(e=r),e0&&(i*=256);)r+=this[t+--e]*i;return r},u.prototype.readUInt8=function(t,e){return e||R(t,1,this.length),this[t]},u.prototype.readUInt16LE=function(t,e){return e||R(t,2,this.length),this[t]|this[t+1]<<8},u.prototype.readUInt16BE=function(t,e){return e||R(t,2,this.length),this[t]<<8|this[t+1]},u.prototype.readUInt32LE=function(t,e){return e||R(t,4,this.length),(this[t]|this[t+1]<<8|this[t+2]<<16)+16777216*this[t+3]},u.prototype.readUInt32BE=function(t,e){return e||R(t,4,this.length),16777216*this[t]+(this[t+1]<<16|this[t+2]<<8|this[t+3])},u.prototype.readIntLE=function(t,e,n){t|=0,e|=0,n||R(t,e,this.length);for(var r=this[t],i=1,o=0;++o=(i*=128)&&(r-=Math.pow(2,8*e)),r},u.prototype.readIntBE=function(t,e,n){t|=0,e|=0,n||R(t,e,this.length);for(var r=e,i=1,o=this[t+--r];r>0&&(i*=256);)o+=this[t+--r]*i;return o>=(i*=128)&&(o-=Math.pow(2,8*e)),o},u.prototype.readInt8=function(t,e){return e||R(t,1,this.length),128&this[t]?-1*(255-this[t]+1):this[t]},u.prototype.readInt16LE=function(t,e){e||R(t,2,this.length);var n=this[t]|this[t+1]<<8;return 32768&n?4294901760|n:n},u.prototype.readInt16BE=function(t,e){e||R(t,2,this.length);var n=this[t+1]|this[t]<<8;return 32768&n?4294901760|n:n},u.prototype.readInt32LE=function(t,e){return e||R(t,4,this.length),this[t]|this[t+1]<<8|this[t+2]<<16|this[t+3]<<24},u.prototype.readInt32BE=function(t,e){return e||R(t,4,this.length),this[t]<<24|this[t+1]<<16|this[t+2]<<8|this[t+3]},u.prototype.readFloatLE=function(t,e){return e||R(t,4,this.length),i.read(this,t,!0,23,4)},u.prototype.readFloatBE=function(t,e){return e||R(t,4,this.length),i.read(this,t,!1,23,4)},u.prototype.readDoubleLE=function(t,e){return e||R(t,8,this.length),i.read(this,t,!0,52,8)},u.prototype.readDoubleBE=function(t,e){return e||R(t,8,this.length),i.read(this,t,!1,52,8)},u.prototype.writeUIntLE=function(t,e,n,r){(t=+t,e|=0,n|=0,r)||I(this,t,e,n,Math.pow(2,8*n)-1,0);var i=1,o=0;for(this[e]=255&t;++o=0&&(o*=256);)this[e+i]=t/o&255;return e+n},u.prototype.writeUInt8=function(t,e,n){return t=+t,e|=0,n||I(this,t,e,1,255,0),u.TYPED_ARRAY_SUPPORT||(t=Math.floor(t)),this[e]=255&t,e+1},u.prototype.writeUInt16LE=function(t,e,n){return t=+t,e|=0,n||I(this,t,e,2,65535,0),u.TYPED_ARRAY_SUPPORT?(this[e]=255&t,this[e+1]=t>>>8):N(this,t,e,!0),e+2},u.prototype.writeUInt16BE=function(t,e,n){return t=+t,e|=0,n||I(this,t,e,2,65535,0),u.TYPED_ARRAY_SUPPORT?(this[e]=t>>>8,this[e+1]=255&t):N(this,t,e,!1),e+2},u.prototype.writeUInt32LE=function(t,e,n){return t=+t,e|=0,n||I(this,t,e,4,4294967295,0),u.TYPED_ARRAY_SUPPORT?(this[e+3]=t>>>24,this[e+2]=t>>>16,this[e+1]=t>>>8,this[e]=255&t):B(this,t,e,!0),e+4},u.prototype.writeUInt32BE=function(t,e,n){return t=+t,e|=0,n||I(this,t,e,4,4294967295,0),u.TYPED_ARRAY_SUPPORT?(this[e]=t>>>24,this[e+1]=t>>>16,this[e+2]=t>>>8,this[e+3]=255&t):B(this,t,e,!1),e+4},u.prototype.writeIntLE=function(t,e,n,r){if(t=+t,e|=0,!r){var i=Math.pow(2,8*n-1);I(this,t,e,n,i-1,-i)}var o=0,a=1,s=0;for(this[e]=255&t;++o>0)-s&255;return e+n},u.prototype.writeIntBE=function(t,e,n,r){if(t=+t,e|=0,!r){var i=Math.pow(2,8*n-1);I(this,t,e,n,i-1,-i)}var o=n-1,a=1,s=0;for(this[e+o]=255&t;--o>=0&&(a*=256);)t<0&&0===s&&0!==this[e+o+1]&&(s=1),this[e+o]=(t/a>>0)-s&255;return e+n},u.prototype.writeInt8=function(t,e,n){return t=+t,e|=0,n||I(this,t,e,1,127,-128),u.TYPED_ARRAY_SUPPORT||(t=Math.floor(t)),t<0&&(t=255+t+1),this[e]=255&t,e+1},u.prototype.writeInt16LE=function(t,e,n){return t=+t,e|=0,n||I(this,t,e,2,32767,-32768),u.TYPED_ARRAY_SUPPORT?(this[e]=255&t,this[e+1]=t>>>8):N(this,t,e,!0),e+2},u.prototype.writeInt16BE=function(t,e,n){return t=+t,e|=0,n||I(this,t,e,2,32767,-32768),u.TYPED_ARRAY_SUPPORT?(this[e]=t>>>8,this[e+1]=255&t):N(this,t,e,!1),e+2},u.prototype.writeInt32LE=function(t,e,n){return t=+t,e|=0,n||I(this,t,e,4,2147483647,-2147483648),u.TYPED_ARRAY_SUPPORT?(this[e]=255&t,this[e+1]=t>>>8,this[e+2]=t>>>16,this[e+3]=t>>>24):B(this,t,e,!0),e+4},u.prototype.writeInt32BE=function(t,e,n){return t=+t,e|=0,n||I(this,t,e,4,2147483647,-2147483648),t<0&&(t=4294967295+t+1),u.TYPED_ARRAY_SUPPORT?(this[e]=t>>>24,this[e+1]=t>>>16,this[e+2]=t>>>8,this[e+3]=255&t):B(this,t,e,!1),e+4},u.prototype.writeFloatLE=function(t,e,n){return P(this,t,e,!0,n)},u.prototype.writeFloatBE=function(t,e,n){return P(this,t,e,!1,n)},u.prototype.writeDoubleLE=function(t,e,n){return F(this,t,e,!0,n)},u.prototype.writeDoubleBE=function(t,e,n){return F(this,t,e,!1,n)},u.prototype.copy=function(t,e,n,r){if(n||(n=0),r||0===r||(r=this.length),e>=t.length&&(e=t.length),e||(e=0),r>0&&r=this.length)throw new RangeError("sourceStart out of bounds");if(r<0)throw new RangeError("sourceEnd out of bounds");r>this.length&&(r=this.length),t.length-e=0;--i)t[i+e]=this[i+n];else if(o<1e3||!u.TYPED_ARRAY_SUPPORT)for(i=0;i>>=0,n=void 0===n?this.length:n>>>0,t||(t=0),"number"==typeof t)for(o=e;o55295&&n<57344){if(!i){if(n>56319){(e-=3)>-1&&o.push(239,191,189);continue}if(a+1===r){(e-=3)>-1&&o.push(239,191,189);continue}i=n;continue}if(n<56320){(e-=3)>-1&&o.push(239,191,189),i=n;continue}n=65536+(i-55296<<10|n-56320)}else i&&(e-=3)>-1&&o.push(239,191,189);if(i=null,n<128){if((e-=1)<0)break;o.push(n)}else if(n<2048){if((e-=2)<0)break;o.push(n>>6|192,63&n|128)}else if(n<65536){if((e-=3)<0)break;o.push(n>>12|224,n>>6&63|128,63&n|128)}else{if(!(n<1114112))throw new Error("Invalid code point");if((e-=4)<0)break;o.push(n>>18|240,n>>12&63|128,n>>6&63|128,63&n|128)}}return o}function z(t){return r.toByteArray(function(t){if((t=function(t){return t.trim?t.trim():t.replace(/^\s+|\s+$/g,"")}(t).replace(q,"")).length<2)return"";for(;t.length%4!=0;)t+="=";return t}(t))}function Y(t,e,n,r){for(var i=0;i=e.length||i>=t.length);++i)e[i+n]=t[i];return i}}).call(this,n(11))},function(t,e){t.exports=function(t){return t.webpackPolyfill||(t.deprecate=function(){},t.paths=[],t.children||(t.children=[]),Object.defineProperty(t,"loaded",{enumerable:!0,get:function(){return t.l}}),Object.defineProperty(t,"id",{enumerable:!0,get:function(){return t.i}}),t.webpackPolyfill=1),t}},function(t,e,n){"use strict";var r=n(4),i=n(19).Graph;function o(t,e,n,i){var o;do{o=r.uniqueId(i)}while(t.hasNode(o));return n.dummy=e,t.setNode(o,n),o}function a(t){return r.max(r.map(t.nodes(),(function(e){var n=t.node(e).rank;if(!r.isUndefined(n))return n})))}t.exports={addDummyNode:o,simplify:function(t){var e=(new i).setGraph(t.graph());return r.forEach(t.nodes(),(function(n){e.setNode(n,t.node(n))})),r.forEach(t.edges(),(function(n){var r=e.edge(n.v,n.w)||{weight:0,minlen:1},i=t.edge(n);e.setEdge(n.v,n.w,{weight:r.weight+i.weight,minlen:Math.max(r.minlen,i.minlen)})})),e},asNonCompoundGraph:function(t){var e=new i({multigraph:t.isMultigraph()}).setGraph(t.graph());return r.forEach(t.nodes(),(function(n){t.children(n).length||e.setNode(n,t.node(n))})),r.forEach(t.edges(),(function(n){e.setEdge(n,t.edge(n))})),e},successorWeights:function(t){var e=r.map(t.nodes(),(function(e){var n={};return r.forEach(t.outEdges(e),(function(e){n[e.w]=(n[e.w]||0)+t.edge(e).weight})),n}));return r.zipObject(t.nodes(),e)},predecessorWeights:function(t){var e=r.map(t.nodes(),(function(e){var n={};return r.forEach(t.inEdges(e),(function(e){n[e.v]=(n[e.v]||0)+t.edge(e).weight})),n}));return r.zipObject(t.nodes(),e)},intersectRect:function(t,e){var n,r,i=t.x,o=t.y,a=e.x-i,s=e.y-o,u=t.width/2,c=t.height/2;if(!a&&!s)throw new Error("Not possible to find intersection inside of the rectangle");Math.abs(s)*u>Math.abs(a)*c?(s<0&&(c=-c),n=c*a/s,r=c):(a<0&&(u=-u),n=u,r=u*s/a);return{x:i+n,y:o+r}},buildLayerMatrix:function(t){var e=r.map(r.range(a(t)+1),(function(){return[]}));return r.forEach(t.nodes(),(function(n){var i=t.node(n),o=i.rank;r.isUndefined(o)||(e[o][i.order]=n)})),e},normalizeRanks:function(t){var e=r.min(r.map(t.nodes(),(function(e){return t.node(e).rank})));r.forEach(t.nodes(),(function(n){var i=t.node(n);r.has(i,"rank")&&(i.rank-=e)}))},removeEmptyRanks:function(t){var e=r.min(r.map(t.nodes(),(function(e){return t.node(e).rank}))),n=[];r.forEach(t.nodes(),(function(r){var i=t.node(r).rank-e;n[i]||(n[i]=[]),n[i].push(r)}));var i=0,o=t.graph().nodeRankFactor;r.forEach(n,(function(e,n){r.isUndefined(e)&&n%o!=0?--i:i&&r.forEach(e,(function(e){t.node(e).rank+=i}))}))},addBorderNode:function(t,e,n,r){var i={width:0,height:0};arguments.length>=4&&(i.rank=n,i.order=r);return o(t,"border",i,e)},maxRank:a,partition:function(t,e){var n={lhs:[],rhs:[]};return r.forEach(t,(function(t){e(t)?n.lhs.push(t):n.rhs.push(t)})),n},time:function(t,e){var n=r.now();try{return e()}finally{console.log(t+" time: "+(r.now()-n)+"ms")}},notime:function(t,e){return e()}}},function(t,e){var n;n=function(){return this}();try{n=n||new Function("return this")()}catch(t){"object"==typeof window&&(n=window)}t.exports=n},function(t,e,n){var r;try{r={clone:n(235),constant:n(99),each:n(100),filter:n(150),has:n(106),isArray:n(6),isEmpty:n(311),isFunction:n(37),isUndefined:n(161),keys:n(27),map:n(162),reduce:n(164),size:n(314),transform:n(320),union:n(321),values:n(169)}}catch(t){}r||(r=window._),t.exports=r},function(t,e){t.exports=function(t){var e=typeof t;return null!=t&&("object"==e||"function"==e)}},function(t,e,n){var r=n(43);t.exports={isSubgraph:function(t,e){return!!t.children(e).length},edgeToId:function(t){return o(t.v)+":"+o(t.w)+":"+o(t.name)},applyStyle:function(t,e){e&&t.attr("style",e)},applyClass:function(t,e,n){e&&t.attr("class",e).attr("class",n+" "+t.attr("class"))},applyTransition:function(t,e){var n=e.graph();if(r.isPlainObject(n)){var i=n.transition;if(r.isFunction(i))return i(t)}return t}};var i=/:/g;function o(t){return t?String(t).replace(i,"\\:"):""}},function(t,e){function n(t,e){if(!t)throw new Error(e||"Assertion failed")}t.exports=n,n.equal=function(t,e,n){if(t!=e)throw new Error(n||"Assertion failed: "+t+" != "+e)}},function(t,e,n){"use strict";var r=e,i=n(5),o=n(15),a=n(213);r.assert=o,r.toArray=a.toArray,r.zero2=a.zero2,r.toHex=a.toHex,r.encode=a.encode,r.getNAF=function(t,e){for(var n=[],r=1<=0;){var o;if(i.isOdd()){var a=i.andln(r-1);o=a>(r>>1)-1?(r>>1)-a:a,i.isubn(o)}else o=0;n.push(o);for(var s=0!==i.cmpn(0)&&0===i.andln(r-1)?e+1:1,u=1;u0||e.cmpn(-i)>0;){var o,a,s,u=t.andln(3)+r&3,c=e.andln(3)+i&3;if(3===u&&(u=-1),3===c&&(c=-1),0==(1&u))o=0;else o=3!==(s=t.andln(7)+r&7)&&5!==s||2!==c?u:-u;if(n[0].push(o),0==(1&c))a=0;else a=3!==(s=e.andln(7)+i&7)&&5!==s||2!==u?c:-c;n[1].push(a),2*r===o+1&&(r=1-r),2*i===a+1&&(i=1-i),t.iushrn(1),e.iushrn(1)}return n},r.cachedProperty=function(t,e,n){var r="_"+e;t.prototype[e]=function(){return void 0!==this[r]?this[r]:this[r]=n.call(this)}},r.parseBytes=function(t){return"string"==typeof t?r.toArray(t,"hex"):t},r.intFromLE=function(t){return new i(t,"hex","le")}},function(t,e,n){ +/** + * @license + * Copyright (c) 2012-2013 Chris Pettitt + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +t.exports={graphlib:n(346),dagre:n(175),intersect:n(403),render:n(405),util:n(14),version:n(417)}},function(t,e,n){var r=n(131),i="object"==typeof self&&self&&self.Object===Object&&self,o=r||i||Function("return this")();t.exports=o},function(t,e,n){var r;try{r=n(22)}catch(t){}r||(r=window.graphlib),t.exports=r},function(t,e){t.exports=function(t){return null!=t&&"object"==typeof t}},function(t,e,n){"use strict";var r=n(15),i=n(2);function o(t,e){return 55296==(64512&t.charCodeAt(e))&&(!(e<0||e+1>=t.length)&&56320==(64512&t.charCodeAt(e+1)))}function a(t){return(t>>>24|t>>>8&65280|t<<8&16711680|(255&t)<<24)>>>0}function s(t){return 1===t.length?"0"+t:t}function u(t){return 7===t.length?"0"+t:6===t.length?"00"+t:5===t.length?"000"+t:4===t.length?"0000"+t:3===t.length?"00000"+t:2===t.length?"000000"+t:1===t.length?"0000000"+t:t}e.inherits=i,e.toArray=function(t,e){if(Array.isArray(t))return t.slice();if(!t)return[];var n=[];if("string"==typeof t)if(e){if("hex"===e)for((t=t.replace(/[^a-z0-9]+/gi,"")).length%2!=0&&(t="0"+t),i=0;i>6|192,n[r++]=63&a|128):o(t,i)?(a=65536+((1023&a)<<10)+(1023&t.charCodeAt(++i)),n[r++]=a>>18|240,n[r++]=a>>12&63|128,n[r++]=a>>6&63|128,n[r++]=63&a|128):(n[r++]=a>>12|224,n[r++]=a>>6&63|128,n[r++]=63&a|128)}else for(i=0;i>>0}return a},e.split32=function(t,e){for(var n=new Array(4*t.length),r=0,i=0;r>>24,n[i+1]=o>>>16&255,n[i+2]=o>>>8&255,n[i+3]=255&o):(n[i+3]=o>>>24,n[i+2]=o>>>16&255,n[i+1]=o>>>8&255,n[i]=255&o)}return n},e.rotr32=function(t,e){return t>>>e|t<<32-e},e.rotl32=function(t,e){return t<>>32-e},e.sum32=function(t,e){return t+e>>>0},e.sum32_3=function(t,e,n){return t+e+n>>>0},e.sum32_4=function(t,e,n,r){return t+e+n+r>>>0},e.sum32_5=function(t,e,n,r,i){return t+e+n+r+i>>>0},e.sum64=function(t,e,n,r){var i=t[e],o=r+t[e+1]>>>0,a=(o>>0,t[e+1]=o},e.sum64_hi=function(t,e,n,r){return(e+r>>>0>>0},e.sum64_lo=function(t,e,n,r){return e+r>>>0},e.sum64_4_hi=function(t,e,n,r,i,o,a,s){var u=0,c=e;return u+=(c=c+r>>>0)>>0)>>0)>>0},e.sum64_4_lo=function(t,e,n,r,i,o,a,s){return e+r+o+s>>>0},e.sum64_5_hi=function(t,e,n,r,i,o,a,s,u,c){var f=0,l=e;return f+=(l=l+r>>>0)>>0)>>0)>>0)>>0},e.sum64_5_lo=function(t,e,n,r,i,o,a,s,u,c){return e+r+o+s+c>>>0},e.rotr64_hi=function(t,e,n){return(e<<32-n|t>>>n)>>>0},e.rotr64_lo=function(t,e,n){return(t<<32-n|e>>>n)>>>0},e.shr64_hi=function(t,e,n){return t>>>n},e.shr64_lo=function(t,e,n){return(t<<32-n|e>>>n)>>>0}},function(t,e,n){var r=n(234);t.exports={Graph:r.Graph,json:n(336),alg:n(337),version:r.version}},function(t,e,n){(function(t){t.exports=function(){"use strict";var e,r;function i(){return e.apply(null,arguments)}function o(t){return t instanceof Array||"[object Array]"===Object.prototype.toString.call(t)}function a(t){return null!=t&&"[object Object]"===Object.prototype.toString.call(t)}function s(t){return void 0===t}function u(t){return"number"==typeof t||"[object Number]"===Object.prototype.toString.call(t)}function c(t){return t instanceof Date||"[object Date]"===Object.prototype.toString.call(t)}function f(t,e){var n,r=[];for(n=0;n>>0,r=0;rAt(t)?(o=t+1,a=s-At(t)):(o=t,a=s),{year:o,dayOfYear:a}}function Vt(t,e,n){var r,i,o=zt(t.year(),e,n),a=Math.floor((t.dayOfYear()-o-1)/7)+1;return a<1?r=a+Ht(i=t.year()-1,e,n):a>Ht(t.year(),e,n)?(r=a-Ht(t.year(),e,n),i=t.year()+1):(i=t.year(),r=a),{week:r,year:i}}function Ht(t,e,n){var r=zt(t,e,n),i=zt(t+1,e,n);return(At(t)-r+i)/7}V("w",["ww",2],"wo","week"),V("W",["WW",2],"Wo","isoWeek"),N("week","w"),N("isoWeek","W"),F("week",5),F("isoWeek",5),ft("w",J),ft("ww",J,W),ft("W",J),ft("WW",J,W),gt(["w","ww","W","WW"],(function(t,e,n,r){e[r.substr(0,1)]=k(t)})),V("d",0,"do","day"),V("dd",0,0,(function(t){return this.localeData().weekdaysMin(this,t)})),V("ddd",0,0,(function(t){return this.localeData().weekdaysShort(this,t)})),V("dddd",0,0,(function(t){return this.localeData().weekdays(this,t)})),V("e",0,0,"weekday"),V("E",0,0,"isoWeekday"),N("day","d"),N("weekday","e"),N("isoWeekday","E"),F("day",11),F("weekday",11),F("isoWeekday",11),ft("d",J),ft("e",J),ft("E",J),ft("dd",(function(t,e){return e.weekdaysMinRegex(t)})),ft("ddd",(function(t,e){return e.weekdaysShortRegex(t)})),ft("dddd",(function(t,e){return e.weekdaysRegex(t)})),gt(["dd","ddd","dddd"],(function(t,e,n,r){var i=n._locale.weekdaysParse(t,r,n._strict);null!=i?e.d=i:p(n).invalidWeekday=t})),gt(["d","e","E"],(function(t,e,n,r){e[r]=k(t)}));var $t="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),Gt="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),Wt="Su_Mo_Tu_We_Th_Fr_Sa".split("_"),Kt=ut,Xt=ut,Zt=ut;function Jt(){function t(t,e){return e.length-t.length}var e,n,r,i,o,a=[],s=[],u=[],c=[];for(e=0;e<7;e++)n=d([2e3,1]).day(e),r=this.weekdaysMin(n,""),i=this.weekdaysShort(n,""),o=this.weekdays(n,""),a.push(r),s.push(i),u.push(o),c.push(r),c.push(i),c.push(o);for(a.sort(t),s.sort(t),u.sort(t),c.sort(t),e=0;e<7;e++)s[e]=ht(s[e]),u[e]=ht(u[e]),c[e]=ht(c[e]);this._weekdaysRegex=new RegExp("^("+c.join("|")+")","i"),this._weekdaysShortRegex=this._weekdaysRegex,this._weekdaysMinRegex=this._weekdaysRegex,this._weekdaysStrictRegex=new RegExp("^("+u.join("|")+")","i"),this._weekdaysShortStrictRegex=new RegExp("^("+s.join("|")+")","i"),this._weekdaysMinStrictRegex=new RegExp("^("+a.join("|")+")","i")}function Qt(){return this.hours()%12||12}function te(t,e){V(t,0,0,(function(){return this.localeData().meridiem(this.hours(),this.minutes(),e)}))}function ee(t,e){return e._meridiemParse}V("H",["HH",2],0,"hour"),V("h",["hh",2],0,Qt),V("k",["kk",2],0,(function(){return this.hours()||24})),V("hmm",0,0,(function(){return""+Qt.apply(this)+q(this.minutes(),2)})),V("hmmss",0,0,(function(){return""+Qt.apply(this)+q(this.minutes(),2)+q(this.seconds(),2)})),V("Hmm",0,0,(function(){return""+this.hours()+q(this.minutes(),2)})),V("Hmmss",0,0,(function(){return""+this.hours()+q(this.minutes(),2)+q(this.seconds(),2)})),te("a",!0),te("A",!1),N("hour","h"),F("hour",13),ft("a",ee),ft("A",ee),ft("H",J),ft("h",J),ft("k",J),ft("HH",J,W),ft("hh",J,W),ft("kk",J,W),ft("hmm",Q),ft("hmmss",tt),ft("Hmm",Q),ft("Hmmss",tt),pt(["H","HH"],vt),pt(["k","kk"],(function(t,e,n){var r=k(t);e[vt]=24===r?0:r})),pt(["a","A"],(function(t,e,n){n._isPm=n._locale.isPM(t),n._meridiem=t})),pt(["h","hh"],(function(t,e,n){e[vt]=k(t),p(n).bigHour=!0})),pt("hmm",(function(t,e,n){var r=t.length-2;e[vt]=k(t.substr(0,r)),e[_t]=k(t.substr(r)),p(n).bigHour=!0})),pt("hmmss",(function(t,e,n){var r=t.length-4,i=t.length-2;e[vt]=k(t.substr(0,r)),e[_t]=k(t.substr(r,2)),e[wt]=k(t.substr(i)),p(n).bigHour=!0})),pt("Hmm",(function(t,e,n){var r=t.length-2;e[vt]=k(t.substr(0,r)),e[_t]=k(t.substr(r))})),pt("Hmmss",(function(t,e,n){var r=t.length-4,i=t.length-2;e[vt]=k(t.substr(0,r)),e[_t]=k(t.substr(r,2)),e[wt]=k(t.substr(i))}));var ne,re=Dt("Hours",!0),ie={calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},longDateFormat:{LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},invalidDate:"Invalid date",ordinal:"%d",dayOfMonthOrdinalParse:/\d{1,2}/,relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},months:Nt,monthsShort:Bt,week:{dow:0,doy:6},weekdays:$t,weekdaysMin:Wt,weekdaysShort:Gt,meridiemParse:/[ap]\.?m?\.?/i},oe={},ae={};function se(t){return t?t.toLowerCase().replace("_","-"):t}function ue(e){var r=null;if(!oe[e]&&void 0!==t&&t&&t.exports)try{r=ne._abbr,n(233)("./"+e),ce(r)}catch(e){}return oe[e]}function ce(t,e){var n;return t&&((n=s(e)?le(t):fe(t,e))?ne=n:"undefined"!=typeof console&&console.warn&&console.warn("Locale "+t+" not found. Did you forget to load it?")),ne._abbr}function fe(t,e){if(null!==e){var n,r=ie;if(e.abbr=t,null!=oe[t])D("defineLocaleOverride","use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale See http://momentjs.com/guides/#/warnings/define-locale/ for more info."),r=oe[t]._config;else if(null!=e.parentLocale)if(null!=oe[e.parentLocale])r=oe[e.parentLocale]._config;else{if(null==(n=ue(e.parentLocale)))return ae[e.parentLocale]||(ae[e.parentLocale]=[]),ae[e.parentLocale].push({name:t,config:e}),null;r=n._config}return oe[t]=new R(O(r,e)),ae[t]&&ae[t].forEach((function(t){fe(t.name,t.config)})),ce(t),oe[t]}return delete oe[t],null}function le(t){var e;if(t&&t._locale&&t._locale._abbr&&(t=t._locale._abbr),!t)return ne;if(!o(t)){if(e=ue(t))return e;t=[t]}return function(t){for(var e,n,r,i,o=0;o=e&&E(i,n,!0)>=e-1)break;e--}o++}return ne}(t)}function he(t){var e,n=t._a;return n&&-2===p(t).overflow&&(e=n[bt]<0||11Rt(n[yt],n[bt])?mt:n[vt]<0||24Ht(n,o,a)?p(t)._overflowWeeks=!0:null!=u?p(t)._overflowWeekday=!0:(s=Yt(n,r,i,o,a),t._a[yt]=s.year,t._dayOfYear=s.dayOfYear)}(t),null!=t._dayOfYear&&(a=de(t._a[yt],r[yt]),(t._dayOfYear>At(a)||0===t._dayOfYear)&&(p(t)._overflowDayOfYear=!0),n=Ut(a,0,t._dayOfYear),t._a[bt]=n.getUTCMonth(),t._a[mt]=n.getUTCDate()),e=0;e<3&&null==t._a[e];++e)t._a[e]=s[e]=r[e];for(;e<7;e++)t._a[e]=s[e]=null==t._a[e]?2===e?1:0:t._a[e];24===t._a[vt]&&0===t._a[_t]&&0===t._a[wt]&&0===t._a[xt]&&(t._nextDay=!0,t._a[vt]=0),t._d=(t._useUTC?Ut:function(t,e,n,r,i,o,a){var s=new Date(t,e,n,r,i,o,a);return t<100&&0<=t&&isFinite(s.getFullYear())&&s.setFullYear(t),s}).apply(null,s),o=t._useUTC?t._d.getUTCDay():t._d.getDay(),null!=t._tzm&&t._d.setUTCMinutes(t._d.getUTCMinutes()-t._tzm),t._nextDay&&(t._a[vt]=24),t._w&&void 0!==t._w.d&&t._w.d!==o&&(p(t).weekdayMismatch=!0)}}var ge=/^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,ye=/^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,be=/Z|[+-]\d\d(?::?\d\d)?/,me=[["YYYYYY-MM-DD",/[+-]\d{6}-\d\d-\d\d/],["YYYY-MM-DD",/\d{4}-\d\d-\d\d/],["GGGG-[W]WW-E",/\d{4}-W\d\d-\d/],["GGGG-[W]WW",/\d{4}-W\d\d/,!1],["YYYY-DDD",/\d{4}-\d{3}/],["YYYY-MM",/\d{4}-\d\d/,!1],["YYYYYYMMDD",/[+-]\d{10}/],["YYYYMMDD",/\d{8}/],["GGGG[W]WWE",/\d{4}W\d{3}/],["GGGG[W]WW",/\d{4}W\d{2}/,!1],["YYYYDDD",/\d{7}/]],ve=[["HH:mm:ss.SSSS",/\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss,SSSS",/\d\d:\d\d:\d\d,\d+/],["HH:mm:ss",/\d\d:\d\d:\d\d/],["HH:mm",/\d\d:\d\d/],["HHmmss.SSSS",/\d\d\d\d\d\d\.\d+/],["HHmmss,SSSS",/\d\d\d\d\d\d,\d+/],["HHmmss",/\d\d\d\d\d\d/],["HHmm",/\d\d\d\d/],["HH",/\d\d/]],_e=/^\/?Date\((\-?\d+)/i;function we(t){var e,n,r,i,o,a,s=t._i,u=ge.exec(s)||ye.exec(s);if(u){for(p(t).iso=!0,e=0,n=me.length;en.valueOf():n.valueOf()this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()},fn.isLocal=function(){return!!this.isValid()&&!this._isUTC},fn.isUtcOffset=function(){return!!this.isValid()&&this._isUTC},fn.isUtc=Ue,fn.isUTC=Ue,fn.zoneAbbr=function(){return this._isUTC?"UTC":""},fn.zoneName=function(){return this._isUTC?"Coordinated Universal Time":""},fn.dates=S("dates accessor is deprecated. Use date instead.",rn),fn.months=S("months accessor is deprecated. Use month instead",Pt),fn.years=S("years accessor is deprecated. Use year instead",Mt),fn.zone=S("moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/",(function(t,e){return null!=t?("string"!=typeof t&&(t=-t),this.utcOffset(t,e),this):-this.utcOffset()})),fn.isDSTShifted=S("isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information",(function(){if(!s(this._isDSTShifted))return this._isDSTShifted;var t={};if(m(t,this),(t=Se(t))._a){var e=t._isUTC?d(t._a):Me(t._a);this._isDSTShifted=this.isValid()&&0l&&M.push("'"+this.terminals_[A]+"'");C=p.showPosition?"Parse error on line "+(u+1)+":\n"+p.showPosition()+"\nExpecting "+M.join(", ")+", got '"+(this.terminals_[_]||_)+"'":"Parse error on line "+(u+1)+": Unexpected "+(_==h?"end of input":"'"+(this.terminals_[_]||_)+"'"),this.parseError(C,{text:p.match,token:this.terminals_[_]||_,line:p.yylineno,loc:b,expected:M})}if(k[0]instanceof Array&&k.length>1)throw new Error("Parse Error: multiple actions possible at state: "+x+", token: "+_);switch(k[0]){case 1:n.push(_),i.push(p.yytext),o.push(p.yylloc),n.push(k[1]),_=null,w?(_=w,w=null):(c=p.yyleng,s=p.yytext,u=p.yylineno,b=p.yylloc,f>0&&f--);break;case 2:if(S=this.productions_[k[1]][1],D.$=i[i.length-S],D._$={first_line:o[o.length-(S||1)].first_line,last_line:o[o.length-1].last_line,first_column:o[o.length-(S||1)].first_column,last_column:o[o.length-1].last_column},m&&(D._$.range=[o[o.length-(S||1)].range[0],o[o.length-1].range[1]]),void 0!==(E=this.performAction.apply(D,[s,c,u,g.yy,k[1],i,o].concat(d))))return E;S&&(n=n.slice(0,-1*S*2),i=i.slice(0,-1*S),o=o.slice(0,-1*S)),n.push(this.productions_[k[1]][0]),i.push(D.$),o.push(D._$),T=a[n[n.length-2]][n[n.length-1]],n.push(T);break;case 3:return!0}}return!0}},l={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var o in i)this[o]=i[o];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),o=0;oe[0].length)){if(e=n,r=o,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[o])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var t=this.next();return t||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{"case-insensitive":!0},performAction:function(t,e,n,r){switch(n){case 0:return 10;case 1:case 2:case 3:break;case 4:this.begin("href");break;case 5:this.popState();break;case 6:return 23;case 7:this.begin("callbackname");break;case 8:this.popState();break;case 9:this.popState(),this.begin("callbackargs");break;case 10:return 21;case 11:this.popState();break;case 12:return 22;case 13:this.begin("click");break;case 14:this.popState();break;case 15:return 20;case 16:return 4;case 17:return 11;case 18:return 12;case 19:return 13;case 20:return 14;case 21:return"date";case 22:return 15;case 23:return 16;case 24:return 18;case 25:return 19;case 26:return":";case 27:return 6;case 28:return"INVALID"}},rules:[/^(?:[\n]+)/i,/^(?:\s+)/i,/^(?:#[^\n]*)/i,/^(?:%[^\n]*)/i,/^(?:href[\s]+["])/i,/^(?:["])/i,/^(?:[^"]*)/i,/^(?:call[\s]+)/i,/^(?:\([\s]*\))/i,/^(?:\()/i,/^(?:[^(]*)/i,/^(?:\))/i,/^(?:[^)]*)/i,/^(?:click[\s]+)/i,/^(?:[\s\n])/i,/^(?:[^\s\n]*)/i,/^(?:gantt\b)/i,/^(?:dateFormat\s[^#\n;]+)/i,/^(?:inclusiveEndDates\b)/i,/^(?:axisFormat\s[^#\n;]+)/i,/^(?:excludes\s[^#\n;]+)/i,/^(?:\d\d\d\d-\d\d-\d\d\b)/i,/^(?:title\s[^#\n;]+)/i,/^(?:section\s[^#:\n;]+)/i,/^(?:[^#:\n;]+)/i,/^(?::[^#\n;]+)/i,/^(?::)/i,/^(?:$)/i,/^(?:.)/i],conditions:{callbackargs:{rules:[11,12],inclusive:!1},callbackname:{rules:[8,9,10],inclusive:!1},href:{rules:[5,6],inclusive:!1},click:{rules:[14,15],inclusive:!1},INITIAL:{rules:[0,1,2,3,4,7,13,16,17,18,19,20,21,22,23,24,25,26,27,28],inclusive:!0}}};function h(){this.yy={}}return f.lexer=l,h.prototype=f,f.Parser=h,new h}();e.parser=i,e.Parser=i.Parser,e.parse=function(){return i.parse.apply(i,arguments)},e.main=function(r){r[1]||(console.log("Usage: "+r[0]+" FILE"),t.exit(1));var i=n(28).readFileSync(n(29).normalize(r[1]),"utf8");return e.parser.parse(i)},n.c[n.s]===r&&e.main(t.argv.slice(1))}).call(this,n(7),n(9)(t))},function(t,e,n){var r=n(134),i=n(95),o=n(24);t.exports=function(t){return o(t)?r(t):i(t)}},function(t,e){},function(t,e,n){(function(t){function n(t,e){for(var n=0,r=t.length-1;r>=0;r--){var i=t[r];"."===i?t.splice(r,1):".."===i?(t.splice(r,1),n++):n&&(t.splice(r,1),n--)}if(e)for(;n--;n)t.unshift("..");return t}function r(t,e){if(t.filter)return t.filter(e);for(var n=[],r=0;r=-1&&!i;o--){var a=o>=0?arguments[o]:t.cwd();if("string"!=typeof a)throw new TypeError("Arguments to path.resolve must be strings");a&&(e=a+"/"+e,i="/"===a.charAt(0))}return(i?"/":"")+(e=n(r(e.split("/"),(function(t){return!!t})),!i).join("/"))||"."},e.normalize=function(t){var o=e.isAbsolute(t),a="/"===i(t,-1);return(t=n(r(t.split("/"),(function(t){return!!t})),!o).join("/"))||o||(t="."),t&&a&&(t+="/"),(o?"/":"")+t},e.isAbsolute=function(t){return"/"===t.charAt(0)},e.join=function(){var t=Array.prototype.slice.call(arguments,0);return e.normalize(r(t,(function(t,e){if("string"!=typeof t)throw new TypeError("Arguments to path.join must be strings");return t})).join("/"))},e.relative=function(t,n){function r(t){for(var e=0;e=0&&""===t[n];n--);return e>n?[]:t.slice(e,n-e+1)}t=e.resolve(t).substr(1),n=e.resolve(n).substr(1);for(var i=r(t.split("/")),o=r(n.split("/")),a=Math.min(i.length,o.length),s=a,u=0;u=1;--o)if(47===(e=t.charCodeAt(o))){if(!i){r=o;break}}else i=!1;return-1===r?n?"/":".":n&&1===r?"/":t.slice(0,r)},e.basename=function(t,e){var n=function(t){"string"!=typeof t&&(t+="");var e,n=0,r=-1,i=!0;for(e=t.length-1;e>=0;--e)if(47===t.charCodeAt(e)){if(!i){n=e+1;break}}else-1===r&&(i=!1,r=e+1);return-1===r?"":t.slice(n,r)}(t);return e&&n.substr(-1*e.length)===e&&(n=n.substr(0,n.length-e.length)),n},e.extname=function(t){"string"!=typeof t&&(t+="");for(var e=-1,n=0,r=-1,i=!0,o=0,a=t.length-1;a>=0;--a){var s=t.charCodeAt(a);if(47!==s)-1===r&&(i=!1,r=a+1),46===s?-1===e?e=a:1!==o&&(o=1):-1!==e&&(o=-1);else if(!i){n=a+1;break}}return-1===e||-1===r||0===o||1===o&&e===r-1&&e===n+1?"":t.slice(e,r)};var i="b"==="ab".substr(-1)?function(t,e,n){return t.substr(e,n)}:function(t,e,n){return e<0&&(e=t.length+e),t.substr(e,n)}}).call(this,n(7))},function(t,e,n){var r;if(!r)try{r=n(0)}catch(t){}r||(r=window.d3),t.exports=r},function(t,e,n){var r=n(3).Buffer,i=n(112).Transform,o=n(117).StringDecoder;function a(t){i.call(this),this.hashMode="string"==typeof t,this.hashMode?this[t]=this._finalOrDigest:this.final=this._finalOrDigest,this._final&&(this.__final=this._final,this._final=null),this._decoder=null,this._encoding=null}n(2)(a,i),a.prototype.update=function(t,e,n){"string"==typeof t&&(t=r.from(t,e));var i=this._update(t);return this.hashMode?this:(n&&(i=this._toString(i,n)),i)},a.prototype.setAutoPadding=function(){},a.prototype.getAuthTag=function(){throw new Error("trying to get auth tag in unsupported state")},a.prototype.setAuthTag=function(){throw new Error("trying to set auth tag in unsupported state")},a.prototype.setAAD=function(){throw new Error("trying to set aad in unsupported state")},a.prototype._transform=function(t,e,n){var r;try{this.hashMode?this._update(t):this.push(this._update(t))}catch(t){r=t}finally{n(r)}},a.prototype._flush=function(t){var e;try{this.push(this.__final())}catch(t){e=t}t(e)},a.prototype._finalOrDigest=function(t){var e=this.__final()||r.alloc(0);return t&&(e=this._toString(e,t,!0)),e},a.prototype._toString=function(t,e,n){if(this._decoder||(this._decoder=new o(e),this._encoding=e),this._encoding!==e)throw new Error("can't switch encodings");var r=this._decoder.write(t);return n&&(r+=this._decoder.end()),r},t.exports=a},function(t,e,n){var r=n(246),i=n(251);t.exports=function(t,e){var n=i(t,e);return r(n)?n:void 0}},function(t,e,n){var r=n(38),i=n(247),o=n(248),a="[object Null]",s="[object Undefined]",u=r?r.toStringTag:void 0;t.exports=function(t){return null==t?void 0===t?s:a:u&&u in Object(t)?i(t):o(t)}},function(t,e){t.exports=function(t){return t}},function(t,e,n){"use strict";var r=n(78),i=Object.keys||function(t){var e=[];for(var n in t)e.push(n);return e};t.exports=l;var o=n(54);o.inherits=n(2);var a=n(193),s=n(116);o.inherits(l,a);for(var u=i(s.prototype),c=0;co)throw new RangeError("requested too many random bytes");var n=a.allocUnsafe(t);if(t>0)if(t>i)for(var u=0;u=this._finalSize&&(this._update(this._block),this._block.fill(0));var n=8*this._len;if(n<=4294967295)this._block.writeUInt32BE(n,this._blockSize-4);else{var r=(4294967295&n)>>>0,i=(n-r)/4294967296;this._block.writeUInt32BE(i,this._blockSize-8),this._block.writeUInt32BE(r,this._blockSize-4)}this._update(this._block);var o=this._hash();return t?o.toString(t):o},i.prototype._update=function(){throw new Error("_update must be implemented by subclass")},t.exports=i},function(t,e,n){(function(t,r){var i=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[1,12],n=[1,15],r=[1,13],i=[1,14],o=[1,17],a=[1,18],s=[1,19],u=[6,8],c=[1,28],f=[1,29],l=[1,30],h=[1,31],d=[1,32],p=[1,33],g=[6,8,13,18,26,29,30,31,32,33,34],y=[6,8,13,18,22,26,29,30,31,32,33,34,48,49,50],b=[26,48,49,50],m=[26,33,34,48,49,50],v=[26,29,30,31,32,48,49,50],_=[6,8,13],w=[1,50],x={trace:function(){},yy:{},symbols_:{error:2,mermaidDoc:3,graphConfig:4,CLASS_DIAGRAM:5,NEWLINE:6,statements:7,EOF:8,statement:9,className:10,alphaNumToken:11,relationStatement:12,LABEL:13,classStatement:14,methodStatement:15,annotationStatement:16,CLASS:17,STRUCT_START:18,members:19,STRUCT_STOP:20,ANNOTATION_START:21,ANNOTATION_END:22,MEMBER:23,SEPARATOR:24,relation:25,STR:26,relationType:27,lineType:28,AGGREGATION:29,EXTENSION:30,COMPOSITION:31,DEPENDENCY:32,LINE:33,DOTTED_LINE:34,commentToken:35,textToken:36,graphCodeTokens:37,textNoTagsToken:38,TAGSTART:39,TAGEND:40,"==":41,"--":42,PCT:43,DEFAULT:44,SPACE:45,MINUS:46,keywords:47,UNICODE_TEXT:48,NUM:49,ALPHA:50,$accept:0,$end:1},terminals_:{2:"error",5:"CLASS_DIAGRAM",6:"NEWLINE",8:"EOF",13:"LABEL",17:"CLASS",18:"STRUCT_START",20:"STRUCT_STOP",21:"ANNOTATION_START",22:"ANNOTATION_END",23:"MEMBER",24:"SEPARATOR",26:"STR",29:"AGGREGATION",30:"EXTENSION",31:"COMPOSITION",32:"DEPENDENCY",33:"LINE",34:"DOTTED_LINE",37:"graphCodeTokens",39:"TAGSTART",40:"TAGEND",41:"==",42:"--",43:"PCT",44:"DEFAULT",45:"SPACE",46:"MINUS",47:"keywords",48:"UNICODE_TEXT",49:"NUM",50:"ALPHA"},productions_:[0,[3,1],[4,4],[7,1],[7,2],[7,3],[10,2],[10,1],[9,1],[9,2],[9,1],[9,1],[9,1],[14,2],[14,5],[16,4],[19,1],[19,2],[15,1],[15,2],[15,1],[15,1],[12,3],[12,4],[12,4],[12,5],[25,3],[25,2],[25,2],[25,1],[27,1],[27,1],[27,1],[27,1],[28,1],[28,1],[35,1],[35,1],[36,1],[36,1],[36,1],[36,1],[36,1],[36,1],[36,1],[38,1],[38,1],[38,1],[38,1],[11,1],[11,1],[11,1]],performAction:function(t,e,n,r,i,o,a){var s=o.length-1;switch(i){case 6:this.$=o[s-1]+o[s];break;case 7:this.$=o[s];break;case 8:r.addRelation(o[s]);break;case 9:o[s-1].title=r.cleanupLabel(o[s]),r.addRelation(o[s-1]);break;case 13:r.addClass(o[s]);break;case 14:r.addClass(o[s-3]),r.addMembers(o[s-3],o[s-1]);break;case 15:r.addAnnotation(o[s],o[s-2]);break;case 16:this.$=[o[s]];break;case 17:o[s].push(o[s-1]),this.$=o[s];break;case 18:break;case 19:r.addMember(o[s-1],r.cleanupLabel(o[s]));break;case 20:case 21:break;case 22:this.$={id1:o[s-2],id2:o[s],relation:o[s-1],relationTitle1:"none",relationTitle2:"none"};break;case 23:this.$={id1:o[s-3],id2:o[s],relation:o[s-1],relationTitle1:o[s-2],relationTitle2:"none"};break;case 24:this.$={id1:o[s-3],id2:o[s],relation:o[s-2],relationTitle1:"none",relationTitle2:o[s-1]};break;case 25:this.$={id1:o[s-4],id2:o[s],relation:o[s-2],relationTitle1:o[s-3],relationTitle2:o[s-1]};break;case 26:this.$={type1:o[s-2],type2:o[s],lineType:o[s-1]};break;case 27:this.$={type1:"none",type2:o[s],lineType:o[s-1]};break;case 28:this.$={type1:o[s-1],type2:"none",lineType:o[s]};break;case 29:this.$={type1:"none",type2:"none",lineType:o[s]};break;case 30:this.$=r.relationType.AGGREGATION;break;case 31:this.$=r.relationType.EXTENSION;break;case 32:this.$=r.relationType.COMPOSITION;break;case 33:this.$=r.relationType.DEPENDENCY;break;case 34:this.$=r.lineType.LINE;break;case 35:this.$=r.lineType.DOTTED_LINE}},table:[{3:1,4:2,5:[1,3]},{1:[3]},{1:[2,1]},{6:[1,4]},{7:5,9:6,10:11,11:16,12:7,14:8,15:9,16:10,17:e,21:n,23:r,24:i,48:o,49:a,50:s},{8:[1,20]},{6:[1,21],8:[2,3]},t(u,[2,8],{13:[1,22]}),t(u,[2,10]),t(u,[2,11]),t(u,[2,12]),t(u,[2,18],{25:23,27:26,28:27,13:[1,25],26:[1,24],29:c,30:f,31:l,32:h,33:d,34:p}),{10:34,11:16,48:o,49:a,50:s},t(u,[2,20]),t(u,[2,21]),{11:35,48:o,49:a,50:s},t(g,[2,7],{11:16,10:36,48:o,49:a,50:s}),t(y,[2,49]),t(y,[2,50]),t(y,[2,51]),{1:[2,2]},{7:37,8:[2,4],9:6,10:11,11:16,12:7,14:8,15:9,16:10,17:e,21:n,23:r,24:i,48:o,49:a,50:s},t(u,[2,9]),{10:38,11:16,26:[1,39],48:o,49:a,50:s},{25:40,27:26,28:27,29:c,30:f,31:l,32:h,33:d,34:p},t(u,[2,19]),{28:41,33:d,34:p},t(b,[2,29],{27:42,29:c,30:f,31:l,32:h}),t(m,[2,30]),t(m,[2,31]),t(m,[2,32]),t(m,[2,33]),t(v,[2,34]),t(v,[2,35]),t(u,[2,13],{18:[1,43]}),{22:[1,44]},t(g,[2,6]),{8:[2,5]},t(_,[2,22]),{10:45,11:16,48:o,49:a,50:s},{10:46,11:16,26:[1,47],48:o,49:a,50:s},t(b,[2,28],{27:48,29:c,30:f,31:l,32:h}),t(b,[2,27]),{19:49,23:w},{10:51,11:16,48:o,49:a,50:s},t(_,[2,24]),t(_,[2,23]),{10:52,11:16,48:o,49:a,50:s},t(b,[2,26]),{20:[1,53]},{19:54,20:[2,16],23:w},t(u,[2,15]),t(_,[2,25]),t(u,[2,14]),{20:[2,17]}],defaultActions:{2:[2,1],20:[2,2],37:[2,5],54:[2,17]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],o=[],a=this.table,s="",u=0,c=0,f=0,l=2,h=1,d=o.slice.call(arguments,1),p=Object.create(this.lexer),g={yy:{}};for(var y in this.yy)Object.prototype.hasOwnProperty.call(this.yy,y)&&(g.yy[y]=this.yy[y]);p.setInput(t,g.yy),g.yy.lexer=p,g.yy.parser=this,void 0===p.yylloc&&(p.yylloc={});var b=p.yylloc;o.push(b);var m=p.options&&p.options.ranges;function v(){var t;return"number"!=typeof(t=r.pop()||p.lex()||h)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof g.yy.parseError?this.parseError=g.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var _,w,x,k,E,A,S,T,M,D={};;){if(x=n[n.length-1],this.defaultActions[x]?k=this.defaultActions[x]:(null==_&&(_=v()),k=a[x]&&a[x][_]),void 0===k||!k.length||!k[0]){var C="";for(A in M=[],a[x])this.terminals_[A]&&A>l&&M.push("'"+this.terminals_[A]+"'");C=p.showPosition?"Parse error on line "+(u+1)+":\n"+p.showPosition()+"\nExpecting "+M.join(", ")+", got '"+(this.terminals_[_]||_)+"'":"Parse error on line "+(u+1)+": Unexpected "+(_==h?"end of input":"'"+(this.terminals_[_]||_)+"'"),this.parseError(C,{text:p.match,token:this.terminals_[_]||_,line:p.yylineno,loc:b,expected:M})}if(k[0]instanceof Array&&k.length>1)throw new Error("Parse Error: multiple actions possible at state: "+x+", token: "+_);switch(k[0]){case 1:n.push(_),i.push(p.yytext),o.push(p.yylloc),n.push(k[1]),_=null,w?(_=w,w=null):(c=p.yyleng,s=p.yytext,u=p.yylineno,b=p.yylloc,f>0&&f--);break;case 2:if(S=this.productions_[k[1]][1],D.$=i[i.length-S],D._$={first_line:o[o.length-(S||1)].first_line,last_line:o[o.length-1].last_line,first_column:o[o.length-(S||1)].first_column,last_column:o[o.length-1].last_column},m&&(D._$.range=[o[o.length-(S||1)].range[0],o[o.length-1].range[1]]),void 0!==(E=this.performAction.apply(D,[s,c,u,g.yy,k[1],i,o].concat(d))))return E;S&&(n=n.slice(0,-1*S*2),i=i.slice(0,-1*S),o=o.slice(0,-1*S)),n.push(this.productions_[k[1]][0]),i.push(D.$),o.push(D._$),T=a[n[n.length-2]][n[n.length-1]],n.push(T);break;case 3:return!0}}return!0}},k={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var o in i)this[o]=i[o];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),o=0;oe[0].length)){if(e=n,r=o,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[o])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var t=this.next();return t||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{},performAction:function(t,e,n,r){switch(n){case 0:break;case 1:return 6;case 2:break;case 3:return 5;case 4:return this.begin("struct"),18;case 5:return this.popState(),20;case 6:break;case 7:return"MEMBER";case 8:return 17;case 9:return 21;case 10:return 22;case 11:this.begin("string");break;case 12:this.popState();break;case 13:return"STR";case 14:case 15:return 30;case 16:case 17:return 32;case 18:return 31;case 19:return 29;case 20:return 33;case 21:return 34;case 22:return 13;case 23:return 46;case 24:return"DOT";case 25:return"PLUS";case 26:return 43;case 27:case 28:return"EQUALS";case 29:return 50;case 30:return"PUNCTUATION";case 31:return 49;case 32:return 48;case 33:return 45;case 34:return 8}},rules:[/^(?:%%[^\n]*)/,/^(?:\n+)/,/^(?:\s+)/,/^(?:classDiagram\b)/,/^(?:[\{])/,/^(?:\})/,/^(?:[\n])/,/^(?:[^\{\}\n]*)/,/^(?:class\b)/,/^(?:<<)/,/^(?:>>)/,/^(?:["])/,/^(?:["])/,/^(?:[^"]*)/,/^(?:\s*<\|)/,/^(?:\s*\|>)/,/^(?:\s*>)/,/^(?:\s*<)/,/^(?:\s*\*)/,/^(?:\s*o\b)/,/^(?:--)/,/^(?:\.\.)/,/^(?::[^\n;]+)/,/^(?:-)/,/^(?:\.)/,/^(?:\+)/,/^(?:%)/,/^(?:=)/,/^(?:=)/,/^(?:\w+)/,/^(?:[!"#$%&'*+,-.`?\\\/])/,/^(?:[0-9]+)/,/^(?:[\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6]|[\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377]|[\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5]|[\u03F7-\u0481\u048A-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA]|[\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE]|[\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA]|[\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0]|[\u08A2-\u08AC\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0977]|[\u0979-\u097F\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2]|[\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A]|[\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39]|[\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8]|[\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C]|[\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C]|[\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99]|[\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0]|[\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3D]|[\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3]|[\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10]|[\u0D12-\u0D3A\u0D3D\u0D4E\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1]|[\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81]|[\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3]|[\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6]|[\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A]|[\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081]|[\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D]|[\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0]|[\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310]|[\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C]|[\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u1700-\u170C\u170E-\u1711]|[\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7]|[\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191C]|[\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16]|[\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF]|[\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC]|[\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D]|[\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D]|[\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3]|[\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F]|[\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128]|[\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184]|[\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3]|[\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6]|[\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE]|[\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C]|[\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D]|[\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC]|[\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B]|[\uA640-\uA66E\uA67F-\uA697\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788]|[\uA78B-\uA78E\uA790-\uA793\uA7A0-\uA7AA\uA7F8-\uA801\uA803-\uA805]|[\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB]|[\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uAA00-\uAA28]|[\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA80-\uAAAF\uAAB1\uAAB5]|[\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4]|[\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E]|[\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D]|[\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36]|[\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D]|[\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC]|[\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF]|[\uFFD2-\uFFD7\uFFDA-\uFFDC])/,/^(?:\s)/,/^(?:$)/],conditions:{string:{rules:[12,13],inclusive:!1},struct:{rules:[5,6,7],inclusive:!1},INITIAL:{rules:[0,1,2,3,4,8,9,10,11,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34],inclusive:!0}}};function E(){this.yy={}}return x.lexer=k,E.prototype=x,x.Parser=E,new E}();e.parser=i,e.Parser=i.Parser,e.parse=function(){return i.parse.apply(i,arguments)},e.main=function(r){r[1]||(console.log("Usage: "+r[0]+" FILE"),t.exit(1));var i=n(28).readFileSync(n(29).normalize(r[1]),"utf8");return e.parser.parse(i)},n.c[n.s]===r&&e.main(t.argv.slice(1))}).call(this,n(7),n(9)(t))},function(t,e,n){(function(t,r){var i=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[1,2],n=[1,3],r=[1,4],i=[2,4],o=[1,9],a=[1,11],s=[1,13],u=[1,14],c=[1,15],f=[1,16],l=[1,21],h=[1,17],d=[1,18],p=[1,19],g=[1,20],y=[1,22],b=[1,4,5,13,14,16,18,19,21,22,23,24,25,28],m=[1,4,5,11,12,13,14,16,18,19,21,22,23,24,25,28],v=[4,5,13,14,16,18,19,21,22,23,24,25,28],_={trace:function(){},yy:{},symbols_:{error:2,start:3,SPACE:4,NL:5,SD:6,document:7,line:8,statement:9,idStatement:10,DESCR:11,"--\x3e":12,HIDE_EMPTY:13,scale:14,WIDTH:15,COMPOSIT_STATE:16,STRUCT_START:17,STRUCT_STOP:18,STATE_DESCR:19,AS:20,ID:21,FORK:22,JOIN:23,CONCURRENT:24,note:25,notePosition:26,NOTE_TEXT:27,EDGE_STATE:28,left_of:29,right_of:30,$accept:0,$end:1},terminals_:{2:"error",4:"SPACE",5:"NL",6:"SD",11:"DESCR",12:"--\x3e",13:"HIDE_EMPTY",14:"scale",15:"WIDTH",16:"COMPOSIT_STATE",17:"STRUCT_START",18:"STRUCT_STOP",19:"STATE_DESCR",20:"AS",21:"ID",22:"FORK",23:"JOIN",24:"CONCURRENT",25:"note",27:"NOTE_TEXT",28:"EDGE_STATE",29:"left_of",30:"right_of"},productions_:[0,[3,2],[3,2],[3,2],[7,0],[7,2],[8,2],[8,1],[8,1],[9,1],[9,2],[9,3],[9,4],[9,1],[9,2],[9,1],[9,4],[9,3],[9,6],[9,1],[9,1],[9,1],[9,4],[9,4],[10,1],[10,1],[26,1],[26,1]],performAction:function(t,e,n,r,i,o,a){var s=o.length-1;switch(i){case 3:return r.setRootDoc(o[s]),o[s];case 4:this.$=[];break;case 5:"nl"!=o[s]&&(o[s-1].push(o[s]),this.$=o[s-1]);break;case 6:case 7:this.$=o[s];break;case 8:this.$="nl";break;case 9:this.$={stmt:"state",id:o[s],type:"default",description:""};break;case 10:this.$={stmt:"state",id:o[s-1],type:"default",description:o[s].trim()};break;case 11:this.$={stmt:"relation",state1:{stmt:"state",id:o[s-2],type:"default",description:""},state2:{stmt:"state",id:o[s],type:"default",description:""}};break;case 12:this.$={stmt:"relation",state1:{stmt:"state",id:o[s-3],type:"default",description:""},state2:{stmt:"state",id:o[s-1],type:"default",description:""},description:o[s].substr(1).trim()};break;case 16:this.$={stmt:"state",id:o[s-3],type:"default",description:"",doc:o[s-1]};break;case 17:var u=o[s],c=o[s-2].trim();if(o[s].match(":")){var f=o[s].split(":");u=f[0],c=[c,f[1]]}this.$={stmt:"state",id:u,type:"default",description:c};break;case 18:this.$={stmt:"state",id:o[s-3],type:"default",description:o[s-5],doc:o[s-1]};break;case 19:this.$={stmt:"state",id:o[s],type:"fork"};break;case 20:this.$={stmt:"state",id:o[s],type:"join"};break;case 21:this.$={stmt:"state",id:r.getDividerId(),type:"divider"};break;case 22:this.$={stmt:"state",id:o[s-1].trim(),note:{position:o[s-2].trim(),text:o[s].trim()}};break;case 24:case 25:this.$=o[s]}},table:[{3:1,4:e,5:n,6:r},{1:[3]},{3:5,4:e,5:n,6:r},{3:6,4:e,5:n,6:r},t([1,4,5,13,14,16,19,21,22,23,24,25,28],i,{7:7}),{1:[2,1]},{1:[2,2]},{1:[2,3],4:o,5:a,8:8,9:10,10:12,13:s,14:u,16:c,19:f,21:l,22:h,23:d,24:p,25:g,28:y},t(b,[2,5]),{9:23,10:12,13:s,14:u,16:c,19:f,21:l,22:h,23:d,24:p,25:g,28:y},t(b,[2,7]),t(b,[2,8]),t(b,[2,9],{11:[1,24],12:[1,25]}),t(b,[2,13]),{15:[1,26]},t(b,[2,15],{17:[1,27]}),{20:[1,28]},t(b,[2,19]),t(b,[2,20]),t(b,[2,21]),{26:29,27:[1,30],29:[1,31],30:[1,32]},t(m,[2,24]),t(m,[2,25]),t(b,[2,6]),t(b,[2,10]),{10:33,21:l,28:y},t(b,[2,14]),t(v,i,{7:34}),{21:[1,35]},{21:[1,36]},{20:[1,37]},{21:[2,26]},{21:[2,27]},t(b,[2,11],{11:[1,38]}),{4:o,5:a,8:8,9:10,10:12,13:s,14:u,16:c,18:[1,39],19:f,21:l,22:h,23:d,24:p,25:g,28:y},t(b,[2,17],{17:[1,40]}),{27:[1,41]},{21:[1,42]},t(b,[2,12]),t(b,[2,16]),t(v,i,{7:43}),t(b,[2,22]),t(b,[2,23]),{4:o,5:a,8:8,9:10,10:12,13:s,14:u,16:c,18:[1,44],19:f,21:l,22:h,23:d,24:p,25:g,28:y},t(b,[2,18])],defaultActions:{5:[2,1],6:[2,2],31:[2,26],32:[2,27]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],o=[],a=this.table,s="",u=0,c=0,f=0,l=2,h=1,d=o.slice.call(arguments,1),p=Object.create(this.lexer),g={yy:{}};for(var y in this.yy)Object.prototype.hasOwnProperty.call(this.yy,y)&&(g.yy[y]=this.yy[y]);p.setInput(t,g.yy),g.yy.lexer=p,g.yy.parser=this,void 0===p.yylloc&&(p.yylloc={});var b=p.yylloc;o.push(b);var m=p.options&&p.options.ranges;function v(){var t;return"number"!=typeof(t=r.pop()||p.lex()||h)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof g.yy.parseError?this.parseError=g.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var _,w,x,k,E,A,S,T,M,D={};;){if(x=n[n.length-1],this.defaultActions[x]?k=this.defaultActions[x]:(null==_&&(_=v()),k=a[x]&&a[x][_]),void 0===k||!k.length||!k[0]){var C="";for(A in M=[],a[x])this.terminals_[A]&&A>l&&M.push("'"+this.terminals_[A]+"'");C=p.showPosition?"Parse error on line "+(u+1)+":\n"+p.showPosition()+"\nExpecting "+M.join(", ")+", got '"+(this.terminals_[_]||_)+"'":"Parse error on line "+(u+1)+": Unexpected "+(_==h?"end of input":"'"+(this.terminals_[_]||_)+"'"),this.parseError(C,{text:p.match,token:this.terminals_[_]||_,line:p.yylineno,loc:b,expected:M})}if(k[0]instanceof Array&&k.length>1)throw new Error("Parse Error: multiple actions possible at state: "+x+", token: "+_);switch(k[0]){case 1:n.push(_),i.push(p.yytext),o.push(p.yylloc),n.push(k[1]),_=null,w?(_=w,w=null):(c=p.yyleng,s=p.yytext,u=p.yylineno,b=p.yylloc,f>0&&f--);break;case 2:if(S=this.productions_[k[1]][1],D.$=i[i.length-S],D._$={first_line:o[o.length-(S||1)].first_line,last_line:o[o.length-1].last_line,first_column:o[o.length-(S||1)].first_column,last_column:o[o.length-1].last_column},m&&(D._$.range=[o[o.length-(S||1)].range[0],o[o.length-1].range[1]]),void 0!==(E=this.performAction.apply(D,[s,c,u,g.yy,k[1],i,o].concat(d))))return E;S&&(n=n.slice(0,-1*S*2),i=i.slice(0,-1*S),o=o.slice(0,-1*S)),n.push(this.productions_[k[1]][0]),i.push(D.$),o.push(D._$),T=a[n[n.length-2]][n[n.length-1]],n.push(T);break;case 3:return!0}}return!0}},w={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var o in i)this[o]=i[o];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),o=0;oe[0].length)){if(e=n,r=o,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[o])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var t=this.next();return t||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{"case-insensitive":!0},performAction:function(t,e,n,r){switch(n){case 0:return 5;case 1:case 2:case 3:case 4:break;case 5:return this.pushState("SCALE"),14;case 6:return 15;case 7:this.popState();break;case 8:this.pushState("STATE");break;case 9:return this.popState(),e.yytext=e.yytext.slice(0,-8).trim(),22;case 10:return this.popState(),e.yytext=e.yytext.slice(0,-8).trim(),23;case 11:return this.popState(),e.yytext=e.yytext.slice(0,-8).trim(),22;case 12:return this.popState(),e.yytext=e.yytext.slice(0,-8).trim(),23;case 13:this.begin("STATE_STRING");break;case 14:return this.popState(),this.pushState("STATE_ID"),"AS";case 15:return this.popState(),"ID";case 16:this.popState();break;case 17:return"STATE_DESCR";case 18:return 16;case 19:this.popState();break;case 20:return this.popState(),this.pushState("struct"),17;case 21:return this.popState(),18;case 22:break;case 23:return this.begin("NOTE"),25;case 24:return this.popState(),this.pushState("NOTE_ID"),29;case 25:return this.popState(),this.pushState("NOTE_ID"),30;case 26:this.popState(),this.pushState("FLOATING_NOTE");break;case 27:return this.popState(),this.pushState("FLOATING_NOTE_ID"),"AS";case 28:break;case 29:return"NOTE_TEXT";case 30:return this.popState(),"ID";case 31:return this.popState(),this.pushState("NOTE_TEXT"),21;case 32:return this.popState(),e.yytext=e.yytext.substr(2).trim(),27;case 33:return this.popState(),e.yytext=e.yytext.slice(0,-8).trim(),27;case 34:return 6;case 35:return 13;case 36:return 28;case 37:return 21;case 38:return e.yytext=e.yytext.trim(),11;case 39:return 12;case 40:return 24;case 41:return 5;case 42:return"INVALID"}},rules:[/^(?:[\n]+)/i,/^(?:\s+)/i,/^(?:((?!\n)\s)+)/i,/^(?:#[^\n]*)/i,/^(?:%[^\n]*)/i,/^(?:scale\s+)/i,/^(?:\d+)/i,/^(?:\s+width\b)/i,/^(?:state\s+)/i,/^(?:.*<>)/i,/^(?:.*<>)/i,/^(?:.*\[\[fork\]\])/i,/^(?:.*\[\[join\]\])/i,/^(?:["])/i,/^(?:as\s*)/i,/^(?:[^\n\{]*)/i,/^(?:["])/i,/^(?:[^"]*)/i,/^(?:[^\n\s\{]+)/i,/^(?:\n)/i,/^(?:\{)/i,/^(?:\})/i,/^(?:[\n])/i,/^(?:note\s+)/i,/^(?:left of\b)/i,/^(?:right of\b)/i,/^(?:")/i,/^(?:\s*as\s*)/i,/^(?:["])/i,/^(?:[^"]*)/i,/^(?:[^\n]*)/i,/^(?:\s*[^:\n\s\-]+)/i,/^(?:\s*:[^:\n;]+)/i,/^(?:\s*[^:;]+end note\b)/i,/^(?:stateDiagram\s+)/i,/^(?:hide empty description\b)/i,/^(?:\[\*\])/i,/^(?:[^:\n\s\-\{]+)/i,/^(?:\s*:[^:\n;]+)/i,/^(?:-->)/i,/^(?:--)/i,/^(?:$)/i,/^(?:.)/i],conditions:{LINE:{rules:[2,3],inclusive:!1},struct:{rules:[2,3,8,21,22,23,36,37,38,39,40],inclusive:!1},FLOATING_NOTE_ID:{rules:[30],inclusive:!1},FLOATING_NOTE:{rules:[27,28,29],inclusive:!1},NOTE_TEXT:{rules:[32,33],inclusive:!1},NOTE_ID:{rules:[31],inclusive:!1},NOTE:{rules:[24,25,26],inclusive:!1},SCALE:{rules:[6,7],inclusive:!1},ALIAS:{rules:[],inclusive:!1},STATE_ID:{rules:[15],inclusive:!1},STATE_STRING:{rules:[16,17],inclusive:!1},FORK_STATE:{rules:[],inclusive:!1},STATE:{rules:[2,3,9,10,11,12,13,14,18,19,20],inclusive:!1},ID:{rules:[2,3],inclusive:!1},INITIAL:{rules:[0,1,3,4,5,8,20,23,34,35,36,37,38,39,41,42],inclusive:!0}}};function x(){this.yy={}}return _.lexer=w,x.prototype=_,_.Parser=x,new x}();e.parser=i,e.Parser=i.Parser,e.parse=function(){return i.parse.apply(i,arguments)},e.main=function(r){r[1]||(console.log("Usage: "+r[0]+" FILE"),t.exit(1));var i=n(28).readFileSync(n(29).normalize(r[1]),"utf8");return e.parser.parse(i)},n.c[n.s]===r&&e.main(t.argv.slice(1))}).call(this,n(7),n(9)(t))},function(t,e,n){(function(t,n){(function(){var r,i=200,o="Unsupported core-js use. Try https://npms.io/search?q=ponyfill.",a="Expected a function",s="__lodash_hash_undefined__",u=500,c="__lodash_placeholder__",f=1,l=2,h=4,d=1,p=2,g=1,y=2,b=4,m=8,v=16,_=32,w=64,x=128,k=256,E=512,A=30,S="...",T=800,M=16,D=1,C=2,O=1/0,R=9007199254740991,I=17976931348623157e292,N=NaN,B=4294967295,L=B-1,P=B>>>1,F=[["ary",x],["bind",g],["bindKey",y],["curry",m],["curryRight",v],["flip",E],["partial",_],["partialRight",w],["rearg",k]],q="[object Arguments]",j="[object Array]",U="[object AsyncFunction]",z="[object Boolean]",Y="[object Date]",V="[object DOMException]",H="[object Error]",$="[object Function]",G="[object GeneratorFunction]",W="[object Map]",K="[object Number]",X="[object Null]",Z="[object Object]",J="[object Proxy]",Q="[object RegExp]",tt="[object Set]",et="[object String]",nt="[object Symbol]",rt="[object Undefined]",it="[object WeakMap]",ot="[object WeakSet]",at="[object ArrayBuffer]",st="[object DataView]",ut="[object Float32Array]",ct="[object Float64Array]",ft="[object Int8Array]",lt="[object Int16Array]",ht="[object Int32Array]",dt="[object Uint8Array]",pt="[object Uint8ClampedArray]",gt="[object Uint16Array]",yt="[object Uint32Array]",bt=/\b__p \+= '';/g,mt=/\b(__p \+=) '' \+/g,vt=/(__e\(.*?\)|\b__t\)) \+\n'';/g,_t=/&(?:amp|lt|gt|quot|#39);/g,wt=/[&<>"']/g,xt=RegExp(_t.source),kt=RegExp(wt.source),Et=/<%-([\s\S]+?)%>/g,At=/<%([\s\S]+?)%>/g,St=/<%=([\s\S]+?)%>/g,Tt=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,Mt=/^\w*$/,Dt=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g,Ct=/[\\^$.*+?()[\]{}|]/g,Ot=RegExp(Ct.source),Rt=/^\s+|\s+$/g,It=/^\s+/,Nt=/\s+$/,Bt=/\{(?:\n\/\* \[wrapped with .+\] \*\/)?\n?/,Lt=/\{\n\/\* \[wrapped with (.+)\] \*/,Pt=/,? & /,Ft=/[^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/g,qt=/\\(\\)?/g,jt=/\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g,Ut=/\w*$/,zt=/^[-+]0x[0-9a-f]+$/i,Yt=/^0b[01]+$/i,Vt=/^\[object .+?Constructor\]$/,Ht=/^0o[0-7]+$/i,$t=/^(?:0|[1-9]\d*)$/,Gt=/[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g,Wt=/($^)/,Kt=/['\n\r\u2028\u2029\\]/g,Xt="\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff",Zt="\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000",Jt="[\\ud800-\\udfff]",Qt="["+Zt+"]",te="["+Xt+"]",ee="\\d+",ne="[\\u2700-\\u27bf]",re="[a-z\\xdf-\\xf6\\xf8-\\xff]",ie="[^\\ud800-\\udfff"+Zt+ee+"\\u2700-\\u27bfa-z\\xdf-\\xf6\\xf8-\\xffA-Z\\xc0-\\xd6\\xd8-\\xde]",oe="\\ud83c[\\udffb-\\udfff]",ae="[^\\ud800-\\udfff]",se="(?:\\ud83c[\\udde6-\\uddff]){2}",ue="[\\ud800-\\udbff][\\udc00-\\udfff]",ce="[A-Z\\xc0-\\xd6\\xd8-\\xde]",fe="(?:"+re+"|"+ie+")",le="(?:"+ce+"|"+ie+")",he="(?:"+te+"|"+oe+")"+"?",de="[\\ufe0e\\ufe0f]?"+he+("(?:\\u200d(?:"+[ae,se,ue].join("|")+")[\\ufe0e\\ufe0f]?"+he+")*"),pe="(?:"+[ne,se,ue].join("|")+")"+de,ge="(?:"+[ae+te+"?",te,se,ue,Jt].join("|")+")",ye=RegExp("['’]","g"),be=RegExp(te,"g"),me=RegExp(oe+"(?="+oe+")|"+ge+de,"g"),ve=RegExp([ce+"?"+re+"+(?:['’](?:d|ll|m|re|s|t|ve))?(?="+[Qt,ce,"$"].join("|")+")",le+"+(?:['’](?:D|LL|M|RE|S|T|VE))?(?="+[Qt,ce+fe,"$"].join("|")+")",ce+"?"+fe+"+(?:['’](?:d|ll|m|re|s|t|ve))?",ce+"+(?:['’](?:D|LL|M|RE|S|T|VE))?","\\d*(?:1ST|2ND|3RD|(?![123])\\dTH)(?=\\b|[a-z_])","\\d*(?:1st|2nd|3rd|(?![123])\\dth)(?=\\b|[A-Z_])",ee,pe].join("|"),"g"),_e=RegExp("[\\u200d\\ud800-\\udfff"+Xt+"\\ufe0e\\ufe0f]"),we=/[a-z][A-Z]|[A-Z]{2}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/,xe=["Array","Buffer","DataView","Date","Error","Float32Array","Float64Array","Function","Int8Array","Int16Array","Int32Array","Map","Math","Object","Promise","RegExp","Set","String","Symbol","TypeError","Uint8Array","Uint8ClampedArray","Uint16Array","Uint32Array","WeakMap","_","clearTimeout","isFinite","parseInt","setTimeout"],ke=-1,Ee={};Ee[ut]=Ee[ct]=Ee[ft]=Ee[lt]=Ee[ht]=Ee[dt]=Ee[pt]=Ee[gt]=Ee[yt]=!0,Ee[q]=Ee[j]=Ee[at]=Ee[z]=Ee[st]=Ee[Y]=Ee[H]=Ee[$]=Ee[W]=Ee[K]=Ee[Z]=Ee[Q]=Ee[tt]=Ee[et]=Ee[it]=!1;var Ae={};Ae[q]=Ae[j]=Ae[at]=Ae[st]=Ae[z]=Ae[Y]=Ae[ut]=Ae[ct]=Ae[ft]=Ae[lt]=Ae[ht]=Ae[W]=Ae[K]=Ae[Z]=Ae[Q]=Ae[tt]=Ae[et]=Ae[nt]=Ae[dt]=Ae[pt]=Ae[gt]=Ae[yt]=!0,Ae[H]=Ae[$]=Ae[it]=!1;var Se={"\\":"\\","'":"'","\n":"n","\r":"r","\u2028":"u2028","\u2029":"u2029"},Te=parseFloat,Me=parseInt,De="object"==typeof t&&t&&t.Object===Object&&t,Ce="object"==typeof self&&self&&self.Object===Object&&self,Oe=De||Ce||Function("return this")(),Re=e&&!e.nodeType&&e,Ie=Re&&"object"==typeof n&&n&&!n.nodeType&&n,Ne=Ie&&Ie.exports===Re,Be=Ne&&De.process,Le=function(){try{var t=Ie&&Ie.require&&Ie.require("util").types;return t||Be&&Be.binding&&Be.binding("util")}catch(t){}}(),Pe=Le&&Le.isArrayBuffer,Fe=Le&&Le.isDate,qe=Le&&Le.isMap,je=Le&&Le.isRegExp,Ue=Le&&Le.isSet,ze=Le&&Le.isTypedArray;function Ye(t,e,n){switch(n.length){case 0:return t.call(e);case 1:return t.call(e,n[0]);case 2:return t.call(e,n[0],n[1]);case 3:return t.call(e,n[0],n[1],n[2])}return t.apply(e,n)}function Ve(t,e,n,r){for(var i=-1,o=null==t?0:t.length;++i-1}function Xe(t,e,n){for(var r=-1,i=null==t?0:t.length;++r-1;);return n}function vn(t,e){for(var n=t.length;n--&&an(e,t[n],0)>-1;);return n}var _n=ln({"À":"A","Á":"A","Â":"A","Ã":"A","Ä":"A","Å":"A","à":"a","á":"a","â":"a","ã":"a","ä":"a","å":"a","Ç":"C","ç":"c","Ð":"D","ð":"d","È":"E","É":"E","Ê":"E","Ë":"E","è":"e","é":"e","ê":"e","ë":"e","Ì":"I","Í":"I","Î":"I","Ï":"I","ì":"i","í":"i","î":"i","ï":"i","Ñ":"N","ñ":"n","Ò":"O","Ó":"O","Ô":"O","Õ":"O","Ö":"O","Ø":"O","ò":"o","ó":"o","ô":"o","õ":"o","ö":"o","ø":"o","Ù":"U","Ú":"U","Û":"U","Ü":"U","ù":"u","ú":"u","û":"u","ü":"u","Ý":"Y","ý":"y","ÿ":"y","Æ":"Ae","æ":"ae","Þ":"Th","þ":"th","ß":"ss","Ā":"A","Ă":"A","Ą":"A","ā":"a","ă":"a","ą":"a","Ć":"C","Ĉ":"C","Ċ":"C","Č":"C","ć":"c","ĉ":"c","ċ":"c","č":"c","Ď":"D","Đ":"D","ď":"d","đ":"d","Ē":"E","Ĕ":"E","Ė":"E","Ę":"E","Ě":"E","ē":"e","ĕ":"e","ė":"e","ę":"e","ě":"e","Ĝ":"G","Ğ":"G","Ġ":"G","Ģ":"G","ĝ":"g","ğ":"g","ġ":"g","ģ":"g","Ĥ":"H","Ħ":"H","ĥ":"h","ħ":"h","Ĩ":"I","Ī":"I","Ĭ":"I","Į":"I","İ":"I","ĩ":"i","ī":"i","ĭ":"i","į":"i","ı":"i","Ĵ":"J","ĵ":"j","Ķ":"K","ķ":"k","ĸ":"k","Ĺ":"L","Ļ":"L","Ľ":"L","Ŀ":"L","Ł":"L","ĺ":"l","ļ":"l","ľ":"l","ŀ":"l","ł":"l","Ń":"N","Ņ":"N","Ň":"N","Ŋ":"N","ń":"n","ņ":"n","ň":"n","ŋ":"n","Ō":"O","Ŏ":"O","Ő":"O","ō":"o","ŏ":"o","ő":"o","Ŕ":"R","Ŗ":"R","Ř":"R","ŕ":"r","ŗ":"r","ř":"r","Ś":"S","Ŝ":"S","Ş":"S","Š":"S","ś":"s","ŝ":"s","ş":"s","š":"s","Ţ":"T","Ť":"T","Ŧ":"T","ţ":"t","ť":"t","ŧ":"t","Ũ":"U","Ū":"U","Ŭ":"U","Ů":"U","Ű":"U","Ų":"U","ũ":"u","ū":"u","ŭ":"u","ů":"u","ű":"u","ų":"u","Ŵ":"W","ŵ":"w","Ŷ":"Y","ŷ":"y","Ÿ":"Y","Ź":"Z","Ż":"Z","Ž":"Z","ź":"z","ż":"z","ž":"z","IJ":"IJ","ij":"ij","Œ":"Oe","œ":"oe","ʼn":"'n","ſ":"s"}),wn=ln({"&":"&","<":"<",">":">",'"':""","'":"'"});function xn(t){return"\\"+Se[t]}function kn(t){return _e.test(t)}function En(t){var e=-1,n=Array(t.size);return t.forEach((function(t,r){n[++e]=[r,t]})),n}function An(t,e){return function(n){return t(e(n))}}function Sn(t,e){for(var n=-1,r=t.length,i=0,o=[];++n",""":'"',"'":"'"});var Rn=function t(e){var n,Xt=(e=null==e?Oe:Rn.defaults(Oe.Object(),e,Rn.pick(Oe,xe))).Array,Zt=e.Date,Jt=e.Error,Qt=e.Function,te=e.Math,ee=e.Object,ne=e.RegExp,re=e.String,ie=e.TypeError,oe=Xt.prototype,ae=Qt.prototype,se=ee.prototype,ue=e["__core-js_shared__"],ce=ae.toString,fe=se.hasOwnProperty,le=0,he=(n=/[^.]+$/.exec(ue&&ue.keys&&ue.keys.IE_PROTO||""))?"Symbol(src)_1."+n:"",de=se.toString,pe=ce.call(ee),ge=Oe._,me=ne("^"+ce.call(fe).replace(Ct,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$"),_e=Ne?e.Buffer:r,Se=e.Symbol,De=e.Uint8Array,Ce=_e?_e.allocUnsafe:r,Re=An(ee.getPrototypeOf,ee),Ie=ee.create,Be=se.propertyIsEnumerable,Le=oe.splice,nn=Se?Se.isConcatSpreadable:r,ln=Se?Se.iterator:r,In=Se?Se.toStringTag:r,Nn=function(){try{var t=qo(ee,"defineProperty");return t({},"",{}),t}catch(t){}}(),Bn=e.clearTimeout!==Oe.clearTimeout&&e.clearTimeout,Ln=Zt&&Zt.now!==Oe.Date.now&&Zt.now,Pn=e.setTimeout!==Oe.setTimeout&&e.setTimeout,Fn=te.ceil,qn=te.floor,jn=ee.getOwnPropertySymbols,Un=_e?_e.isBuffer:r,zn=e.isFinite,Yn=oe.join,Vn=An(ee.keys,ee),Hn=te.max,$n=te.min,Gn=Zt.now,Wn=e.parseInt,Kn=te.random,Xn=oe.reverse,Zn=qo(e,"DataView"),Jn=qo(e,"Map"),Qn=qo(e,"Promise"),tr=qo(e,"Set"),er=qo(e,"WeakMap"),nr=qo(ee,"create"),rr=er&&new er,ir={},or=la(Zn),ar=la(Jn),sr=la(Qn),ur=la(tr),cr=la(er),fr=Se?Se.prototype:r,lr=fr?fr.valueOf:r,hr=fr?fr.toString:r;function dr(t){if(Ms(t)&&!bs(t)&&!(t instanceof br)){if(t instanceof yr)return t;if(fe.call(t,"__wrapped__"))return ha(t)}return new yr(t)}var pr=function(){function t(){}return function(e){if(!Ts(e))return{};if(Ie)return Ie(e);t.prototype=e;var n=new t;return t.prototype=r,n}}();function gr(){}function yr(t,e){this.__wrapped__=t,this.__actions__=[],this.__chain__=!!e,this.__index__=0,this.__values__=r}function br(t){this.__wrapped__=t,this.__actions__=[],this.__dir__=1,this.__filtered__=!1,this.__iteratees__=[],this.__takeCount__=B,this.__views__=[]}function mr(t){var e=-1,n=null==t?0:t.length;for(this.clear();++e=e?t:e)),t}function Br(t,e,n,i,o,a){var s,u=e&f,c=e&l,d=e&h;if(n&&(s=o?n(t,i,o,a):n(t)),s!==r)return s;if(!Ts(t))return t;var p=bs(t);if(p){if(s=function(t){var e=t.length,n=new t.constructor(e);e&&"string"==typeof t[0]&&fe.call(t,"index")&&(n.index=t.index,n.input=t.input);return n}(t),!u)return no(t,s)}else{var g=zo(t),y=g==$||g==G;if(ws(t))return Xi(t,u);if(g==Z||g==q||y&&!o){if(s=c||y?{}:Vo(t),!u)return c?function(t,e){return ro(t,Uo(t),e)}(t,function(t,e){return t&&ro(e,ou(e),t)}(s,t)):function(t,e){return ro(t,jo(t),e)}(t,Or(s,t))}else{if(!Ae[g])return o?t:{};s=function(t,e,n){var r=t.constructor;switch(e){case at:return Zi(t);case z:case Y:return new r(+t);case st:return function(t,e){var n=e?Zi(t.buffer):t.buffer;return new t.constructor(n,t.byteOffset,t.byteLength)}(t,n);case ut:case ct:case ft:case lt:case ht:case dt:case pt:case gt:case yt:return Ji(t,n);case W:return new r;case K:case et:return new r(t);case Q:return function(t){var e=new t.constructor(t.source,Ut.exec(t));return e.lastIndex=t.lastIndex,e}(t);case tt:return new r;case nt:return i=t,lr?ee(lr.call(i)):{}}var i}(t,g,u)}}a||(a=new xr);var b=a.get(t);if(b)return b;a.set(t,s),Is(t)?t.forEach((function(r){s.add(Br(r,e,n,r,t,a))})):Ds(t)&&t.forEach((function(r,i){s.set(i,Br(r,e,n,i,t,a))}));var m=p?r:(d?c?Ro:Oo:c?ou:iu)(t);return He(m||t,(function(r,i){m&&(r=t[i=r]),Mr(s,i,Br(r,e,n,i,t,a))})),s}function Lr(t,e,n){var i=n.length;if(null==t)return!i;for(t=ee(t);i--;){var o=n[i],a=e[o],s=t[o];if(s===r&&!(o in t)||!a(s))return!1}return!0}function Pr(t,e,n){if("function"!=typeof t)throw new ie(a);return ia((function(){t.apply(r,n)}),e)}function Fr(t,e,n,r){var o=-1,a=Ke,s=!0,u=t.length,c=[],f=e.length;if(!u)return c;n&&(e=Ze(e,gn(n))),r?(a=Xe,s=!1):e.length>=i&&(a=bn,s=!1,e=new wr(e));t:for(;++o-1},vr.prototype.set=function(t,e){var n=this.__data__,r=Dr(n,t);return r<0?(++this.size,n.push([t,e])):n[r][1]=e,this},_r.prototype.clear=function(){this.size=0,this.__data__={hash:new mr,map:new(Jn||vr),string:new mr}},_r.prototype.delete=function(t){var e=Po(this,t).delete(t);return this.size-=e?1:0,e},_r.prototype.get=function(t){return Po(this,t).get(t)},_r.prototype.has=function(t){return Po(this,t).has(t)},_r.prototype.set=function(t,e){var n=Po(this,t),r=n.size;return n.set(t,e),this.size+=n.size==r?0:1,this},wr.prototype.add=wr.prototype.push=function(t){return this.__data__.set(t,s),this},wr.prototype.has=function(t){return this.__data__.has(t)},xr.prototype.clear=function(){this.__data__=new vr,this.size=0},xr.prototype.delete=function(t){var e=this.__data__,n=e.delete(t);return this.size=e.size,n},xr.prototype.get=function(t){return this.__data__.get(t)},xr.prototype.has=function(t){return this.__data__.has(t)},xr.prototype.set=function(t,e){var n=this.__data__;if(n instanceof vr){var r=n.__data__;if(!Jn||r.length0&&n(s)?e>1?Vr(s,e-1,n,r,i):Je(i,s):r||(i[i.length]=s)}return i}var Hr=so(),$r=so(!0);function Gr(t,e){return t&&Hr(t,e,iu)}function Wr(t,e){return t&&$r(t,e,iu)}function Kr(t,e){return We(e,(function(e){return Es(t[e])}))}function Xr(t,e){for(var n=0,i=(e=$i(e,t)).length;null!=t&&ne}function ti(t,e){return null!=t&&fe.call(t,e)}function ei(t,e){return null!=t&&e in ee(t)}function ni(t,e,n){for(var i=n?Xe:Ke,o=t[0].length,a=t.length,s=a,u=Xt(a),c=1/0,f=[];s--;){var l=t[s];s&&e&&(l=Ze(l,gn(e))),c=$n(l.length,c),u[s]=!n&&(e||o>=120&&l.length>=120)?new wr(s&&l):r}l=t[0];var h=-1,d=u[0];t:for(;++h=s)return u;var c=n[r];return u*("desc"==c?-1:1)}}return t.index-e.index}(t,e,n)}))}function mi(t,e,n){for(var r=-1,i=e.length,o={};++r-1;)s!==t&&Le.call(s,u,1),Le.call(t,u,1);return t}function _i(t,e){for(var n=t?e.length:0,r=n-1;n--;){var i=e[n];if(n==r||i!==o){var o=i;$o(i)?Le.call(t,i,1):Fi(t,i)}}return t}function wi(t,e){return t+qn(Kn()*(e-t+1))}function xi(t,e){var n="";if(!t||e<1||e>R)return n;do{e%2&&(n+=t),(e=qn(e/2))&&(t+=t)}while(e);return n}function ki(t,e){return oa(ta(t,e,Cu),t+"")}function Ei(t){return Er(du(t))}function Ai(t,e){var n=du(t);return ua(n,Nr(e,0,n.length))}function Si(t,e,n,i){if(!Ts(t))return t;for(var o=-1,a=(e=$i(e,t)).length,s=a-1,u=t;null!=u&&++oi?0:i+e),(n=n>i?i:n)<0&&(n+=i),i=e>n?0:n-e>>>0,e>>>=0;for(var o=Xt(i);++r>>1,a=t[o];null!==a&&!Bs(a)&&(n?a<=e:a=i){var f=e?null:ko(t);if(f)return Tn(f);s=!1,o=bn,c=new wr}else c=e?[]:u;t:for(;++r=i?t:Ci(t,e,n)}var Ki=Bn||function(t){return Oe.clearTimeout(t)};function Xi(t,e){if(e)return t.slice();var n=t.length,r=Ce?Ce(n):new t.constructor(n);return t.copy(r),r}function Zi(t){var e=new t.constructor(t.byteLength);return new De(e).set(new De(t)),e}function Ji(t,e){var n=e?Zi(t.buffer):t.buffer;return new t.constructor(n,t.byteOffset,t.length)}function Qi(t,e){if(t!==e){var n=t!==r,i=null===t,o=t==t,a=Bs(t),s=e!==r,u=null===e,c=e==e,f=Bs(e);if(!u&&!f&&!a&&t>e||a&&s&&c&&!u&&!f||i&&s&&c||!n&&c||!o)return 1;if(!i&&!a&&!f&&t1?n[o-1]:r,s=o>2?n[2]:r;for(a=t.length>3&&"function"==typeof a?(o--,a):r,s&&Go(n[0],n[1],s)&&(a=o<3?r:a,o=1),e=ee(e);++i-1?o[a?e[s]:s]:r}}function ho(t){return Co((function(e){var n=e.length,i=n,o=yr.prototype.thru;for(t&&e.reverse();i--;){var s=e[i];if("function"!=typeof s)throw new ie(a);if(o&&!u&&"wrapper"==No(s))var u=new yr([],!0)}for(i=u?i:n;++i1&&m.reverse(),l&&cu))return!1;var f=a.get(t);if(f&&a.get(e))return f==e;var l=-1,h=!0,g=n&p?new wr:r;for(a.set(t,e),a.set(e,t);++l-1&&t%1==0&&t1?"& ":"")+e[r],e=e.join(n>2?", ":" "),t.replace(Bt,"{\n/* [wrapped with "+e+"] */\n")}(r,function(t,e){return He(F,(function(n){var r="_."+n[0];e&n[1]&&!Ke(t,r)&&t.push(r)})),t.sort()}(function(t){var e=t.match(Lt);return e?e[1].split(Pt):[]}(r),n)))}function sa(t){var e=0,n=0;return function(){var i=Gn(),o=M-(i-n);if(n=i,o>0){if(++e>=T)return arguments[0]}else e=0;return t.apply(r,arguments)}}function ua(t,e){var n=-1,i=t.length,o=i-1;for(e=e===r?i:e;++n1?t[e-1]:r;return n="function"==typeof n?(t.pop(),n):r,Ra(t,n)}));function qa(t){var e=dr(t);return e.__chain__=!0,e}function ja(t,e){return e(t)}var Ua=Co((function(t){var e=t.length,n=e?t[0]:0,i=this.__wrapped__,o=function(e){return Ir(e,t)};return!(e>1||this.__actions__.length)&&i instanceof br&&$o(n)?((i=i.slice(n,+n+(e?1:0))).__actions__.push({func:ja,args:[o],thisArg:r}),new yr(i,this.__chain__).thru((function(t){return e&&!t.length&&t.push(r),t}))):this.thru(o)}));var za=io((function(t,e,n){fe.call(t,n)?++t[n]:Rr(t,n,1)}));var Ya=lo(ya),Va=lo(ba);function Ha(t,e){return(bs(t)?He:qr)(t,Lo(e,3))}function $a(t,e){return(bs(t)?$e:jr)(t,Lo(e,3))}var Ga=io((function(t,e,n){fe.call(t,n)?t[n].push(e):Rr(t,n,[e])}));var Wa=ki((function(t,e,n){var r=-1,i="function"==typeof e,o=vs(t)?Xt(t.length):[];return qr(t,(function(t){o[++r]=i?Ye(e,t,n):ri(t,e,n)})),o})),Ka=io((function(t,e,n){Rr(t,n,e)}));function Xa(t,e){return(bs(t)?Ze:hi)(t,Lo(e,3))}var Za=io((function(t,e,n){t[n?0:1].push(e)}),(function(){return[[],[]]}));var Ja=ki((function(t,e){if(null==t)return[];var n=e.length;return n>1&&Go(t,e[0],e[1])?e=[]:n>2&&Go(e[0],e[1],e[2])&&(e=[e[0]]),bi(t,Vr(e,1),[])})),Qa=Ln||function(){return Oe.Date.now()};function ts(t,e,n){return e=n?r:e,e=t&&null==e?t.length:e,Ao(t,x,r,r,r,r,e)}function es(t,e){var n;if("function"!=typeof e)throw new ie(a);return t=Us(t),function(){return--t>0&&(n=e.apply(this,arguments)),t<=1&&(e=r),n}}var ns=ki((function(t,e,n){var r=g;if(n.length){var i=Sn(n,Bo(ns));r|=_}return Ao(t,r,e,n,i)})),rs=ki((function(t,e,n){var r=g|y;if(n.length){var i=Sn(n,Bo(rs));r|=_}return Ao(e,r,t,n,i)}));function is(t,e,n){var i,o,s,u,c,f,l=0,h=!1,d=!1,p=!0;if("function"!=typeof t)throw new ie(a);function g(e){var n=i,a=o;return i=o=r,l=e,u=t.apply(a,n)}function y(t){var n=t-f;return f===r||n>=e||n<0||d&&t-l>=s}function b(){var t=Qa();if(y(t))return m(t);c=ia(b,function(t){var n=e-(t-f);return d?$n(n,s-(t-l)):n}(t))}function m(t){return c=r,p&&i?g(t):(i=o=r,u)}function v(){var t=Qa(),n=y(t);if(i=arguments,o=this,f=t,n){if(c===r)return function(t){return l=t,c=ia(b,e),h?g(t):u}(f);if(d)return Ki(c),c=ia(b,e),g(f)}return c===r&&(c=ia(b,e)),u}return e=Ys(e)||0,Ts(n)&&(h=!!n.leading,s=(d="maxWait"in n)?Hn(Ys(n.maxWait)||0,e):s,p="trailing"in n?!!n.trailing:p),v.cancel=function(){c!==r&&Ki(c),l=0,i=f=o=c=r},v.flush=function(){return c===r?u:m(Qa())},v}var os=ki((function(t,e){return Pr(t,1,e)})),as=ki((function(t,e,n){return Pr(t,Ys(e)||0,n)}));function ss(t,e){if("function"!=typeof t||null!=e&&"function"!=typeof e)throw new ie(a);var n=function(){var r=arguments,i=e?e.apply(this,r):r[0],o=n.cache;if(o.has(i))return o.get(i);var a=t.apply(this,r);return n.cache=o.set(i,a)||o,a};return n.cache=new(ss.Cache||_r),n}function us(t){if("function"!=typeof t)throw new ie(a);return function(){var e=arguments;switch(e.length){case 0:return!t.call(this);case 1:return!t.call(this,e[0]);case 2:return!t.call(this,e[0],e[1]);case 3:return!t.call(this,e[0],e[1],e[2])}return!t.apply(this,e)}}ss.Cache=_r;var cs=Gi((function(t,e){var n=(e=1==e.length&&bs(e[0])?Ze(e[0],gn(Lo())):Ze(Vr(e,1),gn(Lo()))).length;return ki((function(r){for(var i=-1,o=$n(r.length,n);++i=e})),ys=ii(function(){return arguments}())?ii:function(t){return Ms(t)&&fe.call(t,"callee")&&!Be.call(t,"callee")},bs=Xt.isArray,ms=Pe?gn(Pe):function(t){return Ms(t)&&Jr(t)==at};function vs(t){return null!=t&&Ss(t.length)&&!Es(t)}function _s(t){return Ms(t)&&vs(t)}var ws=Un||Yu,xs=Fe?gn(Fe):function(t){return Ms(t)&&Jr(t)==Y};function ks(t){if(!Ms(t))return!1;var e=Jr(t);return e==H||e==V||"string"==typeof t.message&&"string"==typeof t.name&&!Os(t)}function Es(t){if(!Ts(t))return!1;var e=Jr(t);return e==$||e==G||e==U||e==J}function As(t){return"number"==typeof t&&t==Us(t)}function Ss(t){return"number"==typeof t&&t>-1&&t%1==0&&t<=R}function Ts(t){var e=typeof t;return null!=t&&("object"==e||"function"==e)}function Ms(t){return null!=t&&"object"==typeof t}var Ds=qe?gn(qe):function(t){return Ms(t)&&zo(t)==W};function Cs(t){return"number"==typeof t||Ms(t)&&Jr(t)==K}function Os(t){if(!Ms(t)||Jr(t)!=Z)return!1;var e=Re(t);if(null===e)return!0;var n=fe.call(e,"constructor")&&e.constructor;return"function"==typeof n&&n instanceof n&&ce.call(n)==pe}var Rs=je?gn(je):function(t){return Ms(t)&&Jr(t)==Q};var Is=Ue?gn(Ue):function(t){return Ms(t)&&zo(t)==tt};function Ns(t){return"string"==typeof t||!bs(t)&&Ms(t)&&Jr(t)==et}function Bs(t){return"symbol"==typeof t||Ms(t)&&Jr(t)==nt}var Ls=ze?gn(ze):function(t){return Ms(t)&&Ss(t.length)&&!!Ee[Jr(t)]};var Ps=_o(li),Fs=_o((function(t,e){return t<=e}));function qs(t){if(!t)return[];if(vs(t))return Ns(t)?Cn(t):no(t);if(ln&&t[ln])return function(t){for(var e,n=[];!(e=t.next()).done;)n.push(e.value);return n}(t[ln]());var e=zo(t);return(e==W?En:e==tt?Tn:du)(t)}function js(t){return t?(t=Ys(t))===O||t===-O?(t<0?-1:1)*I:t==t?t:0:0===t?t:0}function Us(t){var e=js(t),n=e%1;return e==e?n?e-n:e:0}function zs(t){return t?Nr(Us(t),0,B):0}function Ys(t){if("number"==typeof t)return t;if(Bs(t))return N;if(Ts(t)){var e="function"==typeof t.valueOf?t.valueOf():t;t=Ts(e)?e+"":e}if("string"!=typeof t)return 0===t?t:+t;t=t.replace(Rt,"");var n=Yt.test(t);return n||Ht.test(t)?Me(t.slice(2),n?2:8):zt.test(t)?N:+t}function Vs(t){return ro(t,ou(t))}function Hs(t){return null==t?"":Li(t)}var $s=oo((function(t,e){if(Zo(e)||vs(e))ro(e,iu(e),t);else for(var n in e)fe.call(e,n)&&Mr(t,n,e[n])})),Gs=oo((function(t,e){ro(e,ou(e),t)})),Ws=oo((function(t,e,n,r){ro(e,ou(e),t,r)})),Ks=oo((function(t,e,n,r){ro(e,iu(e),t,r)})),Xs=Co(Ir);var Zs=ki((function(t,e){t=ee(t);var n=-1,i=e.length,o=i>2?e[2]:r;for(o&&Go(e[0],e[1],o)&&(i=1);++n1),e})),ro(t,Ro(t),n),r&&(n=Br(n,f|l|h,Mo));for(var i=e.length;i--;)Fi(n,e[i]);return n}));var cu=Co((function(t,e){return null==t?{}:function(t,e){return mi(t,e,(function(e,n){return tu(t,n)}))}(t,e)}));function fu(t,e){if(null==t)return{};var n=Ze(Ro(t),(function(t){return[t]}));return e=Lo(e),mi(t,n,(function(t,n){return e(t,n[0])}))}var lu=Eo(iu),hu=Eo(ou);function du(t){return null==t?[]:yn(t,iu(t))}var pu=co((function(t,e,n){return e=e.toLowerCase(),t+(n?gu(e):e)}));function gu(t){return ku(Hs(t).toLowerCase())}function yu(t){return(t=Hs(t))&&t.replace(Gt,_n).replace(be,"")}var bu=co((function(t,e,n){return t+(n?"-":"")+e.toLowerCase()})),mu=co((function(t,e,n){return t+(n?" ":"")+e.toLowerCase()})),vu=uo("toLowerCase");var _u=co((function(t,e,n){return t+(n?"_":"")+e.toLowerCase()}));var wu=co((function(t,e,n){return t+(n?" ":"")+ku(e)}));var xu=co((function(t,e,n){return t+(n?" ":"")+e.toUpperCase()})),ku=uo("toUpperCase");function Eu(t,e,n){return t=Hs(t),(e=n?r:e)===r?function(t){return we.test(t)}(t)?function(t){return t.match(ve)||[]}(t):function(t){return t.match(Ft)||[]}(t):t.match(e)||[]}var Au=ki((function(t,e){try{return Ye(t,r,e)}catch(t){return ks(t)?t:new Jt(t)}})),Su=Co((function(t,e){return He(e,(function(e){e=fa(e),Rr(t,e,ns(t[e],t))})),t}));function Tu(t){return function(){return t}}var Mu=ho(),Du=ho(!0);function Cu(t){return t}function Ou(t){return ui("function"==typeof t?t:Br(t,f))}var Ru=ki((function(t,e){return function(n){return ri(n,t,e)}})),Iu=ki((function(t,e){return function(n){return ri(t,n,e)}}));function Nu(t,e,n){var r=iu(e),i=Kr(e,r);null!=n||Ts(e)&&(i.length||!r.length)||(n=e,e=t,t=this,i=Kr(e,iu(e)));var o=!(Ts(n)&&"chain"in n&&!n.chain),a=Es(t);return He(i,(function(n){var r=e[n];t[n]=r,a&&(t.prototype[n]=function(){var e=this.__chain__;if(o||e){var n=t(this.__wrapped__),i=n.__actions__=no(this.__actions__);return i.push({func:r,args:arguments,thisArg:t}),n.__chain__=e,n}return r.apply(t,Je([this.value()],arguments))})})),t}function Bu(){}var Lu=bo(Ze),Pu=bo(Ge),Fu=bo(en);function qu(t){return Wo(t)?fn(fa(t)):function(t){return function(e){return Xr(e,t)}}(t)}var ju=vo(),Uu=vo(!0);function zu(){return[]}function Yu(){return!1}var Vu=yo((function(t,e){return t+e}),0),Hu=xo("ceil"),$u=yo((function(t,e){return t/e}),1),Gu=xo("floor");var Wu,Ku=yo((function(t,e){return t*e}),1),Xu=xo("round"),Zu=yo((function(t,e){return t-e}),0);return dr.after=function(t,e){if("function"!=typeof e)throw new ie(a);return t=Us(t),function(){if(--t<1)return e.apply(this,arguments)}},dr.ary=ts,dr.assign=$s,dr.assignIn=Gs,dr.assignInWith=Ws,dr.assignWith=Ks,dr.at=Xs,dr.before=es,dr.bind=ns,dr.bindAll=Su,dr.bindKey=rs,dr.castArray=function(){if(!arguments.length)return[];var t=arguments[0];return bs(t)?t:[t]},dr.chain=qa,dr.chunk=function(t,e,n){e=(n?Go(t,e,n):e===r)?1:Hn(Us(e),0);var i=null==t?0:t.length;if(!i||e<1)return[];for(var o=0,a=0,s=Xt(Fn(i/e));oo?0:o+n),(i=i===r||i>o?o:Us(i))<0&&(i+=o),i=n>i?0:zs(i);n>>0)?(t=Hs(t))&&("string"==typeof e||null!=e&&!Rs(e))&&!(e=Li(e))&&kn(t)?Wi(Cn(t),0,n):t.split(e,n):[]},dr.spread=function(t,e){if("function"!=typeof t)throw new ie(a);return e=null==e?0:Hn(Us(e),0),ki((function(n){var r=n[e],i=Wi(n,0,e);return r&&Je(i,r),Ye(t,this,i)}))},dr.tail=function(t){var e=null==t?0:t.length;return e?Ci(t,1,e):[]},dr.take=function(t,e,n){return t&&t.length?Ci(t,0,(e=n||e===r?1:Us(e))<0?0:e):[]},dr.takeRight=function(t,e,n){var i=null==t?0:t.length;return i?Ci(t,(e=i-(e=n||e===r?1:Us(e)))<0?0:e,i):[]},dr.takeRightWhile=function(t,e){return t&&t.length?ji(t,Lo(e,3),!1,!0):[]},dr.takeWhile=function(t,e){return t&&t.length?ji(t,Lo(e,3)):[]},dr.tap=function(t,e){return e(t),t},dr.throttle=function(t,e,n){var r=!0,i=!0;if("function"!=typeof t)throw new ie(a);return Ts(n)&&(r="leading"in n?!!n.leading:r,i="trailing"in n?!!n.trailing:i),is(t,e,{leading:r,maxWait:e,trailing:i})},dr.thru=ja,dr.toArray=qs,dr.toPairs=lu,dr.toPairsIn=hu,dr.toPath=function(t){return bs(t)?Ze(t,fa):Bs(t)?[t]:no(ca(Hs(t)))},dr.toPlainObject=Vs,dr.transform=function(t,e,n){var r=bs(t),i=r||ws(t)||Ls(t);if(e=Lo(e,4),null==n){var o=t&&t.constructor;n=i?r?new o:[]:Ts(t)&&Es(o)?pr(Re(t)):{}}return(i?He:Gr)(t,(function(t,r,i){return e(n,t,r,i)})),n},dr.unary=function(t){return ts(t,1)},dr.union=Ma,dr.unionBy=Da,dr.unionWith=Ca,dr.uniq=function(t){return t&&t.length?Pi(t):[]},dr.uniqBy=function(t,e){return t&&t.length?Pi(t,Lo(e,2)):[]},dr.uniqWith=function(t,e){return e="function"==typeof e?e:r,t&&t.length?Pi(t,r,e):[]},dr.unset=function(t,e){return null==t||Fi(t,e)},dr.unzip=Oa,dr.unzipWith=Ra,dr.update=function(t,e,n){return null==t?t:qi(t,e,Hi(n))},dr.updateWith=function(t,e,n,i){return i="function"==typeof i?i:r,null==t?t:qi(t,e,Hi(n),i)},dr.values=du,dr.valuesIn=function(t){return null==t?[]:yn(t,ou(t))},dr.without=Ia,dr.words=Eu,dr.wrap=function(t,e){return fs(Hi(e),t)},dr.xor=Na,dr.xorBy=Ba,dr.xorWith=La,dr.zip=Pa,dr.zipObject=function(t,e){return Yi(t||[],e||[],Mr)},dr.zipObjectDeep=function(t,e){return Yi(t||[],e||[],Si)},dr.zipWith=Fa,dr.entries=lu,dr.entriesIn=hu,dr.extend=Gs,dr.extendWith=Ws,Nu(dr,dr),dr.add=Vu,dr.attempt=Au,dr.camelCase=pu,dr.capitalize=gu,dr.ceil=Hu,dr.clamp=function(t,e,n){return n===r&&(n=e,e=r),n!==r&&(n=(n=Ys(n))==n?n:0),e!==r&&(e=(e=Ys(e))==e?e:0),Nr(Ys(t),e,n)},dr.clone=function(t){return Br(t,h)},dr.cloneDeep=function(t){return Br(t,f|h)},dr.cloneDeepWith=function(t,e){return Br(t,f|h,e="function"==typeof e?e:r)},dr.cloneWith=function(t,e){return Br(t,h,e="function"==typeof e?e:r)},dr.conformsTo=function(t,e){return null==e||Lr(t,e,iu(e))},dr.deburr=yu,dr.defaultTo=function(t,e){return null==t||t!=t?e:t},dr.divide=$u,dr.endsWith=function(t,e,n){t=Hs(t),e=Li(e);var i=t.length,o=n=n===r?i:Nr(Us(n),0,i);return(n-=e.length)>=0&&t.slice(n,o)==e},dr.eq=ds,dr.escape=function(t){return(t=Hs(t))&&kt.test(t)?t.replace(wt,wn):t},dr.escapeRegExp=function(t){return(t=Hs(t))&&Ot.test(t)?t.replace(Ct,"\\$&"):t},dr.every=function(t,e,n){var i=bs(t)?Ge:Ur;return n&&Go(t,e,n)&&(e=r),i(t,Lo(e,3))},dr.find=Ya,dr.findIndex=ya,dr.findKey=function(t,e){return rn(t,Lo(e,3),Gr)},dr.findLast=Va,dr.findLastIndex=ba,dr.findLastKey=function(t,e){return rn(t,Lo(e,3),Wr)},dr.floor=Gu,dr.forEach=Ha,dr.forEachRight=$a,dr.forIn=function(t,e){return null==t?t:Hr(t,Lo(e,3),ou)},dr.forInRight=function(t,e){return null==t?t:$r(t,Lo(e,3),ou)},dr.forOwn=function(t,e){return t&&Gr(t,Lo(e,3))},dr.forOwnRight=function(t,e){return t&&Wr(t,Lo(e,3))},dr.get=Qs,dr.gt=ps,dr.gte=gs,dr.has=function(t,e){return null!=t&&Yo(t,e,ti)},dr.hasIn=tu,dr.head=va,dr.identity=Cu,dr.includes=function(t,e,n,r){t=vs(t)?t:du(t),n=n&&!r?Us(n):0;var i=t.length;return n<0&&(n=Hn(i+n,0)),Ns(t)?n<=i&&t.indexOf(e,n)>-1:!!i&&an(t,e,n)>-1},dr.indexOf=function(t,e,n){var r=null==t?0:t.length;if(!r)return-1;var i=null==n?0:Us(n);return i<0&&(i=Hn(r+i,0)),an(t,e,i)},dr.inRange=function(t,e,n){return e=js(e),n===r?(n=e,e=0):n=js(n),function(t,e,n){return t>=$n(e,n)&&t=-R&&t<=R},dr.isSet=Is,dr.isString=Ns,dr.isSymbol=Bs,dr.isTypedArray=Ls,dr.isUndefined=function(t){return t===r},dr.isWeakMap=function(t){return Ms(t)&&zo(t)==it},dr.isWeakSet=function(t){return Ms(t)&&Jr(t)==ot},dr.join=function(t,e){return null==t?"":Yn.call(t,e)},dr.kebabCase=bu,dr.last=ka,dr.lastIndexOf=function(t,e,n){var i=null==t?0:t.length;if(!i)return-1;var o=i;return n!==r&&(o=(o=Us(n))<0?Hn(i+o,0):$n(o,i-1)),e==e?function(t,e,n){for(var r=n+1;r--;)if(t[r]===e)return r;return r}(t,e,o):on(t,un,o,!0)},dr.lowerCase=mu,dr.lowerFirst=vu,dr.lt=Ps,dr.lte=Fs,dr.max=function(t){return t&&t.length?zr(t,Cu,Qr):r},dr.maxBy=function(t,e){return t&&t.length?zr(t,Lo(e,2),Qr):r},dr.mean=function(t){return cn(t,Cu)},dr.meanBy=function(t,e){return cn(t,Lo(e,2))},dr.min=function(t){return t&&t.length?zr(t,Cu,li):r},dr.minBy=function(t,e){return t&&t.length?zr(t,Lo(e,2),li):r},dr.stubArray=zu,dr.stubFalse=Yu,dr.stubObject=function(){return{}},dr.stubString=function(){return""},dr.stubTrue=function(){return!0},dr.multiply=Ku,dr.nth=function(t,e){return t&&t.length?yi(t,Us(e)):r},dr.noConflict=function(){return Oe._===this&&(Oe._=ge),this},dr.noop=Bu,dr.now=Qa,dr.pad=function(t,e,n){t=Hs(t);var r=(e=Us(e))?Dn(t):0;if(!e||r>=e)return t;var i=(e-r)/2;return mo(qn(i),n)+t+mo(Fn(i),n)},dr.padEnd=function(t,e,n){t=Hs(t);var r=(e=Us(e))?Dn(t):0;return e&&re){var i=t;t=e,e=i}if(n||t%1||e%1){var o=Kn();return $n(t+o*(e-t+Te("1e-"+((o+"").length-1))),e)}return wi(t,e)},dr.reduce=function(t,e,n){var r=bs(t)?Qe:hn,i=arguments.length<3;return r(t,Lo(e,4),n,i,qr)},dr.reduceRight=function(t,e,n){var r=bs(t)?tn:hn,i=arguments.length<3;return r(t,Lo(e,4),n,i,jr)},dr.repeat=function(t,e,n){return e=(n?Go(t,e,n):e===r)?1:Us(e),xi(Hs(t),e)},dr.replace=function(){var t=arguments,e=Hs(t[0]);return t.length<3?e:e.replace(t[1],t[2])},dr.result=function(t,e,n){var i=-1,o=(e=$i(e,t)).length;for(o||(o=1,t=r);++iR)return[];var n=B,r=$n(t,B);e=Lo(e),t-=B;for(var i=pn(r,e);++n=a)return t;var u=n-Dn(i);if(u<1)return i;var c=s?Wi(s,0,u).join(""):t.slice(0,u);if(o===r)return c+i;if(s&&(u+=c.length-u),Rs(o)){if(t.slice(u).search(o)){var f,l=c;for(o.global||(o=ne(o.source,Hs(Ut.exec(o))+"g")),o.lastIndex=0;f=o.exec(l);)var h=f.index;c=c.slice(0,h===r?u:h)}}else if(t.indexOf(Li(o),u)!=u){var d=c.lastIndexOf(o);d>-1&&(c=c.slice(0,d))}return c+i},dr.unescape=function(t){return(t=Hs(t))&&xt.test(t)?t.replace(_t,On):t},dr.uniqueId=function(t){var e=++le;return Hs(t)+e},dr.upperCase=xu,dr.upperFirst=ku,dr.each=Ha,dr.eachRight=$a,dr.first=va,Nu(dr,(Wu={},Gr(dr,(function(t,e){fe.call(dr.prototype,e)||(Wu[e]=t)})),Wu),{chain:!1}),dr.VERSION="4.17.15",He(["bind","bindKey","curry","curryRight","partial","partialRight"],(function(t){dr[t].placeholder=dr})),He(["drop","take"],(function(t,e){br.prototype[t]=function(n){n=n===r?1:Hn(Us(n),0);var i=this.__filtered__&&!e?new br(this):this.clone();return i.__filtered__?i.__takeCount__=$n(n,i.__takeCount__):i.__views__.push({size:$n(n,B),type:t+(i.__dir__<0?"Right":"")}),i},br.prototype[t+"Right"]=function(e){return this.reverse()[t](e).reverse()}})),He(["filter","map","takeWhile"],(function(t,e){var n=e+1,r=n==D||3==n;br.prototype[t]=function(t){var e=this.clone();return e.__iteratees__.push({iteratee:Lo(t,3),type:n}),e.__filtered__=e.__filtered__||r,e}})),He(["head","last"],(function(t,e){var n="take"+(e?"Right":"");br.prototype[t]=function(){return this[n](1).value()[0]}})),He(["initial","tail"],(function(t,e){var n="drop"+(e?"":"Right");br.prototype[t]=function(){return this.__filtered__?new br(this):this[n](1)}})),br.prototype.compact=function(){return this.filter(Cu)},br.prototype.find=function(t){return this.filter(t).head()},br.prototype.findLast=function(t){return this.reverse().find(t)},br.prototype.invokeMap=ki((function(t,e){return"function"==typeof t?new br(this):this.map((function(n){return ri(n,t,e)}))})),br.prototype.reject=function(t){return this.filter(us(Lo(t)))},br.prototype.slice=function(t,e){t=Us(t);var n=this;return n.__filtered__&&(t>0||e<0)?new br(n):(t<0?n=n.takeRight(-t):t&&(n=n.drop(t)),e!==r&&(n=(e=Us(e))<0?n.dropRight(-e):n.take(e-t)),n)},br.prototype.takeRightWhile=function(t){return this.reverse().takeWhile(t).reverse()},br.prototype.toArray=function(){return this.take(B)},Gr(br.prototype,(function(t,e){var n=/^(?:filter|find|map|reject)|While$/.test(e),i=/^(?:head|last)$/.test(e),o=dr[i?"take"+("last"==e?"Right":""):e],a=i||/^find/.test(e);o&&(dr.prototype[e]=function(){var e=this.__wrapped__,s=i?[1]:arguments,u=e instanceof br,c=s[0],f=u||bs(e),l=function(t){var e=o.apply(dr,Je([t],s));return i&&h?e[0]:e};f&&n&&"function"==typeof c&&1!=c.length&&(u=f=!1);var h=this.__chain__,d=!!this.__actions__.length,p=a&&!h,g=u&&!d;if(!a&&f){e=g?e:new br(this);var y=t.apply(e,s);return y.__actions__.push({func:ja,args:[l],thisArg:r}),new yr(y,h)}return p&&g?t.apply(this,s):(y=this.thru(l),p?i?y.value()[0]:y.value():y)})})),He(["pop","push","shift","sort","splice","unshift"],(function(t){var e=oe[t],n=/^(?:push|sort|unshift)$/.test(t)?"tap":"thru",r=/^(?:pop|shift)$/.test(t);dr.prototype[t]=function(){var t=arguments;if(r&&!this.__chain__){var i=this.value();return e.apply(bs(i)?i:[],t)}return this[n]((function(n){return e.apply(bs(n)?n:[],t)}))}})),Gr(br.prototype,(function(t,e){var n=dr[e];if(n){var r=n.name+"";fe.call(ir,r)||(ir[r]=[]),ir[r].push({name:e,func:n})}})),ir[po(r,y).name]=[{name:"wrapper",func:r}],br.prototype.clone=function(){var t=new br(this.__wrapped__);return t.__actions__=no(this.__actions__),t.__dir__=this.__dir__,t.__filtered__=this.__filtered__,t.__iteratees__=no(this.__iteratees__),t.__takeCount__=this.__takeCount__,t.__views__=no(this.__views__),t},br.prototype.reverse=function(){if(this.__filtered__){var t=new br(this);t.__dir__=-1,t.__filtered__=!0}else(t=this.clone()).__dir__*=-1;return t},br.prototype.value=function(){var t=this.__wrapped__.value(),e=this.__dir__,n=bs(t),r=e<0,i=n?t.length:0,o=function(t,e,n){var r=-1,i=n.length;for(;++r=this.__values__.length;return{done:t,value:t?r:this.__values__[this.__index__++]}},dr.prototype.plant=function(t){for(var e,n=this;n instanceof gr;){var i=ha(n);i.__index__=0,i.__values__=r,e?o.__wrapped__=i:e=i;var o=i;n=n.__wrapped__}return o.__wrapped__=t,e},dr.prototype.reverse=function(){var t=this.__wrapped__;if(t instanceof br){var e=t;return this.__actions__.length&&(e=new br(this)),(e=e.reverse()).__actions__.push({func:ja,args:[Ta],thisArg:r}),new yr(e,this.__chain__)}return this.thru(Ta)},dr.prototype.toJSON=dr.prototype.valueOf=dr.prototype.value=function(){return Ui(this.__wrapped__,this.__actions__)},dr.prototype.first=dr.prototype.head,ln&&(dr.prototype[ln]=function(){return this}),dr}();"function"==typeof define&&"object"==typeof define.amd&&define.amd?(Oe._=Rn,define((function(){return Rn}))):Ie?((Ie.exports=Rn)._=Rn,Re._=Rn):Oe._=Rn}).call(this)}).call(this,n(11),n(9)(t))},function(t,e,n){var r=n(66),i=n(67);t.exports=function(t,e,n,o){var a=!n;n||(n={});for(var s=-1,u=e.length;++s=this._delta8){var n=(t=this.pending).length%this._delta8;this.pending=t.slice(t.length-n,t.length),0===this.pending.length&&(this.pending=null),t=r.join32(t,0,t.length-n,this.endian);for(var i=0;i>>24&255,r[i++]=t>>>16&255,r[i++]=t>>>8&255,r[i++]=255&t}else for(r[i++]=255&t,r[i++]=t>>>8&255,r[i++]=t>>>16&255,r[i++]=t>>>24&255,r[i++]=0,r[i++]=0,r[i++]=0,r[i++]=0,o=8;ol&&M.push("'"+this.terminals_[A]+"'");C=p.showPosition?"Parse error on line "+(u+1)+":\n"+p.showPosition()+"\nExpecting "+M.join(", ")+", got '"+(this.terminals_[_]||_)+"'":"Parse error on line "+(u+1)+": Unexpected "+(_==h?"end of input":"'"+(this.terminals_[_]||_)+"'"),this.parseError(C,{text:p.match,token:this.terminals_[_]||_,line:p.yylineno,loc:b,expected:M})}if(k[0]instanceof Array&&k.length>1)throw new Error("Parse Error: multiple actions possible at state: "+x+", token: "+_);switch(k[0]){case 1:n.push(_),i.push(p.yytext),o.push(p.yylloc),n.push(k[1]),_=null,w?(_=w,w=null):(c=p.yyleng,s=p.yytext,u=p.yylineno,b=p.yylloc,f>0&&f--);break;case 2:if(S=this.productions_[k[1]][1],D.$=i[i.length-S],D._$={first_line:o[o.length-(S||1)].first_line,last_line:o[o.length-1].last_line,first_column:o[o.length-(S||1)].first_column,last_column:o[o.length-1].last_column},m&&(D._$.range=[o[o.length-(S||1)].range[0],o[o.length-1].range[1]]),void 0!==(E=this.performAction.apply(D,[s,c,u,g.yy,k[1],i,o].concat(d))))return E;S&&(n=n.slice(0,-1*S*2),i=i.slice(0,-1*S),o=o.slice(0,-1*S)),n.push(this.productions_[k[1]][0]),i.push(D.$),o.push(D._$),T=a[n[n.length-2]][n[n.length-1]],n.push(T);break;case 3:return!0}}return!0}},Ut={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var o in i)this[o]=i[o];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),o=0;oe[0].length)){if(e=n,r=o,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[o])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var t=this.next();return t||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{},performAction:function(t,e,n,r){switch(n){case 0:break;case 1:this.begin("string");break;case 2:this.popState();break;case 3:return"STR";case 4:return 89;case 5:return 98;case 6:return 90;case 7:return 103;case 8:return 91;case 9:return 92;case 10:return 93;case 11:return t.lex.firstGraph()&&this.begin("dir"),12;case 12:return 26;case 13:return 30;case 14:case 15:case 16:case 17:case 18:case 19:case 20:case 21:case 22:case 23:return this.popState(),13;case 24:return 106;case 25:return 114;case 26:return 34;case 27:return 111;case 28:return 8;case 29:return 107;case 30:return 125;case 31:return 55;case 32:return 51;case 33:return 74;case 34:return 76;case 35:return 75;case 36:return 78;case 37:return 80;case 38:return 81;case 39:return 82;case 40:case 41:return 79;case 42:case 43:return 77;case 44:return 78;case 45:return 53;case 46:return 57;case 47:return 63;case 48:return 59;case 49:return 61;case 50:return 65;case 51:return 63;case 52:return 59;case 53:return 61;case 54:return 65;case 55:return 71;case 56:return 67;case 57:return 69;case 58:return 73;case 59:return 52;case 60:return 56;case 61:return 54;case 62:return 60;case 63:return 64;case 64:return 62;case 65:return 68;case 66:return 72;case 67:return 70;case 68:return 50;case 69:return 58;case 70:return 66;case 71:return 38;case 72:return 39;case 73:return 112;case 74:return 115;case 75:return 126;case 76:return 123;case 77:return 105;case 78:case 79:return 124;case 80:return 117;case 81:return 42;case 82:return 95;case 83:return 94;case 84:return 110;case 85:return 44;case 86:return 43;case 87:return 46;case 88:return 45;case 89:return 121;case 90:return 122;case 91:return 83;case 92:return 36;case 93:return 37;case 94:return 28;case 95:return 29;case 96:return 40;case 97:return 41;case 98:return 127;case 99:return 9;case 100:return 10;case 101:return 11}},rules:[/^(?:%%[^\n]*)/,/^(?:["])/,/^(?:["])/,/^(?:[^"]*)/,/^(?:style\b)/,/^(?:default\b)/,/^(?:linkStyle\b)/,/^(?:interpolate\b)/,/^(?:classDef\b)/,/^(?:class\b)/,/^(?:click\b)/,/^(?:graph\b)/,/^(?:subgraph\b)/,/^(?:end\b\s*)/,/^(?:\s*LR\b)/,/^(?:\s*RL\b)/,/^(?:\s*TB\b)/,/^(?:\s*BT\b)/,/^(?:\s*TD\b)/,/^(?:\s*BR\b)/,/^(?:\s*<)/,/^(?:\s*>)/,/^(?:\s*\^)/,/^(?:\s*v\b)/,/^(?:[0-9]+)/,/^(?:#)/,/^(?::::)/,/^(?::)/,/^(?:;)/,/^(?:,)/,/^(?:\*)/,/^(?:\s*--[x]\s*)/,/^(?:\s*-->\s*)/,/^(?:\s*<-->\s*)/,/^(?:\s*[x]--[x]\s*)/,/^(?:\s*[o]--[o]\s*)/,/^(?:\s*[o]\.-[o]\s*)/,/^(?:\s*<==>\s*)/,/^(?:\s*[o]==[o]\s*)/,/^(?:\s*[x]==[x]\s*)/,/^(?:\s*[x].-[x]\s*)/,/^(?:\s*[x]-\.-[x]\s*)/,/^(?:\s*<\.->\s*)/,/^(?:\s*<-\.->\s*)/,/^(?:\s*[o]-\.-[o]\s*)/,/^(?:\s*--[o]\s*)/,/^(?:\s*---\s*)/,/^(?:\s*-\.-[x]\s*)/,/^(?:\s*-\.->\s*)/,/^(?:\s*-\.-[o]\s*)/,/^(?:\s*-\.-\s*)/,/^(?:\s*.-[x]\s*)/,/^(?:\s*\.->\s*)/,/^(?:\s*\.-[o]\s*)/,/^(?:\s*\.-\s*)/,/^(?:\s*==[x]\s*)/,/^(?:\s*==>\s*)/,/^(?:\s*==[o]\s*)/,/^(?:\s*==[\=]\s*)/,/^(?:\s*<--\s*)/,/^(?:\s*[x]--\s*)/,/^(?:\s*[o]--\s*)/,/^(?:\s*<-\.\s*)/,/^(?:\s*[x]-\.\s*)/,/^(?:\s*[o]-\.\s*)/,/^(?:\s*<==\s*)/,/^(?:\s*[x]==\s*)/,/^(?:\s*[o]==\s*)/,/^(?:\s*--\s*)/,/^(?:\s*-\.\s*)/,/^(?:\s*==\s*)/,/^(?:\(-)/,/^(?:-\))/,/^(?:-)/,/^(?:\.)/,/^(?:[\_])/,/^(?:\+)/,/^(?:%)/,/^(?:=)/,/^(?:=)/,/^(?:<)/,/^(?:>)/,/^(?:\^)/,/^(?:v\b)/,/^(?:[A-Za-z]+)/,/^(?:\\\])/,/^(?:\[\/)/,/^(?:\/\])/,/^(?:\[\\)/,/^(?:[!"#$%&'*+,-.`?\\_\/])/,/^(?:[\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6]|[\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377]|[\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5]|[\u03F7-\u0481\u048A-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA]|[\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE]|[\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA]|[\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0]|[\u08A2-\u08AC\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0977]|[\u0979-\u097F\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2]|[\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A]|[\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39]|[\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8]|[\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C]|[\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C]|[\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99]|[\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0]|[\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3D]|[\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3]|[\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10]|[\u0D12-\u0D3A\u0D3D\u0D4E\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1]|[\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81]|[\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3]|[\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6]|[\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A]|[\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081]|[\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D]|[\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0]|[\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310]|[\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C]|[\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u1700-\u170C\u170E-\u1711]|[\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7]|[\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191C]|[\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16]|[\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF]|[\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC]|[\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D]|[\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D]|[\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3]|[\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F]|[\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128]|[\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184]|[\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3]|[\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6]|[\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE]|[\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C]|[\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D]|[\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC]|[\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B]|[\uA640-\uA66E\uA67F-\uA697\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788]|[\uA78B-\uA78E\uA790-\uA793\uA7A0-\uA7AA\uA7F8-\uA801\uA803-\uA805]|[\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB]|[\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uAA00-\uAA28]|[\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA80-\uAAAF\uAAB1\uAAB5]|[\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4]|[\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E]|[\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D]|[\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36]|[\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D]|[\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC]|[\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF]|[\uFFD2-\uFFD7\uFFDA-\uFFDC])/,/^(?:\|)/,/^(?:\()/,/^(?:\))/,/^(?:\[)/,/^(?:\])/,/^(?:\{)/,/^(?:\})/,/^(?:")/,/^(?:(\r|\n|\r\n)+)/,/^(?:\s)/,/^(?:$)/],conditions:{dir:{rules:[14,15,16,17,18,19,20,21,22,23],inclusive:!1},string:{rules:[2,3],inclusive:!1},INITIAL:{rules:[0,1,4,5,6,7,8,9,10,11,12,13,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101],inclusive:!0}}};function zt(){this.yy={}}return jt.lexer=Ut,zt.prototype=jt,jt.Parser=zt,new zt}();e.parser=i,e.Parser=i.Parser,e.parse=function(){return i.parse.apply(i,arguments)},e.main=function(r){r[1]||(console.log("Usage: "+r[0]+" FILE"),t.exit(1));var i=n(28).readFileSync(n(29).normalize(r[1]),"utf8");return e.parser.parse(i)},n.c[n.s]===r&&e.main(t.argv.slice(1))}).call(this,n(7),n(9)(t))},function(t,e,n){var r=n(62),i=n(241),o=n(242),a=n(243),s=n(244),u=n(245);function c(t){var e=this.__data__=new r(t);this.size=e.size}c.prototype.clear=i,c.prototype.delete=o,c.prototype.get=a,c.prototype.has=s,c.prototype.set=u,t.exports=c},function(t,e,n){var r=n(236),i=n(237),o=n(238),a=n(239),s=n(240);function u(t){var e=-1,n=null==t?0:t.length;for(this.clear();++e-1&&t%1==0&&t>>24]^f[p>>>16&255]^l[g>>>8&255]^h[255&y]^e[b++],a=c[p>>>24]^f[g>>>16&255]^l[y>>>8&255]^h[255&d]^e[b++],s=c[g>>>24]^f[y>>>16&255]^l[d>>>8&255]^h[255&p]^e[b++],u=c[y>>>24]^f[d>>>16&255]^l[p>>>8&255]^h[255&g]^e[b++],d=o,p=a,g=s,y=u;return o=(r[d>>>24]<<24|r[p>>>16&255]<<16|r[g>>>8&255]<<8|r[255&y])^e[b++],a=(r[p>>>24]<<24|r[g>>>16&255]<<16|r[y>>>8&255]<<8|r[255&d])^e[b++],s=(r[g>>>24]<<24|r[y>>>16&255]<<16|r[d>>>8&255]<<8|r[255&p])^e[b++],u=(r[y>>>24]<<24|r[d>>>16&255]<<16|r[p>>>8&255]<<8|r[255&g])^e[b++],[o>>>=0,a>>>=0,s>>>=0,u>>>=0]}var s=[0,1,2,4,8,16,32,64,128,27,54],u=function(){for(var t=new Array(256),e=0;e<256;e++)t[e]=e<128?e<<1:e<<1^283;for(var n=[],r=[],i=[[],[],[],[]],o=[[],[],[],[]],a=0,s=0,u=0;u<256;++u){var c=s^s<<1^s<<2^s<<3^s<<4;c=c>>>8^255&c^99,n[a]=c,r[c]=a;var f=t[a],l=t[f],h=t[l],d=257*t[c]^16843008*c;i[0][a]=d<<24|d>>>8,i[1][a]=d<<16|d>>>16,i[2][a]=d<<8|d>>>24,i[3][a]=d,d=16843009*h^65537*l^257*f^16843008*a,o[0][c]=d<<24|d>>>8,o[1][c]=d<<16|d>>>16,o[2][c]=d<<8|d>>>24,o[3][c]=d,0===a?a=s=1:(a=f^t[t[t[h^f]]],s^=t[t[s]])}return{SBOX:n,INV_SBOX:r,SUB_MIX:i,INV_SUB_MIX:o}}();function c(t){this._key=i(t),this._reset()}c.blockSize=16,c.keySize=32,c.prototype.blockSize=c.blockSize,c.prototype.keySize=c.keySize,c.prototype._reset=function(){for(var t=this._key,e=t.length,n=e+6,r=4*(n+1),i=[],o=0;o>>24,a=u.SBOX[a>>>24]<<24|u.SBOX[a>>>16&255]<<16|u.SBOX[a>>>8&255]<<8|u.SBOX[255&a],a^=s[o/e|0]<<24):e>6&&o%e==4&&(a=u.SBOX[a>>>24]<<24|u.SBOX[a>>>16&255]<<16|u.SBOX[a>>>8&255]<<8|u.SBOX[255&a]),i[o]=i[o-e]^a}for(var c=[],f=0;f>>24]]^u.INV_SUB_MIX[1][u.SBOX[h>>>16&255]]^u.INV_SUB_MIX[2][u.SBOX[h>>>8&255]]^u.INV_SUB_MIX[3][u.SBOX[255&h]]}this._nRounds=n,this._keySchedule=i,this._invKeySchedule=c},c.prototype.encryptBlockRaw=function(t){return a(t=i(t),this._keySchedule,u.SUB_MIX,u.SBOX,this._nRounds)},c.prototype.encryptBlock=function(t){var e=this.encryptBlockRaw(t),n=r.allocUnsafe(16);return n.writeUInt32BE(e[0],0),n.writeUInt32BE(e[1],4),n.writeUInt32BE(e[2],8),n.writeUInt32BE(e[3],12),n},c.prototype.decryptBlock=function(t){var e=(t=i(t))[1];t[1]=t[3],t[3]=e;var n=a(t,this._invKeySchedule,u.INV_SUB_MIX,u.INV_SBOX,this._nRounds),o=r.allocUnsafe(16);return o.writeUInt32BE(n[0],0),o.writeUInt32BE(n[3],4),o.writeUInt32BE(n[2],8),o.writeUInt32BE(n[1],12),o},c.prototype.scrub=function(){o(this._keySchedule),o(this._invKeySchedule),o(this._key)},t.exports.AES=c},function(t,e,n){var r=n(3).Buffer,i=n(111);t.exports=function(t,e,n,o){if(r.isBuffer(t)||(t=r.from(t,"binary")),e&&(r.isBuffer(e)||(e=r.from(e,"binary")),8!==e.length))throw new RangeError("salt should be Buffer with 8 byte length");for(var a=n/8,s=r.alloc(a),u=r.alloc(o||0),c=r.alloc(0);a>0||o>0;){var f=new i;f.update(c),f.update(t),e&&f.update(e),c=f.digest();var l=0;if(a>0){var h=s.length-a;l=Math.min(a,c.length),c.copy(s,h,0,l),a-=l}if(l0){var d=u.length-o,p=Math.min(o,c.length-l);c.copy(u,d,l,l+p),o-=p}}return c.fill(0),{key:s,iv:u}}},function(t,e,n){"use strict";var r=n(5),i=n(16),o=i.getNAF,a=i.getJSF,s=i.assert;function u(t,e){this.type=t,this.p=new r(e.p,16),this.red=e.prime?r.red(e.prime):r.mont(this.p),this.zero=new r(0).toRed(this.red),this.one=new r(1).toRed(this.red),this.two=new r(2).toRed(this.red),this.n=e.n&&new r(e.n,16),this.g=e.g&&this.pointFromJSON(e.g,e.gRed),this._wnafT1=new Array(4),this._wnafT2=new Array(4),this._wnafT3=new Array(4),this._wnafT4=new Array(4);var n=this.n&&this.p.div(this.n);!n||n.cmpn(100)>0?this.redN=null:(this._maxwellTrick=!0,this.redN=this.n.toRed(this.red))}function c(t,e){this.curve=t,this.type=e,this.precomputed=null}t.exports=u,u.prototype.point=function(){throw new Error("Not implemented")},u.prototype.validate=function(){throw new Error("Not implemented")},u.prototype._fixedNafMul=function(t,e){s(t.precomputed);var n=t._getDoubles(),r=o(e,1),i=(1<=u;e--)c=(c<<1)+r[e];a.push(c)}for(var f=this.jpoint(null,null,null),l=this.jpoint(null,null,null),h=i;h>0;h--){for(u=0;u=0;c--){for(e=0;c>=0&&0===a[c];c--)e++;if(c>=0&&e++,u=u.dblp(e),c<0)break;var f=a[c];s(0!==f),u="affine"===t.type?f>0?u.mixedAdd(i[f-1>>1]):u.mixedAdd(i[-f-1>>1].neg()):f>0?u.add(i[f-1>>1]):u.add(i[-f-1>>1].neg())}return"affine"===t.type?u.toP():u},u.prototype._wnafMulAdd=function(t,e,n,r,i){for(var s=this._wnafT1,u=this._wnafT2,c=this._wnafT3,f=0,l=0;l=1;l-=2){var d=l-1,p=l;if(1===s[d]&&1===s[p]){var g=[e[d],null,null,e[p]];0===e[d].y.cmp(e[p].y)?(g[1]=e[d].add(e[p]),g[2]=e[d].toJ().mixedAdd(e[p].neg())):0===e[d].y.cmp(e[p].y.redNeg())?(g[1]=e[d].toJ().mixedAdd(e[p]),g[2]=e[d].add(e[p].neg())):(g[1]=e[d].toJ().mixedAdd(e[p]),g[2]=e[d].toJ().mixedAdd(e[p].neg()));var y=[-3,-1,-5,-7,0,7,5,1,3],b=a(n[d],n[p]);f=Math.max(b[0].length,f),c[d]=new Array(f),c[p]=new Array(f);for(var m=0;m=0;l--){for(var k=0;l>=0;){var E=!0;for(m=0;m=0&&k++,w=w.dblp(k),l<0)break;for(m=0;m0?A=u[m][S-1>>1]:S<0&&(A=u[m][-S-1>>1].neg()),w="affine"===A.type?w.mixedAdd(A):w.add(A))}}for(l=0;l=Math.ceil((t.bitLength()+1)/e.step)},c.prototype._getDoubles=function(t,e){if(this.precomputed&&this.precomputed.doubles)return this.precomputed.doubles;for(var n=[this],r=this,i=0;i-1}(s)?s:(n=s.match(o))?(e=n[0],r.test(e)?"about:blank":s):"about:blank"}}},function(t,e,n){(function(t,r){var i=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[2,3],n=[1,7],r=[7,12,15,17,19,20,21],i=[7,11,12,15,17,19,20,21],o=[2,20],a=[1,32],s={trace:function(){},yy:{},symbols_:{error:2,start:3,GG:4,":":5,document:6,EOF:7,DIR:8,options:9,body:10,OPT:11,NL:12,line:13,statement:14,COMMIT:15,commit_arg:16,BRANCH:17,ID:18,CHECKOUT:19,MERGE:20,RESET:21,reset_arg:22,STR:23,HEAD:24,reset_parents:25,CARET:26,$accept:0,$end:1},terminals_:{2:"error",4:"GG",5:":",7:"EOF",8:"DIR",11:"OPT",12:"NL",15:"COMMIT",17:"BRANCH",18:"ID",19:"CHECKOUT",20:"MERGE",21:"RESET",23:"STR",24:"HEAD",26:"CARET"},productions_:[0,[3,4],[3,5],[6,0],[6,2],[9,2],[9,1],[10,0],[10,2],[13,2],[13,1],[14,2],[14,2],[14,2],[14,2],[14,2],[16,0],[16,1],[22,2],[22,2],[25,0],[25,2]],performAction:function(t,e,n,r,i,o,a){var s=o.length-1;switch(i){case 1:return o[s-1];case 2:return r.setDirection(o[s-3]),o[s-1];case 4:r.setOptions(o[s-1]),this.$=o[s];break;case 5:o[s-1]+=o[s],this.$=o[s-1];break;case 7:this.$=[];break;case 8:o[s-1].push(o[s]),this.$=o[s-1];break;case 9:this.$=o[s-1];break;case 11:r.commit(o[s]);break;case 12:r.branch(o[s]);break;case 13:r.checkout(o[s]);break;case 14:r.merge(o[s]);break;case 15:r.reset(o[s]);break;case 16:this.$="";break;case 17:this.$=o[s];break;case 18:this.$=o[s-1]+":"+o[s];break;case 19:this.$=o[s-1]+":"+r.count,r.count=0;break;case 20:r.count=0;break;case 21:r.count+=1}},table:[{3:1,4:[1,2]},{1:[3]},{5:[1,3],8:[1,4]},{6:5,7:e,9:6,12:n},{5:[1,8]},{7:[1,9]},t(r,[2,7],{10:10,11:[1,11]}),t(i,[2,6]),{6:12,7:e,9:6,12:n},{1:[2,1]},{7:[2,4],12:[1,15],13:13,14:14,15:[1,16],17:[1,17],19:[1,18],20:[1,19],21:[1,20]},t(i,[2,5]),{7:[1,21]},t(r,[2,8]),{12:[1,22]},t(r,[2,10]),{12:[2,16],16:23,23:[1,24]},{18:[1,25]},{18:[1,26]},{18:[1,27]},{18:[1,30],22:28,24:[1,29]},{1:[2,2]},t(r,[2,9]),{12:[2,11]},{12:[2,17]},{12:[2,12]},{12:[2,13]},{12:[2,14]},{12:[2,15]},{12:o,25:31,26:a},{12:o,25:33,26:a},{12:[2,18]},{12:o,25:34,26:a},{12:[2,19]},{12:[2,21]}],defaultActions:{9:[2,1],21:[2,2],23:[2,11],24:[2,17],25:[2,12],26:[2,13],27:[2,14],28:[2,15],31:[2,18],33:[2,19],34:[2,21]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],o=[],a=this.table,s="",u=0,c=0,f=0,l=2,h=1,d=o.slice.call(arguments,1),p=Object.create(this.lexer),g={yy:{}};for(var y in this.yy)Object.prototype.hasOwnProperty.call(this.yy,y)&&(g.yy[y]=this.yy[y]);p.setInput(t,g.yy),g.yy.lexer=p,g.yy.parser=this,void 0===p.yylloc&&(p.yylloc={});var b=p.yylloc;o.push(b);var m=p.options&&p.options.ranges;function v(){var t;return"number"!=typeof(t=r.pop()||p.lex()||h)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof g.yy.parseError?this.parseError=g.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var _,w,x,k,E,A,S,T,M,D={};;){if(x=n[n.length-1],this.defaultActions[x]?k=this.defaultActions[x]:(null==_&&(_=v()),k=a[x]&&a[x][_]),void 0===k||!k.length||!k[0]){var C="";for(A in M=[],a[x])this.terminals_[A]&&A>l&&M.push("'"+this.terminals_[A]+"'");C=p.showPosition?"Parse error on line "+(u+1)+":\n"+p.showPosition()+"\nExpecting "+M.join(", ")+", got '"+(this.terminals_[_]||_)+"'":"Parse error on line "+(u+1)+": Unexpected "+(_==h?"end of input":"'"+(this.terminals_[_]||_)+"'"),this.parseError(C,{text:p.match,token:this.terminals_[_]||_,line:p.yylineno,loc:b,expected:M})}if(k[0]instanceof Array&&k.length>1)throw new Error("Parse Error: multiple actions possible at state: "+x+", token: "+_);switch(k[0]){case 1:n.push(_),i.push(p.yytext),o.push(p.yylloc),n.push(k[1]),_=null,w?(_=w,w=null):(c=p.yyleng,s=p.yytext,u=p.yylineno,b=p.yylloc,f>0&&f--);break;case 2:if(S=this.productions_[k[1]][1],D.$=i[i.length-S],D._$={first_line:o[o.length-(S||1)].first_line,last_line:o[o.length-1].last_line,first_column:o[o.length-(S||1)].first_column,last_column:o[o.length-1].last_column},m&&(D._$.range=[o[o.length-(S||1)].range[0],o[o.length-1].range[1]]),void 0!==(E=this.performAction.apply(D,[s,c,u,g.yy,k[1],i,o].concat(d))))return E;S&&(n=n.slice(0,-1*S*2),i=i.slice(0,-1*S),o=o.slice(0,-1*S)),n.push(this.productions_[k[1]][0]),i.push(D.$),o.push(D._$),T=a[n[n.length-2]][n[n.length-1]],n.push(T);break;case 3:return!0}}return!0}},u={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var o in i)this[o]=i[o];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),o=0;oe[0].length)){if(e=n,r=o,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[o])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var t=this.next();return t||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{"case-insensitive":!0},performAction:function(t,e,n,r){switch(n){case 0:return 12;case 1:case 2:case 3:break;case 4:return 4;case 5:return 15;case 6:return 17;case 7:return 20;case 8:return 21;case 9:return 19;case 10:case 11:return 8;case 12:return 5;case 13:return 26;case 14:this.begin("options");break;case 15:this.popState();break;case 16:return 11;case 17:this.begin("string");break;case 18:this.popState();break;case 19:return 23;case 20:return 18;case 21:return 7}},rules:[/^(?:(\r?\n)+)/i,/^(?:\s+)/i,/^(?:#[^\n]*)/i,/^(?:%[^\n]*)/i,/^(?:gitGraph\b)/i,/^(?:commit\b)/i,/^(?:branch\b)/i,/^(?:merge\b)/i,/^(?:reset\b)/i,/^(?:checkout\b)/i,/^(?:LR\b)/i,/^(?:BT\b)/i,/^(?::)/i,/^(?:\^)/i,/^(?:options\r?\n)/i,/^(?:end\r?\n)/i,/^(?:[^\n]+\r?\n)/i,/^(?:["])/i,/^(?:["])/i,/^(?:[^"]*)/i,/^(?:[a-zA-Z][a-zA-Z0-9_]+)/i,/^(?:$)/i],conditions:{options:{rules:[15,16],inclusive:!1},string:{rules:[18,19],inclusive:!1},INITIAL:{rules:[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,17,20,21],inclusive:!0}}};function c(){this.yy={}}return s.lexer=u,c.prototype=s,s.Parser=c,new c}();e.parser=i,e.Parser=i.Parser,e.parse=function(){return i.parse.apply(i,arguments)},e.main=function(r){r[1]||(console.log("Usage: "+r[0]+" FILE"),t.exit(1));var i=n(28).readFileSync(n(29).normalize(r[1]),"utf8");return e.parser.parse(i)},n.c[n.s]===r&&e.main(t.argv.slice(1))}).call(this,n(7),n(9)(t))},function(t,e,n){(function(t,r){var i=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[6,9,10],n={trace:function(){},yy:{},symbols_:{error:2,start:3,info:4,document:5,EOF:6,line:7,statement:8,NL:9,showInfo:10,$accept:0,$end:1},terminals_:{2:"error",4:"info",6:"EOF",9:"NL",10:"showInfo"},productions_:[0,[3,3],[5,0],[5,2],[7,1],[7,1],[8,1]],performAction:function(t,e,n,r,i,o,a){o.length;switch(i){case 1:return r;case 4:break;case 6:r.setInfo(!0)}},table:[{3:1,4:[1,2]},{1:[3]},t(e,[2,2],{5:3}),{6:[1,4],7:5,8:6,9:[1,7],10:[1,8]},{1:[2,1]},t(e,[2,3]),t(e,[2,4]),t(e,[2,5]),t(e,[2,6])],defaultActions:{4:[2,1]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],o=[],a=this.table,s="",u=0,c=0,f=0,l=2,h=1,d=o.slice.call(arguments,1),p=Object.create(this.lexer),g={yy:{}};for(var y in this.yy)Object.prototype.hasOwnProperty.call(this.yy,y)&&(g.yy[y]=this.yy[y]);p.setInput(t,g.yy),g.yy.lexer=p,g.yy.parser=this,void 0===p.yylloc&&(p.yylloc={});var b=p.yylloc;o.push(b);var m=p.options&&p.options.ranges;function v(){var t;return"number"!=typeof(t=r.pop()||p.lex()||h)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof g.yy.parseError?this.parseError=g.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var _,w,x,k,E,A,S,T,M,D={};;){if(x=n[n.length-1],this.defaultActions[x]?k=this.defaultActions[x]:(null==_&&(_=v()),k=a[x]&&a[x][_]),void 0===k||!k.length||!k[0]){var C="";for(A in M=[],a[x])this.terminals_[A]&&A>l&&M.push("'"+this.terminals_[A]+"'");C=p.showPosition?"Parse error on line "+(u+1)+":\n"+p.showPosition()+"\nExpecting "+M.join(", ")+", got '"+(this.terminals_[_]||_)+"'":"Parse error on line "+(u+1)+": Unexpected "+(_==h?"end of input":"'"+(this.terminals_[_]||_)+"'"),this.parseError(C,{text:p.match,token:this.terminals_[_]||_,line:p.yylineno,loc:b,expected:M})}if(k[0]instanceof Array&&k.length>1)throw new Error("Parse Error: multiple actions possible at state: "+x+", token: "+_);switch(k[0]){case 1:n.push(_),i.push(p.yytext),o.push(p.yylloc),n.push(k[1]),_=null,w?(_=w,w=null):(c=p.yyleng,s=p.yytext,u=p.yylineno,b=p.yylloc,f>0&&f--);break;case 2:if(S=this.productions_[k[1]][1],D.$=i[i.length-S],D._$={first_line:o[o.length-(S||1)].first_line,last_line:o[o.length-1].last_line,first_column:o[o.length-(S||1)].first_column,last_column:o[o.length-1].last_column},m&&(D._$.range=[o[o.length-(S||1)].range[0],o[o.length-1].range[1]]),void 0!==(E=this.performAction.apply(D,[s,c,u,g.yy,k[1],i,o].concat(d))))return E;S&&(n=n.slice(0,-1*S*2),i=i.slice(0,-1*S),o=o.slice(0,-1*S)),n.push(this.productions_[k[1]][0]),i.push(D.$),o.push(D._$),T=a[n[n.length-2]][n[n.length-1]],n.push(T);break;case 3:return!0}}return!0}},r={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var o in i)this[o]=i[o];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),o=0;oe[0].length)){if(e=n,r=o,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[o])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var t=this.next();return t||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{"case-insensitive":!0},performAction:function(t,e,n,r){switch(n){case 0:return 4;case 1:return 9;case 2:return"space";case 3:return 10;case 4:return 6;case 5:return"TXT"}},rules:[/^(?:info\b)/i,/^(?:[\s\n\r]+)/i,/^(?:[\s]+)/i,/^(?:showInfo\b)/i,/^(?:$)/i,/^(?:.)/i],conditions:{INITIAL:{rules:[0,1,2,3,4,5],inclusive:!0}}};function i(){this.yy={}}return n.lexer=r,i.prototype=n,n.Parser=i,new i}();e.parser=i,e.Parser=i.Parser,e.parse=function(){return i.parse.apply(i,arguments)},e.main=function(r){r[1]||(console.log("Usage: "+r[0]+" FILE"),t.exit(1));var i=n(28).readFileSync(n(29).normalize(r[1]),"utf8");return e.parser.parse(i)},n.c[n.s]===r&&e.main(t.argv.slice(1))}).call(this,n(7),n(9)(t))},function(t,e,n){(function(t,r){var i=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[6,9,10,12],n={trace:function(){},yy:{},symbols_:{error:2,start:3,pie:4,document:5,EOF:6,line:7,statement:8,NL:9,STR:10,VALUE:11,title:12,$accept:0,$end:1},terminals_:{2:"error",4:"pie",6:"EOF",9:"NL",10:"STR",11:"VALUE",12:"title"},productions_:[0,[3,3],[5,0],[5,2],[7,1],[7,1],[8,2],[8,1]],performAction:function(t,e,n,r,i,o,a){var s=o.length-1;switch(i){case 4:break;case 6:console.log("str:"+o[s-1]+" value: "+o[s]),r.addSection(o[s-1],r.cleanupValue(o[s]));break;case 7:r.setTitle(o[s].substr(6)),this.$=o[s].substr(6)}},table:[{3:1,4:[1,2]},{1:[3]},t(e,[2,2],{5:3}),{6:[1,4],7:5,8:6,9:[1,7],10:[1,8],12:[1,9]},{1:[2,1]},t(e,[2,3]),t(e,[2,4]),t(e,[2,5]),{11:[1,10]},t(e,[2,7]),t(e,[2,6])],defaultActions:{4:[2,1]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],o=[],a=this.table,s="",u=0,c=0,f=0,l=2,h=1,d=o.slice.call(arguments,1),p=Object.create(this.lexer),g={yy:{}};for(var y in this.yy)Object.prototype.hasOwnProperty.call(this.yy,y)&&(g.yy[y]=this.yy[y]);p.setInput(t,g.yy),g.yy.lexer=p,g.yy.parser=this,void 0===p.yylloc&&(p.yylloc={});var b=p.yylloc;o.push(b);var m=p.options&&p.options.ranges;function v(){var t;return"number"!=typeof(t=r.pop()||p.lex()||h)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof g.yy.parseError?this.parseError=g.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var _,w,x,k,E,A,S,T,M,D={};;){if(x=n[n.length-1],this.defaultActions[x]?k=this.defaultActions[x]:(null==_&&(_=v()),k=a[x]&&a[x][_]),void 0===k||!k.length||!k[0]){var C="";for(A in M=[],a[x])this.terminals_[A]&&A>l&&M.push("'"+this.terminals_[A]+"'");C=p.showPosition?"Parse error on line "+(u+1)+":\n"+p.showPosition()+"\nExpecting "+M.join(", ")+", got '"+(this.terminals_[_]||_)+"'":"Parse error on line "+(u+1)+": Unexpected "+(_==h?"end of input":"'"+(this.terminals_[_]||_)+"'"),this.parseError(C,{text:p.match,token:this.terminals_[_]||_,line:p.yylineno,loc:b,expected:M})}if(k[0]instanceof Array&&k.length>1)throw new Error("Parse Error: multiple actions possible at state: "+x+", token: "+_);switch(k[0]){case 1:n.push(_),i.push(p.yytext),o.push(p.yylloc),n.push(k[1]),_=null,w?(_=w,w=null):(c=p.yyleng,s=p.yytext,u=p.yylineno,b=p.yylloc,f>0&&f--);break;case 2:if(S=this.productions_[k[1]][1],D.$=i[i.length-S],D._$={first_line:o[o.length-(S||1)].first_line,last_line:o[o.length-1].last_line,first_column:o[o.length-(S||1)].first_column,last_column:o[o.length-1].last_column},m&&(D._$.range=[o[o.length-(S||1)].range[0],o[o.length-1].range[1]]),void 0!==(E=this.performAction.apply(D,[s,c,u,g.yy,k[1],i,o].concat(d))))return E;S&&(n=n.slice(0,-1*S*2),i=i.slice(0,-1*S),o=o.slice(0,-1*S)),n.push(this.productions_[k[1]][0]),i.push(D.$),o.push(D._$),T=a[n[n.length-2]][n[n.length-1]],n.push(T);break;case 3:return!0}}return!0}},r={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var o in i)this[o]=i[o];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),o=0;oe[0].length)){if(e=n,r=o,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[o])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var t=this.next();return t||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{"case-insensitive":!0},performAction:function(t,e,n,r){switch(n){case 0:case 1:break;case 2:return 4;case 3:return 9;case 4:return"space";case 5:return 12;case 6:this.begin("string");break;case 7:this.popState();break;case 8:return"STR";case 9:return"VALUE";case 10:return 6}},rules:[/^(?:%%[^\n]*)/i,/^(?:\s+)/i,/^(?:pie\b)/i,/^(?:[\s\n\r]+)/i,/^(?:[\s]+)/i,/^(?:title\s[^#\n;]+)/i,/^(?:["])/i,/^(?:["])/i,/^(?:[^"]*)/i,/^(?::[\s]*[\d]+(?:\.[\d]+)?)/i,/^(?:$)/i],conditions:{string:{rules:[7,8],inclusive:!1},INITIAL:{rules:[0,1,2,3,4,5,6,9,10],inclusive:!0}}};function i(){this.yy={}}return n.lexer=r,i.prototype=n,n.Parser=i,new i}();e.parser=i,e.Parser=i.Parser,e.parse=function(){return i.parse.apply(i,arguments)},e.main=function(r){r[1]||(console.log("Usage: "+r[0]+" FILE"),t.exit(1));var i=n(28).readFileSync(n(29).normalize(r[1]),"utf8");return e.parser.parse(i)},n.c[n.s]===r&&e.main(t.argv.slice(1))}).call(this,n(7),n(9)(t))},function(t){t.exports=JSON.parse('{"name":"mermaid","version":"8.4.3","description":"Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.","main":"dist/mermaid.core.js","keywords":["diagram","markdown","flowchart","sequence diagram","gantt","class diagram","git graph"],"scripts":{"build":"webpack --progress --colors","postbuild":"documentation build src/mermaidAPI.js --shallow -f md --markdown-toc false -o docs/mermaidAPI.md","build:watch":"yarn build --watch","minify":"minify ./dist/mermaid.js > ./dist/mermaid.min.js","release":"yarn build -p --config webpack.config.prod.babel.js","lint":"eslint src","e2e:depr":"yarn lint && jest e2e --config e2e/jest.config.js","cypress":"percy exec -- cypress run","e2e":"start-server-and-test dev http://localhost:9000/ cypress","e2e-upd":"yarn lint && jest e2e -u --config e2e/jest.config.js","dev":"webpack-dev-server --config webpack.config.e2e.js","test":"yarn lint && jest src/.*","test:watch":"jest --watch src","prepublishOnly":"yarn build && yarn release && yarn test && yarn e2e","prepush":"yarn test"},"repository":{"type":"git","url":"https://github.com/knsv/mermaid"},"author":"Knut Sveidqvist","license":"MIT","standard":{"ignore":["**/parser/*.js","dist/**/*.js","cypress/**/*.js"],"globals":["page"]},"dependencies":{"@braintree/sanitize-url":"^3.1.0","crypto-random-string":"^3.0.1","d3":"^5.7.0","dagre-d3-unofficial":"0.6.4","dagre":"^0.8.4","graphlib":"^2.1.7","he":"^1.2.0","lodash":"^4.17.11","minify":"^4.1.1","moment-mini":"^2.22.1","scope-css":"^1.2.1"},"devDependencies":{"documentation":"^12.0.1","prettier":"^1.18.2","eslint":"^6.3.0","eslint-config-prettier":"^6.3.0","eslint-plugin-prettier":"^3.1.0","@babel/core":"^7.2.2","@babel/preset-env":"^7.2.0","@babel/register":"^7.0.0","@percy/cypress":"^2.0.1","babel-core":"7.0.0-bridge.0","babel-jest":"^23.6.0","babel-loader":"^8.0.4","coveralls":"^3.0.2","css-loader":"^2.0.1","css-to-string-loader":"^0.1.3","cypress":"3.4.0","husky":"^1.2.1","identity-obj-proxy":"^3.0.0","jest":"^24.9.0","jison":"^0.4.18","moment":"^2.23.0","node-sass":"^4.12.0","puppeteer":"^1.17.0","sass-loader":"^7.1.0","start-server-and-test":"^1.10.0","webpack":"^4.27.1","webpack-cli":"^3.1.2","webpack-dev-server":"^3.4.1","webpack-node-externals":"^1.7.2","yarn-upgrade-all":"^0.5.0"},"files":["dist"],"yarn-upgrade-all":{"ignore":["babel-core"]}}')},function(t,e,n){"use strict";var r=n(12);t.exports=s;var i="\0",o="\0",a="";function s(t){this._isDirected=!r.has(t,"directed")||t.directed,this._isMultigraph=!!r.has(t,"multigraph")&&t.multigraph,this._isCompound=!!r.has(t,"compound")&&t.compound,this._label=void 0,this._defaultNodeLabelFn=r.constant(void 0),this._defaultEdgeLabelFn=r.constant(void 0),this._nodes={},this._isCompound&&(this._parent={},this._children={},this._children[o]={}),this._in={},this._preds={},this._out={},this._sucs={},this._edgeObjs={},this._edgeLabels={}}function u(t,e){t[e]?t[e]++:t[e]=1}function c(t,e){--t[e]||delete t[e]}function f(t,e,n,o){var s=""+e,u=""+n;if(!t&&s>u){var c=s;s=u,u=c}return s+a+u+a+(r.isUndefined(o)?i:o)}function l(t,e,n,r){var i=""+e,o=""+n;if(!t&&i>o){var a=i;i=o,o=a}var s={v:i,w:o};return r&&(s.name=r),s}function h(t,e){return f(t,e.v,e.w,e.name)}s.prototype._nodeCount=0,s.prototype._edgeCount=0,s.prototype.isDirected=function(){return this._isDirected},s.prototype.isMultigraph=function(){return this._isMultigraph},s.prototype.isCompound=function(){return this._isCompound},s.prototype.setGraph=function(t){return this._label=t,this},s.prototype.graph=function(){return this._label},s.prototype.setDefaultNodeLabel=function(t){return r.isFunction(t)||(t=r.constant(t)),this._defaultNodeLabelFn=t,this},s.prototype.nodeCount=function(){return this._nodeCount},s.prototype.nodes=function(){return r.keys(this._nodes)},s.prototype.sources=function(){var t=this;return r.filter(this.nodes(),(function(e){return r.isEmpty(t._in[e])}))},s.prototype.sinks=function(){var t=this;return r.filter(this.nodes(),(function(e){return r.isEmpty(t._out[e])}))},s.prototype.setNodes=function(t,e){var n=arguments,i=this;return r.each(t,(function(t){n.length>1?i.setNode(t,e):i.setNode(t)})),this},s.prototype.setNode=function(t,e){return r.has(this._nodes,t)?(arguments.length>1&&(this._nodes[t]=e),this):(this._nodes[t]=arguments.length>1?e:this._defaultNodeLabelFn(t),this._isCompound&&(this._parent[t]=o,this._children[t]={},this._children[o][t]=!0),this._in[t]={},this._preds[t]={},this._out[t]={},this._sucs[t]={},++this._nodeCount,this)},s.prototype.node=function(t){return this._nodes[t]},s.prototype.hasNode=function(t){return r.has(this._nodes,t)},s.prototype.removeNode=function(t){var e=this;if(r.has(this._nodes,t)){var n=function(t){e.removeEdge(e._edgeObjs[t])};delete this._nodes[t],this._isCompound&&(this._removeFromParentsChildList(t),delete this._parent[t],r.each(this.children(t),(function(t){e.setParent(t)})),delete this._children[t]),r.each(r.keys(this._in[t]),n),delete this._in[t],delete this._preds[t],r.each(r.keys(this._out[t]),n),delete this._out[t],delete this._sucs[t],--this._nodeCount}return this},s.prototype.setParent=function(t,e){if(!this._isCompound)throw new Error("Cannot set parent in a non-compound graph");if(r.isUndefined(e))e=o;else{for(var n=e+="";!r.isUndefined(n);n=this.parent(n))if(n===t)throw new Error("Setting "+e+" as parent of "+t+" would create a cycle");this.setNode(e)}return this.setNode(t),this._removeFromParentsChildList(t),this._parent[t]=e,this._children[e][t]=!0,this},s.prototype._removeFromParentsChildList=function(t){delete this._children[this._parent[t]][t]},s.prototype.parent=function(t){if(this._isCompound){var e=this._parent[t];if(e!==o)return e}},s.prototype.children=function(t){if(r.isUndefined(t)&&(t=o),this._isCompound){var e=this._children[t];if(e)return r.keys(e)}else{if(t===o)return this.nodes();if(this.hasNode(t))return[]}},s.prototype.predecessors=function(t){var e=this._preds[t];if(e)return r.keys(e)},s.prototype.successors=function(t){var e=this._sucs[t];if(e)return r.keys(e)},s.prototype.neighbors=function(t){var e=this.predecessors(t);if(e)return r.union(e,this.successors(t))},s.prototype.isLeaf=function(t){return 0===(this.isDirected()?this.successors(t):this.neighbors(t)).length},s.prototype.filterNodes=function(t){var e=new this.constructor({directed:this._isDirected,multigraph:this._isMultigraph,compound:this._isCompound});e.setGraph(this.graph());var n=this;r.each(this._nodes,(function(n,r){t(r)&&e.setNode(r,n)})),r.each(this._edgeObjs,(function(t){e.hasNode(t.v)&&e.hasNode(t.w)&&e.setEdge(t,n.edge(t))}));var i={};return this._isCompound&&r.each(e.nodes(),(function(t){e.setParent(t,function t(r){var o=n.parent(r);return void 0===o||e.hasNode(o)?(i[r]=o,o):o in i?i[o]:t(o)}(t))})),e},s.prototype.setDefaultEdgeLabel=function(t){return r.isFunction(t)||(t=r.constant(t)),this._defaultEdgeLabelFn=t,this},s.prototype.edgeCount=function(){return this._edgeCount},s.prototype.edges=function(){return r.values(this._edgeObjs)},s.prototype.setPath=function(t,e){var n=this,i=arguments;return r.reduce(t,(function(t,r){return i.length>1?n.setEdge(t,r,e):n.setEdge(t,r),r})),this},s.prototype.setEdge=function(){var t,e,n,i,o=!1,a=arguments[0];"object"==typeof a&&null!==a&&"v"in a?(t=a.v,e=a.w,n=a.name,2===arguments.length&&(i=arguments[1],o=!0)):(t=a,e=arguments[1],n=arguments[3],arguments.length>2&&(i=arguments[2],o=!0)),t=""+t,e=""+e,r.isUndefined(n)||(n=""+n);var s=f(this._isDirected,t,e,n);if(r.has(this._edgeLabels,s))return o&&(this._edgeLabels[s]=i),this;if(!r.isUndefined(n)&&!this._isMultigraph)throw new Error("Cannot set a named edge when isMultigraph = false");this.setNode(t),this.setNode(e),this._edgeLabels[s]=o?i:this._defaultEdgeLabelFn(t,e,n);var c=l(this._isDirected,t,e,n);return t=c.v,e=c.w,Object.freeze(c),this._edgeObjs[s]=c,u(this._preds[e],t),u(this._sucs[t],e),this._in[e][s]=c,this._out[t][s]=c,this._edgeCount++,this},s.prototype.edge=function(t,e,n){var r=1===arguments.length?h(this._isDirected,arguments[0]):f(this._isDirected,t,e,n);return this._edgeLabels[r]},s.prototype.hasEdge=function(t,e,n){var i=1===arguments.length?h(this._isDirected,arguments[0]):f(this._isDirected,t,e,n);return r.has(this._edgeLabels,i)},s.prototype.removeEdge=function(t,e,n){var r=1===arguments.length?h(this._isDirected,arguments[0]):f(this._isDirected,t,e,n),i=this._edgeObjs[r];return i&&(t=i.v,e=i.w,delete this._edgeLabels[r],delete this._edgeObjs[r],c(this._preds[e],t),c(this._sucs[t],e),delete this._in[e][r],delete this._out[t][r],this._edgeCount--),this},s.prototype.inEdges=function(t,e){var n=this._in[t];if(n){var i=r.values(n);return e?r.filter(i,(function(t){return t.v===e})):i}},s.prototype.outEdges=function(t,e){var n=this._out[t];if(n){var i=r.values(n);return e?r.filter(i,(function(t){return t.w===e})):i}},s.prototype.nodeEdges=function(t,e){var n=this.inEdges(t,e);if(n)return n.concat(this.outEdges(t,e))}},function(t,e,n){var r=n(32)(n(18),"Map");t.exports=r},function(t,e,n){var r=n(252),i=n(259),o=n(261),a=n(262),s=n(263);function u(t){var e=-1,n=null==t?0:t.length;for(this.clear();++e-1&&t%1==0&&t<=n}},function(t,e,n){(function(t){var r=n(131),i=e&&!e.nodeType&&e,o=i&&"object"==typeof t&&t&&!t.nodeType&&t,a=o&&o.exports===i&&r.process,s=function(){try{var t=o&&o.require&&o.require("util").types;return t||a&&a.binding&&a.binding("util")}catch(t){}}();t.exports=s}).call(this,n(9)(t))},function(t,e,n){var r=n(70),i=n(269),o=Object.prototype.hasOwnProperty;t.exports=function(t){if(!r(t))return i(t);var e=[];for(var n in Object(t))o.call(t,n)&&"constructor"!=n&&e.push(n);return e}},function(t,e,n){var r=n(138),i=n(139),o=Object.prototype.propertyIsEnumerable,a=Object.getOwnPropertySymbols,s=a?function(t){return null==t?[]:(t=Object(t),r(a(t),(function(e){return o.call(t,e)})))}:i;t.exports=s},function(t,e){t.exports=function(t,e){for(var n=-1,r=e.length,i=t.length;++n0&&o(f)?n>1?t(f,n-1,o,a,s):r(s,f):a||(s[s.length]=f)}return s}},function(t,e,n){var r=n(42);t.exports=function(t,e,n){for(var i=-1,o=t.length;++i>>32-e}function c(t,e,n,r,i,o,a){return u(t+(e&n|~e&r)+i+o|0,a)+e|0}function f(t,e,n,r,i,o,a){return u(t+(e&r|n&~r)+i+o|0,a)+e|0}function l(t,e,n,r,i,o,a){return u(t+(e^n^r)+i+o|0,a)+e|0}function h(t,e,n,r,i,o,a){return u(t+(n^(e|~r))+i+o|0,a)+e|0}r(s,i),s.prototype._update=function(){for(var t=a,e=0;e<16;++e)t[e]=this._block.readInt32LE(4*e);var n=this._a,r=this._b,i=this._c,o=this._d;n=c(n,r,i,o,t[0],3614090360,7),o=c(o,n,r,i,t[1],3905402710,12),i=c(i,o,n,r,t[2],606105819,17),r=c(r,i,o,n,t[3],3250441966,22),n=c(n,r,i,o,t[4],4118548399,7),o=c(o,n,r,i,t[5],1200080426,12),i=c(i,o,n,r,t[6],2821735955,17),r=c(r,i,o,n,t[7],4249261313,22),n=c(n,r,i,o,t[8],1770035416,7),o=c(o,n,r,i,t[9],2336552879,12),i=c(i,o,n,r,t[10],4294925233,17),r=c(r,i,o,n,t[11],2304563134,22),n=c(n,r,i,o,t[12],1804603682,7),o=c(o,n,r,i,t[13],4254626195,12),i=c(i,o,n,r,t[14],2792965006,17),n=f(n,r=c(r,i,o,n,t[15],1236535329,22),i,o,t[1],4129170786,5),o=f(o,n,r,i,t[6],3225465664,9),i=f(i,o,n,r,t[11],643717713,14),r=f(r,i,o,n,t[0],3921069994,20),n=f(n,r,i,o,t[5],3593408605,5),o=f(o,n,r,i,t[10],38016083,9),i=f(i,o,n,r,t[15],3634488961,14),r=f(r,i,o,n,t[4],3889429448,20),n=f(n,r,i,o,t[9],568446438,5),o=f(o,n,r,i,t[14],3275163606,9),i=f(i,o,n,r,t[3],4107603335,14),r=f(r,i,o,n,t[8],1163531501,20),n=f(n,r,i,o,t[13],2850285829,5),o=f(o,n,r,i,t[2],4243563512,9),i=f(i,o,n,r,t[7],1735328473,14),n=l(n,r=f(r,i,o,n,t[12],2368359562,20),i,o,t[5],4294588738,4),o=l(o,n,r,i,t[8],2272392833,11),i=l(i,o,n,r,t[11],1839030562,16),r=l(r,i,o,n,t[14],4259657740,23),n=l(n,r,i,o,t[1],2763975236,4),o=l(o,n,r,i,t[4],1272893353,11),i=l(i,o,n,r,t[7],4139469664,16),r=l(r,i,o,n,t[10],3200236656,23),n=l(n,r,i,o,t[13],681279174,4),o=l(o,n,r,i,t[0],3936430074,11),i=l(i,o,n,r,t[3],3572445317,16),r=l(r,i,o,n,t[6],76029189,23),n=l(n,r,i,o,t[9],3654602809,4),o=l(o,n,r,i,t[12],3873151461,11),i=l(i,o,n,r,t[15],530742520,16),n=h(n,r=l(r,i,o,n,t[2],3299628645,23),i,o,t[0],4096336452,6),o=h(o,n,r,i,t[7],1126891415,10),i=h(i,o,n,r,t[14],2878612391,15),r=h(r,i,o,n,t[5],4237533241,21),n=h(n,r,i,o,t[12],1700485571,6),o=h(o,n,r,i,t[3],2399980690,10),i=h(i,o,n,r,t[10],4293915773,15),r=h(r,i,o,n,t[1],2240044497,21),n=h(n,r,i,o,t[8],1873313359,6),o=h(o,n,r,i,t[15],4264355552,10),i=h(i,o,n,r,t[6],2734768916,15),r=h(r,i,o,n,t[13],1309151649,21),n=h(n,r,i,o,t[4],4149444226,6),o=h(o,n,r,i,t[11],3174756917,10),i=h(i,o,n,r,t[2],718787259,15),r=h(r,i,o,n,t[9],3951481745,21),this._a=this._a+n|0,this._b=this._b+r|0,this._c=this._c+i|0,this._d=this._d+o|0},s.prototype._digest=function(){this._block[this._blockOffset++]=128,this._blockOffset>56&&(this._block.fill(0,this._blockOffset,64),this._update(),this._blockOffset=0),this._block.fill(0,this._blockOffset,56),this._block.writeUInt32LE(this._length[0],56),this._block.writeUInt32LE(this._length[1],60),this._update();var t=o.allocUnsafe(16);return t.writeInt32LE(this._a,0),t.writeInt32LE(this._b,4),t.writeInt32LE(this._c,8),t.writeInt32LE(this._d,12),t},t.exports=s},function(t,e,n){t.exports=i;var r=n(113).EventEmitter;function i(){r.call(this)}n(2)(i,r),i.Readable=n(114),i.Writable=n(428),i.Duplex=n(429),i.Transform=n(430),i.PassThrough=n(431),i.Stream=i,i.prototype.pipe=function(t,e){var n=this;function i(e){t.writable&&!1===t.write(e)&&n.pause&&n.pause()}function o(){n.readable&&n.resume&&n.resume()}n.on("data",i),t.on("drain",o),t._isStdio||e&&!1===e.end||(n.on("end",s),n.on("close",u));var a=!1;function s(){a||(a=!0,t.end())}function u(){a||(a=!0,"function"==typeof t.destroy&&t.destroy())}function c(t){if(f(),0===r.listenerCount(this,"error"))throw t}function f(){n.removeListener("data",i),t.removeListener("drain",o),n.removeListener("end",s),n.removeListener("close",u),n.removeListener("error",c),t.removeListener("error",c),n.removeListener("end",f),n.removeListener("close",f),t.removeListener("close",f)}return n.on("error",c),t.on("error",c),n.on("end",f),n.on("close",f),t.on("close",f),t.emit("pipe",n),t}},function(t,e,n){"use strict";var r,i="object"==typeof Reflect?Reflect:null,o=i&&"function"==typeof i.apply?i.apply:function(t,e,n){return Function.prototype.apply.call(t,e,n)};r=i&&"function"==typeof i.ownKeys?i.ownKeys:Object.getOwnPropertySymbols?function(t){return Object.getOwnPropertyNames(t).concat(Object.getOwnPropertySymbols(t))}:function(t){return Object.getOwnPropertyNames(t)};var a=Number.isNaN||function(t){return t!=t};function s(){s.init.call(this)}t.exports=s,s.EventEmitter=s,s.prototype._events=void 0,s.prototype._eventsCount=0,s.prototype._maxListeners=void 0;var u=10;function c(t){return void 0===t._maxListeners?s.defaultMaxListeners:t._maxListeners}function f(t,e,n,r){var i,o,a,s;if("function"!=typeof n)throw new TypeError('The "listener" argument must be of type Function. Received type '+typeof n);if(void 0===(o=t._events)?(o=t._events=Object.create(null),t._eventsCount=0):(void 0!==o.newListener&&(t.emit("newListener",e,n.listener?n.listener:n),o=t._events),a=o[e]),void 0===a)a=o[e]=n,++t._eventsCount;else if("function"==typeof a?a=o[e]=r?[n,a]:[a,n]:r?a.unshift(n):a.push(n),(i=c(t))>0&&a.length>i&&!a.warned){a.warned=!0;var u=new Error("Possible EventEmitter memory leak detected. "+a.length+" "+String(e)+" listeners added. Use emitter.setMaxListeners() to increase limit");u.name="MaxListenersExceededWarning",u.emitter=t,u.type=e,u.count=a.length,s=u,console&&console.warn&&console.warn(s)}return t}function l(){for(var t=[],e=0;e0&&(a=e[0]),a instanceof Error)throw a;var s=new Error("Unhandled error."+(a?" ("+a.message+")":""));throw s.context=a,s}var u=i[t];if(void 0===u)return!1;if("function"==typeof u)o(u,this,e);else{var c=u.length,f=g(u,c);for(n=0;n=0;o--)if(n[o]===e||n[o].listener===e){a=n[o].listener,i=o;break}if(i<0)return this;0===i?n.shift():function(t,e){for(;e+1=0;r--)this.removeListener(t,e[r]);return this},s.prototype.listeners=function(t){return d(this,t,!0)},s.prototype.rawListeners=function(t){return d(this,t,!1)},s.listenerCount=function(t,e){return"function"==typeof t.listenerCount?t.listenerCount(e):p.call(t,e)},s.prototype.listenerCount=p,s.prototype.eventNames=function(){return this._eventsCount>0?r(this._events):[]}},function(t,e,n){(e=t.exports=n(193)).Stream=e,e.Readable=e,e.Writable=n(116),e.Duplex=n(35),e.Transform=n(196),e.PassThrough=n(427)},function(t,e,n){var r=n(8),i=r.Buffer;function o(t,e){for(var n in t)e[n]=t[n]}function a(t,e,n){return i(t,e,n)}i.from&&i.alloc&&i.allocUnsafe&&i.allocUnsafeSlow?t.exports=r:(o(r,e),e.Buffer=a),o(i,a),a.from=function(t,e,n){if("number"==typeof t)throw new TypeError("Argument must not be a number");return i(t,e,n)},a.alloc=function(t,e,n){if("number"!=typeof t)throw new TypeError("Argument must be a number");var r=i(t);return void 0!==e?"string"==typeof n?r.fill(e,n):r.fill(e):r.fill(0),r},a.allocUnsafe=function(t){if("number"!=typeof t)throw new TypeError("Argument must be a number");return i(t)},a.allocUnsafeSlow=function(t){if("number"!=typeof t)throw new TypeError("Argument must be a number");return r.SlowBuffer(t)}},function(t,e,n){"use strict";(function(e,r,i){var o=n(78);function a(t){var e=this;this.next=null,this.entry=null,this.finish=function(){!function(t,e,n){var r=t.entry;t.entry=null;for(;r;){var i=r.callback;e.pendingcb--,i(n),r=r.next}e.corkedRequestsFree?e.corkedRequestsFree.next=t:e.corkedRequestsFree=t}(e,t)}}t.exports=m;var s,u=!e.browser&&["v0.10","v0.9."].indexOf(e.version.slice(0,5))>-1?r:o.nextTick;m.WritableState=b;var c=n(54);c.inherits=n(2);var f={deprecate:n(426)},l=n(194),h=n(115).Buffer,d=i.Uint8Array||function(){};var p,g=n(195);function y(){}function b(t,e){s=s||n(35),t=t||{};var r=e instanceof s;this.objectMode=!!t.objectMode,r&&(this.objectMode=this.objectMode||!!t.writableObjectMode);var i=t.highWaterMark,c=t.writableHighWaterMark,f=this.objectMode?16:16384;this.highWaterMark=i||0===i?i:r&&(c||0===c)?c:f,this.highWaterMark=Math.floor(this.highWaterMark),this.finalCalled=!1,this.needDrain=!1,this.ending=!1,this.ended=!1,this.finished=!1,this.destroyed=!1;var l=!1===t.decodeStrings;this.decodeStrings=!l,this.defaultEncoding=t.defaultEncoding||"utf8",this.length=0,this.writing=!1,this.corked=0,this.sync=!0,this.bufferProcessing=!1,this.onwrite=function(t){!function(t,e){var n=t._writableState,r=n.sync,i=n.writecb;if(function(t){t.writing=!1,t.writecb=null,t.length-=t.writelen,t.writelen=0}(n),e)!function(t,e,n,r,i){--e.pendingcb,n?(o.nextTick(i,r),o.nextTick(E,t,e),t._writableState.errorEmitted=!0,t.emit("error",r)):(i(r),t._writableState.errorEmitted=!0,t.emit("error",r),E(t,e))}(t,n,r,e,i);else{var a=x(n);a||n.corked||n.bufferProcessing||!n.bufferedRequest||w(t,n),r?u(_,t,n,a,i):_(t,n,a,i)}}(e,t)},this.writecb=null,this.writelen=0,this.bufferedRequest=null,this.lastBufferedRequest=null,this.pendingcb=0,this.prefinished=!1,this.errorEmitted=!1,this.bufferedRequestCount=0,this.corkedRequestsFree=new a(this)}function m(t){if(s=s||n(35),!(p.call(m,this)||this instanceof s))return new m(t);this._writableState=new b(t,this),this.writable=!0,t&&("function"==typeof t.write&&(this._write=t.write),"function"==typeof t.writev&&(this._writev=t.writev),"function"==typeof t.destroy&&(this._destroy=t.destroy),"function"==typeof t.final&&(this._final=t.final)),l.call(this)}function v(t,e,n,r,i,o,a){e.writelen=r,e.writecb=a,e.writing=!0,e.sync=!0,n?t._writev(i,e.onwrite):t._write(i,o,e.onwrite),e.sync=!1}function _(t,e,n,r){n||function(t,e){0===e.length&&e.needDrain&&(e.needDrain=!1,t.emit("drain"))}(t,e),e.pendingcb--,r(),E(t,e)}function w(t,e){e.bufferProcessing=!0;var n=e.bufferedRequest;if(t._writev&&n&&n.next){var r=e.bufferedRequestCount,i=new Array(r),o=e.corkedRequestsFree;o.entry=n;for(var s=0,u=!0;n;)i[s]=n,n.isBuf||(u=!1),n=n.next,s+=1;i.allBuffers=u,v(t,e,!0,e.length,i,"",o.finish),e.pendingcb++,e.lastBufferedRequest=null,o.next?(e.corkedRequestsFree=o.next,o.next=null):e.corkedRequestsFree=new a(e),e.bufferedRequestCount=0}else{for(;n;){var c=n.chunk,f=n.encoding,l=n.callback;if(v(t,e,!1,e.objectMode?1:c.length,c,f,l),n=n.next,e.bufferedRequestCount--,e.writing)break}null===n&&(e.lastBufferedRequest=null)}e.bufferedRequest=n,e.bufferProcessing=!1}function x(t){return t.ending&&0===t.length&&null===t.bufferedRequest&&!t.finished&&!t.writing}function k(t,e){t._final((function(n){e.pendingcb--,n&&t.emit("error",n),e.prefinished=!0,t.emit("prefinish"),E(t,e)}))}function E(t,e){var n=x(e);return n&&(!function(t,e){e.prefinished||e.finalCalled||("function"==typeof t._final?(e.pendingcb++,e.finalCalled=!0,o.nextTick(k,t,e)):(e.prefinished=!0,t.emit("prefinish")))}(t,e),0===e.pendingcb&&(e.finished=!0,t.emit("finish"))),n}c.inherits(m,l),b.prototype.getBuffer=function(){for(var t=this.bufferedRequest,e=[];t;)e.push(t),t=t.next;return e},function(){try{Object.defineProperty(b.prototype,"buffer",{get:f.deprecate((function(){return this.getBuffer()}),"_writableState.buffer is deprecated. Use _writableState.getBuffer instead.","DEP0003")})}catch(t){}}(),"function"==typeof Symbol&&Symbol.hasInstance&&"function"==typeof Function.prototype[Symbol.hasInstance]?(p=Function.prototype[Symbol.hasInstance],Object.defineProperty(m,Symbol.hasInstance,{value:function(t){return!!p.call(this,t)||this===m&&(t&&t._writableState instanceof b)}})):p=function(t){return t instanceof this},m.prototype.pipe=function(){this.emit("error",new Error("Cannot pipe, not readable"))},m.prototype.write=function(t,e,n){var r,i=this._writableState,a=!1,s=!i.objectMode&&(r=t,h.isBuffer(r)||r instanceof d);return s&&!h.isBuffer(t)&&(t=function(t){return h.from(t)}(t)),"function"==typeof e&&(n=e,e=null),s?e="buffer":e||(e=i.defaultEncoding),"function"!=typeof n&&(n=y),i.ended?function(t,e){var n=new Error("write after end");t.emit("error",n),o.nextTick(e,n)}(this,n):(s||function(t,e,n,r){var i=!0,a=!1;return null===n?a=new TypeError("May not write null values to stream"):"string"==typeof n||void 0===n||e.objectMode||(a=new TypeError("Invalid non-string/buffer chunk")),a&&(t.emit("error",a),o.nextTick(r,a),i=!1),i}(this,i,t,n))&&(i.pendingcb++,a=function(t,e,n,r,i,o){if(!n){var a=function(t,e,n){t.objectMode||!1===t.decodeStrings||"string"!=typeof e||(e=h.from(e,n));return e}(e,r,i);r!==a&&(n=!0,i="buffer",r=a)}var s=e.objectMode?1:r.length;e.length+=s;var u=e.length-1))throw new TypeError("Unknown encoding: "+t);return this._writableState.defaultEncoding=t,this},Object.defineProperty(m.prototype,"writableHighWaterMark",{enumerable:!1,get:function(){return this._writableState.highWaterMark}}),m.prototype._write=function(t,e,n){n(new Error("_write() is not implemented"))},m.prototype._writev=null,m.prototype.end=function(t,e,n){var r=this._writableState;"function"==typeof t?(n=t,t=null,e=null):"function"==typeof e&&(n=e,e=null),null!=t&&this.write(t,e),r.corked&&(r.corked=1,this.uncork()),r.ending||r.finished||function(t,e,n){e.ending=!0,E(t,e),n&&(e.finished?o.nextTick(n):t.once("finish",n));e.ended=!0,t.writable=!1}(this,r,n)},Object.defineProperty(m.prototype,"destroyed",{get:function(){return void 0!==this._writableState&&this._writableState.destroyed},set:function(t){this._writableState&&(this._writableState.destroyed=t)}}),m.prototype.destroy=g.destroy,m.prototype._undestroy=g.undestroy,m.prototype._destroy=function(t,e){this.end(),e(t)}}).call(this,n(7),n(424).setImmediate,n(11))},function(t,e,n){"use strict";var r=n(3).Buffer,i=r.isEncoding||function(t){switch((t=""+t)&&t.toLowerCase()){case"hex":case"utf8":case"utf-8":case"ascii":case"binary":case"base64":case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":case"raw":return!0;default:return!1}};function o(t){var e;switch(this.encoding=function(t){var e=function(t){if(!t)return"utf8";for(var e;;)switch(t){case"utf8":case"utf-8":return"utf8";case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return"utf16le";case"latin1":case"binary":return"latin1";case"base64":case"ascii":case"hex":return t;default:if(e)return;t=(""+t).toLowerCase(),e=!0}}(t);if("string"!=typeof e&&(r.isEncoding===i||!i(t)))throw new Error("Unknown encoding: "+t);return e||t}(t),this.encoding){case"utf16le":this.text=u,this.end=c,e=4;break;case"utf8":this.fillLast=s,e=4;break;case"base64":this.text=f,this.end=l,e=3;break;default:return this.write=h,void(this.end=d)}this.lastNeed=0,this.lastTotal=0,this.lastChar=r.allocUnsafe(e)}function a(t){return t<=127?0:t>>5==6?2:t>>4==14?3:t>>3==30?4:t>>6==2?-1:-2}function s(t){var e=this.lastTotal-this.lastNeed,n=function(t,e,n){if(128!=(192&e[0]))return t.lastNeed=0,"�";if(t.lastNeed>1&&e.length>1){if(128!=(192&e[1]))return t.lastNeed=1,"�";if(t.lastNeed>2&&e.length>2&&128!=(192&e[2]))return t.lastNeed=2,"�"}}(this,t);return void 0!==n?n:this.lastNeed<=t.length?(t.copy(this.lastChar,e,0,this.lastNeed),this.lastChar.toString(this.encoding,0,this.lastTotal)):(t.copy(this.lastChar,e,0,t.length),void(this.lastNeed-=t.length))}function u(t,e){if((t.length-e)%2==0){var n=t.toString("utf16le",e);if(n){var r=n.charCodeAt(n.length-1);if(r>=55296&&r<=56319)return this.lastNeed=2,this.lastTotal=4,this.lastChar[0]=t[t.length-2],this.lastChar[1]=t[t.length-1],n.slice(0,-1)}return n}return this.lastNeed=1,this.lastTotal=2,this.lastChar[0]=t[t.length-1],t.toString("utf16le",e,t.length-1)}function c(t){var e=t&&t.length?this.write(t):"";if(this.lastNeed){var n=this.lastTotal-this.lastNeed;return e+this.lastChar.toString("utf16le",0,n)}return e}function f(t,e){var n=(t.length-e)%3;return 0===n?t.toString("base64",e):(this.lastNeed=3-n,this.lastTotal=3,1===n?this.lastChar[0]=t[t.length-1]:(this.lastChar[0]=t[t.length-2],this.lastChar[1]=t[t.length-1]),t.toString("base64",e,t.length-n))}function l(t){var e=t&&t.length?this.write(t):"";return this.lastNeed?e+this.lastChar.toString("base64",0,3-this.lastNeed):e}function h(t){return t.toString(this.encoding)}function d(t){return t&&t.length?this.write(t):""}e.StringDecoder=o,o.prototype.write=function(t){if(0===t.length)return"";var e,n;if(this.lastNeed){if(void 0===(e=this.fillLast(t)))return"";n=this.lastNeed,this.lastNeed=0}else n=0;return n=0)return i>0&&(t.lastNeed=i-1),i;if(--r=0)return i>0&&(t.lastNeed=i-2),i;if(--r=0)return i>0&&(2===i?i=0:t.lastNeed=i-3),i;return 0}(this,t,e);if(!this.lastNeed)return t.toString("utf8",e);this.lastTotal=n;var r=t.length-(n-this.lastNeed);return t.copy(this.lastChar,0,r),t.toString("utf8",e,r)},o.prototype.fillLast=function(t){if(this.lastNeed<=t.length)return t.copy(this.lastChar,this.lastTotal-this.lastNeed,0,this.lastNeed),this.lastChar.toString(this.encoding,0,this.lastTotal);t.copy(this.lastChar,this.lastTotal-this.lastNeed,0,t.length),this.lastNeed-=t.length}},function(t,e,n){"use strict";var r=n(8).Buffer,i=n(2),o=n(192),a=new Array(16),s=[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,7,4,13,1,10,6,15,3,12,0,9,5,2,14,11,8,3,10,14,4,9,15,8,1,2,7,0,6,13,11,5,12,1,9,11,10,0,8,12,4,13,3,7,15,14,5,6,2,4,0,5,9,7,12,2,10,14,1,3,8,11,6,15,13],u=[5,14,7,0,9,2,11,4,13,6,15,8,1,10,3,12,6,11,3,7,0,13,5,10,14,15,8,12,4,9,1,2,15,5,1,3,7,14,6,9,11,8,12,2,10,0,4,13,8,6,4,1,3,11,15,0,5,12,2,13,9,7,10,14,12,15,10,4,1,5,8,7,6,2,13,14,0,3,9,11],c=[11,14,15,12,5,8,7,9,11,13,14,15,6,7,9,8,7,6,8,13,11,9,7,15,7,12,15,9,11,7,13,12,11,13,6,7,14,9,13,15,14,8,13,6,5,12,7,5,11,12,14,15,14,15,9,8,9,14,5,6,8,6,5,12,9,15,5,11,6,8,13,12,5,12,13,14,11,8,5,6],f=[8,9,9,11,13,15,15,5,7,7,8,11,14,14,12,6,9,13,15,7,12,8,9,11,7,7,12,7,6,15,13,11,9,7,15,11,8,6,6,14,12,13,5,14,13,13,7,5,15,5,8,11,14,14,6,14,6,9,12,9,12,5,15,8,8,5,12,9,12,5,14,6,8,13,6,5,15,13,11,11],l=[0,1518500249,1859775393,2400959708,2840853838],h=[1352829926,1548603684,1836072691,2053994217,0];function d(){o.call(this,64),this._a=1732584193,this._b=4023233417,this._c=2562383102,this._d=271733878,this._e=3285377520}function p(t,e){return t<>>32-e}function g(t,e,n,r,i,o,a,s){return p(t+(e^n^r)+o+a|0,s)+i|0}function y(t,e,n,r,i,o,a,s){return p(t+(e&n|~e&r)+o+a|0,s)+i|0}function b(t,e,n,r,i,o,a,s){return p(t+((e|~n)^r)+o+a|0,s)+i|0}function m(t,e,n,r,i,o,a,s){return p(t+(e&r|n&~r)+o+a|0,s)+i|0}function v(t,e,n,r,i,o,a,s){return p(t+(e^(n|~r))+o+a|0,s)+i|0}i(d,o),d.prototype._update=function(){for(var t=a,e=0;e<16;++e)t[e]=this._block.readInt32LE(4*e);for(var n=0|this._a,r=0|this._b,i=0|this._c,o=0|this._d,d=0|this._e,_=0|this._a,w=0|this._b,x=0|this._c,k=0|this._d,E=0|this._e,A=0;A<80;A+=1){var S,T;A<16?(S=g(n,r,i,o,d,t[s[A]],l[0],c[A]),T=v(_,w,x,k,E,t[u[A]],h[0],f[A])):A<32?(S=y(n,r,i,o,d,t[s[A]],l[1],c[A]),T=m(_,w,x,k,E,t[u[A]],h[1],f[A])):A<48?(S=b(n,r,i,o,d,t[s[A]],l[2],c[A]),T=b(_,w,x,k,E,t[u[A]],h[2],f[A])):A<64?(S=m(n,r,i,o,d,t[s[A]],l[3],c[A]),T=y(_,w,x,k,E,t[u[A]],h[3],f[A])):(S=v(n,r,i,o,d,t[s[A]],l[4],c[A]),T=g(_,w,x,k,E,t[u[A]],h[4],f[A])),n=d,d=o,o=p(i,10),i=r,r=S,_=E,E=k,k=p(x,10),x=w,w=T}var M=this._b+i+k|0;this._b=this._c+o+E|0,this._c=this._d+d+_|0,this._d=this._e+n+w|0,this._e=this._a+r+x|0,this._a=M},d.prototype._digest=function(){this._block[this._blockOffset++]=128,this._blockOffset>56&&(this._block.fill(0,this._blockOffset,64),this._update(),this._blockOffset=0),this._block.fill(0,this._blockOffset,56),this._block.writeUInt32LE(this._length[0],56),this._block.writeUInt32LE(this._length[1],60),this._update();var t=r.alloc?r.alloc(20):new r(20);return t.writeInt32LE(this._a,0),t.writeInt32LE(this._b,4),t.writeInt32LE(this._c,8),t.writeInt32LE(this._d,12),t.writeInt32LE(this._e,16),t},t.exports=d},function(t,e,n){(e=t.exports=function(t){t=t.toLowerCase();var n=e[t];if(!n)throw new Error(t+" is not supported (we accept pull requests)");return new n}).sha=n(432),e.sha1=n(433),e.sha224=n(434),e.sha256=n(197),e.sha384=n(435),e.sha512=n(198)},function(t,e,n){"use strict";e.utils=n(441),e.Cipher=n(442),e.DES=n(443),e.CBC=n(444),e.EDE=n(445)},function(t,e,n){var r=n(446),i=n(454),o=n(208);e.createCipher=e.Cipher=r.createCipher,e.createCipheriv=e.Cipheriv=r.createCipheriv,e.createDecipher=e.Decipher=i.createDecipher,e.createDecipheriv=e.Decipheriv=i.createDecipheriv,e.listCiphers=e.getCiphers=function(){return Object.keys(o)}},function(t,e,n){var r={ECB:n(447),CBC:n(448),CFB:n(449),CFB8:n(450),CFB1:n(451),OFB:n(452),CTR:n(206),GCM:n(206)},i=n(208);for(var o in i)i[o].module=r[i[o].mode];t.exports=i},function(t,e,n){var r;function i(t){this.rand=t}if(t.exports=function(t){return r||(r=new i(null)),r.generate(t)},t.exports.Rand=i,i.prototype.generate=function(t){return this._rand(t)},i.prototype._rand=function(t){if(this.rand.getBytes)return this.rand.getBytes(t);for(var e=new Uint8Array(t),n=0;n=0||!n.umod(t.prime1)||!n.umod(t.prime2);)n=new r(i(e));return n}t.exports=o,o.getr=a}).call(this,n(8).Buffer)},function(t,e,n){"use strict";var r=e;r.version=n(463).version,r.utils=n(16),r.rand=n(123),r.curve=n(214),r.curves=n(126),r.ec=n(474),r.eddsa=n(478)},function(t,e,n){"use strict";var r,i=e,o=n(127),a=n(214),s=n(16).assert;function u(t){"short"===t.type?this.curve=new a.short(t):"edwards"===t.type?this.curve=new a.edwards(t):this.curve=new a.mont(t),this.g=this.curve.g,this.n=this.curve.n,this.hash=t.hash,s(this.g.validate(),"Invalid curve"),s(this.g.mul(this.n).isInfinity(),"Invalid curve, G*N != O")}function c(t,e){Object.defineProperty(i,t,{configurable:!0,enumerable:!0,get:function(){var n=new u(e);return Object.defineProperty(i,t,{configurable:!0,enumerable:!0,value:n}),n}})}i.PresetCurve=u,c("p192",{type:"short",prime:"p192",p:"ffffffff ffffffff ffffffff fffffffe ffffffff ffffffff",a:"ffffffff ffffffff ffffffff fffffffe ffffffff fffffffc",b:"64210519 e59c80e7 0fa7e9ab 72243049 feb8deec c146b9b1",n:"ffffffff ffffffff ffffffff 99def836 146bc9b1 b4d22831",hash:o.sha256,gRed:!1,g:["188da80e b03090f6 7cbf20eb 43a18800 f4ff0afd 82ff1012","07192b95 ffc8da78 631011ed 6b24cdd5 73f977a1 1e794811"]}),c("p224",{type:"short",prime:"p224",p:"ffffffff ffffffff ffffffff ffffffff 00000000 00000000 00000001",a:"ffffffff ffffffff ffffffff fffffffe ffffffff ffffffff fffffffe",b:"b4050a85 0c04b3ab f5413256 5044b0b7 d7bfd8ba 270b3943 2355ffb4",n:"ffffffff ffffffff ffffffff ffff16a2 e0b8f03e 13dd2945 5c5c2a3d",hash:o.sha256,gRed:!1,g:["b70e0cbd 6bb4bf7f 321390b9 4a03c1d3 56c21122 343280d6 115c1d21","bd376388 b5f723fb 4c22dfe6 cd4375a0 5a074764 44d58199 85007e34"]}),c("p256",{type:"short",prime:null,p:"ffffffff 00000001 00000000 00000000 00000000 ffffffff ffffffff ffffffff",a:"ffffffff 00000001 00000000 00000000 00000000 ffffffff ffffffff fffffffc",b:"5ac635d8 aa3a93e7 b3ebbd55 769886bc 651d06b0 cc53b0f6 3bce3c3e 27d2604b",n:"ffffffff 00000000 ffffffff ffffffff bce6faad a7179e84 f3b9cac2 fc632551",hash:o.sha256,gRed:!1,g:["6b17d1f2 e12c4247 f8bce6e5 63a440f2 77037d81 2deb33a0 f4a13945 d898c296","4fe342e2 fe1a7f9b 8ee7eb4a 7c0f9e16 2bce3357 6b315ece cbb64068 37bf51f5"]}),c("p384",{type:"short",prime:null,p:"ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff fffffffe ffffffff 00000000 00000000 ffffffff",a:"ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff fffffffe ffffffff 00000000 00000000 fffffffc",b:"b3312fa7 e23ee7e4 988e056b e3f82d19 181d9c6e fe814112 0314088f 5013875a c656398d 8a2ed19d 2a85c8ed d3ec2aef",n:"ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff c7634d81 f4372ddf 581a0db2 48b0a77a ecec196a ccc52973",hash:o.sha384,gRed:!1,g:["aa87ca22 be8b0537 8eb1c71e f320ad74 6e1d3b62 8ba79b98 59f741e0 82542a38 5502f25d bf55296c 3a545e38 72760ab7","3617de4a 96262c6f 5d9e98bf 9292dc29 f8f41dbd 289a147c e9da3113 b5f0b8c0 0a60b1ce 1d7e819d 7a431d7c 90ea0e5f"]}),c("p521",{type:"short",prime:null,p:"000001ff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff",a:"000001ff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff fffffffc",b:"00000051 953eb961 8e1c9a1f 929a21a0 b68540ee a2da725b 99b315f3 b8b48991 8ef109e1 56193951 ec7e937b 1652c0bd 3bb1bf07 3573df88 3d2c34f1 ef451fd4 6b503f00",n:"000001ff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff fffffffa 51868783 bf2f966b 7fcc0148 f709a5d0 3bb5c9b8 899c47ae bb6fb71e 91386409",hash:o.sha512,gRed:!1,g:["000000c6 858e06b7 0404e9cd 9e3ecb66 2395b442 9c648139 053fb521 f828af60 6b4d3dba a14b5e77 efe75928 fe1dc127 a2ffa8de 3348b3c1 856a429b f97e7e31 c2e5bd66","00000118 39296a78 9a3bc004 5c8a5fb4 2c7d1bd9 98f54449 579b4468 17afbd17 273e662c 97ee7299 5ef42640 c550b901 3fad0761 353c7086 a272c240 88be9476 9fd16650"]}),c("curve25519",{type:"mont",prime:"p25519",p:"7fffffffffffffff ffffffffffffffff ffffffffffffffff ffffffffffffffed",a:"76d06",b:"1",n:"1000000000000000 0000000000000000 14def9dea2f79cd6 5812631a5cf5d3ed",hash:o.sha256,gRed:!1,g:["9"]}),c("ed25519",{type:"edwards",prime:"p25519",p:"7fffffffffffffff ffffffffffffffff ffffffffffffffff ffffffffffffffed",a:"-1",c:"1",d:"52036cee2b6ffe73 8cc740797779e898 00700a4d4141d8ab 75eb4dca135978a3",n:"1000000000000000 0000000000000000 14def9dea2f79cd6 5812631a5cf5d3ed",hash:o.sha256,gRed:!1,g:["216936d3cd6e53fec0a4e231fdd6dc5c692cc7609525a7b2c9562d608f25d51a","6666666666666666666666666666666666666666666666666666666666666658"]});try{r=n(473)}catch(t){r=void 0}c("secp256k1",{type:"short",prime:"k256",p:"ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff fffffffe fffffc2f",a:"0",b:"7",n:"ffffffff ffffffff ffffffff fffffffe baaedce6 af48a03b bfd25e8c d0364141",h:"1",hash:o.sha256,beta:"7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee",lambda:"5363ad4cc05c30e0a5261c028812645a122e22ea20816678df02967c1b23bd72",basis:[{a:"3086d221a7d46bcde86c90e49284eb15",b:"-e4437ed6010e88286f547fa90abfe4c3"},{a:"114ca50f7a8e2f3f657c1108d9d44cfd8",b:"3086d221a7d46bcde86c90e49284eb15"}],gRed:!1,g:["79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798","483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8",r]})},function(t,e,n){var r=e;r.utils=n(21),r.common=n(56),r.sha=n(467),r.ripemd=n(471),r.hmac=n(472),r.sha1=r.sha.sha1,r.sha256=r.sha.sha256,r.sha224=r.sha.sha224,r.sha384=r.sha.sha384,r.sha512=r.sha.sha512,r.ripemd160=r.ripemd.ripemd160},function(t,e,n){var r=n(14);t.exports=function(t,e){var n=t.append("foreignObject").attr("width","100000"),i=n.append("xhtml:div");i.attr("xmlns","http://www.w3.org/1999/xhtml");var o=e.label;switch(typeof o){case"function":i.insert(o);break;case"object":i.insert((function(){return o}));break;default:i.html(o)}r.applyStyle(i,e.labelStyle),i.style("display","inline-block"),i.style("white-space","nowrap");var a=i.node().getBoundingClientRect();return n.attr("width",a.width).attr("height",a.height),n}},function(t,e){},function(t,e,n){var r=n(61),i=n(92),o=n(66),a=n(264),s=n(270),u=n(136),c=n(137),f=n(273),l=n(274),h=n(141),d=n(275),p=n(41),g=n(279),y=n(280),b=n(146),m=n(6),v=n(39),_=n(284),w=n(13),x=n(286),k=n(27),E=1,A=2,S=4,T="[object Arguments]",M="[object Function]",D="[object GeneratorFunction]",C="[object Object]",O={};O[T]=O["[object Array]"]=O["[object ArrayBuffer]"]=O["[object DataView]"]=O["[object Boolean]"]=O["[object Date]"]=O["[object Float32Array]"]=O["[object Float64Array]"]=O["[object Int8Array]"]=O["[object Int16Array]"]=O["[object Int32Array]"]=O["[object Map]"]=O["[object Number]"]=O[C]=O["[object RegExp]"]=O["[object Set]"]=O["[object String]"]=O["[object Symbol]"]=O["[object Uint8Array]"]=O["[object Uint8ClampedArray]"]=O["[object Uint16Array]"]=O["[object Uint32Array]"]=!0,O["[object Error]"]=O[M]=O["[object WeakMap]"]=!1,t.exports=function t(e,n,R,I,N,B){var L,P=n&E,F=n&A,q=n&S;if(R&&(L=N?R(e,I,N,B):R(e)),void 0!==L)return L;if(!w(e))return e;var j=m(e);if(j){if(L=g(e),!P)return c(e,L)}else{var U=p(e),z=U==M||U==D;if(v(e))return u(e,P);if(U==C||U==T||z&&!N){if(L=F||z?{}:b(e),!P)return F?l(e,s(L,e)):f(e,a(L,e))}else{if(!O[U])return N?e:{};L=y(e,U,P)}}B||(B=new r);var Y=B.get(e);if(Y)return Y;B.set(e,L),x(e)?e.forEach((function(r){L.add(t(r,n,R,r,e,B))})):_(e)&&e.forEach((function(r,i){L.set(i,t(r,n,R,i,e,B))}));var V=q?F?d:h:F?keysIn:k,H=j?void 0:V(e);return i(H||e,(function(r,i){H&&(r=e[i=r]),o(L,i,t(r,n,R,i,e,B))})),L}},function(t,e,n){(function(e){var n="object"==typeof e&&e&&e.Object===Object&&e;t.exports=n}).call(this,n(11))},function(t,e){var n=Function.prototype.toString;t.exports=function(t){if(null!=t){try{return n.call(t)}catch(t){}try{return t+""}catch(t){}}return""}},function(t,e,n){var r=n(32),i=function(){try{var t=r(Object,"defineProperty");return t({},"",{}),t}catch(t){}}();t.exports=i},function(t,e,n){var r=n(265),i=n(50),o=n(6),a=n(39),s=n(68),u=n(51),c=Object.prototype.hasOwnProperty;t.exports=function(t,e){var n=o(t),f=!n&&i(t),l=!n&&!f&&a(t),h=!n&&!f&&!l&&u(t),d=n||f||l||h,p=d?r(t.length,String):[],g=p.length;for(var y in t)!e&&!c.call(t,y)||d&&("length"==y||l&&("offset"==y||"parent"==y)||h&&("buffer"==y||"byteLength"==y||"byteOffset"==y)||s(y,g))||p.push(y);return p}},function(t,e){t.exports=function(t,e){return function(n){return t(e(n))}}},function(t,e,n){(function(t){var r=n(18),i=e&&!e.nodeType&&e,o=i&&"object"==typeof t&&t&&!t.nodeType&&t,a=o&&o.exports===i?r.Buffer:void 0,s=a?a.allocUnsafe:void 0;t.exports=function(t,e){if(e)return t.slice();var n=t.length,r=s?s(n):new t.constructor(n);return t.copy(r),r}}).call(this,n(9)(t))},function(t,e){t.exports=function(t,e){var n=-1,r=t.length;for(e||(e=Array(r));++nh))return!1;var p=f.get(t);if(p&&f.get(e))return p==e;var g=-1,y=!0,b=n&s?new r:void 0;for(f.set(t,e),f.set(e,t);++g0&&(o=u.removeMin(),(a=s[o]).distance!==Number.POSITIVE_INFINITY);)r(o).forEach(c);return s}(t,String(e),n||o,r||function(e){return t.outEdges(e)})};var o=r.constant(1)},function(t,e,n){var r=n(12);function i(){this._arr=[],this._keyIndices={}}t.exports=i,i.prototype.size=function(){return this._arr.length},i.prototype.keys=function(){return this._arr.map((function(t){return t.key}))},i.prototype.has=function(t){return r.has(this._keyIndices,t)},i.prototype.priority=function(t){var e=this._keyIndices[t];if(void 0!==e)return this._arr[e].priority},i.prototype.min=function(){if(0===this.size())throw new Error("Queue underflow");return this._arr[0].key},i.prototype.add=function(t,e){var n=this._keyIndices;if(t=String(t),!r.has(n,t)){var i=this._arr,o=i.length;return n[t]=o,i.push({key:t,priority:e}),this._decrease(o),!0}return!1},i.prototype.removeMin=function(){this._swap(0,this._arr.length-1);var t=this._arr.pop();return delete this._keyIndices[t.key],this._heapify(0),t.key},i.prototype.decrease=function(t,e){var n=this._keyIndices[t];if(e>this._arr[n].priority)throw new Error("New priority is greater than current priority. Key: "+t+" Old: "+this._arr[n].priority+" New: "+e);this._arr[n].priority=e,this._decrease(n)},i.prototype._heapify=function(t){var e=this._arr,n=2*t,r=n+1,i=t;n>1].priority2?e[2]:void 0;for(c&&o(e[0],e[1],c)&&(r=1);++n1&&a.sort((function(t,e){var r=t.x-n.x,i=t.y-n.y,o=Math.sqrt(r*r+i*i),a=e.x-n.x,s=e.y-n.y,u=Math.sqrt(a*a+s*s);return oMath.abs(a)*c?(s<0&&(c=-c),n=0===s?0:c*a/s,r=c):(a<0&&(u=-u),n=u,r=0===a?0:u*s/a);return{x:i+n,y:o+r}}},function(t,e){var n={}.toString;t.exports=Array.isArray||function(t){return"[object Array]"==n.call(t)}},function(t,e,n){"use strict";var r=n(3).Buffer,i=n(112).Transform;function o(t){i.call(this),this._block=r.allocUnsafe(t),this._blockSize=t,this._blockOffset=0,this._length=[0,0,0,0],this._finalized=!1}n(2)(o,i),o.prototype._transform=function(t,e,n){var r=null;try{this.update(t,e)}catch(t){r=t}n(r)},o.prototype._flush=function(t){var e=null;try{this.push(this.digest())}catch(t){e=t}t(e)},o.prototype.update=function(t,e){if(function(t,e){if(!r.isBuffer(t)&&"string"!=typeof t)throw new TypeError(e+" must be a string or a buffer")}(t,"Data"),this._finalized)throw new Error("Digest already called");r.isBuffer(t)||(t=r.from(t,e));for(var n=this._block,i=0;this._blockOffset+t.length-i>=this._blockSize;){for(var o=this._blockOffset;o0;++a)this._length[a]+=s,(s=this._length[a]/4294967296|0)>0&&(this._length[a]-=4294967296*s);return this},o.prototype._update=function(){throw new Error("_update is not implemented")},o.prototype.digest=function(t){if(this._finalized)throw new Error("Digest already called");this._finalized=!0;var e=this._digest();void 0!==t&&(e=e.toString(t)),this._block.fill(0),this._blockOffset=0;for(var n=0;n<4;++n)this._length[n]=0;return e},o.prototype._digest=function(){throw new Error("_digest is not implemented")},t.exports=o},function(t,e,n){"use strict";(function(e,r){var i=n(78);t.exports=v;var o,a=n(191);v.ReadableState=m;n(113).EventEmitter;var s=function(t,e){return t.listeners(e).length},u=n(194),c=n(115).Buffer,f=e.Uint8Array||function(){};var l=n(54);l.inherits=n(2);var h=n(421),d=void 0;d=h&&h.debuglog?h.debuglog("stream"):function(){};var p,g=n(422),y=n(195);l.inherits(v,u);var b=["error","close","destroy","pause","resume"];function m(t,e){t=t||{};var r=e instanceof(o=o||n(35));this.objectMode=!!t.objectMode,r&&(this.objectMode=this.objectMode||!!t.readableObjectMode);var i=t.highWaterMark,a=t.readableHighWaterMark,s=this.objectMode?16:16384;this.highWaterMark=i||0===i?i:r&&(a||0===a)?a:s,this.highWaterMark=Math.floor(this.highWaterMark),this.buffer=new g,this.length=0,this.pipes=null,this.pipesCount=0,this.flowing=null,this.ended=!1,this.endEmitted=!1,this.reading=!1,this.sync=!0,this.needReadable=!1,this.emittedReadable=!1,this.readableListening=!1,this.resumeScheduled=!1,this.destroyed=!1,this.defaultEncoding=t.defaultEncoding||"utf8",this.awaitDrain=0,this.readingMore=!1,this.decoder=null,this.encoding=null,t.encoding&&(p||(p=n(117).StringDecoder),this.decoder=new p(t.encoding),this.encoding=t.encoding)}function v(t){if(o=o||n(35),!(this instanceof v))return new v(t);this._readableState=new m(t,this),this.readable=!0,t&&("function"==typeof t.read&&(this._read=t.read),"function"==typeof t.destroy&&(this._destroy=t.destroy)),u.call(this)}function _(t,e,n,r,i){var o,a=t._readableState;null===e?(a.reading=!1,function(t,e){if(e.ended)return;if(e.decoder){var n=e.decoder.end();n&&n.length&&(e.buffer.push(n),e.length+=e.objectMode?1:n.length)}e.ended=!0,E(t)}(t,a)):(i||(o=function(t,e){var n;r=e,c.isBuffer(r)||r instanceof f||"string"==typeof e||void 0===e||t.objectMode||(n=new TypeError("Invalid non-string/buffer chunk"));var r;return n}(a,e)),o?t.emit("error",o):a.objectMode||e&&e.length>0?("string"==typeof e||a.objectMode||Object.getPrototypeOf(e)===c.prototype||(e=function(t){return c.from(t)}(e)),r?a.endEmitted?t.emit("error",new Error("stream.unshift() after end event")):w(t,a,e,!0):a.ended?t.emit("error",new Error("stream.push() after EOF")):(a.reading=!1,a.decoder&&!n?(e=a.decoder.write(e),a.objectMode||0!==e.length?w(t,a,e,!1):S(t,a)):w(t,a,e,!1))):r||(a.reading=!1));return function(t){return!t.ended&&(t.needReadable||t.lengthe.highWaterMark&&(e.highWaterMark=function(t){return t>=x?t=x:(t--,t|=t>>>1,t|=t>>>2,t|=t>>>4,t|=t>>>8,t|=t>>>16,t++),t}(t)),t<=e.length?t:e.ended?e.length:(e.needReadable=!0,0))}function E(t){var e=t._readableState;e.needReadable=!1,e.emittedReadable||(d("emitReadable",e.flowing),e.emittedReadable=!0,e.sync?i.nextTick(A,t):A(t))}function A(t){d("emit readable"),t.emit("readable"),C(t)}function S(t,e){e.readingMore||(e.readingMore=!0,i.nextTick(T,t,e))}function T(t,e){for(var n=e.length;!e.reading&&!e.flowing&&!e.ended&&e.length=e.length?(n=e.decoder?e.buffer.join(""):1===e.buffer.length?e.buffer.head.data:e.buffer.concat(e.length),e.buffer.clear()):n=function(t,e,n){var r;to.length?o.length:t;if(a===o.length?i+=o:i+=o.slice(0,t),0===(t-=a)){a===o.length?(++r,n.next?e.head=n.next:e.head=e.tail=null):(e.head=n,n.data=o.slice(a));break}++r}return e.length-=r,i}(t,e):function(t,e){var n=c.allocUnsafe(t),r=e.head,i=1;r.data.copy(n),t-=r.data.length;for(;r=r.next;){var o=r.data,a=t>o.length?o.length:t;if(o.copy(n,n.length-t,0,a),0===(t-=a)){a===o.length?(++i,r.next?e.head=r.next:e.head=e.tail=null):(e.head=r,r.data=o.slice(a));break}++i}return e.length-=i,n}(t,e);return r}(t,e.buffer,e.decoder),n);var n}function R(t){var e=t._readableState;if(e.length>0)throw new Error('"endReadable()" called on non-empty stream');e.endEmitted||(e.ended=!0,i.nextTick(I,e,t))}function I(t,e){t.endEmitted||0!==t.length||(t.endEmitted=!0,e.readable=!1,e.emit("end"))}function N(t,e){for(var n=0,r=t.length;n=e.highWaterMark||e.ended))return d("read: emitReadable",e.length,e.ended),0===e.length&&e.ended?R(this):E(this),null;if(0===(t=k(t,e))&&e.ended)return 0===e.length&&R(this),null;var r,i=e.needReadable;return d("need readable",i),(0===e.length||e.length-t0?O(t,e):null)?(e.needReadable=!0,t=0):e.length-=t,0===e.length&&(e.ended||(e.needReadable=!0),n!==t&&e.ended&&R(this)),null!==r&&this.emit("data",r),r},v.prototype._read=function(t){this.emit("error",new Error("_read() is not implemented"))},v.prototype.pipe=function(t,e){var n=this,o=this._readableState;switch(o.pipesCount){case 0:o.pipes=t;break;case 1:o.pipes=[o.pipes,t];break;default:o.pipes.push(t)}o.pipesCount+=1,d("pipe count=%d opts=%j",o.pipesCount,e);var u=(!e||!1!==e.end)&&t!==r.stdout&&t!==r.stderr?f:v;function c(e,r){d("onunpipe"),e===n&&r&&!1===r.hasUnpiped&&(r.hasUnpiped=!0,d("cleanup"),t.removeListener("close",b),t.removeListener("finish",m),t.removeListener("drain",l),t.removeListener("error",y),t.removeListener("unpipe",c),n.removeListener("end",f),n.removeListener("end",v),n.removeListener("data",g),h=!0,!o.awaitDrain||t._writableState&&!t._writableState.needDrain||l())}function f(){d("onend"),t.end()}o.endEmitted?i.nextTick(u):n.once("end",u),t.on("unpipe",c);var l=function(t){return function(){var e=t._readableState;d("pipeOnDrain",e.awaitDrain),e.awaitDrain&&e.awaitDrain--,0===e.awaitDrain&&s(t,"data")&&(e.flowing=!0,C(t))}}(n);t.on("drain",l);var h=!1;var p=!1;function g(e){d("ondata"),p=!1,!1!==t.write(e)||p||((1===o.pipesCount&&o.pipes===t||o.pipesCount>1&&-1!==N(o.pipes,t))&&!h&&(d("false write response, pause",n._readableState.awaitDrain),n._readableState.awaitDrain++,p=!0),n.pause())}function y(e){d("onerror",e),v(),t.removeListener("error",y),0===s(t,"error")&&t.emit("error",e)}function b(){t.removeListener("finish",m),v()}function m(){d("onfinish"),t.removeListener("close",b),v()}function v(){d("unpipe"),n.unpipe(t)}return n.on("data",g),function(t,e,n){if("function"==typeof t.prependListener)return t.prependListener(e,n);t._events&&t._events[e]?a(t._events[e])?t._events[e].unshift(n):t._events[e]=[n,t._events[e]]:t.on(e,n)}(t,"error",y),t.once("close",b),t.once("finish",m),t.emit("pipe",n),o.flowing||(d("pipe resume"),n.resume()),t},v.prototype.unpipe=function(t){var e=this._readableState,n={hasUnpiped:!1};if(0===e.pipesCount)return this;if(1===e.pipesCount)return t&&t!==e.pipes?this:(t||(t=e.pipes),e.pipes=null,e.pipesCount=0,e.flowing=!1,t&&t.emit("unpipe",this,n),this);if(!t){var r=e.pipes,i=e.pipesCount;e.pipes=null,e.pipesCount=0,e.flowing=!1;for(var o=0;o>>2|t<<30)^(t>>>13|t<<19)^(t>>>22|t<<10)}function h(t){return(t>>>6|t<<26)^(t>>>11|t<<21)^(t>>>25|t<<7)}function d(t){return(t>>>7|t<<25)^(t>>>18|t<<14)^t>>>3}r(u,i),u.prototype.init=function(){return this._a=1779033703,this._b=3144134277,this._c=1013904242,this._d=2773480762,this._e=1359893119,this._f=2600822924,this._g=528734635,this._h=1541459225,this},u.prototype._update=function(t){for(var e,n=this._w,r=0|this._a,i=0|this._b,o=0|this._c,s=0|this._d,u=0|this._e,p=0|this._f,g=0|this._g,y=0|this._h,b=0;b<16;++b)n[b]=t.readInt32BE(4*b);for(;b<64;++b)n[b]=0|(((e=n[b-2])>>>17|e<<15)^(e>>>19|e<<13)^e>>>10)+n[b-7]+d(n[b-15])+n[b-16];for(var m=0;m<64;++m){var v=y+h(u)+c(u,p,g)+a[m]+n[m]|0,_=l(r)+f(r,i,o)|0;y=g,g=p,p=u,u=s+v|0,s=o,o=i,i=r,r=v+_|0}this._a=r+this._a|0,this._b=i+this._b|0,this._c=o+this._c|0,this._d=s+this._d|0,this._e=u+this._e|0,this._f=p+this._f|0,this._g=g+this._g|0,this._h=y+this._h|0},u.prototype._hash=function(){var t=o.allocUnsafe(32);return t.writeInt32BE(this._a,0),t.writeInt32BE(this._b,4),t.writeInt32BE(this._c,8),t.writeInt32BE(this._d,12),t.writeInt32BE(this._e,16),t.writeInt32BE(this._f,20),t.writeInt32BE(this._g,24),t.writeInt32BE(this._h,28),t},t.exports=u},function(t,e,n){var r=n(2),i=n(45),o=n(3).Buffer,a=[1116352408,3609767458,1899447441,602891725,3049323471,3964484399,3921009573,2173295548,961987163,4081628472,1508970993,3053834265,2453635748,2937671579,2870763221,3664609560,3624381080,2734883394,310598401,1164996542,607225278,1323610764,1426881987,3590304994,1925078388,4068182383,2162078206,991336113,2614888103,633803317,3248222580,3479774868,3835390401,2666613458,4022224774,944711139,264347078,2341262773,604807628,2007800933,770255983,1495990901,1249150122,1856431235,1555081692,3175218132,1996064986,2198950837,2554220882,3999719339,2821834349,766784016,2952996808,2566594879,3210313671,3203337956,3336571891,1034457026,3584528711,2466948901,113926993,3758326383,338241895,168717936,666307205,1188179964,773529912,1546045734,1294757372,1522805485,1396182291,2643833823,1695183700,2343527390,1986661051,1014477480,2177026350,1206759142,2456956037,344077627,2730485921,1290863460,2820302411,3158454273,3259730800,3505952657,3345764771,106217008,3516065817,3606008344,3600352804,1432725776,4094571909,1467031594,275423344,851169720,430227734,3100823752,506948616,1363258195,659060556,3750685593,883997877,3785050280,958139571,3318307427,1322822218,3812723403,1537002063,2003034995,1747873779,3602036899,1955562222,1575990012,2024104815,1125592928,2227730452,2716904306,2361852424,442776044,2428436474,593698344,2756734187,3733110249,3204031479,2999351573,3329325298,3815920427,3391569614,3928383900,3515267271,566280711,3940187606,3454069534,4118630271,4000239992,116418474,1914138554,174292421,2731055270,289380356,3203993006,460393269,320620315,685471733,587496836,852142971,1086792851,1017036298,365543100,1126000580,2618297676,1288033470,3409855158,1501505948,4234509866,1607167915,987167468,1816402316,1246189591],s=new Array(160);function u(){this.init(),this._w=s,i.call(this,128,112)}function c(t,e,n){return n^t&(e^n)}function f(t,e,n){return t&e|n&(t|e)}function l(t,e){return(t>>>28|e<<4)^(e>>>2|t<<30)^(e>>>7|t<<25)}function h(t,e){return(t>>>14|e<<18)^(t>>>18|e<<14)^(e>>>9|t<<23)}function d(t,e){return(t>>>1|e<<31)^(t>>>8|e<<24)^t>>>7}function p(t,e){return(t>>>1|e<<31)^(t>>>8|e<<24)^(t>>>7|e<<25)}function g(t,e){return(t>>>19|e<<13)^(e>>>29|t<<3)^t>>>6}function y(t,e){return(t>>>19|e<<13)^(e>>>29|t<<3)^(t>>>6|e<<26)}function b(t,e){return t>>>0>>0?1:0}r(u,i),u.prototype.init=function(){return this._ah=1779033703,this._bh=3144134277,this._ch=1013904242,this._dh=2773480762,this._eh=1359893119,this._fh=2600822924,this._gh=528734635,this._hh=1541459225,this._al=4089235720,this._bl=2227873595,this._cl=4271175723,this._dl=1595750129,this._el=2917565137,this._fl=725511199,this._gl=4215389547,this._hl=327033209,this},u.prototype._update=function(t){for(var e=this._w,n=0|this._ah,r=0|this._bh,i=0|this._ch,o=0|this._dh,s=0|this._eh,u=0|this._fh,m=0|this._gh,v=0|this._hh,_=0|this._al,w=0|this._bl,x=0|this._cl,k=0|this._dl,E=0|this._el,A=0|this._fl,S=0|this._gl,T=0|this._hl,M=0;M<32;M+=2)e[M]=t.readInt32BE(4*M),e[M+1]=t.readInt32BE(4*M+4);for(;M<160;M+=2){var D=e[M-30],C=e[M-30+1],O=d(D,C),R=p(C,D),I=g(D=e[M-4],C=e[M-4+1]),N=y(C,D),B=e[M-14],L=e[M-14+1],P=e[M-32],F=e[M-32+1],q=R+L|0,j=O+B+b(q,R)|0;j=(j=j+I+b(q=q+N|0,N)|0)+P+b(q=q+F|0,F)|0,e[M]=j,e[M+1]=q}for(var U=0;U<160;U+=2){j=e[U],q=e[U+1];var z=f(n,r,i),Y=f(_,w,x),V=l(n,_),H=l(_,n),$=h(s,E),G=h(E,s),W=a[U],K=a[U+1],X=c(s,u,m),Z=c(E,A,S),J=T+G|0,Q=v+$+b(J,T)|0;Q=(Q=(Q=Q+X+b(J=J+Z|0,Z)|0)+W+b(J=J+K|0,K)|0)+j+b(J=J+q|0,q)|0;var tt=H+Y|0,et=V+z+b(tt,H)|0;v=m,T=S,m=u,S=A,u=s,A=E,s=o+Q+b(E=k+J|0,k)|0,o=i,k=x,i=r,x=w,r=n,w=_,n=Q+et+b(_=J+tt|0,J)|0}this._al=this._al+_|0,this._bl=this._bl+w|0,this._cl=this._cl+x|0,this._dl=this._dl+k|0,this._el=this._el+E|0,this._fl=this._fl+A|0,this._gl=this._gl+S|0,this._hl=this._hl+T|0,this._ah=this._ah+n+b(this._al,_)|0,this._bh=this._bh+r+b(this._bl,w)|0,this._ch=this._ch+i+b(this._cl,x)|0,this._dh=this._dh+o+b(this._dl,k)|0,this._eh=this._eh+s+b(this._el,E)|0,this._fh=this._fh+u+b(this._fl,A)|0,this._gh=this._gh+m+b(this._gl,S)|0,this._hh=this._hh+v+b(this._hl,T)|0},u.prototype._hash=function(){var t=o.allocUnsafe(64);function e(e,n,r){t.writeInt32BE(e,r),t.writeInt32BE(n,r+4)}return e(this._ah,this._al,0),e(this._bh,this._bl,8),e(this._ch,this._cl,16),e(this._dh,this._dl,24),e(this._eh,this._el,32),e(this._fh,this._fl,40),e(this._gh,this._gl,48),e(this._hh,this._hl,56),t},t.exports=u},function(t,e,n){"use strict";var r=n(2),i=n(436),o=n(31),a=n(3).Buffer,s=n(200),u=n(118),c=n(119),f=a.alloc(128);function l(t,e){o.call(this,"digest"),"string"==typeof e&&(e=a.from(e));var n="sha512"===t||"sha384"===t?128:64;(this._alg=t,this._key=e,e.length>n)?e=("rmd160"===t?new u:c(t)).update(e).digest():e.lengthn||o!=o)throw new TypeError("Bad key length")}}).call(this,n(8).Buffer)},function(t,e,n){(function(e){var n;e.browser?n="utf-8":n=parseInt(e.version.split(".")[0].slice(1),10)>=6?"utf-8":"binary";t.exports=n}).call(this,n(7))},function(t,e,n){var r=n(200),i=n(118),o=n(119),a=n(203),s=n(204),u=n(3).Buffer,c=u.alloc(128),f={md5:16,sha1:20,sha224:28,sha256:32,sha384:48,sha512:64,rmd160:20,ripemd160:20};function l(t,e,n){var a=function(t){return"rmd160"===t||"ripemd160"===t?function(t){return(new i).update(t).digest()}:"md5"===t?r:function(e){return o(t).update(e).digest()}}(t),s="sha512"===t||"sha384"===t?128:64;e.length>s?e=a(e):e.lengtht;)n.ishrn(1);if(n.isEven()&&n.iadd(s),n.testn(1)||n.iadd(u),e.cmp(u)){if(!e.cmp(c))for(;n.mod(f).cmp(l);)n.iadd(d)}else for(;n.mod(o).cmp(h);)n.iadd(d);if(y(p=n.shrn(1))&&y(n)&&b(p)&&b(n)&&a.test(p)&&a.test(n))return n}}},function(t,e,n){var r=n(5),i=n(123);function o(t){this.rand=t||new i.Rand}t.exports=o,o.create=function(t){return new o(t)},o.prototype._randbelow=function(t){var e=t.bitLength(),n=Math.ceil(e/8);do{var i=new r(this.rand.generate(n))}while(i.cmp(t)>=0);return i},o.prototype._randrange=function(t,e){var n=e.sub(t);return t.add(this._randbelow(n))},o.prototype.test=function(t,e,n){var i=t.bitLength(),o=r.mont(t),a=new r(1).toRed(o);e||(e=Math.max(1,i/48|0));for(var s=t.subn(1),u=0;!s.testn(u);u++);for(var c=t.shrn(u),f=s.toRed(o);e>0;e--){var l=this._randrange(new r(2),s);n&&n(l);var h=l.toRed(o).redPow(c);if(0!==h.cmp(a)&&0!==h.cmp(f)){for(var d=1;d0;e--){var f=this._randrange(new r(2),a),l=t.gcd(f);if(0!==l.cmpn(1))return l;var h=f.toRed(i).redPow(u);if(0!==h.cmp(o)&&0!==h.cmp(c)){for(var d=1;d>8,a=255&i;o?n.push(o,a):n.push(a)}return n},r.zero2=i,r.toHex=o,r.encode=function(t,e){return"hex"===e?o(t):t}},function(t,e,n){"use strict";var r=e;r.base=n(81),r.short=n(464),r.mont=n(465),r.edwards=n(466)},function(t,e,n){"use strict";var r=n(21).rotr32;function i(t,e,n){return t&e^~t&n}function o(t,e,n){return t&e^t&n^e&n}function a(t,e,n){return t^e^n}e.ft_1=function(t,e,n,r){return 0===t?i(e,n,r):1===t||3===t?a(e,n,r):2===t?o(e,n,r):void 0},e.ch32=i,e.maj32=o,e.p32=a,e.s0_256=function(t){return r(t,2)^r(t,13)^r(t,22)},e.s1_256=function(t){return r(t,6)^r(t,11)^r(t,25)},e.g0_256=function(t){return r(t,7)^r(t,18)^t>>>3},e.g1_256=function(t){return r(t,17)^r(t,19)^t>>>10}},function(t,e,n){"use strict";var r=n(21),i=n(56),o=n(215),a=n(15),s=r.sum32,u=r.sum32_4,c=r.sum32_5,f=o.ch32,l=o.maj32,h=o.s0_256,d=o.s1_256,p=o.g0_256,g=o.g1_256,y=i.BlockHash,b=[1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298];function m(){if(!(this instanceof m))return new m;y.call(this),this.h=[1779033703,3144134277,1013904242,2773480762,1359893119,2600822924,528734635,1541459225],this.k=b,this.W=new Array(64)}r.inherits(m,y),t.exports=m,m.blockSize=512,m.outSize=256,m.hmacStrength=192,m.padLength=64,m.prototype._update=function(t,e){for(var n=this.W,r=0;r<16;r++)n[r]=t[e+r];for(;r>6],i=0==(32&n);if(31==(31&n)){var o=n;for(n=0;128==(128&o);){if(o=t.readUInt8(e),t.isError(o))return o;n<<=7,n|=127&o}}else n&=31;return{cls:r,primitive:i,tag:n,tagStr:s.tag[n]}}function l(t,e,n){var r=t.readUInt8(n);if(t.isError(r))return r;if(!e&&128===r)return null;if(0==(128&r))return r;var i=127&r;if(i>4)return t.error("length octect is too long");r=0;for(var o=0;o=31)return r.error("Multi-octet tag encoding unsupported");e||(i|=32);return i|=s.tagClassByName[n||"universal"]<<6}(t,e,n,this.reporter);if(r.length<128)return(o=new i(2))[0]=a,o[1]=r.length,this._createEncoderBuffer([o,r]);for(var u=1,c=r.length;c>=256;c>>=8)u++;(o=new i(2+u))[0]=a,o[1]=128|u;c=1+u;for(var f=r.length;f>0;c--,f>>=8)o[c]=255&f;return this._createEncoderBuffer([o,r])},c.prototype._encodeStr=function(t,e){if("bitstr"===e)return this._createEncoderBuffer([0|t.unused,t.data]);if("bmpstr"===e){for(var n=new i(2*t.length),r=0;r=40)return this.reporter.error("Second objid identifier OOB");t.splice(0,2,40*t[0]+t[1])}var o=0;for(r=0;r=128;a>>=7)o++}var s=new i(o),u=s.length-1;for(r=t.length-1;r>=0;r--){a=t[r];for(s[u--]=127&a;(a>>=7)>0;)s[u--]=128|127&a}return this._createEncoderBuffer(s)},c.prototype._encodeTime=function(t,e){var n,r=new Date(t);return"gentime"===e?n=[f(r.getFullYear()),f(r.getUTCMonth()+1),f(r.getUTCDate()),f(r.getUTCHours()),f(r.getUTCMinutes()),f(r.getUTCSeconds()),"Z"].join(""):"utctime"===e?n=[f(r.getFullYear()%100),f(r.getUTCMonth()+1),f(r.getUTCDate()),f(r.getUTCHours()),f(r.getUTCMinutes()),f(r.getUTCSeconds()),"Z"].join(""):this.reporter.error("Encoding "+e+" time is not supported yet"),this._encodeStr(n,"octstr")},c.prototype._encodeNull=function(){return this._createEncoderBuffer("")},c.prototype._encodeInt=function(t,e){if("string"==typeof t){if(!e)return this.reporter.error("String int or enum given, but no values map");if(!e.hasOwnProperty(t))return this.reporter.error("Values map doesn't contain: "+JSON.stringify(t));t=e[t]}if("number"!=typeof t&&!i.isBuffer(t)){var n=t.toArray();!t.sign&&128&n[0]&&n.unshift(0),t=new i(n)}if(i.isBuffer(t)){var r=t.length;0===t.length&&r++;var o=new i(r);return t.copy(o),0===t.length&&(o[0]=0),this._createEncoderBuffer(o)}if(t<128)return this._createEncoderBuffer(t);if(t<256)return this._createEncoderBuffer([0,t]);r=1;for(var a=t;a>=256;a>>=8)r++;for(a=(o=new Array(r)).length-1;a>=0;a--)o[a]=255&t,t>>=8;return 128&o[0]&&o.unshift(0),this._createEncoderBuffer(new i(o))},c.prototype._encodeBool=function(t){return this._createEncoderBuffer(t?255:0)},c.prototype._use=function(t,e){return"function"==typeof t&&(t=t(e)),t._getEncoder("der").tree},c.prototype._skipDefault=function(t,e,n){var r,i=this._baseState;if(null===i.default)return!1;var o=t.join();if(void 0===i.defaultBuffer&&(i.defaultBuffer=this._encodeValue(i.default,e,n).join()),o.length!==i.defaultBuffer.length)return!1;for(r=0;r\u20D2|\u205F\u200A|\u219D\u0338|\u2202\u0338|\u2220\u20D2|\u2229\uFE00|\u222A\uFE00|\u223C\u20D2|\u223D\u0331|\u223E\u0333|\u2242\u0338|\u224B\u0338|\u224D\u20D2|\u224E\u0338|\u224F\u0338|\u2250\u0338|\u2261\u20E5|\u2264\u20D2|\u2265\u20D2|\u2266\u0338|\u2267\u0338|\u2268\uFE00|\u2269\uFE00|\u226A\u0338|\u226A\u20D2|\u226B\u0338|\u226B\u20D2|\u227F\u0338|\u2282\u20D2|\u2283\u20D2|\u228A\uFE00|\u228B\uFE00|\u228F\u0338|\u2290\u0338|\u2293\uFE00|\u2294\uFE00|\u22B4\u20D2|\u22B5\u20D2|\u22D8\u0338|\u22D9\u0338|\u22DA\uFE00|\u22DB\uFE00|\u22F5\u0338|\u22F9\u0338|\u2933\u0338|\u29CF\u0338|\u29D0\u0338|\u2A6D\u0338|\u2A70\u0338|\u2A7D\u0338|\u2A7E\u0338|\u2AA1\u0338|\u2AA2\u0338|\u2AAC\uFE00|\u2AAD\uFE00|\u2AAF\u0338|\u2AB0\u0338|\u2AC5\u0338|\u2AC6\u0338|\u2ACB\uFE00|\u2ACC\uFE00|\u2AFD\u20E5|[\xA0-\u0113\u0116-\u0122\u0124-\u012B\u012E-\u014D\u0150-\u017E\u0192\u01B5\u01F5\u0237\u02C6\u02C7\u02D8-\u02DD\u0311\u0391-\u03A1\u03A3-\u03A9\u03B1-\u03C9\u03D1\u03D2\u03D5\u03D6\u03DC\u03DD\u03F0\u03F1\u03F5\u03F6\u0401-\u040C\u040E-\u044F\u0451-\u045C\u045E\u045F\u2002-\u2005\u2007-\u2010\u2013-\u2016\u2018-\u201A\u201C-\u201E\u2020-\u2022\u2025\u2026\u2030-\u2035\u2039\u203A\u203E\u2041\u2043\u2044\u204F\u2057\u205F-\u2063\u20AC\u20DB\u20DC\u2102\u2105\u210A-\u2113\u2115-\u211E\u2122\u2124\u2127-\u2129\u212C\u212D\u212F-\u2131\u2133-\u2138\u2145-\u2148\u2153-\u215E\u2190-\u219B\u219D-\u21A7\u21A9-\u21AE\u21B0-\u21B3\u21B5-\u21B7\u21BA-\u21DB\u21DD\u21E4\u21E5\u21F5\u21FD-\u2205\u2207-\u2209\u220B\u220C\u220F-\u2214\u2216-\u2218\u221A\u221D-\u2238\u223A-\u2257\u2259\u225A\u225C\u225F-\u2262\u2264-\u228B\u228D-\u229B\u229D-\u22A5\u22A7-\u22B0\u22B2-\u22BB\u22BD-\u22DB\u22DE-\u22E3\u22E6-\u22F7\u22F9-\u22FE\u2305\u2306\u2308-\u2310\u2312\u2313\u2315\u2316\u231C-\u231F\u2322\u2323\u232D\u232E\u2336\u233D\u233F\u237C\u23B0\u23B1\u23B4-\u23B6\u23DC-\u23DF\u23E2\u23E7\u2423\u24C8\u2500\u2502\u250C\u2510\u2514\u2518\u251C\u2524\u252C\u2534\u253C\u2550-\u256C\u2580\u2584\u2588\u2591-\u2593\u25A1\u25AA\u25AB\u25AD\u25AE\u25B1\u25B3-\u25B5\u25B8\u25B9\u25BD-\u25BF\u25C2\u25C3\u25CA\u25CB\u25EC\u25EF\u25F8-\u25FC\u2605\u2606\u260E\u2640\u2642\u2660\u2663\u2665\u2666\u266A\u266D-\u266F\u2713\u2717\u2720\u2736\u2758\u2772\u2773\u27C8\u27C9\u27E6-\u27ED\u27F5-\u27FA\u27FC\u27FF\u2902-\u2905\u290C-\u2913\u2916\u2919-\u2920\u2923-\u292A\u2933\u2935-\u2939\u293C\u293D\u2945\u2948-\u294B\u294E-\u2976\u2978\u2979\u297B-\u297F\u2985\u2986\u298B-\u2996\u299A\u299C\u299D\u29A4-\u29B7\u29B9\u29BB\u29BC\u29BE-\u29C5\u29C9\u29CD-\u29D0\u29DC-\u29DE\u29E3-\u29E5\u29EB\u29F4\u29F6\u2A00-\u2A02\u2A04\u2A06\u2A0C\u2A0D\u2A10-\u2A17\u2A22-\u2A27\u2A29\u2A2A\u2A2D-\u2A31\u2A33-\u2A3C\u2A3F\u2A40\u2A42-\u2A4D\u2A50\u2A53-\u2A58\u2A5A-\u2A5D\u2A5F\u2A66\u2A6A\u2A6D-\u2A75\u2A77-\u2A9A\u2A9D-\u2AA2\u2AA4-\u2AB0\u2AB3-\u2AC8\u2ACB\u2ACC\u2ACF-\u2ADB\u2AE4\u2AE6-\u2AE9\u2AEB-\u2AF3\u2AFD\uFB00-\uFB04]|\uD835[\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDCCF\uDD04\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDD6B]/g,l={"­":"shy","‌":"zwnj","‍":"zwj","‎":"lrm","⁣":"ic","⁢":"it","⁡":"af","‏":"rlm","​":"ZeroWidthSpace","⁠":"NoBreak","̑":"DownBreve","⃛":"tdot","⃜":"DotDot","\t":"Tab","\n":"NewLine"," ":"puncsp"," ":"MediumSpace"," ":"thinsp"," ":"hairsp"," ":"emsp13"," ":"ensp"," ":"emsp14"," ":"emsp"," ":"numsp"," ":"nbsp","  ":"ThickSpace","‾":"oline",_:"lowbar","‐":"dash","–":"ndash","—":"mdash","―":"horbar",",":"comma",";":"semi","⁏":"bsemi",":":"colon","⩴":"Colone","!":"excl","¡":"iexcl","?":"quest","¿":"iquest",".":"period","‥":"nldr","…":"mldr","·":"middot","'":"apos","‘":"lsquo","’":"rsquo","‚":"sbquo","‹":"lsaquo","›":"rsaquo",'"':"quot","“":"ldquo","”":"rdquo","„":"bdquo","«":"laquo","»":"raquo","(":"lpar",")":"rpar","[":"lsqb","]":"rsqb","{":"lcub","}":"rcub","⌈":"lceil","⌉":"rceil","⌊":"lfloor","⌋":"rfloor","⦅":"lopar","⦆":"ropar","⦋":"lbrke","⦌":"rbrke","⦍":"lbrkslu","⦎":"rbrksld","⦏":"lbrksld","⦐":"rbrkslu","⦑":"langd","⦒":"rangd","⦓":"lparlt","⦔":"rpargt","⦕":"gtlPar","⦖":"ltrPar","⟦":"lobrk","⟧":"robrk","⟨":"lang","⟩":"rang","⟪":"Lang","⟫":"Rang","⟬":"loang","⟭":"roang","❲":"lbbrk","❳":"rbbrk","‖":"Vert","§":"sect","¶":"para","@":"commat","*":"ast","/":"sol",undefined:null,"&":"amp","#":"num","%":"percnt","‰":"permil","‱":"pertenk","†":"dagger","‡":"Dagger","•":"bull","⁃":"hybull","′":"prime","″":"Prime","‴":"tprime","⁗":"qprime","‵":"bprime","⁁":"caret","`":"grave","´":"acute","˜":"tilde","^":"Hat","¯":"macr","˘":"breve","˙":"dot","¨":"die","˚":"ring","˝":"dblac","¸":"cedil","˛":"ogon","ˆ":"circ","ˇ":"caron","°":"deg","©":"copy","®":"reg","℗":"copysr","℘":"wp","℞":"rx","℧":"mho","℩":"iiota","←":"larr","↚":"nlarr","→":"rarr","↛":"nrarr","↑":"uarr","↓":"darr","↔":"harr","↮":"nharr","↕":"varr","↖":"nwarr","↗":"nearr","↘":"searr","↙":"swarr","↝":"rarrw","↝̸":"nrarrw","↞":"Larr","↟":"Uarr","↠":"Rarr","↡":"Darr","↢":"larrtl","↣":"rarrtl","↤":"mapstoleft","↥":"mapstoup","↦":"map","↧":"mapstodown","↩":"larrhk","↪":"rarrhk","↫":"larrlp","↬":"rarrlp","↭":"harrw","↰":"lsh","↱":"rsh","↲":"ldsh","↳":"rdsh","↵":"crarr","↶":"cularr","↷":"curarr","↺":"olarr","↻":"orarr","↼":"lharu","↽":"lhard","↾":"uharr","↿":"uharl","⇀":"rharu","⇁":"rhard","⇂":"dharr","⇃":"dharl","⇄":"rlarr","⇅":"udarr","⇆":"lrarr","⇇":"llarr","⇈":"uuarr","⇉":"rrarr","⇊":"ddarr","⇋":"lrhar","⇌":"rlhar","⇐":"lArr","⇍":"nlArr","⇑":"uArr","⇒":"rArr","⇏":"nrArr","⇓":"dArr","⇔":"iff","⇎":"nhArr","⇕":"vArr","⇖":"nwArr","⇗":"neArr","⇘":"seArr","⇙":"swArr","⇚":"lAarr","⇛":"rAarr","⇝":"zigrarr","⇤":"larrb","⇥":"rarrb","⇵":"duarr","⇽":"loarr","⇾":"roarr","⇿":"hoarr","∀":"forall","∁":"comp","∂":"part","∂̸":"npart","∃":"exist","∄":"nexist","∅":"empty","∇":"Del","∈":"in","∉":"notin","∋":"ni","∌":"notni","϶":"bepsi","∏":"prod","∐":"coprod","∑":"sum","+":"plus","±":"pm","÷":"div","×":"times","<":"lt","≮":"nlt","<⃒":"nvlt","=":"equals","≠":"ne","=⃥":"bne","⩵":"Equal",">":"gt","≯":"ngt",">⃒":"nvgt","¬":"not","|":"vert","¦":"brvbar","−":"minus","∓":"mp","∔":"plusdo","⁄":"frasl","∖":"setmn","∗":"lowast","∘":"compfn","√":"Sqrt","∝":"prop","∞":"infin","∟":"angrt","∠":"ang","∠⃒":"nang","∡":"angmsd","∢":"angsph","∣":"mid","∤":"nmid","∥":"par","∦":"npar","∧":"and","∨":"or","∩":"cap","∩︀":"caps","∪":"cup","∪︀":"cups","∫":"int","∬":"Int","∭":"tint","⨌":"qint","∮":"oint","∯":"Conint","∰":"Cconint","∱":"cwint","∲":"cwconint","∳":"awconint","∴":"there4","∵":"becaus","∶":"ratio","∷":"Colon","∸":"minusd","∺":"mDDot","∻":"homtht","∼":"sim","≁":"nsim","∼⃒":"nvsim","∽":"bsim","∽̱":"race","∾":"ac","∾̳":"acE","∿":"acd","≀":"wr","≂":"esim","≂̸":"nesim","≃":"sime","≄":"nsime","≅":"cong","≇":"ncong","≆":"simne","≈":"ap","≉":"nap","≊":"ape","≋":"apid","≋̸":"napid","≌":"bcong","≍":"CupCap","≭":"NotCupCap","≍⃒":"nvap","≎":"bump","≎̸":"nbump","≏":"bumpe","≏̸":"nbumpe","≐":"doteq","≐̸":"nedot","≑":"eDot","≒":"efDot","≓":"erDot","≔":"colone","≕":"ecolon","≖":"ecir","≗":"cire","≙":"wedgeq","≚":"veeeq","≜":"trie","≟":"equest","≡":"equiv","≢":"nequiv","≡⃥":"bnequiv","≤":"le","≰":"nle","≤⃒":"nvle","≥":"ge","≱":"nge","≥⃒":"nvge","≦":"lE","≦̸":"nlE","≧":"gE","≧̸":"ngE","≨︀":"lvnE","≨":"lnE","≩":"gnE","≩︀":"gvnE","≪":"ll","≪̸":"nLtv","≪⃒":"nLt","≫":"gg","≫̸":"nGtv","≫⃒":"nGt","≬":"twixt","≲":"lsim","≴":"nlsim","≳":"gsim","≵":"ngsim","≶":"lg","≸":"ntlg","≷":"gl","≹":"ntgl","≺":"pr","⊀":"npr","≻":"sc","⊁":"nsc","≼":"prcue","⋠":"nprcue","≽":"sccue","⋡":"nsccue","≾":"prsim","≿":"scsim","≿̸":"NotSucceedsTilde","⊂":"sub","⊄":"nsub","⊂⃒":"vnsub","⊃":"sup","⊅":"nsup","⊃⃒":"vnsup","⊆":"sube","⊈":"nsube","⊇":"supe","⊉":"nsupe","⊊︀":"vsubne","⊊":"subne","⊋︀":"vsupne","⊋":"supne","⊍":"cupdot","⊎":"uplus","⊏":"sqsub","⊏̸":"NotSquareSubset","⊐":"sqsup","⊐̸":"NotSquareSuperset","⊑":"sqsube","⋢":"nsqsube","⊒":"sqsupe","⋣":"nsqsupe","⊓":"sqcap","⊓︀":"sqcaps","⊔":"sqcup","⊔︀":"sqcups","⊕":"oplus","⊖":"ominus","⊗":"otimes","⊘":"osol","⊙":"odot","⊚":"ocir","⊛":"oast","⊝":"odash","⊞":"plusb","⊟":"minusb","⊠":"timesb","⊡":"sdotb","⊢":"vdash","⊬":"nvdash","⊣":"dashv","⊤":"top","⊥":"bot","⊧":"models","⊨":"vDash","⊭":"nvDash","⊩":"Vdash","⊮":"nVdash","⊪":"Vvdash","⊫":"VDash","⊯":"nVDash","⊰":"prurel","⊲":"vltri","⋪":"nltri","⊳":"vrtri","⋫":"nrtri","⊴":"ltrie","⋬":"nltrie","⊴⃒":"nvltrie","⊵":"rtrie","⋭":"nrtrie","⊵⃒":"nvrtrie","⊶":"origof","⊷":"imof","⊸":"mumap","⊹":"hercon","⊺":"intcal","⊻":"veebar","⊽":"barvee","⊾":"angrtvb","⊿":"lrtri","⋀":"Wedge","⋁":"Vee","⋂":"xcap","⋃":"xcup","⋄":"diam","⋅":"sdot","⋆":"Star","⋇":"divonx","⋈":"bowtie","⋉":"ltimes","⋊":"rtimes","⋋":"lthree","⋌":"rthree","⋍":"bsime","⋎":"cuvee","⋏":"cuwed","⋐":"Sub","⋑":"Sup","⋒":"Cap","⋓":"Cup","⋔":"fork","⋕":"epar","⋖":"ltdot","⋗":"gtdot","⋘":"Ll","⋘̸":"nLl","⋙":"Gg","⋙̸":"nGg","⋚︀":"lesg","⋚":"leg","⋛":"gel","⋛︀":"gesl","⋞":"cuepr","⋟":"cuesc","⋦":"lnsim","⋧":"gnsim","⋨":"prnsim","⋩":"scnsim","⋮":"vellip","⋯":"ctdot","⋰":"utdot","⋱":"dtdot","⋲":"disin","⋳":"isinsv","⋴":"isins","⋵":"isindot","⋵̸":"notindot","⋶":"notinvc","⋷":"notinvb","⋹":"isinE","⋹̸":"notinE","⋺":"nisd","⋻":"xnis","⋼":"nis","⋽":"notnivc","⋾":"notnivb","⌅":"barwed","⌆":"Barwed","⌌":"drcrop","⌍":"dlcrop","⌎":"urcrop","⌏":"ulcrop","⌐":"bnot","⌒":"profline","⌓":"profsurf","⌕":"telrec","⌖":"target","⌜":"ulcorn","⌝":"urcorn","⌞":"dlcorn","⌟":"drcorn","⌢":"frown","⌣":"smile","⌭":"cylcty","⌮":"profalar","⌶":"topbot","⌽":"ovbar","⌿":"solbar","⍼":"angzarr","⎰":"lmoust","⎱":"rmoust","⎴":"tbrk","⎵":"bbrk","⎶":"bbrktbrk","⏜":"OverParenthesis","⏝":"UnderParenthesis","⏞":"OverBrace","⏟":"UnderBrace","⏢":"trpezium","⏧":"elinters","␣":"blank","─":"boxh","│":"boxv","┌":"boxdr","┐":"boxdl","└":"boxur","┘":"boxul","├":"boxvr","┤":"boxvl","┬":"boxhd","┴":"boxhu","┼":"boxvh","═":"boxH","║":"boxV","╒":"boxdR","╓":"boxDr","╔":"boxDR","╕":"boxdL","╖":"boxDl","╗":"boxDL","╘":"boxuR","╙":"boxUr","╚":"boxUR","╛":"boxuL","╜":"boxUl","╝":"boxUL","╞":"boxvR","╟":"boxVr","╠":"boxVR","╡":"boxvL","╢":"boxVl","╣":"boxVL","╤":"boxHd","╥":"boxhD","╦":"boxHD","╧":"boxHu","╨":"boxhU","╩":"boxHU","╪":"boxvH","╫":"boxVh","╬":"boxVH","▀":"uhblk","▄":"lhblk","█":"block","░":"blk14","▒":"blk12","▓":"blk34","□":"squ","▪":"squf","▫":"EmptyVerySmallSquare","▭":"rect","▮":"marker","▱":"fltns","△":"xutri","▴":"utrif","▵":"utri","▸":"rtrif","▹":"rtri","▽":"xdtri","▾":"dtrif","▿":"dtri","◂":"ltrif","◃":"ltri","◊":"loz","○":"cir","◬":"tridot","◯":"xcirc","◸":"ultri","◹":"urtri","◺":"lltri","◻":"EmptySmallSquare","◼":"FilledSmallSquare","★":"starf","☆":"star","☎":"phone","♀":"female","♂":"male","♠":"spades","♣":"clubs","♥":"hearts","♦":"diams","♪":"sung","✓":"check","✗":"cross","✠":"malt","✶":"sext","❘":"VerticalSeparator","⟈":"bsolhsub","⟉":"suphsol","⟵":"xlarr","⟶":"xrarr","⟷":"xharr","⟸":"xlArr","⟹":"xrArr","⟺":"xhArr","⟼":"xmap","⟿":"dzigrarr","⤂":"nvlArr","⤃":"nvrArr","⤄":"nvHarr","⤅":"Map","⤌":"lbarr","⤍":"rbarr","⤎":"lBarr","⤏":"rBarr","⤐":"RBarr","⤑":"DDotrahd","⤒":"UpArrowBar","⤓":"DownArrowBar","⤖":"Rarrtl","⤙":"latail","⤚":"ratail","⤛":"lAtail","⤜":"rAtail","⤝":"larrfs","⤞":"rarrfs","⤟":"larrbfs","⤠":"rarrbfs","⤣":"nwarhk","⤤":"nearhk","⤥":"searhk","⤦":"swarhk","⤧":"nwnear","⤨":"toea","⤩":"tosa","⤪":"swnwar","⤳":"rarrc","⤳̸":"nrarrc","⤵":"cudarrr","⤶":"ldca","⤷":"rdca","⤸":"cudarrl","⤹":"larrpl","⤼":"curarrm","⤽":"cularrp","⥅":"rarrpl","⥈":"harrcir","⥉":"Uarrocir","⥊":"lurdshar","⥋":"ldrushar","⥎":"LeftRightVector","⥏":"RightUpDownVector","⥐":"DownLeftRightVector","⥑":"LeftUpDownVector","⥒":"LeftVectorBar","⥓":"RightVectorBar","⥔":"RightUpVectorBar","⥕":"RightDownVectorBar","⥖":"DownLeftVectorBar","⥗":"DownRightVectorBar","⥘":"LeftUpVectorBar","⥙":"LeftDownVectorBar","⥚":"LeftTeeVector","⥛":"RightTeeVector","⥜":"RightUpTeeVector","⥝":"RightDownTeeVector","⥞":"DownLeftTeeVector","⥟":"DownRightTeeVector","⥠":"LeftUpTeeVector","⥡":"LeftDownTeeVector","⥢":"lHar","⥣":"uHar","⥤":"rHar","⥥":"dHar","⥦":"luruhar","⥧":"ldrdhar","⥨":"ruluhar","⥩":"rdldhar","⥪":"lharul","⥫":"llhard","⥬":"rharul","⥭":"lrhard","⥮":"udhar","⥯":"duhar","⥰":"RoundImplies","⥱":"erarr","⥲":"simrarr","⥳":"larrsim","⥴":"rarrsim","⥵":"rarrap","⥶":"ltlarr","⥸":"gtrarr","⥹":"subrarr","⥻":"suplarr","⥼":"lfisht","⥽":"rfisht","⥾":"ufisht","⥿":"dfisht","⦚":"vzigzag","⦜":"vangrt","⦝":"angrtvbd","⦤":"ange","⦥":"range","⦦":"dwangle","⦧":"uwangle","⦨":"angmsdaa","⦩":"angmsdab","⦪":"angmsdac","⦫":"angmsdad","⦬":"angmsdae","⦭":"angmsdaf","⦮":"angmsdag","⦯":"angmsdah","⦰":"bemptyv","⦱":"demptyv","⦲":"cemptyv","⦳":"raemptyv","⦴":"laemptyv","⦵":"ohbar","⦶":"omid","⦷":"opar","⦹":"operp","⦻":"olcross","⦼":"odsold","⦾":"olcir","⦿":"ofcir","⧀":"olt","⧁":"ogt","⧂":"cirscir","⧃":"cirE","⧄":"solb","⧅":"bsolb","⧉":"boxbox","⧍":"trisb","⧎":"rtriltri","⧏":"LeftTriangleBar","⧏̸":"NotLeftTriangleBar","⧐":"RightTriangleBar","⧐̸":"NotRightTriangleBar","⧜":"iinfin","⧝":"infintie","⧞":"nvinfin","⧣":"eparsl","⧤":"smeparsl","⧥":"eqvparsl","⧫":"lozf","⧴":"RuleDelayed","⧶":"dsol","⨀":"xodot","⨁":"xoplus","⨂":"xotime","⨄":"xuplus","⨆":"xsqcup","⨍":"fpartint","⨐":"cirfnint","⨑":"awint","⨒":"rppolint","⨓":"scpolint","⨔":"npolint","⨕":"pointint","⨖":"quatint","⨗":"intlarhk","⨢":"pluscir","⨣":"plusacir","⨤":"simplus","⨥":"plusdu","⨦":"plussim","⨧":"plustwo","⨩":"mcomma","⨪":"minusdu","⨭":"loplus","⨮":"roplus","⨯":"Cross","⨰":"timesd","⨱":"timesbar","⨳":"smashp","⨴":"lotimes","⨵":"rotimes","⨶":"otimesas","⨷":"Otimes","⨸":"odiv","⨹":"triplus","⨺":"triminus","⨻":"tritime","⨼":"iprod","⨿":"amalg","⩀":"capdot","⩂":"ncup","⩃":"ncap","⩄":"capand","⩅":"cupor","⩆":"cupcap","⩇":"capcup","⩈":"cupbrcap","⩉":"capbrcup","⩊":"cupcup","⩋":"capcap","⩌":"ccups","⩍":"ccaps","⩐":"ccupssm","⩓":"And","⩔":"Or","⩕":"andand","⩖":"oror","⩗":"orslope","⩘":"andslope","⩚":"andv","⩛":"orv","⩜":"andd","⩝":"ord","⩟":"wedbar","⩦":"sdote","⩪":"simdot","⩭":"congdot","⩭̸":"ncongdot","⩮":"easter","⩯":"apacir","⩰":"apE","⩰̸":"napE","⩱":"eplus","⩲":"pluse","⩳":"Esim","⩷":"eDDot","⩸":"equivDD","⩹":"ltcir","⩺":"gtcir","⩻":"ltquest","⩼":"gtquest","⩽":"les","⩽̸":"nles","⩾":"ges","⩾̸":"nges","⩿":"lesdot","⪀":"gesdot","⪁":"lesdoto","⪂":"gesdoto","⪃":"lesdotor","⪄":"gesdotol","⪅":"lap","⪆":"gap","⪇":"lne","⪈":"gne","⪉":"lnap","⪊":"gnap","⪋":"lEg","⪌":"gEl","⪍":"lsime","⪎":"gsime","⪏":"lsimg","⪐":"gsiml","⪑":"lgE","⪒":"glE","⪓":"lesges","⪔":"gesles","⪕":"els","⪖":"egs","⪗":"elsdot","⪘":"egsdot","⪙":"el","⪚":"eg","⪝":"siml","⪞":"simg","⪟":"simlE","⪠":"simgE","⪡":"LessLess","⪡̸":"NotNestedLessLess","⪢":"GreaterGreater","⪢̸":"NotNestedGreaterGreater","⪤":"glj","⪥":"gla","⪦":"ltcc","⪧":"gtcc","⪨":"lescc","⪩":"gescc","⪪":"smt","⪫":"lat","⪬":"smte","⪬︀":"smtes","⪭":"late","⪭︀":"lates","⪮":"bumpE","⪯":"pre","⪯̸":"npre","⪰":"sce","⪰̸":"nsce","⪳":"prE","⪴":"scE","⪵":"prnE","⪶":"scnE","⪷":"prap","⪸":"scap","⪹":"prnap","⪺":"scnap","⪻":"Pr","⪼":"Sc","⪽":"subdot","⪾":"supdot","⪿":"subplus","⫀":"supplus","⫁":"submult","⫂":"supmult","⫃":"subedot","⫄":"supedot","⫅":"subE","⫅̸":"nsubE","⫆":"supE","⫆̸":"nsupE","⫇":"subsim","⫈":"supsim","⫋︀":"vsubnE","⫋":"subnE","⫌︀":"vsupnE","⫌":"supnE","⫏":"csub","⫐":"csup","⫑":"csube","⫒":"csupe","⫓":"subsup","⫔":"supsub","⫕":"subsub","⫖":"supsup","⫗":"suphsub","⫘":"supdsub","⫙":"forkv","⫚":"topfork","⫛":"mlcp","⫤":"Dashv","⫦":"Vdashl","⫧":"Barv","⫨":"vBar","⫩":"vBarv","⫫":"Vbar","⫬":"Not","⫭":"bNot","⫮":"rnmid","⫯":"cirmid","⫰":"midcir","⫱":"topcir","⫲":"nhpar","⫳":"parsim","⫽":"parsl","⫽⃥":"nparsl","♭":"flat","♮":"natur","♯":"sharp","¤":"curren","¢":"cent",$:"dollar","£":"pound","¥":"yen","€":"euro","¹":"sup1","½":"half","⅓":"frac13","¼":"frac14","⅕":"frac15","⅙":"frac16","⅛":"frac18","²":"sup2","⅔":"frac23","⅖":"frac25","³":"sup3","¾":"frac34","⅗":"frac35","⅜":"frac38","⅘":"frac45","⅚":"frac56","⅝":"frac58","⅞":"frac78","𝒶":"ascr","𝕒":"aopf","𝔞":"afr","𝔸":"Aopf","𝔄":"Afr","𝒜":"Ascr","ª":"ordf","á":"aacute","Á":"Aacute","à":"agrave","À":"Agrave","ă":"abreve","Ă":"Abreve","â":"acirc","Â":"Acirc","å":"aring","Å":"angst","ä":"auml","Ä":"Auml","ã":"atilde","Ã":"Atilde","ą":"aogon","Ą":"Aogon","ā":"amacr","Ā":"Amacr","æ":"aelig","Æ":"AElig","𝒷":"bscr","𝕓":"bopf","𝔟":"bfr","𝔹":"Bopf","ℬ":"Bscr","𝔅":"Bfr","𝔠":"cfr","𝒸":"cscr","𝕔":"copf","ℭ":"Cfr","𝒞":"Cscr","ℂ":"Copf","ć":"cacute","Ć":"Cacute","ĉ":"ccirc","Ĉ":"Ccirc","č":"ccaron","Č":"Ccaron","ċ":"cdot","Ċ":"Cdot","ç":"ccedil","Ç":"Ccedil","℅":"incare","𝔡":"dfr","ⅆ":"dd","𝕕":"dopf","𝒹":"dscr","𝒟":"Dscr","𝔇":"Dfr","ⅅ":"DD","𝔻":"Dopf","ď":"dcaron","Ď":"Dcaron","đ":"dstrok","Đ":"Dstrok","ð":"eth","Ð":"ETH","ⅇ":"ee","ℯ":"escr","𝔢":"efr","𝕖":"eopf","ℰ":"Escr","𝔈":"Efr","𝔼":"Eopf","é":"eacute","É":"Eacute","è":"egrave","È":"Egrave","ê":"ecirc","Ê":"Ecirc","ě":"ecaron","Ě":"Ecaron","ë":"euml","Ë":"Euml","ė":"edot","Ė":"Edot","ę":"eogon","Ę":"Eogon","ē":"emacr","Ē":"Emacr","𝔣":"ffr","𝕗":"fopf","𝒻":"fscr","𝔉":"Ffr","𝔽":"Fopf","ℱ":"Fscr","ff":"fflig","ffi":"ffilig","ffl":"ffllig","fi":"filig",fj:"fjlig","fl":"fllig","ƒ":"fnof","ℊ":"gscr","𝕘":"gopf","𝔤":"gfr","𝒢":"Gscr","𝔾":"Gopf","𝔊":"Gfr","ǵ":"gacute","ğ":"gbreve","Ğ":"Gbreve","ĝ":"gcirc","Ĝ":"Gcirc","ġ":"gdot","Ġ":"Gdot","Ģ":"Gcedil","𝔥":"hfr","ℎ":"planckh","𝒽":"hscr","𝕙":"hopf","ℋ":"Hscr","ℌ":"Hfr","ℍ":"Hopf","ĥ":"hcirc","Ĥ":"Hcirc","ℏ":"hbar","ħ":"hstrok","Ħ":"Hstrok","𝕚":"iopf","𝔦":"ifr","𝒾":"iscr","ⅈ":"ii","𝕀":"Iopf","ℐ":"Iscr","ℑ":"Im","í":"iacute","Í":"Iacute","ì":"igrave","Ì":"Igrave","î":"icirc","Î":"Icirc","ï":"iuml","Ï":"Iuml","ĩ":"itilde","Ĩ":"Itilde","İ":"Idot","į":"iogon","Į":"Iogon","ī":"imacr","Ī":"Imacr","ij":"ijlig","IJ":"IJlig","ı":"imath","𝒿":"jscr","𝕛":"jopf","𝔧":"jfr","𝒥":"Jscr","𝔍":"Jfr","𝕁":"Jopf","ĵ":"jcirc","Ĵ":"Jcirc","ȷ":"jmath","𝕜":"kopf","𝓀":"kscr","𝔨":"kfr","𝒦":"Kscr","𝕂":"Kopf","𝔎":"Kfr","ķ":"kcedil","Ķ":"Kcedil","𝔩":"lfr","𝓁":"lscr","ℓ":"ell","𝕝":"lopf","ℒ":"Lscr","𝔏":"Lfr","𝕃":"Lopf","ĺ":"lacute","Ĺ":"Lacute","ľ":"lcaron","Ľ":"Lcaron","ļ":"lcedil","Ļ":"Lcedil","ł":"lstrok","Ł":"Lstrok","ŀ":"lmidot","Ŀ":"Lmidot","𝔪":"mfr","𝕞":"mopf","𝓂":"mscr","𝔐":"Mfr","𝕄":"Mopf","ℳ":"Mscr","𝔫":"nfr","𝕟":"nopf","𝓃":"nscr","ℕ":"Nopf","𝒩":"Nscr","𝔑":"Nfr","ń":"nacute","Ń":"Nacute","ň":"ncaron","Ň":"Ncaron","ñ":"ntilde","Ñ":"Ntilde","ņ":"ncedil","Ņ":"Ncedil","№":"numero","ŋ":"eng","Ŋ":"ENG","𝕠":"oopf","𝔬":"ofr","ℴ":"oscr","𝒪":"Oscr","𝔒":"Ofr","𝕆":"Oopf","º":"ordm","ó":"oacute","Ó":"Oacute","ò":"ograve","Ò":"Ograve","ô":"ocirc","Ô":"Ocirc","ö":"ouml","Ö":"Ouml","ő":"odblac","Ő":"Odblac","õ":"otilde","Õ":"Otilde","ø":"oslash","Ø":"Oslash","ō":"omacr","Ō":"Omacr","œ":"oelig","Œ":"OElig","𝔭":"pfr","𝓅":"pscr","𝕡":"popf","ℙ":"Popf","𝔓":"Pfr","𝒫":"Pscr","𝕢":"qopf","𝔮":"qfr","𝓆":"qscr","𝒬":"Qscr","𝔔":"Qfr","ℚ":"Qopf","ĸ":"kgreen","𝔯":"rfr","𝕣":"ropf","𝓇":"rscr","ℛ":"Rscr","ℜ":"Re","ℝ":"Ropf","ŕ":"racute","Ŕ":"Racute","ř":"rcaron","Ř":"Rcaron","ŗ":"rcedil","Ŗ":"Rcedil","𝕤":"sopf","𝓈":"sscr","𝔰":"sfr","𝕊":"Sopf","𝔖":"Sfr","𝒮":"Sscr","Ⓢ":"oS","ś":"sacute","Ś":"Sacute","ŝ":"scirc","Ŝ":"Scirc","š":"scaron","Š":"Scaron","ş":"scedil","Ş":"Scedil","ß":"szlig","𝔱":"tfr","𝓉":"tscr","𝕥":"topf","𝒯":"Tscr","𝔗":"Tfr","𝕋":"Topf","ť":"tcaron","Ť":"Tcaron","ţ":"tcedil","Ţ":"Tcedil","™":"trade","ŧ":"tstrok","Ŧ":"Tstrok","𝓊":"uscr","𝕦":"uopf","𝔲":"ufr","𝕌":"Uopf","𝔘":"Ufr","𝒰":"Uscr","ú":"uacute","Ú":"Uacute","ù":"ugrave","Ù":"Ugrave","ŭ":"ubreve","Ŭ":"Ubreve","û":"ucirc","Û":"Ucirc","ů":"uring","Ů":"Uring","ü":"uuml","Ü":"Uuml","ű":"udblac","Ű":"Udblac","ũ":"utilde","Ũ":"Utilde","ų":"uogon","Ų":"Uogon","ū":"umacr","Ū":"Umacr","𝔳":"vfr","𝕧":"vopf","𝓋":"vscr","𝔙":"Vfr","𝕍":"Vopf","𝒱":"Vscr","𝕨":"wopf","𝓌":"wscr","𝔴":"wfr","𝒲":"Wscr","𝕎":"Wopf","𝔚":"Wfr","ŵ":"wcirc","Ŵ":"Wcirc","𝔵":"xfr","𝓍":"xscr","𝕩":"xopf","𝕏":"Xopf","𝔛":"Xfr","𝒳":"Xscr","𝔶":"yfr","𝓎":"yscr","𝕪":"yopf","𝒴":"Yscr","𝔜":"Yfr","𝕐":"Yopf","ý":"yacute","Ý":"Yacute","ŷ":"ycirc","Ŷ":"Ycirc","ÿ":"yuml","Ÿ":"Yuml","𝓏":"zscr","𝔷":"zfr","𝕫":"zopf","ℨ":"Zfr","ℤ":"Zopf","𝒵":"Zscr","ź":"zacute","Ź":"Zacute","ž":"zcaron","Ž":"Zcaron","ż":"zdot","Ż":"Zdot","Ƶ":"imped","þ":"thorn","Þ":"THORN","ʼn":"napos","α":"alpha","Α":"Alpha","β":"beta","Β":"Beta","γ":"gamma","Γ":"Gamma","δ":"delta","Δ":"Delta","ε":"epsi","ϵ":"epsiv","Ε":"Epsilon","ϝ":"gammad","Ϝ":"Gammad","ζ":"zeta","Ζ":"Zeta","η":"eta","Η":"Eta","θ":"theta","ϑ":"thetav","Θ":"Theta","ι":"iota","Ι":"Iota","κ":"kappa","ϰ":"kappav","Κ":"Kappa","λ":"lambda","Λ":"Lambda","μ":"mu","µ":"micro","Μ":"Mu","ν":"nu","Ν":"Nu","ξ":"xi","Ξ":"Xi","ο":"omicron","Ο":"Omicron","π":"pi","ϖ":"piv","Π":"Pi","ρ":"rho","ϱ":"rhov","Ρ":"Rho","σ":"sigma","Σ":"Sigma","ς":"sigmaf","τ":"tau","Τ":"Tau","υ":"upsi","Υ":"Upsilon","ϒ":"Upsi","φ":"phi","ϕ":"phiv","Φ":"Phi","χ":"chi","Χ":"Chi","ψ":"psi","Ψ":"Psi","ω":"omega","Ω":"ohm","а":"acy","А":"Acy","б":"bcy","Б":"Bcy","в":"vcy","В":"Vcy","г":"gcy","Г":"Gcy","ѓ":"gjcy","Ѓ":"GJcy","д":"dcy","Д":"Dcy","ђ":"djcy","Ђ":"DJcy","е":"iecy","Е":"IEcy","ё":"iocy","Ё":"IOcy","є":"jukcy","Є":"Jukcy","ж":"zhcy","Ж":"ZHcy","з":"zcy","З":"Zcy","ѕ":"dscy","Ѕ":"DScy","и":"icy","И":"Icy","і":"iukcy","І":"Iukcy","ї":"yicy","Ї":"YIcy","й":"jcy","Й":"Jcy","ј":"jsercy","Ј":"Jsercy","к":"kcy","К":"Kcy","ќ":"kjcy","Ќ":"KJcy","л":"lcy","Л":"Lcy","љ":"ljcy","Љ":"LJcy","м":"mcy","М":"Mcy","н":"ncy","Н":"Ncy","њ":"njcy","Њ":"NJcy","о":"ocy","О":"Ocy","п":"pcy","П":"Pcy","р":"rcy","Р":"Rcy","с":"scy","С":"Scy","т":"tcy","Т":"Tcy","ћ":"tshcy","Ћ":"TSHcy","у":"ucy","У":"Ucy","ў":"ubrcy","Ў":"Ubrcy","ф":"fcy","Ф":"Fcy","х":"khcy","Х":"KHcy","ц":"tscy","Ц":"TScy","ч":"chcy","Ч":"CHcy","џ":"dzcy","Џ":"DZcy","ш":"shcy","Ш":"SHcy","щ":"shchcy","Щ":"SHCHcy","ъ":"hardcy","Ъ":"HARDcy","ы":"ycy","Ы":"Ycy","ь":"softcy","Ь":"SOFTcy","э":"ecy","Э":"Ecy","ю":"yucy","Ю":"YUcy","я":"yacy","Я":"YAcy","ℵ":"aleph","ℶ":"beth","ℷ":"gimel","ℸ":"daleth"},h=/["&'<>`]/g,d={'"':""","&":"&","'":"'","<":"<",">":">","`":"`"},p=/&#(?:[xX][^a-fA-F0-9]|[^0-9xX])/,g=/[\0-\x08\x0B\x0E-\x1F\x7F-\x9F\uFDD0-\uFDEF\uFFFE\uFFFF]|[\uD83F\uD87F\uD8BF\uD8FF\uD93F\uD97F\uD9BF\uD9FF\uDA3F\uDA7F\uDABF\uDAFF\uDB3F\uDB7F\uDBBF\uDBFF][\uDFFE\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]/,y=/&(CounterClockwiseContourIntegral|DoubleLongLeftRightArrow|ClockwiseContourIntegral|NotNestedGreaterGreater|NotSquareSupersetEqual|DiacriticalDoubleAcute|NotRightTriangleEqual|NotSucceedsSlantEqual|NotPrecedesSlantEqual|CloseCurlyDoubleQuote|NegativeVeryThinSpace|DoubleContourIntegral|FilledVerySmallSquare|CapitalDifferentialD|OpenCurlyDoubleQuote|EmptyVerySmallSquare|NestedGreaterGreater|DoubleLongRightArrow|NotLeftTriangleEqual|NotGreaterSlantEqual|ReverseUpEquilibrium|DoubleLeftRightArrow|NotSquareSubsetEqual|NotDoubleVerticalBar|RightArrowLeftArrow|NotGreaterFullEqual|NotRightTriangleBar|SquareSupersetEqual|DownLeftRightVector|DoubleLongLeftArrow|leftrightsquigarrow|LeftArrowRightArrow|NegativeMediumSpace|blacktriangleright|RightDownVectorBar|PrecedesSlantEqual|RightDoubleBracket|SucceedsSlantEqual|NotLeftTriangleBar|RightTriangleEqual|SquareIntersection|RightDownTeeVector|ReverseEquilibrium|NegativeThickSpace|longleftrightarrow|Longleftrightarrow|LongLeftRightArrow|DownRightTeeVector|DownRightVectorBar|GreaterSlantEqual|SquareSubsetEqual|LeftDownVectorBar|LeftDoubleBracket|VerticalSeparator|rightleftharpoons|NotGreaterGreater|NotSquareSuperset|blacktriangleleft|blacktriangledown|NegativeThinSpace|LeftDownTeeVector|NotLessSlantEqual|leftrightharpoons|DoubleUpDownArrow|DoubleVerticalBar|LeftTriangleEqual|FilledSmallSquare|twoheadrightarrow|NotNestedLessLess|DownLeftTeeVector|DownLeftVectorBar|RightAngleBracket|NotTildeFullEqual|NotReverseElement|RightUpDownVector|DiacriticalTilde|NotSucceedsTilde|circlearrowright|NotPrecedesEqual|rightharpoondown|DoubleRightArrow|NotSucceedsEqual|NonBreakingSpace|NotRightTriangle|LessEqualGreater|RightUpTeeVector|LeftAngleBracket|GreaterFullEqual|DownArrowUpArrow|RightUpVectorBar|twoheadleftarrow|GreaterEqualLess|downharpoonright|RightTriangleBar|ntrianglerighteq|NotSupersetEqual|LeftUpDownVector|DiacriticalAcute|rightrightarrows|vartriangleright|UpArrowDownArrow|DiacriticalGrave|UnderParenthesis|EmptySmallSquare|LeftUpVectorBar|leftrightarrows|DownRightVector|downharpoonleft|trianglerighteq|ShortRightArrow|OverParenthesis|DoubleLeftArrow|DoubleDownArrow|NotSquareSubset|bigtriangledown|ntrianglelefteq|UpperRightArrow|curvearrowright|vartriangleleft|NotLeftTriangle|nleftrightarrow|LowerRightArrow|NotHumpDownHump|NotGreaterTilde|rightthreetimes|LeftUpTeeVector|NotGreaterEqual|straightepsilon|LeftTriangleBar|rightsquigarrow|ContourIntegral|rightleftarrows|CloseCurlyQuote|RightDownVector|LeftRightVector|nLeftrightarrow|leftharpoondown|circlearrowleft|SquareSuperset|OpenCurlyQuote|hookrightarrow|HorizontalLine|DiacriticalDot|NotLessGreater|ntriangleright|DoubleRightTee|InvisibleComma|InvisibleTimes|LowerLeftArrow|DownLeftVector|NotSubsetEqual|curvearrowleft|trianglelefteq|NotVerticalBar|TildeFullEqual|downdownarrows|NotGreaterLess|RightTeeVector|ZeroWidthSpace|looparrowright|LongRightArrow|doublebarwedge|ShortLeftArrow|ShortDownArrow|RightVectorBar|GreaterGreater|ReverseElement|rightharpoonup|LessSlantEqual|leftthreetimes|upharpoonright|rightarrowtail|LeftDownVector|Longrightarrow|NestedLessLess|UpperLeftArrow|nshortparallel|leftleftarrows|leftrightarrow|Leftrightarrow|LeftRightArrow|longrightarrow|upharpoonleft|RightArrowBar|ApplyFunction|LeftTeeVector|leftarrowtail|NotEqualTilde|varsubsetneqq|varsupsetneqq|RightTeeArrow|SucceedsEqual|SucceedsTilde|LeftVectorBar|SupersetEqual|hookleftarrow|DifferentialD|VerticalTilde|VeryThinSpace|blacktriangle|bigtriangleup|LessFullEqual|divideontimes|leftharpoonup|UpEquilibrium|ntriangleleft|RightTriangle|measuredangle|shortparallel|longleftarrow|Longleftarrow|LongLeftArrow|DoubleLeftTee|Poincareplane|PrecedesEqual|triangleright|DoubleUpArrow|RightUpVector|fallingdotseq|looparrowleft|PrecedesTilde|NotTildeEqual|NotTildeTilde|smallsetminus|Proportional|triangleleft|triangledown|UnderBracket|NotHumpEqual|exponentiale|ExponentialE|NotLessTilde|HilbertSpace|RightCeiling|blacklozenge|varsupsetneq|HumpDownHump|GreaterEqual|VerticalLine|LeftTeeArrow|NotLessEqual|DownTeeArrow|LeftTriangle|varsubsetneq|Intersection|NotCongruent|DownArrowBar|LeftUpVector|LeftArrowBar|risingdotseq|GreaterTilde|RoundImplies|SquareSubset|ShortUpArrow|NotSuperset|quaternions|precnapprox|backepsilon|preccurlyeq|OverBracket|blacksquare|MediumSpace|VerticalBar|circledcirc|circleddash|CircleMinus|CircleTimes|LessGreater|curlyeqprec|curlyeqsucc|diamondsuit|UpDownArrow|Updownarrow|RuleDelayed|Rrightarrow|updownarrow|RightVector|nRightarrow|nrightarrow|eqslantless|LeftCeiling|Equilibrium|SmallCircle|expectation|NotSucceeds|thickapprox|GreaterLess|SquareUnion|NotPrecedes|NotLessLess|straightphi|succnapprox|succcurlyeq|SubsetEqual|sqsupseteq|Proportion|Laplacetrf|ImaginaryI|supsetneqq|NotGreater|gtreqqless|NotElement|ThickSpace|TildeEqual|TildeTilde|Fouriertrf|rmoustache|EqualTilde|eqslantgtr|UnderBrace|LeftVector|UpArrowBar|nLeftarrow|nsubseteqq|subsetneqq|nsupseteqq|nleftarrow|succapprox|lessapprox|UpTeeArrow|upuparrows|curlywedge|lesseqqgtr|varepsilon|varnothing|RightFloor|complement|CirclePlus|sqsubseteq|Lleftarrow|circledast|RightArrow|Rightarrow|rightarrow|lmoustache|Bernoullis|precapprox|mapstoleft|mapstodown|longmapsto|dotsquare|downarrow|DoubleDot|nsubseteq|supsetneq|leftarrow|nsupseteq|subsetneq|ThinSpace|ngeqslant|subseteqq|HumpEqual|NotSubset|triangleq|NotCupCap|lesseqgtr|heartsuit|TripleDot|Leftarrow|Coproduct|Congruent|varpropto|complexes|gvertneqq|LeftArrow|LessTilde|supseteqq|MinusPlus|CircleDot|nleqslant|NotExists|gtreqless|nparallel|UnionPlus|LeftFloor|checkmark|CenterDot|centerdot|Mellintrf|gtrapprox|bigotimes|OverBrace|spadesuit|therefore|pitchfork|rationals|PlusMinus|Backslash|Therefore|DownBreve|backsimeq|backprime|DownArrow|nshortmid|Downarrow|lvertneqq|eqvparsl|imagline|imagpart|infintie|integers|Integral|intercal|LessLess|Uarrocir|intlarhk|sqsupset|angmsdaf|sqsubset|llcorner|vartheta|cupbrcap|lnapprox|Superset|SuchThat|succnsim|succneqq|angmsdag|biguplus|curlyvee|trpezium|Succeeds|NotTilde|bigwedge|angmsdah|angrtvbd|triminus|cwconint|fpartint|lrcorner|smeparsl|subseteq|urcorner|lurdshar|laemptyv|DDotrahd|approxeq|ldrushar|awconint|mapstoup|backcong|shortmid|triangle|geqslant|gesdotol|timesbar|circledR|circledS|setminus|multimap|naturals|scpolint|ncongdot|RightTee|boxminus|gnapprox|boxtimes|andslope|thicksim|angmsdaa|varsigma|cirfnint|rtriltri|angmsdab|rppolint|angmsdac|barwedge|drbkarow|clubsuit|thetasym|bsolhsub|capbrcup|dzigrarr|doteqdot|DotEqual|dotminus|UnderBar|NotEqual|realpart|otimesas|ulcorner|hksearow|hkswarow|parallel|PartialD|elinters|emptyset|plusacir|bbrktbrk|angmsdad|pointint|bigoplus|angmsdae|Precedes|bigsqcup|varkappa|notindot|supseteq|precneqq|precnsim|profalar|profline|profsurf|leqslant|lesdotor|raemptyv|subplus|notnivb|notnivc|subrarr|zigrarr|vzigzag|submult|subedot|Element|between|cirscir|larrbfs|larrsim|lotimes|lbrksld|lbrkslu|lozenge|ldrdhar|dbkarow|bigcirc|epsilon|simrarr|simplus|ltquest|Epsilon|luruhar|gtquest|maltese|npolint|eqcolon|npreceq|bigodot|ddagger|gtrless|bnequiv|harrcir|ddotseq|equivDD|backsim|demptyv|nsqsube|nsqsupe|Upsilon|nsubset|upsilon|minusdu|nsucceq|swarrow|nsupset|coloneq|searrow|boxplus|napprox|natural|asympeq|alefsym|congdot|nearrow|bigstar|diamond|supplus|tritime|LeftTee|nvinfin|triplus|NewLine|nvltrie|nvrtrie|nwarrow|nexists|Diamond|ruluhar|Implies|supmult|angzarr|suplarr|suphsub|questeq|because|digamma|Because|olcross|bemptyv|omicron|Omicron|rotimes|NoBreak|intprod|angrtvb|orderof|uwangle|suphsol|lesdoto|orslope|DownTee|realine|cudarrl|rdldhar|OverBar|supedot|lessdot|supdsub|topfork|succsim|rbrkslu|rbrksld|pertenk|cudarrr|isindot|planckh|lessgtr|pluscir|gesdoto|plussim|plustwo|lesssim|cularrp|rarrsim|Cayleys|notinva|notinvb|notinvc|UpArrow|Uparrow|uparrow|NotLess|dwangle|precsim|Product|curarrm|Cconint|dotplus|rarrbfs|ccupssm|Cedilla|cemptyv|notniva|quatint|frac35|frac38|frac45|frac56|frac58|frac78|tridot|xoplus|gacute|gammad|Gammad|lfisht|lfloor|bigcup|sqsupe|gbreve|Gbreve|lharul|sqsube|sqcups|Gcedil|apacir|llhard|lmidot|Lmidot|lmoust|andand|sqcaps|approx|Abreve|spades|circeq|tprime|divide|topcir|Assign|topbot|gesdot|divonx|xuplus|timesd|gesles|atilde|solbar|SOFTcy|loplus|timesb|lowast|lowbar|dlcorn|dlcrop|softcy|dollar|lparlt|thksim|lrhard|Atilde|lsaquo|smashp|bigvee|thinsp|wreath|bkarow|lsquor|lstrok|Lstrok|lthree|ltimes|ltlarr|DotDot|simdot|ltrPar|weierp|xsqcup|angmsd|sigmav|sigmaf|zeetrf|Zcaron|zcaron|mapsto|vsupne|thetav|cirmid|marker|mcomma|Zacute|vsubnE|there4|gtlPar|vsubne|bottom|gtrarr|SHCHcy|shchcy|midast|midcir|middot|minusb|minusd|gtrdot|bowtie|sfrown|mnplus|models|colone|seswar|Colone|mstpos|searhk|gtrsim|nacute|Nacute|boxbox|telrec|hairsp|Tcedil|nbumpe|scnsim|ncaron|Ncaron|ncedil|Ncedil|hamilt|Scedil|nearhk|hardcy|HARDcy|tcedil|Tcaron|commat|nequiv|nesear|tcaron|target|hearts|nexist|varrho|scedil|Scaron|scaron|hellip|Sacute|sacute|hercon|swnwar|compfn|rtimes|rthree|rsquor|rsaquo|zacute|wedgeq|homtht|barvee|barwed|Barwed|rpargt|horbar|conint|swarhk|roplus|nltrie|hslash|hstrok|Hstrok|rmoust|Conint|bprime|hybull|hyphen|iacute|Iacute|supsup|supsub|supsim|varphi|coprod|brvbar|agrave|Supset|supset|igrave|Igrave|notinE|Agrave|iiiint|iinfin|copysr|wedbar|Verbar|vangrt|becaus|incare|verbar|inodot|bullet|drcorn|intcal|drcrop|cularr|vellip|Utilde|bumpeq|cupcap|dstrok|Dstrok|CupCap|cupcup|cupdot|eacute|Eacute|supdot|iquest|easter|ecaron|Ecaron|ecolon|isinsv|utilde|itilde|Itilde|curarr|succeq|Bumpeq|cacute|ulcrop|nparsl|Cacute|nprcue|egrave|Egrave|nrarrc|nrarrw|subsup|subsub|nrtrie|jsercy|nsccue|Jsercy|kappav|kcedil|Kcedil|subsim|ulcorn|nsimeq|egsdot|veebar|kgreen|capand|elsdot|Subset|subset|curren|aacute|lacute|Lacute|emptyv|ntilde|Ntilde|lagran|lambda|Lambda|capcap|Ugrave|langle|subdot|emsp13|numero|emsp14|nvdash|nvDash|nVdash|nVDash|ugrave|ufisht|nvHarr|larrfs|nvlArr|larrhk|larrlp|larrpl|nvrArr|Udblac|nwarhk|larrtl|nwnear|oacute|Oacute|latail|lAtail|sstarf|lbrace|odblac|Odblac|lbrack|udblac|odsold|eparsl|lcaron|Lcaron|ograve|Ograve|lcedil|Lcedil|Aacute|ssmile|ssetmn|squarf|ldquor|capcup|ominus|cylcty|rharul|eqcirc|dagger|rfloor|rfisht|Dagger|daleth|equals|origof|capdot|equest|dcaron|Dcaron|rdquor|oslash|Oslash|otilde|Otilde|otimes|Otimes|urcrop|Ubreve|ubreve|Yacute|Uacute|uacute|Rcedil|rcedil|urcorn|parsim|Rcaron|Vdashl|rcaron|Tstrok|percnt|period|permil|Exists|yacute|rbrack|rbrace|phmmat|ccaron|Ccaron|planck|ccedil|plankv|tstrok|female|plusdo|plusdu|ffilig|plusmn|ffllig|Ccedil|rAtail|dfisht|bernou|ratail|Rarrtl|rarrtl|angsph|rarrpl|rarrlp|rarrhk|xwedge|xotime|forall|ForAll|Vvdash|vsupnE|preceq|bigcap|frac12|frac13|frac14|primes|rarrfs|prnsim|frac15|Square|frac16|square|lesdot|frac18|frac23|propto|prurel|rarrap|rangle|puncsp|frac25|Racute|qprime|racute|lesges|frac34|abreve|AElig|eqsim|utdot|setmn|urtri|Equal|Uring|seArr|uring|searr|dashv|Dashv|mumap|nabla|iogon|Iogon|sdote|sdotb|scsim|napid|napos|equiv|natur|Acirc|dblac|erarr|nbump|iprod|erDot|ucirc|awint|esdot|angrt|ncong|isinE|scnap|Scirc|scirc|ndash|isins|Ubrcy|nearr|neArr|isinv|nedot|ubrcy|acute|Ycirc|iukcy|Iukcy|xutri|nesim|caret|jcirc|Jcirc|caron|twixt|ddarr|sccue|exist|jmath|sbquo|ngeqq|angst|ccaps|lceil|ngsim|UpTee|delta|Delta|rtrif|nharr|nhArr|nhpar|rtrie|jukcy|Jukcy|kappa|rsquo|Kappa|nlarr|nlArr|TSHcy|rrarr|aogon|Aogon|fflig|xrarr|tshcy|ccirc|nleqq|filig|upsih|nless|dharl|nlsim|fjlig|ropar|nltri|dharr|robrk|roarr|fllig|fltns|roang|rnmid|subnE|subne|lAarr|trisb|Ccirc|acirc|ccups|blank|VDash|forkv|Vdash|langd|cedil|blk12|blk14|laquo|strns|diams|notin|vDash|larrb|blk34|block|disin|uplus|vdash|vBarv|aelig|starf|Wedge|check|xrArr|lates|lbarr|lBarr|notni|lbbrk|bcong|frasl|lbrke|frown|vrtri|vprop|vnsup|gamma|Gamma|wedge|xodot|bdquo|srarr|doteq|ldquo|boxdl|boxdL|gcirc|Gcirc|boxDl|boxDL|boxdr|boxdR|boxDr|TRADE|trade|rlhar|boxDR|vnsub|npart|vltri|rlarr|boxhd|boxhD|nprec|gescc|nrarr|nrArr|boxHd|boxHD|boxhu|boxhU|nrtri|boxHu|clubs|boxHU|times|colon|Colon|gimel|xlArr|Tilde|nsime|tilde|nsmid|nspar|THORN|thorn|xlarr|nsube|nsubE|thkap|xhArr|comma|nsucc|boxul|boxuL|nsupe|nsupE|gneqq|gnsim|boxUl|boxUL|grave|boxur|boxuR|boxUr|boxUR|lescc|angle|bepsi|boxvh|varpi|boxvH|numsp|Theta|gsime|gsiml|theta|boxVh|boxVH|boxvl|gtcir|gtdot|boxvL|boxVl|boxVL|crarr|cross|Cross|nvsim|boxvr|nwarr|nwArr|sqsup|dtdot|Uogon|lhard|lharu|dtrif|ocirc|Ocirc|lhblk|duarr|odash|sqsub|Hacek|sqcup|llarr|duhar|oelig|OElig|ofcir|boxvR|uogon|lltri|boxVr|csube|uuarr|ohbar|csupe|ctdot|olarr|olcir|harrw|oline|sqcap|omacr|Omacr|omega|Omega|boxVR|aleph|lneqq|lnsim|loang|loarr|rharu|lobrk|hcirc|operp|oplus|rhard|Hcirc|orarr|Union|order|ecirc|Ecirc|cuepr|szlig|cuesc|breve|reals|eDDot|Breve|hoarr|lopar|utrif|rdquo|Umacr|umacr|efDot|swArr|ultri|alpha|rceil|ovbar|swarr|Wcirc|wcirc|smtes|smile|bsemi|lrarr|aring|parsl|lrhar|bsime|uhblk|lrtri|cupor|Aring|uharr|uharl|slarr|rbrke|bsolb|lsime|rbbrk|RBarr|lsimg|phone|rBarr|rbarr|icirc|lsquo|Icirc|emacr|Emacr|ratio|simne|plusb|simlE|simgE|simeq|pluse|ltcir|ltdot|empty|xharr|xdtri|iexcl|Alpha|ltrie|rarrw|pound|ltrif|xcirc|bumpe|prcue|bumpE|asymp|amacr|cuvee|Sigma|sigma|iiint|udhar|iiota|ijlig|IJlig|supnE|imacr|Imacr|prime|Prime|image|prnap|eogon|Eogon|rarrc|mdash|mDDot|cuwed|imath|supne|imped|Amacr|udarr|prsim|micro|rarrb|cwint|raquo|infin|eplus|range|rangd|Ucirc|radic|minus|amalg|veeeq|rAarr|epsiv|ycirc|quest|sharp|quot|zwnj|Qscr|race|qscr|Qopf|qopf|qint|rang|Rang|Zscr|zscr|Zopf|zopf|rarr|rArr|Rarr|Pscr|pscr|prop|prod|prnE|prec|ZHcy|zhcy|prap|Zeta|zeta|Popf|popf|Zdot|plus|zdot|Yuml|yuml|phiv|YUcy|yucy|Yscr|yscr|perp|Yopf|yopf|part|para|YIcy|Ouml|rcub|yicy|YAcy|rdca|ouml|osol|Oscr|rdsh|yacy|real|oscr|xvee|andd|rect|andv|Xscr|oror|ordm|ordf|xscr|ange|aopf|Aopf|rHar|Xopf|opar|Oopf|xopf|xnis|rhov|oopf|omid|xmap|oint|apid|apos|ogon|ascr|Ascr|odot|odiv|xcup|xcap|ocir|oast|nvlt|nvle|nvgt|nvge|nvap|Wscr|wscr|auml|ntlg|ntgl|nsup|nsub|nsim|Nscr|nscr|nsce|Wopf|ring|npre|wopf|npar|Auml|Barv|bbrk|Nopf|nopf|nmid|nLtv|beta|ropf|Ropf|Beta|beth|nles|rpar|nleq|bnot|bNot|nldr|NJcy|rscr|Rscr|Vscr|vscr|rsqb|njcy|bopf|nisd|Bopf|rtri|Vopf|nGtv|ngtr|vopf|boxh|boxH|boxv|nges|ngeq|boxV|bscr|scap|Bscr|bsim|Vert|vert|bsol|bull|bump|caps|cdot|ncup|scnE|ncap|nbsp|napE|Cdot|cent|sdot|Vbar|nang|vBar|chcy|Mscr|mscr|sect|semi|CHcy|Mopf|mopf|sext|circ|cire|mldr|mlcp|cirE|comp|shcy|SHcy|vArr|varr|cong|copf|Copf|copy|COPY|malt|male|macr|lvnE|cscr|ltri|sime|ltcc|simg|Cscr|siml|csub|Uuml|lsqb|lsim|uuml|csup|Lscr|lscr|utri|smid|lpar|cups|smte|lozf|darr|Lopf|Uscr|solb|lopf|sopf|Sopf|lneq|uscr|spar|dArr|lnap|Darr|dash|Sqrt|LJcy|ljcy|lHar|dHar|Upsi|upsi|diam|lesg|djcy|DJcy|leqq|dopf|Dopf|dscr|Dscr|dscy|ldsh|ldca|squf|DScy|sscr|Sscr|dsol|lcub|late|star|Star|Uopf|Larr|lArr|larr|uopf|dtri|dzcy|sube|subE|Lang|lang|Kscr|kscr|Kopf|kopf|KJcy|kjcy|KHcy|khcy|DZcy|ecir|edot|eDot|Jscr|jscr|succ|Jopf|jopf|Edot|uHar|emsp|ensp|Iuml|iuml|eopf|isin|Iscr|iscr|Eopf|epar|sung|epsi|escr|sup1|sup2|sup3|Iota|iota|supe|supE|Iopf|iopf|IOcy|iocy|Escr|esim|Esim|imof|Uarr|QUOT|uArr|uarr|euml|IEcy|iecy|Idot|Euml|euro|excl|Hscr|hscr|Hopf|hopf|TScy|tscy|Tscr|hbar|tscr|flat|tbrk|fnof|hArr|harr|half|fopf|Fopf|tdot|gvnE|fork|trie|gtcc|fscr|Fscr|gdot|gsim|Gscr|gscr|Gopf|gopf|gneq|Gdot|tosa|gnap|Topf|topf|geqq|toea|GJcy|gjcy|tint|gesl|mid|Sfr|ggg|top|ges|gla|glE|glj|geq|gne|gEl|gel|gnE|Gcy|gcy|gap|Tfr|tfr|Tcy|tcy|Hat|Tau|Ffr|tau|Tab|hfr|Hfr|ffr|Fcy|fcy|icy|Icy|iff|ETH|eth|ifr|Ifr|Eta|eta|int|Int|Sup|sup|ucy|Ucy|Sum|sum|jcy|ENG|ufr|Ufr|eng|Jcy|jfr|els|ell|egs|Efr|efr|Jfr|uml|kcy|Kcy|Ecy|ecy|kfr|Kfr|lap|Sub|sub|lat|lcy|Lcy|leg|Dot|dot|lEg|leq|les|squ|div|die|lfr|Lfr|lgE|Dfr|dfr|Del|deg|Dcy|dcy|lne|lnE|sol|loz|smt|Cup|lrm|cup|lsh|Lsh|sim|shy|map|Map|mcy|Mcy|mfr|Mfr|mho|gfr|Gfr|sfr|cir|Chi|chi|nap|Cfr|vcy|Vcy|cfr|Scy|scy|ncy|Ncy|vee|Vee|Cap|cap|nfr|scE|sce|Nfr|nge|ngE|nGg|vfr|Vfr|ngt|bot|nGt|nis|niv|Rsh|rsh|nle|nlE|bne|Bfr|bfr|nLl|nlt|nLt|Bcy|bcy|not|Not|rlm|wfr|Wfr|npr|nsc|num|ocy|ast|Ocy|ofr|xfr|Xfr|Ofr|ogt|ohm|apE|olt|Rho|ape|rho|Rfr|rfr|ord|REG|ang|reg|orv|And|and|AMP|Rcy|amp|Afr|ycy|Ycy|yen|yfr|Yfr|rcy|par|pcy|Pcy|pfr|Pfr|phi|Phi|afr|Acy|acy|zcy|Zcy|piv|acE|acd|zfr|Zfr|pre|prE|psi|Psi|qfr|Qfr|zwj|Or|ge|Gg|gt|gg|el|oS|lt|Lt|LT|Re|lg|gl|eg|ne|Im|it|le|DD|wp|wr|nu|Nu|dd|lE|Sc|sc|pi|Pi|ee|af|ll|Ll|rx|gE|xi|pm|Xi|ic|pr|Pr|in|ni|mp|mu|ac|Mu|or|ap|Gt|GT|ii);|&(Aacute|Agrave|Atilde|Ccedil|Eacute|Egrave|Iacute|Igrave|Ntilde|Oacute|Ograve|Oslash|Otilde|Uacute|Ugrave|Yacute|aacute|agrave|atilde|brvbar|ccedil|curren|divide|eacute|egrave|frac12|frac14|frac34|iacute|igrave|iquest|middot|ntilde|oacute|ograve|oslash|otilde|plusmn|uacute|ugrave|yacute|AElig|Acirc|Aring|Ecirc|Icirc|Ocirc|THORN|Ucirc|acirc|acute|aelig|aring|cedil|ecirc|icirc|iexcl|laquo|micro|ocirc|pound|raquo|szlig|thorn|times|ucirc|Auml|COPY|Euml|Iuml|Ouml|QUOT|Uuml|auml|cent|copy|euml|iuml|macr|nbsp|ordf|ordm|ouml|para|quot|sect|sup1|sup2|sup3|uuml|yuml|AMP|ETH|REG|amp|deg|eth|not|reg|shy|uml|yen|GT|LT|gt|lt)(?!;)([=a-zA-Z0-9]?)|&#([0-9]+)(;?)|&#[xX]([a-fA-F0-9]+)(;?)|&([0-9a-zA-Z]+)/g,b={aacute:"á",Aacute:"Á",abreve:"ă",Abreve:"Ă",ac:"∾",acd:"∿",acE:"∾̳",acirc:"â",Acirc:"Â",acute:"´",acy:"а",Acy:"А",aelig:"æ",AElig:"Æ",af:"⁡",afr:"𝔞",Afr:"𝔄",agrave:"à",Agrave:"À",alefsym:"ℵ",aleph:"ℵ",alpha:"α",Alpha:"Α",amacr:"ā",Amacr:"Ā",amalg:"⨿",amp:"&",AMP:"&",and:"∧",And:"⩓",andand:"⩕",andd:"⩜",andslope:"⩘",andv:"⩚",ang:"∠",ange:"⦤",angle:"∠",angmsd:"∡",angmsdaa:"⦨",angmsdab:"⦩",angmsdac:"⦪",angmsdad:"⦫",angmsdae:"⦬",angmsdaf:"⦭",angmsdag:"⦮",angmsdah:"⦯",angrt:"∟",angrtvb:"⊾",angrtvbd:"⦝",angsph:"∢",angst:"Å",angzarr:"⍼",aogon:"ą",Aogon:"Ą",aopf:"𝕒",Aopf:"𝔸",ap:"≈",apacir:"⩯",ape:"≊",apE:"⩰",apid:"≋",apos:"'",ApplyFunction:"⁡",approx:"≈",approxeq:"≊",aring:"å",Aring:"Å",ascr:"𝒶",Ascr:"𝒜",Assign:"≔",ast:"*",asymp:"≈",asympeq:"≍",atilde:"ã",Atilde:"Ã",auml:"ä",Auml:"Ä",awconint:"∳",awint:"⨑",backcong:"≌",backepsilon:"϶",backprime:"‵",backsim:"∽",backsimeq:"⋍",Backslash:"∖",Barv:"⫧",barvee:"⊽",barwed:"⌅",Barwed:"⌆",barwedge:"⌅",bbrk:"⎵",bbrktbrk:"⎶",bcong:"≌",bcy:"б",Bcy:"Б",bdquo:"„",becaus:"∵",because:"∵",Because:"∵",bemptyv:"⦰",bepsi:"϶",bernou:"ℬ",Bernoullis:"ℬ",beta:"β",Beta:"Β",beth:"ℶ",between:"≬",bfr:"𝔟",Bfr:"𝔅",bigcap:"⋂",bigcirc:"◯",bigcup:"⋃",bigodot:"⨀",bigoplus:"⨁",bigotimes:"⨂",bigsqcup:"⨆",bigstar:"★",bigtriangledown:"▽",bigtriangleup:"△",biguplus:"⨄",bigvee:"⋁",bigwedge:"⋀",bkarow:"⤍",blacklozenge:"⧫",blacksquare:"▪",blacktriangle:"▴",blacktriangledown:"▾",blacktriangleleft:"◂",blacktriangleright:"▸",blank:"␣",blk12:"▒",blk14:"░",blk34:"▓",block:"█",bne:"=⃥",bnequiv:"≡⃥",bnot:"⌐",bNot:"⫭",bopf:"𝕓",Bopf:"𝔹",bot:"⊥",bottom:"⊥",bowtie:"⋈",boxbox:"⧉",boxdl:"┐",boxdL:"╕",boxDl:"╖",boxDL:"╗",boxdr:"┌",boxdR:"╒",boxDr:"╓",boxDR:"╔",boxh:"─",boxH:"═",boxhd:"┬",boxhD:"╥",boxHd:"╤",boxHD:"╦",boxhu:"┴",boxhU:"╨",boxHu:"╧",boxHU:"╩",boxminus:"⊟",boxplus:"⊞",boxtimes:"⊠",boxul:"┘",boxuL:"╛",boxUl:"╜",boxUL:"╝",boxur:"└",boxuR:"╘",boxUr:"╙",boxUR:"╚",boxv:"│",boxV:"║",boxvh:"┼",boxvH:"╪",boxVh:"╫",boxVH:"╬",boxvl:"┤",boxvL:"╡",boxVl:"╢",boxVL:"╣",boxvr:"├",boxvR:"╞",boxVr:"╟",boxVR:"╠",bprime:"‵",breve:"˘",Breve:"˘",brvbar:"¦",bscr:"𝒷",Bscr:"ℬ",bsemi:"⁏",bsim:"∽",bsime:"⋍",bsol:"\\",bsolb:"⧅",bsolhsub:"⟈",bull:"•",bullet:"•",bump:"≎",bumpe:"≏",bumpE:"⪮",bumpeq:"≏",Bumpeq:"≎",cacute:"ć",Cacute:"Ć",cap:"∩",Cap:"⋒",capand:"⩄",capbrcup:"⩉",capcap:"⩋",capcup:"⩇",capdot:"⩀",CapitalDifferentialD:"ⅅ",caps:"∩︀",caret:"⁁",caron:"ˇ",Cayleys:"ℭ",ccaps:"⩍",ccaron:"č",Ccaron:"Č",ccedil:"ç",Ccedil:"Ç",ccirc:"ĉ",Ccirc:"Ĉ",Cconint:"∰",ccups:"⩌",ccupssm:"⩐",cdot:"ċ",Cdot:"Ċ",cedil:"¸",Cedilla:"¸",cemptyv:"⦲",cent:"¢",centerdot:"·",CenterDot:"·",cfr:"𝔠",Cfr:"ℭ",chcy:"ч",CHcy:"Ч",check:"✓",checkmark:"✓",chi:"χ",Chi:"Χ",cir:"○",circ:"ˆ",circeq:"≗",circlearrowleft:"↺",circlearrowright:"↻",circledast:"⊛",circledcirc:"⊚",circleddash:"⊝",CircleDot:"⊙",circledR:"®",circledS:"Ⓢ",CircleMinus:"⊖",CirclePlus:"⊕",CircleTimes:"⊗",cire:"≗",cirE:"⧃",cirfnint:"⨐",cirmid:"⫯",cirscir:"⧂",ClockwiseContourIntegral:"∲",CloseCurlyDoubleQuote:"”",CloseCurlyQuote:"’",clubs:"♣",clubsuit:"♣",colon:":",Colon:"∷",colone:"≔",Colone:"⩴",coloneq:"≔",comma:",",commat:"@",comp:"∁",compfn:"∘",complement:"∁",complexes:"ℂ",cong:"≅",congdot:"⩭",Congruent:"≡",conint:"∮",Conint:"∯",ContourIntegral:"∮",copf:"𝕔",Copf:"ℂ",coprod:"∐",Coproduct:"∐",copy:"©",COPY:"©",copysr:"℗",CounterClockwiseContourIntegral:"∳",crarr:"↵",cross:"✗",Cross:"⨯",cscr:"𝒸",Cscr:"𝒞",csub:"⫏",csube:"⫑",csup:"⫐",csupe:"⫒",ctdot:"⋯",cudarrl:"⤸",cudarrr:"⤵",cuepr:"⋞",cuesc:"⋟",cularr:"↶",cularrp:"⤽",cup:"∪",Cup:"⋓",cupbrcap:"⩈",cupcap:"⩆",CupCap:"≍",cupcup:"⩊",cupdot:"⊍",cupor:"⩅",cups:"∪︀",curarr:"↷",curarrm:"⤼",curlyeqprec:"⋞",curlyeqsucc:"⋟",curlyvee:"⋎",curlywedge:"⋏",curren:"¤",curvearrowleft:"↶",curvearrowright:"↷",cuvee:"⋎",cuwed:"⋏",cwconint:"∲",cwint:"∱",cylcty:"⌭",dagger:"†",Dagger:"‡",daleth:"ℸ",darr:"↓",dArr:"⇓",Darr:"↡",dash:"‐",dashv:"⊣",Dashv:"⫤",dbkarow:"⤏",dblac:"˝",dcaron:"ď",Dcaron:"Ď",dcy:"д",Dcy:"Д",dd:"ⅆ",DD:"ⅅ",ddagger:"‡",ddarr:"⇊",DDotrahd:"⤑",ddotseq:"⩷",deg:"°",Del:"∇",delta:"δ",Delta:"Δ",demptyv:"⦱",dfisht:"⥿",dfr:"𝔡",Dfr:"𝔇",dHar:"⥥",dharl:"⇃",dharr:"⇂",DiacriticalAcute:"´",DiacriticalDot:"˙",DiacriticalDoubleAcute:"˝",DiacriticalGrave:"`",DiacriticalTilde:"˜",diam:"⋄",diamond:"⋄",Diamond:"⋄",diamondsuit:"♦",diams:"♦",die:"¨",DifferentialD:"ⅆ",digamma:"ϝ",disin:"⋲",div:"÷",divide:"÷",divideontimes:"⋇",divonx:"⋇",djcy:"ђ",DJcy:"Ђ",dlcorn:"⌞",dlcrop:"⌍",dollar:"$",dopf:"𝕕",Dopf:"𝔻",dot:"˙",Dot:"¨",DotDot:"⃜",doteq:"≐",doteqdot:"≑",DotEqual:"≐",dotminus:"∸",dotplus:"∔",dotsquare:"⊡",doublebarwedge:"⌆",DoubleContourIntegral:"∯",DoubleDot:"¨",DoubleDownArrow:"⇓",DoubleLeftArrow:"⇐",DoubleLeftRightArrow:"⇔",DoubleLeftTee:"⫤",DoubleLongLeftArrow:"⟸",DoubleLongLeftRightArrow:"⟺",DoubleLongRightArrow:"⟹",DoubleRightArrow:"⇒",DoubleRightTee:"⊨",DoubleUpArrow:"⇑",DoubleUpDownArrow:"⇕",DoubleVerticalBar:"∥",downarrow:"↓",Downarrow:"⇓",DownArrow:"↓",DownArrowBar:"⤓",DownArrowUpArrow:"⇵",DownBreve:"̑",downdownarrows:"⇊",downharpoonleft:"⇃",downharpoonright:"⇂",DownLeftRightVector:"⥐",DownLeftTeeVector:"⥞",DownLeftVector:"↽",DownLeftVectorBar:"⥖",DownRightTeeVector:"⥟",DownRightVector:"⇁",DownRightVectorBar:"⥗",DownTee:"⊤",DownTeeArrow:"↧",drbkarow:"⤐",drcorn:"⌟",drcrop:"⌌",dscr:"𝒹",Dscr:"𝒟",dscy:"ѕ",DScy:"Ѕ",dsol:"⧶",dstrok:"đ",Dstrok:"Đ",dtdot:"⋱",dtri:"▿",dtrif:"▾",duarr:"⇵",duhar:"⥯",dwangle:"⦦",dzcy:"џ",DZcy:"Џ",dzigrarr:"⟿",eacute:"é",Eacute:"É",easter:"⩮",ecaron:"ě",Ecaron:"Ě",ecir:"≖",ecirc:"ê",Ecirc:"Ê",ecolon:"≕",ecy:"э",Ecy:"Э",eDDot:"⩷",edot:"ė",eDot:"≑",Edot:"Ė",ee:"ⅇ",efDot:"≒",efr:"𝔢",Efr:"𝔈",eg:"⪚",egrave:"è",Egrave:"È",egs:"⪖",egsdot:"⪘",el:"⪙",Element:"∈",elinters:"⏧",ell:"ℓ",els:"⪕",elsdot:"⪗",emacr:"ē",Emacr:"Ē",empty:"∅",emptyset:"∅",EmptySmallSquare:"◻",emptyv:"∅",EmptyVerySmallSquare:"▫",emsp:" ",emsp13:" ",emsp14:" ",eng:"ŋ",ENG:"Ŋ",ensp:" ",eogon:"ę",Eogon:"Ę",eopf:"𝕖",Eopf:"𝔼",epar:"⋕",eparsl:"⧣",eplus:"⩱",epsi:"ε",epsilon:"ε",Epsilon:"Ε",epsiv:"ϵ",eqcirc:"≖",eqcolon:"≕",eqsim:"≂",eqslantgtr:"⪖",eqslantless:"⪕",Equal:"⩵",equals:"=",EqualTilde:"≂",equest:"≟",Equilibrium:"⇌",equiv:"≡",equivDD:"⩸",eqvparsl:"⧥",erarr:"⥱",erDot:"≓",escr:"ℯ",Escr:"ℰ",esdot:"≐",esim:"≂",Esim:"⩳",eta:"η",Eta:"Η",eth:"ð",ETH:"Ð",euml:"ë",Euml:"Ë",euro:"€",excl:"!",exist:"∃",Exists:"∃",expectation:"ℰ",exponentiale:"ⅇ",ExponentialE:"ⅇ",fallingdotseq:"≒",fcy:"ф",Fcy:"Ф",female:"♀",ffilig:"ffi",fflig:"ff",ffllig:"ffl",ffr:"𝔣",Ffr:"𝔉",filig:"fi",FilledSmallSquare:"◼",FilledVerySmallSquare:"▪",fjlig:"fj",flat:"♭",fllig:"fl",fltns:"▱",fnof:"ƒ",fopf:"𝕗",Fopf:"𝔽",forall:"∀",ForAll:"∀",fork:"⋔",forkv:"⫙",Fouriertrf:"ℱ",fpartint:"⨍",frac12:"½",frac13:"⅓",frac14:"¼",frac15:"⅕",frac16:"⅙",frac18:"⅛",frac23:"⅔",frac25:"⅖",frac34:"¾",frac35:"⅗",frac38:"⅜",frac45:"⅘",frac56:"⅚",frac58:"⅝",frac78:"⅞",frasl:"⁄",frown:"⌢",fscr:"𝒻",Fscr:"ℱ",gacute:"ǵ",gamma:"γ",Gamma:"Γ",gammad:"ϝ",Gammad:"Ϝ",gap:"⪆",gbreve:"ğ",Gbreve:"Ğ",Gcedil:"Ģ",gcirc:"ĝ",Gcirc:"Ĝ",gcy:"г",Gcy:"Г",gdot:"ġ",Gdot:"Ġ",ge:"≥",gE:"≧",gel:"⋛",gEl:"⪌",geq:"≥",geqq:"≧",geqslant:"⩾",ges:"⩾",gescc:"⪩",gesdot:"⪀",gesdoto:"⪂",gesdotol:"⪄",gesl:"⋛︀",gesles:"⪔",gfr:"𝔤",Gfr:"𝔊",gg:"≫",Gg:"⋙",ggg:"⋙",gimel:"ℷ",gjcy:"ѓ",GJcy:"Ѓ",gl:"≷",gla:"⪥",glE:"⪒",glj:"⪤",gnap:"⪊",gnapprox:"⪊",gne:"⪈",gnE:"≩",gneq:"⪈",gneqq:"≩",gnsim:"⋧",gopf:"𝕘",Gopf:"𝔾",grave:"`",GreaterEqual:"≥",GreaterEqualLess:"⋛",GreaterFullEqual:"≧",GreaterGreater:"⪢",GreaterLess:"≷",GreaterSlantEqual:"⩾",GreaterTilde:"≳",gscr:"ℊ",Gscr:"𝒢",gsim:"≳",gsime:"⪎",gsiml:"⪐",gt:">",Gt:"≫",GT:">",gtcc:"⪧",gtcir:"⩺",gtdot:"⋗",gtlPar:"⦕",gtquest:"⩼",gtrapprox:"⪆",gtrarr:"⥸",gtrdot:"⋗",gtreqless:"⋛",gtreqqless:"⪌",gtrless:"≷",gtrsim:"≳",gvertneqq:"≩︀",gvnE:"≩︀",Hacek:"ˇ",hairsp:" ",half:"½",hamilt:"ℋ",hardcy:"ъ",HARDcy:"Ъ",harr:"↔",hArr:"⇔",harrcir:"⥈",harrw:"↭",Hat:"^",hbar:"ℏ",hcirc:"ĥ",Hcirc:"Ĥ",hearts:"♥",heartsuit:"♥",hellip:"…",hercon:"⊹",hfr:"𝔥",Hfr:"ℌ",HilbertSpace:"ℋ",hksearow:"⤥",hkswarow:"⤦",hoarr:"⇿",homtht:"∻",hookleftarrow:"↩",hookrightarrow:"↪",hopf:"𝕙",Hopf:"ℍ",horbar:"―",HorizontalLine:"─",hscr:"𝒽",Hscr:"ℋ",hslash:"ℏ",hstrok:"ħ",Hstrok:"Ħ",HumpDownHump:"≎",HumpEqual:"≏",hybull:"⁃",hyphen:"‐",iacute:"í",Iacute:"Í",ic:"⁣",icirc:"î",Icirc:"Î",icy:"и",Icy:"И",Idot:"İ",iecy:"е",IEcy:"Е",iexcl:"¡",iff:"⇔",ifr:"𝔦",Ifr:"ℑ",igrave:"ì",Igrave:"Ì",ii:"ⅈ",iiiint:"⨌",iiint:"∭",iinfin:"⧜",iiota:"℩",ijlig:"ij",IJlig:"IJ",Im:"ℑ",imacr:"ī",Imacr:"Ī",image:"ℑ",ImaginaryI:"ⅈ",imagline:"ℐ",imagpart:"ℑ",imath:"ı",imof:"⊷",imped:"Ƶ",Implies:"⇒",in:"∈",incare:"℅",infin:"∞",infintie:"⧝",inodot:"ı",int:"∫",Int:"∬",intcal:"⊺",integers:"ℤ",Integral:"∫",intercal:"⊺",Intersection:"⋂",intlarhk:"⨗",intprod:"⨼",InvisibleComma:"⁣",InvisibleTimes:"⁢",iocy:"ё",IOcy:"Ё",iogon:"į",Iogon:"Į",iopf:"𝕚",Iopf:"𝕀",iota:"ι",Iota:"Ι",iprod:"⨼",iquest:"¿",iscr:"𝒾",Iscr:"ℐ",isin:"∈",isindot:"⋵",isinE:"⋹",isins:"⋴",isinsv:"⋳",isinv:"∈",it:"⁢",itilde:"ĩ",Itilde:"Ĩ",iukcy:"і",Iukcy:"І",iuml:"ï",Iuml:"Ï",jcirc:"ĵ",Jcirc:"Ĵ",jcy:"й",Jcy:"Й",jfr:"𝔧",Jfr:"𝔍",jmath:"ȷ",jopf:"𝕛",Jopf:"𝕁",jscr:"𝒿",Jscr:"𝒥",jsercy:"ј",Jsercy:"Ј",jukcy:"є",Jukcy:"Є",kappa:"κ",Kappa:"Κ",kappav:"ϰ",kcedil:"ķ",Kcedil:"Ķ",kcy:"к",Kcy:"К",kfr:"𝔨",Kfr:"𝔎",kgreen:"ĸ",khcy:"х",KHcy:"Х",kjcy:"ќ",KJcy:"Ќ",kopf:"𝕜",Kopf:"𝕂",kscr:"𝓀",Kscr:"𝒦",lAarr:"⇚",lacute:"ĺ",Lacute:"Ĺ",laemptyv:"⦴",lagran:"ℒ",lambda:"λ",Lambda:"Λ",lang:"⟨",Lang:"⟪",langd:"⦑",langle:"⟨",lap:"⪅",Laplacetrf:"ℒ",laquo:"«",larr:"←",lArr:"⇐",Larr:"↞",larrb:"⇤",larrbfs:"⤟",larrfs:"⤝",larrhk:"↩",larrlp:"↫",larrpl:"⤹",larrsim:"⥳",larrtl:"↢",lat:"⪫",latail:"⤙",lAtail:"⤛",late:"⪭",lates:"⪭︀",lbarr:"⤌",lBarr:"⤎",lbbrk:"❲",lbrace:"{",lbrack:"[",lbrke:"⦋",lbrksld:"⦏",lbrkslu:"⦍",lcaron:"ľ",Lcaron:"Ľ",lcedil:"ļ",Lcedil:"Ļ",lceil:"⌈",lcub:"{",lcy:"л",Lcy:"Л",ldca:"⤶",ldquo:"“",ldquor:"„",ldrdhar:"⥧",ldrushar:"⥋",ldsh:"↲",le:"≤",lE:"≦",LeftAngleBracket:"⟨",leftarrow:"←",Leftarrow:"⇐",LeftArrow:"←",LeftArrowBar:"⇤",LeftArrowRightArrow:"⇆",leftarrowtail:"↢",LeftCeiling:"⌈",LeftDoubleBracket:"⟦",LeftDownTeeVector:"⥡",LeftDownVector:"⇃",LeftDownVectorBar:"⥙",LeftFloor:"⌊",leftharpoondown:"↽",leftharpoonup:"↼",leftleftarrows:"⇇",leftrightarrow:"↔",Leftrightarrow:"⇔",LeftRightArrow:"↔",leftrightarrows:"⇆",leftrightharpoons:"⇋",leftrightsquigarrow:"↭",LeftRightVector:"⥎",LeftTee:"⊣",LeftTeeArrow:"↤",LeftTeeVector:"⥚",leftthreetimes:"⋋",LeftTriangle:"⊲",LeftTriangleBar:"⧏",LeftTriangleEqual:"⊴",LeftUpDownVector:"⥑",LeftUpTeeVector:"⥠",LeftUpVector:"↿",LeftUpVectorBar:"⥘",LeftVector:"↼",LeftVectorBar:"⥒",leg:"⋚",lEg:"⪋",leq:"≤",leqq:"≦",leqslant:"⩽",les:"⩽",lescc:"⪨",lesdot:"⩿",lesdoto:"⪁",lesdotor:"⪃",lesg:"⋚︀",lesges:"⪓",lessapprox:"⪅",lessdot:"⋖",lesseqgtr:"⋚",lesseqqgtr:"⪋",LessEqualGreater:"⋚",LessFullEqual:"≦",LessGreater:"≶",lessgtr:"≶",LessLess:"⪡",lesssim:"≲",LessSlantEqual:"⩽",LessTilde:"≲",lfisht:"⥼",lfloor:"⌊",lfr:"𝔩",Lfr:"𝔏",lg:"≶",lgE:"⪑",lHar:"⥢",lhard:"↽",lharu:"↼",lharul:"⥪",lhblk:"▄",ljcy:"љ",LJcy:"Љ",ll:"≪",Ll:"⋘",llarr:"⇇",llcorner:"⌞",Lleftarrow:"⇚",llhard:"⥫",lltri:"◺",lmidot:"ŀ",Lmidot:"Ŀ",lmoust:"⎰",lmoustache:"⎰",lnap:"⪉",lnapprox:"⪉",lne:"⪇",lnE:"≨",lneq:"⪇",lneqq:"≨",lnsim:"⋦",loang:"⟬",loarr:"⇽",lobrk:"⟦",longleftarrow:"⟵",Longleftarrow:"⟸",LongLeftArrow:"⟵",longleftrightarrow:"⟷",Longleftrightarrow:"⟺",LongLeftRightArrow:"⟷",longmapsto:"⟼",longrightarrow:"⟶",Longrightarrow:"⟹",LongRightArrow:"⟶",looparrowleft:"↫",looparrowright:"↬",lopar:"⦅",lopf:"𝕝",Lopf:"𝕃",loplus:"⨭",lotimes:"⨴",lowast:"∗",lowbar:"_",LowerLeftArrow:"↙",LowerRightArrow:"↘",loz:"◊",lozenge:"◊",lozf:"⧫",lpar:"(",lparlt:"⦓",lrarr:"⇆",lrcorner:"⌟",lrhar:"⇋",lrhard:"⥭",lrm:"‎",lrtri:"⊿",lsaquo:"‹",lscr:"𝓁",Lscr:"ℒ",lsh:"↰",Lsh:"↰",lsim:"≲",lsime:"⪍",lsimg:"⪏",lsqb:"[",lsquo:"‘",lsquor:"‚",lstrok:"ł",Lstrok:"Ł",lt:"<",Lt:"≪",LT:"<",ltcc:"⪦",ltcir:"⩹",ltdot:"⋖",lthree:"⋋",ltimes:"⋉",ltlarr:"⥶",ltquest:"⩻",ltri:"◃",ltrie:"⊴",ltrif:"◂",ltrPar:"⦖",lurdshar:"⥊",luruhar:"⥦",lvertneqq:"≨︀",lvnE:"≨︀",macr:"¯",male:"♂",malt:"✠",maltese:"✠",map:"↦",Map:"⤅",mapsto:"↦",mapstodown:"↧",mapstoleft:"↤",mapstoup:"↥",marker:"▮",mcomma:"⨩",mcy:"м",Mcy:"М",mdash:"—",mDDot:"∺",measuredangle:"∡",MediumSpace:" ",Mellintrf:"ℳ",mfr:"𝔪",Mfr:"𝔐",mho:"℧",micro:"µ",mid:"∣",midast:"*",midcir:"⫰",middot:"·",minus:"−",minusb:"⊟",minusd:"∸",minusdu:"⨪",MinusPlus:"∓",mlcp:"⫛",mldr:"…",mnplus:"∓",models:"⊧",mopf:"𝕞",Mopf:"𝕄",mp:"∓",mscr:"𝓂",Mscr:"ℳ",mstpos:"∾",mu:"μ",Mu:"Μ",multimap:"⊸",mumap:"⊸",nabla:"∇",nacute:"ń",Nacute:"Ń",nang:"∠⃒",nap:"≉",napE:"⩰̸",napid:"≋̸",napos:"ʼn",napprox:"≉",natur:"♮",natural:"♮",naturals:"ℕ",nbsp:" ",nbump:"≎̸",nbumpe:"≏̸",ncap:"⩃",ncaron:"ň",Ncaron:"Ň",ncedil:"ņ",Ncedil:"Ņ",ncong:"≇",ncongdot:"⩭̸",ncup:"⩂",ncy:"н",Ncy:"Н",ndash:"–",ne:"≠",nearhk:"⤤",nearr:"↗",neArr:"⇗",nearrow:"↗",nedot:"≐̸",NegativeMediumSpace:"​",NegativeThickSpace:"​",NegativeThinSpace:"​",NegativeVeryThinSpace:"​",nequiv:"≢",nesear:"⤨",nesim:"≂̸",NestedGreaterGreater:"≫",NestedLessLess:"≪",NewLine:"\n",nexist:"∄",nexists:"∄",nfr:"𝔫",Nfr:"𝔑",nge:"≱",ngE:"≧̸",ngeq:"≱",ngeqq:"≧̸",ngeqslant:"⩾̸",nges:"⩾̸",nGg:"⋙̸",ngsim:"≵",ngt:"≯",nGt:"≫⃒",ngtr:"≯",nGtv:"≫̸",nharr:"↮",nhArr:"⇎",nhpar:"⫲",ni:"∋",nis:"⋼",nisd:"⋺",niv:"∋",njcy:"њ",NJcy:"Њ",nlarr:"↚",nlArr:"⇍",nldr:"‥",nle:"≰",nlE:"≦̸",nleftarrow:"↚",nLeftarrow:"⇍",nleftrightarrow:"↮",nLeftrightarrow:"⇎",nleq:"≰",nleqq:"≦̸",nleqslant:"⩽̸",nles:"⩽̸",nless:"≮",nLl:"⋘̸",nlsim:"≴",nlt:"≮",nLt:"≪⃒",nltri:"⋪",nltrie:"⋬",nLtv:"≪̸",nmid:"∤",NoBreak:"⁠",NonBreakingSpace:" ",nopf:"𝕟",Nopf:"ℕ",not:"¬",Not:"⫬",NotCongruent:"≢",NotCupCap:"≭",NotDoubleVerticalBar:"∦",NotElement:"∉",NotEqual:"≠",NotEqualTilde:"≂̸",NotExists:"∄",NotGreater:"≯",NotGreaterEqual:"≱",NotGreaterFullEqual:"≧̸",NotGreaterGreater:"≫̸",NotGreaterLess:"≹",NotGreaterSlantEqual:"⩾̸",NotGreaterTilde:"≵",NotHumpDownHump:"≎̸",NotHumpEqual:"≏̸",notin:"∉",notindot:"⋵̸",notinE:"⋹̸",notinva:"∉",notinvb:"⋷",notinvc:"⋶",NotLeftTriangle:"⋪",NotLeftTriangleBar:"⧏̸",NotLeftTriangleEqual:"⋬",NotLess:"≮",NotLessEqual:"≰",NotLessGreater:"≸",NotLessLess:"≪̸",NotLessSlantEqual:"⩽̸",NotLessTilde:"≴",NotNestedGreaterGreater:"⪢̸",NotNestedLessLess:"⪡̸",notni:"∌",notniva:"∌",notnivb:"⋾",notnivc:"⋽",NotPrecedes:"⊀",NotPrecedesEqual:"⪯̸",NotPrecedesSlantEqual:"⋠",NotReverseElement:"∌",NotRightTriangle:"⋫",NotRightTriangleBar:"⧐̸",NotRightTriangleEqual:"⋭",NotSquareSubset:"⊏̸",NotSquareSubsetEqual:"⋢",NotSquareSuperset:"⊐̸",NotSquareSupersetEqual:"⋣",NotSubset:"⊂⃒",NotSubsetEqual:"⊈",NotSucceeds:"⊁",NotSucceedsEqual:"⪰̸",NotSucceedsSlantEqual:"⋡",NotSucceedsTilde:"≿̸",NotSuperset:"⊃⃒",NotSupersetEqual:"⊉",NotTilde:"≁",NotTildeEqual:"≄",NotTildeFullEqual:"≇",NotTildeTilde:"≉",NotVerticalBar:"∤",npar:"∦",nparallel:"∦",nparsl:"⫽⃥",npart:"∂̸",npolint:"⨔",npr:"⊀",nprcue:"⋠",npre:"⪯̸",nprec:"⊀",npreceq:"⪯̸",nrarr:"↛",nrArr:"⇏",nrarrc:"⤳̸",nrarrw:"↝̸",nrightarrow:"↛",nRightarrow:"⇏",nrtri:"⋫",nrtrie:"⋭",nsc:"⊁",nsccue:"⋡",nsce:"⪰̸",nscr:"𝓃",Nscr:"𝒩",nshortmid:"∤",nshortparallel:"∦",nsim:"≁",nsime:"≄",nsimeq:"≄",nsmid:"∤",nspar:"∦",nsqsube:"⋢",nsqsupe:"⋣",nsub:"⊄",nsube:"⊈",nsubE:"⫅̸",nsubset:"⊂⃒",nsubseteq:"⊈",nsubseteqq:"⫅̸",nsucc:"⊁",nsucceq:"⪰̸",nsup:"⊅",nsupe:"⊉",nsupE:"⫆̸",nsupset:"⊃⃒",nsupseteq:"⊉",nsupseteqq:"⫆̸",ntgl:"≹",ntilde:"ñ",Ntilde:"Ñ",ntlg:"≸",ntriangleleft:"⋪",ntrianglelefteq:"⋬",ntriangleright:"⋫",ntrianglerighteq:"⋭",nu:"ν",Nu:"Ν",num:"#",numero:"№",numsp:" ",nvap:"≍⃒",nvdash:"⊬",nvDash:"⊭",nVdash:"⊮",nVDash:"⊯",nvge:"≥⃒",nvgt:">⃒",nvHarr:"⤄",nvinfin:"⧞",nvlArr:"⤂",nvle:"≤⃒",nvlt:"<⃒",nvltrie:"⊴⃒",nvrArr:"⤃",nvrtrie:"⊵⃒",nvsim:"∼⃒",nwarhk:"⤣",nwarr:"↖",nwArr:"⇖",nwarrow:"↖",nwnear:"⤧",oacute:"ó",Oacute:"Ó",oast:"⊛",ocir:"⊚",ocirc:"ô",Ocirc:"Ô",ocy:"о",Ocy:"О",odash:"⊝",odblac:"ő",Odblac:"Ő",odiv:"⨸",odot:"⊙",odsold:"⦼",oelig:"œ",OElig:"Œ",ofcir:"⦿",ofr:"𝔬",Ofr:"𝔒",ogon:"˛",ograve:"ò",Ograve:"Ò",ogt:"⧁",ohbar:"⦵",ohm:"Ω",oint:"∮",olarr:"↺",olcir:"⦾",olcross:"⦻",oline:"‾",olt:"⧀",omacr:"ō",Omacr:"Ō",omega:"ω",Omega:"Ω",omicron:"ο",Omicron:"Ο",omid:"⦶",ominus:"⊖",oopf:"𝕠",Oopf:"𝕆",opar:"⦷",OpenCurlyDoubleQuote:"“",OpenCurlyQuote:"‘",operp:"⦹",oplus:"⊕",or:"∨",Or:"⩔",orarr:"↻",ord:"⩝",order:"ℴ",orderof:"ℴ",ordf:"ª",ordm:"º",origof:"⊶",oror:"⩖",orslope:"⩗",orv:"⩛",oS:"Ⓢ",oscr:"ℴ",Oscr:"𝒪",oslash:"ø",Oslash:"Ø",osol:"⊘",otilde:"õ",Otilde:"Õ",otimes:"⊗",Otimes:"⨷",otimesas:"⨶",ouml:"ö",Ouml:"Ö",ovbar:"⌽",OverBar:"‾",OverBrace:"⏞",OverBracket:"⎴",OverParenthesis:"⏜",par:"∥",para:"¶",parallel:"∥",parsim:"⫳",parsl:"⫽",part:"∂",PartialD:"∂",pcy:"п",Pcy:"П",percnt:"%",period:".",permil:"‰",perp:"⊥",pertenk:"‱",pfr:"𝔭",Pfr:"𝔓",phi:"φ",Phi:"Φ",phiv:"ϕ",phmmat:"ℳ",phone:"☎",pi:"π",Pi:"Π",pitchfork:"⋔",piv:"ϖ",planck:"ℏ",planckh:"ℎ",plankv:"ℏ",plus:"+",plusacir:"⨣",plusb:"⊞",pluscir:"⨢",plusdo:"∔",plusdu:"⨥",pluse:"⩲",PlusMinus:"±",plusmn:"±",plussim:"⨦",plustwo:"⨧",pm:"±",Poincareplane:"ℌ",pointint:"⨕",popf:"𝕡",Popf:"ℙ",pound:"£",pr:"≺",Pr:"⪻",prap:"⪷",prcue:"≼",pre:"⪯",prE:"⪳",prec:"≺",precapprox:"⪷",preccurlyeq:"≼",Precedes:"≺",PrecedesEqual:"⪯",PrecedesSlantEqual:"≼",PrecedesTilde:"≾",preceq:"⪯",precnapprox:"⪹",precneqq:"⪵",precnsim:"⋨",precsim:"≾",prime:"′",Prime:"″",primes:"ℙ",prnap:"⪹",prnE:"⪵",prnsim:"⋨",prod:"∏",Product:"∏",profalar:"⌮",profline:"⌒",profsurf:"⌓",prop:"∝",Proportion:"∷",Proportional:"∝",propto:"∝",prsim:"≾",prurel:"⊰",pscr:"𝓅",Pscr:"𝒫",psi:"ψ",Psi:"Ψ",puncsp:" ",qfr:"𝔮",Qfr:"𝔔",qint:"⨌",qopf:"𝕢",Qopf:"ℚ",qprime:"⁗",qscr:"𝓆",Qscr:"𝒬",quaternions:"ℍ",quatint:"⨖",quest:"?",questeq:"≟",quot:'"',QUOT:'"',rAarr:"⇛",race:"∽̱",racute:"ŕ",Racute:"Ŕ",radic:"√",raemptyv:"⦳",rang:"⟩",Rang:"⟫",rangd:"⦒",range:"⦥",rangle:"⟩",raquo:"»",rarr:"→",rArr:"⇒",Rarr:"↠",rarrap:"⥵",rarrb:"⇥",rarrbfs:"⤠",rarrc:"⤳",rarrfs:"⤞",rarrhk:"↪",rarrlp:"↬",rarrpl:"⥅",rarrsim:"⥴",rarrtl:"↣",Rarrtl:"⤖",rarrw:"↝",ratail:"⤚",rAtail:"⤜",ratio:"∶",rationals:"ℚ",rbarr:"⤍",rBarr:"⤏",RBarr:"⤐",rbbrk:"❳",rbrace:"}",rbrack:"]",rbrke:"⦌",rbrksld:"⦎",rbrkslu:"⦐",rcaron:"ř",Rcaron:"Ř",rcedil:"ŗ",Rcedil:"Ŗ",rceil:"⌉",rcub:"}",rcy:"р",Rcy:"Р",rdca:"⤷",rdldhar:"⥩",rdquo:"”",rdquor:"”",rdsh:"↳",Re:"ℜ",real:"ℜ",realine:"ℛ",realpart:"ℜ",reals:"ℝ",rect:"▭",reg:"®",REG:"®",ReverseElement:"∋",ReverseEquilibrium:"⇋",ReverseUpEquilibrium:"⥯",rfisht:"⥽",rfloor:"⌋",rfr:"𝔯",Rfr:"ℜ",rHar:"⥤",rhard:"⇁",rharu:"⇀",rharul:"⥬",rho:"ρ",Rho:"Ρ",rhov:"ϱ",RightAngleBracket:"⟩",rightarrow:"→",Rightarrow:"⇒",RightArrow:"→",RightArrowBar:"⇥",RightArrowLeftArrow:"⇄",rightarrowtail:"↣",RightCeiling:"⌉",RightDoubleBracket:"⟧",RightDownTeeVector:"⥝",RightDownVector:"⇂",RightDownVectorBar:"⥕",RightFloor:"⌋",rightharpoondown:"⇁",rightharpoonup:"⇀",rightleftarrows:"⇄",rightleftharpoons:"⇌",rightrightarrows:"⇉",rightsquigarrow:"↝",RightTee:"⊢",RightTeeArrow:"↦",RightTeeVector:"⥛",rightthreetimes:"⋌",RightTriangle:"⊳",RightTriangleBar:"⧐",RightTriangleEqual:"⊵",RightUpDownVector:"⥏",RightUpTeeVector:"⥜",RightUpVector:"↾",RightUpVectorBar:"⥔",RightVector:"⇀",RightVectorBar:"⥓",ring:"˚",risingdotseq:"≓",rlarr:"⇄",rlhar:"⇌",rlm:"‏",rmoust:"⎱",rmoustache:"⎱",rnmid:"⫮",roang:"⟭",roarr:"⇾",robrk:"⟧",ropar:"⦆",ropf:"𝕣",Ropf:"ℝ",roplus:"⨮",rotimes:"⨵",RoundImplies:"⥰",rpar:")",rpargt:"⦔",rppolint:"⨒",rrarr:"⇉",Rrightarrow:"⇛",rsaquo:"›",rscr:"𝓇",Rscr:"ℛ",rsh:"↱",Rsh:"↱",rsqb:"]",rsquo:"’",rsquor:"’",rthree:"⋌",rtimes:"⋊",rtri:"▹",rtrie:"⊵",rtrif:"▸",rtriltri:"⧎",RuleDelayed:"⧴",ruluhar:"⥨",rx:"℞",sacute:"ś",Sacute:"Ś",sbquo:"‚",sc:"≻",Sc:"⪼",scap:"⪸",scaron:"š",Scaron:"Š",sccue:"≽",sce:"⪰",scE:"⪴",scedil:"ş",Scedil:"Ş",scirc:"ŝ",Scirc:"Ŝ",scnap:"⪺",scnE:"⪶",scnsim:"⋩",scpolint:"⨓",scsim:"≿",scy:"с",Scy:"С",sdot:"⋅",sdotb:"⊡",sdote:"⩦",searhk:"⤥",searr:"↘",seArr:"⇘",searrow:"↘",sect:"§",semi:";",seswar:"⤩",setminus:"∖",setmn:"∖",sext:"✶",sfr:"𝔰",Sfr:"𝔖",sfrown:"⌢",sharp:"♯",shchcy:"щ",SHCHcy:"Щ",shcy:"ш",SHcy:"Ш",ShortDownArrow:"↓",ShortLeftArrow:"←",shortmid:"∣",shortparallel:"∥",ShortRightArrow:"→",ShortUpArrow:"↑",shy:"­",sigma:"σ",Sigma:"Σ",sigmaf:"ς",sigmav:"ς",sim:"∼",simdot:"⩪",sime:"≃",simeq:"≃",simg:"⪞",simgE:"⪠",siml:"⪝",simlE:"⪟",simne:"≆",simplus:"⨤",simrarr:"⥲",slarr:"←",SmallCircle:"∘",smallsetminus:"∖",smashp:"⨳",smeparsl:"⧤",smid:"∣",smile:"⌣",smt:"⪪",smte:"⪬",smtes:"⪬︀",softcy:"ь",SOFTcy:"Ь",sol:"/",solb:"⧄",solbar:"⌿",sopf:"𝕤",Sopf:"𝕊",spades:"♠",spadesuit:"♠",spar:"∥",sqcap:"⊓",sqcaps:"⊓︀",sqcup:"⊔",sqcups:"⊔︀",Sqrt:"√",sqsub:"⊏",sqsube:"⊑",sqsubset:"⊏",sqsubseteq:"⊑",sqsup:"⊐",sqsupe:"⊒",sqsupset:"⊐",sqsupseteq:"⊒",squ:"□",square:"□",Square:"□",SquareIntersection:"⊓",SquareSubset:"⊏",SquareSubsetEqual:"⊑",SquareSuperset:"⊐",SquareSupersetEqual:"⊒",SquareUnion:"⊔",squarf:"▪",squf:"▪",srarr:"→",sscr:"𝓈",Sscr:"𝒮",ssetmn:"∖",ssmile:"⌣",sstarf:"⋆",star:"☆",Star:"⋆",starf:"★",straightepsilon:"ϵ",straightphi:"ϕ",strns:"¯",sub:"⊂",Sub:"⋐",subdot:"⪽",sube:"⊆",subE:"⫅",subedot:"⫃",submult:"⫁",subne:"⊊",subnE:"⫋",subplus:"⪿",subrarr:"⥹",subset:"⊂",Subset:"⋐",subseteq:"⊆",subseteqq:"⫅",SubsetEqual:"⊆",subsetneq:"⊊",subsetneqq:"⫋",subsim:"⫇",subsub:"⫕",subsup:"⫓",succ:"≻",succapprox:"⪸",succcurlyeq:"≽",Succeeds:"≻",SucceedsEqual:"⪰",SucceedsSlantEqual:"≽",SucceedsTilde:"≿",succeq:"⪰",succnapprox:"⪺",succneqq:"⪶",succnsim:"⋩",succsim:"≿",SuchThat:"∋",sum:"∑",Sum:"∑",sung:"♪",sup:"⊃",Sup:"⋑",sup1:"¹",sup2:"²",sup3:"³",supdot:"⪾",supdsub:"⫘",supe:"⊇",supE:"⫆",supedot:"⫄",Superset:"⊃",SupersetEqual:"⊇",suphsol:"⟉",suphsub:"⫗",suplarr:"⥻",supmult:"⫂",supne:"⊋",supnE:"⫌",supplus:"⫀",supset:"⊃",Supset:"⋑",supseteq:"⊇",supseteqq:"⫆",supsetneq:"⊋",supsetneqq:"⫌",supsim:"⫈",supsub:"⫔",supsup:"⫖",swarhk:"⤦",swarr:"↙",swArr:"⇙",swarrow:"↙",swnwar:"⤪",szlig:"ß",Tab:"\t",target:"⌖",tau:"τ",Tau:"Τ",tbrk:"⎴",tcaron:"ť",Tcaron:"Ť",tcedil:"ţ",Tcedil:"Ţ",tcy:"т",Tcy:"Т",tdot:"⃛",telrec:"⌕",tfr:"𝔱",Tfr:"𝔗",there4:"∴",therefore:"∴",Therefore:"∴",theta:"θ",Theta:"Θ",thetasym:"ϑ",thetav:"ϑ",thickapprox:"≈",thicksim:"∼",ThickSpace:"  ",thinsp:" ",ThinSpace:" ",thkap:"≈",thksim:"∼",thorn:"þ",THORN:"Þ",tilde:"˜",Tilde:"∼",TildeEqual:"≃",TildeFullEqual:"≅",TildeTilde:"≈",times:"×",timesb:"⊠",timesbar:"⨱",timesd:"⨰",tint:"∭",toea:"⤨",top:"⊤",topbot:"⌶",topcir:"⫱",topf:"𝕥",Topf:"𝕋",topfork:"⫚",tosa:"⤩",tprime:"‴",trade:"™",TRADE:"™",triangle:"▵",triangledown:"▿",triangleleft:"◃",trianglelefteq:"⊴",triangleq:"≜",triangleright:"▹",trianglerighteq:"⊵",tridot:"◬",trie:"≜",triminus:"⨺",TripleDot:"⃛",triplus:"⨹",trisb:"⧍",tritime:"⨻",trpezium:"⏢",tscr:"𝓉",Tscr:"𝒯",tscy:"ц",TScy:"Ц",tshcy:"ћ",TSHcy:"Ћ",tstrok:"ŧ",Tstrok:"Ŧ",twixt:"≬",twoheadleftarrow:"↞",twoheadrightarrow:"↠",uacute:"ú",Uacute:"Ú",uarr:"↑",uArr:"⇑",Uarr:"↟",Uarrocir:"⥉",ubrcy:"ў",Ubrcy:"Ў",ubreve:"ŭ",Ubreve:"Ŭ",ucirc:"û",Ucirc:"Û",ucy:"у",Ucy:"У",udarr:"⇅",udblac:"ű",Udblac:"Ű",udhar:"⥮",ufisht:"⥾",ufr:"𝔲",Ufr:"𝔘",ugrave:"ù",Ugrave:"Ù",uHar:"⥣",uharl:"↿",uharr:"↾",uhblk:"▀",ulcorn:"⌜",ulcorner:"⌜",ulcrop:"⌏",ultri:"◸",umacr:"ū",Umacr:"Ū",uml:"¨",UnderBar:"_",UnderBrace:"⏟",UnderBracket:"⎵",UnderParenthesis:"⏝",Union:"⋃",UnionPlus:"⊎",uogon:"ų",Uogon:"Ų",uopf:"𝕦",Uopf:"𝕌",uparrow:"↑",Uparrow:"⇑",UpArrow:"↑",UpArrowBar:"⤒",UpArrowDownArrow:"⇅",updownarrow:"↕",Updownarrow:"⇕",UpDownArrow:"↕",UpEquilibrium:"⥮",upharpoonleft:"↿",upharpoonright:"↾",uplus:"⊎",UpperLeftArrow:"↖",UpperRightArrow:"↗",upsi:"υ",Upsi:"ϒ",upsih:"ϒ",upsilon:"υ",Upsilon:"Υ",UpTee:"⊥",UpTeeArrow:"↥",upuparrows:"⇈",urcorn:"⌝",urcorner:"⌝",urcrop:"⌎",uring:"ů",Uring:"Ů",urtri:"◹",uscr:"𝓊",Uscr:"𝒰",utdot:"⋰",utilde:"ũ",Utilde:"Ũ",utri:"▵",utrif:"▴",uuarr:"⇈",uuml:"ü",Uuml:"Ü",uwangle:"⦧",vangrt:"⦜",varepsilon:"ϵ",varkappa:"ϰ",varnothing:"∅",varphi:"ϕ",varpi:"ϖ",varpropto:"∝",varr:"↕",vArr:"⇕",varrho:"ϱ",varsigma:"ς",varsubsetneq:"⊊︀",varsubsetneqq:"⫋︀",varsupsetneq:"⊋︀",varsupsetneqq:"⫌︀",vartheta:"ϑ",vartriangleleft:"⊲",vartriangleright:"⊳",vBar:"⫨",Vbar:"⫫",vBarv:"⫩",vcy:"в",Vcy:"В",vdash:"⊢",vDash:"⊨",Vdash:"⊩",VDash:"⊫",Vdashl:"⫦",vee:"∨",Vee:"⋁",veebar:"⊻",veeeq:"≚",vellip:"⋮",verbar:"|",Verbar:"‖",vert:"|",Vert:"‖",VerticalBar:"∣",VerticalLine:"|",VerticalSeparator:"❘",VerticalTilde:"≀",VeryThinSpace:" ",vfr:"𝔳",Vfr:"𝔙",vltri:"⊲",vnsub:"⊂⃒",vnsup:"⊃⃒",vopf:"𝕧",Vopf:"𝕍",vprop:"∝",vrtri:"⊳",vscr:"𝓋",Vscr:"𝒱",vsubne:"⊊︀",vsubnE:"⫋︀",vsupne:"⊋︀",vsupnE:"⫌︀",Vvdash:"⊪",vzigzag:"⦚",wcirc:"ŵ",Wcirc:"Ŵ",wedbar:"⩟",wedge:"∧",Wedge:"⋀",wedgeq:"≙",weierp:"℘",wfr:"𝔴",Wfr:"𝔚",wopf:"𝕨",Wopf:"𝕎",wp:"℘",wr:"≀",wreath:"≀",wscr:"𝓌",Wscr:"𝒲",xcap:"⋂",xcirc:"◯",xcup:"⋃",xdtri:"▽",xfr:"𝔵",Xfr:"𝔛",xharr:"⟷",xhArr:"⟺",xi:"ξ",Xi:"Ξ",xlarr:"⟵",xlArr:"⟸",xmap:"⟼",xnis:"⋻",xodot:"⨀",xopf:"𝕩",Xopf:"𝕏",xoplus:"⨁",xotime:"⨂",xrarr:"⟶",xrArr:"⟹",xscr:"𝓍",Xscr:"𝒳",xsqcup:"⨆",xuplus:"⨄",xutri:"△",xvee:"⋁",xwedge:"⋀",yacute:"ý",Yacute:"Ý",yacy:"я",YAcy:"Я",ycirc:"ŷ",Ycirc:"Ŷ",ycy:"ы",Ycy:"Ы",yen:"¥",yfr:"𝔶",Yfr:"𝔜",yicy:"ї",YIcy:"Ї",yopf:"𝕪",Yopf:"𝕐",yscr:"𝓎",Yscr:"𝒴",yucy:"ю",YUcy:"Ю",yuml:"ÿ",Yuml:"Ÿ",zacute:"ź",Zacute:"Ź",zcaron:"ž",Zcaron:"Ž",zcy:"з",Zcy:"З",zdot:"ż",Zdot:"Ż",zeetrf:"ℨ",ZeroWidthSpace:"​",zeta:"ζ",Zeta:"Ζ",zfr:"𝔷",Zfr:"ℨ",zhcy:"ж",ZHcy:"Ж",zigrarr:"⇝",zopf:"𝕫",Zopf:"ℤ",zscr:"𝓏",Zscr:"𝒵",zwj:"‍",zwnj:"‌"},m={aacute:"á",Aacute:"Á",acirc:"â",Acirc:"Â",acute:"´",aelig:"æ",AElig:"Æ",agrave:"à",Agrave:"À",amp:"&",AMP:"&",aring:"å",Aring:"Å",atilde:"ã",Atilde:"Ã",auml:"ä",Auml:"Ä",brvbar:"¦",ccedil:"ç",Ccedil:"Ç",cedil:"¸",cent:"¢",copy:"©",COPY:"©",curren:"¤",deg:"°",divide:"÷",eacute:"é",Eacute:"É",ecirc:"ê",Ecirc:"Ê",egrave:"è",Egrave:"È",eth:"ð",ETH:"Ð",euml:"ë",Euml:"Ë",frac12:"½",frac14:"¼",frac34:"¾",gt:">",GT:">",iacute:"í",Iacute:"Í",icirc:"î",Icirc:"Î",iexcl:"¡",igrave:"ì",Igrave:"Ì",iquest:"¿",iuml:"ï",Iuml:"Ï",laquo:"«",lt:"<",LT:"<",macr:"¯",micro:"µ",middot:"·",nbsp:" ",not:"¬",ntilde:"ñ",Ntilde:"Ñ",oacute:"ó",Oacute:"Ó",ocirc:"ô",Ocirc:"Ô",ograve:"ò",Ograve:"Ò",ordf:"ª",ordm:"º",oslash:"ø",Oslash:"Ø",otilde:"õ",Otilde:"Õ",ouml:"ö",Ouml:"Ö",para:"¶",plusmn:"±",pound:"£",quot:'"',QUOT:'"',raquo:"»",reg:"®",REG:"®",sect:"§",shy:"­",sup1:"¹",sup2:"²",sup3:"³",szlig:"ß",thorn:"þ",THORN:"Þ",times:"×",uacute:"ú",Uacute:"Ú",ucirc:"û",Ucirc:"Û",ugrave:"ù",Ugrave:"Ù",uml:"¨",uuml:"ü",Uuml:"Ü",yacute:"ý",Yacute:"Ý",yen:"¥",yuml:"ÿ"},v={0:"�",128:"€",130:"‚",131:"ƒ",132:"„",133:"…",134:"†",135:"‡",136:"ˆ",137:"‰",138:"Š",139:"‹",140:"Œ",142:"Ž",145:"‘",146:"’",147:"“",148:"”",149:"•",150:"–",151:"—",152:"˜",153:"™",154:"š",155:"›",156:"œ",158:"ž",159:"Ÿ"},_=[1,2,3,4,5,6,7,8,11,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,64976,64977,64978,64979,64980,64981,64982,64983,64984,64985,64986,64987,64988,64989,64990,64991,64992,64993,64994,64995,64996,64997,64998,64999,65e3,65001,65002,65003,65004,65005,65006,65007,65534,65535,131070,131071,196606,196607,262142,262143,327678,327679,393214,393215,458750,458751,524286,524287,589822,589823,655358,655359,720894,720895,786430,786431,851966,851967,917502,917503,983038,983039,1048574,1048575,1114110,1114111],w=String.fromCharCode,x={}.hasOwnProperty,k=function(t,e){return x.call(t,e)},E=function(t,e){if(!t)return e;var n,r={};for(n in e)r[n]=k(t,n)?t[n]:e[n];return r},A=function(t,e){var n="";return t>=55296&&t<=57343||t>1114111?(e&&M("character reference outside the permissible Unicode range"),"�"):k(v,t)?(e&&M("disallowed character reference"),v[t]):(e&&function(t,e){for(var n=-1,r=t.length;++n65535&&(n+=w((t-=65536)>>>10&1023|55296),t=56320|1023&t),n+=w(t))},S=function(t){return"&#x"+t.toString(16).toUpperCase()+";"},T=function(t){return"&#"+t+";"},M=function(t){throw Error("Parse error: "+t)},D=function(t,e){(e=E(e,D.options)).strict&&g.test(t)&&M("forbidden code point");var n=e.encodeEverything,r=e.useNamedReferences,i=e.allowUnsafeSymbols,o=e.decimal?T:S,a=function(t){return o(t.charCodeAt(0))};return n?(t=t.replace(u,(function(t){return r&&k(l,t)?"&"+l[t]+";":a(t)})),r&&(t=t.replace(/>\u20D2/g,">⃒").replace(/<\u20D2/g,"<⃒").replace(/fj/g,"fj")),r&&(t=t.replace(f,(function(t){return"&"+l[t]+";"})))):r?(i||(t=t.replace(h,(function(t){return"&"+l[t]+";"}))),t=(t=t.replace(/>\u20D2/g,">⃒").replace(/<\u20D2/g,"<⃒")).replace(f,(function(t){return"&"+l[t]+";"}))):i||(t=t.replace(h,a)),t.replace(s,(function(t){var e=t.charCodeAt(0),n=t.charCodeAt(1);return o(1024*(e-55296)+n-56320+65536)})).replace(c,a)};D.options={allowUnsafeSymbols:!1,encodeEverything:!1,strict:!1,useNamedReferences:!1,decimal:!1};var C=function(t,e){var n=(e=E(e,C.options)).strict;return n&&p.test(t)&&M("malformed character reference"),t.replace(y,(function(t,r,i,o,a,s,u,c,f){var l,h,d,p,g,y;return r?b[g=r]:i?(g=i,(y=o)&&e.isAttributeValue?(n&&"="==y&&M("`&` did not start a character reference"),t):(n&&M("named character reference was not terminated by a semicolon"),m[g]+(y||""))):a?(d=a,h=s,n&&!h&&M("character reference was not terminated by a semicolon"),l=parseInt(d,10),A(l,n)):u?(p=u,h=c,n&&!h&&M("character reference was not terminated by a semicolon"),l=parseInt(p,16),A(l,n)):(n&&M("named character reference was not terminated by a semicolon"),t)}))};C.options={isAttributeValue:!1,strict:!1};var O={version:"1.2.0",encode:D,decode:C,escape:function(t){return t.replace(h,(function(t){return d[t]}))},unescape:C};if("function"==typeof define&&"object"==typeof define.amd&&define.amd)define((function(){return O}));else if(i&&!i.nodeType)if(o)o.exports=O;else for(var R in O)k(O,R)&&(i[R]=O[R]);else r.he=O}(this)}).call(this,n(9)(t),n(11))},function(t,e,n){"use strict";var r=n(229),i=n(230),o=n(231);function a(t,e,n){if(!t)return t;if(!e)return t;"string"==typeof n&&(n={keyframes:n}),n||(n={keyframes:!1}),t=s(t,e+" $1$2");var i=e.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&");t=(t=(t=(t=t.replace(new RegExp("("+i+")\\s*\\1(?=[\\s\\r\\n,{])","g"),"$1")).replace(new RegExp("("+i+")\\s*:host","g"),"$1")).replace(new RegExp("("+i+")\\s*@","g"),"@")).replace(new RegExp("("+i+")\\s*:root","g"),":root");for(var o,a=[],u=/@keyframes\s+([a-zA-Z0-9_-]+)\s*{/g;null!==(o=u.exec(t));)a.indexOf(o[1])<0&&a.push(o[1]);var c=r(e);return a.forEach((function(e){var r=(!0===n.keyframes?c+"-":"string"==typeof n.keyframes?n.keyframes:"")+e;t=(t=t.replace(new RegExp("(@keyframes\\s+)"+e+"(\\s*{)","g"),"$1"+r+"$2")).replace(new RegExp("(animation(?:-name)?\\s*:[^;]*\\s*)"+e+"([\\s;}])","g"),"$1"+r+"$2")})),t=t.replace(new RegExp("("+i+" )(\\s*(?:to|from|[+-]?(?:(?:\\.\\d+)|(?:\\d+(?:\\.\\d*)?))%))(?=[\\s\\r\\n,{])","g"),"$2")}function s(t,e){var n=[];return t=o(t),t=(t=i.replace(t,!0,n)).replace(/([^\r\n,{}]+)(,(?=[^}]*{)|\s*{)/g,e),t=i.paste(t,n)}t.exports=a,a.replace=s},function(t,e,n){"use strict";const r=n(418),i="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~".split(""),o=(t,e)=>{const n=e.length,i=Math.floor(65536/n)*n-1,o=2*Math.ceil(1.1*t);let a="",s=0;for(;si||(a+=e[t%n],s++)}}return a},a=[void 0,"hex","base64","url-safe"];t.exports=({length:t,type:e,characters:n})=>{if(!(t>=0&&Number.isFinite(t)))throw new TypeError("Expected a `length` to be a non-negative finite number");if(void 0!==e&&void 0!==n)throw new TypeError("Expected either `type` or `characters`");if(void 0!==n&&"string"!=typeof n)throw new TypeError("Expected `characters` to be string");if(!a.includes(e))throw new TypeError(`Unknown type: ${e}`);if(void 0===e&&void 0===n&&(e="hex"),"hex"===e||void 0===e&&void 0===n)return r.randomBytes(Math.ceil(.5*t)).toString("hex").slice(0,t);if("base64"===e)return r.randomBytes(Math.ceil(.75*t)).toString("base64").slice(0,t);if("url-safe"===e)return o(t,i);if(0===n.length)throw new TypeError("Expected `characters` string length to be greater than or equal to 1");if(n.length>65536)throw new TypeError("Expected `characters` string length to be less or equal to 65536");return o(t,n.split(""))}},function(t,e,n){var r;r=function(){var t=JSON.parse('{"$":"dollar","%":"percent","&":"and","<":"less",">":"greater","|":"or","¢":"cent","£":"pound","¤":"currency","¥":"yen","©":"(c)","ª":"a","®":"(r)","º":"o","À":"A","Á":"A","Â":"A","Ã":"A","Ä":"A","Å":"A","Æ":"AE","Ç":"C","È":"E","É":"E","Ê":"E","Ë":"E","Ì":"I","Í":"I","Î":"I","Ï":"I","Ð":"D","Ñ":"N","Ò":"O","Ó":"O","Ô":"O","Õ":"O","Ö":"O","Ø":"O","Ù":"U","Ú":"U","Û":"U","Ü":"U","Ý":"Y","Þ":"TH","ß":"ss","à":"a","á":"a","â":"a","ã":"a","ä":"a","å":"a","æ":"ae","ç":"c","è":"e","é":"e","ê":"e","ë":"e","ì":"i","í":"i","î":"i","ï":"i","ð":"d","ñ":"n","ò":"o","ó":"o","ô":"o","õ":"o","ö":"o","ø":"o","ù":"u","ú":"u","û":"u","ü":"u","ý":"y","þ":"th","ÿ":"y","Ā":"A","ā":"a","Ă":"A","ă":"a","Ą":"A","ą":"a","Ć":"C","ć":"c","Č":"C","č":"c","Ď":"D","ď":"d","Đ":"DJ","đ":"dj","Ē":"E","ē":"e","Ė":"E","ė":"e","Ę":"e","ę":"e","Ě":"E","ě":"e","Ğ":"G","ğ":"g","Ģ":"G","ģ":"g","Ĩ":"I","ĩ":"i","Ī":"i","ī":"i","Į":"I","į":"i","İ":"I","ı":"i","Ķ":"k","ķ":"k","Ļ":"L","ļ":"l","Ľ":"L","ľ":"l","Ł":"L","ł":"l","Ń":"N","ń":"n","Ņ":"N","ņ":"n","Ň":"N","ň":"n","Ő":"O","ő":"o","Œ":"OE","œ":"oe","Ŕ":"R","ŕ":"r","Ř":"R","ř":"r","Ś":"S","ś":"s","Ş":"S","ş":"s","Š":"S","š":"s","Ţ":"T","ţ":"t","Ť":"T","ť":"t","Ũ":"U","ũ":"u","Ū":"u","ū":"u","Ů":"U","ů":"u","Ű":"U","ű":"u","Ų":"U","ų":"u","Ŵ":"W","ŵ":"w","Ŷ":"Y","ŷ":"y","Ÿ":"Y","Ź":"Z","ź":"z","Ż":"Z","ż":"z","Ž":"Z","ž":"z","ƒ":"f","Ơ":"O","ơ":"o","Ư":"U","ư":"u","Lj":"LJ","lj":"lj","Nj":"NJ","nj":"nj","Ș":"S","ș":"s","Ț":"T","ț":"t","˚":"o","Ά":"A","Έ":"E","Ή":"H","Ί":"I","Ό":"O","Ύ":"Y","Ώ":"W","ΐ":"i","Α":"A","Β":"B","Γ":"G","Δ":"D","Ε":"E","Ζ":"Z","Η":"H","Θ":"8","Ι":"I","Κ":"K","Λ":"L","Μ":"M","Ν":"N","Ξ":"3","Ο":"O","Π":"P","Ρ":"R","Σ":"S","Τ":"T","Υ":"Y","Φ":"F","Χ":"X","Ψ":"PS","Ω":"W","Ϊ":"I","Ϋ":"Y","ά":"a","έ":"e","ή":"h","ί":"i","ΰ":"y","α":"a","β":"b","γ":"g","δ":"d","ε":"e","ζ":"z","η":"h","θ":"8","ι":"i","κ":"k","λ":"l","μ":"m","ν":"n","ξ":"3","ο":"o","π":"p","ρ":"r","ς":"s","σ":"s","τ":"t","υ":"y","φ":"f","χ":"x","ψ":"ps","ω":"w","ϊ":"i","ϋ":"y","ό":"o","ύ":"y","ώ":"w","Ё":"Yo","Ђ":"DJ","Є":"Ye","І":"I","Ї":"Yi","Ј":"J","Љ":"LJ","Њ":"NJ","Ћ":"C","Џ":"DZ","А":"A","Б":"B","В":"V","Г":"G","Д":"D","Е":"E","Ж":"Zh","З":"Z","И":"I","Й":"J","К":"K","Л":"L","М":"M","Н":"N","О":"O","П":"P","Р":"R","С":"S","Т":"T","У":"U","Ф":"F","Х":"H","Ц":"C","Ч":"Ch","Ш":"Sh","Щ":"Sh","Ъ":"U","Ы":"Y","Ь":"","Э":"E","Ю":"Yu","Я":"Ya","а":"a","б":"b","в":"v","г":"g","д":"d","е":"e","ж":"zh","з":"z","и":"i","й":"j","к":"k","л":"l","м":"m","н":"n","о":"o","п":"p","р":"r","с":"s","т":"t","у":"u","ф":"f","х":"h","ц":"c","ч":"ch","ш":"sh","щ":"sh","ъ":"u","ы":"y","ь":"","э":"e","ю":"yu","я":"ya","ё":"yo","ђ":"dj","є":"ye","і":"i","ї":"yi","ј":"j","љ":"lj","њ":"nj","ћ":"c","џ":"dz","Ґ":"G","ґ":"g","฿":"baht","ა":"a","ბ":"b","გ":"g","დ":"d","ე":"e","ვ":"v","ზ":"z","თ":"t","ი":"i","კ":"k","ლ":"l","მ":"m","ნ":"n","ო":"o","პ":"p","ჟ":"zh","რ":"r","ს":"s","ტ":"t","უ":"u","ფ":"f","ქ":"k","ღ":"gh","ყ":"q","შ":"sh","ჩ":"ch","ც":"ts","ძ":"dz","წ":"ts","ჭ":"ch","ხ":"kh","ჯ":"j","ჰ":"h","Ẁ":"W","ẁ":"w","Ẃ":"W","ẃ":"w","Ẅ":"W","ẅ":"w","ẞ":"SS","Ạ":"A","ạ":"a","Ả":"A","ả":"a","Ấ":"A","ấ":"a","Ầ":"A","ầ":"a","Ẩ":"A","ẩ":"a","Ẫ":"A","ẫ":"a","Ậ":"A","ậ":"a","Ắ":"A","ắ":"a","Ằ":"A","ằ":"a","Ẳ":"A","ẳ":"a","Ẵ":"A","ẵ":"a","Ặ":"A","ặ":"a","Ẹ":"E","ẹ":"e","Ẻ":"E","ẻ":"e","Ẽ":"E","ẽ":"e","Ế":"E","ế":"e","Ề":"E","ề":"e","Ể":"E","ể":"e","Ễ":"E","ễ":"e","Ệ":"E","ệ":"e","Ỉ":"I","ỉ":"i","Ị":"I","ị":"i","Ọ":"O","ọ":"o","Ỏ":"O","ỏ":"o","Ố":"O","ố":"o","Ồ":"O","ồ":"o","Ổ":"O","ổ":"o","Ỗ":"O","ỗ":"o","Ộ":"O","ộ":"o","Ớ":"O","ớ":"o","Ờ":"O","ờ":"o","Ở":"O","ở":"o","Ỡ":"O","ỡ":"o","Ợ":"O","ợ":"o","Ụ":"U","ụ":"u","Ủ":"U","ủ":"u","Ứ":"U","ứ":"u","Ừ":"U","ừ":"u","Ử":"U","ử":"u","Ữ":"U","ữ":"u","Ự":"U","ự":"u","Ỳ":"Y","ỳ":"y","Ỵ":"Y","ỵ":"y","Ỷ":"Y","ỷ":"y","Ỹ":"Y","ỹ":"y","‘":"\'","’":"\'","“":"\\"","”":"\\"","†":"+","•":"*","…":"...","₠":"ecu","₢":"cruzeiro","₣":"french franc","₤":"lira","₥":"mill","₦":"naira","₧":"peseta","₨":"rupee","₩":"won","₪":"new shequel","₫":"dong","€":"euro","₭":"kip","₮":"tugrik","₯":"drachma","₰":"penny","₱":"peso","₲":"guarani","₳":"austral","₴":"hryvnia","₵":"cedi","₹":"indian rupee","₽":"russian ruble","₿":"bitcoin","℠":"sm","™":"tm","∂":"d","∆":"delta","∑":"sum","∞":"infinity","♥":"love","元":"yuan","円":"yen","﷼":"rial"}'),e=JSON.parse('{"bg":{"locale":"Bulgarian","ѝ":"u"}}');function n(n,r){if("string"!=typeof n)throw new Error("slugify: string argument expected");var i=e[(r="string"==typeof r?{replacement:r}:r||{}).locale]||{},o=n.split("").reduce((function(e,n){return e+(i[n]||t[n]||n).replace(r.remove||/[^\w\s$*_+~.()'"!\-:@]/g,"")}),"").trim().replace(/[-\s]+/g,r.replacement||"-");return r.lower?o.toLowerCase():o}return n.extend=function(e){for(var n in e)t[n]=e[n]},n},t.exports=r(),t.exports.default=r()},function(t,e,n){ +/*! + * Escaper v2.5.3 + * https://github.com/kobezzza/Escaper + * + * Released under the MIT license + * https://github.com/kobezzza/Escaper/blob/master/LICENSE + * + * Date: Tue, 23 Jan 2018 15:58:45 GMT + */ +!function(t){"use strict";var e="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},n=void 0,r=n={VERSION:[2,5,3],content:[],cache:{},snakeskinRgxp:null,symbols:null,replace:M,paste:C},i={'"':!0,"'":!0,"`":!0},o={"/":!0};for(var a in i){if(!i.hasOwnProperty(a))break;o[a]=!0}var s={"//":!0,"//*":!0,"//!":!0,"//#":!0,"//@":!0,"//$":!0},u={"/*":!0,"/**":!0,"/*!":!0,"/*#":!0,"/*@":!0,"/*$":!0},c=[],f={};for(var l in o){if(!o.hasOwnProperty(l))break;c.push(l),f[l]=!0}for(var h in s){if(!s.hasOwnProperty(h))break;c.push(h),f[h]=!0}for(var d in u){if(!u.hasOwnProperty(d))break;c.push(d),f[d]=!0}var p=[],g={g:!0,m:!0,i:!0,y:!0,u:!0};for(var y in g){if(!g.hasOwnProperty(y))break;p.push(y)}var b={"-":!0,"+":!0,"*":!0,"%":!0,"~":!0,">":!0,"<":!0,"^":!0,",":!0,";":!0,"=":!0,"|":!0,"&":!0,"!":!0,"?":!0,":":!0,"(":!0,"{":!0,"[":!0},m={return:!0,yield:!0,await:!0,typeof:!0,void:!0,instanceof:!0,delete:!0,in:!0,new:!0,of:!0};function v(t,e,n){for(var r in t){if(!t.hasOwnProperty(r))break;r in e==0&&(e[r]=n)}}var _=void 0,w=void 0,x=/[^\s/]/,k=/[a-z]/,E=/\s/,A=/[\r\n]/,S=/\${pos}/g,T={object:!0,function:!0};function M(t,r,a,l){_=_||n.symbols||"a-z",w=w||n.snakeskinRgxp||new RegExp("[!$"+_+"_]","i");var h=n,d=h.cache,y=h.content,M=Boolean(r&&T[void 0===r?"undefined":e(r)]),D=M?Object(r):{};function C(t){return D["@label"]?D["@label"].replace(S,t):"__ESCAPER_QUOT__"+t+"_"}var O=!1;"boolean"==typeof r&&(O=Boolean(r)),"@comments"in D&&(v(u,D,D["@comments"]),v(s,D,D["@comments"]),delete D["@comments"]),"@strings"in D&&(v(i,D,D["@strings"]),delete D["@strings"]),"@literals"in D&&(v(o,D,D["@literals"]),delete D["@literals"]),"@all"in D&&(v(f,D,D["@all"]),delete D["@all"]);for(var R="",I=-1;++I2&&u[j])&&(D[j]&&(H=t.substring(U,K+1),-1===D[j]?$="":($=C(L.length),L.push(H)),t=t.substring(0,U)+$+t.substring(K+1),K+=$.length-H.length),j=!1);else{if(!P){if("/"===X&&((s[J]||u[J])&&(j=s[Q]||u[Q]?Q:J),j)){U=K;continue}b[X]||m[W]?(F=!0,W=""):x.test(X)&&(F=!1),k.test(X)?G+=X:(W=G,G="");var tt=!1;l&&("|"===X&&w.test(Z)?(V=!0,F=!1,tt=!0):V&&E.test(X)&&(V=!1,F=!0,tt=!0)),tt||(b[X]?F=!0:x.test(X)&&(F=!1))}if("/"!==P||q||("["===X?z=!0:"]"===X&&(z=!1)),!P&&Y&&("}"===X?Y--:"{"===X&&Y++,Y||(X="`")),"`"!==P||q||"${"!==J||(X="`",K++,Y++),!f[X]||"/"===X&&!F||P){if(P&&("\\"===X||q))q=!q;else if(f[X]&&P===X&&!q&&("/"!==P||!z)){if("/"===X)for(var et=-1;++et-1}},function(t,e,n){var r=n(63);t.exports=function(t,e){var n=this.__data__,i=r(n,t);return i<0?(++this.size,n.push([t,e])):n[i][1]=e,this}},function(t,e,n){var r=n(62);t.exports=function(){this.__data__=new r,this.size=0}},function(t,e){t.exports=function(t){var e=this.__data__,n=e.delete(t);return this.size=e.size,n}},function(t,e){t.exports=function(t){return this.__data__.get(t)}},function(t,e){t.exports=function(t){return this.__data__.has(t)}},function(t,e,n){var r=n(62),i=n(90),o=n(91),a=200;t.exports=function(t,e){var n=this.__data__;if(n instanceof r){var s=n.__data__;if(!i||s.length0){if(++e>=n)return arguments[0]}else e=0;return t.apply(void 0,arguments)}}},function(t,e,n){var r=n(153),i=n(327),o=n(331),a=n(154),s=n(332),u=n(103),c=200;t.exports=function(t,e,n){var f=-1,l=i,h=t.length,d=!0,p=[],g=p;if(n)d=!1,l=o;else if(h>=c){var y=e?null:s(t);if(y)return u(y);d=!1,l=a,g=new r}else g=e?[]:p;t:for(;++f-1}},function(t,e,n){var r=n(167),i=n(329),o=n(330);t.exports=function(t,e,n){return e==e?o(t,e,n):r(t,i,n)}},function(t,e){t.exports=function(t){return t!=t}},function(t,e){t.exports=function(t,e,n){for(var r=n-1,i=t.length;++r1||1===e.length&&t.hasEdge(e[0],e[0])}))}},function(t,e,n){var r=n(12);t.exports=function(t,e,n){return function(t,e,n){var r={},i=t.nodes();return i.forEach((function(t){r[t]={},r[t][t]={distance:0},i.forEach((function(e){t!==e&&(r[t][e]={distance:Number.POSITIVE_INFINITY})})),n(t).forEach((function(n){var i=n.v===t?n.w:n.v,o=e(n);r[t][i]={distance:o,predecessor:t}}))})),i.forEach((function(t){var e=r[t];i.forEach((function(n){var o=r[n];i.forEach((function(n){var r=o[t],i=e[n],a=o[n],s=r.distance+i.distance;s0;){if(n=u.removeMin(),r.has(s,n))a.setEdge(n,s[n]);else{if(f)throw new Error("Input graph is not connected: "+t);f=!0}t.nodeEdges(n).forEach(c)}return a}},function(t,e,n){var r;try{r=n(22)}catch(t){}r||(r=window.graphlib),t.exports=r},function(t,e,n){"use strict";var r=n(4),i=n(380),o=n(383),a=n(384),s=n(10).normalizeRanks,u=n(386),c=n(10).removeEmptyRanks,f=n(387),l=n(388),h=n(389),d=n(390),p=n(399),g=n(10),y=n(19).Graph;t.exports=function(t,e){var n=e&&e.debugTiming?g.time:g.notime;n("layout",(function(){var e=n(" buildLayoutGraph",(function(){return function(t){var e=new y({multigraph:!0,compound:!0}),n=S(t.graph());return e.setGraph(r.merge({},m,A(n,b),r.pick(n,v))),r.forEach(t.nodes(),(function(n){var i=S(t.node(n));e.setNode(n,r.defaults(A(i,_),w)),e.setParent(n,t.parent(n))})),r.forEach(t.edges(),(function(n){var i=S(t.edge(n));e.setEdge(n,r.merge({},k,A(i,x),r.pick(i,E)))})),e}(t)}));n(" runLayout",(function(){!function(t,e){e(" makeSpaceForEdgeLabels",(function(){!function(t){var e=t.graph();e.ranksep/=2,r.forEach(t.edges(),(function(n){var r=t.edge(n);r.minlen*=2,"c"!==r.labelpos.toLowerCase()&&("TB"===e.rankdir||"BT"===e.rankdir?r.width+=r.labeloffset:r.height+=r.labeloffset)}))}(t)})),e(" removeSelfEdges",(function(){!function(t){r.forEach(t.edges(),(function(e){if(e.v===e.w){var n=t.node(e.v);n.selfEdges||(n.selfEdges=[]),n.selfEdges.push({e:e,label:t.edge(e)}),t.removeEdge(e)}}))}(t)})),e(" acyclic",(function(){i.run(t)})),e(" nestingGraph.run",(function(){f.run(t)})),e(" rank",(function(){a(g.asNonCompoundGraph(t))})),e(" injectEdgeLabelProxies",(function(){!function(t){r.forEach(t.edges(),(function(e){var n=t.edge(e);if(n.width&&n.height){var r=t.node(e.v),i={rank:(t.node(e.w).rank-r.rank)/2+r.rank,e:e};g.addDummyNode(t,"edge-proxy",i,"_ep")}}))}(t)})),e(" removeEmptyRanks",(function(){c(t)})),e(" nestingGraph.cleanup",(function(){f.cleanup(t)})),e(" normalizeRanks",(function(){s(t)})),e(" assignRankMinMax",(function(){!function(t){var e=0;r.forEach(t.nodes(),(function(n){var i=t.node(n);i.borderTop&&(i.minRank=t.node(i.borderTop).rank,i.maxRank=t.node(i.borderBottom).rank,e=r.max(e,i.maxRank))})),t.graph().maxRank=e}(t)})),e(" removeEdgeLabelProxies",(function(){!function(t){r.forEach(t.nodes(),(function(e){var n=t.node(e);"edge-proxy"===n.dummy&&(t.edge(n.e).labelRank=n.rank,t.removeNode(e))}))}(t)})),e(" normalize.run",(function(){o.run(t)})),e(" parentDummyChains",(function(){u(t)})),e(" addBorderSegments",(function(){l(t)})),e(" order",(function(){d(t)})),e(" insertSelfEdges",(function(){!function(t){var e=g.buildLayerMatrix(t);r.forEach(e,(function(e){var n=0;r.forEach(e,(function(e,i){var o=t.node(e);o.order=i+n,r.forEach(o.selfEdges,(function(e){g.addDummyNode(t,"selfedge",{width:e.label.width,height:e.label.height,rank:o.rank,order:i+ ++n,e:e.e,label:e.label},"_se")})),delete o.selfEdges}))}))}(t)})),e(" adjustCoordinateSystem",(function(){h.adjust(t)})),e(" position",(function(){p(t)})),e(" positionSelfEdges",(function(){!function(t){r.forEach(t.nodes(),(function(e){var n=t.node(e);if("selfedge"===n.dummy){var r=t.node(n.e.v),i=r.x+r.width/2,o=r.y,a=n.x-i,s=r.height/2;t.setEdge(n.e,n.label),t.removeNode(e),n.label.points=[{x:i+2*a/3,y:o-s},{x:i+5*a/6,y:o-s},{x:i+a,y:o},{x:i+5*a/6,y:o+s},{x:i+2*a/3,y:o+s}],n.label.x=n.x,n.label.y=n.y}}))}(t)})),e(" removeBorderNodes",(function(){!function(t){r.forEach(t.nodes(),(function(e){if(t.children(e).length){var n=t.node(e),i=t.node(n.borderTop),o=t.node(n.borderBottom),a=t.node(r.last(n.borderLeft)),s=t.node(r.last(n.borderRight));n.width=Math.abs(s.x-a.x),n.height=Math.abs(o.y-i.y),n.x=a.x+n.width/2,n.y=i.y+n.height/2}})),r.forEach(t.nodes(),(function(e){"border"===t.node(e).dummy&&t.removeNode(e)}))}(t)})),e(" normalize.undo",(function(){o.undo(t)})),e(" fixupEdgeLabelCoords",(function(){!function(t){r.forEach(t.edges(),(function(e){var n=t.edge(e);if(r.has(n,"x"))switch("l"!==n.labelpos&&"r"!==n.labelpos||(n.width-=n.labeloffset),n.labelpos){case"l":n.x-=n.width/2+n.labeloffset;break;case"r":n.x+=n.width/2+n.labeloffset}}))}(t)})),e(" undoCoordinateSystem",(function(){h.undo(t)})),e(" translateGraph",(function(){!function(t){var e=Number.POSITIVE_INFINITY,n=0,i=Number.POSITIVE_INFINITY,o=0,a=t.graph(),s=a.marginx||0,u=a.marginy||0;function c(t){var r=t.x,a=t.y,s=t.width,u=t.height;e=Math.min(e,r-s/2),n=Math.max(n,r+s/2),i=Math.min(i,a-u/2),o=Math.max(o,a+u/2)}r.forEach(t.nodes(),(function(e){c(t.node(e))})),r.forEach(t.edges(),(function(e){var n=t.edge(e);r.has(n,"x")&&c(n)})),e-=s,i-=u,r.forEach(t.nodes(),(function(n){var r=t.node(n);r.x-=e,r.y-=i})),r.forEach(t.edges(),(function(n){var o=t.edge(n);r.forEach(o.points,(function(t){t.x-=e,t.y-=i})),r.has(o,"x")&&(o.x-=e),r.has(o,"y")&&(o.y-=i)})),a.width=n-e+s,a.height=o-i+u}(t)})),e(" assignNodeIntersects",(function(){!function(t){r.forEach(t.edges(),(function(e){var n,r,i=t.edge(e),o=t.node(e.v),a=t.node(e.w);i.points?(n=i.points[0],r=i.points[i.points.length-1]):(i.points=[],n=a,r=o),i.points.unshift(g.intersectRect(o,n)),i.points.push(g.intersectRect(a,r))}))}(t)})),e(" reversePoints",(function(){!function(t){r.forEach(t.edges(),(function(e){var n=t.edge(e);n.reversed&&n.points.reverse()}))}(t)})),e(" acyclic.undo",(function(){i.undo(t)}))}(e,n)})),n(" updateInputGraph",(function(){!function(t,e){r.forEach(t.nodes(),(function(n){var r=t.node(n),i=e.node(n);r&&(r.x=i.x,r.y=i.y,e.children(n).length&&(r.width=i.width,r.height=i.height))})),r.forEach(t.edges(),(function(n){var i=t.edge(n),o=e.edge(n);i.points=o.points,r.has(o,"x")&&(i.x=o.x,i.y=o.y)})),t.graph().width=e.graph().width,t.graph().height=e.graph().height}(t,e)}))}))};var b=["nodesep","edgesep","ranksep","marginx","marginy"],m={ranksep:50,edgesep:20,nodesep:50,rankdir:"tb"},v=["acyclicer","ranker","rankdir","align"],_=["width","height"],w={width:0,height:0},x=["minlen","weight","width","height","labeloffset"],k={minlen:1,weight:1,width:0,height:0,labeloffset:10,labelpos:"r"},E=["labelpos"];function A(t,e){return r.mapValues(r.pick(t,e),Number)}function S(t){var e={};return r.forEach(t,(function(t,n){e[n.toLowerCase()]=t})),e}},function(t,e,n){var r=n(130),i=1,o=4;t.exports=function(t){return r(t,i|o)}},function(t,e,n){var r=n(350)(n(351));t.exports=r},function(t,e,n){var r=n(25),i=n(24),o=n(27);t.exports=function(t){return function(e,n,a){var s=Object(e);if(!i(e)){var u=r(n,3);e=o(e),n=function(t){return u(s[t],t,s)}}var c=t(e,n,a);return c>-1?s[u?e[c]:c]:void 0}}},function(t,e,n){var r=n(167),i=n(25),o=n(352),a=Math.max;t.exports=function(t,e,n){var s=null==t?0:t.length;if(!s)return-1;var u=null==n?0:o(n);return u<0&&(u=a(s+u,0)),r(t,i(e,3),u)}},function(t,e,n){var r=n(177);t.exports=function(t){var e=r(t),n=e%1;return e==e?n?e-n:e:0}},function(t,e,n){var r=n(13),i=n(42),o=NaN,a=/^\s+|\s+$/g,s=/^[-+]0x[0-9a-f]+$/i,u=/^0b[01]+$/i,c=/^0o[0-7]+$/i,f=parseInt;t.exports=function(t){if("number"==typeof t)return t;if(i(t))return o;if(r(t)){var e="function"==typeof t.valueOf?t.valueOf():t;t=r(e)?e+"":e}if("string"!=typeof t)return 0===t?t:+t;t=t.replace(a,"");var n=u.test(t);return n||c.test(t)?f(t.slice(2),n?2:8):s.test(t)?o:+t}},function(t,e,n){var r=n(102),i=n(149),o=n(40);t.exports=function(t,e){return null==t?t:r(t,i(e),o)}},function(t,e){t.exports=function(t){var e=null==t?0:t.length;return e?t[e-1]:void 0}},function(t,e,n){var r=n(67),i=n(101),o=n(25);t.exports=function(t,e){var n={};return e=o(e,3),i(t,(function(t,i,o){r(n,i,e(t,i,o))})),n}},function(t,e,n){var r=n(108),i=n(358),o=n(34);t.exports=function(t){return t&&t.length?r(t,o,i):void 0}},function(t,e){t.exports=function(t,e){return t>e}},function(t,e,n){var r=n(360),i=n(363)((function(t,e,n){r(t,e,n)}));t.exports=i},function(t,e,n){var r=n(61),i=n(179),o=n(102),a=n(361),s=n(13),u=n(40),c=n(181);t.exports=function t(e,n,f,l,h){e!==n&&o(n,(function(o,u){if(h||(h=new r),s(o))a(e,n,u,f,t,l,h);else{var d=l?l(c(e,u),o,u+"",e,n,h):void 0;void 0===d&&(d=o),i(e,u,d)}}),u)}},function(t,e,n){var r=n(179),i=n(136),o=n(145),a=n(137),s=n(146),u=n(50),c=n(6),f=n(168),l=n(39),h=n(37),d=n(13),p=n(180),g=n(51),y=n(181),b=n(362);t.exports=function(t,e,n,m,v,_,w){var x=y(t,n),k=y(e,n),E=w.get(k);if(E)r(t,n,E);else{var A=_?_(x,k,n+"",t,e,w):void 0,S=void 0===A;if(S){var T=c(k),M=!T&&l(k),D=!T&&!M&&g(k);A=k,T||M||D?c(x)?A=x:f(x)?A=a(x):M?(S=!1,A=i(k,!0)):D?(S=!1,A=o(k,!0)):A=[]:p(k)||u(k)?(A=x,u(x)?A=b(x):d(x)&&!h(x)||(A=s(k))):S=!1}S&&(w.set(k,A),v(A,k,m,_,w),w.delete(k)),r(t,n,A)}}},function(t,e,n){var r=n(49),i=n(40);t.exports=function(t){return r(t,i(t))}},function(t,e,n){var r=n(75),i=n(76);t.exports=function(t){return r((function(e,n){var r=-1,o=n.length,a=o>1?n[o-1]:void 0,s=o>2?n[2]:void 0;for(a=t.length>3&&"function"==typeof a?(o--,a):void 0,s&&i(n[0],n[1],s)&&(a=o<3?void 0:a,o=1),e=Object(e);++r1&&a(t,e[0],e[1])?e=[]:n>2&&a(e[0],e[1],e[2])&&(e=[e[0]]),i(t,r(e,1),[])}));t.exports=s},function(t,e,n){var r=n(74),i=n(25),o=n(163),a=n(375),s=n(69),u=n(376),c=n(34);t.exports=function(t,e,n){var f=-1;e=r(e.length?e:[c],s(i));var l=o(t,(function(t,n,i){return{criteria:r(e,(function(e){return e(t)})),index:++f,value:t}}));return a(l,(function(t,e){return u(t,e,n)}))}},function(t,e){t.exports=function(t,e){var n=t.length;for(t.sort(e);n--;)t[n]=t[n].value;return t}},function(t,e,n){var r=n(377);t.exports=function(t,e,n){for(var i=-1,o=t.criteria,a=e.criteria,s=o.length,u=n.length;++i=u?c:c*("desc"==n[i]?-1:1)}return t.index-e.index}},function(t,e,n){var r=n(42);t.exports=function(t,e){if(t!==e){var n=void 0!==t,i=null===t,o=t==t,a=r(t),s=void 0!==e,u=null===e,c=e==e,f=r(e);if(!u&&!f&&!a&&t>e||a&&s&&c&&!u&&!f||i&&s&&c||!n&&c||!o)return 1;if(!i&&!a&&!f&&t0;--u)if(r=e[u].dequeue()){i=i.concat(s(t,e,n,r,!0));break}}return i}(n.graph,n.buckets,n.zeroIdx);return r.flatten(r.map(c,(function(e){return t.outEdges(e.v,e.w)})),!0)};var a=r.constant(1);function s(t,e,n,i,o){var a=o?[]:void 0;return r.forEach(t.inEdges(i.v),(function(r){var i=t.edge(r),s=t.node(r.v);o&&a.push({v:r.v,w:r.w}),s.out-=i,u(e,n,s)})),r.forEach(t.outEdges(i.v),(function(r){var i=t.edge(r),o=r.w,a=t.node(o);a.in-=i,u(e,n,a)})),t.removeNode(i.v),a}function u(t,e,n){n.out?n.in?t[n.out-n.in+e].enqueue(n):t[t.length-1].enqueue(n):t[0].enqueue(n)}},function(t,e){function n(){var t={};t._next=t._prev=t,this._sentinel=t}function r(t){t._prev._next=t._next,t._next._prev=t._prev,delete t._next,delete t._prev}function i(t,e){if("_next"!==t&&"_prev"!==t)return e}t.exports=n,n.prototype.dequeue=function(){var t=this._sentinel,e=t._prev;if(e!==t)return r(e),e},n.prototype.enqueue=function(t){var e=this._sentinel;t._prev&&t._next&&r(t),t._next=e._next,e._next._prev=t,e._next=t,t._prev=e},n.prototype.toString=function(){for(var t=[],e=this._sentinel,n=e._prev;n!==e;)t.push(JSON.stringify(n,i)),n=n._prev;return"["+t.join(", ")+"]"}},function(t,e,n){"use strict";var r=n(4),i=n(10);t.exports={run:function(t){t.graph().dummyChains=[],r.forEach(t.edges(),(function(e){!function(t,e){var n,r,o,a=e.v,s=t.node(a).rank,u=e.w,c=t.node(u).rank,f=e.name,l=t.edge(e),h=l.labelRank;if(c===s+1)return;for(t.removeEdge(e),o=0,++s;su.lim&&(c=u,f=!0);var l=r.filter(e.edges(),(function(e){return f===b(t,t.node(e.v),c)&&f!==b(t,t.node(e.w),c)}));return r.minBy(l,(function(t){return o(e,t)}))}function y(t,e,n,i){var o=n.v,a=n.w;t.removeEdge(o,a),t.setEdge(i.v,i.w,{}),d(t),l(t,e),function(t,e){var n=r.find(t.nodes(),(function(t){return!e.node(t).parent})),i=s(t,n);i=i.slice(1),r.forEach(i,(function(n){var r=t.node(n).parent,i=e.edge(n,r),o=!1;i||(i=e.edge(r,n),o=!0),e.node(n).rank=e.node(r).rank+(o?i.minlen:-i.minlen)}))}(t,e)}function b(t,e,n){return n.low<=e.lim&&e.lim<=n.lim}t.exports=f,f.initLowLimValues=d,f.initCutValues=l,f.calcCutValue=h,f.leaveEdge=p,f.enterEdge=g,f.exchangeEdges=y},function(t,e,n){var r=n(4);t.exports=function(t){var e=function(t){var e={},n=0;return r.forEach(t.children(),(function i(o){var a=n;r.forEach(t.children(o),i);e[o]={low:a,lim:n++}})),e}(t);r.forEach(t.graph().dummyChains,(function(n){for(var r=t.node(n),i=r.edgeObj,o=function(t,e,n,r){var i,o,a=[],s=[],u=Math.min(e[n].low,e[r].low),c=Math.max(e[n].lim,e[r].lim);i=n;do{i=t.parent(i),a.push(i)}while(i&&(e[i].low>u||c>e[i].lim));o=i,i=r;for(;(i=t.parent(i))!==o;)s.push(i);return{path:a.concat(s.reverse()),lca:o}}(t,e,i.v,i.w),a=o.path,s=o.lca,u=0,c=a[u],f=!0;n!==i.w;){if(r=t.node(n),f){for(;(c=a[u])!==s&&t.node(c).maxRank=2),s=f.buildLayerMatrix(t);var y=o(t,s);y0;)e%2&&(n+=u[e+1]),u[e=e-1>>1]+=t.weight;c+=t.weight*n}))),c}t.exports=function(t,e){for(var n=0,r=1;r=t.barycenter)&&function(t,e){var n=0,r=0;t.weight&&(n+=t.barycenter*t.weight,r+=t.weight);e.weight&&(n+=e.barycenter*e.weight,r+=e.weight);t.vs=e.vs.concat(t.vs),t.barycenter=n/r,t.weight=r,t.i=Math.min(e.i,t.i),e.merged=!0}(t,e)}}function i(e){return function(n){n.in.push(e),0==--n.indegree&&t.push(n)}}for(;t.length;){var o=t.pop();e.push(o),r.forEach(o.in.reverse(),n(o)),r.forEach(o.out,i(o))}return r.map(r.filter(e,(function(t){return!t.merged})),(function(t){return r.pick(t,["vs","i","barycenter","weight"])}))}(r.filter(n,(function(t){return!t.indegree})))}},function(t,e,n){var r=n(4),i=n(10);function o(t,e,n){for(var i;e.length&&(i=r.last(e)).i<=n;)e.pop(),t.push(i.vs),n++;return n}t.exports=function(t,e){var n=i.partition(t,(function(t){return r.has(t,"barycenter")})),a=n.lhs,s=r.sortBy(n.rhs,(function(t){return-t.i})),u=[],c=0,f=0,l=0;a.sort((h=!!e,function(t,e){return t.barycentere.barycenter?1:h?e.i-t.i:t.i-e.i})),l=o(u,s,l),r.forEach(a,(function(t){l+=t.vs.length,u.push(t.vs),c+=t.barycenter*t.weight,f+=t.weight,l=o(u,s,l)}));var h;var d={vs:r.flatten(u,!0)};f&&(d.barycenter=c/f,d.weight=f);return d}},function(t,e,n){var r=n(4),i=n(19).Graph;t.exports=function(t,e,n){var o=function(t){var e;for(;t.hasNode(e=r.uniqueId("_root")););return e}(t),a=new i({compound:!0}).setGraph({root:o}).setDefaultNodeLabel((function(e){return t.node(e)}));return r.forEach(t.nodes(),(function(i){var s=t.node(i),u=t.parent(i);(s.rank===e||s.minRank<=e&&e<=s.maxRank)&&(a.setNode(i),a.setParent(i,u||o),r.forEach(t[n](i),(function(e){var n=e.v===i?e.w:e.v,o=a.edge(n,i),s=r.isUndefined(o)?0:o.weight;a.setEdge(n,i,{weight:t.edge(e).weight+s})})),r.has(s,"minRank")&&a.setNode(i,{borderLeft:s.borderLeft[e],borderRight:s.borderRight[e]}))})),a}},function(t,e,n){var r=n(4);t.exports=function(t,e,n){var i,o={};r.forEach(n,(function(n){for(var r,a,s=t.parent(n);s;){if((r=t.parent(s))?(a=o[r],o[r]=s):(a=i,i=s),a&&a!==s)return void e.setEdge(a,s);s=r}}))}},function(t,e,n){"use strict";var r=n(4),i=n(10),o=n(400).positionX;t.exports=function(t){(function(t){var e=i.buildLayerMatrix(t),n=t.graph().ranksep,o=0;r.forEach(e,(function(e){var i=r.max(r.map(e,(function(e){return t.node(e).height})));r.forEach(e,(function(e){t.node(e).y=o+i/2})),o+=i+n}))})(t=i.asNonCompoundGraph(t)),r.forEach(o(t),(function(e,n){t.node(n).x=e}))}},function(t,e,n){"use strict";var r=n(4),i=n(19).Graph,o=n(10);function a(t,e){var n={};return r.reduce(e,(function(e,i){var o=0,a=0,s=e.length,c=r.last(i);return r.forEach(i,(function(e,f){var l=function(t,e){if(t.node(e).dummy)return r.find(t.predecessors(e),(function(e){return t.node(e).dummy}))}(t,e),h=l?t.node(l).order:s;(l||e===c)&&(r.forEach(i.slice(a,f+1),(function(e){r.forEach(t.predecessors(e),(function(r){var i=t.node(r),a=i.order;!(as)&&u(n,e,c)}))}))}return r.reduce(e,(function(e,n){var o,a=-1,s=0;return r.forEach(n,(function(r,u){if("border"===t.node(r).dummy){var c=t.predecessors(r);c.length&&(o=t.node(c[0]).order,i(n,s,u,a,o),s=u,a=o)}i(n,s,n.length,o,e.length)})),n})),n}function u(t,e,n){if(e>n){var r=e;e=n,n=r}var i=t[e];i||(t[e]=i={}),i[n]=!0}function c(t,e,n){if(e>n){var i=e;e=n,n=i}return r.has(t[e],n)}function f(t,e,n,i){var o={},a={},s={};return r.forEach(e,(function(t){r.forEach(t,(function(t,e){o[t]=t,a[t]=t,s[t]=e}))})),r.forEach(e,(function(t){var e=-1;r.forEach(t,(function(t){var u=i(t);if(u.length)for(var f=((u=r.sortBy(u,(function(t){return s[t]}))).length-1)/2,l=Math.floor(f),h=Math.ceil(f);l<=h;++l){var d=u[l];a[t]===t&&e0}t.exports=function(t,e,r,i){var o,a,s,u,c,f,l,h,d,p,g,y,b;if(o=e.y-t.y,s=t.x-e.x,c=e.x*t.y-t.x*e.y,d=o*r.x+s*r.y+c,p=o*i.x+s*i.y+c,0!==d&&0!==p&&n(d,p))return;if(a=i.y-r.y,u=r.x-i.x,f=i.x*r.y-r.x*i.y,l=a*t.x+u*t.y+f,h=a*e.x+u*e.y+f,0!==l&&0!==h&&n(l,h))return;if(0===(g=o*u-a*s))return;return y=Math.abs(g/2),{x:(b=s*f-u*c)<0?(b-y)/g:(b+y)/g,y:(b=a*c-o*f)<0?(b-y)/g:(b+y)/g}}},function(t,e,n){var r=n(43),i=n(30),o=n(175).layout;t.exports=function(){var t=n(406),e=n(409),i=n(410),c=n(411),f=n(412),l=n(413),h=n(414),d=n(415),p=n(416),g=function(n,g){!function(t){t.nodes().forEach((function(e){var n=t.node(e);r.has(n,"label")||t.children(e).length||(n.label=e),r.has(n,"paddingX")&&r.defaults(n,{paddingLeft:n.paddingX,paddingRight:n.paddingX}),r.has(n,"paddingY")&&r.defaults(n,{paddingTop:n.paddingY,paddingBottom:n.paddingY}),r.has(n,"padding")&&r.defaults(n,{paddingLeft:n.padding,paddingRight:n.padding,paddingTop:n.padding,paddingBottom:n.padding}),r.defaults(n,a),r.each(["paddingLeft","paddingRight","paddingTop","paddingBottom"],(function(t){n[t]=Number(n[t])})),r.has(n,"width")&&(n._prevWidth=n.width),r.has(n,"height")&&(n._prevHeight=n.height)})),t.edges().forEach((function(e){var n=t.edge(e);r.has(n,"label")||(n.label=""),r.defaults(n,s)}))}(g);var y=u(n,"output"),b=u(y,"clusters"),m=u(y,"edgePaths"),v=i(u(y,"edgeLabels"),g),_=t(u(y,"nodes"),g,d);o(g),f(_,g),l(v,g),c(m,g,p);var w=e(b,g);h(w,g),function(t){r.each(t.nodes(),(function(e){var n=t.node(e);r.has(n,"_prevWidth")?n.width=n._prevWidth:delete n.width,r.has(n,"_prevHeight")?n.height=n._prevHeight:delete n.height,delete n._prevWidth,delete n._prevHeight}))}(g)};return g.createNodes=function(e){return arguments.length?(t=e,g):t},g.createClusters=function(t){return arguments.length?(e=t,g):e},g.createEdgeLabels=function(t){return arguments.length?(i=t,g):i},g.createEdgePaths=function(t){return arguments.length?(c=t,g):c},g.shapes=function(t){return arguments.length?(d=t,g):d},g.arrows=function(t){return arguments.length?(p=t,g):p},g};var a={paddingLeft:10,paddingRight:10,paddingTop:10,paddingBottom:10,rx:0,ry:0,shape:"rect"},s={arrowhead:"normal",curve:i.curveLinear};function u(t,e){var n=t.select("g."+e);return n.empty()&&(n=t.append("g").attr("class",e)),n}},function(t,e,n){"use strict";var r=n(43),i=n(110),o=n(14),a=n(30);t.exports=function(t,e,n){var s,u=e.nodes().filter((function(t){return!o.isSubgraph(e,t)})),c=t.selectAll("g.node").data(u,(function(t){return t})).classed("update",!0);c.exit().remove(),c.enter().append("g").attr("class","node").style("opacity",0),(c=t.selectAll("g.node")).each((function(t){var s=e.node(t),u=a.select(this);o.applyClass(u,s.class,(u.classed("update")?"update ":"")+"node"),u.select("g.label").remove();var c=u.append("g").attr("class","label"),f=i(c,s),l=n[s.shape],h=r.pick(f.node().getBBox(),"width","height");s.elem=this,s.id&&u.attr("id",s.id),s.labelId&&c.attr("id",s.labelId),r.has(s,"width")&&(h.width=s.width),r.has(s,"height")&&(h.height=s.height),h.width+=s.paddingLeft+s.paddingRight,h.height+=s.paddingTop+s.paddingBottom,c.attr("transform","translate("+(s.paddingLeft-s.paddingRight)/2+","+(s.paddingTop-s.paddingBottom)/2+")");var d=a.select(this);d.select(".label-container").remove();var p=l(d,h,s).classed("label-container",!0);o.applyStyle(p,s.style);var g=p.node().getBBox();s.width=g.width,s.height=g.height})),s=c.exit?c.exit():c.selectAll(null);return o.applyTransition(s,e).style("opacity",0).remove(),c}},function(t,e,n){var r=n(14);t.exports=function(t,e){for(var n=t.append("text"),i=function(t){for(var e,n="",r=!1,i=0;i0?a-4:a;for(n=0;n>16&255,u[f++]=e>>8&255,u[f++]=255&e;2===s&&(e=i[t.charCodeAt(n)]<<2|i[t.charCodeAt(n+1)]>>4,u[f++]=255&e);1===s&&(e=i[t.charCodeAt(n)]<<10|i[t.charCodeAt(n+1)]<<4|i[t.charCodeAt(n+2)]>>2,u[f++]=e>>8&255,u[f++]=255&e);return u},e.fromByteArray=function(t){for(var e,n=t.length,i=n%3,o=[],a=0,s=n-i;as?s:a+16383));1===i?(e=t[n-1],o.push(r[e>>2]+r[e<<4&63]+"==")):2===i&&(e=(t[n-2]<<8)+t[n-1],o.push(r[e>>10]+r[e>>4&63]+r[e<<2&63]+"="));return o.join("")};for(var r=[],i=[],o="undefined"!=typeof Uint8Array?Uint8Array:Array,a="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",s=0,u=a.length;s0)throw new Error("Invalid string. Length must be a multiple of 4");var n=t.indexOf("=");return-1===n&&(n=e),[n,n===e?0:4-n%4]}function f(t,e,n){for(var i,o,a=[],s=e;s>18&63]+r[o>>12&63]+r[o>>6&63]+r[63&o]);return a.join("")}i["-".charCodeAt(0)]=62,i["_".charCodeAt(0)]=63},function(t,e){e.read=function(t,e,n,r,i){var o,a,s=8*i-r-1,u=(1<>1,f=-7,l=n?i-1:0,h=n?-1:1,d=t[e+l];for(l+=h,o=d&(1<<-f)-1,d>>=-f,f+=s;f>0;o=256*o+t[e+l],l+=h,f-=8);for(a=o&(1<<-f)-1,o>>=-f,f+=r;f>0;a=256*a+t[e+l],l+=h,f-=8);if(0===o)o=1-c;else{if(o===u)return a?NaN:1/0*(d?-1:1);a+=Math.pow(2,r),o-=c}return(d?-1:1)*a*Math.pow(2,o-r)},e.write=function(t,e,n,r,i,o){var a,s,u,c=8*o-i-1,f=(1<>1,h=23===i?Math.pow(2,-24)-Math.pow(2,-77):0,d=r?0:o-1,p=r?1:-1,g=e<0||0===e&&1/e<0?1:0;for(e=Math.abs(e),isNaN(e)||e===1/0?(s=isNaN(e)?1:0,a=f):(a=Math.floor(Math.log(e)/Math.LN2),e*(u=Math.pow(2,-a))<1&&(a--,u*=2),(e+=a+l>=1?h/u:h*Math.pow(2,1-l))*u>=2&&(a++,u/=2),a+l>=f?(s=0,a=f):a+l>=1?(s=(e*u-1)*Math.pow(2,i),a+=l):(s=e*Math.pow(2,l-1)*Math.pow(2,i),a=0));i>=8;t[n+d]=255&s,d+=p,s/=256,i-=8);for(a=a<0;t[n+d]=255&a,d+=p,a/=256,c-=8);t[n+d-p]|=128*g}},function(t,e){},function(t,e,n){"use strict";var r=n(115).Buffer,i=n(423);t.exports=function(){function t(){!function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}(this,t),this.head=null,this.tail=null,this.length=0}return t.prototype.push=function(t){var e={data:t,next:null};this.length>0?this.tail.next=e:this.head=e,this.tail=e,++this.length},t.prototype.unshift=function(t){var e={data:t,next:this.head};0===this.length&&(this.tail=e),this.head=e,++this.length},t.prototype.shift=function(){if(0!==this.length){var t=this.head.data;return 1===this.length?this.head=this.tail=null:this.head=this.head.next,--this.length,t}},t.prototype.clear=function(){this.head=this.tail=null,this.length=0},t.prototype.join=function(t){if(0===this.length)return"";for(var e=this.head,n=""+e.data;e=e.next;)n+=t+e.data;return n},t.prototype.concat=function(t){if(0===this.length)return r.alloc(0);if(1===this.length)return this.head.data;for(var e,n,i,o=r.allocUnsafe(t>>>0),a=this.head,s=0;a;)e=a.data,n=o,i=s,e.copy(n,i),s+=a.data.length,a=a.next;return o},t}(),i&&i.inspect&&i.inspect.custom&&(t.exports.prototype[i.inspect.custom]=function(){var t=i.inspect({length:this.length});return this.constructor.name+" "+t})},function(t,e){},function(t,e,n){(function(t){var r=void 0!==t&&t||"undefined"!=typeof self&&self||window,i=Function.prototype.apply;function o(t,e){this._id=t,this._clearFn=e}e.setTimeout=function(){return new o(i.call(setTimeout,r,arguments),clearTimeout)},e.setInterval=function(){return new o(i.call(setInterval,r,arguments),clearInterval)},e.clearTimeout=e.clearInterval=function(t){t&&t.close()},o.prototype.unref=o.prototype.ref=function(){},o.prototype.close=function(){this._clearFn.call(r,this._id)},e.enroll=function(t,e){clearTimeout(t._idleTimeoutId),t._idleTimeout=e},e.unenroll=function(t){clearTimeout(t._idleTimeoutId),t._idleTimeout=-1},e._unrefActive=e.active=function(t){clearTimeout(t._idleTimeoutId);var e=t._idleTimeout;e>=0&&(t._idleTimeoutId=setTimeout((function(){t._onTimeout&&t._onTimeout()}),e))},n(425),e.setImmediate="undefined"!=typeof self&&self.setImmediate||void 0!==t&&t.setImmediate||this&&this.setImmediate,e.clearImmediate="undefined"!=typeof self&&self.clearImmediate||void 0!==t&&t.clearImmediate||this&&this.clearImmediate}).call(this,n(11))},function(t,e,n){(function(t,e){!function(t,n){"use strict";if(!t.setImmediate){var r,i,o,a,s,u=1,c={},f=!1,l=t.document,h=Object.getPrototypeOf&&Object.getPrototypeOf(t);h=h&&h.setTimeout?h:t,"[object process]"==={}.toString.call(t.process)?r=function(t){e.nextTick((function(){p(t)}))}:!function(){if(t.postMessage&&!t.importScripts){var e=!0,n=t.onmessage;return t.onmessage=function(){e=!1},t.postMessage("","*"),t.onmessage=n,e}}()?t.MessageChannel?((o=new MessageChannel).port1.onmessage=function(t){p(t.data)},r=function(t){o.port2.postMessage(t)}):l&&"onreadystatechange"in l.createElement("script")?(i=l.documentElement,r=function(t){var e=l.createElement("script");e.onreadystatechange=function(){p(t),e.onreadystatechange=null,i.removeChild(e),e=null},i.appendChild(e)}):r=function(t){setTimeout(p,0,t)}:(a="setImmediate$"+Math.random()+"$",s=function(e){e.source===t&&"string"==typeof e.data&&0===e.data.indexOf(a)&&p(+e.data.slice(a.length))},t.addEventListener?t.addEventListener("message",s,!1):t.attachEvent("onmessage",s),r=function(e){t.postMessage(a+e,"*")}),h.setImmediate=function(t){"function"!=typeof t&&(t=new Function(""+t));for(var e=new Array(arguments.length-1),n=0;n>>2}function f(t,e,n,r){return 0===t?e&n|~e&r:2===t?e&n|e&r|n&r:e^n^r}r(u,i),u.prototype.init=function(){return this._a=1732584193,this._b=4023233417,this._c=2562383102,this._d=271733878,this._e=3285377520,this},u.prototype._update=function(t){for(var e,n=this._w,r=0|this._a,i=0|this._b,o=0|this._c,s=0|this._d,u=0|this._e,l=0;l<16;++l)n[l]=t.readInt32BE(4*l);for(;l<80;++l)n[l]=n[l-3]^n[l-8]^n[l-14]^n[l-16];for(var h=0;h<80;++h){var d=~~(h/20),p=0|((e=r)<<5|e>>>27)+f(d,i,o,s)+u+n[h]+a[d];u=s,s=o,o=c(i),i=r,r=p}this._a=r+this._a|0,this._b=i+this._b|0,this._c=o+this._c|0,this._d=s+this._d|0,this._e=u+this._e|0},u.prototype._hash=function(){var t=o.allocUnsafe(20);return t.writeInt32BE(0|this._a,0),t.writeInt32BE(0|this._b,4),t.writeInt32BE(0|this._c,8),t.writeInt32BE(0|this._d,12),t.writeInt32BE(0|this._e,16),t},t.exports=u},function(t,e,n){var r=n(2),i=n(45),o=n(3).Buffer,a=[1518500249,1859775393,-1894007588,-899497514],s=new Array(80);function u(){this.init(),this._w=s,i.call(this,64,56)}function c(t){return t<<5|t>>>27}function f(t){return t<<30|t>>>2}function l(t,e,n,r){return 0===t?e&n|~e&r:2===t?e&n|e&r|n&r:e^n^r}r(u,i),u.prototype.init=function(){return this._a=1732584193,this._b=4023233417,this._c=2562383102,this._d=271733878,this._e=3285377520,this},u.prototype._update=function(t){for(var e,n=this._w,r=0|this._a,i=0|this._b,o=0|this._c,s=0|this._d,u=0|this._e,h=0;h<16;++h)n[h]=t.readInt32BE(4*h);for(;h<80;++h)n[h]=(e=n[h-3]^n[h-8]^n[h-14]^n[h-16])<<1|e>>>31;for(var d=0;d<80;++d){var p=~~(d/20),g=c(r)+l(p,i,o,s)+u+n[d]+a[p]|0;u=s,s=o,o=f(i),i=r,r=g}this._a=r+this._a|0,this._b=i+this._b|0,this._c=o+this._c|0,this._d=s+this._d|0,this._e=u+this._e|0},u.prototype._hash=function(){var t=o.allocUnsafe(20);return t.writeInt32BE(0|this._a,0),t.writeInt32BE(0|this._b,4),t.writeInt32BE(0|this._c,8),t.writeInt32BE(0|this._d,12),t.writeInt32BE(0|this._e,16),t},t.exports=u},function(t,e,n){var r=n(2),i=n(197),o=n(45),a=n(3).Buffer,s=new Array(64);function u(){this.init(),this._w=s,o.call(this,64,56)}r(u,i),u.prototype.init=function(){return this._a=3238371032,this._b=914150663,this._c=812702999,this._d=4144912697,this._e=4290775857,this._f=1750603025,this._g=1694076839,this._h=3204075428,this},u.prototype._hash=function(){var t=a.allocUnsafe(28);return t.writeInt32BE(this._a,0),t.writeInt32BE(this._b,4),t.writeInt32BE(this._c,8),t.writeInt32BE(this._d,12),t.writeInt32BE(this._e,16),t.writeInt32BE(this._f,20),t.writeInt32BE(this._g,24),t},t.exports=u},function(t,e,n){var r=n(2),i=n(198),o=n(45),a=n(3).Buffer,s=new Array(160);function u(){this.init(),this._w=s,o.call(this,128,112)}r(u,i),u.prototype.init=function(){return this._ah=3418070365,this._bh=1654270250,this._ch=2438529370,this._dh=355462360,this._eh=1731405415,this._fh=2394180231,this._gh=3675008525,this._hh=1203062813,this._al=3238371032,this._bl=914150663,this._cl=812702999,this._dl=4144912697,this._el=4290775857,this._fl=1750603025,this._gl=1694076839,this._hl=3204075428,this},u.prototype._hash=function(){var t=a.allocUnsafe(48);function e(e,n,r){t.writeInt32BE(e,r),t.writeInt32BE(n,r+4)}return e(this._ah,this._al,0),e(this._bh,this._bl,8),e(this._ch,this._cl,16),e(this._dh,this._dl,24),e(this._eh,this._el,32),e(this._fh,this._fl,40),t},t.exports=u},function(t,e,n){"use strict";var r=n(2),i=n(3).Buffer,o=n(31),a=i.alloc(128),s=64;function u(t,e){o.call(this,"digest"),"string"==typeof e&&(e=i.from(e)),this._alg=t,this._key=e,e.length>s?e=t(e):e.length>>0},e.writeUInt32BE=function(t,e,n){t[0+n]=e>>>24,t[1+n]=e>>>16&255,t[2+n]=e>>>8&255,t[3+n]=255&e},e.ip=function(t,e,n,r){for(var i=0,o=0,a=6;a>=0;a-=2){for(var s=0;s<=24;s+=8)i<<=1,i|=e>>>s+a&1;for(s=0;s<=24;s+=8)i<<=1,i|=t>>>s+a&1}for(a=6;a>=0;a-=2){for(s=1;s<=25;s+=8)o<<=1,o|=e>>>s+a&1;for(s=1;s<=25;s+=8)o<<=1,o|=t>>>s+a&1}n[r+0]=i>>>0,n[r+1]=o>>>0},e.rip=function(t,e,n,r){for(var i=0,o=0,a=0;a<4;a++)for(var s=24;s>=0;s-=8)i<<=1,i|=e>>>s+a&1,i<<=1,i|=t>>>s+a&1;for(a=4;a<8;a++)for(s=24;s>=0;s-=8)o<<=1,o|=e>>>s+a&1,o<<=1,o|=t>>>s+a&1;n[r+0]=i>>>0,n[r+1]=o>>>0},e.pc1=function(t,e,n,r){for(var i=0,o=0,a=7;a>=5;a--){for(var s=0;s<=24;s+=8)i<<=1,i|=e>>s+a&1;for(s=0;s<=24;s+=8)i<<=1,i|=t>>s+a&1}for(s=0;s<=24;s+=8)i<<=1,i|=e>>s+a&1;for(a=1;a<=3;a++){for(s=0;s<=24;s+=8)o<<=1,o|=e>>s+a&1;for(s=0;s<=24;s+=8)o<<=1,o|=t>>s+a&1}for(s=0;s<=24;s+=8)o<<=1,o|=t>>s+a&1;n[r+0]=i>>>0,n[r+1]=o>>>0},e.r28shl=function(t,e){return t<>>28-e};var r=[14,11,17,4,27,23,25,0,13,22,7,18,5,9,16,24,2,20,12,21,1,8,15,26,15,4,25,19,9,1,26,16,5,11,23,8,12,7,17,0,22,3,10,14,6,20,27,24];e.pc2=function(t,e,n,i){for(var o=0,a=0,s=r.length>>>1,u=0;u>>r[u]&1;for(u=s;u>>r[u]&1;n[i+0]=o>>>0,n[i+1]=a>>>0},e.expand=function(t,e,n){var r=0,i=0;r=(1&t)<<5|t>>>27;for(var o=23;o>=15;o-=4)r<<=6,r|=t>>>o&63;for(o=11;o>=3;o-=4)i|=t>>>o&63,i<<=6;i|=(31&t)<<1|t>>>31,e[n+0]=r>>>0,e[n+1]=i>>>0};var i=[14,0,4,15,13,7,1,4,2,14,15,2,11,13,8,1,3,10,10,6,6,12,12,11,5,9,9,5,0,3,7,8,4,15,1,12,14,8,8,2,13,4,6,9,2,1,11,7,15,5,12,11,9,3,7,14,3,10,10,0,5,6,0,13,15,3,1,13,8,4,14,7,6,15,11,2,3,8,4,14,9,12,7,0,2,1,13,10,12,6,0,9,5,11,10,5,0,13,14,8,7,10,11,1,10,3,4,15,13,4,1,2,5,11,8,6,12,7,6,12,9,0,3,5,2,14,15,9,10,13,0,7,9,0,14,9,6,3,3,4,15,6,5,10,1,2,13,8,12,5,7,14,11,12,4,11,2,15,8,1,13,1,6,10,4,13,9,0,8,6,15,9,3,8,0,7,11,4,1,15,2,14,12,3,5,11,10,5,14,2,7,12,7,13,13,8,14,11,3,5,0,6,6,15,9,0,10,3,1,4,2,7,8,2,5,12,11,1,12,10,4,14,15,9,10,3,6,15,9,0,0,6,12,10,11,1,7,13,13,8,15,9,1,4,3,5,14,11,5,12,2,7,8,2,4,14,2,14,12,11,4,2,1,12,7,4,10,7,11,13,6,1,8,5,5,0,3,15,15,10,13,3,0,9,14,8,9,6,4,11,2,8,1,12,11,7,10,1,13,14,7,2,8,13,15,6,9,15,12,0,5,9,6,10,3,4,0,5,14,3,12,10,1,15,10,4,15,2,9,7,2,12,6,9,8,5,0,6,13,1,3,13,4,14,14,0,7,11,5,3,11,8,9,4,14,3,15,2,5,12,2,9,8,5,12,15,3,10,7,11,0,14,4,1,10,7,1,6,13,0,11,8,6,13,4,13,11,0,2,11,14,7,15,4,0,9,8,1,13,10,3,14,12,3,9,5,7,12,5,2,10,15,6,8,1,6,1,6,4,11,11,13,13,8,12,1,3,4,7,10,14,7,10,9,15,5,6,0,8,15,0,14,5,2,9,3,2,12,13,1,2,15,8,13,4,8,6,10,15,3,11,7,1,4,10,12,9,5,3,6,14,11,5,0,0,14,12,9,7,2,7,2,11,1,4,14,1,7,9,4,12,10,14,8,2,13,0,15,6,12,10,9,13,0,15,3,3,5,5,6,8,11];e.substitute=function(t,e){for(var n=0,r=0;r<4;r++){n<<=4,n|=i[64*r+(t>>>18-6*r&63)]}for(r=0;r<4;r++){n<<=4,n|=i[256+64*r+(e>>>18-6*r&63)]}return n>>>0};var o=[16,25,12,11,3,20,4,15,31,17,9,6,27,14,1,22,30,24,8,18,0,5,29,23,13,19,2,26,10,21,28,7];e.permute=function(t){for(var e=0,n=0;n>>o[n]&1;return e>>>0},e.padSplit=function(t,e,n){for(var r=t.toString(2);r.length0;r--)e+=this._buffer(t,e),n+=this._flushBuffer(i,n);return e+=this._buffer(t,e),i},i.prototype.final=function(t){var e,n;return t&&(e=this.update(t)),n="encrypt"===this.type?this._finalEncrypt():this._finalDecrypt(),e?e.concat(n):n},i.prototype._pad=function(t,e){if(0===e)return!1;for(;e>>1];n=a.r28shl(n,s),i=a.r28shl(i,s),a.pc2(n,i,t.keys,o)}},c.prototype._update=function(t,e,n,r){var i=this._desState,o=a.readUInt32BE(t,e),s=a.readUInt32BE(t,e+4);a.ip(o,s,i.tmp,0),o=i.tmp[0],s=i.tmp[1],"encrypt"===this.type?this._encrypt(i,o,s,i.tmp,0):this._decrypt(i,o,s,i.tmp,0),o=i.tmp[0],s=i.tmp[1],a.writeUInt32BE(n,o,r),a.writeUInt32BE(n,s,r+4)},c.prototype._pad=function(t,e){for(var n=t.length-e,r=e;r>>0,o=h}a.rip(s,o,r,i)},c.prototype._decrypt=function(t,e,n,r,i){for(var o=n,s=e,u=t.keys.length-2;u>=0;u-=2){var c=t.keys[u],f=t.keys[u+1];a.expand(o,t.tmp,0),c^=t.tmp[0],f^=t.tmp[1];var l=a.substitute(c,f),h=o;o=(s^a.permute(l))>>>0,s=h}a.rip(o,s,r,i)}},function(t,e,n){"use strict";var r=n(15),i=n(2),o={};function a(t){r.equal(t.length,8,"Invalid IV length"),this.iv=new Array(8);for(var e=0;e15){var t=this.cache.slice(0,16);return this.cache=this.cache.slice(16),t}return null},h.prototype.flush=function(){for(var t=16-this.cache.length,e=o.allocUnsafe(t),n=-1;++n>a%8,t._prev=o(t._prev,n?r:i);return s}function o(t,e){var n=t.length,i=-1,o=r.allocUnsafe(t.length);for(t=r.concat([t,r.from([e])]);++i>7;return o}e.encrypt=function(t,e,n){for(var o=e.length,a=r.allocUnsafe(o),s=-1;++s>>0,0),e.writeUInt32BE(t[1]>>>0,4),e.writeUInt32BE(t[2]>>>0,8),e.writeUInt32BE(t[3]>>>0,12),e}function a(t){this.h=t,this.state=r.alloc(16,0),this.cache=r.allocUnsafe(0)}a.prototype.ghash=function(t){for(var e=-1;++e0;e--)r[e]=r[e]>>>1|(1&r[e-1])<<31;r[0]=r[0]>>>1,n&&(r[0]=r[0]^225<<24)}this.state=o(i)},a.prototype.update=function(t){var e;for(this.cache=r.concat([this.cache,t]);this.cache.length>=16;)e=this.cache.slice(0,16),this.cache=this.cache.slice(16),this.ghash(e)},a.prototype.final=function(t,e){return this.cache.length&&this.ghash(r.concat([this.cache,i],16)),this.ghash(o([0,t,0,e])),this.state},t.exports=a},function(t,e,n){var r=n(209),i=n(3).Buffer,o=n(122),a=n(210),s=n(31),u=n(79),c=n(80);function f(t,e,n){s.call(this),this._cache=new l,this._last=void 0,this._cipher=new u.AES(e),this._prev=i.from(n),this._mode=t,this._autopadding=!0}function l(){this.cache=i.allocUnsafe(0)}function h(t,e,n){var s=o[t.toLowerCase()];if(!s)throw new TypeError("invalid suite type");if("string"==typeof n&&(n=i.from(n)),"GCM"!==s.mode&&n.length!==s.iv)throw new TypeError("invalid iv length "+n.length);if("string"==typeof e&&(e=i.from(e)),e.length!==s.key/8)throw new TypeError("invalid key length "+e.length);return"stream"===s.type?new a(s.module,e,n,!0):"auth"===s.type?new r(s.module,e,n,!0):new f(s.module,e,n)}n(2)(f,s),f.prototype._update=function(t){var e,n;this._cache.add(t);for(var r=[];e=this._cache.get(this._autopadding);)n=this._mode.decrypt(this,e),r.push(n);return i.concat(r)},f.prototype._final=function(){var t=this._cache.flush();if(this._autopadding)return function(t){var e=t[15];if(e<1||e>16)throw new Error("unable to decrypt data");var n=-1;for(;++n16)return e=this.cache.slice(0,16),this.cache=this.cache.slice(16),e}else if(this.cache.length>=16)return e=this.cache.slice(0,16),this.cache=this.cache.slice(16),e;return null},l.prototype.flush=function(){if(this.cache.length)return this.cache},e.createDecipher=function(t,e){var n=o[t.toLowerCase()];if(!n)throw new TypeError("invalid suite type");var r=c(e,!1,n.key,n.iv);return h(t,r.key,r.iv)},e.createDecipheriv=h},function(t,e){e["des-ecb"]={key:8,iv:0},e["des-cbc"]=e.des={key:8,iv:8},e["des-ede3-cbc"]=e.des3={key:24,iv:8},e["des-ede3"]={key:24,iv:0},e["des-ede-cbc"]={key:16,iv:8},e["des-ede"]={key:16,iv:0}},function(t,e,n){(function(t){var r=n(211),i=n(459),o=n(460);var a={binary:!0,hex:!0,base64:!0};e.DiffieHellmanGroup=e.createDiffieHellmanGroup=e.getDiffieHellman=function(e){var n=new t(i[e].prime,"hex"),r=new t(i[e].gen,"hex");return new o(n,r)},e.createDiffieHellman=e.DiffieHellman=function e(n,i,s,u){return t.isBuffer(i)||void 0===a[i]?e(n,"binary",i,s):(i=i||"binary",u=u||"binary",s=s||new t([2]),t.isBuffer(s)||(s=new t(s,u)),"number"==typeof n?new o(r(n,s),s,!0):(t.isBuffer(n)||(n=new t(n,i)),new o(n,s,!0)))}}).call(this,n(8).Buffer)},function(t,e){},function(t,e){},function(t){t.exports=JSON.parse('{"modp1":{"gen":"02","prime":"ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a63a3620ffffffffffffffff"},"modp2":{"gen":"02","prime":"ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece65381ffffffffffffffff"},"modp5":{"gen":"02","prime":"ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf0598da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb9ed529077096966d670c354e4abc9804f1746c08ca237327ffffffffffffffff"},"modp14":{"gen":"02","prime":"ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf0598da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb9ed529077096966d670c354e4abc9804f1746c08ca18217c32905e462e36ce3be39e772c180e86039b2783a2ec07a28fb5c55df06f4c52c9de2bcbf6955817183995497cea956ae515d2261898fa051015728e5a8aacaa68ffffffffffffffff"},"modp15":{"gen":"02","prime":"ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf0598da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb9ed529077096966d670c354e4abc9804f1746c08ca18217c32905e462e36ce3be39e772c180e86039b2783a2ec07a28fb5c55df06f4c52c9de2bcbf6955817183995497cea956ae515d2261898fa051015728e5a8aaac42dad33170d04507a33a85521abdf1cba64ecfb850458dbef0a8aea71575d060c7db3970f85a6e1e4c7abf5ae8cdb0933d71e8c94e04a25619dcee3d2261ad2ee6bf12ffa06d98a0864d87602733ec86a64521f2b18177b200cbbe117577a615d6c770988c0bad946e208e24fa074e5ab3143db5bfce0fd108e4b82d120a93ad2caffffffffffffffff"},"modp16":{"gen":"02","prime":"ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf0598da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb9ed529077096966d670c354e4abc9804f1746c08ca18217c32905e462e36ce3be39e772c180e86039b2783a2ec07a28fb5c55df06f4c52c9de2bcbf6955817183995497cea956ae515d2261898fa051015728e5a8aaac42dad33170d04507a33a85521abdf1cba64ecfb850458dbef0a8aea71575d060c7db3970f85a6e1e4c7abf5ae8cdb0933d71e8c94e04a25619dcee3d2261ad2ee6bf12ffa06d98a0864d87602733ec86a64521f2b18177b200cbbe117577a615d6c770988c0bad946e208e24fa074e5ab3143db5bfce0fd108e4b82d120a92108011a723c12a787e6d788719a10bdba5b2699c327186af4e23c1a946834b6150bda2583e9ca2ad44ce8dbbbc2db04de8ef92e8efc141fbecaa6287c59474e6bc05d99b2964fa090c3a2233ba186515be7ed1f612970cee2d7afb81bdd762170481cd0069127d5b05aa993b4ea988d8fddc186ffb7dc90a6c08f4df435c934063199ffffffffffffffff"},"modp17":{"gen":"02","prime":"ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf0598da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb9ed529077096966d670c354e4abc9804f1746c08ca18217c32905e462e36ce3be39e772c180e86039b2783a2ec07a28fb5c55df06f4c52c9de2bcbf6955817183995497cea956ae515d2261898fa051015728e5a8aaac42dad33170d04507a33a85521abdf1cba64ecfb850458dbef0a8aea71575d060c7db3970f85a6e1e4c7abf5ae8cdb0933d71e8c94e04a25619dcee3d2261ad2ee6bf12ffa06d98a0864d87602733ec86a64521f2b18177b200cbbe117577a615d6c770988c0bad946e208e24fa074e5ab3143db5bfce0fd108e4b82d120a92108011a723c12a787e6d788719a10bdba5b2699c327186af4e23c1a946834b6150bda2583e9ca2ad44ce8dbbbc2db04de8ef92e8efc141fbecaa6287c59474e6bc05d99b2964fa090c3a2233ba186515be7ed1f612970cee2d7afb81bdd762170481cd0069127d5b05aa993b4ea988d8fddc186ffb7dc90a6c08f4df435c93402849236c3fab4d27c7026c1d4dcb2602646dec9751e763dba37bdf8ff9406ad9e530ee5db382f413001aeb06a53ed9027d831179727b0865a8918da3edbebcf9b14ed44ce6cbaced4bb1bdb7f1447e6cc254b332051512bd7af426fb8f401378cd2bf5983ca01c64b92ecf032ea15d1721d03f482d7ce6e74fef6d55e702f46980c82b5a84031900b1c9e59e7c97fbec7e8f323a97a7e36cc88be0f1d45b7ff585ac54bd407b22b4154aacc8f6d7ebf48e1d814cc5ed20f8037e0a79715eef29be32806a1d58bb7c5da76f550aa3d8a1fbff0eb19ccb1a313d55cda56c9ec2ef29632387fe8d76e3c0468043e8f663f4860ee12bf2d5b0b7474d6e694f91e6dcc4024ffffffffffffffff"},"modp18":{"gen":"02","prime":"ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf0598da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb9ed529077096966d670c354e4abc9804f1746c08ca18217c32905e462e36ce3be39e772c180e86039b2783a2ec07a28fb5c55df06f4c52c9de2bcbf6955817183995497cea956ae515d2261898fa051015728e5a8aaac42dad33170d04507a33a85521abdf1cba64ecfb850458dbef0a8aea71575d060c7db3970f85a6e1e4c7abf5ae8cdb0933d71e8c94e04a25619dcee3d2261ad2ee6bf12ffa06d98a0864d87602733ec86a64521f2b18177b200cbbe117577a615d6c770988c0bad946e208e24fa074e5ab3143db5bfce0fd108e4b82d120a92108011a723c12a787e6d788719a10bdba5b2699c327186af4e23c1a946834b6150bda2583e9ca2ad44ce8dbbbc2db04de8ef92e8efc141fbecaa6287c59474e6bc05d99b2964fa090c3a2233ba186515be7ed1f612970cee2d7afb81bdd762170481cd0069127d5b05aa993b4ea988d8fddc186ffb7dc90a6c08f4df435c93402849236c3fab4d27c7026c1d4dcb2602646dec9751e763dba37bdf8ff9406ad9e530ee5db382f413001aeb06a53ed9027d831179727b0865a8918da3edbebcf9b14ed44ce6cbaced4bb1bdb7f1447e6cc254b332051512bd7af426fb8f401378cd2bf5983ca01c64b92ecf032ea15d1721d03f482d7ce6e74fef6d55e702f46980c82b5a84031900b1c9e59e7c97fbec7e8f323a97a7e36cc88be0f1d45b7ff585ac54bd407b22b4154aacc8f6d7ebf48e1d814cc5ed20f8037e0a79715eef29be32806a1d58bb7c5da76f550aa3d8a1fbff0eb19ccb1a313d55cda56c9ec2ef29632387fe8d76e3c0468043e8f663f4860ee12bf2d5b0b7474d6e694f91e6dbe115974a3926f12fee5e438777cb6a932df8cd8bec4d073b931ba3bc832b68d9dd300741fa7bf8afc47ed2576f6936ba424663aab639c5ae4f5683423b4742bf1c978238f16cbe39d652de3fdb8befc848ad922222e04a4037c0713eb57a81a23f0c73473fc646cea306b4bcbc8862f8385ddfa9d4b7fa2c087e879683303ed5bdd3a062b3cf5b3a278a66d2a13f83f44f82ddf310ee074ab6a364597e899a0255dc164f31cc50846851df9ab48195ded7ea1b1d510bd7ee74d73faf36bc31ecfa268359046f4eb879f924009438b481c6cd7889a002ed5ee382bc9190da6fc026e479558e4475677e9aa9e3050e2765694dfc81f56e880b96e7160c980dd98edd3dfffffffffffffffff"}}')},function(t,e,n){(function(e){var r=n(5),i=new(n(212)),o=new r(24),a=new r(11),s=new r(10),u=new r(3),c=new r(7),f=n(211),l=n(44);function h(t,n){return n=n||"utf8",e.isBuffer(t)||(t=new e(t,n)),this._pub=new r(t),this}function d(t,n){return n=n||"utf8",e.isBuffer(t)||(t=new e(t,n)),this._priv=new r(t),this}t.exports=g;var p={};function g(t,e,n){this.setGenerator(e),this.__prime=new r(t),this._prime=r.mont(this.__prime),this._primeLen=t.length,this._pub=void 0,this._priv=void 0,this._primeCode=void 0,n?(this.setPublicKey=h,this.setPrivateKey=d):this._primeCode=8}function y(t,n){var r=new e(t.toArray());return n?r.toString(n):r}Object.defineProperty(g.prototype,"verifyError",{enumerable:!0,get:function(){return"number"!=typeof this._primeCode&&(this._primeCode=function(t,e){var n=e.toString("hex"),r=[n,t.toString(16)].join("_");if(r in p)return p[r];var l,h=0;if(t.isEven()||!f.simpleSieve||!f.fermatTest(t)||!i.test(t))return h+=1,h+="02"===n||"05"===n?8:4,p[r]=h,h;switch(i.test(t.shrn(1))||(h+=2),n){case"02":t.mod(o).cmp(a)&&(h+=8);break;case"05":(l=t.mod(s)).cmp(u)&&l.cmp(c)&&(h+=8);break;default:h+=4}return p[r]=h,h}(this.__prime,this.__gen)),this._primeCode}}),g.prototype.generateKeys=function(){return this._priv||(this._priv=new r(l(this._primeLen))),this._pub=this._gen.toRed(this._prime).redPow(this._priv).fromRed(),this.getPublicKey()},g.prototype.computeSecret=function(t){var n=(t=(t=new r(t)).toRed(this._prime)).redPow(this._priv).fromRed(),i=new e(n.toArray()),o=this.getPrime();if(i.length0&&n.ishrn(r),n}function l(t,n,i){var o,a;do{for(o=new e(0);8*o.length","license":"MIT","bugs":{"url":"https://github.com/indutny/elliptic/issues"},"homepage":"https://github.com/indutny/elliptic","devDependencies":{"brfs":"^1.4.3","coveralls":"^3.0.4","grunt":"^1.0.4","grunt-browserify":"^5.0.0","grunt-cli":"^1.2.0","grunt-contrib-connect":"^1.0.0","grunt-contrib-copy":"^1.0.0","grunt-contrib-uglify":"^1.0.1","grunt-mocha-istanbul":"^3.0.1","grunt-saucelabs":"^9.0.1","istanbul":"^0.4.2","jscs":"^3.0.7","jshint":"^2.6.0","mocha":"^6.1.4"},"dependencies":{"bn.js":"^4.4.0","brorand":"^1.0.1","hash.js":"^1.0.0","hmac-drbg":"^1.0.0","inherits":"^2.0.1","minimalistic-assert":"^1.0.0","minimalistic-crypto-utils":"^1.0.0"}}')},function(t,e,n){"use strict";var r=n(16),i=n(5),o=n(2),a=n(81),s=r.assert;function u(t){a.call(this,"short",t),this.a=new i(t.a,16).toRed(this.red),this.b=new i(t.b,16).toRed(this.red),this.tinv=this.two.redInvm(),this.zeroA=0===this.a.fromRed().cmpn(0),this.threeA=0===this.a.fromRed().sub(this.p).cmpn(-3),this.endo=this._getEndomorphism(t),this._endoWnafT1=new Array(4),this._endoWnafT2=new Array(4)}function c(t,e,n,r){a.BasePoint.call(this,t,"affine"),null===e&&null===n?(this.x=null,this.y=null,this.inf=!0):(this.x=new i(e,16),this.y=new i(n,16),r&&(this.x.forceRed(this.curve.red),this.y.forceRed(this.curve.red)),this.x.red||(this.x=this.x.toRed(this.curve.red)),this.y.red||(this.y=this.y.toRed(this.curve.red)),this.inf=!1)}function f(t,e,n,r){a.BasePoint.call(this,t,"jacobian"),null===e&&null===n&&null===r?(this.x=this.curve.one,this.y=this.curve.one,this.z=new i(0)):(this.x=new i(e,16),this.y=new i(n,16),this.z=new i(r,16)),this.x.red||(this.x=this.x.toRed(this.curve.red)),this.y.red||(this.y=this.y.toRed(this.curve.red)),this.z.red||(this.z=this.z.toRed(this.curve.red)),this.zOne=this.z===this.curve.one}o(u,a),t.exports=u,u.prototype._getEndomorphism=function(t){if(this.zeroA&&this.g&&this.n&&1===this.p.modn(3)){var e,n;if(t.beta)e=new i(t.beta,16).toRed(this.red);else{var r=this._getEndoRoots(this.p);e=(e=r[0].cmp(r[1])<0?r[0]:r[1]).toRed(this.red)}if(t.lambda)n=new i(t.lambda,16);else{var o=this._getEndoRoots(this.n);0===this.g.mul(o[0]).x.cmp(this.g.x.redMul(e))?n=o[0]:(n=o[1],s(0===this.g.mul(n).x.cmp(this.g.x.redMul(e))))}return{beta:e,lambda:n,basis:t.basis?t.basis.map((function(t){return{a:new i(t.a,16),b:new i(t.b,16)}})):this._getEndoBasis(n)}}},u.prototype._getEndoRoots=function(t){var e=t===this.p?this.red:i.mont(t),n=new i(2).toRed(e).redInvm(),r=n.redNeg(),o=new i(3).toRed(e).redNeg().redSqrt().redMul(n);return[r.redAdd(o).fromRed(),r.redSub(o).fromRed()]},u.prototype._getEndoBasis=function(t){for(var e,n,r,o,a,s,u,c,f,l=this.n.ushrn(Math.floor(this.n.bitLength()/2)),h=t,d=this.n.clone(),p=new i(1),g=new i(0),y=new i(0),b=new i(1),m=0;0!==h.cmpn(0);){var v=d.div(h);c=d.sub(v.mul(h)),f=y.sub(v.mul(p));var _=b.sub(v.mul(g));if(!r&&c.cmp(l)<0)e=u.neg(),n=p,r=c.neg(),o=f;else if(r&&2==++m)break;u=c,d=h,h=c,y=p,p=f,b=g,g=_}a=c.neg(),s=f;var w=r.sqr().add(o.sqr());return a.sqr().add(s.sqr()).cmp(w)>=0&&(a=e,s=n),r.negative&&(r=r.neg(),o=o.neg()),a.negative&&(a=a.neg(),s=s.neg()),[{a:r,b:o},{a:a,b:s}]},u.prototype._endoSplit=function(t){var e=this.endo.basis,n=e[0],r=e[1],i=r.b.mul(t).divRound(this.n),o=n.b.neg().mul(t).divRound(this.n),a=i.mul(n.a),s=o.mul(r.a),u=i.mul(n.b),c=o.mul(r.b);return{k1:t.sub(a).sub(s),k2:u.add(c).neg()}},u.prototype.pointFromX=function(t,e){(t=new i(t,16)).red||(t=t.toRed(this.red));var n=t.redSqr().redMul(t).redIAdd(t.redMul(this.a)).redIAdd(this.b),r=n.redSqrt();if(0!==r.redSqr().redSub(n).cmp(this.zero))throw new Error("invalid point");var o=r.fromRed().isOdd();return(e&&!o||!e&&o)&&(r=r.redNeg()),this.point(t,r)},u.prototype.validate=function(t){if(t.inf)return!0;var e=t.x,n=t.y,r=this.a.redMul(e),i=e.redSqr().redMul(e).redIAdd(r).redIAdd(this.b);return 0===n.redSqr().redISub(i).cmpn(0)},u.prototype._endoWnafMulAdd=function(t,e,n){for(var r=this._endoWnafT1,i=this._endoWnafT2,o=0;o":""},c.prototype.isInfinity=function(){return this.inf},c.prototype.add=function(t){if(this.inf)return t;if(t.inf)return this;if(this.eq(t))return this.dbl();if(this.neg().eq(t))return this.curve.point(null,null);if(0===this.x.cmp(t.x))return this.curve.point(null,null);var e=this.y.redSub(t.y);0!==e.cmpn(0)&&(e=e.redMul(this.x.redSub(t.x).redInvm()));var n=e.redSqr().redISub(this.x).redISub(t.x),r=e.redMul(this.x.redSub(n)).redISub(this.y);return this.curve.point(n,r)},c.prototype.dbl=function(){if(this.inf)return this;var t=this.y.redAdd(this.y);if(0===t.cmpn(0))return this.curve.point(null,null);var e=this.curve.a,n=this.x.redSqr(),r=t.redInvm(),i=n.redAdd(n).redIAdd(n).redIAdd(e).redMul(r),o=i.redSqr().redISub(this.x.redAdd(this.x)),a=i.redMul(this.x.redSub(o)).redISub(this.y);return this.curve.point(o,a)},c.prototype.getX=function(){return this.x.fromRed()},c.prototype.getY=function(){return this.y.fromRed()},c.prototype.mul=function(t){return t=new i(t,16),this.isInfinity()?this:this._hasDoubles(t)?this.curve._fixedNafMul(this,t):this.curve.endo?this.curve._endoWnafMulAdd([this],[t]):this.curve._wnafMul(this,t)},c.prototype.mulAdd=function(t,e,n){var r=[this,e],i=[t,n];return this.curve.endo?this.curve._endoWnafMulAdd(r,i):this.curve._wnafMulAdd(1,r,i,2)},c.prototype.jmulAdd=function(t,e,n){var r=[this,e],i=[t,n];return this.curve.endo?this.curve._endoWnafMulAdd(r,i,!0):this.curve._wnafMulAdd(1,r,i,2,!0)},c.prototype.eq=function(t){return this===t||this.inf===t.inf&&(this.inf||0===this.x.cmp(t.x)&&0===this.y.cmp(t.y))},c.prototype.neg=function(t){if(this.inf)return this;var e=this.curve.point(this.x,this.y.redNeg());if(t&&this.precomputed){var n=this.precomputed,r=function(t){return t.neg()};e.precomputed={naf:n.naf&&{wnd:n.naf.wnd,points:n.naf.points.map(r)},doubles:n.doubles&&{step:n.doubles.step,points:n.doubles.points.map(r)}}}return e},c.prototype.toJ=function(){return this.inf?this.curve.jpoint(null,null,null):this.curve.jpoint(this.x,this.y,this.curve.one)},o(f,a.BasePoint),u.prototype.jpoint=function(t,e,n){return new f(this,t,e,n)},f.prototype.toP=function(){if(this.isInfinity())return this.curve.point(null,null);var t=this.z.redInvm(),e=t.redSqr(),n=this.x.redMul(e),r=this.y.redMul(e).redMul(t);return this.curve.point(n,r)},f.prototype.neg=function(){return this.curve.jpoint(this.x,this.y.redNeg(),this.z)},f.prototype.add=function(t){if(this.isInfinity())return t;if(t.isInfinity())return this;var e=t.z.redSqr(),n=this.z.redSqr(),r=this.x.redMul(e),i=t.x.redMul(n),o=this.y.redMul(e.redMul(t.z)),a=t.y.redMul(n.redMul(this.z)),s=r.redSub(i),u=o.redSub(a);if(0===s.cmpn(0))return 0!==u.cmpn(0)?this.curve.jpoint(null,null,null):this.dbl();var c=s.redSqr(),f=c.redMul(s),l=r.redMul(c),h=u.redSqr().redIAdd(f).redISub(l).redISub(l),d=u.redMul(l.redISub(h)).redISub(o.redMul(f)),p=this.z.redMul(t.z).redMul(s);return this.curve.jpoint(h,d,p)},f.prototype.mixedAdd=function(t){if(this.isInfinity())return t.toJ();if(t.isInfinity())return this;var e=this.z.redSqr(),n=this.x,r=t.x.redMul(e),i=this.y,o=t.y.redMul(e).redMul(this.z),a=n.redSub(r),s=i.redSub(o);if(0===a.cmpn(0))return 0!==s.cmpn(0)?this.curve.jpoint(null,null,null):this.dbl();var u=a.redSqr(),c=u.redMul(a),f=n.redMul(u),l=s.redSqr().redIAdd(c).redISub(f).redISub(f),h=s.redMul(f.redISub(l)).redISub(i.redMul(c)),d=this.z.redMul(a);return this.curve.jpoint(l,h,d)},f.prototype.dblp=function(t){if(0===t)return this;if(this.isInfinity())return this;if(!t)return this.dbl();if(this.curve.zeroA||this.curve.threeA){for(var e=this,n=0;n=0)return!1;if(n.redIAdd(i),0===this.x.cmp(n))return!0}},f.prototype.inspect=function(){return this.isInfinity()?"":""},f.prototype.isInfinity=function(){return 0===this.z.cmpn(0)}},function(t,e,n){"use strict";var r=n(5),i=n(2),o=n(81),a=n(16);function s(t){o.call(this,"mont",t),this.a=new r(t.a,16).toRed(this.red),this.b=new r(t.b,16).toRed(this.red),this.i4=new r(4).toRed(this.red).redInvm(),this.two=new r(2).toRed(this.red),this.a24=this.i4.redMul(this.a.redAdd(this.two))}function u(t,e,n){o.BasePoint.call(this,t,"projective"),null===e&&null===n?(this.x=this.curve.one,this.z=this.curve.zero):(this.x=new r(e,16),this.z=new r(n,16),this.x.red||(this.x=this.x.toRed(this.curve.red)),this.z.red||(this.z=this.z.toRed(this.curve.red)))}i(s,o),t.exports=s,s.prototype.validate=function(t){var e=t.normalize().x,n=e.redSqr(),r=n.redMul(e).redAdd(n.redMul(this.a)).redAdd(e);return 0===r.redSqrt().redSqr().cmp(r)},i(u,o.BasePoint),s.prototype.decodePoint=function(t,e){return this.point(a.toArray(t,e),1)},s.prototype.point=function(t,e){return new u(this,t,e)},s.prototype.pointFromJSON=function(t){return u.fromJSON(this,t)},u.prototype.precompute=function(){},u.prototype._encode=function(){return this.getX().toArray("be",this.curve.p.byteLength())},u.fromJSON=function(t,e){return new u(t,e[0],e[1]||t.one)},u.prototype.inspect=function(){return this.isInfinity()?"":""},u.prototype.isInfinity=function(){return 0===this.z.cmpn(0)},u.prototype.dbl=function(){var t=this.x.redAdd(this.z).redSqr(),e=this.x.redSub(this.z).redSqr(),n=t.redSub(e),r=t.redMul(e),i=n.redMul(e.redAdd(this.curve.a24.redMul(n)));return this.curve.point(r,i)},u.prototype.add=function(){throw new Error("Not supported on Montgomery curve")},u.prototype.diffAdd=function(t,e){var n=this.x.redAdd(this.z),r=this.x.redSub(this.z),i=t.x.redAdd(t.z),o=t.x.redSub(t.z).redMul(n),a=i.redMul(r),s=e.z.redMul(o.redAdd(a).redSqr()),u=e.x.redMul(o.redISub(a).redSqr());return this.curve.point(s,u)},u.prototype.mul=function(t){for(var e=t.clone(),n=this,r=this.curve.point(null,null),i=[];0!==e.cmpn(0);e.iushrn(1))i.push(e.andln(1));for(var o=i.length-1;o>=0;o--)0===i[o]?(n=n.diffAdd(r,this),r=r.dbl()):(r=n.diffAdd(r,this),n=n.dbl());return r},u.prototype.mulAdd=function(){throw new Error("Not supported on Montgomery curve")},u.prototype.jumlAdd=function(){throw new Error("Not supported on Montgomery curve")},u.prototype.eq=function(t){return 0===this.getX().cmp(t.getX())},u.prototype.normalize=function(){return this.x=this.x.redMul(this.z.redInvm()),this.z=this.curve.one,this},u.prototype.getX=function(){return this.normalize(),this.x.fromRed()}},function(t,e,n){"use strict";var r=n(16),i=n(5),o=n(2),a=n(81),s=r.assert;function u(t){this.twisted=1!=(0|t.a),this.mOneA=this.twisted&&-1==(0|t.a),this.extended=this.mOneA,a.call(this,"edwards",t),this.a=new i(t.a,16).umod(this.red.m),this.a=this.a.toRed(this.red),this.c=new i(t.c,16).toRed(this.red),this.c2=this.c.redSqr(),this.d=new i(t.d,16).toRed(this.red),this.dd=this.d.redAdd(this.d),s(!this.twisted||0===this.c.fromRed().cmpn(1)),this.oneC=1==(0|t.c)}function c(t,e,n,r,o){a.BasePoint.call(this,t,"projective"),null===e&&null===n&&null===r?(this.x=this.curve.zero,this.y=this.curve.one,this.z=this.curve.one,this.t=this.curve.zero,this.zOne=!0):(this.x=new i(e,16),this.y=new i(n,16),this.z=r?new i(r,16):this.curve.one,this.t=o&&new i(o,16),this.x.red||(this.x=this.x.toRed(this.curve.red)),this.y.red||(this.y=this.y.toRed(this.curve.red)),this.z.red||(this.z=this.z.toRed(this.curve.red)),this.t&&!this.t.red&&(this.t=this.t.toRed(this.curve.red)),this.zOne=this.z===this.curve.one,this.curve.extended&&!this.t&&(this.t=this.x.redMul(this.y),this.zOne||(this.t=this.t.redMul(this.z.redInvm()))))}o(u,a),t.exports=u,u.prototype._mulA=function(t){return this.mOneA?t.redNeg():this.a.redMul(t)},u.prototype._mulC=function(t){return this.oneC?t:this.c.redMul(t)},u.prototype.jpoint=function(t,e,n,r){return this.point(t,e,n,r)},u.prototype.pointFromX=function(t,e){(t=new i(t,16)).red||(t=t.toRed(this.red));var n=t.redSqr(),r=this.c2.redSub(this.a.redMul(n)),o=this.one.redSub(this.c2.redMul(this.d).redMul(n)),a=r.redMul(o.redInvm()),s=a.redSqrt();if(0!==s.redSqr().redSub(a).cmp(this.zero))throw new Error("invalid point");var u=s.fromRed().isOdd();return(e&&!u||!e&&u)&&(s=s.redNeg()),this.point(t,s)},u.prototype.pointFromY=function(t,e){(t=new i(t,16)).red||(t=t.toRed(this.red));var n=t.redSqr(),r=n.redSub(this.c2),o=n.redMul(this.d).redMul(this.c2).redSub(this.a),a=r.redMul(o.redInvm());if(0===a.cmp(this.zero)){if(e)throw new Error("invalid point");return this.point(this.zero,t)}var s=a.redSqrt();if(0!==s.redSqr().redSub(a).cmp(this.zero))throw new Error("invalid point");return s.fromRed().isOdd()!==e&&(s=s.redNeg()),this.point(s,t)},u.prototype.validate=function(t){if(t.isInfinity())return!0;t.normalize();var e=t.x.redSqr(),n=t.y.redSqr(),r=e.redMul(this.a).redAdd(n),i=this.c2.redMul(this.one.redAdd(this.d.redMul(e).redMul(n)));return 0===r.cmp(i)},o(c,a.BasePoint),u.prototype.pointFromJSON=function(t){return c.fromJSON(this,t)},u.prototype.point=function(t,e,n,r){return new c(this,t,e,n,r)},c.fromJSON=function(t,e){return new c(t,e[0],e[1],e[2])},c.prototype.inspect=function(){return this.isInfinity()?"":""},c.prototype.isInfinity=function(){return 0===this.x.cmpn(0)&&(0===this.y.cmp(this.z)||this.zOne&&0===this.y.cmp(this.curve.c))},c.prototype._extDbl=function(){var t=this.x.redSqr(),e=this.y.redSqr(),n=this.z.redSqr();n=n.redIAdd(n);var r=this.curve._mulA(t),i=this.x.redAdd(this.y).redSqr().redISub(t).redISub(e),o=r.redAdd(e),a=o.redSub(n),s=r.redSub(e),u=i.redMul(a),c=o.redMul(s),f=i.redMul(s),l=a.redMul(o);return this.curve.point(u,c,l,f)},c.prototype._projDbl=function(){var t,e,n,r=this.x.redAdd(this.y).redSqr(),i=this.x.redSqr(),o=this.y.redSqr();if(this.curve.twisted){var a=(c=this.curve._mulA(i)).redAdd(o);if(this.zOne)t=r.redSub(i).redSub(o).redMul(a.redSub(this.curve.two)),e=a.redMul(c.redSub(o)),n=a.redSqr().redSub(a).redSub(a);else{var s=this.z.redSqr(),u=a.redSub(s).redISub(s);t=r.redSub(i).redISub(o).redMul(u),e=a.redMul(c.redSub(o)),n=a.redMul(u)}}else{var c=i.redAdd(o);s=this.curve._mulC(this.z).redSqr(),u=c.redSub(s).redSub(s);t=this.curve._mulC(r.redISub(c)).redMul(u),e=this.curve._mulC(c).redMul(i.redISub(o)),n=c.redMul(u)}return this.curve.point(t,e,n)},c.prototype.dbl=function(){return this.isInfinity()?this:this.curve.extended?this._extDbl():this._projDbl()},c.prototype._extAdd=function(t){var e=this.y.redSub(this.x).redMul(t.y.redSub(t.x)),n=this.y.redAdd(this.x).redMul(t.y.redAdd(t.x)),r=this.t.redMul(this.curve.dd).redMul(t.t),i=this.z.redMul(t.z.redAdd(t.z)),o=n.redSub(e),a=i.redSub(r),s=i.redAdd(r),u=n.redAdd(e),c=o.redMul(a),f=s.redMul(u),l=o.redMul(u),h=a.redMul(s);return this.curve.point(c,f,h,l)},c.prototype._projAdd=function(t){var e,n,r=this.z.redMul(t.z),i=r.redSqr(),o=this.x.redMul(t.x),a=this.y.redMul(t.y),s=this.curve.d.redMul(o).redMul(a),u=i.redSub(s),c=i.redAdd(s),f=this.x.redAdd(this.y).redMul(t.x.redAdd(t.y)).redISub(o).redISub(a),l=r.redMul(u).redMul(f);return this.curve.twisted?(e=r.redMul(c).redMul(a.redSub(this.curve._mulA(o))),n=u.redMul(c)):(e=r.redMul(c).redMul(a.redSub(o)),n=this.curve._mulC(u).redMul(c)),this.curve.point(l,e,n)},c.prototype.add=function(t){return this.isInfinity()?t:t.isInfinity()?this:this.curve.extended?this._extAdd(t):this._projAdd(t)},c.prototype.mul=function(t){return this._hasDoubles(t)?this.curve._fixedNafMul(this,t):this.curve._wnafMul(this,t)},c.prototype.mulAdd=function(t,e,n){return this.curve._wnafMulAdd(1,[this,e],[t,n],2,!1)},c.prototype.jmulAdd=function(t,e,n){return this.curve._wnafMulAdd(1,[this,e],[t,n],2,!0)},c.prototype.normalize=function(){if(this.zOne)return this;var t=this.z.redInvm();return this.x=this.x.redMul(t),this.y=this.y.redMul(t),this.t&&(this.t=this.t.redMul(t)),this.z=this.curve.one,this.zOne=!0,this},c.prototype.neg=function(){return this.curve.point(this.x.redNeg(),this.y,this.z,this.t&&this.t.redNeg())},c.prototype.getX=function(){return this.normalize(),this.x.fromRed()},c.prototype.getY=function(){return this.normalize(),this.y.fromRed()},c.prototype.eq=function(t){return this===t||0===this.getX().cmp(t.getX())&&0===this.getY().cmp(t.getY())},c.prototype.eqXToP=function(t){var e=t.toRed(this.curve.red).redMul(this.z);if(0===this.x.cmp(e))return!0;for(var n=t.clone(),r=this.curve.redN.redMul(this.z);;){if(n.iadd(this.curve.n),n.cmp(this.curve.p)>=0)return!1;if(e.redIAdd(r),0===this.x.cmp(e))return!0}},c.prototype.toP=c.prototype.normalize,c.prototype.mixedAdd=c.prototype.add},function(t,e,n){"use strict";e.sha1=n(468),e.sha224=n(469),e.sha256=n(216),e.sha384=n(470),e.sha512=n(217)},function(t,e,n){"use strict";var r=n(21),i=n(56),o=n(215),a=r.rotl32,s=r.sum32,u=r.sum32_5,c=o.ft_1,f=i.BlockHash,l=[1518500249,1859775393,2400959708,3395469782];function h(){if(!(this instanceof h))return new h;f.call(this),this.h=[1732584193,4023233417,2562383102,271733878,3285377520],this.W=new Array(80)}r.inherits(h,f),t.exports=h,h.blockSize=512,h.outSize=160,h.hmacStrength=80,h.padLength=64,h.prototype._update=function(t,e){for(var n=this.W,r=0;r<16;r++)n[r]=t[e+r];for(;rthis.blockSize&&(t=(new this.Hash).update(t).digest()),i(t.length<=this.blockSize);for(var e=t.length;e0))return a.iaddn(1),this.keyFromPrivate(a)}},l.prototype._truncateToN=function(t,e){var n=8*t.byteLength()-this.n.bitLength();return n>0&&(t=t.ushrn(n)),!e&&t.cmp(this.n)>=0?t.sub(this.n):t},l.prototype.sign=function(t,e,n,o){"object"==typeof n&&(o=n,n=null),o||(o={}),e=this.keyFromPrivate(e,n),t=this._truncateToN(new r(t,16));for(var a=this.n.byteLength(),s=e.getPrivate().toArray("be",a),u=t.toArray("be",a),c=new i({hash:this.hash,entropy:s,nonce:u,pers:o.pers,persEnc:o.persEnc||"utf8"}),l=this.n.sub(new r(1)),h=0;;h++){var d=o.k?o.k(h):new r(c.generate(this.n.byteLength()));if(!((d=this._truncateToN(d,!0)).cmpn(1)<=0||d.cmp(l)>=0)){var p=this.g.mul(d);if(!p.isInfinity()){var g=p.getX(),y=g.umod(this.n);if(0!==y.cmpn(0)){var b=d.invm(this.n).mul(y.mul(e.getPrivate()).iadd(t));if(0!==(b=b.umod(this.n)).cmpn(0)){var m=(p.getY().isOdd()?1:0)|(0!==g.cmp(y)?2:0);return o.canonical&&b.cmp(this.nh)>0&&(b=this.n.sub(b),m^=1),new f({r:y,s:b,recoveryParam:m})}}}}}},l.prototype.verify=function(t,e,n,i){t=this._truncateToN(new r(t,16)),n=this.keyFromPublic(n,i);var o=(e=new f(e,"hex")).r,a=e.s;if(o.cmpn(1)<0||o.cmp(this.n)>=0)return!1;if(a.cmpn(1)<0||a.cmp(this.n)>=0)return!1;var s,u=a.invm(this.n),c=u.mul(t).umod(this.n),l=u.mul(o).umod(this.n);return this.curve._maxwellTrick?!(s=this.g.jmulAdd(c,n.getPublic(),l)).isInfinity()&&s.eqXToP(o):!(s=this.g.mulAdd(c,n.getPublic(),l)).isInfinity()&&0===s.getX().umod(this.n).cmp(o)},l.prototype.recoverPubKey=function(t,e,n,i){u((3&n)===n,"The recovery param is more than two bits"),e=new f(e,i);var o=this.n,a=new r(t),s=e.r,c=e.s,l=1&n,h=n>>1;if(s.cmp(this.curve.p.umod(this.curve.n))>=0&&h)throw new Error("Unable to find sencond key candinate");s=h?this.curve.pointFromX(s.add(this.curve.n),l):this.curve.pointFromX(s,l);var d=e.r.invm(o),p=o.sub(a).mul(d).umod(o),g=c.mul(d).umod(o);return this.g.mulAdd(p,s,g)},l.prototype.getKeyRecoveryParam=function(t,e,n,r){if(null!==(e=new f(e,r)).recoveryParam)return e.recoveryParam;for(var i=0;i<4;i++){var o;try{o=this.recoverPubKey(t,e,i)}catch(t){continue}if(o.eq(n))return i}throw new Error("Unable to find valid recovery factor")}},function(t,e,n){"use strict";var r=n(127),i=n(213),o=n(15);function a(t){if(!(this instanceof a))return new a(t);this.hash=t.hash,this.predResist=!!t.predResist,this.outLen=this.hash.outSize,this.minEntropy=t.minEntropy||this.hash.hmacStrength,this._reseed=null,this.reseedInterval=null,this.K=null,this.V=null;var e=i.toArray(t.entropy,t.entropyEnc||"hex"),n=i.toArray(t.nonce,t.nonceEnc||"hex"),r=i.toArray(t.pers,t.persEnc||"hex");o(e.length>=this.minEntropy/8,"Not enough entropy. Minimum is: "+this.minEntropy+" bits"),this._init(e,n,r)}t.exports=a,a.prototype._init=function(t,e,n){var r=t.concat(e).concat(n);this.K=new Array(this.outLen/8),this.V=new Array(this.outLen/8);for(var i=0;i=this.minEntropy/8,"Not enough entropy. Minimum is: "+this.minEntropy+" bits"),this._update(t.concat(n||[])),this._reseed=1},a.prototype.generate=function(t,e,n,r){if(this._reseed>this.reseedInterval)throw new Error("Reseed is required");"string"!=typeof e&&(r=n,n=e,e=null),n&&(n=i.toArray(n,r||"hex"),this._update(n));for(var o=[];o.length"}},function(t,e,n){"use strict";var r=n(5),i=n(16),o=i.assert;function a(t,e){if(t instanceof a)return t;this._importDER(t,e)||(o(t.r&&t.s,"Signature without r or s"),this.r=new r(t.r,16),this.s=new r(t.s,16),void 0===t.recoveryParam?this.recoveryParam=null:this.recoveryParam=t.recoveryParam)}function s(){this.place=0}function u(t,e){var n=t[e.place++];if(!(128&n))return n;for(var r=15&n,i=0,o=0,a=e.place;o>>3);for(t.push(128|n);--n;)t.push(e>>>(n<<3)&255);t.push(e)}}t.exports=a,a.prototype._importDER=function(t,e){t=i.toArray(t,e);var n=new s;if(48!==t[n.place++])return!1;if(u(t,n)+n.place!==t.length)return!1;if(2!==t[n.place++])return!1;var o=u(t,n),a=t.slice(n.place,o+n.place);if(n.place+=o,2!==t[n.place++])return!1;var c=u(t,n);if(t.length!==c+n.place)return!1;var f=t.slice(n.place,c+n.place);return 0===a[0]&&128&a[1]&&(a=a.slice(1)),0===f[0]&&128&f[1]&&(f=f.slice(1)),this.r=new r(a),this.s=new r(f),this.recoveryParam=null,!0},a.prototype.toDER=function(t){var e=this.r.toArray(),n=this.s.toArray();for(128&e[0]&&(e=[0].concat(e)),128&n[0]&&(n=[0].concat(n)),e=c(e),n=c(n);!(n[0]||128&n[1]);)n=n.slice(1);var r=[2];f(r,e.length),(r=r.concat(e)).push(2),f(r,n.length);var o=r.concat(n),a=[48];return f(a,o.length),a=a.concat(o),i.encode(a,t)}},function(t,e,n){"use strict";var r=n(127),i=n(126),o=n(16),a=o.assert,s=o.parseBytes,u=n(479),c=n(480);function f(t){if(a("ed25519"===t,"only tested with ed25519 so far"),!(this instanceof f))return new f(t);t=i[t].curve;this.curve=t,this.g=t.g,this.g.precompute(t.n.bitLength()+1),this.pointClass=t.point().constructor,this.encodingLength=Math.ceil(t.n.bitLength()/8),this.hash=r.sha512}t.exports=f,f.prototype.sign=function(t,e){t=s(t);var n=this.keyFromSecret(e),r=this.hashInt(n.messagePrefix(),t),i=this.g.mul(r),o=this.encodePoint(i),a=this.hashInt(o,n.pubBytes(),t).mul(n.priv()),u=r.add(a).umod(this.curve.n);return this.makeSignature({R:i,S:u,Rencoded:o})},f.prototype.verify=function(t,e,n){t=s(t),e=this.makeSignature(e);var r=this.keyFromPublic(n),i=this.hashInt(e.Rencoded(),r.pubBytes(),t),o=this.g.mul(e.S());return e.R().add(r.pub().mul(i)).eq(o)},f.prototype.hashInt=function(){for(var t=this.hash(),e=0;e=e)throw new Error("invalid sig")}t.exports=function(t,n,u,c,f){var l=o(u);if("ec"===l.type){if("ecdsa"!==c&&"ecdsa/rsa"!==c)throw new Error("wrong public key type");return function(t,e,n){var r=a[n.data.algorithm.curve.join(".")];if(!r)throw new Error("unknown curve "+n.data.algorithm.curve.join("."));var o=new i(r),s=n.data.subjectPrivateKey.data;return o.verify(e,t,s)}(t,n,l)}if("dsa"===l.type){if("dsa"!==c)throw new Error("wrong public key type");return function(t,e,n){var i=n.data.p,a=n.data.q,u=n.data.g,c=n.data.pub_key,f=o.signature.decode(t,"der"),l=f.s,h=f.r;s(l,a),s(h,a);var d=r.mont(i),p=l.invm(a);return 0===u.toRed(d).redPow(new r(e).mul(p).mod(a)).fromRed().mul(c.toRed(d).redPow(h.mul(p).mod(a)).fromRed()).mod(i).mod(a).cmp(h)}(t,n,l)}if("rsa"!==c&&"ecdsa/rsa"!==c)throw new Error("wrong public key type");n=e.concat([f,n]);for(var h=l.modulus.byteLength(),d=[1],p=0;n.length+d.length+2n-h-2)throw new Error("message too long");var d=l.alloc(n-r-h-2),p=n-f-1,g=i(f),y=s(l.concat([c,d,l.alloc(1,1),e],p),a(g,p)),b=s(g,a(y,f));return new u(l.concat([l.alloc(1),b,y],n))}(p,e);else if(1===h)d=function(t,e,n){var r,o=e.length,a=t.modulus.byteLength();if(o>a-11)throw new Error("message too long");r=n?l.alloc(a-o-3,255):function(t){var e,n=l.allocUnsafe(t),r=0,o=i(2*t),a=0;for(;r=0)throw new Error("data too long for modulus")}return n?f(d,p):c(d,p)}},function(t,e,n){var r=n(82),i=n(223),o=n(224),a=n(5),s=n(124),u=n(53),c=n(225),f=n(3).Buffer;t.exports=function(t,e,n){var l;l=t.padding?t.padding:n?1:4;var h,d=r(t),p=d.modulus.byteLength();if(e.length>p||new a(e).cmp(d.modulus)>=0)throw new Error("decryption error");h=n?c(new a(e),d):s(e,d);var g=f.alloc(p-h.length);if(h=f.concat([g,h],p),4===l)return function(t,e){var n=t.modulus.byteLength(),r=u("sha1").update(f.alloc(0)).digest(),a=r.length;if(0!==e[0])throw new Error("decryption error");var s=e.slice(1,a+1),c=e.slice(a+1),l=o(s,i(c,a)),h=o(c,i(l,n-a-1));if(function(t,e){t=f.from(t),e=f.from(e);var n=0,r=t.length;t.length!==e.length&&(n++,r=Math.min(t.length,e.length));var i=-1;for(;++i=e.length){o++;break}var a=e.slice(2,i-1);("0002"!==r.toString("hex")&&!n||"0001"!==r.toString("hex")&&n)&&o++;a.length<8&&o++;if(o)throw new Error("decryption error");return e.slice(i)}(0,h,n);if(3===l)return h;throw new Error("unknown padding")}},function(t,e,n){"use strict";(function(t,r){function i(){throw new Error("secure random number generation not supported by this browser\nuse chrome, FireFox or Internet Explorer 11")}var o=n(3),a=n(44),s=o.Buffer,u=o.kMaxLength,c=t.crypto||t.msCrypto,f=Math.pow(2,32)-1;function l(t,e){if("number"!=typeof t||t!=t)throw new TypeError("offset must be a number");if(t>f||t<0)throw new TypeError("offset must be a uint32");if(t>u||t>e)throw new RangeError("offset out of range")}function h(t,e,n){if("number"!=typeof t||t!=t)throw new TypeError("size must be a number");if(t>f||t<0)throw new TypeError("size must be a uint32");if(t+e>n||t>u)throw new RangeError("buffer too small")}function d(t,e,n,i){if(r.browser){var o=t.buffer,s=new Uint8Array(o,e,n);return c.getRandomValues(s),i?void r.nextTick((function(){i(null,t)})):t}if(!i)return a(n).copy(t,e),t;a(n,(function(n,r){if(n)return i(n);r.copy(t,e),i(null,t)}))}c&&c.getRandomValues||!r.browser?(e.randomFill=function(e,n,r,i){if(!(s.isBuffer(e)||e instanceof t.Uint8Array))throw new TypeError('"buf" argument must be a Buffer or Uint8Array');if("function"==typeof n)i=n,n=0,r=e.length;else if("function"==typeof r)i=r,r=e.length-n;else if("function"!=typeof i)throw new TypeError('"cb" argument must be a function');return l(n,e.length),h(r,n,e.length),d(e,n,r,i)},e.randomFillSync=function(e,n,r){void 0===n&&(n=0);if(!(s.isBuffer(e)||e instanceof t.Uint8Array))throw new TypeError('"buf" argument must be a Buffer or Uint8Array');l(n,e.length),void 0===r&&(r=e.length-n);return h(r,n,e.length),d(e,n,r)}):(e.randomFill=i,e.randomFillSync=i)}).call(this,n(11),n(7))},function(t,e,n){var r={"./dark/index.scss":501,"./default/index.scss":503,"./forest/index.scss":505,"./neutral/index.scss":507};function i(t){var e=o(t);return n(e)}function o(t){if(!n.o(r,t)){var e=new Error("Cannot find module '"+t+"'");throw e.code="MODULE_NOT_FOUND",e}return r[t]}i.keys=function(){return Object.keys(r)},i.resolve=o,t.exports=i,i.id=500},function(t,e,n){var r=n(502);t.exports="string"==typeof r?r:r.toString()},function(t,e,n){(t.exports=n(83)(!1)).push([t.i,".label{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);color:#333}.label text{fill:#333}.node rect,.node circle,.node ellipse,.node polygon{fill:#BDD5EA;stroke:purple;stroke-width:1px}.node .label{text-align:center}.node.clickable{cursor:pointer}.arrowheadPath{fill:#d3d3d3}.edgePath .path{stroke:#d3d3d3;stroke-width:1.5px}.edgeLabel{background-color:#e8e8e8;text-align:center}.cluster rect{fill:#6D6D65;stroke:rgba(255,255,255,0.25);stroke-width:1px}.cluster text{fill:#F9FFFE}div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);font-size:12px;background:#6D6D65;border:1px solid rgba(255,255,255,0.25);border-radius:2px;pointer-events:none;z-index:100}.actor{stroke:#81B1DB;fill:#BDD5EA}text.actor{fill:#000;stroke:none}.actor-line{stroke:#d3d3d3}.messageLine0{stroke-width:1.5;stroke-dasharray:'2 2';stroke:#d3d3d3}.messageLine1{stroke-width:1.5;stroke-dasharray:'2 2';stroke:#d3d3d3}#arrowhead{fill:#d3d3d3}.sequenceNumber{fill:#fff}#sequencenumber{fill:#d3d3d3}#crosshead path{fill:#d3d3d3 !important;stroke:#d3d3d3 !important}.messageText{fill:#d3d3d3;stroke:none}.labelBox{stroke:#81B1DB;fill:#BDD5EA}.labelText{fill:#323D47;stroke:none}.loopText{fill:#d3d3d3;stroke:none}.loopLine{stroke-width:2;stroke-dasharray:'2 2';stroke:#81B1DB}.note{stroke:rgba(255,255,255,0.25);fill:#fff5ad}.noteText{fill:black;stroke:none;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);font-size:14px}.activation0{fill:#f4f4f4;stroke:#666}.activation1{fill:#f4f4f4;stroke:#666}.activation2{fill:#f4f4f4;stroke:#666}.mermaid-main-font{font-family:\"trebuchet ms\", verdana, arial;font-family:var(--mermaid-font-family)}.section{stroke:none;opacity:0.2}.section0{fill:rgba(255,255,255,0.3)}.section2{fill:#EAE8B9}.section1,.section3{fill:#fff;opacity:0.2}.sectionTitle0{fill:#F9FFFE}.sectionTitle1{fill:#F9FFFE}.sectionTitle2{fill:#F9FFFE}.sectionTitle3{fill:#F9FFFE}.sectionTitle{text-anchor:start;font-size:11px;text-height:14px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}.grid .tick{stroke:#d3d3d3;opacity:0.3;shape-rendering:crispEdges}.grid .tick text{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}.grid path{stroke-width:0}.today{fill:none;stroke:#DB5757;stroke-width:2px}.task{stroke-width:2}.taskText{text-anchor:middle;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}.taskText:not([font-size]){font-size:11px}.taskTextOutsideRight{fill:#323D47;text-anchor:start;font-size:11px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}.taskTextOutsideLeft{fill:#323D47;text-anchor:end;font-size:11px}.task.clickable{cursor:pointer}.taskText.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}.taskTextOutsideLeft.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}.taskTextOutsideRight.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}.taskText0,.taskText1,.taskText2,.taskText3{fill:#323D47}.task0,.task1,.task2,.task3{fill:#BDD5EA;stroke:rgba(255,255,255,0.5)}.taskTextOutside0,.taskTextOutside2{fill:#d3d3d3}.taskTextOutside1,.taskTextOutside3{fill:#d3d3d3}.active0,.active1,.active2,.active3{fill:#81B1DB;stroke:rgba(255,255,255,0.5)}.activeText0,.activeText1,.activeText2,.activeText3{fill:#323D47 !important}.done0,.done1,.done2,.done3{stroke:grey;fill:#d3d3d3;stroke-width:2}.doneText0,.doneText1,.doneText2,.doneText3{fill:#323D47 !important}.crit0,.crit1,.crit2,.crit3{stroke:#E83737;fill:#E83737;stroke-width:2}.activeCrit0,.activeCrit1,.activeCrit2,.activeCrit3{stroke:#E83737;fill:#81B1DB;stroke-width:2}.doneCrit0,.doneCrit1,.doneCrit2,.doneCrit3{stroke:#E83737;fill:#d3d3d3;stroke-width:2;cursor:pointer;shape-rendering:crispEdges}.milestone{transform:rotate(45deg) scale(0.8, 0.8)}.milestoneText{font-style:italic}.doneCritText0,.doneCritText1,.doneCritText2,.doneCritText3{fill:#323D47 !important}.activeCritText0,.activeCritText1,.activeCritText2,.activeCritText3{fill:#323D47 !important}.titleText{text-anchor:middle;font-size:18px;fill:#323D47;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}g.classGroup text{fill:purple;stroke:none;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);font-size:10px}g.classGroup text .title{font-weight:bolder}g.classGroup rect{fill:#BDD5EA;stroke:purple}g.classGroup line{stroke:purple;stroke-width:1}.classLabel .box{stroke:none;stroke-width:0;fill:#BDD5EA;opacity:0.5}.classLabel .label{fill:purple;font-size:10px}.relation{stroke:purple;stroke-width:1;fill:none}#compositionStart{fill:purple;stroke:purple;stroke-width:1}#compositionEnd{fill:purple;stroke:purple;stroke-width:1}#aggregationStart{fill:#BDD5EA;stroke:purple;stroke-width:1}#aggregationEnd{fill:#BDD5EA;stroke:purple;stroke-width:1}#dependencyStart{fill:purple;stroke:purple;stroke-width:1}#dependencyEnd{fill:purple;stroke:purple;stroke-width:1}#extensionStart{fill:purple;stroke:purple;stroke-width:1}#extensionEnd{fill:purple;stroke:purple;stroke-width:1}.commit-id,.commit-msg,.branch-label{fill:lightgrey;color:lightgrey;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}.pieTitleText{text-anchor:middle;font-size:25px;fill:#323D47;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}.slice{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}g.stateGroup text{fill:purple;stroke:none;font-size:10px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}g.stateGroup text{fill:purple;stroke:none;font-size:10px}g.stateGroup .state-title{font-weight:bolder;fill:#000}g.stateGroup rect{fill:#BDD5EA;stroke:purple}g.stateGroup line{stroke:purple;stroke-width:1}.transition{stroke:purple;stroke-width:1;fill:none}.stateGroup .composit{fill:white;border-bottom:1px}.state-note{stroke:rgba(255,255,255,0.25);fill:#fff5ad}.state-note text{fill:black;stroke:none;font-size:10px}.stateLabel .box{stroke:none;stroke-width:0;fill:#BDD5EA;opacity:0.5}.stateLabel text{fill:#000;font-size:10px;font-weight:bold;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}:root{--mermaid-font-family: '\"trebuchet ms\", verdana, arial';--mermaid-font-family: \"Comic Sans MS\", \"Comic Sans\", cursive}\n",""])},function(t,e,n){var r=n(504);t.exports="string"==typeof r?r:r.toString()},function(t,e,n){(t.exports=n(83)(!1)).push([t.i,".label{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);color:#333}.label text{fill:#333}.node rect,.node circle,.node ellipse,.node polygon{fill:#ECECFF;stroke:#9370db;stroke-width:1px}.node .label{text-align:center}.node.clickable{cursor:pointer}.arrowheadPath{fill:#333}.edgePath .path{stroke:#333;stroke-width:1.5px}.edgeLabel{background-color:#e8e8e8;text-align:center}.cluster rect{fill:#ffffde;stroke:#aa3;stroke-width:1px}.cluster text{fill:#333}div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);font-size:12px;background:#ffffde;border:1px solid #aa3;border-radius:2px;pointer-events:none;z-index:100}.actor{stroke:#ccf;fill:#ECECFF}text.actor{fill:#000;stroke:none}.actor-line{stroke:grey}.messageLine0{stroke-width:1.5;stroke-dasharray:'2 2';stroke:#333}.messageLine1{stroke-width:1.5;stroke-dasharray:'2 2';stroke:#333}#arrowhead{fill:#333}.sequenceNumber{fill:#fff}#sequencenumber{fill:#333}#crosshead path{fill:#333 !important;stroke:#333 !important}.messageText{fill:#333;stroke:none}.labelBox{stroke:#ccf;fill:#ECECFF}.labelText{fill:#000;stroke:none}.loopText{fill:#000;stroke:none}.loopLine{stroke-width:2;stroke-dasharray:'2 2';stroke:#ccf}.note{stroke:#aa3;fill:#fff5ad}.noteText{fill:black;stroke:none;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);font-size:14px}.activation0{fill:#f4f4f4;stroke:#666}.activation1{fill:#f4f4f4;stroke:#666}.activation2{fill:#f4f4f4;stroke:#666}.mermaid-main-font{font-family:\"trebuchet ms\", verdana, arial;font-family:var(--mermaid-font-family)}.section{stroke:none;opacity:0.2}.section0{fill:rgba(102,102,255,0.49)}.section2{fill:#fff400}.section1,.section3{fill:#fff;opacity:0.2}.sectionTitle0{fill:#333}.sectionTitle1{fill:#333}.sectionTitle2{fill:#333}.sectionTitle3{fill:#333}.sectionTitle{text-anchor:start;font-size:11px;text-height:14px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}.grid .tick{stroke:#d3d3d3;opacity:0.3;shape-rendering:crispEdges}.grid .tick text{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}.grid path{stroke-width:0}.today{fill:none;stroke:red;stroke-width:2px}.task{stroke-width:2}.taskText{text-anchor:middle;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}.taskText:not([font-size]){font-size:11px}.taskTextOutsideRight{fill:#000;text-anchor:start;font-size:11px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}.taskTextOutsideLeft{fill:#000;text-anchor:end;font-size:11px}.task.clickable{cursor:pointer}.taskText.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}.taskTextOutsideLeft.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}.taskTextOutsideRight.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}.taskText0,.taskText1,.taskText2,.taskText3{fill:#fff}.task0,.task1,.task2,.task3{fill:#8a90dd;stroke:#534fbc}.taskTextOutside0,.taskTextOutside2{fill:#000}.taskTextOutside1,.taskTextOutside3{fill:#000}.active0,.active1,.active2,.active3{fill:#bfc7ff;stroke:#534fbc}.activeText0,.activeText1,.activeText2,.activeText3{fill:#000 !important}.done0,.done1,.done2,.done3{stroke:grey;fill:#d3d3d3;stroke-width:2}.doneText0,.doneText1,.doneText2,.doneText3{fill:#000 !important}.crit0,.crit1,.crit2,.crit3{stroke:#f88;fill:red;stroke-width:2}.activeCrit0,.activeCrit1,.activeCrit2,.activeCrit3{stroke:#f88;fill:#bfc7ff;stroke-width:2}.doneCrit0,.doneCrit1,.doneCrit2,.doneCrit3{stroke:#f88;fill:#d3d3d3;stroke-width:2;cursor:pointer;shape-rendering:crispEdges}.milestone{transform:rotate(45deg) scale(0.8, 0.8)}.milestoneText{font-style:italic}.doneCritText0,.doneCritText1,.doneCritText2,.doneCritText3{fill:#000 !important}.activeCritText0,.activeCritText1,.activeCritText2,.activeCritText3{fill:#000 !important}.titleText{text-anchor:middle;font-size:18px;fill:#000;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}g.classGroup text{fill:#9370db;stroke:none;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);font-size:10px}g.classGroup text .title{font-weight:bolder}g.classGroup rect{fill:#ECECFF;stroke:#9370db}g.classGroup line{stroke:#9370db;stroke-width:1}.classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5}.classLabel .label{fill:#9370db;font-size:10px}.relation{stroke:#9370db;stroke-width:1;fill:none}#compositionStart{fill:#9370db;stroke:#9370db;stroke-width:1}#compositionEnd{fill:#9370db;stroke:#9370db;stroke-width:1}#aggregationStart{fill:#ECECFF;stroke:#9370db;stroke-width:1}#aggregationEnd{fill:#ECECFF;stroke:#9370db;stroke-width:1}#dependencyStart{fill:#9370db;stroke:#9370db;stroke-width:1}#dependencyEnd{fill:#9370db;stroke:#9370db;stroke-width:1}#extensionStart{fill:#9370db;stroke:#9370db;stroke-width:1}#extensionEnd{fill:#9370db;stroke:#9370db;stroke-width:1}.commit-id,.commit-msg,.branch-label{fill:lightgrey;color:lightgrey;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}.pieTitleText{text-anchor:middle;font-size:25px;fill:#000;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}.slice{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}g.stateGroup text{fill:#9370db;stroke:none;font-size:10px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}g.stateGroup text{fill:#9370db;stroke:none;font-size:10px}g.stateGroup .state-title{font-weight:bolder;fill:#000}g.stateGroup rect{fill:#ECECFF;stroke:#9370db}g.stateGroup line{stroke:#9370db;stroke-width:1}.transition{stroke:#9370db;stroke-width:1;fill:none}.stateGroup .composit{fill:white;border-bottom:1px}.state-note{stroke:#aa3;fill:#fff5ad}.state-note text{fill:black;stroke:none;font-size:10px}.stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5}.stateLabel text{fill:#000;font-size:10px;font-weight:bold;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}:root{--mermaid-font-family: '\"trebuchet ms\", verdana, arial';--mermaid-font-family: \"Comic Sans MS\", \"Comic Sans\", cursive}\n",""])},function(t,e,n){var r=n(506);t.exports="string"==typeof r?r:r.toString()},function(t,e,n){(t.exports=n(83)(!1)).push([t.i,".label{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);color:#333}.label text{fill:#333}.node rect,.node circle,.node ellipse,.node polygon{fill:#cde498;stroke:#13540c;stroke-width:1px}.node .label{text-align:center}.node.clickable{cursor:pointer}.arrowheadPath{fill:green}.edgePath .path{stroke:green;stroke-width:1.5px}.edgeLabel{background-color:#e8e8e8;text-align:center}.cluster rect{fill:#cdffb2;stroke:#6eaa49;stroke-width:1px}.cluster text{fill:#333}div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);font-size:12px;background:#cdffb2;border:1px solid #6eaa49;border-radius:2px;pointer-events:none;z-index:100}.actor{stroke:#13540c;fill:#cde498}text.actor{fill:#000;stroke:none}.actor-line{stroke:grey}.messageLine0{stroke-width:1.5;stroke-dasharray:'2 2';stroke:#333}.messageLine1{stroke-width:1.5;stroke-dasharray:'2 2';stroke:#333}#arrowhead{fill:#333}.sequenceNumber{fill:#fff}#sequencenumber{fill:#333}#crosshead path{fill:#333 !important;stroke:#333 !important}.messageText{fill:#333;stroke:none}.labelBox{stroke:#326932;fill:#cde498}.labelText{fill:#000;stroke:none}.loopText{fill:#000;stroke:none}.loopLine{stroke-width:2;stroke-dasharray:'2 2';stroke:#326932}.note{stroke:#6eaa49;fill:#fff5ad}.noteText{fill:black;stroke:none;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);font-size:14px}.activation0{fill:#f4f4f4;stroke:#666}.activation1{fill:#f4f4f4;stroke:#666}.activation2{fill:#f4f4f4;stroke:#666}.mermaid-main-font{font-family:\"trebuchet ms\", verdana, arial;font-family:var(--mermaid-font-family)}.section{stroke:none;opacity:0.2}.section0{fill:#6eaa49}.section2{fill:#6eaa49}.section1,.section3{fill:#fff;opacity:0.2}.sectionTitle0{fill:#333}.sectionTitle1{fill:#333}.sectionTitle2{fill:#333}.sectionTitle3{fill:#333}.sectionTitle{text-anchor:start;font-size:11px;text-height:14px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}.grid .tick{stroke:#d3d3d3;opacity:0.3;shape-rendering:crispEdges}.grid .tick text{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}.grid path{stroke-width:0}.today{fill:none;stroke:red;stroke-width:2px}.task{stroke-width:2}.taskText{text-anchor:middle;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}.taskText:not([font-size]){font-size:11px}.taskTextOutsideRight{fill:#000;text-anchor:start;font-size:11px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}.taskTextOutsideLeft{fill:#000;text-anchor:end;font-size:11px}.task.clickable{cursor:pointer}.taskText.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}.taskTextOutsideLeft.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}.taskTextOutsideRight.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}.taskText0,.taskText1,.taskText2,.taskText3{fill:#fff}.task0,.task1,.task2,.task3{fill:#487e3a;stroke:#13540c}.taskTextOutside0,.taskTextOutside2{fill:#000}.taskTextOutside1,.taskTextOutside3{fill:#000}.active0,.active1,.active2,.active3{fill:#cde498;stroke:#13540c}.activeText0,.activeText1,.activeText2,.activeText3{fill:#000 !important}.done0,.done1,.done2,.done3{stroke:grey;fill:#d3d3d3;stroke-width:2}.doneText0,.doneText1,.doneText2,.doneText3{fill:#000 !important}.crit0,.crit1,.crit2,.crit3{stroke:#f88;fill:red;stroke-width:2}.activeCrit0,.activeCrit1,.activeCrit2,.activeCrit3{stroke:#f88;fill:#cde498;stroke-width:2}.doneCrit0,.doneCrit1,.doneCrit2,.doneCrit3{stroke:#f88;fill:#d3d3d3;stroke-width:2;cursor:pointer;shape-rendering:crispEdges}.milestone{transform:rotate(45deg) scale(0.8, 0.8)}.milestoneText{font-style:italic}.doneCritText0,.doneCritText1,.doneCritText2,.doneCritText3{fill:#000 !important}.activeCritText0,.activeCritText1,.activeCritText2,.activeCritText3{fill:#000 !important}.titleText{text-anchor:middle;font-size:18px;fill:#000;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}g.classGroup text{fill:#13540c;stroke:none;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);font-size:10px}g.classGroup text .title{font-weight:bolder}g.classGroup rect{fill:#cde498;stroke:#13540c}g.classGroup line{stroke:#13540c;stroke-width:1}.classLabel .box{stroke:none;stroke-width:0;fill:#cde498;opacity:0.5}.classLabel .label{fill:#13540c;font-size:10px}.relation{stroke:#13540c;stroke-width:1;fill:none}#compositionStart{fill:#13540c;stroke:#13540c;stroke-width:1}#compositionEnd{fill:#13540c;stroke:#13540c;stroke-width:1}#aggregationStart{fill:#cde498;stroke:#13540c;stroke-width:1}#aggregationEnd{fill:#cde498;stroke:#13540c;stroke-width:1}#dependencyStart{fill:#13540c;stroke:#13540c;stroke-width:1}#dependencyEnd{fill:#13540c;stroke:#13540c;stroke-width:1}#extensionStart{fill:#13540c;stroke:#13540c;stroke-width:1}#extensionEnd{fill:#13540c;stroke:#13540c;stroke-width:1}.commit-id,.commit-msg,.branch-label{fill:lightgrey;color:lightgrey;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}.pieTitleText{text-anchor:middle;font-size:25px;fill:#000;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}.slice{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}g.stateGroup text{fill:#13540c;stroke:none;font-size:10px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}g.stateGroup text{fill:#13540c;stroke:none;font-size:10px}g.stateGroup .state-title{font-weight:bolder;fill:#000}g.stateGroup rect{fill:#cde498;stroke:#13540c}g.stateGroup line{stroke:#13540c;stroke-width:1}.transition{stroke:#13540c;stroke-width:1;fill:none}.stateGroup .composit{fill:white;border-bottom:1px}.state-note{stroke:#6eaa49;fill:#fff5ad}.state-note text{fill:black;stroke:none;font-size:10px}.stateLabel .box{stroke:none;stroke-width:0;fill:#cde498;opacity:0.5}.stateLabel text{fill:#000;font-size:10px;font-weight:bold;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}:root{--mermaid-font-family: '\"trebuchet ms\", verdana, arial';--mermaid-font-family: \"Comic Sans MS\", \"Comic Sans\", cursive}\n",""])},function(t,e,n){var r=n(508);t.exports="string"==typeof r?r:r.toString()},function(t,e,n){(t.exports=n(83)(!1)).push([t.i,".label{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);color:#333}.label text{fill:#333}.node rect,.node circle,.node ellipse,.node polygon{fill:#eee;stroke:#999;stroke-width:1px}.node .label{text-align:center}.node.clickable{cursor:pointer}.arrowheadPath{fill:#333}.edgePath .path{stroke:#666;stroke-width:1.5px}.edgeLabel{background-color:#fff;text-align:center}.cluster rect{fill:#eaf2fb;stroke:#26a;stroke-width:1px}.cluster text{fill:#333}div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);font-size:12px;background:#eaf2fb;border:1px solid #26a;border-radius:2px;pointer-events:none;z-index:100}.actor{stroke:#999;fill:#eee}text.actor{fill:#333;stroke:none}.actor-line{stroke:#666}.messageLine0{stroke-width:1.5;stroke-dasharray:'2 2';stroke:#333}.messageLine1{stroke-width:1.5;stroke-dasharray:'2 2';stroke:#333}#arrowhead{fill:#333}.sequenceNumber{fill:#fff}#sequencenumber{fill:#333}#crosshead path{fill:#333 !important;stroke:#333 !important}.messageText{fill:#333;stroke:none}.labelBox{stroke:#999;fill:#eee}.labelText{fill:#333;stroke:none}.loopText{fill:#333;stroke:none}.loopLine{stroke-width:2;stroke-dasharray:'2 2';stroke:#999}.note{stroke:#770;fill:#ffa}.noteText{fill:black;stroke:none;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);font-size:14px}.activation0{fill:#f4f4f4;stroke:#666}.activation1{fill:#f4f4f4;stroke:#666}.activation2{fill:#f4f4f4;stroke:#666}.mermaid-main-font{font-family:\"trebuchet ms\", verdana, arial;font-family:var(--mermaid-font-family)}.section{stroke:none;opacity:0.2}.section0{fill:#80b3e6}.section2{fill:#80b3e6}.section1,.section3{fill:#fff;opacity:0.2}.sectionTitle0{fill:#333}.sectionTitle1{fill:#333}.sectionTitle2{fill:#333}.sectionTitle3{fill:#333}.sectionTitle{text-anchor:start;font-size:11px;text-height:14px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}.grid .tick{stroke:#e6e6e6;opacity:0.3;shape-rendering:crispEdges}.grid .tick text{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}.grid path{stroke-width:0}.today{fill:none;stroke:#d42;stroke-width:2px}.task{stroke-width:2}.taskText{text-anchor:middle;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}.taskText:not([font-size]){font-size:11px}.taskTextOutsideRight{fill:#333;text-anchor:start;font-size:11px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}.taskTextOutsideLeft{fill:#333;text-anchor:end;font-size:11px}.task.clickable{cursor:pointer}.taskText.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}.taskTextOutsideLeft.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}.taskTextOutsideRight.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}.taskText0,.taskText1,.taskText2,.taskText3{fill:#fff}.task0,.task1,.task2,.task3{fill:#26a;stroke:#1a4d80}.taskTextOutside0,.taskTextOutside2{fill:#333}.taskTextOutside1,.taskTextOutside3{fill:#333}.active0,.active1,.active2,.active3{fill:#eee;stroke:#1a4d80}.activeText0,.activeText1,.activeText2,.activeText3{fill:#333 !important}.done0,.done1,.done2,.done3{stroke:#666;fill:#bbb;stroke-width:2}.doneText0,.doneText1,.doneText2,.doneText3{fill:#333 !important}.crit0,.crit1,.crit2,.crit3{stroke:#b1361b;fill:#d42;stroke-width:2}.activeCrit0,.activeCrit1,.activeCrit2,.activeCrit3{stroke:#b1361b;fill:#eee;stroke-width:2}.doneCrit0,.doneCrit1,.doneCrit2,.doneCrit3{stroke:#b1361b;fill:#bbb;stroke-width:2;cursor:pointer;shape-rendering:crispEdges}.milestone{transform:rotate(45deg) scale(0.8, 0.8)}.milestoneText{font-style:italic}.doneCritText0,.doneCritText1,.doneCritText2,.doneCritText3{fill:#333 !important}.activeCritText0,.activeCritText1,.activeCritText2,.activeCritText3{fill:#333 !important}.titleText{text-anchor:middle;font-size:18px;fill:#333;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}g.classGroup text{fill:#999;stroke:none;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);font-size:10px}g.classGroup text .title{font-weight:bolder}g.classGroup rect{fill:#eee;stroke:#999}g.classGroup line{stroke:#999;stroke-width:1}.classLabel .box{stroke:none;stroke-width:0;fill:#eee;opacity:0.5}.classLabel .label{fill:#999;font-size:10px}.relation{stroke:#999;stroke-width:1;fill:none}#compositionStart{fill:#999;stroke:#999;stroke-width:1}#compositionEnd{fill:#999;stroke:#999;stroke-width:1}#aggregationStart{fill:#eee;stroke:#999;stroke-width:1}#aggregationEnd{fill:#eee;stroke:#999;stroke-width:1}#dependencyStart{fill:#999;stroke:#999;stroke-width:1}#dependencyEnd{fill:#999;stroke:#999;stroke-width:1}#extensionStart{fill:#999;stroke:#999;stroke-width:1}#extensionEnd{fill:#999;stroke:#999;stroke-width:1}.commit-id,.commit-msg,.branch-label{fill:lightgrey;color:lightgrey;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}.pieTitleText{text-anchor:middle;font-size:25px;fill:#333;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}.slice{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}g.stateGroup text{fill:#999;stroke:none;font-size:10px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}g.stateGroup text{fill:#999;stroke:none;font-size:10px}g.stateGroup .state-title{font-weight:bolder;fill:#000}g.stateGroup rect{fill:#eee;stroke:#999}g.stateGroup line{stroke:#999;stroke-width:1}.transition{stroke:#999;stroke-width:1;fill:none}.stateGroup .composit{fill:white;border-bottom:1px}.state-note{stroke:#770;fill:#ffa}.state-note text{fill:black;stroke:none;font-size:10px}.stateLabel .box{stroke:none;stroke-width:0;fill:#eee;opacity:0.5}.stateLabel text{fill:#000;font-size:10px;font-weight:bold;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}:root{--mermaid-font-family: '\"trebuchet ms\", verdana, arial';--mermaid-font-family: \"Comic Sans MS\", \"Comic Sans\", cursive}\n",""])},function(t,e,n){"use strict";n.r(e);var r=n(226),i=n.n(r),o=n(0),a=n(227),s=n.n(a),u=n(88);let c={};const f=t=>{!function(t){const e=Object.keys(t);for(let n=0;nc;var h=n(23),d=n.n(h);const p=1,g=2,y=3,b=4,m=5,v={debug:()=>{},info:()=>{},warn:()=>{},error:()=>{},fatal:()=>{}},_=function(t){v.debug=()=>{},v.info=()=>{},v.warn=()=>{},v.error=()=>{},v.fatal=()=>{},t<=m&&(v.fatal=console.log.bind(console,"",w("FATAL"))),t<=b&&(v.error=console.log.bind(console,"",w("ERROR"))),t<=y&&(v.warn=console.log.bind(console,"",w("WARN"))),t<=g&&(v.info=console.log.bind(console,"",w("INFO"))),t<=p&&(v.debug=console.log.bind(console,"",w("DEBUG")))},w=t=>{return`${d()().format("HH:mm:ss.SSS")} : ${t} : `},x=(t,e)=>{if(!t)return e;const n=`curve${t.charAt(0).toUpperCase()+t.slice(1)}`;return o[n]||e},k=(t,e)=>t&&e?Math.sqrt(Math.pow(e.x-t.x,2)+Math.pow(e.y-t.y,2)):0;var E={detectType:function(t){return t=t.replace(/^\s*%%.*\n/g,"\n"),v.debug("Detecting diagram type based on the text "+t),t.match(/^\s*sequenceDiagram/)?"sequence":t.match(/^\s*gantt/)?"gantt":t.match(/^\s*classDiagram/)?"class":t.match(/^\s*stateDiagram/)?"state":t.match(/^\s*gitGraph/)?"git":t.match(/^\s*info/)?"info":t.match(/^\s*pie/)?"pie":"flowchart"},isSubstringInArray:function(t,e){for(let n=0;n{return(t=>{let e,n=0;t.forEach(t=>{n+=k(t,e),e=t});let r,i=n/2;return e=void 0,t.forEach(t=>{if(e&&!r){const n=k(t,e);if(n=1&&(r={x:t.x,y:t.y}),o>0&&o<1&&(r={x:(1-o)*e.x+o*t.x,y:(1-o)*e.y+o*t.y})}}e=t}),r})(t)},calcCardinalityPosition:(t,e,n)=>{let r,i=0;e[0]!==n&&(e=e.reverse()),e.forEach(t=>{i+=k(t,r),r=t});let o,a=25;r=void 0,e.forEach(t=>{if(r&&!o){const e=k(t,r);if(e=1&&(o={x:t.x,y:t.y}),n>0&&n<1&&(o={x:(1-n)*r.x+n*t.x,y:(1-n)*r.y+n*t.y})}}r=t});let s=t?10:5,u=Math.atan2(e[0].y-o.y,e[0].x-o.x),c={x:0,y:0};return c.x=Math.sin(u)*s+(e[0].x+o.x)/2,c.y=-Math.cos(u)*s+(e[0].y+o.y)/2,c}},A=n(22),S=n.n(A),T=n(84);const M=l();let D,C={},O=[],R=[],I=[],N={},B={},L=0,P=!0,F=[];const q=t=>{let e=t;return"loose"!==M.securityLevel&&(e=(e=(e=(e=(e=e.replace(/
/g,"#br#")).replace(//g,"#br#")).replace(//g,">")).replace(/=/g,"=")).replace(/#br#/g,"
")),e},j=function(t,e){t.split(",").forEach((function(t){let n=t;t[0].match(/\d/)&&(n=""+n),void 0!==C[n]&&C[n].classes.push(e),void 0!==N[n]&&N[n].classes.push(e)}))},U=function(t,e){t.split(",").forEach((function(t){void 0!==e&&(B[t]=q(e))}))},z=function(t){let e=o.select(".mermaidTooltip");null===(e._groups||e)[0][0]&&(e=o.select("body").append("div").attr("class","mermaidTooltip").style("opacity",0)),o.select(t).select("svg").selectAll("g.node").on("mouseover",(function(){const t=o.select(this);if(null===t.attr("title"))return;const n=this.getBoundingClientRect();e.transition().duration(200).style("opacity",".9"),e.html(t.attr("title")).style("left",n.left+(n.right-n.left)/2+"px").style("top",n.top-14+document.body.scrollTop+"px"),t.classed("hover",!0)})).on("mouseout",(function(){e.transition().duration(500).style("opacity",0),o.select(this).classed("hover",!1)}))};F.push(z);const Y=function(t){for(let e=0;e2e3)return;if(H[V]=e,I[e].id===t)return{result:!0,count:0};let r=0,i=1;for(;r=0){const n=$(t,e);if(n.result)return{result:!0,count:i+n.count};i+=n.count}r+=1}return{result:!1,count:i}};var G={addVertex:function(t,e,n,r,i){let o,a=t;void 0!==a&&0!==a.trim().length&&(a[0].match(/\d/)&&(a=""+a),void 0===C[a]&&(C[a]={id:a,styles:[],classes:[]}),void 0!==e?('"'===(o=q(e.trim()))[0]&&'"'===o[o.length-1]&&(o=o.substring(1,o.length-1)),C[a].text=o):C[a].text||(C[a].text=t),void 0!==n&&(C[a].type=n),null!=r&&r.forEach((function(t){C[a].styles.push(t)})),null!=i&&i.forEach((function(t){C[a].classes.push(t)})))},addLink:function(t,e,n,r){let i=t,o=e;i[0].match(/\d/)&&(i=""+i),o[0].match(/\d/)&&(o=""+o),v.info("Got edge...",i,o);const a={start:i,end:o,type:void 0,text:""};void 0!==(r=n.text)&&(a.text=q(r.trim()),'"'===a.text[0]&&'"'===a.text[a.text.length-1]&&(a.text=a.text.substring(1,a.text.length-1))),void 0!==n&&(a.type=n.type,a.stroke=n.stroke),O.push(a)},updateLinkInterpolate:function(t,e){t.forEach((function(t){"default"===t?O.defaultInterpolate=e:O[t].interpolate=e}))},updateLink:function(t,e){t.forEach((function(t){"default"===t?O.defaultStyle=e:(-1===E.isSubstringInArray("fill",e)&&e.push("fill:none"),O[t].style=e)}))},addClass:function(t,e){void 0===R[t]&&(R[t]={id:t,styles:[]}),null!=e&&e.forEach((function(e){R[t].styles.push(e)}))},setDirection:function(t){(D=t).match(/.*/)&&(D="LR"),D.match(/.*v/)&&(D="TB")},setClass:j,getTooltip:function(t){return B[t]},setClickEvent:function(t,e,n){t.split(",").forEach((function(t){!function(t,e){let n=t;t[0].match(/\d/)&&(n=""+n),"loose"===M.securityLevel&&void 0!==e&&void 0!==C[n]&&F.push((function(){const t=document.querySelector(`[id="${n}"]`);null!==t&&t.addEventListener("click",(function(){window[e](n)}),!1)}))}(t,e)})),U(t,n),j(t,"clickable")},setLink:function(t,e,n){t.split(",").forEach((function(t){let n=t;t[0].match(/\d/)&&(n=""+n),void 0!==C[n]&&("loose"!==M.securityLevel?C[n].link=Object(T.sanitizeUrl)(e):C[n].link=e)})),U(t,n),j(t,"clickable")},bindFunctions:function(t){F.forEach((function(e){e(t)}))},getDirection:function(){return D.trim()},getVertices:function(){return C},getEdges:function(){return O},getClasses:function(){return R},clear:function(){C={},R={},O=[],(F=[]).push(z),I=[],N={},L=0,B=[],P=!0},defaultStyle:function(){return"fill:#ffa;stroke: #f66; stroke-width: 3px; stroke-dasharray: 5, 5;fill:#ffa;stroke: #666;"},addSubGraph:function(t,e,n){let r=t,i=n;t===n&&n.match(/\s/)&&(r=void 0);let o=[];o=function(t){const e={boolean:{},number:{},string:{}},n=[];return t.filter((function(t){const r=typeof t;return""!==t.trim()&&(r in e?!e[r].hasOwnProperty(t)&&(e[r][t]=!0):!(n.indexOf(t)>=0)&&n.push(t))}))}(o.concat.apply(o,e));for(let t=0;t0&&$("none",I.length-1,0)},getSubGraphs:function(){return I},lex:{firstGraph:()=>!!P&&(P=!1,!0)}},W=n(60),K=n.n(W),X=n(17),Z=n.n(X),J=n(128),Q=n.n(J);function tt(t,e,n){const r=.9*(e.width+e.height),i=[{x:r/2,y:0},{x:r,y:-r/2},{x:r/2,y:-r},{x:0,y:-r/2}],o=ut(t,r,r,i);return n.intersect=function(t){return Z.a.intersect.polygon(n,i,t)},o}function et(t,e,n){const r=e.height,i=r/4,o=e.width+2*i,a=[{x:i,y:0},{x:o-i,y:0},{x:o,y:-r/2},{x:o-i,y:-r},{x:i,y:-r},{x:0,y:-r/2}],s=ut(t,o,r,a);return n.intersect=function(t){return Z.a.intersect.polygon(n,a,t)},s}function nt(t,e,n){const r=e.width,i=e.height,o=[{x:-i/2,y:0},{x:r,y:0},{x:r,y:-i},{x:-i/2,y:-i},{x:0,y:-i/2}],a=ut(t,r,i,o);return n.intersect=function(t){return Z.a.intersect.polygon(n,o,t)},a}function rt(t,e,n){const r=e.width,i=e.height,o=[{x:-2*i/6,y:0},{x:r-i/6,y:0},{x:r+2*i/6,y:-i},{x:i/6,y:-i}],a=ut(t,r,i,o);return n.intersect=function(t){return Z.a.intersect.polygon(n,o,t)},a}function it(t,e,n){const r=e.width,i=e.height,o=[{x:2*i/6,y:0},{x:r+i/6,y:0},{x:r-2*i/6,y:-i},{x:-i/6,y:-i}],a=ut(t,r,i,o);return n.intersect=function(t){return Z.a.intersect.polygon(n,o,t)},a}function ot(t,e,n){const r=e.width,i=e.height,o=[{x:-2*i/6,y:0},{x:r+2*i/6,y:0},{x:r-i/6,y:-i},{x:i/6,y:-i}],a=ut(t,r,i,o);return n.intersect=function(t){return Z.a.intersect.polygon(n,o,t)},a}function at(t,e,n){const r=e.width,i=e.height,o=[{x:i/6,y:0},{x:r-i/6,y:0},{x:r+2*i/6,y:-i},{x:-2*i/6,y:-i}],a=ut(t,r,i,o);return n.intersect=function(t){return Z.a.intersect.polygon(n,o,t)},a}function st(t,e,n){const r=e.width,i=e.height,o=[{x:0,y:0},{x:r+i/2,y:0},{x:r,y:-i/2},{x:r+i/2,y:-i},{x:0,y:-i}],a=ut(t,r,i,o);return n.intersect=function(t){return Z.a.intersect.polygon(n,o,t)},a}function ut(t,e,n,r){return t.insert("polygon",":first-child").attr("points",r.map((function(t){return t.x+","+t.y})).join(" ")).attr("transform","translate("+-e/2+","+n/2+")")}var ct={addToRender:function(t){t.shapes().question=tt,t.shapes().hexagon=et,t.shapes().rect_left_inv_arrow=nt,t.shapes().lean_right=rt,t.shapes().lean_left=it,t.shapes().trapezoid=ot,t.shapes().inv_trapezoid=at,t.shapes().rect_right_inv_arrow=st}};const ft={},lt=function(t,e,n){const r=o.select(`[id="${n}"]`),i=Object.keys(t),a=function(t,e,{label:n}){if(n)for(let n=0;n0&&(o=i.classes.join(" "));let s="";s=a(s,i.styles,{label:!1});let u="";u=a(u,i.styles,{label:!0});let c,f=void 0!==i.text?i.text:i.id;if(l().flowchart.htmlLabels){const t={label:f.replace(/fa[lrsb]?:fa-[\w-]+/g,t=>``)};(c=Q()(r,t).node()).parentNode.removeChild(c)}else{const t=document.createElementNS("http://www.w3.org/2000/svg","text"),e=f.split(//);for(let n=0;n'+i.text+"
"):(a.labelType="text",a.style=a.style||"stroke: #333; stroke-width: 1.5px;fill:none",a.label=i.text.replace(/
/g,"\n"))):a.label=i.text.replace(/
/g,"\n")),e.setEdge(i.start,i.end,a,r)}))};var dt=function(t){const e=Object.keys(t);for(let n=0;n=0;t--)i=s[t],G.addVertex(i.id,i.title,"group",void 0,i.classes);const u=G.getVertices(),c=G.getEdges();let f=0;for(f=s.length-1;f>=0;f--){i=s[f],o.selectAll("cluster").append("text");for(let t=0;t/gi," "),r=t.append("text");r.attr("x",e.x),r.attr("y",e.y),r.style("text-anchor",e.anchor),r.attr("fill",e.fill),void 0!==e.class&&r.attr("class",e.class);const i=r.append("tspan");return i.attr("x",e.x+2*e.textMargin),i.attr("fill",e.fill),i.text(n),r},mt=function(t,e){const n=t.append("polygon");var r,i,o,a,s;n.attr("points",(r=e.x,i=e.y,r+","+i+" "+(r+(o=50))+","+i+" "+(r+o)+","+(i+(a=20)-(s=7))+" "+(r+o-1.2*s)+","+(i+a)+" "+r+","+(i+a))),n.attr("class","labelBox"),e.y=e.y+e.labelMargin,e.x=e.x+.5*e.labelMargin,bt(t,e)};let vt=-1;const _t=function(){return{x:0,y:0,fill:void 0,"text-anchor":"start",style:"#666",width:100,height:100,textMargin:0,rx:0,ry:0}},wt=function(){return{x:0,y:0,fill:"#EDF2AE",stroke:"#666",width:100,anchor:"start",height:100,rx:0,ry:0}},xt=function(){function t(t,e,n,i,o,a,s){r(e.append("text").attr("x",n+o/2).attr("y",i+a/2+5).style("text-anchor","middle").text(t),s)}function e(t,e,n,i,o,a,s,u){const{actorFontSize:c,actorFontFamily:f}=u,l=t.split(//gi);for(let t=0;t{let o=0;const a=t.split(//gi);for(const t of a){const a=kt.getTextObj();a.x=e,a.y=n+o,a.textMargin=Pt.noteMargin,a.dy="1em",a.text=t,a.class="noteText";const s=kt.drawText(r,a,i);o+=(s._groups||s)[0][0].getBBox().height}return o})(r.message,e-4,n+24,a,o.width-Pt.noteMargin);Ft.insert(e,n,e+o.width,n+2*Pt.noteMargin+u),s.attr("height",u+2*Pt.noteMargin),Ft.bumpVerticalPos(u+2*Pt.noteMargin)},jt=function(t,e,n,r){for(let i=0;ie&&(r.starty=e-6,e+=12),kt.drawActivation(n,r,e,Pt,Ut(t.from.actor).length),Ft.insert(r.startx,e-10,r.stopx,e)}(t,Ft.getVerticalPos());break;case Et.parser.yy.LINETYPE.LOOP_START:Ft.bumpVerticalPos(Pt.boxMargin),Ft.newLoop(t.message),Ft.bumpVerticalPos(Pt.boxMargin+Pt.boxTextMargin);break;case Et.parser.yy.LINETYPE.LOOP_END:e=Ft.endLoop(),kt.drawLoop(n,e,"loop",Pt),Ft.bumpVerticalPos(Pt.boxMargin);break;case Et.parser.yy.LINETYPE.RECT_START:Ft.bumpVerticalPos(Pt.boxMargin),Ft.newLoop(void 0,t.message),Ft.bumpVerticalPos(Pt.boxMargin);break;case Et.parser.yy.LINETYPE.RECT_END:{const t=Ft.endLoop();kt.drawBackgroundRect(n,t),Ft.bumpVerticalPos(Pt.boxMargin);break}case Et.parser.yy.LINETYPE.OPT_START:Ft.bumpVerticalPos(Pt.boxMargin),Ft.newLoop(t.message),Ft.bumpVerticalPos(Pt.boxMargin+Pt.boxTextMargin);break;case Et.parser.yy.LINETYPE.OPT_END:e=Ft.endLoop(),kt.drawLoop(n,e,"opt",Pt),Ft.bumpVerticalPos(Pt.boxMargin);break;case Et.parser.yy.LINETYPE.ALT_START:Ft.bumpVerticalPos(Pt.boxMargin),Ft.newLoop(t.message),Ft.bumpVerticalPos(Pt.boxMargin+Pt.boxTextMargin);break;case Et.parser.yy.LINETYPE.ALT_ELSE:Ft.bumpVerticalPos(Pt.boxMargin),e=Ft.addSectionToLoop(t.message),Ft.bumpVerticalPos(Pt.boxMargin);break;case Et.parser.yy.LINETYPE.ALT_END:e=Ft.endLoop(),kt.drawLoop(n,e,"alt",Pt),Ft.bumpVerticalPos(Pt.boxMargin);break;case Et.parser.yy.LINETYPE.PAR_START:Ft.bumpVerticalPos(Pt.boxMargin),Ft.newLoop(t.message),Ft.bumpVerticalPos(Pt.boxMargin+Pt.boxTextMargin);break;case Et.parser.yy.LINETYPE.PAR_AND:Ft.bumpVerticalPos(Pt.boxMargin),e=Ft.addSectionToLoop(t.message),Ft.bumpVerticalPos(Pt.boxMargin);break;case Et.parser.yy.LINETYPE.PAR_END:e=Ft.endLoop(),kt.drawLoop(n,e,"par",Pt),Ft.bumpVerticalPos(Pt.boxMargin);break;default:try{Ft.bumpVerticalPos(Pt.messageMargin);const e=zt(t.from),o=zt(t.to),a=e[0]<=o[0]?1:0,s=e[0]/gi);for(const t of f)u=a.append("text").attr("x",s).attr("y",r-7+17*c).style("text-anchor","middle").attr("class","messageText").text(t.trim()),c++;const l=17*(c-1);let h,d=(u._groups||u)[0][0].getBBox().width;if(e===n){h=Pt.rightAngles?a.append("path").attr("d",`M ${e},${r+l} H ${e+Pt.width/2} V ${r+25+l} H ${e}`):a.append("path").attr("d","M "+e+","+(r+l)+" C "+(e+60)+","+(r-10+l)+" "+(e+60)+","+(r+30+l)+" "+e+","+(r+20+l)),Ft.bumpVerticalPos(30+l);const t=Math.max(d/2,100);Ft.insert(e-t,Ft.getVerticalPos()-10+l,n+t,Ft.getVerticalPos()+l)}else(h=a.append("line")).attr("x1",e),h.attr("y1",r),h.attr("x2",n),h.attr("y2",r),Ft.insert(e,Ft.getVerticalPos()-10+l,n,Ft.getVerticalPos()+l);i.type===Et.parser.yy.LINETYPE.DOTTED||i.type===Et.parser.yy.LINETYPE.DOTTED_CROSS||i.type===Et.parser.yy.LINETYPE.DOTTED_OPEN?(h.style("stroke-dasharray","3, 3"),h.attr("class","messageLine1")):h.attr("class","messageLine0");let p="";Pt.arrowMarkerAbsolute&&(p=(p=(p=window.location.protocol+"//"+window.location.host+window.location.pathname+window.location.search).replace(/\(/g,"\\(")).replace(/\)/g,"\\)")),h.attr("stroke-width",2),h.attr("stroke","black"),h.style("fill","none"),i.type!==Et.parser.yy.LINETYPE.SOLID&&i.type!==Et.parser.yy.LINETYPE.DOTTED||h.attr("marker-end","url("+p+"#arrowhead)"),i.type!==Et.parser.yy.LINETYPE.SOLID_CROSS&&i.type!==Et.parser.yy.LINETYPE.DOTTED_CROSS||h.attr("marker-end","url("+p+"#crosshead)"),Pt.showSequenceNumbers&&(h.attr("marker-start","url("+p+"#sequencenumber)"),a.append("text").attr("x",e).attr("y",r+4).attr("font-family","sans-serif").attr("font-size","12px").attr("text-anchor","middle").attr("textLength","16px").attr("class","sequenceNumber").text(o))}(n,r,i,u,t,l);const c=e.concat(o);Ft.insert(Math.min.apply(null,c),u,Math.max.apply(null,c),u)}catch(t){v.error("error while drawing message",t)}}[Et.parser.yy.LINETYPE.SOLID_OPEN,Et.parser.yy.LINETYPE.DOTTED_OPEN,Et.parser.yy.LINETYPE.SOLID,Et.parser.yy.LINETYPE.DOTTED,Et.parser.yy.LINETYPE.SOLID_CROSS,Et.parser.yy.LINETYPE.DOTTED_CROSS].includes(t.type)&&l++})),Pt.mirrorActors&&(Ft.bumpVerticalPos(2*Pt.boxMargin),jt(n,s,u,Ft.getVerticalPos()));const h=Ft.getBounds();v.debug("For line height fix Querying: #"+e+" .actor-line"),o.selectAll("#"+e+" .actor-line").attr("y2",h.stopy);let d=h.stopy-h.starty+2*Pt.diagramMarginY;Pt.mirrorActors&&(d=d-Pt.boxMargin+Pt.bottomMarginAdj);const p=h.stopx-h.startx+2*Pt.diagramMarginX;f&&n.append("text").text(f).attr("x",(h.stopx-h.startx)/2-2*Pt.diagramMarginX).attr("y",-25),Pt.useMaxWidth?(n.attr("height","100%"),n.attr("width","100%"),n.attr("style","max-width:"+p+"px;")):(n.attr("height",d),n.attr("width",p));const g=f?40:0;n.attr("viewBox",h.startx-Pt.diagramMarginX+" -"+(Pt.diagramMarginY+g)+" "+p+" "+(d+g))},Ht=n(26),$t=n.n(Ht);const Gt=l();let Wt="",Kt="",Xt=[],Zt="",Jt=[],Qt=[],te="";const ee=["active","done","crit","milestone"];let ne=[],re=!1;const ie=function(t,e,n){return t.isoWeekday()>=6&&n.indexOf("weekends")>=0||(n.indexOf(t.format("dddd").toLowerCase())>=0||n.indexOf(t.format(e.trim()))>=0)},oe=function(t,e,n){if(!n.length||t.manualEndTime)return;let r=d()(t.startTime,e,!0);r.add(1,"d");let i=d()(t.endTime,e,!0),o=ae(r,i,e,n);t.endTime=i.toDate(),t.renderEndTime=o},ae=function(t,e,n,r){let i=!1,o=null;for(;t.date()<=e.date();)i||(o=e.toDate()),(i=ie(t,n,r))&&e.add(1,"d"),t.add(1,"d");return o},se=function(t,e,n){n=n.trim();const r=/^after\s+([\d\w-]+)/.exec(n.trim());if(null!==r){const t=ye(r[1]);if(void 0===t){const t=new Date;return t.setHours(0,0,0,0),t}return t.endTime}let i=d()(n,e.trim(),!0);return i.isValid()?i.toDate():(v.debug("Invalid date:"+n),v.debug("With date format:"+e.trim()),new Date)},ue=function(t,e){if(null!==t)switch(t[2]){case"s":e.add(t[1],"seconds");break;case"m":e.add(t[1],"minutes");break;case"h":e.add(t[1],"hours");break;case"d":e.add(t[1],"days");break;case"w":e.add(t[1],"weeks")}return e.toDate()},ce=function(t,e,n,r){r=r||!1,n=n.trim();let i=d()(n,e.trim(),!0);return i.isValid()?(r&&i.add(1,"d"),i.toDate()):ue(/^([\d]+)([wdhms])/.exec(n.trim()),d()(t))};let fe=0;const le=function(t){return void 0===t?"task"+(fe+=1):t};let he,de,pe=[];const ge={},ye=function(t){const e=ge[t];return pe[e]},be=function(){const t=function(t){const e=pe[t];let n="";switch(pe[t].raw.startTime.type){case"prevTaskEnd":{const t=ye(e.prevTaskId);e.startTime=t.endTime;break}case"getStartDate":(n=se(0,Wt,pe[t].raw.startTime.startData))&&(pe[t].startTime=n)}return pe[t].startTime&&(pe[t].endTime=ce(pe[t].startTime,Wt,pe[t].raw.endTime.data,re),pe[t].endTime&&(pe[t].processed=!0,pe[t].manualEndTime=d()(pe[t].raw.endTime.data,"YYYY-MM-DD",!0).isValid(),oe(pe[t],Wt,Xt))),pe[t].processed};let e=!0;for(let n=0;n{window[e](...r)})}(t,e,n)})),me(t,"clickable")},setLink:function(t,e){let n=e;"loose"!==Gt.securityLevel&&(n=Object(T.sanitizeUrl)(e)),t.split(",").forEach((function(t){void 0!==ye(t)&&ve(t,()=>{window.open(n,"_self")})})),me(t,"clickable")},bindFunctions:function(t){ne.forEach((function(e){e(t)}))},durationToDate:ue};function we(t,e,n){let r=!0;for(;r;)r=!1,n.forEach((function(n){const i=new RegExp("^\\s*"+n+"\\s*$");t[0].match(i)&&(e[n]=!0,t.shift(1),r=!0)}))}Ht.parser.yy=_e;const xe={titleTopMargin:25,barHeight:20,barGap:4,topPadding:50,rightPadding:75,leftPadding:75,gridLineStartPadding:35,fontSize:11,fontFamily:'"Open-Sans", "sans-serif"'};let ke;var Ee=function(t){Object.keys(t).forEach((function(e){xe[e]=t[e]}))},Ae=function(t,e){Ht.parser.yy.clear(),Ht.parser.parse(t);const n=document.getElementById(e);void 0===(ke=n.parentElement.offsetWidth)&&(ke=1200),void 0!==xe.useWidth&&(ke=xe.useWidth);const r=Ht.parser.yy.getTasks(),i=r.length*(xe.barHeight+xe.barGap)+2*xe.topPadding;n.setAttribute("height","100%"),n.setAttribute("viewBox","0 0 "+ke+" "+i);const a=o.select(`[id="${e}"]`),s=o.scaleTime().domain([o.min(r,(function(t){return t.startTime})),o.max(r,(function(t){return t.endTime}))]).rangeRound([0,ke-xe.leftPadding-xe.rightPadding]);let u=[];for(let t=0;t0&&(e=t.classes.join(" "));let n=0;for(let e=0;en-e?n+o+1.5*xe.leftPadding>c?e+r-5:n+r+5:(n-e)/2+e+r})).attr("y",(function(t,r){return r*e+xe.barHeight/2+(xe.fontSize/2-2)+n})).attr("text-height",i).attr("class",(function(t){const e=s(t.startTime);let n=s(t.endTime);t.milestone&&(n=e+i);const r=this.getBBox().width;let o="";t.classes.length>0&&(o=t.classes.join(" "));let a=0;for(let e=0;en-e?n+r+1.5*xe.leftPadding>c?o+" taskTextOutsideLeft taskTextOutside"+a+" "+f:o+" taskTextOutsideRight taskTextOutside"+a+" "+f+" width-"+r:o+" taskText taskText"+a+" "+f+" width-"+r}))}(t,i,l,h,r,0,e),function(t,e){const n=[];let r=0;for(let t=0;t0))return i[1]*t/2+e;for(let a=0;a>")?n.annotations.push(t.substring(2,t.length-2)):t.endsWith(")")?n.methods.push(t):t&&n.members.push(t)}};var Re={addClass:Ce,clear:function(){Me=[],De={}},getClass:function(t){return De[t]},getClasses:function(){return De},addAnnotation:function(t,e){De[t].annotations.push(e)},getRelations:function(){return Me},addRelation:function(t){v.debug("Adding relation: "+JSON.stringify(t)),Ce(t.id1),Ce(t.id2),Me.push(t)},addMember:Oe,addMembers:function(t,e){Array.isArray(e)&&(e.reverse(),e.forEach(e=>Oe(t,e)))},cleanupLabel:function(t){return":"===t.substring(0,1)?t.substr(2).trim():t.trim()},lineType:{LINE:0,DOTTED_LINE:1},relationType:{AGGREGATION:0,EXTENSION:1,COMPOSITION:2,DEPENDENCY:3}},Ie=n(46),Ne=n.n(Ie);Ie.parser.yy=Re;let Be={},Le=0;const Pe={dividerMargin:10,padding:5,textHeight:10},Fe=function(t){const e=Object.keys(Be);for(let n=0;n "+t.w+": "+JSON.stringify(i.edge(t))),function(t,e,n){const r=function(t){switch(t){case Re.relationType.AGGREGATION:return"aggregation";case Re.relationType.EXTENSION:return"extension";case Re.relationType.COMPOSITION:return"composition";case Re.relationType.DEPENDENCY:return"dependency"}};e.points=e.points.filter(t=>!Number.isNaN(t.y));const i=e.points,a=o.line().x((function(t){return t.x})).y((function(t){return t.y})).curve(o.curveBasis),s=t.append("path").attr("d",a(i)).attr("id","edge"+qe).attr("class","relation");let u,c,f="";Pe.arrowMarkerAbsolute&&(f=(f=(f=window.location.protocol+"//"+window.location.host+window.location.pathname+window.location.search).replace(/\(/g,"\\(")).replace(/\)/g,"\\)")),"none"!==n.relation.type1&&s.attr("marker-start","url("+f+"#"+r(n.relation.type1)+"Start)"),"none"!==n.relation.type2&&s.attr("marker-end","url("+f+"#"+r(n.relation.type2)+"End)");const l=e.points.length;let h,d,p,g,y=E.calcLabelPosition(e.points);if(u=y.x,c=y.y,l%2!=0&&l>1){let t=E.calcCardinalityPosition("none"!==n.relation.type1,e.points,e.points[0]),r=E.calcCardinalityPosition("none"!==n.relation.type2,e.points,e.points[l-1]);v.debug("cardinality_1_point "+JSON.stringify(t)),v.debug("cardinality_2_point "+JSON.stringify(r)),h=t.x,d=t.y,p=r.x,g=r.y}if(void 0!==n.title){const e=t.append("g").attr("class","classLabel"),r=e.append("text").attr("class","label").attr("x",u).attr("y",c).attr("fill","red").attr("text-anchor","middle").text(n.title);window.label=r;const i=r.node().getBBox();e.insert("rect",":first-child").attr("class","box").attr("x",i.x-Pe.padding/2).attr("y",i.y-Pe.padding/2).attr("width",i.width+Pe.padding).attr("height",i.height+Pe.padding)}if(v.info("Rendering relation "+JSON.stringify(n)),void 0!==n.relationTitle1&&"none"!==n.relationTitle1){t.append("g").attr("class","cardinality").append("text").attr("class","type1").attr("x",h).attr("y",d).attr("fill","black").attr("font-size","6").text(n.relationTitle1)}if(void 0!==n.relationTitle2&&"none"!==n.relationTitle2){t.append("g").attr("class","cardinality").append("text").attr("class","type2").attr("x",p).attr("y",g).attr("fill","black").attr("font-size","6").text(n.relationTitle2)}qe++}(n,i.edge(t),i.edge(t).relation))})),n.attr("height","100%"),n.attr("width",`${1.5*i.graph().width+20}`),n.attr("viewBox","-10 -10 "+(i.graph().width+20)+" "+(i.graph().height+20))};let Ye=[];let Ve={root:{relations:[],states:{},documents:{}}},He=Ve.root,$e=0;const Ge=function(t,e,n,r,i){void 0===He.states[t]?He.states[t]={id:t,descriptions:[],type:e,doc:n,note:i}:(He.states[t].doc||(He.states[t].doc=n),He.states[t].type||(He.states[t].type=e)),r&&("string"==typeof r&&Xe(t,r.trim()),"object"==typeof r&&r.forEach(e=>Xe(t,e.trim()))),i&&(He.states[t].note=i)},We=function(){He=(Ve={root:{relations:[],states:{},documents:{}}}).root},Ke=function(t,e,n){let r=t,i=e,o="default",a="default";"[*]"===t&&(r="start"+ ++$e,o="start"),"[*]"===e&&(i="end"+$e,a="end"),Ge(r,o),Ge(i,a),He.relations.push({id1:r,id2:i,title:n})},Xe=function(t,e){const n=He.states[t];let r=e;":"===r[0]&&(r=r.substr(1).trim()),n.descriptions.push(r)};let Ze=0;var Je={addState:Ge,clear:We,getState:function(t){return He.states[t]},getStates:function(){return He.states},getRelations:function(){return He.relations},addRelation:Ke,getDividerId:()=>"divider-id-"+ ++Ze,cleanupLabel:function(t){return":"===t.substring(0,1)?t.substr(2).trim():t.trim()},lineType:{LINE:0,DOTTED_LINE:1},relationType:{AGGREGATION:0,EXTENSION:1,COMPOSITION:2,DEPENDENCY:3},logDocuments:function(){v.info("Documents = ",Ve)},getRootDoc:()=>Ye,setRootDoc:t=>{v.info("Setting root doc",t),Ye=t},extract:t=>{We(),t.forEach(t=>{"state"===t.stmt&&Ge(t.id,t.type,t.doc,t.description,t.note),"relation"===t.stmt&&Ke(t.state1.id,t.state2.id,t.description)})}},Qe=n(47),tn=n.n(Qe);const en={};var nn=(t,e)=>{en[t]=e};const rn=(t,e)=>{const n=t.append("text").attr("x",2*l().state.padding).attr("y",l().state.textHeight+1.5*l().state.padding).attr("font-size",l().state.fontSize).attr("class","state-title").text(e.descriptions[0]).node().getBBox(),r=n.height,i=t.append("text").attr("x",l().state.padding).attr("y",r+.2*l().state.padding+l().state.dividerMargin+l().state.textHeight).attr("class","state-description");let o=!0,a=!0;e.descriptions.forEach((function(t){o||(!function(t,e,n){const r=t.append("tspan").attr("x",2*l().state.padding).text(e);n||r.attr("dy",l().state.textHeight)}(i,t,a),a=!1),o=!1}));const s=t.append("line").attr("x1",l().state.padding).attr("y1",l().state.padding+r+l().state.dividerMargin/2).attr("y2",l().state.padding+r+l().state.dividerMargin/2).attr("class","descr-divider"),u=i.node().getBBox(),c=Math.max(u.width,n.width);return s.attr("x2",c+3*l().state.padding),t.insert("rect",":first-child").attr("x",l().state.padding).attr("y",l().state.padding).attr("width",c+2*l().state.padding).attr("height",u.height+r+2*l().state.padding).attr("rx",l().state.radius),t},on=(t,e)=>{const n=t.append("text").attr("x",2*l().state.padding).attr("y",l().state.titleShift).attr("font-size",l().state.fontSize).attr("class","state-title").text(e.id),r=n.node().getBBox(),i=1-l().state.textHeight,o=t.append("line").attr("x1",0).attr("y1",i).attr("y2",i).attr("class","descr-divider"),a=t.node().getBBox();return n.attr("x",a.width/2-r.width/2),o.attr("x2",a.width+l().state.padding),t.insert("rect",":first-child").attr("x",a.x).attr("y",i).attr("class","composit").attr("width",a.width+l().state.padding).attr("height",a.height+l().state.textHeight+l().state.titleShift+1).attr("rx","0"),t.insert("rect",":first-child").attr("x",a.x).attr("y",l().state.titleShift-l().state.textHeight-l().state.padding).attr("width",a.width+l().state.padding).attr("height",3*l().state.textHeight).attr("rx",l().state.radius),t.insert("rect",":first-child").attr("x",a.x).attr("y",l().state.titleShift-l().state.textHeight-l().state.padding).attr("width",a.width+l().state.padding).attr("height",a.height+3+2*l().state.textHeight).attr("rx",l().state.radius),t},an=(t,e)=>{e.attr("class","state-note");const n=e.append("rect").attr("x",0).attr("y",l().state.padding),r=e.append("g"),{textWidth:i,textHeight:o}=((t,e,n,r)=>{let i=0;const o=r.append("text");o.style("text-anchor","start"),o.attr("class","noteText");let a=t.replace(/\r\n/g,"
");const s=(a=a.replace(/\n/g,"
")).split(//gi);let u=1.25*l().state.noteMargin;for(const t of s){const r=t.trim();if(r.length>0){const t=o.append("tspan");if(t.text(r),0===u){u+=t.node().getBBox().height}i+=u,t.attr("x",e+l().state.noteMargin),t.attr("y",n+i+1.25*l().state.noteMargin)}}return{textWidth:o.node().getBBox().width,textHeight:i}})(t,0,0,r);return n.attr("height",o+2*l().state.noteMargin),n.attr("width",i+2*l().state.noteMargin),n},sn=function(t,e){const n=e.id,r={id:n,label:e.id,width:0,height:0},i=t.append("g").attr("id",n).attr("class","stateGroup");"start"===e.type&&(t=>t.append("circle").style("stroke","black").style("fill","black").attr("r",l().state.sizeUnit).attr("cx",l().state.padding+l().state.sizeUnit).attr("cy",l().state.padding+l().state.sizeUnit))(i),"end"===e.type&&(t=>(t.append("circle").style("stroke","black").style("fill","white").attr("r",l().state.sizeUnit+l().state.miniPadding).attr("cx",l().state.padding+l().state.sizeUnit+l().state.miniPadding).attr("cy",l().state.padding+l().state.sizeUnit+l().state.miniPadding),t.append("circle").style("stroke","black").style("fill","black").attr("r",l().state.sizeUnit).attr("cx",l().state.padding+l().state.sizeUnit+2).attr("cy",l().state.padding+l().state.sizeUnit+2)))(i),"fork"!==e.type&&"join"!==e.type||((t,e)=>{let n=l().state.forkWidth,r=l().state.forkHeight;if(e.parentId){let t=n;n=r,r=t}t.append("rect").style("stroke","black").style("fill","black").attr("width",n).attr("height",r).attr("x",l().state.padding).attr("y",l().state.padding)})(i,e),"note"===e.type&&an(e.note.text,i),"divider"===e.type&&(t=>t.append("line").style("stroke","grey").style("stroke-dasharray","3").attr("x1",l().state.textHeight).attr("class","divider").attr("x2",2*l().state.textHeight).attr("y1",0).attr("y2",0))(i),"default"===e.type&&0===e.descriptions.length&&((t,e)=>{const n=t.append("text").attr("x",2*l().state.padding).attr("y",l().state.textHeight+2*l().state.padding).attr("font-size",l().state.fontSize).attr("class","state-title").text(e.id),r=n.node().getBBox();t.insert("rect",":first-child").attr("x",l().state.padding).attr("y",l().state.padding).attr("width",r.width+2*l().state.padding).attr("height",r.height+2*l().state.padding).attr("rx",l().state.radius)})(i,e),"default"===e.type&&e.descriptions.length>0&&rn(i,e);const o=i.node().getBBox();return r.width=o.width+2*l().state.padding,r.height=o.height+2*l().state.padding,nn(n,r),r};let un=0;let cn;Qe.parser.yy=Je;const fn={},ln=t=>t?t.length*cn.fontSizeFactor:1,hn=t=>{if(!t)return 1;let e=t.replace(//gi,"#br#");return(e=e.replace(/\\n/g,"#br#")).split("#br#")},dn=(t,e,n)=>{const r=new S.a.Graph({compound:!0});n?r.setGraph({rankdir:"LR",compound:!0,ranker:"tight-tree",ranksep:cn.edgeLengthFactor}):r.setGraph({rankdir:"TB",compound:!0,ranksep:cn.edgeLengthFactor,ranker:"tight-tree"}),r.setDefaultEdgeLabel((function(){return{}})),Je.extract(t);const i=Je.getStates(),a=Je.getRelations(),s=Object.keys(i);for(let t=0;t{const e=t.parentElement;let n=0,r=0;e&&(e.parentElement&&(n=e.parentElement.getBBox().width),r=parseInt(e.getAttribute("data-x-shift"),10),Number.isNaN(r)&&(r=0)),t.setAttribute("x1",0-r),t.setAttribute("x2",n-r)})}else v.debug("No Node "+t+": "+JSON.stringify(r.node(t)))}));let c=u.getBBox();r.edges().forEach((function(t){void 0!==t&&void 0!==r.edge(t)&&(v.debug("Edge "+t.v+" -> "+t.w+": "+JSON.stringify(r.edge(t))),function(t,e,n){e.points=e.points.filter(t=>!Number.isNaN(t.y));const r=e.points,i=o.line().x((function(t){return t.x})).y((function(t){return t.y})).curve(o.curveBasis),a=t.append("path").attr("d",i(r)).attr("id","edge"+un).attr("class","transition");let s="";if(l().state.arrowMarkerAbsolute&&(s=(s=(s=window.location.protocol+"//"+window.location.host+window.location.pathname+window.location.search).replace(/\(/g,"\\(")).replace(/\)/g,"\\)")),a.attr("marker-end","url("+s+"#"+function(t){switch(t){case Je.relationType.AGGREGATION:return"aggregation";case Je.relationType.EXTENSION:return"extension";case Je.relationType.COMPOSITION:return"composition";case Je.relationType.DEPENDENCY:return"dependency"}}(Je.relationType.DEPENDENCY)+"End)"),void 0!==n.title){const r=t.append("g").attr("class","stateLabel"),{x:i,y:o}=E.calcLabelPosition(e.points),a=(t=>{let e=t.replace(//gi,"#br#");return(e=e.replace(/\\n/g,"#br#")).split("#br#")})(n.title);let s=0;const u=[];for(let t=0;t<=a.length;t++){const e=r.append("text").attr("text-anchor","middle").text(a[t]).attr("x",i).attr("y",o+s);if(0===s){const t=e.node().getBBox();s=t.height}u.push(e)}if(a.length>1){const t=a.length*s*.25;u.forEach((e,n)=>e.attr("y",o+n*s-t))}const c=r.node().getBBox();r.insert("rect",":first-child").attr("class","box").attr("x",c.x-l().state.padding/2).attr("y",c.y-l().state.padding/2).attr("width",c.width+l().state.padding).attr("height",c.height+l().state.padding)}un++}(e,r.edge(t),r.edge(t).relation))})),c=u.getBBox();const f={id:n||"root",label:n||"root",width:0,height:0};return f.width=c.width+2*cn.padding,f.height=c.height+2*cn.padding,v.info("Doc rendered",f,r),f};var pn=function(){},gn=function(t,e){cn=l().state,Qe.parser.yy.clear(),Qe.parser.parse(t),v.debug("Rendering diagram "+t);const n=o.select(`[id='${e}']`);n.append("defs").append("marker").attr("id","dependencyEnd").attr("refX",19).attr("refY",7).attr("markerWidth",20).attr("markerHeight",28).attr("orient","auto").append("path").attr("d","M 19,7 L9,13 L14,7 L9,1 Z"),new S.a.Graph({multigraph:!1,compound:!0,rankdir:"RL"}).setDefaultEdgeLabel((function(){return{}}));const r=Je.getRootDoc();dn(r,n);const i=cn.padding,a=n.node().getBBox(),s=a.width+2*i,u=a.height+2*i;n.attr("width",2*s),n.attr("viewBox",`${a.x-cn.padding} ${a.y-cn.padding} `+s+" "+u)},yn=n(48),bn=n.n(yn),mn=n(228),vn=n.n(mn);let _n={},wn=null,xn={master:wn},kn="master",En="LR",An=0;function Sn(){return vn()({length:7,characters:"0123456789abcdef"})}function Tn(t,e){for(v.debug("Entering isfastforwardable:",t.id,e.id);t.seq<=e.seq&&t!==e&&null!=e.parent;){if(Array.isArray(e.parent))return v.debug("In merge commit:",e.parent),Tn(t,_n[e.parent[0]])||Tn(t,_n[e.parent[1]]);e=_n[e.parent]}return v.debug(t.id,e.id),t.id===e.id}let Mn={};function Dn(t,e,n){const r=t.indexOf(e);-1===r?t.push(n):t.splice(r,1,n)}const Cn=function(){const t=Object.keys(_n).map((function(t){return _n[t]}));return t.forEach((function(t){v.debug(t.id)})),bn.a.orderBy(t,["seq"],["desc"])};var On={setDirection:function(t){En=t},setOptions:function(t){v.debug("options str",t),t=(t=t&&t.trim())||"{}";try{Mn=JSON.parse(t)}catch(t){v.error("error while parsing gitGraph options",t.message)}},getOptions:function(){return Mn},commit:function(t){const e={id:Sn(),message:t,seq:An++,parent:null==wn?null:wn.id};wn=e,_n[e.id]=e,xn[kn]=e.id,v.debug("in pushCommit "+e.id)},branch:function(t){xn[t]=null!=wn?wn.id:null,v.debug("in createBranch")},merge:function(t){const e=_n[xn[kn]],n=_n[xn[t]];if(function(t,e){return t.seq>e.seq&&Tn(e,t)}(e,n))v.debug("Already merged");else{if(Tn(e,n))xn[kn]=xn[t],wn=_n[xn[kn]];else{const e={id:Sn(),message:"merged branch "+t+" into "+kn,seq:An++,parent:[null==wn?null:wn.id,xn[t]]};wn=e,_n[e.id]=e,xn[kn]=e.id}v.debug(xn),v.debug("in mergeBranch")}},checkout:function(t){v.debug("in checkout");const e=xn[kn=t];wn=_n[e]},reset:function(t){v.debug("in reset",t);const e=t.split(":")[0];let n=parseInt(t.split(":")[1]),r="HEAD"===e?wn:_n[xn[e]];for(v.debug(r,n);n>0;)if(n--,!(r=_n[r.parent])){const t="Critical error - unique parent commit not found during reset";throw v.error(t),t}wn=r,xn[kn]=r.id},prettyPrint:function(){v.debug(_n),function t(e){const n=bn.a.maxBy(e,"seq");let r="";e.forEach((function(t){r+=t===n?"\t*":"\t|"}));const i=[r,n.id,n.seq];for(let t in xn)xn[t]===n.id&&i.push(t);if(v.debug(i.join(" ")),Array.isArray(n.parent)){const t=_n[n.parent[0]];Dn(e,n,t),e.push(_n[n.parent[1]])}else{if(null==n.parent)return;{const t=_n[n.parent];Dn(e,n,t)}}t(e=bn.a.uniqBy(e,"id"))}([Cn()[0]])},clear:function(){_n={},xn={master:wn=null},kn="master",An=0},getBranchesAsObjArray:function(){const t=[];for(let e in xn)t.push({name:e,commit:_n[xn[e]]});return t},getBranches:function(){return xn},getCommits:function(){return _n},getCommitsArray:Cn,getCurrentBranch:function(){return kn},getDirection:function(){return En},getHead:function(){return wn}},Rn=n(85),In=n.n(Rn);let Nn,Bn={},Ln={nodeSpacing:150,nodeFillColor:"yellow",nodeStrokeWidth:2,nodeStrokeColor:"grey",lineStrokeWidth:4,branchOffset:50,lineColor:"grey",leftMargin:50,branchColors:["#442f74","#983351","#609732","#AA9A39"],nodeRadius:10,nodeLabel:{width:75,height:100,x:-25,y:0}},Pn={};function Fn(t,e,n,r){const i=x(r,o.curveBasis),a=Ln.branchColors[n%Ln.branchColors.length],s=o.line().x((function(t){return Math.round(t.x)})).y((function(t){return Math.round(t.y)})).curve(i);t.append("svg:path").attr("d",s(e)).style("stroke",a).style("stroke-width",Ln.lineStrokeWidth).style("fill","none")}function qn(t,e){e=e||t.node().getBBox();const n=t.node().getCTM();return{left:n.e+e.x*n.a,top:n.f+e.y*n.d,width:e.width,height:e.height}}function jn(t,e,n,r,i){v.debug("svgDrawLineForCommits: ",e,n);const o=qn(t.select("#node-"+e+" circle")),a=qn(t.select("#node-"+n+" circle"));switch(r){case"LR":if(o.left-a.left>Ln.nodeSpacing){const e={x:o.left-Ln.nodeSpacing,y:a.top+a.height/2};Fn(t,[e,{x:a.left+a.width,y:a.top+a.height/2}],i,"linear"),Fn(t,[{x:o.left,y:o.top+o.height/2},{x:o.left-Ln.nodeSpacing/2,y:o.top+o.height/2},{x:o.left-Ln.nodeSpacing/2,y:e.y},e],i)}else Fn(t,[{x:o.left,y:o.top+o.height/2},{x:o.left-Ln.nodeSpacing/2,y:o.top+o.height/2},{x:o.left-Ln.nodeSpacing/2,y:a.top+a.height/2},{x:a.left+a.width,y:a.top+a.height/2}],i);break;case"BT":if(a.top-o.top>Ln.nodeSpacing){const e={x:a.left+a.width/2,y:o.top+o.height+Ln.nodeSpacing};Fn(t,[e,{x:a.left+a.width/2,y:a.top}],i,"linear"),Fn(t,[{x:o.left+o.width/2,y:o.top+o.height},{x:o.left+o.width/2,y:o.top+o.height+Ln.nodeSpacing/2},{x:a.left+a.width/2,y:e.y-Ln.nodeSpacing/2},e],i)}else Fn(t,[{x:o.left+o.width/2,y:o.top+o.height},{x:o.left+o.width/2,y:o.top+Ln.nodeSpacing/2},{x:a.left+a.width/2,y:a.top-Ln.nodeSpacing/2},{x:a.left+a.width/2,y:a.top}],i)}}function Un(t,e){return t.select(e).node().cloneNode(!0)}function zn(t,e,n,r){let i;const o=Object.keys(Bn).length;if("string"==typeof e)do{if(i=Bn[e],v.debug("in renderCommitHistory",i.id,i.seq),t.select("#node-"+e).size()>0)return;let a;t.append((function(){return Un(t,"#def-commit")})).attr("class","commit").attr("id",(function(){return"node-"+i.id})).attr("transform",(function(){switch(r){case"LR":return"translate("+(i.seq*Ln.nodeSpacing+Ln.leftMargin)+", "+Nn*Ln.branchOffset+")";case"BT":return"translate("+(Nn*Ln.branchOffset+Ln.leftMargin)+", "+(o-i.seq)*Ln.nodeSpacing+")"}})).attr("fill",Ln.nodeFillColor).attr("stroke",Ln.nodeStrokeColor).attr("stroke-width",Ln.nodeStrokeWidth);for(let t in n)if(n[t].commit===i){a=n[t];break}a&&(v.debug("found branch ",a.name),t.select("#node-"+i.id+" p").append("xhtml:span").attr("class","branch-label").text(a.name+", ")),t.select("#node-"+i.id+" p").append("xhtml:span").attr("class","commit-id").text(i.id),""!==i.message&&"BT"===r&&t.select("#node-"+i.id+" p").append("xhtml:span").attr("class","commit-msg").text(", "+i.message),e=i.parent}while(e&&Bn[e]);Array.isArray(e)&&(v.debug("found merge commmit",e),zn(t,e[0],n,r),Nn++,zn(t,e[1],n,r),Nn--)}function Yn(t,e,n,r){for(r=r||0;e.seq>0&&!e.lineDrawn;)"string"==typeof e.parent?(jn(t,e.id,e.parent,n,r),e.lineDrawn=!0,e=Bn[e.parent]):Array.isArray(e.parent)&&(jn(t,e.id,e.parent[0],n,r),jn(t,e.id,e.parent[1],n,r+1),Yn(t,Bn[e.parent[1]],n,r+1),e.lineDrawn=!0,e=Bn[e.parent[0]])}var Vn=function(t){Pn=t},Hn=function(t,e,n){try{const r=In.a.parser;r.yy=On,v.debug("in gitgraph renderer",t+"\n","id:",e,n),r.parse(t+"\n"),Ln=bn.a.assign(Ln,Pn,On.getOptions()),v.debug("effective options",Ln);const i=On.getDirection();Bn=On.getCommits();const a=On.getBranchesAsObjArray();"BT"===i&&(Ln.nodeLabel.x=a.length*Ln.branchOffset,Ln.nodeLabel.width="100%",Ln.nodeLabel.y=-2*Ln.nodeRadius);const s=o.select(`[id="${e}"]`);!function(t){t.append("defs").append("g").attr("id","def-commit").append("circle").attr("r",Ln.nodeRadius).attr("cx",0).attr("cy",0),t.select("#def-commit").append("foreignObject").attr("width",Ln.nodeLabel.width).attr("height",Ln.nodeLabel.height).attr("x",Ln.nodeLabel.x).attr("y",Ln.nodeLabel.y).attr("class","node-label").attr("requiredFeatures","http://www.w3.org/TR/SVG11/feature#Extensibility").append("p").html("")}(s),Nn=1;for(let t in a){const e=a[t];zn(s,e.commit.id,a,i),Yn(s,e.commit,i),Nn++}s.attr("height",(function(){return"BT"===i?Object.keys(Bn).length*Ln.nodeSpacing:(a.length+1)*Ln.branchOffset}))}catch(t){v.error("Error while rendering gitgraph"),v.error(t.message)}},$n="",Gn=!1;var Wn={setMessage:t=>{v.debug("Setting message to: "+t),$n=t},getMessage:()=>$n,setInfo:t=>{Gn=t},getInfo:()=>Gn},Kn=n(86),Xn=n.n(Kn);const Zn={};var Jn=function(t){Object.keys(t).forEach((function(e){Zn[e]=t[e]}))},Qn=(t,e,n)=>{try{const r=Xn.a.parser;r.yy=Wn,v.debug("Renering info diagram\n"+t),r.parse(t),v.debug("Parsed info diagram");const i=o.select("#"+e);i.append("g").append("text").attr("x",100).attr("y",40).attr("class","version").attr("font-size","32px").style("text-anchor","middle").text("v "+n),i.attr("height",100),i.attr("width",400)}catch(t){v.error("Error while rendering info diagram"),v.error(t.message)}};let tr={},er="";var nr={addSection:function(t,e){void 0===tr[t]&&(tr[t]=e,v.debug("Added new section :",t))},getSections:()=>tr,cleanupValue:function(t){return":"===t.substring(0,1)?(t=t.substring(1).trim(),Number(t.trim())):Number(t.trim())},clear:function(){tr={},er=""},setTitle:function(t){er=t},getTitle:function(){return er}},rr=n(87),ir=n.n(rr);const or={};let ar;var sr=function(t){Object.keys(t).forEach((function(e){or[e]=t[e]}))},ur=(t,e)=>{try{const h=ir.a.parser;h.yy=nr,v.debug("Rendering info diagram\n"+t),h.yy.clear(),h.parse(t),v.debug("Parsed info diagram");const d=document.getElementById(e);void 0===(ar=d.parentElement.offsetWidth)&&(ar=1200),void 0!==or.useWidth&&(ar=or.useWidth);const p=450;d.setAttribute("height","100%"),d.setAttribute("viewBox","0 0 "+ar+" "+p);var n=ar,r=Math.min(n,450)/2-40,i=o.select("#"+e).append("svg").attr("width",n).attr("height",450).append("g").attr("transform","translate("+n/2+",225)"),a=nr.getSections(),s=0;Object.keys(a).forEach((function(t){s+=a[t]})),v.info(a);var u=o.scaleOrdinal().domain(a).range(o.schemeSet2),c=o.pie().value((function(t){return t.value}))(o.entries(a)),f=o.arc().innerRadius(0).outerRadius(r);i.selectAll("mySlices").data(c).enter().append("path").attr("d",f).attr("fill",(function(t){return u(t.data.key)})).attr("stroke","black").style("stroke-width","2px").style("opacity",.7),i.selectAll("mySlices").data(c).enter().append("text").text((function(t){return(t.data.value/s*100).toFixed(0)+"%"})).attr("transform",(function(t){return"translate("+f.centroid(t)+")"})).style("text-anchor","middle").attr("class","slice").style("font-size",17),i.append("text").text(h.yy.getTitle()).attr("x",0).attr("y",-(p-50)/2).attr("class","pieTitleText");var l=i.selectAll(".legend").data(u.domain()).enter().append("g").attr("class","legend").attr("transform",(function(t,e){return"translate(216,"+(22*e-22*u.domain().length/2)+")"}));l.append("rect").attr("width",18).attr("height",18).style("fill",u).style("stroke",u),l.append("text").attr("x",22).attr("y",14).text((function(t){return t}))}catch(t){v.error("Error while rendering info diagram"),v.error(t.message)}};const cr={};for(const t of["default","forest","dark","neutral"])cr[t]=n(500)(`./${t}/index.scss`);const fr={theme:"default",themeCSS:void 0,fontFamily:'"trebuchet ms", verdana, arial;',logLevel:5,securityLevel:"strict",startOnLoad:!0,arrowMarkerAbsolute:!1,flowchart:{htmlLabels:!0,curve:"linear"},sequence:{diagramMarginX:50,diagramMarginY:10,actorMargin:50,width:150,height:65,boxMargin:10,boxTextMargin:5,noteMargin:10,messageMargin:35,mirrorActors:!0,bottomMarginAdj:1,useMaxWidth:!0,rightAngles:!1,showSequenceNumbers:!1},gantt:{titleTopMargin:25,barHeight:20,barGap:4,topPadding:50,leftPadding:75,gridLineStartPadding:35,fontSize:11,fontFamily:'"Open-Sans", "sans-serif"',numberSectionStyles:4,axisFormat:"%Y-%m-%d"},class:{},git:{},state:{dividerMargin:10,sizeUnit:5,padding:8,textHeight:10,titleShift:-15,noteMargin:10,forkWidth:70,forkHeight:7,miniPadding:2,fontSizeFactor:5.02,fontSize:24,labelHeight:16,edgeLengthFactor:"20",compositTitleSize:35,radius:5}};_(fr.logLevel),f(fr);const lr=function(t){const e=Object.keys(t);for(let n=0;n * { ${t[e].styles.join(" !important; ")} !important; }`}const h=document.createElement("style");h.innerHTML=s()(l,`#${t}`),c.insertBefore(h,f);const d=document.createElement("style"),p=window.getComputedStyle(c);switch(d.innerHTML=`#${t} {\n color: ${p.color};\n font: ${p.font};\n }`,c.insertBefore(d,f),a){case"git":fr.flowchart.arrowMarkerAbsolute=fr.arrowMarkerAbsolute,Vn(fr.git),Hn(e,t,!1);break;case"flowchart":fr.flowchart.arrowMarkerAbsolute=fr.arrowMarkerAbsolute,dt(fr.flowchart),gt(e,t,!1);break;case"sequence":fr.sequence.arrowMarkerAbsolute=fr.arrowMarkerAbsolute,fr.sequenceDiagram?(Yt(Object.assign(fr.sequence,fr.sequenceDiagram)),console.error("`mermaid config.sequenceDiagram` has been renamed to `config.sequence`. Please update your mermaid config.")):Yt(fr.sequence),Vt(e,t);break;case"gantt":fr.gantt.arrowMarkerAbsolute=fr.arrowMarkerAbsolute,Ee(fr.gantt),Ae(e,t);break;case"class":fr.class.arrowMarkerAbsolute=fr.arrowMarkerAbsolute,Ue(fr.class),ze(e,t);break;case"state":pn(fr.state),gn(e,t);break;case"info":fr.class.arrowMarkerAbsolute=fr.arrowMarkerAbsolute,Jn(fr.class),Qn(e,t,u.version);break;case"pie":fr.class.arrowMarkerAbsolute=fr.arrowMarkerAbsolute,sr(fr.class),ur(e,t,u.version)}o.select(`[id="${t}"]`).selectAll("foreignobject > *").attr("xmlns","http://www.w3.org/1999/xhtml");let g=o.select("#d"+t).node().innerHTML;if(fr.arrowMarkerAbsolute&&"false"!==fr.arrowMarkerAbsolute||(g=g.replace(/marker-end="url\(.*?#/g,'marker-end="url(#',"g")),g=function(t){let e=t;return e=(e=(e=e.replace(/fl°°/g,(function(){return"&#"}))).replace(/fl°/g,(function(){return"&"}))).replace(/¶ß/g,(function(){return";"}))}(g),void 0!==n)switch(a){case"flowchart":n(g,G.bindFunctions);break;case"gantt":n(g,_e.bindFunctions);break;default:n(g)}else v.debug("CB = undefined!");const y=o.select("#d"+t).node();return null!==y&&"function"==typeof y.remove&&o.select("#d"+t).node().remove(),g},parse:function(t){const e=E.detectType(t);let n;switch(v.debug("Type "+e),e){case"git":(n=In.a).parser.yy=On;break;case"flowchart":G.clear(),(n=K.a).parser.yy=G;break;case"sequence":(n=At.a).parser.yy=Lt;break;case"gantt":(n=$t.a).parser.yy=_e;break;case"class":(n=Ne.a).parser.yy=Re;break;case"state":(n=tn.a).parser.yy=Je;break;case"info":v.debug("info info info"),(n=Xn.a).parser.yy=Wn;break;case"pie":v.debug("pie"),(n=ir.a).parser.yy=nr}n.parser.yy.parseError=(t,e)=>{throw{str:t,hash:e}},n.parse(t)},initialize:function(t){v.debug("Initializing mermaidAPI ",u.version),"object"==typeof t&&lr(t),f(fr),_(fr.logLevel)},getConfig:l};const dr=function(){let t;pr.startOnLoad?(t=hr.getConfig()).startOnLoad&&pr.init():void 0===pr.startOnLoad&&(v.debug("In start, no config"),(t=hr.getConfig()).startOnLoad&&pr.init())};"undefined"!=typeof document&& +/*! + * Wait for document loaded before starting the execution + */ +window.addEventListener("load",(function(){dr()}),!1);const pr={startOnLoad:!0,htmlLabels:!0,mermaidAPI:hr,parse:hr.parse,render:hr.render,init:function(){const t=hr.getConfig();let e,n,r;v.debug("Starting rendering diagrams"),arguments.length>=2?( +/*! sequence config was passed as #1 */ +void 0!==arguments[0]&&(pr.sequenceConfig=arguments[0]),e=arguments[1]):e=arguments[0],"function"==typeof arguments[arguments.length-1]?(n=arguments[arguments.length-1],v.debug("Callback function found")):void 0!==t.mermaid&&("function"==typeof t.mermaid.callback?(n=t.mermaid.callback,v.debug("Callback function found")):v.debug("No Callback function found")),e=void 0===e?document.querySelectorAll(".mermaid"):"string"==typeof e?document.querySelectorAll(e):e instanceof window.Node?[e]:e,v.debug("Start On Load before: "+pr.startOnLoad),void 0!==pr.startOnLoad&&(v.debug("Start On Load inner: "+pr.startOnLoad),hr.initialize({startOnLoad:pr.startOnLoad})),void 0!==pr.ganttConfig&&hr.initialize({gantt:pr.ganttConfig});for(let t=0;t/gi,"
"),hr.render(a,r,(t,e)=>{o.innerHTML=t,void 0!==n&&n(a),e&&e(o)},o)}},initialize:function(t){v.debug("Initializing mermaid "),void 0!==t.mermaid&&(void 0!==t.mermaid.startOnLoad&&(pr.startOnLoad=t.mermaid.startOnLoad),void 0!==t.mermaid.htmlLabels&&(pr.htmlLabels=t.mermaid.htmlLabels)),hr.initialize(t)},contentLoaded:dr};e.default=pr}]).default})); +//# sourceMappingURL=mermaid.min.js.map \ No newline at end of file diff --git a/book/shared/tabs.js b/book/shared/tabs.js new file mode 100644 index 0000000000..a25254ed33 --- /dev/null +++ b/book/shared/tabs.js @@ -0,0 +1,97 @@ +/** + * Returns true if browser supports HTML5 localStorage. + */ +function supportsHTML5Storage() { + try { + return 'localStorage' in window && window['localStorage'] !== null; + } catch (e) { + return false; + } +} + +/** + * Event handler for when a tab is clicked. + */ +function onClickTab(event) { + let target = event.currentTarget; + let language = target.dataset.lang; + + const initialTargetOffset = target.offsetTop; + const initialScrollPosition = window.scrollY; + switchAllTabs(language); + + // Keep initial perceived scroll position after resizing + // that may happen due to activation of multiple tabs in the same page. + const finalTargetOffset = target.offsetTop; + window.scrollTo({ + top: initialScrollPosition + (finalTargetOffset - initialTargetOffset) + }); +} + +/** + * Switches the displayed tab for the given container. + * + * :param container: The div containing both the tab bar and the individual tabs + * as direct children. + * :param language: The language to switch to. + */ +function switchTab(container, language) { + const previouslyActiveTab = container.querySelector(".tabcontents .active"); + previouslyActiveTab && previouslyActiveTab.classList.remove("active"); + let tab = container.querySelector(`.tabcontents [data-lang="${language}"]`); + tab && tab.classList.add("active"); + + const previouslyActiveButton = container.querySelector(".tabbar .active"); + previouslyActiveButton && previouslyActiveButton.classList.remove("active"); + let button = container.querySelector(`.tabbar [data-lang="${language}"]`); + button && button.classList.add("active"); +} + +/** + * Switches all tabs on the page to the given language. + * + * :param language: The language to switch to. + */ +function switchAllTabs(language) { + const containers = document.getElementsByClassName("tabs"); + for (let i = 0; i < containers.length; ++i) { + switchTab(containers[i], language); + } + + if (supportsHTML5Storage()) { + localStorage.setItem("glean-preferred-language", language); + } +} + +/** + * Opens all tabs on the page to the given language on page load. + */ +function openTabs() { + if (!supportsHTML5Storage()) { + return; + } + + let containers = document.getElementsByClassName("tabs"); + for (let i = 0; i < containers.length; ++i) { + // Create tabs for every language that has content + let tabs = containers[i].children[0]; + let tabcontents = containers[i].children[1]; + for (let tabcontent of tabcontents.children) { + let button = document.createElement("button"); + button.dataset.lang = tabcontent.dataset.lang; + button.className = "tablinks"; + button.onclick = onClickTab; + button.innerText = tabcontent.dataset.lang; + tabs.appendChild(button); + } + } + + var language = localStorage.getItem("glean-preferred-language"); + if (language == null) { + language = "Kotlin"; + } + + switchAllTabs(language); +} + +openTabs() diff --git a/book/tomorrow-night.css b/book/tomorrow-night.css new file mode 100644 index 0000000000..81fe276e7f --- /dev/null +++ b/book/tomorrow-night.css @@ -0,0 +1,102 @@ +/* Tomorrow Night Theme */ +/* https://github.com/jmblog/color-themes-for-highlightjs */ +/* Original theme - https://github.com/chriskempson/tomorrow-theme */ +/* https://github.com/jmblog/color-themes-for-highlightjs */ + +/* Tomorrow Comment */ +.hljs-comment { + color: #969896; +} + +/* Tomorrow Red */ +.hljs-variable, +.hljs-attribute, +.hljs-tag, +.hljs-regexp, +.ruby .hljs-constant, +.xml .hljs-tag .hljs-title, +.xml .hljs-pi, +.xml .hljs-doctype, +.html .hljs-doctype, +.css .hljs-id, +.css .hljs-class, +.css .hljs-pseudo { + color: #cc6666; +} + +/* Tomorrow Orange */ +.hljs-number, +.hljs-preprocessor, +.hljs-pragma, +.hljs-built_in, +.hljs-literal, +.hljs-params, +.hljs-constant { + color: #de935f; +} + +/* Tomorrow Yellow */ +.ruby .hljs-class .hljs-title, +.css .hljs-rule .hljs-attribute { + color: #f0c674; +} + +/* Tomorrow Green */ +.hljs-string, +.hljs-value, +.hljs-inheritance, +.hljs-header, +.hljs-name, +.ruby .hljs-symbol, +.xml .hljs-cdata { + color: #b5bd68; +} + +/* Tomorrow Aqua */ +.hljs-title, +.css .hljs-hexcolor { + color: #8abeb7; +} + +/* Tomorrow Blue */ +.hljs-function, +.python .hljs-decorator, +.python .hljs-title, +.ruby .hljs-function .hljs-title, +.ruby .hljs-title .hljs-keyword, +.perl .hljs-sub, +.javascript .hljs-title, +.coffeescript .hljs-title { + color: #81a2be; +} + +/* Tomorrow Purple */ +.hljs-keyword, +.javascript .hljs-function { + color: #b294bb; +} + +.hljs { + display: block; + overflow-x: auto; + background: #1d1f21; + color: #c5c8c6; +} + +.coffeescript .javascript, +.javascript .xml, +.tex .hljs-formula, +.xml .javascript, +.xml .vbscript, +.xml .css, +.xml .hljs-cdata { + opacity: 0.5; +} + +.hljs-addition { + color: #718c00; +} + +.hljs-deletion { + color: #c82829; +} diff --git a/index.html b/index.html new file mode 100644 index 0000000000..bef5147d66 --- /dev/null +++ b/index.html @@ -0,0 +1 @@ + diff --git a/shared/a-s.css b/shared/a-s.css new file mode 100644 index 0000000000..0eacd2aff0 --- /dev/null +++ b/shared/a-s.css @@ -0,0 +1,169 @@ +/* Style the tab */ +.tabbar { + overflow: hidden; + border: 1px solid #ccc; + background-color: #f1f1f1; +} + +/* Style the buttons that are used to open the tab content */ +.tabbar button { + background-color: inherit; + float: left; + border: none; + outline: none; + cursor: pointer; + padding: 14px 16px; + transition: 0.3s; +} + +/* Change background color of buttons on hover */ +.tabbar button:hover { + background-color: #ddd; +} + +/* Create an active/current tablink class */ +.tabbar button.active { + background-color: #ccc; +} + +/* The container that holds all of the tab contents */ +.tabcontents { + display: flex; +} + +/* The container for each individual language */ +.tab { + display: none; + width: 100%; + border: 1px solid #ccc; + border-top: none; + padding: 6px 12px; +} + +.tab.active { + display: block; +} + +/* The footer with the "Open on GitHub" link */ +footer#open-on-gh { + font-size: 0.8em; + text-align: center; + border-top: 1px solid black; + padding: 5px 0; +} + +/* Distribution simulator styles */ + +#simulator-container { + display: flex; + flex-wrap: wrap; + overflow: hidden; +} + +#simulator-container h3 { + margin-top: 20px; +} + +#simulator-container .input-group { + width: 100%; +} + +#simulator-container .input-group label { + display: block; + font-weight: bold; + font-size: 14px; + margin-bottom: 7px; +} + +#simulator-container .input-group input, +#simulator-container .input-group select, +#custom-data-modal textarea { + display: block; + width: 100%; + padding: 5px; + margin-bottom: 10px; + border-radius: 3px; + border: 1px solid #e0e0e0; + box-sizing: border-box; +} + +#histogram-props, +#data-options { + width: 50%; + box-sizing: border-box; +} + +#data-options { + padding-right: 50px; +} + +#data-options .input-group { + margin-bottom: 10px; +} + +#data-options .input-group:first-of-type { + margin-top: 20px; +} + +#data-options .input-group:last-of-type { + margin-bottom: 0; +} + +#data-options .input-group label { + display: inline-block; +} + +#data-options .input-group input { + display: inline; + width: auto; +} + +#histogram-chart-container { + width: 100%; + padding: 30px; + border: 1px solid #e0e0e0; + margin: 30px 0; + overflow: hidden; + position: relative; +} + +#histogram-chart { + margin-top: 50px; + width: 100%; +} + +#histogram-chart-legend { + font-size: 14px; + text-align: center; + width: 100%; +} + +#histogram-functional-props, +#histogram-non-functional-props { + display: none; +} + +#custom-data-modal-overlay { + background-color: rgba(0, 0, 0, .5); + position: fixed; + width: 100vw; + height: 100vh; + top: 0; + left: 0; + z-index: 999; + display: none; +} + +#custom-data-modal { + width: 50%; + background-color: white; + border-radius: 5px; + position: relative; + top: 15%; + left: 25%; + padding: 50px; +} + +.hide { + display: none !important; +} diff --git a/shared/mermaid-init.js b/shared/mermaid-init.js new file mode 100644 index 0000000000..4da13c04b0 --- /dev/null +++ b/shared/mermaid-init.js @@ -0,0 +1,4 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +mermaid.initialize({startOnLoad:true}); diff --git a/shared/mermaid.css b/shared/mermaid.css new file mode 100644 index 0000000000..13a8ce8776 --- /dev/null +++ b/shared/mermaid.css @@ -0,0 +1,356 @@ +/* +Licensed under the MIT License (MIT). +Copyright (c) 2014 - 2018 Knut Sveidqvist +Full license: https://github.com/mermaid-js/mermaid/blob/develop/LICENSE +*/ +/* Flowchart variables */ +/* Sequence Diagram variables */ +/* Gantt chart variables */ +.mermaid .mermaid .label { + color: #333; +} +.mermaid .node rect, +.mermaid .node circle, +.mermaid .node ellipse, +.mermaid .node polygon { + fill: #ECECFF; + stroke: #CCCCFF; + stroke-width: 1px; +} +.mermaid .arrowheadPath { + fill: #333333; +} +.mermaid .edgePath .path { + stroke: #333333; +} +.mermaid .edgeLabel { + background-color: #e8e8e8; +} +.mermaid .cluster rect { + fill: #ffffde !important; + rx: 4 !important; + stroke: #aaaa33 !important; + stroke-width: 1px !important; +} +.mermaid .cluster text { + fill: #333; +} +.mermaid .actor { + stroke: #CCCCFF; + fill: #ECECFF; +} +.mermaid text.actor { + fill: black; + stroke: none; +} +.mermaid .actor-line { + stroke: grey; +} +.mermaid .messageLine0 { + stroke-width: 1.5; + stroke-dasharray: "2 2"; + marker-end: "url(#arrowhead)"; + stroke: #333; +} +.mermaid .messageLine1 { + stroke-width: 1.5; + stroke-dasharray: "2 2"; + stroke: #333; +} +.mermaid #arrowhead { + fill: #333; +} +.mermaid #crosshead path { + fill: #333 !important; + stroke: #333 !important; +} +.mermaid .messageText { + fill: #333; + stroke: none; +} +.mermaid .labelBox { + stroke: #CCCCFF; + fill: #ECECFF; +} +.mermaid .labelText { + fill: black; + stroke: none; +} +.mermaid .loopText { + fill: black; + stroke: none; +} +.mermaid .loopLine { + stroke-width: 2; + stroke-dasharray: "2 2"; + marker-end: "url(#arrowhead)"; + stroke: #CCCCFF; +} +.mermaid .note { + stroke: #aaaa33; + fill: #fff5ad; +} +.mermaid .noteText { + fill: black; + stroke: none; + font-family: 'trebuchet ms', verdana, arial; + font-size: 14px; +} +/** Section styling */ +.mermaid .section { + stroke: none; + opacity: 0.2; +} +.mermaid .section0 { + fill: rgba(102, 102, 255, 0.49); +} +.mermaid .section2 { + fill: #fff400; +} +.mermaid .section1, +.mermaid .section3 { + fill: white; + opacity: 0.2; +} +.mermaid .sectionTitle0 { + fill: #333; +} +.mermaid .sectionTitle1 { + fill: #333; +} +.mermaid .sectionTitle2 { + fill: #333; +} +.mermaid .sectionTitle3 { + fill: #333; +} +.mermaid .sectionTitle { + text-anchor: start; + font-size: 11px; + text-height: 14px; +} +/* Grid and axis */ +.mermaid .grid .tick { + stroke: lightgrey; + opacity: 0.3; + shape-rendering: crispEdges; +} +.mermaid .grid path { + stroke-width: 0; +} +/* Today line */ +.mermaid .today { + fill: none; + stroke: red; + stroke-width: 2px; +} +/* Task styling */ +/* Default task */ +.mermaid .task { + stroke-width: 2; +} +.mermaid .taskText { + text-anchor: middle; + font-size: 11px; +} +.mermaid .taskTextOutsideRight { + fill: black; + text-anchor: start; + font-size: 11px; +} +.mermaid .taskTextOutsideLeft { + fill: black; + text-anchor: end; + font-size: 11px; +} +/* Specific task settings for the sections*/ +.mermaid .taskText0, +.mermaid .taskText1, +.mermaid .taskText2, +.mermaid .taskText3 { + fill: white; +} +.mermaid .task0, +.mermaid .task1, +.mermaid .task2, +.mermaid .task3 { + fill: #8a90dd; + stroke: #534fbc; +} +.mermaid .taskTextOutside0, +.mermaid .taskTextOutside2 { + fill: black; +} +.mermaid .taskTextOutside1, +.mermaid .taskTextOutside3 { + fill: black; +} +/* Active task */ +.mermaid .active0, +.mermaid .active1, +.mermaid .active2, +.mermaid .active3 { + fill: #bfc7ff; + stroke: #534fbc; +} +.mermaid .activeText0, +.mermaid .activeText1, +.mermaid .activeText2, +.mermaid .activeText3 { + fill: black !important; +} +/* Completed task */ +.mermaid .done0, +.mermaid .done1, +.mermaid .done2, +.mermaid .done3 { + stroke: grey; + fill: lightgrey; + stroke-width: 2; +} +.mermaid .doneText0, +.mermaid .doneText1, +.mermaid .doneText2, +.mermaid .doneText3 { + fill: black !important; +} +/* Tasks on the critical line */ +.mermaid .crit0, +.mermaid .crit1, +.mermaid .crit2, +.mermaid .crit3 { + stroke: #ff8888; + fill: red; + stroke-width: 2; +} +.mermaid .activeCrit0, +.mermaid .activeCrit1, +.mermaid .activeCrit2, +.mermaid .activeCrit3 { + stroke: #ff8888; + fill: #bfc7ff; + stroke-width: 2; +} +.mermaid .doneCrit0, +.mermaid .doneCrit1, +.mermaid .doneCrit2, +.mermaid .doneCrit3 { + stroke: #ff8888; + fill: lightgrey; + stroke-width: 2; + cursor: pointer; + shape-rendering: crispEdges; +} +.mermaid .doneCritText0, +.mermaid .doneCritText1, +.mermaid .doneCritText2, +.mermaid .doneCritText3 { + fill: black !important; +} +.mermaid .activeCritText0, +.mermaid .activeCritText1, +.mermaid .activeCritText2, +.mermaid .activeCritText3 { + fill: black !important; +} +.mermaid .titleText { + text-anchor: middle; + font-size: 18px; + fill: black; +} +.mermaid g.classGroup text { + fill: #9370DB; + stroke: none; + font-family: 'trebuchet ms', verdana, arial; + font-size: 10px; +} +.mermaid g.classGroup rect { + fill: #ECECFF; + stroke: #9370DB; +} +.mermaid g.classGroup line { + stroke: #9370DB; + stroke-width: 1; +} +.mermaid svg .classLabel .box { + stroke: none; + stroke-width: 0; + fill: #ECECFF; + opacity: 0.5; +} +.mermaid svg .classLabel .label { + fill: #9370DB; + font-size: 10px; +} +.mermaid .relation { + stroke: #9370DB; + stroke-width: 1; + fill: none; +} +.mermaid .composition { + fill: #9370DB; + stroke: #9370DB; + stroke-width: 1; +} +.mermaid #compositionStart { + fill: #9370DB; + stroke: #9370DB; + stroke-width: 1; +} +.mermaid #compositionEnd { + fill: #9370DB; + stroke: #9370DB; + stroke-width: 1; +} +.mermaid .aggregation { + fill: #ECECFF; + stroke: #9370DB; + stroke-width: 1; +} +.mermaid #aggregationStart { + fill: #ECECFF; + stroke: #9370DB; + stroke-width: 1; +} +.mermaid #aggregationEnd { + fill: #ECECFF; + stroke: #9370DB; + stroke-width: 1; +} +.mermaid #dependencyStart { + fill: #9370DB; + stroke: #9370DB; + stroke-width: 1; +} +.mermaid #dependencyEnd { + fill: #9370DB; + stroke: #9370DB; + stroke-width: 1; +} +.mermaid #extensionStart { + fill: #9370DB; + stroke: #9370DB; + stroke-width: 1; +} +.mermaid #extensionEnd { + fill: #9370DB; + stroke: #9370DB; + stroke-width: 1; +} +.mermaid .node text { + font-family: 'trebuchet ms', verdana, arial; + font-size: 14px; +} +.mermaid div.mermaidTooltip { + position: absolute; + text-align: center; + max-width: 200px; + padding: 2px; + font-family: 'trebuchet ms', verdana, arial; + font-size: 12px; + background: #ffffde; + border: 1px solid #aaaa33; + border-radius: 2px; + pointer-events: none; + z-index: 100; +} diff --git a/shared/mermaid.min.js b/shared/mermaid.min.js new file mode 100644 index 0000000000..0a72f3373b --- /dev/null +++ b/shared/mermaid.min.js @@ -0,0 +1,49 @@ +!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.mermaid=e():t.mermaid=e()}(window,(function(){return function(t){var e={};function n(r){if(e[r])return e[r].exports;var i=e[r]={i:r,l:!1,exports:{}};return t[r].call(i.exports,i,i.exports,n),i.l=!0,i.exports}return n.m=t,n.c=e,n.d=function(t,e,r){n.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:r})},n.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},n.t=function(t,e){if(1&e&&(t=n(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var i in t)n.d(r,i,function(e){return t[e]}.bind(null,i));return r},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s=509)}([function(t,e,n){"use strict";n.r(e);var r=function(t,e){return te?1:t>=e?0:NaN},i=function(t){var e;return 1===t.length&&(e=t,t=function(t,n){return r(e(t),n)}),{left:function(e,n,r,i){for(null==r&&(r=0),null==i&&(i=e.length);r>>1;t(e[o],n)<0?r=o+1:i=o}return r},right:function(e,n,r,i){for(null==r&&(r=0),null==i&&(i=e.length);r>>1;t(e[o],n)>0?i=o:r=o+1}return r}}};var o=i(r),a=o.right,s=o.left,u=a,c=function(t,e){null==e&&(e=f);for(var n=0,r=t.length-1,i=t[0],o=new Array(r<0?0:r);nt?1:e>=t?0:NaN},d=function(t){return null===t?NaN:+t},p=function(t,e){var n,r,i=t.length,o=0,a=-1,s=0,u=0;if(null==e)for(;++a1)return u/(o-1)},g=function(t,e){var n=p(t,e);return n?Math.sqrt(n):n},y=function(t,e){var n,r,i,o=t.length,a=-1;if(null==e){for(;++a=n)for(r=i=n;++an&&(r=n),i=n)for(r=i=n;++an&&(r=n),i0)return[t];if((r=e0)for(t=Math.ceil(t/a),e=Math.floor(e/a),o=new Array(i=Math.ceil(e-t+1));++s=0?(o>=k?10:o>=E?5:o>=A?2:1)*Math.pow(10,i):-Math.pow(10,-i)/(o>=k?10:o>=E?5:o>=A?2:1)}function M(t,e,n){var r=Math.abs(e-t)/Math.max(0,n),i=Math.pow(10,Math.floor(Math.log(r)/Math.LN10)),o=r/i;return o>=k?i*=10:o>=E?i*=5:o>=A&&(i*=2),el;)h.pop(),--d;var p,g=new Array(d+1);for(i=0;i<=d;++i)(p=g[i]=[]).x0=i>0?h[i-1]:f,p.x1=i=1)return+n(t[r-1],r-1,t);var r,i=(r-1)*e,o=Math.floor(i),a=+n(t[o],o,t);return a+(+n(t[o+1],o+1,t)-a)*(i-o)}},R=function(t,e,n){return t=v.call(t,d).sort(r),Math.ceil((n-e)/(2*(O(t,.75)-O(t,.25))*Math.pow(t.length,-1/3)))},I=function(t,e,n){return Math.ceil((n-e)/(3.5*g(t)*Math.pow(t.length,-1/3)))},N=function(t,e){var n,r,i=t.length,o=-1;if(null==e){for(;++o=n)for(r=n;++or&&(r=n)}else for(;++o=n)for(r=n;++or&&(r=n);return r},B=function(t,e){var n,r=t.length,i=r,o=-1,a=0;if(null==e)for(;++o=0;)for(e=(r=t[i]).length;--e>=0;)n[--a]=r[e];return n},F=function(t,e){var n,r,i=t.length,o=-1;if(null==e){for(;++o=n)for(r=n;++on&&(r=n)}else for(;++o=n)for(r=n;++on&&(r=n);return r},q=function(t,e){for(var n=e.length,r=new Array(n);n--;)r[n]=t[e[n]];return r},j=function(t,e){if(n=t.length){var n,i,o=0,a=0,s=t[a];for(null==e&&(e=r);++o=0&&(n=t.slice(r+1),t=t.slice(0,r)),t&&!e.hasOwnProperty(t))throw new Error("unknown type: "+t);return{type:t,name:n}}))}function dt(t,e){for(var n,r=0,i=t.length;r0)for(var n,r,i=new Array(n),o=0;o=0&&"xmlns"!==(e=t.slice(0,n))&&(t=t.slice(n+1)),bt.hasOwnProperty(e)?{space:bt[e],local:t}:t};function vt(t){return function(){var e=this.ownerDocument,n=this.namespaceURI;return n===yt&&e.documentElement.namespaceURI===yt?e.createElement(t):e.createElementNS(n,t)}}function _t(t){return function(){return this.ownerDocument.createElementNS(t.space,t.local)}}var wt=function(t){var e=mt(t);return(e.local?_t:vt)(e)};function xt(){}var kt=function(t){return null==t?xt:function(){return this.querySelector(t)}};function Et(){return[]}var At=function(t){return null==t?Et:function(){return this.querySelectorAll(t)}},St=function(t){return function(){return this.matches(t)}},Tt=function(t){return new Array(t.length)};function Mt(t,e){this.ownerDocument=t.ownerDocument,this.namespaceURI=t.namespaceURI,this._next=null,this._parent=t,this.__data__=e}Mt.prototype={constructor:Mt,appendChild:function(t){return this._parent.insertBefore(t,this._next)},insertBefore:function(t,e){return this._parent.insertBefore(t,e)},querySelector:function(t){return this._parent.querySelector(t)},querySelectorAll:function(t){return this._parent.querySelectorAll(t)}};var Dt="$";function Ct(t,e,n,r,i,o){for(var a,s=0,u=e.length,c=o.length;se?1:t>=e?0:NaN}function It(t){return function(){this.removeAttribute(t)}}function Nt(t){return function(){this.removeAttributeNS(t.space,t.local)}}function Bt(t,e){return function(){this.setAttribute(t,e)}}function Lt(t,e){return function(){this.setAttributeNS(t.space,t.local,e)}}function Pt(t,e){return function(){var n=e.apply(this,arguments);null==n?this.removeAttribute(t):this.setAttribute(t,n)}}function Ft(t,e){return function(){var n=e.apply(this,arguments);null==n?this.removeAttributeNS(t.space,t.local):this.setAttributeNS(t.space,t.local,n)}}var qt=function(t){return t.ownerDocument&&t.ownerDocument.defaultView||t.document&&t||t.defaultView};function jt(t){return function(){this.style.removeProperty(t)}}function Ut(t,e,n){return function(){this.style.setProperty(t,e,n)}}function zt(t,e,n){return function(){var r=e.apply(this,arguments);null==r?this.style.removeProperty(t):this.style.setProperty(t,r,n)}}function Yt(t,e){return t.style.getPropertyValue(e)||qt(t).getComputedStyle(t,null).getPropertyValue(e)}function Vt(t){return function(){delete this[t]}}function Ht(t,e){return function(){this[t]=e}}function $t(t,e){return function(){var n=e.apply(this,arguments);null==n?delete this[t]:this[t]=n}}function Gt(t){return t.trim().split(/^|\s+/)}function Wt(t){return t.classList||new Kt(t)}function Kt(t){this._node=t,this._names=Gt(t.getAttribute("class")||"")}function Xt(t,e){for(var n=Wt(t),r=-1,i=e.length;++r=0&&(this._names.splice(e,1),this._node.setAttribute("class",this._names.join(" ")))},contains:function(t){return this._names.indexOf(t)>=0}};function ee(){this.textContent=""}function ne(t){return function(){this.textContent=t}}function re(t){return function(){var e=t.apply(this,arguments);this.textContent=null==e?"":e}}function ie(){this.innerHTML=""}function oe(t){return function(){this.innerHTML=t}}function ae(t){return function(){var e=t.apply(this,arguments);this.innerHTML=null==e?"":e}}function se(){this.nextSibling&&this.parentNode.appendChild(this)}function ue(){this.previousSibling&&this.parentNode.insertBefore(this,this.parentNode.firstChild)}function ce(){return null}function fe(){var t=this.parentNode;t&&t.removeChild(this)}function le(){return this.parentNode.insertBefore(this.cloneNode(!1),this.nextSibling)}function he(){return this.parentNode.insertBefore(this.cloneNode(!0),this.nextSibling)}var de={},pe=null;"undefined"!=typeof document&&("onmouseenter"in document.documentElement||(de={mouseenter:"mouseover",mouseleave:"mouseout"}));function ge(t,e,n){return t=ye(t,e,n),function(e){var n=e.relatedTarget;n&&(n===this||8&n.compareDocumentPosition(this))||t.call(this,e)}}function ye(t,e,n){return function(r){var i=pe;pe=r;try{t.call(this,this.__data__,e,n)}finally{pe=i}}}function be(t){return t.trim().split(/^|\s+/).map((function(t){var e="",n=t.indexOf(".");return n>=0&&(e=t.slice(n+1),t=t.slice(0,n)),{type:t,name:e}}))}function me(t){return function(){var e=this.__on;if(e){for(var n,r=0,i=-1,o=e.length;r=w&&(w=_+1);!(v=b[w])&&++w=0;)(r=i[o])&&(a&&4^r.compareDocumentPosition(a)&&a.parentNode.insertBefore(r,a),a=r);return this},sort:function(t){function e(e,n){return e&&n?t(e.__data__,n.__data__):!e-!n}t||(t=Rt);for(var n=this._groups,r=n.length,i=new Array(r),o=0;o1?this.each((null==e?jt:"function"==typeof e?zt:Ut)(t,e,null==n?"":n)):Yt(this.node(),t)},property:function(t,e){return arguments.length>1?this.each((null==e?Vt:"function"==typeof e?$t:Ht)(t,e)):this.node()[t]},classed:function(t,e){var n=Gt(t+"");if(arguments.length<2){for(var r=Wt(this.node()),i=-1,o=n.length;++il}u.mouse("drag")}function g(){Me(pe.view).on("mousemove.drag mouseup.drag",null),ze(pe.view,n),je(),u.mouse("end")}function y(){if(i.apply(this,arguments)){var t,e,n=pe.changedTouches,r=o.apply(this,arguments),a=n.length;for(t=0;t>8&15|e>>4&240,e>>4&15|240&e,(15&e)<<4|15&e,1):8===n?new mn(e>>24&255,e>>16&255,e>>8&255,(255&e)/255):4===n?new mn(e>>12&15|e>>8&240,e>>8&15|e>>4&240,e>>4&15|240&e,((15&e)<<4|15&e)/255):null):(e=rn.exec(t))?new mn(e[1],e[2],e[3],1):(e=on.exec(t))?new mn(255*e[1]/100,255*e[2]/100,255*e[3]/100,1):(e=an.exec(t))?gn(e[1],e[2],e[3],e[4]):(e=sn.exec(t))?gn(255*e[1]/100,255*e[2]/100,255*e[3]/100,e[4]):(e=un.exec(t))?xn(e[1],e[2]/100,e[3]/100,1):(e=cn.exec(t))?xn(e[1],e[2]/100,e[3]/100,e[4]):fn.hasOwnProperty(t)?pn(fn[t]):"transparent"===t?new mn(NaN,NaN,NaN,0):null}function pn(t){return new mn(t>>16&255,t>>8&255,255&t,1)}function gn(t,e,n,r){return r<=0&&(t=e=n=NaN),new mn(t,e,n,r)}function yn(t){return t instanceof Je||(t=dn(t)),t?new mn((t=t.rgb()).r,t.g,t.b,t.opacity):new mn}function bn(t,e,n,r){return 1===arguments.length?yn(t):new mn(t,e,n,null==r?1:r)}function mn(t,e,n,r){this.r=+t,this.g=+e,this.b=+n,this.opacity=+r}function vn(){return"#"+wn(this.r)+wn(this.g)+wn(this.b)}function _n(){var t=this.opacity;return(1===(t=isNaN(t)?1:Math.max(0,Math.min(1,t)))?"rgb(":"rgba(")+Math.max(0,Math.min(255,Math.round(this.r)||0))+", "+Math.max(0,Math.min(255,Math.round(this.g)||0))+", "+Math.max(0,Math.min(255,Math.round(this.b)||0))+(1===t?")":", "+t+")")}function wn(t){return((t=Math.max(0,Math.min(255,Math.round(t)||0)))<16?"0":"")+t.toString(16)}function xn(t,e,n,r){return r<=0?t=e=n=NaN:n<=0||n>=1?t=e=NaN:e<=0&&(t=NaN),new An(t,e,n,r)}function kn(t){if(t instanceof An)return new An(t.h,t.s,t.l,t.opacity);if(t instanceof Je||(t=dn(t)),!t)return new An;if(t instanceof An)return t;var e=(t=t.rgb()).r/255,n=t.g/255,r=t.b/255,i=Math.min(e,n,r),o=Math.max(e,n,r),a=NaN,s=o-i,u=(o+i)/2;return s?(a=e===o?(n-r)/s+6*(n0&&u<1?0:a,new An(a,s,u,t.opacity)}function En(t,e,n,r){return 1===arguments.length?kn(t):new An(t,e,n,null==r?1:r)}function An(t,e,n,r){this.h=+t,this.s=+e,this.l=+n,this.opacity=+r}function Sn(t,e,n){return 255*(t<60?e+(n-e)*t/60:t<180?n:t<240?e+(n-e)*(240-t)/60:e)}function Tn(t,e,n,r,i){var o=t*t,a=o*t;return((1-3*t+3*o-a)*e+(4-6*o+3*a)*n+(1+3*t+3*o-3*a)*r+a*i)/6}Xe(Je,dn,{copy:function(t){return Object.assign(new this.constructor,this,t)},displayable:function(){return this.rgb().displayable()},hex:ln,formatHex:ln,formatHsl:function(){return kn(this).formatHsl()},formatRgb:hn,toString:hn}),Xe(mn,bn,Ze(Je,{brighter:function(t){return t=null==t?1/.7:Math.pow(1/.7,t),new mn(this.r*t,this.g*t,this.b*t,this.opacity)},darker:function(t){return t=null==t?.7:Math.pow(.7,t),new mn(this.r*t,this.g*t,this.b*t,this.opacity)},rgb:function(){return this},displayable:function(){return-.5<=this.r&&this.r<255.5&&-.5<=this.g&&this.g<255.5&&-.5<=this.b&&this.b<255.5&&0<=this.opacity&&this.opacity<=1},hex:vn,formatHex:vn,formatRgb:_n,toString:_n})),Xe(An,En,Ze(Je,{brighter:function(t){return t=null==t?1/.7:Math.pow(1/.7,t),new An(this.h,this.s,this.l*t,this.opacity)},darker:function(t){return t=null==t?.7:Math.pow(.7,t),new An(this.h,this.s,this.l*t,this.opacity)},rgb:function(){var t=this.h%360+360*(this.h<0),e=isNaN(t)||isNaN(this.s)?0:this.s,n=this.l,r=n+(n<.5?n:1-n)*e,i=2*n-r;return new mn(Sn(t>=240?t-240:t+120,i,r),Sn(t,i,r),Sn(t<120?t+240:t-120,i,r),this.opacity)},displayable:function(){return(0<=this.s&&this.s<=1||isNaN(this.s))&&0<=this.l&&this.l<=1&&0<=this.opacity&&this.opacity<=1},formatHsl:function(){var t=this.opacity;return(1===(t=isNaN(t)?1:Math.max(0,Math.min(1,t)))?"hsl(":"hsla(")+(this.h||0)+", "+100*(this.s||0)+"%, "+100*(this.l||0)+"%"+(1===t?")":", "+t+")")}}));var Mn=function(t){var e=t.length-1;return function(n){var r=n<=0?n=0:n>=1?(n=1,e-1):Math.floor(n*e),i=t[r],o=t[r+1],a=r>0?t[r-1]:2*i-o,s=r180||n<-180?n-360*Math.round(n/360):n):Cn(isNaN(t)?e:t)}function In(t){return 1==(t=+t)?Nn:function(e,n){return n-e?function(t,e,n){return t=Math.pow(t,n),e=Math.pow(e,n)-t,n=1/n,function(r){return Math.pow(t+r*e,n)}}(e,n,t):Cn(isNaN(e)?n:e)}}function Nn(t,e){var n=e-t;return n?On(t,n):Cn(isNaN(t)?e:t)}var Bn=function t(e){var n=In(e);function r(t,e){var r=n((t=bn(t)).r,(e=bn(e)).r),i=n(t.g,e.g),o=n(t.b,e.b),a=Nn(t.opacity,e.opacity);return function(e){return t.r=r(e),t.g=i(e),t.b=o(e),t.opacity=a(e),t+""}}return r.gamma=t,r}(1);function Ln(t){return function(e){var n,r,i=e.length,o=new Array(i),a=new Array(i),s=new Array(i);for(n=0;no&&(i=e.slice(o,i),s[a]?s[a]+=i:s[++a]=i),(n=n[0])===(r=r[0])?s[a]?s[a]+=r:s[++a]=r:(s[++a]=null,u.push({i:a,x:Un(n,r)})),o=Vn.lastIndex;return o180?e+=360:e-t>180&&(t+=360),o.push({i:n.push(i(n)+"rotate(",null,r)-2,x:Un(t,e)})):e&&n.push(i(n)+"rotate("+e+r)}(o.rotate,a.rotate,s,u),function(t,e,n,o){t!==e?o.push({i:n.push(i(n)+"skewX(",null,r)-2,x:Un(t,e)}):e&&n.push(i(n)+"skewX("+e+r)}(o.skewX,a.skewX,s,u),function(t,e,n,r,o,a){if(t!==n||e!==r){var s=o.push(i(o)+"scale(",null,",",null,")");a.push({i:s-4,x:Un(t,n)},{i:s-2,x:Un(e,r)})}else 1===n&&1===r||o.push(i(o)+"scale("+n+","+r+")")}(o.scaleX,o.scaleY,a.scaleX,a.scaleY,s,u),o=a=null,function(t){for(var e,n=-1,r=u.length;++n_r?Math.pow(t,1/3):t/vr+br}function Sr(t){return t>mr?t*t*t:vr*(t-br)}function Tr(t){return 255*(t<=.0031308?12.92*t:1.055*Math.pow(t,1/2.4)-.055)}function Mr(t){return(t/=255)<=.04045?t/12.92:Math.pow((t+.055)/1.055,2.4)}function Dr(t){if(t instanceof Rr)return new Rr(t.h,t.c,t.l,t.opacity);if(t instanceof Er||(t=wr(t)),0===t.a&&0===t.b)return new Rr(NaN,0=0&&e._call.call(null,t),e=e._next;--Qr}function di(){ii=(ri=ai.now())+oi,Qr=ti=0;try{hi()}finally{Qr=0,function(){var t,e,n=Xr,r=1/0;for(;n;)n._call?(r>n._time&&(r=n._time),t=n,n=n._next):(e=n._next,n._next=null,n=t?t._next=e:Xr=e);Zr=t,gi(r)}(),ii=0}}function pi(){var t=ai.now(),e=t-ri;e>ni&&(oi-=e,ri=t)}function gi(t){Qr||(ti&&(ti=clearTimeout(ti)),t-ii>24?(t<1/0&&(ti=setTimeout(di,t-ai.now()-oi)),ei&&(ei=clearInterval(ei))):(ei||(ri=ai.now(),ei=setInterval(pi,ni)),Qr=1,si(di)))}fi.prototype=li.prototype={constructor:fi,restart:function(t,e,n){if("function"!=typeof t)throw new TypeError("callback is not a function");n=(null==n?ui():+n)+(null==e?0:+e),this._next||Zr===this||(Zr?Zr._next=this:Xr=this,Zr=this),this._call=t,this._time=n,gi()},stop:function(){this._call&&(this._call=null,this._time=1/0,gi())}};var yi=function(t,e,n){var r=new fi;return e=null==e?0:+e,r.restart((function(n){r.stop(),t(n+e)}),e,n),r},bi=function(t,e,n){var r=new fi,i=e;return null==e?(r.restart(t,e,n),r):(e=+e,n=null==n?ui():+n,r.restart((function o(a){a+=i,r.restart(o,i+=e,n),t(a)}),e,n),r)},mi=gt("start","end","cancel","interrupt"),vi=[],_i=0,wi=1,xi=2,ki=3,Ei=4,Ai=5,Si=6,Ti=function(t,e,n,r,i,o){var a=t.__transition;if(a){if(n in a)return}else t.__transition={};!function(t,e,n){var r,i=t.__transition;function o(u){var c,f,l,h;if(n.state!==wi)return s();for(c in i)if((h=i[c]).name===n.name){if(h.state===ki)return yi(o);h.state===Ei?(h.state=Si,h.timer.stop(),h.on.call("interrupt",t,t.__data__,h.index,h.group),delete i[c]):+c_i)throw new Error("too late; already scheduled");return n}function Di(t,e){var n=Ci(t,e);if(n.state>ki)throw new Error("too late; already running");return n}function Ci(t,e){var n=t.__transition;if(!n||!(n=n[e]))throw new Error("transition not found");return n}var Oi=function(t,e){var n,r,i,o=t.__transition,a=!0;if(o){for(i in e=null==e?null:e+"",o)(n=o[i]).name===e?(r=n.state>xi&&n.state=0&&(t=t.slice(0,e)),!t||"start"===t}))}(e)?Mi:Di;return function(){var a=o(this,t),s=a.on;s!==r&&(i=(r=s).copy()).on(e,n),a.on=i}}var Xi=Te.prototype.constructor;function Zi(t){return function(){this.style.removeProperty(t)}}function Ji(t,e,n){var r,i;function o(){var o=e.apply(this,arguments);return o!==i&&(r=(i=o)&&function(t,e,n){return function(r){this.style.setProperty(t,e(r),n)}}(t,o,n)),r}return o._value=e,o}var Qi=0;function to(t,e,n,r){this._groups=t,this._parents=e,this._name=n,this._id=r}function eo(t){return Te().transition(t)}function no(){return++Qi}var ro=Te.prototype;function io(t){return+t}function oo(t){return t*t}function ao(t){return t*(2-t)}function so(t){return((t*=2)<=1?t*t:--t*(2-t)+1)/2}function uo(t){return t*t*t}function co(t){return--t*t*t+1}function fo(t){return((t*=2)<=1?t*t*t:(t-=2)*t*t+2)/2}to.prototype=eo.prototype={constructor:to,select:function(t){var e=this._name,n=this._id;"function"!=typeof t&&(t=kt(t));for(var r=this._groups,i=r.length,o=new Array(i),a=0;awi&&n.name===e)return new to([[t]],Wo,e,+r);return null},Xo=function(t){return function(){return t}},Zo=function(t,e,n){this.target=t,this.type=e,this.selection=n};function Jo(){pe.stopImmediatePropagation()}var Qo=function(){pe.preventDefault(),pe.stopImmediatePropagation()},ta={name:"drag"},ea={name:"space"},na={name:"handle"},ra={name:"center"};function ia(t){return[+t[0],+t[1]]}function oa(t){return[ia(t[0]),ia(t[1])]}var aa={name:"x",handles:["w","e"].map(pa),input:function(t,e){return null==t?null:[[+t[0],e[0][1]],[+t[1],e[1][1]]]},output:function(t){return t&&[t[0][0],t[1][0]]}},sa={name:"y",handles:["n","s"].map(pa),input:function(t,e){return null==t?null:[[e[0][0],+t[0]],[e[1][0],+t[1]]]},output:function(t){return t&&[t[0][1],t[1][1]]}},ua={name:"xy",handles:["n","w","e","s","nw","ne","sw","se"].map(pa),input:function(t){return null==t?null:oa(t)},output:function(t){return t}},ca={overlay:"crosshair",selection:"move",n:"ns-resize",e:"ew-resize",s:"ns-resize",w:"ew-resize",nw:"nwse-resize",ne:"nesw-resize",se:"nwse-resize",sw:"nesw-resize"},fa={e:"w",w:"e",nw:"ne",ne:"nw",se:"sw",sw:"se"},la={n:"s",s:"n",nw:"sw",ne:"se",se:"ne",sw:"nw"},ha={overlay:1,selection:1,n:null,e:1,s:null,w:-1,nw:-1,ne:1,se:1,sw:-1},da={overlay:1,selection:1,n:-1,e:null,s:1,w:null,nw:-1,ne:-1,se:1,sw:1};function pa(t){return{type:t}}function ga(){return!pe.ctrlKey&&!pe.button}function ya(){var t=this.ownerSVGElement||this;return t.hasAttribute("viewBox")?[[(t=t.viewBox.baseVal).x,t.y],[t.x+t.width,t.y+t.height]]:[[0,0],[t.width.baseVal.value,t.height.baseVal.value]]}function ba(){return navigator.maxTouchPoints||"ontouchstart"in this}function ma(t){for(;!t.__brush;)if(!(t=t.parentNode))return;return t.__brush}function va(t){var e=t.__brush;return e?e.dim.output(e.selection):null}function _a(){return ka(aa)}function wa(){return ka(sa)}var xa=function(){return ka(ua)};function ka(t){var e,n=ya,r=ga,i=ba,o=!0,a=gt(u,"start","brush","end"),s=6;function u(e){var n=e.property("__brush",g).selectAll(".overlay").data([pa("overlay")]);n.enter().append("rect").attr("class","overlay").attr("pointer-events","all").attr("cursor",ca.overlay).merge(n).each((function(){var t=ma(this).extent;Me(this).attr("x",t[0][0]).attr("y",t[0][1]).attr("width",t[1][0]-t[0][0]).attr("height",t[1][1]-t[0][1])})),e.selectAll(".selection").data([pa("selection")]).enter().append("rect").attr("class","selection").attr("cursor",ca.selection).attr("fill","#777").attr("fill-opacity",.3).attr("stroke","#fff").attr("shape-rendering","crispEdges");var r=e.selectAll(".handle").data(t.handles,(function(t){return t.type}));r.exit().remove(),r.enter().append("rect").attr("class",(function(t){return"handle handle--"+t.type})).attr("cursor",(function(t){return ca[t.type]})),e.each(c).attr("fill","none").attr("pointer-events","all").on("mousedown.brush",h).filter(i).on("touchstart.brush",h).on("touchmove.brush",d).on("touchend.brush touchcancel.brush",p).style("touch-action","none").style("-webkit-tap-highlight-color","rgba(0,0,0,0)")}function c(){var t=Me(this),e=ma(this).selection;e?(t.selectAll(".selection").style("display",null).attr("x",e[0][0]).attr("y",e[0][1]).attr("width",e[1][0]-e[0][0]).attr("height",e[1][1]-e[0][1]),t.selectAll(".handle").style("display",null).attr("x",(function(t){return"e"===t.type[t.type.length-1]?e[1][0]-s/2:e[0][0]-s/2})).attr("y",(function(t){return"s"===t.type[0]?e[1][1]-s/2:e[0][1]-s/2})).attr("width",(function(t){return"n"===t.type||"s"===t.type?e[1][0]-e[0][0]+s:s})).attr("height",(function(t){return"e"===t.type||"w"===t.type?e[1][1]-e[0][1]+s:s}))):t.selectAll(".selection,.handle").style("display","none").attr("x",null).attr("y",null).attr("width",null).attr("height",null)}function f(t,e,n){return!n&&t.__brush.emitter||new l(t,e)}function l(t,e){this.that=t,this.args=e,this.state=t.__brush,this.active=0}function h(){if((!e||pe.touches)&&r.apply(this,arguments)){var n,i,a,s,u,l,h,d,p,g,y,b,m=this,v=pe.target.__data__.type,_="selection"===(o&&pe.metaKey?v="overlay":v)?ta:o&&pe.altKey?ra:na,w=t===sa?null:ha[v],x=t===aa?null:da[v],k=ma(m),E=k.extent,A=k.selection,S=E[0][0],T=E[0][1],M=E[1][0],D=E[1][1],C=0,O=0,R=w&&x&&o&&pe.shiftKey,I=pe.touches?(b=pe.changedTouches[0].identifier,function(t){return Pe(t,pe.touches,b)}):Be,N=I(m),B=N,L=f(m,arguments,!0).beforestart();"overlay"===v?(A&&(p=!0),k.selection=A=[[n=t===sa?S:N[0],a=t===aa?T:N[1]],[u=t===sa?M:n,h=t===aa?D:a]]):(n=A[0][0],a=A[0][1],u=A[1][0],h=A[1][1]),i=n,s=a,l=u,d=h;var P=Me(m).attr("pointer-events","none"),F=P.selectAll(".overlay").attr("cursor",ca[v]);if(pe.touches)L.moved=j,L.ended=z;else{var q=Me(pe.view).on("mousemove.brush",j,!0).on("mouseup.brush",z,!0);o&&q.on("keydown.brush",(function(){switch(pe.keyCode){case 16:R=w&&x;break;case 18:_===na&&(w&&(u=l-C*w,n=i+C*w),x&&(h=d-O*x,a=s+O*x),_=ra,U());break;case 32:_!==na&&_!==ra||(w<0?u=l-C:w>0&&(n=i-C),x<0?h=d-O:x>0&&(a=s-O),_=ea,F.attr("cursor",ca.selection),U());break;default:return}Qo()}),!0).on("keyup.brush",(function(){switch(pe.keyCode){case 16:R&&(g=y=R=!1,U());break;case 18:_===ra&&(w<0?u=l:w>0&&(n=i),x<0?h=d:x>0&&(a=s),_=na,U());break;case 32:_===ea&&(pe.altKey?(w&&(u=l-C*w,n=i+C*w),x&&(h=d-O*x,a=s+O*x),_=ra):(w<0?u=l:w>0&&(n=i),x<0?h=d:x>0&&(a=s),_=na),F.attr("cursor",ca[v]),U());break;default:return}Qo()}),!0),Ue(pe.view)}Jo(),Oi(m),c.call(m),L.start()}function j(){var t=I(m);!R||g||y||(Math.abs(t[0]-B[0])>Math.abs(t[1]-B[1])?y=!0:g=!0),B=t,p=!0,Qo(),U()}function U(){var t;switch(C=B[0]-N[0],O=B[1]-N[1],_){case ea:case ta:w&&(C=Math.max(S-n,Math.min(M-u,C)),i=n+C,l=u+C),x&&(O=Math.max(T-a,Math.min(D-h,O)),s=a+O,d=h+O);break;case na:w<0?(C=Math.max(S-n,Math.min(M-n,C)),i=n+C,l=u):w>0&&(C=Math.max(S-u,Math.min(M-u,C)),i=n,l=u+C),x<0?(O=Math.max(T-a,Math.min(D-a,O)),s=a+O,d=h):x>0&&(O=Math.max(T-h,Math.min(D-h,O)),s=a,d=h+O);break;case ra:w&&(i=Math.max(S,Math.min(M,n-C*w)),l=Math.max(S,Math.min(M,u+C*w))),x&&(s=Math.max(T,Math.min(D,a-O*x)),d=Math.max(T,Math.min(D,h+O*x)))}l1e-6)if(Math.abs(f*s-u*c)>1e-6&&i){var h=n-o,d=r-a,p=s*s+u*u,g=h*h+d*d,y=Math.sqrt(p),b=Math.sqrt(l),m=i*Math.tan((Na-Math.acos((p+l-g)/(2*y*b)))/2),v=m/b,_=m/y;Math.abs(v-1)>1e-6&&(this._+="L"+(t+v*c)+","+(e+v*f)),this._+="A"+i+","+i+",0,0,"+ +(f*h>c*d)+","+(this._x1=t+_*s)+","+(this._y1=e+_*u)}else this._+="L"+(this._x1=t)+","+(this._y1=e);else;},arc:function(t,e,n,r,i,o){t=+t,e=+e,o=!!o;var a=(n=+n)*Math.cos(r),s=n*Math.sin(r),u=t+a,c=e+s,f=1^o,l=o?r-i:i-r;if(n<0)throw new Error("negative radius: "+n);null===this._x1?this._+="M"+u+","+c:(Math.abs(this._x1-u)>1e-6||Math.abs(this._y1-c)>1e-6)&&(this._+="L"+u+","+c),n&&(l<0&&(l=l%Ba+Ba),l>La?this._+="A"+n+","+n+",0,1,"+f+","+(t-a)+","+(e-s)+"A"+n+","+n+",0,1,"+f+","+(this._x1=u)+","+(this._y1=c):l>1e-6&&(this._+="A"+n+","+n+",0,"+ +(l>=Na)+","+f+","+(this._x1=t+n*Math.cos(i))+","+(this._y1=e+n*Math.sin(i))))},rect:function(t,e,n,r){this._+="M"+(this._x0=this._x1=+t)+","+(this._y0=this._y1=+e)+"h"+ +n+"v"+ +r+"h"+-n+"Z"},toString:function(){return this._}};var qa=Fa;function ja(t){return t.source}function Ua(t){return t.target}function za(t){return t.radius}function Ya(t){return t.startAngle}function Va(t){return t.endAngle}var Ha=function(){var t=ja,e=Ua,n=za,r=Ya,i=Va,o=null;function a(){var a,s=Ra.call(arguments),u=t.apply(this,s),c=e.apply(this,s),f=+n.apply(this,(s[0]=u,s)),l=r.apply(this,s)-Ta,h=i.apply(this,s)-Ta,d=f*Ea(l),p=f*Aa(l),g=+n.apply(this,(s[0]=c,s)),y=r.apply(this,s)-Ta,b=i.apply(this,s)-Ta;if(o||(o=a=qa()),o.moveTo(d,p),o.arc(0,0,f,l,h),l===y&&h===b||(o.quadraticCurveTo(0,0,g*Ea(y),g*Aa(y)),o.arc(0,0,g,y,b)),o.quadraticCurveTo(0,0,d,p),o.closePath(),a)return o=null,a+""||null}return a.radius=function(t){return arguments.length?(n="function"==typeof t?t:Ia(+t),a):n},a.startAngle=function(t){return arguments.length?(r="function"==typeof t?t:Ia(+t),a):r},a.endAngle=function(t){return arguments.length?(i="function"==typeof t?t:Ia(+t),a):i},a.source=function(e){return arguments.length?(t=e,a):t},a.target=function(t){return arguments.length?(e=t,a):e},a.context=function(t){return arguments.length?(o=null==t?null:t,a):o},a};function $a(){}function Ga(t,e){var n=new $a;if(t instanceof $a)t.each((function(t,e){n.set(e,t)}));else if(Array.isArray(t)){var r,i=-1,o=t.length;if(null==e)for(;++i=r.length)return null!=t&&n.sort(t),null!=e?e(n):n;for(var u,c,f,l=-1,h=n.length,d=r[i++],p=Wa(),g=a();++lr.length)return n;var a,s=i[o-1];return null!=e&&o>=r.length?a=n.entries():(a=[],n.each((function(e,n){a.push({key:n,values:t(e,o)})}))),null!=s?a.sort((function(t,e){return s(t.key,e.key)})):a}(o(t,0,Ja,Qa),0)},key:function(t){return r.push(t),n},sortKeys:function(t){return i[r.length-1]=t,n},sortValues:function(e){return t=e,n},rollup:function(t){return e=t,n}}};function Xa(){return{}}function Za(t,e,n){t[e]=n}function Ja(){return Wa()}function Qa(t,e,n){t.set(e,n)}function ts(){}var es=Wa.prototype;function ns(t,e){var n=new ts;if(t instanceof ts)t.each((function(t){n.add(t)}));else if(t){var r=-1,i=t.length;if(null==e)for(;++rr!=d>r&&n<(h-c)*(r-f)/(d-f)+c&&(i=-i)}return i}function ds(t,e,n){var r,i,o,a;return function(t,e,n){return(e[0]-t[0])*(n[1]-t[1])==(n[0]-t[0])*(e[1]-t[1])}(t,e,n)&&(i=t[r=+(t[0]===e[0])],o=n[r],a=e[r],i<=o&&o<=a||a<=o&&o<=i)}var ps=function(){},gs=[[],[[[1,1.5],[.5,1]]],[[[1.5,1],[1,1.5]]],[[[1.5,1],[.5,1]]],[[[1,.5],[1.5,1]]],[[[1,1.5],[.5,1]],[[1,.5],[1.5,1]]],[[[1,.5],[1,1.5]]],[[[1,.5],[.5,1]]],[[[.5,1],[1,.5]]],[[[1,1.5],[1,.5]]],[[[.5,1],[1,.5]],[[1.5,1],[1,1.5]]],[[[1.5,1],[1,.5]]],[[[.5,1],[1.5,1]]],[[[1,1.5],[1.5,1]]],[[[.5,1],[1,1.5]]],[]],ys=function(){var t=1,e=1,n=D,r=s;function i(t){var e=n(t);if(Array.isArray(e))e=e.slice().sort(us);else{var r=y(t),i=r[0],a=r[1];e=M(i,a,e),e=x(Math.floor(i/e)*e,Math.floor(a/e)*e,e)}return e.map((function(e){return o(t,e)}))}function o(n,i){var o=[],s=[];return function(n,r,i){var o,s,u,c,f,l,h=new Array,d=new Array;o=s=-1,c=n[0]>=r,gs[c<<1].forEach(p);for(;++o=r,gs[u|c<<1].forEach(p);gs[c<<0].forEach(p);for(;++s=r,f=n[s*t]>=r,gs[c<<1|f<<2].forEach(p);++o=r,l=f,f=n[s*t+o+1]>=r,gs[u|c<<1|f<<2|l<<3].forEach(p);gs[c|f<<3].forEach(p)}o=-1,f=n[s*t]>=r,gs[f<<2].forEach(p);for(;++o=r,gs[f<<2|l<<3].forEach(p);function p(t){var e,n,r=[t[0][0]+o,t[0][1]+s],u=[t[1][0]+o,t[1][1]+s],c=a(r),f=a(u);(e=d[c])?(n=h[f])?(delete d[e.end],delete h[n.start],e===n?(e.ring.push(u),i(e.ring)):h[e.start]=d[n.end]={start:e.start,end:n.end,ring:e.ring.concat(n.ring)}):(delete d[e.end],e.ring.push(u),d[e.end=f]=e):(e=h[f])?(n=d[c])?(delete h[e.start],delete d[n.end],e===n?(e.ring.push(u),i(e.ring)):h[n.start]=d[e.end]={start:n.start,end:e.end,ring:n.ring.concat(e.ring)}):(delete h[e.start],e.ring.unshift(r),h[e.start=c]=e):h[c]=d[f]={start:c,end:f,ring:[r,u]}}gs[f<<3].forEach(p)}(n,i,(function(t){r(t,n,i),cs(t)>0?o.push([t]):s.push(t)})),s.forEach((function(t){for(var e,n=0,r=o.length;n0&&a0&&s0&&o>0))throw new Error("invalid size");return t=r,e=o,i},i.thresholds=function(t){return arguments.length?(n="function"==typeof t?t:Array.isArray(t)?fs(ss.call(t)):fs(t),i):n},i.smooth=function(t){return arguments.length?(r=t?s:ps,i):r===s},i};function bs(t,e,n){for(var r=t.width,i=t.height,o=1+(n<<1),a=0;a=n&&(s>=o&&(u-=t.data[s-o+a*r]),e.data[s-n+a*r]=u/Math.min(s+1,r-1+o-s,o))}function ms(t,e,n){for(var r=t.width,i=t.height,o=1+(n<<1),a=0;a=n&&(s>=o&&(u-=t.data[a+(s-o)*r]),e.data[a+(s-n)*r]=u/Math.min(s+1,i-1+o-s,o))}function vs(t){return t[0]}function _s(t){return t[1]}function ws(){return 1}var xs=function(){var t=vs,e=_s,n=ws,r=960,i=500,o=20,a=2,s=3*o,u=r+2*s>>a,c=i+2*s>>a,f=fs(20);function l(r){var i=new Float32Array(u*c),l=new Float32Array(u*c);r.forEach((function(r,o,f){var l=+t(r,o,f)+s>>a,h=+e(r,o,f)+s>>a,d=+n(r,o,f);l>=0&&l=0&&h>a),ms({width:u,height:c,data:l},{width:u,height:c,data:i},o>>a),bs({width:u,height:c,data:i},{width:u,height:c,data:l},o>>a),ms({width:u,height:c,data:l},{width:u,height:c,data:i},o>>a),bs({width:u,height:c,data:i},{width:u,height:c,data:l},o>>a),ms({width:u,height:c,data:l},{width:u,height:c,data:i},o>>a);var d=f(i);if(!Array.isArray(d)){var p=N(i);d=M(0,p,d),(d=x(0,Math.floor(p/d)*d,d)).shift()}return ys().thresholds(d).size([u,c])(i).map(h)}function h(t){return t.value*=Math.pow(2,-2*a),t.coordinates.forEach(d),t}function d(t){t.forEach(p)}function p(t){t.forEach(g)}function g(t){t[0]=t[0]*Math.pow(2,a)-s,t[1]=t[1]*Math.pow(2,a)-s}function y(){return u=r+2*(s=3*o)>>a,c=i+2*s>>a,l}return l.x=function(e){return arguments.length?(t="function"==typeof e?e:fs(+e),l):t},l.y=function(t){return arguments.length?(e="function"==typeof t?t:fs(+t),l):e},l.weight=function(t){return arguments.length?(n="function"==typeof t?t:fs(+t),l):n},l.size=function(t){if(!arguments.length)return[r,i];var e=Math.ceil(t[0]),n=Math.ceil(t[1]);if(!(e>=0||e>=0))throw new Error("invalid size");return r=e,i=n,y()},l.cellSize=function(t){if(!arguments.length)return 1<=1))throw new Error("invalid cell size");return a=Math.floor(Math.log(t)/Math.LN2),y()},l.thresholds=function(t){return arguments.length?(f="function"==typeof t?t:Array.isArray(t)?fs(ss.call(t)):fs(t),l):f},l.bandwidth=function(t){if(!arguments.length)return Math.sqrt(o*(o+1));if(!((t=+t)>=0))throw new Error("invalid bandwidth");return o=Math.round((Math.sqrt(4*t*t+1)-1)/2),y()},l},ks={},Es={},As=34,Ss=10,Ts=13;function Ms(t){return new Function("d","return {"+t.map((function(t,e){return JSON.stringify(t)+": d["+e+"]"})).join(",")+"}")}function Ds(t){var e=Object.create(null),n=[];return t.forEach((function(t){for(var r in t)r in e||n.push(e[r]=r)})),n}function Cs(t,e){var n=t+"",r=n.length;return r9999?"+"+Cs(e,6):Cs(e,4))+"-"+Cs(t.getUTCMonth()+1,2)+"-"+Cs(t.getUTCDate(),2)+(o?"T"+Cs(n,2)+":"+Cs(r,2)+":"+Cs(i,2)+"."+Cs(o,3)+"Z":i?"T"+Cs(n,2)+":"+Cs(r,2)+":"+Cs(i,2)+"Z":r||n?"T"+Cs(n,2)+":"+Cs(r,2)+"Z":"")}var Rs=function(t){var e=new RegExp('["'+t+"\n\r]"),n=t.charCodeAt(0);function r(t,e){var r,i=[],o=t.length,a=0,s=0,u=o<=0,c=!1;function f(){if(u)return Es;if(c)return c=!1,ks;var e,r,i=a;if(t.charCodeAt(i)===As){for(;a++=o?u=!0:(r=t.charCodeAt(a++))===Ss?c=!0:r===Ts&&(c=!0,t.charCodeAt(a)===Ss&&++a),t.slice(i+1,e-1).replace(/""/g,'"')}for(;a=(o=(g+b)/2))?g=o:b=o,(f=n>=(a=(y+m)/2))?y=a:m=a,i=d,!(d=d[l=f<<1|c]))return i[l]=p,t;if(s=+t._x.call(null,d.data),u=+t._y.call(null,d.data),e===s&&n===u)return p.next=d,i?i[l]=p:t._root=p,t;do{i=i?i[l]=new Array(4):t._root=new Array(4),(c=e>=(o=(g+b)/2))?g=o:b=o,(f=n>=(a=(y+m)/2))?y=a:m=a}while((l=f<<1|c)==(h=(u>=a)<<1|s>=o));return i[h]=d,i[l]=p,t}var du=function(t,e,n,r,i){this.node=t,this.x0=e,this.y0=n,this.x1=r,this.y1=i};function pu(t){return t[0]}function gu(t){return t[1]}function yu(t,e,n){var r=new bu(null==e?pu:e,null==n?gu:n,NaN,NaN,NaN,NaN);return null==t?r:r.addAll(t)}function bu(t,e,n,r,i,o){this._x=t,this._y=e,this._x0=n,this._y0=r,this._x1=i,this._y1=o,this._root=void 0}function mu(t){for(var e={data:t.data},n=e;t=t.next;)n=n.next={data:t.data};return e}var vu=yu.prototype=bu.prototype;function _u(t){return t.x+t.vx}function wu(t){return t.y+t.vy}vu.copy=function(){var t,e,n=new bu(this._x,this._y,this._x0,this._y0,this._x1,this._y1),r=this._root;if(!r)return n;if(!r.length)return n._root=mu(r),n;for(t=[{source:r,target:n._root=new Array(4)}];r=t.pop();)for(var i=0;i<4;++i)(e=r.source[i])&&(e.length?t.push({source:e,target:r.target[i]=new Array(4)}):r.target[i]=mu(e));return n},vu.add=function(t){var e=+this._x.call(null,t),n=+this._y.call(null,t);return hu(this.cover(e,n),e,n,t)},vu.addAll=function(t){var e,n,r,i,o=t.length,a=new Array(o),s=new Array(o),u=1/0,c=1/0,f=-1/0,l=-1/0;for(n=0;nf&&(f=r),il&&(l=i));if(u>f||c>l)return this;for(this.cover(u,c).cover(f,l),n=0;nt||t>=i||r>e||e>=o;)switch(s=(eh||(o=u.y0)>d||(a=u.x1)=b)<<1|t>=y)&&(u=p[p.length-1],p[p.length-1]=p[p.length-1-c],p[p.length-1-c]=u)}else{var m=t-+this._x.call(null,g.data),v=e-+this._y.call(null,g.data),_=m*m+v*v;if(_=(s=(p+y)/2))?p=s:y=s,(f=a>=(u=(g+b)/2))?g=u:b=u,e=d,!(d=d[l=f<<1|c]))return this;if(!d.length)break;(e[l+1&3]||e[l+2&3]||e[l+3&3])&&(n=e,h=l)}for(;d.data!==t;)if(r=d,!(d=d.next))return this;return(i=d.next)&&delete d.next,r?(i?r.next=i:delete r.next,this):e?(i?e[l]=i:delete e[l],(d=e[0]||e[1]||e[2]||e[3])&&d===(e[3]||e[2]||e[1]||e[0])&&!d.length&&(n?n[h]=d:this._root=d),this):(this._root=i,this)},vu.removeAll=function(t){for(var e=0,n=t.length;eu+d||ic+d||os.index){var p=u-a.x-a.vx,g=c-a.y-a.vy,y=p*p+g*g;yt.r&&(t.r=t[e].r)}function s(){if(e){var r,i,o=e.length;for(n=new Array(o),r=0;r1?(null==n?s.remove(t):s.set(t,d(n)),e):s.get(t)},find:function(e,n,r){var i,o,a,s,u,c=0,f=t.length;for(null==r?r=1/0:r*=r,c=0;c1?(c.on(t,n),e):c.on(t)}}},Ou=function(){var t,e,n,r,i=fu(-30),o=1,a=1/0,s=.81;function u(r){var i,o=t.length,a=yu(t,Su,Tu).visitAfter(f);for(n=r,i=0;i=a)){(t.data!==e||t.next)&&(0===f&&(d+=(f=lu())*f),0===l&&(d+=(l=lu())*l),d1?r[0]+r.slice(2):r,+t.slice(n+1)]},Lu=function(t){return(t=Bu(Math.abs(t)))?t[1]:NaN},Pu=/^(?:(.)?([<>=^]))?([+\-( ])?([$#])?(0)?(\d+)?(,)?(\.\d+)?(~)?([a-z%])?$/i;function Fu(t){if(!(e=Pu.exec(t)))throw new Error("invalid format: "+t);var e;return new qu({fill:e[1],align:e[2],sign:e[3],symbol:e[4],zero:e[5],width:e[6],comma:e[7],precision:e[8]&&e[8].slice(1),trim:e[9],type:e[10]})}function qu(t){this.fill=void 0===t.fill?" ":t.fill+"",this.align=void 0===t.align?">":t.align+"",this.sign=void 0===t.sign?"-":t.sign+"",this.symbol=void 0===t.symbol?"":t.symbol+"",this.zero=!!t.zero,this.width=void 0===t.width?void 0:+t.width,this.comma=!!t.comma,this.precision=void 0===t.precision?void 0:+t.precision,this.trim=!!t.trim,this.type=void 0===t.type?"":t.type+""}Fu.prototype=qu.prototype,qu.prototype.toString=function(){return this.fill+this.align+this.sign+this.symbol+(this.zero?"0":"")+(void 0===this.width?"":Math.max(1,0|this.width))+(this.comma?",":"")+(void 0===this.precision?"":"."+Math.max(0,0|this.precision))+(this.trim?"~":"")+this.type};var ju,Uu,zu,Yu,Vu=function(t){t:for(var e,n=t.length,r=1,i=-1;r0){if(!+t[r])break t;i=0}}return i>0?t.slice(0,i)+t.slice(e+1):t},Hu=function(t,e){var n=Bu(t,e);if(!n)return t+"";var r=n[0],i=n[1];return i<0?"0."+new Array(-i).join("0")+r:r.length>i+1?r.slice(0,i+1)+"."+r.slice(i+1):r+new Array(i-r.length+2).join("0")},$u={"%":function(t,e){return(100*t).toFixed(e)},b:function(t){return Math.round(t).toString(2)},c:function(t){return t+""},d:function(t){return Math.round(t).toString(10)},e:function(t,e){return t.toExponential(e)},f:function(t,e){return t.toFixed(e)},g:function(t,e){return t.toPrecision(e)},o:function(t){return Math.round(t).toString(8)},p:function(t,e){return Hu(100*t,e)},r:Hu,s:function(t,e){var n=Bu(t,e);if(!n)return t+"";var r=n[0],i=n[1],o=i-(ju=3*Math.max(-8,Math.min(8,Math.floor(i/3))))+1,a=r.length;return o===a?r:o>a?r+new Array(o-a+1).join("0"):o>0?r.slice(0,o)+"."+r.slice(o):"0."+new Array(1-o).join("0")+Bu(t,Math.max(0,e+o-1))[0]},X:function(t){return Math.round(t).toString(16).toUpperCase()},x:function(t){return Math.round(t).toString(16)}},Gu=function(t){return t},Wu=Array.prototype.map,Ku=["y","z","a","f","p","n","µ","m","","k","M","G","T","P","E","Z","Y"],Xu=function(t){var e,n,r=void 0===t.grouping||void 0===t.thousands?Gu:(e=Wu.call(t.grouping,Number),n=t.thousands+"",function(t,r){for(var i=t.length,o=[],a=0,s=e[0],u=0;i>0&&s>0&&(u+s+1>r&&(s=Math.max(1,r-u)),o.push(t.substring(i-=s,i+s)),!((u+=s+1)>r));)s=e[a=(a+1)%e.length];return o.reverse().join(n)}),i=void 0===t.currency?"":t.currency[0]+"",o=void 0===t.currency?"":t.currency[1]+"",a=void 0===t.decimal?".":t.decimal+"",s=void 0===t.numerals?Gu:function(t){return function(e){return e.replace(/[0-9]/g,(function(e){return t[+e]}))}}(Wu.call(t.numerals,String)),u=void 0===t.percent?"%":t.percent+"",c=void 0===t.minus?"-":t.minus+"",f=void 0===t.nan?"NaN":t.nan+"";function l(t){var e=(t=Fu(t)).fill,n=t.align,l=t.sign,h=t.symbol,d=t.zero,p=t.width,g=t.comma,y=t.precision,b=t.trim,m=t.type;"n"===m?(g=!0,m="g"):$u[m]||(void 0===y&&(y=12),b=!0,m="g"),(d||"0"===e&&"="===n)&&(d=!0,e="0",n="=");var v="$"===h?i:"#"===h&&/[boxX]/.test(m)?"0"+m.toLowerCase():"",_="$"===h?o:/[%p]/.test(m)?u:"",w=$u[m],x=/[defgprs%]/.test(m);function k(t){var i,o,u,h=v,k=_;if("c"===m)k=w(t)+k,t="";else{var E=(t=+t)<0;if(t=isNaN(t)?f:w(Math.abs(t),y),b&&(t=Vu(t)),E&&0==+t&&(E=!1),h=(E?"("===l?l:c:"-"===l||"("===l?"":l)+h,k=("s"===m?Ku[8+ju/3]:"")+k+(E&&"("===l?")":""),x)for(i=-1,o=t.length;++i(u=t.charCodeAt(i))||u>57){k=(46===u?a+t.slice(i+1):t.slice(i))+k,t=t.slice(0,i);break}}g&&!d&&(t=r(t,1/0));var A=h.length+t.length+k.length,S=A>1)+h+t+k+S.slice(A);break;default:t=S+h+t+k}return s(t)}return y=void 0===y?6:/[gprs]/.test(m)?Math.max(1,Math.min(21,y)):Math.max(0,Math.min(20,y)),k.toString=function(){return t+""},k}return{format:l,formatPrefix:function(t,e){var n=l(((t=Fu(t)).type="f",t)),r=3*Math.max(-8,Math.min(8,Math.floor(Lu(e)/3))),i=Math.pow(10,-r),o=Ku[8+r/3];return function(t){return n(i*t)+o}}}};function Zu(t){return Uu=Xu(t),zu=Uu.format,Yu=Uu.formatPrefix,Uu}Zu({decimal:".",thousands:",",grouping:[3],currency:["$",""],minus:"-"});var Ju=function(t){return Math.max(0,-Lu(Math.abs(t)))},Qu=function(t,e){return Math.max(0,3*Math.max(-8,Math.min(8,Math.floor(Lu(e)/3)))-Lu(Math.abs(t)))},tc=function(t,e){return t=Math.abs(t),e=Math.abs(e)-t,Math.max(0,Lu(e)-Lu(t))+1},ec=function(){return new nc};function nc(){this.reset()}nc.prototype={constructor:nc,reset:function(){this.s=this.t=0},add:function(t){ic(rc,t,this.t),ic(this,rc.s,this.s),this.s?this.t+=rc.t:this.s=rc.t},valueOf:function(){return this.s}};var rc=new nc;function ic(t,e,n){var r=t.s=e+n,i=r-e,o=r-i;t.t=e-o+(n-i)}var oc=1e-6,ac=1e-12,sc=Math.PI,uc=sc/2,cc=sc/4,fc=2*sc,lc=180/sc,hc=sc/180,dc=Math.abs,pc=Math.atan,gc=Math.atan2,yc=Math.cos,bc=Math.ceil,mc=Math.exp,vc=(Math.floor,Math.log),_c=Math.pow,wc=Math.sin,xc=Math.sign||function(t){return t>0?1:t<0?-1:0},kc=Math.sqrt,Ec=Math.tan;function Ac(t){return t>1?0:t<-1?sc:Math.acos(t)}function Sc(t){return t>1?uc:t<-1?-uc:Math.asin(t)}function Tc(t){return(t=wc(t/2))*t}function Mc(){}function Dc(t,e){t&&Oc.hasOwnProperty(t.type)&&Oc[t.type](t,e)}var Cc={Feature:function(t,e){Dc(t.geometry,e)},FeatureCollection:function(t,e){for(var n=t.features,r=-1,i=n.length;++r=0?1:-1,i=r*n,o=yc(e=(e*=hc)/2+cc),a=wc(e),s=Fc*a,u=Pc*o+s*yc(i),c=s*r*wc(i);jc.add(gc(c,u)),Lc=t,Pc=o,Fc=a}var Gc=function(t){return Uc.reset(),qc(t,zc),2*Uc};function Wc(t){return[gc(t[1],t[0]),Sc(t[2])]}function Kc(t){var e=t[0],n=t[1],r=yc(n);return[r*yc(e),r*wc(e),wc(n)]}function Xc(t,e){return t[0]*e[0]+t[1]*e[1]+t[2]*e[2]}function Zc(t,e){return[t[1]*e[2]-t[2]*e[1],t[2]*e[0]-t[0]*e[2],t[0]*e[1]-t[1]*e[0]]}function Jc(t,e){t[0]+=e[0],t[1]+=e[1],t[2]+=e[2]}function Qc(t,e){return[t[0]*e,t[1]*e,t[2]*e]}function tf(t){var e=kc(t[0]*t[0]+t[1]*t[1]+t[2]*t[2]);t[0]/=e,t[1]/=e,t[2]/=e}var ef,nf,rf,of,af,sf,uf,cf,ff,lf,hf=ec(),df={point:pf,lineStart:yf,lineEnd:bf,polygonStart:function(){df.point=mf,df.lineStart=vf,df.lineEnd=_f,hf.reset(),zc.polygonStart()},polygonEnd:function(){zc.polygonEnd(),df.point=pf,df.lineStart=yf,df.lineEnd=bf,jc<0?(ef=-(rf=180),nf=-(of=90)):hf>oc?of=90:hf<-oc&&(nf=-90),lf[0]=ef,lf[1]=rf},sphere:function(){ef=-(rf=180),nf=-(of=90)}};function pf(t,e){ff.push(lf=[ef=t,rf=t]),eof&&(of=e)}function gf(t,e){var n=Kc([t*hc,e*hc]);if(cf){var r=Zc(cf,n),i=Zc([r[1],-r[0],0],r);tf(i),i=Wc(i);var o,a=t-af,s=a>0?1:-1,u=i[0]*lc*s,c=dc(a)>180;c^(s*afof&&(of=o):c^(s*af<(u=(u+360)%360-180)&&uof&&(of=e)),c?twf(ef,rf)&&(rf=t):wf(t,rf)>wf(ef,rf)&&(ef=t):rf>=ef?(trf&&(rf=t)):t>af?wf(ef,t)>wf(ef,rf)&&(rf=t):wf(t,rf)>wf(ef,rf)&&(ef=t)}else ff.push(lf=[ef=t,rf=t]);eof&&(of=e),cf=n,af=t}function yf(){df.point=gf}function bf(){lf[0]=ef,lf[1]=rf,df.point=pf,cf=null}function mf(t,e){if(cf){var n=t-af;hf.add(dc(n)>180?n+(n>0?360:-360):n)}else sf=t,uf=e;zc.point(t,e),gf(t,e)}function vf(){zc.lineStart()}function _f(){mf(sf,uf),zc.lineEnd(),dc(hf)>oc&&(ef=-(rf=180)),lf[0]=ef,lf[1]=rf,cf=null}function wf(t,e){return(e-=t)<0?e+360:e}function xf(t,e){return t[0]-e[0]}function kf(t,e){return t[0]<=t[1]?t[0]<=e&&e<=t[1]:ewf(r[0],r[1])&&(r[1]=i[1]),wf(i[0],r[1])>wf(r[0],r[1])&&(r[0]=i[0])):o.push(r=i);for(a=-1/0,e=0,r=o[n=o.length-1];e<=n;r=i,++e)i=o[e],(s=wf(r[1],i[0]))>a&&(a=s,ef=i[0],rf=r[1])}return ff=lf=null,ef===1/0||nf===1/0?[[NaN,NaN],[NaN,NaN]]:[[ef,nf],[rf,of]]},Uf={sphere:Mc,point:zf,lineStart:Vf,lineEnd:Gf,polygonStart:function(){Uf.lineStart=Wf,Uf.lineEnd=Kf},polygonEnd:function(){Uf.lineStart=Vf,Uf.lineEnd=Gf}};function zf(t,e){t*=hc;var n=yc(e*=hc);Yf(n*yc(t),n*wc(t),wc(e))}function Yf(t,e,n){Sf+=(t-Sf)/++Ef,Tf+=(e-Tf)/Ef,Mf+=(n-Mf)/Ef}function Vf(){Uf.point=Hf}function Hf(t,e){t*=hc;var n=yc(e*=hc);Pf=n*yc(t),Ff=n*wc(t),qf=wc(e),Uf.point=$f,Yf(Pf,Ff,qf)}function $f(t,e){t*=hc;var n=yc(e*=hc),r=n*yc(t),i=n*wc(t),o=wc(e),a=gc(kc((a=Ff*o-qf*i)*a+(a=qf*r-Pf*o)*a+(a=Pf*i-Ff*r)*a),Pf*r+Ff*i+qf*o);Af+=a,Df+=a*(Pf+(Pf=r)),Cf+=a*(Ff+(Ff=i)),Of+=a*(qf+(qf=o)),Yf(Pf,Ff,qf)}function Gf(){Uf.point=zf}function Wf(){Uf.point=Xf}function Kf(){Zf(Bf,Lf),Uf.point=zf}function Xf(t,e){Bf=t,Lf=e,t*=hc,e*=hc,Uf.point=Zf;var n=yc(e);Pf=n*yc(t),Ff=n*wc(t),qf=wc(e),Yf(Pf,Ff,qf)}function Zf(t,e){t*=hc;var n=yc(e*=hc),r=n*yc(t),i=n*wc(t),o=wc(e),a=Ff*o-qf*i,s=qf*r-Pf*o,u=Pf*i-Ff*r,c=kc(a*a+s*s+u*u),f=Sc(c),l=c&&-f/c;Rf+=l*a,If+=l*s,Nf+=l*u,Af+=f,Df+=f*(Pf+(Pf=r)),Cf+=f*(Ff+(Ff=i)),Of+=f*(qf+(qf=o)),Yf(Pf,Ff,qf)}var Jf=function(t){Ef=Af=Sf=Tf=Mf=Df=Cf=Of=Rf=If=Nf=0,qc(t,Uf);var e=Rf,n=If,r=Nf,i=e*e+n*n+r*r;return isc?t+Math.round(-t/fc)*fc:t,e]}function nl(t,e,n){return(t%=fc)?e||n?tl(il(t),ol(e,n)):il(t):e||n?ol(e,n):el}function rl(t){return function(e,n){return[(e+=t)>sc?e-fc:e<-sc?e+fc:e,n]}}function il(t){var e=rl(t);return e.invert=rl(-t),e}function ol(t,e){var n=yc(t),r=wc(t),i=yc(e),o=wc(e);function a(t,e){var a=yc(e),s=yc(t)*a,u=wc(t)*a,c=wc(e),f=c*n+s*r;return[gc(u*i-f*o,s*n-c*r),Sc(f*i+u*o)]}return a.invert=function(t,e){var a=yc(e),s=yc(t)*a,u=wc(t)*a,c=wc(e),f=c*i-u*o;return[gc(u*i+c*o,s*n+f*r),Sc(f*n-s*r)]},a}el.invert=el;var al=function(t){function e(e){return(e=t(e[0]*hc,e[1]*hc))[0]*=lc,e[1]*=lc,e}return t=nl(t[0]*hc,t[1]*hc,t.length>2?t[2]*hc:0),e.invert=function(e){return(e=t.invert(e[0]*hc,e[1]*hc))[0]*=lc,e[1]*=lc,e},e};function sl(t,e,n,r,i,o){if(n){var a=yc(e),s=wc(e),u=r*n;null==i?(i=e+r*fc,o=e-u/2):(i=ul(a,i),o=ul(a,o),(r>0?io)&&(i+=r*fc));for(var c,f=i;r>0?f>o:f1&&e.push(e.pop().concat(e.shift()))},result:function(){var n=e;return e=[],t=null,n}}},ll=function(t,e){return dc(t[0]-e[0])=0;--o)i.point((f=c[o])[0],f[1]);else r(h.x,h.p.x,-1,i);h=h.p}c=(h=h.o).z,d=!d}while(!h.v);i.lineEnd()}}};function pl(t){if(e=t.length){for(var e,n,r=0,i=t[0];++r=0?1:-1,A=E*k,S=A>sc,T=g*w;if(gl.add(gc(T*E*wc(A),y*x+T*yc(A))),a+=S?k+E*fc:k,S^d>=n^v>=n){var M=Zc(Kc(h),Kc(m));tf(M);var D=Zc(o,M);tf(D);var C=(S^k>=0?-1:1)*Sc(D[2]);(r>C||r===C&&(M[0]||M[1]))&&(s+=S^k>=0?1:-1)}}return(a<-oc||a0){for(l||(i.polygonStart(),l=!0),i.lineStart(),t=0;t1&&2&u&&h.push(h.pop().concat(h.shift())),a.push(h.filter(vl))}return h}};function vl(t){return t.length>1}function _l(t,e){return((t=t.x)[0]<0?t[1]-uc-oc:uc-t[1])-((e=e.x)[0]<0?e[1]-uc-oc:uc-e[1])}var wl=ml((function(){return!0}),(function(t){var e,n=NaN,r=NaN,i=NaN;return{lineStart:function(){t.lineStart(),e=1},point:function(o,a){var s=o>0?sc:-sc,u=dc(o-n);dc(u-sc)0?uc:-uc),t.point(i,r),t.lineEnd(),t.lineStart(),t.point(s,r),t.point(o,r),e=0):i!==s&&u>=sc&&(dc(n-i)oc?pc((wc(e)*(o=yc(r))*wc(n)-wc(r)*(i=yc(e))*wc(t))/(i*o*a)):(e+r)/2}(n,r,o,a),t.point(i,r),t.lineEnd(),t.lineStart(),t.point(s,r),e=0),t.point(n=o,r=a),i=s},lineEnd:function(){t.lineEnd(),n=r=NaN},clean:function(){return 2-e}}}),(function(t,e,n,r){var i;if(null==t)i=n*uc,r.point(-sc,i),r.point(0,i),r.point(sc,i),r.point(sc,0),r.point(sc,-i),r.point(0,-i),r.point(-sc,-i),r.point(-sc,0),r.point(-sc,i);else if(dc(t[0]-e[0])>oc){var o=t[0]0,i=dc(e)>oc;function o(t,n){return yc(t)*yc(n)>e}function a(t,n,r){var i=[1,0,0],o=Zc(Kc(t),Kc(n)),a=Xc(o,o),s=o[0],u=a-s*s;if(!u)return!r&&t;var c=e*a/u,f=-e*s/u,l=Zc(i,o),h=Qc(i,c);Jc(h,Qc(o,f));var d=l,p=Xc(h,d),g=Xc(d,d),y=p*p-g*(Xc(h,h)-1);if(!(y<0)){var b=kc(y),m=Qc(d,(-p-b)/g);if(Jc(m,h),m=Wc(m),!r)return m;var v,_=t[0],w=n[0],x=t[1],k=n[1];w<_&&(v=_,_=w,w=v);var E=w-_,A=dc(E-sc)0^m[1]<(dc(m[0]-_)sc^(_<=m[0]&&m[0]<=w)){var S=Qc(d,(-p+b)/g);return Jc(S,h),[m,Wc(S)]}}}function s(e,n){var i=r?t:sc-t,o=0;return e<-i?o|=1:e>i&&(o|=2),n<-i?o|=4:n>i&&(o|=8),o}return ml(o,(function(t){var e,n,u,c,f;return{lineStart:function(){c=u=!1,f=1},point:function(l,h){var d,p=[l,h],g=o(l,h),y=r?g?0:s(l,h):g?s(l+(l<0?sc:-sc),h):0;if(!e&&(c=u=g)&&t.lineStart(),g!==u&&(!(d=a(e,p))||ll(e,d)||ll(p,d))&&(p[0]+=oc,p[1]+=oc,g=o(p[0],p[1])),g!==u)f=0,g?(t.lineStart(),d=a(p,e),t.point(d[0],d[1])):(d=a(e,p),t.point(d[0],d[1]),t.lineEnd()),e=d;else if(i&&e&&r^g){var b;y&n||!(b=a(p,e,!0))||(f=0,r?(t.lineStart(),t.point(b[0][0],b[0][1]),t.point(b[1][0],b[1][1]),t.lineEnd()):(t.point(b[1][0],b[1][1]),t.lineEnd(),t.lineStart(),t.point(b[0][0],b[0][1])))}!g||e&&ll(e,p)||t.point(p[0],p[1]),e=p,u=g,n=y},lineEnd:function(){u&&t.lineEnd(),e=null},clean:function(){return f|(c&&u)<<1}}}),(function(e,r,i,o){sl(o,t,n,i,e,r)}),r?[0,-t]:[-sc,t-sc])},kl=function(t,e,n,r,i,o){var a,s=t[0],u=t[1],c=0,f=1,l=e[0]-s,h=e[1]-u;if(a=n-s,l||!(a>0)){if(a/=l,l<0){if(a0){if(a>f)return;a>c&&(c=a)}if(a=i-s,l||!(a<0)){if(a/=l,l<0){if(a>f)return;a>c&&(c=a)}else if(l>0){if(a0)){if(a/=h,h<0){if(a0){if(a>f)return;a>c&&(c=a)}if(a=o-u,h||!(a<0)){if(a/=h,h<0){if(a>f)return;a>c&&(c=a)}else if(h>0){if(a0&&(t[0]=s+c*l,t[1]=u+c*h),f<1&&(e[0]=s+f*l,e[1]=u+f*h),!0}}}}},El=1e9,Al=-El;function Sl(t,e,n,r){function i(i,o){return t<=i&&i<=n&&e<=o&&o<=r}function o(i,o,s,c){var f=0,l=0;if(null==i||(f=a(i,s))!==(l=a(o,s))||u(i,o)<0^s>0)do{c.point(0===f||3===f?t:n,f>1?r:e)}while((f=(f+s+4)%4)!==l);else c.point(o[0],o[1])}function a(r,i){return dc(r[0]-t)0?0:3:dc(r[0]-n)0?2:1:dc(r[1]-e)0?1:0:i>0?3:2}function s(t,e){return u(t.x,e.x)}function u(t,e){var n=a(t,1),r=a(e,1);return n!==r?n-r:0===n?e[1]-t[1]:1===n?t[0]-e[0]:2===n?t[1]-e[1]:e[0]-t[0]}return function(a){var u,c,f,l,h,d,p,g,y,b,m,v=a,_=fl(),w={point:x,lineStart:function(){w.point=k,c&&c.push(f=[]);b=!0,y=!1,p=g=NaN},lineEnd:function(){u&&(k(l,h),d&&y&&_.rejoin(),u.push(_.result()));w.point=x,y&&v.lineEnd()},polygonStart:function(){v=_,u=[],c=[],m=!0},polygonEnd:function(){var e=function(){for(var e=0,n=0,i=c.length;nr&&(h-o)*(r-a)>(d-a)*(t-o)&&++e:d<=r&&(h-o)*(r-a)<(d-a)*(t-o)&&--e;return e}(),n=m&&e,i=(u=P(u)).length;(n||i)&&(a.polygonStart(),n&&(a.lineStart(),o(null,null,1,a),a.lineEnd()),i&&dl(u,s,e,o,a),a.polygonEnd());v=a,u=c=f=null}};function x(t,e){i(t,e)&&v.point(t,e)}function k(o,a){var s=i(o,a);if(c&&f.push([o,a]),b)l=o,h=a,d=s,b=!1,s&&(v.lineStart(),v.point(o,a));else if(s&&y)v.point(o,a);else{var u=[p=Math.max(Al,Math.min(El,p)),g=Math.max(Al,Math.min(El,g))],_=[o=Math.max(Al,Math.min(El,o)),a=Math.max(Al,Math.min(El,a))];kl(u,_,t,e,n,r)?(y||(v.lineStart(),v.point(u[0],u[1])),v.point(_[0],_[1]),s||v.lineEnd(),m=!1):s&&(v.lineStart(),v.point(o,a),m=!1)}p=o,g=a,y=s}return w}}var Tl,Ml,Dl,Cl=function(){var t,e,n,r=0,i=0,o=960,a=500;return n={stream:function(n){return t&&e===n?t:t=Sl(r,i,o,a)(e=n)},extent:function(s){return arguments.length?(r=+s[0][0],i=+s[0][1],o=+s[1][0],a=+s[1][1],t=e=null,n):[[r,i],[o,a]]}}},Ol=ec(),Rl={sphere:Mc,point:Mc,lineStart:function(){Rl.point=Nl,Rl.lineEnd=Il},lineEnd:Mc,polygonStart:Mc,polygonEnd:Mc};function Il(){Rl.point=Rl.lineEnd=Mc}function Nl(t,e){Tl=t*=hc,Ml=wc(e*=hc),Dl=yc(e),Rl.point=Bl}function Bl(t,e){t*=hc;var n=wc(e*=hc),r=yc(e),i=dc(t-Tl),o=yc(i),a=r*wc(i),s=Dl*n-Ml*r*o,u=Ml*n+Dl*r*o;Ol.add(gc(kc(a*a+s*s),u)),Tl=t,Ml=n,Dl=r}var Ll=function(t){return Ol.reset(),qc(t,Rl),+Ol},Pl=[null,null],Fl={type:"LineString",coordinates:Pl},ql=function(t,e){return Pl[0]=t,Pl[1]=e,Ll(Fl)},jl={Feature:function(t,e){return zl(t.geometry,e)},FeatureCollection:function(t,e){for(var n=t.features,r=-1,i=n.length;++r0&&(i=ql(t[o],t[o-1]))>0&&n<=i&&r<=i&&(n+r-i)*(1-Math.pow((n-r)/i,2))oc})).map(u)).concat(x(bc(o/d)*d,i,d).filter((function(t){return dc(t%g)>oc})).map(c))}return b.lines=function(){return m().map((function(t){return{type:"LineString",coordinates:t}}))},b.outline=function(){return{type:"Polygon",coordinates:[f(r).concat(l(a).slice(1),f(n).reverse().slice(1),l(s).reverse().slice(1))]}},b.extent=function(t){return arguments.length?b.extentMajor(t).extentMinor(t):b.extentMinor()},b.extentMajor=function(t){return arguments.length?(r=+t[0][0],n=+t[1][0],s=+t[0][1],a=+t[1][1],r>n&&(t=r,r=n,n=t),s>a&&(t=s,s=a,a=t),b.precision(y)):[[r,s],[n,a]]},b.extentMinor=function(n){return arguments.length?(e=+n[0][0],t=+n[1][0],o=+n[0][1],i=+n[1][1],e>t&&(n=e,e=t,t=n),o>i&&(n=o,o=i,i=n),b.precision(y)):[[e,o],[t,i]]},b.step=function(t){return arguments.length?b.stepMajor(t).stepMinor(t):b.stepMinor()},b.stepMajor=function(t){return arguments.length?(p=+t[0],g=+t[1],b):[p,g]},b.stepMinor=function(t){return arguments.length?(h=+t[0],d=+t[1],b):[h,d]},b.precision=function(h){return arguments.length?(y=+h,u=Kl(o,i,90),c=Xl(e,t,y),f=Kl(s,a,90),l=Xl(r,n,y),b):y},b.extentMajor([[-180,-90+oc],[180,90-oc]]).extentMinor([[-180,-80-oc],[180,80+oc]])}function Jl(){return Zl()()}var Ql,th,eh,nh,rh=function(t,e){var n=t[0]*hc,r=t[1]*hc,i=e[0]*hc,o=e[1]*hc,a=yc(r),s=wc(r),u=yc(o),c=wc(o),f=a*yc(n),l=a*wc(n),h=u*yc(i),d=u*wc(i),p=2*Sc(kc(Tc(o-r)+a*u*Tc(i-n))),g=wc(p),y=p?function(t){var e=wc(t*=p)/g,n=wc(p-t)/g,r=n*f+e*h,i=n*l+e*d,o=n*s+e*c;return[gc(i,r)*lc,gc(o,kc(r*r+i*i))*lc]}:function(){return[n*lc,r*lc]};return y.distance=p,y},ih=function(t){return t},oh=ec(),ah=ec(),sh={point:Mc,lineStart:Mc,lineEnd:Mc,polygonStart:function(){sh.lineStart=uh,sh.lineEnd=lh},polygonEnd:function(){sh.lineStart=sh.lineEnd=sh.point=Mc,oh.add(dc(ah)),ah.reset()},result:function(){var t=oh/2;return oh.reset(),t}};function uh(){sh.point=ch}function ch(t,e){sh.point=fh,Ql=eh=t,th=nh=e}function fh(t,e){ah.add(nh*t-eh*e),eh=t,nh=e}function lh(){fh(Ql,th)}var hh=sh,dh=1/0,ph=dh,gh=-dh,yh=gh;var bh,mh,vh,_h,wh={point:function(t,e){tgh&&(gh=t);eyh&&(yh=e)},lineStart:Mc,lineEnd:Mc,polygonStart:Mc,polygonEnd:Mc,result:function(){var t=[[dh,ph],[gh,yh]];return gh=yh=-(ph=dh=1/0),t}},xh=0,kh=0,Eh=0,Ah=0,Sh=0,Th=0,Mh=0,Dh=0,Ch=0,Oh={point:Rh,lineStart:Ih,lineEnd:Lh,polygonStart:function(){Oh.lineStart=Ph,Oh.lineEnd=Fh},polygonEnd:function(){Oh.point=Rh,Oh.lineStart=Ih,Oh.lineEnd=Lh},result:function(){var t=Ch?[Mh/Ch,Dh/Ch]:Th?[Ah/Th,Sh/Th]:Eh?[xh/Eh,kh/Eh]:[NaN,NaN];return xh=kh=Eh=Ah=Sh=Th=Mh=Dh=Ch=0,t}};function Rh(t,e){xh+=t,kh+=e,++Eh}function Ih(){Oh.point=Nh}function Nh(t,e){Oh.point=Bh,Rh(vh=t,_h=e)}function Bh(t,e){var n=t-vh,r=e-_h,i=kc(n*n+r*r);Ah+=i*(vh+t)/2,Sh+=i*(_h+e)/2,Th+=i,Rh(vh=t,_h=e)}function Lh(){Oh.point=Rh}function Ph(){Oh.point=qh}function Fh(){jh(bh,mh)}function qh(t,e){Oh.point=jh,Rh(bh=vh=t,mh=_h=e)}function jh(t,e){var n=t-vh,r=e-_h,i=kc(n*n+r*r);Ah+=i*(vh+t)/2,Sh+=i*(_h+e)/2,Th+=i,Mh+=(i=_h*t-vh*e)*(vh+t),Dh+=i*(_h+e),Ch+=3*i,Rh(vh=t,_h=e)}var Uh=Oh;function zh(t){this._context=t}zh.prototype={_radius:4.5,pointRadius:function(t){return this._radius=t,this},polygonStart:function(){this._line=0},polygonEnd:function(){this._line=NaN},lineStart:function(){this._point=0},lineEnd:function(){0===this._line&&this._context.closePath(),this._point=NaN},point:function(t,e){switch(this._point){case 0:this._context.moveTo(t,e),this._point=1;break;case 1:this._context.lineTo(t,e);break;default:this._context.moveTo(t+this._radius,e),this._context.arc(t,e,this._radius,0,fc)}},result:Mc};var Yh,Vh,Hh,$h,Gh,Wh=ec(),Kh={point:Mc,lineStart:function(){Kh.point=Xh},lineEnd:function(){Yh&&Zh(Vh,Hh),Kh.point=Mc},polygonStart:function(){Yh=!0},polygonEnd:function(){Yh=null},result:function(){var t=+Wh;return Wh.reset(),t}};function Xh(t,e){Kh.point=Zh,Vh=$h=t,Hh=Gh=e}function Zh(t,e){$h-=t,Gh-=e,Wh.add(kc($h*$h+Gh*Gh)),$h=t,Gh=e}var Jh=Kh;function Qh(){this._string=[]}function td(t){return"m0,"+t+"a"+t+","+t+" 0 1,1 0,"+-2*t+"a"+t+","+t+" 0 1,1 0,"+2*t+"z"}Qh.prototype={_radius:4.5,_circle:td(4.5),pointRadius:function(t){return(t=+t)!==this._radius&&(this._radius=t,this._circle=null),this},polygonStart:function(){this._line=0},polygonEnd:function(){this._line=NaN},lineStart:function(){this._point=0},lineEnd:function(){0===this._line&&this._string.push("Z"),this._point=NaN},point:function(t,e){switch(this._point){case 0:this._string.push("M",t,",",e),this._point=1;break;case 1:this._string.push("L",t,",",e);break;default:null==this._circle&&(this._circle=td(this._radius)),this._string.push("M",t,",",e,this._circle)}},result:function(){if(this._string.length){var t=this._string.join("");return this._string=[],t}return null}};var ed=function(t,e){var n,r,i=4.5;function o(t){return t&&("function"==typeof i&&r.pointRadius(+i.apply(this,arguments)),qc(t,n(r))),r.result()}return o.area=function(t){return qc(t,n(hh)),hh.result()},o.measure=function(t){return qc(t,n(Jh)),Jh.result()},o.bounds=function(t){return qc(t,n(wh)),wh.result()},o.centroid=function(t){return qc(t,n(Uh)),Uh.result()},o.projection=function(e){return arguments.length?(n=null==e?(t=null,ih):(t=e).stream,o):t},o.context=function(t){return arguments.length?(r=null==t?(e=null,new Qh):new zh(e=t),"function"!=typeof i&&r.pointRadius(i),o):e},o.pointRadius=function(t){return arguments.length?(i="function"==typeof t?t:(r.pointRadius(+t),+t),o):i},o.projection(t).context(e)},nd=function(t){return{stream:rd(t)}};function rd(t){return function(e){var n=new id;for(var r in t)n[r]=t[r];return n.stream=e,n}}function id(){}function od(t,e,n){var r=t.clipExtent&&t.clipExtent();return t.scale(150).translate([0,0]),null!=r&&t.clipExtent(null),qc(n,t.stream(wh)),e(wh.result()),null!=r&&t.clipExtent(r),t}function ad(t,e,n){return od(t,(function(n){var r=e[1][0]-e[0][0],i=e[1][1]-e[0][1],o=Math.min(r/(n[1][0]-n[0][0]),i/(n[1][1]-n[0][1])),a=+e[0][0]+(r-o*(n[1][0]+n[0][0]))/2,s=+e[0][1]+(i-o*(n[1][1]+n[0][1]))/2;t.scale(150*o).translate([a,s])}),n)}function sd(t,e,n){return ad(t,[[0,0],e],n)}function ud(t,e,n){return od(t,(function(n){var r=+e,i=r/(n[1][0]-n[0][0]),o=(r-i*(n[1][0]+n[0][0]))/2,a=-i*n[0][1];t.scale(150*i).translate([o,a])}),n)}function cd(t,e,n){return od(t,(function(n){var r=+e,i=r/(n[1][1]-n[0][1]),o=-i*n[0][0],a=(r-i*(n[1][1]+n[0][1]))/2;t.scale(150*i).translate([o,a])}),n)}id.prototype={constructor:id,point:function(t,e){this.stream.point(t,e)},sphere:function(){this.stream.sphere()},lineStart:function(){this.stream.lineStart()},lineEnd:function(){this.stream.lineEnd()},polygonStart:function(){this.stream.polygonStart()},polygonEnd:function(){this.stream.polygonEnd()}};var fd=16,ld=yc(30*hc),hd=function(t,e){return+e?function(t,e){function n(r,i,o,a,s,u,c,f,l,h,d,p,g,y){var b=c-r,m=f-i,v=b*b+m*m;if(v>4*e&&g--){var _=a+h,w=s+d,x=u+p,k=kc(_*_+w*w+x*x),E=Sc(x/=k),A=dc(dc(x)-1)e||dc((b*D+m*C)/v-.5)>.3||a*h+s*d+u*p2?t[2]%360*hc:0,T()):[y*lc,b*lc,m*lc]},A.angle=function(t){return arguments.length?(v=t%360*hc,T()):v*lc},A.precision=function(t){return arguments.length?(a=hd(s,E=t*t),M()):kc(E)},A.fitExtent=function(t,e){return ad(A,t,e)},A.fitSize=function(t,e){return sd(A,t,e)},A.fitWidth=function(t,e){return ud(A,t,e)},A.fitHeight=function(t,e){return cd(A,t,e)},function(){return e=t.apply(this,arguments),A.invert=e.invert&&S,T()}}function md(t){var e=0,n=sc/3,r=bd(t),i=r(e,n);return i.parallels=function(t){return arguments.length?r(e=t[0]*hc,n=t[1]*hc):[e*lc,n*lc]},i}function vd(t,e){var n=wc(t),r=(n+wc(e))/2;if(dc(r)=.12&&i<.234&&r>=-.425&&r<-.214?s:i>=.166&&i<.234&&r>=-.214&&r<-.115?u:a).invert(t)},f.stream=function(n){return t&&e===n?t:(r=[a.stream(e=n),s.stream(n),u.stream(n)],i=r.length,t={point:function(t,e){for(var n=-1;++n0?e<-uc+oc&&(e=-uc+oc):e>uc-oc&&(e=uc-oc);var n=i/_c(Rd(e),r);return[n*wc(r*t),i-n*yc(r*t)]}return o.invert=function(t,e){var n=i-e,o=xc(r)*kc(t*t+n*n);return[gc(t,dc(n))/r*xc(n),2*pc(_c(i/o,1/r))-uc]},o}var Nd=function(){return md(Id).scale(109.5).parallels([30,30])};function Bd(t,e){return[t,e]}Bd.invert=Bd;var Ld=function(){return yd(Bd).scale(152.63)};function Pd(t,e){var n=yc(t),r=t===e?wc(t):(n-yc(e))/(e-t),i=n/r+t;if(dc(r)oc&&--i>0);return[t/(.8707+(o=r*r)*(o*(o*o*o*(.003971-.001529*o)-.013791)-.131979)),r]};var Zd=function(){return yd(Xd).scale(175.295)};function Jd(t,e){return[yc(e)*wc(t),wc(e)]}Jd.invert=Ed(Sc);var Qd=function(){return yd(Jd).scale(249.5).clipAngle(90+oc)};function tp(t,e){var n=yc(e),r=1+yc(t)*n;return[n*wc(t)/r,wc(e)/r]}tp.invert=Ed((function(t){return 2*pc(t)}));var ep=function(){return yd(tp).scale(250).clipAngle(142)};function np(t,e){return[vc(Ec((uc+e)/2)),-t]}np.invert=function(t,e){return[-e,2*pc(mc(t))-uc]};var rp=function(){var t=Od(np),e=t.center,n=t.rotate;return t.center=function(t){return arguments.length?e([-t[1],t[0]]):[(t=e())[1],-t[0]]},t.rotate=function(t){return arguments.length?n([t[0],t[1],t.length>2?t[2]+90:90]):[(t=n())[0],t[1],t[2]-90]},n([0,0,90]).scale(159.155)};function ip(t,e){return t.parent===e.parent?1:2}function op(t,e){return t+e.x}function ap(t,e){return Math.max(t,e.y)}var sp=function(){var t=ip,e=1,n=1,r=!1;function i(i){var o,a=0;i.eachAfter((function(e){var n=e.children;n?(e.x=function(t){return t.reduce(op,0)/t.length}(n),e.y=function(t){return 1+t.reduce(ap,0)}(n)):(e.x=o?a+=t(e,o):0,e.y=0,o=e)}));var s=function(t){for(var e;e=t.children;)t=e[0];return t}(i),u=function(t){for(var e;e=t.children;)t=e[e.length-1];return t}(i),c=s.x-t(s,u)/2,f=u.x+t(u,s)/2;return i.eachAfter(r?function(t){t.x=(t.x-i.x)*e,t.y=(i.y-t.y)*n}:function(t){t.x=(t.x-c)/(f-c)*e,t.y=(1-(i.y?t.y/i.y:1))*n})}return i.separation=function(e){return arguments.length?(t=e,i):t},i.size=function(t){return arguments.length?(r=!1,e=+t[0],n=+t[1],i):r?null:[e,n]},i.nodeSize=function(t){return arguments.length?(r=!0,e=+t[0],n=+t[1],i):r?[e,n]:null},i};function up(t){var e=0,n=t.children,r=n&&n.length;if(r)for(;--r>=0;)e+=n[r].value;else e=1;t.value=e}function cp(t,e){var n,r,i,o,a,s=new dp(t),u=+t.value&&(s.value=t.value),c=[s];for(null==e&&(e=fp);n=c.pop();)if(u&&(n.value=+n.data.value),(i=e(n.data))&&(a=i.length))for(n.children=new Array(a),o=a-1;o>=0;--o)c.push(r=n.children[o]=new dp(i[o])),r.parent=n,r.depth=n.depth+1;return s.eachBefore(hp)}function fp(t){return t.children}function lp(t){t.data=t.data.data}function hp(t){var e=0;do{t.height=e}while((t=t.parent)&&t.height<++e)}function dp(t){this.data=t,this.depth=this.height=0,this.parent=null}dp.prototype=cp.prototype={constructor:dp,count:function(){return this.eachAfter(up)},each:function(t){var e,n,r,i,o=this,a=[o];do{for(e=a.reverse(),a=[];o=e.pop();)if(t(o),n=o.children)for(r=0,i=n.length;r=0;--n)i.push(e[n]);return this},sum:function(t){return this.eachAfter((function(e){for(var n=+t(e.data)||0,r=e.children,i=r&&r.length;--i>=0;)n+=r[i].value;e.value=n}))},sort:function(t){return this.eachBefore((function(e){e.children&&e.children.sort(t)}))},path:function(t){for(var e=this,n=function(t,e){if(t===e)return t;var n=t.ancestors(),r=e.ancestors(),i=null;t=n.pop(),e=r.pop();for(;t===e;)i=t,t=n.pop(),e=r.pop();return i}(e,t),r=[e];e!==n;)e=e.parent,r.push(e);for(var i=r.length;t!==n;)r.splice(i,0,t),t=t.parent;return r},ancestors:function(){for(var t=this,e=[t];t=t.parent;)e.push(t);return e},descendants:function(){var t=[];return this.each((function(e){t.push(e)})),t},leaves:function(){var t=[];return this.eachBefore((function(e){e.children||t.push(e)})),t},links:function(){var t=this,e=[];return t.each((function(n){n!==t&&e.push({source:n.parent,target:n})})),e},copy:function(){return cp(this).eachBefore(lp)}};var pp=Array.prototype.slice;var gp=function(t){for(var e,n,r=0,i=(t=function(t){for(var e,n,r=t.length;r;)n=Math.random()*r--|0,e=t[r],t[r]=t[n],t[n]=e;return t}(pp.call(t))).length,o=[];r0&&n*n>r*r+i*i}function vp(t,e){for(var n=0;n(a*=a)?(r=(c+a-i)/(2*c),o=Math.sqrt(Math.max(0,a/c-r*r)),n.x=t.x-r*s-o*u,n.y=t.y-r*u+o*s):(r=(c+i-a)/(2*c),o=Math.sqrt(Math.max(0,i/c-r*r)),n.x=e.x+r*s-o*u,n.y=e.y+r*u+o*s)):(n.x=e.x+n.r,n.y=e.y)}function Ep(t,e){var n=t.r+e.r-1e-6,r=e.x-t.x,i=e.y-t.y;return n>0&&n*n>r*r+i*i}function Ap(t){var e=t._,n=t.next._,r=e.r+n.r,i=(e.x*n.r+n.x*e.r)/r,o=(e.y*n.r+n.y*e.r)/r;return i*i+o*o}function Sp(t){this._=t,this.next=null,this.previous=null}function Tp(t){if(!(i=t.length))return 0;var e,n,r,i,o,a,s,u,c,f,l;if((e=t[0]).x=0,e.y=0,!(i>1))return e.r;if(n=t[1],e.x=-n.r,n.x=e.r,n.y=0,!(i>2))return e.r+n.r;kp(n,e,r=t[2]),e=new Sp(e),n=new Sp(n),r=new Sp(r),e.next=r.previous=n,n.next=e.previous=r,r.next=n.previous=e;t:for(s=3;s0)throw new Error("cycle");return o}return n.id=function(e){return arguments.length?(t=Cp(e),n):t},n.parentId=function(t){return arguments.length?(e=Cp(t),n):e},n};function Gp(t,e){return t.parent===e.parent?1:2}function Wp(t){var e=t.children;return e?e[0]:t.t}function Kp(t){var e=t.children;return e?e[e.length-1]:t.t}function Xp(t,e,n){var r=n/(e.i-t.i);e.c-=r,e.s+=n,t.c+=r,e.z+=n,e.m+=n}function Zp(t,e,n){return t.a.parent===e.parent?t.a:n}function Jp(t,e){this._=t,this.parent=null,this.children=null,this.A=null,this.a=this,this.z=0,this.m=0,this.c=0,this.s=0,this.t=null,this.i=e}Jp.prototype=Object.create(dp.prototype);var Qp=function(){var t=Gp,e=1,n=1,r=null;function i(i){var u=function(t){for(var e,n,r,i,o,a=new Jp(t,0),s=[a];e=s.pop();)if(r=e._.children)for(e.children=new Array(o=r.length),i=o-1;i>=0;--i)s.push(n=e.children[i]=new Jp(r[i],i)),n.parent=e;return(a.parent=new Jp(null,0)).children=[a],a}(i);if(u.eachAfter(o),u.parent.m=-u.z,u.eachBefore(a),r)i.eachBefore(s);else{var c=i,f=i,l=i;i.eachBefore((function(t){t.xf.x&&(f=t),t.depth>l.depth&&(l=t)}));var h=c===f?1:t(c,f)/2,d=h-c.x,p=e/(f.x+h+d),g=n/(l.depth||1);i.eachBefore((function(t){t.x=(t.x+d)*p,t.y=t.depth*g}))}return i}function o(e){var n=e.children,r=e.parent.children,i=e.i?r[e.i-1]:null;if(n){!function(t){for(var e,n=0,r=0,i=t.children,o=i.length;--o>=0;)(e=i[o]).z+=n,e.m+=n,n+=e.s+(r+=e.c)}(e);var o=(n[0].z+n[n.length-1].z)/2;i?(e.z=i.z+t(e._,i._),e.m=e.z-o):e.z=o}else i&&(e.z=i.z+t(e._,i._));e.parent.A=function(e,n,r){if(n){for(var i,o=e,a=e,s=n,u=o.parent.children[0],c=o.m,f=a.m,l=s.m,h=u.m;s=Kp(s),o=Wp(o),s&&o;)u=Wp(u),(a=Kp(a)).a=e,(i=s.z+l-o.z-c+t(s._,o._))>0&&(Xp(Zp(s,e,r),e,i),c+=i,f+=i),l+=s.m,c+=o.m,h+=u.m,f+=a.m;s&&!Kp(a)&&(a.t=s,a.m+=l-f),o&&!Wp(u)&&(u.t=o,u.m+=c-h,r=e)}return r}(e,i,e.parent.A||r[0])}function a(t){t._.x=t.z+t.parent.m,t.m+=t.parent.m}function s(t){t.x*=e,t.y=t.depth*n}return i.separation=function(e){return arguments.length?(t=e,i):t},i.size=function(t){return arguments.length?(r=!1,e=+t[0],n=+t[1],i):r?null:[e,n]},i.nodeSize=function(t){return arguments.length?(r=!0,e=+t[0],n=+t[1],i):r?[e,n]:null},i},tg=function(t,e,n,r,i){for(var o,a=t.children,s=-1,u=a.length,c=t.value&&(i-n)/t.value;++sh&&(h=s),y=f*f*g,(d=Math.max(h/y,y/l))>p){f-=s;break}p=d}b.push(a={value:f,dice:u1?e:1)},n}(eg),ig=function(){var t=rg,e=!1,n=1,r=1,i=[0],o=Op,a=Op,s=Op,u=Op,c=Op;function f(t){return t.x0=t.y0=0,t.x1=n,t.y1=r,t.eachBefore(l),i=[0],e&&t.eachBefore(Fp),t}function l(e){var n=i[e.depth],r=e.x0+n,f=e.y0+n,l=e.x1-n,h=e.y1-n;l=n-1){var f=s[e];return f.x0=i,f.y0=o,f.x1=a,void(f.y1=u)}var l=c[e],h=r/2+l,d=e+1,p=n-1;for(;d>>1;c[g]u-o){var m=(i*b+a*y)/r;t(e,d,y,i,o,m,u),t(d,n,b,m,o,a,u)}else{var v=(o*b+u*y)/r;t(e,d,y,i,o,a,v),t(d,n,b,i,v,a,u)}}(0,u,t.value,e,n,r,i)},ag=function(t,e,n,r,i){(1&t.depth?tg:qp)(t,e,n,r,i)},sg=function t(e){function n(t,n,r,i,o){if((a=t._squarify)&&a.ratio===e)for(var a,s,u,c,f,l=-1,h=a.length,d=t.value;++l1?e:1)},n}(eg),ug=function(t){for(var e,n=-1,r=t.length,i=t[r-1],o=0;++n1&&fg(t[n[r-2]],t[n[r-1]],t[i])<=0;)--r;n[r++]=i}return n.slice(0,r)}var dg=function(t){if((n=t.length)<3)return null;var e,n,r=new Array(n),i=new Array(n);for(e=0;e=0;--e)c.push(t[r[o[e]][2]]);for(e=+s;es!=c>s&&a<(u-n)*(s-r)/(c-r)+n&&(f=!f),u=n,c=r;return f},gg=function(t){for(var e,n,r=-1,i=t.length,o=t[i-1],a=o[0],s=o[1],u=0;++r1);return t+n*o*Math.sqrt(-2*Math.log(i)/i)}}return n.source=t,n}(yg),vg=function t(e){function n(){var t=mg.source(e).apply(this,arguments);return function(){return Math.exp(t())}}return n.source=t,n}(yg),_g=function t(e){function n(t){return function(){for(var n=0,r=0;rr&&(e=n,n=r,r=e),function(t){return Math.max(n,Math.min(r,t))}}function Fg(t,e,n){var r=t[0],i=t[1],o=e[0],a=e[1];return i2?qg:Fg,i=o=null,l}function l(e){return isNaN(e=+e)?n:(i||(i=r(a.map(t),s,u)))(t(c(e)))}return l.invert=function(n){return c(e((o||(o=r(s,a.map(t),Un)))(n)))},l.domain=function(t){return arguments.length?(a=Sg.call(t,Ig),c===Bg||(c=Pg(a)),f()):a.slice()},l.range=function(t){return arguments.length?(s=Tg.call(t),f()):s.slice()},l.rangeRound=function(t){return s=Tg.call(t),u=Qn,f()},l.clamp=function(t){return arguments.length?(c=t?Pg(a):Bg,l):c!==Bg},l.interpolate=function(t){return arguments.length?(u=t,f()):u},l.unknown=function(t){return arguments.length?(n=t,l):n},function(n,r){return t=n,e=r,f()}}function zg(t,e){return Ug()(t,e)}var Yg=function(t,e,n,r){var i,o=M(t,e,n);switch((r=Fu(null==r?",f":r)).type){case"s":var a=Math.max(Math.abs(t),Math.abs(e));return null!=r.precision||isNaN(i=Qu(o,a))||(r.precision=i),Yu(r,a);case"":case"e":case"g":case"p":case"r":null!=r.precision||isNaN(i=tc(o,Math.max(Math.abs(t),Math.abs(e))))||(r.precision=i-("e"===r.type));break;case"f":case"%":null!=r.precision||isNaN(i=Ju(o))||(r.precision=i-2*("%"===r.type))}return zu(r)};function Vg(t){var e=t.domain;return t.ticks=function(t){var n=e();return S(n[0],n[n.length-1],null==t?10:t)},t.tickFormat=function(t,n){var r=e();return Yg(r[0],r[r.length-1],null==t?10:t,n)},t.nice=function(n){null==n&&(n=10);var r,i=e(),o=0,a=i.length-1,s=i[o],u=i[a];return u0?r=T(s=Math.floor(s/r)*r,u=Math.ceil(u/r)*r,n):r<0&&(r=T(s=Math.ceil(s*r)/r,u=Math.floor(u*r)/r,n)),r>0?(i[o]=Math.floor(s/r)*r,i[a]=Math.ceil(u/r)*r,e(i)):r<0&&(i[o]=Math.ceil(s*r)/r,i[a]=Math.floor(u*r)/r,e(i)),t},t}function Hg(){var t=zg(Bg,Bg);return t.copy=function(){return jg(t,Hg())},kg.apply(t,arguments),Vg(t)}function $g(t){var e;function n(t){return isNaN(t=+t)?e:t}return n.invert=n,n.domain=n.range=function(e){return arguments.length?(t=Sg.call(e,Ig),n):t.slice()},n.unknown=function(t){return arguments.length?(e=t,n):e},n.copy=function(){return $g(t).unknown(e)},t=arguments.length?Sg.call(t,Ig):[0,1],Vg(n)}var Gg=function(t,e){var n,r=0,i=(t=t.slice()).length-1,o=t[r],a=t[i];return a0){for(;hu)break;g.push(l)}}else for(;h=1;--f)if(!((l=c*f)u)break;g.push(l)}}else g=S(h,d,Math.min(d-h,p)).map(n);return r?g.reverse():g},r.tickFormat=function(t,i){if(null==i&&(i=10===o?".0e":","),"function"!=typeof i&&(i=zu(i)),t===1/0)return i;null==t&&(t=10);var a=Math.max(1,o*t/r.ticks().length);return function(t){var r=t/n(Math.round(e(t)));return r*o0?i[r-1]:e[0],r=r?[i[r-1],n]:[i[a-1],i[a]]},a.unknown=function(e){return arguments.length?(t=e,a):a},a.thresholds=function(){return i.slice()},a.copy=function(){return dy().domain([e,n]).range(o).unknown(t)},kg.apply(Vg(a),arguments)}function py(){var t,e=[.5],n=[0,1],r=1;function i(i){return i<=i?n[u(e,i,0,r)]:t}return i.domain=function(t){return arguments.length?(e=Tg.call(t),r=Math.min(e.length,n.length-1),i):e.slice()},i.range=function(t){return arguments.length?(n=Tg.call(t),r=Math.min(e.length,n.length-1),i):n.slice()},i.invertExtent=function(t){var r=n.indexOf(t);return[e[r-1],e[r]]},i.unknown=function(e){return arguments.length?(t=e,i):t},i.copy=function(){return py().domain(e).range(n).unknown(t)},kg.apply(i,arguments)}var gy=new Date,yy=new Date;function by(t,e,n,r){function i(e){return t(e=0===arguments.length?new Date:new Date(+e)),e}return i.floor=function(e){return t(e=new Date(+e)),e},i.ceil=function(n){return t(n=new Date(n-1)),e(n,1),t(n),n},i.round=function(t){var e=i(t),n=i.ceil(t);return t-e0))return s;do{s.push(a=new Date(+n)),e(n,o),t(n)}while(a=e)for(;t(e),!n(e);)e.setTime(e-1)}),(function(t,r){if(t>=t)if(r<0)for(;++r<=0;)for(;e(t,-1),!n(t););else for(;--r>=0;)for(;e(t,1),!n(t););}))},n&&(i.count=function(e,r){return gy.setTime(+e),yy.setTime(+r),t(gy),t(yy),Math.floor(n(gy,yy))},i.every=function(t){return t=Math.floor(t),isFinite(t)&&t>0?t>1?i.filter(r?function(e){return r(e)%t==0}:function(e){return i.count(0,e)%t==0}):i:null}),i}var my=by((function(t){t.setMonth(0,1),t.setHours(0,0,0,0)}),(function(t,e){t.setFullYear(t.getFullYear()+e)}),(function(t,e){return e.getFullYear()-t.getFullYear()}),(function(t){return t.getFullYear()}));my.every=function(t){return isFinite(t=Math.floor(t))&&t>0?by((function(e){e.setFullYear(Math.floor(e.getFullYear()/t)*t),e.setMonth(0,1),e.setHours(0,0,0,0)}),(function(e,n){e.setFullYear(e.getFullYear()+n*t)})):null};var vy=my,_y=my.range,wy=by((function(t){t.setDate(1),t.setHours(0,0,0,0)}),(function(t,e){t.setMonth(t.getMonth()+e)}),(function(t,e){return e.getMonth()-t.getMonth()+12*(e.getFullYear()-t.getFullYear())}),(function(t){return t.getMonth()})),xy=wy,ky=wy.range,Ey=6e4,Ay=6048e5;function Sy(t){return by((function(e){e.setDate(e.getDate()-(e.getDay()+7-t)%7),e.setHours(0,0,0,0)}),(function(t,e){t.setDate(t.getDate()+7*e)}),(function(t,e){return(e-t-(e.getTimezoneOffset()-t.getTimezoneOffset())*Ey)/Ay}))}var Ty=Sy(0),My=Sy(1),Dy=Sy(2),Cy=Sy(3),Oy=Sy(4),Ry=Sy(5),Iy=Sy(6),Ny=Ty.range,By=My.range,Ly=Dy.range,Py=Cy.range,Fy=Oy.range,qy=Ry.range,jy=Iy.range,Uy=by((function(t){t.setHours(0,0,0,0)}),(function(t,e){t.setDate(t.getDate()+e)}),(function(t,e){return(e-t-(e.getTimezoneOffset()-t.getTimezoneOffset())*Ey)/864e5}),(function(t){return t.getDate()-1})),zy=Uy,Yy=Uy.range,Vy=by((function(t){t.setTime(t-t.getMilliseconds()-1e3*t.getSeconds()-t.getMinutes()*Ey)}),(function(t,e){t.setTime(+t+36e5*e)}),(function(t,e){return(e-t)/36e5}),(function(t){return t.getHours()})),Hy=Vy,$y=Vy.range,Gy=by((function(t){t.setTime(t-t.getMilliseconds()-1e3*t.getSeconds())}),(function(t,e){t.setTime(+t+e*Ey)}),(function(t,e){return(e-t)/Ey}),(function(t){return t.getMinutes()})),Wy=Gy,Ky=Gy.range,Xy=by((function(t){t.setTime(t-t.getMilliseconds())}),(function(t,e){t.setTime(+t+1e3*e)}),(function(t,e){return(e-t)/1e3}),(function(t){return t.getUTCSeconds()})),Zy=Xy,Jy=Xy.range,Qy=by((function(){}),(function(t,e){t.setTime(+t+e)}),(function(t,e){return e-t}));Qy.every=function(t){return t=Math.floor(t),isFinite(t)&&t>0?t>1?by((function(e){e.setTime(Math.floor(e/t)*t)}),(function(e,n){e.setTime(+e+n*t)}),(function(e,n){return(n-e)/t})):Qy:null};var tb=Qy,eb=Qy.range;function nb(t){return by((function(e){e.setUTCDate(e.getUTCDate()-(e.getUTCDay()+7-t)%7),e.setUTCHours(0,0,0,0)}),(function(t,e){t.setUTCDate(t.getUTCDate()+7*e)}),(function(t,e){return(e-t)/Ay}))}var rb=nb(0),ib=nb(1),ob=nb(2),ab=nb(3),sb=nb(4),ub=nb(5),cb=nb(6),fb=rb.range,lb=ib.range,hb=ob.range,db=ab.range,pb=sb.range,gb=ub.range,yb=cb.range,bb=by((function(t){t.setUTCHours(0,0,0,0)}),(function(t,e){t.setUTCDate(t.getUTCDate()+e)}),(function(t,e){return(e-t)/864e5}),(function(t){return t.getUTCDate()-1})),mb=bb,vb=bb.range,_b=by((function(t){t.setUTCMonth(0,1),t.setUTCHours(0,0,0,0)}),(function(t,e){t.setUTCFullYear(t.getUTCFullYear()+e)}),(function(t,e){return e.getUTCFullYear()-t.getUTCFullYear()}),(function(t){return t.getUTCFullYear()}));_b.every=function(t){return isFinite(t=Math.floor(t))&&t>0?by((function(e){e.setUTCFullYear(Math.floor(e.getUTCFullYear()/t)*t),e.setUTCMonth(0,1),e.setUTCHours(0,0,0,0)}),(function(e,n){e.setUTCFullYear(e.getUTCFullYear()+n*t)})):null};var wb=_b,xb=_b.range;function kb(t){if(0<=t.y&&t.y<100){var e=new Date(-1,t.m,t.d,t.H,t.M,t.S,t.L);return e.setFullYear(t.y),e}return new Date(t.y,t.m,t.d,t.H,t.M,t.S,t.L)}function Eb(t){if(0<=t.y&&t.y<100){var e=new Date(Date.UTC(-1,t.m,t.d,t.H,t.M,t.S,t.L));return e.setUTCFullYear(t.y),e}return new Date(Date.UTC(t.y,t.m,t.d,t.H,t.M,t.S,t.L))}function Ab(t){return{y:t,m:0,d:1,H:0,M:0,S:0,L:0}}function Sb(t){var e=t.dateTime,n=t.date,r=t.time,i=t.periods,o=t.days,a=t.shortDays,s=t.months,u=t.shortMonths,c=Fb(i),f=qb(i),l=Fb(o),h=qb(o),d=Fb(a),p=qb(a),g=Fb(s),y=qb(s),b=Fb(u),m=qb(u),v={a:function(t){return a[t.getDay()]},A:function(t){return o[t.getDay()]},b:function(t){return u[t.getMonth()]},B:function(t){return s[t.getMonth()]},c:null,d:om,e:om,f:fm,H:am,I:sm,j:um,L:cm,m:lm,M:hm,p:function(t){return i[+(t.getHours()>=12)]},Q:jm,s:Um,S:dm,u:pm,U:gm,V:ym,w:bm,W:mm,x:null,X:null,y:vm,Y:_m,Z:wm,"%":qm},_={a:function(t){return a[t.getUTCDay()]},A:function(t){return o[t.getUTCDay()]},b:function(t){return u[t.getUTCMonth()]},B:function(t){return s[t.getUTCMonth()]},c:null,d:xm,e:xm,f:Tm,H:km,I:Em,j:Am,L:Sm,m:Mm,M:Dm,p:function(t){return i[+(t.getUTCHours()>=12)]},Q:jm,s:Um,S:Cm,u:Om,U:Rm,V:Im,w:Nm,W:Bm,x:null,X:null,y:Lm,Y:Pm,Z:Fm,"%":qm},w={a:function(t,e,n){var r=d.exec(e.slice(n));return r?(t.w=p[r[0].toLowerCase()],n+r[0].length):-1},A:function(t,e,n){var r=l.exec(e.slice(n));return r?(t.w=h[r[0].toLowerCase()],n+r[0].length):-1},b:function(t,e,n){var r=b.exec(e.slice(n));return r?(t.m=m[r[0].toLowerCase()],n+r[0].length):-1},B:function(t,e,n){var r=g.exec(e.slice(n));return r?(t.m=y[r[0].toLowerCase()],n+r[0].length):-1},c:function(t,n,r){return E(t,e,n,r)},d:Kb,e:Kb,f:em,H:Zb,I:Zb,j:Xb,L:tm,m:Wb,M:Jb,p:function(t,e,n){var r=c.exec(e.slice(n));return r?(t.p=f[r[0].toLowerCase()],n+r[0].length):-1},Q:rm,s:im,S:Qb,u:Ub,U:zb,V:Yb,w:jb,W:Vb,x:function(t,e,r){return E(t,n,e,r)},X:function(t,e,n){return E(t,r,e,n)},y:$b,Y:Hb,Z:Gb,"%":nm};function x(t,e){return function(n){var r,i,o,a=[],s=-1,u=0,c=t.length;for(n instanceof Date||(n=new Date(+n));++s53)return null;"w"in o||(o.w=1),"Z"in o?(i=(r=Eb(Ab(o.y))).getUTCDay(),r=i>4||0===i?ib.ceil(r):ib(r),r=mb.offset(r,7*(o.V-1)),o.y=r.getUTCFullYear(),o.m=r.getUTCMonth(),o.d=r.getUTCDate()+(o.w+6)%7):(i=(r=e(Ab(o.y))).getDay(),r=i>4||0===i?My.ceil(r):My(r),r=zy.offset(r,7*(o.V-1)),o.y=r.getFullYear(),o.m=r.getMonth(),o.d=r.getDate()+(o.w+6)%7)}else("W"in o||"U"in o)&&("w"in o||(o.w="u"in o?o.u%7:"W"in o?1:0),i="Z"in o?Eb(Ab(o.y)).getUTCDay():e(Ab(o.y)).getDay(),o.m=0,o.d="W"in o?(o.w+6)%7+7*o.W-(i+5)%7:o.w+7*o.U-(i+6)%7);return"Z"in o?(o.H+=o.Z/100|0,o.M+=o.Z%100,Eb(o)):e(o)}}function E(t,e,n,r){for(var i,o,a=0,s=e.length,u=n.length;a=u)return-1;if(37===(i=e.charCodeAt(a++))){if(i=e.charAt(a++),!(o=w[i in Rb?e.charAt(a++):i])||(r=o(t,n,r))<0)return-1}else if(i!=n.charCodeAt(r++))return-1}return r}return(v.x=x(n,v),v.X=x(r,v),v.c=x(e,v),_.x=x(n,_),_.X=x(r,_),_.c=x(e,_),{format:function(t){var e=x(t+="",v);return e.toString=function(){return t},e},parse:function(t){var e=k(t+="",kb);return e.toString=function(){return t},e},utcFormat:function(t){var e=x(t+="",_);return e.toString=function(){return t},e},utcParse:function(t){var e=k(t,Eb);return e.toString=function(){return t},e}})}var Tb,Mb,Db,Cb,Ob,Rb={"-":"",_:" ",0:"0"},Ib=/^\s*\d+/,Nb=/^%/,Bb=/[\\^$*+?|[\]().{}]/g;function Lb(t,e,n){var r=t<0?"-":"",i=(r?-t:t)+"",o=i.length;return r+(o68?1900:2e3),n+r[0].length):-1}function Gb(t,e,n){var r=/^(Z)|([+-]\d\d)(?::?(\d\d))?/.exec(e.slice(n,n+6));return r?(t.Z=r[1]?0:-(r[2]+(r[3]||"00")),n+r[0].length):-1}function Wb(t,e,n){var r=Ib.exec(e.slice(n,n+2));return r?(t.m=r[0]-1,n+r[0].length):-1}function Kb(t,e,n){var r=Ib.exec(e.slice(n,n+2));return r?(t.d=+r[0],n+r[0].length):-1}function Xb(t,e,n){var r=Ib.exec(e.slice(n,n+3));return r?(t.m=0,t.d=+r[0],n+r[0].length):-1}function Zb(t,e,n){var r=Ib.exec(e.slice(n,n+2));return r?(t.H=+r[0],n+r[0].length):-1}function Jb(t,e,n){var r=Ib.exec(e.slice(n,n+2));return r?(t.M=+r[0],n+r[0].length):-1}function Qb(t,e,n){var r=Ib.exec(e.slice(n,n+2));return r?(t.S=+r[0],n+r[0].length):-1}function tm(t,e,n){var r=Ib.exec(e.slice(n,n+3));return r?(t.L=+r[0],n+r[0].length):-1}function em(t,e,n){var r=Ib.exec(e.slice(n,n+6));return r?(t.L=Math.floor(r[0]/1e3),n+r[0].length):-1}function nm(t,e,n){var r=Nb.exec(e.slice(n,n+1));return r?n+r[0].length:-1}function rm(t,e,n){var r=Ib.exec(e.slice(n));return r?(t.Q=+r[0],n+r[0].length):-1}function im(t,e,n){var r=Ib.exec(e.slice(n));return r?(t.Q=1e3*+r[0],n+r[0].length):-1}function om(t,e){return Lb(t.getDate(),e,2)}function am(t,e){return Lb(t.getHours(),e,2)}function sm(t,e){return Lb(t.getHours()%12||12,e,2)}function um(t,e){return Lb(1+zy.count(vy(t),t),e,3)}function cm(t,e){return Lb(t.getMilliseconds(),e,3)}function fm(t,e){return cm(t,e)+"000"}function lm(t,e){return Lb(t.getMonth()+1,e,2)}function hm(t,e){return Lb(t.getMinutes(),e,2)}function dm(t,e){return Lb(t.getSeconds(),e,2)}function pm(t){var e=t.getDay();return 0===e?7:e}function gm(t,e){return Lb(Ty.count(vy(t),t),e,2)}function ym(t,e){var n=t.getDay();return t=n>=4||0===n?Oy(t):Oy.ceil(t),Lb(Oy.count(vy(t),t)+(4===vy(t).getDay()),e,2)}function bm(t){return t.getDay()}function mm(t,e){return Lb(My.count(vy(t),t),e,2)}function vm(t,e){return Lb(t.getFullYear()%100,e,2)}function _m(t,e){return Lb(t.getFullYear()%1e4,e,4)}function wm(t){var e=t.getTimezoneOffset();return(e>0?"-":(e*=-1,"+"))+Lb(e/60|0,"0",2)+Lb(e%60,"0",2)}function xm(t,e){return Lb(t.getUTCDate(),e,2)}function km(t,e){return Lb(t.getUTCHours(),e,2)}function Em(t,e){return Lb(t.getUTCHours()%12||12,e,2)}function Am(t,e){return Lb(1+mb.count(wb(t),t),e,3)}function Sm(t,e){return Lb(t.getUTCMilliseconds(),e,3)}function Tm(t,e){return Sm(t,e)+"000"}function Mm(t,e){return Lb(t.getUTCMonth()+1,e,2)}function Dm(t,e){return Lb(t.getUTCMinutes(),e,2)}function Cm(t,e){return Lb(t.getUTCSeconds(),e,2)}function Om(t){var e=t.getUTCDay();return 0===e?7:e}function Rm(t,e){return Lb(rb.count(wb(t),t),e,2)}function Im(t,e){var n=t.getUTCDay();return t=n>=4||0===n?sb(t):sb.ceil(t),Lb(sb.count(wb(t),t)+(4===wb(t).getUTCDay()),e,2)}function Nm(t){return t.getUTCDay()}function Bm(t,e){return Lb(ib.count(wb(t),t),e,2)}function Lm(t,e){return Lb(t.getUTCFullYear()%100,e,2)}function Pm(t,e){return Lb(t.getUTCFullYear()%1e4,e,4)}function Fm(){return"+0000"}function qm(){return"%"}function jm(t){return+t}function Um(t){return Math.floor(+t/1e3)}function zm(t){return Tb=Sb(t),Mb=Tb.format,Db=Tb.parse,Cb=Tb.utcFormat,Ob=Tb.utcParse,Tb}zm({dateTime:"%x, %X",date:"%-m/%-d/%Y",time:"%-I:%M:%S %p",periods:["AM","PM"],days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]});var Ym=Date.prototype.toISOString?function(t){return t.toISOString()}:Cb("%Y-%m-%dT%H:%M:%S.%LZ");var Vm=+new Date("2000-01-01T00:00:00.000Z")?function(t){var e=new Date(t);return isNaN(e)?null:e}:Ob("%Y-%m-%dT%H:%M:%S.%LZ"),Hm=1e3,$m=60*Hm,Gm=60*$m,Wm=24*Gm,Km=7*Wm,Xm=30*Wm,Zm=365*Wm;function Jm(t){return new Date(t)}function Qm(t){return t instanceof Date?+t:+new Date(+t)}function tv(t,e,n,r,o,a,s,u,c){var f=zg(Bg,Bg),l=f.invert,h=f.domain,d=c(".%L"),p=c(":%S"),g=c("%I:%M"),y=c("%I %p"),b=c("%a %d"),m=c("%b %d"),v=c("%B"),_=c("%Y"),w=[[s,1,Hm],[s,5,5*Hm],[s,15,15*Hm],[s,30,30*Hm],[a,1,$m],[a,5,5*$m],[a,15,15*$m],[a,30,30*$m],[o,1,Gm],[o,3,3*Gm],[o,6,6*Gm],[o,12,12*Gm],[r,1,Wm],[r,2,2*Wm],[n,1,Km],[e,1,Xm],[e,3,3*Xm],[t,1,Zm]];function x(i){return(s(i)1)&&(t-=Math.floor(t));var e=Math.abs(t-.5);return z_.h=360*t-100,z_.s=1.5-1.5*e,z_.l=.8-.9*e,z_+""},V_=bn(),H_=Math.PI/3,$_=2*Math.PI/3,G_=function(t){var e;return t=(.5-t)*Math.PI,V_.r=255*(e=Math.sin(t))*e,V_.g=255*(e=Math.sin(t+H_))*e,V_.b=255*(e=Math.sin(t+$_))*e,V_+""},W_=function(t){return t=Math.max(0,Math.min(1,t)),"rgb("+Math.max(0,Math.min(255,Math.round(34.61+t*(1172.33-t*(10793.56-t*(33300.12-t*(38394.49-14825.05*t)))))))+", "+Math.max(0,Math.min(255,Math.round(23.31+t*(557.33+t*(1225.33-t*(3574.96-t*(1073.77+707.56*t)))))))+", "+Math.max(0,Math.min(255,Math.round(27.2+t*(3211.1-t*(15327.97-t*(27814-t*(22569.18-6838.66*t)))))))+")"};function K_(t){var e=t.length;return function(n){return t[Math.max(0,Math.min(e-1,Math.floor(n*e)))]}}var X_=K_(Sv("44015444025645045745055946075a46085c460a5d460b5e470d60470e6147106347116447136548146748166848176948186a481a6c481b6d481c6e481d6f481f70482071482173482374482475482576482677482878482979472a7a472c7a472d7b472e7c472f7d46307e46327e46337f463480453581453781453882443983443a83443b84433d84433e85423f854240864241864142874144874045884046883f47883f48893e49893e4a893e4c8a3d4d8a3d4e8a3c4f8a3c508b3b518b3b528b3a538b3a548c39558c39568c38588c38598c375a8c375b8d365c8d365d8d355e8d355f8d34608d34618d33628d33638d32648e32658e31668e31678e31688e30698e306a8e2f6b8e2f6c8e2e6d8e2e6e8e2e6f8e2d708e2d718e2c718e2c728e2c738e2b748e2b758e2a768e2a778e2a788e29798e297a8e297b8e287c8e287d8e277e8e277f8e27808e26818e26828e26828e25838e25848e25858e24868e24878e23888e23898e238a8d228b8d228c8d228d8d218e8d218f8d21908d21918c20928c20928c20938c1f948c1f958b1f968b1f978b1f988b1f998a1f9a8a1e9b8a1e9c891e9d891f9e891f9f881fa0881fa1881fa1871fa28720a38620a48621a58521a68522a78522a88423a98324aa8325ab8225ac8226ad8127ad8128ae8029af7f2ab07f2cb17e2db27d2eb37c2fb47c31b57b32b67a34b67935b77937b87838b9773aba763bbb753dbc743fbc7340bd7242be7144bf7046c06f48c16e4ac16d4cc26c4ec36b50c46a52c56954c56856c66758c7655ac8645cc8635ec96260ca6063cb5f65cb5e67cc5c69cd5b6ccd5a6ece5870cf5773d05675d05477d1537ad1517cd2507fd34e81d34d84d44b86d54989d5488bd6468ed64590d74393d74195d84098d83e9bd93c9dd93ba0da39a2da37a5db36a8db34aadc32addc30b0dd2fb2dd2db5de2bb8de29bade28bddf26c0df25c2df23c5e021c8e020cae11fcde11dd0e11cd2e21bd5e21ad8e219dae319dde318dfe318e2e418e5e419e7e419eae51aece51befe51cf1e51df4e61ef6e620f8e621fbe723fde725")),Z_=K_(Sv("00000401000501010601010802010902020b02020d03030f03031204041405041606051806051a07061c08071e0907200a08220b09240c09260d0a290e0b2b100b2d110c2f120d31130d34140e36150e38160f3b180f3d19103f1a10421c10441d11471e114920114b21114e22115024125325125527125829115a2a115c2c115f2d11612f116331116533106734106936106b38106c390f6e3b0f703d0f713f0f72400f74420f75440f764510774710784910784a10794c117a4e117b4f127b51127c52137c54137d56147d57157e59157e5a167e5c167f5d177f5f187f601880621980641a80651a80671b80681c816a1c816b1d816d1d816e1e81701f81721f817320817521817621817822817922827b23827c23827e24828025828125818326818426818627818827818928818b29818c29818e2a81902a81912b81932b80942c80962c80982d80992d809b2e7f9c2e7f9e2f7fa02f7fa1307ea3307ea5317ea6317da8327daa337dab337cad347cae347bb0357bb2357bb3367ab5367ab73779b83779ba3878bc3978bd3977bf3a77c03a76c23b75c43c75c53c74c73d73c83e73ca3e72cc3f71cd4071cf4070d0416fd2426fd3436ed5446dd6456cd8456cd9466bdb476adc4869de4968df4a68e04c67e24d66e34e65e44f64e55064e75263e85362e95462ea5661eb5760ec5860ed5a5fee5b5eef5d5ef05f5ef1605df2625df2645cf3655cf4675cf4695cf56b5cf66c5cf66e5cf7705cf7725cf8745cf8765cf9785df9795df97b5dfa7d5efa7f5efa815ffb835ffb8560fb8761fc8961fc8a62fc8c63fc8e64fc9065fd9266fd9467fd9668fd9869fd9a6afd9b6bfe9d6cfe9f6dfea16efea36ffea571fea772fea973feaa74feac76feae77feb078feb27afeb47bfeb67cfeb77efeb97ffebb81febd82febf84fec185fec287fec488fec68afec88cfeca8dfecc8ffecd90fecf92fed194fed395fed597fed799fed89afdda9cfddc9efddea0fde0a1fde2a3fde3a5fde5a7fde7a9fde9aafdebacfcecaefceeb0fcf0b2fcf2b4fcf4b6fcf6b8fcf7b9fcf9bbfcfbbdfcfdbf")),J_=K_(Sv("00000401000501010601010802010a02020c02020e03021004031204031405041706041907051b08051d09061f0a07220b07240c08260d08290e092b10092d110a30120a32140b34150b37160b39180c3c190c3e1b0c411c0c431e0c451f0c48210c4a230c4c240c4f260c51280b53290b552b0b572d0b592f0a5b310a5c320a5e340a5f3609613809623909633b09643d09653e0966400a67420a68440a68450a69470b6a490b6a4a0c6b4c0c6b4d0d6c4f0d6c510e6c520e6d540f6d550f6d57106e59106e5a116e5c126e5d126e5f136e61136e62146e64156e65156e67166e69166e6a176e6c186e6d186e6f196e71196e721a6e741a6e751b6e771c6d781c6d7a1d6d7c1d6d7d1e6d7f1e6c801f6c82206c84206b85216b87216b88226a8a226a8c23698d23698f24699025689225689326679526679727669827669a28659b29649d29649f2a63a02a63a22b62a32c61a52c60a62d60a82e5fa92e5eab2f5ead305dae305cb0315bb1325ab3325ab43359b63458b73557b93556ba3655bc3754bd3853bf3952c03a51c13a50c33b4fc43c4ec63d4dc73e4cc83f4bca404acb4149cc4248ce4347cf4446d04545d24644d34743d44842d54a41d74b3fd84c3ed94d3dda4e3cdb503bdd513ade5238df5337e05536e15635e25734e35933e45a31e55c30e65d2fe75e2ee8602de9612bea632aeb6429eb6628ec6726ed6925ee6a24ef6c23ef6e21f06f20f1711ff1731df2741cf3761bf37819f47918f57b17f57d15f67e14f68013f78212f78410f8850ff8870ef8890cf98b0bf98c0af98e09fa9008fa9207fa9407fb9606fb9706fb9906fb9b06fb9d07fc9f07fca108fca309fca50afca60cfca80dfcaa0ffcac11fcae12fcb014fcb216fcb418fbb61afbb81dfbba1ffbbc21fbbe23fac026fac228fac42afac62df9c72ff9c932f9cb35f8cd37f8cf3af7d13df7d340f6d543f6d746f5d949f5db4cf4dd4ff4df53f4e156f3e35af3e55df2e661f2e865f2ea69f1ec6df1ed71f1ef75f1f179f2f27df2f482f3f586f3f68af4f88ef5f992f6fa96f8fb9af9fc9dfafda1fcffa4")),Q_=K_(Sv("0d088710078813078916078a19068c1b068d1d068e20068f2206902406912605912805922a05932c05942e05952f059631059733059735049837049938049a3a049a3c049b3e049c3f049c41049d43039e44039e46039f48039f4903a04b03a14c02a14e02a25002a25102a35302a35502a45601a45801a45901a55b01a55c01a65e01a66001a66100a76300a76400a76600a76700a86900a86a00a86c00a86e00a86f00a87100a87201a87401a87501a87701a87801a87a02a87b02a87d03a87e03a88004a88104a78305a78405a78606a68707a68808a68a09a58b0aa58d0ba58e0ca48f0da4910ea3920fa39410a29511a19613a19814a099159f9a169f9c179e9d189d9e199da01a9ca11b9ba21d9aa31e9aa51f99a62098a72197a82296aa2395ab2494ac2694ad2793ae2892b02991b12a90b22b8fb32c8eb42e8db52f8cb6308bb7318ab83289ba3388bb3488bc3587bd3786be3885bf3984c03a83c13b82c23c81c33d80c43e7fc5407ec6417dc7427cc8437bc9447aca457acb4679cc4778cc4977cd4a76ce4b75cf4c74d04d73d14e72d24f71d35171d45270d5536fd5546ed6556dd7566cd8576bd9586ada5a6ada5b69db5c68dc5d67dd5e66de5f65de6164df6263e06363e16462e26561e26660e3685fe4695ee56a5de56b5de66c5ce76e5be76f5ae87059e97158e97257ea7457eb7556eb7655ec7754ed7953ed7a52ee7b51ef7c51ef7e50f07f4ff0804ef1814df1834cf2844bf3854bf3874af48849f48948f58b47f58c46f68d45f68f44f79044f79143f79342f89441f89540f9973ff9983ef99a3efa9b3dfa9c3cfa9e3bfb9f3afba139fba238fca338fca537fca636fca835fca934fdab33fdac33fdae32fdaf31fdb130fdb22ffdb42ffdb52efeb72dfeb82cfeba2cfebb2bfebd2afebe2afec029fdc229fdc328fdc527fdc627fdc827fdca26fdcb26fccd25fcce25fcd025fcd225fbd324fbd524fbd724fad824fada24f9dc24f9dd25f8df25f8e125f7e225f7e425f6e626f6e826f5e926f5eb27f4ed27f3ee27f3f027f2f227f1f426f1f525f0f724f0f921")),tw=function(t){return function(){return t}},ew=Math.abs,nw=Math.atan2,rw=Math.cos,iw=Math.max,ow=Math.min,aw=Math.sin,sw=Math.sqrt,uw=1e-12,cw=Math.PI,fw=cw/2,lw=2*cw;function hw(t){return t>=1?fw:t<=-1?-fw:Math.asin(t)}function dw(t){return t.innerRadius}function pw(t){return t.outerRadius}function gw(t){return t.startAngle}function yw(t){return t.endAngle}function bw(t){return t&&t.padAngle}function mw(t,e,n,r,i,o,a){var s=t-n,u=e-r,c=(a?o:-o)/sw(s*s+u*u),f=c*u,l=-c*s,h=t+f,d=e+l,p=n+f,g=r+l,y=(h+p)/2,b=(d+g)/2,m=p-h,v=g-d,_=m*m+v*v,w=i-o,x=h*g-p*d,k=(v<0?-1:1)*sw(iw(0,w*w*_-x*x)),E=(x*v-m*k)/_,A=(-x*m-v*k)/_,S=(x*v+m*k)/_,T=(-x*m+v*k)/_,M=E-y,D=A-b,C=S-y,O=T-b;return M*M+D*D>C*C+O*O&&(E=S,A=T),{cx:E,cy:A,x01:-f,y01:-l,x11:E*(i/w-1),y11:A*(i/w-1)}}var vw=function(){var t=dw,e=pw,n=tw(0),r=null,i=gw,o=yw,a=bw,s=null;function u(){var u,c,f,l=+t.apply(this,arguments),h=+e.apply(this,arguments),d=i.apply(this,arguments)-fw,p=o.apply(this,arguments)-fw,g=ew(p-d),y=p>d;if(s||(s=u=qa()),huw)if(g>lw-uw)s.moveTo(h*rw(d),h*aw(d)),s.arc(0,0,h,d,p,!y),l>uw&&(s.moveTo(l*rw(p),l*aw(p)),s.arc(0,0,l,p,d,y));else{var b,m,v=d,_=p,w=d,x=p,k=g,E=g,A=a.apply(this,arguments)/2,S=A>uw&&(r?+r.apply(this,arguments):sw(l*l+h*h)),T=ow(ew(h-l)/2,+n.apply(this,arguments)),M=T,D=T;if(S>uw){var C=hw(S/l*aw(A)),O=hw(S/h*aw(A));(k-=2*C)>uw?(w+=C*=y?1:-1,x-=C):(k=0,w=x=(d+p)/2),(E-=2*O)>uw?(v+=O*=y?1:-1,_-=O):(E=0,v=_=(d+p)/2)}var R=h*rw(v),I=h*aw(v),N=l*rw(x),B=l*aw(x);if(T>uw){var L,P=h*rw(_),F=h*aw(_),q=l*rw(w),j=l*aw(w);if(g1?0:f<-1?cw:Math.acos(f))/2),$=sw(L[0]*L[0]+L[1]*L[1]);M=ow(T,(l-$)/(H-1)),D=ow(T,(h-$)/(H+1))}}E>uw?D>uw?(b=mw(q,j,R,I,h,D,y),m=mw(P,F,N,B,h,D,y),s.moveTo(b.cx+b.x01,b.cy+b.y01),Duw&&k>uw?M>uw?(b=mw(N,B,P,F,l,-M,y),m=mw(R,I,q,j,l,-M,y),s.lineTo(b.cx+b.x01,b.cy+b.y01),M=f;--l)s.point(y[l],b[l]);s.lineEnd(),s.areaEnd()}g&&(y[c]=+t(h,c,u),b[c]=+n(h,c,u),s.point(e?+e(h,c,u):y[c],r?+r(h,c,u):b[c]))}if(d)return s=null,d+""||null}function c(){return Ew().defined(i).curve(a).context(o)}return u.x=function(n){return arguments.length?(t="function"==typeof n?n:tw(+n),e=null,u):t},u.x0=function(e){return arguments.length?(t="function"==typeof e?e:tw(+e),u):t},u.x1=function(t){return arguments.length?(e=null==t?null:"function"==typeof t?t:tw(+t),u):e},u.y=function(t){return arguments.length?(n="function"==typeof t?t:tw(+t),r=null,u):n},u.y0=function(t){return arguments.length?(n="function"==typeof t?t:tw(+t),u):n},u.y1=function(t){return arguments.length?(r=null==t?null:"function"==typeof t?t:tw(+t),u):r},u.lineX0=u.lineY0=function(){return c().x(t).y(n)},u.lineY1=function(){return c().x(t).y(r)},u.lineX1=function(){return c().x(e).y(n)},u.defined=function(t){return arguments.length?(i="function"==typeof t?t:tw(!!t),u):i},u.curve=function(t){return arguments.length?(a=t,null!=o&&(s=a(o)),u):a},u.context=function(t){return arguments.length?(null==t?o=s=null:s=a(o=t),u):o},u},Sw=function(t,e){return et?1:e>=t?0:NaN},Tw=function(t){return t},Mw=function(){var t=Tw,e=Sw,n=null,r=tw(0),i=tw(lw),o=tw(0);function a(a){var s,u,c,f,l,h=a.length,d=0,p=new Array(h),g=new Array(h),y=+r.apply(this,arguments),b=Math.min(lw,Math.max(-lw,i.apply(this,arguments)-y)),m=Math.min(Math.abs(b)/h,o.apply(this,arguments)),v=m*(b<0?-1:1);for(s=0;s0&&(d+=l);for(null!=e?p.sort((function(t,n){return e(g[t],g[n])})):null!=n&&p.sort((function(t,e){return n(a[t],a[e])})),s=0,c=d?(b-h*v)/d:0;s0?l*c:0)+v,g[u]={data:a[u],index:s,value:l,startAngle:y,endAngle:f,padAngle:m};return g}return a.value=function(e){return arguments.length?(t="function"==typeof e?e:tw(+e),a):t},a.sortValues=function(t){return arguments.length?(e=t,n=null,a):e},a.sort=function(t){return arguments.length?(n=t,e=null,a):n},a.startAngle=function(t){return arguments.length?(r="function"==typeof t?t:tw(+t),a):r},a.endAngle=function(t){return arguments.length?(i="function"==typeof t?t:tw(+t),a):i},a.padAngle=function(t){return arguments.length?(o="function"==typeof t?t:tw(+t),a):o},a},Dw=Ow(ww);function Cw(t){this._curve=t}function Ow(t){function e(e){return new Cw(t(e))}return e._curve=t,e}function Rw(t){var e=t.curve;return t.angle=t.x,delete t.x,t.radius=t.y,delete t.y,t.curve=function(t){return arguments.length?e(Ow(t)):e()._curve},t}Cw.prototype={areaStart:function(){this._curve.areaStart()},areaEnd:function(){this._curve.areaEnd()},lineStart:function(){this._curve.lineStart()},lineEnd:function(){this._curve.lineEnd()},point:function(t,e){this._curve.point(e*Math.sin(t),e*-Math.cos(t))}};var Iw=function(){return Rw(Ew().curve(Dw))},Nw=function(){var t=Aw().curve(Dw),e=t.curve,n=t.lineX0,r=t.lineX1,i=t.lineY0,o=t.lineY1;return t.angle=t.x,delete t.x,t.startAngle=t.x0,delete t.x0,t.endAngle=t.x1,delete t.x1,t.radius=t.y,delete t.y,t.innerRadius=t.y0,delete t.y0,t.outerRadius=t.y1,delete t.y1,t.lineStartAngle=function(){return Rw(n())},delete t.lineX0,t.lineEndAngle=function(){return Rw(r())},delete t.lineX1,t.lineInnerRadius=function(){return Rw(i())},delete t.lineY0,t.lineOuterRadius=function(){return Rw(o())},delete t.lineY1,t.curve=function(t){return arguments.length?e(Ow(t)):e()._curve},t},Bw=function(t,e){return[(e=+e)*Math.cos(t-=Math.PI/2),e*Math.sin(t)]},Lw=Array.prototype.slice;function Pw(t){return t.source}function Fw(t){return t.target}function qw(t){var e=Pw,n=Fw,r=xw,i=kw,o=null;function a(){var a,s=Lw.call(arguments),u=e.apply(this,s),c=n.apply(this,s);if(o||(o=a=qa()),t(o,+r.apply(this,(s[0]=u,s)),+i.apply(this,s),+r.apply(this,(s[0]=c,s)),+i.apply(this,s)),a)return o=null,a+""||null}return a.source=function(t){return arguments.length?(e=t,a):e},a.target=function(t){return arguments.length?(n=t,a):n},a.x=function(t){return arguments.length?(r="function"==typeof t?t:tw(+t),a):r},a.y=function(t){return arguments.length?(i="function"==typeof t?t:tw(+t),a):i},a.context=function(t){return arguments.length?(o=null==t?null:t,a):o},a}function jw(t,e,n,r,i){t.moveTo(e,n),t.bezierCurveTo(e=(e+r)/2,n,e,i,r,i)}function Uw(t,e,n,r,i){t.moveTo(e,n),t.bezierCurveTo(e,n=(n+i)/2,r,n,r,i)}function zw(t,e,n,r,i){var o=Bw(e,n),a=Bw(e,n=(n+i)/2),s=Bw(r,n),u=Bw(r,i);t.moveTo(o[0],o[1]),t.bezierCurveTo(a[0],a[1],s[0],s[1],u[0],u[1])}function Yw(){return qw(jw)}function Vw(){return qw(Uw)}function Hw(){var t=qw(zw);return t.angle=t.x,delete t.x,t.radius=t.y,delete t.y,t}var $w={draw:function(t,e){var n=Math.sqrt(e/cw);t.moveTo(n,0),t.arc(0,0,n,0,lw)}},Gw={draw:function(t,e){var n=Math.sqrt(e/5)/2;t.moveTo(-3*n,-n),t.lineTo(-n,-n),t.lineTo(-n,-3*n),t.lineTo(n,-3*n),t.lineTo(n,-n),t.lineTo(3*n,-n),t.lineTo(3*n,n),t.lineTo(n,n),t.lineTo(n,3*n),t.lineTo(-n,3*n),t.lineTo(-n,n),t.lineTo(-3*n,n),t.closePath()}},Ww=Math.sqrt(1/3),Kw=2*Ww,Xw={draw:function(t,e){var n=Math.sqrt(e/Kw),r=n*Ww;t.moveTo(0,-n),t.lineTo(r,0),t.lineTo(0,n),t.lineTo(-r,0),t.closePath()}},Zw=Math.sin(cw/10)/Math.sin(7*cw/10),Jw=Math.sin(lw/10)*Zw,Qw=-Math.cos(lw/10)*Zw,tx={draw:function(t,e){var n=Math.sqrt(.8908130915292852*e),r=Jw*n,i=Qw*n;t.moveTo(0,-n),t.lineTo(r,i);for(var o=1;o<5;++o){var a=lw*o/5,s=Math.cos(a),u=Math.sin(a);t.lineTo(u*n,-s*n),t.lineTo(s*r-u*i,u*r+s*i)}t.closePath()}},ex={draw:function(t,e){var n=Math.sqrt(e),r=-n/2;t.rect(r,r,n,n)}},nx=Math.sqrt(3),rx={draw:function(t,e){var n=-Math.sqrt(e/(3*nx));t.moveTo(0,2*n),t.lineTo(-nx*n,-n),t.lineTo(nx*n,-n),t.closePath()}},ix=Math.sqrt(3)/2,ox=1/Math.sqrt(12),ax=3*(ox/2+1),sx={draw:function(t,e){var n=Math.sqrt(e/ax),r=n/2,i=n*ox,o=r,a=n*ox+n,s=-o,u=a;t.moveTo(r,i),t.lineTo(o,a),t.lineTo(s,u),t.lineTo(-.5*r-ix*i,ix*r+-.5*i),t.lineTo(-.5*o-ix*a,ix*o+-.5*a),t.lineTo(-.5*s-ix*u,ix*s+-.5*u),t.lineTo(-.5*r+ix*i,-.5*i-ix*r),t.lineTo(-.5*o+ix*a,-.5*a-ix*o),t.lineTo(-.5*s+ix*u,-.5*u-ix*s),t.closePath()}},ux=[$w,Gw,Xw,ex,tx,rx,sx],cx=function(){var t=tw($w),e=tw(64),n=null;function r(){var r;if(n||(n=r=qa()),t.apply(this,arguments).draw(n,+e.apply(this,arguments)),r)return n=null,r+""||null}return r.type=function(e){return arguments.length?(t="function"==typeof e?e:tw(e),r):t},r.size=function(t){return arguments.length?(e="function"==typeof t?t:tw(+t),r):e},r.context=function(t){return arguments.length?(n=null==t?null:t,r):n},r},fx=function(){};function lx(t,e,n){t._context.bezierCurveTo((2*t._x0+t._x1)/3,(2*t._y0+t._y1)/3,(t._x0+2*t._x1)/3,(t._y0+2*t._y1)/3,(t._x0+4*t._x1+e)/6,(t._y0+4*t._y1+n)/6)}function hx(t){this._context=t}hx.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=NaN,this._point=0},lineEnd:function(){switch(this._point){case 3:lx(this,this._x1,this._y1);case 2:this._context.lineTo(this._x1,this._y1)}(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,e):this._context.moveTo(t,e);break;case 1:this._point=2;break;case 2:this._point=3,this._context.lineTo((5*this._x0+this._x1)/6,(5*this._y0+this._y1)/6);default:lx(this,t,e)}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=e}};var dx=function(t){return new hx(t)};function px(t){this._context=t}px.prototype={areaStart:fx,areaEnd:fx,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._y0=this._y1=this._y2=this._y3=this._y4=NaN,this._point=0},lineEnd:function(){switch(this._point){case 1:this._context.moveTo(this._x2,this._y2),this._context.closePath();break;case 2:this._context.moveTo((this._x2+2*this._x3)/3,(this._y2+2*this._y3)/3),this._context.lineTo((this._x3+2*this._x2)/3,(this._y3+2*this._y2)/3),this._context.closePath();break;case 3:this.point(this._x2,this._y2),this.point(this._x3,this._y3),this.point(this._x4,this._y4)}},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1,this._x2=t,this._y2=e;break;case 1:this._point=2,this._x3=t,this._y3=e;break;case 2:this._point=3,this._x4=t,this._y4=e,this._context.moveTo((this._x0+4*this._x1+t)/6,(this._y0+4*this._y1+e)/6);break;default:lx(this,t,e)}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=e}};var gx=function(t){return new px(t)};function yx(t){this._context=t}yx.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=NaN,this._point=0},lineEnd:function(){(this._line||0!==this._line&&3===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3;var n=(this._x0+4*this._x1+t)/6,r=(this._y0+4*this._y1+e)/6;this._line?this._context.lineTo(n,r):this._context.moveTo(n,r);break;case 3:this._point=4;default:lx(this,t,e)}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=e}};var bx=function(t){return new yx(t)};function mx(t,e){this._basis=new hx(t),this._beta=e}mx.prototype={lineStart:function(){this._x=[],this._y=[],this._basis.lineStart()},lineEnd:function(){var t=this._x,e=this._y,n=t.length-1;if(n>0)for(var r,i=t[0],o=e[0],a=t[n]-i,s=e[n]-o,u=-1;++u<=n;)r=u/n,this._basis.point(this._beta*t[u]+(1-this._beta)*(i+r*a),this._beta*e[u]+(1-this._beta)*(o+r*s));this._x=this._y=null,this._basis.lineEnd()},point:function(t,e){this._x.push(+t),this._y.push(+e)}};var vx=function t(e){function n(t){return 1===e?new hx(t):new mx(t,e)}return n.beta=function(e){return t(+e)},n}(.85);function _x(t,e,n){t._context.bezierCurveTo(t._x1+t._k*(t._x2-t._x0),t._y1+t._k*(t._y2-t._y0),t._x2+t._k*(t._x1-e),t._y2+t._k*(t._y1-n),t._x2,t._y2)}function wx(t,e){this._context=t,this._k=(1-e)/6}wx.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._point=0},lineEnd:function(){switch(this._point){case 2:this._context.lineTo(this._x2,this._y2);break;case 3:_x(this,this._x1,this._y1)}(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,e):this._context.moveTo(t,e);break;case 1:this._point=2,this._x1=t,this._y1=e;break;case 2:this._point=3;default:_x(this,t,e)}this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=e}};var xx=function t(e){function n(t){return new wx(t,e)}return n.tension=function(e){return t(+e)},n}(0);function kx(t,e){this._context=t,this._k=(1-e)/6}kx.prototype={areaStart:fx,areaEnd:fx,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._x5=this._y0=this._y1=this._y2=this._y3=this._y4=this._y5=NaN,this._point=0},lineEnd:function(){switch(this._point){case 1:this._context.moveTo(this._x3,this._y3),this._context.closePath();break;case 2:this._context.lineTo(this._x3,this._y3),this._context.closePath();break;case 3:this.point(this._x3,this._y3),this.point(this._x4,this._y4),this.point(this._x5,this._y5)}},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1,this._x3=t,this._y3=e;break;case 1:this._point=2,this._context.moveTo(this._x4=t,this._y4=e);break;case 2:this._point=3,this._x5=t,this._y5=e;break;default:_x(this,t,e)}this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=e}};var Ex=function t(e){function n(t){return new kx(t,e)}return n.tension=function(e){return t(+e)},n}(0);function Ax(t,e){this._context=t,this._k=(1-e)/6}Ax.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._point=0},lineEnd:function(){(this._line||0!==this._line&&3===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3,this._line?this._context.lineTo(this._x2,this._y2):this._context.moveTo(this._x2,this._y2);break;case 3:this._point=4;default:_x(this,t,e)}this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=e}};var Sx=function t(e){function n(t){return new Ax(t,e)}return n.tension=function(e){return t(+e)},n}(0);function Tx(t,e,n){var r=t._x1,i=t._y1,o=t._x2,a=t._y2;if(t._l01_a>uw){var s=2*t._l01_2a+3*t._l01_a*t._l12_a+t._l12_2a,u=3*t._l01_a*(t._l01_a+t._l12_a);r=(r*s-t._x0*t._l12_2a+t._x2*t._l01_2a)/u,i=(i*s-t._y0*t._l12_2a+t._y2*t._l01_2a)/u}if(t._l23_a>uw){var c=2*t._l23_2a+3*t._l23_a*t._l12_a+t._l12_2a,f=3*t._l23_a*(t._l23_a+t._l12_a);o=(o*c+t._x1*t._l23_2a-e*t._l12_2a)/f,a=(a*c+t._y1*t._l23_2a-n*t._l12_2a)/f}t._context.bezierCurveTo(r,i,o,a,t._x2,t._y2)}function Mx(t,e){this._context=t,this._alpha=e}Mx.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._l01_a=this._l12_a=this._l23_a=this._l01_2a=this._l12_2a=this._l23_2a=this._point=0},lineEnd:function(){switch(this._point){case 2:this._context.lineTo(this._x2,this._y2);break;case 3:this.point(this._x2,this._y2)}(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,e){if(t=+t,e=+e,this._point){var n=this._x2-t,r=this._y2-e;this._l23_a=Math.sqrt(this._l23_2a=Math.pow(n*n+r*r,this._alpha))}switch(this._point){case 0:this._point=1,this._line?this._context.lineTo(t,e):this._context.moveTo(t,e);break;case 1:this._point=2;break;case 2:this._point=3;default:Tx(this,t,e)}this._l01_a=this._l12_a,this._l12_a=this._l23_a,this._l01_2a=this._l12_2a,this._l12_2a=this._l23_2a,this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=e}};var Dx=function t(e){function n(t){return e?new Mx(t,e):new wx(t,0)}return n.alpha=function(e){return t(+e)},n}(.5);function Cx(t,e){this._context=t,this._alpha=e}Cx.prototype={areaStart:fx,areaEnd:fx,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._x5=this._y0=this._y1=this._y2=this._y3=this._y4=this._y5=NaN,this._l01_a=this._l12_a=this._l23_a=this._l01_2a=this._l12_2a=this._l23_2a=this._point=0},lineEnd:function(){switch(this._point){case 1:this._context.moveTo(this._x3,this._y3),this._context.closePath();break;case 2:this._context.lineTo(this._x3,this._y3),this._context.closePath();break;case 3:this.point(this._x3,this._y3),this.point(this._x4,this._y4),this.point(this._x5,this._y5)}},point:function(t,e){if(t=+t,e=+e,this._point){var n=this._x2-t,r=this._y2-e;this._l23_a=Math.sqrt(this._l23_2a=Math.pow(n*n+r*r,this._alpha))}switch(this._point){case 0:this._point=1,this._x3=t,this._y3=e;break;case 1:this._point=2,this._context.moveTo(this._x4=t,this._y4=e);break;case 2:this._point=3,this._x5=t,this._y5=e;break;default:Tx(this,t,e)}this._l01_a=this._l12_a,this._l12_a=this._l23_a,this._l01_2a=this._l12_2a,this._l12_2a=this._l23_2a,this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=e}};var Ox=function t(e){function n(t){return e?new Cx(t,e):new kx(t,0)}return n.alpha=function(e){return t(+e)},n}(.5);function Rx(t,e){this._context=t,this._alpha=e}Rx.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._l01_a=this._l12_a=this._l23_a=this._l01_2a=this._l12_2a=this._l23_2a=this._point=0},lineEnd:function(){(this._line||0!==this._line&&3===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,e){if(t=+t,e=+e,this._point){var n=this._x2-t,r=this._y2-e;this._l23_a=Math.sqrt(this._l23_2a=Math.pow(n*n+r*r,this._alpha))}switch(this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3,this._line?this._context.lineTo(this._x2,this._y2):this._context.moveTo(this._x2,this._y2);break;case 3:this._point=4;default:Tx(this,t,e)}this._l01_a=this._l12_a,this._l12_a=this._l23_a,this._l01_2a=this._l12_2a,this._l12_2a=this._l23_2a,this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=e}};var Ix=function t(e){function n(t){return e?new Rx(t,e):new Ax(t,0)}return n.alpha=function(e){return t(+e)},n}(.5);function Nx(t){this._context=t}Nx.prototype={areaStart:fx,areaEnd:fx,lineStart:function(){this._point=0},lineEnd:function(){this._point&&this._context.closePath()},point:function(t,e){t=+t,e=+e,this._point?this._context.lineTo(t,e):(this._point=1,this._context.moveTo(t,e))}};var Bx=function(t){return new Nx(t)};function Lx(t){return t<0?-1:1}function Px(t,e,n){var r=t._x1-t._x0,i=e-t._x1,o=(t._y1-t._y0)/(r||i<0&&-0),a=(n-t._y1)/(i||r<0&&-0),s=(o*i+a*r)/(r+i);return(Lx(o)+Lx(a))*Math.min(Math.abs(o),Math.abs(a),.5*Math.abs(s))||0}function Fx(t,e){var n=t._x1-t._x0;return n?(3*(t._y1-t._y0)/n-e)/2:e}function qx(t,e,n){var r=t._x0,i=t._y0,o=t._x1,a=t._y1,s=(o-r)/3;t._context.bezierCurveTo(r+s,i+s*e,o-s,a-s*n,o,a)}function jx(t){this._context=t}function Ux(t){this._context=new zx(t)}function zx(t){this._context=t}function Yx(t){return new jx(t)}function Vx(t){return new Ux(t)}function Hx(t){this._context=t}function $x(t){var e,n,r=t.length-1,i=new Array(r),o=new Array(r),a=new Array(r);for(i[0]=0,o[0]=2,a[0]=t[0]+2*t[1],e=1;e=0;--e)i[e]=(a[e]-i[e+1])/o[e];for(o[r-1]=(t[r]+i[r-1])/2,e=0;e=0&&(this._t=1-this._t,this._line=1-this._line)},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,e):this._context.moveTo(t,e);break;case 1:this._point=2;default:if(this._t<=0)this._context.lineTo(this._x,e),this._context.lineTo(t,e);else{var n=this._x*(1-this._t)+t*this._t;this._context.lineTo(n,this._y),this._context.lineTo(n,e)}}this._x=t,this._y=e}};var Kx=function(t){return new Wx(t,.5)};function Xx(t){return new Wx(t,0)}function Zx(t){return new Wx(t,1)}var Jx=function(t,e){if((i=t.length)>1)for(var n,r,i,o=1,a=t[e[0]],s=a.length;o=0;)n[e]=e;return n};function tk(t,e){return t[e]}var ek=function(){var t=tw([]),e=Qx,n=Jx,r=tk;function i(i){var o,a,s=t.apply(this,arguments),u=i.length,c=s.length,f=new Array(c);for(o=0;o0){for(var n,r,i,o=0,a=t[0].length;o0)for(var n,r,i,o,a,s,u=0,c=t[e[0]].length;u=0?(r[0]=o,r[1]=o+=i):i<0?(r[1]=a,r[0]=a+=i):r[0]=o},ik=function(t,e){if((n=t.length)>0){for(var n,r=0,i=t[e[0]],o=i.length;r0&&(r=(n=t[e[0]]).length)>0){for(var n,r,i,o=0,a=1;ao&&(o=e,r=n);return r}var uk=function(t){var e=t.map(ck);return Qx(t).sort((function(t,n){return e[t]-e[n]}))};function ck(t){for(var e,n=0,r=-1,i=t.length;++r0)){if(o/=h,h<0){if(o0){if(o>l)return;o>f&&(f=o)}if(o=r-u,h||!(o<0)){if(o/=h,h<0){if(o>l)return;o>f&&(f=o)}else if(h>0){if(o0)){if(o/=d,d<0){if(o0){if(o>l)return;o>f&&(f=o)}if(o=i-c,d||!(o<0)){if(o/=d,d<0){if(o>l)return;o>f&&(f=o)}else if(d>0){if(o0||l<1)||(f>0&&(t[0]=[u+f*h,c+f*d]),l<1&&(t[1]=[u+l*h,c+l*d]),!0)}}}}}function Sk(t,e,n,r,i){var o=t[1];if(o)return!0;var a,s,u=t[0],c=t.left,f=t.right,l=c[0],h=c[1],d=f[0],p=f[1],g=(l+d)/2,y=(h+p)/2;if(p===h){if(g=r)return;if(l>d){if(u){if(u[1]>=i)return}else u=[g,n];o=[g,i]}else{if(u){if(u[1]1)if(l>d){if(u){if(u[1]>=i)return}else u=[(n-s)/a,n];o=[(i-s)/a,i]}else{if(u){if(u[1]=r)return}else u=[e,a*e+s];o=[r,a*r+s]}else{if(u){if(u[0]=-Wk)){var d=u*u+c*c,p=f*f+l*l,g=(l*d-c*p)/h,y=(u*p-f*d)/h,b=Ok.pop()||new Rk;b.arc=t,b.site=i,b.x=g+a,b.y=(b.cy=y+s)+Math.sqrt(g*g+y*y),t.circle=b;for(var m=null,v=Hk._;v;)if(b.yGk)s=s.L;else{if(!((i=o-zk(s,a))>Gk)){r>-Gk?(e=s.P,n=s):i>-Gk?(e=s,n=s.N):e=n=s;break}if(!s.R){e=s;break}s=s.R}!function(t){Vk[t.index]={site:t,halfedges:[]}}(t);var u=Pk(t);if(Yk.insert(e,u),e||n){if(e===n)return Nk(e),n=Pk(e.site),Yk.insert(u,n),u.edge=n.edge=xk(e.site,u.site),Ik(e),void Ik(n);if(n){Nk(e),Nk(n);var c=e.site,f=c[0],l=c[1],h=t[0]-f,d=t[1]-l,p=n.site,g=p[0]-f,y=p[1]-l,b=2*(h*y-d*g),m=h*h+d*d,v=g*g+y*y,_=[(y*m-d*v)/b+f,(h*v-g*m)/b+l];Ek(n.edge,c,p,_),u.edge=xk(c,t,null,_),n.edge=xk(t,p,null,_),Ik(e),Ik(n)}else u.edge=xk(e.site,u.site)}}function Uk(t,e){var n=t.site,r=n[0],i=n[1],o=i-e;if(!o)return r;var a=t.P;if(!a)return-1/0;var s=(n=a.site)[0],u=n[1],c=u-e;if(!c)return s;var f=s-r,l=1/o-1/c,h=f/c;return l?(-h+Math.sqrt(h*h-2*l*(f*f/(-2*c)-u+c/2+i-o/2)))/l+r:(r+s)/2}function zk(t,e){var n=t.N;if(n)return Uk(n,e);var r=t.site;return r[1]===e?r[0]:1/0}var Yk,Vk,Hk,$k,Gk=1e-6,Wk=1e-12;function Kk(t,e){return e[1]-t[1]||e[0]-t[0]}function Xk(t,e){var n,r,i,o=t.sort(Kk).pop();for($k=[],Vk=new Array(t.length),Yk=new wk,Hk=new wk;;)if(i=Ck,o&&(!i||o[1]Gk||Math.abs(i[0][1]-i[1][1])>Gk)||delete $k[o]}(a,s,u,c),function(t,e,n,r){var i,o,a,s,u,c,f,l,h,d,p,g,y=Vk.length,b=!0;for(i=0;iGk||Math.abs(g-h)>Gk)&&(u.splice(s,0,$k.push(kk(a,d,Math.abs(p-t)Gk?[t,Math.abs(l-t)Gk?[Math.abs(h-r)Gk?[n,Math.abs(l-n)Gk?[Math.abs(h-e)=s)return null;var u=t-i.site[0],c=e-i.site[1],f=u*u+c*c;do{i=o.cells[r=a],a=null,i.halfedges.forEach((function(n){var r=o.edges[n],s=r.left;if(s!==i.site&&s||(s=r.right)){var u=t-s[0],c=e-s[1],l=u*u+c*c;lr?(r+i)/2:Math.min(0,r)||Math.max(0,i),a>o?(o+a)/2:Math.min(0,o)||Math.max(0,a))}var lE=function(){var t,e,n=oE,r=aE,i=fE,o=uE,a=cE,s=[0,1/0],u=[[-1/0,-1/0],[1/0,1/0]],c=250,f=ur,l=gt("start","zoom","end"),h=500,d=150,p=0;function g(t){t.property("__zoom",sE).on("wheel.zoom",x).on("mousedown.zoom",k).on("dblclick.zoom",E).filter(a).on("touchstart.zoom",A).on("touchmove.zoom",S).on("touchend.zoom touchcancel.zoom",T).style("touch-action","none").style("-webkit-tap-highlight-color","rgba(0,0,0,0)")}function y(t,e){return(e=Math.max(s[0],Math.min(s[1],e)))===t.k?t:new tE(e,t.x,t.y)}function b(t,e,n){var r=e[0]-n[0]*t.k,i=e[1]-n[1]*t.k;return r===t.x&&i===t.y?t:new tE(t.k,r,i)}function m(t){return[(+t[0][0]+ +t[1][0])/2,(+t[0][1]+ +t[1][1])/2]}function v(t,e,n){t.on("start.zoom",(function(){_(this,arguments).start()})).on("interrupt.zoom end.zoom",(function(){_(this,arguments).end()})).tween("zoom",(function(){var t=this,i=arguments,o=_(t,i),a=r.apply(t,i),s=null==n?m(a):"function"==typeof n?n.apply(t,i):n,u=Math.max(a[1][0]-a[0][0],a[1][1]-a[0][1]),c=t.__zoom,l="function"==typeof e?e.apply(t,i):e,h=f(c.invert(s).concat(u/c.k),l.invert(s).concat(u/l.k));return function(t){if(1===t)t=l;else{var e=h(t),n=u/e[2];t=new tE(n,s[0]-e[0]*n,s[1]-e[1]*n)}o.zoom(null,t)}}))}function _(t,e,n){return!n&&t.__zooming||new w(t,e)}function w(t,e){this.that=t,this.args=e,this.active=0,this.extent=r.apply(t,e),this.taps=0}function x(){if(n.apply(this,arguments)){var t=_(this,arguments),e=this.__zoom,r=Math.max(s[0],Math.min(s[1],e.k*Math.pow(2,o.apply(this,arguments)))),a=Be(this);if(t.wheel)t.mouse[0][0]===a[0]&&t.mouse[0][1]===a[1]||(t.mouse[1]=e.invert(t.mouse[0]=a)),clearTimeout(t.wheel);else{if(e.k===r)return;t.mouse=[a,e.invert(a)],Oi(this),t.start()}iE(),t.wheel=setTimeout((function(){t.wheel=null,t.end()}),d),t.zoom("mouse",i(b(y(e,r),t.mouse[0],t.mouse[1]),t.extent,u))}}function k(){if(!e&&n.apply(this,arguments)){var t=_(this,arguments,!0),r=Me(pe.view).on("mousemove.zoom",(function(){if(iE(),!t.moved){var e=pe.clientX-a,n=pe.clientY-s;t.moved=e*e+n*n>p}t.zoom("mouse",i(b(t.that.__zoom,t.mouse[0]=Be(t.that),t.mouse[1]),t.extent,u))}),!0).on("mouseup.zoom",(function(){r.on("mousemove.zoom mouseup.zoom",null),ze(pe.view,t.moved),iE(),t.end()}),!0),o=Be(this),a=pe.clientX,s=pe.clientY;Ue(pe.view),rE(),t.mouse=[o,this.__zoom.invert(o)],Oi(this),t.start()}}function E(){if(n.apply(this,arguments)){var t=this.__zoom,e=Be(this),o=t.invert(e),a=t.k*(pe.shiftKey?.5:2),s=i(b(y(t,a),e,o),r.apply(this,arguments),u);iE(),c>0?Me(this).transition().duration(c).call(v,s,e):Me(this).call(g.transform,s)}}function A(){if(n.apply(this,arguments)){var e,r,i,o,a=pe.touches,s=a.length,u=_(this,arguments,pe.changedTouches.length===s);for(rE(),r=0;rl&&M.push("'"+this.terminals_[A]+"'");C=p.showPosition?"Parse error on line "+(u+1)+":\n"+p.showPosition()+"\nExpecting "+M.join(", ")+", got '"+(this.terminals_[_]||_)+"'":"Parse error on line "+(u+1)+": Unexpected "+(_==h?"end of input":"'"+(this.terminals_[_]||_)+"'"),this.parseError(C,{text:p.match,token:this.terminals_[_]||_,line:p.yylineno,loc:b,expected:M})}if(k[0]instanceof Array&&k.length>1)throw new Error("Parse Error: multiple actions possible at state: "+x+", token: "+_);switch(k[0]){case 1:n.push(_),i.push(p.yytext),o.push(p.yylloc),n.push(k[1]),_=null,w?(_=w,w=null):(c=p.yyleng,s=p.yytext,u=p.yylineno,b=p.yylloc,f>0&&f--);break;case 2:if(S=this.productions_[k[1]][1],D.$=i[i.length-S],D._$={first_line:o[o.length-(S||1)].first_line,last_line:o[o.length-1].last_line,first_column:o[o.length-(S||1)].first_column,last_column:o[o.length-1].last_column},m&&(D._$.range=[o[o.length-(S||1)].range[0],o[o.length-1].range[1]]),void 0!==(E=this.performAction.apply(D,[s,c,u,g.yy,k[1],i,o].concat(d))))return E;S&&(n=n.slice(0,-1*S*2),i=i.slice(0,-1*S),o=o.slice(0,-1*S)),n.push(this.productions_[k[1]][0]),i.push(D.$),o.push(D._$),T=a[n[n.length-2]][n[n.length-1]],n.push(T);break;case 3:return!0}}return!0}},A={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var o in i)this[o]=i[o];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),o=0;oe[0].length)){if(e=n,r=o,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[o])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var t=this.next();return t||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{"case-insensitive":!0},performAction:function(t,e,n,r){switch(n){case 0:return 5;case 1:case 2:case 3:case 4:break;case 5:return this.begin("ID"),10;case 6:return e.yytext=e.yytext.trim(),this.begin("ALIAS"),41;case 7:return this.popState(),this.popState(),this.begin("LINE"),12;case 8:return this.popState(),this.popState(),5;case 9:return this.begin("LINE"),20;case 10:return this.begin("LINE"),22;case 11:return this.begin("LINE"),23;case 12:return this.begin("LINE"),24;case 13:return this.begin("LINE"),29;case 14:return this.begin("LINE"),26;case 15:return this.begin("LINE"),28;case 16:return this.popState(),13;case 17:return 21;case 18:return 36;case 19:return 37;case 20:return 32;case 21:return 30;case 22:return this.begin("ID"),15;case 23:return this.begin("ID"),16;case 24:return 18;case 25:return 6;case 26:return 35;case 27:return 5;case 28:return e.yytext=e.yytext.trim(),41;case 29:return 44;case 30:return 45;case 31:return 42;case 32:return 43;case 33:return 46;case 34:return 47;case 35:return 48;case 36:return 39;case 37:return 40;case 38:return 5;case 39:return"INVALID"}},rules:[/^(?:[\n]+)/i,/^(?:\s+)/i,/^(?:((?!\n)\s)+)/i,/^(?:#[^\n]*)/i,/^(?:%[^\n]*)/i,/^(?:participant\b)/i,/^(?:[^\->:\n,;]+?(?=((?!\n)\s)+as(?!\n)\s|[#\n;]|$))/i,/^(?:as\b)/i,/^(?:(?:))/i,/^(?:loop\b)/i,/^(?:rect\b)/i,/^(?:opt\b)/i,/^(?:alt\b)/i,/^(?:else\b)/i,/^(?:par\b)/i,/^(?:and\b)/i,/^(?:[^#\n;]*)/i,/^(?:end\b)/i,/^(?:left of\b)/i,/^(?:right of\b)/i,/^(?:over\b)/i,/^(?:note\b)/i,/^(?:activate\b)/i,/^(?:deactivate\b)/i,/^(?:title\b)/i,/^(?:sequenceDiagram\b)/i,/^(?:,)/i,/^(?:;)/i,/^(?:[^\+\->:\n,;]+)/i,/^(?:->>)/i,/^(?:-->>)/i,/^(?:->)/i,/^(?:-->)/i,/^(?:-[x])/i,/^(?:--[x])/i,/^(?::[^#\n;]+)/i,/^(?:\+)/i,/^(?:-)/i,/^(?:$)/i,/^(?:.)/i],conditions:{LINE:{rules:[2,3,16],inclusive:!1},ALIAS:{rules:[2,3,7,8],inclusive:!1},ID:{rules:[2,3,6],inclusive:!1},INITIAL:{rules:[0,1,3,4,5,9,10,11,12,13,14,15,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39],inclusive:!0}}};function S(){this.yy={}}return E.lexer=A,S.prototype=E,E.Parser=S,new S}();e.parser=i,e.Parser=i.Parser,e.parse=function(){return i.parse.apply(i,arguments)},e.main=function(r){r[1]||(console.log("Usage: "+r[0]+" FILE"),t.exit(1));var i=n(28).readFileSync(n(29).normalize(r[1]),"utf8");return e.parser.parse(i)},n.c[n.s]===r&&e.main(t.argv.slice(1))}).call(this,n(7),n(9)(t))},function(t,e){"function"==typeof Object.create?t.exports=function(t,e){e&&(t.super_=e,t.prototype=Object.create(e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}))}:t.exports=function(t,e){if(e){t.super_=e;var n=function(){};n.prototype=e.prototype,t.prototype=new n,t.prototype.constructor=t}}},function(t,e,n){var r=n(8),i=r.Buffer;function o(t,e){for(var n in t)e[n]=t[n]}function a(t,e,n){return i(t,e,n)}i.from&&i.alloc&&i.allocUnsafe&&i.allocUnsafeSlow?t.exports=r:(o(r,e),e.Buffer=a),a.prototype=Object.create(i.prototype),o(i,a),a.from=function(t,e,n){if("number"==typeof t)throw new TypeError("Argument must not be a number");return i(t,e,n)},a.alloc=function(t,e,n){if("number"!=typeof t)throw new TypeError("Argument must be a number");var r=i(t);return void 0!==e?"string"==typeof n?r.fill(e,n):r.fill(e):r.fill(0),r},a.allocUnsafe=function(t){if("number"!=typeof t)throw new TypeError("Argument must be a number");return i(t)},a.allocUnsafeSlow=function(t){if("number"!=typeof t)throw new TypeError("Argument must be a number");return r.SlowBuffer(t)}},function(t,e,n){var r;try{r={cloneDeep:n(348),constant:n(99),defaults:n(176),each:n(100),filter:n(150),find:n(349),flatten:n(178),forEach:n(148),forIn:n(354),has:n(106),isUndefined:n(161),last:n(355),map:n(162),mapValues:n(356),max:n(357),merge:n(359),min:n(364),minBy:n(365),now:n(366),pick:n(183),range:n(184),reduce:n(164),sortBy:n(373),uniqueId:n(185),values:n(169),zipObject:n(378)}}catch(t){}r||(r=window._),t.exports=r},function(t,e,n){(function(t){!function(t,e){"use strict";function r(t,e){if(!t)throw new Error(e||"Assertion failed")}function i(t,e){t.super_=e;var n=function(){};n.prototype=e.prototype,t.prototype=new n,t.prototype.constructor=t}function o(t,e,n){if(o.isBN(t))return t;this.negative=0,this.words=null,this.length=0,this.red=null,null!==t&&("le"!==e&&"be"!==e||(n=e,e=10),this._init(t||0,e||10,n||"be"))}var a;"object"==typeof t?t.exports=o:e.BN=o,o.BN=o,o.wordSize=26;try{a=n(457).Buffer}catch(t){}function s(t,e,n){for(var r=0,i=Math.min(t.length,n),o=e;o=49&&a<=54?a-49+10:a>=17&&a<=22?a-17+10:15&a}return r}function u(t,e,n,r){for(var i=0,o=Math.min(t.length,n),a=e;a=49?s-49+10:s>=17?s-17+10:s}return i}o.isBN=function(t){return t instanceof o||null!==t&&"object"==typeof t&&t.constructor.wordSize===o.wordSize&&Array.isArray(t.words)},o.max=function(t,e){return t.cmp(e)>0?t:e},o.min=function(t,e){return t.cmp(e)<0?t:e},o.prototype._init=function(t,e,n){if("number"==typeof t)return this._initNumber(t,e,n);if("object"==typeof t)return this._initArray(t,e,n);"hex"===e&&(e=16),r(e===(0|e)&&e>=2&&e<=36);var i=0;"-"===(t=t.toString().replace(/\s+/g,""))[0]&&i++,16===e?this._parseHex(t,i):this._parseBase(t,e,i),"-"===t[0]&&(this.negative=1),this.strip(),"le"===n&&this._initArray(this.toArray(),e,n)},o.prototype._initNumber=function(t,e,n){t<0&&(this.negative=1,t=-t),t<67108864?(this.words=[67108863&t],this.length=1):t<4503599627370496?(this.words=[67108863&t,t/67108864&67108863],this.length=2):(r(t<9007199254740992),this.words=[67108863&t,t/67108864&67108863,1],this.length=3),"le"===n&&this._initArray(this.toArray(),e,n)},o.prototype._initArray=function(t,e,n){if(r("number"==typeof t.length),t.length<=0)return this.words=[0],this.length=1,this;this.length=Math.ceil(t.length/3),this.words=new Array(this.length);for(var i=0;i=0;i-=3)a=t[i]|t[i-1]<<8|t[i-2]<<16,this.words[o]|=a<>>26-s&67108863,(s+=24)>=26&&(s-=26,o++);else if("le"===n)for(i=0,o=0;i>>26-s&67108863,(s+=24)>=26&&(s-=26,o++);return this.strip()},o.prototype._parseHex=function(t,e){this.length=Math.ceil((t.length-e)/6),this.words=new Array(this.length);for(var n=0;n=e;n-=6)i=s(t,n,n+6),this.words[r]|=i<>>26-o&4194303,(o+=24)>=26&&(o-=26,r++);n+6!==e&&(i=s(t,e,n+6),this.words[r]|=i<>>26-o&4194303),this.strip()},o.prototype._parseBase=function(t,e,n){this.words=[0],this.length=1;for(var r=0,i=1;i<=67108863;i*=e)r++;r--,i=i/e|0;for(var o=t.length-n,a=o%r,s=Math.min(o,o-a)+n,c=0,f=n;f1&&0===this.words[this.length-1];)this.length--;return this._normSign()},o.prototype._normSign=function(){return 1===this.length&&0===this.words[0]&&(this.negative=0),this},o.prototype.inspect=function(){return(this.red?""};var c=["","0","00","000","0000","00000","000000","0000000","00000000","000000000","0000000000","00000000000","000000000000","0000000000000","00000000000000","000000000000000","0000000000000000","00000000000000000","000000000000000000","0000000000000000000","00000000000000000000","000000000000000000000","0000000000000000000000","00000000000000000000000","000000000000000000000000","0000000000000000000000000"],f=[0,0,25,16,12,11,10,9,8,8,7,7,7,7,6,6,6,6,6,6,6,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5],l=[0,0,33554432,43046721,16777216,48828125,60466176,40353607,16777216,43046721,1e7,19487171,35831808,62748517,7529536,11390625,16777216,24137569,34012224,47045881,64e6,4084101,5153632,6436343,7962624,9765625,11881376,14348907,17210368,20511149,243e5,28629151,33554432,39135393,45435424,52521875,60466176];function h(t,e,n){n.negative=e.negative^t.negative;var r=t.length+e.length|0;n.length=r,r=r-1|0;var i=0|t.words[0],o=0|e.words[0],a=i*o,s=67108863&a,u=a/67108864|0;n.words[0]=s;for(var c=1;c>>26,l=67108863&u,h=Math.min(c,e.length-1),d=Math.max(0,c-t.length+1);d<=h;d++){var p=c-d|0;f+=(a=(i=0|t.words[p])*(o=0|e.words[d])+l)/67108864|0,l=67108863&a}n.words[c]=0|l,u=0|f}return 0!==u?n.words[c]=0|u:n.length--,n.strip()}o.prototype.toString=function(t,e){var n;if(e=0|e||1,16===(t=t||10)||"hex"===t){n="";for(var i=0,o=0,a=0;a>>24-i&16777215)||a!==this.length-1?c[6-u.length]+u+n:u+n,(i+=2)>=26&&(i-=26,a--)}for(0!==o&&(n=o.toString(16)+n);n.length%e!=0;)n="0"+n;return 0!==this.negative&&(n="-"+n),n}if(t===(0|t)&&t>=2&&t<=36){var h=f[t],d=l[t];n="";var p=this.clone();for(p.negative=0;!p.isZero();){var g=p.modn(d).toString(t);n=(p=p.idivn(d)).isZero()?g+n:c[h-g.length]+g+n}for(this.isZero()&&(n="0"+n);n.length%e!=0;)n="0"+n;return 0!==this.negative&&(n="-"+n),n}r(!1,"Base should be between 2 and 36")},o.prototype.toNumber=function(){var t=this.words[0];return 2===this.length?t+=67108864*this.words[1]:3===this.length&&1===this.words[2]?t+=4503599627370496+67108864*this.words[1]:this.length>2&&r(!1,"Number can only safely store up to 53 bits"),0!==this.negative?-t:t},o.prototype.toJSON=function(){return this.toString(16)},o.prototype.toBuffer=function(t,e){return r(void 0!==a),this.toArrayLike(a,t,e)},o.prototype.toArray=function(t,e){return this.toArrayLike(Array,t,e)},o.prototype.toArrayLike=function(t,e,n){var i=this.byteLength(),o=n||Math.max(1,i);r(i<=o,"byte array longer than desired length"),r(o>0,"Requested array length <= 0"),this.strip();var a,s,u="le"===e,c=new t(o),f=this.clone();if(u){for(s=0;!f.isZero();s++)a=f.andln(255),f.iushrn(8),c[s]=a;for(;s=4096&&(n+=13,e>>>=13),e>=64&&(n+=7,e>>>=7),e>=8&&(n+=4,e>>>=4),e>=2&&(n+=2,e>>>=2),n+e},o.prototype._zeroBits=function(t){if(0===t)return 26;var e=t,n=0;return 0==(8191&e)&&(n+=13,e>>>=13),0==(127&e)&&(n+=7,e>>>=7),0==(15&e)&&(n+=4,e>>>=4),0==(3&e)&&(n+=2,e>>>=2),0==(1&e)&&n++,n},o.prototype.bitLength=function(){var t=this.words[this.length-1],e=this._countBits(t);return 26*(this.length-1)+e},o.prototype.zeroBits=function(){if(this.isZero())return 0;for(var t=0,e=0;et.length?this.clone().ior(t):t.clone().ior(this)},o.prototype.uor=function(t){return this.length>t.length?this.clone().iuor(t):t.clone().iuor(this)},o.prototype.iuand=function(t){var e;e=this.length>t.length?t:this;for(var n=0;nt.length?this.clone().iand(t):t.clone().iand(this)},o.prototype.uand=function(t){return this.length>t.length?this.clone().iuand(t):t.clone().iuand(this)},o.prototype.iuxor=function(t){var e,n;this.length>t.length?(e=this,n=t):(e=t,n=this);for(var r=0;rt.length?this.clone().ixor(t):t.clone().ixor(this)},o.prototype.uxor=function(t){return this.length>t.length?this.clone().iuxor(t):t.clone().iuxor(this)},o.prototype.inotn=function(t){r("number"==typeof t&&t>=0);var e=0|Math.ceil(t/26),n=t%26;this._expand(e),n>0&&e--;for(var i=0;i0&&(this.words[i]=~this.words[i]&67108863>>26-n),this.strip()},o.prototype.notn=function(t){return this.clone().inotn(t)},o.prototype.setn=function(t,e){r("number"==typeof t&&t>=0);var n=t/26|0,i=t%26;return this._expand(n+1),this.words[n]=e?this.words[n]|1<t.length?(n=this,r=t):(n=t,r=this);for(var i=0,o=0;o>>26;for(;0!==i&&o>>26;if(this.length=n.length,0!==i)this.words[this.length]=i,this.length++;else if(n!==this)for(;ot.length?this.clone().iadd(t):t.clone().iadd(this)},o.prototype.isub=function(t){if(0!==t.negative){t.negative=0;var e=this.iadd(t);return t.negative=1,e._normSign()}if(0!==this.negative)return this.negative=0,this.iadd(t),this.negative=1,this._normSign();var n,r,i=this.cmp(t);if(0===i)return this.negative=0,this.length=1,this.words[0]=0,this;i>0?(n=this,r=t):(n=t,r=this);for(var o=0,a=0;a>26,this.words[a]=67108863&e;for(;0!==o&&a>26,this.words[a]=67108863&e;if(0===o&&a>>13,d=0|a[1],p=8191&d,g=d>>>13,y=0|a[2],b=8191&y,m=y>>>13,v=0|a[3],_=8191&v,w=v>>>13,x=0|a[4],k=8191&x,E=x>>>13,A=0|a[5],S=8191&A,T=A>>>13,M=0|a[6],D=8191&M,C=M>>>13,O=0|a[7],R=8191&O,I=O>>>13,N=0|a[8],B=8191&N,L=N>>>13,P=0|a[9],F=8191&P,q=P>>>13,j=0|s[0],U=8191&j,z=j>>>13,Y=0|s[1],V=8191&Y,H=Y>>>13,$=0|s[2],G=8191&$,W=$>>>13,K=0|s[3],X=8191&K,Z=K>>>13,J=0|s[4],Q=8191&J,tt=J>>>13,et=0|s[5],nt=8191&et,rt=et>>>13,it=0|s[6],ot=8191&it,at=it>>>13,st=0|s[7],ut=8191&st,ct=st>>>13,ft=0|s[8],lt=8191&ft,ht=ft>>>13,dt=0|s[9],pt=8191&dt,gt=dt>>>13;n.negative=t.negative^e.negative,n.length=19;var yt=(c+(r=Math.imul(l,U))|0)+((8191&(i=(i=Math.imul(l,z))+Math.imul(h,U)|0))<<13)|0;c=((o=Math.imul(h,z))+(i>>>13)|0)+(yt>>>26)|0,yt&=67108863,r=Math.imul(p,U),i=(i=Math.imul(p,z))+Math.imul(g,U)|0,o=Math.imul(g,z);var bt=(c+(r=r+Math.imul(l,V)|0)|0)+((8191&(i=(i=i+Math.imul(l,H)|0)+Math.imul(h,V)|0))<<13)|0;c=((o=o+Math.imul(h,H)|0)+(i>>>13)|0)+(bt>>>26)|0,bt&=67108863,r=Math.imul(b,U),i=(i=Math.imul(b,z))+Math.imul(m,U)|0,o=Math.imul(m,z),r=r+Math.imul(p,V)|0,i=(i=i+Math.imul(p,H)|0)+Math.imul(g,V)|0,o=o+Math.imul(g,H)|0;var mt=(c+(r=r+Math.imul(l,G)|0)|0)+((8191&(i=(i=i+Math.imul(l,W)|0)+Math.imul(h,G)|0))<<13)|0;c=((o=o+Math.imul(h,W)|0)+(i>>>13)|0)+(mt>>>26)|0,mt&=67108863,r=Math.imul(_,U),i=(i=Math.imul(_,z))+Math.imul(w,U)|0,o=Math.imul(w,z),r=r+Math.imul(b,V)|0,i=(i=i+Math.imul(b,H)|0)+Math.imul(m,V)|0,o=o+Math.imul(m,H)|0,r=r+Math.imul(p,G)|0,i=(i=i+Math.imul(p,W)|0)+Math.imul(g,G)|0,o=o+Math.imul(g,W)|0;var vt=(c+(r=r+Math.imul(l,X)|0)|0)+((8191&(i=(i=i+Math.imul(l,Z)|0)+Math.imul(h,X)|0))<<13)|0;c=((o=o+Math.imul(h,Z)|0)+(i>>>13)|0)+(vt>>>26)|0,vt&=67108863,r=Math.imul(k,U),i=(i=Math.imul(k,z))+Math.imul(E,U)|0,o=Math.imul(E,z),r=r+Math.imul(_,V)|0,i=(i=i+Math.imul(_,H)|0)+Math.imul(w,V)|0,o=o+Math.imul(w,H)|0,r=r+Math.imul(b,G)|0,i=(i=i+Math.imul(b,W)|0)+Math.imul(m,G)|0,o=o+Math.imul(m,W)|0,r=r+Math.imul(p,X)|0,i=(i=i+Math.imul(p,Z)|0)+Math.imul(g,X)|0,o=o+Math.imul(g,Z)|0;var _t=(c+(r=r+Math.imul(l,Q)|0)|0)+((8191&(i=(i=i+Math.imul(l,tt)|0)+Math.imul(h,Q)|0))<<13)|0;c=((o=o+Math.imul(h,tt)|0)+(i>>>13)|0)+(_t>>>26)|0,_t&=67108863,r=Math.imul(S,U),i=(i=Math.imul(S,z))+Math.imul(T,U)|0,o=Math.imul(T,z),r=r+Math.imul(k,V)|0,i=(i=i+Math.imul(k,H)|0)+Math.imul(E,V)|0,o=o+Math.imul(E,H)|0,r=r+Math.imul(_,G)|0,i=(i=i+Math.imul(_,W)|0)+Math.imul(w,G)|0,o=o+Math.imul(w,W)|0,r=r+Math.imul(b,X)|0,i=(i=i+Math.imul(b,Z)|0)+Math.imul(m,X)|0,o=o+Math.imul(m,Z)|0,r=r+Math.imul(p,Q)|0,i=(i=i+Math.imul(p,tt)|0)+Math.imul(g,Q)|0,o=o+Math.imul(g,tt)|0;var wt=(c+(r=r+Math.imul(l,nt)|0)|0)+((8191&(i=(i=i+Math.imul(l,rt)|0)+Math.imul(h,nt)|0))<<13)|0;c=((o=o+Math.imul(h,rt)|0)+(i>>>13)|0)+(wt>>>26)|0,wt&=67108863,r=Math.imul(D,U),i=(i=Math.imul(D,z))+Math.imul(C,U)|0,o=Math.imul(C,z),r=r+Math.imul(S,V)|0,i=(i=i+Math.imul(S,H)|0)+Math.imul(T,V)|0,o=o+Math.imul(T,H)|0,r=r+Math.imul(k,G)|0,i=(i=i+Math.imul(k,W)|0)+Math.imul(E,G)|0,o=o+Math.imul(E,W)|0,r=r+Math.imul(_,X)|0,i=(i=i+Math.imul(_,Z)|0)+Math.imul(w,X)|0,o=o+Math.imul(w,Z)|0,r=r+Math.imul(b,Q)|0,i=(i=i+Math.imul(b,tt)|0)+Math.imul(m,Q)|0,o=o+Math.imul(m,tt)|0,r=r+Math.imul(p,nt)|0,i=(i=i+Math.imul(p,rt)|0)+Math.imul(g,nt)|0,o=o+Math.imul(g,rt)|0;var xt=(c+(r=r+Math.imul(l,ot)|0)|0)+((8191&(i=(i=i+Math.imul(l,at)|0)+Math.imul(h,ot)|0))<<13)|0;c=((o=o+Math.imul(h,at)|0)+(i>>>13)|0)+(xt>>>26)|0,xt&=67108863,r=Math.imul(R,U),i=(i=Math.imul(R,z))+Math.imul(I,U)|0,o=Math.imul(I,z),r=r+Math.imul(D,V)|0,i=(i=i+Math.imul(D,H)|0)+Math.imul(C,V)|0,o=o+Math.imul(C,H)|0,r=r+Math.imul(S,G)|0,i=(i=i+Math.imul(S,W)|0)+Math.imul(T,G)|0,o=o+Math.imul(T,W)|0,r=r+Math.imul(k,X)|0,i=(i=i+Math.imul(k,Z)|0)+Math.imul(E,X)|0,o=o+Math.imul(E,Z)|0,r=r+Math.imul(_,Q)|0,i=(i=i+Math.imul(_,tt)|0)+Math.imul(w,Q)|0,o=o+Math.imul(w,tt)|0,r=r+Math.imul(b,nt)|0,i=(i=i+Math.imul(b,rt)|0)+Math.imul(m,nt)|0,o=o+Math.imul(m,rt)|0,r=r+Math.imul(p,ot)|0,i=(i=i+Math.imul(p,at)|0)+Math.imul(g,ot)|0,o=o+Math.imul(g,at)|0;var kt=(c+(r=r+Math.imul(l,ut)|0)|0)+((8191&(i=(i=i+Math.imul(l,ct)|0)+Math.imul(h,ut)|0))<<13)|0;c=((o=o+Math.imul(h,ct)|0)+(i>>>13)|0)+(kt>>>26)|0,kt&=67108863,r=Math.imul(B,U),i=(i=Math.imul(B,z))+Math.imul(L,U)|0,o=Math.imul(L,z),r=r+Math.imul(R,V)|0,i=(i=i+Math.imul(R,H)|0)+Math.imul(I,V)|0,o=o+Math.imul(I,H)|0,r=r+Math.imul(D,G)|0,i=(i=i+Math.imul(D,W)|0)+Math.imul(C,G)|0,o=o+Math.imul(C,W)|0,r=r+Math.imul(S,X)|0,i=(i=i+Math.imul(S,Z)|0)+Math.imul(T,X)|0,o=o+Math.imul(T,Z)|0,r=r+Math.imul(k,Q)|0,i=(i=i+Math.imul(k,tt)|0)+Math.imul(E,Q)|0,o=o+Math.imul(E,tt)|0,r=r+Math.imul(_,nt)|0,i=(i=i+Math.imul(_,rt)|0)+Math.imul(w,nt)|0,o=o+Math.imul(w,rt)|0,r=r+Math.imul(b,ot)|0,i=(i=i+Math.imul(b,at)|0)+Math.imul(m,ot)|0,o=o+Math.imul(m,at)|0,r=r+Math.imul(p,ut)|0,i=(i=i+Math.imul(p,ct)|0)+Math.imul(g,ut)|0,o=o+Math.imul(g,ct)|0;var Et=(c+(r=r+Math.imul(l,lt)|0)|0)+((8191&(i=(i=i+Math.imul(l,ht)|0)+Math.imul(h,lt)|0))<<13)|0;c=((o=o+Math.imul(h,ht)|0)+(i>>>13)|0)+(Et>>>26)|0,Et&=67108863,r=Math.imul(F,U),i=(i=Math.imul(F,z))+Math.imul(q,U)|0,o=Math.imul(q,z),r=r+Math.imul(B,V)|0,i=(i=i+Math.imul(B,H)|0)+Math.imul(L,V)|0,o=o+Math.imul(L,H)|0,r=r+Math.imul(R,G)|0,i=(i=i+Math.imul(R,W)|0)+Math.imul(I,G)|0,o=o+Math.imul(I,W)|0,r=r+Math.imul(D,X)|0,i=(i=i+Math.imul(D,Z)|0)+Math.imul(C,X)|0,o=o+Math.imul(C,Z)|0,r=r+Math.imul(S,Q)|0,i=(i=i+Math.imul(S,tt)|0)+Math.imul(T,Q)|0,o=o+Math.imul(T,tt)|0,r=r+Math.imul(k,nt)|0,i=(i=i+Math.imul(k,rt)|0)+Math.imul(E,nt)|0,o=o+Math.imul(E,rt)|0,r=r+Math.imul(_,ot)|0,i=(i=i+Math.imul(_,at)|0)+Math.imul(w,ot)|0,o=o+Math.imul(w,at)|0,r=r+Math.imul(b,ut)|0,i=(i=i+Math.imul(b,ct)|0)+Math.imul(m,ut)|0,o=o+Math.imul(m,ct)|0,r=r+Math.imul(p,lt)|0,i=(i=i+Math.imul(p,ht)|0)+Math.imul(g,lt)|0,o=o+Math.imul(g,ht)|0;var At=(c+(r=r+Math.imul(l,pt)|0)|0)+((8191&(i=(i=i+Math.imul(l,gt)|0)+Math.imul(h,pt)|0))<<13)|0;c=((o=o+Math.imul(h,gt)|0)+(i>>>13)|0)+(At>>>26)|0,At&=67108863,r=Math.imul(F,V),i=(i=Math.imul(F,H))+Math.imul(q,V)|0,o=Math.imul(q,H),r=r+Math.imul(B,G)|0,i=(i=i+Math.imul(B,W)|0)+Math.imul(L,G)|0,o=o+Math.imul(L,W)|0,r=r+Math.imul(R,X)|0,i=(i=i+Math.imul(R,Z)|0)+Math.imul(I,X)|0,o=o+Math.imul(I,Z)|0,r=r+Math.imul(D,Q)|0,i=(i=i+Math.imul(D,tt)|0)+Math.imul(C,Q)|0,o=o+Math.imul(C,tt)|0,r=r+Math.imul(S,nt)|0,i=(i=i+Math.imul(S,rt)|0)+Math.imul(T,nt)|0,o=o+Math.imul(T,rt)|0,r=r+Math.imul(k,ot)|0,i=(i=i+Math.imul(k,at)|0)+Math.imul(E,ot)|0,o=o+Math.imul(E,at)|0,r=r+Math.imul(_,ut)|0,i=(i=i+Math.imul(_,ct)|0)+Math.imul(w,ut)|0,o=o+Math.imul(w,ct)|0,r=r+Math.imul(b,lt)|0,i=(i=i+Math.imul(b,ht)|0)+Math.imul(m,lt)|0,o=o+Math.imul(m,ht)|0;var St=(c+(r=r+Math.imul(p,pt)|0)|0)+((8191&(i=(i=i+Math.imul(p,gt)|0)+Math.imul(g,pt)|0))<<13)|0;c=((o=o+Math.imul(g,gt)|0)+(i>>>13)|0)+(St>>>26)|0,St&=67108863,r=Math.imul(F,G),i=(i=Math.imul(F,W))+Math.imul(q,G)|0,o=Math.imul(q,W),r=r+Math.imul(B,X)|0,i=(i=i+Math.imul(B,Z)|0)+Math.imul(L,X)|0,o=o+Math.imul(L,Z)|0,r=r+Math.imul(R,Q)|0,i=(i=i+Math.imul(R,tt)|0)+Math.imul(I,Q)|0,o=o+Math.imul(I,tt)|0,r=r+Math.imul(D,nt)|0,i=(i=i+Math.imul(D,rt)|0)+Math.imul(C,nt)|0,o=o+Math.imul(C,rt)|0,r=r+Math.imul(S,ot)|0,i=(i=i+Math.imul(S,at)|0)+Math.imul(T,ot)|0,o=o+Math.imul(T,at)|0,r=r+Math.imul(k,ut)|0,i=(i=i+Math.imul(k,ct)|0)+Math.imul(E,ut)|0,o=o+Math.imul(E,ct)|0,r=r+Math.imul(_,lt)|0,i=(i=i+Math.imul(_,ht)|0)+Math.imul(w,lt)|0,o=o+Math.imul(w,ht)|0;var Tt=(c+(r=r+Math.imul(b,pt)|0)|0)+((8191&(i=(i=i+Math.imul(b,gt)|0)+Math.imul(m,pt)|0))<<13)|0;c=((o=o+Math.imul(m,gt)|0)+(i>>>13)|0)+(Tt>>>26)|0,Tt&=67108863,r=Math.imul(F,X),i=(i=Math.imul(F,Z))+Math.imul(q,X)|0,o=Math.imul(q,Z),r=r+Math.imul(B,Q)|0,i=(i=i+Math.imul(B,tt)|0)+Math.imul(L,Q)|0,o=o+Math.imul(L,tt)|0,r=r+Math.imul(R,nt)|0,i=(i=i+Math.imul(R,rt)|0)+Math.imul(I,nt)|0,o=o+Math.imul(I,rt)|0,r=r+Math.imul(D,ot)|0,i=(i=i+Math.imul(D,at)|0)+Math.imul(C,ot)|0,o=o+Math.imul(C,at)|0,r=r+Math.imul(S,ut)|0,i=(i=i+Math.imul(S,ct)|0)+Math.imul(T,ut)|0,o=o+Math.imul(T,ct)|0,r=r+Math.imul(k,lt)|0,i=(i=i+Math.imul(k,ht)|0)+Math.imul(E,lt)|0,o=o+Math.imul(E,ht)|0;var Mt=(c+(r=r+Math.imul(_,pt)|0)|0)+((8191&(i=(i=i+Math.imul(_,gt)|0)+Math.imul(w,pt)|0))<<13)|0;c=((o=o+Math.imul(w,gt)|0)+(i>>>13)|0)+(Mt>>>26)|0,Mt&=67108863,r=Math.imul(F,Q),i=(i=Math.imul(F,tt))+Math.imul(q,Q)|0,o=Math.imul(q,tt),r=r+Math.imul(B,nt)|0,i=(i=i+Math.imul(B,rt)|0)+Math.imul(L,nt)|0,o=o+Math.imul(L,rt)|0,r=r+Math.imul(R,ot)|0,i=(i=i+Math.imul(R,at)|0)+Math.imul(I,ot)|0,o=o+Math.imul(I,at)|0,r=r+Math.imul(D,ut)|0,i=(i=i+Math.imul(D,ct)|0)+Math.imul(C,ut)|0,o=o+Math.imul(C,ct)|0,r=r+Math.imul(S,lt)|0,i=(i=i+Math.imul(S,ht)|0)+Math.imul(T,lt)|0,o=o+Math.imul(T,ht)|0;var Dt=(c+(r=r+Math.imul(k,pt)|0)|0)+((8191&(i=(i=i+Math.imul(k,gt)|0)+Math.imul(E,pt)|0))<<13)|0;c=((o=o+Math.imul(E,gt)|0)+(i>>>13)|0)+(Dt>>>26)|0,Dt&=67108863,r=Math.imul(F,nt),i=(i=Math.imul(F,rt))+Math.imul(q,nt)|0,o=Math.imul(q,rt),r=r+Math.imul(B,ot)|0,i=(i=i+Math.imul(B,at)|0)+Math.imul(L,ot)|0,o=o+Math.imul(L,at)|0,r=r+Math.imul(R,ut)|0,i=(i=i+Math.imul(R,ct)|0)+Math.imul(I,ut)|0,o=o+Math.imul(I,ct)|0,r=r+Math.imul(D,lt)|0,i=(i=i+Math.imul(D,ht)|0)+Math.imul(C,lt)|0,o=o+Math.imul(C,ht)|0;var Ct=(c+(r=r+Math.imul(S,pt)|0)|0)+((8191&(i=(i=i+Math.imul(S,gt)|0)+Math.imul(T,pt)|0))<<13)|0;c=((o=o+Math.imul(T,gt)|0)+(i>>>13)|0)+(Ct>>>26)|0,Ct&=67108863,r=Math.imul(F,ot),i=(i=Math.imul(F,at))+Math.imul(q,ot)|0,o=Math.imul(q,at),r=r+Math.imul(B,ut)|0,i=(i=i+Math.imul(B,ct)|0)+Math.imul(L,ut)|0,o=o+Math.imul(L,ct)|0,r=r+Math.imul(R,lt)|0,i=(i=i+Math.imul(R,ht)|0)+Math.imul(I,lt)|0,o=o+Math.imul(I,ht)|0;var Ot=(c+(r=r+Math.imul(D,pt)|0)|0)+((8191&(i=(i=i+Math.imul(D,gt)|0)+Math.imul(C,pt)|0))<<13)|0;c=((o=o+Math.imul(C,gt)|0)+(i>>>13)|0)+(Ot>>>26)|0,Ot&=67108863,r=Math.imul(F,ut),i=(i=Math.imul(F,ct))+Math.imul(q,ut)|0,o=Math.imul(q,ct),r=r+Math.imul(B,lt)|0,i=(i=i+Math.imul(B,ht)|0)+Math.imul(L,lt)|0,o=o+Math.imul(L,ht)|0;var Rt=(c+(r=r+Math.imul(R,pt)|0)|0)+((8191&(i=(i=i+Math.imul(R,gt)|0)+Math.imul(I,pt)|0))<<13)|0;c=((o=o+Math.imul(I,gt)|0)+(i>>>13)|0)+(Rt>>>26)|0,Rt&=67108863,r=Math.imul(F,lt),i=(i=Math.imul(F,ht))+Math.imul(q,lt)|0,o=Math.imul(q,ht);var It=(c+(r=r+Math.imul(B,pt)|0)|0)+((8191&(i=(i=i+Math.imul(B,gt)|0)+Math.imul(L,pt)|0))<<13)|0;c=((o=o+Math.imul(L,gt)|0)+(i>>>13)|0)+(It>>>26)|0,It&=67108863;var Nt=(c+(r=Math.imul(F,pt))|0)+((8191&(i=(i=Math.imul(F,gt))+Math.imul(q,pt)|0))<<13)|0;return c=((o=Math.imul(q,gt))+(i>>>13)|0)+(Nt>>>26)|0,Nt&=67108863,u[0]=yt,u[1]=bt,u[2]=mt,u[3]=vt,u[4]=_t,u[5]=wt,u[6]=xt,u[7]=kt,u[8]=Et,u[9]=At,u[10]=St,u[11]=Tt,u[12]=Mt,u[13]=Dt,u[14]=Ct,u[15]=Ot,u[16]=Rt,u[17]=It,u[18]=Nt,0!==c&&(u[19]=c,n.length++),n};function p(t,e,n){return(new g).mulp(t,e,n)}function g(t,e){this.x=t,this.y=e}Math.imul||(d=h),o.prototype.mulTo=function(t,e){var n=this.length+t.length;return 10===this.length&&10===t.length?d(this,t,e):n<63?h(this,t,e):n<1024?function(t,e,n){n.negative=e.negative^t.negative,n.length=t.length+e.length;for(var r=0,i=0,o=0;o>>26)|0)>>>26,a&=67108863}n.words[o]=s,r=a,a=i}return 0!==r?n.words[o]=r:n.length--,n.strip()}(this,t,e):p(this,t,e)},g.prototype.makeRBT=function(t){for(var e=new Array(t),n=o.prototype._countBits(t)-1,r=0;r>=1;return r},g.prototype.permute=function(t,e,n,r,i,o){for(var a=0;a>>=1)i++;return 1<>>=13,n[2*a+1]=8191&o,o>>>=13;for(a=2*e;a>=26,e+=i/67108864|0,e+=o>>>26,this.words[n]=67108863&o}return 0!==e&&(this.words[n]=e,this.length++),this},o.prototype.muln=function(t){return this.clone().imuln(t)},o.prototype.sqr=function(){return this.mul(this)},o.prototype.isqr=function(){return this.imul(this.clone())},o.prototype.pow=function(t){var e=function(t){for(var e=new Array(t.bitLength()),n=0;n>>i}return e}(t);if(0===e.length)return new o(1);for(var n=this,r=0;r=0);var e,n=t%26,i=(t-n)/26,o=67108863>>>26-n<<26-n;if(0!==n){var a=0;for(e=0;e>>26-n}a&&(this.words[e]=a,this.length++)}if(0!==i){for(e=this.length-1;e>=0;e--)this.words[e+i]=this.words[e];for(e=0;e=0),i=e?(e-e%26)/26:0;var o=t%26,a=Math.min((t-o)/26,this.length),s=67108863^67108863>>>o<a)for(this.length-=a,c=0;c=0&&(0!==f||c>=i);c--){var l=0|this.words[c];this.words[c]=f<<26-o|l>>>o,f=l&s}return u&&0!==f&&(u.words[u.length++]=f),0===this.length&&(this.words[0]=0,this.length=1),this.strip()},o.prototype.ishrn=function(t,e,n){return r(0===this.negative),this.iushrn(t,e,n)},o.prototype.shln=function(t){return this.clone().ishln(t)},o.prototype.ushln=function(t){return this.clone().iushln(t)},o.prototype.shrn=function(t){return this.clone().ishrn(t)},o.prototype.ushrn=function(t){return this.clone().iushrn(t)},o.prototype.testn=function(t){r("number"==typeof t&&t>=0);var e=t%26,n=(t-e)/26,i=1<=0);var e=t%26,n=(t-e)/26;if(r(0===this.negative,"imaskn works only with positive numbers"),this.length<=n)return this;if(0!==e&&n++,this.length=Math.min(n,this.length),0!==e){var i=67108863^67108863>>>e<=67108864;e++)this.words[e]-=67108864,e===this.length-1?this.words[e+1]=1:this.words[e+1]++;return this.length=Math.max(this.length,e+1),this},o.prototype.isubn=function(t){if(r("number"==typeof t),r(t<67108864),t<0)return this.iaddn(-t);if(0!==this.negative)return this.negative=0,this.iaddn(t),this.negative=1,this;if(this.words[0]-=t,1===this.length&&this.words[0]<0)this.words[0]=-this.words[0],this.negative=1;else for(var e=0;e>26)-(u/67108864|0),this.words[i+n]=67108863&o}for(;i>26,this.words[i+n]=67108863&o;if(0===s)return this.strip();for(r(-1===s),s=0,i=0;i>26,this.words[i]=67108863&o;return this.negative=1,this.strip()},o.prototype._wordDiv=function(t,e){var n=(this.length,t.length),r=this.clone(),i=t,a=0|i.words[i.length-1];0!==(n=26-this._countBits(a))&&(i=i.ushln(n),r.iushln(n),a=0|i.words[i.length-1]);var s,u=r.length-i.length;if("mod"!==e){(s=new o(null)).length=u+1,s.words=new Array(s.length);for(var c=0;c=0;l--){var h=67108864*(0|r.words[i.length+l])+(0|r.words[i.length+l-1]);for(h=Math.min(h/a|0,67108863),r._ishlnsubmul(i,h,l);0!==r.negative;)h--,r.negative=0,r._ishlnsubmul(i,1,l),r.isZero()||(r.negative^=1);s&&(s.words[l]=h)}return s&&s.strip(),r.strip(),"div"!==e&&0!==n&&r.iushrn(n),{div:s||null,mod:r}},o.prototype.divmod=function(t,e,n){return r(!t.isZero()),this.isZero()?{div:new o(0),mod:new o(0)}:0!==this.negative&&0===t.negative?(s=this.neg().divmod(t,e),"mod"!==e&&(i=s.div.neg()),"div"!==e&&(a=s.mod.neg(),n&&0!==a.negative&&a.iadd(t)),{div:i,mod:a}):0===this.negative&&0!==t.negative?(s=this.divmod(t.neg(),e),"mod"!==e&&(i=s.div.neg()),{div:i,mod:s.mod}):0!=(this.negative&t.negative)?(s=this.neg().divmod(t.neg(),e),"div"!==e&&(a=s.mod.neg(),n&&0!==a.negative&&a.isub(t)),{div:s.div,mod:a}):t.length>this.length||this.cmp(t)<0?{div:new o(0),mod:this}:1===t.length?"div"===e?{div:this.divn(t.words[0]),mod:null}:"mod"===e?{div:null,mod:new o(this.modn(t.words[0]))}:{div:this.divn(t.words[0]),mod:new o(this.modn(t.words[0]))}:this._wordDiv(t,e);var i,a,s},o.prototype.div=function(t){return this.divmod(t,"div",!1).div},o.prototype.mod=function(t){return this.divmod(t,"mod",!1).mod},o.prototype.umod=function(t){return this.divmod(t,"mod",!0).mod},o.prototype.divRound=function(t){var e=this.divmod(t);if(e.mod.isZero())return e.div;var n=0!==e.div.negative?e.mod.isub(t):e.mod,r=t.ushrn(1),i=t.andln(1),o=n.cmp(r);return o<0||1===i&&0===o?e.div:0!==e.div.negative?e.div.isubn(1):e.div.iaddn(1)},o.prototype.modn=function(t){r(t<=67108863);for(var e=(1<<26)%t,n=0,i=this.length-1;i>=0;i--)n=(e*n+(0|this.words[i]))%t;return n},o.prototype.idivn=function(t){r(t<=67108863);for(var e=0,n=this.length-1;n>=0;n--){var i=(0|this.words[n])+67108864*e;this.words[n]=i/t|0,e=i%t}return this.strip()},o.prototype.divn=function(t){return this.clone().idivn(t)},o.prototype.egcd=function(t){r(0===t.negative),r(!t.isZero());var e=this,n=t.clone();e=0!==e.negative?e.umod(t):e.clone();for(var i=new o(1),a=new o(0),s=new o(0),u=new o(1),c=0;e.isEven()&&n.isEven();)e.iushrn(1),n.iushrn(1),++c;for(var f=n.clone(),l=e.clone();!e.isZero();){for(var h=0,d=1;0==(e.words[0]&d)&&h<26;++h,d<<=1);if(h>0)for(e.iushrn(h);h-- >0;)(i.isOdd()||a.isOdd())&&(i.iadd(f),a.isub(l)),i.iushrn(1),a.iushrn(1);for(var p=0,g=1;0==(n.words[0]&g)&&p<26;++p,g<<=1);if(p>0)for(n.iushrn(p);p-- >0;)(s.isOdd()||u.isOdd())&&(s.iadd(f),u.isub(l)),s.iushrn(1),u.iushrn(1);e.cmp(n)>=0?(e.isub(n),i.isub(s),a.isub(u)):(n.isub(e),s.isub(i),u.isub(a))}return{a:s,b:u,gcd:n.iushln(c)}},o.prototype._invmp=function(t){r(0===t.negative),r(!t.isZero());var e=this,n=t.clone();e=0!==e.negative?e.umod(t):e.clone();for(var i,a=new o(1),s=new o(0),u=n.clone();e.cmpn(1)>0&&n.cmpn(1)>0;){for(var c=0,f=1;0==(e.words[0]&f)&&c<26;++c,f<<=1);if(c>0)for(e.iushrn(c);c-- >0;)a.isOdd()&&a.iadd(u),a.iushrn(1);for(var l=0,h=1;0==(n.words[0]&h)&&l<26;++l,h<<=1);if(l>0)for(n.iushrn(l);l-- >0;)s.isOdd()&&s.iadd(u),s.iushrn(1);e.cmp(n)>=0?(e.isub(n),a.isub(s)):(n.isub(e),s.isub(a))}return(i=0===e.cmpn(1)?a:s).cmpn(0)<0&&i.iadd(t),i},o.prototype.gcd=function(t){if(this.isZero())return t.abs();if(t.isZero())return this.abs();var e=this.clone(),n=t.clone();e.negative=0,n.negative=0;for(var r=0;e.isEven()&&n.isEven();r++)e.iushrn(1),n.iushrn(1);for(;;){for(;e.isEven();)e.iushrn(1);for(;n.isEven();)n.iushrn(1);var i=e.cmp(n);if(i<0){var o=e;e=n,n=o}else if(0===i||0===n.cmpn(1))break;e.isub(n)}return n.iushln(r)},o.prototype.invm=function(t){return this.egcd(t).a.umod(t)},o.prototype.isEven=function(){return 0==(1&this.words[0])},o.prototype.isOdd=function(){return 1==(1&this.words[0])},o.prototype.andln=function(t){return this.words[0]&t},o.prototype.bincn=function(t){r("number"==typeof t);var e=t%26,n=(t-e)/26,i=1<>>26,s&=67108863,this.words[a]=s}return 0!==o&&(this.words[a]=o,this.length++),this},o.prototype.isZero=function(){return 1===this.length&&0===this.words[0]},o.prototype.cmpn=function(t){var e,n=t<0;if(0!==this.negative&&!n)return-1;if(0===this.negative&&n)return 1;if(this.strip(),this.length>1)e=1;else{n&&(t=-t),r(t<=67108863,"Number is too big");var i=0|this.words[0];e=i===t?0:it.length)return 1;if(this.length=0;n--){var r=0|this.words[n],i=0|t.words[n];if(r!==i){ri&&(e=1);break}}return e},o.prototype.gtn=function(t){return 1===this.cmpn(t)},o.prototype.gt=function(t){return 1===this.cmp(t)},o.prototype.gten=function(t){return this.cmpn(t)>=0},o.prototype.gte=function(t){return this.cmp(t)>=0},o.prototype.ltn=function(t){return-1===this.cmpn(t)},o.prototype.lt=function(t){return-1===this.cmp(t)},o.prototype.lten=function(t){return this.cmpn(t)<=0},o.prototype.lte=function(t){return this.cmp(t)<=0},o.prototype.eqn=function(t){return 0===this.cmpn(t)},o.prototype.eq=function(t){return 0===this.cmp(t)},o.red=function(t){return new x(t)},o.prototype.toRed=function(t){return r(!this.red,"Already a number in reduction context"),r(0===this.negative,"red works only with positives"),t.convertTo(this)._forceRed(t)},o.prototype.fromRed=function(){return r(this.red,"fromRed works only with numbers in reduction context"),this.red.convertFrom(this)},o.prototype._forceRed=function(t){return this.red=t,this},o.prototype.forceRed=function(t){return r(!this.red,"Already a number in reduction context"),this._forceRed(t)},o.prototype.redAdd=function(t){return r(this.red,"redAdd works only with red numbers"),this.red.add(this,t)},o.prototype.redIAdd=function(t){return r(this.red,"redIAdd works only with red numbers"),this.red.iadd(this,t)},o.prototype.redSub=function(t){return r(this.red,"redSub works only with red numbers"),this.red.sub(this,t)},o.prototype.redISub=function(t){return r(this.red,"redISub works only with red numbers"),this.red.isub(this,t)},o.prototype.redShl=function(t){return r(this.red,"redShl works only with red numbers"),this.red.shl(this,t)},o.prototype.redMul=function(t){return r(this.red,"redMul works only with red numbers"),this.red._verify2(this,t),this.red.mul(this,t)},o.prototype.redIMul=function(t){return r(this.red,"redMul works only with red numbers"),this.red._verify2(this,t),this.red.imul(this,t)},o.prototype.redSqr=function(){return r(this.red,"redSqr works only with red numbers"),this.red._verify1(this),this.red.sqr(this)},o.prototype.redISqr=function(){return r(this.red,"redISqr works only with red numbers"),this.red._verify1(this),this.red.isqr(this)},o.prototype.redSqrt=function(){return r(this.red,"redSqrt works only with red numbers"),this.red._verify1(this),this.red.sqrt(this)},o.prototype.redInvm=function(){return r(this.red,"redInvm works only with red numbers"),this.red._verify1(this),this.red.invm(this)},o.prototype.redNeg=function(){return r(this.red,"redNeg works only with red numbers"),this.red._verify1(this),this.red.neg(this)},o.prototype.redPow=function(t){return r(this.red&&!t.red,"redPow(normalNum)"),this.red._verify1(this),this.red.pow(this,t)};var y={k256:null,p224:null,p192:null,p25519:null};function b(t,e){this.name=t,this.p=new o(e,16),this.n=this.p.bitLength(),this.k=new o(1).iushln(this.n).isub(this.p),this.tmp=this._tmp()}function m(){b.call(this,"k256","ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff fffffffe fffffc2f")}function v(){b.call(this,"p224","ffffffff ffffffff ffffffff ffffffff 00000000 00000000 00000001")}function _(){b.call(this,"p192","ffffffff ffffffff ffffffff fffffffe ffffffff ffffffff")}function w(){b.call(this,"25519","7fffffffffffffff ffffffffffffffff ffffffffffffffff ffffffffffffffed")}function x(t){if("string"==typeof t){var e=o._prime(t);this.m=e.p,this.prime=e}else r(t.gtn(1),"modulus must be greater than 1"),this.m=t,this.prime=null}function k(t){x.call(this,t),this.shift=this.m.bitLength(),this.shift%26!=0&&(this.shift+=26-this.shift%26),this.r=new o(1).iushln(this.shift),this.r2=this.imod(this.r.sqr()),this.rinv=this.r._invmp(this.m),this.minv=this.rinv.mul(this.r).isubn(1).div(this.m),this.minv=this.minv.umod(this.r),this.minv=this.r.sub(this.minv)}b.prototype._tmp=function(){var t=new o(null);return t.words=new Array(Math.ceil(this.n/13)),t},b.prototype.ireduce=function(t){var e,n=t;do{this.split(n,this.tmp),e=(n=(n=this.imulK(n)).iadd(this.tmp)).bitLength()}while(e>this.n);var r=e0?n.isub(this.p):n.strip(),n},b.prototype.split=function(t,e){t.iushrn(this.n,0,e)},b.prototype.imulK=function(t){return t.imul(this.k)},i(m,b),m.prototype.split=function(t,e){for(var n=Math.min(t.length,9),r=0;r>>22,i=o}i>>>=22,t.words[r-10]=i,0===i&&t.length>10?t.length-=10:t.length-=9},m.prototype.imulK=function(t){t.words[t.length]=0,t.words[t.length+1]=0,t.length+=2;for(var e=0,n=0;n>>=26,t.words[n]=i,e=r}return 0!==e&&(t.words[t.length++]=e),t},o._prime=function(t){if(y[t])return y[t];var e;if("k256"===t)e=new m;else if("p224"===t)e=new v;else if("p192"===t)e=new _;else{if("p25519"!==t)throw new Error("Unknown prime "+t);e=new w}return y[t]=e,e},x.prototype._verify1=function(t){r(0===t.negative,"red works only with positives"),r(t.red,"red works only with red numbers")},x.prototype._verify2=function(t,e){r(0==(t.negative|e.negative),"red works only with positives"),r(t.red&&t.red===e.red,"red works only with red numbers")},x.prototype.imod=function(t){return this.prime?this.prime.ireduce(t)._forceRed(this):t.umod(this.m)._forceRed(this)},x.prototype.neg=function(t){return t.isZero()?t.clone():this.m.sub(t)._forceRed(this)},x.prototype.add=function(t,e){this._verify2(t,e);var n=t.add(e);return n.cmp(this.m)>=0&&n.isub(this.m),n._forceRed(this)},x.prototype.iadd=function(t,e){this._verify2(t,e);var n=t.iadd(e);return n.cmp(this.m)>=0&&n.isub(this.m),n},x.prototype.sub=function(t,e){this._verify2(t,e);var n=t.sub(e);return n.cmpn(0)<0&&n.iadd(this.m),n._forceRed(this)},x.prototype.isub=function(t,e){this._verify2(t,e);var n=t.isub(e);return n.cmpn(0)<0&&n.iadd(this.m),n},x.prototype.shl=function(t,e){return this._verify1(t),this.imod(t.ushln(e))},x.prototype.imul=function(t,e){return this._verify2(t,e),this.imod(t.imul(e))},x.prototype.mul=function(t,e){return this._verify2(t,e),this.imod(t.mul(e))},x.prototype.isqr=function(t){return this.imul(t,t.clone())},x.prototype.sqr=function(t){return this.mul(t,t)},x.prototype.sqrt=function(t){if(t.isZero())return t.clone();var e=this.m.andln(3);if(r(e%2==1),3===e){var n=this.m.add(new o(1)).iushrn(2);return this.pow(t,n)}for(var i=this.m.subn(1),a=0;!i.isZero()&&0===i.andln(1);)a++,i.iushrn(1);r(!i.isZero());var s=new o(1).toRed(this),u=s.redNeg(),c=this.m.subn(1).iushrn(1),f=this.m.bitLength();for(f=new o(2*f*f).toRed(this);0!==this.pow(f,c).cmp(u);)f.redIAdd(u);for(var l=this.pow(f,i),h=this.pow(t,i.addn(1).iushrn(1)),d=this.pow(t,i),p=a;0!==d.cmp(s);){for(var g=d,y=0;0!==g.cmp(s);y++)g=g.redSqr();r(y=0;r--){for(var c=e.words[r],f=u-1;f>=0;f--){var l=c>>f&1;i!==n[0]&&(i=this.sqr(i)),0!==l||0!==a?(a<<=1,a|=l,(4===++s||0===r&&0===f)&&(i=this.mul(i,n[a]),s=0,a=0)):s=0}u=26}return i},x.prototype.convertTo=function(t){var e=t.umod(this.m);return e===t?e.clone():e},x.prototype.convertFrom=function(t){var e=t.clone();return e.red=null,e},o.mont=function(t){return new k(t)},i(k,x),k.prototype.convertTo=function(t){return this.imod(t.ushln(this.shift))},k.prototype.convertFrom=function(t){var e=this.imod(t.mul(this.rinv));return e.red=null,e},k.prototype.imul=function(t,e){if(t.isZero()||e.isZero())return t.words[0]=0,t.length=1,t;var n=t.imul(e),r=n.maskn(this.shift).mul(this.minv).imaskn(this.shift).mul(this.m),i=n.isub(r).iushrn(this.shift),o=i;return i.cmp(this.m)>=0?o=i.isub(this.m):i.cmpn(0)<0&&(o=i.iadd(this.m)),o._forceRed(this)},k.prototype.mul=function(t,e){if(t.isZero()||e.isZero())return new o(0)._forceRed(this);var n=t.mul(e),r=n.maskn(this.shift).mul(this.minv).imaskn(this.shift).mul(this.m),i=n.isub(r).iushrn(this.shift),a=i;return i.cmp(this.m)>=0?a=i.isub(this.m):i.cmpn(0)<0&&(a=i.iadd(this.m)),a._forceRed(this)},k.prototype.invm=function(t){return this.imod(t._invmp(this.m).mul(this.r2))._forceRed(this)}}(t,this)}).call(this,n(9)(t))},function(t,e){var n=Array.isArray;t.exports=n},function(t,e){var n,r,i=t.exports={};function o(){throw new Error("setTimeout has not been defined")}function a(){throw new Error("clearTimeout has not been defined")}function s(t){if(n===setTimeout)return setTimeout(t,0);if((n===o||!n)&&setTimeout)return n=setTimeout,setTimeout(t,0);try{return n(t,0)}catch(e){try{return n.call(null,t,0)}catch(e){return n.call(this,t,0)}}}!function(){try{n="function"==typeof setTimeout?setTimeout:o}catch(t){n=o}try{r="function"==typeof clearTimeout?clearTimeout:a}catch(t){r=a}}();var u,c=[],f=!1,l=-1;function h(){f&&u&&(f=!1,u.length?c=u.concat(c):l=-1,c.length&&d())}function d(){if(!f){var t=s(h);f=!0;for(var e=c.length;e;){for(u=c,c=[];++l1)for(var n=1;n + * @license MIT + */ +var r=n(419),i=n(420),o=n(191);function a(){return u.TYPED_ARRAY_SUPPORT?2147483647:1073741823}function s(t,e){if(a()=a())throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+a().toString(16)+" bytes");return 0|t}function p(t,e){if(u.isBuffer(t))return t.length;if("undefined"!=typeof ArrayBuffer&&"function"==typeof ArrayBuffer.isView&&(ArrayBuffer.isView(t)||t instanceof ArrayBuffer))return t.byteLength;"string"!=typeof t&&(t=""+t);var n=t.length;if(0===n)return 0;for(var r=!1;;)switch(e){case"ascii":case"latin1":case"binary":return n;case"utf8":case"utf-8":case void 0:return U(t).length;case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return 2*n;case"hex":return n>>>1;case"base64":return z(t).length;default:if(r)return U(t).length;e=(""+e).toLowerCase(),r=!0}}function g(t,e,n){var r=!1;if((void 0===e||e<0)&&(e=0),e>this.length)return"";if((void 0===n||n>this.length)&&(n=this.length),n<=0)return"";if((n>>>=0)<=(e>>>=0))return"";for(t||(t="utf8");;)switch(t){case"hex":return C(this,e,n);case"utf8":case"utf-8":return S(this,e,n);case"ascii":return M(this,e,n);case"latin1":case"binary":return D(this,e,n);case"base64":return A(this,e,n);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return O(this,e,n);default:if(r)throw new TypeError("Unknown encoding: "+t);t=(t+"").toLowerCase(),r=!0}}function y(t,e,n){var r=t[e];t[e]=t[n],t[n]=r}function b(t,e,n,r,i){if(0===t.length)return-1;if("string"==typeof n?(r=n,n=0):n>2147483647?n=2147483647:n<-2147483648&&(n=-2147483648),n=+n,isNaN(n)&&(n=i?0:t.length-1),n<0&&(n=t.length+n),n>=t.length){if(i)return-1;n=t.length-1}else if(n<0){if(!i)return-1;n=0}if("string"==typeof e&&(e=u.from(e,r)),u.isBuffer(e))return 0===e.length?-1:m(t,e,n,r,i);if("number"==typeof e)return e&=255,u.TYPED_ARRAY_SUPPORT&&"function"==typeof Uint8Array.prototype.indexOf?i?Uint8Array.prototype.indexOf.call(t,e,n):Uint8Array.prototype.lastIndexOf.call(t,e,n):m(t,[e],n,r,i);throw new TypeError("val must be string, number or Buffer")}function m(t,e,n,r,i){var o,a=1,s=t.length,u=e.length;if(void 0!==r&&("ucs2"===(r=String(r).toLowerCase())||"ucs-2"===r||"utf16le"===r||"utf-16le"===r)){if(t.length<2||e.length<2)return-1;a=2,s/=2,u/=2,n/=2}function c(t,e){return 1===a?t[e]:t.readUInt16BE(e*a)}if(i){var f=-1;for(o=n;os&&(n=s-u),o=n;o>=0;o--){for(var l=!0,h=0;hi&&(r=i):r=i;var o=e.length;if(o%2!=0)throw new TypeError("Invalid hex string");r>o/2&&(r=o/2);for(var a=0;a>8,i=n%256,o.push(i),o.push(r);return o}(e,t.length-n),t,n,r)}function A(t,e,n){return 0===e&&n===t.length?r.fromByteArray(t):r.fromByteArray(t.slice(e,n))}function S(t,e,n){n=Math.min(t.length,n);for(var r=[],i=e;i239?4:c>223?3:c>191?2:1;if(i+l<=n)switch(l){case 1:c<128&&(f=c);break;case 2:128==(192&(o=t[i+1]))&&(u=(31&c)<<6|63&o)>127&&(f=u);break;case 3:o=t[i+1],a=t[i+2],128==(192&o)&&128==(192&a)&&(u=(15&c)<<12|(63&o)<<6|63&a)>2047&&(u<55296||u>57343)&&(f=u);break;case 4:o=t[i+1],a=t[i+2],s=t[i+3],128==(192&o)&&128==(192&a)&&128==(192&s)&&(u=(15&c)<<18|(63&o)<<12|(63&a)<<6|63&s)>65535&&u<1114112&&(f=u)}null===f?(f=65533,l=1):f>65535&&(f-=65536,r.push(f>>>10&1023|55296),f=56320|1023&f),r.push(f),i+=l}return function(t){var e=t.length;if(e<=T)return String.fromCharCode.apply(String,t);var n="",r=0;for(;r0&&(t=this.toString("hex",0,n).match(/.{2}/g).join(" "),this.length>n&&(t+=" ... ")),""},u.prototype.compare=function(t,e,n,r,i){if(!u.isBuffer(t))throw new TypeError("Argument must be a Buffer");if(void 0===e&&(e=0),void 0===n&&(n=t?t.length:0),void 0===r&&(r=0),void 0===i&&(i=this.length),e<0||n>t.length||r<0||i>this.length)throw new RangeError("out of range index");if(r>=i&&e>=n)return 0;if(r>=i)return-1;if(e>=n)return 1;if(this===t)return 0;for(var o=(i>>>=0)-(r>>>=0),a=(n>>>=0)-(e>>>=0),s=Math.min(o,a),c=this.slice(r,i),f=t.slice(e,n),l=0;li)&&(n=i),t.length>0&&(n<0||e<0)||e>this.length)throw new RangeError("Attempt to write outside buffer bounds");r||(r="utf8");for(var o=!1;;)switch(r){case"hex":return v(this,t,e,n);case"utf8":case"utf-8":return _(this,t,e,n);case"ascii":return w(this,t,e,n);case"latin1":case"binary":return x(this,t,e,n);case"base64":return k(this,t,e,n);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return E(this,t,e,n);default:if(o)throw new TypeError("Unknown encoding: "+r);r=(""+r).toLowerCase(),o=!0}},u.prototype.toJSON=function(){return{type:"Buffer",data:Array.prototype.slice.call(this._arr||this,0)}};var T=4096;function M(t,e,n){var r="";n=Math.min(t.length,n);for(var i=e;ir)&&(n=r);for(var i="",o=e;on)throw new RangeError("Trying to access beyond buffer length")}function I(t,e,n,r,i,o){if(!u.isBuffer(t))throw new TypeError('"buffer" argument must be a Buffer instance');if(e>i||et.length)throw new RangeError("Index out of range")}function N(t,e,n,r){e<0&&(e=65535+e+1);for(var i=0,o=Math.min(t.length-n,2);i>>8*(r?i:1-i)}function B(t,e,n,r){e<0&&(e=4294967295+e+1);for(var i=0,o=Math.min(t.length-n,4);i>>8*(r?i:3-i)&255}function L(t,e,n,r,i,o){if(n+r>t.length)throw new RangeError("Index out of range");if(n<0)throw new RangeError("Index out of range")}function P(t,e,n,r,o){return o||L(t,0,n,4),i.write(t,e,n,r,23,4),n+4}function F(t,e,n,r,o){return o||L(t,0,n,8),i.write(t,e,n,r,52,8),n+8}u.prototype.slice=function(t,e){var n,r=this.length;if((t=~~t)<0?(t+=r)<0&&(t=0):t>r&&(t=r),(e=void 0===e?r:~~e)<0?(e+=r)<0&&(e=0):e>r&&(e=r),e0&&(i*=256);)r+=this[t+--e]*i;return r},u.prototype.readUInt8=function(t,e){return e||R(t,1,this.length),this[t]},u.prototype.readUInt16LE=function(t,e){return e||R(t,2,this.length),this[t]|this[t+1]<<8},u.prototype.readUInt16BE=function(t,e){return e||R(t,2,this.length),this[t]<<8|this[t+1]},u.prototype.readUInt32LE=function(t,e){return e||R(t,4,this.length),(this[t]|this[t+1]<<8|this[t+2]<<16)+16777216*this[t+3]},u.prototype.readUInt32BE=function(t,e){return e||R(t,4,this.length),16777216*this[t]+(this[t+1]<<16|this[t+2]<<8|this[t+3])},u.prototype.readIntLE=function(t,e,n){t|=0,e|=0,n||R(t,e,this.length);for(var r=this[t],i=1,o=0;++o=(i*=128)&&(r-=Math.pow(2,8*e)),r},u.prototype.readIntBE=function(t,e,n){t|=0,e|=0,n||R(t,e,this.length);for(var r=e,i=1,o=this[t+--r];r>0&&(i*=256);)o+=this[t+--r]*i;return o>=(i*=128)&&(o-=Math.pow(2,8*e)),o},u.prototype.readInt8=function(t,e){return e||R(t,1,this.length),128&this[t]?-1*(255-this[t]+1):this[t]},u.prototype.readInt16LE=function(t,e){e||R(t,2,this.length);var n=this[t]|this[t+1]<<8;return 32768&n?4294901760|n:n},u.prototype.readInt16BE=function(t,e){e||R(t,2,this.length);var n=this[t+1]|this[t]<<8;return 32768&n?4294901760|n:n},u.prototype.readInt32LE=function(t,e){return e||R(t,4,this.length),this[t]|this[t+1]<<8|this[t+2]<<16|this[t+3]<<24},u.prototype.readInt32BE=function(t,e){return e||R(t,4,this.length),this[t]<<24|this[t+1]<<16|this[t+2]<<8|this[t+3]},u.prototype.readFloatLE=function(t,e){return e||R(t,4,this.length),i.read(this,t,!0,23,4)},u.prototype.readFloatBE=function(t,e){return e||R(t,4,this.length),i.read(this,t,!1,23,4)},u.prototype.readDoubleLE=function(t,e){return e||R(t,8,this.length),i.read(this,t,!0,52,8)},u.prototype.readDoubleBE=function(t,e){return e||R(t,8,this.length),i.read(this,t,!1,52,8)},u.prototype.writeUIntLE=function(t,e,n,r){(t=+t,e|=0,n|=0,r)||I(this,t,e,n,Math.pow(2,8*n)-1,0);var i=1,o=0;for(this[e]=255&t;++o=0&&(o*=256);)this[e+i]=t/o&255;return e+n},u.prototype.writeUInt8=function(t,e,n){return t=+t,e|=0,n||I(this,t,e,1,255,0),u.TYPED_ARRAY_SUPPORT||(t=Math.floor(t)),this[e]=255&t,e+1},u.prototype.writeUInt16LE=function(t,e,n){return t=+t,e|=0,n||I(this,t,e,2,65535,0),u.TYPED_ARRAY_SUPPORT?(this[e]=255&t,this[e+1]=t>>>8):N(this,t,e,!0),e+2},u.prototype.writeUInt16BE=function(t,e,n){return t=+t,e|=0,n||I(this,t,e,2,65535,0),u.TYPED_ARRAY_SUPPORT?(this[e]=t>>>8,this[e+1]=255&t):N(this,t,e,!1),e+2},u.prototype.writeUInt32LE=function(t,e,n){return t=+t,e|=0,n||I(this,t,e,4,4294967295,0),u.TYPED_ARRAY_SUPPORT?(this[e+3]=t>>>24,this[e+2]=t>>>16,this[e+1]=t>>>8,this[e]=255&t):B(this,t,e,!0),e+4},u.prototype.writeUInt32BE=function(t,e,n){return t=+t,e|=0,n||I(this,t,e,4,4294967295,0),u.TYPED_ARRAY_SUPPORT?(this[e]=t>>>24,this[e+1]=t>>>16,this[e+2]=t>>>8,this[e+3]=255&t):B(this,t,e,!1),e+4},u.prototype.writeIntLE=function(t,e,n,r){if(t=+t,e|=0,!r){var i=Math.pow(2,8*n-1);I(this,t,e,n,i-1,-i)}var o=0,a=1,s=0;for(this[e]=255&t;++o>0)-s&255;return e+n},u.prototype.writeIntBE=function(t,e,n,r){if(t=+t,e|=0,!r){var i=Math.pow(2,8*n-1);I(this,t,e,n,i-1,-i)}var o=n-1,a=1,s=0;for(this[e+o]=255&t;--o>=0&&(a*=256);)t<0&&0===s&&0!==this[e+o+1]&&(s=1),this[e+o]=(t/a>>0)-s&255;return e+n},u.prototype.writeInt8=function(t,e,n){return t=+t,e|=0,n||I(this,t,e,1,127,-128),u.TYPED_ARRAY_SUPPORT||(t=Math.floor(t)),t<0&&(t=255+t+1),this[e]=255&t,e+1},u.prototype.writeInt16LE=function(t,e,n){return t=+t,e|=0,n||I(this,t,e,2,32767,-32768),u.TYPED_ARRAY_SUPPORT?(this[e]=255&t,this[e+1]=t>>>8):N(this,t,e,!0),e+2},u.prototype.writeInt16BE=function(t,e,n){return t=+t,e|=0,n||I(this,t,e,2,32767,-32768),u.TYPED_ARRAY_SUPPORT?(this[e]=t>>>8,this[e+1]=255&t):N(this,t,e,!1),e+2},u.prototype.writeInt32LE=function(t,e,n){return t=+t,e|=0,n||I(this,t,e,4,2147483647,-2147483648),u.TYPED_ARRAY_SUPPORT?(this[e]=255&t,this[e+1]=t>>>8,this[e+2]=t>>>16,this[e+3]=t>>>24):B(this,t,e,!0),e+4},u.prototype.writeInt32BE=function(t,e,n){return t=+t,e|=0,n||I(this,t,e,4,2147483647,-2147483648),t<0&&(t=4294967295+t+1),u.TYPED_ARRAY_SUPPORT?(this[e]=t>>>24,this[e+1]=t>>>16,this[e+2]=t>>>8,this[e+3]=255&t):B(this,t,e,!1),e+4},u.prototype.writeFloatLE=function(t,e,n){return P(this,t,e,!0,n)},u.prototype.writeFloatBE=function(t,e,n){return P(this,t,e,!1,n)},u.prototype.writeDoubleLE=function(t,e,n){return F(this,t,e,!0,n)},u.prototype.writeDoubleBE=function(t,e,n){return F(this,t,e,!1,n)},u.prototype.copy=function(t,e,n,r){if(n||(n=0),r||0===r||(r=this.length),e>=t.length&&(e=t.length),e||(e=0),r>0&&r=this.length)throw new RangeError("sourceStart out of bounds");if(r<0)throw new RangeError("sourceEnd out of bounds");r>this.length&&(r=this.length),t.length-e=0;--i)t[i+e]=this[i+n];else if(o<1e3||!u.TYPED_ARRAY_SUPPORT)for(i=0;i>>=0,n=void 0===n?this.length:n>>>0,t||(t=0),"number"==typeof t)for(o=e;o55295&&n<57344){if(!i){if(n>56319){(e-=3)>-1&&o.push(239,191,189);continue}if(a+1===r){(e-=3)>-1&&o.push(239,191,189);continue}i=n;continue}if(n<56320){(e-=3)>-1&&o.push(239,191,189),i=n;continue}n=65536+(i-55296<<10|n-56320)}else i&&(e-=3)>-1&&o.push(239,191,189);if(i=null,n<128){if((e-=1)<0)break;o.push(n)}else if(n<2048){if((e-=2)<0)break;o.push(n>>6|192,63&n|128)}else if(n<65536){if((e-=3)<0)break;o.push(n>>12|224,n>>6&63|128,63&n|128)}else{if(!(n<1114112))throw new Error("Invalid code point");if((e-=4)<0)break;o.push(n>>18|240,n>>12&63|128,n>>6&63|128,63&n|128)}}return o}function z(t){return r.toByteArray(function(t){if((t=function(t){return t.trim?t.trim():t.replace(/^\s+|\s+$/g,"")}(t).replace(q,"")).length<2)return"";for(;t.length%4!=0;)t+="=";return t}(t))}function Y(t,e,n,r){for(var i=0;i=e.length||i>=t.length);++i)e[i+n]=t[i];return i}}).call(this,n(11))},function(t,e){t.exports=function(t){return t.webpackPolyfill||(t.deprecate=function(){},t.paths=[],t.children||(t.children=[]),Object.defineProperty(t,"loaded",{enumerable:!0,get:function(){return t.l}}),Object.defineProperty(t,"id",{enumerable:!0,get:function(){return t.i}}),t.webpackPolyfill=1),t}},function(t,e,n){"use strict";var r=n(4),i=n(19).Graph;function o(t,e,n,i){var o;do{o=r.uniqueId(i)}while(t.hasNode(o));return n.dummy=e,t.setNode(o,n),o}function a(t){return r.max(r.map(t.nodes(),(function(e){var n=t.node(e).rank;if(!r.isUndefined(n))return n})))}t.exports={addDummyNode:o,simplify:function(t){var e=(new i).setGraph(t.graph());return r.forEach(t.nodes(),(function(n){e.setNode(n,t.node(n))})),r.forEach(t.edges(),(function(n){var r=e.edge(n.v,n.w)||{weight:0,minlen:1},i=t.edge(n);e.setEdge(n.v,n.w,{weight:r.weight+i.weight,minlen:Math.max(r.minlen,i.minlen)})})),e},asNonCompoundGraph:function(t){var e=new i({multigraph:t.isMultigraph()}).setGraph(t.graph());return r.forEach(t.nodes(),(function(n){t.children(n).length||e.setNode(n,t.node(n))})),r.forEach(t.edges(),(function(n){e.setEdge(n,t.edge(n))})),e},successorWeights:function(t){var e=r.map(t.nodes(),(function(e){var n={};return r.forEach(t.outEdges(e),(function(e){n[e.w]=(n[e.w]||0)+t.edge(e).weight})),n}));return r.zipObject(t.nodes(),e)},predecessorWeights:function(t){var e=r.map(t.nodes(),(function(e){var n={};return r.forEach(t.inEdges(e),(function(e){n[e.v]=(n[e.v]||0)+t.edge(e).weight})),n}));return r.zipObject(t.nodes(),e)},intersectRect:function(t,e){var n,r,i=t.x,o=t.y,a=e.x-i,s=e.y-o,u=t.width/2,c=t.height/2;if(!a&&!s)throw new Error("Not possible to find intersection inside of the rectangle");Math.abs(s)*u>Math.abs(a)*c?(s<0&&(c=-c),n=c*a/s,r=c):(a<0&&(u=-u),n=u,r=u*s/a);return{x:i+n,y:o+r}},buildLayerMatrix:function(t){var e=r.map(r.range(a(t)+1),(function(){return[]}));return r.forEach(t.nodes(),(function(n){var i=t.node(n),o=i.rank;r.isUndefined(o)||(e[o][i.order]=n)})),e},normalizeRanks:function(t){var e=r.min(r.map(t.nodes(),(function(e){return t.node(e).rank})));r.forEach(t.nodes(),(function(n){var i=t.node(n);r.has(i,"rank")&&(i.rank-=e)}))},removeEmptyRanks:function(t){var e=r.min(r.map(t.nodes(),(function(e){return t.node(e).rank}))),n=[];r.forEach(t.nodes(),(function(r){var i=t.node(r).rank-e;n[i]||(n[i]=[]),n[i].push(r)}));var i=0,o=t.graph().nodeRankFactor;r.forEach(n,(function(e,n){r.isUndefined(e)&&n%o!=0?--i:i&&r.forEach(e,(function(e){t.node(e).rank+=i}))}))},addBorderNode:function(t,e,n,r){var i={width:0,height:0};arguments.length>=4&&(i.rank=n,i.order=r);return o(t,"border",i,e)},maxRank:a,partition:function(t,e){var n={lhs:[],rhs:[]};return r.forEach(t,(function(t){e(t)?n.lhs.push(t):n.rhs.push(t)})),n},time:function(t,e){var n=r.now();try{return e()}finally{console.log(t+" time: "+(r.now()-n)+"ms")}},notime:function(t,e){return e()}}},function(t,e){var n;n=function(){return this}();try{n=n||new Function("return this")()}catch(t){"object"==typeof window&&(n=window)}t.exports=n},function(t,e,n){var r;try{r={clone:n(235),constant:n(99),each:n(100),filter:n(150),has:n(106),isArray:n(6),isEmpty:n(311),isFunction:n(37),isUndefined:n(161),keys:n(27),map:n(162),reduce:n(164),size:n(314),transform:n(320),union:n(321),values:n(169)}}catch(t){}r||(r=window._),t.exports=r},function(t,e){t.exports=function(t){var e=typeof t;return null!=t&&("object"==e||"function"==e)}},function(t,e,n){var r=n(43);t.exports={isSubgraph:function(t,e){return!!t.children(e).length},edgeToId:function(t){return o(t.v)+":"+o(t.w)+":"+o(t.name)},applyStyle:function(t,e){e&&t.attr("style",e)},applyClass:function(t,e,n){e&&t.attr("class",e).attr("class",n+" "+t.attr("class"))},applyTransition:function(t,e){var n=e.graph();if(r.isPlainObject(n)){var i=n.transition;if(r.isFunction(i))return i(t)}return t}};var i=/:/g;function o(t){return t?String(t).replace(i,"\\:"):""}},function(t,e){function n(t,e){if(!t)throw new Error(e||"Assertion failed")}t.exports=n,n.equal=function(t,e,n){if(t!=e)throw new Error(n||"Assertion failed: "+t+" != "+e)}},function(t,e,n){"use strict";var r=e,i=n(5),o=n(15),a=n(213);r.assert=o,r.toArray=a.toArray,r.zero2=a.zero2,r.toHex=a.toHex,r.encode=a.encode,r.getNAF=function(t,e){for(var n=[],r=1<=0;){var o;if(i.isOdd()){var a=i.andln(r-1);o=a>(r>>1)-1?(r>>1)-a:a,i.isubn(o)}else o=0;n.push(o);for(var s=0!==i.cmpn(0)&&0===i.andln(r-1)?e+1:1,u=1;u0||e.cmpn(-i)>0;){var o,a,s,u=t.andln(3)+r&3,c=e.andln(3)+i&3;if(3===u&&(u=-1),3===c&&(c=-1),0==(1&u))o=0;else o=3!==(s=t.andln(7)+r&7)&&5!==s||2!==c?u:-u;if(n[0].push(o),0==(1&c))a=0;else a=3!==(s=e.andln(7)+i&7)&&5!==s||2!==u?c:-c;n[1].push(a),2*r===o+1&&(r=1-r),2*i===a+1&&(i=1-i),t.iushrn(1),e.iushrn(1)}return n},r.cachedProperty=function(t,e,n){var r="_"+e;t.prototype[e]=function(){return void 0!==this[r]?this[r]:this[r]=n.call(this)}},r.parseBytes=function(t){return"string"==typeof t?r.toArray(t,"hex"):t},r.intFromLE=function(t){return new i(t,"hex","le")}},function(t,e,n){ +/** + * @license + * Copyright (c) 2012-2013 Chris Pettitt + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +t.exports={graphlib:n(346),dagre:n(175),intersect:n(403),render:n(405),util:n(14),version:n(417)}},function(t,e,n){var r=n(131),i="object"==typeof self&&self&&self.Object===Object&&self,o=r||i||Function("return this")();t.exports=o},function(t,e,n){var r;try{r=n(22)}catch(t){}r||(r=window.graphlib),t.exports=r},function(t,e){t.exports=function(t){return null!=t&&"object"==typeof t}},function(t,e,n){"use strict";var r=n(15),i=n(2);function o(t,e){return 55296==(64512&t.charCodeAt(e))&&(!(e<0||e+1>=t.length)&&56320==(64512&t.charCodeAt(e+1)))}function a(t){return(t>>>24|t>>>8&65280|t<<8&16711680|(255&t)<<24)>>>0}function s(t){return 1===t.length?"0"+t:t}function u(t){return 7===t.length?"0"+t:6===t.length?"00"+t:5===t.length?"000"+t:4===t.length?"0000"+t:3===t.length?"00000"+t:2===t.length?"000000"+t:1===t.length?"0000000"+t:t}e.inherits=i,e.toArray=function(t,e){if(Array.isArray(t))return t.slice();if(!t)return[];var n=[];if("string"==typeof t)if(e){if("hex"===e)for((t=t.replace(/[^a-z0-9]+/gi,"")).length%2!=0&&(t="0"+t),i=0;i>6|192,n[r++]=63&a|128):o(t,i)?(a=65536+((1023&a)<<10)+(1023&t.charCodeAt(++i)),n[r++]=a>>18|240,n[r++]=a>>12&63|128,n[r++]=a>>6&63|128,n[r++]=63&a|128):(n[r++]=a>>12|224,n[r++]=a>>6&63|128,n[r++]=63&a|128)}else for(i=0;i>>0}return a},e.split32=function(t,e){for(var n=new Array(4*t.length),r=0,i=0;r>>24,n[i+1]=o>>>16&255,n[i+2]=o>>>8&255,n[i+3]=255&o):(n[i+3]=o>>>24,n[i+2]=o>>>16&255,n[i+1]=o>>>8&255,n[i]=255&o)}return n},e.rotr32=function(t,e){return t>>>e|t<<32-e},e.rotl32=function(t,e){return t<>>32-e},e.sum32=function(t,e){return t+e>>>0},e.sum32_3=function(t,e,n){return t+e+n>>>0},e.sum32_4=function(t,e,n,r){return t+e+n+r>>>0},e.sum32_5=function(t,e,n,r,i){return t+e+n+r+i>>>0},e.sum64=function(t,e,n,r){var i=t[e],o=r+t[e+1]>>>0,a=(o>>0,t[e+1]=o},e.sum64_hi=function(t,e,n,r){return(e+r>>>0>>0},e.sum64_lo=function(t,e,n,r){return e+r>>>0},e.sum64_4_hi=function(t,e,n,r,i,o,a,s){var u=0,c=e;return u+=(c=c+r>>>0)>>0)>>0)>>0},e.sum64_4_lo=function(t,e,n,r,i,o,a,s){return e+r+o+s>>>0},e.sum64_5_hi=function(t,e,n,r,i,o,a,s,u,c){var f=0,l=e;return f+=(l=l+r>>>0)>>0)>>0)>>0)>>0},e.sum64_5_lo=function(t,e,n,r,i,o,a,s,u,c){return e+r+o+s+c>>>0},e.rotr64_hi=function(t,e,n){return(e<<32-n|t>>>n)>>>0},e.rotr64_lo=function(t,e,n){return(t<<32-n|e>>>n)>>>0},e.shr64_hi=function(t,e,n){return t>>>n},e.shr64_lo=function(t,e,n){return(t<<32-n|e>>>n)>>>0}},function(t,e,n){var r=n(234);t.exports={Graph:r.Graph,json:n(336),alg:n(337),version:r.version}},function(t,e,n){(function(t){t.exports=function(){"use strict";var e,r;function i(){return e.apply(null,arguments)}function o(t){return t instanceof Array||"[object Array]"===Object.prototype.toString.call(t)}function a(t){return null!=t&&"[object Object]"===Object.prototype.toString.call(t)}function s(t){return void 0===t}function u(t){return"number"==typeof t||"[object Number]"===Object.prototype.toString.call(t)}function c(t){return t instanceof Date||"[object Date]"===Object.prototype.toString.call(t)}function f(t,e){var n,r=[];for(n=0;n>>0,r=0;rAt(t)?(o=t+1,a=s-At(t)):(o=t,a=s),{year:o,dayOfYear:a}}function Vt(t,e,n){var r,i,o=zt(t.year(),e,n),a=Math.floor((t.dayOfYear()-o-1)/7)+1;return a<1?r=a+Ht(i=t.year()-1,e,n):a>Ht(t.year(),e,n)?(r=a-Ht(t.year(),e,n),i=t.year()+1):(i=t.year(),r=a),{week:r,year:i}}function Ht(t,e,n){var r=zt(t,e,n),i=zt(t+1,e,n);return(At(t)-r+i)/7}V("w",["ww",2],"wo","week"),V("W",["WW",2],"Wo","isoWeek"),N("week","w"),N("isoWeek","W"),F("week",5),F("isoWeek",5),ft("w",J),ft("ww",J,W),ft("W",J),ft("WW",J,W),gt(["w","ww","W","WW"],(function(t,e,n,r){e[r.substr(0,1)]=k(t)})),V("d",0,"do","day"),V("dd",0,0,(function(t){return this.localeData().weekdaysMin(this,t)})),V("ddd",0,0,(function(t){return this.localeData().weekdaysShort(this,t)})),V("dddd",0,0,(function(t){return this.localeData().weekdays(this,t)})),V("e",0,0,"weekday"),V("E",0,0,"isoWeekday"),N("day","d"),N("weekday","e"),N("isoWeekday","E"),F("day",11),F("weekday",11),F("isoWeekday",11),ft("d",J),ft("e",J),ft("E",J),ft("dd",(function(t,e){return e.weekdaysMinRegex(t)})),ft("ddd",(function(t,e){return e.weekdaysShortRegex(t)})),ft("dddd",(function(t,e){return e.weekdaysRegex(t)})),gt(["dd","ddd","dddd"],(function(t,e,n,r){var i=n._locale.weekdaysParse(t,r,n._strict);null!=i?e.d=i:p(n).invalidWeekday=t})),gt(["d","e","E"],(function(t,e,n,r){e[r]=k(t)}));var $t="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),Gt="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),Wt="Su_Mo_Tu_We_Th_Fr_Sa".split("_"),Kt=ut,Xt=ut,Zt=ut;function Jt(){function t(t,e){return e.length-t.length}var e,n,r,i,o,a=[],s=[],u=[],c=[];for(e=0;e<7;e++)n=d([2e3,1]).day(e),r=this.weekdaysMin(n,""),i=this.weekdaysShort(n,""),o=this.weekdays(n,""),a.push(r),s.push(i),u.push(o),c.push(r),c.push(i),c.push(o);for(a.sort(t),s.sort(t),u.sort(t),c.sort(t),e=0;e<7;e++)s[e]=ht(s[e]),u[e]=ht(u[e]),c[e]=ht(c[e]);this._weekdaysRegex=new RegExp("^("+c.join("|")+")","i"),this._weekdaysShortRegex=this._weekdaysRegex,this._weekdaysMinRegex=this._weekdaysRegex,this._weekdaysStrictRegex=new RegExp("^("+u.join("|")+")","i"),this._weekdaysShortStrictRegex=new RegExp("^("+s.join("|")+")","i"),this._weekdaysMinStrictRegex=new RegExp("^("+a.join("|")+")","i")}function Qt(){return this.hours()%12||12}function te(t,e){V(t,0,0,(function(){return this.localeData().meridiem(this.hours(),this.minutes(),e)}))}function ee(t,e){return e._meridiemParse}V("H",["HH",2],0,"hour"),V("h",["hh",2],0,Qt),V("k",["kk",2],0,(function(){return this.hours()||24})),V("hmm",0,0,(function(){return""+Qt.apply(this)+q(this.minutes(),2)})),V("hmmss",0,0,(function(){return""+Qt.apply(this)+q(this.minutes(),2)+q(this.seconds(),2)})),V("Hmm",0,0,(function(){return""+this.hours()+q(this.minutes(),2)})),V("Hmmss",0,0,(function(){return""+this.hours()+q(this.minutes(),2)+q(this.seconds(),2)})),te("a",!0),te("A",!1),N("hour","h"),F("hour",13),ft("a",ee),ft("A",ee),ft("H",J),ft("h",J),ft("k",J),ft("HH",J,W),ft("hh",J,W),ft("kk",J,W),ft("hmm",Q),ft("hmmss",tt),ft("Hmm",Q),ft("Hmmss",tt),pt(["H","HH"],vt),pt(["k","kk"],(function(t,e,n){var r=k(t);e[vt]=24===r?0:r})),pt(["a","A"],(function(t,e,n){n._isPm=n._locale.isPM(t),n._meridiem=t})),pt(["h","hh"],(function(t,e,n){e[vt]=k(t),p(n).bigHour=!0})),pt("hmm",(function(t,e,n){var r=t.length-2;e[vt]=k(t.substr(0,r)),e[_t]=k(t.substr(r)),p(n).bigHour=!0})),pt("hmmss",(function(t,e,n){var r=t.length-4,i=t.length-2;e[vt]=k(t.substr(0,r)),e[_t]=k(t.substr(r,2)),e[wt]=k(t.substr(i)),p(n).bigHour=!0})),pt("Hmm",(function(t,e,n){var r=t.length-2;e[vt]=k(t.substr(0,r)),e[_t]=k(t.substr(r))})),pt("Hmmss",(function(t,e,n){var r=t.length-4,i=t.length-2;e[vt]=k(t.substr(0,r)),e[_t]=k(t.substr(r,2)),e[wt]=k(t.substr(i))}));var ne,re=Dt("Hours",!0),ie={calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},longDateFormat:{LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},invalidDate:"Invalid date",ordinal:"%d",dayOfMonthOrdinalParse:/\d{1,2}/,relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},months:Nt,monthsShort:Bt,week:{dow:0,doy:6},weekdays:$t,weekdaysMin:Wt,weekdaysShort:Gt,meridiemParse:/[ap]\.?m?\.?/i},oe={},ae={};function se(t){return t?t.toLowerCase().replace("_","-"):t}function ue(e){var r=null;if(!oe[e]&&void 0!==t&&t&&t.exports)try{r=ne._abbr,n(233)("./"+e),ce(r)}catch(e){}return oe[e]}function ce(t,e){var n;return t&&((n=s(e)?le(t):fe(t,e))?ne=n:"undefined"!=typeof console&&console.warn&&console.warn("Locale "+t+" not found. Did you forget to load it?")),ne._abbr}function fe(t,e){if(null!==e){var n,r=ie;if(e.abbr=t,null!=oe[t])D("defineLocaleOverride","use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale See http://momentjs.com/guides/#/warnings/define-locale/ for more info."),r=oe[t]._config;else if(null!=e.parentLocale)if(null!=oe[e.parentLocale])r=oe[e.parentLocale]._config;else{if(null==(n=ue(e.parentLocale)))return ae[e.parentLocale]||(ae[e.parentLocale]=[]),ae[e.parentLocale].push({name:t,config:e}),null;r=n._config}return oe[t]=new R(O(r,e)),ae[t]&&ae[t].forEach((function(t){fe(t.name,t.config)})),ce(t),oe[t]}return delete oe[t],null}function le(t){var e;if(t&&t._locale&&t._locale._abbr&&(t=t._locale._abbr),!t)return ne;if(!o(t)){if(e=ue(t))return e;t=[t]}return function(t){for(var e,n,r,i,o=0;o=e&&E(i,n,!0)>=e-1)break;e--}o++}return ne}(t)}function he(t){var e,n=t._a;return n&&-2===p(t).overflow&&(e=n[bt]<0||11Rt(n[yt],n[bt])?mt:n[vt]<0||24Ht(n,o,a)?p(t)._overflowWeeks=!0:null!=u?p(t)._overflowWeekday=!0:(s=Yt(n,r,i,o,a),t._a[yt]=s.year,t._dayOfYear=s.dayOfYear)}(t),null!=t._dayOfYear&&(a=de(t._a[yt],r[yt]),(t._dayOfYear>At(a)||0===t._dayOfYear)&&(p(t)._overflowDayOfYear=!0),n=Ut(a,0,t._dayOfYear),t._a[bt]=n.getUTCMonth(),t._a[mt]=n.getUTCDate()),e=0;e<3&&null==t._a[e];++e)t._a[e]=s[e]=r[e];for(;e<7;e++)t._a[e]=s[e]=null==t._a[e]?2===e?1:0:t._a[e];24===t._a[vt]&&0===t._a[_t]&&0===t._a[wt]&&0===t._a[xt]&&(t._nextDay=!0,t._a[vt]=0),t._d=(t._useUTC?Ut:function(t,e,n,r,i,o,a){var s=new Date(t,e,n,r,i,o,a);return t<100&&0<=t&&isFinite(s.getFullYear())&&s.setFullYear(t),s}).apply(null,s),o=t._useUTC?t._d.getUTCDay():t._d.getDay(),null!=t._tzm&&t._d.setUTCMinutes(t._d.getUTCMinutes()-t._tzm),t._nextDay&&(t._a[vt]=24),t._w&&void 0!==t._w.d&&t._w.d!==o&&(p(t).weekdayMismatch=!0)}}var ge=/^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,ye=/^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,be=/Z|[+-]\d\d(?::?\d\d)?/,me=[["YYYYYY-MM-DD",/[+-]\d{6}-\d\d-\d\d/],["YYYY-MM-DD",/\d{4}-\d\d-\d\d/],["GGGG-[W]WW-E",/\d{4}-W\d\d-\d/],["GGGG-[W]WW",/\d{4}-W\d\d/,!1],["YYYY-DDD",/\d{4}-\d{3}/],["YYYY-MM",/\d{4}-\d\d/,!1],["YYYYYYMMDD",/[+-]\d{10}/],["YYYYMMDD",/\d{8}/],["GGGG[W]WWE",/\d{4}W\d{3}/],["GGGG[W]WW",/\d{4}W\d{2}/,!1],["YYYYDDD",/\d{7}/]],ve=[["HH:mm:ss.SSSS",/\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss,SSSS",/\d\d:\d\d:\d\d,\d+/],["HH:mm:ss",/\d\d:\d\d:\d\d/],["HH:mm",/\d\d:\d\d/],["HHmmss.SSSS",/\d\d\d\d\d\d\.\d+/],["HHmmss,SSSS",/\d\d\d\d\d\d,\d+/],["HHmmss",/\d\d\d\d\d\d/],["HHmm",/\d\d\d\d/],["HH",/\d\d/]],_e=/^\/?Date\((\-?\d+)/i;function we(t){var e,n,r,i,o,a,s=t._i,u=ge.exec(s)||ye.exec(s);if(u){for(p(t).iso=!0,e=0,n=me.length;en.valueOf():n.valueOf()this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()},fn.isLocal=function(){return!!this.isValid()&&!this._isUTC},fn.isUtcOffset=function(){return!!this.isValid()&&this._isUTC},fn.isUtc=Ue,fn.isUTC=Ue,fn.zoneAbbr=function(){return this._isUTC?"UTC":""},fn.zoneName=function(){return this._isUTC?"Coordinated Universal Time":""},fn.dates=S("dates accessor is deprecated. Use date instead.",rn),fn.months=S("months accessor is deprecated. Use month instead",Pt),fn.years=S("years accessor is deprecated. Use year instead",Mt),fn.zone=S("moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/",(function(t,e){return null!=t?("string"!=typeof t&&(t=-t),this.utcOffset(t,e),this):-this.utcOffset()})),fn.isDSTShifted=S("isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information",(function(){if(!s(this._isDSTShifted))return this._isDSTShifted;var t={};if(m(t,this),(t=Se(t))._a){var e=t._isUTC?d(t._a):Me(t._a);this._isDSTShifted=this.isValid()&&0l&&M.push("'"+this.terminals_[A]+"'");C=p.showPosition?"Parse error on line "+(u+1)+":\n"+p.showPosition()+"\nExpecting "+M.join(", ")+", got '"+(this.terminals_[_]||_)+"'":"Parse error on line "+(u+1)+": Unexpected "+(_==h?"end of input":"'"+(this.terminals_[_]||_)+"'"),this.parseError(C,{text:p.match,token:this.terminals_[_]||_,line:p.yylineno,loc:b,expected:M})}if(k[0]instanceof Array&&k.length>1)throw new Error("Parse Error: multiple actions possible at state: "+x+", token: "+_);switch(k[0]){case 1:n.push(_),i.push(p.yytext),o.push(p.yylloc),n.push(k[1]),_=null,w?(_=w,w=null):(c=p.yyleng,s=p.yytext,u=p.yylineno,b=p.yylloc,f>0&&f--);break;case 2:if(S=this.productions_[k[1]][1],D.$=i[i.length-S],D._$={first_line:o[o.length-(S||1)].first_line,last_line:o[o.length-1].last_line,first_column:o[o.length-(S||1)].first_column,last_column:o[o.length-1].last_column},m&&(D._$.range=[o[o.length-(S||1)].range[0],o[o.length-1].range[1]]),void 0!==(E=this.performAction.apply(D,[s,c,u,g.yy,k[1],i,o].concat(d))))return E;S&&(n=n.slice(0,-1*S*2),i=i.slice(0,-1*S),o=o.slice(0,-1*S)),n.push(this.productions_[k[1]][0]),i.push(D.$),o.push(D._$),T=a[n[n.length-2]][n[n.length-1]],n.push(T);break;case 3:return!0}}return!0}},l={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var o in i)this[o]=i[o];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),o=0;oe[0].length)){if(e=n,r=o,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[o])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var t=this.next();return t||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{"case-insensitive":!0},performAction:function(t,e,n,r){switch(n){case 0:return 10;case 1:case 2:case 3:break;case 4:this.begin("href");break;case 5:this.popState();break;case 6:return 23;case 7:this.begin("callbackname");break;case 8:this.popState();break;case 9:this.popState(),this.begin("callbackargs");break;case 10:return 21;case 11:this.popState();break;case 12:return 22;case 13:this.begin("click");break;case 14:this.popState();break;case 15:return 20;case 16:return 4;case 17:return 11;case 18:return 12;case 19:return 13;case 20:return 14;case 21:return"date";case 22:return 15;case 23:return 16;case 24:return 18;case 25:return 19;case 26:return":";case 27:return 6;case 28:return"INVALID"}},rules:[/^(?:[\n]+)/i,/^(?:\s+)/i,/^(?:#[^\n]*)/i,/^(?:%[^\n]*)/i,/^(?:href[\s]+["])/i,/^(?:["])/i,/^(?:[^"]*)/i,/^(?:call[\s]+)/i,/^(?:\([\s]*\))/i,/^(?:\()/i,/^(?:[^(]*)/i,/^(?:\))/i,/^(?:[^)]*)/i,/^(?:click[\s]+)/i,/^(?:[\s\n])/i,/^(?:[^\s\n]*)/i,/^(?:gantt\b)/i,/^(?:dateFormat\s[^#\n;]+)/i,/^(?:inclusiveEndDates\b)/i,/^(?:axisFormat\s[^#\n;]+)/i,/^(?:excludes\s[^#\n;]+)/i,/^(?:\d\d\d\d-\d\d-\d\d\b)/i,/^(?:title\s[^#\n;]+)/i,/^(?:section\s[^#:\n;]+)/i,/^(?:[^#:\n;]+)/i,/^(?::[^#\n;]+)/i,/^(?::)/i,/^(?:$)/i,/^(?:.)/i],conditions:{callbackargs:{rules:[11,12],inclusive:!1},callbackname:{rules:[8,9,10],inclusive:!1},href:{rules:[5,6],inclusive:!1},click:{rules:[14,15],inclusive:!1},INITIAL:{rules:[0,1,2,3,4,7,13,16,17,18,19,20,21,22,23,24,25,26,27,28],inclusive:!0}}};function h(){this.yy={}}return f.lexer=l,h.prototype=f,f.Parser=h,new h}();e.parser=i,e.Parser=i.Parser,e.parse=function(){return i.parse.apply(i,arguments)},e.main=function(r){r[1]||(console.log("Usage: "+r[0]+" FILE"),t.exit(1));var i=n(28).readFileSync(n(29).normalize(r[1]),"utf8");return e.parser.parse(i)},n.c[n.s]===r&&e.main(t.argv.slice(1))}).call(this,n(7),n(9)(t))},function(t,e,n){var r=n(134),i=n(95),o=n(24);t.exports=function(t){return o(t)?r(t):i(t)}},function(t,e){},function(t,e,n){(function(t){function n(t,e){for(var n=0,r=t.length-1;r>=0;r--){var i=t[r];"."===i?t.splice(r,1):".."===i?(t.splice(r,1),n++):n&&(t.splice(r,1),n--)}if(e)for(;n--;n)t.unshift("..");return t}function r(t,e){if(t.filter)return t.filter(e);for(var n=[],r=0;r=-1&&!i;o--){var a=o>=0?arguments[o]:t.cwd();if("string"!=typeof a)throw new TypeError("Arguments to path.resolve must be strings");a&&(e=a+"/"+e,i="/"===a.charAt(0))}return(i?"/":"")+(e=n(r(e.split("/"),(function(t){return!!t})),!i).join("/"))||"."},e.normalize=function(t){var o=e.isAbsolute(t),a="/"===i(t,-1);return(t=n(r(t.split("/"),(function(t){return!!t})),!o).join("/"))||o||(t="."),t&&a&&(t+="/"),(o?"/":"")+t},e.isAbsolute=function(t){return"/"===t.charAt(0)},e.join=function(){var t=Array.prototype.slice.call(arguments,0);return e.normalize(r(t,(function(t,e){if("string"!=typeof t)throw new TypeError("Arguments to path.join must be strings");return t})).join("/"))},e.relative=function(t,n){function r(t){for(var e=0;e=0&&""===t[n];n--);return e>n?[]:t.slice(e,n-e+1)}t=e.resolve(t).substr(1),n=e.resolve(n).substr(1);for(var i=r(t.split("/")),o=r(n.split("/")),a=Math.min(i.length,o.length),s=a,u=0;u=1;--o)if(47===(e=t.charCodeAt(o))){if(!i){r=o;break}}else i=!1;return-1===r?n?"/":".":n&&1===r?"/":t.slice(0,r)},e.basename=function(t,e){var n=function(t){"string"!=typeof t&&(t+="");var e,n=0,r=-1,i=!0;for(e=t.length-1;e>=0;--e)if(47===t.charCodeAt(e)){if(!i){n=e+1;break}}else-1===r&&(i=!1,r=e+1);return-1===r?"":t.slice(n,r)}(t);return e&&n.substr(-1*e.length)===e&&(n=n.substr(0,n.length-e.length)),n},e.extname=function(t){"string"!=typeof t&&(t+="");for(var e=-1,n=0,r=-1,i=!0,o=0,a=t.length-1;a>=0;--a){var s=t.charCodeAt(a);if(47!==s)-1===r&&(i=!1,r=a+1),46===s?-1===e?e=a:1!==o&&(o=1):-1!==e&&(o=-1);else if(!i){n=a+1;break}}return-1===e||-1===r||0===o||1===o&&e===r-1&&e===n+1?"":t.slice(e,r)};var i="b"==="ab".substr(-1)?function(t,e,n){return t.substr(e,n)}:function(t,e,n){return e<0&&(e=t.length+e),t.substr(e,n)}}).call(this,n(7))},function(t,e,n){var r;if(!r)try{r=n(0)}catch(t){}r||(r=window.d3),t.exports=r},function(t,e,n){var r=n(3).Buffer,i=n(112).Transform,o=n(117).StringDecoder;function a(t){i.call(this),this.hashMode="string"==typeof t,this.hashMode?this[t]=this._finalOrDigest:this.final=this._finalOrDigest,this._final&&(this.__final=this._final,this._final=null),this._decoder=null,this._encoding=null}n(2)(a,i),a.prototype.update=function(t,e,n){"string"==typeof t&&(t=r.from(t,e));var i=this._update(t);return this.hashMode?this:(n&&(i=this._toString(i,n)),i)},a.prototype.setAutoPadding=function(){},a.prototype.getAuthTag=function(){throw new Error("trying to get auth tag in unsupported state")},a.prototype.setAuthTag=function(){throw new Error("trying to set auth tag in unsupported state")},a.prototype.setAAD=function(){throw new Error("trying to set aad in unsupported state")},a.prototype._transform=function(t,e,n){var r;try{this.hashMode?this._update(t):this.push(this._update(t))}catch(t){r=t}finally{n(r)}},a.prototype._flush=function(t){var e;try{this.push(this.__final())}catch(t){e=t}t(e)},a.prototype._finalOrDigest=function(t){var e=this.__final()||r.alloc(0);return t&&(e=this._toString(e,t,!0)),e},a.prototype._toString=function(t,e,n){if(this._decoder||(this._decoder=new o(e),this._encoding=e),this._encoding!==e)throw new Error("can't switch encodings");var r=this._decoder.write(t);return n&&(r+=this._decoder.end()),r},t.exports=a},function(t,e,n){var r=n(246),i=n(251);t.exports=function(t,e){var n=i(t,e);return r(n)?n:void 0}},function(t,e,n){var r=n(38),i=n(247),o=n(248),a="[object Null]",s="[object Undefined]",u=r?r.toStringTag:void 0;t.exports=function(t){return null==t?void 0===t?s:a:u&&u in Object(t)?i(t):o(t)}},function(t,e){t.exports=function(t){return t}},function(t,e,n){"use strict";var r=n(78),i=Object.keys||function(t){var e=[];for(var n in t)e.push(n);return e};t.exports=l;var o=n(54);o.inherits=n(2);var a=n(193),s=n(116);o.inherits(l,a);for(var u=i(s.prototype),c=0;co)throw new RangeError("requested too many random bytes");var n=a.allocUnsafe(t);if(t>0)if(t>i)for(var u=0;u=this._finalSize&&(this._update(this._block),this._block.fill(0));var n=8*this._len;if(n<=4294967295)this._block.writeUInt32BE(n,this._blockSize-4);else{var r=(4294967295&n)>>>0,i=(n-r)/4294967296;this._block.writeUInt32BE(i,this._blockSize-8),this._block.writeUInt32BE(r,this._blockSize-4)}this._update(this._block);var o=this._hash();return t?o.toString(t):o},i.prototype._update=function(){throw new Error("_update must be implemented by subclass")},t.exports=i},function(t,e,n){(function(t,r){var i=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[1,12],n=[1,15],r=[1,13],i=[1,14],o=[1,17],a=[1,18],s=[1,19],u=[6,8],c=[1,28],f=[1,29],l=[1,30],h=[1,31],d=[1,32],p=[1,33],g=[6,8,13,18,26,29,30,31,32,33,34],y=[6,8,13,18,22,26,29,30,31,32,33,34,48,49,50],b=[26,48,49,50],m=[26,33,34,48,49,50],v=[26,29,30,31,32,48,49,50],_=[6,8,13],w=[1,50],x={trace:function(){},yy:{},symbols_:{error:2,mermaidDoc:3,graphConfig:4,CLASS_DIAGRAM:5,NEWLINE:6,statements:7,EOF:8,statement:9,className:10,alphaNumToken:11,relationStatement:12,LABEL:13,classStatement:14,methodStatement:15,annotationStatement:16,CLASS:17,STRUCT_START:18,members:19,STRUCT_STOP:20,ANNOTATION_START:21,ANNOTATION_END:22,MEMBER:23,SEPARATOR:24,relation:25,STR:26,relationType:27,lineType:28,AGGREGATION:29,EXTENSION:30,COMPOSITION:31,DEPENDENCY:32,LINE:33,DOTTED_LINE:34,commentToken:35,textToken:36,graphCodeTokens:37,textNoTagsToken:38,TAGSTART:39,TAGEND:40,"==":41,"--":42,PCT:43,DEFAULT:44,SPACE:45,MINUS:46,keywords:47,UNICODE_TEXT:48,NUM:49,ALPHA:50,$accept:0,$end:1},terminals_:{2:"error",5:"CLASS_DIAGRAM",6:"NEWLINE",8:"EOF",13:"LABEL",17:"CLASS",18:"STRUCT_START",20:"STRUCT_STOP",21:"ANNOTATION_START",22:"ANNOTATION_END",23:"MEMBER",24:"SEPARATOR",26:"STR",29:"AGGREGATION",30:"EXTENSION",31:"COMPOSITION",32:"DEPENDENCY",33:"LINE",34:"DOTTED_LINE",37:"graphCodeTokens",39:"TAGSTART",40:"TAGEND",41:"==",42:"--",43:"PCT",44:"DEFAULT",45:"SPACE",46:"MINUS",47:"keywords",48:"UNICODE_TEXT",49:"NUM",50:"ALPHA"},productions_:[0,[3,1],[4,4],[7,1],[7,2],[7,3],[10,2],[10,1],[9,1],[9,2],[9,1],[9,1],[9,1],[14,2],[14,5],[16,4],[19,1],[19,2],[15,1],[15,2],[15,1],[15,1],[12,3],[12,4],[12,4],[12,5],[25,3],[25,2],[25,2],[25,1],[27,1],[27,1],[27,1],[27,1],[28,1],[28,1],[35,1],[35,1],[36,1],[36,1],[36,1],[36,1],[36,1],[36,1],[36,1],[38,1],[38,1],[38,1],[38,1],[11,1],[11,1],[11,1]],performAction:function(t,e,n,r,i,o,a){var s=o.length-1;switch(i){case 6:this.$=o[s-1]+o[s];break;case 7:this.$=o[s];break;case 8:r.addRelation(o[s]);break;case 9:o[s-1].title=r.cleanupLabel(o[s]),r.addRelation(o[s-1]);break;case 13:r.addClass(o[s]);break;case 14:r.addClass(o[s-3]),r.addMembers(o[s-3],o[s-1]);break;case 15:r.addAnnotation(o[s],o[s-2]);break;case 16:this.$=[o[s]];break;case 17:o[s].push(o[s-1]),this.$=o[s];break;case 18:break;case 19:r.addMember(o[s-1],r.cleanupLabel(o[s]));break;case 20:case 21:break;case 22:this.$={id1:o[s-2],id2:o[s],relation:o[s-1],relationTitle1:"none",relationTitle2:"none"};break;case 23:this.$={id1:o[s-3],id2:o[s],relation:o[s-1],relationTitle1:o[s-2],relationTitle2:"none"};break;case 24:this.$={id1:o[s-3],id2:o[s],relation:o[s-2],relationTitle1:"none",relationTitle2:o[s-1]};break;case 25:this.$={id1:o[s-4],id2:o[s],relation:o[s-2],relationTitle1:o[s-3],relationTitle2:o[s-1]};break;case 26:this.$={type1:o[s-2],type2:o[s],lineType:o[s-1]};break;case 27:this.$={type1:"none",type2:o[s],lineType:o[s-1]};break;case 28:this.$={type1:o[s-1],type2:"none",lineType:o[s]};break;case 29:this.$={type1:"none",type2:"none",lineType:o[s]};break;case 30:this.$=r.relationType.AGGREGATION;break;case 31:this.$=r.relationType.EXTENSION;break;case 32:this.$=r.relationType.COMPOSITION;break;case 33:this.$=r.relationType.DEPENDENCY;break;case 34:this.$=r.lineType.LINE;break;case 35:this.$=r.lineType.DOTTED_LINE}},table:[{3:1,4:2,5:[1,3]},{1:[3]},{1:[2,1]},{6:[1,4]},{7:5,9:6,10:11,11:16,12:7,14:8,15:9,16:10,17:e,21:n,23:r,24:i,48:o,49:a,50:s},{8:[1,20]},{6:[1,21],8:[2,3]},t(u,[2,8],{13:[1,22]}),t(u,[2,10]),t(u,[2,11]),t(u,[2,12]),t(u,[2,18],{25:23,27:26,28:27,13:[1,25],26:[1,24],29:c,30:f,31:l,32:h,33:d,34:p}),{10:34,11:16,48:o,49:a,50:s},t(u,[2,20]),t(u,[2,21]),{11:35,48:o,49:a,50:s},t(g,[2,7],{11:16,10:36,48:o,49:a,50:s}),t(y,[2,49]),t(y,[2,50]),t(y,[2,51]),{1:[2,2]},{7:37,8:[2,4],9:6,10:11,11:16,12:7,14:8,15:9,16:10,17:e,21:n,23:r,24:i,48:o,49:a,50:s},t(u,[2,9]),{10:38,11:16,26:[1,39],48:o,49:a,50:s},{25:40,27:26,28:27,29:c,30:f,31:l,32:h,33:d,34:p},t(u,[2,19]),{28:41,33:d,34:p},t(b,[2,29],{27:42,29:c,30:f,31:l,32:h}),t(m,[2,30]),t(m,[2,31]),t(m,[2,32]),t(m,[2,33]),t(v,[2,34]),t(v,[2,35]),t(u,[2,13],{18:[1,43]}),{22:[1,44]},t(g,[2,6]),{8:[2,5]},t(_,[2,22]),{10:45,11:16,48:o,49:a,50:s},{10:46,11:16,26:[1,47],48:o,49:a,50:s},t(b,[2,28],{27:48,29:c,30:f,31:l,32:h}),t(b,[2,27]),{19:49,23:w},{10:51,11:16,48:o,49:a,50:s},t(_,[2,24]),t(_,[2,23]),{10:52,11:16,48:o,49:a,50:s},t(b,[2,26]),{20:[1,53]},{19:54,20:[2,16],23:w},t(u,[2,15]),t(_,[2,25]),t(u,[2,14]),{20:[2,17]}],defaultActions:{2:[2,1],20:[2,2],37:[2,5],54:[2,17]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],o=[],a=this.table,s="",u=0,c=0,f=0,l=2,h=1,d=o.slice.call(arguments,1),p=Object.create(this.lexer),g={yy:{}};for(var y in this.yy)Object.prototype.hasOwnProperty.call(this.yy,y)&&(g.yy[y]=this.yy[y]);p.setInput(t,g.yy),g.yy.lexer=p,g.yy.parser=this,void 0===p.yylloc&&(p.yylloc={});var b=p.yylloc;o.push(b);var m=p.options&&p.options.ranges;function v(){var t;return"number"!=typeof(t=r.pop()||p.lex()||h)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof g.yy.parseError?this.parseError=g.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var _,w,x,k,E,A,S,T,M,D={};;){if(x=n[n.length-1],this.defaultActions[x]?k=this.defaultActions[x]:(null==_&&(_=v()),k=a[x]&&a[x][_]),void 0===k||!k.length||!k[0]){var C="";for(A in M=[],a[x])this.terminals_[A]&&A>l&&M.push("'"+this.terminals_[A]+"'");C=p.showPosition?"Parse error on line "+(u+1)+":\n"+p.showPosition()+"\nExpecting "+M.join(", ")+", got '"+(this.terminals_[_]||_)+"'":"Parse error on line "+(u+1)+": Unexpected "+(_==h?"end of input":"'"+(this.terminals_[_]||_)+"'"),this.parseError(C,{text:p.match,token:this.terminals_[_]||_,line:p.yylineno,loc:b,expected:M})}if(k[0]instanceof Array&&k.length>1)throw new Error("Parse Error: multiple actions possible at state: "+x+", token: "+_);switch(k[0]){case 1:n.push(_),i.push(p.yytext),o.push(p.yylloc),n.push(k[1]),_=null,w?(_=w,w=null):(c=p.yyleng,s=p.yytext,u=p.yylineno,b=p.yylloc,f>0&&f--);break;case 2:if(S=this.productions_[k[1]][1],D.$=i[i.length-S],D._$={first_line:o[o.length-(S||1)].first_line,last_line:o[o.length-1].last_line,first_column:o[o.length-(S||1)].first_column,last_column:o[o.length-1].last_column},m&&(D._$.range=[o[o.length-(S||1)].range[0],o[o.length-1].range[1]]),void 0!==(E=this.performAction.apply(D,[s,c,u,g.yy,k[1],i,o].concat(d))))return E;S&&(n=n.slice(0,-1*S*2),i=i.slice(0,-1*S),o=o.slice(0,-1*S)),n.push(this.productions_[k[1]][0]),i.push(D.$),o.push(D._$),T=a[n[n.length-2]][n[n.length-1]],n.push(T);break;case 3:return!0}}return!0}},k={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var o in i)this[o]=i[o];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),o=0;oe[0].length)){if(e=n,r=o,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[o])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var t=this.next();return t||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{},performAction:function(t,e,n,r){switch(n){case 0:break;case 1:return 6;case 2:break;case 3:return 5;case 4:return this.begin("struct"),18;case 5:return this.popState(),20;case 6:break;case 7:return"MEMBER";case 8:return 17;case 9:return 21;case 10:return 22;case 11:this.begin("string");break;case 12:this.popState();break;case 13:return"STR";case 14:case 15:return 30;case 16:case 17:return 32;case 18:return 31;case 19:return 29;case 20:return 33;case 21:return 34;case 22:return 13;case 23:return 46;case 24:return"DOT";case 25:return"PLUS";case 26:return 43;case 27:case 28:return"EQUALS";case 29:return 50;case 30:return"PUNCTUATION";case 31:return 49;case 32:return 48;case 33:return 45;case 34:return 8}},rules:[/^(?:%%[^\n]*)/,/^(?:\n+)/,/^(?:\s+)/,/^(?:classDiagram\b)/,/^(?:[\{])/,/^(?:\})/,/^(?:[\n])/,/^(?:[^\{\}\n]*)/,/^(?:class\b)/,/^(?:<<)/,/^(?:>>)/,/^(?:["])/,/^(?:["])/,/^(?:[^"]*)/,/^(?:\s*<\|)/,/^(?:\s*\|>)/,/^(?:\s*>)/,/^(?:\s*<)/,/^(?:\s*\*)/,/^(?:\s*o\b)/,/^(?:--)/,/^(?:\.\.)/,/^(?::[^\n;]+)/,/^(?:-)/,/^(?:\.)/,/^(?:\+)/,/^(?:%)/,/^(?:=)/,/^(?:=)/,/^(?:\w+)/,/^(?:[!"#$%&'*+,-.`?\\\/])/,/^(?:[0-9]+)/,/^(?:[\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6]|[\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377]|[\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5]|[\u03F7-\u0481\u048A-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA]|[\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE]|[\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA]|[\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0]|[\u08A2-\u08AC\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0977]|[\u0979-\u097F\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2]|[\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A]|[\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39]|[\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8]|[\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C]|[\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C]|[\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99]|[\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0]|[\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3D]|[\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3]|[\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10]|[\u0D12-\u0D3A\u0D3D\u0D4E\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1]|[\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81]|[\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3]|[\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6]|[\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A]|[\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081]|[\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D]|[\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0]|[\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310]|[\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C]|[\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u1700-\u170C\u170E-\u1711]|[\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7]|[\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191C]|[\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16]|[\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF]|[\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC]|[\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D]|[\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D]|[\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3]|[\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F]|[\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128]|[\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184]|[\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3]|[\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6]|[\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE]|[\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C]|[\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D]|[\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC]|[\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B]|[\uA640-\uA66E\uA67F-\uA697\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788]|[\uA78B-\uA78E\uA790-\uA793\uA7A0-\uA7AA\uA7F8-\uA801\uA803-\uA805]|[\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB]|[\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uAA00-\uAA28]|[\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA80-\uAAAF\uAAB1\uAAB5]|[\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4]|[\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E]|[\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D]|[\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36]|[\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D]|[\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC]|[\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF]|[\uFFD2-\uFFD7\uFFDA-\uFFDC])/,/^(?:\s)/,/^(?:$)/],conditions:{string:{rules:[12,13],inclusive:!1},struct:{rules:[5,6,7],inclusive:!1},INITIAL:{rules:[0,1,2,3,4,8,9,10,11,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34],inclusive:!0}}};function E(){this.yy={}}return x.lexer=k,E.prototype=x,x.Parser=E,new E}();e.parser=i,e.Parser=i.Parser,e.parse=function(){return i.parse.apply(i,arguments)},e.main=function(r){r[1]||(console.log("Usage: "+r[0]+" FILE"),t.exit(1));var i=n(28).readFileSync(n(29).normalize(r[1]),"utf8");return e.parser.parse(i)},n.c[n.s]===r&&e.main(t.argv.slice(1))}).call(this,n(7),n(9)(t))},function(t,e,n){(function(t,r){var i=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[1,2],n=[1,3],r=[1,4],i=[2,4],o=[1,9],a=[1,11],s=[1,13],u=[1,14],c=[1,15],f=[1,16],l=[1,21],h=[1,17],d=[1,18],p=[1,19],g=[1,20],y=[1,22],b=[1,4,5,13,14,16,18,19,21,22,23,24,25,28],m=[1,4,5,11,12,13,14,16,18,19,21,22,23,24,25,28],v=[4,5,13,14,16,18,19,21,22,23,24,25,28],_={trace:function(){},yy:{},symbols_:{error:2,start:3,SPACE:4,NL:5,SD:6,document:7,line:8,statement:9,idStatement:10,DESCR:11,"--\x3e":12,HIDE_EMPTY:13,scale:14,WIDTH:15,COMPOSIT_STATE:16,STRUCT_START:17,STRUCT_STOP:18,STATE_DESCR:19,AS:20,ID:21,FORK:22,JOIN:23,CONCURRENT:24,note:25,notePosition:26,NOTE_TEXT:27,EDGE_STATE:28,left_of:29,right_of:30,$accept:0,$end:1},terminals_:{2:"error",4:"SPACE",5:"NL",6:"SD",11:"DESCR",12:"--\x3e",13:"HIDE_EMPTY",14:"scale",15:"WIDTH",16:"COMPOSIT_STATE",17:"STRUCT_START",18:"STRUCT_STOP",19:"STATE_DESCR",20:"AS",21:"ID",22:"FORK",23:"JOIN",24:"CONCURRENT",25:"note",27:"NOTE_TEXT",28:"EDGE_STATE",29:"left_of",30:"right_of"},productions_:[0,[3,2],[3,2],[3,2],[7,0],[7,2],[8,2],[8,1],[8,1],[9,1],[9,2],[9,3],[9,4],[9,1],[9,2],[9,1],[9,4],[9,3],[9,6],[9,1],[9,1],[9,1],[9,4],[9,4],[10,1],[10,1],[26,1],[26,1]],performAction:function(t,e,n,r,i,o,a){var s=o.length-1;switch(i){case 3:return r.setRootDoc(o[s]),o[s];case 4:this.$=[];break;case 5:"nl"!=o[s]&&(o[s-1].push(o[s]),this.$=o[s-1]);break;case 6:case 7:this.$=o[s];break;case 8:this.$="nl";break;case 9:this.$={stmt:"state",id:o[s],type:"default",description:""};break;case 10:this.$={stmt:"state",id:o[s-1],type:"default",description:o[s].trim()};break;case 11:this.$={stmt:"relation",state1:{stmt:"state",id:o[s-2],type:"default",description:""},state2:{stmt:"state",id:o[s],type:"default",description:""}};break;case 12:this.$={stmt:"relation",state1:{stmt:"state",id:o[s-3],type:"default",description:""},state2:{stmt:"state",id:o[s-1],type:"default",description:""},description:o[s].substr(1).trim()};break;case 16:this.$={stmt:"state",id:o[s-3],type:"default",description:"",doc:o[s-1]};break;case 17:var u=o[s],c=o[s-2].trim();if(o[s].match(":")){var f=o[s].split(":");u=f[0],c=[c,f[1]]}this.$={stmt:"state",id:u,type:"default",description:c};break;case 18:this.$={stmt:"state",id:o[s-3],type:"default",description:o[s-5],doc:o[s-1]};break;case 19:this.$={stmt:"state",id:o[s],type:"fork"};break;case 20:this.$={stmt:"state",id:o[s],type:"join"};break;case 21:this.$={stmt:"state",id:r.getDividerId(),type:"divider"};break;case 22:this.$={stmt:"state",id:o[s-1].trim(),note:{position:o[s-2].trim(),text:o[s].trim()}};break;case 24:case 25:this.$=o[s]}},table:[{3:1,4:e,5:n,6:r},{1:[3]},{3:5,4:e,5:n,6:r},{3:6,4:e,5:n,6:r},t([1,4,5,13,14,16,19,21,22,23,24,25,28],i,{7:7}),{1:[2,1]},{1:[2,2]},{1:[2,3],4:o,5:a,8:8,9:10,10:12,13:s,14:u,16:c,19:f,21:l,22:h,23:d,24:p,25:g,28:y},t(b,[2,5]),{9:23,10:12,13:s,14:u,16:c,19:f,21:l,22:h,23:d,24:p,25:g,28:y},t(b,[2,7]),t(b,[2,8]),t(b,[2,9],{11:[1,24],12:[1,25]}),t(b,[2,13]),{15:[1,26]},t(b,[2,15],{17:[1,27]}),{20:[1,28]},t(b,[2,19]),t(b,[2,20]),t(b,[2,21]),{26:29,27:[1,30],29:[1,31],30:[1,32]},t(m,[2,24]),t(m,[2,25]),t(b,[2,6]),t(b,[2,10]),{10:33,21:l,28:y},t(b,[2,14]),t(v,i,{7:34}),{21:[1,35]},{21:[1,36]},{20:[1,37]},{21:[2,26]},{21:[2,27]},t(b,[2,11],{11:[1,38]}),{4:o,5:a,8:8,9:10,10:12,13:s,14:u,16:c,18:[1,39],19:f,21:l,22:h,23:d,24:p,25:g,28:y},t(b,[2,17],{17:[1,40]}),{27:[1,41]},{21:[1,42]},t(b,[2,12]),t(b,[2,16]),t(v,i,{7:43}),t(b,[2,22]),t(b,[2,23]),{4:o,5:a,8:8,9:10,10:12,13:s,14:u,16:c,18:[1,44],19:f,21:l,22:h,23:d,24:p,25:g,28:y},t(b,[2,18])],defaultActions:{5:[2,1],6:[2,2],31:[2,26],32:[2,27]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],o=[],a=this.table,s="",u=0,c=0,f=0,l=2,h=1,d=o.slice.call(arguments,1),p=Object.create(this.lexer),g={yy:{}};for(var y in this.yy)Object.prototype.hasOwnProperty.call(this.yy,y)&&(g.yy[y]=this.yy[y]);p.setInput(t,g.yy),g.yy.lexer=p,g.yy.parser=this,void 0===p.yylloc&&(p.yylloc={});var b=p.yylloc;o.push(b);var m=p.options&&p.options.ranges;function v(){var t;return"number"!=typeof(t=r.pop()||p.lex()||h)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof g.yy.parseError?this.parseError=g.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var _,w,x,k,E,A,S,T,M,D={};;){if(x=n[n.length-1],this.defaultActions[x]?k=this.defaultActions[x]:(null==_&&(_=v()),k=a[x]&&a[x][_]),void 0===k||!k.length||!k[0]){var C="";for(A in M=[],a[x])this.terminals_[A]&&A>l&&M.push("'"+this.terminals_[A]+"'");C=p.showPosition?"Parse error on line "+(u+1)+":\n"+p.showPosition()+"\nExpecting "+M.join(", ")+", got '"+(this.terminals_[_]||_)+"'":"Parse error on line "+(u+1)+": Unexpected "+(_==h?"end of input":"'"+(this.terminals_[_]||_)+"'"),this.parseError(C,{text:p.match,token:this.terminals_[_]||_,line:p.yylineno,loc:b,expected:M})}if(k[0]instanceof Array&&k.length>1)throw new Error("Parse Error: multiple actions possible at state: "+x+", token: "+_);switch(k[0]){case 1:n.push(_),i.push(p.yytext),o.push(p.yylloc),n.push(k[1]),_=null,w?(_=w,w=null):(c=p.yyleng,s=p.yytext,u=p.yylineno,b=p.yylloc,f>0&&f--);break;case 2:if(S=this.productions_[k[1]][1],D.$=i[i.length-S],D._$={first_line:o[o.length-(S||1)].first_line,last_line:o[o.length-1].last_line,first_column:o[o.length-(S||1)].first_column,last_column:o[o.length-1].last_column},m&&(D._$.range=[o[o.length-(S||1)].range[0],o[o.length-1].range[1]]),void 0!==(E=this.performAction.apply(D,[s,c,u,g.yy,k[1],i,o].concat(d))))return E;S&&(n=n.slice(0,-1*S*2),i=i.slice(0,-1*S),o=o.slice(0,-1*S)),n.push(this.productions_[k[1]][0]),i.push(D.$),o.push(D._$),T=a[n[n.length-2]][n[n.length-1]],n.push(T);break;case 3:return!0}}return!0}},w={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var o in i)this[o]=i[o];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),o=0;oe[0].length)){if(e=n,r=o,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[o])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var t=this.next();return t||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{"case-insensitive":!0},performAction:function(t,e,n,r){switch(n){case 0:return 5;case 1:case 2:case 3:case 4:break;case 5:return this.pushState("SCALE"),14;case 6:return 15;case 7:this.popState();break;case 8:this.pushState("STATE");break;case 9:return this.popState(),e.yytext=e.yytext.slice(0,-8).trim(),22;case 10:return this.popState(),e.yytext=e.yytext.slice(0,-8).trim(),23;case 11:return this.popState(),e.yytext=e.yytext.slice(0,-8).trim(),22;case 12:return this.popState(),e.yytext=e.yytext.slice(0,-8).trim(),23;case 13:this.begin("STATE_STRING");break;case 14:return this.popState(),this.pushState("STATE_ID"),"AS";case 15:return this.popState(),"ID";case 16:this.popState();break;case 17:return"STATE_DESCR";case 18:return 16;case 19:this.popState();break;case 20:return this.popState(),this.pushState("struct"),17;case 21:return this.popState(),18;case 22:break;case 23:return this.begin("NOTE"),25;case 24:return this.popState(),this.pushState("NOTE_ID"),29;case 25:return this.popState(),this.pushState("NOTE_ID"),30;case 26:this.popState(),this.pushState("FLOATING_NOTE");break;case 27:return this.popState(),this.pushState("FLOATING_NOTE_ID"),"AS";case 28:break;case 29:return"NOTE_TEXT";case 30:return this.popState(),"ID";case 31:return this.popState(),this.pushState("NOTE_TEXT"),21;case 32:return this.popState(),e.yytext=e.yytext.substr(2).trim(),27;case 33:return this.popState(),e.yytext=e.yytext.slice(0,-8).trim(),27;case 34:return 6;case 35:return 13;case 36:return 28;case 37:return 21;case 38:return e.yytext=e.yytext.trim(),11;case 39:return 12;case 40:return 24;case 41:return 5;case 42:return"INVALID"}},rules:[/^(?:[\n]+)/i,/^(?:\s+)/i,/^(?:((?!\n)\s)+)/i,/^(?:#[^\n]*)/i,/^(?:%[^\n]*)/i,/^(?:scale\s+)/i,/^(?:\d+)/i,/^(?:\s+width\b)/i,/^(?:state\s+)/i,/^(?:.*<>)/i,/^(?:.*<>)/i,/^(?:.*\[\[fork\]\])/i,/^(?:.*\[\[join\]\])/i,/^(?:["])/i,/^(?:as\s*)/i,/^(?:[^\n\{]*)/i,/^(?:["])/i,/^(?:[^"]*)/i,/^(?:[^\n\s\{]+)/i,/^(?:\n)/i,/^(?:\{)/i,/^(?:\})/i,/^(?:[\n])/i,/^(?:note\s+)/i,/^(?:left of\b)/i,/^(?:right of\b)/i,/^(?:")/i,/^(?:\s*as\s*)/i,/^(?:["])/i,/^(?:[^"]*)/i,/^(?:[^\n]*)/i,/^(?:\s*[^:\n\s\-]+)/i,/^(?:\s*:[^:\n;]+)/i,/^(?:\s*[^:;]+end note\b)/i,/^(?:stateDiagram\s+)/i,/^(?:hide empty description\b)/i,/^(?:\[\*\])/i,/^(?:[^:\n\s\-\{]+)/i,/^(?:\s*:[^:\n;]+)/i,/^(?:-->)/i,/^(?:--)/i,/^(?:$)/i,/^(?:.)/i],conditions:{LINE:{rules:[2,3],inclusive:!1},struct:{rules:[2,3,8,21,22,23,36,37,38,39,40],inclusive:!1},FLOATING_NOTE_ID:{rules:[30],inclusive:!1},FLOATING_NOTE:{rules:[27,28,29],inclusive:!1},NOTE_TEXT:{rules:[32,33],inclusive:!1},NOTE_ID:{rules:[31],inclusive:!1},NOTE:{rules:[24,25,26],inclusive:!1},SCALE:{rules:[6,7],inclusive:!1},ALIAS:{rules:[],inclusive:!1},STATE_ID:{rules:[15],inclusive:!1},STATE_STRING:{rules:[16,17],inclusive:!1},FORK_STATE:{rules:[],inclusive:!1},STATE:{rules:[2,3,9,10,11,12,13,14,18,19,20],inclusive:!1},ID:{rules:[2,3],inclusive:!1},INITIAL:{rules:[0,1,3,4,5,8,20,23,34,35,36,37,38,39,41,42],inclusive:!0}}};function x(){this.yy={}}return _.lexer=w,x.prototype=_,_.Parser=x,new x}();e.parser=i,e.Parser=i.Parser,e.parse=function(){return i.parse.apply(i,arguments)},e.main=function(r){r[1]||(console.log("Usage: "+r[0]+" FILE"),t.exit(1));var i=n(28).readFileSync(n(29).normalize(r[1]),"utf8");return e.parser.parse(i)},n.c[n.s]===r&&e.main(t.argv.slice(1))}).call(this,n(7),n(9)(t))},function(t,e,n){(function(t,n){(function(){var r,i=200,o="Unsupported core-js use. Try https://npms.io/search?q=ponyfill.",a="Expected a function",s="__lodash_hash_undefined__",u=500,c="__lodash_placeholder__",f=1,l=2,h=4,d=1,p=2,g=1,y=2,b=4,m=8,v=16,_=32,w=64,x=128,k=256,E=512,A=30,S="...",T=800,M=16,D=1,C=2,O=1/0,R=9007199254740991,I=17976931348623157e292,N=NaN,B=4294967295,L=B-1,P=B>>>1,F=[["ary",x],["bind",g],["bindKey",y],["curry",m],["curryRight",v],["flip",E],["partial",_],["partialRight",w],["rearg",k]],q="[object Arguments]",j="[object Array]",U="[object AsyncFunction]",z="[object Boolean]",Y="[object Date]",V="[object DOMException]",H="[object Error]",$="[object Function]",G="[object GeneratorFunction]",W="[object Map]",K="[object Number]",X="[object Null]",Z="[object Object]",J="[object Proxy]",Q="[object RegExp]",tt="[object Set]",et="[object String]",nt="[object Symbol]",rt="[object Undefined]",it="[object WeakMap]",ot="[object WeakSet]",at="[object ArrayBuffer]",st="[object DataView]",ut="[object Float32Array]",ct="[object Float64Array]",ft="[object Int8Array]",lt="[object Int16Array]",ht="[object Int32Array]",dt="[object Uint8Array]",pt="[object Uint8ClampedArray]",gt="[object Uint16Array]",yt="[object Uint32Array]",bt=/\b__p \+= '';/g,mt=/\b(__p \+=) '' \+/g,vt=/(__e\(.*?\)|\b__t\)) \+\n'';/g,_t=/&(?:amp|lt|gt|quot|#39);/g,wt=/[&<>"']/g,xt=RegExp(_t.source),kt=RegExp(wt.source),Et=/<%-([\s\S]+?)%>/g,At=/<%([\s\S]+?)%>/g,St=/<%=([\s\S]+?)%>/g,Tt=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,Mt=/^\w*$/,Dt=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g,Ct=/[\\^$.*+?()[\]{}|]/g,Ot=RegExp(Ct.source),Rt=/^\s+|\s+$/g,It=/^\s+/,Nt=/\s+$/,Bt=/\{(?:\n\/\* \[wrapped with .+\] \*\/)?\n?/,Lt=/\{\n\/\* \[wrapped with (.+)\] \*/,Pt=/,? & /,Ft=/[^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/g,qt=/\\(\\)?/g,jt=/\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g,Ut=/\w*$/,zt=/^[-+]0x[0-9a-f]+$/i,Yt=/^0b[01]+$/i,Vt=/^\[object .+?Constructor\]$/,Ht=/^0o[0-7]+$/i,$t=/^(?:0|[1-9]\d*)$/,Gt=/[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g,Wt=/($^)/,Kt=/['\n\r\u2028\u2029\\]/g,Xt="\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff",Zt="\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000",Jt="[\\ud800-\\udfff]",Qt="["+Zt+"]",te="["+Xt+"]",ee="\\d+",ne="[\\u2700-\\u27bf]",re="[a-z\\xdf-\\xf6\\xf8-\\xff]",ie="[^\\ud800-\\udfff"+Zt+ee+"\\u2700-\\u27bfa-z\\xdf-\\xf6\\xf8-\\xffA-Z\\xc0-\\xd6\\xd8-\\xde]",oe="\\ud83c[\\udffb-\\udfff]",ae="[^\\ud800-\\udfff]",se="(?:\\ud83c[\\udde6-\\uddff]){2}",ue="[\\ud800-\\udbff][\\udc00-\\udfff]",ce="[A-Z\\xc0-\\xd6\\xd8-\\xde]",fe="(?:"+re+"|"+ie+")",le="(?:"+ce+"|"+ie+")",he="(?:"+te+"|"+oe+")"+"?",de="[\\ufe0e\\ufe0f]?"+he+("(?:\\u200d(?:"+[ae,se,ue].join("|")+")[\\ufe0e\\ufe0f]?"+he+")*"),pe="(?:"+[ne,se,ue].join("|")+")"+de,ge="(?:"+[ae+te+"?",te,se,ue,Jt].join("|")+")",ye=RegExp("['’]","g"),be=RegExp(te,"g"),me=RegExp(oe+"(?="+oe+")|"+ge+de,"g"),ve=RegExp([ce+"?"+re+"+(?:['’](?:d|ll|m|re|s|t|ve))?(?="+[Qt,ce,"$"].join("|")+")",le+"+(?:['’](?:D|LL|M|RE|S|T|VE))?(?="+[Qt,ce+fe,"$"].join("|")+")",ce+"?"+fe+"+(?:['’](?:d|ll|m|re|s|t|ve))?",ce+"+(?:['’](?:D|LL|M|RE|S|T|VE))?","\\d*(?:1ST|2ND|3RD|(?![123])\\dTH)(?=\\b|[a-z_])","\\d*(?:1st|2nd|3rd|(?![123])\\dth)(?=\\b|[A-Z_])",ee,pe].join("|"),"g"),_e=RegExp("[\\u200d\\ud800-\\udfff"+Xt+"\\ufe0e\\ufe0f]"),we=/[a-z][A-Z]|[A-Z]{2}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/,xe=["Array","Buffer","DataView","Date","Error","Float32Array","Float64Array","Function","Int8Array","Int16Array","Int32Array","Map","Math","Object","Promise","RegExp","Set","String","Symbol","TypeError","Uint8Array","Uint8ClampedArray","Uint16Array","Uint32Array","WeakMap","_","clearTimeout","isFinite","parseInt","setTimeout"],ke=-1,Ee={};Ee[ut]=Ee[ct]=Ee[ft]=Ee[lt]=Ee[ht]=Ee[dt]=Ee[pt]=Ee[gt]=Ee[yt]=!0,Ee[q]=Ee[j]=Ee[at]=Ee[z]=Ee[st]=Ee[Y]=Ee[H]=Ee[$]=Ee[W]=Ee[K]=Ee[Z]=Ee[Q]=Ee[tt]=Ee[et]=Ee[it]=!1;var Ae={};Ae[q]=Ae[j]=Ae[at]=Ae[st]=Ae[z]=Ae[Y]=Ae[ut]=Ae[ct]=Ae[ft]=Ae[lt]=Ae[ht]=Ae[W]=Ae[K]=Ae[Z]=Ae[Q]=Ae[tt]=Ae[et]=Ae[nt]=Ae[dt]=Ae[pt]=Ae[gt]=Ae[yt]=!0,Ae[H]=Ae[$]=Ae[it]=!1;var Se={"\\":"\\","'":"'","\n":"n","\r":"r","\u2028":"u2028","\u2029":"u2029"},Te=parseFloat,Me=parseInt,De="object"==typeof t&&t&&t.Object===Object&&t,Ce="object"==typeof self&&self&&self.Object===Object&&self,Oe=De||Ce||Function("return this")(),Re=e&&!e.nodeType&&e,Ie=Re&&"object"==typeof n&&n&&!n.nodeType&&n,Ne=Ie&&Ie.exports===Re,Be=Ne&&De.process,Le=function(){try{var t=Ie&&Ie.require&&Ie.require("util").types;return t||Be&&Be.binding&&Be.binding("util")}catch(t){}}(),Pe=Le&&Le.isArrayBuffer,Fe=Le&&Le.isDate,qe=Le&&Le.isMap,je=Le&&Le.isRegExp,Ue=Le&&Le.isSet,ze=Le&&Le.isTypedArray;function Ye(t,e,n){switch(n.length){case 0:return t.call(e);case 1:return t.call(e,n[0]);case 2:return t.call(e,n[0],n[1]);case 3:return t.call(e,n[0],n[1],n[2])}return t.apply(e,n)}function Ve(t,e,n,r){for(var i=-1,o=null==t?0:t.length;++i-1}function Xe(t,e,n){for(var r=-1,i=null==t?0:t.length;++r-1;);return n}function vn(t,e){for(var n=t.length;n--&&an(e,t[n],0)>-1;);return n}var _n=ln({"À":"A","Á":"A","Â":"A","Ã":"A","Ä":"A","Å":"A","à":"a","á":"a","â":"a","ã":"a","ä":"a","å":"a","Ç":"C","ç":"c","Ð":"D","ð":"d","È":"E","É":"E","Ê":"E","Ë":"E","è":"e","é":"e","ê":"e","ë":"e","Ì":"I","Í":"I","Î":"I","Ï":"I","ì":"i","í":"i","î":"i","ï":"i","Ñ":"N","ñ":"n","Ò":"O","Ó":"O","Ô":"O","Õ":"O","Ö":"O","Ø":"O","ò":"o","ó":"o","ô":"o","õ":"o","ö":"o","ø":"o","Ù":"U","Ú":"U","Û":"U","Ü":"U","ù":"u","ú":"u","û":"u","ü":"u","Ý":"Y","ý":"y","ÿ":"y","Æ":"Ae","æ":"ae","Þ":"Th","þ":"th","ß":"ss","Ā":"A","Ă":"A","Ą":"A","ā":"a","ă":"a","ą":"a","Ć":"C","Ĉ":"C","Ċ":"C","Č":"C","ć":"c","ĉ":"c","ċ":"c","č":"c","Ď":"D","Đ":"D","ď":"d","đ":"d","Ē":"E","Ĕ":"E","Ė":"E","Ę":"E","Ě":"E","ē":"e","ĕ":"e","ė":"e","ę":"e","ě":"e","Ĝ":"G","Ğ":"G","Ġ":"G","Ģ":"G","ĝ":"g","ğ":"g","ġ":"g","ģ":"g","Ĥ":"H","Ħ":"H","ĥ":"h","ħ":"h","Ĩ":"I","Ī":"I","Ĭ":"I","Į":"I","İ":"I","ĩ":"i","ī":"i","ĭ":"i","į":"i","ı":"i","Ĵ":"J","ĵ":"j","Ķ":"K","ķ":"k","ĸ":"k","Ĺ":"L","Ļ":"L","Ľ":"L","Ŀ":"L","Ł":"L","ĺ":"l","ļ":"l","ľ":"l","ŀ":"l","ł":"l","Ń":"N","Ņ":"N","Ň":"N","Ŋ":"N","ń":"n","ņ":"n","ň":"n","ŋ":"n","Ō":"O","Ŏ":"O","Ő":"O","ō":"o","ŏ":"o","ő":"o","Ŕ":"R","Ŗ":"R","Ř":"R","ŕ":"r","ŗ":"r","ř":"r","Ś":"S","Ŝ":"S","Ş":"S","Š":"S","ś":"s","ŝ":"s","ş":"s","š":"s","Ţ":"T","Ť":"T","Ŧ":"T","ţ":"t","ť":"t","ŧ":"t","Ũ":"U","Ū":"U","Ŭ":"U","Ů":"U","Ű":"U","Ų":"U","ũ":"u","ū":"u","ŭ":"u","ů":"u","ű":"u","ų":"u","Ŵ":"W","ŵ":"w","Ŷ":"Y","ŷ":"y","Ÿ":"Y","Ź":"Z","Ż":"Z","Ž":"Z","ź":"z","ż":"z","ž":"z","IJ":"IJ","ij":"ij","Œ":"Oe","œ":"oe","ʼn":"'n","ſ":"s"}),wn=ln({"&":"&","<":"<",">":">",'"':""","'":"'"});function xn(t){return"\\"+Se[t]}function kn(t){return _e.test(t)}function En(t){var e=-1,n=Array(t.size);return t.forEach((function(t,r){n[++e]=[r,t]})),n}function An(t,e){return function(n){return t(e(n))}}function Sn(t,e){for(var n=-1,r=t.length,i=0,o=[];++n",""":'"',"'":"'"});var Rn=function t(e){var n,Xt=(e=null==e?Oe:Rn.defaults(Oe.Object(),e,Rn.pick(Oe,xe))).Array,Zt=e.Date,Jt=e.Error,Qt=e.Function,te=e.Math,ee=e.Object,ne=e.RegExp,re=e.String,ie=e.TypeError,oe=Xt.prototype,ae=Qt.prototype,se=ee.prototype,ue=e["__core-js_shared__"],ce=ae.toString,fe=se.hasOwnProperty,le=0,he=(n=/[^.]+$/.exec(ue&&ue.keys&&ue.keys.IE_PROTO||""))?"Symbol(src)_1."+n:"",de=se.toString,pe=ce.call(ee),ge=Oe._,me=ne("^"+ce.call(fe).replace(Ct,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$"),_e=Ne?e.Buffer:r,Se=e.Symbol,De=e.Uint8Array,Ce=_e?_e.allocUnsafe:r,Re=An(ee.getPrototypeOf,ee),Ie=ee.create,Be=se.propertyIsEnumerable,Le=oe.splice,nn=Se?Se.isConcatSpreadable:r,ln=Se?Se.iterator:r,In=Se?Se.toStringTag:r,Nn=function(){try{var t=qo(ee,"defineProperty");return t({},"",{}),t}catch(t){}}(),Bn=e.clearTimeout!==Oe.clearTimeout&&e.clearTimeout,Ln=Zt&&Zt.now!==Oe.Date.now&&Zt.now,Pn=e.setTimeout!==Oe.setTimeout&&e.setTimeout,Fn=te.ceil,qn=te.floor,jn=ee.getOwnPropertySymbols,Un=_e?_e.isBuffer:r,zn=e.isFinite,Yn=oe.join,Vn=An(ee.keys,ee),Hn=te.max,$n=te.min,Gn=Zt.now,Wn=e.parseInt,Kn=te.random,Xn=oe.reverse,Zn=qo(e,"DataView"),Jn=qo(e,"Map"),Qn=qo(e,"Promise"),tr=qo(e,"Set"),er=qo(e,"WeakMap"),nr=qo(ee,"create"),rr=er&&new er,ir={},or=la(Zn),ar=la(Jn),sr=la(Qn),ur=la(tr),cr=la(er),fr=Se?Se.prototype:r,lr=fr?fr.valueOf:r,hr=fr?fr.toString:r;function dr(t){if(Ms(t)&&!bs(t)&&!(t instanceof br)){if(t instanceof yr)return t;if(fe.call(t,"__wrapped__"))return ha(t)}return new yr(t)}var pr=function(){function t(){}return function(e){if(!Ts(e))return{};if(Ie)return Ie(e);t.prototype=e;var n=new t;return t.prototype=r,n}}();function gr(){}function yr(t,e){this.__wrapped__=t,this.__actions__=[],this.__chain__=!!e,this.__index__=0,this.__values__=r}function br(t){this.__wrapped__=t,this.__actions__=[],this.__dir__=1,this.__filtered__=!1,this.__iteratees__=[],this.__takeCount__=B,this.__views__=[]}function mr(t){var e=-1,n=null==t?0:t.length;for(this.clear();++e=e?t:e)),t}function Br(t,e,n,i,o,a){var s,u=e&f,c=e&l,d=e&h;if(n&&(s=o?n(t,i,o,a):n(t)),s!==r)return s;if(!Ts(t))return t;var p=bs(t);if(p){if(s=function(t){var e=t.length,n=new t.constructor(e);e&&"string"==typeof t[0]&&fe.call(t,"index")&&(n.index=t.index,n.input=t.input);return n}(t),!u)return no(t,s)}else{var g=zo(t),y=g==$||g==G;if(ws(t))return Xi(t,u);if(g==Z||g==q||y&&!o){if(s=c||y?{}:Vo(t),!u)return c?function(t,e){return ro(t,Uo(t),e)}(t,function(t,e){return t&&ro(e,ou(e),t)}(s,t)):function(t,e){return ro(t,jo(t),e)}(t,Or(s,t))}else{if(!Ae[g])return o?t:{};s=function(t,e,n){var r=t.constructor;switch(e){case at:return Zi(t);case z:case Y:return new r(+t);case st:return function(t,e){var n=e?Zi(t.buffer):t.buffer;return new t.constructor(n,t.byteOffset,t.byteLength)}(t,n);case ut:case ct:case ft:case lt:case ht:case dt:case pt:case gt:case yt:return Ji(t,n);case W:return new r;case K:case et:return new r(t);case Q:return function(t){var e=new t.constructor(t.source,Ut.exec(t));return e.lastIndex=t.lastIndex,e}(t);case tt:return new r;case nt:return i=t,lr?ee(lr.call(i)):{}}var i}(t,g,u)}}a||(a=new xr);var b=a.get(t);if(b)return b;a.set(t,s),Is(t)?t.forEach((function(r){s.add(Br(r,e,n,r,t,a))})):Ds(t)&&t.forEach((function(r,i){s.set(i,Br(r,e,n,i,t,a))}));var m=p?r:(d?c?Ro:Oo:c?ou:iu)(t);return He(m||t,(function(r,i){m&&(r=t[i=r]),Mr(s,i,Br(r,e,n,i,t,a))})),s}function Lr(t,e,n){var i=n.length;if(null==t)return!i;for(t=ee(t);i--;){var o=n[i],a=e[o],s=t[o];if(s===r&&!(o in t)||!a(s))return!1}return!0}function Pr(t,e,n){if("function"!=typeof t)throw new ie(a);return ia((function(){t.apply(r,n)}),e)}function Fr(t,e,n,r){var o=-1,a=Ke,s=!0,u=t.length,c=[],f=e.length;if(!u)return c;n&&(e=Ze(e,gn(n))),r?(a=Xe,s=!1):e.length>=i&&(a=bn,s=!1,e=new wr(e));t:for(;++o-1},vr.prototype.set=function(t,e){var n=this.__data__,r=Dr(n,t);return r<0?(++this.size,n.push([t,e])):n[r][1]=e,this},_r.prototype.clear=function(){this.size=0,this.__data__={hash:new mr,map:new(Jn||vr),string:new mr}},_r.prototype.delete=function(t){var e=Po(this,t).delete(t);return this.size-=e?1:0,e},_r.prototype.get=function(t){return Po(this,t).get(t)},_r.prototype.has=function(t){return Po(this,t).has(t)},_r.prototype.set=function(t,e){var n=Po(this,t),r=n.size;return n.set(t,e),this.size+=n.size==r?0:1,this},wr.prototype.add=wr.prototype.push=function(t){return this.__data__.set(t,s),this},wr.prototype.has=function(t){return this.__data__.has(t)},xr.prototype.clear=function(){this.__data__=new vr,this.size=0},xr.prototype.delete=function(t){var e=this.__data__,n=e.delete(t);return this.size=e.size,n},xr.prototype.get=function(t){return this.__data__.get(t)},xr.prototype.has=function(t){return this.__data__.has(t)},xr.prototype.set=function(t,e){var n=this.__data__;if(n instanceof vr){var r=n.__data__;if(!Jn||r.length0&&n(s)?e>1?Vr(s,e-1,n,r,i):Je(i,s):r||(i[i.length]=s)}return i}var Hr=so(),$r=so(!0);function Gr(t,e){return t&&Hr(t,e,iu)}function Wr(t,e){return t&&$r(t,e,iu)}function Kr(t,e){return We(e,(function(e){return Es(t[e])}))}function Xr(t,e){for(var n=0,i=(e=$i(e,t)).length;null!=t&&ne}function ti(t,e){return null!=t&&fe.call(t,e)}function ei(t,e){return null!=t&&e in ee(t)}function ni(t,e,n){for(var i=n?Xe:Ke,o=t[0].length,a=t.length,s=a,u=Xt(a),c=1/0,f=[];s--;){var l=t[s];s&&e&&(l=Ze(l,gn(e))),c=$n(l.length,c),u[s]=!n&&(e||o>=120&&l.length>=120)?new wr(s&&l):r}l=t[0];var h=-1,d=u[0];t:for(;++h=s)return u;var c=n[r];return u*("desc"==c?-1:1)}}return t.index-e.index}(t,e,n)}))}function mi(t,e,n){for(var r=-1,i=e.length,o={};++r-1;)s!==t&&Le.call(s,u,1),Le.call(t,u,1);return t}function _i(t,e){for(var n=t?e.length:0,r=n-1;n--;){var i=e[n];if(n==r||i!==o){var o=i;$o(i)?Le.call(t,i,1):Fi(t,i)}}return t}function wi(t,e){return t+qn(Kn()*(e-t+1))}function xi(t,e){var n="";if(!t||e<1||e>R)return n;do{e%2&&(n+=t),(e=qn(e/2))&&(t+=t)}while(e);return n}function ki(t,e){return oa(ta(t,e,Cu),t+"")}function Ei(t){return Er(du(t))}function Ai(t,e){var n=du(t);return ua(n,Nr(e,0,n.length))}function Si(t,e,n,i){if(!Ts(t))return t;for(var o=-1,a=(e=$i(e,t)).length,s=a-1,u=t;null!=u&&++oi?0:i+e),(n=n>i?i:n)<0&&(n+=i),i=e>n?0:n-e>>>0,e>>>=0;for(var o=Xt(i);++r>>1,a=t[o];null!==a&&!Bs(a)&&(n?a<=e:a=i){var f=e?null:ko(t);if(f)return Tn(f);s=!1,o=bn,c=new wr}else c=e?[]:u;t:for(;++r=i?t:Ci(t,e,n)}var Ki=Bn||function(t){return Oe.clearTimeout(t)};function Xi(t,e){if(e)return t.slice();var n=t.length,r=Ce?Ce(n):new t.constructor(n);return t.copy(r),r}function Zi(t){var e=new t.constructor(t.byteLength);return new De(e).set(new De(t)),e}function Ji(t,e){var n=e?Zi(t.buffer):t.buffer;return new t.constructor(n,t.byteOffset,t.length)}function Qi(t,e){if(t!==e){var n=t!==r,i=null===t,o=t==t,a=Bs(t),s=e!==r,u=null===e,c=e==e,f=Bs(e);if(!u&&!f&&!a&&t>e||a&&s&&c&&!u&&!f||i&&s&&c||!n&&c||!o)return 1;if(!i&&!a&&!f&&t1?n[o-1]:r,s=o>2?n[2]:r;for(a=t.length>3&&"function"==typeof a?(o--,a):r,s&&Go(n[0],n[1],s)&&(a=o<3?r:a,o=1),e=ee(e);++i-1?o[a?e[s]:s]:r}}function ho(t){return Co((function(e){var n=e.length,i=n,o=yr.prototype.thru;for(t&&e.reverse();i--;){var s=e[i];if("function"!=typeof s)throw new ie(a);if(o&&!u&&"wrapper"==No(s))var u=new yr([],!0)}for(i=u?i:n;++i1&&m.reverse(),l&&cu))return!1;var f=a.get(t);if(f&&a.get(e))return f==e;var l=-1,h=!0,g=n&p?new wr:r;for(a.set(t,e),a.set(e,t);++l-1&&t%1==0&&t1?"& ":"")+e[r],e=e.join(n>2?", ":" "),t.replace(Bt,"{\n/* [wrapped with "+e+"] */\n")}(r,function(t,e){return He(F,(function(n){var r="_."+n[0];e&n[1]&&!Ke(t,r)&&t.push(r)})),t.sort()}(function(t){var e=t.match(Lt);return e?e[1].split(Pt):[]}(r),n)))}function sa(t){var e=0,n=0;return function(){var i=Gn(),o=M-(i-n);if(n=i,o>0){if(++e>=T)return arguments[0]}else e=0;return t.apply(r,arguments)}}function ua(t,e){var n=-1,i=t.length,o=i-1;for(e=e===r?i:e;++n1?t[e-1]:r;return n="function"==typeof n?(t.pop(),n):r,Ra(t,n)}));function qa(t){var e=dr(t);return e.__chain__=!0,e}function ja(t,e){return e(t)}var Ua=Co((function(t){var e=t.length,n=e?t[0]:0,i=this.__wrapped__,o=function(e){return Ir(e,t)};return!(e>1||this.__actions__.length)&&i instanceof br&&$o(n)?((i=i.slice(n,+n+(e?1:0))).__actions__.push({func:ja,args:[o],thisArg:r}),new yr(i,this.__chain__).thru((function(t){return e&&!t.length&&t.push(r),t}))):this.thru(o)}));var za=io((function(t,e,n){fe.call(t,n)?++t[n]:Rr(t,n,1)}));var Ya=lo(ya),Va=lo(ba);function Ha(t,e){return(bs(t)?He:qr)(t,Lo(e,3))}function $a(t,e){return(bs(t)?$e:jr)(t,Lo(e,3))}var Ga=io((function(t,e,n){fe.call(t,n)?t[n].push(e):Rr(t,n,[e])}));var Wa=ki((function(t,e,n){var r=-1,i="function"==typeof e,o=vs(t)?Xt(t.length):[];return qr(t,(function(t){o[++r]=i?Ye(e,t,n):ri(t,e,n)})),o})),Ka=io((function(t,e,n){Rr(t,n,e)}));function Xa(t,e){return(bs(t)?Ze:hi)(t,Lo(e,3))}var Za=io((function(t,e,n){t[n?0:1].push(e)}),(function(){return[[],[]]}));var Ja=ki((function(t,e){if(null==t)return[];var n=e.length;return n>1&&Go(t,e[0],e[1])?e=[]:n>2&&Go(e[0],e[1],e[2])&&(e=[e[0]]),bi(t,Vr(e,1),[])})),Qa=Ln||function(){return Oe.Date.now()};function ts(t,e,n){return e=n?r:e,e=t&&null==e?t.length:e,Ao(t,x,r,r,r,r,e)}function es(t,e){var n;if("function"!=typeof e)throw new ie(a);return t=Us(t),function(){return--t>0&&(n=e.apply(this,arguments)),t<=1&&(e=r),n}}var ns=ki((function(t,e,n){var r=g;if(n.length){var i=Sn(n,Bo(ns));r|=_}return Ao(t,r,e,n,i)})),rs=ki((function(t,e,n){var r=g|y;if(n.length){var i=Sn(n,Bo(rs));r|=_}return Ao(e,r,t,n,i)}));function is(t,e,n){var i,o,s,u,c,f,l=0,h=!1,d=!1,p=!0;if("function"!=typeof t)throw new ie(a);function g(e){var n=i,a=o;return i=o=r,l=e,u=t.apply(a,n)}function y(t){var n=t-f;return f===r||n>=e||n<0||d&&t-l>=s}function b(){var t=Qa();if(y(t))return m(t);c=ia(b,function(t){var n=e-(t-f);return d?$n(n,s-(t-l)):n}(t))}function m(t){return c=r,p&&i?g(t):(i=o=r,u)}function v(){var t=Qa(),n=y(t);if(i=arguments,o=this,f=t,n){if(c===r)return function(t){return l=t,c=ia(b,e),h?g(t):u}(f);if(d)return Ki(c),c=ia(b,e),g(f)}return c===r&&(c=ia(b,e)),u}return e=Ys(e)||0,Ts(n)&&(h=!!n.leading,s=(d="maxWait"in n)?Hn(Ys(n.maxWait)||0,e):s,p="trailing"in n?!!n.trailing:p),v.cancel=function(){c!==r&&Ki(c),l=0,i=f=o=c=r},v.flush=function(){return c===r?u:m(Qa())},v}var os=ki((function(t,e){return Pr(t,1,e)})),as=ki((function(t,e,n){return Pr(t,Ys(e)||0,n)}));function ss(t,e){if("function"!=typeof t||null!=e&&"function"!=typeof e)throw new ie(a);var n=function(){var r=arguments,i=e?e.apply(this,r):r[0],o=n.cache;if(o.has(i))return o.get(i);var a=t.apply(this,r);return n.cache=o.set(i,a)||o,a};return n.cache=new(ss.Cache||_r),n}function us(t){if("function"!=typeof t)throw new ie(a);return function(){var e=arguments;switch(e.length){case 0:return!t.call(this);case 1:return!t.call(this,e[0]);case 2:return!t.call(this,e[0],e[1]);case 3:return!t.call(this,e[0],e[1],e[2])}return!t.apply(this,e)}}ss.Cache=_r;var cs=Gi((function(t,e){var n=(e=1==e.length&&bs(e[0])?Ze(e[0],gn(Lo())):Ze(Vr(e,1),gn(Lo()))).length;return ki((function(r){for(var i=-1,o=$n(r.length,n);++i=e})),ys=ii(function(){return arguments}())?ii:function(t){return Ms(t)&&fe.call(t,"callee")&&!Be.call(t,"callee")},bs=Xt.isArray,ms=Pe?gn(Pe):function(t){return Ms(t)&&Jr(t)==at};function vs(t){return null!=t&&Ss(t.length)&&!Es(t)}function _s(t){return Ms(t)&&vs(t)}var ws=Un||Yu,xs=Fe?gn(Fe):function(t){return Ms(t)&&Jr(t)==Y};function ks(t){if(!Ms(t))return!1;var e=Jr(t);return e==H||e==V||"string"==typeof t.message&&"string"==typeof t.name&&!Os(t)}function Es(t){if(!Ts(t))return!1;var e=Jr(t);return e==$||e==G||e==U||e==J}function As(t){return"number"==typeof t&&t==Us(t)}function Ss(t){return"number"==typeof t&&t>-1&&t%1==0&&t<=R}function Ts(t){var e=typeof t;return null!=t&&("object"==e||"function"==e)}function Ms(t){return null!=t&&"object"==typeof t}var Ds=qe?gn(qe):function(t){return Ms(t)&&zo(t)==W};function Cs(t){return"number"==typeof t||Ms(t)&&Jr(t)==K}function Os(t){if(!Ms(t)||Jr(t)!=Z)return!1;var e=Re(t);if(null===e)return!0;var n=fe.call(e,"constructor")&&e.constructor;return"function"==typeof n&&n instanceof n&&ce.call(n)==pe}var Rs=je?gn(je):function(t){return Ms(t)&&Jr(t)==Q};var Is=Ue?gn(Ue):function(t){return Ms(t)&&zo(t)==tt};function Ns(t){return"string"==typeof t||!bs(t)&&Ms(t)&&Jr(t)==et}function Bs(t){return"symbol"==typeof t||Ms(t)&&Jr(t)==nt}var Ls=ze?gn(ze):function(t){return Ms(t)&&Ss(t.length)&&!!Ee[Jr(t)]};var Ps=_o(li),Fs=_o((function(t,e){return t<=e}));function qs(t){if(!t)return[];if(vs(t))return Ns(t)?Cn(t):no(t);if(ln&&t[ln])return function(t){for(var e,n=[];!(e=t.next()).done;)n.push(e.value);return n}(t[ln]());var e=zo(t);return(e==W?En:e==tt?Tn:du)(t)}function js(t){return t?(t=Ys(t))===O||t===-O?(t<0?-1:1)*I:t==t?t:0:0===t?t:0}function Us(t){var e=js(t),n=e%1;return e==e?n?e-n:e:0}function zs(t){return t?Nr(Us(t),0,B):0}function Ys(t){if("number"==typeof t)return t;if(Bs(t))return N;if(Ts(t)){var e="function"==typeof t.valueOf?t.valueOf():t;t=Ts(e)?e+"":e}if("string"!=typeof t)return 0===t?t:+t;t=t.replace(Rt,"");var n=Yt.test(t);return n||Ht.test(t)?Me(t.slice(2),n?2:8):zt.test(t)?N:+t}function Vs(t){return ro(t,ou(t))}function Hs(t){return null==t?"":Li(t)}var $s=oo((function(t,e){if(Zo(e)||vs(e))ro(e,iu(e),t);else for(var n in e)fe.call(e,n)&&Mr(t,n,e[n])})),Gs=oo((function(t,e){ro(e,ou(e),t)})),Ws=oo((function(t,e,n,r){ro(e,ou(e),t,r)})),Ks=oo((function(t,e,n,r){ro(e,iu(e),t,r)})),Xs=Co(Ir);var Zs=ki((function(t,e){t=ee(t);var n=-1,i=e.length,o=i>2?e[2]:r;for(o&&Go(e[0],e[1],o)&&(i=1);++n1),e})),ro(t,Ro(t),n),r&&(n=Br(n,f|l|h,Mo));for(var i=e.length;i--;)Fi(n,e[i]);return n}));var cu=Co((function(t,e){return null==t?{}:function(t,e){return mi(t,e,(function(e,n){return tu(t,n)}))}(t,e)}));function fu(t,e){if(null==t)return{};var n=Ze(Ro(t),(function(t){return[t]}));return e=Lo(e),mi(t,n,(function(t,n){return e(t,n[0])}))}var lu=Eo(iu),hu=Eo(ou);function du(t){return null==t?[]:yn(t,iu(t))}var pu=co((function(t,e,n){return e=e.toLowerCase(),t+(n?gu(e):e)}));function gu(t){return ku(Hs(t).toLowerCase())}function yu(t){return(t=Hs(t))&&t.replace(Gt,_n).replace(be,"")}var bu=co((function(t,e,n){return t+(n?"-":"")+e.toLowerCase()})),mu=co((function(t,e,n){return t+(n?" ":"")+e.toLowerCase()})),vu=uo("toLowerCase");var _u=co((function(t,e,n){return t+(n?"_":"")+e.toLowerCase()}));var wu=co((function(t,e,n){return t+(n?" ":"")+ku(e)}));var xu=co((function(t,e,n){return t+(n?" ":"")+e.toUpperCase()})),ku=uo("toUpperCase");function Eu(t,e,n){return t=Hs(t),(e=n?r:e)===r?function(t){return we.test(t)}(t)?function(t){return t.match(ve)||[]}(t):function(t){return t.match(Ft)||[]}(t):t.match(e)||[]}var Au=ki((function(t,e){try{return Ye(t,r,e)}catch(t){return ks(t)?t:new Jt(t)}})),Su=Co((function(t,e){return He(e,(function(e){e=fa(e),Rr(t,e,ns(t[e],t))})),t}));function Tu(t){return function(){return t}}var Mu=ho(),Du=ho(!0);function Cu(t){return t}function Ou(t){return ui("function"==typeof t?t:Br(t,f))}var Ru=ki((function(t,e){return function(n){return ri(n,t,e)}})),Iu=ki((function(t,e){return function(n){return ri(t,n,e)}}));function Nu(t,e,n){var r=iu(e),i=Kr(e,r);null!=n||Ts(e)&&(i.length||!r.length)||(n=e,e=t,t=this,i=Kr(e,iu(e)));var o=!(Ts(n)&&"chain"in n&&!n.chain),a=Es(t);return He(i,(function(n){var r=e[n];t[n]=r,a&&(t.prototype[n]=function(){var e=this.__chain__;if(o||e){var n=t(this.__wrapped__),i=n.__actions__=no(this.__actions__);return i.push({func:r,args:arguments,thisArg:t}),n.__chain__=e,n}return r.apply(t,Je([this.value()],arguments))})})),t}function Bu(){}var Lu=bo(Ze),Pu=bo(Ge),Fu=bo(en);function qu(t){return Wo(t)?fn(fa(t)):function(t){return function(e){return Xr(e,t)}}(t)}var ju=vo(),Uu=vo(!0);function zu(){return[]}function Yu(){return!1}var Vu=yo((function(t,e){return t+e}),0),Hu=xo("ceil"),$u=yo((function(t,e){return t/e}),1),Gu=xo("floor");var Wu,Ku=yo((function(t,e){return t*e}),1),Xu=xo("round"),Zu=yo((function(t,e){return t-e}),0);return dr.after=function(t,e){if("function"!=typeof e)throw new ie(a);return t=Us(t),function(){if(--t<1)return e.apply(this,arguments)}},dr.ary=ts,dr.assign=$s,dr.assignIn=Gs,dr.assignInWith=Ws,dr.assignWith=Ks,dr.at=Xs,dr.before=es,dr.bind=ns,dr.bindAll=Su,dr.bindKey=rs,dr.castArray=function(){if(!arguments.length)return[];var t=arguments[0];return bs(t)?t:[t]},dr.chain=qa,dr.chunk=function(t,e,n){e=(n?Go(t,e,n):e===r)?1:Hn(Us(e),0);var i=null==t?0:t.length;if(!i||e<1)return[];for(var o=0,a=0,s=Xt(Fn(i/e));oo?0:o+n),(i=i===r||i>o?o:Us(i))<0&&(i+=o),i=n>i?0:zs(i);n>>0)?(t=Hs(t))&&("string"==typeof e||null!=e&&!Rs(e))&&!(e=Li(e))&&kn(t)?Wi(Cn(t),0,n):t.split(e,n):[]},dr.spread=function(t,e){if("function"!=typeof t)throw new ie(a);return e=null==e?0:Hn(Us(e),0),ki((function(n){var r=n[e],i=Wi(n,0,e);return r&&Je(i,r),Ye(t,this,i)}))},dr.tail=function(t){var e=null==t?0:t.length;return e?Ci(t,1,e):[]},dr.take=function(t,e,n){return t&&t.length?Ci(t,0,(e=n||e===r?1:Us(e))<0?0:e):[]},dr.takeRight=function(t,e,n){var i=null==t?0:t.length;return i?Ci(t,(e=i-(e=n||e===r?1:Us(e)))<0?0:e,i):[]},dr.takeRightWhile=function(t,e){return t&&t.length?ji(t,Lo(e,3),!1,!0):[]},dr.takeWhile=function(t,e){return t&&t.length?ji(t,Lo(e,3)):[]},dr.tap=function(t,e){return e(t),t},dr.throttle=function(t,e,n){var r=!0,i=!0;if("function"!=typeof t)throw new ie(a);return Ts(n)&&(r="leading"in n?!!n.leading:r,i="trailing"in n?!!n.trailing:i),is(t,e,{leading:r,maxWait:e,trailing:i})},dr.thru=ja,dr.toArray=qs,dr.toPairs=lu,dr.toPairsIn=hu,dr.toPath=function(t){return bs(t)?Ze(t,fa):Bs(t)?[t]:no(ca(Hs(t)))},dr.toPlainObject=Vs,dr.transform=function(t,e,n){var r=bs(t),i=r||ws(t)||Ls(t);if(e=Lo(e,4),null==n){var o=t&&t.constructor;n=i?r?new o:[]:Ts(t)&&Es(o)?pr(Re(t)):{}}return(i?He:Gr)(t,(function(t,r,i){return e(n,t,r,i)})),n},dr.unary=function(t){return ts(t,1)},dr.union=Ma,dr.unionBy=Da,dr.unionWith=Ca,dr.uniq=function(t){return t&&t.length?Pi(t):[]},dr.uniqBy=function(t,e){return t&&t.length?Pi(t,Lo(e,2)):[]},dr.uniqWith=function(t,e){return e="function"==typeof e?e:r,t&&t.length?Pi(t,r,e):[]},dr.unset=function(t,e){return null==t||Fi(t,e)},dr.unzip=Oa,dr.unzipWith=Ra,dr.update=function(t,e,n){return null==t?t:qi(t,e,Hi(n))},dr.updateWith=function(t,e,n,i){return i="function"==typeof i?i:r,null==t?t:qi(t,e,Hi(n),i)},dr.values=du,dr.valuesIn=function(t){return null==t?[]:yn(t,ou(t))},dr.without=Ia,dr.words=Eu,dr.wrap=function(t,e){return fs(Hi(e),t)},dr.xor=Na,dr.xorBy=Ba,dr.xorWith=La,dr.zip=Pa,dr.zipObject=function(t,e){return Yi(t||[],e||[],Mr)},dr.zipObjectDeep=function(t,e){return Yi(t||[],e||[],Si)},dr.zipWith=Fa,dr.entries=lu,dr.entriesIn=hu,dr.extend=Gs,dr.extendWith=Ws,Nu(dr,dr),dr.add=Vu,dr.attempt=Au,dr.camelCase=pu,dr.capitalize=gu,dr.ceil=Hu,dr.clamp=function(t,e,n){return n===r&&(n=e,e=r),n!==r&&(n=(n=Ys(n))==n?n:0),e!==r&&(e=(e=Ys(e))==e?e:0),Nr(Ys(t),e,n)},dr.clone=function(t){return Br(t,h)},dr.cloneDeep=function(t){return Br(t,f|h)},dr.cloneDeepWith=function(t,e){return Br(t,f|h,e="function"==typeof e?e:r)},dr.cloneWith=function(t,e){return Br(t,h,e="function"==typeof e?e:r)},dr.conformsTo=function(t,e){return null==e||Lr(t,e,iu(e))},dr.deburr=yu,dr.defaultTo=function(t,e){return null==t||t!=t?e:t},dr.divide=$u,dr.endsWith=function(t,e,n){t=Hs(t),e=Li(e);var i=t.length,o=n=n===r?i:Nr(Us(n),0,i);return(n-=e.length)>=0&&t.slice(n,o)==e},dr.eq=ds,dr.escape=function(t){return(t=Hs(t))&&kt.test(t)?t.replace(wt,wn):t},dr.escapeRegExp=function(t){return(t=Hs(t))&&Ot.test(t)?t.replace(Ct,"\\$&"):t},dr.every=function(t,e,n){var i=bs(t)?Ge:Ur;return n&&Go(t,e,n)&&(e=r),i(t,Lo(e,3))},dr.find=Ya,dr.findIndex=ya,dr.findKey=function(t,e){return rn(t,Lo(e,3),Gr)},dr.findLast=Va,dr.findLastIndex=ba,dr.findLastKey=function(t,e){return rn(t,Lo(e,3),Wr)},dr.floor=Gu,dr.forEach=Ha,dr.forEachRight=$a,dr.forIn=function(t,e){return null==t?t:Hr(t,Lo(e,3),ou)},dr.forInRight=function(t,e){return null==t?t:$r(t,Lo(e,3),ou)},dr.forOwn=function(t,e){return t&&Gr(t,Lo(e,3))},dr.forOwnRight=function(t,e){return t&&Wr(t,Lo(e,3))},dr.get=Qs,dr.gt=ps,dr.gte=gs,dr.has=function(t,e){return null!=t&&Yo(t,e,ti)},dr.hasIn=tu,dr.head=va,dr.identity=Cu,dr.includes=function(t,e,n,r){t=vs(t)?t:du(t),n=n&&!r?Us(n):0;var i=t.length;return n<0&&(n=Hn(i+n,0)),Ns(t)?n<=i&&t.indexOf(e,n)>-1:!!i&&an(t,e,n)>-1},dr.indexOf=function(t,e,n){var r=null==t?0:t.length;if(!r)return-1;var i=null==n?0:Us(n);return i<0&&(i=Hn(r+i,0)),an(t,e,i)},dr.inRange=function(t,e,n){return e=js(e),n===r?(n=e,e=0):n=js(n),function(t,e,n){return t>=$n(e,n)&&t=-R&&t<=R},dr.isSet=Is,dr.isString=Ns,dr.isSymbol=Bs,dr.isTypedArray=Ls,dr.isUndefined=function(t){return t===r},dr.isWeakMap=function(t){return Ms(t)&&zo(t)==it},dr.isWeakSet=function(t){return Ms(t)&&Jr(t)==ot},dr.join=function(t,e){return null==t?"":Yn.call(t,e)},dr.kebabCase=bu,dr.last=ka,dr.lastIndexOf=function(t,e,n){var i=null==t?0:t.length;if(!i)return-1;var o=i;return n!==r&&(o=(o=Us(n))<0?Hn(i+o,0):$n(o,i-1)),e==e?function(t,e,n){for(var r=n+1;r--;)if(t[r]===e)return r;return r}(t,e,o):on(t,un,o,!0)},dr.lowerCase=mu,dr.lowerFirst=vu,dr.lt=Ps,dr.lte=Fs,dr.max=function(t){return t&&t.length?zr(t,Cu,Qr):r},dr.maxBy=function(t,e){return t&&t.length?zr(t,Lo(e,2),Qr):r},dr.mean=function(t){return cn(t,Cu)},dr.meanBy=function(t,e){return cn(t,Lo(e,2))},dr.min=function(t){return t&&t.length?zr(t,Cu,li):r},dr.minBy=function(t,e){return t&&t.length?zr(t,Lo(e,2),li):r},dr.stubArray=zu,dr.stubFalse=Yu,dr.stubObject=function(){return{}},dr.stubString=function(){return""},dr.stubTrue=function(){return!0},dr.multiply=Ku,dr.nth=function(t,e){return t&&t.length?yi(t,Us(e)):r},dr.noConflict=function(){return Oe._===this&&(Oe._=ge),this},dr.noop=Bu,dr.now=Qa,dr.pad=function(t,e,n){t=Hs(t);var r=(e=Us(e))?Dn(t):0;if(!e||r>=e)return t;var i=(e-r)/2;return mo(qn(i),n)+t+mo(Fn(i),n)},dr.padEnd=function(t,e,n){t=Hs(t);var r=(e=Us(e))?Dn(t):0;return e&&re){var i=t;t=e,e=i}if(n||t%1||e%1){var o=Kn();return $n(t+o*(e-t+Te("1e-"+((o+"").length-1))),e)}return wi(t,e)},dr.reduce=function(t,e,n){var r=bs(t)?Qe:hn,i=arguments.length<3;return r(t,Lo(e,4),n,i,qr)},dr.reduceRight=function(t,e,n){var r=bs(t)?tn:hn,i=arguments.length<3;return r(t,Lo(e,4),n,i,jr)},dr.repeat=function(t,e,n){return e=(n?Go(t,e,n):e===r)?1:Us(e),xi(Hs(t),e)},dr.replace=function(){var t=arguments,e=Hs(t[0]);return t.length<3?e:e.replace(t[1],t[2])},dr.result=function(t,e,n){var i=-1,o=(e=$i(e,t)).length;for(o||(o=1,t=r);++iR)return[];var n=B,r=$n(t,B);e=Lo(e),t-=B;for(var i=pn(r,e);++n=a)return t;var u=n-Dn(i);if(u<1)return i;var c=s?Wi(s,0,u).join(""):t.slice(0,u);if(o===r)return c+i;if(s&&(u+=c.length-u),Rs(o)){if(t.slice(u).search(o)){var f,l=c;for(o.global||(o=ne(o.source,Hs(Ut.exec(o))+"g")),o.lastIndex=0;f=o.exec(l);)var h=f.index;c=c.slice(0,h===r?u:h)}}else if(t.indexOf(Li(o),u)!=u){var d=c.lastIndexOf(o);d>-1&&(c=c.slice(0,d))}return c+i},dr.unescape=function(t){return(t=Hs(t))&&xt.test(t)?t.replace(_t,On):t},dr.uniqueId=function(t){var e=++le;return Hs(t)+e},dr.upperCase=xu,dr.upperFirst=ku,dr.each=Ha,dr.eachRight=$a,dr.first=va,Nu(dr,(Wu={},Gr(dr,(function(t,e){fe.call(dr.prototype,e)||(Wu[e]=t)})),Wu),{chain:!1}),dr.VERSION="4.17.15",He(["bind","bindKey","curry","curryRight","partial","partialRight"],(function(t){dr[t].placeholder=dr})),He(["drop","take"],(function(t,e){br.prototype[t]=function(n){n=n===r?1:Hn(Us(n),0);var i=this.__filtered__&&!e?new br(this):this.clone();return i.__filtered__?i.__takeCount__=$n(n,i.__takeCount__):i.__views__.push({size:$n(n,B),type:t+(i.__dir__<0?"Right":"")}),i},br.prototype[t+"Right"]=function(e){return this.reverse()[t](e).reverse()}})),He(["filter","map","takeWhile"],(function(t,e){var n=e+1,r=n==D||3==n;br.prototype[t]=function(t){var e=this.clone();return e.__iteratees__.push({iteratee:Lo(t,3),type:n}),e.__filtered__=e.__filtered__||r,e}})),He(["head","last"],(function(t,e){var n="take"+(e?"Right":"");br.prototype[t]=function(){return this[n](1).value()[0]}})),He(["initial","tail"],(function(t,e){var n="drop"+(e?"":"Right");br.prototype[t]=function(){return this.__filtered__?new br(this):this[n](1)}})),br.prototype.compact=function(){return this.filter(Cu)},br.prototype.find=function(t){return this.filter(t).head()},br.prototype.findLast=function(t){return this.reverse().find(t)},br.prototype.invokeMap=ki((function(t,e){return"function"==typeof t?new br(this):this.map((function(n){return ri(n,t,e)}))})),br.prototype.reject=function(t){return this.filter(us(Lo(t)))},br.prototype.slice=function(t,e){t=Us(t);var n=this;return n.__filtered__&&(t>0||e<0)?new br(n):(t<0?n=n.takeRight(-t):t&&(n=n.drop(t)),e!==r&&(n=(e=Us(e))<0?n.dropRight(-e):n.take(e-t)),n)},br.prototype.takeRightWhile=function(t){return this.reverse().takeWhile(t).reverse()},br.prototype.toArray=function(){return this.take(B)},Gr(br.prototype,(function(t,e){var n=/^(?:filter|find|map|reject)|While$/.test(e),i=/^(?:head|last)$/.test(e),o=dr[i?"take"+("last"==e?"Right":""):e],a=i||/^find/.test(e);o&&(dr.prototype[e]=function(){var e=this.__wrapped__,s=i?[1]:arguments,u=e instanceof br,c=s[0],f=u||bs(e),l=function(t){var e=o.apply(dr,Je([t],s));return i&&h?e[0]:e};f&&n&&"function"==typeof c&&1!=c.length&&(u=f=!1);var h=this.__chain__,d=!!this.__actions__.length,p=a&&!h,g=u&&!d;if(!a&&f){e=g?e:new br(this);var y=t.apply(e,s);return y.__actions__.push({func:ja,args:[l],thisArg:r}),new yr(y,h)}return p&&g?t.apply(this,s):(y=this.thru(l),p?i?y.value()[0]:y.value():y)})})),He(["pop","push","shift","sort","splice","unshift"],(function(t){var e=oe[t],n=/^(?:push|sort|unshift)$/.test(t)?"tap":"thru",r=/^(?:pop|shift)$/.test(t);dr.prototype[t]=function(){var t=arguments;if(r&&!this.__chain__){var i=this.value();return e.apply(bs(i)?i:[],t)}return this[n]((function(n){return e.apply(bs(n)?n:[],t)}))}})),Gr(br.prototype,(function(t,e){var n=dr[e];if(n){var r=n.name+"";fe.call(ir,r)||(ir[r]=[]),ir[r].push({name:e,func:n})}})),ir[po(r,y).name]=[{name:"wrapper",func:r}],br.prototype.clone=function(){var t=new br(this.__wrapped__);return t.__actions__=no(this.__actions__),t.__dir__=this.__dir__,t.__filtered__=this.__filtered__,t.__iteratees__=no(this.__iteratees__),t.__takeCount__=this.__takeCount__,t.__views__=no(this.__views__),t},br.prototype.reverse=function(){if(this.__filtered__){var t=new br(this);t.__dir__=-1,t.__filtered__=!0}else(t=this.clone()).__dir__*=-1;return t},br.prototype.value=function(){var t=this.__wrapped__.value(),e=this.__dir__,n=bs(t),r=e<0,i=n?t.length:0,o=function(t,e,n){var r=-1,i=n.length;for(;++r=this.__values__.length;return{done:t,value:t?r:this.__values__[this.__index__++]}},dr.prototype.plant=function(t){for(var e,n=this;n instanceof gr;){var i=ha(n);i.__index__=0,i.__values__=r,e?o.__wrapped__=i:e=i;var o=i;n=n.__wrapped__}return o.__wrapped__=t,e},dr.prototype.reverse=function(){var t=this.__wrapped__;if(t instanceof br){var e=t;return this.__actions__.length&&(e=new br(this)),(e=e.reverse()).__actions__.push({func:ja,args:[Ta],thisArg:r}),new yr(e,this.__chain__)}return this.thru(Ta)},dr.prototype.toJSON=dr.prototype.valueOf=dr.prototype.value=function(){return Ui(this.__wrapped__,this.__actions__)},dr.prototype.first=dr.prototype.head,ln&&(dr.prototype[ln]=function(){return this}),dr}();"function"==typeof define&&"object"==typeof define.amd&&define.amd?(Oe._=Rn,define((function(){return Rn}))):Ie?((Ie.exports=Rn)._=Rn,Re._=Rn):Oe._=Rn}).call(this)}).call(this,n(11),n(9)(t))},function(t,e,n){var r=n(66),i=n(67);t.exports=function(t,e,n,o){var a=!n;n||(n={});for(var s=-1,u=e.length;++s=this._delta8){var n=(t=this.pending).length%this._delta8;this.pending=t.slice(t.length-n,t.length),0===this.pending.length&&(this.pending=null),t=r.join32(t,0,t.length-n,this.endian);for(var i=0;i>>24&255,r[i++]=t>>>16&255,r[i++]=t>>>8&255,r[i++]=255&t}else for(r[i++]=255&t,r[i++]=t>>>8&255,r[i++]=t>>>16&255,r[i++]=t>>>24&255,r[i++]=0,r[i++]=0,r[i++]=0,r[i++]=0,o=8;ol&&M.push("'"+this.terminals_[A]+"'");C=p.showPosition?"Parse error on line "+(u+1)+":\n"+p.showPosition()+"\nExpecting "+M.join(", ")+", got '"+(this.terminals_[_]||_)+"'":"Parse error on line "+(u+1)+": Unexpected "+(_==h?"end of input":"'"+(this.terminals_[_]||_)+"'"),this.parseError(C,{text:p.match,token:this.terminals_[_]||_,line:p.yylineno,loc:b,expected:M})}if(k[0]instanceof Array&&k.length>1)throw new Error("Parse Error: multiple actions possible at state: "+x+", token: "+_);switch(k[0]){case 1:n.push(_),i.push(p.yytext),o.push(p.yylloc),n.push(k[1]),_=null,w?(_=w,w=null):(c=p.yyleng,s=p.yytext,u=p.yylineno,b=p.yylloc,f>0&&f--);break;case 2:if(S=this.productions_[k[1]][1],D.$=i[i.length-S],D._$={first_line:o[o.length-(S||1)].first_line,last_line:o[o.length-1].last_line,first_column:o[o.length-(S||1)].first_column,last_column:o[o.length-1].last_column},m&&(D._$.range=[o[o.length-(S||1)].range[0],o[o.length-1].range[1]]),void 0!==(E=this.performAction.apply(D,[s,c,u,g.yy,k[1],i,o].concat(d))))return E;S&&(n=n.slice(0,-1*S*2),i=i.slice(0,-1*S),o=o.slice(0,-1*S)),n.push(this.productions_[k[1]][0]),i.push(D.$),o.push(D._$),T=a[n[n.length-2]][n[n.length-1]],n.push(T);break;case 3:return!0}}return!0}},Ut={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var o in i)this[o]=i[o];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),o=0;oe[0].length)){if(e=n,r=o,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[o])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var t=this.next();return t||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{},performAction:function(t,e,n,r){switch(n){case 0:break;case 1:this.begin("string");break;case 2:this.popState();break;case 3:return"STR";case 4:return 89;case 5:return 98;case 6:return 90;case 7:return 103;case 8:return 91;case 9:return 92;case 10:return 93;case 11:return t.lex.firstGraph()&&this.begin("dir"),12;case 12:return 26;case 13:return 30;case 14:case 15:case 16:case 17:case 18:case 19:case 20:case 21:case 22:case 23:return this.popState(),13;case 24:return 106;case 25:return 114;case 26:return 34;case 27:return 111;case 28:return 8;case 29:return 107;case 30:return 125;case 31:return 55;case 32:return 51;case 33:return 74;case 34:return 76;case 35:return 75;case 36:return 78;case 37:return 80;case 38:return 81;case 39:return 82;case 40:case 41:return 79;case 42:case 43:return 77;case 44:return 78;case 45:return 53;case 46:return 57;case 47:return 63;case 48:return 59;case 49:return 61;case 50:return 65;case 51:return 63;case 52:return 59;case 53:return 61;case 54:return 65;case 55:return 71;case 56:return 67;case 57:return 69;case 58:return 73;case 59:return 52;case 60:return 56;case 61:return 54;case 62:return 60;case 63:return 64;case 64:return 62;case 65:return 68;case 66:return 72;case 67:return 70;case 68:return 50;case 69:return 58;case 70:return 66;case 71:return 38;case 72:return 39;case 73:return 112;case 74:return 115;case 75:return 126;case 76:return 123;case 77:return 105;case 78:case 79:return 124;case 80:return 117;case 81:return 42;case 82:return 95;case 83:return 94;case 84:return 110;case 85:return 44;case 86:return 43;case 87:return 46;case 88:return 45;case 89:return 121;case 90:return 122;case 91:return 83;case 92:return 36;case 93:return 37;case 94:return 28;case 95:return 29;case 96:return 40;case 97:return 41;case 98:return 127;case 99:return 9;case 100:return 10;case 101:return 11}},rules:[/^(?:%%[^\n]*)/,/^(?:["])/,/^(?:["])/,/^(?:[^"]*)/,/^(?:style\b)/,/^(?:default\b)/,/^(?:linkStyle\b)/,/^(?:interpolate\b)/,/^(?:classDef\b)/,/^(?:class\b)/,/^(?:click\b)/,/^(?:graph\b)/,/^(?:subgraph\b)/,/^(?:end\b\s*)/,/^(?:\s*LR\b)/,/^(?:\s*RL\b)/,/^(?:\s*TB\b)/,/^(?:\s*BT\b)/,/^(?:\s*TD\b)/,/^(?:\s*BR\b)/,/^(?:\s*<)/,/^(?:\s*>)/,/^(?:\s*\^)/,/^(?:\s*v\b)/,/^(?:[0-9]+)/,/^(?:#)/,/^(?::::)/,/^(?::)/,/^(?:;)/,/^(?:,)/,/^(?:\*)/,/^(?:\s*--[x]\s*)/,/^(?:\s*-->\s*)/,/^(?:\s*<-->\s*)/,/^(?:\s*[x]--[x]\s*)/,/^(?:\s*[o]--[o]\s*)/,/^(?:\s*[o]\.-[o]\s*)/,/^(?:\s*<==>\s*)/,/^(?:\s*[o]==[o]\s*)/,/^(?:\s*[x]==[x]\s*)/,/^(?:\s*[x].-[x]\s*)/,/^(?:\s*[x]-\.-[x]\s*)/,/^(?:\s*<\.->\s*)/,/^(?:\s*<-\.->\s*)/,/^(?:\s*[o]-\.-[o]\s*)/,/^(?:\s*--[o]\s*)/,/^(?:\s*---\s*)/,/^(?:\s*-\.-[x]\s*)/,/^(?:\s*-\.->\s*)/,/^(?:\s*-\.-[o]\s*)/,/^(?:\s*-\.-\s*)/,/^(?:\s*.-[x]\s*)/,/^(?:\s*\.->\s*)/,/^(?:\s*\.-[o]\s*)/,/^(?:\s*\.-\s*)/,/^(?:\s*==[x]\s*)/,/^(?:\s*==>\s*)/,/^(?:\s*==[o]\s*)/,/^(?:\s*==[\=]\s*)/,/^(?:\s*<--\s*)/,/^(?:\s*[x]--\s*)/,/^(?:\s*[o]--\s*)/,/^(?:\s*<-\.\s*)/,/^(?:\s*[x]-\.\s*)/,/^(?:\s*[o]-\.\s*)/,/^(?:\s*<==\s*)/,/^(?:\s*[x]==\s*)/,/^(?:\s*[o]==\s*)/,/^(?:\s*--\s*)/,/^(?:\s*-\.\s*)/,/^(?:\s*==\s*)/,/^(?:\(-)/,/^(?:-\))/,/^(?:-)/,/^(?:\.)/,/^(?:[\_])/,/^(?:\+)/,/^(?:%)/,/^(?:=)/,/^(?:=)/,/^(?:<)/,/^(?:>)/,/^(?:\^)/,/^(?:v\b)/,/^(?:[A-Za-z]+)/,/^(?:\\\])/,/^(?:\[\/)/,/^(?:\/\])/,/^(?:\[\\)/,/^(?:[!"#$%&'*+,-.`?\\_\/])/,/^(?:[\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6]|[\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377]|[\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5]|[\u03F7-\u0481\u048A-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA]|[\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE]|[\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA]|[\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0]|[\u08A2-\u08AC\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0977]|[\u0979-\u097F\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2]|[\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A]|[\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39]|[\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8]|[\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C]|[\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C]|[\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99]|[\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0]|[\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3D]|[\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3]|[\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10]|[\u0D12-\u0D3A\u0D3D\u0D4E\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1]|[\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81]|[\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3]|[\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6]|[\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A]|[\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081]|[\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D]|[\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0]|[\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310]|[\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C]|[\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u1700-\u170C\u170E-\u1711]|[\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7]|[\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191C]|[\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16]|[\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF]|[\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC]|[\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D]|[\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D]|[\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3]|[\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F]|[\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128]|[\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184]|[\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3]|[\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6]|[\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE]|[\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C]|[\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D]|[\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC]|[\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B]|[\uA640-\uA66E\uA67F-\uA697\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788]|[\uA78B-\uA78E\uA790-\uA793\uA7A0-\uA7AA\uA7F8-\uA801\uA803-\uA805]|[\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB]|[\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uAA00-\uAA28]|[\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA80-\uAAAF\uAAB1\uAAB5]|[\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4]|[\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E]|[\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D]|[\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36]|[\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D]|[\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC]|[\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF]|[\uFFD2-\uFFD7\uFFDA-\uFFDC])/,/^(?:\|)/,/^(?:\()/,/^(?:\))/,/^(?:\[)/,/^(?:\])/,/^(?:\{)/,/^(?:\})/,/^(?:")/,/^(?:(\r|\n|\r\n)+)/,/^(?:\s)/,/^(?:$)/],conditions:{dir:{rules:[14,15,16,17,18,19,20,21,22,23],inclusive:!1},string:{rules:[2,3],inclusive:!1},INITIAL:{rules:[0,1,4,5,6,7,8,9,10,11,12,13,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101],inclusive:!0}}};function zt(){this.yy={}}return jt.lexer=Ut,zt.prototype=jt,jt.Parser=zt,new zt}();e.parser=i,e.Parser=i.Parser,e.parse=function(){return i.parse.apply(i,arguments)},e.main=function(r){r[1]||(console.log("Usage: "+r[0]+" FILE"),t.exit(1));var i=n(28).readFileSync(n(29).normalize(r[1]),"utf8");return e.parser.parse(i)},n.c[n.s]===r&&e.main(t.argv.slice(1))}).call(this,n(7),n(9)(t))},function(t,e,n){var r=n(62),i=n(241),o=n(242),a=n(243),s=n(244),u=n(245);function c(t){var e=this.__data__=new r(t);this.size=e.size}c.prototype.clear=i,c.prototype.delete=o,c.prototype.get=a,c.prototype.has=s,c.prototype.set=u,t.exports=c},function(t,e,n){var r=n(236),i=n(237),o=n(238),a=n(239),s=n(240);function u(t){var e=-1,n=null==t?0:t.length;for(this.clear();++e-1&&t%1==0&&t>>24]^f[p>>>16&255]^l[g>>>8&255]^h[255&y]^e[b++],a=c[p>>>24]^f[g>>>16&255]^l[y>>>8&255]^h[255&d]^e[b++],s=c[g>>>24]^f[y>>>16&255]^l[d>>>8&255]^h[255&p]^e[b++],u=c[y>>>24]^f[d>>>16&255]^l[p>>>8&255]^h[255&g]^e[b++],d=o,p=a,g=s,y=u;return o=(r[d>>>24]<<24|r[p>>>16&255]<<16|r[g>>>8&255]<<8|r[255&y])^e[b++],a=(r[p>>>24]<<24|r[g>>>16&255]<<16|r[y>>>8&255]<<8|r[255&d])^e[b++],s=(r[g>>>24]<<24|r[y>>>16&255]<<16|r[d>>>8&255]<<8|r[255&p])^e[b++],u=(r[y>>>24]<<24|r[d>>>16&255]<<16|r[p>>>8&255]<<8|r[255&g])^e[b++],[o>>>=0,a>>>=0,s>>>=0,u>>>=0]}var s=[0,1,2,4,8,16,32,64,128,27,54],u=function(){for(var t=new Array(256),e=0;e<256;e++)t[e]=e<128?e<<1:e<<1^283;for(var n=[],r=[],i=[[],[],[],[]],o=[[],[],[],[]],a=0,s=0,u=0;u<256;++u){var c=s^s<<1^s<<2^s<<3^s<<4;c=c>>>8^255&c^99,n[a]=c,r[c]=a;var f=t[a],l=t[f],h=t[l],d=257*t[c]^16843008*c;i[0][a]=d<<24|d>>>8,i[1][a]=d<<16|d>>>16,i[2][a]=d<<8|d>>>24,i[3][a]=d,d=16843009*h^65537*l^257*f^16843008*a,o[0][c]=d<<24|d>>>8,o[1][c]=d<<16|d>>>16,o[2][c]=d<<8|d>>>24,o[3][c]=d,0===a?a=s=1:(a=f^t[t[t[h^f]]],s^=t[t[s]])}return{SBOX:n,INV_SBOX:r,SUB_MIX:i,INV_SUB_MIX:o}}();function c(t){this._key=i(t),this._reset()}c.blockSize=16,c.keySize=32,c.prototype.blockSize=c.blockSize,c.prototype.keySize=c.keySize,c.prototype._reset=function(){for(var t=this._key,e=t.length,n=e+6,r=4*(n+1),i=[],o=0;o>>24,a=u.SBOX[a>>>24]<<24|u.SBOX[a>>>16&255]<<16|u.SBOX[a>>>8&255]<<8|u.SBOX[255&a],a^=s[o/e|0]<<24):e>6&&o%e==4&&(a=u.SBOX[a>>>24]<<24|u.SBOX[a>>>16&255]<<16|u.SBOX[a>>>8&255]<<8|u.SBOX[255&a]),i[o]=i[o-e]^a}for(var c=[],f=0;f>>24]]^u.INV_SUB_MIX[1][u.SBOX[h>>>16&255]]^u.INV_SUB_MIX[2][u.SBOX[h>>>8&255]]^u.INV_SUB_MIX[3][u.SBOX[255&h]]}this._nRounds=n,this._keySchedule=i,this._invKeySchedule=c},c.prototype.encryptBlockRaw=function(t){return a(t=i(t),this._keySchedule,u.SUB_MIX,u.SBOX,this._nRounds)},c.prototype.encryptBlock=function(t){var e=this.encryptBlockRaw(t),n=r.allocUnsafe(16);return n.writeUInt32BE(e[0],0),n.writeUInt32BE(e[1],4),n.writeUInt32BE(e[2],8),n.writeUInt32BE(e[3],12),n},c.prototype.decryptBlock=function(t){var e=(t=i(t))[1];t[1]=t[3],t[3]=e;var n=a(t,this._invKeySchedule,u.INV_SUB_MIX,u.INV_SBOX,this._nRounds),o=r.allocUnsafe(16);return o.writeUInt32BE(n[0],0),o.writeUInt32BE(n[3],4),o.writeUInt32BE(n[2],8),o.writeUInt32BE(n[1],12),o},c.prototype.scrub=function(){o(this._keySchedule),o(this._invKeySchedule),o(this._key)},t.exports.AES=c},function(t,e,n){var r=n(3).Buffer,i=n(111);t.exports=function(t,e,n,o){if(r.isBuffer(t)||(t=r.from(t,"binary")),e&&(r.isBuffer(e)||(e=r.from(e,"binary")),8!==e.length))throw new RangeError("salt should be Buffer with 8 byte length");for(var a=n/8,s=r.alloc(a),u=r.alloc(o||0),c=r.alloc(0);a>0||o>0;){var f=new i;f.update(c),f.update(t),e&&f.update(e),c=f.digest();var l=0;if(a>0){var h=s.length-a;l=Math.min(a,c.length),c.copy(s,h,0,l),a-=l}if(l0){var d=u.length-o,p=Math.min(o,c.length-l);c.copy(u,d,l,l+p),o-=p}}return c.fill(0),{key:s,iv:u}}},function(t,e,n){"use strict";var r=n(5),i=n(16),o=i.getNAF,a=i.getJSF,s=i.assert;function u(t,e){this.type=t,this.p=new r(e.p,16),this.red=e.prime?r.red(e.prime):r.mont(this.p),this.zero=new r(0).toRed(this.red),this.one=new r(1).toRed(this.red),this.two=new r(2).toRed(this.red),this.n=e.n&&new r(e.n,16),this.g=e.g&&this.pointFromJSON(e.g,e.gRed),this._wnafT1=new Array(4),this._wnafT2=new Array(4),this._wnafT3=new Array(4),this._wnafT4=new Array(4);var n=this.n&&this.p.div(this.n);!n||n.cmpn(100)>0?this.redN=null:(this._maxwellTrick=!0,this.redN=this.n.toRed(this.red))}function c(t,e){this.curve=t,this.type=e,this.precomputed=null}t.exports=u,u.prototype.point=function(){throw new Error("Not implemented")},u.prototype.validate=function(){throw new Error("Not implemented")},u.prototype._fixedNafMul=function(t,e){s(t.precomputed);var n=t._getDoubles(),r=o(e,1),i=(1<=u;e--)c=(c<<1)+r[e];a.push(c)}for(var f=this.jpoint(null,null,null),l=this.jpoint(null,null,null),h=i;h>0;h--){for(u=0;u=0;c--){for(e=0;c>=0&&0===a[c];c--)e++;if(c>=0&&e++,u=u.dblp(e),c<0)break;var f=a[c];s(0!==f),u="affine"===t.type?f>0?u.mixedAdd(i[f-1>>1]):u.mixedAdd(i[-f-1>>1].neg()):f>0?u.add(i[f-1>>1]):u.add(i[-f-1>>1].neg())}return"affine"===t.type?u.toP():u},u.prototype._wnafMulAdd=function(t,e,n,r,i){for(var s=this._wnafT1,u=this._wnafT2,c=this._wnafT3,f=0,l=0;l=1;l-=2){var d=l-1,p=l;if(1===s[d]&&1===s[p]){var g=[e[d],null,null,e[p]];0===e[d].y.cmp(e[p].y)?(g[1]=e[d].add(e[p]),g[2]=e[d].toJ().mixedAdd(e[p].neg())):0===e[d].y.cmp(e[p].y.redNeg())?(g[1]=e[d].toJ().mixedAdd(e[p]),g[2]=e[d].add(e[p].neg())):(g[1]=e[d].toJ().mixedAdd(e[p]),g[2]=e[d].toJ().mixedAdd(e[p].neg()));var y=[-3,-1,-5,-7,0,7,5,1,3],b=a(n[d],n[p]);f=Math.max(b[0].length,f),c[d]=new Array(f),c[p]=new Array(f);for(var m=0;m=0;l--){for(var k=0;l>=0;){var E=!0;for(m=0;m=0&&k++,w=w.dblp(k),l<0)break;for(m=0;m0?A=u[m][S-1>>1]:S<0&&(A=u[m][-S-1>>1].neg()),w="affine"===A.type?w.mixedAdd(A):w.add(A))}}for(l=0;l=Math.ceil((t.bitLength()+1)/e.step)},c.prototype._getDoubles=function(t,e){if(this.precomputed&&this.precomputed.doubles)return this.precomputed.doubles;for(var n=[this],r=this,i=0;i-1}(s)?s:(n=s.match(o))?(e=n[0],r.test(e)?"about:blank":s):"about:blank"}}},function(t,e,n){(function(t,r){var i=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[2,3],n=[1,7],r=[7,12,15,17,19,20,21],i=[7,11,12,15,17,19,20,21],o=[2,20],a=[1,32],s={trace:function(){},yy:{},symbols_:{error:2,start:3,GG:4,":":5,document:6,EOF:7,DIR:8,options:9,body:10,OPT:11,NL:12,line:13,statement:14,COMMIT:15,commit_arg:16,BRANCH:17,ID:18,CHECKOUT:19,MERGE:20,RESET:21,reset_arg:22,STR:23,HEAD:24,reset_parents:25,CARET:26,$accept:0,$end:1},terminals_:{2:"error",4:"GG",5:":",7:"EOF",8:"DIR",11:"OPT",12:"NL",15:"COMMIT",17:"BRANCH",18:"ID",19:"CHECKOUT",20:"MERGE",21:"RESET",23:"STR",24:"HEAD",26:"CARET"},productions_:[0,[3,4],[3,5],[6,0],[6,2],[9,2],[9,1],[10,0],[10,2],[13,2],[13,1],[14,2],[14,2],[14,2],[14,2],[14,2],[16,0],[16,1],[22,2],[22,2],[25,0],[25,2]],performAction:function(t,e,n,r,i,o,a){var s=o.length-1;switch(i){case 1:return o[s-1];case 2:return r.setDirection(o[s-3]),o[s-1];case 4:r.setOptions(o[s-1]),this.$=o[s];break;case 5:o[s-1]+=o[s],this.$=o[s-1];break;case 7:this.$=[];break;case 8:o[s-1].push(o[s]),this.$=o[s-1];break;case 9:this.$=o[s-1];break;case 11:r.commit(o[s]);break;case 12:r.branch(o[s]);break;case 13:r.checkout(o[s]);break;case 14:r.merge(o[s]);break;case 15:r.reset(o[s]);break;case 16:this.$="";break;case 17:this.$=o[s];break;case 18:this.$=o[s-1]+":"+o[s];break;case 19:this.$=o[s-1]+":"+r.count,r.count=0;break;case 20:r.count=0;break;case 21:r.count+=1}},table:[{3:1,4:[1,2]},{1:[3]},{5:[1,3],8:[1,4]},{6:5,7:e,9:6,12:n},{5:[1,8]},{7:[1,9]},t(r,[2,7],{10:10,11:[1,11]}),t(i,[2,6]),{6:12,7:e,9:6,12:n},{1:[2,1]},{7:[2,4],12:[1,15],13:13,14:14,15:[1,16],17:[1,17],19:[1,18],20:[1,19],21:[1,20]},t(i,[2,5]),{7:[1,21]},t(r,[2,8]),{12:[1,22]},t(r,[2,10]),{12:[2,16],16:23,23:[1,24]},{18:[1,25]},{18:[1,26]},{18:[1,27]},{18:[1,30],22:28,24:[1,29]},{1:[2,2]},t(r,[2,9]),{12:[2,11]},{12:[2,17]},{12:[2,12]},{12:[2,13]},{12:[2,14]},{12:[2,15]},{12:o,25:31,26:a},{12:o,25:33,26:a},{12:[2,18]},{12:o,25:34,26:a},{12:[2,19]},{12:[2,21]}],defaultActions:{9:[2,1],21:[2,2],23:[2,11],24:[2,17],25:[2,12],26:[2,13],27:[2,14],28:[2,15],31:[2,18],33:[2,19],34:[2,21]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],o=[],a=this.table,s="",u=0,c=0,f=0,l=2,h=1,d=o.slice.call(arguments,1),p=Object.create(this.lexer),g={yy:{}};for(var y in this.yy)Object.prototype.hasOwnProperty.call(this.yy,y)&&(g.yy[y]=this.yy[y]);p.setInput(t,g.yy),g.yy.lexer=p,g.yy.parser=this,void 0===p.yylloc&&(p.yylloc={});var b=p.yylloc;o.push(b);var m=p.options&&p.options.ranges;function v(){var t;return"number"!=typeof(t=r.pop()||p.lex()||h)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof g.yy.parseError?this.parseError=g.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var _,w,x,k,E,A,S,T,M,D={};;){if(x=n[n.length-1],this.defaultActions[x]?k=this.defaultActions[x]:(null==_&&(_=v()),k=a[x]&&a[x][_]),void 0===k||!k.length||!k[0]){var C="";for(A in M=[],a[x])this.terminals_[A]&&A>l&&M.push("'"+this.terminals_[A]+"'");C=p.showPosition?"Parse error on line "+(u+1)+":\n"+p.showPosition()+"\nExpecting "+M.join(", ")+", got '"+(this.terminals_[_]||_)+"'":"Parse error on line "+(u+1)+": Unexpected "+(_==h?"end of input":"'"+(this.terminals_[_]||_)+"'"),this.parseError(C,{text:p.match,token:this.terminals_[_]||_,line:p.yylineno,loc:b,expected:M})}if(k[0]instanceof Array&&k.length>1)throw new Error("Parse Error: multiple actions possible at state: "+x+", token: "+_);switch(k[0]){case 1:n.push(_),i.push(p.yytext),o.push(p.yylloc),n.push(k[1]),_=null,w?(_=w,w=null):(c=p.yyleng,s=p.yytext,u=p.yylineno,b=p.yylloc,f>0&&f--);break;case 2:if(S=this.productions_[k[1]][1],D.$=i[i.length-S],D._$={first_line:o[o.length-(S||1)].first_line,last_line:o[o.length-1].last_line,first_column:o[o.length-(S||1)].first_column,last_column:o[o.length-1].last_column},m&&(D._$.range=[o[o.length-(S||1)].range[0],o[o.length-1].range[1]]),void 0!==(E=this.performAction.apply(D,[s,c,u,g.yy,k[1],i,o].concat(d))))return E;S&&(n=n.slice(0,-1*S*2),i=i.slice(0,-1*S),o=o.slice(0,-1*S)),n.push(this.productions_[k[1]][0]),i.push(D.$),o.push(D._$),T=a[n[n.length-2]][n[n.length-1]],n.push(T);break;case 3:return!0}}return!0}},u={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var o in i)this[o]=i[o];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),o=0;oe[0].length)){if(e=n,r=o,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[o])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var t=this.next();return t||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{"case-insensitive":!0},performAction:function(t,e,n,r){switch(n){case 0:return 12;case 1:case 2:case 3:break;case 4:return 4;case 5:return 15;case 6:return 17;case 7:return 20;case 8:return 21;case 9:return 19;case 10:case 11:return 8;case 12:return 5;case 13:return 26;case 14:this.begin("options");break;case 15:this.popState();break;case 16:return 11;case 17:this.begin("string");break;case 18:this.popState();break;case 19:return 23;case 20:return 18;case 21:return 7}},rules:[/^(?:(\r?\n)+)/i,/^(?:\s+)/i,/^(?:#[^\n]*)/i,/^(?:%[^\n]*)/i,/^(?:gitGraph\b)/i,/^(?:commit\b)/i,/^(?:branch\b)/i,/^(?:merge\b)/i,/^(?:reset\b)/i,/^(?:checkout\b)/i,/^(?:LR\b)/i,/^(?:BT\b)/i,/^(?::)/i,/^(?:\^)/i,/^(?:options\r?\n)/i,/^(?:end\r?\n)/i,/^(?:[^\n]+\r?\n)/i,/^(?:["])/i,/^(?:["])/i,/^(?:[^"]*)/i,/^(?:[a-zA-Z][a-zA-Z0-9_]+)/i,/^(?:$)/i],conditions:{options:{rules:[15,16],inclusive:!1},string:{rules:[18,19],inclusive:!1},INITIAL:{rules:[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,17,20,21],inclusive:!0}}};function c(){this.yy={}}return s.lexer=u,c.prototype=s,s.Parser=c,new c}();e.parser=i,e.Parser=i.Parser,e.parse=function(){return i.parse.apply(i,arguments)},e.main=function(r){r[1]||(console.log("Usage: "+r[0]+" FILE"),t.exit(1));var i=n(28).readFileSync(n(29).normalize(r[1]),"utf8");return e.parser.parse(i)},n.c[n.s]===r&&e.main(t.argv.slice(1))}).call(this,n(7),n(9)(t))},function(t,e,n){(function(t,r){var i=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[6,9,10],n={trace:function(){},yy:{},symbols_:{error:2,start:3,info:4,document:5,EOF:6,line:7,statement:8,NL:9,showInfo:10,$accept:0,$end:1},terminals_:{2:"error",4:"info",6:"EOF",9:"NL",10:"showInfo"},productions_:[0,[3,3],[5,0],[5,2],[7,1],[7,1],[8,1]],performAction:function(t,e,n,r,i,o,a){o.length;switch(i){case 1:return r;case 4:break;case 6:r.setInfo(!0)}},table:[{3:1,4:[1,2]},{1:[3]},t(e,[2,2],{5:3}),{6:[1,4],7:5,8:6,9:[1,7],10:[1,8]},{1:[2,1]},t(e,[2,3]),t(e,[2,4]),t(e,[2,5]),t(e,[2,6])],defaultActions:{4:[2,1]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],o=[],a=this.table,s="",u=0,c=0,f=0,l=2,h=1,d=o.slice.call(arguments,1),p=Object.create(this.lexer),g={yy:{}};for(var y in this.yy)Object.prototype.hasOwnProperty.call(this.yy,y)&&(g.yy[y]=this.yy[y]);p.setInput(t,g.yy),g.yy.lexer=p,g.yy.parser=this,void 0===p.yylloc&&(p.yylloc={});var b=p.yylloc;o.push(b);var m=p.options&&p.options.ranges;function v(){var t;return"number"!=typeof(t=r.pop()||p.lex()||h)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof g.yy.parseError?this.parseError=g.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var _,w,x,k,E,A,S,T,M,D={};;){if(x=n[n.length-1],this.defaultActions[x]?k=this.defaultActions[x]:(null==_&&(_=v()),k=a[x]&&a[x][_]),void 0===k||!k.length||!k[0]){var C="";for(A in M=[],a[x])this.terminals_[A]&&A>l&&M.push("'"+this.terminals_[A]+"'");C=p.showPosition?"Parse error on line "+(u+1)+":\n"+p.showPosition()+"\nExpecting "+M.join(", ")+", got '"+(this.terminals_[_]||_)+"'":"Parse error on line "+(u+1)+": Unexpected "+(_==h?"end of input":"'"+(this.terminals_[_]||_)+"'"),this.parseError(C,{text:p.match,token:this.terminals_[_]||_,line:p.yylineno,loc:b,expected:M})}if(k[0]instanceof Array&&k.length>1)throw new Error("Parse Error: multiple actions possible at state: "+x+", token: "+_);switch(k[0]){case 1:n.push(_),i.push(p.yytext),o.push(p.yylloc),n.push(k[1]),_=null,w?(_=w,w=null):(c=p.yyleng,s=p.yytext,u=p.yylineno,b=p.yylloc,f>0&&f--);break;case 2:if(S=this.productions_[k[1]][1],D.$=i[i.length-S],D._$={first_line:o[o.length-(S||1)].first_line,last_line:o[o.length-1].last_line,first_column:o[o.length-(S||1)].first_column,last_column:o[o.length-1].last_column},m&&(D._$.range=[o[o.length-(S||1)].range[0],o[o.length-1].range[1]]),void 0!==(E=this.performAction.apply(D,[s,c,u,g.yy,k[1],i,o].concat(d))))return E;S&&(n=n.slice(0,-1*S*2),i=i.slice(0,-1*S),o=o.slice(0,-1*S)),n.push(this.productions_[k[1]][0]),i.push(D.$),o.push(D._$),T=a[n[n.length-2]][n[n.length-1]],n.push(T);break;case 3:return!0}}return!0}},r={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var o in i)this[o]=i[o];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),o=0;oe[0].length)){if(e=n,r=o,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[o])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var t=this.next();return t||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{"case-insensitive":!0},performAction:function(t,e,n,r){switch(n){case 0:return 4;case 1:return 9;case 2:return"space";case 3:return 10;case 4:return 6;case 5:return"TXT"}},rules:[/^(?:info\b)/i,/^(?:[\s\n\r]+)/i,/^(?:[\s]+)/i,/^(?:showInfo\b)/i,/^(?:$)/i,/^(?:.)/i],conditions:{INITIAL:{rules:[0,1,2,3,4,5],inclusive:!0}}};function i(){this.yy={}}return n.lexer=r,i.prototype=n,n.Parser=i,new i}();e.parser=i,e.Parser=i.Parser,e.parse=function(){return i.parse.apply(i,arguments)},e.main=function(r){r[1]||(console.log("Usage: "+r[0]+" FILE"),t.exit(1));var i=n(28).readFileSync(n(29).normalize(r[1]),"utf8");return e.parser.parse(i)},n.c[n.s]===r&&e.main(t.argv.slice(1))}).call(this,n(7),n(9)(t))},function(t,e,n){(function(t,r){var i=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[6,9,10,12],n={trace:function(){},yy:{},symbols_:{error:2,start:3,pie:4,document:5,EOF:6,line:7,statement:8,NL:9,STR:10,VALUE:11,title:12,$accept:0,$end:1},terminals_:{2:"error",4:"pie",6:"EOF",9:"NL",10:"STR",11:"VALUE",12:"title"},productions_:[0,[3,3],[5,0],[5,2],[7,1],[7,1],[8,2],[8,1]],performAction:function(t,e,n,r,i,o,a){var s=o.length-1;switch(i){case 4:break;case 6:console.log("str:"+o[s-1]+" value: "+o[s]),r.addSection(o[s-1],r.cleanupValue(o[s]));break;case 7:r.setTitle(o[s].substr(6)),this.$=o[s].substr(6)}},table:[{3:1,4:[1,2]},{1:[3]},t(e,[2,2],{5:3}),{6:[1,4],7:5,8:6,9:[1,7],10:[1,8],12:[1,9]},{1:[2,1]},t(e,[2,3]),t(e,[2,4]),t(e,[2,5]),{11:[1,10]},t(e,[2,7]),t(e,[2,6])],defaultActions:{4:[2,1]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],o=[],a=this.table,s="",u=0,c=0,f=0,l=2,h=1,d=o.slice.call(arguments,1),p=Object.create(this.lexer),g={yy:{}};for(var y in this.yy)Object.prototype.hasOwnProperty.call(this.yy,y)&&(g.yy[y]=this.yy[y]);p.setInput(t,g.yy),g.yy.lexer=p,g.yy.parser=this,void 0===p.yylloc&&(p.yylloc={});var b=p.yylloc;o.push(b);var m=p.options&&p.options.ranges;function v(){var t;return"number"!=typeof(t=r.pop()||p.lex()||h)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof g.yy.parseError?this.parseError=g.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var _,w,x,k,E,A,S,T,M,D={};;){if(x=n[n.length-1],this.defaultActions[x]?k=this.defaultActions[x]:(null==_&&(_=v()),k=a[x]&&a[x][_]),void 0===k||!k.length||!k[0]){var C="";for(A in M=[],a[x])this.terminals_[A]&&A>l&&M.push("'"+this.terminals_[A]+"'");C=p.showPosition?"Parse error on line "+(u+1)+":\n"+p.showPosition()+"\nExpecting "+M.join(", ")+", got '"+(this.terminals_[_]||_)+"'":"Parse error on line "+(u+1)+": Unexpected "+(_==h?"end of input":"'"+(this.terminals_[_]||_)+"'"),this.parseError(C,{text:p.match,token:this.terminals_[_]||_,line:p.yylineno,loc:b,expected:M})}if(k[0]instanceof Array&&k.length>1)throw new Error("Parse Error: multiple actions possible at state: "+x+", token: "+_);switch(k[0]){case 1:n.push(_),i.push(p.yytext),o.push(p.yylloc),n.push(k[1]),_=null,w?(_=w,w=null):(c=p.yyleng,s=p.yytext,u=p.yylineno,b=p.yylloc,f>0&&f--);break;case 2:if(S=this.productions_[k[1]][1],D.$=i[i.length-S],D._$={first_line:o[o.length-(S||1)].first_line,last_line:o[o.length-1].last_line,first_column:o[o.length-(S||1)].first_column,last_column:o[o.length-1].last_column},m&&(D._$.range=[o[o.length-(S||1)].range[0],o[o.length-1].range[1]]),void 0!==(E=this.performAction.apply(D,[s,c,u,g.yy,k[1],i,o].concat(d))))return E;S&&(n=n.slice(0,-1*S*2),i=i.slice(0,-1*S),o=o.slice(0,-1*S)),n.push(this.productions_[k[1]][0]),i.push(D.$),o.push(D._$),T=a[n[n.length-2]][n[n.length-1]],n.push(T);break;case 3:return!0}}return!0}},r={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var o in i)this[o]=i[o];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),o=0;oe[0].length)){if(e=n,r=o,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[o])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var t=this.next();return t||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{"case-insensitive":!0},performAction:function(t,e,n,r){switch(n){case 0:case 1:break;case 2:return 4;case 3:return 9;case 4:return"space";case 5:return 12;case 6:this.begin("string");break;case 7:this.popState();break;case 8:return"STR";case 9:return"VALUE";case 10:return 6}},rules:[/^(?:%%[^\n]*)/i,/^(?:\s+)/i,/^(?:pie\b)/i,/^(?:[\s\n\r]+)/i,/^(?:[\s]+)/i,/^(?:title\s[^#\n;]+)/i,/^(?:["])/i,/^(?:["])/i,/^(?:[^"]*)/i,/^(?::[\s]*[\d]+(?:\.[\d]+)?)/i,/^(?:$)/i],conditions:{string:{rules:[7,8],inclusive:!1},INITIAL:{rules:[0,1,2,3,4,5,6,9,10],inclusive:!0}}};function i(){this.yy={}}return n.lexer=r,i.prototype=n,n.Parser=i,new i}();e.parser=i,e.Parser=i.Parser,e.parse=function(){return i.parse.apply(i,arguments)},e.main=function(r){r[1]||(console.log("Usage: "+r[0]+" FILE"),t.exit(1));var i=n(28).readFileSync(n(29).normalize(r[1]),"utf8");return e.parser.parse(i)},n.c[n.s]===r&&e.main(t.argv.slice(1))}).call(this,n(7),n(9)(t))},function(t){t.exports=JSON.parse('{"name":"mermaid","version":"8.4.3","description":"Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.","main":"dist/mermaid.core.js","keywords":["diagram","markdown","flowchart","sequence diagram","gantt","class diagram","git graph"],"scripts":{"build":"webpack --progress --colors","postbuild":"documentation build src/mermaidAPI.js --shallow -f md --markdown-toc false -o docs/mermaidAPI.md","build:watch":"yarn build --watch","minify":"minify ./dist/mermaid.js > ./dist/mermaid.min.js","release":"yarn build -p --config webpack.config.prod.babel.js","lint":"eslint src","e2e:depr":"yarn lint && jest e2e --config e2e/jest.config.js","cypress":"percy exec -- cypress run","e2e":"start-server-and-test dev http://localhost:9000/ cypress","e2e-upd":"yarn lint && jest e2e -u --config e2e/jest.config.js","dev":"webpack-dev-server --config webpack.config.e2e.js","test":"yarn lint && jest src/.*","test:watch":"jest --watch src","prepublishOnly":"yarn build && yarn release && yarn test && yarn e2e","prepush":"yarn test"},"repository":{"type":"git","url":"https://github.com/knsv/mermaid"},"author":"Knut Sveidqvist","license":"MIT","standard":{"ignore":["**/parser/*.js","dist/**/*.js","cypress/**/*.js"],"globals":["page"]},"dependencies":{"@braintree/sanitize-url":"^3.1.0","crypto-random-string":"^3.0.1","d3":"^5.7.0","dagre-d3-unofficial":"0.6.4","dagre":"^0.8.4","graphlib":"^2.1.7","he":"^1.2.0","lodash":"^4.17.11","minify":"^4.1.1","moment-mini":"^2.22.1","scope-css":"^1.2.1"},"devDependencies":{"documentation":"^12.0.1","prettier":"^1.18.2","eslint":"^6.3.0","eslint-config-prettier":"^6.3.0","eslint-plugin-prettier":"^3.1.0","@babel/core":"^7.2.2","@babel/preset-env":"^7.2.0","@babel/register":"^7.0.0","@percy/cypress":"^2.0.1","babel-core":"7.0.0-bridge.0","babel-jest":"^23.6.0","babel-loader":"^8.0.4","coveralls":"^3.0.2","css-loader":"^2.0.1","css-to-string-loader":"^0.1.3","cypress":"3.4.0","husky":"^1.2.1","identity-obj-proxy":"^3.0.0","jest":"^24.9.0","jison":"^0.4.18","moment":"^2.23.0","node-sass":"^4.12.0","puppeteer":"^1.17.0","sass-loader":"^7.1.0","start-server-and-test":"^1.10.0","webpack":"^4.27.1","webpack-cli":"^3.1.2","webpack-dev-server":"^3.4.1","webpack-node-externals":"^1.7.2","yarn-upgrade-all":"^0.5.0"},"files":["dist"],"yarn-upgrade-all":{"ignore":["babel-core"]}}')},function(t,e,n){"use strict";var r=n(12);t.exports=s;var i="\0",o="\0",a="";function s(t){this._isDirected=!r.has(t,"directed")||t.directed,this._isMultigraph=!!r.has(t,"multigraph")&&t.multigraph,this._isCompound=!!r.has(t,"compound")&&t.compound,this._label=void 0,this._defaultNodeLabelFn=r.constant(void 0),this._defaultEdgeLabelFn=r.constant(void 0),this._nodes={},this._isCompound&&(this._parent={},this._children={},this._children[o]={}),this._in={},this._preds={},this._out={},this._sucs={},this._edgeObjs={},this._edgeLabels={}}function u(t,e){t[e]?t[e]++:t[e]=1}function c(t,e){--t[e]||delete t[e]}function f(t,e,n,o){var s=""+e,u=""+n;if(!t&&s>u){var c=s;s=u,u=c}return s+a+u+a+(r.isUndefined(o)?i:o)}function l(t,e,n,r){var i=""+e,o=""+n;if(!t&&i>o){var a=i;i=o,o=a}var s={v:i,w:o};return r&&(s.name=r),s}function h(t,e){return f(t,e.v,e.w,e.name)}s.prototype._nodeCount=0,s.prototype._edgeCount=0,s.prototype.isDirected=function(){return this._isDirected},s.prototype.isMultigraph=function(){return this._isMultigraph},s.prototype.isCompound=function(){return this._isCompound},s.prototype.setGraph=function(t){return this._label=t,this},s.prototype.graph=function(){return this._label},s.prototype.setDefaultNodeLabel=function(t){return r.isFunction(t)||(t=r.constant(t)),this._defaultNodeLabelFn=t,this},s.prototype.nodeCount=function(){return this._nodeCount},s.prototype.nodes=function(){return r.keys(this._nodes)},s.prototype.sources=function(){var t=this;return r.filter(this.nodes(),(function(e){return r.isEmpty(t._in[e])}))},s.prototype.sinks=function(){var t=this;return r.filter(this.nodes(),(function(e){return r.isEmpty(t._out[e])}))},s.prototype.setNodes=function(t,e){var n=arguments,i=this;return r.each(t,(function(t){n.length>1?i.setNode(t,e):i.setNode(t)})),this},s.prototype.setNode=function(t,e){return r.has(this._nodes,t)?(arguments.length>1&&(this._nodes[t]=e),this):(this._nodes[t]=arguments.length>1?e:this._defaultNodeLabelFn(t),this._isCompound&&(this._parent[t]=o,this._children[t]={},this._children[o][t]=!0),this._in[t]={},this._preds[t]={},this._out[t]={},this._sucs[t]={},++this._nodeCount,this)},s.prototype.node=function(t){return this._nodes[t]},s.prototype.hasNode=function(t){return r.has(this._nodes,t)},s.prototype.removeNode=function(t){var e=this;if(r.has(this._nodes,t)){var n=function(t){e.removeEdge(e._edgeObjs[t])};delete this._nodes[t],this._isCompound&&(this._removeFromParentsChildList(t),delete this._parent[t],r.each(this.children(t),(function(t){e.setParent(t)})),delete this._children[t]),r.each(r.keys(this._in[t]),n),delete this._in[t],delete this._preds[t],r.each(r.keys(this._out[t]),n),delete this._out[t],delete this._sucs[t],--this._nodeCount}return this},s.prototype.setParent=function(t,e){if(!this._isCompound)throw new Error("Cannot set parent in a non-compound graph");if(r.isUndefined(e))e=o;else{for(var n=e+="";!r.isUndefined(n);n=this.parent(n))if(n===t)throw new Error("Setting "+e+" as parent of "+t+" would create a cycle");this.setNode(e)}return this.setNode(t),this._removeFromParentsChildList(t),this._parent[t]=e,this._children[e][t]=!0,this},s.prototype._removeFromParentsChildList=function(t){delete this._children[this._parent[t]][t]},s.prototype.parent=function(t){if(this._isCompound){var e=this._parent[t];if(e!==o)return e}},s.prototype.children=function(t){if(r.isUndefined(t)&&(t=o),this._isCompound){var e=this._children[t];if(e)return r.keys(e)}else{if(t===o)return this.nodes();if(this.hasNode(t))return[]}},s.prototype.predecessors=function(t){var e=this._preds[t];if(e)return r.keys(e)},s.prototype.successors=function(t){var e=this._sucs[t];if(e)return r.keys(e)},s.prototype.neighbors=function(t){var e=this.predecessors(t);if(e)return r.union(e,this.successors(t))},s.prototype.isLeaf=function(t){return 0===(this.isDirected()?this.successors(t):this.neighbors(t)).length},s.prototype.filterNodes=function(t){var e=new this.constructor({directed:this._isDirected,multigraph:this._isMultigraph,compound:this._isCompound});e.setGraph(this.graph());var n=this;r.each(this._nodes,(function(n,r){t(r)&&e.setNode(r,n)})),r.each(this._edgeObjs,(function(t){e.hasNode(t.v)&&e.hasNode(t.w)&&e.setEdge(t,n.edge(t))}));var i={};return this._isCompound&&r.each(e.nodes(),(function(t){e.setParent(t,function t(r){var o=n.parent(r);return void 0===o||e.hasNode(o)?(i[r]=o,o):o in i?i[o]:t(o)}(t))})),e},s.prototype.setDefaultEdgeLabel=function(t){return r.isFunction(t)||(t=r.constant(t)),this._defaultEdgeLabelFn=t,this},s.prototype.edgeCount=function(){return this._edgeCount},s.prototype.edges=function(){return r.values(this._edgeObjs)},s.prototype.setPath=function(t,e){var n=this,i=arguments;return r.reduce(t,(function(t,r){return i.length>1?n.setEdge(t,r,e):n.setEdge(t,r),r})),this},s.prototype.setEdge=function(){var t,e,n,i,o=!1,a=arguments[0];"object"==typeof a&&null!==a&&"v"in a?(t=a.v,e=a.w,n=a.name,2===arguments.length&&(i=arguments[1],o=!0)):(t=a,e=arguments[1],n=arguments[3],arguments.length>2&&(i=arguments[2],o=!0)),t=""+t,e=""+e,r.isUndefined(n)||(n=""+n);var s=f(this._isDirected,t,e,n);if(r.has(this._edgeLabels,s))return o&&(this._edgeLabels[s]=i),this;if(!r.isUndefined(n)&&!this._isMultigraph)throw new Error("Cannot set a named edge when isMultigraph = false");this.setNode(t),this.setNode(e),this._edgeLabels[s]=o?i:this._defaultEdgeLabelFn(t,e,n);var c=l(this._isDirected,t,e,n);return t=c.v,e=c.w,Object.freeze(c),this._edgeObjs[s]=c,u(this._preds[e],t),u(this._sucs[t],e),this._in[e][s]=c,this._out[t][s]=c,this._edgeCount++,this},s.prototype.edge=function(t,e,n){var r=1===arguments.length?h(this._isDirected,arguments[0]):f(this._isDirected,t,e,n);return this._edgeLabels[r]},s.prototype.hasEdge=function(t,e,n){var i=1===arguments.length?h(this._isDirected,arguments[0]):f(this._isDirected,t,e,n);return r.has(this._edgeLabels,i)},s.prototype.removeEdge=function(t,e,n){var r=1===arguments.length?h(this._isDirected,arguments[0]):f(this._isDirected,t,e,n),i=this._edgeObjs[r];return i&&(t=i.v,e=i.w,delete this._edgeLabels[r],delete this._edgeObjs[r],c(this._preds[e],t),c(this._sucs[t],e),delete this._in[e][r],delete this._out[t][r],this._edgeCount--),this},s.prototype.inEdges=function(t,e){var n=this._in[t];if(n){var i=r.values(n);return e?r.filter(i,(function(t){return t.v===e})):i}},s.prototype.outEdges=function(t,e){var n=this._out[t];if(n){var i=r.values(n);return e?r.filter(i,(function(t){return t.w===e})):i}},s.prototype.nodeEdges=function(t,e){var n=this.inEdges(t,e);if(n)return n.concat(this.outEdges(t,e))}},function(t,e,n){var r=n(32)(n(18),"Map");t.exports=r},function(t,e,n){var r=n(252),i=n(259),o=n(261),a=n(262),s=n(263);function u(t){var e=-1,n=null==t?0:t.length;for(this.clear();++e-1&&t%1==0&&t<=n}},function(t,e,n){(function(t){var r=n(131),i=e&&!e.nodeType&&e,o=i&&"object"==typeof t&&t&&!t.nodeType&&t,a=o&&o.exports===i&&r.process,s=function(){try{var t=o&&o.require&&o.require("util").types;return t||a&&a.binding&&a.binding("util")}catch(t){}}();t.exports=s}).call(this,n(9)(t))},function(t,e,n){var r=n(70),i=n(269),o=Object.prototype.hasOwnProperty;t.exports=function(t){if(!r(t))return i(t);var e=[];for(var n in Object(t))o.call(t,n)&&"constructor"!=n&&e.push(n);return e}},function(t,e,n){var r=n(138),i=n(139),o=Object.prototype.propertyIsEnumerable,a=Object.getOwnPropertySymbols,s=a?function(t){return null==t?[]:(t=Object(t),r(a(t),(function(e){return o.call(t,e)})))}:i;t.exports=s},function(t,e){t.exports=function(t,e){for(var n=-1,r=e.length,i=t.length;++n0&&o(f)?n>1?t(f,n-1,o,a,s):r(s,f):a||(s[s.length]=f)}return s}},function(t,e,n){var r=n(42);t.exports=function(t,e,n){for(var i=-1,o=t.length;++i>>32-e}function c(t,e,n,r,i,o,a){return u(t+(e&n|~e&r)+i+o|0,a)+e|0}function f(t,e,n,r,i,o,a){return u(t+(e&r|n&~r)+i+o|0,a)+e|0}function l(t,e,n,r,i,o,a){return u(t+(e^n^r)+i+o|0,a)+e|0}function h(t,e,n,r,i,o,a){return u(t+(n^(e|~r))+i+o|0,a)+e|0}r(s,i),s.prototype._update=function(){for(var t=a,e=0;e<16;++e)t[e]=this._block.readInt32LE(4*e);var n=this._a,r=this._b,i=this._c,o=this._d;n=c(n,r,i,o,t[0],3614090360,7),o=c(o,n,r,i,t[1],3905402710,12),i=c(i,o,n,r,t[2],606105819,17),r=c(r,i,o,n,t[3],3250441966,22),n=c(n,r,i,o,t[4],4118548399,7),o=c(o,n,r,i,t[5],1200080426,12),i=c(i,o,n,r,t[6],2821735955,17),r=c(r,i,o,n,t[7],4249261313,22),n=c(n,r,i,o,t[8],1770035416,7),o=c(o,n,r,i,t[9],2336552879,12),i=c(i,o,n,r,t[10],4294925233,17),r=c(r,i,o,n,t[11],2304563134,22),n=c(n,r,i,o,t[12],1804603682,7),o=c(o,n,r,i,t[13],4254626195,12),i=c(i,o,n,r,t[14],2792965006,17),n=f(n,r=c(r,i,o,n,t[15],1236535329,22),i,o,t[1],4129170786,5),o=f(o,n,r,i,t[6],3225465664,9),i=f(i,o,n,r,t[11],643717713,14),r=f(r,i,o,n,t[0],3921069994,20),n=f(n,r,i,o,t[5],3593408605,5),o=f(o,n,r,i,t[10],38016083,9),i=f(i,o,n,r,t[15],3634488961,14),r=f(r,i,o,n,t[4],3889429448,20),n=f(n,r,i,o,t[9],568446438,5),o=f(o,n,r,i,t[14],3275163606,9),i=f(i,o,n,r,t[3],4107603335,14),r=f(r,i,o,n,t[8],1163531501,20),n=f(n,r,i,o,t[13],2850285829,5),o=f(o,n,r,i,t[2],4243563512,9),i=f(i,o,n,r,t[7],1735328473,14),n=l(n,r=f(r,i,o,n,t[12],2368359562,20),i,o,t[5],4294588738,4),o=l(o,n,r,i,t[8],2272392833,11),i=l(i,o,n,r,t[11],1839030562,16),r=l(r,i,o,n,t[14],4259657740,23),n=l(n,r,i,o,t[1],2763975236,4),o=l(o,n,r,i,t[4],1272893353,11),i=l(i,o,n,r,t[7],4139469664,16),r=l(r,i,o,n,t[10],3200236656,23),n=l(n,r,i,o,t[13],681279174,4),o=l(o,n,r,i,t[0],3936430074,11),i=l(i,o,n,r,t[3],3572445317,16),r=l(r,i,o,n,t[6],76029189,23),n=l(n,r,i,o,t[9],3654602809,4),o=l(o,n,r,i,t[12],3873151461,11),i=l(i,o,n,r,t[15],530742520,16),n=h(n,r=l(r,i,o,n,t[2],3299628645,23),i,o,t[0],4096336452,6),o=h(o,n,r,i,t[7],1126891415,10),i=h(i,o,n,r,t[14],2878612391,15),r=h(r,i,o,n,t[5],4237533241,21),n=h(n,r,i,o,t[12],1700485571,6),o=h(o,n,r,i,t[3],2399980690,10),i=h(i,o,n,r,t[10],4293915773,15),r=h(r,i,o,n,t[1],2240044497,21),n=h(n,r,i,o,t[8],1873313359,6),o=h(o,n,r,i,t[15],4264355552,10),i=h(i,o,n,r,t[6],2734768916,15),r=h(r,i,o,n,t[13],1309151649,21),n=h(n,r,i,o,t[4],4149444226,6),o=h(o,n,r,i,t[11],3174756917,10),i=h(i,o,n,r,t[2],718787259,15),r=h(r,i,o,n,t[9],3951481745,21),this._a=this._a+n|0,this._b=this._b+r|0,this._c=this._c+i|0,this._d=this._d+o|0},s.prototype._digest=function(){this._block[this._blockOffset++]=128,this._blockOffset>56&&(this._block.fill(0,this._blockOffset,64),this._update(),this._blockOffset=0),this._block.fill(0,this._blockOffset,56),this._block.writeUInt32LE(this._length[0],56),this._block.writeUInt32LE(this._length[1],60),this._update();var t=o.allocUnsafe(16);return t.writeInt32LE(this._a,0),t.writeInt32LE(this._b,4),t.writeInt32LE(this._c,8),t.writeInt32LE(this._d,12),t},t.exports=s},function(t,e,n){t.exports=i;var r=n(113).EventEmitter;function i(){r.call(this)}n(2)(i,r),i.Readable=n(114),i.Writable=n(428),i.Duplex=n(429),i.Transform=n(430),i.PassThrough=n(431),i.Stream=i,i.prototype.pipe=function(t,e){var n=this;function i(e){t.writable&&!1===t.write(e)&&n.pause&&n.pause()}function o(){n.readable&&n.resume&&n.resume()}n.on("data",i),t.on("drain",o),t._isStdio||e&&!1===e.end||(n.on("end",s),n.on("close",u));var a=!1;function s(){a||(a=!0,t.end())}function u(){a||(a=!0,"function"==typeof t.destroy&&t.destroy())}function c(t){if(f(),0===r.listenerCount(this,"error"))throw t}function f(){n.removeListener("data",i),t.removeListener("drain",o),n.removeListener("end",s),n.removeListener("close",u),n.removeListener("error",c),t.removeListener("error",c),n.removeListener("end",f),n.removeListener("close",f),t.removeListener("close",f)}return n.on("error",c),t.on("error",c),n.on("end",f),n.on("close",f),t.on("close",f),t.emit("pipe",n),t}},function(t,e,n){"use strict";var r,i="object"==typeof Reflect?Reflect:null,o=i&&"function"==typeof i.apply?i.apply:function(t,e,n){return Function.prototype.apply.call(t,e,n)};r=i&&"function"==typeof i.ownKeys?i.ownKeys:Object.getOwnPropertySymbols?function(t){return Object.getOwnPropertyNames(t).concat(Object.getOwnPropertySymbols(t))}:function(t){return Object.getOwnPropertyNames(t)};var a=Number.isNaN||function(t){return t!=t};function s(){s.init.call(this)}t.exports=s,s.EventEmitter=s,s.prototype._events=void 0,s.prototype._eventsCount=0,s.prototype._maxListeners=void 0;var u=10;function c(t){return void 0===t._maxListeners?s.defaultMaxListeners:t._maxListeners}function f(t,e,n,r){var i,o,a,s;if("function"!=typeof n)throw new TypeError('The "listener" argument must be of type Function. Received type '+typeof n);if(void 0===(o=t._events)?(o=t._events=Object.create(null),t._eventsCount=0):(void 0!==o.newListener&&(t.emit("newListener",e,n.listener?n.listener:n),o=t._events),a=o[e]),void 0===a)a=o[e]=n,++t._eventsCount;else if("function"==typeof a?a=o[e]=r?[n,a]:[a,n]:r?a.unshift(n):a.push(n),(i=c(t))>0&&a.length>i&&!a.warned){a.warned=!0;var u=new Error("Possible EventEmitter memory leak detected. "+a.length+" "+String(e)+" listeners added. Use emitter.setMaxListeners() to increase limit");u.name="MaxListenersExceededWarning",u.emitter=t,u.type=e,u.count=a.length,s=u,console&&console.warn&&console.warn(s)}return t}function l(){for(var t=[],e=0;e0&&(a=e[0]),a instanceof Error)throw a;var s=new Error("Unhandled error."+(a?" ("+a.message+")":""));throw s.context=a,s}var u=i[t];if(void 0===u)return!1;if("function"==typeof u)o(u,this,e);else{var c=u.length,f=g(u,c);for(n=0;n=0;o--)if(n[o]===e||n[o].listener===e){a=n[o].listener,i=o;break}if(i<0)return this;0===i?n.shift():function(t,e){for(;e+1=0;r--)this.removeListener(t,e[r]);return this},s.prototype.listeners=function(t){return d(this,t,!0)},s.prototype.rawListeners=function(t){return d(this,t,!1)},s.listenerCount=function(t,e){return"function"==typeof t.listenerCount?t.listenerCount(e):p.call(t,e)},s.prototype.listenerCount=p,s.prototype.eventNames=function(){return this._eventsCount>0?r(this._events):[]}},function(t,e,n){(e=t.exports=n(193)).Stream=e,e.Readable=e,e.Writable=n(116),e.Duplex=n(35),e.Transform=n(196),e.PassThrough=n(427)},function(t,e,n){var r=n(8),i=r.Buffer;function o(t,e){for(var n in t)e[n]=t[n]}function a(t,e,n){return i(t,e,n)}i.from&&i.alloc&&i.allocUnsafe&&i.allocUnsafeSlow?t.exports=r:(o(r,e),e.Buffer=a),o(i,a),a.from=function(t,e,n){if("number"==typeof t)throw new TypeError("Argument must not be a number");return i(t,e,n)},a.alloc=function(t,e,n){if("number"!=typeof t)throw new TypeError("Argument must be a number");var r=i(t);return void 0!==e?"string"==typeof n?r.fill(e,n):r.fill(e):r.fill(0),r},a.allocUnsafe=function(t){if("number"!=typeof t)throw new TypeError("Argument must be a number");return i(t)},a.allocUnsafeSlow=function(t){if("number"!=typeof t)throw new TypeError("Argument must be a number");return r.SlowBuffer(t)}},function(t,e,n){"use strict";(function(e,r,i){var o=n(78);function a(t){var e=this;this.next=null,this.entry=null,this.finish=function(){!function(t,e,n){var r=t.entry;t.entry=null;for(;r;){var i=r.callback;e.pendingcb--,i(n),r=r.next}e.corkedRequestsFree?e.corkedRequestsFree.next=t:e.corkedRequestsFree=t}(e,t)}}t.exports=m;var s,u=!e.browser&&["v0.10","v0.9."].indexOf(e.version.slice(0,5))>-1?r:o.nextTick;m.WritableState=b;var c=n(54);c.inherits=n(2);var f={deprecate:n(426)},l=n(194),h=n(115).Buffer,d=i.Uint8Array||function(){};var p,g=n(195);function y(){}function b(t,e){s=s||n(35),t=t||{};var r=e instanceof s;this.objectMode=!!t.objectMode,r&&(this.objectMode=this.objectMode||!!t.writableObjectMode);var i=t.highWaterMark,c=t.writableHighWaterMark,f=this.objectMode?16:16384;this.highWaterMark=i||0===i?i:r&&(c||0===c)?c:f,this.highWaterMark=Math.floor(this.highWaterMark),this.finalCalled=!1,this.needDrain=!1,this.ending=!1,this.ended=!1,this.finished=!1,this.destroyed=!1;var l=!1===t.decodeStrings;this.decodeStrings=!l,this.defaultEncoding=t.defaultEncoding||"utf8",this.length=0,this.writing=!1,this.corked=0,this.sync=!0,this.bufferProcessing=!1,this.onwrite=function(t){!function(t,e){var n=t._writableState,r=n.sync,i=n.writecb;if(function(t){t.writing=!1,t.writecb=null,t.length-=t.writelen,t.writelen=0}(n),e)!function(t,e,n,r,i){--e.pendingcb,n?(o.nextTick(i,r),o.nextTick(E,t,e),t._writableState.errorEmitted=!0,t.emit("error",r)):(i(r),t._writableState.errorEmitted=!0,t.emit("error",r),E(t,e))}(t,n,r,e,i);else{var a=x(n);a||n.corked||n.bufferProcessing||!n.bufferedRequest||w(t,n),r?u(_,t,n,a,i):_(t,n,a,i)}}(e,t)},this.writecb=null,this.writelen=0,this.bufferedRequest=null,this.lastBufferedRequest=null,this.pendingcb=0,this.prefinished=!1,this.errorEmitted=!1,this.bufferedRequestCount=0,this.corkedRequestsFree=new a(this)}function m(t){if(s=s||n(35),!(p.call(m,this)||this instanceof s))return new m(t);this._writableState=new b(t,this),this.writable=!0,t&&("function"==typeof t.write&&(this._write=t.write),"function"==typeof t.writev&&(this._writev=t.writev),"function"==typeof t.destroy&&(this._destroy=t.destroy),"function"==typeof t.final&&(this._final=t.final)),l.call(this)}function v(t,e,n,r,i,o,a){e.writelen=r,e.writecb=a,e.writing=!0,e.sync=!0,n?t._writev(i,e.onwrite):t._write(i,o,e.onwrite),e.sync=!1}function _(t,e,n,r){n||function(t,e){0===e.length&&e.needDrain&&(e.needDrain=!1,t.emit("drain"))}(t,e),e.pendingcb--,r(),E(t,e)}function w(t,e){e.bufferProcessing=!0;var n=e.bufferedRequest;if(t._writev&&n&&n.next){var r=e.bufferedRequestCount,i=new Array(r),o=e.corkedRequestsFree;o.entry=n;for(var s=0,u=!0;n;)i[s]=n,n.isBuf||(u=!1),n=n.next,s+=1;i.allBuffers=u,v(t,e,!0,e.length,i,"",o.finish),e.pendingcb++,e.lastBufferedRequest=null,o.next?(e.corkedRequestsFree=o.next,o.next=null):e.corkedRequestsFree=new a(e),e.bufferedRequestCount=0}else{for(;n;){var c=n.chunk,f=n.encoding,l=n.callback;if(v(t,e,!1,e.objectMode?1:c.length,c,f,l),n=n.next,e.bufferedRequestCount--,e.writing)break}null===n&&(e.lastBufferedRequest=null)}e.bufferedRequest=n,e.bufferProcessing=!1}function x(t){return t.ending&&0===t.length&&null===t.bufferedRequest&&!t.finished&&!t.writing}function k(t,e){t._final((function(n){e.pendingcb--,n&&t.emit("error",n),e.prefinished=!0,t.emit("prefinish"),E(t,e)}))}function E(t,e){var n=x(e);return n&&(!function(t,e){e.prefinished||e.finalCalled||("function"==typeof t._final?(e.pendingcb++,e.finalCalled=!0,o.nextTick(k,t,e)):(e.prefinished=!0,t.emit("prefinish")))}(t,e),0===e.pendingcb&&(e.finished=!0,t.emit("finish"))),n}c.inherits(m,l),b.prototype.getBuffer=function(){for(var t=this.bufferedRequest,e=[];t;)e.push(t),t=t.next;return e},function(){try{Object.defineProperty(b.prototype,"buffer",{get:f.deprecate((function(){return this.getBuffer()}),"_writableState.buffer is deprecated. Use _writableState.getBuffer instead.","DEP0003")})}catch(t){}}(),"function"==typeof Symbol&&Symbol.hasInstance&&"function"==typeof Function.prototype[Symbol.hasInstance]?(p=Function.prototype[Symbol.hasInstance],Object.defineProperty(m,Symbol.hasInstance,{value:function(t){return!!p.call(this,t)||this===m&&(t&&t._writableState instanceof b)}})):p=function(t){return t instanceof this},m.prototype.pipe=function(){this.emit("error",new Error("Cannot pipe, not readable"))},m.prototype.write=function(t,e,n){var r,i=this._writableState,a=!1,s=!i.objectMode&&(r=t,h.isBuffer(r)||r instanceof d);return s&&!h.isBuffer(t)&&(t=function(t){return h.from(t)}(t)),"function"==typeof e&&(n=e,e=null),s?e="buffer":e||(e=i.defaultEncoding),"function"!=typeof n&&(n=y),i.ended?function(t,e){var n=new Error("write after end");t.emit("error",n),o.nextTick(e,n)}(this,n):(s||function(t,e,n,r){var i=!0,a=!1;return null===n?a=new TypeError("May not write null values to stream"):"string"==typeof n||void 0===n||e.objectMode||(a=new TypeError("Invalid non-string/buffer chunk")),a&&(t.emit("error",a),o.nextTick(r,a),i=!1),i}(this,i,t,n))&&(i.pendingcb++,a=function(t,e,n,r,i,o){if(!n){var a=function(t,e,n){t.objectMode||!1===t.decodeStrings||"string"!=typeof e||(e=h.from(e,n));return e}(e,r,i);r!==a&&(n=!0,i="buffer",r=a)}var s=e.objectMode?1:r.length;e.length+=s;var u=e.length-1))throw new TypeError("Unknown encoding: "+t);return this._writableState.defaultEncoding=t,this},Object.defineProperty(m.prototype,"writableHighWaterMark",{enumerable:!1,get:function(){return this._writableState.highWaterMark}}),m.prototype._write=function(t,e,n){n(new Error("_write() is not implemented"))},m.prototype._writev=null,m.prototype.end=function(t,e,n){var r=this._writableState;"function"==typeof t?(n=t,t=null,e=null):"function"==typeof e&&(n=e,e=null),null!=t&&this.write(t,e),r.corked&&(r.corked=1,this.uncork()),r.ending||r.finished||function(t,e,n){e.ending=!0,E(t,e),n&&(e.finished?o.nextTick(n):t.once("finish",n));e.ended=!0,t.writable=!1}(this,r,n)},Object.defineProperty(m.prototype,"destroyed",{get:function(){return void 0!==this._writableState&&this._writableState.destroyed},set:function(t){this._writableState&&(this._writableState.destroyed=t)}}),m.prototype.destroy=g.destroy,m.prototype._undestroy=g.undestroy,m.prototype._destroy=function(t,e){this.end(),e(t)}}).call(this,n(7),n(424).setImmediate,n(11))},function(t,e,n){"use strict";var r=n(3).Buffer,i=r.isEncoding||function(t){switch((t=""+t)&&t.toLowerCase()){case"hex":case"utf8":case"utf-8":case"ascii":case"binary":case"base64":case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":case"raw":return!0;default:return!1}};function o(t){var e;switch(this.encoding=function(t){var e=function(t){if(!t)return"utf8";for(var e;;)switch(t){case"utf8":case"utf-8":return"utf8";case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return"utf16le";case"latin1":case"binary":return"latin1";case"base64":case"ascii":case"hex":return t;default:if(e)return;t=(""+t).toLowerCase(),e=!0}}(t);if("string"!=typeof e&&(r.isEncoding===i||!i(t)))throw new Error("Unknown encoding: "+t);return e||t}(t),this.encoding){case"utf16le":this.text=u,this.end=c,e=4;break;case"utf8":this.fillLast=s,e=4;break;case"base64":this.text=f,this.end=l,e=3;break;default:return this.write=h,void(this.end=d)}this.lastNeed=0,this.lastTotal=0,this.lastChar=r.allocUnsafe(e)}function a(t){return t<=127?0:t>>5==6?2:t>>4==14?3:t>>3==30?4:t>>6==2?-1:-2}function s(t){var e=this.lastTotal-this.lastNeed,n=function(t,e,n){if(128!=(192&e[0]))return t.lastNeed=0,"�";if(t.lastNeed>1&&e.length>1){if(128!=(192&e[1]))return t.lastNeed=1,"�";if(t.lastNeed>2&&e.length>2&&128!=(192&e[2]))return t.lastNeed=2,"�"}}(this,t);return void 0!==n?n:this.lastNeed<=t.length?(t.copy(this.lastChar,e,0,this.lastNeed),this.lastChar.toString(this.encoding,0,this.lastTotal)):(t.copy(this.lastChar,e,0,t.length),void(this.lastNeed-=t.length))}function u(t,e){if((t.length-e)%2==0){var n=t.toString("utf16le",e);if(n){var r=n.charCodeAt(n.length-1);if(r>=55296&&r<=56319)return this.lastNeed=2,this.lastTotal=4,this.lastChar[0]=t[t.length-2],this.lastChar[1]=t[t.length-1],n.slice(0,-1)}return n}return this.lastNeed=1,this.lastTotal=2,this.lastChar[0]=t[t.length-1],t.toString("utf16le",e,t.length-1)}function c(t){var e=t&&t.length?this.write(t):"";if(this.lastNeed){var n=this.lastTotal-this.lastNeed;return e+this.lastChar.toString("utf16le",0,n)}return e}function f(t,e){var n=(t.length-e)%3;return 0===n?t.toString("base64",e):(this.lastNeed=3-n,this.lastTotal=3,1===n?this.lastChar[0]=t[t.length-1]:(this.lastChar[0]=t[t.length-2],this.lastChar[1]=t[t.length-1]),t.toString("base64",e,t.length-n))}function l(t){var e=t&&t.length?this.write(t):"";return this.lastNeed?e+this.lastChar.toString("base64",0,3-this.lastNeed):e}function h(t){return t.toString(this.encoding)}function d(t){return t&&t.length?this.write(t):""}e.StringDecoder=o,o.prototype.write=function(t){if(0===t.length)return"";var e,n;if(this.lastNeed){if(void 0===(e=this.fillLast(t)))return"";n=this.lastNeed,this.lastNeed=0}else n=0;return n=0)return i>0&&(t.lastNeed=i-1),i;if(--r=0)return i>0&&(t.lastNeed=i-2),i;if(--r=0)return i>0&&(2===i?i=0:t.lastNeed=i-3),i;return 0}(this,t,e);if(!this.lastNeed)return t.toString("utf8",e);this.lastTotal=n;var r=t.length-(n-this.lastNeed);return t.copy(this.lastChar,0,r),t.toString("utf8",e,r)},o.prototype.fillLast=function(t){if(this.lastNeed<=t.length)return t.copy(this.lastChar,this.lastTotal-this.lastNeed,0,this.lastNeed),this.lastChar.toString(this.encoding,0,this.lastTotal);t.copy(this.lastChar,this.lastTotal-this.lastNeed,0,t.length),this.lastNeed-=t.length}},function(t,e,n){"use strict";var r=n(8).Buffer,i=n(2),o=n(192),a=new Array(16),s=[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,7,4,13,1,10,6,15,3,12,0,9,5,2,14,11,8,3,10,14,4,9,15,8,1,2,7,0,6,13,11,5,12,1,9,11,10,0,8,12,4,13,3,7,15,14,5,6,2,4,0,5,9,7,12,2,10,14,1,3,8,11,6,15,13],u=[5,14,7,0,9,2,11,4,13,6,15,8,1,10,3,12,6,11,3,7,0,13,5,10,14,15,8,12,4,9,1,2,15,5,1,3,7,14,6,9,11,8,12,2,10,0,4,13,8,6,4,1,3,11,15,0,5,12,2,13,9,7,10,14,12,15,10,4,1,5,8,7,6,2,13,14,0,3,9,11],c=[11,14,15,12,5,8,7,9,11,13,14,15,6,7,9,8,7,6,8,13,11,9,7,15,7,12,15,9,11,7,13,12,11,13,6,7,14,9,13,15,14,8,13,6,5,12,7,5,11,12,14,15,14,15,9,8,9,14,5,6,8,6,5,12,9,15,5,11,6,8,13,12,5,12,13,14,11,8,5,6],f=[8,9,9,11,13,15,15,5,7,7,8,11,14,14,12,6,9,13,15,7,12,8,9,11,7,7,12,7,6,15,13,11,9,7,15,11,8,6,6,14,12,13,5,14,13,13,7,5,15,5,8,11,14,14,6,14,6,9,12,9,12,5,15,8,8,5,12,9,12,5,14,6,8,13,6,5,15,13,11,11],l=[0,1518500249,1859775393,2400959708,2840853838],h=[1352829926,1548603684,1836072691,2053994217,0];function d(){o.call(this,64),this._a=1732584193,this._b=4023233417,this._c=2562383102,this._d=271733878,this._e=3285377520}function p(t,e){return t<>>32-e}function g(t,e,n,r,i,o,a,s){return p(t+(e^n^r)+o+a|0,s)+i|0}function y(t,e,n,r,i,o,a,s){return p(t+(e&n|~e&r)+o+a|0,s)+i|0}function b(t,e,n,r,i,o,a,s){return p(t+((e|~n)^r)+o+a|0,s)+i|0}function m(t,e,n,r,i,o,a,s){return p(t+(e&r|n&~r)+o+a|0,s)+i|0}function v(t,e,n,r,i,o,a,s){return p(t+(e^(n|~r))+o+a|0,s)+i|0}i(d,o),d.prototype._update=function(){for(var t=a,e=0;e<16;++e)t[e]=this._block.readInt32LE(4*e);for(var n=0|this._a,r=0|this._b,i=0|this._c,o=0|this._d,d=0|this._e,_=0|this._a,w=0|this._b,x=0|this._c,k=0|this._d,E=0|this._e,A=0;A<80;A+=1){var S,T;A<16?(S=g(n,r,i,o,d,t[s[A]],l[0],c[A]),T=v(_,w,x,k,E,t[u[A]],h[0],f[A])):A<32?(S=y(n,r,i,o,d,t[s[A]],l[1],c[A]),T=m(_,w,x,k,E,t[u[A]],h[1],f[A])):A<48?(S=b(n,r,i,o,d,t[s[A]],l[2],c[A]),T=b(_,w,x,k,E,t[u[A]],h[2],f[A])):A<64?(S=m(n,r,i,o,d,t[s[A]],l[3],c[A]),T=y(_,w,x,k,E,t[u[A]],h[3],f[A])):(S=v(n,r,i,o,d,t[s[A]],l[4],c[A]),T=g(_,w,x,k,E,t[u[A]],h[4],f[A])),n=d,d=o,o=p(i,10),i=r,r=S,_=E,E=k,k=p(x,10),x=w,w=T}var M=this._b+i+k|0;this._b=this._c+o+E|0,this._c=this._d+d+_|0,this._d=this._e+n+w|0,this._e=this._a+r+x|0,this._a=M},d.prototype._digest=function(){this._block[this._blockOffset++]=128,this._blockOffset>56&&(this._block.fill(0,this._blockOffset,64),this._update(),this._blockOffset=0),this._block.fill(0,this._blockOffset,56),this._block.writeUInt32LE(this._length[0],56),this._block.writeUInt32LE(this._length[1],60),this._update();var t=r.alloc?r.alloc(20):new r(20);return t.writeInt32LE(this._a,0),t.writeInt32LE(this._b,4),t.writeInt32LE(this._c,8),t.writeInt32LE(this._d,12),t.writeInt32LE(this._e,16),t},t.exports=d},function(t,e,n){(e=t.exports=function(t){t=t.toLowerCase();var n=e[t];if(!n)throw new Error(t+" is not supported (we accept pull requests)");return new n}).sha=n(432),e.sha1=n(433),e.sha224=n(434),e.sha256=n(197),e.sha384=n(435),e.sha512=n(198)},function(t,e,n){"use strict";e.utils=n(441),e.Cipher=n(442),e.DES=n(443),e.CBC=n(444),e.EDE=n(445)},function(t,e,n){var r=n(446),i=n(454),o=n(208);e.createCipher=e.Cipher=r.createCipher,e.createCipheriv=e.Cipheriv=r.createCipheriv,e.createDecipher=e.Decipher=i.createDecipher,e.createDecipheriv=e.Decipheriv=i.createDecipheriv,e.listCiphers=e.getCiphers=function(){return Object.keys(o)}},function(t,e,n){var r={ECB:n(447),CBC:n(448),CFB:n(449),CFB8:n(450),CFB1:n(451),OFB:n(452),CTR:n(206),GCM:n(206)},i=n(208);for(var o in i)i[o].module=r[i[o].mode];t.exports=i},function(t,e,n){var r;function i(t){this.rand=t}if(t.exports=function(t){return r||(r=new i(null)),r.generate(t)},t.exports.Rand=i,i.prototype.generate=function(t){return this._rand(t)},i.prototype._rand=function(t){if(this.rand.getBytes)return this.rand.getBytes(t);for(var e=new Uint8Array(t),n=0;n=0||!n.umod(t.prime1)||!n.umod(t.prime2);)n=new r(i(e));return n}t.exports=o,o.getr=a}).call(this,n(8).Buffer)},function(t,e,n){"use strict";var r=e;r.version=n(463).version,r.utils=n(16),r.rand=n(123),r.curve=n(214),r.curves=n(126),r.ec=n(474),r.eddsa=n(478)},function(t,e,n){"use strict";var r,i=e,o=n(127),a=n(214),s=n(16).assert;function u(t){"short"===t.type?this.curve=new a.short(t):"edwards"===t.type?this.curve=new a.edwards(t):this.curve=new a.mont(t),this.g=this.curve.g,this.n=this.curve.n,this.hash=t.hash,s(this.g.validate(),"Invalid curve"),s(this.g.mul(this.n).isInfinity(),"Invalid curve, G*N != O")}function c(t,e){Object.defineProperty(i,t,{configurable:!0,enumerable:!0,get:function(){var n=new u(e);return Object.defineProperty(i,t,{configurable:!0,enumerable:!0,value:n}),n}})}i.PresetCurve=u,c("p192",{type:"short",prime:"p192",p:"ffffffff ffffffff ffffffff fffffffe ffffffff ffffffff",a:"ffffffff ffffffff ffffffff fffffffe ffffffff fffffffc",b:"64210519 e59c80e7 0fa7e9ab 72243049 feb8deec c146b9b1",n:"ffffffff ffffffff ffffffff 99def836 146bc9b1 b4d22831",hash:o.sha256,gRed:!1,g:["188da80e b03090f6 7cbf20eb 43a18800 f4ff0afd 82ff1012","07192b95 ffc8da78 631011ed 6b24cdd5 73f977a1 1e794811"]}),c("p224",{type:"short",prime:"p224",p:"ffffffff ffffffff ffffffff ffffffff 00000000 00000000 00000001",a:"ffffffff ffffffff ffffffff fffffffe ffffffff ffffffff fffffffe",b:"b4050a85 0c04b3ab f5413256 5044b0b7 d7bfd8ba 270b3943 2355ffb4",n:"ffffffff ffffffff ffffffff ffff16a2 e0b8f03e 13dd2945 5c5c2a3d",hash:o.sha256,gRed:!1,g:["b70e0cbd 6bb4bf7f 321390b9 4a03c1d3 56c21122 343280d6 115c1d21","bd376388 b5f723fb 4c22dfe6 cd4375a0 5a074764 44d58199 85007e34"]}),c("p256",{type:"short",prime:null,p:"ffffffff 00000001 00000000 00000000 00000000 ffffffff ffffffff ffffffff",a:"ffffffff 00000001 00000000 00000000 00000000 ffffffff ffffffff fffffffc",b:"5ac635d8 aa3a93e7 b3ebbd55 769886bc 651d06b0 cc53b0f6 3bce3c3e 27d2604b",n:"ffffffff 00000000 ffffffff ffffffff bce6faad a7179e84 f3b9cac2 fc632551",hash:o.sha256,gRed:!1,g:["6b17d1f2 e12c4247 f8bce6e5 63a440f2 77037d81 2deb33a0 f4a13945 d898c296","4fe342e2 fe1a7f9b 8ee7eb4a 7c0f9e16 2bce3357 6b315ece cbb64068 37bf51f5"]}),c("p384",{type:"short",prime:null,p:"ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff fffffffe ffffffff 00000000 00000000 ffffffff",a:"ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff fffffffe ffffffff 00000000 00000000 fffffffc",b:"b3312fa7 e23ee7e4 988e056b e3f82d19 181d9c6e fe814112 0314088f 5013875a c656398d 8a2ed19d 2a85c8ed d3ec2aef",n:"ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff c7634d81 f4372ddf 581a0db2 48b0a77a ecec196a ccc52973",hash:o.sha384,gRed:!1,g:["aa87ca22 be8b0537 8eb1c71e f320ad74 6e1d3b62 8ba79b98 59f741e0 82542a38 5502f25d bf55296c 3a545e38 72760ab7","3617de4a 96262c6f 5d9e98bf 9292dc29 f8f41dbd 289a147c e9da3113 b5f0b8c0 0a60b1ce 1d7e819d 7a431d7c 90ea0e5f"]}),c("p521",{type:"short",prime:null,p:"000001ff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff",a:"000001ff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff fffffffc",b:"00000051 953eb961 8e1c9a1f 929a21a0 b68540ee a2da725b 99b315f3 b8b48991 8ef109e1 56193951 ec7e937b 1652c0bd 3bb1bf07 3573df88 3d2c34f1 ef451fd4 6b503f00",n:"000001ff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff fffffffa 51868783 bf2f966b 7fcc0148 f709a5d0 3bb5c9b8 899c47ae bb6fb71e 91386409",hash:o.sha512,gRed:!1,g:["000000c6 858e06b7 0404e9cd 9e3ecb66 2395b442 9c648139 053fb521 f828af60 6b4d3dba a14b5e77 efe75928 fe1dc127 a2ffa8de 3348b3c1 856a429b f97e7e31 c2e5bd66","00000118 39296a78 9a3bc004 5c8a5fb4 2c7d1bd9 98f54449 579b4468 17afbd17 273e662c 97ee7299 5ef42640 c550b901 3fad0761 353c7086 a272c240 88be9476 9fd16650"]}),c("curve25519",{type:"mont",prime:"p25519",p:"7fffffffffffffff ffffffffffffffff ffffffffffffffff ffffffffffffffed",a:"76d06",b:"1",n:"1000000000000000 0000000000000000 14def9dea2f79cd6 5812631a5cf5d3ed",hash:o.sha256,gRed:!1,g:["9"]}),c("ed25519",{type:"edwards",prime:"p25519",p:"7fffffffffffffff ffffffffffffffff ffffffffffffffff ffffffffffffffed",a:"-1",c:"1",d:"52036cee2b6ffe73 8cc740797779e898 00700a4d4141d8ab 75eb4dca135978a3",n:"1000000000000000 0000000000000000 14def9dea2f79cd6 5812631a5cf5d3ed",hash:o.sha256,gRed:!1,g:["216936d3cd6e53fec0a4e231fdd6dc5c692cc7609525a7b2c9562d608f25d51a","6666666666666666666666666666666666666666666666666666666666666658"]});try{r=n(473)}catch(t){r=void 0}c("secp256k1",{type:"short",prime:"k256",p:"ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff fffffffe fffffc2f",a:"0",b:"7",n:"ffffffff ffffffff ffffffff fffffffe baaedce6 af48a03b bfd25e8c d0364141",h:"1",hash:o.sha256,beta:"7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee",lambda:"5363ad4cc05c30e0a5261c028812645a122e22ea20816678df02967c1b23bd72",basis:[{a:"3086d221a7d46bcde86c90e49284eb15",b:"-e4437ed6010e88286f547fa90abfe4c3"},{a:"114ca50f7a8e2f3f657c1108d9d44cfd8",b:"3086d221a7d46bcde86c90e49284eb15"}],gRed:!1,g:["79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798","483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8",r]})},function(t,e,n){var r=e;r.utils=n(21),r.common=n(56),r.sha=n(467),r.ripemd=n(471),r.hmac=n(472),r.sha1=r.sha.sha1,r.sha256=r.sha.sha256,r.sha224=r.sha.sha224,r.sha384=r.sha.sha384,r.sha512=r.sha.sha512,r.ripemd160=r.ripemd.ripemd160},function(t,e,n){var r=n(14);t.exports=function(t,e){var n=t.append("foreignObject").attr("width","100000"),i=n.append("xhtml:div");i.attr("xmlns","http://www.w3.org/1999/xhtml");var o=e.label;switch(typeof o){case"function":i.insert(o);break;case"object":i.insert((function(){return o}));break;default:i.html(o)}r.applyStyle(i,e.labelStyle),i.style("display","inline-block"),i.style("white-space","nowrap");var a=i.node().getBoundingClientRect();return n.attr("width",a.width).attr("height",a.height),n}},function(t,e){},function(t,e,n){var r=n(61),i=n(92),o=n(66),a=n(264),s=n(270),u=n(136),c=n(137),f=n(273),l=n(274),h=n(141),d=n(275),p=n(41),g=n(279),y=n(280),b=n(146),m=n(6),v=n(39),_=n(284),w=n(13),x=n(286),k=n(27),E=1,A=2,S=4,T="[object Arguments]",M="[object Function]",D="[object GeneratorFunction]",C="[object Object]",O={};O[T]=O["[object Array]"]=O["[object ArrayBuffer]"]=O["[object DataView]"]=O["[object Boolean]"]=O["[object Date]"]=O["[object Float32Array]"]=O["[object Float64Array]"]=O["[object Int8Array]"]=O["[object Int16Array]"]=O["[object Int32Array]"]=O["[object Map]"]=O["[object Number]"]=O[C]=O["[object RegExp]"]=O["[object Set]"]=O["[object String]"]=O["[object Symbol]"]=O["[object Uint8Array]"]=O["[object Uint8ClampedArray]"]=O["[object Uint16Array]"]=O["[object Uint32Array]"]=!0,O["[object Error]"]=O[M]=O["[object WeakMap]"]=!1,t.exports=function t(e,n,R,I,N,B){var L,P=n&E,F=n&A,q=n&S;if(R&&(L=N?R(e,I,N,B):R(e)),void 0!==L)return L;if(!w(e))return e;var j=m(e);if(j){if(L=g(e),!P)return c(e,L)}else{var U=p(e),z=U==M||U==D;if(v(e))return u(e,P);if(U==C||U==T||z&&!N){if(L=F||z?{}:b(e),!P)return F?l(e,s(L,e)):f(e,a(L,e))}else{if(!O[U])return N?e:{};L=y(e,U,P)}}B||(B=new r);var Y=B.get(e);if(Y)return Y;B.set(e,L),x(e)?e.forEach((function(r){L.add(t(r,n,R,r,e,B))})):_(e)&&e.forEach((function(r,i){L.set(i,t(r,n,R,i,e,B))}));var V=q?F?d:h:F?keysIn:k,H=j?void 0:V(e);return i(H||e,(function(r,i){H&&(r=e[i=r]),o(L,i,t(r,n,R,i,e,B))})),L}},function(t,e,n){(function(e){var n="object"==typeof e&&e&&e.Object===Object&&e;t.exports=n}).call(this,n(11))},function(t,e){var n=Function.prototype.toString;t.exports=function(t){if(null!=t){try{return n.call(t)}catch(t){}try{return t+""}catch(t){}}return""}},function(t,e,n){var r=n(32),i=function(){try{var t=r(Object,"defineProperty");return t({},"",{}),t}catch(t){}}();t.exports=i},function(t,e,n){var r=n(265),i=n(50),o=n(6),a=n(39),s=n(68),u=n(51),c=Object.prototype.hasOwnProperty;t.exports=function(t,e){var n=o(t),f=!n&&i(t),l=!n&&!f&&a(t),h=!n&&!f&&!l&&u(t),d=n||f||l||h,p=d?r(t.length,String):[],g=p.length;for(var y in t)!e&&!c.call(t,y)||d&&("length"==y||l&&("offset"==y||"parent"==y)||h&&("buffer"==y||"byteLength"==y||"byteOffset"==y)||s(y,g))||p.push(y);return p}},function(t,e){t.exports=function(t,e){return function(n){return t(e(n))}}},function(t,e,n){(function(t){var r=n(18),i=e&&!e.nodeType&&e,o=i&&"object"==typeof t&&t&&!t.nodeType&&t,a=o&&o.exports===i?r.Buffer:void 0,s=a?a.allocUnsafe:void 0;t.exports=function(t,e){if(e)return t.slice();var n=t.length,r=s?s(n):new t.constructor(n);return t.copy(r),r}}).call(this,n(9)(t))},function(t,e){t.exports=function(t,e){var n=-1,r=t.length;for(e||(e=Array(r));++nh))return!1;var p=f.get(t);if(p&&f.get(e))return p==e;var g=-1,y=!0,b=n&s?new r:void 0;for(f.set(t,e),f.set(e,t);++g0&&(o=u.removeMin(),(a=s[o]).distance!==Number.POSITIVE_INFINITY);)r(o).forEach(c);return s}(t,String(e),n||o,r||function(e){return t.outEdges(e)})};var o=r.constant(1)},function(t,e,n){var r=n(12);function i(){this._arr=[],this._keyIndices={}}t.exports=i,i.prototype.size=function(){return this._arr.length},i.prototype.keys=function(){return this._arr.map((function(t){return t.key}))},i.prototype.has=function(t){return r.has(this._keyIndices,t)},i.prototype.priority=function(t){var e=this._keyIndices[t];if(void 0!==e)return this._arr[e].priority},i.prototype.min=function(){if(0===this.size())throw new Error("Queue underflow");return this._arr[0].key},i.prototype.add=function(t,e){var n=this._keyIndices;if(t=String(t),!r.has(n,t)){var i=this._arr,o=i.length;return n[t]=o,i.push({key:t,priority:e}),this._decrease(o),!0}return!1},i.prototype.removeMin=function(){this._swap(0,this._arr.length-1);var t=this._arr.pop();return delete this._keyIndices[t.key],this._heapify(0),t.key},i.prototype.decrease=function(t,e){var n=this._keyIndices[t];if(e>this._arr[n].priority)throw new Error("New priority is greater than current priority. Key: "+t+" Old: "+this._arr[n].priority+" New: "+e);this._arr[n].priority=e,this._decrease(n)},i.prototype._heapify=function(t){var e=this._arr,n=2*t,r=n+1,i=t;n>1].priority2?e[2]:void 0;for(c&&o(e[0],e[1],c)&&(r=1);++n1&&a.sort((function(t,e){var r=t.x-n.x,i=t.y-n.y,o=Math.sqrt(r*r+i*i),a=e.x-n.x,s=e.y-n.y,u=Math.sqrt(a*a+s*s);return oMath.abs(a)*c?(s<0&&(c=-c),n=0===s?0:c*a/s,r=c):(a<0&&(u=-u),n=u,r=0===a?0:u*s/a);return{x:i+n,y:o+r}}},function(t,e){var n={}.toString;t.exports=Array.isArray||function(t){return"[object Array]"==n.call(t)}},function(t,e,n){"use strict";var r=n(3).Buffer,i=n(112).Transform;function o(t){i.call(this),this._block=r.allocUnsafe(t),this._blockSize=t,this._blockOffset=0,this._length=[0,0,0,0],this._finalized=!1}n(2)(o,i),o.prototype._transform=function(t,e,n){var r=null;try{this.update(t,e)}catch(t){r=t}n(r)},o.prototype._flush=function(t){var e=null;try{this.push(this.digest())}catch(t){e=t}t(e)},o.prototype.update=function(t,e){if(function(t,e){if(!r.isBuffer(t)&&"string"!=typeof t)throw new TypeError(e+" must be a string or a buffer")}(t,"Data"),this._finalized)throw new Error("Digest already called");r.isBuffer(t)||(t=r.from(t,e));for(var n=this._block,i=0;this._blockOffset+t.length-i>=this._blockSize;){for(var o=this._blockOffset;o0;++a)this._length[a]+=s,(s=this._length[a]/4294967296|0)>0&&(this._length[a]-=4294967296*s);return this},o.prototype._update=function(){throw new Error("_update is not implemented")},o.prototype.digest=function(t){if(this._finalized)throw new Error("Digest already called");this._finalized=!0;var e=this._digest();void 0!==t&&(e=e.toString(t)),this._block.fill(0),this._blockOffset=0;for(var n=0;n<4;++n)this._length[n]=0;return e},o.prototype._digest=function(){throw new Error("_digest is not implemented")},t.exports=o},function(t,e,n){"use strict";(function(e,r){var i=n(78);t.exports=v;var o,a=n(191);v.ReadableState=m;n(113).EventEmitter;var s=function(t,e){return t.listeners(e).length},u=n(194),c=n(115).Buffer,f=e.Uint8Array||function(){};var l=n(54);l.inherits=n(2);var h=n(421),d=void 0;d=h&&h.debuglog?h.debuglog("stream"):function(){};var p,g=n(422),y=n(195);l.inherits(v,u);var b=["error","close","destroy","pause","resume"];function m(t,e){t=t||{};var r=e instanceof(o=o||n(35));this.objectMode=!!t.objectMode,r&&(this.objectMode=this.objectMode||!!t.readableObjectMode);var i=t.highWaterMark,a=t.readableHighWaterMark,s=this.objectMode?16:16384;this.highWaterMark=i||0===i?i:r&&(a||0===a)?a:s,this.highWaterMark=Math.floor(this.highWaterMark),this.buffer=new g,this.length=0,this.pipes=null,this.pipesCount=0,this.flowing=null,this.ended=!1,this.endEmitted=!1,this.reading=!1,this.sync=!0,this.needReadable=!1,this.emittedReadable=!1,this.readableListening=!1,this.resumeScheduled=!1,this.destroyed=!1,this.defaultEncoding=t.defaultEncoding||"utf8",this.awaitDrain=0,this.readingMore=!1,this.decoder=null,this.encoding=null,t.encoding&&(p||(p=n(117).StringDecoder),this.decoder=new p(t.encoding),this.encoding=t.encoding)}function v(t){if(o=o||n(35),!(this instanceof v))return new v(t);this._readableState=new m(t,this),this.readable=!0,t&&("function"==typeof t.read&&(this._read=t.read),"function"==typeof t.destroy&&(this._destroy=t.destroy)),u.call(this)}function _(t,e,n,r,i){var o,a=t._readableState;null===e?(a.reading=!1,function(t,e){if(e.ended)return;if(e.decoder){var n=e.decoder.end();n&&n.length&&(e.buffer.push(n),e.length+=e.objectMode?1:n.length)}e.ended=!0,E(t)}(t,a)):(i||(o=function(t,e){var n;r=e,c.isBuffer(r)||r instanceof f||"string"==typeof e||void 0===e||t.objectMode||(n=new TypeError("Invalid non-string/buffer chunk"));var r;return n}(a,e)),o?t.emit("error",o):a.objectMode||e&&e.length>0?("string"==typeof e||a.objectMode||Object.getPrototypeOf(e)===c.prototype||(e=function(t){return c.from(t)}(e)),r?a.endEmitted?t.emit("error",new Error("stream.unshift() after end event")):w(t,a,e,!0):a.ended?t.emit("error",new Error("stream.push() after EOF")):(a.reading=!1,a.decoder&&!n?(e=a.decoder.write(e),a.objectMode||0!==e.length?w(t,a,e,!1):S(t,a)):w(t,a,e,!1))):r||(a.reading=!1));return function(t){return!t.ended&&(t.needReadable||t.lengthe.highWaterMark&&(e.highWaterMark=function(t){return t>=x?t=x:(t--,t|=t>>>1,t|=t>>>2,t|=t>>>4,t|=t>>>8,t|=t>>>16,t++),t}(t)),t<=e.length?t:e.ended?e.length:(e.needReadable=!0,0))}function E(t){var e=t._readableState;e.needReadable=!1,e.emittedReadable||(d("emitReadable",e.flowing),e.emittedReadable=!0,e.sync?i.nextTick(A,t):A(t))}function A(t){d("emit readable"),t.emit("readable"),C(t)}function S(t,e){e.readingMore||(e.readingMore=!0,i.nextTick(T,t,e))}function T(t,e){for(var n=e.length;!e.reading&&!e.flowing&&!e.ended&&e.length=e.length?(n=e.decoder?e.buffer.join(""):1===e.buffer.length?e.buffer.head.data:e.buffer.concat(e.length),e.buffer.clear()):n=function(t,e,n){var r;to.length?o.length:t;if(a===o.length?i+=o:i+=o.slice(0,t),0===(t-=a)){a===o.length?(++r,n.next?e.head=n.next:e.head=e.tail=null):(e.head=n,n.data=o.slice(a));break}++r}return e.length-=r,i}(t,e):function(t,e){var n=c.allocUnsafe(t),r=e.head,i=1;r.data.copy(n),t-=r.data.length;for(;r=r.next;){var o=r.data,a=t>o.length?o.length:t;if(o.copy(n,n.length-t,0,a),0===(t-=a)){a===o.length?(++i,r.next?e.head=r.next:e.head=e.tail=null):(e.head=r,r.data=o.slice(a));break}++i}return e.length-=i,n}(t,e);return r}(t,e.buffer,e.decoder),n);var n}function R(t){var e=t._readableState;if(e.length>0)throw new Error('"endReadable()" called on non-empty stream');e.endEmitted||(e.ended=!0,i.nextTick(I,e,t))}function I(t,e){t.endEmitted||0!==t.length||(t.endEmitted=!0,e.readable=!1,e.emit("end"))}function N(t,e){for(var n=0,r=t.length;n=e.highWaterMark||e.ended))return d("read: emitReadable",e.length,e.ended),0===e.length&&e.ended?R(this):E(this),null;if(0===(t=k(t,e))&&e.ended)return 0===e.length&&R(this),null;var r,i=e.needReadable;return d("need readable",i),(0===e.length||e.length-t0?O(t,e):null)?(e.needReadable=!0,t=0):e.length-=t,0===e.length&&(e.ended||(e.needReadable=!0),n!==t&&e.ended&&R(this)),null!==r&&this.emit("data",r),r},v.prototype._read=function(t){this.emit("error",new Error("_read() is not implemented"))},v.prototype.pipe=function(t,e){var n=this,o=this._readableState;switch(o.pipesCount){case 0:o.pipes=t;break;case 1:o.pipes=[o.pipes,t];break;default:o.pipes.push(t)}o.pipesCount+=1,d("pipe count=%d opts=%j",o.pipesCount,e);var u=(!e||!1!==e.end)&&t!==r.stdout&&t!==r.stderr?f:v;function c(e,r){d("onunpipe"),e===n&&r&&!1===r.hasUnpiped&&(r.hasUnpiped=!0,d("cleanup"),t.removeListener("close",b),t.removeListener("finish",m),t.removeListener("drain",l),t.removeListener("error",y),t.removeListener("unpipe",c),n.removeListener("end",f),n.removeListener("end",v),n.removeListener("data",g),h=!0,!o.awaitDrain||t._writableState&&!t._writableState.needDrain||l())}function f(){d("onend"),t.end()}o.endEmitted?i.nextTick(u):n.once("end",u),t.on("unpipe",c);var l=function(t){return function(){var e=t._readableState;d("pipeOnDrain",e.awaitDrain),e.awaitDrain&&e.awaitDrain--,0===e.awaitDrain&&s(t,"data")&&(e.flowing=!0,C(t))}}(n);t.on("drain",l);var h=!1;var p=!1;function g(e){d("ondata"),p=!1,!1!==t.write(e)||p||((1===o.pipesCount&&o.pipes===t||o.pipesCount>1&&-1!==N(o.pipes,t))&&!h&&(d("false write response, pause",n._readableState.awaitDrain),n._readableState.awaitDrain++,p=!0),n.pause())}function y(e){d("onerror",e),v(),t.removeListener("error",y),0===s(t,"error")&&t.emit("error",e)}function b(){t.removeListener("finish",m),v()}function m(){d("onfinish"),t.removeListener("close",b),v()}function v(){d("unpipe"),n.unpipe(t)}return n.on("data",g),function(t,e,n){if("function"==typeof t.prependListener)return t.prependListener(e,n);t._events&&t._events[e]?a(t._events[e])?t._events[e].unshift(n):t._events[e]=[n,t._events[e]]:t.on(e,n)}(t,"error",y),t.once("close",b),t.once("finish",m),t.emit("pipe",n),o.flowing||(d("pipe resume"),n.resume()),t},v.prototype.unpipe=function(t){var e=this._readableState,n={hasUnpiped:!1};if(0===e.pipesCount)return this;if(1===e.pipesCount)return t&&t!==e.pipes?this:(t||(t=e.pipes),e.pipes=null,e.pipesCount=0,e.flowing=!1,t&&t.emit("unpipe",this,n),this);if(!t){var r=e.pipes,i=e.pipesCount;e.pipes=null,e.pipesCount=0,e.flowing=!1;for(var o=0;o>>2|t<<30)^(t>>>13|t<<19)^(t>>>22|t<<10)}function h(t){return(t>>>6|t<<26)^(t>>>11|t<<21)^(t>>>25|t<<7)}function d(t){return(t>>>7|t<<25)^(t>>>18|t<<14)^t>>>3}r(u,i),u.prototype.init=function(){return this._a=1779033703,this._b=3144134277,this._c=1013904242,this._d=2773480762,this._e=1359893119,this._f=2600822924,this._g=528734635,this._h=1541459225,this},u.prototype._update=function(t){for(var e,n=this._w,r=0|this._a,i=0|this._b,o=0|this._c,s=0|this._d,u=0|this._e,p=0|this._f,g=0|this._g,y=0|this._h,b=0;b<16;++b)n[b]=t.readInt32BE(4*b);for(;b<64;++b)n[b]=0|(((e=n[b-2])>>>17|e<<15)^(e>>>19|e<<13)^e>>>10)+n[b-7]+d(n[b-15])+n[b-16];for(var m=0;m<64;++m){var v=y+h(u)+c(u,p,g)+a[m]+n[m]|0,_=l(r)+f(r,i,o)|0;y=g,g=p,p=u,u=s+v|0,s=o,o=i,i=r,r=v+_|0}this._a=r+this._a|0,this._b=i+this._b|0,this._c=o+this._c|0,this._d=s+this._d|0,this._e=u+this._e|0,this._f=p+this._f|0,this._g=g+this._g|0,this._h=y+this._h|0},u.prototype._hash=function(){var t=o.allocUnsafe(32);return t.writeInt32BE(this._a,0),t.writeInt32BE(this._b,4),t.writeInt32BE(this._c,8),t.writeInt32BE(this._d,12),t.writeInt32BE(this._e,16),t.writeInt32BE(this._f,20),t.writeInt32BE(this._g,24),t.writeInt32BE(this._h,28),t},t.exports=u},function(t,e,n){var r=n(2),i=n(45),o=n(3).Buffer,a=[1116352408,3609767458,1899447441,602891725,3049323471,3964484399,3921009573,2173295548,961987163,4081628472,1508970993,3053834265,2453635748,2937671579,2870763221,3664609560,3624381080,2734883394,310598401,1164996542,607225278,1323610764,1426881987,3590304994,1925078388,4068182383,2162078206,991336113,2614888103,633803317,3248222580,3479774868,3835390401,2666613458,4022224774,944711139,264347078,2341262773,604807628,2007800933,770255983,1495990901,1249150122,1856431235,1555081692,3175218132,1996064986,2198950837,2554220882,3999719339,2821834349,766784016,2952996808,2566594879,3210313671,3203337956,3336571891,1034457026,3584528711,2466948901,113926993,3758326383,338241895,168717936,666307205,1188179964,773529912,1546045734,1294757372,1522805485,1396182291,2643833823,1695183700,2343527390,1986661051,1014477480,2177026350,1206759142,2456956037,344077627,2730485921,1290863460,2820302411,3158454273,3259730800,3505952657,3345764771,106217008,3516065817,3606008344,3600352804,1432725776,4094571909,1467031594,275423344,851169720,430227734,3100823752,506948616,1363258195,659060556,3750685593,883997877,3785050280,958139571,3318307427,1322822218,3812723403,1537002063,2003034995,1747873779,3602036899,1955562222,1575990012,2024104815,1125592928,2227730452,2716904306,2361852424,442776044,2428436474,593698344,2756734187,3733110249,3204031479,2999351573,3329325298,3815920427,3391569614,3928383900,3515267271,566280711,3940187606,3454069534,4118630271,4000239992,116418474,1914138554,174292421,2731055270,289380356,3203993006,460393269,320620315,685471733,587496836,852142971,1086792851,1017036298,365543100,1126000580,2618297676,1288033470,3409855158,1501505948,4234509866,1607167915,987167468,1816402316,1246189591],s=new Array(160);function u(){this.init(),this._w=s,i.call(this,128,112)}function c(t,e,n){return n^t&(e^n)}function f(t,e,n){return t&e|n&(t|e)}function l(t,e){return(t>>>28|e<<4)^(e>>>2|t<<30)^(e>>>7|t<<25)}function h(t,e){return(t>>>14|e<<18)^(t>>>18|e<<14)^(e>>>9|t<<23)}function d(t,e){return(t>>>1|e<<31)^(t>>>8|e<<24)^t>>>7}function p(t,e){return(t>>>1|e<<31)^(t>>>8|e<<24)^(t>>>7|e<<25)}function g(t,e){return(t>>>19|e<<13)^(e>>>29|t<<3)^t>>>6}function y(t,e){return(t>>>19|e<<13)^(e>>>29|t<<3)^(t>>>6|e<<26)}function b(t,e){return t>>>0>>0?1:0}r(u,i),u.prototype.init=function(){return this._ah=1779033703,this._bh=3144134277,this._ch=1013904242,this._dh=2773480762,this._eh=1359893119,this._fh=2600822924,this._gh=528734635,this._hh=1541459225,this._al=4089235720,this._bl=2227873595,this._cl=4271175723,this._dl=1595750129,this._el=2917565137,this._fl=725511199,this._gl=4215389547,this._hl=327033209,this},u.prototype._update=function(t){for(var e=this._w,n=0|this._ah,r=0|this._bh,i=0|this._ch,o=0|this._dh,s=0|this._eh,u=0|this._fh,m=0|this._gh,v=0|this._hh,_=0|this._al,w=0|this._bl,x=0|this._cl,k=0|this._dl,E=0|this._el,A=0|this._fl,S=0|this._gl,T=0|this._hl,M=0;M<32;M+=2)e[M]=t.readInt32BE(4*M),e[M+1]=t.readInt32BE(4*M+4);for(;M<160;M+=2){var D=e[M-30],C=e[M-30+1],O=d(D,C),R=p(C,D),I=g(D=e[M-4],C=e[M-4+1]),N=y(C,D),B=e[M-14],L=e[M-14+1],P=e[M-32],F=e[M-32+1],q=R+L|0,j=O+B+b(q,R)|0;j=(j=j+I+b(q=q+N|0,N)|0)+P+b(q=q+F|0,F)|0,e[M]=j,e[M+1]=q}for(var U=0;U<160;U+=2){j=e[U],q=e[U+1];var z=f(n,r,i),Y=f(_,w,x),V=l(n,_),H=l(_,n),$=h(s,E),G=h(E,s),W=a[U],K=a[U+1],X=c(s,u,m),Z=c(E,A,S),J=T+G|0,Q=v+$+b(J,T)|0;Q=(Q=(Q=Q+X+b(J=J+Z|0,Z)|0)+W+b(J=J+K|0,K)|0)+j+b(J=J+q|0,q)|0;var tt=H+Y|0,et=V+z+b(tt,H)|0;v=m,T=S,m=u,S=A,u=s,A=E,s=o+Q+b(E=k+J|0,k)|0,o=i,k=x,i=r,x=w,r=n,w=_,n=Q+et+b(_=J+tt|0,J)|0}this._al=this._al+_|0,this._bl=this._bl+w|0,this._cl=this._cl+x|0,this._dl=this._dl+k|0,this._el=this._el+E|0,this._fl=this._fl+A|0,this._gl=this._gl+S|0,this._hl=this._hl+T|0,this._ah=this._ah+n+b(this._al,_)|0,this._bh=this._bh+r+b(this._bl,w)|0,this._ch=this._ch+i+b(this._cl,x)|0,this._dh=this._dh+o+b(this._dl,k)|0,this._eh=this._eh+s+b(this._el,E)|0,this._fh=this._fh+u+b(this._fl,A)|0,this._gh=this._gh+m+b(this._gl,S)|0,this._hh=this._hh+v+b(this._hl,T)|0},u.prototype._hash=function(){var t=o.allocUnsafe(64);function e(e,n,r){t.writeInt32BE(e,r),t.writeInt32BE(n,r+4)}return e(this._ah,this._al,0),e(this._bh,this._bl,8),e(this._ch,this._cl,16),e(this._dh,this._dl,24),e(this._eh,this._el,32),e(this._fh,this._fl,40),e(this._gh,this._gl,48),e(this._hh,this._hl,56),t},t.exports=u},function(t,e,n){"use strict";var r=n(2),i=n(436),o=n(31),a=n(3).Buffer,s=n(200),u=n(118),c=n(119),f=a.alloc(128);function l(t,e){o.call(this,"digest"),"string"==typeof e&&(e=a.from(e));var n="sha512"===t||"sha384"===t?128:64;(this._alg=t,this._key=e,e.length>n)?e=("rmd160"===t?new u:c(t)).update(e).digest():e.lengthn||o!=o)throw new TypeError("Bad key length")}}).call(this,n(8).Buffer)},function(t,e,n){(function(e){var n;e.browser?n="utf-8":n=parseInt(e.version.split(".")[0].slice(1),10)>=6?"utf-8":"binary";t.exports=n}).call(this,n(7))},function(t,e,n){var r=n(200),i=n(118),o=n(119),a=n(203),s=n(204),u=n(3).Buffer,c=u.alloc(128),f={md5:16,sha1:20,sha224:28,sha256:32,sha384:48,sha512:64,rmd160:20,ripemd160:20};function l(t,e,n){var a=function(t){return"rmd160"===t||"ripemd160"===t?function(t){return(new i).update(t).digest()}:"md5"===t?r:function(e){return o(t).update(e).digest()}}(t),s="sha512"===t||"sha384"===t?128:64;e.length>s?e=a(e):e.lengtht;)n.ishrn(1);if(n.isEven()&&n.iadd(s),n.testn(1)||n.iadd(u),e.cmp(u)){if(!e.cmp(c))for(;n.mod(f).cmp(l);)n.iadd(d)}else for(;n.mod(o).cmp(h);)n.iadd(d);if(y(p=n.shrn(1))&&y(n)&&b(p)&&b(n)&&a.test(p)&&a.test(n))return n}}},function(t,e,n){var r=n(5),i=n(123);function o(t){this.rand=t||new i.Rand}t.exports=o,o.create=function(t){return new o(t)},o.prototype._randbelow=function(t){var e=t.bitLength(),n=Math.ceil(e/8);do{var i=new r(this.rand.generate(n))}while(i.cmp(t)>=0);return i},o.prototype._randrange=function(t,e){var n=e.sub(t);return t.add(this._randbelow(n))},o.prototype.test=function(t,e,n){var i=t.bitLength(),o=r.mont(t),a=new r(1).toRed(o);e||(e=Math.max(1,i/48|0));for(var s=t.subn(1),u=0;!s.testn(u);u++);for(var c=t.shrn(u),f=s.toRed(o);e>0;e--){var l=this._randrange(new r(2),s);n&&n(l);var h=l.toRed(o).redPow(c);if(0!==h.cmp(a)&&0!==h.cmp(f)){for(var d=1;d0;e--){var f=this._randrange(new r(2),a),l=t.gcd(f);if(0!==l.cmpn(1))return l;var h=f.toRed(i).redPow(u);if(0!==h.cmp(o)&&0!==h.cmp(c)){for(var d=1;d>8,a=255&i;o?n.push(o,a):n.push(a)}return n},r.zero2=i,r.toHex=o,r.encode=function(t,e){return"hex"===e?o(t):t}},function(t,e,n){"use strict";var r=e;r.base=n(81),r.short=n(464),r.mont=n(465),r.edwards=n(466)},function(t,e,n){"use strict";var r=n(21).rotr32;function i(t,e,n){return t&e^~t&n}function o(t,e,n){return t&e^t&n^e&n}function a(t,e,n){return t^e^n}e.ft_1=function(t,e,n,r){return 0===t?i(e,n,r):1===t||3===t?a(e,n,r):2===t?o(e,n,r):void 0},e.ch32=i,e.maj32=o,e.p32=a,e.s0_256=function(t){return r(t,2)^r(t,13)^r(t,22)},e.s1_256=function(t){return r(t,6)^r(t,11)^r(t,25)},e.g0_256=function(t){return r(t,7)^r(t,18)^t>>>3},e.g1_256=function(t){return r(t,17)^r(t,19)^t>>>10}},function(t,e,n){"use strict";var r=n(21),i=n(56),o=n(215),a=n(15),s=r.sum32,u=r.sum32_4,c=r.sum32_5,f=o.ch32,l=o.maj32,h=o.s0_256,d=o.s1_256,p=o.g0_256,g=o.g1_256,y=i.BlockHash,b=[1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298];function m(){if(!(this instanceof m))return new m;y.call(this),this.h=[1779033703,3144134277,1013904242,2773480762,1359893119,2600822924,528734635,1541459225],this.k=b,this.W=new Array(64)}r.inherits(m,y),t.exports=m,m.blockSize=512,m.outSize=256,m.hmacStrength=192,m.padLength=64,m.prototype._update=function(t,e){for(var n=this.W,r=0;r<16;r++)n[r]=t[e+r];for(;r>6],i=0==(32&n);if(31==(31&n)){var o=n;for(n=0;128==(128&o);){if(o=t.readUInt8(e),t.isError(o))return o;n<<=7,n|=127&o}}else n&=31;return{cls:r,primitive:i,tag:n,tagStr:s.tag[n]}}function l(t,e,n){var r=t.readUInt8(n);if(t.isError(r))return r;if(!e&&128===r)return null;if(0==(128&r))return r;var i=127&r;if(i>4)return t.error("length octect is too long");r=0;for(var o=0;o=31)return r.error("Multi-octet tag encoding unsupported");e||(i|=32);return i|=s.tagClassByName[n||"universal"]<<6}(t,e,n,this.reporter);if(r.length<128)return(o=new i(2))[0]=a,o[1]=r.length,this._createEncoderBuffer([o,r]);for(var u=1,c=r.length;c>=256;c>>=8)u++;(o=new i(2+u))[0]=a,o[1]=128|u;c=1+u;for(var f=r.length;f>0;c--,f>>=8)o[c]=255&f;return this._createEncoderBuffer([o,r])},c.prototype._encodeStr=function(t,e){if("bitstr"===e)return this._createEncoderBuffer([0|t.unused,t.data]);if("bmpstr"===e){for(var n=new i(2*t.length),r=0;r=40)return this.reporter.error("Second objid identifier OOB");t.splice(0,2,40*t[0]+t[1])}var o=0;for(r=0;r=128;a>>=7)o++}var s=new i(o),u=s.length-1;for(r=t.length-1;r>=0;r--){a=t[r];for(s[u--]=127&a;(a>>=7)>0;)s[u--]=128|127&a}return this._createEncoderBuffer(s)},c.prototype._encodeTime=function(t,e){var n,r=new Date(t);return"gentime"===e?n=[f(r.getFullYear()),f(r.getUTCMonth()+1),f(r.getUTCDate()),f(r.getUTCHours()),f(r.getUTCMinutes()),f(r.getUTCSeconds()),"Z"].join(""):"utctime"===e?n=[f(r.getFullYear()%100),f(r.getUTCMonth()+1),f(r.getUTCDate()),f(r.getUTCHours()),f(r.getUTCMinutes()),f(r.getUTCSeconds()),"Z"].join(""):this.reporter.error("Encoding "+e+" time is not supported yet"),this._encodeStr(n,"octstr")},c.prototype._encodeNull=function(){return this._createEncoderBuffer("")},c.prototype._encodeInt=function(t,e){if("string"==typeof t){if(!e)return this.reporter.error("String int or enum given, but no values map");if(!e.hasOwnProperty(t))return this.reporter.error("Values map doesn't contain: "+JSON.stringify(t));t=e[t]}if("number"!=typeof t&&!i.isBuffer(t)){var n=t.toArray();!t.sign&&128&n[0]&&n.unshift(0),t=new i(n)}if(i.isBuffer(t)){var r=t.length;0===t.length&&r++;var o=new i(r);return t.copy(o),0===t.length&&(o[0]=0),this._createEncoderBuffer(o)}if(t<128)return this._createEncoderBuffer(t);if(t<256)return this._createEncoderBuffer([0,t]);r=1;for(var a=t;a>=256;a>>=8)r++;for(a=(o=new Array(r)).length-1;a>=0;a--)o[a]=255&t,t>>=8;return 128&o[0]&&o.unshift(0),this._createEncoderBuffer(new i(o))},c.prototype._encodeBool=function(t){return this._createEncoderBuffer(t?255:0)},c.prototype._use=function(t,e){return"function"==typeof t&&(t=t(e)),t._getEncoder("der").tree},c.prototype._skipDefault=function(t,e,n){var r,i=this._baseState;if(null===i.default)return!1;var o=t.join();if(void 0===i.defaultBuffer&&(i.defaultBuffer=this._encodeValue(i.default,e,n).join()),o.length!==i.defaultBuffer.length)return!1;for(r=0;r\u20D2|\u205F\u200A|\u219D\u0338|\u2202\u0338|\u2220\u20D2|\u2229\uFE00|\u222A\uFE00|\u223C\u20D2|\u223D\u0331|\u223E\u0333|\u2242\u0338|\u224B\u0338|\u224D\u20D2|\u224E\u0338|\u224F\u0338|\u2250\u0338|\u2261\u20E5|\u2264\u20D2|\u2265\u20D2|\u2266\u0338|\u2267\u0338|\u2268\uFE00|\u2269\uFE00|\u226A\u0338|\u226A\u20D2|\u226B\u0338|\u226B\u20D2|\u227F\u0338|\u2282\u20D2|\u2283\u20D2|\u228A\uFE00|\u228B\uFE00|\u228F\u0338|\u2290\u0338|\u2293\uFE00|\u2294\uFE00|\u22B4\u20D2|\u22B5\u20D2|\u22D8\u0338|\u22D9\u0338|\u22DA\uFE00|\u22DB\uFE00|\u22F5\u0338|\u22F9\u0338|\u2933\u0338|\u29CF\u0338|\u29D0\u0338|\u2A6D\u0338|\u2A70\u0338|\u2A7D\u0338|\u2A7E\u0338|\u2AA1\u0338|\u2AA2\u0338|\u2AAC\uFE00|\u2AAD\uFE00|\u2AAF\u0338|\u2AB0\u0338|\u2AC5\u0338|\u2AC6\u0338|\u2ACB\uFE00|\u2ACC\uFE00|\u2AFD\u20E5|[\xA0-\u0113\u0116-\u0122\u0124-\u012B\u012E-\u014D\u0150-\u017E\u0192\u01B5\u01F5\u0237\u02C6\u02C7\u02D8-\u02DD\u0311\u0391-\u03A1\u03A3-\u03A9\u03B1-\u03C9\u03D1\u03D2\u03D5\u03D6\u03DC\u03DD\u03F0\u03F1\u03F5\u03F6\u0401-\u040C\u040E-\u044F\u0451-\u045C\u045E\u045F\u2002-\u2005\u2007-\u2010\u2013-\u2016\u2018-\u201A\u201C-\u201E\u2020-\u2022\u2025\u2026\u2030-\u2035\u2039\u203A\u203E\u2041\u2043\u2044\u204F\u2057\u205F-\u2063\u20AC\u20DB\u20DC\u2102\u2105\u210A-\u2113\u2115-\u211E\u2122\u2124\u2127-\u2129\u212C\u212D\u212F-\u2131\u2133-\u2138\u2145-\u2148\u2153-\u215E\u2190-\u219B\u219D-\u21A7\u21A9-\u21AE\u21B0-\u21B3\u21B5-\u21B7\u21BA-\u21DB\u21DD\u21E4\u21E5\u21F5\u21FD-\u2205\u2207-\u2209\u220B\u220C\u220F-\u2214\u2216-\u2218\u221A\u221D-\u2238\u223A-\u2257\u2259\u225A\u225C\u225F-\u2262\u2264-\u228B\u228D-\u229B\u229D-\u22A5\u22A7-\u22B0\u22B2-\u22BB\u22BD-\u22DB\u22DE-\u22E3\u22E6-\u22F7\u22F9-\u22FE\u2305\u2306\u2308-\u2310\u2312\u2313\u2315\u2316\u231C-\u231F\u2322\u2323\u232D\u232E\u2336\u233D\u233F\u237C\u23B0\u23B1\u23B4-\u23B6\u23DC-\u23DF\u23E2\u23E7\u2423\u24C8\u2500\u2502\u250C\u2510\u2514\u2518\u251C\u2524\u252C\u2534\u253C\u2550-\u256C\u2580\u2584\u2588\u2591-\u2593\u25A1\u25AA\u25AB\u25AD\u25AE\u25B1\u25B3-\u25B5\u25B8\u25B9\u25BD-\u25BF\u25C2\u25C3\u25CA\u25CB\u25EC\u25EF\u25F8-\u25FC\u2605\u2606\u260E\u2640\u2642\u2660\u2663\u2665\u2666\u266A\u266D-\u266F\u2713\u2717\u2720\u2736\u2758\u2772\u2773\u27C8\u27C9\u27E6-\u27ED\u27F5-\u27FA\u27FC\u27FF\u2902-\u2905\u290C-\u2913\u2916\u2919-\u2920\u2923-\u292A\u2933\u2935-\u2939\u293C\u293D\u2945\u2948-\u294B\u294E-\u2976\u2978\u2979\u297B-\u297F\u2985\u2986\u298B-\u2996\u299A\u299C\u299D\u29A4-\u29B7\u29B9\u29BB\u29BC\u29BE-\u29C5\u29C9\u29CD-\u29D0\u29DC-\u29DE\u29E3-\u29E5\u29EB\u29F4\u29F6\u2A00-\u2A02\u2A04\u2A06\u2A0C\u2A0D\u2A10-\u2A17\u2A22-\u2A27\u2A29\u2A2A\u2A2D-\u2A31\u2A33-\u2A3C\u2A3F\u2A40\u2A42-\u2A4D\u2A50\u2A53-\u2A58\u2A5A-\u2A5D\u2A5F\u2A66\u2A6A\u2A6D-\u2A75\u2A77-\u2A9A\u2A9D-\u2AA2\u2AA4-\u2AB0\u2AB3-\u2AC8\u2ACB\u2ACC\u2ACF-\u2ADB\u2AE4\u2AE6-\u2AE9\u2AEB-\u2AF3\u2AFD\uFB00-\uFB04]|\uD835[\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDCCF\uDD04\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDD6B]/g,l={"­":"shy","‌":"zwnj","‍":"zwj","‎":"lrm","⁣":"ic","⁢":"it","⁡":"af","‏":"rlm","​":"ZeroWidthSpace","⁠":"NoBreak","̑":"DownBreve","⃛":"tdot","⃜":"DotDot","\t":"Tab","\n":"NewLine"," ":"puncsp"," ":"MediumSpace"," ":"thinsp"," ":"hairsp"," ":"emsp13"," ":"ensp"," ":"emsp14"," ":"emsp"," ":"numsp"," ":"nbsp","  ":"ThickSpace","‾":"oline",_:"lowbar","‐":"dash","–":"ndash","—":"mdash","―":"horbar",",":"comma",";":"semi","⁏":"bsemi",":":"colon","⩴":"Colone","!":"excl","¡":"iexcl","?":"quest","¿":"iquest",".":"period","‥":"nldr","…":"mldr","·":"middot","'":"apos","‘":"lsquo","’":"rsquo","‚":"sbquo","‹":"lsaquo","›":"rsaquo",'"':"quot","“":"ldquo","”":"rdquo","„":"bdquo","«":"laquo","»":"raquo","(":"lpar",")":"rpar","[":"lsqb","]":"rsqb","{":"lcub","}":"rcub","⌈":"lceil","⌉":"rceil","⌊":"lfloor","⌋":"rfloor","⦅":"lopar","⦆":"ropar","⦋":"lbrke","⦌":"rbrke","⦍":"lbrkslu","⦎":"rbrksld","⦏":"lbrksld","⦐":"rbrkslu","⦑":"langd","⦒":"rangd","⦓":"lparlt","⦔":"rpargt","⦕":"gtlPar","⦖":"ltrPar","⟦":"lobrk","⟧":"robrk","⟨":"lang","⟩":"rang","⟪":"Lang","⟫":"Rang","⟬":"loang","⟭":"roang","❲":"lbbrk","❳":"rbbrk","‖":"Vert","§":"sect","¶":"para","@":"commat","*":"ast","/":"sol",undefined:null,"&":"amp","#":"num","%":"percnt","‰":"permil","‱":"pertenk","†":"dagger","‡":"Dagger","•":"bull","⁃":"hybull","′":"prime","″":"Prime","‴":"tprime","⁗":"qprime","‵":"bprime","⁁":"caret","`":"grave","´":"acute","˜":"tilde","^":"Hat","¯":"macr","˘":"breve","˙":"dot","¨":"die","˚":"ring","˝":"dblac","¸":"cedil","˛":"ogon","ˆ":"circ","ˇ":"caron","°":"deg","©":"copy","®":"reg","℗":"copysr","℘":"wp","℞":"rx","℧":"mho","℩":"iiota","←":"larr","↚":"nlarr","→":"rarr","↛":"nrarr","↑":"uarr","↓":"darr","↔":"harr","↮":"nharr","↕":"varr","↖":"nwarr","↗":"nearr","↘":"searr","↙":"swarr","↝":"rarrw","↝̸":"nrarrw","↞":"Larr","↟":"Uarr","↠":"Rarr","↡":"Darr","↢":"larrtl","↣":"rarrtl","↤":"mapstoleft","↥":"mapstoup","↦":"map","↧":"mapstodown","↩":"larrhk","↪":"rarrhk","↫":"larrlp","↬":"rarrlp","↭":"harrw","↰":"lsh","↱":"rsh","↲":"ldsh","↳":"rdsh","↵":"crarr","↶":"cularr","↷":"curarr","↺":"olarr","↻":"orarr","↼":"lharu","↽":"lhard","↾":"uharr","↿":"uharl","⇀":"rharu","⇁":"rhard","⇂":"dharr","⇃":"dharl","⇄":"rlarr","⇅":"udarr","⇆":"lrarr","⇇":"llarr","⇈":"uuarr","⇉":"rrarr","⇊":"ddarr","⇋":"lrhar","⇌":"rlhar","⇐":"lArr","⇍":"nlArr","⇑":"uArr","⇒":"rArr","⇏":"nrArr","⇓":"dArr","⇔":"iff","⇎":"nhArr","⇕":"vArr","⇖":"nwArr","⇗":"neArr","⇘":"seArr","⇙":"swArr","⇚":"lAarr","⇛":"rAarr","⇝":"zigrarr","⇤":"larrb","⇥":"rarrb","⇵":"duarr","⇽":"loarr","⇾":"roarr","⇿":"hoarr","∀":"forall","∁":"comp","∂":"part","∂̸":"npart","∃":"exist","∄":"nexist","∅":"empty","∇":"Del","∈":"in","∉":"notin","∋":"ni","∌":"notni","϶":"bepsi","∏":"prod","∐":"coprod","∑":"sum","+":"plus","±":"pm","÷":"div","×":"times","<":"lt","≮":"nlt","<⃒":"nvlt","=":"equals","≠":"ne","=⃥":"bne","⩵":"Equal",">":"gt","≯":"ngt",">⃒":"nvgt","¬":"not","|":"vert","¦":"brvbar","−":"minus","∓":"mp","∔":"plusdo","⁄":"frasl","∖":"setmn","∗":"lowast","∘":"compfn","√":"Sqrt","∝":"prop","∞":"infin","∟":"angrt","∠":"ang","∠⃒":"nang","∡":"angmsd","∢":"angsph","∣":"mid","∤":"nmid","∥":"par","∦":"npar","∧":"and","∨":"or","∩":"cap","∩︀":"caps","∪":"cup","∪︀":"cups","∫":"int","∬":"Int","∭":"tint","⨌":"qint","∮":"oint","∯":"Conint","∰":"Cconint","∱":"cwint","∲":"cwconint","∳":"awconint","∴":"there4","∵":"becaus","∶":"ratio","∷":"Colon","∸":"minusd","∺":"mDDot","∻":"homtht","∼":"sim","≁":"nsim","∼⃒":"nvsim","∽":"bsim","∽̱":"race","∾":"ac","∾̳":"acE","∿":"acd","≀":"wr","≂":"esim","≂̸":"nesim","≃":"sime","≄":"nsime","≅":"cong","≇":"ncong","≆":"simne","≈":"ap","≉":"nap","≊":"ape","≋":"apid","≋̸":"napid","≌":"bcong","≍":"CupCap","≭":"NotCupCap","≍⃒":"nvap","≎":"bump","≎̸":"nbump","≏":"bumpe","≏̸":"nbumpe","≐":"doteq","≐̸":"nedot","≑":"eDot","≒":"efDot","≓":"erDot","≔":"colone","≕":"ecolon","≖":"ecir","≗":"cire","≙":"wedgeq","≚":"veeeq","≜":"trie","≟":"equest","≡":"equiv","≢":"nequiv","≡⃥":"bnequiv","≤":"le","≰":"nle","≤⃒":"nvle","≥":"ge","≱":"nge","≥⃒":"nvge","≦":"lE","≦̸":"nlE","≧":"gE","≧̸":"ngE","≨︀":"lvnE","≨":"lnE","≩":"gnE","≩︀":"gvnE","≪":"ll","≪̸":"nLtv","≪⃒":"nLt","≫":"gg","≫̸":"nGtv","≫⃒":"nGt","≬":"twixt","≲":"lsim","≴":"nlsim","≳":"gsim","≵":"ngsim","≶":"lg","≸":"ntlg","≷":"gl","≹":"ntgl","≺":"pr","⊀":"npr","≻":"sc","⊁":"nsc","≼":"prcue","⋠":"nprcue","≽":"sccue","⋡":"nsccue","≾":"prsim","≿":"scsim","≿̸":"NotSucceedsTilde","⊂":"sub","⊄":"nsub","⊂⃒":"vnsub","⊃":"sup","⊅":"nsup","⊃⃒":"vnsup","⊆":"sube","⊈":"nsube","⊇":"supe","⊉":"nsupe","⊊︀":"vsubne","⊊":"subne","⊋︀":"vsupne","⊋":"supne","⊍":"cupdot","⊎":"uplus","⊏":"sqsub","⊏̸":"NotSquareSubset","⊐":"sqsup","⊐̸":"NotSquareSuperset","⊑":"sqsube","⋢":"nsqsube","⊒":"sqsupe","⋣":"nsqsupe","⊓":"sqcap","⊓︀":"sqcaps","⊔":"sqcup","⊔︀":"sqcups","⊕":"oplus","⊖":"ominus","⊗":"otimes","⊘":"osol","⊙":"odot","⊚":"ocir","⊛":"oast","⊝":"odash","⊞":"plusb","⊟":"minusb","⊠":"timesb","⊡":"sdotb","⊢":"vdash","⊬":"nvdash","⊣":"dashv","⊤":"top","⊥":"bot","⊧":"models","⊨":"vDash","⊭":"nvDash","⊩":"Vdash","⊮":"nVdash","⊪":"Vvdash","⊫":"VDash","⊯":"nVDash","⊰":"prurel","⊲":"vltri","⋪":"nltri","⊳":"vrtri","⋫":"nrtri","⊴":"ltrie","⋬":"nltrie","⊴⃒":"nvltrie","⊵":"rtrie","⋭":"nrtrie","⊵⃒":"nvrtrie","⊶":"origof","⊷":"imof","⊸":"mumap","⊹":"hercon","⊺":"intcal","⊻":"veebar","⊽":"barvee","⊾":"angrtvb","⊿":"lrtri","⋀":"Wedge","⋁":"Vee","⋂":"xcap","⋃":"xcup","⋄":"diam","⋅":"sdot","⋆":"Star","⋇":"divonx","⋈":"bowtie","⋉":"ltimes","⋊":"rtimes","⋋":"lthree","⋌":"rthree","⋍":"bsime","⋎":"cuvee","⋏":"cuwed","⋐":"Sub","⋑":"Sup","⋒":"Cap","⋓":"Cup","⋔":"fork","⋕":"epar","⋖":"ltdot","⋗":"gtdot","⋘":"Ll","⋘̸":"nLl","⋙":"Gg","⋙̸":"nGg","⋚︀":"lesg","⋚":"leg","⋛":"gel","⋛︀":"gesl","⋞":"cuepr","⋟":"cuesc","⋦":"lnsim","⋧":"gnsim","⋨":"prnsim","⋩":"scnsim","⋮":"vellip","⋯":"ctdot","⋰":"utdot","⋱":"dtdot","⋲":"disin","⋳":"isinsv","⋴":"isins","⋵":"isindot","⋵̸":"notindot","⋶":"notinvc","⋷":"notinvb","⋹":"isinE","⋹̸":"notinE","⋺":"nisd","⋻":"xnis","⋼":"nis","⋽":"notnivc","⋾":"notnivb","⌅":"barwed","⌆":"Barwed","⌌":"drcrop","⌍":"dlcrop","⌎":"urcrop","⌏":"ulcrop","⌐":"bnot","⌒":"profline","⌓":"profsurf","⌕":"telrec","⌖":"target","⌜":"ulcorn","⌝":"urcorn","⌞":"dlcorn","⌟":"drcorn","⌢":"frown","⌣":"smile","⌭":"cylcty","⌮":"profalar","⌶":"topbot","⌽":"ovbar","⌿":"solbar","⍼":"angzarr","⎰":"lmoust","⎱":"rmoust","⎴":"tbrk","⎵":"bbrk","⎶":"bbrktbrk","⏜":"OverParenthesis","⏝":"UnderParenthesis","⏞":"OverBrace","⏟":"UnderBrace","⏢":"trpezium","⏧":"elinters","␣":"blank","─":"boxh","│":"boxv","┌":"boxdr","┐":"boxdl","└":"boxur","┘":"boxul","├":"boxvr","┤":"boxvl","┬":"boxhd","┴":"boxhu","┼":"boxvh","═":"boxH","║":"boxV","╒":"boxdR","╓":"boxDr","╔":"boxDR","╕":"boxdL","╖":"boxDl","╗":"boxDL","╘":"boxuR","╙":"boxUr","╚":"boxUR","╛":"boxuL","╜":"boxUl","╝":"boxUL","╞":"boxvR","╟":"boxVr","╠":"boxVR","╡":"boxvL","╢":"boxVl","╣":"boxVL","╤":"boxHd","╥":"boxhD","╦":"boxHD","╧":"boxHu","╨":"boxhU","╩":"boxHU","╪":"boxvH","╫":"boxVh","╬":"boxVH","▀":"uhblk","▄":"lhblk","█":"block","░":"blk14","▒":"blk12","▓":"blk34","□":"squ","▪":"squf","▫":"EmptyVerySmallSquare","▭":"rect","▮":"marker","▱":"fltns","△":"xutri","▴":"utrif","▵":"utri","▸":"rtrif","▹":"rtri","▽":"xdtri","▾":"dtrif","▿":"dtri","◂":"ltrif","◃":"ltri","◊":"loz","○":"cir","◬":"tridot","◯":"xcirc","◸":"ultri","◹":"urtri","◺":"lltri","◻":"EmptySmallSquare","◼":"FilledSmallSquare","★":"starf","☆":"star","☎":"phone","♀":"female","♂":"male","♠":"spades","♣":"clubs","♥":"hearts","♦":"diams","♪":"sung","✓":"check","✗":"cross","✠":"malt","✶":"sext","❘":"VerticalSeparator","⟈":"bsolhsub","⟉":"suphsol","⟵":"xlarr","⟶":"xrarr","⟷":"xharr","⟸":"xlArr","⟹":"xrArr","⟺":"xhArr","⟼":"xmap","⟿":"dzigrarr","⤂":"nvlArr","⤃":"nvrArr","⤄":"nvHarr","⤅":"Map","⤌":"lbarr","⤍":"rbarr","⤎":"lBarr","⤏":"rBarr","⤐":"RBarr","⤑":"DDotrahd","⤒":"UpArrowBar","⤓":"DownArrowBar","⤖":"Rarrtl","⤙":"latail","⤚":"ratail","⤛":"lAtail","⤜":"rAtail","⤝":"larrfs","⤞":"rarrfs","⤟":"larrbfs","⤠":"rarrbfs","⤣":"nwarhk","⤤":"nearhk","⤥":"searhk","⤦":"swarhk","⤧":"nwnear","⤨":"toea","⤩":"tosa","⤪":"swnwar","⤳":"rarrc","⤳̸":"nrarrc","⤵":"cudarrr","⤶":"ldca","⤷":"rdca","⤸":"cudarrl","⤹":"larrpl","⤼":"curarrm","⤽":"cularrp","⥅":"rarrpl","⥈":"harrcir","⥉":"Uarrocir","⥊":"lurdshar","⥋":"ldrushar","⥎":"LeftRightVector","⥏":"RightUpDownVector","⥐":"DownLeftRightVector","⥑":"LeftUpDownVector","⥒":"LeftVectorBar","⥓":"RightVectorBar","⥔":"RightUpVectorBar","⥕":"RightDownVectorBar","⥖":"DownLeftVectorBar","⥗":"DownRightVectorBar","⥘":"LeftUpVectorBar","⥙":"LeftDownVectorBar","⥚":"LeftTeeVector","⥛":"RightTeeVector","⥜":"RightUpTeeVector","⥝":"RightDownTeeVector","⥞":"DownLeftTeeVector","⥟":"DownRightTeeVector","⥠":"LeftUpTeeVector","⥡":"LeftDownTeeVector","⥢":"lHar","⥣":"uHar","⥤":"rHar","⥥":"dHar","⥦":"luruhar","⥧":"ldrdhar","⥨":"ruluhar","⥩":"rdldhar","⥪":"lharul","⥫":"llhard","⥬":"rharul","⥭":"lrhard","⥮":"udhar","⥯":"duhar","⥰":"RoundImplies","⥱":"erarr","⥲":"simrarr","⥳":"larrsim","⥴":"rarrsim","⥵":"rarrap","⥶":"ltlarr","⥸":"gtrarr","⥹":"subrarr","⥻":"suplarr","⥼":"lfisht","⥽":"rfisht","⥾":"ufisht","⥿":"dfisht","⦚":"vzigzag","⦜":"vangrt","⦝":"angrtvbd","⦤":"ange","⦥":"range","⦦":"dwangle","⦧":"uwangle","⦨":"angmsdaa","⦩":"angmsdab","⦪":"angmsdac","⦫":"angmsdad","⦬":"angmsdae","⦭":"angmsdaf","⦮":"angmsdag","⦯":"angmsdah","⦰":"bemptyv","⦱":"demptyv","⦲":"cemptyv","⦳":"raemptyv","⦴":"laemptyv","⦵":"ohbar","⦶":"omid","⦷":"opar","⦹":"operp","⦻":"olcross","⦼":"odsold","⦾":"olcir","⦿":"ofcir","⧀":"olt","⧁":"ogt","⧂":"cirscir","⧃":"cirE","⧄":"solb","⧅":"bsolb","⧉":"boxbox","⧍":"trisb","⧎":"rtriltri","⧏":"LeftTriangleBar","⧏̸":"NotLeftTriangleBar","⧐":"RightTriangleBar","⧐̸":"NotRightTriangleBar","⧜":"iinfin","⧝":"infintie","⧞":"nvinfin","⧣":"eparsl","⧤":"smeparsl","⧥":"eqvparsl","⧫":"lozf","⧴":"RuleDelayed","⧶":"dsol","⨀":"xodot","⨁":"xoplus","⨂":"xotime","⨄":"xuplus","⨆":"xsqcup","⨍":"fpartint","⨐":"cirfnint","⨑":"awint","⨒":"rppolint","⨓":"scpolint","⨔":"npolint","⨕":"pointint","⨖":"quatint","⨗":"intlarhk","⨢":"pluscir","⨣":"plusacir","⨤":"simplus","⨥":"plusdu","⨦":"plussim","⨧":"plustwo","⨩":"mcomma","⨪":"minusdu","⨭":"loplus","⨮":"roplus","⨯":"Cross","⨰":"timesd","⨱":"timesbar","⨳":"smashp","⨴":"lotimes","⨵":"rotimes","⨶":"otimesas","⨷":"Otimes","⨸":"odiv","⨹":"triplus","⨺":"triminus","⨻":"tritime","⨼":"iprod","⨿":"amalg","⩀":"capdot","⩂":"ncup","⩃":"ncap","⩄":"capand","⩅":"cupor","⩆":"cupcap","⩇":"capcup","⩈":"cupbrcap","⩉":"capbrcup","⩊":"cupcup","⩋":"capcap","⩌":"ccups","⩍":"ccaps","⩐":"ccupssm","⩓":"And","⩔":"Or","⩕":"andand","⩖":"oror","⩗":"orslope","⩘":"andslope","⩚":"andv","⩛":"orv","⩜":"andd","⩝":"ord","⩟":"wedbar","⩦":"sdote","⩪":"simdot","⩭":"congdot","⩭̸":"ncongdot","⩮":"easter","⩯":"apacir","⩰":"apE","⩰̸":"napE","⩱":"eplus","⩲":"pluse","⩳":"Esim","⩷":"eDDot","⩸":"equivDD","⩹":"ltcir","⩺":"gtcir","⩻":"ltquest","⩼":"gtquest","⩽":"les","⩽̸":"nles","⩾":"ges","⩾̸":"nges","⩿":"lesdot","⪀":"gesdot","⪁":"lesdoto","⪂":"gesdoto","⪃":"lesdotor","⪄":"gesdotol","⪅":"lap","⪆":"gap","⪇":"lne","⪈":"gne","⪉":"lnap","⪊":"gnap","⪋":"lEg","⪌":"gEl","⪍":"lsime","⪎":"gsime","⪏":"lsimg","⪐":"gsiml","⪑":"lgE","⪒":"glE","⪓":"lesges","⪔":"gesles","⪕":"els","⪖":"egs","⪗":"elsdot","⪘":"egsdot","⪙":"el","⪚":"eg","⪝":"siml","⪞":"simg","⪟":"simlE","⪠":"simgE","⪡":"LessLess","⪡̸":"NotNestedLessLess","⪢":"GreaterGreater","⪢̸":"NotNestedGreaterGreater","⪤":"glj","⪥":"gla","⪦":"ltcc","⪧":"gtcc","⪨":"lescc","⪩":"gescc","⪪":"smt","⪫":"lat","⪬":"smte","⪬︀":"smtes","⪭":"late","⪭︀":"lates","⪮":"bumpE","⪯":"pre","⪯̸":"npre","⪰":"sce","⪰̸":"nsce","⪳":"prE","⪴":"scE","⪵":"prnE","⪶":"scnE","⪷":"prap","⪸":"scap","⪹":"prnap","⪺":"scnap","⪻":"Pr","⪼":"Sc","⪽":"subdot","⪾":"supdot","⪿":"subplus","⫀":"supplus","⫁":"submult","⫂":"supmult","⫃":"subedot","⫄":"supedot","⫅":"subE","⫅̸":"nsubE","⫆":"supE","⫆̸":"nsupE","⫇":"subsim","⫈":"supsim","⫋︀":"vsubnE","⫋":"subnE","⫌︀":"vsupnE","⫌":"supnE","⫏":"csub","⫐":"csup","⫑":"csube","⫒":"csupe","⫓":"subsup","⫔":"supsub","⫕":"subsub","⫖":"supsup","⫗":"suphsub","⫘":"supdsub","⫙":"forkv","⫚":"topfork","⫛":"mlcp","⫤":"Dashv","⫦":"Vdashl","⫧":"Barv","⫨":"vBar","⫩":"vBarv","⫫":"Vbar","⫬":"Not","⫭":"bNot","⫮":"rnmid","⫯":"cirmid","⫰":"midcir","⫱":"topcir","⫲":"nhpar","⫳":"parsim","⫽":"parsl","⫽⃥":"nparsl","♭":"flat","♮":"natur","♯":"sharp","¤":"curren","¢":"cent",$:"dollar","£":"pound","¥":"yen","€":"euro","¹":"sup1","½":"half","⅓":"frac13","¼":"frac14","⅕":"frac15","⅙":"frac16","⅛":"frac18","²":"sup2","⅔":"frac23","⅖":"frac25","³":"sup3","¾":"frac34","⅗":"frac35","⅜":"frac38","⅘":"frac45","⅚":"frac56","⅝":"frac58","⅞":"frac78","𝒶":"ascr","𝕒":"aopf","𝔞":"afr","𝔸":"Aopf","𝔄":"Afr","𝒜":"Ascr","ª":"ordf","á":"aacute","Á":"Aacute","à":"agrave","À":"Agrave","ă":"abreve","Ă":"Abreve","â":"acirc","Â":"Acirc","å":"aring","Å":"angst","ä":"auml","Ä":"Auml","ã":"atilde","Ã":"Atilde","ą":"aogon","Ą":"Aogon","ā":"amacr","Ā":"Amacr","æ":"aelig","Æ":"AElig","𝒷":"bscr","𝕓":"bopf","𝔟":"bfr","𝔹":"Bopf","ℬ":"Bscr","𝔅":"Bfr","𝔠":"cfr","𝒸":"cscr","𝕔":"copf","ℭ":"Cfr","𝒞":"Cscr","ℂ":"Copf","ć":"cacute","Ć":"Cacute","ĉ":"ccirc","Ĉ":"Ccirc","č":"ccaron","Č":"Ccaron","ċ":"cdot","Ċ":"Cdot","ç":"ccedil","Ç":"Ccedil","℅":"incare","𝔡":"dfr","ⅆ":"dd","𝕕":"dopf","𝒹":"dscr","𝒟":"Dscr","𝔇":"Dfr","ⅅ":"DD","𝔻":"Dopf","ď":"dcaron","Ď":"Dcaron","đ":"dstrok","Đ":"Dstrok","ð":"eth","Ð":"ETH","ⅇ":"ee","ℯ":"escr","𝔢":"efr","𝕖":"eopf","ℰ":"Escr","𝔈":"Efr","𝔼":"Eopf","é":"eacute","É":"Eacute","è":"egrave","È":"Egrave","ê":"ecirc","Ê":"Ecirc","ě":"ecaron","Ě":"Ecaron","ë":"euml","Ë":"Euml","ė":"edot","Ė":"Edot","ę":"eogon","Ę":"Eogon","ē":"emacr","Ē":"Emacr","𝔣":"ffr","𝕗":"fopf","𝒻":"fscr","𝔉":"Ffr","𝔽":"Fopf","ℱ":"Fscr","ff":"fflig","ffi":"ffilig","ffl":"ffllig","fi":"filig",fj:"fjlig","fl":"fllig","ƒ":"fnof","ℊ":"gscr","𝕘":"gopf","𝔤":"gfr","𝒢":"Gscr","𝔾":"Gopf","𝔊":"Gfr","ǵ":"gacute","ğ":"gbreve","Ğ":"Gbreve","ĝ":"gcirc","Ĝ":"Gcirc","ġ":"gdot","Ġ":"Gdot","Ģ":"Gcedil","𝔥":"hfr","ℎ":"planckh","𝒽":"hscr","𝕙":"hopf","ℋ":"Hscr","ℌ":"Hfr","ℍ":"Hopf","ĥ":"hcirc","Ĥ":"Hcirc","ℏ":"hbar","ħ":"hstrok","Ħ":"Hstrok","𝕚":"iopf","𝔦":"ifr","𝒾":"iscr","ⅈ":"ii","𝕀":"Iopf","ℐ":"Iscr","ℑ":"Im","í":"iacute","Í":"Iacute","ì":"igrave","Ì":"Igrave","î":"icirc","Î":"Icirc","ï":"iuml","Ï":"Iuml","ĩ":"itilde","Ĩ":"Itilde","İ":"Idot","į":"iogon","Į":"Iogon","ī":"imacr","Ī":"Imacr","ij":"ijlig","IJ":"IJlig","ı":"imath","𝒿":"jscr","𝕛":"jopf","𝔧":"jfr","𝒥":"Jscr","𝔍":"Jfr","𝕁":"Jopf","ĵ":"jcirc","Ĵ":"Jcirc","ȷ":"jmath","𝕜":"kopf","𝓀":"kscr","𝔨":"kfr","𝒦":"Kscr","𝕂":"Kopf","𝔎":"Kfr","ķ":"kcedil","Ķ":"Kcedil","𝔩":"lfr","𝓁":"lscr","ℓ":"ell","𝕝":"lopf","ℒ":"Lscr","𝔏":"Lfr","𝕃":"Lopf","ĺ":"lacute","Ĺ":"Lacute","ľ":"lcaron","Ľ":"Lcaron","ļ":"lcedil","Ļ":"Lcedil","ł":"lstrok","Ł":"Lstrok","ŀ":"lmidot","Ŀ":"Lmidot","𝔪":"mfr","𝕞":"mopf","𝓂":"mscr","𝔐":"Mfr","𝕄":"Mopf","ℳ":"Mscr","𝔫":"nfr","𝕟":"nopf","𝓃":"nscr","ℕ":"Nopf","𝒩":"Nscr","𝔑":"Nfr","ń":"nacute","Ń":"Nacute","ň":"ncaron","Ň":"Ncaron","ñ":"ntilde","Ñ":"Ntilde","ņ":"ncedil","Ņ":"Ncedil","№":"numero","ŋ":"eng","Ŋ":"ENG","𝕠":"oopf","𝔬":"ofr","ℴ":"oscr","𝒪":"Oscr","𝔒":"Ofr","𝕆":"Oopf","º":"ordm","ó":"oacute","Ó":"Oacute","ò":"ograve","Ò":"Ograve","ô":"ocirc","Ô":"Ocirc","ö":"ouml","Ö":"Ouml","ő":"odblac","Ő":"Odblac","õ":"otilde","Õ":"Otilde","ø":"oslash","Ø":"Oslash","ō":"omacr","Ō":"Omacr","œ":"oelig","Œ":"OElig","𝔭":"pfr","𝓅":"pscr","𝕡":"popf","ℙ":"Popf","𝔓":"Pfr","𝒫":"Pscr","𝕢":"qopf","𝔮":"qfr","𝓆":"qscr","𝒬":"Qscr","𝔔":"Qfr","ℚ":"Qopf","ĸ":"kgreen","𝔯":"rfr","𝕣":"ropf","𝓇":"rscr","ℛ":"Rscr","ℜ":"Re","ℝ":"Ropf","ŕ":"racute","Ŕ":"Racute","ř":"rcaron","Ř":"Rcaron","ŗ":"rcedil","Ŗ":"Rcedil","𝕤":"sopf","𝓈":"sscr","𝔰":"sfr","𝕊":"Sopf","𝔖":"Sfr","𝒮":"Sscr","Ⓢ":"oS","ś":"sacute","Ś":"Sacute","ŝ":"scirc","Ŝ":"Scirc","š":"scaron","Š":"Scaron","ş":"scedil","Ş":"Scedil","ß":"szlig","𝔱":"tfr","𝓉":"tscr","𝕥":"topf","𝒯":"Tscr","𝔗":"Tfr","𝕋":"Topf","ť":"tcaron","Ť":"Tcaron","ţ":"tcedil","Ţ":"Tcedil","™":"trade","ŧ":"tstrok","Ŧ":"Tstrok","𝓊":"uscr","𝕦":"uopf","𝔲":"ufr","𝕌":"Uopf","𝔘":"Ufr","𝒰":"Uscr","ú":"uacute","Ú":"Uacute","ù":"ugrave","Ù":"Ugrave","ŭ":"ubreve","Ŭ":"Ubreve","û":"ucirc","Û":"Ucirc","ů":"uring","Ů":"Uring","ü":"uuml","Ü":"Uuml","ű":"udblac","Ű":"Udblac","ũ":"utilde","Ũ":"Utilde","ų":"uogon","Ų":"Uogon","ū":"umacr","Ū":"Umacr","𝔳":"vfr","𝕧":"vopf","𝓋":"vscr","𝔙":"Vfr","𝕍":"Vopf","𝒱":"Vscr","𝕨":"wopf","𝓌":"wscr","𝔴":"wfr","𝒲":"Wscr","𝕎":"Wopf","𝔚":"Wfr","ŵ":"wcirc","Ŵ":"Wcirc","𝔵":"xfr","𝓍":"xscr","𝕩":"xopf","𝕏":"Xopf","𝔛":"Xfr","𝒳":"Xscr","𝔶":"yfr","𝓎":"yscr","𝕪":"yopf","𝒴":"Yscr","𝔜":"Yfr","𝕐":"Yopf","ý":"yacute","Ý":"Yacute","ŷ":"ycirc","Ŷ":"Ycirc","ÿ":"yuml","Ÿ":"Yuml","𝓏":"zscr","𝔷":"zfr","𝕫":"zopf","ℨ":"Zfr","ℤ":"Zopf","𝒵":"Zscr","ź":"zacute","Ź":"Zacute","ž":"zcaron","Ž":"Zcaron","ż":"zdot","Ż":"Zdot","Ƶ":"imped","þ":"thorn","Þ":"THORN","ʼn":"napos","α":"alpha","Α":"Alpha","β":"beta","Β":"Beta","γ":"gamma","Γ":"Gamma","δ":"delta","Δ":"Delta","ε":"epsi","ϵ":"epsiv","Ε":"Epsilon","ϝ":"gammad","Ϝ":"Gammad","ζ":"zeta","Ζ":"Zeta","η":"eta","Η":"Eta","θ":"theta","ϑ":"thetav","Θ":"Theta","ι":"iota","Ι":"Iota","κ":"kappa","ϰ":"kappav","Κ":"Kappa","λ":"lambda","Λ":"Lambda","μ":"mu","µ":"micro","Μ":"Mu","ν":"nu","Ν":"Nu","ξ":"xi","Ξ":"Xi","ο":"omicron","Ο":"Omicron","π":"pi","ϖ":"piv","Π":"Pi","ρ":"rho","ϱ":"rhov","Ρ":"Rho","σ":"sigma","Σ":"Sigma","ς":"sigmaf","τ":"tau","Τ":"Tau","υ":"upsi","Υ":"Upsilon","ϒ":"Upsi","φ":"phi","ϕ":"phiv","Φ":"Phi","χ":"chi","Χ":"Chi","ψ":"psi","Ψ":"Psi","ω":"omega","Ω":"ohm","а":"acy","А":"Acy","б":"bcy","Б":"Bcy","в":"vcy","В":"Vcy","г":"gcy","Г":"Gcy","ѓ":"gjcy","Ѓ":"GJcy","д":"dcy","Д":"Dcy","ђ":"djcy","Ђ":"DJcy","е":"iecy","Е":"IEcy","ё":"iocy","Ё":"IOcy","є":"jukcy","Є":"Jukcy","ж":"zhcy","Ж":"ZHcy","з":"zcy","З":"Zcy","ѕ":"dscy","Ѕ":"DScy","и":"icy","И":"Icy","і":"iukcy","І":"Iukcy","ї":"yicy","Ї":"YIcy","й":"jcy","Й":"Jcy","ј":"jsercy","Ј":"Jsercy","к":"kcy","К":"Kcy","ќ":"kjcy","Ќ":"KJcy","л":"lcy","Л":"Lcy","љ":"ljcy","Љ":"LJcy","м":"mcy","М":"Mcy","н":"ncy","Н":"Ncy","њ":"njcy","Њ":"NJcy","о":"ocy","О":"Ocy","п":"pcy","П":"Pcy","р":"rcy","Р":"Rcy","с":"scy","С":"Scy","т":"tcy","Т":"Tcy","ћ":"tshcy","Ћ":"TSHcy","у":"ucy","У":"Ucy","ў":"ubrcy","Ў":"Ubrcy","ф":"fcy","Ф":"Fcy","х":"khcy","Х":"KHcy","ц":"tscy","Ц":"TScy","ч":"chcy","Ч":"CHcy","џ":"dzcy","Џ":"DZcy","ш":"shcy","Ш":"SHcy","щ":"shchcy","Щ":"SHCHcy","ъ":"hardcy","Ъ":"HARDcy","ы":"ycy","Ы":"Ycy","ь":"softcy","Ь":"SOFTcy","э":"ecy","Э":"Ecy","ю":"yucy","Ю":"YUcy","я":"yacy","Я":"YAcy","ℵ":"aleph","ℶ":"beth","ℷ":"gimel","ℸ":"daleth"},h=/["&'<>`]/g,d={'"':""","&":"&","'":"'","<":"<",">":">","`":"`"},p=/&#(?:[xX][^a-fA-F0-9]|[^0-9xX])/,g=/[\0-\x08\x0B\x0E-\x1F\x7F-\x9F\uFDD0-\uFDEF\uFFFE\uFFFF]|[\uD83F\uD87F\uD8BF\uD8FF\uD93F\uD97F\uD9BF\uD9FF\uDA3F\uDA7F\uDABF\uDAFF\uDB3F\uDB7F\uDBBF\uDBFF][\uDFFE\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]/,y=/&(CounterClockwiseContourIntegral|DoubleLongLeftRightArrow|ClockwiseContourIntegral|NotNestedGreaterGreater|NotSquareSupersetEqual|DiacriticalDoubleAcute|NotRightTriangleEqual|NotSucceedsSlantEqual|NotPrecedesSlantEqual|CloseCurlyDoubleQuote|NegativeVeryThinSpace|DoubleContourIntegral|FilledVerySmallSquare|CapitalDifferentialD|OpenCurlyDoubleQuote|EmptyVerySmallSquare|NestedGreaterGreater|DoubleLongRightArrow|NotLeftTriangleEqual|NotGreaterSlantEqual|ReverseUpEquilibrium|DoubleLeftRightArrow|NotSquareSubsetEqual|NotDoubleVerticalBar|RightArrowLeftArrow|NotGreaterFullEqual|NotRightTriangleBar|SquareSupersetEqual|DownLeftRightVector|DoubleLongLeftArrow|leftrightsquigarrow|LeftArrowRightArrow|NegativeMediumSpace|blacktriangleright|RightDownVectorBar|PrecedesSlantEqual|RightDoubleBracket|SucceedsSlantEqual|NotLeftTriangleBar|RightTriangleEqual|SquareIntersection|RightDownTeeVector|ReverseEquilibrium|NegativeThickSpace|longleftrightarrow|Longleftrightarrow|LongLeftRightArrow|DownRightTeeVector|DownRightVectorBar|GreaterSlantEqual|SquareSubsetEqual|LeftDownVectorBar|LeftDoubleBracket|VerticalSeparator|rightleftharpoons|NotGreaterGreater|NotSquareSuperset|blacktriangleleft|blacktriangledown|NegativeThinSpace|LeftDownTeeVector|NotLessSlantEqual|leftrightharpoons|DoubleUpDownArrow|DoubleVerticalBar|LeftTriangleEqual|FilledSmallSquare|twoheadrightarrow|NotNestedLessLess|DownLeftTeeVector|DownLeftVectorBar|RightAngleBracket|NotTildeFullEqual|NotReverseElement|RightUpDownVector|DiacriticalTilde|NotSucceedsTilde|circlearrowright|NotPrecedesEqual|rightharpoondown|DoubleRightArrow|NotSucceedsEqual|NonBreakingSpace|NotRightTriangle|LessEqualGreater|RightUpTeeVector|LeftAngleBracket|GreaterFullEqual|DownArrowUpArrow|RightUpVectorBar|twoheadleftarrow|GreaterEqualLess|downharpoonright|RightTriangleBar|ntrianglerighteq|NotSupersetEqual|LeftUpDownVector|DiacriticalAcute|rightrightarrows|vartriangleright|UpArrowDownArrow|DiacriticalGrave|UnderParenthesis|EmptySmallSquare|LeftUpVectorBar|leftrightarrows|DownRightVector|downharpoonleft|trianglerighteq|ShortRightArrow|OverParenthesis|DoubleLeftArrow|DoubleDownArrow|NotSquareSubset|bigtriangledown|ntrianglelefteq|UpperRightArrow|curvearrowright|vartriangleleft|NotLeftTriangle|nleftrightarrow|LowerRightArrow|NotHumpDownHump|NotGreaterTilde|rightthreetimes|LeftUpTeeVector|NotGreaterEqual|straightepsilon|LeftTriangleBar|rightsquigarrow|ContourIntegral|rightleftarrows|CloseCurlyQuote|RightDownVector|LeftRightVector|nLeftrightarrow|leftharpoondown|circlearrowleft|SquareSuperset|OpenCurlyQuote|hookrightarrow|HorizontalLine|DiacriticalDot|NotLessGreater|ntriangleright|DoubleRightTee|InvisibleComma|InvisibleTimes|LowerLeftArrow|DownLeftVector|NotSubsetEqual|curvearrowleft|trianglelefteq|NotVerticalBar|TildeFullEqual|downdownarrows|NotGreaterLess|RightTeeVector|ZeroWidthSpace|looparrowright|LongRightArrow|doublebarwedge|ShortLeftArrow|ShortDownArrow|RightVectorBar|GreaterGreater|ReverseElement|rightharpoonup|LessSlantEqual|leftthreetimes|upharpoonright|rightarrowtail|LeftDownVector|Longrightarrow|NestedLessLess|UpperLeftArrow|nshortparallel|leftleftarrows|leftrightarrow|Leftrightarrow|LeftRightArrow|longrightarrow|upharpoonleft|RightArrowBar|ApplyFunction|LeftTeeVector|leftarrowtail|NotEqualTilde|varsubsetneqq|varsupsetneqq|RightTeeArrow|SucceedsEqual|SucceedsTilde|LeftVectorBar|SupersetEqual|hookleftarrow|DifferentialD|VerticalTilde|VeryThinSpace|blacktriangle|bigtriangleup|LessFullEqual|divideontimes|leftharpoonup|UpEquilibrium|ntriangleleft|RightTriangle|measuredangle|shortparallel|longleftarrow|Longleftarrow|LongLeftArrow|DoubleLeftTee|Poincareplane|PrecedesEqual|triangleright|DoubleUpArrow|RightUpVector|fallingdotseq|looparrowleft|PrecedesTilde|NotTildeEqual|NotTildeTilde|smallsetminus|Proportional|triangleleft|triangledown|UnderBracket|NotHumpEqual|exponentiale|ExponentialE|NotLessTilde|HilbertSpace|RightCeiling|blacklozenge|varsupsetneq|HumpDownHump|GreaterEqual|VerticalLine|LeftTeeArrow|NotLessEqual|DownTeeArrow|LeftTriangle|varsubsetneq|Intersection|NotCongruent|DownArrowBar|LeftUpVector|LeftArrowBar|risingdotseq|GreaterTilde|RoundImplies|SquareSubset|ShortUpArrow|NotSuperset|quaternions|precnapprox|backepsilon|preccurlyeq|OverBracket|blacksquare|MediumSpace|VerticalBar|circledcirc|circleddash|CircleMinus|CircleTimes|LessGreater|curlyeqprec|curlyeqsucc|diamondsuit|UpDownArrow|Updownarrow|RuleDelayed|Rrightarrow|updownarrow|RightVector|nRightarrow|nrightarrow|eqslantless|LeftCeiling|Equilibrium|SmallCircle|expectation|NotSucceeds|thickapprox|GreaterLess|SquareUnion|NotPrecedes|NotLessLess|straightphi|succnapprox|succcurlyeq|SubsetEqual|sqsupseteq|Proportion|Laplacetrf|ImaginaryI|supsetneqq|NotGreater|gtreqqless|NotElement|ThickSpace|TildeEqual|TildeTilde|Fouriertrf|rmoustache|EqualTilde|eqslantgtr|UnderBrace|LeftVector|UpArrowBar|nLeftarrow|nsubseteqq|subsetneqq|nsupseteqq|nleftarrow|succapprox|lessapprox|UpTeeArrow|upuparrows|curlywedge|lesseqqgtr|varepsilon|varnothing|RightFloor|complement|CirclePlus|sqsubseteq|Lleftarrow|circledast|RightArrow|Rightarrow|rightarrow|lmoustache|Bernoullis|precapprox|mapstoleft|mapstodown|longmapsto|dotsquare|downarrow|DoubleDot|nsubseteq|supsetneq|leftarrow|nsupseteq|subsetneq|ThinSpace|ngeqslant|subseteqq|HumpEqual|NotSubset|triangleq|NotCupCap|lesseqgtr|heartsuit|TripleDot|Leftarrow|Coproduct|Congruent|varpropto|complexes|gvertneqq|LeftArrow|LessTilde|supseteqq|MinusPlus|CircleDot|nleqslant|NotExists|gtreqless|nparallel|UnionPlus|LeftFloor|checkmark|CenterDot|centerdot|Mellintrf|gtrapprox|bigotimes|OverBrace|spadesuit|therefore|pitchfork|rationals|PlusMinus|Backslash|Therefore|DownBreve|backsimeq|backprime|DownArrow|nshortmid|Downarrow|lvertneqq|eqvparsl|imagline|imagpart|infintie|integers|Integral|intercal|LessLess|Uarrocir|intlarhk|sqsupset|angmsdaf|sqsubset|llcorner|vartheta|cupbrcap|lnapprox|Superset|SuchThat|succnsim|succneqq|angmsdag|biguplus|curlyvee|trpezium|Succeeds|NotTilde|bigwedge|angmsdah|angrtvbd|triminus|cwconint|fpartint|lrcorner|smeparsl|subseteq|urcorner|lurdshar|laemptyv|DDotrahd|approxeq|ldrushar|awconint|mapstoup|backcong|shortmid|triangle|geqslant|gesdotol|timesbar|circledR|circledS|setminus|multimap|naturals|scpolint|ncongdot|RightTee|boxminus|gnapprox|boxtimes|andslope|thicksim|angmsdaa|varsigma|cirfnint|rtriltri|angmsdab|rppolint|angmsdac|barwedge|drbkarow|clubsuit|thetasym|bsolhsub|capbrcup|dzigrarr|doteqdot|DotEqual|dotminus|UnderBar|NotEqual|realpart|otimesas|ulcorner|hksearow|hkswarow|parallel|PartialD|elinters|emptyset|plusacir|bbrktbrk|angmsdad|pointint|bigoplus|angmsdae|Precedes|bigsqcup|varkappa|notindot|supseteq|precneqq|precnsim|profalar|profline|profsurf|leqslant|lesdotor|raemptyv|subplus|notnivb|notnivc|subrarr|zigrarr|vzigzag|submult|subedot|Element|between|cirscir|larrbfs|larrsim|lotimes|lbrksld|lbrkslu|lozenge|ldrdhar|dbkarow|bigcirc|epsilon|simrarr|simplus|ltquest|Epsilon|luruhar|gtquest|maltese|npolint|eqcolon|npreceq|bigodot|ddagger|gtrless|bnequiv|harrcir|ddotseq|equivDD|backsim|demptyv|nsqsube|nsqsupe|Upsilon|nsubset|upsilon|minusdu|nsucceq|swarrow|nsupset|coloneq|searrow|boxplus|napprox|natural|asympeq|alefsym|congdot|nearrow|bigstar|diamond|supplus|tritime|LeftTee|nvinfin|triplus|NewLine|nvltrie|nvrtrie|nwarrow|nexists|Diamond|ruluhar|Implies|supmult|angzarr|suplarr|suphsub|questeq|because|digamma|Because|olcross|bemptyv|omicron|Omicron|rotimes|NoBreak|intprod|angrtvb|orderof|uwangle|suphsol|lesdoto|orslope|DownTee|realine|cudarrl|rdldhar|OverBar|supedot|lessdot|supdsub|topfork|succsim|rbrkslu|rbrksld|pertenk|cudarrr|isindot|planckh|lessgtr|pluscir|gesdoto|plussim|plustwo|lesssim|cularrp|rarrsim|Cayleys|notinva|notinvb|notinvc|UpArrow|Uparrow|uparrow|NotLess|dwangle|precsim|Product|curarrm|Cconint|dotplus|rarrbfs|ccupssm|Cedilla|cemptyv|notniva|quatint|frac35|frac38|frac45|frac56|frac58|frac78|tridot|xoplus|gacute|gammad|Gammad|lfisht|lfloor|bigcup|sqsupe|gbreve|Gbreve|lharul|sqsube|sqcups|Gcedil|apacir|llhard|lmidot|Lmidot|lmoust|andand|sqcaps|approx|Abreve|spades|circeq|tprime|divide|topcir|Assign|topbot|gesdot|divonx|xuplus|timesd|gesles|atilde|solbar|SOFTcy|loplus|timesb|lowast|lowbar|dlcorn|dlcrop|softcy|dollar|lparlt|thksim|lrhard|Atilde|lsaquo|smashp|bigvee|thinsp|wreath|bkarow|lsquor|lstrok|Lstrok|lthree|ltimes|ltlarr|DotDot|simdot|ltrPar|weierp|xsqcup|angmsd|sigmav|sigmaf|zeetrf|Zcaron|zcaron|mapsto|vsupne|thetav|cirmid|marker|mcomma|Zacute|vsubnE|there4|gtlPar|vsubne|bottom|gtrarr|SHCHcy|shchcy|midast|midcir|middot|minusb|minusd|gtrdot|bowtie|sfrown|mnplus|models|colone|seswar|Colone|mstpos|searhk|gtrsim|nacute|Nacute|boxbox|telrec|hairsp|Tcedil|nbumpe|scnsim|ncaron|Ncaron|ncedil|Ncedil|hamilt|Scedil|nearhk|hardcy|HARDcy|tcedil|Tcaron|commat|nequiv|nesear|tcaron|target|hearts|nexist|varrho|scedil|Scaron|scaron|hellip|Sacute|sacute|hercon|swnwar|compfn|rtimes|rthree|rsquor|rsaquo|zacute|wedgeq|homtht|barvee|barwed|Barwed|rpargt|horbar|conint|swarhk|roplus|nltrie|hslash|hstrok|Hstrok|rmoust|Conint|bprime|hybull|hyphen|iacute|Iacute|supsup|supsub|supsim|varphi|coprod|brvbar|agrave|Supset|supset|igrave|Igrave|notinE|Agrave|iiiint|iinfin|copysr|wedbar|Verbar|vangrt|becaus|incare|verbar|inodot|bullet|drcorn|intcal|drcrop|cularr|vellip|Utilde|bumpeq|cupcap|dstrok|Dstrok|CupCap|cupcup|cupdot|eacute|Eacute|supdot|iquest|easter|ecaron|Ecaron|ecolon|isinsv|utilde|itilde|Itilde|curarr|succeq|Bumpeq|cacute|ulcrop|nparsl|Cacute|nprcue|egrave|Egrave|nrarrc|nrarrw|subsup|subsub|nrtrie|jsercy|nsccue|Jsercy|kappav|kcedil|Kcedil|subsim|ulcorn|nsimeq|egsdot|veebar|kgreen|capand|elsdot|Subset|subset|curren|aacute|lacute|Lacute|emptyv|ntilde|Ntilde|lagran|lambda|Lambda|capcap|Ugrave|langle|subdot|emsp13|numero|emsp14|nvdash|nvDash|nVdash|nVDash|ugrave|ufisht|nvHarr|larrfs|nvlArr|larrhk|larrlp|larrpl|nvrArr|Udblac|nwarhk|larrtl|nwnear|oacute|Oacute|latail|lAtail|sstarf|lbrace|odblac|Odblac|lbrack|udblac|odsold|eparsl|lcaron|Lcaron|ograve|Ograve|lcedil|Lcedil|Aacute|ssmile|ssetmn|squarf|ldquor|capcup|ominus|cylcty|rharul|eqcirc|dagger|rfloor|rfisht|Dagger|daleth|equals|origof|capdot|equest|dcaron|Dcaron|rdquor|oslash|Oslash|otilde|Otilde|otimes|Otimes|urcrop|Ubreve|ubreve|Yacute|Uacute|uacute|Rcedil|rcedil|urcorn|parsim|Rcaron|Vdashl|rcaron|Tstrok|percnt|period|permil|Exists|yacute|rbrack|rbrace|phmmat|ccaron|Ccaron|planck|ccedil|plankv|tstrok|female|plusdo|plusdu|ffilig|plusmn|ffllig|Ccedil|rAtail|dfisht|bernou|ratail|Rarrtl|rarrtl|angsph|rarrpl|rarrlp|rarrhk|xwedge|xotime|forall|ForAll|Vvdash|vsupnE|preceq|bigcap|frac12|frac13|frac14|primes|rarrfs|prnsim|frac15|Square|frac16|square|lesdot|frac18|frac23|propto|prurel|rarrap|rangle|puncsp|frac25|Racute|qprime|racute|lesges|frac34|abreve|AElig|eqsim|utdot|setmn|urtri|Equal|Uring|seArr|uring|searr|dashv|Dashv|mumap|nabla|iogon|Iogon|sdote|sdotb|scsim|napid|napos|equiv|natur|Acirc|dblac|erarr|nbump|iprod|erDot|ucirc|awint|esdot|angrt|ncong|isinE|scnap|Scirc|scirc|ndash|isins|Ubrcy|nearr|neArr|isinv|nedot|ubrcy|acute|Ycirc|iukcy|Iukcy|xutri|nesim|caret|jcirc|Jcirc|caron|twixt|ddarr|sccue|exist|jmath|sbquo|ngeqq|angst|ccaps|lceil|ngsim|UpTee|delta|Delta|rtrif|nharr|nhArr|nhpar|rtrie|jukcy|Jukcy|kappa|rsquo|Kappa|nlarr|nlArr|TSHcy|rrarr|aogon|Aogon|fflig|xrarr|tshcy|ccirc|nleqq|filig|upsih|nless|dharl|nlsim|fjlig|ropar|nltri|dharr|robrk|roarr|fllig|fltns|roang|rnmid|subnE|subne|lAarr|trisb|Ccirc|acirc|ccups|blank|VDash|forkv|Vdash|langd|cedil|blk12|blk14|laquo|strns|diams|notin|vDash|larrb|blk34|block|disin|uplus|vdash|vBarv|aelig|starf|Wedge|check|xrArr|lates|lbarr|lBarr|notni|lbbrk|bcong|frasl|lbrke|frown|vrtri|vprop|vnsup|gamma|Gamma|wedge|xodot|bdquo|srarr|doteq|ldquo|boxdl|boxdL|gcirc|Gcirc|boxDl|boxDL|boxdr|boxdR|boxDr|TRADE|trade|rlhar|boxDR|vnsub|npart|vltri|rlarr|boxhd|boxhD|nprec|gescc|nrarr|nrArr|boxHd|boxHD|boxhu|boxhU|nrtri|boxHu|clubs|boxHU|times|colon|Colon|gimel|xlArr|Tilde|nsime|tilde|nsmid|nspar|THORN|thorn|xlarr|nsube|nsubE|thkap|xhArr|comma|nsucc|boxul|boxuL|nsupe|nsupE|gneqq|gnsim|boxUl|boxUL|grave|boxur|boxuR|boxUr|boxUR|lescc|angle|bepsi|boxvh|varpi|boxvH|numsp|Theta|gsime|gsiml|theta|boxVh|boxVH|boxvl|gtcir|gtdot|boxvL|boxVl|boxVL|crarr|cross|Cross|nvsim|boxvr|nwarr|nwArr|sqsup|dtdot|Uogon|lhard|lharu|dtrif|ocirc|Ocirc|lhblk|duarr|odash|sqsub|Hacek|sqcup|llarr|duhar|oelig|OElig|ofcir|boxvR|uogon|lltri|boxVr|csube|uuarr|ohbar|csupe|ctdot|olarr|olcir|harrw|oline|sqcap|omacr|Omacr|omega|Omega|boxVR|aleph|lneqq|lnsim|loang|loarr|rharu|lobrk|hcirc|operp|oplus|rhard|Hcirc|orarr|Union|order|ecirc|Ecirc|cuepr|szlig|cuesc|breve|reals|eDDot|Breve|hoarr|lopar|utrif|rdquo|Umacr|umacr|efDot|swArr|ultri|alpha|rceil|ovbar|swarr|Wcirc|wcirc|smtes|smile|bsemi|lrarr|aring|parsl|lrhar|bsime|uhblk|lrtri|cupor|Aring|uharr|uharl|slarr|rbrke|bsolb|lsime|rbbrk|RBarr|lsimg|phone|rBarr|rbarr|icirc|lsquo|Icirc|emacr|Emacr|ratio|simne|plusb|simlE|simgE|simeq|pluse|ltcir|ltdot|empty|xharr|xdtri|iexcl|Alpha|ltrie|rarrw|pound|ltrif|xcirc|bumpe|prcue|bumpE|asymp|amacr|cuvee|Sigma|sigma|iiint|udhar|iiota|ijlig|IJlig|supnE|imacr|Imacr|prime|Prime|image|prnap|eogon|Eogon|rarrc|mdash|mDDot|cuwed|imath|supne|imped|Amacr|udarr|prsim|micro|rarrb|cwint|raquo|infin|eplus|range|rangd|Ucirc|radic|minus|amalg|veeeq|rAarr|epsiv|ycirc|quest|sharp|quot|zwnj|Qscr|race|qscr|Qopf|qopf|qint|rang|Rang|Zscr|zscr|Zopf|zopf|rarr|rArr|Rarr|Pscr|pscr|prop|prod|prnE|prec|ZHcy|zhcy|prap|Zeta|zeta|Popf|popf|Zdot|plus|zdot|Yuml|yuml|phiv|YUcy|yucy|Yscr|yscr|perp|Yopf|yopf|part|para|YIcy|Ouml|rcub|yicy|YAcy|rdca|ouml|osol|Oscr|rdsh|yacy|real|oscr|xvee|andd|rect|andv|Xscr|oror|ordm|ordf|xscr|ange|aopf|Aopf|rHar|Xopf|opar|Oopf|xopf|xnis|rhov|oopf|omid|xmap|oint|apid|apos|ogon|ascr|Ascr|odot|odiv|xcup|xcap|ocir|oast|nvlt|nvle|nvgt|nvge|nvap|Wscr|wscr|auml|ntlg|ntgl|nsup|nsub|nsim|Nscr|nscr|nsce|Wopf|ring|npre|wopf|npar|Auml|Barv|bbrk|Nopf|nopf|nmid|nLtv|beta|ropf|Ropf|Beta|beth|nles|rpar|nleq|bnot|bNot|nldr|NJcy|rscr|Rscr|Vscr|vscr|rsqb|njcy|bopf|nisd|Bopf|rtri|Vopf|nGtv|ngtr|vopf|boxh|boxH|boxv|nges|ngeq|boxV|bscr|scap|Bscr|bsim|Vert|vert|bsol|bull|bump|caps|cdot|ncup|scnE|ncap|nbsp|napE|Cdot|cent|sdot|Vbar|nang|vBar|chcy|Mscr|mscr|sect|semi|CHcy|Mopf|mopf|sext|circ|cire|mldr|mlcp|cirE|comp|shcy|SHcy|vArr|varr|cong|copf|Copf|copy|COPY|malt|male|macr|lvnE|cscr|ltri|sime|ltcc|simg|Cscr|siml|csub|Uuml|lsqb|lsim|uuml|csup|Lscr|lscr|utri|smid|lpar|cups|smte|lozf|darr|Lopf|Uscr|solb|lopf|sopf|Sopf|lneq|uscr|spar|dArr|lnap|Darr|dash|Sqrt|LJcy|ljcy|lHar|dHar|Upsi|upsi|diam|lesg|djcy|DJcy|leqq|dopf|Dopf|dscr|Dscr|dscy|ldsh|ldca|squf|DScy|sscr|Sscr|dsol|lcub|late|star|Star|Uopf|Larr|lArr|larr|uopf|dtri|dzcy|sube|subE|Lang|lang|Kscr|kscr|Kopf|kopf|KJcy|kjcy|KHcy|khcy|DZcy|ecir|edot|eDot|Jscr|jscr|succ|Jopf|jopf|Edot|uHar|emsp|ensp|Iuml|iuml|eopf|isin|Iscr|iscr|Eopf|epar|sung|epsi|escr|sup1|sup2|sup3|Iota|iota|supe|supE|Iopf|iopf|IOcy|iocy|Escr|esim|Esim|imof|Uarr|QUOT|uArr|uarr|euml|IEcy|iecy|Idot|Euml|euro|excl|Hscr|hscr|Hopf|hopf|TScy|tscy|Tscr|hbar|tscr|flat|tbrk|fnof|hArr|harr|half|fopf|Fopf|tdot|gvnE|fork|trie|gtcc|fscr|Fscr|gdot|gsim|Gscr|gscr|Gopf|gopf|gneq|Gdot|tosa|gnap|Topf|topf|geqq|toea|GJcy|gjcy|tint|gesl|mid|Sfr|ggg|top|ges|gla|glE|glj|geq|gne|gEl|gel|gnE|Gcy|gcy|gap|Tfr|tfr|Tcy|tcy|Hat|Tau|Ffr|tau|Tab|hfr|Hfr|ffr|Fcy|fcy|icy|Icy|iff|ETH|eth|ifr|Ifr|Eta|eta|int|Int|Sup|sup|ucy|Ucy|Sum|sum|jcy|ENG|ufr|Ufr|eng|Jcy|jfr|els|ell|egs|Efr|efr|Jfr|uml|kcy|Kcy|Ecy|ecy|kfr|Kfr|lap|Sub|sub|lat|lcy|Lcy|leg|Dot|dot|lEg|leq|les|squ|div|die|lfr|Lfr|lgE|Dfr|dfr|Del|deg|Dcy|dcy|lne|lnE|sol|loz|smt|Cup|lrm|cup|lsh|Lsh|sim|shy|map|Map|mcy|Mcy|mfr|Mfr|mho|gfr|Gfr|sfr|cir|Chi|chi|nap|Cfr|vcy|Vcy|cfr|Scy|scy|ncy|Ncy|vee|Vee|Cap|cap|nfr|scE|sce|Nfr|nge|ngE|nGg|vfr|Vfr|ngt|bot|nGt|nis|niv|Rsh|rsh|nle|nlE|bne|Bfr|bfr|nLl|nlt|nLt|Bcy|bcy|not|Not|rlm|wfr|Wfr|npr|nsc|num|ocy|ast|Ocy|ofr|xfr|Xfr|Ofr|ogt|ohm|apE|olt|Rho|ape|rho|Rfr|rfr|ord|REG|ang|reg|orv|And|and|AMP|Rcy|amp|Afr|ycy|Ycy|yen|yfr|Yfr|rcy|par|pcy|Pcy|pfr|Pfr|phi|Phi|afr|Acy|acy|zcy|Zcy|piv|acE|acd|zfr|Zfr|pre|prE|psi|Psi|qfr|Qfr|zwj|Or|ge|Gg|gt|gg|el|oS|lt|Lt|LT|Re|lg|gl|eg|ne|Im|it|le|DD|wp|wr|nu|Nu|dd|lE|Sc|sc|pi|Pi|ee|af|ll|Ll|rx|gE|xi|pm|Xi|ic|pr|Pr|in|ni|mp|mu|ac|Mu|or|ap|Gt|GT|ii);|&(Aacute|Agrave|Atilde|Ccedil|Eacute|Egrave|Iacute|Igrave|Ntilde|Oacute|Ograve|Oslash|Otilde|Uacute|Ugrave|Yacute|aacute|agrave|atilde|brvbar|ccedil|curren|divide|eacute|egrave|frac12|frac14|frac34|iacute|igrave|iquest|middot|ntilde|oacute|ograve|oslash|otilde|plusmn|uacute|ugrave|yacute|AElig|Acirc|Aring|Ecirc|Icirc|Ocirc|THORN|Ucirc|acirc|acute|aelig|aring|cedil|ecirc|icirc|iexcl|laquo|micro|ocirc|pound|raquo|szlig|thorn|times|ucirc|Auml|COPY|Euml|Iuml|Ouml|QUOT|Uuml|auml|cent|copy|euml|iuml|macr|nbsp|ordf|ordm|ouml|para|quot|sect|sup1|sup2|sup3|uuml|yuml|AMP|ETH|REG|amp|deg|eth|not|reg|shy|uml|yen|GT|LT|gt|lt)(?!;)([=a-zA-Z0-9]?)|&#([0-9]+)(;?)|&#[xX]([a-fA-F0-9]+)(;?)|&([0-9a-zA-Z]+)/g,b={aacute:"á",Aacute:"Á",abreve:"ă",Abreve:"Ă",ac:"∾",acd:"∿",acE:"∾̳",acirc:"â",Acirc:"Â",acute:"´",acy:"а",Acy:"А",aelig:"æ",AElig:"Æ",af:"⁡",afr:"𝔞",Afr:"𝔄",agrave:"à",Agrave:"À",alefsym:"ℵ",aleph:"ℵ",alpha:"α",Alpha:"Α",amacr:"ā",Amacr:"Ā",amalg:"⨿",amp:"&",AMP:"&",and:"∧",And:"⩓",andand:"⩕",andd:"⩜",andslope:"⩘",andv:"⩚",ang:"∠",ange:"⦤",angle:"∠",angmsd:"∡",angmsdaa:"⦨",angmsdab:"⦩",angmsdac:"⦪",angmsdad:"⦫",angmsdae:"⦬",angmsdaf:"⦭",angmsdag:"⦮",angmsdah:"⦯",angrt:"∟",angrtvb:"⊾",angrtvbd:"⦝",angsph:"∢",angst:"Å",angzarr:"⍼",aogon:"ą",Aogon:"Ą",aopf:"𝕒",Aopf:"𝔸",ap:"≈",apacir:"⩯",ape:"≊",apE:"⩰",apid:"≋",apos:"'",ApplyFunction:"⁡",approx:"≈",approxeq:"≊",aring:"å",Aring:"Å",ascr:"𝒶",Ascr:"𝒜",Assign:"≔",ast:"*",asymp:"≈",asympeq:"≍",atilde:"ã",Atilde:"Ã",auml:"ä",Auml:"Ä",awconint:"∳",awint:"⨑",backcong:"≌",backepsilon:"϶",backprime:"‵",backsim:"∽",backsimeq:"⋍",Backslash:"∖",Barv:"⫧",barvee:"⊽",barwed:"⌅",Barwed:"⌆",barwedge:"⌅",bbrk:"⎵",bbrktbrk:"⎶",bcong:"≌",bcy:"б",Bcy:"Б",bdquo:"„",becaus:"∵",because:"∵",Because:"∵",bemptyv:"⦰",bepsi:"϶",bernou:"ℬ",Bernoullis:"ℬ",beta:"β",Beta:"Β",beth:"ℶ",between:"≬",bfr:"𝔟",Bfr:"𝔅",bigcap:"⋂",bigcirc:"◯",bigcup:"⋃",bigodot:"⨀",bigoplus:"⨁",bigotimes:"⨂",bigsqcup:"⨆",bigstar:"★",bigtriangledown:"▽",bigtriangleup:"△",biguplus:"⨄",bigvee:"⋁",bigwedge:"⋀",bkarow:"⤍",blacklozenge:"⧫",blacksquare:"▪",blacktriangle:"▴",blacktriangledown:"▾",blacktriangleleft:"◂",blacktriangleright:"▸",blank:"␣",blk12:"▒",blk14:"░",blk34:"▓",block:"█",bne:"=⃥",bnequiv:"≡⃥",bnot:"⌐",bNot:"⫭",bopf:"𝕓",Bopf:"𝔹",bot:"⊥",bottom:"⊥",bowtie:"⋈",boxbox:"⧉",boxdl:"┐",boxdL:"╕",boxDl:"╖",boxDL:"╗",boxdr:"┌",boxdR:"╒",boxDr:"╓",boxDR:"╔",boxh:"─",boxH:"═",boxhd:"┬",boxhD:"╥",boxHd:"╤",boxHD:"╦",boxhu:"┴",boxhU:"╨",boxHu:"╧",boxHU:"╩",boxminus:"⊟",boxplus:"⊞",boxtimes:"⊠",boxul:"┘",boxuL:"╛",boxUl:"╜",boxUL:"╝",boxur:"└",boxuR:"╘",boxUr:"╙",boxUR:"╚",boxv:"│",boxV:"║",boxvh:"┼",boxvH:"╪",boxVh:"╫",boxVH:"╬",boxvl:"┤",boxvL:"╡",boxVl:"╢",boxVL:"╣",boxvr:"├",boxvR:"╞",boxVr:"╟",boxVR:"╠",bprime:"‵",breve:"˘",Breve:"˘",brvbar:"¦",bscr:"𝒷",Bscr:"ℬ",bsemi:"⁏",bsim:"∽",bsime:"⋍",bsol:"\\",bsolb:"⧅",bsolhsub:"⟈",bull:"•",bullet:"•",bump:"≎",bumpe:"≏",bumpE:"⪮",bumpeq:"≏",Bumpeq:"≎",cacute:"ć",Cacute:"Ć",cap:"∩",Cap:"⋒",capand:"⩄",capbrcup:"⩉",capcap:"⩋",capcup:"⩇",capdot:"⩀",CapitalDifferentialD:"ⅅ",caps:"∩︀",caret:"⁁",caron:"ˇ",Cayleys:"ℭ",ccaps:"⩍",ccaron:"č",Ccaron:"Č",ccedil:"ç",Ccedil:"Ç",ccirc:"ĉ",Ccirc:"Ĉ",Cconint:"∰",ccups:"⩌",ccupssm:"⩐",cdot:"ċ",Cdot:"Ċ",cedil:"¸",Cedilla:"¸",cemptyv:"⦲",cent:"¢",centerdot:"·",CenterDot:"·",cfr:"𝔠",Cfr:"ℭ",chcy:"ч",CHcy:"Ч",check:"✓",checkmark:"✓",chi:"χ",Chi:"Χ",cir:"○",circ:"ˆ",circeq:"≗",circlearrowleft:"↺",circlearrowright:"↻",circledast:"⊛",circledcirc:"⊚",circleddash:"⊝",CircleDot:"⊙",circledR:"®",circledS:"Ⓢ",CircleMinus:"⊖",CirclePlus:"⊕",CircleTimes:"⊗",cire:"≗",cirE:"⧃",cirfnint:"⨐",cirmid:"⫯",cirscir:"⧂",ClockwiseContourIntegral:"∲",CloseCurlyDoubleQuote:"”",CloseCurlyQuote:"’",clubs:"♣",clubsuit:"♣",colon:":",Colon:"∷",colone:"≔",Colone:"⩴",coloneq:"≔",comma:",",commat:"@",comp:"∁",compfn:"∘",complement:"∁",complexes:"ℂ",cong:"≅",congdot:"⩭",Congruent:"≡",conint:"∮",Conint:"∯",ContourIntegral:"∮",copf:"𝕔",Copf:"ℂ",coprod:"∐",Coproduct:"∐",copy:"©",COPY:"©",copysr:"℗",CounterClockwiseContourIntegral:"∳",crarr:"↵",cross:"✗",Cross:"⨯",cscr:"𝒸",Cscr:"𝒞",csub:"⫏",csube:"⫑",csup:"⫐",csupe:"⫒",ctdot:"⋯",cudarrl:"⤸",cudarrr:"⤵",cuepr:"⋞",cuesc:"⋟",cularr:"↶",cularrp:"⤽",cup:"∪",Cup:"⋓",cupbrcap:"⩈",cupcap:"⩆",CupCap:"≍",cupcup:"⩊",cupdot:"⊍",cupor:"⩅",cups:"∪︀",curarr:"↷",curarrm:"⤼",curlyeqprec:"⋞",curlyeqsucc:"⋟",curlyvee:"⋎",curlywedge:"⋏",curren:"¤",curvearrowleft:"↶",curvearrowright:"↷",cuvee:"⋎",cuwed:"⋏",cwconint:"∲",cwint:"∱",cylcty:"⌭",dagger:"†",Dagger:"‡",daleth:"ℸ",darr:"↓",dArr:"⇓",Darr:"↡",dash:"‐",dashv:"⊣",Dashv:"⫤",dbkarow:"⤏",dblac:"˝",dcaron:"ď",Dcaron:"Ď",dcy:"д",Dcy:"Д",dd:"ⅆ",DD:"ⅅ",ddagger:"‡",ddarr:"⇊",DDotrahd:"⤑",ddotseq:"⩷",deg:"°",Del:"∇",delta:"δ",Delta:"Δ",demptyv:"⦱",dfisht:"⥿",dfr:"𝔡",Dfr:"𝔇",dHar:"⥥",dharl:"⇃",dharr:"⇂",DiacriticalAcute:"´",DiacriticalDot:"˙",DiacriticalDoubleAcute:"˝",DiacriticalGrave:"`",DiacriticalTilde:"˜",diam:"⋄",diamond:"⋄",Diamond:"⋄",diamondsuit:"♦",diams:"♦",die:"¨",DifferentialD:"ⅆ",digamma:"ϝ",disin:"⋲",div:"÷",divide:"÷",divideontimes:"⋇",divonx:"⋇",djcy:"ђ",DJcy:"Ђ",dlcorn:"⌞",dlcrop:"⌍",dollar:"$",dopf:"𝕕",Dopf:"𝔻",dot:"˙",Dot:"¨",DotDot:"⃜",doteq:"≐",doteqdot:"≑",DotEqual:"≐",dotminus:"∸",dotplus:"∔",dotsquare:"⊡",doublebarwedge:"⌆",DoubleContourIntegral:"∯",DoubleDot:"¨",DoubleDownArrow:"⇓",DoubleLeftArrow:"⇐",DoubleLeftRightArrow:"⇔",DoubleLeftTee:"⫤",DoubleLongLeftArrow:"⟸",DoubleLongLeftRightArrow:"⟺",DoubleLongRightArrow:"⟹",DoubleRightArrow:"⇒",DoubleRightTee:"⊨",DoubleUpArrow:"⇑",DoubleUpDownArrow:"⇕",DoubleVerticalBar:"∥",downarrow:"↓",Downarrow:"⇓",DownArrow:"↓",DownArrowBar:"⤓",DownArrowUpArrow:"⇵",DownBreve:"̑",downdownarrows:"⇊",downharpoonleft:"⇃",downharpoonright:"⇂",DownLeftRightVector:"⥐",DownLeftTeeVector:"⥞",DownLeftVector:"↽",DownLeftVectorBar:"⥖",DownRightTeeVector:"⥟",DownRightVector:"⇁",DownRightVectorBar:"⥗",DownTee:"⊤",DownTeeArrow:"↧",drbkarow:"⤐",drcorn:"⌟",drcrop:"⌌",dscr:"𝒹",Dscr:"𝒟",dscy:"ѕ",DScy:"Ѕ",dsol:"⧶",dstrok:"đ",Dstrok:"Đ",dtdot:"⋱",dtri:"▿",dtrif:"▾",duarr:"⇵",duhar:"⥯",dwangle:"⦦",dzcy:"џ",DZcy:"Џ",dzigrarr:"⟿",eacute:"é",Eacute:"É",easter:"⩮",ecaron:"ě",Ecaron:"Ě",ecir:"≖",ecirc:"ê",Ecirc:"Ê",ecolon:"≕",ecy:"э",Ecy:"Э",eDDot:"⩷",edot:"ė",eDot:"≑",Edot:"Ė",ee:"ⅇ",efDot:"≒",efr:"𝔢",Efr:"𝔈",eg:"⪚",egrave:"è",Egrave:"È",egs:"⪖",egsdot:"⪘",el:"⪙",Element:"∈",elinters:"⏧",ell:"ℓ",els:"⪕",elsdot:"⪗",emacr:"ē",Emacr:"Ē",empty:"∅",emptyset:"∅",EmptySmallSquare:"◻",emptyv:"∅",EmptyVerySmallSquare:"▫",emsp:" ",emsp13:" ",emsp14:" ",eng:"ŋ",ENG:"Ŋ",ensp:" ",eogon:"ę",Eogon:"Ę",eopf:"𝕖",Eopf:"𝔼",epar:"⋕",eparsl:"⧣",eplus:"⩱",epsi:"ε",epsilon:"ε",Epsilon:"Ε",epsiv:"ϵ",eqcirc:"≖",eqcolon:"≕",eqsim:"≂",eqslantgtr:"⪖",eqslantless:"⪕",Equal:"⩵",equals:"=",EqualTilde:"≂",equest:"≟",Equilibrium:"⇌",equiv:"≡",equivDD:"⩸",eqvparsl:"⧥",erarr:"⥱",erDot:"≓",escr:"ℯ",Escr:"ℰ",esdot:"≐",esim:"≂",Esim:"⩳",eta:"η",Eta:"Η",eth:"ð",ETH:"Ð",euml:"ë",Euml:"Ë",euro:"€",excl:"!",exist:"∃",Exists:"∃",expectation:"ℰ",exponentiale:"ⅇ",ExponentialE:"ⅇ",fallingdotseq:"≒",fcy:"ф",Fcy:"Ф",female:"♀",ffilig:"ffi",fflig:"ff",ffllig:"ffl",ffr:"𝔣",Ffr:"𝔉",filig:"fi",FilledSmallSquare:"◼",FilledVerySmallSquare:"▪",fjlig:"fj",flat:"♭",fllig:"fl",fltns:"▱",fnof:"ƒ",fopf:"𝕗",Fopf:"𝔽",forall:"∀",ForAll:"∀",fork:"⋔",forkv:"⫙",Fouriertrf:"ℱ",fpartint:"⨍",frac12:"½",frac13:"⅓",frac14:"¼",frac15:"⅕",frac16:"⅙",frac18:"⅛",frac23:"⅔",frac25:"⅖",frac34:"¾",frac35:"⅗",frac38:"⅜",frac45:"⅘",frac56:"⅚",frac58:"⅝",frac78:"⅞",frasl:"⁄",frown:"⌢",fscr:"𝒻",Fscr:"ℱ",gacute:"ǵ",gamma:"γ",Gamma:"Γ",gammad:"ϝ",Gammad:"Ϝ",gap:"⪆",gbreve:"ğ",Gbreve:"Ğ",Gcedil:"Ģ",gcirc:"ĝ",Gcirc:"Ĝ",gcy:"г",Gcy:"Г",gdot:"ġ",Gdot:"Ġ",ge:"≥",gE:"≧",gel:"⋛",gEl:"⪌",geq:"≥",geqq:"≧",geqslant:"⩾",ges:"⩾",gescc:"⪩",gesdot:"⪀",gesdoto:"⪂",gesdotol:"⪄",gesl:"⋛︀",gesles:"⪔",gfr:"𝔤",Gfr:"𝔊",gg:"≫",Gg:"⋙",ggg:"⋙",gimel:"ℷ",gjcy:"ѓ",GJcy:"Ѓ",gl:"≷",gla:"⪥",glE:"⪒",glj:"⪤",gnap:"⪊",gnapprox:"⪊",gne:"⪈",gnE:"≩",gneq:"⪈",gneqq:"≩",gnsim:"⋧",gopf:"𝕘",Gopf:"𝔾",grave:"`",GreaterEqual:"≥",GreaterEqualLess:"⋛",GreaterFullEqual:"≧",GreaterGreater:"⪢",GreaterLess:"≷",GreaterSlantEqual:"⩾",GreaterTilde:"≳",gscr:"ℊ",Gscr:"𝒢",gsim:"≳",gsime:"⪎",gsiml:"⪐",gt:">",Gt:"≫",GT:">",gtcc:"⪧",gtcir:"⩺",gtdot:"⋗",gtlPar:"⦕",gtquest:"⩼",gtrapprox:"⪆",gtrarr:"⥸",gtrdot:"⋗",gtreqless:"⋛",gtreqqless:"⪌",gtrless:"≷",gtrsim:"≳",gvertneqq:"≩︀",gvnE:"≩︀",Hacek:"ˇ",hairsp:" ",half:"½",hamilt:"ℋ",hardcy:"ъ",HARDcy:"Ъ",harr:"↔",hArr:"⇔",harrcir:"⥈",harrw:"↭",Hat:"^",hbar:"ℏ",hcirc:"ĥ",Hcirc:"Ĥ",hearts:"♥",heartsuit:"♥",hellip:"…",hercon:"⊹",hfr:"𝔥",Hfr:"ℌ",HilbertSpace:"ℋ",hksearow:"⤥",hkswarow:"⤦",hoarr:"⇿",homtht:"∻",hookleftarrow:"↩",hookrightarrow:"↪",hopf:"𝕙",Hopf:"ℍ",horbar:"―",HorizontalLine:"─",hscr:"𝒽",Hscr:"ℋ",hslash:"ℏ",hstrok:"ħ",Hstrok:"Ħ",HumpDownHump:"≎",HumpEqual:"≏",hybull:"⁃",hyphen:"‐",iacute:"í",Iacute:"Í",ic:"⁣",icirc:"î",Icirc:"Î",icy:"и",Icy:"И",Idot:"İ",iecy:"е",IEcy:"Е",iexcl:"¡",iff:"⇔",ifr:"𝔦",Ifr:"ℑ",igrave:"ì",Igrave:"Ì",ii:"ⅈ",iiiint:"⨌",iiint:"∭",iinfin:"⧜",iiota:"℩",ijlig:"ij",IJlig:"IJ",Im:"ℑ",imacr:"ī",Imacr:"Ī",image:"ℑ",ImaginaryI:"ⅈ",imagline:"ℐ",imagpart:"ℑ",imath:"ı",imof:"⊷",imped:"Ƶ",Implies:"⇒",in:"∈",incare:"℅",infin:"∞",infintie:"⧝",inodot:"ı",int:"∫",Int:"∬",intcal:"⊺",integers:"ℤ",Integral:"∫",intercal:"⊺",Intersection:"⋂",intlarhk:"⨗",intprod:"⨼",InvisibleComma:"⁣",InvisibleTimes:"⁢",iocy:"ё",IOcy:"Ё",iogon:"į",Iogon:"Į",iopf:"𝕚",Iopf:"𝕀",iota:"ι",Iota:"Ι",iprod:"⨼",iquest:"¿",iscr:"𝒾",Iscr:"ℐ",isin:"∈",isindot:"⋵",isinE:"⋹",isins:"⋴",isinsv:"⋳",isinv:"∈",it:"⁢",itilde:"ĩ",Itilde:"Ĩ",iukcy:"і",Iukcy:"І",iuml:"ï",Iuml:"Ï",jcirc:"ĵ",Jcirc:"Ĵ",jcy:"й",Jcy:"Й",jfr:"𝔧",Jfr:"𝔍",jmath:"ȷ",jopf:"𝕛",Jopf:"𝕁",jscr:"𝒿",Jscr:"𝒥",jsercy:"ј",Jsercy:"Ј",jukcy:"є",Jukcy:"Є",kappa:"κ",Kappa:"Κ",kappav:"ϰ",kcedil:"ķ",Kcedil:"Ķ",kcy:"к",Kcy:"К",kfr:"𝔨",Kfr:"𝔎",kgreen:"ĸ",khcy:"х",KHcy:"Х",kjcy:"ќ",KJcy:"Ќ",kopf:"𝕜",Kopf:"𝕂",kscr:"𝓀",Kscr:"𝒦",lAarr:"⇚",lacute:"ĺ",Lacute:"Ĺ",laemptyv:"⦴",lagran:"ℒ",lambda:"λ",Lambda:"Λ",lang:"⟨",Lang:"⟪",langd:"⦑",langle:"⟨",lap:"⪅",Laplacetrf:"ℒ",laquo:"«",larr:"←",lArr:"⇐",Larr:"↞",larrb:"⇤",larrbfs:"⤟",larrfs:"⤝",larrhk:"↩",larrlp:"↫",larrpl:"⤹",larrsim:"⥳",larrtl:"↢",lat:"⪫",latail:"⤙",lAtail:"⤛",late:"⪭",lates:"⪭︀",lbarr:"⤌",lBarr:"⤎",lbbrk:"❲",lbrace:"{",lbrack:"[",lbrke:"⦋",lbrksld:"⦏",lbrkslu:"⦍",lcaron:"ľ",Lcaron:"Ľ",lcedil:"ļ",Lcedil:"Ļ",lceil:"⌈",lcub:"{",lcy:"л",Lcy:"Л",ldca:"⤶",ldquo:"“",ldquor:"„",ldrdhar:"⥧",ldrushar:"⥋",ldsh:"↲",le:"≤",lE:"≦",LeftAngleBracket:"⟨",leftarrow:"←",Leftarrow:"⇐",LeftArrow:"←",LeftArrowBar:"⇤",LeftArrowRightArrow:"⇆",leftarrowtail:"↢",LeftCeiling:"⌈",LeftDoubleBracket:"⟦",LeftDownTeeVector:"⥡",LeftDownVector:"⇃",LeftDownVectorBar:"⥙",LeftFloor:"⌊",leftharpoondown:"↽",leftharpoonup:"↼",leftleftarrows:"⇇",leftrightarrow:"↔",Leftrightarrow:"⇔",LeftRightArrow:"↔",leftrightarrows:"⇆",leftrightharpoons:"⇋",leftrightsquigarrow:"↭",LeftRightVector:"⥎",LeftTee:"⊣",LeftTeeArrow:"↤",LeftTeeVector:"⥚",leftthreetimes:"⋋",LeftTriangle:"⊲",LeftTriangleBar:"⧏",LeftTriangleEqual:"⊴",LeftUpDownVector:"⥑",LeftUpTeeVector:"⥠",LeftUpVector:"↿",LeftUpVectorBar:"⥘",LeftVector:"↼",LeftVectorBar:"⥒",leg:"⋚",lEg:"⪋",leq:"≤",leqq:"≦",leqslant:"⩽",les:"⩽",lescc:"⪨",lesdot:"⩿",lesdoto:"⪁",lesdotor:"⪃",lesg:"⋚︀",lesges:"⪓",lessapprox:"⪅",lessdot:"⋖",lesseqgtr:"⋚",lesseqqgtr:"⪋",LessEqualGreater:"⋚",LessFullEqual:"≦",LessGreater:"≶",lessgtr:"≶",LessLess:"⪡",lesssim:"≲",LessSlantEqual:"⩽",LessTilde:"≲",lfisht:"⥼",lfloor:"⌊",lfr:"𝔩",Lfr:"𝔏",lg:"≶",lgE:"⪑",lHar:"⥢",lhard:"↽",lharu:"↼",lharul:"⥪",lhblk:"▄",ljcy:"љ",LJcy:"Љ",ll:"≪",Ll:"⋘",llarr:"⇇",llcorner:"⌞",Lleftarrow:"⇚",llhard:"⥫",lltri:"◺",lmidot:"ŀ",Lmidot:"Ŀ",lmoust:"⎰",lmoustache:"⎰",lnap:"⪉",lnapprox:"⪉",lne:"⪇",lnE:"≨",lneq:"⪇",lneqq:"≨",lnsim:"⋦",loang:"⟬",loarr:"⇽",lobrk:"⟦",longleftarrow:"⟵",Longleftarrow:"⟸",LongLeftArrow:"⟵",longleftrightarrow:"⟷",Longleftrightarrow:"⟺",LongLeftRightArrow:"⟷",longmapsto:"⟼",longrightarrow:"⟶",Longrightarrow:"⟹",LongRightArrow:"⟶",looparrowleft:"↫",looparrowright:"↬",lopar:"⦅",lopf:"𝕝",Lopf:"𝕃",loplus:"⨭",lotimes:"⨴",lowast:"∗",lowbar:"_",LowerLeftArrow:"↙",LowerRightArrow:"↘",loz:"◊",lozenge:"◊",lozf:"⧫",lpar:"(",lparlt:"⦓",lrarr:"⇆",lrcorner:"⌟",lrhar:"⇋",lrhard:"⥭",lrm:"‎",lrtri:"⊿",lsaquo:"‹",lscr:"𝓁",Lscr:"ℒ",lsh:"↰",Lsh:"↰",lsim:"≲",lsime:"⪍",lsimg:"⪏",lsqb:"[",lsquo:"‘",lsquor:"‚",lstrok:"ł",Lstrok:"Ł",lt:"<",Lt:"≪",LT:"<",ltcc:"⪦",ltcir:"⩹",ltdot:"⋖",lthree:"⋋",ltimes:"⋉",ltlarr:"⥶",ltquest:"⩻",ltri:"◃",ltrie:"⊴",ltrif:"◂",ltrPar:"⦖",lurdshar:"⥊",luruhar:"⥦",lvertneqq:"≨︀",lvnE:"≨︀",macr:"¯",male:"♂",malt:"✠",maltese:"✠",map:"↦",Map:"⤅",mapsto:"↦",mapstodown:"↧",mapstoleft:"↤",mapstoup:"↥",marker:"▮",mcomma:"⨩",mcy:"м",Mcy:"М",mdash:"—",mDDot:"∺",measuredangle:"∡",MediumSpace:" ",Mellintrf:"ℳ",mfr:"𝔪",Mfr:"𝔐",mho:"℧",micro:"µ",mid:"∣",midast:"*",midcir:"⫰",middot:"·",minus:"−",minusb:"⊟",minusd:"∸",minusdu:"⨪",MinusPlus:"∓",mlcp:"⫛",mldr:"…",mnplus:"∓",models:"⊧",mopf:"𝕞",Mopf:"𝕄",mp:"∓",mscr:"𝓂",Mscr:"ℳ",mstpos:"∾",mu:"μ",Mu:"Μ",multimap:"⊸",mumap:"⊸",nabla:"∇",nacute:"ń",Nacute:"Ń",nang:"∠⃒",nap:"≉",napE:"⩰̸",napid:"≋̸",napos:"ʼn",napprox:"≉",natur:"♮",natural:"♮",naturals:"ℕ",nbsp:" ",nbump:"≎̸",nbumpe:"≏̸",ncap:"⩃",ncaron:"ň",Ncaron:"Ň",ncedil:"ņ",Ncedil:"Ņ",ncong:"≇",ncongdot:"⩭̸",ncup:"⩂",ncy:"н",Ncy:"Н",ndash:"–",ne:"≠",nearhk:"⤤",nearr:"↗",neArr:"⇗",nearrow:"↗",nedot:"≐̸",NegativeMediumSpace:"​",NegativeThickSpace:"​",NegativeThinSpace:"​",NegativeVeryThinSpace:"​",nequiv:"≢",nesear:"⤨",nesim:"≂̸",NestedGreaterGreater:"≫",NestedLessLess:"≪",NewLine:"\n",nexist:"∄",nexists:"∄",nfr:"𝔫",Nfr:"𝔑",nge:"≱",ngE:"≧̸",ngeq:"≱",ngeqq:"≧̸",ngeqslant:"⩾̸",nges:"⩾̸",nGg:"⋙̸",ngsim:"≵",ngt:"≯",nGt:"≫⃒",ngtr:"≯",nGtv:"≫̸",nharr:"↮",nhArr:"⇎",nhpar:"⫲",ni:"∋",nis:"⋼",nisd:"⋺",niv:"∋",njcy:"њ",NJcy:"Њ",nlarr:"↚",nlArr:"⇍",nldr:"‥",nle:"≰",nlE:"≦̸",nleftarrow:"↚",nLeftarrow:"⇍",nleftrightarrow:"↮",nLeftrightarrow:"⇎",nleq:"≰",nleqq:"≦̸",nleqslant:"⩽̸",nles:"⩽̸",nless:"≮",nLl:"⋘̸",nlsim:"≴",nlt:"≮",nLt:"≪⃒",nltri:"⋪",nltrie:"⋬",nLtv:"≪̸",nmid:"∤",NoBreak:"⁠",NonBreakingSpace:" ",nopf:"𝕟",Nopf:"ℕ",not:"¬",Not:"⫬",NotCongruent:"≢",NotCupCap:"≭",NotDoubleVerticalBar:"∦",NotElement:"∉",NotEqual:"≠",NotEqualTilde:"≂̸",NotExists:"∄",NotGreater:"≯",NotGreaterEqual:"≱",NotGreaterFullEqual:"≧̸",NotGreaterGreater:"≫̸",NotGreaterLess:"≹",NotGreaterSlantEqual:"⩾̸",NotGreaterTilde:"≵",NotHumpDownHump:"≎̸",NotHumpEqual:"≏̸",notin:"∉",notindot:"⋵̸",notinE:"⋹̸",notinva:"∉",notinvb:"⋷",notinvc:"⋶",NotLeftTriangle:"⋪",NotLeftTriangleBar:"⧏̸",NotLeftTriangleEqual:"⋬",NotLess:"≮",NotLessEqual:"≰",NotLessGreater:"≸",NotLessLess:"≪̸",NotLessSlantEqual:"⩽̸",NotLessTilde:"≴",NotNestedGreaterGreater:"⪢̸",NotNestedLessLess:"⪡̸",notni:"∌",notniva:"∌",notnivb:"⋾",notnivc:"⋽",NotPrecedes:"⊀",NotPrecedesEqual:"⪯̸",NotPrecedesSlantEqual:"⋠",NotReverseElement:"∌",NotRightTriangle:"⋫",NotRightTriangleBar:"⧐̸",NotRightTriangleEqual:"⋭",NotSquareSubset:"⊏̸",NotSquareSubsetEqual:"⋢",NotSquareSuperset:"⊐̸",NotSquareSupersetEqual:"⋣",NotSubset:"⊂⃒",NotSubsetEqual:"⊈",NotSucceeds:"⊁",NotSucceedsEqual:"⪰̸",NotSucceedsSlantEqual:"⋡",NotSucceedsTilde:"≿̸",NotSuperset:"⊃⃒",NotSupersetEqual:"⊉",NotTilde:"≁",NotTildeEqual:"≄",NotTildeFullEqual:"≇",NotTildeTilde:"≉",NotVerticalBar:"∤",npar:"∦",nparallel:"∦",nparsl:"⫽⃥",npart:"∂̸",npolint:"⨔",npr:"⊀",nprcue:"⋠",npre:"⪯̸",nprec:"⊀",npreceq:"⪯̸",nrarr:"↛",nrArr:"⇏",nrarrc:"⤳̸",nrarrw:"↝̸",nrightarrow:"↛",nRightarrow:"⇏",nrtri:"⋫",nrtrie:"⋭",nsc:"⊁",nsccue:"⋡",nsce:"⪰̸",nscr:"𝓃",Nscr:"𝒩",nshortmid:"∤",nshortparallel:"∦",nsim:"≁",nsime:"≄",nsimeq:"≄",nsmid:"∤",nspar:"∦",nsqsube:"⋢",nsqsupe:"⋣",nsub:"⊄",nsube:"⊈",nsubE:"⫅̸",nsubset:"⊂⃒",nsubseteq:"⊈",nsubseteqq:"⫅̸",nsucc:"⊁",nsucceq:"⪰̸",nsup:"⊅",nsupe:"⊉",nsupE:"⫆̸",nsupset:"⊃⃒",nsupseteq:"⊉",nsupseteqq:"⫆̸",ntgl:"≹",ntilde:"ñ",Ntilde:"Ñ",ntlg:"≸",ntriangleleft:"⋪",ntrianglelefteq:"⋬",ntriangleright:"⋫",ntrianglerighteq:"⋭",nu:"ν",Nu:"Ν",num:"#",numero:"№",numsp:" ",nvap:"≍⃒",nvdash:"⊬",nvDash:"⊭",nVdash:"⊮",nVDash:"⊯",nvge:"≥⃒",nvgt:">⃒",nvHarr:"⤄",nvinfin:"⧞",nvlArr:"⤂",nvle:"≤⃒",nvlt:"<⃒",nvltrie:"⊴⃒",nvrArr:"⤃",nvrtrie:"⊵⃒",nvsim:"∼⃒",nwarhk:"⤣",nwarr:"↖",nwArr:"⇖",nwarrow:"↖",nwnear:"⤧",oacute:"ó",Oacute:"Ó",oast:"⊛",ocir:"⊚",ocirc:"ô",Ocirc:"Ô",ocy:"о",Ocy:"О",odash:"⊝",odblac:"ő",Odblac:"Ő",odiv:"⨸",odot:"⊙",odsold:"⦼",oelig:"œ",OElig:"Œ",ofcir:"⦿",ofr:"𝔬",Ofr:"𝔒",ogon:"˛",ograve:"ò",Ograve:"Ò",ogt:"⧁",ohbar:"⦵",ohm:"Ω",oint:"∮",olarr:"↺",olcir:"⦾",olcross:"⦻",oline:"‾",olt:"⧀",omacr:"ō",Omacr:"Ō",omega:"ω",Omega:"Ω",omicron:"ο",Omicron:"Ο",omid:"⦶",ominus:"⊖",oopf:"𝕠",Oopf:"𝕆",opar:"⦷",OpenCurlyDoubleQuote:"“",OpenCurlyQuote:"‘",operp:"⦹",oplus:"⊕",or:"∨",Or:"⩔",orarr:"↻",ord:"⩝",order:"ℴ",orderof:"ℴ",ordf:"ª",ordm:"º",origof:"⊶",oror:"⩖",orslope:"⩗",orv:"⩛",oS:"Ⓢ",oscr:"ℴ",Oscr:"𝒪",oslash:"ø",Oslash:"Ø",osol:"⊘",otilde:"õ",Otilde:"Õ",otimes:"⊗",Otimes:"⨷",otimesas:"⨶",ouml:"ö",Ouml:"Ö",ovbar:"⌽",OverBar:"‾",OverBrace:"⏞",OverBracket:"⎴",OverParenthesis:"⏜",par:"∥",para:"¶",parallel:"∥",parsim:"⫳",parsl:"⫽",part:"∂",PartialD:"∂",pcy:"п",Pcy:"П",percnt:"%",period:".",permil:"‰",perp:"⊥",pertenk:"‱",pfr:"𝔭",Pfr:"𝔓",phi:"φ",Phi:"Φ",phiv:"ϕ",phmmat:"ℳ",phone:"☎",pi:"π",Pi:"Π",pitchfork:"⋔",piv:"ϖ",planck:"ℏ",planckh:"ℎ",plankv:"ℏ",plus:"+",plusacir:"⨣",plusb:"⊞",pluscir:"⨢",plusdo:"∔",plusdu:"⨥",pluse:"⩲",PlusMinus:"±",plusmn:"±",plussim:"⨦",plustwo:"⨧",pm:"±",Poincareplane:"ℌ",pointint:"⨕",popf:"𝕡",Popf:"ℙ",pound:"£",pr:"≺",Pr:"⪻",prap:"⪷",prcue:"≼",pre:"⪯",prE:"⪳",prec:"≺",precapprox:"⪷",preccurlyeq:"≼",Precedes:"≺",PrecedesEqual:"⪯",PrecedesSlantEqual:"≼",PrecedesTilde:"≾",preceq:"⪯",precnapprox:"⪹",precneqq:"⪵",precnsim:"⋨",precsim:"≾",prime:"′",Prime:"″",primes:"ℙ",prnap:"⪹",prnE:"⪵",prnsim:"⋨",prod:"∏",Product:"∏",profalar:"⌮",profline:"⌒",profsurf:"⌓",prop:"∝",Proportion:"∷",Proportional:"∝",propto:"∝",prsim:"≾",prurel:"⊰",pscr:"𝓅",Pscr:"𝒫",psi:"ψ",Psi:"Ψ",puncsp:" ",qfr:"𝔮",Qfr:"𝔔",qint:"⨌",qopf:"𝕢",Qopf:"ℚ",qprime:"⁗",qscr:"𝓆",Qscr:"𝒬",quaternions:"ℍ",quatint:"⨖",quest:"?",questeq:"≟",quot:'"',QUOT:'"',rAarr:"⇛",race:"∽̱",racute:"ŕ",Racute:"Ŕ",radic:"√",raemptyv:"⦳",rang:"⟩",Rang:"⟫",rangd:"⦒",range:"⦥",rangle:"⟩",raquo:"»",rarr:"→",rArr:"⇒",Rarr:"↠",rarrap:"⥵",rarrb:"⇥",rarrbfs:"⤠",rarrc:"⤳",rarrfs:"⤞",rarrhk:"↪",rarrlp:"↬",rarrpl:"⥅",rarrsim:"⥴",rarrtl:"↣",Rarrtl:"⤖",rarrw:"↝",ratail:"⤚",rAtail:"⤜",ratio:"∶",rationals:"ℚ",rbarr:"⤍",rBarr:"⤏",RBarr:"⤐",rbbrk:"❳",rbrace:"}",rbrack:"]",rbrke:"⦌",rbrksld:"⦎",rbrkslu:"⦐",rcaron:"ř",Rcaron:"Ř",rcedil:"ŗ",Rcedil:"Ŗ",rceil:"⌉",rcub:"}",rcy:"р",Rcy:"Р",rdca:"⤷",rdldhar:"⥩",rdquo:"”",rdquor:"”",rdsh:"↳",Re:"ℜ",real:"ℜ",realine:"ℛ",realpart:"ℜ",reals:"ℝ",rect:"▭",reg:"®",REG:"®",ReverseElement:"∋",ReverseEquilibrium:"⇋",ReverseUpEquilibrium:"⥯",rfisht:"⥽",rfloor:"⌋",rfr:"𝔯",Rfr:"ℜ",rHar:"⥤",rhard:"⇁",rharu:"⇀",rharul:"⥬",rho:"ρ",Rho:"Ρ",rhov:"ϱ",RightAngleBracket:"⟩",rightarrow:"→",Rightarrow:"⇒",RightArrow:"→",RightArrowBar:"⇥",RightArrowLeftArrow:"⇄",rightarrowtail:"↣",RightCeiling:"⌉",RightDoubleBracket:"⟧",RightDownTeeVector:"⥝",RightDownVector:"⇂",RightDownVectorBar:"⥕",RightFloor:"⌋",rightharpoondown:"⇁",rightharpoonup:"⇀",rightleftarrows:"⇄",rightleftharpoons:"⇌",rightrightarrows:"⇉",rightsquigarrow:"↝",RightTee:"⊢",RightTeeArrow:"↦",RightTeeVector:"⥛",rightthreetimes:"⋌",RightTriangle:"⊳",RightTriangleBar:"⧐",RightTriangleEqual:"⊵",RightUpDownVector:"⥏",RightUpTeeVector:"⥜",RightUpVector:"↾",RightUpVectorBar:"⥔",RightVector:"⇀",RightVectorBar:"⥓",ring:"˚",risingdotseq:"≓",rlarr:"⇄",rlhar:"⇌",rlm:"‏",rmoust:"⎱",rmoustache:"⎱",rnmid:"⫮",roang:"⟭",roarr:"⇾",robrk:"⟧",ropar:"⦆",ropf:"𝕣",Ropf:"ℝ",roplus:"⨮",rotimes:"⨵",RoundImplies:"⥰",rpar:")",rpargt:"⦔",rppolint:"⨒",rrarr:"⇉",Rrightarrow:"⇛",rsaquo:"›",rscr:"𝓇",Rscr:"ℛ",rsh:"↱",Rsh:"↱",rsqb:"]",rsquo:"’",rsquor:"’",rthree:"⋌",rtimes:"⋊",rtri:"▹",rtrie:"⊵",rtrif:"▸",rtriltri:"⧎",RuleDelayed:"⧴",ruluhar:"⥨",rx:"℞",sacute:"ś",Sacute:"Ś",sbquo:"‚",sc:"≻",Sc:"⪼",scap:"⪸",scaron:"š",Scaron:"Š",sccue:"≽",sce:"⪰",scE:"⪴",scedil:"ş",Scedil:"Ş",scirc:"ŝ",Scirc:"Ŝ",scnap:"⪺",scnE:"⪶",scnsim:"⋩",scpolint:"⨓",scsim:"≿",scy:"с",Scy:"С",sdot:"⋅",sdotb:"⊡",sdote:"⩦",searhk:"⤥",searr:"↘",seArr:"⇘",searrow:"↘",sect:"§",semi:";",seswar:"⤩",setminus:"∖",setmn:"∖",sext:"✶",sfr:"𝔰",Sfr:"𝔖",sfrown:"⌢",sharp:"♯",shchcy:"щ",SHCHcy:"Щ",shcy:"ш",SHcy:"Ш",ShortDownArrow:"↓",ShortLeftArrow:"←",shortmid:"∣",shortparallel:"∥",ShortRightArrow:"→",ShortUpArrow:"↑",shy:"­",sigma:"σ",Sigma:"Σ",sigmaf:"ς",sigmav:"ς",sim:"∼",simdot:"⩪",sime:"≃",simeq:"≃",simg:"⪞",simgE:"⪠",siml:"⪝",simlE:"⪟",simne:"≆",simplus:"⨤",simrarr:"⥲",slarr:"←",SmallCircle:"∘",smallsetminus:"∖",smashp:"⨳",smeparsl:"⧤",smid:"∣",smile:"⌣",smt:"⪪",smte:"⪬",smtes:"⪬︀",softcy:"ь",SOFTcy:"Ь",sol:"/",solb:"⧄",solbar:"⌿",sopf:"𝕤",Sopf:"𝕊",spades:"♠",spadesuit:"♠",spar:"∥",sqcap:"⊓",sqcaps:"⊓︀",sqcup:"⊔",sqcups:"⊔︀",Sqrt:"√",sqsub:"⊏",sqsube:"⊑",sqsubset:"⊏",sqsubseteq:"⊑",sqsup:"⊐",sqsupe:"⊒",sqsupset:"⊐",sqsupseteq:"⊒",squ:"□",square:"□",Square:"□",SquareIntersection:"⊓",SquareSubset:"⊏",SquareSubsetEqual:"⊑",SquareSuperset:"⊐",SquareSupersetEqual:"⊒",SquareUnion:"⊔",squarf:"▪",squf:"▪",srarr:"→",sscr:"𝓈",Sscr:"𝒮",ssetmn:"∖",ssmile:"⌣",sstarf:"⋆",star:"☆",Star:"⋆",starf:"★",straightepsilon:"ϵ",straightphi:"ϕ",strns:"¯",sub:"⊂",Sub:"⋐",subdot:"⪽",sube:"⊆",subE:"⫅",subedot:"⫃",submult:"⫁",subne:"⊊",subnE:"⫋",subplus:"⪿",subrarr:"⥹",subset:"⊂",Subset:"⋐",subseteq:"⊆",subseteqq:"⫅",SubsetEqual:"⊆",subsetneq:"⊊",subsetneqq:"⫋",subsim:"⫇",subsub:"⫕",subsup:"⫓",succ:"≻",succapprox:"⪸",succcurlyeq:"≽",Succeeds:"≻",SucceedsEqual:"⪰",SucceedsSlantEqual:"≽",SucceedsTilde:"≿",succeq:"⪰",succnapprox:"⪺",succneqq:"⪶",succnsim:"⋩",succsim:"≿",SuchThat:"∋",sum:"∑",Sum:"∑",sung:"♪",sup:"⊃",Sup:"⋑",sup1:"¹",sup2:"²",sup3:"³",supdot:"⪾",supdsub:"⫘",supe:"⊇",supE:"⫆",supedot:"⫄",Superset:"⊃",SupersetEqual:"⊇",suphsol:"⟉",suphsub:"⫗",suplarr:"⥻",supmult:"⫂",supne:"⊋",supnE:"⫌",supplus:"⫀",supset:"⊃",Supset:"⋑",supseteq:"⊇",supseteqq:"⫆",supsetneq:"⊋",supsetneqq:"⫌",supsim:"⫈",supsub:"⫔",supsup:"⫖",swarhk:"⤦",swarr:"↙",swArr:"⇙",swarrow:"↙",swnwar:"⤪",szlig:"ß",Tab:"\t",target:"⌖",tau:"τ",Tau:"Τ",tbrk:"⎴",tcaron:"ť",Tcaron:"Ť",tcedil:"ţ",Tcedil:"Ţ",tcy:"т",Tcy:"Т",tdot:"⃛",telrec:"⌕",tfr:"𝔱",Tfr:"𝔗",there4:"∴",therefore:"∴",Therefore:"∴",theta:"θ",Theta:"Θ",thetasym:"ϑ",thetav:"ϑ",thickapprox:"≈",thicksim:"∼",ThickSpace:"  ",thinsp:" ",ThinSpace:" ",thkap:"≈",thksim:"∼",thorn:"þ",THORN:"Þ",tilde:"˜",Tilde:"∼",TildeEqual:"≃",TildeFullEqual:"≅",TildeTilde:"≈",times:"×",timesb:"⊠",timesbar:"⨱",timesd:"⨰",tint:"∭",toea:"⤨",top:"⊤",topbot:"⌶",topcir:"⫱",topf:"𝕥",Topf:"𝕋",topfork:"⫚",tosa:"⤩",tprime:"‴",trade:"™",TRADE:"™",triangle:"▵",triangledown:"▿",triangleleft:"◃",trianglelefteq:"⊴",triangleq:"≜",triangleright:"▹",trianglerighteq:"⊵",tridot:"◬",trie:"≜",triminus:"⨺",TripleDot:"⃛",triplus:"⨹",trisb:"⧍",tritime:"⨻",trpezium:"⏢",tscr:"𝓉",Tscr:"𝒯",tscy:"ц",TScy:"Ц",tshcy:"ћ",TSHcy:"Ћ",tstrok:"ŧ",Tstrok:"Ŧ",twixt:"≬",twoheadleftarrow:"↞",twoheadrightarrow:"↠",uacute:"ú",Uacute:"Ú",uarr:"↑",uArr:"⇑",Uarr:"↟",Uarrocir:"⥉",ubrcy:"ў",Ubrcy:"Ў",ubreve:"ŭ",Ubreve:"Ŭ",ucirc:"û",Ucirc:"Û",ucy:"у",Ucy:"У",udarr:"⇅",udblac:"ű",Udblac:"Ű",udhar:"⥮",ufisht:"⥾",ufr:"𝔲",Ufr:"𝔘",ugrave:"ù",Ugrave:"Ù",uHar:"⥣",uharl:"↿",uharr:"↾",uhblk:"▀",ulcorn:"⌜",ulcorner:"⌜",ulcrop:"⌏",ultri:"◸",umacr:"ū",Umacr:"Ū",uml:"¨",UnderBar:"_",UnderBrace:"⏟",UnderBracket:"⎵",UnderParenthesis:"⏝",Union:"⋃",UnionPlus:"⊎",uogon:"ų",Uogon:"Ų",uopf:"𝕦",Uopf:"𝕌",uparrow:"↑",Uparrow:"⇑",UpArrow:"↑",UpArrowBar:"⤒",UpArrowDownArrow:"⇅",updownarrow:"↕",Updownarrow:"⇕",UpDownArrow:"↕",UpEquilibrium:"⥮",upharpoonleft:"↿",upharpoonright:"↾",uplus:"⊎",UpperLeftArrow:"↖",UpperRightArrow:"↗",upsi:"υ",Upsi:"ϒ",upsih:"ϒ",upsilon:"υ",Upsilon:"Υ",UpTee:"⊥",UpTeeArrow:"↥",upuparrows:"⇈",urcorn:"⌝",urcorner:"⌝",urcrop:"⌎",uring:"ů",Uring:"Ů",urtri:"◹",uscr:"𝓊",Uscr:"𝒰",utdot:"⋰",utilde:"ũ",Utilde:"Ũ",utri:"▵",utrif:"▴",uuarr:"⇈",uuml:"ü",Uuml:"Ü",uwangle:"⦧",vangrt:"⦜",varepsilon:"ϵ",varkappa:"ϰ",varnothing:"∅",varphi:"ϕ",varpi:"ϖ",varpropto:"∝",varr:"↕",vArr:"⇕",varrho:"ϱ",varsigma:"ς",varsubsetneq:"⊊︀",varsubsetneqq:"⫋︀",varsupsetneq:"⊋︀",varsupsetneqq:"⫌︀",vartheta:"ϑ",vartriangleleft:"⊲",vartriangleright:"⊳",vBar:"⫨",Vbar:"⫫",vBarv:"⫩",vcy:"в",Vcy:"В",vdash:"⊢",vDash:"⊨",Vdash:"⊩",VDash:"⊫",Vdashl:"⫦",vee:"∨",Vee:"⋁",veebar:"⊻",veeeq:"≚",vellip:"⋮",verbar:"|",Verbar:"‖",vert:"|",Vert:"‖",VerticalBar:"∣",VerticalLine:"|",VerticalSeparator:"❘",VerticalTilde:"≀",VeryThinSpace:" ",vfr:"𝔳",Vfr:"𝔙",vltri:"⊲",vnsub:"⊂⃒",vnsup:"⊃⃒",vopf:"𝕧",Vopf:"𝕍",vprop:"∝",vrtri:"⊳",vscr:"𝓋",Vscr:"𝒱",vsubne:"⊊︀",vsubnE:"⫋︀",vsupne:"⊋︀",vsupnE:"⫌︀",Vvdash:"⊪",vzigzag:"⦚",wcirc:"ŵ",Wcirc:"Ŵ",wedbar:"⩟",wedge:"∧",Wedge:"⋀",wedgeq:"≙",weierp:"℘",wfr:"𝔴",Wfr:"𝔚",wopf:"𝕨",Wopf:"𝕎",wp:"℘",wr:"≀",wreath:"≀",wscr:"𝓌",Wscr:"𝒲",xcap:"⋂",xcirc:"◯",xcup:"⋃",xdtri:"▽",xfr:"𝔵",Xfr:"𝔛",xharr:"⟷",xhArr:"⟺",xi:"ξ",Xi:"Ξ",xlarr:"⟵",xlArr:"⟸",xmap:"⟼",xnis:"⋻",xodot:"⨀",xopf:"𝕩",Xopf:"𝕏",xoplus:"⨁",xotime:"⨂",xrarr:"⟶",xrArr:"⟹",xscr:"𝓍",Xscr:"𝒳",xsqcup:"⨆",xuplus:"⨄",xutri:"△",xvee:"⋁",xwedge:"⋀",yacute:"ý",Yacute:"Ý",yacy:"я",YAcy:"Я",ycirc:"ŷ",Ycirc:"Ŷ",ycy:"ы",Ycy:"Ы",yen:"¥",yfr:"𝔶",Yfr:"𝔜",yicy:"ї",YIcy:"Ї",yopf:"𝕪",Yopf:"𝕐",yscr:"𝓎",Yscr:"𝒴",yucy:"ю",YUcy:"Ю",yuml:"ÿ",Yuml:"Ÿ",zacute:"ź",Zacute:"Ź",zcaron:"ž",Zcaron:"Ž",zcy:"з",Zcy:"З",zdot:"ż",Zdot:"Ż",zeetrf:"ℨ",ZeroWidthSpace:"​",zeta:"ζ",Zeta:"Ζ",zfr:"𝔷",Zfr:"ℨ",zhcy:"ж",ZHcy:"Ж",zigrarr:"⇝",zopf:"𝕫",Zopf:"ℤ",zscr:"𝓏",Zscr:"𝒵",zwj:"‍",zwnj:"‌"},m={aacute:"á",Aacute:"Á",acirc:"â",Acirc:"Â",acute:"´",aelig:"æ",AElig:"Æ",agrave:"à",Agrave:"À",amp:"&",AMP:"&",aring:"å",Aring:"Å",atilde:"ã",Atilde:"Ã",auml:"ä",Auml:"Ä",brvbar:"¦",ccedil:"ç",Ccedil:"Ç",cedil:"¸",cent:"¢",copy:"©",COPY:"©",curren:"¤",deg:"°",divide:"÷",eacute:"é",Eacute:"É",ecirc:"ê",Ecirc:"Ê",egrave:"è",Egrave:"È",eth:"ð",ETH:"Ð",euml:"ë",Euml:"Ë",frac12:"½",frac14:"¼",frac34:"¾",gt:">",GT:">",iacute:"í",Iacute:"Í",icirc:"î",Icirc:"Î",iexcl:"¡",igrave:"ì",Igrave:"Ì",iquest:"¿",iuml:"ï",Iuml:"Ï",laquo:"«",lt:"<",LT:"<",macr:"¯",micro:"µ",middot:"·",nbsp:" ",not:"¬",ntilde:"ñ",Ntilde:"Ñ",oacute:"ó",Oacute:"Ó",ocirc:"ô",Ocirc:"Ô",ograve:"ò",Ograve:"Ò",ordf:"ª",ordm:"º",oslash:"ø",Oslash:"Ø",otilde:"õ",Otilde:"Õ",ouml:"ö",Ouml:"Ö",para:"¶",plusmn:"±",pound:"£",quot:'"',QUOT:'"',raquo:"»",reg:"®",REG:"®",sect:"§",shy:"­",sup1:"¹",sup2:"²",sup3:"³",szlig:"ß",thorn:"þ",THORN:"Þ",times:"×",uacute:"ú",Uacute:"Ú",ucirc:"û",Ucirc:"Û",ugrave:"ù",Ugrave:"Ù",uml:"¨",uuml:"ü",Uuml:"Ü",yacute:"ý",Yacute:"Ý",yen:"¥",yuml:"ÿ"},v={0:"�",128:"€",130:"‚",131:"ƒ",132:"„",133:"…",134:"†",135:"‡",136:"ˆ",137:"‰",138:"Š",139:"‹",140:"Œ",142:"Ž",145:"‘",146:"’",147:"“",148:"”",149:"•",150:"–",151:"—",152:"˜",153:"™",154:"š",155:"›",156:"œ",158:"ž",159:"Ÿ"},_=[1,2,3,4,5,6,7,8,11,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,64976,64977,64978,64979,64980,64981,64982,64983,64984,64985,64986,64987,64988,64989,64990,64991,64992,64993,64994,64995,64996,64997,64998,64999,65e3,65001,65002,65003,65004,65005,65006,65007,65534,65535,131070,131071,196606,196607,262142,262143,327678,327679,393214,393215,458750,458751,524286,524287,589822,589823,655358,655359,720894,720895,786430,786431,851966,851967,917502,917503,983038,983039,1048574,1048575,1114110,1114111],w=String.fromCharCode,x={}.hasOwnProperty,k=function(t,e){return x.call(t,e)},E=function(t,e){if(!t)return e;var n,r={};for(n in e)r[n]=k(t,n)?t[n]:e[n];return r},A=function(t,e){var n="";return t>=55296&&t<=57343||t>1114111?(e&&M("character reference outside the permissible Unicode range"),"�"):k(v,t)?(e&&M("disallowed character reference"),v[t]):(e&&function(t,e){for(var n=-1,r=t.length;++n65535&&(n+=w((t-=65536)>>>10&1023|55296),t=56320|1023&t),n+=w(t))},S=function(t){return"&#x"+t.toString(16).toUpperCase()+";"},T=function(t){return"&#"+t+";"},M=function(t){throw Error("Parse error: "+t)},D=function(t,e){(e=E(e,D.options)).strict&&g.test(t)&&M("forbidden code point");var n=e.encodeEverything,r=e.useNamedReferences,i=e.allowUnsafeSymbols,o=e.decimal?T:S,a=function(t){return o(t.charCodeAt(0))};return n?(t=t.replace(u,(function(t){return r&&k(l,t)?"&"+l[t]+";":a(t)})),r&&(t=t.replace(/>\u20D2/g,">⃒").replace(/<\u20D2/g,"<⃒").replace(/fj/g,"fj")),r&&(t=t.replace(f,(function(t){return"&"+l[t]+";"})))):r?(i||(t=t.replace(h,(function(t){return"&"+l[t]+";"}))),t=(t=t.replace(/>\u20D2/g,">⃒").replace(/<\u20D2/g,"<⃒")).replace(f,(function(t){return"&"+l[t]+";"}))):i||(t=t.replace(h,a)),t.replace(s,(function(t){var e=t.charCodeAt(0),n=t.charCodeAt(1);return o(1024*(e-55296)+n-56320+65536)})).replace(c,a)};D.options={allowUnsafeSymbols:!1,encodeEverything:!1,strict:!1,useNamedReferences:!1,decimal:!1};var C=function(t,e){var n=(e=E(e,C.options)).strict;return n&&p.test(t)&&M("malformed character reference"),t.replace(y,(function(t,r,i,o,a,s,u,c,f){var l,h,d,p,g,y;return r?b[g=r]:i?(g=i,(y=o)&&e.isAttributeValue?(n&&"="==y&&M("`&` did not start a character reference"),t):(n&&M("named character reference was not terminated by a semicolon"),m[g]+(y||""))):a?(d=a,h=s,n&&!h&&M("character reference was not terminated by a semicolon"),l=parseInt(d,10),A(l,n)):u?(p=u,h=c,n&&!h&&M("character reference was not terminated by a semicolon"),l=parseInt(p,16),A(l,n)):(n&&M("named character reference was not terminated by a semicolon"),t)}))};C.options={isAttributeValue:!1,strict:!1};var O={version:"1.2.0",encode:D,decode:C,escape:function(t){return t.replace(h,(function(t){return d[t]}))},unescape:C};if("function"==typeof define&&"object"==typeof define.amd&&define.amd)define((function(){return O}));else if(i&&!i.nodeType)if(o)o.exports=O;else for(var R in O)k(O,R)&&(i[R]=O[R]);else r.he=O}(this)}).call(this,n(9)(t),n(11))},function(t,e,n){"use strict";var r=n(229),i=n(230),o=n(231);function a(t,e,n){if(!t)return t;if(!e)return t;"string"==typeof n&&(n={keyframes:n}),n||(n={keyframes:!1}),t=s(t,e+" $1$2");var i=e.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&");t=(t=(t=(t=t.replace(new RegExp("("+i+")\\s*\\1(?=[\\s\\r\\n,{])","g"),"$1")).replace(new RegExp("("+i+")\\s*:host","g"),"$1")).replace(new RegExp("("+i+")\\s*@","g"),"@")).replace(new RegExp("("+i+")\\s*:root","g"),":root");for(var o,a=[],u=/@keyframes\s+([a-zA-Z0-9_-]+)\s*{/g;null!==(o=u.exec(t));)a.indexOf(o[1])<0&&a.push(o[1]);var c=r(e);return a.forEach((function(e){var r=(!0===n.keyframes?c+"-":"string"==typeof n.keyframes?n.keyframes:"")+e;t=(t=t.replace(new RegExp("(@keyframes\\s+)"+e+"(\\s*{)","g"),"$1"+r+"$2")).replace(new RegExp("(animation(?:-name)?\\s*:[^;]*\\s*)"+e+"([\\s;}])","g"),"$1"+r+"$2")})),t=t.replace(new RegExp("("+i+" )(\\s*(?:to|from|[+-]?(?:(?:\\.\\d+)|(?:\\d+(?:\\.\\d*)?))%))(?=[\\s\\r\\n,{])","g"),"$2")}function s(t,e){var n=[];return t=o(t),t=(t=i.replace(t,!0,n)).replace(/([^\r\n,{}]+)(,(?=[^}]*{)|\s*{)/g,e),t=i.paste(t,n)}t.exports=a,a.replace=s},function(t,e,n){"use strict";const r=n(418),i="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~".split(""),o=(t,e)=>{const n=e.length,i=Math.floor(65536/n)*n-1,o=2*Math.ceil(1.1*t);let a="",s=0;for(;si||(a+=e[t%n],s++)}}return a},a=[void 0,"hex","base64","url-safe"];t.exports=({length:t,type:e,characters:n})=>{if(!(t>=0&&Number.isFinite(t)))throw new TypeError("Expected a `length` to be a non-negative finite number");if(void 0!==e&&void 0!==n)throw new TypeError("Expected either `type` or `characters`");if(void 0!==n&&"string"!=typeof n)throw new TypeError("Expected `characters` to be string");if(!a.includes(e))throw new TypeError(`Unknown type: ${e}`);if(void 0===e&&void 0===n&&(e="hex"),"hex"===e||void 0===e&&void 0===n)return r.randomBytes(Math.ceil(.5*t)).toString("hex").slice(0,t);if("base64"===e)return r.randomBytes(Math.ceil(.75*t)).toString("base64").slice(0,t);if("url-safe"===e)return o(t,i);if(0===n.length)throw new TypeError("Expected `characters` string length to be greater than or equal to 1");if(n.length>65536)throw new TypeError("Expected `characters` string length to be less or equal to 65536");return o(t,n.split(""))}},function(t,e,n){var r;r=function(){var t=JSON.parse('{"$":"dollar","%":"percent","&":"and","<":"less",">":"greater","|":"or","¢":"cent","£":"pound","¤":"currency","¥":"yen","©":"(c)","ª":"a","®":"(r)","º":"o","À":"A","Á":"A","Â":"A","Ã":"A","Ä":"A","Å":"A","Æ":"AE","Ç":"C","È":"E","É":"E","Ê":"E","Ë":"E","Ì":"I","Í":"I","Î":"I","Ï":"I","Ð":"D","Ñ":"N","Ò":"O","Ó":"O","Ô":"O","Õ":"O","Ö":"O","Ø":"O","Ù":"U","Ú":"U","Û":"U","Ü":"U","Ý":"Y","Þ":"TH","ß":"ss","à":"a","á":"a","â":"a","ã":"a","ä":"a","å":"a","æ":"ae","ç":"c","è":"e","é":"e","ê":"e","ë":"e","ì":"i","í":"i","î":"i","ï":"i","ð":"d","ñ":"n","ò":"o","ó":"o","ô":"o","õ":"o","ö":"o","ø":"o","ù":"u","ú":"u","û":"u","ü":"u","ý":"y","þ":"th","ÿ":"y","Ā":"A","ā":"a","Ă":"A","ă":"a","Ą":"A","ą":"a","Ć":"C","ć":"c","Č":"C","č":"c","Ď":"D","ď":"d","Đ":"DJ","đ":"dj","Ē":"E","ē":"e","Ė":"E","ė":"e","Ę":"e","ę":"e","Ě":"E","ě":"e","Ğ":"G","ğ":"g","Ģ":"G","ģ":"g","Ĩ":"I","ĩ":"i","Ī":"i","ī":"i","Į":"I","į":"i","İ":"I","ı":"i","Ķ":"k","ķ":"k","Ļ":"L","ļ":"l","Ľ":"L","ľ":"l","Ł":"L","ł":"l","Ń":"N","ń":"n","Ņ":"N","ņ":"n","Ň":"N","ň":"n","Ő":"O","ő":"o","Œ":"OE","œ":"oe","Ŕ":"R","ŕ":"r","Ř":"R","ř":"r","Ś":"S","ś":"s","Ş":"S","ş":"s","Š":"S","š":"s","Ţ":"T","ţ":"t","Ť":"T","ť":"t","Ũ":"U","ũ":"u","Ū":"u","ū":"u","Ů":"U","ů":"u","Ű":"U","ű":"u","Ų":"U","ų":"u","Ŵ":"W","ŵ":"w","Ŷ":"Y","ŷ":"y","Ÿ":"Y","Ź":"Z","ź":"z","Ż":"Z","ż":"z","Ž":"Z","ž":"z","ƒ":"f","Ơ":"O","ơ":"o","Ư":"U","ư":"u","Lj":"LJ","lj":"lj","Nj":"NJ","nj":"nj","Ș":"S","ș":"s","Ț":"T","ț":"t","˚":"o","Ά":"A","Έ":"E","Ή":"H","Ί":"I","Ό":"O","Ύ":"Y","Ώ":"W","ΐ":"i","Α":"A","Β":"B","Γ":"G","Δ":"D","Ε":"E","Ζ":"Z","Η":"H","Θ":"8","Ι":"I","Κ":"K","Λ":"L","Μ":"M","Ν":"N","Ξ":"3","Ο":"O","Π":"P","Ρ":"R","Σ":"S","Τ":"T","Υ":"Y","Φ":"F","Χ":"X","Ψ":"PS","Ω":"W","Ϊ":"I","Ϋ":"Y","ά":"a","έ":"e","ή":"h","ί":"i","ΰ":"y","α":"a","β":"b","γ":"g","δ":"d","ε":"e","ζ":"z","η":"h","θ":"8","ι":"i","κ":"k","λ":"l","μ":"m","ν":"n","ξ":"3","ο":"o","π":"p","ρ":"r","ς":"s","σ":"s","τ":"t","υ":"y","φ":"f","χ":"x","ψ":"ps","ω":"w","ϊ":"i","ϋ":"y","ό":"o","ύ":"y","ώ":"w","Ё":"Yo","Ђ":"DJ","Є":"Ye","І":"I","Ї":"Yi","Ј":"J","Љ":"LJ","Њ":"NJ","Ћ":"C","Џ":"DZ","А":"A","Б":"B","В":"V","Г":"G","Д":"D","Е":"E","Ж":"Zh","З":"Z","И":"I","Й":"J","К":"K","Л":"L","М":"M","Н":"N","О":"O","П":"P","Р":"R","С":"S","Т":"T","У":"U","Ф":"F","Х":"H","Ц":"C","Ч":"Ch","Ш":"Sh","Щ":"Sh","Ъ":"U","Ы":"Y","Ь":"","Э":"E","Ю":"Yu","Я":"Ya","а":"a","б":"b","в":"v","г":"g","д":"d","е":"e","ж":"zh","з":"z","и":"i","й":"j","к":"k","л":"l","м":"m","н":"n","о":"o","п":"p","р":"r","с":"s","т":"t","у":"u","ф":"f","х":"h","ц":"c","ч":"ch","ш":"sh","щ":"sh","ъ":"u","ы":"y","ь":"","э":"e","ю":"yu","я":"ya","ё":"yo","ђ":"dj","є":"ye","і":"i","ї":"yi","ј":"j","љ":"lj","њ":"nj","ћ":"c","џ":"dz","Ґ":"G","ґ":"g","฿":"baht","ა":"a","ბ":"b","გ":"g","დ":"d","ე":"e","ვ":"v","ზ":"z","თ":"t","ი":"i","კ":"k","ლ":"l","მ":"m","ნ":"n","ო":"o","პ":"p","ჟ":"zh","რ":"r","ს":"s","ტ":"t","უ":"u","ფ":"f","ქ":"k","ღ":"gh","ყ":"q","შ":"sh","ჩ":"ch","ც":"ts","ძ":"dz","წ":"ts","ჭ":"ch","ხ":"kh","ჯ":"j","ჰ":"h","Ẁ":"W","ẁ":"w","Ẃ":"W","ẃ":"w","Ẅ":"W","ẅ":"w","ẞ":"SS","Ạ":"A","ạ":"a","Ả":"A","ả":"a","Ấ":"A","ấ":"a","Ầ":"A","ầ":"a","Ẩ":"A","ẩ":"a","Ẫ":"A","ẫ":"a","Ậ":"A","ậ":"a","Ắ":"A","ắ":"a","Ằ":"A","ằ":"a","Ẳ":"A","ẳ":"a","Ẵ":"A","ẵ":"a","Ặ":"A","ặ":"a","Ẹ":"E","ẹ":"e","Ẻ":"E","ẻ":"e","Ẽ":"E","ẽ":"e","Ế":"E","ế":"e","Ề":"E","ề":"e","Ể":"E","ể":"e","Ễ":"E","ễ":"e","Ệ":"E","ệ":"e","Ỉ":"I","ỉ":"i","Ị":"I","ị":"i","Ọ":"O","ọ":"o","Ỏ":"O","ỏ":"o","Ố":"O","ố":"o","Ồ":"O","ồ":"o","Ổ":"O","ổ":"o","Ỗ":"O","ỗ":"o","Ộ":"O","ộ":"o","Ớ":"O","ớ":"o","Ờ":"O","ờ":"o","Ở":"O","ở":"o","Ỡ":"O","ỡ":"o","Ợ":"O","ợ":"o","Ụ":"U","ụ":"u","Ủ":"U","ủ":"u","Ứ":"U","ứ":"u","Ừ":"U","ừ":"u","Ử":"U","ử":"u","Ữ":"U","ữ":"u","Ự":"U","ự":"u","Ỳ":"Y","ỳ":"y","Ỵ":"Y","ỵ":"y","Ỷ":"Y","ỷ":"y","Ỹ":"Y","ỹ":"y","‘":"\'","’":"\'","“":"\\"","”":"\\"","†":"+","•":"*","…":"...","₠":"ecu","₢":"cruzeiro","₣":"french franc","₤":"lira","₥":"mill","₦":"naira","₧":"peseta","₨":"rupee","₩":"won","₪":"new shequel","₫":"dong","€":"euro","₭":"kip","₮":"tugrik","₯":"drachma","₰":"penny","₱":"peso","₲":"guarani","₳":"austral","₴":"hryvnia","₵":"cedi","₹":"indian rupee","₽":"russian ruble","₿":"bitcoin","℠":"sm","™":"tm","∂":"d","∆":"delta","∑":"sum","∞":"infinity","♥":"love","元":"yuan","円":"yen","﷼":"rial"}'),e=JSON.parse('{"bg":{"locale":"Bulgarian","ѝ":"u"}}');function n(n,r){if("string"!=typeof n)throw new Error("slugify: string argument expected");var i=e[(r="string"==typeof r?{replacement:r}:r||{}).locale]||{},o=n.split("").reduce((function(e,n){return e+(i[n]||t[n]||n).replace(r.remove||/[^\w\s$*_+~.()'"!\-:@]/g,"")}),"").trim().replace(/[-\s]+/g,r.replacement||"-");return r.lower?o.toLowerCase():o}return n.extend=function(e){for(var n in e)t[n]=e[n]},n},t.exports=r(),t.exports.default=r()},function(t,e,n){ +/*! + * Escaper v2.5.3 + * https://github.com/kobezzza/Escaper + * + * Released under the MIT license + * https://github.com/kobezzza/Escaper/blob/master/LICENSE + * + * Date: Tue, 23 Jan 2018 15:58:45 GMT + */ +!function(t){"use strict";var e="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},n=void 0,r=n={VERSION:[2,5,3],content:[],cache:{},snakeskinRgxp:null,symbols:null,replace:M,paste:C},i={'"':!0,"'":!0,"`":!0},o={"/":!0};for(var a in i){if(!i.hasOwnProperty(a))break;o[a]=!0}var s={"//":!0,"//*":!0,"//!":!0,"//#":!0,"//@":!0,"//$":!0},u={"/*":!0,"/**":!0,"/*!":!0,"/*#":!0,"/*@":!0,"/*$":!0},c=[],f={};for(var l in o){if(!o.hasOwnProperty(l))break;c.push(l),f[l]=!0}for(var h in s){if(!s.hasOwnProperty(h))break;c.push(h),f[h]=!0}for(var d in u){if(!u.hasOwnProperty(d))break;c.push(d),f[d]=!0}var p=[],g={g:!0,m:!0,i:!0,y:!0,u:!0};for(var y in g){if(!g.hasOwnProperty(y))break;p.push(y)}var b={"-":!0,"+":!0,"*":!0,"%":!0,"~":!0,">":!0,"<":!0,"^":!0,",":!0,";":!0,"=":!0,"|":!0,"&":!0,"!":!0,"?":!0,":":!0,"(":!0,"{":!0,"[":!0},m={return:!0,yield:!0,await:!0,typeof:!0,void:!0,instanceof:!0,delete:!0,in:!0,new:!0,of:!0};function v(t,e,n){for(var r in t){if(!t.hasOwnProperty(r))break;r in e==0&&(e[r]=n)}}var _=void 0,w=void 0,x=/[^\s/]/,k=/[a-z]/,E=/\s/,A=/[\r\n]/,S=/\${pos}/g,T={object:!0,function:!0};function M(t,r,a,l){_=_||n.symbols||"a-z",w=w||n.snakeskinRgxp||new RegExp("[!$"+_+"_]","i");var h=n,d=h.cache,y=h.content,M=Boolean(r&&T[void 0===r?"undefined":e(r)]),D=M?Object(r):{};function C(t){return D["@label"]?D["@label"].replace(S,t):"__ESCAPER_QUOT__"+t+"_"}var O=!1;"boolean"==typeof r&&(O=Boolean(r)),"@comments"in D&&(v(u,D,D["@comments"]),v(s,D,D["@comments"]),delete D["@comments"]),"@strings"in D&&(v(i,D,D["@strings"]),delete D["@strings"]),"@literals"in D&&(v(o,D,D["@literals"]),delete D["@literals"]),"@all"in D&&(v(f,D,D["@all"]),delete D["@all"]);for(var R="",I=-1;++I2&&u[j])&&(D[j]&&(H=t.substring(U,K+1),-1===D[j]?$="":($=C(L.length),L.push(H)),t=t.substring(0,U)+$+t.substring(K+1),K+=$.length-H.length),j=!1);else{if(!P){if("/"===X&&((s[J]||u[J])&&(j=s[Q]||u[Q]?Q:J),j)){U=K;continue}b[X]||m[W]?(F=!0,W=""):x.test(X)&&(F=!1),k.test(X)?G+=X:(W=G,G="");var tt=!1;l&&("|"===X&&w.test(Z)?(V=!0,F=!1,tt=!0):V&&E.test(X)&&(V=!1,F=!0,tt=!0)),tt||(b[X]?F=!0:x.test(X)&&(F=!1))}if("/"!==P||q||("["===X?z=!0:"]"===X&&(z=!1)),!P&&Y&&("}"===X?Y--:"{"===X&&Y++,Y||(X="`")),"`"!==P||q||"${"!==J||(X="`",K++,Y++),!f[X]||"/"===X&&!F||P){if(P&&("\\"===X||q))q=!q;else if(f[X]&&P===X&&!q&&("/"!==P||!z)){if("/"===X)for(var et=-1;++et-1}},function(t,e,n){var r=n(63);t.exports=function(t,e){var n=this.__data__,i=r(n,t);return i<0?(++this.size,n.push([t,e])):n[i][1]=e,this}},function(t,e,n){var r=n(62);t.exports=function(){this.__data__=new r,this.size=0}},function(t,e){t.exports=function(t){var e=this.__data__,n=e.delete(t);return this.size=e.size,n}},function(t,e){t.exports=function(t){return this.__data__.get(t)}},function(t,e){t.exports=function(t){return this.__data__.has(t)}},function(t,e,n){var r=n(62),i=n(90),o=n(91),a=200;t.exports=function(t,e){var n=this.__data__;if(n instanceof r){var s=n.__data__;if(!i||s.length0){if(++e>=n)return arguments[0]}else e=0;return t.apply(void 0,arguments)}}},function(t,e,n){var r=n(153),i=n(327),o=n(331),a=n(154),s=n(332),u=n(103),c=200;t.exports=function(t,e,n){var f=-1,l=i,h=t.length,d=!0,p=[],g=p;if(n)d=!1,l=o;else if(h>=c){var y=e?null:s(t);if(y)return u(y);d=!1,l=a,g=new r}else g=e?[]:p;t:for(;++f-1}},function(t,e,n){var r=n(167),i=n(329),o=n(330);t.exports=function(t,e,n){return e==e?o(t,e,n):r(t,i,n)}},function(t,e){t.exports=function(t){return t!=t}},function(t,e){t.exports=function(t,e,n){for(var r=n-1,i=t.length;++r1||1===e.length&&t.hasEdge(e[0],e[0])}))}},function(t,e,n){var r=n(12);t.exports=function(t,e,n){return function(t,e,n){var r={},i=t.nodes();return i.forEach((function(t){r[t]={},r[t][t]={distance:0},i.forEach((function(e){t!==e&&(r[t][e]={distance:Number.POSITIVE_INFINITY})})),n(t).forEach((function(n){var i=n.v===t?n.w:n.v,o=e(n);r[t][i]={distance:o,predecessor:t}}))})),i.forEach((function(t){var e=r[t];i.forEach((function(n){var o=r[n];i.forEach((function(n){var r=o[t],i=e[n],a=o[n],s=r.distance+i.distance;s0;){if(n=u.removeMin(),r.has(s,n))a.setEdge(n,s[n]);else{if(f)throw new Error("Input graph is not connected: "+t);f=!0}t.nodeEdges(n).forEach(c)}return a}},function(t,e,n){var r;try{r=n(22)}catch(t){}r||(r=window.graphlib),t.exports=r},function(t,e,n){"use strict";var r=n(4),i=n(380),o=n(383),a=n(384),s=n(10).normalizeRanks,u=n(386),c=n(10).removeEmptyRanks,f=n(387),l=n(388),h=n(389),d=n(390),p=n(399),g=n(10),y=n(19).Graph;t.exports=function(t,e){var n=e&&e.debugTiming?g.time:g.notime;n("layout",(function(){var e=n(" buildLayoutGraph",(function(){return function(t){var e=new y({multigraph:!0,compound:!0}),n=S(t.graph());return e.setGraph(r.merge({},m,A(n,b),r.pick(n,v))),r.forEach(t.nodes(),(function(n){var i=S(t.node(n));e.setNode(n,r.defaults(A(i,_),w)),e.setParent(n,t.parent(n))})),r.forEach(t.edges(),(function(n){var i=S(t.edge(n));e.setEdge(n,r.merge({},k,A(i,x),r.pick(i,E)))})),e}(t)}));n(" runLayout",(function(){!function(t,e){e(" makeSpaceForEdgeLabels",(function(){!function(t){var e=t.graph();e.ranksep/=2,r.forEach(t.edges(),(function(n){var r=t.edge(n);r.minlen*=2,"c"!==r.labelpos.toLowerCase()&&("TB"===e.rankdir||"BT"===e.rankdir?r.width+=r.labeloffset:r.height+=r.labeloffset)}))}(t)})),e(" removeSelfEdges",(function(){!function(t){r.forEach(t.edges(),(function(e){if(e.v===e.w){var n=t.node(e.v);n.selfEdges||(n.selfEdges=[]),n.selfEdges.push({e:e,label:t.edge(e)}),t.removeEdge(e)}}))}(t)})),e(" acyclic",(function(){i.run(t)})),e(" nestingGraph.run",(function(){f.run(t)})),e(" rank",(function(){a(g.asNonCompoundGraph(t))})),e(" injectEdgeLabelProxies",(function(){!function(t){r.forEach(t.edges(),(function(e){var n=t.edge(e);if(n.width&&n.height){var r=t.node(e.v),i={rank:(t.node(e.w).rank-r.rank)/2+r.rank,e:e};g.addDummyNode(t,"edge-proxy",i,"_ep")}}))}(t)})),e(" removeEmptyRanks",(function(){c(t)})),e(" nestingGraph.cleanup",(function(){f.cleanup(t)})),e(" normalizeRanks",(function(){s(t)})),e(" assignRankMinMax",(function(){!function(t){var e=0;r.forEach(t.nodes(),(function(n){var i=t.node(n);i.borderTop&&(i.minRank=t.node(i.borderTop).rank,i.maxRank=t.node(i.borderBottom).rank,e=r.max(e,i.maxRank))})),t.graph().maxRank=e}(t)})),e(" removeEdgeLabelProxies",(function(){!function(t){r.forEach(t.nodes(),(function(e){var n=t.node(e);"edge-proxy"===n.dummy&&(t.edge(n.e).labelRank=n.rank,t.removeNode(e))}))}(t)})),e(" normalize.run",(function(){o.run(t)})),e(" parentDummyChains",(function(){u(t)})),e(" addBorderSegments",(function(){l(t)})),e(" order",(function(){d(t)})),e(" insertSelfEdges",(function(){!function(t){var e=g.buildLayerMatrix(t);r.forEach(e,(function(e){var n=0;r.forEach(e,(function(e,i){var o=t.node(e);o.order=i+n,r.forEach(o.selfEdges,(function(e){g.addDummyNode(t,"selfedge",{width:e.label.width,height:e.label.height,rank:o.rank,order:i+ ++n,e:e.e,label:e.label},"_se")})),delete o.selfEdges}))}))}(t)})),e(" adjustCoordinateSystem",(function(){h.adjust(t)})),e(" position",(function(){p(t)})),e(" positionSelfEdges",(function(){!function(t){r.forEach(t.nodes(),(function(e){var n=t.node(e);if("selfedge"===n.dummy){var r=t.node(n.e.v),i=r.x+r.width/2,o=r.y,a=n.x-i,s=r.height/2;t.setEdge(n.e,n.label),t.removeNode(e),n.label.points=[{x:i+2*a/3,y:o-s},{x:i+5*a/6,y:o-s},{x:i+a,y:o},{x:i+5*a/6,y:o+s},{x:i+2*a/3,y:o+s}],n.label.x=n.x,n.label.y=n.y}}))}(t)})),e(" removeBorderNodes",(function(){!function(t){r.forEach(t.nodes(),(function(e){if(t.children(e).length){var n=t.node(e),i=t.node(n.borderTop),o=t.node(n.borderBottom),a=t.node(r.last(n.borderLeft)),s=t.node(r.last(n.borderRight));n.width=Math.abs(s.x-a.x),n.height=Math.abs(o.y-i.y),n.x=a.x+n.width/2,n.y=i.y+n.height/2}})),r.forEach(t.nodes(),(function(e){"border"===t.node(e).dummy&&t.removeNode(e)}))}(t)})),e(" normalize.undo",(function(){o.undo(t)})),e(" fixupEdgeLabelCoords",(function(){!function(t){r.forEach(t.edges(),(function(e){var n=t.edge(e);if(r.has(n,"x"))switch("l"!==n.labelpos&&"r"!==n.labelpos||(n.width-=n.labeloffset),n.labelpos){case"l":n.x-=n.width/2+n.labeloffset;break;case"r":n.x+=n.width/2+n.labeloffset}}))}(t)})),e(" undoCoordinateSystem",(function(){h.undo(t)})),e(" translateGraph",(function(){!function(t){var e=Number.POSITIVE_INFINITY,n=0,i=Number.POSITIVE_INFINITY,o=0,a=t.graph(),s=a.marginx||0,u=a.marginy||0;function c(t){var r=t.x,a=t.y,s=t.width,u=t.height;e=Math.min(e,r-s/2),n=Math.max(n,r+s/2),i=Math.min(i,a-u/2),o=Math.max(o,a+u/2)}r.forEach(t.nodes(),(function(e){c(t.node(e))})),r.forEach(t.edges(),(function(e){var n=t.edge(e);r.has(n,"x")&&c(n)})),e-=s,i-=u,r.forEach(t.nodes(),(function(n){var r=t.node(n);r.x-=e,r.y-=i})),r.forEach(t.edges(),(function(n){var o=t.edge(n);r.forEach(o.points,(function(t){t.x-=e,t.y-=i})),r.has(o,"x")&&(o.x-=e),r.has(o,"y")&&(o.y-=i)})),a.width=n-e+s,a.height=o-i+u}(t)})),e(" assignNodeIntersects",(function(){!function(t){r.forEach(t.edges(),(function(e){var n,r,i=t.edge(e),o=t.node(e.v),a=t.node(e.w);i.points?(n=i.points[0],r=i.points[i.points.length-1]):(i.points=[],n=a,r=o),i.points.unshift(g.intersectRect(o,n)),i.points.push(g.intersectRect(a,r))}))}(t)})),e(" reversePoints",(function(){!function(t){r.forEach(t.edges(),(function(e){var n=t.edge(e);n.reversed&&n.points.reverse()}))}(t)})),e(" acyclic.undo",(function(){i.undo(t)}))}(e,n)})),n(" updateInputGraph",(function(){!function(t,e){r.forEach(t.nodes(),(function(n){var r=t.node(n),i=e.node(n);r&&(r.x=i.x,r.y=i.y,e.children(n).length&&(r.width=i.width,r.height=i.height))})),r.forEach(t.edges(),(function(n){var i=t.edge(n),o=e.edge(n);i.points=o.points,r.has(o,"x")&&(i.x=o.x,i.y=o.y)})),t.graph().width=e.graph().width,t.graph().height=e.graph().height}(t,e)}))}))};var b=["nodesep","edgesep","ranksep","marginx","marginy"],m={ranksep:50,edgesep:20,nodesep:50,rankdir:"tb"},v=["acyclicer","ranker","rankdir","align"],_=["width","height"],w={width:0,height:0},x=["minlen","weight","width","height","labeloffset"],k={minlen:1,weight:1,width:0,height:0,labeloffset:10,labelpos:"r"},E=["labelpos"];function A(t,e){return r.mapValues(r.pick(t,e),Number)}function S(t){var e={};return r.forEach(t,(function(t,n){e[n.toLowerCase()]=t})),e}},function(t,e,n){var r=n(130),i=1,o=4;t.exports=function(t){return r(t,i|o)}},function(t,e,n){var r=n(350)(n(351));t.exports=r},function(t,e,n){var r=n(25),i=n(24),o=n(27);t.exports=function(t){return function(e,n,a){var s=Object(e);if(!i(e)){var u=r(n,3);e=o(e),n=function(t){return u(s[t],t,s)}}var c=t(e,n,a);return c>-1?s[u?e[c]:c]:void 0}}},function(t,e,n){var r=n(167),i=n(25),o=n(352),a=Math.max;t.exports=function(t,e,n){var s=null==t?0:t.length;if(!s)return-1;var u=null==n?0:o(n);return u<0&&(u=a(s+u,0)),r(t,i(e,3),u)}},function(t,e,n){var r=n(177);t.exports=function(t){var e=r(t),n=e%1;return e==e?n?e-n:e:0}},function(t,e,n){var r=n(13),i=n(42),o=NaN,a=/^\s+|\s+$/g,s=/^[-+]0x[0-9a-f]+$/i,u=/^0b[01]+$/i,c=/^0o[0-7]+$/i,f=parseInt;t.exports=function(t){if("number"==typeof t)return t;if(i(t))return o;if(r(t)){var e="function"==typeof t.valueOf?t.valueOf():t;t=r(e)?e+"":e}if("string"!=typeof t)return 0===t?t:+t;t=t.replace(a,"");var n=u.test(t);return n||c.test(t)?f(t.slice(2),n?2:8):s.test(t)?o:+t}},function(t,e,n){var r=n(102),i=n(149),o=n(40);t.exports=function(t,e){return null==t?t:r(t,i(e),o)}},function(t,e){t.exports=function(t){var e=null==t?0:t.length;return e?t[e-1]:void 0}},function(t,e,n){var r=n(67),i=n(101),o=n(25);t.exports=function(t,e){var n={};return e=o(e,3),i(t,(function(t,i,o){r(n,i,e(t,i,o))})),n}},function(t,e,n){var r=n(108),i=n(358),o=n(34);t.exports=function(t){return t&&t.length?r(t,o,i):void 0}},function(t,e){t.exports=function(t,e){return t>e}},function(t,e,n){var r=n(360),i=n(363)((function(t,e,n){r(t,e,n)}));t.exports=i},function(t,e,n){var r=n(61),i=n(179),o=n(102),a=n(361),s=n(13),u=n(40),c=n(181);t.exports=function t(e,n,f,l,h){e!==n&&o(n,(function(o,u){if(h||(h=new r),s(o))a(e,n,u,f,t,l,h);else{var d=l?l(c(e,u),o,u+"",e,n,h):void 0;void 0===d&&(d=o),i(e,u,d)}}),u)}},function(t,e,n){var r=n(179),i=n(136),o=n(145),a=n(137),s=n(146),u=n(50),c=n(6),f=n(168),l=n(39),h=n(37),d=n(13),p=n(180),g=n(51),y=n(181),b=n(362);t.exports=function(t,e,n,m,v,_,w){var x=y(t,n),k=y(e,n),E=w.get(k);if(E)r(t,n,E);else{var A=_?_(x,k,n+"",t,e,w):void 0,S=void 0===A;if(S){var T=c(k),M=!T&&l(k),D=!T&&!M&&g(k);A=k,T||M||D?c(x)?A=x:f(x)?A=a(x):M?(S=!1,A=i(k,!0)):D?(S=!1,A=o(k,!0)):A=[]:p(k)||u(k)?(A=x,u(x)?A=b(x):d(x)&&!h(x)||(A=s(k))):S=!1}S&&(w.set(k,A),v(A,k,m,_,w),w.delete(k)),r(t,n,A)}}},function(t,e,n){var r=n(49),i=n(40);t.exports=function(t){return r(t,i(t))}},function(t,e,n){var r=n(75),i=n(76);t.exports=function(t){return r((function(e,n){var r=-1,o=n.length,a=o>1?n[o-1]:void 0,s=o>2?n[2]:void 0;for(a=t.length>3&&"function"==typeof a?(o--,a):void 0,s&&i(n[0],n[1],s)&&(a=o<3?void 0:a,o=1),e=Object(e);++r1&&a(t,e[0],e[1])?e=[]:n>2&&a(e[0],e[1],e[2])&&(e=[e[0]]),i(t,r(e,1),[])}));t.exports=s},function(t,e,n){var r=n(74),i=n(25),o=n(163),a=n(375),s=n(69),u=n(376),c=n(34);t.exports=function(t,e,n){var f=-1;e=r(e.length?e:[c],s(i));var l=o(t,(function(t,n,i){return{criteria:r(e,(function(e){return e(t)})),index:++f,value:t}}));return a(l,(function(t,e){return u(t,e,n)}))}},function(t,e){t.exports=function(t,e){var n=t.length;for(t.sort(e);n--;)t[n]=t[n].value;return t}},function(t,e,n){var r=n(377);t.exports=function(t,e,n){for(var i=-1,o=t.criteria,a=e.criteria,s=o.length,u=n.length;++i=u?c:c*("desc"==n[i]?-1:1)}return t.index-e.index}},function(t,e,n){var r=n(42);t.exports=function(t,e){if(t!==e){var n=void 0!==t,i=null===t,o=t==t,a=r(t),s=void 0!==e,u=null===e,c=e==e,f=r(e);if(!u&&!f&&!a&&t>e||a&&s&&c&&!u&&!f||i&&s&&c||!n&&c||!o)return 1;if(!i&&!a&&!f&&t0;--u)if(r=e[u].dequeue()){i=i.concat(s(t,e,n,r,!0));break}}return i}(n.graph,n.buckets,n.zeroIdx);return r.flatten(r.map(c,(function(e){return t.outEdges(e.v,e.w)})),!0)};var a=r.constant(1);function s(t,e,n,i,o){var a=o?[]:void 0;return r.forEach(t.inEdges(i.v),(function(r){var i=t.edge(r),s=t.node(r.v);o&&a.push({v:r.v,w:r.w}),s.out-=i,u(e,n,s)})),r.forEach(t.outEdges(i.v),(function(r){var i=t.edge(r),o=r.w,a=t.node(o);a.in-=i,u(e,n,a)})),t.removeNode(i.v),a}function u(t,e,n){n.out?n.in?t[n.out-n.in+e].enqueue(n):t[t.length-1].enqueue(n):t[0].enqueue(n)}},function(t,e){function n(){var t={};t._next=t._prev=t,this._sentinel=t}function r(t){t._prev._next=t._next,t._next._prev=t._prev,delete t._next,delete t._prev}function i(t,e){if("_next"!==t&&"_prev"!==t)return e}t.exports=n,n.prototype.dequeue=function(){var t=this._sentinel,e=t._prev;if(e!==t)return r(e),e},n.prototype.enqueue=function(t){var e=this._sentinel;t._prev&&t._next&&r(t),t._next=e._next,e._next._prev=t,e._next=t,t._prev=e},n.prototype.toString=function(){for(var t=[],e=this._sentinel,n=e._prev;n!==e;)t.push(JSON.stringify(n,i)),n=n._prev;return"["+t.join(", ")+"]"}},function(t,e,n){"use strict";var r=n(4),i=n(10);t.exports={run:function(t){t.graph().dummyChains=[],r.forEach(t.edges(),(function(e){!function(t,e){var n,r,o,a=e.v,s=t.node(a).rank,u=e.w,c=t.node(u).rank,f=e.name,l=t.edge(e),h=l.labelRank;if(c===s+1)return;for(t.removeEdge(e),o=0,++s;su.lim&&(c=u,f=!0);var l=r.filter(e.edges(),(function(e){return f===b(t,t.node(e.v),c)&&f!==b(t,t.node(e.w),c)}));return r.minBy(l,(function(t){return o(e,t)}))}function y(t,e,n,i){var o=n.v,a=n.w;t.removeEdge(o,a),t.setEdge(i.v,i.w,{}),d(t),l(t,e),function(t,e){var n=r.find(t.nodes(),(function(t){return!e.node(t).parent})),i=s(t,n);i=i.slice(1),r.forEach(i,(function(n){var r=t.node(n).parent,i=e.edge(n,r),o=!1;i||(i=e.edge(r,n),o=!0),e.node(n).rank=e.node(r).rank+(o?i.minlen:-i.minlen)}))}(t,e)}function b(t,e,n){return n.low<=e.lim&&e.lim<=n.lim}t.exports=f,f.initLowLimValues=d,f.initCutValues=l,f.calcCutValue=h,f.leaveEdge=p,f.enterEdge=g,f.exchangeEdges=y},function(t,e,n){var r=n(4);t.exports=function(t){var e=function(t){var e={},n=0;return r.forEach(t.children(),(function i(o){var a=n;r.forEach(t.children(o),i);e[o]={low:a,lim:n++}})),e}(t);r.forEach(t.graph().dummyChains,(function(n){for(var r=t.node(n),i=r.edgeObj,o=function(t,e,n,r){var i,o,a=[],s=[],u=Math.min(e[n].low,e[r].low),c=Math.max(e[n].lim,e[r].lim);i=n;do{i=t.parent(i),a.push(i)}while(i&&(e[i].low>u||c>e[i].lim));o=i,i=r;for(;(i=t.parent(i))!==o;)s.push(i);return{path:a.concat(s.reverse()),lca:o}}(t,e,i.v,i.w),a=o.path,s=o.lca,u=0,c=a[u],f=!0;n!==i.w;){if(r=t.node(n),f){for(;(c=a[u])!==s&&t.node(c).maxRank=2),s=f.buildLayerMatrix(t);var y=o(t,s);y0;)e%2&&(n+=u[e+1]),u[e=e-1>>1]+=t.weight;c+=t.weight*n}))),c}t.exports=function(t,e){for(var n=0,r=1;r=t.barycenter)&&function(t,e){var n=0,r=0;t.weight&&(n+=t.barycenter*t.weight,r+=t.weight);e.weight&&(n+=e.barycenter*e.weight,r+=e.weight);t.vs=e.vs.concat(t.vs),t.barycenter=n/r,t.weight=r,t.i=Math.min(e.i,t.i),e.merged=!0}(t,e)}}function i(e){return function(n){n.in.push(e),0==--n.indegree&&t.push(n)}}for(;t.length;){var o=t.pop();e.push(o),r.forEach(o.in.reverse(),n(o)),r.forEach(o.out,i(o))}return r.map(r.filter(e,(function(t){return!t.merged})),(function(t){return r.pick(t,["vs","i","barycenter","weight"])}))}(r.filter(n,(function(t){return!t.indegree})))}},function(t,e,n){var r=n(4),i=n(10);function o(t,e,n){for(var i;e.length&&(i=r.last(e)).i<=n;)e.pop(),t.push(i.vs),n++;return n}t.exports=function(t,e){var n=i.partition(t,(function(t){return r.has(t,"barycenter")})),a=n.lhs,s=r.sortBy(n.rhs,(function(t){return-t.i})),u=[],c=0,f=0,l=0;a.sort((h=!!e,function(t,e){return t.barycentere.barycenter?1:h?e.i-t.i:t.i-e.i})),l=o(u,s,l),r.forEach(a,(function(t){l+=t.vs.length,u.push(t.vs),c+=t.barycenter*t.weight,f+=t.weight,l=o(u,s,l)}));var h;var d={vs:r.flatten(u,!0)};f&&(d.barycenter=c/f,d.weight=f);return d}},function(t,e,n){var r=n(4),i=n(19).Graph;t.exports=function(t,e,n){var o=function(t){var e;for(;t.hasNode(e=r.uniqueId("_root")););return e}(t),a=new i({compound:!0}).setGraph({root:o}).setDefaultNodeLabel((function(e){return t.node(e)}));return r.forEach(t.nodes(),(function(i){var s=t.node(i),u=t.parent(i);(s.rank===e||s.minRank<=e&&e<=s.maxRank)&&(a.setNode(i),a.setParent(i,u||o),r.forEach(t[n](i),(function(e){var n=e.v===i?e.w:e.v,o=a.edge(n,i),s=r.isUndefined(o)?0:o.weight;a.setEdge(n,i,{weight:t.edge(e).weight+s})})),r.has(s,"minRank")&&a.setNode(i,{borderLeft:s.borderLeft[e],borderRight:s.borderRight[e]}))})),a}},function(t,e,n){var r=n(4);t.exports=function(t,e,n){var i,o={};r.forEach(n,(function(n){for(var r,a,s=t.parent(n);s;){if((r=t.parent(s))?(a=o[r],o[r]=s):(a=i,i=s),a&&a!==s)return void e.setEdge(a,s);s=r}}))}},function(t,e,n){"use strict";var r=n(4),i=n(10),o=n(400).positionX;t.exports=function(t){(function(t){var e=i.buildLayerMatrix(t),n=t.graph().ranksep,o=0;r.forEach(e,(function(e){var i=r.max(r.map(e,(function(e){return t.node(e).height})));r.forEach(e,(function(e){t.node(e).y=o+i/2})),o+=i+n}))})(t=i.asNonCompoundGraph(t)),r.forEach(o(t),(function(e,n){t.node(n).x=e}))}},function(t,e,n){"use strict";var r=n(4),i=n(19).Graph,o=n(10);function a(t,e){var n={};return r.reduce(e,(function(e,i){var o=0,a=0,s=e.length,c=r.last(i);return r.forEach(i,(function(e,f){var l=function(t,e){if(t.node(e).dummy)return r.find(t.predecessors(e),(function(e){return t.node(e).dummy}))}(t,e),h=l?t.node(l).order:s;(l||e===c)&&(r.forEach(i.slice(a,f+1),(function(e){r.forEach(t.predecessors(e),(function(r){var i=t.node(r),a=i.order;!(as)&&u(n,e,c)}))}))}return r.reduce(e,(function(e,n){var o,a=-1,s=0;return r.forEach(n,(function(r,u){if("border"===t.node(r).dummy){var c=t.predecessors(r);c.length&&(o=t.node(c[0]).order,i(n,s,u,a,o),s=u,a=o)}i(n,s,n.length,o,e.length)})),n})),n}function u(t,e,n){if(e>n){var r=e;e=n,n=r}var i=t[e];i||(t[e]=i={}),i[n]=!0}function c(t,e,n){if(e>n){var i=e;e=n,n=i}return r.has(t[e],n)}function f(t,e,n,i){var o={},a={},s={};return r.forEach(e,(function(t){r.forEach(t,(function(t,e){o[t]=t,a[t]=t,s[t]=e}))})),r.forEach(e,(function(t){var e=-1;r.forEach(t,(function(t){var u=i(t);if(u.length)for(var f=((u=r.sortBy(u,(function(t){return s[t]}))).length-1)/2,l=Math.floor(f),h=Math.ceil(f);l<=h;++l){var d=u[l];a[t]===t&&e0}t.exports=function(t,e,r,i){var o,a,s,u,c,f,l,h,d,p,g,y,b;if(o=e.y-t.y,s=t.x-e.x,c=e.x*t.y-t.x*e.y,d=o*r.x+s*r.y+c,p=o*i.x+s*i.y+c,0!==d&&0!==p&&n(d,p))return;if(a=i.y-r.y,u=r.x-i.x,f=i.x*r.y-r.x*i.y,l=a*t.x+u*t.y+f,h=a*e.x+u*e.y+f,0!==l&&0!==h&&n(l,h))return;if(0===(g=o*u-a*s))return;return y=Math.abs(g/2),{x:(b=s*f-u*c)<0?(b-y)/g:(b+y)/g,y:(b=a*c-o*f)<0?(b-y)/g:(b+y)/g}}},function(t,e,n){var r=n(43),i=n(30),o=n(175).layout;t.exports=function(){var t=n(406),e=n(409),i=n(410),c=n(411),f=n(412),l=n(413),h=n(414),d=n(415),p=n(416),g=function(n,g){!function(t){t.nodes().forEach((function(e){var n=t.node(e);r.has(n,"label")||t.children(e).length||(n.label=e),r.has(n,"paddingX")&&r.defaults(n,{paddingLeft:n.paddingX,paddingRight:n.paddingX}),r.has(n,"paddingY")&&r.defaults(n,{paddingTop:n.paddingY,paddingBottom:n.paddingY}),r.has(n,"padding")&&r.defaults(n,{paddingLeft:n.padding,paddingRight:n.padding,paddingTop:n.padding,paddingBottom:n.padding}),r.defaults(n,a),r.each(["paddingLeft","paddingRight","paddingTop","paddingBottom"],(function(t){n[t]=Number(n[t])})),r.has(n,"width")&&(n._prevWidth=n.width),r.has(n,"height")&&(n._prevHeight=n.height)})),t.edges().forEach((function(e){var n=t.edge(e);r.has(n,"label")||(n.label=""),r.defaults(n,s)}))}(g);var y=u(n,"output"),b=u(y,"clusters"),m=u(y,"edgePaths"),v=i(u(y,"edgeLabels"),g),_=t(u(y,"nodes"),g,d);o(g),f(_,g),l(v,g),c(m,g,p);var w=e(b,g);h(w,g),function(t){r.each(t.nodes(),(function(e){var n=t.node(e);r.has(n,"_prevWidth")?n.width=n._prevWidth:delete n.width,r.has(n,"_prevHeight")?n.height=n._prevHeight:delete n.height,delete n._prevWidth,delete n._prevHeight}))}(g)};return g.createNodes=function(e){return arguments.length?(t=e,g):t},g.createClusters=function(t){return arguments.length?(e=t,g):e},g.createEdgeLabels=function(t){return arguments.length?(i=t,g):i},g.createEdgePaths=function(t){return arguments.length?(c=t,g):c},g.shapes=function(t){return arguments.length?(d=t,g):d},g.arrows=function(t){return arguments.length?(p=t,g):p},g};var a={paddingLeft:10,paddingRight:10,paddingTop:10,paddingBottom:10,rx:0,ry:0,shape:"rect"},s={arrowhead:"normal",curve:i.curveLinear};function u(t,e){var n=t.select("g."+e);return n.empty()&&(n=t.append("g").attr("class",e)),n}},function(t,e,n){"use strict";var r=n(43),i=n(110),o=n(14),a=n(30);t.exports=function(t,e,n){var s,u=e.nodes().filter((function(t){return!o.isSubgraph(e,t)})),c=t.selectAll("g.node").data(u,(function(t){return t})).classed("update",!0);c.exit().remove(),c.enter().append("g").attr("class","node").style("opacity",0),(c=t.selectAll("g.node")).each((function(t){var s=e.node(t),u=a.select(this);o.applyClass(u,s.class,(u.classed("update")?"update ":"")+"node"),u.select("g.label").remove();var c=u.append("g").attr("class","label"),f=i(c,s),l=n[s.shape],h=r.pick(f.node().getBBox(),"width","height");s.elem=this,s.id&&u.attr("id",s.id),s.labelId&&c.attr("id",s.labelId),r.has(s,"width")&&(h.width=s.width),r.has(s,"height")&&(h.height=s.height),h.width+=s.paddingLeft+s.paddingRight,h.height+=s.paddingTop+s.paddingBottom,c.attr("transform","translate("+(s.paddingLeft-s.paddingRight)/2+","+(s.paddingTop-s.paddingBottom)/2+")");var d=a.select(this);d.select(".label-container").remove();var p=l(d,h,s).classed("label-container",!0);o.applyStyle(p,s.style);var g=p.node().getBBox();s.width=g.width,s.height=g.height})),s=c.exit?c.exit():c.selectAll(null);return o.applyTransition(s,e).style("opacity",0).remove(),c}},function(t,e,n){var r=n(14);t.exports=function(t,e){for(var n=t.append("text"),i=function(t){for(var e,n="",r=!1,i=0;i0?a-4:a;for(n=0;n>16&255,u[f++]=e>>8&255,u[f++]=255&e;2===s&&(e=i[t.charCodeAt(n)]<<2|i[t.charCodeAt(n+1)]>>4,u[f++]=255&e);1===s&&(e=i[t.charCodeAt(n)]<<10|i[t.charCodeAt(n+1)]<<4|i[t.charCodeAt(n+2)]>>2,u[f++]=e>>8&255,u[f++]=255&e);return u},e.fromByteArray=function(t){for(var e,n=t.length,i=n%3,o=[],a=0,s=n-i;as?s:a+16383));1===i?(e=t[n-1],o.push(r[e>>2]+r[e<<4&63]+"==")):2===i&&(e=(t[n-2]<<8)+t[n-1],o.push(r[e>>10]+r[e>>4&63]+r[e<<2&63]+"="));return o.join("")};for(var r=[],i=[],o="undefined"!=typeof Uint8Array?Uint8Array:Array,a="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",s=0,u=a.length;s0)throw new Error("Invalid string. Length must be a multiple of 4");var n=t.indexOf("=");return-1===n&&(n=e),[n,n===e?0:4-n%4]}function f(t,e,n){for(var i,o,a=[],s=e;s>18&63]+r[o>>12&63]+r[o>>6&63]+r[63&o]);return a.join("")}i["-".charCodeAt(0)]=62,i["_".charCodeAt(0)]=63},function(t,e){e.read=function(t,e,n,r,i){var o,a,s=8*i-r-1,u=(1<>1,f=-7,l=n?i-1:0,h=n?-1:1,d=t[e+l];for(l+=h,o=d&(1<<-f)-1,d>>=-f,f+=s;f>0;o=256*o+t[e+l],l+=h,f-=8);for(a=o&(1<<-f)-1,o>>=-f,f+=r;f>0;a=256*a+t[e+l],l+=h,f-=8);if(0===o)o=1-c;else{if(o===u)return a?NaN:1/0*(d?-1:1);a+=Math.pow(2,r),o-=c}return(d?-1:1)*a*Math.pow(2,o-r)},e.write=function(t,e,n,r,i,o){var a,s,u,c=8*o-i-1,f=(1<>1,h=23===i?Math.pow(2,-24)-Math.pow(2,-77):0,d=r?0:o-1,p=r?1:-1,g=e<0||0===e&&1/e<0?1:0;for(e=Math.abs(e),isNaN(e)||e===1/0?(s=isNaN(e)?1:0,a=f):(a=Math.floor(Math.log(e)/Math.LN2),e*(u=Math.pow(2,-a))<1&&(a--,u*=2),(e+=a+l>=1?h/u:h*Math.pow(2,1-l))*u>=2&&(a++,u/=2),a+l>=f?(s=0,a=f):a+l>=1?(s=(e*u-1)*Math.pow(2,i),a+=l):(s=e*Math.pow(2,l-1)*Math.pow(2,i),a=0));i>=8;t[n+d]=255&s,d+=p,s/=256,i-=8);for(a=a<0;t[n+d]=255&a,d+=p,a/=256,c-=8);t[n+d-p]|=128*g}},function(t,e){},function(t,e,n){"use strict";var r=n(115).Buffer,i=n(423);t.exports=function(){function t(){!function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}(this,t),this.head=null,this.tail=null,this.length=0}return t.prototype.push=function(t){var e={data:t,next:null};this.length>0?this.tail.next=e:this.head=e,this.tail=e,++this.length},t.prototype.unshift=function(t){var e={data:t,next:this.head};0===this.length&&(this.tail=e),this.head=e,++this.length},t.prototype.shift=function(){if(0!==this.length){var t=this.head.data;return 1===this.length?this.head=this.tail=null:this.head=this.head.next,--this.length,t}},t.prototype.clear=function(){this.head=this.tail=null,this.length=0},t.prototype.join=function(t){if(0===this.length)return"";for(var e=this.head,n=""+e.data;e=e.next;)n+=t+e.data;return n},t.prototype.concat=function(t){if(0===this.length)return r.alloc(0);if(1===this.length)return this.head.data;for(var e,n,i,o=r.allocUnsafe(t>>>0),a=this.head,s=0;a;)e=a.data,n=o,i=s,e.copy(n,i),s+=a.data.length,a=a.next;return o},t}(),i&&i.inspect&&i.inspect.custom&&(t.exports.prototype[i.inspect.custom]=function(){var t=i.inspect({length:this.length});return this.constructor.name+" "+t})},function(t,e){},function(t,e,n){(function(t){var r=void 0!==t&&t||"undefined"!=typeof self&&self||window,i=Function.prototype.apply;function o(t,e){this._id=t,this._clearFn=e}e.setTimeout=function(){return new o(i.call(setTimeout,r,arguments),clearTimeout)},e.setInterval=function(){return new o(i.call(setInterval,r,arguments),clearInterval)},e.clearTimeout=e.clearInterval=function(t){t&&t.close()},o.prototype.unref=o.prototype.ref=function(){},o.prototype.close=function(){this._clearFn.call(r,this._id)},e.enroll=function(t,e){clearTimeout(t._idleTimeoutId),t._idleTimeout=e},e.unenroll=function(t){clearTimeout(t._idleTimeoutId),t._idleTimeout=-1},e._unrefActive=e.active=function(t){clearTimeout(t._idleTimeoutId);var e=t._idleTimeout;e>=0&&(t._idleTimeoutId=setTimeout((function(){t._onTimeout&&t._onTimeout()}),e))},n(425),e.setImmediate="undefined"!=typeof self&&self.setImmediate||void 0!==t&&t.setImmediate||this&&this.setImmediate,e.clearImmediate="undefined"!=typeof self&&self.clearImmediate||void 0!==t&&t.clearImmediate||this&&this.clearImmediate}).call(this,n(11))},function(t,e,n){(function(t,e){!function(t,n){"use strict";if(!t.setImmediate){var r,i,o,a,s,u=1,c={},f=!1,l=t.document,h=Object.getPrototypeOf&&Object.getPrototypeOf(t);h=h&&h.setTimeout?h:t,"[object process]"==={}.toString.call(t.process)?r=function(t){e.nextTick((function(){p(t)}))}:!function(){if(t.postMessage&&!t.importScripts){var e=!0,n=t.onmessage;return t.onmessage=function(){e=!1},t.postMessage("","*"),t.onmessage=n,e}}()?t.MessageChannel?((o=new MessageChannel).port1.onmessage=function(t){p(t.data)},r=function(t){o.port2.postMessage(t)}):l&&"onreadystatechange"in l.createElement("script")?(i=l.documentElement,r=function(t){var e=l.createElement("script");e.onreadystatechange=function(){p(t),e.onreadystatechange=null,i.removeChild(e),e=null},i.appendChild(e)}):r=function(t){setTimeout(p,0,t)}:(a="setImmediate$"+Math.random()+"$",s=function(e){e.source===t&&"string"==typeof e.data&&0===e.data.indexOf(a)&&p(+e.data.slice(a.length))},t.addEventListener?t.addEventListener("message",s,!1):t.attachEvent("onmessage",s),r=function(e){t.postMessage(a+e,"*")}),h.setImmediate=function(t){"function"!=typeof t&&(t=new Function(""+t));for(var e=new Array(arguments.length-1),n=0;n>>2}function f(t,e,n,r){return 0===t?e&n|~e&r:2===t?e&n|e&r|n&r:e^n^r}r(u,i),u.prototype.init=function(){return this._a=1732584193,this._b=4023233417,this._c=2562383102,this._d=271733878,this._e=3285377520,this},u.prototype._update=function(t){for(var e,n=this._w,r=0|this._a,i=0|this._b,o=0|this._c,s=0|this._d,u=0|this._e,l=0;l<16;++l)n[l]=t.readInt32BE(4*l);for(;l<80;++l)n[l]=n[l-3]^n[l-8]^n[l-14]^n[l-16];for(var h=0;h<80;++h){var d=~~(h/20),p=0|((e=r)<<5|e>>>27)+f(d,i,o,s)+u+n[h]+a[d];u=s,s=o,o=c(i),i=r,r=p}this._a=r+this._a|0,this._b=i+this._b|0,this._c=o+this._c|0,this._d=s+this._d|0,this._e=u+this._e|0},u.prototype._hash=function(){var t=o.allocUnsafe(20);return t.writeInt32BE(0|this._a,0),t.writeInt32BE(0|this._b,4),t.writeInt32BE(0|this._c,8),t.writeInt32BE(0|this._d,12),t.writeInt32BE(0|this._e,16),t},t.exports=u},function(t,e,n){var r=n(2),i=n(45),o=n(3).Buffer,a=[1518500249,1859775393,-1894007588,-899497514],s=new Array(80);function u(){this.init(),this._w=s,i.call(this,64,56)}function c(t){return t<<5|t>>>27}function f(t){return t<<30|t>>>2}function l(t,e,n,r){return 0===t?e&n|~e&r:2===t?e&n|e&r|n&r:e^n^r}r(u,i),u.prototype.init=function(){return this._a=1732584193,this._b=4023233417,this._c=2562383102,this._d=271733878,this._e=3285377520,this},u.prototype._update=function(t){for(var e,n=this._w,r=0|this._a,i=0|this._b,o=0|this._c,s=0|this._d,u=0|this._e,h=0;h<16;++h)n[h]=t.readInt32BE(4*h);for(;h<80;++h)n[h]=(e=n[h-3]^n[h-8]^n[h-14]^n[h-16])<<1|e>>>31;for(var d=0;d<80;++d){var p=~~(d/20),g=c(r)+l(p,i,o,s)+u+n[d]+a[p]|0;u=s,s=o,o=f(i),i=r,r=g}this._a=r+this._a|0,this._b=i+this._b|0,this._c=o+this._c|0,this._d=s+this._d|0,this._e=u+this._e|0},u.prototype._hash=function(){var t=o.allocUnsafe(20);return t.writeInt32BE(0|this._a,0),t.writeInt32BE(0|this._b,4),t.writeInt32BE(0|this._c,8),t.writeInt32BE(0|this._d,12),t.writeInt32BE(0|this._e,16),t},t.exports=u},function(t,e,n){var r=n(2),i=n(197),o=n(45),a=n(3).Buffer,s=new Array(64);function u(){this.init(),this._w=s,o.call(this,64,56)}r(u,i),u.prototype.init=function(){return this._a=3238371032,this._b=914150663,this._c=812702999,this._d=4144912697,this._e=4290775857,this._f=1750603025,this._g=1694076839,this._h=3204075428,this},u.prototype._hash=function(){var t=a.allocUnsafe(28);return t.writeInt32BE(this._a,0),t.writeInt32BE(this._b,4),t.writeInt32BE(this._c,8),t.writeInt32BE(this._d,12),t.writeInt32BE(this._e,16),t.writeInt32BE(this._f,20),t.writeInt32BE(this._g,24),t},t.exports=u},function(t,e,n){var r=n(2),i=n(198),o=n(45),a=n(3).Buffer,s=new Array(160);function u(){this.init(),this._w=s,o.call(this,128,112)}r(u,i),u.prototype.init=function(){return this._ah=3418070365,this._bh=1654270250,this._ch=2438529370,this._dh=355462360,this._eh=1731405415,this._fh=2394180231,this._gh=3675008525,this._hh=1203062813,this._al=3238371032,this._bl=914150663,this._cl=812702999,this._dl=4144912697,this._el=4290775857,this._fl=1750603025,this._gl=1694076839,this._hl=3204075428,this},u.prototype._hash=function(){var t=a.allocUnsafe(48);function e(e,n,r){t.writeInt32BE(e,r),t.writeInt32BE(n,r+4)}return e(this._ah,this._al,0),e(this._bh,this._bl,8),e(this._ch,this._cl,16),e(this._dh,this._dl,24),e(this._eh,this._el,32),e(this._fh,this._fl,40),t},t.exports=u},function(t,e,n){"use strict";var r=n(2),i=n(3).Buffer,o=n(31),a=i.alloc(128),s=64;function u(t,e){o.call(this,"digest"),"string"==typeof e&&(e=i.from(e)),this._alg=t,this._key=e,e.length>s?e=t(e):e.length>>0},e.writeUInt32BE=function(t,e,n){t[0+n]=e>>>24,t[1+n]=e>>>16&255,t[2+n]=e>>>8&255,t[3+n]=255&e},e.ip=function(t,e,n,r){for(var i=0,o=0,a=6;a>=0;a-=2){for(var s=0;s<=24;s+=8)i<<=1,i|=e>>>s+a&1;for(s=0;s<=24;s+=8)i<<=1,i|=t>>>s+a&1}for(a=6;a>=0;a-=2){for(s=1;s<=25;s+=8)o<<=1,o|=e>>>s+a&1;for(s=1;s<=25;s+=8)o<<=1,o|=t>>>s+a&1}n[r+0]=i>>>0,n[r+1]=o>>>0},e.rip=function(t,e,n,r){for(var i=0,o=0,a=0;a<4;a++)for(var s=24;s>=0;s-=8)i<<=1,i|=e>>>s+a&1,i<<=1,i|=t>>>s+a&1;for(a=4;a<8;a++)for(s=24;s>=0;s-=8)o<<=1,o|=e>>>s+a&1,o<<=1,o|=t>>>s+a&1;n[r+0]=i>>>0,n[r+1]=o>>>0},e.pc1=function(t,e,n,r){for(var i=0,o=0,a=7;a>=5;a--){for(var s=0;s<=24;s+=8)i<<=1,i|=e>>s+a&1;for(s=0;s<=24;s+=8)i<<=1,i|=t>>s+a&1}for(s=0;s<=24;s+=8)i<<=1,i|=e>>s+a&1;for(a=1;a<=3;a++){for(s=0;s<=24;s+=8)o<<=1,o|=e>>s+a&1;for(s=0;s<=24;s+=8)o<<=1,o|=t>>s+a&1}for(s=0;s<=24;s+=8)o<<=1,o|=t>>s+a&1;n[r+0]=i>>>0,n[r+1]=o>>>0},e.r28shl=function(t,e){return t<>>28-e};var r=[14,11,17,4,27,23,25,0,13,22,7,18,5,9,16,24,2,20,12,21,1,8,15,26,15,4,25,19,9,1,26,16,5,11,23,8,12,7,17,0,22,3,10,14,6,20,27,24];e.pc2=function(t,e,n,i){for(var o=0,a=0,s=r.length>>>1,u=0;u>>r[u]&1;for(u=s;u>>r[u]&1;n[i+0]=o>>>0,n[i+1]=a>>>0},e.expand=function(t,e,n){var r=0,i=0;r=(1&t)<<5|t>>>27;for(var o=23;o>=15;o-=4)r<<=6,r|=t>>>o&63;for(o=11;o>=3;o-=4)i|=t>>>o&63,i<<=6;i|=(31&t)<<1|t>>>31,e[n+0]=r>>>0,e[n+1]=i>>>0};var i=[14,0,4,15,13,7,1,4,2,14,15,2,11,13,8,1,3,10,10,6,6,12,12,11,5,9,9,5,0,3,7,8,4,15,1,12,14,8,8,2,13,4,6,9,2,1,11,7,15,5,12,11,9,3,7,14,3,10,10,0,5,6,0,13,15,3,1,13,8,4,14,7,6,15,11,2,3,8,4,14,9,12,7,0,2,1,13,10,12,6,0,9,5,11,10,5,0,13,14,8,7,10,11,1,10,3,4,15,13,4,1,2,5,11,8,6,12,7,6,12,9,0,3,5,2,14,15,9,10,13,0,7,9,0,14,9,6,3,3,4,15,6,5,10,1,2,13,8,12,5,7,14,11,12,4,11,2,15,8,1,13,1,6,10,4,13,9,0,8,6,15,9,3,8,0,7,11,4,1,15,2,14,12,3,5,11,10,5,14,2,7,12,7,13,13,8,14,11,3,5,0,6,6,15,9,0,10,3,1,4,2,7,8,2,5,12,11,1,12,10,4,14,15,9,10,3,6,15,9,0,0,6,12,10,11,1,7,13,13,8,15,9,1,4,3,5,14,11,5,12,2,7,8,2,4,14,2,14,12,11,4,2,1,12,7,4,10,7,11,13,6,1,8,5,5,0,3,15,15,10,13,3,0,9,14,8,9,6,4,11,2,8,1,12,11,7,10,1,13,14,7,2,8,13,15,6,9,15,12,0,5,9,6,10,3,4,0,5,14,3,12,10,1,15,10,4,15,2,9,7,2,12,6,9,8,5,0,6,13,1,3,13,4,14,14,0,7,11,5,3,11,8,9,4,14,3,15,2,5,12,2,9,8,5,12,15,3,10,7,11,0,14,4,1,10,7,1,6,13,0,11,8,6,13,4,13,11,0,2,11,14,7,15,4,0,9,8,1,13,10,3,14,12,3,9,5,7,12,5,2,10,15,6,8,1,6,1,6,4,11,11,13,13,8,12,1,3,4,7,10,14,7,10,9,15,5,6,0,8,15,0,14,5,2,9,3,2,12,13,1,2,15,8,13,4,8,6,10,15,3,11,7,1,4,10,12,9,5,3,6,14,11,5,0,0,14,12,9,7,2,7,2,11,1,4,14,1,7,9,4,12,10,14,8,2,13,0,15,6,12,10,9,13,0,15,3,3,5,5,6,8,11];e.substitute=function(t,e){for(var n=0,r=0;r<4;r++){n<<=4,n|=i[64*r+(t>>>18-6*r&63)]}for(r=0;r<4;r++){n<<=4,n|=i[256+64*r+(e>>>18-6*r&63)]}return n>>>0};var o=[16,25,12,11,3,20,4,15,31,17,9,6,27,14,1,22,30,24,8,18,0,5,29,23,13,19,2,26,10,21,28,7];e.permute=function(t){for(var e=0,n=0;n>>o[n]&1;return e>>>0},e.padSplit=function(t,e,n){for(var r=t.toString(2);r.length0;r--)e+=this._buffer(t,e),n+=this._flushBuffer(i,n);return e+=this._buffer(t,e),i},i.prototype.final=function(t){var e,n;return t&&(e=this.update(t)),n="encrypt"===this.type?this._finalEncrypt():this._finalDecrypt(),e?e.concat(n):n},i.prototype._pad=function(t,e){if(0===e)return!1;for(;e>>1];n=a.r28shl(n,s),i=a.r28shl(i,s),a.pc2(n,i,t.keys,o)}},c.prototype._update=function(t,e,n,r){var i=this._desState,o=a.readUInt32BE(t,e),s=a.readUInt32BE(t,e+4);a.ip(o,s,i.tmp,0),o=i.tmp[0],s=i.tmp[1],"encrypt"===this.type?this._encrypt(i,o,s,i.tmp,0):this._decrypt(i,o,s,i.tmp,0),o=i.tmp[0],s=i.tmp[1],a.writeUInt32BE(n,o,r),a.writeUInt32BE(n,s,r+4)},c.prototype._pad=function(t,e){for(var n=t.length-e,r=e;r>>0,o=h}a.rip(s,o,r,i)},c.prototype._decrypt=function(t,e,n,r,i){for(var o=n,s=e,u=t.keys.length-2;u>=0;u-=2){var c=t.keys[u],f=t.keys[u+1];a.expand(o,t.tmp,0),c^=t.tmp[0],f^=t.tmp[1];var l=a.substitute(c,f),h=o;o=(s^a.permute(l))>>>0,s=h}a.rip(o,s,r,i)}},function(t,e,n){"use strict";var r=n(15),i=n(2),o={};function a(t){r.equal(t.length,8,"Invalid IV length"),this.iv=new Array(8);for(var e=0;e15){var t=this.cache.slice(0,16);return this.cache=this.cache.slice(16),t}return null},h.prototype.flush=function(){for(var t=16-this.cache.length,e=o.allocUnsafe(t),n=-1;++n>a%8,t._prev=o(t._prev,n?r:i);return s}function o(t,e){var n=t.length,i=-1,o=r.allocUnsafe(t.length);for(t=r.concat([t,r.from([e])]);++i>7;return o}e.encrypt=function(t,e,n){for(var o=e.length,a=r.allocUnsafe(o),s=-1;++s>>0,0),e.writeUInt32BE(t[1]>>>0,4),e.writeUInt32BE(t[2]>>>0,8),e.writeUInt32BE(t[3]>>>0,12),e}function a(t){this.h=t,this.state=r.alloc(16,0),this.cache=r.allocUnsafe(0)}a.prototype.ghash=function(t){for(var e=-1;++e0;e--)r[e]=r[e]>>>1|(1&r[e-1])<<31;r[0]=r[0]>>>1,n&&(r[0]=r[0]^225<<24)}this.state=o(i)},a.prototype.update=function(t){var e;for(this.cache=r.concat([this.cache,t]);this.cache.length>=16;)e=this.cache.slice(0,16),this.cache=this.cache.slice(16),this.ghash(e)},a.prototype.final=function(t,e){return this.cache.length&&this.ghash(r.concat([this.cache,i],16)),this.ghash(o([0,t,0,e])),this.state},t.exports=a},function(t,e,n){var r=n(209),i=n(3).Buffer,o=n(122),a=n(210),s=n(31),u=n(79),c=n(80);function f(t,e,n){s.call(this),this._cache=new l,this._last=void 0,this._cipher=new u.AES(e),this._prev=i.from(n),this._mode=t,this._autopadding=!0}function l(){this.cache=i.allocUnsafe(0)}function h(t,e,n){var s=o[t.toLowerCase()];if(!s)throw new TypeError("invalid suite type");if("string"==typeof n&&(n=i.from(n)),"GCM"!==s.mode&&n.length!==s.iv)throw new TypeError("invalid iv length "+n.length);if("string"==typeof e&&(e=i.from(e)),e.length!==s.key/8)throw new TypeError("invalid key length "+e.length);return"stream"===s.type?new a(s.module,e,n,!0):"auth"===s.type?new r(s.module,e,n,!0):new f(s.module,e,n)}n(2)(f,s),f.prototype._update=function(t){var e,n;this._cache.add(t);for(var r=[];e=this._cache.get(this._autopadding);)n=this._mode.decrypt(this,e),r.push(n);return i.concat(r)},f.prototype._final=function(){var t=this._cache.flush();if(this._autopadding)return function(t){var e=t[15];if(e<1||e>16)throw new Error("unable to decrypt data");var n=-1;for(;++n16)return e=this.cache.slice(0,16),this.cache=this.cache.slice(16),e}else if(this.cache.length>=16)return e=this.cache.slice(0,16),this.cache=this.cache.slice(16),e;return null},l.prototype.flush=function(){if(this.cache.length)return this.cache},e.createDecipher=function(t,e){var n=o[t.toLowerCase()];if(!n)throw new TypeError("invalid suite type");var r=c(e,!1,n.key,n.iv);return h(t,r.key,r.iv)},e.createDecipheriv=h},function(t,e){e["des-ecb"]={key:8,iv:0},e["des-cbc"]=e.des={key:8,iv:8},e["des-ede3-cbc"]=e.des3={key:24,iv:8},e["des-ede3"]={key:24,iv:0},e["des-ede-cbc"]={key:16,iv:8},e["des-ede"]={key:16,iv:0}},function(t,e,n){(function(t){var r=n(211),i=n(459),o=n(460);var a={binary:!0,hex:!0,base64:!0};e.DiffieHellmanGroup=e.createDiffieHellmanGroup=e.getDiffieHellman=function(e){var n=new t(i[e].prime,"hex"),r=new t(i[e].gen,"hex");return new o(n,r)},e.createDiffieHellman=e.DiffieHellman=function e(n,i,s,u){return t.isBuffer(i)||void 0===a[i]?e(n,"binary",i,s):(i=i||"binary",u=u||"binary",s=s||new t([2]),t.isBuffer(s)||(s=new t(s,u)),"number"==typeof n?new o(r(n,s),s,!0):(t.isBuffer(n)||(n=new t(n,i)),new o(n,s,!0)))}}).call(this,n(8).Buffer)},function(t,e){},function(t,e){},function(t){t.exports=JSON.parse('{"modp1":{"gen":"02","prime":"ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a63a3620ffffffffffffffff"},"modp2":{"gen":"02","prime":"ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece65381ffffffffffffffff"},"modp5":{"gen":"02","prime":"ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf0598da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb9ed529077096966d670c354e4abc9804f1746c08ca237327ffffffffffffffff"},"modp14":{"gen":"02","prime":"ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf0598da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb9ed529077096966d670c354e4abc9804f1746c08ca18217c32905e462e36ce3be39e772c180e86039b2783a2ec07a28fb5c55df06f4c52c9de2bcbf6955817183995497cea956ae515d2261898fa051015728e5a8aacaa68ffffffffffffffff"},"modp15":{"gen":"02","prime":"ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf0598da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb9ed529077096966d670c354e4abc9804f1746c08ca18217c32905e462e36ce3be39e772c180e86039b2783a2ec07a28fb5c55df06f4c52c9de2bcbf6955817183995497cea956ae515d2261898fa051015728e5a8aaac42dad33170d04507a33a85521abdf1cba64ecfb850458dbef0a8aea71575d060c7db3970f85a6e1e4c7abf5ae8cdb0933d71e8c94e04a25619dcee3d2261ad2ee6bf12ffa06d98a0864d87602733ec86a64521f2b18177b200cbbe117577a615d6c770988c0bad946e208e24fa074e5ab3143db5bfce0fd108e4b82d120a93ad2caffffffffffffffff"},"modp16":{"gen":"02","prime":"ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf0598da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb9ed529077096966d670c354e4abc9804f1746c08ca18217c32905e462e36ce3be39e772c180e86039b2783a2ec07a28fb5c55df06f4c52c9de2bcbf6955817183995497cea956ae515d2261898fa051015728e5a8aaac42dad33170d04507a33a85521abdf1cba64ecfb850458dbef0a8aea71575d060c7db3970f85a6e1e4c7abf5ae8cdb0933d71e8c94e04a25619dcee3d2261ad2ee6bf12ffa06d98a0864d87602733ec86a64521f2b18177b200cbbe117577a615d6c770988c0bad946e208e24fa074e5ab3143db5bfce0fd108e4b82d120a92108011a723c12a787e6d788719a10bdba5b2699c327186af4e23c1a946834b6150bda2583e9ca2ad44ce8dbbbc2db04de8ef92e8efc141fbecaa6287c59474e6bc05d99b2964fa090c3a2233ba186515be7ed1f612970cee2d7afb81bdd762170481cd0069127d5b05aa993b4ea988d8fddc186ffb7dc90a6c08f4df435c934063199ffffffffffffffff"},"modp17":{"gen":"02","prime":"ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf0598da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb9ed529077096966d670c354e4abc9804f1746c08ca18217c32905e462e36ce3be39e772c180e86039b2783a2ec07a28fb5c55df06f4c52c9de2bcbf6955817183995497cea956ae515d2261898fa051015728e5a8aaac42dad33170d04507a33a85521abdf1cba64ecfb850458dbef0a8aea71575d060c7db3970f85a6e1e4c7abf5ae8cdb0933d71e8c94e04a25619dcee3d2261ad2ee6bf12ffa06d98a0864d87602733ec86a64521f2b18177b200cbbe117577a615d6c770988c0bad946e208e24fa074e5ab3143db5bfce0fd108e4b82d120a92108011a723c12a787e6d788719a10bdba5b2699c327186af4e23c1a946834b6150bda2583e9ca2ad44ce8dbbbc2db04de8ef92e8efc141fbecaa6287c59474e6bc05d99b2964fa090c3a2233ba186515be7ed1f612970cee2d7afb81bdd762170481cd0069127d5b05aa993b4ea988d8fddc186ffb7dc90a6c08f4df435c93402849236c3fab4d27c7026c1d4dcb2602646dec9751e763dba37bdf8ff9406ad9e530ee5db382f413001aeb06a53ed9027d831179727b0865a8918da3edbebcf9b14ed44ce6cbaced4bb1bdb7f1447e6cc254b332051512bd7af426fb8f401378cd2bf5983ca01c64b92ecf032ea15d1721d03f482d7ce6e74fef6d55e702f46980c82b5a84031900b1c9e59e7c97fbec7e8f323a97a7e36cc88be0f1d45b7ff585ac54bd407b22b4154aacc8f6d7ebf48e1d814cc5ed20f8037e0a79715eef29be32806a1d58bb7c5da76f550aa3d8a1fbff0eb19ccb1a313d55cda56c9ec2ef29632387fe8d76e3c0468043e8f663f4860ee12bf2d5b0b7474d6e694f91e6dcc4024ffffffffffffffff"},"modp18":{"gen":"02","prime":"ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf0598da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb9ed529077096966d670c354e4abc9804f1746c08ca18217c32905e462e36ce3be39e772c180e86039b2783a2ec07a28fb5c55df06f4c52c9de2bcbf6955817183995497cea956ae515d2261898fa051015728e5a8aaac42dad33170d04507a33a85521abdf1cba64ecfb850458dbef0a8aea71575d060c7db3970f85a6e1e4c7abf5ae8cdb0933d71e8c94e04a25619dcee3d2261ad2ee6bf12ffa06d98a0864d87602733ec86a64521f2b18177b200cbbe117577a615d6c770988c0bad946e208e24fa074e5ab3143db5bfce0fd108e4b82d120a92108011a723c12a787e6d788719a10bdba5b2699c327186af4e23c1a946834b6150bda2583e9ca2ad44ce8dbbbc2db04de8ef92e8efc141fbecaa6287c59474e6bc05d99b2964fa090c3a2233ba186515be7ed1f612970cee2d7afb81bdd762170481cd0069127d5b05aa993b4ea988d8fddc186ffb7dc90a6c08f4df435c93402849236c3fab4d27c7026c1d4dcb2602646dec9751e763dba37bdf8ff9406ad9e530ee5db382f413001aeb06a53ed9027d831179727b0865a8918da3edbebcf9b14ed44ce6cbaced4bb1bdb7f1447e6cc254b332051512bd7af426fb8f401378cd2bf5983ca01c64b92ecf032ea15d1721d03f482d7ce6e74fef6d55e702f46980c82b5a84031900b1c9e59e7c97fbec7e8f323a97a7e36cc88be0f1d45b7ff585ac54bd407b22b4154aacc8f6d7ebf48e1d814cc5ed20f8037e0a79715eef29be32806a1d58bb7c5da76f550aa3d8a1fbff0eb19ccb1a313d55cda56c9ec2ef29632387fe8d76e3c0468043e8f663f4860ee12bf2d5b0b7474d6e694f91e6dbe115974a3926f12fee5e438777cb6a932df8cd8bec4d073b931ba3bc832b68d9dd300741fa7bf8afc47ed2576f6936ba424663aab639c5ae4f5683423b4742bf1c978238f16cbe39d652de3fdb8befc848ad922222e04a4037c0713eb57a81a23f0c73473fc646cea306b4bcbc8862f8385ddfa9d4b7fa2c087e879683303ed5bdd3a062b3cf5b3a278a66d2a13f83f44f82ddf310ee074ab6a364597e899a0255dc164f31cc50846851df9ab48195ded7ea1b1d510bd7ee74d73faf36bc31ecfa268359046f4eb879f924009438b481c6cd7889a002ed5ee382bc9190da6fc026e479558e4475677e9aa9e3050e2765694dfc81f56e880b96e7160c980dd98edd3dfffffffffffffffff"}}')},function(t,e,n){(function(e){var r=n(5),i=new(n(212)),o=new r(24),a=new r(11),s=new r(10),u=new r(3),c=new r(7),f=n(211),l=n(44);function h(t,n){return n=n||"utf8",e.isBuffer(t)||(t=new e(t,n)),this._pub=new r(t),this}function d(t,n){return n=n||"utf8",e.isBuffer(t)||(t=new e(t,n)),this._priv=new r(t),this}t.exports=g;var p={};function g(t,e,n){this.setGenerator(e),this.__prime=new r(t),this._prime=r.mont(this.__prime),this._primeLen=t.length,this._pub=void 0,this._priv=void 0,this._primeCode=void 0,n?(this.setPublicKey=h,this.setPrivateKey=d):this._primeCode=8}function y(t,n){var r=new e(t.toArray());return n?r.toString(n):r}Object.defineProperty(g.prototype,"verifyError",{enumerable:!0,get:function(){return"number"!=typeof this._primeCode&&(this._primeCode=function(t,e){var n=e.toString("hex"),r=[n,t.toString(16)].join("_");if(r in p)return p[r];var l,h=0;if(t.isEven()||!f.simpleSieve||!f.fermatTest(t)||!i.test(t))return h+=1,h+="02"===n||"05"===n?8:4,p[r]=h,h;switch(i.test(t.shrn(1))||(h+=2),n){case"02":t.mod(o).cmp(a)&&(h+=8);break;case"05":(l=t.mod(s)).cmp(u)&&l.cmp(c)&&(h+=8);break;default:h+=4}return p[r]=h,h}(this.__prime,this.__gen)),this._primeCode}}),g.prototype.generateKeys=function(){return this._priv||(this._priv=new r(l(this._primeLen))),this._pub=this._gen.toRed(this._prime).redPow(this._priv).fromRed(),this.getPublicKey()},g.prototype.computeSecret=function(t){var n=(t=(t=new r(t)).toRed(this._prime)).redPow(this._priv).fromRed(),i=new e(n.toArray()),o=this.getPrime();if(i.length0&&n.ishrn(r),n}function l(t,n,i){var o,a;do{for(o=new e(0);8*o.length","license":"MIT","bugs":{"url":"https://github.com/indutny/elliptic/issues"},"homepage":"https://github.com/indutny/elliptic","devDependencies":{"brfs":"^1.4.3","coveralls":"^3.0.4","grunt":"^1.0.4","grunt-browserify":"^5.0.0","grunt-cli":"^1.2.0","grunt-contrib-connect":"^1.0.0","grunt-contrib-copy":"^1.0.0","grunt-contrib-uglify":"^1.0.1","grunt-mocha-istanbul":"^3.0.1","grunt-saucelabs":"^9.0.1","istanbul":"^0.4.2","jscs":"^3.0.7","jshint":"^2.6.0","mocha":"^6.1.4"},"dependencies":{"bn.js":"^4.4.0","brorand":"^1.0.1","hash.js":"^1.0.0","hmac-drbg":"^1.0.0","inherits":"^2.0.1","minimalistic-assert":"^1.0.0","minimalistic-crypto-utils":"^1.0.0"}}')},function(t,e,n){"use strict";var r=n(16),i=n(5),o=n(2),a=n(81),s=r.assert;function u(t){a.call(this,"short",t),this.a=new i(t.a,16).toRed(this.red),this.b=new i(t.b,16).toRed(this.red),this.tinv=this.two.redInvm(),this.zeroA=0===this.a.fromRed().cmpn(0),this.threeA=0===this.a.fromRed().sub(this.p).cmpn(-3),this.endo=this._getEndomorphism(t),this._endoWnafT1=new Array(4),this._endoWnafT2=new Array(4)}function c(t,e,n,r){a.BasePoint.call(this,t,"affine"),null===e&&null===n?(this.x=null,this.y=null,this.inf=!0):(this.x=new i(e,16),this.y=new i(n,16),r&&(this.x.forceRed(this.curve.red),this.y.forceRed(this.curve.red)),this.x.red||(this.x=this.x.toRed(this.curve.red)),this.y.red||(this.y=this.y.toRed(this.curve.red)),this.inf=!1)}function f(t,e,n,r){a.BasePoint.call(this,t,"jacobian"),null===e&&null===n&&null===r?(this.x=this.curve.one,this.y=this.curve.one,this.z=new i(0)):(this.x=new i(e,16),this.y=new i(n,16),this.z=new i(r,16)),this.x.red||(this.x=this.x.toRed(this.curve.red)),this.y.red||(this.y=this.y.toRed(this.curve.red)),this.z.red||(this.z=this.z.toRed(this.curve.red)),this.zOne=this.z===this.curve.one}o(u,a),t.exports=u,u.prototype._getEndomorphism=function(t){if(this.zeroA&&this.g&&this.n&&1===this.p.modn(3)){var e,n;if(t.beta)e=new i(t.beta,16).toRed(this.red);else{var r=this._getEndoRoots(this.p);e=(e=r[0].cmp(r[1])<0?r[0]:r[1]).toRed(this.red)}if(t.lambda)n=new i(t.lambda,16);else{var o=this._getEndoRoots(this.n);0===this.g.mul(o[0]).x.cmp(this.g.x.redMul(e))?n=o[0]:(n=o[1],s(0===this.g.mul(n).x.cmp(this.g.x.redMul(e))))}return{beta:e,lambda:n,basis:t.basis?t.basis.map((function(t){return{a:new i(t.a,16),b:new i(t.b,16)}})):this._getEndoBasis(n)}}},u.prototype._getEndoRoots=function(t){var e=t===this.p?this.red:i.mont(t),n=new i(2).toRed(e).redInvm(),r=n.redNeg(),o=new i(3).toRed(e).redNeg().redSqrt().redMul(n);return[r.redAdd(o).fromRed(),r.redSub(o).fromRed()]},u.prototype._getEndoBasis=function(t){for(var e,n,r,o,a,s,u,c,f,l=this.n.ushrn(Math.floor(this.n.bitLength()/2)),h=t,d=this.n.clone(),p=new i(1),g=new i(0),y=new i(0),b=new i(1),m=0;0!==h.cmpn(0);){var v=d.div(h);c=d.sub(v.mul(h)),f=y.sub(v.mul(p));var _=b.sub(v.mul(g));if(!r&&c.cmp(l)<0)e=u.neg(),n=p,r=c.neg(),o=f;else if(r&&2==++m)break;u=c,d=h,h=c,y=p,p=f,b=g,g=_}a=c.neg(),s=f;var w=r.sqr().add(o.sqr());return a.sqr().add(s.sqr()).cmp(w)>=0&&(a=e,s=n),r.negative&&(r=r.neg(),o=o.neg()),a.negative&&(a=a.neg(),s=s.neg()),[{a:r,b:o},{a:a,b:s}]},u.prototype._endoSplit=function(t){var e=this.endo.basis,n=e[0],r=e[1],i=r.b.mul(t).divRound(this.n),o=n.b.neg().mul(t).divRound(this.n),a=i.mul(n.a),s=o.mul(r.a),u=i.mul(n.b),c=o.mul(r.b);return{k1:t.sub(a).sub(s),k2:u.add(c).neg()}},u.prototype.pointFromX=function(t,e){(t=new i(t,16)).red||(t=t.toRed(this.red));var n=t.redSqr().redMul(t).redIAdd(t.redMul(this.a)).redIAdd(this.b),r=n.redSqrt();if(0!==r.redSqr().redSub(n).cmp(this.zero))throw new Error("invalid point");var o=r.fromRed().isOdd();return(e&&!o||!e&&o)&&(r=r.redNeg()),this.point(t,r)},u.prototype.validate=function(t){if(t.inf)return!0;var e=t.x,n=t.y,r=this.a.redMul(e),i=e.redSqr().redMul(e).redIAdd(r).redIAdd(this.b);return 0===n.redSqr().redISub(i).cmpn(0)},u.prototype._endoWnafMulAdd=function(t,e,n){for(var r=this._endoWnafT1,i=this._endoWnafT2,o=0;o":""},c.prototype.isInfinity=function(){return this.inf},c.prototype.add=function(t){if(this.inf)return t;if(t.inf)return this;if(this.eq(t))return this.dbl();if(this.neg().eq(t))return this.curve.point(null,null);if(0===this.x.cmp(t.x))return this.curve.point(null,null);var e=this.y.redSub(t.y);0!==e.cmpn(0)&&(e=e.redMul(this.x.redSub(t.x).redInvm()));var n=e.redSqr().redISub(this.x).redISub(t.x),r=e.redMul(this.x.redSub(n)).redISub(this.y);return this.curve.point(n,r)},c.prototype.dbl=function(){if(this.inf)return this;var t=this.y.redAdd(this.y);if(0===t.cmpn(0))return this.curve.point(null,null);var e=this.curve.a,n=this.x.redSqr(),r=t.redInvm(),i=n.redAdd(n).redIAdd(n).redIAdd(e).redMul(r),o=i.redSqr().redISub(this.x.redAdd(this.x)),a=i.redMul(this.x.redSub(o)).redISub(this.y);return this.curve.point(o,a)},c.prototype.getX=function(){return this.x.fromRed()},c.prototype.getY=function(){return this.y.fromRed()},c.prototype.mul=function(t){return t=new i(t,16),this.isInfinity()?this:this._hasDoubles(t)?this.curve._fixedNafMul(this,t):this.curve.endo?this.curve._endoWnafMulAdd([this],[t]):this.curve._wnafMul(this,t)},c.prototype.mulAdd=function(t,e,n){var r=[this,e],i=[t,n];return this.curve.endo?this.curve._endoWnafMulAdd(r,i):this.curve._wnafMulAdd(1,r,i,2)},c.prototype.jmulAdd=function(t,e,n){var r=[this,e],i=[t,n];return this.curve.endo?this.curve._endoWnafMulAdd(r,i,!0):this.curve._wnafMulAdd(1,r,i,2,!0)},c.prototype.eq=function(t){return this===t||this.inf===t.inf&&(this.inf||0===this.x.cmp(t.x)&&0===this.y.cmp(t.y))},c.prototype.neg=function(t){if(this.inf)return this;var e=this.curve.point(this.x,this.y.redNeg());if(t&&this.precomputed){var n=this.precomputed,r=function(t){return t.neg()};e.precomputed={naf:n.naf&&{wnd:n.naf.wnd,points:n.naf.points.map(r)},doubles:n.doubles&&{step:n.doubles.step,points:n.doubles.points.map(r)}}}return e},c.prototype.toJ=function(){return this.inf?this.curve.jpoint(null,null,null):this.curve.jpoint(this.x,this.y,this.curve.one)},o(f,a.BasePoint),u.prototype.jpoint=function(t,e,n){return new f(this,t,e,n)},f.prototype.toP=function(){if(this.isInfinity())return this.curve.point(null,null);var t=this.z.redInvm(),e=t.redSqr(),n=this.x.redMul(e),r=this.y.redMul(e).redMul(t);return this.curve.point(n,r)},f.prototype.neg=function(){return this.curve.jpoint(this.x,this.y.redNeg(),this.z)},f.prototype.add=function(t){if(this.isInfinity())return t;if(t.isInfinity())return this;var e=t.z.redSqr(),n=this.z.redSqr(),r=this.x.redMul(e),i=t.x.redMul(n),o=this.y.redMul(e.redMul(t.z)),a=t.y.redMul(n.redMul(this.z)),s=r.redSub(i),u=o.redSub(a);if(0===s.cmpn(0))return 0!==u.cmpn(0)?this.curve.jpoint(null,null,null):this.dbl();var c=s.redSqr(),f=c.redMul(s),l=r.redMul(c),h=u.redSqr().redIAdd(f).redISub(l).redISub(l),d=u.redMul(l.redISub(h)).redISub(o.redMul(f)),p=this.z.redMul(t.z).redMul(s);return this.curve.jpoint(h,d,p)},f.prototype.mixedAdd=function(t){if(this.isInfinity())return t.toJ();if(t.isInfinity())return this;var e=this.z.redSqr(),n=this.x,r=t.x.redMul(e),i=this.y,o=t.y.redMul(e).redMul(this.z),a=n.redSub(r),s=i.redSub(o);if(0===a.cmpn(0))return 0!==s.cmpn(0)?this.curve.jpoint(null,null,null):this.dbl();var u=a.redSqr(),c=u.redMul(a),f=n.redMul(u),l=s.redSqr().redIAdd(c).redISub(f).redISub(f),h=s.redMul(f.redISub(l)).redISub(i.redMul(c)),d=this.z.redMul(a);return this.curve.jpoint(l,h,d)},f.prototype.dblp=function(t){if(0===t)return this;if(this.isInfinity())return this;if(!t)return this.dbl();if(this.curve.zeroA||this.curve.threeA){for(var e=this,n=0;n=0)return!1;if(n.redIAdd(i),0===this.x.cmp(n))return!0}},f.prototype.inspect=function(){return this.isInfinity()?"":""},f.prototype.isInfinity=function(){return 0===this.z.cmpn(0)}},function(t,e,n){"use strict";var r=n(5),i=n(2),o=n(81),a=n(16);function s(t){o.call(this,"mont",t),this.a=new r(t.a,16).toRed(this.red),this.b=new r(t.b,16).toRed(this.red),this.i4=new r(4).toRed(this.red).redInvm(),this.two=new r(2).toRed(this.red),this.a24=this.i4.redMul(this.a.redAdd(this.two))}function u(t,e,n){o.BasePoint.call(this,t,"projective"),null===e&&null===n?(this.x=this.curve.one,this.z=this.curve.zero):(this.x=new r(e,16),this.z=new r(n,16),this.x.red||(this.x=this.x.toRed(this.curve.red)),this.z.red||(this.z=this.z.toRed(this.curve.red)))}i(s,o),t.exports=s,s.prototype.validate=function(t){var e=t.normalize().x,n=e.redSqr(),r=n.redMul(e).redAdd(n.redMul(this.a)).redAdd(e);return 0===r.redSqrt().redSqr().cmp(r)},i(u,o.BasePoint),s.prototype.decodePoint=function(t,e){return this.point(a.toArray(t,e),1)},s.prototype.point=function(t,e){return new u(this,t,e)},s.prototype.pointFromJSON=function(t){return u.fromJSON(this,t)},u.prototype.precompute=function(){},u.prototype._encode=function(){return this.getX().toArray("be",this.curve.p.byteLength())},u.fromJSON=function(t,e){return new u(t,e[0],e[1]||t.one)},u.prototype.inspect=function(){return this.isInfinity()?"":""},u.prototype.isInfinity=function(){return 0===this.z.cmpn(0)},u.prototype.dbl=function(){var t=this.x.redAdd(this.z).redSqr(),e=this.x.redSub(this.z).redSqr(),n=t.redSub(e),r=t.redMul(e),i=n.redMul(e.redAdd(this.curve.a24.redMul(n)));return this.curve.point(r,i)},u.prototype.add=function(){throw new Error("Not supported on Montgomery curve")},u.prototype.diffAdd=function(t,e){var n=this.x.redAdd(this.z),r=this.x.redSub(this.z),i=t.x.redAdd(t.z),o=t.x.redSub(t.z).redMul(n),a=i.redMul(r),s=e.z.redMul(o.redAdd(a).redSqr()),u=e.x.redMul(o.redISub(a).redSqr());return this.curve.point(s,u)},u.prototype.mul=function(t){for(var e=t.clone(),n=this,r=this.curve.point(null,null),i=[];0!==e.cmpn(0);e.iushrn(1))i.push(e.andln(1));for(var o=i.length-1;o>=0;o--)0===i[o]?(n=n.diffAdd(r,this),r=r.dbl()):(r=n.diffAdd(r,this),n=n.dbl());return r},u.prototype.mulAdd=function(){throw new Error("Not supported on Montgomery curve")},u.prototype.jumlAdd=function(){throw new Error("Not supported on Montgomery curve")},u.prototype.eq=function(t){return 0===this.getX().cmp(t.getX())},u.prototype.normalize=function(){return this.x=this.x.redMul(this.z.redInvm()),this.z=this.curve.one,this},u.prototype.getX=function(){return this.normalize(),this.x.fromRed()}},function(t,e,n){"use strict";var r=n(16),i=n(5),o=n(2),a=n(81),s=r.assert;function u(t){this.twisted=1!=(0|t.a),this.mOneA=this.twisted&&-1==(0|t.a),this.extended=this.mOneA,a.call(this,"edwards",t),this.a=new i(t.a,16).umod(this.red.m),this.a=this.a.toRed(this.red),this.c=new i(t.c,16).toRed(this.red),this.c2=this.c.redSqr(),this.d=new i(t.d,16).toRed(this.red),this.dd=this.d.redAdd(this.d),s(!this.twisted||0===this.c.fromRed().cmpn(1)),this.oneC=1==(0|t.c)}function c(t,e,n,r,o){a.BasePoint.call(this,t,"projective"),null===e&&null===n&&null===r?(this.x=this.curve.zero,this.y=this.curve.one,this.z=this.curve.one,this.t=this.curve.zero,this.zOne=!0):(this.x=new i(e,16),this.y=new i(n,16),this.z=r?new i(r,16):this.curve.one,this.t=o&&new i(o,16),this.x.red||(this.x=this.x.toRed(this.curve.red)),this.y.red||(this.y=this.y.toRed(this.curve.red)),this.z.red||(this.z=this.z.toRed(this.curve.red)),this.t&&!this.t.red&&(this.t=this.t.toRed(this.curve.red)),this.zOne=this.z===this.curve.one,this.curve.extended&&!this.t&&(this.t=this.x.redMul(this.y),this.zOne||(this.t=this.t.redMul(this.z.redInvm()))))}o(u,a),t.exports=u,u.prototype._mulA=function(t){return this.mOneA?t.redNeg():this.a.redMul(t)},u.prototype._mulC=function(t){return this.oneC?t:this.c.redMul(t)},u.prototype.jpoint=function(t,e,n,r){return this.point(t,e,n,r)},u.prototype.pointFromX=function(t,e){(t=new i(t,16)).red||(t=t.toRed(this.red));var n=t.redSqr(),r=this.c2.redSub(this.a.redMul(n)),o=this.one.redSub(this.c2.redMul(this.d).redMul(n)),a=r.redMul(o.redInvm()),s=a.redSqrt();if(0!==s.redSqr().redSub(a).cmp(this.zero))throw new Error("invalid point");var u=s.fromRed().isOdd();return(e&&!u||!e&&u)&&(s=s.redNeg()),this.point(t,s)},u.prototype.pointFromY=function(t,e){(t=new i(t,16)).red||(t=t.toRed(this.red));var n=t.redSqr(),r=n.redSub(this.c2),o=n.redMul(this.d).redMul(this.c2).redSub(this.a),a=r.redMul(o.redInvm());if(0===a.cmp(this.zero)){if(e)throw new Error("invalid point");return this.point(this.zero,t)}var s=a.redSqrt();if(0!==s.redSqr().redSub(a).cmp(this.zero))throw new Error("invalid point");return s.fromRed().isOdd()!==e&&(s=s.redNeg()),this.point(s,t)},u.prototype.validate=function(t){if(t.isInfinity())return!0;t.normalize();var e=t.x.redSqr(),n=t.y.redSqr(),r=e.redMul(this.a).redAdd(n),i=this.c2.redMul(this.one.redAdd(this.d.redMul(e).redMul(n)));return 0===r.cmp(i)},o(c,a.BasePoint),u.prototype.pointFromJSON=function(t){return c.fromJSON(this,t)},u.prototype.point=function(t,e,n,r){return new c(this,t,e,n,r)},c.fromJSON=function(t,e){return new c(t,e[0],e[1],e[2])},c.prototype.inspect=function(){return this.isInfinity()?"":""},c.prototype.isInfinity=function(){return 0===this.x.cmpn(0)&&(0===this.y.cmp(this.z)||this.zOne&&0===this.y.cmp(this.curve.c))},c.prototype._extDbl=function(){var t=this.x.redSqr(),e=this.y.redSqr(),n=this.z.redSqr();n=n.redIAdd(n);var r=this.curve._mulA(t),i=this.x.redAdd(this.y).redSqr().redISub(t).redISub(e),o=r.redAdd(e),a=o.redSub(n),s=r.redSub(e),u=i.redMul(a),c=o.redMul(s),f=i.redMul(s),l=a.redMul(o);return this.curve.point(u,c,l,f)},c.prototype._projDbl=function(){var t,e,n,r=this.x.redAdd(this.y).redSqr(),i=this.x.redSqr(),o=this.y.redSqr();if(this.curve.twisted){var a=(c=this.curve._mulA(i)).redAdd(o);if(this.zOne)t=r.redSub(i).redSub(o).redMul(a.redSub(this.curve.two)),e=a.redMul(c.redSub(o)),n=a.redSqr().redSub(a).redSub(a);else{var s=this.z.redSqr(),u=a.redSub(s).redISub(s);t=r.redSub(i).redISub(o).redMul(u),e=a.redMul(c.redSub(o)),n=a.redMul(u)}}else{var c=i.redAdd(o);s=this.curve._mulC(this.z).redSqr(),u=c.redSub(s).redSub(s);t=this.curve._mulC(r.redISub(c)).redMul(u),e=this.curve._mulC(c).redMul(i.redISub(o)),n=c.redMul(u)}return this.curve.point(t,e,n)},c.prototype.dbl=function(){return this.isInfinity()?this:this.curve.extended?this._extDbl():this._projDbl()},c.prototype._extAdd=function(t){var e=this.y.redSub(this.x).redMul(t.y.redSub(t.x)),n=this.y.redAdd(this.x).redMul(t.y.redAdd(t.x)),r=this.t.redMul(this.curve.dd).redMul(t.t),i=this.z.redMul(t.z.redAdd(t.z)),o=n.redSub(e),a=i.redSub(r),s=i.redAdd(r),u=n.redAdd(e),c=o.redMul(a),f=s.redMul(u),l=o.redMul(u),h=a.redMul(s);return this.curve.point(c,f,h,l)},c.prototype._projAdd=function(t){var e,n,r=this.z.redMul(t.z),i=r.redSqr(),o=this.x.redMul(t.x),a=this.y.redMul(t.y),s=this.curve.d.redMul(o).redMul(a),u=i.redSub(s),c=i.redAdd(s),f=this.x.redAdd(this.y).redMul(t.x.redAdd(t.y)).redISub(o).redISub(a),l=r.redMul(u).redMul(f);return this.curve.twisted?(e=r.redMul(c).redMul(a.redSub(this.curve._mulA(o))),n=u.redMul(c)):(e=r.redMul(c).redMul(a.redSub(o)),n=this.curve._mulC(u).redMul(c)),this.curve.point(l,e,n)},c.prototype.add=function(t){return this.isInfinity()?t:t.isInfinity()?this:this.curve.extended?this._extAdd(t):this._projAdd(t)},c.prototype.mul=function(t){return this._hasDoubles(t)?this.curve._fixedNafMul(this,t):this.curve._wnafMul(this,t)},c.prototype.mulAdd=function(t,e,n){return this.curve._wnafMulAdd(1,[this,e],[t,n],2,!1)},c.prototype.jmulAdd=function(t,e,n){return this.curve._wnafMulAdd(1,[this,e],[t,n],2,!0)},c.prototype.normalize=function(){if(this.zOne)return this;var t=this.z.redInvm();return this.x=this.x.redMul(t),this.y=this.y.redMul(t),this.t&&(this.t=this.t.redMul(t)),this.z=this.curve.one,this.zOne=!0,this},c.prototype.neg=function(){return this.curve.point(this.x.redNeg(),this.y,this.z,this.t&&this.t.redNeg())},c.prototype.getX=function(){return this.normalize(),this.x.fromRed()},c.prototype.getY=function(){return this.normalize(),this.y.fromRed()},c.prototype.eq=function(t){return this===t||0===this.getX().cmp(t.getX())&&0===this.getY().cmp(t.getY())},c.prototype.eqXToP=function(t){var e=t.toRed(this.curve.red).redMul(this.z);if(0===this.x.cmp(e))return!0;for(var n=t.clone(),r=this.curve.redN.redMul(this.z);;){if(n.iadd(this.curve.n),n.cmp(this.curve.p)>=0)return!1;if(e.redIAdd(r),0===this.x.cmp(e))return!0}},c.prototype.toP=c.prototype.normalize,c.prototype.mixedAdd=c.prototype.add},function(t,e,n){"use strict";e.sha1=n(468),e.sha224=n(469),e.sha256=n(216),e.sha384=n(470),e.sha512=n(217)},function(t,e,n){"use strict";var r=n(21),i=n(56),o=n(215),a=r.rotl32,s=r.sum32,u=r.sum32_5,c=o.ft_1,f=i.BlockHash,l=[1518500249,1859775393,2400959708,3395469782];function h(){if(!(this instanceof h))return new h;f.call(this),this.h=[1732584193,4023233417,2562383102,271733878,3285377520],this.W=new Array(80)}r.inherits(h,f),t.exports=h,h.blockSize=512,h.outSize=160,h.hmacStrength=80,h.padLength=64,h.prototype._update=function(t,e){for(var n=this.W,r=0;r<16;r++)n[r]=t[e+r];for(;rthis.blockSize&&(t=(new this.Hash).update(t).digest()),i(t.length<=this.blockSize);for(var e=t.length;e0))return a.iaddn(1),this.keyFromPrivate(a)}},l.prototype._truncateToN=function(t,e){var n=8*t.byteLength()-this.n.bitLength();return n>0&&(t=t.ushrn(n)),!e&&t.cmp(this.n)>=0?t.sub(this.n):t},l.prototype.sign=function(t,e,n,o){"object"==typeof n&&(o=n,n=null),o||(o={}),e=this.keyFromPrivate(e,n),t=this._truncateToN(new r(t,16));for(var a=this.n.byteLength(),s=e.getPrivate().toArray("be",a),u=t.toArray("be",a),c=new i({hash:this.hash,entropy:s,nonce:u,pers:o.pers,persEnc:o.persEnc||"utf8"}),l=this.n.sub(new r(1)),h=0;;h++){var d=o.k?o.k(h):new r(c.generate(this.n.byteLength()));if(!((d=this._truncateToN(d,!0)).cmpn(1)<=0||d.cmp(l)>=0)){var p=this.g.mul(d);if(!p.isInfinity()){var g=p.getX(),y=g.umod(this.n);if(0!==y.cmpn(0)){var b=d.invm(this.n).mul(y.mul(e.getPrivate()).iadd(t));if(0!==(b=b.umod(this.n)).cmpn(0)){var m=(p.getY().isOdd()?1:0)|(0!==g.cmp(y)?2:0);return o.canonical&&b.cmp(this.nh)>0&&(b=this.n.sub(b),m^=1),new f({r:y,s:b,recoveryParam:m})}}}}}},l.prototype.verify=function(t,e,n,i){t=this._truncateToN(new r(t,16)),n=this.keyFromPublic(n,i);var o=(e=new f(e,"hex")).r,a=e.s;if(o.cmpn(1)<0||o.cmp(this.n)>=0)return!1;if(a.cmpn(1)<0||a.cmp(this.n)>=0)return!1;var s,u=a.invm(this.n),c=u.mul(t).umod(this.n),l=u.mul(o).umod(this.n);return this.curve._maxwellTrick?!(s=this.g.jmulAdd(c,n.getPublic(),l)).isInfinity()&&s.eqXToP(o):!(s=this.g.mulAdd(c,n.getPublic(),l)).isInfinity()&&0===s.getX().umod(this.n).cmp(o)},l.prototype.recoverPubKey=function(t,e,n,i){u((3&n)===n,"The recovery param is more than two bits"),e=new f(e,i);var o=this.n,a=new r(t),s=e.r,c=e.s,l=1&n,h=n>>1;if(s.cmp(this.curve.p.umod(this.curve.n))>=0&&h)throw new Error("Unable to find sencond key candinate");s=h?this.curve.pointFromX(s.add(this.curve.n),l):this.curve.pointFromX(s,l);var d=e.r.invm(o),p=o.sub(a).mul(d).umod(o),g=c.mul(d).umod(o);return this.g.mulAdd(p,s,g)},l.prototype.getKeyRecoveryParam=function(t,e,n,r){if(null!==(e=new f(e,r)).recoveryParam)return e.recoveryParam;for(var i=0;i<4;i++){var o;try{o=this.recoverPubKey(t,e,i)}catch(t){continue}if(o.eq(n))return i}throw new Error("Unable to find valid recovery factor")}},function(t,e,n){"use strict";var r=n(127),i=n(213),o=n(15);function a(t){if(!(this instanceof a))return new a(t);this.hash=t.hash,this.predResist=!!t.predResist,this.outLen=this.hash.outSize,this.minEntropy=t.minEntropy||this.hash.hmacStrength,this._reseed=null,this.reseedInterval=null,this.K=null,this.V=null;var e=i.toArray(t.entropy,t.entropyEnc||"hex"),n=i.toArray(t.nonce,t.nonceEnc||"hex"),r=i.toArray(t.pers,t.persEnc||"hex");o(e.length>=this.minEntropy/8,"Not enough entropy. Minimum is: "+this.minEntropy+" bits"),this._init(e,n,r)}t.exports=a,a.prototype._init=function(t,e,n){var r=t.concat(e).concat(n);this.K=new Array(this.outLen/8),this.V=new Array(this.outLen/8);for(var i=0;i=this.minEntropy/8,"Not enough entropy. Minimum is: "+this.minEntropy+" bits"),this._update(t.concat(n||[])),this._reseed=1},a.prototype.generate=function(t,e,n,r){if(this._reseed>this.reseedInterval)throw new Error("Reseed is required");"string"!=typeof e&&(r=n,n=e,e=null),n&&(n=i.toArray(n,r||"hex"),this._update(n));for(var o=[];o.length"}},function(t,e,n){"use strict";var r=n(5),i=n(16),o=i.assert;function a(t,e){if(t instanceof a)return t;this._importDER(t,e)||(o(t.r&&t.s,"Signature without r or s"),this.r=new r(t.r,16),this.s=new r(t.s,16),void 0===t.recoveryParam?this.recoveryParam=null:this.recoveryParam=t.recoveryParam)}function s(){this.place=0}function u(t,e){var n=t[e.place++];if(!(128&n))return n;for(var r=15&n,i=0,o=0,a=e.place;o>>3);for(t.push(128|n);--n;)t.push(e>>>(n<<3)&255);t.push(e)}}t.exports=a,a.prototype._importDER=function(t,e){t=i.toArray(t,e);var n=new s;if(48!==t[n.place++])return!1;if(u(t,n)+n.place!==t.length)return!1;if(2!==t[n.place++])return!1;var o=u(t,n),a=t.slice(n.place,o+n.place);if(n.place+=o,2!==t[n.place++])return!1;var c=u(t,n);if(t.length!==c+n.place)return!1;var f=t.slice(n.place,c+n.place);return 0===a[0]&&128&a[1]&&(a=a.slice(1)),0===f[0]&&128&f[1]&&(f=f.slice(1)),this.r=new r(a),this.s=new r(f),this.recoveryParam=null,!0},a.prototype.toDER=function(t){var e=this.r.toArray(),n=this.s.toArray();for(128&e[0]&&(e=[0].concat(e)),128&n[0]&&(n=[0].concat(n)),e=c(e),n=c(n);!(n[0]||128&n[1]);)n=n.slice(1);var r=[2];f(r,e.length),(r=r.concat(e)).push(2),f(r,n.length);var o=r.concat(n),a=[48];return f(a,o.length),a=a.concat(o),i.encode(a,t)}},function(t,e,n){"use strict";var r=n(127),i=n(126),o=n(16),a=o.assert,s=o.parseBytes,u=n(479),c=n(480);function f(t){if(a("ed25519"===t,"only tested with ed25519 so far"),!(this instanceof f))return new f(t);t=i[t].curve;this.curve=t,this.g=t.g,this.g.precompute(t.n.bitLength()+1),this.pointClass=t.point().constructor,this.encodingLength=Math.ceil(t.n.bitLength()/8),this.hash=r.sha512}t.exports=f,f.prototype.sign=function(t,e){t=s(t);var n=this.keyFromSecret(e),r=this.hashInt(n.messagePrefix(),t),i=this.g.mul(r),o=this.encodePoint(i),a=this.hashInt(o,n.pubBytes(),t).mul(n.priv()),u=r.add(a).umod(this.curve.n);return this.makeSignature({R:i,S:u,Rencoded:o})},f.prototype.verify=function(t,e,n){t=s(t),e=this.makeSignature(e);var r=this.keyFromPublic(n),i=this.hashInt(e.Rencoded(),r.pubBytes(),t),o=this.g.mul(e.S());return e.R().add(r.pub().mul(i)).eq(o)},f.prototype.hashInt=function(){for(var t=this.hash(),e=0;e=e)throw new Error("invalid sig")}t.exports=function(t,n,u,c,f){var l=o(u);if("ec"===l.type){if("ecdsa"!==c&&"ecdsa/rsa"!==c)throw new Error("wrong public key type");return function(t,e,n){var r=a[n.data.algorithm.curve.join(".")];if(!r)throw new Error("unknown curve "+n.data.algorithm.curve.join("."));var o=new i(r),s=n.data.subjectPrivateKey.data;return o.verify(e,t,s)}(t,n,l)}if("dsa"===l.type){if("dsa"!==c)throw new Error("wrong public key type");return function(t,e,n){var i=n.data.p,a=n.data.q,u=n.data.g,c=n.data.pub_key,f=o.signature.decode(t,"der"),l=f.s,h=f.r;s(l,a),s(h,a);var d=r.mont(i),p=l.invm(a);return 0===u.toRed(d).redPow(new r(e).mul(p).mod(a)).fromRed().mul(c.toRed(d).redPow(h.mul(p).mod(a)).fromRed()).mod(i).mod(a).cmp(h)}(t,n,l)}if("rsa"!==c&&"ecdsa/rsa"!==c)throw new Error("wrong public key type");n=e.concat([f,n]);for(var h=l.modulus.byteLength(),d=[1],p=0;n.length+d.length+2n-h-2)throw new Error("message too long");var d=l.alloc(n-r-h-2),p=n-f-1,g=i(f),y=s(l.concat([c,d,l.alloc(1,1),e],p),a(g,p)),b=s(g,a(y,f));return new u(l.concat([l.alloc(1),b,y],n))}(p,e);else if(1===h)d=function(t,e,n){var r,o=e.length,a=t.modulus.byteLength();if(o>a-11)throw new Error("message too long");r=n?l.alloc(a-o-3,255):function(t){var e,n=l.allocUnsafe(t),r=0,o=i(2*t),a=0;for(;r=0)throw new Error("data too long for modulus")}return n?f(d,p):c(d,p)}},function(t,e,n){var r=n(82),i=n(223),o=n(224),a=n(5),s=n(124),u=n(53),c=n(225),f=n(3).Buffer;t.exports=function(t,e,n){var l;l=t.padding?t.padding:n?1:4;var h,d=r(t),p=d.modulus.byteLength();if(e.length>p||new a(e).cmp(d.modulus)>=0)throw new Error("decryption error");h=n?c(new a(e),d):s(e,d);var g=f.alloc(p-h.length);if(h=f.concat([g,h],p),4===l)return function(t,e){var n=t.modulus.byteLength(),r=u("sha1").update(f.alloc(0)).digest(),a=r.length;if(0!==e[0])throw new Error("decryption error");var s=e.slice(1,a+1),c=e.slice(a+1),l=o(s,i(c,a)),h=o(c,i(l,n-a-1));if(function(t,e){t=f.from(t),e=f.from(e);var n=0,r=t.length;t.length!==e.length&&(n++,r=Math.min(t.length,e.length));var i=-1;for(;++i=e.length){o++;break}var a=e.slice(2,i-1);("0002"!==r.toString("hex")&&!n||"0001"!==r.toString("hex")&&n)&&o++;a.length<8&&o++;if(o)throw new Error("decryption error");return e.slice(i)}(0,h,n);if(3===l)return h;throw new Error("unknown padding")}},function(t,e,n){"use strict";(function(t,r){function i(){throw new Error("secure random number generation not supported by this browser\nuse chrome, FireFox or Internet Explorer 11")}var o=n(3),a=n(44),s=o.Buffer,u=o.kMaxLength,c=t.crypto||t.msCrypto,f=Math.pow(2,32)-1;function l(t,e){if("number"!=typeof t||t!=t)throw new TypeError("offset must be a number");if(t>f||t<0)throw new TypeError("offset must be a uint32");if(t>u||t>e)throw new RangeError("offset out of range")}function h(t,e,n){if("number"!=typeof t||t!=t)throw new TypeError("size must be a number");if(t>f||t<0)throw new TypeError("size must be a uint32");if(t+e>n||t>u)throw new RangeError("buffer too small")}function d(t,e,n,i){if(r.browser){var o=t.buffer,s=new Uint8Array(o,e,n);return c.getRandomValues(s),i?void r.nextTick((function(){i(null,t)})):t}if(!i)return a(n).copy(t,e),t;a(n,(function(n,r){if(n)return i(n);r.copy(t,e),i(null,t)}))}c&&c.getRandomValues||!r.browser?(e.randomFill=function(e,n,r,i){if(!(s.isBuffer(e)||e instanceof t.Uint8Array))throw new TypeError('"buf" argument must be a Buffer or Uint8Array');if("function"==typeof n)i=n,n=0,r=e.length;else if("function"==typeof r)i=r,r=e.length-n;else if("function"!=typeof i)throw new TypeError('"cb" argument must be a function');return l(n,e.length),h(r,n,e.length),d(e,n,r,i)},e.randomFillSync=function(e,n,r){void 0===n&&(n=0);if(!(s.isBuffer(e)||e instanceof t.Uint8Array))throw new TypeError('"buf" argument must be a Buffer or Uint8Array');l(n,e.length),void 0===r&&(r=e.length-n);return h(r,n,e.length),d(e,n,r)}):(e.randomFill=i,e.randomFillSync=i)}).call(this,n(11),n(7))},function(t,e,n){var r={"./dark/index.scss":501,"./default/index.scss":503,"./forest/index.scss":505,"./neutral/index.scss":507};function i(t){var e=o(t);return n(e)}function o(t){if(!n.o(r,t)){var e=new Error("Cannot find module '"+t+"'");throw e.code="MODULE_NOT_FOUND",e}return r[t]}i.keys=function(){return Object.keys(r)},i.resolve=o,t.exports=i,i.id=500},function(t,e,n){var r=n(502);t.exports="string"==typeof r?r:r.toString()},function(t,e,n){(t.exports=n(83)(!1)).push([t.i,".label{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);color:#333}.label text{fill:#333}.node rect,.node circle,.node ellipse,.node polygon{fill:#BDD5EA;stroke:purple;stroke-width:1px}.node .label{text-align:center}.node.clickable{cursor:pointer}.arrowheadPath{fill:#d3d3d3}.edgePath .path{stroke:#d3d3d3;stroke-width:1.5px}.edgeLabel{background-color:#e8e8e8;text-align:center}.cluster rect{fill:#6D6D65;stroke:rgba(255,255,255,0.25);stroke-width:1px}.cluster text{fill:#F9FFFE}div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);font-size:12px;background:#6D6D65;border:1px solid rgba(255,255,255,0.25);border-radius:2px;pointer-events:none;z-index:100}.actor{stroke:#81B1DB;fill:#BDD5EA}text.actor{fill:#000;stroke:none}.actor-line{stroke:#d3d3d3}.messageLine0{stroke-width:1.5;stroke-dasharray:'2 2';stroke:#d3d3d3}.messageLine1{stroke-width:1.5;stroke-dasharray:'2 2';stroke:#d3d3d3}#arrowhead{fill:#d3d3d3}.sequenceNumber{fill:#fff}#sequencenumber{fill:#d3d3d3}#crosshead path{fill:#d3d3d3 !important;stroke:#d3d3d3 !important}.messageText{fill:#d3d3d3;stroke:none}.labelBox{stroke:#81B1DB;fill:#BDD5EA}.labelText{fill:#323D47;stroke:none}.loopText{fill:#d3d3d3;stroke:none}.loopLine{stroke-width:2;stroke-dasharray:'2 2';stroke:#81B1DB}.note{stroke:rgba(255,255,255,0.25);fill:#fff5ad}.noteText{fill:black;stroke:none;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);font-size:14px}.activation0{fill:#f4f4f4;stroke:#666}.activation1{fill:#f4f4f4;stroke:#666}.activation2{fill:#f4f4f4;stroke:#666}.mermaid-main-font{font-family:\"trebuchet ms\", verdana, arial;font-family:var(--mermaid-font-family)}.section{stroke:none;opacity:0.2}.section0{fill:rgba(255,255,255,0.3)}.section2{fill:#EAE8B9}.section1,.section3{fill:#fff;opacity:0.2}.sectionTitle0{fill:#F9FFFE}.sectionTitle1{fill:#F9FFFE}.sectionTitle2{fill:#F9FFFE}.sectionTitle3{fill:#F9FFFE}.sectionTitle{text-anchor:start;font-size:11px;text-height:14px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}.grid .tick{stroke:#d3d3d3;opacity:0.3;shape-rendering:crispEdges}.grid .tick text{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}.grid path{stroke-width:0}.today{fill:none;stroke:#DB5757;stroke-width:2px}.task{stroke-width:2}.taskText{text-anchor:middle;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}.taskText:not([font-size]){font-size:11px}.taskTextOutsideRight{fill:#323D47;text-anchor:start;font-size:11px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}.taskTextOutsideLeft{fill:#323D47;text-anchor:end;font-size:11px}.task.clickable{cursor:pointer}.taskText.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}.taskTextOutsideLeft.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}.taskTextOutsideRight.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}.taskText0,.taskText1,.taskText2,.taskText3{fill:#323D47}.task0,.task1,.task2,.task3{fill:#BDD5EA;stroke:rgba(255,255,255,0.5)}.taskTextOutside0,.taskTextOutside2{fill:#d3d3d3}.taskTextOutside1,.taskTextOutside3{fill:#d3d3d3}.active0,.active1,.active2,.active3{fill:#81B1DB;stroke:rgba(255,255,255,0.5)}.activeText0,.activeText1,.activeText2,.activeText3{fill:#323D47 !important}.done0,.done1,.done2,.done3{stroke:grey;fill:#d3d3d3;stroke-width:2}.doneText0,.doneText1,.doneText2,.doneText3{fill:#323D47 !important}.crit0,.crit1,.crit2,.crit3{stroke:#E83737;fill:#E83737;stroke-width:2}.activeCrit0,.activeCrit1,.activeCrit2,.activeCrit3{stroke:#E83737;fill:#81B1DB;stroke-width:2}.doneCrit0,.doneCrit1,.doneCrit2,.doneCrit3{stroke:#E83737;fill:#d3d3d3;stroke-width:2;cursor:pointer;shape-rendering:crispEdges}.milestone{transform:rotate(45deg) scale(0.8, 0.8)}.milestoneText{font-style:italic}.doneCritText0,.doneCritText1,.doneCritText2,.doneCritText3{fill:#323D47 !important}.activeCritText0,.activeCritText1,.activeCritText2,.activeCritText3{fill:#323D47 !important}.titleText{text-anchor:middle;font-size:18px;fill:#323D47;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}g.classGroup text{fill:purple;stroke:none;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);font-size:10px}g.classGroup text .title{font-weight:bolder}g.classGroup rect{fill:#BDD5EA;stroke:purple}g.classGroup line{stroke:purple;stroke-width:1}.classLabel .box{stroke:none;stroke-width:0;fill:#BDD5EA;opacity:0.5}.classLabel .label{fill:purple;font-size:10px}.relation{stroke:purple;stroke-width:1;fill:none}#compositionStart{fill:purple;stroke:purple;stroke-width:1}#compositionEnd{fill:purple;stroke:purple;stroke-width:1}#aggregationStart{fill:#BDD5EA;stroke:purple;stroke-width:1}#aggregationEnd{fill:#BDD5EA;stroke:purple;stroke-width:1}#dependencyStart{fill:purple;stroke:purple;stroke-width:1}#dependencyEnd{fill:purple;stroke:purple;stroke-width:1}#extensionStart{fill:purple;stroke:purple;stroke-width:1}#extensionEnd{fill:purple;stroke:purple;stroke-width:1}.commit-id,.commit-msg,.branch-label{fill:lightgrey;color:lightgrey;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}.pieTitleText{text-anchor:middle;font-size:25px;fill:#323D47;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}.slice{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}g.stateGroup text{fill:purple;stroke:none;font-size:10px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}g.stateGroup text{fill:purple;stroke:none;font-size:10px}g.stateGroup .state-title{font-weight:bolder;fill:#000}g.stateGroup rect{fill:#BDD5EA;stroke:purple}g.stateGroup line{stroke:purple;stroke-width:1}.transition{stroke:purple;stroke-width:1;fill:none}.stateGroup .composit{fill:white;border-bottom:1px}.state-note{stroke:rgba(255,255,255,0.25);fill:#fff5ad}.state-note text{fill:black;stroke:none;font-size:10px}.stateLabel .box{stroke:none;stroke-width:0;fill:#BDD5EA;opacity:0.5}.stateLabel text{fill:#000;font-size:10px;font-weight:bold;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}:root{--mermaid-font-family: '\"trebuchet ms\", verdana, arial';--mermaid-font-family: \"Comic Sans MS\", \"Comic Sans\", cursive}\n",""])},function(t,e,n){var r=n(504);t.exports="string"==typeof r?r:r.toString()},function(t,e,n){(t.exports=n(83)(!1)).push([t.i,".label{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);color:#333}.label text{fill:#333}.node rect,.node circle,.node ellipse,.node polygon{fill:#ECECFF;stroke:#9370db;stroke-width:1px}.node .label{text-align:center}.node.clickable{cursor:pointer}.arrowheadPath{fill:#333}.edgePath .path{stroke:#333;stroke-width:1.5px}.edgeLabel{background-color:#e8e8e8;text-align:center}.cluster rect{fill:#ffffde;stroke:#aa3;stroke-width:1px}.cluster text{fill:#333}div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);font-size:12px;background:#ffffde;border:1px solid #aa3;border-radius:2px;pointer-events:none;z-index:100}.actor{stroke:#ccf;fill:#ECECFF}text.actor{fill:#000;stroke:none}.actor-line{stroke:grey}.messageLine0{stroke-width:1.5;stroke-dasharray:'2 2';stroke:#333}.messageLine1{stroke-width:1.5;stroke-dasharray:'2 2';stroke:#333}#arrowhead{fill:#333}.sequenceNumber{fill:#fff}#sequencenumber{fill:#333}#crosshead path{fill:#333 !important;stroke:#333 !important}.messageText{fill:#333;stroke:none}.labelBox{stroke:#ccf;fill:#ECECFF}.labelText{fill:#000;stroke:none}.loopText{fill:#000;stroke:none}.loopLine{stroke-width:2;stroke-dasharray:'2 2';stroke:#ccf}.note{stroke:#aa3;fill:#fff5ad}.noteText{fill:black;stroke:none;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);font-size:14px}.activation0{fill:#f4f4f4;stroke:#666}.activation1{fill:#f4f4f4;stroke:#666}.activation2{fill:#f4f4f4;stroke:#666}.mermaid-main-font{font-family:\"trebuchet ms\", verdana, arial;font-family:var(--mermaid-font-family)}.section{stroke:none;opacity:0.2}.section0{fill:rgba(102,102,255,0.49)}.section2{fill:#fff400}.section1,.section3{fill:#fff;opacity:0.2}.sectionTitle0{fill:#333}.sectionTitle1{fill:#333}.sectionTitle2{fill:#333}.sectionTitle3{fill:#333}.sectionTitle{text-anchor:start;font-size:11px;text-height:14px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}.grid .tick{stroke:#d3d3d3;opacity:0.3;shape-rendering:crispEdges}.grid .tick text{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}.grid path{stroke-width:0}.today{fill:none;stroke:red;stroke-width:2px}.task{stroke-width:2}.taskText{text-anchor:middle;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}.taskText:not([font-size]){font-size:11px}.taskTextOutsideRight{fill:#000;text-anchor:start;font-size:11px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}.taskTextOutsideLeft{fill:#000;text-anchor:end;font-size:11px}.task.clickable{cursor:pointer}.taskText.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}.taskTextOutsideLeft.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}.taskTextOutsideRight.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}.taskText0,.taskText1,.taskText2,.taskText3{fill:#fff}.task0,.task1,.task2,.task3{fill:#8a90dd;stroke:#534fbc}.taskTextOutside0,.taskTextOutside2{fill:#000}.taskTextOutside1,.taskTextOutside3{fill:#000}.active0,.active1,.active2,.active3{fill:#bfc7ff;stroke:#534fbc}.activeText0,.activeText1,.activeText2,.activeText3{fill:#000 !important}.done0,.done1,.done2,.done3{stroke:grey;fill:#d3d3d3;stroke-width:2}.doneText0,.doneText1,.doneText2,.doneText3{fill:#000 !important}.crit0,.crit1,.crit2,.crit3{stroke:#f88;fill:red;stroke-width:2}.activeCrit0,.activeCrit1,.activeCrit2,.activeCrit3{stroke:#f88;fill:#bfc7ff;stroke-width:2}.doneCrit0,.doneCrit1,.doneCrit2,.doneCrit3{stroke:#f88;fill:#d3d3d3;stroke-width:2;cursor:pointer;shape-rendering:crispEdges}.milestone{transform:rotate(45deg) scale(0.8, 0.8)}.milestoneText{font-style:italic}.doneCritText0,.doneCritText1,.doneCritText2,.doneCritText3{fill:#000 !important}.activeCritText0,.activeCritText1,.activeCritText2,.activeCritText3{fill:#000 !important}.titleText{text-anchor:middle;font-size:18px;fill:#000;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}g.classGroup text{fill:#9370db;stroke:none;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);font-size:10px}g.classGroup text .title{font-weight:bolder}g.classGroup rect{fill:#ECECFF;stroke:#9370db}g.classGroup line{stroke:#9370db;stroke-width:1}.classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5}.classLabel .label{fill:#9370db;font-size:10px}.relation{stroke:#9370db;stroke-width:1;fill:none}#compositionStart{fill:#9370db;stroke:#9370db;stroke-width:1}#compositionEnd{fill:#9370db;stroke:#9370db;stroke-width:1}#aggregationStart{fill:#ECECFF;stroke:#9370db;stroke-width:1}#aggregationEnd{fill:#ECECFF;stroke:#9370db;stroke-width:1}#dependencyStart{fill:#9370db;stroke:#9370db;stroke-width:1}#dependencyEnd{fill:#9370db;stroke:#9370db;stroke-width:1}#extensionStart{fill:#9370db;stroke:#9370db;stroke-width:1}#extensionEnd{fill:#9370db;stroke:#9370db;stroke-width:1}.commit-id,.commit-msg,.branch-label{fill:lightgrey;color:lightgrey;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}.pieTitleText{text-anchor:middle;font-size:25px;fill:#000;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}.slice{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}g.stateGroup text{fill:#9370db;stroke:none;font-size:10px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}g.stateGroup text{fill:#9370db;stroke:none;font-size:10px}g.stateGroup .state-title{font-weight:bolder;fill:#000}g.stateGroup rect{fill:#ECECFF;stroke:#9370db}g.stateGroup line{stroke:#9370db;stroke-width:1}.transition{stroke:#9370db;stroke-width:1;fill:none}.stateGroup .composit{fill:white;border-bottom:1px}.state-note{stroke:#aa3;fill:#fff5ad}.state-note text{fill:black;stroke:none;font-size:10px}.stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5}.stateLabel text{fill:#000;font-size:10px;font-weight:bold;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}:root{--mermaid-font-family: '\"trebuchet ms\", verdana, arial';--mermaid-font-family: \"Comic Sans MS\", \"Comic Sans\", cursive}\n",""])},function(t,e,n){var r=n(506);t.exports="string"==typeof r?r:r.toString()},function(t,e,n){(t.exports=n(83)(!1)).push([t.i,".label{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);color:#333}.label text{fill:#333}.node rect,.node circle,.node ellipse,.node polygon{fill:#cde498;stroke:#13540c;stroke-width:1px}.node .label{text-align:center}.node.clickable{cursor:pointer}.arrowheadPath{fill:green}.edgePath .path{stroke:green;stroke-width:1.5px}.edgeLabel{background-color:#e8e8e8;text-align:center}.cluster rect{fill:#cdffb2;stroke:#6eaa49;stroke-width:1px}.cluster text{fill:#333}div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);font-size:12px;background:#cdffb2;border:1px solid #6eaa49;border-radius:2px;pointer-events:none;z-index:100}.actor{stroke:#13540c;fill:#cde498}text.actor{fill:#000;stroke:none}.actor-line{stroke:grey}.messageLine0{stroke-width:1.5;stroke-dasharray:'2 2';stroke:#333}.messageLine1{stroke-width:1.5;stroke-dasharray:'2 2';stroke:#333}#arrowhead{fill:#333}.sequenceNumber{fill:#fff}#sequencenumber{fill:#333}#crosshead path{fill:#333 !important;stroke:#333 !important}.messageText{fill:#333;stroke:none}.labelBox{stroke:#326932;fill:#cde498}.labelText{fill:#000;stroke:none}.loopText{fill:#000;stroke:none}.loopLine{stroke-width:2;stroke-dasharray:'2 2';stroke:#326932}.note{stroke:#6eaa49;fill:#fff5ad}.noteText{fill:black;stroke:none;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);font-size:14px}.activation0{fill:#f4f4f4;stroke:#666}.activation1{fill:#f4f4f4;stroke:#666}.activation2{fill:#f4f4f4;stroke:#666}.mermaid-main-font{font-family:\"trebuchet ms\", verdana, arial;font-family:var(--mermaid-font-family)}.section{stroke:none;opacity:0.2}.section0{fill:#6eaa49}.section2{fill:#6eaa49}.section1,.section3{fill:#fff;opacity:0.2}.sectionTitle0{fill:#333}.sectionTitle1{fill:#333}.sectionTitle2{fill:#333}.sectionTitle3{fill:#333}.sectionTitle{text-anchor:start;font-size:11px;text-height:14px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}.grid .tick{stroke:#d3d3d3;opacity:0.3;shape-rendering:crispEdges}.grid .tick text{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}.grid path{stroke-width:0}.today{fill:none;stroke:red;stroke-width:2px}.task{stroke-width:2}.taskText{text-anchor:middle;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}.taskText:not([font-size]){font-size:11px}.taskTextOutsideRight{fill:#000;text-anchor:start;font-size:11px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}.taskTextOutsideLeft{fill:#000;text-anchor:end;font-size:11px}.task.clickable{cursor:pointer}.taskText.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}.taskTextOutsideLeft.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}.taskTextOutsideRight.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}.taskText0,.taskText1,.taskText2,.taskText3{fill:#fff}.task0,.task1,.task2,.task3{fill:#487e3a;stroke:#13540c}.taskTextOutside0,.taskTextOutside2{fill:#000}.taskTextOutside1,.taskTextOutside3{fill:#000}.active0,.active1,.active2,.active3{fill:#cde498;stroke:#13540c}.activeText0,.activeText1,.activeText2,.activeText3{fill:#000 !important}.done0,.done1,.done2,.done3{stroke:grey;fill:#d3d3d3;stroke-width:2}.doneText0,.doneText1,.doneText2,.doneText3{fill:#000 !important}.crit0,.crit1,.crit2,.crit3{stroke:#f88;fill:red;stroke-width:2}.activeCrit0,.activeCrit1,.activeCrit2,.activeCrit3{stroke:#f88;fill:#cde498;stroke-width:2}.doneCrit0,.doneCrit1,.doneCrit2,.doneCrit3{stroke:#f88;fill:#d3d3d3;stroke-width:2;cursor:pointer;shape-rendering:crispEdges}.milestone{transform:rotate(45deg) scale(0.8, 0.8)}.milestoneText{font-style:italic}.doneCritText0,.doneCritText1,.doneCritText2,.doneCritText3{fill:#000 !important}.activeCritText0,.activeCritText1,.activeCritText2,.activeCritText3{fill:#000 !important}.titleText{text-anchor:middle;font-size:18px;fill:#000;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}g.classGroup text{fill:#13540c;stroke:none;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);font-size:10px}g.classGroup text .title{font-weight:bolder}g.classGroup rect{fill:#cde498;stroke:#13540c}g.classGroup line{stroke:#13540c;stroke-width:1}.classLabel .box{stroke:none;stroke-width:0;fill:#cde498;opacity:0.5}.classLabel .label{fill:#13540c;font-size:10px}.relation{stroke:#13540c;stroke-width:1;fill:none}#compositionStart{fill:#13540c;stroke:#13540c;stroke-width:1}#compositionEnd{fill:#13540c;stroke:#13540c;stroke-width:1}#aggregationStart{fill:#cde498;stroke:#13540c;stroke-width:1}#aggregationEnd{fill:#cde498;stroke:#13540c;stroke-width:1}#dependencyStart{fill:#13540c;stroke:#13540c;stroke-width:1}#dependencyEnd{fill:#13540c;stroke:#13540c;stroke-width:1}#extensionStart{fill:#13540c;stroke:#13540c;stroke-width:1}#extensionEnd{fill:#13540c;stroke:#13540c;stroke-width:1}.commit-id,.commit-msg,.branch-label{fill:lightgrey;color:lightgrey;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}.pieTitleText{text-anchor:middle;font-size:25px;fill:#000;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}.slice{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}g.stateGroup text{fill:#13540c;stroke:none;font-size:10px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}g.stateGroup text{fill:#13540c;stroke:none;font-size:10px}g.stateGroup .state-title{font-weight:bolder;fill:#000}g.stateGroup rect{fill:#cde498;stroke:#13540c}g.stateGroup line{stroke:#13540c;stroke-width:1}.transition{stroke:#13540c;stroke-width:1;fill:none}.stateGroup .composit{fill:white;border-bottom:1px}.state-note{stroke:#6eaa49;fill:#fff5ad}.state-note text{fill:black;stroke:none;font-size:10px}.stateLabel .box{stroke:none;stroke-width:0;fill:#cde498;opacity:0.5}.stateLabel text{fill:#000;font-size:10px;font-weight:bold;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}:root{--mermaid-font-family: '\"trebuchet ms\", verdana, arial';--mermaid-font-family: \"Comic Sans MS\", \"Comic Sans\", cursive}\n",""])},function(t,e,n){var r=n(508);t.exports="string"==typeof r?r:r.toString()},function(t,e,n){(t.exports=n(83)(!1)).push([t.i,".label{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);color:#333}.label text{fill:#333}.node rect,.node circle,.node ellipse,.node polygon{fill:#eee;stroke:#999;stroke-width:1px}.node .label{text-align:center}.node.clickable{cursor:pointer}.arrowheadPath{fill:#333}.edgePath .path{stroke:#666;stroke-width:1.5px}.edgeLabel{background-color:#fff;text-align:center}.cluster rect{fill:#eaf2fb;stroke:#26a;stroke-width:1px}.cluster text{fill:#333}div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);font-size:12px;background:#eaf2fb;border:1px solid #26a;border-radius:2px;pointer-events:none;z-index:100}.actor{stroke:#999;fill:#eee}text.actor{fill:#333;stroke:none}.actor-line{stroke:#666}.messageLine0{stroke-width:1.5;stroke-dasharray:'2 2';stroke:#333}.messageLine1{stroke-width:1.5;stroke-dasharray:'2 2';stroke:#333}#arrowhead{fill:#333}.sequenceNumber{fill:#fff}#sequencenumber{fill:#333}#crosshead path{fill:#333 !important;stroke:#333 !important}.messageText{fill:#333;stroke:none}.labelBox{stroke:#999;fill:#eee}.labelText{fill:#333;stroke:none}.loopText{fill:#333;stroke:none}.loopLine{stroke-width:2;stroke-dasharray:'2 2';stroke:#999}.note{stroke:#770;fill:#ffa}.noteText{fill:black;stroke:none;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);font-size:14px}.activation0{fill:#f4f4f4;stroke:#666}.activation1{fill:#f4f4f4;stroke:#666}.activation2{fill:#f4f4f4;stroke:#666}.mermaid-main-font{font-family:\"trebuchet ms\", verdana, arial;font-family:var(--mermaid-font-family)}.section{stroke:none;opacity:0.2}.section0{fill:#80b3e6}.section2{fill:#80b3e6}.section1,.section3{fill:#fff;opacity:0.2}.sectionTitle0{fill:#333}.sectionTitle1{fill:#333}.sectionTitle2{fill:#333}.sectionTitle3{fill:#333}.sectionTitle{text-anchor:start;font-size:11px;text-height:14px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}.grid .tick{stroke:#e6e6e6;opacity:0.3;shape-rendering:crispEdges}.grid .tick text{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}.grid path{stroke-width:0}.today{fill:none;stroke:#d42;stroke-width:2px}.task{stroke-width:2}.taskText{text-anchor:middle;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}.taskText:not([font-size]){font-size:11px}.taskTextOutsideRight{fill:#333;text-anchor:start;font-size:11px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}.taskTextOutsideLeft{fill:#333;text-anchor:end;font-size:11px}.task.clickable{cursor:pointer}.taskText.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}.taskTextOutsideLeft.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}.taskTextOutsideRight.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}.taskText0,.taskText1,.taskText2,.taskText3{fill:#fff}.task0,.task1,.task2,.task3{fill:#26a;stroke:#1a4d80}.taskTextOutside0,.taskTextOutside2{fill:#333}.taskTextOutside1,.taskTextOutside3{fill:#333}.active0,.active1,.active2,.active3{fill:#eee;stroke:#1a4d80}.activeText0,.activeText1,.activeText2,.activeText3{fill:#333 !important}.done0,.done1,.done2,.done3{stroke:#666;fill:#bbb;stroke-width:2}.doneText0,.doneText1,.doneText2,.doneText3{fill:#333 !important}.crit0,.crit1,.crit2,.crit3{stroke:#b1361b;fill:#d42;stroke-width:2}.activeCrit0,.activeCrit1,.activeCrit2,.activeCrit3{stroke:#b1361b;fill:#eee;stroke-width:2}.doneCrit0,.doneCrit1,.doneCrit2,.doneCrit3{stroke:#b1361b;fill:#bbb;stroke-width:2;cursor:pointer;shape-rendering:crispEdges}.milestone{transform:rotate(45deg) scale(0.8, 0.8)}.milestoneText{font-style:italic}.doneCritText0,.doneCritText1,.doneCritText2,.doneCritText3{fill:#333 !important}.activeCritText0,.activeCritText1,.activeCritText2,.activeCritText3{fill:#333 !important}.titleText{text-anchor:middle;font-size:18px;fill:#333;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}g.classGroup text{fill:#999;stroke:none;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);font-size:10px}g.classGroup text .title{font-weight:bolder}g.classGroup rect{fill:#eee;stroke:#999}g.classGroup line{stroke:#999;stroke-width:1}.classLabel .box{stroke:none;stroke-width:0;fill:#eee;opacity:0.5}.classLabel .label{fill:#999;font-size:10px}.relation{stroke:#999;stroke-width:1;fill:none}#compositionStart{fill:#999;stroke:#999;stroke-width:1}#compositionEnd{fill:#999;stroke:#999;stroke-width:1}#aggregationStart{fill:#eee;stroke:#999;stroke-width:1}#aggregationEnd{fill:#eee;stroke:#999;stroke-width:1}#dependencyStart{fill:#999;stroke:#999;stroke-width:1}#dependencyEnd{fill:#999;stroke:#999;stroke-width:1}#extensionStart{fill:#999;stroke:#999;stroke-width:1}#extensionEnd{fill:#999;stroke:#999;stroke-width:1}.commit-id,.commit-msg,.branch-label{fill:lightgrey;color:lightgrey;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}.pieTitleText{text-anchor:middle;font-size:25px;fill:#333;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}.slice{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}g.stateGroup text{fill:#999;stroke:none;font-size:10px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}g.stateGroup text{fill:#999;stroke:none;font-size:10px}g.stateGroup .state-title{font-weight:bolder;fill:#000}g.stateGroup rect{fill:#eee;stroke:#999}g.stateGroup line{stroke:#999;stroke-width:1}.transition{stroke:#999;stroke-width:1;fill:none}.stateGroup .composit{fill:white;border-bottom:1px}.state-note{stroke:#770;fill:#ffa}.state-note text{fill:black;stroke:none;font-size:10px}.stateLabel .box{stroke:none;stroke-width:0;fill:#eee;opacity:0.5}.stateLabel text{fill:#000;font-size:10px;font-weight:bold;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}:root{--mermaid-font-family: '\"trebuchet ms\", verdana, arial';--mermaid-font-family: \"Comic Sans MS\", \"Comic Sans\", cursive}\n",""])},function(t,e,n){"use strict";n.r(e);var r=n(226),i=n.n(r),o=n(0),a=n(227),s=n.n(a),u=n(88);let c={};const f=t=>{!function(t){const e=Object.keys(t);for(let n=0;nc;var h=n(23),d=n.n(h);const p=1,g=2,y=3,b=4,m=5,v={debug:()=>{},info:()=>{},warn:()=>{},error:()=>{},fatal:()=>{}},_=function(t){v.debug=()=>{},v.info=()=>{},v.warn=()=>{},v.error=()=>{},v.fatal=()=>{},t<=m&&(v.fatal=console.log.bind(console,"",w("FATAL"))),t<=b&&(v.error=console.log.bind(console,"",w("ERROR"))),t<=y&&(v.warn=console.log.bind(console,"",w("WARN"))),t<=g&&(v.info=console.log.bind(console,"",w("INFO"))),t<=p&&(v.debug=console.log.bind(console,"",w("DEBUG")))},w=t=>{return`${d()().format("HH:mm:ss.SSS")} : ${t} : `},x=(t,e)=>{if(!t)return e;const n=`curve${t.charAt(0).toUpperCase()+t.slice(1)}`;return o[n]||e},k=(t,e)=>t&&e?Math.sqrt(Math.pow(e.x-t.x,2)+Math.pow(e.y-t.y,2)):0;var E={detectType:function(t){return t=t.replace(/^\s*%%.*\n/g,"\n"),v.debug("Detecting diagram type based on the text "+t),t.match(/^\s*sequenceDiagram/)?"sequence":t.match(/^\s*gantt/)?"gantt":t.match(/^\s*classDiagram/)?"class":t.match(/^\s*stateDiagram/)?"state":t.match(/^\s*gitGraph/)?"git":t.match(/^\s*info/)?"info":t.match(/^\s*pie/)?"pie":"flowchart"},isSubstringInArray:function(t,e){for(let n=0;n{return(t=>{let e,n=0;t.forEach(t=>{n+=k(t,e),e=t});let r,i=n/2;return e=void 0,t.forEach(t=>{if(e&&!r){const n=k(t,e);if(n=1&&(r={x:t.x,y:t.y}),o>0&&o<1&&(r={x:(1-o)*e.x+o*t.x,y:(1-o)*e.y+o*t.y})}}e=t}),r})(t)},calcCardinalityPosition:(t,e,n)=>{let r,i=0;e[0]!==n&&(e=e.reverse()),e.forEach(t=>{i+=k(t,r),r=t});let o,a=25;r=void 0,e.forEach(t=>{if(r&&!o){const e=k(t,r);if(e=1&&(o={x:t.x,y:t.y}),n>0&&n<1&&(o={x:(1-n)*r.x+n*t.x,y:(1-n)*r.y+n*t.y})}}r=t});let s=t?10:5,u=Math.atan2(e[0].y-o.y,e[0].x-o.x),c={x:0,y:0};return c.x=Math.sin(u)*s+(e[0].x+o.x)/2,c.y=-Math.cos(u)*s+(e[0].y+o.y)/2,c}},A=n(22),S=n.n(A),T=n(84);const M=l();let D,C={},O=[],R=[],I=[],N={},B={},L=0,P=!0,F=[];const q=t=>{let e=t;return"loose"!==M.securityLevel&&(e=(e=(e=(e=(e=e.replace(/
/g,"#br#")).replace(//g,"#br#")).replace(//g,">")).replace(/=/g,"=")).replace(/#br#/g,"
")),e},j=function(t,e){t.split(",").forEach((function(t){let n=t;t[0].match(/\d/)&&(n=""+n),void 0!==C[n]&&C[n].classes.push(e),void 0!==N[n]&&N[n].classes.push(e)}))},U=function(t,e){t.split(",").forEach((function(t){void 0!==e&&(B[t]=q(e))}))},z=function(t){let e=o.select(".mermaidTooltip");null===(e._groups||e)[0][0]&&(e=o.select("body").append("div").attr("class","mermaidTooltip").style("opacity",0)),o.select(t).select("svg").selectAll("g.node").on("mouseover",(function(){const t=o.select(this);if(null===t.attr("title"))return;const n=this.getBoundingClientRect();e.transition().duration(200).style("opacity",".9"),e.html(t.attr("title")).style("left",n.left+(n.right-n.left)/2+"px").style("top",n.top-14+document.body.scrollTop+"px"),t.classed("hover",!0)})).on("mouseout",(function(){e.transition().duration(500).style("opacity",0),o.select(this).classed("hover",!1)}))};F.push(z);const Y=function(t){for(let e=0;e2e3)return;if(H[V]=e,I[e].id===t)return{result:!0,count:0};let r=0,i=1;for(;r=0){const n=$(t,e);if(n.result)return{result:!0,count:i+n.count};i+=n.count}r+=1}return{result:!1,count:i}};var G={addVertex:function(t,e,n,r,i){let o,a=t;void 0!==a&&0!==a.trim().length&&(a[0].match(/\d/)&&(a=""+a),void 0===C[a]&&(C[a]={id:a,styles:[],classes:[]}),void 0!==e?('"'===(o=q(e.trim()))[0]&&'"'===o[o.length-1]&&(o=o.substring(1,o.length-1)),C[a].text=o):C[a].text||(C[a].text=t),void 0!==n&&(C[a].type=n),null!=r&&r.forEach((function(t){C[a].styles.push(t)})),null!=i&&i.forEach((function(t){C[a].classes.push(t)})))},addLink:function(t,e,n,r){let i=t,o=e;i[0].match(/\d/)&&(i=""+i),o[0].match(/\d/)&&(o=""+o),v.info("Got edge...",i,o);const a={start:i,end:o,type:void 0,text:""};void 0!==(r=n.text)&&(a.text=q(r.trim()),'"'===a.text[0]&&'"'===a.text[a.text.length-1]&&(a.text=a.text.substring(1,a.text.length-1))),void 0!==n&&(a.type=n.type,a.stroke=n.stroke),O.push(a)},updateLinkInterpolate:function(t,e){t.forEach((function(t){"default"===t?O.defaultInterpolate=e:O[t].interpolate=e}))},updateLink:function(t,e){t.forEach((function(t){"default"===t?O.defaultStyle=e:(-1===E.isSubstringInArray("fill",e)&&e.push("fill:none"),O[t].style=e)}))},addClass:function(t,e){void 0===R[t]&&(R[t]={id:t,styles:[]}),null!=e&&e.forEach((function(e){R[t].styles.push(e)}))},setDirection:function(t){(D=t).match(/.*/)&&(D="LR"),D.match(/.*v/)&&(D="TB")},setClass:j,getTooltip:function(t){return B[t]},setClickEvent:function(t,e,n){t.split(",").forEach((function(t){!function(t,e){let n=t;t[0].match(/\d/)&&(n=""+n),"loose"===M.securityLevel&&void 0!==e&&void 0!==C[n]&&F.push((function(){const t=document.querySelector(`[id="${n}"]`);null!==t&&t.addEventListener("click",(function(){window[e](n)}),!1)}))}(t,e)})),U(t,n),j(t,"clickable")},setLink:function(t,e,n){t.split(",").forEach((function(t){let n=t;t[0].match(/\d/)&&(n=""+n),void 0!==C[n]&&("loose"!==M.securityLevel?C[n].link=Object(T.sanitizeUrl)(e):C[n].link=e)})),U(t,n),j(t,"clickable")},bindFunctions:function(t){F.forEach((function(e){e(t)}))},getDirection:function(){return D.trim()},getVertices:function(){return C},getEdges:function(){return O},getClasses:function(){return R},clear:function(){C={},R={},O=[],(F=[]).push(z),I=[],N={},L=0,B=[],P=!0},defaultStyle:function(){return"fill:#ffa;stroke: #f66; stroke-width: 3px; stroke-dasharray: 5, 5;fill:#ffa;stroke: #666;"},addSubGraph:function(t,e,n){let r=t,i=n;t===n&&n.match(/\s/)&&(r=void 0);let o=[];o=function(t){const e={boolean:{},number:{},string:{}},n=[];return t.filter((function(t){const r=typeof t;return""!==t.trim()&&(r in e?!e[r].hasOwnProperty(t)&&(e[r][t]=!0):!(n.indexOf(t)>=0)&&n.push(t))}))}(o.concat.apply(o,e));for(let t=0;t0&&$("none",I.length-1,0)},getSubGraphs:function(){return I},lex:{firstGraph:()=>!!P&&(P=!1,!0)}},W=n(60),K=n.n(W),X=n(17),Z=n.n(X),J=n(128),Q=n.n(J);function tt(t,e,n){const r=.9*(e.width+e.height),i=[{x:r/2,y:0},{x:r,y:-r/2},{x:r/2,y:-r},{x:0,y:-r/2}],o=ut(t,r,r,i);return n.intersect=function(t){return Z.a.intersect.polygon(n,i,t)},o}function et(t,e,n){const r=e.height,i=r/4,o=e.width+2*i,a=[{x:i,y:0},{x:o-i,y:0},{x:o,y:-r/2},{x:o-i,y:-r},{x:i,y:-r},{x:0,y:-r/2}],s=ut(t,o,r,a);return n.intersect=function(t){return Z.a.intersect.polygon(n,a,t)},s}function nt(t,e,n){const r=e.width,i=e.height,o=[{x:-i/2,y:0},{x:r,y:0},{x:r,y:-i},{x:-i/2,y:-i},{x:0,y:-i/2}],a=ut(t,r,i,o);return n.intersect=function(t){return Z.a.intersect.polygon(n,o,t)},a}function rt(t,e,n){const r=e.width,i=e.height,o=[{x:-2*i/6,y:0},{x:r-i/6,y:0},{x:r+2*i/6,y:-i},{x:i/6,y:-i}],a=ut(t,r,i,o);return n.intersect=function(t){return Z.a.intersect.polygon(n,o,t)},a}function it(t,e,n){const r=e.width,i=e.height,o=[{x:2*i/6,y:0},{x:r+i/6,y:0},{x:r-2*i/6,y:-i},{x:-i/6,y:-i}],a=ut(t,r,i,o);return n.intersect=function(t){return Z.a.intersect.polygon(n,o,t)},a}function ot(t,e,n){const r=e.width,i=e.height,o=[{x:-2*i/6,y:0},{x:r+2*i/6,y:0},{x:r-i/6,y:-i},{x:i/6,y:-i}],a=ut(t,r,i,o);return n.intersect=function(t){return Z.a.intersect.polygon(n,o,t)},a}function at(t,e,n){const r=e.width,i=e.height,o=[{x:i/6,y:0},{x:r-i/6,y:0},{x:r+2*i/6,y:-i},{x:-2*i/6,y:-i}],a=ut(t,r,i,o);return n.intersect=function(t){return Z.a.intersect.polygon(n,o,t)},a}function st(t,e,n){const r=e.width,i=e.height,o=[{x:0,y:0},{x:r+i/2,y:0},{x:r,y:-i/2},{x:r+i/2,y:-i},{x:0,y:-i}],a=ut(t,r,i,o);return n.intersect=function(t){return Z.a.intersect.polygon(n,o,t)},a}function ut(t,e,n,r){return t.insert("polygon",":first-child").attr("points",r.map((function(t){return t.x+","+t.y})).join(" ")).attr("transform","translate("+-e/2+","+n/2+")")}var ct={addToRender:function(t){t.shapes().question=tt,t.shapes().hexagon=et,t.shapes().rect_left_inv_arrow=nt,t.shapes().lean_right=rt,t.shapes().lean_left=it,t.shapes().trapezoid=ot,t.shapes().inv_trapezoid=at,t.shapes().rect_right_inv_arrow=st}};const ft={},lt=function(t,e,n){const r=o.select(`[id="${n}"]`),i=Object.keys(t),a=function(t,e,{label:n}){if(n)for(let n=0;n0&&(o=i.classes.join(" "));let s="";s=a(s,i.styles,{label:!1});let u="";u=a(u,i.styles,{label:!0});let c,f=void 0!==i.text?i.text:i.id;if(l().flowchart.htmlLabels){const t={label:f.replace(/fa[lrsb]?:fa-[\w-]+/g,t=>``)};(c=Q()(r,t).node()).parentNode.removeChild(c)}else{const t=document.createElementNS("http://www.w3.org/2000/svg","text"),e=f.split(//);for(let n=0;n'+i.text+"
"):(a.labelType="text",a.style=a.style||"stroke: #333; stroke-width: 1.5px;fill:none",a.label=i.text.replace(/
/g,"\n"))):a.label=i.text.replace(/
/g,"\n")),e.setEdge(i.start,i.end,a,r)}))};var dt=function(t){const e=Object.keys(t);for(let n=0;n=0;t--)i=s[t],G.addVertex(i.id,i.title,"group",void 0,i.classes);const u=G.getVertices(),c=G.getEdges();let f=0;for(f=s.length-1;f>=0;f--){i=s[f],o.selectAll("cluster").append("text");for(let t=0;t/gi," "),r=t.append("text");r.attr("x",e.x),r.attr("y",e.y),r.style("text-anchor",e.anchor),r.attr("fill",e.fill),void 0!==e.class&&r.attr("class",e.class);const i=r.append("tspan");return i.attr("x",e.x+2*e.textMargin),i.attr("fill",e.fill),i.text(n),r},mt=function(t,e){const n=t.append("polygon");var r,i,o,a,s;n.attr("points",(r=e.x,i=e.y,r+","+i+" "+(r+(o=50))+","+i+" "+(r+o)+","+(i+(a=20)-(s=7))+" "+(r+o-1.2*s)+","+(i+a)+" "+r+","+(i+a))),n.attr("class","labelBox"),e.y=e.y+e.labelMargin,e.x=e.x+.5*e.labelMargin,bt(t,e)};let vt=-1;const _t=function(){return{x:0,y:0,fill:void 0,"text-anchor":"start",style:"#666",width:100,height:100,textMargin:0,rx:0,ry:0}},wt=function(){return{x:0,y:0,fill:"#EDF2AE",stroke:"#666",width:100,anchor:"start",height:100,rx:0,ry:0}},xt=function(){function t(t,e,n,i,o,a,s){r(e.append("text").attr("x",n+o/2).attr("y",i+a/2+5).style("text-anchor","middle").text(t),s)}function e(t,e,n,i,o,a,s,u){const{actorFontSize:c,actorFontFamily:f}=u,l=t.split(//gi);for(let t=0;t{let o=0;const a=t.split(//gi);for(const t of a){const a=kt.getTextObj();a.x=e,a.y=n+o,a.textMargin=Pt.noteMargin,a.dy="1em",a.text=t,a.class="noteText";const s=kt.drawText(r,a,i);o+=(s._groups||s)[0][0].getBBox().height}return o})(r.message,e-4,n+24,a,o.width-Pt.noteMargin);Ft.insert(e,n,e+o.width,n+2*Pt.noteMargin+u),s.attr("height",u+2*Pt.noteMargin),Ft.bumpVerticalPos(u+2*Pt.noteMargin)},jt=function(t,e,n,r){for(let i=0;ie&&(r.starty=e-6,e+=12),kt.drawActivation(n,r,e,Pt,Ut(t.from.actor).length),Ft.insert(r.startx,e-10,r.stopx,e)}(t,Ft.getVerticalPos());break;case Et.parser.yy.LINETYPE.LOOP_START:Ft.bumpVerticalPos(Pt.boxMargin),Ft.newLoop(t.message),Ft.bumpVerticalPos(Pt.boxMargin+Pt.boxTextMargin);break;case Et.parser.yy.LINETYPE.LOOP_END:e=Ft.endLoop(),kt.drawLoop(n,e,"loop",Pt),Ft.bumpVerticalPos(Pt.boxMargin);break;case Et.parser.yy.LINETYPE.RECT_START:Ft.bumpVerticalPos(Pt.boxMargin),Ft.newLoop(void 0,t.message),Ft.bumpVerticalPos(Pt.boxMargin);break;case Et.parser.yy.LINETYPE.RECT_END:{const t=Ft.endLoop();kt.drawBackgroundRect(n,t),Ft.bumpVerticalPos(Pt.boxMargin);break}case Et.parser.yy.LINETYPE.OPT_START:Ft.bumpVerticalPos(Pt.boxMargin),Ft.newLoop(t.message),Ft.bumpVerticalPos(Pt.boxMargin+Pt.boxTextMargin);break;case Et.parser.yy.LINETYPE.OPT_END:e=Ft.endLoop(),kt.drawLoop(n,e,"opt",Pt),Ft.bumpVerticalPos(Pt.boxMargin);break;case Et.parser.yy.LINETYPE.ALT_START:Ft.bumpVerticalPos(Pt.boxMargin),Ft.newLoop(t.message),Ft.bumpVerticalPos(Pt.boxMargin+Pt.boxTextMargin);break;case Et.parser.yy.LINETYPE.ALT_ELSE:Ft.bumpVerticalPos(Pt.boxMargin),e=Ft.addSectionToLoop(t.message),Ft.bumpVerticalPos(Pt.boxMargin);break;case Et.parser.yy.LINETYPE.ALT_END:e=Ft.endLoop(),kt.drawLoop(n,e,"alt",Pt),Ft.bumpVerticalPos(Pt.boxMargin);break;case Et.parser.yy.LINETYPE.PAR_START:Ft.bumpVerticalPos(Pt.boxMargin),Ft.newLoop(t.message),Ft.bumpVerticalPos(Pt.boxMargin+Pt.boxTextMargin);break;case Et.parser.yy.LINETYPE.PAR_AND:Ft.bumpVerticalPos(Pt.boxMargin),e=Ft.addSectionToLoop(t.message),Ft.bumpVerticalPos(Pt.boxMargin);break;case Et.parser.yy.LINETYPE.PAR_END:e=Ft.endLoop(),kt.drawLoop(n,e,"par",Pt),Ft.bumpVerticalPos(Pt.boxMargin);break;default:try{Ft.bumpVerticalPos(Pt.messageMargin);const e=zt(t.from),o=zt(t.to),a=e[0]<=o[0]?1:0,s=e[0]/gi);for(const t of f)u=a.append("text").attr("x",s).attr("y",r-7+17*c).style("text-anchor","middle").attr("class","messageText").text(t.trim()),c++;const l=17*(c-1);let h,d=(u._groups||u)[0][0].getBBox().width;if(e===n){h=Pt.rightAngles?a.append("path").attr("d",`M ${e},${r+l} H ${e+Pt.width/2} V ${r+25+l} H ${e}`):a.append("path").attr("d","M "+e+","+(r+l)+" C "+(e+60)+","+(r-10+l)+" "+(e+60)+","+(r+30+l)+" "+e+","+(r+20+l)),Ft.bumpVerticalPos(30+l);const t=Math.max(d/2,100);Ft.insert(e-t,Ft.getVerticalPos()-10+l,n+t,Ft.getVerticalPos()+l)}else(h=a.append("line")).attr("x1",e),h.attr("y1",r),h.attr("x2",n),h.attr("y2",r),Ft.insert(e,Ft.getVerticalPos()-10+l,n,Ft.getVerticalPos()+l);i.type===Et.parser.yy.LINETYPE.DOTTED||i.type===Et.parser.yy.LINETYPE.DOTTED_CROSS||i.type===Et.parser.yy.LINETYPE.DOTTED_OPEN?(h.style("stroke-dasharray","3, 3"),h.attr("class","messageLine1")):h.attr("class","messageLine0");let p="";Pt.arrowMarkerAbsolute&&(p=(p=(p=window.location.protocol+"//"+window.location.host+window.location.pathname+window.location.search).replace(/\(/g,"\\(")).replace(/\)/g,"\\)")),h.attr("stroke-width",2),h.attr("stroke","black"),h.style("fill","none"),i.type!==Et.parser.yy.LINETYPE.SOLID&&i.type!==Et.parser.yy.LINETYPE.DOTTED||h.attr("marker-end","url("+p+"#arrowhead)"),i.type!==Et.parser.yy.LINETYPE.SOLID_CROSS&&i.type!==Et.parser.yy.LINETYPE.DOTTED_CROSS||h.attr("marker-end","url("+p+"#crosshead)"),Pt.showSequenceNumbers&&(h.attr("marker-start","url("+p+"#sequencenumber)"),a.append("text").attr("x",e).attr("y",r+4).attr("font-family","sans-serif").attr("font-size","12px").attr("text-anchor","middle").attr("textLength","16px").attr("class","sequenceNumber").text(o))}(n,r,i,u,t,l);const c=e.concat(o);Ft.insert(Math.min.apply(null,c),u,Math.max.apply(null,c),u)}catch(t){v.error("error while drawing message",t)}}[Et.parser.yy.LINETYPE.SOLID_OPEN,Et.parser.yy.LINETYPE.DOTTED_OPEN,Et.parser.yy.LINETYPE.SOLID,Et.parser.yy.LINETYPE.DOTTED,Et.parser.yy.LINETYPE.SOLID_CROSS,Et.parser.yy.LINETYPE.DOTTED_CROSS].includes(t.type)&&l++})),Pt.mirrorActors&&(Ft.bumpVerticalPos(2*Pt.boxMargin),jt(n,s,u,Ft.getVerticalPos()));const h=Ft.getBounds();v.debug("For line height fix Querying: #"+e+" .actor-line"),o.selectAll("#"+e+" .actor-line").attr("y2",h.stopy);let d=h.stopy-h.starty+2*Pt.diagramMarginY;Pt.mirrorActors&&(d=d-Pt.boxMargin+Pt.bottomMarginAdj);const p=h.stopx-h.startx+2*Pt.diagramMarginX;f&&n.append("text").text(f).attr("x",(h.stopx-h.startx)/2-2*Pt.diagramMarginX).attr("y",-25),Pt.useMaxWidth?(n.attr("height","100%"),n.attr("width","100%"),n.attr("style","max-width:"+p+"px;")):(n.attr("height",d),n.attr("width",p));const g=f?40:0;n.attr("viewBox",h.startx-Pt.diagramMarginX+" -"+(Pt.diagramMarginY+g)+" "+p+" "+(d+g))},Ht=n(26),$t=n.n(Ht);const Gt=l();let Wt="",Kt="",Xt=[],Zt="",Jt=[],Qt=[],te="";const ee=["active","done","crit","milestone"];let ne=[],re=!1;const ie=function(t,e,n){return t.isoWeekday()>=6&&n.indexOf("weekends")>=0||(n.indexOf(t.format("dddd").toLowerCase())>=0||n.indexOf(t.format(e.trim()))>=0)},oe=function(t,e,n){if(!n.length||t.manualEndTime)return;let r=d()(t.startTime,e,!0);r.add(1,"d");let i=d()(t.endTime,e,!0),o=ae(r,i,e,n);t.endTime=i.toDate(),t.renderEndTime=o},ae=function(t,e,n,r){let i=!1,o=null;for(;t.date()<=e.date();)i||(o=e.toDate()),(i=ie(t,n,r))&&e.add(1,"d"),t.add(1,"d");return o},se=function(t,e,n){n=n.trim();const r=/^after\s+([\d\w-]+)/.exec(n.trim());if(null!==r){const t=ye(r[1]);if(void 0===t){const t=new Date;return t.setHours(0,0,0,0),t}return t.endTime}let i=d()(n,e.trim(),!0);return i.isValid()?i.toDate():(v.debug("Invalid date:"+n),v.debug("With date format:"+e.trim()),new Date)},ue=function(t,e){if(null!==t)switch(t[2]){case"s":e.add(t[1],"seconds");break;case"m":e.add(t[1],"minutes");break;case"h":e.add(t[1],"hours");break;case"d":e.add(t[1],"days");break;case"w":e.add(t[1],"weeks")}return e.toDate()},ce=function(t,e,n,r){r=r||!1,n=n.trim();let i=d()(n,e.trim(),!0);return i.isValid()?(r&&i.add(1,"d"),i.toDate()):ue(/^([\d]+)([wdhms])/.exec(n.trim()),d()(t))};let fe=0;const le=function(t){return void 0===t?"task"+(fe+=1):t};let he,de,pe=[];const ge={},ye=function(t){const e=ge[t];return pe[e]},be=function(){const t=function(t){const e=pe[t];let n="";switch(pe[t].raw.startTime.type){case"prevTaskEnd":{const t=ye(e.prevTaskId);e.startTime=t.endTime;break}case"getStartDate":(n=se(0,Wt,pe[t].raw.startTime.startData))&&(pe[t].startTime=n)}return pe[t].startTime&&(pe[t].endTime=ce(pe[t].startTime,Wt,pe[t].raw.endTime.data,re),pe[t].endTime&&(pe[t].processed=!0,pe[t].manualEndTime=d()(pe[t].raw.endTime.data,"YYYY-MM-DD",!0).isValid(),oe(pe[t],Wt,Xt))),pe[t].processed};let e=!0;for(let n=0;n{window[e](...r)})}(t,e,n)})),me(t,"clickable")},setLink:function(t,e){let n=e;"loose"!==Gt.securityLevel&&(n=Object(T.sanitizeUrl)(e)),t.split(",").forEach((function(t){void 0!==ye(t)&&ve(t,()=>{window.open(n,"_self")})})),me(t,"clickable")},bindFunctions:function(t){ne.forEach((function(e){e(t)}))},durationToDate:ue};function we(t,e,n){let r=!0;for(;r;)r=!1,n.forEach((function(n){const i=new RegExp("^\\s*"+n+"\\s*$");t[0].match(i)&&(e[n]=!0,t.shift(1),r=!0)}))}Ht.parser.yy=_e;const xe={titleTopMargin:25,barHeight:20,barGap:4,topPadding:50,rightPadding:75,leftPadding:75,gridLineStartPadding:35,fontSize:11,fontFamily:'"Open-Sans", "sans-serif"'};let ke;var Ee=function(t){Object.keys(t).forEach((function(e){xe[e]=t[e]}))},Ae=function(t,e){Ht.parser.yy.clear(),Ht.parser.parse(t);const n=document.getElementById(e);void 0===(ke=n.parentElement.offsetWidth)&&(ke=1200),void 0!==xe.useWidth&&(ke=xe.useWidth);const r=Ht.parser.yy.getTasks(),i=r.length*(xe.barHeight+xe.barGap)+2*xe.topPadding;n.setAttribute("height","100%"),n.setAttribute("viewBox","0 0 "+ke+" "+i);const a=o.select(`[id="${e}"]`),s=o.scaleTime().domain([o.min(r,(function(t){return t.startTime})),o.max(r,(function(t){return t.endTime}))]).rangeRound([0,ke-xe.leftPadding-xe.rightPadding]);let u=[];for(let t=0;t0&&(e=t.classes.join(" "));let n=0;for(let e=0;en-e?n+o+1.5*xe.leftPadding>c?e+r-5:n+r+5:(n-e)/2+e+r})).attr("y",(function(t,r){return r*e+xe.barHeight/2+(xe.fontSize/2-2)+n})).attr("text-height",i).attr("class",(function(t){const e=s(t.startTime);let n=s(t.endTime);t.milestone&&(n=e+i);const r=this.getBBox().width;let o="";t.classes.length>0&&(o=t.classes.join(" "));let a=0;for(let e=0;en-e?n+r+1.5*xe.leftPadding>c?o+" taskTextOutsideLeft taskTextOutside"+a+" "+f:o+" taskTextOutsideRight taskTextOutside"+a+" "+f+" width-"+r:o+" taskText taskText"+a+" "+f+" width-"+r}))}(t,i,l,h,r,0,e),function(t,e){const n=[];let r=0;for(let t=0;t0))return i[1]*t/2+e;for(let a=0;a>")?n.annotations.push(t.substring(2,t.length-2)):t.endsWith(")")?n.methods.push(t):t&&n.members.push(t)}};var Re={addClass:Ce,clear:function(){Me=[],De={}},getClass:function(t){return De[t]},getClasses:function(){return De},addAnnotation:function(t,e){De[t].annotations.push(e)},getRelations:function(){return Me},addRelation:function(t){v.debug("Adding relation: "+JSON.stringify(t)),Ce(t.id1),Ce(t.id2),Me.push(t)},addMember:Oe,addMembers:function(t,e){Array.isArray(e)&&(e.reverse(),e.forEach(e=>Oe(t,e)))},cleanupLabel:function(t){return":"===t.substring(0,1)?t.substr(2).trim():t.trim()},lineType:{LINE:0,DOTTED_LINE:1},relationType:{AGGREGATION:0,EXTENSION:1,COMPOSITION:2,DEPENDENCY:3}},Ie=n(46),Ne=n.n(Ie);Ie.parser.yy=Re;let Be={},Le=0;const Pe={dividerMargin:10,padding:5,textHeight:10},Fe=function(t){const e=Object.keys(Be);for(let n=0;n "+t.w+": "+JSON.stringify(i.edge(t))),function(t,e,n){const r=function(t){switch(t){case Re.relationType.AGGREGATION:return"aggregation";case Re.relationType.EXTENSION:return"extension";case Re.relationType.COMPOSITION:return"composition";case Re.relationType.DEPENDENCY:return"dependency"}};e.points=e.points.filter(t=>!Number.isNaN(t.y));const i=e.points,a=o.line().x((function(t){return t.x})).y((function(t){return t.y})).curve(o.curveBasis),s=t.append("path").attr("d",a(i)).attr("id","edge"+qe).attr("class","relation");let u,c,f="";Pe.arrowMarkerAbsolute&&(f=(f=(f=window.location.protocol+"//"+window.location.host+window.location.pathname+window.location.search).replace(/\(/g,"\\(")).replace(/\)/g,"\\)")),"none"!==n.relation.type1&&s.attr("marker-start","url("+f+"#"+r(n.relation.type1)+"Start)"),"none"!==n.relation.type2&&s.attr("marker-end","url("+f+"#"+r(n.relation.type2)+"End)");const l=e.points.length;let h,d,p,g,y=E.calcLabelPosition(e.points);if(u=y.x,c=y.y,l%2!=0&&l>1){let t=E.calcCardinalityPosition("none"!==n.relation.type1,e.points,e.points[0]),r=E.calcCardinalityPosition("none"!==n.relation.type2,e.points,e.points[l-1]);v.debug("cardinality_1_point "+JSON.stringify(t)),v.debug("cardinality_2_point "+JSON.stringify(r)),h=t.x,d=t.y,p=r.x,g=r.y}if(void 0!==n.title){const e=t.append("g").attr("class","classLabel"),r=e.append("text").attr("class","label").attr("x",u).attr("y",c).attr("fill","red").attr("text-anchor","middle").text(n.title);window.label=r;const i=r.node().getBBox();e.insert("rect",":first-child").attr("class","box").attr("x",i.x-Pe.padding/2).attr("y",i.y-Pe.padding/2).attr("width",i.width+Pe.padding).attr("height",i.height+Pe.padding)}if(v.info("Rendering relation "+JSON.stringify(n)),void 0!==n.relationTitle1&&"none"!==n.relationTitle1){t.append("g").attr("class","cardinality").append("text").attr("class","type1").attr("x",h).attr("y",d).attr("fill","black").attr("font-size","6").text(n.relationTitle1)}if(void 0!==n.relationTitle2&&"none"!==n.relationTitle2){t.append("g").attr("class","cardinality").append("text").attr("class","type2").attr("x",p).attr("y",g).attr("fill","black").attr("font-size","6").text(n.relationTitle2)}qe++}(n,i.edge(t),i.edge(t).relation))})),n.attr("height","100%"),n.attr("width",`${1.5*i.graph().width+20}`),n.attr("viewBox","-10 -10 "+(i.graph().width+20)+" "+(i.graph().height+20))};let Ye=[];let Ve={root:{relations:[],states:{},documents:{}}},He=Ve.root,$e=0;const Ge=function(t,e,n,r,i){void 0===He.states[t]?He.states[t]={id:t,descriptions:[],type:e,doc:n,note:i}:(He.states[t].doc||(He.states[t].doc=n),He.states[t].type||(He.states[t].type=e)),r&&("string"==typeof r&&Xe(t,r.trim()),"object"==typeof r&&r.forEach(e=>Xe(t,e.trim()))),i&&(He.states[t].note=i)},We=function(){He=(Ve={root:{relations:[],states:{},documents:{}}}).root},Ke=function(t,e,n){let r=t,i=e,o="default",a="default";"[*]"===t&&(r="start"+ ++$e,o="start"),"[*]"===e&&(i="end"+$e,a="end"),Ge(r,o),Ge(i,a),He.relations.push({id1:r,id2:i,title:n})},Xe=function(t,e){const n=He.states[t];let r=e;":"===r[0]&&(r=r.substr(1).trim()),n.descriptions.push(r)};let Ze=0;var Je={addState:Ge,clear:We,getState:function(t){return He.states[t]},getStates:function(){return He.states},getRelations:function(){return He.relations},addRelation:Ke,getDividerId:()=>"divider-id-"+ ++Ze,cleanupLabel:function(t){return":"===t.substring(0,1)?t.substr(2).trim():t.trim()},lineType:{LINE:0,DOTTED_LINE:1},relationType:{AGGREGATION:0,EXTENSION:1,COMPOSITION:2,DEPENDENCY:3},logDocuments:function(){v.info("Documents = ",Ve)},getRootDoc:()=>Ye,setRootDoc:t=>{v.info("Setting root doc",t),Ye=t},extract:t=>{We(),t.forEach(t=>{"state"===t.stmt&&Ge(t.id,t.type,t.doc,t.description,t.note),"relation"===t.stmt&&Ke(t.state1.id,t.state2.id,t.description)})}},Qe=n(47),tn=n.n(Qe);const en={};var nn=(t,e)=>{en[t]=e};const rn=(t,e)=>{const n=t.append("text").attr("x",2*l().state.padding).attr("y",l().state.textHeight+1.5*l().state.padding).attr("font-size",l().state.fontSize).attr("class","state-title").text(e.descriptions[0]).node().getBBox(),r=n.height,i=t.append("text").attr("x",l().state.padding).attr("y",r+.2*l().state.padding+l().state.dividerMargin+l().state.textHeight).attr("class","state-description");let o=!0,a=!0;e.descriptions.forEach((function(t){o||(!function(t,e,n){const r=t.append("tspan").attr("x",2*l().state.padding).text(e);n||r.attr("dy",l().state.textHeight)}(i,t,a),a=!1),o=!1}));const s=t.append("line").attr("x1",l().state.padding).attr("y1",l().state.padding+r+l().state.dividerMargin/2).attr("y2",l().state.padding+r+l().state.dividerMargin/2).attr("class","descr-divider"),u=i.node().getBBox(),c=Math.max(u.width,n.width);return s.attr("x2",c+3*l().state.padding),t.insert("rect",":first-child").attr("x",l().state.padding).attr("y",l().state.padding).attr("width",c+2*l().state.padding).attr("height",u.height+r+2*l().state.padding).attr("rx",l().state.radius),t},on=(t,e)=>{const n=t.append("text").attr("x",2*l().state.padding).attr("y",l().state.titleShift).attr("font-size",l().state.fontSize).attr("class","state-title").text(e.id),r=n.node().getBBox(),i=1-l().state.textHeight,o=t.append("line").attr("x1",0).attr("y1",i).attr("y2",i).attr("class","descr-divider"),a=t.node().getBBox();return n.attr("x",a.width/2-r.width/2),o.attr("x2",a.width+l().state.padding),t.insert("rect",":first-child").attr("x",a.x).attr("y",i).attr("class","composit").attr("width",a.width+l().state.padding).attr("height",a.height+l().state.textHeight+l().state.titleShift+1).attr("rx","0"),t.insert("rect",":first-child").attr("x",a.x).attr("y",l().state.titleShift-l().state.textHeight-l().state.padding).attr("width",a.width+l().state.padding).attr("height",3*l().state.textHeight).attr("rx",l().state.radius),t.insert("rect",":first-child").attr("x",a.x).attr("y",l().state.titleShift-l().state.textHeight-l().state.padding).attr("width",a.width+l().state.padding).attr("height",a.height+3+2*l().state.textHeight).attr("rx",l().state.radius),t},an=(t,e)=>{e.attr("class","state-note");const n=e.append("rect").attr("x",0).attr("y",l().state.padding),r=e.append("g"),{textWidth:i,textHeight:o}=((t,e,n,r)=>{let i=0;const o=r.append("text");o.style("text-anchor","start"),o.attr("class","noteText");let a=t.replace(/\r\n/g,"
");const s=(a=a.replace(/\n/g,"
")).split(//gi);let u=1.25*l().state.noteMargin;for(const t of s){const r=t.trim();if(r.length>0){const t=o.append("tspan");if(t.text(r),0===u){u+=t.node().getBBox().height}i+=u,t.attr("x",e+l().state.noteMargin),t.attr("y",n+i+1.25*l().state.noteMargin)}}return{textWidth:o.node().getBBox().width,textHeight:i}})(t,0,0,r);return n.attr("height",o+2*l().state.noteMargin),n.attr("width",i+2*l().state.noteMargin),n},sn=function(t,e){const n=e.id,r={id:n,label:e.id,width:0,height:0},i=t.append("g").attr("id",n).attr("class","stateGroup");"start"===e.type&&(t=>t.append("circle").style("stroke","black").style("fill","black").attr("r",l().state.sizeUnit).attr("cx",l().state.padding+l().state.sizeUnit).attr("cy",l().state.padding+l().state.sizeUnit))(i),"end"===e.type&&(t=>(t.append("circle").style("stroke","black").style("fill","white").attr("r",l().state.sizeUnit+l().state.miniPadding).attr("cx",l().state.padding+l().state.sizeUnit+l().state.miniPadding).attr("cy",l().state.padding+l().state.sizeUnit+l().state.miniPadding),t.append("circle").style("stroke","black").style("fill","black").attr("r",l().state.sizeUnit).attr("cx",l().state.padding+l().state.sizeUnit+2).attr("cy",l().state.padding+l().state.sizeUnit+2)))(i),"fork"!==e.type&&"join"!==e.type||((t,e)=>{let n=l().state.forkWidth,r=l().state.forkHeight;if(e.parentId){let t=n;n=r,r=t}t.append("rect").style("stroke","black").style("fill","black").attr("width",n).attr("height",r).attr("x",l().state.padding).attr("y",l().state.padding)})(i,e),"note"===e.type&&an(e.note.text,i),"divider"===e.type&&(t=>t.append("line").style("stroke","grey").style("stroke-dasharray","3").attr("x1",l().state.textHeight).attr("class","divider").attr("x2",2*l().state.textHeight).attr("y1",0).attr("y2",0))(i),"default"===e.type&&0===e.descriptions.length&&((t,e)=>{const n=t.append("text").attr("x",2*l().state.padding).attr("y",l().state.textHeight+2*l().state.padding).attr("font-size",l().state.fontSize).attr("class","state-title").text(e.id),r=n.node().getBBox();t.insert("rect",":first-child").attr("x",l().state.padding).attr("y",l().state.padding).attr("width",r.width+2*l().state.padding).attr("height",r.height+2*l().state.padding).attr("rx",l().state.radius)})(i,e),"default"===e.type&&e.descriptions.length>0&&rn(i,e);const o=i.node().getBBox();return r.width=o.width+2*l().state.padding,r.height=o.height+2*l().state.padding,nn(n,r),r};let un=0;let cn;Qe.parser.yy=Je;const fn={},ln=t=>t?t.length*cn.fontSizeFactor:1,hn=t=>{if(!t)return 1;let e=t.replace(//gi,"#br#");return(e=e.replace(/\\n/g,"#br#")).split("#br#")},dn=(t,e,n)=>{const r=new S.a.Graph({compound:!0});n?r.setGraph({rankdir:"LR",compound:!0,ranker:"tight-tree",ranksep:cn.edgeLengthFactor}):r.setGraph({rankdir:"TB",compound:!0,ranksep:cn.edgeLengthFactor,ranker:"tight-tree"}),r.setDefaultEdgeLabel((function(){return{}})),Je.extract(t);const i=Je.getStates(),a=Je.getRelations(),s=Object.keys(i);for(let t=0;t{const e=t.parentElement;let n=0,r=0;e&&(e.parentElement&&(n=e.parentElement.getBBox().width),r=parseInt(e.getAttribute("data-x-shift"),10),Number.isNaN(r)&&(r=0)),t.setAttribute("x1",0-r),t.setAttribute("x2",n-r)})}else v.debug("No Node "+t+": "+JSON.stringify(r.node(t)))}));let c=u.getBBox();r.edges().forEach((function(t){void 0!==t&&void 0!==r.edge(t)&&(v.debug("Edge "+t.v+" -> "+t.w+": "+JSON.stringify(r.edge(t))),function(t,e,n){e.points=e.points.filter(t=>!Number.isNaN(t.y));const r=e.points,i=o.line().x((function(t){return t.x})).y((function(t){return t.y})).curve(o.curveBasis),a=t.append("path").attr("d",i(r)).attr("id","edge"+un).attr("class","transition");let s="";if(l().state.arrowMarkerAbsolute&&(s=(s=(s=window.location.protocol+"//"+window.location.host+window.location.pathname+window.location.search).replace(/\(/g,"\\(")).replace(/\)/g,"\\)")),a.attr("marker-end","url("+s+"#"+function(t){switch(t){case Je.relationType.AGGREGATION:return"aggregation";case Je.relationType.EXTENSION:return"extension";case Je.relationType.COMPOSITION:return"composition";case Je.relationType.DEPENDENCY:return"dependency"}}(Je.relationType.DEPENDENCY)+"End)"),void 0!==n.title){const r=t.append("g").attr("class","stateLabel"),{x:i,y:o}=E.calcLabelPosition(e.points),a=(t=>{let e=t.replace(//gi,"#br#");return(e=e.replace(/\\n/g,"#br#")).split("#br#")})(n.title);let s=0;const u=[];for(let t=0;t<=a.length;t++){const e=r.append("text").attr("text-anchor","middle").text(a[t]).attr("x",i).attr("y",o+s);if(0===s){const t=e.node().getBBox();s=t.height}u.push(e)}if(a.length>1){const t=a.length*s*.25;u.forEach((e,n)=>e.attr("y",o+n*s-t))}const c=r.node().getBBox();r.insert("rect",":first-child").attr("class","box").attr("x",c.x-l().state.padding/2).attr("y",c.y-l().state.padding/2).attr("width",c.width+l().state.padding).attr("height",c.height+l().state.padding)}un++}(e,r.edge(t),r.edge(t).relation))})),c=u.getBBox();const f={id:n||"root",label:n||"root",width:0,height:0};return f.width=c.width+2*cn.padding,f.height=c.height+2*cn.padding,v.info("Doc rendered",f,r),f};var pn=function(){},gn=function(t,e){cn=l().state,Qe.parser.yy.clear(),Qe.parser.parse(t),v.debug("Rendering diagram "+t);const n=o.select(`[id='${e}']`);n.append("defs").append("marker").attr("id","dependencyEnd").attr("refX",19).attr("refY",7).attr("markerWidth",20).attr("markerHeight",28).attr("orient","auto").append("path").attr("d","M 19,7 L9,13 L14,7 L9,1 Z"),new S.a.Graph({multigraph:!1,compound:!0,rankdir:"RL"}).setDefaultEdgeLabel((function(){return{}}));const r=Je.getRootDoc();dn(r,n);const i=cn.padding,a=n.node().getBBox(),s=a.width+2*i,u=a.height+2*i;n.attr("width",2*s),n.attr("viewBox",`${a.x-cn.padding} ${a.y-cn.padding} `+s+" "+u)},yn=n(48),bn=n.n(yn),mn=n(228),vn=n.n(mn);let _n={},wn=null,xn={master:wn},kn="master",En="LR",An=0;function Sn(){return vn()({length:7,characters:"0123456789abcdef"})}function Tn(t,e){for(v.debug("Entering isfastforwardable:",t.id,e.id);t.seq<=e.seq&&t!==e&&null!=e.parent;){if(Array.isArray(e.parent))return v.debug("In merge commit:",e.parent),Tn(t,_n[e.parent[0]])||Tn(t,_n[e.parent[1]]);e=_n[e.parent]}return v.debug(t.id,e.id),t.id===e.id}let Mn={};function Dn(t,e,n){const r=t.indexOf(e);-1===r?t.push(n):t.splice(r,1,n)}const Cn=function(){const t=Object.keys(_n).map((function(t){return _n[t]}));return t.forEach((function(t){v.debug(t.id)})),bn.a.orderBy(t,["seq"],["desc"])};var On={setDirection:function(t){En=t},setOptions:function(t){v.debug("options str",t),t=(t=t&&t.trim())||"{}";try{Mn=JSON.parse(t)}catch(t){v.error("error while parsing gitGraph options",t.message)}},getOptions:function(){return Mn},commit:function(t){const e={id:Sn(),message:t,seq:An++,parent:null==wn?null:wn.id};wn=e,_n[e.id]=e,xn[kn]=e.id,v.debug("in pushCommit "+e.id)},branch:function(t){xn[t]=null!=wn?wn.id:null,v.debug("in createBranch")},merge:function(t){const e=_n[xn[kn]],n=_n[xn[t]];if(function(t,e){return t.seq>e.seq&&Tn(e,t)}(e,n))v.debug("Already merged");else{if(Tn(e,n))xn[kn]=xn[t],wn=_n[xn[kn]];else{const e={id:Sn(),message:"merged branch "+t+" into "+kn,seq:An++,parent:[null==wn?null:wn.id,xn[t]]};wn=e,_n[e.id]=e,xn[kn]=e.id}v.debug(xn),v.debug("in mergeBranch")}},checkout:function(t){v.debug("in checkout");const e=xn[kn=t];wn=_n[e]},reset:function(t){v.debug("in reset",t);const e=t.split(":")[0];let n=parseInt(t.split(":")[1]),r="HEAD"===e?wn:_n[xn[e]];for(v.debug(r,n);n>0;)if(n--,!(r=_n[r.parent])){const t="Critical error - unique parent commit not found during reset";throw v.error(t),t}wn=r,xn[kn]=r.id},prettyPrint:function(){v.debug(_n),function t(e){const n=bn.a.maxBy(e,"seq");let r="";e.forEach((function(t){r+=t===n?"\t*":"\t|"}));const i=[r,n.id,n.seq];for(let t in xn)xn[t]===n.id&&i.push(t);if(v.debug(i.join(" ")),Array.isArray(n.parent)){const t=_n[n.parent[0]];Dn(e,n,t),e.push(_n[n.parent[1]])}else{if(null==n.parent)return;{const t=_n[n.parent];Dn(e,n,t)}}t(e=bn.a.uniqBy(e,"id"))}([Cn()[0]])},clear:function(){_n={},xn={master:wn=null},kn="master",An=0},getBranchesAsObjArray:function(){const t=[];for(let e in xn)t.push({name:e,commit:_n[xn[e]]});return t},getBranches:function(){return xn},getCommits:function(){return _n},getCommitsArray:Cn,getCurrentBranch:function(){return kn},getDirection:function(){return En},getHead:function(){return wn}},Rn=n(85),In=n.n(Rn);let Nn,Bn={},Ln={nodeSpacing:150,nodeFillColor:"yellow",nodeStrokeWidth:2,nodeStrokeColor:"grey",lineStrokeWidth:4,branchOffset:50,lineColor:"grey",leftMargin:50,branchColors:["#442f74","#983351","#609732","#AA9A39"],nodeRadius:10,nodeLabel:{width:75,height:100,x:-25,y:0}},Pn={};function Fn(t,e,n,r){const i=x(r,o.curveBasis),a=Ln.branchColors[n%Ln.branchColors.length],s=o.line().x((function(t){return Math.round(t.x)})).y((function(t){return Math.round(t.y)})).curve(i);t.append("svg:path").attr("d",s(e)).style("stroke",a).style("stroke-width",Ln.lineStrokeWidth).style("fill","none")}function qn(t,e){e=e||t.node().getBBox();const n=t.node().getCTM();return{left:n.e+e.x*n.a,top:n.f+e.y*n.d,width:e.width,height:e.height}}function jn(t,e,n,r,i){v.debug("svgDrawLineForCommits: ",e,n);const o=qn(t.select("#node-"+e+" circle")),a=qn(t.select("#node-"+n+" circle"));switch(r){case"LR":if(o.left-a.left>Ln.nodeSpacing){const e={x:o.left-Ln.nodeSpacing,y:a.top+a.height/2};Fn(t,[e,{x:a.left+a.width,y:a.top+a.height/2}],i,"linear"),Fn(t,[{x:o.left,y:o.top+o.height/2},{x:o.left-Ln.nodeSpacing/2,y:o.top+o.height/2},{x:o.left-Ln.nodeSpacing/2,y:e.y},e],i)}else Fn(t,[{x:o.left,y:o.top+o.height/2},{x:o.left-Ln.nodeSpacing/2,y:o.top+o.height/2},{x:o.left-Ln.nodeSpacing/2,y:a.top+a.height/2},{x:a.left+a.width,y:a.top+a.height/2}],i);break;case"BT":if(a.top-o.top>Ln.nodeSpacing){const e={x:a.left+a.width/2,y:o.top+o.height+Ln.nodeSpacing};Fn(t,[e,{x:a.left+a.width/2,y:a.top}],i,"linear"),Fn(t,[{x:o.left+o.width/2,y:o.top+o.height},{x:o.left+o.width/2,y:o.top+o.height+Ln.nodeSpacing/2},{x:a.left+a.width/2,y:e.y-Ln.nodeSpacing/2},e],i)}else Fn(t,[{x:o.left+o.width/2,y:o.top+o.height},{x:o.left+o.width/2,y:o.top+Ln.nodeSpacing/2},{x:a.left+a.width/2,y:a.top-Ln.nodeSpacing/2},{x:a.left+a.width/2,y:a.top}],i)}}function Un(t,e){return t.select(e).node().cloneNode(!0)}function zn(t,e,n,r){let i;const o=Object.keys(Bn).length;if("string"==typeof e)do{if(i=Bn[e],v.debug("in renderCommitHistory",i.id,i.seq),t.select("#node-"+e).size()>0)return;let a;t.append((function(){return Un(t,"#def-commit")})).attr("class","commit").attr("id",(function(){return"node-"+i.id})).attr("transform",(function(){switch(r){case"LR":return"translate("+(i.seq*Ln.nodeSpacing+Ln.leftMargin)+", "+Nn*Ln.branchOffset+")";case"BT":return"translate("+(Nn*Ln.branchOffset+Ln.leftMargin)+", "+(o-i.seq)*Ln.nodeSpacing+")"}})).attr("fill",Ln.nodeFillColor).attr("stroke",Ln.nodeStrokeColor).attr("stroke-width",Ln.nodeStrokeWidth);for(let t in n)if(n[t].commit===i){a=n[t];break}a&&(v.debug("found branch ",a.name),t.select("#node-"+i.id+" p").append("xhtml:span").attr("class","branch-label").text(a.name+", ")),t.select("#node-"+i.id+" p").append("xhtml:span").attr("class","commit-id").text(i.id),""!==i.message&&"BT"===r&&t.select("#node-"+i.id+" p").append("xhtml:span").attr("class","commit-msg").text(", "+i.message),e=i.parent}while(e&&Bn[e]);Array.isArray(e)&&(v.debug("found merge commmit",e),zn(t,e[0],n,r),Nn++,zn(t,e[1],n,r),Nn--)}function Yn(t,e,n,r){for(r=r||0;e.seq>0&&!e.lineDrawn;)"string"==typeof e.parent?(jn(t,e.id,e.parent,n,r),e.lineDrawn=!0,e=Bn[e.parent]):Array.isArray(e.parent)&&(jn(t,e.id,e.parent[0],n,r),jn(t,e.id,e.parent[1],n,r+1),Yn(t,Bn[e.parent[1]],n,r+1),e.lineDrawn=!0,e=Bn[e.parent[0]])}var Vn=function(t){Pn=t},Hn=function(t,e,n){try{const r=In.a.parser;r.yy=On,v.debug("in gitgraph renderer",t+"\n","id:",e,n),r.parse(t+"\n"),Ln=bn.a.assign(Ln,Pn,On.getOptions()),v.debug("effective options",Ln);const i=On.getDirection();Bn=On.getCommits();const a=On.getBranchesAsObjArray();"BT"===i&&(Ln.nodeLabel.x=a.length*Ln.branchOffset,Ln.nodeLabel.width="100%",Ln.nodeLabel.y=-2*Ln.nodeRadius);const s=o.select(`[id="${e}"]`);!function(t){t.append("defs").append("g").attr("id","def-commit").append("circle").attr("r",Ln.nodeRadius).attr("cx",0).attr("cy",0),t.select("#def-commit").append("foreignObject").attr("width",Ln.nodeLabel.width).attr("height",Ln.nodeLabel.height).attr("x",Ln.nodeLabel.x).attr("y",Ln.nodeLabel.y).attr("class","node-label").attr("requiredFeatures","http://www.w3.org/TR/SVG11/feature#Extensibility").append("p").html("")}(s),Nn=1;for(let t in a){const e=a[t];zn(s,e.commit.id,a,i),Yn(s,e.commit,i),Nn++}s.attr("height",(function(){return"BT"===i?Object.keys(Bn).length*Ln.nodeSpacing:(a.length+1)*Ln.branchOffset}))}catch(t){v.error("Error while rendering gitgraph"),v.error(t.message)}},$n="",Gn=!1;var Wn={setMessage:t=>{v.debug("Setting message to: "+t),$n=t},getMessage:()=>$n,setInfo:t=>{Gn=t},getInfo:()=>Gn},Kn=n(86),Xn=n.n(Kn);const Zn={};var Jn=function(t){Object.keys(t).forEach((function(e){Zn[e]=t[e]}))},Qn=(t,e,n)=>{try{const r=Xn.a.parser;r.yy=Wn,v.debug("Renering info diagram\n"+t),r.parse(t),v.debug("Parsed info diagram");const i=o.select("#"+e);i.append("g").append("text").attr("x",100).attr("y",40).attr("class","version").attr("font-size","32px").style("text-anchor","middle").text("v "+n),i.attr("height",100),i.attr("width",400)}catch(t){v.error("Error while rendering info diagram"),v.error(t.message)}};let tr={},er="";var nr={addSection:function(t,e){void 0===tr[t]&&(tr[t]=e,v.debug("Added new section :",t))},getSections:()=>tr,cleanupValue:function(t){return":"===t.substring(0,1)?(t=t.substring(1).trim(),Number(t.trim())):Number(t.trim())},clear:function(){tr={},er=""},setTitle:function(t){er=t},getTitle:function(){return er}},rr=n(87),ir=n.n(rr);const or={};let ar;var sr=function(t){Object.keys(t).forEach((function(e){or[e]=t[e]}))},ur=(t,e)=>{try{const h=ir.a.parser;h.yy=nr,v.debug("Rendering info diagram\n"+t),h.yy.clear(),h.parse(t),v.debug("Parsed info diagram");const d=document.getElementById(e);void 0===(ar=d.parentElement.offsetWidth)&&(ar=1200),void 0!==or.useWidth&&(ar=or.useWidth);const p=450;d.setAttribute("height","100%"),d.setAttribute("viewBox","0 0 "+ar+" "+p);var n=ar,r=Math.min(n,450)/2-40,i=o.select("#"+e).append("svg").attr("width",n).attr("height",450).append("g").attr("transform","translate("+n/2+",225)"),a=nr.getSections(),s=0;Object.keys(a).forEach((function(t){s+=a[t]})),v.info(a);var u=o.scaleOrdinal().domain(a).range(o.schemeSet2),c=o.pie().value((function(t){return t.value}))(o.entries(a)),f=o.arc().innerRadius(0).outerRadius(r);i.selectAll("mySlices").data(c).enter().append("path").attr("d",f).attr("fill",(function(t){return u(t.data.key)})).attr("stroke","black").style("stroke-width","2px").style("opacity",.7),i.selectAll("mySlices").data(c).enter().append("text").text((function(t){return(t.data.value/s*100).toFixed(0)+"%"})).attr("transform",(function(t){return"translate("+f.centroid(t)+")"})).style("text-anchor","middle").attr("class","slice").style("font-size",17),i.append("text").text(h.yy.getTitle()).attr("x",0).attr("y",-(p-50)/2).attr("class","pieTitleText");var l=i.selectAll(".legend").data(u.domain()).enter().append("g").attr("class","legend").attr("transform",(function(t,e){return"translate(216,"+(22*e-22*u.domain().length/2)+")"}));l.append("rect").attr("width",18).attr("height",18).style("fill",u).style("stroke",u),l.append("text").attr("x",22).attr("y",14).text((function(t){return t}))}catch(t){v.error("Error while rendering info diagram"),v.error(t.message)}};const cr={};for(const t of["default","forest","dark","neutral"])cr[t]=n(500)(`./${t}/index.scss`);const fr={theme:"default",themeCSS:void 0,fontFamily:'"trebuchet ms", verdana, arial;',logLevel:5,securityLevel:"strict",startOnLoad:!0,arrowMarkerAbsolute:!1,flowchart:{htmlLabels:!0,curve:"linear"},sequence:{diagramMarginX:50,diagramMarginY:10,actorMargin:50,width:150,height:65,boxMargin:10,boxTextMargin:5,noteMargin:10,messageMargin:35,mirrorActors:!0,bottomMarginAdj:1,useMaxWidth:!0,rightAngles:!1,showSequenceNumbers:!1},gantt:{titleTopMargin:25,barHeight:20,barGap:4,topPadding:50,leftPadding:75,gridLineStartPadding:35,fontSize:11,fontFamily:'"Open-Sans", "sans-serif"',numberSectionStyles:4,axisFormat:"%Y-%m-%d"},class:{},git:{},state:{dividerMargin:10,sizeUnit:5,padding:8,textHeight:10,titleShift:-15,noteMargin:10,forkWidth:70,forkHeight:7,miniPadding:2,fontSizeFactor:5.02,fontSize:24,labelHeight:16,edgeLengthFactor:"20",compositTitleSize:35,radius:5}};_(fr.logLevel),f(fr);const lr=function(t){const e=Object.keys(t);for(let n=0;n * { ${t[e].styles.join(" !important; ")} !important; }`}const h=document.createElement("style");h.innerHTML=s()(l,`#${t}`),c.insertBefore(h,f);const d=document.createElement("style"),p=window.getComputedStyle(c);switch(d.innerHTML=`#${t} {\n color: ${p.color};\n font: ${p.font};\n }`,c.insertBefore(d,f),a){case"git":fr.flowchart.arrowMarkerAbsolute=fr.arrowMarkerAbsolute,Vn(fr.git),Hn(e,t,!1);break;case"flowchart":fr.flowchart.arrowMarkerAbsolute=fr.arrowMarkerAbsolute,dt(fr.flowchart),gt(e,t,!1);break;case"sequence":fr.sequence.arrowMarkerAbsolute=fr.arrowMarkerAbsolute,fr.sequenceDiagram?(Yt(Object.assign(fr.sequence,fr.sequenceDiagram)),console.error("`mermaid config.sequenceDiagram` has been renamed to `config.sequence`. Please update your mermaid config.")):Yt(fr.sequence),Vt(e,t);break;case"gantt":fr.gantt.arrowMarkerAbsolute=fr.arrowMarkerAbsolute,Ee(fr.gantt),Ae(e,t);break;case"class":fr.class.arrowMarkerAbsolute=fr.arrowMarkerAbsolute,Ue(fr.class),ze(e,t);break;case"state":pn(fr.state),gn(e,t);break;case"info":fr.class.arrowMarkerAbsolute=fr.arrowMarkerAbsolute,Jn(fr.class),Qn(e,t,u.version);break;case"pie":fr.class.arrowMarkerAbsolute=fr.arrowMarkerAbsolute,sr(fr.class),ur(e,t,u.version)}o.select(`[id="${t}"]`).selectAll("foreignobject > *").attr("xmlns","http://www.w3.org/1999/xhtml");let g=o.select("#d"+t).node().innerHTML;if(fr.arrowMarkerAbsolute&&"false"!==fr.arrowMarkerAbsolute||(g=g.replace(/marker-end="url\(.*?#/g,'marker-end="url(#',"g")),g=function(t){let e=t;return e=(e=(e=e.replace(/fl°°/g,(function(){return"&#"}))).replace(/fl°/g,(function(){return"&"}))).replace(/¶ß/g,(function(){return";"}))}(g),void 0!==n)switch(a){case"flowchart":n(g,G.bindFunctions);break;case"gantt":n(g,_e.bindFunctions);break;default:n(g)}else v.debug("CB = undefined!");const y=o.select("#d"+t).node();return null!==y&&"function"==typeof y.remove&&o.select("#d"+t).node().remove(),g},parse:function(t){const e=E.detectType(t);let n;switch(v.debug("Type "+e),e){case"git":(n=In.a).parser.yy=On;break;case"flowchart":G.clear(),(n=K.a).parser.yy=G;break;case"sequence":(n=At.a).parser.yy=Lt;break;case"gantt":(n=$t.a).parser.yy=_e;break;case"class":(n=Ne.a).parser.yy=Re;break;case"state":(n=tn.a).parser.yy=Je;break;case"info":v.debug("info info info"),(n=Xn.a).parser.yy=Wn;break;case"pie":v.debug("pie"),(n=ir.a).parser.yy=nr}n.parser.yy.parseError=(t,e)=>{throw{str:t,hash:e}},n.parse(t)},initialize:function(t){v.debug("Initializing mermaidAPI ",u.version),"object"==typeof t&&lr(t),f(fr),_(fr.logLevel)},getConfig:l};const dr=function(){let t;pr.startOnLoad?(t=hr.getConfig()).startOnLoad&&pr.init():void 0===pr.startOnLoad&&(v.debug("In start, no config"),(t=hr.getConfig()).startOnLoad&&pr.init())};"undefined"!=typeof document&& +/*! + * Wait for document loaded before starting the execution + */ +window.addEventListener("load",(function(){dr()}),!1);const pr={startOnLoad:!0,htmlLabels:!0,mermaidAPI:hr,parse:hr.parse,render:hr.render,init:function(){const t=hr.getConfig();let e,n,r;v.debug("Starting rendering diagrams"),arguments.length>=2?( +/*! sequence config was passed as #1 */ +void 0!==arguments[0]&&(pr.sequenceConfig=arguments[0]),e=arguments[1]):e=arguments[0],"function"==typeof arguments[arguments.length-1]?(n=arguments[arguments.length-1],v.debug("Callback function found")):void 0!==t.mermaid&&("function"==typeof t.mermaid.callback?(n=t.mermaid.callback,v.debug("Callback function found")):v.debug("No Callback function found")),e=void 0===e?document.querySelectorAll(".mermaid"):"string"==typeof e?document.querySelectorAll(e):e instanceof window.Node?[e]:e,v.debug("Start On Load before: "+pr.startOnLoad),void 0!==pr.startOnLoad&&(v.debug("Start On Load inner: "+pr.startOnLoad),hr.initialize({startOnLoad:pr.startOnLoad})),void 0!==pr.ganttConfig&&hr.initialize({gantt:pr.ganttConfig});for(let t=0;t/gi,"
"),hr.render(a,r,(t,e)=>{o.innerHTML=t,void 0!==n&&n(a),e&&e(o)},o)}},initialize:function(t){v.debug("Initializing mermaid "),void 0!==t.mermaid&&(void 0!==t.mermaid.startOnLoad&&(pr.startOnLoad=t.mermaid.startOnLoad),void 0!==t.mermaid.htmlLabels&&(pr.htmlLabels=t.mermaid.htmlLabels)),hr.initialize(t)},contentLoaded:dr};e.default=pr}]).default})); +//# sourceMappingURL=mermaid.min.js.map \ No newline at end of file diff --git a/shared/tab_footer.md b/shared/tab_footer.md new file mode 100644 index 0000000000..bdd6bc9687 --- /dev/null +++ b/shared/tab_footer.md @@ -0,0 +1,2 @@ + + diff --git a/shared/tab_header.md b/shared/tab_header.md new file mode 100644 index 0000000000..3adf4ed984 --- /dev/null +++ b/shared/tab_header.md @@ -0,0 +1,3 @@ +
+
+
diff --git a/shared/tabs.js b/shared/tabs.js new file mode 100644 index 0000000000..a25254ed33 --- /dev/null +++ b/shared/tabs.js @@ -0,0 +1,97 @@ +/** + * Returns true if browser supports HTML5 localStorage. + */ +function supportsHTML5Storage() { + try { + return 'localStorage' in window && window['localStorage'] !== null; + } catch (e) { + return false; + } +} + +/** + * Event handler for when a tab is clicked. + */ +function onClickTab(event) { + let target = event.currentTarget; + let language = target.dataset.lang; + + const initialTargetOffset = target.offsetTop; + const initialScrollPosition = window.scrollY; + switchAllTabs(language); + + // Keep initial perceived scroll position after resizing + // that may happen due to activation of multiple tabs in the same page. + const finalTargetOffset = target.offsetTop; + window.scrollTo({ + top: initialScrollPosition + (finalTargetOffset - initialTargetOffset) + }); +} + +/** + * Switches the displayed tab for the given container. + * + * :param container: The div containing both the tab bar and the individual tabs + * as direct children. + * :param language: The language to switch to. + */ +function switchTab(container, language) { + const previouslyActiveTab = container.querySelector(".tabcontents .active"); + previouslyActiveTab && previouslyActiveTab.classList.remove("active"); + let tab = container.querySelector(`.tabcontents [data-lang="${language}"]`); + tab && tab.classList.add("active"); + + const previouslyActiveButton = container.querySelector(".tabbar .active"); + previouslyActiveButton && previouslyActiveButton.classList.remove("active"); + let button = container.querySelector(`.tabbar [data-lang="${language}"]`); + button && button.classList.add("active"); +} + +/** + * Switches all tabs on the page to the given language. + * + * :param language: The language to switch to. + */ +function switchAllTabs(language) { + const containers = document.getElementsByClassName("tabs"); + for (let i = 0; i < containers.length; ++i) { + switchTab(containers[i], language); + } + + if (supportsHTML5Storage()) { + localStorage.setItem("glean-preferred-language", language); + } +} + +/** + * Opens all tabs on the page to the given language on page load. + */ +function openTabs() { + if (!supportsHTML5Storage()) { + return; + } + + let containers = document.getElementsByClassName("tabs"); + for (let i = 0; i < containers.length; ++i) { + // Create tabs for every language that has content + let tabs = containers[i].children[0]; + let tabcontents = containers[i].children[1]; + for (let tabcontent of tabcontents.children) { + let button = document.createElement("button"); + button.dataset.lang = tabcontent.dataset.lang; + button.className = "tablinks"; + button.onclick = onClickTab; + button.innerText = tabcontent.dataset.lang; + tabs.appendChild(button); + } + } + + var language = localStorage.getItem("glean-preferred-language"); + if (language == null) { + language = "Kotlin"; + } + + switchAllTabs(language); +} + +openTabs()